diff --git a/.doc/contributing-guide/2021.contributing-stickers-side-by-side.png b/.doc/contributing-guide/2021.contributing-stickers-side-by-side.png new file mode 100644 index 0000000000..d63f1f72a6 Binary files /dev/null and b/.doc/contributing-guide/2021.contributing-stickers-side-by-side.png differ diff --git a/.doc/contributing-guide/2022.contributing-stickers-side-by-side.png b/.doc/contributing-guide/2022.contributing-stickers-side-by-side.png new file mode 100644 index 0000000000..adaea4d86e Binary files /dev/null and b/.doc/contributing-guide/2022.contributing-stickers-side-by-side.png differ diff --git a/.doc/contributing-guide/contributing-stickers-side-by-side.png b/.doc/contributing-guide/contributing-stickers-side-by-side.png deleted file mode 100644 index 8bc8cc2082..0000000000 Binary files a/.doc/contributing-guide/contributing-stickers-side-by-side.png and /dev/null differ diff --git a/.doc/itop-version-history.md b/.doc/itop-version-history.md new file mode 100644 index 0000000000..32ee32dbf4 --- /dev/null +++ b/.doc/itop-version-history.md @@ -0,0 +1,61 @@ +# iTop version history + +```mermaid +%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true,'mainBranchName': 'develop','rotateCommitLabel': true}} }%% +gitGraph + commit id: "2016-07-06" tag: "2.3.0" + branch support/2.3 order: 900 + commit id: "2016-07-08" tag: "2.3.1" + commit id: "2016-12-22" tag: "2.3.3" + commit id: "2017-04-14" tag: "2.3.4" + checkout develop + commit id: "2017-07-12" tag: "2.4.0-beta" type: REVERSE + commit id: "2017-11-16" tag: "2.4.0" + branch support/2.4 order: 890 + commit id: "2018-02-14" tag: "2.4.1" + checkout develop + commit id: "2018-04-25" tag: "2.5.0-beta" type: REVERSE + checkout support/2.4 + commit id: "2018-06-14" tag: "2.4.2" + checkout develop + commit id: "2018-06-27" tag: "2.5.0" + branch support/2.5 order: 880 + checkout develop + commit id: "2019-01-09" tag: "2.6.0" + branch support/2.6 order: 870 + commit id: "2019-03-28" tag: "2.6.1" + checkout develop + commit id: "2019-12-18" tag: "2.7.0-beta" type: REVERSE + checkout support/2.5 + commit id: "2020-01-22" tag: "2.5.4" + checkout support/2.6 + commit id: "2020-01-23" tag: "2.6.3" + checkout develop + commit id: "2020-01-29" tag: "2.7.0-beta2" type: REVERSE + branch support/2.7 order: 860 + commit id: "2020-04-01" tag: "2.7.0-1" + checkout support/2.6 + commit id: "2020-04-22" tag: "2.6.4" + checkout support/2.7 + commit id: "2020-06-26" tag: "2.7.1" + checkout support/2.7 + commit id: "2020-12-09" tag: "2.7.3" + commit id: "2021-03-31" tag: "2.7.4" + checkout develop + commit id: "2021-04-06" tag: "3.0.0-beta" type: REVERSE + checkout support/2.7 + commit id: "2021-07-05" tag: "2.7.5" + checkout develop + commit id: "2021-07-05." tag: "3.0.0-beta2" type: REVERSE + checkout support/2.7 + commit id: "2021-12-17" tag: "2.7.6" + checkout develop + commit id: "2022-01-04" tag: "3.0.0" + branch support/3.0 order: 850 + commit id: "2022-04-08" tag: "3.0.1" + checkout support/2.7 + commit id: "2022-07-11" tag: "2.7.7" + checkout support/3.0 + commit id: "2022-09-12" tag: "3.0.2-1" + checkout develop +``` diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..690f3fdbc1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,48 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.bash text eol=lf +*.bat text eol=lf +*.cmd text eol=lf +*.css text eol=lf +*.scss text eol=lf +*.dist text eol=lf +.editorconfig text eol=lf +.env* text eol=lf +.gitignore text eol=lf +.htaccess text eol=lf +*.htm text eol=lf +*.html text eol=lf +*.ini text eol=lf +*.js text eol=lf +*.json text eol=lf +*.lock text eol=lf +*.md text eol=lf +*.php text eol=lf +*.php_cs text eol=lf +*.php8 text eol=lf +*.plex text eol=lf +*.sh text eol=lf +*.svg text eol=lf +*.ts text eol=lf +*.twig text eol=lf +*.txt text eol=lf +*.xml text eol=lf +*.xsd text eol=lf +*.yaml text eol=lf +*.yml text eol=lf + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpeg binary +*.jpg binary +*.gif binary +*.ico binary +*.pdf binary +*.swf binary +*.zip binary +*.ttf binary +*.woff binary +*.woff2 binary diff --git a/.gitignore b/.gitignore index 4c314c12db..9029eaca76 100644 --- a/.gitignore +++ b/.gitignore @@ -45,9 +45,13 @@ test/vendor/* !/log/index.php !/log/web.config +# PHPUnit cache file +/test/.phpunit.result.cache + # Jetbrains /.idea/** +!/.idea/IntelliLang.xml # doc. generation /.doc/vendor diff --git a/.idea/IntelliLang.xml b/.idea/IntelliLang.xml new file mode 100644 index 0000000000..2703c6f182 --- /dev/null +++ b/.idea/IntelliLang.xml @@ -0,0 +1,15 @@ + + + + + iTop - Class method code + + name(..) = 'method' and count(/itop_design) = 1 + + + iTop - Snippet code + + name(..) = 'snippets' and count(/itop_design) = 1 + + + \ No newline at end of file diff --git a/.make/composer/listOutdated.php b/.make/composer/listOutdated.php index 4c47bfa81e..84ab90d740 100644 --- a/.make/composer/listOutdated.php +++ b/.make/composer/listOutdated.php @@ -19,16 +19,23 @@ * */ -$iTopFolder = __DIR__ . "/../../" ; +/** + * Alias for `composer show -loD` + * You can also use `composer outdated -D` + * + * @link https://getcomposer.org/doc/03-cli.md#show + */ -require_once ("$iTopFolder/approot.inc.php"); +$iTopFolder = __DIR__."/../../"; + +require_once("$iTopFolder/approot.inc.php"); $sApproot = APPROOT; $aTrace = array(); $aParamsConfig = array( 'composer-path' => array( - 'default' => 'composer.phar', - ) + 'default' => 'composer', + ), ); $aParamsConfigNotFound = array_flip(array_keys($aParamsConfig)); $aGivenArgs = $argv; diff --git a/.make/composer/rmDeniedTestDir.php b/.make/composer/rmDeniedTestDir.php index 6a5f7837ce..a7f0072c52 100644 --- a/.make/composer/rmDeniedTestDir.php +++ b/.make/composer/rmDeniedTestDir.php @@ -50,14 +50,24 @@ foreach ($aDeniedButStillPresent as $sDir) continue; } - try - { + try { SetupUtils::rrmdir($sDir); echo "OK Remove denied test dir: '$sDir'\n"; } - catch (\Exception $e) - { + catch (\Exception $e) { echo "\nFAILED to remove denied test dir: '$sDir'\n"; } +} + +$aAllowedAndDeniedDirs = array_merge( + $oiTopComposer->ListAllowedTestDir(), + $oiTopComposer->ListDeniedTestDir() +); +$aExistingDirs = $oiTopComposer->ListAllTestDir(); +$aMissing = array_diff($aExistingDirs, $aAllowedAndDeniedDirs); +if (false === empty($aMissing)) { + echo "Some new tests dirs exists !\n" + .' They must be declared either in the allowed or denied list in '.iTopComposer::class." (see N°2651).\n" + .' List of dirs:'."\n".var_export($aMissing, true); } \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d03f2bdb9..32c75ebccc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,11 +27,14 @@ If you have an idea you're sure would benefit to all of iTop users, you may [create a corresponding ticket](https://sourceforge.net/p/itop/tickets/new/) to submit it, but be warned that there are lots of good reasons to refuse such changes. -### 📄 License -iTop is distributed under the AGPL-3.0 license (see the [license.txt] file), -your code must comply with this license. +### 📄 License and copyright +iTop is distributed under the AGPL-3.0 license (see the [license.txt] file). -If you want to use another license, you may [create an extension][wiki new ext]. +The iTop repository is divided in three parts: iTop (mainly PHP/JS/XML sources and dictionaries), images, and third-party libraries. +Combodo has the copyright on most of the source files in the iTop part of the repository: please do not modify the existing file copyrights. +Anyhow, you are encouraged to signal your contribution by the mean of `@author` annotations. + +If you want to use another license or keep the code ownership (copyright), you may [create an extension][wiki new ext]. [license.txt]: https://github.com/Combodo/iTop/blob/develop/license.txt [wiki new ext]: https://www.itophub.io/wiki/page?id=latest%3Acustomization%3Astart#by_writing_your_own_extension @@ -52,26 +55,26 @@ Here are the branches we use and their meaning : For example, if no version is currently prepared for shipping we could have: -- `develop` containing future 3.0.0 version +- `develop` containing future 3.1.0 version +- `support/3.0`: 3.0.x maintenance version - `support/2.7`: 2.7.x maintenance version - `support/2.6`: 2.6.x maintenance version -- `support/2.5`: 2.5.x maintenance version -In this example, when 3.0.0-beta is shipped that will become: +In this example, when 3.1.0-beta is shipped that will become: -- `develop`: future 3.1.0 version -- `release/3.0.0`: 3.0.0-beta +- `develop`: future 3.2.0 version +- `release/3.1.0`: 3.1.0-beta +- `support/3.0`: 3.0.x maintenance version - `support/2.7`: 2.7.x maintenance version - `support/2.6`: 2.6.x maintenance version -- `support/2.5`: 2.5.x maintenance version -And when 3.0.0 final will be out: +And when 3.1.0 final will be out: -- `develop`: future 3.1.0 version -- `support/3.0`: 3.0.x maintenance version (will host developments for 3.0.1) +- `develop`: future 3.2.0 version +- `support/3.1`: 3.1.x maintenance version (will host developments for 3.1.1) +- `support/3.0`: 3.0.x maintenance version - `support/2.7`: 2.7.x maintenance version - `support/2.6`: 2.6.x maintenance version -- `support/2.5`: 2.5.x maintenance version Also note that we have a "micro-version" concept : each of those versions have a very small amount of modifications. They are made from `support/*` branches as well. For example 2.6.2-1 and 2.6.2-2 were made from the `support/2.6.2` branch. @@ -111,9 +114,9 @@ Our tests are located in the `test/` directory, containing a PHPUnit config file * Use the present tense ("Add feature" not "Added feature") * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") * Limit the first line to 72 characters or less -* Please start the commit message with an applicable emoji code (following the [Gitmoji guide](https://gitmoji.carloscuesta.me/)). - Beware to use the code (for example `:bug:`) and not the character (🐛) as Unicode support in git clients is very poor for now... - Emoji examples : +* Please start the commit message with an applicable emoji code (following the [Gitmoji guide](https://gitmoji.dev/)). + Beware to use the code (for example `:bug:`) and not the character (🐛) as Unicode support in git clients is very poor for now... + Emoji examples : * 🌐 `:globe_with_meridians:` for translations * 🎨 `:art:` when improving the format/structure of the code * ⚡️ `:zap:` when improving performance @@ -133,19 +136,17 @@ When your code is working, please: * stash as much as possible your commits, * rebase your branch on our repo last commit, -* create a pull request. +* create a pull request +* mind to check the "[Allow edits from maintainers](https://docs.github.com/en/github-ae@latest/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork)" option ! Detailed procedure to work on fork and create PR is available [in GitHub help pages](https://help.github.com/articles/creating-a-pull-request-from-a-fork/). -You might check the ["Allow edits from maintainers" PR checkbox][allow_edits_checkbox] to ease review. - -[allow_edits_checkbox]: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork#enabling-repository-maintainer-permissions-on-existing-pull-requests ### 🙏 We are thankful -We are thankful for all your contributions to the iTop universe! As a thank you gift, we will send stickers to every iTop (& extensions) contributors! +We are thankful for all your contributions to the iTop universe! As a thank you gift, we will send stickers to every iTop (& extensions) contributors! -Stickers' design might change from one year to another. For the first year we wanted to try a "craft beer label" look, see examples below: +We have one sticker per contribution type. You might get multiple stickers with one contribution though :) * Bug hunter: Fix a bug * Translator: Add/update translations @@ -157,4 +158,6 @@ Stickers' design might change from one year to another. For the first year we wa * Beta tester: Test and give feedback on beta releases * Extension developer: Develop and publish an extension -![](.doc/contributing-guide/contributing-stickers-side-by-side.png) +Here is the design of each stickers for year 2022: + +![iTop stickers 2022](.doc/contributing-guide/2022.contributing-stickers-side-by-side.png) diff --git a/README.md b/README.md index b291bbc3d7..637259eccc 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,11 @@

- +

-# iTop - ITSM & CMDB - -iTop stands for *IT Operations Portal*. -It is a complete open source, ITIL, web based service management tool including a fully customizable CMDB, a helpdesk system and a document management tool. -iTop also offers mass import tools and web services to integrate with your IT +iTop stands for IT Operations Portal. It is a complete open source and web based IT service management platform including a fully customizable CMDB, a helpdesk system and a document management tool. It is ITIL compliant and easily customizable and extensible thanks to a high number of adds-on and web services to integrate with your IT. + +iTop also offers mass import tools to help you being even more efficient. ## Features - Fully configurable [Configuration Management (CMDB)][10] @@ -42,6 +40,7 @@ iTop also offers mass import tools and web services to integrate with your IT - [Software requirements][4] - [Documentation][5] covering both iTop and its official extensions - [iTop Hub][6] : discover and install extensions ! + - [iTop versions history][7] [1]: https://sourceforge.net/p/itop/discussion/ @@ -50,6 +49,7 @@ iTop also offers mass import tools and web services to integrate with your IT [4]: https://www.itophub.io/wiki/page?id=latest:install:upgrading_itop [5]: https://www.itophub.io/wiki [6]: https://store.itophub.io/en_US/ +[7]: .doc/itop-version-history.md [10]: https://www.itophub.io/wiki/page?id=latest%3Adatamodel%3Astart#configuration_management_cmdb [11]: https://www.itophub.io/wiki/page?id=latest%3Adatamodel%3Astart#ticketing @@ -78,18 +78,19 @@ We would like to give a special thank you 🤗 to the people from the community - Alves, David - Beck, Pedro +- Beer, Christian (a.k.a [@ChristianBeer](https://www.github.com/ChristianBeer)) - Bilger, Jean-François -- Bostoen, Jeffrey (a.k.a @jbostoen) +- Bostoen, Jeffrey (a.k.a [@jbostoen](https://www.github.com/jbostoen)) - Cardoso, Anderson - Cassaro, Bruno -- Casteleyn, Thomas (a.k.a @Hipska) +- Casteleyn, Thomas (a.k.a [@Hipska](https://www.github.com/Hipska)) - Castro, Randall Badilla - Colantoni, Maria Laura - Couronné, Guy - Dvořák, Lukáš - Goethals, Stefan - Gumble, David -- Kaltefleiter, Lars (a.k.a @larhip) +- Kaltefleiter, Lars (a.k.a [@larhip](https://www.github.com/larhip)) - Khamit, Shamil - Kincel, Martin - Konečný, Kamil @@ -98,12 +99,15 @@ We would like to give a special thank you 🤗 to the people from the community - Lazcano, Federico - Lucas, Jonathan - Malik, Remie -- Mindêllo de Andrade, Lucas (a.k.a @rokam) +- Mindêllo de Andrade, Lucas (a.k.a [@rokam](https://www.github.com/rokam)) +- Mozart de Oliveira, Eduardo (a.k.a [@eduardomozart](https://github.com/eduardomozart)) - Raenker, Martin +- Roháč, Richard (a.k.a [@RohacRichard](https://github.com/RohacRichard)) - Rosenke, Stephan +- Rudner, Björn (a.k.a [@rudnerbjoern](https://github.com/rudnerbjoern)) - Seki, Shoji - Shilov, Vladimir -- Stukalov, Ilya (a.k.a @ilya-stukalov) +- Stukalov, Ilya (a.k.a [@ilya](https://www.github.com/ilya)-stukalov) - Tulio, Marco - Turrubiates, Miguel @@ -114,6 +118,7 @@ We would like to give a special thank you 🤗 to the people from the community - DudekArtur - Karkoff1212 - Laura +- nv35 - Purple Grape - Schlobinux - theBigOne diff --git a/addons/userrights/userrightsmatrix.class.inc.php b/addons/userrights/userrightsmatrix.class.inc.php index d80af3ed04..da64e17df7 100644 --- a/addons/userrights/userrightsmatrix.class.inc.php +++ b/addons/userrights/userrightsmatrix.class.inc.php @@ -121,7 +121,6 @@ class UserRightsMatrix extends UserRightsAddOnAPI public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US') { // Maybe we should check that no other user with userid == 0 exists - CMDBObject::SetTrackInfo('Initialization'); $oUser = new UserLocal(); $oUser->Set('login', $sAdminUser); $oUser->Set('password', $sAdminPwd); diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 1fb9fc3b1e..9d1c771bec 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -10,7 +10,7 @@ define('PORTAL_PROFILE_NAME', 'Portal user'); class UserRightsBaseClassGUI extends cmdbAbstractObject { // Whenever something changes, reload the privileges - + protected function AfterInsert() { UserRights::FlushPrivileges(); @@ -59,7 +59,7 @@ class URP_Profiles extends UserRightsBaseClassGUI } protected static $m_aCacheProfiles = null; - + public static function DoCreateProfile($sName, $sDescription) { if (is_null(self::$m_aCacheProfiles)) @@ -71,7 +71,7 @@ class URP_Profiles extends UserRightsBaseClassGUI { self::$m_aCacheProfiles[$oProfile->Get('name')] = $oProfile->GetKey(); } - } + } $sCacheKey = $sName; if (isset(self::$m_aCacheProfiles[$sCacheKey])) @@ -82,10 +82,10 @@ class URP_Profiles extends UserRightsBaseClassGUI $oNewObj->Set('name', $sName); $oNewObj->Set('description', $sDescription); $iId = $oNewObj->DBInsertNoReload(); - self::$m_aCacheProfiles[$sCacheKey] = $iId; + self::$m_aCacheProfiles[$sCacheKey] = $iId; return $iId; } - + function GetGrantAsHtml($oUserRights, $sClass, $sAction) { $bGrant = $oUserRights->GetProfileActionGrant($this->GetKey(), $sClass, $sAction); @@ -102,7 +102,7 @@ class URP_Profiles extends UserRightsBaseClassGUI return ''.Dict::S('UI:UserManagement:ActionAllowed:No').''; } } - + function DoShowGrantSumary($oPage) { if ($this->GetRawName() == "Administrator") @@ -114,7 +114,7 @@ class URP_Profiles extends UserRightsBaseClassGUI // Note: for sure, we assume that the instance is derived from UserRightsProfile $oUserRights = UserRights::GetModuleInstance(); - + $aDisplayData = array(); foreach (MetaModel::GetClasses('bizmodel,grant_by_profile') as $sClass) { @@ -123,12 +123,12 @@ class URP_Profiles extends UserRightsBaseClassGUI { $bGrant = $oUserRights->GetClassStimulusGrant($this->GetKey(), $sClass, $sStimulusCode); if ($bGrant === true) - { - $aStimuli[] = ''.htmlentities($oStimulus->GetLabel(), ENT_QUOTES, 'UTF-8').''; + { + $aStimuli[] = ''.utils::EscapeHtml($oStimulus->GetLabel()).''; } } $sStimuli = implode(', ', $aStimuli); - + $aDisplayData[] = array( 'class' => MetaModel::GetName($sClass), 'read' => $this->GetGrantAsHtml($oUserRights, $sClass, 'r'), @@ -140,7 +140,7 @@ class URP_Profiles extends UserRightsBaseClassGUI 'stimuli' => $sStimuli, ); } - + $aDisplayConfig = array(); $aDisplayConfig['class'] = array('label' => Dict::S('UI:UserManagement:Class'), 'description' => Dict::S('UI:UserManagement:Class+')); $aDisplayConfig['read'] = array('label' => Dict::S('UI:UserManagement:Action:Read'), 'description' => Dict::S('UI:UserManagement:Action:Read+')); @@ -198,7 +198,7 @@ class URP_Profiles extends UserRightsBaseClassGUI * @param $aReasons array To store the reasons why the attribute is read-only (info about the synchro replicas) * @param $sTargetState string The target state in which to evalutate the flags, if empty the current state will be used * @return integer Flags: the binary combination of the flags applicable to this attribute - */ + */ public function GetAttributeFlags($sAttCode, &$aReasons = array(), $sTargetState = '') { $iFlags = parent::GetAttributeFlags($sAttCode, $aReasons, $sTargetState); @@ -404,7 +404,7 @@ class URP_UserOrg extends UserRightsBaseClassGUI { if (!UserRights::IsLoggedIn() || UserRights::IsAdministrator()) { return; } - $oUser = UserRights::GetUserObject(); + $oUser = UserRights::GetUserObject(); $oAddon = UserRights::GetModuleInstance(); $aOrgs = $oAddon->GetUserOrgs($oUser, ''); if (count($aOrgs) > 0) @@ -435,20 +435,18 @@ class UserRightsProfile extends UserRightsAddOnAPI // Installation: create the very first user public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US') { - CMDBObject::SetTrackInfo('Initialization'); + CMDBObject::SetCurrentChangeFromParams('Initialization create administrator'); $iContactId = 0; // Support drastic data model changes: no organization class (or not writable)! - if (MetaModel::IsValidClass('Organization') && !MetaModel::IsAbstract('Organization')) - { + if (MetaModel::IsValidClass('Organization') && !MetaModel::IsAbstract('Organization')) { $oOrg = MetaModel::NewObject('Organization'); $oOrg->Set('name', 'My Company/Department'); $oOrg->Set('code', 'SOMECODE'); $iOrgId = $oOrg->DBInsertNoReload(); // Support drastic data model changes: no Person class (or not writable)! - if (MetaModel::IsValidClass('Person') && !MetaModel::IsAbstract('Person')) - { + if (MetaModel::IsValidClass('Person') && !MetaModel::IsAbstract('Person')) { $oContact = MetaModel::NewObject('Person'); $oContact->Set('name', 'My last name'); $oContact->Set('first_name', 'My first name'); @@ -528,7 +526,7 @@ class UserRightsProfile extends UserRightsAddOnAPI $oSearch->AllowAllData(); $oCondition = new BinaryExpression(new FieldExpression('userid'), '=', new VariableExpression('userid')); $oSearch->AddConditionExpression($oCondition); - + $oUserOrgSet = new DBObjectSet($oSearch, array(), array('userid' => $iUser)); while ($oUserOrg = $oUserOrgSet->Fetch()) { @@ -738,8 +736,10 @@ class UserRightsProfile extends UserRightsAddOnAPI // load and cache permissions for the current user on the given class // $iUser = $oUser->GetKey(); - $aTest = @$this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode]; - if (is_array($aTest)) return $aTest; + if (isset($this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode])){ + $aTest = $this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode]; + if (is_array($aTest)) return $aTest; + } $sAction = self::$m_aActionCodes[$iActionCode]; @@ -905,8 +905,8 @@ class UserRightsProfile extends UserRightsAddOnAPI /** * Find out which attribute is corresponding the the dimension 'owner org' - * returns null if no such attribute has been found (no filtering should occur) - */ + * returns null if no such attribute has been found (no filtering should occur) + */ public static function GetOwnerOrganizationAttCode($sClass) { $sAttCode = null; diff --git a/addons/userrights/userrightsprofile.db.class.inc.php b/addons/userrights/userrightsprofile.db.class.inc.php index ac5b2277b3..10fc826d9d 100644 --- a/addons/userrights/userrightsprofile.db.class.inc.php +++ b/addons/userrights/userrightsprofile.db.class.inc.php @@ -278,8 +278,8 @@ class URP_Profiles extends UserRightsBaseClassGUI { $oGrant = $oUserRights->GetClassStimulusGrant($this->GetKey(), $sClass, $sStimulusCode); if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes')) - { - $aStimuli[] = ''.htmlentities($oStimulus->GetLabel(), ENT_QUOTES, 'UTF-8').''; + { + $aStimuli[] = ''.utils::EscapeHtml($oStimulus->GetLabel()).''; } } $sStimuli = implode(', ', $aStimuli); @@ -508,24 +508,18 @@ class UserRightsProfile extends UserRightsAddOnAPI public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US') { // Create a change to record the history of the User object - /** @var \CMDBChange $oChange */ - $oChange = MetaModel::NewObject("CMDBChange"); - $oChange->Set("date", time()); - $oChange->Set("userinfo", "Initialization"); + CMDBObject::SetCurrentChangeFromParams('Initialization : create first user admin profile'); $iContactId = 0; // Support drastic data model changes: no organization class (or not writable)! - if (MetaModel::IsValidClass('Organization') && !MetaModel::IsAbstract('Organization')) - { + if (MetaModel::IsValidClass('Organization') && !MetaModel::IsAbstract('Organization')) { $oOrg = MetaModel::NewObject('Organization'); $oOrg->Set('name', 'My Company/Department'); $oOrg->Set('code', 'SOMECODE'); - $oOrg::SetCurrentChange($oChange); $iOrgId = $oOrg->DBInsertNoReload(); // Support drastic data model changes: no Person class (or not writable)! - if (MetaModel::IsValidClass('Person') && !MetaModel::IsAbstract('Person')) - { + if (MetaModel::IsValidClass('Person') && !MetaModel::IsAbstract('Person')) { $oContact = MetaModel::NewObject('Person'); $oContact->Set('name', 'My last name'); $oContact->Set('first_name', 'My first name'); @@ -534,7 +528,6 @@ class UserRightsProfile extends UserRightsAddOnAPI $oContact->Set('org_id', $iOrgId); } $oContact->Set('email', 'my.email@foo.org'); - $oContact::SetCurrentChange($oChange); $iContactId = $oContact->DBInsertNoReload(); } } @@ -543,24 +536,22 @@ class UserRightsProfile extends UserRightsAddOnAPI $oUser = new UserLocal(); $oUser->Set('login', $sAdminUser); $oUser->Set('password', $sAdminPwd); - if (MetaModel::IsValidAttCode('UserLocal', 'contactid') && ($iContactId != 0)) - { + if (MetaModel::IsValidAttCode('UserLocal', 'contactid') && ($iContactId != 0)) { $oUser->Set('contactid', $iContactId); } $oUser->Set('language', $sLanguage); // Language was chosen during the installation // Add this user to the very specific 'admin' profile $oAdminProfile = MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => ADMIN_PROFILE_NAME), true /*all data*/); - if (is_object($oAdminProfile)) - { + if (is_object($oAdminProfile)) { $oUserProfile = new URP_UserProfile(); $oUserProfile->Set('profileid', $oAdminProfile->GetKey()); $oUserProfile->Set('reason', 'By definition, the administrator must have the administrator profile'); $oSet = DBObjectSet::FromObject($oUserProfile); $oUser->Set('profile_list', $oSet); } - $oUser::SetCurrentChange($oChange); - $iUserId = $oUser->DBInsertNoReload(); + $oUser->DBInsertNoReload(); + return true; } diff --git a/addons/userrights/userrightsprojection.class.inc.php b/addons/userrights/userrightsprojection.class.inc.php index 61fb07fe63..84f239c526 100644 --- a/addons/userrights/userrightsprojection.class.inc.php +++ b/addons/userrights/userrightsprojection.class.inc.php @@ -110,8 +110,8 @@ class URP_Profiles extends UserRightsBaseClass { $oGrant = $oUserRights->GetClassStimulusGrant($this->GetKey(), $sClass, $sStimulusCode); if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes')) - { - $aStimuli[] = ''.htmlentities($oStimulus->GetLabel(), ENT_QUOTES, 'UTF-8').''; + { + $aStimuli[] = ''.utils::EscapeHtml($oStimulus->GetLabel()).''; } } $sStimuli = implode(', ', $aStimuli); @@ -568,14 +568,11 @@ class UserRightsProjection extends UserRightsAddOnAPI public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US') { // Create a change to record the history of the User object - $oChange = MetaModel::NewObject("CMDBChange"); - $oChange->Set("date", time()); - $oChange->Set("userinfo", "Initialization"); + CMDBObject::SetCurrentChangeFromParams('Initialization : create first user admin'); $oOrg = new Organization(); $oOrg->Set('name', 'My Company/Department'); $oOrg->Set('code', 'SOMECODE'); - $oOrg::SetCurrentChange($oChange); $iOrgId = $oOrg->DBInsertNoReload(); $oContact = new Person(); @@ -584,7 +581,6 @@ class UserRightsProjection extends UserRightsAddOnAPI //$oContact->Set('status', 'available'); $oContact->Set('org_id', $iOrgId); $oContact->Set('email', 'my.email@foo.org'); - $oContact::SetCurrentChange($oChange); $iContactId = $oContact->DBInsertNoReload(); $oUser = new UserLocal(); @@ -592,7 +588,6 @@ class UserRightsProjection extends UserRightsAddOnAPI $oUser->Set('password', $sAdminPwd); $oUser->Set('contactid', $iContactId); $oUser->Set('language', $sLanguage); // Language was chosen during the installation - $oUser::SetCurrentChange($oChange); $iUserId = $oUser->DBInsertNoReload(); // Add this user to the very specific 'admin' profile @@ -600,7 +595,6 @@ class UserRightsProjection extends UserRightsAddOnAPI $oUserProfile->Set('userid', $iUserId); $oUserProfile->Set('profileid', ADMIN_PROFILE_ID); $oUserProfile->Set('reason', 'By definition, the administrator must have the administrator profile'); - $oUserProfile::SetCurrentChange($oChange); $oUserProfile->DBInsertNoReload(); return true; } diff --git a/application/ajaxwebpage.class.inc.php b/application/ajaxwebpage.class.inc.php index d6303d067f..57e96cdf23 100644 --- a/application/ajaxwebpage.class.inc.php +++ b/application/ajaxwebpage.class.inc.php @@ -5,16 +5,20 @@ * @copyright Copyright (C) 2010-2021 Combodo SARL */ -// cannot notify depreciation for now as this is still MASSIVELY used in iTop core ! +// cannot notify depreciation for now as this is still load in autoloader //DeprecatedCallsLog::NotifyDeprecatedFile('moved to sources/Application/WebPage/AjaxPage.php, now loadable using autoloader'); /** * Class ajax_page * - * @deprecated will be removed in 3.1.0 - moved to AjaxPage + * @deprecated 3.0.0 will be removed in 3.1.0 - moved to AjaxPage */ class ajax_page extends AjaxPage { - + function __construct($s_title) + { + DeprecatedCallsLog::NotifyDeprecatedPhpMethod('ajax_page is deprecated. Please use AjaxPage instead'); + parent::__construct($s_title); + } } diff --git a/application/applicationcontext.class.inc.php b/application/applicationcontext.class.inc.php index 1361f92d30..903fa2cee1 100644 --- a/application/applicationcontext.class.inc.php +++ b/application/applicationcontext.class.inc.php @@ -224,7 +224,7 @@ class ApplicationContext { $sContext = ""; foreach ($this->aValues as $sName => $sValue) { - $sContext .= "\n"; + $sContext .= "\n"; } return $sContext; } @@ -238,7 +238,7 @@ class ApplicationContext { $aContextInputBlocks = []; foreach ($this->aValues as $sName => $sValue) { - $aContextInputBlocks[] = InputUIBlockFactory::MakeForHidden("c[$sName]", htmlentities($sValue, ENT_QUOTES, 'UTF-8')); + $aContextInputBlocks[] = InputUIBlockFactory::MakeForHidden("c[$sName]", utils::EscapeHtml($sValue)); } return $aContextInputBlocks; } diff --git a/application/applicationextension.inc.php b/application/applicationextension.inc.php index 94254dc951..6d2f959452 100644 --- a/application/applicationextension.inc.php +++ b/application/applicationextension.inc.php @@ -1184,7 +1184,7 @@ interface iPageUIBlockExtension * @api * @package Extensibility * @since 2.7.0 - * @deprecated since 3.0.0 use AbstractPageUIBlockExtension instead + * @deprecated 3.0.0 use AbstractPageUIBlockExtension instead */ abstract class AbstractPageUIExtension implements iPageUIExtension { @@ -1402,11 +1402,11 @@ interface iBackofficeDictEntriesPrefixesExtension /** * Implement this interface to add content to any enhanced portal page * - * IMPORTANT! Experimental API, may be removed at anytime, we don't recommend to use it just now! - * * @api * @package Extensibility - * @since 2.4.0 + * + * @since 2.4.0 interface creation + * @since 2.7.0 change method signatures due to Silex to Symfony migration */ interface iPortalUIExtension { @@ -1479,7 +1479,11 @@ interface iPortalUIExtension } /** - * IMPORTANT! Experimental API, may be removed at anytime, we don't recommend to use it just now! + * Extend this class instead of iPortalUIExtension if you don't need to overload all methods + * + * @api + * @package Extensibility + * @since 2.4.0 */ abstract class AbstractPortalUIExtension implements iPortalUIExtension { diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index fb5a4987a2..b6cc4a95d1 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -341,13 +341,17 @@ JS } /** - * Important: For compatibility reasons, this function still allows to manipulate the $oPage. In that case, markup will be put above the real header of the panel. - * To insert something IN the panel, we now need to add UIBlocks in either the "subtitle" or "toolbar" sections of the array that will be returned. + * @param \WebPage $oPage Warning, since 3.0.0 this parameter was kept for compatibility reason. You shouldn't write directly on the page! + * When writing to the page, markup will be put above the real header of the panel. + * To insert something IN the panel, we now need to add UIBlocks in either the "subtitle" or "toolbar" sections of the array that will be returned. + * @param bool $bEditMode Deprecated parameter in iTop 3.0.0, use {@see GetDisplayMode()} and ENUM_DISPLAY_MODE_* constants instead * - * @param \WebPage $oPage - * @param bool $bEditMode Note that this parameter is no longer used in this method. Use {@see static::$sDisplayMode} instead - * - * @return array UIBlocks to be inserted in the "subtitle" and the "toolbar" sections of the ObjectDetails block. eg. ['subtitle' => [, ], 'toolbar' => []] + * @return array{ + * subtitle: \Combodo\iTop\Application\UI\Base\UIBlock[], + * toolbar: \Combodo\iTop\Application\UI\Base\UIBlock[] + * } + * blocks to be inserted in the "subtitle" and the "toolbar" sections of the ObjectDetails block. + * eg. ['subtitle' => [, ], 'toolbar' => []] * * @throws \ApplicationException * @throws \ArchivedObjectException @@ -356,7 +360,10 @@ JS * @throws \MySQLException * @throws \OQLException * - * @since 3.0.0 $bEditMode is deprecated and no longer used + * @since 3.0.0 $bEditMode is deprecated, see param documentation above + * @since 3.0.0 Changed signature: Method must return header content in an array (no more writing directly to the $oPage) + * + * @noinspection PhpUnusedParameterInspection */ public function DisplayBareHeader(WebPage $oPage, $bEditMode = false) { @@ -721,7 +728,7 @@ HTML $oClassIcon = new MedallionIcon(MetaModel::GetClassIcon($sTargetClass, false)); $oClassIcon->SetDescription($oAttDef->GetDescription())->AddCSSClass('ibo-block-list--medallion'); $oPage->AddUiBlock($oClassIcon); - + $sDisplayValue = ''; // not used $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $oLinkSet, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).''; @@ -980,10 +987,8 @@ HTML $this->GetSynchroReplicaFlags($sAttCode, $aReasons); $sTip = ''; foreach ($aReasons as $aRow) { - $sDescription = htmlentities($aRow['description'], ENT_QUOTES, - 'UTF-8'); - $sDescription = str_replace(array("\r\n", "\n"), "
", - $sDescription); + $sDescription = utils::EscapeHtml($aRow['description']); + $sDescription = str_replace(array("\r\n", "\n"), "
", $sDescription); $sTip .= "
"; $sTip .= "
Synchronized with {$aRow['name']}
"; $sTip .= "
$sDescription
"; @@ -1396,7 +1401,7 @@ HTML } else { if ($oAttDef instanceof AttributeCaseLog) { $rawValue = $oObj->Get($sAttCodeEx); - $outputValue = str_replace("\n", "
", htmlentities($rawValue->__toString(), ENT_QUOTES, 'UTF-8')); + $outputValue = str_replace("\n", "
", utils::EscapeHtml($rawValue->__toString())); // Trick for Excel: treat the content as text even if it begins with an equal sign $aRow[$oAttDef->GetCode()] = $outputValue; } else { @@ -1410,9 +1415,9 @@ HTML } } if ($bLocalize) { - $outputValue = htmlentities($oFinalAttDef->GetEditValue($rawValue), ENT_QUOTES, 'UTF-8'); + $outputValue = utils::EscapeHtml($oFinalAttDef->GetEditValue($rawValue)); } else { - $outputValue = htmlentities($rawValue, ENT_QUOTES, 'UTF-8'); + $outputValue = utils::EscapeHtml($rawValue); } $aRow[$oAttDef->GetCode()] = $outputValue; } @@ -1448,7 +1453,7 @@ HTML * @throws \MissingQueryArgument * @throws \MySQLException * @throws \MySQLHasGoneAwayException - * @deprecated since 3.0.0 + * @deprecated 3.0.0 */ public static function GetDisplayExtendedSet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { @@ -1888,7 +1893,7 @@ HTML { $rawValue = $oObj->Get($sAttCodeEx); $outputValue = str_replace("\n", "
", - htmlentities($rawValue->__toString(), ENT_QUOTES, 'UTF-8')); + utils::EscapeHtml($rawValue->__toString())); // Trick for Excel: treat the content as text even if it begins with an equal sign $aRow[] = ''.$outputValue.''; } @@ -1905,14 +1910,11 @@ HTML $rawValue = ''; } } - if ($bLocalize) - { - $outputValue = htmlentities($oFinalAttDef->GetEditValue($rawValue), ENT_QUOTES, - 'UTF-8'); + if ($bLocalize) { + $outputValue = utils::EscapeHtml($oFinalAttDef->GetEditValue($rawValue)); } - else - { - $outputValue = htmlentities($rawValue, ENT_QUOTES, 'UTF-8'); + else { + $outputValue = utils::EscapeHtml($rawValue); } $aRow[] = ''.$outputValue.''; } @@ -2131,7 +2133,7 @@ HTML; $sDisplayValueForHtml = utils::EscapeHtml($sDisplayValue); $sHTMLValue = << - +
{$sValidationSpan}{$sReloadSpan} HTML; break; @@ -2149,7 +2151,7 @@ HTML; $sHours = ""; $sMinutes = ""; $sSeconds = ""; - $sHidden = ""; + $sHidden = ""; $sHTMLValue = Dict::Format('UI:DurationForm_Days_Hours_Minutes_Seconds', $sDays, $sHours, $sMinutes, $sSeconds).$sHidden." ".$sValidationSpan.$sReloadSpan; $oPage->add_ready_script("$('#{$iId}').on('update', function(evt, sFormId) { return ToggleDurationField('$iId'); });"); break; @@ -2159,8 +2161,7 @@ HTML; $aEventsList[] = 'validate'; $aEventsList[] = 'keyup'; $aEventsList[] = 'change'; - $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; + $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; break; case 'OQLExpression': @@ -2312,13 +2313,13 @@ EOF $sHeader = '
'; // will be hidden in CSS (via :empty) if it remains empty $sEditValue = is_object($value) ? $value->GetModifiedEntry('html') : ''; - $sPreviousLog = is_object($value) ? $value->GetAsHTML($oPage, true /* bEditMode */, array('AttributeText', 'RenderWikiHtml')) : ''; + $sPreviousLog = is_object($value) ? $value->GetAsHTML($oPage, true /* bEditMode */, array('AttributeText', 'RenderWikiHtml')) : ''; $iEntriesCount = is_object($value) ? count($value->GetIndex()) : 0; $sHidden = ""; // To know how many entries the case log already contains $sHTMLValue = "$sHeader
"; - $sHTMLValue .= ""; - $sHTMLValue .= "$sPreviousLog
{$sValidationSpan}{$sReloadSpan}$sHidden"; + $sHTMLValue .= ""; + $sHTMLValue .= "$sPreviousLog{$sValidationSpan}{$sReloadSpan}$sHidden"; // Note: This should be refactored for all types of attribute (see at the end of this function) but as we are doing this for a maintenance release, we are scheduling it for the next main release in to order to avoid regressions as much as possible. $sNullValue = $oAttDef->GetNullValue(); @@ -2563,16 +2564,16 @@ JS case 'Set': case 'TagSet': - $sInputType = self::ENUM_INPUT_TYPE_TAGSET; - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/selectize.min.js'); - $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/selectize.default.css'); - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.itop-set-widget.js'); + $sInputType = self::ENUM_INPUT_TYPE_TAGSET; + $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/selectize.min.js'); + $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/selectize.default.css'); + $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.itop-set-widget.js'); - $oPage->add_dict_entry('Core:AttributeSet:placeholder'); + $oPage->add_dict_entry('Core:AttributeSet:placeholder'); - /** @var \ormSet $value */ + /** @var \ormSet $value */ $sJson = $oAttDef->GetJsonForWidget($value, $aArgs); - $sEscapedJson = htmlentities($sJson, ENT_QUOTES, 'UTF-8'); + $sEscapedJson = utils::EscapeHtml($sJson); $sSetInputName = "attr_{$sFormPrefix}{$sAttCode}"; // handle form validation @@ -3685,8 +3686,7 @@ HTML; break; default: - $oPage->add("
".htmlentities(MyHelpers::beautifulstr($data, 1000, true), ENT_QUOTES,
-								'UTF-8')."
\n"); + $oPage->add("
".utils::EscapeHtml(MyHelpers::beautifulstr($data, 1000, true))."
\n"); } break; @@ -3929,7 +3929,7 @@ HTML; } elseif ($iFlags & OPT_ATT_SLAVE) { - $aErrors[$sAttCode] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $oAttDef->GetLabel()); + $aErrors[$sAttCode] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $oAttDef->GetLabel(), $sAttCode); } else { @@ -4515,11 +4515,9 @@ HTML; */ protected function SetWarningsAsSessionMessages($sMessageIdPrefix) { - if (!empty($this->m_aCheckWarnings) && is_array($this->m_aCheckWarnings)) - { + if (!empty($this->m_aCheckWarnings) && is_array($this->m_aCheckWarnings)) { $iMsgNb = 0; - foreach ($this->m_aCheckWarnings as $sWarningMessage) - { + foreach ($this->m_aCheckWarnings as $sWarningMessage) { $iMsgNb++; $sMessageId = "$sMessageIdPrefix-$iMsgNb"; // each message must have its own messageId ! $this->SetSessionMessageFromInstance($sMessageId, $sWarningMessage, 'warning', 0); @@ -4527,12 +4525,6 @@ HTML; } } - protected static function BulkUpdateTracked_Internal(DBSearch $oFilter, array $aValues) - { - // Todo - invoke the extension - return parent::BulkUpdateTracked_Internal($oFilter, $aValues); - } - protected function DBDeleteTracked_Internal(&$oDeletionPlan = null) { // Invoke extensions before the deletion (the deletion will do some cleanup and we might loose some information @@ -4735,9 +4727,8 @@ HTML; { $aReasons = array(); $sTip = ''; - foreach($aReasons as $aRow) - { - $sDescription = htmlentities($aRow['description'], ENT_QUOTES, 'UTF-8'); + foreach($aReasons as $aRow) { + $sDescription = utils::EscapeHtml($aRow['description']); $sDescription = str_replace(array("\r\n", "\n"), "
", $sDescription); $sTip .= "
"; $sTip .= "
Synchronized with {$aRow['name']}
"; @@ -4749,8 +4740,7 @@ HTML; // Attribute is read-only $sHTMLValue = $this->GetAsHTML($sAttCode); - $sHTMLValue .= ''; + $sHTMLValue .= ''; $aFieldsMap[$sAttCode] = $sInputId; } else @@ -5086,7 +5076,7 @@ HTML if ($sAttCode != MetaModel::GetStateAttributeCode($sClass) || !MetaModel::HasLifecycle($sClass)) { $sValueCheckbox = ''; } - $aComments[$sAttCode] .= '
'.$iCount.$sValueCheckbox.'
'; + $aComments[$sAttCode] .= '
'.$iCount.$sValueCheckbox.'
'; } $sReadyScript .= 'ToggleField('.(($iCount == 1) ? 'true' : 'false').', \''.$iFormId.'_'.$sAttCode.'\');'."\n"; } @@ -5204,11 +5194,14 @@ EOF } else { $sStatus = $bResult ? Dict::S('UI:BulkModifyStatusModified') : Dict::S('UI:BulkModifyStatusSkipped'); } - $sChecked = $bResult ? 'checked' : ''; + + $aErrorsToDisplay = array_map(function($sError) { + return utils::HtmlEntities($sError); + }, $aErrors); $aRows[] = array( 'object' => $oObj->GetHyperlink(), 'status' => $sStatus, - 'errors' => '

'.($bResult ? '' : implode('

', $aErrors)).'

', + 'errors' => '

'.($bResult ? '' : implode('

', $aErrorsToDisplay)).'

', ); if ($bResult && (!$bPreview)) { $oObj->DBUpdate(); @@ -5217,7 +5210,7 @@ EOF set_time_limit(intval($iPreviousTimeLimit)); $oTable = DataTableUIBlockFactory::MakeForForm('BulkModify', $aHeaders, $aRows); $oTable->AddOption("bFullscreen", true); - + $oPanel = PanelUIBlockFactory::MakeForClass($sClass, ''); $oPanel->SetIcon($sClassIcon); $oPanel->SetTitle($sHeaderTitle); @@ -5423,13 +5416,13 @@ EOF $oFailAlertBlock = AlertUIBlockFactory::MakeForDanger('', Dict::S('UI:Delete:SorryDeletionNotAllowed')); $oFailAlertBlock->SetIsClosable(false); $oP->AddUiBlock($oFailAlertBlock); - } + } else { $oWarningAlertBlock = AlertUIBlockFactory::MakeForWarning('', Dict::S('UI:Delete:PleaseDoTheManualOperations')); $oWarningAlertBlock->SetIsClosable(false); $oP->AddUiBlock($oWarningAlertBlock); } - + $oForm = FormUIBlockFactory::MakeStandard(''); $oP->AddSubBlock($oForm); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', utils::ReadParam('transaction_id', '', false, 'transaction_id'))); diff --git a/application/dashboard.class.inc.php b/application/dashboard.class.inc.php index dd3c42c4a4..d473c22341 100644 --- a/application/dashboard.class.inc.php +++ b/application/dashboard.class.inc.php @@ -789,6 +789,7 @@ class RuntimeDashboard extends Dashboard /** * @inheritDoc + * @return bool $bIsNew * @throws \Exception */ public function Save() @@ -798,6 +799,7 @@ class RuntimeDashboard extends Dashboard $oUDSearch->AddCondition('user_id', UserRights::GetUserId(), '='); $oUDSearch->AddCondition('menu_code', $this->sId, '='); $oUDSet = new DBObjectSet($oUDSearch); + $bIsNew = false; if ($oUDSet->Count() > 0) { // Assuming there is at most one couple {user, menu}! @@ -811,10 +813,12 @@ class RuntimeDashboard extends Dashboard $oUserDashboard->Set('user_id', UserRights::GetUserId()); $oUserDashboard->Set('menu_code', $this->sId); $oUserDashboard->Set('contents', $sXml); + $bIsNew = true; } utils::PushArchiveMode(false); $oUserDashboard->DBWrite(); utils::PopArchiveMode(); + return $bIsNew; } /** diff --git a/application/dashlet.class.inc.php b/application/dashlet.class.inc.php index bf61a27859..2ba7e3b78d 100644 --- a/application/dashlet.class.inc.php +++ b/application/dashlet.class.inc.php @@ -262,7 +262,7 @@ abstract class Dashlet } } catch (OqlException $e) { $oDashletContainer->AddCSSClass("dashlet-content"); - $oDashletContainer->AddHtml('

'.$e->GetUserFriendlyDescription().'

'); + $oDashletContainer->AddHtml('

'.utils::HtmlEntities($e->GetUserFriendlyDescription()).'

'); } catch (Exception $e) { $oDashletContainer->AddCSSClass("dashlet-content"); $oDashletContainer->AddHtml('

'.$e->getMessage().'

'); diff --git a/application/datatable.class.inc.php b/application/datatable.class.inc.php index 4e4ce06b4a..139d1a3b27 100644 --- a/application/datatable.class.inc.php +++ b/application/datatable.class.inc.php @@ -19,7 +19,7 @@ use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer; * * You should have received a copy of the GNU Affero General Public License * - * @deprecated since 3.0.0 use Combodo\iTop\Application\UI\Base\Component\DataTable\Datatable + * @deprecated 3.0.0 use Combodo\iTop\Application\UI\Base\Component\DataTable\Datatable */ class DataTable diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 2592910069..534d3031ae 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -476,7 +476,7 @@ class DisplayBlock $oExceptionAlert = AlertUIBlockFactory::MakeForFailure('Cannot display results', $sExceptionContent); $oHtml->AddSubBlock($oExceptionAlert); } - IssueLog::Error('Exception during GetDisplay: '.$e->getMessage()); + ExceptionLog::LogException($e); } } else { // render it as an Ajax (asynchronous) call @@ -566,7 +566,7 @@ class DisplayBlock if (($this->m_sStyle != 'links') && ($this->m_sStyle != 'search') && ($this->m_sStyle != 'list_search')) { $oAppContext = new ApplicationContext(); $sClass = $this->m_oFilter->GetClass(); - $aFilterCodes = array_keys(MetaModel::GetClassFilterDefs($sClass)); + $aFilterCodes = MetaModel::GetFiltersList($sClass); $aCallSpec = array($sClass, 'MapContextParam'); if (is_callable($aCallSpec)) { foreach ($oAppContext->GetNames() as $sContextParam) { @@ -1054,6 +1054,11 @@ JS $oBlock->AddSubBlock($oPill); } $aExtraParams['query_params'] = $this->m_oFilter->GetInternalParams(); + if(isset($aExtraParams['query_params']['this->object()'])){ + $aExtraParams['query_params']['this->class'] = get_class($aExtraParams['query_params']['this->object()']); + $aExtraParams['query_params']['this->id'] = $aExtraParams['query_params']['this->object()']->GetKey(); + unset($aExtraParams['query_params']['this->object()']); + } $aRefreshParams = ['filter' => $this->m_oFilter->ToOQL(), "extra_params" => json_encode($aExtraParams)]; $oBlock->SetJSRefresh( "$('#".$oBlock->GetId()."').block(); @@ -1202,6 +1207,7 @@ JS $sTitle = Dict::Format($sFormat, $iTotalCount); $oBlock = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]); $oBlock->AddSubTitleBlock(new Html($sTitle)); + $oBlock->AddCSSClass('ibo-datatable-panel'); if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){ $oBlock->SetIcon($aExtraParams["panel_icon"]); } diff --git a/application/exceptions/CoreCannotSaveObjectException.php b/application/exceptions/CoreCannotSaveObjectException.php index d7e4235d9c..dcbe20ff5b 100644 --- a/application/exceptions/CoreCannotSaveObjectException.php +++ b/application/exceptions/CoreCannotSaveObjectException.php @@ -44,15 +44,15 @@ class CoreCannotSaveObjectException extends CoreException public function getHtmlMessage() { $sTitle = Dict::S('UI:Error:SaveFailed'); - $sContent = "{$sTitle}"; + $sContent = "".utils::HtmlEntities($sTitle).""; if (count($this->aIssues) == 1) { $sIssue = reset($this->aIssues); - $sContent .= " {$sIssue}"; + $sContent .= " ".utils::HtmlEntities($sIssue).""; } else { $sContent .= '
    '; foreach ($this->aIssues as $sError) { - $sContent .= "
  • $sError
  • "; + $sContent .= "
  • ".utils::HtmlEntities($sError)."
  • "; } $sContent .= '
'; } diff --git a/application/forms.class.inc.php b/application/forms.class.inc.php index d9280a03b2..3a720c39b2 100644 --- a/application/forms.class.inc.php +++ b/application/forms.class.inc.php @@ -838,7 +838,8 @@ class DesignerFormField { $sId = $this->oForm->GetFieldId($this->sCode); $sName = $this->oForm->GetFieldName($this->sCode); - return array('label' => $this->sLabel, 'value' => "defaultValue, ENT_QUOTES, 'UTF-8')."\">"); + + return array('label' => $this->sLabel, 'value' => "defaultValue)."\">"); } /** @@ -1012,9 +1013,8 @@ class DesignerTextField extends DesignerFormField $sId = $this->oForm->GetFieldId($this->sCode); $sName = $this->oForm->GetFieldName($this->sCode); - if ($this->IsReadOnly()) - { - $sHtmlValue = "".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."defaultValue, ENT_QUOTES, 'UTF-8')."\"/>"; + if ($this->IsReadOnly()) { + $sHtmlValue = "".utils::EscapeHtml($this->defaultValue)."defaultValue)."\"/>"; } else { @@ -1038,11 +1038,10 @@ $('#$sId').on('change keyup validate', function() { ValidateWithPattern('$sId', EOF ); $sCSSClasses = ''; - if (count($this->aCSSClasses) > 0) - { + if (count($this->aCSSClasses) > 0) { $sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"'; } - $sHtmlValue = "defaultValue, ENT_QUOTES, 'UTF-8')."\">"; + $sHtmlValue = "defaultValue)."\">"; } return array('label' => $this->sLabel, 'value' => $sHtmlValue); } @@ -1101,10 +1100,9 @@ class DesignerLongTextField extends DesignerTextField { $sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"'; } - if (!$this->IsReadOnly()) - { + if (!$this->IsReadOnly()) { $oP->add_ready_script( -<<".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8').""; + $sValue = ""; } - else - { - $sValue = "
".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."
"; + else { + $sValue = "
".utils::EscapeHtml($this->defaultValue)."
"; } return array('label' => $this->sLabel, 'value' => $sValue); } @@ -1145,9 +1142,8 @@ class DesignerIntegerField extends DesignerFormField $sId = $this->oForm->GetFieldId($this->sCode); $sName = $this->oForm->GetFieldName($this->sCode); - if ($this->IsReadOnly()) - { - $sHtmlValue = "".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."defaultValue, ENT_QUOTES, 'UTF-8')."\"/>"; + if ($this->IsReadOnly()) { + $sHtmlValue = "".utils::EscapeHtml($this->defaultValue)."defaultValue)."\"/>"; } else { @@ -1164,11 +1160,10 @@ $('#$sId').on('change keyup validate', function() { ValidateInteger('$sId', $sMa EOF ); $sCSSClasses = ''; - if (count($this->aCSSClasses) > 0) - { + if (count($this->aCSSClasses) > 0) { $sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"'; } - $sHtmlValue = "defaultValue, ENT_QUOTES, 'UTF-8')."\">"; + $sHtmlValue = "defaultValue)."\">"; } return array('label' => $this->sLabel, 'value' => $sHtmlValue); } @@ -1272,7 +1267,7 @@ class DesignerComboField extends DesignerFormField $sChecked = $this->defaultValue ? 'checked' : ''; $sMandatory = $this->bMandatory ? 'true' : 'false'; $sReadOnly = $this->IsReadOnly() ? 'disabled="disabled"' : ''; - if ($this->IsSorted()) + if ($this->IsSorted() ) { asort($this->aAllowedValues); } @@ -1289,22 +1284,18 @@ class DesignerComboField extends DesignerFormField { if ($this->bMultipleSelection) { - if(in_array($sKey, $this->defaultValue)) - { + if(in_array($sKey, $this->defaultValue)) { $aSelected[] = $sDisplayValue; - $aHiddenValues[] = ""; + $aHiddenValues[] = ""; } - } - else - { - if ($sKey == $this->defaultValue) - { + } else { + if ($sKey == $this->defaultValue) { $aSelected[] = $sDisplayValue; - $aHiddenValues[] = ""; + $aHiddenValues[] = ""; } } } - $sHtml = "".htmlentities(implode(', ', $aSelected), ENT_QUOTES, 'UTF-8').implode($aHiddenValues).""; + $sHtml = "".utils::EscapeHtml(implode(', ', $aSelected)).implode($aHiddenValues).""; } else { @@ -1320,19 +1311,15 @@ class DesignerComboField extends DesignerFormField $sHtml .= ""; } } - foreach($this->aAllowedValues as $sKey => $sDisplayValue) - { - if ($this->bMultipleSelection) - { + foreach ($this->aAllowedValues as $sKey => $sDisplayValue) { + if ($this->bMultipleSelection) { $sSelected = in_array($sKey, $this->defaultValue) ? 'selected' : ''; - } - else - { + } else { $sSelected = ($sKey == $this->defaultValue) ? 'selected' : ''; } // Quick and dirty: display the menu parents as a tree - $sHtmlValue = str_replace(' ', ' ', htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')); - $sHtml .= ""; + $sHtmlValue = str_replace(' ', ' ', $sDisplayValue); + $sHtml .= ""; } $sHtml .= ""; if ($this->bOtherChoices) @@ -1383,10 +1370,9 @@ class DesignerBooleanField extends DesignerFormField $sId = $this->oForm->GetFieldId($this->sCode); $sName = $this->oForm->GetFieldName($this->sCode); $sChecked = $this->defaultValue ? 'checked' : ''; - if ($this->IsReadOnly()) - { + if ($this->IsReadOnly()) { $sLabel = $this->defaultValue ? Dict::S('UI:UserManagement:ActionAllowed:Yes') : Dict::S('UI:UserManagement:ActionAllowed:No'); //TODO use our own yes/no translations - $sHtmlValue = "".htmlentities($sLabel)."defaultValue, ENT_QUOTES, 'UTF-8')."\"/>"; + $sHtmlValue = "".utils::EscapeHtml($sLabel)."defaultValue)."\"/>"; } else { @@ -1454,8 +1440,8 @@ class DesignerHiddenField extends DesignerFormField { $sId = $this->oForm->GetFieldId($this->sCode); $sName = $this->oForm->GetFieldName($this->sCode); - $sChecked = $this->defaultValue ? 'checked' : ''; - return array('label' =>'', 'value' => "defaultValue, ENT_QUOTES, 'UTF-8')."\">"); + + return array('label' => '', 'value' => "defaultValue)."\">"); } } @@ -1522,7 +1508,7 @@ class DesignerIconSelectionField extends DesignerFormField EOF ); } else { - $sValue = ' '.htmlentities($this->aAllowedValues[$idx]['label'], ENT_QUOTES, 'UTF-8').''; + $sValue = ' '.utils::EscapeHtml($this->aAllowedValues[$idx]['label']).''; } $sReadOnly = $this->IsReadOnly() ? 'disabled' : ''; return array('label' => $this->sLabel, 'value' => $sValue); @@ -1669,14 +1655,14 @@ class DesignerSortableField extends DesignerFormField $sId = $this->oForm->GetFieldId($this->sCode); $sName = $this->oForm->GetFieldName($this->sCode); $sReadOnly = $this->IsReadOnly() ? 'readonly="readonly"' : ''; - $aResult = array('label' => $this->sLabel, 'value' => "defaultValue, ENT_QUOTES, 'UTF-8')."\">"); - + $aResult = array('label' => $this->sLabel, 'value' => "defaultValue)."\">"); + $sJSFields = json_encode(array_keys($this->aAllowedValues)); $oP->add_ready_script( "$('#$sId').sortable_field({aAvailableFields: $sJSFields});" ); - + return $aResult; } } @@ -1765,8 +1751,8 @@ class DesignerFormSelectorField extends DesignerFormField foreach ($this->aSubForms as $iKey => $aFormData) { if ($iKey == $this->defaultValue) // Default value is actually the index { - $sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8'); - $sHiddenValue = ""; + $sDisplayValue = utils::EscapeHtml($aFormData['label']); + $sHiddenValue = ""; break; } } @@ -1774,8 +1760,8 @@ class DesignerFormSelectorField extends DesignerFormField } else { $sHtml = "'; + $sHTMLValue .= ''; $aFieldsMap[$sAttCode] = $iInputId; $aParams['this->comments('.$sAttCode.')'] = $sSynchroIcon; } diff --git a/application/twigextension.class.inc.php b/application/twigextension.class.inc.php index 8a8cec26f6..5234eb96a3 100644 --- a/application/twigextension.class.inc.php +++ b/application/twigextension.class.inc.php @@ -4,14 +4,17 @@ namespace Combodo\iTop; use AttributeDate; use AttributeDateTime; +use DeprecatedCallsLog; use Dict; use Exception; use MetaModel; -use Twig_Environment; -use Twig_SimpleFilter; -use Twig_SimpleFunction; +use Twig\Environment; +use Twig\TwigFilter; +use Twig\TwigFunction; use utils; +DeprecatedCallsLog::NotifyDeprecatedFile('instead use sources/Application/TwigBase/Twig/Extension.php, which is loaded by the autoloader'); + /** * Class TwigExtension * @@ -25,13 +28,13 @@ class TwigExtension * Registers Twig extensions such as filters or functions. * It allows us to access some stuff directly in twig. * - * @param \Twig_Environment $oTwigEnv + * @param Environment $oTwigEnv */ - public static function RegisterTwigExtensions(Twig_Environment &$oTwigEnv) + public static function RegisterTwigExtensions(Environment &$oTwigEnv) { // Filter to translate a string via the Dict::S function // Usage in twig: {{ 'String:ToTranslate'|dict_s }} - $oTwigEnv->addFilter(new Twig_SimpleFilter('dict_s', + $oTwigEnv->addFilter(new TwigFilter('dict_s', function ($sStringCode, $sDefault = null, $bUserLanguageOnly = false) { return Dict::S($sStringCode, $sDefault, $bUserLanguageOnly); }) @@ -39,7 +42,7 @@ class TwigExtension // Filter to format a string via the Dict::Format function // Usage in twig: {{ 'String:ToTranslate'|dict_format() }} - $oTwigEnv->addFilter(new Twig_SimpleFilter('dict_format', + $oTwigEnv->addFilter(new TwigFilter('dict_format', function ($sStringCode, $sParam01 = null, $sParam02 = null, $sParam03 = null, $sParam04 = null) { return Dict::Format($sStringCode, $sParam01, $sParam02, $sParam03, $sParam04); }) @@ -48,16 +51,13 @@ class TwigExtension // Filter to format output // example a DateTime is converted to user format // Usage in twig: {{ 'String:ToFormat'|output_format }} - $oTwigEnv->addFilter(new Twig_SimpleFilter('date_format', + $oTwigEnv->addFilter(new TwigFilter('date_format', function ($sDate) { - try - { - if (preg_match('@^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$@', trim($sDate))) - { + try { + if (preg_match('@^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$@', trim($sDate))) { return AttributeDateTime::GetFormat()->Format($sDate); } - if (preg_match('@^\d\d\d\d-\d\d-\d\d$@', trim($sDate))) - { + if (preg_match('@^\d\d\d\d-\d\d-\d\d$@', trim($sDate))) { return AttributeDate::GetFormat()->Format($sDate); } } @@ -72,7 +72,7 @@ class TwigExtension // Filter to format output // example a DateTime is converted to user format // Usage in twig: {{ 'String:ToFormat'|output_format }} - $oTwigEnv->addFilter(new Twig_SimpleFilter('size_format', + $oTwigEnv->addFilter(new TwigFilter('size_format', function ($sSize) { return utils::BytesToFriendlyFormat($sSize); }) @@ -80,24 +80,25 @@ class TwigExtension // Filter to enable base64 encode/decode // Usage in twig: {{ 'String to encode'|base64_encode }} - $oTwigEnv->addFilter(new Twig_SimpleFilter('base64_encode', 'base64_encode')); - $oTwigEnv->addFilter(new Twig_SimpleFilter('base64_decode', 'base64_decode')); + $oTwigEnv->addFilter(new TwigFilter('base64_encode', 'base64_encode')); + $oTwigEnv->addFilter(new TwigFilter('base64_decode', 'base64_decode')); // Filter to enable json decode (encode already exists) // Usage in twig: {{ aSomeArray|json_decode }} - $oTwigEnv->addFilter(new Twig_SimpleFilter('json_decode', function ($sJsonString, $bAssoc = false) { + $oTwigEnv->addFilter(new TwigFilter('json_decode', function ($sJsonString, $bAssoc = false) { return json_decode($sJsonString, $bAssoc); }) ); // Filter to add itopversion to an url - $oTwigEnv->addFilter(new Twig_SimpleFilter('add_itop_version', function ($sUrl) { + $oTwigEnv->addFilter(new TwigFilter('add_itop_version', function ($sUrl) { $sUrl = utils::AddParameterToUrl($sUrl, 'itopversion', ITOP_VERSION); + return $sUrl; })); // Filter to add a module's version to an url - $oTwigEnv->addFilter(new Twig_SimpleFilter('add_module_version', function ($sUrl, $sModuleName) { + $oTwigEnv->addFilter(new TwigFilter('add_module_version', function ($sUrl, $sModuleName) { $sModuleVersion = utils::GetCompiledModuleVersion($sModuleName); $sUrl = utils::AddParameterToUrl($sUrl, 'moduleversion', $sModuleVersion); @@ -106,38 +107,36 @@ class TwigExtension // Function to check our current environment // Usage in twig: {% if is_development_environment() %} - $oTwigEnv->addFunction(new Twig_SimpleFunction('is_development_environment', function() - { + $oTwigEnv->addFunction(new TwigFunction('is_development_environment', function () { return utils::IsDevelopmentEnvironment(); })); // Function to get configuration parameter // Usage in twig: {{ get_config_parameter('foo') }} - $oTwigEnv->addFunction(new Twig_SimpleFunction('get_config_parameter', function($sParamName) - { + $oTwigEnv->addFunction(new TwigFunction('get_config_parameter', function ($sParamName) { $oConfig = MetaModel::GetConfig(); + return $oConfig->Get($sParamName); })); // Function to get a module setting // Usage in twig: {{ get_module_setting(, [, ]) }} // since 3.0.0, but see N°4034 for upcoming evolutions in the 3.1 - $oTwigEnv->addFunction(new Twig_SimpleFunction('get_module_setting', function (string $sModuleCode, string $sPropertyCode, $defaultValue = null) { + $oTwigEnv->addFunction(new TwigFunction('get_module_setting', function (string $sModuleCode, string $sPropertyCode, $defaultValue = null) { $oConfig = MetaModel::GetConfig(); + return $oConfig->GetModuleSetting($sModuleCode, $sPropertyCode, $defaultValue); })); // Function to get the URL of a static page in a module // Usage in twig: {{ get_static_page_module_url('itop-my-module', 'path-to-my-page') }} - $oTwigEnv->addFunction(new Twig_SimpleFunction('get_static_page_module_url', function($sModuleName, $sPage) - { + $oTwigEnv->addFunction(new TwigFunction('get_static_page_module_url', function ($sModuleName, $sPage) { return utils::GetAbsoluteUrlModulesRoot().$sModuleName.'/'.$sPage; })); // Function to get the URL of a php page in a module // Usage in twig: {{ get_page_module_url('itop-my-module', 'path-to-my-my-page.php') }} - $oTwigEnv->addFunction(new Twig_SimpleFunction('get_page_module_url', function($sModuleName, $sPage) - { + $oTwigEnv->addFunction(new TwigFunction('get_page_module_url', function ($sModuleName, $sPage) { return utils::GetAbsoluteUrlModulePage($sModuleName, $sPage); })); } diff --git a/application/ui.extkeywidget.class.inc.php b/application/ui.extkeywidget.class.inc.php index 4cec8e2221..162198c319 100644 --- a/application/ui.extkeywidget.class.inc.php +++ b/application/ui.extkeywidget.class.inc.php @@ -211,14 +211,23 @@ class UIExtKeyWidget $sClassAllowed = $oAllowedValues->GetClass(); $bAddingValue = false; + // N°4792 - load only the required fields + $aFieldsToLoad = []; + $aComplementAttributeSpec = MetaModel::GetNameSpec($oAllowedValues->GetClass(), FriendlyNameType::COMPLEMENTARY); $sFormatAdditionalField = $aComplementAttributeSpec[0]; $aAdditionalField = $aComplementAttributeSpec[1]; if (count($aAdditionalField) > 0) { $bAddingValue = true; + $aFieldsToLoad[$sClassAllowed] = $aAdditionalField; } $sObjectImageAttCode = MetaModel::GetImageAttributeCode($sClassAllowed); + if (!empty($sObjectImageAttCode)) { + $aFieldsToLoad[$sClassAllowed][] = $sObjectImageAttCode; + } + $aFieldsToLoad[$sClassAllowed][] = 'friendlyname'; + $oAllowedValues->OptimizeColumnLoad($aFieldsToLoad); $bInitValue = false; while ($oObj = $oAllowedValues->Fetch()) { $aOption = []; @@ -298,7 +307,7 @@ EOF $sHTMLValue .= "iId\" value=\"$sDisplayValue\" placeholder='...'/>"; // another hidden input to store & pass the object's Id - $sHTMLValue .= "iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" />\n"; + $sHTMLValue .= "iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".utils::HtmlEntities($value)."\" />\n"; $JSSearchMode = $this->bSearchMode ? 'true' : 'false'; // Scripts to start the autocomplete and bind some events to it @@ -610,7 +619,7 @@ EOF $sHTMLValue .= "
iId}\" onClick=\"oACWidget_{$this->iId}.Search();\">
"; // another hidden input to store & pass the object's Id - $sHTMLValue .= "iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" />\n"; + $sHTMLValue .= "iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".utils::EscapeHtml($value)."\" />\n"; $JSSearchMode = $this->bSearchMode ? 'true' : 'false'; // Scripts to start the autocomplete and bind some events to it @@ -769,16 +778,14 @@ JS * @param DBObject $oObj The current object for the OQL context * @param string $sContains The text of the autocomplete to filter the results * @param string $sOutputFormat - * @param null $sOperation for the values @see ValueSetObjects->LoadValues() + * @param null $sOperation for the values @see ValueSetObjects->LoadValues() not used since 3.0.0 * * @throws CoreException * @throws OQLException * * @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $oObj for PHP 8.0 compatibility */ - public function AutoComplete( - WebPage $oP, $sFilter, $oObj, $sContains, $sOutputFormat = self::ENUM_OUTPUT_FORMAT_CSV, $sOperation = null - ) + public function AutoComplete(WebPage $oP, $sFilter, $oObj, $sContains, $sOutputFormat = self::ENUM_OUTPUT_FORMAT_CSV, $sOperation = null ) { if (is_null($sFilter)) { throw new Exception('Implementation: null value for allowed values definition'); @@ -792,13 +799,13 @@ JS $oValuesSet->SetSort(false); $oValuesSet->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode); $oValuesSet->SetLimit($iMax); - $aValuesContains = $oValuesSet->GetValuesForAutocomplete(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'start_with'); - asort($aValuesContains); - $aValues = $aValuesContains; + $aValuesStartWith = $oValuesSet->GetValuesForAutocomplete(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'start_with'); + asort($aValuesStartWith); + $aValues = $aValuesStartWith; if (sizeof($aValues) < $iMax) { $aValuesContains = $oValuesSet->GetValuesForAutocomplete(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'contains'); asort($aValuesContains); - $iSize = sizeof($aValuesContains); + $iSize = sizeof($aValues); foreach ($aValuesContains as $sKey => $sFriendlyName) { if (!isset($aValues[$sKey])) @@ -814,7 +821,9 @@ JS elseif (!in_array($sContains, $aValues)) { $aValuesEquals = $oValuesSet->GetValuesForAutocomplete(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'equals'); - $aValues = array_merge($aValuesEquals, $aValues); + // Note: Here we cannot use array_merge as it would reindex the numeric keys starting from 0 when keys are actually the objects ID. + // As a workaround we use array_replace as it does preserve numeric keys. It's ok if some values from $aValuesEquals are replaced with values from $aValues as they contain the same data. + $aValues = array_replace($aValuesEquals, $aValues); } switch($sOutputFormat) @@ -962,7 +971,7 @@ HTML foreach (MetaModel::ListAttributeDefs($this->sTargetClass) as $sAttCode => $oAttDef) { if (($oAttDef instanceof AttributeBlob) || (false)) { $aFieldsFlags[$sAttCode] = OPT_ATT_READONLY; - $aFieldsComments[$sAttCode] = ' '; + $aFieldsComments[$sAttCode] = ' '; } } cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), array('formPrefix' => $this->iId, 'noRelations' => true, 'fieldsFlags' => $aFieldsFlags, 'fieldsComments' => $aFieldsComments)); @@ -973,9 +982,9 @@ HTML ); $oPage->add_ready_script(<<iId}').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true}); +$('#ac_create_{$this->iId}').dialog({ width: $(window).width() * 0.6, height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true}); $('#dcr_{$this->iId} form').removeAttr('onsubmit'); -$('#dcr_{$this->iId} form').on('submit.uilinksWizard', oACWidget_{$this->iId}.DoCreateObject); +$('#dcr_{$this->iId} form').find('button[type="submit"]').on('click', oACWidget_{$this->iId}.DoCreateObject); JS ); } diff --git a/application/ui.linksdirectwidget.class.inc.php b/application/ui.linksdirectwidget.class.inc.php index 1a750e3278..2bd2d16469 100644 --- a/application/ui.linksdirectwidget.class.inc.php +++ b/application/ui.linksdirectwidget.class.inc.php @@ -255,7 +255,7 @@ class UILinksWidgetDirect $oDiv = UIContentBlockUIBlockFactory::MakeStandard($this->sInputid, ['listContainer']); $oPage->AddSubBlock($oDiv); $oDatatable = DataTableUIBlockFactory::MakeForForm($this->sInputid, $aAttribs, $aData); - $oDatatable->SetOptions(['select_mode' => 'custom']); + $oDatatable->SetOptions(['select_mode' => 'custom', 'disable_hyperlinks' => true]); $oDiv->AddSubBlock($oDatatable); $sInputName = $sFormPrefix.'attr_'.$this->sAttCode; $aLabels = array( diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index 1f4e63da53..c8b599068e 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -377,33 +377,34 @@ JS $aForm = array(); $iMaxAddedId = 0; $iAddedId = -1; // Unique id for new links - while ($oCurrentLink = $oValue->Fetch()) - { + $oBlock->aRemoved = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->m_sAttCode}_tbd", '[]', 'raw_data')); + while ($oCurrentLink = $oValue->Fetch()) { // We try to retrieve the remote object as usual - $oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote), - false /* Must not be found */); - // If successful, it means that we can edit its link - if ($oLinkedObj !== null) { - $bReadOnly = false; - } // Else we retrieve it without restrictions (silos) and will display its link as readonly - else { - $bReadOnly = true; - $oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote), false /* Must not be found */, true); - } + if (!in_array($oCurrentLink->GetKey(), $oBlock->aRemoved)) { + $oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote), false /* Must not be found */); + // If successful, it means that we can edit its link + if ($oLinkedObj !== null) { + $bReadOnly = false; + } // Else we retrieve it without restrictions (silos) and will display its link as readonly + else { + $bReadOnly = true; + $oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote), false /* Must not be found */, true); + } - if ($oCurrentLink->IsNew()) { - $key = $iAddedId--; - } else { - $key = $oCurrentLink->GetKey(); - } + if ($oCurrentLink->IsNew()) { + $key = $iAddedId--; + } else { + $key = $oCurrentLink->GetKey(); + } - $iMaxAddedId = max($iMaxAddedId, $key); - $aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj, $key, $bReadOnly); + $iMaxAddedId = max($iMaxAddedId, $key); + $aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj, $key, $bReadOnly); + } } - $oBlock->iMaxAddedId = (int) $iMaxAddedId; + $oBlock->iMaxAddedId = (int)$iMaxAddedId; $oDataTable = DataTableUIBlockFactory::MakeForForm("{$this->m_sAttCode}{$this->m_sNameSuffix}", $this->m_aTableConfig, $aForm); - $oDataTable->SetOptions(['select_mode' => 'custom']); + $oDataTable->SetOptions(['select_mode' => 'custom', 'disable_hyperlinks' => true]); $oBlock->AddSubBlock($oDataTable); $oBlock->AddControls(); diff --git a/application/ui.passwordwidget.class.inc.php b/application/ui.passwordwidget.class.inc.php index 16e333ab3e..58aca4fa3e 100644 --- a/application/ui.passwordwidget.class.inc.php +++ b/application/ui.passwordwidget.class.inc.php @@ -60,8 +60,8 @@ class UIPasswordWidget $sChangedValue = (($sPasswordValue != '*****') || ($sConfirmPasswordValue != '*****')) ? 1 : 0; $sHtmlValue = ''; $sHtmlValue .= '
'; - $sHtmlValue .= ''; - $sHtmlValue .= '
'; + $sHtmlValue .= ''; + $sHtmlValue .= '
'; $sHtmlValue .= '
'; $sHtmlValue .= ''; diff --git a/application/utils.inc.php b/application/utils.inc.php index 7f761d864c..d94b79338c 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -21,6 +21,8 @@ use Combodo\iTop\Application\Helper\Session; use Combodo\iTop\Application\UI\Base\iUIBlock; use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock; use ScssPhp\ScssPhp\Compiler; +use ScssPhp\ScssPhp\OutputStyle; +use ScssPhp\ScssPhp\ValueConverter; /** @@ -97,6 +99,11 @@ class utils * @since 3.0.0 */ public const ENUM_SANITIZATION_FILTER_RAW_DATA = 'raw_data'; + /** + * @var string + * @since 3.0.2, 3.1.0 N°4899 + */ + public const ENUM_SANITIZATION_FILTER_URL = 'url'; /** * @var string @@ -377,6 +384,7 @@ class utils * @since 2.5.2 2.6.0 new 'transaction_id' filter * @since 2.7.0 new 'element_identifier' filter * @since 3.0.0 new utils::ENUM_SANITIZATION_* const + * @since 2.7.7, 3.0.2, 3.1.0 N°4899 - new 'url' filter */ protected static function Sanitize_Internal($value, $sSanitizationFilter) { @@ -454,6 +462,11 @@ class utils $retValue = preg_replace('/[^a-zA-Z0-9_]/', '', $value); break; + // For URL + case static::ENUM_SANITIZATION_FILTER_URL: + $retValue = filter_var($value, FILTER_SANITIZE_URL); + break; + default: case static::ENUM_SANITIZATION_FILTER_RAW_DATA: $retValue = $value; @@ -800,21 +813,20 @@ class utils */ public static function StringToTime($sDate, $sFormat) { - // Source: http://php.net/manual/fr/function.strftime.php + // Source: http://php.net/manual/fr/function.strftime.php // (alternative: http://www.php.net/manual/fr/datetime.formats.date.php) static $aDateTokens = null; static $aDateRegexps = null; - if (is_null($aDateTokens)) - { - $aSpec = array( - '%d' =>'(?[0-9]{2})', + if (is_null($aDateTokens)) { + $aSpec = array( + '%d' => '(?[0-9]{2})', '%m' => '(?[0-9]{2})', '%y' => '(?[0-9]{2})', '%Y' => '(?[0-9]{4})', '%H' => '(?[0-2][0-9])', '%i' => '(?[0-5][0-9])', '%s' => '(?[0-5][0-9])', - ); + ); $aDateTokens = array_keys($aSpec); $aDateRegexps = array_values($aSpec); } @@ -1814,7 +1826,7 @@ class utils */ public static function HtmlEntities($sValue) { - return htmlentities($sValue, ENT_QUOTES, 'UTF-8'); + return htmlentities($sValue ?? '', ENT_QUOTES, 'UTF-8'); } /** @@ -1830,7 +1842,7 @@ class utils public static function EscapeHtml($sValue) { return htmlspecialchars( - $sValue, + $sValue ?? '', ENT_QUOTES | ENT_DISALLOWED | ENT_HTML5, WebPage::PAGES_CHARSET, false @@ -1879,7 +1891,8 @@ class utils { $sText = str_replace("\r\n", "\n", $sText); $sText = str_replace("\r", "\n", $sText); - return str_replace("\n", '
', htmlentities($sText, ENT_QUOTES, 'UTF-8')); + + return str_replace("\n", '
', utils::EscapeHtml($sText)); } /** @@ -1930,19 +1943,23 @@ class utils public static function CompileCSSFromSASS($sSassContent, $aImportPaths = array(), $aVariables = array()) { $oSass = new Compiler(); - $oSass->setFormatter('ScssPhp\\ScssPhp\\Formatter\\Compressed'); + $oSass->setOutputStyle(OutputStyle::COMPRESSED); // Setting our variables - $oSass->setVariables($aVariables); + $aScssVariables = []; + foreach ($aVariables as $entry => $value) { + $aScssVariables[$entry] = ValueConverter::parseValue($value); + } + $oSass->addVariables($aScssVariables); // Setting our imports paths $oSass->setImportPaths($aImportPaths); // Temporary disabling max exec time while compiling $iCurrentMaxExecTime = (int) ini_get('max_execution_time'); set_time_limit(0); // Compiling SASS - $sCss = $oSass->compile($sSassContent); + $oCompilationRes = $oSass->compileString($sSassContent); set_time_limit(intval($iCurrentMaxExecTime)); - return $sCss; + return $oCompilationRes->getCss(); } /** @@ -2701,10 +2718,24 @@ HTML; $aAutoloadClassMaps = array_merge($aAutoloadClassMaps, glob(APPROOT.'env-'.utils::GetCurrentEnvironment().'/*/vendor/composer/autoload_classmap.php')); $aClassMap = []; + $aAutoloaderErrors = []; foreach ($aAutoloadClassMaps as $sAutoloadFile) { + if (false === static::RealPath($sAutoloadFile, APPROOT)) { + // can happen when we still have the autoloader symlink in env-*, but it points to a file that no longer exists + $aAutoloaderErrors[] = $sAutoloadFile; + continue; + } $aTmpClassMap = include $sAutoloadFile; + /** @noinspection SlowArrayOperationsInLoopInspection we are getting an associative array so the documented workarounds cannot be used */ $aClassMap = array_merge($aClassMap, $aTmpClassMap); } + if (count($aAutoloaderErrors) > 0) { + IssueLog::Debug( + "\utils::GetClassesForInterface cannot load some of the autoloader files", + LogChannels::CORE, + ['autoloader_errors' => $aAutoloaderErrors] + ); + } // Add already loaded classes $aCurrentClasses = array_fill_keys(get_declared_classes(), ''); @@ -2712,7 +2743,7 @@ HTML; foreach ($aClassMap as $sPHPClass => $sPHPFile) { $bSkipped = false; - + // Check if our class matches name filter, or is in an excluded path if ($sClassNameFilter !== '' && strpos($sPHPClass, $sClassNameFilter) === false) { $bSkipped = true; @@ -2809,6 +2840,54 @@ HTML; return $aPrefs[$sShortcutId]; } + //---------------------------------------------- + // PHP function helpers + //---------------------------------------------- + + /** + * Helper around the native strlen() PHP method to keep allowing usage of null value when computing the length of a string as null value is no longer allowed with PHP 8.1+ + * @link https://www.php.net/releases/8.1/en.php#deprecations_and_bc_breaks "Passing null to non-nullable internal function parameters is deprecated" + * + * @param string|null $sString + * + * @return int Length of $sString, 0 if null + * @since 3.0.2 N°5172 + */ + public static function StrLen(?string $sString): int + { + return strlen($sString ?? ''); + } + + /** + * Helper around the native strlen() PHP method to test a string for null or empty value + * + * @link https://www.php.net/releases/8.1/en.php#deprecations_and_bc_breaks "Passing null to non-nullable internal function parameters is deprecated" + * + * @param string|null $sString + * + * @return bool if string null or empty + * @since 3.0.2 N°5302 + */ + public static function IsNullOrEmptyString(?string $sString): bool + { + return $sString === null || strlen($sString) === 0; + } + + /** + * Helper around the native strlen() PHP method to test a string not null or empty value + * + * @link https://www.php.net/releases/8.1/en.php#deprecations_and_bc_breaks "Passing null to non-nullable internal function parameters is deprecated" + * + * @param string|null $sString + * + * @return bool if string is not null and not empty + * @since 3.0.2 N°5302 + */ + public static function IsNotNullOrEmptyString(?string $sString): bool + { + return !static::IsNullOrEmptyString($sString); + } + //---------------------------------------------- // Environment helpers //---------------------------------------------- diff --git a/application/wizardhelper.class.inc.php b/application/wizardhelper.class.inc.php index 998386eaf0..fd6f0a7df3 100644 --- a/application/wizardhelper.class.inc.php +++ b/application/wizardhelper.class.inc.php @@ -359,6 +359,10 @@ class WizardHelper JS; } + /* + * Function with an old pattern of code + * @deprecated 3.1.0 + */ static function ParseJsonSet($oMe, $sLinkClass, $sExtKeyToMe, $sJsonSet) { $aSet = json_decode($sJsonSet, true); // true means hash array instead of object diff --git a/composer.json b/composer.json index 5806de5fa4..52d6fe3ee5 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,10 @@ { + "name": "combodo/itop", + "description": "IT Operations Portal", "type": "project", - "license": "AGPLv3", + "license": "AGPL-3.0-only", "require": { - "php": ">=7.1.3 <8.0.0", + "php": ">=7.4.0 <8.2.0", "ext-ctype": "*", "ext-dom": "*", "ext-gd": "*", @@ -10,22 +12,26 @@ "ext-json": "*", "ext-mysqli": "*", "ext-soap": "*", - "combodo/tcpdf": "6.3.5", - "nikic/php-parser": "^4.12.0", - "pear/archive_tar": "1.4.14", - "pelago/emogrifier": "3.1.0", - "scssphp/scssphp": "1.0.6", - "swiftmailer/swiftmailer": "5.4.12", - "symfony/console": "~3.4.47", - "symfony/dotenv": "~3.4.47", - "symfony/framework-bundle": "~3.4.47", - "symfony/polyfill-php70": "1.*", - "symfony/twig-bundle": "~3.4.47", - "symfony/yaml": "~3.4.47" + "apereo/phpcas" : "~1.3", + "combodo/tcpdf": "~6.4.4", + "guzzlehttp/guzzle": "^7.4.5", + "laminas/laminas-mail": "^2.11", + "laminas/laminas-servicemanager": "^3.5", + "league/oauth2-google": "^3.0", + "nikic/php-parser": "~4.14.0", + "pear/archive_tar": "~1.4.14", + "pelago/emogrifier": "^6.0.0", + "scssphp/scssphp": "^1.10.3", + "symfony/console": "5.4.*", + "symfony/dotenv": "5.4.*", + "symfony/framework-bundle": "5.4.*", + "symfony/twig-bundle": "5.4.*", + "symfony/yaml": "5.4.*", + "thenetworg/oauth2-azure": "^2.0" }, "require-dev": { - "symfony/stopwatch": "~3.4.47", - "symfony/web-profiler-bundle": "~3.4.47" + "symfony/stopwatch": "5.4.*", + "symfony/web-profiler-bundle": "5.4.*" }, "suggest": { "ext-libsodium": "Required to use the AttributeEncryptedString.", @@ -37,7 +43,7 @@ }, "config": { "platform": { - "php": "7.1.3" + "php": "7.4.0" }, "vendor-dir": "lib", "preferred-install": { @@ -54,12 +60,7 @@ "sources" ], "exclude-from-classmap": [ - "core/dbobjectsearch.class.php", - "core/legacy/dbobjectsearchlegacy.class.php", - "core/querybuildercontext.class.inc.php", - "core/legacy/querybuildercontextlegacy.class.inc.php", - "core/querybuilderexpressions.class.inc.php", - "core/legacy/querybuilderexpressionslegacy.class.inc.php", + "application/twigextension.class.inc.php", "core/oql/build/PHP/", "core/apc-emulation.php", "application/startup.inc.php", diff --git a/composer.lock b/composer.lock index 63a0c2e1cb..a9a26c6866 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,88 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4c5cdd1e0feb4abaab8f86959ffc7a64", + "content-hash": "276d2024de344c0d4105b15850560696", "packages": [ { - "name": "combodo/tcpdf", - "version": "6.3.5", + "name": "apereo/phpcas", + "version": "1.5.0", "source": { "type": "git", - "url": "https://github.com/combodo-itop-libs/TCPDF.git", - "reference": "aedd4b7b8cf7fcc24e617c405c9d3304150f4b94" + "url": "https://github.com/apereo/phpCAS.git", + "reference": "d6f5797fb568726f34c8e48741776d81e4a2646b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/combodo-itop-libs/TCPDF/zipball/aedd4b7b8cf7fcc24e617c405c9d3304150f4b94", - "reference": "aedd4b7b8cf7fcc24e617c405c9d3304150f4b94", + "url": "https://api.github.com/repos/apereo/phpCAS/zipball/d6f5797fb568726f34c8e48741776d81e4a2646b", + "reference": "d6f5797fb568726f34c8e48741776d81e4a2646b", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-dom": "*", + "php": ">=7.1.0", + "psr/log": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "monolog/monolog": "^1.0.0 || ^2.0.0", + "phpstan/phpstan": "^1.5", + "phpunit/phpunit": ">=7.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "source/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Joachim Fritschi", + "email": "jfritschi@freenet.de", + "homepage": "https://github.com/jfritschi" + }, + { + "name": "Adam Franco", + "homepage": "https://github.com/adamfranco" + }, + { + "name": "Henry Pan", + "homepage": "https://github.com/phy25" + } + ], + "description": "Provides a simple API for authenticating users against a CAS server", + "homepage": "https://wiki.jasig.org/display/CASC/phpCAS", + "keywords": [ + "apereo", + "cas", + "jasig" + ], + "support": { + "issues": "https://github.com/apereo/phpCAS/issues", + "source": "https://github.com/apereo/phpCAS/tree/1.5.0" + }, + "time": "2022-05-03T21:12:54+00:00" + }, + { + "name": "combodo/tcpdf", + "version": "6.4.4", + "source": { + "type": "git", + "url": "https://github.com/combodo-itop-libs/TCPDF.git", + "reference": "0e31c013ccd000aa6762e9186778aa6e259ac8e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/combodo-itop-libs/TCPDF/zipball/0e31c013ccd000aa6762e9186778aa6e259ac8e8", + "reference": "0e31c013ccd000aa6762e9186778aa6e259ac8e8", "shasum": "" }, "require": { @@ -62,22 +130,984 @@ "email": "contact@combodo.com" } ], - "description": "TCPDF fork adding requirements for iTop: Specific fonts.", + "description": "TCPDF is a PHP class for generating PDF documents and barcodes.", "homepage": "https://github.com/combodo-itop-libs/TCPDF", - "time": "2020-09-28T12:19:09+00:00" + "keywords": [ + "PDFD32000-2008", + "TCPDF", + "barcodes", + "datamatrix", + "pdf", + "pdf417", + "qrcode" + ], + "support": { + "source": "https://github.com/combodo-itop-libs/TCPDF/tree/6.4.4" + }, + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_donations¤cy_code=GBP&business=paypal@tecnick.com&item_name=donation%20for%20tcpdf%20project", + "type": "custom" + } + ], + "time": "2022-03-10T14:36:39+00:00" }, { - "name": "nikic/php-parser", - "version": "v4.12.0", + "name": "firebase/php-jwt", + "version": "v6.3.0", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "6608f01670c3cc5079e18c1dab1104e002579143" + "url": "https://github.com/firebase/php-jwt.git", + "reference": "018dfc4e1da92ad8a1b90adc4893f476a3b41cb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143", - "reference": "6608f01670c3cc5079e18c1dab1104e002579143", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/018dfc4e1da92ad8a1b90adc4893f476a3b41cb8", + "reference": "018dfc4e1da92ad8a1b90adc4893f476a3b41cb8", + "shasum": "" + }, + "require": { + "php": "^7.1||^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.5||^7.4", + "phpspec/prophecy-phpunit": "^1.1", + "phpunit/phpunit": "^7.5||^9.5", + "psr/cache": "^1.0||^2.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.3.0" + }, + "time": "2022-07-15T16:48:45+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.4.5", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "1dd98b0564cb3f6bd16ce683cb755f94c10fbd82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/1dd98b0564cb3f6bd16ce683cb755f94c10fbd82", + "reference": "1dd98b0564cb3f6bd16ce683cb755f94c10fbd82", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.9 || ^2.4", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1 || ^2.0 || ^3.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" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.4-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.4.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2022-06-20T22:16:13+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.5.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2021-10-22T20:56:57+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "13388f00956b1503577598873fffb5ae994b5737" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/13388f00956b1503577598873fffb5ae994b5737", + "reference": "13388f00956b1503577598873fffb5ae994b5737", + "shasum": "" + }, + "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" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.4.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2022-06-20T21:43:11+00:00" + }, + { + "name": "laminas/laminas-loader", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-loader.git", + "reference": "d0589ec9dd48365fd95ad10d1c906efd7711c16b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-loader/zipball/d0589ec9dd48365fd95ad10d1c906efd7711c16b", + "reference": "d0589ec9dd48365fd95ad10d1c906efd7711c16b", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-loader": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.2.1", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Loader\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Autoloading and plugin loading strategies", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "loader" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-loader/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-loader/issues", + "rss": "https://github.com/laminas/laminas-loader/releases.atom", + "source": "https://github.com/laminas/laminas-loader" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-02T18:30:53+00:00" + }, + { + "name": "laminas/laminas-mail", + "version": "2.16.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-mail.git", + "reference": "1ee1a384b96c8af29ecad9b3a7adc27a150ebc49" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-mail/zipball/1ee1a384b96c8af29ecad9b3a7adc27a150ebc49", + "reference": "1ee1a384b96c8af29ecad9b3a7adc27a150ebc49", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "laminas/laminas-loader": "^2.8", + "laminas/laminas-mime": "^2.9.1", + "laminas/laminas-stdlib": "^3.6", + "laminas/laminas-validator": "^2.15", + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "symfony/polyfill-intl-idn": "^1.24.0", + "symfony/polyfill-mbstring": "^1.12.0", + "webmozart/assert": "^1.10" + }, + "conflict": { + "zendframework/zend-mail": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-crypt": "^2.6 || ^3.4", + "laminas/laminas-db": "^2.13.3", + "laminas/laminas-servicemanager": "^3.7", + "phpunit/phpunit": "^9.5.5", + "psalm/plugin-phpunit": "^0.15.1", + "symfony/process": "^5.3.7", + "vimeo/psalm": "^4.7" + }, + "suggest": { + "laminas/laminas-crypt": "Crammd5 support in SMTP Auth", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" + }, + "type": "library", + "extra": { + "laminas": { + "component": "Laminas\\Mail", + "config-provider": "Laminas\\Mail\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Mail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Provides generalized functionality to compose and send both text and MIME-compliant multipart e-mail messages", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "mail" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-mail/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-mail/issues", + "rss": "https://github.com/laminas/laminas-mail/releases.atom", + "source": "https://github.com/laminas/laminas-mail" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-02-23T21:08:17+00:00" + }, + { + "name": "laminas/laminas-mime", + "version": "2.9.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-mime.git", + "reference": "72d21a1b4bb7086d4a4d7058c0abca180b209184" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-mime/zipball/72d21a1b4bb7086d4a4d7058c0abca180b209184", + "reference": "72d21a1b4bb7086d4a4d7058c0abca180b209184", + "shasum": "" + }, + "require": { + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-mime": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.2.1", + "laminas/laminas-mail": "^2.12", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "laminas/laminas-mail": "Laminas\\Mail component" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Mime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Create and parse MIME messages and parts", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "mime" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-mime/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-mime/issues", + "rss": "https://github.com/laminas/laminas-mime/releases.atom", + "source": "https://github.com/laminas/laminas-mime" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-20T21:19:24+00:00" + }, + { + "name": "laminas/laminas-servicemanager", + "version": "3.16.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-servicemanager.git", + "reference": "863c66733740cd36ebf5e700f4258ef2c68a2a24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/863c66733740cd36ebf5e700f4258ef2c68a2a24", + "reference": "863c66733740cd36ebf5e700f4258ef2c68a2a24", + "shasum": "" + }, + "require": { + "laminas/laminas-stdlib": "^3.2.1", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0", + "psr/container": "^1.0" + }, + "conflict": { + "ext-psr": "*", + "laminas/laminas-code": "<3.3.1", + "zendframework/zend-code": "<3.3.1", + "zendframework/zend-servicemanager": "*" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "replace": { + "container-interop/container-interop": "^1.2.0" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.0", + "laminas/laminas-coding-standard": "~2.3.0", + "laminas/laminas-container-config-test": "^0.7", + "laminas/laminas-dependency-plugin": "^2.1.2", + "mikey179/vfsstream": "^1.6.10@alpha", + "ocramius/proxy-manager": "^2.11", + "phpbench/phpbench": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5.5", + "psalm/plugin-phpunit": "^0.17.0", + "vimeo/psalm": "^4.8" + }, + "suggest": { + "ocramius/proxy-manager": "ProxyManager ^2.1.1 to handle lazy initialization of services" + }, + "bin": [ + "bin/generate-deps-for-config-factory", + "bin/generate-factory-for-class" + ], + "type": "library", + "autoload": { + "files": [ + "src/autoload.php" + ], + "psr-4": { + "Laminas\\ServiceManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Factory-Driven Dependency Injection Container", + "homepage": "https://laminas.dev", + "keywords": [ + "PSR-11", + "dependency-injection", + "di", + "dic", + "laminas", + "service-manager", + "servicemanager" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-servicemanager/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-servicemanager/issues", + "rss": "https://github.com/laminas/laminas-servicemanager/releases.atom", + "source": "https://github.com/laminas/laminas-servicemanager" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-07-27T14:58:17+00:00" + }, + { + "name": "laminas/laminas-stdlib", + "version": "3.12.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-stdlib.git", + "reference": "c5aed3c798018e31fbb7b1e421b8d96bf2cda453" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/c5aed3c798018e31fbb7b1e421b8d96bf2cda453", + "reference": "c5aed3c798018e31fbb7b1e421b8d96bf2cda453", + "shasum": "" + }, + "require": { + "php": "^7.4 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-stdlib": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.3.0", + "phpbench/phpbench": "^1.2.6", + "phpstan/phpdoc-parser": "^0.5.4", + "phpunit/phpunit": "^9.5.23", + "psalm/plugin-phpunit": "^0.17.0", + "vimeo/psalm": "^4.26" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Stdlib\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "SPL extensions, array utilities, error handlers, and more", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "stdlib" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-stdlib/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-stdlib/issues", + "rss": "https://github.com/laminas/laminas-stdlib/releases.atom", + "source": "https://github.com/laminas/laminas-stdlib" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-08-22T22:55:58+00:00" + }, + { + "name": "laminas/laminas-validator", + "version": "2.23.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-validator.git", + "reference": "6d61b6cc3b222f13807a18d9247cdfb084958b03" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/6d61b6cc3b222f13807a18d9247cdfb084958b03", + "reference": "6d61b6cc3b222f13807a18d9247cdfb084958b03", + "shasum": "" + }, + "require": { + "laminas/laminas-servicemanager": "^3.12.0", + "laminas/laminas-stdlib": "^3.10", + "php": "^7.4 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-validator": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.3.0", + "laminas/laminas-db": "^2.7", + "laminas/laminas-filter": "^2.14.0", + "laminas/laminas-http": "^2.14.2", + "laminas/laminas-i18n": "^2.15.0", + "laminas/laminas-session": "^2.12.1", + "laminas/laminas-uri": "^2.9.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5.21", + "psalm/plugin-phpunit": "^0.17.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "vimeo/psalm": "^4.24.0" + }, + "suggest": { + "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator", + "laminas/laminas-filter": "Laminas\\Filter component, required by the Digits validator", + "laminas/laminas-i18n": "Laminas\\I18n component to allow translation of validation error messages", + "laminas/laminas-i18n-resources": "Translations of validator messages", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "laminas/laminas-session": "Laminas\\Session component, ^2.8; required by the Csrf validator", + "laminas/laminas-uri": "Laminas\\Uri component, required by the Uri and Sitemap\\Loc validators", + "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators" + }, + "type": "library", + "extra": { + "laminas": { + "component": "Laminas\\Validator", + "config-provider": "Laminas\\Validator\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Validator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "validator" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-validator/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-validator/issues", + "rss": "https://github.com/laminas/laminas-validator/releases.atom", + "source": "https://github.com/laminas/laminas-validator" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-07-27T19:17:59+00:00" + }, + { + "name": "league/oauth2-client", + "version": "2.6.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-client.git", + "reference": "2334c249907190c132364f5dae0287ab8666aa19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/2334c249907190c132364f5dae0287ab8666aa19", + "reference": "2334c249907190c132364f5dae0287ab8666aa19", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "paragonie/random_compat": "^1 || ^2 || ^9.99", + "php": "^5.6 || ^7.0 || ^8.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" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + ], + "description": "OAuth 2.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "identity", + "idp", + "oauth", + "oauth2", + "single sign on" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-client/issues", + "source": "https://github.com/thephpleague/oauth2-client/tree/2.6.1" + }, + "time": "2021-12-22T16:42:49+00:00" + }, + { + "name": "league/oauth2-google", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-google.git", + "reference": "6b79441f244040760bed5fdcd092a2bda7cf34c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-google/zipball/6b79441f244040760bed5fdcd092a2bda7cf34c6", + "reference": "6b79441f244040760bed5fdcd092a2bda7cf34c6", + "shasum": "" + }, + "require": { + "league/oauth2-client": "^2.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^2.0", + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^6.0", + "squizlabs/php_codesniffer": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com", + "homepage": "http://shadowhand.me" + } + ], + "description": "Google OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "Authentication", + "authorization", + "client", + "google", + "oauth", + "oauth2" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-google/issues", + "source": "https://github.com/thephpleague/oauth2-google/tree/3.0.4" + }, + "time": "2021-01-27T16:09:03+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.14.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", "shasum": "" }, "require": { @@ -118,39 +1148,35 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0" }, - "time": "2021-07-21T10:44:31+00:00" + "time": "2022-05-31T20:59:12+00:00" }, { "name": "paragonie/random_compat", - "version": "v2.0.18", + "version": "v9.99.100", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db" + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/0a58ef6e3146256cc3dc7cc393927bcc7d1b72db", - "reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", "shasum": "" }, "require": { - "php": ">=5.2.0" + "php": ">= 7" }, "require-dev": { - "phpunit/phpunit": "4.*|5.*" + "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", - "autoload": { - "files": [ - "lib/random.php" - ] - }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" @@ -169,7 +1195,12 @@ "pseudorandom", "random" ], - "time": "2019-01-03T20:59:08+00:00" + "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" }, { "name": "pear/archive_tar", @@ -304,16 +1335,16 @@ }, { "name": "pear/pear-core-minimal", - "version": "v1.10.10", + "version": "v1.10.11", "source": { "type": "git", "url": "https://github.com/pear/pear-core-minimal.git", - "reference": "625a3c429d9b2c1546438679074cac1b089116a7" + "reference": "68d0d32ada737153b7e93b8d3c710ebe70ac867d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pear/pear-core-minimal/zipball/625a3c429d9b2c1546438679074cac1b089116a7", - "reference": "625a3c429d9b2c1546438679074cac1b089116a7", + "url": "https://api.github.com/repos/pear/pear-core-minimal/zipball/68d0d32ada737153b7e93b8d3c710ebe70ac867d", + "reference": "68d0d32ada737153b7e93b8d3c710ebe70ac867d", "shasum": "" }, "require": { @@ -348,7 +1379,7 @@ "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR", "source": "https://github.com/pear/pear-core-minimal" }, - "time": "2019-11-19T19:00:24+00:00" + "time": "2021-08-10T22:31:03+00:00" }, { "name": "pear/pear_exception", @@ -411,39 +1442,39 @@ }, { "name": "pelago/emogrifier", - "version": "v3.1.0", + "version": "v6.0.0", "source": { "type": "git", "url": "https://github.com/MyIntervals/emogrifier.git", - "reference": "f6a5c7d44612d86c3901c93f1592f5440e6b2cd8" + "reference": "aa72d5407efac118f3896bcb995a2cba793df0ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/f6a5c7d44612d86c3901c93f1592f5440e6b2cd8", - "reference": "f6a5c7d44612d86c3901c93f1592f5440e6b2cd8", + "url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/aa72d5407efac118f3896bcb995a2cba793df0ae", + "reference": "aa72d5407efac118f3896bcb995a2cba793df0ae", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", - "php": "^5.6 || ~7.0 || ~7.1 || ~7.2 || ~7.3 || ~7.4", - "symfony/css-selector": "^2.8 || ^3.0 || ^4.0 || ^5.0" + "php": "~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0", + "sabberworm/php-css-parser": "^8.3.1", + "symfony/css-selector": "^3.4.32 || ^4.4 || ^5.3 || ^6.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.15.3", - "phpmd/phpmd": "^2.7.0", - "phpunit/phpunit": "^5.7.27", - "squizlabs/php_codesniffer": "^3.5.0" + "php-parallel-lint/php-parallel-lint": "^1.3.0", + "phpunit/phpunit": "^8.5.16", + "rawr/cross-data-providers": "^2.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0.x-dev" + "dev-main": "7.0.x-dev" } }, "autoload": { "psr-4": { - "Pelago\\": "src/" + "Pelago\\Emogrifier\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -485,7 +1516,7 @@ "issues": "https://github.com/MyIntervals/emogrifier/issues", "source": "https://github.com/MyIntervals/emogrifier" }, - "time": "2019-12-26T19:37:31+00:00" + "time": "2021-09-16T16:22:04+00:00" }, { "name": "psr/cache", @@ -531,20 +1562,228 @@ "psr", "psr-6" ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, "time": "2016-08-06T20:24:11+00:00" }, { "name": "psr/container", - "version": "1.0.0", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", "shasum": "" }, "require": { @@ -558,7 +1797,7 @@ }, "autoload": { "psr-4": { - "Psr\\Container\\": "src/" + "Psr\\Http\\Message\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -571,29 +1810,33 @@ "homepage": "http://www.php-fig.org/" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" ], - "time": "2017-02-14T16:28:37+00:00" + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" }, { "name": "psr/log", - "version": "1.1.2", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", - "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", "shasum": "" }, "require": { @@ -617,7 +1860,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for logging libraries", @@ -627,34 +1870,84 @@ "psr", "psr-3" ], - "time": "2019-11-01T11:05:21+00:00" + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" }, { - "name": "psr/simple-cache", - "version": "1.0.1", + "name": "ralouphie/getallheaders", + "version": "3.0.3", "source": { "type": "git", - "url": "https://github.com/php-fig/simple-cache.git", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "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" + }, + { + "name": "sabberworm/php-css-parser", + "version": "8.4.0", + "source": { + "type": "git", + "url": "https://github.com/sabberworm/PHP-CSS-Parser.git", + "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/e41d2140031d533348b2192a83f02d8dd8a71d30", + "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=5.6.20" + }, + "require-dev": { + "codacy/coverage": "^1.4", + "phpunit/phpunit": "^4.8.36" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" + }, + "type": "library", "autoload": { "psr-4": { - "Psr\\SimpleCache\\": "src/" + "Sabberworm\\CSS\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -663,32 +1956,34 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Raphael Schweikert" } ], - "description": "Common interfaces for simple caching", + "description": "Parser for CSS Files written in PHP", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", "keywords": [ - "cache", - "caching", - "psr", - "psr-16", - "simple-cache" + "css", + "parser", + "stylesheet" ], - "time": "2017-10-23T01:57:42+00:00" + "support": { + "issues": "https://github.com/sabberworm/PHP-CSS-Parser/issues", + "source": "https://github.com/sabberworm/PHP-CSS-Parser/tree/8.4.0" + }, + "time": "2021-12-11T13:40:54+00:00" }, { "name": "scssphp/scssphp", - "version": "1.0.6", + "version": "v1.10.5", "source": { "type": "git", "url": "https://github.com/scssphp/scssphp.git", - "reference": "5b3c9d704950d8f9637f5110c36c281ec47dc13c" + "reference": "6d44282ccf283e133ab70b6282f8e068ff2f9bf9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scssphp/scssphp/zipball/5b3c9d704950d8f9637f5110c36c281ec47dc13c", - "reference": "5b3c9d704950d8f9637f5110c36c281ec47dc13c", + "url": "https://api.github.com/repos/scssphp/scssphp/zipball/6d44282ccf283e133ab70b6282f8e068ff2f9bf9", + "reference": "6d44282ccf283e133ab70b6282f8e068ff2f9bf9", "shasum": "" }, "require": { @@ -697,15 +1992,30 @@ "php": ">=5.6.0" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.3", - "squizlabs/php_codesniffer": "~2.5", - "twbs/bootstrap": "~4.3", + "bamarni/composer-bin-plugin": "^1.4", + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.3 || ^9.4", + "sass/sass-spec": "*", + "squizlabs/php_codesniffer": "~3.5", + "symfony/phpunit-bridge": "^5.1", + "thoughtbot/bourbon": "^7.0", + "twbs/bootstrap": "~5.0", + "twbs/bootstrap4": "4.6.1", "zurb/foundation": "~6.5" }, + "suggest": { + "ext-iconv": "Can be used as fallback when ext-mbstring is not available", + "ext-mbstring": "For best performance, mbstring should be installed as it is faster than ext-iconv" + }, "bin": [ "bin/pscss" ], "type": "library", + "extra": { + "bamarni-bin": { + "forward-command": false, + "bin-links": false + } + }, "autoload": { "psr-4": { "ScssPhp\\ScssPhp\\": "src/" @@ -736,95 +2046,60 @@ "scss", "stylesheet" ], - "time": "2019-12-12T05:00:52+00:00" - }, - { - "name": "swiftmailer/swiftmailer", - "version": "v5.4.12", - "source": { - "type": "git", - "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "181b89f18a90f8925ef805f950d47a7190e9b950" + "support": { + "issues": "https://github.com/scssphp/scssphp/issues", + "source": "https://github.com/scssphp/scssphp/tree/v1.10.5" }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/181b89f18a90f8925ef805f950d47a7190e9b950", - "reference": "181b89f18a90f8925ef805f950d47a7190e9b950", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "mockery/mockery": "~0.9.1", - "symfony/phpunit-bridge": "~3.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.4-dev" - } - }, - "autoload": { - "files": [ - "lib/swift_required.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Chris Corbyn" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Swiftmailer, free feature-rich PHP mailer", - "homepage": "https://swiftmailer.symfony.com", - "keywords": [ - "email", - "mail", - "mailer" - ], - "time": "2018-07-31T09:26:32+00:00" + "time": "2022-07-27T15:52:39+00:00" }, { "name": "symfony/cache", - "version": "v3.4.47", + "version": "v5.4.11", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "a7a14c4832760bd1fbd31be2859ffedc9b6ff813" + "reference": "5a0fff46df349f0db3fe242263451fddf5277362" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/a7a14c4832760bd1fbd31be2859ffedc9b6ff813", - "reference": "a7a14c4832760bd1fbd31be2859ffedc9b6ff813", + "url": "https://api.github.com/repos/symfony/cache/zipball/5a0fff46df349f0db3fe242263451fddf5277362", + "reference": "5a0fff46df349f0db3fe242263451fddf5277362", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "psr/cache": "~1.0", - "psr/log": "~1.0", - "psr/simple-cache": "^1.0", - "symfony/polyfill-apcu": "~1.1" + "php": ">=7.2.5", + "psr/cache": "^1.0|^2.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^1.1.7|^2", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/var-exporter": "^4.4|^5.0|^6.0" }, "conflict": { - "symfony/var-dumper": "<3.3" + "doctrine/dbal": "<2.13.1", + "symfony/dependency-injection": "<4.4", + "symfony/http-kernel": "<4.4", + "symfony/var-dumper": "<4.4" }, "provide": { - "psr/cache-implementation": "1.0", - "psr/simple-cache-implementation": "1.0" + "psr/cache-implementation": "1.0|2.0", + "psr/simple-cache-implementation": "1.0|2.0", + "symfony/cache-implementation": "1.0|2.0" }, "require-dev": { "cache/integration-tests": "dev-master", - "doctrine/cache": "^1.6", - "doctrine/dbal": "^2.4|^3.0", - "predis/predis": "^1.0" + "doctrine/cache": "^1.6|^2.0", + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1", + "psr/simple-cache": "^1.0|^2.0", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/filesystem": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/messenger": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" }, "type": "library", "autoload": { @@ -849,14 +2124,14 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Cache component with PSR-6, PSR-16, and tags", + "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", "homepage": "https://symfony.com", "keywords": [ "caching", "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v3.4.47" + "source": "https://github.com/symfony/cache/tree/v5.4.11" }, "funding": [ { @@ -872,40 +2147,43 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-07-28T15:25:17+00:00" }, { - "name": "symfony/class-loader", - "version": "v3.4.47", + "name": "symfony/cache-contracts", + "version": "v2.5.2", "source": { "type": "git", - "url": "https://github.com/symfony/class-loader.git", - "reference": "a22265a9f3511c0212bf79f54910ca5a77c0e92c" + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/a22265a9f3511c0212bf79f54910ca5a77c0e92c", - "reference": "a22265a9f3511c0212bf79f54910ca5a77c0e92c", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/64be4a7acb83b6f2bf6de9a02cee6dad41277ebc", + "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" - }, - "require-dev": { - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/polyfill-apcu": "~1.1" + "php": ">=7.2.5", + "psr/cache": "^1.0|^2.0|^3.0" }, "suggest": { - "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM" + "symfony/cache-implementation": "" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, "autoload": { "psr-4": { - "Symfony\\Component\\ClassLoader\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Contracts\\Cache\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -913,18 +2191,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony ClassLoader Component", + "description": "Generic abstractions related to caching", "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], "support": { - "source": "https://github.com/symfony/class-loader/tree/v3.4.47" + "source": "https://github.com/symfony/cache-contracts/tree/v2.5.2" }, "funding": [ { @@ -940,36 +2226,39 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-01-02T09:53:40+00:00" }, { "name": "symfony/config", - "version": "v3.4.47", + "version": "v5.4.11", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "bc6b3fd3930d4b53a60b42fe2ed6fc466b75f03f" + "reference": "ec79e03125c1d2477e43dde8528535d90cc78379" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/bc6b3fd3930d4b53a60b42fe2ed6fc466b75f03f", - "reference": "bc6b3fd3930d4b53a60b42fe2ed6fc466b75f03f", + "url": "https://api.github.com/repos/symfony/config/zipball/ec79e03125c1d2477e43dde8528535d90cc78379", + "reference": "ec79e03125c1d2477e43dde8528535d90cc78379", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/filesystem": "~2.8|~3.0|~4.0", - "symfony/polyfill-ctype": "~1.8" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/filesystem": "^4.4|^5.0|^6.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22" }, "conflict": { - "symfony/dependency-injection": "<3.3", - "symfony/finder": "<3.3" + "symfony/finder": "<4.4" }, "require-dev": { - "symfony/dependency-injection": "~3.3|~4.0", - "symfony/event-dispatcher": "~3.3|~4.0", - "symfony/finder": "~3.3|~4.0", - "symfony/yaml": "~3.0|~4.0" + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/messenger": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/yaml": "^4.4|^5.0|^6.0" }, "suggest": { "symfony/yaml": "To use the yaml reference dumper" @@ -997,10 +2286,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Config Component", + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v3.4.47" + "source": "https://github.com/symfony/config/tree/v5.4.11" }, "funding": [ { @@ -1016,41 +2305,50 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-07-20T13:00:38+00:00" }, { "name": "symfony/console", - "version": "v3.4.47", + "version": "v5.4.11", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "a10b1da6fc93080c180bba7219b5ff5b7518fe81" + "reference": "535846c7ee6bc4dd027ca0d93220601456734b10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/a10b1da6fc93080c180bba7219b5ff5b7518fe81", - "reference": "a10b1da6fc93080c180bba7219b5ff5b7518fe81", + "url": "https://api.github.com/repos/symfony/console/zipball/535846c7ee6bc4dd027ca0d93220601456734b10", + "reference": "535846c7ee6bc4dd027ca0d93220601456734b10", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/debug": "~2.8|~3.0|~4.0", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" }, "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/process": "<3.3" + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" }, "provide": { - "psr/log-implementation": "1.0" + "psr/log-implementation": "1.0|2.0" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~3.3|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~2.8|~3.0|~4.0", - "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.3|~4.0" + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" }, "suggest": { "psr/log": "For using the console logger", @@ -1081,10 +2379,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Console Component", + "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], "support": { - "source": "https://github.com/symfony/console/tree/v3.4.47" + "source": "https://github.com/symfony/console/tree/v5.4.11" }, "funding": [ { @@ -1100,24 +2404,25 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-07-22T10:42:43+00:00" }, { "name": "symfony/css-selector", - "version": "v3.4.47", + "version": "v5.4.11", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "da3d9da2ce0026771f5fe64cb332158f1bd2bc33" + "reference": "c1681789f059ab756001052164726ae88512ae3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/da3d9da2ce0026771f5fe64cb332158f1bd2bc33", - "reference": "da3d9da2ce0026771f5fe64cb332158f1bd2bc33", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/c1681789f059ab756001052164726ae88512ae3d", + "reference": "c1681789f059ab756001052164726ae88512ae3d", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -1146,10 +2451,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony CssSelector Component", + "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v3.4.47" + "source": "https://github.com/symfony/css-selector/tree/v5.4.11" }, "funding": [ { @@ -1165,107 +2470,45 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" - }, - { - "name": "symfony/debug", - "version": "v3.4.47", - "source": { - "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "ab42889de57fdfcfcc0759ab102e2fd4ea72dcae" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/ab42889de57fdfcfcc0759ab102e2fd4ea72dcae", - "reference": "ab42889de57fdfcfcc0759ab102e2fd4ea72dcae", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8", - "psr/log": "~1.0" - }, - "conflict": { - "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" - }, - "require-dev": { - "symfony/http-kernel": "~2.8|~3.0|~4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Debug\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Debug Component", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/debug/tree/v3.4.47" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-06-27T16:58:25+00:00" }, { "name": "symfony/dependency-injection", - "version": "v3.4.47", + "version": "v5.4.11", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "51d2a2708c6ceadad84393f8581df1dcf9e5e84b" + "reference": "a8b9251016e9476db73e25fa836904bc0bf74c62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/51d2a2708c6ceadad84393f8581df1dcf9e5e84b", - "reference": "51d2a2708c6ceadad84393f8581df1dcf9e5e84b", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a8b9251016e9476db73e25fa836904bc0bf74c62", + "reference": "a8b9251016e9476db73e25fa836904bc0bf74c62", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "psr/container": "^1.0" + "php": ">=7.2.5", + "psr/container": "^1.1.1", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22", + "symfony/service-contracts": "^1.1.6|^2" }, "conflict": { - "symfony/config": "<3.3.7", - "symfony/finder": "<3.3", - "symfony/proxy-manager-bridge": "<3.4", - "symfony/yaml": "<3.4" + "ext-psr": "<1.1|>=2", + "symfony/config": "<5.3", + "symfony/finder": "<4.4", + "symfony/proxy-manager-bridge": "<4.4", + "symfony/yaml": "<4.4.26" }, "provide": { - "psr/container-implementation": "1.0" + "psr/container-implementation": "1.0", + "symfony/service-implementation": "1.0|2.0" }, "require-dev": { - "symfony/config": "~3.3|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/yaml": "~3.4|~4.0" + "symfony/config": "^5.3|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/yaml": "^4.4.26|^5.0|^6.0" }, "suggest": { "symfony/config": "", @@ -1297,10 +2540,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony DependencyInjection Component", + "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v3.4.47" + "source": "https://github.com/symfony/dependency-injection/tree/v5.4.11" }, "funding": [ { @@ -1316,27 +2559,96 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-07-20T13:00:38+00:00" }, { - "name": "symfony/dotenv", - "version": "v3.4.47", + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", "source": { "type": "git", - "url": "https://github.com/symfony/dotenv.git", - "reference": "1022723ac4f56b001d99691d96c6025dbf1404f1" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/1022723ac4f56b001d99691d96c6025dbf1404f1", - "reference": "1022723ac4f56b001d99691d96c6025dbf1404f1", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" + }, + { + "name": "symfony/dotenv", + "version": "v5.4.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/dotenv.git", + "reference": "83a2310904a4f5d4f42526227b5a578ac82232a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/83a2310904a4f5d4f42526227b5a578ac82232a9", + "reference": "83a2310904a4f5d4f42526227b5a578ac82232a9", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3" }, "require-dev": { - "symfony/process": "^3.4.2|^4.0" + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0" }, "type": "library", "autoload": { @@ -1369,7 +2681,7 @@ "environment" ], "support": { - "source": "https://github.com/symfony/dotenv/tree/v3.4.47" + "source": "https://github.com/symfony/dotenv/tree/v5.4.5" }, "funding": [ { @@ -1385,35 +2697,115 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-02-15T17:04:12+00:00" }, { - "name": "symfony/event-dispatcher", - "version": "v3.4.47", + "name": "symfony/error-handler", + "version": "v5.4.11", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "31fde73757b6bad247c54597beef974919ec6860" + "url": "https://github.com/symfony/error-handler.git", + "reference": "f75d17cb4769eb38cd5fccbda95cd80a054d35c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/31fde73757b6bad247c54597beef974919ec6860", - "reference": "31fde73757b6bad247c54597beef974919ec6860", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/f75d17cb4769eb38cd5fccbda95cd80a054d35c8", + "reference": "f75d17cb4769eb38cd5fccbda95cd80a054d35c8", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" - }, - "conflict": { - "symfony/dependency-injection": "<3.3" + "php": ">=7.2.5", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^4.4|^5.0|^6.0" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~2.8|~3.0|~4.0", - "symfony/debug": "~3.4|~4.4", - "symfony/dependency-injection": "~3.3|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/stopwatch": "~2.8|~3.0|~4.0" + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/serializer": "^4.4|^5.0|^6.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v5.4.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-29T07:37:50+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v5.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", + "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/event-dispatcher-contracts": "^2|^3", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^4.4|^5.0|^6.0" }, "suggest": { "symfony/dependency-injection": "", @@ -1442,10 +2834,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony EventDispatcher Component", + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v3.4.47" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.9" }, "funding": [ { @@ -1461,25 +2853,106 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-05-05T16:45:39+00:00" }, { - "name": "symfony/filesystem", - "version": "v3.4.47", + "name": "symfony/event-dispatcher-contracts", + "version": "v2.5.2", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "e58d7841cddfed6e846829040dca2cca0ebbbbb3" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/e58d7841cddfed6e846829040dca2cca0ebbbbb3", - "reference": "e58d7841cddfed6e846829040dca2cca0ebbbbb3", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/f98b54df6ad059855739db6fcbc2d36995283fe1", + "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8" + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "6699fb0228d1bc35b12aed6dd5e7455457609ddd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/6699fb0228d1bc35b12aed6dd5e7455457609ddd", + "reference": "6699fb0228d1bc35b12aed6dd5e7455457609ddd", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -1504,10 +2977,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Filesystem Component", + "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v3.4.47" + "source": "https://github.com/symfony/filesystem/tree/v5.4.11" }, "funding": [ { @@ -1523,24 +2996,26 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-07-20T13:00:38+00:00" }, { "name": "symfony/finder", - "version": "v3.4.47", + "version": "v5.4.11", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "b6b6ad3db3edb1b4b1c1896b1975fb684994de6e" + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/b6b6ad3db3edb1b4b1c1896b1975fb684994de6e", - "reference": "b6b6ad3db3edb1b4b1c1896b1975fb684994de6e", + "url": "https://api.github.com/repos/symfony/finder/zipball/7872a66f57caffa2916a584db1aa7f12adc76f8c", + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -1565,10 +3040,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Finder Component", + "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v3.4.47" + "source": "https://github.com/symfony/finder/tree/v5.4.11" }, "funding": [ { @@ -1584,80 +3059,104 @@ "type": "tidelift" } ], - "time": "2020-11-16T17:02:08+00:00" + "time": "2022-07-29T07:37:50+00:00" }, { "name": "symfony/framework-bundle", - "version": "v3.4.47", + "version": "v5.4.11", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "6c95e747b75ddd2af61152ce93bf87299d15710e" + "reference": "a0660b602357d5c2ceaac1c9f80c5820bbff803d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/6c95e747b75ddd2af61152ce93bf87299d15710e", - "reference": "6c95e747b75ddd2af61152ce93bf87299d15710e", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/a0660b602357d5c2ceaac1c9f80c5820bbff803d", + "reference": "a0660b602357d5c2ceaac1c9f80c5820bbff803d", "shasum": "" }, "require": { "ext-xml": "*", - "php": "^5.5.9|>=7.0.8", - "symfony/cache": "~3.4.31|^4.3.4", - "symfony/class-loader": "~3.2", - "symfony/config": "^3.4.31|^4.3.4", - "symfony/debug": "~2.8|~3.0|~4.0", - "symfony/dependency-injection": "^3.4.24|^4.2.5", - "symfony/event-dispatcher": "~3.4|~4.0", - "symfony/filesystem": "~2.8|~3.0|~4.0", - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/http-foundation": "^3.4.38|^4.3", - "symfony/http-kernel": "^3.4.44|^4.3.4", + "php": ">=7.2.5", + "symfony/cache": "^5.2|^6.0", + "symfony/config": "^5.3|^6.0", + "symfony/dependency-injection": "^5.4.5|^6.0.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/error-handler": "^4.4.1|^5.0.1|^6.0", + "symfony/event-dispatcher": "^5.1|^6.0", + "symfony/filesystem": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^5.3|^6.0", + "symfony/http-kernel": "^5.4|^6.0", "symfony/polyfill-mbstring": "~1.0", - "symfony/routing": "^3.4.5|^4.0.5" + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22", + "symfony/routing": "^5.3|^6.0" }, "conflict": { - "phpdocumentor/reflection-docblock": "<3.0", - "phpdocumentor/type-resolver": "<0.2.1", - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", - "symfony/asset": "<3.3", - "symfony/console": "<3.4", - "symfony/form": "<3.4", - "symfony/property-info": "<3.3", - "symfony/serializer": "<3.3", - "symfony/stopwatch": "<3.4", - "symfony/translation": "<3.4", - "symfony/validator": "<3.4", - "symfony/workflow": "<3.3" + "doctrine/annotations": "<1.13.1", + "doctrine/cache": "<1.11", + "doctrine/persistence": "<1.3", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "phpunit/phpunit": "<5.4.3", + "symfony/asset": "<5.3", + "symfony/console": "<5.2.5", + "symfony/dom-crawler": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/form": "<5.2", + "symfony/http-client": "<4.4", + "symfony/lock": "<4.4", + "symfony/mailer": "<5.2", + "symfony/messenger": "<5.4", + "symfony/mime": "<4.4", + "symfony/property-access": "<5.3", + "symfony/property-info": "<4.4", + "symfony/security-csrf": "<5.3", + "symfony/serializer": "<5.2", + "symfony/service-contracts": ">=3.0", + "symfony/stopwatch": "<4.4", + "symfony/translation": "<5.3", + "symfony/twig-bridge": "<4.4", + "symfony/twig-bundle": "<4.4", + "symfony/validator": "<5.2", + "symfony/web-profiler-bundle": "<4.4", + "symfony/workflow": "<5.2" }, "require-dev": { - "doctrine/annotations": "~1.7", - "doctrine/cache": "~1.0", - "fig/link-util": "^1.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0", - "symfony/asset": "~3.3|~4.0", - "symfony/browser-kit": "~2.8|~3.0|~4.0", - "symfony/console": "~3.4.31|^4.3.4", - "symfony/css-selector": "~2.8|~3.0|~4.0", - "symfony/dom-crawler": "~2.8|~3.0|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/form": "^3.4.31|^4.3.4", - "symfony/lock": "~3.4|~4.0", + "doctrine/annotations": "^1.13.1", + "doctrine/cache": "^1.11|^2.0", + "doctrine/persistence": "^1.3|^2|^3", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^5.3|^6.0", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/console": "^5.4.9|^6.0.9", + "symfony/css-selector": "^4.4|^5.0|^6.0", + "symfony/dom-crawler": "^4.4.30|^5.3.7|^6.0", + "symfony/dotenv": "^5.1|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/form": "^5.2|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/mailer": "^5.2|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/notifier": "^5.4|^6.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/process": "~2.8|~3.0|~4.0", - "symfony/property-info": "~3.3|~4.0", - "symfony/security-core": "~3.2|~4.0", - "symfony/security-csrf": "^2.8.31|^3.3.13|~4.0", - "symfony/serializer": "~3.3|~4.0", - "symfony/stopwatch": "~3.4|~4.0", - "symfony/templating": "~2.8|~3.0|~4.0", - "symfony/translation": "~3.4|~4.0", - "symfony/validator": "~3.4|~4.0", - "symfony/var-dumper": "~3.3|~4.0", - "symfony/web-link": "~3.3|~4.0", - "symfony/workflow": "~3.3|~4.0", - "symfony/yaml": "~3.2|~4.0", - "twig/twig": "~1.34|~2.4" + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/property-info": "^4.4|^5.0|^6.0", + "symfony/rate-limiter": "^5.2|^6.0", + "symfony/security-bundle": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0", + "symfony/string": "^5.0|^6.0", + "symfony/translation": "^5.3|^6.0", + "symfony/twig-bundle": "^4.4|^5.0|^6.0", + "symfony/validator": "^5.2|^6.0", + "symfony/web-link": "^4.4|^5.0|^6.0", + "symfony/workflow": "^5.2|^6.0", + "symfony/yaml": "^4.4|^5.0|^6.0", + "twig/twig": "^2.10|^3.0" }, "suggest": { "ext-apcu": "For best performance of the system caches", @@ -1692,10 +3191,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony FrameworkBundle", + "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v3.4.47" + "source": "https://github.com/symfony/framework-bundle/tree/v5.4.11" }, "funding": [ { @@ -1711,29 +3210,36 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-07-20T13:00:38+00:00" }, { "name": "symfony/http-foundation", - "version": "v3.4.47", + "version": "v5.4.11", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "b9885fcce6fe494201da4f70a9309770e9d13dc8" + "reference": "0a5868e0999e9d47859ba3d918548ff6943e6389" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/b9885fcce6fe494201da4f70a9309770e9d13dc8", - "reference": "b9885fcce6fe494201da4f70a9309770e9d13dc8", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/0a5868e0999e9d47859ba3d918548ff6943e6389", + "reference": "0a5868e0999e9d47859ba3d918548ff6943e6389", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php70": "~1.6" + "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "symfony/expression-language": "~2.8|~3.0|~4.0" + "predis/predis": "~1.0", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/mime": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/mime": "To use the file extension guesser" }, "type": "library", "autoload": { @@ -1758,10 +3264,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony HttpFoundation Component", + "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v3.4.47" + "source": "https://github.com/symfony/http-foundation/tree/v5.4.11" }, "funding": [ { @@ -1777,65 +3283,75 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-07-20T13:00:38+00:00" }, { "name": "symfony/http-kernel", - "version": "v3.4.49", + "version": "v5.4.11", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "5aa72405f5bd5583c36ed6e756acb17d3f98ac40" + "reference": "4fd590a2ef3f62560dbbf6cea511995dd77321ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/5aa72405f5bd5583c36ed6e756acb17d3f98ac40", - "reference": "5aa72405f5bd5583c36ed6e756acb17d3f98ac40", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/4fd590a2ef3f62560dbbf6cea511995dd77321ee", + "reference": "4fd590a2ef3f62560dbbf6cea511995dd77321ee", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "psr/log": "~1.0", - "symfony/debug": "^3.3.3|~4.0", - "symfony/event-dispatcher": "~2.8|~3.0|~4.0", - "symfony/http-foundation": "~3.4.12|~4.0.12|^4.1.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php56": "~1.8" + "php": ">=7.2.5", + "psr/log": "^1|^2", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^5.0|^6.0", + "symfony/http-foundation": "^5.3.7|^6.0", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16" }, "conflict": { - "symfony/config": "<2.8", - "symfony/dependency-injection": "<3.4.10|<4.0.10,>=4", - "symfony/var-dumper": "<3.3", - "twig/twig": "<1.34|<2.4,>=2" + "symfony/browser-kit": "<5.4", + "symfony/cache": "<5.0", + "symfony/config": "<5.0", + "symfony/console": "<4.4", + "symfony/dependency-injection": "<5.3", + "symfony/doctrine-bridge": "<5.0", + "symfony/form": "<5.0", + "symfony/http-client": "<5.0", + "symfony/mailer": "<5.0", + "symfony/messenger": "<5.0", + "symfony/translation": "<5.0", + "symfony/twig-bridge": "<5.0", + "symfony/validator": "<5.0", + "twig/twig": "<2.13" }, "provide": { - "psr/log-implementation": "1.0" + "psr/log-implementation": "1.0|2.0" }, "require-dev": { - "psr/cache": "~1.0", - "symfony/browser-kit": "~2.8|~3.0|~4.0", - "symfony/class-loader": "~2.8|~3.0", - "symfony/config": "~2.8|~3.0|~4.0", - "symfony/console": "~2.8|~3.0|~4.0", - "symfony/css-selector": "~2.8|~3.0|~4.0", - "symfony/dependency-injection": "^3.4.10|^4.0.10", - "symfony/dom-crawler": "~2.8|~3.0|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/process": "~2.8|~3.0|~4.0", - "symfony/routing": "~3.4|~4.0", - "symfony/stopwatch": "~2.8|~3.0|~4.0", - "symfony/templating": "~2.8|~3.0|~4.0", - "symfony/translation": "~2.8|~3.0|~4.0", - "symfony/var-dumper": "~3.3|~4.0" + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/config": "^5.0|^6.0", + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/css-selector": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.3|^6.0", + "symfony/dom-crawler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/http-client-contracts": "^1.1|^2|^3", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/routing": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0", + "symfony/translation": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2|^3", + "twig/twig": "^2.13|^3.0.4" }, "suggest": { "symfony/browser-kit": "", "symfony/config": "", "symfony/console": "", - "symfony/dependency-injection": "", - "symfony/finder": "", - "symfony/var-dumper": "" + "symfony/dependency-injection": "" }, "type": "library", "autoload": { @@ -1860,10 +3376,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony HttpKernel Component", + "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v3.4.49" + "source": "https://github.com/symfony/http-kernel/tree/v5.4.11" }, "funding": [ { @@ -1879,29 +3395,35 @@ "type": "tidelift" } ], - "time": "2021-05-19T12:06:59+00:00" + "time": "2022-07-29T12:30:22+00:00" }, { - "name": "symfony/polyfill-apcu", - "version": "v1.19.0", + "name": "symfony/polyfill-ctype", + "version": "v1.26.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-apcu.git", - "reference": "b44b51e7814c23bfbd793a16ead5d7ce43ed23c5" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/b44b51e7814c23bfbd793a16ead5d7ce43ed23c5", - "reference": "b44b51e7814c23bfbd793a16ead5d7ce43ed23c5", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.19-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1912,89 +3434,9 @@ "files": [ "bootstrap.php" ], - "psr-4": { - "Symfony\\Polyfill\\Apcu\\": "" - } - }, - "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 apcu_* functions to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "apcu", - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-apcu/tree/v1.19.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-10-21T09:57:48+00:00" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.19.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "aed596913b70fae57be53d86faa2e9ef85a2297b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/aed596913b70fae57be53d86faa2e9ef85a2297b", - "reference": "aed596913b70fae57be53d86faa2e9ef85a2297b", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.19-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" - }, - "files": [ - "bootstrap.php" - ] + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2019,7 +3461,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.19.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" }, "funding": [ { @@ -2035,32 +3477,32 @@ "type": "tidelift" } ], - "time": "2020-10-23T09:01:57+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.19.0", + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.26.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "b5f7b932ee6fa802fc792eabd77c4c88084517ce" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "433d05519ce6990bf3530fba6957499d327395c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b5f7b932ee6fa802fc792eabd77c4c88084517ce", - "reference": "b5f7b932ee6fa802fc792eabd77c4c88084517ce", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", + "reference": "433d05519ce6990bf3530fba6957499d327395c2", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "suggest": { - "ext-mbstring": "For best performance" + "ext-intl": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.19-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2068,11 +3510,182 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, "files": [ "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/59a8d271f00dd0e4c2e518104cc7963f655a1aa8", + "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2089,6 +3702,90 @@ "homepage": "https://symfony.com/contributors" } ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ @@ -2099,7 +3796,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.19.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" }, "funding": [ { @@ -2115,30 +3812,29 @@ "type": "tidelift" } ], - "time": "2020-10-23T09:01:57+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { - "name": "symfony/polyfill-php56", - "version": "v1.19.0", + "name": "symfony/polyfill-php72", + "version": "v1.26.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "ea19621731cbd973a6702cfedef3419768bf3372" + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/ea19621731cbd973a6702cfedef3419768bf3372", - "reference": "ea19621731cbd973a6702cfedef3419768bf3372", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/bf44a9fd41feaac72b074de600314a93e2ae78e2", + "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2", "shasum": "" }, "require": { - "php": ">=5.3.3", - "symfony/polyfill-util": "~1.0" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.19-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2146,12 +3842,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php56\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2167,7 +3863,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -2176,7 +3872,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php56/tree/v1.19.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.26.0" }, "funding": [ { @@ -2192,30 +3888,29 @@ "type": "tidelift" } ], - "time": "2020-10-23T09:01:57+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { - "name": "symfony/polyfill-php70", - "version": "v1.19.0", + "name": "symfony/polyfill-php73", + "version": "v1.26.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "3fe414077251a81a1b15b1c709faf5c2fbae3d4e" + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/3fe414077251a81a1b15b1c709faf5c2fbae3d4e", - "reference": "3fe414077251a81a1b15b1c709faf5c2fbae3d4e", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", "shasum": "" }, "require": { - "paragonie/random_compat": "~1.0|~2.0|~9.99", - "php": ">=5.3.3" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.19-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2223,12 +3918,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php70\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -2247,7 +3942,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -2256,7 +3951,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php70/tree/v1.19.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" }, "funding": [ { @@ -2272,29 +3967,29 @@ "type": "tidelift" } ], - "time": "2020-10-23T09:01:57+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { - "name": "symfony/polyfill-util", - "version": "v1.19.0", + "name": "symfony/polyfill-php80", + "version": "v1.26.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-util.git", - "reference": "8df0c3e6a4b85df9a5c6f3f2f46fba5c5c47058a" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/8df0c3e6a4b85df9a5c6f3f2f46fba5c5c47058a", - "reference": "8df0c3e6a4b85df9a5c6f3f2f46fba5c5c47058a", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.19-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2302,9 +3997,98 @@ } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Polyfill\\Util\\": "" + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-10T07:21:04+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1", + "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2320,16 +4104,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony utilities for portability of PHP codes", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ - "compat", "compatibility", "polyfill", + "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-util/tree/v1.19.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0" }, "funding": [ { @@ -2345,41 +4129,43 @@ "type": "tidelift" } ], - "time": "2020-10-21T09:57:48+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/routing", - "version": "v3.4.47", + "version": "v5.4.11", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "3e522ac69cadffd8131cc2b22157fa7662331a6c" + "reference": "3e01ccd9b2a3a4167ba2b3c53612762300300226" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/3e522ac69cadffd8131cc2b22157fa7662331a6c", - "reference": "3e522ac69cadffd8131cc2b22157fa7662331a6c", + "url": "https://api.github.com/repos/symfony/routing/zipball/3e01ccd9b2a3a4167ba2b3c53612762300300226", + "reference": "3e01ccd9b2a3a4167ba2b3c53612762300300226", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" }, "conflict": { - "symfony/config": "<3.3.1", - "symfony/dependency-injection": "<3.3", - "symfony/yaml": "<3.4" + "doctrine/annotations": "<1.12", + "symfony/config": "<5.3", + "symfony/dependency-injection": "<4.4", + "symfony/yaml": "<4.4" }, "require-dev": { - "doctrine/annotations": "~1.0", - "psr/log": "~1.0", - "symfony/config": "^3.3.1|~4.0", - "symfony/dependency-injection": "~3.3|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/http-foundation": "~2.8|~3.0|~4.0", - "symfony/yaml": "~3.4|~4.0" + "doctrine/annotations": "^1.12", + "psr/log": "^1|^2|^3", + "symfony/config": "^5.3|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/yaml": "^4.4|^5.0|^6.0" }, "suggest": { - "doctrine/annotations": "For using the annotation loader", "symfony/config": "For using the all-in-one router or any loader", "symfony/expression-language": "For using expression matching", "symfony/http-foundation": "For using a Symfony Request object", @@ -2408,7 +4194,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Routing Component", + "description": "Maps an HTTP request to a set of configuration variables", "homepage": "https://symfony.com", "keywords": [ "router", @@ -2417,7 +4203,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v3.4.47" + "source": "https://github.com/symfony/routing/tree/v5.4.11" }, "funding": [ { @@ -2433,51 +4219,315 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-07-20T13:00:38+00:00" }, { - "name": "symfony/twig-bridge", - "version": "v3.4.47", + "name": "symfony/service-contracts", + "version": "v2.5.2", "source": { "type": "git", - "url": "https://github.com/symfony/twig-bridge.git", - "reference": "090d19d6f1ea5b9e1d79f372785aa5e5c9cd4042" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/090d19d6f1ea5b9e1d79f372785aa5e5c9cd4042", - "reference": "090d19d6f1ea5b9e1d79f372785aa5e5c9cd4042", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "twig/twig": "^1.41|^2.10" + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" }, "conflict": { - "symfony/console": "<3.4", - "symfony/form": "<3.4.31|>=4.0,<4.3.4" + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-30T19:17:29+00:00" + }, + { + "name": "symfony/string", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/5eb661e49ad389e4ae2b6e4df8d783a8a6548322", + "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" }, "require-dev": { - "fig/link-util": "^1.0", - "symfony/asset": "~2.8|~3.0|~4.0", - "symfony/console": "~3.4|~4.0", - "symfony/dependency-injection": "~2.8|~3.0|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/form": "^3.4.31|^4.3.4", - "symfony/http-foundation": "^3.3.11|~4.0", - "symfony/http-kernel": "~3.2|~4.0", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-24T16:15:25+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/136b19dd05cdf0709db6537d058bcab6dd6e2dbe", + "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-27T16:58:25+00:00" + }, + { + "name": "symfony/twig-bridge", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "63b8a50d48c9fe3d04e77307d4f1771dd848baa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/63b8a50d48c9fe3d04e77307d4f1771dd848baa8", + "reference": "63b8a50d48c9fe3d04e77307d4f1771dd848baa8", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16", + "symfony/translation-contracts": "^1.1|^2|^3", + "twig/twig": "^2.13|^3.0.4" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/console": "<5.3", + "symfony/form": "<5.3", + "symfony/http-foundation": "<5.3", + "symfony/http-kernel": "<4.4", + "symfony/translation": "<5.2", + "symfony/workflow": "<5.2" + }, + "require-dev": { + "doctrine/annotations": "^1.12", + "egulias/email-validator": "^2.1.10|^3", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^4.4|^5.0|^6.0", + "symfony/console": "^5.3|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/form": "^5.3|^6.0", + "symfony/http-foundation": "^5.3|^6.0", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/intl": "^4.4|^5.0|^6.0", + "symfony/mime": "^5.2|^6.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/routing": "~2.8|~3.0|~4.0", - "symfony/security": "^2.8.31|^3.3.13|~4.0", - "symfony/security-acl": "~2.8|~3.0", - "symfony/stopwatch": "~2.8|~3.0|~4.0", - "symfony/templating": "~2.8|~3.0|~4.0", - "symfony/translation": "~2.8|~3.0|~4.0", - "symfony/var-dumper": "~2.8.10|~3.1.4|~3.2|~4.0", - "symfony/web-link": "~3.3|~4.0", - "symfony/workflow": "~3.3|~4.0", - "symfony/yaml": "~2.8|~3.0|~4.0" + "symfony/property-info": "^4.4|^5.1|^6.0", + "symfony/routing": "^4.4|^5.0|^6.0", + "symfony/security-acl": "^2.8|^3.0", + "symfony/security-core": "^4.4|^5.0|^6.0", + "symfony/security-csrf": "^4.4|^5.0|^6.0", + "symfony/security-http": "^4.4|^5.0|^6.0", + "symfony/serializer": "^5.2|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0", + "symfony/translation": "^5.2|^6.0", + "symfony/web-link": "^4.4|^5.0|^6.0", + "symfony/workflow": "^5.2|^6.0", + "symfony/yaml": "^4.4|^5.0|^6.0", + "twig/cssinliner-extra": "^2.12|^3", + "twig/inky-extra": "^2.12|^3", + "twig/markdown-extra": "^2.12|^3" }, "suggest": { "symfony/asset": "For using the AssetExtension", @@ -2486,9 +4536,10 @@ "symfony/form": "For using the FormExtension", "symfony/http-kernel": "For using the HttpKernelExtension", "symfony/routing": "For using the RoutingExtension", - "symfony/security": "For using the SecurityExtension", + "symfony/security-core": "For using the SecurityExtension", + "symfony/security-csrf": "For using the CsrfExtension", + "symfony/security-http": "For using the LogoutUrlExtension", "symfony/stopwatch": "For using the StopwatchExtension", - "symfony/templating": "For using the TwigEngine", "symfony/translation": "For using the TranslationExtension", "symfony/var-dumper": "For using the DumpExtension", "symfony/web-link": "For using the WebLinkExtension", @@ -2517,10 +4568,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Twig Bridge", + "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v3.4.47" + "source": "https://github.com/symfony/twig-bridge/tree/v5.4.11" }, "funding": [ { @@ -2536,50 +4587,52 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-07-20T13:00:38+00:00" }, { "name": "symfony/twig-bundle", - "version": "v3.4.47", + "version": "v5.4.8", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "977b3096e2df96bc8a8d2329e83466cfc30c373d" + "reference": "c992b4474c3a31f3c40a1ca593d213833f91b818" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/977b3096e2df96bc8a8d2329e83466cfc30c373d", - "reference": "977b3096e2df96bc8a8d2329e83466cfc30c373d", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/c992b4474c3a31f3c40a1ca593d213833f91b818", + "reference": "c992b4474c3a31f3c40a1ca593d213833f91b818", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/config": "~3.2|~4.0", - "symfony/debug": "~2.8|~3.0|~4.0", - "symfony/http-foundation": "~2.8|~3.0|~4.0", - "symfony/http-kernel": "^3.3|~4.0", + "php": ">=7.2.5", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^5.0|^6.0", "symfony/polyfill-ctype": "~1.8", - "symfony/twig-bridge": "^3.4.3|^4.0.3", - "twig/twig": "~1.41|~2.10" + "symfony/polyfill-php80": "^1.16", + "symfony/twig-bridge": "^5.3|^6.0", + "twig/twig": "^2.13|^3.0.4" }, "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/event-dispatcher": "<3.3.1" + "symfony/dependency-injection": "<5.3", + "symfony/framework-bundle": "<5.0", + "symfony/service-contracts": ">=3.0", + "symfony/translation": "<5.0" }, "require-dev": { - "doctrine/annotations": "~1.7", - "doctrine/cache": "~1.0", - "symfony/asset": "~2.8|~3.0|~4.0", - "symfony/dependency-injection": "~3.4.24|^4.2.5", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/form": "~2.8|~3.0|~4.0", - "symfony/framework-bundle": "^3.3.11|~4.0", - "symfony/routing": "~2.8|~3.0|~4.0", - "symfony/stopwatch": "~2.8|~3.0|~4.0", - "symfony/templating": "~2.8|~3.0|~4.0", - "symfony/web-link": "~3.3|~4.0", - "symfony/yaml": "~2.8|~3.0|~4.0" + "doctrine/annotations": "^1.10.4", + "doctrine/cache": "^1.0|^2.0", + "symfony/asset": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.3|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/form": "^4.4|^5.0|^6.0", + "symfony/framework-bundle": "^5.0|^6.0", + "symfony/routing": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0", + "symfony/translation": "^5.0|^6.0", + "symfony/web-link": "^4.4|^5.0|^6.0", + "symfony/yaml": "^4.4|^5.0|^6.0" }, "type": "symfony-bundle", "autoload": { @@ -2604,10 +4657,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony TwigBundle", + "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bundle/tree/v3.4.47" + "source": "https://github.com/symfony/twig-bundle/tree/v5.4.8" }, "funding": [ { @@ -2623,238 +4676,46 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" - }, - { - "name": "symfony/yaml", - "version": "v3.4.47", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "88289caa3c166321883f67fe5130188ebbb47094" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/88289caa3c166321883f67fe5130188ebbb47094", - "reference": "88289caa3c166321883f67fe5130188ebbb47094", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/console": "<3.4" - }, - "require-dev": { - "symfony/console": "~3.4|~4.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v3.4.47" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-10-24T10:57:07+00:00" - }, - { - "name": "twig/twig", - "version": "v1.42.4", - "source": { - "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "e587180584c3d2d6cb864a0454e777bb6dcb6152" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/e587180584c3d2d6cb864a0454e777bb6dcb6152", - "reference": "e587180584c3d2d6cb864a0454e777bb6dcb6152", - "shasum": "" - }, - "require": { - "php": ">=5.5.0", - "symfony/polyfill-ctype": "^1.8" - }, - "require-dev": { - "psr/container": "^1.0", - "symfony/debug": "^3.4|^4.2", - "symfony/phpunit-bridge": "^4.4@dev|^5.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.42-dev" - } - }, - "autoload": { - "psr-0": { - "Twig_": "lib/" - }, - "psr-4": { - "Twig\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" - }, - { - "name": "Twig Team", - "homepage": "https://twig.symfony.com/contributors", - "role": "Contributors" - }, - { - "name": "Armin Ronacher", - "email": "armin.ronacher@active-4.com", - "role": "Project Founder" - } - ], - "description": "Twig, the flexible, fast, and secure template language for PHP", - "homepage": "https://twig.symfony.com", - "keywords": [ - "templating" - ], - "time": "2019-11-11T16:49:32+00:00" - } - ], - "packages-dev": [ - { - "name": "symfony/stopwatch", - "version": "v3.4.47", - "source": { - "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "298b81faad4ce60e94466226b2abbb8c9bca7462" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/298b81faad4ce60e94466226b2abbb8c9bca7462", - "reference": "298b81faad4ce60e94466226b2abbb8c9bca7462", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Stopwatch\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Stopwatch Component", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/stopwatch/tree/v3.4.47" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-04-03T13:03:10+00:00" }, { "name": "symfony/var-dumper", - "version": "v3.4.47", + "version": "v5.4.11", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "0719f6cf4633a38b2c1585140998579ce23b4b7d" + "reference": "b8f306d7b8ef34fb3db3305be97ba8e088fb4861" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0719f6cf4633a38b2c1585140998579ce23b4b7d", - "reference": "0719f6cf4633a38b2c1585140998579ce23b4b7d", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8f306d7b8ef34fb3db3305be97ba8e088fb4861", + "reference": "b8f306d7b8ef34fb3db3305be97ba8e088fb4861", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16" }, "conflict": { - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<4.4" }, "require-dev": { "ext-iconv": "*", - "twig/twig": "~1.34|~2.4" + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/uid": "^5.1|^6.0", + "twig/twig": "^2.13|^3.0.4" }, "suggest": { "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", "ext-intl": "To show region name in time zone dump", - "ext-symfony_debug": "" + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" }, + "bin": [ + "Resources/bin/var-dump-server" + ], "type": "library", "autoload": { "files": [ @@ -2881,14 +4742,14 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony mechanism for exploring and dumping PHP variables", + "description": "Provides mechanisms for walking through any arbitrary PHP variable", "homepage": "https://symfony.com", "keywords": [ "debug", "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v3.4.47" + "source": "https://github.com/symfony/var-dumper/tree/v5.4.11" }, "funding": [ { @@ -2904,44 +4765,446 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-07-20T13:00:38+00:00" }, { - "name": "symfony/web-profiler-bundle", - "version": "v3.4.47", + "name": "symfony/var-exporter", + "version": "v5.4.10", "source": { "type": "git", - "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "ccb83b3a508f4a683e44f571f127beebdc315ff9" + "url": "https://github.com/symfony/var-exporter.git", + "reference": "8fc03ee75eeece3d9be1ef47d26d79bea1afb340" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/ccb83b3a508f4a683e44f571f127beebdc315ff9", - "reference": "ccb83b3a508f4a683e44f571f127beebdc315ff9", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/8fc03ee75eeece3d9be1ef47d26d79bea1afb340", + "reference": "8fc03ee75eeece3d9be1ef47d26d79bea1afb340", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/config": "~3.4|~4.0", - "symfony/http-kernel": "~3.4.25|^4.2.6", - "symfony/polyfill-php70": "~1.0", - "symfony/routing": "~3.4.7|~4.0", - "symfony/twig-bundle": "~3.4|~4.0", - "symfony/var-dumper": "~3.3|~4.0", - "twig/twig": "~1.34|~2.4" - }, - "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/event-dispatcher": "<3.3.1", - "symfony/framework-bundle": ">4.3.99", - "symfony/var-dumper": "<3.3" + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "symfony/browser-kit": "~3.4|~4.0", - "symfony/console": "~3.4|~4.0", - "symfony/css-selector": "~3.4|~4.0", - "symfony/framework-bundle": "~3.4|~4.0", - "symfony/stopwatch": "~3.4|~4.0" + "symfony/var-dumper": "^4.4.9|^5.0.9|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v5.4.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-27T12:56:18+00:00" + }, + { + "name": "symfony/yaml", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "05d4ea560f3402c6c116afd99fdc66e60eda227e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/05d4ea560f3402c6c116afd99fdc66e60eda227e", + "reference": "05d4ea560f3402c6c116afd99fdc66e60eda227e", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<5.3" + }, + "require-dev": { + "symfony/console": "^5.3|^6.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v5.4.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-27T16:58:25+00:00" + }, + { + "name": "thenetworg/oauth2-azure", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/TheNetworg/oauth2-azure.git", + "reference": "06fb2d620fb6e6c934f632c7ec7c5ea2e978a844" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TheNetworg/oauth2-azure/zipball/06fb2d620fb6e6c934f632c7ec7c5ea2e978a844", + "reference": "06fb2d620fb6e6c934f632c7ec7c5ea2e978a844", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-openssl": "*", + "firebase/php-jwt": "~3.0||~4.0||~5.0||~6.0", + "league/oauth2-client": "~2.0", + "php": "^7.1|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "TheNetworg\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Hajek", + "email": "jan.hajek@thenetw.org", + "homepage": "https://thenetw.org" + } + ], + "description": "Azure Active Directory OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "SSO", + "aad", + "authorization", + "azure", + "azure active directory", + "client", + "microsoft", + "oauth", + "oauth2", + "windows azure" + ], + "support": { + "issues": "https://github.com/TheNetworg/oauth2-azure/issues", + "source": "https://github.com/TheNetworg/oauth2-azure/tree/v2.1.1" + }, + "time": "2022-06-23T10:35:36+00:00" + }, + { + "name": "twig/twig", + "version": "v3.4.2", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077", + "reference": "e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.4.2" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2022-08-12T06:47:24+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + } + ], + "packages-dev": [ + { + "name": "symfony/stopwatch", + "version": "v5.4.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "4d04b5c24f3c9a1a168a131f6cbe297155bc0d30" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/4d04b5c24f3c9a1a168a131f6cbe297155bc0d30", + "reference": "4d04b5c24f3c9a1a168a131f6cbe297155bc0d30", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/service-contracts": "^1|^2|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v5.4.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-02-18T16:06:09+00:00" + }, + { + "name": "symfony/web-profiler-bundle", + "version": "v5.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/web-profiler-bundle.git", + "reference": "f61c99d8dbd864b11935851b598f784bcff36fc7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/f61c99d8dbd864b11935851b598f784bcff36fc7", + "reference": "f61c99d8dbd864b11935851b598f784bcff36fc7", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/framework-bundle": "^5.3|^6.0", + "symfony/http-kernel": "^5.3|^6.0", + "symfony/polyfill-php80": "^1.16", + "symfony/routing": "^4.4|^5.0|^6.0", + "symfony/twig-bundle": "^4.4|^5.0|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "conflict": { + "symfony/dependency-injection": "<5.2", + "symfony/form": "<4.4", + "symfony/mailer": "<5.4", + "symfony/messenger": "<4.4" + }, + "require-dev": { + "symfony/browser-kit": "^4.4|^5.0|^6.0", + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/css-selector": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0" }, "type": "symfony-bundle", "autoload": { @@ -2966,10 +5229,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony WebProfilerBundle", + "description": "Provides a development tool that gives detailed information about the execution of any request", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v3.4.47" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v5.4.10" }, "funding": [ { @@ -2985,7 +5248,7 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-06-06T19:10:58+00:00" } ], "aliases": [], @@ -2994,7 +5257,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.1.3 <8.0.0", + "php": ">=7.4.0 <8.2.0", "ext-ctype": "*", "ext-dom": "*", "ext-gd": "*", @@ -3005,7 +5268,7 @@ }, "platform-dev": [], "platform-overrides": { - "php": "7.1.3" + "php": "7.4.0" }, "plugin-api-version": "2.1.0" } diff --git a/core/MyHelpers.class.inc.php b/core/MyHelpers.class.inc.php index f5260d987b..d3f47be315 100644 --- a/core/MyHelpers.class.inc.php +++ b/core/MyHelpers.class.inc.php @@ -470,9 +470,9 @@ class Str public static function pure2html($pure, $maxLength = false) { // Check for HTML entities, but be careful the DB is in UTF-8 - return $maxLength - ? htmlentities(substr($pure, 0, $maxLength), ENT_QUOTES, 'UTF-8') - : htmlentities($pure, ENT_QUOTES, 'UTF-8'); + return $maxLength + ? utils::EscapeHtml(substr($pure, 0, $maxLength)) + : utils::EscapeHtml($pure); } public static function pure2sql($pure, $maxLength = false) { diff --git a/core/action.class.inc.php b/core/action.class.inc.php index a45529db2b..b2d070bb56 100644 --- a/core/action.class.inc.php +++ b/core/action.class.inc.php @@ -200,6 +200,17 @@ abstract class ActionNotification extends Action */ class ActionEmail extends ActionNotification { + /** + * @var string + * @since 3.0.1 + */ + const ENUM_HEADER_NAME_MESSAGE_ID = 'Message-ID'; + /** + * @var string + * @since 3.0.1 + */ + const ENUM_HEADER_NAME_REFERENCES = 'References'; + /** * @inheritDoc */ @@ -207,13 +218,13 @@ class ActionEmail extends ActionNotification { $aParams = array ( - "category" => "grant_by_profile,core/cmdb,application", - "key_type" => "autoincrement", - "name_attcode" => "name", - "state_attcode" => "", - "reconc_keys" => array('name'), - "db_table" => "priv_action_email", - "db_key_field" => "id", + "category" => "grant_by_profile,core/cmdb,application", + "key_type" => "autoincrement", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array('name'), + "db_table" => "priv_action_email", + "db_key_field" => "id", "db_finalclass_field" => "", ); MetaModel::Init_Params($aParams); @@ -266,7 +277,7 @@ class ActionEmail extends ActionNotification protected function FindRecipients($sRecipAttCode, $aArgs) { $sOQL = $this->Get($sRecipAttCode); - if (strlen($sOQL) == '') return ''; + if (strlen($sOQL) === 0) return ''; try { @@ -416,9 +427,8 @@ class ActionEmail extends ActionNotification $sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs); $oObj = $aContextArgs['this->object()']; - $sMessageId = sprintf('iTop_%s_%d_%f@%s.openitop.org', get_class($oObj), $oObj->GetKey(), microtime(true /* get as float*/), - MetaModel::GetEnvironmentId()); - $sReference = '<'.$sMessageId.'>'; + $sMessageId = $this->GenerateIdentifierForHeaders($oObj, static::ENUM_HEADER_NAME_MESSAGE_ID); + $sReference = $this->GenerateIdentifierForHeaders($oObj, static::ENUM_HEADER_NAME_REFERENCES); } catch (Exception $e) { /** @noinspection PhpUnhandledExceptionInspection */ @@ -456,8 +466,7 @@ class ActionEmail extends ActionNotification $oEmail = new EMail(); - if ($this->IsBeingTested()) - { + if ($this->IsBeingTested()) { $oEmail->SetSubject('TEST['.$sSubject.']'); $sTestBody = $sBody; $sTestBody .= "
\n"; @@ -467,8 +476,8 @@ class ActionEmail extends ActionNotification $sTestBody .= "
  • TO: $sTo
  • \n"; $sTestBody .= "
  • CC: $sCC
  • \n"; $sTestBody .= "
  • BCC: $sBCC
  • \n"; - $sTestBody .= empty($sFromLabel) ? "
  • From: $sFrom
  • \n": "
  • From: $sFromLabel <$sFrom>
  • \n"; - $sTestBody .= empty($sReplyToLabel) ? "
  • Reply-To: $sReplyTo
  • \n": "
  • Reply-To: $sReplyToLabel <$sReplyTo>
  • \n"; + $sTestBody .= empty($sFromLabel) ? "
  • From: $sFrom
  • \n" : "
  • From: $sFromLabel <$sFrom>
  • \n"; + $sTestBody .= empty($sReplyToLabel) ? "
  • Reply-To: $sReplyTo
  • \n" : "
  • Reply-To: $sReplyToLabel <$sReplyTo>
  • \n"; $sTestBody .= "
  • References: $sReference
  • \n"; $sTestBody .= "\n"; $sTestBody .= "

    \n"; @@ -478,9 +487,9 @@ class ActionEmail extends ActionNotification $oEmail->SetRecipientFrom($sFrom, $sFromLabel); $oEmail->SetReferences($sReference); $oEmail->SetMessageId($sMessageId); - } - else - { + // Note: N°4849 We pass the "References" identifier instead of the "Message-ID" on purpose as we want notifications emails to group around the triggering iTop object, not just the users' replies to the notification + $oEmail->SetInReplyTo($sReference); + } else { $oEmail->SetSubject($sSubject); $oEmail->SetBody($sBody, 'text/html', $sStyles); $oEmail->SetRecipientTO($sTo); @@ -490,6 +499,8 @@ class ActionEmail extends ActionNotification $oEmail->SetRecipientReplyTo($sReplyTo, $sReplyToLabel); $oEmail->SetReferences($sReference); $oEmail->SetMessageId($sMessageId); + // Note: N°4849 We pass the "References" identifier instead of the "Message-ID" on purpose as we want notifications emails to group around the triggering iTop object, not just the users' replies to the notification + $oEmail->SetInReplyTo($sReference); } if (isset($aContextArgs['attachments'])) @@ -516,26 +527,64 @@ class ActionEmail extends ActionNotification { case EMAIL_SEND_OK: return "Sent"; - + case EMAIL_SEND_PENDING: return "Pending"; - + case EMAIL_SEND_ERROR: return "Errors: ".implode(', ', $aErrors); } } - } - else - { - if (is_array($this->m_aMailErrors) && count($this->m_aMailErrors) > 0) - { + } else { + if (is_array($this->m_aMailErrors) && count($this->m_aMailErrors) > 0) { $sError = implode(', ', $this->m_aMailErrors); - } - else - { + } else { $sError = 'Unknown reason'; } + return 'Notification was not sent: '.$sError; } } + + /** + * @param \DBObject $oObject + * @param string $sHeaderName {@see \ActionEmail::ENUM_HEADER_NAME_REFERENCES}, {@see \ActionEmail::ENUM_HEADER_NAME_MESSAGE_ID} + * + * @return string The formatted identifier for $sHeaderName based on $oObject + * @throws \Exception + * @since 3.0.1 N°4849 + */ + protected function GenerateIdentifierForHeaders(DBObject $oObject, string $sHeaderName): string + { + $sObjClass = get_class($oObject); + $sObjId = $oObject->GetKey(); + $sAppName = utils::Sanitize(ITOP_APPLICATION_SHORT, '', utils::ENUM_SANITIZATION_FILTER_VARIABLE_NAME); + + switch ($sHeaderName) { + case static::ENUM_HEADER_NAME_MESSAGE_ID: + case static::ENUM_HEADER_NAME_REFERENCES: + // Prefix + $sPrefix = sprintf('%s_%s_%d', $sAppName, $sObjClass, $sObjId); + if ($sHeaderName === static::ENUM_HEADER_NAME_MESSAGE_ID) { + $sPrefix .= sprintf('_%F', microtime(true /* get as float*/)); + } + // Suffix + $sSuffix = sprintf('@%s.openitop.org', MetaModel::GetEnvironmentId()); + // Identifier + $sIdentifier = $sPrefix.$sSuffix; + if ($sHeaderName === static::ENUM_HEADER_NAME_REFERENCES) { + $sIdentifier = "<$sIdentifier>"; + } + + return $sIdentifier; + } + + // Requested header name invalid + $sErrorMessage = sprintf('%s: Could not generate identifier for header "%s", only %s are supported', static::class, $sHeaderName, implode(' / ', [static::ENUM_HEADER_NAME_MESSAGE_ID, static::ENUM_HEADER_NAME_REFERENCES])); + IssueLog::Error($sErrorMessage, LogChannels::NOTIFICATIONS, [ + 'Object' => $sObjClass.'::'.$sObjId.' ('.$oObject->GetRawName().')', + 'Action' => get_class($this).'::'.$this->GetKey().' ('.$this->GetRawName().')', + ]); + throw new Exception($sErrorMessage); + } } diff --git a/core/asynctask.class.inc.php b/core/asynctask.class.inc.php index 5c7d329bfe..05bc5bd746 100644 --- a/core/asynctask.class.inc.php +++ b/core/asynctask.class.inc.php @@ -293,7 +293,7 @@ abstract class AsyncTask extends DBObject $this->Set('remaining_retries', $this->GetMaxRetries($iErrorCode)); } - $this->Set('last_error', $sErrorMessage); + $this->SetTrim('last_error', $sErrorMessage); $this->Set('last_error_code', $iErrorCode); // Note: can be ZERO !!! $this->Set('last_attempt', time()); @@ -409,8 +409,6 @@ class AsyncSendEmail extends AsyncTask $oNew->Set('to', $oEMail->GetRecipientTO(true /* string */)); $oNew->Set('subject', $oEMail->GetSubject()); -// $oNew->Set('version', 1); -// $sMessage = serialize($oEMail); $oNew->Set('version', 2); $sMessage = $oEMail->SerializeV2(); $oNew->Set('message', $sMessage); diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 80ad1102e9..1a3c933672 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -854,6 +854,11 @@ abstract class AttributeDefinition //abstract protected GetBasicFilterHTMLInput(); abstract public function GetBasicFilterSQLExpr($sOpCode, $value); + /** + * since 3.1.0 return has changed (N°4690 - Deprecate "FilterCodes") + * + * @return array filtercode => attributecode + */ public function GetFilterDefinitions() { return array(); @@ -1685,7 +1690,7 @@ class AttributeLinkedSet extends AttributeDefinition { if ($sObjClass == $this->GetLinkedClass()) { - // Simplify the output if the exact class could be determined implicitely + // Simplify the output if the exact class could be determined implicitely continue; } } @@ -2007,7 +2012,7 @@ class AttributeLinkedSet extends AttributeDefinition { if ($sObjClass == $this->GetLinkedClass()) { - // Simplify the output if the exact class could be determined implicitely + // Simplify the output if the exact class could be determined implicitely continue; } } @@ -2412,7 +2417,7 @@ class AttributeDBFieldVoid extends AttributeDefinition return false; } - // + // protected function ScalarToSQL($value) { return $value; @@ -2452,7 +2457,7 @@ class AttributeDBFieldVoid extends AttributeDefinition public function GetFilterDefinitions() { - return array($this->GetCode() => new FilterFromAttribute($this)); + return array($this->GetCode() => $this->GetCode()); } public function GetBasicFilterOperators() @@ -4174,20 +4179,21 @@ class AttributeText extends AttributeString public function GetEditValue($sValue, $oHostObj = null) { - if ($this->GetFormat() == 'text') - { - if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER)) - { - foreach($aAllMatches as $iPos => $aMatches) - { + // N°4517 - PHP 8.1 compatibility: str_replace call with null cause deprecated message + if ($sValue == null) { + return ''; + } + + if ($this->GetFormat() == 'text') { + if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER)) { + foreach ($aAllMatches as $iPos => $aMatches) { $sClass = trim($aMatches[1]); $sName = trim($aMatches[2]); $sLabel = (!empty($aMatches[4])) ? trim($aMatches[4]) : null; - if (MetaModel::IsValidClass($sClass)) - { + if (MetaModel::IsValidClass($sClass)) { $sClassLabel = MetaModel::GetName($sClass); - $sReplacement = "[[$sClassLabel:$sName" . (!empty($sLabel) ? " | $sLabel" : "") . "]]"; + $sReplacement = "[[$sClassLabel:$sName".(!empty($sLabel) ? " | $sLabel" : "")."]]"; $sValue = str_replace($aMatches[0], $sReplacement, $sValue); } } @@ -4224,31 +4230,31 @@ class AttributeText extends AttributeString public function MakeRealValue($proposedValue, $oHostObj) { $sValue = $proposedValue; - switch ($this->GetFormat()) - { + + // N°4517 - PHP 8.1 compatibility: str_replace call with null cause deprecated message + if ($sValue == null) { + return ''; + } + + switch ($this->GetFormat()) { case 'html': - if (($sValue !== null) && ($sValue !== '')) - { + if (($sValue !== null) && ($sValue !== '')) { $sValue = HTMLSanitizer::Sanitize($sValue); } break; case 'text': default: - if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER)) - { - foreach($aAllMatches as $iPos => $aMatches) - { + if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER)) { + foreach ($aAllMatches as $iPos => $aMatches) { $sClassLabel = trim($aMatches[1]); $sName = trim($aMatches[2]); - $sLabel = (!empty($aMatches[4])) ? trim($aMatches[4]) : null; + $sLabel = (!empty($aMatches[4])) ? trim($aMatches[4]) : null; - if (!MetaModel::IsValidClass($sClassLabel)) - { + if (!MetaModel::IsValidClass($sClassLabel)) { $sClass = MetaModel::GetClassFromLabel($sClassLabel); - if ($sClass) - { - $sReplacement = "[[$sClassLabel:$sName" . (!empty($sLabel) ? " | $sLabel" : "") . "]]"; + if ($sClass) { + $sReplacement = "[[$sClassLabel:$sName".(!empty($sLabel) ? " | $sLabel" : "")."]]"; $sValue = str_replace($aMatches[0], $sReplacement, $sValue); } } @@ -4587,7 +4593,13 @@ class AttributeCaseLog extends AttributeLongText { if (strlen($proposedValue) > 0) { - $oCaseLog->AddLogEntry($proposedValue); + //N°5135 - add impersonation information in caselog + if (UserRights::IsImpersonated()){ + $sOnBehalfOf = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUserFriendlyName(), UserRights::GetUserFriendlyName()); + $oCaseLog->AddLogEntry($proposedValue, $sOnBehalfOf, UserRights::GetConnectedUserId()); + } else { + $oCaseLog->AddLogEntry($proposedValue); + } } } $ret = $oCaseLog; @@ -5394,7 +5406,7 @@ class AttributeEnum extends AttributeString { if (is_null($sValue)) { - // Unless a specific label is defined for the null value of this enum, use a generic "undefined" label + // Unless a specific label is defined for the null value of this enum, use a generic "undefined" label $sLabel = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue, Dict::S('Enum:Undefined')); } @@ -5416,7 +5428,7 @@ class AttributeEnum extends AttributeString { if (is_null($sValue)) { - // Unless a specific label is defined for the null value of this enum, use a generic "undefined" label + // Unless a specific label is defined for the null value of this enum, use a generic "undefined" label $sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+', Dict::S('Enum:Undefined')); } @@ -7213,7 +7225,7 @@ class AttributeExternalField extends AttributeDefinition protected function GetSQLCol($bFullSpec = false) { - // throw new CoreException("external attribute: does it make any sense to request its type ?"); + // throw new CoreException("external attribute: does it make any sense to request its type ?"); $oExtAttDef = $this->GetExtAttDef(); return $oExtAttDef->GetSQLCol($bFullSpec); @@ -7486,7 +7498,7 @@ class AttributeExternalField extends AttributeDefinition public function GetFilterDefinitions() { - return array($this->GetCode() => new FilterFromAttribute($this)); + return array($this->GetCode() => $this->GetCode()); } public function GetBasicFilterOperators() @@ -8523,18 +8535,17 @@ class AttributeStopWatch extends AttributeDefinition public function GetFilterDefinitions() { $aRes = array( - $this->GetCode() => new FilterFromAttribute($this), - $this->GetCode().'_started' => new FilterFromAttribute($this, '_started'), - $this->GetCode().'_laststart' => new FilterFromAttribute($this, '_laststart'), - $this->GetCode().'_stopped' => new FilterFromAttribute($this, '_stopped') + $this->GetCode() => $this->GetCode(), + $this->GetCode().'_started' => $this->GetCode(), + $this->GetCode().'_laststart' => $this->GetCode(), + $this->GetCode().'_stopped' => $this->GetCode(), ); - foreach($this->ListThresholds() as $iThreshold => $aFoo) - { + foreach ($this->ListThresholds() as $iThreshold => $aFoo) { $sPrefix = $this->GetCode().'_'.$iThreshold; - $aRes[$sPrefix.'_deadline'] = new FilterFromAttribute($this, '_deadline'); - $aRes[$sPrefix.'_passed'] = new FilterFromAttribute($this, '_passed'); - $aRes[$sPrefix.'_triggered'] = new FilterFromAttribute($this, '_triggered'); - $aRes[$sPrefix.'_overrun'] = new FilterFromAttribute($this, '_overrun'); + $aRes[$sPrefix.'_deadline'] = $this->GetCode(); + $aRes[$sPrefix.'_passed'] = $this->GetCode(); + $aRes[$sPrefix.'_triggered'] = $this->GetCode(); + $aRes[$sPrefix.'_overrun'] = $this->GetCode(); } return $aRes; @@ -9262,7 +9273,7 @@ class AttributeSubItem extends AttributeDefinition return $res; } - // + // // protected function ScalarToSQL($value) {return $value;} // format value as a valuable SQL literal (quoted outside) public function FromSQLToValue($aCols, $sPrefix = '') @@ -9276,7 +9287,7 @@ class AttributeSubItem extends AttributeDefinition public function GetFilterDefinitions() { - return array($this->GetCode() => new FilterFromAttribute($this)); + return array($this->GetCode() => $this->GetCode()); } public function GetBasicFilterOperators() @@ -11935,7 +11946,7 @@ class AttributeFriendlyName extends AttributeDefinition public function GetFilterDefinitions() { - return array($this->GetCode() => new FilterFromAttribute($this)); + return array($this->GetCode() => $this->GetCode()); } public function GetBasicFilterOperators() @@ -12772,7 +12783,7 @@ class AttributeCustomFields extends AttributeDefinition $sRet = $value->GetAsHTML($bLocalize); } catch (Exception $e) { - $sRet = 'Custom field error: '.htmlentities($e->getMessage(), ENT_QUOTES, 'UTF-8'); + $sRet = 'Custom field error: '.utils::EscapeHtml($e->getMessage()); } return $sRet; diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index 85dbb92839..3c5e814aa0 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -79,22 +79,30 @@ class CMDBChangeOp extends DBObject implements iCMDBChangeOp /** * @inheritDoc - */ + */ public function GetDescription() { return ''; } /** - * Safety net: in case the change is not given, let's guarantee that it will - * be set to the current ongoing change (or create a new one) - */ + * Safety net: + * * if change isn't persisted yet, use the current change and persist it if needed + * * in case the change is not given, let's guarantee that it will be set to the current ongoing change (or create a new one) + * + * @since 2.7.7 3.0.2 3.1.0 N°3717 do persist the current change if needed + */ protected function OnInsert() { - if ($this->Get('change') <= 0) - { - $this->Set('change', CMDBObject::GetCurrentChange()); + $iChange = $this->Get('change'); + if (($iChange <= 0) || (is_null($iChange))) { + $oChange = CMDBObject::GetCurrentChange(); + if ($oChange->IsNew()) { + $oChange->DBWrite(); + } + $this->Set('change', $oChange); } + parent::OnInsert(); } } @@ -342,20 +350,30 @@ class CMDBChangeOpSetAttributeURL extends CMDBChangeOpSetAttribute { $aParams = array ( - "category" => "core/cmdb", - "key_type" => "", - "name_attcode" => "change", - "state_attcode" => "", - "reconc_keys" => array(), - "db_table" => "priv_changeop_setatt_url", - "db_key_field" => "id", + "category" => "core/cmdb", + "key_type" => "", + "name_attcode" => "change", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_changeop_setatt_url", + "db_key_field" => "id", "db_finalclass_field" => "", ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeURL("oldvalue", array("allowed_values"=>null, "sql"=>"oldvalue", "target" => '_blank', "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeURL("newvalue", array("allowed_values"=>null, "sql"=>"newvalue", "target" => '_blank', "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - + + // N°4910 (oldvalue), N°5423 (newvalue) + // We cannot have validation here, as AttributeUrl validation is field dependant. + // The validation will be done when editing the iTop object, it isn't the history API responsibility + // + // Pattern is retrieved using this order : + // 1. try to get the pattern from the field definition (datamodel) + // 2. from the iTop config + // 3. config parameter default value + // see \AttributeURL::GetValidationPattern + MetaModel::Init_AddAttribute(new AttributeURL("oldvalue", array("allowed_values" => null, "sql" => "oldvalue", "target" => '_blank', "default_value" => null, "is_null_allowed" => true, "depends_on" => array(), "validation_pattern" => '.*'))); + MetaModel::Init_AddAttribute(new AttributeURL("newvalue", array("allowed_values" => null, "sql" => "newvalue", "target" => '_blank', "default_value" => null, "is_null_allowed" => true, "depends_on" => array(), "validation_pattern" => '.*'))); + // Display lists MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for a list @@ -867,7 +885,7 @@ class CMDBChangeOpSetAttributeCaseLog extends CMDBChangeOpSetAttribute */ protected function ToHtml($sRawText) { - return str_replace(array("\r\n", "\n", "\r"), "
    ", htmlentities($sRawText, ENT_QUOTES, 'UTF-8')); + return str_replace(array("\r\n", "\n", "\r"), "
    ", utils::EscapeHtml($sRawText)); } } @@ -1159,9 +1177,8 @@ class CMDBChangeOpSetAttributeCustomFields extends CMDBChangeOpSetAttribute $oHandler = $oAttDef->GetHandler($aValues); $sValueDesc = $oHandler->GetAsHTML($aValues); } - catch (Exception $e) - { - $sValueDesc = 'Custom field error: '.htmlentities($e->getMessage(), ENT_QUOTES, 'UTF-8'); + catch (Exception $e) { + $sValueDesc = 'Custom field error: '.utils::EscapeHtml($e->getMessage()); } $sTextView = '
    '.$sValueDesc.'
    '; diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index 4a070d2397..8e6a836032 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -3,7 +3,7 @@ // // This file is part of iTop. // -// iTop is free software; you can redistribute it and/or modify +// iTop is free software; you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. @@ -113,6 +113,26 @@ abstract class CMDBObject extends DBObject self::$m_oCurrChange = $oChange; } + /** + * @param string $sUserInfo + * @param string $sOrigin + * @param \DateTime $oDate + * + * @throws \CoreException + * + * @since 2.7.7 3.0.2 3.1.0 N°3717 new method to reset current change + */ + public static function SetCurrentChangeFromParams($sUserInfo, $sOrigin = null, $oDate = null) + { + static::SetTrackInfo($sUserInfo); + static::SetTrackOrigin($sOrigin); + static::CreateChange(); + + if (!is_null($oDate)) { + static::$m_oCurrChange->Set("date", $oDate); + } + } + // // Todo: simplify the APIs and do not pass the current change as an argument anymore // SetTrackInfo to be invoked in very few cases (UI.php, CSV import, Data synchro) @@ -144,6 +164,8 @@ abstract class CMDBObject extends DBObject * $oMyChange->Set("userinfo", 'this is done by ... for ...'); * $iChangeId = $oMyChange->DBInsert(); * + * **warning** : this will do nothing if current change already exists ! + * * @see SetCurrentChange to specify a CMDBObject instance instead * * @param string $sInfo @@ -171,6 +193,8 @@ abstract class CMDBObject extends DBObject /** * Provides information about the origin of the change * + * **warning** : this will do nothing if current change already exists ! + * * @see SetTrackInfo * @see SetCurrentChange to specify a CMDBObject instance instead * @@ -181,19 +205,21 @@ abstract class CMDBObject extends DBObject { self::$m_sOrigin = $sOrigin; } - + /** * Get the additional information (defaulting to user name) - */ - protected static function GetTrackInfo() + */ + public static function GetTrackInfo() { - if (is_null(self::$m_sInfo)) - { + if (is_null(self::$m_sInfo)) { return CMDBChange::GetCurrentUserName(); - } - else - { - return self::$m_sInfo; + } else { + //N°5135 - add impersonation information in activity log/current cmdb change + if (UserRights::IsImpersonated()){ + return sprintf("%s (%s)", CMDBChange::GetCurrentUserName(), self::$m_sInfo); + } else { + return self::$m_sInfo; + } } } @@ -206,7 +232,10 @@ abstract class CMDBObject extends DBObject */ protected static function GetTrackUserId() { - if (is_null(self::$m_sUserId)) + if (is_null(self::$m_sUserId) + //N°5135 - indicate impersonation inside changelogs + && (false === UserRights::IsImpersonated()) + ) { return CMDBChange::GetCurrentUserId(); } @@ -215,10 +244,10 @@ abstract class CMDBObject extends DBObject return self::$m_sUserId; } } - + /** * Get the 'origin' information (defaulting to 'interactive') - */ + */ protected static function GetTrackOrigin() { if (is_null(self::$m_sOrigin)) @@ -243,15 +272,17 @@ abstract class CMDBObject extends DBObject * @throws \CoreWarning * @throws \MySQLException * @throws \OQLException + * + * @since 2.7.7 3.0.2 3.1.0 N°3717 {@see CMDBChange} **will be persisted later** in {@see \CMDBChangeOp::OnInsert} (was done previously directly here) + * This will avoid creating in DB CMDBChange lines without any corresponding CMDBChangeOp */ - protected static function CreateChange() + public static function CreateChange() { self::$m_oCurrChange = MetaModel::NewObject("CMDBChange"); self::$m_oCurrChange->Set("date", time()); self::$m_oCurrChange->Set("userinfo", self::GetTrackInfo()); self::$m_oCurrChange->Set("user_id", self::GetTrackUserId()); self::$m_oCurrChange->Set("origin", self::GetTrackOrigin()); - self::$m_oCurrChange->DBInsert(); } /** @@ -592,6 +623,9 @@ abstract class CMDBObject extends DBObject return $this->DBCloneTracked_Internal(); } + /** + * @deprecated 3.1.0 N°5232 not used + */ public function DBCloneTracked(CMDBChange $oChange, $newKey = null) { self::SetCurrentChange($oChange); @@ -601,7 +635,7 @@ abstract class CMDBObject extends DBObject protected function DBCloneTracked_Internal($newKey = null) { $newKey = parent::DBClone($newKey); - $oClone = MetaModel::GetObject(get_class($this), $newKey); + $oClone = MetaModel::GetObject(get_class($this), $newKey); return $newKey; } @@ -614,7 +648,7 @@ abstract class CMDBObject extends DBObject { return; } - + $ret = parent::DBUpdate(); return $ret; } @@ -654,46 +688,7 @@ abstract class CMDBObject extends DBObject protected function DBDeleteTracked_Internal(&$oDeletionPlan = null) { $ret = parent::DBDelete($oDeletionPlan); - return $ret; - } - public static function BulkUpdate(DBSearch $oFilter, array $aValues) - { - return static::BulkUpdateTracked_Internal($oFilter, $aValues); - } - - public static function BulkUpdateTracked(CMDBChange $oChange, DBSearch $oFilter, array $aValues) - { - self::SetCurrentChange($oChange); - static::BulkUpdateTracked_Internal($oFilter, $aValues); - } - - protected static function BulkUpdateTracked_Internal(DBSearch $oFilter, array $aValues) - { - // $aValues is an array of $sAttCode => $value - - // Get the list of objects to update (and load it before doing the change) - $oObjSet = new CMDBObjectSet($oFilter); - $oObjSet->Load(); - - // Keep track of the previous values (will be overwritten when the objects are synchronized with the DB) - $aOriginalValues = array(); - $oObjSet->Rewind(); - while ($oItem = $oObjSet->Fetch()) - { - $aOriginalValues[$oItem->GetKey()] = $oItem->m_aOrigValues; - } - - // Update in one single efficient query - $ret = parent::BulkUpdate($oFilter, $aValues); - - // Record... in many queries !!! - $oObjSet->Rewind(); - while ($oItem = $oObjSet->Fetch()) - { - $aChangedValues = $oItem->ListChangedValues($aValues); - $oItem->RecordAttChanges($aChangedValues, $aOriginalValues[$oItem->GetKey()]); - } return $ret; } @@ -738,11 +733,11 @@ abstract class CMDBObject extends DBObject class CMDBObjectSet extends DBObjectSet { // this is the public interface (?) - + // I have to define those constructors here... :-( // just to get the right object class in return. // I have to think again to those things: maybe it will work fine if a have a constructor define here (?) - + static public function FromScratch($sClass) { $oFilter = new DBObjectSearch($sClass); @@ -751,7 +746,7 @@ class CMDBObjectSet extends DBObjectSet // NOTE: THIS DOES NOT WORK IF m_bLoaded is private in the base class (and you will not get any error message) $oRetSet->m_bLoaded = true; // no DB load return $oRetSet; - } + } // create an object set ex nihilo // input = array of objects @@ -760,7 +755,7 @@ class CMDBObjectSet extends DBObjectSet $oRetSet = self::FromScratch($sClass); $oRetSet->AddObjectArray($aObjects, $sClass); return $oRetSet; - } + } static public function FromArrayAssoc($aClasses, $aObjects) { diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index 48d67fcf4a..c725d0cecf 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -157,7 +157,7 @@ class CMDBSource $iPort = null; self::InitServerAndPort($sDbHost, $sServer, $iPort); - $iFlags = null; + $iFlags = 0; // *some* errors (like connection errors) will throw mysqli_sql_exception instead of generating warnings printed to the output // but some other errors will still cause the query() method to return false !!! @@ -166,7 +166,6 @@ class CMDBSource try { $oMysqli = new mysqli(); - $oMysqli->init(); if ($bTlsEnabled) { @@ -351,19 +350,21 @@ class CMDBSource } /** + * Get the version of the database server. + * * @return string * @throws \MySQLException * - * @uses \CMDBSource::QueryToCol() so needs a connection opened ! + * @uses \CMDBSource::QueryToScalar() so needs a connection opened ! */ public static function GetDBVersion() { - $aVersions = self::QueryToCol('SELECT Version() as version', 'version'); - return $aVersions[0]; + return static::QueryToScalar('SELECT VERSION()', 0); } /** - * @return string + * @deprecated Use `CMDBSource::GetDBVersion` instead. + * @uses mysqli_get_server_info */ public static function GetServerInfo() { @@ -705,7 +706,11 @@ class CMDBSource private static function Commit() { $aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3); - $sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()'; + if(isset($aStackTrace[2]['class']) && isset($aStackTrace[2]['function'])) { + $sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()'; + } else { + $sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].') '; + } if (!self::IsInsideTransaction()) { // should not happen ! IssueLog::Error("No Transaction COMMIT $sCaller", LogChannels::CMDB_SOURCE); @@ -739,7 +744,11 @@ class CMDBSource private static function Rollback() { $aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3); - $sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()'; + if(isset($aStackTrace[2]['class']) && isset($aStackTrace[2]['function'])) { + $sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()'; + } else { + $sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].') '; + } if (!self::IsInsideTransaction()) { // should not happen ! IssueLog::Error("No Transaction ROLLBACK $sCaller", LogChannels::CMDB_SOURCE); @@ -1499,20 +1508,14 @@ class CMDBSource * Returns the value of the specified server variable * @param string $sVarName Name of the server variable * @return mixed Current value of the variable - */ + * @throws \MySQLQueryHasNoResultException|\MySQLException + */ public static function GetServerVariable($sVarName) { - $result = ''; - $sSql = "SELECT @@$sVarName as theVar"; - $aRows = self::QueryToArray($sSql); - if (count($aRows) > 0) - { - $result = $aRows[0]['theVar']; - } - return $result; + $sSql = 'SELECT @@'.$sVarName; + return static::QueryToScalar($sSql, 0) ?: ''; } - /** * Returns the privileges of the current user * @return string privileges in a raw format diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 11cdaa4f67..74cf107a28 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -489,13 +489,21 @@ class Config 'show_in_conf_sample' => true, ], 'cron_max_execution_time' => [ - 'type' => 'integer', - 'description' => 'Duration (seconds) of the page cron.php, must be shorter than php setting max_execution_time and shorter than the web server response timeout', - 'default' => 600, - 'value' => 600, - 'source_of_value' => '', + 'type' => 'integer', + 'description' => 'Duration (seconds) of the cron.php script : if exceeded the script will exit even if there are remaining tasks to process. Must be shorter than php max_execution_time setting (note than when using CLI, this is set to 0 by default which means unlimited). If cron.php is ran via web, it must be shorter than the web server response timeout.', + 'default' => 600, + 'value' => 600, + 'source_of_value' => '', 'show_in_conf_sample' => true, ], + 'cron_task_max_execution_time' => [ + 'type' => 'integer', + 'description' => 'Background tasks will use this value (integer) multiplicated by its periodicity (in seconds) as max duration per cron execution. 0 is unlimited time', + 'default' => 0, + 'value' => 0, + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ], 'cron_sleep' => [ 'type' => 'integer', 'description' => 'Duration (seconds) before cron.php checks again if something must be done', @@ -522,7 +530,7 @@ class Config ], 'email_transport' => [ 'type' => 'string', - 'description' => 'Mean to send emails: PHPMail (uses the function mail()) or SMTP (implements the client protocol)', + 'description' => 'Mean to send emails: PHPMail (uses the function mail()), SMTP (implements the client protocol) or SMTP_OAuth (connect to the server using OAuth 2.0)', 'default' => "PHPMail", 'value' => "PHPMail", 'source_of_value' => '', @@ -544,7 +552,7 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ], - 'email_transport_smtp.encryption' => [ + 'email_transport_smtp.encryption' => [ 'type' => 'string', 'description' => 'tls or ssl (optional)', 'default' => "", @@ -552,28 +560,28 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ], - 'email_transport_smtp.username' => [ - 'type' => 'string', - 'description' => 'Authentication user (optional)', - 'default' => "", - 'value' => "", - 'source_of_value' => '', + 'email_transport_smtp.username' => [ + 'type' => 'string', + 'description' => 'Authentication user (optional)', + 'default' => "", + 'value' => "", + 'source_of_value' => '', 'show_in_conf_sample' => false, ], - 'email_transport_smtp.password' => [ - 'type' => 'string', - 'description' => 'Authentication password (optional)', - 'default' => "", - 'value' => "", - 'source_of_value' => '', + 'email_transport_smtp.password' => [ + 'type' => 'string', + 'description' => 'Authentication password (optional)', + 'default' => "", + 'value' => "", + 'source_of_value' => '', 'show_in_conf_sample' => false, ], - 'email_css' => [ - 'type' => 'string', - 'description' => 'CSS that will override the standard stylesheet used for the notifications', - 'default' => "", - 'value' => "", - 'source_of_value' => '', + 'email_css' => [ + 'type' => 'string', + 'description' => 'CSS that will override the standard stylesheet used for the notifications', + 'default' => "", + 'value' => "", + 'source_of_value' => '', 'show_in_conf_sample' => false, ], 'email_default_sender_address' => [ @@ -878,7 +886,7 @@ class Config 'type' => 'string', 'description' => 'Regular expression to validate/detect the format of an URL (URL attributes and Wiki formatting for Text attributes)', 'default' => /** @lang RegExp */ - '(https?|ftp)\://([a-zA-Z0-9+!*(),;?&=\$_.-]+(\:[a-zA-Z0-9+!*(),;?&=\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\:[0-9]{2,5})?(/([a-zA-Z0-9:%+\$_-]\.?)+)*/?(\?[a-zA-Z+&\$_.-][a-zA-Z0-9;:[\]@&%=+/\$_.-]*)?(#[a-zA-Z_.-][a-zA-Z0-9+\$_.-]*)?', + '(https?|ftp)\://([a-zA-Z0-9+!*(),;?&=\$_.-]+(\:[a-zA-Z0-9+!*(),;?&=\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\:[0-9]{2,5})?(/([a-zA-Z0-9:%+\$_-]\.?)+)*/?(\?[a-zA-Z+&\$_.-][a-zA-Z0-9;:[\]@&%=+/\$_.-]*)?(#[a-zA-Z0-9_.-][a-zA-Z0-9+\$_.-]*)?', // SCHEME....... USER....................... PASSWORD.......................... HOST/IP........... PORT.......... PATH......................... GET............................................ ANCHOR.......................... // Example: http://User:passWord@127.0.0.1:8888/patH/Page.php?arrayArgument[2]=something:blah20#myAnchor // RegExp source: http://www.php.net/manual/fr/function.preg-match.php#93824 @@ -1000,8 +1008,8 @@ class Config 'type' => 'integer', 'description' => 'Maximum length of the history table (in the "History" tab on each object) before it gets truncated. Latest modifications are displayed first.', // examples... not used - 'default' => 50, - 'value' => 50, + 'default' => 200, + 'value' => 200, 'source_of_value' => '', 'show_in_conf_sample' => false, ], @@ -1448,14 +1456,6 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ], - 'use_legacy_dbsearch' => [ - 'type' => 'bool', - 'description' => 'If set, DBSearch will use legacy SQL query generation', - 'default' => false, - 'value' => false, - 'source_of_value' => '', - 'show_in_conf_sample' => false, - ], 'query_cache_enabled' => [ 'type' => 'bool', 'description' => 'If set, DBSearch will use cache for query generation', @@ -1544,6 +1544,14 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ], + 'setup.launch_button.enabled' => [ + 'type' => 'bool', + 'description' => 'If true displays in the Application Upgrade screen a button allowing to launch the setup in a single click (no more manual config file permission change needed)', + 'default' => null, + 'value' => false, + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ], ]; public function IsProperty($sPropCode) @@ -1863,7 +1871,7 @@ class Config { // Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack) throw new ConfigException('Syntax error in configuration file', - array('file' => $sConfigFile, 'error' => ''.htmlentities($sNoise, ENT_QUOTES, 'UTF-8').'')); + array('file' => $sConfigFile, 'error' => ''.utils::EscapeHtml($sNoise, ENT_QUOTES).'')); } if (!isset($MySettings) || !is_array($MySettings)) diff --git a/core/csvbulkexport.class.inc.php b/core/csvbulkexport.class.inc.php index 50a92f8f0c..dd445217ef 100644 --- a/core/csvbulkexport.class.inc.php +++ b/core/csvbulkexport.class.inc.php @@ -125,8 +125,8 @@ class CSVBulkExport extends TabularBulkExport $sRawSeparator = utils::ReadParam('separator', ',', true, 'raw_data'); $sCustomDateTimeFormat = utils::ReadParam('', ',', true, 'raw_data'); $aSep = array( - ';' => Dict::S('UI:CSVImport:SeparatorSemicolon+'), - ',' => Dict::S('UI:CSVImport:SeparatorComma+'), + ';' => Dict::S('UI:CSVImport:SeparatorSemicolon+'), + ',' => Dict::S('UI:CSVImport:SeparatorComma+'), 'tab' => Dict::S('UI:CSVImport:SeparatorTab+'), ); $sOtherSeparator = ''; @@ -134,10 +134,10 @@ class CSVBulkExport extends TabularBulkExport $sOtherSeparator = $sRawSeparator; $sRawSeparator = 'other'; } - $aSep['other'] = Dict::S('UI:CSVImport:SeparatorOther').' '; + $aSep['other'] = Dict::S('UI:CSVImport:SeparatorOther').' '; foreach ($aSep as $sVal => $sLabel) { - $oRadio = InputUIBlockFactory::MakeForInputWithLabel($sLabel, "separator", htmlentities($sVal, ENT_QUOTES, 'UTF-8'), $sLabel, "radio"); + $oRadio = InputUIBlockFactory::MakeForInputWithLabel($sLabel, "separator", utils::EscapeHtml($sVal), $sLabel, "radio"); $oRadio->GetInput()->SetIsChecked(($sVal == $sRawSeparator)); $oRadio->SetBeforeInput(false); $oRadio->GetInput()->AddCSSClass('ibo-input--label-right'); @@ -152,7 +152,7 @@ class CSVBulkExport extends TabularBulkExport $sRawQualifier = utils::ReadParam('text-qualifier', '"', true, 'raw_data'); $aQualifiers = array( - '"' => Dict::S('UI:CSVImport:QualifierDoubleQuote+'), + '"' => Dict::S('UI:CSVImport:QualifierDoubleQuote+'), '\'' => Dict::S('UI:CSVImport:QualifierSimpleQuote+'), ); $sOtherQualifier = ''; @@ -160,10 +160,10 @@ class CSVBulkExport extends TabularBulkExport $sOtherQualifier = $sRawQualifier; $sRawQualifier = 'other'; } - $aQualifiers['other'] = Dict::S('UI:CSVImport:QualifierOther').' '; + $aQualifiers['other'] = Dict::S('UI:CSVImport:QualifierOther').' '; foreach ($aQualifiers as $sVal => $sLabel) { - $oRadio = InputUIBlockFactory::MakeForInputWithLabel($sLabel, "text-qualifier", htmlentities($sVal, ENT_QUOTES, 'UTF-8'), $sLabel, "radio"); + $oRadio = InputUIBlockFactory::MakeForInputWithLabel($sLabel, "text-qualifier", utils::EscapeHtml($sVal), $sLabel, "radio"); $oRadio->GetInput()->SetIsChecked(($sVal == $sRawSeparator)); $oRadio->SetBeforeInput(false); $oRadio->GetInput()->AddCSSClass('ibo-input--label-right'); @@ -209,8 +209,8 @@ class CSVBulkExport extends TabularBulkExport $sDateTimeFormat = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data'); - $sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8'); - $sExample = htmlentities(date((string)AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8'); + $sDefaultFormat = utils::EscapeHtml((string)AttributeDateTime::GetFormat()); + $sExample = utils::EscapeHtml(date((string)AttributeDateTime::GetFormat())); $oRadioDefault = InputUIBlockFactory::MakeForInputWithLabel(Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample), "csv_date_format_radio", "default", "csv_date_time_format_default", "radio"); $oRadioDefault->GetInput()->SetIsChecked(($sDateTimeFormat == (string)AttributeDateTime::GetFormat())); $oRadioDefault->SetBeforeInput(false); @@ -218,7 +218,7 @@ class CSVBulkExport extends TabularBulkExport $oFieldSetDate->AddSubBlock($oRadioDefault); $oFieldSetDate->AddSubBlock(new Html('
    ')); - $sFormatInput = ''; + $sFormatInput = ''; $oRadioCustom = InputUIBlockFactory::MakeForInputWithLabel(Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput), "csv_date_format_radio", "custom", "csv_date_time_format_custom", "radio"); $oRadioCustom->SetDescription(Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip')); $oRadioCustom->GetInput()->SetIsChecked($sDateTimeFormat !== (string)AttributeDateTime::GetFormat()); @@ -246,17 +246,18 @@ EOF } protected function GetSampleData($oObj, $sAttCode) - { - if ($sAttCode != 'id') - { + { + if ($sAttCode != 'id') { $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime { $sClass = (get_class($oAttDef) == 'AttributeDateTime') ? 'user-formatted-date-time' : 'user-formatted-date'; - return '
    '.htmlentities($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj), ENT_QUOTES, 'UTF-8').'
    '; + + return '
    '.utils::EscapeHtml($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj)).'
    '; } } - return '
    '.htmlentities($this->GetValue($oObj, $sAttCode), ENT_QUOTES, 'UTF-8').'
    '; + + return '
    '.utils::EscapeHtml($this->GetValue($oObj, $sAttCode)).'
    '; } protected function GetValue($oObj, $sAttCode) diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 0dc184733e..d71485a09e 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -1,6 +1,6 @@ Get($sAttCode.'_friendlyname'), ENT_QUOTES, 'UTF-8'); + $sHtmlLabel = utils::EscapeHtml($this->Get($sAttCode.'_friendlyname')); $bArchived = $this->IsArchived($sAttCode); $bObsolete = $this->IsObsolete($sAttCode); + return $this->MakeHyperLink($sTargetClass, $iTargetKey, $sHtmlLabel, null, true, $bArchived, $bObsolete); } } @@ -1471,7 +1472,7 @@ abstract class DBObject implements iDisplay public function GetIcon($bImgTag = true) { $sClass = get_class($this); - + if($this->HasHighlightIcon()) { $sIconUrl = MetaModel::GetHighlightScale($sClass)[$this->ComputeHighlightCode()]['icon']; if($bImgTag) { @@ -1506,7 +1507,7 @@ abstract class DBObject implements iDisplay { $bHasInstanceIcon = false; $sClass = get_class($this); - + if (!$this->IsNew() && MetaModel::HasImageAttributeCode($sClass)) { $sImageAttCode = MetaModel::GetImageAttributeCode($sClass); if (!empty($sImageAttCode)) { @@ -1515,7 +1516,7 @@ abstract class DBObject implements iDisplay $bHasInstanceIcon = !$oImage->IsEmpty(); } } - + return $bHasInstanceIcon; } @@ -1540,7 +1541,7 @@ abstract class DBObject implements iDisplay $bHasHighlightIcon = true; } } - + return $bHasHighlightIcon; } @@ -1588,7 +1589,7 @@ abstract class DBObject implements iDisplay */ public function GetName($sType = FriendlyNameType::SHORT) { - return htmlentities($this->GetRawName($sType), ENT_QUOTES, 'UTF-8'); + return utils::EscapeHtml($this->GetRawName($sType)); } /** @@ -1929,7 +1930,7 @@ abstract class DBObject implements iDisplay /** @var \AttributeExternalKey $oAtt */ $sTargetClass = $oAtt->GetTargetClass(); if (false === MetaModel::IsObjectInDB($sTargetClass, $toCheck)) { - return "Target object not found ($sTargetClass::$toCheck)"; + return "Target object not found (".$sTargetClass.".::".$toCheck.")"; } } if ($oAtt->IsHierarchicalKey()) @@ -2026,9 +2027,9 @@ abstract class DBObject implements iDisplay /** * check attributes together * - * @overwritable-hook You can extend this method in order to provide your own logic. - * - * @return bool + * @overwritable-hook You can extend this method in order to provide your own logic. + * + * @return true|string true if successful, the error description otherwise */ public function CheckConsistency() { @@ -2249,8 +2250,9 @@ abstract class DBObject implements iDisplay foreach($aChanges as $sAttCode => $value) { $res = $this->CheckValue($sAttCode); if ($res !== true) { + $sAttLabel = $this->GetLabel($sAttCode); // $res contains the error description - $this->m_aCheckIssues[] = "Unexpected value for attribute '$sAttCode': $res"; + $this->m_aCheckIssues[] = Dict::Format('Core:CheckValueError', $sAttLabel, $sAttCode, $res); } $this->DoCheckLinkedSetDuplicates($sAttCode, $value); @@ -2265,7 +2267,7 @@ abstract class DBObject implements iDisplay if ($res !== true) { // $res contains the error description - $this->m_aCheckIssues[] = "Consistency rules not followed: $res"; + $this->m_aCheckIssues[] = Dict::Format('Core:CheckConsistencyError', $res); } // Synchronization: are we attempting to modify an attribute for which an external source is master? @@ -2278,12 +2280,10 @@ abstract class DBObject implements iDisplay if ($iFlags & OPT_ATT_SLAVE) { // Note: $aReasonInfo['name'] could be reported (the task owning the attribute) - $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); - $sAttLabel = $oAttDef->GetLabel(); if (!empty($aReasons)) { - // Todo: associate the attribute code with the error - $this->m_aCheckIssues[] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $sAttLabel); + $sAttLabel = $this->GetLabel($sAttCode); + $this->m_aCheckIssues[] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $sAttLabel, $sAttCode); } } } @@ -2934,7 +2934,7 @@ abstract class DBObject implements iDisplay utils::EnrichRaisedException($oTrigger, $e); } } - + // - TriggerOnObjectMention $this->ActivateOnMentionTriggers(true); @@ -3202,15 +3202,15 @@ abstract class DBObject implements iDisplay // - TriggerOnObjectMention $this->ActivateOnMentionTriggers(false); - $bHasANewExternalKeyValue = false; + $bNeedReload = false; $aHierarchicalKeys = array(); $aDBChanges = array(); foreach ($aChanges as $sAttCode => $valuecurr) { $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); - if ($oAttDef->IsExternalKey()) + if ($oAttDef->IsExternalKey() || $oAttDef->IsLinkSet()) { - $bHasANewExternalKeyValue = true; + $bNeedReload = true; } if ($oAttDef->IsBasedOnDBColumns()) { @@ -3368,24 +3368,10 @@ abstract class DBObject implements iDisplay $this->m_aModifiedAtt = array(); try { - // - TriggerOnObjectUpdate - $aParams = array('class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL)); - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectUpdate AS t WHERE t.target_class IN (:class_list)"), - array(), $aParams); - while ($oTrigger = $oSet->Fetch()) { - /** @var \TriggerOnObjectUpdate $oTrigger */ - try { - $oTrigger->DoActivate($this->ToArgs('this')); - } - catch (Exception $e) { - utils::EnrichRaisedException($oTrigger, $e); - } - } - $this->AfterUpdate(); // Reload to get the external attributes - if ($bHasANewExternalKeyValue) { + if ($bNeedReload) { $this->Reload(true /* AllowAllData */); } else { // Reset original values although the object has not been reloaded @@ -3398,6 +3384,20 @@ abstract class DBObject implements iDisplay } } } + + // - TriggerOnObjectUpdate + $aParams = array('class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL)); + $oSet = new DBObjectSet(DBObjectSearch::FromOQL('SELECT TriggerOnObjectUpdate AS t WHERE t.target_class IN (:class_list)'), + array(), $aParams); + while ($oTrigger = $oSet->Fetch()) { + /** @var \TriggerOnObjectUpdate $oTrigger */ + try { + $oTrigger->DoActivate($this->ToArgs('this')); + } + catch (Exception $e) { + utils::EnrichRaisedException($oTrigger, $e); + } + } } catch (Exception $e) { @@ -3817,7 +3817,7 @@ abstract class DBObject implements iDisplay } /** - * @internal + * @overwritable-hook You can extend this method in order to provide your own logic. * * @return array * @@ -4434,14 +4434,22 @@ abstract class DBObject implements iDisplay $sAttCode = $sPlaceholderAttCode; } - if ($sVerb == 'hyperlink') + if (in_array($sVerb, ['hyperlink', 'url'])) { $sPortalId = ($sAttCode === '') ? 'console' : $sAttCode; if (!array_key_exists($sPortalId, self::$aPortalToURLMaker)) { throw new Exception("Unknown portal id '$sPortalId' in placeholder '$sPlaceholderAttCode''"); } - $ret = $this->GetHyperlink(self::$aPortalToURLMaker[$sPortalId], false); + + if($sVerb == 'hyperlink') + { + $ret = $this->GetHyperlink(self::$aPortalToURLMaker[$sPortalId], false); + } + else + { + $ret = ApplicationContext::MakeObjectUrl(get_class($this), $this->GetKey(), self::$aPortalToURLMaker[$sPortalId], false); + } } else { @@ -5201,45 +5209,34 @@ abstract class DBObject implements iDisplay if ($sSourceAttCode == 'id') { $oSourceAttDef = null; - } - else - { - if (!MetaModel::IsValidAttCode(get_class($this), $sDestAttCode)) - { + } else { + if (!MetaModel::IsValidAttCode(get_class($this), $sDestAttCode)) { throw new Exception("Unknown attribute ".get_class($this)."::".$sDestAttCode); } - if (!MetaModel::IsValidAttCode(get_class($oSourceObject), $sSourceAttCode)) - { + if (!MetaModel::IsValidAttCode(get_class($oSourceObject), $sSourceAttCode)) { throw new Exception("Unknown attribute ".get_class($oSourceObject)."::".$sSourceAttCode); } $oSourceAttDef = MetaModel::GetAttributeDef(get_class($oSourceObject), $sSourceAttCode); } - if (is_object($oSourceAttDef) && $oSourceAttDef->IsLinkSet()) - { + if (is_object($oSourceAttDef) && $oSourceAttDef->IsLinkSet()) { // The copy requires that we create a new object set (the semantic of DBObject::Set is unclear about link sets) /** @var \AttributeLinkedSet $oSourceAttDef */ - $oDestSet = DBObjectSet::FromScratch($oSourceAttDef->GetLinkedClass()); + $oDestSet = $this->Get($sDestAttCode); $oSourceSet = $oSourceObject->Get($sSourceAttCode); $oSourceSet->Rewind(); /** @var \DBObject $oSourceLink */ - while ($oSourceLink = $oSourceSet->Fetch()) - { + while ($oSourceLink = $oSourceSet->Fetch()) { // Clone the link $sLinkClass = get_class($oSourceLink); $oLinkClone = MetaModel::NewObject($sLinkClass); - foreach(MetaModel::ListAttributeDefs($sLinkClass) as $sAttCode => $oAttDef) - { + foreach (MetaModel::ListAttributeDefs($sLinkClass) as $sAttCode => $oAttDef) { // As of now, ignore other attribute (do not attempt to recurse!) - if ($oAttDef->IsScalar() && $oAttDef->IsWritable()) - { + if ($oAttDef->IsScalar() && $oAttDef->IsWritable()) { $oLinkClone->Set($sAttCode, $oSourceLink->Get($sAttCode)); } } - - // Not necessary - this will be handled by DBObject - // $oLinkClone->Set($oSourceAttDef->GetExtKeyToMe(), 0); - $oDestSet->AddObject($oLinkClone); + $oDestSet->AddItem($oLinkClone); } $this->Set($sDestAttCode, $oDestSet); } diff --git a/core/dbobjectiterator.php b/core/dbobjectiterator.php index 85e1e5208d..8f93112669 100644 --- a/core/dbobjectiterator.php +++ b/core/dbobjectiterator.php @@ -38,7 +38,7 @@ interface iDBObjectSetIterator extends Countable * * @return int */ - public function Count(); + public function Count(): int; /** * Reset the cursor to the first item in the collection. Equivalent to Seek(0) @@ -52,7 +52,7 @@ interface iDBObjectSetIterator extends Countable * * @param int $iRow */ - public function Seek($iPosition); + public function Seek($iPosition): void; /** * Fetch the object at the current position in the collection and move the cursor to the next position. diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 8be53e24b4..3406b51a81 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -413,6 +413,10 @@ class DBObjectSearch extends DBSearch } /** + * Important: If you need to add a condition on the same $sFilterCode several times with different $value values; do not use this method as the previous $value occurences will be replaced by the last. Instead use: + * * {@see \DBObjectSearch::AddConditionExpression()} in loops to add conditions one by one + * * {@see \DBObjectSearch::AddConditionForInOperatorUsingParam()} for IN/NOT IN queries with lots of params at once + * * @param string $sFilterCode * @param mixed $value * @param string $sOpCode operator to use : 'IN', 'NOT IN', 'Contains',' Begins with', 'Finishes with', ... @@ -423,22 +427,16 @@ class DBObjectSearch extends DBSearch * @param bool $bParseSearchString * * @throws \CoreException - * - * @see AddConditionForInOperatorUsingParam for IN/NOT IN queries with lots of params */ public function AddCondition($sFilterCode, $value, $sOpCode = null, $bParseSearchString = false) { - MyHelpers::CheckKeyInArray('filter code in class: '.$this->GetClass(), $sFilterCode, MetaModel::GetClassFilterDefs($this->GetClass())); + MyHelpers::CheckKeyInArray('filter code in class: '.$this->GetClass(), $sFilterCode, MetaModel::GetFilterAttribList($this->GetClass())); $oField = new FieldExpression($sFilterCode, $this->GetClassAlias()); - if (empty($sOpCode)) - { - if ($sFilterCode == 'id') - { + if (empty($sOpCode)) { + if ($sFilterCode == 'id') { $sOpCode = '='; - } - else - { + } else { $oAttDef = MetaModel::GetAttributeDef($this->GetClass(), $sFilterCode); $oNewCondition = $oAttDef->GetSmartConditionExpression($value, $oField, $this->m_aParams); $this->AddConditionExpression($oNewCondition); @@ -1900,16 +1898,13 @@ class DBObjectSearch extends DBSearch // Hide objects that are not visible to the current user // $oSearch = $this; - if (!$this->IsAllDataAllowed() && !$this->IsDataFiltered()) - { + if (!$this->IsAllDataAllowed() && !$this->IsDataFiltered()) { $oVisibleObjects = UserRights::GetSelectFilter($this->GetClass(), $this->GetModifierProperties('UserRightsGetSelectFilter')); - if ($oVisibleObjects === false) - { + if ($oVisibleObjects === false) { // Make sure this is a valid search object, saying NO for all $oVisibleObjects = DBObjectSearch::FromEmptySet($this->GetClass()); } - if (is_object($oVisibleObjects)) - { + if (is_object($oVisibleObjects)) { $oVisibleObjects->AllowAllData(); $oSearch = $this->Intersect($oVisibleObjects); $oSearch->SetDataFiltered(); @@ -1929,63 +1924,49 @@ class DBObjectSearch extends DBSearch if (isset($_SERVER['REQUEST_URI'])) { $aContextData['sRequestUri'] = $_SERVER['REQUEST_URI']; - } - else if (isset($_SERVER['SCRIPT_NAME'])) - { + } else if (isset($_SERVER['SCRIPT_NAME'])) { $aContextData['sRequestUri'] = $_SERVER['SCRIPT_NAME']; - } - else - { + } else { $aContextData['sRequestUri'] = ''; } // Need to identify the query $sOqlQuery = $oSearch->ToOql(false, null, true); - if ((strpos($sOqlQuery, '`id` IN (') !== false) || (strpos($sOqlQuery, '`id` NOT IN (') !== false)) - { + if ((strpos($sOqlQuery, '`id` IN (') !== false) || (strpos($sOqlQuery, '`id` NOT IN (') !== false)) { // Requests containing "id IN" are not worth caching $bCanCache = false; } $aContextData['sOqlQuery'] = $sOqlQuery; - if (count($aModifierProperties)) - { + if (count($aModifierProperties)) { array_multisort($aModifierProperties); $sModifierProperties = json_encode($aModifierProperties); - } - else - { + } else { $sModifierProperties = ''; } $aContextData['sModifierProperties'] = $sModifierProperties; $sRawId = Dict::GetUserLanguage().'-'.$sOqlQuery.$sModifierProperties; - if (!is_null($aAttToLoad)) - { + if (!is_null($aAttToLoad)) { $sRawId .= json_encode($aAttToLoad); } $aContextData['aAttToLoad'] = $aAttToLoad; - if (!is_null($aGroupByExpr)) - { - foreach($aGroupByExpr as $sAlias => $oExpr) - { - $sRawId .= 'g:'.$sAlias.'!'.$oExpr->Render(); + if (!is_null($aGroupByExpr)) { + foreach ($aGroupByExpr as $sAlias => $oExpr) { + $sRawId .= 'g:'.$sAlias.'!'.$oExpr->RenderExpression(); } } - if (!is_null($aSelectExpr)) - { - foreach($aSelectExpr as $sAlias => $oExpr) - { - $sRawId .= 'se:'.$sAlias.'!'.$oExpr->Render(); + if (!is_null($aSelectExpr)) { + foreach ($aSelectExpr as $sAlias => $oExpr) { + $sRawId .= 'se:'.$sAlias.'!'.$oExpr->RenderExpression(); } } $aContextData['aGroupByExpr'] = $aGroupByExpr; $aContextData['aSelectExpr'] = $aSelectExpr; $sRawId .= $bGetCount; $aContextData['bGetCount'] = $bGetCount; - if (is_array($aSelectedClasses)) - { + if (is_array($aSelectedClasses)) { $sRawId .= implode(',', $aSelectedClasses); // Unions may alter the list of selected columns } $aContextData['aSelectedClasses'] = $aSelectedClasses; @@ -2096,17 +2077,13 @@ class DBObjectSearch extends DBSearch // 3rd step - position the attributes in the hierarchy of classes // $oSubClassExp->Browse(function($oNode) use ($sSubClass) { - if ($oNode instanceof FieldExpression) - { + if ($oNode instanceof FieldExpression) { $sAttCode = $oNode->GetName(); $oAttDef = MetaModel::GetAttributeDef($sSubClass, $sAttCode); - if ($oAttDef->IsExternalField()) - { + if ($oAttDef->IsExternalField()) { $sKeyAttCode = $oAttDef->GetKeyAttCode(); $sClassOfAttribute = MetaModel::GetAttributeOrigin($sSubClass, $sKeyAttCode); - } - else - { + } else { $sClassOfAttribute = MetaModel::GetAttributeOrigin($sSubClass, $sAttCode); } $sParent = MetaModel::GetAttributeOrigin($sClassOfAttribute, $oNode->GetName()); @@ -2114,12 +2091,11 @@ class DBObjectSearch extends DBSearch } }); - $sSignature = $oSubClassExp->Render(); - if (!array_key_exists($sSignature, $aExpressions)) - { + $sSignature = $oSubClassExp->RenderExpression(); + if (!array_key_exists($sSignature, $aExpressions)) { $aExpressions[$sSignature] = array( 'expression' => $oSubClassExp, - 'classes' => array(), + 'classes' => array(), ); } $aExpressions[$sSignature]['classes'][] = $sSubClass; diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php index 57c2da7864..fb8ebb43b1 100644 --- a/core/dbobjectset.class.php +++ b/core/dbobjectset.class.php @@ -842,7 +842,7 @@ class DBObjectSet implements iDBObjectSetIterator * @throws \MySQLException * @throws \MySQLHasGoneAwayException */ - public function Count() + public function Count(): int { if (is_null($this->m_iNumTotalDBRows)) { @@ -1077,14 +1077,13 @@ class DBObjectSet implements iDBObjectSetIterator * * @param int $iRow * - * @return int|mixed - * * @throws \CoreException * @throws \MissingQueryArgument * @throws \MySQLException * @throws \MySQLHasGoneAwayException + * @since 3.1.0 N°4517 Now returns void for return type to match parent class and be compatible with PHP 8.1 */ - public function Seek($iRow) + public function Seek($iRow): void { if (!$this->m_bLoaded) $this->Load(); @@ -1093,7 +1092,6 @@ class DBObjectSet implements iDBObjectSetIterator { $this->m_oSQLResult->data_seek($this->m_iCurrRow); } - return $this->m_iCurrRow; } /** diff --git a/core/dbsearch.class.php b/core/dbsearch.class.php index 849f2a56f7..e0eb85cbe6 100644 --- a/core/dbsearch.class.php +++ b/core/dbsearch.class.php @@ -18,23 +18,6 @@ */ -$bUseLegacyDBSearch = utils::GetConfig()->Get('use_legacy_dbsearch'); - -if ($bUseLegacyDBSearch) -{ - // excluded from autoload - require_once (APPROOT.'core/legacy/querybuilderexpressionslegacy.class.inc.php'); - require_once (APPROOT.'core/legacy/querybuildercontextlegacy.class.inc.php'); - require_once(APPROOT.'core/legacy/dbobjectsearchlegacy.class.php'); -} -else -{ - // excluded from autoload - require_once (APPROOT.'core/querybuilderexpressions.class.inc.php'); - require_once (APPROOT.'core/querybuildercontext.class.inc.php'); - require_once(APPROOT.'core/dbobjectsearch.class.php'); -} - /** * An object search * @@ -1659,7 +1642,7 @@ abstract class DBSearch $oSet = new DBObjectSet($this); if (MetaModel::IsStandaloneClass($sClass)) { - $oSet->OptimizeColumnLoad(array($this->GetClassAlias() => array(''))); + $oSet->OptimizeColumnLoad(array($this->GetClassAlias() => array())); $aIds = array($sClass => $oSet->GetColumnAsArray('id')); } else @@ -1724,4 +1707,16 @@ abstract class DBSearch { $this->SetShowObsoleteData(utils::ShowObsoleteData()); } + + /** + * To ease the debug of filters + * @internal + * + * @return string + * + */ + public function __toString() + { + return $this->ToOQL(); + } } diff --git a/core/designdocument.class.inc.php b/core/designdocument.class.inc.php index a9448859d7..3e6e373ed1 100644 --- a/core/designdocument.class.inc.php +++ b/core/designdocument.class.inc.php @@ -26,8 +26,10 @@ namespace Combodo\iTop; -use \DOMDocument; -use \DOMFormatException; +use DOMDocument; +use DOMFormatException; +use IssueLog; +use LogAPI; /** * Class \Combodo\iTop\DesignDocument @@ -64,9 +66,13 @@ class DesignDocument extends DOMDocument * @param $filename * @param int $options */ - public function load($filename, $options = 0) + public function load($filename, $options = null) { - parent::load($filename, LIBXML_NOBLANKS); + libxml_clear_errors(); + if (parent::load($filename, LIBXML_NOBLANKS) === false) { + $aErrors = libxml_get_errors(); + IssueLog::Error("Error loading $filename", LogAPI::CHANNEL_DEFAULT, $aErrors); + } } /** @@ -77,10 +83,12 @@ class DesignDocument extends DOMDocument * * @return int */ - public function save($filename, $options = 0) + // Return type union is not supported by PHP 7.4, we can remove the following PHP attribute and add the return type once iTop min PHP version is PHP 8.0+ + #[\ReturnTypeWillChange] + public function save($filename, $options = null) { $this->documentElement->setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance"); - return parent::save($filename, LIBXML_NOBLANKS); + return parent::save($filename); } /** @@ -91,13 +99,12 @@ class DesignDocument extends DOMDocument public function Dump($bReturnRes = false) { $sXml = $this->saveXML(); - if ($bReturnRes) - { + if ($bReturnRes) { return $sXml; } echo "
    \n";
    -		echo htmlentities($sXml);
    +		echo utils::EscapeHtml($sXml);
     		echo "
    \n"; return ''; @@ -190,13 +197,13 @@ class DesignElement extends \DOMElement $oDoc->appendChild($oClone); $sXml = $oDoc->saveXML($oClone); - if ($bReturnRes) - { + if ($bReturnRes) { return $sXml; } echo "
    \n";
    -		echo htmlentities($sXml);
    +		echo utils::EscapeHtml($sXml);
     		echo "
    \n"; + return ''; } /** diff --git a/core/displayablegraph.class.inc.php b/core/displayablegraph.class.inc.php index b87bfd25fb..0602e4e8d5 100644 --- a/core/displayablegraph.class.inc.php +++ b/core/displayablegraph.class.inc.php @@ -459,11 +459,10 @@ class DisplayableNode extends GraphNode { $aContext = $aContextDefs[$key]; $aRootCauses = array(); - foreach($aObjects as $oRootCause) - { + foreach ($aObjects as $oRootCause) { $aRootCauses[] = $oRootCause->GetHyperlink(); } - $sHtml .= '

     '.implode(', ', $aRootCauses).'

    '; + $sHtml .= '

     '.implode(', ', $aRootCauses).'

    '; } $sHtml .= '
    '; } @@ -1195,8 +1194,10 @@ class DisplayableGraph extends SimpleGraph * @param float $xMax Right coordinate of the bounding box to display the graph * @param float $yMin Top coordinate of the bounding box to display the graph * @param float $yMax Bottom coordinate of the bounding box to display the graph + * + * @since 2.7.7 3.0.2 3.1.0 N°4985 $sComments param is no longer optional */ - function RenderAsPDF(PDFPage $oPage, $sComments = '', $sContextKey, $xMin = -1, $xMax = -1, $yMin = -1, $yMax = -1) + function RenderAsPDF(PDFPage $oPage, $sComments, $sContextKey, $xMin = -1, $xMax = -1, $yMin = -1, $yMax = -1) { $aContextDefs = static::GetContextDefinitions($sContextKey, false); // No need to develop the parameters $oPdf = $oPage->get_tcpdf(); @@ -1333,18 +1334,17 @@ class DisplayableGraph extends SimpleGraph } $oPdf->Rect($xMin, $yMin, $fMaxWidth + $fIconSize + 3*$fPadding, $yMax - $yMin, 'D'); - if ($sComments != '') - { + if ($sComments != '') { // Draw the comment text (surrounded by a rectangle) - $xPos = $xMin + $fMaxWidth + $fIconSize + 4*$fPadding; - $w = $xMax - $xPos - 2*$fPadding; + $xPos = $xMin + $fMaxWidth + $fIconSize + 4 * $fPadding; + $w = $xMax - $xPos - 2 * $fPadding; $iNbLines = 1; - $sText = '

    '.str_replace("\n", '
    ', htmlentities($sComments, ENT_QUOTES, 'UTF-8'), $iNbLines).'

    '; + $sText = '

    '.str_replace("\n", '
    ', utils::EscapeHtml($sComments), $iNbLines).'

    '; $fLineHeight = $oPdf->getStringHeight($w, $sText); - $h = (1+$iNbLines) * $fLineHeight; - $yPos = $yMax - 2*$fPadding - $h; + $h = (1 + $iNbLines) * $fLineHeight; + $yPos = $yMax - 2 * $fPadding - $h; $oPdf->writeHTMLCell($w, $h, $xPos + $fPadding, $yPos + $fPadding, $sText, 0 /* border */, 1 /* ln */); - $oPdf->Rect($xPos, $yPos, $w + 2*$fPadding, $h + 2*$fPadding, 'D'); + $oPdf->Rect($xPos, $yPos, $w + 2 * $fPadding, $h + 2 * $fPadding, 'D'); $yMax = $yPos - $fPadding; } @@ -1421,13 +1421,14 @@ class DisplayableGraph extends SimpleGraph * @param int $iObjKey * @param string $sContextKey * @param array $aContextParams + * @param bool $bLazyLoading since 2.7.7 3.0.1 * * @throws \CoreException * @throws \DictExceptionMissingString */ - function Display(WebPage $oP, $aResults, $sRelation, ApplicationContext $oAppContext, $aExcludedObjects, $sObjClass, $iObjKey, $sContextKey, $aContextParams = array(), bool $sLazyLoading = false) + function Display(WebPage $oP, $aResults, $sRelation, ApplicationContext $oAppContext, $aExcludedObjects, $sObjClass, $iObjKey, $sContextKey, $aContextParams = array(), bool $bLazyLoading = false) { - list($aExcludedByClass, $aAdditionalContexts) = $this->DisplayFiltering($sContextKey, $aContextParams, $aExcludedObjects, $oP, $aResults, $sLazyLoading); + list($aExcludedByClass, $aAdditionalContexts) = $this->DisplayFiltering($sContextKey, $aContextParams, $aExcludedObjects, $oP, $aResults, $bLazyLoading); $iGroupingThreshold = utils::ReadParam('g', 5); @@ -1508,10 +1509,12 @@ class DisplayableGraph extends SimpleGraph // Export as Attachment requires GD (for building the PDF) AND a valid objclass/objkey couple unset($aParams['export_as_attachment']); } - if ($oP->IsPrintableVersion() || !$sLazyLoading) { + if ($oP->IsPrintableVersion() || !$bLazyLoading) { $oP->add_ready_script(" $('#$sId').simple_graph(".json_encode($aParams).");"); } else { $oP->add_script("function Load(){var aExcluded = []; $('input[name^=excluded]').each( function() {if (!$(this).prop('checked')) { aExcluded.push($(this).val()); }} ); var params= $.extend(".json_encode($aParams).", {excluded_classes: aExcluded}); $('#$sId').simple_graph(params);}"); + $oP->add_ready_script("$('#impacted_objects_lists').html('".utils::TextToHtml(Dict::S('Relation:impacts/NoFilteredData'))."');$('#impacted_groups').html('".utils::TextToHtml(Dict::S('Relation:impacts/NoFilteredData'))."');"); + } } catch(Exception $e) @@ -1551,6 +1554,7 @@ EOF * @param array $aExcludedObjects * @param \WebPage $oP * @param array $aResults + * @param bool $bLazyLoading * * @return array * @throws \CoreException @@ -1560,7 +1564,7 @@ EOF * @throws \Twig\Error\RuntimeError * @throws \Twig\Error\SyntaxError */ - public function DisplayFiltering(string $sContextKey, array $aContextParams, array $aExcludedObjects, WebPage $oP, array $aResults, bool $sLazyLoading = false): array + public function DisplayFiltering(string $sContextKey, array $aContextParams, array $aExcludedObjects, WebPage $oP, array $aResults, bool $bLazyLoading = false): array { $aContextDefs = static::GetContextDefinitions($sContextKey, true, $aContextParams); $aExcludedByClass = array(); @@ -1590,7 +1594,7 @@ EOF $("#ReloadMovieBtn").button().button("disable"); EOF ); - if ($sLazyLoading) { + if ($bLazyLoading) { $oP->add_ready_script("$('#ReloadMovieBtn').button('enable');"); } else { $oP->add_ready_script("$('#dh_flash').addClass('closed');"); @@ -1614,7 +1618,7 @@ EOF $idx++; } $oUiHtmlBlock->AddHtml("
    "); - if ($sLazyLoading) { + if ($bLazyLoading) { $sOnCLick = "Load(); $('#ReloadMovieBtn').attr('onclick','DoReload()');$('#ReloadMovieBtn').html('".Dict::S('UI:Button:Refresh')."');"; $oUiHtmlBlock->AddHtml("
    "); } else { diff --git a/core/email.class.inc.php b/core/email.class.inc.php index d265dc97e9..7223592913 100644 --- a/core/email.class.inc.php +++ b/core/email.class.inc.php @@ -1,5 +1,5 @@ setCharset('UTF-8'); - +use Combodo\iTop\Core\Email\EmailFactory; +use Combodo\iTop\Core\Email\iEMail; define ('EMAIL_SEND_OK', 0); define ('EMAIL_SEND_PENDING', 1); define ('EMAIL_SEND_ERROR', 2); -class EMail +class EMail implements iEMail { + /** + * @see self::LoadConfig() + * @var Config + * @since 2.7.7 3.0.2 3.1.0 N°3169 N°5102 Move attribute to children classes + * @since 2.7.8 3.0.3 3.1.0 N°4947 pull up the attribute back to the Email class as config init is done here + */ + protected static $m_oConfig = null; + protected $oMailer; + // Serialization formats const ORIGINAL_FORMAT = 1; // Original format, consisting in serializing the whole object, inculding the Swift Mailer's object. - // Did not work with attachements since their binary representation cannot be stored as a valid UTF-8 string + // Did not work with attachements since their binary representation cannot be stored as a valid UTF-8 string const FORMAT_V2 = 2; // New format, only the raw data are serialized (base64 encoded if needed) - - protected static $m_oConfig = null; - protected $m_aData; // For storing data to serialize - - public function LoadConfig($sConfigFile = ITOP_DEFAULT_CONFIG_FILE) - { - if (is_null(self::$m_oConfig)) - { - self::$m_oConfig = new Config($sConfigFile); - } - } - - protected $m_oMessage; public function __construct() { - $this->m_aData = array(); - $this->m_oMessage = Swift_Message::newInstance(); - $this->SetRecipientFrom(MetaModel::GetConfig()->Get('email_default_sender_address'), MetaModel::GetConfig()->Get('email_default_sender_label')); + $this->oMailer = EmailFactory::GetMailer(); + } + + /** + * Sets {@see m_oConfig} if current attribute is null + * + * @returns \Config the current {@see m_oConfig} value + * @throws \ConfigException + * @throws \CoreException + * + * @uses utils::GetConfig() + * + * @since 2.7.7 3.0.2 3.1.0 N°3169 N°5102 Move method to children classes + * @since 2.7.8 3.0.3 3.1.0 N°4947 Pull up to the parent class, and remove `$sConfigFile` param + */ + public function LoadConfig() + { + if (is_null(static::$m_oConfig)) { + static::$m_oConfig = utils::GetConfig(); + } + + return static::$m_oConfig; + } + + /** + * @return void + * @throws \ConfigException + * @throws \CoreException + * @since 2.7.>8 3.0.3 3.1.0 N°4947 Method creation, to factorize same code in children classes + */ + protected function InitRecipientFrom() + { + $oConfig = $this->LoadConfig(); + $this->SetRecipientFrom( + $oConfig->Get('email_default_sender_address'), + $oConfig->Get('email_default_sender_label') + ); } /** @@ -70,490 +96,110 @@ class EMail */ public function SerializeV2() { - return serialize($this->m_aData); + return $this->oMailer->SerializeV2(); } - + /** * Custom de-serialization method + * * @param string $sSerializedMessage The serialized representation of the message + * + * @return \Email + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \Symfony\Component\CssSelector\Exception\SyntaxErrorException */ - static public function UnSerializeV2($sSerializedMessage) + public static function UnSerializeV2($sSerializedMessage) { - $aData = unserialize($sSerializedMessage); - $oMessage = new Email(); - - if (array_key_exists('body', $aData)) - { - $oMessage->SetBody($aData['body']['body'], $aData['body']['mimeType']); - } - if (array_key_exists('message_id', $aData)) - { - $oMessage->SetMessageId($aData['message_id']); - } - if (array_key_exists('bcc', $aData)) - { - $oMessage->SetRecipientBCC($aData['bcc']); - } - if (array_key_exists('cc', $aData)) - { - $oMessage->SetRecipientCC($aData['cc']); - } - if (array_key_exists('from', $aData)) - { - $oMessage->SetRecipientFrom($aData['from']['address'], $aData['from']['label']); - } - if (array_key_exists('reply_to', $aData)) - { - $oMessage->SetRecipientReplyTo($aData['reply_to']['address'], $aData['reply_to']['label']); - } - if (array_key_exists('to', $aData)) - { - $oMessage->SetRecipientTO($aData['to']); - } - if (array_key_exists('subject', $aData)) - { - $oMessage->SetSubject($aData['subject']); - } - - - if (array_key_exists('headers', $aData)) - { - foreach($aData['headers'] as $sKey => $sValue) - { - $oMessage->AddToHeader($sKey, $sValue); - } - } - if (array_key_exists('parts', $aData)) - { - foreach($aData['parts'] as $aPart) - { - $oMessage->AddPart($aPart['text'], $aPart['mimeType']); - } - } - if (array_key_exists('attachments', $aData)) - { - foreach($aData['attachments'] as $aAttachment) - { - $oMessage->AddAttachment(base64_decode($aAttachment['data']), $aAttachment['filename'], $aAttachment['mimeType']); - } - } - return $oMessage; - } - - protected function SendAsynchronous(&$aIssues, $oLog = null) - { - try - { - AsyncSendEmail::AddToQueue($this, $oLog); - } - catch(Exception $e) - { - $aIssues = array($e->GetMessage()); - return EMAIL_SEND_ERROR; - } - $aIssues = array(); - return EMAIL_SEND_PENDING; - } - - protected function SendSynchronous(&$aIssues, $oLog = null) - { - // If the body of the message is in HTML, embed all images based on attachments - $this->EmbedInlineImages(); - - $this->LoadConfig(); - - $sTransport = self::$m_oConfig->Get('email_transport'); - switch ($sTransport) - { - case 'SMTP': - $sHost = self::$m_oConfig->Get('email_transport_smtp.host'); - $sPort = self::$m_oConfig->Get('email_transport_smtp.port'); - $sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption'); - $sUserName = self::$m_oConfig->Get('email_transport_smtp.username'); - $sPassword = self::$m_oConfig->Get('email_transport_smtp.password'); - - $oTransport = Swift_SmtpTransport::newInstance($sHost, $sPort, $sEncryption); - if (strlen($sUserName) > 0) - { - $oTransport->setUsername($sUserName); - $oTransport->setPassword($sPassword); - } - break; - - case 'Null': - $oTransport = Swift_NullTransport::newInstance(); - break; - - case 'LogFile': - $oTransport = Swift_LogFileTransport::newInstance(); - $oTransport->setLogFile(APPROOT.'log/mail.log'); - break; - - case 'PHPMail': - default: - $oTransport = Swift_MailTransport::newInstance(); - } - - $oMailer = Swift_Mailer::newInstance($oTransport); - - $aFailedRecipients = array(); - $this->m_oMessage->setMaxLineLength(0); - $oKPI = new ExecutionKPI(); - try - { - $iSent = $oMailer->send($this->m_oMessage, $aFailedRecipients); - if ($iSent === 0) - { - // Beware: it seems that $aFailedRecipients sometimes contains the recipients that actually received the message !!! - IssueLog::Warning('Email sending failed: Some recipients were invalid, aFailedRecipients contains: '.implode(', ', $aFailedRecipients)); - $aIssues = array('Some recipients were invalid.'); - $oKPI->ComputeStats('Email Sent', 'Error received'); - return EMAIL_SEND_ERROR; - } - else - { - $aIssues = array(); - $oKPI->ComputeStats('Email Sent', 'Succeded'); - return EMAIL_SEND_OK; - } - } - catch (Exception $e) - { - $oKPI->ComputeStats('Email Sent', 'Error received'); - throw $e; - } - } - - /** - * Reprocess the body of the message (if it is an HTML message) - * to replace the URL of images based on attachments by a link - * to an embedded image (i.e. cid:....) - */ - protected function EmbedInlineImages() - { - if ($this->m_aData['body']['mimeType'] == 'text/html') - { - $oDOMDoc = new DOMDocument(); - $oDOMDoc->preserveWhitespace = true; - @$oDOMDoc->loadHTML(''.$this->m_aData['body']['body']); // For loading HTML chunks where the character set is not specified - - $oXPath = new DOMXPath($oDOMDoc); - $sXPath = '//img[@'.InlineImage::DOM_ATTR_ID.']'; - $oImagesList = $oXPath->query($sXPath); - - if ($oImagesList->length != 0) - { - foreach($oImagesList as $oImg) - { - $iAttId = $oImg->getAttribute(InlineImage::DOM_ATTR_ID); - $oAttachment = MetaModel::GetObject('InlineImage', $iAttId, false, true /* Allow All Data */); - if ($oAttachment) - { - $sImageSecret = $oImg->getAttribute('data-img-secret'); - $sAttachmentSecret = $oAttachment->Get('secret'); - if ($sImageSecret !== $sAttachmentSecret) - { - // @see N°1921 - // If copying from another iTop we could get an IMG pointing to an InlineImage with wrong secret - continue; - } - - $oDoc = $oAttachment->Get('contents'); - $oSwiftImage = new Swift_Image($oDoc->GetData(), $oDoc->GetFileName(), $oDoc->GetMimeType()); - $sCid = $this->m_oMessage->embed($oSwiftImage); - $oImg->setAttribute('src', $sCid); - } - } - } - $sHtmlBody = $oDOMDoc->saveHTML(); - $this->m_oMessage->setBody($sHtmlBody, 'text/html', 'UTF-8'); - } + return EmailFactory::GetMailer()::UnSerializeV2($sSerializedMessage); } public function Send(&$aIssues, $bForceSynchronous = false, $oLog = null) { - //select a default sender if none is provided. - if(empty($this->m_aData['from']['address']) && !empty($this->m_aData['to'])){ - $this->SetRecipientFrom($this->m_aData['to']); - } - - if ($bForceSynchronous) - { - return $this->SendSynchronous($aIssues, $oLog); - } - else - { - $bConfigASYNC = MetaModel::GetConfig()->Get('email_asynchronous'); - if ($bConfigASYNC) - { - return $this->SendAsynchronous($aIssues, $oLog); - } - else - { - return $this->SendSynchronous($aIssues, $oLog); - } - } + return $this->oMailer->Send($aIssues, $bForceSynchronous, $oLog); } public function AddToHeader($sKey, $sValue) { - if (!array_key_exists('headers', $this->m_aData)) - { - $this->m_aData['headers'] = array(); - } - $this->m_aData['headers'][$sKey] = $sValue; - - if (strlen($sValue) > 0) - { - $oHeaders = $this->m_oMessage->getHeaders(); - switch(strtolower($sKey)) - { - case 'return-path': - $this->m_oMessage->setReturnPath($sValue); - break; - - default: - $oHeaders->addTextHeader($sKey, $sValue); - } - } + $this->oMailer->AddToHeader($sKey, $sValue); } public function SetMessageId($sId) { - $this->m_aData['message_id'] = $sId; - - // Note: Swift will add the angle brackets for you - // so let's remove the angle brackets if present, for historical reasons - $sId = str_replace(array('<', '>'), '', $sId); - - $oMsgId = $this->m_oMessage->getHeaders()->get('Message-ID'); - $oMsgId->SetId($sId); + $this->oMailer->SetMessageId($sId); } - + public function SetReferences($sReferences) { - $this->AddToHeader('References', $sReferences); + $this->oMailer->SetReferences($sReferences); + } + + /** + * Set the "In-Reply-To" header to allow emails to group as a conversation in modern mail clients (GMail, Outlook 2016+, ...) + * + * @link https://en.wikipedia.org/wiki/Email#Header_fields + * + * @param string $sMessageId + * + * @since 3.0.1 N°4849 + */ + public function SetInReplyTo(string $sMessageId) + { + $this->AddToHeader('In-Reply-To', $sMessageId); } public function SetBody($sBody, $sMimeType = 'text/html', $sCustomStyles = null) { - if (($sMimeType === 'text/html') && ($sCustomStyles !== null)) - { - $oDomDocument = CssInliner::fromHtml($sBody)->inlineCss($sCustomStyles)->getDomDocument(); - HtmlPruner::fromDomDocument($oDomDocument)->removeElementsWithDisplayNone(); - $sBody = CssToAttributeConverter::fromDomDocument($oDomDocument)->convertCssToVisualAttributes()->render(); // Adds html/body tags if not already present + $this->oMailer->SetBody($sBody, $sMimeType, $sCustomStyles); } - $this->m_aData['body'] = array('body' => $sBody, 'mimeType' => $sMimeType); - $this->m_oMessage->setBody($sBody, $sMimeType); - } public function AddPart($sText, $sMimeType = 'text/html') { - if (!array_key_exists('parts', $this->m_aData)) - { - $this->m_aData['parts'] = array(); - } - $this->m_aData['parts'][] = array('text' => $sText, 'mimeType' => $sMimeType); - $this->m_oMessage->addPart($sText, $sMimeType); + $this->oMailer->AddPart($sText, $sMimeType); } public function AddAttachment($data, $sFileName, $sMimeType) { - if (!array_key_exists('attachments', $this->m_aData)) - { - $this->m_aData['attachments'] = array(); - } - $this->m_aData['attachments'][] = array('data' => base64_encode($data), 'filename' => $sFileName, 'mimeType' => $sMimeType); - $this->m_oMessage->attach(Swift_Attachment::newInstance($data, $sFileName, $sMimeType)); + $this->oMailer->AddAttachment($data, $sFileName, $sMimeType); } public function SetSubject($sSubject) { - $this->m_aData['subject'] = $sSubject; - $this->m_oMessage->setSubject($sSubject); + $this->oMailer->SetSubject($sSubject); } public function GetSubject() { - return $this->m_oMessage->getSubject(); + return $this->oMailer->GetSubject(); } - /** - * Helper to transform and sanitize addresses - * - get rid of empty addresses - */ - protected function AddressStringToArray($sAddressCSVList) - { - $aAddresses = array(); - foreach(explode(',', $sAddressCSVList) as $sAddress) - { - $sAddress = trim($sAddress); - if (strlen($sAddress) > 0) - { - $aAddresses[] = $sAddress; - } - } - return $aAddresses; - } - public function SetRecipientTO($sAddress) { - $this->m_aData['to'] = $sAddress; - if (!empty($sAddress)) - { - $aAddresses = $this->AddressStringToArray($sAddress); - $this->m_oMessage->setTo($aAddresses); - } + $this->oMailer->SetRecipientTO($sAddress); } public function GetRecipientTO($bAsString = false) { - $aRes = $this->m_oMessage->getTo(); - if ($aRes === null) - { - // There is no "To" header field - $aRes = array(); - } - if ($bAsString) - { - $aStrings = array(); - foreach ($aRes as $sEmail => $sName) - { - if (is_null($sName)) - { - $aStrings[] = $sEmail; - } - else - { - $sName = str_replace(array('<', '>'), '', $sName); - $aStrings[] = "$sName <$sEmail>"; - } - } - return implode(', ', $aStrings); - } - else - { - return $aRes; - } + return $this->oMailer->GetRecipientTO($bAsString); } public function SetRecipientCC($sAddress) { - $this->m_aData['cc'] = $sAddress; - if (!empty($sAddress)) - { - $aAddresses = $this->AddressStringToArray($sAddress); - $this->m_oMessage->setCc($aAddresses); - } + $this->oMailer->SetRecipientCC($sAddress); } public function SetRecipientBCC($sAddress) { - $this->m_aData['bcc'] = $sAddress; - if (!empty($sAddress)) - { - $aAddresses = $this->AddressStringToArray($sAddress); - $this->m_oMessage->setBcc($aAddresses); - } + $this->oMailer->SetRecipientBCC($sAddress); } public function SetRecipientFrom($sAddress, $sLabel = '') { - $this->m_aData['from'] = array('address' => $sAddress, 'label' => $sLabel); - if ($sLabel != '') - { - $this->m_oMessage->setFrom(array($sAddress => $sLabel)); - } - else if (!empty($sAddress)) - { - $this->m_oMessage->setFrom($sAddress); - } + $this->oMailer->SetRecipientFrom($sAddress, $sLabel); } public function SetRecipientReplyTo($sAddress, $sLabel = '') { - $this->m_aData['reply_to'] = array('address' => $sAddress, 'label' => $sLabel); - if ($sLabel != '') - { - $this->m_oMessage->setReplyTo(array($sAddress => $sLabel)); - } - else if (!empty($sAddress)) - { - $this->m_oMessage->setReplyTo($sAddress); - } - } - -} - -///////////////////////////////////////////////////////////////////////////////////// - -/** - * Extension to SwiftMailer: "debug" transport that pretends messages have been sent, - * but just log them to a file. - * - * @package Swift - * @author Denis Flaven - */ -class Swift_Transport_LogFileTransport extends Swift_Transport_NullTransport -{ - protected $sLogFile; - - /** - * Sends the given message. - * - * @param Swift_Mime_Message $message - * @param string[] $failedRecipients An array of failures by-reference - * - * @return int The number of sent emails - */ - public function send(Swift_Mime_Message $message, &$failedRecipients = null) - { - $hFile = @fopen($this->sLogFile, 'a'); - if ($hFile) - { - $sTxt = "================== ".date('Y-m-d H:i:s')." ==================\n"; - $sTxt .= $message->toString()."\n"; - - @fwrite($hFile, $sTxt); - @fclose($hFile); - } - - return parent::send($message, $failedRecipients); - } - - public function setLogFile($sFilename) - { - $this->sLogFile = $sFilename; - } -} - -/** - * Pretends messages have been sent, but just log them to a file. - * - * @package Swift - * @author Denis Flaven - */ -class Swift_LogFileTransport extends Swift_Transport_LogFileTransport -{ - /** - * Create a new LogFileTransport. - */ - public function __construct() - { - call_user_func_array( - array($this, 'Swift_Transport_LogFileTransport::__construct'), - Swift_DependencyContainer::getInstance() - ->createDependenciesFor('transport.null') - ); - } - - /** - * Create a new LogFileTransport instance. - * - * @return Swift_LogFileTransport - */ - public static function newInstance() - { - return new self(); + $this->oMailer->SetRecipientReplyTo($sAddress); } } \ No newline at end of file diff --git a/core/excelbulkexport.class.inc.php b/core/excelbulkexport.class.inc.php index caecf7111c..78c81ed5d6 100644 --- a/core/excelbulkexport.class.inc.php +++ b/core/excelbulkexport.class.inc.php @@ -100,8 +100,8 @@ class ExcelBulkExport extends TabularBulkExport $sDateTimeFormat = utils::ReadParam('date_format', (string)AttributeDateTime::GetFormat(), true, 'raw_data'); - $sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8'); - $sExample = htmlentities(date((string)AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8'); + $sDefaultFormat = utils::EscapeHtml((string)AttributeDateTime::GetFormat()); + $sExample = utils::EscapeHtml(date((string)AttributeDateTime::GetFormat())); $oRadioDefault = InputUIBlockFactory::MakeForInputWithLabel(Dict::Format('Core:BulkExport:DateTimeFormatDefault_Example', $sDefaultFormat, $sExample), "excel_date_format_radio", "default", "excel_date_time_format_default", "radio"); $oRadioDefault->GetInput()->SetIsChecked(($sDateTimeFormat == (string)AttributeDateTime::GetFormat())); $oRadioDefault->SetBeforeInput(false); @@ -109,7 +109,7 @@ class ExcelBulkExport extends TabularBulkExport $oFieldSetDate->AddSubBlock($oRadioDefault); $oFieldSetDate->AddSubBlock(new Html('
    ')); - $sFormatInput = ''; + $sFormatInput = ''; $oRadioCustom = InputUIBlockFactory::MakeForInputWithLabel(Dict::Format('Core:BulkExport:DateTimeFormatCustom_Format', $sFormatInput), "excel_date_format_radio", "custom", "excel_date_time_format_custom", "radio"); $oRadioCustom->SetDescription(Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip')); $oRadioCustom->GetInput()->SetIsChecked($sDateTimeFormat !== (string)AttributeDateTime::GetFormat()); @@ -156,16 +156,17 @@ EOF protected function GetSampleData($oObj, $sAttCode) { - if ($sAttCode != 'id') - { + if ($sAttCode != 'id') { $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime { $sClass = (get_class($oAttDef) == 'AttributeDateTime') ? 'user-formatted-date-time' : 'user-formatted-date'; - return '
    '.htmlentities($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj), ENT_QUOTES, 'UTF-8').'
    '; + + return '
    '.utils::EscapeHtml($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj)).'
    '; } } - return '
    '.htmlentities($this->GetValue($oObj, $sAttCode), ENT_QUOTES, 'UTF-8').'
    '; + + return '
    '.utils::EscapeHtml($this->GetValue($oObj, $sAttCode)).'
    '; } protected function GetValue($oObj, $sAttCode) diff --git a/core/expression.class.inc.php b/core/expression.class.inc.php index 1135cff4ca..5e05bf7103 100644 --- a/core/expression.class.inc.php +++ b/core/expression.class.inc.php @@ -1,4 +1,8 @@ GetClass().". Do not use. Will be removed in next version."); $this->m_sCode = $sCode; $this->m_aParams = $aParams; $this->ConsistencyCheck(); @@ -98,8 +100,9 @@ abstract class FilterDefinition } /** - * Match against the object unique identifier + * Match against the object unique identifier * + * @deprecated 3.1.0 N°4690 - Deprecate "FilterCodes" * @package iTopORM */ class FilterPrivateKey extends FilterDefinition @@ -145,8 +148,9 @@ class FilterPrivateKey extends FilterDefinition } /** - * Match against an existing attribute (the attribute type will determine the available operators) + * Match against an existing attribute (the attribute type will determine the available operators) * + * @deprecated 3.1.0 N°4690 - Deprecate "FilterCodes" * @package iTopORM */ class FilterFromAttribute extends FilterDefinition diff --git a/core/htmlbulkexport.class.inc.php b/core/htmlbulkexport.class.inc.php index 0b5a62bf27..685734e860 100644 --- a/core/htmlbulkexport.class.inc.php +++ b/core/htmlbulkexport.class.inc.php @@ -62,7 +62,8 @@ class HTMLBulkExport extends TabularBulkExport if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime { $sClass = (get_class($oAttDef) == 'AttributeDateTime') ? 'user-formatted-date-time' : 'user-formatted-date'; - return '
    '.htmlentities($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj), ENT_QUOTES, 'UTF-8').'
    '; + + return '
    '.utils::EscapeHtml($oAttDef->GetEditValue($oObj->Get($sAttCode), $oObj)).'
    '; } } return $this->GetValue($oObj, $sAttCode); diff --git a/core/htmlsanitizer.class.inc.php b/core/htmlsanitizer.class.inc.php index 2be8b1da52..8d8b14dbf2 100644 --- a/core/htmlsanitizer.class.inc.php +++ b/core/htmlsanitizer.class.inc.php @@ -108,6 +108,18 @@ abstract class DOMSanitizer extends HTMLSanitizer { /** @var DOMDocument */ protected $oDoc; + /** + * @var string Class to use for InlineImage static method calls + * @used-by \Combodo\iTop\Test\UnitTest\Core\Sanitizer\HTMLDOMSanitizerTest::testDoSanitizeCallInlineImageProcessImageTag + */ + protected $sInlineImageClassName; + + public function __construct($sInlineImageClassName = InlineImage::class) + { + parent::__construct(); + + $this->sInlineImageClassName = $sInlineImageClassName; + } abstract public function GetTagsWhiteList(); @@ -203,7 +215,7 @@ abstract class DOMSanitizer extends HTMLSanitizer // Recurse $this->CleanNode($oNode); if (($oNode instanceof DOMElement) && (strtolower($oNode->tagName) == 'img')) { - InlineImage::ProcessImageTag($oNode); + $this->sInlineImageClassName::ProcessImageTag($oNode); } } } @@ -338,6 +350,30 @@ class HTMLDOMSanitizer extends DOMSanitizer 'white-space', ); + public function __construct($sInlineImageClassName = InlineImage::class) + { + parent::__construct($sInlineImageClassName); + + // Building href validation pattern from url and email validation patterns as the patterns are not used the same way in HTML content than in standard attributes value. + // eg. "foo@bar.com" vs "mailto:foo@bar.com?subject=Title&body=Hello%20world" + if (!array_key_exists('href', self::$aAttrsWhiteList)) { + // Regular urls + $sUrlPattern = utils::GetConfig()->Get('url_validation_pattern'); + + // Mailto urls + $sMailtoPattern = '(mailto:('.utils::GetConfig()->Get('email_validation_pattern').')(?:\?(?:subject|body)=([a-zA-Z0-9+\$_.-]*)(?:&(?:subject|body)=([a-zA-Z0-9+\$_.-]*))?)?)'; + + // Notification placeholders + // eg. $this->caller_id$, $this->hyperlink()$, $this->hyperlink(portal)$, $APP_URL$, $MODULES_URL$, ... + // Note: Authorize both $xxx$ and %24xxx%24 as the latter one is encoded when used in HTML attributes (eg. a[href]) + $sPlaceholderPattern = '(\$|%24)[\w-]*(->[\w]*(\([\w-]*?\))?)?(\$|%24)'; + + $sPattern = $sUrlPattern.'|'.$sMailtoPattern.'|'.$sPlaceholderPattern; + $sPattern = '/'.str_replace('/', '\/', $sPattern).'/i'; + self::$aAttrsWhiteList['href'] = $sPattern; + } + } + public function GetTagsWhiteList() { return static::$aTagsWhiteList; @@ -363,31 +399,6 @@ class HTMLDOMSanitizer extends DOMSanitizer return static::$aStylesWhiteList; } - public function __construct() - { - parent::__construct(); - - // Building href validation pattern from url and email validation patterns as the patterns are not used the same way in HTML content than in standard attributes value. - // eg. "foo@bar.com" vs "mailto:foo@bar.com?subject=Title&body=Hello%20world" - if (!array_key_exists('href', self::$aAttrsWhiteList)) - { - // Regular urls - $sUrlPattern = utils::GetConfig()->Get('url_validation_pattern'); - - // Mailto urls - $sMailtoPattern = '(mailto:(' . utils::GetConfig()->Get('email_validation_pattern') . ')(?:\?(?:subject|body)=([a-zA-Z0-9+\$_.-]*)(?:&(?:subject|body)=([a-zA-Z0-9+\$_.-]*))?)?)'; - - // Notification placeholders - // eg. $this->caller_id$, $this->hyperlink()$, $this->hyperlink(portal)$, $APP_URL$, $MODULES_URL$, ... - // Note: Authorize both $xxx$ and %24xxx%24 as the latter one is encoded when used in HTML attributes (eg. a[href]) - $sPlaceholderPattern = '(\$|%24)[\w-]*(->[\w]*(\([\w-]*?\))?)?(\$|%24)'; - - $sPattern = $sUrlPattern . '|' . $sMailtoPattern . '|' . $sPlaceholderPattern; - $sPattern = '/'.str_replace('/', '\/', $sPattern).'/i'; - self::$aAttrsWhiteList['href'] = $sPattern; - } - } - public function LoadDoc($sHTML) { @$this->oDoc->loadHTML(''.$sHTML); // For loading HTML chunks where the character set is not specified diff --git a/core/inlineimage.class.inc.php b/core/inlineimage.class.inc.php index ec71d82a0c..66aa6d70db 100644 --- a/core/inlineimage.class.inc.php +++ b/core/inlineimage.class.inc.php @@ -295,13 +295,12 @@ class InlineImage extends DBObject { $sImgTag = $aImgInfo[0][0]; $sSecret = ''; - if (preg_match('/data-img-secret="([0-9a-f]+)"/', $sImgTag, $aSecretMatches)) - { + if (preg_match('/data-img-secret="([0-9a-f]+)"/', $sImgTag, $aSecretMatches)) { $sSecret = '&s='.$aSecretMatches[1]; } $sAttId = $aImgInfo[2][0]; - - $sNewImgTag = preg_replace('/src="[^"]+"/', 'src="'.htmlentities($sUrl.$sAttId.$sSecret, ENT_QUOTES, 'UTF-8').'"', $sImgTag); // preserve other attributes, must convert & to & to be idempotent with CKEditor + + $sNewImgTag = preg_replace('/src="[^"]+"/', 'src="'.utils::EscapeHtml($sUrl.$sAttId.$sSecret).'"', $sImgTag); // preserve other attributes, must convert & to & to be idempotent with CKEditor $aNeedles[] = $sImgTag; $aReplacements[] = $sNewImgTag; } @@ -536,8 +535,8 @@ JS $iObjKey = $oObject->GetKey(); $sAbsoluteUrlAppRoot = utils::GetAbsoluteUrlAppRoot(); - $sToggleFullScreen = htmlentities(Dict::S('UI:ToggleFullScreen'), ENT_QUOTES, 'UTF-8'); - + $sToggleFullScreen = utils::EscapeHtml(Dict::S('UI:ToggleFullScreen')); + return << -// - -// THIS FILE IS DEPRECATED - -/** @internal Dev hack for disabling some query build optimizations (Folding/Merging) */ -define('ENABLE_OPT', true); - -/** - * A search over a DBObject - * - * This is the most common search cases, the other class representing a search is DBUnionSearch. - * For clarity purpose, since only the constructor vary between DBObjectSearch and DBUnionSearch, all the API is documented on the common - * ancestor: DBSearch Please refer to DBSearch's documentation - * - * @api - * @see DBSearch - * @see DBUnionSearch - * @package iTopORM - * @phpdoc-tuning-exclude-inherited this tag prevent PHPdoc from displaying inherited methods. This is done in order to force the API doc. - * location into DBSearch only. - */ -class DBObjectSearch extends DBSearch -{ - private $m_aClasses; // queried classes (alias => class name), the first item is the class corresponding to this filter (the rest is coming from subfilters) - private $m_aSelectedClasses; // selected for the output (alias => class name) - private $m_oSearchCondition; - private $m_aParams; - private $m_aPointingTo; - private $m_aReferencedBy; - - /** - * @var bool whether or not some information should be hidden to the current user. Default to false == hide information. - * @see AllowAllData() - */ - protected $m_bAllowAllData = false; - protected $m_bDataFiltered = false; - - public function ToJSON() - { - return '{}'; - } - - /** - * DBObjectSearch constructor. - * - * @api - * - * @param string $sClass - * @param string|null $sClassAlias - * - * @throws Exception - */ - public function __construct($sClass, $sClassAlias = null) - { - parent::__construct(); - - if (is_null($sClassAlias)) $sClassAlias = $sClass; - if(!is_string($sClass)) throw new Exception('DBObjectSearch::__construct called with a non-string parameter: $sClass = '.print_r($sClass, true)); - if(!MetaModel::IsValidClass($sClass)) throw new Exception('DBObjectSearch::__construct called for an invalid class: "'.$sClass.'"'); - - $this->m_aSelectedClasses = array($sClassAlias => $sClass); - $this->m_aClasses = array($sClassAlias => $sClass); - $this->m_oSearchCondition = new TrueExpression; - $this->m_aParams = array(); - $this->m_aPointingTo = array(); - $this->m_aReferencedBy = array(); - } - - public function AllowAllData($bAllowAllData = true) {$this->m_bAllowAllData = $bAllowAllData;} - public function IsAllDataAllowed() {return $this->m_bAllowAllData;} - protected function IsDataFiltered() {return $this->m_bDataFiltered; } - protected function SetDataFiltered() {$this->m_bDataFiltered = true;} - - // Create a search definition that leads to 0 result, still a valid search object - static public function FromEmptySet($sClass) - { - $oResultFilter = new DBObjectSearch($sClass); - $oResultFilter->m_oSearchCondition = new FalseExpression; - return $oResultFilter; - } - - - public function GetJoinedClasses() {return $this->m_aClasses;} - - public function GetClassName($sAlias) - { - if (array_key_exists($sAlias, $this->m_aSelectedClasses)) - { - return $this->m_aSelectedClasses[$sAlias]; - } - else - { - throw new CoreException("Invalid class alias '$sAlias'"); - } - } - - public function GetClass() - { - return reset($this->m_aSelectedClasses); - } - public function GetClassAlias() - { - reset($this->m_aSelectedClasses); - return key($this->m_aSelectedClasses); - } - - public function GetFirstJoinedClass() - { - return reset($this->m_aClasses); - } - public function GetFirstJoinedClassAlias() - { - reset($this->m_aClasses); - return key($this->m_aClasses); - } - - /** - * Change the class (only subclasses are supported as of now, because the conditions must fit the new class) - * Defaults to the first selected class (most of the time it is also the first joined class - * - * @param $sNewClass - * @param null $sAlias - * - * @throws \CoreException - */ - public function ChangeClass($sNewClass, $sAlias = null) - { - if (is_null($sAlias)) - { - $sAlias = $this->GetClassAlias(); - } - else - { - if (!array_key_exists($sAlias, $this->m_aSelectedClasses)) - { - // discard silently - necessary when recursing on the related nodes (see code below) - return; - } - } - $sCurrClass = $this->GetClassName($sAlias); - if ($sNewClass == $sCurrClass) - { - // Skip silently - return; - } - if (!MetaModel::IsParentClass($sCurrClass, $sNewClass)) - { - throw new Exception("Could not change the search class from '$sCurrClass' to '$sNewClass'. Only child classes are permitted."); - } - - // Change for this node - // - $this->m_aSelectedClasses[$sAlias] = $sNewClass; - $this->m_aClasses[$sAlias] = $sNewClass; - - // Change for all the related node (yes, this was necessary with some queries - strange effects otherwise) - // - foreach($this->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo) - { - foreach($aPointingTo as $iOperatorCode => $aFilter) - { - foreach($aFilter as $oExtFilter) - { - $oExtFilter->ChangeClass($sNewClass, $sAlias); - } - } - } - foreach($this->m_aReferencedBy as $sForeignClass => $aReferences) - { - foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator) - { - foreach ($aFiltersByOperator as $iOperatorCode => $aFilters) - { - foreach ($aFilters as $oForeignFilter) - { - $oForeignFilter->ChangeClass($sNewClass, $sAlias); - } - } - } - } - } - - public function GetSelectedClasses() - { - return $this->m_aSelectedClasses; - } - - /** - * @param array $aSelectedClasses array of aliases - * @throws CoreException - */ - public function SetSelectedClasses($aSelectedClasses) - { - $this->m_aSelectedClasses = array(); - foreach ($aSelectedClasses as $sAlias) - { - if (!array_key_exists($sAlias, $this->m_aClasses)) - { - throw new CoreException("SetSelectedClasses: Invalid class alias $sAlias"); - } - $this->m_aSelectedClasses[$sAlias] = $this->m_aClasses[$sAlias]; - } - } - - /** - * Change any alias of the query tree - * - * @param $sOldName - * @param $sNewName - * - * @return bool True if the alias has been found and changed - * @throws \Exception - */ - public function RenameAlias($sOldName, $sNewName) - { - $bFound = false; - if (array_key_exists($sOldName, $this->m_aClasses)) - { - $bFound = true; - } - if (array_key_exists($sNewName, $this->m_aClasses)) - { - throw new Exception("RenameAlias: alias '$sNewName' already used"); - } - - $aClasses = array(); - foreach ($this->m_aClasses as $sAlias => $sClass) - { - if ($sAlias === $sOldName) - { - $aClasses[$sNewName] = $sClass; - } - else - { - $aClasses[$sAlias] = $sClass; - } - } - $this->m_aClasses = $aClasses; - - $aSelectedClasses = array(); - foreach ($this->m_aSelectedClasses as $sAlias => $sClass) - { - if ($sAlias === $sOldName) - { - $aSelectedClasses[$sNewName] = $sClass; - } - else - { - $aSelectedClasses[$sAlias] = $sClass; - } - } - $this->m_aSelectedClasses = $aSelectedClasses; - - $this->m_oSearchCondition->RenameAlias($sOldName, $sNewName); - - foreach($this->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo) - { - foreach($aPointingTo as $iOperatorCode => $aFilter) - { - foreach($aFilter as $oExtFilter) - { - $bFound = $oExtFilter->RenameAlias($sOldName, $sNewName) || $bFound; - } - } - } - foreach($this->m_aReferencedBy as $sForeignClass => $aReferences) - { - foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator) - { - foreach ($aFiltersByOperator as $iOperatorCode => $aFilters) - { - foreach ($aFilters as $oForeignFilter) - { - $bFound = $oForeignFilter->RenameAlias($sOldName, $sNewName) || $bFound; - } - } - } - } - return $bFound; - } - - - public function RenameAliasesInNameSpace($aClassAliases, $aAliasTranslation = array()) - { - } - - public function TranslateConditions($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true) - { - } - - - public function SetModifierProperty($sPluginClass, $sProperty, $value) - { - $this->m_aModifierProperties[$sPluginClass][$sProperty] = $value; - } - - public function GetModifierProperties($sPluginClass) - { - if (array_key_exists($sPluginClass, $this->m_aModifierProperties)) - { - return $this->m_aModifierProperties[$sPluginClass]; - } - else - { - return array(); - } - } - - public function IsAny() - { - if (!$this->m_oSearchCondition->IsTrue()) return false; - if (count($this->m_aPointingTo) > 0) return false; - if (count($this->m_aReferencedBy) > 0) return false; - return true; - } - - protected function TransferConditionExpression($oFilter, $aTranslation) - { - // Prevent collisions in the parameter names by renaming them if needed - foreach($this->m_aParams as $sParam => $value) - { - if (array_key_exists($sParam, $oFilter->m_aParams) && ($value != $oFilter->m_aParams[$sParam])) - { - // Generate a new and unique name for the collinding parameter - $index = 1; - while(array_key_exists($sParam.$index, $oFilter->m_aParams)) - { - $index++; - } - $secondValue = $oFilter->m_aParams[$sParam]; - $oFilter->RenameParam($sParam, $sParam.$index); - unset($oFilter->m_aParams[$sParam]); - $oFilter->m_aParams[$sParam.$index] = $secondValue; - } - } - $oTranslated = $oFilter->GetCriteria()->Translate($aTranslation, false, false /* leave unresolved fields */); - $this->AddConditionExpression($oTranslated); - $this->m_aParams = array_merge($this->m_aParams, $oFilter->m_aParams); - } - - protected function RenameParam($sOldName, $sNewName) - { - $this->m_oSearchCondition->RenameParam($sOldName, $sNewName); - foreach($this->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo) - { - foreach($aPointingTo as $iOperatorCode => $aFilter) - { - foreach($aFilter as $oExtFilter) - { - $oExtFilter->RenameParam($sOldName, $sNewName); - } - } - } - foreach($this->m_aReferencedBy as $sForeignClass => $aReferences) - { - foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator) - { - foreach ($aFiltersByOperator as $iOperatorCode => $aFilters) - { - foreach ($aFilters as $oForeignFilter) - { - $oForeignFilter->RenameParam($sOldName, $sNewName); - } - } - } - } - } - - public function ResetCondition() - { - $this->m_oSearchCondition = new TrueExpression(); - // ? is that usefull/enough, do I need to rebuild the list after the subqueries ? - } - - public function MergeConditionExpression($oExpression) - { - $this->m_oSearchCondition = $this->m_oSearchCondition->LogOr($oExpression); - } - - public function AddConditionExpression($oExpression) - { - $this->m_oSearchCondition = $this->m_oSearchCondition->LogAnd($oExpression); - } - - public function AddNameCondition($sName) - { - $oValueExpr = new ScalarExpression($sName); - $oNameExpr = new FieldExpression('friendlyname', $this->GetClassAlias()); - $oNewCondition = new BinaryExpression($oNameExpr, '=', $oValueExpr); - $this->AddConditionExpression($oNewCondition); - } - - /** - * @param string $sFilterCode - * @param mixed $value - * @param string $sOpCode operator to use : 'IN', 'NOT IN', 'Contains',' Begins with', 'Finishes with', ... - * @param bool $bParseSearchString - * - * @throws \CoreException - * - * @see AddConditionForInOperatorUsingParam for IN/NOT IN queries with lots of params - */ - public function AddCondition($sFilterCode, $value, $sOpCode = null, $bParseSearchString = false) - { - MyHelpers::CheckKeyInArray('filter code in class: '.$this->GetClass(), $sFilterCode, MetaModel::GetClassFilterDefs($this->GetClass())); - - $oField = new FieldExpression($sFilterCode, $this->GetClassAlias()); - if (empty($sOpCode)) - { - if ($sFilterCode == 'id') - { - $sOpCode = '='; - } - else - { - $oAttDef = MetaModel::GetAttributeDef($this->GetClass(), $sFilterCode); - $oNewCondition = $oAttDef->GetSmartConditionExpression($value, $oField, $this->m_aParams); - $this->AddConditionExpression($oNewCondition); - return; - } - } - // Parse search strings if needed and if the filter code corresponds to a valid attcode - if($bParseSearchString && MetaModel::IsValidAttCode($this->GetClass(), $sFilterCode)) - { - $oAttDef = MetaModel::GetAttributeDef($this->GetClass(), $sFilterCode); - $value = $oAttDef->ParseSearchString($value); - } - - // Preserve backward compatibility - quick n'dirty way to change that API semantic - // - switch($sOpCode) - { - case 'SameDay': - case 'SameMonth': - case 'SameYear': - case 'Today': - case '>|': - case '<|': - case '=|': - throw new CoreException('Deprecated operator, please consider using OQL (SQL) expressions like "(TO_DAYS(NOW()) - TO_DAYS(x)) AS AgeDays"', array('operator' => $sOpCode)); - break; - - case 'IN': - if (!is_array($value)) $value = array($value); - if (count($value) === 0) throw new Exception('AddCondition '.$sOpCode.': Value cannot be an empty array.'); - $sListExpr = '('.implode(', ', CMDBSource::Quote($value)).')'; - $sOQLCondition = $oField->Render()." IN $sListExpr"; - break; - - case 'NOTIN': - if (!is_array($value)) $value = array($value); - if (count($value) === 0) throw new Exception('AddCondition '.$sOpCode.': Value cannot be an empty array.'); - $sListExpr = '('.implode(', ', CMDBSource::Quote($value)).')'; - $sOQLCondition = $oField->Render()." NOT IN $sListExpr"; - break; - - case 'Contains': - $this->m_aParams[$sFilterCode] = "%$value%"; - $sOperator = 'LIKE'; - break; - - case 'Begins with': - $this->m_aParams[$sFilterCode] = "$value%"; - $sOperator = 'LIKE'; - break; - - case 'Finishes with': - $this->m_aParams[$sFilterCode] = "%$value"; - $sOperator = 'LIKE'; - break; - - default: - if ($value === null) - { - switch ($sOpCode) - { - case '=': - $sOpCode = '*Expression*'; - $oExpression = new FunctionExpression('ISNULL', array($oField)); - break; - case '!=': - $sOpCode = '*Expression*'; - $oExpression = new FunctionExpression('ISNULL', array($oField)); - $oExpression = new BinaryExpression($oExpression, '=', new ScalarExpression(0)); - break; - default: - throw new Exception("AddCondition on null value: unsupported operator '$sOpCode''"); - } - } - else - { - $this->m_aParams[$sFilterCode] = $value; - $sOperator = $sOpCode; - } - } - - switch($sOpCode) - { - case '*Expression*': - $oNewCondition = $oExpression; - break; - case "IN": - case "NOTIN": - // this will parse all of the values... Can take forever if there are lots of them ! - // In this case using a parameter is far better : WHERE ... IN (:my_param) - $oNewCondition = Expression::FromOQL($sOQLCondition); - break; - - case 'MATCHES': - $oRightExpr = new ScalarExpression($value); - $oNewCondition = new MatchExpression($oField, $oRightExpr); - break; - - case 'Contains': - case 'Begins with': - case 'Finishes with': - default: - $oRightExpr = new VariableExpression($sFilterCode); - $oNewCondition = new BinaryExpression($oField, $sOperator, $oRightExpr); - } - - $this->AddConditionExpression($oNewCondition); - } - - /** - * @param string $sFilterCode attribute code to use - * @param array $aValues - * @param bool $bPositiveMatch if true will add a IN filter, else a NOT IN - * - * @throws \CoreException - * - * @since 2.5.0 N°1418 - */ - public function AddConditionForInOperatorUsingParam($sFilterCode, $aValues, $bPositiveMatch = true) - { - $oFieldExpression = new FieldExpression($sFilterCode, $this->GetClassAlias()); - - $sOperator = $bPositiveMatch ? 'IN' : 'NOT IN'; - - $sInParamName = $this->GenerateUniqueParamName(); - $oParamExpression = new VariableExpression($sInParamName); - $this->GetInternalParamsByRef()[$sInParamName] = $aValues; - - $oListExpression = new ListExpression(array($oParamExpression)); - - $oInCondition = new BinaryExpression($oFieldExpression, $sOperator, $oListExpression); - $this->AddConditionExpression($oInCondition); - } - - /** - * Specify a condition on external keys or link sets - * @param string $sAttSpec Can be either an attribute code or extkey->[sAttSpec] or linkset->[sAttSpec] and so on, recursively - * Example: infra_list->ci_id->location_id->country - * @param $value - * @return void - * @throws \CoreException - * @throws \CoreWarning - */ - public function AddConditionAdvanced($sAttSpec, $value) - { - $sClass = $this->GetClass(); - - $iPos = strpos($sAttSpec, '->'); - if ($iPos !== false) - { - $sAttCode = substr($sAttSpec, 0, $iPos); - $sSubSpec = substr($sAttSpec, $iPos + 2); - - if (!MetaModel::IsValidAttCode($sClass, $sAttCode)) - { - throw new Exception("Invalid attribute code '$sClass/$sAttCode' in condition specification '$sAttSpec'"); - } - - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - if ($oAttDef->IsLinkSet()) - { - $sTargetClass = $oAttDef->GetLinkedClass(); - $sExtKeyToMe = $oAttDef->GetExtKeyToMe(); - - $oNewFilter = new DBObjectSearch($sTargetClass); - $oNewFilter->AddConditionAdvanced($sSubSpec, $value); - - $this->AddCondition_ReferencedBy($oNewFilter, $sExtKeyToMe); - } - elseif ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) - { - $sTargetClass = $oAttDef->GetTargetClass(EXTKEY_ABSOLUTE); - - $oNewFilter = new DBObjectSearch($sTargetClass); - $oNewFilter->AddConditionAdvanced($sSubSpec, $value); - - $this->AddCondition_PointingTo($oNewFilter, $sAttCode); - } - else - { - throw new Exception("Attribute specification '$sAttSpec', '$sAttCode' should be either a link set or an external key"); - } - } - else - { - // $sAttSpec is an attribute code - // - if (is_array($value)) - { - $oField = new FieldExpression($sAttSpec, $this->GetClass()); - $oListExpr = ListExpression::FromScalars($value); - $oInValues = new BinaryExpression($oField, 'IN', $oListExpr); - - $this->AddConditionExpression($oInValues); - } - else - { - $this->AddCondition($sAttSpec, $value); - } - } - } - - public function AddCondition_FullText($sNeedle) - { - // Transform the full text condition into additional condition expression - $aFullTextFields = array(); - foreach (MetaModel::ListAttributeDefs($this->GetClass()) as $sAttCode => $oAttDef) - { - if (!$oAttDef->IsScalar()) continue; - if ($oAttDef->IsExternalKey()) continue; - $aFullTextFields[] = new FieldExpression($sAttCode, $this->GetClassAlias()); - } - $oTextFields = new CharConcatWSExpression(' ', $aFullTextFields); - - $sQueryParam = 'needle'; - $oFlexNeedle = new CharConcatExpression(array(new ScalarExpression('%'), new VariableExpression($sQueryParam), new ScalarExpression('%'))); - - $oNewCond = new BinaryExpression($oTextFields, 'LIKE', $oFlexNeedle); - $this->AddConditionExpression($oNewCond); - $this->m_aParams[$sQueryParam] = $sNeedle; - } - - protected function AddToNameSpace(&$aClassAliases, &$aAliasTranslation, $bTranslateMainAlias = true) - { - if ($bTranslateMainAlias) - { - $sOrigAlias = $this->GetFirstJoinedClassAlias(); - if (array_key_exists($sOrigAlias, $aClassAliases)) - { - $sNewAlias = MetaModel::GenerateUniqueAlias($aClassAliases, $sOrigAlias, $this->GetFirstJoinedClass()); - if (isset($this->m_aSelectedClasses[$sOrigAlias])) - { - $this->m_aSelectedClasses[$sNewAlias] = $this->GetFirstJoinedClass(); - unset($this->m_aSelectedClasses[$sOrigAlias]); - } - - // TEMPORARY ALGORITHM (m_aClasses is not correctly updated, it is not possible to add a subtree onto a subnode) - // Replace the element at the same position (unset + set is not enough because the hash array is ordered) - $aPrevList = $this->m_aClasses; - $this->m_aClasses = array(); - foreach ($aPrevList as $sSomeAlias => $sSomeClass) - { - if ($sSomeAlias == $sOrigAlias) - { - $this->m_aClasses[$sNewAlias] = $sSomeClass; // note: GetFirstJoinedClass now returns '' !!! - } - else - { - $this->m_aClasses[$sSomeAlias] = $sSomeClass; - } - } - - // Translate the condition expression with the new alias - $aAliasTranslation[$sOrigAlias]['*'] = $sNewAlias; - } - - // add the alias into the filter aliases list - $aClassAliases[$this->GetFirstJoinedClassAlias()] = $this->GetFirstJoinedClass(); - } - - foreach($this->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo) - { - foreach($aPointingTo as $iOperatorCode => $aFilter) - { - foreach($aFilter as $oFilter) - { - $oFilter->AddToNameSpace($aClassAliases, $aAliasTranslation); - } - } - } - - foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences) - { - foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator) - { - foreach ($aFiltersByOperator as $iOperatorCode => $aFilters) - { - foreach ($aFilters as $oForeignFilter) - { - $oForeignFilter->AddToNameSpace($aClassAliases, $aAliasTranslation); - } - } - } - } - } - - - // Browse the tree nodes recursively - // - protected function GetNode($sAlias) - { - if ($this->GetFirstJoinedClassAlias() == $sAlias) - { - return $this; - } - else - { - foreach($this->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo) - { - foreach($aPointingTo as $iOperatorCode => $aFilter) - { - foreach($aFilter as $oFilter) - { - $ret = $oFilter->GetNode($sAlias); - if (is_object($ret)) - { - return $ret; - } - } - } - } - foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences) - { - foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator) - { - foreach ($aFiltersByOperator as $iOperatorCode => $aFilters) - { - foreach ($aFilters as $oForeignFilter) - { - $ret = $oForeignFilter->GetNode($sAlias); - if (is_object($ret)) - { - return $ret; - } - } - } - } - } - } - // Not found - return null; - } - - /** - * Helper to - * - convert a translation table (format optimized for the translation in an expression tree) into simple hash - * - compile over an eventually existing map - * - * @param array $aRealiasingMap Map to update - * @param array $aAliasTranslation Translation table resulting from calls to MergeWith_InNamespace - * @return void of => - */ - protected function UpdateRealiasingMap(&$aRealiasingMap, $aAliasTranslation) - { - if ($aRealiasingMap !== null) - { - foreach ($aAliasTranslation as $sPrevAlias => $aRules) - { - if (isset($aRules['*'])) - { - $sNewAlias = $aRules['*']; - $sOriginalAlias = array_search($sPrevAlias, $aRealiasingMap); - if ($sOriginalAlias !== false) - { - $aRealiasingMap[$sOriginalAlias] = $sNewAlias; - } - else - { - $aRealiasingMap[$sPrevAlias] = $sNewAlias; - } - } - } - } - } - - /** - * Completes the list of alias=>class by browsing the whole structure recursively - * This a workaround to handle some cases in which the list of classes is not correctly updated. - * This code should disappear as soon as DBObjectSearch get split between a container search class and a Node class - * - * @param array $aClasses List to be completed - */ - protected function RecomputeClassList(&$aClasses) - { - $aClasses[$this->GetFirstJoinedClassAlias()] = $this->GetFirstJoinedClass(); - - // Recurse in the query tree - foreach($this->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo) - { - foreach($aPointingTo as $iOperatorCode => $aFilter) - { - foreach($aFilter as $oFilter) - { - $oFilter->RecomputeClassList($aClasses); - } - } - } - - foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences) - { - foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator) - { - foreach ($aFiltersByOperator as $iOperatorCode => $aFilters) - { - foreach ($aFilters as $oForeignFilter) - { - $oForeignFilter->RecomputeClassList($aClasses); - } - } - } - } - } - - /** - * @param DBObjectSearch $oFilter - * @param $sExtKeyAttCode - * @param int $iOperatorCode - * @param null $aRealiasingMap array of => , for each alias that has changed - * @throws CoreException - * @throws CoreWarning - */ - public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null) - { - if (!MetaModel::IsValidKeyAttCode($this->GetClass(), $sExtKeyAttCode)) - { - throw new CoreWarning("The attribute code '$sExtKeyAttCode' is not an external key of the class '{$this->GetClass()}'"); - } - $oAttExtKey = MetaModel::GetAttributeDef($this->GetClass(), $sExtKeyAttCode); - if(!MetaModel::IsSameFamilyBranch($oFilter->GetClass(), $oAttExtKey->GetTargetClass())) - { - throw new CoreException("The specified filter (pointing to {$oFilter->GetClass()}) is not compatible with the key '{$this->GetClass()}::$sExtKeyAttCode', which is pointing to {$oAttExtKey->GetTargetClass()}"); - } - if(($iOperatorCode != TREE_OPERATOR_EQUALS) && !($oAttExtKey instanceof AttributeHierarchicalKey)) - { - throw new CoreException("The specified tree operator $iOperatorCode is not applicable to the key '{$this->GetClass()}::$sExtKeyAttCode', which is not a HierarchicalKey"); - } - // Note: though it seems to be a good practice to clone the given source filter - // (as it was done and fixed an issue in Intersect()) - // this was not implemented here because it was causing a regression (login as admin, select an org, click on any badge) - // root cause: FromOQL relies on the fact that the passed filter can be modified later - // NO: $oFilter = $oFilter->DeepClone(); - // See also: Trac #639, and self::AddCondition_ReferencedBy() - $aAliasTranslation = array(); - $res = $this->AddCondition_PointingTo_InNameSpace($oFilter, $sExtKeyAttCode, $this->m_aClasses, $aAliasTranslation, $iOperatorCode); - $this->TransferConditionExpression($oFilter, $aAliasTranslation); - $this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation); - - if (ENABLE_OPT && ($oFilter->GetClass() == $oFilter->GetFirstJoinedClass())) - { - if (isset($oFilter->m_aReferencedBy[$this->GetClass()][$sExtKeyAttCode][$iOperatorCode])) - { - foreach ($oFilter->m_aReferencedBy[$this->GetClass()][$sExtKeyAttCode][$iOperatorCode] as $oRemoteFilter) - { - if ($this->GetClass() == $oRemoteFilter->GetClass()) - { - // Optimization - fold sibling query - $aAliasTranslation = array(); - $this->MergeWith_InNamespace($oRemoteFilter, $this->m_aClasses, $aAliasTranslation); - unset($oFilter->m_aReferencedBy[$this->GetClass()][$sExtKeyAttCode][$iOperatorCode]); - $this->m_oSearchCondition = $this->m_oSearchCondition->Translate($aAliasTranslation, false, false); - $this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation); - break; - } - } - } - } - $this->RecomputeClassList($this->m_aClasses); - return $res; - } - - protected function AddCondition_PointingTo_InNameSpace(DBObjectSearch $oFilter, $sExtKeyAttCode, &$aClassAliases, &$aAliasTranslation, $iOperatorCode) - { - // Find the node on which the new tree must be attached (most of the time it is "this") - $oReceivingFilter = $this->GetNode($this->GetClassAlias()); - - $bMerged = false; - if (ENABLE_OPT && isset($oReceivingFilter->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode])) - { - foreach ($oReceivingFilter->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode] as $oExisting) - { - if ($oExisting->GetClass() == $oFilter->GetClass()) - { - $oExisting->MergeWith_InNamespace($oFilter, $oExisting->m_aClasses, $aAliasTranslation); - $bMerged = true; - break; - } - } - } - if (!$bMerged) - { - $oFilter->AddToNamespace($aClassAliases, $aAliasTranslation); - $oReceivingFilter->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode][] = $oFilter; - } - } - - /** - * @param DBObjectSearch $oFilter - * @param $sForeignExtKeyAttCode - * @param int $iOperatorCode - * @param null $aRealiasingMap array of => , for each alias that has changed - * @return void - * @throws \CoreException - */ - public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null) - { - $sForeignClass = $oFilter->GetClass(); - if (!MetaModel::IsValidKeyAttCode($sForeignClass, $sForeignExtKeyAttCode)) - { - throw new CoreException("The attribute code '$sForeignExtKeyAttCode' is not an external key of the class '{$sForeignClass}'"); - } - $oAttExtKey = MetaModel::GetAttributeDef($sForeignClass, $sForeignExtKeyAttCode); - if(!MetaModel::IsSameFamilyBranch($this->GetClass(), $oAttExtKey->GetTargetClass())) - { - // à refaire en spécifique dans FromOQL - throw new CoreException("The specified filter (objects referencing an object of class {$this->GetClass()}) is not compatible with the key '{$sForeignClass}::$sForeignExtKeyAttCode', which is pointing to {$oAttExtKey->GetTargetClass()}"); - } - - // Note: though it seems to be a good practice to clone the given source filter - // (as it was done and fixed an issue in Intersect()) - // this was not implemented here because it was causing a regression (login as admin, select an org, click on any badge) - // root cause: FromOQL relies on the fact that the passed filter can be modified later - // NO: $oFilter = $oFilter->DeepClone(); - // See also: Trac #639, and self::AddCondition_PointingTo() - $aAliasTranslation = array(); - $this->AddCondition_ReferencedBy_InNameSpace($oFilter, $sForeignExtKeyAttCode, $this->m_aClasses, $aAliasTranslation, $iOperatorCode); - $this->TransferConditionExpression($oFilter, $aAliasTranslation); - $this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation); - - if (ENABLE_OPT && ($oFilter->GetClass() == $oFilter->GetFirstJoinedClass())) - { - if (isset($oFilter->m_aPointingTo[$sForeignExtKeyAttCode][$iOperatorCode])) - { - foreach ($oFilter->m_aPointingTo[$sForeignExtKeyAttCode][$iOperatorCode] as $oRemoteFilter) - { - if ($this->GetClass() == $oRemoteFilter->GetClass()) - { - // Optimization - fold sibling query - $aAliasTranslation = array(); - $this->MergeWith_InNamespace($oRemoteFilter, $this->m_aClasses, $aAliasTranslation); - unset($oFilter->m_aPointingTo[$sForeignExtKeyAttCode][$iOperatorCode]); - $this->m_oSearchCondition = $this->m_oSearchCondition->Translate($aAliasTranslation, false, false); - $this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation); - break; - } - } - } - } - $this->RecomputeClassList($this->m_aClasses); - } - - protected function AddCondition_ReferencedBy_InNameSpace(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, &$aClassAliases, &$aAliasTranslation, $iOperatorCode) - { - $sForeignClass = $oFilter->GetClass(); - - // Find the node on which the new tree must be attached (most of the time it is "this") - $oReceivingFilter = $this->GetNode($this->GetClassAlias()); - - $bMerged = false; - if (ENABLE_OPT && isset($oReceivingFilter->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode])) - { - foreach ($oReceivingFilter->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode] as $oExisting) - { - if ($oExisting->GetClass() == $oFilter->GetClass()) - { - $oExisting->MergeWith_InNamespace($oFilter, $oExisting->m_aClasses, $aAliasTranslation); - $bMerged = true; - break; - } - } - } - if (!$bMerged) - { - $oFilter->AddToNamespace($aClassAliases, $aAliasTranslation); - $oReceivingFilter->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode][] = $oFilter; - } - } - - /** - * Filter this search with another search. - * Initial search is unmodified. - * The difference with Intersect, is that an alias can be provided, - * the filtered class does not need to be the first joined class. - * - * @param string $sClassAlias class being filtered - * @param DBSearch $oFilter Filter to apply - * - * @return DBSearch The filtered search - * @throws \CoreException - */ - public function Filter($sClassAlias, DBSearch $oFilter) - { - // If the conditions are the correct ones for Intersect - if (($this->GetFirstJoinedClassAlias() == $sClassAlias)) - { - return $this->Intersect($oFilter); - } - - /** @var \DBObjectSearch $oFilteredSearch */ - $oFilteredSearch = $this->DeepClone(); - $oFilterExpression = self::FilterSubClass($oFilteredSearch, $sClassAlias, $oFilter, $this->m_aClasses); - if ($oFilterExpression === false) - { - throw new CoreException("Limitation: cannot filter search"); - } - - $oFilteredSearch->AddConditionExpression($oFilterExpression); - - return $oFilteredSearch; - } - - /** - * Filter "in place" the search (filtered part is replaced in the initial search) - * - * @param DBObjectSearch $oSearch Search to filter, modified with the given filter - * @param string $sClassAlias class to filter - * @param \DBSearch $oFilter Filter to apply - * - * @return \Expression|false - * @throws \CoreException - */ - private static function FilterSubClass(DBObjectSearch &$oSearch, $sClassAlias, DBSearch $oFilter, $aRootClasses) - { - if (($oSearch->GetFirstJoinedClassAlias() == $sClassAlias)) - { - $oSearch->ResetCondition(); - $oSearch = $oSearch->IntersectSubClass($oFilter, $aRootClasses); - return $oSearch->GetCriteria(); - } - - /** @var Expression $oFilterExpression */ - // Search in the filter tree where is the correct DBSearch - foreach ($oSearch->m_aPointingTo as $sExtKey => $aPointingTo) - { - foreach ($aPointingTo as $iOperatorCode => $aFilters) - { - foreach ($aFilters as $index => $oExtFilter) - { - $oFilterExpression = self::FilterSubClass($oExtFilter, $sClassAlias, $oFilter, $aRootClasses); - if ($oFilterExpression !== false) - { - $oSearch->m_aPointingTo[$sExtKey][$iOperatorCode][$index] = $oExtFilter; - return $oFilterExpression; - } - } - } - } - - foreach($oSearch->m_aReferencedBy as $sForeignClass => $aReferences) - { - foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator) - { - foreach ($aFiltersByOperator as $iOperatorCode => $aFilters) - { - foreach ($aFilters as $index => $oForeignFilter) - { - $oFilterExpression = self::FilterSubClass($oForeignFilter, $sClassAlias, $oFilter, $aRootClasses); - if ($oFilterExpression !== false) - { - $oSearch->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode][$iOperatorCode][$index] = $oForeignFilter; - return $oFilterExpression; - } - } - } - } - } - - return false; - } - - /** - * @inheritDoc - * @throws \CoreException - */ - public function Intersect(DBSearch $oFilter) - { - return $this->IntersectSubClass($oFilter, $this->m_aClasses); - } - - /** - * @param \DBSearch $oFilter - * @param array $aRootClasses classes of the root search (for aliases) - * - * @return \DBUnionSearch|mixed - * @throws \CoreException - */ - protected function IntersectSubClass(DBSearch $oFilter, $aRootClasses) - { - if ($oFilter instanceof DBUnionSearch) - { - // Develop! - $aFilters = $oFilter->GetSearches(); - } - else - { - $aFilters = array($oFilter); - } - - $aSearches = array(); - foreach ($aFilters as $oRightFilter) - { - // Limitation: the queried class must be the first declared class - if ($oRightFilter->GetFirstJoinedClassAlias() != $oRightFilter->GetClassAlias()) - { - throw new CoreException("Limitation: cannot merge two queries if the queried class ({$oRightFilter->GetClass()} AS {$oRightFilter->GetClassAlias()}) is not the first joined class ({$oRightFilter->GetFirstJoinedClass()} AS {$oRightFilter->GetFirstJoinedClassAlias()})"); - } - - /** @var \DBObjectSearch $oLeftFilter */ - $oLeftFilter = $this->DeepClone(); - $oRightFilter = $oRightFilter->DeepClone(); - - $bAllowAllData = ($oLeftFilter->IsAllDataAllowed() && $oRightFilter->IsAllDataAllowed()); - if ($bAllowAllData) - { - $oLeftFilter->AllowAllData(); - } - - if ($oLeftFilter->GetFirstJoinedClass() != $oRightFilter->GetClass()) - { - if (MetaModel::IsParentClass($oLeftFilter->GetFirstJoinedClass(), $oRightFilter->GetClass())) - { - // Specialize $oLeftFilter - $oLeftFilter->ChangeClass($oRightFilter->GetClass(), $oLeftFilter->GetFirstJoinedClassAlias()); - } - elseif (MetaModel::IsParentClass($oRightFilter->GetFirstJoinedClass(), $oLeftFilter->GetClass())) - { - // Specialize $oRightFilter - $oRightFilter->ChangeClass($oLeftFilter->GetClass()); - } - else - { - throw new CoreException("Attempting to merge a filter of class '{$oLeftFilter->GetClass()}' with a filter of class '{$oRightFilter->GetClass()}'"); - } - } - - $aAliasTranslation = array(); - $oLeftFilter->MergeWith_InNamespace($oRightFilter, $aRootClasses, $aAliasTranslation); - $oLeftFilter->TransferConditionExpression($oRightFilter, $aAliasTranslation); - $aSearches[] = $oLeftFilter; - } - if (count($aSearches) == 1) - { - // return a DBObjectSearch - return $aSearches[0]; - } - else - { - return new DBUnionSearch($aSearches); - } - } - - protected function MergeWith_InNamespace($oFilter, &$aClassAliases, &$aAliasTranslation) - { - if ($this->GetClass() != $oFilter->GetClass()) - { - throw new CoreException("Attempting to merge a filter of class '{$this->GetClass()}' with a filter of class '{$oFilter->GetClass()}'"); - } - - // Translate search condition into our aliasing scheme - $aAliasTranslation[$oFilter->GetClassAlias()]['*'] = $this->GetClassAlias(); - - foreach($oFilter->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo) - { - foreach($aPointingTo as $iOperatorCode => $aFilter) - { - foreach($aFilter as $oExtFilter) - { - $this->AddCondition_PointingTo_InNamespace($oExtFilter, $sExtKeyAttCode, $aClassAliases, $aAliasTranslation, $iOperatorCode); - } - } - } - foreach($oFilter->m_aReferencedBy as $sForeignClass => $aReferences) - { - foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator) - { - foreach ($aFiltersByOperator as $iOperatorCode => $aFilters) - { - foreach ($aFilters as $oForeignFilter) - { - $this->AddCondition_ReferencedBy_InNamespace($oForeignFilter, $sForeignExtKeyAttCode, $aClassAliases, $aAliasTranslation, $iOperatorCode); - } - } - } - } - } - - public function GetCriteria() {return $this->m_oSearchCondition;} - public function GetCriteria_FullText() {throw new Exception("Removed GetCriteria_FullText");} - public function GetCriteria_PointingTo($sKeyAttCode = "") - { - if (empty($sKeyAttCode)) - { - return $this->m_aPointingTo; - } - if (!array_key_exists($sKeyAttCode, $this->m_aPointingTo)) return array(); - return $this->m_aPointingTo[$sKeyAttCode]; - } - protected function GetCriteria_ReferencedBy() - { - return $this->m_aReferencedBy; - } - - public function SetInternalParams($aParams) - { - return $this->m_aParams = $aParams; - } - - /** - * @return array warning : array returned by value - * @see self::GetInternalParamsByRef to get the attribute by reference - */ - public function GetInternalParams() - { - return $this->m_aParams; - } - - /** - * @return array - * @see http://php.net/manual/en/language.references.return.php - * @since 2.5.1 N°1582 - */ - public function &GetInternalParamsByRef() - { - return $this->m_aParams; - } - - /** - * @param string $sKey - * @param mixed $value - * @param bool $bDoNotOverride - * - * @throws \CoreUnexpectedValue if $bDoNotOverride and $sKey already exists - */ - public function AddInternalParam($sKey, $value, $bDoNotOverride = false) - { - if ($bDoNotOverride) - { - if (array_key_exists($sKey, $this->m_aParams)) - { - throw new CoreUnexpectedValue("The key $sKey already exists with value : ".$this->m_aParams[$sKey]); - } - } - - $this->m_aParams[$sKey] = $value; - } - - public function GetQueryParams($bExcludeMagicParams = true) - { - $aParams = array(); - $this->m_oSearchCondition->Render($aParams, true); - - if ($bExcludeMagicParams) - { - $aRet = array(); - - // Make the list of acceptable arguments... could be factorized with run_query, into oSearch->GetQueryParams($bExclude magic params) - $aNakedMagicArguments = array(); - foreach (MetaModel::PrepareQueryArguments(array()) as $sArgName => $value) - { - $iPos = strpos($sArgName, '->object()'); - if ($iPos === false) - { - $aNakedMagicArguments[$sArgName] = $value; - } - else - { - $aNakedMagicArguments[substr($sArgName, 0, $iPos)] = true; - } - } - foreach ($aParams as $sParam => $foo) - { - $iPos = strpos($sParam, '->'); - if ($iPos === false) - { - $sRefName = $sParam; - } - else - { - $sRefName = substr($sParam, 0, $iPos); - } - if (!array_key_exists($sRefName, $aNakedMagicArguments)) - { - $aRet[$sParam] = $foo; - } - } - } - - return $aRet; - } - - public function ListConstantFields() - { - return $this->m_oSearchCondition->ListConstantFields(); - } - - /** - * Turn the parameters (:xxx) into scalar values in order to easily - * serialize a search - * @param $aArgs -*/ - public function ApplyParameters($aArgs) - { - $this->m_oSearchCondition->ApplyParameters(array_merge($this->m_aParams, $aArgs)); - } - - public function ToOQL($bDevelopParams = false, $aContextParams = null, $bWithAllowAllFlag = false) - { - // Currently unused, but could be useful later - $bRetrofitParams = false; - - if ($bDevelopParams) - { - if (is_null($aContextParams)) - { - $aParams = array_merge($this->m_aParams); - } - else - { - $aParams = array_merge($aContextParams, $this->m_aParams); - } - $aParams = MetaModel::PrepareQueryArguments($aParams); - } - else - { - // Leave it as is, the rendering will be made with parameters in clear - $aParams = null; - } - - $aSelectedAliases = array(); - foreach ($this->m_aSelectedClasses as $sAlias => $sClass) - { - $aSelectedAliases[] = '`' . $sAlias . '`'; - } - $sSelectedClasses = implode(', ', $aSelectedAliases); - $sRes = 'SELECT '.$sSelectedClasses.' FROM'; - - $sRes .= ' ' . $this->GetFirstJoinedClass() . ' AS `' . $this->GetFirstJoinedClassAlias() . '`'; - $sRes .= $this->ToOQL_Joins(); - $sRes .= " WHERE ".$this->m_oSearchCondition->Render($aParams, $bRetrofitParams); - - if ($bWithAllowAllFlag && $this->m_bAllowAllData) - { - $sRes .= " ALLOW ALL DATA"; - } - return $sRes; - } - - protected function OperatorCodeToOQL($iOperatorCode) - { - switch($iOperatorCode) - { - case TREE_OPERATOR_EQUALS: - $sOperator = ' = '; - break; - - case TREE_OPERATOR_BELOW: - $sOperator = ' BELOW '; - break; - - case TREE_OPERATOR_BELOW_STRICT: - $sOperator = ' BELOW STRICT '; - break; - - case TREE_OPERATOR_NOT_BELOW: - $sOperator = ' NOT BELOW '; - break; - - case TREE_OPERATOR_NOT_BELOW_STRICT: - $sOperator = ' NOT BELOW STRICT '; - break; - - case TREE_OPERATOR_ABOVE: - $sOperator = ' ABOVE '; - break; - - case TREE_OPERATOR_ABOVE_STRICT: - $sOperator = ' ABOVE STRICT '; - break; - - case TREE_OPERATOR_NOT_ABOVE: - $sOperator = ' NOT ABOVE '; - break; - - case TREE_OPERATOR_NOT_ABOVE_STRICT: - $sOperator = ' NOT ABOVE STRICT '; - break; - - } - return $sOperator; - } - - protected function ToOQL_Joins() - { - $sRes = ''; - foreach($this->m_aPointingTo as $sExtKey => $aPointingTo) - { - foreach($aPointingTo as $iOperatorCode => $aFilter) - { - $sOperator = $this->OperatorCodeToOQL($iOperatorCode); - foreach($aFilter as $oFilter) - { - $sRes .= ' JOIN ' . $oFilter->GetFirstJoinedClass() . ' AS `' . $oFilter->GetFirstJoinedClassAlias() . '` ON `' . $this->GetFirstJoinedClassAlias() . '`.' . $sExtKey . $sOperator . '`' . $oFilter->GetFirstJoinedClassAlias() . '`.id'; - $sRes .= $oFilter->ToOQL_Joins(); - } - } - } - foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences) - { - foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator) - { - foreach ($aFiltersByOperator as $iOperatorCode => $aFilters) - { - $sOperator = $this->OperatorCodeToOQL($iOperatorCode); - foreach ($aFilters as $oForeignFilter) - { - $sRes .= ' JOIN ' . $oForeignFilter->GetFirstJoinedClass() . ' AS `' . $oForeignFilter->GetFirstJoinedClassAlias() . '` ON `' . $oForeignFilter->GetFirstJoinedClassAlias() . '`.' . $sForeignExtKeyAttCode . $sOperator . '`' . $this->GetFirstJoinedClassAlias() . '`.id'; - $sRes .= $oForeignFilter->ToOQL_Joins(); - } - } - } - } - return $sRes; - } - - protected function OQLExpressionToCondition($sQuery, $oExpression, $aClassAliases) - { - if ($oExpression instanceof BinaryOqlExpression) - { - $sOperator = $oExpression->GetOperator(); - $oLeft = $this->OQLExpressionToCondition($sQuery, $oExpression->GetLeftExpr(), $aClassAliases); - $oRight = $this->OQLExpressionToCondition($sQuery, $oExpression->GetRightExpr(), $aClassAliases); - return new BinaryExpression($oLeft, $sOperator, $oRight); - } - elseif ($oExpression instanceof MatchOqlExpression) - { - $oLeft = $this->OQLExpressionToCondition($sQuery, $oExpression->GetLeftExpr(), $aClassAliases); - $oRight = $this->OQLExpressionToCondition($sQuery, $oExpression->GetRightExpr(), $aClassAliases); - - return new MatchExpression($oLeft, $oRight); - } - elseif ($oExpression instanceof FieldOqlExpression) - { - $sClassAlias = $oExpression->GetParent(); - $sFltCode = $oExpression->GetName(); - if (empty($sClassAlias)) - { - // Need to find the right alias - // Build an array of field => array of aliases - $aFieldClasses = array(); - foreach($aClassAliases as $sAlias => $sReal) - { - foreach(MetaModel::GetFiltersList($sReal) as $sAnFltCode) - { - $aFieldClasses[$sAnFltCode][] = $sAlias; - } - } - $sClassAlias = $aFieldClasses[$sFltCode][0]; - } - return new FieldExpression($sFltCode, $sClassAlias); - } - elseif ($oExpression instanceof VariableOqlExpression) - { - return new VariableExpression($oExpression->GetName()); - } - elseif ($oExpression instanceof TrueOqlExpression) - { - return new TrueExpression; - } - elseif ($oExpression instanceof ScalarOqlExpression) - { - return new ScalarExpression($oExpression->GetValue()); - } - elseif ($oExpression instanceof ListOqlExpression) - { - $aItems = array(); - foreach ($oExpression->GetItems() as $oItemExpression) - { - $aItems[] = $this->OQLExpressionToCondition($sQuery, $oItemExpression, $aClassAliases); - } - return new ListExpression($aItems); - } - elseif ($oExpression instanceof FunctionOqlExpression) - { - $aArgs = array(); - foreach ($oExpression->GetArgs() as $oArgExpression) - { - $aArgs[] = $this->OQLExpressionToCondition($sQuery, $oArgExpression, $aClassAliases); - } - return new FunctionExpression($oExpression->GetVerb(), $aArgs); - } - elseif ($oExpression instanceof IntervalOqlExpression) - { - return new IntervalExpression($oExpression->GetValue(), $oExpression->GetUnit()); - } - else - { - throw new CoreException('Unknown expression type', array('class'=>get_class($oExpression), 'query'=>$sQuery)); - } - } - - public function InitFromOqlQuery(OqlQuery $oOqlQuery, $sQuery) - { - $oModelReflection = new ModelReflectionRuntime(); - $sClass = $oOqlQuery->GetClass($oModelReflection); - $sClassAlias = $oOqlQuery->GetClassAlias(); - - $aAliases = array($sClassAlias => $sClass); - - // Note: the condition must be built here, it may be altered later on when optimizing some joins - $oConditionTree = $oOqlQuery->GetCondition(); - if ($oConditionTree instanceof Expression) - { - $aRawAliases = array($sClassAlias => $sClass); - $aJoinSpecs = $oOqlQuery->GetJoins(); - if (is_array($aJoinSpecs)) - { - foreach ($aJoinSpecs as $oJoinSpec) - { - $aRawAliases[$oJoinSpec->GetClassAlias()] = $oJoinSpec->GetClass(); - } - } - $this->m_oSearchCondition = $this->OQLExpressionToCondition($sQuery, $oConditionTree, $aRawAliases); - } - - // Maintain an array of filters, because the flat list is in fact referring to a tree - // And this will be an easy way to dispatch the conditions - // $this will be referenced by the other filters, or the other way around... - $aJoinItems = array($sClassAlias => $this); - - $aJoinSpecs = $oOqlQuery->GetJoins(); - if (is_array($aJoinSpecs)) - { - $aAliasTranslation = array(); - foreach ($aJoinSpecs as $oJoinSpec) - { - $sJoinClass = $oJoinSpec->GetClass(); - $sJoinClassAlias = $oJoinSpec->GetClassAlias(); - if (isset($aAliasTranslation[$sJoinClassAlias]['*'])) - { - $sJoinClassAlias = $aAliasTranslation[$sJoinClassAlias]['*']; - } - - // Assumption: ext key on the left only !!! - // normalization should take care of this - $oLeftField = $oJoinSpec->GetLeftField(); - $sFromClass = $oLeftField->GetParent(); - if (isset($aAliasTranslation[$sFromClass]['*'])) - { - $sFromClass = $aAliasTranslation[$sFromClass]['*']; - } - $sExtKeyAttCode = $oLeftField->GetName(); - - $oRightField = $oJoinSpec->GetRightField(); - $sToClass = $oRightField->GetParent(); - if (isset($aAliasTranslation[$sToClass]['*'])) - { - $sToClass = $aAliasTranslation[$sToClass]['*']; - } - - $aAliases[$sJoinClassAlias] = $sJoinClass; - $aJoinItems[$sJoinClassAlias] = new DBObjectSearch($sJoinClass, $sJoinClassAlias); - - $sOperator = $oJoinSpec->GetOperator(); - switch($sOperator) - { - case '=': - default: - $iOperatorCode = TREE_OPERATOR_EQUALS; - break; - case 'BELOW': - $iOperatorCode = TREE_OPERATOR_BELOW; - break; - case 'BELOW_STRICT': - $iOperatorCode = TREE_OPERATOR_BELOW_STRICT; - break; - case 'NOT_BELOW': - $iOperatorCode = TREE_OPERATOR_NOT_BELOW; - break; - case 'NOT_BELOW_STRICT': - $iOperatorCode = TREE_OPERATOR_NOT_BELOW_STRICT; - break; - case 'ABOVE': - $iOperatorCode = TREE_OPERATOR_ABOVE; - break; - case 'ABOVE_STRICT': - $iOperatorCode = TREE_OPERATOR_ABOVE_STRICT; - break; - case 'NOT_ABOVE': - $iOperatorCode = TREE_OPERATOR_NOT_ABOVE; - break; - case 'NOT_ABOVE_STRICT': - $iOperatorCode = TREE_OPERATOR_NOT_ABOVE_STRICT; - break; - } - - if ($sFromClass == $sJoinClassAlias) - { - $oReceiver = $aJoinItems[$sToClass]; - $oNewComer = $aJoinItems[$sFromClass]; - $oReceiver->AddCondition_ReferencedBy_InNameSpace($oNewComer, $sExtKeyAttCode, $oReceiver->m_aClasses, $aAliasTranslation, $iOperatorCode); - } - else - { - $oReceiver = $aJoinItems[$sFromClass]; - $oNewComer = $aJoinItems[$sToClass]; - $oReceiver->AddCondition_PointingTo_InNameSpace($oNewComer, $sExtKeyAttCode, $oReceiver->m_aClasses, $aAliasTranslation, $iOperatorCode); - } - } - $this->m_oSearchCondition = $this->m_oSearchCondition->Translate($aAliasTranslation, false, false /* leave unresolved fields */); - } - - // Check and prepare the select information - $this->m_aSelectedClasses = array(); - foreach ($oOqlQuery->GetSelectedClasses() as $oClassDetails) - { - $sClassToSelect = $oClassDetails->GetValue(); - $this->m_aSelectedClasses[$sClassToSelect] = $aAliases[$sClassToSelect]; - } - $this->m_aClasses = $aAliases; - } - - //////////////////////////////////////////////////////////////////////////// - // - // Construction of the SQL queries - // - //////////////////////////////////////////////////////////////////////////// - - public function MakeDeleteQuery($aArgs = array()) - { - $aModifierProperties = MetaModel::MakeModifierProperties($this); - $oBuild = new QueryBuilderContext($this, $aModifierProperties); - $oSQLQuery = $this->MakeSQLObjectQuery($oBuild, array($this->GetClassAlias() => array()), array()); - $oSQLQuery->SetCondition($oBuild->m_oQBExpressions->GetCondition()); - $oSQLQuery->SetSelect($oBuild->m_oQBExpressions->GetSelect()); - $oSQLQuery->OptimizeJoins(array()); - $aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams()); - $sRet = $oSQLQuery->RenderDelete($aScalarArgs); - return $sRet; - } - - public function MakeUpdateQuery($aValues, $aArgs = array()) - { - // $aValues is an array of $sAttCode => $value - $aModifierProperties = MetaModel::MakeModifierProperties($this); - $oBuild = new QueryBuilderContext($this, $aModifierProperties); - $aRequested = array(); // Requested attributes are the updated attributes - foreach ($aValues as $sAttCode => $value) - { - $aRequested[$sAttCode] = MetaModel::GetAttributeDef($this->GetClass(), $sAttCode); - } - $oSQLQuery = $this->MakeSQLObjectQuery($oBuild, array($this->GetClassAlias() => $aRequested), $aValues); - $oSQLQuery->SetCondition($oBuild->m_oQBExpressions->GetCondition()); - $oSQLQuery->SetSelect($oBuild->m_oQBExpressions->GetSelect()); - $oSQLQuery->OptimizeJoins(array()); - $aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams()); - $sRet = $oSQLQuery->RenderUpdate($aScalarArgs); - return $sRet; - } - - /** - * Generate an INSERT statement. - * Note : unlike `RenderUpdate` and `RenderSelect`, it is limited to one and only one table. - * - * @param array $aValues is an array of $sAttCode => $value - * @param array $aArgs - * - * @return string - * @throws \CoreException - */ - public function MakeInsertQuery($aValues, $aArgs = array()) - { - $oSQLObjectQueryBuilder = new SQLObjectQueryBuilder($this); - $oSQLQuery = $oSQLObjectQueryBuilder->MakeSQLObjectUpdateQuery($aValues); - $aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams()); - $sRet = $oSQLQuery->RenderInsert($aScalarArgs); - return $sRet; - } - - public function GetSQLQueryStructure($aAttToLoad, $bGetCount, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null) - { - // Hide objects that are not visible to the current user - // - $oSearch = $this; - if (!$this->IsAllDataAllowed() && !$this->IsDataFiltered()) - { - $oVisibleObjects = UserRights::GetSelectFilter($this->GetClass(), $this->GetModifierProperties('UserRightsGetSelectFilter')); - if ($oVisibleObjects === false) - { - // Make sure this is a valid search object, saying NO for all - $oVisibleObjects = DBObjectSearch::FromEmptySet($this->GetClass()); - } - if (is_object($oVisibleObjects)) - { - $oVisibleObjects->AllowAllData(); - $oSearch = $this->Intersect($oVisibleObjects); - $oSearch->SetDataFiltered(); - } - } - - // Compute query modifiers properties (can be set in the search itself, by the context, etc.) - // - $aModifierProperties = MetaModel::MakeModifierProperties($oSearch); - - // Create a unique cache id - // - $aContextData = array(); - $bCanCache = true; - if (self::$m_bQueryCacheEnabled || self::$m_bTraceQueries) - { - if (isset($_SERVER['REQUEST_URI'])) - { - $aContextData['sRequestUri'] = $_SERVER['REQUEST_URI']; - } - else if (isset($_SERVER['SCRIPT_NAME'])) - { - $aContextData['sRequestUri'] = $_SERVER['SCRIPT_NAME']; - } - else - { - $aContextData['sRequestUri'] = ''; - } - - // Need to identify the query - $sOqlQuery = $oSearch->ToOql(false, null, true); - if ((strpos($sOqlQuery, '`id` IN (') !== false) || (strpos($sOqlQuery, '`id` NOT IN (') !== false)) - { - // Requests containing "id IN" are not worth caching - $bCanCache = false; - } - - $aContextData['sOqlQuery'] = $sOqlQuery; - - if (count($aModifierProperties)) - { - array_multisort($aModifierProperties); - $sModifierProperties = json_encode($aModifierProperties); - } - else - { - $sModifierProperties = ''; - } - $aContextData['sModifierProperties'] = $sModifierProperties; - - $sRawId = Dict::GetUserLanguage().'-'.$sOqlQuery.$sModifierProperties; - if (!is_null($aAttToLoad)) - { - $sRawId .= json_encode($aAttToLoad); - } - $aContextData['aAttToLoad'] = $aAttToLoad; - if (!is_null($aGroupByExpr)) - { - foreach($aGroupByExpr as $sAlias => $oExpr) - { - $sRawId .= 'g:'.$sAlias.'!'.$oExpr->Render(); - } - } - if (!is_null($aSelectExpr)) - { - foreach($aSelectExpr as $sAlias => $oExpr) - { - $sRawId .= 'se:'.$sAlias.'!'.$oExpr->Render(); - } - } - $aContextData['aGroupByExpr'] = $aGroupByExpr; - $aContextData['aSelectExpr'] = $aSelectExpr; - $sRawId .= $bGetCount; - $aContextData['bGetCount'] = $bGetCount; - if (is_array($aSelectedClasses)) - { - $sRawId .= implode(',', $aSelectedClasses); // Unions may alter the list of selected columns - } - $aContextData['aSelectedClasses'] = $aSelectedClasses; - $bIsArchiveMode = $oSearch->GetArchiveMode(); - $sRawId .= $bIsArchiveMode ? '--arch' : ''; - $bShowObsoleteData = $oSearch->GetShowObsoleteData(); - $sRawId .= $bShowObsoleteData ? '--obso' : ''; - $aContextData['bIsArchiveMode'] = $bIsArchiveMode; - $aContextData['bShowObsoleteData'] = $bShowObsoleteData; - $sOqlId = md5($sRawId); - } - else - { - $sOqlQuery = "SELECTING... ".$oSearch->GetClass(); - $sOqlId = "query id ? n/a"; - } - - - // Query caching - // - $sOqlAPCCacheId = null; - if (self::$m_bQueryCacheEnabled && $bCanCache) - { - // Warning: using directly the query string as the key to the hash array can FAIL if the string - // is long and the differences are only near the end... so it's safer (but not bullet proof?) - // to use a hash (like md5) of the string as the key ! - // - // Example of two queries that were found as similar by the hash array: - // SELECT SLT JOIN lnkSLTToSLA AS L1 ON L1.slt_id=SLT.id JOIN SLA ON L1.sla_id = SLA.id JOIN lnkContractToSLA AS L2 ON L2.sla_id = SLA.id JOIN CustomerContract ON L2.contract_id = CustomerContract.id WHERE SLT.ticket_priority = 1 AND SLA.service_id = 3 AND SLT.metric = 'TTO' AND CustomerContract.customer_id = 2 - // and - // SELECT SLT JOIN lnkSLTToSLA AS L1 ON L1.slt_id=SLT.id JOIN SLA ON L1.sla_id = SLA.id JOIN lnkContractToSLA AS L2 ON L2.sla_id = SLA.id JOIN CustomerContract ON L2.contract_id = CustomerContract.id WHERE SLT.ticket_priority = 1 AND SLA.service_id = 3 AND SLT.metric = 'TTR' AND CustomerContract.customer_id = 2 - // the only difference is R instead or O at position 285 (TTR instead of TTO)... - // - if (array_key_exists($sOqlId, self::$m_aQueryStructCache)) - { - // hit! - - $oSQLQuery = unserialize(serialize(self::$m_aQueryStructCache[$sOqlId])); - // Note: cloning is not enough because the subtree is made of objects - } - elseif (self::$m_bUseAPCCache) - { - // Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter - // - $sOqlAPCCacheId = 'itop-'.MetaModel::GetEnvironmentId().'-query-cache-'.$sOqlId; - $oKPI = new ExecutionKPI(); - $result = apc_fetch($sOqlAPCCacheId); - $oKPI->ComputeStats('Query APC (fetch)', $sOqlQuery); - - if (is_object($result)) - { - $oSQLQuery = $result; - self::$m_aQueryStructCache[$sOqlId] = $oSQLQuery; - } - } - } - - if (!isset($oSQLQuery)) - { - $oKPI = new ExecutionKPI(); - $oSQLQuery = $oSearch->BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr, $aSelectedClasses, $aSelectExpr); - $oKPI->ComputeStats('BuildSQLQueryStruct', $sOqlQuery); - - if (self::$m_bQueryCacheEnabled) - { - if ($bCanCache && self::$m_bUseAPCCache) - { - $oSQLQuery->m_aContextData = $aContextData; - $oKPI = new ExecutionKPI(); - apc_store($sOqlAPCCacheId, $oSQLQuery, self::$m_iQueryCacheTTL); - $oKPI->ComputeStats('Query APC (store)', $sOqlQuery); - } - - self::$m_aQueryStructCache[$sOqlId] = $oSQLQuery->DeepClone(); - } - } - return $oSQLQuery; - } - - /** - * @param array $aAttToLoad - * @param bool $bGetCount - * @param array $aModifierProperties - * @param array $aGroupByExpr - * @param array $aSelectedClasses - * @param array $aSelectExpr - * - * @return null|SQLObjectQuery - * @throws \CoreException - */ - protected function BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null) - { - $oBuild = new QueryBuilderContext($this, $aModifierProperties, $aGroupByExpr, $aSelectedClasses, $aSelectExpr); - - $oSQLQuery = $this->MakeSQLObjectQuery($oBuild, $aAttToLoad, array()); - $oSQLQuery->SetCondition($oBuild->m_oQBExpressions->GetCondition()); - if (is_array($aGroupByExpr)) - { - $aCols = $oBuild->m_oQBExpressions->GetGroupBy(); - $oSQLQuery->SetGroupBy($aCols); - $oSQLQuery->SetSelect($aCols); - } - else - { - $oSQLQuery->SetSelect($oBuild->m_oQBExpressions->GetSelect()); - } - if ($aSelectExpr) - { - // Get the fields corresponding to the select expressions - foreach($oBuild->m_oQBExpressions->GetSelect() as $sAlias => $oExpr) - { - if (key_exists($sAlias, $aSelectExpr)) - { - $oSQLQuery->AddSelect($sAlias, $oExpr); - } - } - } - - $aMandatoryTables = null; - if (self::$m_bOptimizeQueries) - { - if ($bGetCount) - { - // Simplify the query if just getting the count - $oSQLQuery->SetSelect(array()); - } - $oBuild->m_oQBExpressions->GetMandatoryTables($aMandatoryTables); - $oSQLQuery->OptimizeJoins($aMandatoryTables); - } - // Filter tables as late as possible: do not interfere with the optimization process - foreach ($oBuild->GetFilteredTables() as $sTableAlias => $aConditions) - { - if ($aMandatoryTables && array_key_exists($sTableAlias, $aMandatoryTables)) - { - foreach ($aConditions as $oCondition) - { - $oSQLQuery->AddCondition($oCondition); - } - } - } - - return $oSQLQuery; - } - - - /** - * @param $oBuild - * @param null $aAttToLoad - * @param array $aValues - * @return null|SQLObjectQuery - * @throws \CoreException - */ - protected function MakeSQLObjectQuery(&$oBuild, $aAttToLoad = null, $aValues = array()) - { - // Note: query class might be different than the class of the filter - // -> this occurs when we are linking our class to an external class (referenced by, or pointing to) - $sClass = $this->GetFirstJoinedClass(); - $sClassAlias = $this->GetFirstJoinedClassAlias(); - - $bIsOnQueriedClass = array_key_exists($sClassAlias, $oBuild->GetRootFilter()->GetSelectedClasses()); - - //self::DbgTrace("Entering: ".$this->ToOQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY")); - - //$sRootClass = MetaModel::GetRootClass($sClass); - $sKeyField = MetaModel::DBGetKey($sClass); - - if ($bIsOnQueriedClass) - { - // default to the whole list of attributes + the very std id/finalclass - $oBuild->m_oQBExpressions->AddSelect($sClassAlias.'id', new FieldExpression('id', $sClassAlias)); - if (is_null($aAttToLoad) || !array_key_exists($sClassAlias, $aAttToLoad)) - { - $sSelectedClass = $oBuild->GetSelectedClass($sClassAlias); - $aAttList = MetaModel::ListAttributeDefs($sSelectedClass); - } - else - { - $aAttList = $aAttToLoad[$sClassAlias]; - } - foreach ($aAttList as $sAttCode => $oAttDef) - { - if (!$oAttDef->IsScalar()) continue; - // keep because it can be used for sorting - if (!$oAttDef->LoadInObject()) continue; - - if ($oAttDef->IsBasedOnOQLExpression()) - { - $oBuild->m_oQBExpressions->AddSelect($sClassAlias.$sAttCode, new FieldExpression($sAttCode, $sClassAlias)); - } - else - { - foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr) - { - $oBuild->m_oQBExpressions->AddSelect($sClassAlias.$sAttCode.$sColId, new FieldExpression($sAttCode.$sColId, $sClassAlias)); - } - } - } - } - //echo "

    oQBExpr ".__LINE__.":

    \n".print_r($oBuild->m_oQBExpressions, true)."

    \n"; - $aExpectedAtts = array(); // array of (attcode => fieldexpression) - //echo "

    ".__LINE__.": GetUnresolvedFields($sClassAlias, ...)

    \n"; - $oBuild->m_oQBExpressions->GetUnresolvedFields($sClassAlias, $aExpectedAtts); - - // Compute a clear view of required joins (from the current class) - // Build the list of external keys: - // -> ext keys required by an explicit join - // -> ext keys mentionned in a 'pointing to' condition - // -> ext keys required for an external field - // -> ext keys required for a friendly name - // - $aExtKeys = array(); // array of sTableClass => array of (sAttCode (keys) => array of (sAttCode (fields)=> oAttDef)) - // - // Optimization: could be partially computed once for all (cached) ? - // - - if ($bIsOnQueriedClass) - { - // Get all Ext keys for the queried class (??) - foreach(MetaModel::GetKeysList($sClass) as $sKeyAttCode) - { - $sKeyTableClass = MetaModel::GetAttributeOrigin($sClass, $sKeyAttCode); - $aExtKeys[$sKeyTableClass][$sKeyAttCode] = array(); - } - } - // Get all Ext keys used by the filter - foreach ($this->GetCriteria_PointingTo() as $sKeyAttCode => $aPointingTo) - { - if (array_key_exists(TREE_OPERATOR_EQUALS, $aPointingTo)) - { - $sKeyTableClass = MetaModel::GetAttributeOrigin($sClass, $sKeyAttCode); - $aExtKeys[$sKeyTableClass][$sKeyAttCode] = array(); - } - } - - $aFNJoinAlias = array(); // array of (subclass => alias) - foreach ($aExpectedAtts as $sExpectedAttCode => $oExpression) - { - if (!MetaModel::IsValidAttCode($sClass, $sExpectedAttCode)) continue; - $oAttDef = MetaModel::GetAttributeDef($sClass, $sExpectedAttCode); - if ($oAttDef->IsBasedOnOQLExpression()) - { - // To optimize: detect a restriction on child classes in the condition expression - // e.g. SELECT FunctionalCI WHERE finalclass IN ('Server', 'VirtualMachine') - $oExpression = static::GetPolymorphicExpression($sClass, $sExpectedAttCode); - - $aRequiredFields = array(); - $oExpression->GetUnresolvedFields('', $aRequiredFields); - $aTranslateFields = array(); - foreach($aRequiredFields as $sSubClass => $aFields) - { - foreach($aFields as $sAttCode => $oField) - { - $oAttDef = MetaModel::GetAttributeDef($sSubClass, $sAttCode); - if ($oAttDef->IsExternalKey()) - { - $sClassOfAttribute = MetaModel::GetAttributeOrigin($sSubClass, $sAttCode); - $aExtKeys[$sClassOfAttribute][$sAttCode] = array(); - } - elseif ($oAttDef->IsExternalField()) - { - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - $sClassOfAttribute = MetaModel::GetAttributeOrigin($sSubClass, $sKeyAttCode); - $aExtKeys[$sClassOfAttribute][$sKeyAttCode][$sAttCode] = $oAttDef; - } - else - { - $sClassOfAttribute = MetaModel::GetAttributeOrigin($sSubClass, $sAttCode); - } - - if (MetaModel::IsParentClass($sClassOfAttribute, $sClass)) - { - // The attribute is part of the standard query - // - $sAliasForAttribute = $sClassAlias; - } - else - { - // The attribute will be available from an additional outer join - // For each subclass (table) one single join is enough - // - if (!array_key_exists($sClassOfAttribute, $aFNJoinAlias)) - { - $sAliasForAttribute = $oBuild->GenerateClassAlias($sClassAlias.'_fn_'.$sClassOfAttribute, $sClassOfAttribute); - $aFNJoinAlias[$sClassOfAttribute] = $sAliasForAttribute; - } - else - { - $sAliasForAttribute = $aFNJoinAlias[$sClassOfAttribute]; - } - } - - $aTranslateFields[$sSubClass][$sAttCode] = new FieldExpression($sAttCode, $sAliasForAttribute); - } - } - $oExpression = $oExpression->Translate($aTranslateFields, false); - - $aTranslateNow = array(); - $aTranslateNow[$sClassAlias][$sExpectedAttCode] = $oExpression; - $oBuild->m_oQBExpressions->Translate($aTranslateNow, false); - } - } - - // Add the ext fields used in the select (eventually adds an external key) - foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) - { - if ($oAttDef->IsExternalField()) - { - if (array_key_exists($sAttCode, $aExpectedAtts)) - { - // Add the external attribute - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - $sKeyTableClass = MetaModel::GetAttributeOrigin($sClass, $sKeyAttCode); - $aExtKeys[$sKeyTableClass][$sKeyAttCode][$sAttCode] = $oAttDef; - } - } - } - - $bRootFirst = MetaModel::GetConfig()->Get('optimize_requests_for_join_count'); - if ($bRootFirst) - { - // First query built from the root, adding all tables including the leaf - // Before N.1065 we were joining from the leaf first, but this wasn't a good choice : - // most of the time (obsolescence, friendlyname, ...) we want to get a root attribute ! - // - $oSelectBase = null; - $aClassHierarchy = MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL, true); - $bIsClassStandaloneClass = (count($aClassHierarchy) == 1); - foreach($aClassHierarchy as $sSomeClass) - { - if (!MetaModel::HasTable($sSomeClass)) - { - continue; - } - - self::DbgTrace("Adding join from root to leaf: $sSomeClass... let's call MakeSQLObjectQuerySingleTable()"); - $oSelectParentTable = $this->MakeSQLObjectQuerySingleTable($oBuild, $aAttToLoad, $sSomeClass, $aExtKeys, $aValues); - if (is_null($oSelectBase)) - { - $oSelectBase = $oSelectParentTable; - if (!$bIsClassStandaloneClass && (MetaModel::IsRootClass($sSomeClass))) - { - // As we're linking to root class first, we're adding a where clause on the finalClass attribute : - // COALESCE($sRootClassFinalClass IN ('$sExpectedClasses'), 1) - // If we don't, the child classes can be removed in the query optimisation phase, including the leaf that was queried - // So we still need to filter records to only those corresponding to the child classes ! - // The coalesce is mandatory if we have a polymorphic query (left join) - $oClassListExpr = ListExpression::FromScalars(MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL)); - $sFinalClassSqlColumnName = MetaModel::DBGetClassField($sSomeClass); - $oClassExpr = new FieldExpression($sFinalClassSqlColumnName, $oSelectBase->GetTableAlias()); - $oInExpression = new BinaryExpression($oClassExpr, 'IN', $oClassListExpr); - $oTrueExpression = new TrueExpression(); - $aCoalesceAttr = array($oInExpression, $oTrueExpression); - $oFinalClassRestriction = new FunctionExpression("COALESCE", $aCoalesceAttr); - - $oBuild->m_oQBExpressions->AddCondition($oFinalClassRestriction); - } - } - else - { - $oSelectBase->AddInnerJoin($oSelectParentTable, $sKeyField, MetaModel::DBGetKey($sSomeClass)); - } - } - } - else - { - // First query built upon on the leaf (ie current) class - // - self::DbgTrace("Main (=leaf) class, call MakeSQLObjectQuerySingleTable()"); - if (MetaModel::HasTable($sClass)) - { - $oSelectBase = $this->MakeSQLObjectQuerySingleTable($oBuild, $aAttToLoad, $sClass, $aExtKeys, $aValues); - } - else - { - $oSelectBase = null; - - // As the join will not filter on the expected classes, we have to specify it explicitely - $sExpectedClasses = implode("', '", MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL)); - $oFinalClassRestriction = Expression::FromOQL("`$sClassAlias`.finalclass IN ('$sExpectedClasses')"); - $oBuild->m_oQBExpressions->AddCondition($oFinalClassRestriction); - } - - // Then we join the queries of the eventual parent classes (compound model) - foreach(MetaModel::EnumParentClasses($sClass) as $sParentClass) - { - if (!MetaModel::HasTable($sParentClass)) continue; - - self::DbgTrace("Parent class: $sParentClass... let's call MakeSQLObjectQuerySingleTable()"); - $oSelectParentTable = $this->MakeSQLObjectQuerySingleTable($oBuild, $aAttToLoad, $sParentClass, $aExtKeys, $aValues); - if (is_null($oSelectBase)) - { - $oSelectBase = $oSelectParentTable; - } - else - { - $oSelectBase->AddInnerJoin($oSelectParentTable, $sKeyField, MetaModel::DBGetKey($sParentClass)); - } - } - } - - // Filter on objects referencing me - // - foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences) - { - foreach($aReferences as $sForeignExtKeyAttCode => $aFiltersByOperator) - { - foreach ($aFiltersByOperator as $iOperatorCode => $aFilters) - { - foreach ($aFilters as $oForeignFilter) - { - $oForeignKeyAttDef = MetaModel::GetAttributeDef($sForeignClass, $sForeignExtKeyAttCode); - - self::DbgTrace("Referenced by foreign key: $sForeignExtKeyAttCode... let's call MakeSQLObjectQuery()"); - //self::DbgTrace($oForeignFilter); - //self::DbgTrace($oForeignFilter->ToOQL()); - //self::DbgTrace($oSelectForeign); - //self::DbgTrace($oSelectForeign->RenderSelect(array())); - - $sForeignClassAlias = $oForeignFilter->GetFirstJoinedClassAlias(); - $oBuild->m_oQBExpressions->PushJoinField(new FieldExpression($sForeignExtKeyAttCode, $sForeignClassAlias)); - - if ($oForeignKeyAttDef instanceof AttributeObjectKey) - { - $sClassAttCode = $oForeignKeyAttDef->Get('class_attcode'); - - // Add the condition: `$sForeignClassAlias`.$sClassAttCode IN (subclasses of $sClass') - $oClassListExpr = ListExpression::FromScalars(MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL)); - $oClassExpr = new FieldExpression($sClassAttCode, $sForeignClassAlias); - $oClassRestriction = new BinaryExpression($oClassExpr, 'IN', $oClassListExpr); - $oBuild->m_oQBExpressions->AddCondition($oClassRestriction); - } - - $oSelectForeign = $oForeignFilter->MakeSQLObjectQuery($oBuild, $aAttToLoad); - - $oJoinExpr = $oBuild->m_oQBExpressions->PopJoinField(); - $sForeignKeyTable = $oJoinExpr->GetParent(); - $sForeignKeyColumn = $oJoinExpr->GetName(); - - if ($iOperatorCode == TREE_OPERATOR_EQUALS) - { - $oSelectBase->AddInnerJoin($oSelectForeign, $sKeyField, $sForeignKeyColumn, $sForeignKeyTable); - } - else - { - // Hierarchical key - $KeyLeft = $oForeignKeyAttDef->GetSQLLeft(); - $KeyRight = $oForeignKeyAttDef->GetSQLRight(); - - $oSelectBase->AddInnerJoinTree($oSelectForeign, $KeyLeft, $KeyRight, $KeyLeft, $KeyRight, $sForeignKeyTable, $iOperatorCode, true); - } - } - } - } - } - - // Additional JOINS for Friendly names - // - foreach ($aFNJoinAlias as $sSubClass => $sSubClassAlias) - { - $oSubClassFilter = new DBObjectSearch($sSubClass, $sSubClassAlias); - $oSelectFN = $oSubClassFilter->MakeSQLObjectQuerySingleTable($oBuild, $aAttToLoad, $sSubClass, $aExtKeys, array()); - $oSelectBase->AddLeftJoin($oSelectFN, $sKeyField, MetaModel::DBGetKey($sSubClass)); - } - - // That's all... cross fingers and we'll get some working query - - //MyHelpers::var_dump_html($oSelectBase, true); - //MyHelpers::var_dump_html($oSelectBase->RenderSelect(), true); - if (self::$m_bDebugQuery) $oSelectBase->DisplayHtml(); - return $oSelectBase; - } - - protected function MakeSQLObjectQuerySingleTable(&$oBuild, $aAttToLoad, $sTableClass, $aExtKeys, $aValues) - { - // $aExtKeys is an array of sTableClass => array of (sAttCode (keys) => array of sAttCode (fields)) - - // Prepare the query for a single table (compound objects) - // Ignores the items (attributes/filters) that are not on the target table - // Perform an (inner or left) join for every external key (and specify the expected fields) - // - // Returns an SQLQuery - // - $sTargetClass = $this->GetFirstJoinedClass(); - $sTargetAlias = $this->GetFirstJoinedClassAlias(); - $sTable = MetaModel::DBGetTable($sTableClass); - $sTableAlias = $oBuild->GenerateTableAlias($sTargetAlias.'_'.$sTable, $sTable); - - $aTranslation = array(); - $aExpectedAtts = array(); - $oBuild->m_oQBExpressions->GetUnresolvedFields($sTargetAlias, $aExpectedAtts); - - $bIsOnQueriedClass = array_key_exists($sTargetAlias, $oBuild->GetRootFilter()->GetSelectedClasses()); - - self::DbgTrace("Entering: tableclass=$sTableClass, filter=".$this->ToOQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY")); - - // 1 - SELECT and UPDATE - // - // Note: no need for any values nor fields for foreign Classes (ie not the queried Class) - // - $aUpdateValues = array(); - - - // 1/a - Get the key and friendly name - // - // We need one pkey to be the key, let's take the first one available - $oSelectedIdField = null; - $oIdField = new FieldExpressionResolved(MetaModel::DBGetKey($sTableClass), $sTableAlias); - $aTranslation[$sTargetAlias]['id'] = $oIdField; - - if ($bIsOnQueriedClass) - { - // Add this field to the list of queried fields (required for the COUNT to work fine) - $oSelectedIdField = $oIdField; - } - - // 1/b - Get the other attributes - // - foreach(MetaModel::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef) - { - // Skip this attribute if not defined in this table - if (MetaModel::GetAttributeOrigin($sTargetClass, $sAttCode) != $sTableClass) continue; - - // Skip this attribute if not made of SQL columns - if (count($oAttDef->GetSQLExpressions()) == 0) continue; - - // Update... - // - if ($bIsOnQueriedClass && array_key_exists($sAttCode, $aValues)) - { - assert ($oAttDef->IsBasedOnDBColumns()); - foreach ($oAttDef->GetSQLValues($aValues[$sAttCode]) as $sColumn => $sValue) - { - $aUpdateValues[$sColumn] = $sValue; - } - } - } - - // 2 - The SQL query, for this table only - // - $oSelectBase = new SQLObjectQuery($sTable, $sTableAlias, array(), $bIsOnQueriedClass, $aUpdateValues, $oSelectedIdField); - - // 3 - Resolve expected expressions (translation table: alias.attcode => table.column) - // - foreach(MetaModel::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef) - { - // Skip this attribute if not defined in this table - if (MetaModel::GetAttributeOrigin($sTargetClass, $sAttCode) != $sTableClass) continue; - - // Select... - // - if ($oAttDef->IsExternalField()) - { - // skip, this will be handled in the joined tables (done hereabove) - } - else - { - // standard field, or external key - // add it to the output - foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr) - { - if (array_key_exists($sAttCode.$sColId, $aExpectedAtts)) - { - $oFieldSQLExp = new FieldExpressionResolved($sSQLExpr, $sTableAlias); - /** - * @var string $sPluginClass - * @var iQueryModifier $oQueryModifier - */ - foreach (MetaModel::EnumPlugins('iQueryModifier') as $sPluginClass => $oQueryModifier) - { - $oFieldSQLExp = $oQueryModifier->GetFieldExpression($oBuild, $sTargetClass, $sAttCode, $sColId, $oFieldSQLExp, $oSelectBase); - } - $aTranslation[$sTargetAlias][$sAttCode.$sColId] = $oFieldSQLExp; - } - } - } - } - - // 4 - The external keys -> joins... - // - $aAllPointingTo = $this->GetCriteria_PointingTo(); - - if (array_key_exists($sTableClass, $aExtKeys)) - { - foreach ($aExtKeys[$sTableClass] as $sKeyAttCode => $aExtFields) - { - $oKeyAttDef = MetaModel::GetAttributeDef($sTableClass, $sKeyAttCode); - - $aPointingTo = $this->GetCriteria_PointingTo($sKeyAttCode); - if (!array_key_exists(TREE_OPERATOR_EQUALS, $aPointingTo)) - { - // The join was not explicitely defined in the filter, - // we need to do it now - $sKeyClass = $oKeyAttDef->GetTargetClass(); - $sKeyClassAlias = $oBuild->GenerateClassAlias($sKeyClass.'_'.$sKeyAttCode, $sKeyClass); - $oExtFilter = new DBObjectSearch($sKeyClass, $sKeyClassAlias); - - $aAllPointingTo[$sKeyAttCode][TREE_OPERATOR_EQUALS][$sKeyClassAlias] = $oExtFilter; - } - } - } - - foreach ($aAllPointingTo as $sKeyAttCode => $aPointingTo) - { - foreach($aPointingTo as $iOperatorCode => $aFilter) - { - foreach($aFilter as $oExtFilter) - { - if (!MetaModel::IsValidAttCode($sTableClass, $sKeyAttCode)) continue; // Not defined in the class, skip it - // The aliases should not conflict because normalization occured while building the filter - $oKeyAttDef = MetaModel::GetAttributeDef($sTableClass, $sKeyAttCode); - $sKeyClass = $oExtFilter->GetFirstJoinedClass(); - $sKeyClassAlias = $oExtFilter->GetFirstJoinedClassAlias(); - - // Note: there is no search condition in $oExtFilter, because normalization did merge the condition onto the top of the filter tree - - if ($iOperatorCode == TREE_OPERATOR_EQUALS) - { - if (array_key_exists($sTableClass, $aExtKeys) && array_key_exists($sKeyAttCode, $aExtKeys[$sTableClass])) - { - // Specify expected attributes for the target class query - // ... and use the current alias ! - $aTranslateNow = array(); // Translation for external fields - must be performed before the join is done (recursion...) - foreach($aExtKeys[$sTableClass][$sKeyAttCode] as $sAttCode => $oAtt) - { - $oExtAttDef = $oAtt->GetExtAttDef(); - if ($oExtAttDef->IsBasedOnOQLExpression()) - { - $aTranslateNow[$sTargetAlias][$sAttCode] = new FieldExpression($oExtAttDef->GetCode(), $sKeyClassAlias); - } - else - { - $sExtAttCode = $oAtt->GetExtAttCode(); - // Translate mainclass.extfield => remoteclassalias.remotefieldcode - $oRemoteAttDef = MetaModel::GetAttributeDef($sKeyClass, $sExtAttCode); - foreach ($oRemoteAttDef->GetSQLExpressions() as $sColId => $sRemoteAttExpr) - { - $aTranslateNow[$sTargetAlias][$sAttCode.$sColId] = new FieldExpression($sExtAttCode, $sKeyClassAlias); - } - } - } - - if ($oKeyAttDef instanceof AttributeObjectKey) - { - // Add the condition: `$sTargetAlias`.$sClassAttCode IN (subclasses of $sKeyClass') - $sClassAttCode = $oKeyAttDef->Get('class_attcode'); - $oClassAttDef = MetaModel::GetAttributeDef($sTargetClass, $sClassAttCode); - foreach ($oClassAttDef->GetSQLExpressions() as $sColId => $sSQLExpr) - { - $aTranslateNow[$sTargetAlias][$sClassAttCode.$sColId] = new FieldExpressionResolved($sSQLExpr, $sTableAlias); - } - - $oClassListExpr = ListExpression::FromScalars(MetaModel::EnumChildClasses($sKeyClass, ENUM_CHILD_CLASSES_ALL)); - $oClassExpr = new FieldExpression($sClassAttCode, $sTargetAlias); - $oClassRestriction = new BinaryExpression($oClassExpr, 'IN', $oClassListExpr); - $oBuild->m_oQBExpressions->AddCondition($oClassRestriction); - } - - // Translate prior to recursing - // - $oBuild->m_oQBExpressions->Translate($aTranslateNow, false); - - self::DbgTrace("External key $sKeyAttCode (class: $sKeyClass), call MakeSQLObjectQuery()"); - $oBuild->m_oQBExpressions->PushJoinField(new FieldExpression('id', $sKeyClassAlias)); - - $oSelectExtKey = $oExtFilter->MakeSQLObjectQuery($oBuild, $aAttToLoad); - - $oJoinExpr = $oBuild->m_oQBExpressions->PopJoinField(); - $sExternalKeyTable = $oJoinExpr->GetParent(); - $sExternalKeyField = $oJoinExpr->GetName(); - - $aCols = $oKeyAttDef->GetSQLExpressions(); // Workaround a PHP bug: sometimes issuing a Notice if invoking current(somefunc()) - $sLocalKeyField = current($aCols); // get the first column for an external key - - self::DbgTrace("External key $sKeyAttCode, Join on $sLocalKeyField = $sExternalKeyField"); - if ($oKeyAttDef->IsNullAllowed()) - { - $oSelectBase->AddLeftJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField); - } - else - { - $oSelectBase->AddInnerJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField, $sExternalKeyTable); - } - } - } - elseif(MetaModel::GetAttributeOrigin($sKeyClass, $sKeyAttCode) == $sTableClass) - { - $oBuild->m_oQBExpressions->PushJoinField(new FieldExpression($sKeyAttCode, $sKeyClassAlias)); - $oSelectExtKey = $oExtFilter->MakeSQLObjectQuery($oBuild, $aAttToLoad); - $oJoinExpr = $oBuild->m_oQBExpressions->PopJoinField(); - $sExternalKeyTable = $oJoinExpr->GetParent(); - $sExternalKeyField = $oJoinExpr->GetName(); - $sLeftIndex = $sExternalKeyField.'_left'; // TODO use GetSQLLeft() - $sRightIndex = $sExternalKeyField.'_right'; // TODO use GetSQLRight() - - $LocalKeyLeft = $oKeyAttDef->GetSQLLeft(); - $LocalKeyRight = $oKeyAttDef->GetSQLRight(); - - $oSelectBase->AddInnerJoinTree($oSelectExtKey, $LocalKeyLeft, $LocalKeyRight, $sLeftIndex, $sRightIndex, $sExternalKeyTable, $iOperatorCode); - } - } - } - } - - // Translate the selected columns - // - $oBuild->m_oQBExpressions->Translate($aTranslation, false); - - // Filter out archived records - // - if (MetaModel::IsArchivable($sTableClass)) - { - if (!$oBuild->GetRootFilter()->GetArchiveMode()) - { - $bIsOnJoinedClass = array_key_exists($sTargetAlias, $oBuild->GetRootFilter()->GetJoinedClasses()); - if ($bIsOnJoinedClass) - { - if (MetaModel::IsParentClass($sTableClass, $sTargetClass)) - { - $oNotArchived = new BinaryExpression(new FieldExpressionResolved('archive_flag', $sTableAlias), '=', new ScalarExpression(0)); - $oBuild->AddFilteredTable($sTableAlias, $oNotArchived); - } - } - } - } - return $oSelectBase; - } - - /** - * Get the expression for the class and its subclasses (if finalclass = 'subclass' ...) - * Simplifies the final expression by grouping classes having the same expression - * @param $sClass - * @param $sAttCode - * @return \FunctionExpression|mixed|null - * @throws \CoreException -*/ - static public function GetPolymorphicExpression($sClass, $sAttCode) - { - $oExpression = ExpressionCache::GetCachedExpression($sClass, $sAttCode); - if (!empty($oExpression)) - { - return $oExpression; - } - - // 1st step - get all of the required expressions (instantiable classes) - // and group them using their OQL representation - // - $aExpressions = array(); // signature => array('expression' => oExp, 'classes' => array of classes) - foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sSubClass) - { - if (($sSubClass != $sClass) && MetaModel::IsAbstract($sSubClass)) continue; - - $oAttDef = MetaModel::GetAttributeDef($sSubClass, $sAttCode); - $oSubClassExp = $oAttDef->GetOQLExpression($sSubClass); - - // 3rd step - position the attributes in the hierarchy of classes - // - $oSubClassExp->Browse(function($oNode) use ($sSubClass) { - if ($oNode instanceof FieldExpression) - { - $sAttCode = $oNode->GetName(); - $oAttDef = MetaModel::GetAttributeDef($sSubClass, $sAttCode); - if ($oAttDef->IsExternalField()) - { - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - $sClassOfAttribute = MetaModel::GetAttributeOrigin($sSubClass, $sKeyAttCode); - } - else - { - $sClassOfAttribute = MetaModel::GetAttributeOrigin($sSubClass, $sAttCode); - } - $sParent = MetaModel::GetAttributeOrigin($sClassOfAttribute, $oNode->GetName()); - $oNode->SetParent($sParent); - } - }); - - $sSignature = $oSubClassExp->Render(); - if (!array_key_exists($sSignature, $aExpressions)) - { - $aExpressions[$sSignature] = array( - 'expression' => $oSubClassExp, - 'classes' => array(), - ); - } - $aExpressions[$sSignature]['classes'][] = $sSubClass; - } - - // 2nd step - build the final name expression depending on the finalclass - // - if (count($aExpressions) == 1) - { - $aExpData = reset($aExpressions); - $oExpression = $aExpData['expression']; - } - else - { - $oExpression = null; - foreach ($aExpressions as $sSignature => $aExpData) - { - $oClassListExpr = ListExpression::FromScalars($aExpData['classes']); - $oClassExpr = new FieldExpression('finalclass', $sClass); - $oClassInList = new BinaryExpression($oClassExpr, 'IN', $oClassListExpr); - - if (is_null($oExpression)) - { - $oExpression = $aExpData['expression']; - } - else - { - $oExpression = new FunctionExpression('IF', array($oClassInList, $aExpData['expression'], $oExpression)); - } - } - } - return $oExpression; - } - - /** - * @param array $aAttCodes array of attCodes to search into - * @param string $sNeedle one word to be searched - * - * @throws \CoreException - */ - public function AddCondition_FullTextOnAttributes(array $aAttCodes, $sNeedle) - { - } - - public function ListParameters() - { - return $this->GetCriteria()->ListParameters(); - } -} diff --git a/core/legacy/querybuildercontextlegacy.class.inc.php b/core/legacy/querybuildercontextlegacy.class.inc.php deleted file mode 100644 index 8af0fc3d1d..0000000000 --- a/core/legacy/querybuildercontextlegacy.class.inc.php +++ /dev/null @@ -1,107 +0,0 @@ - - -/** - * Associated with the metamodel -> MakeQuery/MakeQuerySingleTable - * - * @copyright Copyright (C) 2010-2021 Combodo SARL - * @license http://opensource.org/licenses/AGPL-3.0 - */ - -class QueryBuilderContext -{ - protected $m_oRootFilter; - protected $m_aClassAliases; - protected $m_aTableAliases; - protected $m_aModifierProperties; - protected $m_aSelectedClasses; - protected $m_aFilteredTables; - - public $m_oQBExpressions; - - public function __construct($oFilter, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null) - { - $this->m_oRootFilter = $oFilter; - $this->m_oQBExpressions = new QueryBuilderExpressions($oFilter, $aGroupByExpr, $aSelectExpr); - - $this->m_aClassAliases = $oFilter->GetJoinedClasses(); - $this->m_aTableAliases = array(); - $this->m_aFilteredTables = array(); - - $this->m_aModifierProperties = $aModifierProperties; - if (is_null($aSelectedClasses)) - { - $this->m_aSelectedClasses = $oFilter->GetSelectedClasses(); - } - else - { - // For the unions, the selected classes can be upper in the hierarchy (lowest common ancestor) - $this->m_aSelectedClasses = $aSelectedClasses; - } - } - - public function GetRootFilter() - { - return $this->m_oRootFilter; - } - - public function GenerateTableAlias($sNewName, $sRealName) - { - return MetaModel::GenerateUniqueAlias($this->m_aTableAliases, $sNewName, $sRealName); - } - - public function GenerateClassAlias($sNewName, $sRealName) - { - return MetaModel::GenerateUniqueAlias($this->m_aClassAliases, $sNewName, $sRealName); - } - - public function GetModifierProperties($sPluginClass) - { - if (array_key_exists($sPluginClass, $this->m_aModifierProperties)) - { - return $this->m_aModifierProperties[$sPluginClass]; - } - else - { - return array(); - } - } - - public function GetSelectedClass($sAlias) - { - return $this->m_aSelectedClasses[$sAlias]; - } - - public function AddFilteredTable($sTableAlias, $oCondition) - { - if (array_key_exists($sTableAlias, $this->m_aFilteredTables)) - { - $this->m_aFilteredTables[$sTableAlias][] = $oCondition; - } - else - { - $this->m_aFilteredTables[$sTableAlias] = array($oCondition); - } - } - - public function GetFilteredTables() - { - return $this->m_aFilteredTables; - } -} - diff --git a/core/legacy/querybuilderexpressionslegacy.class.inc.php b/core/legacy/querybuilderexpressionslegacy.class.inc.php deleted file mode 100644 index 2bd5d911cd..0000000000 --- a/core/legacy/querybuilderexpressionslegacy.class.inc.php +++ /dev/null @@ -1,186 +0,0 @@ -m_oConditionExpr = $oSearch->GetCriteria(); - if (!$oSearch->GetShowObsoleteData()) - { - foreach ($oSearch->GetSelectedClasses() as $sAlias => $sClass) - { - if (MetaModel::IsObsoletable($sClass)) - { - $oNotObsolete = new BinaryExpression(new FieldExpression('obsolescence_flag', $sAlias), '=', new ScalarExpression(0)); - $this->m_oConditionExpr = $this->m_oConditionExpr->LogAnd($oNotObsolete); - } - } - } - $this->m_aSelectExpr = is_null($aSelectExpr) ? array() : $aSelectExpr; - $this->m_aGroupByExpr = $aGroupByExpr; - $this->m_aJoinFields = array(); - - $this->m_aClassIds = array(); - foreach ($oSearch->GetJoinedClasses() as $sClassAlias => $sClass) - { - $this->m_aClassIds[$sClassAlias] = new FieldExpression('id', $sClassAlias); - } - } - - public function GetSelect() - { - return $this->m_aSelectExpr; - } - - public function GetGroupBy() - { - return $this->m_aGroupByExpr; - } - - public function GetCondition() - { - return $this->m_oConditionExpr; - } - - /** - * @return Expression|mixed - */ - public function PopJoinField() - { - return array_pop($this->m_aJoinFields); - } - - /** - * @param string $sAttAlias - * @param Expression $oExpression - */ - public function AddSelect($sAttAlias, Expression $oExpression) - { - $this->m_aSelectExpr[$sAttAlias] = $oExpression; - } - - /** - * @param Expression $oExpression - */ - public function AddCondition(Expression $oExpression) - { - $this->m_oConditionExpr = $this->m_oConditionExpr->LogAnd($oExpression); - } - - /** - * @param Expression $oExpression - */ - public function PushJoinField(Expression $oExpression) - { - array_push($this->m_aJoinFields, $oExpression); - } - - /** - * Get tables representing the queried objects - * Could be further optimized: when the first join is an outer join, then the rest can be omitted - * - * @param array $aTables - * - * @return array - */ - public function GetMandatoryTables(&$aTables = null) - { - if (is_null($aTables)) - { - $aTables = array(); - } - - foreach ($this->m_aClassIds as $sClass => $oExpression) - { - $oExpression->CollectUsedParents($aTables); - } - - return $aTables; - } - - public function GetUnresolvedFields($sAlias, &$aUnresolved) - { - $this->m_oConditionExpr->GetUnresolvedFields($sAlias, $aUnresolved); - foreach ($this->m_aSelectExpr as $sColAlias => $oExpr) - { - $oExpr->GetUnresolvedFields($sAlias, $aUnresolved); - } - if ($this->m_aGroupByExpr) - { - foreach ($this->m_aGroupByExpr as $sColAlias => $oExpr) - { - $oExpr->GetUnresolvedFields($sAlias, $aUnresolved); - } - } - foreach ($this->m_aJoinFields as $oExpression) - { - $oExpression->GetUnresolvedFields($sAlias, $aUnresolved); - } - } - - public function Translate($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true) - { - $this->m_oConditionExpr = $this->m_oConditionExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved); - foreach ($this->m_aSelectExpr as $sColAlias => $oExpr) - { - $this->m_aSelectExpr[$sColAlias] = $oExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved); - } - if ($this->m_aGroupByExpr) - { - foreach ($this->m_aGroupByExpr as $sColAlias => $oExpr) - { - $this->m_aGroupByExpr[$sColAlias] = $oExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved); - } - } - foreach ($this->m_aJoinFields as $index => $oExpression) - { - $this->m_aJoinFields[$index] = $oExpression->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved); - } - - foreach ($this->m_aClassIds as $sClass => $oExpression) - { - $this->m_aClassIds[$sClass] = $oExpression->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved); - } - } - - public function RenameParam($sOldName, $sNewName) - { - $this->m_oConditionExpr->RenameParam($sOldName, $sNewName); - foreach ($this->m_aSelectExpr as $sColAlias => $oExpr) - { - $this->m_aSelectExpr[$sColAlias] = $oExpr->RenameParam($sOldName, $sNewName); - } - if ($this->m_aGroupByExpr) - { - foreach ($this->m_aGroupByExpr as $sColAlias => $oExpr) - { - $this->m_aGroupByExpr[$sColAlias] = $oExpr->RenameParam($sOldName, $sNewName); - } - } - foreach ($this->m_aJoinFields as $index => $oExpression) - { - $this->m_aJoinFields[$index] = $oExpression->RenameParam($sOldName, $sNewName); - } - } -} \ No newline at end of file diff --git a/core/log.class.inc.php b/core/log.class.inc.php index fe81358f42..19861ee77b 100644 --- a/core/log.class.inc.php +++ b/core/log.class.inc.php @@ -544,7 +544,14 @@ class LogChannels { public const APC = 'apc'; - public const CLI = 'CLI'; + /** + * @var string + * @since 3.0.1 N°4849 + * @since 2.7.7 N°4635 + */ + public const NOTIFICATIONS = 'notifications'; + + public const CLI = 'CLI'; /** * @var string @@ -704,6 +711,7 @@ abstract class LogAPI /** * @throws \ConfigException if log wrongly configured * @uses GetMinLogLevel + * @since 3.0.0 N°3731 */ final public static function IsLogLevelEnabled(string $sLevel, string $sChannel, string $sConfigKey = self::ENUM_CONFIG_PARAM_FILE): bool { diff --git a/core/metamodel.class.php b/core/metamodel.class.php index e975ae448c..c6a58a8d09 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -461,13 +461,13 @@ abstract class MetaModel $oStyle = self::$m_aClassParams[$sClass]['style']; $sIcon = $oStyle->GetIconAsAbsUrl(); } - if (strlen($sIcon) == 0) { + if (utils::IsNullOrEmptyString($sIcon)) { $sParentClass = self::GetParentPersistentClass($sClass); if (strlen($sParentClass) > 0) { return self::GetClassIcon($sParentClass, $bImgTag, $sMoreStyles); } } - $sIcon = str_replace('/modules/', '/env-'.self::$m_sEnvironment.'/', $sIcon); // Support of pre-2.0 modules + $sIcon = str_replace('/modules/', '/env-'.self::$m_sEnvironment.'/', $sIcon ?? ''); // Support of pre-2.0 modules if ($bImgTag && ($sIcon != '')) { $sIcon = ""; } @@ -494,7 +494,7 @@ abstract class MetaModel $oStyle = new ormStyle("ibo-class-style--$sClass", "ibo-class-style-alt--$sClass"); } - if ((strlen($oStyle->GetMainColor()) > 0) && (strlen($oStyle->GetComplementaryColor()) > 0) && (strlen($oStyle->GetIconAsRelPath()) > 0)) { + if (utils::IsNotNullOrEmptyString($oStyle->GetMainColor()) && utils::IsNotNullOrEmptyString($oStyle->GetComplementaryColor()) && utils::IsNotNullOrEmptyString($oStyle->GetIconAsRelPath())) { // all the parameters are set, no need to search in the parent classes return $oStyle; } @@ -504,18 +504,18 @@ abstract class MetaModel while (strlen($sParentClass) > 0) { $oParentStyle = self::GetClassStyle($sParentClass); if (!is_null($oParentStyle)) { - if (strlen($oStyle->GetMainColor()) == 0) { + if (utils::IsNullOrEmptyString($oStyle->GetMainColor())) { $oStyle->SetMainColor($oParentStyle->GetMainColor()); $oStyle->SetStyleClass($oParentStyle->GetStyleClass()); } - if (strlen($oStyle->GetComplementaryColor()) == 0) { + if (utils::IsNullOrEmptyString($oStyle->GetComplementaryColor())) { $oStyle->SetComplementaryColor($oParentStyle->GetComplementaryColor()); $oStyle->SetAltStyleClass($oParentStyle->GetAltStyleClass()); } - if (strlen($oStyle->GetIconAsRelPath()) == 0) { + if (utils::IsNullOrEmptyString($oStyle->GetIconAsRelPath())) { $oStyle->SetIcon($oParentStyle->GetIconAsRelPath()); } - if ((strlen($oStyle->GetMainColor()) > 0) && (strlen($oStyle->GetComplementaryColor()) > 0) && (strlen($oStyle->GetIconAsRelPath()) > 0)) { + if (utils::IsNotNullOrEmptyString($oStyle->GetMainColor()) && utils::IsNotNullOrEmptyString($oStyle->GetComplementaryColor()) && utils::IsNotNullOrEmptyString($oStyle->GetIconAsRelPath())) { // all the parameters are set, no need to search in the parent classes return $oStyle; } @@ -523,7 +523,7 @@ abstract class MetaModel $sParentClass = self::GetParentPersistentClass($sParentClass); } - if ((strlen($oStyle->GetMainColor()) == 0) && (strlen($oStyle->GetComplementaryColor()) == 0) && (strlen($oStyle->GetIconAsRelPath()) == 0)) { + if (utils::IsNullOrEmptyString($oStyle->GetMainColor()) && utils::IsNullOrEmptyString($oStyle->GetComplementaryColor()) && utils::IsNullOrEmptyString($oStyle->GetIconAsRelPath())) { return null; } @@ -1119,9 +1119,7 @@ abstract class MetaModel return self::$m_aAttribOrigins[$sClass][$sAttCode]; } - /** - * @deprecated do not use : dead code, will be removed in the future - * + /** * * @param string $sClass * @param string $sAttCode * @@ -1130,9 +1128,11 @@ abstract class MetaModel */ final public static function GetFilterCodeOrigin($sClass, $sAttCode) { - self::_check_subclass($sClass); + if ($sAttCode == 'id') { + return MetaModel::GetRootClass($sClass); + } - return self::$m_aFilterOrigins[$sClass][$sAttCode]; + return MetaModel::GetAttributeOrigin($sClass, self::$m_aFilterAttribList[$sClass][$sAttCode]); } /** @@ -1481,7 +1481,6 @@ abstract class MetaModel } /** - * @deprecated do not use : dead code, will be removed in the future * * @param string $sClass * @@ -1490,11 +1489,9 @@ abstract class MetaModel */ final public static function GetFiltersList($sClass) { - // cannot notify depreciation for now as this is still MASSIVELY used in iTop core ! - //DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future'); self::_check_subclass($sClass); - return array_keys(self::$m_aFilterDefs[$sClass]); + return array_keys(self::$m_aFilterAttribList[$sClass]); } /** @@ -1564,9 +1561,7 @@ abstract class MetaModel $oKeyAttDef = MetaModel::GetAttributeDef($sClass, $sExtKeyAttCode); $sRemoteClass = $oKeyAttDef->GetTargetClass(); $bRes = MetaModel::IsValidAttCode($sRemoteClass, $sRemoteAttCode, true); - } - else - { + } else { $bRes = false; } } @@ -1591,7 +1586,6 @@ abstract class MetaModel } /** - * @deprecated do not use : dead code, will be removed in the future * * @param string $sClass * @param string $sFilterCode @@ -1600,13 +1594,11 @@ abstract class MetaModel */ final public static function IsValidFilterCode($sClass, $sFilterCode) { - // cannot notify depreciation for now as this is still MASSIVELY used in iTop core ! - //DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future'); - if (!array_key_exists($sClass, self::$m_aFilterDefs)) { + if (!array_key_exists($sClass, self::$m_aFilterAttribList)) { return false; } - return (array_key_exists($sFilterCode, self::$m_aFilterDefs[$sClass])); + return (array_key_exists($sFilterCode, self::$m_aFilterAttribList[$sClass])); } /** @@ -1880,31 +1872,21 @@ abstract class MetaModel public static function GetDescription($sClass, $sAttCode) { $oAttDef = self::GetAttributeDef($sClass, $sAttCode); - if ($oAttDef) - { + if ($oAttDef) { return $oAttDef->GetDescription(); } + return ""; } /** - * Filters of a given class may contain filters defined in a parent class - * - Some filters are a copy of the definition - * - Some filters correspond to the upper class table definition (compound objects) - * (see also attributes definition) - * - * @deprecated do not use : dead code, will be removed in the future - * @var array array of ("classname" => array filterdef) + * @var array array of (FilterCode => AttributeCode) */ - private static $m_aFilterDefs = array(); - /** - * @deprecated do not use : dead code, will be removed in the future - * @var array array of ("classname" => array of ("attcode"=>"sourceclass")) - */ - private static $m_aFilterOrigins = array(); + private static $m_aFilterAttribList = array(); /** - * @deprecated do not use : dead code, will be removed in the future + * @deprecated 3.0.0 do not use : dead code, will be removed in the future N°4690 - Deprecate "FilterCodes" + * instead of array_keys(MetaModel::GetClassFilterDefs($sClass)); use MetaModel::GetFiltersList($sClass) * * @param string $sClass * @@ -1914,34 +1896,25 @@ abstract class MetaModel public static function GetClassFilterDefs($sClass) { // cannot notify depreciation for now as this is still MASSIVELY used in iTop core ! - //DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future'); - self::_check_subclass($sClass); + DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use MetaModel::GetClassFilterDefs: dead code, will be removed in the future. Use MetaModel::GetFiltersList or MetaModel::GetFiltersAttributes'); - return self::$m_aFilterDefs[$sClass]; + return self::$m_aFilterAttribList[$sClass]; } /** - * @deprecated do not use : dead code, will be removed in the future * * @param string $sClass - * @param string $sFilterCode * - * @return mixed + * @return array ($sFilterCode=>$sAttributeCode) + id=>id * @throws \CoreException */ - final public static function GetClassFilterDef($sClass, $sFilterCode) + public static function GetFilterAttribList($sClass) { - DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future'); - self::_check_subclass($sClass); - if (!array_key_exists($sFilterCode, self::$m_aFilterDefs[$sClass])) { - throw new CoreException("Unknown filter code '$sFilterCode' for class '$sClass'"); - } - - return self::$m_aFilterDefs[$sClass][$sFilterCode]; + return self::$m_aFilterAttribList[$sClass]; } /** - * @deprecated do not use : dead code, will be removed in the future + * @deprecated 3.0.0 do not use : dead code, will be removed in the future use GetLabel instead N°4690 - Deprecate "FilterCodes" * * @param string $sClass * @param string $sFilterCode @@ -1951,103 +1924,9 @@ abstract class MetaModel */ public static function GetFilterLabel($sClass, $sFilterCode) { - DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future'); - $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); - if ($oFilter) { - return $oFilter->GetLabel(); - } + DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use MetaModel::GetFilterLabel : dead code, will be removed in the future. Use MetaModel::GetLabel instead'); - return ""; - } - - /** - * @deprecated do not use : dead code, will be removed in the future - * @param string $sClass - * @param string $sFilterCode - * - * @return string - * @throws \CoreException - */ - public static function GetFilterDescription($sClass, $sFilterCode) - { - DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future'); - $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); - if ($oFilter) { - return $oFilter->GetDescription(); - } - - return ""; - } - - /** - * @deprecated do not use : dead code, will be removed in the future - * @param string $sClass - * @param string $sFilterCode - * - * @return array - * @throws \CoreException - */ - public static function GetFilterOperators($sClass, $sFilterCode) - { - DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future'); - $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); - if ($oFilter) { - return $oFilter->GetOperators(); - } - - return array(); - } - - /** - * @deprecated do not use : dead code, will be removed in the future - * @param string $sClass - * @param string $sFilterCode - * - * @return array - * @throws \CoreException - */ - public static function GetFilterLooseOperator($sClass, $sFilterCode) - { - DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future'); - $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); - if ($oFilter) { - return $oFilter->GetLooseOperator(); - } - - return array(); - } - - /** - * @deprecated do not use : dead code, will be removed in the future - * @param string $sClass - * @param string $sFilterCode - * @param string $sOpCode - * - * @return string - * @throws \CoreException - */ - public static function GetFilterOpDescription($sClass, $sFilterCode, $sOpCode) - { - DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future'); - $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); - if ($oFilter) { - return $oFilter->GetOpDescription($sOpCode); - } - - return ""; - } - - /** - * @deprecated do not use : dead code, will be removed in the future - * @param string $sFilterCode - * - * @return string - */ - public static function GetFilterHTMLInput($sFilterCode) - { - DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future'); - - return ""; + return this::GetLabel($sClass, $sFilterCode); } /** @@ -2367,17 +2246,14 @@ abstract class MetaModel $aNeighbourData['sFromClass'] = $aNeighbourData['sDefinedInClass']; try { - if (strlen($aNeighbourData['sQueryDown']) == 0) - { + if (Utils::StrLen($aNeighbourData['sQueryDown']) == 0) { $oAttDef = self::GetAttributeDef($sClass, $aNeighbourData['sAttribute']); - if ($oAttDef instanceof AttributeExternalKey) - { + if ($oAttDef instanceof AttributeExternalKey) { $sTargetClass = $oAttDef->GetTargetClass(); $aNeighbourData['sToClass'] = $sTargetClass; $aNeighbourData['sQueryDown'] = 'SELECT '.$sTargetClass.' AS o WHERE o.id = :this->'.$aNeighbourData['sAttribute']; $aNeighbourData['sQueryUp'] = 'SELECT '.$aNeighbourData['sFromClass'].' AS o WHERE o.'.$aNeighbourData['sAttribute'].' = :this->id'; - } - elseif ($oAttDef instanceof AttributeLinkedSet) + } elseif ($oAttDef instanceof AttributeLinkedSet) { $sLinkedClass = $oAttDef->GetLinkedClass(); $sExtKeyToMe = $oAttDef->GetExtKeyToMe(); @@ -2889,6 +2765,8 @@ abstract class MetaModel } /** + * @deprecated 3.1.0 use GetAllowedValues_att N°4690 - Deprecate "FilterCodes" + * * @param string $sClass * @param string $sFltCode * @param array $aArgs @@ -2899,10 +2777,12 @@ abstract class MetaModel */ public static function GetAllowedValues_flt($sClass, $sFltCode, $aArgs = array(), $sContains = '') { - $oFltDef = self::GetClassFilterDef($sClass, $sFltCode); - return $oFltDef->GetAllowedValues($aArgs, $sContains); + DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use MetaModel::GetAllowedValues_flt: dead code, will be removed in the future. Use MetaModel::GetAllowedValues'); + + return self::GetAllowedValues_att($sClass, $sFltCode); } + /** * @param string $sClass * @param string $sAttCode @@ -2969,17 +2849,14 @@ abstract class MetaModel private static function AddMagicAttribute(AttributeDefinition $oAttribute, $sTargetClass, $sOriginClass = null) { $sCode = $oAttribute->GetCode(); - if (is_null($sOriginClass)) - { + if (is_null($sOriginClass)) { $sOriginClass = $sTargetClass; } $oAttribute->SetHostClass($sTargetClass); self::$m_aAttribDefs[$sTargetClass][$sCode] = $oAttribute; self::$m_aAttribOrigins[$sTargetClass][$sCode] = $sOriginClass; - $oFlt = new FilterFromAttribute($oAttribute); - self::$m_aFilterDefs[$sTargetClass][$sCode] = $oFlt; - self::$m_aFilterOrigins[$sTargetClass][$sCode] = $sOriginClass; + self::$m_aFilterAttribList[$sTargetClass][$sCode] = $sCode; } /** @@ -3171,44 +3048,38 @@ abstract class MetaModel self::$m_aClassParams[$sRootClass]["db_finalclass_field"] = 'finalclass'; } $oClassAtt = new AttributeFinalClass('finalclass', array( - "sql" => $sDbFinalClassField, - "default_value" => $sRootClass, + "sql" => $sDbFinalClassField, + "default_value" => $sRootClass, "is_null_allowed" => false, - "depends_on" => array() + "depends_on" => array(), )); self::AddMagicAttribute($oClassAtt, $sRootClass); $bObsoletable = array_key_exists($sRootClass, $aObsoletableRootClasses); - if ($bObsoletable && is_null(self::$m_aClassParams[$sRootClass]['obsolescence_expression'])) - { + if ($bObsoletable && is_null(self::$m_aClassParams[$sRootClass]['obsolescence_expression'])) { self::$m_aClassParams[$sRootClass]['obsolescence_expression'] = '0'; } - foreach(self::EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_EXCLUDETOP) as $sChildClass) - { - if (array_key_exists('finalclass', self::$m_aAttribDefs[$sChildClass])) - { + foreach (self::EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_EXCLUDETOP) as $sChildClass) { + if (array_key_exists('finalclass', self::$m_aAttribDefs[$sChildClass])) { throw new CoreException("Class $sChildClass, 'finalclass' is a reserved keyword, it cannot be used as an attribute code"); } - if (array_key_exists('finalclass', self::$m_aFilterDefs[$sChildClass])) - { + if (array_key_exists('finalclass', self::$m_aFilterAttribList[$sChildClass])) { throw new CoreException("Class $sChildClass, 'finalclass' is a reserved keyword, it cannot be used as a filter code"); } $oCloned = clone $oClassAtt; $oCloned->SetFixedValue($sChildClass); self::AddMagicAttribute($oCloned, $sChildClass, $sRootClass); - if ($bObsoletable && is_null(self::$m_aClassParams[$sChildClass]['obsolescence_expression'])) - { + if ($bObsoletable && is_null(self::$m_aClassParams[$sChildClass]['obsolescence_expression'])) { self::$m_aClassParams[$sChildClass]['obsolescence_expression'] = '0'; } } } // Add magic attributes to the classes - foreach(self::GetClasses() as $sClass) - { + foreach (self::GetClasses() as $sClass) { $sRootClass = self::$m_aRootClasses[$sClass]; // Create the friendly name attribute @@ -3216,40 +3087,35 @@ abstract class MetaModel $oFriendlyName = new AttributeFriendlyName($sFriendlyNameAttCode); self::AddMagicAttribute($oFriendlyName, $sClass); - if (self::$m_aClassParams[$sClass]["archive_root"]) - { + if (self::$m_aClassParams[$sClass]["archive_root"]) { // Create archive attributes on top the archivable hierarchy $oArchiveFlag = new AttributeArchiveFlag('archive_flag'); self::AddMagicAttribute($oArchiveFlag, $sClass); $oArchiveDate = new AttributeArchiveDate('archive_date', array('magic' => true, "allowed_values" => null, "sql" => 'archive_date', "default_value" => '', "is_null_allowed" => true, "depends_on" => array())); self::AddMagicAttribute($oArchiveDate, $sClass); - } - elseif (self::$m_aClassParams[$sClass]["archive"]) - { + } elseif (self::$m_aClassParams[$sClass]["archive"]) { $sArchiveRoot = self::$m_aClassParams[$sClass]['archive_root_class']; // Inherit archive attributes $oArchiveFlag = clone self::$m_aAttribDefs[$sArchiveRoot]['archive_flag']; $oArchiveFlag->SetHostClass($sClass); self::$m_aAttribDefs[$sClass]['archive_flag'] = $oArchiveFlag; self::$m_aAttribOrigins[$sClass]['archive_flag'] = $sArchiveRoot; + $oArchiveDate = clone self::$m_aAttribDefs[$sArchiveRoot]['archive_date']; $oArchiveDate->SetHostClass($sClass); self::$m_aAttribDefs[$sClass]['archive_date'] = $oArchiveDate; self::$m_aAttribOrigins[$sClass]['archive_date'] = $sArchiveRoot; + } - if (!is_null(self::$m_aClassParams[$sClass]['obsolescence_expression'])) - { + if (!is_null(self::$m_aClassParams[$sClass]['obsolescence_expression'])) { $oObsolescenceFlag = new AttributeObsolescenceFlag('obsolescence_flag'); self::AddMagicAttribute($oObsolescenceFlag, $sClass); - if (self::$m_aRootClasses[$sClass] == $sClass) - { + if (self::$m_aRootClasses[$sClass] == $sClass) { $oObsolescenceDate = new AttributeObsolescenceDate('obsolescence_date', array('magic' => true, "allowed_values" => null, "sql" => 'obsolescence_date', "default_value" => '', "is_null_allowed" => true, "depends_on" => array())); self::AddMagicAttribute($oObsolescenceDate, $sClass); - } - else - { + } else { $oObsolescenceDate = clone self::$m_aAttribDefs[$sRootClass]['obsolescence_date']; $oObsolescenceDate->SetHostClass($sClass); self::$m_aAttribDefs[$sClass]['obsolescence_date'] = $oObsolescenceDate; @@ -3261,40 +3127,24 @@ abstract class MetaModel // Prepare external fields and filters // Add final class to external keys // Add magic attributes to external keys (finalclass, friendlyname, archive_flag, obsolescence_flag) - foreach(self::GetClasses() as $sClass) - { - foreach(self::$m_aAttribDefs[$sClass] as $sAttCode => $oAttDef) - { + foreach (self::GetClasses() as $sClass) { + foreach (self::$m_aAttribDefs[$sClass] as $sAttCode => $oAttDef) { // Compute the filter codes // - foreach($oAttDef->GetFilterDefinitions() as $sFilterCode => $oFilterDef) - { - self::$m_aFilterDefs[$sClass][$sFilterCode] = $oFilterDef; - - if ($oAttDef->IsExternalField()) - { - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - $oKeyDef = self::GetAttributeDef($sClass, $sKeyAttCode); - self::$m_aFilterOrigins[$sClass][$sFilterCode] = $oKeyDef->GetTargetClass(); - } - else - { - self::$m_aFilterOrigins[$sClass][$sFilterCode] = self::$m_aAttribOrigins[$sClass][$sAttCode]; - } + foreach ($oAttDef->GetFilterDefinitions() as $sFilterCode => $sCode) { + self::$m_aFilterAttribList[$sClass][$sFilterCode] = $sCode; } // Compute the fields that will be used to display a pointer to another object // - if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) - { + if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) { // oAttDef is either // - an external KEY / FIELD (direct), // - an external field pointing to an external KEY / FIELD // - an external field pointing to an external field pointing to.... $sRemoteClass = $oAttDef->GetTargetClass(EXTKEY_ABSOLUTE); - if ($oAttDef->IsExternalField()) - { + if ($oAttDef->IsExternalField()) { // This is a key, but the value comes from elsewhere // Create an external field pointing to the remote friendly name attribute $sKeyAttCode = $oAttDef->GetKeyAttCode(); @@ -3302,24 +3152,21 @@ abstract class MetaModel $sFriendlyNameAttCode = $sAttCode.'_friendlyname'; $oFriendlyName = new AttributeExternalField($sFriendlyNameAttCode, array("allowed_values" => null, "extkey_attcode" => $sKeyAttCode, "target_attcode" => $sRemoteAttCode, "depends_on" => array())); self::AddMagicAttribute($oFriendlyName, $sClass, self::$m_aAttribOrigins[$sClass][$sKeyAttCode]); - } - else - { + } else { // Create the friendly name attribute $sFriendlyNameAttCode = $sAttCode.'_friendlyname'; $oFriendlyName = new AttributeExternalField($sFriendlyNameAttCode, array('allowed_values' => null, 'extkey_attcode' => $sAttCode, "target_attcode" => 'friendlyname', 'depends_on' => array())); self::AddMagicAttribute($oFriendlyName, $sClass, self::$m_aAttribOrigins[$sClass][$sAttCode]); - if (self::HasChildrenClasses($sRemoteClass)) - { + if (self::HasChildrenClasses($sRemoteClass)) { // First, create an external field attribute, that gets the final class $sClassRecallAttCode = $sAttCode.'_finalclass_recall'; $oClassRecall = new AttributeExternalField($sClassRecallAttCode, array( - "allowed_values" => null, - "extkey_attcode" => $sAttCode, - "target_attcode" => "finalclass", + "allowed_values" => null, + "extkey_attcode" => $sAttCode, + "target_attcode" => "finalclass", "is_null_allowed" => true, - "depends_on" => array() + "depends_on" => array(), )); self::AddMagicAttribute($oClassRecall, $sClass, self::$m_aAttribOrigins[$sClass][$sAttCode]); @@ -3350,18 +3197,14 @@ abstract class MetaModel } } - if (self::IsArchivable($sRemoteClass)) - { + if (self::IsArchivable($sRemoteClass)) { $sCode = $sAttCode.'_archive_flag'; - if ($oAttDef->IsExternalField()) - { + if ($oAttDef->IsExternalField()) { // This is a key, but the value comes from elsewhere // Create an external field pointing to the remote attribute $sKeyAttCode = $oAttDef->GetKeyAttCode(); $sRemoteAttCode = $oAttDef->GetExtAttCode().'_archive_flag'; - } - else - { + } else { $sKeyAttCode = $sAttCode; $sRemoteAttCode = 'archive_flag'; } @@ -3369,18 +3212,14 @@ abstract class MetaModel self::AddMagicAttribute($oMagic, $sClass, self::$m_aAttribOrigins[$sClass][$sKeyAttCode]); } - if (self::IsObsoletable($sRemoteClass)) - { + if (self::IsObsoletable($sRemoteClass)) { $sCode = $sAttCode.'_obsolescence_flag'; - if ($oAttDef->IsExternalField()) - { + if ($oAttDef->IsExternalField()) { // This is a key, but the value comes from elsewhere // Create an external field pointing to the remote attribute $sKeyAttCode = $oAttDef->GetKeyAttCode(); $sRemoteAttCode = $oAttDef->GetExtAttCode().'_obsolescence_flag'; - } - else - { + } else { $sKeyAttCode = $sAttCode; $sRemoteAttCode = 'obsolescence_flag'; } @@ -3388,11 +3227,9 @@ abstract class MetaModel self::AddMagicAttribute($oMagic, $sClass, self::$m_aAttribOrigins[$sClass][$sKeyAttCode]); } } - if ($oAttDef instanceof AttributeMetaEnum) - { + if ($oAttDef instanceof AttributeMetaEnum) { $aMappingData = $oAttDef->GetMapRule($sClass); - if ($aMappingData != null) - { + if ($aMappingData != null) { $sEnumAttCode = $aMappingData['attcode']; self::$m_aEnumToMeta[$sClass][$sEnumAttCode][$sAttCode] = $oAttDef; } @@ -3401,17 +3238,10 @@ abstract class MetaModel // Add a 'id' filter // - if (array_key_exists('id', self::$m_aAttribDefs[$sClass])) - { + if (array_key_exists('id', self::$m_aAttribDefs[$sClass])) { throw new CoreException("Class $sClass, 'id' is a reserved keyword, it cannot be used as an attribute code"); } - if (array_key_exists('id', self::$m_aFilterDefs[$sClass])) - { - throw new CoreException("Class $sClass, 'id' is a reserved keyword, it cannot be used as a filter code"); - } - $oFilter = new FilterPrivateKey('id', array('id_field' => self::DBGetKey($sClass))); - self::$m_aFilterDefs[$sClass]['id'] = $oFilter; - self::$m_aFilterOrigins[$sClass]['id'] = $sClass; + self::$m_aFilterAttribList[$sClass]['id'] = 'id'; } } @@ -3571,8 +3401,7 @@ abstract class MetaModel self::$m_aAttribDefs[$sClass] = array(); self::$m_aAttribOrigins[$sClass] = array(); - self::$m_aFilterDefs[$sClass] = array(); - self::$m_aFilterOrigins[$sClass] = array(); + self::$m_aFilterAttribList[$sClass] = array(); } /** @@ -3602,25 +3431,20 @@ abstract class MetaModel public static function Init_InheritAttributes($sSourceClass = null) { $sTargetClass = self::GetCallersPHPClass("Init"); - if (empty($sSourceClass)) - { + if (empty($sSourceClass)) { // Default: inherit from parent class $sSourceClass = self::GetParentPersistentClass($sTargetClass); - if (empty($sSourceClass)) - { + if (empty($sSourceClass)) { return; } // no attributes for the mother of all classes } - if (isset(self::$m_aAttribDefs[$sSourceClass])) - { - if (!isset(self::$m_aAttribDefs[$sTargetClass])) - { + if (isset(self::$m_aAttribDefs[$sSourceClass])) { + if (!isset(self::$m_aAttribDefs[$sTargetClass])) { self::$m_aAttribDefs[$sTargetClass] = array(); self::$m_aAttribOrigins[$sTargetClass] = array(); } self::$m_aAttribDefs[$sTargetClass] = self::object_array_mergeclone(self::$m_aAttribDefs[$sTargetClass], self::$m_aAttribDefs[$sSourceClass]); - foreach(self::$m_aAttribDefs[$sTargetClass] as $sAttCode => $oAttDef) - { + foreach (self::$m_aAttribDefs[$sTargetClass] as $sAttCode => $oAttDef) { $oAttDef->SetHostClass($sTargetClass); } self::$m_aAttribOrigins[$sTargetClass] = array_merge(self::$m_aAttribOrigins[$sTargetClass], self::$m_aAttribOrigins[$sSourceClass]); @@ -3675,22 +3499,18 @@ abstract class MetaModel */ public static function Init_AddAttribute(AttributeDefinition $oAtt, $sTargetClass = null) { - if (!$sTargetClass) - { + if (!$sTargetClass) { $sTargetClass = self::GetCallersPHPClass("Init"); } $sAttCode = $oAtt->GetCode(); - if ($sAttCode == 'finalclass') - { + if ($sAttCode == 'finalclass') { throw new Exception("Declaration of $sTargetClass: using the reserved keyword '$sAttCode' in attribute declaration"); } - if ($sAttCode == 'friendlyname') - { + if ($sAttCode == 'friendlyname') { throw new Exception("Declaration of $sTargetClass: using the reserved keyword '$sAttCode' in attribute declaration"); } - if (array_key_exists($sAttCode, self::$m_aAttribDefs[$sTargetClass])) - { + if (array_key_exists($sAttCode, self::$m_aAttribDefs[$sTargetClass])) { throw new Exception("Declaration of $sTargetClass: attempting to redeclare the inherited attribute '$sAttCode', originally declared in ".self::$m_aAttribOrigins[$sTargetClass][$sAttCode]); } @@ -3727,6 +3547,7 @@ abstract class MetaModel { // The corresponding external key has already been ignored self::$m_aIgnoredAttributes[$sTargetClass][$oAtt->GetCode()] = self::$m_aIgnoredAttributes[$sTargetClass][$sExtKeyAttCode]; + return; } //TODO Check if the target attribute is still there @@ -4989,18 +4810,6 @@ abstract class MetaModel } } } - foreach(self::GetClassFilterDefs($sClass) as $sFltCode => $oFilterDef) - { - if (method_exists($oFilterDef, '__GetRefAttribute')) - { - $oAttDef = $oFilterDef->__GetRefAttribute(); - if (!self::IsValidAttCode($sClass, $oAttDef->GetCode())) - { - $aErrors[$sClass][] = "Wrong attribute code '".$oAttDef->GetCode()."' (wrong class) for the \"basic\" filter $sFltCode"; - $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; - } - } - } // Lifecycle // @@ -5188,27 +4997,23 @@ abstract class MetaModel */ public static function DBShowApplyForm($sRepairUrl, $sSQLStatementArgName, $aSQLFixes) { - if (empty($sRepairUrl)) - { + if (empty($sRepairUrl)) { return; } // By design, some queries might be blank, we have to ignore them $aCleanFixes = array(); - foreach($aSQLFixes as $sSQLFix) - { - if (!empty($sSQLFix)) - { + foreach ($aSQLFixes as $sSQLFix) { + if (!empty($sSQLFix)) { $aCleanFixes[] = $sSQLFix; } } - if (count($aCleanFixes) == 0) - { + if (count($aCleanFixes) == 0) { return; } echo "
    \n"; - echo " \n"; + echo " \n"; echo " \n"; echo "
    \n"; } @@ -5454,24 +5259,21 @@ abstract class MetaModel $sRes = ''; $sRes .= "// Dictionnay conventions\n"; - $sRes .= htmlentities("// Class:\n", ENT_QUOTES, 'UTF-8'); - $sRes .= htmlentities("// Class:+\n", ENT_QUOTES, 'UTF-8'); - $sRes .= htmlentities("// Class:/Attribute:\n", ENT_QUOTES, 'UTF-8'); - $sRes .= htmlentities("// Class:/Attribute:+\n", ENT_QUOTES, 'UTF-8'); - $sRes .= htmlentities("// Class:/Attribute:/Value:\n", ENT_QUOTES, 'UTF-8'); - $sRes .= htmlentities("// Class:/Attribute:/Value:+\n", ENT_QUOTES, 'UTF-8'); - $sRes .= htmlentities("// Class:/Stimulus:\n", ENT_QUOTES, 'UTF-8'); - $sRes .= htmlentities("// Class:/Stimulus:+\n", ENT_QUOTES, 'UTF-8'); + $sRes .= utils::EscapeHtml("// Class:\n"); + $sRes .= utils::EscapeHtml("// Class:+\n"); + $sRes .= utils::EscapeHtml("// Class:/Attribute:\n"); + $sRes .= utils::EscapeHtml("// Class:/Attribute:+\n"); + $sRes .= utils::EscapeHtml("// Class:/Attribute:/Value:\n"); + $sRes .= utils::EscapeHtml("// Class:/Attribute:/Value:+\n"); + $sRes .= utils::EscapeHtml("// Class:/Stimulus:\n"); + $sRes .= utils::EscapeHtml("// Class:/Stimulus:+\n"); $sRes .= "\n"; // Note: I did not use EnumCategories(), because a given class maybe found in several categories // Need to invent the "module", to characterize the origins of a class - if (strlen($sModules) == 0) - { + if (strlen($sModules) == 0) { $aModules = array('bizmodel', 'core/cmdb', 'gui', 'application', 'addon/userrights'); - } - else - { + } else { $aModules = explode(', ', $sModules); } @@ -5479,17 +5281,14 @@ abstract class MetaModel $sRes .= "// Note: The classes have been grouped by categories: ".implode(', ', $aModules)."\n"; $sRes .= "//////////////////////////////////////////////////////////////////////\n"; - foreach($aModules as $sCategory) - { + foreach ($aModules as $sCategory) { $sRes .= "//////////////////////////////////////////////////////////////////////\n"; $sRes .= "// Classes in '$sCategory'\n"; $sRes .= "//////////////////////////////////////////////////////////////////////\n"; $sRes .= "//\n"; $sRes .= "\n"; - foreach(self::GetClasses($sCategory) as $sClass) - { - if (!self::HasTable($sClass)) - { + foreach (self::GetClasses($sCategory) as $sClass) { + if (!self::HasTable($sClass)) { continue; } @@ -6619,16 +6418,14 @@ abstract class MetaModel // classes have to be derived from cmdbabstract (to be editable in the UI) require_once(APPROOT.'/application/cmdbabstract.class.inc.php'); - if (!defined('MODULESROOT')) - { + if (!defined('MODULESROOT')) { define('MODULESROOT', APPROOT.'env-'.self::$m_sEnvironment.'/'); } require_once(APPROOT.'core/autoload.php'); require_once(APPROOT.'env-'.self::$m_sEnvironment.'/autoload.php'); - foreach(self::$m_oConfig->GetAddons() as $sModule => $sToInclude) - { + foreach (self::$m_oConfig->GetAddons() as $sModule => $sToInclude) { self::IncludeModule($sToInclude, 'addons'); } @@ -6636,16 +6433,14 @@ abstract class MetaModel $sTablePrefix = self::$m_oConfig->Get('db_subname'); $oKPI->ComputeAndReport('Load config'); - if (self::$m_bUseAPCCache) - { + if (self::$m_bUseAPCCache) { $oKPI = new ExecutionKPI(); // Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter // $sOqlAPCCacheId = 'itop-'.MetaModel::GetEnvironmentId().'-metamodel'; $result = apc_fetch($sOqlAPCCacheId); - if (is_array($result)) - { + if (is_array($result)) { // todo - verifier que toutes les classes mentionnees ici sont chargees dans InitClasses() self::$m_aExtensionClassNames = $result['m_aExtensionClassNames']; self::$m_Category2Class = $result['m_Category2Class']; @@ -6656,8 +6451,7 @@ abstract class MetaModel self::$m_aAttribDefs = $result['m_aAttribDefs']; self::$m_aAttribOrigins = $result['m_aAttribOrigins']; self::$m_aIgnoredAttributes = $result['m_aIgnoredAttributes']; - self::$m_aFilterDefs = $result['m_aFilterDefs']; - self::$m_aFilterOrigins = $result['m_aFilterOrigins']; + self::$m_aFilterAttribList = $result['m_aFilterList']; self::$m_aListInfos = $result['m_aListInfos']; self::$m_aListData = $result['m_aListData']; self::$m_aRelationInfos = $result['m_aRelationInfos']; @@ -6693,8 +6487,7 @@ abstract class MetaModel $aCache['m_aAttribDefs'] = self::$m_aAttribDefs; // array of ("classname" => array of attributes) $aCache['m_aAttribOrigins'] = self::$m_aAttribOrigins; // array of ("classname" => array of ("attcode"=>"sourceclass")) $aCache['m_aIgnoredAttributes'] = self::$m_aIgnoredAttributes; //array of ("classname" => array of ("attcode") - $aCache['m_aFilterDefs'] = self::$m_aFilterDefs; // array of ("classname" => array filterdef) - $aCache['m_aFilterOrigins'] = self::$m_aFilterOrigins; // array of ("classname" => array of ("attcode"=>"sourceclass")) + $aCache['m_aFilterList'] = self::$m_aFilterAttribList; // array of ("classname" => array filterdef) $aCache['m_aListInfos'] = self::$m_aListInfos; // array of ("listcode" => various info on the list, common to every classes) $aCache['m_aListData'] = self::$m_aListData; // array of ("classname" => array of "listcode" => list) $aCache['m_aRelationInfos'] = self::$m_aRelationInfos; // array of ("relcode" => various info on the list, common to every classes) @@ -7145,20 +6938,28 @@ abstract class MetaModel /** * @param string $sClass * @param string $sAttCode - * @param $value + * @param mixed $value * @param bool $bMustBeFoundUnique + * @param bool $bAllowAllData + * + * @return \DBObject if $bMustBeFoundUnique=true and no object or multiple objects found will throw a CoreException + * else will return null * - * @return \DBObject * @throws \CoreException - * @throws \Exception + * @throws \CoreUnexpectedValue + * @throws \MissingQueryArgument + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + * + * @since 2.7.7 Add new $bAllowAllData parameter */ - public static function GetObjectByColumn($sClass, $sAttCode, $value, $bMustBeFoundUnique = true) + public static function GetObjectByColumn($sClass, $sAttCode, $value, $bMustBeFoundUnique = true, $bAllowAllData = false) { - if (!isset(self::$m_aCacheObjectByColumn[$sClass][$sAttCode][$value])) - { + if (!isset(self::$m_aCacheObjectByColumn[$sClass][$sAttCode][$value])) { self::_check_subclass($sClass); $oObjSearch = new DBObjectSearch($sClass); + $oObjSearch->AllowAllData($bAllowAllData); $oObjSearch->AddCondition($sAttCode, $value, '='); $oSet = new DBObjectSet($oObjSearch); if ($oSet->Count() == 1) @@ -7211,30 +7012,26 @@ abstract class MetaModel */ public static function GetHyperLink($sTargetClass, $iKey) { - if ($iKey < 0) - { + if ($iKey < 0) { return "$sTargetClass: $iKey (invalid value)"; } $oObj = self::GetObject($sTargetClass, $iKey, false); - if (is_null($oObj)) - { + if (is_null($oObj)) { // Whatever we are looking for, the root class is the key to search for $sRootClass = self::GetRootClass($sTargetClass); $oSearch = DBObjectSearch::FromOQL('SELECT CMDBChangeOpDelete WHERE objclass = :objclass AND objkey = :objkey', array('objclass' => $sRootClass, 'objkey' => $iKey)); $oSet = new DBObjectSet($oSearch); $oRecord = $oSet->Fetch(); // An empty fname is obtained with iTop < 2.0 - if (is_null($oRecord) || (strlen(trim($oRecord->Get('fname'))) == 0)) - { + if (is_null($oRecord) || (strlen(trim($oRecord->Get('fname'))) == 0)) { $sName = Dict::Format('Core:UnknownObjectLabel', $sTargetClass, $iKey); $sTitle = Dict::S('Core:UnknownObjectTip'); - } - else - { + } else { $sName = $oRecord->Get('fname'); $sTitle = Dict::Format('Core:DeletedObjectTip', $oRecord->Get('date'), $oRecord->Get('userinfo')); } - return ''.htmlentities($sName, ENT_QUOTES, 'UTF-8').''; + + return ''.utils::EscapeHtml($sName).''; } return $oObj->GetHyperLink(); } @@ -7266,38 +7063,11 @@ abstract class MetaModel } /** - * Deletion of records, bypassing {@link DBObject::DBDelete} !!! - * It is NOT recommended to use this shortcut - * In particular, it will not work - * - if the class is not a final class - * - if the class has a hierarchical key (need to rebuild the indexes) - * - if the class overload DBDelete ! + * @internal * - * @deprecated do not use : dead code, will be removed in the future - * @experimental - * - * @param \DBObjectSearch $oFilter - * - * @throws \MySQLException - * @throws \MySQLHasGoneAwayException - * @todo: protect it against forbidden usages (in such a case, delete objects one by one) - * - */ - public static function BulkDelete(DBObjectSearch $oFilter) - { - DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future'); - $sSQL = $oFilter->MakeDeleteQuery(); - if (!self::DBIsReadOnly()) { - CMDBSource::Query($sSQL); - } - } - - /** - * @param DBObjectSearch $oFilter * @param array $aValues array of attcode => value + * @param DBObjectSearch $oFilter * - * @deprecated do not use : dead code, will be removed in the future - * @experimental * @return int Modified objects * @throws \MySQLException * @throws \MySQLHasGoneAwayException @@ -7503,14 +7273,11 @@ abstract class MetaModel $aSearches = array(); $aReplacements = array(); - foreach ($aParams as $sSearch => $replace) - { + foreach ($aParams as $sSearch => $replace) { // Some environment parameters are objects, we just need scalars - if (is_object($replace)) - { + if (is_object($replace)) { $iPos = strpos($sSearch, '->object()'); - if ($iPos !== false) - { + if ($iPos !== false) { // Expand the parameters for the object $sName = substr($sSearch, 0, $iPos); // Note: Capturing @@ -7518,63 +7285,67 @@ abstract class MetaModel // 2 - The arrow // 3 - The attribute code $aRegExps = array( - '/(\\$)'.$sName.'-(>|>)([^\\$]+)\\$/', // Support both syntaxes: $this->xxx$ or $this->xxx$ for HTML compatibility - '/(%24)'.$sName.'-(>|>)([^%24]+)%24/', // Support for urlencoded in HTML attributes (%20this->xxx%20) - ); - foreach($aRegExps as $sRegExp) - { - if(preg_match_all($sRegExp, $sInput, $aMatches)) - { - foreach($aMatches[3] as $idx => $sPlaceholderAttCode) - { - try - { - $sReplacement = $replace->GetForTemplate($sPlaceholderAttCode); - if($sReplacement !== null) - { - $aReplacements[] = $sReplacement; - $aSearches[] = $aMatches[1][$idx] . $sName . '-' . $aMatches[2][$idx] . $sPlaceholderAttCode . $aMatches[1][$idx]; - } - } - catch(Exception $e) - { - // No replacement will occur - } - } - } - } - } - else - { + '/(\\$)'.$sName.'-(>|>)([^\\$]+)\\$/', // Support both syntaxes: $this->xxx$ or $this->xxx$ for HTML compatibility + '/(%24)'.$sName.'-(>|>)([^%24]+)%24/', // Support for urlencoded in HTML attributes (%20this->xxx%20) + ); + foreach ($aRegExps as $sRegExp) { + if (preg_match_all($sRegExp, $sInput, $aMatches)) { + foreach ($aMatches[3] as $idx => $sPlaceholderAttCode) { + try { + $sReplacement = $replace->GetForTemplate($sPlaceholderAttCode); + if ($sReplacement !== null) { + $aReplacements[] = $sReplacement; + $aSearches[] = $aMatches[1][$idx].$sName.'-'.$aMatches[2][$idx].$sPlaceholderAttCode.$aMatches[1][$idx]; + } + } + catch (Exception $e) { + $aContext = [ + 'placeholder' => $sPlaceholderAttCode, + 'replace class' => get_class($replace), + ]; + if ($replace instanceof DBObject) { + $aContext['replace id'] = $replace->GetKey(); + } + IssueLog::Debug( + 'Invalid placeholder in notification, no replacement will occur!', + LogChannels::NOTIFICATIONS, + $aContext + ); + } + } + } + } + } else { continue; // Ignore this non-scalar value } - } - else - { + } else { $aRegExps = array( '/(\$)'.$sSearch.'\$/', // Support for regular placeholders (eg. $APP_URL$) '/(%24)'.$sSearch.'%24/', // Support for urlencoded in HTML attributes (eg. %24APP_URL%24) ); - foreach($aRegExps as $sRegExp) - { - if(preg_match_all($sRegExp, $sInput, $aMatches)) - { - foreach($aMatches[1] as $idx => $sDelimiter) - { - try - { - $aReplacements[] = (string) $replace; - $aSearches[] = $aMatches[1][$idx] . $sSearch . $aMatches[1][$idx]; + foreach ($aRegExps as $sRegExp) { + if (preg_match_all($sRegExp, $sInput, $aMatches)) { + foreach ($aMatches[1] as $idx => $sDelimiter) { + try { + $aReplacements[] = (string)$replace; + $aSearches[] = $aMatches[1][$idx].$sSearch.$aMatches[1][$idx]; } - catch(Exception $e) - { - // No replacement will occur + catch (Exception $e) { + IssueLog::Debug( + 'Invalid placeholder in notification, no replacement will occur !', + LogChannels::NOTIFICATIONS, + [ + 'placeholder' => $sPlaceholderAttCode, + 'replace' => $replace, + ] + ); } } } } } } + return str_replace($aSearches, $aReplacements, $sInput); } diff --git a/core/oql/expression.class.inc.php b/core/oql/expression.class.inc.php index 089a70e718..84cb6ac723 100644 --- a/core/oql/expression.class.inc.php +++ b/core/oql/expression.class.inc.php @@ -107,7 +107,7 @@ abstract class Expression { /** * recursive rendering * - * @deprecated use RenderExpression + * @deprecated 3.0.0 use RenderExpression * * @param array $aArgs used as input by default, or used as output if bRetrofitParams set to True * @param bool $bRetrofitParams @@ -118,7 +118,7 @@ abstract class Expression { public function Render(&$aArgs = null, $bRetrofitParams = false) { // cannot notify depreciation for now as this is still MASSIVELY used in iTop core ! - //DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use RenderExpression'); + DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use RenderExpression'); return $this->RenderExpression(false, $aArgs, $bRetrofitParams); } @@ -1765,6 +1765,7 @@ class FieldExpression extends UnaryExpression */ public function MakeValueLabel($oFilter, $sValue, $sDefault) { + $sAttCode = $this->GetName(); $sParentAlias = $this->GetParent(); @@ -2762,25 +2763,96 @@ class FunctionExpression extends Expression return $iRet; case 'DATE_FORMAT': - if (count($this->m_aArgs) != 2) - { + if (count($this->m_aArgs) != 2) { throw new \Exception("Function {$this->m_sVerb} requires 2 arguments"); } $oDate = new DateTime($this->m_aArgs[0]->Evaluate($aArgs)); - $sFormat = $this->m_aArgs[1]->Evaluate($aArgs); - $sFormat = str_replace( - array('%y', '%x', '%w', '%W', '%v', '%T', '%S', '%r', '%p', '%M', '%l', '%k', '%I', '%h', '%b', '%a', '%D', '%c', '%e', '%Y', '%d', '%m', '%H', '%i', '%s'), - array('y', 'o', 'w', 'l', 'W', 'H:i:s', 's', 'h:i:s A', 'A', 'F', 'g', 'H', 'h', 'h','M', 'D', 'jS', 'n', 'j', 'Y', 'd', 'm', 'H', 'i', 's'), - $sFormat); - if (preg_match('/%j/', $sFormat)) - { - $sFormat = str_replace('%j', date_format($oDate, 'z') + 1, $sFormat); - } - if (preg_match('/%[fUuVX]/', $sFormat)) - { + $sFormatForMysqlDateFormat = $this->m_aArgs[1]->Evaluate($aArgs); + + if (preg_match('/%[fUuVX]/', $sFormatForMysqlDateFormat)) { throw new NotYetEvaluatedExpression("Expression ".$this->RenderExpression().' cannot be evaluated (known limitation)'); } - $sRet = date_format($oDate, $sFormat); + + if (preg_match('/%j/', $sFormatForMysqlDateFormat)) { + $sFormatForMysqlDateFormat = str_replace('%j', 'z', $sFormatForMysqlDateFormat); + $sRet = date_format($oDate, $sFormatForMysqlDateFormat); + $sRet++; + /** @noinspection PhpUnnecessaryLocalVariableInspection */ + $sRet = str_pad($sRet, 3, '0', STR_PAD_LEFT); + + return $sRet; + } + + /** + * @var string[] $aFormatsForMysqlDateFormat + * @link https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-format + */ + //@formatter:off we want to keep every single item on its own line to ease comp between MySQL and PHP formats ! + $aFormatsForMysqlDateFormat = [ + '%y', + '%x', + '%w', + '%W', + '%v', + '%T', + '%S', + '%r', + '%p', + '%M', + '%l', + '%k', + '%I', + '%h', + '%b', + '%a', + '%D', + '%c', + '%e', + '%Y', + '%d', + '%m', + '%H', + '%i', + '%s' + ]; + //@formatter:on + /** + * @var string[] $aFormatsForPhpDateFormat + * @link https://www.php.net/manual/en/datetime.format.php + */ + //@formatter:off we want to keep every single item on its own line to ease comp between MySQL and PHP formats ! + $aFormatsForPhpDateFormat = [ + 'y', + 'o', + 'w', + 'l', + 'W', + 'H:i:s', + 's', + 'h:i:s A', + 'A', + 'F', + 'g', + 'G', + 'h', + 'h', + 'M', + 'D', + 'jS', + 'n', + 'j', + 'Y', + 'd', + 'm', + 'H', + 'i', + 's' + ]; + //@formatter:on + $sFormatForPhpDateFormat = str_replace($aFormatsForMysqlDateFormat, $aFormatsForPhpDateFormat, $sFormatForMysqlDateFormat); + /** @noinspection PhpUnnecessaryLocalVariableInspection */ + $sRet = date_format($oDate, $sFormatForPhpDateFormat); + return $sRet; case 'TO_DAYS': @@ -2978,8 +3050,7 @@ class FunctionExpression extends Expression public function MakeValueLabel($oFilter, $sValue, $sDefault) { static $aWeekDayToString = null; - if (is_null($aWeekDayToString)) - { + if (is_null($aWeekDayToString)) { // Init the correspondance table $aWeekDayToString = array( 0 => Dict::S('DayOfWeek-Sunday'), @@ -2988,7 +3059,7 @@ class FunctionExpression extends Expression 3 => Dict::S('DayOfWeek-Wednesday'), 4 => Dict::S('DayOfWeek-Thursday'), 5 => Dict::S('DayOfWeek-Friday'), - 6 => Dict::S('DayOfWeek-Saturday') + 6 => Dict::S('DayOfWeek-Saturday'), ); } static $aMonthToString = null; @@ -2996,15 +3067,15 @@ class FunctionExpression extends Expression { // Init the correspondance table $aMonthToString = array( - 1 => Dict::S('Month-01'), - 2 => Dict::S('Month-02'), - 3 => Dict::S('Month-03'), - 4 => Dict::S('Month-04'), - 5 => Dict::S('Month-05'), - 6 => Dict::S('Month-06'), - 7 => Dict::S('Month-07'), - 8 => Dict::S('Month-08'), - 9 => Dict::S('Month-09'), + 1 => Dict::S('Month-01'), + 2 => Dict::S('Month-02'), + 3 => Dict::S('Month-03'), + 4 => Dict::S('Month-04'), + 5 => Dict::S('Month-05'), + 6 => Dict::S('Month-06'), + 7 => Dict::S('Month-07'), + 8 => Dict::S('Month-08'), + 9 => Dict::S('Month-09'), 10 => Dict::S('Month-10'), 11 => Dict::S('Month-11'), 12 => Dict::S('Month-12'), @@ -3012,30 +3083,22 @@ class FunctionExpression extends Expression } $sRes = $sDefault; - if (strtolower($this->m_sVerb) == 'date_format') - { + if (strtolower($this->m_sVerb) == 'date_format') { $oFormatExpr = $this->m_aArgs[1]; - if ($oFormatExpr->Render() == "'%w'") - { - if (isset($aWeekDayToString[(int)$sValue])) - { + + if ($oFormatExpr->RenderExpression() == "'%w'") { + if (isset($aWeekDayToString[(int)$sValue])) { $sRes = $aWeekDayToString[(int)$sValue]; } - } - elseif ($oFormatExpr->Render() == "'%Y-%m'") - { + } elseif ($oFormatExpr->RenderExpression() == "'%Y-%m'") { // yyyy-mm => "yyyy month" - $iMonth = (int) substr($sValue, -2); // the two last chars + $iMonth = (int)substr($sValue, -2); // the two last chars $sRes = substr($sValue, 0, 4).' '.$aMonthToString[$iMonth]; - } - elseif ($oFormatExpr->Render() == "'%Y-%m-%d'") - { + } elseif ($oFormatExpr->RenderExpression() == "'%Y-%m-%d'") { // yyyy-mm-dd => "month d" - $iMonth = (int) substr($sValue, 5, 2); + $iMonth = (int)substr($sValue, 5, 2); $sRes = $aMonthToString[$iMonth].' '.(int)substr($sValue, -2); - } - elseif ($oFormatExpr->Render() == "'%H'") - { + } elseif ($oFormatExpr->RenderExpression() == "'%H'") { // H => "H Hour(s)" $sRes = $sValue.':00'; } diff --git a/core/oql/oql-parser.php b/core/oql/oql-parser.php index 83fe29aa69..5bd6d15774 100644 --- a/core/oql/oql-parser.php +++ b/core/oql/oql-parser.php @@ -33,17 +33,19 @@ class OQLParser_yyToken implements ArrayAccess return $this->string; } - function offsetExists($offset) + function offsetExists($offset): bool { return isset($this->metadata[$offset]); } + // Return type mixed is not supported by PHP 7.4, we can remove the following PHP attribute and add the return type once iTop min PHP version is PHP 8.0+ + #[\ReturnTypeWillChange] function offsetGet($offset) { return $this->metadata[$offset]; } - function offsetSet($offset, $value) + function offsetSet($offset, $value): void { if ($offset === null) { if (isset($value[0])) { @@ -66,7 +68,7 @@ class OQLParser_yyToken implements ArrayAccess } } - function offsetUnset($offset) + function offsetUnset($offset): void { unset($this->metadata[$offset]); } diff --git a/core/oql/oqlexception.class.inc.php b/core/oql/oqlexception.class.inc.php index f336bc4e38..81e771b279 100644 --- a/core/oql/oqlexception.class.inc.php +++ b/core/oql/oqlexception.class.inc.php @@ -58,22 +58,19 @@ class OQLException extends CoreException public function getHtmlDesc($sHighlightHtmlBegin = '', $sHighlightHtmlEnd = '') { - $sRet = htmlentities($this->m_MyIssue.", found '".$this->m_sUnexpected."' in: ", ENT_QUOTES, 'UTF-8'); - $sRet .= htmlentities(substr($this->m_sInput, 0, $this->m_iCol), ENT_QUOTES, 'UTF-8'); - $sRet .= $sHighlightHtmlBegin.htmlentities(substr($this->m_sInput, $this->m_iCol, strlen($this->m_sUnexpected)), ENT_QUOTES, 'UTF-8').$sHighlightHtmlEnd; - $sRet .= htmlentities(substr($this->m_sInput, $this->m_iCol + strlen($this->m_sUnexpected)), ENT_QUOTES, 'UTF-8'); + $sRet = utils::EscapeHtml($this->m_MyIssue.", found '".$this->m_sUnexpected."' in: "); + $sRet .= utils::EscapeHtml(substr($this->m_sInput, 0, $this->m_iCol)); + $sRet .= $sHighlightHtmlBegin.utils::EscapeHtml(substr($this->m_sInput, $this->m_iCol, strlen($this->m_sUnexpected))).$sHighlightHtmlEnd; + $sRet .= utils::EscapeHtml(substr($this->m_sInput, $this->m_iCol + strlen($this->m_sUnexpected))); - if (!is_null($this->m_aExpecting) && (count($this->m_aExpecting) > 0)) - { - if (count($this->m_aExpecting) < 30) - { + if (!is_null($this->m_aExpecting) && (count($this->m_aExpecting) > 0)) { + if (count($this->m_aExpecting) < 30) { $sExpectations = '{'.implode(', ', $this->m_aExpecting).'}'; - $sRet .= ", expecting ".htmlentities($sExpectations, ENT_QUOTES, 'UTF-8'); - } + $sRet .= ", expecting ".utils::EscapeHtml($sExpectations); + } $sSuggest = self::FindClosestString($this->m_sUnexpected, $this->m_aExpecting); - if (strlen($sSuggest) > 0) - { - $sRet .= ", I would suggest to use '$sHighlightHtmlBegin".htmlentities($sSuggest, ENT_QUOTES, 'UTF-8')."$sHighlightHtmlEnd'"; + if (strlen($sSuggest) > 0) { + $sRet .= ", I would suggest to use '$sHighlightHtmlBegin".utils::EscapeHtml($sSuggest)."$sHighlightHtmlEnd'"; } } diff --git a/core/ormStyle.class.inc.php b/core/ormStyle.class.inc.php index e7c62a0eaa..dabbd59bd8 100644 --- a/core/ormStyle.class.inc.php +++ b/core/ormStyle.class.inc.php @@ -50,7 +50,7 @@ class ormStyle */ public function HasMainColor(): bool { - return strlen($this->sMainColor) > 0; + return utils::IsNotNullOrEmptyString($this->sMainColor); } /** @@ -68,7 +68,7 @@ class ormStyle */ public function SetMainColor(?string $sMainColor) { - $this->sMainColor = (strlen($sMainColor) === 0) ? null : $sMainColor; + $this->sMainColor = utils::IsNullOrEmptyString($sMainColor) ? null : $sMainColor; return $this; } @@ -78,7 +78,7 @@ class ormStyle */ public function HasComplementaryColor(): bool { - return strlen($this->sComplementaryColor) > 0; + return utils::IsNotNullOrEmptyString($this->sComplementaryColor); } /** @@ -96,7 +96,7 @@ class ormStyle */ public function SetComplementaryColor(?string $sComplementaryColor) { - $this->sComplementaryColor = (strlen($sComplementaryColor) === 0) ? null : $sComplementaryColor; + $this->sComplementaryColor = utils::IsNullOrEmptyString($sComplementaryColor) ? null : $sComplementaryColor; return $this; } @@ -116,7 +116,7 @@ class ormStyle */ public function HasStyleClass(): bool { - return strlen($this->sStyleClass) > 0; + return utils::IsNotNullOrEmptyString($this->sStyleClass); } /** @@ -134,7 +134,7 @@ class ormStyle */ public function SetStyleClass(?string $sStyleClass) { - $this->sStyleClass = (strlen($sStyleClass) === 0) ? null : $sStyleClass; + $this->sStyleClass = utils::IsNullOrEmptyString($sStyleClass) ? null : $sStyleClass; return $this; } @@ -144,7 +144,7 @@ class ormStyle */ public function HasAltStyleClass(): bool { - return strlen($this->sAltStyleClass) > 0; + return utils::IsNotNullOrEmptyString($this->sAltStyleClass); } /** @@ -162,7 +162,7 @@ class ormStyle */ public function SetAltStyleClass(?string $sAltStyleClass) { - $this->sAltStyleClass = (strlen($sAltStyleClass) === 0) ? null : $sAltStyleClass; + $this->sAltStyleClass = utils::IsNullOrEmptyString($sAltStyleClass) ? null : $sAltStyleClass; return $this; } @@ -172,7 +172,7 @@ class ormStyle */ public function HasDecorationClasses(): bool { - return strlen($this->sDecorationClasses) > 0; + return utils::IsNotNullOrEmptyString($this->sDecorationClasses); } /** @@ -190,7 +190,7 @@ class ormStyle */ public function SetDecorationClasses(?string $sDecorationClasses) { - $this->sDecorationClasses = (strlen($sDecorationClasses) === 0) ? null : $sDecorationClasses; + $this->sDecorationClasses = utils::IsNullOrEmptyString($sDecorationClasses) ? null : $sDecorationClasses; return $this; } @@ -200,7 +200,7 @@ class ormStyle */ public function HasIcon(): bool { - return strlen($this->sIcon) > 0; + return utils::IsNotNullOrEmptyString($this->sIcon); } /** @@ -210,7 +210,7 @@ class ormStyle */ public function SetIcon(?string $sIcon) { - $this->sIcon = (strlen($sIcon) === 0) ? null : $sIcon; + $this->sIcon = utils::IsNullOrEmptyString($sIcon) ? null : $sIcon; return $this; } diff --git a/core/ormcaselog.class.inc.php b/core/ormcaselog.class.inc.php index 2d024fa31b..de90d241e8 100644 --- a/core/ormcaselog.class.inc.php +++ b/core/ormcaselog.class.inc.php @@ -155,7 +155,7 @@ class ormCaseLog { break; case static::ENUM_FORMAT_HTML: - $sHtmlEntry = $sTextEntry; + $sHtmlEntry = InlineImage::FixUrls($sTextEntry); $sTextEntry = utils::HtmlToText($sHtmlEntry); break; } @@ -252,7 +252,7 @@ class ormCaseLog { if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == static::ENUM_FORMAT_TEXT)) { $sCSSClass = 'caselog_entry'; - $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "
    ", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8')); + $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "
    ", utils::EscapeHtml($sTextEntry)); } else { @@ -292,19 +292,15 @@ class ormCaseLog { } // Process the case of an eventual remainder (quick migration of AttributeText fields) - if ($iPos < (strlen($this->m_sLog) - 1)) - { + if ($iPos < (strlen($this->m_sLog) - 1)) { $sTextEntry = substr($this->m_sLog, $iPos); - $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "
    ", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8')); + $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "
    ", utils::EscapeHtml($sTextEntry)); - if (count($this->m_aIndex) == 0) - { + if (count($this->m_aIndex) == 0) { $sHtml .= '
    '; $sHtml .= $sTextEntry; $sHtml .= '
    '; - } - else - { + } else { $sHtml .= '
    '; $sHtml .= Dict::S('UI:CaseLog:InitialValue'); $sHtml .= '
    '; @@ -327,24 +323,18 @@ class ormCaseLog { $sHtml = '

    Tous ces modules peuvent être installés séparément, à votre rythme.

    ', - 'UI:WelcomeMenu:RightBlock' => '

    '.ITOP_APPLICATION_SHORT.' a été conçu pour les fournisseurs de service, il permet à vos équipes IT de gérer facilement de multiples clients et organisations. + 'UI:WelcomeMenu:RightBlock' => '

    ITOP_APPLICATION_SHORT a été conçu pour les fournisseurs de service, il permet à vos équipes IT de gérer facilement de multiples clients et organisations.

      iTop fournit un riche ensemble de processus métier pour:
    • Augmenter l\'efficacité de la gestion de votre SI
    • Accroitre la performance de vos équipes d\'exploitation
    • @@ -467,6 +467,8 @@ Nous espérons que vous aimerez cette version autant que nous avons eu du plaisi 'UI:Error:MaintenanceTitle' => 'Maintenance', 'UI:Error:InvalidToken' => 'Erreur: l\'opération a déjà été effectuée (CSRF token not found)', + 'UI:Error:SMTP:UnknownVendor' => 'Le provider SMTP OAuth 2.0 %1$s n\'existe pas', + 'UI:GroupBy:Count' => 'Nombre', 'UI:GroupBy:Count+' => 'Nombre d\'éléments', 'UI:CountOfObjects' => '%1$d objets correspondants aux critères.', @@ -1631,7 +1633,6 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Afficher au plus %1$s messages dans le menu %2$s.', )); - Dict::Add('FR FR', 'French', 'Français', array( 'Menu:DataSources' => 'Synchronisation', 'Menu:DataSources+' => '', @@ -1651,22 +1652,22 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Menu:AuditCategories' => 'Catégories d\'audit', 'Menu:AuditCategories+' => 'Catégories d\'audit', 'Menu:Notifications:Title' => 'Catégories d\'audit', - 'Menu:RunQueriesMenu' => 'Requêtes OQL', - 'Menu:RunQueriesMenu+' => 'Executer une requête OQL', - 'Menu:QueryMenu' => 'Livre des requêtes', - 'Menu:QueryMenu+' => 'Livre des requêtes', - 'Menu:UniversalSearchMenu' => 'Recherche Universelle', - 'Menu:UniversalSearchMenu+' => 'Rechercher n\'importe quel objet...', - 'Menu:UserManagementMenu' => 'Gestion des Utilisateurs', - 'Menu:UserManagementMenu+' => 'Gestion des Utilisateurs', - 'Menu:ProfilesMenu' => 'Profils', - 'Menu:ProfilesMenu+' => 'Profils', - 'Menu:ProfilesMenu:Title' => 'Profils', - 'Menu:UserAccountsMenu' => 'Comptes Utilisateurs', - 'Menu:UserAccountsMenu+' => 'Comptes Utilisateurs', + 'Menu:RunQueriesMenu' => 'Requêtes OQL', + 'Menu:RunQueriesMenu+' => 'Executer une requête OQL', + 'Menu:QueryMenu' => 'Livre des requêtes', + 'Menu:QueryMenu+' => 'Livre des requêtes', + 'Menu:UniversalSearchMenu' => 'Recherche Universelle', + 'Menu:UniversalSearchMenu+' => 'Rechercher n\'importe quel objet...', + 'Menu:UserManagementMenu' => 'Gestion des Utilisateurs', + 'Menu:UserManagementMenu+' => 'Gestion des Utilisateurs', + 'Menu:ProfilesMenu' => 'Profils', + 'Menu:ProfilesMenu+' => 'Profils', + 'Menu:ProfilesMenu:Title' => 'Profils', + 'Menu:UserAccountsMenu' => 'Comptes Utilisateurs', + 'Menu:UserAccountsMenu+' => 'Comptes Utilisateurs', 'Menu:UserAccountsMenu:Title' => 'Comptes Utilisateurs', - 'Menu:MyShortcuts' => 'Mes raccourcis', - 'Menu:UserManagement' => 'Utilisateurs', - 'Menu:Queries' => 'Requêtes', - 'Menu:ConfigurationTools' => 'Configuration', + 'Menu:MyShortcuts' => 'Mes raccourcis', + 'Menu:UserManagement' => 'Utilisateurs', + 'Menu:Queries' => 'Requêtes', + 'Menu:ConfigurationTools' => 'Configuration', )); diff --git a/dictionaries/hu.dictionary.itop.core.php b/dictionaries/hu.dictionary.itop.core.php index 5c91fc1f60..d7c567697e 100755 --- a/dictionaries/hu.dictionary.itop.core.php +++ b/dictionaries/hu.dictionary.itop.core.php @@ -21,22 +21,24 @@ */ Dict::Add('HU HU', 'Hungarian', 'Magyar', array( 'Core:DeletedObjectLabel' => '%1s (deleted)~~', - 'Core:DeletedObjectTip' => 'The object has been deleted on %1$s (%2$s)~~', + 'Core:DeletedObjectTip' => 'The object has been deleted on %1$s (%2$s)~~', 'Core:UnknownObjectLabel' => 'Object not found (class: %1$s, id: %2$d)~~', - 'Core:UnknownObjectTip' => 'The object could not be found. It may have been deleted some time ago and the log has been purged since.~~', + 'Core:UnknownObjectTip' => 'The object could not be found. It may have been deleted some time ago and the log has been purged since.~~', 'Core:UniquenessDefaultError' => 'Uniqueness rule \'%1$s\' in error~~', + 'Core:CheckConsistencyError' => 'Consistency rules not followed: %1$s~~', + 'Core:CheckValueError' => 'Unexpected value for attribute \'%1$s\' (%2$s) : %3$s~~', - 'Core:AttributeLinkedSet' => 'Objektum tömbök', + 'Core:AttributeLinkedSet' => 'Objektum tömbök', 'Core:AttributeLinkedSet+' => '', 'Core:AttributeLinkedSetDuplicatesFound' => 'Duplicates in the \'%1$s\' field : %2$s~~', - 'Core:AttributeDashboard' => 'Dashboard~~', + 'Core:AttributeDashboard' => 'Dashboard~~', 'Core:AttributeDashboard+' => '', - 'Core:AttributePhoneNumber' => 'Phone number~~', + 'Core:AttributePhoneNumber' => 'Phone number~~', 'Core:AttributePhoneNumber+' => '', 'Core:AttributeObsolescenceDate' => 'Obsolescence date~~', @@ -517,9 +519,9 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array( 'Class:ActionEmail' => 'E-mail értesítés', 'Class:ActionEmail+' => '', 'Class:ActionEmail/Attribute:status+' => 'This status drives who will be notified: just the Test recipient, all (To, cc and Bcc) or no-one~~', - 'Class:ActionEmail/Attribute:status/Value:test+' => 'Only the Test recipient is notified~~', - 'Class:ActionEmail/Attribute:status/Value:enabled+' => 'All To, Cc and Bcc emails are notified~~', - 'Class:ActionEmail/Attribute:status/Value:disabled+' => 'The email notification will not be sent~~', + 'Class:ActionEmail/Attribute:status/Value:test' => 'Only the Test recipient is notified~~', + 'Class:ActionEmail/Attribute:status/Value:enabled' => 'All To, Cc and Bcc emails are notified~~', + 'Class:ActionEmail/Attribute:status/Value:disabled' => 'The email notification will not be sent~~', 'Class:ActionEmail/Attribute:test_recipient' => 'Teszt címzett', 'Class:ActionEmail/Attribute:test_recipient+' => '', 'Class:ActionEmail/Attribute:from' => 'Feladó~~', diff --git a/dictionaries/hu.dictionary.itop.ui.php b/dictionaries/hu.dictionary.itop.ui.php index a2ab953174..89929addd5 100755 --- a/dictionaries/hu.dictionary.itop.ui.php +++ b/dictionaries/hu.dictionary.itop.ui.php @@ -461,6 +461,8 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Error:MaintenanceTitle' => 'Maintenance~~', 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', + 'UI:Error:SMTP:UnknownVendor' => 'OAuth SMTP provider %1$s does not exist (email_transport_smtp.oauth.provider)~~', + 'UI:GroupBy:Count' => 'Számossága', 'UI:GroupBy:Count+' => '', 'UI:CountOfObjects' => '%1$d darab objektum felel meg a kritériumoknak.', @@ -1645,31 +1647,31 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array( 'Menu:AuditCategories' => 'Audit kategóriák', 'Menu:AuditCategories+' => '', 'Menu:Notifications:Title' => 'Audit kategóriák', - 'Menu:RunQueriesMenu' => 'Lekérdezés futtatás', - 'Menu:RunQueriesMenu+' => '', - 'Menu:QueryMenu' => 'Query phrasebook~~', - 'Menu:QueryMenu+' => 'Query phrasebook~~', - 'Menu:UniversalSearchMenu' => 'Univerzális keresés', - 'Menu:UniversalSearchMenu+' => '', - 'Menu:UserManagementMenu' => 'Felhasználó menedzsment', - 'Menu:UserManagementMenu+' => '', - 'Menu:ProfilesMenu' => 'Profilok', - 'Menu:ProfilesMenu+' => '', - 'Menu:ProfilesMenu:Title' => 'Profilok', - 'Menu:UserAccountsMenu' => 'Felhasználói fiókok', - 'Menu:UserAccountsMenu+' => '', + 'Menu:RunQueriesMenu' => 'Lekérdezés futtatás', + 'Menu:RunQueriesMenu+' => '', + 'Menu:QueryMenu' => 'Query phrasebook~~', + 'Menu:QueryMenu+' => 'Query phrasebook~~', + 'Menu:UniversalSearchMenu' => 'Univerzális keresés', + 'Menu:UniversalSearchMenu+' => '', + 'Menu:UserManagementMenu' => 'Felhasználó menedzsment', + 'Menu:UserManagementMenu+' => '', + 'Menu:ProfilesMenu' => 'Profilok', + 'Menu:ProfilesMenu+' => '', + 'Menu:ProfilesMenu:Title' => 'Profilok', + 'Menu:UserAccountsMenu' => 'Felhasználói fiókok', + 'Menu:UserAccountsMenu+' => '', 'Menu:UserAccountsMenu:Title' => 'Felhasználói fiókok', - 'Menu:MyShortcuts' => 'My Shortcuts~~', - 'Menu:UserManagement' => 'User Management~~', - 'Menu:Queries' => 'Queries~~', - 'Menu:ConfigurationTools' => 'Configuration~~', + 'Menu:MyShortcuts' => 'My Shortcuts~~', + 'Menu:UserManagement' => 'User Management~~', + 'Menu:Queries' => 'Queries~~', + 'Menu:ConfigurationTools' => 'Configuration~~', )); // Additional language entries not present in English dict Dict::Add('HU HU', 'Hungarian', 'Magyar', array( - 'UI:Toggle:StandardDashboard' => 'Standard~~', - 'UI:Toggle:CustomDashboard' => 'Custom~~', - 'UI:Display_X_ItemsPerPage' => 'Display %1$s items per page~~', - 'UI:Dashboard:Edit' => 'Edit This Page...~~', - 'UI:Dashboard:Revert' => 'Revert To Original Version...~~', + 'UI:Toggle:StandardDashboard' => 'Standard~~', + 'UI:Toggle:CustomDashboard' => 'Custom~~', + 'UI:Display_X_ItemsPerPage' => 'Display %1$s items per page~~', + 'UI:Dashboard:Edit' => 'Edit This Page...~~', + 'UI:Dashboard:Revert' => 'Revert To Original Version...~~', )); diff --git a/dictionaries/it.dictionary.itop.core.php b/dictionaries/it.dictionary.itop.core.php index a996ef39b0..6df05fc87f 100644 --- a/dictionaries/it.dictionary.itop.core.php +++ b/dictionaries/it.dictionary.itop.core.php @@ -23,22 +23,24 @@ */ Dict::Add('IT IT', 'Italian', 'Italiano', array( 'Core:DeletedObjectLabel' => '%1s (deleted)~~', - 'Core:DeletedObjectTip' => 'The object has been deleted on %1$s (%2$s)~~', + 'Core:DeletedObjectTip' => 'The object has been deleted on %1$s (%2$s)~~', 'Core:UnknownObjectLabel' => 'Object not found (class: %1$s, id: %2$d)~~', - 'Core:UnknownObjectTip' => 'The object could not be found. It may have been deleted some time ago and the log has been purged since.~~', + 'Core:UnknownObjectTip' => 'The object could not be found. It may have been deleted some time ago and the log has been purged since.~~', 'Core:UniquenessDefaultError' => 'Uniqueness rule \'%1$s\' in error~~', + 'Core:CheckConsistencyError' => 'Consistency rules not followed: %1$s~~', + 'Core:CheckValueError' => 'Unexpected value for attribute \'%1$s\' (%2$s) : %3$s~~', - 'Core:AttributeLinkedSet' => 'Array di oggetti', + 'Core:AttributeLinkedSet' => 'Array di oggetti', 'Core:AttributeLinkedSet+' => 'Ogni tipo di oggetto della stessa classe o sottoclasse', 'Core:AttributeLinkedSetDuplicatesFound' => 'Duplicates in the \'%1$s\' field : %2$s~~', - 'Core:AttributeDashboard' => 'Dashboard~~', + 'Core:AttributeDashboard' => 'Dashboard~~', 'Core:AttributeDashboard+' => '', - 'Core:AttributePhoneNumber' => 'Phone number~~', + 'Core:AttributePhoneNumber' => 'Phone number~~', 'Core:AttributePhoneNumber+' => '', 'Core:AttributeObsolescenceDate' => 'Obsolescence date~~', @@ -519,9 +521,9 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array( 'Class:ActionEmail' => 'Email di notifica', 'Class:ActionEmail+' => '', 'Class:ActionEmail/Attribute:status+' => 'This status drives who will be notified: just the Test recipient, all (To, cc and Bcc) or no-one~~', - 'Class:ActionEmail/Attribute:status/Value:test+' => 'Only the Test recipient is notified~~', - 'Class:ActionEmail/Attribute:status/Value:enabled+' => 'All To, Cc and Bcc emails are notified~~', - 'Class:ActionEmail/Attribute:status/Value:disabled+' => 'The email notification will not be sent~~', + 'Class:ActionEmail/Attribute:status/Value:test' => 'Only the Test recipient is notified~~', + 'Class:ActionEmail/Attribute:status/Value:enabled' => 'All To, Cc and Bcc emails are notified~~', + 'Class:ActionEmail/Attribute:status/Value:disabled' => 'The email notification will not be sent~~', 'Class:ActionEmail/Attribute:test_recipient' => 'Test destinatario', 'Class:ActionEmail/Attribute:test_recipient+' => '', 'Class:ActionEmail/Attribute:from' => 'Da~~', diff --git a/dictionaries/it.dictionary.itop.ui.php b/dictionaries/it.dictionary.itop.ui.php index 76408ff80c..22c6d29fc3 100644 --- a/dictionaries/it.dictionary.itop.ui.php +++ b/dictionaries/it.dictionary.itop.ui.php @@ -472,6 +472,8 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Error:MaintenanceTitle' => 'Maintenance~~', 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', + 'UI:Error:SMTP:UnknownVendor' => 'OAuth SMTP provider %1$s does not exist (email_transport_smtp.oauth.provider)~~', + 'UI:GroupBy:Count' => 'Conteggio', 'UI:GroupBy:Count+' => '', 'UI:CountOfObjects' => '%1$d oggetti corrispondenti ai criteri.', @@ -1656,31 +1658,31 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array( 'Menu:AuditCategories' => 'Categorie di Audit', 'Menu:AuditCategories+' => '', 'Menu:Notifications:Title' => 'Categorie di Audit', - 'Menu:RunQueriesMenu' => 'Esegui query', - 'Menu:RunQueriesMenu+' => '', - 'Menu:QueryMenu' => 'Rubbrica delle Query', - 'Menu:QueryMenu+' => 'Rubbrica delle Query', - 'Menu:UniversalSearchMenu' => 'Ricerca universale', - 'Menu:UniversalSearchMenu+' => '', - 'Menu:UserManagementMenu' => 'Gestione degli utenti', - 'Menu:UserManagementMenu+' => '', - 'Menu:ProfilesMenu' => 'Profili', - 'Menu:ProfilesMenu+' => '', - 'Menu:ProfilesMenu:Title' => 'Profili', - 'Menu:UserAccountsMenu' => 'Account utente', - 'Menu:UserAccountsMenu+' => '', + 'Menu:RunQueriesMenu' => 'Esegui query', + 'Menu:RunQueriesMenu+' => '', + 'Menu:QueryMenu' => 'Rubbrica delle Query', + 'Menu:QueryMenu+' => 'Rubbrica delle Query', + 'Menu:UniversalSearchMenu' => 'Ricerca universale', + 'Menu:UniversalSearchMenu+' => '', + 'Menu:UserManagementMenu' => 'Gestione degli utenti', + 'Menu:UserManagementMenu+' => '', + 'Menu:ProfilesMenu' => 'Profili', + 'Menu:ProfilesMenu+' => '', + 'Menu:ProfilesMenu:Title' => 'Profili', + 'Menu:UserAccountsMenu' => 'Account utente', + 'Menu:UserAccountsMenu+' => '', 'Menu:UserAccountsMenu:Title' => 'Account utente', - 'Menu:MyShortcuts' => 'Le mie scorciatoie', - 'Menu:UserManagement' => 'Gestione utenti', - 'Menu:Queries' => 'Interrogazioni', - 'Menu:ConfigurationTools' => 'configurazione', + 'Menu:MyShortcuts' => 'Le mie scorciatoie', + 'Menu:UserManagement' => 'Gestione utenti', + 'Menu:Queries' => 'Interrogazioni', + 'Menu:ConfigurationTools' => 'configurazione', )); // Additional language entries not present in English dict Dict::Add('IT IT', 'Italian', 'Italiano', array( - 'UI:Toggle:StandardDashboard' => 'Standard~~', - 'UI:Toggle:CustomDashboard' => 'Custom~~', - 'UI:Display_X_ItemsPerPage' => 'Display %1$s items per page~~', - 'UI:Dashboard:Edit' => 'Edit This Page...~~', - 'UI:Dashboard:Revert' => 'Revert To Original Version...~~', + 'UI:Toggle:StandardDashboard' => 'Standard~~', + 'UI:Toggle:CustomDashboard' => 'Custom~~', + 'UI:Display_X_ItemsPerPage' => 'Display %1$s items per page~~', + 'UI:Dashboard:Edit' => 'Edit This Page...~~', + 'UI:Dashboard:Revert' => 'Revert To Original Version...~~', )); diff --git a/dictionaries/ja.dictionary.itop.core.php b/dictionaries/ja.dictionary.itop.core.php index 655e1c02ff..48b1c6308e 100644 --- a/dictionaries/ja.dictionary.itop.core.php +++ b/dictionaries/ja.dictionary.itop.core.php @@ -21,22 +21,24 @@ */ Dict::Add('JA JP', 'Japanese', '日本語', array( 'Core:DeletedObjectLabel' => '%1s (削除されました)', - 'Core:DeletedObjectTip' => 'オブジェクトは削除されました %1$s (%2$s)', + 'Core:DeletedObjectTip' => 'オブジェクトは削除されました %1$s (%2$s)', 'Core:UnknownObjectLabel' => 'オブジェクトは見つかりません (クラス: %1$s, id: %2$d)', - 'Core:UnknownObjectTip' => 'オブジェクトは見つかりません。しばらく前に削除され、その後ログが削除されたかもしれません。', + 'Core:UnknownObjectTip' => 'オブジェクトは見つかりません。しばらく前に削除され、その後ログが削除されたかもしれません。', 'Core:UniquenessDefaultError' => 'Uniqueness rule \'%1$s\' in error~~', + 'Core:CheckConsistencyError' => 'Consistency rules not followed: %1$s~~', + 'Core:CheckValueError' => 'Unexpected value for attribute \'%1$s\' (%2$s) : %3$s~~', - 'Core:AttributeLinkedSet' => 'オブジェクト配列', + 'Core:AttributeLinkedSet' => 'オブジェクト配列', 'Core:AttributeLinkedSet+' => '同一あるいはサブクラスに属するオブジェクト', 'Core:AttributeLinkedSetDuplicatesFound' => 'Duplicates in the \'%1$s\' field : %2$s~~', - 'Core:AttributeDashboard' => 'Dashboard~~', + 'Core:AttributeDashboard' => 'Dashboard~~', 'Core:AttributeDashboard+' => '~~', - 'Core:AttributePhoneNumber' => 'Phone number~~', + 'Core:AttributePhoneNumber' => 'Phone number~~', 'Core:AttributePhoneNumber+' => '~~', 'Core:AttributeObsolescenceDate' => 'Obsolescence date~~', @@ -517,9 +519,9 @@ Dict::Add('JA JP', 'Japanese', '日本語', array( 'Class:ActionEmail' => 'メール通知', 'Class:ActionEmail+' => '', 'Class:ActionEmail/Attribute:status+' => 'This status drives who will be notified: just the Test recipient, all (To, cc and Bcc) or no-one~~', - 'Class:ActionEmail/Attribute:status/Value:test+' => 'Only the Test recipient is notified~~', - 'Class:ActionEmail/Attribute:status/Value:enabled+' => 'All To, Cc and Bcc emails are notified~~', - 'Class:ActionEmail/Attribute:status/Value:disabled+' => 'The email notification will not be sent~~', + 'Class:ActionEmail/Attribute:status/Value:test' => 'Only the Test recipient is notified~~', + 'Class:ActionEmail/Attribute:status/Value:enabled' => 'All To, Cc and Bcc emails are notified~~', + 'Class:ActionEmail/Attribute:status/Value:disabled' => 'The email notification will not be sent~~', 'Class:ActionEmail/Attribute:test_recipient' => 'テストレシピ', 'Class:ActionEmail/Attribute:test_recipient+' => '状態がテストの場合の宛先', 'Class:ActionEmail/Attribute:from' => 'From~~', diff --git a/dictionaries/ja.dictionary.itop.ui.php b/dictionaries/ja.dictionary.itop.ui.php index 76c3f0ae17..3c78e7d903 100644 --- a/dictionaries/ja.dictionary.itop.ui.php +++ b/dictionaries/ja.dictionary.itop.ui.php @@ -461,6 +461,8 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Error:MaintenanceTitle' => 'Maintenance~~', 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', + 'UI:Error:SMTP:UnknownVendor' => 'OAuth SMTP provider %1$s does not exist (email_transport_smtp.oauth.provider)~~', + 'UI:GroupBy:Count' => 'カウント', 'UI:GroupBy:Count+' => '要素数', 'UI:CountOfObjects' => '%1$d 個のオブジェクトが条件にマッチしました。', @@ -1645,30 +1647,30 @@ Dict::Add('JA JP', 'Japanese', '日本語', array( 'Menu:AuditCategories' => '監査カテゴリ', 'Menu:AuditCategories+' => '監査カテゴリ', 'Menu:Notifications:Title' => '監査カテゴリ', - 'Menu:RunQueriesMenu' => 'クエリ実行', - 'Menu:RunQueriesMenu+' => '任意のクエリを実行', - 'Menu:QueryMenu' => 'クエリのフレーズブック', - 'Menu:QueryMenu+' => 'クエリのフレーズブック', - 'Menu:UniversalSearchMenu' => '全検索', - 'Menu:UniversalSearchMenu+' => '何か...検索', - 'Menu:UserManagementMenu' => 'ユーザ管理', - 'Menu:UserManagementMenu+' => 'ユーザ管理', - 'Menu:ProfilesMenu' => 'プロフィール', - 'Menu:ProfilesMenu+' => 'プロフィール', - 'Menu:ProfilesMenu:Title' => 'プロフィール', - 'Menu:UserAccountsMenu' => 'ユーザアカウント', - 'Menu:UserAccountsMenu+' => 'ユーザアカウント', + 'Menu:RunQueriesMenu' => 'クエリ実行', + 'Menu:RunQueriesMenu+' => '任意のクエリを実行', + 'Menu:QueryMenu' => 'クエリのフレーズブック', + 'Menu:QueryMenu+' => 'クエリのフレーズブック', + 'Menu:UniversalSearchMenu' => '全検索', + 'Menu:UniversalSearchMenu+' => '何か...検索', + 'Menu:UserManagementMenu' => 'ユーザ管理', + 'Menu:UserManagementMenu+' => 'ユーザ管理', + 'Menu:ProfilesMenu' => 'プロフィール', + 'Menu:ProfilesMenu+' => 'プロフィール', + 'Menu:ProfilesMenu:Title' => 'プロフィール', + 'Menu:UserAccountsMenu' => 'ユーザアカウント', + 'Menu:UserAccountsMenu+' => 'ユーザアカウント', 'Menu:UserAccountsMenu:Title' => 'ユーザアカウント', - 'Menu:MyShortcuts' => '私のショートカット', - 'Menu:UserManagement' => 'User Management~~', - 'Menu:Queries' => 'Queries~~', - 'Menu:ConfigurationTools' => 'Configuration~~', + 'Menu:MyShortcuts' => '私のショートカット', + 'Menu:UserManagement' => 'User Management~~', + 'Menu:Queries' => 'Queries~~', + 'Menu:ConfigurationTools' => 'Configuration~~', )); // Additional language entries not present in English dict Dict::Add('JA JP', 'Japanese', '日本語', array( - 'UI:Toggle:StandardDashboard' => 'Standard~~', - 'UI:Toggle:CustomDashboard' => 'Custom~~', - 'UI:Dashboard:Edit' => 'このページを編集...', - 'UI:Dashboard:Revert' => '元のバージョンに戻す...', + 'UI:Toggle:StandardDashboard' => 'Standard~~', + 'UI:Toggle:CustomDashboard' => 'Custom~~', + 'UI:Dashboard:Edit' => 'このページを編集...', + 'UI:Dashboard:Revert' => '元のバージョンに戻す...', )); diff --git a/dictionaries/nl.dictionary.itop.core.php b/dictionaries/nl.dictionary.itop.core.php index 005162f6ed..ad9891a783 100644 --- a/dictionaries/nl.dictionary.itop.core.php +++ b/dictionaries/nl.dictionary.itop.core.php @@ -22,29 +22,31 @@ * http://www.linprofs.com * * @author Hipska (2018, 2019) - * @author Jeffrey Bostoen - (2019 - 2020) + * @author Jeffrey Bostoen (2019 - 2022) * * @copyright Copyright (C) 2010-2021 Combodo SARL * @licence http://opensource.org/licenses/AGPL-3.0 */ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'Core:DeletedObjectLabel' => '%1s (verwijderd)', - 'Core:DeletedObjectTip' => 'Het object is verwijderd op %1$s (%2$s)', + 'Core:DeletedObjectTip' => 'Het object is verwijderd op %1$s (%2$s)', 'Core:UnknownObjectLabel' => 'Object niet gevonden (klasse: %1$s, id: %2$d)', - 'Core:UnknownObjectTip' => 'Object kon niet worden gevonden. Het kan al eerder verwijderd zijn waardoor ook de historiek al gewist is.', + 'Core:UnknownObjectTip' => 'Object kon niet worden gevonden. Het kan al eerder verwijderd zijn waardoor ook de historiek al gewist is.', 'Core:UniquenessDefaultError' => 'De regel \'%1$s\' die unieke waardes afdwingt, blokkeert deze actie', + 'Core:CheckConsistencyError' => 'Consistency rules not followed: %1$s~~', + 'Core:CheckValueError' => 'Unexpected value for attribute \'%1$s\' (%2$s) : %3$s~~', - 'Core:AttributeLinkedSet' => 'Reeks van objecten', + 'Core:AttributeLinkedSet' => 'Reeks van objecten', 'Core:AttributeLinkedSet+' => 'Elke soort objecten van dezelfde klasse of subklasse', - 'Core:AttributeLinkedSetDuplicatesFound' => 'Duplicates in the \'%1$s\' field : %2$s~~', + 'Core:AttributeLinkedSetDuplicatesFound' => 'Dubbele records in het \'%1$s\' veld : %2$s', - 'Core:AttributeDashboard' => 'Dashboard', + 'Core:AttributeDashboard' => 'Dashboard', 'Core:AttributeDashboard+' => '', - 'Core:AttributePhoneNumber' => 'Telefoonnummer', + 'Core:AttributePhoneNumber' => 'Telefoonnummer', 'Core:AttributePhoneNumber+' => '', 'Core:AttributeObsolescenceDate' => 'Buiten gebruik sinds', @@ -237,14 +239,14 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'Class:CMDBChange/Attribute:date+' => 'De datum en tijd waarop de aanpassingen zijn waargenomen ', 'Class:CMDBChange/Attribute:userinfo' => 'Info', 'Class:CMDBChange/Attribute:userinfo+' => 'Info over wie/wat (bv. welke service) de aanpassing heeft doorgevoerd', - 'Class:CMDBChange/Attribute:origin/Value:interactive' => 'User interaction in the GUI~~', - 'Class:CMDBChange/Attribute:origin/Value:csv-import.php' => 'CSV import script~~', - 'Class:CMDBChange/Attribute:origin/Value:csv-interactive' => 'CSV import in the GUI~~', - 'Class:CMDBChange/Attribute:origin/Value:email-processing' => 'Email processing~~', - 'Class:CMDBChange/Attribute:origin/Value:synchro-data-source' => 'Synchro. data source~~', - 'Class:CMDBChange/Attribute:origin/Value:webservice-rest' => 'REST/JSON webservices~~', - 'Class:CMDBChange/Attribute:origin/Value:webservice-soap' => 'SOAP webservices~~', - 'Class:CMDBChange/Attribute:origin/Value:custom-extension' => 'By an extension~~', + 'Class:CMDBChange/Attribute:origin/Value:interactive' => 'Gebruikersinteractie in de GUI', + 'Class:CMDBChange/Attribute:origin/Value:csv-import.php' => 'CSV import script', + 'Class:CMDBChange/Attribute:origin/Value:csv-interactive' => 'CSV import in de GUI', + 'Class:CMDBChange/Attribute:origin/Value:email-processing' => 'Verwerking e-mail', + 'Class:CMDBChange/Attribute:origin/Value:synchro-data-source' => 'Synchro. databron', + 'Class:CMDBChange/Attribute:origin/Value:webservice-rest' => 'REST/JSON webservices', + 'Class:CMDBChange/Attribute:origin/Value:webservice-soap' => 'SOAP webservices', + 'Class:CMDBChange/Attribute:origin/Value:custom-extension' => 'Via een extensie', )); // @@ -505,7 +507,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'Class:Action/Attribute:trigger_list+' => 'Triggers gelinkt aan deze actie', 'Class:Action/Attribute:finalclass' => 'Type', 'Class:Action/Attribute:finalclass+' => '', - 'Action:WarningNoTriggerLinked' => 'Warning, no trigger is linked to the action. It will not be active until it has at least 1.~~', + 'Action:WarningNoTriggerLinked' => 'Opgelet: er is geen trigger gelinkt aan deze actie. Zonder minstens 1 actieve trigger zal de actie nooit uitgevoerd worden.', )); // @@ -525,9 +527,9 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'Class:ActionEmail' => 'E-mailmelding', 'Class:ActionEmail+' => '', 'Class:ActionEmail/Attribute:status+' => 'Status bepaalt wie op de hoogte zal gesteld worden: enkel de testontvanger, iedereen (Aan, CC en BCC) of niemand', - 'Class:ActionEmail/Attribute:status/Value:test+' => 'Enkel de testontvanger zal op de hoogte gesteld worden', - 'Class:ActionEmail/Attribute:status/Value:enabled+' => 'Alle Aan, CC en BCC bestemingen zullen op de hoogte gesteld worden', - 'Class:ActionEmail/Attribute:status/Value:disabled+' => 'De e-mailmelding zal niet verstuurd worden', + 'Class:ActionEmail/Attribute:status/Value:test' => 'Enkel de testontvanger zal op de hoogte gesteld worden', + 'Class:ActionEmail/Attribute:status/Value:enabled' => 'Alle Aan, CC en BCC bestemingen zullen op de hoogte gesteld worden', + 'Class:ActionEmail/Attribute:status/Value:disabled' => 'De e-mailmelding zal niet verstuurd worden', 'Class:ActionEmail/Attribute:test_recipient' => 'Testontvanger', 'Class:ActionEmail/Attribute:test_recipient+' => 'Bestemming als de status op "Test" staat', 'Class:ActionEmail/Attribute:from' => 'Van (e-mail)', @@ -585,7 +587,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'Class:TriggerOnObject/Attribute:target_class' => 'Toegepast op klasse', 'Class:TriggerOnObject/Attribute:target_class+' => '', 'Class:TriggerOnObject/Attribute:filter' => 'Filter', - 'Class:TriggerOnObject/Attribute:filter+' => 'Limit the object list (of the target class) which will activate the trigger~~', + 'Class:TriggerOnObject/Attribute:filter+' => 'Beperk de objecten (van de opgegeven klasse) die de trigger zullen activeren.', 'TriggerOnObject:WrongFilterQuery' => 'Verkeerde filter-query: %1$s', 'TriggerOnObject:WrongFilterClass' => 'De filter-query moet verwijzen naar objecten van klasse "%1$s"', )); @@ -662,10 +664,10 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( // Dict::Add('NL NL', 'Dutch', 'Nederlands', array( - 'Class:TriggerOnObjectMention' => 'Trigger (on object mention)~~', - 'Class:TriggerOnObjectMention+' => 'Trigger on mention (@xxx) of an object of [a child class of] the given class in a log attribute~~', - 'Class:TriggerOnObjectMention/Attribute:mentioned_filter' => 'Mentioned filter~~', - 'Class:TriggerOnObjectMention/Attribute:mentioned_filter+' => 'Limit the list of mentioned objects which will activate the trigger. If empty, any mentioned object (of any class) will activate it.~~', + 'Class:TriggerOnObjectMention' => 'Trigger (bij vermelden van object)', + 'Class:TriggerOnObjectMention+' => 'Trigger bij vermelden (@xxx) van een object van de opgegeven klasse (of subklasse ervan) in een log', + 'Class:TriggerOnObjectMention/Attribute:mentioned_filter' => 'Filter', + 'Class:TriggerOnObjectMention/Attribute:mentioned_filter+' => 'Beperk de lijst van vermelde objecten die de trigger zullen activeren. Indien leeg, zullen alle objecten (van eender welke klasse) de trigger activeren.', )); // @@ -1095,8 +1097,8 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'Class:AsyncTask/Attribute:last_error+' => '', 'Class:AsyncTask/Attribute:last_attempt' => 'Laatste poging', 'Class:AsyncTask/Attribute:last_attempt+' => '', - 'Class:AsyncTask:InvalidConfig_Class_Keys' => 'Invalid format for the configuration of "async_task_retries[%1$s]". Expecting an array with the following keys: %2$s~~', - 'Class:AsyncTask:InvalidConfig_Class_InvalidKey_Keys' => 'Invalid format for the configuration of "async_task_retries[%1$s]": unexpected key "%2$s". Expecting only the following keys: %3$s~~', + 'Class:AsyncTask:InvalidConfig_Class_Keys' => 'Ongeldig formaat bij de configuratie van "async_tasks_retries[%1$s]". Er wordt een Array verwacht met de volgende sleutels: %2$s', + 'Class:AsyncTask:InvalidConfig_Class_InvalidKey_Keys' => 'Ongeldig formaat bij de configuratie van "async_tasks_retries[%1$s]": onverwachte sleutel "%2$s". Enkel deze sleutels worden verwacht: %3$s', )); // diff --git a/dictionaries/nl.dictionary.itop.ui.php b/dictionaries/nl.dictionary.itop.ui.php index ba5824b1e7..9381d81c10 100644 --- a/dictionaries/nl.dictionary.itop.ui.php +++ b/dictionaries/nl.dictionary.itop.ui.php @@ -112,7 +112,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'Class:User/Attribute:language/Value:FR FR+' => 'Frans (Frankrijk)', 'Class:User/Attribute:profile_list' => 'Profielen', 'Class:User/Attribute:profile_list+' => 'Rollen waarmee rechten verleend zijn aan deze account.', - 'Class:User/Attribute:allowed_org_list' => 'Mijn organisaties', + 'Class:User/Attribute:allowed_org_list' => 'Toegestane organisaties', 'Class:User/Attribute:allowed_org_list+' => 'De eindgebruiker heeft toestemming om data te bekijken van de gerelateerde organisaties. Als er geen organisatie is opgegeven, heeft de persoon toegang tot data van alle organisaties.', 'Class:User/Attribute:status' => 'Status', 'Class:User/Attribute:status+' => 'De gebruikersaccount kan in- of uitgeschakeld zijn.', @@ -121,10 +121,10 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'Class:User/Error:LoginMustBeUnique' => 'Login moet uniek zijn - "%1s" is al in gebruik', 'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Minstens één profiel moet toegewezen zijn aan deze gebruiker', - 'Class:User/Error:ProfileNotAllowed' => 'Profile "%1$s" cannot be added it will deny the access to backoffice~~', - 'Class:User/Error:StatusChangeIsNotAllowed' => 'Changing status is not allowed for your own User~~', - 'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Allowed organizations must contain User organization~~', - 'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'The current list of profiles does not give sufficient access rights (Users are not modifiable anymore)~~', + 'Class:User/Error:ProfileNotAllowed' => 'Profiel "%1$s" kan niet toegevoegd worden omdat het de toegang tot de backoffice zou ontzeggen.', + 'Class:User/Error:StatusChangeIsNotAllowed' => 'Je kan de status voor je eigen gebruikersaccount niet wijzigen.', + 'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'De toegestande organisaties moeten minstens de organisatie bevatten waartoe de gebruikersaccount behoort.', + 'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'De huidige lijst van profielen heeft niet voldoende toegangsrechten (gebruikersaccount zijn niet meer wijzigbaar).', 'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'Minstens één organisatie moet toegewezen zijn aan deze gebruiker', 'Class:User/Error:OrganizationNotAllowed' => 'Organisatie is niet toegestaan.', 'Class:User/Error:UserOrganizationNotAllowed' => 'De gebruikersaccount behoort niet tot de organisaties waar je zelf rechten voor hebt.', @@ -368,14 +368,14 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
    • Het beheren van het belangrijkste middel: documentatie.

    ', - 'UI:WelcomeMenu:Text'=> '
    Congratulations, you landed on '.ITOP_APPLICATION.' '.ITOP_VERSION_NAME.'!
    + 'UI:WelcomeMenu:Text'=> '
    Proficiat, je werkt nu met '.ITOP_APPLICATION.' '.ITOP_VERSION_NAME.'!
    -
    This version features a brand new modern and accessible backoffice design.
    +
    Deze versie heeft een volledig nieuw, modern en toegankelijk uiterlijk.
    -
    We kept '.ITOP_APPLICATION.' core functions that you liked and modernized them to make you love them. -We hope you’ll enjoy this version as much as we enjoyed imagining and creating it.
    +
    De belangrijkste en meest vertrouwde '.ITOP_APPLICATION.' functies hebben we behouden en een modern jasje gegeven. +We hopen dat je even hard van deze versie geniet als dat we zelf ervan hebben genoten om het te ontwerpen.
    -
    Customize your '.ITOP_APPLICATION.' preferences for a personalized experience.
    ~~', +
    Wijzig je '.ITOP_APPLICATION.'-voorkeuren voor een gepersonaliseerde ervaring.
    ', 'UI:WelcomeMenu:AllOpenRequests' => 'Open aanvragen: %1$d', 'UI:WelcomeMenu:MyCalls' => 'Mijn aanvragen', 'UI:WelcomeMenu:OpenIncidents' => 'Open incidenten: %1$d', @@ -383,8 +383,8 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:WelcomeMenu:MyIncidents' => 'Aan mij toegewezen incidenten', 'UI:AllOrganizations' => ' Alle Organisaties ', 'UI:YourSearch' => 'Jouw zoekopdracht', - 'UI:LoggedAsMessage' => 'Ingelogd als %1$s (%2$s)~~', - 'UI:LoggedAsMessage+Admin' => 'Ingelogd als %1$s (%2$s, Beheerder)~~', + 'UI:LoggedAsMessage' => 'Ingelogd als %1$s (%2$s)', + 'UI:LoggedAsMessage+Admin' => 'Ingelogd als %1$s (%2$s, Beheerder)', 'UI:Button:Logoff' => 'Log uit', 'UI:Button:GlobalSearch' => 'Zoek', 'UI:Button:Search' => ' Zoek ', @@ -423,7 +423,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Button:Insert' => 'Invoegen', 'UI:Button:More' => 'Meer', 'UI:Button:Less' => 'Minder', - 'UI:Button:Wait' => 'Please wait while updating fields~~', + 'UI:Button:Wait' => 'Even geduld terwijl de velden vernieuwd worden', 'UI:Treeview:CollapseAll' => 'Alles inklappen', 'UI:Treeview:ExpandAll' => 'Alles uitklappen', 'UI:UserPref:DoNotShowAgain' => 'Niet meer opnieuw tonen', @@ -470,7 +470,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Error:InvalidDashboard' => 'Fout: ongeldig dashboard', 'UI:Error:MaintenanceMode' => 'Toepassing is momenteel in onderhoud', 'UI:Error:MaintenanceTitle' => 'Onderhoud', - 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', + 'UI:Error:InvalidToken' => 'Fout: de gevraagde bewerking werd al uitgevoerd (CSRF token niet gevonden)', + + 'UI:Error:SMTP:UnknownVendor' => 'OAuth SMTP provider %1$s does not exist (email_transport_smtp.oauth.provider)~~', 'UI:GroupBy:Count' => 'Aantal', 'UI:GroupBy:Count+' => 'Aantal objecten', @@ -703,18 +705,18 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Audit:Dashboard:ObjectsAudited' => 'Gecontroleerde objecten', 'UI:Audit:Dashboard:ObjectsInError' => 'Foutieve objecten', 'UI:Audit:Dashboard:ObjectsValidated' => 'Gevalideerde objecten', - 'UI:Audit:AuditCategory:Subtitle' => '%1$s errors ouf of %2$s - %3$s%%~~', + 'UI:Audit:AuditCategory:Subtitle' => '%1$s fouten van de %2$s - %3$s%%', 'UI:RunQuery:Title' => ITOP_APPLICATION_SHORT.' - Evaluatie van OQL-query', 'UI:RunQuery:QueryExamples' => 'Voorbeelden van query\'s', - 'UI:RunQuery:QueryResults' => 'Query Results~~', + 'UI:RunQuery:QueryResults' => 'Query-resultaten', 'UI:RunQuery:HeaderPurpose' => 'Doel', 'UI:RunQuery:HeaderPurpose+' => 'Uitleg over de query', 'UI:RunQuery:HeaderOQLExpression' => 'OQL-expressie', 'UI:RunQuery:HeaderOQLExpression+' => 'De query in OQL syntax', 'UI:RunQuery:ExpressionToEvaluate' => 'Expressie om te evalueren: ', - 'UI:RunQuery:QueryArguments' => 'Query Arguments~~', + 'UI:RunQuery:QueryArguments' => 'Query-argumenten', 'UI:RunQuery:MoreInfo' => 'Meer informatie over de query: ', 'UI:RunQuery:DevelopedQuery' => 'Herschreven query-expressie: ', 'UI:RunQuery:SerializedFilter' => 'Geserialiseerde filter: ', @@ -726,7 +728,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Query:UrlForExcel' => 'URL om te gebruiken voor MS Excel-webquery\'s', 'UI:Query:UrlV1' => 'De lijst van velden is leeg gelaten. De pagina export-V2.php kan niet aangeroepen worden zonder deze informatie.Daarom verwijst de onderstaande link naar de oude export-pagina: export.php. Deze verouderde versie heeft enkele beperkingen: de lijst van geëxporteerde velden kan verschillen afhankelijk van het gekozen export-formaat en het datamodel van '.ITOP_APPLICATION_SHORT.'. Als je wil dat de lijst van geëxporteerde kolommen hetzelfde blijft over lange tijd, dan moet je een waarde opgeven voor het attribuut "Velden" en de pagina export-V2.php gebruiken.', 'UI:Schema:Title' => ITOP_APPLICATION_SHORT.' objecten-schema', - 'UI:Schema:TitleForClass' => '%1$s schema~~', + 'UI:Schema:TitleForClass' => '%1$s schema', 'UI:Schema:CategoryMenuItem' => 'Categorie %1$s', 'UI:Schema:Relationships' => 'Relaties', 'UI:Schema:AbstractClass' => 'Abstracte klasse: objecten van deze klasse kunnen niet worden geïnstantieerd.', @@ -793,7 +795,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Schema:Attribute/Filter' => 'Filter', 'UI:Schema:DefaultNullValue' => 'Standaardwaarde null : "%1$s"', 'UI:LinksWidget:Autocomplete+' => 'Typ de eerste 3 karakters...', - 'UI:Edit:SearchQuery' => 'Select a predefined query~~', + 'UI:Edit:SearchQuery' => 'Kies een vooraf gedefinieerde query', 'UI:Edit:TestQuery' => 'Test query', 'UI:Combo:SelectValue' => '--- selecteer een waarde ---', 'UI:Label:SelectedObjects' => 'Geselecteerde objecten: ', @@ -992,24 +994,24 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating

    -Acties define the actions to be performed when the triggers execute. For now there are only two kind of actions: +Acties definieer de acties die uitgevoerd moeten worden als de triggers geactiveerd worden. Er zijn momenteel 2 soorten standaardacties:

      -
    1. Sending an email message: Such actions also define the template to be used for sending the email as well as the other parameters of the message like the recipients, importance, etc.
      +
    2. Stuur een e-mail: Bij deze acties definieer je een sjabloon voor het bericht en ook parameters zoals ontvanger(s), prioriteit, enz.
      Een speciale testpagina (email.test.php) is beschikbaar voor het testen en oplossen van eventuele problemen met jouw PHP e-mailconfiguratie.
    3. -
    4. Outgoing webhooks: Allow integration with a third-party application by sending structured data to a defined URL.
    5. +
    6. Webhooks: Staat toe om te integreren met toepassingen van derde partijen, door gestructureerde data te sturen naar een URL.

    Acties moeten gekoppeld zijn aan triggers. -Bij die koppeling wordt aan elke actie een volgorde-nummer gegeven. Dit bepaalt in welke volgorde de acties moeten worden uitgevoerd.

    ~~', +Bij die koppeling wordt aan elke actie een volgorde-nummer gegeven. Dit bepaalt in welke volgorde de acties moeten worden uitgevoerd.

    ', 'UI:NotificationsMenu:Triggers' => 'Triggers', 'UI:NotificationsMenu:AvailableTriggers' => 'Beschikbare triggers', 'UI:NotificationsMenu:OnCreate' => 'Wanneer een object is aangemaakt', 'UI:NotificationsMenu:OnStateEnter' => 'Wanneer een object een bepaalde fase intreedt', 'UI:NotificationsMenu:OnStateLeave' => 'Wanneer een object een bepaalde fase uittreedt', 'UI:NotificationsMenu:Actions' => 'Acties', - 'UI:NotificationsMenu:Actions:ActionEmail' => 'Email actions~~', - 'UI:NotificationsMenu:Actions:ActionWebhook' => 'Webhook actions (outgoing integrations)~~', - 'UI:NotificationsMenu:Actions:Action' => 'Other actions~~', + 'UI:NotificationsMenu:Actions:ActionEmail' => 'Email acties', + 'UI:NotificationsMenu:Actions:ActionWebhook' => 'Webhook acties (uitgaande integraties)', + 'UI:NotificationsMenu:Actions:Action' => 'Andere acties', 'UI:NotificationsMenu:AvailableActions' => 'Beschikbare acties', 'Menu:TagAdminMenu' => 'Tags-configuratie', @@ -1053,8 +1055,8 @@ Bij die koppeling wordt aan elke actie een volgorde-nummer gegeven. Dit bepaalt 'UI:iTopVersion:Long' => '%1$s versie %2$s-%3$s uitgegeven op %4$s', 'UI:PropertiesTab' => 'Eigenschappen', - 'UI:OpenDocumentInNewWindow_' => 'Open~~', - 'UI:DownloadDocument_' => 'Download~~', + 'UI:OpenDocumentInNewWindow_' => 'Open', + 'UI:DownloadDocument_' => 'Download', 'UI:Document:NoPreview' => 'Er is geen voorbeeld beschikbaar voor dit soort document', 'UI:Download-CSV' => 'Download %1$s', @@ -1176,7 +1178,7 @@ Bij die koppeling wordt aan elke actie een volgorde-nummer gegeven. Dit bepaalt 'UI:ArchiveMode:Banner+' => 'Gearchiveerde objecten zijn zichtbaar, maar kunnen niet worden aangepast', 'UI:FavoriteOrganizations' => 'Favoriete organisaties', 'UI:FavoriteOrganizations+' => 'Duid in onderstaande lijst de organisaties aan die je wilt zien in de keuzelijst voor een snelle toegang. Dit is geen beveiligingsinstelling; objecten van elke organisatie zijn nog steed zichtbaar en toegankelijk door "Alle Organisaties" te selecteren in de keuzelijst.', - 'UI:FavoriteLanguage' => 'Taal van de gebruikersinterface~~', + 'UI:FavoriteLanguage' => 'Taal van de gebruikersinterface', 'UI:Favorites:SelectYourLanguage' => 'Selecteer jouw taal', 'UI:FavoriteOtherSettings' => 'Overige instellingen', 'UI:Favorites:Default_X_ItemsPerPage' => 'Standaardlengte: %1$s items per pagina', @@ -1218,16 +1220,16 @@ Bij die koppeling wordt aan elke actie een volgorde-nummer gegeven. Dit bepaalt 'UI:OQL:UnknownClassAndFix' => 'Onbekende klasse "%1$s". Je zou "%2$s" kunnen proberen.', 'UI:OQL:UnknownClassNoFix' => 'Onbekende klasse "%1$s"', - 'UI:Dashboard:EditCustom' => 'Edit custom version...~~', - 'UI:Dashboard:CreateCustom' => 'Create a custom version...~~', - 'UI:Dashboard:DeleteCustom' => 'Delete custom version...~~', + 'UI:Dashboard:EditCustom' => 'Bewerk aangepaste versie...', + 'UI:Dashboard:CreateCustom' => 'Maak aangepaste versie...', + 'UI:Dashboard:DeleteCustom' => 'Verwijder aangepaste versie...', 'UI:Dashboard:RevertConfirm' => 'Alle bewerkingen die zijn gemaakt aan de originele versie zullen verloren gaan. Bevestig dat je wilt doorgaan.', 'UI:ExportDashBoard' => 'Exporteer naar een bestand', 'UI:ImportDashBoard' => 'Importeer vanuit een bestand', 'UI:ImportDashboardTitle' => 'Importeer vanuit een bestand', 'UI:ImportDashboardText' => 'Selecteer een bestand van het dashboard om te importeren:', 'UI:Dashboard:Actions' => 'Dashboard acties', - 'UI:Dashboard:NotUpToDateUntilContainerSaved' => 'This dashboard displays information that does not include the on-going changes.~~', + 'UI:Dashboard:NotUpToDateUntilContainerSaved' => 'Dit dashboard toont informatie die nog geen rekening houdt met de wijzigingen die nu gemaakt worden.', 'UI:DashletCreation:Title' => 'Maak een nieuwe Dashlet aan', @@ -1240,8 +1242,8 @@ Bij die koppeling wordt aan elke actie een volgorde-nummer gegeven. Dit bepaalt 'UI:DashboardEdit:AutoReload' => 'Automatisch vernieuwen', 'UI:DashboardEdit:AutoReloadSec' => 'Interval voor het automatisch vernieuwen (seconden)', 'UI:DashboardEdit:AutoReloadSec+' => 'Het toegestane minimum is 5 seconden', - 'UI:DashboardEdit:Revert' => 'Revert~~', - 'UI:DashboardEdit:Apply' => 'Apply~~', + 'UI:DashboardEdit:Revert' => 'Herstel', + 'UI:DashboardEdit:Apply' => 'Opslaan', 'UI:DashboardEdit:Layout' => 'Layout', 'UI:DashboardEdit:Properties' => 'Eigenschappen van dashboard', @@ -1500,9 +1502,9 @@ Bij die koppeling wordt aan elke actie een volgorde-nummer gegeven. Dit bepaalt 'UI:Search:AddCriteria:List:RecentlyUsed:Placeholder' => 'Nog geen.', // - Criteria header actions - 'UI:Search:Criteria:Toggle' => 'Minimize / Expand~~', - 'UI:Search:Criteria:Remove' => 'Remove~~', - 'UI:Search:Criteria:Locked' => 'Locked~~', + 'UI:Search:Criteria:Toggle' => 'Klap in / uit', + 'UI:Search:Criteria:Remove' => 'Verwijder', + 'UI:Search:Criteria:Locked' => 'Vergrendeld', // - Criteria titles // - Default widget @@ -1659,30 +1661,30 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'Menu:AuditCategories' => 'Auditcategorieën', 'Menu:AuditCategories+' => 'Auditcategorieën', 'Menu:Notifications:Title' => 'Auditcategorieën', - 'Menu:RunQueriesMenu' => 'Query\'s uitvoeren', - 'Menu:RunQueriesMenu+' => 'Voer een query uit', - 'Menu:QueryMenu' => 'Favoriete query\'s', - 'Menu:QueryMenu+' => 'Favoriete query\'s', - 'Menu:UniversalSearchMenu' => 'Globale zoekopdracht', - 'Menu:UniversalSearchMenu+' => 'Zoek in alle data...', - 'Menu:UserManagementMenu' => 'Gebruikersbeheer', - 'Menu:UserManagementMenu+' => 'Gebruikersbeheer', - 'Menu:ProfilesMenu' => 'Profielen', - 'Menu:ProfilesMenu+' => 'Profielen', - 'Menu:ProfilesMenu:Title' => 'Profielen', - 'Menu:UserAccountsMenu' => 'Gebruikersaccounts', - 'Menu:UserAccountsMenu+' => 'Gebruikersaccounts', + 'Menu:RunQueriesMenu' => 'Query\'s uitvoeren', + 'Menu:RunQueriesMenu+' => 'Voer een query uit', + 'Menu:QueryMenu' => 'Voorgedefinieerde query\'s', + 'Menu:QueryMenu+' => 'Voorgedefinieerde query\'s', + 'Menu:UniversalSearchMenu' => 'Globale zoekopdracht', + 'Menu:UniversalSearchMenu+' => 'Zoek in alle data...', + 'Menu:UserManagementMenu' => 'Gebruikersbeheer', + 'Menu:UserManagementMenu+' => 'Gebruikersbeheer', + 'Menu:ProfilesMenu' => 'Profielen', + 'Menu:ProfilesMenu+' => 'Profielen', + 'Menu:ProfilesMenu:Title' => 'Profielen', + 'Menu:UserAccountsMenu' => 'Gebruikersaccounts', + 'Menu:UserAccountsMenu+' => 'Gebruikersaccounts', 'Menu:UserAccountsMenu:Title' => 'Gebruikersaccounts', - 'Menu:MyShortcuts' => 'Mijn snelkoppelingen', - 'Menu:UserManagement' => 'Gebruikersbeheer', - 'Menu:Queries' => 'Query\'s', - 'Menu:ConfigurationTools' => 'Configuratie', + 'Menu:MyShortcuts' => 'Mijn snelkoppelingen', + 'Menu:UserManagement' => 'Gebruikersbeheer', + 'Menu:Queries' => 'Query\'s', + 'Menu:ConfigurationTools' => 'Configuratie', )); // Additional language entries not present in English dict Dict::Add('NL NL', 'Dutch', 'Nederlands', array( - 'UI:Toggle:StandardDashboard' => 'Standaard', - 'UI:Toggle:CustomDashboard' => 'Aangepast', - 'UI:Dashboard:Edit' => 'Bewerk deze pagina...', - 'UI:Dashboard:Revert' => 'Herstel de originele versie...', + 'UI:Toggle:StandardDashboard' => 'Standaard', + 'UI:Toggle:CustomDashboard' => 'Aangepast', + 'UI:Dashboard:Edit' => 'Bewerk deze pagina...', + 'UI:Dashboard:Revert' => 'Herstel de originele versie...', )); diff --git a/dictionaries/pl.dictionary.itop.core.php b/dictionaries/pl.dictionary.itop.core.php index e38f2fb2c9..279a62c2a6 100644 --- a/dictionaries/pl.dictionary.itop.core.php +++ b/dictionaries/pl.dictionary.itop.core.php @@ -22,22 +22,24 @@ */ Dict::Add('PL PL', 'Polish', 'Polski', array( 'Core:DeletedObjectLabel' => '%1s (usunięto)', - 'Core:DeletedObjectTip' => 'Obiekt został usunięty w dniu %1$s (%2$s)', + 'Core:DeletedObjectTip' => 'Obiekt został usunięty w dniu %1$s (%2$s)', 'Core:UnknownObjectLabel' => 'Nie znaleziono obiektu (klasa: %1$s, id: %2$d)', - 'Core:UnknownObjectTip' => 'Nie można znaleźć obiektu. Być może został usunięty jakiś czas temu, a od tego czasu dziennik został wyczyszczony.', + 'Core:UnknownObjectTip' => 'Nie można znaleźć obiektu. Być może został usunięty jakiś czas temu, a od tego czasu dziennik został wyczyszczony.', 'Core:UniquenessDefaultError' => 'Błąd zasady niepowtarzalności \'%1$s\'', + 'Core:CheckConsistencyError' => 'Consistency rules not followed: %1$s~~', + 'Core:CheckValueError' => 'Unexpected value for attribute \'%1$s\' (%2$s) : %3$s~~', - 'Core:AttributeLinkedSet' => 'Tablica obiektów', + 'Core:AttributeLinkedSet' => 'Tablica obiektów', 'Core:AttributeLinkedSet+' => 'Wszelkiego rodzaju obiekty tej samej klasy lub podklasy', 'Core:AttributeLinkedSetDuplicatesFound' => 'Duplikaty w polu \'%1$s\' : %2$s', - 'Core:AttributeDashboard' => 'Pulpit', + 'Core:AttributeDashboard' => 'Pulpit', 'Core:AttributeDashboard+' => '', - 'Core:AttributePhoneNumber' => 'Numer telefonu', + 'Core:AttributePhoneNumber' => 'Numer telefonu', 'Core:AttributePhoneNumber+' => '', 'Core:AttributeObsolescenceDate' => 'Data utraty ważności', @@ -518,9 +520,9 @@ Dict::Add('PL PL', 'Polish', 'Polski', array( 'Class:ActionEmail' => 'Powiadomienie e-mail', 'Class:ActionEmail+' => '', 'Class:ActionEmail/Attribute:status+' => 'Ten status decyduje o tym, kto zostanie powiadomiony: tylko odbiorca testowy, wszyscy (Do, DW i UDW) lub nikt', - 'Class:ActionEmail/Attribute:status/Value:test+' => 'Powiadomiony zostanie tylko odbiorca testowy', - 'Class:ActionEmail/Attribute:status/Value:enabled+' => 'Wszystkie e-maile "Do", "DW" i "UDW" są powiadamiane', - 'Class:ActionEmail/Attribute:status/Value:disabled+' => 'Powiadomienie e-mail nie zostanie wysłane', + 'Class:ActionEmail/Attribute:status/Value:test' => 'Powiadomiony zostanie tylko odbiorca testowy', + 'Class:ActionEmail/Attribute:status/Value:enabled' => 'Wszystkie e-maile "Do", "DW" i "UDW" są powiadamiane', + 'Class:ActionEmail/Attribute:status/Value:disabled' => 'Powiadomienie e-mail nie zostanie wysłane', 'Class:ActionEmail/Attribute:test_recipient' => 'Odbiorca testowy', 'Class:ActionEmail/Attribute:test_recipient+' => 'Miejsce docelowe w przypadku, gdy status jest ustawiony na "Test"', 'Class:ActionEmail/Attribute:from' => 'Z', diff --git a/dictionaries/pl.dictionary.itop.ui.php b/dictionaries/pl.dictionary.itop.ui.php index 82d9133dec..20594a471f 100644 --- a/dictionaries/pl.dictionary.itop.ui.php +++ b/dictionaries/pl.dictionary.itop.ui.php @@ -1657,30 +1657,46 @@ Dict::Add('PL PL', 'Polish', 'Polski', array( 'Menu:AuditCategories' => 'Kategorie audytu', 'Menu:AuditCategories+' => 'Kategorie audytu', 'Menu:Notifications:Title' => 'Kategorie audytu', - 'Menu:RunQueriesMenu' => 'Zapytania', - 'Menu:RunQueriesMenu+' => 'Uruchom dowolne zapytanie', - 'Menu:QueryMenu' => 'Słownik zapytań', - 'Menu:QueryMenu+' => 'Słownik zapytań', - 'Menu:UniversalSearchMenu' => 'Wyszukiwanie uniwersalne', - 'Menu:UniversalSearchMenu+' => 'Wyszukiwanie wszystkiego...', - 'Menu:UserManagementMenu' => 'Zarządzanie użytkownikami', - 'Menu:UserManagementMenu+' => 'UZarządzanie użytkownikami', - 'Menu:ProfilesMenu' => 'Profile', - 'Menu:ProfilesMenu+' => 'Profile', - 'Menu:ProfilesMenu:Title' => 'Profile', - 'Menu:UserAccountsMenu' => 'Konta użytkowników', - 'Menu:UserAccountsMenu+' => 'Konta użytkowników', + 'Menu:RunQueriesMenu' => 'Zapytania', + 'Menu:RunQueriesMenu+' => 'Uruchom dowolne zapytanie', + 'Menu:QueryMenu' => 'Słownik zapytań', + 'Menu:QueryMenu+' => 'Słownik zapytań', + 'Menu:UniversalSearchMenu' => 'Wyszukiwanie uniwersalne', + 'Menu:UniversalSearchMenu+' => 'Wyszukiwanie wszystkiego...', + 'Menu:UserManagementMenu' => 'Zarządzanie użytkownikami', + 'Menu:UserManagementMenu+' => 'UZarządzanie użytkownikami', + 'Menu:ProfilesMenu' => 'Profile', + 'Menu:ProfilesMenu+' => 'Profile', + 'Menu:ProfilesMenu:Title' => 'Profile', + 'Menu:UserAccountsMenu' => 'Konta użytkowników', + 'Menu:UserAccountsMenu+' => 'Konta użytkowników', 'Menu:UserAccountsMenu:Title' => 'Konta użytkowników', - 'Menu:MyShortcuts' => 'Moje skróty', - 'Menu:UserManagement' => 'Zarządzanie użytkownikami', - 'Menu:Queries' => 'Zapytania', - 'Menu:ConfigurationTools' => 'Konfiguracja', + 'Menu:MyShortcuts' => 'Moje skróty', + 'Menu:UserManagement' => 'Zarządzanie użytkownikami', + 'Menu:Queries' => 'Zapytania', + 'Menu:ConfigurationTools' => 'Konfiguracja', +)); + +// OAuth +Dict::Add('PL PL', 'Polish', 'Polski', array( + 'Menu:OAuthWizardMenu' => 'OAuth 2.0~~', + 'core/Operation:Wizard/Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Page:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Panel:Title' => 'OAuth 2.0 Configuration~~', + 'UI:OAuth:Wizard:Form:Input:ClientId:Label' => 'Client Id~~', + 'UI:OAuth:Wizard:Form:Input:ClientSecret:Label' => 'Client Secret~~', + 'UI:OAuth:Wizard:Form:Input:Scope:Label' => 'Scope~~', + 'UI:OAuth:Wizard:Form:Input:Additional:Label' => 'Additional parameters~~', + 'UI:OAuth:Wizard:Form:Input:RedirectUri:Label' => 'Redirect Uri~~', + 'UI:OAuth:Wizard:Form:Button:Submit:Label' => 'Authentication~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Title' => 'Configuration for SMTP~~', + 'UI:OAuth:Wizard:ResultConf:Panel:Description' => 'Paste this content into your configuration file to use this OAuth connection for your outgoing emails~~', )); // Additional language entries not present in English dict Dict::Add('PL PL', 'Polish', 'Polski', array( - 'UI:Button:SearchInHIerarchy' => ' Szukaj w hierarchii ', - 'UI:Error:TemplateRendering' => 'Błąd renderowania szablonu', - 'UI:Toggle:StandardDashboard' => 'Standard', - 'UI:Toggle:CustomDashboard' => 'Własny', + 'UI:Button:SearchInHIerarchy' => ' Szukaj w hierarchii ', + 'UI:Error:TemplateRendering' => 'Błąd renderowania szablonu', + 'UI:Toggle:StandardDashboard' => 'Standard', + 'UI:Toggle:CustomDashboard' => 'Własny', )); diff --git a/dictionaries/pt_br.dictionary.itop.core.php b/dictionaries/pt_br.dictionary.itop.core.php index 31e928b281..5cb8420b13 100644 --- a/dictionaries/pt_br.dictionary.itop.core.php +++ b/dictionaries/pt_br.dictionary.itop.core.php @@ -23,28 +23,30 @@ */ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Core:DeletedObjectLabel' => '%1s (excluído)', - 'Core:DeletedObjectTip' => 'O objeto foi excluído em %1$s (%2$s)', + 'Core:DeletedObjectTip' => 'O objeto foi excluído em %1$s (%2$s)', 'Core:UnknownObjectLabel' => 'Objeto não encontrado (classe: %1$s, id: %2$d)', - 'Core:UnknownObjectTip' => 'O objeto não pode ser encontrado. Ele pode ter sido eliminado há algum tempo e o log foi removido desde então.', + 'Core:UnknownObjectTip' => 'O objeto não pode ser encontrado. Ele pode ter sido eliminado há algum tempo e o log foi removido desde então', 'Core:UniquenessDefaultError' => 'Regra de exclusividade \'%1$s\' com erro', + 'Core:CheckConsistencyError' => 'Regras de consistência não seguidas: %1$s', + 'Core:CheckValueError' => 'Valor inesperado para o atributo \'%1$s\' (%2$s) : %3$s~~', 'Core:AttributeLinkedSet' => 'Array de objetos', - 'Core:AttributeLinkedSet+' => 'Qualquer tipo de objetos da mesma classe ou subclasses', + 'Core:AttributeLinkedSet+' => 'Quaisquer tipos de objetos da mesma classe ou subclasses', - 'Core:AttributeLinkedSetDuplicatesFound' => 'Duplicates in the \'%1$s\' field : %2$s~~', + 'Core:AttributeLinkedSetDuplicatesFound' => 'Duplicatas no campo \'%1$s\' : %2$s', - 'Core:AttributeDashboard' => 'Painel de controle', + 'Core:AttributeDashboard' => 'Painel do '.ITOP_APPLICATION_SHORT, 'Core:AttributeDashboard+' => '', - 'Core:AttributePhoneNumber' => 'Número de telefone', + 'Core:AttributePhoneNumber' => 'Número de telefone', 'Core:AttributePhoneNumber+' => '', - 'Core:AttributeObsolescenceDate' => 'Obsolescência data', + 'Core:AttributeObsolescenceDate' => 'Data de obsolescência', 'Core:AttributeObsolescenceDate+' => '', - 'Core:AttributeTagSet' => 'Lista de etiquetas', + 'Core:AttributeTagSet' => 'Lista de tags', 'Core:AttributeTagSet+' => '', 'Core:AttributeSet:placeholder' => 'clique para adicionar', 'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromClass' => '%1$s (%2$s)', @@ -55,10 +57,10 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Core:AttributeCaseLog+' => '', 'Core:AttributeMetaEnum' => 'Enum Computado', - 'Core:AttributeMetaEnum+' => '', + 'Core:AttributeMetaEnum+' => 'Exibir Strings alfanuméricas computadas', 'Core:AttributeLinkedSetIndirect' => 'Array de objetos (N-N)', - 'Core:AttributeLinkedSetIndirect+' => 'Qualquer tipo de objetos [sub-classe] da mesma classe', + 'Core:AttributeLinkedSetIndirect+' => 'Qualquer tipo de objetos [subclasse] da mesma classe', 'Core:AttributeInteger' => 'Inteiro', 'Core:AttributeInteger+' => 'Valor numérico (não pode ser negativo)', @@ -66,40 +68,40 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Core:AttributeDecimal' => 'Decimal', 'Core:AttributeDecimal+' => 'Valor decimal (não pode ser negativo)', - 'Core:AttributeBoolean' => 'Boolean', + 'Core:AttributeBoolean' => 'Booleano', 'Core:AttributeBoolean+' => '', 'Core:AttributeBoolean/Value:null' => '', 'Core:AttributeBoolean/Value:yes' => 'Sim', 'Core:AttributeBoolean/Value:no' => 'Não', - 'Core:AttributeArchiveFlag' => 'Arquivar bandeira', + 'Core:AttributeArchiveFlag' => 'Flag de arquivamento', 'Core:AttributeArchiveFlag/Value:yes' => 'Sim', - 'Core:AttributeArchiveFlag/Value:yes+' => 'Este objeto é visível apenas no modo de arquivo', + 'Core:AttributeArchiveFlag/Value:yes+' => 'Este objeto é visível apenas no modo de arquivamento', 'Core:AttributeArchiveFlag/Value:no' => 'Não', 'Core:AttributeArchiveFlag/Label' => 'Arquivado', 'Core:AttributeArchiveFlag/Label+' => '', 'Core:AttributeArchiveDate/Label' => 'Data de arquivamento', 'Core:AttributeArchiveDate/Label+' => '', - 'Core:AttributeObsolescenceFlag' => 'Obsolescence flag~~', + 'Core:AttributeObsolescenceFlag' => 'Flag de obsolescência', 'Core:AttributeObsolescenceFlag/Value:yes' => 'Sim', - 'Core:AttributeObsolescenceFlag/Value:yes+' => 'Este objeto é excluído da análise de impacto e oculto dos resultados de pesquisa', + 'Core:AttributeObsolescenceFlag/Value:yes+' => 'Este objeto será excluído da análise de impacto e ocultado dos resultados de pesquisa', 'Core:AttributeObsolescenceFlag/Value:no' => 'Não', 'Core:AttributeObsolescenceFlag/Label' => 'Obsoleto', - 'Core:AttributeObsolescenceFlag/Label+' => 'Computado dinamicamente em outros atributos', - 'Core:AttributeObsolescenceDate/Label' => 'Obsolescência data', + 'Core:AttributeObsolescenceFlag/Label+' => 'Calculado dinamicamente com base em outros atributos do objeto', + 'Core:AttributeObsolescenceDate/Label' => 'Data de obsolescência', 'Core:AttributeObsolescenceDate/Label+' => 'Data aproximada em que o objeto foi considerado obsoleto', 'Core:AttributeString' => 'String', - 'Core:AttributeString+' => 'Seqüência alfanumérica', + 'Core:AttributeString+' => 'Sequência alfanumérica', 'Core:AttributeClass' => 'Classe', 'Core:AttributeClass+' => '', - 'Core:AttributeApplicationLanguage' => 'Linguagem usuário', - 'Core:AttributeApplicationLanguage+' => 'Linguagem e país (EN US)', + 'Core:AttributeApplicationLanguage' => 'Idioma do usuário', + 'Core:AttributeApplicationLanguage+' => 'Idioma e país (por exemplo: EN US)', - 'Core:AttributeFinalClass' => 'Classe (auto)', + 'Core:AttributeFinalClass' => 'Classe (automática)', 'Core:AttributeFinalClass+' => 'Classe real do objeto (criada automaticamente pelo sistema)', 'Core:AttributePassword' => 'Senha', @@ -114,28 +116,28 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Core:AttributeText+' => 'Cadeia de caracteres Multi-linha', 'Core:AttributeHTML' => 'HTML', - 'Core:AttributeHTML+' => 'HTML string', + 'Core:AttributeHTML+' => 'String HTML', - 'Core:AttributeEmailAddress' => 'Endereço email', + 'Core:AttributeEmailAddress' => 'Endereço de e-mail', 'Core:AttributeEmailAddress+' => '', 'Core:AttributeIPAddress' => 'Endereço IP', 'Core:AttributeIPAddress+' => '', 'Core:AttributeOQL' => 'OQL', - 'Core:AttributeOQL+' => 'Expressão Object Query Langage', + 'Core:AttributeOQL+' => 'Expressão Object Query Language (OQL)', 'Core:AttributeEnum' => 'Enum', - 'Core:AttributeEnum+' => 'Lista de pré-definida seqüências alfanuméricas', + 'Core:AttributeEnum+' => 'Lista de sequências alfanuméricas pré-definidas', - 'Core:AttributeTemplateString' => 'Modelo string', - 'Core:AttributeTemplateString+' => 'Espaço reservado contendo string', + 'Core:AttributeTemplateString' => 'String do modelo', + 'Core:AttributeTemplateString+' => 'String de uma linha, contendo espaços reservados para dados do '.ITOP_APPLICATION_SHORT, - 'Core:AttributeTemplateText' => 'Template text', - 'Core:AttributeTemplateText+' => 'Texto contendo espaços reservados', + 'Core:AttributeTemplateText' => 'Texto do modelo', + 'Core:AttributeTemplateText+' => 'Texto contendo espaços reservados para dados do '.ITOP_APPLICATION_SHORT, - 'Core:AttributeTemplateHTML' => 'Modelo HTML', - 'Core:AttributeTemplateHTML+' => 'HTML contendo espaços reservados', + 'Core:AttributeTemplateHTML' => 'HTML do modelo', + 'Core:AttributeTemplateHTML+' => 'Código HTML contendo espaços reservados para dados do '.ITOP_APPLICATION_SHORT, 'Core:AttributeDateTime' => 'Data/hora', 'Core:AttributeDateTime+' => 'Data e hora (ano-mês-dia hh:mm:ss)', @@ -170,38 +172,38 @@ Operadores:
    [data,data]

    ', - 'Core:AttributeDeadline' => 'Tempo determinado', + 'Core:AttributeDeadline' => 'Prazo determinado', 'Core:AttributeDeadline+' => 'Data, apresentada relativamente ao tempo atual', 'Core:AttributeExternalKey' => 'Chave externa', 'Core:AttributeExternalKey+' => 'Chave externa (ou foreign)', 'Core:AttributeHierarchicalKey' => 'Chave hierárquica', - 'Core:AttributeHierarchicalKey+' => 'Chave externa (ou foreign) para o principal', + 'Core:AttributeHierarchicalKey+' => 'Chave externa (ou foreign key) para o objeto pai', 'Core:AttributeExternalField' => 'Campo externo', 'Core:AttributeExternalField+' => 'Campo mapeado para uma chave externa', 'Core:AttributeURL' => 'URL', - 'Core:AttributeURL+' => 'URL absoluto ou relativo como um texto', + 'Core:AttributeURL+' => 'URL absoluto ou relativo como texto', 'Core:AttributeBlob' => 'Blob', 'Core:AttributeBlob+' => 'Qualquer conteúdo binário (documento)', - 'Core:AttributeOneWayPassword' => 'Uma forma de senha', - 'Core:AttributeOneWayPassword+' => 'Uma forma de senha encriptado', + 'Core:AttributeOneWayPassword' => 'Senha criptografada', + 'Core:AttributeOneWayPassword+' => 'Uma senha encriptada de uma só via (one-way)', 'Core:AttributeTable' => 'Tabela', - 'Core:AttributeTable+' => 'Matriz indexada tem duas dimensões', + 'Core:AttributeTable+' => 'Matriz indexada com duas dimensões', 'Core:AttributePropertySet' => 'Propriedades', 'Core:AttributePropertySet+' => 'Lista de propriedades sem categoria (nome e valor)', 'Core:AttributeFriendlyName' => 'Nome amigável', - 'Core:AttributeFriendlyName+' => 'Atributo criado automaticamente; o nome amigável é gerado depois de vários atributos', + 'Core:AttributeFriendlyName+' => 'Atributo criado automaticamente; o nome amigável é baseado nos diferentes atributos do objeto', 'Core:FriendlyName-Label' => 'Nome amigável', - 'Core:FriendlyName-Description' => 'Nome amigável', + 'Core:FriendlyName-Description' => '', 'Core:AttributeTag' => 'Etiquetas', 'Core:AttributeTag+' => '', @@ -211,7 +213,7 @@ Operadores:
    'Core:Context=Setup' => 'Setup', 'Core:Context=GUI:Console' => 'Console', 'Core:Context=CRON' => 'cron', - 'Core:Context=GUI:Portal' => 'Portal', + 'Core:Context=GUI:Portal' => 'Portal do usuário', )); @@ -225,20 +227,20 @@ Operadores:
    // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:CMDBChange' => 'Mudanças', - 'Class:CMDBChange+' => 'Rastreamento de mudanças', + 'Class:CMDBChange' => 'Alterações no CMDB', + 'Class:CMDBChange+' => 'Controle de mudanças no CMDB', 'Class:CMDBChange/Attribute:date' => 'Data', - 'Class:CMDBChange/Attribute:date+' => 'Data e hora em que as mudanças foram registrados', - 'Class:CMDBChange/Attribute:userinfo' => 'Mais Informações', - 'Class:CMDBChange/Attribute:userinfo+' => 'Informações solicitantes definidos', - 'Class:CMDBChange/Attribute:origin/Value:interactive' => 'User interaction in the GUI~~', - 'Class:CMDBChange/Attribute:origin/Value:csv-import.php' => 'CSV import script~~', - 'Class:CMDBChange/Attribute:origin/Value:csv-interactive' => 'CSV import in the GUI~~', - 'Class:CMDBChange/Attribute:origin/Value:email-processing' => 'Email processing~~', - 'Class:CMDBChange/Attribute:origin/Value:synchro-data-source' => 'Synchro. data source~~', - 'Class:CMDBChange/Attribute:origin/Value:webservice-rest' => 'REST/JSON webservices~~', - 'Class:CMDBChange/Attribute:origin/Value:webservice-soap' => 'SOAP webservices~~', - 'Class:CMDBChange/Attribute:origin/Value:custom-extension' => 'By an extension~~', + 'Class:CMDBChange/Attribute:date+' => 'Data e hora em que as alterações foram registradas', + 'Class:CMDBChange/Attribute:userinfo' => 'Informações adicionais', + 'Class:CMDBChange/Attribute:userinfo+' => 'Informações definidas pelos solicitantes', + 'Class:CMDBChange/Attribute:origin/Value:interactive' => 'Interação do usuário (GUI)', + 'Class:CMDBChange/Attribute:origin/Value:csv-import.php' => 'Script de importação CSV', + 'Class:CMDBChange/Attribute:origin/Value:csv-interactive' => 'Importação de CSV interativa (GUI)', + 'Class:CMDBChange/Attribute:origin/Value:email-processing' => 'Processamento de e-mail', + 'Class:CMDBChange/Attribute:origin/Value:synchro-data-source' => 'Origem de dados Synchro', + 'Class:CMDBChange/Attribute:origin/Value:webservice-rest' => 'REST/JSON webservices', + 'Class:CMDBChange/Attribute:origin/Value:webservice-soap' => 'SOAP WebServices', + 'Class:CMDBChange/Attribute:origin/Value:custom-extension' => 'Por uma extensão', )); // @@ -246,19 +248,19 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:CMDBChangeOp' => 'Operações de mudanças', - 'Class:CMDBChangeOp+' => 'Operações de controle de mudança', - 'Class:CMDBChangeOp/Attribute:change' => 'Mudança', + 'Class:CMDBChangeOp' => 'Operações de alteração', + 'Class:CMDBChangeOp+' => 'Operações de controle de alteração', + 'Class:CMDBChangeOp/Attribute:change' => 'Alteração', 'Class:CMDBChangeOp/Attribute:change+' => '', 'Class:CMDBChangeOp/Attribute:date' => 'Data', - 'Class:CMDBChangeOp/Attribute:date+' => 'Data e hora da mudança', + 'Class:CMDBChangeOp/Attribute:date+' => 'Data e hora da alteração', 'Class:CMDBChangeOp/Attribute:userinfo' => 'Usuário', - 'Class:CMDBChangeOp/Attribute:userinfo+' => 'Quem fez essa mudança', - 'Class:CMDBChangeOp/Attribute:objclass' => 'Classe objeto', + 'Class:CMDBChangeOp/Attribute:userinfo+' => 'Quem fez essa alteração', + 'Class:CMDBChangeOp/Attribute:objclass' => 'Classe do objeto', 'Class:CMDBChangeOp/Attribute:objclass+' => '', - 'Class:CMDBChangeOp/Attribute:objkey' => 'ID objeto', + 'Class:CMDBChangeOp/Attribute:objkey' => 'ID do objeto', 'Class:CMDBChangeOp/Attribute:objkey+' => '', - 'Class:CMDBChangeOp/Attribute:finalclass' => 'tipo', + 'Class:CMDBChangeOp/Attribute:finalclass' => 'Tipo', 'Class:CMDBChangeOp/Attribute:finalclass+' => '', )); @@ -267,8 +269,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:CMDBChangeOpCreate' => 'Criação do objeto', - 'Class:CMDBChangeOpCreate+' => 'Rastreamento de criação do objeto', + 'Class:CMDBChangeOpCreate' => 'Criação de objeto', + 'Class:CMDBChangeOpCreate+' => 'Controle de criação do objeto', )); // @@ -276,8 +278,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:CMDBChangeOpDelete' => 'Objeto excluído', - 'Class:CMDBChangeOpDelete+' => 'Rastreamento de exclusão do objeto', + 'Class:CMDBChangeOpDelete' => 'Exclusão de objeto', + 'Class:CMDBChangeOpDelete+' => 'Controle de exclusão do objeto', )); // @@ -285,10 +287,10 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:CMDBChangeOpSetAttribute' => 'Objeto alterado', - 'Class:CMDBChangeOpSetAttribute+' => 'Rastreamento alteração propriedade Objeto', + 'Class:CMDBChangeOpSetAttribute' => 'Alteração de propriedades', + 'Class:CMDBChangeOpSetAttribute+' => 'Controle de alteração de propriedades do objeto', 'Class:CMDBChangeOpSetAttribute/Attribute:attcode' => 'Atributo', - 'Class:CMDBChangeOpSetAttribute/Attribute:attcode+' => 'Código da propriedade modificado', + 'Class:CMDBChangeOpSetAttribute/Attribute:attcode+' => 'Código da propriedade modificada', )); // @@ -296,28 +298,28 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:CMDBChangeOpSetAttributeScalar' => 'Propriedade alterado', - 'Class:CMDBChangeOpSetAttributeScalar+' => 'Propriedades escalares objeto de controle de alterações', + 'Class:CMDBChangeOpSetAttributeScalar' => 'Alteração de propriedades escalares', + 'Class:CMDBChangeOpSetAttributeScalar+' => 'Controle de alterações de propriedades escalares do objeto', 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue' => 'Valor anterior', - 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue+' => 'valor anterior do atributo', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue+' => 'Valor anterior do atributo', 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue' => 'Novo valor', - 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue+' => 'novo valor do atributo', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue+' => 'Novo valor do atributo', )); // Used by CMDBChangeOp... & derived classes Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Change:ObjectCreated' => 'Objeto criado', 'Change:ObjectDeleted' => 'Objeto excluído', 'Change:ObjectModified' => 'Objeto modificado', - 'Change:TwoAttributesChanged' => 'Edited %1$s and %2$s~~', - 'Change:ThreeAttributesChanged' => 'Edited %1$s, %2$s and 1 other~~', - 'Change:FourOrMoreAttributesChanged' => 'Edited %1$s, %2$s and %3$s others~~', - 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s configurado para %2$s (valor anterior: %3$s)', - 'Change:AttName_SetTo' => '%1$s configurado para %2$s', - 'Change:Text_AppendedTo_AttName' => '%1$s anexado ao %2$s', - 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s modificado, valor anterior: %2$s', - 'Change:AttName_Changed' => '%1$s modificado', - 'Change:AttName_EntryAdded' => '%1$s modificado, nova entrada adicionada: %2$s', - 'Change:State_Changed_NewValue_OldValue' => 'Changed from %2$s to %1$s~~', + 'Change:TwoAttributesChanged' => 'Modificado %1$s e %2$s', + 'Change:ThreeAttributesChanged' => 'Modificado %1$s, %2$s e 1 outro', + 'Change:FourOrMoreAttributesChanged' => 'Modificado %1$s, %2$s e %3$s outros', + 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s definido para %2$s (valor anterior: %3$s)', + 'Change:AttName_SetTo' => '%1$s definido para %2$s', + 'Change:Text_AppendedTo_AttName' => '%1$s anexado a(o) %2$s', + 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s modificado(a), valor anterior: %2$s', + 'Change:AttName_Changed' => '%1$s modificado(a)', + 'Change:AttName_EntryAdded' => '%1$s modificado(a), nova entrada adicionada: %2$s', + 'Change:State_Changed_NewValue_OldValue' => 'Modificado de %2$s para %1$s', 'Change:LinkSet:Added' => 'adicionado %1$s', 'Change:LinkSet:Removed' => 'excluído %1$s', 'Change:LinkSet:Modified' => 'modificado %1$s', @@ -328,10 +330,10 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:CMDBChangeOpSetAttributeBlob' => 'data mudança', - 'Class:CMDBChangeOpSetAttributeBlob+' => 'controle de alterações de dados', + 'Class:CMDBChangeOpSetAttributeBlob' => 'Alteração de conteúdo (Blob)', + 'Class:CMDBChangeOpSetAttributeBlob+' => 'Controle de alterações de conteúdo de dados (Blob)', 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata' => 'Valor anterior', - 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata+' => 'conteúdo anterior do atributo', + 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata+' => 'Conteúdo anterior do atributo', )); // @@ -339,10 +341,10 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:CMDBChangeOpSetAttributeText' => 'mudança texto', - 'Class:CMDBChangeOpSetAttributeText+' => 'controle de alterações de texto', + 'Class:CMDBChangeOpSetAttributeText' => 'Alteração de texto', + 'Class:CMDBChangeOpSetAttributeText+' => 'Controle de alterações de texto do objeto', 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata' => 'Valor anterior', - 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata+' => 'conteúdo anterior do atributo', + 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata+' => 'Conteúdo anterior do atributo', )); // @@ -350,14 +352,14 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:Event' => 'Evento registros', + 'Class:Event' => 'Registro de evento', 'Class:Event+' => 'Um evento interno do aplicativo', - 'Class:Event/Attribute:message' => 'Mensagens', - 'Class:Event/Attribute:message+' => 'pequena descrição deste evento', + 'Class:Event/Attribute:message' => 'Mensagem', + 'Class:Event/Attribute:message+' => 'Descrição curta deste evento', 'Class:Event/Attribute:date' => 'Data', - 'Class:Event/Attribute:date+' => 'data e hora em que as mudanças foram registradas', - 'Class:Event/Attribute:userinfo' => 'Informações usuário', - 'Class:Event/Attribute:userinfo+' => 'identificação do usuário que estava fazendo a ação que desencadeou este evento', + 'Class:Event/Attribute:date+' => 'Data e hora em que o evento foi registrado', + 'Class:Event/Attribute:userinfo' => 'Informações do usuário', + 'Class:Event/Attribute:userinfo+' => 'Identificação do usuário que estava executando a ação que desencadeou este evento', 'Class:Event/Attribute:finalclass' => 'Tipo', 'Class:Event/Attribute:finalclass+' => '', )); @@ -367,14 +369,14 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:EventNotification' => 'Evento notificação', - 'Class:EventNotification+' => 'Rastreamento de uma notificação que foi enviada', + 'Class:EventNotification' => 'Notificação de evento', + 'Class:EventNotification+' => 'Controle de notificações que foram enviadas', 'Class:EventNotification/Attribute:trigger_id' => 'Gatilho', - 'Class:EventNotification/Attribute:trigger_id+' => 'conta usuário', - 'Class:EventNotification/Attribute:action_id' => 'usuário', - 'Class:EventNotification/Attribute:action_id+' => 'conta usuário', - 'Class:EventNotification/Attribute:object_id' => 'Id objeto', - 'Class:EventNotification/Attribute:object_id+' => 'Id objeto (classe definida pelo gatilho?)', + 'Class:EventNotification/Attribute:trigger_id+' => 'Conta de usuário', + 'Class:EventNotification/Attribute:action_id' => 'Usuário', + 'Class:EventNotification/Attribute:action_id+' => 'Conta de usuário', + 'Class:EventNotification/Attribute:object_id' => 'ID do objeto', + 'Class:EventNotification/Attribute:object_id+' => 'ID do objeto (classe definida pelo gatilho?)', )); // @@ -382,20 +384,20 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:EventNotificationEmail' => 'Evento envio email', - 'Class:EventNotificationEmail+' => 'Rastreamento de um e-mail que foi enviado', + 'Class:EventNotificationEmail' => 'Evento de envio de e-mail', + 'Class:EventNotificationEmail+' => 'Controle de e-mails que foram enviados', 'Class:EventNotificationEmail/Attribute:to' => 'Para', - 'Class:EventNotificationEmail/Attribute:to+' => '', + 'Class:EventNotificationEmail/Attribute:to+' => 'Endereço(s) de e-mail do(s) destinatário(s)', 'Class:EventNotificationEmail/Attribute:cc' => 'CC', - 'Class:EventNotificationEmail/Attribute:cc+' => '', + 'Class:EventNotificationEmail/Attribute:cc+' => 'Endereço(s) de e-mail do(s) destinaráio(s) com cópia', 'Class:EventNotificationEmail/Attribute:bcc' => 'CCO', - 'Class:EventNotificationEmail/Attribute:bcc+' => '', + 'Class:EventNotificationEmail/Attribute:bcc+' => 'Endereço(s) de e-mail do(s) destinatário(s) com cópia oculta', 'Class:EventNotificationEmail/Attribute:from' => 'De', - 'Class:EventNotificationEmail/Attribute:from+' => 'Remetente da mensagem', + 'Class:EventNotificationEmail/Attribute:from+' => 'Remetente do e-mail', 'Class:EventNotificationEmail/Attribute:subject' => 'Assunto', - 'Class:EventNotificationEmail/Attribute:subject+' => '', + 'Class:EventNotificationEmail/Attribute:subject+' => 'Título do e-mail', 'Class:EventNotificationEmail/Attribute:body' => 'Corpo', - 'Class:EventNotificationEmail/Attribute:body+' => '', + 'Class:EventNotificationEmail/Attribute:body+' => 'Conteúdo do e-mail', 'Class:EventNotificationEmail/Attribute:attachments' => 'Anexos', 'Class:EventNotificationEmail/Attribute:attachments+' => '', )); @@ -405,21 +407,21 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:EventIssue' => 'Evento entrega', - 'Class:EventIssue+' => 'Rastreamento de entrega (aviso, erro, etc.)', + 'Class:EventIssue' => 'Evento de entrega', + 'Class:EventIssue+' => 'Controle de entrega (aviso, erro, etc.)', 'Class:EventIssue/Attribute:issue' => 'Entrega', 'Class:EventIssue/Attribute:issue+' => 'O que aconteceu', 'Class:EventIssue/Attribute:impact' => 'Impacto', 'Class:EventIssue/Attribute:impact+' => 'Quais são as consequências', 'Class:EventIssue/Attribute:page' => 'Página', - 'Class:EventIssue/Attribute:page+' => 'HTTP ponto de entrada', - 'Class:EventIssue/Attribute:arguments_post' => 'Argumentos postados', + 'Class:EventIssue/Attribute:page+' => 'Ponto de entrada HTTP', + 'Class:EventIssue/Attribute:arguments_post' => 'Argumentos POST', 'Class:EventIssue/Attribute:arguments_post+' => 'Argumentos HTTP POST', 'Class:EventIssue/Attribute:arguments_get' => 'Argumentos URL', 'Class:EventIssue/Attribute:arguments_get+' => 'Argumentos HTTP GET', - 'Class:EventIssue/Attribute:callstack' => 'Quantidade solicitações', - 'Class:EventIssue/Attribute:callstack+' => 'Quantidade de solicitações', - 'Class:EventIssue/Attribute:data' => 'Dado', + 'Class:EventIssue/Attribute:callstack' => 'Quantidade de solicitações', + 'Class:EventIssue/Attribute:callstack+' => '', + 'Class:EventIssue/Attribute:data' => 'Dados', 'Class:EventIssue/Attribute:data+' => 'Mais informações', )); @@ -429,34 +431,34 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:EventWebService' => 'Evento Web service', - 'Class:EventWebService+' => 'Rastreamento de uma solicitação de serviço web', - 'Class:EventWebService/Attribute:verb' => 'Verbo', + 'Class:EventWebService+' => 'Controle de uma solicitação de WebService', + 'Class:EventWebService/Attribute:verb' => 'Verb', 'Class:EventWebService/Attribute:verb+' => 'Nome da operação', 'Class:EventWebService/Attribute:result' => 'Resultado', - 'Class:EventWebService/Attribute:result+' => 'Sucesso/fracasso geral', - 'Class:EventWebService/Attribute:log_info' => 'Log informação', - 'Class:EventWebService/Attribute:log_info+' => 'Resultado log informação', + 'Class:EventWebService/Attribute:result+' => 'Sucesso/erro geral', + 'Class:EventWebService/Attribute:log_info' => 'Log de resultado', + 'Class:EventWebService/Attribute:log_info+' => '', 'Class:EventWebService/Attribute:log_warning' => 'Log de alerta', - 'Class:EventWebService/Attribute:log_warning+' => 'Resultado log de alerta', + 'Class:EventWebService/Attribute:log_warning+' => '', 'Class:EventWebService/Attribute:log_error' => 'Log de erro', - 'Class:EventWebService/Attribute:log_error+' => 'Resultado log de erro', - 'Class:EventWebService/Attribute:data' => 'Dado', - 'Class:EventWebService/Attribute:data+' => 'Resultado dado', + 'Class:EventWebService/Attribute:log_error+' => '', + 'Class:EventWebService/Attribute:data' => 'Dados', + 'Class:EventWebService/Attribute:data+' => 'Mais informações', )); Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:EventRestService' => 'Chamada REST/JSON', - 'Class:EventRestService+' => 'Rastreio de uma chamada de serviço REST/JSON', - 'Class:EventRestService/Attribute:operation' => 'Operation', - 'Class:EventRestService/Attribute:operation+' => 'Argument \'operation\'', + 'Class:EventRestService+' => 'Controle de uma chamada de serviço REST/JSON', + 'Class:EventRestService/Attribute:operation' => 'Operação', + 'Class:EventRestService/Attribute:operation+' => 'Argumento \'operation\'', 'Class:EventRestService/Attribute:version' => 'Versão', - 'Class:EventRestService/Attribute:version+' => 'Argument \'version\'', + 'Class:EventRestService/Attribute:version+' => 'Argumento \'version\'', 'Class:EventRestService/Attribute:json_input' => 'Input', 'Class:EventRestService/Attribute:json_input+' => 'Argumento \'json_data\'', - 'Class:EventRestService/Attribute:code' => 'Code', + 'Class:EventRestService/Attribute:code' => 'Código', 'Class:EventRestService/Attribute:code+' => 'Código de resultado', - 'Class:EventRestService/Attribute:json_output' => 'Response', - 'Class:EventRestService/Attribute:json_output+' => 'Resposta HTTP (json)', + 'Class:EventRestService/Attribute:json_output' => 'Resposta', + 'Class:EventRestService/Attribute:json_output+' => 'Resposta HTTP (JSON)', 'Class:EventRestService/Attribute:provider' => 'Provedor', 'Class:EventRestService/Attribute:provider+' => 'Classe PHP implementando a operação esperada', )); @@ -466,14 +468,14 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:EventLoginUsage' => 'Login utilizado', - 'Class:EventLoginUsage+' => 'Conexão com a aplicação', + 'Class:EventLoginUsage' => 'Logins', + 'Class:EventLoginUsage+' => 'Conexões com a aplicação', 'Class:EventLoginUsage/Attribute:user_id' => 'Login', 'Class:EventLoginUsage/Attribute:user_id+' => '', - 'Class:EventLoginUsage/Attribute:contact_name' => 'Nome usuário', + 'Class:EventLoginUsage/Attribute:contact_name' => 'Nome de usuário', 'Class:EventLoginUsage/Attribute:contact_name+' => '', - 'Class:EventLoginUsage/Attribute:contact_email' => 'Email usuário', - 'Class:EventLoginUsage/Attribute:contact_email+' => 'Endereço email deste usuário', + 'Class:EventLoginUsage/Attribute:contact_email' => 'E-mail do usuário', + 'Class:EventLoginUsage/Attribute:contact_email+' => 'Endereço de e-mail deste usuário', )); // @@ -482,24 +484,24 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:Action' => 'Ação personalizada', - 'Class:Action+' => 'Ação definida pelo usuário', + 'Class:Action+' => 'Ações definidas pelo usuário', 'Class:Action/Attribute:name' => 'Nome', 'Class:Action/Attribute:name+' => '', 'Class:Action/Attribute:description' => 'Descrição', 'Class:Action/Attribute:description+' => '', 'Class:Action/Attribute:status' => 'Status', - 'Class:Action/Attribute:status+' => 'Em produção ou ?', - 'Class:Action/Attribute:status/Value:test' => 'sendo testado', + 'Class:Action/Attribute:status+' => 'Ativo ou ?', + 'Class:Action/Attribute:status/Value:test' => 'Em homologação', 'Class:Action/Attribute:status/Value:test+' => '', - 'Class:Action/Attribute:status/Value:enabled' => 'Em produção', + 'Class:Action/Attribute:status/Value:enabled' => 'Ativo', 'Class:Action/Attribute:status/Value:enabled+' => '', 'Class:Action/Attribute:status/Value:disabled' => 'Inativo', 'Class:Action/Attribute:status/Value:disabled+' => '', 'Class:Action/Attribute:trigger_list' => 'Gatilhos relacionados', - 'Class:Action/Attribute:trigger_list+' => 'Gatilhos ligados a esta ação', + 'Class:Action/Attribute:trigger_list+' => 'Gatilhos associadas à esta ação', 'Class:Action/Attribute:finalclass' => 'Tipo', 'Class:Action/Attribute:finalclass+' => '', - 'Action:WarningNoTriggerLinked' => 'Warning, no trigger is linked to the action. It will not be active until it has at least 1.~~', + 'Action:WarningNoTriggerLinked' => 'Aviso, nenhum gatilho está associado à ação. Não será ativo até que esta ação tenha pelo menos um gatilho associado', )); // @@ -516,39 +518,39 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:ActionEmail' => 'Notificação email', - 'Class:ActionEmail+' => '', - 'Class:ActionEmail/Attribute:status+' => 'This status drives who will be notified: just the Test recipient, all (To, cc and Bcc) or no-one~~', - 'Class:ActionEmail/Attribute:status/Value:test+' => 'Only the Test recipient is notified~~', - 'Class:ActionEmail/Attribute:status/Value:enabled+' => 'All To, Cc and Bcc emails are notified~~', - 'Class:ActionEmail/Attribute:status/Value:disabled+' => 'The email notification will not be sent~~', - 'Class:ActionEmail/Attribute:test_recipient' => 'Testar destinatário', - 'Class:ActionEmail/Attribute:test_recipient+' => 'Destinatário em caso o estado está definido como "Teste"', - 'Class:ActionEmail/Attribute:from' => 'De~~', - 'Class:ActionEmail/Attribute:from+' => 'Será enviado para o cabeçalho de email~~', - 'Class:ActionEmail/Attribute:from_label' => 'From (label)~~', - 'Class:ActionEmail/Attribute:from_label+' => 'Sender display name will be sent into the email header~~', - 'Class:ActionEmail/Attribute:reply_to' => 'Responder para~~', - 'Class:ActionEmail/Attribute:reply_to+' => 'Será enviado para o cabeçalho de email~~', - 'Class:ActionEmail/Attribute:reply_to_label' => 'Reply to (label)~~', - 'Class:ActionEmail/Attribute:reply_to_label+' => 'Reply to display name will be sent into the email header~~', + 'Class:ActionEmail' => 'Notificação via E-mail', + 'Class:ActionEmail+' => 'Lista de Notificações via E-mail', + 'Class:ActionEmail/Attribute:status+' => 'Esse status especifica quem será notificado: apenas o destinatário do Teste, todos (Para, CC e CCO) ou ninguém', + 'Class:ActionEmail/Attribute:status/Value:test' => 'Apenas o destinatário de teste é notificado', + 'Class:ActionEmail/Attribute:status/Value:enabled' => 'Todos os endereços de e-mails dos campos Para, CC e CCO são notificados', + 'Class:ActionEmail/Attribute:status/Value:disabled' => 'A notificação de e-mail não será enviada', + 'Class:ActionEmail/Attribute:test_recipient' => 'Destinatário de teste', + 'Class:ActionEmail/Attribute:test_recipient+' => 'Destinatário caso o status esteja definido como "teste"', + 'Class:ActionEmail/Attribute:from' => 'De', + 'Class:ActionEmail/Attribute:from+' => 'Endereço de e-mail do remetente enviado no cabeçalho do e-mail', + 'Class:ActionEmail/Attribute:from_label' => 'De (campo)', + 'Class:ActionEmail/Attribute:from_label+' => 'Nome de exibição enviado no cabeçalho do e-mail', + 'Class:ActionEmail/Attribute:reply_to' => 'Responder para', + 'Class:ActionEmail/Attribute:reply_to+' => 'Endereço de e-mail enviado no cabeçalho do e-mail', + 'Class:ActionEmail/Attribute:reply_to_label' => 'Responder para (campo)', + 'Class:ActionEmail/Attribute:reply_to_label+' => 'Nome de exibição enviado no cabeçalho do e-mail', 'Class:ActionEmail/Attribute:to' => 'Para', - 'Class:ActionEmail/Attribute:to+' => 'Destinatário para o email', + 'Class:ActionEmail/Attribute:to+' => 'Endereço(s) de e-mail do(s) destinatário(s)', 'Class:ActionEmail/Attribute:cc' => 'CC', - 'Class:ActionEmail/Attribute:cc+' => '', + 'Class:ActionEmail/Attribute:cc+' => 'Endereço(s) de e-mail do(s) destinaráio(s) com cópia', 'Class:ActionEmail/Attribute:bcc' => 'CCO', - 'Class:ActionEmail/Attribute:bcc+' => '', - 'Class:ActionEmail/Attribute:subject' => 'assunto', - 'Class:ActionEmail/Attribute:subject+' => 'Título do email', - 'Class:ActionEmail/Attribute:body' => 'corpo', - 'Class:ActionEmail/Attribute:body+' => 'Conteúdo do email', - 'Class:ActionEmail/Attribute:importance' => 'importância', - 'Class:ActionEmail/Attribute:importance+' => 'Flag importância', - 'Class:ActionEmail/Attribute:importance/Value:low' => 'baixo', + 'Class:ActionEmail/Attribute:bcc+' => 'Endereço(s) de e-mail do(s) destinatário(s) com cópia oculta', + 'Class:ActionEmail/Attribute:subject' => 'Assunto', + 'Class:ActionEmail/Attribute:subject+' => 'Título do e-mail', + 'Class:ActionEmail/Attribute:body' => 'Corpo', + 'Class:ActionEmail/Attribute:body+' => 'Conteúdo do e-mail', + 'Class:ActionEmail/Attribute:importance' => 'Prioridade', + 'Class:ActionEmail/Attribute:importance+' => '', + 'Class:ActionEmail/Attribute:importance/Value:low' => 'Baixa', 'Class:ActionEmail/Attribute:importance/Value:low+' => '', - 'Class:ActionEmail/Attribute:importance/Value:normal' => 'normal', + 'Class:ActionEmail/Attribute:importance/Value:normal' => 'Normal', 'Class:ActionEmail/Attribute:importance/Value:normal+' => '', - 'Class:ActionEmail/Attribute:importance/Value:high' => 'alto', + 'Class:ActionEmail/Attribute:importance/Value:high' => 'Alta', 'Class:ActionEmail/Attribute:importance/Value:high+' => '', )); @@ -560,13 +562,13 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:Trigger' => 'Gatilho', 'Class:Trigger+' => 'Manipulador de eventos personalizado', 'Class:Trigger/Attribute:description' => 'Descrição', - 'Class:Trigger/Attribute:description+' => 'uma linha descrição', + 'Class:Trigger/Attribute:description+' => 'Uma descrição curta', 'Class:Trigger/Attribute:action_list' => 'Ações desencadeadas', - 'Class:Trigger/Attribute:action_list+' => 'Ações executadas quando o gatilho é ativado', + 'Class:Trigger/Attribute:action_list+' => 'Ações executadas quando o gatilho é acionado', 'Class:Trigger/Attribute:finalclass' => 'Tipo', 'Class:Trigger/Attribute:finalclass+' => '', 'Class:Trigger/Attribute:context' => 'Contexto', - 'Class:Trigger/Attribute:context+' => 'Contexto para permitir o gatilho ser ativado', + 'Class:Trigger/Attribute:context+' => 'Contexto para permitir o acionamento do gatilho', )); // @@ -579,7 +581,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:TriggerOnObject/Attribute:target_class' => 'Classe alvo', 'Class:TriggerOnObject/Attribute:target_class+' => '', 'Class:TriggerOnObject/Attribute:filter' => 'Filtro', - 'Class:TriggerOnObject/Attribute:filter+' => 'Limit the object list (of the target class) which will activate the trigger~~', + 'Class:TriggerOnObject/Attribute:filter+' => 'Limita a lista de objetos (da classe de destino) que irá ativar o gatilho', 'TriggerOnObject:WrongFilterQuery' => 'Consulta de filtro incorreta: %1$s', 'TriggerOnObject:WrongFilterClass' => 'A consulta de filtro deve retornar objetos da classe \\"%1$s\\"', )); @@ -589,8 +591,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:TriggerOnPortalUpdate' => 'Gatilho (quando atualizado a partir do portal)', - 'Class:TriggerOnPortalUpdate+' => 'Gatilho de uma atualização do usuário final a partir do portal', + 'Class:TriggerOnPortalUpdate' => 'Gatilho (quando atualizado a partir do portal do usuário)', + 'Class:TriggerOnPortalUpdate+' => 'Gatilho acionado a partir de uma atualização do usuário final através do portal do usuário', )); // @@ -598,9 +600,9 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:TriggerOnStateChange' => 'Gatilho (na mudança de estado)', - 'Class:TriggerOnStateChange+' => 'Gatilho de mudança do estado do objeto', - 'Class:TriggerOnStateChange/Attribute:state' => 'State', + 'Class:TriggerOnStateChange' => 'Gatilho (na mudança de status)', + 'Class:TriggerOnStateChange+' => 'Gatilho de mudança de status do objeto', + 'Class:TriggerOnStateChange/Attribute:state' => 'Status', 'Class:TriggerOnStateChange/Attribute:state+' => '', )); @@ -609,8 +611,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:TriggerOnStateEnter' => 'Gatilho (ao entrar em um estado)', - 'Class:TriggerOnStateEnter+' => 'Gatilho de mudança do estado do objeto - entrando', + 'Class:TriggerOnStateEnter' => 'Gatilho (ao entrar em um status)', + 'Class:TriggerOnStateEnter+' => 'Gatilho de mudança de status do objeto - entrada', )); // @@ -618,8 +620,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:TriggerOnStateLeave' => 'Gatilho (para sair de um estado)', - 'Class:TriggerOnStateLeave+' => 'Gatilho de mudança do estado do objeto - saindo', + 'Class:TriggerOnStateLeave' => 'Gatilho (ao sair de um status)', + 'Class:TriggerOnStateLeave+' => 'Gatilho de mudança de status do objeto - saída', )); // @@ -628,7 +630,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:TriggerOnObjectCreate' => 'Gatilho (na criação do objeto)', - 'Class:TriggerOnObjectCreate+' => 'Gatilho de criação do objeto de [a classe filha] determinada classe', + 'Class:TriggerOnObjectCreate+' => 'Gatilho de criação de objeto de [uma classe filha] de determinada classe', )); // @@ -636,8 +638,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:TriggerOnObjectDelete' => 'Trigger (na exclusão de objetos)', - 'Class:TriggerOnObjectDelete+' => 'Trigger na exclusão de objeto de [uma classe filho] da classe dada', + 'Class:TriggerOnObjectDelete' => 'Gatilho (na exclusão de objetos)', + 'Class:TriggerOnObjectDelete+' => 'Gatilho na exclusão de objeto de [uma classe filha] de determinada classe', )); // @@ -645,8 +647,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:TriggerOnObjectUpdate' => 'Trigger (na atualização do objeto)', - 'Class:TriggerOnObjectUpdate+' => 'Trigger na atualização de objeto de [uma classe filho] da classe dada', + 'Class:TriggerOnObjectUpdate' => 'Gatilho (na atualização do objeto)', + 'Class:TriggerOnObjectUpdate+' => 'Gatilho na atualização de objeto de [uma classe filha] de uma determinada classe', 'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Campos de destino', 'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '', )); @@ -656,10 +658,10 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:TriggerOnObjectMention' => 'Trigger (on object mention)~~', - 'Class:TriggerOnObjectMention+' => 'Trigger on mention (@xxx) of an object of [a child class of] the given class in a log attribute~~', - 'Class:TriggerOnObjectMention/Attribute:mentioned_filter' => 'Mentioned filter~~', - 'Class:TriggerOnObjectMention/Attribute:mentioned_filter+' => 'Limit the list of mentioned objects which will activate the trigger. If empty, any mentioned object (of any class) will activate it.~~', + 'Class:TriggerOnObjectMention' => 'Gatilho (na menção do objeto)', + 'Class:TriggerOnObjectMention+' => 'Gatilho em menção (@xxx) de um objeto de [uma classe filha] de uma determinada classe em um atributo de log', + 'Class:TriggerOnObjectMention/Attribute:mentioned_filter' => 'Filtro de menções', + 'Class:TriggerOnObjectMention/Attribute:mentioned_filter+' => 'Limita a lista de objetos mencionados que ativarão o gatilho. Se vazio, qualquer objeto mencionado (de qualquer classe) irá ativá-lo', )); // @@ -667,11 +669,11 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:TriggerOnThresholdReached' => 'Gatilho (na atingimento de limite)', - 'Class:TriggerOnThresholdReached+' => 'Gatilho de limite do cronômetro atingido', + 'Class:TriggerOnThresholdReached' => 'Gatilho (no alcance do limite)', + 'Class:TriggerOnThresholdReached+' => 'Gatilho no alcance do limite do cronômetro', 'Class:TriggerOnThresholdReached/Attribute:stop_watch_code' => 'Cronômetro', 'Class:TriggerOnThresholdReached/Attribute:stop_watch_code+' => '', - 'Class:TriggerOnThresholdReached/Attribute:threshold_index' => 'Entrada', + 'Class:TriggerOnThresholdReached/Attribute:threshold_index' => 'Limite', 'Class:TriggerOnThresholdReached/Attribute:threshold_index+' => '', )); @@ -680,8 +682,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:lnkTriggerAction' => 'Ações/Gatilho', - 'Class:lnkTriggerAction+' => 'Ligação entre um Gatilho e uma Ação', + 'Class:lnkTriggerAction' => 'Ação/Gatilho', + 'Class:lnkTriggerAction+' => 'Link Gatilho / Ação', 'Class:lnkTriggerAction/Attribute:action_id' => 'Ação', 'Class:lnkTriggerAction/Attribute:action_id+' => 'Ação a ser executada', 'Class:lnkTriggerAction/Attribute:action_name' => 'Ação', @@ -706,30 +708,30 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:SynchroDataSource/Attribute:user_id' => 'Usuário', 'Class:SynchroDataSource/Attribute:notify_contact_id' => 'Contato para notificação', 'Class:SynchroDataSource/Attribute:notify_contact_id+' => 'Contato para notificar em caso de erro', - 'Class:SynchroDataSource/Attribute:url_icon' => 'Icones hiperlink', - 'Class:SynchroDataSource/Attribute:url_icon+' => 'Hiperlink de uma pequena imagem representando o aplicativo com o qual o iTop é sincronizado', - 'Class:SynchroDataSource/Attribute:url_application' => 'Hiperlink aplicativo', - 'Class:SynchroDataSource/Attribute:url_application+' => 'Hiperlink para o objeto na aplicação externa com a qual o iTop é sincronizado (se aplicável). As substituições possíveis: $this->attribute$ e $replica->primary_key$', - 'Class:SynchroDataSource/Attribute:reconciliation_policy' => 'Política reconciliação', - 'Class:SynchroDataSource/Attribute:full_load_periodicity' => 'Intervalo da carga plena', - 'Class:SynchroDataSource/Attribute:full_load_periodicity+' => 'A recarga completa de todos os dados devem ocorrer pelo menos tão frequentemente como especificado aqui', + 'Class:SynchroDataSource/Attribute:url_icon' => 'Ícone de Hiperlink', + 'Class:SynchroDataSource/Attribute:url_icon+' => 'Hiperlink de uma pequena imagem representando o aplicativo com o qual o '.ITOP_APPLICATION_SHORT.' é sincronizado', + 'Class:SynchroDataSource/Attribute:url_application' => 'Hiperlink de aplicativo', + 'Class:SynchroDataSource/Attribute:url_application+' => 'Hiperlink para o objeto na aplicação externa com a qual o '.ITOP_APPLICATION_SHORT.' é sincronizado (se aplicável). As substituições possíveis: $this->attribute$ e $replica->primary_key$', + 'Class:SynchroDataSource/Attribute:reconciliation_policy' => 'Política de reconciliação', + 'Class:SynchroDataSource/Attribute:full_load_periodicity' => 'Intervalo de obsolescência programada', + 'Class:SynchroDataSource/Attribute:full_load_periodicity+' => 'Um objeto é considerado obsoleto se não aparecer nos dados além desse tempo', 'Class:SynchroDataSource/Attribute:action_on_zero' => 'Ação sobre zero', 'Class:SynchroDataSource/Attribute:action_on_zero+' => 'Medidas tomadas quando a busca retorna nenhum objeto', 'Class:SynchroDataSource/Attribute:action_on_one' => 'Ação em um', - 'Class:SynchroDataSource/Attribute:action_on_one+' => 'Medidas tomadas quando a busca retorna exatamente um objeto', + 'Class:SynchroDataSource/Attribute:action_on_one+' => 'Medidas tomadas quando a busca retorna exatamente um único objeto', 'Class:SynchroDataSource/Attribute:action_on_multiple' => 'Ação em muitos', 'Class:SynchroDataSource/Attribute:action_on_multiple+' => 'Medidas tomadas quando a busca retorna mais de um objeto', 'Class:SynchroDataSource/Attribute:user_delete_policy' => 'Usuários permitidos', 'Class:SynchroDataSource/Attribute:user_delete_policy+' => 'Quem tem permissão para excluir objetos sincronizados', 'Class:SynchroDataSource/Attribute:delete_policy/Value:never' => 'Ninguém', - 'Class:SynchroDataSource/Attribute:delete_policy/Value:depends' => 'Somente Administradores', - 'Class:SynchroDataSource/Attribute:delete_policy/Value:always' => 'Todos os usuários permitidos', + 'Class:SynchroDataSource/Attribute:delete_policy/Value:depends' => 'Somente administradores', + 'Class:SynchroDataSource/Attribute:delete_policy/Value:always' => 'Todos os usuários', 'Class:SynchroDataSource/Attribute:delete_policy_update' => 'Regras de atualização', 'Class:SynchroDataSource/Attribute:delete_policy_update+' => 'Sintaxe: nome_do_campo:valor; ...', - 'Class:SynchroDataSource/Attribute:delete_policy_retention' => 'Duração de retenção', - 'Class:SynchroDataSource/Attribute:delete_policy_retention+' => 'Quanto tempo um objeto obsoleto é mantida antes de ser excluído', - 'Class:SynchroDataSource/Attribute:database_table_name' => 'Tabela de dados', - 'Class:SynchroDataSource/Attribute:database_table_name+' => 'Nome da tabela para armazenar os dados de sincronização. Se for deixado vazio, um nome padrão será computado.', + 'Class:SynchroDataSource/Attribute:delete_policy_retention' => 'Duração da retenção', + 'Class:SynchroDataSource/Attribute:delete_policy_retention+' => 'Quanto tempo um objeto obsoleto é mantido antes de ser excluído', + 'Class:SynchroDataSource/Attribute:database_table_name' => 'Tabela do banco de dados', + 'Class:SynchroDataSource/Attribute:database_table_name+' => 'Nome da tabela para armazenar os dados de sincronização. Se for deixado vazio, um nome padrão será computado', 'SynchroDataSource:Description' => 'Descrição', 'SynchroDataSource:Reconciliation' => 'Pesquisa & reconciliação', 'SynchroDataSource:Deletion' => 'Regras de exclusão', @@ -750,12 +752,12 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Core:SynchroUpdate:Yes' => 'Sim', 'Core:SynchroUpdate:No' => 'Não', 'Core:Synchro:LastestStatus' => 'Último Status', - 'Core:Synchro:History' => 'Histórico sincronização', - 'Core:Synchro:NeverRun' => 'Este sincronismo nunca foi executado. Sem registo ainda.', - 'Core:Synchro:SynchroEndedOn_Date' => 'A última sincronização terminou em %1$s.', - 'Core:Synchro:SynchroRunningStartedOn_Date' => 'A sincronização começou em %1$s ainda está em execução...', - 'Menu:DataSources' => 'Fontes de dados de sincronização', // Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:DataSources+' => 'Todas fontes de dados de sincronização', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Core:Synchro:History' => 'Histórico de sincronização', + 'Core:Synchro:NeverRun' => 'Esta sincronização nunca foi executada. Sem registro ainda', + 'Core:Synchro:SynchroEndedOn_Date' => 'A última sincronização terminou em %1$s', + 'Core:Synchro:SynchroRunningStartedOn_Date' => 'A sincronização iniciou em %1$s ainda está em execução...', + 'Menu:DataSources' => 'Fontes de Sincronização de Dados', // Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:DataSources+' => 'Lista de Fontes de Sincronização de Dados', // Duplicated into itop-welcome-itil (will be removed from here...) 'Core:Synchro:label_repl_ignored' => 'Ignoradas (%1$s)', 'Core:Synchro:label_repl_disappeared' => 'Desaparecido (%1$s)', 'Core:Synchro:label_repl_existing' => 'Saindo (%1$s)', @@ -772,17 +774,17 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Core:Synchro:label_obj_created' => 'Criado (%1$s)', 'Core:Synchro:label_obj_new_errors' => 'Erros (%1$s)', 'Core:SynchroLogTitle' => '%1$s - %2$s', - 'Core:Synchro:Nb_Replica' => 'Replica processado: %1$s', + 'Core:Synchro:Nb_Replica' => 'Réplica processada: %1$s', 'Core:Synchro:Nb_Class:Objects' => '%1$s: %2$s', - 'Class:SynchroDataSource/Error:AtLeastOneReconciliationKeyMustBeSpecified' => 'Pelo menos uma chave de reconciliação deve ser especificado, ou a política de reconciliação deve ser a de usar a chave primária.', - 'Class:SynchroDataSource/Error:DeleteRetentionDurationMustBeSpecified' => 'Um período de retenção excluir deve ser especificado, já que objetos devem ser excluídos depois de ser marcado como obsoleto.', - 'Class:SynchroDataSource/Error:DeletePolicyUpdateMustBeSpecified' => 'Objetos obsoletos devem ser atualizados, mas nenhuma atualização é especificado.', - 'Class:SynchroDataSource/Error:DataTableAlreadyExists' => 'A tabela %1$s já existe na base de dados. Por favor, use um outro nome para a tabela de dados sincronizada.', + 'Class:SynchroDataSource/Error:AtLeastOneReconciliationKeyMustBeSpecified' => 'Pelo menos uma chave de reconciliação deve ser especificada, ou a política de reconciliação deve ser a de usar a chave primária', + 'Class:SynchroDataSource/Error:DeleteRetentionDurationMustBeSpecified' => 'Um período de retenção de exclusão deve ser especificado, já que objetos devem ser excluídos depois de serem marcados como obsoletos', + 'Class:SynchroDataSource/Error:DeletePolicyUpdateMustBeSpecified' => 'Objetos obsoletos devem ser atualizados, mas nenhuma política de atualização foi especificada', + 'Class:SynchroDataSource/Error:DataTableAlreadyExists' => 'A tabela "%1$s" já existe no banco de dados. Por favor, use um outro nome para a tabela de dados sincronizada', 'Core:SynchroReplica:PublicData' => 'Dados públicos', - 'Core:SynchroReplica:PrivateDetails' => 'Detalhes privado', + 'Core:SynchroReplica:PrivateDetails' => 'Detalhes privados', 'Core:SynchroReplica:BackToDataSource' => 'Voltar para a fonte de dados sincronização: %1$s', - 'Core:SynchroReplica:ListOfReplicas' => 'Lista de replica', - 'Core:SynchroAttExtKey:ReconciliationById' => 'Id (chave primária)', + 'Core:SynchroReplica:ListOfReplicas' => 'Lista de réplica', + 'Core:SynchroAttExtKey:ReconciliationById' => 'ID (chave primária)', 'Core:SynchroAtt:attcode' => 'Atributo', 'Core:SynchroAtt:attcode+' => 'Campo do objeto', 'Core:SynchroAtt:reconciliation' => 'Reconciliação?', @@ -791,38 +793,38 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Core:SynchroAtt:update+' => 'Usado para atualizar o objeto', 'Core:SynchroAtt:update_policy' => 'Política de atualização', 'Core:SynchroAtt:update_policy+' => 'Comportamento do campo atualizado', - 'Core:SynchroAtt:reconciliation_attcode' => 'Chave reconciliação', - 'Core:SynchroAtt:reconciliation_attcode+' => 'Código atributo para a reconciliação chave externa', + 'Core:SynchroAtt:reconciliation_attcode' => 'Chave de reconciliação', + 'Core:SynchroAtt:reconciliation_attcode+' => 'Código de atributo para a reconciliação de chave externa', 'Core:SyncDataExchangeComment' => '(Sincronização dado)', 'Core:Synchro:ListOfDataSources' => 'Lista de fontes de dados:', 'Core:Synchro:LastSynchro' => 'Última sincronização:', 'Core:Synchro:ThisObjectIsSynchronized' => 'Este objeto é sincronizado com uma fonte de dados externa', - 'Core:Synchro:TheObjectWasCreatedBy_Source' => 'O objeto foi criado pela fonte de dados externa %1$s', - 'Core:Synchro:TheObjectCanBeDeletedBy_Source' => 'O objeto não pode ser excluído pela fonte de dados externa %1$s', - 'Core:Synchro:TheObjectCannotBeDeletedByUser_Source' => 'Você não pode excluir o objeto porque é propriedade de uma fonte de dados externa %1$s', + 'Core:Synchro:TheObjectWasCreatedBy_Source' => 'O objeto foi criado pela fonte de dados externa "%1$s"', + 'Core:Synchro:TheObjectCanBeDeletedBy_Source' => 'O objeto não pode ser excluído pela fonte de dados externa "%1$s"', + 'Core:Synchro:TheObjectCannotBeDeletedByUser_Source' => 'Você não pode excluir o objeto porque é propriedade de uma fonte de dados externa "%1$s"', 'TitleSynchroExecution' => 'Execução da sincronização', - 'Class:SynchroDataSource:DataTable' => 'Tabela base de dados: %1$s', - 'Core:SyncDataSourceObsolete' => 'A fonte de dados é marcado como obsoleto. Operação cancelada.', - 'Core:SyncDataSourceAccessRestriction' => 'Adminstradores ou apenas o usuário especificado na fonte de dados pode executar esta operação. Operação cancelada.', - 'Core:SyncTooManyMissingReplicas' => 'Todos os registros foram intocado por algum tempo (todos os objetos podem ser apagados). Verifique se o processo que grava na tabela de sincronização ainda está em execução. Operação cancelada.', - 'Core:SyncSplitModeCLIOnly' => 'A sincronização pode ser executado em pedaços só se for executado em modo CLI', - 'Core:Synchro:ListReplicas_AllReplicas_Errors_Warnings' => '%1$s replicas, %2$s erro(s), %3$s alerta(s).', - 'Core:SynchroReplica:TargetObject' => 'Objeto(s) sincronizado(s): %1$s', - 'Class:AsyncSendEmail' => 'Email (assíncrono)', + 'Class:SynchroDataSource:DataTable' => 'Tabela do banco de dados: "%1$s"', + 'Core:SyncDataSourceObsolete' => 'A fonte de dados está marcada como obsoleta. Operação cancelada', + 'Core:SyncDataSourceAccessRestriction' => 'Adminstradores ou apenas o usuário especificado na fonte de dados pode executar esta operação. Operação cancelada', + 'Core:SyncTooManyMissingReplicas' => 'Todos os registros estão intocados a algum tempo (todos os objetos podem ser apagados). Verifique se o processo que grava na tabela de sincronização ainda está em execução. Operação cancelada', + 'Core:SyncSplitModeCLIOnly' => 'A sincronização pode ser executada em pedaços só se for executada em modo CLI', + 'Core:Synchro:ListReplicas_AllReplicas_Errors_Warnings' => '%1$s réplica(s), %2$s erro(s), %3$s alerta(s)', + 'Core:SynchroReplica:TargetObject' => 'Objeto sincronizado: %1$s', + 'Class:AsyncSendEmail' => 'E-mail (assíncrono)', 'Class:AsyncSendEmail/Attribute:to' => 'Para', 'Class:AsyncSendEmail/Attribute:subject' => 'Assunto', 'Class:AsyncSendEmail/Attribute:body' => 'Corpo', 'Class:AsyncSendEmail/Attribute:header' => 'Cabeçalho', - 'Class:CMDBChangeOpSetAttributeOneWayPassword' => 'Senha criptograda', + 'Class:CMDBChangeOpSetAttributeOneWayPassword' => 'Senha criptografada', 'Class:CMDBChangeOpSetAttributeOneWayPassword/Attribute:prev_pwd' => 'Valor anterior', 'Class:CMDBChangeOpSetAttributeEncrypted' => 'Campo criptografado', 'Class:CMDBChangeOpSetAttributeEncrypted/Attribute:prevstring' => 'Valor anterior', - 'Class:CMDBChangeOpSetAttributeCaseLog' => 'Caso Log', + 'Class:CMDBChangeOpSetAttributeCaseLog' => 'Case Log~~', 'Class:CMDBChangeOpSetAttributeCaseLog/Attribute:lastentry' => 'Última entrada', - 'Class:SynchroDataSource' => 'Fonte de dados sincronização', - 'Class:SynchroDataSource/Attribute:status/Value:implementation' => 'Implementação', + 'Class:SynchroDataSource' => 'Fonte de Sincronização de Dados', + 'Class:SynchroDataSource/Attribute:status/Value:implementation' => 'Em homologação', 'Class:SynchroDataSource/Attribute:status/Value:obsolete' => 'Obsoleto', - 'Class:SynchroDataSource/Attribute:status/Value:production' => 'Produção', + 'Class:SynchroDataSource/Attribute:status/Value:production' => 'Em produção', 'Class:SynchroDataSource/Attribute:scope_restriction' => 'Restrição de escopo', 'Class:SynchroDataSource/Attribute:reconciliation_policy/Value:use_attributes' => 'Use os atributos', 'Class:SynchroDataSource/Attribute:reconciliation_policy/Value:use_primary_key' => 'Use o campo primary_key', @@ -832,76 +834,76 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:SynchroDataSource/Attribute:action_on_one/Value:update' => 'Atualizar', 'Class:SynchroDataSource/Attribute:action_on_multiple/Value:create' => 'Criar', 'Class:SynchroDataSource/Attribute:action_on_multiple/Value:error' => 'Erro', - 'Class:SynchroDataSource/Attribute:action_on_multiple/Value:take_first' => 'TPegue o primeiro (acaso?)', - 'Class:SynchroDataSource/Attribute:delete_policy' => 'Política exclusão', + 'Class:SynchroDataSource/Attribute:action_on_multiple/Value:take_first' => 'Pegue o primeiro (ao acaso?)', + 'Class:SynchroDataSource/Attribute:delete_policy' => 'Política de exclusão', 'Class:SynchroDataSource/Attribute:delete_policy/Value:delete' => 'Excluir', 'Class:SynchroDataSource/Attribute:delete_policy/Value:ignore' => 'Ignorar', 'Class:SynchroDataSource/Attribute:delete_policy/Value:update' => 'Atualizar', - 'Class:SynchroDataSource/Attribute:delete_policy/Value:update_then_delete' => 'Atualize quando excluído', - 'Class:SynchroDataSource/Attribute:attribute_list' => 'Listar atributos', + 'Class:SynchroDataSource/Attribute:delete_policy/Value:update_then_delete' => 'Atualizar então Excluir', + 'Class:SynchroDataSource/Attribute:attribute_list' => 'Exibir atributos', 'Class:SynchroDataSource/Attribute:user_delete_policy/Value:administrators' => 'Somente administradores', - 'Class:SynchroDataSource/Attribute:user_delete_policy/Value:everybody' => 'Permissão total para excluir esses objetos', + 'Class:SynchroDataSource/Attribute:user_delete_policy/Value:everybody' => 'Todos os usuários', 'Class:SynchroDataSource/Attribute:user_delete_policy/Value:nobody' => 'Ninguém', - 'Class:SynchroAttribute' => 'Atributo sincronização', + 'Class:SynchroAttribute' => 'Atributo de sincronização', 'Class:SynchroAttribute/Attribute:sync_source_id' => 'Fonte de dados', - 'Class:SynchroAttribute/Attribute:attcode' => 'Código atributo', + 'Class:SynchroAttribute/Attribute:attcode' => 'Código do atributo', 'Class:SynchroAttribute/Attribute:update' => 'Atualizar', 'Class:SynchroAttribute/Attribute:reconcile' => 'Reconciliar', - 'Class:SynchroAttribute/Attribute:update_policy' => 'Política atualizar', - 'Class:SynchroAttribute/Attribute:update_policy/Value:master_locked' => 'Trancado', - 'Class:SynchroAttribute/Attribute:update_policy/Value:master_unlocked' => 'Destrancado', - 'Class:SynchroAttribute/Attribute:update_policy/Value:write_if_empty' => 'Iniciando se vazio', + 'Class:SynchroAttribute/Attribute:update_policy' => 'Política de atualização', + 'Class:SynchroAttribute/Attribute:update_policy/Value:master_locked' => 'Slave', + 'Class:SynchroAttribute/Attribute:update_policy/Value:master_unlocked' => 'Master', + 'Class:SynchroAttribute/Attribute:update_policy/Value:write_if_empty' => 'Atualizar se vazio', 'Class:SynchroAttribute/Attribute:finalclass' => 'Classe', - 'Class:SynchroAttExtKey' => 'Atributo sincronização (ExtKey)', - 'Class:SynchroAttExtKey/Attribute:reconciliation_attcode' => 'Atributo reconciliação', - 'Class:SynchroAttLinkSet' => 'Atributo sincronização (Linkset)', + 'Class:SynchroAttExtKey' => 'Atributo de sincronização (ExtKey)', + 'Class:SynchroAttExtKey/Attribute:reconciliation_attcode' => 'Atributo de reconciliação', + 'Class:SynchroAttLinkSet' => 'Atributo de sincronização (Linkset)', 'Class:SynchroAttLinkSet/Attribute:row_separator' => 'Separador de linhas', - 'Class:SynchroAttLinkSet/Attribute:attribute_separator' => 'Separador atributos', - 'Class:SynchroLog' => 'Log sincronização', - 'Class:SynchroLog/Attribute:sync_source_id' => 'Fonte de dados sincronização', - 'Class:SynchroLog/Attribute:start_date' => 'Data início', + 'Class:SynchroAttLinkSet/Attribute:attribute_separator' => 'Separador de atributos', + 'Class:SynchroLog' => 'Log de sincronização', + 'Class:SynchroLog/Attribute:sync_source_id' => 'Fonte de sincronização de dados', + 'Class:SynchroLog/Attribute:start_date' => 'Data de início', 'Class:SynchroLog/Attribute:end_date' => 'Data final', 'Class:SynchroLog/Attribute:status' => 'Status', - 'Class:SynchroLog/Attribute:status/Value:completed' => 'Completado', + 'Class:SynchroLog/Attribute:status/Value:completed' => 'Finalizado', 'Class:SynchroLog/Attribute:status/Value:error' => 'Erro', - 'Class:SynchroLog/Attribute:status/Value:running' => 'Ainda está em execução', - 'Class:SynchroLog/Attribute:stats_nb_replica_seen' => 'Número réplica vista', - 'Class:SynchroLog/Attribute:stats_nb_replica_total' => 'Número réplica total', - 'Class:SynchroLog/Attribute:stats_nb_obj_deleted' => 'Número objeto(s) excluído(s)', - 'Class:SynchroLog/Attribute:stats_nb_obj_deleted_errors' => 'Número de erros enquanto excluindo', - 'Class:SynchroLog/Attribute:stats_nb_obj_obsoleted' => 'Número objeto(s) obsoleto(s)', - 'Class:SynchroLog/Attribute:stats_nb_obj_obsoleted_errors' => 'Número de erros enquanto obsoletos', - 'Class:SynchroLog/Attribute:stats_nb_obj_created' => 'Número objeto(s) criado(s)', - 'Class:SynchroLog/Attribute:stats_nb_obj_created_errors' => 'Número de erros enquanto criando', - 'Class:SynchroLog/Attribute:stats_nb_obj_updated' => 'Número objeto(s) atualizado(s)', - 'Class:SynchroLog/Attribute:stats_nb_obj_updated_errors' => 'Número de erros enquanto atualizando', - 'Class:SynchroLog/Attribute:stats_nb_replica_reconciled_errors' => 'Número de erros durante reconciliação', - 'Class:SynchroLog/Attribute:stats_nb_replica_disappeared_no_action' => 'Número réplica desaparecida', - 'Class:SynchroLog/Attribute:stats_nb_obj_new_updated' => 'Número objetos atualizados', - 'Class:SynchroLog/Attribute:stats_nb_obj_new_unchanged' => 'Número objetos inalterados', + 'Class:SynchroLog/Attribute:status/Value:running' => 'Em execução', + 'Class:SynchroLog/Attribute:stats_nb_replica_seen' => 'Número réplica vista~~', + 'Class:SynchroLog/Attribute:stats_nb_replica_total' => 'Número réplica total~~', + 'Class:SynchroLog/Attribute:stats_nb_obj_deleted' => 'Número de objeto(s) excluído(s)', + 'Class:SynchroLog/Attribute:stats_nb_obj_deleted_errors' => 'Número de erros durante a exclusão', + 'Class:SynchroLog/Attribute:stats_nb_obj_obsoleted' => 'Número de objeto(s) obsoleto(s)', + 'Class:SynchroLog/Attribute:stats_nb_obj_obsoleted_errors' => 'Número de erros durante a obsolescência', + 'Class:SynchroLog/Attribute:stats_nb_obj_created' => 'Número de objeto(s) criado(s)', + 'Class:SynchroLog/Attribute:stats_nb_obj_created_errors' => 'Número de erros durante a criação', + 'Class:SynchroLog/Attribute:stats_nb_obj_updated' => 'Número de objeto(s) atualizado(s)', + 'Class:SynchroLog/Attribute:stats_nb_obj_updated_errors' => 'Número de erros durante a atualização', + 'Class:SynchroLog/Attribute:stats_nb_replica_reconciled_errors' => 'Número de erros durante a reconciliação', + 'Class:SynchroLog/Attribute:stats_nb_replica_disappeared_no_action' => 'Número de réplicas desaparecidas', + 'Class:SynchroLog/Attribute:stats_nb_obj_new_updated' => 'Número de objetos atualizados', + 'Class:SynchroLog/Attribute:stats_nb_obj_new_unchanged' => 'Número de objetos inalterados', 'Class:SynchroLog/Attribute:last_error' => 'Últimos erros', 'Class:SynchroLog/Attribute:traces' => 'Rastrear', - 'Class:SynchroReplica' => 'Réplica sincronização', + 'Class:SynchroReplica' => 'Sincronização de réplica', 'Class:SynchroReplica/Attribute:sync_source_id' => 'Fonte de dados', - 'Class:SynchroReplica/Attribute:dest_id' => 'Objeto destino (ID)', - 'Class:SynchroReplica/Attribute:dest_class' => 'Tipo destino', + 'Class:SynchroReplica/Attribute:dest_id' => 'Objeto de destino (ID)', + 'Class:SynchroReplica/Attribute:dest_class' => 'Tipo de destino', 'Class:SynchroReplica/Attribute:status_last_seen' => 'Visto pela última vez', 'Class:SynchroReplica/Attribute:status' => 'Status', 'Class:SynchroReplica/Attribute:status/Value:modified' => 'Modificado', 'Class:SynchroReplica/Attribute:status/Value:new' => 'Novo', 'Class:SynchroReplica/Attribute:status/Value:obsolete' => 'Obsoleto', - 'Class:SynchroReplica/Attribute:status/Value:orphan' => 'Orfão', + 'Class:SynchroReplica/Attribute:status/Value:orphan' => 'Órfão', 'Class:SynchroReplica/Attribute:status/Value:synchronized' => 'Sincronizado', 'Class:SynchroReplica/Attribute:status_dest_creator' => 'Objeto criado?', 'Class:SynchroReplica/Attribute:status_last_error' => 'Último Erro', 'Class:SynchroReplica/Attribute:status_last_warning' => 'Alertas', - 'Class:SynchroReplica/Attribute:info_creation_date' => 'Data criação', + 'Class:SynchroReplica/Attribute:info_creation_date' => 'Data de criação', 'Class:SynchroReplica/Attribute:info_last_modified' => 'Última data modificação', - 'Class:appUserPreferences' => 'Preferência de usuário', + 'Class:appUserPreferences' => 'Preferências de usuário', 'Class:appUserPreferences/Attribute:userid' => 'Usuário', 'Class:appUserPreferences/Attribute:preferences' => 'Preferências', - 'Core:ExecProcess:Code1' => 'Comando errado ou comando terminou com erros (por exemplo, nome do script errado)', - 'Core:ExecProcess:Code255' => 'PHP erro (parsing, or runtime)', + 'Core:ExecProcess:Code1' => 'Comando incorreto ou comando terminou com erros (por exemplo, nome do script errado)', + 'Core:ExecProcess:Code255' => 'Erro PHP (parsing, ou runtime)', // Attribute Duration 'Core:Duration_Seconds' => '%1$ds', @@ -916,7 +918,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Bulk export 'Core:BulkExport:MissingParameter_Param' => 'Parâmetro ausente \\"%1$s\\"', - 'Core:BulkExport:InvalidParameter_Query' => 'Valor inválido para o parâmetro \\"query\\". Não há nenhum Phrasebook de consulta correspondente ao id: \\"%1$s\\".', + 'Core:BulkExport:InvalidParameter_Query' => 'Valor inválido para o parâmetro \\"query\\". Não há nenhum entrada no livro de consultas correspondente ao ID: \\"%1$s\\"', 'Core:BulkExport:ExportFormatPrompt' => 'Formato de exportação:', 'Core:BulkExportOf_Class' => '%1$s Export', 'Core:BulkExport:ClickHereToDownload_FileName' => 'Clique aqui para baixar %1$s', @@ -926,44 +928,44 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Core:BulkExport:CSVFormat' => 'Valores separados por vírgula (*.csv)', 'Core:BulkExport:XLSXFormat' => 'Excel 2007 ou mais recente (*.xlsx)', 'Core:BulkExport:PDFFormat' => 'Documento PDF (*.pdf)', - 'Core:BulkExport:DragAndDropHelp' => 'Arrastar e soltar os cabeçalhos das colunas para organizar as colunas. Visualização de %1$s linhas. Número total de linhas para exportar: %2$s.', + 'Core:BulkExport:DragAndDropHelp' => 'Segure e arraste os cabeçalhos das colunas para organizar as colunas. Visualização de %1$s linha(s). Número total de linhas para exportar: %2$s', 'Core:BulkExport:EmptyPreview' => 'Selecione as colunas a serem exportadas da lista acima', 'Core:BulkExport:ColumnsOrder' => 'Ordem das colunas', - 'Core:BulkExport:AvailableColumnsFrom_Class' => 'Colunas disponíveis de %1$s', - 'Core:BulkExport:NoFieldSelected' => 'Selecione pelo menos uma coluna para ser exportada', - 'Core:BulkExport:CheckAll' => 'Check All~~', - 'Core:BulkExport:UncheckAll' => 'Desmarque tudo', + 'Core:BulkExport:AvailableColumnsFrom_Class' => 'Colunas disponíveis de(a) %1$s', + 'Core:BulkExport:NoFieldSelected' => 'Selecione pelo menos uma coluna para exportar', + 'Core:BulkExport:CheckAll' => 'Marcar todos', + 'Core:BulkExport:UncheckAll' => 'Desmarcar todos', 'Core:BulkExport:ExportCancelledByUser' => 'Exportação cancelada pelo usuário', - 'Core:BulkExport:CSVOptions' => 'CSV Options', - 'Core:BulkExport:CSVLocalization' => 'Localização', + 'Core:BulkExport:CSVOptions' => 'Opções de exportação CSV', + 'Core:BulkExport:CSVLocalization' => 'Codificação de caracteres:', 'Core:BulkExport:PDFOptions' => 'Opções de PDF', 'Core:BulkExport:PDFPageFormat' => 'Formato da página', - 'Core:BulkExport:PDFPageSize' => 'Tamanho da página:', + 'Core:BulkExport:PDFPageSize' => 'Tamanho da página', 'Core:BulkExport:PageSize-A4' => 'A4', 'Core:BulkExport:PageSize-A3' => 'A3', 'Core:BulkExport:PageSize-Letter' => 'Carta', - 'Core:BulkExport:PDFPageOrientation' => 'Orientação de Página:', - 'Core:BulkExport:PageOrientation-L' => 'Landscape~~', - 'Core:BulkExport:PageOrientation-P' => 'Portrait~~', + 'Core:BulkExport:PDFPageOrientation' => 'Orientação da Página', + 'Core:BulkExport:PageOrientation-L' => 'Paisagem', + 'Core:BulkExport:PageOrientation-P' => 'Retrato', 'Core:BulkExport:XMLFormat' => 'Arquivo XML (*.xml)', 'Core:BulkExport:XMLOptions' => 'Opções XML', 'Core:BulkExport:SpreadsheetFormat' => 'Formato HTML de planilha (*.html)', 'Core:BulkExport:SpreadsheetOptions' => 'Opções de planilha', - 'Core:BulkExport:OptionNoLocalize' => 'Código de exportação em vez de Label', - 'Core:BulkExport:OptionLinkSets' => 'Incluir objetos vinculados', + 'Core:BulkExport:OptionNoLocalize' => 'Usar Código de exportação ao invés do Título dos objetos', + 'Core:BulkExport:OptionLinkSets' => 'Incluir objetos associados', 'Core:BulkExport:OptionFormattedText' => 'Preservar formatação de texto', 'Core:BulkExport:ScopeDefinition' => 'Definição dos objetos a exportar', - 'Core:BulkExportLabelOQLExpression' => 'OQL Query:~~', - 'Core:BulkExportLabelPhrasebookEntry' => 'Query Phrasebook Entry:~~', - 'Core:BulkExportMessageEmptyOQL' => 'Por favor insira uma consulta OQL válida.', - 'Core:BulkExportMessageEmptyPhrasebookEntry' => 'Por favor, selecione uma entrada de livro de frases válida.', + 'Core:BulkExportLabelOQLExpression' => 'Consulta OQL:', + 'Core:BulkExportLabelPhrasebookEntry' => 'Entrada do livro de consultas:', + 'Core:BulkExportMessageEmptyOQL' => 'Por favor, insira uma consulta OQL válida', + 'Core:BulkExportMessageEmptyPhrasebookEntry' => 'Por favor, selecione uma entrada válida do livro de consultas', 'Core:BulkExportQueryPlaceholder' => 'Digite uma consulta OQL aqui ...', - 'Core:BulkExportCanRunNonInteractive' => 'Clique aqui para executar a exportação no modo não interativo.', - 'Core:BulkExportLegacyExport' => 'Clique aqui para acessar a exportação legada.', + 'Core:BulkExportCanRunNonInteractive' => 'Clique aqui para executar a exportação no modo não interativo', + 'Core:BulkExportLegacyExport' => 'Clique aqui para acessar a exportação legada', 'Core:BulkExport:XLSXOptions' => 'Opções do Excel', - 'Core:BulkExport:TextFormat' => 'Campos de texto contendo alguma marcação HTML', + 'Core:BulkExport:TextFormat' => 'Campos de texto contendo códigos HTML', 'Core:BulkExport:DateTimeFormat' => 'Formato de data e hora', - 'Core:BulkExport:DateTimeFormatDefault_Example' => 'Formato padrão (%1$s), por ex. %2$s', + 'Core:BulkExport:DateTimeFormatDefault_Example' => 'Formato padrão (%1$s), por exemplo: %2$s', 'Core:BulkExport:DateTimeFormatCustom_Format' => 'Formato personalizado: %1$s', 'Core:BulkExport:PDF:PageNumber' => 'Página %1$s', 'Core:DateTime:Placeholder_d' => 'DD', // Day of the month: 2 digits (with leading zero) @@ -980,9 +982,9 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Core:DateTime:Placeholder_A' => 'AM/PM', // AM/PM (uppercase) 'Core:DateTime:Placeholder_i' => 'mm', // minutes, 2 digits: 00..59 'Core:DateTime:Placeholder_s' => 'ss', // seconds, 2 digits 00..59 - 'Core:Validator:Default' => 'Formato errado', + 'Core:Validator:Default' => 'Formato inválido', 'Core:Validator:Mandatory' => 'Por favor, preencha este campo', - 'Core:Validator:MustBeInteger' => 'Deve ser um inteiro', + 'Core:Validator:MustBeInteger' => 'Deve ser um número inteiro', 'Core:Validator:MustSelectOne' => 'Por favor, selecione um', )); @@ -999,18 +1001,18 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:TagSetFieldData/Attribute:label+' => 'Rótulo exibido', 'Class:TagSetFieldData/Attribute:description' => 'Descrição', 'Class:TagSetFieldData/Attribute:description+' => '', - 'Class:TagSetFieldData/Attribute:finalclass' => 'Tag class~~', + 'Class:TagSetFieldData/Attribute:finalclass' => 'Classe da etiqueta', 'Class:TagSetFieldData/Attribute:obj_class' => 'Classe de objeto', - 'Class:TagSetFieldData/Attribute:obj_attcode' => 'Código de campo', + 'Class:TagSetFieldData/Attribute:obj_attcode' => 'Código de atributo', - 'Core:TagSetFieldData:ErrorDeleteUsedTag' => 'Tags usadas não podem ser deletadas', - 'Core:TagSetFieldData:ErrorDuplicateTagCodeOrLabel' => 'Tags códigos ou rótulos devem ser únicos', - 'Core:TagSetFieldData:ErrorTagCodeSyntax' => 'O código de tags deve conter entre 3 e %1$d caracteres alfanuméricos', + 'Core:TagSetFieldData:ErrorDeleteUsedTag' => 'Tags em uso não podem ser deletadas', + 'Core:TagSetFieldData:ErrorDuplicateTagCodeOrLabel' => 'O código de tag ou rótulo devem ser únicos', + 'Core:TagSetFieldData:ErrorTagCodeSyntax' => 'O código de tag deve conter entre 3 e %1$d caracteres alfanuméricos', 'Core:TagSetFieldData:ErrorTagCodeReservedWord' => 'O código de tag escolhido é uma palavra reservada', - 'Core:TagSetFieldData:ErrorTagLabelSyntax' => 'O rótulo de etiquetas não deve conter \'%1$s\'nem estar vazio', - 'Core:TagSetFieldData:ErrorCodeUpdateNotAllowed' => 'Tags Código não pode ser alterado quando usado', - 'Core:TagSetFieldData:ErrorClassUpdateNotAllowed' => 'Tags "Object Class" não pode ser alterado', - 'Core:TagSetFieldData:ErrorAttCodeUpdateNotAllowed' => 'Tags "Código do atributo" não pode ser alterado', + 'Core:TagSetFieldData:ErrorTagLabelSyntax' => 'O rótulo da tag não deve conter \'%1$s\' nem estar vazio', + 'Core:TagSetFieldData:ErrorCodeUpdateNotAllowed' => 'Tags de código não podem ser alteradas quando em uso', + 'Core:TagSetFieldData:ErrorClassUpdateNotAllowed' => 'Tags de "Classe de Objeto" não podem ser alteradas', + 'Core:TagSetFieldData:ErrorAttCodeUpdateNotAllowed' => 'Tags de "Código do atributo" não podem ser alteradas', 'Core:TagSetFieldData:WhereIsThisTagTab' => 'Uso de tags (%1$d)', 'Core:TagSetFieldData:NoEntryFound' => 'Nenhuma entrada encontrada para esta tag', )); @@ -1019,7 +1021,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Class: DBProperty // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:DBProperty' => 'Propriedade do DB', + 'Class:DBProperty' => 'Propriedades do DB', 'Class:DBProperty+' => '', 'Class:DBProperty/Attribute:name' => 'Nome', 'Class:DBProperty/Attribute:name+' => '', @@ -1027,9 +1029,9 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:DBProperty/Attribute:description+' => '', 'Class:DBProperty/Attribute:value' => 'Valor', 'Class:DBProperty/Attribute:value+' => '', - 'Class:DBProperty/Attribute:change_date' => 'Data de mudança', + 'Class:DBProperty/Attribute:change_date' => 'Data de alteração', 'Class:DBProperty/Attribute:change_date+' => '', - 'Class:DBProperty/Attribute:change_comment' => 'Alterar comentário', + 'Class:DBProperty/Attribute:change_comment' => 'Editar comentário', 'Class:DBProperty/Attribute:change_comment+' => '', )); @@ -1037,27 +1039,27 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Class: BackgroundTask // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:BackgroundTask' => 'Tarefa de fundo', + 'Class:BackgroundTask' => 'Tarefas de fundo', 'Class:BackgroundTask+' => '', - 'Class:BackgroundTask/Attribute:class_name' => 'Nome da turma', + 'Class:BackgroundTask/Attribute:class_name' => 'Nome da classe', 'Class:BackgroundTask/Attribute:class_name+' => '', - 'Class:BackgroundTask/Attribute:first_run_date' => 'Primeira data de execução', + 'Class:BackgroundTask/Attribute:first_run_date' => 'Data da primeira execução', 'Class:BackgroundTask/Attribute:first_run_date+' => '', - 'Class:BackgroundTask/Attribute:latest_run_date' => 'Data de execução mais recente', + 'Class:BackgroundTask/Attribute:latest_run_date' => 'Data da execução mais recente', 'Class:BackgroundTask/Attribute:latest_run_date+' => '', - 'Class:BackgroundTask/Attribute:next_run_date' => 'Próxima data de execução', + 'Class:BackgroundTask/Attribute:next_run_date' => 'Data da próxima execução', 'Class:BackgroundTask/Attribute:next_run_date+' => '', - 'Class:BackgroundTask/Attribute:total_exec_count' => 'Total exec. conte', + 'Class:BackgroundTask/Attribute:total_exec_count' => 'Número total de execuções', 'Class:BackgroundTask/Attribute:total_exec_count+' => '', - 'Class:BackgroundTask/Attribute:latest_run_duration' => 'Duração de execução mais recente', + 'Class:BackgroundTask/Attribute:latest_run_duration' => 'Duração da execução mais recente', 'Class:BackgroundTask/Attribute:latest_run_duration+' => '', - 'Class:BackgroundTask/Attribute:min_run_duration' => 'Min. duração de execução', + 'Class:BackgroundTask/Attribute:min_run_duration' => 'Duração mín. de execução', 'Class:BackgroundTask/Attribute:min_run_duration+' => '', - 'Class:BackgroundTask/Attribute:max_run_duration' => 'Max. duração de execução', + 'Class:BackgroundTask/Attribute:max_run_duration' => 'Duração máx. de execução', 'Class:BackgroundTask/Attribute:max_run_duration+' => '', 'Class:BackgroundTask/Attribute:average_run_duration' => 'Duração média de execução', 'Class:BackgroundTask/Attribute:average_run_duration+' => '', - 'Class:BackgroundTask/Attribute:running' => 'Correndo', + 'Class:BackgroundTask/Attribute:running' => 'Em execução', 'Class:BackgroundTask/Attribute:running+' => '', 'Class:BackgroundTask/Attribute:status' => 'Status', 'Class:BackgroundTask/Attribute:status+' => '', @@ -1067,30 +1069,30 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Class: AsyncTask // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:AsyncTask' => 'Assíncrono. tarefa', + 'Class:AsyncTask' => 'Tarefa assíncrona', 'Class:AsyncTask+' => '', - 'Class:AsyncTask/Attribute:created' => 'Criado', + 'Class:AsyncTask/Attribute:created' => 'Criada', 'Class:AsyncTask/Attribute:created+' => '', - 'Class:AsyncTask/Attribute:started' => 'Iniciado', + 'Class:AsyncTask/Attribute:started' => 'Iniciada', 'Class:AsyncTask/Attribute:started+' => '', - 'Class:AsyncTask/Attribute:planned' => 'Planejado', + 'Class:AsyncTask/Attribute:planned' => 'Planejada', 'Class:AsyncTask/Attribute:planned+' => '', 'Class:AsyncTask/Attribute:event_id' => 'Evento', 'Class:AsyncTask/Attribute:event_id+' => '', - 'Class:AsyncTask/Attribute:finalclass' => 'Aula final', + 'Class:AsyncTask/Attribute:finalclass' => 'Classe final', 'Class:AsyncTask/Attribute:finalclass+' => '', - 'Class:AsyncTask/Attribute:status' => 'Status~~', + 'Class:AsyncTask/Attribute:status' => 'Status', 'Class:AsyncTask/Attribute:status+' => '', - 'Class:AsyncTask/Attribute:remaining_retries' => 'Remaining retries~~', + 'Class:AsyncTask/Attribute:remaining_retries' => 'Tentativas restantes', 'Class:AsyncTask/Attribute:remaining_retries+' => '', - 'Class:AsyncTask/Attribute:last_error_code' => 'Last error code~~', + 'Class:AsyncTask/Attribute:last_error_code' => 'Último código de erro', 'Class:AsyncTask/Attribute:last_error_code+' => '', - 'Class:AsyncTask/Attribute:last_error' => 'Last error~~', + 'Class:AsyncTask/Attribute:last_error' => 'Último erro', 'Class:AsyncTask/Attribute:last_error+' => '', - 'Class:AsyncTask/Attribute:last_attempt' => 'Last attempt~~', + 'Class:AsyncTask/Attribute:last_attempt' => 'Última tentativa', 'Class:AsyncTask/Attribute:last_attempt+' => '', - 'Class:AsyncTask:InvalidConfig_Class_Keys' => 'Invalid format for the configuration of "async_task_retries[%1$s]". Expecting an array with the following keys: %2$s~~', - 'Class:AsyncTask:InvalidConfig_Class_InvalidKey_Keys' => 'Invalid format for the configuration of "async_task_retries[%1$s]": unexpected key "%2$s". Expecting only the following keys: %3$s~~', + 'Class:AsyncTask:InvalidConfig_Class_Keys' => 'Formato inválido para a configuração de "async_task_retries[%1$s]". Esperando um array com as seguintes chaves: %2$s', + 'Class:AsyncTask:InvalidConfig_Class_InvalidKey_Keys' => 'Formato inválido para a configuração de "async_task_retries[%1$s]": chave inesperada "%2$s". Esperando somente as seguintes chaves: %3$s', )); // @@ -1098,7 +1100,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:AbstractResource' => 'Recurso abstrato', + 'Class:AbstractResource' => 'Recurso Abstrato', 'Class:AbstractResource+' => '', )); @@ -1107,7 +1109,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:ResourceAdminMenu' => 'Recurso Admin Menu', + 'Class:ResourceAdminMenu' => 'Recurso Menu de Administração', 'Class:ResourceAdminMenu+' => '', )); @@ -1116,7 +1118,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:ResourceRunQueriesMenu' => 'Menu de consultas de execução de recursos', + 'Class:ResourceRunQueriesMenu' => 'Recurso Livro de Consultas', 'Class:ResourceRunQueriesMenu+' => '', )); @@ -1125,7 +1127,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:ResourceSystemMenu' => 'Resource System Menu~~', + 'Class:ResourceSystemMenu' => 'Menu de Recursos do Sistema', 'Class:ResourceSystemMenu+' => '', )); diff --git a/dictionaries/pt_br.dictionary.itop.ui.php b/dictionaries/pt_br.dictionary.itop.ui.php index 1e8c7dc632..58099b8666 100644 --- a/dictionaries/pt_br.dictionary.itop.ui.php +++ b/dictionaries/pt_br.dictionary.itop.ui.php @@ -15,7 +15,7 @@ // Class: AuditCategory // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:AuditCategory' => 'Categoria Auditoria', + 'Class:AuditCategory' => 'Categoria de Auditoria', 'Class:AuditCategory+' => 'Uma seção dentro da auditoria', 'Class:AuditCategory/Attribute:name' => 'Nome', 'Class:AuditCategory/Attribute:name+' => 'Nome curto para esta categoria', @@ -23,8 +23,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:AuditCategory/Attribute:description+' => 'Longa descrição para esta categoria de auditoria', 'Class:AuditCategory/Attribute:definition_set' => 'Definir Regra', 'Class:AuditCategory/Attribute:definition_set+' => 'Expressão OQL que define o conjunto de objetos para auditoria', - 'Class:AuditCategory/Attribute:rules_list' => 'Regras Auditoria', - 'Class:AuditCategory/Attribute:rules_list+' => 'Regra auditoria para essa categoria', + 'Class:AuditCategory/Attribute:rules_list' => 'Regras de Auditoria', + 'Class:AuditCategory/Attribute:rules_list+' => 'Regra de auditoria para essa categoria', )); // @@ -32,23 +32,23 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:AuditRule' => 'Regra de auditoria', + 'Class:AuditRule' => 'Regra de Auditoria', 'Class:AuditRule+' => 'Uma regra para verificar se uma determinada categoria de Auditoria', 'Class:AuditRule/Attribute:name' => 'Nome', 'Class:AuditRule/Attribute:name+' => 'Nome curto para esta regra', 'Class:AuditRule/Attribute:description' => 'Descrição', 'Class:AuditRule/Attribute:description+' => 'Descrição longa para essa regra', - 'Class:TagSetFieldData/Attribute:finalclass' => 'Tag class~~', - 'Class:TagSetFieldData/Attribute:obj_class' => 'Classe de objeto', - 'Class:TagSetFieldData/Attribute:obj_attcode' => 'Código de campo', + 'Class:TagSetFieldData/Attribute:finalclass' => 'Classe da tag', + 'Class:TagSetFieldData/Attribute:obj_class' => 'Classe do objeto', + 'Class:TagSetFieldData/Attribute:obj_attcode' => 'Código do campo', 'Class:AuditRule/Attribute:query' => 'Executar consulta', 'Class:AuditRule/Attribute:query+' => 'Executar a expressão OQL', 'Class:AuditRule/Attribute:valid_flag' => 'Objetos válidos?', 'Class:AuditRule/Attribute:valid_flag+' => 'Verdadeiro se a regra retornar o objeto válido, falso caso contrário', 'Class:AuditRule/Attribute:valid_flag/Value:true' => 'Verdadeiro', - 'Class:AuditRule/Attribute:valid_flag/Value:true+' => 'Verdadeiro', + 'Class:AuditRule/Attribute:valid_flag/Value:true+' => '', 'Class:AuditRule/Attribute:valid_flag/Value:false' => 'Falso', - 'Class:AuditRule/Attribute:valid_flag/Value:false+' => 'Falso', + 'Class:AuditRule/Attribute:valid_flag/Value:false+' => '', 'Class:AuditRule/Attribute:category_id' => 'Categoria', 'Class:AuditRule/Attribute:category_id+' => 'A categoria para esta regra', 'Class:AuditRule/Attribute:category_name' => 'Categoria', @@ -63,19 +63,19 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:Query' => 'Consulta', 'Class:Query+' => 'Uma consulta é um conjunto de dados definido de uma forma dinâmica', 'Class:Query/Attribute:name' => 'Nome', - 'Class:Query/Attribute:name+' => 'Identificar a consulta', + 'Class:Query/Attribute:name+' => 'Identificação da consulta', 'Class:Query/Attribute:description' => 'Descrição', - 'Class:Query/Attribute:description+' => 'Descrição longa para a consulta (finalidade, uso, etc)', - 'Class:Query/Attribute:is_template' => 'Template for OQL fields~~', - 'Class:Query/Attribute:is_template+' => 'Usable as source for recipient OQL in Notifications~~', - 'Class:Query/Attribute:is_template/Value:yes' => 'Yes~~', - 'Class:Query/Attribute:is_template/Value:no' => 'No~~', + 'Class:Query/Attribute:description+' => 'Descrição longa para a consulta (finalidade, uso, etc.)', + 'Class:Query/Attribute:is_template' => 'Template para campos OQL', + 'Class:Query/Attribute:is_template+' => 'Utilizável como origem para o Destinatário OQL em Notificações', + 'Class:Query/Attribute:is_template/Value:yes' => 'Sim', + 'Class:Query/Attribute:is_template/Value:no' => 'Não', 'Class:QueryOQL/Attribute:fields' => 'Campos', - 'Class:QueryOQL/Attribute:fields+' => 'Vírgula separando a lista de atributos (ou alias.attribute) para exportar.', + 'Class:QueryOQL/Attribute:fields+' => 'Lista separada por vírgulas de atributos (ou alias.attribute) para exportar', 'Class:QueryOQL' => 'Consulta OQL', - 'Class:QueryOQL+' => 'Uma consulta baseada no Object Query Language OQL', + 'Class:QueryOQL+' => 'Uma consulta baseada no Object Query Language (OQL)', 'Class:QueryOQL/Attribute:oql' => 'Expressão', - 'Class:QueryOQL/Attribute:oql+' => 'Expressão OQL', + 'Class:QueryOQL/Attribute:oql+' => 'Expressão Object Query Language (OQL)', )); ////////////////////////////////////////////////////////////////////// @@ -89,46 +89,46 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:User' => 'Usuário', - 'Class:User+' => 'Login', + 'Class:User+' => '', 'Class:User/Attribute:finalclass' => 'Tipo de conta', 'Class:User/Attribute:finalclass+' => '', - 'Class:User/Attribute:contactid' => 'Contato (pessoa)', - 'Class:User/Attribute:contactid+' => 'Dados pessoais a partir dos dados do negócio', + 'Class:User/Attribute:contactid' => 'Pessoa', + 'Class:User/Attribute:contactid+' => '', 'Class:User/Attribute:org_id' => 'Organização', - 'Class:User/Attribute:org_id+' => 'Organization of the associated person~~', - 'Class:User/Attribute:last_name' => 'Último nome', - 'Class:User/Attribute:last_name+' => 'Nome do contato correspondente', + 'Class:User/Attribute:org_id+' => 'Organização à qual esse usuário pertence', + 'Class:User/Attribute:last_name' => 'Sobrenome', + 'Class:User/Attribute:last_name+' => 'Último nome do usuário correspondente', 'Class:User/Attribute:first_name' => 'Primeiro nome', - 'Class:User/Attribute:first_name+' => 'Primeiro nome do contato correspondente', - 'Class:User/Attribute:email' => 'Email', - 'Class:User/Attribute:email+' => 'Email do contato correspondente', + 'Class:User/Attribute:first_name+' => 'Primeiro nome do usuário correspondente', + 'Class:User/Attribute:email' => 'E-mail', + 'Class:User/Attribute:email+' => 'Endereço de e-mail do usuário correspondente', 'Class:User/Attribute:login' => 'Login', - 'Class:User/Attribute:login+' => 'String de identificação do usuário', - 'Class:User/Attribute:language' => 'Linguagem', - 'Class:User/Attribute:language+' => 'Linguagem usuário', - 'Class:User/Attribute:language/Value:EN US' => 'English', - 'Class:User/Attribute:language/Value:EN US+' => 'English (U.S.)', - 'Class:User/Attribute:language/Value:FR FR' => 'French', - 'Class:User/Attribute:language/Value:FR FR+' => 'French (France)', + 'Class:User/Attribute:login+' => 'Login de acesso ao '.ITOP_APPLICATION_SHORT, + 'Class:User/Attribute:language' => 'Idioma', + 'Class:User/Attribute:language+' => 'Idioma do usuário correspondente', + 'Class:User/Attribute:language/Value:EN US' => 'Inglês', + 'Class:User/Attribute:language/Value:EN US+' => 'Inglês (E.U.A.)', + 'Class:User/Attribute:language/Value:FR FR' => 'Francês', + 'Class:User/Attribute:language/Value:FR FR+' => 'Francês (França)', 'Class:User/Attribute:profile_list' => 'Perfil', - 'Class:User/Attribute:profile_list+' => 'Regras, permissões de direito para essa pessoa', + 'Class:User/Attribute:profile_list+' => 'Permissões de acesso para esse usuário', 'Class:User/Attribute:allowed_org_list' => 'Organizações permitidas', - 'Class:User/Attribute:allowed_org_list+' => 'O usuário está permitido ver as informações para a(s) organização(ões) abaixo. Se nenhuma organização for especificada, não há restrição.', + 'Class:User/Attribute:allowed_org_list+' => 'O usuário tem permissão de ver as informações para a(s) organização(ões) abaixo. Se nenhuma organização for especificada, não há restrição', 'Class:User/Attribute:status' => 'Status', - 'Class:User/Attribute:status+' => 'Se a conta de usuário está habilitada ou desabilitada.', - 'Class:User/Attribute:status/Value:enabled' => 'Ativado', - 'Class:User/Attribute:status/Value:disabled' => 'Desativado', + 'Class:User/Attribute:status+' => 'Se a conta de usuário está habilitada ou desabilitada', + 'Class:User/Attribute:status/Value:enabled' => 'Ativa', + 'Class:User/Attribute:status/Value:disabled' => 'Desativada', 'Class:User/Error:LoginMustBeUnique' => 'Login é único - "%1s" já está ativo', - 'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Pelo menos um perfil deve ser atribuído a esse usuário.', - 'Class:User/Error:ProfileNotAllowed' => 'Profile "%1$s" cannot be added it will deny the access to backoffice~~', - 'Class:User/Error:StatusChangeIsNotAllowed' => 'Changing status is not allowed for your own User~~', - 'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Allowed organizations must contain User organization~~', - 'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'The current list of profiles does not give sufficient access rights (Users are not modifiable anymore)~~', - 'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'Pelo menos uma organização deve ser atribuída a este usuário.', - 'Class:User/Error:OrganizationNotAllowed' => 'Organização não permitida.', - 'Class:User/Error:UserOrganizationNotAllowed' => 'A conta de usuário não pertence às suas organizações permitidas.', - 'Class:User/Error:PersonIsMandatory' => 'O contato é obrigatório.', + 'Class:User/Error:AtLeastOneProfileIsNeeded' => 'Pelo menos um perfil deve ser atribuído a esse usuário', + 'Class:User/Error:ProfileNotAllowed' => 'O perfil "%1$s" não pode ser adicionado, ele negará o acesso ao backoffice', + 'Class:User/Error:StatusChangeIsNotAllowed' => 'Alterar o status da conta não é permitido para o seu próprio usuário', + 'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'As organizações permitidas devem conter apenas usuários pertencentes a organização', + 'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'A lista atual de perfis não fornece permissões de acesso suficientes (os usuários não são mais modificáveis)', + 'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'Pelo menos uma organização deve ser atribuída a esse usuário', + 'Class:User/Error:OrganizationNotAllowed' => 'Organização não permitida', + 'Class:User/Error:UserOrganizationNotAllowed' => 'A conta de usuário não pertence às suas organizações permitidas', + 'Class:User/Error:PersonIsMandatory' => 'O contato é obrigatório', 'Class:UserInternal' => 'Usuário Interno', 'Class:UserInternal+' => 'Usuário definido dentro do '.ITOP_APPLICATION_SHORT, )); @@ -138,12 +138,12 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:URP_Profiles' => 'Perfis', - 'Class:URP_Profiles+' => 'Perfil do usuário', + 'Class:URP_Profiles' => 'Perfil', + 'Class:URP_Profiles+' => 'Perfil do Usuário', 'Class:URP_Profiles/Attribute:name' => 'Nome', 'Class:URP_Profiles/Attribute:name+' => '', 'Class:URP_Profiles/Attribute:description' => 'Descrição', - 'Class:URP_Profiles/Attribute:description+' => 'uma linha descrição', + 'Class:URP_Profiles/Attribute:description+' => 'Uma descrição curta', 'Class:URP_Profiles/Attribute:user_list' => 'Usuários', 'Class:URP_Profiles/Attribute:user_list+' => 'Pessoas que possuem esse perfil', )); @@ -153,14 +153,14 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:URP_Dimensions' => 'dimensão', - 'Class:URP_Dimensions+' => 'dimensão de aplicação (definição silos)', + 'Class:URP_Dimensions' => 'Dimensão', + 'Class:URP_Dimensions+' => 'Dimensão de aplicação (definição de silos)', 'Class:URP_Dimensions/Attribute:name' => 'Nome', 'Class:URP_Dimensions/Attribute:name+' => '', 'Class:URP_Dimensions/Attribute:description' => 'Descrição', - 'Class:URP_Dimensions/Attribute:description+' => 'uma linha descrição', + 'Class:URP_Dimensions/Attribute:description+' => 'Uma descrição curta', 'Class:URP_Dimensions/Attribute:type' => 'Tipo', - 'Class:URP_Dimensions/Attribute:type+' => 'nome classe ou tipo dado (unidade projeção)', + 'Class:URP_Dimensions/Attribute:type+' => 'Nome da classe ou tipo de dado (unidade de mapeamento)', )); // @@ -168,19 +168,19 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:URP_UserProfile' => 'Usuário para perfil', - 'Class:URP_UserProfile+' => 'Perfil usuário', + 'Class:URP_UserProfile' => 'Perfil de Usuário', + 'Class:URP_UserProfile+' => 'Perfil de Usuário', 'Class:URP_UserProfile/Name' => 'Link entre %1$s e %2$s', 'Class:URP_UserProfile/Attribute:userid' => 'Usuário', - 'Class:URP_UserProfile/Attribute:userid+' => 'Conta usuário', + 'Class:URP_UserProfile/Attribute:userid+' => 'Conta de usuário', 'Class:URP_UserProfile/Attribute:userlogin' => 'Login', - 'Class:URP_UserProfile/Attribute:userlogin+' => 'Login', + 'Class:URP_UserProfile/Attribute:userlogin+' => '', 'Class:URP_UserProfile/Attribute:profileid' => 'Perfil', 'Class:URP_UserProfile/Attribute:profileid+' => 'Perfil utilizado', 'Class:URP_UserProfile/Attribute:profile' => 'Perfil', - 'Class:URP_UserProfile/Attribute:profile+' => 'Nome perfil', + 'Class:URP_UserProfile/Attribute:profile+' => 'Nome do perfil', 'Class:URP_UserProfile/Attribute:reason' => 'Função', - 'Class:URP_UserProfile/Attribute:reason+' => 'Explicação por que esta pessoa teve ter esse perfil', + 'Class:URP_UserProfile/Attribute:reason+' => 'Explicação por que esta pessoa deve ter essa função', )); // @@ -189,19 +189,19 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:URP_UserOrg' => 'Organização usuário', + 'Class:URP_UserOrg' => 'Organização do usuário', 'Class:URP_UserOrg+' => 'Organizações permitidas', 'Class:URP_UserOrg/Name' => 'Link entre %1$s e %2$s', - 'Class:URP_UserOrg/Attribute:userid' => 'Usário', - 'Class:URP_UserOrg/Attribute:userid+' => 'Conta usuário', + 'Class:URP_UserOrg/Attribute:userid' => 'Usuário', + 'Class:URP_UserOrg/Attribute:userid+' => 'Conta de usuário', 'Class:URP_UserOrg/Attribute:userlogin' => 'Login', - 'Class:URP_UserOrg/Attribute:userlogin+' => 'Login', + 'Class:URP_UserOrg/Attribute:userlogin+' => '', 'Class:URP_UserOrg/Attribute:allowed_org_id' => 'Organização', 'Class:URP_UserOrg/Attribute:allowed_org_id+' => 'Organização permitida', 'Class:URP_UserOrg/Attribute:allowed_org_name' => 'Organização', 'Class:URP_UserOrg/Attribute:allowed_org_name+' => 'Organização permitida', 'Class:URP_UserOrg/Attribute:reason' => 'Função', - 'Class:URP_UserOrg/Attribute:reason+' => 'explicação por que essa pessoa é permitida ver as informações da organização abaixo', + 'Class:URP_UserOrg/Attribute:reason+' => 'Explicação por que essa pessoa tem permissão para ver os dados pertencentes com essa organização', )); // @@ -209,18 +209,18 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Class:URP_ProfileProjection' => 'profile_projection', - 'Class:URP_ProfileProjection+' => 'profile projections', + 'Class:URP_ProfileProjection' => 'Mapeamentos de Perfil', + 'Class:URP_ProfileProjection+' => '', 'Class:URP_ProfileProjection/Attribute:dimensionid' => 'Dimensão', - 'Class:URP_ProfileProjection/Attribute:dimensionid+' => 'Dimensão aplicação', + 'Class:URP_ProfileProjection/Attribute:dimensionid+' => 'Dimensão de aplicação', 'Class:URP_ProfileProjection/Attribute:dimension' => 'Dimensão', - 'Class:URP_ProfileProjection/Attribute:dimension+' => 'Dimensão aplicação', + 'Class:URP_ProfileProjection/Attribute:dimension+' => 'Dimensão de aplicação', 'Class:URP_ProfileProjection/Attribute:profileid' => 'Perfil', 'Class:URP_ProfileProjection/Attribute:profileid+' => 'Perfil utilizado', 'Class:URP_ProfileProjection/Attribute:profile' => 'Perfil', - 'Class:URP_ProfileProjection/Attribute:profile+' => 'Nome perfil', - 'Class:URP_ProfileProjection/Attribute:value' => 'Valor de expressão', - 'Class:URP_ProfileProjection/Attribute:value+' => 'Expressão OQL (usando $ user) | constante | | + código de atributo', + 'Class:URP_ProfileProjection/Attribute:profile+' => 'Nome do perfil', + 'Class:URP_ProfileProjection/Attribute:value' => 'Valor da expressão', + 'Class:URP_ProfileProjection/Attribute:value+' => 'Expressão OQL (usando $user) | constante | | +código de atributo', 'Class:URP_ProfileProjection/Attribute:attribute' => 'Atributo', 'Class:URP_ProfileProjection/Attribute:attribute+' => 'Código de atributo alvo (opcional)', )); @@ -237,7 +237,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:URP_ClassProjection/Attribute:dimension' => 'Dimensão', 'Class:URP_ClassProjection/Attribute:dimension+' => 'Dimensão aplicação', 'Class:URP_ClassProjection/Attribute:class' => 'Classe', - 'Class:URP_ClassProjection/Attribute:class+' => 'Classe alvoTarget class', + 'Class:URP_ClassProjection/Attribute:class+' => 'Classe alvo', 'Class:URP_ClassProjection/Attribute:value' => 'Expressão de valor', 'Class:URP_ClassProjection/Attribute:value+' => 'Expressão OQL (usando $ user) | constante | | + código de atributo', 'Class:URP_ClassProjection/Attribute:attribute' => 'Atributo', @@ -260,11 +260,11 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:URP_ActionGrant/Attribute:permission' => 'Permissões', 'Class:URP_ActionGrant/Attribute:permission+' => 'Permitido ou não permitido?', 'Class:URP_ActionGrant/Attribute:permission/Value:yes' => 'Sim', - 'Class:URP_ActionGrant/Attribute:permission/Value:yes+' => 'Sim', + 'Class:URP_ActionGrant/Attribute:permission/Value:yes+' => '', 'Class:URP_ActionGrant/Attribute:permission/Value:no' => 'Não', - 'Class:URP_ActionGrant/Attribute:permission/Value:no+' => 'Não', + 'Class:URP_ActionGrant/Attribute:permission/Value:no+' => '', 'Class:URP_ActionGrant/Attribute:action' => 'Ação', - 'Class:URP_ActionGrant/Attribute:action+' => 'operações a realizar em determinada classe', + 'Class:URP_ActionGrant/Attribute:action+' => 'Operações a realizar em determinada classe', )); // @@ -273,7 +273,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:URP_StimulusGrant' => 'stimulus_permission', - 'Class:URP_StimulusGrant+' => 'permissões de Incentivo do ciclo de vida do objeto', + 'Class:URP_StimulusGrant+' => 'Permissões de estímulo do ciclo de vida do objeto', 'Class:URP_StimulusGrant/Attribute:profileid' => 'Perfil', 'Class:URP_StimulusGrant/Attribute:profileid+' => 'Perfil utilizado', 'Class:URP_StimulusGrant/Attribute:profile' => 'Perfil', @@ -283,11 +283,11 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:URP_StimulusGrant/Attribute:permission' => 'Permissão', 'Class:URP_StimulusGrant/Attribute:permission+' => 'Permitido ou não permitido?', 'Class:URP_StimulusGrant/Attribute:permission/Value:yes' => 'Sim', - 'Class:URP_StimulusGrant/Attribute:permission/Value:yes+' => 'Sim', + 'Class:URP_StimulusGrant/Attribute:permission/Value:yes+' => '', 'Class:URP_StimulusGrant/Attribute:permission/Value:no' => 'Não', - 'Class:URP_StimulusGrant/Attribute:permission/Value:no+' => 'Não', - 'Class:URP_StimulusGrant/Attribute:stimulus' => 'Incentivo', - 'Class:URP_StimulusGrant/Attribute:stimulus+' => 'Código incentivo', + 'Class:URP_StimulusGrant/Attribute:permission/Value:no+' => '', + 'Class:URP_StimulusGrant/Attribute:stimulus' => 'Estímulo', + 'Class:URP_StimulusGrant/Attribute:stimulus+' => 'Código do estímulo', )); // @@ -296,11 +296,11 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:URP_AttributeGrant' => 'attribute_permission', - 'Class:URP_AttributeGrant+' => 'Permissões no nível de atributos', - 'Class:URP_AttributeGrant/Attribute:actiongrantid' => 'Ação permissão', - 'Class:URP_AttributeGrant/Attribute:actiongrantid+' => 'Ação permissão', + 'Class:URP_AttributeGrant+' => 'Permissões a nível de atributos', + 'Class:URP_AttributeGrant/Attribute:actiongrantid' => 'Concessão de permissão', + 'Class:URP_AttributeGrant/Attribute:actiongrantid+' => 'Concessão de permissão', 'Class:URP_AttributeGrant/Attribute:attcode' => 'Atributo', - 'Class:URP_AttributeGrant/Attribute:attcode+' => 'Código atributo', + 'Class:URP_AttributeGrant/Attribute:attcode+' => 'Código do atributo', )); // @@ -311,7 +311,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Class:UserDashboard+' => '', 'Class:UserDashboard/Attribute:user_id' => 'Usuário', 'Class:UserDashboard/Attribute:user_id+' => '', - 'Class:UserDashboard/Attribute:menu_code' => 'Código de menu', + 'Class:UserDashboard/Attribute:menu_code' => 'Código do menu', 'Class:UserDashboard/Attribute:menu_code+' => '', 'Class:UserDashboard/Attribute:contents' => 'Conteúdo', 'Class:UserDashboard/Attribute:contents+' => '', @@ -335,70 +335,70 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'BooleanLabel:yes' => 'Sim', 'BooleanLabel:no' => 'Não', - 'UI:Login:Title' => ITOP_APPLICATION_SHORT.' login', - 'Menu:WelcomeMenu' => 'Bem-vindo ao '.ITOP_APPLICATION_SHORT, // Duplicated into itop-welcome-itil (will be removed from here...) + 'UI:Login:Title' => 'Login no '.ITOP_APPLICATION_SHORT, + 'Menu:WelcomeMenu' => 'Página inicial do '.ITOP_APPLICATION_SHORT, // Duplicated into itop-welcome-itil (will be removed from here...) 'Menu:WelcomeMenu+' => 'Bem-vindo ao '.ITOP_APPLICATION_SHORT, // Duplicated into itop-welcome-itil (will be removed from here...) 'Menu:WelcomeMenuPage' => 'Bem-vindo ao '.ITOP_APPLICATION_SHORT, // Duplicated into itop-welcome-itil (will be removed from here...) 'Menu:WelcomeMenuPage+' => 'Bem-vindo ao '.ITOP_APPLICATION_SHORT, // Duplicated into itop-welcome-itil (will be removed from here...) 'UI:WelcomeMenu:Title' => 'Bem-vindo ao '.ITOP_APPLICATION_SHORT, - 'UI:WelcomeMenu:LeftBlock' => '

    iTop é um completo, OpenSource, portal de operação IT.

    -
      Inclui: -
    • completo CMDB (Configuration management database) documentar e gerenciar inventários IT
    • -
    • módulo Gerenciador Incidentes para monitorar e comunicar sobre todas as questões que ocorrem na TI
    • -
    • módulo Gerenciador Mudanças/Alterações para planejar e monitorar mudanças/alterações na TI
    • -
    • base de dados com erros conhecidos para agilizar a solução de incidentes
    • -
    • módulo Problemas para documentar todas as interrupções e notificar os contatos adequados
    • -
    • painéis para obter rapidamente uma visão geral de TI
    • + 'UI:WelcomeMenu:LeftBlock' => '

      O '.ITOP_APPLICATION_SHORT.' é um Portal Operacional de TI de código aberto completo.

      +
        Ele inclui: +
      • Um CMDB (Configuration Management Database) completo para documentar e gerenciar o inventário de TI.
      • +
      • Um módulo de Gerenciamento de Incidentes para rastrear e comunicar todos os incidentes que ocorrem na TI.
      • +
      • Um módulo de Gerenciamento de Mudanças para planejar e acompanhar as mudanças no ambiente de TI.
      • +
      • Um banco de dados de Erros conhecidos para acelerar a solução de incidentes.
      • +
      • Um módulo de interrupção para documentar todas as interrupções planejadas e notificar os contatos apropriados.
      • +
      • Painéis para obter rapidamente uma visão geral de sua TI.
      -

      Todos os módulos podem ser configurados, passo a passo, cada um, independente dos outros.

      ', +

      Todos os módulos podem ser configurados, passo a passo, independentemente uns dos outros.

      ', - 'UI:WelcomeMenu:RightBlock' => '

      Um provedor de serviço, que permite gerenciar facilmente múltiplos clientes ou organizações. -

        Oferece um conjunto rico em recursos de processos de negócios que: -
      • melhora a eficácia da gestão de TI
      • -
      • melhora operações de TI
      • -
      • melhora a satisfação do cliente e fornece aos gestores uma visão sobre o desempenho dos negócios.
      • + 'UI:WelcomeMenu:RightBlock' => '

        O '.ITOP_APPLICATION_SHORT.' é orientado para o provedor de serviços, ele permite que os especialistas de TI gerenciem facilmente vários clientes ou organizações. +

          O '.ITOP_APPLICATION_SHORT.' oferece um conjunto rico em recursos de processos de negócios que: +
        • Melhora a eficácia do gerenciamento de TI
        • +
        • Impulsiona o desempenho das operações de TI
        • +
        • Melhora a satisfação do cliente e fornece aos executivos insights sobre o desempenho dos negócios.

        -

        É completamente aberto para ser integrado com as infra-estruturas de IT atuais.

        +

        O '.ITOP_APPLICATION_SHORT.' está totalmente aberto para ser integrado à sua infraestrutura de gerenciamento de TI atual.

        -

          Este portal em gestão de TI vai ajudar você a: -
        • gerenciar melhor o complexo ambiente de TI
        • -
        • implementar processos ITIL no seu próprio rítmo
        • -
        • gerenciar o ativo mais importante de sua TI: Documentação
        • +
            Adotar esta nova geração de portal operacional de TI ajudará você a: +
          • Gerenciar melhor um ambiente de TI cada vez mais complexo.
          • +
          • Implementar processos ITIL no seu próprio ritmo.
          • +
          • Gerenciar o ativo mais importante de sua TI: Documentação.

          ', - 'UI:WelcomeMenu:Text'=> '
          Congratulations, you landed on '.ITOP_APPLICATION.' '.ITOP_VERSION_NAME.'!
          + 'UI:WelcomeMenu:Text'=> '
          Parabéns, você desembarcou no '.ITOP_APPLICATION.' '.ITOP_VERSION_NAME.'!
          -
          This version features a brand new modern and accessible backoffice design.
          +
          Esta versão apresenta um novo design de backoffice moderno e acessível.
          -
          We kept '.ITOP_APPLICATION.' core functions that you liked and modernized them to make you love them. -We hope you’ll enjoy this version as much as we enjoyed imagining and creating it.
          +
          Nós mantivemos as funções principais do '.ITOP_APPLICATION.' que você gostou e as modernizou para fazer você amá-las. +Esperamos que você goste desta versão tanto quanto gostamos de imaginá-la e criá-la.
          -
          Customize your '.ITOP_APPLICATION.' preferences for a personalized experience.
          ~~', +
          Personalize as preferências de seu '.ITOP_APPLICATION.' para uma experiência personalizada.
          ', 'UI:WelcomeMenu:AllOpenRequests' => 'Solicitações abertas: %1$d', 'UI:WelcomeMenu:MyCalls' => 'Minhas solicitações', 'UI:WelcomeMenu:OpenIncidents' => 'Incidentes abertos: %1$d', 'UI:WelcomeMenu:AllConfigItems' => 'Itens de Configuração: %1$d', 'UI:WelcomeMenu:MyIncidents' => 'Incidentes atribuídos a mim', - 'UI:AllOrganizations' => ' Todas organizações ', + 'UI:AllOrganizations' => ' Todas as organizações ', 'UI:YourSearch' => 'Sua pesquisa', - 'UI:LoggedAsMessage' => 'Logado como %1$s (%2$s)~~', - 'UI:LoggedAsMessage+Admin' => 'Logado como %1$s (%2$s, Administrador)~~', + 'UI:LoggedAsMessage' => 'Autenticado como %1$s (%2$s)', + 'UI:LoggedAsMessage+Admin' => 'Autenticado como %1$s (%2$s, Administrador)', 'UI:Button:Logoff' => 'Sair', 'UI:Button:GlobalSearch' => 'Pesquisar', 'UI:Button:Search' => ' Pesquisar ', - 'UI:Button:Clear' => ' Clear ~~', - 'UI:Button:SearchInHierarchy' => 'Search in hierarchy~~', + 'UI:Button:Clear' => ' Limpar ', + 'UI:Button:SearchInHierarchy' => 'Pesquisar na hierarquia', 'UI:Button:Query' => ' Consultar ', 'UI:Button:Ok' => 'Ok', 'UI:Button:Save' => 'Salvar', - 'UI:Button:SaveAnd' => 'Save and %1$s~~', + 'UI:Button:SaveAnd' => 'Salvar e %1$s', 'UI:Button:Cancel' => 'Cancelar', 'UI:Button:Close' => 'Fechar', - 'UI:Button:Apply' => 'Aplicar', - 'UI:Button:Send' => 'Send~~', - 'UI:Button:SendAnd' => 'Send and %1$s~~', + 'UI:Button:Apply' => 'Salvar', + 'UI:Button:Send' => 'Enviar', + 'UI:Button:SendAnd' => 'Enviar e %1$s', 'UI:Button:Back' => ' << Voltar ', 'UI:Button:Restart' => ' |<< Reiniciar ', 'UI:Button:Next' => ' Próximo >> ', @@ -423,70 +423,72 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Button:Insert' => 'Inserir', 'UI:Button:More' => 'Mais', 'UI:Button:Less' => 'Menos', - 'UI:Button:Wait' => 'Please wait while updating fields~~', - 'UI:Treeview:CollapseAll' => 'Collapse All~~', - 'UI:Treeview:ExpandAll' => 'Expand All~~', - 'UI:UserPref:DoNotShowAgain' => 'Do not show again~~', - 'UI:InputFile:NoFileSelected' => 'No File Selected~~', - 'UI:InputFile:SelectFile' => 'Select a file~~', + 'UI:Button:Wait' => 'Por favor, aguarde enquanto atualiza os campos', + 'UI:Treeview:CollapseAll' => 'Recolher todos', + 'UI:Treeview:ExpandAll' => 'Expandir todos', + 'UI:UserPref:DoNotShowAgain' => 'Não exibir novamente', + 'UI:InputFile:NoFileSelected' => 'Nenhum arquivo selecionado', + 'UI:InputFile:SelectFile' => 'Selecione um arquivo', 'UI:SearchToggle' => 'Pesquisar', 'UI:ClickToCreateNew' => 'Criar um(a) %1$s', - 'UI:SearchFor_Class' => 'Pesquisar por objeto(s) %1$s', - 'UI:NoObjectToDisplay' => 'Nenhum objeto encontrado.', + 'UI:SearchFor_Class' => 'Pesquisar por objeto(s) de %1$s', + 'UI:NoObjectToDisplay' => 'Nenhum objeto encontrado', 'UI:Error:SaveFailed' => 'O objeto não pode ser salvo:', - 'UI:Error:MandatoryTemplateParameter_object_id' => 'Parâmetro Object_id é obrigatório quando link_attr é especificado. Verifique a definição do modelo de exibição.', - 'UI:Error:MandatoryTemplateParameter_target_attr' => 'Parâmetro Target_attr é obrigatório quando link_attr é especificado. Verifique a definição do modelo de exibição.', - 'UI:Error:MandatoryTemplateParameter_group_by' => 'Parâmetro Group_by é obrigatório. Verifique a definição do modelo de exibição.', - 'UI:Error:InvalidGroupByFields' => 'Lista inválida dos campos para agrupar por: "%1$s".', - 'UI:Error:UnsupportedStyleOfBlock' => 'Erro: o estilo não suportada do bloco: "%1$s".', + 'UI:Error:MandatoryTemplateParameter_object_id' => 'Parâmetro Object_id é obrigatório quando link_attr é especificado. Verifique a definição do modelo de exibição', + 'UI:Error:MandatoryTemplateParameter_target_attr' => 'Parâmetro Target_attr é obrigatório quando link_attr é especificado. Verifique a definição do modelo de exibição', + 'UI:Error:MandatoryTemplateParameter_group_by' => 'Parâmetro Group_by é obrigatório. Verifique a definição do modelo de exibição', + 'UI:Error:InvalidGroupByFields' => 'Lista inválida dos campos para agrupar por: "%1$s"', + 'UI:Error:UnsupportedStyleOfBlock' => 'Erro: o estilo não suportada do bloco: "%1$s"', 'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'Definição de ligação incorreta: a classe de objetos para gerenciar: %1$s não foi encontrado como uma chave externa na classe %2$s', - 'UI:Error:Object_Class_Id_NotFound' => 'Objeto: %1$s:%2$d não encontrado.', - 'UI:Error:WizardCircularReferenceInDependencies' => 'Erro: Referência circular nas dependências entre os campos, verifique o modelo de dados.', - 'UI:Error:UploadedFileTooBig' => 'O arquivo a ser carregado é muito grande. (Tamanho máximo permitido é de %1$s). Para modificar esse limite, contate o administrador do '.ITOP_APPLICATION_SHORT.'. (Verifique a configuração do PHP para upload_max_filesize e post_max_size no servidor).', - 'UI:Error:UploadedFileTruncated.' => 'Arquivo enviado tem sido truncado!', - 'UI:Error:NoTmpDir' => 'Diretório temporário não está definido.', - 'UI:Error:CannotWriteToTmp_Dir' => 'Não foi possível gravar o arquivo temporário para o disco. upload_tmp_dir = "%1$s".', - 'UI:Error:UploadStoppedByExtension_FileName' => 'Upload parou por extensão. (Nome do arquivo original = "%1$s").', - 'UI:Error:UploadFailedUnknownCause_Code' => 'Arquivo carregado falhou, causa desconhecida. (Código erro = "%1$s").', + 'UI:Error:Object_Class_Id_NotFound' => 'Objeto: %1$s:%2$d não encontrado', + 'UI:Error:WizardCircularReferenceInDependencies' => 'Erro: Referência circular nas dependências entre os campos, verifique o modelo de dados', + 'UI:Error:UploadedFileTooBig' => 'O arquivo a ser carregado é muito grande (Tamanho máximo permitido é de %1$s). Para modificar esse limite, contate o administrador do '.ITOP_APPLICATION_SHORT.' (Verifique a configuração do PHP para upload_max_filesize e post_max_size no servidor)', + 'UI:Error:UploadedFileTruncated.' => 'Arquivo enviado foi truncado!', + 'UI:Error:NoTmpDir' => 'Diretório temporário não está definido', + 'UI:Error:CannotWriteToTmp_Dir' => 'Não foi possível gravar o arquivo temporário para o disco. upload_tmp_dir = "%1$s"', + 'UI:Error:UploadStoppedByExtension_FileName' => 'Upload parou por extensão. (Nome do arquivo original = "%1$s")', + 'UI:Error:UploadFailedUnknownCause_Code' => 'Arquivo carregado falhou, causa desconhecida. (Código do erro = "%1$s")', - 'UI:Error:1ParametersMissing' => 'Erro: o parâmetro a seguir deve ser especificado para esta operação: %1$s.', - 'UI:Error:2ParametersMissing' => 'Erro: os seguintes parâmetros devem ser especificados para esta operação: %1$s e %2$s.', - 'UI:Error:3ParametersMissing' => 'Erro: os seguintes parâmetros devem ser especificados para esta operação: %1$s, %2$s e %3$s.', - 'UI:Error:4ParametersMissing' => 'Erro: os seguintes parâmetros devem ser especificados para esta operação: %1$s, %2$s, %3$s e %4$s.', - 'UI:Error:IncorrectOQLQuery_Message' => 'Erro: incorreta consulta OQL: %1$s', + 'UI:Error:1ParametersMissing' => 'Erro: o parâmetro a seguir deve ser especificado para esta operação: %1$s', + 'UI:Error:2ParametersMissing' => 'Erro: os seguintes parâmetros devem ser especificados para esta operação: %1$s e %2$s', + 'UI:Error:3ParametersMissing' => 'Erro: os seguintes parâmetros devem ser especificados para esta operação: %1$s, %2$s e %3$s', + 'UI:Error:4ParametersMissing' => 'Erro: os seguintes parâmetros devem ser especificados para esta operação: %1$s, %2$s, %3$s e %4$s', + 'UI:Error:IncorrectOQLQuery_Message' => 'Erro: consulta OQL incorreta: %1$s', 'UI:Error:AnErrorOccuredWhileRunningTheQuery_Message' => 'Ocorreu um erro ao executar a consulta: %1$s', - 'UI:Error:ObjectAlreadyUpdated' => 'Erro: o objeto já foi atualizado.', - 'UI:Error:ObjectCannotBeUpdated' => 'Erro: objecto não pode ser atualizado.', + 'UI:Error:ObjectAlreadyUpdated' => 'Erro: o objeto já foi atualizado', + 'UI:Error:ObjectCannotBeUpdated' => 'Erro: objeto não pode ser atualizado', 'UI:Error:ObjectsAlreadyDeleted' => 'Erro: objetos já foram apagados', 'UI:Error:BulkDeleteNotAllowedOn_Class' => 'Você não tem permissão de executar exclusão em massa dos objetos da classe %1$s', 'UI:Error:DeleteNotAllowedOn_Class' => 'Você não tem permissão para excluir objeto(s) da classe %1$s', - 'UI:Error:ReadNotAllowedOn_Class' => 'You are not allowed to view objects of class %1$s~~', + 'UI:Error:ReadNotAllowedOn_Class' => 'Você não tem permissão para ler objeto(s) da classe %1$s', 'UI:Error:BulkModifyNotAllowedOn_Class' => 'Você não tem permissão de executar atualização em massa dos objetos da classe %1$s', - 'UI:Error:ObjectAlreadyCloned' => 'Erro: o objeto já foi clonado.', - 'UI:Error:ObjectAlreadyCreated' => 'Erro: o objeto já foi criado.', - 'UI:Error:Invalid_Stimulus_On_Object_In_State' => 'Erro: invalid stimulus "%1$s" on object %2$s in state "%3$s".', + 'UI:Error:ObjectAlreadyCloned' => 'Erro: o objeto já foi clonado', + 'UI:Error:ObjectAlreadyCreated' => 'Erro: o objeto já foi criado', + 'UI:Error:Invalid_Stimulus_On_Object_In_State' => 'Erro: estímulo inválido "%1$s" no objeto %2$s com status "%3$s"', 'UI:Error:InvalidDashboardFile' => 'Erro: arquivo de painel inválido', 'UI:Error:InvalidDashboard' => 'Erro: painel inválido', 'UI:Error:MaintenanceMode' => 'A aplicação está em manutenção', 'UI:Error:MaintenanceTitle' => 'Manutenção', - 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', + 'UI:Error:InvalidToken' => 'Erro: A operação solicitada já foi executada (token CSRF não encontrado)', + + 'UI:Error:SMTP:UnknownVendor' => 'OAuth SMTP provider %1$s does not exist (email_transport_smtp.oauth.provider)~~', 'UI:GroupBy:Count' => 'Número', 'UI:GroupBy:Count+' => 'Número de elementos', - 'UI:CountOfObjects' => '%1$d objetos correspondem aos critérios.', - 'UI_CountOfObjectsShort' => '%1$d objetos.', - 'UI:NoObject_Class_ToDisplay' => 'Nenhum %1$s para mostrar', - 'UI:History:LastModified_On_By' => 'Última modificação em %1$s por %2$s.', + 'UI:CountOfObjects' => '%1$d objeto(s) correspondem aos critérios', + 'UI_CountOfObjectsShort' => '%1$d objeto(s)', + 'UI:NoObject_Class_ToDisplay' => 'Nenhum %1$s para exibir', + 'UI:History:LastModified_On_By' => 'Última modificação em %1$s por %2$s', 'UI:HistoryTab' => 'Histórico', - 'UI:NotificationsTab' => 'Notificação', + 'UI:NotificationsTab' => 'Notificações', 'UI:History:BulkImports' => 'Histórico', 'UI:History:BulkImports+' => 'Lista de importação CSV', 'UI:History:BulkImportDetails' => 'Alterações resultantes da importação CSV realizado em %1$s (por %2$s)', 'UI:History:Date' => 'Data', 'UI:History:Date+' => 'Data da alteração', 'UI:History:User' => 'Usuário', - 'UI:History:User+' => 'Usuário que fez a alteração', + 'UI:History:User+' => 'Usuário que realizou a alteração', 'UI:History:Changes' => 'Alteração', 'UI:History:Changes+' => 'Alteração feita no objeto', 'UI:History:StatsCreations' => 'Criado', @@ -498,27 +500,27 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Loading' => 'Carregando...', 'UI:Menu:Actions' => 'Ações', 'UI:Menu:OtherActions' => 'Outras ações', - 'UI:Menu:Transitions' => 'Transitions~~', - 'UI:Menu:OtherTransitions' => 'Other Transitions~~', + 'UI:Menu:Transitions' => 'Transições', + 'UI:Menu:OtherTransitions' => 'Outras Transições', 'UI:Menu:New' => 'Novo...', 'UI:Menu:Add' => 'Adicionar...', 'UI:Menu:Manage' => 'Gerenciar...', - 'UI:Menu:EMail' => 'eMail', - 'UI:Menu:CSVExport' => 'Exportar CSV...', - 'UI:Menu:Modify' => 'Modificar...', + 'UI:Menu:EMail' => 'Enviar via e-mail', + 'UI:Menu:CSVExport' => 'Exportar para CSV...', + 'UI:Menu:Modify' => 'Editar...', 'UI:Menu:Delete' => 'Excluir...', - 'UI:Menu:BulkDelete' => 'Excluir...', - 'UI:UndefinedObject' => 'indefinido', + 'UI:Menu:BulkDelete' => 'Exclução em massa...', + 'UI:UndefinedObject' => '(n/a)', 'UI:Document:OpenInNewWindow:Download' => 'Abrir em uma nova janela: %1$s, Download: %2$s', 'UI:SplitDateTime-Date' => 'data', 'UI:SplitDateTime-Time' => 'hora', - 'UI:TruncatedResults' => '%1$d objetos apresentado fora do %2$d', - 'UI:DisplayAll' => 'Mostrar todos', - 'UI:CollapseList' => 'Colapso', + 'UI:TruncatedResults' => '%1$d objeto(s) de %2$d', + 'UI:DisplayAll' => 'Exibir todos', + 'UI:CollapseList' => 'Recolher lista', 'UI:CountOfResults' => '%1$d objeto(s)', - 'UI:ChangesLogTitle' => 'Alteração log (%1$d):', - 'UI:EmptyChangesLogTitle' => 'Alteração log está limpo', - 'UI:SearchFor_Class_Objects' => 'Pesquisa para objeto %1$s ', + 'UI:ChangesLogTitle' => 'Log de alteração(ões) (%1$d):', + 'UI:EmptyChangesLogTitle' => 'Log de alteração(ões) está limpo', + 'UI:SearchFor_Class_Objects' => 'Pesquisa de objeto(s) de %1$s ', 'UI:OQLQueryBuilderTitle' => 'Construir consulta OQL', 'UI:OQLQueryTab' => 'Consulta OQL', 'UI:SimpleSearchTab' => 'Pesquisa simples', @@ -530,36 +532,36 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:SearchValue:UncheckAll' => 'Desmarcar todos', 'UI:SelectOne' => '-- selecione um --', 'UI:Login:Welcome' => 'Bem-vindo ao '.ITOP_APPLICATION_SHORT.'!', - 'UI:Login:IncorrectLoginPassword' => 'Usuário/senha incorreto, tente novamente.', + 'UI:Login:IncorrectLoginPassword' => 'Usuário e/ou senha inválido(s), tente novamente', 'UI:Login:IdentifyYourself' => 'Identifique-se antes continuar', 'UI:Login:UserNamePrompt' => 'Usuário', 'UI:Login:PasswordPrompt' => 'Senha', 'UI:Login:ForgotPwd' => 'Esqueceu sua senha?', 'UI:Login:ForgotPwdForm' => 'Esqueceu sua senha', - 'UI:Login:ForgotPwdForm+' => ITOP_APPLICATION_SHORT.' pode enviar um e-mail em que você vai encontrar instruções para seguir para redefinir sua conta.', + 'UI:Login:ForgotPwdForm+' => 'O '.ITOP_APPLICATION_SHORT.' pode enviar um e-mail em que você vai encontrar instruções para seguir para redefinir sua conta', 'UI:Login:ResetPassword' => 'Enviar agora', - 'UI:Login:ResetPwdFailed' => 'Falha para enviar email: %1$s', - 'UI:Login:SeparatorOr' => 'Or~~', + 'UI:Login:ResetPwdFailed' => 'Falha ao enviar e-mail: %1$s', + 'UI:Login:SeparatorOr' => 'Ou', 'UI:ResetPwd-Error-WrongLogin' => '\'%1$s\' não é um login válido', - 'UI:ResetPwd-Error-NotPossible' => 'conta externa não é permitida alteração de senha.', - 'UI:ResetPwd-Error-FixedPwd' => 'a conta não permite alterar senha.', - 'UI:ResetPwd-Error-NoContact' => 'a conta não está associada a uma pessoa.', - 'UI:ResetPwd-Error-NoEmailAtt' => 'a conta não está associada a uma pessoa que contenha um endereço de e-mail. Por favor, contate o administrador.', - 'UI:ResetPwd-Error-NoEmail' => 'faltando um endereço de e-mail. Por favor, contate o administrador.', - 'UI:ResetPwd-Error-Send' => 'email transport technical issue. Please Contact your administrator.', - 'UI:ResetPwd-EmailSent' => 'Please check your email box and follow the instructions. If you receive no email, please check the login you typed.~~', + 'UI:ResetPwd-Error-NotPossible' => 'Não é permitida alteração de senha de contas externas', + 'UI:ResetPwd-Error-FixedPwd' => 'A conta não permite alteração de senha', + 'UI:ResetPwd-Error-NoContact' => 'A conta não está associada a uma pessoa', + 'UI:ResetPwd-Error-NoEmailAtt' => 'A conta não está associada a uma pessoa que contém um endereço de e-mail no '.ITOP_APPLICATION_SHORT.'. Por favor, contate o administrador', + 'UI:ResetPwd-Error-NoEmail' => 'A conta não contém um endereço de e-mail. Por favor, contate o administrador', + 'UI:ResetPwd-Error-Send' => 'Houve um problema técnico de transporte de e-mail. Por favor, contate o administrador', + 'UI:ResetPwd-EmailSent' => 'Verifique sua caixa de e-mail e siga as instruções. Se você não receber nenhum e-mail, verifique a caixa de SPAM e o login que você digitou', 'UI:ResetPwd-EmailSubject' => 'Alterar a senha', 'UI:ResetPwd-EmailBody' => '

          Você solicitou a alteração da senha do '.ITOP_APPLICATION_SHORT.'.

          Por favor, siga este link (passo simples) para digitar a nova senha

          .', 'UI:ResetPwd-Title' => 'Alterar senha', - 'UI:ResetPwd-Error-InvalidToken' => 'Desculpe, a senha já foi alterada, ou deve ter recebido vários e-mails. Por favor, certifique-se que você use o link fornecido no último e-mail recebido.', - 'UI:ResetPwd-Error-EnterPassword' => 'Digite a nova senha para a conta \'%1$s\'.', - 'UI:ResetPwd-Ready' => 'A senha foi alterada com sucesso.', + 'UI:ResetPwd-Error-InvalidToken' => 'Desculpe, a senha já foi alterada, ou você deve ter recebido múltiplos e-mails. Por favor, certifique-se que você acessou o link fornecido no último e-mail recebido', + 'UI:ResetPwd-Error-EnterPassword' => 'Digite a nova senha para a conta \'%1$s\'', + 'UI:ResetPwd-Ready' => 'A senha foi alterada com sucesso', 'UI:ResetPwd-Login' => 'Clique para entrar...', 'UI:Login:About' => '', - 'UI:Login:ChangeYourPassword' => 'Altere sua senha', + 'UI:Login:ChangeYourPassword' => 'Alterar sua senha', 'UI:Login:OldPasswordPrompt' => 'Senha antiga', 'UI:Login:NewPasswordPrompt' => 'Nova senha', 'UI:Login:RetypeNewPasswordPrompt' => 'Repetir nova senha', @@ -570,46 +572,46 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:ChangePwdMenu' => 'Alterar senha...', 'UI:Login:PasswordChanged' => 'Senha alterada com sucesso', 'UI:AccessRO-All' => 'Somente-leitura', - 'UI:AccessRO-Users' => 'Somente leitura para usuário final', + 'UI:AccessRO-Users' => ITOP_APPLICATION.' é somente leitura para usuários finais', 'UI:ApplicationEnvironment' => 'Ambiente da aplicação: %1$s', - 'UI:Login:RetypePwdDoesNotMatch' => 'Nova senha e Repetir nova senha são diferentes. Tente novamente!', - 'UI:Button:Login' => 'Entrar '.ITOP_APPLICATION_SHORT, - 'UI:Login:Error:AccessRestricted' => 'Acesso restrito. Por favor, contacte o administrador.', - 'UI:Login:Error:AccessAdmin' => 'Acesso restrito somente para privilégios administrativo. Por favor, contacte o administrador.', - 'UI:Login:Error:WrongOrganizationName' => 'Unknown organization~~', - 'UI:Login:Error:MultipleContactsHaveSameEmail' => 'Multiple contacts have the same e-mail~~', - 'UI:Login:Error:NoValidProfiles' => 'No valid profile provided~~', + 'UI:Login:RetypePwdDoesNotMatch' => '"Nova senha" e "Repetir nova senha" são diferentes. Tente novamente!', + 'UI:Button:Login' => 'Login', + 'UI:Login:Error:AccessRestricted' => 'Acesso restrito. Por favor, contacte o administrador', + 'UI:Login:Error:AccessAdmin' => 'Acesso restrito somente para usuários com privilégios administrativos. Por favor, contacte o administrador', + 'UI:Login:Error:WrongOrganizationName' => 'Organização não encontrada', + 'UI:Login:Error:MultipleContactsHaveSameEmail' => 'Vários contatos têm o mesmo e-mail', + 'UI:Login:Error:NoValidProfiles' => 'Nenhum perfil válido fornecido', 'UI:CSVImport:MappingSelectOne' => '-- selecione um --', - 'UI:CSVImport:MappingNotApplicable' => '-- ignore este campo --', - 'UI:CSVImport:NoData' => 'Nenhum data configurado..., por favor providencie alguns dados!', + 'UI:CSVImport:MappingNotApplicable' => '-- ignorar este campo --', + 'UI:CSVImport:NoData' => 'Nenhum dado configurado. Por favor, providencie alguns dados!', 'UI:Title:DataPreview' => 'Visualizar dados', - 'UI:CSVImport:ErrorOnlyOneColumn' => 'Error: The data contains only one column. Did you select the appropriate separator character?', + 'UI:CSVImport:ErrorOnlyOneColumn' => 'Erro: Os dados contêm apenas uma coluna. Você selecionou o caractere separador apropriado?', 'UI:CSVImport:FieldName' => 'Campo %1$d', - 'UI:CSVImport:DataLine1' => 'Dados linha 1', - 'UI:CSVImport:DataLine2' => 'Dados linha 2', - 'UI:CSVImport:idField' => 'id (Chave primária)', - 'UI:Title:BulkImport' => 'Importar em massa', - 'UI:Title:BulkImport+' => 'CSV Ajuda Importação', + 'UI:CSVImport:DataLine1' => 'Dados da linha 1', + 'UI:CSVImport:DataLine2' => 'Dados da linha 2', + 'UI:CSVImport:idField' => 'ID (Chave primária)', + 'UI:Title:BulkImport' => 'Importação em massa', + 'UI:Title:BulkImport+' => 'Assistente de Importação CSV', 'UI:Title:BulkSynchro_nbItem_ofClass_class' => 'Sincronização de %1$d objetos da classe %2$s', 'UI:CSVImport:ClassesSelectOne' => '-- selecione um --', - 'UI:CSVImport:ErrorExtendedAttCode' => 'Erro interno: "%1$s" é um código incorreto porque "%2$s" não é uma chave externa da classe"%3$s"', - 'UI:CSVImport:ObjectsWillStayUnchanged' => '%1$d objetos permanecerão inalteradas.', - 'UI:CSVImport:ObjectsWillBeModified' => '%1$d objetos serão modificados.', - 'UI:CSVImport:ObjectsWillBeAdded' => '%1$d objetos serão adicionados.', - 'UI:CSVImport:ObjectsWillHaveErrors' => '%1$d objetos terão erros.', - 'UI:CSVImport:ObjectsRemainedUnchanged' => '%1$d objetos manteve-se inalteradas.', - 'UI:CSVImport:ObjectsWereModified' => '%1$d objetos foram modificados.', - 'UI:CSVImport:ObjectsWereAdded' => '%1$d objetos foram adicionados.', - 'UI:CSVImport:ObjectsHadErrors' => '%1$d objetos tinham erros.', - 'UI:Title:CSVImportStep2' => 'Passo 2 de 5: Opções dados CSV', + 'UI:CSVImport:ErrorExtendedAttCode' => 'Erro interno: "%1$s" é um código incorreto porque "%2$s" não é uma chave externa da classe "%3$s"', + 'UI:CSVImport:ObjectsWillStayUnchanged' => '%1$d objetos permanecerão inalterados', + 'UI:CSVImport:ObjectsWillBeModified' => '%1$d objetos serão modificados', + 'UI:CSVImport:ObjectsWillBeAdded' => '%1$d objetos serão adicionados', + 'UI:CSVImport:ObjectsWillHaveErrors' => '%1$d objetos terão erros', + 'UI:CSVImport:ObjectsRemainedUnchanged' => '%1$d objetos manteve-se inalterados', + 'UI:CSVImport:ObjectsWereModified' => '%1$d objetos foram modificados', + 'UI:CSVImport:ObjectsWereAdded' => '%1$d objetos foram adicionados', + 'UI:CSVImport:ObjectsHadErrors' => '%1$d objetos apresentaram erros', + 'UI:Title:CSVImportStep2' => 'Passo 2 de 5: Opções de importação CSV', 'UI:Title:CSVImportStep3' => 'Passo 3 de 5: Mapeamento de dados', - 'UI:Title:CSVImportStep4' => 'Passo 4 de 5: Simulação Importação', - 'UI:Title:CSVImportStep5' => 'Passo 5 de 5: Importação completada', + 'UI:Title:CSVImportStep4' => 'Passo 4 de 5: Simulação da importação', + 'UI:Title:CSVImportStep5' => 'Passo 5 de 5: Importação concluída', 'UI:CSVImport:LinesNotImported' => 'Linhas que não podem ser carregadas:', 'UI:CSVImport:LinesNotImported+' => 'As linhas a seguir não foram importadas, porque elas contêm erros', 'UI:CSVImport:SeparatorComma+' => ', (vírgula)', 'UI:CSVImport:SeparatorSemicolon+' => '; (ponto e vírgula)', - 'UI:CSVImport:SeparatorTab+' => 'tab', + 'UI:CSVImport:SeparatorTab+' => ' (tabulação)', 'UI:CSVImport:SeparatorOther' => 'outro:', 'UI:CSVImport:QualifierDoubleQuote+' => '" (aspas duplas)', 'UI:CSVImport:QualifierSimpleQuote+' => '\' (aspas simples)', @@ -618,119 +620,119 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:CSVImport:Skip_N_LinesAtTheBeginning' => 'Pular %1$s linha(s) no início do arquivo', 'UI:CSVImport:CSVDataPreview' => 'Visualizar dados CSV', 'UI:CSVImport:SelectFile' => 'Selecione o arquivo a importar:', - 'UI:CSVImport:Tab:LoadFromFile' => 'Carregar por um arquivo', + 'UI:CSVImport:Tab:LoadFromFile' => 'Carregar de um arquivo', 'UI:CSVImport:Tab:CopyPaste' => 'Copiar e colar dados', - 'UI:CSVImport:Tab:Templates' => 'Modelos', - 'UI:CSVImport:PasteData' => 'Colar os dados para importar:', - 'UI:CSVImport:PickClassForTemplate' => 'Escolha o modelo para baixar: ', - 'UI:CSVImport:SeparatorCharacter' => 'Caracter separador:', - 'UI:CSVImport:TextQualifierCharacter' => 'Caracter qualificador de texto', + 'UI:CSVImport:Tab:Templates' => 'Modelos de CSV', + 'UI:CSVImport:PasteData' => 'Cole os dados para importar:', + 'UI:CSVImport:PickClassForTemplate' => 'Escolha o modelo CSV para baixar: ', + 'UI:CSVImport:SeparatorCharacter' => 'Caracter separador de texto:', + 'UI:CSVImport:TextQualifierCharacter' => 'Caracter qualificador de texto:', 'UI:CSVImport:CommentsAndHeader' => 'Comentários e cabeçalho', 'UI:CSVImport:SelectClass' => 'Selecione a classe para importar:', 'UI:CSVImport:AdvancedMode' => 'Modo avançado', - 'UI:CSVImport:AdvancedMode+' => 'No modo avançado o "id" (chave primária) dos objetos pode ser usado para atualizar e renomear objetos.No entanto, a coluna "id" (se houver) só pode ser usado como um critério de pesquisa e não pode ser combinado com qualquer outro critério de busca.', - 'UI:CSVImport:SelectAClassFirst' => 'Para configurar o mapeamento, selecione uma classe primeira.', + 'UI:CSVImport:AdvancedMode+' => 'No modo avançado o "ID" (chave primária) dos objetos pode ser usado para atualizar e renomear objetos. No entanto, a coluna "ID" (se houver) só pode ser usado como um critério de pesquisa e não pode ser combinado com qualquer outro critério de busca', + 'UI:CSVImport:SelectAClassFirst' => 'Para configurar o mapeamento, selecione uma classe primeiro', 'UI:CSVImport:HeaderFields' => 'Campos', 'UI:CSVImport:HeaderMappings' => 'Mapeamentos', 'UI:CSVImport:HeaderSearch' => 'Pesquisar?', - 'UI:CSVImport:AlertIncompleteMapping' => 'Por favor, selecione um mapeamento para cada campo.', - 'UI:CSVImport:AlertMultipleMapping' => 'Por favor, certifique-se que um campo de destino é mapeado apenas uma vez.', + 'UI:CSVImport:AlertIncompleteMapping' => 'Por favor, selecione um mapeamento para cada campo', + 'UI:CSVImport:AlertMultipleMapping' => 'Por favor, certifique-se que um campo de destino é mapeado apenas uma vez', 'UI:CSVImport:AlertNoSearchCriteria' => 'Selecione ao menos um critério de busca', - 'UI:CSVImport:Encoding' => 'Codificação de caracteres', + 'UI:CSVImport:Encoding' => 'Codificação de caracteres:', 'UI:UniversalSearchTitle' => 'Pesquisa Universal', 'UI:UniversalSearch:Error' => 'Erro: %1$s', 'UI:UniversalSearch:LabelSelectTheClass' => 'Selecione a classe para pesquisar: ', 'UI:CSVReport-Value-Modified' => 'Modificado', - 'UI:CSVReport-Value-SetIssue' => 'Não pode ser modificado - razão: %1$s', - 'UI:CSVReport-Value-ChangeIssue' => 'Não pode ser modificado para %1$s - razão: %2$s', - 'UI:CSVReport-Value-NoMatch' => 'Não combina', + 'UI:CSVReport-Value-SetIssue' => 'Não pode ser modificado - motivo: %1$s', + 'UI:CSVReport-Value-ChangeIssue' => 'Não pode ser modificado para %1$s - motivo: %2$s', + 'UI:CSVReport-Value-NoMatch' => 'Não corresponde', 'UI:CSVReport-Value-Missing' => 'Faltando valor obrigatório', - 'UI:CSVReport-Value-Ambiguous' => 'Ambiguous: found %1$s objects', - 'UI:CSVReport-Row-Unchanged' => 'unchanged', + 'UI:CSVReport-Value-Ambiguous' => 'Ambíguo: encontrado %1$s objeto(s)', + 'UI:CSVReport-Row-Unchanged' => 'inalterado', 'UI:CSVReport-Row-Created' => 'criado', - 'UI:CSVReport-Row-Updated' => 'atualizado colunas %1$d', - 'UI:CSVReport-Row-Disappeared' => 'disappeared, changed %1$d cols', - 'UI:CSVReport-Row-Issue' => 'Issue: %1$s', + 'UI:CSVReport-Row-Updated' => 'atualizado %1$d colunas', + 'UI:CSVReport-Row-Disappeared' => 'desapareceu, alterado %1$d coluna(s)', + 'UI:CSVReport-Row-Issue' => 'Problema: %1$s', 'UI:CSVReport-Value-Issue-Null' => 'Nulo não permitido', 'UI:CSVReport-Value-Issue-NotFound' => 'Objeto não encontrado', 'UI:CSVReport-Value-Issue-FoundMany' => 'Encontrado %1$d combinações', - 'UI:CSVReport-Value-Issue-Readonly' => 'The attribute \'%1$s\' is read-only and cannot be modified (current value: %2$s, proposed value: %3$s)', - 'UI:CSVReport-Value-Issue-Format' => 'Failed to process input: %1$s', - 'UI:CSVReport-Value-Issue-NoMatch' => 'Unexpected value for attribute \'%1$s\': no match found, check spelling', - 'UI:CSVReport-Value-Issue-Unknown' => 'Unexpected value for attribute \'%1$s\': %2$s', - 'UI:CSVReport-Row-Issue-Inconsistent' => 'Attributes not consistent with each others: %1$s', - 'UI:CSVReport-Row-Issue-Attribute' => 'Unexpected attribute value(s)', - 'UI:CSVReport-Row-Issue-MissingExtKey' => 'Could not be created, due to missing external key(s): %1$s', - 'UI:CSVReport-Row-Issue-DateFormat' => 'wrong date format', - 'UI:CSVReport-Row-Issue-Reconciliation' => 'failed to reconcile', - 'UI:CSVReport-Row-Issue-Ambiguous' => 'ambiguous reconciliation', - 'UI:CSVReport-Row-Issue-Internal' => 'Internal error: %1$s, %2$s', + 'UI:CSVReport-Value-Issue-Readonly' => 'O atributo \'%1$s\' é somente-leitura e não pode ser modificado (valor atual: %2$s, valor proposto: %3$s)', + 'UI:CSVReport-Value-Issue-Format' => 'Falha ao processar a entrada: %1$s', + 'UI:CSVReport-Value-Issue-NoMatch' => 'Valor inesperado para o atributo \'%1$s\': nenhuma correspondência encontrada, verifique a ortografia', + 'UI:CSVReport-Value-Issue-Unknown' => 'Valor inesperado para o atributo \'%1$s\': %2$s', + 'UI:CSVReport-Row-Issue-Inconsistent' => 'Atributos não consistentes uns com os outros: %1$s', + 'UI:CSVReport-Row-Issue-Attribute' => 'Valor(es) de atributo inesperado(s)', + 'UI:CSVReport-Row-Issue-MissingExtKey' => 'Não foi possível criar devido à(s) chave(s) externa(s) ausente(s): %1$s', + 'UI:CSVReport-Row-Issue-DateFormat' => 'formato de data inválido', + 'UI:CSVReport-Row-Issue-Reconciliation' => 'não conseguiu reconciliar', + 'UI:CSVReport-Row-Issue-Ambiguous' => 'reconciliação ambígua', + 'UI:CSVReport-Row-Issue-Internal' => 'Erro interno: %1$s, %2$s', 'UI:CSVReport-Icon-Unchanged' => 'Não modificado', 'UI:CSVReport-Icon-Modified' => 'Modificado', - 'UI:CSVReport-Icon-Missing' => 'Missing', - 'UI:CSVReport-Object-MissingToUpdate' => 'Missing object: will be updated', - 'UI:CSVReport-Object-MissingUpdated' => 'Missing object: updated', + 'UI:CSVReport-Icon-Missing' => 'Ausente', + 'UI:CSVReport-Object-MissingToUpdate' => 'Objeto ausente: será atualizado', + 'UI:CSVReport-Object-MissingUpdated' => 'Objeto ausente: atualizado', 'UI:CSVReport-Icon-Created' => 'Criado', 'UI:CSVReport-Object-ToCreate' => 'Objeto acaba ser criado', 'UI:CSVReport-Object-Created' => 'Objeto criado', 'UI:CSVReport-Icon-Error' => 'Erro', 'UI:CSVReport-Object-Error' => 'ERRO: %1$s', - 'UI:CSVReport-Object-Ambiguous' => 'AMBIGUOUS: %1$s', - 'UI:CSVReport-Stats-Errors' => '%1$.0f %% of the loaded objects have errors and will be ignored.', - 'UI:CSVReport-Stats-Created' => '%1$.0f %% of the loaded objects will be created.', - 'UI:CSVReport-Stats-Modified' => '%1$.0f %% of the loaded objects will be modified.', + 'UI:CSVReport-Object-Ambiguous' => 'AMBÍGUO: %1$s', + 'UI:CSVReport-Stats-Errors' => '%1$.0f %% dos objetos carregados têm erros e serão ignorados', + 'UI:CSVReport-Stats-Created' => '%1$.0f %% dos objetos carregados serão criados', + 'UI:CSVReport-Stats-Modified' => '%1$.0f %% dos objetos carregados serão modificados', 'UI:CSVExport:AdvancedMode' => 'Modo avançado', - 'UI:CSVExport:AdvancedMode+' => 'In advanced mode, several columns are added to the export: the id of the object, the id of external keys and their reconciliation attributes.', - 'UI:CSVExport:LostChars' => 'Encoding issue', - 'UI:CSVExport:LostChars+' => 'The downloaded file will be encoded into %1$s. iTop has detected some characters that are not compatible with this format. Those characters will either be replaced by a substitute (e.g. accentuated chars losing the accent), or they will be discarded. You can copy/paste the data from your web browser. Alternatively, you can contact your administrator to change the encoding (See parameter \'csv_file_default_charset\').', + 'UI:CSVExport:AdvancedMode+' => 'No modo avançado, várias colunas são adicionadas à exportação: o ID do objeto, o ID das chaves externas e seus atributos de reconciliação', + 'UI:CSVExport:LostChars' => 'Problema de codificação', + 'UI:CSVExport:LostChars+' => 'O arquivo baixado será codificado em %1$s. O '.ITOP_APPLICATION_SHORT.' detectou alguns caracteres que não são compatíveis com este formato. Esses caracteres serão substituídos por um substituto (por exemplo, caracteres acentuados perdendo o acento) ou serão descartados. Você pode copiar/colar os dados do seu navegador da web. Como alternativa, você pode entrar em contato com seu administrador para alterar a codificação (consulte o parâmetro \'csv_file_default_charset\' do arquivo de configuração do '.ITOP_APPLICATION_SHORT.')', - 'UI:Audit:Title' => 'CMDB Auditoria', + 'UI:Audit:Title' => 'Auditoria do CMDB', 'UI:Audit:InteractiveAudit' => 'Auditoria Interativa', - 'UI:Audit:HeaderAuditRule' => 'Regra de auditoria', + 'UI:Audit:HeaderAuditRule' => 'Regra de Auditoria', 'UI:Audit:HeaderNbObjects' => '# Objetos', 'UI:Audit:HeaderNbErrors' => '# Erros', 'UI:Audit:PercentageOk' => '% Ok', - 'UI:Audit:OqlError' => 'OQL Error~~', - 'UI:Audit:Error:ValueNA' => 'n/a~~', - 'UI:Audit:ErrorIn_Rule' => 'Error in Rule~~', - 'UI:Audit:ErrorIn_Rule_Reason' => 'OQL erro na regra %1$s: %2$s.', - 'UI:Audit:ErrorIn_Category' => 'Error in Category~~', - 'UI:Audit:ErrorIn_Category_Reason' => 'OQL erro na categoria %1$s: %2$s.', - 'UI:Audit:AuditErrors' => 'Audit Errors~~', - 'UI:Audit:Dashboard:ObjectsAudited' => 'Objects audited~~', - 'UI:Audit:Dashboard:ObjectsInError' => 'Objects in errors~~', - 'UI:Audit:Dashboard:ObjectsValidated' => 'Objects validated~~', - 'UI:Audit:AuditCategory:Subtitle' => '%1$s errors ouf of %2$s - %3$s%%~~', + 'UI:Audit:OqlError' => 'Erro OQL', + 'UI:Audit:Error:ValueNA' => 'n/a', + 'UI:Audit:ErrorIn_Rule' => 'Erro na Regra', + 'UI:Audit:ErrorIn_Rule_Reason' => 'Erro OQL na Regra %1$s: %2$s', + 'UI:Audit:ErrorIn_Category' => 'Erro na Categoria', + 'UI:Audit:ErrorIn_Category_Reason' => 'Erro OQL na Categoria %1$s: %2$s', + 'UI:Audit:AuditErrors' => 'Erros de auditoria', + 'UI:Audit:Dashboard:ObjectsAudited' => 'Objetos auditados', + 'UI:Audit:Dashboard:ObjectsInError' => 'Objetos com erros', + 'UI:Audit:Dashboard:ObjectsValidated' => 'Objetos validados', + 'UI:Audit:AuditCategory:Subtitle' => '%1$s erros de %2$s - %3$s%%', 'UI:RunQuery:Title' => 'Avaliar consultas OQL', 'UI:RunQuery:QueryExamples' => 'Exemplos de consultas', - 'UI:RunQuery:QueryResults' => 'Query Results~~', + 'UI:RunQuery:QueryResults' => 'Resultado da consulta', 'UI:RunQuery:HeaderPurpose' => 'Propósito', 'UI:RunQuery:HeaderPurpose+' => 'Explicação sobre a consulta', 'UI:RunQuery:HeaderOQLExpression' => 'A consulta na sintaxe OQL', 'UI:RunQuery:HeaderOQLExpression+' => 'A consulta na sintaxe OQL', 'UI:RunQuery:ExpressionToEvaluate' => 'Expressão para avaliar: ', - 'UI:RunQuery:QueryArguments' => 'Query Arguments~~', + 'UI:RunQuery:QueryArguments' => 'Argumentos da consulta', 'UI:RunQuery:MoreInfo' => 'Mais informações sobre a consulta: ', 'UI:RunQuery:DevelopedQuery' => 'Redevelopped query expression: ', 'UI:RunQuery:SerializedFilter' => 'Filtro serializado: ', 'UI:RunQuery:DevelopedOQL' => 'Developed OQL~~', 'UI:RunQuery:DevelopedOQLCount' => 'Developed OQL for count~~', - 'UI:RunQuery:ResultSQLCount' => 'Resulting SQL for count~~', - 'UI:RunQuery:ResultSQL' => 'Resulting SQL~~', + 'UI:RunQuery:ResultSQLCount' => 'Contagem de SQL Resultante', + 'UI:RunQuery:ResultSQL' => 'SQL Resultante', 'UI:RunQuery:Error' => 'Ocorreu um erro ao executar a consulta', 'UI:Query:UrlForExcel' => 'URL a ser usada para consultas web MS-Excel', - 'UI:Query:UrlV1' => 'A lista de campos não foi especificada. A página export-V2.php não pode ser chamada sem essa informação. Portanto, o URL sugerido abaixo aponta para a página herdada: export.php. Essa versão herdada da exportação tem a seguinte limitação: a lista de campos exportados pode variar dependendo do formato de saída e do modelo de dados do '.ITOP_APPLICATION_SHORT.'. Se você quiser garantir que a lista de colunas exportadas permaneça estável a longo prazo, então você deve especificar um valor para o atributo "Fields" e usar a página export-V2.php.', + 'UI:Query:UrlV1' => 'A lista de campos não foi especificada. A página export-V2.php não pode ser chamada sem essa informação. Portanto, o URL sugerido abaixo aponta para a página herdada: export.php. Essa versão herdada da exportação tem a seguinte limitação: a lista de campos exportados pode variar dependendo do formato de saída e do modelo de dados do '.ITOP_APPLICATION_SHORT.'. Se você quiser garantir que a lista de colunas exportadas permaneça estável a longo prazo, então você deve especificar um valor para o atributo "Fields" e usar a página export-V2.php', 'UI:Schema:Title' => 'Esquema de objetos', - 'UI:Schema:TitleForClass' => 'Esquema de %1$s~~', + 'UI:Schema:TitleForClass' => 'Esquema de %1$s', 'UI:Schema:CategoryMenuItem' => 'Categoria %1$s', 'UI:Schema:Relationships' => 'Relações', - 'UI:Schema:AbstractClass' => 'Classe abstrata: nenhum objeto desta classe pode ser instanciada.', - 'UI:Schema:NonAbstractClass' => 'Classe não-abstrata: os objetos desta classe pode ser instanciada.', + 'UI:Schema:AbstractClass' => 'Classe abstrata: nenhum objeto desta classe pode ser instanciado', + 'UI:Schema:NonAbstractClass' => 'Classe não-abstrata: os objetos desta classe pode ser instanciado', 'UI:Schema:ClassHierarchyTitle' => 'Hierarquia de classes', 'UI:Schema:AllClasses' => 'Todas classes', 'UI:Schema:ExternalKey_To' => 'Chave externa para %1$s', @@ -739,23 +741,23 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Schema:NullAllowed' => 'Nulo permitido', 'UI:Schema:NullNotAllowed' => 'Nulo não permitido', 'UI:Schema:Attributes' => 'Atributos', - 'UI:Schema:AttributeCode' => 'Código atributo', + 'UI:Schema:AttributeCode' => 'Código do atributo', 'UI:Schema:AttributeCode+' => 'Código interno do atributo', 'UI:Schema:Label' => 'Rótulo', 'UI:Schema:Label+' => 'Rótulo do atributo', 'UI:Schema:Type' => 'Tipo', - 'UI:Schema:Type+' => 'Tipo dado do atributo', + 'UI:Schema:Type+' => 'Tipo de dado do atributo', 'UI:Schema:Origin' => 'Origem', - 'UI:Schema:Origin+' => 'The base class in which this attribute is defined', + 'UI:Schema:Origin+' => 'A classe base na qual este atributo é definido', 'UI:Schema:Description' => 'Descrição', 'UI:Schema:Description+' => 'Descrição do atributo', - 'UI:Schema:AllowedValues' => 'Permitido valores', + 'UI:Schema:AllowedValues' => 'Valores permitidos', 'UI:Schema:AllowedValues+' => 'Restrições sobre os valores possíveis para este atributo', 'UI:Schema:MoreInfo' => 'Mais informações', 'UI:Schema:MoreInfo+' => 'Mais informações sobre o campo definido no banco de dados', 'UI:Schema:SearchCriteria' => 'Search criteria', - 'UI:Schema:FilterCode' => 'Código filtro', + 'UI:Schema:FilterCode' => 'Código de filtro', 'UI:Schema:FilterCode+' => 'Código deste critério de pesquisa', 'UI:Schema:FilterDescription' => 'Descrição', 'UI:Schema:FilterDescription+' => 'Descrição do critério de pesquisa', @@ -776,47 +778,47 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Schema:Links:1-n' => 'Classes apontando para %1$s (1:n links):', 'UI:Schema:Links:n-n' => 'Classes apontando para %1$s (n:n links):', 'UI:Schema:Links:All' => 'Gráfico de todas as classes relacionadas', - 'UI:Schema:NoLifeCyle' => 'Não há ciclo de vida definido para esta classe.', + 'UI:Schema:NoLifeCyle' => 'Não há ciclo de vida definido para esta classe', 'UI:Schema:LifeCycleTransitions' => 'Transições', 'UI:Schema:LifeCyleAttributeOptions' => 'Opções de atributo', 'UI:Schema:LifeCycleHiddenAttribute' => 'Oculto', - 'UI:Schema:LifeCycleReadOnlyAttribute' => 'Somente leitura', + 'UI:Schema:LifeCycleReadOnlyAttribute' => 'Somente-leitura', 'UI:Schema:LifeCycleMandatoryAttribute' => 'Obrigatório', - 'UI:Schema:LifeCycleAttributeMustChange' => 'Tem que mudar', - 'UI:Schema:LifeCycleAttributeMustPrompt' => 'Usuário será solicitado para alterar o valor', + 'UI:Schema:LifeCycleAttributeMustChange' => 'Deve alterar', + 'UI:Schema:LifeCycleAttributeMustPrompt' => 'Usuário será solicitado a alterar o valor', 'UI:Schema:LifeCycleEmptyList' => 'Lista vazia', 'UI:Schema:ClassFilter' => 'Classe:', 'UI:Schema:DisplayLabel' => 'Exibir:', - 'UI:Schema:DisplaySelector/LabelAndCode' => 'Etiqueta e código', + 'UI:Schema:DisplaySelector/LabelAndCode' => 'Rótulo e código', 'UI:Schema:DisplaySelector/Label' => 'Rótulo', 'UI:Schema:DisplaySelector/Code' => 'Código', 'UI:Schema:Attribute/Filter' => 'Filtro', 'UI:Schema:DefaultNullValue' => 'Padrão nulo : "%1$s"', - 'UI:LinksWidget:Autocomplete+' => 'Tipo os 3 primeiro caracteres...', - 'UI:Edit:SearchQuery' => 'Select a predefined query~~', + 'UI:LinksWidget:Autocomplete+' => 'Digite os três caracteres iniciais...', + 'UI:Edit:SearchQuery' => 'Selecionar uma consulta pré-definida', 'UI:Edit:TestQuery' => 'Testar consulta', 'UI:Combo:SelectValue' => '--- selecione um valor ---', - 'UI:Label:SelectedObjects' => 'Selected objects: ', - 'UI:Label:AvailableObjects' => 'Available objects: ', + 'UI:Label:SelectedObjects' => 'Objetos selecionados: ', + 'UI:Label:AvailableObjects' => 'Objetos disponíveis: ', 'UI:Link_Class_Attributes' => '%1$s atributos', 'UI:SelectAllToggle+' => 'Marcar todas / Desmarcar todas', - 'UI:AddObjectsOf_Class_LinkedWith_Class_Instance' => 'Adicionar %1$s objetos vinculados com %2$s: %3$s', - 'UI:AddObjectsOf_Class_LinkedWith_Class' => 'Adicionar %1$s objetos vinculados com o %2$s', - 'UI:ManageObjectsOf_Class_LinkedWith_Class_Instance' => 'Gerenciar %1$s objetos vinculados com %2$s: %3$s', - 'UI:AddLinkedObjectsOf_Class' => 'Adicionar %1$s...', - 'UI:RemoveLinkedObjectsOf_Class' => 'Excluir objetos selecionados', - 'UI:Message:EmptyList:UseAdd' => 'A lista está vazia, use o botão "Adicionar..." para adicionar elementos.', - 'UI:Message:EmptyList:UseSearchForm' => 'Use o formulário de busca acima para procurar objetos a ser adicionado.', + 'UI:AddObjectsOf_Class_LinkedWith_Class_Instance' => 'Associar objetos de %1$s com %2$s: %3$s', + 'UI:AddObjectsOf_Class_LinkedWith_Class' => 'Associar objetos de %1$s com %2$s', + 'UI:ManageObjectsOf_Class_LinkedWith_Class_Instance' => 'Gerenciar vínculo de objetos de %1$s com %2$s: %3$s', + 'UI:AddLinkedObjectsOf_Class' => 'Associar %1$s...', + 'UI:RemoveLinkedObjectsOf_Class' => 'Desassociar objeto(s) selecionado(s)', + 'UI:Message:EmptyList:UseAdd' => 'A lista está vazia, use o botão "Associar..." para adicionar elementos', + 'UI:Message:EmptyList:UseSearchForm' => 'Use o formulário de busca acima para procurar objeto(s) a ser(em) adicionado(s)', 'UI:Wizard:FinalStepTitle' => 'Passo final: confirmação', 'UI:Title:DeletionOf_Object' => 'Excluindo de %1$s', 'UI:Title:BulkDeletionOf_Count_ObjectsOf_Class' => 'Exclusão em massa de %1$d objetos da classe %2$s', - 'UI:Delete:NotAllowedToDelete' => 'Você não tem permissão para excluir este objeto.', + 'UI:Delete:NotAllowedToDelete' => 'Você não tem permissão para excluir este objeto', 'UI:Delete:NotAllowedToUpdate_Fields' => 'Você não tem permissão para atualizar o(s) seguinte(s) campo(s): %1$s', 'UI:Error:ActionNotAllowed' => 'Você não tem permissão para fazer essa ação', 'UI:Error:NotEnoughRightsToDelete' => 'Este objeto não pode ser apagado porque o usuário atual não tem direitos suficientes', 'UI:Error:CannotDeleteBecause' => 'Este objeto não pode ser excluído porque: %1$s', - 'UI:Error:CannotDeleteBecauseOfDepencies' => 'Este objeto não pode ser excluído porque algumas operações manuais devem ser realizadas antes de que', - 'UI:Error:CannotDeleteBecauseManualOpNeeded' => 'Este objeto não pode ser excluído porque algumas operações manuais devem ser realizadas antes de que', + 'UI:Error:CannotDeleteBecauseOfDepencies' => 'Este objeto não pode ser excluído porque algumas operações manuais devem ser realizadas antes', + 'UI:Error:CannotDeleteBecauseManualOpNeeded' => 'Este objeto não pode ser excluído porque algumas operações manuais devem ser realizadas antes', 'UI:Archive_User_OnBehalfOf_User' => '%1$s em nome de %2$s', 'UI:Delete:Deleted' => 'excluído', 'UI:Delete:AutomaticallyDeleted' => 'excluído automaticamente', @@ -824,146 +826,146 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Delete:CleaningUpRefencesTo_Object' => 'Limpeza de todas as referências a %1$s...', 'UI:Delete:CleaningUpRefencesTo_Several_ObjectsOf_Class' => 'Limpeza de todas as referências a %1$d objetos da classe %2$s...', 'UI:Delete:Done+' => 'O que foi feito...', - 'UI:Delete:_Name_Class_Deleted' => '%1$s - %2$s excluído.', - 'UI:Delete:ConfirmDeletionOf_Name' => 'Exclusão de %1$s', + 'UI:Delete:_Name_Class_Deleted' => '%1$s - %2$s excluído', + 'UI:Delete:ConfirmDeletionOf_Name' => 'Excluir "%1$s"', 'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => 'Exclusão de %1$d objetos da classe %2$s', 'UI:Delete:CannotDeleteBecause' => 'Não pode ser excluído: %1$s', - 'UI:Delete:ShouldBeDeletedAtomaticallyButNotPossible' => 'Deve ser excluído automaticamente, mas isso não é viável: %1$s', - 'UI:Delete:MustBeDeletedManuallyButNotPossible' => 'Devem ser excluído manualmente, mas isso não é viável: %1$s', + 'UI:Delete:ShouldBeDeletedAtomaticallyButNotPossible' => 'Deve ser excluído automaticamente, mas isso não é possível: %1$s', + 'UI:Delete:MustBeDeletedManuallyButNotPossible' => 'Devem ser excluído manualmente, mas isso não é possível: %1$s', 'UI:Delete:WillBeDeletedAutomatically' => 'Será automaticamente excluído', 'UI:Delete:MustBeDeletedManually' => 'Será manualmente excluído', - 'UI:Delete:CannotUpdateBecause_Issue' => 'Devem ser atualizados automaticamente, mas: %1$s', - 'UI:Delete:WillAutomaticallyUpdate_Fields' => 'será automaticamente atualizada (redefinir: %1$s)', - 'UI:Delete:Count_Objects/LinksReferencing_Object' => '%1$d objetos/links são referências %2$s', + 'UI:Delete:CannotUpdateBecause_Issue' => 'Devem ser atualizados automaticamente, porém: %1$s', + 'UI:Delete:WillAutomaticallyUpdate_Fields' => 'Será automaticamente atualizado (redefinir: %1$s)', + 'UI:Delete:Count_Objects/LinksReferencing_Object' => '%1$d objetos/links fazem referência a(o) "%2$s"', 'UI:Delete:Count_Objects/LinksReferencingTheObjects' => '%1$d objetos/links fazem referências a alguns dos objetos a serem excluídos', - 'UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity' => 'Para garantir a integridade do banco de dados, qualquer referência deve ser eliminada', + 'UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity' => 'Para garantir a integridade do banco de dados, todas as referências a este objeto devem ser eliminadas', 'UI:Delete:Consequence+' => 'O que será feito', - 'UI:Delete:SorryDeletionNotAllowed' => 'Por favor, realize as operações manuais listados acima antes de solicitar a exclusão do referido objeto', - 'UI:Delete:PleaseDoTheManualOperations' => 'Por favor, realize as operações manuais listados acima antes de solicitar a exclusão do referido objeto', - 'UI:Delect:Confirm_Object' => 'Por favor, confirme se você deseja excluir %1$s.', - 'UI:Delect:Confirm_Count_ObjectsOf_Class' => 'Por favor, confirme que você deseja excluir o seguinte %1$d objetos da classe %2$s.', + 'UI:Delete:SorryDeletionNotAllowed' => 'Por favor, realize as operações manuais listadas acima antes de solicitar a exclusão do referido objeto', + 'UI:Delete:PleaseDoTheManualOperations' => 'Por favor, realize as operações manuais listadas acima antes de solicitar a exclusão do referido objeto', + 'UI:Delect:Confirm_Object' => 'Por favor, confirme se você deseja excluir "%1$s"', + 'UI:Delect:Confirm_Count_ObjectsOf_Class' => 'Por favor, confirme que você deseja excluir o(s) seguinte(s) %1$d objeto(s) da classe "%2$s"', 'UI:WelcomeToITop' => 'Bem-vindo ao '.ITOP_APPLICATION_SHORT, - 'UI:DetailsPageTitle' => '%1$s - %2$s detalhes', + 'UI:DetailsPageTitle' => '%1$s - Detalhes do(a) %2$s', 'UI:ErrorPageTitle' => 'Erro', - 'UI:ObjectDoesNotExist' => 'Desculpe, este objeto não existe (ou você não tem permissão para vê-lo).', - 'UI:ObjectArchived' => 'Este objeto foi arquivado. Por favor habilite o modo de arquivamento ou entre em contato com o seu administrador.', + 'UI:ObjectDoesNotExist' => 'Desculpe, este objeto não existe (ou você não tem permissão para vê-lo)', + 'UI:ObjectArchived' => 'Este objeto foi arquivado. Por favor, habilite o modo de arquivamento ou entre em contato com o seu administrador', 'Tag:Archived' => 'Arquivado', - 'Tag:Archived+' => 'Pode ser acessado apenas no modo de arquivo', + 'Tag:Archived+' => 'Pode ser acessado apenas no modo de arquivamento', 'Tag:Obsolete' => 'Obsoleto', 'Tag:Obsolete+' => 'Excluído da análise de impacto e resultados de pesquisa', 'Tag:Synchronized' => 'Sincronizado', 'ObjectRef:Archived' => 'Arquivado', 'ObjectRef:Obsolete' => 'Obsoleto', - 'UI:SearchResultsPageTitle' => 'Resultado da pesquisa', - 'UI:SearchResultsTitle' => 'Resultado da pesquisa', - 'UI:SearchResultsTitle+' => 'Resultados de pesquisa de texto completo', - 'UI:Search:NoSearch' => 'Nada a pesquisar de', - 'UI:Search:NeedleTooShort' => 'A string de pesquisa \\"%1$s\\" é muito curta. Por favor digite pelo menos %2$d caracteres.', - 'UI:Search:Ongoing' => 'Procurando por \\"%1$s\\"', + 'UI:SearchResultsPageTitle' => 'Resultados da pesquisa', + 'UI:SearchResultsTitle' => 'Resultados da pesquisa', + 'UI:SearchResultsTitle+' => 'Resultados da pesquisa de texto completo', + 'UI:Search:NoSearch' => 'Nada a pesquisar', + 'UI:Search:NeedleTooShort' => 'A string de pesquisa \"%1$s\" é muito curta. Por favor, digite pelo menos %2$d caracteres.', + 'UI:Search:Ongoing' => 'Procurando por \"%1$s\"', 'UI:Search:Enlarge' => 'Amplie a pesquisa', - 'UI:FullTextSearchTitle_Text' => 'Resultado para "%1$s":', + 'UI:FullTextSearchTitle_Text' => 'Resultados da pesquisa para "%1$s":', 'UI:Search:Count_ObjectsOf_Class_Found' => '%1$d objeto(s) da classe %2$s encontrado(s).', - 'UI:Search:NoObjectFound' => 'Nenhum objeto encontrado.', - 'UI:ModificationPageTitle_Object_Class' => '%1$s - %2$s modificados', - 'UI:ModificationTitle_Class_Object' => 'Modificação de %1$s: %2$s', - 'UI:ClonePageTitle_Object_Class' => 'Clone %1$s - %2$s modificação', - 'UI:CloneTitle_Class_Object' => 'Clone de %1$s: %2$s', - 'UI:CreationPageTitle_Class' => 'Criação de um(a) novo(a) %1$s ', - 'UI:CreationTitle_Class' => 'Criação de um(a) novo(a) %1$s', + 'UI:Search:NoObjectFound' => 'Nenhum objeto encontrado', + 'UI:ModificationPageTitle_Object_Class' => '%1$s - Modificação de(a) %2$s', + 'UI:ModificationTitle_Class_Object' => 'Modificação de(a) %1$s: %2$s', + 'UI:ClonePageTitle_Object_Class' => 'Clonagem de %1$s - Modificação de %2$s', + 'UI:CloneTitle_Class_Object' => 'Clonagem de %1$s: %2$s', + 'UI:CreationPageTitle_Class' => 'Criar um(a) novo(a) %1$s ', + 'UI:CreationTitle_Class' => 'Criar um(a) novo(a) %1$s', 'UI:SelectTheTypeOf_Class_ToCreate' => 'Selecione o tipo de %1$s para criar:', - 'UI:Class_Object_NotUpdated' => 'Nenhuma alteração detectado, %1$s (%2$s) não tenha sido modificado.', - 'UI:Class_Object_Updated' => '%1$s (%2$s) atualizado.', + 'UI:Class_Object_NotUpdated' => 'Nenhuma modificação detectada, %1$s (%2$s) não foi modificado(a)', + 'UI:Class_Object_Updated' => '%1$s (%2$s) atualizado(a)', 'UI:BulkDeletePageTitle' => 'Exclusão em massa', - 'UI:BulkDeleteTitle' => 'Selecione os objetos que você deseja excluir:', - 'UI:PageTitle:ObjectCreated' => 'Objeto criado.', - 'UI:Title:Object_Of_Class_Created' => '%1$s - %2$s criado.', - 'UI:Apply_Stimulus_On_Object_In_State_ToTarget_State' => 'Aplicando %1$s em objeto: %2$s em estado %3$s a meta do estado: %4$s.', + 'UI:BulkDeleteTitle' => 'Selecione o(s) objeto(s) que você deseja excluir:', + 'UI:PageTitle:ObjectCreated' => 'Objeto criado', + 'UI:Title:Object_Of_Class_Created' => '%1$s - %2$s criado(a)', + 'UI:Apply_Stimulus_On_Object_In_State_ToTarget_State' => 'Aplicando %1$s no objeto: %2$s com status %3$s para o status alvo: %4$s', 'UI:ObjectCouldNotBeWritten' => 'O objeto não pode ser gravado: %1$s', 'UI:PageTitle:FatalError' => 'Erro fatal', - 'UI:SystemIntrusion' => 'Acesso negado. Você tem tentando realizar uma operação que não é permitido para você.', - 'UI:FatalErrorMessage' => 'Erro fatal, o sistema não pode continuar.', - 'UI:Error_Details' => 'Erro: %1$s.', + 'UI:SystemIntrusion' => 'Acesso negado. Você está tentando realizar uma operação que não é permitida para você', + 'UI:FatalErrorMessage' => 'Erro fatal, o sistema não pode continuar', + 'UI:Error_Details' => 'Erro: %1$s', - 'UI:PageTitle:ProfileProjections' => 'Gerenciamento Usuários - projeções de classe', + 'UI:PageTitle:ProfileProjections' => 'Gerenciamento de Usuários - Mapeamento de Perfis', 'UI:UserManagement:Class' => 'Classe', 'UI:UserManagement:Class+' => 'Classe de objetos', 'UI:UserManagement:ProjectedObject' => 'Objeto', - 'UI:UserManagement:ProjectedObject+' => 'Objetos projetados', + 'UI:UserManagement:ProjectedObject+' => 'Objetos mapeados', 'UI:UserManagement:AnyObject' => '* qualquer *', 'UI:UserManagement:User' => 'Usuário', - 'UI:UserManagement:User+' => 'Usuário(s) envolvido(s) na projeção', - 'UI:UserManagement:Action:Read' => 'Leitura', - 'UI:UserManagement:Action:Read+' => 'Leitura/mostrar objetos', - 'UI:UserManagement:Action:Modify' => 'Modificação', - 'UI:UserManagement:Action:Modify+' => 'Criar e editar (modificar) objetos', + 'UI:UserManagement:User+' => 'Usuário(s) envolvido(s) no mapeamento', + 'UI:UserManagement:Action:Read' => 'Ler', + 'UI:UserManagement:Action:Read+' => 'Ler/Exibir objetos', + 'UI:UserManagement:Action:Modify' => 'Editar', + 'UI:UserManagement:Action:Modify+' => 'Criar e editar objetos', 'UI:UserManagement:Action:Delete' => 'Excluir', 'UI:UserManagement:Action:Delete+' => 'Excluir objetos', - 'UI:UserManagement:Action:BulkRead' => 'Leitura em massa (Exportar)', - 'UI:UserManagement:Action:BulkRead+' => 'Listar objetos ou exportar em massa', - 'UI:UserManagement:Action:BulkModify' => 'Modificar em massa', + 'UI:UserManagement:Action:BulkRead' => 'Exibir/Exportar objetos em massa', + 'UI:UserManagement:Action:BulkRead+' => 'Exibir objetos ou exportar em massa', + 'UI:UserManagement:Action:BulkModify' => 'Edição em massa', 'UI:UserManagement:Action:BulkModify+' => 'Criar/editar em massa (importar CSV)', - 'UI:UserManagement:Action:BulkDelete' => 'Excluir em massa', + 'UI:UserManagement:Action:BulkDelete' => 'Exclusão em massa', 'UI:UserManagement:Action:BulkDelete+' => 'Excluir objeto(s) em massa', - 'UI:UserManagement:Action:Stimuli' => 'Stimuli', - 'UI:UserManagement:Action:Stimuli+' => 'Permitido ações (composta)', - 'UI:UserManagement:Action' => 'Ação', - 'UI:UserManagement:Action+' => 'Ação realizada pelo usuário', + 'UI:UserManagement:Action:Stimuli' => 'Estímulo', + 'UI:UserManagement:Action:Stimuli+' => 'Ações permitidas (composta)', + 'UI:UserManagement:Action' => 'Ações', + 'UI:UserManagement:Action+' => 'Ações realizadas pelo usuário', 'UI:UserManagement:TitleActions' => 'Ações', - 'UI:UserManagement:Permission' => 'Permissão', - 'UI:UserManagement:Permission+' => 'Permissões usuários', + 'UI:UserManagement:Permission' => 'Permissões', + 'UI:UserManagement:Permission+' => 'Permissões de usuários', 'UI:UserManagement:Attributes' => 'Atributos', 'UI:UserManagement:ActionAllowed:Yes' => 'Sim', 'UI:UserManagement:ActionAllowed:No' => 'Não', - 'UI:UserManagement:AdminProfile+' => 'Administradores tem total acesso leitura/gravação para todos os objetos no banco de dados.', + 'UI:UserManagement:AdminProfile+' => 'Administradores tem total acesso leitura/gravação para todos os objetos no banco de dados', 'UI:UserManagement:NoLifeCycleApplicable' => 'N/A', - 'UI:UserManagement:NoLifeCycleApplicable+' => 'Ciclo de vida não tem sido definida para esta classe', - 'UI:UserManagement:GrantMatrix' => 'Permissões concedidas', + 'UI:UserManagement:NoLifeCycleApplicable+' => 'Ciclo de vida não foi definido para esta classe', + 'UI:UserManagement:GrantMatrix' => 'Permissões de acesso', 'Menu:AdminTools' => 'Ferramentas Administrativas',// Duplicated into itop-welcome-itil (will be removed from here...) 'Menu:AdminTools+' => 'Ferramentas Administrativas',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:AdminTools?' => 'Ferramentas acessíveis apenas para usuários com o perfil do administrador',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:SystemTools' => 'System~~', + 'Menu:AdminTools?' => 'Ferramentas acessíveis apenas para usuários com o perfil de administrador',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:SystemTools' => 'Sistema', - 'UI:ChangeManagementMenu' => 'Gerenciamento Mudanças', - 'UI:ChangeManagementMenu+' => 'Gerenciamento Mudanças', + 'UI:ChangeManagementMenu' => 'Gerenciamento de Mudanças', + 'UI:ChangeManagementMenu+' => 'Gerenciamento de Mudanças', 'UI:ChangeManagementMenu:Title' => 'Visão geral', 'UI-ChangeManagementMenu-ChangesByType' => 'Mudanças por tipo', 'UI-ChangeManagementMenu-ChangesByStatus' => 'Mudanças por status', - 'UI-ChangeManagementMenu-ChangesNotYetAssigned' => 'Mudanças ainda não atribuídas', + 'UI-ChangeManagementMenu-ChangesNotYetAssigned' => 'Mudanças não atribuídas', - 'UI:ConfigurationManagementMenu' => 'Gerenciamento Configurações', - 'UI:ConfigurationManagementMenu+' => 'Gerenciamento Configurações', + 'UI:ConfigurationManagementMenu' => 'Gerenciamento Configuração', + 'UI:ConfigurationManagementMenu+' => 'Gerenciamento de Configuração', 'UI:ConfigurationManagementMenu:Title' => 'Visão geral', - 'UI-ConfigurationManagementMenu-InfraByType' => 'Objetos Infra-estrutura por tipo', - 'UI-ConfigurationManagementMenu-InfraByStatus' => 'Objetos Infra-estrutura por status', + 'UI-ConfigurationManagementMenu-InfraByType' => 'Objetos de infraestrutura por tipo', + 'UI-ConfigurationManagementMenu-InfraByStatus' => 'Objetos de infraestrutura por status', - 'UI:ConfigMgmtMenuOverview:Title' => 'Painel para Gerenciamento Configurações', + 'UI:ConfigMgmtMenuOverview:Title' => 'Painel de Gerenciamento de Configuração', 'UI-ConfigMgmtMenuOverview-FunctionalCIbyStatus' => 'Itens de configuração por status', 'UI-ConfigMgmtMenuOverview-FunctionalCIByType' => 'Itens de configuração por tipo', - 'UI:RequestMgmtMenuOverview:Title' => 'Painel para Gerenciamento Solicitações', - 'UI-RequestManagementOverview-RequestByService' => 'Solicitações usuários por serviço', - 'UI-RequestManagementOverview-RequestByPriority' => 'Solicitações usuários por prioridade', - 'UI-RequestManagementOverview-RequestUnassigned' => 'Solicitações usuários não atribuídos a um agente', + 'UI:RequestMgmtMenuOverview:Title' => 'Painel de Gerenciamento de Solicitações', + 'UI-RequestManagementOverview-RequestByService' => 'Solicitações de usuários por serviço', + 'UI-RequestManagementOverview-RequestByPriority' => 'Solicitações de usuários por prioridade', + 'UI-RequestManagementOverview-RequestUnassigned' => 'Solicitações de usuários não atribuídas a um agente', - 'UI:IncidentMgmtMenuOverview:Title' => 'Painel para Gerenciamento Incidentes', + 'UI:IncidentMgmtMenuOverview:Title' => 'Painel de Gerenciamento de Incidentes', 'UI-IncidentManagementOverview-IncidentByService' => 'Incidentes por serviço', 'UI-IncidentManagementOverview-IncidentByPriority' => 'Incidentes por prioridade', - 'UI-IncidentManagementOverview-IncidentUnassigned' => 'Incidentes por ainda atribuído a um agente', + 'UI-IncidentManagementOverview-IncidentUnassigned' => 'Incidentes não atribuídos a um agente', - 'UI:ChangeMgmtMenuOverview:Title' => 'Painel para Gerenciamento Mudanças', + 'UI:ChangeMgmtMenuOverview:Title' => 'Painel de Gerenciamento de Mudanças', 'UI-ChangeManagementOverview-ChangeByType' => 'Mudanças por tipo', - 'UI-ChangeManagementOverview-ChangeUnassigned' => 'Mudanças ainda não atribuído a um agente', - 'UI-ChangeManagementOverview-ChangeWithOutage' => 'Interrupções devido a alterações', + 'UI-ChangeManagementOverview-ChangeUnassigned' => 'Mudanças não atribuídas a um agente', + 'UI-ChangeManagementOverview-ChangeWithOutage' => 'Interrupções devido a mudanças', - 'UI:ServiceMgmtMenuOverview:Title' => 'Painel para Gerenciamento Serviços', - 'UI-ServiceManagementOverview-CustomerContractToRenew' => 'Contratos clientes a serem renovados em 30 dias', - 'UI-ServiceManagementOverview-ProviderContractToRenew' => 'Contratos provedores a serem renovados em 30 dias', + 'UI:ServiceMgmtMenuOverview:Title' => 'Painel de Gerenciamento de Serviços', + 'UI-ServiceManagementOverview-CustomerContractToRenew' => 'Contratos de clientes a serem renovados em 30 dias', + 'UI-ServiceManagementOverview-ProviderContractToRenew' => 'Contratos de provedores a serem renovados em 30 dias', 'UI:ContactsMenu' => 'Contatos', 'UI:ContactsMenu+' => 'Contatos', 'UI:ContactsMenu:Title' => 'Visão geral', - 'UI-ContactsMenu-ContactsByLocation' => 'Contatos por localidade', + 'UI-ContactsMenu-ContactsByLocation' => 'Contatos por localização', 'UI-ContactsMenu-ContactsByType' => 'Contatos por tipo', 'UI-ContactsMenu-ContactsByStatus' => 'Contatos por status', @@ -971,7 +973,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'Menu:CSVImportMenu+' => 'Criação ou atualização em massa',// Duplicated into itop-welcome-itil (will be removed from here...) 'Menu:DataModelMenu' => 'Modelo Dados',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:DataModelMenu+' => 'Visão geral do Modelo Dados',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:DataModelMenu+' => 'Visão geral do Modelo de Dados',// Duplicated into itop-welcome-itil (will be removed from here...) 'Menu:ExportMenu' => 'Exportar',// Duplicated into itop-welcome-itil (will be removed from here...) 'Menu:ExportMenu+' => 'Exportar o resultado de qualquer consulta em HTML, CSV ou XML',// Duplicated into itop-welcome-itil (will be removed from here...) @@ -981,58 +983,58 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:NotificationsMenu:Title' => 'Configuração de Notificações', 'UI:NotificationsMenu:Help' => 'Ajuda', 'UI:NotificationsMenu:HelpContent' => '

          As Notificações são totalmente personalizáveis​​. Elas são baseadas em dois conjuntos de objetos: Gatilhos e Ações.

          -

          Gatilhos define when a notification will be executed. There are different triggers as part of iTop core, but others can be brought by extensions: +

          Gatilhos definem quando uma notificação será executada. Existem diferentes gatilhos como parte do núcleo do iTop, mas outros podem ser trazidos por extensões:

            -
          1. Some triggers are executed when an object of the specified class is created, updated or deleted.
          2. -
          3. Some triggers are executed when an object of a given class enter or leave a specified state.
          4. -
          5. Some triggers are executed when a threshold on TTO or TTR has been reached.
          6. +
          7. Alguns gatilhos são acionados quando um objeto de uma determinada classe é criado, atualizado ou excluído.
          8. +
          9. Alguns gatilhos são acionados quando um objeto de uma determinada classe entra ou sai de um status específico.
          10. +
          11. Alguns gatilhos são acionados quando um limite de TTO ou TTR for alcançado.

          -Ações define the actions to be performed when the triggers execute. For now there are only two kind of actions: +Ações definem as ações a serem executadas quando os gatilhos forem acionados. Por enquanto, existem apenas dois tipos de ações:

            -
          1. Sending an email message: Such actions also define the template to be used for sending the email as well as the other parameters of the message like the recipients, importance, etc.
            - A special page: email.test.php is available for testing and troubleshooting your PHP mail configuration.
          2. -
          3. Outgoing webhooks: Allow integration with a third-party application by sending structured data to a defined URL.
          4. +
          5. Envio de uma mensagem de e-mail: Tais ações também definem o modelo a ser usado para enviar o e-mail, bem como os demais parâmetros da mensagem, como destinatário(s), prioridade, etc.
            + Uma página especial: email.test.php está disponível para testar e solucionar problemas de configuração de e-mail PHP.
          6. +
          7. Webhooks de saída: permite a integração com um aplicativo de terceiros enviando dados estruturados para um URL definido.

          -

          To be executed, actions must be associated to triggers. -When associated with a trigger, each action is given an "order" number, specifying in which order the actions are to be executed.

          ~~', +

          Para serem executadas, as ações devem estar associadas a gatilhos. +Quando associada a um gatilho, cada ação recebe um número de "ordem", especificando em qual ordem as ações devem ser executadas.

          ', 'UI:NotificationsMenu:Triggers' => 'Gatilhos', - 'UI:NotificationsMenu:AvailableTriggers' => 'Available triggers', - 'UI:NotificationsMenu:OnCreate' => 'When an object is created', - 'UI:NotificationsMenu:OnStateEnter' => 'When an object enters a given state', - 'UI:NotificationsMenu:OnStateLeave' => 'When an object leaves a given state', + 'UI:NotificationsMenu:AvailableTriggers' => 'Gatilhos disponíveis', + 'UI:NotificationsMenu:OnCreate' => 'Quando um objeto é criado', + 'UI:NotificationsMenu:OnStateEnter' => 'Quando um objeto entra em um determinado status', + 'UI:NotificationsMenu:OnStateLeave' => 'Quando um objeto sai um determinado status', 'UI:NotificationsMenu:Actions' => 'Ações', - 'UI:NotificationsMenu:Actions:ActionEmail' => 'Email actions~~', - 'UI:NotificationsMenu:Actions:ActionWebhook' => 'Webhook actions (outgoing integrations)~~', - 'UI:NotificationsMenu:Actions:Action' => 'Other actions~~', - 'UI:NotificationsMenu:AvailableActions' => 'Available actions', + 'UI:NotificationsMenu:Actions:ActionEmail' => 'Ações de e-mail', + 'UI:NotificationsMenu:Actions:ActionWebhook' => 'Ações do Webhook (integrações de saída)', + 'UI:NotificationsMenu:Actions:Action' => 'Outras ações', + 'UI:NotificationsMenu:AvailableActions' => 'Ações disponíveis', - 'Menu:TagAdminMenu' => 'Configuração de tags', + 'Menu:TagAdminMenu' => 'Configuração de Tags', 'Menu:TagAdminMenu+' => 'Gerenciamento de valores de tags', - 'UI:TagAdminMenu:Title' => 'Configuração de tags', + 'UI:TagAdminMenu:Title' => 'Configuração de Tags', 'UI:TagAdminMenu:NoTags' => 'Nenhum campo Tag configurado', 'UI:TagSetFieldData:Error' => 'Erro: %1$s', - 'Menu:AuditCategories' => 'Categoria Auditorias',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:AuditCategories+' => 'Categoria Auditorias',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:Notifications:Title' => 'Categoria Auditorias',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:AuditCategories' => 'Categorias de Auditoria',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:AuditCategories+' => 'Categorias de Auditoria',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:Notifications:Title' => 'Categorias de Auditoria',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:RunQueriesMenu' => 'Executar consultas',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:RunQueriesMenu' => 'Executar Consultas',// Duplicated into itop-welcome-itil (will be removed from here...) 'Menu:RunQueriesMenu+' => 'Executar qualquer consulta',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:QueryMenu' => 'Consulta definida',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:QueryMenu+' => 'Consulta definida',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:QueryMenu' => 'Livro de Consultas',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:QueryMenu+' => 'Livro de Consultas',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:DataAdministration' => 'Administração Dados',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:DataAdministration+' => 'Administração Dados',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:DataAdministration' => 'Administração de Dados',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:DataAdministration+' => 'Administração de Dados',// Duplicated into itop-welcome-itil (will be removed from here...) 'Menu:UniversalSearchMenu' => 'Pesquisa Universal',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:UniversalSearchMenu+' => 'Pesquisar por nada...',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:UniversalSearchMenu+' => 'Pesquisar em todo o '.ITOP_APPLICATION_SHORT.'...',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:UserManagementMenu' => 'Gerenciamento Usuários',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Menu:UserManagementMenu+' => 'Gerenciamento Usuários',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:UserManagementMenu' => 'Gerenciamento de Usuários',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:UserManagementMenu+' => 'Gerenciamento de Usuários',// Duplicated into itop-welcome-itil (will be removed from here...) 'Menu:ProfilesMenu' => 'Perfis',// Duplicated into itop-welcome-itil (will be removed from here...) 'Menu:ProfilesMenu+' => 'Perfis',// Duplicated into itop-welcome-itil (will be removed from here...) @@ -1047,11 +1049,11 @@ When associated with a trigger, each action is given an "order" number, specifyi // Duplicated into itop-welcome-itil (will be removed from here...) 'UI:iTopVersion:Short' => '%1$s versão %2$s', - 'UI:iTopVersion:Long' => '%1$s versão %2$s-%3$s construído %4$s', + 'UI:iTopVersion:Long' => '%1$s versão %2$s-%3$s compilação %4$s', 'UI:PropertiesTab' => 'Propriedades', - 'UI:OpenDocumentInNewWindow_' => 'Abrir~~', - 'UI:DownloadDocument_' => 'Baixar~~', + 'UI:OpenDocumentInNewWindow_' => 'Abrir', + 'UI:DownloadDocument_' => 'Baixar', 'UI:Document:NoPreview' => 'Nenhuma visualização está disponível para este documento', 'UI:Download-CSV' => 'Download %1$s', @@ -1061,11 +1063,11 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:Deadline_Hours_Minutes' => '%1$dh %2$dmin', 'UI:Deadline_Days_Hours_Minutes' => '%1$dd %2$dh %3$dmin', 'UI:Help' => 'Ajuda', - 'UI:PasswordConfirm' => 'Confirmar', - 'UI:BeforeAdding_Class_ObjectsSaveThisObject' => 'Antes de adicionar mais %1$s objetos, salvar este objeto.', + 'UI:PasswordConfirm' => 'Repetir senha', + 'UI:BeforeAdding_Class_ObjectsSaveThisObject' => 'Antes de adicionar mais %1$s objetos, salve este objeto', 'UI:DisplayThisMessageAtStartup' => 'Exibir esta mensagem na inicialização', 'UI:RelationshipGraph' => 'Visualizar gráfico', - 'UI:RelationshipList' => 'Listar', + 'UI:RelationshipList' => 'Exibir', 'UI:RelationGroups' => 'Grupos', 'UI:OperationCancelled' => 'Operação cancelada', 'UI:ElementsDisplayed' => 'Filtrando', @@ -1075,24 +1077,24 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:Relation:AdditionalContextInfo' => 'Informações de contexto adicionais', 'UI:Relation:NoneSelected' => 'Nenhum', 'UI:Relation:Zoom' => 'Zoom', - 'UI:Relation:ExportAsAttachment' => 'Exportar como Anexo ...', - 'UI:Relation:DrillDown' => 'Detalhes ...', + 'UI:Relation:ExportAsAttachment' => 'Exportar como Anexo...', + 'UI:Relation:DrillDown' => 'Detalhes...', 'UI:Relation:PDFExportOptions' => 'Opções de exportação de PDF', 'UI:Relation:AttachmentExportOptions_Name' => 'Opções de anexo para %1$s', 'UI:RelationOption:Untitled' => 'Sem título', - 'UI:Relation:Key' => 'Key~~', + 'UI:Relation:Key' => 'Chave', 'UI:Relation:Comments' => 'Comentários', - 'UI:RelationOption:Title' => 'Title~~', + 'UI:RelationOption:Title' => 'Título', 'UI:RelationOption:IncludeList' => 'Incluir a lista de objetos', 'UI:RelationOption:Comments' => 'Comentários', - 'UI:Button:Export' => 'Export~~', + 'UI:Button:Export' => 'Exportar', 'UI:Relation:PDFExportPageFormat' => 'Formato da página', 'UI:PageFormat_A3' => 'A3', 'UI:PageFormat_A4' => 'A4', 'UI:PageFormat_Letter' => 'Carta', 'UI:Relation:PDFExportPageOrientation' => 'Orientação da página', - 'UI:PageOrientation_Portrait' => 'Portrait~~', - 'UI:PageOrientation_Landscape' => 'Landscape~~', + 'UI:PageOrientation_Portrait' => 'Retrato', + 'UI:PageOrientation_Landscape' => 'Paisagem', 'UI:RelationTooltip:Redundancy' => 'Redundância', 'UI:RelationTooltip:ImpactedItems_N_of_M' => '# de itens impactados: %1$d / %2$d', 'UI:RelationTooltip:CriticalThreshold_N_of_M' => 'Limite crítico: %1$d / %2$d', @@ -1101,47 +1103,47 @@ When associated with a trigger, each action is given an "order" number, specifyi 'Portal:Refresh' => 'Atualizar', 'Portal:Back' => 'Voltar', 'Portal:WelcomeUserOrg' => 'Bem-vindo %1$s, de %2$s', - 'Portal:TitleDetailsFor_Request' => 'Detalhes para solicitação', - 'Portal:ShowOngoing' => 'Mostrar solicitações abertas', - 'Portal:ShowClosed' => 'Mostrar solicitações fechadas', - 'Portal:CreateNewRequest' => 'Criar uma nova solicitação', - 'Portal:CreateNewRequestItil' => 'Criar uma nova solicitação', - 'Portal:CreateNewIncidentItil' => 'Criar um novo relatório de incidente', + 'Portal:TitleDetailsFor_Request' => 'Detalhes da solicitação', + 'Portal:ShowOngoing' => 'Exibir solicitações abertas', + 'Portal:ShowClosed' => 'Exibir solicitações fechadas', + 'Portal:CreateNewRequest' => 'Criar uma nova Solicitação', + 'Portal:CreateNewRequestItil' => 'Criar uma nova Solicitação', + 'Portal:CreateNewIncidentItil' => 'Criar um novo Relatório de Incidente', 'Portal:ChangeMyPassword' => 'Alterar minha senha', 'Portal:Disconnect' => 'Sair', 'Portal:OpenRequests' => 'Minhas solicitações abertas', 'Portal:ClosedRequests' => 'Minhas solicitações fechadas', 'Portal:ResolvedRequests' => 'Minhas solicitações resolvidas', - 'Portal:SelectService' => 'Selecione um serviço de um catálogo:', + 'Portal:SelectService' => 'Selecione um serviço do catálogo:', 'Portal:PleaseSelectOneService' => 'Selecione um serviço', - 'Portal:SelectSubcategoryFrom_Service' => 'Selecione um sub-serviço do serviço %1$s:', - 'Portal:PleaseSelectAServiceSubCategory' => 'Selecione uma sub-categoria', - 'Portal:DescriptionOfTheRequest' => 'Digite a descrição de sua solicitação:', - 'Portal:TitleRequestDetailsFor_Request' => 'Detalhe da solicitação %1$s:', + 'Portal:SelectSubcategoryFrom_Service' => 'Selecione um subserviço do serviço %1$s:', + 'Portal:PleaseSelectAServiceSubCategory' => 'Selecione uma subcategoria', + 'Portal:DescriptionOfTheRequest' => 'Digite a descrição da sua solicitação:', + 'Portal:TitleRequestDetailsFor_Request' => 'Detalhes da solicitação %1$s:', 'Portal:NoOpenRequest' => 'Nenhuma solicitação nesta categoria', 'Portal:NoClosedRequest' => 'Nenhuma solicitação nesta categoria', 'Portal:Button:ReopenTicket' => 'Re-abrir esta solicitação', 'Portal:Button:CloseTicket' => 'Fechar esta solicitação', 'Portal:Button:UpdateRequest' => 'Atualizar a solicitação', - 'Portal:EnterYourCommentsOnTicket' => 'Digite seu comentário sobre a resolução/solução de sua solicitação:', + 'Portal:EnterYourCommentsOnTicket' => 'Digite seu comentário referente a solução da sua solicitação:', 'Portal:ErrorNoContactForThisUser' => 'Erro: o usuário atual não esta associado com um contato/pessoa. Por favor, contacte o administrador.', 'Portal:Attachments' => 'Anexos', 'Portal:AddAttachment' => ' Adicionar anexo ', 'Portal:RemoveAttachment' => ' Remover anexo ', 'Portal:Attachment_No_To_Ticket_Name' => 'Anexo #%1$d para %2$s (%3$s)', 'Portal:SelectRequestTemplate' => 'Selecione um modelo para %1$s', - 'Enum:Undefined' => 'Indefinido', + 'Enum:Undefined' => '(n/a)', 'UI:DurationForm_Days_Hours_Minutes_Seconds' => '%1$s dias %2$s horas %3$s minutos %4$s segundos', 'UI:ModifyAllPageTitle' => 'Modificar todos', - 'UI:Modify_N_ObjectsOf_Class' => 'Modificando objeto %1$d da classe %2$s', - 'UI:Modify_M_ObjectsOf_Class_OutOf_N' => 'Modificando objeto %1$d da classe %2$s fora de %3$d', - 'UI:Menu:ModifyAll' => 'Modificar...', + 'UI:Modify_N_ObjectsOf_Class' => 'Editando objeto %1$d da classe %2$s', + 'UI:Modify_M_ObjectsOf_Class_OutOf_N' => 'Editando objeto %1$d da classe %2$s de %3$d', + 'UI:Menu:ModifyAll' => 'Edição em massa...', 'UI:Button:ModifyAll' => 'Modificar todos', 'UI:Button:PreviewModifications' => 'Visualizar modificações >>', 'UI:ModifiedObject' => 'Objeto modificado', 'UI:BulkModifyStatus' => 'Operação', 'UI:BulkModifyStatus+' => 'Status da operação', - 'UI:BulkModifyErrors' => 'Erros (se qualquer)', + 'UI:BulkModifyErrors' => 'Erros (se houver)', 'UI:BulkModifyErrors+' => 'Erros que impedem a modificação', 'UI:BulkModifyStatusOk' => 'Ok', 'UI:BulkModifyStatusError' => 'Erro', @@ -1150,60 +1152,60 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:BulkModify_Count_DistinctValues' => '%1$d valores distintos:', 'UI:BulkModify:Value_Exists_N_Times' => '%1$s, %2$d tempo(s)', 'UI:BulkModify:N_MoreValues' => '%1$d mais valores...', - 'UI:AttemptingToSetAReadOnlyAttribute_Name' => 'Tentativa de definir o campo somente leitura: %1$s', - 'UI:FailedToApplyStimuli' => 'A ação falhou.', - 'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: modificando objetos %2$d da classe %3$s', + 'UI:AttemptingToSetAReadOnlyAttribute_Name' => 'Tentativa de definir o campo como somente-leitura: %1$s', + 'UI:FailedToApplyStimuli' => 'A ação falhou', + 'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: modificando %2$d objetos da classe %3$s', 'UI:CaseLogTypeYourTextHere' => 'Digite seu texto aqui:', 'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:', 'UI:CaseLog:InitialValue' => 'Valor inicial:', - 'UI:AttemptingToSetASlaveAttribute_Name' => 'O campo %1$s não é editável, porque é originado pela sincronização de dados. Valor não definido.', - 'UI:ActionNotAllowed' => 'Você não tem permissão para executar esta ação nesses objetos.', - 'UI:BulkAction:NoObjectSelected' => 'Por favor, selecione pelo menos um objeto para realizar esta operação.', - 'UI:AttemptingToChangeASlaveAttribute_Name' => 'O campo %1$s não é editável, porque é originado pela sincronização de dados. Valor não definido.', - 'UI:Pagination:HeaderSelection' => 'Total: %1$s objetos (%2$s objetos selecionados).', - 'UI:Pagination:HeaderNoSelection' => 'Total: %1$s objetos.', - 'UI:Pagination:PageSize' => '%1$s objetos por página', + 'UI:AttemptingToSetASlaveAttribute_Name' => 'O campo %1$s não é editável, porque é originado pela sincronização de dados. Valor não definido', + 'UI:ActionNotAllowed' => 'Você não tem permissão para executar esta ação nesses objetos', + 'UI:BulkAction:NoObjectSelected' => 'Por favor, selecione pelo menos um objeto para realizar esta operação', + 'UI:AttemptingToChangeASlaveAttribute_Name' => 'O campo %1$s não é editável, porque é originado pela sincronização de dados. Valor não definido', + 'UI:Pagination:HeaderSelection' => 'Total: %1$s objeto(s) (%2$s objeto(s) selecionado(s))', + 'UI:Pagination:HeaderNoSelection' => 'Total: %1$s objeto(s)', + 'UI:Pagination:PageSize' => '%1$s objeto(s) por página', 'UI:Pagination:PagesLabel' => 'Páginas:', - 'UI:Pagination:All' => 'Todos', + 'UI:Pagination:All' => 'Tudo', 'UI:HierarchyOf_Class' => 'Hierarquia de %1$s', 'UI:Preferences' => 'Preferências...', - 'UI:ArchiveModeOn' => 'Ativar o modo de arquivo', - 'UI:ArchiveModeOff' => 'Desativar modo de arquivo', - 'UI:ArchiveMode:Banner' => 'Modo de arquivo', + 'UI:ArchiveModeOn' => 'Ativar o modo de arquivamento', + 'UI:ArchiveModeOff' => 'Desativar modo de arquivamento', + 'UI:ArchiveMode:Banner' => 'Modo de arquivamento', 'UI:ArchiveMode:Banner+' => 'Objetos arquivados são visíveis e nenhuma modificação é permitida', 'UI:FavoriteOrganizations' => 'Organizações favoritas', - 'UI:FavoriteOrganizations+' => 'Confira na lista abaixo as organizações que você deseja ver no menu drop-down para um acesso rápido.Note-se que esta não é uma configuração de segurança, objetos de qualquer organização ainda são visíveis e podem ser acessadas ao selecionar \\"Todos Organizações\\" na lista drop-down.', - 'UI:FavoriteLanguage' => 'Idioma do painel do Usuário~~', - 'UI:Favorites:SelectYourLanguage' => 'Selecione sua linguagem preferida', + 'UI:FavoriteOrganizations+' => 'Confira na lista abaixo as organizações que você deseja ver no menu suspenso para acesso rápido. Note que esta não é uma configuração de segurança, objetos de qualquer organização ainda são visíveis e podem ser acessados ao selecionar "Todas as Organizações" no menu suspenso.', + 'UI:FavoriteLanguage' => 'Idioma do painel do usuário', + 'UI:Favorites:SelectYourLanguage' => 'Selecione seu idioma preferido', 'UI:FavoriteOtherSettings' => 'Outras configurações', - 'UI:Favorites:Default_X_ItemsPerPage' => 'Quantidade padrão para listas: %1$s itens por página~~', - 'UI:Favorites:ShowObsoleteData' => 'Mostrar dados obsoletos', - 'UI:Favorites:ShowObsoleteData+' => 'Mostrar dados obsoletos nos resultados de pesquisa e listas de itens para selecionar', - 'UI:NavigateAwayConfirmationMessage' => 'Qualquer modificações serão descartados.', - 'UI:CancelConfirmationMessage' => 'Você vai perder as suas alterações. Continuar mesmo assim?', - 'UI:AutoApplyConfirmationMessage' => 'Algumas mudanças ainda não foram aplicadas. Você quer levá-los em conta?', - 'UI:Create_Class_InState' => 'Criar o estado %1$s: ', + 'UI:Favorites:Default_X_ItemsPerPage' => 'Quantidade padrão para listas %1$s item(ns) por página', + 'UI:Favorites:ShowObsoleteData' => 'Exibir dados obsoletos', + 'UI:Favorites:ShowObsoleteData+' => 'Exibir dados obsoletos nos resultados de pesquisa e listas de itens para selecionar', + 'UI:NavigateAwayConfirmationMessage' => 'Quaisquer modificações serão descartadas', + 'UI:CancelConfirmationMessage' => 'Você irá perder as suas alterações. Continuar mesmo assim?', + 'UI:AutoApplyConfirmationMessage' => 'Algumas alterações ainda não foram aplicadas. Você quer que o '.ITOP_APPLICATION_SHORT.' os leve em consideração?', + 'UI:Create_Class_InState' => 'Criar o status %1$s: ', 'UI:OrderByHint_Values' => 'Classificar por: %1$s', 'UI:Menu:AddToDashboard' => 'Adicionar ao painel...', 'UI:Button:Refresh' => 'Atualizar', 'UI:Button:GoPrint' => 'Imprimir ...', - 'UI:ExplainPrintable' => 'Clique no ícone %1$s para ocultar itens da impressão.
          Use o recurso "pré-visualização de impressão" do seu navegador para visualizar antes de imprimir.
          Nota: este cabeçalho e outros controles de ajuste não ser impresso.', - 'UI:PrintResolution:FullSize' => 'Tamanho grande', + 'UI:ExplainPrintable' => 'Clique no ícone %1$s para ocultar itens da impressão.
          Use o recurso de "pré-visualização de impressão" do seu navegador para visualizar antes de imprimir.
          Nota: este cabeçalho e outros controles de ajuste não serão impressos', + 'UI:PrintResolution:FullSize' => 'Tamanho total', 'UI:PrintResolution:A4Portrait' => 'Retrato A4', 'UI:PrintResolution:A4Landscape' => 'Paisagem A4', 'UI:PrintResolution:LetterPortrait' => 'Carta Retrato', 'UI:PrintResolution:LetterLandscape' => 'Carta Retrato', - 'UI:Toggle:SwitchToStandardDashboard' => 'Switch to standard dashboard~~', - 'UI:Toggle:SwitchToCustomDashboard' => 'Switch to custom dashboard~~', + 'UI:Toggle:SwitchToStandardDashboard' => 'Alternar para o painel padrão', + 'UI:Toggle:SwitchToCustomDashboard' => 'Alternar para o painel personalizado', 'UI:ConfigureThisList' => 'Configurar esta lista...', - 'UI:ListConfigurationTitle' => 'Listar configuração', + 'UI:ListConfigurationTitle' => 'Configurações de exibição', 'UI:ColumnsAndSortOrder' => 'Colunas e ordem de classificação:', - 'UI:UseDefaultSettings' => 'Use a configuração padrão', - 'UI:UseSpecificSettings' => 'Use as seguintes configurações:', - 'UI:Display_X_ItemsPerPage_prefix' => 'Mostrar', + 'UI:UseDefaultSettings' => 'Usar a configuração padrão', + 'UI:UseSpecificSettings' => 'Usar as seguintes configurações:', + 'UI:Display_X_ItemsPerPage_prefix' => 'Exibir', 'UI:Display_X_ItemsPerPage_suffix' => 'itens por página', - 'UI:UseSavetheSettings' => 'Salvar configurações', + 'UI:UseSavetheSettings' => 'Salvar configurações:', 'UI:OnlyForThisList' => 'Somente para esta lista', 'UI:ForAllLists' => 'Para todas as listas', 'UI:ExtKey_AsLink' => '%1$s (Link)', @@ -1212,19 +1214,19 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:Button:MoveUp' => 'Mover acima', 'UI:Button:MoveDown' => 'Mover abaixo', - 'UI:OQL:UnknownClassAndFix' => 'Classe desconhecida "%1$s". Você pode tentar "%2$s" em vez.', - 'UI:OQL:UnknownClassNoFix' => 'Classe desconhecida "%1$s"', + 'UI:OQL:UnknownClassAndFix' => 'Classe desconhecida "%1$s". Ao invés disso, você pode tentar a classe "%2$s"', + 'UI:OQL:UnknownClassNoFix' => 'Classe desconhecida: "%1$s"', - 'UI:Dashboard:EditCustom' => 'Edit custom version...~~', - 'UI:Dashboard:CreateCustom' => 'Create a custom version...~~', - 'UI:Dashboard:DeleteCustom' => 'Delete custom version...~~', - 'UI:Dashboard:RevertConfirm' => 'Cada alterações feitas na versão original será perdido. Por favor, confirme que você quer fazer isso.', - 'UI:ExportDashBoard' => 'Exportar para um arquivo', - 'UI:ImportDashBoard' => 'Importar pelo arquivo...', - 'UI:ImportDashboardTitle' => 'Importar por um arquivo', - 'UI:ImportDashboardText' => 'Selecione um arquivo do painel para importar:', - 'UI:Dashboard:Actions' => 'Dashboard actions~~', - 'UI:Dashboard:NotUpToDateUntilContainerSaved' => 'This dashboard displays information that does not include the on-going changes.~~', + 'UI:Dashboard:EditCustom' => 'Editar visão personalizada...', + 'UI:Dashboard:CreateCustom' => 'Criar uma visão personalizada...', + 'UI:Dashboard:DeleteCustom' => 'Excluir visão personalizada...', + 'UI:Dashboard:RevertConfirm' => 'As alterações realizadas na visão original serão perdidas. Por favor, confirme que você quer fazer isso', + 'UI:ExportDashBoard' => 'Exportar visão para um arquivo', + 'UI:ImportDashBoard' => 'Importar visão de um arquivo...', + 'UI:ImportDashboardTitle' => 'Importar visão de um arquivo', + 'UI:ImportDashboardText' => 'Selecione um arquivo do Painel para importar:', + 'UI:Dashboard:Actions' => 'Ações do Painel', + 'UI:Dashboard:NotUpToDateUntilContainerSaved' => 'Este painel exibe informações que não incluem as alterações em andamento', 'UI:DashletCreation:Title' => 'Criar um novo Painel', @@ -1235,10 +1237,10 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:DashboardEdit:Title' => 'Editor', 'UI:DashboardEdit:DashboardTitle' => 'Título', 'UI:DashboardEdit:AutoReload' => 'Atualizar automaticamente', - 'UI:DashboardEdit:AutoReloadSec' => 'Intervalo atualização automática (segundos)', - 'UI:DashboardEdit:AutoReloadSec+' => 'O mínimo permitido é %1$d segundos', - 'UI:DashboardEdit:Revert' => 'Revert~~', - 'UI:DashboardEdit:Apply' => 'Apply~~', + 'UI:DashboardEdit:AutoReloadSec' => 'Intervalo de atualização automática (segundos)', + 'UI:DashboardEdit:AutoReloadSec+' => 'O intervalo mínimo permitido é %1$d segundos', + 'UI:DashboardEdit:Revert' => 'Reverter', + 'UI:DashboardEdit:Apply' => 'Salvar', 'UI:DashboardEdit:Layout' => 'Layout', 'UI:DashboardEdit:Properties' => 'Propriedades', @@ -1250,14 +1252,14 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:DashletUnknown:Label' => 'Desconhecido', 'UI:DashletUnknown:Description' => 'Dashlet desconhecido (pode ter sido desinstalado)', - 'UI:DashletUnknown:RenderText:View' => 'Não é possível renderizar este dashlet.', - 'UI:DashletUnknown:RenderText:Edit' => 'Não é possível renderizar este dashlet (classe "%1$s"). Verifique com seu administrador se ainda está disponível. ', - 'UI:DashletUnknown:RenderNoDataText:Edit' => 'Não há visualização disponível para este dashlet (classe "%1$s").', + 'UI:DashletUnknown:RenderText:View' => 'Não é possível renderizar este dashlet', + 'UI:DashletUnknown:RenderText:Edit' => 'Não é possível renderizar este dashlet (classe "%1$s"). Verifique com seu administrador se este dashlet ainda está disponível', + 'UI:DashletUnknown:RenderNoDataText:Edit' => 'Não há visualização disponível para este dashlet (classe "%1$s")', 'UI:DashletUnknown:Prop-XMLConfiguration' => 'Configuração (mostrada como XML bruta)', 'UI:DashletProxy:Label' => 'Proxy', 'UI:DashletProxy:Description' => 'Proxy dashlet', - 'UI:DashletProxy:RenderNoDataText:Edit' => 'Nenhuma pré-visualização disponível para este dashlet de terceiros (classe "%1$s").', + 'UI:DashletProxy:RenderNoDataText:Edit' => 'Nenhuma pré-visualização disponível para este dashlet de terceiros (classe "%1$s")', 'UI:DashletProxy:Prop-XMLConfiguration' => 'Configuração (mostrada como XML bruta)', 'UI:DashletPlainText:Label' => 'Texto', @@ -1274,7 +1276,7 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:DashletGroupBy:Prop-Title' => 'Título', 'UI:DashletGroupBy:Prop-Query' => 'Questão', 'UI:DashletGroupBy:Prop-Style' => 'Estilo', - 'UI:DashletGroupBy:Prop-GroupBy' => 'Grupo por...', + 'UI:DashletGroupBy:Prop-GroupBy' => 'Agrupar por...', 'UI:DashletGroupBy:Prop-GroupBy:Hour' => 'Hora de %1$s (0-23)', 'UI:DashletGroupBy:Prop-GroupBy:Month' => 'Mês de %1$s (1 - 12)', 'UI:DashletGroupBy:Prop-GroupBy:DayOfWeek' => 'Dia da semana para %1$s', @@ -1285,12 +1287,12 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth' => '%1$s (dia do mês)', 'UI:DashletGroupBy:MissingGroupBy' => 'Por favor, selecione o campo no qual os objetos serão agrupados', - 'UI:DashletGroupByPie:Label' => 'Pie Chart', - 'UI:DashletGroupByPie:Description' => 'Pie Chart', - 'UI:DashletGroupByBars:Label' => 'Bar Chart', - 'UI:DashletGroupByBars:Description' => 'Bar Chart', + 'UI:DashletGroupByPie:Label' => 'Gráfico de Pizza', + 'UI:DashletGroupByPie:Description' => 'Gráfico de Pizza', + 'UI:DashletGroupByBars:Label' => 'Gráfico de Barras', + 'UI:DashletGroupByBars:Description' => 'Gráfico de Barras', 'UI:DashletGroupByTable:Label' => 'Grupo por (tabela)', - 'UI:DashletGroupByTable:Description' => 'Listar (Agrupado por um campo)', + 'UI:DashletGroupByTable:Description' => 'Exibir (Agrupado por um campo)', // New in 2.5 'UI:DashletGroupBy:Prop-Function' => 'Função de agregação', @@ -1302,9 +1304,9 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:DashletGroupBy:Order:asc' => 'Ascendente', 'UI:DashletGroupBy:Order:desc' => 'Descendente', - 'UI:GroupBy:count' => 'Contagem', + 'UI:GroupBy:count' => 'Total', 'UI:GroupBy:count+' => 'Número de elementos', - 'UI:GroupBy:sum' => 'Sum~~', + 'UI:GroupBy:sum' => 'Soma', 'UI:GroupBy:sum+' => 'Soma de %1$s', 'UI:GroupBy:avg' => 'Média', 'UI:GroupBy:avg+' => 'Média de %1$s', @@ -1325,14 +1327,14 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:DashletHeaderDynamic:Prop-Title' => 'Título', 'UI:DashletHeaderDynamic:Prop-Title:Default' => 'Contatos', 'UI:DashletHeaderDynamic:Prop-Icon' => 'Ícone', - 'UI:DashletHeaderDynamic:Prop-Subtitle' => 'Sub-título', + 'UI:DashletHeaderDynamic:Prop-Subtitle' => 'Subtítulo', 'UI:DashletHeaderDynamic:Prop-Subtitle:Default' => 'Contatos', 'UI:DashletHeaderDynamic:Prop-Query' => 'Consulta', 'UI:DashletHeaderDynamic:Prop-GroupBy' => 'Grupo por', 'UI:DashletHeaderDynamic:Prop-Values' => 'Valores', - 'UI:DashletBadge:Label' => 'Divisa', - 'UI:DashletBadge:Description' => 'Ícone objeto com novo/pesquisa', + 'UI:DashletBadge:Label' => 'Ícone', + 'UI:DashletBadge:Description' => 'Ícone representando uma classe de objetos, bem como links para criar/pesquisar', 'UI:DashletBadge:Prop-Class' => 'Classe', 'DayOfWeek-Sunday' => 'Domingo', @@ -1380,24 +1382,24 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:Menu:ShortcutList' => 'Criar um atalho...', 'UI:ShortcutRenameDlg:Title' => 'Renomear o atalho', 'UI:ShortcutListDlg:Title' => 'Criar um atalho para a lista', - 'UI:ShortcutDelete:Confirm' => 'Por favor, confirme que você deseja excluir o(s) atalho(s).', + 'UI:ShortcutDelete:Confirm' => 'Por favor, confirme que você deseja excluir o(s) atalho(s)', 'Menu:MyShortcuts' => 'Meus atalhos',// Duplicated into itop-welcome-itil (will be removed from here...) - 'Class:Shortcut' => 'Atalho', + 'Class:Shortcut' => 'Atalhos', 'Class:Shortcut+' => '', 'Class:Shortcut/Attribute:name' => 'Nome', - 'Class:Shortcut/Attribute:name+' => 'Nome usado no menu e título da página', - 'Class:ShortcutOQL' => 'Resultado pesquisa atalho', + 'Class:Shortcut/Attribute:name+' => 'Nome exibido no menu e título da página', + 'Class:ShortcutOQL' => 'Atalho para resultados da pesquisa', 'Class:ShortcutOQL+' => '', 'Class:ShortcutOQL/Attribute:oql' => 'Consulta', - 'Class:ShortcutOQL/Attribute:oql+' => 'definição da lista de objetos para procurar', + 'Class:ShortcutOQL/Attribute:oql+' => 'Definição OQL da lista de objetos para procurar', 'Class:ShortcutOQL/Attribute:auto_reload' => 'Atualizar automaticamente', 'Class:ShortcutOQL/Attribute:auto_reload/Value:none' => 'Desabilitado', 'Class:ShortcutOQL/Attribute:auto_reload/Value:custom' => 'Avaliar', 'Class:ShortcutOQL/Attribute:auto_reload_sec' => 'Intervalo atualização automática (segundos)', 'Class:ShortcutOQL/Attribute:auto_reload_sec/tip' => 'O mínimo permitido é %1$d sgundos', - 'UI:FillAllMandatoryFields' => 'Por favor, preencha todos os campos obrigatórios.', - 'UI:ValueMustBeSet' => 'Por favor especifique um valor', + 'UI:FillAllMandatoryFields' => 'Por favor, preencha todos os campos obrigatórios', + 'UI:ValueMustBeSet' => 'Por favor, especifique um valor', 'UI:ValueMustBeChanged' => 'Por favor, altere o valor', 'UI:ValueInvalidFormat' => 'Formato inválido', @@ -1428,10 +1430,10 @@ When associated with a trigger, each action is given an "order" number, specifyi ', 'UI:Button:Remove' => 'Excluir', - 'UI:AddAnExisting_Class' => 'Adicionar objetos do tipo %1$s...', + 'UI:AddAnExisting_Class' => 'Associar objetos do tipo %1$s...', 'UI:SelectionOf_Class' => 'Selecionar objetos do tipo %1$s', - 'UI:AboutBox' => 'Sobre o iTop ...', + 'UI:AboutBox' => 'Sobre o '.ITOP_APPLICATION_SHORT.'...', 'UI:About:Title' => 'Sobre o '.ITOP_APPLICATION_SHORT, 'UI:About:DataModel' => 'Modelo de dados', 'UI:About:Support' => 'Informações de suporte', @@ -1439,32 +1441,32 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:About:InstallationOptions' => 'Opções de instalação', 'UI:About:ManualExtensionSource' => 'Extensão', 'UI:About:Extension_Version' => 'Versão: %1$s', - 'UI:About:RemoteExtensionSource' => 'Dado', + 'UI:About:RemoteExtensionSource' => 'iTop Hub', - 'UI:DisconnectedDlgMessage' => 'Você está desconectado. Você deve se identificar para continuar usando o aplicativo.', + 'UI:DisconnectedDlgMessage' => 'Você foi desconectado. Você deve se identificar novamente para continuar usando o aplicativo.', 'UI:DisconnectedDlgTitle' => 'Atenção!', - 'UI:LoginAgain' => 'Login novamente', - 'UI:StayOnThePage' => 'Fique nessa página', + 'UI:LoginAgain' => 'Entrar novamente', + 'UI:StayOnThePage' => 'Permanecer nessa página', 'ExcelExporter:ExportMenu' => 'Exportar para Excel...', 'ExcelExporter:ExportDialogTitle' => 'Exportar para Excel', 'ExcelExporter:ExportButton' => 'Exportar', - 'ExcelExporter:DownloadButton' => 'Download %1$s', + 'ExcelExporter:DownloadButton' => 'Baixar %1$s', 'ExcelExporter:RetrievingData' => 'Recuperando dados...', 'ExcelExporter:BuildingExcelFile' => 'Construindo o arquivo do Excel...', 'ExcelExporter:Done' => 'Feito.', 'ExcelExport:AutoDownload' => 'Inicie o download automaticamente quando a exportação estiver pronta', - 'ExcelExport:PreparingExport' => 'Preparando a exportação ...', + 'ExcelExport:PreparingExport' => 'Preparando a exportação...', 'ExcelExport:Statistics' => 'Estatísticas', - 'portal:legacy_portal' => 'Portal do usuário final', + 'portal:legacy_portal' => 'Portal do usuário (legado) do '.ITOP_APPLICATION_SHORT, 'portal:backoffice' => 'Interface de usuário back-office do '.ITOP_APPLICATION_SHORT, - 'UI:CurrentObjectIsLockedBy_User' => 'O objeto está bloqueado, pois está sendo modificado por %1$s.', - 'UI:CurrentObjectIsLockedBy_User_Explanation' => 'O objeto está sendo modificado por %1$s. Suas modificações não podem ser enviadas, pois seriam sobrescritas.', - 'UI:CurrentObjectIsSoftLockedBy_User' => 'The object is currently being modified by %1$s. You\'ll be able to submit your modifications once they have finished.~~', - 'UI:CurrentObjectLockExpired' => 'O bloqueio para impedir modificações simultâneas do objeto expirou.', - 'UI:CurrentObjectLockExpired_Explanation' => 'O bloqueio para impedir modificações simultâneas do objeto expirou. Você não pode mais enviar sua modificação, pois outros usuários agora podem modificar este objeto.', - 'UI:ConcurrentLockKilled' => 'O bloqueio impedindo modificações no objeto atual foi deletado.', + 'UI:CurrentObjectIsLockedBy_User' => 'O objeto está bloqueado, pois está sendo modificado por %1$s', + 'UI:CurrentObjectIsLockedBy_User_Explanation' => 'O objeto está sendo modificado por %1$s. Suas modificações não podem ser enviadas, pois seriam sobrescritas', + 'UI:CurrentObjectIsSoftLockedBy_User' => 'O objeto está sendo modificado por %1$s. Você será capaz de enviar suas modificações quando terminarem', + 'UI:CurrentObjectLockExpired' => 'O bloqueio para impedir modificações simultâneas do objeto expirou', + 'UI:CurrentObjectLockExpired_Explanation' => 'O bloqueio para impedir modificações simultâneas do objeto expirou. Você não pode mais enviar sua modificação, pois outros usuários agora podem modificar este objeto', + 'UI:ConcurrentLockKilled' => 'O bloqueio impedindo modificações no objeto atual foi removido', 'UI:Menu:KillConcurrentLock' => 'Matar o bloqueio de modificação simultânea!', 'UI:Menu:ExportPDF' => 'Exportar como PDF...', @@ -1474,32 +1476,32 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:UploadInlineImageLegend' => 'Carregar uma nova imagem', 'UI:SelectInlineImageToUpload' => 'Selecione a imagem para enviar', 'UI:AvailableInlineImagesLegend' => 'Imagens disponíveis', - 'UI:NoInlineImage' => 'Não há imagem disponível no servidor. Use o botão "Browse" acima para selecionar uma imagem do seu computador e fazer o upload para o servidor. ', + 'UI:NoInlineImage' => 'Não há imagem disponível no servidor. Use o botão "Escolher arquivo" acima para selecionar uma imagem do seu computador e fazer o upload para o servidor', 'UI:ToggleFullScreen' => 'Alternancia Maximizar / Minimizar', 'UI:Button:ResetImage' => 'Recupere a imagem anterior', 'UI:Button:RemoveImage' => 'Remover a imagem', - 'UI:Button:UploadImage' => 'Upload an image from the disk~~', - 'UI:UploadNotSupportedInThisMode' => 'A modificação de imagens ou arquivos não é suportada neste modo.', + 'UI:Button:UploadImage' => 'Carregar uma imagem do disco', + 'UI:UploadNotSupportedInThisMode' => 'A modificação de imagens ou arquivos não é suportada neste modo', - 'UI:Button:RemoveDocument' => 'Remove the document~~', + 'UI:Button:RemoveDocument' => 'Remover o documento', // Search form 'UI:Search:Toggle' => 'Minimizar / Expandir', 'UI:Search:AutoSubmit:DisabledHint' => 'O envio automático foi desativado para esta classe', - 'UI:Search:Obsolescence:DisabledHint' => 'Baseado nas suas preferências, dados obsoletos estão escondidos', - 'UI:Search:NoAutoSubmit:ExplainText' => 'Adicione algum critério na caixa de pesquisa ou clique no botão de pesquisa para visualizar os objetos.', + 'UI:Search:Obsolescence:DisabledHint' => 'Baseado nas suas preferências de usuário, dados obsoletos não são exibidos', + 'UI:Search:NoAutoSubmit:ExplainText' => 'Adicione algum critério na caixa de pesquisa ou clique no botão de pesquisa para visualizar os objetos', 'UI:Search:Criterion:MoreMenu:AddCriteria' => 'Adicionar novos critérios', // - Add new criteria button - 'UI:Search:AddCriteria:List:RecentlyUsed:Title' => 'Recentemente usado', + 'UI:Search:AddCriteria:List:RecentlyUsed:Title' => 'Usado recentemente', 'UI:Search:AddCriteria:List:MostPopular:Title' => 'Mais popular', 'UI:Search:AddCriteria:List:Others:Title' => 'Outros', - 'UI:Search:AddCriteria:List:RecentlyUsed:Placeholder' => 'Nenhum ainda.', + 'UI:Search:AddCriteria:List:RecentlyUsed:Placeholder' => 'Nenhum ainda', // - Criteria header actions - 'UI:Search:Criteria:Toggle' => 'Minimize / Expand~~', - 'UI:Search:Criteria:Remove' => 'Remove~~', - 'UI:Search:Criteria:Locked' => 'Locked~~', + 'UI:Search:Criteria:Toggle' => 'Minimizar / Expandir', + 'UI:Search:Criteria:Remove' => 'Remover', + 'UI:Search:Criteria:Locked' => 'Bloqueado', // - Criteria titles // - Default widget @@ -1508,7 +1510,7 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:Search:Criteria:Title:Default:NotEmpty' => '%1$s não está vazio', 'UI:Search:Criteria:Title:Default:Equals' => '%1$s é igual a %2$s', 'UI:Search:Criteria:Title:Default:Contains' => '%1$s contém %2$s', - 'UI:Search:Criteria:Title:Default:StartsWith' => '%1$s começa com %2$s', + 'UI:Search:Criteria:Title:Default:StartsWith' => '%1$s inicia com %2$s', 'UI:Search:Criteria:Title:Default:EndsWith' => '%1$s termina com %2$s', 'UI:Search:Criteria:Title:Default:RegExp' => '%1$s corresponde a %2$s', 'UI:Search:Criteria:Title:Default:GreaterThan' => '%1$s > %2$s', @@ -1553,31 +1555,31 @@ When associated with a trigger, each action is given an "order" number, specifyi // - Default widget 'UI:Search:Criteria:Operator:Default:Empty' => 'Está vazio', 'UI:Search:Criteria:Operator:Default:NotEmpty' => 'Não está vazio', - 'UI:Search:Criteria:Operator:Default:Equals' => 'Iguais', + 'UI:Search:Criteria:Operator:Default:Equals' => 'Igual', 'UI:Search:Criteria:Operator:Default:Between' => 'Entre', // - String widget 'UI:Search:Criteria:Operator:String:Contains' => 'Contém', - 'UI:Search:Criteria:Operator:String:StartsWith' => 'Começa com', + 'UI:Search:Criteria:Operator:String:StartsWith' => 'Inicia com', 'UI:Search:Criteria:Operator:String:EndsWith' => 'Termina com', 'UI:Search:Criteria:Operator:String:RegExp' => 'Exp. Regular ', // - Numeric widget - 'UI:Search:Criteria:Operator:Numeric:Equals' => 'Iguais',// => '=', + 'UI:Search:Criteria:Operator:Numeric:Equals' => 'Igual',// => '=', 'UI:Search:Criteria:Operator:Numeric:GreaterThan' => 'Maior',// => '>', 'UI:Search:Criteria:Operator:Numeric:GreaterThanOrEquals' => 'Maior',// > '>=', 'UI:Search:Criteria:Operator:Numeric:LessThan' => 'Menor',// => '<', 'UI:Search:Criteria:Operator:Numeric:LessThanOrEquals' => 'Menor / igual a',// > '<=', - 'UI:Search:Criteria:Operator:Numeric:Different' => 'Diferente',// => '≠', + 'UI:Search:Criteria:Operator:Numeric:Different' => 'Diferente de',// => '≠', // - Tag Set Widget - 'UI:Search:Criteria:Operator:TagSet:Matches' => 'Matches~~', + 'UI:Search:Criteria:Operator:TagSet:Matches' => 'Correspondências', // - Other translations 'UI:Search:Value:Filter:Placeholder' => 'Filtrar...', - 'UI:Search:Value:Search:Placeholder' => 'Buscar...', - 'UI:Search:Value:Autocomplete:StartTyping' => 'Comece a digitar valores possíveis.', + 'UI:Search:Value:Search:Placeholder' => 'Pesquisar...', + 'UI:Search:Value:Autocomplete:StartTyping' => 'Comece a digitar os valores possíveis', 'UI:Search:Value:Autocomplete:Wait' => 'Aguarde...', - 'UI:Search:Value:Autocomplete:NoResult' => 'Sem resultados.', + 'UI:Search:Value:Autocomplete:NoResult' => 'Sem resultados', 'UI:Search:Value:Toggler:CheckAllNone' => 'Marcar todos / nenhum', - 'UI:Search:Value:Toggler:CheckAllNoneFiltered' => 'Marcar todos / nenhum visiveis', + 'UI:Search:Value:Toggler:CheckAllNoneFiltered' => 'Marcar todos / Nenhum visíveis', // - Widget other translations 'UI:Search:Criteria:Numeric:From' => 'De', @@ -1592,20 +1594,20 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:Search:Criteria:DateTime:PlaceholderFromTime' => 'Qualquer data', 'UI:Search:Criteria:DateTime:PlaceholderUntil' => 'Qualquer data', 'UI:Search:Criteria:DateTime:PlaceholderUntilTime' => 'Qualquer data', - 'UI:Search:Criteria:HierarchicalKey:ChildrenIncluded:Hint' => 'Os filhos dos objetos selecionados serão incluídos.', + 'UI:Search:Criteria:HierarchicalKey:ChildrenIncluded:Hint' => 'Os objetos filhos dos objetos selecionados serão incluídos', - 'UI:Search:Criteria:Raw:Filtered' => 'Filtered', + 'UI:Search:Criteria:Raw:Filtered' => 'Filtrado', 'UI:Search:Criteria:Raw:FilteredOn' => 'Filtrado em %1$s', - 'UI:StateChanged' => 'State changed~~', + 'UI:StateChanged' => 'Status alterado', )); // // Expression to Natural language // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Expression:Operator:AND' => ' AND ', - 'Expression:Operator:OR' => ' OR ', + 'Expression:Operator:AND' => ' E ', + 'Expression:Operator:OR' => ' OU ', 'Expression:Operator:=' => ': ', 'Expression:Unit:Short:DAY' => 'd', @@ -1614,7 +1616,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Expression:Unit:Short:YEAR' => 'a', 'Expression:Unit:Long:DAY' => 'dia(s)', - 'Expression:Unit:Long:HOUR' => 'hora(s)~~', + 'Expression:Unit:Long:HOUR' => 'hora(s)', 'Expression:Unit:Long:MINUTE' => 'minuto(s)', 'Expression:Verb:NOW' => 'agora', @@ -1626,60 +1628,60 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( // Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'UI:Newsroom:NoNewMessage' => 'Nenhuma mensagem nova', - 'UI:Newsroom:XNewMessage' => '%1$s new message(s)~~', + 'UI:Newsroom:XNewMessage' => '%1$s nova(s) mensagem(ns)', 'UI:Newsroom:MarkAllAsRead' => 'Marcar todas as mensagens como lidas', 'UI:Newsroom:ViewAllMessages' => 'Ver todas as mensagens', - 'UI:Newsroom:Preferences' => 'Preferências de sala de notícias', + 'UI:Newsroom:Preferences' => 'Preferências da sala de notícias', 'UI:Newsroom:ConfigurationLink' => 'Configuração', 'UI:Newsroom:ResetCache' => 'Redefinir cache', - 'UI:Newsroom:DisplayMessagesFor_Provider' => 'Mostrar mensagens de %1$s', - 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Exibir até %1$s mensagens no menu %2$s.', + 'UI:Newsroom:DisplayMessagesFor_Provider' => 'Exibir mensagens do(a) %1$s', + 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Exibir até %1$s mensagem(ns) no menu %2$s', )); Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'Menu:DataSources' => 'Fontes de dados de sincronização', - 'Menu:DataSources+' => 'Todas fontes de dados de sincronização', - 'Menu:WelcomeMenu' => 'Bem-vindo ao '.ITOP_APPLICATION_SHORT, + 'Menu:DataSources' => 'Fontes de Sincronização de Dados', + 'Menu:DataSources+' => 'Lista de Fontes de Sincronização de Dados', + 'Menu:WelcomeMenu' => 'Página inicial do '.ITOP_APPLICATION_SHORT, 'Menu:WelcomeMenu+' => 'Bem-vindo ao '.ITOP_APPLICATION_SHORT, - 'Menu:WelcomeMenuPage' => 'Bem-vindo ao '.ITOP_APPLICATION_SHORT, + 'Menu:WelcomeMenuPage' => 'Página inicial do '.ITOP_APPLICATION_SHORT, 'Menu:WelcomeMenuPage+' => 'Bem-vindo ao '.ITOP_APPLICATION_SHORT, 'Menu:AdminTools' => 'Ferramentas Administrativas', 'Menu:AdminTools+' => 'Ferramentas Administrativas', - 'Menu:AdminTools?' => 'Ferramentas acessíveis apenas para usuários com o perfil do administrador', - 'Menu:DataModelMenu' => 'Modelo Dados', - 'Menu:DataModelMenu+' => 'Visão geral do Modelo Dados', - 'Menu:ExportMenu' => 'Exportar', + 'Menu:AdminTools?' => 'Ferramentas acessíveis apenas para usuários com perfil de administrador', + 'Menu:DataModelMenu' => 'Modelo de Dados', + 'Menu:DataModelMenu+' => 'Visão geral do Modelo de Dados', + 'Menu:ExportMenu' => 'Exportar Consulta', 'Menu:ExportMenu+' => 'Exportar o resultado de qualquer consulta em HTML, CSV ou XML', 'Menu:NotificationsMenu' => 'Notificações', 'Menu:NotificationsMenu+' => 'Configuração de Notificações', - 'Menu:AuditCategories' => 'Categoria Auditorias', - 'Menu:AuditCategories+' => 'Categoria Auditorias', - 'Menu:Notifications:Title' => 'Categoria Auditorias', - 'Menu:RunQueriesMenu' => 'Executar consultas', + 'Menu:AuditCategories' => 'Categorias de Auditoria', + 'Menu:AuditCategories+' => 'Lista de Categorias de Auditoria', + 'Menu:Notifications:Title' => 'Categorias de Auditoria', + 'Menu:RunQueriesMenu' => 'Executar Consultas', 'Menu:RunQueriesMenu+' => 'Executar qualquer consulta', - 'Menu:QueryMenu' => 'Consulta definida', - 'Menu:QueryMenu+' => 'Consulta definida', + 'Menu:QueryMenu' => 'Livro de Consultas', + 'Menu:QueryMenu+' => 'Lista de Livro de Consultas', 'Menu:UniversalSearchMenu' => 'Pesquisa Universal', - 'Menu:UniversalSearchMenu+' => 'Pesquisar por nada...', - 'Menu:UserManagementMenu' => 'Gerenciamento Usuários', - 'Menu:UserManagementMenu+' => 'Gerenciamento Usuários', - 'Menu:ProfilesMenu' => 'Perfis', - 'Menu:ProfilesMenu+' => 'Perfis', - 'Menu:ProfilesMenu:Title' => 'Perfis', - 'Menu:UserAccountsMenu' => 'Contas usuários', - 'Menu:UserAccountsMenu+' => 'Contas usuários', - 'Menu:UserAccountsMenu:Title' => 'Contas usuários', + 'Menu:UniversalSearchMenu+' => 'Pesquisar por todo o aplicativo...', + 'Menu:UserManagementMenu' => 'Gerenciamento de Usuários', + 'Menu:UserManagementMenu+' => '', + 'Menu:ProfilesMenu' => 'Perfis de Usuário', + 'Menu:ProfilesMenu+' => 'Lista de Perfis de Usuário', + 'Menu:ProfilesMenu:Title' => 'Perfis de Usuário', + 'Menu:UserAccountsMenu' => 'Contas de Usuários', + 'Menu:UserAccountsMenu+' => 'Lista de Contas de Usuário', + 'Menu:UserAccountsMenu:Title' => 'Contas de Usuários', 'Menu:MyShortcuts' => 'Meus atalhos', - 'Menu:UserManagement' => 'Gerenciamento de usuários', + 'Menu:UserManagement' => 'Gerenciamento de Usuários', 'Menu:Queries' => 'Consultas', - 'Menu:ConfigurationTools' => 'Configuração', + 'Menu:ConfigurationTools' => 'Configurações', )); // Additional language entries not present in English dict Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'UI:Toggle:StandardDashboard' => 'Padrão', - 'UI:Toggle:CustomDashboard' => 'Customizado', - 'UI:Dashboard:Edit' => 'Editar esta página...', - 'UI:Dashboard:Revert' => 'Reverter para versão original...', + 'UI:Toggle:StandardDashboard' => 'Padrão', + 'UI:Toggle:CustomDashboard' => 'Customizado', + 'UI:Dashboard:Edit' => 'Editar esta página...', + 'UI:Dashboard:Revert' => 'Reverter para versão original...', )); diff --git a/dictionaries/ru.dictionary.itop.core.php b/dictionaries/ru.dictionary.itop.core.php index 77923d610e..7ca2fb9337 100644 --- a/dictionaries/ru.dictionary.itop.core.php +++ b/dictionaries/ru.dictionary.itop.core.php @@ -10,22 +10,24 @@ */ Dict::Add('RU RU', 'Russian', 'Русский', array( 'Core:DeletedObjectLabel' => '%1ы (удален)', - 'Core:DeletedObjectTip' => 'Объект был удален %1$s (%2$s)', + 'Core:DeletedObjectTip' => 'Объект был удален %1$s (%2$s)', 'Core:UnknownObjectLabel' => 'Объект не найден (class: %1$s, id: %2$d)', - 'Core:UnknownObjectTip' => 'Объект не удается найти. Возможно, он был удален некоторое время назад, и журнал с тех пор был очищен.', + 'Core:UnknownObjectTip' => 'Объект не удается найти. Возможно, он был удален некоторое время назад, и журнал с тех пор был очищен.', 'Core:UniquenessDefaultError' => 'Ошибка правила уникальности \'%1$s\'', + 'Core:CheckConsistencyError' => 'Consistency rules not followed: %1$s~~', + 'Core:CheckValueError' => 'Unexpected value for attribute \'%1$s\' (%2$s) : %3$s~~', - 'Core:AttributeLinkedSet' => 'Массив объектов (1-n)', + 'Core:AttributeLinkedSet' => 'Массив объектов (1-n)', 'Core:AttributeLinkedSet+' => 'Список объектов заданного класса, указывающих на текущий объект', 'Core:AttributeLinkedSetDuplicatesFound' => 'Duplicates in the \'%1$s\' field : %2$s~~', - 'Core:AttributeDashboard' => 'Дашборд', + 'Core:AttributeDashboard' => 'Дашборд', 'Core:AttributeDashboard+' => '', - 'Core:AttributePhoneNumber' => 'Номер телефона', + 'Core:AttributePhoneNumber' => 'Номер телефона', 'Core:AttributePhoneNumber+' => '', 'Core:AttributeObsolescenceDate' => 'Дата устаревания', @@ -506,9 +508,9 @@ Dict::Add('RU RU', 'Russian', 'Русский', array( 'Class:ActionEmail' => 'Уведомление по email', 'Class:ActionEmail+' => '', 'Class:ActionEmail/Attribute:status+' => 'This status drives who will be notified: just the Test recipient, all (To, cc and Bcc) or no-one~~', - 'Class:ActionEmail/Attribute:status/Value:test+' => 'Only the Test recipient is notified~~', - 'Class:ActionEmail/Attribute:status/Value:enabled+' => 'All To, Cc and Bcc emails are notified~~', - 'Class:ActionEmail/Attribute:status/Value:disabled+' => 'The email notification will not be sent~~', + 'Class:ActionEmail/Attribute:status/Value:test' => 'Only the Test recipient is notified~~', + 'Class:ActionEmail/Attribute:status/Value:enabled' => 'All To, Cc and Bcc emails are notified~~', + 'Class:ActionEmail/Attribute:status/Value:disabled' => 'The email notification will not be sent~~', 'Class:ActionEmail/Attribute:test_recipient' => 'Тестовый получатель', 'Class:ActionEmail/Attribute:test_recipient+' => 'Получатель, если уведомление в статусе "Тест"', 'Class:ActionEmail/Attribute:from' => 'От~~', diff --git a/dictionaries/ru.dictionary.itop.ui.php b/dictionaries/ru.dictionary.itop.ui.php index 84923d4e88..edc0a00ae7 100644 --- a/dictionaries/ru.dictionary.itop.ui.php +++ b/dictionaries/ru.dictionary.itop.ui.php @@ -473,6 +473,8 @@ Dict::Add('RU RU', 'Russian', 'Русский', array( 'UI:Error:MaintenanceTitle' => 'Техническое обслуживание', 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', + 'UI:Error:SMTP:UnknownVendor' => 'OAuth SMTP provider %1$s does not exist (email_transport_smtp.oauth.provider)~~', + 'UI:GroupBy:Count' => 'Количество', 'UI:GroupBy:Count+' => 'Количество элементов', 'UI:CountOfObjects' => '%1$d объектов соответствует критериям.', @@ -1656,31 +1658,31 @@ Dict::Add('RU RU', 'Russian', 'Русский', array( 'Menu:AuditCategories' => 'Категории аудита', 'Menu:AuditCategories+' => 'Категории аудита', 'Menu:Notifications:Title' => 'Категории аудита', - 'Menu:RunQueriesMenu' => 'Выполнение запросов', - 'Menu:RunQueriesMenu+' => 'Выполнение любых запросов', - 'Menu:QueryMenu' => 'Книга запросов', - 'Menu:QueryMenu+' => 'Книга запросов', - 'Menu:UniversalSearchMenu' => 'Универсальный поиск', - 'Menu:UniversalSearchMenu+' => 'Поиск чего угодно...', - 'Menu:UserManagementMenu' => 'Управление пользователями', - 'Menu:UserManagementMenu+' => 'Управление пользователями', - 'Menu:ProfilesMenu' => 'Профили', - 'Menu:ProfilesMenu+' => 'Профили пользователей', - 'Menu:ProfilesMenu:Title' => 'Профили пользователей', - 'Menu:UserAccountsMenu' => 'Учетные записи', - 'Menu:UserAccountsMenu+' => 'Учетные записи пользователей', + 'Menu:RunQueriesMenu' => 'Выполнение запросов', + 'Menu:RunQueriesMenu+' => 'Выполнение любых запросов', + 'Menu:QueryMenu' => 'Книга запросов', + 'Menu:QueryMenu+' => 'Книга запросов', + 'Menu:UniversalSearchMenu' => 'Универсальный поиск', + 'Menu:UniversalSearchMenu+' => 'Поиск чего угодно...', + 'Menu:UserManagementMenu' => 'Управление пользователями', + 'Menu:UserManagementMenu+' => 'Управление пользователями', + 'Menu:ProfilesMenu' => 'Профили', + 'Menu:ProfilesMenu+' => 'Профили пользователей', + 'Menu:ProfilesMenu:Title' => 'Профили пользователей', + 'Menu:UserAccountsMenu' => 'Учетные записи', + 'Menu:UserAccountsMenu+' => 'Учетные записи пользователей', 'Menu:UserAccountsMenu:Title' => 'Учетные записи пользователей', - 'Menu:MyShortcuts' => 'Избранное', - 'Menu:UserManagement' => 'Управление пользователями', - 'Menu:Queries' => 'Запросы OQL', - 'Menu:ConfigurationTools' => 'Конфигурация', + 'Menu:MyShortcuts' => 'Избранное', + 'Menu:UserManagement' => 'Управление пользователями', + 'Menu:Queries' => 'Запросы OQL', + 'Menu:ConfigurationTools' => 'Конфигурация', )); // Additional language entries not present in English dict Dict::Add('RU RU', 'Russian', 'Русский', array( - 'UI:CSVImport:ObjectsReчmainedUnchanged' => '%1$d объект(ов) не изменились.', - 'UI:Toggle:StandardDashboard' => 'Стандартный', - 'UI:Toggle:CustomDashboard' => 'Пользовательский', - 'UI:Dashboard:Edit' => 'Редактировать дашборд...', - 'UI:Dashboard:Revert' => 'Вернуть стандартную версию...', + 'UI:CSVImport:ObjectsReчmainedUnchanged' => '%1$d объект(ов) не изменились.', + 'UI:Toggle:StandardDashboard' => 'Стандартный', + 'UI:Toggle:CustomDashboard' => 'Пользовательский', + 'UI:Dashboard:Edit' => 'Редактировать дашборд...', + 'UI:Dashboard:Revert' => 'Вернуть стандартную версию...', )); diff --git a/dictionaries/sk.dictionary.itop.core.php b/dictionaries/sk.dictionary.itop.core.php index 1d77370bf5..9c21dac90f 100644 --- a/dictionaries/sk.dictionary.itop.core.php +++ b/dictionaries/sk.dictionary.itop.core.php @@ -20,22 +20,24 @@ */ Dict::Add('SK SK', 'Slovak', 'Slovenčina', array( 'Core:DeletedObjectLabel' => '%1s (odstránené)', - 'Core:DeletedObjectTip' => 'Objekt bol odstránený o %1$s (%2$s)', + 'Core:DeletedObjectTip' => 'Objekt bol odstránený o %1$s (%2$s)', 'Core:UnknownObjectLabel' => 'Objekt nebol nájdený (trieda: %1$s, id: %2$d)', - 'Core:UnknownObjectTip' => 'Objekt nebol nájdený. Mohol byť odstránený pred nejakým časom a záznam bol odvtedy nenávratne zmazaný.', + 'Core:UnknownObjectTip' => 'Objekt nebol nájdený. Mohol byť odstránený pred nejakým časom a záznam bol odvtedy nenávratne zmazaný.', 'Core:UniquenessDefaultError' => 'Uniqueness rule \'%1$s\' in error~~', + 'Core:CheckConsistencyError' => 'Consistency rules not followed: %1$s~~', + 'Core:CheckValueError' => 'Unexpected value for attribute \'%1$s\' (%2$s) : %3$s~~', - 'Core:AttributeLinkedSet' => 'Pole objektov', + 'Core:AttributeLinkedSet' => 'Pole objektov', 'Core:AttributeLinkedSet+' => '', 'Core:AttributeLinkedSetDuplicatesFound' => 'Duplicates in the \'%1$s\' field : %2$s~~', - 'Core:AttributeDashboard' => 'Dashboard~~', + 'Core:AttributeDashboard' => 'Dashboard~~', 'Core:AttributeDashboard+' => '', - 'Core:AttributePhoneNumber' => 'Phone number~~', + 'Core:AttributePhoneNumber' => 'Phone number~~', 'Core:AttributePhoneNumber+' => '', 'Core:AttributeObsolescenceDate' => 'Obsolescence date~~', @@ -516,9 +518,9 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', array( 'Class:ActionEmail' => 'Emailová notifikácia', 'Class:ActionEmail+' => '', 'Class:ActionEmail/Attribute:status+' => 'This status drives who will be notified: just the Test recipient, all (To, cc and Bcc) or no-one~~', - 'Class:ActionEmail/Attribute:status/Value:test+' => 'Only the Test recipient is notified~~', - 'Class:ActionEmail/Attribute:status/Value:enabled+' => 'All To, Cc and Bcc emails are notified~~', - 'Class:ActionEmail/Attribute:status/Value:disabled+' => 'The email notification will not be sent~~', + 'Class:ActionEmail/Attribute:status/Value:test' => 'Only the Test recipient is notified~~', + 'Class:ActionEmail/Attribute:status/Value:enabled' => 'All To, Cc and Bcc emails are notified~~', + 'Class:ActionEmail/Attribute:status/Value:disabled' => 'The email notification will not be sent~~', 'Class:ActionEmail/Attribute:test_recipient' => 'Testovací príjemca', 'Class:ActionEmail/Attribute:test_recipient+' => '', 'Class:ActionEmail/Attribute:from' => 'Od~~', diff --git a/dictionaries/sk.dictionary.itop.ui.php b/dictionaries/sk.dictionary.itop.ui.php index 95bb4e6d3d..d1a1960ca1 100644 --- a/dictionaries/sk.dictionary.itop.ui.php +++ b/dictionaries/sk.dictionary.itop.ui.php @@ -462,6 +462,8 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Error:MaintenanceTitle' => 'Maintenance~~', 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', + 'UI:Error:SMTP:UnknownVendor' => 'OAuth SMTP provider %1$s does not exist (email_transport_smtp.oauth.provider)~~', + 'UI:GroupBy:Count' => 'Počet', 'UI:GroupBy:Count+' => '', 'UI:CountOfObjects' => '%1$d objekt/y/ov sa nezhoduje s kritériami.', @@ -1648,30 +1650,30 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', array( 'Menu:AuditCategories' => 'Kategórie auditu', 'Menu:AuditCategories+' => '', 'Menu:Notifications:Title' => 'Kategórie auditu', - 'Menu:RunQueriesMenu' => 'Spustiť dopyty', - 'Menu:RunQueriesMenu+' => '', - 'Menu:QueryMenu' => 'Dopyt frázy', - 'Menu:QueryMenu+' => '', - 'Menu:UniversalSearchMenu' => 'Univerzálne vyhľadávanie', - 'Menu:UniversalSearchMenu+' => '', - 'Menu:UserManagementMenu' => 'Užívateľský manažment', - 'Menu:UserManagementMenu+' => '', - 'Menu:ProfilesMenu' => 'Profily', - 'Menu:ProfilesMenu+' => '', - 'Menu:ProfilesMenu:Title' => 'Profily', - 'Menu:UserAccountsMenu' => 'Užívateľské účty', - 'Menu:UserAccountsMenu+' => '', + 'Menu:RunQueriesMenu' => 'Spustiť dopyty', + 'Menu:RunQueriesMenu+' => '', + 'Menu:QueryMenu' => 'Dopyt frázy', + 'Menu:QueryMenu+' => '', + 'Menu:UniversalSearchMenu' => 'Univerzálne vyhľadávanie', + 'Menu:UniversalSearchMenu+' => '', + 'Menu:UserManagementMenu' => 'Užívateľský manažment', + 'Menu:UserManagementMenu+' => '', + 'Menu:ProfilesMenu' => 'Profily', + 'Menu:ProfilesMenu+' => '', + 'Menu:ProfilesMenu:Title' => 'Profily', + 'Menu:UserAccountsMenu' => 'Užívateľské účty', + 'Menu:UserAccountsMenu+' => '', 'Menu:UserAccountsMenu:Title' => 'Užívateľské účty', - 'Menu:MyShortcuts' => 'Moje skratky', - 'Menu:UserManagement' => 'User Management~~', - 'Menu:Queries' => 'Queries~~', - 'Menu:ConfigurationTools' => 'Configuration~~', + 'Menu:MyShortcuts' => 'Moje skratky', + 'Menu:UserManagement' => 'User Management~~', + 'Menu:Queries' => 'Queries~~', + 'Menu:ConfigurationTools' => 'Configuration~~', )); // Additional language entries not present in English dict Dict::Add('SK SK', 'Slovak', 'Slovenčina', array( - 'UI:Toggle:StandardDashboard' => 'Standard~~', - 'UI:Toggle:CustomDashboard' => 'Custom~~', - 'UI:Dashboard:Edit' => 'Upraviť túto stránku...', - 'UI:Dashboard:Revert' => 'Vrátiť sa do originálnej verzie...', + 'UI:Toggle:StandardDashboard' => 'Standard~~', + 'UI:Toggle:CustomDashboard' => 'Custom~~', + 'UI:Dashboard:Edit' => 'Upraviť túto stránku...', + 'UI:Dashboard:Revert' => 'Vrátiť sa do originálnej verzie...', )); diff --git a/dictionaries/tr.dictionary.itop.core.php b/dictionaries/tr.dictionary.itop.core.php index f3ba7eec25..f2d37fea29 100644 --- a/dictionaries/tr.dictionary.itop.core.php +++ b/dictionaries/tr.dictionary.itop.core.php @@ -31,22 +31,24 @@ // Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'Core:DeletedObjectLabel' => '%1s (Silinmiş)', - 'Core:DeletedObjectTip' => 'Nesne%1$s (%2$s) \'de silinmiştir', + 'Core:DeletedObjectTip' => 'Nesne%1$s (%2$s) \'de silinmiştir', 'Core:UnknownObjectLabel' => 'Nesne bulunamadı (sınıf: %1$s, id: %2$d)', - 'Core:UnknownObjectTip' => 'Nesne bulunamadı.Nesne ve günlük kaydı bir süre önce silinmiş olabilir', + 'Core:UnknownObjectTip' => 'Nesne bulunamadı.Nesne ve günlük kaydı bir süre önce silinmiş olabilir', 'Core:UniquenessDefaultError' => 'Uniqueness rule \'%1$s\' in error~~', + 'Core:CheckConsistencyError' => 'Consistency rules not followed: %1$s~~', + 'Core:CheckValueError' => 'Unexpected value for attribute \'%1$s\' (%2$s) : %3$s~~', - 'Core:AttributeLinkedSet' => 'Nesnelerin dizisi', + 'Core:AttributeLinkedSet' => 'Nesnelerin dizisi', 'Core:AttributeLinkedSet+' => 'Aynı sınıf veya alt sınıfın her türlü nesnesi', 'Core:AttributeLinkedSetDuplicatesFound' => 'Duplicates in the \'%1$s\' field : %2$s~~', - 'Core:AttributeDashboard' => 'Dashboard~~', + 'Core:AttributeDashboard' => 'Dashboard~~', 'Core:AttributeDashboard+' => '', - 'Core:AttributePhoneNumber' => 'Phone number~~', + 'Core:AttributePhoneNumber' => 'Phone number~~', 'Core:AttributePhoneNumber+' => '', 'Core:AttributeObsolescenceDate' => 'Obsolescence date~~', @@ -527,9 +529,9 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'Class:ActionEmail' => 'E-posta bildirimi', 'Class:ActionEmail+' => '', 'Class:ActionEmail/Attribute:status+' => 'This status drives who will be notified: just the Test recipient, all (To, cc and Bcc) or no-one~~', - 'Class:ActionEmail/Attribute:status/Value:test+' => 'Only the Test recipient is notified~~', - 'Class:ActionEmail/Attribute:status/Value:enabled+' => 'All To, Cc and Bcc emails are notified~~', - 'Class:ActionEmail/Attribute:status/Value:disabled+' => 'The email notification will not be sent~~', + 'Class:ActionEmail/Attribute:status/Value:test' => 'Only the Test recipient is notified~~', + 'Class:ActionEmail/Attribute:status/Value:enabled' => 'All To, Cc and Bcc emails are notified~~', + 'Class:ActionEmail/Attribute:status/Value:disabled' => 'The email notification will not be sent~~', 'Class:ActionEmail/Attribute:test_recipient' => 'Test alıcısı', 'Class:ActionEmail/Attribute:test_recipient+' => 'Durumu "Test" olması durumundaki alıcı', 'Class:ActionEmail/Attribute:from' => 'Kimden~~', diff --git a/dictionaries/tr.dictionary.itop.ui.php b/dictionaries/tr.dictionary.itop.ui.php index 574774bb88..7f1b7264c5 100644 --- a/dictionaries/tr.dictionary.itop.ui.php +++ b/dictionaries/tr.dictionary.itop.ui.php @@ -472,6 +472,8 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Error:MaintenanceTitle' => 'Maintenance~~', 'UI:Error:InvalidToken' => 'Error: the requested operation has already been performed (CSRF token not found)~~', + 'UI:Error:SMTP:UnknownVendor' => 'OAuth SMTP provider %1$s does not exist (email_transport_smtp.oauth.provider)~~', + 'UI:GroupBy:Count' => 'Say', 'UI:GroupBy:Count+' => 'Eleman sayısı', 'UI:CountOfObjects' => 'Kritere uyan %1$d nesne bulundu.', @@ -1694,31 +1696,31 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array( 'Menu:AuditCategories' => 'Denetleme Kategorileri', 'Menu:AuditCategories+' => 'Denetleme Kategorileri', 'Menu:Notifications:Title' => 'Denetleme Kategorileri', - 'Menu:RunQueriesMenu' => 'Sorgu çalıştır', - 'Menu:RunQueriesMenu+' => 'Sorgu çalıştır', - 'Menu:QueryMenu' => 'Query phrasebook~~', - 'Menu:QueryMenu+' => 'Query phrasebook~~', - 'Menu:UniversalSearchMenu' => 'Genel sorgu', - 'Menu:UniversalSearchMenu+' => 'Herhangi bir arama...', - 'Menu:UserManagementMenu' => 'Kullanıcı Yönetimi', - 'Menu:UserManagementMenu+' => 'Kullanıcı Yönetimi', - 'Menu:ProfilesMenu' => 'Profiller', - 'Menu:ProfilesMenu+' => 'Profiller', - 'Menu:ProfilesMenu:Title' => 'Profiller', - 'Menu:UserAccountsMenu' => 'Kullanıcı Hesapları', - 'Menu:UserAccountsMenu+' => 'Kullanıcı Hesapları', + 'Menu:RunQueriesMenu' => 'Sorgu çalıştır', + 'Menu:RunQueriesMenu+' => 'Sorgu çalıştır', + 'Menu:QueryMenu' => 'Query phrasebook~~', + 'Menu:QueryMenu+' => 'Query phrasebook~~', + 'Menu:UniversalSearchMenu' => 'Genel sorgu', + 'Menu:UniversalSearchMenu+' => 'Herhangi bir arama...', + 'Menu:UserManagementMenu' => 'Kullanıcı Yönetimi', + 'Menu:UserManagementMenu+' => 'Kullanıcı Yönetimi', + 'Menu:ProfilesMenu' => 'Profiller', + 'Menu:ProfilesMenu+' => 'Profiller', + 'Menu:ProfilesMenu:Title' => 'Profiller', + 'Menu:UserAccountsMenu' => 'Kullanıcı Hesapları', + 'Menu:UserAccountsMenu+' => 'Kullanıcı Hesapları', 'Menu:UserAccountsMenu:Title' => 'Kullanıcı Hesapları', - 'Menu:MyShortcuts' => 'My Shortcuts~~', - 'Menu:UserManagement' => 'User Management~~', - 'Menu:Queries' => 'Queries~~', - 'Menu:ConfigurationTools' => 'Configuration~~', + 'Menu:MyShortcuts' => 'My Shortcuts~~', + 'Menu:UserManagement' => 'User Management~~', + 'Menu:Queries' => 'Queries~~', + 'Menu:ConfigurationTools' => 'Configuration~~', )); // Additional language entries not present in English dict Dict::Add('TR TR', 'Turkish', 'Türkçe', array( - 'UI:Toggle:StandardDashboard' => 'Standard~~', - 'UI:Toggle:CustomDashboard' => 'Custom~~', - 'UI:Display_X_ItemsPerPage' => 'Display %1$s items per page~~', - 'UI:Dashboard:Edit' => 'Edit This Page...~~', - 'UI:Dashboard:Revert' => 'Revert To Original Version...~~', + 'UI:Toggle:StandardDashboard' => 'Standard~~', + 'UI:Toggle:CustomDashboard' => 'Custom~~', + 'UI:Display_X_ItemsPerPage' => 'Display %1$s items per page~~', + 'UI:Dashboard:Edit' => 'Edit This Page...~~', + 'UI:Dashboard:Revert' => 'Revert To Original Version...~~', )); diff --git a/dictionaries/ui/components/breadcrumbs/de.dictionary.itop.breadcrumbs.php b/dictionaries/ui/components/breadcrumbs/de.dictionary.itop.breadcrumbs.php index 0d8bbb4307..475fb622e5 100644 --- a/dictionaries/ui/components/breadcrumbs/de.dictionary.itop.breadcrumbs.php +++ b/dictionaries/ui/components/breadcrumbs/de.dictionary.itop.breadcrumbs.php @@ -19,5 +19,5 @@ // Global search Dict::Add('DE DE', 'German', 'Deutsch', array( - 'UI:Component:Breadcrumbs:PreviousItemsListToggler:Label' => 'Previous pages~~', -)); \ No newline at end of file + 'UI:Component:Breadcrumbs:PreviousItemsListToggler:Label' => 'Vorherige Seiten', +)); diff --git a/dictionaries/ui/components/breadcrumbs/es_cr.dictionary.itop.breadcrumbs.php b/dictionaries/ui/components/breadcrumbs/es_cr.dictionary.itop.breadcrumbs.php index 9263109b1e..560cc049a0 100644 --- a/dictionaries/ui/components/breadcrumbs/es_cr.dictionary.itop.breadcrumbs.php +++ b/dictionaries/ui/components/breadcrumbs/es_cr.dictionary.itop.breadcrumbs.php @@ -19,5 +19,5 @@ // Global search Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'UI:Component:Breadcrumbs:PreviousItemsListToggler:Label' => 'Previous pages~~', + 'UI:Component:Breadcrumbs:PreviousItemsListToggler:Label' => 'Páginas anteriores', )); \ No newline at end of file diff --git a/dictionaries/ui/components/breadcrumbs/nl.dictionary.itop.breadcrumbs.php b/dictionaries/ui/components/breadcrumbs/nl.dictionary.itop.breadcrumbs.php index 0ad7cb4292..6d9e3b8fc2 100644 --- a/dictionaries/ui/components/breadcrumbs/nl.dictionary.itop.breadcrumbs.php +++ b/dictionaries/ui/components/breadcrumbs/nl.dictionary.itop.breadcrumbs.php @@ -19,5 +19,5 @@ // Global search Dict::Add('NL NL', 'Dutch', 'Nederlands', array( - 'UI:Component:Breadcrumbs:PreviousItemsListToggler:Label' => 'Previous pages~~', + 'UI:Component:Breadcrumbs:PreviousItemsListToggler:Label' => 'Vorige pagina\'s', )); \ No newline at end of file diff --git a/dictionaries/ui/components/breadcrumbs/pl.dictionary.itop.breadcrumbs.php b/dictionaries/ui/components/breadcrumbs/pl.dictionary.itop.breadcrumbs.php new file mode 100644 index 0000000000..a8ac7a3c99 --- /dev/null +++ b/dictionaries/ui/components/breadcrumbs/pl.dictionary.itop.breadcrumbs.php @@ -0,0 +1,22 @@ + 'Previous pages~~', +)); \ No newline at end of file diff --git a/dictionaries/ui/components/breadcrumbs/pt_br.dictionary.itop.breadcrumbs.php b/dictionaries/ui/components/breadcrumbs/pt_br.dictionary.itop.breadcrumbs.php index 1a90def38e..2cde139d45 100644 --- a/dictionaries/ui/components/breadcrumbs/pt_br.dictionary.itop.breadcrumbs.php +++ b/dictionaries/ui/components/breadcrumbs/pt_br.dictionary.itop.breadcrumbs.php @@ -19,5 +19,5 @@ // Global search Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'UI:Component:Breadcrumbs:PreviousItemsListToggler:Label' => 'Previous pages~~', + 'UI:Component:Breadcrumbs:PreviousItemsListToggler:Label' => 'Páginas anteriores', )); \ No newline at end of file diff --git a/dictionaries/ui/components/datatable/de.dictionary.itop.datatable.php b/dictionaries/ui/components/datatable/de.dictionary.itop.datatable.php index 40d0b4b97f..ac1b8e1020 100644 --- a/dictionaries/ui/components/datatable/de.dictionary.itop.datatable.php +++ b/dictionaries/ui/components/datatable/de.dictionary.itop.datatable.php @@ -24,8 +24,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'UI:Datatables:Language:Info' => '_TOTAL_ Objekte', 'UI:Datatables:Language:InfoEmpty' => 'Keine Information', 'UI:Datatables:Language:EmptyTable' => 'Keine Daten in dieser Tabelle verfügbar', - 'UI:Datatables:Language:Error' => 'An error occured while running the query~~', + 'UI:Datatables:Language:Error' => 'Bei der Ausführung des Query ist ein Fehler aufgetreten', 'UI:Datatables:Language:DisplayLength:All' => 'Alle', 'UI:Datatables:Language:Sort:Ascending' => 'Aktivieren für aufsteigende Sortierung', 'UI:Datatables:Language:Sort:Descending' => 'Aktivieren für absteigende Sortierung', -)); \ No newline at end of file +)); diff --git a/dictionaries/ui/components/datatable/es_cr.dictionary.itop.datatable.php b/dictionaries/ui/components/datatable/es_cr.dictionary.itop.datatable.php index da3dbcd6dd..89fc2771c2 100644 --- a/dictionaries/ui/components/datatable/es_cr.dictionary.itop.datatable.php +++ b/dictionaries/ui/components/datatable/es_cr.dictionary.itop.datatable.php @@ -18,14 +18,14 @@ */ // Display DataTable Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'UI:Datatables:Language:Processing' => 'Please wait...~~', - 'UI:Datatables:Language:LengthMenu' => '_MENU_ per page~~', - 'UI:Datatables:Language:ZeroRecords' => 'No result~~', - 'UI:Datatables:Language:Info' => '_TOTAL_ item(s)~~', - 'UI:Datatables:Language:InfoEmpty' => 'No information~~', - 'UI:Datatables:Language:EmptyTable' => 'No data available in this table~~', - 'UI:Datatables:Language:Error' => 'An error occured while running the query~~', - 'UI:Datatables:Language:DisplayLength:All' => 'All~~', - 'UI:Datatables:Language:Sort:Ascending' => 'enable for an ascending sort~~', - 'UI:Datatables:Language:Sort:Descending' => 'enable for a descending sort~~', + 'UI:Datatables:Language:Processing' => 'Por favor espere...', + 'UI:Datatables:Language:LengthMenu' => '_MENU_ por página', + 'UI:Datatables:Language:ZeroRecords' => 'Sin Resultados', + 'UI:Datatables:Language:Info' => '_TOTAL_ elemento(s)', + 'UI:Datatables:Language:InfoEmpty' => 'Sin información', + 'UI:Datatables:Language:EmptyTable' => 'Sin datos disponibles en esta tabla', + 'UI:Datatables:Language:Error' => 'Un error ocurrió mientras se ejecutaba la consulta', + 'UI:Datatables:Language:DisplayLength:All' => 'Todos', + 'UI:Datatables:Language:Sort:Ascending' => 'habilitar ordenamiento ascendente', + 'UI:Datatables:Language:Sort:Descending' => 'habilitar ordenamiento descendente', )); \ No newline at end of file diff --git a/dictionaries/ui/components/datatable/nl.dictionary.itop.datatable.php b/dictionaries/ui/components/datatable/nl.dictionary.itop.datatable.php index 49537998cf..d4cf500678 100644 --- a/dictionaries/ui/components/datatable/nl.dictionary.itop.datatable.php +++ b/dictionaries/ui/components/datatable/nl.dictionary.itop.datatable.php @@ -24,7 +24,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'UI:Datatables:Language:Info' => '_TOTAL_ item(s)', 'UI:Datatables:Language:InfoEmpty' => 'Geen informatie', 'UI:Datatables:Language:EmptyTable' => 'Geen data in deze tabel', - 'UI:Datatables:Language:Error' => 'An error occured while running the query~~', + 'UI:Datatables:Language:Error' => 'Er was een fout bij het uitvoeren van de query.', 'UI:Datatables:Language:DisplayLength:All' => 'Alles', 'UI:Datatables:Language:Sort:Ascending' => 'Oplopend sorteren', 'UI:Datatables:Language:Sort:Descending' => 'Aflopend sorteren', diff --git a/dictionaries/ui/components/datatable/pl.dictionary.itop.datatable.php b/dictionaries/ui/components/datatable/pl.dictionary.itop.datatable.php new file mode 100644 index 0000000000..deaa8ba88a --- /dev/null +++ b/dictionaries/ui/components/datatable/pl.dictionary.itop.datatable.php @@ -0,0 +1,31 @@ + 'Please wait...~~', + 'UI:Datatables:Language:LengthMenu' => '_MENU_ per page~~', + 'UI:Datatables:Language:ZeroRecords' => 'No result~~', + 'UI:Datatables:Language:Info' => '_TOTAL_ item(s)~~', + 'UI:Datatables:Language:InfoEmpty' => 'No information~~', + 'UI:Datatables:Language:EmptyTable' => 'No data available in this table~~', + 'UI:Datatables:Language:Error' => 'An error occured while running the query~~', + 'UI:Datatables:Language:DisplayLength:All' => 'All~~', + 'UI:Datatables:Language:Sort:Ascending' => 'enable for an ascending sort~~', + 'UI:Datatables:Language:Sort:Descending' => 'enable for a descending sort~~', +)); \ No newline at end of file diff --git a/dictionaries/ui/components/datatable/pt_br.dictionary.itop.datatable.php b/dictionaries/ui/components/datatable/pt_br.dictionary.itop.datatable.php index 807e52b2a7..93309afc50 100644 --- a/dictionaries/ui/components/datatable/pt_br.dictionary.itop.datatable.php +++ b/dictionaries/ui/components/datatable/pt_br.dictionary.itop.datatable.php @@ -18,14 +18,14 @@ */ // Display DataTable Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'UI:Datatables:Language:Processing' => 'Please wait...~~', - 'UI:Datatables:Language:LengthMenu' => '_MENU_ per page~~', - 'UI:Datatables:Language:ZeroRecords' => 'No result~~', - 'UI:Datatables:Language:Info' => '_TOTAL_ item(s)~~', - 'UI:Datatables:Language:InfoEmpty' => 'No information~~', - 'UI:Datatables:Language:EmptyTable' => 'No data available in this table~~', - 'UI:Datatables:Language:Error' => 'An error occured while running the query~~', - 'UI:Datatables:Language:DisplayLength:All' => 'All~~', - 'UI:Datatables:Language:Sort:Ascending' => 'enable for an ascending sort~~', - 'UI:Datatables:Language:Sort:Descending' => 'enable for a descending sort~~', + 'UI:Datatables:Language:Processing' => 'Aguarde...', + 'UI:Datatables:Language:LengthMenu' => '_MENU_ por página', + 'UI:Datatables:Language:ZeroRecords' => 'Nenhum resultado', + 'UI:Datatables:Language:Info' => '_TOTAL_ item(ns)', + 'UI:Datatables:Language:InfoEmpty' => 'Nenhuma informação', + 'UI:Datatables:Language:EmptyTable' => 'Não há dados disponíveis nesta tabela', + 'UI:Datatables:Language:Error' => 'Ocorreu um erro ao executar a consulta', + 'UI:Datatables:Language:DisplayLength:All' => 'Tudo', + 'UI:Datatables:Language:Sort:Ascending' => 'Ordem ascendente', + 'UI:Datatables:Language:Sort:Descending' => 'Ordem descendente', )); \ No newline at end of file diff --git a/dictionaries/ui/components/datatable/zh_cn.dictionary.itop.datatable.php b/dictionaries/ui/components/datatable/zh_cn.dictionary.itop.datatable.php index 0328dd3299..48b7147595 100644 --- a/dictionaries/ui/components/datatable/zh_cn.dictionary.itop.datatable.php +++ b/dictionaries/ui/components/datatable/zh_cn.dictionary.itop.datatable.php @@ -24,8 +24,8 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'UI:Datatables:Language:Info' => '_TOTAL_ 项', 'UI:Datatables:Language:InfoEmpty' => '未找到相关信息', 'UI:Datatables:Language:EmptyTable' => '表格中暂无数据', - 'UI:Datatables:Language:Error' => 'An error occured while running the query~~', + 'UI:Datatables:Language:Error' => '运行查询时出错', 'UI:Datatables:Language:DisplayLength:All' => '全部', - 'UI:Datatables:Language:Sort:Ascending' => '顺序排序', - 'UI:Datatables:Language:Sort:Descending' => '倒序排序', + 'UI:Datatables:Language:Sort:Ascending' => '采用顺序排序', + 'UI:Datatables:Language:Sort:Descending' => '采用倒序排序', )); \ No newline at end of file diff --git a/dictionaries/ui/components/field/es_cr.dictionary.itop.field.php b/dictionaries/ui/components/field/es_cr.dictionary.itop.field.php index f2fc3dbf20..567fea6d67 100644 --- a/dictionaries/ui/components/field/es_cr.dictionary.itop.field.php +++ b/dictionaries/ui/components/field/es_cr.dictionary.itop.field.php @@ -18,5 +18,5 @@ */ // Global search Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'UI:Component:Field:BulkModify:UnknownValues:Tooltip' => 'Unknown values~~', + 'UI:Component:Field:BulkModify:UnknownValues:Tooltip' => 'Valores desconocidos', )); \ No newline at end of file diff --git a/dictionaries/ui/components/field/pl.dictionary.itop.field.php b/dictionaries/ui/components/field/pl.dictionary.itop.field.php new file mode 100644 index 0000000000..b73cbad0d8 --- /dev/null +++ b/dictionaries/ui/components/field/pl.dictionary.itop.field.php @@ -0,0 +1,22 @@ + 'Unknown values~~', +)); \ No newline at end of file diff --git a/dictionaries/ui/components/field/pt_br.dictionary.itop.field.php b/dictionaries/ui/components/field/pt_br.dictionary.itop.field.php index 7742d65e6c..5c38ca5ca3 100644 --- a/dictionaries/ui/components/field/pt_br.dictionary.itop.field.php +++ b/dictionaries/ui/components/field/pt_br.dictionary.itop.field.php @@ -18,5 +18,5 @@ */ // Global search Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'UI:Component:Field:BulkModify:UnknownValues:Tooltip' => 'Unknown values~~', + 'UI:Component:Field:BulkModify:UnknownValues:Tooltip' => 'Valores desconhecidos', )); \ No newline at end of file diff --git a/dictionaries/ui/components/global-search/es_cr.dictionary.itop.global-search.php b/dictionaries/ui/components/global-search/es_cr.dictionary.itop.global-search.php index 131ceb69e0..83066ee374 100644 --- a/dictionaries/ui/components/global-search/es_cr.dictionary.itop.global-search.php +++ b/dictionaries/ui/components/global-search/es_cr.dictionary.itop.global-search.php @@ -18,10 +18,10 @@ */ // Global search Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'UI:Component:GlobalSearch:Tooltip' => 'Search throughout the whole application~~', - 'UI:Component:GlobalSearch:Input:Placeholder' => 'Search...~~', - 'UI:Component:GlobalSearch:Recents:Title' => 'Recents~~', - 'UI:Component:GlobalSearch:LastQueries:NoQuery:Placeholder' => 'You haven\'t run any search yet~~', - 'UI:Component:GlobalSearch:HistoryDisabled' => 'History is disabled~~', - 'UI:Component:GlobalSearch:KeyboardShortcut:OpenDrawer' => 'Open global search~~', + 'UI:Component:GlobalSearch:Tooltip' => 'Buscar en toda la aplicación', + 'UI:Component:GlobalSearch:Input:Placeholder' => 'Búsqueda...', + 'UI:Component:GlobalSearch:Recents:Title' => 'Recientes', + 'UI:Component:GlobalSearch:LastQueries:NoQuery:Placeholder' => 'Aún no ha realizado ninguna búsqueda', + 'UI:Component:GlobalSearch:HistoryDisabled' => 'El historial está deshabilitado', + 'UI:Component:GlobalSearch:KeyboardShortcut:OpenDrawer' => 'Abrir búsqueda global', )); \ No newline at end of file diff --git a/dictionaries/ui/components/global-search/pl.dictionary.itop.global-search.php b/dictionaries/ui/components/global-search/pl.dictionary.itop.global-search.php new file mode 100644 index 0000000000..b817eea87b --- /dev/null +++ b/dictionaries/ui/components/global-search/pl.dictionary.itop.global-search.php @@ -0,0 +1,27 @@ + 'Search throughout the whole application~~', + 'UI:Component:GlobalSearch:Input:Placeholder' => 'Search...~~', + 'UI:Component:GlobalSearch:Recents:Title' => 'Recents~~', + 'UI:Component:GlobalSearch:LastQueries:NoQuery:Placeholder' => 'You haven\'t run any search yet~~', + 'UI:Component:GlobalSearch:HistoryDisabled' => 'History is disabled~~', + 'UI:Component:GlobalSearch:KeyboardShortcut:OpenDrawer' => 'Open global search~~', +)); \ No newline at end of file diff --git a/dictionaries/ui/components/global-search/pt_br.dictionary.itop.global-search.php b/dictionaries/ui/components/global-search/pt_br.dictionary.itop.global-search.php index cb75676ebb..5cf2a92220 100644 --- a/dictionaries/ui/components/global-search/pt_br.dictionary.itop.global-search.php +++ b/dictionaries/ui/components/global-search/pt_br.dictionary.itop.global-search.php @@ -18,10 +18,10 @@ */ // Global search Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'UI:Component:GlobalSearch:Tooltip' => 'Search throughout the whole application~~', - 'UI:Component:GlobalSearch:Input:Placeholder' => 'Search...~~', - 'UI:Component:GlobalSearch:Recents:Title' => 'Recents~~', - 'UI:Component:GlobalSearch:LastQueries:NoQuery:Placeholder' => 'You haven\'t run any search yet~~', - 'UI:Component:GlobalSearch:HistoryDisabled' => 'History is disabled~~', - 'UI:Component:GlobalSearch:KeyboardShortcut:OpenDrawer' => 'Open global search~~', + 'UI:Component:GlobalSearch:Tooltip' => 'Pesquisar em todo o '.ITOP_APPLICATION_SHORT, + 'UI:Component:GlobalSearch:Input:Placeholder' => 'Pesquisar...', + 'UI:Component:GlobalSearch:Recents:Title' => 'Recentes', + 'UI:Component:GlobalSearch:LastQueries:NoQuery:Placeholder' => 'Você ainda não realizou nenhuma pesquisa', + 'UI:Component:GlobalSearch:HistoryDisabled' => 'O histórico está desativado', + 'UI:Component:GlobalSearch:KeyboardShortcut:OpenDrawer' => 'Abrir pesquisa universal', )); \ No newline at end of file diff --git a/dictionaries/ui/components/input/de.dictionary.itop.input.php b/dictionaries/ui/components/input/de.dictionary.itop.input.php index a0fff6b5ca..c13e4b9a50 100644 --- a/dictionaries/ui/components/input/de.dictionary.itop.input.php +++ b/dictionaries/ui/components/input/de.dictionary.itop.input.php @@ -19,5 +19,5 @@ // Input Dict::Add('DE DE', 'German', 'Deutsch', array( - 'UI:Component:Input:Password:DoesNotMatch' => 'Passwords do not match~~', -)); \ No newline at end of file + 'UI:Component:Input:Password:DoesNotMatch' => 'Passwörter stimmen nicht überein', +)); diff --git a/dictionaries/ui/components/input/es_cr.dictionary.itop.input.php b/dictionaries/ui/components/input/es_cr.dictionary.itop.input.php index c07f1311a0..6c0923a54f 100644 --- a/dictionaries/ui/components/input/es_cr.dictionary.itop.input.php +++ b/dictionaries/ui/components/input/es_cr.dictionary.itop.input.php @@ -19,5 +19,5 @@ // Input Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'UI:Component:Input:Password:DoesNotMatch' => 'Passwords do not match~~', + 'UI:Component:Input:Password:DoesNotMatch' => 'No coincide la contraseña', )); \ No newline at end of file diff --git a/dictionaries/ui/components/input/nl.dictionary.itop.input.php b/dictionaries/ui/components/input/nl.dictionary.itop.input.php index 7947008cc7..4447f12a0f 100644 --- a/dictionaries/ui/components/input/nl.dictionary.itop.input.php +++ b/dictionaries/ui/components/input/nl.dictionary.itop.input.php @@ -19,5 +19,5 @@ // Input Dict::Add('NL NL', 'Dutch', 'Nederlands', array( - 'UI:Component:Input:Password:DoesNotMatch' => 'Passwords do not match~~', + 'UI:Component:Input:Password:DoesNotMatch' => 'Wachtwoorden komen niet overeen', )); \ No newline at end of file diff --git a/dictionaries/ui/components/input/pl.dictionary.itop.input.php b/dictionaries/ui/components/input/pl.dictionary.itop.input.php new file mode 100644 index 0000000000..d57c16c62c --- /dev/null +++ b/dictionaries/ui/components/input/pl.dictionary.itop.input.php @@ -0,0 +1,22 @@ + 'Passwords do not match~~', +)); \ No newline at end of file diff --git a/dictionaries/ui/components/input/pt_br.dictionary.itop.input.php b/dictionaries/ui/components/input/pt_br.dictionary.itop.input.php index 813bfdd66c..675f510885 100644 --- a/dictionaries/ui/components/input/pt_br.dictionary.itop.input.php +++ b/dictionaries/ui/components/input/pt_br.dictionary.itop.input.php @@ -19,5 +19,5 @@ // Input Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'UI:Component:Input:Password:DoesNotMatch' => 'Passwords do not match~~', + 'UI:Component:Input:Password:DoesNotMatch' => 'Senhas não correspondem', )); \ No newline at end of file diff --git a/dictionaries/ui/components/quick-create/es_cr.dictionary.itop.quick-create.php b/dictionaries/ui/components/quick-create/es_cr.dictionary.itop.quick-create.php index a44c57849f..db2058abab 100644 --- a/dictionaries/ui/components/quick-create/es_cr.dictionary.itop.quick-create.php +++ b/dictionaries/ui/components/quick-create/es_cr.dictionary.itop.quick-create.php @@ -18,10 +18,10 @@ */ // Quick create Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'UI:Component:QuickCreate:Tooltip' => 'Quickly create any type of object~~', - 'UI:Component:QuickCreate:Input:Placeholder' => 'Select object type...~~', - 'UI:Component:QuickCreate:Recents:Title' => 'Recents~~', - 'UI:Component:QuickCreate:LastClasses:NoClass:Placeholder' => 'You haven\'t create any object yet~~', - 'UI:Component:QuickCreate:HistoryDisabled' => 'History is disabled~~', - 'UI:Component:QuickCreate:KeyboardShortcut:OpenDrawer' => 'Open quick create~~', + 'UI:Component:QuickCreate:Tooltip' => 'Crear rápidamente cualquier tipo de objeto', + 'UI:Component:QuickCreate:Input:Placeholder' => 'Seleccionar tipo de objeto...', + 'UI:Component:QuickCreate:Recents:Title' => 'Recientes', + 'UI:Component:QuickCreate:LastClasses:NoClass:Placeholder' => 'Aún no has creado ningún objeto.', + 'UI:Component:QuickCreate:HistoryDisabled' => 'El historial está deshabilitado', + 'UI:Component:QuickCreate:KeyboardShortcut:OpenDrawer' => 'Abrir creación rápida', )); \ No newline at end of file diff --git a/dictionaries/ui/components/quick-create/pl.dictionary.itop.quick-create.php b/dictionaries/ui/components/quick-create/pl.dictionary.itop.quick-create.php new file mode 100644 index 0000000000..6e8be7e795 --- /dev/null +++ b/dictionaries/ui/components/quick-create/pl.dictionary.itop.quick-create.php @@ -0,0 +1,27 @@ + 'Quickly create any type of object~~', + 'UI:Component:QuickCreate:Input:Placeholder' => 'Select object type...~~', + 'UI:Component:QuickCreate:Recents:Title' => 'Recents~~', + 'UI:Component:QuickCreate:LastClasses:NoClass:Placeholder' => 'You haven\'t create any object yet~~', + 'UI:Component:QuickCreate:HistoryDisabled' => 'History is disabled~~', + 'UI:Component:QuickCreate:KeyboardShortcut:OpenDrawer' => 'Open quick create~~', +)); \ No newline at end of file diff --git a/dictionaries/ui/components/quick-create/pt_br.dictionary.itop.quick-create.php b/dictionaries/ui/components/quick-create/pt_br.dictionary.itop.quick-create.php index 649673a61c..ee8c980733 100644 --- a/dictionaries/ui/components/quick-create/pt_br.dictionary.itop.quick-create.php +++ b/dictionaries/ui/components/quick-create/pt_br.dictionary.itop.quick-create.php @@ -18,10 +18,10 @@ */ // Quick create Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'UI:Component:QuickCreate:Tooltip' => 'Quickly create any type of object~~', - 'UI:Component:QuickCreate:Input:Placeholder' => 'Select object type...~~', - 'UI:Component:QuickCreate:Recents:Title' => 'Recents~~', - 'UI:Component:QuickCreate:LastClasses:NoClass:Placeholder' => 'You haven\'t create any object yet~~', - 'UI:Component:QuickCreate:HistoryDisabled' => 'History is disabled~~', - 'UI:Component:QuickCreate:KeyboardShortcut:OpenDrawer' => 'Open quick create~~', + 'UI:Component:QuickCreate:Tooltip' => 'Crie rapidamente qualquer tipo de objeto', + 'UI:Component:QuickCreate:Input:Placeholder' => 'Selecione o tipo de objeto...', + 'UI:Component:QuickCreate:Recents:Title' => 'Recentes', + 'UI:Component:QuickCreate:LastClasses:NoClass:Placeholder' => 'Você ainda não criou nenhum objeto', + 'UI:Component:QuickCreate:HistoryDisabled' => 'O histórico está desativado', + 'UI:Component:QuickCreate:KeyboardShortcut:OpenDrawer' => 'Abrir criação rápida', )); \ No newline at end of file diff --git a/dictionaries/ui/layouts/activity-panel/es_cr.dictionary.itop.activity-panel.php b/dictionaries/ui/layouts/activity-panel/es_cr.dictionary.itop.activity-panel.php index 0ab1258c30..fc2e6b9f50 100644 --- a/dictionaries/ui/layouts/activity-panel/es_cr.dictionary.itop.activity-panel.php +++ b/dictionaries/ui/layouts/activity-panel/es_cr.dictionary.itop.activity-panel.php @@ -18,41 +18,41 @@ */ // Activity panel Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'UI:Layout:ActivityPanel:SizeToggler:Expand:Tooltip' => 'Expand~~', - 'UI:Layout:ActivityPanel:SizeToggler:Reduce:Tooltip' => 'Reduce~~', - 'UI:Layout:ActivityPanel:DisplayToggler:Close:Tooltip' => 'Close~~', - 'UI:Layout:ActivityPanel:LoadMoreEntries:Tooltip' => 'Load more entries~~', - 'UI:Layout:ActivityPanel:LoadAllEntries:Tooltip' => 'Load all previous entries~~', + 'UI:Layout:ActivityPanel:SizeToggler:Expand:Tooltip' => 'Expandir', + 'UI:Layout:ActivityPanel:SizeToggler:Reduce:Tooltip' => 'Reducir', + 'UI:Layout:ActivityPanel:DisplayToggler:Close:Tooltip' => 'Cerrar', + 'UI:Layout:ActivityPanel:LoadMoreEntries:Tooltip' => 'Cargar más entradas', + 'UI:Layout:ActivityPanel:LoadAllEntries:Tooltip' => 'Cargar tooas las entradas previas', // Tabs - 'UI:Layout:ActivityPanel:Tab:Activity:Title' => 'Activity~~', - 'UI:Layout:ActivityPanel:Tab:Log:DraftIndicator:Tooltip' => 'Draft entry~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Title' => 'Logs~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Tooltip' => 'Show / hide logs entries on this tab~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Menu:Hint' => 'Choose which logs to display on this tab~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Transitions:Title' => 'State changes~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Transitions:Tooltip' => 'Show / hide state changes on this tab~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Edits:Title' => 'Edits~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Edits:Tooltip' => 'Show / hide fields edits on this tab~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Action:OpenAll:Tooltip' => 'Open all entries~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Action:CloseAll:Tooltip' => 'Close all entries~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Info:AuthorsCount:Tooltip' => 'Number of persons interacting in the visible entries~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Info:MessagesCount:Tooltip' => 'Number of messages in the visible log(s)~~', + 'UI:Layout:ActivityPanel:Tab:Activity:Title' => 'Actividad', + 'UI:Layout:ActivityPanel:Tab:Log:DraftIndicator:Tooltip' => 'Borrador de entrada', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Title' => 'Bitácoras', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Tooltip' => 'Mostrar/ocultar entradas en bitácora en esta pestaña', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Menu:Hint' => 'Elija qué bitácora mostrar en esta pestaña', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Transitions:Title' => 'Cambios de estado', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Transitions:Tooltip' => 'Mostrar/ocultar cambios de estado en esta pestaña', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Edits:Title' => 'Ediciones', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Edits:Tooltip' => 'Mostrar/ocultar ediciones de campos en esta pestaña', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Action:OpenAll:Tooltip' => 'Abrir todas las entradas', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Action:CloseAll:Tooltip' => 'Cerrar todas las entradas', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Info:AuthorsCount:Tooltip' => 'Número de personas interactuando en las entradas visibles', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Info:MessagesCount:Tooltip' => 'Número de mensajes visibles en las bitácoras', // Compose button - 'UI:Layout:ActivityPanel:ComposeButton:Tooltip' => 'Compose a new log entry~~', + 'UI:Layout:ActivityPanel:ComposeButton:Tooltip' => 'Redactar una nueva entrada de registro', // Case log entry - 'UI:Layout:ActivityPanel:MultipleEntriesSaveConfirmation:Title' => 'Multiple logs save~~', - 'UI:Layout:ActivityPanel:MultipleEntriesSaveConfirmation:Explanation' => 'By pressing the "save" button, you will submit entries for all the edited logs at once.~~', + 'UI:Layout:ActivityPanel:MultipleEntriesSaveConfirmation:Title' => 'Guardar varias bitácoras', + 'UI:Layout:ActivityPanel:MultipleEntriesSaveConfirmation:Explanation' => 'Al presionar el botón "Guardar", enviará entradas para todos las bitácoras editadas a la vez.', // Notification entry - 'UI:Layout:ActivityPanel:NotificationEntry:MessageLink:Tooltip' => 'Click to open the notifications tab and get more information~~', + 'UI:Layout:ActivityPanel:NotificationEntry:MessageLink:Tooltip' => 'Haga click para abrir la pestaña de notificaciones y obtener más información', // Placeholder - 'UI:Layout:ActivityPanel:NoEntry:Placeholder:Hint' => 'It\'s calm up here, no activity yet~~', + 'UI:Layout:ActivityPanel:NoEntry:Placeholder:Hint' => 'Está tranquilo aquí, aún no hay actividad.', // Closed cover - 'UI:Layout:ActivityPanel:ClosedCover:Title' => 'Activity panel~~', - 'UI:Layout:ActivityPanel:ClosedCover:Tooltip' => 'Click to open the activity panel~~', + 'UI:Layout:ActivityPanel:ClosedCover:Title' => 'Panel de actividad', + 'UI:Layout:ActivityPanel:ClosedCover:Tooltip' => 'Dar click para abrir el panel de actividades.', )); \ No newline at end of file diff --git a/dictionaries/ui/layouts/activity-panel/nl.dictionary.itop.activity-panel.php b/dictionaries/ui/layouts/activity-panel/nl.dictionary.itop.activity-panel.php index f26448fd98..5c26806507 100644 --- a/dictionaries/ui/layouts/activity-panel/nl.dictionary.itop.activity-panel.php +++ b/dictionaries/ui/layouts/activity-panel/nl.dictionary.itop.activity-panel.php @@ -21,36 +21,36 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'UI:Layout:ActivityPanel:SizeToggler:Expand:Tooltip' => 'Vergroot', 'UI:Layout:ActivityPanel:SizeToggler:Reduce:Tooltip' => 'Reduceer', 'UI:Layout:ActivityPanel:DisplayToggler:Close:Tooltip' => 'Sluit', - 'UI:Layout:ActivityPanel:LoadMoreEntries:Tooltip' => 'Load more entries~~', - 'UI:Layout:ActivityPanel:LoadAllEntries:Tooltip' => 'Load all previous entries~~', + 'UI:Layout:ActivityPanel:LoadMoreEntries:Tooltip' => 'Laad meer items', + 'UI:Layout:ActivityPanel:LoadAllEntries:Tooltip' => 'Laad alle vorige items', // Tabs 'UI:Layout:ActivityPanel:Tab:Activity:Title' => 'Activiteiten', 'UI:Layout:ActivityPanel:Tab:Log:DraftIndicator:Tooltip' => 'Concept', 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Title' => 'Logs', 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Tooltip' => 'Toon / verberg logs', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Menu:Hint' => 'Choose which logs to display on this tab~~', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Menu:Hint' => 'Kies welke logs weergegeven worden op deze tab', 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Transitions:Title' => 'Statuswijzigingen', 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Transitions:Tooltip' => 'Toon / verberg statuswijzigingen', 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Edits:Title' => 'Aanpassingen', 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Edits:Tooltip' => 'Toon / verberg aanpassingen', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Action:OpenAll:Tooltip' => 'Open all entries~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Action:CloseAll:Tooltip' => 'Close all entries~~', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Action:OpenAll:Tooltip' => 'Open alle items', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Action:CloseAll:Tooltip' => 'Sluit alle items', 'UI:Layout:ActivityPanel:Tab:Toolbar:Info:AuthorsCount:Tooltip' => 'Aantal participerende personen in de zichtbare berichten', 'UI:Layout:ActivityPanel:Tab:Toolbar:Info:MessagesCount:Tooltip' => 'Aantal berichten in de zichtbare logs', // Compose button - 'UI:Layout:ActivityPanel:ComposeButton:Tooltip' => 'Compose a new log entry~~', + 'UI:Layout:ActivityPanel:ComposeButton:Tooltip' => 'Voeg item toe', // Case log entry - 'UI:Layout:ActivityPanel:MultipleEntriesSaveConfirmation:Title' => 'Multiple logs save~~', - 'UI:Layout:ActivityPanel:MultipleEntriesSaveConfirmation:Explanation' => 'By pressing the "save" button, you will submit entries for all the edited logs at once.~~', + 'UI:Layout:ActivityPanel:MultipleEntriesSaveConfirmation:Title' => 'Meerdere logs opslaan', + 'UI:Layout:ActivityPanel:MultipleEntriesSaveConfirmation:Explanation' => 'Door op deze knop te drukken, wordt alle invoer in elke log tegelijk opgeslagen.', // Notification entry - 'UI:Layout:ActivityPanel:NotificationEntry:MessageLink:Tooltip' => 'Click to open the notifications tab and get more information~~', + 'UI:Layout:ActivityPanel:NotificationEntry:MessageLink:Tooltip' => 'Klik om het tabblad Notificaties te openen en meer info te zien', // Placeholder - 'UI:Layout:ActivityPanel:NoEntry:Placeholder:Hint' => 'It\'s calm up here, no activity yet~~', + 'UI:Layout:ActivityPanel:NoEntry:Placeholder:Hint' => 'Nog geen activiteit.', // Closed cover 'UI:Layout:ActivityPanel:ClosedCover:Title' => 'Activiteitenpaneel', diff --git a/dictionaries/ui/layouts/activity-panel/pl.dictionary.itop.activity-panel.php b/dictionaries/ui/layouts/activity-panel/pl.dictionary.itop.activity-panel.php new file mode 100644 index 0000000000..25576ae565 --- /dev/null +++ b/dictionaries/ui/layouts/activity-panel/pl.dictionary.itop.activity-panel.php @@ -0,0 +1,58 @@ + 'Expand~~', + 'UI:Layout:ActivityPanel:SizeToggler:Reduce:Tooltip' => 'Reduce~~', + 'UI:Layout:ActivityPanel:DisplayToggler:Close:Tooltip' => 'Close~~', + 'UI:Layout:ActivityPanel:LoadMoreEntries:Tooltip' => 'Load more entries~~', + 'UI:Layout:ActivityPanel:LoadAllEntries:Tooltip' => 'Load all previous entries~~', + + // Tabs + 'UI:Layout:ActivityPanel:Tab:Activity:Title' => 'Activity~~', + 'UI:Layout:ActivityPanel:Tab:Log:DraftIndicator:Tooltip' => 'Draft entry~~', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Title' => 'Logs~~', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Tooltip' => 'Show / hide logs entries on this tab~~', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Menu:Hint' => 'Choose which logs to display on this tab~~', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Transitions:Title' => 'State changes~~', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Transitions:Tooltip' => 'Show / hide state changes on this tab~~', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Edits:Title' => 'Edits~~', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Edits:Tooltip' => 'Show / hide fields edits on this tab~~', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Action:OpenAll:Tooltip' => 'Open all entries~~', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Action:CloseAll:Tooltip' => 'Close all entries~~', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Info:AuthorsCount:Tooltip' => 'Number of persons interacting in the visible entries~~', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Info:MessagesCount:Tooltip' => 'Number of messages in the visible log(s)~~', + + // Compose button + 'UI:Layout:ActivityPanel:ComposeButton:Tooltip' => 'Compose a new log entry~~', + + // Case log entry + 'UI:Layout:ActivityPanel:MultipleEntriesSaveConfirmation:Title' => 'Multiple logs save~~', + 'UI:Layout:ActivityPanel:MultipleEntriesSaveConfirmation:Explanation' => 'By pressing the "save" button, you will submit entries for all the edited logs at once.~~', + + // Notification entry + 'UI:Layout:ActivityPanel:NotificationEntry:MessageLink:Tooltip' => 'Click to open the notifications tab and get more information~~', + + // Placeholder + 'UI:Layout:ActivityPanel:NoEntry:Placeholder:Hint' => 'It\'s calm up here, no activity yet~~', + + // Closed cover + 'UI:Layout:ActivityPanel:ClosedCover:Title' => 'Activity panel~~', + 'UI:Layout:ActivityPanel:ClosedCover:Tooltip' => 'Click to open the activity panel~~', +)); \ No newline at end of file diff --git a/dictionaries/ui/layouts/activity-panel/pt_br.dictionary.itop.activity-panel.php b/dictionaries/ui/layouts/activity-panel/pt_br.dictionary.itop.activity-panel.php index c57d0beaf5..4d0182e2c9 100644 --- a/dictionaries/ui/layouts/activity-panel/pt_br.dictionary.itop.activity-panel.php +++ b/dictionaries/ui/layouts/activity-panel/pt_br.dictionary.itop.activity-panel.php @@ -18,41 +18,41 @@ */ // Activity panel Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'UI:Layout:ActivityPanel:SizeToggler:Expand:Tooltip' => 'Expand~~', - 'UI:Layout:ActivityPanel:SizeToggler:Reduce:Tooltip' => 'Reduce~~', - 'UI:Layout:ActivityPanel:DisplayToggler:Close:Tooltip' => 'Close~~', - 'UI:Layout:ActivityPanel:LoadMoreEntries:Tooltip' => 'Load more entries~~', - 'UI:Layout:ActivityPanel:LoadAllEntries:Tooltip' => 'Load all previous entries~~', + 'UI:Layout:ActivityPanel:SizeToggler:Expand:Tooltip' => 'Expandir', + 'UI:Layout:ActivityPanel:SizeToggler:Reduce:Tooltip' => 'Recolher', + 'UI:Layout:ActivityPanel:DisplayToggler:Close:Tooltip' => 'Fechar', + 'UI:Layout:ActivityPanel:LoadMoreEntries:Tooltip' => 'Carregar mais entradas', + 'UI:Layout:ActivityPanel:LoadAllEntries:Tooltip' => 'Carregar todas as entradas anteriores', // Tabs - 'UI:Layout:ActivityPanel:Tab:Activity:Title' => 'Activity~~', - 'UI:Layout:ActivityPanel:Tab:Log:DraftIndicator:Tooltip' => 'Draft entry~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Title' => 'Logs~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Tooltip' => 'Show / hide logs entries on this tab~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Menu:Hint' => 'Choose which logs to display on this tab~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Transitions:Title' => 'State changes~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Transitions:Tooltip' => 'Show / hide state changes on this tab~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Edits:Title' => 'Edits~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Edits:Tooltip' => 'Show / hide fields edits on this tab~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Action:OpenAll:Tooltip' => 'Open all entries~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Action:CloseAll:Tooltip' => 'Close all entries~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Info:AuthorsCount:Tooltip' => 'Number of persons interacting in the visible entries~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Info:MessagesCount:Tooltip' => 'Number of messages in the visible log(s)~~', + 'UI:Layout:ActivityPanel:Tab:Activity:Title' => 'Atividades', + 'UI:Layout:ActivityPanel:Tab:Log:DraftIndicator:Tooltip' => 'Entrada de rascunho', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Title' => 'Logs', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Tooltip' => 'Exibir / Ocultar entradas de logs nesta guia', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Menu:Hint' => 'Escolha quais logs a serem exibidos nesta guia', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Transitions:Title' => 'Alterações de status', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Transitions:Tooltip' => 'Exibir / Ocultar alterações de status nesta guia', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Edits:Title' => 'Edições', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Edits:Tooltip' => 'Exibir / Ocultar edições de campos nesta guia', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Action:OpenAll:Tooltip' => 'Abrir todas as entradas', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Action:CloseAll:Tooltip' => 'Recolher todas as entradas', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Info:AuthorsCount:Tooltip' => 'Número de pessoas que contribuíram para as entradas exibidas', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Info:MessagesCount:Tooltip' => 'Número de entradas no(s) log(s) exibido(s)', // Compose button - 'UI:Layout:ActivityPanel:ComposeButton:Tooltip' => 'Compose a new log entry~~', + 'UI:Layout:ActivityPanel:ComposeButton:Tooltip' => 'Adicionar uma nova entrada no log', // Case log entry - 'UI:Layout:ActivityPanel:MultipleEntriesSaveConfirmation:Title' => 'Multiple logs save~~', - 'UI:Layout:ActivityPanel:MultipleEntriesSaveConfirmation:Explanation' => 'By pressing the "save" button, you will submit entries for all the edited logs at once.~~', + 'UI:Layout:ActivityPanel:MultipleEntriesSaveConfirmation:Title' => 'Salvar múltiplos logs', + 'UI:Layout:ActivityPanel:MultipleEntriesSaveConfirmation:Explanation' => 'Ao clicar no botão de "salvar", você enviará entradas para todos os logs alterados de uma só vez.', // Notification entry - 'UI:Layout:ActivityPanel:NotificationEntry:MessageLink:Tooltip' => 'Click to open the notifications tab and get more information~~', + 'UI:Layout:ActivityPanel:NotificationEntry:MessageLink:Tooltip' => 'Clique para abrir a guia de notificações e obter mais informações', // Placeholder - 'UI:Layout:ActivityPanel:NoEntry:Placeholder:Hint' => 'It\'s calm up here, no activity yet~~', + 'UI:Layout:ActivityPanel:NoEntry:Placeholder:Hint' => 'Está calmo aqui, nenhuma atividade ainda', // Closed cover - 'UI:Layout:ActivityPanel:ClosedCover:Title' => 'Activity panel~~', - 'UI:Layout:ActivityPanel:ClosedCover:Tooltip' => 'Click to open the activity panel~~', + 'UI:Layout:ActivityPanel:ClosedCover:Title' => 'Painel de Atividades', + 'UI:Layout:ActivityPanel:ClosedCover:Tooltip' => 'Clique para abrir o Painel de Atividades', )); \ No newline at end of file diff --git a/dictionaries/ui/layouts/activity-panel/zh_cn.dictionary.itop.activity-panel.php b/dictionaries/ui/layouts/activity-panel/zh_cn.dictionary.itop.activity-panel.php index cd953418ea..8efa7a6353 100644 --- a/dictionaries/ui/layouts/activity-panel/zh_cn.dictionary.itop.activity-panel.php +++ b/dictionaries/ui/layouts/activity-panel/zh_cn.dictionary.itop.activity-panel.php @@ -31,13 +31,13 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Tooltip' => '显示/隐藏 日志', 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Logs:Menu:Hint' => '请选择要显示的日志', 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Transitions:Title' => '状态变化', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Transitions:Tooltip' => '显示 / 隐藏 状态变化', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Transitions:Tooltip' => '显示/隐藏 状态变化', 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Edits:Title' => '编辑', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Edits:Tooltip' => '显示 / 隐藏 字段编辑', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Filter:Edits:Tooltip' => '显示/隐藏 字段编辑', 'UI:Layout:ActivityPanel:Tab:Toolbar:Action:OpenAll:Tooltip' => '全部打开', 'UI:Layout:ActivityPanel:Tab:Toolbar:Action:CloseAll:Tooltip' => '全部关闭', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Info:AuthorsCount:Tooltip' => 'Number of persons interacting in the visible entries~~', - 'UI:Layout:ActivityPanel:Tab:Toolbar:Info:MessagesCount:Tooltip' => 'Number of messages in the visible log(s)~~', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Info:AuthorsCount:Tooltip' => '正在查看此条目的人数', + 'UI:Layout:ActivityPanel:Tab:Toolbar:Info:MessagesCount:Tooltip' => '此日志的消息数', // Compose button 'UI:Layout:ActivityPanel:ComposeButton:Tooltip' => '撰写新的条目', @@ -50,7 +50,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'UI:Layout:ActivityPanel:NotificationEntry:MessageLink:Tooltip' => '点击打开通知栏以获得更多信息', // Placeholder - 'UI:Layout:ActivityPanel:NoEntry:Placeholder:Hint' => '很平静, 暂无任何活动', + 'UI:Layout:ActivityPanel:NoEntry:Placeholder:Hint' => '暂无任何活动', // Closed cover 'UI:Layout:ActivityPanel:ClosedCover:Title' => '活动面板', diff --git a/dictionaries/ui/layouts/navigation-menu/de.dictionary.itop.navigation-menu.php b/dictionaries/ui/layouts/navigation-menu/de.dictionary.itop.navigation-menu.php index a1dcd4d265..b0c230e1e2 100644 --- a/dictionaries/ui/layouts/navigation-menu/de.dictionary.itop.navigation-menu.php +++ b/dictionaries/ui/layouts/navigation-menu/de.dictionary.itop.navigation-menu.php @@ -19,7 +19,7 @@ // Navigation menu Dict::Add('DE DE', 'German', 'Deutsch', array( 'UI:Layout:NavigationMenu:CompanyLogo:AltText' => 'Unternehmenslogo', - 'UI:Layout:NavigationMenu:Silo:Label' => 'Select organization to filter on~~', + 'UI:Layout:NavigationMenu:Silo:Label' => 'Organisation zum filtern auswählen', 'UI:Layout:NavigationMenu:Toggler:Tooltip' => 'Ausklappen/Einklappen', 'UI:Layout:NavigationMenu:Toggler:TooltipWithSiloLabel' => 'Ausklappen/Einklappen (Gefiltert nach %1$s)', 'UI:Layout:NavigationMenu:MenuFilter:Input:Placeholder' => 'Filter...', @@ -28,7 +28,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'UI:Layout:NavigationMenu:MenuFilter:Placeholder:Hint' => 'Kein Ergebnis für diesen Menü-Filter', 'UI:Layout:NavigationMenu:UserInfo:WelcomeMessage:Text' => 'Hi %1$s!', 'UI:Layout:NavigationMenu:UserInfo:Picture:AltText' => '%1$s\'s Profilbild', - 'UI:Layout:NavigationMenu:UserMenu:Toggler:Label' => 'Open user menu~~', + 'UI:Layout:NavigationMenu:UserMenu:Toggler:Label' => 'Benutzermenü öffnen', 'UI:Layout:NavigationMenu:KeyboardShortcut:FocusFilter' => 'Filtere Menüeinträge', -)); \ No newline at end of file +)); diff --git a/dictionaries/ui/layouts/navigation-menu/es_cr.dictionary.itop.navigation-menu.php b/dictionaries/ui/layouts/navigation-menu/es_cr.dictionary.itop.navigation-menu.php index c85414efd8..a1b1450a00 100644 --- a/dictionaries/ui/layouts/navigation-menu/es_cr.dictionary.itop.navigation-menu.php +++ b/dictionaries/ui/layouts/navigation-menu/es_cr.dictionary.itop.navigation-menu.php @@ -18,17 +18,17 @@ */ // Navigation menu Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'UI:Layout:NavigationMenu:CompanyLogo:AltText' => 'Company logo~~', - 'UI:Layout:NavigationMenu:Silo:Label' => 'Select organization to filter on~~', - 'UI:Layout:NavigationMenu:Toggler:Tooltip' => 'Expand / Collapse~~', - 'UI:Layout:NavigationMenu:Toggler:TooltipWithSiloLabel' => 'Expand / Collapse (Filtered on %1$s)~~', - 'UI:Layout:NavigationMenu:MenuFilter:Input:Placeholder' => 'Filter...~~', - 'UI:Layout:NavigationMenu:MenuFilter:Input:Tooltip' => 'Type your keywords to filter menus~~', - 'UI:Layout:NavigationMenu:MenuFilter:Input:Hint' => 'Matches from all menu groups will be displayed~~', - 'UI:Layout:NavigationMenu:MenuFilter:Placeholder:Hint' => 'No result for this menu filter~~', - 'UI:Layout:NavigationMenu:UserInfo:WelcomeMessage:Text' => 'Hi %1$s!~~', - 'UI:Layout:NavigationMenu:UserInfo:Picture:AltText' => '%1$s\'s contact picture~~', - 'UI:Layout:NavigationMenu:UserMenu:Toggler:Label' => 'Open user menu~~', - 'UI:Layout:NavigationMenu:KeyboardShortcut:FocusFilter' => 'Filter menu entries~~', + 'UI:Layout:NavigationMenu:CompanyLogo:AltText' => 'Logotipo de la Compañia', + 'UI:Layout:NavigationMenu:Silo:Label' => 'Seleccione la organización para filtrar', + 'UI:Layout:NavigationMenu:Toggler:Tooltip' => 'Expandir / Contraer', + 'UI:Layout:NavigationMenu:Toggler:TooltipWithSiloLabel' => 'Expandir / Contraer (Filtrado por %1$s)', + 'UI:Layout:NavigationMenu:MenuFilter:Input:Placeholder' => 'Filtrar...', + 'UI:Layout:NavigationMenu:MenuFilter:Input:Tooltip' => 'Escriba sus palabras clave para filtrar los menús', + 'UI:Layout:NavigationMenu:MenuFilter:Input:Hint' => 'Se mostrarán las coincidencias de todos los grupos del menú', + 'UI:Layout:NavigationMenu:MenuFilter:Placeholder:Hint' => 'No hay resultados para este filtro de menú', + 'UI:Layout:NavigationMenu:UserInfo:WelcomeMessage:Text' => '!Hola %1$s!', + 'UI:Layout:NavigationMenu:UserInfo:Picture:AltText' => 'Foto de contacto de %1$s', + 'UI:Layout:NavigationMenu:UserMenu:Toggler:Label' => 'Abrir menú de usuario', + 'UI:Layout:NavigationMenu:KeyboardShortcut:FocusFilter' => 'Filtrar entradas del menú', )); \ No newline at end of file diff --git a/dictionaries/ui/layouts/navigation-menu/nl.dictionary.itop.navigation-menu.php b/dictionaries/ui/layouts/navigation-menu/nl.dictionary.itop.navigation-menu.php index a85e1e1393..8bf1802aec 100644 --- a/dictionaries/ui/layouts/navigation-menu/nl.dictionary.itop.navigation-menu.php +++ b/dictionaries/ui/layouts/navigation-menu/nl.dictionary.itop.navigation-menu.php @@ -19,16 +19,16 @@ // Navigation menu Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'UI:Layout:NavigationMenu:CompanyLogo:AltText' => 'Bedrijfslogo', - 'UI:Layout:NavigationMenu:Silo:Label' => 'Select organization to filter on~~', + 'UI:Layout:NavigationMenu:Silo:Label' => 'Selecteer de organisatie waarop gefilterd moet worden', 'UI:Layout:NavigationMenu:Toggler:Tooltip' => 'Vergroot / Verberg', 'UI:Layout:NavigationMenu:Toggler:TooltipWithSiloLabel' => 'Vergroot / Verberg (Gefilterd op %1$s)', - 'UI:Layout:NavigationMenu:MenuFilter:Input:Placeholder' => 'Filter…', - 'UI:Layout:NavigationMenu:MenuFilter:Input:Tooltip' => 'Type your keywords to filter menus~~', - 'UI:Layout:NavigationMenu:MenuFilter:Input:Hint' => 'Matches from all menu groups will be displayed~~', - 'UI:Layout:NavigationMenu:MenuFilter:Placeholder:Hint' => 'No result for this menu filter~~', + 'UI:Layout:NavigationMenu:MenuFilter:Input:Placeholder' => 'Filter...', + 'UI:Layout:NavigationMenu:MenuFilter:Input:Tooltip' => 'Typ een zoekterm om de menu\'s te filteren', + 'UI:Layout:NavigationMenu:MenuFilter:Input:Hint' => 'Resultaten van alle menugroepen zullen getoond worden', + 'UI:Layout:NavigationMenu:MenuFilter:Placeholder:Hint' => 'Geen resultaten voor deze menufilter', 'UI:Layout:NavigationMenu:UserInfo:WelcomeMessage:Text' => 'Welkom %1$s!', 'UI:Layout:NavigationMenu:UserInfo:Picture:AltText' => '%1$s\'s profielfoto', - 'UI:Layout:NavigationMenu:UserMenu:Toggler:Label' => 'Open user menu~~', + 'UI:Layout:NavigationMenu:UserMenu:Toggler:Label' => 'Open gebruikersmenu', 'UI:Layout:NavigationMenu:KeyboardShortcut:FocusFilter' => 'Filter menu\'s', )); \ No newline at end of file diff --git a/dictionaries/ui/layouts/navigation-menu/pl.dictionary.itop.navigation-menu.php b/dictionaries/ui/layouts/navigation-menu/pl.dictionary.itop.navigation-menu.php new file mode 100644 index 0000000000..197e78dcb7 --- /dev/null +++ b/dictionaries/ui/layouts/navigation-menu/pl.dictionary.itop.navigation-menu.php @@ -0,0 +1,34 @@ + 'Company logo~~', + 'UI:Layout:NavigationMenu:Silo:Label' => 'Select organization to filter on~~', + 'UI:Layout:NavigationMenu:Toggler:Tooltip' => 'Expand / Collapse~~', + 'UI:Layout:NavigationMenu:Toggler:TooltipWithSiloLabel' => 'Expand / Collapse (Filtered on %1$s)~~', + 'UI:Layout:NavigationMenu:MenuFilter:Input:Placeholder' => 'Filter...~~', + 'UI:Layout:NavigationMenu:MenuFilter:Input:Tooltip' => 'Type your keywords to filter menus~~', + 'UI:Layout:NavigationMenu:MenuFilter:Input:Hint' => 'Matches from all menu groups will be displayed~~', + 'UI:Layout:NavigationMenu:MenuFilter:Placeholder:Hint' => 'Seems like there is no matching menu~~', + 'UI:Layout:NavigationMenu:UserInfo:WelcomeMessage:Text' => 'Hi %1$s!~~', + 'UI:Layout:NavigationMenu:UserInfo:Picture:AltText' => '%1$s\'s contact picture~~', + 'UI:Layout:NavigationMenu:UserMenu:Toggler:Label' => 'Open user menu~~', + 'UI:Layout:NavigationMenu:KeyboardShortcut:FocusFilter' => 'Filter menu entries~~', + +)); \ No newline at end of file diff --git a/dictionaries/ui/layouts/navigation-menu/pt_br.dictionary.itop.navigation-menu.php b/dictionaries/ui/layouts/navigation-menu/pt_br.dictionary.itop.navigation-menu.php index 3b2339c97f..1bce74713f 100644 --- a/dictionaries/ui/layouts/navigation-menu/pt_br.dictionary.itop.navigation-menu.php +++ b/dictionaries/ui/layouts/navigation-menu/pt_br.dictionary.itop.navigation-menu.php @@ -18,17 +18,17 @@ */ // Navigation menu Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'UI:Layout:NavigationMenu:CompanyLogo:AltText' => 'Company logo~~', - 'UI:Layout:NavigationMenu:Silo:Label' => 'Select organization to filter on~~', - 'UI:Layout:NavigationMenu:Toggler:Tooltip' => 'Expand / Collapse~~', - 'UI:Layout:NavigationMenu:Toggler:TooltipWithSiloLabel' => 'Expand / Collapse (Filtered on %1$s)~~', - 'UI:Layout:NavigationMenu:MenuFilter:Input:Placeholder' => 'Filter...~~', - 'UI:Layout:NavigationMenu:MenuFilter:Input:Tooltip' => 'Type your keywords to filter menus~~', - 'UI:Layout:NavigationMenu:MenuFilter:Input:Hint' => 'Matches from all menu groups will be displayed~~', - 'UI:Layout:NavigationMenu:MenuFilter:Placeholder:Hint' => 'No result for this menu filter~~', - 'UI:Layout:NavigationMenu:UserInfo:WelcomeMessage:Text' => 'Hi %1$s!~~', - 'UI:Layout:NavigationMenu:UserInfo:Picture:AltText' => '%1$s\'s contact picture~~', - 'UI:Layout:NavigationMenu:UserMenu:Toggler:Label' => 'Open user menu~~', - 'UI:Layout:NavigationMenu:KeyboardShortcut:FocusFilter' => 'Filter menu entries~~', + 'UI:Layout:NavigationMenu:CompanyLogo:AltText' => 'Logo da organização', + 'UI:Layout:NavigationMenu:Silo:Label' => 'Selecione a organização para filtrar', + 'UI:Layout:NavigationMenu:Toggler:Tooltip' => 'Expandir / Recolher', + 'UI:Layout:NavigationMenu:Toggler:TooltipWithSiloLabel' => 'Expandir / Recolher (Filtrado em %1$s)', + 'UI:Layout:NavigationMenu:MenuFilter:Input:Placeholder' => 'Filtrar...', + 'UI:Layout:NavigationMenu:MenuFilter:Input:Tooltip' => 'Digite palavras-chave para filtrar os menus', + 'UI:Layout:NavigationMenu:MenuFilter:Input:Hint' => 'As correspondências em todos os grupos de menus serão exibidas', + 'UI:Layout:NavigationMenu:MenuFilter:Placeholder:Hint' => 'Nenhum resultado para este filtro de menu', + 'UI:Layout:NavigationMenu:UserInfo:WelcomeMessage:Text' => 'Olá %1$s!', + 'UI:Layout:NavigationMenu:UserInfo:Picture:AltText' => 'Imagem do contato %1$', + 'UI:Layout:NavigationMenu:UserMenu:Toggler:Label' => 'Abrir menu do usuário', + 'UI:Layout:NavigationMenu:KeyboardShortcut:FocusFilter' => 'Filtrar entradas de menu', )); \ No newline at end of file diff --git a/dictionaries/ui/layouts/navigation-menu/zh_cn.dictionary.itop.navigation-menu.php b/dictionaries/ui/layouts/navigation-menu/zh_cn.dictionary.itop.navigation-menu.php index cf66141816..8175c2b262 100644 --- a/dictionaries/ui/layouts/navigation-menu/zh_cn.dictionary.itop.navigation-menu.php +++ b/dictionaries/ui/layouts/navigation-menu/zh_cn.dictionary.itop.navigation-menu.php @@ -19,16 +19,16 @@ // Navigation menu Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'UI:Layout:NavigationMenu:CompanyLogo:AltText' => '公司logo', - 'UI:Layout:NavigationMenu:Silo:Label' => 'Select organization to filter on~~', - 'UI:Layout:NavigationMenu:Toggler:Tooltip' => '展开 / 折叠', - 'UI:Layout:NavigationMenu:Toggler:TooltipWithSiloLabel' => '展开 / 折叠 (Filtered on %1$s)~~', + 'UI:Layout:NavigationMenu:Silo:Label' => '请选择要过滤的组织', + 'UI:Layout:NavigationMenu:Toggler:Tooltip' => '展开/折叠', + 'UI:Layout:NavigationMenu:Toggler:TooltipWithSiloLabel' => '展开/折叠 (过滤在 %1$s)', 'UI:Layout:NavigationMenu:MenuFilter:Input:Placeholder' => '过滤器...', 'UI:Layout:NavigationMenu:MenuFilter:Input:Tooltip' => '请输入要过滤的关键字', 'UI:Layout:NavigationMenu:MenuFilter:Input:Hint' => '将会显示所有匹配的菜单', - 'UI:Layout:NavigationMenu:MenuFilter:Placeholder:Hint' => 'No result for this menu filter~~', + 'UI:Layout:NavigationMenu:MenuFilter:Placeholder:Hint' => '此菜单暂无过滤结果', 'UI:Layout:NavigationMenu:UserInfo:WelcomeMessage:Text' => 'Hi %1$s!', - 'UI:Layout:NavigationMenu:UserInfo:Picture:AltText' => '%1$s\'s contact picture~~', - 'UI:Layout:NavigationMenu:UserMenu:Toggler:Label' => 'Open user menu~~', - 'UI:Layout:NavigationMenu:KeyboardShortcut:FocusFilter' => 'Filter menu entries~~', + 'UI:Layout:NavigationMenu:UserInfo:Picture:AltText' => '%1$s 的联系人头像', + 'UI:Layout:NavigationMenu:UserMenu:Toggler:Label' => '打开用户菜单', + 'UI:Layout:NavigationMenu:KeyboardShortcut:FocusFilter' => '过滤菜单', )); \ No newline at end of file diff --git a/dictionaries/ui/layouts/object-details/es_cr.dictionary.itop.object-details.php b/dictionaries/ui/layouts/object-details/es_cr.dictionary.itop.object-details.php index 76d1d6ab9b..011682c669 100644 --- a/dictionaries/ui/layouts/object-details/es_cr.dictionary.itop.object-details.php +++ b/dictionaries/ui/layouts/object-details/es_cr.dictionary.itop.object-details.php @@ -4,8 +4,8 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [ - 'UI:Layout:ObjectDetails:KeyboardShortcut:EditObject' => 'Edit displayed object~~', - 'UI:Layout:ObjectDetails:KeyboardShortcut:DeleteObject' => 'Delete displayed object~~', - 'UI:Layout:ObjectDetails:KeyboardShortcut:NewObject' => 'Create a new object (with same class as displayed object)~~', - 'UI:Layout:ObjectDetails:KeyboardShortcut:SaveObject' => 'Save displayed object~~', -]); + 'UI:Layout:ObjectDetails:KeyboardShortcut:EditObject' => 'Editar objeto mostrado', + 'UI:Layout:ObjectDetails:KeyboardShortcut:DeleteObject' => 'Borrar objeto mostrado', + 'UI:Layout:ObjectDetails:KeyboardShortcut:NewObject' => 'Crear un nuevo objeto (con la misma clase que el objeto mostrado)', + 'UI:Layout:ObjectDetails:KeyboardShortcut:SaveObject' => 'Guardar objeto mostrado', +]); \ No newline at end of file diff --git a/dictionaries/ui/layouts/object-details/nl.dictionary.itop.object-details.php b/dictionaries/ui/layouts/object-details/nl.dictionary.itop.object-details.php index f57225e593..12af5284fb 100644 --- a/dictionaries/ui/layouts/object-details/nl.dictionary.itop.object-details.php +++ b/dictionaries/ui/layouts/object-details/nl.dictionary.itop.object-details.php @@ -6,6 +6,6 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [ 'UI:Layout:ObjectDetails:KeyboardShortcut:EditObject' => 'Pas huidig object aan', 'UI:Layout:ObjectDetails:KeyboardShortcut:DeleteObject' => 'Verwijder huidig object', - 'UI:Layout:ObjectDetails:KeyboardShortcut:NewObject' => 'Create a new object (with same class as displayed object)~~', + 'UI:Layout:ObjectDetails:KeyboardShortcut:NewObject' => 'Maak een nieuw object (van dezelfde klasse als het getoonde object)', 'UI:Layout:ObjectDetails:KeyboardShortcut:SaveObject' => 'Bewaar huidig object', ]); diff --git a/dictionaries/ui/layouts/object-details/pl.dictionary.itop.object-details.php b/dictionaries/ui/layouts/object-details/pl.dictionary.itop.object-details.php new file mode 100644 index 0000000000..5d248de27e --- /dev/null +++ b/dictionaries/ui/layouts/object-details/pl.dictionary.itop.object-details.php @@ -0,0 +1,11 @@ + 'Edit displayed object~~', + 'UI:Layout:ObjectDetails:KeyboardShortcut:DeleteObject' => 'Delete displayed object~~', + 'UI:Layout:ObjectDetails:KeyboardShortcut:NewObject' => 'Create a new object (with same class as displayed object)~~', + 'UI:Layout:ObjectDetails:KeyboardShortcut:SaveObject' => 'Save displayed object~~', +]); diff --git a/dictionaries/ui/layouts/object-details/pt_br.dictionary.itop.object-details.php b/dictionaries/ui/layouts/object-details/pt_br.dictionary.itop.object-details.php index 270169fa22..6c82015e39 100644 --- a/dictionaries/ui/layouts/object-details/pt_br.dictionary.itop.object-details.php +++ b/dictionaries/ui/layouts/object-details/pt_br.dictionary.itop.object-details.php @@ -4,8 +4,8 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [ - 'UI:Layout:ObjectDetails:KeyboardShortcut:EditObject' => 'Edit displayed object~~', - 'UI:Layout:ObjectDetails:KeyboardShortcut:DeleteObject' => 'Delete displayed object~~', - 'UI:Layout:ObjectDetails:KeyboardShortcut:NewObject' => 'Create a new object (with same class as displayed object)~~', - 'UI:Layout:ObjectDetails:KeyboardShortcut:SaveObject' => 'Save displayed object~~', + 'UI:Layout:ObjectDetails:KeyboardShortcut:EditObject' => 'Modificar objeto atual', + 'UI:Layout:ObjectDetails:KeyboardShortcut:DeleteObject' => 'Excluir objeto atual', + 'UI:Layout:ObjectDetails:KeyboardShortcut:NewObject' => 'Criar um novo objeto (com a mesma classe do objeto atual)', + 'UI:Layout:ObjectDetails:KeyboardShortcut:SaveObject' => 'Salvar objeto atual', ]); diff --git a/dictionaries/ui/layouts/object-details/zh_cn.dictionary.itop.object-details.php b/dictionaries/ui/layouts/object-details/zh_cn.dictionary.itop.object-details.php index b0a061729c..43a0b768e4 100644 --- a/dictionaries/ui/layouts/object-details/zh_cn.dictionary.itop.object-details.php +++ b/dictionaries/ui/layouts/object-details/zh_cn.dictionary.itop.object-details.php @@ -4,8 +4,8 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ Dict::Add('ZH CN', 'Chinese', '简体中文', [ - 'UI:Layout:ObjectDetails:KeyboardShortcut:EditObject' => '编辑正在显示的对象', - 'UI:Layout:ObjectDetails:KeyboardShortcut:DeleteObject' => '删除正在显示的对象', - 'UI:Layout:ObjectDetails:KeyboardShortcut:NewObject' => '创建新对象 (与正在显示的对象相同)', - 'UI:Layout:ObjectDetails:KeyboardShortcut:SaveObject' => '保存正在显示的对象', + 'UI:Layout:ObjectDetails:KeyboardShortcut:EditObject' => '编辑当前对象', + 'UI:Layout:ObjectDetails:KeyboardShortcut:DeleteObject' => '删除当前对象', + 'UI:Layout:ObjectDetails:KeyboardShortcut:NewObject' => '创建新对象 (与当前对象相同)', + 'UI:Layout:ObjectDetails:KeyboardShortcut:SaveObject' => '保存当前对象', ]); diff --git a/dictionaries/ui/layouts/page-content/es_cr.dictionary.itop.page-content.php b/dictionaries/ui/layouts/page-content/es_cr.dictionary.itop.page-content.php index e1a4c57117..41ad07540b 100644 --- a/dictionaries/ui/layouts/page-content/es_cr.dictionary.itop.page-content.php +++ b/dictionaries/ui/layouts/page-content/es_cr.dictionary.itop.page-content.php @@ -4,5 +4,5 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [ - 'UIBlock:Error:CannotGetBlocks' => 'Could not retrieve blocks from content area "%1$s" as it does seem to exists for page content "%2$s"~~', -]); + 'UIBlock:Error:CannotGetBlocks' => 'No se pudieron recuperar bloques del área de contenido "%1$s", ya que parece existir para el contenido de la página "%2$s"', +]); \ No newline at end of file diff --git a/dictionaries/ui/layouts/page-content/nl.dictionary.itop.page-content.php b/dictionaries/ui/layouts/page-content/nl.dictionary.itop.page-content.php index 4d84f3d49b..7fa504db38 100644 --- a/dictionaries/ui/layouts/page-content/nl.dictionary.itop.page-content.php +++ b/dictionaries/ui/layouts/page-content/nl.dictionary.itop.page-content.php @@ -4,5 +4,5 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ Dict::Add('NL NL', 'Dutch', 'Nederlands', [ - 'UIBlock:Error:CannotGetBlocks' => 'Could not retrieve blocks from content area "%1$s" as it does seem to exists for page content "%2$s"~~', + 'UIBlock:Error:CannotGetBlocks' => 'Kan de blokken niet opvragen van inhoudsvak "%1$s" gezien het al lijkt te bestaan voor de paginainhoud "%2$s"', ]); diff --git a/dictionaries/ui/layouts/page-content/pl.dictionary.itop.page-content.php b/dictionaries/ui/layouts/page-content/pl.dictionary.itop.page-content.php new file mode 100644 index 0000000000..8be299eff1 --- /dev/null +++ b/dictionaries/ui/layouts/page-content/pl.dictionary.itop.page-content.php @@ -0,0 +1,8 @@ + 'Could not retrieve blocks from content area "%1$s" as it does seem to exists for page content "%2$s"~~', +]); diff --git a/dictionaries/ui/layouts/page-content/pt_br.dictionary.itop.page-content.php b/dictionaries/ui/layouts/page-content/pt_br.dictionary.itop.page-content.php index ade46f6a81..d0b392fc00 100644 --- a/dictionaries/ui/layouts/page-content/pt_br.dictionary.itop.page-content.php +++ b/dictionaries/ui/layouts/page-content/pt_br.dictionary.itop.page-content.php @@ -4,5 +4,5 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [ - 'UIBlock:Error:CannotGetBlocks' => 'Could not retrieve blocks from content area "%1$s" as it does seem to exists for page content "%2$s"~~', + 'UIBlock:Error:CannotGetBlocks' => 'Não é possível recuperar blocos da área de conteúdo "%1$s" porque ele parece existir para o conteúdo da página "%2$s"', ]); diff --git a/dictionaries/ui/layouts/tab-container/es_cr.dictionary.itop.tab-container.php b/dictionaries/ui/layouts/tab-container/es_cr.dictionary.itop.tab-container.php index 1c11c32381..f46416cbe7 100644 --- a/dictionaries/ui/layouts/tab-container/es_cr.dictionary.itop.tab-container.php +++ b/dictionaries/ui/layouts/tab-container/es_cr.dictionary.itop.tab-container.php @@ -4,7 +4,7 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [ - 'UI:Layout:TabContainer:ExtraTabsListToggler:Label' => 'Other tabs~~', - 'UIBlock:Error:AddBlockNotTabForbidden' => 'Cannot add block %1$s to %2$s (only Tab blocks are allowed)~~', - 'UIBlock:TabContainer:RemoteTabLoad' => 'Click to load this tab~~', -]); + 'UI:Layout:TabContainer:ExtraTabsListToggler:Label' => 'Otras pestañas', + 'UIBlock:Error:AddBlockNotTabForbidden' => 'No se puede agregar el bloque %1$s a %2$s (solo se permiten bloques de pestañas)', + 'UIBlock:TabContainer:RemoteTabLoad' => 'Dar click para cargar esta pestaña', +]); \ No newline at end of file diff --git a/dictionaries/ui/layouts/tab-container/nl.dictionary.itop.tab-container.php b/dictionaries/ui/layouts/tab-container/nl.dictionary.itop.tab-container.php index c5c79c46d2..1f73a98fc5 100644 --- a/dictionaries/ui/layouts/tab-container/nl.dictionary.itop.tab-container.php +++ b/dictionaries/ui/layouts/tab-container/nl.dictionary.itop.tab-container.php @@ -4,7 +4,7 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ Dict::Add('NL NL', 'Dutch', 'Nederlands', [ - 'UI:Layout:TabContainer:ExtraTabsListToggler:Label' => 'Other tabs~~', - 'UIBlock:Error:AddBlockNotTabForbidden' => 'Cannot add block %1$s to %2$s (only Tab blocks are allowed)~~', - 'UIBlock:TabContainer:RemoteTabLoad' => 'Click to load this tab~~', + 'UI:Layout:TabContainer:ExtraTabsListToggler:Label' => 'Andere tabbladen', + 'UIBlock:Error:AddBlockNotTabForbidden' => 'Kan blok %1$s niet toevoegen aan %2$s (enkel tabbladen zijn toegelaten)', + 'UIBlock:TabContainer:RemoteTabLoad' => 'Klik om dit tabblad in te laden', ]); diff --git a/dictionaries/ui/layouts/tab-container/pl.dictionary.itop.tab-container.php b/dictionaries/ui/layouts/tab-container/pl.dictionary.itop.tab-container.php new file mode 100644 index 0000000000..0b37133ce7 --- /dev/null +++ b/dictionaries/ui/layouts/tab-container/pl.dictionary.itop.tab-container.php @@ -0,0 +1,10 @@ + 'Other tabs~~', + 'UIBlock:Error:AddBlockNotTabForbidden' => 'Cannot add block %1$s to %2$s (only Tab blocks are allowed)~~', + 'UIBlock:TabContainer:RemoteTabLoad' => 'Click to load this tab~~', +]); diff --git a/dictionaries/ui/layouts/tab-container/pt_br.dictionary.itop.tab-container.php b/dictionaries/ui/layouts/tab-container/pt_br.dictionary.itop.tab-container.php index 0769b0d05a..2c676f0b18 100644 --- a/dictionaries/ui/layouts/tab-container/pt_br.dictionary.itop.tab-container.php +++ b/dictionaries/ui/layouts/tab-container/pt_br.dictionary.itop.tab-container.php @@ -4,7 +4,7 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [ - 'UI:Layout:TabContainer:ExtraTabsListToggler:Label' => 'Other tabs~~', - 'UIBlock:Error:AddBlockNotTabForbidden' => 'Cannot add block %1$s to %2$s (only Tab blocks are allowed)~~', - 'UIBlock:TabContainer:RemoteTabLoad' => 'Click to load this tab~~', + 'UI:Layout:TabContainer:ExtraTabsListToggler:Label' => 'Outras guias', + 'UIBlock:Error:AddBlockNotTabForbidden' => 'Não é possível adicionar o bloco %1$s a %2$s (apenas blocos de Guia são permitidos)', + 'UIBlock:TabContainer:RemoteTabLoad' => 'Clique para carregar esta guia', ]); diff --git a/dictionaries/ui/layouts/tab-container/zh_cn.dictionary.itop.tab-container.php b/dictionaries/ui/layouts/tab-container/zh_cn.dictionary.itop.tab-container.php index f92172ff72..484ff17685 100644 --- a/dictionaries/ui/layouts/tab-container/zh_cn.dictionary.itop.tab-container.php +++ b/dictionaries/ui/layouts/tab-container/zh_cn.dictionary.itop.tab-container.php @@ -4,7 +4,7 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ Dict::Add('ZH CN', 'Chinese', '简体中文', [ - 'UI:Layout:TabContainer:ExtraTabsListToggler:Label' => 'Other tabs~~', - 'UIBlock:Error:AddBlockNotTabForbidden' => 'Cannot add block %1$s to %2$s (only Tab blocks are allowed)~~', - 'UIBlock:TabContainer:RemoteTabLoad' => 'Click to load this tab~~', + 'UI:Layout:TabContainer:ExtraTabsListToggler:Label' => '其它标签页', + 'UIBlock:Error:AddBlockNotTabForbidden' => '无法添加 %1$s 到 %2$s (仅允许标签)', + 'UIBlock:TabContainer:RemoteTabLoad' => '点击加载此标签', ]); diff --git a/dictionaries/ui/layouts/ui-content-block/es_cr.dictionary.itop.ui-content-block.php b/dictionaries/ui/layouts/ui-content-block/es_cr.dictionary.itop.ui-content-block.php index ed9fa2d73e..06c736d117 100644 --- a/dictionaries/ui/layouts/ui-content-block/es_cr.dictionary.itop.ui-content-block.php +++ b/dictionaries/ui/layouts/ui-content-block/es_cr.dictionary.itop.ui-content-block.php @@ -4,5 +4,5 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [ - 'UIBlock:Error:AddBlockForbidden' => 'Cannot add block to %1$s~~', -]); + 'UIBlock:Error:AddBlockForbidden' => 'No se puede agregar bloque a %1$s', +]); \ No newline at end of file diff --git a/dictionaries/ui/layouts/ui-content-block/nl.dictionary.itop.ui-content-block.php b/dictionaries/ui/layouts/ui-content-block/nl.dictionary.itop.ui-content-block.php index d3ce24e956..410e78e4bd 100644 --- a/dictionaries/ui/layouts/ui-content-block/nl.dictionary.itop.ui-content-block.php +++ b/dictionaries/ui/layouts/ui-content-block/nl.dictionary.itop.ui-content-block.php @@ -4,5 +4,5 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ Dict::Add('NL NL', 'Dutch', 'Nederlands', [ - 'UIBlock:Error:AddBlockForbidden' => 'Cannot add block to %1$s~~', + 'UIBlock:Error:AddBlockForbidden' => 'Kan het blok niet toevoegen aan %1$s', ]); diff --git a/dictionaries/ui/layouts/ui-content-block/pl.dictionary.itop.ui-content-block.php b/dictionaries/ui/layouts/ui-content-block/pl.dictionary.itop.ui-content-block.php new file mode 100644 index 0000000000..b4e98f948f --- /dev/null +++ b/dictionaries/ui/layouts/ui-content-block/pl.dictionary.itop.ui-content-block.php @@ -0,0 +1,8 @@ + 'Cannot add block to %1$s~~', +]); diff --git a/dictionaries/ui/layouts/ui-content-block/pt_br.dictionary.itop.ui-content-block.php b/dictionaries/ui/layouts/ui-content-block/pt_br.dictionary.itop.ui-content-block.php index 6667954945..de9a51091a 100644 --- a/dictionaries/ui/layouts/ui-content-block/pt_br.dictionary.itop.ui-content-block.php +++ b/dictionaries/ui/layouts/ui-content-block/pt_br.dictionary.itop.ui-content-block.php @@ -4,5 +4,5 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [ - 'UIBlock:Error:AddBlockForbidden' => 'Cannot add block to %1$s~~', + 'UIBlock:Error:AddBlockForbidden' => 'Não é possível adicionar o bloco a %1$s', ]); diff --git a/dictionaries/ui/layouts/ui-content-block/zh_cn.dictionary.itop.ui-content-block.php b/dictionaries/ui/layouts/ui-content-block/zh_cn.dictionary.itop.ui-content-block.php index 6d64326b53..6f6ed380d8 100644 --- a/dictionaries/ui/layouts/ui-content-block/zh_cn.dictionary.itop.ui-content-block.php +++ b/dictionaries/ui/layouts/ui-content-block/zh_cn.dictionary.itop.ui-content-block.php @@ -4,5 +4,5 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ Dict::Add('ZH CN', 'Chinese', '简体中文', [ - 'UIBlock:Error:AddBlockForbidden' => 'Cannot add block to %1$s~~', + 'UIBlock:Error:AddBlockForbidden' => '无法添加到 %1$s', ]); diff --git a/dictionaries/ui/pages/errorpage/es_cr.dictionary.itop.errorpage.php b/dictionaries/ui/pages/errorpage/es_cr.dictionary.itop.errorpage.php index 875a5c72da..7f4790db1b 100644 --- a/dictionaries/ui/pages/errorpage/es_cr.dictionary.itop.errorpage.php +++ b/dictionaries/ui/pages/errorpage/es_cr.dictionary.itop.errorpage.php @@ -18,8 +18,8 @@ */ // Navigation menu Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'UI:ErrorPage:UnstableVersion' => 'You are using an unstable version that may include bugs. If you think this error occurred because of this please send us your feedback!~~', - 'UI:ErrorPage:KittyDisclaimer' => 'No kitty were injured during the making of this GIF and release. + 'UI:ErrorPage:UnstableVersion' => 'Está utilizando una versión inestable que puede incluir errores. Si cree que este error ocurrió debido a esto, ¡envíenos sus comentarios!', + 'UI:ErrorPage:KittyDisclaimer' => 'Ningún gatito resultó herido durante la realización de este GIF y lanzamiento. -- The R&D Team~~', +- El Equipo de I&D', )); \ No newline at end of file diff --git a/dictionaries/ui/pages/errorpage/pl.dictionary.itop.errorpage.php b/dictionaries/ui/pages/errorpage/pl.dictionary.itop.errorpage.php new file mode 100644 index 0000000000..9dd6f56c3b --- /dev/null +++ b/dictionaries/ui/pages/errorpage/pl.dictionary.itop.errorpage.php @@ -0,0 +1,25 @@ + 'You are using an unstable version that may include bugs. If you think this error occurred because of this please send us your feedback!~~', + 'UI:ErrorPage:KittyDisclaimer' => 'No kitty were injured during the making of this GIF and release. + +- The R&D Team~~', +)); \ No newline at end of file diff --git a/dictionaries/ui/pages/errorpage/pt_br.dictionary.itop.errorpage.php b/dictionaries/ui/pages/errorpage/pt_br.dictionary.itop.errorpage.php index ba22526b3c..9e0be72e7a 100644 --- a/dictionaries/ui/pages/errorpage/pt_br.dictionary.itop.errorpage.php +++ b/dictionaries/ui/pages/errorpage/pt_br.dictionary.itop.errorpage.php @@ -18,8 +18,8 @@ */ // Navigation menu Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'UI:ErrorPage:UnstableVersion' => 'You are using an unstable version that may include bugs. If you think this error occurred because of this please send us your feedback!~~', - 'UI:ErrorPage:KittyDisclaimer' => 'No kitty were injured during the making of this GIF and release. + 'UI:ErrorPage:UnstableVersion' => 'Você está usando uma versão instável que pode incluir bugs. Se você acha que esse erro ocorreu por causa disso, envie-nos o seu feedback!', + 'UI:ErrorPage:KittyDisclaimer' => 'Nenhum gatinho foi ferido durante a confecção deste GIF e lançamento de versão do '.ITOP_APPLICATION_SHORT.'. -- The R&D Team~~', +- The R&D Team', )); \ No newline at end of file diff --git a/dictionaries/ui/pages/preferences/de.dictionary.itop.preferences.php b/dictionaries/ui/pages/preferences/de.dictionary.itop.preferences.php index 1513986c25..5eb734d3e3 100644 --- a/dictionaries/ui/pages/preferences/de.dictionary.itop.preferences.php +++ b/dictionaries/ui/pages/preferences/de.dictionary.itop.preferences.php @@ -30,12 +30,12 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'UI:Preferences:RichText:ToolbarState:Collapsed' => 'Eingeklappt', 'UI:Preferences:ActivityPanel:Title' => 'Aktives Panel', 'UI:Preferences:ActivityPanel:EntryFormOpened' => 'Formular standardmäßig geöffnet', - 'UI:Preferences:ActivityPanel:EntryFormOpened+' => 'Ob ein Formular, wenn ein Objekt angezeigt wird, standardmäßig geöffnet ist. Wenn dieser Haken nicht gesetzt ist, können Sie das Formmular mit einem Klick auf den Compose-Button trotzdem jederzeit öffnen.', + 'UI:Preferences:ActivityPanel:EntryFormOpened+' => 'Ob ein Formular, wenn ein Objekt angezeigt wird, standardmäßig geöffnet ist. Wenn dieser Haken nicht gesetzt ist, können Sie das Formular mit einem Klick auf den Compose-Button trotzdem jederzeit öffnen.', 'UI:Preferences:PersonalizeKeyboardShortcuts:Title' => 'Keyboard-Shortcuts dieser Applikation', 'UI:Preferences:PersonalizeKeyboardShortcuts:Input:Hint' => 'Geben Sie einen Keyboard-Shortcut ein', 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Tooltip' => 'Nehmen Sie einen Keyboard-Shortcut auf', - 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Reset' => 'Reset~~', - 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Reset:Tooltip' => 'Back to default keyboard shortcut~~', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Reset' => 'Zurücksetzen', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Reset:Tooltip' => 'Auf den Standard-Keyboard-Shortcut zurücksetzen', 'UI:Preferences:Tabs:Title' => 'Tabs', 'UI:Preferences:Tabs:Layout:Label' => 'Layout', 'UI:Preferences:Tabs:Layout:Horizontal' => 'Horizontal', diff --git a/dictionaries/ui/pages/preferences/es_cr.dictionary.itop.preferences.php b/dictionaries/ui/pages/preferences/es_cr.dictionary.itop.preferences.php index fd85f8f9f1..5e61e44f5e 100644 --- a/dictionaries/ui/pages/preferences/es_cr.dictionary.itop.preferences.php +++ b/dictionaries/ui/pages/preferences/es_cr.dictionary.itop.preferences.php @@ -18,31 +18,31 @@ */ // Navigation menu Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'UI:Preferences:Title' => 'Preferences~~', - 'UI:Preferences:UserInterface:Title' => 'User interface~~', - 'UI:Preferences:General:Title' => 'General~~', - 'UI:Preferences:General:Theme' => 'Theme~~', - 'UI:Preferences:General:Theme:DefaultThemeLabel' => '%1$s (default)~~', - 'UI:Preferences:Lists:Title' => 'Lists~~', - 'UI:Preferences:RichText:Title' => 'Rich text editor~~', - 'UI:Preferences:RichText:ToolbarState' => 'Toolbar default state~~', - 'UI:Preferences:RichText:ToolbarState:Expanded' => 'Expanded~~', - 'UI:Preferences:RichText:ToolbarState:Collapsed' => 'Collapsed~~', - 'UI:Preferences:ActivityPanel:Title' => 'Activity panel~~', - 'UI:Preferences:ActivityPanel:EntryFormOpened' => 'Entry form opened by default~~', - 'UI:Preferences:ActivityPanel:EntryFormOpened+' => 'Whether the entry form will be opened when displaying an object. If unchecked, you will still be able to open it by clicking the compose button~~', - 'UI:Preferences:PersonalizeKeyboardShortcuts:Title' => 'Application keyboard shortcuts~~', - 'UI:Preferences:PersonalizeKeyboardShortcuts:Input:Hint' => 'Type a keyboard shortcut~~', - 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Tooltip' => 'Record a keyboard shortcut~~', - 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Reset' => 'Reset~~', - 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Reset:Tooltip' => 'Back to default keyboard shortcut~~', - 'UI:Preferences:Tabs:Title' => 'Tabs~~', - 'UI:Preferences:Tabs:Layout:Label' => 'Layout~~', - 'UI:Preferences:Tabs:Layout:Horizontal' => 'Horizontal~~', - 'UI:Preferences:Tabs:Layout:Vertical' => 'Vertical~~', - 'UI:Preferences:Tabs:Scrollable:Label' => 'Navigation~~', - 'UI:Preferences:Tabs:Scrollable:Classic' => 'Classic~~', - 'UI:Preferences:Tabs:Scrollable:Scrollable' => 'Scrollable~~', - 'UI:Preferences:ChooseAPlaceholder' => 'User placeholder image~~', - 'UI:Preferences:ChooseAPlaceholder+' => 'Choose a placeholder image that will be displayed if the contact linked to your user doesn\'t have one~~', -)); + 'UI:Preferences:Title' => 'Preferencias', + 'UI:Preferences:UserInterface:Title' => 'Interfaz de Usuario', + 'UI:Preferences:General:Title' => 'General', + 'UI:Preferences:General:Theme' => 'Tema', + 'UI:Preferences:General:Theme:DefaultThemeLabel' => '%1$s (predeterminado)', + 'UI:Preferences:Lists:Title' => 'Listas', + 'UI:Preferences:RichText:Title' => 'Editor de texto enriquecido', + 'UI:Preferences:RichText:ToolbarState' => 'Estado predeterminado de la barra de herramientas', + 'UI:Preferences:RichText:ToolbarState:Expanded' => 'Expandido', + 'UI:Preferences:RichText:ToolbarState:Collapsed' => 'Contraído', + 'UI:Preferences:ActivityPanel:Title' => 'Panel de Actividad', + 'UI:Preferences:ActivityPanel:EntryFormOpened' => 'Formulario de entrada abierto de forma predeterminada', + 'UI:Preferences:ActivityPanel:EntryFormOpened+' => 'Si el formulario de entrada se abrirá cuando se muestre un objeto. Si no está marcado, aún podrá abrirlo haciendo click en el botón de redacción', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Title' => 'Métodos abreviados de teclado de la aplicación', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Input:Hint' => 'Escriba un atajo de teclado', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Tooltip' => 'Grabar un atajo de teclado', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Reset' => 'Restablecer', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Reset:Tooltip' => 'Volver al método abreviado de teclado predeterminado', + 'UI:Preferences:Tabs:Title' => 'Pestañas', + 'UI:Preferences:Tabs:Layout:Label' => 'Formato', + 'UI:Preferences:Tabs:Layout:Horizontal' => 'Horizontal', + 'UI:Preferences:Tabs:Layout:Vertical' => 'Vertical', + 'UI:Preferences:Tabs:Scrollable:Label' => 'Navigación', + 'UI:Preferences:Tabs:Scrollable:Classic' => 'Clásico', + 'UI:Preferences:Tabs:Scrollable:Scrollable' => 'Desplazable', + 'UI:Preferences:ChooseAPlaceholder' => 'Imagen de marcador de posición de usuario', + 'UI:Preferences:ChooseAPlaceholder+' => 'Elija una imagen de marcador de posición que se mostrará si el contacto vinculado a su usuario no tiene uno', +)); \ No newline at end of file diff --git a/dictionaries/ui/pages/preferences/nl.dictionary.itop.preferences.php b/dictionaries/ui/pages/preferences/nl.dictionary.itop.preferences.php index db44e1c7cf..d05608a32c 100644 --- a/dictionaries/ui/pages/preferences/nl.dictionary.itop.preferences.php +++ b/dictionaries/ui/pages/preferences/nl.dictionary.itop.preferences.php @@ -24,18 +24,18 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'UI:Preferences:General:Theme' => 'Thema', 'UI:Preferences:General:Theme:DefaultThemeLabel' => '%1$s (standaard)', 'UI:Preferences:Lists:Title' => 'Lijsten', - 'UI:Preferences:RichText:Title' => 'Rich text editor~~', + 'UI:Preferences:RichText:Title' => 'Rich text editor', 'UI:Preferences:RichText:ToolbarState' => 'Standaard status werkbalk', - 'UI:Preferences:RichText:ToolbarState:Expanded' => 'Uitgeklapt~~', - 'UI:Preferences:RichText:ToolbarState:Collapsed' => 'Ingeklapt~~', + 'UI:Preferences:RichText:ToolbarState:Expanded' => 'Uitgeklapt', + 'UI:Preferences:RichText:ToolbarState:Collapsed' => 'Ingeklapt', 'UI:Preferences:ActivityPanel:Title' => 'Activiteitenpaneel', - 'UI:Preferences:ActivityPanel:EntryFormOpened' => 'Entry form opened by default~~', - 'UI:Preferences:ActivityPanel:EntryFormOpened+' => 'Whether the entry form will be opened when displaying an object. If unchecked, you will still be able to open it by clicking the compose button~~', + 'UI:Preferences:ActivityPanel:EntryFormOpened' => 'Invoerformulier standaard opengeklapt', + 'UI:Preferences:ActivityPanel:EntryFormOpened+' => 'Of het invulformulier standaard opengeklapt is terwijl een object getoond wordt. Als dit uitgevinkt is, kan je het nog altijd openen door op de "Log toevoegen"-knop te klikken.', 'UI:Preferences:PersonalizeKeyboardShortcuts:Title' => 'Sneltoetsen', 'UI:Preferences:PersonalizeKeyboardShortcuts:Input:Hint' => 'Typ een sneltoets', 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Tooltip' => 'Neem een sneltoets op', - 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Reset' => 'Reset~~', - 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Reset:Tooltip' => 'Back to default keyboard shortcut~~', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Reset' => 'Reset', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Reset:Tooltip' => 'Terugzetten naar de standaard sneltoetsen', 'UI:Preferences:Tabs:Title' => 'Tabs', 'UI:Preferences:Tabs:Layout:Label' => 'Layout', 'UI:Preferences:Tabs:Layout:Horizontal' => 'Horizontaal', @@ -44,5 +44,5 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'UI:Preferences:Tabs:Scrollable:Classic' => 'Klassiek', 'UI:Preferences:Tabs:Scrollable:Scrollable' => 'Scrollbaar', 'UI:Preferences:ChooseAPlaceholder' => 'Gebruikersafbeelding placeholder', - 'UI:Preferences:ChooseAPlaceholder+' => 'Choose a placeholder image that will be displayed if the contact linked to your user doesn\'t have one~~', + 'UI:Preferences:ChooseAPlaceholder+' => 'Kies een standaard afbeelding die getoond wordt als het contact gelinkt aan jouw gebruiker geen eigen afbeelding heeft.', )); diff --git a/dictionaries/ui/pages/preferences/pl.dictionary.itop.preferences.php b/dictionaries/ui/pages/preferences/pl.dictionary.itop.preferences.php new file mode 100644 index 0000000000..86ad140537 --- /dev/null +++ b/dictionaries/ui/pages/preferences/pl.dictionary.itop.preferences.php @@ -0,0 +1,48 @@ + 'Preferences~~', + 'UI:Preferences:UserInterface:Title' => 'User interface~~', + 'UI:Preferences:General:Title' => 'General~~', + 'UI:Preferences:General:Theme' => 'Theme~~', + 'UI:Preferences:General:Theme:DefaultThemeLabel' => '%1$s (default)~~', + 'UI:Preferences:Lists:Title' => 'Lists~~', + 'UI:Preferences:RichText:Title' => 'Rich text editor~~', + 'UI:Preferences:RichText:ToolbarState' => 'Toolbar default state~~', + 'UI:Preferences:RichText:ToolbarState:Expanded' => 'Expanded~~', + 'UI:Preferences:RichText:ToolbarState:Collapsed' => 'Collapsed~~', + 'UI:Preferences:ActivityPanel:Title' => 'Activity panel~~', + 'UI:Preferences:ActivityPanel:EntryFormOpened' => 'Entry form opened by default~~', + 'UI:Preferences:ActivityPanel:EntryFormOpened+' => 'Whether the entry form will be opened when displaying an object. If unchecked, you will still be able to open it by clicking the compose button~~', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Title' => 'Application keyboard shortcuts~~', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Input:Hint' => 'Type a keyboard shortcut~~', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Tooltip' => 'Record a keyboard shortcut~~', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Reset' => 'Reset~~', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Reset:Tooltip' => 'Back to default keyboard shortcut~~', + 'UI:Preferences:Tabs:Title' => 'Tabs~~', + 'UI:Preferences:Tabs:Layout:Label' => 'Layout~~', + 'UI:Preferences:Tabs:Layout:Horizontal' => 'Horizontal~~', + 'UI:Preferences:Tabs:Layout:Vertical' => 'Vertical~~', + 'UI:Preferences:Tabs:Scrollable:Label' => 'Navigation~~', + 'UI:Preferences:Tabs:Scrollable:Classic' => 'Classic~~', + 'UI:Preferences:Tabs:Scrollable:Scrollable' => 'Scrollable~~', + 'UI:Preferences:ChooseAPlaceholder' => 'User placeholder image~~', + 'UI:Preferences:ChooseAPlaceholder+' => 'Choose a placeholder image that will be displayed if the contact linked to your user doesn\'t have one~~', +)); diff --git a/dictionaries/ui/pages/preferences/pt_br.dictionary.itop.preferences.php b/dictionaries/ui/pages/preferences/pt_br.dictionary.itop.preferences.php index a5604f9566..bd353a7763 100644 --- a/dictionaries/ui/pages/preferences/pt_br.dictionary.itop.preferences.php +++ b/dictionaries/ui/pages/preferences/pt_br.dictionary.itop.preferences.php @@ -18,31 +18,31 @@ */ // Navigation menu Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( - 'UI:Preferences:Title' => 'Preferences~~', - 'UI:Preferences:UserInterface:Title' => 'User interface~~', - 'UI:Preferences:General:Title' => 'General~~', - 'UI:Preferences:General:Theme' => 'Theme~~', - 'UI:Preferences:General:Theme:DefaultThemeLabel' => '%1$s (default)~~', - 'UI:Preferences:Lists:Title' => 'Lists~~', - 'UI:Preferences:RichText:Title' => 'Rich text editor~~', - 'UI:Preferences:RichText:ToolbarState' => 'Toolbar default state~~', - 'UI:Preferences:RichText:ToolbarState:Expanded' => 'Expanded~~', - 'UI:Preferences:RichText:ToolbarState:Collapsed' => 'Collapsed~~', - 'UI:Preferences:ActivityPanel:Title' => 'Activity panel~~', - 'UI:Preferences:ActivityPanel:EntryFormOpened' => 'Entry form opened by default~~', - 'UI:Preferences:ActivityPanel:EntryFormOpened+' => 'Whether the entry form will be opened when displaying an object. If unchecked, you will still be able to open it by clicking the compose button~~', - 'UI:Preferences:PersonalizeKeyboardShortcuts:Title' => 'Application keyboard shortcuts~~', - 'UI:Preferences:PersonalizeKeyboardShortcuts:Input:Hint' => 'Type a keyboard shortcut~~', - 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Tooltip' => 'Record a keyboard shortcut~~', - 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Reset' => 'Reset~~', - 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Reset:Tooltip' => 'Back to default keyboard shortcut~~', - 'UI:Preferences:Tabs:Title' => 'Tabs~~', - 'UI:Preferences:Tabs:Layout:Label' => 'Layout~~', - 'UI:Preferences:Tabs:Layout:Horizontal' => 'Horizontal~~', - 'UI:Preferences:Tabs:Layout:Vertical' => 'Vertical~~', - 'UI:Preferences:Tabs:Scrollable:Label' => 'Navigation~~', - 'UI:Preferences:Tabs:Scrollable:Classic' => 'Classic~~', - 'UI:Preferences:Tabs:Scrollable:Scrollable' => 'Scrollable~~', - 'UI:Preferences:ChooseAPlaceholder' => 'User placeholder image~~', - 'UI:Preferences:ChooseAPlaceholder+' => 'Choose a placeholder image that will be displayed if the contact linked to your user doesn\'t have one~~', + 'UI:Preferences:Title' => 'Preferências', + 'UI:Preferences:UserInterface:Title' => 'Interface de usuário', + 'UI:Preferences:General:Title' => 'Geral', + 'UI:Preferences:General:Theme' => 'Tema', + 'UI:Preferences:General:Theme:DefaultThemeLabel' => '%1$s (padrão)', + 'UI:Preferences:Lists:Title' => 'Listas', + 'UI:Preferences:RichText:Title' => 'Editor de Rich Text', + 'UI:Preferences:RichText:ToolbarState' => 'Estado padrão da barra de ferramentas', + 'UI:Preferences:RichText:ToolbarState:Expanded' => 'Expandida', + 'UI:Preferences:RichText:ToolbarState:Collapsed' => 'Recolhida', + 'UI:Preferences:ActivityPanel:Title' => 'Painel de atividades', + 'UI:Preferences:ActivityPanel:EntryFormOpened' => 'Formulário de adicionar nova entrada aberto por padrão', + 'UI:Preferences:ActivityPanel:EntryFormOpened+' => 'Se o formulário de adicionar nova entrada será aberto automaticamente ao abrir o Painel de Atividades. Se desmarcado, você ainda poderá abri-lo clicando no botão de "adicionar"', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Title' => 'Atalhos de teclado de aplicativo', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Input:Hint' => 'Pressione um atalho de teclado', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Tooltip' => 'Grave um atalho de teclado', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Reset' => 'Redefinir', + 'UI:Preferences:PersonalizeKeyboardShortcuts:Button:Reset:Tooltip' => 'Voltar ao atalho padrão do teclado', + 'UI:Preferences:Tabs:Title' => 'Guias', + 'UI:Preferences:Tabs:Layout:Label' => 'Layout', + 'UI:Preferences:Tabs:Layout:Horizontal' => 'Horizontal', + 'UI:Preferences:Tabs:Layout:Vertical' => 'Vertical', + 'UI:Preferences:Tabs:Scrollable:Label' => 'Navegação', + 'UI:Preferences:Tabs:Scrollable:Classic' => 'Clássica', + 'UI:Preferences:Tabs:Scrollable:Scrollable' => 'Rolável', + 'UI:Preferences:ChooseAPlaceholder' => 'Avatar padrão do usuário', + 'UI:Preferences:ChooseAPlaceholder+' => 'Escolha uma imagem padrão que será exibida caso o contato associado ao usuário não possuir nenhuma', )); diff --git a/dictionaries/ui/pages/preferences/zh_cn.dictionary.itop.preferences.php b/dictionaries/ui/pages/preferences/zh_cn.dictionary.itop.preferences.php index 4dfe47a86e..5890a6ec69 100644 --- a/dictionaries/ui/pages/preferences/zh_cn.dictionary.itop.preferences.php +++ b/dictionaries/ui/pages/preferences/zh_cn.dictionary.itop.preferences.php @@ -44,5 +44,5 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'UI:Preferences:Tabs:Scrollable:Classic' => '经典', 'UI:Preferences:Tabs:Scrollable:Scrollable' => '可滚动', 'UI:Preferences:ChooseAPlaceholder' => '用户的默认头像', - 'UI:Preferences:ChooseAPlaceholder+' => 'Choose a placeholder image that will be displayed if the contact linked to your user doesn\'t have one~~', + 'UI:Preferences:ChooseAPlaceholder+' => '选择一个占位图片,将在用户联系人没有设定头像图片时显示', )); diff --git a/dictionaries/zh_cn.dictionary.itop.core.php b/dictionaries/zh_cn.dictionary.itop.core.php index 41a2d5e226..cd28f6b965 100644 --- a/dictionaries/zh_cn.dictionary.itop.core.php +++ b/dictionaries/zh_cn.dictionary.itop.core.php @@ -22,22 +22,24 @@ */ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Core:DeletedObjectLabel' => '%1s (已删除)', - 'Core:DeletedObjectTip' => 'The object has been deleted on %1$s (%2$s)', + 'Core:DeletedObjectTip' => '对象已被删除于 %1$s (%2$s)', 'Core:UnknownObjectLabel' => '对象找不到 (class: %1$s, id: %2$d)', - 'Core:UnknownObjectTip' => 'The object could not be found. It may have been deleted some time ago and the log has been purged since.', + 'Core:UnknownObjectTip' => 'The object could not be found. It may have been deleted some time ago and the log has been purged since.', 'Core:UniquenessDefaultError' => 'Uniqueness rule \'%1$s\' in error~~', + 'Core:CheckConsistencyError' => 'Consistency rules not followed: %1$s~~', + 'Core:CheckValueError' => 'Unexpected value for attribute \'%1$s\' (%2$s) : %3$s~~', - 'Core:AttributeLinkedSet' => '对象数组', + 'Core:AttributeLinkedSet' => '对象数组', 'Core:AttributeLinkedSet+' => 'Any kind of objects of the same class or subclass', 'Core:AttributeLinkedSetDuplicatesFound' => 'Duplicates in the \'%1$s\' field : %2$s~~', - 'Core:AttributeDashboard' => '仪表盘', + 'Core:AttributeDashboard' => '仪表盘', 'Core:AttributeDashboard+' => '', - 'Core:AttributePhoneNumber' => '电话号码', + 'Core:AttributePhoneNumber' => '电话号码', 'Core:AttributePhoneNumber+' => '', 'Core:AttributeObsolescenceDate' => '报废日期', @@ -73,7 +75,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Core:AttributeArchiveFlag' => '是否归档', 'Core:AttributeArchiveFlag/Value:yes' => '是', - 'Core:AttributeArchiveFlag/Value:yes+' => '该对象仅在归档模式可见', + 'Core:AttributeArchiveFlag/Value:yes+' => '此对象仅在归档模式可见', 'Core:AttributeArchiveFlag/Value:no' => '否', 'Core:AttributeArchiveFlag/Label' => '已归档', 'Core:AttributeArchiveFlag/Label+' => '', @@ -113,18 +115,18 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Core:AttributeText+' => '多行字符串', 'Core:AttributeHTML' => 'HTML', - 'Core:AttributeHTML+' => 'HTML string', + 'Core:AttributeHTML+' => 'HTML字符串', 'Core:AttributeEmailAddress' => '邮箱地址', 'Core:AttributeEmailAddress+' => '邮箱地址', - 'Core:AttributeIPAddress' => 'IP 地址', - 'Core:AttributeIPAddress+' => 'IP 地址', + 'Core:AttributeIPAddress' => 'IP地址', + 'Core:AttributeIPAddress+' => 'IP地址', 'Core:AttributeOQL' => 'OQL', 'Core:AttributeOQL+' => 'Object Query Langage expression', - 'Core:AttributeEnum' => 'Enum', + 'Core:AttributeEnum' => '列举', 'Core:AttributeEnum+' => 'List of predefined alphanumeric strings', 'Core:AttributeTemplateString' => '字符模板', @@ -133,11 +135,11 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Core:AttributeTemplateText' => '文字模板', 'Core:AttributeTemplateText+' => '包含占位符的文本', - 'Core:AttributeTemplateHTML' => 'HTML 模板', + 'Core:AttributeTemplateHTML' => 'HTML模板', 'Core:AttributeTemplateHTML+' => 'HTML containing placeholders', 'Core:AttributeDateTime' => '日期/时间', - 'Core:AttributeDateTime+' => 'Date and time (year-month-day hh:mm:ss)', + 'Core:AttributeDateTime+' => 'Date and time (年-月-日 时:分:秒)', 'Core:AttributeDateTime?SmartSearch' => '

          日期格式:
          @@ -178,7 +180,7 @@ Operators:
          'Core:AttributeHierarchicalKey' => 'Hierarchical Key', 'Core:AttributeHierarchicalKey+' => 'External (or foreign) key to the parent', - 'Core:AttributeExternalField' => 'External field', + 'Core:AttributeExternalField' => '外部字段', 'Core:AttributeExternalField+' => 'Field mapped to an external key', 'Core:AttributeURL' => 'URL', @@ -190,8 +192,8 @@ Operators:
          'Core:AttributeOneWayPassword' => '单向密码', 'Core:AttributeOneWayPassword+' => '单向加密(或哈希) 的密码', - 'Core:AttributeTable' => 'Table', - 'Core:AttributeTable+' => 'Indexed array having two dimensions', + 'Core:AttributeTable' => '表', + 'Core:AttributeTable+' => '带索引的二维数组', 'Core:AttributePropertySet' => '属性', 'Core:AttributePropertySet+' => 'List of untyped properties (name and value)', @@ -206,10 +208,10 @@ Operators:
          'Core:AttributeTag+' => '标签', 'Core:Context=REST/JSON' => 'REST', - 'Core:Context=Synchro' => 'Synchro~~', + 'Core:Context=Synchro' => '同步', 'Core:Context=Setup' => '安装向导', - 'Core:Context=GUI:Console' => 'Console~~', - 'Core:Context=CRON' => 'cron~~', + 'Core:Context=GUI:Console' => '命令行', + 'Core:Context=CRON' => '定时任务', 'Core:Context=GUI:Portal' => '门户', )); @@ -228,11 +230,11 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:CMDBChange+' => '变更跟踪', 'Class:CMDBChange/Attribute:date' => '日期', 'Class:CMDBChange/Attribute:date+' => '变更被记录的日期和时间', - 'Class:CMDBChange/Attribute:userinfo' => '杂项. 信息', + 'Class:CMDBChange/Attribute:userinfo' => '杂项信息', 'Class:CMDBChange/Attribute:userinfo+' => '呼叫者已定义的信息', - 'Class:CMDBChange/Attribute:origin/Value:interactive' => 'User interaction in the GUI~~', - 'Class:CMDBChange/Attribute:origin/Value:csv-import.php' => 'CSV import script~~', - 'Class:CMDBChange/Attribute:origin/Value:csv-interactive' => 'CSV import in the GUI~~', + 'Class:CMDBChange/Attribute:origin/Value:interactive' => '图形界面交互', + 'Class:CMDBChange/Attribute:origin/Value:csv-import.php' => '使用脚本导入CSV', + 'Class:CMDBChange/Attribute:origin/Value:csv-interactive' => '使用图形界面导入CSV', 'Class:CMDBChange/Attribute:origin/Value:email-processing' => 'Email processing~~', 'Class:CMDBChange/Attribute:origin/Value:synchro-data-source' => 'Synchro. data source~~', 'Class:CMDBChange/Attribute:origin/Value:webservice-rest' => 'REST/JSON webservices~~', @@ -298,7 +300,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:CMDBChangeOpSetAttributeScalar' => '属性更改跟踪', 'Class:CMDBChangeOpSetAttributeScalar+' => '对象属性更改跟踪', 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue' => '旧值', - 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue+' => '以前该属性的值', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue+' => '以前此属性的值', 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue' => '新值', 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue+' => '属性的新值', )); @@ -330,7 +332,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:CMDBChangeOpSetAttributeBlob' => '数据变更跟踪', 'Class:CMDBChangeOpSetAttributeBlob+' => '数据变更跟踪', 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata' => '之前的值', - 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata+' => '该数据之前的内容', + 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata+' => '此数据之前的内容', )); // @@ -341,7 +343,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:CMDBChangeOpSetAttributeText' => '文本变更跟踪', 'Class:CMDBChangeOpSetAttributeText+' => '文本变更跟踪', 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata' => '旧值', - 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata+' => '该文本之前的内容', + 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata+' => '此文本之前的内容', )); // @@ -412,10 +414,10 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:EventIssue/Attribute:impact+' => '重要性如何', 'Class:EventIssue/Attribute:page' => 'Page', 'Class:EventIssue/Attribute:page+' => 'HTTP entry point', - 'Class:EventIssue/Attribute:arguments_post' => 'POST 参数', - 'Class:EventIssue/Attribute:arguments_post+' => 'HTTP POST 参数', - 'Class:EventIssue/Attribute:arguments_get' => 'URL 参数', - 'Class:EventIssue/Attribute:arguments_get+' => 'HTTP GET 参数', + 'Class:EventIssue/Attribute:arguments_post' => 'POST参数', + 'Class:EventIssue/Attribute:arguments_post+' => 'HTTP POST参数', + 'Class:EventIssue/Attribute:arguments_get' => 'URL参数', + 'Class:EventIssue/Attribute:arguments_get+' => 'HTTP GET参数', 'Class:EventIssue/Attribute:callstack' => '调用栈', 'Class:EventIssue/Attribute:callstack+' => 'Call stack', 'Class:EventIssue/Attribute:data' => '数据', @@ -455,9 +457,9 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:EventRestService/Attribute:code' => '代码', 'Class:EventRestService/Attribute:code+' => '返回代码', 'Class:EventRestService/Attribute:json_output' => '响应', - 'Class:EventRestService/Attribute:json_output+' => 'HTTP 响应 (json)', - 'Class:EventRestService/Attribute:provider' => 'Provider', - 'Class:EventRestService/Attribute:provider+' => 'PHP class implementing the expected operation', + 'Class:EventRestService/Attribute:json_output+' => 'HTTP响应 (json)', + 'Class:EventRestService/Attribute:provider' => '提供者', + 'Class:EventRestService/Attribute:provider+' => '实现此功能的PHP类', )); // @@ -495,7 +497,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:Action/Attribute:status/Value:disabled' => '停用', 'Class:Action/Attribute:status/Value:disabled+' => '停用', 'Class:Action/Attribute:trigger_list' => '相关的触发器', - 'Class:Action/Attribute:trigger_list+' => '该操作关联的触发器', + 'Class:Action/Attribute:trigger_list+' => '此操作关联的触发器', 'Class:Action/Attribute:finalclass' => 'Action sub-class', 'Class:Action/Attribute:finalclass+' => 'Name of the final class', 'Action:WarningNoTriggerLinked' => 'Warning, no trigger is linked to the action. It will not be active until it has at least 1.~~', @@ -518,9 +520,9 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:ActionEmail' => '邮件通知', 'Class:ActionEmail+' => '', 'Class:ActionEmail/Attribute:status+' => 'This status drives who will be notified: just the Test recipient, all (To, cc and Bcc) or no-one~~', - 'Class:ActionEmail/Attribute:status/Value:test+' => 'Only the Test recipient is notified~~', - 'Class:ActionEmail/Attribute:status/Value:enabled+' => 'All To, Cc and Bcc emails are notified~~', - 'Class:ActionEmail/Attribute:status/Value:disabled+' => 'The email notification will not be sent~~', + 'Class:ActionEmail/Attribute:status/Value:test' => 'Only the Test recipient is notified~~', + 'Class:ActionEmail/Attribute:status/Value:enabled' => 'All To, Cc and Bcc emails are notified~~', + 'Class:ActionEmail/Attribute:status/Value:disabled' => 'The email notification will not be sent~~', 'Class:ActionEmail/Attribute:test_recipient' => '测试收件人', 'Class:ActionEmail/Attribute:test_recipient+' => 'Detination in case status is set to "Test"', 'Class:ActionEmail/Attribute:from' => '发件人~~', @@ -742,7 +744,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Core:Synchro:ModifiedLabel' => '已修改', 'Core:Synchro:UnchangedLabel' => '保持不变', 'Core:Synchro:ReconciledErrorsLabel' => '错误', - 'Core:Synchro:ReconciledLabel' => 'Reconciled', + 'Core:Synchro:ReconciledLabel' => '已核对', 'Core:Synchro:ReconciledNewLabel' => '已创建', 'Core:SynchroReconcile:Yes' => '是', 'Core:SynchroReconcile:No' => '否', @@ -839,13 +841,13 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:SynchroDataSource/Attribute:delete_policy/Value:update_then_delete' => '先更新再删除', 'Class:SynchroDataSource/Attribute:attribute_list' => '属性列表', 'Class:SynchroDataSource/Attribute:user_delete_policy/Value:administrators' => '仅限管理员', - 'Class:SynchroDataSource/Attribute:user_delete_policy/Value:everybody' => 'Everybody allowed to delete such objects', + 'Class:SynchroDataSource/Attribute:user_delete_policy/Value:everybody' => '允许所有人删除此对象', 'Class:SynchroDataSource/Attribute:user_delete_policy/Value:nobody' => 'Nobody', 'Class:SynchroAttribute' => '同步属性', 'Class:SynchroAttribute/Attribute:sync_source_id' => '同步数据源', 'Class:SynchroAttribute/Attribute:attcode' => '属性代码', 'Class:SynchroAttribute/Attribute:update' => '更新', - 'Class:SynchroAttribute/Attribute:reconcile' => 'Reconcile', + 'Class:SynchroAttribute/Attribute:reconcile' => '核对', 'Class:SynchroAttribute/Attribute:update_policy' => '更新策略', 'Class:SynchroAttribute/Attribute:update_policy/Value:master_locked' => '加锁', 'Class:SynchroAttribute/Attribute:update_policy/Value:master_unlocked' => '解锁', @@ -924,7 +926,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Core:BulkExport:HTMLFormat' => '网页 (*.html)', 'Core:BulkExport:CSVFormat' => 'CSV (*.csv)', 'Core:BulkExport:XLSXFormat' => 'Excel 2007+ (*.xlsx)', - 'Core:BulkExport:PDFFormat' => 'PDF 文档 (*.pdf)', + 'Core:BulkExport:PDFFormat' => 'PDF文档 (*.pdf)', 'Core:BulkExport:DragAndDropHelp' => '可拖动或删除列头进行排序. 正在预览 %1$s 行. 一共需要导出: %2$s 行.', 'Core:BulkExport:EmptyPreview' => '请选择要导出的列', 'Core:BulkExport:ColumnsOrder' => '列顺序', @@ -933,9 +935,9 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Core:BulkExport:CheckAll' => '全选', 'Core:BulkExport:UncheckAll' => '反选', 'Core:BulkExport:ExportCancelledByUser' => '导出被用户取消', - 'Core:BulkExport:CSVOptions' => 'CSV 选项', + 'Core:BulkExport:CSVOptions' => 'CSV选项', 'Core:BulkExport:CSVLocalization' => '本地化', - 'Core:BulkExport:PDFOptions' => 'PDF 选项', + 'Core:BulkExport:PDFOptions' => 'PDF选项', 'Core:BulkExport:PDFPageFormat' => '页面格式', 'Core:BulkExport:PDFPageSize' => '页面大小:', 'Core:BulkExport:PageSize-A4' => 'A4', @@ -952,14 +954,14 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Core:BulkExport:OptionLinkSets' => '包含外链的对象', 'Core:BulkExport:OptionFormattedText' => '保持文本格式', 'Core:BulkExport:ScopeDefinition' => '定义要导出的对象', - 'Core:BulkExportLabelOQLExpression' => 'OQL 查询:', + 'Core:BulkExportLabelOQLExpression' => 'OQL查询:', 'Core:BulkExportLabelPhrasebookEntry' => 'Query Phrasebook Entry:', - 'Core:BulkExportMessageEmptyOQL' => '请输入有效的OQL 查询.', + 'Core:BulkExportMessageEmptyOQL' => '请输入有效的OQL查询.', 'Core:BulkExportMessageEmptyPhrasebookEntry' => 'Please select a valid phrasebook entry.', - 'Core:BulkExportQueryPlaceholder' => '请在这里输入OQL 查询...', + 'Core:BulkExportQueryPlaceholder' => '请在这里输入OQL查询...', 'Core:BulkExportCanRunNonInteractive' => '点击这里运行非交互式导出.', 'Core:BulkExportLegacyExport' => '点击这里进入旧版导出.', - 'Core:BulkExport:XLSXOptions' => 'Excel 选项', + 'Core:BulkExport:XLSXOptions' => 'Excel选项', 'Core:BulkExport:TextFormat' => '文本中包含一些HTML 标记', 'Core:BulkExport:DateTimeFormat' => '日期和时间格式', 'Core:BulkExport:DateTimeFormatDefault_Example' => '默认格式 (%1$s), e.g. %2$s', @@ -1018,7 +1020,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( // Class: DBProperty // Dict::Add('ZH CN', 'Chinese', '简体中文', array( - 'Class:DBProperty' => 'DB 属性', + 'Class:DBProperty' => '数据库属性', 'Class:DBProperty+' => '~~', 'Class:DBProperty/Attribute:name' => '名称', 'Class:DBProperty/Attribute:name+' => '', diff --git a/dictionaries/zh_cn.dictionary.itop.ui.php b/dictionaries/zh_cn.dictionary.itop.ui.php index d87ae4d0ba..8b05300d7e 100644 --- a/dictionaries/zh_cn.dictionary.itop.ui.php +++ b/dictionaries/zh_cn.dictionary.itop.ui.php @@ -25,11 +25,11 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:AuditCategory/Attribute:name' => '类别名称', 'Class:AuditCategory/Attribute:name+' => '类别简称', 'Class:AuditCategory/Attribute:description' => '审计类别描述', - 'Class:AuditCategory/Attribute:description+' => '该审计类别的详细描述', + 'Class:AuditCategory/Attribute:description+' => '此审计类别的详细描述', 'Class:AuditCategory/Attribute:definition_set' => '定义', 'Class:AuditCategory/Attribute:definition_set+' => '定义用于审计的对象的OQL表达式', 'Class:AuditCategory/Attribute:rules_list' => '审计规则', - 'Class:AuditCategory/Attribute:rules_list+' => '该类别的审计规则', + 'Class:AuditCategory/Attribute:rules_list+' => '此类别的审计规则', )); // @@ -43,11 +43,11 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:AuditRule/Attribute:name+' => '规则名称', 'Class:AuditRule/Attribute:description' => '审计规则描述', 'Class:AuditRule/Attribute:description+' => '审计规则详细描述', - 'Class:TagSetFieldData/Attribute:finalclass' => 'Tag class~~', - 'Class:TagSetFieldData/Attribute:obj_class' => 'Object class~~', + 'Class:TagSetFieldData/Attribute:finalclass' => '标签类', + 'Class:TagSetFieldData/Attribute:obj_class' => '对象类', 'Class:TagSetFieldData/Attribute:obj_attcode' => 'Field code~~', 'Class:AuditRule/Attribute:query' => '要运行的查询', - 'Class:AuditRule/Attribute:query+' => '要运行的OQL 表达式', + 'Class:AuditRule/Attribute:query+' => '要运行的OQL表达式', 'Class:AuditRule/Attribute:valid_flag' => '是否有效?', 'Class:AuditRule/Attribute:valid_flag+' => '若规则返回有效对象则True,否则False', 'Class:AuditRule/Attribute:valid_flag/Value:true' => 'true', @@ -55,9 +55,9 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:AuditRule/Attribute:valid_flag/Value:false' => 'false', 'Class:AuditRule/Attribute:valid_flag/Value:false+' => 'false', 'Class:AuditRule/Attribute:category_id' => '类别', - 'Class:AuditRule/Attribute:category_id+' => '该规则对应的类别', + 'Class:AuditRule/Attribute:category_id+' => '从规则对应的类别', 'Class:AuditRule/Attribute:category_name' => '类别', - 'Class:AuditRule/Attribute:category_name+' => '该规则对应类别的名称', + 'Class:AuditRule/Attribute:category_name+' => '此规则对应类别的名称', )); // @@ -70,17 +70,17 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:Query/Attribute:name' => '名称', 'Class:Query/Attribute:name+' => '查询的名称', 'Class:Query/Attribute:description' => '描述', - 'Class:Query/Attribute:description+' => '请描述此查询 (目的、用法等等.)', - 'Class:Query/Attribute:is_template' => 'Template for OQL fields~~', + 'Class:Query/Attribute:description+' => '请描述此查询 (目的,用法等等.)', + 'Class:Query/Attribute:is_template' => 'OQL模板', 'Class:Query/Attribute:is_template+' => 'Usable as source for recipient OQL in Notifications~~', 'Class:Query/Attribute:is_template/Value:yes' => '是', 'Class:Query/Attribute:is_template/Value:no' => '否', 'Class:QueryOQL/Attribute:fields' => '区域', 'Class:QueryOQL/Attribute:fields+' => '属性之间使用逗号分隔 (or alias.attribute) to export~~', - 'Class:QueryOQL' => 'OQL 查询', - 'Class:QueryOQL+' => 'A query based on the Object Query Language', + 'Class:QueryOQL' => 'OQL查询', + 'Class:QueryOQL+' => '一种基于对象查询的语言', 'Class:QueryOQL/Attribute:oql' => '表达式', - 'Class:QueryOQL/Attribute:oql+' => 'OQL 表达式', + 'Class:QueryOQL/Attribute:oql+' => 'OQL表达式', )); ////////////////////////////////////////////////////////////////////// @@ -112,11 +112,11 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:User/Attribute:language' => '语言', 'Class:User/Attribute:language+' => '用户语言', 'Class:User/Attribute:language/Value:EN US' => '英语', - 'Class:User/Attribute:language/Value:EN US+' => '英语 (U.S.)', + 'Class:User/Attribute:language/Value:EN US+' => '英语(U.S.)', 'Class:User/Attribute:language/Value:FR FR' => '法语', - 'Class:User/Attribute:language/Value:FR FR+' => '法语 (France)', + 'Class:User/Attribute:language/Value:FR FR+' => '法语(France)', 'Class:User/Attribute:profile_list' => '角色', - 'Class:User/Attribute:profile_list+' => '授予该用户的角色', + 'Class:User/Attribute:profile_list+' => '授予此用户的角色', 'Class:User/Attribute:allowed_org_list' => '可访问的组织', 'Class:User/Attribute:allowed_org_list+' => '目标用户可以看到以下组织的数据. 如果没有指定,则无限制.', 'Class:User/Attribute:status' => '状态', @@ -125,14 +125,14 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:User/Attribute:status/Value:disabled' => '停用', 'Class:User/Error:LoginMustBeUnique' => '登录名必须唯一 - "%1s" 已经被使用.', - 'Class:User/Error:AtLeastOneProfileIsNeeded' => '必须指定至少一个角色给该用户.', + 'Class:User/Error:AtLeastOneProfileIsNeeded' => '必须指定至少一个角色给此用户.', 'Class:User/Error:ProfileNotAllowed' => 'Profile "%1$s" cannot be added it will deny the access to backoffice~~', 'Class:User/Error:StatusChangeIsNotAllowed' => 'Changing status is not allowed for your own User~~', 'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Allowed organizations must contain User organization~~', 'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'The current list of profiles does not give sufficient access rights (Users are not modifiable anymore)~~', - 'Class:User/Error:AtLeastOneOrganizationIsNeeded' => '必须为该用户指定一个组织.', - 'Class:User/Error:OrganizationNotAllowed' => '该组织不被允许.', - 'Class:User/Error:UserOrganizationNotAllowed' => '该用户账户不属于那个组织.', + 'Class:User/Error:AtLeastOneOrganizationIsNeeded' => '必须为此用户指定一个组织.', + 'Class:User/Error:OrganizationNotAllowed' => '此组织不被允许.', + 'Class:User/Error:UserOrganizationNotAllowed' => '此用户账户不属于那个组织.', 'Class:User/Error:PersonIsMandatory' => '联系人必填.', 'Class:UserInternal' => '内部用户', 'Class:UserInternal+' => ITOP_APPLICATION_SHORT.' 内部定义的用户', @@ -150,7 +150,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:URP_Profiles/Attribute:description' => '描述', 'Class:URP_Profiles/Attribute:description+' => '单行描述', 'Class:URP_Profiles/Attribute:user_list' => '用户', - 'Class:URP_Profiles/Attribute:user_list+' => '拥有该角色的用户', + 'Class:URP_Profiles/Attribute:user_list+' => '拥有此角色的用户', )); // @@ -175,7 +175,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:URP_UserProfile' => '角色目标用户', 'Class:URP_UserProfile+' => '用户的角色', - 'Class:URP_UserProfile/Name' => '链接 %1$s 和 %2$s', + 'Class:URP_UserProfile/Name' => '关联 %1$s 和 %2$s', 'Class:URP_UserProfile/Attribute:userid' => '用户', 'Class:URP_UserProfile/Attribute:userid+' => '用户帐户', 'Class:URP_UserProfile/Attribute:userlogin' => '登录名', @@ -185,7 +185,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:URP_UserProfile/Attribute:profile' => '角色', 'Class:URP_UserProfile/Attribute:profile+' => '角色名称', 'Class:URP_UserProfile/Attribute:reason' => '原因', - 'Class:URP_UserProfile/Attribute:reason+' => '解释为什么此用户需要拥有该角色', + 'Class:URP_UserProfile/Attribute:reason+' => '解释为什么此用户需要拥有此角色', )); // @@ -196,7 +196,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:URP_UserOrg' => '用户组织', 'Class:URP_UserOrg+' => '可以访问的组织', - 'Class:URP_UserOrg/Name' => '链接 %1$s 和 %2$s', + 'Class:URP_UserOrg/Name' => '关联 %1$s 和 %2$s', 'Class:URP_UserOrg/Attribute:userid' => '用户', 'Class:URP_UserOrg/Attribute:userid+' => '用户帐户', 'Class:URP_UserOrg/Attribute:userlogin' => '登录名', @@ -206,7 +206,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:URP_UserOrg/Attribute:allowed_org_name' => '组织', 'Class:URP_UserOrg/Attribute:allowed_org_name+' => '可以访问的组织', 'Class:URP_UserOrg/Attribute:reason' => '原因', - 'Class:URP_UserOrg/Attribute:reason+' => '解释为什么此用户可以访问该组织的数据', + 'Class:URP_UserOrg/Attribute:reason+' => '解释为什么此用户可以访问此组织的数据', )); // @@ -225,7 +225,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:URP_ProfileProjection/Attribute:profile' => '角色', 'Class:URP_ProfileProjection/Attribute:profile+' => '角色名称', 'Class:URP_ProfileProjection/Attribute:value' => '值表达式', - 'Class:URP_ProfileProjection/Attribute:value+' => 'OQL 表达式 (using $user) | constant | | +attribute code', + 'Class:URP_ProfileProjection/Attribute:value+' => 'OQL表达式 (using $user) | constant | | +attribute code', 'Class:URP_ProfileProjection/Attribute:attribute' => '属性', 'Class:URP_ProfileProjection/Attribute:attribute+' => '目标属性编码 (可选)', )); @@ -244,7 +244,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Class:URP_ClassProjection/Attribute:class' => '类', 'Class:URP_ClassProjection/Attribute:class+' => '目标类', 'Class:URP_ClassProjection/Attribute:value' => '值表达式', - 'Class:URP_ClassProjection/Attribute:value+' => 'OQL 表达式 (using $this) | constant | | +attribute code', + 'Class:URP_ClassProjection/Attribute:value+' => 'OQL表达式 (using $this) | constant | | +attribute code', 'Class:URP_ClassProjection/Attribute:attribute' => '属性', 'Class:URP_ClassProjection/Attribute:attribute+' => '目标属性编码 (可选)', )); @@ -347,12 +347,12 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Menu:WelcomeMenuPage+' => '欢迎使用 '.ITOP_APPLICATION_SHORT, // Duplicated into itop-welcome-itil (will be removed from here...) 'UI:WelcomeMenu:Title' => '欢迎使用 '.ITOP_APPLICATION_SHORT, - 'UI:WelcomeMenu:LeftBlock' => '

          '.ITOP_APPLICATION_SHORT.' 是完全开源的IT 操作门户.

          + 'UI:WelcomeMenu:LeftBlock' => '

          '.ITOP_APPLICATION_SHORT.' 是完全开源的IT操作门户.

            它包括: -
          • 完整的CMDB(Configuration management database),用于登记和管理您的IT 资产.
          • -
          • 事件管理模块用于跟踪和传递所有发生在IT 系统中的事件.
          • -
          • 变更管理模块用于规划和跟踪IT 环境中发生的变化.
          • -
          • 已知错误数据库可加速事件的处理.
          • +
          • 完整的CMDB(配置管理数据库),用于登记和管理您的IT资产.
          • +
          • 事件管理模块用于跟踪和传递所有发生在IT系统中的事件.
          • +
          • 变更管理模块用于规划和跟踪IT环境中发生的变化.
          • +
          • 已知问题数据库可加速事件的处理.
          • 停机模块记录所有计划内的停机并通知对应的联系人.
          • 通过仪表盘快速获得您的IT概况.
          @@ -360,27 +360,27 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'UI:WelcomeMenu:RightBlock' => '

          '.ITOP_APPLICATION_SHORT.' 是面向服务供应商的, 它使得IT 工程师能够更方便地管理多客户和多组织.

            '.ITOP_APPLICATION_SHORT.' 提供功能丰富的业务处理流程: -
          • 提高IT 管理效率
          • -
          • 提升IT 可操作能力
          • +
          • 提高IT管理效率
          • +
          • 提升IT可操作能力
          • 提高用户满意度,提升业务能力.

          -

          '.ITOP_APPLICATION_SHORT.' 是完全开放的,可被集成到现有的IT 管理架构之中.

          +

          '.ITOP_APPLICATION_SHORT.' 是完全开放的,可被集成到现有的IT管理架构之中.

          -

            利用这个新一代的IT 操作门户, 可以帮助您: -
          • 更好地管理越来越复杂的IT 环境.
          • -
          • 逐步实现ITIL 流程.
          • +
              利用这个新一代的IT操作门户, 可以帮助您: +
            • 更好地管理越来越复杂的IT环境.
            • +
            • 逐步实现ITIL流程.
            • 管理IT 中最重要的资产: 文档.

            ', - 'UI:WelcomeMenu:Text'=> '
            Congratulations, you landed on '.ITOP_APPLICATION.' '.ITOP_VERSION_NAME.'!
            + 'UI:WelcomeMenu:Text'=> '
            恭喜, 您运行的是 '.ITOP_APPLICATION.' '.ITOP_VERSION_NAME.'!
            -
            This version features a brand new modern and accessible backoffice design.
            +
            此版本全新设计了一个现代且易用的后台界面.
            -
            We kept '.ITOP_APPLICATION.' core functions that you liked and modernized them to make you love them. -We hope you’ll enjoy this version as much as we enjoyed imagining and creating it.
            +
            我们不仅保留 '.ITOP_APPLICATION.' 中您所喜爱的核心功能,同时使其更具现代化. +我们衷心地希望您喜爱此版本,正如我们在设计和实现它的时候一样.
            -
            Customize your '.ITOP_APPLICATION.' preferences for a personalized experience.
            ', +
            定制化您的 '.ITOP_APPLICATION.' 设置,实现个性化体验.
            ', 'UI:WelcomeMenu:AllOpenRequests' => '所有打开的需求: %1$d', 'UI:WelcomeMenu:MyCalls' => '我办理的需求', 'UI:WelcomeMenu:OpenIncidents' => '所有打开的事件: %1$d', @@ -395,7 +395,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Button:Search' => '搜索', 'UI:Button:Clear' => ' 清空', 'UI:Button:SearchInHierarchy' => ' 递归搜索', - 'UI:Button:Query' => ' 查询 ', + 'UI:Button:Query' => '查询', 'UI:Button:Ok' => 'OK', 'UI:Button:Save' => '保存', 'UI:Button:SaveAnd' => '保存并 %1$s', @@ -404,31 +404,31 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Button:Apply' => '应用', 'UI:Button:Send' => '发送', 'UI:Button:SendAnd' => '发送并 %1$s', - 'UI:Button:Back' => ' << 上一步 ', - 'UI:Button:Restart' => ' |<< 重来 ', - 'UI:Button:Next' => ' 下一步 >> ', - 'UI:Button:Finish' => ' 结束 ', + 'UI:Button:Back' => '<< 上一步', + 'UI:Button:Restart' => '|<< 重来', + 'UI:Button:Next' => '下一步 >>', + 'UI:Button:Finish' => '结束', 'UI:Button:DoImport' => ' 执行导入 ! ', - 'UI:Button:Done' => ' 完成 ', - 'UI:Button:SimulateImport' => ' 开始导入 ', + 'UI:Button:Done' => '完成', + 'UI:Button:SimulateImport' => '开始导入', 'UI:Button:Test' => '测试!', - 'UI:Button:Evaluate' => ' 测试 ', - 'UI:Button:Evaluate:Title' => ' 评估 (Ctrl+Enter)', - 'UI:Button:AddObject' => ' 添加... ', - 'UI:Button:BrowseObjects' => ' 浏览... ', - 'UI:Button:Add' => ' 添加 ', - 'UI:Button:AddToList' => ' << 添加 ', - 'UI:Button:RemoveFromList' => ' 移除 >> ', - 'UI:Button:FilterList' => ' 过滤... ', - 'UI:Button:Create' => ' 创建 ', - 'UI:Button:Delete' => ' 删除 ! ', - 'UI:Button:Rename' => ' 重命名... ', - 'UI:Button:ChangePassword' => ' 修改密码 ', - 'UI:Button:ResetPassword' => ' 重置密码 ', + 'UI:Button:Evaluate' => '测试', + 'UI:Button:Evaluate:Title' => '评估 (Ctrl+Enter)', + 'UI:Button:AddObject' => '添加... ', + 'UI:Button:BrowseObjects' => '浏览...', + 'UI:Button:Add' => '添加', + 'UI:Button:AddToList' => '<< 添加', + 'UI:Button:RemoveFromList' => '移除 >>', + 'UI:Button:FilterList' => '过滤...', + 'UI:Button:Create' => '创建', + 'UI:Button:Delete' => '删除!', + 'UI:Button:Rename' => '重命名...', + 'UI:Button:ChangePassword' => '修改密码', + 'UI:Button:ResetPassword' => '重置密码', 'UI:Button:Insert' => '插入', 'UI:Button:More' => '更多', 'UI:Button:Less' => '更少', - 'UI:Button:Wait' => '正在更新字段,请稍候', + 'UI:Button:Wait' => '正在更新字段,请稍候', 'UI:Treeview:CollapseAll' => '全部收起', 'UI:Treeview:ExpandAll' => '全部展开', 'UI:UserPref:DoNotShowAgain' => '不再显示', @@ -455,21 +455,21 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Error:UploadStoppedByExtension_FileName' => '上传因为扩展名被停止. (Original file name = "%1$s").', 'UI:Error:UploadFailedUnknownCause_Code' => '文件上传失败, 原因未知. (Error code = "%1$s").', - 'UI:Error:1ParametersMissing' => '错误: 必须为该操作指定以下参数: %1$s.', - 'UI:Error:2ParametersMissing' => '错误: 必须为该操作指定以下参数: %1$s and %2$s.', - 'UI:Error:3ParametersMissing' => '错误: 必须为该操作指定以下参数: %1$s, %2$s and %3$s.', - 'UI:Error:4ParametersMissing' => '错误: 必须为该操作指定以下参数: %1$s, %2$s, %3$s and %4$s.', - 'UI:Error:IncorrectOQLQuery_Message' => '错误: 错误的 OQL 查询: %1$s', - 'UI:Error:AnErrorOccuredWhileRunningTheQuery_Message' => '运行该查询时发生了一个错误: %1$s', - 'UI:Error:ObjectAlreadyUpdated' => '错误: 该对象已更新.', + 'UI:Error:1ParametersMissing' => '错误: 必须为此操作指定以下参数: %1$s.', + 'UI:Error:2ParametersMissing' => '错误: 必须为此操作指定以下参数: %1$s and %2$s.', + 'UI:Error:3ParametersMissing' => '错误: 必须为此操作指定以下参数: %1$s, %2$s and %3$s.', + 'UI:Error:4ParametersMissing' => '错误: 必须为此操作指定以下参数: %1$s, %2$s, %3$s and %4$s.', + 'UI:Error:IncorrectOQLQuery_Message' => '错误: 错误的OQL查询: %1$s', + 'UI:Error:AnErrorOccuredWhileRunningTheQuery_Message' => '运行此查询时发生了一个错误: %1$s', + 'UI:Error:ObjectAlreadyUpdated' => '错误: 此对象已更新.', 'UI:Error:ObjectCannotBeUpdated' => '错误: 对象无法更新.', 'UI:Error:ObjectsAlreadyDeleted' => '错误: 对象已被删除!', 'UI:Error:BulkDeleteNotAllowedOn_Class' => '您无权进行 %1$s 类对象的批量删除', 'UI:Error:DeleteNotAllowedOn_Class' => '您无权删除 %1$s 类的对象', 'UI:Error:ReadNotAllowedOn_Class' => '您无权查看对象的 %1$s', 'UI:Error:BulkModifyNotAllowedOn_Class' => '您无权进行 %1$s 类对象的批量更新', - 'UI:Error:ObjectAlreadyCloned' => '错误: 该对象已被克隆!', - 'UI:Error:ObjectAlreadyCreated' => '错误: 该对象已被创建!', + 'UI:Error:ObjectAlreadyCloned' => '错误: 此对象已被克隆!', + 'UI:Error:ObjectAlreadyCreated' => '错误: 此对象已被创建!', 'UI:Error:Invalid_Stimulus_On_Object_In_State' => '错误: 在对象 %2$s 的 "%3$s" 状态上的无效刺激 "%1$s" .', 'UI:Error:InvalidDashboardFile' => 'Error: 无效的仪表盘文件', 'UI:Error:InvalidDashboard' => 'Error: 无效的仪表盘', @@ -477,6 +477,8 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Error:MaintenanceTitle' => '维护', 'UI:Error:InvalidToken' => 'Error: 所请求的操作已执行 (未发现 CSRF token )', + 'UI:Error:SMTP:UnknownVendor' => 'OAuth SMTP provider %1$s does not exist (email_transport_smtp.oauth.provider)~~', + 'UI:GroupBy:Count' => '个数', 'UI:GroupBy:Count+' => '项目数', 'UI:CountOfObjects' => '%1$d 个对象符合指定的条件.', @@ -493,7 +495,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:History:User' => '用户', 'UI:History:User+' => '造成变更的用户', 'UI:History:Changes' => '变更', - 'UI:History:Changes+' => '对该对象所做的变更', + 'UI:History:Changes+' => '对此对象所做的变更', 'UI:History:StatsCreations' => '已创建', 'UI:History:StatsCreations+' => '已创建的对象个数', 'UI:History:StatsModifs' => '已修改', @@ -509,7 +511,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Menu:Add' => '添加...', 'UI:Menu:Manage' => '管理...', 'UI:Menu:EMail' => '邮件', - 'UI:Menu:CSVExport' => 'CSV 导出...', + 'UI:Menu:CSVExport' => 'CSV导出...', 'UI:Menu:Modify' => '修改...', 'UI:Menu:Delete' => '删除...', 'UI:Menu:BulkDelete' => '删除...', @@ -517,15 +519,15 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Document:OpenInNewWindow:Download' => '在新窗口打开: %1$s, 下载: %2$s', 'UI:SplitDateTime-Date' => '日期', 'UI:SplitDateTime-Time' => '时间', - 'UI:TruncatedResults' => '显示 %1$d 个对象,共 %2$d 个', + 'UI:TruncatedResults' => '显示 %1$d 个对象,共 %2$d 个', 'UI:DisplayAll' => '全部显示', 'UI:CollapseList' => '收起', 'UI:CountOfResults' => '%1$d 个对象', 'UI:ChangesLogTitle' => '变更记录 (%1$d):', 'UI:EmptyChangesLogTitle' => '变更记录为空', 'UI:SearchFor_Class_Objects' => '搜索 %1$s ', - 'UI:OQLQueryBuilderTitle' => 'OQL 查询构建器', - 'UI:OQLQueryTab' => 'OQL 查询', + 'UI:OQLQueryBuilderTitle' => 'OQL查询构建器', + 'UI:OQLQueryTab' => 'OQL查询', 'UI:SimpleSearchTab' => '简单搜索', 'UI:Details+' => '详情', 'UI:SearchValue:Any' => '* 任何 *', @@ -548,9 +550,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:ResetPwd-Error-WrongLogin' => '\'%1$s\' 用户名无效', 'UI:ResetPwd-Error-NotPossible' => '外部账户不允许重置密码.', - 'UI:ResetPwd-Error-FixedPwd' => '该账户不允许重置密码.', - 'UI:ResetPwd-Error-NoContact' => '该账户没有关联到个人.', - 'UI:ResetPwd-Error-NoEmailAtt' => '该账户未关联邮箱地址,请联系管理员.', + 'UI:ResetPwd-Error-FixedPwd' => '此账户不允许重置密码.', + 'UI:ResetPwd-Error-NoContact' => '此账户没有关联到个人.', + 'UI:ResetPwd-Error-NoEmailAtt' => '此账户未关联邮箱地址,请联系管理员.', 'UI:ResetPwd-Error-NoEmail' => '缺少邮箱地址. 请联系管理员.', 'UI:ResetPwd-Error-Send' => '邮件发送存在技术原因. 请联系管理员.', 'UI:ResetPwd-EmailSent' => '请检查您的收件箱并根据指引进行操作. 如果您没有收到邮件, 请检查您登录时的输入是否存在错误.', @@ -585,7 +587,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Login:Error:MultipleContactsHaveSameEmail' => '多个联系人存在相同的邮箱', 'UI:Login:Error:NoValidProfiles' => '无效的资料', 'UI:CSVImport:MappingSelectOne' => '-- 请选择 --', - 'UI:CSVImport:MappingNotApplicable' => '-- 忽略该栏 --', + 'UI:CSVImport:MappingNotApplicable' => '-- 忽略此栏 --', 'UI:CSVImport:NoData' => '数据为空..., 请提供数据!', 'UI:Title:DataPreview' => '数据预览', 'UI:CSVImport:ErrorOnlyOneColumn' => '错误: 数据仅包含一列. 您选择了合适的分隔符了吗?', @@ -594,7 +596,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:CSVImport:DataLine2' => '数据行 2', 'UI:CSVImport:idField' => 'id (主键)', 'UI:Title:BulkImport' => ITOP_APPLICATION_SHORT.' - 批量导入', - 'UI:Title:BulkImport+' => 'CSV 导入向导', + 'UI:Title:BulkImport+' => 'CSV导入向导', 'UI:Title:BulkSynchro_nbItem_ofClass_class' => '同步 %2$s 个对象中的 %1$d', 'UI:CSVImport:ClassesSelectOne' => '-- 请选择 --', 'UI:CSVImport:ErrorExtendedAttCode' => '内部错误: "%1$s" 是错误的编码, 因为 "%2$s" 不是类 "%3$s" 的外键', @@ -634,7 +636,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:CSVImport:SelectClass' => '选择要导入的类别:', 'UI:CSVImport:AdvancedMode' => '高级模式', 'UI:CSVImport:AdvancedMode+' => '在高级模式中,对象的"id" (主键) 可以被用来修改和重命名对象. 不管怎样,列 "id" (如果存在) 只能被用做一个搜索条件,不能与其它搜索条件混用.', - 'UI:CSVImport:SelectAClassFirst' => '要配置映射,请先选择一个类.', + 'UI:CSVImport:SelectAClassFirst' => '要配置映射,请先选择一个类.', 'UI:CSVImport:HeaderFields' => '栏目', 'UI:CSVImport:HeaderMappings' => '映射', 'UI:CSVImport:HeaderSearch' => '搜索?', @@ -683,7 +685,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:CSVReport-Icon-Error' => '错误', 'UI:CSVReport-Object-Error' => '错误: %1$s', 'UI:CSVReport-Object-Ambiguous' => 'AMBIGUOUS: %1$s', - 'UI:CSVReport-Stats-Errors' => '%1$.0f %% 已加载的对象包含错误,它们将会被忽略.', + 'UI:CSVReport-Stats-Errors' => '%1$.0f %% 已加载的对象包含错误,它们将会被忽略.', 'UI:CSVReport-Stats-Created' => '%1$.0f %% 已加载的对象将会被创建.', 'UI:CSVReport-Stats-Modified' => '%1$.0f %% 已加载的对象将会被修改.', @@ -711,31 +713,31 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Audit:AuditCategory:Subtitle' => '%2$s 个中有 %1$s 个错误 - %3$s%%', - 'UI:RunQuery:Title' => ITOP_APPLICATION_SHORT.' - OQL 查询评估', + 'UI:RunQuery:Title' => ITOP_APPLICATION_SHORT.' - OQL查询评估', 'UI:RunQuery:QueryExamples' => '示例查询', 'UI:RunQuery:QueryResults' => '查询结果', 'UI:RunQuery:HeaderPurpose' => '目的', - 'UI:RunQuery:HeaderPurpose+' => '该查询的解释', + 'UI:RunQuery:HeaderPurpose+' => '此查询的解释', 'UI:RunQuery:HeaderOQLExpression' => 'OQL 表达式', 'UI:RunQuery:HeaderOQLExpression+' => 'OQL 语法表示的查询', 'UI:RunQuery:ExpressionToEvaluate' => '请输入表达式: ', 'UI:RunQuery:QueryArguments' => '查询参数', - 'UI:RunQuery:MoreInfo' => '该查询的更多信息: ', + 'UI:RunQuery:MoreInfo' => '此查询的更多信息: ', 'UI:RunQuery:DevelopedQuery' => '重新开发的查询表达式: ', 'UI:RunQuery:SerializedFilter' => '序列化的过滤器: ', 'UI:RunQuery:DevelopedOQL' => '优化的 OQL', 'UI:RunQuery:DevelopedOQLCount' => 'Developed OQL for count~~', 'UI:RunQuery:ResultSQLCount' => 'Resulting SQL for count~~', 'UI:RunQuery:ResultSQL' => 'Resulting SQL~~', - 'UI:RunQuery:Error' => '运行该查询时发生了一个错误', + 'UI:RunQuery:Error' => '运行此查询时发生了一个错误', 'UI:Query:UrlForExcel' => 'URL to use for MS-Excel web queries', 'UI:Query:UrlV1' => 'The list of fields has been left unspecified. The page export-V2.php cannot be invoked without this information. Therefore, the URL suggested here below points to the legacy page: export.php. This legacy version of the export has the following limitation: the list of exported fields may vary depending on the output format and the data model of '.ITOP_APPLICATION_SHORT.'.
            Should you want to garantee that the list of exported columns will remain stable on the long run, then you must specify a value for the attribute "Fields" and use the page export-V2.php.', 'UI:Schema:Title' => ITOP_APPLICATION_SHORT.' 对象模型', 'UI:Schema:TitleForClass' => '%1$s schema~~', 'UI:Schema:CategoryMenuItem' => '类别 %1$s', 'UI:Schema:Relationships' => '关联', - 'UI:Schema:AbstractClass' => '抽象类: 该类不能实例化对象.', - 'UI:Schema:NonAbstractClass' => '非抽象类: 该类可以实例化对象.', + 'UI:Schema:AbstractClass' => '抽象类: 此类不能实例化对象.', + 'UI:Schema:NonAbstractClass' => '非抽象类: 此类可以实例化对象.', 'UI:Schema:ClassHierarchyTitle' => '类层级', 'UI:Schema:AllClasses' => '所有类', 'UI:Schema:ExternalKey_To' => '%1$s的外键', @@ -752,20 +754,20 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Schema:Type+' => '属性的数据类型', 'UI:Schema:Origin' => '来自', - 'UI:Schema:Origin+' => '该属性被定义的基类', + 'UI:Schema:Origin+' => '此属性被定义的基类', 'UI:Schema:Description' => '描述', 'UI:Schema:Description+' => '属性的描述', 'UI:Schema:AllowedValues' => '允许值', - 'UI:Schema:AllowedValues+' => '该属性取值的限制', + 'UI:Schema:AllowedValues+' => '此属性取值的限制', 'UI:Schema:MoreInfo' => '更多信息', - 'UI:Schema:MoreInfo+' => '该栏目在数据库中被定义的更多信息', + 'UI:Schema:MoreInfo+' => '此栏目在数据库中被定义的更多信息', 'UI:Schema:SearchCriteria' => '搜索条件', 'UI:Schema:FilterCode' => '过滤器编码', - 'UI:Schema:FilterCode+' => '该搜索条件的编码', + 'UI:Schema:FilterCode+' => '此搜索条件的编码', 'UI:Schema:FilterDescription' => '描述', - 'UI:Schema:FilterDescription+' => '该搜索条件的描述', + 'UI:Schema:FilterDescription+' => '此搜索条件的描述', 'UI:Schema:AvailOperators' => '可用的运算符', - 'UI:Schema:AvailOperators+' => '该搜索条件可能的运算符', + 'UI:Schema:AvailOperators+' => '此搜索条件可能的运算符', 'UI:Schema:ChildClasses' => '子类', 'UI:Schema:ReferencingClasses' => '相关类', 'UI:Schema:RelatedClasses' => '相关类', @@ -779,9 +781,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Schema:Class_ReferencingClasses_From_By' => '%1$s 被类 %2$s 参照, 通过栏目 %3$s', 'UI:Schema:Class_IsLinkedTo_Class_Via_ClassAndAttribute' => '%1$s 被链接到 %2$s 通过 %3$s::%4$s', 'UI:Schema:Links:1-n' => '类指向 %1$s (1:n 链接):', - 'UI:Schema:Links:n-n' => '类链接到 %1$s (n:n 链接):', + 'UI:Schema:Links:n-n' => '类关联到 %1$s (n:n 链接):', 'UI:Schema:Links:All' => '全部相关类的图', - 'UI:Schema:NoLifeCyle' => '该类没有生命周期的定义.', + 'UI:Schema:NoLifeCyle' => '此类没有生命周期的定义.', 'UI:Schema:LifeCycleTransitions' => '状态和转换', 'UI:Schema:LifeCyleAttributeOptions' => '属性选项', 'UI:Schema:LifeCycleHiddenAttribute' => '隐藏', @@ -804,7 +806,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Label:SelectedObjects' => '被选的对象: ', 'UI:Label:AvailableObjects' => '可用的对象: ', 'UI:Link_Class_Attributes' => '%1$s 属性', - 'UI:SelectAllToggle+' => '全选 / 反选', + 'UI:SelectAllToggle+' => '全选/反选', 'UI:AddObjectsOf_Class_LinkedWith_Class_Instance' => '添加 %1$s 个对象, 链接 %2$s: %3$s', 'UI:AddObjectsOf_Class_LinkedWith_Class' => ' %1$s ', 'UI:ManageObjectsOf_Class_LinkedWith_Class_Instance' => '管理 %1$s 个对象, 链接 %2$s: %3$s', @@ -815,13 +817,13 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Wizard:FinalStepTitle' => '最后一步: 确认', 'UI:Title:DeletionOf_Object' => '删除 %1$s', 'UI:Title:BulkDeletionOf_Count_ObjectsOf_Class' => '批量删除 %1$d 个 %2$s 类的对象', - 'UI:Delete:NotAllowedToDelete' => '您无权删除该对象', + 'UI:Delete:NotAllowedToDelete' => '您无权删除此对象', 'UI:Delete:NotAllowedToUpdate_Fields' => '您无权更新以下栏目: %1$s', 'UI:Error:ActionNotAllowed' => '您无权进行操作', - 'UI:Error:NotEnoughRightsToDelete' => '无法删除该对象, 因为当前用户没有足够的权限', - 'UI:Error:CannotDeleteBecause' => '无法删除该对象,因为: %1$s', - 'UI:Error:CannotDeleteBecauseOfDepencies' => '无法删除该对象, 因为必须事先完成一些手动操作', - 'UI:Error:CannotDeleteBecauseManualOpNeeded' => '无法删除该对象,必须事先完成一些手动操作', + 'UI:Error:NotEnoughRightsToDelete' => '无法删除此对象, 因为当前用户没有足够的权限', + 'UI:Error:CannotDeleteBecause' => '无法删除此对象,因为: %1$s', + 'UI:Error:CannotDeleteBecauseOfDepencies' => '无法删除此对象, 因为必须事先完成一些手动操作', + 'UI:Error:CannotDeleteBecauseManualOpNeeded' => '无法删除此对象,必须事先完成一些手动操作', 'UI:Archive_User_OnBehalfOf_User' => '%1$s on behalf of %2$s', 'UI:Delete:Deleted' => '已删除', 'UI:Delete:AutomaticallyDeleted' => '已自动删除', @@ -834,7 +836,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => '删除 %2$s 类的 %1$d 个对象', 'UI:Delete:CannotDeleteBecause' => '无法删除: %1$s', 'UI:Delete:ShouldBeDeletedAtomaticallyButNotPossible' => '应该自动删除, 但您无权这样做', - 'UI:Delete:MustBeDeletedManuallyButNotPossible' => '必须手动删除 - 但您无权删除该对象, 请联系管理员', + 'UI:Delete:MustBeDeletedManuallyButNotPossible' => '必须手动删除 - 但您无权删除此对象, 请联系管理员', 'UI:Delete:WillBeDeletedAutomatically' => '将被自动删除', 'UI:Delete:MustBeDeletedManually' => '必须手动删除', 'UI:Delete:CannotUpdateBecause_Issue' => '应该被自动更新, 但是: %1$s', @@ -843,14 +845,14 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Delete:Count_Objects/LinksReferencingTheObjects' => '%1$d 个对象/链接 关联了一些即将要删除的对象', 'UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity' => '为了确保数据库的完整性, 任何与之关联的项目也会被删除', 'UI:Delete:Consequence+' => '要做什么', - 'UI:Delete:SorryDeletionNotAllowed' => '抱歉, 您无权删除该对象, 请看上述详细解释', - 'UI:Delete:PleaseDoTheManualOperations' => '在删除该对象之前, 请先手工完成上述列出的操作', + 'UI:Delete:SorryDeletionNotAllowed' => '抱歉, 您无权删除此对象, 请看上述详细解释', + 'UI:Delete:PleaseDoTheManualOperations' => '在删除此对象之前, 请先手工完成上述列出的操作', 'UI:Delect:Confirm_Object' => '请确认要删除 %1$s.', 'UI:Delect:Confirm_Count_ObjectsOf_Class' => '请确认要删除下列 %2$s 类的 %1$d 个对象.', 'UI:WelcomeToITop' => '欢迎使用 '.ITOP_APPLICATION_SHORT, 'UI:DetailsPageTitle' => ITOP_APPLICATION_SHORT.' - %1$s - %2$s 详细内容', 'UI:ErrorPageTitle' => ITOP_APPLICATION_SHORT.' - 错误', - 'UI:ObjectDoesNotExist' => '抱歉, 该对象不存在 (或无权浏览该对象).', + 'UI:ObjectDoesNotExist' => '抱歉, 此对象不存在 (或无权浏览此对象).', 'UI:ObjectArchived' => '对象已被归档. 请启用归档模式或联系管理员.', 'Tag:Archived' => '已归档', 'Tag:Archived+' => '仅能在归档模式下访问', @@ -896,7 +898,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:UserManagement:ProjectedObject+' => '被映射的对象', 'UI:UserManagement:AnyObject' => '* 任何 *', 'UI:UserManagement:User' => '用户', - 'UI:UserManagement:User+' => '与该映射相关的用户', + 'UI:UserManagement:User+' => '与此映射相关的用户', 'UI:UserManagement:Action:Read' => '读', 'UI:UserManagement:Action:Read+' => '读/显示 对象', 'UI:UserManagement:Action:Modify' => '修改', @@ -906,13 +908,13 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:UserManagement:Action:BulkRead' => '批量读取(导出)', 'UI:UserManagement:Action:BulkRead+' => '列出对象或批量导出', 'UI:UserManagement:Action:BulkModify' => '批量修改', - 'UI:UserManagement:Action:BulkModify+' => '批量创建/编辑 (CSV 导入)', + 'UI:UserManagement:Action:BulkModify+' => '批量创建/编辑 (CSV导入)', 'UI:UserManagement:Action:BulkDelete' => '批量删除', 'UI:UserManagement:Action:BulkDelete+' => '批量删除对象', 'UI:UserManagement:Action:Stimuli' => 'Stimuli', 'UI:UserManagement:Action:Stimuli+' => '许可的 (复合的) 操作', 'UI:UserManagement:Action' => '操作', - 'UI:UserManagement:Action+' => '该用户进行的操作', + 'UI:UserManagement:Action+' => '此用户进行的操作', 'UI:UserManagement:TitleActions' => '操作', 'UI:UserManagement:Permission' => '许可', 'UI:UserManagement:Permission+' => '用户的许可', @@ -921,7 +923,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:UserManagement:ActionAllowed:No' => '否', 'UI:UserManagement:AdminProfile+' => '管理员拥有数据库中所有对象完整的读/写/访问权限.', 'UI:UserManagement:NoLifeCycleApplicable' => 'N/A', - 'UI:UserManagement:NoLifeCycleApplicable+' => '该类未定义生命周期', + 'UI:UserManagement:NoLifeCycleApplicable+' => '此类未定义生命周期', 'UI:UserManagement:GrantMatrix' => '授权矩阵', 'Menu:AdminTools' => '管理工具',// Duplicated into itop-welcome-itil (will be removed from here...) @@ -972,7 +974,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI-ContactsMenu-ContactsByType' => '按类别划分联系人', 'UI-ContactsMenu-ContactsByStatus' => '按状态划分联系人', - 'Menu:CSVImportMenu' => 'CSV 导入',// Duplicated into itop-welcome-itil (will be removed from here...) + 'Menu:CSVImportMenu' => 'CSV导入',// Duplicated into itop-welcome-itil (will be removed from here...) 'Menu:CSVImportMenu+' => '批量创建或修改',// Duplicated into itop-welcome-itil (will be removed from here...) 'Menu:DataModelMenu' => '数据模型',// Duplicated into itop-welcome-itil (will be removed from here...) @@ -1057,7 +1059,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:OpenDocumentInNewWindow_' => '打开', 'UI:DownloadDocument_' => '下载', - 'UI:Document:NoPreview' => '该类文档无法预览', + 'UI:Document:NoPreview' => '此类文档无法预览', 'UI:Download-CSV' => '下载 %1$s', 'UI:DeadlineMissedBy_duration' => '超过 %1$s', @@ -1067,7 +1069,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Deadline_Days_Hours_Minutes' => '%1$dd %2$dh %3$dmin', 'UI:Help' => '帮助', 'UI:PasswordConfirm' => '确认', - 'UI:BeforeAdding_Class_ObjectsSaveThisObject' => '在添加更多 %1$s 之前, 保存该对象.', + 'UI:BeforeAdding_Class_ObjectsSaveThisObject' => '在添加更多 %1$s 之前, 保存此对象.', 'UI:DisplayThisMessageAtStartup' => '在启动时显示此消息', 'UI:RelationshipGraph' => '图览', 'UI:RelationshipList' => '列表', @@ -1082,7 +1084,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Relation:Zoom' => '放大', 'UI:Relation:ExportAsAttachment' => '导出为附件...', 'UI:Relation:DrillDown' => '详情...', - 'UI:Relation:PDFExportOptions' => 'PDF 导出选项', + 'UI:Relation:PDFExportOptions' => 'PDF导出选项', 'UI:Relation:AttachmentExportOptions_Name' => 'Options for Attachment to %1$s', 'UI:RelationOption:Untitled' => '无标题', 'UI:Relation:Key' => 'Key', @@ -1123,12 +1125,12 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'Portal:PleaseSelectAServiceSubCategory' => '请选择子类', 'Portal:DescriptionOfTheRequest' => '请输入描述:', 'Portal:TitleRequestDetailsFor_Request' => '需求详情 %1$s:', - 'Portal:NoOpenRequest' => '该类别中没有打开的需求.', - 'Portal:NoClosedRequest' => '该分类中没有需求', + 'Portal:NoOpenRequest' => '此类别中没有打开的需求.', + 'Portal:NoClosedRequest' => '此分类中没有需求', 'Portal:Button:ReopenTicket' => '重新打开这个工单', 'Portal:Button:CloseTicket' => '关闭这个工单', 'Portal:Button:UpdateRequest' => '更新需求', - 'Portal:EnterYourCommentsOnTicket' => '请点评该工单的解决方案:', + 'Portal:EnterYourCommentsOnTicket' => '请点评此工单的解决方案:', 'Portal:ErrorNoContactForThisUser' => '错误: 当前用户没有与任何联系人关联. 请联系管理员.', 'Portal:Attachments' => '附件', 'Portal:AddAttachment' => ' 添加附件 ', @@ -1161,7 +1163,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:CaseLogTypeYourTextHere' => '请在这里输入内容...', 'UI:CaseLog:Header_Date_UserName' => '%1$s - %2$s:', 'UI:CaseLog:InitialValue' => '初始值:', - 'UI:AttemptingToSetASlaveAttribute_Name' => '字段 %1$s 不可写,因为它由数据同步管理. 值未设置.', + 'UI:AttemptingToSetASlaveAttribute_Name' => '字段 %1$s 不可写,因为它由数据同步管理. 值未设置.', 'UI:ActionNotAllowed' => '您无权操作这些对象.', 'UI:BulkAction:NoObjectSelected' => '请至少选择一个对象进行操作', 'UI:AttemptingToChangeASlaveAttribute_Name' => 'The field %1$s is not writable because it is mastered by the data synchronization. Value remains unchanged.', @@ -1177,7 +1179,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:ArchiveMode:Banner' => '归档模式', 'UI:ArchiveMode:Banner+' => '已归档的对象可见但不允许修改', 'UI:FavoriteOrganizations' => '快速访问', - 'UI:FavoriteOrganizations+' => '进入组织下的列表,可实现通过下拉菜单快速访问. 请注意,这并不是一个安全设置, 其他组织的对象依然可以通过选择 "所有组织" 下拉列表看到.', + 'UI:FavoriteOrganizations+' => '进入组织下的列表,可实现通过下拉菜单快速访问. 请注意,这并不是一个安全设置, 其他组织的对象依然可以通过选择 "所有组织" 下拉列表看到.', 'UI:FavoriteLanguage' => '语言', 'UI:Favorites:SelectYourLanguage' => '选择语言', 'UI:FavoriteOtherSettings' => '其他设置', @@ -1384,7 +1386,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Menu:ShortcutList' => '创建快捷方式...', 'UI:ShortcutRenameDlg:Title' => '重命名快捷方式', - 'UI:ShortcutListDlg:Title' => '为该列表创建快捷方式', + 'UI:ShortcutListDlg:Title' => '为此列表创建快捷方式', 'UI:ShortcutDelete:Confirm' => '请确认是否删除这个(些)快捷方式.', 'Menu:MyShortcuts' => '我的快捷方式',// Duplicated into itop-welcome-itil (will be removed from here...) 'Class:Shortcut' => '快捷方式', @@ -1446,17 +1448,17 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:About:Extension_Version' => '版本: %1$s', 'UI:About:RemoteExtensionSource' => '数据', - 'UI:DisconnectedDlgMessage' => '您已断开. 要继续使用,需要重新验证您的用户名和密码.', + 'UI:DisconnectedDlgMessage' => '您已断开. 要继续使用,需要重新验证您的用户名和密码.', 'UI:DisconnectedDlgTitle' => '警告!', 'UI:LoginAgain' => '再次登录', 'UI:StayOnThePage' => '保持在当前页面', - 'ExcelExporter:ExportMenu' => 'Excel 导出...', - 'ExcelExporter:ExportDialogTitle' => 'Excel 导出', + 'ExcelExporter:ExportMenu' => 'Excel导出...', + 'ExcelExporter:ExportDialogTitle' => 'Excel导出', 'ExcelExporter:ExportButton' => '导出', 'ExcelExporter:DownloadButton' => '下载 %1$s', 'ExcelExporter:RetrievingData' => '正在检索数据...', - 'ExcelExporter:BuildingExcelFile' => '正在创建Excel 文件...', + 'ExcelExporter:BuildingExcelFile' => '正在创建Excel文件...', 'ExcelExporter:Done' => '完成.', 'ExcelExport:AutoDownload' => '导出准备好之后自动开始下载', 'ExcelExport:PreparingExport' => '正在准备导出...', @@ -1465,7 +1467,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'portal:backoffice' => ITOP_APPLICATION_SHORT.' 后台用户界面', 'UI:CurrentObjectIsLockedBy_User' => '对象被锁住,因为正在修改 %1$s.', - 'UI:CurrentObjectIsLockedBy_User_Explanation' => '该对象正在被 %1$s 修改. 您的修改无法提交因为它们会冲突.', + 'UI:CurrentObjectIsLockedBy_User_Explanation' => '此对象正在被 %1$s 修改. 您的修改无法提交因为它们会冲突.', 'UI:CurrentObjectIsSoftLockedBy_User' => 'The object is currently being modified by %1$s. You\'ll be able to submit your modifications once they have finished.~~', 'UI:CurrentObjectLockExpired' => '并发修改的锁定期已过.', 'UI:CurrentObjectLockExpired_Explanation' => 'The lock to prevent concurrent modifications of the object has expired. You can no longer submit your modification since other users are now allowed to modify this object.', @@ -1479,9 +1481,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:UploadInlineImageLegend' => '上传新图片', 'UI:SelectInlineImageToUpload' => '选择要上传的图片', 'UI:AvailableInlineImagesLegend' => '可用的图片', - 'UI:NoInlineImage' => '服务器上没有图片. 使用上面的 "浏览" 按钮,从您的电脑上选择并上传到服务器.', + 'UI:NoInlineImage' => '服务器上没有图片. 使用上面的 "浏览" 按钮,从您的电脑上选择并上传到服务器.', - 'UI:ToggleFullScreen' => '切换 最大化 / 最小化', + 'UI:ToggleFullScreen' => '切换 最大化/最小化', 'UI:Button:ResetImage' => '恢复之前的图片', 'UI:Button:RemoveImage' => '移除图片', 'UI:Button:UploadImage' => '从硬盘上传图像', @@ -1490,10 +1492,10 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Button:RemoveDocument' => '移除文档', // Search form - 'UI:Search:Toggle' => '折叠 / 展开', - 'UI:Search:AutoSubmit:DisabledHint' => '该类别已禁用自动提交', + 'UI:Search:Toggle' => '折叠/展开', + 'UI:Search:AutoSubmit:DisabledHint' => '此类别已禁用自动提交', 'UI:Search:Obsolescence:DisabledHint' => '根据您的设置, 废弃的数据会被隐藏', - 'UI:Search:NoAutoSubmit:ExplainText' => '在搜索框中添加规则,或者单击对象按钮查看对象.', + 'UI:Search:NoAutoSubmit:ExplainText' => '在搜索框中添加规则,或者单击对象按钮查看对象.', 'UI:Search:Criterion:MoreMenu:AddCriteria' => '添加条件', // - Add new criteria button 'UI:Search:AddCriteria:List:RecentlyUsed:Title' => '最近使用', @@ -1502,7 +1504,7 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Search:AddCriteria:List:RecentlyUsed:Placeholder' => '还没有.', // - Criteria header actions - 'UI:Search:Criteria:Toggle' => '折叠 / 展开', + 'UI:Search:Criteria:Toggle' => '折叠/展开', 'UI:Search:Criteria:Remove' => '移除', 'UI:Search:Criteria:Locked' => '已锁定', @@ -1568,9 +1570,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating // - Numeric widget 'UI:Search:Criteria:Operator:Numeric:Equals' => '等于',// => '=', 'UI:Search:Criteria:Operator:Numeric:GreaterThan' => '大于',// => '>', - 'UI:Search:Criteria:Operator:Numeric:GreaterThanOrEquals' => '大于 / 等于',// > '>=', + 'UI:Search:Criteria:Operator:Numeric:GreaterThanOrEquals' => '大于/等于',// > '>=', 'UI:Search:Criteria:Operator:Numeric:LessThan' => '小于',// => '<', - 'UI:Search:Criteria:Operator:Numeric:LessThanOrEquals' => '小于 / 等于',// > '<=', + 'UI:Search:Criteria:Operator:Numeric:LessThanOrEquals' => '小于/等于',// > '<=', 'UI:Search:Criteria:Operator:Numeric:Different' => '不同',// => '≠', // - Tag Set Widget 'UI:Search:Criteria:Operator:TagSet:Matches' => '匹配', @@ -1581,8 +1583,8 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating 'UI:Search:Value:Autocomplete:StartTyping' => '开始输入值.', 'UI:Search:Value:Autocomplete:Wait' => '请稍后...', 'UI:Search:Value:Autocomplete:NoResult' => '没有找到结果.', - 'UI:Search:Value:Toggler:CheckAllNone' => '全选 / 不选', - 'UI:Search:Value:Toggler:CheckAllNoneFiltered' => '全选 / 不选', + 'UI:Search:Value:Toggler:CheckAllNone' => '全选/不选', + 'UI:Search:Value:Toggler:CheckAllNoneFiltered' => '全选/不选', // - Widget other translations 'UI:Search:Criteria:Numeric:From' => '从', @@ -1661,30 +1663,30 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array( 'Menu:AuditCategories' => '审计类别', 'Menu:AuditCategories+' => '审计类别', 'Menu:Notifications:Title' => '审计类别', - 'Menu:RunQueriesMenu' => '运行查询', - 'Menu:RunQueriesMenu+' => '运行任何查询', - 'Menu:QueryMenu' => '查询手册', - 'Menu:QueryMenu+' => '查询手册', - 'Menu:UniversalSearchMenu' => '全局搜索', - 'Menu:UniversalSearchMenu+' => '搜索所有...', - 'Menu:UserManagementMenu' => '用户管理', - 'Menu:UserManagementMenu+' => '用户管理', - 'Menu:ProfilesMenu' => '角色~~', - 'Menu:ProfilesMenu+' => '角色~~', - 'Menu:ProfilesMenu:Title' => '角色~~', - 'Menu:UserAccountsMenu' => '用户帐户', - 'Menu:UserAccountsMenu+' => '用户帐户', + 'Menu:RunQueriesMenu' => '运行查询', + 'Menu:RunQueriesMenu+' => '运行任何查询', + 'Menu:QueryMenu' => '查询手册', + 'Menu:QueryMenu+' => '查询手册', + 'Menu:UniversalSearchMenu' => '全局搜索', + 'Menu:UniversalSearchMenu+' => '搜索所有...', + 'Menu:UserManagementMenu' => '用户管理', + 'Menu:UserManagementMenu+' => '用户管理', + 'Menu:ProfilesMenu' => '角色~~', + 'Menu:ProfilesMenu+' => '角色~~', + 'Menu:ProfilesMenu:Title' => '角色~~', + 'Menu:UserAccountsMenu' => '用户帐户', + 'Menu:UserAccountsMenu+' => '用户帐户', 'Menu:UserAccountsMenu:Title' => '用户帐户', - 'Menu:MyShortcuts' => '我的快捷方式', - 'Menu:UserManagement' => '用户管理', - 'Menu:Queries' => '查询', - 'Menu:ConfigurationTools' => '配置', + 'Menu:MyShortcuts' => '我的快捷方式', + 'Menu:UserManagement' => '用户管理', + 'Menu:Queries' => '查询', + 'Menu:ConfigurationTools' => '配置', )); // Additional language entries not present in English dict Dict::Add('ZH CN', 'Chinese', '简体中文', array( - 'UI:Toggle:StandardDashboard' => '标准', - 'UI:Toggle:CustomDashboard' => '自定义', - 'UI:Dashboard:Edit' => '编辑这个页面...', - 'UI:Dashboard:Revert' => '还原到初始版本...', + 'UI:Toggle:StandardDashboard' => '标准', + 'UI:Toggle:CustomDashboard' => '自定义', + 'UI:Dashboard:Edit' => '编辑这个页面...', + 'UI:Dashboard:Revert' => '还原到初始版本...', )); diff --git a/images/favicon.ico b/images/favicon.ico index 0d812b0f73..0b3cd45868 100644 Binary files a/images/favicon.ico and b/images/favicon.ico differ diff --git a/images/icons/icons8-security-pass.svg b/images/icons/icons8-security-pass.svg new file mode 100644 index 0000000000..c4d8dec931 --- /dev/null +++ b/images/icons/icons8-security-pass.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/itop-logo-external.png b/images/itop-logo-external.png index 8bd1b0439e..40f1a0535e 100644 Binary files a/images/itop-logo-external.png and b/images/itop-logo-external.png differ diff --git a/images/itop-logo-square-64.png b/images/itop-logo-square-64.png index c88cf302f9..63a369352d 100644 Binary files a/images/itop-logo-square-64.png and b/images/itop-logo-square-64.png differ diff --git a/images/itop-logo-square.png b/images/itop-logo-square.png index 5f77d84d40..090de8adb6 100644 Binary files a/images/itop-logo-square.png and b/images/itop-logo-square.png differ diff --git a/images/itop-logo.png b/images/itop-logo.png index 0519b9de78..4c6b55779d 100644 Binary files a/images/itop-logo.png and b/images/itop-logo.png differ diff --git a/images/logo-combodo.png b/images/logo-combodo.png old mode 100755 new mode 100644 index 66b1e07d23..ae3b82348a Binary files a/images/logo-combodo.png and b/images/logo-combodo.png differ diff --git a/images/logo-itop-dark-bg.svg b/images/logo-itop-dark-bg.svg index 98f047d9ca..4ce457db7b 100644 --- a/images/logo-itop-dark-bg.svg +++ b/images/logo-itop-dark-bg.svg @@ -1,59 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/images/logos/logo-combodo-dark.svg b/images/logos/logo-combodo-dark.svg new file mode 100644 index 0000000000..aad057c3ba --- /dev/null +++ b/images/logos/logo-combodo-dark.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + diff --git a/images/logos/logo-combodo-light.svg b/images/logos/logo-combodo-light.svg new file mode 100644 index 0000000000..39687130ad --- /dev/null +++ b/images/logos/logo-combodo-light.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + diff --git a/images/logos/logo-itop-baseline-dark.svg b/images/logos/logo-itop-baseline-dark.svg new file mode 100644 index 0000000000..444ba89e79 --- /dev/null +++ b/images/logos/logo-itop-baseline-dark.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + diff --git a/images/logos/logo-itop-baseline-light.svg b/images/logos/logo-itop-baseline-light.svg new file mode 100644 index 0000000000..ffea897a03 --- /dev/null +++ b/images/logos/logo-itop-baseline-light.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + diff --git a/images/logos/logo-itop-compact-dark.svg b/images/logos/logo-itop-compact-dark.svg new file mode 100644 index 0000000000..1acc17bd3f --- /dev/null +++ b/images/logos/logo-itop-compact-dark.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/images/logos/logo-itop-compact-light.svg b/images/logos/logo-itop-compact-light.svg new file mode 100644 index 0000000000..4a7532d6d0 --- /dev/null +++ b/images/logos/logo-itop-compact-light.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/images/logos/logo-itop-compact-orange.svg b/images/logos/logo-itop-compact-orange.svg new file mode 100644 index 0000000000..cd71f05373 --- /dev/null +++ b/images/logos/logo-itop-compact-orange.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/images/logos/logo-itop-simple-dark.svg b/images/logos/logo-itop-simple-dark.svg new file mode 100644 index 0000000000..c1a545cdb2 --- /dev/null +++ b/images/logos/logo-itop-simple-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/logos/logo-itop-simple-light.svg b/images/logos/logo-itop-simple-light.svg new file mode 100644 index 0000000000..4ce457db7b --- /dev/null +++ b/images/logos/logo-itop-simple-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/index.php b/index.php index 61a6734258..c50fd8cbb6 100644 --- a/index.php +++ b/index.php @@ -25,9 +25,11 @@ if (file_exists(dirname(__FILE__).'/'.$sConfigFile)) } else { - echo "

            Security Warning: the configuration file '$sConfigFile' should be read-only.

            "; - echo "

            Please modify the access rights to this file.

            "; - echo "

            Click here to ignore this warning and continue to run iTop.

            "; + echo <<Security Warning: the configuration file '{$sConfigFile}' should be read-only.

            +

            Please modify the access rights to this file.

            +

            Click here to ignore this warning and continue to run iTop.

            +HTML; } } else diff --git a/js/ckeditor.on-init.js b/js/ckeditor.on-init.js index d485099938..4e519d82f6 100644 --- a/js/ckeditor.on-init.js +++ b/js/ckeditor.on-init.js @@ -21,3 +21,31 @@ if ((CKEDITOR !== undefined) && (CKEDITOR.plugins.registered['disabler'] === und }); } + +// Rewrite the CKEditor Mentions plugin regexp to make it suitable for all Unicode alphabets. +if (CKEDITOR !== undefined && CKEDITOR.plugins.registered['mentions']) { + // From https://github.com/ckeditor/ckeditor4/blob/a3786007fb979d7d7bff3d10c34a2d422935baed/plugins/mentions/plugin.js#L147 + function createPattern(marker, minChars) { + // Escape marker if it's a regex token + // https://github.com/tc39/proposal-regex-escaping/blob/main/EscapedChars.md#syntaxcharacter-proposal + const regexTokens = ['^', '$', '\\', '.', '*', '+', '?', '(', ')', '[', ']', '{', '}', '|']; + if (regexTokens.indexOf(marker) >= 0) { + marker = '\\' + marker; + } + + let pattern = marker + '[\\p{L}\\p{N}_-]'; + if ( minChars ) { + pattern += '{' + minChars + ',}'; + } else { + pattern += '*'; + } + pattern += '$'; + return new RegExp(pattern, 'u'); + } + + CKEDITOR.on('instanceLoaded', event => { + event.editor.config.mentions.forEach(config => { + config.pattern = createPattern(config.marker, config.minChars); + }); + }); +} diff --git a/js/components/button-group.js b/js/components/button-group.js new file mode 100644 index 0000000000..a459a23720 --- /dev/null +++ b/js/components/button-group.js @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2013-2022 Combodo SARL + * + * This file is part of iTop. + * + * iTop is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * iTop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + */ + +; +// Apply a listener to element so we don't have to create one for every button on the page + +// ibo-button-group elements +$('body').on('enter_loading_state.button_group.itop', '[data-role="ibo-button-group"]', function(){ + $(this).find('[data-role="ibo-button"]').each(function(){ + $(this).prop('disabled', true); + }); + $(this).find('[data-role="ibo-button"]:first').trigger('enter_loading_state.button.itop'); +}) +.on('leave_loading_state.button_group.itop', '[data-role="ibo-button-group"]', function(){ + $(this).find('[data-role="ibo-button"]').each(function(){ + $(this).prop('disabled', false); + }); + $(this).find('[data-role="ibo-button"]:first').trigger('leave_loading_state.button.itop'); +}); \ No newline at end of file diff --git a/js/components/button.js b/js/components/button.js index 5d5b238061..8c404e8ed9 100644 --- a/js/components/button.js +++ b/js/components/button.js @@ -17,7 +17,9 @@ */ ; -// Apply a listener to element so we don't havec to create one for every button on the page +// Apply a listener to element so we don't have to create one for every button on the page + +// ibo-button elements $('body').on('enter_loading_state.button.itop', '[data-role="ibo-button"]', function(){ $(this).addClass('ibo-is-loading').prop('disabled', true); }) diff --git a/js/console_form_handler.js b/js/console_form_handler.js index 194a786298..24e428cc2e 100644 --- a/js/console_form_handler.js +++ b/js/console_form_handler.js @@ -35,8 +35,7 @@ $(function() { var me = this; - this.element - .append('
            ') + this.element.append('
            ') .addClass('console_form_handler'); this.options.oWizardHelper = window[this.options.wizard_helper_var_name]; @@ -48,13 +47,13 @@ $(function() // revert other modifications here _destroy: function() { - this.element - .removeClass('console_form_handler'); + this.element.removeClass('console_form_handler'); this._super(); }, _onUpdateFields: function(event, data) { var me = this; + me._updatePreviousValues(); var sFormPath = data.form_path; var sUpdateUrl = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'; @@ -64,7 +63,6 @@ $(function() { operation: 'custom_fields_update', attcode: this.options.custom_field_attcode, - //current_values: this.getCurrentValues(), requested_fields: data.requested_fields, form_path: sFormPath, json_obj: this.options.oWizardHelper.UpdateWizardToJSON() @@ -85,6 +83,7 @@ $(function() me.element.find('.last-error').text(data.error); } me._onUpdateAlways(data, sFormPath); + me.element.find('[data-field-id="previous_values"]').find('input[type="hidden"]').val('{}'); }); }, // On initialization or update diff --git a/js/extkeywidget.js b/js/extkeywidget.js index 6014debc57..830b06984a 100644 --- a/js/extkeywidget.js +++ b/js/extkeywidget.js @@ -564,6 +564,7 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper var oTemp = $('
            '+data.name+'
            '); var txt = oTemp.text(); // this causes HTML entities to be interpreted + var prevValue = $('#'+me.id).val(); var newValue; if ($('#label_'+me.id).length) { newValue = iObjectId; @@ -576,7 +577,6 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper newValue = txt; } - var prevValue = $('#'+me.id).val(); $('#'+me.id).val(newValue); if (prevValue != newValue) { // dependent fields will be updated using the WizardHelper JS object @@ -736,7 +736,10 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper var sId = $(this).attr('id'); var editorInst = CKEDITOR.instances[sId]; if (editorInst) { - editorInst.updateElement(); + editorInst.destroy(); + } + if ($('#'+sId).data('timeout_validate') != undefined) { + clearInterval($('#'+sId).data('timeout_validate')); } } diff --git a/js/form_handler.js b/js/form_handler.js index c709c5e432..285d167383 100644 --- a/js/form_handler.js +++ b/js/form_handler.js @@ -92,12 +92,27 @@ $(function() { return this.options.field_set.triggerHandler('get_current_values'); }, - + /** + * @private + * @since 3.0.2 3.1.0 + */ + _updatePreviousValues: function() + { + let me = this; + if(this.element.find('[data-attribute-previous-value]').length > 0) { + let aPreviousValues = {}; + $(this.element.find('[data-attribute-previous-value]')).each(function (iIdx, oElem) { + aPreviousValues[$(oElem).data('field-id')] = $(oElem).data('attribute-previous-value'); + }); + me.element.find('[data-field-id="previous_values"]').find('input[type="hidden"]').val(JSON.stringify(aPreviousValues)); + } + }, // Events callback // - Update fields depending on the update ones _onUpdateFields: function(oEvent, oData) { var me = this; + me._updatePreviousValues(); var sFormPath = oData.form_path; // Data checks diff --git a/js/forms-json-utils.js b/js/forms-json-utils.js index daaf94d0c5..56ba702654 100644 --- a/js/forms-json-utils.js +++ b/js/forms-json-utils.js @@ -304,33 +304,29 @@ function ValidateField(sFieldId, sPattern, bMandatory, sFormId, nullValue, origi function ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, originalValue) { + if ($('#'+sFieldId).length === 0) { + return; + } + var bValid; var sExplain = ''; var sTextContent; - if ($('#'+sFieldId).prop('disabled')) - { + if ($('#'+sFieldId).prop('disabled')) { bValid = true; // disabled fields are not checked - } - else - { + } else { // Get the contents without the tags var oFormattedContents = $("#cke_"+sFieldId+" iframe"); - if (oFormattedContents.length == 0) - { + if (oFormattedContents.length == 0) { var oSourceContents = $("#cke_"+sFieldId+" textarea.cke_source"); sTextContent = oSourceContents.val(); - } - else - { + } else { sTextContent = oFormattedContents.contents().find("body").text(); - - if (sTextContent == '') - { + + if (sTextContent == '') { // No plain text, maybe there is just an image... var oImg = oFormattedContents.contents().find("body img"); - if (oImg.length != 0) - { + if (oImg.length != 0) { sTextContent = 'image'; } } @@ -339,27 +335,19 @@ function ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue, // Get the original value without the tags var oFormattedOriginalContents = (originalValue !== undefined) ? $('
            ').html(originalValue) : undefined; var sTextOriginalContents = (oFormattedOriginalContents !== undefined) ? oFormattedOriginalContents.text() : undefined; - - if (bMandatory && (sTextContent == nullValue)) - { + + if (bMandatory && (sTextContent == nullValue)) { bValid = false; sExplain = Dict.S('UI:ValueMustBeSet'); - } - else if ((sTextOriginalContents != undefined) && (sTextContent == sTextOriginalContents)) - { + } else if ((sTextOriginalContents != undefined) && (sTextContent == sTextOriginalContents)) { bValid = false; - if (sTextOriginalContents == nullValue) - { + if (sTextOriginalContents == nullValue) { sExplain = Dict.S('UI:ValueMustBeSet'); - } - else - { + } else { // Note: value change check is not working well yet as the HTML to Text conversion is not exactly the same when done from the PHP value or the CKEditor value. sExplain = Dict.S('UI:ValueMustBeChanged'); } - } - else - { + } else { bValid = true; } } diff --git a/js/layouts/activity-panel/activity-panel.js b/js/layouts/activity-panel/activity-panel.js index 2842963e36..c2dc1b5c43 100644 --- a/js/layouts/activity-panel/activity-panel.js +++ b/js/layouts/activity-panel/activity-panel.js @@ -1262,7 +1262,6 @@ $(function() }); this._UpdateEntryGroupsVisibility(); - this._UpdateLoadMoreEntriesButtonVisibility(); this._UpdateMessagesCounters(); }, _ShowAllEntries: function() @@ -1337,30 +1336,6 @@ $(function() } }); }, - /** - * Update the "load more entries" button visibility regarding the current filters - * - * @private - * @return {void} - */ - _UpdateLoadMoreEntriesButtonVisibility: function () { - const oMoreButtonElem = this.element.find(this.js_selectors.load_more_entries); - const oAllButtonElem = this.element.find(this.js_selectors.load_all_entries); - - // Check if button exists (if all entries have been loaded, we might have remove it - if (oMoreButtonElem.length === 0) { - return; - } - - // Show button only if the states / edits filters are selected as log entries are always fully loaded - if (this._GetActiveTabToolbarElement().find(this.js_selectors.activity_filter + '[data-target-entry-types!="'+this.enums.entry_types.caselog+'"]:checked').length > 0) { - oMoreButtonElem.removeClass(this.css_classes.is_hidden); - oAllButtonElem.removeClass(this.css_classes.is_hidden); - } else { - oMoreButtonElem.addClass(this.css_classes.is_hidden); - oAllButtonElem.addClass(this.css_classes.is_hidden); - } - }, /** * Load the next entries and append them to the current ones * diff --git a/js/layouts/tab-container/tab-container.js b/js/layouts/tab-container/tab-container.js index 24c5ae63e0..7040529ac2 100644 --- a/js/layouts/tab-container/tab-container.js +++ b/js/layouts/tab-container/tab-container.js @@ -156,7 +156,7 @@ $(function() }); me._updateExtraTabsList(); }, { - root: $('.ibo-tab-container--tabs-list')[0], + root: this.element.find(this.js_selectors.tabs_list)[0], threshold: [0.9] // N°4783 Should be completely visible, but lowering the threshold prevents a bug in the JS Observer API when the window is zoomed in/out, in which case all items respond as being hidden even when they are not. }); this.element.find(this.js_selectors.tab_header).each(function(){ @@ -262,6 +262,16 @@ $(function() // Prevent anchor default behaviour oEvent.preventDefault(); + // Compute list position + // Note: Arbitrary +6px for the position as we don't want it to be exactly against the toggler + let fTopOffset = this.element.find(this.js_selectors.extra_tabs_list_toggler).offset().top + this.element.find(this.js_selectors.extra_tabs_list_toggler).outerHeight() + 6; + // We need to compute position from the right side of the screen because at this time the list isn't visible and we can't know its width, so we can't position it regarding the left side of the screen + // Note: We use window.innerWidth instead of outerWidth as we need the width of the actual viewport, not the OS browser window + let fRightOffset = window.innerWidth - this.element.find(this.js_selectors.extra_tabs_list_toggler).offset().left - this.element.find(this.js_selectors.extra_tabs_list_toggler).outerWidth(); + this.element.find(this.js_selectors.extra_tabs_list) + .css('top', fTopOffset + 'px') + .css('right', fRightOffset + 'px'); + // TODO 3.0.0: Should/could we use a popover menu instead here? this.element.find(this.js_selectors.extra_tabs_list).toggleClass(this.css_classes.is_hidden); }, diff --git a/js/linksdirectwidget.js b/js/linksdirectwidget.js index af3b637b2e..b5fd109ca0 100644 --- a/js/linksdirectwidget.js +++ b/js/linksdirectwidget.js @@ -71,102 +71,102 @@ $(function() this.toBeAdded = []; this.inputToBeRemoved = $(''); this.toBeRemoved = []; - - + + this.element .after(this.inputToBeCreated) - .after(this.inputToBeDeleted) - .after(this.inputToBeAdded) - .after(this.inputToBeRemoved) - .after('      ') - .after(this.indicator); - for(k in this.options.buttons) - { + .after(this.inputToBeDeleted) + .after(this.inputToBeAdded) + .after(this.inputToBeRemoved) + .after('      ') + .after(this.indicator); + for (k in this.options.buttons) { this.element.after(this.oButtons[this.options.buttons[k]]).after('   '); } - - this.element.find('.selectList'+this.id).bind('change', function() { me._updateButtons(); }); - this.oButtons['delete'].on('click', function() { - $('.selectList'+me.id+':checked', me.element).each( function() { me._deleteRow($(this)); }); + + this.element.find('.selectList'+this.id).bind('change', function () { + me._updateButtons(); }); - this.oButtons['create'].on('click', function() { + this.oButtons['delete'].on('click', function () { + $('.selectList'+me.id+':checked', me.element).each(function () { + me._deleteRow($(this)); + }); + }); + this.oButtons['create'].on('click', function () { me._createRow(); }); - this.oButtons['remove'].on('click', function() { - $('.selectList'+me.id+':checked', me.element).each( function() { me._removeRow($(this)); }); + this.oButtons['remove'].on('click', function () { + $('.selectList'+me.id+':checked', me.element).each(function () { + me._removeRow($(this)); + }); }); - this.oButtons['add'].on('click', function() { + this.oButtons['add'].on('click', function () { me._selectToAdd(); }); - + this._updateButtons(); }, - + // called when created, and later when changing options - _refresh: function() - { + _refresh: function () { this._updateButtons(); }, // events bound via _bind are removed automatically // revert other modifications here - _destroy: function() - { + _destroy: function () { this.element - .removeClass('itop-directlinks'); + .removeClass('itop-directlinks'); }, // _setOptions is called with a hash of all options that are changing - _setOptions: function() - { + _setOptions: function () { // in 1.9 would use _superApply this._superApply(arguments); }, // _setOption is called for each individual option that is changing - _setOption: function( key, value ) - { + _setOption: function (key, value) { // in 1.9 would use _super this._superApply(arguments); - - if (key == 'fields') this._refresh(); + + if (key == 'fields') { + this._refresh(); + } }, - _updateButtons: function() - { + _updateButtons: function () { var oChecked = $('.selectList'+this.id+':checked', this.element); - switch(oChecked.length) - { + switch (oChecked.length) { case 0: this.oButtons['delete'].prop('disabled', true); this.oButtons['remove'].prop('disabled', true); this.oButtons['modify'].prop('disabled', true); - break; - + break; + case 1: this.oButtons['delete'].prop('disabled', false); this.oButtons['remove'].prop('disabled', false); this.oButtons['modify'].prop('disabled', false); - break; - + break; + default: this.oButtons['delete'].prop('disabled', false); this.oButtons['remove'].prop('disabled', false); this.oButtons['modify'].prop('disabled', true); - break; + break; } }, - _updateTable: function() - { + _updateTable: function () { var me = this; /* this.datatable.trigger("update").trigger("applyWidgets"); this.datatable.tableHover();*/ - this.datatable.find('.selectList'+this.id).bind('change', function() { me._updateButtons(); }); + this.datatable.find('.selectList'+this.id).bind('change', function () { + me._updateButtons(); + }); }, - _updateDlgPosition: function() - { - this.oDlg.dialog('option', { position: { my: "center", at: "center", of: window }}); + _updateDlgPosition: function () { + this.oDlg.dialog('option', {position: {my: "center", at: "center", of: window}}); }, - _createRow: function() - { + _createRow: function () { this.oButtons['create'].prop('disabled', true); this.indicator.html(''); oParams = this.options.submit_parameters; @@ -176,30 +176,39 @@ $(function() oParams.att_code = this.options.att_code; oParams.iInputId = this.id; var me = this; - if (this.options.oWizardHelper) - { + if (this.options.oWizardHelper) { this.options.oWizardHelper.UpdateWizard(); oParams.json = this.options.oWizardHelper.ToJSON(); } - $.post(this.options.submit_to, oParams, function(data){ + $.post(this.options.submit_to, oParams, function (data) { me.oDlg = $('
            '); $('body').append(me.oDlg); me.oDlg.html(data); - me.oDlg.find('form').removeAttr('onsubmit').bind('submit', function() { me._onCreateRow(); return false; } ); - me.oDlg.find('button.cancel').off('click').on('click', function() { me.oDlg.dialog('close'); } ); - + me.oDlg.find('form').removeAttr('onsubmit'); + me.oDlg.find('button[type="submit"]').on('click', function (event) { + me._onCreateRow(); + return false; + }); + setTimeout(function () { + me.oDlg.find('button.cancel').off('click').on('click', function () { + me.oDlg.dialog('close'); + }); + }, 500); me.oDlg.dialog({ title: me.options.labels['creation_title'], modal: true, width: 'auto', height: 'auto', - maxHeight: $(window).height() - 50, - position: { my: "center", at: "center", of: window }, - close: function() { me._onDlgClose(); } + maxHeight: $(window).height()-50, + position: {my: "center", at: "center", of: window}, + close: function () { + me._onDlgClose(); + } }); me.indicator.html(''); me.oButtons['create'].prop('disabled', false); me._updateDlgPosition(); + }); }, _selectToAdd: function() @@ -221,38 +230,46 @@ $(function() } ); - if (this.options.oWizardHelper) - { + if (this.options.oWizardHelper) { this.options.oWizardHelper.UpdateWizard(); oParams.json = this.options.oWizardHelper.ToJSON(); } var me = this; - $.post(this.options.submit_to, oParams, function(data){ + $.post(this.options.submit_to, oParams, function (data) { me.oDlg = $('
            '); $('body').append(me.oDlg); me.oDlg.html(data); - me.oDlg.find('form').removeAttr('onsubmit').bind('submit', function() { me._onSearchToAdd(); return false; } ); - $('#SearchFormToAdd_'+me.id).resize(function() { me._onSearchDlgUpdateSize(); }); - + me.oDlg.find('form').removeAttr('onsubmit').bind('submit', function () { + me._onSearchToAdd(); + return false; + }); + $('#SearchFormToAdd_'+me.id).resize(function () { + me._onSearchDlgUpdateSize(); + }); + me.oDlg.dialog({ title: me.options.labels['selection_title'], modal: true, - width: $(window).width()*0.8, - height: $(window).height()*0.8, - maxHeight: $(window).height() - 50, - position: { my: "center", at: "center", of: window }, - close: function() { me._onDlgClose(); }, - resizeStop: function() { me._onSearchDlgUpdateSize(); }, + width: $(window).width() * 0.8, + height: $(window).height() * 0.8, + maxHeight: $(window).height()-50, + position: {my: "center", at: "center", of: window}, + close: function () { + me._onDlgClose(); + }, + resizeStop: function () { + me._onSearchDlgUpdateSize(); + }, buttons: [ { text: Dict.S('UI:Button:Cancel'), class: "cancel ibo-is-alternative ibo-is-neutral", - click: function() { + click: function () { $(this).dialog('close'); } }, { - text: Dict.S('UI:Button:Add'), + text: Dict.S('UI:Button:Add'), class: "ok ibo-is-regular ibo-is-primary", click: function() { me._onDoAdd(); @@ -439,39 +456,48 @@ $(function() var me = this; me.oDlg.find('button').prop('disabled', true); me.oDlg.find('span.indicator').html(''); - $.post(this.options.submit_to, oParams, function(data){ + $.post(this.options.submit_to, oParams, function (data) { me.oDlg.html(data); - me.oDlg.find('form').removeAttr('onsubmit').bind('submit', function() { me._onCreateRow(); return false; } ); - me.oDlg.find('button.cancel').off('click').on('click', function() { me.oDlg.dialog('close'); } ); - me._updateDlgPosition(); + me.oDlg.find('form').removeAttr('onsubmit').bind('submit', function () { + me._onCreateRow(); + return false; + }); + me.oDlg.find('button.cancel').off('click').on('click', function () { + me.oDlg.dialog('close'); + }); + me._updateDlgPosition(); }); }, - _onCreateRow: function() - { + _onCreateRow: function () { // Validate the form + var me = this; var sFormId = this.oDlg.find('form').attr('id'); - if (CheckFields(sFormId, true)) - { + if (CheckFields(sFormId, true)) { // Gather the values from the form + me.oDlg.find('.htmlEditor').each(function () { + CKEDITOR.instances[this.id].destroy(); + if ($('#'+this.id).data('timeout_validate') != undefined) { + clearInterval($('#'+this.id).data('timeout_validate')); + } + }); + oParams = this.options.submit_parameters; var oValues = {}; - this.oDlg.find(':input').each( function() { - if (this.name != '') - { + this.oDlg.find(':input').each(function () { + if (this.name != '') { oParams[this.name] = this.value; oValues[this.name] = this.value; } }); var nextIdx = 0; - for(k in this.toBeCreated) - { + for (k in this.toBeCreated) { nextIdx++; } nextIdx++; this.toBeCreated[nextIdx] = oValues; this.inputToBeCreated.val(JSON.stringify(this.toBeCreated)); this.oDlg.dialog('close'); - + oParams = this.options.submit_parameters; oParams.operation = 'getLinksetRow'; oParams['class'] = this.options.class_name; @@ -483,7 +509,7 @@ $(function() this.oButtons['create'].prop('disabled', true); this.indicator.html(''); - $.post(this.options.submit_to, oParams, function(data){ + $.post(this.options.submit_to, oParams, function (data) { // From data variable we get data entry and insert the first (and only) one me.datatable.DataTable().row.add(data.data[0]).draw(); $('#datatable_'+me.id+' .dataTables_empty').hide(); @@ -494,34 +520,27 @@ $(function() }); } }, - _onDlgClose: function() - { + _onDlgClose: function () { this.oDlg.remove(); this.oDlg = null; }, - _onSearchDlgUpdateSize: function() - { + _onSearchDlgUpdateSize: function () { var searchHeight = $('#SearchFormToAdd_'+this.id).outerHeight(); var dlgHeight = this.oDlg.height(); - $('.wizContainer', this.oDlg).height(dlgHeight - 20); - $('#SearchResultsToAdd_'+this.id).height(dlgHeight - 50 - searchHeight); + $('.wizContainer', this.oDlg).height(dlgHeight-20); + $('#SearchResultsToAdd_'+this.id).height(dlgHeight-50-searchHeight); }, - _deleteRow: function(oCheckbox) - { + _deleteRow: function (oCheckbox) { var iObjKey = parseInt(oCheckbox.val(), 10); // Number in base 10 - - if (iObjKey > 0) - { + + if (iObjKey > 0) { // Existing objet: add it to the "to be deleted" list // if it has not just been added now - if (this._InArray(this.toBeAdded, iObjKey)) - { + if (this._InArray(this.toBeAdded, iObjKey)) { this.toBeAdded = this._ArrayRemove(this.toBeAdded, iObjKey); - this.inputToBeAdded.val(JSON.stringify(this.toBeAdded)); - } - else - { - this.toBeDeleted.push(iObjKey); + this.inputToBeAdded.val(JSON.stringify(this.toBeAdded)); + } else { + this.toBeDeleted.push(iObjKey); this.inputToBeDeleted.val(JSON.stringify(this.toBeDeleted)); } } diff --git a/js/linkswidget.js b/js/linkswidget.js index d7a51736c7..ae6334b848 100644 --- a/js/linkswidget.js +++ b/js/linkswidget.js @@ -19,7 +19,7 @@ * * @since 3.0.0 Add iMaxAddedId parameter */ -function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizHelper, sExtKeyToRemote, bDoSearch, iMaxAddedId = 0) { +function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizHelper, sExtKeyToRemote, bDoSearch, iMaxAddedId = 0, aRemoved = []) { this.id = id; this.iInputId = iInputId; this.sClass = sClass; @@ -30,7 +30,7 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizH this.sExtKeyToRemote = sExtKeyToRemote; this.iMaxAddedId = iMaxAddedId; this.aAdded = []; - this.aRemoved = []; + this.aRemoved = aRemoved; this.aModified = {}; this.bDoSearch = bDoSearch; // false if the search is not launched let me = this; diff --git a/js/search/search_form_criteria_enum.js b/js/search/search_form_criteria_enum.js index 9ad81696be..3fb73d42e4 100644 --- a/js/search/search_form_criteria_enum.js +++ b/js/search/search_form_criteria_enum.js @@ -428,7 +428,6 @@ $(function() bSearchMode: 'true', sOutputFormat: 'json', operation: 'ac_extkey', - sAutocompleteOperation: 'equals_start_with' } ) .done(function(oResponse, sStatus, oXHR){ @@ -441,28 +440,7 @@ $(function() return; } - oACXHR = $.post( - AddAppContext(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'), - { - sTargetClass: me.options.field.target_class, - sFilter: 'SELECT ' + me.options.field.target_class, - q: sQuery, - bSearchMode: 'true', - sOutputFormat: 'json', - operation: 'ac_extkey', - sAutocompleteOperation: 'contains' - } - ) - .done(function(oResponseContains, sStatus, oXHR){ - //filter duplicates - $.each(oResponse, function(index, value) { - delete oResponseContains[index]; - }); - - me._onACSearchContainsSuccess(oResponseContains); - - - }); + me._onACSearchContainsSuccess(oResponse); }) .fail(function(oResponse, sStatus, oXHR){ me._onACSearchFail(oResponse, sStatus); }) .always(function(oResponse, sStatus, oXHR){ @@ -701,44 +679,16 @@ $(function() } } this._setACWaitTempHint(); - }, - // Autocomplete CONTAINS callbacks - _onACSearchContainsSuccess: function(oResponse) - { - if(typeof oResponse !== 'object') - { - this._emptyACTempHint(); - return false; - } - - var oDynamicListElem = this.element.find('.sfc_opc_mc_items_dynamic'); - if(Object.keys(oResponse).length > 0) - { - // Note: Response is indexed by labels from server so the JSON is always ordered on decoding. - for(var skey in oResponse) - { - var sValue = oResponse[skey].value; - var sLabel = oResponse[skey].label; - - // Note: We don't use the _isSelectedValue() method here as it only returns "applied" values; at this moment will could have a checked value that is not among selected (me.options.values) yet. The result would be an hidden item from the AC results. - var bSelected = (this.element.find(this._getSelectedValuesWrapperSelector() + ' .sfc_opc_mc_item[data-value-code="' + sValue + '"]').length > 0); - var bInitChecked = bSelected; - var bInitHidden = bSelected; - var oValueElem = this._makeListItemElement(sLabel, sValue, bInitChecked, bInitHidden,oResponse[skey].obsolescence_flag,oResponse[skey].additional_field); - oValueElem.appendTo(oDynamicListElem); - } - } - - if (oDynamicListElem.find('.sfc_opc_mc_item').length == 0) - { - this._setACNoResultHint(); - } - else - { - this._emptyACTempHint(); + if (oDynamicListElem.find('.sfc_opc_mc_item').length == 0) + { + this._setACNoResultHint(); } - }, + else + { + this._emptyACTempHint(); + } + }, _onACSearchFail: function(oResponse, sStatus) { if(sStatus !== 'abort') diff --git a/js/tabularfieldsselector.js b/js/tabularfieldsselector.js index d4463df5b4..4863e657cc 100644 --- a/js/tabularfieldsselector.js +++ b/js/tabularfieldsselector.js @@ -51,8 +51,8 @@ $(function () {
            `+this._format(this.options.labels.columns_selection, i)+`
    `; sContent += ` -
    -
    +
    +
    diff --git a/js/utils.js b/js/utils.js index 78907be060..3f4cbb7ed7 100644 --- a/js/utils.js +++ b/js/utils.js @@ -389,11 +389,13 @@ function ExportListDlg(sOQL, sDataTableId, sFormat, sDlgTitle) { var oColumns = $('#'+sDataTableName).DataTable().ajax.params()['columns']; for (var j in oColumns) { if (oColumns[j]['data']) { - var sCode = oColumns[j]['data'].split("/"); - if (sCode[1] == '_key_') { - sCode[1] = 'id'; + if (oColumns[j]['data']!='id') { + var sCode = oColumns[j]['data'].split("/"); + if (sCode[1] == '_key_') { + sCode[1] = 'id'; + } + aFields.push(sCode[0]+'.'+sCode[1]); } - aFields.push(sCode[0]+'.'+sCode[1]); } else { for (var k in oColumns[j]) { if (oColumns[j][k].checked) { @@ -807,8 +809,10 @@ const CombodoTooltip = { oOptions['content'] = sContent; // Interaction (selection, click, ...) have to be enabled manually - // Important: When set to true, if "data-tooltip-append-to" is not specified, tooltip will be append to the parent element instead of the body - const bInteractive = oElem.attr('data-tooltip-interaction-enabled') === 'true'; + // Important: When set to true, if "data-tooltip-append-to" is not specified, tooltip will be appended to the parent element instead of the body + // Note: Defaults to true if it contains hyperlink + let bDefaultInteractive = (bEnableHTML && sContent.indexOf(" -1) + const bInteractive = oElem.attr('data-tooltip-interaction-enabled') !== undefined ? oElem.attr('data-tooltip-interaction-enabled') === 'true' : bDefaultInteractive; oOptions['interactive'] = bInteractive; // Element to append the tooltip to diff --git a/js/wizardhelper.js b/js/wizardhelper.js index c31f8ea87c..5877e75627 100644 --- a/js/wizardhelper.js +++ b/js/wizardhelper.js @@ -176,6 +176,9 @@ function WizardHelper(sClass, sFormPrefix, sState, sInitialState, sStimulus) { var sString = "$('#"+aRefreshed[i]+"').trigger('change').trigger('update');"; window.setTimeout(sString, 1); // Synchronous 'trigger' does nothing, call it asynchronously } + if($('.blockUI').length == 0) { + $('.disabledDuringFieldLoading').prop("disabled", false).removeClass('disabledDuringFieldLoading'); + } }; this.UpdateWizard = function () { @@ -201,6 +204,10 @@ function WizardHelper(sClass, sFormPrefix, sState, sInitialState, sStimulus) { function (html) { $('#ajax_content').html(html); $('.blockUI').parent().unblock(); + + if($('.blockUI').length == 0) { + $('.disabledDuringFieldLoading').prop("disabled", false).removeClass('disabledDuringFieldLoading'); + } } ); }; @@ -235,7 +242,8 @@ function WizardHelper(sClass, sFormPrefix, sState, sInitialState, sStimulus) { this.ResetQuery(); this.UpdateWizard(); - while (index < aFieldNames.length) + var fieldForm = null; + while (index < aFieldNames.length ) { sAttCode = aFieldNames[index]; sFieldId = this.GetFieldId(sAttCode); @@ -247,11 +255,16 @@ function WizardHelper(sClass, sFormPrefix, sState, sInitialState, sStimulus) { message: '', overlayCSS: {backgroundColor: '#f1f1f1', opacity: 0.3} }); + fieldForm = $('#field_' + sFieldId).closest('form'); this.RequestAllowedValues(sAttCode); } index++; } + if ((fieldForm !== null) && ($('.blockUI').length > 0)) { + fieldForm.find('button[type=submit]:not(:disabled)').prop("disabled", true).addClass('disabledDuringFieldLoading'); + } + if (nbOfFieldsToUpdate > 0) { this.AjaxQueryServer(); diff --git a/lib/apereo/phpcas/.codecov.yml b/lib/apereo/phpcas/.codecov.yml new file mode 100644 index 0000000000..ddfdd0effb --- /dev/null +++ b/lib/apereo/phpcas/.codecov.yml @@ -0,0 +1,17 @@ +codecov: + strict_yaml_branch: master + +coverage: + round: up + precision: 2 + status: + project: + default: + target: "70%" + informational: true + patch: # temporarily disabled + default: + target: "70%" + informational: true + +comment: false diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/.gitattributes b/lib/apereo/phpcas/.gitattributes similarity index 100% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/.gitattributes rename to lib/apereo/phpcas/.gitattributes diff --git a/lib/apereo/phpcas/.github/dependabot.yml b/lib/apereo/phpcas/.github/dependabot.yml new file mode 100644 index 0000000000..c630ffa6b3 --- /dev/null +++ b/lib/apereo/phpcas/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: composer + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/lib/apereo/phpcas/.github/workflows/test.yml b/lib/apereo/phpcas/.github/workflows/test.yml new file mode 100644 index 0000000000..081e334d61 --- /dev/null +++ b/lib/apereo/phpcas/.github/workflows/test.yml @@ -0,0 +1,43 @@ +name: Test + +on: + - push + - pull_request + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php-version: + - php: '7.2' + phpunit: '8' + - php: '7.3' + phpunit: latest + - php: '7.4' + phpunit: latest + - php: '8.0' + phpunit: latest + - php: '8.1' + phpunit: latest + steps: + - uses: actions/checkout@v2 + - uses: php-actions/composer@v6 + with: + php_version: "${{ matrix.php-version.php }}" + - name: Run PHPUnit + uses: php-actions/phpunit@v3 + with: + version: "${{ matrix.php-version.phpunit }}" + php_version: "${{ matrix.php-version.php }}" + php_extensions: xdebug + args: --verbose --coverage-clover=coverage.xml + bootstrap: vendor/autoload.php + env: + XDEBUG_MODE: coverage + - name: Report coverage + uses: codecov/codecov-action@v1 + with: + files: coverage.xml diff --git a/lib/apereo/phpcas/CAS.php b/lib/apereo/phpcas/CAS.php new file mode 100644 index 0000000000..6ddcf07bce --- /dev/null +++ b/lib/apereo/phpcas/CAS.php @@ -0,0 +1,32 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +require_once __DIR__.'/source/CAS.php'; + +trigger_error('Including CAS.php is deprecated. Install phpCAS using composer instead.', E_USER_DEPRECATED); diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/LICENSE b/lib/apereo/phpcas/LICENSE similarity index 100% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/LICENSE rename to lib/apereo/phpcas/LICENSE diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/NOTICE b/lib/apereo/phpcas/NOTICE similarity index 100% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/NOTICE rename to lib/apereo/phpcas/NOTICE diff --git a/lib/apereo/phpcas/README.md b/lib/apereo/phpcas/README.md new file mode 100644 index 0000000000..d48128912e --- /dev/null +++ b/lib/apereo/phpcas/README.md @@ -0,0 +1,35 @@ +phpCAS +======= + +phpCAS is an authentication library that allows PHP applications to easily authenticate +users via a Central Authentication Service (CAS) server. + +Please see the wiki website for more information: + +https://apereo.github.io/phpCAS/ + +Api documentation can be found here: + +https://apereo.github.io/phpCAS/api/ + + +[![Test](https://github.com/apereo/phpCAS/actions/workflows/test.yml/badge.svg)](https://github.com/apereo/phpCAS/actions/workflows/test.yml) + +LICENSE +------- + +Copyright 2007-2020, Apereo Foundation. +This project includes software developed by Apereo Foundation. +http://www.apereo.org/ + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this software except in compliance with the License. +You may obtain a copy of the License at: + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/lib/apereo/phpcas/composer.json b/lib/apereo/phpcas/composer.json new file mode 100644 index 0000000000..89ab7b9f61 --- /dev/null +++ b/lib/apereo/phpcas/composer.json @@ -0,0 +1,55 @@ +{ + "name" : "apereo/phpcas", + "description" : "Provides a simple API for authenticating users against a CAS server", + "keywords" : [ + "cas", + "jasig", + "apereo" + ], + "homepage" : "https://wiki.jasig.org/display/CASC/phpCAS", + "type" : "library", + "license" : "Apache-2.0", + "authors" : [{ + "name" : "Joachim Fritschi", + "homepage" : "https://github.com/jfritschi", + "email" : "jfritschi@freenet.de" + }, { + "name" : "Adam Franco", + "homepage" : "https://github.com/adamfranco" + }, { + "name" : "Henry Pan", + "homepage" : "https://github.com/phy25" + } + ], + "require" : { + "php" : ">=7.1.0", + "ext-curl" : "*", + "ext-dom" : "*", + "psr/log" : "^1.0 || ^2.0 || ^3.0" + }, + "require-dev" : { + "monolog/monolog" : "^1.0.0 || ^2.0.0", + "phpunit/phpunit" : ">=7.5", + "phpstan/phpstan" : "^1.5" + }, + "autoload" : { + "classmap" : [ + "source/" + ] + }, + "autoload-dev" : { + "files": ["source/CAS.php"], + "psr-4" : { + "PhpCas\\" : "test/CAS/" + } + }, + "scripts" : { + "test" : "phpunit", + "phpstan" : "phpstan" + }, + "extra" : { + "branch-alias" : { + "dev-master" : "1.3.x-dev" + } + } +} diff --git a/lib/apereo/phpcas/phpunit.xml.dist b/lib/apereo/phpcas/phpunit.xml.dist new file mode 100644 index 0000000000..f0431f153f --- /dev/null +++ b/lib/apereo/phpcas/phpunit.xml.dist @@ -0,0 +1,13 @@ + + + + + source/ + + + + + test/CAS/Tests/ + + + diff --git a/lib/apereo/phpcas/source/CAS.php b/lib/apereo/phpcas/source/CAS.php new file mode 100644 index 0000000000..c30e22d0ab --- /dev/null +++ b/lib/apereo/phpcas/source/CAS.php @@ -0,0 +1,2065 @@ + + * @author Olivier Berger + * @author Brett Bieber + * @author Joachim Fritschi + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * @ingroup public + */ + +use Psr\Log\LoggerInterface; + +// +// hack by Vangelis Haniotakis to handle the absence of $_SERVER['REQUEST_URI'] +// in IIS +// +if (!isset($_SERVER['REQUEST_URI']) && isset($_SERVER['SCRIPT_NAME']) && isset($_SERVER['QUERY_STRING'])) { + $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['QUERY_STRING']; +} + + +// ######################################################################## +// CONSTANTS +// ######################################################################## + +// ------------------------------------------------------------------------ +// CAS VERSIONS +// ------------------------------------------------------------------------ + +/** + * phpCAS version. accessible for the user by phpCAS::getVersion(). + */ +define('PHPCAS_VERSION', '1.5.0'); + +/** + * @addtogroup public + * @{ + */ + +/** + * phpCAS supported protocols. accessible for the user by phpCAS::getSupportedProtocols(). + */ + +/** + * CAS version 1.0 + */ +define("CAS_VERSION_1_0", '1.0'); +/*! + * CAS version 2.0 +*/ +define("CAS_VERSION_2_0", '2.0'); +/** + * CAS version 3.0 + */ +define("CAS_VERSION_3_0", '3.0'); + +// ------------------------------------------------------------------------ +// SAML defines +// ------------------------------------------------------------------------ + +/** + * SAML protocol + */ +define("SAML_VERSION_1_1", 'S1'); + +/** + * XML header for SAML POST + */ +define("SAML_XML_HEADER", ''); + +/** + * SOAP envelope for SAML POST + */ +define("SAML_SOAP_ENV", ''); + +/** + * SOAP body for SAML POST + */ +define("SAML_SOAP_BODY", ''); + +/** + * SAMLP request + */ +define("SAMLP_REQUEST", ''); +define("SAMLP_REQUEST_CLOSE", ''); + +/** + * SAMLP artifact tag (for the ticket) + */ +define("SAML_ASSERTION_ARTIFACT", ''); + +/** + * SAMLP close + */ +define("SAML_ASSERTION_ARTIFACT_CLOSE", ''); + +/** + * SOAP body close + */ +define("SAML_SOAP_BODY_CLOSE", ''); + +/** + * SOAP envelope close + */ +define("SAML_SOAP_ENV_CLOSE", ''); + +/** + * SAML Attributes + */ +define("SAML_ATTRIBUTES", 'SAMLATTRIBS'); + +/** @} */ +/** + * @addtogroup publicPGTStorage + * @{ + */ +// ------------------------------------------------------------------------ +// FILE PGT STORAGE +// ------------------------------------------------------------------------ +/** + * Default path used when storing PGT's to file + */ +define("CAS_PGT_STORAGE_FILE_DEFAULT_PATH", session_save_path()); +/** @} */ +// ------------------------------------------------------------------------ +// SERVICE ACCESS ERRORS +// ------------------------------------------------------------------------ +/** + * @addtogroup publicServices + * @{ + */ + +/** + * phpCAS::service() error code on success + */ +define("PHPCAS_SERVICE_OK", 0); +/** + * phpCAS::service() error code when the PT could not retrieve because + * the CAS server did not respond. + */ +define("PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE", 1); +/** + * phpCAS::service() error code when the PT could not retrieve because + * the response of the CAS server was ill-formed. + */ +define("PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE", 2); +/** + * phpCAS::service() error code when the PT could not retrieve because + * the CAS server did not want to. + */ +define("PHPCAS_SERVICE_PT_FAILURE", 3); +/** + * phpCAS::service() error code when the service was not available. + */ +define("PHPCAS_SERVICE_NOT_AVAILABLE", 4); + +// ------------------------------------------------------------------------ +// SERVICE TYPES +// ------------------------------------------------------------------------ +/** + * phpCAS::getProxiedService() type for HTTP GET + */ +define("PHPCAS_PROXIED_SERVICE_HTTP_GET", 'CAS_ProxiedService_Http_Get'); +/** + * phpCAS::getProxiedService() type for HTTP POST + */ +define("PHPCAS_PROXIED_SERVICE_HTTP_POST", 'CAS_ProxiedService_Http_Post'); +/** + * phpCAS::getProxiedService() type for IMAP + */ +define("PHPCAS_PROXIED_SERVICE_IMAP", 'CAS_ProxiedService_Imap'); + + +/** @} */ +// ------------------------------------------------------------------------ +// LANGUAGES +// ------------------------------------------------------------------------ +/** + * @addtogroup publicLang + * @{ + */ + +define("PHPCAS_LANG_ENGLISH", 'CAS_Languages_English'); +define("PHPCAS_LANG_FRENCH", 'CAS_Languages_French'); +define("PHPCAS_LANG_GREEK", 'CAS_Languages_Greek'); +define("PHPCAS_LANG_GERMAN", 'CAS_Languages_German'); +define("PHPCAS_LANG_JAPANESE", 'CAS_Languages_Japanese'); +define("PHPCAS_LANG_SPANISH", 'CAS_Languages_Spanish'); +define("PHPCAS_LANG_CATALAN", 'CAS_Languages_Catalan'); +define("PHPCAS_LANG_CHINESE_SIMPLIFIED", 'CAS_Languages_ChineseSimplified'); +define("PHPCAS_LANG_GALEGO", 'CAS_Languages_Galego'); +define("PHPCAS_LANG_PORTUGUESE", 'CAS_Languages_Portuguese'); + +/** @} */ + +/** + * @addtogroup internalLang + * @{ + */ + +/** + * phpCAS default language (when phpCAS::setLang() is not used) + */ +define("PHPCAS_LANG_DEFAULT", PHPCAS_LANG_ENGLISH); + +/** @} */ +// ------------------------------------------------------------------------ +// DEBUG +// ------------------------------------------------------------------------ +/** + * @addtogroup publicDebug + * @{ + */ + +/** + * The default directory for the debug file under Unix. + * @return string directory for the debug file + */ +function gettmpdir() { +if (!empty($_ENV['TMP'])) { return realpath($_ENV['TMP']); } +if (!empty($_ENV['TMPDIR'])) { return realpath( $_ENV['TMPDIR']); } +if (!empty($_ENV['TEMP'])) { return realpath( $_ENV['TEMP']); } +return "/tmp"; +} +define('DEFAULT_DEBUG_DIR', gettmpdir()."/"); + +/** @} */ + +// include the class autoloader +require_once __DIR__ . '/CAS/Autoload.php'; + +/** + * The phpCAS class is a simple container for the phpCAS library. It provides CAS + * authentication for web applications written in PHP. + * + * @ingroup public + * @class phpCAS + * @category Authentication + * @package PhpCAS + * @author Pascal Aubry + * @author Olivier Berger + * @author Brett Bieber + * @author Joachim Fritschi + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +class phpCAS +{ + + /** + * This variable is used by the interface class phpCAS. + * + * @var CAS_Client + * @hideinitializer + */ + private static $_PHPCAS_CLIENT; + + /** + * @var array + * This variable is used to store where the initializer is called from + * (to print a comprehensive error in case of multiple calls). + * + * @hideinitializer + */ + private static $_PHPCAS_INIT_CALL; + + /** + * @var array + * This variable is used to store phpCAS debug mode. + * + * @hideinitializer + */ + private static $_PHPCAS_DEBUG; + + /** + * This variable is used to enable verbose mode + * This pevents debug info to be show to the user. Since it's a security + * feature the default is false + * + * @hideinitializer + */ + private static $_PHPCAS_VERBOSE = false; + + + // ######################################################################## + // INITIALIZATION + // ######################################################################## + + /** + * @addtogroup publicInit + * @{ + */ + + /** + * phpCAS client initializer. + * + * @param string $server_version the version of the CAS server + * @param string $server_hostname the hostname of the CAS server + * @param int $server_port the port the CAS server is running on + * @param string $server_uri the URI the CAS server is responding on + * @param bool $changeSessionID Allow phpCAS to change the session_id + * (Single Sign Out/handleLogoutRequests + * is based on that change) + * @param \SessionHandlerInterface $sessionHandler the session handler + * + * @return void a newly created CAS_Client object + * @note Only one of the phpCAS::client() and phpCAS::proxy functions should be + * called, only once, and before all other methods (except phpCAS::getVersion() + * and phpCAS::setDebug()). + */ + public static function client($server_version, $server_hostname, + $server_port, $server_uri, $changeSessionID = true, \SessionHandlerInterface $sessionHandler = null + ) { + phpCAS :: traceBegin(); + if (is_object(self::$_PHPCAS_CLIENT)) { + phpCAS :: error(self::$_PHPCAS_INIT_CALL['method'] . '() has already been called (at ' . self::$_PHPCAS_INIT_CALL['file'] . ':' . self::$_PHPCAS_INIT_CALL['line'] . ')'); + } + + // store where the initializer is called from + $dbg = debug_backtrace(); + self::$_PHPCAS_INIT_CALL = array ( + 'done' => true, + 'file' => $dbg[0]['file'], + 'line' => $dbg[0]['line'], + 'method' => __CLASS__ . '::' . __FUNCTION__ + ); + + // initialize the object $_PHPCAS_CLIENT + try { + self::$_PHPCAS_CLIENT = new CAS_Client( + $server_version, false, $server_hostname, $server_port, $server_uri, + $changeSessionID, $sessionHandler + ); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + phpCAS :: traceEnd(); + } + + /** + * phpCAS proxy initializer. + * + * @param string $server_version the version of the CAS server + * @param string $server_hostname the hostname of the CAS server + * @param string $server_port the port the CAS server is running on + * @param string $server_uri the URI the CAS server is responding on + * @param bool $changeSessionID Allow phpCAS to change the session_id + * (Single Sign Out/handleLogoutRequests + * is based on that change) + * @param \SessionHandlerInterface $sessionHandler the session handler + * + * @return void a newly created CAS_Client object + * @note Only one of the phpCAS::client() and phpCAS::proxy functions should be + * called, only once, and before all other methods (except phpCAS::getVersion() + * and phpCAS::setDebug()). + */ + public static function proxy($server_version, $server_hostname, + $server_port, $server_uri, $changeSessionID = true, \SessionHandlerInterface $sessionHandler = null + ) { + phpCAS :: traceBegin(); + if (is_object(self::$_PHPCAS_CLIENT)) { + phpCAS :: error(self::$_PHPCAS_INIT_CALL['method'] . '() has already been called (at ' . self::$_PHPCAS_INIT_CALL['file'] . ':' . self::$_PHPCAS_INIT_CALL['line'] . ')'); + } + + // store where the initialzer is called from + $dbg = debug_backtrace(); + self::$_PHPCAS_INIT_CALL = array ( + 'done' => true, + 'file' => $dbg[0]['file'], + 'line' => $dbg[0]['line'], + 'method' => __CLASS__ . '::' . __FUNCTION__ + ); + + // initialize the object $_PHPCAS_CLIENT + try { + self::$_PHPCAS_CLIENT = new CAS_Client( + $server_version, true, $server_hostname, $server_port, $server_uri, + $changeSessionID, $sessionHandler + ); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + phpCAS :: traceEnd(); + } + + /** + * Answer whether or not the client or proxy has been initialized + * + * @return bool + */ + public static function isInitialized () + { + return (is_object(self::$_PHPCAS_CLIENT)); + } + + /** @} */ + // ######################################################################## + // DEBUGGING + // ######################################################################## + + /** + * @addtogroup publicDebug + * @{ + */ + + /** + * Set/unset PSR-3 logger + * + * @param LoggerInterface $logger the PSR-3 logger used for logging, or + * null to stop logging. + * + * @return void + */ + public static function setLogger($logger = null) + { + if (empty(self::$_PHPCAS_DEBUG['unique_id'])) { + self::$_PHPCAS_DEBUG['unique_id'] = substr(strtoupper(md5(uniqid(''))), 0, 4); + } + self::$_PHPCAS_DEBUG['logger'] = $logger; + self::$_PHPCAS_DEBUG['indent'] = 0; + phpCAS :: trace('START ('.date("Y-m-d H:i:s").') phpCAS-' . PHPCAS_VERSION . ' ******************'); + } + + /** + * Set/unset debug mode + * + * @param string $filename the name of the file used for logging, or false + * to stop debugging. + * + * @return void + * + * @deprecated + */ + public static function setDebug($filename = '') + { + trigger_error('phpCAS::setDebug() is deprecated in favor of phpCAS::setLogger().', E_USER_DEPRECATED); + + if ($filename != false && gettype($filename) != 'string') { + phpCAS :: error('type mismatched for parameter $dbg (should be false or the name of the log file)'); + } + if ($filename === false) { + self::$_PHPCAS_DEBUG['filename'] = false; + + } else { + if (empty ($filename)) { + if (preg_match('/^Win.*/', getenv('OS'))) { + if (isset ($_ENV['TMP'])) { + $debugDir = $_ENV['TMP'] . '/'; + } else { + $debugDir = ''; + } + } else { + $debugDir = DEFAULT_DEBUG_DIR; + } + $filename = $debugDir . 'phpCAS.log'; + } + + if (empty (self::$_PHPCAS_DEBUG['unique_id'])) { + self::$_PHPCAS_DEBUG['unique_id'] = substr(strtoupper(md5(uniqid(''))), 0, 4); + } + + self::$_PHPCAS_DEBUG['filename'] = $filename; + self::$_PHPCAS_DEBUG['indent'] = 0; + + phpCAS :: trace('START ('.date("Y-m-d H:i:s").') phpCAS-' . PHPCAS_VERSION . ' ******************'); + } + } + + /** + * Enable verbose errors messages in the website output + * This is a security relevant since internal status info may leak an may + * help an attacker. Default is therefore false + * + * @param bool $verbose enable verbose output + * + * @return void + */ + public static function setVerbose($verbose) + { + if ($verbose === true) { + self::$_PHPCAS_VERBOSE = true; + } else { + self::$_PHPCAS_VERBOSE = false; + } + } + + + /** + * Show is verbose mode is on + * + * @return bool verbose + */ + public static function getVerbose() + { + return self::$_PHPCAS_VERBOSE; + } + + /** + * Logs a string in debug mode. + * + * @param string $str the string to write + * + * @return void + * @private + */ + public static function log($str) + { + $indent_str = "."; + + + if (isset(self::$_PHPCAS_DEBUG['logger']) || !empty(self::$_PHPCAS_DEBUG['filename'])) { + for ($i = 0; $i < self::$_PHPCAS_DEBUG['indent']; $i++) { + + $indent_str .= '| '; + } + // allow for multiline output with proper identing. Usefull for + // dumping cas answers etc. + $str2 = str_replace("\n", "\n" . self::$_PHPCAS_DEBUG['unique_id'] . ' ' . $indent_str, $str); + $str3 = self::$_PHPCAS_DEBUG['unique_id'] . ' ' . $indent_str . $str2; + if (isset(self::$_PHPCAS_DEBUG['logger'])) { + self::$_PHPCAS_DEBUG['logger']->info($str3); + } + if (!empty(self::$_PHPCAS_DEBUG['filename'])) { + // Check if file exists and modifiy file permissions to be only + // readable by the webserver + if (!file_exists(self::$_PHPCAS_DEBUG['filename'])) { + touch(self::$_PHPCAS_DEBUG['filename']); + // Chmod will fail on windows + @chmod(self::$_PHPCAS_DEBUG['filename'], 0600); + } + error_log($str3 . "\n", 3, self::$_PHPCAS_DEBUG['filename']); + } + } + + } + + /** + * This method is used by interface methods to print an error and where the + * function was originally called from. + * + * @param string $msg the message to print + * + * @return void + * @private + */ + public static function error($msg) + { + phpCAS :: traceBegin(); + $dbg = debug_backtrace(); + $function = '?'; + $file = '?'; + $line = '?'; + if (is_array($dbg)) { + for ($i = 1; $i < sizeof($dbg); $i++) { + if (is_array($dbg[$i]) && isset($dbg[$i]['class']) ) { + if ($dbg[$i]['class'] == __CLASS__) { + $function = $dbg[$i]['function']; + $file = $dbg[$i]['file']; + $line = $dbg[$i]['line']; + } + } + } + } + if (self::$_PHPCAS_VERBOSE) { + echo "
    \nphpCAS error: " . __CLASS__ . "::" . $function . '(): ' . htmlentities($msg) . " in " . $file . " on line " . $line . "
    \n"; + } + phpCAS :: trace($msg . ' in ' . $file . 'on line ' . $line ); + phpCAS :: traceEnd(); + + throw new CAS_GracefullTerminationException(__CLASS__ . "::" . $function . '(): ' . $msg); + } + + /** + * This method is used to log something in debug mode. + * + * @param string $str string to log + * + * @return void + */ + public static function trace($str) + { + $dbg = debug_backtrace(); + phpCAS :: log($str . ' [' . basename($dbg[0]['file']) . ':' . $dbg[0]['line'] . ']'); + } + + /** + * This method is used to indicate the start of the execution of a function + * in debug mode. + * + * @return void + */ + public static function traceBegin() + { + $dbg = debug_backtrace(); + $str = '=> '; + if (!empty ($dbg[1]['class'])) { + $str .= $dbg[1]['class'] . '::'; + } + $str .= $dbg[1]['function'] . '('; + if (is_array($dbg[1]['args'])) { + foreach ($dbg[1]['args'] as $index => $arg) { + if ($index != 0) { + $str .= ', '; + } + if (is_object($arg)) { + $str .= get_class($arg); + } else { + $str .= str_replace(array("\r\n", "\n", "\r"), "", var_export($arg, true)); + } + } + } + if (isset($dbg[1]['file'])) { + $file = basename($dbg[1]['file']); + } else { + $file = 'unknown_file'; + } + if (isset($dbg[1]['line'])) { + $line = $dbg[1]['line']; + } else { + $line = 'unknown_line'; + } + $str .= ') [' . $file . ':' . $line . ']'; + phpCAS :: log($str); + if (!isset(self::$_PHPCAS_DEBUG['indent'])) { + self::$_PHPCAS_DEBUG['indent'] = 0; + } else { + self::$_PHPCAS_DEBUG['indent']++; + } + } + + /** + * This method is used to indicate the end of the execution of a function in + * debug mode. + * + * @param mixed $res the result of the function + * + * @return void + */ + public static function traceEnd($res = '') + { + if (empty(self::$_PHPCAS_DEBUG['indent'])) { + self::$_PHPCAS_DEBUG['indent'] = 0; + } else { + self::$_PHPCAS_DEBUG['indent']--; + } + $str = ''; + if (is_object($res)) { + $str .= '<= ' . get_class($res); + } else { + $str .= '<= ' . str_replace(array("\r\n", "\n", "\r"), "", var_export($res, true)); + } + + phpCAS :: log($str); + } + + /** + * This method is used to indicate the end of the execution of the program + * + * @return void + */ + public static function traceExit() + { + phpCAS :: log('exit()'); + while (self::$_PHPCAS_DEBUG['indent'] > 0) { + phpCAS :: log('-'); + self::$_PHPCAS_DEBUG['indent']--; + } + } + + /** @} */ + // ######################################################################## + // INTERNATIONALIZATION + // ######################################################################## + /** + * @addtogroup publicLang + * @{ + */ + + /** + * This method is used to set the language used by phpCAS. + * + * @param string $lang string representing the language. + * + * @return void + * + * @sa PHPCAS_LANG_FRENCH, PHPCAS_LANG_ENGLISH + * @note Can be called only once. + */ + public static function setLang($lang) + { + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setLang($lang); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** @} */ + // ######################################################################## + // VERSION + // ######################################################################## + /** + * @addtogroup public + * @{ + */ + + /** + * This method returns the phpCAS version. + * + * @return string the phpCAS version. + */ + public static function getVersion() + { + return PHPCAS_VERSION; + } + + /** + * This method returns supported protocols. + * + * @return array an array of all supported protocols. Use internal protocol name as array key. + */ + public static function getSupportedProtocols() + { + $supportedProtocols = array(); + $supportedProtocols[CAS_VERSION_1_0] = 'CAS 1.0'; + $supportedProtocols[CAS_VERSION_2_0] = 'CAS 2.0'; + $supportedProtocols[CAS_VERSION_3_0] = 'CAS 3.0'; + $supportedProtocols[SAML_VERSION_1_1] = 'SAML 1.1'; + + return $supportedProtocols; + } + + /** @} */ + // ######################################################################## + // HTML OUTPUT + // ######################################################################## + /** + * @addtogroup publicOutput + * @{ + */ + + /** + * This method sets the HTML header used for all outputs. + * + * @param string $header the HTML header. + * + * @return void + */ + public static function setHTMLHeader($header) + { + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setHTMLHeader($header); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * This method sets the HTML footer used for all outputs. + * + * @param string $footer the HTML footer. + * + * @return void + */ + public static function setHTMLFooter($footer) + { + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setHTMLFooter($footer); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** @} */ + // ######################################################################## + // PGT STORAGE + // ######################################################################## + /** + * @addtogroup publicPGTStorage + * @{ + */ + + /** + * This method can be used to set a custom PGT storage object. + * + * @param CAS_PGTStorage_AbstractStorage $storage a PGT storage object that inherits from the + * CAS_PGTStorage_AbstractStorage class + * + * @return void + */ + public static function setPGTStorage($storage) + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setPGTStorage($storage); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + phpCAS :: traceEnd(); + } + + /** + * This method is used to tell phpCAS to store the response of the + * CAS server to PGT requests in a database. + * + * @param string $dsn_or_pdo a dsn string to use for creating a PDO + * object or a PDO object + * @param string $username the username to use when connecting to the + * database + * @param string $password the password to use when connecting to the + * database + * @param string $table the table to use for storing and retrieving + * PGT's + * @param string $driver_options any driver options to use when connecting + * to the database + * + * @return void + */ + public static function setPGTStorageDb($dsn_or_pdo, $username='', + $password='', $table='', $driver_options=null + ) { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setPGTStorageDb($dsn_or_pdo, $username, $password, $table, $driver_options); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + phpCAS :: traceEnd(); + } + + /** + * This method is used to tell phpCAS to store the response of the + * CAS server to PGT requests onto the filesystem. + * + * @param string $path the path where the PGT's should be stored + * + * @return void + */ + public static function setPGTStorageFile($path = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setPGTStorageFile($path); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + phpCAS :: traceEnd(); + } + /** @} */ + // ######################################################################## + // ACCESS TO EXTERNAL SERVICES + // ######################################################################## + /** + * @addtogroup publicServices + * @{ + */ + + /** + * Answer a proxy-authenticated service handler. + * + * @param string $type The service type. One of + * PHPCAS_PROXIED_SERVICE_HTTP_GET; PHPCAS_PROXIED_SERVICE_HTTP_POST; + * PHPCAS_PROXIED_SERVICE_IMAP + * + * @return CAS_ProxiedService + * @throws InvalidArgumentException If the service type is unknown. + */ + public static function getProxiedService ($type) + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + $res = self::$_PHPCAS_CLIENT->getProxiedService($type); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + return $res; + } + + /** + * Initialize a proxied-service handler with the proxy-ticket it should use. + * + * @param CAS_ProxiedService $proxiedService Proxied Service Handler + * + * @return void + * @throws CAS_ProxyTicketException If there is a proxy-ticket failure. + * The code of the Exception will be one of: + * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_FAILURE + */ + public static function initializeProxiedService (CAS_ProxiedService $proxiedService) + { + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->initializeProxiedService($proxiedService); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * This method is used to access an HTTP[S] service. + * + * @param string $url the service to access. + * @param int &$err_code an error code Possible values are + * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE, + * PHPCAS_SERVICE_NOT_AVAILABLE. + * @param string &$output the output of the service (also used to give an + * error message on failure). + * + * @return bool true on success, false otherwise (in this later case, + * $err_code gives the reason why it failed and $output contains an error + * message). + */ + public static function serviceWeb($url, & $err_code, & $output) + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + $res = self::$_PHPCAS_CLIENT->serviceWeb($url, $err_code, $output); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd($res); + return $res; + } + + /** + * This method is used to access an IMAP/POP3/NNTP service. + * + * @param string $url a string giving the URL of the service, + * including the mailing box for IMAP URLs, as accepted by imap_open(). + * @param string $service a string giving for CAS retrieve Proxy ticket + * @param string $flags options given to imap_open(). + * @param int &$err_code an error code Possible values are + * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE, + * PHPCAS_SERVICE_NOT_AVAILABLE. + * @param string &$err_msg an error message on failure + * @param string &$pt the Proxy Ticket (PT) retrieved from the CAS + * server to access the URL on success, false on error). + * + * @return object|false IMAP stream on success, false otherwise (in this later + * case, $err_code gives the reason why it failed and $err_msg contains an + * error message). + */ + public static function serviceMail($url, $service, $flags, & $err_code, & $err_msg, & $pt) + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + $res = self::$_PHPCAS_CLIENT->serviceMail($url, $service, $flags, $err_code, $err_msg, $pt); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd($res); + return $res; + } + + /** @} */ + // ######################################################################## + // AUTHENTICATION + // ######################################################################## + /** + * @addtogroup publicAuth + * @{ + */ + + /** + * Set the times authentication will be cached before really accessing the + * CAS server in gateway mode: + * - -1: check only once, and then never again (until you pree login) + * - 0: always check + * - n: check every "n" time + * + * @param int $n an integer. + * + * @return void + */ + public static function setCacheTimesForAuthRecheck($n) + { + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setCacheTimesForAuthRecheck($n); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + + /** + * Set a callback function to be run when receiving CAS attributes + * + * The callback function will be passed an $success_elements + * payload of the response (\DOMElement) as its first parameter. + * + * @param string $function Callback function + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public static function setCasAttributeParserCallback($function, array $additionalArgs = array()) + { + phpCAS::_validateClientExists(); + + self::$_PHPCAS_CLIENT->setCasAttributeParserCallback($function, $additionalArgs); + } + + /** + * Set a callback function to be run when a user authenticates. + * + * The callback function will be passed a $logoutTicket as its first + * parameter, followed by any $additionalArgs you pass. The $logoutTicket + * parameter is an opaque string that can be used to map the session-id to + * logout request in order to support single-signout in applications that + * manage their own sessions (rather than letting phpCAS start the session). + * + * phpCAS::forceAuthentication() will always exit and forward client unless + * they are already authenticated. To perform an action at the moment the user + * logs in (such as registering an account, performing logging, etc), register + * a callback function here. + * + * @param callable $function Callback function + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public static function setPostAuthenticateCallback ($function, array $additionalArgs = array()) + { + phpCAS::_validateClientExists(); + + self::$_PHPCAS_CLIENT->setPostAuthenticateCallback($function, $additionalArgs); + } + + /** + * Set a callback function to be run when a single-signout request is + * received. The callback function will be passed a $logoutTicket as its + * first parameter, followed by any $additionalArgs you pass. The + * $logoutTicket parameter is an opaque string that can be used to map a + * session-id to the logout request in order to support single-signout in + * applications that manage their own sessions (rather than letting phpCAS + * start and destroy the session). + * + * @param callable $function Callback function + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public static function setSingleSignoutCallback ($function, array $additionalArgs = array()) + { + phpCAS::_validateClientExists(); + + self::$_PHPCAS_CLIENT->setSingleSignoutCallback($function, $additionalArgs); + } + + /** + * This method is called to check if the user is already authenticated + * locally or has a global cas session. A already existing cas session is + * determined by a cas gateway call.(cas login call without any interactive + * prompt) + * + * @return bool true when the user is authenticated, false when a previous + * gateway login failed or the function will not return if the user is + * redirected to the cas server for a gateway login attempt + */ + public static function checkAuthentication() + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + $auth = self::$_PHPCAS_CLIENT->checkAuthentication(); + + // store where the authentication has been checked and the result + self::$_PHPCAS_CLIENT->markAuthenticationCall($auth); + + phpCAS :: traceEnd($auth); + return $auth; + } + + /** + * This method is called to force authentication if the user was not already + * authenticated. If the user is not authenticated, halt by redirecting to + * the CAS server. + * + * @return bool Authentication + */ + public static function forceAuthentication() + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + $auth = self::$_PHPCAS_CLIENT->forceAuthentication(); + + // store where the authentication has been checked and the result + self::$_PHPCAS_CLIENT->markAuthenticationCall($auth); + + /* if (!$auth) { + phpCAS :: trace('user is not authenticated, redirecting to the CAS server'); + self::$_PHPCAS_CLIENT->forceAuthentication(); + } else { + phpCAS :: trace('no need to authenticate (user `' . phpCAS :: getUser() . '\' is already authenticated)'); + }*/ + + phpCAS :: traceEnd(); + return $auth; + } + + /** + * This method is called to renew the authentication. + * + * @return void + **/ + public static function renewAuthentication() + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + $auth = self::$_PHPCAS_CLIENT->renewAuthentication(); + + // store where the authentication has been checked and the result + self::$_PHPCAS_CLIENT->markAuthenticationCall($auth); + + //self::$_PHPCAS_CLIENT->renewAuthentication(); + phpCAS :: traceEnd(); + } + + /** + * This method is called to check if the user is authenticated (previously or by + * tickets given in the URL). + * + * @return bool true when the user is authenticated. + */ + public static function isAuthenticated() + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + // call the isAuthenticated method of the $_PHPCAS_CLIENT object + $auth = self::$_PHPCAS_CLIENT->isAuthenticated(); + + // store where the authentication has been checked and the result + self::$_PHPCAS_CLIENT->markAuthenticationCall($auth); + + phpCAS :: traceEnd($auth); + return $auth; + } + + /** + * Checks whether authenticated based on $_SESSION. Useful to avoid + * server calls. + * + * @return bool true if authenticated, false otherwise. + * @since 0.4.22 by Brendan Arnold + */ + public static function isSessionAuthenticated() + { + phpCAS::_validateClientExists(); + + return (self::$_PHPCAS_CLIENT->isSessionAuthenticated()); + } + + /** + * This method returns the CAS user's login name. + * + * @return string the login name of the authenticated user + * @warning should only be called after phpCAS::forceAuthentication() + * or phpCAS::checkAuthentication(). + * */ + public static function getUser() + { + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->getUser(); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * Answer attributes about the authenticated user. + * + * @warning should only be called after phpCAS::forceAuthentication() + * or phpCAS::checkAuthentication(). + * + * @return array + */ + public static function getAttributes() + { + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->getAttributes(); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * Answer true if there are attributes for the authenticated user. + * + * @warning should only be called after phpCAS::forceAuthentication() + * or phpCAS::checkAuthentication(). + * + * @return bool + */ + public static function hasAttributes() + { + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->hasAttributes(); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * Answer true if an attribute exists for the authenticated user. + * + * @param string $key attribute name + * + * @return bool + * @warning should only be called after phpCAS::forceAuthentication() + * or phpCAS::checkAuthentication(). + */ + public static function hasAttribute($key) + { + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->hasAttribute($key); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * Answer an attribute for the authenticated user. + * + * @param string $key attribute name + * + * @return mixed string for a single value or an array if multiple values exist. + * @warning should only be called after phpCAS::forceAuthentication() + * or phpCAS::checkAuthentication(). + */ + public static function getAttribute($key) + { + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->getAttribute($key); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * Handle logout requests. + * + * @param bool $check_client additional safety check + * @param array $allowed_clients array of allowed clients + * + * @return void + */ + public static function handleLogoutRequests($check_client = true, $allowed_clients = array()) + { + phpCAS::_validateClientExists(); + + return (self::$_PHPCAS_CLIENT->handleLogoutRequests($check_client, $allowed_clients)); + } + + /** + * This method returns the URL to be used to login. + * + * @return string the login URL + */ + public static function getServerLoginURL() + { + phpCAS::_validateClientExists(); + + return self::$_PHPCAS_CLIENT->getServerLoginURL(); + } + + /** + * Set the login URL of the CAS server. + * + * @param string $url the login URL + * + * @return void + * @since 0.4.21 by Wyman Chan + */ + public static function setServerLoginURL($url = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerLoginURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Set the serviceValidate URL of the CAS server. + * Used for all CAS versions of URL validations. + * Examples: + * CAS 1.0 http://www.exemple.com/validate + * CAS 2.0 http://www.exemple.com/validateURL + * CAS 3.0 http://www.exemple.com/p3/serviceValidate + * + * @param string $url the serviceValidate URL + * + * @return void + */ + public static function setServerServiceValidateURL($url = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerServiceValidateURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Set the proxyValidate URL of the CAS server. + * Used for all CAS versions of proxy URL validations + * Examples: + * CAS 1.0 http://www.exemple.com/ + * CAS 2.0 http://www.exemple.com/proxyValidate + * CAS 3.0 http://www.exemple.com/p3/proxyValidate + * + * @param string $url the proxyValidate URL + * + * @return void + */ + public static function setServerProxyValidateURL($url = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerProxyValidateURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Set the samlValidate URL of the CAS server. + * + * @param string $url the samlValidate URL + * + * @return void + */ + public static function setServerSamlValidateURL($url = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerSamlValidateURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * This method returns the URL to be used to logout. + * + * @return string the URL to use to log out + */ + public static function getServerLogoutURL() + { + phpCAS::_validateClientExists(); + + return self::$_PHPCAS_CLIENT->getServerLogoutURL(); + } + + /** + * Set the logout URL of the CAS server. + * + * @param string $url the logout URL + * + * @return void + * @since 0.4.21 by Wyman Chan + */ + public static function setServerLogoutURL($url = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerLogoutURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * This method is used to logout from CAS. + * + * @param string $params an array that contains the optional url and + * service parameters that will be passed to the CAS server + * + * @return void + */ + public static function logout($params = "") + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + $parsedParams = array (); + if ($params != "") { + if (is_string($params)) { + phpCAS :: error('method `phpCAS::logout($url)\' is now deprecated, use `phpCAS::logoutWithUrl($url)\' instead'); + } + if (!is_array($params)) { + phpCAS :: error('type mismatched for parameter $params (should be `array\')'); + } + foreach ($params as $key => $value) { + if ($key != "service" && $key != "url") { + phpCAS :: error('only `url\' and `service\' parameters are allowed for method `phpCAS::logout($params)\''); + } + $parsedParams[$key] = $value; + } + } + self::$_PHPCAS_CLIENT->logout($parsedParams); + // never reached + phpCAS :: traceEnd(); + } + + /** + * This method is used to logout from CAS. Halts by redirecting to the CAS + * server. + * + * @param string $service a URL that will be transmitted to the CAS server + * + * @return void + */ + public static function logoutWithRedirectService($service) + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + if (!is_string($service)) { + phpCAS :: error('type mismatched for parameter $service (should be `string\')'); + } + self::$_PHPCAS_CLIENT->logout(array ( "service" => $service )); + // never reached + phpCAS :: traceEnd(); + } + + /** + * This method is used to logout from CAS. Halts by redirecting to the CAS + * server. + * + * @param string $url a URL that will be transmitted to the CAS server + * + * @return void + * @deprecated The url parameter has been removed from the CAS server as of + * version 3.3.5.1 + */ + public static function logoutWithUrl($url) + { + trigger_error('Function deprecated for cas servers >= 3.3.5.1', E_USER_DEPRECATED); + phpCAS :: traceBegin(); + if (!is_object(self::$_PHPCAS_CLIENT)) { + phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()'); + } + if (!is_string($url)) { + phpCAS :: error('type mismatched for parameter $url (should be `string\')'); + } + self::$_PHPCAS_CLIENT->logout(array ( "url" => $url )); + // never reached + phpCAS :: traceEnd(); + } + + /** + * This method is used to logout from CAS. Halts by redirecting to the CAS + * server. + * + * @param string $service a URL that will be transmitted to the CAS server + * @param string $url a URL that will be transmitted to the CAS server + * + * @return void + * + * @deprecated The url parameter has been removed from the CAS server as of + * version 3.3.5.1 + */ + public static function logoutWithRedirectServiceAndUrl($service, $url) + { + trigger_error('Function deprecated for cas servers >= 3.3.5.1', E_USER_DEPRECATED); + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + if (!is_string($service)) { + phpCAS :: error('type mismatched for parameter $service (should be `string\')'); + } + if (!is_string($url)) { + phpCAS :: error('type mismatched for parameter $url (should be `string\')'); + } + self::$_PHPCAS_CLIENT->logout( + array ( + "service" => $service, + "url" => $url + ) + ); + // never reached + phpCAS :: traceEnd(); + } + + /** + * Set the fixed URL that will be used by the CAS server to transmit the + * PGT. When this method is not called, a phpCAS script uses its own URL + * for the callback. + * + * @param string $url the URL + * + * @return void + */ + public static function setFixedCallbackURL($url = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setCallbackURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Set the fixed URL that will be set as the CAS service parameter. When this + * method is not called, a phpCAS script uses its own URL. + * + * @param string $url the URL + * + * @return void + */ + public static function setFixedServiceURL($url) + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Get the URL that is set as the CAS service parameter. + * + * @return string Service Url + */ + public static function getServiceURL() + { + phpCAS::_validateProxyExists(); + return (self::$_PHPCAS_CLIENT->getURL()); + } + + /** + * Retrieve a Proxy Ticket from the CAS server. + * + * @param string $target_service Url string of service to proxy + * @param int &$err_code error code + * @param string &$err_msg error message + * + * @return string Proxy Ticket + */ + public static function retrievePT($target_service, & $err_code, & $err_msg) + { + phpCAS::_validateProxyExists(); + + try { + return (self::$_PHPCAS_CLIENT->retrievePT($target_service, $err_code, $err_msg)); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * Set the certificate of the CAS server CA and if the CN should be properly + * verified. + * + * @param string $cert CA certificate file name + * @param bool $validate_cn Validate CN in certificate (default true) + * + * @return void + */ + public static function setCasServerCACert($cert, $validate_cn = true) + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setCasServerCACert($cert, $validate_cn); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Set no SSL validation for the CAS server. + * + * @return void + */ + public static function setNoCasServerValidation() + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + phpCAS :: trace('You have configured no validation of the legitimacy of the cas server. This is not recommended for production use.'); + self::$_PHPCAS_CLIENT->setNoCasServerValidation(); + phpCAS :: traceEnd(); + } + + + /** + * Disable the removal of a CAS-Ticket from the URL when authenticating + * DISABLING POSES A SECURITY RISK: + * We normally remove the ticket by an additional redirect as a security + * precaution to prevent a ticket in the HTTP_REFERRER or be carried over in + * the URL parameter + * + * @return void + */ + public static function setNoClearTicketsFromUrl() + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + self::$_PHPCAS_CLIENT->setNoClearTicketsFromUrl(); + phpCAS :: traceEnd(); + } + + /** @} */ + + /** + * Change CURL options. + * CURL is used to connect through HTTPS to CAS server + * + * @param string $key the option key + * @param string $value the value to set + * + * @return void + */ + public static function setExtraCurlOption($key, $value) + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + self::$_PHPCAS_CLIENT->setExtraCurlOption($key, $value); + phpCAS :: traceEnd(); + } + + /** + * Set a salt/seed for the session-id hash to make it harder to guess. + * + * When $changeSessionID = true phpCAS will create a session-id that is derived + * from the service ticket. Doing so allows phpCAS to look-up and destroy the + * proper session on single-log-out requests. While the service tickets + * provided by the CAS server may include enough data to generate a strong + * hash, clients may provide an additional salt to ensure that session ids + * are not guessable if the session tickets do not have enough entropy. + * + * @param string $salt The salt to combine with the session ticket. + * + * @return void + */ + public static function setSessionIdSalt($salt) { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + self::$_PHPCAS_CLIENT->setSessionIdSalt($salt); + phpCAS :: traceEnd(); + } + + /** + * If you want your service to be proxied you have to enable it (default + * disabled) and define an accepable list of proxies that are allowed to + * proxy your service. + * + * Add each allowed proxy definition object. For the normal CAS_ProxyChain + * class, the constructor takes an array of proxies to match. The list is in + * reverse just as seen from the service. Proxies have to be defined in reverse + * from the service to the user. If a user hits service A and gets proxied via + * B to service C the list of acceptable on C would be array(B,A). The definition + * of an individual proxy can be either a string or a regexp (preg_match is used) + * that will be matched against the proxy list supplied by the cas server + * when validating the proxy tickets. The strings are compared starting from + * the beginning and must fully match with the proxies in the list. + * Example: + * phpCAS::allowProxyChain(new CAS_ProxyChain(array( + * 'https://app.example.com/' + * ))); + * phpCAS::allowProxyChain(new CAS_ProxyChain(array( + * '/^https:\/\/app[0-9]\.example\.com\/rest\//', + * 'http://client.example.com/' + * ))); + * + * For quick testing or in certain production screnarios you might want to + * allow allow any other valid service to proxy your service. To do so, add + * the "Any" chain: + * phpCAS::allowProxyChain(new CAS_ProxyChain_Any); + * THIS SETTING IS HOWEVER NOT RECOMMENDED FOR PRODUCTION AND HAS SECURITY + * IMPLICATIONS: YOU ARE ALLOWING ANY SERVICE TO ACT ON BEHALF OF A USER + * ON THIS SERVICE. + * + * @param CAS_ProxyChain_Interface $proxy_chain A proxy-chain that will be + * matched against the proxies requesting access + * + * @return void + */ + public static function allowProxyChain(CAS_ProxyChain_Interface $proxy_chain) + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + if (self::$_PHPCAS_CLIENT->getServerVersion() !== CAS_VERSION_2_0 + && self::$_PHPCAS_CLIENT->getServerVersion() !== CAS_VERSION_3_0 + ) { + phpCAS :: error('this method can only be used with the cas 2.0/3.0 protocols'); + } + self::$_PHPCAS_CLIENT->getAllowedProxyChains()->allowProxyChain($proxy_chain); + phpCAS :: traceEnd(); + } + + /** + * Answer an array of proxies that are sitting in front of this application. + * This method will only return a non-empty array if we have received and + * validated a Proxy Ticket. + * + * @return array + * @access public + * @since 6/25/09 + */ + public static function getProxies () + { + phpCAS::_validateProxyExists(); + + return(self::$_PHPCAS_CLIENT->getProxies()); + } + + // ######################################################################## + // PGTIOU/PGTID and logoutRequest rebroadcasting + // ######################################################################## + + /** + * Add a pgtIou/pgtId and logoutRequest rebroadcast node. + * + * @param string $rebroadcastNodeUrl The rebroadcast node URL. Can be + * hostname or IP. + * + * @return void + */ + public static function addRebroadcastNode($rebroadcastNodeUrl) + { + phpCAS::traceBegin(); + phpCAS::log('rebroadcastNodeUrl:'.$rebroadcastNodeUrl); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->addRebroadcastNode($rebroadcastNodeUrl); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS::traceEnd(); + } + + /** + * This method is used to add header parameters when rebroadcasting + * pgtIou/pgtId or logoutRequest. + * + * @param String $header Header to send when rebroadcasting. + * + * @return void + */ + public static function addRebroadcastHeader($header) + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->addRebroadcastHeader($header); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Checks if a client already exists + * + * @throws CAS_OutOfSequenceBeforeClientException + * + * @return void + */ + private static function _validateClientExists() + { + if (!is_object(self::$_PHPCAS_CLIENT)) { + throw new CAS_OutOfSequenceBeforeClientException(); + } + } + + /** + * Checks of a proxy client aready exists + * + * @throws CAS_OutOfSequenceBeforeProxyException + * + * @return void + */ + private static function _validateProxyExists() + { + if (!is_object(self::$_PHPCAS_CLIENT)) { + throw new CAS_OutOfSequenceBeforeProxyException(); + } + } + + /** + * @return CAS_Client + */ + public static function getCasClient() + { + return self::$_PHPCAS_CLIENT; + } + + /** + * For testing purposes, use this method to set the client to a test double + * + * @return void + */ + public static function setCasClient(\CAS_Client $client) + { + self::$_PHPCAS_CLIENT = $client; + } +} +// ######################################################################## +// DOCUMENTATION +// ######################################################################## + +// ######################################################################## +// MAIN PAGE + +/** + * @mainpage + * + * The following pages only show the source documentation. + * + */ + +// ######################################################################## +// MODULES DEFINITION + +/** @defgroup public User interface */ + +/** @defgroup publicInit Initialization + * @ingroup public */ + +/** @defgroup publicAuth Authentication + * @ingroup public */ + +/** @defgroup publicServices Access to external services + * @ingroup public */ + +/** @defgroup publicConfig Configuration + * @ingroup public */ + +/** @defgroup publicLang Internationalization + * @ingroup publicConfig */ + +/** @defgroup publicOutput HTML output + * @ingroup publicConfig */ + +/** @defgroup publicPGTStorage PGT storage + * @ingroup publicConfig */ + +/** @defgroup publicDebug Debugging + * @ingroup public */ + +/** @defgroup internal Implementation */ + +/** @defgroup internalAuthentication Authentication + * @ingroup internal */ + +/** @defgroup internalBasic CAS Basic client features (CAS 1.0, Service Tickets) + * @ingroup internal */ + +/** @defgroup internalProxy CAS Proxy features (CAS 2.0, Proxy Granting Tickets) + * @ingroup internal */ + +/** @defgroup internalSAML CAS SAML features (SAML 1.1) + * @ingroup internal */ + +/** @defgroup internalPGTStorage PGT storage + * @ingroup internalProxy */ + +/** @defgroup internalPGTStorageDb PGT storage in a database + * @ingroup internalPGTStorage */ + +/** @defgroup internalPGTStorageFile PGT storage on the filesystem + * @ingroup internalPGTStorage */ + +/** @defgroup internalCallback Callback from the CAS server + * @ingroup internalProxy */ + +/** @defgroup internalProxyServices Proxy other services + * @ingroup internalProxy */ + +/** @defgroup internalService CAS client features (CAS 2.0, Proxied service) + * @ingroup internal */ + +/** @defgroup internalConfig Configuration + * @ingroup internal */ + +/** @defgroup internalBehave Internal behaviour of phpCAS + * @ingroup internalConfig */ + +/** @defgroup internalOutput HTML output + * @ingroup internalConfig */ + +/** @defgroup internalLang Internationalization + * @ingroup internalConfig + * + * To add a new language: + * - 1. define a new constant PHPCAS_LANG_XXXXXX in CAS/CAS.php + * - 2. copy any file from CAS/languages to CAS/languages/XXXXXX.php + * - 3. Make the translations + */ + +/** @defgroup internalDebug Debugging + * @ingroup internal */ + +/** @defgroup internalMisc Miscellaneous + * @ingroup internal */ + +// ######################################################################## +// EXAMPLES + +/** + * @example example_simple.php + */ +/** + * @example example_service.php + */ +/** + * @example example_service_that_proxies.php + */ +/** + * @example example_service_POST.php + */ +/** + * @example example_proxy_serviceWeb.php + */ +/** + * @example example_proxy_serviceWeb_chaining.php + */ +/** + * @example example_proxy_POST.php + */ +/** + * @example example_proxy_GET.php + */ +/** + * @example example_lang.php + */ +/** + * @example example_html.php + */ +/** + * @example example_pgt_storage_file.php + */ +/** + * @example example_pgt_storage_db.php + */ +/** + * @example example_gateway.php + */ +/** + * @example example_logout.php + */ +/** + * @example example_rebroadcast.php + */ +/** + * @example example_custom_urls.php + */ +/** + * @example example_advanced_saml11.php + */ diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/AuthenticationException.php b/lib/apereo/phpcas/source/CAS/AuthenticationException.php similarity index 94% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/AuthenticationException.php rename to lib/apereo/phpcas/source/CAS/AuthenticationException.php index 3b73685ff9..803c88908b 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/AuthenticationException.php +++ b/lib/apereo/phpcas/source/CAS/AuthenticationException.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/AuthenticationException.php * @category Authentication @@ -72,11 +72,15 @@ implements CAS_Exception phpCAS::traceBegin(); $lang = $client->getLangObj(); $client->printHTMLHeader($lang->getAuthenticationFailed()); - printf( - $lang->getYouWereNotAuthenticated(), - htmlentities($client->getURL()), - isset($_SERVER['SERVER_ADMIN']) ? $_SERVER['SERVER_ADMIN']:'' - ); + + if (phpCAS::getVerbose()) { + printf( + $lang->getYouWereNotAuthenticated(), + htmlentities($client->getURL()), + $_SERVER['SERVER_ADMIN'] ?? '' + ); + } + phpCAS::trace($messages[] = 'CAS URL: '.$cas_url); phpCAS::trace($messages[] = 'Authentication failure: '.$failure); if ( $no_response ) { diff --git a/lib/apereo/phpcas/source/CAS/Autoload.php b/lib/apereo/phpcas/source/CAS/Autoload.php new file mode 100644 index 0000000000..29395d592d --- /dev/null +++ b/lib/apereo/phpcas/source/CAS/Autoload.php @@ -0,0 +1,95 @@ + + * @copyright 2008 Regents of the University of Nebraska + * @license http://www1.unl.edu/wdn/wiki/Software_License BSD License + * @link http://code.google.com/p/simplecas/ + **/ + +/** + * Autoload a class + * + * @param string $class Classname to load + * + * @return bool + */ +function CAS_autoload($class) +{ + // Static to hold the Include Path to CAS + static $include_path; + // Check only for CAS classes + if (substr($class, 0, 4) !== 'CAS_' && substr($class, 0, 7) !== 'PhpCas\\') { + return false; + } + + // Setup the include path if it's not already set from a previous call + if (empty($include_path)) { + $include_path = array(dirname(__DIR__)); + } + + // Declare local variable to store the expected full path to the file + foreach ($include_path as $path) { + $class_path = str_replace('_', DIRECTORY_SEPARATOR, $class); + // PhpCas namespace mapping + if (substr($class_path, 0, 7) === 'PhpCas\\') { + $class_path = 'CAS' . DIRECTORY_SEPARATOR . substr($class_path, 7); + } + + $file_path = $path . DIRECTORY_SEPARATOR . $class_path . '.php'; + $fp = @fopen($file_path, 'r', true); + if ($fp) { + fclose($fp); + include $file_path; + if (!class_exists($class, false) && !interface_exists($class, false)) { + die( + new Exception( + 'Class ' . $class . ' was not present in ' . + $file_path . + ' [CAS_autoload]' + ) + ); + } + return true; + } + } + + $e = new Exception( + 'Class ' . $class . ' could not be loaded from ' . + $file_path . ', file does not exist (Path="' + . implode(':', $include_path) .'") [CAS_autoload]' + ); + $trace = $e->getTrace(); + if (isset($trace[2]) && isset($trace[2]['function']) + && in_array($trace[2]['function'], array('class_exists', 'interface_exists', 'trait_exists')) + ) { + return false; + } + if (isset($trace[1]) && isset($trace[1]['function']) + && in_array($trace[1]['function'], array('class_exists', 'interface_exists', 'trait_exists')) + ) { + return false; + } + die ((string) $e); +} + +// Set up autoload if not already configured by composer. +if (!class_exists('CAS_Client')) +{ + trigger_error('phpCAS autoloader is deprecated. Install phpCAS using composer instead.', E_USER_DEPRECATED); + spl_autoload_register('CAS_autoload'); + if (function_exists('__autoload') + && !in_array('__autoload', spl_autoload_functions()) + ) { + // __autoload() was being used, but now would be ignored, add + // it to the autoload stack + spl_autoload_register('__autoload'); + } +} diff --git a/lib/apereo/phpcas/source/CAS/Client.php b/lib/apereo/phpcas/source/CAS/Client.php new file mode 100644 index 0000000000..1a40f63e4f --- /dev/null +++ b/lib/apereo/phpcas/source/CAS/Client.php @@ -0,0 +1,4377 @@ + + * @author Olivier Berger + * @author Brett Bieber + * @author Joachim Fritschi + * @author Adam Franco + * @author Tobias Schiebeck + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * The CAS_Client class is a client interface that provides CAS authentication + * to PHP applications. + * + * @class CAS_Client + * @category Authentication + * @package PhpCAS + * @author Pascal Aubry + * @author Olivier Berger + * @author Brett Bieber + * @author Joachim Fritschi + * @author Adam Franco + * @author Tobias Schiebeck + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + */ + +class CAS_Client +{ + + // ######################################################################## + // HTML OUTPUT + // ######################################################################## + /** + * @addtogroup internalOutput + * @{ + */ + + /** + * This method filters a string by replacing special tokens by appropriate values + * and prints it. The corresponding tokens are taken into account: + * - __CAS_VERSION__ + * - __PHPCAS_VERSION__ + * - __SERVER_BASE_URL__ + * + * Used by CAS_Client::PrintHTMLHeader() and CAS_Client::printHTMLFooter(). + * + * @param string $str the string to filter and output + * + * @return void + */ + private function _htmlFilterOutput($str) + { + $str = str_replace('__CAS_VERSION__', $this->getServerVersion(), $str); + $str = str_replace('__PHPCAS_VERSION__', phpCAS::getVersion(), $str); + $str = str_replace('__SERVER_BASE_URL__', $this->_getServerBaseURL(), $str); + echo $str; + } + + /** + * A string used to print the header of HTML pages. Written by + * CAS_Client::setHTMLHeader(), read by CAS_Client::printHTMLHeader(). + * + * @hideinitializer + * @see CAS_Client::setHTMLHeader, CAS_Client::printHTMLHeader() + */ + private $_output_header = ''; + + /** + * This method prints the header of the HTML output (after filtering). If + * CAS_Client::setHTMLHeader() was not used, a default header is output. + * + * @param string $title the title of the page + * + * @return void + * @see _htmlFilterOutput() + */ + public function printHTMLHeader($title) + { + if (!phpCAS::getVerbose()) { + return; + } + + $this->_htmlFilterOutput( + str_replace( + '__TITLE__', $title, + (empty($this->_output_header) + ? '__TITLE__

    __TITLE__

    ' + : $this->_output_header) + ) + ); + } + + /** + * A string used to print the footer of HTML pages. Written by + * CAS_Client::setHTMLFooter(), read by printHTMLFooter(). + * + * @hideinitializer + * @see CAS_Client::setHTMLFooter, CAS_Client::printHTMLFooter() + */ + private $_output_footer = ''; + + /** + * This method prints the footer of the HTML output (after filtering). If + * CAS_Client::setHTMLFooter() was not used, a default footer is output. + * + * @return void + * @see _htmlFilterOutput() + */ + public function printHTMLFooter() + { + if (!phpCAS::getVerbose()) { + return; + } + + $lang = $this->getLangObj(); + $message = empty($this->_output_footer) + ? '
    phpCAS __PHPCAS_VERSION__ ' . $lang->getUsingServer() . + ' __SERVER_BASE_URL__ (CAS __CAS_VERSION__)
    ' + : $this->_output_footer; + + $this->_htmlFilterOutput($message); + } + + /** + * This method set the HTML header used for all outputs. + * + * @param string $header the HTML header. + * + * @return void + */ + public function setHTMLHeader($header) + { + // Argument Validation + if (gettype($header) != 'string') + throw new CAS_TypeMismatchException($header, '$header', 'string'); + + $this->_output_header = $header; + } + + /** + * This method set the HTML footer used for all outputs. + * + * @param string $footer the HTML footer. + * + * @return void + */ + public function setHTMLFooter($footer) + { + // Argument Validation + if (gettype($footer) != 'string') + throw new CAS_TypeMismatchException($footer, '$footer', 'string'); + + $this->_output_footer = $footer; + } + + /** + * Simple wrapper for printf function, that respects + * phpCAS verbosity setting. + * + * @param string $format + * @param string|int|float ...$values + * + * @see printf() + */ + private function printf(string $format, ...$values): void + { + if (phpCAS::getVerbose()) { + printf($format, ...$values); + } + } + + /** @} */ + + + // ######################################################################## + // INTERNATIONALIZATION + // ######################################################################## + /** + * @addtogroup internalLang + * @{ + */ + /** + * A string corresponding to the language used by phpCAS. Written by + * CAS_Client::setLang(), read by CAS_Client::getLang(). + + * @note debugging information is always in english (debug purposes only). + */ + private $_lang = PHPCAS_LANG_DEFAULT; + + /** + * This method is used to set the language used by phpCAS. + * + * @param string $lang representing the language. + * + * @return void + */ + public function setLang($lang) + { + // Argument Validation + if (gettype($lang) != 'string') + throw new CAS_TypeMismatchException($lang, '$lang', 'string'); + + phpCAS::traceBegin(); + $obj = new $lang(); + if (!($obj instanceof CAS_Languages_LanguageInterface)) { + throw new CAS_InvalidArgumentException( + '$className must implement the CAS_Languages_LanguageInterface' + ); + } + $this->_lang = $lang; + phpCAS::traceEnd(); + } + /** + * Create the language + * + * @return CAS_Languages_LanguageInterface object implementing the class + */ + public function getLangObj() + { + $classname = $this->_lang; + return new $classname(); + } + + /** @} */ + // ######################################################################## + // CAS SERVER CONFIG + // ######################################################################## + /** + * @addtogroup internalConfig + * @{ + */ + + /** + * a record to store information about the CAS server. + * - $_server['version']: the version of the CAS server + * - $_server['hostname']: the hostname of the CAS server + * - $_server['port']: the port the CAS server is running on + * - $_server['uri']: the base URI the CAS server is responding on + * - $_server['base_url']: the base URL of the CAS server + * - $_server['login_url']: the login URL of the CAS server + * - $_server['service_validate_url']: the service validating URL of the + * CAS server + * - $_server['proxy_url']: the proxy URL of the CAS server + * - $_server['proxy_validate_url']: the proxy validating URL of the CAS server + * - $_server['logout_url']: the logout URL of the CAS server + * + * $_server['version'], $_server['hostname'], $_server['port'] and + * $_server['uri'] are written by CAS_Client::CAS_Client(), read by + * CAS_Client::getServerVersion(), CAS_Client::_getServerHostname(), + * CAS_Client::_getServerPort() and CAS_Client::_getServerURI(). + * + * The other fields are written and read by CAS_Client::_getServerBaseURL(), + * CAS_Client::getServerLoginURL(), CAS_Client::getServerServiceValidateURL(), + * CAS_Client::getServerProxyValidateURL() and CAS_Client::getServerLogoutURL(). + * + * @hideinitializer + */ + private $_server = array( + 'version' => '', + 'hostname' => 'none', + 'port' => -1, + 'uri' => 'none'); + + /** + * This method is used to retrieve the version of the CAS server. + * + * @return string the version of the CAS server. + */ + public function getServerVersion() + { + return $this->_server['version']; + } + + /** + * This method is used to retrieve the hostname of the CAS server. + * + * @return string the hostname of the CAS server. + */ + private function _getServerHostname() + { + return $this->_server['hostname']; + } + + /** + * This method is used to retrieve the port of the CAS server. + * + * @return int the port of the CAS server. + */ + private function _getServerPort() + { + return $this->_server['port']; + } + + /** + * This method is used to retrieve the URI of the CAS server. + * + * @return string a URI. + */ + private function _getServerURI() + { + return $this->_server['uri']; + } + + /** + * This method is used to retrieve the base URL of the CAS server. + * + * @return string a URL. + */ + private function _getServerBaseURL() + { + // the URL is build only when needed + if ( empty($this->_server['base_url']) ) { + $this->_server['base_url'] = 'https://' . $this->_getServerHostname(); + if ($this->_getServerPort()!=443) { + $this->_server['base_url'] .= ':' + .$this->_getServerPort(); + } + $this->_server['base_url'] .= $this->_getServerURI(); + } + return $this->_server['base_url']; + } + + /** + * This method is used to retrieve the login URL of the CAS server. + * + * @param bool $gateway true to check authentication, false to force it + * @param bool $renew true to force the authentication with the CAS server + * + * @return string a URL. + * @note It is recommended that CAS implementations ignore the "gateway" + * parameter if "renew" is set + */ + public function getServerLoginURL($gateway=false,$renew=false) + { + phpCAS::traceBegin(); + // the URL is build only when needed + if ( empty($this->_server['login_url']) ) { + $this->_server['login_url'] = $this->_buildQueryUrl($this->_getServerBaseURL().'login','service='.urlencode($this->getURL())); + } + $url = $this->_server['login_url']; + if ($renew) { + // It is recommended that when the "renew" parameter is set, its + // value be "true" + $url = $this->_buildQueryUrl($url, 'renew=true'); + } elseif ($gateway) { + // It is recommended that when the "gateway" parameter is set, its + // value be "true" + $url = $this->_buildQueryUrl($url, 'gateway=true'); + } + phpCAS::traceEnd($url); + return $url; + } + + /** + * This method sets the login URL of the CAS server. + * + * @param string $url the login URL + * + * @return string login url + */ + public function setServerLoginURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['login_url'] = $url; + } + + + /** + * This method sets the serviceValidate URL of the CAS server. + * + * @param string $url the serviceValidate URL + * + * @return string serviceValidate URL + */ + public function setServerServiceValidateURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['service_validate_url'] = $url; + } + + + /** + * This method sets the proxyValidate URL of the CAS server. + * + * @param string $url the proxyValidate URL + * + * @return string proxyValidate URL + */ + public function setServerProxyValidateURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['proxy_validate_url'] = $url; + } + + + /** + * This method sets the samlValidate URL of the CAS server. + * + * @param string $url the samlValidate URL + * + * @return string samlValidate URL + */ + public function setServerSamlValidateURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['saml_validate_url'] = $url; + } + + + /** + * This method is used to retrieve the service validating URL of the CAS server. + * + * @return string serviceValidate URL. + */ + public function getServerServiceValidateURL() + { + phpCAS::traceBegin(); + // the URL is build only when needed + if ( empty($this->_server['service_validate_url']) ) { + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + $this->_server['service_validate_url'] = $this->_getServerBaseURL() + .'validate'; + break; + case CAS_VERSION_2_0: + $this->_server['service_validate_url'] = $this->_getServerBaseURL() + .'serviceValidate'; + break; + case CAS_VERSION_3_0: + $this->_server['service_validate_url'] = $this->_getServerBaseURL() + .'p3/serviceValidate'; + break; + } + } + $url = $this->_buildQueryUrl( + $this->_server['service_validate_url'], + 'service='.urlencode($this->getURL()) + ); + phpCAS::traceEnd($url); + return $url; + } + /** + * This method is used to retrieve the SAML validating URL of the CAS server. + * + * @return string samlValidate URL. + */ + public function getServerSamlValidateURL() + { + phpCAS::traceBegin(); + // the URL is build only when needed + if ( empty($this->_server['saml_validate_url']) ) { + switch ($this->getServerVersion()) { + case SAML_VERSION_1_1: + $this->_server['saml_validate_url'] = $this->_getServerBaseURL().'samlValidate'; + break; + } + } + + $url = $this->_buildQueryUrl( + $this->_server['saml_validate_url'], + 'TARGET='.urlencode($this->getURL()) + ); + phpCAS::traceEnd($url); + return $url; + } + + /** + * This method is used to retrieve the proxy validating URL of the CAS server. + * + * @return string proxyValidate URL. + */ + public function getServerProxyValidateURL() + { + phpCAS::traceBegin(); + // the URL is build only when needed + if ( empty($this->_server['proxy_validate_url']) ) { + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + $this->_server['proxy_validate_url'] = ''; + break; + case CAS_VERSION_2_0: + $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'proxyValidate'; + break; + case CAS_VERSION_3_0: + $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'p3/proxyValidate'; + break; + } + } + $url = $this->_buildQueryUrl( + $this->_server['proxy_validate_url'], + 'service='.urlencode($this->getURL()) + ); + phpCAS::traceEnd($url); + return $url; + } + + + /** + * This method is used to retrieve the proxy URL of the CAS server. + * + * @return string proxy URL. + */ + public function getServerProxyURL() + { + // the URL is build only when needed + if ( empty($this->_server['proxy_url']) ) { + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + $this->_server['proxy_url'] = ''; + break; + case CAS_VERSION_2_0: + case CAS_VERSION_3_0: + $this->_server['proxy_url'] = $this->_getServerBaseURL().'proxy'; + break; + } + } + return $this->_server['proxy_url']; + } + + /** + * This method is used to retrieve the logout URL of the CAS server. + * + * @return string logout URL. + */ + public function getServerLogoutURL() + { + // the URL is build only when needed + if ( empty($this->_server['logout_url']) ) { + $this->_server['logout_url'] = $this->_getServerBaseURL().'logout'; + } + return $this->_server['logout_url']; + } + + /** + * This method sets the logout URL of the CAS server. + * + * @param string $url the logout URL + * + * @return string logout url + */ + public function setServerLogoutURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['logout_url'] = $url; + } + + /** + * An array to store extra curl options. + */ + private $_curl_options = array(); + + /** + * This method is used to set additional user curl options. + * + * @param string $key name of the curl option + * @param string $value value of the curl option + * + * @return void + */ + public function setExtraCurlOption($key, $value) + { + $this->_curl_options[$key] = $value; + } + + /** @} */ + + // ######################################################################## + // Change the internal behaviour of phpcas + // ######################################################################## + + /** + * @addtogroup internalBehave + * @{ + */ + + /** + * The class to instantiate for making web requests in readUrl(). + * The class specified must implement the CAS_Request_RequestInterface. + * By default CAS_Request_CurlRequest is used, but this may be overridden to + * supply alternate request mechanisms for testing. + */ + private $_requestImplementation = 'CAS_Request_CurlRequest'; + + /** + * Override the default implementation used to make web requests in readUrl(). + * This class must implement the CAS_Request_RequestInterface. + * + * @param string $className name of the RequestImplementation class + * + * @return void + */ + public function setRequestImplementation ($className) + { + $obj = new $className; + if (!($obj instanceof CAS_Request_RequestInterface)) { + throw new CAS_InvalidArgumentException( + '$className must implement the CAS_Request_RequestInterface' + ); + } + $this->_requestImplementation = $className; + } + + /** + * @var boolean $_clearTicketsFromUrl; If true, phpCAS will clear session + * tickets from the URL after a successful authentication. + */ + private $_clearTicketsFromUrl = true; + + /** + * Configure the client to not send redirect headers and call exit() on + * authentication success. The normal redirect is used to remove the service + * ticket from the client's URL, but for running unit tests we need to + * continue without exiting. + * + * Needed for testing authentication + * + * @return void + */ + public function setNoClearTicketsFromUrl () + { + $this->_clearTicketsFromUrl = false; + } + + /** + * @var callback $_attributeParserCallbackFunction; + */ + private $_casAttributeParserCallbackFunction = null; + + /** + * @var array $_attributeParserCallbackArgs; + */ + private $_casAttributeParserCallbackArgs = array(); + + /** + * Set a callback function to be run when parsing CAS attributes + * + * The callback function will be passed a XMLNode as its first parameter, + * followed by any $additionalArgs you pass. + * + * @param string $function callback function to call + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public function setCasAttributeParserCallback($function, array $additionalArgs = array()) + { + $this->_casAttributeParserCallbackFunction = $function; + $this->_casAttributeParserCallbackArgs = $additionalArgs; + } + + /** @var callable $_postAuthenticateCallbackFunction; + */ + private $_postAuthenticateCallbackFunction = null; + + /** + * @var array $_postAuthenticateCallbackArgs; + */ + private $_postAuthenticateCallbackArgs = array(); + + /** + * Set a callback function to be run when a user authenticates. + * + * The callback function will be passed a $logoutTicket as its first parameter, + * followed by any $additionalArgs you pass. The $logoutTicket parameter is an + * opaque string that can be used to map a session-id to the logout request + * in order to support single-signout in applications that manage their own + * sessions (rather than letting phpCAS start the session). + * + * phpCAS::forceAuthentication() will always exit and forward client unless + * they are already authenticated. To perform an action at the moment the user + * logs in (such as registering an account, performing logging, etc), register + * a callback function here. + * + * @param callable $function callback function to call + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public function setPostAuthenticateCallback ($function, array $additionalArgs = array()) + { + $this->_postAuthenticateCallbackFunction = $function; + $this->_postAuthenticateCallbackArgs = $additionalArgs; + } + + /** + * @var callable $_signoutCallbackFunction; + */ + private $_signoutCallbackFunction = null; + + /** + * @var array $_signoutCallbackArgs; + */ + private $_signoutCallbackArgs = array(); + + /** + * Set a callback function to be run when a single-signout request is received. + * + * The callback function will be passed a $logoutTicket as its first parameter, + * followed by any $additionalArgs you pass. The $logoutTicket parameter is an + * opaque string that can be used to map a session-id to the logout request in + * order to support single-signout in applications that manage their own sessions + * (rather than letting phpCAS start and destroy the session). + * + * @param callable $function callback function to call + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public function setSingleSignoutCallback ($function, array $additionalArgs = array()) + { + $this->_signoutCallbackFunction = $function; + $this->_signoutCallbackArgs = $additionalArgs; + } + + // ######################################################################## + // Methods for supplying code-flow feedback to integrators. + // ######################################################################## + + /** + * Ensure that this is actually a proxy object or fail with an exception + * + * @throws CAS_OutOfSequenceBeforeProxyException + * + * @return void + */ + public function ensureIsProxy() + { + if (!$this->isProxy()) { + throw new CAS_OutOfSequenceBeforeProxyException(); + } + } + + /** + * Mark the caller of authentication. This will help client integraters determine + * problems with their code flow if they call a function such as getUser() before + * authentication has occurred. + * + * @param bool $auth True if authentication was successful, false otherwise. + * + * @return null + */ + public function markAuthenticationCall ($auth) + { + // store where the authentication has been checked and the result + $dbg = debug_backtrace(); + $this->_authentication_caller = array ( + 'file' => $dbg[1]['file'], + 'line' => $dbg[1]['line'], + 'method' => $dbg[1]['class'] . '::' . $dbg[1]['function'], + 'result' => (boolean)$auth + ); + } + private $_authentication_caller; + + /** + * Answer true if authentication has been checked. + * + * @return bool + */ + public function wasAuthenticationCalled () + { + return !empty($this->_authentication_caller); + } + + /** + * Ensure that authentication was checked. Terminate with exception if no + * authentication was performed + * + * @throws CAS_OutOfSequenceBeforeAuthenticationCallException + * + * @return void + */ + private function _ensureAuthenticationCalled() + { + if (!$this->wasAuthenticationCalled()) { + throw new CAS_OutOfSequenceBeforeAuthenticationCallException(); + } + } + + /** + * Answer the result of the authentication call. + * + * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false + * and markAuthenticationCall() didn't happen. + * + * @return bool + */ + public function wasAuthenticationCallSuccessful () + { + $this->_ensureAuthenticationCalled(); + return $this->_authentication_caller['result']; + } + + + /** + * Ensure that authentication was checked. Terminate with exception if no + * authentication was performed + * + * @throws CAS_OutOfSequenceException + * + * @return void + */ + public function ensureAuthenticationCallSuccessful() + { + $this->_ensureAuthenticationCalled(); + if (!$this->_authentication_caller['result']) { + throw new CAS_OutOfSequenceException( + 'authentication was checked (by ' + . $this->getAuthenticationCallerMethod() + . '() at ' . $this->getAuthenticationCallerFile() + . ':' . $this->getAuthenticationCallerLine() + . ') but the method returned false' + ); + } + } + + /** + * Answer information about the authentication caller. + * + * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false + * and markAuthenticationCall() didn't happen. + * + * @return string the file that called authentication + */ + public function getAuthenticationCallerFile () + { + $this->_ensureAuthenticationCalled(); + return $this->_authentication_caller['file']; + } + + /** + * Answer information about the authentication caller. + * + * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false + * and markAuthenticationCall() didn't happen. + * + * @return int the line that called authentication + */ + public function getAuthenticationCallerLine () + { + $this->_ensureAuthenticationCalled(); + return $this->_authentication_caller['line']; + } + + /** + * Answer information about the authentication caller. + * + * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false + * and markAuthenticationCall() didn't happen. + * + * @return string the method that called authentication + */ + public function getAuthenticationCallerMethod () + { + $this->_ensureAuthenticationCalled(); + return $this->_authentication_caller['method']; + } + + /** @} */ + + // ######################################################################## + // CONSTRUCTOR + // ######################################################################## + /** + * @addtogroup internalConfig + * @{ + */ + + /** + * CAS_Client constructor. + * + * @param string $server_version the version of the CAS server + * @param bool $proxy true if the CAS client is a CAS proxy + * @param string $server_hostname the hostname of the CAS server + * @param int $server_port the port the CAS server is running on + * @param string $server_uri the URI the CAS server is responding on + * @param bool $changeSessionID Allow phpCAS to change the session_id + * (Single Sign Out/handleLogoutRequests + * is based on that change) + * @param \SessionHandlerInterface $sessionHandler the session handler + * + * @return self a newly created CAS_Client object + */ + public function __construct( + $server_version, + $proxy, + $server_hostname, + $server_port, + $server_uri, + $changeSessionID = true, + \SessionHandlerInterface $sessionHandler = null + ) { + // Argument validation + if (gettype($server_version) != 'string') + throw new CAS_TypeMismatchException($server_version, '$server_version', 'string'); + if (gettype($proxy) != 'boolean') + throw new CAS_TypeMismatchException($proxy, '$proxy', 'boolean'); + if (gettype($server_hostname) != 'string') + throw new CAS_TypeMismatchException($server_hostname, '$server_hostname', 'string'); + if (gettype($server_port) != 'integer') + throw new CAS_TypeMismatchException($server_port, '$server_port', 'integer'); + if (gettype($server_uri) != 'string') + throw new CAS_TypeMismatchException($server_uri, '$server_uri', 'string'); + if (gettype($changeSessionID) != 'boolean') + throw new CAS_TypeMismatchException($changeSessionID, '$changeSessionID', 'boolean'); + + if (empty($sessionHandler)) { + $sessionHandler = new CAS_Session_PhpSession; + } + + phpCAS::traceBegin(); + // true : allow to change the session_id(), false session_id won't be + // changed and logout won't be handled because of that + $this->_setChangeSessionID($changeSessionID); + + $this->setSessionHandler($sessionHandler); + + if (!$this->_isLogoutRequest()) { + if (session_id() === "") { + // skip Session Handling for logout requests and if don't want it + session_start(); + phpCAS :: trace("Starting a new session " . session_id()); + } + // init phpCAS session array + if (!isset($_SESSION[static::PHPCAS_SESSION_PREFIX]) + || !is_array($_SESSION[static::PHPCAS_SESSION_PREFIX])) { + $_SESSION[static::PHPCAS_SESSION_PREFIX] = array(); + } + } + + // Only for debug purposes + if ($this->isSessionAuthenticated()){ + phpCAS :: trace("Session is authenticated as: " . $this->getSessionValue('user')); + } else { + phpCAS :: trace("Session is not authenticated"); + } + // are we in proxy mode ? + $this->_proxy = $proxy; + + // Make cookie handling available. + if ($this->isProxy()) { + if (!$this->hasSessionValue('service_cookies')) { + $this->setSessionValue('service_cookies', array()); + } + // TODO remove explicit call to $_SESSION + $this->_serviceCookieJar = new CAS_CookieJar( + $_SESSION[static::PHPCAS_SESSION_PREFIX]['service_cookies'] + ); + } + + // check version + $supportedProtocols = phpCAS::getSupportedProtocols(); + if (isset($supportedProtocols[$server_version]) === false) { + phpCAS::error( + 'this version of CAS (`'.$server_version + .'\') is not supported by phpCAS '.phpCAS::getVersion() + ); + } + + if ($server_version === CAS_VERSION_1_0 && $this->isProxy()) { + phpCAS::error( + 'CAS proxies are not supported in CAS '.$server_version + ); + } + + $this->_server['version'] = $server_version; + + // check hostname + if ( empty($server_hostname) + || !preg_match('/[\.\d\-a-z]*/', $server_hostname) + ) { + phpCAS::error('bad CAS server hostname (`'.$server_hostname.'\')'); + } + $this->_server['hostname'] = $server_hostname; + + // check port + if ( $server_port == 0 + || !is_int($server_port) + ) { + phpCAS::error('bad CAS server port (`'.$server_hostname.'\')'); + } + $this->_server['port'] = $server_port; + + // check URI + if ( !preg_match('/[\.\d\-_a-z\/]*/', $server_uri) ) { + phpCAS::error('bad CAS server URI (`'.$server_uri.'\')'); + } + // add leading and trailing `/' and remove doubles + if(strstr($server_uri, '?') === false) $server_uri .= '/'; + $server_uri = preg_replace('/\/\//', '/', '/'.$server_uri); + $this->_server['uri'] = $server_uri; + + // set to callback mode if PgtIou and PgtId CGI GET parameters are provided + if ( $this->isProxy() ) { + if(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId'])) { + $this->_setCallbackMode(true); + $this->_setCallbackModeUsingPost(false); + } elseif (!empty($_POST['pgtIou'])&&!empty($_POST['pgtId'])) { + $this->_setCallbackMode(true); + $this->_setCallbackModeUsingPost(true); + } else { + $this->_setCallbackMode(false); + $this->_setCallbackModeUsingPost(false); + } + + + } + + if ( $this->_isCallbackMode() ) { + //callback mode: check that phpCAS is secured + if ( !$this->_isHttps() ) { + phpCAS::error( + 'CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server' + ); + } + } else { + //normal mode: get ticket and remove it from CGI parameters for + // developers + $ticket = (isset($_GET['ticket']) ? $_GET['ticket'] : ''); + if (preg_match('/^[SP]T-/', $ticket) ) { + phpCAS::trace('Ticket \''.$ticket.'\' found'); + $this->setTicket($ticket); + unset($_GET['ticket']); + } else if ( !empty($ticket) ) { + //ill-formed ticket, halt + phpCAS::error( + 'ill-formed ticket found in the URL (ticket=`' + .htmlentities($ticket).'\')' + ); + } + + } + phpCAS::traceEnd(); + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX Session Handling XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + /** + * @addtogroup internalConfig + * @{ + */ + + /** The session prefix for phpCAS values */ + const PHPCAS_SESSION_PREFIX = 'phpCAS'; + + /** + * @var bool A variable to whether phpcas will use its own session handling. Default = true + * @hideinitializer + */ + private $_change_session_id = true; + + /** + * @var SessionHandlerInterface + */ + private $_sessionHandler; + + /** + * Set a parameter whether to allow phpCAS to change session_id + * + * @param bool $allowed allow phpCAS to change session_id + * + * @return void + */ + private function _setChangeSessionID($allowed) + { + $this->_change_session_id = $allowed; + } + + /** + * Get whether phpCAS is allowed to change session_id + * + * @return bool + */ + public function getChangeSessionID() + { + return $this->_change_session_id; + } + + /** + * Set the session handler. + * + * @param \SessionHandlerInterface $sessionHandler + * + * @return bool + */ + public function setSessionHandler(\SessionHandlerInterface $sessionHandler) + { + $this->_sessionHandler = $sessionHandler; + if (session_status() !== PHP_SESSION_ACTIVE) { + return session_set_save_handler($this->_sessionHandler, true); + } + return true; + } + + /** + * Get a session value using the given key. + * + * @param string $key + * @param mixed $default default value if the key is not set + * + * @return mixed + */ + protected function getSessionValue($key, $default = null) + { + $this->validateSession($key); + + if (isset($_SESSION[static::PHPCAS_SESSION_PREFIX][$key])) { + return $_SESSION[static::PHPCAS_SESSION_PREFIX][$key]; + } + + return $default; + } + + /** + * Determine whether a session value is set or not. + * + * To check if a session value is empty or not please use + * !!(getSessionValue($key)). + * + * @param string $key + * + * @return bool + */ + protected function hasSessionValue($key) + { + $this->validateSession($key); + + return isset($_SESSION[static::PHPCAS_SESSION_PREFIX][$key]); + } + + /** + * Set a session value using the given key and value. + * + * @param string $key + * @param mixed $value + * + * @return string + */ + protected function setSessionValue($key, $value) + { + $this->validateSession($key); + + $_SESSION[static::PHPCAS_SESSION_PREFIX][$key] = $value; + } + + /** + * Remove a session value with the given key. + * + * @param string $key + */ + protected function removeSessionValue($key) + { + $this->validateSession($key); + + if (isset($_SESSION[static::PHPCAS_SESSION_PREFIX][$key])) { + unset($_SESSION[static::PHPCAS_SESSION_PREFIX][$key]); + return true; + } + + return false; + } + + /** + * Remove all phpCAS session values. + */ + protected function clearSessionValues() + { + unset($_SESSION[static::PHPCAS_SESSION_PREFIX]); + } + + /** + * Ensure $key is a string for session utils input + * + * @param string $key + * + * @return bool + */ + protected function validateSession($key) + { + if (!is_string($key)) { + throw new InvalidArgumentException('Session key must be a string.'); + } + + return true; + } + + /** + * Renaming the session + * + * @param string $ticket name of the ticket + * + * @return void + */ + protected function _renameSession($ticket) + { + phpCAS::traceBegin(); + if ($this->getChangeSessionID()) { + if (!empty($this->_user)) { + $old_session = $_SESSION; + phpCAS :: trace("Killing session: ". session_id()); + session_destroy(); + // set up a new session, of name based on the ticket + $session_id = $this->_sessionIdForTicket($ticket); + phpCAS :: trace("Starting session: ". $session_id); + session_id($session_id); + session_start(); + phpCAS :: trace("Restoring old session vars"); + $_SESSION = $old_session; + } else { + phpCAS :: trace ( + 'Session should only be renamed after successfull authentication' + ); + } + } else { + phpCAS :: trace( + "Skipping session rename since phpCAS is not handling the session." + ); + } + phpCAS::traceEnd(); + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX AUTHENTICATION XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + /** + * @addtogroup internalAuthentication + * @{ + */ + + /** + * The Authenticated user. Written by CAS_Client::_setUser(), read by + * CAS_Client::getUser(). + * + * @hideinitializer + */ + private $_user = ''; + + /** + * This method sets the CAS user's login name. + * + * @param string $user the login name of the authenticated user. + * + * @return void + */ + private function _setUser($user) + { + $this->_user = $user; + } + + /** + * This method returns the CAS user's login name. + * + * @return string the login name of the authenticated user + * + * @warning should be called only after CAS_Client::forceAuthentication() or + * CAS_Client::isAuthenticated(), otherwise halt with an error. + */ + public function getUser() + { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + + return $this->_getUser(); + } + + /** + * This method returns the CAS user's login name. + * + * @return string the login name of the authenticated user + * + * @warning should be called only after CAS_Client::forceAuthentication() or + * CAS_Client::isAuthenticated(), otherwise halt with an error. + */ + private function _getUser() + { + // This is likely a duplicate check that could be removed.... + if ( empty($this->_user) ) { + phpCAS::error( + 'this method should be used only after '.__CLASS__ + .'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()' + ); + } + return $this->_user; + } + + /** + * The Authenticated users attributes. Written by + * CAS_Client::setAttributes(), read by CAS_Client::getAttributes(). + * @attention client applications should use phpCAS::getAttributes(). + * + * @hideinitializer + */ + private $_attributes = array(); + + /** + * Set an array of attributes + * + * @param array $attributes a key value array of attributes + * + * @return void + */ + public function setAttributes($attributes) + { + $this->_attributes = $attributes; + } + + /** + * Get an key values arry of attributes + * + * @return array of attributes + */ + public function getAttributes() + { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + // This is likely a duplicate check that could be removed.... + if ( empty($this->_user) ) { + // if no user is set, there shouldn't be any attributes also... + phpCAS::error( + 'this method should be used only after '.__CLASS__ + .'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()' + ); + } + return $this->_attributes; + } + + /** + * Check whether attributes are available + * + * @return bool attributes available + */ + public function hasAttributes() + { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + + return !empty($this->_attributes); + } + /** + * Check whether a specific attribute with a name is available + * + * @param string $key name of attribute + * + * @return bool is attribute available + */ + public function hasAttribute($key) + { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + + return $this->_hasAttribute($key); + } + + /** + * Check whether a specific attribute with a name is available + * + * @param string $key name of attribute + * + * @return bool is attribute available + */ + private function _hasAttribute($key) + { + return (is_array($this->_attributes) + && array_key_exists($key, $this->_attributes)); + } + + /** + * Get a specific attribute by name + * + * @param string $key name of attribute + * + * @return string attribute values + */ + public function getAttribute($key) + { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + + if ($this->_hasAttribute($key)) { + return $this->_attributes[$key]; + } + } + + /** + * This method is called to renew the authentication of the user + * If the user is authenticated, renew the connection + * If not, redirect to CAS + * + * @return bool true when the user is authenticated; otherwise halt. + */ + public function renewAuthentication() + { + phpCAS::traceBegin(); + // Either way, the user is authenticated by CAS + $this->removeSessionValue('auth_checked'); + if ( $this->isAuthenticated(true) ) { + phpCAS::trace('user already authenticated'); + $res = true; + } else { + $this->redirectToCas(false, true); + // never reached + $res = false; + } + phpCAS::traceEnd(); + return $res; + } + + /** + * This method is called to be sure that the user is authenticated. When not + * authenticated, halt by redirecting to the CAS server; otherwise return true. + * + * @return bool true when the user is authenticated; otherwise halt. + */ + public function forceAuthentication() + { + phpCAS::traceBegin(); + + if ( $this->isAuthenticated() ) { + // the user is authenticated, nothing to be done. + phpCAS::trace('no need to authenticate'); + $res = true; + } else { + // the user is not authenticated, redirect to the CAS server + $this->removeSessionValue('auth_checked'); + $this->redirectToCas(false/* no gateway */); + // never reached + $res = false; + } + phpCAS::traceEnd($res); + return $res; + } + + /** + * An integer that gives the number of times authentication will be cached + * before rechecked. + * + * @hideinitializer + */ + private $_cache_times_for_auth_recheck = 0; + + /** + * Set the number of times authentication will be cached before rechecked. + * + * @param int $n number of times to wait for a recheck + * + * @return void + */ + public function setCacheTimesForAuthRecheck($n) + { + if (gettype($n) != 'integer') + throw new CAS_TypeMismatchException($n, '$n', 'string'); + + $this->_cache_times_for_auth_recheck = $n; + } + + /** + * This method is called to check whether the user is authenticated or not. + * + * @return bool true when the user is authenticated, false when a previous + * gateway login failed or the function will not return if the user is + * redirected to the cas server for a gateway login attempt + */ + public function checkAuthentication() + { + phpCAS::traceBegin(); + $res = false; // default + if ( $this->isAuthenticated() ) { + phpCAS::trace('user is authenticated'); + /* The 'auth_checked' variable is removed just in case it's set. */ + $this->removeSessionValue('auth_checked'); + $res = true; + } else if ($this->getSessionValue('auth_checked')) { + // the previous request has redirected the client to the CAS server + // with gateway=true + $this->removeSessionValue('auth_checked'); + } else { + // avoid a check against CAS on every request + // we need to write this back to session later + $unauth_count = $this->getSessionValue('unauth_count', -2); + + if (($unauth_count != -2 + && $this->_cache_times_for_auth_recheck == -1) + || ($unauth_count >= 0 + && $unauth_count < $this->_cache_times_for_auth_recheck) + ) { + if ($this->_cache_times_for_auth_recheck != -1) { + $unauth_count++; + phpCAS::trace( + 'user is not authenticated (cached for ' + .$unauth_count.' times of ' + .$this->_cache_times_for_auth_recheck.')' + ); + } else { + phpCAS::trace( + 'user is not authenticated (cached for until login pressed)' + ); + } + $this->setSessionValue('unauth_count', $unauth_count); + } else { + $this->setSessionValue('unauth_count', 0); + $this->setSessionValue('auth_checked', true); + phpCAS::trace('user is not authenticated (cache reset)'); + $this->redirectToCas(true/* gateway */); + // never reached + } + } + phpCAS::traceEnd($res); + return $res; + } + + /** + * This method is called to check if the user is authenticated (previously or by + * tickets given in the URL). + * + * @param bool $renew true to force the authentication with the CAS server + * + * @return bool true when the user is authenticated. Also may redirect to the + * same URL without the ticket. + */ + public function isAuthenticated($renew=false) + { + phpCAS::traceBegin(); + $res = false; + + if ( $this->_wasPreviouslyAuthenticated() ) { + if ($this->hasTicket()) { + // User has a additional ticket but was already authenticated + phpCAS::trace( + 'ticket was present and will be discarded, use renewAuthenticate()' + ); + if ($this->_clearTicketsFromUrl) { + phpCAS::trace("Prepare redirect to : ".$this->getURL()); + session_write_close(); + header('Location: '.$this->getURL()); + flush(); + phpCAS::traceExit(); + throw new CAS_GracefullTerminationException(); + } else { + phpCAS::trace( + 'Already authenticated, but skipping ticket clearing since setNoClearTicketsFromUrl() was used.' + ); + $res = true; + } + } else { + // the user has already (previously during the session) been + // authenticated, nothing to be done. + phpCAS::trace( + 'user was already authenticated, no need to look for tickets' + ); + $res = true; + } + + // Mark the auth-check as complete to allow post-authentication + // callbacks to make use of phpCAS::getUser() and similar methods + $this->markAuthenticationCall($res); + } else { + if ($this->hasTicket()) { + $validate_url = ''; + $text_response = ''; + $tree_response = ''; + + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + // if a Service Ticket was given, validate it + phpCAS::trace( + 'CAS 1.0 ticket `'.$this->getTicket().'\' is present' + ); + $this->validateCAS10( + $validate_url, $text_response, $tree_response, $renew + ); // if it fails, it halts + phpCAS::trace( + 'CAS 1.0 ticket `'.$this->getTicket().'\' was validated' + ); + $this->setSessionValue('user', $this->_getUser()); + $res = true; + $logoutTicket = $this->getTicket(); + break; + case CAS_VERSION_2_0: + case CAS_VERSION_3_0: + // if a Proxy Ticket was given, validate it + phpCAS::trace( + 'CAS '.$this->getServerVersion().' ticket `'.$this->getTicket().'\' is present' + ); + $this->validateCAS20( + $validate_url, $text_response, $tree_response, $renew + ); // note: if it fails, it halts + phpCAS::trace( + 'CAS '.$this->getServerVersion().' ticket `'.$this->getTicket().'\' was validated' + ); + if ( $this->isProxy() ) { + $this->_validatePGT( + $validate_url, $text_response, $tree_response + ); // idem + phpCAS::trace('PGT `'.$this->_getPGT().'\' was validated'); + $this->setSessionValue('pgt', $this->_getPGT()); + } + $this->setSessionValue('user', $this->_getUser()); + if (!empty($this->_attributes)) { + $this->setSessionValue('attributes', $this->_attributes); + } + $proxies = $this->getProxies(); + if (!empty($proxies)) { + $this->setSessionValue('proxies', $this->getProxies()); + } + $res = true; + $logoutTicket = $this->getTicket(); + break; + case SAML_VERSION_1_1: + // if we have a SAML ticket, validate it. + phpCAS::trace( + 'SAML 1.1 ticket `'.$this->getTicket().'\' is present' + ); + $this->validateSA( + $validate_url, $text_response, $tree_response, $renew + ); // if it fails, it halts + phpCAS::trace( + 'SAML 1.1 ticket `'.$this->getTicket().'\' was validated' + ); + $this->setSessionValue('user', $this->_getUser()); + $this->setSessionValue('attributes', $this->_attributes); + $res = true; + $logoutTicket = $this->getTicket(); + break; + default: + phpCAS::trace('Protocol error'); + break; + } + } else { + // no ticket given, not authenticated + phpCAS::trace('no ticket found'); + } + + // Mark the auth-check as complete to allow post-authentication + // callbacks to make use of phpCAS::getUser() and similar methods + $this->markAuthenticationCall($res); + + if ($res) { + // call the post-authenticate callback if registered. + if ($this->_postAuthenticateCallbackFunction) { + $args = $this->_postAuthenticateCallbackArgs; + array_unshift($args, $logoutTicket); + call_user_func_array( + $this->_postAuthenticateCallbackFunction, $args + ); + } + + // if called with a ticket parameter, we need to redirect to the + // app without the ticket so that CAS-ification is transparent + // to the browser (for later POSTS) most of the checks and + // errors should have been made now, so we're safe for redirect + // without masking error messages. remove the ticket as a + // security precaution to prevent a ticket in the HTTP_REFERRER + if ($this->_clearTicketsFromUrl) { + phpCAS::trace("Prepare redirect to : ".$this->getURL()); + session_write_close(); + header('Location: '.$this->getURL()); + flush(); + phpCAS::traceExit(); + throw new CAS_GracefullTerminationException(); + } + } + } + phpCAS::traceEnd($res); + return $res; + } + + /** + * This method tells if the current session is authenticated. + * + * @return bool true if authenticated based soley on $_SESSION variable + */ + public function isSessionAuthenticated () + { + return !!$this->getSessionValue('user'); + } + + /** + * This method tells if the user has already been (previously) authenticated + * by looking into the session variables. + * + * @note This function switches to callback mode when needed. + * + * @return bool true when the user has already been authenticated; false otherwise. + */ + private function _wasPreviouslyAuthenticated() + { + phpCAS::traceBegin(); + + if ( $this->_isCallbackMode() ) { + // Rebroadcast the pgtIou and pgtId to all nodes + if ($this->_rebroadcast&&!isset($_POST['rebroadcast'])) { + $this->_rebroadcast(self::PGTIOU); + } + $this->_callback(); + } + + $auth = false; + + if ( $this->isProxy() ) { + // CAS proxy: username and PGT must be present + if ( $this->isSessionAuthenticated() + && $this->getSessionValue('pgt') + ) { + // authentication already done + $this->_setUser($this->getSessionValue('user')); + if ($this->hasSessionValue('attributes')) { + $this->setAttributes($this->getSessionValue('attributes')); + } + $this->_setPGT($this->getSessionValue('pgt')); + phpCAS::trace( + 'user = `'.$this->getSessionValue('user').'\', PGT = `' + .$this->getSessionValue('pgt').'\'' + ); + + // Include the list of proxies + if ($this->hasSessionValue('proxies')) { + $this->_setProxies($this->getSessionValue('proxies')); + phpCAS::trace( + 'proxies = "' + .implode('", "', $this->getSessionValue('proxies')).'"' + ); + } + + $auth = true; + } elseif ( $this->isSessionAuthenticated() + && !$this->getSessionValue('pgt') + ) { + // these two variables should be empty or not empty at the same time + phpCAS::trace( + 'username found (`'.$this->getSessionValue('user') + .'\') but PGT is empty' + ); + // unset all tickets to enforce authentication + $this->clearSessionValues(); + $this->setTicket(''); + } elseif ( !$this->isSessionAuthenticated() + && $this->getSessionValue('pgt') + ) { + // these two variables should be empty or not empty at the same time + phpCAS::trace( + 'PGT found (`'.$this->getSessionValue('pgt') + .'\') but username is empty' + ); + // unset all tickets to enforce authentication + $this->clearSessionValues(); + $this->setTicket(''); + } else { + phpCAS::trace('neither user nor PGT found'); + } + } else { + // `simple' CAS client (not a proxy): username must be present + if ( $this->isSessionAuthenticated() ) { + // authentication already done + $this->_setUser($this->getSessionValue('user')); + if ($this->hasSessionValue('attributes')) { + $this->setAttributes($this->getSessionValue('attributes')); + } + phpCAS::trace('user = `'.$this->getSessionValue('user').'\''); + + // Include the list of proxies + if ($this->hasSessionValue('proxies')) { + $this->_setProxies($this->getSessionValue('proxies')); + phpCAS::trace( + 'proxies = "' + .implode('", "', $this->getSessionValue('proxies')).'"' + ); + } + + $auth = true; + } else { + phpCAS::trace('no user found'); + } + } + + phpCAS::traceEnd($auth); + return $auth; + } + + /** + * This method is used to redirect the client to the CAS server. + * It is used by CAS_Client::forceAuthentication() and + * CAS_Client::checkAuthentication(). + * + * @param bool $gateway true to check authentication, false to force it + * @param bool $renew true to force the authentication with the CAS server + * + * @return void + */ + public function redirectToCas($gateway=false,$renew=false) + { + phpCAS::traceBegin(); + $cas_url = $this->getServerLoginURL($gateway, $renew); + session_write_close(); + if (php_sapi_name() === 'cli') { + @header('Location: '.$cas_url); + } else { + header('Location: '.$cas_url); + } + phpCAS::trace("Redirect to : ".$cas_url); + $lang = $this->getLangObj(); + $this->printHTMLHeader($lang->getAuthenticationWanted()); + $this->printf('

    '. $lang->getShouldHaveBeenRedirected(). '

    ', $cas_url); + $this->printHTMLFooter(); + phpCAS::traceExit(); + throw new CAS_GracefullTerminationException(); + } + + + /** + * This method is used to logout from CAS. + * + * @param array $params an array that contains the optional url and service + * parameters that will be passed to the CAS server + * + * @return void + */ + public function logout($params) + { + phpCAS::traceBegin(); + $cas_url = $this->getServerLogoutURL(); + $paramSeparator = '?'; + if (isset($params['url'])) { + $cas_url = $cas_url . $paramSeparator . "url=" + . urlencode($params['url']); + $paramSeparator = '&'; + } + if (isset($params['service'])) { + $cas_url = $cas_url . $paramSeparator . "service=" + . urlencode($params['service']); + } + header('Location: '.$cas_url); + phpCAS::trace("Prepare redirect to : ".$cas_url); + + phpCAS::trace("Destroying session : ".session_id()); + session_unset(); + session_destroy(); + if (session_status() === PHP_SESSION_NONE) { + phpCAS::trace("Session terminated"); + } else { + phpCAS::error("Session was not terminated"); + phpCAS::trace("Session was not terminated"); + } + $lang = $this->getLangObj(); + $this->printHTMLHeader($lang->getLogout()); + $this->printf('

    '.$lang->getShouldHaveBeenRedirected(). '

    ', $cas_url); + $this->printHTMLFooter(); + phpCAS::traceExit(); + throw new CAS_GracefullTerminationException(); + } + + /** + * Check of the current request is a logout request + * + * @return bool is logout request. + */ + private function _isLogoutRequest() + { + return !empty($_POST['logoutRequest']); + } + + /** + * This method handles logout requests. + * + * @param bool $check_client true to check the client bofore handling + * the request, false not to perform any access control. True by default. + * @param array $allowed_clients an array of host names allowed to send + * logout requests. + * + * @return void + */ + public function handleLogoutRequests($check_client=true, $allowed_clients=array()) + { + phpCAS::traceBegin(); + if (!$this->_isLogoutRequest()) { + phpCAS::trace("Not a logout request"); + phpCAS::traceEnd(); + return; + } + if (!$this->getChangeSessionID() + && is_null($this->_signoutCallbackFunction) + ) { + phpCAS::trace( + "phpCAS can't handle logout requests if it is not allowed to change session_id." + ); + } + phpCAS::trace("Logout requested"); + $decoded_logout_rq = urldecode($_POST['logoutRequest']); + phpCAS::trace("SAML REQUEST: ".$decoded_logout_rq); + $allowed = false; + if ($check_client) { + if ($allowed_clients === array()) { + $allowed_clients = array( $this->_getServerHostname() ); + } + $client_ip = $_SERVER['REMOTE_ADDR']; + $client = gethostbyaddr($client_ip); + phpCAS::trace("Client: ".$client."/".$client_ip); + foreach ($allowed_clients as $allowed_client) { + if (($client == $allowed_client) + || ($client_ip == $allowed_client) + ) { + phpCAS::trace( + "Allowed client '".$allowed_client + ."' matches, logout request is allowed" + ); + $allowed = true; + break; + } else { + phpCAS::trace( + "Allowed client '".$allowed_client."' does not match" + ); + } + } + } else { + phpCAS::trace("No access control set"); + $allowed = true; + } + // If Logout command is permitted proceed with the logout + if ($allowed) { + phpCAS::trace("Logout command allowed"); + // Rebroadcast the logout request + if ($this->_rebroadcast && !isset($_POST['rebroadcast'])) { + $this->_rebroadcast(self::LOGOUT); + } + // Extract the ticket from the SAML Request + preg_match( + "|(.*)|", + $decoded_logout_rq, $tick, PREG_OFFSET_CAPTURE, 3 + ); + $wrappedSamlSessionIndex = preg_replace( + '||', '', $tick[0][0] + ); + $ticket2logout = preg_replace( + '||', '', $wrappedSamlSessionIndex + ); + phpCAS::trace("Ticket to logout: ".$ticket2logout); + + // call the post-authenticate callback if registered. + if ($this->_signoutCallbackFunction) { + $args = $this->_signoutCallbackArgs; + array_unshift($args, $ticket2logout); + call_user_func_array($this->_signoutCallbackFunction, $args); + } + + // If phpCAS is managing the session_id, destroy session thanks to + // session_id. + if ($this->getChangeSessionID()) { + $session_id = $this->_sessionIdForTicket($ticket2logout); + phpCAS::trace("Session id: ".$session_id); + + // destroy a possible application session created before phpcas + if (session_id() !== "") { + session_unset(); + session_destroy(); + } + // fix session ID + session_id($session_id); + $_COOKIE[session_name()]=$session_id; + $_GET[session_name()]=$session_id; + + // Overwrite session + session_start(); + session_unset(); + session_destroy(); + phpCAS::trace("Session ". $session_id . " destroyed"); + } + } else { + phpCAS::error("Unauthorized logout request from client '".$client."'"); + phpCAS::trace("Unauthorized logout request from client '".$client."'"); + } + flush(); + phpCAS::traceExit(); + throw new CAS_GracefullTerminationException(); + + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX BASIC CLIENT FEATURES (CAS 1.0) XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + // ######################################################################## + // ST + // ######################################################################## + /** + * @addtogroup internalBasic + * @{ + */ + + /** + * The Ticket provided in the URL of the request if present + * (empty otherwise). Written by CAS_Client::CAS_Client(), read by + * CAS_Client::getTicket() and CAS_Client::_hasPGT(). + * + * @hideinitializer + */ + private $_ticket = ''; + + /** + * This method returns the Service Ticket provided in the URL of the request. + * + * @return string service ticket. + */ + public function getTicket() + { + return $this->_ticket; + } + + /** + * This method stores the Service Ticket. + * + * @param string $st The Service Ticket. + * + * @return void + */ + public function setTicket($st) + { + $this->_ticket = $st; + } + + /** + * This method tells if a Service Ticket was stored. + * + * @return bool if a Service Ticket has been stored. + */ + public function hasTicket() + { + return !empty($this->_ticket); + } + + /** @} */ + + // ######################################################################## + // ST VALIDATION + // ######################################################################## + /** + * @addtogroup internalBasic + * @{ + */ + + /** + * @var string the certificate of the CAS server CA. + * + * @hideinitializer + */ + private $_cas_server_ca_cert = null; + + + /** + + * validate CN of the CAS server certificate + + * + + * @hideinitializer + + */ + + private $_cas_server_cn_validate = true; + + /** + * Set to true not to validate the CAS server. + * + * @hideinitializer + */ + private $_no_cas_server_validation = false; + + + /** + * Set the CA certificate of the CAS server. + * + * @param string $cert the PEM certificate file name of the CA that emited + * the cert of the server + * @param bool $validate_cn valiate CN of the CAS server certificate + * + * @return void + */ + public function setCasServerCACert($cert, $validate_cn) + { + // Argument validation + if (gettype($cert) != 'string') { + throw new CAS_TypeMismatchException($cert, '$cert', 'string'); + } + if (gettype($validate_cn) != 'boolean') { + throw new CAS_TypeMismatchException($validate_cn, '$validate_cn', 'boolean'); + } + if (!file_exists($cert)) { + throw new CAS_InvalidArgumentException("Certificate file does not exist " . $this->_requestImplementation); + } + $this->_cas_server_ca_cert = $cert; + $this->_cas_server_cn_validate = $validate_cn; + } + + /** + * Set no SSL validation for the CAS server. + * + * @return void + */ + public function setNoCasServerValidation() + { + $this->_no_cas_server_validation = true; + } + + /** + * This method is used to validate a CAS 1,0 ticket; halt on failure, and + * sets $validate_url, $text_reponse and $tree_response on success. + * + * @param string &$validate_url reference to the the URL of the request to + * the CAS server. + * @param string &$text_response reference to the response of the CAS + * server, as is (XML text). + * @param string &$tree_response reference to the response of the CAS + * server, as a DOM XML tree. + * @param bool $renew true to force the authentication with the CAS server + * + * @return bool true when successfull and issue a CAS_AuthenticationException + * and false on an error + * @throws CAS_AuthenticationException + */ + public function validateCAS10(&$validate_url,&$text_response,&$tree_response,$renew=false) + { + phpCAS::traceBegin(); + // build the URL to validate the ticket + $validate_url = $this->getServerServiceValidateURL() + .'&ticket='.urlencode($this->getTicket()); + + if ( $renew ) { + // pass the renew + $validate_url .= '&renew=true'; + } + + $headers = ''; + $err_msg = ''; + // open and read the URL + if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) { + phpCAS::trace( + 'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')' + ); + throw new CAS_AuthenticationException( + $this, 'CAS 1.0 ticket not validated', $validate_url, + true/*$no_response*/ + ); + } + + if (preg_match('/^no\n/', $text_response)) { + phpCAS::trace('Ticket has not been validated'); + throw new CAS_AuthenticationException( + $this, 'ST not validated', $validate_url, false/*$no_response*/, + false/*$bad_response*/, $text_response + ); + } else if (!preg_match('/^yes\n/', $text_response)) { + phpCAS::trace('ill-formed response'); + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, $text_response + ); + } + // ticket has been validated, extract the user name + $arr = preg_split('/\n/', $text_response); + $this->_setUser(trim($arr[1])); + + $this->_renameSession($this->getTicket()); + + // at this step, ticket has been validated and $this->_user has been set, + phpCAS::traceEnd(true); + return true; + } + + /** @} */ + + + // ######################################################################## + // SAML VALIDATION + // ######################################################################## + /** + * @addtogroup internalSAML + * @{ + */ + + /** + * This method is used to validate a SAML TICKET; halt on failure, and sets + * $validate_url, $text_reponse and $tree_response on success. These + * parameters are used later by CAS_Client::_validatePGT() for CAS proxies. + * + * @param string &$validate_url reference to the the URL of the request to + * the CAS server. + * @param string &$text_response reference to the response of the CAS + * server, as is (XML text). + * @param string &$tree_response reference to the response of the CAS + * server, as a DOM XML tree. + * @param bool $renew true to force the authentication with the CAS server + * + * @return bool true when successfull and issue a CAS_AuthenticationException + * and false on an error + * + * @throws CAS_AuthenticationException + */ + public function validateSA(&$validate_url,&$text_response,&$tree_response,$renew=false) + { + phpCAS::traceBegin(); + $result = false; + // build the URL to validate the ticket + $validate_url = $this->getServerSamlValidateURL(); + + if ( $renew ) { + // pass the renew + $validate_url .= '&renew=true'; + } + + $headers = ''; + $err_msg = ''; + // open and read the URL + if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) { + phpCAS::trace( + 'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')' + ); + throw new CAS_AuthenticationException( + $this, 'SA not validated', $validate_url, true/*$no_response*/ + ); + } + + phpCAS::trace('server version: '.$this->getServerVersion()); + + // analyze the result depending on the version + switch ($this->getServerVersion()) { + case SAML_VERSION_1_1: + // create new DOMDocument Object + $dom = new DOMDocument(); + // Fix possible whitspace problems + $dom->preserveWhiteSpace = false; + // read the response of the CAS server into a DOM object + if (!($dom->loadXML($text_response))) { + phpCAS::trace('dom->loadXML() failed'); + throw new CAS_AuthenticationException( + $this, 'SA not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, + $text_response + ); + } + // read the root node of the XML tree + if (!($tree_response = $dom->documentElement)) { + phpCAS::trace('documentElement() failed'); + throw new CAS_AuthenticationException( + $this, 'SA not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, + $text_response + ); + } else if ( $tree_response->localName != 'Envelope' ) { + // insure that tag name is 'Envelope' + phpCAS::trace( + 'bad XML root node (should be `Envelope\' instead of `' + .$tree_response->localName.'\'' + ); + throw new CAS_AuthenticationException( + $this, 'SA not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, + $text_response + ); + } else if ($tree_response->getElementsByTagName("NameIdentifier")->length != 0) { + // check for the NameIdentifier tag in the SAML response + $success_elements = $tree_response->getElementsByTagName("NameIdentifier"); + phpCAS::trace('NameIdentifier found'); + $user = trim($success_elements->item(0)->nodeValue); + phpCAS::trace('user = `'.$user.'`'); + $this->_setUser($user); + $this->_setSessionAttributes($text_response); + $result = true; + } else { + phpCAS::trace('no tag found in SAML payload'); + throw new CAS_AuthenticationException( + $this, 'SA not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, + $text_response + ); + } + } + if ($result) { + $this->_renameSession($this->getTicket()); + } + // at this step, ST has been validated and $this->_user has been set, + phpCAS::traceEnd($result); + return $result; + } + + /** + * This method will parse the DOM and pull out the attributes from the SAML + * payload and put them into an array, then put the array into the session. + * + * @param string $text_response the SAML payload. + * + * @return bool true when successfull and false if no attributes a found + */ + private function _setSessionAttributes($text_response) + { + phpCAS::traceBegin(); + + $result = false; + + $attr_array = array(); + + // create new DOMDocument Object + $dom = new DOMDocument(); + // Fix possible whitspace problems + $dom->preserveWhiteSpace = false; + if (($dom->loadXML($text_response))) { + $xPath = new DOMXPath($dom); + $xPath->registerNamespace('samlp', 'urn:oasis:names:tc:SAML:1.0:protocol'); + $xPath->registerNamespace('saml', 'urn:oasis:names:tc:SAML:1.0:assertion'); + $nodelist = $xPath->query("//saml:Attribute"); + + if ($nodelist) { + foreach ($nodelist as $node) { + $xres = $xPath->query("saml:AttributeValue", $node); + $name = $node->getAttribute("AttributeName"); + $value_array = array(); + foreach ($xres as $node2) { + $value_array[] = $node2->nodeValue; + } + $attr_array[$name] = $value_array; + } + // UGent addition... + foreach ($attr_array as $attr_key => $attr_value) { + if (count($attr_value) > 1) { + $this->_attributes[$attr_key] = $attr_value; + phpCAS::trace("* " . $attr_key . "=" . print_r($attr_value, true)); + } else { + $this->_attributes[$attr_key] = $attr_value[0]; + phpCAS::trace("* " . $attr_key . "=" . $attr_value[0]); + } + } + $result = true; + } else { + phpCAS::trace("SAML Attributes are empty"); + $result = false; + } + } + phpCAS::traceEnd($result); + return $result; + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX PROXY FEATURES (CAS 2.0) XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + // ######################################################################## + // PROXYING + // ######################################################################## + /** + * @addtogroup internalProxy + * @{ + */ + + /** + * @var bool is the client a proxy + * A boolean telling if the client is a CAS proxy or not. Written by + * CAS_Client::CAS_Client(), read by CAS_Client::isProxy(). + */ + private $_proxy; + + /** + * @var CAS_CookieJar Handler for managing service cookies. + */ + private $_serviceCookieJar; + + /** + * Tells if a CAS client is a CAS proxy or not + * + * @return bool true when the CAS client is a CAS proxy, false otherwise + */ + public function isProxy() + { + return $this->_proxy; + } + + + /** @} */ + // ######################################################################## + // PGT + // ######################################################################## + /** + * @addtogroup internalProxy + * @{ + */ + + /** + * the Proxy Grnting Ticket given by the CAS server (empty otherwise). + * Written by CAS_Client::_setPGT(), read by CAS_Client::_getPGT() and + * CAS_Client::_hasPGT(). + * + * @hideinitializer + */ + private $_pgt = ''; + + /** + * This method returns the Proxy Granting Ticket given by the CAS server. + * + * @return string the Proxy Granting Ticket. + */ + private function _getPGT() + { + return $this->_pgt; + } + + /** + * This method stores the Proxy Granting Ticket. + * + * @param string $pgt The Proxy Granting Ticket. + * + * @return void + */ + private function _setPGT($pgt) + { + $this->_pgt = $pgt; + } + + /** + * This method tells if a Proxy Granting Ticket was stored. + * + * @return bool true if a Proxy Granting Ticket has been stored. + */ + private function _hasPGT() + { + return !empty($this->_pgt); + } + + /** @} */ + + // ######################################################################## + // CALLBACK MODE + // ######################################################################## + /** + * @addtogroup internalCallback + * @{ + */ + /** + * each PHP script using phpCAS in proxy mode is its own callback to get the + * PGT back from the CAS server. callback_mode is detected by the constructor + * thanks to the GET parameters. + */ + + /** + * @var bool a boolean to know if the CAS client is running in callback mode. Written by + * CAS_Client::setCallBackMode(), read by CAS_Client::_isCallbackMode(). + * + * @hideinitializer + */ + private $_callback_mode = false; + + /** + * This method sets/unsets callback mode. + * + * @param bool $callback_mode true to set callback mode, false otherwise. + * + * @return void + */ + private function _setCallbackMode($callback_mode) + { + $this->_callback_mode = $callback_mode; + } + + /** + * This method returns true when the CAS client is running in callback mode, + * false otherwise. + * + * @return bool A boolean. + */ + private function _isCallbackMode() + { + return $this->_callback_mode; + } + + /** + * @var bool a boolean to know if the CAS client is using POST parameters when in callback mode. + * Written by CAS_Client::_setCallbackModeUsingPost(), read by CAS_Client::_isCallbackModeUsingPost(). + * + * @hideinitializer + */ + private $_callback_mode_using_post = false; + + /** + * This method sets/unsets usage of POST parameters in callback mode (default/false is GET parameters) + * + * @param bool $callback_mode_using_post true to use POST, false to use GET (default). + * + * @return void + */ + private function _setCallbackModeUsingPost($callback_mode_using_post) + { + $this->_callback_mode_using_post = $callback_mode_using_post; + } + + /** + * This method returns true when the callback mode is using POST, false otherwise. + * + * @return bool A boolean. + */ + private function _isCallbackModeUsingPost() + { + return $this->_callback_mode_using_post; + } + + /** + * the URL that should be used for the PGT callback (in fact the URL of the + * current request without any CGI parameter). Written and read by + * CAS_Client::_getCallbackURL(). + * + * @hideinitializer + */ + private $_callback_url = ''; + + /** + * This method returns the URL that should be used for the PGT callback (in + * fact the URL of the current request without any CGI parameter, except if + * phpCAS::setFixedCallbackURL() was used). + * + * @return string The callback URL + */ + private function _getCallbackURL() + { + // the URL is built when needed only + if ( empty($this->_callback_url) ) { + // remove the ticket if present in the URL + $final_uri = 'https://'; + $final_uri .= $this->_getClientUrl(); + $request_uri = $_SERVER['REQUEST_URI']; + $request_uri = preg_replace('/\?.*$/', '', $request_uri); + $final_uri .= $request_uri; + $this->_callback_url = $final_uri; + } + return $this->_callback_url; + } + + /** + * This method sets the callback url. + * + * @param string $url url to set callback + * + * @return string the callback url + */ + public function setCallbackURL($url) + { + // Sequence validation + $this->ensureIsProxy(); + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_callback_url = $url; + } + + /** + * This method is called by CAS_Client::CAS_Client() when running in callback + * mode. It stores the PGT and its PGT Iou, prints its output and halts. + * + * @return void + */ + private function _callback() + { + phpCAS::traceBegin(); + if ($this->_isCallbackModeUsingPost()) { + $pgtId = $_POST['pgtId']; + $pgtIou = $_POST['pgtIou']; + } else { + $pgtId = $_GET['pgtId']; + $pgtIou = $_GET['pgtIou']; + } + if (preg_match('/^PGTIOU-[\.\-\w]+$/', $pgtIou)) { + if (preg_match('/^[PT]GT-[\.\-\w]+$/', $pgtId)) { + phpCAS::trace('Storing PGT `'.$pgtId.'\' (id=`'.$pgtIou.'\')'); + $this->_storePGT($pgtId, $pgtIou); + if ($this->isXmlResponse()) { + echo '' . "\r\n"; + echo ''; + phpCAS::traceExit("XML response sent"); + } else { + $this->printHTMLHeader('phpCAS callback'); + echo '

    Storing PGT `'.$pgtId.'\' (id=`'.$pgtIou.'\').

    '; + $this->printHTMLFooter(); + phpCAS::traceExit("HTML response sent"); + } + phpCAS::traceExit("Successfull Callback"); + } else { + phpCAS::error('PGT format invalid' . $pgtId); + phpCAS::traceExit('PGT format invalid' . $pgtId); + } + } else { + phpCAS::error('PGTiou format invalid' . $pgtIou); + phpCAS::traceExit('PGTiou format invalid' . $pgtIou); + } + + // Flush the buffer to prevent from sending anything other then a 200 + // Success Status back to the CAS Server. The Exception would normally + // report as a 500 error. + flush(); + throw new CAS_GracefullTerminationException(); + } + + /** + * Check if application/xml or text/xml is pressent in HTTP_ACCEPT header values + * when return value is complex and contains attached q parameters. + * Example: HTTP_ACCEPT = text/html,application/xhtml+xml,application/xml;q=0.9 + * @return bool + */ + private function isXmlResponse() + { + if (!array_key_exists('HTTP_ACCEPT', $_SERVER)) { + return false; + } + if (strpos($_SERVER['HTTP_ACCEPT'], 'application/xml') === false && strpos($_SERVER['HTTP_ACCEPT'], 'text/xml') === false) { + return false; + } + + return true; + } + + /** @} */ + + // ######################################################################## + // PGT STORAGE + // ######################################################################## + /** + * @addtogroup internalPGTStorage + * @{ + */ + + /** + * @var CAS_PGTStorage_AbstractStorage + * an instance of a class inheriting of PGTStorage, used to deal with PGT + * storage. Created by CAS_Client::setPGTStorageFile(), used + * by CAS_Client::setPGTStorageFile() and CAS_Client::_initPGTStorage(). + * + * @hideinitializer + */ + private $_pgt_storage = null; + + /** + * This method is used to initialize the storage of PGT's. + * Halts on error. + * + * @return void + */ + private function _initPGTStorage() + { + // if no SetPGTStorageXxx() has been used, default to file + if ( !is_object($this->_pgt_storage) ) { + $this->setPGTStorageFile(); + } + + // initializes the storage + $this->_pgt_storage->init(); + } + + /** + * This method stores a PGT. Halts on error. + * + * @param string $pgt the PGT to store + * @param string $pgt_iou its corresponding Iou + * + * @return void + */ + private function _storePGT($pgt,$pgt_iou) + { + // ensure that storage is initialized + $this->_initPGTStorage(); + // writes the PGT + $this->_pgt_storage->write($pgt, $pgt_iou); + } + + /** + * This method reads a PGT from its Iou and deletes the corresponding + * storage entry. + * + * @param string $pgt_iou the PGT Iou + * + * @return string mul The PGT corresponding to the Iou, false when not found. + */ + private function _loadPGT($pgt_iou) + { + // ensure that storage is initialized + $this->_initPGTStorage(); + // read the PGT + return $this->_pgt_storage->read($pgt_iou); + } + + /** + * This method can be used to set a custom PGT storage object. + * + * @param CAS_PGTStorage_AbstractStorage $storage a PGT storage object that + * inherits from the CAS_PGTStorage_AbstractStorage class + * + * @return void + */ + public function setPGTStorage($storage) + { + // Sequence validation + $this->ensureIsProxy(); + + // check that the storage has not already been set + if ( is_object($this->_pgt_storage) ) { + phpCAS::error('PGT storage already defined'); + } + + // check to make sure a valid storage object was specified + if ( !($storage instanceof CAS_PGTStorage_AbstractStorage) ) + throw new CAS_TypeMismatchException($storage, '$storage', 'CAS_PGTStorage_AbstractStorage object'); + + // store the PGTStorage object + $this->_pgt_storage = $storage; + } + + /** + * This method is used to tell phpCAS to store the response of the + * CAS server to PGT requests in a database. + * + * @param string|PDO $dsn_or_pdo a dsn string to use for creating a PDO + * object or a PDO object + * @param string $username the username to use when connecting to the + * database + * @param string $password the password to use when connecting to the + * database + * @param string $table the table to use for storing and retrieving + * PGTs + * @param string $driver_options any driver options to use when connecting + * to the database + * + * @return void + */ + public function setPGTStorageDb( + $dsn_or_pdo, $username='', $password='', $table='', $driver_options=null + ) { + // Sequence validation + $this->ensureIsProxy(); + + // Argument validation + if (!(is_object($dsn_or_pdo) && $dsn_or_pdo instanceof PDO) && !is_string($dsn_or_pdo)) + throw new CAS_TypeMismatchException($dsn_or_pdo, '$dsn_or_pdo', 'string or PDO object'); + if (gettype($username) != 'string') + throw new CAS_TypeMismatchException($username, '$username', 'string'); + if (gettype($password) != 'string') + throw new CAS_TypeMismatchException($password, '$password', 'string'); + if (gettype($table) != 'string') + throw new CAS_TypeMismatchException($table, '$password', 'string'); + + // create the storage object + $this->setPGTStorage( + new CAS_PGTStorage_Db( + $this, $dsn_or_pdo, $username, $password, $table, $driver_options + ) + ); + } + + /** + * This method is used to tell phpCAS to store the response of the + * CAS server to PGT requests onto the filesystem. + * + * @param string $path the path where the PGT's should be stored + * + * @return void + */ + public function setPGTStorageFile($path='') + { + // Sequence validation + $this->ensureIsProxy(); + + // Argument validation + if (gettype($path) != 'string') + throw new CAS_TypeMismatchException($path, '$path', 'string'); + + // create the storage object + $this->setPGTStorage(new CAS_PGTStorage_File($this, $path)); + } + + + // ######################################################################## + // PGT VALIDATION + // ######################################################################## + /** + * This method is used to validate a PGT; halt on failure. + * + * @param string &$validate_url the URL of the request to the CAS server. + * @param string $text_response the response of the CAS server, as is + * (XML text); result of + * CAS_Client::validateCAS10() or + * CAS_Client::validateCAS20(). + * @param DOMElement $tree_response the response of the CAS server, as a DOM XML + * tree; result of CAS_Client::validateCAS10() or CAS_Client::validateCAS20(). + * + * @return bool true when successfull and issue a CAS_AuthenticationException + * and false on an error + * + * @throws CAS_AuthenticationException + */ + private function _validatePGT(&$validate_url,$text_response,$tree_response) + { + phpCAS::traceBegin(); + if ( $tree_response->getElementsByTagName("proxyGrantingTicket")->length == 0) { + phpCAS::trace(' not found'); + // authentication succeded, but no PGT Iou was transmitted + throw new CAS_AuthenticationException( + $this, 'Ticket validated but no PGT Iou transmitted', + $validate_url, false/*$no_response*/, false/*$bad_response*/, + $text_response + ); + } else { + // PGT Iou transmitted, extract it + $pgt_iou = trim( + $tree_response->getElementsByTagName("proxyGrantingTicket")->item(0)->nodeValue + ); + if (preg_match('/^PGTIOU-[\.\-\w]+$/', $pgt_iou)) { + $pgt = $this->_loadPGT($pgt_iou); + if ( $pgt == false ) { + phpCAS::trace('could not load PGT'); + throw new CAS_AuthenticationException( + $this, + 'PGT Iou was transmitted but PGT could not be retrieved', + $validate_url, false/*$no_response*/, + false/*$bad_response*/, $text_response + ); + } + $this->_setPGT($pgt); + } else { + phpCAS::trace('PGTiou format error'); + throw new CAS_AuthenticationException( + $this, 'PGT Iou was transmitted but has wrong format', + $validate_url, false/*$no_response*/, false/*$bad_response*/, + $text_response + ); + } + } + phpCAS::traceEnd(true); + return true; + } + + // ######################################################################## + // PGT VALIDATION + // ######################################################################## + + /** + * This method is used to retrieve PT's from the CAS server thanks to a PGT. + * + * @param string $target_service the service to ask for with the PT. + * @param int &$err_code an error code (PHPCAS_SERVICE_OK on success). + * @param string &$err_msg an error message (empty on success). + * + * @return string|false a Proxy Ticket, or false on error. + */ + public function retrievePT($target_service,&$err_code,&$err_msg) + { + // Argument validation + if (gettype($target_service) != 'string') + throw new CAS_TypeMismatchException($target_service, '$target_service', 'string'); + + phpCAS::traceBegin(); + + // by default, $err_msg is set empty and $pt to true. On error, $pt is + // set to false and $err_msg to an error message. At the end, if $pt is false + // and $error_msg is still empty, it is set to 'invalid response' (the most + // commonly encountered error). + $err_msg = ''; + + // build the URL to retrieve the PT + $cas_url = $this->getServerProxyURL().'?targetService=' + .urlencode($target_service).'&pgt='.$this->_getPGT(); + + $headers = ''; + $cas_response = ''; + // open and read the URL + if ( !$this->_readURL($cas_url, $headers, $cas_response, $err_msg) ) { + phpCAS::trace( + 'could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')' + ); + $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE; + $err_msg = 'could not retrieve PT (no response from the CAS server)'; + phpCAS::traceEnd(false); + return false; + } + + $bad_response = false; + + // create new DOMDocument object + $dom = new DOMDocument(); + // Fix possible whitspace problems + $dom->preserveWhiteSpace = false; + // read the response of the CAS server into a DOM object + if ( !($dom->loadXML($cas_response))) { + phpCAS::trace('dom->loadXML() failed'); + // read failed + $bad_response = true; + } + + if ( !$bad_response ) { + // read the root node of the XML tree + if ( !($root = $dom->documentElement) ) { + phpCAS::trace('documentElement failed'); + // read failed + $bad_response = true; + } + } + + if ( !$bad_response ) { + // insure that tag name is 'serviceResponse' + if ( $root->localName != 'serviceResponse' ) { + phpCAS::trace('localName failed'); + // bad root node + $bad_response = true; + } + } + + if ( !$bad_response ) { + // look for a proxySuccess tag + if ( $root->getElementsByTagName("proxySuccess")->length != 0) { + $proxy_success_list = $root->getElementsByTagName("proxySuccess"); + + // authentication succeded, look for a proxyTicket tag + if ( $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->length != 0) { + $err_code = PHPCAS_SERVICE_OK; + $err_msg = ''; + $pt = trim( + $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->item(0)->nodeValue + ); + phpCAS::trace('original PT: '.trim($pt)); + phpCAS::traceEnd($pt); + return $pt; + } else { + phpCAS::trace(' was found, but not '); + } + } else if ($root->getElementsByTagName("proxyFailure")->length != 0) { + // look for a proxyFailure tag + $proxy_failure_list = $root->getElementsByTagName("proxyFailure"); + + // authentication failed, extract the error + $err_code = PHPCAS_SERVICE_PT_FAILURE; + $err_msg = 'PT retrieving failed (code=`' + .$proxy_failure_list->item(0)->getAttribute('code') + .'\', message=`' + .trim($proxy_failure_list->item(0)->nodeValue) + .'\')'; + phpCAS::traceEnd(false); + return false; + } else { + phpCAS::trace('neither nor found'); + } + } + + // at this step, we are sure that the response of the CAS server was + // illformed + $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE; + $err_msg = 'Invalid response from the CAS server (response=`' + .$cas_response.'\')'; + + phpCAS::traceEnd(false); + return false; + } + + /** @} */ + + // ######################################################################## + // READ CAS SERVER ANSWERS + // ######################################################################## + + /** + * @addtogroup internalMisc + * @{ + */ + + /** + * This method is used to acces a remote URL. + * + * @param string $url the URL to access. + * @param string &$headers an array containing the HTTP header lines of the + * response (an empty array on failure). + * @param string &$body the body of the response, as a string (empty on + * failure). + * @param string &$err_msg an error message, filled on failure. + * + * @return bool true on success, false otherwise (in this later case, $err_msg + * contains an error message). + */ + private function _readURL($url, &$headers, &$body, &$err_msg) + { + phpCAS::traceBegin(); + $className = $this->_requestImplementation; + $request = new $className(); + + if (count($this->_curl_options)) { + $request->setCurlOptions($this->_curl_options); + } + + $request->setUrl($url); + + if (empty($this->_cas_server_ca_cert) && !$this->_no_cas_server_validation) { + phpCAS::error( + 'one of the methods phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.' + ); + } + if ($this->_cas_server_ca_cert != '') { + $request->setSslCaCert( + $this->_cas_server_ca_cert, $this->_cas_server_cn_validate + ); + } + + // add extra stuff if SAML + if ($this->getServerVersion() == SAML_VERSION_1_1) { + $request->addHeader("soapaction: http://www.oasis-open.org/committees/security"); + $request->addHeader("cache-control: no-cache"); + $request->addHeader("pragma: no-cache"); + $request->addHeader("accept: text/xml"); + $request->addHeader("connection: keep-alive"); + $request->addHeader("content-type: text/xml"); + $request->makePost(); + $request->setPostBody($this->_buildSAMLPayload()); + } + + if ($request->send()) { + $headers = $request->getResponseHeaders(); + $body = $request->getResponseBody(); + $err_msg = ''; + phpCAS::traceEnd(true); + return true; + } else { + $headers = ''; + $body = ''; + $err_msg = $request->getErrorMessage(); + phpCAS::traceEnd(false); + return false; + } + } + + /** + * This method is used to build the SAML POST body sent to /samlValidate URL. + * + * @return string the SOAP-encased SAMLP artifact (the ticket). + */ + private function _buildSAMLPayload() + { + phpCAS::traceBegin(); + + //get the ticket + $sa = urlencode($this->getTicket()); + + $body = SAML_SOAP_ENV.SAML_SOAP_BODY.SAMLP_REQUEST + .SAML_ASSERTION_ARTIFACT.$sa.SAML_ASSERTION_ARTIFACT_CLOSE + .SAMLP_REQUEST_CLOSE.SAML_SOAP_BODY_CLOSE.SAML_SOAP_ENV_CLOSE; + + phpCAS::traceEnd($body); + return ($body); + } + + /** @} **/ + + // ######################################################################## + // ACCESS TO EXTERNAL SERVICES + // ######################################################################## + + /** + * @addtogroup internalProxyServices + * @{ + */ + + + /** + * Answer a proxy-authenticated service handler. + * + * @param string $type The service type. One of: + * PHPCAS_PROXIED_SERVICE_HTTP_GET, PHPCAS_PROXIED_SERVICE_HTTP_POST, + * PHPCAS_PROXIED_SERVICE_IMAP + * + * @return CAS_ProxiedService + * @throws InvalidArgumentException If the service type is unknown. + */ + public function getProxiedService ($type) + { + // Sequence validation + $this->ensureIsProxy(); + $this->ensureAuthenticationCallSuccessful(); + + // Argument validation + if (gettype($type) != 'string') + throw new CAS_TypeMismatchException($type, '$type', 'string'); + + switch ($type) { + case PHPCAS_PROXIED_SERVICE_HTTP_GET: + case PHPCAS_PROXIED_SERVICE_HTTP_POST: + $requestClass = $this->_requestImplementation; + $request = new $requestClass(); + if (count($this->_curl_options)) { + $request->setCurlOptions($this->_curl_options); + } + $proxiedService = new $type($request, $this->_serviceCookieJar); + if ($proxiedService instanceof CAS_ProxiedService_Testable) { + $proxiedService->setCasClient($this); + } + return $proxiedService; + case PHPCAS_PROXIED_SERVICE_IMAP; + $proxiedService = new CAS_ProxiedService_Imap($this->_getUser()); + if ($proxiedService instanceof CAS_ProxiedService_Testable) { + $proxiedService->setCasClient($this); + } + return $proxiedService; + default: + throw new CAS_InvalidArgumentException( + "Unknown proxied-service type, $type." + ); + } + } + + /** + * Initialize a proxied-service handler with the proxy-ticket it should use. + * + * @param CAS_ProxiedService $proxiedService service handler + * + * @return void + * + * @throws CAS_ProxyTicketException If there is a proxy-ticket failure. + * The code of the Exception will be one of: + * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_FAILURE + * @throws CAS_ProxiedService_Exception If there is a failure getting the + * url from the proxied service. + */ + public function initializeProxiedService (CAS_ProxiedService $proxiedService) + { + // Sequence validation + $this->ensureIsProxy(); + $this->ensureAuthenticationCallSuccessful(); + + $url = $proxiedService->getServiceUrl(); + if (!is_string($url)) { + throw new CAS_ProxiedService_Exception( + "Proxied Service ".get_class($proxiedService) + ."->getServiceUrl() should have returned a string, returned a " + .gettype($url)." instead." + ); + } + $pt = $this->retrievePT($url, $err_code, $err_msg); + if (!$pt) { + throw new CAS_ProxyTicketException($err_msg, $err_code); + } + $proxiedService->setProxyTicket($pt); + } + + /** + * This method is used to access an HTTP[S] service. + * + * @param string $url the service to access. + * @param int &$err_code an error code Possible values are + * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE, + * PHPCAS_SERVICE_NOT_AVAILABLE. + * @param string &$output the output of the service (also used to give an error + * message on failure). + * + * @return bool true on success, false otherwise (in this later case, $err_code + * gives the reason why it failed and $output contains an error message). + */ + public function serviceWeb($url,&$err_code,&$output) + { + // Sequence validation + $this->ensureIsProxy(); + $this->ensureAuthenticationCallSuccessful(); + + // Argument validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + try { + $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_GET); + $service->setUrl($url); + $service->send(); + $output = $service->getResponseBody(); + $err_code = PHPCAS_SERVICE_OK; + return true; + } catch (CAS_ProxyTicketException $e) { + $err_code = $e->getCode(); + $output = $e->getMessage(); + return false; + } catch (CAS_ProxiedService_Exception $e) { + $lang = $this->getLangObj(); + $output = sprintf( + $lang->getServiceUnavailable(), $url, $e->getMessage() + ); + $err_code = PHPCAS_SERVICE_NOT_AVAILABLE; + return false; + } + } + + /** + * This method is used to access an IMAP/POP3/NNTP service. + * + * @param string $url a string giving the URL of the service, including + * the mailing box for IMAP URLs, as accepted by imap_open(). + * @param string $serviceUrl a string giving for CAS retrieve Proxy ticket + * @param string $flags options given to imap_open(). + * @param int &$err_code an error code Possible values are + * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE, + * PHPCAS_SERVICE_NOT_AVAILABLE. + * @param string &$err_msg an error message on failure + * @param string &$pt the Proxy Ticket (PT) retrieved from the CAS + * server to access the URL on success, false on error). + * + * @return object|false an IMAP stream on success, false otherwise (in this later + * case, $err_code gives the reason why it failed and $err_msg contains an + * error message). + */ + public function serviceMail($url,$serviceUrl,$flags,&$err_code,&$err_msg,&$pt) + { + // Sequence validation + $this->ensureIsProxy(); + $this->ensureAuthenticationCallSuccessful(); + + // Argument validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + if (gettype($serviceUrl) != 'string') + throw new CAS_TypeMismatchException($serviceUrl, '$serviceUrl', 'string'); + if (gettype($flags) != 'integer') + throw new CAS_TypeMismatchException($flags, '$flags', 'string'); + + try { + $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_IMAP); + $service->setServiceUrl($serviceUrl); + $service->setMailbox($url); + $service->setOptions($flags); + + $stream = $service->open(); + $err_code = PHPCAS_SERVICE_OK; + $pt = $service->getImapProxyTicket(); + return $stream; + } catch (CAS_ProxyTicketException $e) { + $err_msg = $e->getMessage(); + $err_code = $e->getCode(); + $pt = false; + return false; + } catch (CAS_ProxiedService_Exception $e) { + $lang = $this->getLangObj(); + $err_msg = sprintf( + $lang->getServiceUnavailable(), + $url, + $e->getMessage() + ); + $err_code = PHPCAS_SERVICE_NOT_AVAILABLE; + $pt = false; + return false; + } + } + + /** @} **/ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX PROXIED CLIENT FEATURES (CAS 2.0) XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + // ######################################################################## + // PT + // ######################################################################## + /** + * @addtogroup internalService + * @{ + */ + + /** + * This array will store a list of proxies in front of this application. This + * property will only be populated if this script is being proxied rather than + * accessed directly. + * + * It is set in CAS_Client::validateCAS20() and can be read by + * CAS_Client::getProxies() + * + * @access private + */ + private $_proxies = array(); + + /** + * Answer an array of proxies that are sitting in front of this application. + * + * This method will only return a non-empty array if we have received and + * validated a Proxy Ticket. + * + * @return array + * @access public + */ + public function getProxies() + { + return $this->_proxies; + } + + /** + * Set the Proxy array, probably from persistant storage. + * + * @param array $proxies An array of proxies + * + * @return void + * @access private + */ + private function _setProxies($proxies) + { + $this->_proxies = $proxies; + if (!empty($proxies)) { + // For proxy-authenticated requests people are not viewing the URL + // directly since the client is another application making a + // web-service call. + // Because of this, stripping the ticket from the URL is unnecessary + // and causes another web-service request to be performed. Additionally, + // if session handling on either the client or the server malfunctions + // then the subsequent request will not complete successfully. + $this->setNoClearTicketsFromUrl(); + } + } + + /** + * A container of patterns to be allowed as proxies in front of the cas client. + * + * @var CAS_ProxyChain_AllowedList + */ + private $_allowed_proxy_chains; + + /** + * Answer the CAS_ProxyChain_AllowedList object for this client. + * + * @return CAS_ProxyChain_AllowedList + */ + public function getAllowedProxyChains () + { + if (empty($this->_allowed_proxy_chains)) { + $this->_allowed_proxy_chains = new CAS_ProxyChain_AllowedList(); + } + return $this->_allowed_proxy_chains; + } + + /** @} */ + // ######################################################################## + // PT VALIDATION + // ######################################################################## + /** + * @addtogroup internalProxied + * @{ + */ + + /** + * This method is used to validate a cas 2.0 ST or PT; halt on failure + * Used for all CAS 2.0 validations + * + * @param string &$validate_url the url of the reponse + * @param string &$text_response the text of the repsones + * @param DOMElement &$tree_response the domxml tree of the respones + * @param bool $renew true to force the authentication with the CAS server + * + * @return bool true when successfull and issue a CAS_AuthenticationException + * and false on an error + * + * @throws CAS_AuthenticationException + */ + public function validateCAS20(&$validate_url,&$text_response,&$tree_response, $renew=false) + { + phpCAS::traceBegin(); + phpCAS::trace($text_response); + // build the URL to validate the ticket + if ($this->getAllowedProxyChains()->isProxyingAllowed()) { + $validate_url = $this->getServerProxyValidateURL().'&ticket=' + .urlencode($this->getTicket()); + } else { + $validate_url = $this->getServerServiceValidateURL().'&ticket=' + .urlencode($this->getTicket()); + } + + if ( $this->isProxy() ) { + // pass the callback url for CAS proxies + $validate_url .= '&pgtUrl='.urlencode($this->_getCallbackURL()); + } + + if ( $renew ) { + // pass the renew + $validate_url .= '&renew=true'; + } + + // open and read the URL + if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) { + phpCAS::trace( + 'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')' + ); + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + true/*$no_response*/ + ); + } + + // create new DOMDocument object + $dom = new DOMDocument(); + // Fix possible whitspace problems + $dom->preserveWhiteSpace = false; + // CAS servers should only return data in utf-8 + $dom->encoding = "utf-8"; + // read the response of the CAS server into a DOMDocument object + if ( !($dom->loadXML($text_response))) { + // read failed + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, $text_response + ); + } else if ( !($tree_response = $dom->documentElement) ) { + // read the root node of the XML tree + // read failed + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, $text_response + ); + } else if ($tree_response->localName != 'serviceResponse') { + // insure that tag name is 'serviceResponse' + // bad root node + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, $text_response + ); + } else if ( $tree_response->getElementsByTagName("authenticationFailure")->length != 0) { + // authentication failed, extract the error code and message and throw exception + $auth_fail_list = $tree_response + ->getElementsByTagName("authenticationFailure"); + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, false/*$bad_response*/, + $text_response, + $auth_fail_list->item(0)->getAttribute('code')/*$err_code*/, + trim($auth_fail_list->item(0)->nodeValue)/*$err_msg*/ + ); + } else if ($tree_response->getElementsByTagName("authenticationSuccess")->length != 0) { + // authentication succeded, extract the user name + $success_elements = $tree_response + ->getElementsByTagName("authenticationSuccess"); + if ( $success_elements->item(0)->getElementsByTagName("user")->length == 0) { + // no user specified => error + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, $text_response + ); + } else { + $this->_setUser( + trim( + $success_elements->item(0)->getElementsByTagName("user")->item(0)->nodeValue + ) + ); + $this->_readExtraAttributesCas20($success_elements); + // Store the proxies we are sitting behind for authorization checking + $proxyList = array(); + if ( sizeof($arr = $success_elements->item(0)->getElementsByTagName("proxy")) > 0) { + foreach ($arr as $proxyElem) { + phpCAS::trace("Found Proxy: ".$proxyElem->nodeValue); + $proxyList[] = trim($proxyElem->nodeValue); + } + $this->_setProxies($proxyList); + phpCAS::trace("Storing Proxy List"); + } + // Check if the proxies in front of us are allowed + if (!$this->getAllowedProxyChains()->isProxyListAllowed($proxyList)) { + throw new CAS_AuthenticationException( + $this, 'Proxy not allowed', $validate_url, + false/*$no_response*/, true/*$bad_response*/, + $text_response + ); + } else { + $result = true; + } + } + } else { + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, + $text_response + ); + } + + $this->_renameSession($this->getTicket()); + + // at this step, Ticket has been validated and $this->_user has been set, + + phpCAS::traceEnd($result); + return $result; + } + + /** + * This method recursively parses the attribute XML. + * It also collapses name-value pairs into a single + * array entry. It parses all common formats of + * attributes and well formed XML files. + * + * @param string $root the DOM root element to be parsed + * @param string $namespace namespace of the elements + * + * @return an array of the parsed XML elements + * + * Formats tested: + * + * "Jasig Style" Attributes: + * + * + * + * jsmith + * + * RubyCAS + * Smith + * John + * CN=Staff,OU=Groups,DC=example,DC=edu + * CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu + * + * PGTIOU-84678-8a9d2sfa23casd + * + * + * + * "Jasig Style" Attributes (longer version): + * + * + * + * jsmith + * + * + * surname + * Smith + * + * + * givenName + * John + * + * + * memberOf + * ['CN=Staff,OU=Groups,DC=example,DC=edu', 'CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu'] + * + * + * PGTIOU-84678-8a9d2sfa23casd + * + * + * + * "RubyCAS Style" attributes + * + * + * + * jsmith + * + * RubyCAS + * Smith + * John + * CN=Staff,OU=Groups,DC=example,DC=edu + * CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu + * + * PGTIOU-84678-8a9d2sfa23casd + * + * + * + * "Name-Value" attributes. + * + * Attribute format from these mailing list thread: + * http://jasig.275507.n4.nabble.com/CAS-attributes-and-how-they-appear-in-the-CAS-response-td264272.html + * Note: This is a less widely used format, but in use by at least two institutions. + * + * + * + * jsmith + * + * + * + * + * + * + * + * PGTIOU-84678-8a9d2sfa23casd + * + * + * + * result: + * + * Array ( + * [surname] => Smith + * [givenName] => John + * [memberOf] => Array ( + * [0] => CN=Staff, OU=Groups, DC=example, DC=edu + * [1] => CN=Spanish Department, OU=Departments, OU=Groups, DC=example, DC=edu + * ) + * ) + */ + private function _xml_to_array($root, $namespace = "cas") + { + $result = array(); + if ($root->hasAttributes()) { + $attrs = $root->attributes; + $pair = array(); + foreach ($attrs as $attr) { + if ($attr->name === "name") { + $pair['name'] = $attr->value; + } elseif ($attr->name === "value") { + $pair['value'] = $attr->value; + } else { + $result[$attr->name] = $attr->value; + } + if (array_key_exists('name', $pair) && array_key_exists('value', $pair)) { + $result[$pair['name']] = $pair['value']; + } + } + } + if ($root->hasChildNodes()) { + $children = $root->childNodes; + if ($children->length == 1) { + $child = $children->item(0); + if ($child->nodeType == XML_TEXT_NODE) { + $result['_value'] = $child->nodeValue; + return (count($result) == 1) ? $result['_value'] : $result; + } + } + $groups = array(); + foreach ($children as $child) { + $child_nodeName = str_ireplace($namespace . ":", "", $child->nodeName); + if (in_array($child_nodeName, array("user", "proxies", "proxyGrantingTicket"))) { + continue; + } + if (!isset($result[$child_nodeName])) { + $res = $this->_xml_to_array($child, $namespace); + if (!empty($res)) { + $result[$child_nodeName] = $this->_xml_to_array($child, $namespace); + } + } else { + if (!isset($groups[$child_nodeName])) { + $result[$child_nodeName] = array($result[$child_nodeName]); + $groups[$child_nodeName] = 1; + } + $result[$child_nodeName][] = $this->_xml_to_array($child, $namespace); + } + } + } + return $result; + } + + /** + * This method parses a "JSON-like array" of strings + * into an array of strings + * + * @param string $json_value the json-like string: + * e.g.: + * ['CN=Staff,OU=Groups,DC=example,DC=edu', 'CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu'] + * + * @return array of strings Description + * e.g.: + * Array ( + * [0] => CN=Staff,OU=Groups,DC=example,DC=edu + * [1] => CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu + * ) + */ + private function _parse_json_like_array_value($json_value) + { + $parts = explode(",", trim($json_value, "[]")); + $out = array(); + $quote = ''; + foreach ($parts as $part) { + $part = trim($part); + if ($quote === '') { + $value = ""; + if ($this->_startsWith($part, '\'')) { + $quote = '\''; + } elseif ($this->_startsWith($part, '"')) { + $quote = '"'; + } else { + $out[] = $part; + } + $part = ltrim($part, $quote); + } + if ($quote !== '') { + $value .= $part; + if ($this->_endsWith($part, $quote)) { + $out[] = rtrim($value, $quote); + $quote = ''; + } else { + $value .= ", "; + }; + } + } + return $out; + } + + /** + * This method recursively removes unneccessary hirarchy levels in array-trees. + * into an array of strings + * + * @param array $arr the array to flatten + * e.g.: + * Array ( + * [attributes] => Array ( + * [attribute] => Array ( + * [0] => Array ( + * [name] => surname + * [value] => Smith + * ) + * [1] => Array ( + * [name] => givenName + * [value] => John + * ) + * [2] => Array ( + * [name] => memberOf + * [value] => ['CN=Staff,OU=Groups,DC=example,DC=edu', 'CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu'] + * ) + * ) + * ) + * ) + * + * @return array the flattened array + * e.g.: + * Array ( + * [attribute] => Array ( + * [surname] => Smith + * [givenName] => John + * [memberOf] => Array ( + * [0] => CN=Staff, OU=Groups, DC=example, DC=edu + * [1] => CN=Spanish Department, OU=Departments, OU=Groups, DC=example, DC=edu + * ) + * ) + * ) + */ + private function _flatten_array($arr) + { + if (!is_array($arr)) { + if ($this->_startsWith($arr, '[') && $this->_endsWith($arr, ']')) { + return $this->_parse_json_like_array_value($arr); + } else { + return $arr; + } + } + $out = array(); + foreach ($arr as $key => $val) { + if (!is_array($val)) { + $out[$key] = $val; + } else { + switch (count($val)) { + case 1 : { + $key = key($val); + if (array_key_exists($key, $out)) { + $value = $out[$key]; + if (!is_array($value)) { + $out[$key] = array(); + $out[$key][] = $value; + } + $out[$key][] = $this->_flatten_array($val[$key]); + } else { + $out[$key] = $this->_flatten_array($val[$key]); + }; + break; + }; + case 2 : { + if (array_key_exists("name", $val) && array_key_exists("value", $val)) { + $key = $val['name']; + if (array_key_exists($key, $out)) { + $value = $out[$key]; + if (!is_array($value)) { + $out[$key] = array(); + $out[$key][] = $value; + } + $out[$key][] = $this->_flatten_array($val['value']); + } else { + $out[$key] = $this->_flatten_array($val['value']); + }; + } else { + $out[$key] = $this->_flatten_array($val); + } + break; + }; + default: { + $out[$key] = $this->_flatten_array($val); + } + } + } + } + return $out; + } + + /** + * This method will parse the DOM and pull out the attributes from the XML + * payload and put them into an array, then put the array into the session. + * + * @param DOMNodeList $success_elements payload of the response + * + * @return bool true when successfull, halt otherwise by calling + * CAS_Client::_authError(). + */ + private function _readExtraAttributesCas20($success_elements) + { + phpCAS::traceBegin(); + + $extra_attributes = array(); + if ($this->_casAttributeParserCallbackFunction !== null + && is_callable($this->_casAttributeParserCallbackFunction) + ) { + array_unshift($this->_casAttributeParserCallbackArgs, $success_elements->item(0)); + phpCAS :: trace("Calling attritubeParser callback"); + $extra_attributes = call_user_func_array( + $this->_casAttributeParserCallbackFunction, + $this->_casAttributeParserCallbackArgs + ); + } else { + phpCAS :: trace("Parse extra attributes: "); + $attributes = $this->_xml_to_array($success_elements->item(0)); + phpCAS :: trace(print_r($attributes,true). "\nFLATTEN Array: "); + $extra_attributes = $this->_flatten_array($attributes); + phpCAS :: trace(print_r($extra_attributes, true)."\nFILTER : "); + if (array_key_exists("attribute", $extra_attributes)) { + $extra_attributes = $extra_attributes["attribute"]; + } elseif (array_key_exists("attributes", $extra_attributes)) { + $extra_attributes = $extra_attributes["attributes"]; + }; + phpCAS :: trace(print_r($extra_attributes, true)."return"); + } + $this->setAttributes($extra_attributes); + phpCAS::traceEnd(); + return true; + } + + /** + * Add an attribute value to an array of attributes. + * + * @param array &$attributeArray reference to array + * @param string $name name of attribute + * @param string $value value of attribute + * + * @return void + */ + private function _addAttributeToArray(array &$attributeArray, $name, $value) + { + // If multiple attributes exist, add as an array value + if (isset($attributeArray[$name])) { + // Initialize the array with the existing value + if (!is_array($attributeArray[$name])) { + $existingValue = $attributeArray[$name]; + $attributeArray[$name] = array($existingValue); + } + + $attributeArray[$name][] = trim($value); + } else { + $attributeArray[$name] = trim($value); + } + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX MISC XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + /** + * @addtogroup internalMisc + * @{ + */ + + // ######################################################################## + // URL + // ######################################################################## + /** + * the URL of the current request (without any ticket CGI parameter). Written + * and read by CAS_Client::getURL(). + * + * @hideinitializer + */ + private $_url = ''; + + + /** + * This method sets the URL of the current request + * + * @param string $url url to set for service + * + * @return void + */ + public function setURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + $this->_url = $url; + } + + /** + * This method returns the URL of the current request (without any ticket + * CGI parameter). + * + * @return string The URL + */ + public function getURL() + { + phpCAS::traceBegin(); + // the URL is built when needed only + if ( empty($this->_url) ) { + // remove the ticket if present in the URL + $final_uri = ($this->_isHttps()) ? 'https' : 'http'; + $final_uri .= '://'; + + $final_uri .= $this->_getClientUrl(); + $request_uri = explode('?', $_SERVER['REQUEST_URI'], 2); + $final_uri .= $request_uri[0]; + + if (isset($request_uri[1]) && $request_uri[1]) { + $query_string= $this->_removeParameterFromQueryString('ticket', $request_uri[1]); + + // If the query string still has anything left, + // append it to the final URI + if ($query_string !== '') { + $final_uri .= "?$query_string"; + } + } + + phpCAS::trace("Final URI: $final_uri"); + $this->setURL($final_uri); + } + phpCAS::traceEnd($this->_url); + return $this->_url; + } + + /** + * This method sets the base URL of the CAS server. + * + * @param string $url the base URL + * + * @return string base url + */ + public function setBaseURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['base_url'] = $url; + } + + + /** + * Try to figure out the phpCAS client URL with possible Proxys / Ports etc. + * + * @return string Server URL with domain:port + */ + private function _getClientUrl() + { + if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) { + // explode the host list separated by comma and use the first host + $hosts = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']); + // see rfc7239#5.3 and rfc7230#2.7.1: port is in HTTP_X_FORWARDED_HOST if non default + return $hosts[0]; + } else if (!empty($_SERVER['HTTP_X_FORWARDED_SERVER'])) { + $server_url = $_SERVER['HTTP_X_FORWARDED_SERVER']; + } else { + if (empty($_SERVER['SERVER_NAME'])) { + $server_url = $_SERVER['HTTP_HOST']; + } else { + $server_url = $_SERVER['SERVER_NAME']; + } + } + if (!strpos($server_url, ':')) { + if (empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { + $server_port = $_SERVER['SERVER_PORT']; + } else { + $ports = explode(',', $_SERVER['HTTP_X_FORWARDED_PORT']); + $server_port = $ports[0]; + } + + if ( ($this->_isHttps() && $server_port!=443) + || (!$this->_isHttps() && $server_port!=80) + ) { + $server_url .= ':'; + $server_url .= $server_port; + } + } + return $server_url; + } + + /** + * This method checks to see if the request is secured via HTTPS + * + * @return bool true if https, false otherwise + */ + private function _isHttps() + { + if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) { + return ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https'); + } elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTOCOL'])) { + return ($_SERVER['HTTP_X_FORWARDED_PROTOCOL'] === 'https'); + } elseif ( isset($_SERVER['HTTPS']) + && !empty($_SERVER['HTTPS']) + && strcasecmp($_SERVER['HTTPS'], 'off') !== 0 + ) { + return true; + } + return false; + + } + + /** + * Removes a parameter from a query string + * + * @param string $parameterName name of parameter + * @param string $queryString query string + * + * @return string new query string + * + * @link http://stackoverflow.com/questions/1842681/regular-expression-to-remove-one-parameter-from-query-string + */ + private function _removeParameterFromQueryString($parameterName, $queryString) + { + $parameterName = preg_quote($parameterName); + return preg_replace( + "/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/", + '', $queryString + ); + } + + /** + * This method is used to append query parameters to an url. Since the url + * might already contain parameter it has to be detected and to build a proper + * URL + * + * @param string $url base url to add the query params to + * @param string $query params in query form with & separated + * + * @return string url with query params + */ + private function _buildQueryUrl($url, $query) + { + $url .= (strstr($url, '?') === false) ? '?' : '&'; + $url .= $query; + return $url; + } + + /** + * This method tests if a string starts with a given character. + * + * @param string $text text to test + * @param string $char character to test for + * + * @return bool true if the $text starts with $char + */ + private function _startsWith($text, $char) + { + return (strpos($text, $char) === 0); + } + + /** + * This method tests if a string ends with a given character + * + * @param string $text text to test + * @param string $char character to test for + * + * @return bool true if the $text ends with $char + */ + private function _endsWith($text, $char) + { + return (strpos(strrev($text), $char) === 0); + } + + /** + * Answer a valid session-id given a CAS ticket. + * + * The output must be deterministic to allow single-log-out when presented with + * the ticket to log-out. + * + * + * @param string $ticket name of the ticket + * + * @return string + */ + private function _sessionIdForTicket($ticket) + { + // Hash the ticket to ensure that the value meets the PHP 7.1 requirement + // that session-ids have a length between 22 and 256 characters. + return hash('sha256', $this->_sessionIdSalt . $ticket); + } + + /** + * Set a salt/seed for the session-id hash to make it harder to guess. + * + * @var string $_sessionIdSalt + */ + private $_sessionIdSalt = ''; + + /** + * Set a salt/seed for the session-id hash to make it harder to guess. + * + * @param string $salt + * + * @return void + */ + public function setSessionIdSalt($salt) { + $this->_sessionIdSalt = (string)$salt; + } + + // ######################################################################## + // AUTHENTICATION ERROR HANDLING + // ######################################################################## + /** + * This method is used to print the HTML output when the user was not + * authenticated. + * + * @param string $failure the failure that occured + * @param string $cas_url the URL the CAS server was asked for + * @param bool $no_response the response from the CAS server (other + * parameters are ignored if true) + * @param bool $bad_response bad response from the CAS server ($err_code + * and $err_msg ignored if true) + * @param string $cas_response the response of the CAS server + * @param int $err_code the error code given by the CAS server + * @param string $err_msg the error message given by the CAS server + * + * @return void + */ + private function _authError( + $failure, + $cas_url, + $no_response=false, + $bad_response=false, + $cas_response='', + $err_code=-1, + $err_msg='' + ) { + phpCAS::traceBegin(); + $lang = $this->getLangObj(); + $this->printHTMLHeader($lang->getAuthenticationFailed()); + $this->printf( + $lang->getYouWereNotAuthenticated(), htmlentities($this->getURL()), + isset($_SERVER['SERVER_ADMIN']) ? $_SERVER['SERVER_ADMIN']:'' + ); + phpCAS::trace('CAS URL: '.$cas_url); + phpCAS::trace('Authentication failure: '.$failure); + if ( $no_response ) { + phpCAS::trace('Reason: no response from the CAS server'); + } else { + if ( $bad_response ) { + phpCAS::trace('Reason: bad response from the CAS server'); + } else { + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + phpCAS::trace('Reason: CAS error'); + break; + case CAS_VERSION_2_0: + case CAS_VERSION_3_0: + if ( $err_code === -1 ) { + phpCAS::trace('Reason: no CAS error'); + } else { + phpCAS::trace( + 'Reason: ['.$err_code.'] CAS error: '.$err_msg + ); + } + break; + } + } + phpCAS::trace('CAS response: '.$cas_response); + } + $this->printHTMLFooter(); + phpCAS::traceExit(); + throw new CAS_GracefullTerminationException(); + } + + // ######################################################################## + // PGTIOU/PGTID and logoutRequest rebroadcasting + // ######################################################################## + + /** + * Boolean of whether to rebroadcast pgtIou/pgtId and logoutRequest, and + * array of the nodes. + */ + private $_rebroadcast = false; + private $_rebroadcast_nodes = array(); + + /** + * Constants used for determining rebroadcast node type. + */ + const HOSTNAME = 0; + const IP = 1; + + /** + * Determine the node type from the URL. + * + * @param String $nodeURL The node URL. + * + * @return int hostname + * + */ + private function _getNodeType($nodeURL) + { + phpCAS::traceBegin(); + if (preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $nodeURL)) { + phpCAS::traceEnd(self::IP); + return self::IP; + } else { + phpCAS::traceEnd(self::HOSTNAME); + return self::HOSTNAME; + } + } + + /** + * Store the rebroadcast node for pgtIou/pgtId and logout requests. + * + * @param string $rebroadcastNodeUrl The rebroadcast node URL. + * + * @return void + */ + public function addRebroadcastNode($rebroadcastNodeUrl) + { + // Argument validation + if ( !(bool)preg_match("/^(http|https):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i", $rebroadcastNodeUrl)) + throw new CAS_TypeMismatchException($rebroadcastNodeUrl, '$rebroadcastNodeUrl', 'url'); + + // Store the rebroadcast node and set flag + $this->_rebroadcast = true; + $this->_rebroadcast_nodes[] = $rebroadcastNodeUrl; + } + + /** + * An array to store extra rebroadcast curl options. + */ + private $_rebroadcast_headers = array(); + + /** + * This method is used to add header parameters when rebroadcasting + * pgtIou/pgtId or logoutRequest. + * + * @param string $header Header to send when rebroadcasting. + * + * @return void + */ + public function addRebroadcastHeader($header) + { + if (gettype($header) != 'string') + throw new CAS_TypeMismatchException($header, '$header', 'string'); + + $this->_rebroadcast_headers[] = $header; + } + + /** + * Constants used for determining rebroadcast type (logout or pgtIou/pgtId). + */ + const LOGOUT = 0; + const PGTIOU = 1; + + /** + * This method rebroadcasts logout/pgtIou requests. Can be LOGOUT,PGTIOU + * + * @param int $type type of rebroadcasting. + * + * @return void + */ + private function _rebroadcast($type) + { + phpCAS::traceBegin(); + + $rebroadcast_curl_options = array( + CURLOPT_FAILONERROR => 1, + CURLOPT_FOLLOWLOCATION => 1, + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_CONNECTTIMEOUT => 1, + CURLOPT_TIMEOUT => 4); + + // Try to determine the IP address of the server + if (!empty($_SERVER['SERVER_ADDR'])) { + $ip = $_SERVER['SERVER_ADDR']; + } else if (!empty($_SERVER['LOCAL_ADDR'])) { + // IIS 7 + $ip = $_SERVER['LOCAL_ADDR']; + } + // Try to determine the DNS name of the server + if (!empty($ip)) { + $dns = gethostbyaddr($ip); + } + $multiClassName = 'CAS_Request_CurlMultiRequest'; + $multiRequest = new $multiClassName(); + + for ($i = 0; $i < sizeof($this->_rebroadcast_nodes); $i++) { + if ((($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::HOSTNAME) && !empty($dns) && (stripos($this->_rebroadcast_nodes[$i], $dns) === false)) + || (($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::IP) && !empty($ip) && (stripos($this->_rebroadcast_nodes[$i], $ip) === false)) + ) { + phpCAS::trace( + 'Rebroadcast target URL: '.$this->_rebroadcast_nodes[$i] + .$_SERVER['REQUEST_URI'] + ); + $className = $this->_requestImplementation; + $request = new $className(); + + $url = $this->_rebroadcast_nodes[$i].$_SERVER['REQUEST_URI']; + $request->setUrl($url); + + if (count($this->_rebroadcast_headers)) { + $request->addHeaders($this->_rebroadcast_headers); + } + + $request->makePost(); + if ($type == self::LOGOUT) { + // Logout request + $request->setPostBody( + 'rebroadcast=false&logoutRequest='.$_POST['logoutRequest'] + ); + } else if ($type == self::PGTIOU) { + // pgtIou/pgtId rebroadcast + $request->setPostBody('rebroadcast=false'); + } + + $request->setCurlOptions($rebroadcast_curl_options); + + $multiRequest->addRequest($request); + } else { + phpCAS::trace( + 'Rebroadcast not sent to self: ' + .$this->_rebroadcast_nodes[$i].' == '.(!empty($ip)?$ip:'') + .'/'.(!empty($dns)?$dns:'') + ); + } + } + // We need at least 1 request + if ($multiRequest->getNumRequests() > 0) { + $multiRequest->send(); + } + phpCAS::traceEnd(); + } + + /** @} */ +} diff --git a/lib/apereo/phpcas/source/CAS/CookieJar.php b/lib/apereo/phpcas/source/CAS/CookieJar.php new file mode 100644 index 0000000000..b2439373ad --- /dev/null +++ b/lib/apereo/phpcas/source/CAS/CookieJar.php @@ -0,0 +1,385 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class provides access to service cookies and handles parsing of response + * headers to pull out cookie values. + * + * @class CAS_CookieJar + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_CookieJar +{ + + private $_cookies; + + /** + * Create a new cookie jar by passing it a reference to an array in which it + * should store cookies. + * + * @param array &$storageArray Array to store cookies + * + * @return void + */ + public function __construct (array &$storageArray) + { + $this->_cookies =& $storageArray; + } + + /** + * Store cookies for a web service request. + * Cookie storage is based on RFC 2965: http://www.ietf.org/rfc/rfc2965.txt + * + * @param string $request_url The URL that generated the response headers. + * @param array $response_headers An array of the HTTP response header strings. + * + * @return void + * + * @access private + */ + public function storeCookies ($request_url, $response_headers) + { + $urlParts = parse_url($request_url); + $defaultDomain = $urlParts['host']; + + $cookies = $this->parseCookieHeaders($response_headers, $defaultDomain); + + foreach ($cookies as $cookie) { + // Enforce the same-origin policy by verifying that the cookie + // would match the url that is setting it + if (!$this->cookieMatchesTarget($cookie, $urlParts)) { + continue; + } + + // store the cookie + $this->storeCookie($cookie); + + phpCAS::trace($cookie['name'].' -> '.$cookie['value']); + } + } + + /** + * Retrieve cookies applicable for a web service request. + * Cookie applicability is based on RFC 2965: http://www.ietf.org/rfc/rfc2965.txt + * + * @param string $request_url The url that the cookies will be for. + * + * @return array An array containing cookies. E.g. array('name' => 'val'); + * + * @access private + */ + public function getCookies ($request_url) + { + if (!count($this->_cookies)) { + return array(); + } + + // If our request URL can't be parsed, no cookies apply. + $target = parse_url($request_url); + if ($target === false) { + return array(); + } + + $this->expireCookies(); + + $matching_cookies = array(); + foreach ($this->_cookies as $key => $cookie) { + if ($this->cookieMatchesTarget($cookie, $target)) { + $matching_cookies[$cookie['name']] = $cookie['value']; + } + } + return $matching_cookies; + } + + + /** + * Parse Cookies without PECL + * From the comments in http://php.net/manual/en/function.http-parse-cookie.php + * + * @param array $header array of header lines. + * @param string $defaultDomain The domain to use if none is specified in + * the cookie. + * + * @return array of cookies + */ + protected function parseCookieHeaders( $header, $defaultDomain ) + { + phpCAS::traceBegin(); + $cookies = array(); + foreach ( $header as $line ) { + if ( preg_match('/^Set-Cookie2?: /i', $line)) { + $cookies[] = $this->parseCookieHeader($line, $defaultDomain); + } + } + + phpCAS::traceEnd($cookies); + return $cookies; + } + + /** + * Parse a single cookie header line. + * + * Based on RFC2965 http://www.ietf.org/rfc/rfc2965.txt + * + * @param string $line The header line. + * @param string $defaultDomain The domain to use if none is specified in + * the cookie. + * + * @return array + */ + protected function parseCookieHeader ($line, $defaultDomain) + { + if (!$defaultDomain) { + throw new CAS_InvalidArgumentException( + '$defaultDomain was not provided.' + ); + } + + // Set our default values + $cookie = array( + 'domain' => $defaultDomain, + 'path' => '/', + 'secure' => false, + ); + + $line = preg_replace('/^Set-Cookie2?: /i', '', trim($line)); + + // trim any trailing semicolons. + $line = trim($line, ';'); + + phpCAS::trace("Cookie Line: $line"); + + // This implementation makes the assumption that semicolons will not + // be present in quoted attribute values. While attribute values that + // contain semicolons are allowed by RFC2965, they are hopefully rare + // enough to ignore for our purposes. Most browsers make the same + // assumption. + $attributeStrings = explode(';', $line); + + foreach ( $attributeStrings as $attributeString ) { + // split on the first equals sign and use the rest as value + $attributeParts = explode('=', $attributeString, 2); + + $attributeName = trim($attributeParts[0]); + $attributeNameLC = strtolower($attributeName); + + if (isset($attributeParts[1])) { + $attributeValue = trim($attributeParts[1]); + // Values may be quoted strings. + if (strpos($attributeValue, '"') === 0) { + $attributeValue = trim($attributeValue, '"'); + // unescape any escaped quotes: + $attributeValue = str_replace('\"', '"', $attributeValue); + } + } else { + $attributeValue = null; + } + + switch ($attributeNameLC) { + case 'expires': + $cookie['expires'] = strtotime($attributeValue); + break; + case 'max-age': + $cookie['max-age'] = (int)$attributeValue; + // Set an expiry time based on the max-age + if ($cookie['max-age']) { + $cookie['expires'] = time() + $cookie['max-age']; + } else { + // If max-age is zero, then the cookie should be removed + // imediately so set an expiry before now. + $cookie['expires'] = time() - 1; + } + break; + case 'secure': + $cookie['secure'] = true; + break; + case 'domain': + case 'path': + case 'port': + case 'version': + case 'comment': + case 'commenturl': + case 'discard': + case 'httponly': + case 'samesite': + $cookie[$attributeNameLC] = $attributeValue; + break; + default: + $cookie['name'] = $attributeName; + $cookie['value'] = $attributeValue; + } + } + + return $cookie; + } + + /** + * Add, update, or remove a cookie. + * + * @param array $cookie A cookie array as created by parseCookieHeaders() + * + * @return void + * + * @access protected + */ + protected function storeCookie ($cookie) + { + // Discard any old versions of this cookie. + $this->discardCookie($cookie); + $this->_cookies[] = $cookie; + + } + + /** + * Discard an existing cookie + * + * @param array $cookie An cookie + * + * @return void + * + * @access protected + */ + protected function discardCookie ($cookie) + { + if (!isset($cookie['domain']) + || !isset($cookie['path']) + || !isset($cookie['path']) + ) { + throw new CAS_InvalidArgumentException('Invalid Cookie array passed.'); + } + + foreach ($this->_cookies as $key => $old_cookie) { + if ( $cookie['domain'] == $old_cookie['domain'] + && $cookie['path'] == $old_cookie['path'] + && $cookie['name'] == $old_cookie['name'] + ) { + unset($this->_cookies[$key]); + } + } + } + + /** + * Go through our stored cookies and remove any that are expired. + * + * @return void + * + * @access protected + */ + protected function expireCookies () + { + foreach ($this->_cookies as $key => $cookie) { + if (isset($cookie['expires']) && $cookie['expires'] < time()) { + unset($this->_cookies[$key]); + } + } + } + + /** + * Answer true if cookie is applicable to a target. + * + * @param array $cookie An array of cookie attributes. + * @param array|false $target An array of URL attributes as generated by parse_url(). + * + * @return bool + * + * @access private + */ + protected function cookieMatchesTarget ($cookie, $target) + { + if (!is_array($target)) { + throw new CAS_InvalidArgumentException( + '$target must be an array of URL attributes as generated by parse_url().' + ); + } + if (!isset($target['host'])) { + throw new CAS_InvalidArgumentException( + '$target must be an array of URL attributes as generated by parse_url().' + ); + } + + // Verify that the scheme matches + if ($cookie['secure'] && $target['scheme'] != 'https') { + return false; + } + + // Verify that the host matches + // Match domain and mulit-host cookies + if (strpos($cookie['domain'], '.') === 0) { + // .host.domain.edu cookies are valid for host.domain.edu + if (substr($cookie['domain'], 1) == $target['host']) { + // continue with other checks + } else { + // non-exact host-name matches. + // check that the target host a.b.c.edu is within .b.c.edu + $pos = strripos($target['host'], $cookie['domain']); + if (!$pos) { + return false; + } + // verify that the cookie domain is the last part of the host. + if ($pos + strlen($cookie['domain']) != strlen($target['host'])) { + return false; + } + // verify that the host name does not contain interior dots as per + // RFC 2965 section 3.3.2 Rejecting Cookies + // http://www.ietf.org/rfc/rfc2965.txt + $hostname = substr($target['host'], 0, $pos); + if (strpos($hostname, '.') !== false) { + return false; + } + } + } else { + // If the cookie host doesn't begin with '.', + // the host must case-insensitive match exactly + if (strcasecmp($target['host'], $cookie['domain']) !== 0) { + return false; + } + } + + // Verify that the port matches + if (isset($cookie['ports']) + && !in_array($target['port'], $cookie['ports']) + ) { + return false; + } + + // Verify that the path matches + if (strpos($target['path'], $cookie['path']) !== 0) { + return false; + } + + return true; + } + +} + +?> diff --git a/lib/apereo/phpcas/source/CAS/Exception.php b/lib/apereo/phpcas/source/CAS/Exception.php new file mode 100644 index 0000000000..2ff7cd658d --- /dev/null +++ b/lib/apereo/phpcas/source/CAS/Exception.php @@ -0,0 +1,59 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * A root exception interface for all exceptions in phpCAS. + * + * All exceptions thrown in phpCAS should implement this interface to allow them + * to be caught as a category by clients. Each phpCAS exception should extend + * an appropriate SPL exception class that best fits its type. + * + * For example, an InvalidArgumentException in phpCAS should be defined as + * + * class CAS_InvalidArgumentException + * extends InvalidArgumentException + * implements CAS_Exception + * { } + * + * This definition allows the CAS_InvalidArgumentException to be caught as either + * an InvalidArgumentException or as a CAS_Exception. + * + * @class CAS_Exception + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + */ +interface CAS_Exception +{ + +} +?> diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/GracefullTerminationException.php b/lib/apereo/phpcas/source/CAS/GracefullTerminationException.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/GracefullTerminationException.php rename to lib/apereo/phpcas/source/CAS/GracefullTerminationException.php index d1d035c39b..29aa638cd8 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/GracefullTerminationException.php +++ b/lib/apereo/phpcas/source/CAS/GracefullTerminationException.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/GracefullTerminationException.php * @category Authentication @@ -83,4 +83,4 @@ implements CAS_Exception } } -?> \ No newline at end of file +?> diff --git a/lib/apereo/phpcas/source/CAS/InvalidArgumentException.php b/lib/apereo/phpcas/source/CAS/InvalidArgumentException.php new file mode 100644 index 0000000000..99be2ac329 --- /dev/null +++ b/lib/apereo/phpcas/source/CAS/InvalidArgumentException.php @@ -0,0 +1,46 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Exception that denotes invalid arguments were passed. + * + * @class CAS_InvalidArgumentException + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_InvalidArgumentException +extends InvalidArgumentException +implements CAS_Exception +{ + +} +?> diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/Catalan.php b/lib/apereo/phpcas/source/CAS/Languages/Catalan.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/Catalan.php rename to lib/apereo/phpcas/source/CAS/Languages/Catalan.php index a0b64d8eb5..1ead905fc9 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/Catalan.php +++ b/lib/apereo/phpcas/source/CAS/Languages/Catalan.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/Language/Catalan.php * @category Authentication diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/ChineseSimplified.php b/lib/apereo/phpcas/source/CAS/Languages/ChineseSimplified.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/ChineseSimplified.php rename to lib/apereo/phpcas/source/CAS/Languages/ChineseSimplified.php index bb665937e3..5e33cb650d 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/ChineseSimplified.php +++ b/lib/apereo/phpcas/source/CAS/Languages/ChineseSimplified.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/Language/ChineseSimplified.php * @category Authentication @@ -111,4 +111,4 @@ class CAS_Languages_ChineseSimplified implements CAS_Languages_LanguageInterface { return '服务器 %s 不可用(%s)。'; } -} \ No newline at end of file +} diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/English.php b/lib/apereo/phpcas/source/CAS/Languages/English.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/English.php rename to lib/apereo/phpcas/source/CAS/Languages/English.php index 002c1ba49e..cb13bde93a 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/English.php +++ b/lib/apereo/phpcas/source/CAS/Languages/English.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/Language/English.php * @category Authentication @@ -111,4 +111,4 @@ class CAS_Languages_English implements CAS_Languages_LanguageInterface { return 'The service `%s\' is not available (%s).'; } -} \ No newline at end of file +} diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/French.php b/lib/apereo/phpcas/source/CAS/Languages/French.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/French.php rename to lib/apereo/phpcas/source/CAS/Languages/French.php index b99847a7f0..14f65aba24 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/French.php +++ b/lib/apereo/phpcas/source/CAS/Languages/French.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/Language/French.php * @category Authentication @@ -113,4 +113,4 @@ class CAS_Languages_French implements CAS_Languages_LanguageInterface } } -?> \ No newline at end of file +?> diff --git a/lib/apereo/phpcas/source/CAS/Languages/Galego.php b/lib/apereo/phpcas/source/CAS/Languages/Galego.php new file mode 100644 index 0000000000..d5bf404557 --- /dev/null +++ b/lib/apereo/phpcas/source/CAS/Languages/Galego.php @@ -0,0 +1,117 @@ +aquí para continuar'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return 'Autenticación CAS errada!'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return ' +

    Non estás autenticado

    Podes volver tentalo facendo click aquí.

    Se o problema persiste debería contactar con el administrador deste sitio.

    '; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return 'O servizo `%s\' non está dispoñible (%s).'; + } +} +?> diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/German.php b/lib/apereo/phpcas/source/CAS/Languages/German.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/German.php rename to lib/apereo/phpcas/source/CAS/Languages/German.php index ed3150a80c..b718b14521 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/German.php +++ b/lib/apereo/phpcas/source/CAS/Languages/German.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/Language/German.php * @category Authentication diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/Greek.php b/lib/apereo/phpcas/source/CAS/Languages/Greek.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/Greek.php rename to lib/apereo/phpcas/source/CAS/Languages/Greek.php index 888ce24160..1cfb107e4a 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/Greek.php +++ b/lib/apereo/phpcas/source/CAS/Languages/Greek.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/Language/Greek.php * @category Authentication diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/Japanese.php b/lib/apereo/phpcas/source/CAS/Languages/Japanese.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/Japanese.php rename to lib/apereo/phpcas/source/CAS/Languages/Japanese.php index a15bf17b1f..568148458d 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/Japanese.php +++ b/lib/apereo/phpcas/source/CAS/Languages/Japanese.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/Language/Japanese.php * @category Authentication diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/LanguageInterface.php b/lib/apereo/phpcas/source/CAS/Languages/LanguageInterface.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/LanguageInterface.php rename to lib/apereo/phpcas/source/CAS/Languages/LanguageInterface.php index 5de93aa7b2..dfb0ac5144 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/LanguageInterface.php +++ b/lib/apereo/phpcas/source/CAS/Languages/LanguageInterface.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/Language/LanguageInterface.php * @category Authentication @@ -93,4 +93,4 @@ interface CAS_Languages_LanguageInterface public function getServiceUnavailable(); } -?> \ No newline at end of file +?> diff --git a/lib/apereo/phpcas/source/CAS/Languages/Portuguese.php b/lib/apereo/phpcas/source/CAS/Languages/Portuguese.php new file mode 100644 index 0000000000..a927cad629 --- /dev/null +++ b/lib/apereo/phpcas/source/CAS/Languages/Portuguese.php @@ -0,0 +1,114 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://apereo.atlassian.net/wiki/spaces/CASC/pages/103252517/phpCAS + */ + +/** + * Portuguese language class + * + * @class CAS_Languages_Portuguese + * @category Authentication + * @package PhpCAS + * @author Sherwin Harris + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://apereo.atlassian.net/wiki/spaces/CASC/pages/103252517/phpCAS + * + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ +class CAS_Languages_Portuguese implements CAS_Languages_LanguageInterface +{ + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer() + { + return 'Usando o servidor'; + } + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted() + { + return 'A autenticação do servidor CAS desejado!'; + } + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout() + { + return 'Saida do servidor CAS desejado!'; + } + + /** + * Get the should have been redirected string + * + * @return string should have been redirected + */ + public function getShouldHaveBeenRedirected() + { + return 'Você já deve ter sido redirecionado para o servidor CAS. Clique aqui para continuar'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return 'A autenticação do servidor CAS falheu!'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return '

    Você não foi autenticado.

    Você pode enviar sua solicitação novamente clicando aqui.

    Se o problema persistir, você pode entrar em contato com o administrador deste site.

    '; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return 'O serviço `%s\' não está disponível (%s).'; + } +} diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/Spanish.php b/lib/apereo/phpcas/source/CAS/Languages/Spanish.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/Spanish.php rename to lib/apereo/phpcas/source/CAS/Languages/Spanish.php index 5675a41d80..c6ea50e747 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Languages/Spanish.php +++ b/lib/apereo/phpcas/source/CAS/Languages/Spanish.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/Language/Spanish.php * @category Authentication diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/OutOfSequenceBeforeAuthenticationCallException.php b/lib/apereo/phpcas/source/CAS/OutOfSequenceBeforeAuthenticationCallException.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/OutOfSequenceBeforeAuthenticationCallException.php rename to lib/apereo/phpcas/source/CAS/OutOfSequenceBeforeAuthenticationCallException.php index ef83097958..d4d7680de6 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/OutOfSequenceBeforeAuthenticationCallException.php +++ b/lib/apereo/phpcas/source/CAS/OutOfSequenceBeforeAuthenticationCallException.php @@ -18,7 +18,7 @@ * limitations under the License. * * - * PHP Version 5 + * PHP Version 7 * * @file CAS/OutOfSequenceBeforeAuthenticationCallException.php * @category Authentication diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/OutOfSequenceBeforeClientException.php b/lib/apereo/phpcas/source/CAS/OutOfSequenceBeforeClientException.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/OutOfSequenceBeforeClientException.php rename to lib/apereo/phpcas/source/CAS/OutOfSequenceBeforeClientException.php index f1ea7e2447..6c2c39c587 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/OutOfSequenceBeforeClientException.php +++ b/lib/apereo/phpcas/source/CAS/OutOfSequenceBeforeClientException.php @@ -18,7 +18,7 @@ * limitations under the License. * * - * PHP Version 5 + * PHP Version 7 * * @file CAS/OutOfSequenceBeforeClientException.php * @category Authentication diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/OutOfSequenceBeforeProxyException.php b/lib/apereo/phpcas/source/CAS/OutOfSequenceBeforeProxyException.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/OutOfSequenceBeforeProxyException.php rename to lib/apereo/phpcas/source/CAS/OutOfSequenceBeforeProxyException.php index 8038542ed1..7991555218 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/OutOfSequenceBeforeProxyException.php +++ b/lib/apereo/phpcas/source/CAS/OutOfSequenceBeforeProxyException.php @@ -18,7 +18,7 @@ * limitations under the License. * * - * PHP Version 5 + * PHP Version 7 * * @file CAS/OutOfSequenceBeforeProxyException.php * @category Authentication diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/OutOfSequenceException.php b/lib/apereo/phpcas/source/CAS/OutOfSequenceException.php similarity index 98% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/OutOfSequenceException.php rename to lib/apereo/phpcas/source/CAS/OutOfSequenceException.php index d101811b60..d6f7d88fc1 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/OutOfSequenceException.php +++ b/lib/apereo/phpcas/source/CAS/OutOfSequenceException.php @@ -18,7 +18,7 @@ * limitations under the License. * * - * PHP Version 5 + * PHP Version 7 * * @file CAS/OutOfSequenceException.php * @category Authentication diff --git a/lib/apereo/phpcas/source/CAS/PGTStorage/AbstractStorage.php b/lib/apereo/phpcas/source/CAS/PGTStorage/AbstractStorage.php new file mode 100644 index 0000000000..a93568d60d --- /dev/null +++ b/lib/apereo/phpcas/source/CAS/PGTStorage/AbstractStorage.php @@ -0,0 +1,222 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Basic class for PGT storage + * The CAS_PGTStorage_AbstractStorage class is a generic class for PGT storage. + * This class should not be instanciated itself but inherited by specific PGT + * storage classes. + * + * @class CAS_PGTStorage_AbstractStorage + * @category Authentication + * @package PhpCAS + * @author Pascal Aubry + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @ingroup internalPGTStorage + */ + +abstract class CAS_PGTStorage_AbstractStorage +{ + /** + * @addtogroup internalPGTStorage + * @{ + */ + + // ######################################################################## + // CONSTRUCTOR + // ######################################################################## + + /** + * The constructor of the class, should be called only by inherited classes. + * + * @param CAS_Client $cas_parent the CAS _client instance that creates the + * current object. + * + * @return void + * + * @protected + */ + function __construct($cas_parent) + { + phpCAS::traceBegin(); + if ( !$cas_parent->isProxy() ) { + phpCAS::error( + 'defining PGT storage makes no sense when not using a CAS proxy' + ); + } + phpCAS::traceEnd(); + } + + // ######################################################################## + // DEBUGGING + // ######################################################################## + + /** + * This virtual method returns an informational string giving the type of storage + * used by the object (used for debugging purposes). + * + * @return string + * + * @public + */ + function getStorageType() + { + phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); + } + + /** + * This virtual method returns an informational string giving informations on the + * parameters of the storage.(used for debugging purposes). + * + * @return string + * + * @public + */ + function getStorageInfo() + { + phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); + } + + // ######################################################################## + // ERROR HANDLING + // ######################################################################## + + /** + * string used to store an error message. Written by + * PGTStorage::setErrorMessage(), read by PGTStorage::getErrorMessage(). + * + * @hideinitializer + * @deprecated not used. + */ + var $_error_message=false; + + /** + * This method sets en error message, which can be read later by + * PGTStorage::getErrorMessage(). + * + * @param string $error_message an error message + * + * @return void + * + * @deprecated not used. + */ + function setErrorMessage($error_message) + { + $this->_error_message = $error_message; + } + + /** + * This method returns an error message set by PGTStorage::setErrorMessage(). + * + * @return string an error message when set by PGTStorage::setErrorMessage(), FALSE + * otherwise. + * + * @deprecated not used. + */ + function getErrorMessage() + { + return $this->_error_message; + } + + // ######################################################################## + // INITIALIZATION + // ######################################################################## + + /** + * a boolean telling if the storage has already been initialized. Written by + * PGTStorage::init(), read by PGTStorage::isInitialized(). + * + * @hideinitializer + */ + var $_initialized = false; + + /** + * This method tells if the storage has already been intialized. + * + * @return bool + * + * @protected + */ + function isInitialized() + { + return $this->_initialized; + } + + /** + * This virtual method initializes the object. + * + * @return void + */ + function init() + { + $this->_initialized = true; + } + + // ######################################################################## + // PGT I/O + // ######################################################################## + + /** + * This virtual method stores a PGT and its corresponding PGT Iuo. + * + * @param string $pgt the PGT + * @param string $pgt_iou the PGT iou + * + * @return void + * + * @note Should never be called. + * + */ + function write($pgt,$pgt_iou) + { + phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); + } + + /** + * This virtual method reads a PGT corresponding to a PGT Iou and deletes + * the corresponding storage entry. + * + * @param string $pgt_iou the PGT iou + * + * @return string + * + * @note Should never be called. + */ + function read($pgt_iou) + { + phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); + } + + /** @} */ + +} + +?> diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/PGTStorage/Db.php b/lib/apereo/phpcas/source/CAS/PGTStorage/Db.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/PGTStorage/Db.php rename to lib/apereo/phpcas/source/CAS/PGTStorage/Db.php index 383d11dc72..2efe5a3e85 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/PGTStorage/Db.php +++ b/lib/apereo/phpcas/source/CAS/PGTStorage/Db.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/PGTStorage/Db.php * @category Authentication diff --git a/lib/apereo/phpcas/source/CAS/PGTStorage/File.php b/lib/apereo/phpcas/source/CAS/PGTStorage/File.php new file mode 100644 index 0000000000..fbacd3b7d2 --- /dev/null +++ b/lib/apereo/phpcas/source/CAS/PGTStorage/File.php @@ -0,0 +1,261 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * The CAS_PGTStorage_File class is a class for PGT file storage. An instance of + * this class is returned by CAS_Client::SetPGTStorageFile(). + * + * @class CAS_PGTStorage_File + * @category Authentication + * @package PhpCAS + * @author Pascal Aubry + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * + * @ingroup internalPGTStorageFile + */ + +class CAS_PGTStorage_File extends CAS_PGTStorage_AbstractStorage +{ + /** + * @addtogroup internalPGTStorageFile + * @{ + */ + + /** + * a string telling where PGT's should be stored on the filesystem. Written by + * PGTStorageFile::PGTStorageFile(), read by getPath(). + * + * @private + */ + var $_path; + + /** + * This method returns the name of the directory where PGT's should be stored + * on the filesystem. + * + * @return string the name of a directory (with leading and trailing '/') + * + * @private + */ + function getPath() + { + return $this->_path; + } + + // ######################################################################## + // DEBUGGING + // ######################################################################## + + /** + * This method returns an informational string giving the type of storage + * used by the object (used for debugging purposes). + * + * @return string an informational string. + * @public + */ + function getStorageType() + { + return "file"; + } + + /** + * This method returns an informational string giving informations on the + * parameters of the storage.(used for debugging purposes). + * + * @return string an informational string. + * @public + */ + function getStorageInfo() + { + return 'path=`'.$this->getPath().'\''; + } + + // ######################################################################## + // CONSTRUCTOR + // ######################################################################## + + /** + * The class constructor, called by CAS_Client::SetPGTStorageFile(). + * + * @param CAS_Client $cas_parent the CAS_Client instance that creates the object. + * @param string $path the path where the PGT's should be stored + * + * @return void + * + * @public + */ + function __construct($cas_parent,$path) + { + phpCAS::traceBegin(); + // call the ancestor's constructor + parent::__construct($cas_parent); + + if (empty($path)) { + $path = CAS_PGT_STORAGE_FILE_DEFAULT_PATH; + } + // check that the path is an absolute path + if (getenv("OS")=="Windows_NT" || strtoupper(substr(PHP_OS,0,3)) == 'WIN') { + + if (!preg_match('`^[a-zA-Z]:`', $path)) { + phpCAS::error('an absolute path is needed for PGT storage to file'); + } + + } else { + + if ( $path[0] != '/' ) { + phpCAS::error('an absolute path is needed for PGT storage to file'); + } + + // store the path (with a leading and trailing '/') + $path = preg_replace('|[/]*$|', '/', $path); + $path = preg_replace('|^[/]*|', '/', $path); + } + + $this->_path = $path; + phpCAS::traceEnd(); + } + + // ######################################################################## + // INITIALIZATION + // ######################################################################## + + /** + * This method is used to initialize the storage. Halts on error. + * + * @return void + * @public + */ + function init() + { + phpCAS::traceBegin(); + // if the storage has already been initialized, return immediatly + if ($this->isInitialized()) { + return; + } + // call the ancestor's method (mark as initialized) + parent::init(); + phpCAS::traceEnd(); + } + + // ######################################################################## + // PGT I/O + // ######################################################################## + + /** + * This method returns the filename corresponding to a PGT Iou. + * + * @param string $pgt_iou the PGT iou. + * + * @return string a filename + * @private + */ + function getPGTIouFilename($pgt_iou) + { + phpCAS::traceBegin(); + $filename = $this->getPath()."phpcas-".hash("sha256", $pgt_iou); +// $filename = $this->getPath().$pgt_iou.'.plain'; + phpCAS::trace("Sha256 filename:" . $filename); + phpCAS::traceEnd(); + return $filename; + } + + /** + * This method stores a PGT and its corresponding PGT Iou into a file. Echoes a + * warning on error. + * + * @param string $pgt the PGT + * @param string $pgt_iou the PGT iou + * + * @return void + * + * @public + */ + function write($pgt,$pgt_iou) + { + phpCAS::traceBegin(); + $fname = $this->getPGTIouFilename($pgt_iou); + if (!file_exists($fname)) { + touch($fname); + // Chmod will fail on windows + @chmod($fname, 0600); + if ($f=fopen($fname, "w")) { + if (fputs($f, $pgt) === false) { + phpCAS::error('could not write PGT to `'.$fname.'\''); + } + phpCAS::trace('Successful write of PGT to `'.$fname.'\''); + fclose($f); + } else { + phpCAS::error('could not open `'.$fname.'\''); + } + } else { + phpCAS::error('File exists: `'.$fname.'\''); + } + phpCAS::traceEnd(); + } + + /** + * This method reads a PGT corresponding to a PGT Iou and deletes the + * corresponding file. + * + * @param string $pgt_iou the PGT iou + * + * @return string|false the corresponding PGT, or FALSE on error + * + * @public + */ + function read($pgt_iou) + { + phpCAS::traceBegin(); + $pgt = false; + $fname = $this->getPGTIouFilename($pgt_iou); + if (file_exists($fname)) { + if (!($f=fopen($fname, "r"))) { + phpCAS::error('could not open `'.$fname.'\''); + } else { + if (($pgt=fgets($f)) === false) { + phpCAS::error('could not read PGT from `'.$fname.'\''); + } + phpCAS::trace('Successful read of PGT to `'.$fname.'\''); + fclose($f); + } + // delete the PGT file + @unlink($fname); + } else { + phpCAS::error('No such file `'.$fname.'\''); + } + phpCAS::traceEnd($pgt); + return $pgt; + } + + /** @} */ + +} +?> diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxiedService.php b/lib/apereo/phpcas/source/CAS/ProxiedService.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxiedService.php rename to lib/apereo/phpcas/source/CAS/ProxiedService.php index d70ca9c128..2673ee955f 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxiedService.php +++ b/lib/apereo/phpcas/source/CAS/ProxiedService.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/ProxiedService.php * @category Authentication diff --git a/lib/apereo/phpcas/source/CAS/ProxiedService/Abstract.php b/lib/apereo/phpcas/source/CAS/ProxiedService/Abstract.php new file mode 100644 index 0000000000..0801c723bd --- /dev/null +++ b/lib/apereo/phpcas/source/CAS/ProxiedService/Abstract.php @@ -0,0 +1,149 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class implements common methods for ProxiedService implementations included + * with phpCAS. + * + * @class CAS_ProxiedService_Abstract + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +abstract class CAS_ProxiedService_Abstract +implements CAS_ProxiedService, CAS_ProxiedService_Testable +{ + + /** + * The proxy ticket that can be used when making service requests. + * @var string $_proxyTicket; + */ + private $_proxyTicket; + + /** + * Register a proxy ticket with the Proxy that it can use when making requests. + * + * @param string $proxyTicket proxy ticket + * + * @return void + * @throws InvalidArgumentException If the $proxyTicket is invalid. + * @throws CAS_OutOfSequenceException If called after a proxy ticket has + * already been initialized/set. + */ + public function setProxyTicket ($proxyTicket) + { + if (empty($proxyTicket)) { + throw new CAS_InvalidArgumentException( + 'Trying to initialize with an empty proxy ticket.' + ); + } + if (!empty($this->_proxyTicket)) { + throw new CAS_OutOfSequenceException( + 'Already initialized, cannot change the proxy ticket.' + ); + } + $this->_proxyTicket = $proxyTicket; + } + + /** + * Answer the proxy ticket to be used when making requests. + * + * @return string + * @throws CAS_OutOfSequenceException If called before a proxy ticket has + * already been initialized/set. + */ + protected function getProxyTicket () + { + if (empty($this->_proxyTicket)) { + throw new CAS_OutOfSequenceException( + 'No proxy ticket yet. Call $this->initializeProxyTicket() to aquire the proxy ticket.' + ); + } + + return $this->_proxyTicket; + } + + /** + * @var CAS_Client $_casClient; + */ + private $_casClient; + + /** + * Use a particular CAS_Client->initializeProxiedService() rather than the + * static phpCAS::initializeProxiedService(). + * + * This method should not be called in standard operation, but is needed for unit + * testing. + * + * @param CAS_Client $casClient cas client + * + * @return void + * @throws CAS_OutOfSequenceException If called after a proxy ticket has + * already been initialized/set. + */ + public function setCasClient (CAS_Client $casClient) + { + if (!empty($this->_proxyTicket)) { + throw new CAS_OutOfSequenceException( + 'Already initialized, cannot change the CAS_Client.' + ); + } + + $this->_casClient = $casClient; + } + + /** + * Fetch our proxy ticket. + * + * Descendent classes should call this method once their service URL is available + * to initialize their proxy ticket. + * + * @return void + * @throws CAS_OutOfSequenceException If called after a proxy ticket has + * already been initialized. + */ + protected function initializeProxyTicket() + { + if (!empty($this->_proxyTicket)) { + throw new CAS_OutOfSequenceException( + 'Already initialized, cannot initialize again.' + ); + } + // Allow usage of a particular CAS_Client for unit testing. + if (empty($this->_casClient)) { + phpCAS::initializeProxiedService($this); + } else { + $this->_casClient->initializeProxiedService($this); + } + } + +} +?> diff --git a/lib/apereo/phpcas/source/CAS/ProxiedService/Exception.php b/lib/apereo/phpcas/source/CAS/ProxiedService/Exception.php new file mode 100644 index 0000000000..0f87413dd9 --- /dev/null +++ b/lib/apereo/phpcas/source/CAS/ProxiedService/Exception.php @@ -0,0 +1,46 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * An Exception for problems communicating with a proxied service. + * + * @class CAS_ProxiedService_Exception + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_ProxiedService_Exception +extends Exception +implements CAS_Exception +{ + +} +?> diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxiedService/Http.php b/lib/apereo/phpcas/source/CAS/ProxiedService/Http.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxiedService/Http.php rename to lib/apereo/phpcas/source/CAS/ProxiedService/Http.php index 7c9824fabc..4240b061ae 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxiedService/Http.php +++ b/lib/apereo/phpcas/source/CAS/ProxiedService/Http.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/ProxiedService/Http.php * @category Authentication diff --git a/lib/apereo/phpcas/source/CAS/ProxiedService/Http/Abstract.php b/lib/apereo/phpcas/source/CAS/ProxiedService/Http/Abstract.php new file mode 100644 index 0000000000..8d55edd502 --- /dev/null +++ b/lib/apereo/phpcas/source/CAS/ProxiedService/Http/Abstract.php @@ -0,0 +1,360 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class implements common methods for ProxiedService implementations included + * with phpCAS. + * + * @class CAS_ProxiedService_Http_Abstract + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +abstract class CAS_ProxiedService_Http_Abstract extends +CAS_ProxiedService_Abstract implements CAS_ProxiedService_Http +{ + /** + * The HTTP request mechanism talking to the target service. + * + * @var CAS_Request_RequestInterface $requestHandler + */ + protected $requestHandler; + + /** + * The storage mechanism for cookies set by the target service. + * + * @var CAS_CookieJar $_cookieJar + */ + private $_cookieJar; + + /** + * Constructor. + * + * @param CAS_Request_RequestInterface $requestHandler request handler object + * @param CAS_CookieJar $cookieJar cookieJar object + * + * @return void + */ + public function __construct(CAS_Request_RequestInterface $requestHandler, + CAS_CookieJar $cookieJar + ) { + $this->requestHandler = $requestHandler; + $this->_cookieJar = $cookieJar; + } + + /** + * The target service url. + * @var string $_url; + */ + private $_url; + + /** + * Answer a service identifier (URL) for whom we should fetch a proxy ticket. + * + * @return string + * @throws Exception If no service url is available. + */ + public function getServiceUrl() + { + if (empty($this->_url)) { + throw new CAS_ProxiedService_Exception( + 'No URL set via ' . get_class($this) . '->setUrl($url).' + ); + } + + return $this->_url; + } + + /********************************************************* + * Configure the Request + *********************************************************/ + + /** + * Set the URL of the Request + * + * @param string $url url to set + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setUrl($url) + { + if ($this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot set the URL, request already sent.' + ); + } + if (!is_string($url)) { + throw new CAS_InvalidArgumentException('$url must be a string.'); + } + + $this->_url = $url; + } + + /********************************************************* + * 2. Send the Request + *********************************************************/ + + /** + * Perform the request. + * + * @return void + * @throws CAS_OutOfSequenceException If called multiple times. + * @throws CAS_ProxyTicketException If there is a proxy-ticket failure. + * The code of the Exception will be one of: + * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_FAILURE + * @throws CAS_ProxiedService_Exception If there is a failure sending the + * request to the target service. + */ + public function send() + { + if ($this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot send, request already sent.' + ); + } + + phpCAS::traceBegin(); + + // Get our proxy ticket and append it to our URL. + $this->initializeProxyTicket(); + $url = $this->getServiceUrl(); + if (strstr($url, '?') === false) { + $url = $url . '?ticket=' . $this->getProxyTicket(); + } else { + $url = $url . '&ticket=' . $this->getProxyTicket(); + } + + try { + $this->makeRequest($url); + } catch (Exception $e) { + phpCAS::traceEnd(); + throw $e; + } + } + + /** + * Indicator of the number of requests (including redirects performed. + * + * @var int $_numRequests; + */ + private $_numRequests = 0; + + /** + * The response headers. + * + * @var array $_responseHeaders; + */ + private $_responseHeaders = array(); + + /** + * The response status code. + * + * @var int $_responseStatusCode; + */ + private $_responseStatusCode = ''; + + /** + * The response headers. + * + * @var string $_responseBody; + */ + private $_responseBody = ''; + + /** + * Build and perform a request, following redirects + * + * @param string $url url for the request + * + * @return void + * @throws CAS_ProxyTicketException If there is a proxy-ticket failure. + * The code of the Exception will be one of: + * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_FAILURE + * @throws CAS_ProxiedService_Exception If there is a failure sending the + * request to the target service. + */ + protected function makeRequest($url) + { + // Verify that we are not in a redirect loop + $this->_numRequests++; + if ($this->_numRequests > 4) { + $message = 'Exceeded the maximum number of redirects (3) in proxied service request.'; + phpCAS::trace($message); + throw new CAS_ProxiedService_Exception($message); + } + + // Create a new request. + $request = clone $this->requestHandler; + $request->setUrl($url); + + // Add any cookies to the request. + $request->addCookies($this->_cookieJar->getCookies($url)); + + // Add any other parts of the request needed by concrete classes + $this->populateRequest($request); + + // Perform the request. + phpCAS::trace('Performing proxied service request to \'' . $url . '\''); + if (!$request->send()) { + $message = 'Could not perform proxied service request to URL`' + . $url . '\'. ' . $request->getErrorMessage(); + phpCAS::trace($message); + throw new CAS_ProxiedService_Exception($message); + } + + // Store any cookies from the response; + $this->_cookieJar->storeCookies($url, $request->getResponseHeaders()); + + // Follow any redirects + if ($redirectUrl = $this->getRedirectUrl($request->getResponseHeaders()) + ) { + phpCAS::trace('Found redirect:' . $redirectUrl); + $this->makeRequest($redirectUrl); + } else { + + $this->_responseHeaders = $request->getResponseHeaders(); + $this->_responseBody = $request->getResponseBody(); + $this->_responseStatusCode = $request->getResponseStatusCode(); + } + } + + /** + * Add any other parts of the request needed by concrete classes + * + * @param CAS_Request_RequestInterface $request request interface object + * + * @return void + */ + abstract protected function populateRequest( + CAS_Request_RequestInterface $request + ); + + /** + * Answer a redirect URL if a redirect header is found, otherwise null. + * + * @param array $responseHeaders response header to extract a redirect from + * + * @return string|null + */ + protected function getRedirectUrl(array $responseHeaders) + { + // Check for the redirect after authentication + foreach ($responseHeaders as $header) { + if ( preg_match('/^(Location:|URI:)\s*([^\s]+.*)$/', $header, $matches) + ) { + return trim(array_pop($matches)); + } + } + return null; + } + + /********************************************************* + * 3. Access the response + *********************************************************/ + + /** + * Answer true if our request has been sent yet. + * + * @return bool + */ + protected function hasBeenSent() + { + return ($this->_numRequests > 0); + } + + /** + * Answer the headers of the response. + * + * @return array An array of header strings. + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseHeaders() + { + if (!$this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot access response, request not sent yet.' + ); + } + + return $this->_responseHeaders; + } + + /** + * Answer HTTP status code of the response + * + * @return int + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseStatusCode() + { + if (!$this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot access response, request not sent yet.' + ); + } + + return $this->_responseStatusCode; + } + + /** + * Answer the body of response. + * + * @return string + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseBody() + { + if (!$this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot access response, request not sent yet.' + ); + } + + return $this->_responseBody; + } + + /** + * Answer the cookies from the response. This may include cookies set during + * redirect responses. + * + * @return array An array containing cookies. E.g. array('name' => 'val'); + */ + public function getCookies() + { + return $this->_cookieJar->getCookies($this->getServiceUrl()); + } + +} +?> diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxiedService/Http/Get.php b/lib/apereo/phpcas/source/CAS/ProxiedService/Http/Get.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxiedService/Http/Get.php rename to lib/apereo/phpcas/source/CAS/ProxiedService/Http/Get.php index 78e35de16f..a459d55ae9 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxiedService/Http/Get.php +++ b/lib/apereo/phpcas/source/CAS/ProxiedService/Http/Get.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/ProxiedService/Http/Get.php * @category Authentication diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxiedService/Http/Post.php b/lib/apereo/phpcas/source/CAS/ProxiedService/Http/Post.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxiedService/Http/Post.php rename to lib/apereo/phpcas/source/CAS/ProxiedService/Http/Post.php index 7d4ecd3c03..344c439878 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxiedService/Http/Post.php +++ b/lib/apereo/phpcas/source/CAS/ProxiedService/Http/Post.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/ProxiedService/Http/Post.php * @category Authentication diff --git a/lib/apereo/phpcas/source/CAS/ProxiedService/Imap.php b/lib/apereo/phpcas/source/CAS/ProxiedService/Imap.php new file mode 100644 index 0000000000..c4b47401dd --- /dev/null +++ b/lib/apereo/phpcas/source/CAS/ProxiedService/Imap.php @@ -0,0 +1,281 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Provides access to a proxy-authenticated IMAP stream + * + * @class CAS_ProxiedService_Imap + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_ProxiedService_Imap +extends CAS_ProxiedService_Abstract +{ + + /** + * The username to send via imap_open. + * + * @var string $_username; + */ + private $_username; + + /** + * Constructor. + * + * @param string $username Username + * + * @return void + */ + public function __construct ($username) + { + if (!is_string($username) || !strlen($username)) { + throw new CAS_InvalidArgumentException('Invalid username.'); + } + + $this->_username = $username; + } + + /** + * The target service url. + * @var string $_url; + */ + private $_url; + + /** + * Answer a service identifier (URL) for whom we should fetch a proxy ticket. + * + * @return string + * @throws Exception If no service url is available. + */ + public function getServiceUrl () + { + if (empty($this->_url)) { + throw new CAS_ProxiedService_Exception( + 'No URL set via '.get_class($this).'->getServiceUrl($url).' + ); + } + + return $this->_url; + } + + /********************************************************* + * Configure the Stream + *********************************************************/ + + /** + * Set the URL of the service to pass to CAS for proxy-ticket retrieval. + * + * @param string $url Url to set + * + * @return void + * @throws CAS_OutOfSequenceException If called after the stream has been opened. + */ + public function setServiceUrl ($url) + { + if ($this->hasBeenOpened()) { + throw new CAS_OutOfSequenceException( + 'Cannot set the URL, stream already opened.' + ); + } + if (!is_string($url) || !strlen($url)) { + throw new CAS_InvalidArgumentException('Invalid url.'); + } + + $this->_url = $url; + } + + /** + * The mailbox to open. See the $mailbox parameter of imap_open(). + * + * @var string $_mailbox + */ + private $_mailbox; + + /** + * Set the mailbox to open. See the $mailbox parameter of imap_open(). + * + * @param string $mailbox Mailbox to set + * + * @return void + * @throws CAS_OutOfSequenceException If called after the stream has been opened. + */ + public function setMailbox ($mailbox) + { + if ($this->hasBeenOpened()) { + throw new CAS_OutOfSequenceException( + 'Cannot set the mailbox, stream already opened.' + ); + } + if (!is_string($mailbox) || !strlen($mailbox)) { + throw new CAS_InvalidArgumentException('Invalid mailbox.'); + } + + $this->_mailbox = $mailbox; + } + + /** + * A bit mask of options to pass to imap_open() as the $options parameter. + * + * @var int $_options + */ + private $_options = null; + + /** + * Set the options for opening the stream. See the $options parameter of + * imap_open(). + * + * @param int $options Options for the stream + * + * @return void + * @throws CAS_OutOfSequenceException If called after the stream has been opened. + */ + public function setOptions ($options) + { + if ($this->hasBeenOpened()) { + throw new CAS_OutOfSequenceException( + 'Cannot set options, stream already opened.' + ); + } + if (!is_int($options)) { + throw new CAS_InvalidArgumentException('Invalid options.'); + } + + $this->_options = $options; + } + + /********************************************************* + * 2. Open the stream + *********************************************************/ + + /** + * Open the IMAP stream (similar to imap_open()). + * + * @return resource Returns an IMAP stream on success + * @throws CAS_OutOfSequenceException If called multiple times. + * @throws CAS_ProxyTicketException If there is a proxy-ticket failure. + * The code of the Exception will be one of: + * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_FAILURE + * @throws CAS_ProxiedService_Exception If there is a failure sending the + * request to the target service. + */ + public function open () + { + if ($this->hasBeenOpened()) { + throw new CAS_OutOfSequenceException('Stream already opened.'); + } + if (empty($this->_mailbox)) { + throw new CAS_ProxiedService_Exception( + 'You must specify a mailbox via '.get_class($this) + .'->setMailbox($mailbox)' + ); + } + + phpCAS::traceBegin(); + + // Get our proxy ticket and append it to our URL. + $this->initializeProxyTicket(); + phpCAS::trace('opening IMAP mailbox `'.$this->_mailbox.'\'...'); + $this->_stream = @imap_open( + $this->_mailbox, $this->_username, $this->getProxyTicket(), + $this->_options + ); + if ($this->_stream) { + phpCAS::trace('ok'); + } else { + phpCAS::trace('could not open mailbox'); + // @todo add localization integration. + $message = 'IMAP Error: '.$this->_url.' '. var_export(imap_errors(), true); + phpCAS::trace($message); + throw new CAS_ProxiedService_Exception($message); + } + + phpCAS::traceEnd(); + return $this->_stream; + } + + /** + * Answer true if our request has been sent yet. + * + * @return bool + */ + protected function hasBeenOpened () + { + return !empty($this->_stream); + } + + /********************************************************* + * 3. Access the result + *********************************************************/ + /** + * The IMAP stream + * + * @var resource $_stream + */ + private $_stream; + + /** + * Answer the IMAP stream + * + * @return resource + * @throws CAS_OutOfSequenceException if stream is not opened yet + */ + public function getStream () + { + if (!$this->hasBeenOpened()) { + throw new CAS_OutOfSequenceException( + 'Cannot access stream, not opened yet.' + ); + } + return $this->_stream; + } + + /** + * CAS_Client::serviceMail() needs to return the proxy ticket for some reason, + * so this method provides access to it. + * + * @return string + * @throws CAS_OutOfSequenceException If called before the stream has been + * opened. + */ + public function getImapProxyTicket () + { + if (!$this->hasBeenOpened()) { + throw new CAS_OutOfSequenceException( + 'Cannot access errors, stream not opened yet.' + ); + } + return $this->getProxyTicket(); + } +} +?> diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxiedService/Testable.php b/lib/apereo/phpcas/source/CAS/ProxiedService/Testable.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxiedService/Testable.php rename to lib/apereo/phpcas/source/CAS/ProxiedService/Testable.php index 51f0767627..3ce44fd16c 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxiedService/Testable.php +++ b/lib/apereo/phpcas/source/CAS/ProxiedService/Testable.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/ProxiedService/Testabel.php * @category Authentication diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxyChain.php b/lib/apereo/phpcas/source/CAS/ProxyChain.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxyChain.php rename to lib/apereo/phpcas/source/CAS/ProxyChain.php index 2594d141e5..e200724cef 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxyChain.php +++ b/lib/apereo/phpcas/source/CAS/ProxyChain.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/ProxyChain.php * @category Authentication diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxyChain/AllowedList.php b/lib/apereo/phpcas/source/CAS/ProxyChain/AllowedList.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxyChain/AllowedList.php rename to lib/apereo/phpcas/source/CAS/ProxyChain/AllowedList.php index cafd0e743a..988ddbb3c3 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxyChain/AllowedList.php +++ b/lib/apereo/phpcas/source/CAS/ProxyChain/AllowedList.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/ProxyChain/AllowedList.php * @category Authentication diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxyChain/Any.php b/lib/apereo/phpcas/source/CAS/ProxyChain/Any.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxyChain/Any.php rename to lib/apereo/phpcas/source/CAS/ProxyChain/Any.php index 0cd92f74e9..fe18c5fbf1 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxyChain/Any.php +++ b/lib/apereo/phpcas/source/CAS/ProxyChain/Any.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/ProxyChain/Any.php * @category Authentication diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxyChain/Interface.php b/lib/apereo/phpcas/source/CAS/ProxyChain/Interface.php similarity index 98% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxyChain/Interface.php rename to lib/apereo/phpcas/source/CAS/ProxyChain/Interface.php index d247115db3..b1d6881747 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxyChain/Interface.php +++ b/lib/apereo/phpcas/source/CAS/ProxyChain/Interface.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/ProxyChain/Interface.php * @category Authentication @@ -50,4 +50,4 @@ interface CAS_ProxyChain_Interface */ public function matches(array $list); -} \ No newline at end of file +} diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxyChain/Trusted.php b/lib/apereo/phpcas/source/CAS/ProxyChain/Trusted.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxyChain/Trusted.php rename to lib/apereo/phpcas/source/CAS/ProxyChain/Trusted.php index 7fa6129677..e67d70852a 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxyChain/Trusted.php +++ b/lib/apereo/phpcas/source/CAS/ProxyChain/Trusted.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/ProxyChain/Trusted.php * @category Authentication diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxyTicketException.php b/lib/apereo/phpcas/source/CAS/ProxyTicketException.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxyTicketException.php rename to lib/apereo/phpcas/source/CAS/ProxyTicketException.php index 723304666d..2f825b421c 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/ProxyTicketException.php +++ b/lib/apereo/phpcas/source/CAS/ProxyTicketException.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @class CAS/ProxyTicketException.php * @category Authentication diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Request/AbstractRequest.php b/lib/apereo/phpcas/source/CAS/Request/AbstractRequest.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Request/AbstractRequest.php rename to lib/apereo/phpcas/source/CAS/Request/AbstractRequest.php index 130024bf15..4f9013ee2c 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Request/AbstractRequest.php +++ b/lib/apereo/phpcas/source/CAS/Request/AbstractRequest.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/Request/AbstractRequest.php * @category Authentication diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Request/CurlMultiRequest.php b/lib/apereo/phpcas/source/CAS/Request/CurlMultiRequest.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Request/CurlMultiRequest.php rename to lib/apereo/phpcas/source/CAS/Request/CurlMultiRequest.php index 919c9561d1..850f6f0e4a 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Request/CurlMultiRequest.php +++ b/lib/apereo/phpcas/source/CAS/Request/CurlMultiRequest.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/Request/AbstractRequest.php * @category Authentication diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Request/CurlRequest.php b/lib/apereo/phpcas/source/CAS/Request/CurlRequest.php similarity index 93% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Request/CurlRequest.php rename to lib/apereo/phpcas/source/CAS/Request/CurlRequest.php index 70057712ed..3eaa0d3c16 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Request/CurlRequest.php +++ b/lib/apereo/phpcas/source/CAS/Request/CurlRequest.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/Request/CurlRequest.php * @category Authentication @@ -106,14 +106,7 @@ implements CAS_Request_RequestInterface *********************************************************/ $ch = curl_init($this->url); - if (version_compare(PHP_VERSION, '5.1.3', '>=')) { - //only avaible in php5 - curl_setopt_array($ch, $this->_curlOptions); - } else { - foreach ($this->_curlOptions as $key => $value) { - curl_setopt($ch, $key, $value); - } - } + curl_setopt_array($ch, $this->_curlOptions); /********************************************************* * Set SSL configuration @@ -179,7 +172,7 @@ implements CAS_Request_RequestInterface * * @return void */ - private function _storeResponseBody ($body) + public function _storeResponseBody ($body) { $this->storeResponseBody($body); } @@ -192,7 +185,7 @@ implements CAS_Request_RequestInterface * * @return int */ - private function _curlReadHeaders ($ch, $header) + public function _curlReadHeaders ($ch, $header) { $this->storeResponseHeader($header); return strlen($header); diff --git a/lib/apereo/phpcas/source/CAS/Request/Exception.php b/lib/apereo/phpcas/source/CAS/Request/Exception.php new file mode 100644 index 0000000000..dd5a2a55a5 --- /dev/null +++ b/lib/apereo/phpcas/source/CAS/Request/Exception.php @@ -0,0 +1,45 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * An Exception for problems performing requests + * + * @class CAS_Request_Exception + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_Request_Exception +extends Exception +implements CAS_Exception +{ + +} diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Request/MultiRequestInterface.php b/lib/apereo/phpcas/source/CAS/Request/MultiRequestInterface.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Request/MultiRequestInterface.php rename to lib/apereo/phpcas/source/CAS/Request/MultiRequestInterface.php index f3298274db..41002c7776 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/Request/MultiRequestInterface.php +++ b/lib/apereo/phpcas/source/CAS/Request/MultiRequestInterface.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/Request/MultiRequestInterface.php * @category Authentication diff --git a/lib/apereo/phpcas/source/CAS/Request/RequestInterface.php b/lib/apereo/phpcas/source/CAS/Request/RequestInterface.php new file mode 100644 index 0000000000..b8e8772e93 --- /dev/null +++ b/lib/apereo/phpcas/source/CAS/Request/RequestInterface.php @@ -0,0 +1,179 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This interface defines a class library for performing web requests. + * + * @class CAS_Request_RequestInterface + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +interface CAS_Request_RequestInterface +{ + + /********************************************************* + * Configure the Request + *********************************************************/ + + /** + * Set the URL of the Request + * + * @param string $url url to set + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setUrl ($url); + + /** + * Add a cookie to the request. + * + * @param string $name name of cookie + * @param string $value value of cookie + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addCookie ($name, $value); + + /** + * Add an array of cookies to the request. + * The cookie array is of the form + * array('cookie_name' => 'cookie_value', 'cookie_name2' => cookie_value2') + * + * @param array $cookies cookies to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addCookies (array $cookies); + + /** + * Add a header string to the request. + * + * @param string $header header to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addHeader ($header); + + /** + * Add an array of header strings to the request. + * + * @param array $headers headers to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addHeaders (array $headers); + + /** + * Make the request a POST request rather than the default GET request. + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function makePost (); + + /** + * Add a POST body to the request + * + * @param string $body body to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setPostBody ($body); + + + /** + * Specify the path to an SSL CA certificate to validate the server with. + * + * @param string $caCertPath path to cert file + * @param boolean $validate_cn validate CN of SSL certificate + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setSslCaCert ($caCertPath, $validate_cn = true); + + + + /********************************************************* + * 2. Send the Request + *********************************************************/ + + /** + * Perform the request. + * + * @return bool TRUE on success, FALSE on failure. + * @throws CAS_OutOfSequenceException If called multiple times. + */ + public function send (); + + /********************************************************* + * 3. Access the response + *********************************************************/ + + /** + * Answer the headers of the response. + * + * @return array An array of header strings. + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseHeaders (); + + /** + * Answer HTTP status code of the response + * + * @return int + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseStatusCode (); + + /** + * Answer the body of response. + * + * @return string + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseBody (); + + /** + * Answer a message describing any errors if the request failed. + * + * @return string + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getErrorMessage (); +} diff --git a/lib/apereo/phpcas/source/CAS/Session/PhpSession.php b/lib/apereo/phpcas/source/CAS/Session/PhpSession.php new file mode 100644 index 0000000000..031cbbc70f --- /dev/null +++ b/lib/apereo/phpcas/source/CAS/Session/PhpSession.php @@ -0,0 +1,45 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Empty class used as a default implementation for phpCAS. + * + * Implements the standard PHP session handler without no alterations. + * + * @class CAS_Session_PhpSession + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_Session_PhpSession extends SessionHandler implements SessionHandlerInterface +{ +} diff --git a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/TypeMismatchException.php b/lib/apereo/phpcas/source/CAS/TypeMismatchException.php similarity index 99% rename from datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/TypeMismatchException.php rename to lib/apereo/phpcas/source/CAS/TypeMismatchException.php index 4a13c2df45..72bdc87a17 100644 --- a/datamodels/2.x/authent-cas/vendor/apereo/phpcas/source/CAS/TypeMismatchException.php +++ b/lib/apereo/phpcas/source/CAS/TypeMismatchException.php @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * PHP Version 5 + * PHP Version 7 * * @file CAS/InvalidArgumentException.php * @category Authentication diff --git a/lib/autoload.php b/lib/autoload.php index 79c1600b5c..460e675355 100644 --- a/lib/autoload.php +++ b/lib/autoload.php @@ -4,4 +4,4 @@ require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b::getLoader(); +return ComposerAutoloaderInit7f81b4a2a468a061c306af5e447a9a9f::getLoader(); diff --git a/lib/bin/generate-deps-for-config-factory b/lib/bin/generate-deps-for-config-factory new file mode 100644 index 0000000000..a7a47e4c8a --- /dev/null +++ b/lib/bin/generate-deps-for-config-factory @@ -0,0 +1,97 @@ +#!/usr/bin/env php +handle = fopen($opened_path, $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; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return fstat($this->handle); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + } + } + + if (function_exists('stream_wrapper_register') && stream_wrapper_register('composer-bin-proxy', 'Composer\BinProxyWrapper')) { + include("composer-bin-proxy://" . $binPath); + exit(0); + } +} + +include $binPath; diff --git a/lib/bin/generate-deps-for-config-factory.bat b/lib/bin/generate-deps-for-config-factory.bat new file mode 100644 index 0000000000..3eae0eb2cc --- /dev/null +++ b/lib/bin/generate-deps-for-config-factory.bat @@ -0,0 +1,4 @@ +@ECHO OFF +setlocal DISABLEDELAYEDEXPANSION +SET BIN_TARGET=%~dp0/../laminas/laminas-servicemanager/bin/generate-deps-for-config-factory +php "%BIN_TARGET%" %* diff --git a/lib/bin/generate-factory-for-class b/lib/bin/generate-factory-for-class new file mode 100644 index 0000000000..9aec04dbc4 --- /dev/null +++ b/lib/bin/generate-factory-for-class @@ -0,0 +1,97 @@ +#!/usr/bin/env php +handle = fopen($opened_path, $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; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return fstat($this->handle); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + } + } + + if (function_exists('stream_wrapper_register') && stream_wrapper_register('composer-bin-proxy', 'Composer\BinProxyWrapper')) { + include("composer-bin-proxy://" . $binPath); + exit(0); + } +} + +include $binPath; diff --git a/lib/bin/generate-factory-for-class.bat b/lib/bin/generate-factory-for-class.bat new file mode 100644 index 0000000000..97f2fa32a4 --- /dev/null +++ b/lib/bin/generate-factory-for-class.bat @@ -0,0 +1,4 @@ +@ECHO OFF +setlocal DISABLEDELAYEDEXPANSION +SET BIN_TARGET=%~dp0/../laminas/laminas-servicemanager/bin/generate-factory-for-class +php "%BIN_TARGET%" %* diff --git a/lib/bin/patch-type-declarations b/lib/bin/patch-type-declarations new file mode 100644 index 0000000000..1b0b28230e --- /dev/null +++ b/lib/bin/patch-type-declarations @@ -0,0 +1,97 @@ +#!/usr/bin/env php +handle = fopen($opened_path, $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; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return fstat($this->handle); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + } + } + + if (function_exists('stream_wrapper_register') && stream_wrapper_register('composer-bin-proxy', 'Composer\BinProxyWrapper')) { + include("composer-bin-proxy://" . $binPath); + exit(0); + } +} + +include $binPath; diff --git a/lib/bin/patch-type-declarations.bat b/lib/bin/patch-type-declarations.bat new file mode 100644 index 0000000000..b92a2da31b --- /dev/null +++ b/lib/bin/patch-type-declarations.bat @@ -0,0 +1,4 @@ +@ECHO OFF +setlocal DISABLEDELAYEDEXPANSION +SET BIN_TARGET=%~dp0/../symfony/error-handler/Resources/bin/patch-type-declarations +php "%BIN_TARGET%" %* diff --git a/lib/bin/php-parse b/lib/bin/php-parse index e2e4c58a1b..1bd2c838c0 100644 --- a/lib/bin/php-parse +++ b/lib/bin/php-parse @@ -4,21 +4,117 @@ /** * Proxy PHP file generated by Composer * - * This file includes the referenced bin path (../nikic/php-parser/bin/php-parse) using eval to remove the shebang if present + * This file includes the referenced bin path (../nikic/php-parser/bin/php-parse) + * using a stream wrapper to prevent the shebang from being output on PHP<8 * * @generated */ -$binPath = realpath(__DIR__ . "/" . '../nikic/php-parser/bin/php-parse'); -$contents = file_get_contents($binPath); -$contents = preg_replace('{^#!/.+\r?\n<\?(php)?}', '', $contents, 1, $replaced); -if ($replaced) { - $contents = strtr($contents, array( - '__FILE__' => var_export($binPath, true), - '__DIR__' => var_export(dirname($binPath), true), - )); +namespace Composer; - eval($contents); - exit(0); +$GLOBALS['_composer_bin_dir'] = __DIR__; +$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php'; + +if (PHP_VERSION_ID < 80000) { + if (!class_exists('Composer\BinProxyWrapper')) { + /** + * @internal + */ + final class BinProxyWrapper + { + private $handle; + private $position; + private $realpath; + + public function stream_open($path, $mode, $options, &$opened_path) + { + // 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; + + return (bool) $this->handle; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_seek($offset, $whence) + { + if (0 === fseek($this->handle, $offset, $whence)) { + $this->position = ftell($this->handle); + return true; + } + + return false; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return array(); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + + public function url_stat($path, $flags) + { + $path = substr($path, 17); + if (file_exists($path)) { + return stat($path); + } + + return false; + } + } + } + + if ( + (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) + || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) + ) { + include("phpvfscomposer://" . __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse'); + exit(0); + } } -include $binPath; + +include __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse'; diff --git a/lib/bin/php-parse.bat b/lib/bin/php-parse.bat index a5baa4eda1..2c5096dc39 100644 --- a/lib/bin/php-parse.bat +++ b/lib/bin/php-parse.bat @@ -1,4 +1,5 @@ @ECHO OFF setlocal DISABLEDELAYEDEXPANSION -SET BIN_TARGET=%~dp0/../nikic/php-parser/bin/php-parse +SET BIN_TARGET=%~dp0/php-parse +SET COMPOSER_RUNTIME_BIN_DIR=%~dp0 php "%BIN_TARGET%" %* diff --git a/lib/bin/pscss b/lib/bin/pscss index 2879dcb977..fa9a900db8 100644 --- a/lib/bin/pscss +++ b/lib/bin/pscss @@ -1,14 +1,97 @@ -#!/usr/bin/env sh +#!/usr/bin/env php + /dev/null; cd "../scssphp/scssphp/bin" && pwd) +/** + * Proxy PHP file generated by Composer + * + * This file includes the referenced bin path (../scssphp/scssphp/bin/pscss) using ob_start to remove the shebang if present + * to prevent the shebang from being output on PHP<8 + * + * @generated + */ -if [ -d /proc/cygdrive ]; then - case $(which php) in - $(readlink -n /proc/cygdrive)/*) - # We are in Cygwin using Windows php, so the path must be translated - dir=$(cygpath -m "$dir"); - ;; - esac -fi +namespace Composer; -"${dir}/pscss" "$@" +$binPath = __DIR__ . "/" . '../scssphp/scssphp/bin/pscss'; + +if (PHP_VERSION_ID < 80000) { + if (!class_exists('Composer\BinProxyWrapper')) { + /** + * @internal + */ + final class BinProxyWrapper + { + private $handle; + private $position; + + 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); + $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; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return fstat($this->handle); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + } + } + + if (function_exists('stream_wrapper_register') && stream_wrapper_register('composer-bin-proxy', 'Composer\BinProxyWrapper')) { + include("composer-bin-proxy://" . $binPath); + exit(0); + } +} + +include $binPath; diff --git a/lib/bin/var-dump-server b/lib/bin/var-dump-server new file mode 100644 index 0000000000..10567b2c7d --- /dev/null +++ b/lib/bin/var-dump-server @@ -0,0 +1,97 @@ +#!/usr/bin/env php +handle = fopen($opened_path, $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; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return fstat($this->handle); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + } + } + + if (function_exists('stream_wrapper_register') && stream_wrapper_register('composer-bin-proxy', 'Composer\BinProxyWrapper')) { + include("composer-bin-proxy://" . $binPath); + exit(0); + } +} + +include $binPath; diff --git a/lib/bin/var-dump-server.bat b/lib/bin/var-dump-server.bat new file mode 100644 index 0000000000..46836b50cd --- /dev/null +++ b/lib/bin/var-dump-server.bat @@ -0,0 +1,4 @@ +@ECHO OFF +setlocal DISABLEDELAYEDEXPANSION +SET BIN_TARGET=%~dp0/../symfony/var-dumper/Resources/bin/var-dump-server +php "%BIN_TARGET%" %* diff --git a/lib/bin/yaml-lint b/lib/bin/yaml-lint new file mode 100644 index 0000000000..182c83f098 --- /dev/null +++ b/lib/bin/yaml-lint @@ -0,0 +1,97 @@ +#!/usr/bin/env php +handle = fopen($opened_path, $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; + } + + public function stream_read($count) + { + $data = fread($this->handle, $count); + + if ($this->position === 0) { + $data = preg_replace('{^#!.*\r?\n}', '', $data); + } + + $this->position += strlen($data); + + return $data; + } + + public function stream_cast($castAs) + { + return $this->handle; + } + + public function stream_close() + { + fclose($this->handle); + } + + public function stream_lock($operation) + { + return $operation ? flock($this->handle, $operation) : true; + } + + public function stream_tell() + { + return $this->position; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_stat() + { + return fstat($this->handle); + } + + public function stream_set_option($option, $arg1, $arg2) + { + return true; + } + } + } + + if (function_exists('stream_wrapper_register') && stream_wrapper_register('composer-bin-proxy', 'Composer\BinProxyWrapper')) { + include("composer-bin-proxy://" . $binPath); + exit(0); + } +} + +include $binPath; diff --git a/lib/bin/yaml-lint.bat b/lib/bin/yaml-lint.bat new file mode 100644 index 0000000000..0474134c60 --- /dev/null +++ b/lib/bin/yaml-lint.bat @@ -0,0 +1,4 @@ +@ECHO OFF +setlocal DISABLEDELAYEDEXPANSION +SET BIN_TARGET=%~dp0/../symfony/yaml/Resources/bin/yaml-lint +php "%BIN_TARGET%" %* diff --git a/lib/combodo/tcpdf/.github/FUNDING.yml b/lib/combodo/tcpdf/.github/FUNDING.yml deleted file mode 100644 index ca5b4292e0..0000000000 --- a/lib/combodo/tcpdf/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -custom: ['https://www.paypal.com/cgi-bin/webscr?cmd=_donations¤cy_code=GBP&business=paypal@tecnick.com&item_name=donation%20for%20tcpdf%20project'] diff --git a/lib/combodo/tcpdf/.gitignore b/lib/combodo/tcpdf/.gitignore deleted file mode 100644 index 723ef36f4e..0000000000 --- a/lib/combodo/tcpdf/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.idea \ No newline at end of file diff --git a/lib/combodo/tcpdf/CHANGELOG.TXT b/lib/combodo/tcpdf/CHANGELOG.TXT index 3bdae3e245..e90bd161a9 100644 --- a/lib/combodo/tcpdf/CHANGELOG.TXT +++ b/lib/combodo/tcpdf/CHANGELOG.TXT @@ -1,16 +1,129 @@ -6.2.25 +6.4.4 (2021-12-31) + - PHP 8.1 fixes + +6.4.3 (2021-12-28) + - Fix MultiCell PHPDoc typehint (#407) + - Fix type hint for \TCPDF_STATIC::_freadint (#414) + - Footer and Header font phpdoc fixes + constructor $pdfa phpdoc fix + setHeaderData lw param fix (#402) + - Fix text-annotation state options (#412) + - Fix - Named links have been broken. This fixes. (#415) + - Fixed type in comment for $lw header image logo width in mm + - Change Set to set. Fixes #419 (#421) + - Fix failing tests and failing tests not marking exit code as 1 (#426) + - Fix phpdoc and prefer null as default value (#444) + - Run on PHP 8.1 normally and add nightly PHP as allowed to fail (#452) + - Fix AES128 encryption if the OpenSSL extension is installed (#453) + - Explicitly cast values to int for imagesetpixel (#460) + - Fix cell_height_ratio type (#405) + - Leave &NBSP; lowercase when using text-transform (#403) + +6.4.2 (2021-07-20) + - Fix PHP 8.1 type error with TCPDF_STATIC::pregSplit on preg_split + - Fix a PHP array offset error + - Fixed phpdoc blocks + - Drop a PHP 4 polyfill and add a .gitattributes file + - Added a test-suite + - Removed pointless assignments + - Fix docblock spelling error + - Update version info + - Fix color being filled to type 0 with PHP 8 + - Fix warnings for undefined tags for $lineStyle + - Normalized composer.json + - Allowed transparency in PDF/A-2 and PDF/A-3 + - Add a TCPDF composer example + - Fixed implicit conversion from float to int for PHP 8.1 + - Removed status.txt from font directories, because of filesize + - Fixed type hints + - Removed "U" modifier from regexes + +6.4.1 (2021-03-27) + - Update tcpdf version (no code changes) + +6.4.0 (2021-03-27) + - allow styles on
    tags + - check if file exists before calling unlink + - Fix image file type for urls with query params + - Fix SVGPath should accept 1.19.30 (equiv 1.19,.30) compacted values list + - Fix Second parameter of TCPDF::cell() must be a number + - PHP 8.0 function signature fixes + - Fix vulnerability to roman numeral bombs + - Optimized a regular expression + - Cache file get contents calls + - Remove mb_internal encoding handling + +6.3.5 (2020-02-14) + - Fixed curly braces in pdf417 + - Fixed a syntax error issue when accessing an index of a casted variable + +6.3.4 (2020-02-12) + - Check if imagekeys exist + - Unlink only images in cache + +6.3.3 (2020-02-12) + - Fixed PHP 7.4 - cannot use array offset on integers + - Fixed PDF/A-3B validation issue caused by missing pdfaSchema:property. + - Removed backup changelog files from repo + - Prevents the deletion of non-existent files in /tmp + - Prevent crash in case of no list access in cache path + - Check existence of file before delete it + - Fixed erase users pictures + - Fixed problem with $imagekeys undefined or unlinked + - Fix SVGPath elliptical arc with rx/ry=0 + z should return to initial point + - Fixed PHP 7.4 errors + - handle integers for pages + - Fixed background image doesn't work in RTL + - Fixed PDF/A validity + - Fixed datamatrix.php for PHP 7.4 + - Fixed deprecated PHP features + +6.3.2 (2019-09-20) + - Update ICC profile + +6.3.1 (2019-09-20) + - Fix reported version + - Fix Undefined property: GLPIPDF::$imagekeys + +6.3.0 (2019-09-19) + - fix SpotColor handling in HTML + - Add an additional empty test to prevent error in PHP 7.2 + - Fix the documentation how to calculate the cell height + - Drop duplicated array indices + - Fix TCPDF_STATIC::fileGetContents() + - Introduce other version of pdfA (2 and 3) + - Add UF and AFRelationship missing + - Fix performance issue of cloned instances + - Change glob to readdir which performs better + - URI in PDF can result in E_NOTICE + - Fix a warning for PHP 7.4 + - Fixed gradient offsets for percentage-based stops. + - Fixed file_get_contents return value should also be checked for a non-empty string + - Fix Array and string offset access syntax with curly braces is deprecated + - Fix PHP Warning: chr() expects parameter 1 to be int + - Add a VERSION file + +6.2.26 (2018-10-16) + - Update sRGB.icc with the one from the Debian package icc-profiles-free + - Fix unsupported operand types error when codepoints arrays are merged + +6.2.25 (2018-09-23) - Fix support for image URLs. 6.2.24 - Support remote urls when checking if file exists. -6.2.23 +6.2.23 (2018-09-22) - Simplify file_exists function. -6.2.22 +6.2.22 (2018-09-14) + - Fixes on `include/tcpdf_images.php`, `include/tcpdf_static.php` and `tcpdf.php` about file handling + +6.2.21 (2018-09-14) + - _no code changes_ + +6.2.20 (2018-09-14) - Fix for security vulnerability: Using the phar:// wrapper it was possible to trigger the unserialization of user provided data. -6.2.19 +6.2.19 (2018-09-14) - Merge various fixes for PHP 7.3 compatibility and security. 6.2.13 (2016-06-10) diff --git a/lib/combodo/tcpdf/README.md b/lib/combodo/tcpdf/README.md index db0149f69c..0fb94009b0 100644 --- a/lib/combodo/tcpdf/README.md +++ b/lib/combodo/tcpdf/README.md @@ -6,7 +6,7 @@ * **category** Library * **author** Nicola Asuni -* **copyright** 2002-2020 Nicola Asuni - Tecnick.com LTD +* **copyright** 2002-2021 Nicola Asuni - Tecnick.com LTD * **license** http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) * **link** http://www.tcpdf.org * **source** https://github.com/tecnickcom/TCPDF diff --git a/lib/combodo/tcpdf/VERSION b/lib/combodo/tcpdf/VERSION index b98d1d3fa7..49df80bfeb 100644 --- a/lib/combodo/tcpdf/VERSION +++ b/lib/combodo/tcpdf/VERSION @@ -1 +1 @@ -6.3.5 +6.4.4 diff --git a/lib/combodo/tcpdf/composer.json b/lib/combodo/tcpdf/composer.json index fe90ae9eb5..b53dd6e7f0 100644 --- a/lib/combodo/tcpdf/composer.json +++ b/lib/combodo/tcpdf/composer.json @@ -3,9 +3,19 @@ "replace": { "tecnickcom/tcpdf": "self.version" }, - "homepage": "https://github.com/combodo-itop-libs/TCPDF", "type": "library", - "description": "TCPDF fork adding requirements for iTop: Specific fonts.", + "description": "TCPDF is a PHP class for generating PDF documents and barcodes.", + "keywords": [ + "PDF", + "tcpdf", + "PDFD32000-2008", + "qrcode", + "datamatrix", + "pdf417", + "barcodes" + ], + "homepage": "https://github.com/combodo-itop-libs/TCPDF", + "version": "6.4.4", "license": "LGPL-3.0-only", "authors": [ { diff --git a/lib/combodo/tcpdf/include/barcodes/datamatrix.php b/lib/combodo/tcpdf/include/barcodes/datamatrix.php index 783f99da4c..38bd625f7b 100644 --- a/lib/combodo/tcpdf/include/barcodes/datamatrix.php +++ b/lib/combodo/tcpdf/include/barcodes/datamatrix.php @@ -229,7 +229,7 @@ class Datamatrix { /** * This is the class constructor. * Creates a datamatrix object - * @param $code (string) Code to represent using Datamatrix. + * @param string $code Code to represent using Datamatrix. * @public */ public function __construct($code) { @@ -355,11 +355,11 @@ class Datamatrix { /** * Product of two numbers in a Power-of-Two Galois Field - * @param $a (int) first number to multiply. - * @param $b (int) second number to multiply. - * @param $log (array) Log table. - * @param $alog (array) Anti-Log table. - * @param $gf (array) Number of Factors of the Reed-Solomon polynomial. + * @param int $a first number to multiply. + * @param int $b second number to multiply. + * @param array $log Log table. + * @param array $alog Anti-Log table. + * @param int $gf Number of Factors of the Reed-Solomon polynomial. * @return int product * @protected */ @@ -372,12 +372,12 @@ class Datamatrix { /** * Add error correction codewords to data codewords array (ANNEX E). - * @param $wd (array) Array of datacodewords. - * @param $nb (int) Number of blocks. - * @param $nd (int) Number of data codewords per block. - * @param $nc (int) Number of correction codewords per block. - * @param $gf (int) numner of fields on log/antilog table (power of 2). - * @param $pp (int) The value of its prime modulus polynomial (301 for ECC200). + * @param array $wd Array of datacodewords. + * @param int $nb Number of blocks. + * @param int $nd Number of data codewords per block. + * @param int $nc Number of correction codewords per block. + * @param int $gf numner of fields on log/antilog table (power of 2). + * @param int $pp The value of its prime modulus polynomial (301 for ECC200). * @return array data codewords + error codewords * @protected */ @@ -438,9 +438,9 @@ class Datamatrix { /** * Return the 253-state codeword - * @param $cwpad (int) Pad codeword. - * @param $cwpos (int) Number of data codewords from the beginning of encoded data. - * @return pad codeword + * @param int $cwpad Pad codeword. + * @param int $cwpos Number of data codewords from the beginning of encoded data. + * @return int pad codeword * @protected */ protected function get253StateCodeword($cwpad, $cwpos) { @@ -453,9 +453,9 @@ class Datamatrix { /** * Return the 255-state codeword - * @param $cwpad (int) Pad codeword. - * @param $cwpos (int) Number of data codewords from the beginning of encoded data. - * @return pad codeword + * @param int $cwpad Pad codeword. + * @param int $cwpos Number of data codewords from the beginning of encoded data. + * @return int pad codeword * @protected */ protected function get255StateCodeword($cwpad, $cwpos) { @@ -468,8 +468,8 @@ class Datamatrix { /** * Returns true if the char belongs to the selected mode - * @param $chr (int) Character (byte) to check. - * @param $mode (int) Current encoding mode. + * @param int $chr Character (byte) to check. + * @param int $mode Current encoding mode. * @return boolean true if the char is of the selected mode. * @protected */ @@ -514,9 +514,9 @@ class Datamatrix { /** * The look-ahead test scans the data to be encoded to find the best mode (Annex P - steps from J to S). - * @param $data (string) data to encode - * @param $pos (int) current position - * @param $mode (int) current encoding mode + * @param string $data data to encode + * @param int $pos current position + * @param int $mode current encoding mode * @return int encoding mode * @protected */ @@ -646,8 +646,8 @@ class Datamatrix { /** * Get the switching codeword to a new encoding mode (latch codeword) - * @param $mode (int) New encoding mode. - * @return (int) Switch codeword. + * @param int $mode New encoding mode. + * @return int Switch codeword. * @protected */ protected function getSwitchEncodingCodeword($mode) { @@ -685,8 +685,8 @@ class Datamatrix { /** * Choose the minimum matrix size and return the max number of data codewords. - * @param $numcw (int) Number of current codewords. - * @return number of data codewords in matrix + * @param int $numcw Number of current codewords. + * @return int number of data codewords in matrix * @protected */ protected function getMaxDataCodewords($numcw) { @@ -700,7 +700,7 @@ class Datamatrix { /** * Get high level encoding using the minimum symbol data characters for ECC 200 - * @param $data (string) data to encode + * @param string $data data to encode * @return array of codewords * @protected */ @@ -970,13 +970,13 @@ class Datamatrix { /** * Places "chr+bit" with appropriate wrapping within array[]. * (Annex F - ECC 200 symbol character placement) - * @param $marr (array) Array of symbols. - * @param $nrow (int) Number of rows. - * @param $ncol (int) Number of columns. - * @param $row (int) Row number. - * @param $col (int) Column number. - * @param $chr (int) Char byte. - * @param $bit (int) Bit. + * @param array $marr Array of symbols. + * @param int $nrow Number of rows. + * @param int $ncol Number of columns. + * @param int $row Row number. + * @param int $col Column number. + * @param int $chr Char byte. + * @param int $bit Bit. * @return array * @protected */ @@ -996,12 +996,12 @@ class Datamatrix { /** * Places the 8 bits of a utah-shaped symbol character. * (Annex F - ECC 200 symbol character placement) - * @param $marr (array) Array of symbols. - * @param $nrow (int) Number of rows. - * @param $ncol (int) Number of columns. - * @param $row (int) Row number. - * @param $col (int) Column number. - * @param $chr (int) Char byte. + * @param array $marr Array of symbols. + * @param int $nrow Number of rows. + * @param int $ncol Number of columns. + * @param int $row Row number. + * @param int $col Column number. + * @param int $chr Char byte. * @return array * @protected */ @@ -1020,10 +1020,10 @@ class Datamatrix { /** * Places the 8 bits of the first special corner case. * (Annex F - ECC 200 symbol character placement) - * @param $marr (array) Array of symbols. - * @param $nrow (int) Number of rows. - * @param $ncol (int) Number of columns. - * @param $chr (int) Char byte. + * @param array $marr Array of symbols. + * @param int $nrow Number of rows. + * @param int $ncol Number of columns. + * @param int $chr Char byte. * @return array * @protected */ @@ -1042,10 +1042,10 @@ class Datamatrix { /** * Places the 8 bits of the second special corner case. * (Annex F - ECC 200 symbol character placement) - * @param $marr (array) Array of symbols. - * @param $nrow (int) Number of rows. - * @param $ncol (int) Number of columns. - * @param $chr (int) Char byte. + * @param array $marr Array of symbols. + * @param int $nrow Number of rows. + * @param int $ncol Number of columns. + * @param int $chr Char byte. * @return array * @protected */ @@ -1064,10 +1064,10 @@ class Datamatrix { /** * Places the 8 bits of the third special corner case. * (Annex F - ECC 200 symbol character placement) - * @param $marr (array) Array of symbols. - * @param $nrow (int) Number of rows. - * @param $ncol (int) Number of columns. - * @param $chr (int) Char byte. + * @param array $marr Array of symbols. + * @param int $nrow Number of rows. + * @param int $ncol Number of columns. + * @param int $chr Char byte. * @return array * @protected */ @@ -1086,10 +1086,10 @@ class Datamatrix { /** * Places the 8 bits of the fourth special corner case. * (Annex F - ECC 200 symbol character placement) - * @param $marr (array) Array of symbols. - * @param $nrow (int) Number of rows. - * @param $ncol (int) Number of columns. - * @param $chr (int) Char byte. + * @param array $marr Array of symbols. + * @param int $nrow Number of rows. + * @param int $ncol Number of columns. + * @param int $chr Char byte. * @return array * @protected */ @@ -1108,8 +1108,8 @@ class Datamatrix { /** * Build a placement map. * (Annex F - ECC 200 symbol character placement) - * @param $nrow (int) Number of rows. - * @param $ncol (int) Number of columns. + * @param int $nrow Number of rows. + * @param int $ncol Number of columns. * @return array * @protected */ diff --git a/lib/combodo/tcpdf/include/barcodes/pdf417.php b/lib/combodo/tcpdf/include/barcodes/pdf417.php index 9a58a21f67..742802e1fb 100644 --- a/lib/combodo/tcpdf/include/barcodes/pdf417.php +++ b/lib/combodo/tcpdf/include/barcodes/pdf417.php @@ -523,10 +523,10 @@ class PDF417 { /** * This is the class constructor. * Creates a PDF417 object - * @param $code (string) code to represent using PDF417 - * @param $ecl (int) error correction level (0-8); default -1 = automatic correction level - * @param $aspectratio (float) the width to height of the symbol (excluding quiet zones) - * @param $macro (array) information for macro block + * @param string $code code to represent using PDF417 + * @param int $ecl error correction level (0-8); default -1 = automatic correction level + * @param float $aspectratio the width to height of the symbol (excluding quiet zones) + * @param array $macro information for macro block * @public */ public function __construct($code, $ecl=-1, $aspectratio=2, $macro=array()) { @@ -734,12 +734,13 @@ class PDF417 { /** * Returns the error correction level (0-8) to be used - * @param $ecl (int) error correction level - * @param $numcw (int) number of data codewords + * @param int $ecl error correction level + * @param int $numcw number of data codewords * @return int error correction level * @protected */ protected function getErrorCorrectionLevel($ecl, $numcw) { + $maxecl = 8; // starting error level // check for automatic levels if (($ecl < 0) OR ($ecl > 8)) { if ($numcw < 41) { @@ -755,7 +756,6 @@ class PDF417 { } } // get maximum correction level - $maxecl = 8; // starting error level $maxerrsize = (928 - $numcw); // available codewords for error while ($maxecl > 0) { $errsize = (2 << $ecl); @@ -772,8 +772,8 @@ class PDF417 { /** * Returns the error correction codewords - * @param $cw (array) array of codewords including Symbol Length Descriptor and pad - * @param $ecl (int) error correction level 0-8 + * @param array $cw array of codewords including Symbol Length Descriptor and pad + * @param int $ecl error correction level 0-8 * @return array of error correction codewords * @protected */ @@ -809,8 +809,8 @@ class PDF417 { /** * Create array of sequences from input - * @param $code (string) code - * @return bidimensional array containing characters and classification + * @param string $code code + * @return array bi-dimensional array containing characters and classification * @protected */ protected function getInputSequences($code) { @@ -864,9 +864,9 @@ class PDF417 { /** * Compact data by mode. - * @param $mode (int) compaction mode number - * @param $code (string) data to compact - * @param $addmode (boolean) if true add the mode codeword at first position + * @param int $mode compaction mode number + * @param string $code data to compact + * @param boolean $addmode if true add the mode codeword at first position * @return array of codewords * @protected */ diff --git a/lib/combodo/tcpdf/include/barcodes/qrcode.php b/lib/combodo/tcpdf/include/barcodes/qrcode.php index 7ef2759fd6..2e7f2f5768 100644 --- a/lib/combodo/tcpdf/include/barcodes/qrcode.php +++ b/lib/combodo/tcpdf/include/barcodes/qrcode.php @@ -247,32 +247,6 @@ if (!defined('QRCODEDEFS')) { } // end of definitions -// #*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*# - -// for compatibility with PHP4 -if (!function_exists('str_split')) { - /** - * Convert a string to an array (needed for PHP4 compatibility) - * @param $string (string) The input string. - * @param $split_length (int) Maximum length of the chunk. - * @return If the optional split_length parameter is specified, the returned array will be broken down into chunks with each being split_length in length, otherwise each chunk will be one character in length. FALSE is returned if split_length is less than 1. If the split_length length exceeds the length of string , the entire string is returned as the first (and only) array element. - */ - function str_split($string, $split_length=1) { - if ((strlen($string) > $split_length) OR (!$split_length)) { - do { - $c = strlen($string); - $parts[] = substr($string, 0, $split_length); - $string = substr($string, $split_length); - } while ($string !== false); - } else { - $parts = array($string); - } - return $parts; - } -} - -// ##################################################### - /** * @class QRcode * Class to create QR-code arrays for TCPDF class. @@ -631,8 +605,8 @@ class QRcode { /** * This is the class constructor. * Creates a QRcode object - * @param $code (string) code to represent using QRcode - * @param $eclevel (string) error level:
    • L : About 7% or less errors can be corrected.
    • M : About 15% or less errors can be corrected.
    • Q : About 25% or less errors can be corrected.
    • H : About 30% or less errors can be corrected.
    + * @param string $code code to represent using QRcode + * @param string $eclevel error level:
    • L : About 7% or less errors can be corrected.
    • M : About 15% or less errors can be corrected.
    • Q : About 25% or less errors can be corrected.
    • H : About 30% or less errors can be corrected.
    * @public * @since 1.0.000 */ @@ -683,7 +657,7 @@ class QRcode { /** * Convert the frame in binary form - * @param $frame (array) array to binarize + * @param array $frame array to binarize * @return array frame in binary form */ protected function binarize($frame) { @@ -699,7 +673,7 @@ class QRcode { /** * Encode the input string to QR code - * @param $string (string) input string to encode + * @param string $string input string to encode */ protected function encodeString($string) { $this->dataStr = $string; @@ -715,7 +689,7 @@ class QRcode { /** * Encode mask - * @param $mask (int) masking mode + * @param int $mask masking mode */ protected function encodeMask($mask) { $spec = array(0, 0, 0, 0, 0); @@ -780,8 +754,8 @@ class QRcode { /** * Set frame value at specified position - * @param $at (array) x,y position - * @param $val (int) value of the character to set + * @param array $at x,y position + * @param int $val value of the character to set */ protected function setFrameAt($at, $val) { $this->frame[$at['y']][$at['x']] = chr($val); @@ -789,7 +763,7 @@ class QRcode { /** * Get frame value at specified position - * @param $at (array) x,y position + * @param array $at x,y position * @return value at specified position */ protected function getFrameAt($at) { @@ -853,8 +827,8 @@ class QRcode { /** * Initialize code. - * @param $spec (array) array of ECC specification - * @return 0 in case of success, -1 in case of error + * @param array $spec array of ECC specification + * @return int 0 in case of success, -1 in case of error */ protected function init($spec) { $dl = $this->rsDataCodes1($spec); @@ -932,10 +906,10 @@ class QRcode { /** * Write Format Information on frame and returns the number of black bits - * @param $width (int) frame width - * @param $frame (array) frame - * @param $mask (array) masking mode - * @param $level (int) error correction level + * @param int $width frame width + * @param array $frame frame + * @param array $mask masking mode + * @param int $level error correction level * @return int blacks */ protected function writeFormatInformation($width, &$frame, $mask, $level) { @@ -976,8 +950,8 @@ class QRcode { /** * mask0 - * @param $x (int) X position - * @param $y (int) Y position + * @param int $x X position + * @param int $y Y position * @return int mask */ protected function mask0($x, $y) { @@ -986,8 +960,8 @@ class QRcode { /** * mask1 - * @param $x (int) X position - * @param $y (int) Y position + * @param int $x X position + * @param int $y Y position * @return int mask */ protected function mask1($x, $y) { @@ -996,8 +970,8 @@ class QRcode { /** * mask2 - * @param $x (int) X position - * @param $y (int) Y position + * @param int $x X position + * @param int $y Y position * @return int mask */ protected function mask2($x, $y) { @@ -1006,8 +980,8 @@ class QRcode { /** * mask3 - * @param $x (int) X position - * @param $y (int) Y position + * @param int $x X position + * @param int $y Y position * @return int mask */ protected function mask3($x, $y) { @@ -1016,8 +990,8 @@ class QRcode { /** * mask4 - * @param $x (int) X position - * @param $y (int) Y position + * @param int $x X position + * @param int $y Y position * @return int mask */ protected function mask4($x, $y) { @@ -1026,8 +1000,8 @@ class QRcode { /** * mask5 - * @param $x (int) X position - * @param $y (int) Y position + * @param int $x X position + * @param int $y Y position * @return int mask */ protected function mask5($x, $y) { @@ -1036,8 +1010,8 @@ class QRcode { /** * mask6 - * @param $x (int) X position - * @param $y (int) Y position + * @param int $x X position + * @param int $y Y position * @return int mask */ protected function mask6($x, $y) { @@ -1046,8 +1020,8 @@ class QRcode { /** * mask7 - * @param $x (int) X position - * @param $y (int) Y position + * @param int $x X position + * @param int $y Y position * @return int mask */ protected function mask7($x, $y) { @@ -1056,9 +1030,9 @@ class QRcode { /** * Return bitmask - * @param $maskNo (int) mask number - * @param $width (int) width - * @param $frame (array) frame + * @param int $maskNo mask number + * @param int $width width + * @param array $frame frame * @return array bitmask */ protected function generateMaskNo($maskNo, $width, $frame) { @@ -1078,11 +1052,11 @@ class QRcode { /** * makeMaskNo - * @param $maskNo (int) - * @param $width (int) - * @param $s (int) - * @param $d (int) - * @param $maskGenOnly (boolean) + * @param int $maskNo + * @param int $width + * @param int $s + * @param int $d + * @param boolean $maskGenOnly * @return int b */ protected function makeMaskNo($maskNo, $width, $s, &$d, $maskGenOnly=false) { @@ -1106,10 +1080,10 @@ class QRcode { /** * makeMask - * @param $width (int) - * @param $frame (array) - * @param $maskNo (int) - * @param $level (int) + * @param int $width + * @param array $frame + * @param int $maskNo + * @param int $level * @return array mask */ protected function makeMask($width, $frame, $maskNo, $level) { @@ -1121,7 +1095,7 @@ class QRcode { /** * calcN1N3 - * @param $length (int) + * @param int $length * @return int demerit */ protected function calcN1N3($length) { @@ -1151,8 +1125,8 @@ class QRcode { /** * evaluateSymbol - * @param $width (int) - * @param $frame (array) + * @param int $width + * @param array $frame * @return int demerit */ protected function evaluateSymbol($width, $frame) { @@ -1212,9 +1186,9 @@ class QRcode { /** * mask - * @param $width (int) - * @param $frame (array) - * @param $level (int) + * @param int $width + * @param array $frame + * @param int $level * @return array best mask */ protected function mask($width, $frame, $level) { @@ -1255,8 +1229,8 @@ class QRcode { /** * Return true if the character at specified position is a number - * @param $str (string) string - * @param $pos (int) characted position + * @param string $str string + * @param int $pos characted position * @return boolean true of false */ protected function isdigitat($str, $pos) { @@ -1268,8 +1242,8 @@ class QRcode { /** * Return true if the character at specified position is an alphanumeric character - * @param $str (string) string - * @param $pos (int) characted position + * @param string $str string + * @param int $pos characted position * @return boolean true of false */ protected function isalnumat($str, $pos) { @@ -1281,7 +1255,7 @@ class QRcode { /** * identifyMode - * @param $pos (int) + * @param int $pos * @return int mode */ protected function identifyMode($pos) { @@ -1386,6 +1360,7 @@ class QRcode { $p += 2; } $this->items = $this->appendNewInputItem($this->items, QR_MODE_KJ, $p, str_split($this->dataStr)); + $run = $p; return $run; } @@ -1440,7 +1415,7 @@ class QRcode { /** * splitString - * @return (int) + * @return int */ protected function splitString() { while (strlen($this->dataStr) > 0) { @@ -1455,7 +1430,7 @@ class QRcode { break; } case QR_MODE_KJ: { - if ($hint == QR_MODE_KJ) { + if ($this->hint == QR_MODE_KJ) { $length = $this->eatKanji(); } else { $length = $this->eat8(); @@ -1504,10 +1479,10 @@ class QRcode { /** * newInputItem - * @param $mode (int) - * @param $size (int) - * @param $data (array) - * @param $bstream (array) + * @param int $mode + * @param int $size + * @param array $data + * @param array $bstream * @return array input item */ protected function newInputItem($mode, $size, $data, $bstream=null) { @@ -1528,8 +1503,8 @@ class QRcode { /** * encodeModeNum - * @param $inputitem (array) - * @param $version (int) + * @param array $inputitem + * @param int $version * @return array input item */ protected function encodeModeNum($inputitem, $version) { @@ -1557,8 +1532,8 @@ class QRcode { /** * encodeModeAn - * @param $inputitem (array) - * @param $version (int) + * @param array $inputitem + * @param int $version * @return array input item */ protected function encodeModeAn($inputitem, $version) { @@ -1580,8 +1555,8 @@ class QRcode { /** * encodeMode8 - * @param $inputitem (array) - * @param $version (int) + * @param array $inputitem + * @param int $version * @return array input item */ protected function encodeMode8($inputitem, $version) { @@ -1596,8 +1571,8 @@ class QRcode { /** * encodeModeKanji - * @param $inputitem (array) - * @param $version (int) + * @param array $inputitem + * @param int $version * @return array input item */ protected function encodeModeKanji($inputitem, $version) { @@ -1620,7 +1595,7 @@ class QRcode { /** * encodeModeStructure - * @param $inputitem (array) + * @param array $inputitem * @return array input item */ protected function encodeModeStructure($inputitem) { @@ -1634,8 +1609,8 @@ class QRcode { /** * encodeBitStream - * @param $inputitem (array) - * @param $version (int) + * @param array $inputitem + * @param int $version * @return array input item */ protected function encodeBitStream($inputitem, $version) { @@ -1686,11 +1661,11 @@ class QRcode { /** * Append data to an input object. * The data is copied and appended to the input object. - * @param $items (arrray) input items - * @param $mode (int) encoding mode. - * @param $size (int) size of data (byte). - * @param $data (array) array of input data. - * @return items + * @param array $items input items + * @param int $mode encoding mode. + * @param int $size size of data (byte). + * @param array $data array of input data. + * @return array items * */ protected function appendNewInputItem($items, $mode, $size, $data) { @@ -1703,10 +1678,10 @@ class QRcode { /** * insertStructuredAppendHeader - * @param $items (array) - * @param $size (int) - * @param $index (int) - * @param $parity (int) + * @param array $items + * @param int $size + * @param int $index + * @param int $parity * @return array items */ protected function insertStructuredAppendHeader($items, $size, $index, $parity) { @@ -1724,7 +1699,7 @@ class QRcode { /** * calcParity - * @param $items (array) + * @param array $items * @return int parity */ protected function calcParity($items) { @@ -1741,8 +1716,8 @@ class QRcode { /** * checkModeNum - * @param $size (int) - * @param $data (array) + * @param int $size + * @param array $data * @return boolean true or false */ protected function checkModeNum($size, $data) { @@ -1755,9 +1730,9 @@ class QRcode { } /** - * Look up the alphabet-numeric convesion table (see JIS X0510:2004, pp.19). - * @param $c (int) character value - * @return value + * Look up the alphabet-numeric conversion table (see JIS X0510:2004, pp.19). + * @param int $c character value + * @return int value */ protected function lookAnTable($c) { return (($c > 127)?-1:$this->anTable[$c]); @@ -1765,8 +1740,8 @@ class QRcode { /** * checkModeAn - * @param $size (int) - * @param $data (array) + * @param int $size + * @param array $data * @return boolean true or false */ protected function checkModeAn($size, $data) { @@ -1780,7 +1755,7 @@ class QRcode { /** * estimateBitsModeNum - * @param $size (int) + * @param int $size * @return int number of bits */ protected function estimateBitsModeNum($size) { @@ -1801,7 +1776,7 @@ class QRcode { /** * estimateBitsModeAn - * @param $size (int) + * @param int $size * @return int number of bits */ protected function estimateBitsModeAn($size) { @@ -1814,7 +1789,7 @@ class QRcode { /** * estimateBitsMode8 - * @param $size (int) + * @param int $size * @return int number of bits */ protected function estimateBitsMode8($size) { @@ -1823,7 +1798,7 @@ class QRcode { /** * estimateBitsModeKanji - * @param $size (int) + * @param int $size * @return int number of bits */ protected function estimateBitsModeKanji($size) { @@ -1832,8 +1807,8 @@ class QRcode { /** * checkModeKanji - * @param $size (int) - * @param $data (array) + * @param int $size + * @param array $data * @return boolean true or false */ protected function checkModeKanji($size, $data) { @@ -1851,9 +1826,9 @@ class QRcode { /** * Validate the input data. - * @param $mode (int) encoding mode. - * @param $size (int) size of data (byte). - * @param $data (array) data to validate + * @param int $mode encoding mode. + * @param int $size size of data (byte). + * @param array $data data to validate * @return boolean true in case of valid data, false otherwise */ protected function check($mode, $size, $data) { @@ -1885,8 +1860,8 @@ class QRcode { /** * estimateBitStreamSize - * @param $items (array) - * @param $version (int) + * @param array $items + * @param int $version * @return int bits */ protected function estimateBitStreamSize($items, $version) { @@ -1929,7 +1904,7 @@ class QRcode { /** * estimateVersion - * @param $items (array) + * @param array $items * @return int version */ protected function estimateVersion($items) { @@ -1948,9 +1923,9 @@ class QRcode { /** * lengthOfCode - * @param $mode (int) - * @param $version (int) - * @param $bits (int) + * @param int $mode + * @param int $version + * @param int $bits * @return int size */ protected function lengthOfCode($mode, $version, $bits) { @@ -2005,7 +1980,7 @@ class QRcode { /** * createBitStream - * @param $items (array) + * @param array $items * @return array of items and total bits */ protected function createBitStream($items) { @@ -2020,7 +1995,7 @@ class QRcode { /** * convertData - * @param $items (array) + * @param array $items * @return array items */ protected function convertData($items) { @@ -2049,7 +2024,7 @@ class QRcode { /** * Append Padding Bit to bitstream - * @param $bstream (array) + * @param array $bstream * @return array bitstream */ protected function appendPaddingBit($bstream) { @@ -2082,7 +2057,7 @@ class QRcode { /** * mergeBitStream - * @param $items (array) items + * @param array $items items * @return array bitstream */ protected function mergeBitStream($items) { @@ -2099,7 +2074,7 @@ class QRcode { /** * Returns a stream of bits. - * @param $items (int) + * @param int $items * @return array padded merged byte stream */ protected function getBitStream($items) { @@ -2109,7 +2084,7 @@ class QRcode { /** * Pack all bit streams padding bits into a byte array. - * @param $items (int) + * @param int $items * @return array padded merged byte stream */ protected function getByteStream($items) { @@ -2123,7 +2098,7 @@ class QRcode { /** * Return an array with zeros - * @param $setLength (int) array size + * @param int $setLength array size * @return array */ protected function allocate($setLength) { @@ -2132,8 +2107,8 @@ class QRcode { /** * Return new bitstream from number - * @param $bits (int) number of bits - * @param $num (int) number + * @param int $bits number of bits + * @param int $num number * @return array bitstream */ protected function newFromNum($bits, $num) { @@ -2152,8 +2127,8 @@ class QRcode { /** * Return new bitstream from bytes - * @param $size (int) size - * @param $data (array) bytes + * @param int $size size + * @param array $data bytes * @return array bitstream */ protected function newFromBytes($size, $data) { @@ -2176,8 +2151,8 @@ class QRcode { /** * Append one bitstream to another - * @param $bitstream (array) original bitstream - * @param $append (array) bitstream to append + * @param array $bitstream original bitstream + * @param array $append bitstream to append * @return array bitstream */ protected function appendBitstream($bitstream, $append) { @@ -2192,9 +2167,9 @@ class QRcode { /** * Append one bitstream created from number to another - * @param $bitstream (array) original bitstream - * @param $bits (int) number of bits - * @param $num (int) number + * @param array $bitstream original bitstream + * @param int $bits number of bits + * @param int $num number * @return array bitstream */ protected function appendNum($bitstream, $bits, $num) { @@ -2207,9 +2182,9 @@ class QRcode { /** * Append one bitstream created from bytes to another - * @param $bitstream (array) original bitstream - * @param $size (int) size - * @param $data (array) bytes + * @param array $bitstream original bitstream + * @param int $size size + * @param array $data bytes * @return array bitstream */ protected function appendBytes($bitstream, $size, $data) { @@ -2222,7 +2197,7 @@ class QRcode { /** * Convert bitstream to bytes - * @param $bstream (array) original bitstream + * @param array $bstream original bitstream * @return array of bytes */ protected function bitstreamToByte($bstream) { @@ -2263,11 +2238,11 @@ class QRcode { /** * Replace a value on the array at the specified position - * @param $srctab (array) - * @param $x (int) X position - * @param $y (int) Y position - * @param $repl (string) value to replace - * @param $replLen (int) length of the repl string + * @param array $srctab + * @param int $x X position + * @param int $y Y position + * @param string $repl value to replace + * @param int $replLen length of the repl string * @return array srctab */ protected function qrstrset($srctab, $x, $y, $repl, $replLen=false) { @@ -2277,8 +2252,8 @@ class QRcode { /** * Return maximum data code length (bytes) for the version. - * @param $version (int) version - * @param $level (int) error correction level + * @param int $version version + * @param int $level error correction level * @return int maximum size (bytes) */ protected function getDataLength($version, $level) { @@ -2287,8 +2262,8 @@ class QRcode { /** * Return maximum error correction code length (bytes) for the version. - * @param $version (int) version - * @param $level (int) error correction level + * @param int $version version + * @param int $level error correction level * @return int ECC size (bytes) */ protected function getECCLength($version, $level){ @@ -2297,7 +2272,7 @@ class QRcode { /** * Return the width of the symbol for the version. - * @param $version (int) version + * @param int $version version * @return int width */ protected function getWidth($version) { @@ -2306,7 +2281,7 @@ class QRcode { /** * Return the numer of remainder bits. - * @param $version (int) version + * @param int $version version * @return int number of remainder bits */ protected function getRemainder($version) { @@ -2315,8 +2290,8 @@ class QRcode { /** * Return a version number that satisfies the input code length. - * @param $size (int) input code length (bytes) - * @param $level (int) error correction level + * @param int $size input code length (bytes) + * @param int $level error correction level * @return int version number */ protected function getMinimumVersion($size, $level) { @@ -2332,8 +2307,8 @@ class QRcode { /** * Return the size of length indicator for the mode and version. - * @param $mode (int) encoding mode - * @param $version (int) version + * @param int $mode encoding mode + * @param int $version version * @return int the size of the appropriate length indicator (bits). */ protected function lengthIndicator($mode, $version) { @@ -2352,8 +2327,8 @@ class QRcode { /** * Return the maximum length for the mode and version. - * @param $mode (int) encoding mode - * @param $version (int) version + * @param int $mode encoding mode + * @param int $version version * @return int the maximum length (bytes) */ protected function maximumWords($mode, $version) { @@ -2377,9 +2352,9 @@ class QRcode { /** * Return an array of ECC specification. - * @param $version (int) version - * @param $level (int) error correction level - * @param $spec (array) an array of ECC specification contains as following: {# of type1 blocks, # of data code, # of ecc code, # of type2 blocks, # of data code} + * @param int $version version + * @param int $level error correction level + * @param array $spec an array of ECC specification contains as following: {# of type1 blocks, # of data code, # of ecc code, # of type2 blocks, # of data code} * @return array spec */ protected function getEccSpec($version, $level, $spec) { @@ -2408,9 +2383,9 @@ class QRcode { /** * Put an alignment marker. - * @param $frame (array) frame - * @param $ox (int) X center coordinate of the pattern - * @param $oy (int) Y center coordinate of the pattern + * @param array $frame frame + * @param int $ox X center coordinate of the pattern + * @param int $oy Y center coordinate of the pattern * @return array frame */ protected function putAlignmentMarker($frame, $ox, $oy) { @@ -2431,9 +2406,9 @@ class QRcode { /** * Put an alignment pattern. - * @param $version (int) version - * @param $frame (array) frame - * @param $width (int) width + * @param int $version version + * @param array $frame frame + * @param int $width width * @return array frame */ protected function putAlignmentPattern($version, $frame, $width) { @@ -2473,8 +2448,8 @@ class QRcode { /** * Return BCH encoded version information pattern that is used for the symbol of version 7 or greater. Use lower 18 bits. - * @param $version (int) version - * @return BCH encoded version information pattern + * @param int $version version + * @return string BCH encoded version information pattern */ protected function getVersionPattern($version) { if (($version < 7) OR ($version > QRSPEC_VERSION_MAX)) { @@ -2485,9 +2460,9 @@ class QRcode { /** * Return BCH encoded format information pattern. - * @param $mask (array) - * @param $level (int) error correction level - * @return BCH encoded format information pattern + * @param array $mask + * @param int $level error correction level + * @return string BCH encoded format information pattern */ protected function getFormatInfo($mask, $level) { if (($mask < 0) OR ($mask > 7)) { @@ -2501,9 +2476,9 @@ class QRcode { /** * Put a finder pattern. - * @param $frame (array) frame - * @param $ox (int) X center coordinate of the pattern - * @param $oy (int) Y center coordinate of the pattern + * @param array $frame frame + * @param int $ox X center coordinate of the pattern + * @param int $oy Y center coordinate of the pattern * @return array frame */ protected function putFinderPattern($frame, $ox, $oy) { @@ -2524,8 +2499,8 @@ class QRcode { /** * Return a copy of initialized frame. - * @param $version (int) version - * @return Array of unsigned char. + * @param int $version version + * @return array array of unsigned char. */ protected function createFrame($version) { $width = $this->capacity[$version][QRCAP_WIDTH]; @@ -2589,8 +2564,8 @@ class QRcode { /** * Set new frame for the specified version. - * @param $version (int) version - * @return Array of unsigned char. + * @param int $version version + * @return array array of unsigned char. */ protected function newFrame($version) { if (($version < 1) OR ($version > QRSPEC_VERSION_MAX)) { @@ -2607,7 +2582,7 @@ class QRcode { /** * Return block number 0 - * @param $spec (array) + * @param array $spec * @return int value */ protected function rsBlockNum($spec) { @@ -2616,7 +2591,7 @@ class QRcode { /** * Return block number 1 - * @param $spec (array) + * @param array $spec * @return int value */ protected function rsBlockNum1($spec) { @@ -2625,7 +2600,7 @@ class QRcode { /** * Return data codes 1 - * @param $spec (array) + * @param array $spec * @return int value */ protected function rsDataCodes1($spec) { @@ -2634,7 +2609,7 @@ class QRcode { /** * Return ecc codes 1 - * @param $spec (array) + * @param array $spec * @return int value */ protected function rsEccCodes1($spec) { @@ -2643,7 +2618,7 @@ class QRcode { /** * Return block number 2 - * @param $spec (array) + * @param array $spec * @return int value */ protected function rsBlockNum2($spec) { @@ -2652,7 +2627,7 @@ class QRcode { /** * Return data codes 2 - * @param $spec (array) + * @param array $spec * @return int value */ protected function rsDataCodes2($spec) { @@ -2661,7 +2636,7 @@ class QRcode { /** * Return ecc codes 2 - * @param $spec (array) + * @param array $spec * @return int value */ protected function rsEccCodes2($spec) { @@ -2670,7 +2645,7 @@ class QRcode { /** * Return data length - * @param $spec (array) + * @param array $spec * @return int value */ protected function rsDataLength($spec) { @@ -2679,7 +2654,7 @@ class QRcode { /** * Return ecc length - * @param $spec (array) + * @param array $spec * @return int value */ protected function rsEccLength($spec) { @@ -2692,12 +2667,12 @@ class QRcode { /** * Initialize a Reed-Solomon codec and add it to existing rsitems - * @param $symsize (int) symbol size, bits - * @param $gfpoly (int) Field generator polynomial coefficients - * @param $fcr (int) first root of RS code generator polynomial, index form - * @param $prim (int) primitive element to generate polynomial roots - * @param $nroots (int) RS code generator polynomial degree (number of roots) - * @param $pad (int) padding bytes at front of shortened block + * @param int $symsize symbol size, bits + * @param int $gfpoly Field generator polynomial coefficients + * @param int $fcr first root of RS code generator polynomial, index form + * @param int $prim primitive element to generate polynomial roots + * @param int $nroots RS code generator polynomial degree (number of roots) + * @param int $pad padding bytes at front of shortened block * @return array Array of RS values:
    • mm = Bits per symbol;
    • nn = Symbols per block;
    • alpha_to = log lookup table array;
    • index_of = Antilog lookup table array;
    • genpoly = Generator polynomial array;
    • nroots = Number of generator;
    • roots = number of parity symbols;
    • fcr = First consecutive root, index form;
    • prim = Primitive element, index form;
    • iprim = prim-th root of 1, index form;
    • pad = Padding bytes in shortened block;
    • gfpoly
    . */ protected function init_rs($symsize, $gfpoly, $fcr, $prim, $nroots, $pad) { @@ -2719,8 +2694,8 @@ class QRcode { /** * modnn - * @param $rs (array) RS values - * @param $x (int) X position + * @param array $rs RS values + * @param int $x X position * @return int X osition */ protected function modnn($rs, $x) { @@ -2733,12 +2708,12 @@ class QRcode { /** * Initialize a Reed-Solomon codec and returns an array of values. - * @param $symsize (int) symbol size, bits - * @param $gfpoly (int) Field generator polynomial coefficients - * @param $fcr (int) first root of RS code generator polynomial, index form - * @param $prim (int) primitive element to generate polynomial roots - * @param $nroots (int) RS code generator polynomial degree (number of roots) - * @param $pad (int) padding bytes at front of shortened block + * @param int $symsize symbol size, bits + * @param int $gfpoly Field generator polynomial coefficients + * @param int $fcr first root of RS code generator polynomial, index form + * @param int $prim primitive element to generate polynomial roots + * @param int $nroots RS code generator polynomial degree (number of roots) + * @param int $pad padding bytes at front of shortened block * @return array Array of RS values:
    • mm = Bits per symbol;
    • nn = Symbols per block;
    • alpha_to = log lookup table array;
    • index_of = Antilog lookup table array;
    • genpoly = Generator polynomial array;
    • nroots = Number of generator;
    • roots = number of parity symbols;
    • fcr = First consecutive root, index form;
    • prim = Primitive element, index form;
    • iprim = prim-th root of 1, index form;
    • pad = Padding bytes in shortened block;
    • gfpoly
    . */ protected function init_rs_char($symsize, $gfpoly, $fcr, $prim, $nroots, $pad) { @@ -2820,9 +2795,9 @@ class QRcode { /** * Encode a Reed-Solomon codec and returns the parity array - * @param $rs (array) RS values - * @param $data (array) data - * @param $parity (array) parity + * @param array $rs RS values + * @param array $data data + * @param array $parity parity * @return parity array */ protected function encode_rs_char($rs, $data, $parity) { diff --git a/lib/combodo/tcpdf/include/tcpdf_colors.php b/lib/combodo/tcpdf/include/tcpdf_colors.php index 27fb7afd19..7f337f31a4 100644 --- a/lib/combodo/tcpdf/include/tcpdf_colors.php +++ b/lib/combodo/tcpdf/include/tcpdf_colors.php @@ -240,9 +240,9 @@ class TCPDF_COLORS { /** * Return the Spot color array. - * @param $name (string) Name of the spot color. - * @param $spotc (array) Reference to an array of spot colors. - * @return (array) Spot color array or false if not defined. + * @param string $name Name of the spot color. + * @param array $spotc Reference to an array of spot colors. + * @return array|false Spot color array or false if not defined. * @since 5.9.125 (2011-10-03) * @public static */ @@ -264,10 +264,10 @@ class TCPDF_COLORS { /** * Returns an array (RGB or CMYK) from an html color name, or a six-digit (i.e. #3FE5AA), or three-digit (i.e. #7FF) hexadecimal color, or a javascript color array, or javascript color name. - * @param $hcolor (string) HTML color. - * @param $spotc (array) Reference to an array of spot colors. - * @param $defcol (array) Color to return in case of error. - * @return array RGB or CMYK color, or false in case of error. + * @param string $hcolor HTML color. + * @param array $spotc Reference to an array of spot colors. + * @param array $defcol Color to return in case of error. + * @return array|false RGB or CMYK color, or false in case of error. * @public static */ public static function convertHTMLColorToDec($hcolor, &$spotc, $defcol=array('R'=>128,'G'=>128,'B'=>128)) { @@ -407,8 +407,8 @@ class TCPDF_COLORS { /** * Convert a color array into a string representation. - * @param $c (array) Array of colors. - * @return (string) The color array representation. + * @param array $c Array of colors. + * @return string The color array representation. * @since 5.9.137 (2011-12-01) * @public static */ @@ -438,7 +438,7 @@ class TCPDF_COLORS { /** * Convert color to javascript color. - * @param $color (string) color name or "#RRGGBB" + * @param string $color color name or "#RRGGBB" * @protected * @since 2.1.002 (2008-02-12) * @public static @@ -449,7 +449,7 @@ class TCPDF_COLORS { } if (!in_array($color, self::$jscolor)) { // default transparent color - $color = $jscolor[0]; + $color = self::$jscolor[0]; } return 'color.'.$color; } diff --git a/lib/combodo/tcpdf/include/tcpdf_filters.php b/lib/combodo/tcpdf/include/tcpdf_filters.php index 3bb89c092a..3009cff7c7 100644 --- a/lib/combodo/tcpdf/include/tcpdf_filters.php +++ b/lib/combodo/tcpdf/include/tcpdf_filters.php @@ -60,7 +60,7 @@ class TCPDF_FILTERS { /** * Get a list of available decoding filters. - * @return (array) Array of available filter decoders. + * @return array Array of available filter decoders. * @since 1.0.000 (2011-05-23) * @public static */ @@ -70,9 +70,9 @@ class TCPDF_FILTERS { /** * Decode data using the specified filter type. - * @param $filter (string) Filter name. - * @param $data (string) Data to decode. - * @return Decoded data string. + * @param string $filter Filter name. + * @param string $data Data to decode. + * @return string Decoded data string. * @since 1.0.000 (2011-05-23) * @public static */ @@ -130,8 +130,8 @@ class TCPDF_FILTERS { /** * Standard * Default decoding filter (leaves data unchanged). - * @param $data (string) Data to decode. - * @return Decoded data string. + * @param string $data Data to decode. + * @return string Decoded data string. * @since 1.0.000 (2011-05-23) * @public static */ @@ -142,8 +142,8 @@ class TCPDF_FILTERS { /** * ASCIIHexDecode * Decodes data encoded in an ASCII hexadecimal representation, reproducing the original binary data. - * @param $data (string) Data to decode. - * @return Decoded data string. + * @param string $data Data to decode. + * @return string Decoded data string. * @since 1.0.000 (2011-05-23) * @public static */ @@ -182,8 +182,8 @@ class TCPDF_FILTERS { /** * ASCII85Decode * Decodes data encoded in an ASCII base-85 representation, reproducing the original binary data. - * @param $data (string) Data to decode. - * @return Decoded data string. + * @param string $data Data to decode. + * @return string Decoded data string. * @since 1.0.000 (2011-05-23) * @public static */ @@ -266,8 +266,8 @@ class TCPDF_FILTERS { /** * LZWDecode * Decompresses data encoded using the LZW (Lempel-Ziv-Welch) adaptive compression method, reproducing the original text or binary data. - * @param $data (string) Data to decode. - * @return Decoded data string. + * @param string $data Data to decode. + * @return string Decoded data string. * @since 1.0.000 (2011-05-23) * @public static */ @@ -347,8 +347,8 @@ class TCPDF_FILTERS { /** * FlateDecode * Decompresses data encoded using the zlib/deflate compression method, reproducing the original text or binary data. - * @param $data (string) Data to decode. - * @return Decoded data string. + * @param string $data Data to decode. + * @return string Decoded data string. * @since 1.0.000 (2011-05-23) * @public static */ @@ -364,7 +364,7 @@ class TCPDF_FILTERS { /** * RunLengthDecode * Decompresses data encoded using a byte-oriented run-length encoding algorithm. - * @param $data (string) Data to decode. + * @param string $data Data to decode. * @since 1.0.000 (2011-05-23) * @public static */ @@ -400,8 +400,8 @@ class TCPDF_FILTERS { /** * CCITTFaxDecode (NOT IMPLEMETED - RETURN AN EXCEPTION) * Decompresses data encoded using the CCITT facsimile standard, reproducing the original data (typically monochrome image data at 1 bit per pixel). - * @param $data (string) Data to decode. - * @return Decoded data string. + * @param string $data Data to decode. + * @return string Decoded data string. * @since 1.0.000 (2011-05-23) * @public static */ @@ -413,8 +413,8 @@ class TCPDF_FILTERS { /** * JBIG2Decode (NOT IMPLEMETED - RETURN AN EXCEPTION) * Decompresses data encoded using the JBIG2 standard, reproducing the original monochrome (1 bit per pixel) image data (or an approximation of that data). - * @param $data (string) Data to decode. - * @return Decoded data string. + * @param string $data Data to decode. + * @return string Decoded data string. * @since 1.0.000 (2011-05-23) * @public static */ @@ -426,8 +426,8 @@ class TCPDF_FILTERS { /** * DCTDecode (NOT IMPLEMETED - RETURN AN EXCEPTION) * Decompresses data encoded using a DCT (discrete cosine transform) technique based on the JPEG standard, reproducing image sample data that approximates the original data. - * @param $data (string) Data to decode. - * @return Decoded data string. + * @param string $data Data to decode. + * @return string Decoded data string. * @since 1.0.000 (2011-05-23) * @public static */ @@ -439,8 +439,8 @@ class TCPDF_FILTERS { /** * JPXDecode (NOT IMPLEMETED - RETURN AN EXCEPTION) * Decompresses data encoded using the wavelet-based JPEG2000 standard, reproducing the original image data. - * @param $data (string) Data to decode. - * @return Decoded data string. + * @param string $data Data to decode. + * @return string Decoded data string. * @since 1.0.000 (2011-05-23) * @public static */ @@ -452,8 +452,8 @@ class TCPDF_FILTERS { /** * Crypt (NOT IMPLEMETED - RETURN AN EXCEPTION) * Decrypts data encrypted by a security handler, reproducing the data as it was before encryption. - * @param $data (string) Data to decode. - * @return Decoded data string. + * @param string $data Data to decode. + * @return string Decoded data string. * @since 1.0.000 (2011-05-23) * @public static */ @@ -466,7 +466,7 @@ class TCPDF_FILTERS { /** * Throw an exception. - * @param $msg (string) The error message + * @param string $msg The error message * @since 1.0.000 (2011-05-23) * @public static */ diff --git a/lib/combodo/tcpdf/include/tcpdf_fonts.php b/lib/combodo/tcpdf/include/tcpdf_fonts.php index 218fb6df1e..c692ef87d0 100644 --- a/lib/combodo/tcpdf/include/tcpdf_fonts.php +++ b/lib/combodo/tcpdf/include/tcpdf_fonts.php @@ -55,16 +55,16 @@ class TCPDF_FONTS { /** * Convert and add the selected TrueType or Type1 font to the fonts folder (that must be writeable). - * @param $fontfile (string) Font file (full path). - * @param $fonttype (string) Font type. Leave empty for autodetect mode. Valid values are: TrueTypeUnicode, TrueType, Type1, CID0JP = CID-0 Japanese, CID0KR = CID-0 Korean, CID0CS = CID-0 Chinese Simplified, CID0CT = CID-0 Chinese Traditional. - * @param $enc (string) Name of the encoding table to use. Leave empty for default mode. Omit this parameter for TrueType Unicode and symbolic fonts like Symbol or ZapfDingBats. - * @param $flags (int) Unsigned 32-bit integer containing flags specifying various characteristics of the font (PDF32000:2008 - 9.8.2 Font Descriptor Flags): +1 for fixed font; +4 for symbol or +32 for non-symbol; +64 for italic. Fixed and Italic mode are generally autodetected so you have to set it to 32 = non-symbolic font (default) or 4 = symbolic font. - * @param $outpath (string) Output path for generated font files (must be writeable by the web server). Leave empty for default font folder. - * @param $platid (int) Platform ID for CMAP table to extract (when building a Unicode font for Windows this value should be 3, for Macintosh should be 1). - * @param $encid (int) Encoding ID for CMAP table to extract (when building a Unicode font for Windows this value should be 1, for Macintosh should be 0). When Platform ID is 3, legal values for Encoding ID are: 0=Symbol, 1=Unicode, 2=ShiftJIS, 3=PRC, 4=Big5, 5=Wansung, 6=Johab, 7=Reserved, 8=Reserved, 9=Reserved, 10=UCS-4. - * @param $addcbbox (boolean) If true includes the character bounding box information on the php font file. - * @param $link (boolean) If true link to system font instead of copying the font data (not transportable) - Note: do not work with Type1 fonts. - * @return (string) TCPDF font name or boolean false in case of error. + * @param string $fontfile Font file (full path). + * @param string $fonttype Font type. Leave empty for autodetect mode. Valid values are: TrueTypeUnicode, TrueType, Type1, CID0JP = CID-0 Japanese, CID0KR = CID-0 Korean, CID0CS = CID-0 Chinese Simplified, CID0CT = CID-0 Chinese Traditional. + * @param string $enc Name of the encoding table to use. Leave empty for default mode. Omit this parameter for TrueType Unicode and symbolic fonts like Symbol or ZapfDingBats. + * @param int $flags Unsigned 32-bit integer containing flags specifying various characteristics of the font (PDF32000:2008 - 9.8.2 Font Descriptor Flags): +1 for fixed font; +4 for symbol or +32 for non-symbol; +64 for italic. Fixed and Italic mode are generally autodetected so you have to set it to 32 = non-symbolic font (default) or 4 = symbolic font. + * @param string $outpath Output path for generated font files (must be writeable by the web server). Leave empty for default font folder. + * @param int $platid Platform ID for CMAP table to extract (when building a Unicode font for Windows this value should be 3, for Macintosh should be 1). + * @param int $encid Encoding ID for CMAP table to extract (when building a Unicode font for Windows this value should be 1, for Macintosh should be 0). When Platform ID is 3, legal values for Encoding ID are: 0=Symbol, 1=Unicode, 2=ShiftJIS, 3=PRC, 4=Big5, 5=Wansung, 6=Johab, 7=Reserved, 8=Reserved, 9=Reserved, 10=UCS-4. + * @param boolean $addcbbox If true includes the character bounding box information on the php font file. + * @param boolean $link If true link to system font instead of copying the font data (not transportable) - Note: do not work with Type1 fonts. + * @return string|false TCPDF font name or boolean false in case of error. * @author Nicola Asuni * @since 5.9.123 (2010-09-30) * @public static @@ -557,6 +557,7 @@ class TCPDF_FONTS { $numGlyphs = TCPDF_STATIC::_getUSHORT($font, $offset); // ---------- get CIDToGIDMap ---------- $ctg = array(); + $c = 0; foreach ($encodingTables as $enctable) { // get only specified Platform ID and Encoding ID if (($enctable['platformID'] == $platid) AND ($enctable['encodingID'] == $encid)) { @@ -920,8 +921,8 @@ class TCPDF_FONTS { /** * Returs the checksum of a TTF table. - * @param $table (string) table to check - * @param $length (int) length of table in bytes + * @param string $table table to check + * @param int $length length of table in bytes * @return int checksum * @author Nicola Asuni * @since 5.2.000 (2010-06-02) @@ -942,9 +943,9 @@ class TCPDF_FONTS { /** * Returns a subset of the TrueType font data without the unused glyphs. - * @param $font (string) TrueType font data. - * @param $subsetchars (array) Array of used characters (the glyphs to keep). - * @return (string) A subset of TrueType font data without the unused glyphs. + * @param string $font TrueType font data. + * @param array $subsetchars Array of used characters (the glyphs to keep). + * @return string A subset of TrueType font data without the unused glyphs. * @author Nicola Asuni * @since 5.2.000 (2010-06-02) * @public static @@ -956,6 +957,7 @@ class TCPDF_FONTS { // sfnt version must be 0x00010000 for TrueType version 1.0. return $font; } + $c = 0; $offset += 4; // get number of tables $numTables = TCPDF_STATIC::_getUSHORT($font, $offset); @@ -1387,9 +1389,9 @@ class TCPDF_FONTS { /** * Outputs font widths - * @param $font (array) font data - * @param $cidoffset (int) offset for CID values - * @return PDF command string for font widths + * @param array $font font data + * @param int $cidoffset offset for CID values + * @return string PDF command string for font widths * @author Nicola Asuni * @since 4.4.000 (2008-12-07) * @public static @@ -1495,10 +1497,10 @@ class TCPDF_FONTS { /** * Update the CIDToGIDMap string with a new value. - * @param $map (string) CIDToGIDMap. - * @param $cid (int) CID value. - * @param $gid (int) GID value. - * @return (string) CIDToGIDMap. + * @param string $map CIDToGIDMap. + * @param int $cid CID value. + * @param int $gid GID value. + * @return string CIDToGIDMap. * @author Nicola Asuni * @since 5.9.123 (2011-09-29) * @public static @@ -1533,8 +1535,8 @@ class TCPDF_FONTS { /** * Return font full path - * @param $file (string) Font file name. - * @param $fontdir (string) Font directory (set to false fto search on default directories) + * @param string $file Font file name. + * @param string $fontdir Font directory (set to false fto search on default directories) * @return string Font full path or empty string * @author Nicola Asuni * @since 6.0.025 @@ -1558,8 +1560,8 @@ class TCPDF_FONTS { /** * Get a reference font size. - * @param $size (string) String containing font size value. - * @param $refsize (float) Reference font size in points. + * @param string $size String containing font size value. + * @param float $refsize Reference font size in points. * @return float value in points * @public static */ @@ -1657,9 +1659,9 @@ class TCPDF_FONTS { /** * Returns the unicode caracter specified by the value - * @param $c (int) UTF-8 value - * @param $unicode (boolean) True if we are in unicode mode, false otherwise. - * @return Returns the specified character. + * @param int $c UTF-8 value + * @param boolean $unicode True if we are in unicode mode, false otherwise. + * @return string Returns the specified character. * @since 2.3.000 (2008-03-05) * @public static */ @@ -1686,8 +1688,8 @@ class TCPDF_FONTS { /** * Returns the unicode caracter specified by UTF-8 value - * @param $c (int) UTF-8 value - * @return Returns the specified character. + * @param int $c UTF-8 value + * @return string Returns the specified character. * @public static */ public static function unichrUnicode($c) { @@ -1696,8 +1698,8 @@ class TCPDF_FONTS { /** * Returns the unicode caracter specified by ASCII value - * @param $c (int) UTF-8 value - * @return Returns the specified character. + * @param int $c UTF-8 value + * @return string Returns the specified character. * @public static */ public static function unichrASCII($c) { @@ -1734,8 +1736,8 @@ class TCPDF_FONTS { * W1 = 110110yyyyyyyyyy * W2 = 110111xxxxxxxxxx * - * @param $unicode (array) array containing UTF-8 unicode values - * @param $setbom (boolean) if true set the Byte Order Mark (BOM = 0xFEFF) + * @param array $unicode array containing UTF-8 unicode values + * @param boolean $setbom if true set the Byte Order Mark (BOM = 0xFEFF) * @return string * @protected * @author Nicola Asuni @@ -1770,9 +1772,9 @@ class TCPDF_FONTS { /** * Convert an array of UTF8 values to array of unicode characters - * @param $ta (array) The input array of UTF8 values. - * @param $isunicode (boolean) True for Unicode mode, false otherwise. - * @return Return array of unicode characters + * @param array $ta The input array of UTF8 values. + * @param boolean $isunicode True for Unicode mode, false otherwise. + * @return array Return array of unicode characters * @since 4.5.037 (2009-04-07) * @public static */ @@ -1785,11 +1787,11 @@ class TCPDF_FONTS { /** * Extract a slice of the $strarr array and return it as string. - * @param $strarr (string) The input array of characters. - * @param $start (int) the starting element of $strarr. - * @param $end (int) first element that will not be returned. - * @param $unicode (boolean) True if we are in unicode mode, false otherwise. - * @return Return part of a string + * @param string[] $strarr The input array of characters. + * @param int $start the starting element of $strarr. + * @param int $end first element that will not be returned. + * @param boolean $unicode True if we are in unicode mode, false otherwise. + * @return string Return part of a string * @public static */ public static function UTF8ArrSubString($strarr, $start='', $end='', $unicode=true) { @@ -1808,10 +1810,10 @@ class TCPDF_FONTS { /** * Extract a slice of the $uniarr array and return it as string. - * @param $uniarr (string) The input array of characters. - * @param $start (int) the starting element of $strarr. - * @param $end (int) first element that will not be returned. - * @return Return part of a string + * @param string[] $uniarr The input array of characters. + * @param int $start the starting element of $strarr. + * @param int $end first element that will not be returned. + * @return string Return part of a string * @since 4.5.037 (2009-04-07) * @public static */ @@ -1831,7 +1833,7 @@ class TCPDF_FONTS { /** * Converts UTF-8 characters array to array of Latin1 characters array
    - * @param $unicode (array) array containing UTF-8 unicode values + * @param array $unicode array containing UTF-8 unicode values * @return array * @author Nicola Asuni * @since 4.8.023 (2010-01-15) @@ -1855,9 +1857,9 @@ class TCPDF_FONTS { } /** - * Converts UTF-8 characters array to array of Latin1 string
    - * @param $unicode (array) array containing UTF-8 unicode values - * @return array + * Converts UTF-8 characters array to Latin1 string
    + * @param array $unicode array containing UTF-8 unicode values + * @return string * @author Nicola Asuni * @since 4.8.023 (2010-01-15) * @public static @@ -1882,8 +1884,8 @@ class TCPDF_FONTS { /** * Converts UTF-8 character to integer value.
    * Uses the getUniord() method if the value is not cached. - * @param $uch (string) character string to process. - * @return integer Unicode value + * @param string $uch character string to process. + * @return int Unicode value * @public static */ public static function uniord($uch) { @@ -1921,8 +1923,8 @@ class TCPDF_FONTS { * UTF8-tail = %x80-BF * --------------------------------------------------------------------- * - * @param $uch (string) character string to process. - * @return integer Unicode value + * @param string $uch character string to process. + * @return int Unicode value * @author Nicola Asuni * @public static */ @@ -1988,14 +1990,14 @@ class TCPDF_FONTS { /** * Converts UTF-8 strings to codepoints array.
    * Invalid byte sequences will be replaced with 0xFFFD (replacement character)
    - * @param $str (string) string to process. - * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise. - * @param $currentfont (array) Reference to current font array. + * @param string $str string to process. + * @param boolean $isunicode True when the documetn is in Unicode mode, false otherwise. + * @param array $currentfont Reference to current font array. * @return array containing codepoints (UTF-8 characters values) * @author Nicola Asuni * @public static */ - public static function UTF8StringToArray($str, $isunicode=true, &$currentfont) { + public static function UTF8StringToArray($str, $isunicode, &$currentfont) { if ($isunicode) { // requires PCRE unicode support turned on $chars = TCPDF_STATIC::pregSplit('//','u', $str, -1, PREG_SPLIT_NO_EMPTY); @@ -2014,30 +2016,30 @@ class TCPDF_FONTS { /** * Converts UTF-8 strings to Latin1 when using the standard 14 core fonts.
    - * @param $str (string) string to process. - * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise. - * @param $currentfont (array) Reference to current font array. + * @param string $str string to process. + * @param boolean $isunicode True when the documetn is in Unicode mode, false otherwise. + * @param array $currentfont Reference to current font array. * @return string * @since 3.2.000 (2008-06-23) * @public static */ - public static function UTF8ToLatin1($str, $isunicode=true, &$currentfont) { + public static function UTF8ToLatin1($str, $isunicode, &$currentfont) { $unicode = self::UTF8StringToArray($str, $isunicode, $currentfont); // array containing UTF-8 unicode values return self::UTF8ArrToLatin1($unicode); } /** * Converts UTF-8 strings to UTF16-BE.
    - * @param $str (string) string to process. - * @param $setbom (boolean) if true set the Byte Order Mark (BOM = 0xFEFF) - * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise. - * @param $currentfont (array) Reference to current font array. + * @param string $str string to process. + * @param boolean $setbom if true set the Byte Order Mark (BOM = 0xFEFF) + * @param boolean $isunicode True when the documetn is in Unicode mode, false otherwise. + * @param array $currentfont Reference to current font array. * @return string * @author Nicola Asuni * @since 1.53.0.TC005 (2005-01-05) * @public static */ - public static function UTF8ToUTF16BE($str, $setbom=false, $isunicode=true, &$currentfont) { + public static function UTF8ToUTF16BE($str, $setbom, $isunicode, &$currentfont) { if (!$isunicode) { return $str; // string is not in unicode } @@ -2047,50 +2049,50 @@ class TCPDF_FONTS { /** * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/). - * @param $str (string) string to manipulate. - * @param $setbom (bool) if true set the Byte Order Mark (BOM = 0xFEFF) - * @param $forcertl (bool) if true forces RTL text direction - * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise. - * @param $currentfont (array) Reference to current font array. + * @param string $str string to manipulate. + * @param bool $setbom if true set the Byte Order Mark (BOM = 0xFEFF) + * @param bool $forcertl if true forces RTL text direction + * @param boolean $isunicode True if the document is in Unicode mode, false otherwise. + * @param array $currentfont Reference to current font array. * @return string * @author Nicola Asuni * @since 2.1.000 (2008-01-08) * @public static */ - public static function utf8StrRev($str, $setbom=false, $forcertl=false, $isunicode=true, &$currentfont) { + public static function utf8StrRev($str, $setbom, $forcertl, $isunicode, &$currentfont) { return self::utf8StrArrRev(self::UTF8StringToArray($str, $isunicode, $currentfont), $str, $setbom, $forcertl, $isunicode, $currentfont); } /** * Reverse the RLT substrings array using the Bidirectional Algorithm (http://unicode.org/reports/tr9/). - * @param $arr (array) array of unicode values. - * @param $str (string) string to manipulate (or empty value). - * @param $setbom (bool) if true set the Byte Order Mark (BOM = 0xFEFF) - * @param $forcertl (bool) if true forces RTL text direction - * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise. - * @param $currentfont (array) Reference to current font array. + * @param array $arr array of unicode values. + * @param string $str string to manipulate (or empty value). + * @param bool $setbom if true set the Byte Order Mark (BOM = 0xFEFF) + * @param bool $forcertl if true forces RTL text direction + * @param boolean $isunicode True if the document is in Unicode mode, false otherwise. + * @param array $currentfont Reference to current font array. * @return string * @author Nicola Asuni * @since 4.9.000 (2010-03-27) * @public static */ - public static function utf8StrArrRev($arr, $str='', $setbom=false, $forcertl=false, $isunicode=true, &$currentfont) { + public static function utf8StrArrRev($arr, $str, $setbom, $forcertl, $isunicode, &$currentfont) { return self::arrUTF8ToUTF16BE(self::utf8Bidi($arr, $str, $forcertl, $isunicode, $currentfont), $setbom); } /** * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/). - * @param $ta (array) array of characters composing the string. - * @param $str (string) string to process - * @param $forcertl (bool) if 'R' forces RTL, if 'L' forces LTR - * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise. - * @param $currentfont (array) Reference to current font array. + * @param array $ta array of characters composing the string. + * @param string $str string to process + * @param bool $forcertl if 'R' forces RTL, if 'L' forces LTR + * @param boolean $isunicode True if the document is in Unicode mode, false otherwise. + * @param array $currentfont Reference to current font array. * @return array of unicode chars * @author Nicola Asuni * @since 2.4.000 (2008-03-06) * @public static */ - public static function utf8Bidi($ta, $str='', $forcertl=false, $isunicode=true, &$currentfont) { + public static function utf8Bidi($ta, $str, $forcertl, $isunicode, &$currentfont) { // paragraph embedding level $pel = 0; // max level diff --git a/lib/combodo/tcpdf/include/tcpdf_images.php b/lib/combodo/tcpdf/include/tcpdf_images.php index 5e504f2101..6f2860c60b 100644 --- a/lib/combodo/tcpdf/include/tcpdf_images.php +++ b/lib/combodo/tcpdf/include/tcpdf_images.php @@ -55,6 +55,8 @@ class TCPDF_IMAGES { * Array of hinheritable SVG properties. * @since 5.0.000 (2010-05-02) * @public static + * + * @var string[] */ public static $svginheritprop = array('clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cursor', 'direction', 'display', 'fill', 'fill-opacity', 'fill-rule', 'font', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'image-rendering', 'kerning', 'letter-spacing', 'marker', 'marker-end', 'marker-mid', 'marker-start', 'pointer-events', 'shape-rendering', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-rendering', 'visibility', 'word-spacing', 'writing-mode'); @@ -62,8 +64,8 @@ class TCPDF_IMAGES { /** * Return the image type given the file name or array returned by getimagesize() function. - * @param $imgfile (string) image file name - * @param $iminfo (array) array of image information returned by getimagesize() function. + * @param string $imgfile image file name + * @param array $iminfo array of image information returned by getimagesize() function. * @return string image type * @since 4.8.017 (2009-11-27) * @public static @@ -77,10 +79,7 @@ class TCPDF_IMAGES { } } if (empty($type)) { - $fileinfo = pathinfo($imgfile); - if (isset($fileinfo['extension']) AND (!TCPDF_STATIC::empty_string($fileinfo['extension']))) { - $type = strtolower(trim($fileinfo['extension'])); - } + $type = strtolower(trim(pathinfo(parse_url($imgfile, PHP_URL_PATH), PATHINFO_EXTENSION))); } if ($type == 'jpg') { $type = 'jpeg'; @@ -90,9 +89,9 @@ class TCPDF_IMAGES { /** * Set the transparency for the given GD image. - * @param $new_image (image) GD image object - * @param $image (image) GD image object. - * return GD image object. + * @param resource $new_image GD image object + * @param resource $image GD image object. + * @return resource GD image object $new_image * @since 4.9.016 (2010-04-20) * @public static */ @@ -115,8 +114,8 @@ class TCPDF_IMAGES { /** * Convert the loaded image to a PNG and then return a structure for the PDF creator. * This function requires GD library and write access to the directory defined on K_PATH_CACHE constant. - * @param $image (image) Image object. - * @param $tempfile (string) Temporary file name. + * @param resource $image Image object. + * @param string $tempfile Temporary file name. * return image PNG image object. * @since 4.9.016 (2010-04-20) * @public static @@ -138,10 +137,10 @@ class TCPDF_IMAGES { /** * Convert the loaded image to a JPEG and then return a structure for the PDF creator. * This function requires GD library and write access to the directory defined on K_PATH_CACHE constant. - * @param $image (image) Image object. - * @param $quality (int) JPEG quality. - * @param $tempfile (string) Temporary file name. - * return image JPEG image object. + * @param resource $image Image object. + * @param int $quality JPEG quality. + * @param string $tempfile Temporary file name. + * return array|false image JPEG image object. * @public static */ public static function _toJPEG($image, $quality, $tempfile) { @@ -155,8 +154,8 @@ class TCPDF_IMAGES { /** * Extract info from a JPEG file without using the GD library. - * @param $file (string) image file to parse - * @return array structure containing the image data + * @param string $file image file to parse + * @return array|false structure containing the image data * @public static */ public static function _parsejpeg($file) { @@ -234,8 +233,8 @@ class TCPDF_IMAGES { /** * Extract info from a PNG file without using the GD library. - * @param $file (string) image file to parse - * @return array structure containing the image data + * @param string $file image file to parse + * @return array|false structure containing the image data * @public static */ public static function _parsepng($file) { diff --git a/lib/combodo/tcpdf/include/tcpdf_static.php b/lib/combodo/tcpdf/include/tcpdf_static.php index 06a1dddcc9..a118d0588f 100644 --- a/lib/combodo/tcpdf/include/tcpdf_static.php +++ b/lib/combodo/tcpdf/include/tcpdf_static.php @@ -7,7 +7,7 @@ // Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com // License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html) // ------------------------------------------------------------------- -// Copyright (C) 2002-2015 Nicola Asuni - Tecnick.com LTD +// Copyright (C) 2002-2021 Nicola Asuni - Tecnick.com LTD // // This file is part of TCPDF software library. // @@ -55,7 +55,7 @@ class TCPDF_STATIC { * Current TCPDF version. * @private static */ - private static $tcpdf_version = '6.3.5'; + private static $tcpdf_version = '6.4.4'; /** * String alias for total number of pages. @@ -110,7 +110,7 @@ class TCPDF_STATIC { /** * Return the current TCPDF version. - * @return TCPDF version string + * @return string TCPDF version string * @since 5.9.012 (2010-11-10) * @public static */ @@ -120,7 +120,7 @@ class TCPDF_STATIC { /** * Return the current TCPDF producer. - * @return TCPDF producer string + * @return string TCPDF producer string * @since 6.0.000 (2013-03-16) * @public static */ @@ -130,7 +130,7 @@ class TCPDF_STATIC { /** * Sets the current active configuration setting of magic_quotes_runtime (if the set_magic_quotes_runtime function exist) - * @param $mqr (boolean) FALSE for off, TRUE for on. + * @param boolean $mqr FALSE for off, TRUE for on. * @since 4.6.025 (2009-08-17) * @public static */ @@ -146,7 +146,7 @@ class TCPDF_STATIC { /** * Gets the current active configuration setting of magic_quotes_runtime (if the get_magic_quotes_runtime function exist) - * @return Returns 0 if magic quotes runtime is off or get_magic_quotes_runtime doesn't exist, 1 otherwise. + * @return int Returns 0 if magic quotes runtime is off or get_magic_quotes_runtime doesn't exist, 1 otherwise. * @since 4.6.025 (2009-08-17) * @public static */ @@ -163,13 +163,16 @@ class TCPDF_STATIC { /** * Check if the URL exist. - * @param $url (string) URL to check. - * @return Boolean true if the URl exist, false otherwise. + * @param string $url URL to check. + * @return boolean true if the URl exist, false otherwise. * @since 5.9.204 (2013-01-28) * @public static */ public static function isValidURL($url) { $headers = @get_headers($url); + if ($headers === false) { + return false; + } return (strpos($headers[0], '200') !== false); } @@ -184,8 +187,8 @@ class TCPDF_STATIC { *
  • UTF-8 (hex): 0xC2 0xAD (c2ad)
  • *
  • UTF-8 character: chr(194).chr(173)
  • * - * @param $txt (string) input string - * @param $unicode (boolean) True if we are in unicode mode, false otherwise. + * @param string $txt input string + * @param boolean $unicode True if we are in unicode mode, false otherwise. * @return string without SHY characters. * @since (4.5.019) 2009-02-28 * @public static @@ -201,10 +204,10 @@ class TCPDF_STATIC { /** * Get the border mode accounting for multicell position (opens bottom side of multicell crossing pages) - * @param $brd (mixed) Indicates if borders must be drawn around the cell block. The value can be a number:
    • 0: no border (default)
    • 1: frame
    or a string containing some or all of the following characters (in any order):
    • L: left
    • T: top
    • R: right
    • B: bottom
    or an array of line styles for each border group: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) - * @param $position (string) multicell position: 'start', 'middle', 'end' - * @param $opencell (boolean) True when the cell is left open at the page bottom, false otherwise. - * @return border mode array + * @param string|array|int $brd Indicates if borders must be drawn around the cell block. The value can be a number:
    • 0: no border (default)
    • 1: frame
    or a string containing some or all of the following characters (in any order):
    • L: left
    • T: top
    • R: right
    • B: bottom
    or an array of line styles for each border group: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) + * @param string $position multicell position: 'start', 'middle', 'end' + * @param boolean $opencell True when the cell is left open at the page bottom, false otherwise. + * @return array border mode array * @since 4.4.002 (2008-12-09) * @public static */ @@ -275,8 +278,8 @@ class TCPDF_STATIC { /** * Determine whether a string is empty. - * @param $str (string) string to be checked - * @return boolean true if string is empty + * @param string $str string to be checked + * @return bool true if string is empty * @since 4.5.044 (2009-04-16) * @public static */ @@ -286,8 +289,8 @@ class TCPDF_STATIC { /** * Returns a temporary filename for caching object on filesystem. - * @param $type (string) Type of file (name of the subdir on the tcpdf cache folder). - * @param $file_id (string) TCPDF file_id. + * @param string $type Type of file (name of the subdir on the tcpdf cache folder). + * @param string $file_id TCPDF file_id. * @return string filename. * @since 4.5.000 (2008-12-31) * @public static @@ -298,7 +301,7 @@ class TCPDF_STATIC { /** * Add "\" before "\", "(" and ")" - * @param $s (string) string to escape. + * @param string $s string to escape. * @return string escaped string. * @public static */ @@ -309,8 +312,8 @@ class TCPDF_STATIC { /** * Escape some special characters (< > &) for XML output. - * @param $str (string) Input string to convert. - * @return converted string + * @param string $str Input string to convert. + * @return string converted string * @since 5.9.121 (2011-09-28) * @public static */ @@ -322,8 +325,8 @@ class TCPDF_STATIC { /** * Creates a copy of a class object - * @param $object (object) class object to be cloned - * @return cloned object + * @param object $object class object to be cloned + * @return object cloned object * @since 4.5.029 (2009-03-19) * @public static */ @@ -337,8 +340,8 @@ class TCPDF_STATIC { /** * Output input data and compress it if possible. - * @param $data (string) Data to output. - * @param $length (int) Data length in bytes. + * @param string $data Data to output. + * @param int $length Data length in bytes. * @since 5.9.086 * @public static */ @@ -352,10 +355,10 @@ class TCPDF_STATIC { /** * Replace page number aliases with number. - * @param $page (string) Page content. - * @param $replace (array) Array of replacements (array keys are replacement strings, values are alias arrays). - * @param $diff (int) If passed, this will be set to the total char number difference between alias and replacements. - * @return replaced page content and updated $diff parameter as array. + * @param string $page Page content. + * @param array $replace Array of replacements (array keys are replacement strings, values are alias arrays). + * @param int $diff If passed, this will be set to the total char number difference between alias and replacements. + * @return array replaced page content and updated $diff parameter as array. * @public static */ public static function replacePageNumAliases($page, $replace, $diff=0) { @@ -372,7 +375,7 @@ class TCPDF_STATIC { /** * Returns timestamp in seconds from formatted date-time. - * @param $date (string) Formatted date-time. + * @param string $date Formatted date-time. * @return int seconds. * @since 5.9.152 (2012-03-23) * @public static @@ -387,7 +390,7 @@ class TCPDF_STATIC { /** * Returns a formatted date-time. - * @param $time (int) Time in seconds. + * @param int $time Time in seconds. * @return string escaped date string. * @since 5.9.152 (2012-03-23) * @public static @@ -398,7 +401,7 @@ class TCPDF_STATIC { /** * Returns a string containing random data to be used as a seed for encryption methods. - * @param $seed (string) starting seed value + * @param string $seed starting seed value * @return string containing random data * @author Nicola Asuni * @since 5.9.006 (2010-10-19) @@ -422,8 +425,8 @@ class TCPDF_STATIC { /** * Encrypts a string using MD5 and returns it's value as a binary string. - * @param $str (string) input string - * @return String MD5 encrypted binary string + * @param string $str input string + * @return string MD5 encrypted binary string * @since 2.0.000 (2008-01-02) * @public static */ @@ -432,11 +435,11 @@ class TCPDF_STATIC { } /** - * Returns the input text exrypted using AES algorithm and the specified key. + * Returns the input text encrypted using AES algorithm and the specified key. * This method requires openssl or mcrypt. Text is padded to 16bytes blocks - * @param $key (string) encryption key - * @param $text (String) input text to be encrypted - * @return String encrypted text + * @param string $key encryption key + * @param string $text input text to be encrypted + * @return string encrypted text * @author Nicola Asuni * @since 5.0.005 (2010-05-11) * @public static @@ -446,8 +449,12 @@ class TCPDF_STATIC { $padding = 16 - (strlen($text) % 16); $text .= str_repeat(chr($padding), $padding); if (extension_loaded('openssl')) { - $iv = openssl_random_pseudo_bytes (openssl_cipher_iv_length('aes-256-cbc')); - $text = openssl_encrypt($text, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv); + $algo = 'aes-256-cbc'; + if (strlen($key) == 16) { + $algo = 'aes-128-cbc'; + } + $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($algo)); + $text = openssl_encrypt($text, $algo, $key, OPENSSL_RAW_DATA, $iv); return $iv.substr($text, 0, -16); } $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND); @@ -457,19 +464,23 @@ class TCPDF_STATIC { } /** - * Returns the input text exrypted using AES algorithm and the specified key. + * Returns the input text encrypted using AES algorithm and the specified key. * This method requires openssl or mcrypt. Text is not padded - * @param $key (string) encryption key - * @param $text (String) input text to be encrypted - * @return String encrypted text + * @param string $key encryption key + * @param string $text input text to be encrypted + * @return string encrypted text * @author Nicola Asuni * @since TODO * @public static */ public static function _AESnopad($key, $text) { if (extension_loaded('openssl')) { - $iv = str_repeat("\x00", openssl_cipher_iv_length('aes-256-cbc')); - $text = openssl_encrypt($text, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv); + $algo = 'aes-256-cbc'; + if (strlen($key) == 16) { + $algo = 'aes-128-cbc'; + } + $iv = str_repeat("\x00", openssl_cipher_iv_length($algo)); + $text = openssl_encrypt($text, $algo, $key, OPENSSL_RAW_DATA, $iv); return substr($text, 0, -16); } $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC)); @@ -480,11 +491,11 @@ class TCPDF_STATIC { /** * Returns the input text encrypted using RC4 algorithm and the specified key. * RC4 is the standard encryption algorithm used in PDF format - * @param $key (string) Encryption key. - * @param $text (String) Input text to be encrypted. - * @param $last_enc_key (String) Reference to last RC4 key encrypted. - * @param $last_enc_key_c (String) Reference to last RC4 computed key. - * @return String encrypted text + * @param string $key Encryption key. + * @param string $text Input text to be encrypted. + * @param string $last_enc_key Reference to last RC4 key encrypted. + * @param string $last_enc_key_c Reference to last RC4 computed key. + * @return string encrypted text * @since 2.0.000 (2008-01-02) * @author Klemen Vodopivec, Nicola Asuni * @public static @@ -495,7 +506,7 @@ class TCPDF_STATIC { return $out; } if ($last_enc_key != $key) { - $k = str_repeat($key, ((256 / strlen($key)) + 1)); + $k = str_repeat($key, (int) ((256 / strlen($key)) + 1)); $rc4 = range(0, 255); $j = 0; for ($i = 0; $i < 256; ++$i) { @@ -527,8 +538,8 @@ class TCPDF_STATIC { /** * Return the permission code used on encryption (P value). - * @param $permissions (Array) the set of permissions (specify the ones you want to block). - * @param $mode (int) encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit. + * @param array $permissions the set of permissions (specify the ones you want to block). + * @param int $mode encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit. * @since 5.0.005 (2010-05-12) * @author Nicola Asuni * @public static @@ -564,8 +575,8 @@ class TCPDF_STATIC { /** * Convert hexadecimal string to string - * @param $bs (string) byte-string to convert - * @return String + * @param string $bs byte-string to convert + * @return string * @since 5.0.005 (2010-05-12) * @author Nicola Asuni * @public static @@ -586,8 +597,8 @@ class TCPDF_STATIC { /** * Convert string to hexadecimal string (byte string) - * @param $s (string) string to convert - * @return byte string + * @param string $s string to convert + * @return string byte string * @since 5.0.010 (2010-05-17) * @author Nicola Asuni * @public static @@ -603,8 +614,8 @@ class TCPDF_STATIC { /** * Convert encryption P value to a string of bytes, low-order byte first. - * @param $protection (string) 32bit encryption permission value (P value) - * @return String + * @param string $protection 32bit encryption permission value (P value) + * @return string * @since 5.0.005 (2010-05-12) * @author Nicola Asuni * @public static @@ -620,8 +631,8 @@ class TCPDF_STATIC { /** * Encode a name object. - * @param $name (string) Name object to encode. - * @return (string) Encoded name object. + * @param string $name Name object to encode. + * @return string Encoded name object. * @author Nicola Asuni * @since 5.9.097 (2011-06-23) * @public static @@ -642,9 +653,9 @@ class TCPDF_STATIC { /** * Convert JavaScript form fields properties array to Annotation Properties array. - * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference. - * @param $spot_colors (array) Reference to spot colors array. - * @param $rtl (boolean) True if in Right-To-Left text direction mode, false otherwise. + * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference. + * @param array $spot_colors Reference to spot colors array. + * @param boolean $rtl True if in Right-To-Left text direction mode, false otherwise. * @return array of annotation properties * @author Nicola Asuni * @since 4.8.000 (2009-09-06) @@ -652,7 +663,7 @@ class TCPDF_STATIC { */ public static function getAnnotOptFromJSProp($prop, &$spot_colors, $rtl=false) { if (isset($prop['aopt']) AND is_array($prop['aopt'])) { - // the annotation options area lready defined + // the annotation options are already defined return $prop['aopt']; } $opt = array(); // value to be returned @@ -1011,8 +1022,9 @@ class TCPDF_STATIC { /** * Format the page numbers. - * This method can be overriden for custom formats. - * @param $num (int) page number + * This method can be overridden for custom formats. + * @param int $num page number + * @return string * @since 4.2.005 (2008-11-06) * @public static */ @@ -1022,8 +1034,9 @@ class TCPDF_STATIC { /** * Format the page numbers on the Table Of Content. - * This method can be overriden for custom formats. - * @param $num (int) page number + * This method can be overridden for custom formats. + * @param int $num page number + * @return string * @since 4.5.001 (2009-01-04) * @see addTOC(), addHTMLTOC() * @public static @@ -1034,8 +1047,8 @@ class TCPDF_STATIC { /** * Extracts the CSS properties from a CSS string. - * @param $cssdata (string) string containing CSS definitions. - * @return An array where the keys are the CSS selectors and the values are the CSS properties. + * @param string $cssdata string containing CSS definitions. + * @return array An array where the keys are the CSS selectors and the values are the CSS properties. * @author Nicola Asuni * @since 5.1.000 (2010-05-25) * @public static @@ -1125,20 +1138,20 @@ class TCPDF_STATIC { /** * Cleanup HTML code (requires HTML Tidy library). - * @param $html (string) htmlcode to fix - * @param $default_css (string) CSS commands to add - * @param $tagvs (array) parameters for setHtmlVSpace method - * @param $tidy_options (array) options for tidy_parse_string function - * @param $tagvspaces (array) Array of vertical spaces for tags. + * @param string $html htmlcode to fix + * @param string $default_css CSS commands to add + * @param array|null $tagvs parameters for setHtmlVSpace method + * @param array|null $tidy_options options for tidy_parse_string function + * @param array $tagvspaces Array of vertical spaces for tags. * @return string XHTML code cleaned up * @author Nicola Asuni * @since 5.9.017 (2010-11-16) * @see setHtmlVSpace() * @public static */ - public static function fixHTMLCode($html, $default_css='', $tagvs='', $tidy_options='', &$tagvspaces) { + public static function fixHTMLCode($html, $default_css, $tagvs, $tidy_options, &$tagvspaces) { // configure parameters for HTML Tidy - if ($tidy_options === '') { + if (TCPDF_STATIC::empty_string($tidy_options)) { $tidy_options = array ( 'clean' => 1, 'drop-empty-paras' => 0, @@ -1185,7 +1198,7 @@ class TCPDF_STATIC { // remove some empty tag blocks $html = preg_replace('/]*)><\/div>/', '', $html); $html = preg_replace('/]*)><\/p>/', '', $html); - if ($tagvs !== '') { + if (!TCPDF_STATIC::empty_string($tagvs)) { // set vertical space for some XHTML tags $tagvspaces = $tagvs; } @@ -1195,9 +1208,9 @@ class TCPDF_STATIC { /** * Returns true if the CSS selector is valid for the selected HTML tag - * @param $dom (array) array of HTML tags and properties - * @param $key (int) key of the current HTML tag - * @param $selector (string) CSS selector string + * @param array $dom array of HTML tags and properties + * @param int $key key of the current HTML tag + * @param string $selector CSS selector string * @return true if the selector is valid, false otherwise * @since 5.1.000 (2010-05-25) * @public static @@ -1355,9 +1368,9 @@ class TCPDF_STATIC { /** * Returns the styles array that apply for the selected HTML tag. - * @param $dom (array) array of HTML tags and properties - * @param $key (int) key of the current HTML tag - * @param $css (array) array of CSS properties + * @param array $dom array of HTML tags and properties + * @param int $key key of the current HTML tag + * @param array $css array of CSS properties * @return array containing CSS properties * @since 5.1.000 (2010-05-25) * @public static @@ -1402,7 +1415,7 @@ class TCPDF_STATIC { /** * Compact CSS data array into single string. - * @param $css (array) array of CSS properties + * @param array $css array of CSS properties * @return string containing merged CSS properties * @since 5.9.070 (2011-04-19) * @public static @@ -1433,13 +1446,17 @@ class TCPDF_STATIC { /** * Returns the Roman representation of an integer number - * @param $number (int) number to convert + * @param int $number number to convert * @return string roman representation of the specified number * @since 4.4.004 (2008-12-10) * @public static */ public static function intToRoman($number) { $roman = ''; + if ($number >= 4000) { + // do not represent numbers above 4000 in Roman numerals + return strval($number); + } while ($number >= 1000) { $roman .= 'M'; $number -= 1000; @@ -1497,10 +1514,10 @@ class TCPDF_STATIC { /** * Find position of last occurrence of a substring in a string - * @param $haystack (string) The string to search in. - * @param $needle (string) substring to search. - * @param $offset (int) May be specified to begin searching an arbitrary number of characters into the string. - * @return Returns the position where the needle exists. Returns FALSE if the needle was not found. + * @param string $haystack The string to search in. + * @param string $needle substring to search. + * @param int $offset May be specified to begin searching an arbitrary number of characters into the string. + * @return int|false Returns the position where the needle exists. Returns FALSE if the needle was not found. * @since 4.8.038 (2010-03-13) * @public static */ @@ -1513,7 +1530,7 @@ class TCPDF_STATIC { /** * Returns an array of hyphenation patterns. - * @param $file (string) TEX file containing hypenation patterns. TEX pattrns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/ + * @param string $file TEX file containing hypenation patterns. TEX patterns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/ * @return array of hyphenation patterns * @author Nicola Asuni * @since 4.9.012 (2010-04-12) @@ -1546,7 +1563,7 @@ class TCPDF_STATIC { /** * Get the Path-Painting Operators. - * @param $style (string) Style of rendering. Possible values are: + * @param string $style Style of rendering. Possible values are: *
      *
    • S or D: Stroke the path.
    • *
    • s or d: Close and stroke the path.
    • @@ -1560,7 +1577,8 @@ class TCPDF_STATIC { *
    • CEO: Clipping mode using the nonzero winding number rule to determine which regions lie inside the clipping path
    • *
    • n: End the path object without filling or stroking it.
    • *
    - * @param $default (string) default style + * @param string $default default style + * @return string * @author Nicola Asuni * @since 5.0.000 (2010-04-30) * @public static @@ -1637,9 +1655,9 @@ class TCPDF_STATIC { /** * Get the product of two SVG tranformation matrices - * @param $ta (array) first SVG tranformation matrix - * @param $tb (array) second SVG tranformation matrix - * @return transformation array + * @param array $ta first SVG tranformation matrix + * @param array $tb second SVG tranformation matrix + * @return array transformation array * @author Nicola Asuni * @since 5.0.000 (2010-05-02) * @public static @@ -1657,7 +1675,7 @@ class TCPDF_STATIC { /** * Get the tranformation matrix from SVG transform attribute - * @param $attribute (string) transformation + * @param string $attribute transformation * @return array of transformations * @author Nicola Asuni * @since 5.0.000 (2010-05-02) @@ -1752,10 +1770,10 @@ class TCPDF_STATIC { /** * Returns the angle in radiants between two vectors - * @param $x1 (int) X coordinate of first vector point - * @param $y1 (int) Y coordinate of first vector point - * @param $x2 (int) X coordinate of second vector point - * @param $y2 (int) Y coordinate of second vector point + * @param int $x1 X coordinate of first vector point + * @param int $y1 Y coordinate of first vector point + * @param int $x2 X coordinate of second vector point + * @param int $y2 Y coordinate of second vector point * @author Nicola Asuni * @since 5.0.000 (2010-05-04) * @public static @@ -1777,17 +1795,20 @@ class TCPDF_STATIC { /** * Split string by a regular expression. * This is a wrapper for the preg_split function to avoid the bug: https://bugs.php.net/bug.php?id=45850 - * @param $pattern (string) The regular expression pattern to search for without the modifiers, as a string. - * @param $modifiers (string) The modifiers part of the pattern, - * @param $subject (string) The input string. - * @param $limit (int) If specified, then only substrings up to limit are returned with the rest of the string being placed in the last substring. A limit of -1, 0 or NULL means "no limit" and, as is standard across PHP, you can use NULL to skip to the flags parameter. - * @param $flags (int) The flags as specified on the preg_split PHP function. - * @return Returns an array containing substrings of subject split along boundaries matched by pattern.modifier + * @param string $pattern The regular expression pattern to search for without the modifiers, as a string. + * @param string $modifiers The modifiers part of the pattern, + * @param string $subject The input string. + * @param int $limit If specified, then only substrings up to limit are returned with the rest of the string being placed in the last substring. A limit of -1, 0 or NULL means "no limit" and, as is standard across PHP, you can use NULL to skip to the flags parameter. + * @param int $flags The flags as specified on the preg_split PHP function. + * @return array Returns an array containing substrings of subject split along boundaries matched by pattern.modifier * @author Nicola Asuni * @since 6.0.023 * @public static */ public static function pregSplit($pattern, $modifiers, $subject, $limit=NULL, $flags=NULL) { + // PHP 8.1 deprecates nulls for $limit and $flags + $limit = $limit === null ? -1 : $limit; + $flags = $flags === null ? 0 : $flags; // the bug only happens on PHP 5.2 when using the u modifier if ((strpos($modifiers, 'u') === FALSE) OR (count(preg_split('//u', "\n\t", -1, PREG_SPLIT_NO_EMPTY)) == 2)) { return preg_split($pattern.$modifiers, $subject, $limit, $flags); @@ -1807,9 +1828,9 @@ class TCPDF_STATIC { /** * Wrapper to use fopen only with local files - * @param filename (string) Name of the file to open - * @param $mode (string) - * @return Returns a file pointer resource on success, or FALSE on error. + * @param string $filename Name of the file to open + * @param string $mode + * @return resource|false Returns a file pointer resource on success, or FALSE on error. * @public static */ public static function fopenLocal($filename, $mode) { @@ -1823,9 +1844,10 @@ class TCPDF_STATIC { /** * Check if the URL exist. - * @param url (string) URL to check. - * @return Returns TRUE if the URL exists; FALSE otherwise. + * @param string $url URL to check. + * @return bool Returns TRUE if the URL exists; FALSE otherwise. * @public static + * @since 6.2.25 */ public static function url_exists($url) { $crs = curl_init(); @@ -1842,6 +1864,10 @@ class TCPDF_STATIC { curl_setopt($crs, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($crs, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($crs, CURLOPT_USERAGENT, 'tc-lib-file'); + curl_setopt($crs, CURLOPT_MAXREDIRS, 5); + if (defined('CURLOPT_PROTOCOLS')) { + curl_setopt($crs, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP | CURLPROTO_FTP | CURLPROTO_FTPS); + } curl_exec($crs); $code = curl_getinfo($crs, CURLINFO_HTTP_CODE); curl_close($crs); @@ -1872,8 +1898,8 @@ class TCPDF_STATIC { * Wrapper for file_exists. * Checks whether a file or directory exists. * Only allows some protocols and local files. - * @param filename (string) Path to the file or directory. - * @return Returns TRUE if the file or directory specified by filename exists; FALSE otherwise. + * @param string $filename Path to the file or directory. + * @return bool Returns TRUE if the file or directory specified by filename exists; FALSE otherwise. * @public static */ public static function file_exists($filename) { @@ -1889,8 +1915,8 @@ class TCPDF_STATIC { /** * Reads entire file into a string. * The file can be also an URL. - * @param $file (string) Name of the file or URL to read. - * @return The function returns the read data or FALSE on failure. + * @param string $file Name of the file or URL to read. + * @return string|false The function returns the read data or FALSE on failure. * @author Nicola Asuni * @since 6.0.025 * @public static @@ -1973,6 +1999,10 @@ class TCPDF_STATIC { curl_setopt($crs, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($crs, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($crs, CURLOPT_USERAGENT, 'tc-lib-file'); + curl_setopt($crs, CURLOPT_MAXREDIRS, 5); + if (defined('CURLOPT_PROTOCOLS')) { + curl_setopt($crs, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP | CURLPROTO_FTP | CURLPROTO_FTPS); + } $ret = curl_exec($crs); curl_close($crs); if ($ret !== false) { @@ -1985,8 +2015,8 @@ class TCPDF_STATIC { /** * Get ULONG from string (Big Endian 32-bit unsigned integer). - * @param $str (string) string from where to extract value - * @param $offset (int) point from where to read the data + * @param string $str string from where to extract value + * @param int $offset point from where to read the data * @return int 32 bit value * @author Nicola Asuni * @since 5.2.000 (2010-06-02) @@ -1999,8 +2029,8 @@ class TCPDF_STATIC { /** * Get USHORT from string (Big Endian 16-bit unsigned integer). - * @param $str (string) string from where to extract value - * @param $offset (int) point from where to read the data + * @param string $str string from where to extract value + * @param int $offset point from where to read the data * @return int 16 bit value * @author Nicola Asuni * @since 5.2.000 (2010-06-02) @@ -2013,8 +2043,8 @@ class TCPDF_STATIC { /** * Get SHORT from string (Big Endian 16-bit signed integer). - * @param $str (string) String from where to extract value. - * @param $offset (int) Point from where to read the data. + * @param string $str String from where to extract value. + * @param int $offset Point from where to read the data. * @return int 16 bit value * @author Nicola Asuni * @since 5.2.000 (2010-06-02) @@ -2027,8 +2057,8 @@ class TCPDF_STATIC { /** * Get FWORD from string (Big Endian 16-bit signed integer). - * @param $str (string) String from where to extract value. - * @param $offset (int) Point from where to read the data. + * @param string $str String from where to extract value. + * @param int $offset Point from where to read the data. * @return int 16 bit value * @author Nicola Asuni * @since 5.9.123 (2011-09-30) @@ -2044,8 +2074,8 @@ class TCPDF_STATIC { /** * Get UFWORD from string (Big Endian 16-bit unsigned integer). - * @param $str (string) string from where to extract value - * @param $offset (int) point from where to read the data + * @param string $str string from where to extract value + * @param int $offset point from where to read the data * @return int 16 bit value * @author Nicola Asuni * @since 5.9.123 (2011-09-30) @@ -2058,8 +2088,8 @@ class TCPDF_STATIC { /** * Get FIXED from string (32-bit signed fixed-point number (16.16). - * @param $str (string) string from where to extract value - * @param $offset (int) point from where to read the data + * @param string $str string from where to extract value + * @param int $offset point from where to read the data * @return int 16 bit value * @author Nicola Asuni * @since 5.9.123 (2011-09-30) @@ -2076,8 +2106,8 @@ class TCPDF_STATIC { /** * Get BYTE from string (8-bit unsigned integer). - * @param $str (string) String from where to extract value. - * @param $offset (int) Point from where to read the data. + * @param string $str String from where to extract value. + * @param int $offset Point from where to read the data. * @return int 8 bit value * @author Nicola Asuni * @since 5.2.000 (2010-06-02) @@ -2090,9 +2120,9 @@ class TCPDF_STATIC { /** * Binary-safe and URL-safe file read. * Reads up to length bytes from the file pointer referenced by handle. Reading stops as soon as one of the following conditions is met: length bytes have been read; EOF (end of file) is reached. - * @param $handle (resource) - * @param $length (int) - * @return Returns the read string or FALSE in case of error. + * @param resource $handle + * @param int $length + * @return string|false Returns the read string or FALSE in case of error. * @author Nicola Asuni * @since 4.5.027 (2009-03-16) * @public static @@ -2111,8 +2141,8 @@ class TCPDF_STATIC { /** * Read a 4-byte (32 bit) integer from file. - * @param $f (string) file name. - * @return 4-byte integer + * @param resource $f file resource. + * @return int 4-byte integer * @public static */ public static function _freadint($f) { @@ -2120,11 +2150,12 @@ class TCPDF_STATIC { return $a['i']; } - /** * Array of page formats * measures are calculated in this way: (inches * 72) or (millimeters * 72 / 25.4) * @public static + * + * @var array */ public static $page_formats = array( // ISO 216 A Series + 2 SIS 014711 extensions @@ -2480,7 +2511,7 @@ class TCPDF_STATIC { /** * Get page dimensions from format name. - * @param $format (mixed) The format name @see self::$page_format
      + * @param mixed $format The format name @see self::$page_format
        * @return array containing page width and height in points * @since 5.0.010 (2010-05-17) * @public static @@ -2494,20 +2525,20 @@ class TCPDF_STATIC { /** * Set page boundaries. - * @param $page (int) page number - * @param $type (string) valid values are:
        • 'MediaBox' : the boundaries of the physical medium on which the page shall be displayed or printed;
        • 'CropBox' : the visible region of default user space;
        • 'BleedBox' : the region to which the contents of the page shall be clipped when output in a production environment;
        • 'TrimBox' : the intended dimensions of the finished page after trimming;
        • 'ArtBox' : the page's meaningful content (including potential white space).
        - * @param $llx (float) lower-left x coordinate in user units. - * @param $lly (float) lower-left y coordinate in user units. - * @param $urx (float) upper-right x coordinate in user units. - * @param $ury (float) upper-right y coordinate in user units. - * @param $points (boolean) If true uses user units as unit of measure, otherwise uses PDF points. - * @param $k (float) Scale factor (number of points in user unit). - * @param $pagedim (array) Array of page dimensions. - * @return pagedim array of page dimensions. + * @param int $page page number + * @param string $type valid values are:
        • 'MediaBox' : the boundaries of the physical medium on which the page shall be displayed or printed;
        • 'CropBox' : the visible region of default user space;
        • 'BleedBox' : the region to which the contents of the page shall be clipped when output in a production environment;
        • 'TrimBox' : the intended dimensions of the finished page after trimming;
        • 'ArtBox' : the page's meaningful content (including potential white space).
        + * @param float $llx lower-left x coordinate in user units. + * @param float $lly lower-left y coordinate in user units. + * @param float $urx upper-right x coordinate in user units. + * @param float $ury upper-right y coordinate in user units. + * @param boolean $points If true uses user units as unit of measure, otherwise uses PDF points. + * @param float $k Scale factor (number of points in user unit). + * @param array $pagedim Array of page dimensions. + * @return array pagedim array of page dimensions. * @since 5.0.010 (2010-05-17) * @public static */ - public static function setPageBoxes($page, $type, $llx, $lly, $urx, $ury, $points=false, $k, $pagedim=array()) { + public static function setPageBoxes($page, $type, $llx, $lly, $urx, $ury, $points, $k, $pagedim=array()) { if (!isset($pagedim[$page])) { // initialize array $pagedim[$page] = array(); @@ -2527,9 +2558,9 @@ class TCPDF_STATIC { /** * Swap X and Y coordinates of page boxes (change page boxes orientation). - * @param $page (int) page number - * @param $pagedim (array) Array of page dimensions. - * @return pagedim array of page dimensions. + * @param int $page page number + * @param array $pagedim Array of page dimensions. + * @return array pagedim array of page dimensions. * @since 5.0.010 (2010-05-17) * @public static */ @@ -2550,8 +2581,8 @@ class TCPDF_STATIC { /** * Get the canonical page layout mode. - * @param $layout (string) The page layout. Possible values are:
        • SinglePage Display one page at a time
        • OneColumn Display the pages in one column
        • TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left
        • TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right
        • TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left
        • TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right
        - * @return (string) Canonical page layout name. + * @param string $layout The page layout. Possible values are:
        • SinglePage Display one page at a time
        • OneColumn Display the pages in one column
        • TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left
        • TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right
        • TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left
        • TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right
        + * @return string Canonical page layout name. * @public static */ public static function getPageLayoutMode($layout='SinglePage') { @@ -2593,8 +2624,8 @@ class TCPDF_STATIC { /** * Get the canonical page layout mode. - * @param $mode (string) A name object specifying how the document should be displayed when opened:
        • UseNone Neither document outline nor thumbnail images visible
        • UseOutlines Document outline visible
        • UseThumbs Thumbnail images visible
        • FullScreen Full-screen mode, with no menu bar, window controls, or any other window visible
        • UseOC (PDF 1.5) Optional content group panel visible
        • UseAttachments (PDF 1.6) Attachments panel visible
        - * @return (string) Canonical page mode name. + * @param string $mode A name object specifying how the document should be displayed when opened:
        • UseNone Neither document outline nor thumbnail images visible
        • UseOutlines Document outline visible
        • UseThumbs Thumbnail images visible
        • FullScreen Full-screen mode, with no menu bar, window controls, or any other window visible
        • UseOC (PDF 1.5) Optional content group panel visible
        • UseAttachments (PDF 1.6) Attachments panel visible
        + * @return string Canonical page mode name. * @public static */ public static function getPageMode($mode='UseNone') { diff --git a/lib/combodo/tcpdf/tcpdf.php b/lib/combodo/tcpdf/tcpdf.php index a71afc3a89..4004347303 100644 --- a/lib/combodo/tcpdf/tcpdf.php +++ b/lib/combodo/tcpdf/tcpdf.php @@ -1,13 +1,13 @@ + * @phpstan-var array{0: string, 1: string, 2: float|null} */ protected $header_font; /** * Default font used on page footer. * @protected + * @var array + * @phpstan-var array{0: string, 1: string, 2: float|null} */ protected $footer_font; @@ -652,7 +656,7 @@ class TCPDF { protected $header_title = ''; /** - * String to pring on page header after title. + * String to print on page header after title. * @protected */ protected $header_string = ''; @@ -661,6 +665,8 @@ class TCPDF { * Color for header text (RGB array). * @since 5.9.174 (2012-07-25) * @protected + * @var int[] + * @phpstan-var array{0: int, 1: int, 2: int} */ protected $header_text_color = array(0,0,0); @@ -668,6 +674,8 @@ class TCPDF { * Color for header line (RGB array). * @since 5.9.174 (2012-07-25) * @protected + * @var int[] + * @phpstan-var array{0: int, 1: int, 2: int} */ protected $header_line_color = array(0,0,0); @@ -675,6 +683,8 @@ class TCPDF { * Color for footer text (RGB array). * @since 5.9.174 (2012-07-25) * @protected + * @var int[] + * @phpstan-var array{0: int, 1: int, 2: int} */ protected $footer_text_color = array(0,0,0); @@ -682,6 +692,8 @@ class TCPDF { * Color for footer line (RGB array). * @since 5.9.174 (2012-07-25) * @protected + * @var int[] + * @phpstan-var array{0: int, 1: int, 2: int} */ protected $footer_line_color = array(0,0,0); @@ -773,13 +785,6 @@ class TCPDF { */ protected $encoding = 'UTF-8'; - /** - * PHP internal encoding. - * @protected - * @since 1.53.0.TC016 - */ - protected $internal_encoding; - /** * Boolean flag to indicate if the document language is Right-To-Left. * @protected @@ -923,6 +928,7 @@ class TCPDF { * Default cell height ratio. * @protected * @since 3.0.014 (2008-05-23) + * @var float */ protected $cell_height_ratio = K_CELL_HEIGHT_RATIO; @@ -1800,6 +1806,7 @@ class TCPDF { * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008). * @protected * @since 5.9.152 (2012-03-23) + * @var array */ protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0); @@ -1832,6 +1839,23 @@ class TCPDF { */ protected $gdgammacache = array(); + /** + * Cache array for file content + * @protected + * @var array + * @since 6.3.5 (2020-09-28) + */ + protected $fileContentCache = array(); + + /** + * Whether to allow local file path in image html tags, when prefixed with file:// + * + * @var bool + * @protected + * @since 6.4 (2020-07-23) + */ + protected $allowLocalFiles = false; + //------------------------------------------------------------ // METHODS //------------------------------------------------------------ @@ -1840,24 +1864,17 @@ class TCPDF { * This is the class constructor. * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes). * - * IMPORTANT: Please note that this method sets the mb_internal_encoding to ASCII, so if you are using the mbstring module functions with TCPDF you need to correctly set/unset the mb_internal_encoding when needed. - * - * @param $orientation (string) page orientation. Possible values are (case insensitive):
        • P or Portrait (default)
        • L or Landscape
        • '' (empty string) for automatic orientation
        - * @param $unit (string) User measure unit. Possible values are:
        • pt: point
        • mm: millimeter (default)
        • cm: centimeter
        • in: inch

        A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit. - * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat(). - * @param $unicode (boolean) TRUE means that the input text is unicode (default = true) - * @param $encoding (string) Charset encoding (used only when converting back html entities); default is UTF-8. - * @param $diskcache (boolean) DEPRECATED FEATURE - * @param $pdfa (integer) If not false, set the document to PDF/A mode and the good version (1 or 3). + * @param string $orientation page orientation. Possible values are (case insensitive):
        • P or Portrait (default)
        • L or Landscape
        • '' (empty string) for automatic orientation
        + * @param string $unit User measure unit. Possible values are:
        • pt: point
        • mm: millimeter (default)
        • cm: centimeter
        • in: inch

        A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit. + * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat(). + * @param boolean $unicode TRUE means that the input text is unicode (default = true) + * @param string $encoding Charset encoding (used only when converting back html entities); default is UTF-8. + * @param boolean $diskcache DEPRECATED FEATURE + * @param false|integer $pdfa If not false, set the document to PDF/A mode and the good version (1 or 3). * @public * @see getPageSizeFromFormat(), setPageFormat() */ public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) { - /* Set internal character encoding to ASCII */ - if (function_exists('mb_internal_encoding') AND mb_internal_encoding()) { - $this->internal_encoding = mb_internal_encoding(); - mb_internal_encoding('ASCII'); - } // set file ID for trailer $serformat = (is_array($format) ? json_encode($format) : $format); $this->file_id = md5(TCPDF_STATIC::getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding)); @@ -1932,7 +1949,7 @@ class TCPDF { $this->setPageFormat($format, $orientation); // page margins (1 cm) $margin = 28.35 / $this->k; - $this->SetMargins($margin, $margin); + $this->setMargins($margin, $margin); $this->clMargin = $this->lMargin; $this->crMargin = $this->rMargin; // internal cell padding @@ -1947,11 +1964,11 @@ class TCPDF { $this->linestyleJoin = '0 j'; $this->linestyleDash = '[] 0 d'; // automatic page break - $this->SetAutoPageBreak(true, (2 * $margin)); + $this->setAutoPageBreak(true, (2 * $margin)); // full width display mode - $this->SetDisplayMode('fullwidth'); + $this->setDisplayMode('fullwidth'); // compression - $this->SetCompression(); + $this->setCompression(); // set default PDF version number $this->setPDFVersion(); $this->tcpdflink = true; @@ -1982,7 +1999,7 @@ class TCPDF { // initialize some settings TCPDF_FONTS::utf8Bidi(array(), '', false, $this->isunicode, $this->CurrentFont); // set default font - $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt); + $this->setFont($this->FontFamily, $this->FontStyle, $this->FontSizePt); $this->setHeaderFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt)); $this->setFooterFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt)); // check if PCRE Unicode support is enabled @@ -2024,7 +2041,7 @@ class TCPDF { /** * Set the units of measure for the document. - * @param $unit (string) User measure unit. Possible values are:
        • pt: point
        • mm: millimeter (default)
        • cm: centimeter
        • in: inch

        A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit. + * @param string $unit User measure unit. Possible values are:
        • pt: point
        • mm: millimeter (default)
        • cm: centimeter
        • in: inch

        A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit. * @public * @since 3.0.015 (2008-06-06) */ @@ -2067,7 +2084,7 @@ class TCPDF { /** * Change the format of the current page - * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() documentation or an array of two numbers (width, height) or an array containing the following measures and options:
          + * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() documentation or an array of two numbers (width, height) or an array containing the following measures and options:
            *
          • ['format'] = page format name (one of the above);
          • *
          • ['Rotate'] : The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
          • *
          • ['PZ'] : The page's preferred zoom (magnification) factor.
          • @@ -2111,7 +2128,7 @@ class TCPDF { *
          • ['trans']['SS'] : (Fly transition style only) The starting or ending scale at which the changes shall be drawn. If M specifies an inward transition, the scale of the changes drawn shall progress from SS to 1.0 over the course of the transition. If M specifies an outward transition, the scale of the changes drawn shall progress from 1.0 to SS over the course of the transition. Default: 1.0.
          • *
          • ['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.
          • *
          - * @param $orientation (string) page orientation. Possible values are (case insensitive):
            + * @param string $orientation page orientation. Possible values are (case insensitive):
              *
            • P or Portrait (default)
            • *
            • L or Landscape
            • *
            • '' (empty string) for automatic orientation
            • @@ -2228,13 +2245,13 @@ class TCPDF { /** * Set page orientation. - * @param $orientation (string) page orientation. Possible values are (case insensitive):
              • P or Portrait (default)
              • L or Landscape
              • '' (empty string) for automatic orientation
              - * @param $autopagebreak (boolean) Boolean indicating if auto-page-break mode should be on or off. - * @param $bottommargin (float) bottom margin of the page. + * @param string $orientation page orientation. Possible values are (case insensitive):
              • P or Portrait (default)
              • L or Landscape
              • '' (empty string) for automatic orientation
              + * @param boolean|null $autopagebreak Boolean indicating if auto-page-break mode should be on or off. + * @param float|null $bottommargin bottom margin of the page. * @public * @since 3.0.015 (2008-06-06) */ - public function setPageOrientation($orientation, $autopagebreak='', $bottommargin='') { + public function setPageOrientation($orientation, $autopagebreak=null, $bottommargin=null) { if (!isset($this->pagedim[$this->page]['MediaBox'])) { // the boundaries of the physical medium on which the page shall be displayed or printed $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim); @@ -2306,7 +2323,7 @@ class TCPDF { $bottommargin = 2 * 28.35 / $this->k; } } - $this->SetAutoPageBreak($autopagebreak, $bottommargin); + $this->setAutoPageBreak($autopagebreak, $bottommargin); // store page dimensions $this->pagedim[$this->page]['w'] = $this->wPt; $this->pagedim[$this->page]['h'] = $this->hPt; @@ -2336,7 +2353,7 @@ class TCPDF { * \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words. * \xa0 : Unicode Character 'NO-BREAK SPACE' (U+00A0) * - * @param $re (string) regular expression (leave empty for default). + * @param string $re regular expression (leave empty for default). * @public * @since 4.6.016 (2009-06-15) */ @@ -2360,8 +2377,8 @@ class TCPDF { /** * Enable or disable Right-To-Left language mode - * @param $enable (Boolean) if true enable Right-To-Left language mode. - * @param $resetx (Boolean) if true reset the X position on direction change. + * @param boolean $enable if true enable Right-To-Left language mode. + * @param boolean $resetx if true reset the X position on direction change. * @public * @since 2.0.000 (2008-01-03) */ @@ -2377,7 +2394,7 @@ class TCPDF { /** * Return the RTL status - * @return boolean + * @return bool * @public * @since 4.0.012 (2008-07-24) */ @@ -2387,7 +2404,7 @@ class TCPDF { /** * Force temporary RTL language direction - * @param $mode (mixed) can be false, 'L' for LTR or 'R' for RTL + * @param false|string $mode can be false, 'L' for LTR or 'R' for RTL * @public * @since 2.1.000 (2008-01-09) */ @@ -2419,7 +2436,7 @@ class TCPDF { /** * Return the current temporary RTL status - * @return boolean + * @return bool * @public * @since 4.8.014 (2009-11-04) */ @@ -2429,7 +2446,7 @@ class TCPDF { /** * Set the last cell height. - * @param $h (float) cell height. + * @param float $h cell height. * @author Nicola Asuni * @public * @since 1.53.0.TC034 @@ -2440,9 +2457,10 @@ class TCPDF { /** * Return the cell height - * @param $fontsize (int) Font size in internal units - * @param $padding (boolean) If true add cell padding + * @param int $fontsize Font size in internal units + * @param boolean $padding If true add cell padding * @public + * @return float */ public function getCellHeight($fontsize, $padding=TRUE) { $height = ($fontsize * $this->cell_height_ratio); @@ -2463,7 +2481,7 @@ class TCPDF { /** * Get the last cell height. - * @return last cell height + * @return float last cell height * @public * @since 4.0.017 (2008-08-05) */ @@ -2473,7 +2491,7 @@ class TCPDF { /** * Set the adjusting factor to convert pixels to user units. - * @param $scale (float) adjusting factor to convert pixels to user units. + * @param float $scale adjusting factor to convert pixels to user units. * @author Nicola Asuni * @public * @since 1.5.2 @@ -2496,13 +2514,13 @@ class TCPDF { /** * Returns an array of page dimensions: *
              • $this->pagedim[$this->page]['w'] = page width in points
              • $this->pagedim[$this->page]['h'] = height in points
              • $this->pagedim[$this->page]['wk'] = page width in user units
              • $this->pagedim[$this->page]['hk'] = page height in user units
              • $this->pagedim[$this->page]['tm'] = top margin
              • $this->pagedim[$this->page]['bm'] = bottom margin
              • $this->pagedim[$this->page]['lm'] = left margin
              • $this->pagedim[$this->page]['rm'] = right margin
              • $this->pagedim[$this->page]['pb'] = auto page break
              • $this->pagedim[$this->page]['or'] = page orientation
              • $this->pagedim[$this->page]['olm'] = original left margin
              • $this->pagedim[$this->page]['orm'] = original right margin
              • $this->pagedim[$this->page]['Rotate'] = The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
              • $this->pagedim[$this->page]['PZ'] = The page's preferred zoom (magnification) factor.
              • $this->pagedim[$this->page]['trans'] : the style and duration of the visual transition to use when moving from another page to the given page during a presentation
                • $this->pagedim[$this->page]['trans']['Dur'] = The page's display duration (also called its advance timing): the maximum length of time, in seconds, that the page shall be displayed during presentations before the viewer application shall automatically advance to the next page.
                • $this->pagedim[$this->page]['trans']['S'] = transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade
                • $this->pagedim[$this->page]['trans']['D'] = The duration of the transition effect, in seconds.
                • $this->pagedim[$this->page]['trans']['Dm'] = (Split and Blinds transition styles only) The dimension in which the specified transition effect shall occur: H = Horizontal, V = Vertical. Default value: H.
                • $this->pagedim[$this->page]['trans']['M'] = (Split, Box and Fly transition styles only) The direction of motion for the specified transition effect: I = Inward from the edges of the page, O = Outward from the center of the pageDefault value: I.
                • $this->pagedim[$this->page]['trans']['Di'] = (Wipe, Glitter, Fly, Cover, Uncover and Push transition styles only) The direction in which the specified transition effect shall moves, expressed in degrees counterclockwise starting from a left-to-right direction. If the value is a number, it shall be one of: 0 = Left to right, 90 = Bottom to top (Wipe only), 180 = Right to left (Wipe only), 270 = Top to bottom, 315 = Top-left to bottom-right (Glitter only). If the value is a name, it shall be None, which is relevant only for the Fly transition when the value of SS is not 1.0. Default value: 0.
                • $this->pagedim[$this->page]['trans']['SS'] = (Fly transition style only) The starting or ending scale at which the changes shall be drawn. If M specifies an inward transition, the scale of the changes drawn shall progress from SS to 1.0 over the course of the transition. If M specifies an outward transition, the scale of the changes drawn shall progress from 1.0 to SS over the course of the transition. Default: 1.0.
                • $this->pagedim[$this->page]['trans']['B'] = (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.
              • $this->pagedim[$this->page]['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed
                • $this->pagedim[$this->page]['MediaBox']['llx'] = lower-left x coordinate in points
                • $this->pagedim[$this->page]['MediaBox']['lly'] = lower-left y coordinate in points
                • $this->pagedim[$this->page]['MediaBox']['urx'] = upper-right x coordinate in points
                • $this->pagedim[$this->page]['MediaBox']['ury'] = upper-right y coordinate in points
              • $this->pagedim[$this->page]['CropBox'] : the visible region of default user space
                • $this->pagedim[$this->page]['CropBox']['llx'] = lower-left x coordinate in points
                • $this->pagedim[$this->page]['CropBox']['lly'] = lower-left y coordinate in points
                • $this->pagedim[$this->page]['CropBox']['urx'] = upper-right x coordinate in points
                • $this->pagedim[$this->page]['CropBox']['ury'] = upper-right y coordinate in points
              • $this->pagedim[$this->page]['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment
                • $this->pagedim[$this->page]['BleedBox']['llx'] = lower-left x coordinate in points
                • $this->pagedim[$this->page]['BleedBox']['lly'] = lower-left y coordinate in points
                • $this->pagedim[$this->page]['BleedBox']['urx'] = upper-right x coordinate in points
                • $this->pagedim[$this->page]['BleedBox']['ury'] = upper-right y coordinate in points
              • $this->pagedim[$this->page]['TrimBox'] : the intended dimensions of the finished page after trimming
                • $this->pagedim[$this->page]['TrimBox']['llx'] = lower-left x coordinate in points
                • $this->pagedim[$this->page]['TrimBox']['lly'] = lower-left y coordinate in points
                • $this->pagedim[$this->page]['TrimBox']['urx'] = upper-right x coordinate in points
                • $this->pagedim[$this->page]['TrimBox']['ury'] = upper-right y coordinate in points
              • $this->pagedim[$this->page]['ArtBox'] : the extent of the page's meaningful content
                • $this->pagedim[$this->page]['ArtBox']['llx'] = lower-left x coordinate in points
                • $this->pagedim[$this->page]['ArtBox']['lly'] = lower-left y coordinate in points
                • $this->pagedim[$this->page]['ArtBox']['urx'] = upper-right x coordinate in points
                • $this->pagedim[$this->page]['ArtBox']['ury'] = upper-right y coordinate in points
              - * @param $pagenum (int) page number (empty = current page) + * @param int|null $pagenum page number (empty = current page) * @return array of page dimensions. * @author Nicola Asuni * @public * @since 4.5.027 (2009-03-16) */ - public function getPageDimensions($pagenum='') { + public function getPageDimensions($pagenum=null) { if (empty($pagenum)) { $pagenum = $this->page; } @@ -2511,14 +2529,14 @@ class TCPDF { /** * Returns the page width in units. - * @param $pagenum (int) page number (empty = current page) + * @param int|null $pagenum page number (empty = current page) * @return int page width. * @author Nicola Asuni * @public * @since 1.5.2 * @see getPageDimensions() */ - public function getPageWidth($pagenum='') { + public function getPageWidth($pagenum=null) { if (empty($pagenum)) { return $this->w; } @@ -2527,14 +2545,14 @@ class TCPDF { /** * Returns the page height in units. - * @param $pagenum (int) page number (empty = current page) + * @param int|null $pagenum page number (empty = current page) * @return int page height. * @author Nicola Asuni * @public * @since 1.5.2 * @see getPageDimensions() */ - public function getPageHeight($pagenum='') { + public function getPageHeight($pagenum=null) { if (empty($pagenum)) { return $this->h; } @@ -2543,14 +2561,14 @@ class TCPDF { /** * Returns the page break margin. - * @param $pagenum (int) page number (empty = current page) + * @param int|null $pagenum page number (empty = current page) * @return int page break margin. * @author Nicola Asuni * @public * @since 1.5.2 * @see getPageDimensions() */ - public function getBreakMargin($pagenum='') { + public function getBreakMargin($pagenum=null) { if (empty($pagenum)) { return $this->bMargin; } @@ -2570,19 +2588,19 @@ class TCPDF { /** * Defines the left, top and right margins. - * @param $left (float) Left margin. - * @param $top (float) Top margin. - * @param $right (float) Right margin. Default value is the left one. - * @param $keepmargins (boolean) if true overwrites the default page margins + * @param float $left Left margin. + * @param float $top Top margin. + * @param float $right Right margin. Default value is the left one. + * @param boolean $keepmargins if true overwrites the default page margins * @public * @since 1.0 * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak() */ - public function SetMargins($left, $top, $right=-1, $keepmargins=false) { + public function setMargins($left, $top, $right=null, $keepmargins=false) { //Set left, top and right margins $this->lMargin = $left; $this->tMargin = $top; - if ($right == -1) { + if ($right == -1 OR $right === null) { $right = $left; } $this->rMargin = $right; @@ -2595,12 +2613,12 @@ class TCPDF { /** * Defines the left margin. The method can be called before creating the first page. If the current abscissa gets out of page, it is brought back to the margin. - * @param $margin (float) The margin. + * @param float $margin The margin. * @public * @since 1.4 * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins() */ - public function SetLeftMargin($margin) { + public function setLeftMargin($margin) { //Set left margin $this->lMargin = $margin; if (($this->page > 0) AND ($this->x < $margin)) { @@ -2610,12 +2628,12 @@ class TCPDF { /** * Defines the top margin. The method can be called before creating the first page. - * @param $margin (float) The margin. + * @param float $margin The margin. * @public * @since 1.5 * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins() */ - public function SetTopMargin($margin) { + public function setTopMargin($margin) { //Set top margin $this->tMargin = $margin; if (($this->page > 0) AND ($this->y < $margin)) { @@ -2625,12 +2643,12 @@ class TCPDF { /** * Defines the right margin. The method can be called before creating the first page. - * @param $margin (float) The margin. + * @param float $margin The margin. * @public * @since 1.5 * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins() */ - public function SetRightMargin($margin) { + public function setRightMargin($margin) { $this->rMargin = $margin; if (($this->page > 0) AND ($this->x > ($this->w - $margin))) { $this->x = $this->w - $margin; @@ -2639,12 +2657,12 @@ class TCPDF { /** * Set the same internal Cell padding for top, right, bottom, left- - * @param $pad (float) internal padding. + * @param float $pad internal padding. * @public * @since 2.1.000 (2008-01-09) * @see getCellPaddings(), setCellPaddings() */ - public function SetCellPadding($pad) { + public function setCellPadding($pad) { if ($pad >= 0) { $this->cell_padding['L'] = $pad; $this->cell_padding['T'] = $pad; @@ -2655,25 +2673,25 @@ class TCPDF { /** * Set the internal Cell paddings. - * @param $left (float) left padding - * @param $top (float) top padding - * @param $right (float) right padding - * @param $bottom (float) bottom padding + * @param float|null $left left padding + * @param float|null $top top padding + * @param float|null $right right padding + * @param float|null $bottom bottom padding * @public * @since 5.9.000 (2010-10-03) * @see getCellPaddings(), SetCellPadding() */ - public function setCellPaddings($left='', $top='', $right='', $bottom='') { - if (($left !== '') AND ($left >= 0)) { + public function setCellPaddings($left=null, $top=null, $right=null, $bottom=null) { + if (!TCPDF_STATIC::empty_string($left) AND ($left >= 0)) { $this->cell_padding['L'] = $left; } - if (($top !== '') AND ($top >= 0)) { + if (!TCPDF_STATIC::empty_string($top) AND ($top >= 0)) { $this->cell_padding['T'] = $top; } - if (($right !== '') AND ($right >= 0)) { + if (!TCPDF_STATIC::empty_string($right) AND ($right >= 0)) { $this->cell_padding['R'] = $right; } - if (($bottom !== '') AND ($bottom >= 0)) { + if (!TCPDF_STATIC::empty_string($bottom) AND ($bottom >= 0)) { $this->cell_padding['B'] = $bottom; } } @@ -2691,25 +2709,25 @@ class TCPDF { /** * Set the internal Cell margins. - * @param $left (float) left margin - * @param $top (float) top margin - * @param $right (float) right margin - * @param $bottom (float) bottom margin + * @param float|null $left left margin + * @param float|null $top top margin + * @param float|null $right right margin + * @param float|null $bottom bottom margin * @public * @since 5.9.000 (2010-10-03) * @see getCellMargins() */ - public function setCellMargins($left='', $top='', $right='', $bottom='') { - if (($left !== '') AND ($left >= 0)) { + public function setCellMargins($left=null, $top=null, $right=null, $bottom=null) { + if (!TCPDF_STATIC::empty_string($left) AND ($left >= 0)) { $this->cell_margin['L'] = $left; } - if (($top !== '') AND ($top >= 0)) { + if (!TCPDF_STATIC::empty_string($top) AND ($top >= 0)) { $this->cell_margin['T'] = $top; } - if (($right !== '') AND ($right >= 0)) { + if (!TCPDF_STATIC::empty_string($right) AND ($right >= 0)) { $this->cell_margin['R'] = $right; } - if (($bottom !== '') AND ($bottom >= 0)) { + if (!TCPDF_STATIC::empty_string($bottom) AND ($bottom >= 0)) { $this->cell_margin['B'] = $bottom; } } @@ -2727,8 +2745,8 @@ class TCPDF { /** * Adjust the internal Cell padding array to take account of the line width. - * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) - * @return array of adjustments + * @param string|array|int $brd Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) + * @return void|array array of adjustments * @public * @since 5.9.000 (2010-10-03) */ @@ -2744,7 +2762,11 @@ class TCPDF { $newbrd[$brd[$i]] = true; } $brd = $newbrd; - } elseif (($brd === 1) OR ($brd === true) OR (is_numeric($brd) AND (intval($brd) > 0))) { + } elseif ( + ($brd === 1) + || ($brd === true) + || (is_numeric($brd) && ((int)$brd > 0)) + ) { $brd = array('LRTB' => true); } if (!is_array($brd)) { @@ -2762,7 +2784,7 @@ class TCPDF { // process borders foreach ($brd as $border => $style) { $line_width = $this->LineWidth; - if (is_array($style) AND isset($style['width'])) { + if (is_array($style) && isset($style['width'])) { // get border width $line_width = $style['width']; } @@ -2783,31 +2805,54 @@ class TCPDF { } } // correct internal cell padding if required to avoid overlap between text and lines - if ((strpos($border,'T') !== false) AND ($this->cell_padding['T'] < $adj)) { + if ( + is_numeric($this->cell_padding['T']) + && ($this->cell_padding['T'] < $adj) + && (strpos($border, 'T') !== false) + ) { $this->cell_padding['T'] = $adj; } - if ((strpos($border,'R') !== false) AND ($this->cell_padding['R'] < $adj)) { + if ( + is_numeric($this->cell_padding['R']) + && ($this->cell_padding['R'] < $adj) + && (strpos($border, 'R') !== false) + ) { $this->cell_padding['R'] = $adj; } - if ((strpos($border,'B') !== false) AND ($this->cell_padding['B'] < $adj)) { + if ( + is_numeric($this->cell_padding['B']) + && ($this->cell_padding['B'] < $adj) + && (strpos($border, 'B') !== false) + ) { $this->cell_padding['B'] = $adj; } - if ((strpos($border,'L') !== false) AND ($this->cell_padding['L'] < $adj)) { + if ( + is_numeric($this->cell_padding['L']) + && ($this->cell_padding['L'] < $adj) + && (strpos($border, 'L') !== false) + ) { $this->cell_padding['L'] = $adj; } + } - return array('T' => ($this->cell_padding['T'] - $cp['T']), 'R' => ($this->cell_padding['R'] - $cp['R']), 'B' => ($this->cell_padding['B'] - $cp['B']), 'L' => ($this->cell_padding['L'] - $cp['L'])); + + return array( + 'T' => ($this->cell_padding['T'] - $cp['T']), + 'R' => ($this->cell_padding['R'] - $cp['R']), + 'B' => ($this->cell_padding['B'] - $cp['B']), + 'L' => ($this->cell_padding['L'] - $cp['L']), + ); } /** * Enables or disables the automatic page breaking mode. When enabling, the second parameter is the distance from the bottom of the page that defines the triggering limit. By default, the mode is on and the margin is 2 cm. - * @param $auto (boolean) Boolean indicating if mode should be on or off. - * @param $margin (float) Distance from the bottom of the page. + * @param boolean $auto Boolean indicating if mode should be on or off. + * @param float $margin Distance from the bottom of the page. * @public * @since 1.0 * @see Cell(), MultiCell(), AcceptPageBreak() */ - public function SetAutoPageBreak($auto, $margin=0) { + public function setAutoPageBreak($auto, $margin=0) { $this->AutoPageBreak = $auto ? true : false; $this->bMargin = $margin; $this->PageBreakTrigger = $this->h - $margin; @@ -2815,7 +2860,7 @@ class TCPDF { /** * Return the auto-page-break mode (true or false). - * @return boolean auto-page-break mode + * @return bool auto-page-break mode * @public * @since 5.9.088 */ @@ -2825,13 +2870,13 @@ class TCPDF { /** * Defines the way the document is to be displayed by the viewer. - * @param $zoom (mixed) The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use.
              • fullpage: displays the entire page on screen
              • fullwidth: uses maximum width of window
              • real: uses real size (equivalent to 100% zoom)
              • default: uses viewer default mode
              - * @param $layout (string) The page layout. Possible values are:
              • SinglePage Display one page at a time
              • OneColumn Display the pages in one column
              • TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left
              • TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right
              • TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left
              • TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right
              - * @param $mode (string) A name object specifying how the document should be displayed when opened:
              • UseNone Neither document outline nor thumbnail images visible
              • UseOutlines Document outline visible
              • UseThumbs Thumbnail images visible
              • FullScreen Full-screen mode, with no menu bar, window controls, or any other window visible
              • UseOC (PDF 1.5) Optional content group panel visible
              • UseAttachments (PDF 1.6) Attachments panel visible
              + * @param mixed $zoom The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use.
              • fullpage: displays the entire page on screen
              • fullwidth: uses maximum width of window
              • real: uses real size (equivalent to 100% zoom)
              • default: uses viewer default mode
              + * @param string $layout The page layout. Possible values are:
              • SinglePage Display one page at a time
              • OneColumn Display the pages in one column
              • TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left
              • TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right
              • TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left
              • TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right
              + * @param string $mode A name object specifying how the document should be displayed when opened:
              • UseNone Neither document outline nor thumbnail images visible
              • UseOutlines Document outline visible
              • UseThumbs Thumbnail images visible
              • FullScreen Full-screen mode, with no menu bar, window controls, or any other window visible
              • UseOC (PDF 1.5) Optional content group panel visible
              • UseAttachments (PDF 1.6) Attachments panel visible
              * @public * @since 1.2 */ - public function SetDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') { + public function setDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') { if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) { $this->ZoomMode = $zoom; } else { @@ -2844,11 +2889,11 @@ class TCPDF { /** * Activates or deactivates page compression. When activated, the internal representation of each page is compressed, which leads to a compression ratio of about 2 for the resulting document. Compression is on by default. * Note: the Zlib extension is required for this feature. If not present, compression will be turned off. - * @param $compress (boolean) Boolean indicating if compression must be enabled. + * @param boolean $compress Boolean indicating if compression must be enabled. * @public * @since 1.4 */ - public function SetCompression($compress=true) { + public function setCompression($compress=true) { $this->compress = false; if (function_exists('gzcompress')) { if ($compress) { @@ -2861,7 +2906,7 @@ class TCPDF { /** * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document. - * @param $mode (boolean) If true force sRGB output intent. + * @param boolean $mode If true force sRGB output intent. * @public * @since 5.9.121 (2011-09-28) */ @@ -2872,72 +2917,84 @@ class TCPDF { /** * Turn on/off Unicode mode for document information dictionary (meta tags). * This has effect only when unicode mode is set to false. - * @param $unicode (boolean) if true set the meta information in Unicode + * @param boolean $unicode if true set the meta information in Unicode * @since 5.9.027 (2010-12-01) * @public */ - public function SetDocInfoUnicode($unicode=true) { + public function setDocInfoUnicode($unicode=true) { $this->docinfounicode = $unicode ? true : false; } /** * Defines the title of the document. - * @param $title (string) The title. + * @param string $title The title. * @public * @since 1.2 * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject() */ - public function SetTitle($title) { + public function setTitle($title) { $this->title = $title; } /** * Defines the subject of the document. - * @param $subject (string) The subject. + * @param string $subject The subject. * @public * @since 1.2 * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle() */ - public function SetSubject($subject) { + public function setSubject($subject) { $this->subject = $subject; } /** * Defines the author of the document. - * @param $author (string) The name of the author. + * @param string $author The name of the author. * @public * @since 1.2 * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle() */ - public function SetAuthor($author) { + public function setAuthor($author) { $this->author = $author; } /** * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'. - * @param $keywords (string) The list of keywords. + * @param string $keywords The list of keywords. * @public * @since 1.2 * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle() */ - public function SetKeywords($keywords) { + public function setKeywords($keywords) { $this->keywords = $keywords; } /** * Defines the creator of the document. This is typically the name of the application that generates the PDF. - * @param $creator (string) The name of the creator. + * @param string $creator The name of the creator. * @public * @since 1.2 * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle() */ - public function SetCreator($creator) { + public function setCreator($creator) { $this->creator = $creator; } + /** + * Whether to allow local file path in image html tags, when prefixed with file:// + * + * @param bool $allowLocalFiles true, when local files should be allowed. Otherwise false. + * @public + * @since 6.4 + */ + public function setAllowLocalFiles($allowLocalFiles) { + $this->allowLocalFiles = (bool) $allowLocalFiles; + } + + /** * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true. - * @param $msg (string) The error message + * @param string $msg The error message * @public * @since 1.0 */ @@ -2984,13 +3041,13 @@ class TCPDF { $gvars = $this->getGraphicVars(); $this->setEqualColumns(); $this->lastpage(true); - $this->SetAutoPageBreak(false); + $this->setAutoPageBreak(false); $this->x = 0; $this->y = $this->h - (1 / $this->k); $this->lMargin = 0; $this->_outSaveGraphicsState(); $font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica'; - $this->SetFont($font, '', 1); + $this->setFont($font, '', 1); $this->setTextRenderingMode(0, false, false); $msg = "\x50\x6f\x77\x65\x72\x65\x64\x20\x62\x79\x20\x54\x43\x50\x44\x46\x20\x28\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67\x29"; $lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67"; @@ -3009,8 +3066,8 @@ class TCPDF { /** * Move pointer at the specified document page and update page dimensions. - * @param $pnum (int) page number (1 ... numpages) - * @param $resetmargins (boolean) if true reset left, right, top margins and Y position. + * @param int $pnum page number (1 ... numpages) + * @param boolean $resetmargins if true reset left, right, top margins and Y position. * @public * @since 2.1.000 (2008-01-07) * @see getPage(), lastpage(), getNumPages() @@ -3035,13 +3092,13 @@ class TCPDF { $this->original_rMargin = $this->pagedim[$this->page]['orm']; $this->AutoPageBreak = $this->pagedim[$this->page]['pb']; $this->CurOrientation = $this->pagedim[$this->page]['or']; - $this->SetAutoPageBreak($this->AutoPageBreak, $this->bMargin); + $this->setAutoPageBreak($this->AutoPageBreak, $this->bMargin); // restore graphic settings //$this->setGraphicVars($gvars); if ($resetmargins) { $this->lMargin = $this->pagedim[$this->page]['olm']; $this->rMargin = $this->pagedim[$this->page]['orm']; - $this->SetY($this->tMargin); + $this->setY($this->tMargin); } else { // account for booklet mode if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) { @@ -3057,7 +3114,7 @@ class TCPDF { /** * Reset pointer to the last document page. - * @param $resetmargins (boolean) if true reset left, right, top margins and Y position. + * @param boolean $resetmargins if true reset left, right, top margins and Y position. * @public * @since 2.0.000 (2008-01-04) * @see setPage(), getPage(), getNumPages() @@ -3090,9 +3147,9 @@ class TCPDF { /** * Adds a new TOC (Table Of Content) page to the document. - * @param $orientation (string) page orientation. - * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat(). - * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins + * @param string $orientation page orientation. + * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat(). + * @param boolean $keepmargins if true overwrites the default page margins with the current margins * @public * @since 5.0.001 (2010-05-06) * @see AddPage(), startPage(), endPage(), endTOCPage() @@ -3114,10 +3171,10 @@ class TCPDF { /** * Adds a new page to the document. If a page is already present, the Footer() method is called first to output the footer (if enabled). Then the page is added, the current position set to the top-left corner according to the left and top margins (or top-right if in RTL mode), and Header() is called to display the header (if enabled). * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards. - * @param $orientation (string) page orientation. Possible values are (case insensitive):
              • P or PORTRAIT (default)
              • L or LANDSCAPE
              - * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat(). - * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins - * @param $tocpage (boolean) if true set the tocpage state to true (the added page will be used to display Table Of Content). + * @param string $orientation page orientation. Possible values are (case insensitive):
              • P or PORTRAIT (default)
              • L or LANDSCAPE
              + * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat(). + * @param boolean $keepmargins if true overwrites the default page margins with the current margins + * @param boolean $tocpage if true set the tocpage state to true (the added page will be used to display Table Of Content). * @public * @since 1.0 * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat() @@ -3141,7 +3198,7 @@ class TCPDF { /** * Terminate the current page - * @param $tocpage (boolean) if true set the tocpage state to false (end the page used to display Table Of Content). + * @param boolean $tocpage if true set the tocpage state to false (end the page used to display Table Of Content). * @public * @since 4.2.010 (2008-11-14) * @see AddPage(), startPage(), addTOCPage(), endTOCPage() @@ -3165,9 +3222,9 @@ class TCPDF { /** * Starts a new page to the document. The page must be closed using the endPage() function. * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards. - * @param $orientation (string) page orientation. Possible values are (case insensitive):
              • P or PORTRAIT (default)
              • L or LANDSCAPE
              - * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat(). - * @param $tocpage (boolean) if true the page is designated to contain the Table-Of-Content. + * @param string $orientation page orientation. Possible values are (case insensitive):
              • P or PORTRAIT (default)
              • L or LANDSCAPE
              + * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat(). + * @param boolean $tocpage if true the page is designated to contain the Table-Of-Content. * @since 4.2.010 (2008-11-14) * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat() * @public @@ -3204,7 +3261,7 @@ class TCPDF { if ($this->numpages > $this->page) { // this page has been already added $this->setPage($this->page + 1); - $this->SetY($this->tMargin); + $this->setY($this->tMargin); return; } // start a new page @@ -3252,7 +3309,7 @@ class TCPDF { /** * Set start-writing mark on selected page. * Borders and fills are always created after content and inserted on the position marked by this method. - * @param $page (int) page number (default is the current page) + * @param int $page page number (default is the current page) * @protected * @since 4.6.021 (2009-07-20) */ @@ -3269,12 +3326,12 @@ class TCPDF { /** * Set header data. - * @param $ln (string) header image logo - * @param $lw (string) header image logo width in mm - * @param $ht (string) string to print as title on document header - * @param $hs (string) string to print on document header - * @param $tc (array) RGB array color for text. - * @param $lc (array) RGB array color for line. + * @param string $ln header image logo + * @param int $lw header image logo width in mm + * @param string $ht string to print as title on document header + * @param string $hs string to print on document header + * @param int[] $tc RGB array color for text. + * @param int[] $lc RGB array color for line. * @public */ public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) { @@ -3288,8 +3345,8 @@ class TCPDF { /** * Set footer data. - * @param $tc (array) RGB array color for text. - * @param $lc (array) RGB array color for line. + * @param int[] $tc RGB array color for text. + * @param int[] $lc RGB array color for line. * @public */ public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) { @@ -3300,7 +3357,7 @@ class TCPDF { /** * Returns header data: *
              • $ret['logo'] = logo image
              • $ret['logo_width'] = width of the image logo in user units
              • $ret['title'] = header title
              • $ret['string'] = header description string
              - * @return array() + * @return array * @public * @since 4.0.012 (2008-07-24) */ @@ -3318,7 +3375,7 @@ class TCPDF { /** * Set header margin. * (minimum distance between header and top page margin) - * @param $hm (int) distance in user units + * @param int $hm distance in user units * @public */ public function setHeaderMargin($hm=10) { @@ -3338,7 +3395,7 @@ class TCPDF { /** * Set footer margin. * (minimum distance between footer and bottom page margin) - * @param $fm (int) distance in user units + * @param int $fm distance in user units * @public */ public function setFooterMargin($fm=10) { @@ -3356,7 +3413,7 @@ class TCPDF { } /** * Set a flag to print page header. - * @param $val (boolean) set to true to print the page header (default), false otherwise. + * @param boolean $val set to true to print the page header (default), false otherwise. * @public */ public function setPrintHeader($val=true) { @@ -3365,7 +3422,7 @@ class TCPDF { /** * Set a flag to print page footer. - * @param $val (boolean) set to true to print the page footer (default), false otherwise. + * @param boolean $val set to true to print the page footer (default), false otherwise. * @public */ public function setPrintFooter($val=true) { @@ -3400,7 +3457,7 @@ class TCPDF { /** * Set a flag to automatically reset the xobject template used by Header() method at each page. - * @param $val (boolean) set to true to reset Header xobject template at each page, false otherwise. + * @param boolean $val set to true to reset Header xobject template at each page, false otherwise. * @public */ public function setHeaderTemplateAutoreset($val=true) { @@ -3445,22 +3502,22 @@ class TCPDF { $header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1); } $cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1); - $this->SetTextColorArray($this->header_text_color); + $this->setTextColorArray($this->header_text_color); // header title - $this->SetFont($headerfont[0], 'B', $headerfont[2] + 1); - $this->SetX($header_x); + $this->setFont($headerfont[0], 'B', $headerfont[2] + 1); + $this->setX($header_x); $this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0); // header string - $this->SetFont($headerfont[0], $headerfont[1], $headerfont[2]); - $this->SetX($header_x); + $this->setFont($headerfont[0], $headerfont[1], $headerfont[2]); + $this->setX($header_x); $this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false); // print an ending header line - $this->SetLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color'])); - $this->SetY((2.835 / $this->k) + max($imgy, $this->y)); + $this->setLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color'])); + $this->setY((2.835 / $this->k) + max($imgy, $this->y)); if ($this->rtl) { - $this->SetX($this->original_rMargin); + $this->setX($this->original_rMargin); } else { - $this->SetX($this->original_lMargin); + $this->setX($this->original_lMargin); } $this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C'); $this->endTemplate(); @@ -3491,10 +3548,10 @@ class TCPDF { */ public function Footer() { $cur_y = $this->y; - $this->SetTextColorArray($this->footer_text_color); + $this->setTextColorArray($this->footer_text_color); //set style for cell border $line_width = (0.85 / $this->k); - $this->SetLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color)); + $this->setLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color)); //print document barcode $barcode = $this->getBarcode(); if (!empty($barcode)) { @@ -3520,13 +3577,13 @@ class TCPDF { } else { $pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias(); } - $this->SetY($cur_y); + $this->setY($cur_y); //Print page number if ($this->getRTL()) { - $this->SetX($this->original_rMargin); + $this->setX($this->original_rMargin); $this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L'); } else { - $this->SetX($this->original_lMargin); + $this->setX($this->original_lMargin); $this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R'); } } @@ -3549,20 +3606,20 @@ class TCPDF { $this->_outSaveGraphicsState(); $this->rMargin = $this->original_rMargin; $this->lMargin = $this->original_lMargin; - $this->SetCellPadding(0); + $this->setCellPadding(0); //set current position if ($this->rtl) { - $this->SetXY($this->original_rMargin, $this->header_margin); + $this->setXY($this->original_rMargin, $this->header_margin); } else { - $this->SetXY($this->original_lMargin, $this->header_margin); + $this->setXY($this->original_lMargin, $this->header_margin); } - $this->SetFont($this->header_font[0], $this->header_font[1], $this->header_font[2]); + $this->setFont($this->header_font[0], $this->header_font[1], $this->header_font[2]); $this->Header(); //restore position if ($this->rtl) { - $this->SetXY($this->original_rMargin, $this->tMargin); + $this->setXY($this->original_rMargin, $this->tMargin); } else { - $this->SetXY($this->original_lMargin, $this->tMargin); + $this->setXY($this->original_lMargin, $this->tMargin); } $this->_outRestoreGraphicsState(); $this->lasth = $lasth; @@ -3597,21 +3654,21 @@ class TCPDF { $this->_outSaveGraphicsState(); $this->rMargin = $this->original_rMargin; $this->lMargin = $this->original_lMargin; - $this->SetCellPadding(0); + $this->setCellPadding(0); //set current position $footer_y = $this->h - $this->footer_margin; if ($this->rtl) { - $this->SetXY($this->original_rMargin, $footer_y); + $this->setXY($this->original_rMargin, $footer_y); } else { - $this->SetXY($this->original_lMargin, $footer_y); + $this->setXY($this->original_lMargin, $footer_y); } - $this->SetFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]); + $this->setFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]); $this->Footer(); //restore position if ($this->rtl) { - $this->SetXY($this->original_rMargin, $this->tMargin); + $this->setXY($this->original_rMargin, $this->tMargin); } else { - $this->SetXY($this->original_lMargin, $this->tMargin); + $this->setXY($this->original_lMargin, $this->tMargin); } $this->_outRestoreGraphicsState(); $this->lasth = $lasth; @@ -3629,7 +3686,7 @@ class TCPDF { /** * Check if we are on the page body (excluding page header and footer). - * @return true if we are not in page header nor in page footer, false otherwise. + * @return bool true if we are not in page header nor in page footer, false otherwise. * @protected * @since 5.9.091 (2011-06-15) */ @@ -3715,7 +3772,7 @@ class TCPDF { /** * Returns the array of spot colors. - * @return (array) Spot colors array. + * @return array Spot colors array. * @public * @since 6.0.038 (2013-09-30) */ @@ -3727,11 +3784,11 @@ class TCPDF { * Defines a new spot color. * It can be expressed in RGB components or gray scale. * The method can be called before the first page is created and the value is retained from page to page. - * @param $name (string) Full name of the spot color. - * @param $c (float) Cyan color for CMYK. Value between 0 and 100. - * @param $m (float) Magenta color for CMYK. Value between 0 and 100. - * @param $y (float) Yellow color for CMYK. Value between 0 and 100. - * @param $k (float) Key (Black) color for CMYK. Value between 0 and 100. + * @param string $name Full name of the spot color. + * @param float $c Cyan color for CMYK. Value between 0 and 100. + * @param float $m Magenta color for CMYK. Value between 0 and 100. + * @param float $y Yellow color for CMYK. Value between 0 and 100. + * @param float $k Key (Black) color for CMYK. Value between 0 and 100. * @public * @since 4.0.024 (2008-09-12) * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor() @@ -3745,10 +3802,10 @@ class TCPDF { /** * Set the spot color for the specified type ('draw', 'fill', 'text'). - * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text'). - * @param $name (string) Name of the spot color. - * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default). - * @return (string) PDF color command. + * @param string $type Type of object affected by this color: ('draw', 'fill', 'text'). + * @param string $name Name of the spot color. + * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default). + * @return string PDF color command. * @public * @since 5.9.125 (2011-10-03) */ @@ -3792,37 +3849,37 @@ class TCPDF { /** * Defines the spot color used for all drawing operations (lines, rectangles and cell borders). - * @param $name (string) Name of the spot color. - * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default). + * @param string $name Name of the spot color. + * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default). * @public * @since 4.0.024 (2008-09-12) * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor() */ - public function SetDrawSpotColor($name, $tint=100) { + public function setDrawSpotColor($name, $tint=100) { $this->setSpotColor('draw', $name, $tint); } /** * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds). - * @param $name (string) Name of the spot color. - * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default). + * @param string $name Name of the spot color. + * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default). * @public * @since 4.0.024 (2008-09-12) * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor() */ - public function SetFillSpotColor($name, $tint=100) { + public function setFillSpotColor($name, $tint=100) { $this->setSpotColor('fill', $name, $tint); } /** * Defines the spot color used for text. - * @param $name (string) Name of the spot color. - * @param $tint (int) Intensity of the color (from 0 to 100 ; 100 = full intensity by default). + * @param string $name Name of the spot color. + * @param int $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default). * @public * @since 4.0.024 (2008-09-12) * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor() */ - public function SetTextSpotColor($name, $tint=100) { + public function setTextSpotColor($name, $tint=100) { $this->setSpotColor('text', $name, $tint); } @@ -3830,10 +3887,10 @@ class TCPDF { * Set the color array for the specified type ('draw', 'fill', 'text'). * It can be expressed in RGB, CMYK or GRAY SCALE components. * The method can be called before the first page is created and the value is retained from page to page. - * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text'). - * @param $color (array) Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values). - * @param $ret (boolean) If true do not send the PDF command. - * @return (string) The PDF command or empty string. + * @param string $type Type of object affected by this color: ('draw', 'fill', 'text'). + * @param array $color Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values). + * @param boolean $ret If true do not send the PDF command. + * @return string The PDF command or empty string. * @public * @since 3.1.000 (2008-06-11) */ @@ -3861,14 +3918,14 @@ class TCPDF { * Defines the color used for all drawing operations (lines, rectangles and cell borders). * It can be expressed in RGB, CMYK or GRAY SCALE components. * The method can be called before the first page is created and the value is retained from page to page. - * @param $color (array) Array of colors (1, 3 or 4 values). - * @param $ret (boolean) If true do not send the PDF command. + * @param array $color Array of colors (1, 3 or 4 values). + * @param boolean $ret If true do not send the PDF command. * @return string the PDF command * @public * @since 3.1.000 (2008-06-11) * @see SetDrawColor() */ - public function SetDrawColorArray($color, $ret=false) { + public function setDrawColorArray($color, $ret=false) { return $this->setColorArray('draw', $color, $ret); } @@ -3876,39 +3933,39 @@ class TCPDF { * Defines the color used for all filling operations (filled rectangles and cell backgrounds). * It can be expressed in RGB, CMYK or GRAY SCALE components. * The method can be called before the first page is created and the value is retained from page to page. - * @param $color (array) Array of colors (1, 3 or 4 values). - * @param $ret (boolean) If true do not send the PDF command. + * @param array $color Array of colors (1, 3 or 4 values). + * @param boolean $ret If true do not send the PDF command. * @public * @since 3.1.000 (2008-6-11) * @see SetFillColor() */ - public function SetFillColorArray($color, $ret=false) { + public function setFillColorArray($color, $ret=false) { return $this->setColorArray('fill', $color, $ret); } /** * Defines the color used for text. It can be expressed in RGB components or gray scale. * The method can be called before the first page is created and the value is retained from page to page. - * @param $color (array) Array of colors (1, 3 or 4 values). - * @param $ret (boolean) If true do not send the PDF command. + * @param array $color Array of colors (1, 3 or 4 values). + * @param boolean $ret If true do not send the PDF command. * @public * @since 3.1.000 (2008-6-11) * @see SetFillColor() */ - public function SetTextColorArray($color, $ret=false) { + public function setTextColorArray($color, $ret=false) { return $this->setColorArray('text', $color, $ret); } /** * Defines the color used by the specified type ('draw', 'fill', 'text'). - * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text'). - * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100). - * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100). - * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100). - * @param $col4 (float) KEY (BLACK) color for CMYK (0-100). - * @param $ret (boolean) If true do not send the command. - * @param $name (string) spot color name (if any) - * @return (string) The PDF command or empty string. + * @param string $type Type of object affected by this color: ('draw', 'fill', 'text'). + * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100). + * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100). + * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100). + * @param float $col4 KEY (BLACK) color for CMYK (0-100). + * @param boolean $ret If true do not send the command. + * @param string $name spot color name (if any) + * @return string The PDF command or empty string. * @public * @since 5.9.125 (2011-10-03) */ @@ -3980,7 +4037,7 @@ class TCPDF { } } $this->ColorFlag = ($this->FillColor != $this->TextColor); - if (($type != 'text') AND ($this->state == 2)) { + if (($type != 'text') AND ($this->state == 2) AND $type !== 0) { if (!$ret) { $this->_out($pdfcolor); } @@ -3991,63 +4048,63 @@ class TCPDF { /** * Defines the color used for all drawing operations (lines, rectangles and cell borders). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page. - * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100). - * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100). - * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100). - * @param $col4 (float) KEY (BLACK) color for CMYK (0-100). - * @param $ret (boolean) If true do not send the command. - * @param $name (string) spot color name (if any) + * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100). + * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100). + * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100). + * @param float $col4 KEY (BLACK) color for CMYK (0-100). + * @param boolean $ret If true do not send the command. + * @param string $name spot color name (if any) * @return string the PDF command * @public * @since 1.3 * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell() */ - public function SetDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') { + public function setDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') { return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name); } /** * Defines the color used for all filling operations (filled rectangles and cell backgrounds). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page. - * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100). - * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100). - * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100). - * @param $col4 (float) KEY (BLACK) color for CMYK (0-100). - * @param $ret (boolean) If true do not send the command. - * @param $name (string) Spot color name (if any). - * @return (string) The PDF command. + * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100). + * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100). + * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100). + * @param float $col4 KEY (BLACK) color for CMYK (0-100). + * @param boolean $ret If true do not send the command. + * @param string $name Spot color name (if any). + * @return string The PDF command. * @public * @since 1.3 * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell() */ - public function SetFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') { + public function setFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') { return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name); } /** * Defines the color used for text. It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page. - * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100). - * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100). - * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100). - * @param $col4 (float) KEY (BLACK) color for CMYK (0-100). - * @param $ret (boolean) If true do not send the command. - * @param $name (string) Spot color name (if any). - * @return (string) Empty string. + * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100). + * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100). + * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100). + * @param float $col4 KEY (BLACK) color for CMYK (0-100). + * @param boolean $ret If true do not send the command. + * @param string $name Spot color name (if any). + * @return string Empty string. * @public * @since 1.3 * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell() */ - public function SetTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') { + public function setTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') { return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name); } /** * Returns the length of a string in user unit. A font must be selected.
              - * @param $s (string) The string whose length is to be computed - * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained. - * @param $fontstyle (string) Font style. Possible values are (case insensitive):
              • empty string: regular
              • B: bold
              • I: italic
              • U: underline
              • D: line-through
              • O: overline
              or any combination. The default value is regular. - * @param $fontsize (float) Font size in points. The default value is the current size. - * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length. - * @return mixed int total string length or array of characted widths + * @param string $s The string whose length is to be computed + * @param string $fontname Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained. + * @param string $fontstyle Font style. Possible values are (case insensitive):
              • empty string: regular
              • B: bold
              • I: italic
              • U: underline
              • D: line-through
              • O: overline
              or any combination. The default value is regular. + * @param float $fontsize Font size in points. The default value is the current size. + * @param boolean $getarray if true returns an array of characters widths, if false returns the total length. + * @return float[]|float total string length or array of characted widths * @author Nicola Asuni * @public * @since 1.2 @@ -4058,12 +4115,12 @@ class TCPDF { /** * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.
              - * @param $sa (string) The array of chars whose total length is to be computed - * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained. - * @param $fontstyle (string) Font style. Possible values are (case insensitive):
              • empty string: regular
              • B: bold
              • I: italic
              • U: underline
              • D: line through
              • O: overline
              or any combination. The default value is regular. - * @param $fontsize (float) Font size in points. The default value is the current size. - * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length. - * @return mixed int total string length or array of characted widths + * @param array $sa The array of chars whose total length is to be computed + * @param string $fontname Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained. + * @param string $fontstyle Font style. Possible values are (case insensitive):
              • empty string: regular
              • B: bold
              • I: italic
              • U: underline
              • D: line through
              • O: overline
              or any combination. The default value is regular. + * @param float $fontsize Font size in points. The default value is the current size. + * @param boolean $getarray if true returns an array of characters widths, if false returns the total length. + * @return float[]|float total string length or array of characted widths * @author Nicola Asuni * @public * @since 2.4.000 (2008-03-06) @@ -4074,7 +4131,7 @@ class TCPDF { $prev_FontFamily = $this->FontFamily; $prev_FontStyle = $this->FontStyle; $prev_FontSizePt = $this->FontSizePt; - $this->SetFont($fontname, $fontstyle, $fontsize, '', 'default', false); + $this->setFont($fontname, $fontstyle, $fontsize, '', 'default', false); } // convert UTF-8 array to Latin1 if required if ($this->isunicode AND (!$this->isUnicodeFont())) { @@ -4090,7 +4147,7 @@ class TCPDF { } // restore previous values if (!TCPDF_STATIC::empty_string($fontname)) { - $this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false); + $this->setFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false); } if ($getarray) { return $wa; @@ -4100,8 +4157,8 @@ class TCPDF { /** * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking). - * @param $char (int) The char code whose length is to be returned - * @param $notlast (boolean) If false ignore the font-spacing. + * @param int $char The char code whose length is to be returned + * @param boolean $notlast If false ignore the font-spacing. * @return float char width * @author Nicola Asuni * @public @@ -4123,7 +4180,7 @@ class TCPDF { /** * Returns the length of the char in user unit for the current font. - * @param $char (int) The char code whose length is to be returned + * @param int $char The char code whose length is to be returned * @return float char width * @author Nicola Asuni * @public @@ -4134,8 +4191,8 @@ class TCPDF { // SHY character will not be printed return (0); } - if (isset($this->CurrentFont['cw'][$char])) { - $w = $this->CurrentFont['cw'][$char]; + if (isset($this->CurrentFont['cw'][intval($char)])) { + $w = $this->CurrentFont['cw'][intval($char)]; } elseif (isset($this->CurrentFont['dw'])) { // default width $w = $this->CurrentFont['dw']; @@ -4150,7 +4207,7 @@ class TCPDF { /** * Returns the numbero of characters in a string. - * @param $s (string) The input string. + * @param string $s The input string. * @return int number of characters * @public * @since 2.0.0001 (2008-01-07) @@ -4182,11 +4239,11 @@ class TCPDF { * Imports a TrueType, Type1, core, or CID0 font and makes it available. * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT). * The definition file (and the font file itself when embedding) must be present either in the current directory or in the one indicated by K_PATH_FONTS if the constant is defined. If it could not be found, the error "Could not include font definition file" is generated. - * @param $family (string) Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font. - * @param $style (string) Font style. Possible values are (case insensitive):
              • empty string: regular (default)
              • B: bold
              • I: italic
              • BI or IB: bold italic
              - * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces. - * @return array containing the font data, or false in case of error. - * @param $subset (mixed) if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font. + * @param string $family Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font. + * @param string $style Font style. Possible values are (case insensitive):
              • empty string: regular (default)
              • B: bold
              • I: italic
              • BI or IB: bold italic
              + * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces. + * @return array|false array containing the font data, or false in case of error. + * @param mixed $subset if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font. * @public * @since 1.5 * @see SetFont(), setFontSubsetting() @@ -4436,18 +4493,18 @@ class TCPDF { * The method can be called before the first page is created and the font is retained from page to page. * If you just wish to change the current font size, it is simpler to call SetFontSize(). * Note: for the standard fonts, the font metric files must be accessible. There are three possibilities for this:
              • They are in the current directory (the one where the running script lies)
              • They are in one of the directories defined by the include_path parameter
              • They are in the directory defined by the K_PATH_FONTS constant

              - * @param $family (string) Family font. It can be either a name defined by AddFont() or one of the standard Type1 families (case insensitive):
              • times (Times-Roman)
              • timesb (Times-Bold)
              • timesi (Times-Italic)
              • timesbi (Times-BoldItalic)
              • helvetica (Helvetica)
              • helveticab (Helvetica-Bold)
              • helveticai (Helvetica-Oblique)
              • helveticabi (Helvetica-BoldOblique)
              • courier (Courier)
              • courierb (Courier-Bold)
              • courieri (Courier-Oblique)
              • courierbi (Courier-BoldOblique)
              • symbol (Symbol)
              • zapfdingbats (ZapfDingbats)
              It is also possible to pass an empty string. In that case, the current family is retained. - * @param $style (string) Font style. Possible values are (case insensitive):
              • empty string: regular
              • B: bold
              • I: italic
              • U: underline
              • D: line through
              • O: overline
              or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats basic fonts or other fonts when not defined. - * @param $size (float) Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12 - * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces. - * @param $subset (mixed) if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font. - * @param $out (boolean) if true output the font size command, otherwise only set the font properties. + * @param string $family Family font. It can be either a name defined by AddFont() or one of the standard Type1 families (case insensitive):
              • times (Times-Roman)
              • timesb (Times-Bold)
              • timesi (Times-Italic)
              • timesbi (Times-BoldItalic)
              • helvetica (Helvetica)
              • helveticab (Helvetica-Bold)
              • helveticai (Helvetica-Oblique)
              • helveticabi (Helvetica-BoldOblique)
              • courier (Courier)
              • courierb (Courier-Bold)
              • courieri (Courier-Oblique)
              • courierbi (Courier-BoldOblique)
              • symbol (Symbol)
              • zapfdingbats (ZapfDingbats)
              It is also possible to pass an empty string. In that case, the current family is retained. + * @param string $style Font style. Possible values are (case insensitive):
              • empty string: regular
              • B: bold
              • I: italic
              • U: underline
              • D: line through
              • O: overline
              or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats basic fonts or other fonts when not defined. + * @param float|null $size Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12 + * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces. + * @param mixed $subset if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font. + * @param boolean $out if true output the font size command, otherwise only set the font properties. * @author Nicola Asuni * @public * @since 1.0 * @see AddFont(), SetFontSize() */ - public function SetFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) { + public function setFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) { //Select a font; size given in points if ($size === null) { $size = $this->FontSizePt; @@ -4464,18 +4521,18 @@ class TCPDF { $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']); } $this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']); - $this->SetFontSize($size, $out); + $this->setFontSize($size, $out); } /** * Defines the size of the current font. - * @param $size (float) The font size in points. - * @param $out (boolean) if true output the font size command, otherwise only set the font properties. + * @param float $size The font size in points. + * @param boolean $out if true output the font size command, otherwise only set the font properties. * @public * @since 1.0 * @see SetFont() */ - public function SetFontSize($size, $out=true) { + public function setFontSize($size, $out=true) { $size = (float)$size; // font size in points $this->FontSizePt = $size; @@ -4551,7 +4608,7 @@ class TCPDF { /** * Convert a relative font measure into absolute value. - * @param $s (int) Font measure. + * @param int $s Font measure. * @return float Absolute measure. * @since 5.9.186 (2012-09-13) */ @@ -4561,8 +4618,8 @@ class TCPDF { /** * Returns the glyph bounding box of the specified character in the current font in user units. - * @param $char (int) Input character code. - * @return mixed array(xMin, yMin, xMax, yMax) or FALSE if not defined. + * @param int $char Input character code. + * @return false|array array(xMin, yMin, xMax, yMax) or FALSE if not defined. * @since 5.9.186 (2012-09-13) */ public function getCharBBox($char) { @@ -4580,9 +4637,9 @@ class TCPDF { /** * Return the font descent value - * @param $font (string) font name - * @param $style (string) font style - * @param $size (float) The size (in points) + * @param string $font font name + * @param string $style font style + * @param float $size The size (in points) * @return int font descent * @public * @author Nicola Asuni @@ -4601,9 +4658,9 @@ class TCPDF { /** * Return the font ascent value. - * @param $font (string) font name - * @param $style (string) font style - * @param $size (float) The size (in points) + * @param string $font font name + * @param string $style font style + * @param float $size The size (in points) * @return int font ascent * @public * @author Nicola Asuni @@ -4622,10 +4679,10 @@ class TCPDF { /** * Return true in the character is present in the specified font. - * @param $char (mixed) Character to check (integer value or string) - * @param $font (string) Font name (family name). - * @param $style (string) Font style. - * @return (boolean) true if the char is defined, false otherwise. + * @param mixed $char Character to check (integer value or string) + * @param string $font Font name (family name). + * @param string $style Font style. + * @return bool true if the char is defined, false otherwise. * @public * @since 5.9.153 (2012-03-28) */ @@ -4648,11 +4705,11 @@ class TCPDF { /** * Replace missing font characters on selected font with specified substitutions. - * @param $text (string) Text to process. - * @param $font (string) Font name (family name). - * @param $style (string) Font style. - * @param $subs (array) Array of possible character substitutions. The key is the character to check (integer value) and the value is a single intege value or an array of possible substitutes. - * @return (string) Processed text. + * @param string $text Text to process. + * @param string $font Font name (family name). + * @param string $style Font style. + * @param array $subs Array of possible character substitutions. The key is the character to check (integer value) and the value is a single intege value or an array of possible substitutes. + * @return string Processed text. * @public * @since 5.9.153 (2012-03-28) */ @@ -4689,11 +4746,11 @@ class TCPDF { /** * Defines the default monospaced font. - * @param $font (string) Font name. + * @param string $font Font name. * @public * @since 4.5.025 */ - public function SetDefaultMonospacedFont($font) { + public function setDefaultMonospacedFont($font) { $this->default_monospaced_font = $font; } @@ -4713,14 +4770,14 @@ class TCPDF { /** * Defines the page and position a link points to. - * @param $link (int) The link identifier returned by AddLink() - * @param $y (float) Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page) - * @param $page (int|string) Number of target page; -1 indicates the current page (default value). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages. + * @param int $link The link identifier returned by AddLink() + * @param float $y Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page) + * @param int|string $page Number of target page; -1 indicates the current page (default value). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages. * @public * @since 1.5 * @see AddLink() */ - public function SetLink($link, $y=0, $page=-1) { + public function setLink($link, $y=0, $page=-1) { $fixed = false; if (!empty($page) AND (substr($page, 0, 1) == '*')) { $page = intval(substr($page, 1)); @@ -4739,12 +4796,12 @@ class TCPDF { /** * Puts a link on a rectangular area of the page. * Text or image links are generally put via Cell(), Write() or Image(), but this method can be useful for instance to define a clickable area inside an image. - * @param $x (float) Abscissa of the upper-left corner of the rectangle - * @param $y (float) Ordinate of the upper-left corner of the rectangle - * @param $w (float) Width of the rectangle - * @param $h (float) Height of the rectangle - * @param $link (mixed) URL or identifier returned by AddLink() - * @param $spaces (int) number of spaces on the text to link + * @param float $x Abscissa of the upper-left corner of the rectangle + * @param float $y Ordinate of the upper-left corner of the rectangle + * @param float $w Width of the rectangle + * @param float $h Height of the rectangle + * @param mixed $link URL or identifier returned by AddLink() + * @param int $spaces number of spaces on the text to link * @public * @since 1.5 * @see AddLink(), Annotation(), Cell(), Write(), Image() @@ -4756,13 +4813,13 @@ class TCPDF { /** * Puts a markup annotation on a rectangular area of the page. * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!! - * @param $x (float) Abscissa of the upper-left corner of the rectangle - * @param $y (float) Ordinate of the upper-left corner of the rectangle - * @param $w (float) Width of the rectangle - * @param $h (float) Height of the rectangle - * @param $text (string) annotation text or alternate content - * @param $opt (array) array of options (see section 8.4 of PDF reference 1.7). - * @param $spaces (int) number of spaces on the text to link + * @param float $x Abscissa of the upper-left corner of the rectangle + * @param float $y Ordinate of the upper-left corner of the rectangle + * @param float $w Width of the rectangle + * @param float $h Height of the rectangle + * @param string $text annotation text or alternate content + * @param array $opt array of options (see section 8.4 of PDF reference 1.7). + * @param int $spaces number of spaces on the text to link * @public * @since 4.0.018 (2008-08-06) */ @@ -4863,7 +4920,7 @@ class TCPDF { } reset($this->embeddedfiles); foreach ($this->embeddedfiles as $filename => $filedata) { - $data = TCPDF_STATIC::fileGetContents($filedata['file']); + $data = $this->getCachedFileContents($filedata['file']); if ($data !== FALSE) { $rawsize = strlen($data); if ($rawsize > 0) { @@ -4902,31 +4959,31 @@ class TCPDF { /** * Prints a text cell at the specified position. * This method allows to place a string precisely on the page. - * @param $x (float) Abscissa of the cell origin - * @param $y (float) Ordinate of the cell origin - * @param $txt (string) String to print - * @param $fstroke (int) outline size in user units (false = disable) - * @param $fclip (boolean) if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation). - * @param $ffill (boolean) if true fills the text - * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) - * @param $ln (int) Indicates where the current position should go after the call. Possible values are:
              • 0: to the right (or left for RTL languages)
              • 1: to the beginning of the next line
              • 2: below
              Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0. - * @param $align (string) Allows to center or align the text. Possible values are:
              • L or empty string: left align (default value)
              • C: center
              • R: right align
              • J: justify
              - * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false). - * @param $link (mixed) URL or identifier returned by AddLink(). - * @param $stretch (int) font stretch mode:
              • 0 = disabled
              • 1 = horizontal scaling only if text is larger than cell width
              • 2 = forced horizontal scaling to fit cell width
              • 3 = character spacing only if text is larger than cell width
              • 4 = forced character spacing to fit cell width
              General font stretching and scaling values will be preserved when possible. - * @param $ignore_min_height (boolean) if true ignore automatic minimum height value. - * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:
              • T : cell top
              • A : font top
              • L : font baseline
              • D : font bottom
              • B : cell bottom
              - * @param $valign (string) text vertical alignment inside the cell. Possible values are:
              • T : top
              • C : center
              • B : bottom
              - * @param $rtloff (boolean) if true uses the page top-left corner as origin of axis for $x and $y initial position. + * @param float $x Abscissa of the cell origin + * @param float $y Ordinate of the cell origin + * @param string $txt String to print + * @param int $fstroke outline size in user units (0 = disable) + * @param boolean $fclip if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation). + * @param boolean $ffill if true fills the text + * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) + * @param int $ln Indicates where the current position should go after the call. Possible values are:
              • 0: to the right (or left for RTL languages)
              • 1: to the beginning of the next line
              • 2: below
              Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0. + * @param string $align Allows to center or align the text. Possible values are:
              • L or empty string: left align (default value)
              • C: center
              • R: right align
              • J: justify
              + * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false). + * @param mixed $link URL or identifier returned by AddLink(). + * @param int $stretch font stretch mode:
              • 0 = disabled
              • 1 = horizontal scaling only if text is larger than cell width
              • 2 = forced horizontal scaling to fit cell width
              • 3 = character spacing only if text is larger than cell width
              • 4 = forced character spacing to fit cell width
              General font stretching and scaling values will be preserved when possible. + * @param boolean $ignore_min_height if true ignore automatic minimum height value. + * @param string $calign cell vertical alignment relative to the specified Y value. Possible values are:
              • T : cell top
              • A : font top
              • L : font baseline
              • D : font bottom
              • B : cell bottom
              + * @param string $valign text vertical alignment inside the cell. Possible values are:
              • T : top
              • C : center
              • B : bottom
              + * @param boolean $rtloff if true uses the page top-left corner as origin of axis for $x and $y initial position. * @public * @since 1.0 * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell() */ - public function Text($x, $y, $txt, $fstroke=false, $fclip=false, $ffill=true, $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M', $rtloff=false) { + public function Text($x, $y, $txt, $fstroke=0, $fclip=false, $ffill=true, $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M', $rtloff=false) { $textrendermode = $this->textrendermode; $textstrokewidth = $this->textstrokewidth; $this->setTextRenderingMode($fstroke, $ffill, $fclip); - $this->SetXY($x, $y, $rtloff); + $this->setXY($x, $y, $rtloff); $this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign); // restore previous rendering mode $this->textrendermode = $textrendermode; @@ -4937,7 +4994,7 @@ class TCPDF { * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value. * The default implementation returns a value according to the mode selected by SetAutoPageBreak().
              * This method is called automatically and should not be called directly by the application. - * @return boolean + * @return bool * @public * @since 1.4 * @see SetAutoPageBreak() @@ -4962,14 +5019,14 @@ class TCPDF { /** * Add page if needed. - * @param $h (float) Cell height. Default value: 0. - * @param $y (mixed) starting y position, leave empty for current position. - * @param $addpage (boolean) if true add a page, otherwise only return the true/false state - * @return boolean true in case of page break, false otherwise. + * @param float $h Cell height. Default value: 0. + * @param float|null $y starting y position, leave empty for current position. + * @param bool $addpage if true add a page, otherwise only return the true/false state + * @return bool true in case of page break, false otherwise. * @since 3.2.000 (2008-07-01) * @protected */ - protected function checkPageBreak($h=0, $y='', $addpage=true) { + protected function checkPageBreak($h=0, $y=null, $addpage=true) { if (TCPDF_STATIC::empty_string($y)) { $y = $this->y; } @@ -5007,18 +5064,18 @@ class TCPDF { /** * Prints a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.
              * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting. - * @param $w (float) Cell width. If 0, the cell extends up to the right margin. - * @param $h (float) Cell height. Default value: 0. - * @param $txt (string) String to print. Default value: empty string. - * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) - * @param $ln (int) Indicates where the current position should go after the call. Possible values are:
              • 0: to the right (or left for RTL languages)
              • 1: to the beginning of the next line
              • 2: below
              Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0. - * @param $align (string) Allows to center or align the text. Possible values are:
              • L or empty string: left align (default value)
              • C: center
              • R: right align
              • J: justify
              - * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false). - * @param $link (mixed) URL or identifier returned by AddLink(). - * @param $stretch (int) font stretch mode:
              • 0 = disabled
              • 1 = horizontal scaling only if text is larger than cell width
              • 2 = forced horizontal scaling to fit cell width
              • 3 = character spacing only if text is larger than cell width
              • 4 = forced character spacing to fit cell width
              General font stretching and scaling values will be preserved when possible. - * @param $ignore_min_height (boolean) if true ignore automatic minimum height value. - * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:
              • T : cell top
              • C : center
              • B : cell bottom
              • A : font top
              • L : font baseline
              • D : font bottom
              - * @param $valign (string) text vertical alignment inside the cell. Possible values are:
              • T : top
              • C : center
              • B : bottom
              + * @param float $w Cell width. If 0, the cell extends up to the right margin. + * @param float $h Cell height. Default value: 0. + * @param string $txt String to print. Default value: empty string. + * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) + * @param int $ln Indicates where the current position should go after the call. Possible values are:
              • 0: to the right (or left for RTL languages)
              • 1: to the beginning of the next line
              • 2: below
              Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0. + * @param string $align Allows to center or align the text. Possible values are:
              • L or empty string: left align (default value)
              • C: center
              • R: right align
              • J: justify
              + * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false). + * @param mixed $link URL or identifier returned by AddLink(). + * @param int $stretch font stretch mode:
              • 0 = disabled
              • 1 = horizontal scaling only if text is larger than cell width
              • 2 = forced horizontal scaling to fit cell width
              • 3 = character spacing only if text is larger than cell width
              • 4 = forced character spacing to fit cell width
              General font stretching and scaling values will be preserved when possible. + * @param boolean $ignore_min_height if true ignore automatic minimum height value. + * @param string $calign cell vertical alignment relative to the specified Y value. Possible values are:
              • T : cell top
              • C : center
              • B : cell bottom
              • A : font top
              • L : font baseline
              • D : font bottom
              + * @param string $valign text vertical alignment inside the cell. Possible values are:
              • T : top
              • C : center
              • B : bottom
              * @public * @since 1.0 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak() @@ -5046,9 +5103,9 @@ class TCPDF { // print shadow $this->x += $this->txtshadow['depth_w']; $this->y += $this->txtshadow['depth_h']; - $this->SetFillColorArray($this->txtshadow['color']); - $this->SetTextColorArray($this->txtshadow['color']); - $this->SetDrawColorArray($this->txtshadow['color']); + $this->setFillColorArray($this->txtshadow['color']); + $this->setTextColorArray($this->txtshadow['color']); + $this->setDrawColorArray($this->txtshadow['color']); if ($this->txtshadow['opacity'] != $alpha['CA']) { $this->setAlpha($this->txtshadow['opacity'], $this->txtshadow['blend_mode']); } @@ -5058,9 +5115,9 @@ class TCPDF { //restore data $this->x = $x; $this->y = $y; - $this->SetFillColorArray($bc); - $this->SetTextColorArray($fc); - $this->SetDrawColorArray($sc); + $this->setFillColorArray($bc); + $this->setTextColorArray($fc); + $this->setDrawColorArray($sc); if ($this->txtshadow['opacity'] != $alpha['CA']) { $this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']); } @@ -5075,18 +5132,18 @@ class TCPDF { /** * Returns the PDF string code to print a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.
              * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting. - * @param $w (float) Cell width. If 0, the cell extends up to the right margin. - * @param $h (float) Cell height. Default value: 0. - * @param $txt (string) String to print. Default value: empty string. - * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) - * @param $ln (int) Indicates where the current position should go after the call. Possible values are:
              • 0: to the right (or left for RTL languages)
              • 1: to the beginning of the next line
              • 2: below
              Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0. - * @param $align (string) Allows to center or align the text. Possible values are:
              • L or empty string: left align (default value)
              • C: center
              • R: right align
              • J: justify
              - * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false). - * @param $link (mixed) URL or identifier returned by AddLink(). - * @param $stretch (int) font stretch mode:
              • 0 = disabled
              • 1 = horizontal scaling only if text is larger than cell width
              • 2 = forced horizontal scaling to fit cell width
              • 3 = character spacing only if text is larger than cell width
              • 4 = forced character spacing to fit cell width
              General font stretching and scaling values will be preserved when possible. - * @param $ignore_min_height (boolean) if true ignore automatic minimum height value. - * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:
              • T : cell top
              • C : center
              • B : cell bottom
              • A : font top
              • L : font baseline
              • D : font bottom
              - * @param $valign (string) text vertical alignment inside the cell. Possible values are:
              • T : top
              • M : middle
              • B : bottom
              + * @param float $w Cell width. If 0, the cell extends up to the right margin. + * @param float $h Cell height. Default value: 0. + * @param string $txt String to print. Default value: empty string. + * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) + * @param int $ln Indicates where the current position should go after the call. Possible values are:
              • 0: to the right (or left for RTL languages)
              • 1: to the beginning of the next line
              • 2: below
              Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0. + * @param string $align Allows to center or align the text. Possible values are:
              • L or empty string: left align (default value)
              • C: center
              • R: right align
              • J: justify
              + * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false). + * @param mixed $link URL or identifier returned by AddLink(). + * @param int $stretch font stretch mode:
              • 0 = disabled
              • 1 = horizontal scaling only if text is larger than cell width
              • 2 = forced horizontal scaling to fit cell width
              • 3 = character spacing only if text is larger than cell width
              • 4 = forced character spacing to fit cell width
              General font stretching and scaling values will be preserved when possible. + * @param boolean $ignore_min_height if true ignore automatic minimum height value. + * @param string $calign cell vertical alignment relative to the specified Y value. Possible values are:
              • T : cell top
              • C : center
              • B : cell bottom
              • A : font top
              • L : font baseline
              • D : font bottom
              + * @param string $valign text vertical alignment inside the cell. Possible values are:
              • T : top
              • M : middle
              • B : bottom
              * @return string containing cell code * @protected * @since 1.0 @@ -5554,8 +5611,8 @@ class TCPDF { /** * Replace a char if is defined on the current font. - * @param $oldchar (int) Integer code (unicode) of the character to replace. - * @param $newchar (int) Integer code (unicode) of the new character. + * @param int $oldchar Integer code (unicode) of the character to replace. + * @param int $newchar Integer code (unicode) of the new character. * @return int the replaced char or the old char in case the new char i not defined * @protected * @since 5.9.167 (2012-06-22) @@ -5573,11 +5630,11 @@ class TCPDF { /** * Returns the code to draw the cell border - * @param $x (float) X coordinate. - * @param $y (float) Y coordinate. - * @param $w (float) Cell width. - * @param $h (float) Cell height. - * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) + * @param float $x X coordinate. + * @param float $y Y coordinate. + * @param float $w Cell width. + * @param float $h Cell height. + * @param string|array|int $brd Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) * @return string containing cell border code * @protected * @see SetLineStyle() @@ -5625,7 +5682,7 @@ class TCPDF { if (is_array($style) AND !empty($style)) { // apply border style $prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '; - $s .= $this->SetLineStyle($style, true)."\n"; + $s .= $this->setLineStyle($style, true)."\n"; } switch ($mode) { case 'ext': { @@ -5765,28 +5822,28 @@ class TCPDF { * This method allows printing text with line breaks. * They can be automatic (as soon as the text reaches the right border of the cell) or explicit (via the \n character). As many cells as necessary are output, one below the other.
              * Text can be aligned, centered or justified. The cell block can be framed and the background painted. - * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page. - * @param $h (float) Cell minimum height. The cell extends automatically if needed. - * @param $txt (string) String to print - * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) - * @param $align (string) Allows to center or align the text. Possible values are:
              • L or empty string: left align
              • C: center
              • R: right align
              • J: justification (default value when $ishtml=false)
              - * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false). - * @param $ln (int) Indicates where the current position should go after the call. Possible values are:
              • 0: to the right
              • 1: to the beginning of the next line [DEFAULT]
              • 2: below
              - * @param $x (float) x position in user units - * @param $y (float) y position in user units - * @param $reseth (boolean) if true reset the last cell height (default true). - * @param $stretch (int) font stretch mode:
              • 0 = disabled
              • 1 = horizontal scaling only if text is larger than cell width
              • 2 = forced horizontal scaling to fit cell width
              • 3 = character spacing only if text is larger than cell width
              • 4 = forced character spacing to fit cell width
              General font stretching and scaling values will be preserved when possible. - * @param $ishtml (boolean) INTERNAL USE ONLY -- set to true if $txt is HTML content (default = false). Never set this parameter to true, use instead writeHTMLCell() or writeHTML() methods. - * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width. - * @param $maxh (float) maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature. This feature works only when $ishtml=false. - * @param $valign (string) Vertical alignment of text (requires $maxh = $h > 0). Possible values are:
              • T: TOP
              • M: middle
              • B: bottom
              . This feature works only when $ishtml=false and the cell must fit in a single page. - * @param $fitcell (boolean) if true attempt to fit all the text within the cell by reducing the font size (do not work in HTML mode). $maxh must be greater than 0 and equal to $h. + * @param float $w Width of cells. If 0, they extend up to the right margin of the page. + * @param float $h Cell minimum height. The cell extends automatically if needed. + * @param string $txt String to print + * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) + * @param string $align Allows to center or align the text. Possible values are:
              • L or empty string: left align
              • C: center
              • R: right align
              • J: justification (default value when $ishtml=false)
              + * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false). + * @param int $ln Indicates where the current position should go after the call. Possible values are:
              • 0: to the right
              • 1: to the beginning of the next line [DEFAULT]
              • 2: below
              + * @param float|null $x x position in user units + * @param float|null $y y position in user units + * @param boolean $reseth if true reset the last cell height (default true). + * @param int $stretch font stretch mode:
              • 0 = disabled
              • 1 = horizontal scaling only if text is larger than cell width
              • 2 = forced horizontal scaling to fit cell width
              • 3 = character spacing only if text is larger than cell width
              • 4 = forced character spacing to fit cell width
              General font stretching and scaling values will be preserved when possible. + * @param boolean $ishtml INTERNAL USE ONLY -- set to true if $txt is HTML content (default = false). Never set this parameter to true, use instead writeHTMLCell() or writeHTML() methods. + * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width. + * @param float $maxh maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature. This feature works only when $ishtml=false. + * @param string $valign Vertical alignment of text (requires $maxh = $h > 0). Possible values are:
              • T: TOP
              • M: middle
              • B: bottom
              . This feature works only when $ishtml=false and the cell must fit in a single page. + * @param boolean $fitcell if true attempt to fit all the text within the cell by reducing the font size (do not work in HTML mode). $maxh must be greater than 0 and equal to $h. * @return int Return the number of cells or 1 for html mode. * @public * @since 1.3 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak() */ - public function MultiCell($w, $h, $txt, $border=0, $align='J', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0, $valign='T', $fitcell=false) { + public function MultiCell($w, $h, $txt, $border=0, $align='J', $fill=false, $ln=1, $x=null, $y=null, $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0, $valign='T', $fitcell=false) { $prev_cell_margin = $this->cell_margin; $prev_cell_padding = $this->cell_padding; // adjust internal padding @@ -5801,7 +5858,7 @@ class TCPDF { $this->resetLastH(); } if (!TCPDF_STATIC::empty_string($y)) { - $this->SetY($y); // set y in order to convert negative y values to positive ones + $this->setY($y); // set y in order to convert negative y values to positive ones } $y = $this->GetY(); $resth = 0; @@ -5816,7 +5873,7 @@ class TCPDF { // get current column $startcolumn = $this->current_column; if (!TCPDF_STATIC::empty_string($x)) { - $this->SetX($x); + $this->setX($x); } else { $x = $this->GetX(); } @@ -5877,7 +5934,7 @@ class TCPDF { $maxit = (2 * min(100, max(10, intval($fmax)))); // max number of iterations while ($maxit >= 0) { $fmid = (($fmax + $fmin) / 2); - $this->SetFontSize($fmid, false); + $this->setFontSize($fmid, false); $this->resetLastH(); $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border); $diff = ($maxh - $text_height); @@ -5893,11 +5950,11 @@ class TCPDF { } if ($maxit < 0) { // premature exit, we get the minimum font value to fit the cell - $this->SetFontSize($fmin); + $this->setFontSize($fmin); $this->resetLastH(); $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border); } else { - $this->SetFontSize($fmid); + $this->setFontSize($fmid); $this->resetLastH(); } } @@ -5914,7 +5971,7 @@ class TCPDF { $nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin); if ($fitcell) { // restore font size - $this->SetFontSize($prev_FontSizePt); + $this->setFontSize($prev_FontSizePt); } } if ($autopadding) { @@ -5961,7 +6018,7 @@ class TCPDF { $this->setPage($page); if ($this->num_columns < 2) { // single-column mode - $this->SetX($x); + $this->setX($x); $this->y = $this->tMargin; } // account for margin changes @@ -6123,15 +6180,15 @@ class TCPDF { } if ($ln > 0) { //Go to the beginning of the next line - $this->SetY($currentY + $mc_margin['B']); + $this->setY($currentY + $mc_margin['B']); if ($ln == 2) { - $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']); + $this->setX($x + $w + $mc_margin['L'] + $mc_margin['R']); } } else { // go left or right by case $this->setPage($startpage); $this->y = $y; - $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']); + $this->setX($x + $w + $mc_margin['L'] + $mc_margin['R']); } $this->setContentMark(); $this->cell_padding = $prev_cell_padding; @@ -6143,18 +6200,18 @@ class TCPDF { /** * This method return the estimated number of lines for print a simple text string using Multicell() method. - * @param $txt (string) String for calculating his height - * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page. - * @param $reseth (boolean) if true reset the last cell height (default false). - * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true). - * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding. - * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) + * @param string $txt String for calculating his height + * @param float $w Width of cells. If 0, they extend up to the right margin of the page. + * @param boolean $reseth if true reset the last cell height (default false). + * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width (default true). + * @param array|null $cellpadding Internal cell padding, if empty uses default cell padding. + * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) * @return float Return the minimal height needed for multicell method for printing the $txt param. * @author Alexander Escalona Fern\E1ndez, Nicola Asuni * @public * @since 4.5.011 */ - public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding='', $border=0) { + public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding=null, $border=0) { if ($txt === NULL) { return 0; } @@ -6237,7 +6294,7 @@ class TCPDF { * $start_page = $pdf->getPage(); * // call your printing functions with your parameters * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * $pdf->MultiCell($w=0, $h=0, $txt, $border=1, $align='L', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0); + * $pdf->MultiCell($w=0, $h=0, $txt, $border=1, $align='L', $fill=false, $ln=1, $x=null, $y=null, $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0); * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * // get the new Y * $end_y = $pdf->GetY(); @@ -6263,17 +6320,17 @@ class TCPDF { * // restore previous object * $pdf = $pdf->rollbackTransaction(); * - * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page. - * @param $txt (string) String for calculating his height - * @param $reseth (boolean) if true reset the last cell height (default false). - * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true). - * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding. - * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) + * @param float $w Width of cells. If 0, they extend up to the right margin of the page. + * @param string $txt String for calculating his height + * @param boolean $reseth if true reset the last cell height (default false). + * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width (default true). + * @param array|null $cellpadding Internal cell padding, if empty uses default cell padding. + * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) * @return float Return the minimal height needed for multicell method for printing the $txt param. * @author Nicola Asuni, Alexander Escalona Fern\E1ndez * @public */ - public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding='', $border=0) { + public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding=null, $border=0) { // adjust internal padding $prev_cell_padding = $this->cell_padding; $prev_lasth = $this->lasth; @@ -6290,30 +6347,30 @@ class TCPDF { /** * This method prints text from the current position.
              - * @param $h (float) Line height - * @param $txt (string) String to print - * @param $link (mixed) URL or identifier returned by AddLink() - * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false). - * @param $align (string) Allows to center or align the text. Possible values are:
              • L or empty string: left align (default value)
              • C: center
              • R: right align
              • J: justify
              - * @param $ln (boolean) if true set cursor at the bottom of the line, otherwise set cursor at the top of the line. - * @param $stretch (int) font stretch mode:
              • 0 = disabled
              • 1 = horizontal scaling only if text is larger than cell width
              • 2 = forced horizontal scaling to fit cell width
              • 3 = character spacing only if text is larger than cell width
              • 4 = forced character spacing to fit cell width
              General font stretching and scaling values will be preserved when possible. - * @param $firstline (boolean) if true prints only the first line and return the remaining string. - * @param $firstblock (boolean) if true the string is the starting of a line. - * @param $maxh (float) maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature. - * @param $wadj (float) first line width will be reduced by this amount (used in HTML mode). - * @param $margin (array) margin array of the parent container + * @param float $h Line height + * @param string $txt String to print + * @param mixed $link URL or identifier returned by AddLink() + * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false). + * @param string $align Allows to center or align the text. Possible values are:
              • L or empty string: left align (default value)
              • C: center
              • R: right align
              • J: justify
              + * @param boolean $ln if true set cursor at the bottom of the line, otherwise set cursor at the top of the line. + * @param int $stretch font stretch mode:
              • 0 = disabled
              • 1 = horizontal scaling only if text is larger than cell width
              • 2 = forced horizontal scaling to fit cell width
              • 3 = character spacing only if text is larger than cell width
              • 4 = forced character spacing to fit cell width
              General font stretching and scaling values will be preserved when possible. + * @param boolean $firstline if true prints only the first line and return the remaining string. + * @param boolean $firstblock if true the string is the starting of a line. + * @param float $maxh maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature. + * @param float $wadj first line width will be reduced by this amount (used in HTML mode). + * @param array|null $margin margin array of the parent container * @return mixed Return the number of cells or the remaining string if $firstline = true. * @public * @since 1.5 */ - public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin='') { + public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin=null) { // check page for no-write regions and adapt page margins if necessary list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y); if (strlen($txt) == 0) { // fix empty text $txt = ' '; } - if ($margin === '') { + if (!is_array($margin)) { // set default margins $margin = $this->cell_margin; } @@ -6415,7 +6472,7 @@ class TCPDF { $w = $linew; $tmpcellpadding = $this->cell_padding; if ($maxh == 0) { - $this->SetCellPadding(0); + $this->setCellPadding(0); } } if ($firstblock AND $this->isRTLTextDir()) { @@ -6489,7 +6546,7 @@ class TCPDF { // *** very slow *** $l = $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl, $this->isunicode, $this->CurrentFont)); } else { - $l += $this->GetCharWidth($c); + $l += $this->GetCharWidth($c, ($i+1 < $nb)); } if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) >= $wmax))) { if (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) { @@ -6526,7 +6583,7 @@ class TCPDF { $w = $linew; $tmpcellpadding = $this->cell_padding; if ($maxh == 0) { - $this->SetCellPadding(0); + $this->setCellPadding(0); } } if ($firstblock AND $this->isRTLTextDir()) { @@ -6570,7 +6627,7 @@ class TCPDF { $w = $linew; $tmpcellpadding = $this->cell_padding; if ($maxh == 0) { - $this->SetCellPadding(0); + $this->setCellPadding(0); } } if ($firstblock AND $this->isRTLTextDir()) { @@ -6618,7 +6675,7 @@ class TCPDF { $w = $linew; $tmpcellpadding = $this->cell_padding; if ($maxh == 0) { - $this->SetCellPadding(0); + $this->setCellPadding(0); } } // print the line @@ -6671,13 +6728,10 @@ class TCPDF { switch ($align) { case 'J': case 'C': { - $w = $w; break; } case 'L': { - if ($this->rtl) { - $w = $w; - } else { + if (!$this->rtl) { $w = $l; } break; @@ -6685,8 +6739,6 @@ class TCPDF { case 'R': { if ($this->rtl) { $w = $l; - } else { - $w = $w; } break; } @@ -6712,7 +6764,7 @@ class TCPDF { $w = $linew; $tmpcellpadding = $this->cell_padding; if ($maxh == 0) { - $this->SetCellPadding(0); + $this->setCellPadding(0); } } if ($firstblock AND $this->isRTLTextDir()) { @@ -6734,7 +6786,7 @@ class TCPDF { /** * Returns the remaining width between the current position and margins. - * @return int Return the remaining width + * @return float Return the remaining width * @protected */ protected function getRemainingWidth() { @@ -6748,12 +6800,12 @@ class TCPDF { /** * Set the block dimensions accounting for page breaks and page/column fitting - * @param $w (float) width - * @param $h (float) height - * @param $x (float) X coordinate - * @param $y (float) Y coodiante - * @param $fitonpage (boolean) if true the block is resized to not exceed page dimensions. - * @return array($w, $h, $x, $y) + * @param float $w width + * @param float $h height + * @param float $x X coordinate + * @param float $y Y coodiante + * @param boolean $fitonpage if true the block is resized to not exceed page dimensions. + * @return array array($w, $h, $x, $y) * @protected * @since 5.5.009 (2010-07-05) */ @@ -6829,43 +6881,49 @@ class TCPDF { * The format can be specified explicitly or inferred from the file extension.
              * It is possible to put a link on the image.
              * Remark: if an image is used several times, only one copy will be embedded in the file.
              - * @param $file (string) Name of the file containing the image or a '@' character followed by the image data string. To link an image without embedding it on the document, set an asterisk character before the URL (i.e.: '*http://www.example.com/image.jpg'). - * @param $x (float) Abscissa of the upper-left corner (LTR) or upper-right corner (RTL). - * @param $y (float) Ordinate of the upper-left corner (LTR) or upper-right corner (RTL). - * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated. - * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated. - * @param $type (string) Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension. - * @param $link (mixed) URL or identifier returned by AddLink(). - * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:
              • T: top-right for LTR or top-left for RTL
              • M: middle-right for LTR or middle-left for RTL
              • B: bottom-right for LTR or bottom-left for RTL
              • N: next line
              - * @param $resize (mixed) If true resize (reduce) the image to fit $w and $h (requires GD or ImageMagick library); if false do not resize; if 2 force resize in all cases (upscaling and downscaling). - * @param $dpi (int) dot-per-inch resolution used on resize - * @param $palign (string) Allows to center or align the image on the current line. Possible values are:
              • L : left align
              • C : center
              • R : right align
              • '' : empty string : left for LTR or right for RTL
              - * @param $ismask (boolean) true if this image is a mask, false otherwise - * @param $imgmask (mixed) image object returned by this function or false - * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) - * @param $fitbox (mixed) If not false scale image dimensions proportionally to fit within the ($w, $h) box. $fitbox can be true or a 2 characters string indicating the image alignment inside the box. The first character indicate the horizontal alignment (L = left, C = center, R = right) the second character indicate the vertical algnment (T = top, M = middle, B = bottom). - * @param $hidden (boolean) If true do not display the image. - * @param $fitonpage (boolean) If true the image is resized to not exceed page dimensions. - * @param $alt (boolean) If true the image will be added as alternative and not directly printed (the ID of the image will be returned). - * @param $altimgs (array) Array of alternate images IDs. Each alternative image must be an array with two values: an integer representing the image ID (the value returned by the Image method) and a boolean value to indicate if the image is the default for printing. - * @return image information + * @param string $file Name of the file containing the image or a '@' character followed by the image data string. To link an image without embedding it on the document, set an asterisk character before the URL (i.e.: '*http://www.example.com/image.jpg'). + * @param float|null $x Abscissa of the upper-left corner (LTR) or upper-right corner (RTL). + * @param float|null $y Ordinate of the upper-left corner (LTR) or upper-right corner (RTL). + * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated. + * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated. + * @param string $type Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension. + * @param mixed $link URL or identifier returned by AddLink(). + * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:
              • T: top-right for LTR or top-left for RTL
              • M: middle-right for LTR or middle-left for RTL
              • B: bottom-right for LTR or bottom-left for RTL
              • N: next line
              + * @param mixed $resize If true resize (reduce) the image to fit $w and $h (requires GD or ImageMagick library); if false do not resize; if 2 force resize in all cases (upscaling and downscaling). + * @param int $dpi dot-per-inch resolution used on resize + * @param string $palign Allows to center or align the image on the current line. Possible values are:
              • L : left align
              • C : center
              • R : right align
              • '' : empty string : left for LTR or right for RTL
              + * @param boolean $ismask true if this image is a mask, false otherwise + * @param mixed $imgmask image object returned by this function or false + * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) + * @param mixed $fitbox If not false scale image dimensions proportionally to fit within the ($w, $h) box. $fitbox can be true or a 2 characters string indicating the image alignment inside the box. The first character indicate the horizontal alignment (L = left, C = center, R = right) the second character indicate the vertical algnment (T = top, M = middle, B = bottom). + * @param boolean $hidden If true do not display the image. + * @param boolean $fitonpage If true the image is resized to not exceed page dimensions. + * @param boolean $alt If true the image will be added as alternative and not directly printed (the ID of the image will be returned). + * @param array $altimgs Array of alternate images IDs. Each alternative image must be an array with two values: an integer representing the image ID (the value returned by the Image method) and a boolean value to indicate if the image is the default for printing. + * @return mixed|false image information * @public * @since 1.1 */ - public function Image($file, $x='', $y='', $w=0, $h=0, $type='', $link='', $align='', $resize=false, $dpi=300, $palign='', $ismask=false, $imgmask=false, $border=0, $fitbox=false, $hidden=false, $fitonpage=false, $alt=false, $altimgs=array()) { + public function Image($file, $x=null, $y=null, $w=0, $h=0, $type='', $link='', $align='', $resize=false, $dpi=300, $palign='', $ismask=false, $imgmask=false, $border=0, $fitbox=false, $hidden=false, $fitonpage=false, $alt=false, $altimgs=array()) { if ($this->state != 2) { - return; + return false; } - if (strcmp($x, '') === 0) { + if (TCPDF_STATIC::empty_string($x)) { $x = $this->x; } - if (strcmp($y, '') === 0) { + if (TCPDF_STATIC::empty_string($y)) { $y = $this->y; } // check page for no-write regions and adapt page margins if necessary list($x, $y) = $this->checkPageRegions($h, $x, $y); $exurl = ''; // external streams $imsize = FALSE; + + // Make sure the file variable is not empty or null because accessing $file[0] later + // results in error when running PHP 7.4 + if (empty($file)) { + return false; + } // check if we are passing an image as file or string if ($file[0] === '@') { // image from string @@ -6877,18 +6935,14 @@ class TCPDF { $exurl = $file; } // check if file exist and it is valid - if (!@TCPDF_STATIC::file_exists($file)) { + if (!@$this->fileExists($file)) { return false; } - if (($imsize = @getimagesize($file)) === FALSE) { - if (in_array($file, $this->imagekeys)) { - // get existing image data - $info = $this->getImageBuffer($file); - $imsize = array($info['w'], $info['h']); - } elseif (strpos($file, '__tcpdf_'.$this->file_id.'_img') === FALSE) { - $imgdata = TCPDF_STATIC::fileGetContents($file); - } - } + if (false !== $info = $this->getImageBuffer($file)) { + $imsize = array($info['w'], $info['h']); + } elseif (($imsize = @getimagesize($file)) === FALSE && strpos($file, '__tcpdf_'.$this->file_id.'_img') === FALSE){ + $imgdata = $this->getCachedFileContents($file); + } } if (!empty($imgdata)) { // copy image to cache @@ -7093,7 +7147,7 @@ class TCPDF { $svgimg = substr($file, 1); } else { // get SVG file content - $svgimg = TCPDF_STATIC::fileGetContents($file); + $svgimg = $this->getCachedFileContents($file); } if ($svgimg !== FALSE) { // get width and height @@ -7146,7 +7200,7 @@ class TCPDF { } if ($info === false) { // unable to process image - return; + return false; } TCPDF_STATIC::set_mqr($mqr); if ($ismask) { @@ -7221,7 +7275,7 @@ class TCPDF { break; } case 'N': { - $this->SetY($this->img_rb_y); + $this->setY($this->img_rb_y); break; } default:{ @@ -7238,20 +7292,20 @@ class TCPDF { /** * Extract info from a PNG image with alpha channel using the Imagick or GD library. - * @param $file (string) Name of the file containing the image. - * @param $x (float) Abscissa of the upper-left corner. - * @param $y (float) Ordinate of the upper-left corner. - * @param $wpx (float) Original width of the image in pixels. - * @param $hpx (float) original height of the image in pixels. - * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated. - * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated. - * @param $type (string) Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension. - * @param $link (mixed) URL or identifier returned by AddLink(). - * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:
              • T: top-right for LTR or top-left for RTL
              • M: middle-right for LTR or middle-left for RTL
              • B: bottom-right for LTR or bottom-left for RTL
              • N: next line
              - * @param $resize (boolean) If true resize (reduce) the image to fit $w and $h (requires GD library). - * @param $dpi (int) dot-per-inch resolution used on resize - * @param $palign (string) Allows to center or align the image on the current line. Possible values are:
              • L : left align
              • C : center
              • R : right align
              • '' : empty string : left for LTR or right for RTL
              - * @param $filehash (string) File hash used to build unique file names. + * @param string $file Name of the file containing the image. + * @param float $x Abscissa of the upper-left corner. + * @param float $y Ordinate of the upper-left corner. + * @param float $wpx Original width of the image in pixels. + * @param float $hpx original height of the image in pixels. + * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated. + * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated. + * @param string $type Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension. + * @param mixed $link URL or identifier returned by AddLink(). + * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:
              • T: top-right for LTR or top-left for RTL
              • M: middle-right for LTR or middle-left for RTL
              • B: bottom-right for LTR or bottom-left for RTL
              • N: next line
              + * @param boolean $resize If true resize (reduce) the image to fit $w and $h (requires GD library). + * @param int $dpi dot-per-inch resolution used on resize + * @param string $palign Allows to center or align the image on the current line. Possible values are:
              • L : left align
              • C : center
              • R : right align
              • '' : empty string : left for LTR or right for RTL
              + * @param string $filehash File hash used to build unique file names. * @author Nicola Asuni * @protected * @since 4.3.007 (2008-12-04) @@ -7315,7 +7369,7 @@ class TCPDF { $color = imagecolorat($img, $xpx, $ypx); // get and correct gamma color $alpha = $this->getGDgamma($img, $color); - imagesetpixel($imgalpha, $xpx, $ypx, $alpha); + imagesetpixel($imgalpha, (int) $xpx, (int) $ypx, (int) $alpha); } } imagepng($imgalpha, $tempfile_alpha); @@ -7346,8 +7400,8 @@ class TCPDF { /** * Get the GD-corrected PNG gamma value from alpha color - * @param $img (int) GD image Resource ID. - * @param $c (int) alpha color + * @param resource $img GD image Resource ID. + * @param int $c alpha color * @protected * @since 4.3.007 (2008-12-04) */ @@ -7355,9 +7409,9 @@ class TCPDF { if (!isset($this->gdgammacache['#'.$c])) { $colors = imagecolorsforindex($img, $c); // GD alpha is only 7 bit (0 -> 127) - $this->gdgammacache['#'.$c] = (((127 - $colors['alpha']) / 127) * 255); + $this->gdgammacache['#'.$c] = (int) (((127 - $colors['alpha']) / 127) * 255); // correct gamma - $this->gdgammacache['#'.$c] = (pow(($this->gdgammacache['#'.$c] / 255), 2.2) * 255); + $this->gdgammacache['#'.$c] = (int) (pow(($this->gdgammacache['#'.$c] / 255), 2.2) * 255); // store the latest values on cache to improve performances if (count($this->gdgammacache) > 8) { // remove one element from the cache array @@ -7370,13 +7424,13 @@ class TCPDF { /** * Performs a line break. * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter. - * @param $h (float) The height of the break. By default, the value equals the height of the last printed cell. - * @param $cell (boolean) if true add the current left (or right o for RTL) padding to the X coordinate + * @param float|null $h The height of the break. By default, the value equals the height of the last printed cell. + * @param boolean $cell if true add the current left (or right o for RTL) padding to the X coordinate * @public * @since 1.0 * @see Cell() */ - public function Ln($h='', $cell=false) { + public function Ln($h=null, $cell=false) { if (($this->num_columns > 1) AND ($this->y == $this->columns[$this->current_column]['y']) AND isset($this->columns[$this->current_column]['x']) AND ($this->x == $this->columns[$this->current_column]['x'])) { // revove vertical space from the top of the column return; @@ -7395,7 +7449,7 @@ class TCPDF { } else { $this->x = $this->lMargin + $cellpadding; } - if (is_string($h)) { + if (TCPDF_STATIC::empty_string($h)) { $h = $this->lasth; } $this->y += $h; @@ -7444,13 +7498,13 @@ class TCPDF { /** * Defines the abscissa of the current position. * If the passed value is negative, it is relative to the right of the page (or left if language is RTL). - * @param $x (float) The value of the abscissa in user units. - * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis. + * @param float $x The value of the abscissa in user units. + * @param boolean $rtloff if true always uses the page top-left corner as origin of axis. * @public * @since 1.2 * @see GetX(), GetY(), SetY(), SetXY() */ - public function SetX($x, $rtloff=false) { + public function setX($x, $rtloff=false) { $x = floatval($x); if (!$rtloff AND $this->rtl) { if ($x >= 0) { @@ -7476,14 +7530,14 @@ class TCPDF { /** * Moves the current abscissa back to the left margin and sets the ordinate. * If the passed value is negative, it is relative to the bottom of the page. - * @param $y (float) The value of the ordinate in user units. - * @param $resetx (bool) if true (default) reset the X position. - * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis. + * @param float $y The value of the ordinate in user units. + * @param bool $resetx if true (default) reset the X position. + * @param boolean $rtloff if true always uses the page top-left corner as origin of axis. * @public * @since 1.0 * @see GetX(), GetY(), SetY(), SetXY() */ - public function SetY($y, $resetx=true, $rtloff=false) { + public function setY($y, $resetx=true, $rtloff=false) { $y = floatval($y); if ($resetx) { //reset x @@ -7509,59 +7563,59 @@ class TCPDF { /** * Defines the abscissa and ordinate of the current position. * If the passed values are negative, they are relative respectively to the right and bottom of the page. - * @param $x (float) The value of the abscissa. - * @param $y (float) The value of the ordinate. - * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis. + * @param float $x The value of the abscissa. + * @param float $y The value of the ordinate. + * @param boolean $rtloff if true always uses the page top-left corner as origin of axis. * @public * @since 1.2 * @see SetX(), SetY() */ - public function SetXY($x, $y, $rtloff=false) { - $this->SetY($y, false, $rtloff); - $this->SetX($x, $rtloff); + public function setXY($x, $y, $rtloff=false) { + $this->setY($y, false, $rtloff); + $this->setX($x, $rtloff); } /** * Set the absolute X coordinate of the current pointer. - * @param $x (float) The value of the abscissa in user units. + * @param float $x The value of the abscissa in user units. * @public * @since 5.9.186 (2012-09-13) * @see setAbsX(), setAbsY(), SetAbsXY() */ - public function SetAbsX($x) { + public function setAbsX($x) { $this->x = floatval($x); } /** * Set the absolute Y coordinate of the current pointer. - * @param $y (float) (float) The value of the ordinate in user units. + * @param float $y (float) The value of the ordinate in user units. * @public * @since 5.9.186 (2012-09-13) * @see setAbsX(), setAbsY(), SetAbsXY() */ - public function SetAbsY($y) { + public function setAbsY($y) { $this->y = floatval($y); } /** * Set the absolute X and Y coordinates of the current pointer. - * @param $x (float) The value of the abscissa in user units. - * @param $y (float) (float) The value of the ordinate in user units. + * @param float $x The value of the abscissa in user units. + * @param float $y (float) The value of the ordinate in user units. * @public * @since 5.9.186 (2012-09-13) * @see setAbsX(), setAbsY(), SetAbsXY() */ - public function SetAbsXY($x, $y) { - $this->SetAbsX($x); - $this->SetAbsY($y); + public function setAbsXY($x, $y) { + $this->setAbsX($x); + $this->setAbsY($y); } /** * Send the document to a given destination: string, local file or browser. * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.
              * The method first calls Close() if necessary to terminate the document. - * @param $name (string) The name of the file when saved. Note that special characters are removed and blanks characters are replaced with the underscore character. - * @param $dest (string) Destination where to send the document. It can take one of the following values:
              • I: send the file inline to the browser (default). The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.
              • D: send to the browser and force a file download with the name given by name.
              • F: save to a local server file with the name given by name.
              • S: return the document as a string (name is ignored).
              • FI: equivalent to F + I option
              • FD: equivalent to F + D option
              • E: return the document as base64 mime multi-part email attachment (RFC 2045)
              + * @param string $name The name of the file when saved. Note that special characters are removed and blanks characters are replaced with the underscore character. + * @param string $dest Destination where to send the document. It can take one of the following values:
              • I: send the file inline to the browser (default). The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.
              • D: send to the browser and force a file download with the name given by name.
              • F: save to a local server file with the name given by name.
              • S: return the document as a string (name is ignored).
              • FI: equivalent to F + I option
              • FD: equivalent to F + D option
              • E: return the document as base64 mime multi-part email attachment (RFC 2045)
              * @return string * @public * @since 1.0 @@ -7761,20 +7815,16 @@ class TCPDF { protected static $cleaned_ids = array(); /** * Unset all class variables except the following critical variables. - * @param $destroyall (boolean) if true destroys all class variables, otherwise preserves critical variables. - * @param $preserve_objcopy (boolean) if true preserves the objcopy variable + * @param boolean $destroyall if true destroys all class variables, otherwise preserves critical variables. + * @param boolean $preserve_objcopy if true preserves the objcopy variable * @public * @since 4.5.016 (2009-02-24) */ public function _destroy($destroyall=false, $preserve_objcopy=false) { - // restore internal encoding - if (isset($this->internal_encoding) AND !empty($this->internal_encoding)) { - mb_internal_encoding($this->internal_encoding); - } if (isset(self::$cleaned_ids[$this->file_id])) { $destroyall = false; } - if ($destroyall AND !$preserve_objcopy) { + if ($destroyall AND !$preserve_objcopy && isset($this->file_id)) { self::$cleaned_ids[$this->file_id] = true; // remove all temporary files if ($handle = @opendir(K_PATH_CACHE)) { @@ -7787,7 +7837,7 @@ class TCPDF { } if (isset($this->imagekeys)) { foreach($this->imagekeys as $file) { - if (strpos($file, K_PATH_CACHE) === 0) { + if (strpos($file, K_PATH_CACHE) === 0 && TCPDF_STATIC::file_exists($file)) { @unlink($file); } } @@ -7795,7 +7845,6 @@ class TCPDF { } $preserve = array( 'file_id', - 'internal_encoding', 'state', 'bufferlen', 'buffer', @@ -7834,7 +7883,7 @@ class TCPDF { /** * Return an array containing variations for the basic page number alias. - * @param $a (string) Base alias. + * @param string $a Base alias. * @return array of page number aliases * @protected */ @@ -7871,10 +7920,10 @@ class TCPDF { /** * Replace right shift page number aliases with spaces to correct right alignment. * This works perfectly only when using monospaced fonts. - * @param $page (string) Page content. - * @param $aliases (array) Array of page aliases. - * @param $diff (int) initial difference to add. - * @return replaced page content. + * @param string $page Page content. + * @param array $aliases Array of page aliases. + * @param int $diff initial difference to add. + * @return string replaced page content. * @protected */ protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) { @@ -7909,7 +7958,7 @@ class TCPDF { /** * Set page boxes to be included on page descriptions. - * @param $boxes (array) Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox'). + * @param array $boxes Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox'). * @protected */ protected function setPageBoxTypes($boxes) { @@ -8024,7 +8073,7 @@ class TCPDF { } $out .= ' /Contents '.($this->n + 1).' 0 R'; $out .= ' /Rotate '.$this->pagedim[$n]['Rotate']; - if (!$this->pdfa_mode) { + if (!$this->pdfa_mode || $this->pdfa_version >= 2) { $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>'; } if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) { @@ -8081,7 +8130,7 @@ class TCPDF { /** * Get references to page annotations. - * @param $n (int) page number + * @param int $n page number * @return string * @protected * @author Nicola Asuni @@ -8379,8 +8428,13 @@ class TCPDF { } else { $annots .= ' /Name /Note'; } + $hasStateModel = isset($pl['opt']['statemodel']); + $hasState = isset($pl['opt']['state']); $statemodels = array('Marked', 'Review'); - if (isset($pl['opt']['statemodel']) AND in_array($pl['opt']['statemodel'], $statemodels)) { + if (!$hasStateModel && !$hasState) { + break; + } + if ($hasStateModel AND in_array($pl['opt']['statemodel'], $statemodels)) { $annots .= ' /StateModel /'.$pl['opt']['statemodel']; } else { $pl['opt']['statemodel'] = 'Marked'; @@ -8391,7 +8445,7 @@ class TCPDF { } else { $states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None'); } - if (isset($pl['opt']['state']) AND in_array($pl['opt']['state'], $states)) { + if ($hasState AND in_array($pl['opt']['state'], $states)) { $annots .= ' /State /'.$pl['opt']['state']; } else { if ($pl['opt']['statemodel'] == 'Marked') { @@ -8406,7 +8460,7 @@ class TCPDF { if (is_string($pl['txt']) && !empty($pl['txt'])) { if ($pl['txt'][0] == '#') { // internal destination - $annots .= ' /A <>'; + $annots .= ' /A <>'; } elseif ($pl['txt'][0] == '%') { // embedded PDF file $filename = basename(substr($pl['txt'], 1)); @@ -8769,9 +8823,9 @@ class TCPDF { /** * Put appearance streams XObject used to define annotation's appearance states. - * @param $w (int) annotation width - * @param $h (int) annotation height - * @param $stream (string) appearance stream + * @param int $w annotation width + * @param int $h annotation height + * @param string $stream appearance stream * @return int object ID * @protected * @since 4.8.001 (2009-09-09) @@ -8953,7 +9007,7 @@ class TCPDF { /** * Adds unicode fonts.
              * Based on PDF Reference 1.3 (section 5) - * @param $font (array) font data + * @param array $font font data * @protected * @author Nicola Asuni * @since 1.52.0.TC005 (2005-01-05) @@ -9057,7 +9111,7 @@ class TCPDF { /** * Output CID-0 fonts. * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format - * @param $font (array) font data + * @param array $font font data * @protected * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira * @since 3.2.000 (2008-06-23) @@ -9278,7 +9332,7 @@ class TCPDF { $out .= ' /Matrix [1 0 0 1 0 0]'; $out .= ' /Resources <<'; $out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]'; - if (!$this->pdfa_mode) { + if (!$this->pdfa_mode || $this->pdfa_version >= 2) { // transparency if (isset($data['extgstates']) AND !empty($data['extgstates'])) { $out .= ' /ExtGState <<'; @@ -9416,7 +9470,7 @@ class TCPDF { } $out .= ' >>'; } - if (!$this->pdfa_mode) { + if (!$this->pdfa_mode || $this->pdfa_version >= 2) { // transparency if (isset($this->extgstates) AND !empty($this->extgstates)) { $out .= ' /ExtGState <<'; @@ -9530,7 +9584,7 @@ class TCPDF { /** * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag. * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method! - * @param $xmp (string) Custom XMP data. + * @param string $xmp Custom XMP data. * @since 5.9.128 (2011-10-06) * @public */ @@ -9541,7 +9595,7 @@ class TCPDF { /** * Set additional XMP data to be added on the default XMP data just before the end of "rdf:RDF" tag. * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method! - * @param $xmp (string) Custom XMP RDF data. + * @param string $xmp Custom XMP RDF data. * @since 6.3.0 (2019-09-19) * @public */ @@ -9551,7 +9605,7 @@ class TCPDF { /** * Put XMP data object and return ID. - * @return (int) The object ID. + * @return int The object ID. * @since 5.9.121 (2011-09-28) * @protected */ @@ -10058,8 +10112,8 @@ class TCPDF { /** * Initialize a new page. - * @param $orientation (string) page orientation. Possible values are (case insensitive):
              • P or PORTRAIT (default)
              • L or LANDSCAPE
              - * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat(). + * @param string $orientation page orientation. Possible values are (case insensitive):
              • P or PORTRAIT (default)
              • L or LANDSCAPE
              + * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat(). * @protected * @see getPageSizeFromFormat(), setPageFormat() */ @@ -10123,13 +10177,13 @@ class TCPDF { /** * Return the starting object string for the selected object ID. - * @param $objid (int) Object ID (leave empty to get a new ID). + * @param int|null $objid Object ID (leave empty to get a new ID). * @return string the starting object string * @protected * @since 5.8.009 (2010-08-20) */ - protected function _getobj($objid='') { - if ($objid === '') { + protected function _getobj($objid=null) { + if (TCPDF_STATIC::empty_string($objid)) { ++$this->n; $objid = $this->n; } @@ -10140,9 +10194,9 @@ class TCPDF { /** * Underline text. - * @param $x (int) X coordinate - * @param $y (int) Y coordinate - * @param $txt (string) text to underline + * @param int $x X coordinate + * @param int $y Y coordinate + * @param string $txt text to underline * @protected */ protected function _dounderline($x, $y, $txt) { @@ -10152,9 +10206,9 @@ class TCPDF { /** * Underline for rectangular text area. - * @param $x (int) X coordinate - * @param $y (int) Y coordinate - * @param $w (int) width to underline + * @param int $x X coordinate + * @param int $y Y coordinate + * @param int $w width to underline * @protected * @since 4.8.008 (2009-09-29) */ @@ -10165,9 +10219,9 @@ class TCPDF { /** * Line through text. - * @param $x (int) X coordinate - * @param $y (int) Y coordinate - * @param $txt (string) text to linethrough + * @param int $x X coordinate + * @param int $y Y coordinate + * @param string $txt text to linethrough * @protected */ protected function _dolinethrough($x, $y, $txt) { @@ -10177,9 +10231,9 @@ class TCPDF { /** * Line through for rectangular text area. - * @param $x (int) X coordinate - * @param $y (int) Y coordinate - * @param $w (int) line length (width) + * @param int $x X coordinate + * @param int $y Y coordinate + * @param int $w line length (width) * @protected * @since 4.9.008 (2009-09-29) */ @@ -10190,9 +10244,9 @@ class TCPDF { /** * Overline text. - * @param $x (int) X coordinate - * @param $y (int) Y coordinate - * @param $txt (string) text to overline + * @param int $x X coordinate + * @param int $y Y coordinate + * @param string $txt text to overline * @protected * @since 4.9.015 (2010-04-19) */ @@ -10203,9 +10257,9 @@ class TCPDF { /** * Overline for rectangular text area. - * @param $x (int) X coordinate - * @param $y (int) Y coordinate - * @param $w (int) width to overline + * @param int $x X coordinate + * @param int $y Y coordinate + * @param int $w width to overline * @protected * @since 4.9.015 (2010-04-19) */ @@ -10217,8 +10271,8 @@ class TCPDF { /** * Format a data string for meta information - * @param $s (string) data string to escape. - * @param $n (int) object ID + * @param string $s data string to escape. + * @param int $n object ID * @return string escaped string. * @protected */ @@ -10232,7 +10286,7 @@ class TCPDF { /** * Set the document creation timestamp - * @param $time (mixed) Document creation timestamp in seconds or date-time string. + * @param mixed $time Document creation timestamp in seconds or date-time string. * @public * @since 5.9.152 (2012-03-23) */ @@ -10245,7 +10299,7 @@ class TCPDF { /** * Set the document modification timestamp - * @param $time (mixed) Document modification timestamp in seconds or date-time string. + * @param mixed $time Document modification timestamp in seconds or date-time string. * @public * @since 5.9.152 (2012-03-23) */ @@ -10258,7 +10312,7 @@ class TCPDF { /** * Returns document creation timestamp in seconds. - * @return (int) Creation timestamp in seconds. + * @return int Creation timestamp in seconds. * @public * @since 5.9.152 (2012-03-23) */ @@ -10268,7 +10322,7 @@ class TCPDF { /** * Returns document modification timestamp in seconds. - * @return (int) Modfication timestamp in seconds. + * @return int Modfication timestamp in seconds. * @public * @since 5.9.152 (2012-03-23) */ @@ -10278,8 +10332,8 @@ class TCPDF { /** * Returns a formatted date for meta information - * @param $n (int) Object ID. - * @param $timestamp (int) Timestamp to convert. + * @param int $n Object ID. + * @param int $timestamp Timestamp to convert. * @return string escaped date string. * @protected * @since 4.6.028 (2009-08-25) @@ -10293,8 +10347,8 @@ class TCPDF { /** * Format a text string for meta information - * @param $s (string) string to escape. - * @param $n (int) object ID + * @param string $s string to escape. + * @param int $n object ID * @return string escaped string. * @protected */ @@ -10308,8 +10362,8 @@ class TCPDF { /** * get raw output stream. - * @param $s (string) string to output. - * @param $n (int) object reference for encryption mode + * @param string $s string to output. + * @param int $n object reference for encryption mode * @protected * @author Nicola Asuni * @since 5.5.000 (2010-06-22) @@ -10324,7 +10378,7 @@ class TCPDF { /** * Output a string to the document. - * @param $s (string) string to output. + * @param string $s string to output. * @protected */ protected function _out($s) { @@ -10352,7 +10406,8 @@ class TCPDF { /** * Set header font. - * @param $font (array) Array describing the basic font parameters: (family, style, size). + * @param array $font Array describing the basic font parameters: (family, style, size). + * @phpstan-param array{0: string, 1: string, 2: float|null} $font * @public * @since 1.1 */ @@ -10362,7 +10417,8 @@ class TCPDF { /** * Get header font. - * @return array() Array describing the basic font parameters: (family, style, size). + * @return array Array describing the basic font parameters: (family, style, size). + * @phpstan-return array{0: string, 1: string, 2: float|null} * @public * @since 4.0.012 (2008-07-24) */ @@ -10372,7 +10428,8 @@ class TCPDF { /** * Set footer font. - * @param $font (array) Array describing the basic font parameters: (family, style, size). + * @param array $font Array describing the basic font parameters: (family, style, size). + * @phpstan-param array{0: string, 1: string, 2: float|null} $font * @public * @since 1.1 */ @@ -10382,7 +10439,8 @@ class TCPDF { /** * Get Footer font. - * @return array() Array describing the basic font parameters: (family, style, size). + * @return array Array describing the basic font parameters: (family, style, size). + * @phpstan-return array{0: string, 1: string, 2: float|null} $font * @public * @since 4.0.012 (2008-07-24) */ @@ -10392,7 +10450,7 @@ class TCPDF { /** * Set language array. - * @param $language (array) + * @param array $language * @public * @since 1.1 */ @@ -10418,17 +10476,17 @@ class TCPDF { /** * Output anchor link. - * @param $url (string) link URL or internal link (i.e.: <a href="#23,4.5">link to page 23 at 4.5 Y position</a>) - * @param $name (string) link name - * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false). - * @param $firstline (boolean) if true prints only the first line and return the remaining string. - * @param $color (array) array of RGB text color - * @param $style (string) font style (U, D, B, I) - * @param $firstblock (boolean) if true the string is the starting of a line. - * @return the number of cells used or the remaining text if $firstline = true; + * @param string $url link URL or internal link (i.e.: <a href="#23,4.5">link to page 23 at 4.5 Y position</a>) + * @param string $name link name + * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false). + * @param boolean $firstline if true prints only the first line and return the remaining string. + * @param array|null $color array of RGB text color + * @param string $style font style (U, D, B, I) + * @param boolean $firstblock if true the string is the starting of a line. + * @return int the number of cells used or the remaining text if $firstline = true; * @public */ - public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color='', $style=-1, $firstblock=false) { + public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color=null, $style=-1, $firstblock=false) { if (isset($url[1]) AND ($url[0] == '#') AND is_numeric($url[1])) { // convert url to internal link $lnkdata = explode(',', $url); @@ -10440,32 +10498,32 @@ class TCPDF { $lnky = 0; } $url = $this->AddLink(); - $this->SetLink($url, $lnky, $page); + $this->setLink($url, $lnky, $page); } } // store current settings $prevcolor = $this->fgcolor; $prevstyle = $this->FontStyle; if (empty($color)) { - $this->SetTextColorArray($this->htmlLinkColorArray); + $this->setTextColorArray($this->htmlLinkColorArray); } else { - $this->SetTextColorArray($color); + $this->setTextColorArray($color); } if ($style == -1) { - $this->SetFont('', $this->FontStyle.$this->htmlLinkFontStyle); + $this->setFont('', $this->FontStyle.$this->htmlLinkFontStyle); } else { - $this->SetFont('', $this->FontStyle.$style); + $this->setFont('', $this->FontStyle.$style); } $ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0); // restore settings - $this->SetFont('', $prevstyle); - $this->SetTextColorArray($prevcolor); + $this->setFont('', $prevstyle); + $this->setTextColorArray($prevcolor); return $ret; } /** * Converts pixels to User's Units. - * @param $px (int) pixels + * @param int $px pixels * @return float value in user's unit * @public * @see setImageScale(), getImageScale() @@ -10477,7 +10535,7 @@ class TCPDF { /** * Reverse function for htmlentities. * Convert entities in UTF-8. - * @param $text_to_convert (string) Text to convert. + * @param string $text_to_convert Text to convert. * @return string converted text string * @public */ @@ -10490,7 +10548,7 @@ class TCPDF { /** * Compute encryption key depending on object number where the encrypted data is stored. * This is used for all strings and streams without crypt filter specifier. - * @param $n (int) object number + * @param int $n object number * @return int object key * @protected * @author Nicola Asuni @@ -10509,9 +10567,9 @@ class TCPDF { /** * Encrypt the input string. - * @param $n (int) object number - * @param $s (string) data string to encrypt - * @return encrypted string + * @param int $n object number + * @param string $s data string to encrypt + * @return string encrypted string * @protected * @author Nicola Asuni * @since 5.0.005 (2010-05-11) @@ -10754,7 +10812,7 @@ class TCPDF { /** * Convert password for AES-256 encryption mode - * @param $password (string) password + * @param string $password password * @return string password * @protected * @since 5.9.006 (2010-10-19) @@ -10884,16 +10942,16 @@ class TCPDF { * Remark: the protection against modification is for people who have the full Acrobat product. * If you don't set any password, the document will open as usual. If you set a user password, the PDF viewer will ask for it before displaying the document. The master password, if different from the user one, can be used to get full access. * Note: protecting a document requires to encrypt it, which increases the processing time a lot. This can cause a PHP time-out in some cases, especially if the document contains images or fonts. - * @param $permissions (Array) the set of permissions (specify the ones you want to block):
              • print : Print the document;
              • modify : Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';
              • copy : Copy or otherwise extract text and graphics from the document;
              • annot-forms : Add or modify text annotations, fill in interactive form fields, and, if 'modify' is also set, create or modify interactive form fields (including signature fields);
              • fill-forms : Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified;
              • extract : Extract text and graphics (in support of accessibility to users with disabilities or for other purposes);
              • assemble : Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set;
              • print-high : Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality.
              • owner : (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions.
              - * @param $user_pass (String) user password. Empty by default. - * @param $owner_pass (String) owner password. If not specified, a random value is used. - * @param $mode (int) encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit. - * @param $pubkeys (String) array of recipients containing public-key certificates ('c') and permissions ('p'). For example: array(array('c' => 'file://../examples/data/cert/tcpdf.crt', 'p' => array('print'))) + * @param array $permissions the set of permissions (specify the ones you want to block):
              • print : Print the document;
              • modify : Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';
              • copy : Copy or otherwise extract text and graphics from the document;
              • annot-forms : Add or modify text annotations, fill in interactive form fields, and, if 'modify' is also set, create or modify interactive form fields (including signature fields);
              • fill-forms : Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified;
              • extract : Extract text and graphics (in support of accessibility to users with disabilities or for other purposes);
              • assemble : Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set;
              • print-high : Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality.
              • owner : (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions.
              + * @param string $user_pass user password. Empty by default. + * @param string|null $owner_pass owner password. If not specified, a random value is used. + * @param int $mode encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit. + * @param array|null $pubkeys array of recipients containing public-key certificates ('c') and permissions ('p'). For example: array(array('c' => 'file://../examples/data/cert/tcpdf.crt', 'p' => array('print'))) * @public * @since 2.0.000 (2008-01-02) * @author Nicola Asuni */ - public function SetProtection($permissions=array('print', 'modify', 'copy', 'annot-forms', 'fill-forms', 'extract', 'assemble', 'print-high'), $user_pass='', $owner_pass=null, $mode=0, $pubkeys=null) { + public function setProtection($permissions=array('print', 'modify', 'copy', 'annot-forms', 'fill-forms', 'extract', 'assemble', 'print-high'), $user_pass='', $owner_pass=null, $mode=0, $pubkeys=null) { if ($this->pdfa_mode) { // encryption is not allowed in PDF/A mode return; @@ -11040,9 +11098,9 @@ class TCPDF { } /** * Horizontal Scaling. - * @param $s_x (float) scaling factor for width as percent. 0 is not allowed. - * @param $x (int) abscissa of the scaling center. Default is current x position - * @param $y (int) ordinate of the scaling center. Default is current y position + * @param float $s_x scaling factor for width as percent. 0 is not allowed. + * @param int $x abscissa of the scaling center. Default is current x position + * @param int $y ordinate of the scaling center. Default is current y position * @public * @since 2.1.000 (2008-01-07) * @see StartTransform(), StopTransform() @@ -11053,9 +11111,9 @@ class TCPDF { /** * Vertical Scaling. - * @param $s_y (float) scaling factor for height as percent. 0 is not allowed. - * @param $x (int) abscissa of the scaling center. Default is current x position - * @param $y (int) ordinate of the scaling center. Default is current y position + * @param float $s_y scaling factor for height as percent. 0 is not allowed. + * @param int $x abscissa of the scaling center. Default is current x position + * @param int $y ordinate of the scaling center. Default is current y position * @public * @since 2.1.000 (2008-01-07) * @see StartTransform(), StopTransform() @@ -11066,9 +11124,9 @@ class TCPDF { /** * Vertical and horizontal proportional Scaling. - * @param $s (float) scaling factor for width and height as percent. 0 is not allowed. - * @param $x (int) abscissa of the scaling center. Default is current x position - * @param $y (int) ordinate of the scaling center. Default is current y position + * @param float $s scaling factor for width and height as percent. 0 is not allowed. + * @param int $x abscissa of the scaling center. Default is current x position + * @param int $y ordinate of the scaling center. Default is current y position * @public * @since 2.1.000 (2008-01-07) * @see StartTransform(), StopTransform() @@ -11079,19 +11137,19 @@ class TCPDF { /** * Vertical and horizontal non-proportional Scaling. - * @param $s_x (float) scaling factor for width as percent. 0 is not allowed. - * @param $s_y (float) scaling factor for height as percent. 0 is not allowed. - * @param $x (int) abscissa of the scaling center. Default is current x position - * @param $y (int) ordinate of the scaling center. Default is current y position + * @param float $s_x scaling factor for width as percent. 0 is not allowed. + * @param float $s_y scaling factor for height as percent. 0 is not allowed. + * @param float|null $x abscissa of the scaling center. Default is current x position + * @param float|null $y ordinate of the scaling center. Default is current y position * @public * @since 2.1.000 (2008-01-07) * @see StartTransform(), StopTransform() */ - public function Scale($s_x, $s_y, $x='', $y='') { - if ($x === '') { + public function Scale($s_x, $s_y, $x=null, $y=null) { + if (TCPDF_STATIC::empty_string($x)) { $x = $this->x; } - if ($y === '') { + if (TCPDF_STATIC::empty_string($y)) { $y = $this->y; } if (($s_x == 0) OR ($s_y == 0)) { @@ -11115,55 +11173,55 @@ class TCPDF { /** * Horizontal Mirroring. - * @param $x (int) abscissa of the point. Default is current x position + * @param float|null $x abscissa of the point. Default is current x position * @public * @since 2.1.000 (2008-01-07) * @see StartTransform(), StopTransform() */ - public function MirrorH($x='') { + public function MirrorH($x=null) { $this->Scale(-100, 100, $x); } /** * Verical Mirroring. - * @param $y (int) ordinate of the point. Default is current y position + * @param float|null $y ordinate of the point. Default is current y position * @public * @since 2.1.000 (2008-01-07) * @see StartTransform(), StopTransform() */ - public function MirrorV($y='') { - $this->Scale(100, -100, '', $y); + public function MirrorV($y=null) { + $this->Scale(100, -100, null, $y); } /** * Point reflection mirroring. - * @param $x (int) abscissa of the point. Default is current x position - * @param $y (int) ordinate of the point. Default is current y position + * @param float|null $x abscissa of the point. Default is current x position + * @param float|null $y ordinate of the point. Default is current y position * @public * @since 2.1.000 (2008-01-07) * @see StartTransform(), StopTransform() */ - public function MirrorP($x='',$y='') { + public function MirrorP($x=null,$y=null) { $this->Scale(-100, -100, $x, $y); } /** * Reflection against a straight line through point (x, y) with the gradient angle (angle). - * @param $angle (float) gradient angle of the straight line. Default is 0 (horizontal line). - * @param $x (int) abscissa of the point. Default is current x position - * @param $y (int) ordinate of the point. Default is current y position + * @param float $angle gradient angle of the straight line. Default is 0 (horizontal line). + * @param float|null $x abscissa of the point. Default is current x position + * @param float|null $y ordinate of the point. Default is current y position * @public * @since 2.1.000 (2008-01-07) * @see StartTransform(), StopTransform() */ - public function MirrorL($angle=0, $x='',$y='') { + public function MirrorL($angle=0, $x=null,$y=null) { $this->Scale(-100, 100, $x, $y); $this->Rotate(-2*($angle-90), $x, $y); } /** * Translate graphic object horizontally. - * @param $t_x (int) movement to the right (or left for RTL) + * @param int $t_x movement to the right (or left for RTL) * @public * @since 2.1.000 (2008-01-07) * @see StartTransform(), StopTransform() @@ -11174,7 +11232,7 @@ class TCPDF { /** * Translate graphic object vertically. - * @param $t_y (int) movement to the bottom + * @param int $t_y movement to the bottom * @public * @since 2.1.000 (2008-01-07) * @see StartTransform(), StopTransform() @@ -11185,8 +11243,8 @@ class TCPDF { /** * Translate graphic object horizontally and vertically. - * @param $t_x (int) movement to the right - * @param $t_y (int) movement to the bottom + * @param int $t_x movement to the right + * @param int $t_y movement to the bottom * @public * @since 2.1.000 (2008-01-07) * @see StartTransform(), StopTransform() @@ -11206,18 +11264,18 @@ class TCPDF { /** * Rotate object. - * @param $angle (float) angle in degrees for counter-clockwise rotation - * @param $x (int) abscissa of the rotation center. Default is current x position - * @param $y (int) ordinate of the rotation center. Default is current y position + * @param float $angle angle in degrees for counter-clockwise rotation + * @param float|null $x abscissa of the rotation center. Default is current x position + * @param float|null $y ordinate of the rotation center. Default is current y position * @public * @since 2.1.000 (2008-01-07) * @see StartTransform(), StopTransform() */ - public function Rotate($angle, $x='', $y='') { - if ($x === '') { + public function Rotate($angle, $x=null, $y=null) { + if (TCPDF_STATIC::empty_string($x)) { $x = $this->x; } - if ($y === '') { + if (TCPDF_STATIC::empty_string($y)) { $y = $this->y; } $y = ($this->h - $y) * $this->k; @@ -11236,45 +11294,45 @@ class TCPDF { /** * Skew horizontally. - * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right) - * @param $x (int) abscissa of the skewing center. default is current x position - * @param $y (int) ordinate of the skewing center. default is current y position + * @param float $angle_x angle in degrees between -90 (skew to the left) and 90 (skew to the right) + * @param float|null $x abscissa of the skewing center. default is current x position + * @param float|null $y ordinate of the skewing center. default is current y position * @public * @since 2.1.000 (2008-01-07) * @see StartTransform(), StopTransform() */ - public function SkewX($angle_x, $x='', $y='') { + public function SkewX($angle_x, $x=null, $y=null) { $this->Skew($angle_x, 0, $x, $y); } /** * Skew vertically. - * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top) - * @param $x (int) abscissa of the skewing center. default is current x position - * @param $y (int) ordinate of the skewing center. default is current y position + * @param float $angle_y angle in degrees between -90 (skew to the bottom) and 90 (skew to the top) + * @param float|null $x abscissa of the skewing center. default is current x position + * @param float|null $y ordinate of the skewing center. default is current y position * @public * @since 2.1.000 (2008-01-07) * @see StartTransform(), StopTransform() */ - public function SkewY($angle_y, $x='', $y='') { + public function SkewY($angle_y, $x=null, $y=null) { $this->Skew(0, $angle_y, $x, $y); } /** * Skew. - * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right) - * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top) - * @param $x (int) abscissa of the skewing center. default is current x position - * @param $y (int) ordinate of the skewing center. default is current y position + * @param float $angle_x angle in degrees between -90 (skew to the left) and 90 (skew to the right) + * @param float $angle_y angle in degrees between -90 (skew to the bottom) and 90 (skew to the top) + * @param float|null $x abscissa of the skewing center. default is current x position + * @param float|null $y ordinate of the skewing center. default is current y position * @public * @since 2.1.000 (2008-01-07) * @see StartTransform(), StopTransform() */ - public function Skew($angle_x, $angle_y, $x='', $y='') { - if ($x === '') { + public function Skew($angle_x, $angle_y, $x=null, $y=null) { + if (TCPDF_STATIC::empty_string($x)) { $x = $this->x; } - if ($y === '') { + if (TCPDF_STATIC::empty_string($y)) { $y = $this->y; } if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) { @@ -11296,7 +11354,7 @@ class TCPDF { /** * Apply graphic transformations. - * @param $tm (array) transformation matrix + * @param array $tm transformation matrix * @protected * @since 2.1.000 (2008-01-07) * @see StartTransform(), StopTransform() @@ -11328,12 +11386,12 @@ class TCPDF { /** * Defines the line width. By default, the value equals 0.2 mm. The method can be called before the first page is created and the value is retained from page to page. - * @param $width (float) The width. + * @param float $width The width. * @public * @since 1.0 * @see Line(), Rect(), Cell(), MultiCell() */ - public function SetLineWidth($width) { + public function setLineWidth($width) { //Set line width $this->LineWidth = $width; $this->linestyleWidth = sprintf('%F w', ($width * $this->k)); @@ -11355,7 +11413,7 @@ class TCPDF { /** * Set line style. - * @param $style (array) Line style. Array with keys among the following: + * @param array $style Line style. Array with keys among the following: *
                *
              • width (float): Width of the line in user units.
              • *
              • cap (string): Type of cap to put on the line. Possible values are: @@ -11371,15 +11429,15 @@ class TCPDF { * the point at which the pattern starts.
              • *
              • color (array): Draw color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName).
              • *
              - * @param $ret (boolean) if true do not send the command. + * @param boolean $ret if true do not send the command. * @return string the PDF command * @public * @since 2.1.000 (2008-01-08) */ - public function SetLineStyle($style, $ret=false) { + public function setLineStyle($style, $ret=false) { $s = ''; // string to be returned if (!is_array($style)) { - return; + return $s; } if (isset($style['width'])) { $this->LineWidth = $style['width']; @@ -11423,7 +11481,7 @@ class TCPDF { $s .= $this->linestyleDash.' '; } if (isset($style['color'])) { - $s .= $this->SetDrawColorArray($style['color'], true).' '; + $s .= $this->setDrawColorArray($style['color'], true).' '; } if (!$ret AND ($this->state == 2)) { $this->_out($s); @@ -11433,8 +11491,8 @@ class TCPDF { /** * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment. - * @param $x (float) Abscissa of point. - * @param $y (float) Ordinate of point. + * @param float $x Abscissa of point. + * @param float $y Ordinate of point. * @protected * @since 2.1.000 (2008-01-08) */ @@ -11447,8 +11505,8 @@ class TCPDF { /** * Append a straight line segment from the current point to the point (x, y). * The new current point shall be (x, y). - * @param $x (float) Abscissa of end point. - * @param $y (float) Ordinate of end point. + * @param float $x Abscissa of end point. + * @param float $y Ordinate of end point. * @protected * @since 2.1.000 (2008-01-08) */ @@ -11460,11 +11518,11 @@ class TCPDF { /** * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space. - * @param $x (float) Abscissa of upper-left corner. - * @param $y (float) Ordinate of upper-left corner. - * @param $w (float) Width. - * @param $h (float) Height. - * @param $op (string) options + * @param float $x Abscissa of upper-left corner. + * @param float $y Ordinate of upper-left corner. + * @param float $w Width. + * @param float $h Height. + * @param string $op options * @protected * @since 2.1.000 (2008-01-08) */ @@ -11477,12 +11535,12 @@ class TCPDF { /** * Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x2, y2) as the Bezier control points. * The new current point shall be (x3, y3). - * @param $x1 (float) Abscissa of control point 1. - * @param $y1 (float) Ordinate of control point 1. - * @param $x2 (float) Abscissa of control point 2. - * @param $y2 (float) Ordinate of control point 2. - * @param $x3 (float) Abscissa of end point. - * @param $y3 (float) Ordinate of end point. + * @param float $x1 Abscissa of control point 1. + * @param float $y1 Ordinate of control point 1. + * @param float $x2 Abscissa of control point 2. + * @param float $y2 Ordinate of control point 2. + * @param float $x3 Abscissa of end point. + * @param float $y3 Ordinate of end point. * @protected * @since 2.1.000 (2008-01-08) */ @@ -11495,10 +11553,10 @@ class TCPDF { /** * Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using the current point and (x2, y2) as the Bezier control points. * The new current point shall be (x3, y3). - * @param $x2 (float) Abscissa of control point 2. - * @param $y2 (float) Ordinate of control point 2. - * @param $x3 (float) Abscissa of end point. - * @param $y3 (float) Ordinate of end point. + * @param float $x2 Abscissa of control point 2. + * @param float $y2 Ordinate of control point 2. + * @param float $x3 Abscissa of end point. + * @param float $y3 Ordinate of end point. * @protected * @since 4.9.019 (2010-04-26) */ @@ -11511,10 +11569,10 @@ class TCPDF { /** * Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x3, y3) as the Bezier control points. * The new current point shall be (x3, y3). - * @param $x1 (float) Abscissa of control point 1. - * @param $y1 (float) Ordinate of control point 1. - * @param $x3 (float) Abscissa of end point. - * @param $y3 (float) Ordinate of end point. + * @param float $x1 Abscissa of control point 1. + * @param float $y1 Ordinate of control point 1. + * @param float $x3 Abscissa of end point. + * @param float $y3 Ordinate of end point. * @protected * @since 2.1.000 (2008-01-08) */ @@ -11526,11 +11584,11 @@ class TCPDF { /** * Draws a line between two points. - * @param $x1 (float) Abscissa of first point. - * @param $y1 (float) Ordinate of first point. - * @param $x2 (float) Abscissa of second point. - * @param $y2 (float) Ordinate of second point. - * @param $style (array) Line style. Array like for SetLineStyle(). Default value: default line style (empty array). + * @param float $x1 Abscissa of first point. + * @param float $y1 Ordinate of first point. + * @param float $x2 Abscissa of second point. + * @param float $y2 Ordinate of second point. + * @param array $style Line style. Array like for SetLineStyle(). Default value: default line style (empty array). * @public * @since 1.0 * @see SetLineWidth(), SetDrawColor(), SetLineStyle() @@ -11540,7 +11598,7 @@ class TCPDF { return; } if (is_array($style)) { - $this->SetLineStyle($style); + $this->setLineStyle($style); } $this->_outPoint($x1, $y1); $this->_outLine($x2, $y2); @@ -11549,18 +11607,18 @@ class TCPDF { /** * Draws a rectangle. - * @param $x (float) Abscissa of upper-left corner. - * @param $y (float) Ordinate of upper-left corner. - * @param $w (float) Width. - * @param $h (float) Height. - * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information. - * @param $border_style (array) Border style of rectangle. Array with keys among the following: + * @param float $x Abscissa of upper-left corner. + * @param float $y Ordinate of upper-left corner. + * @param float $w Width. + * @param float $h Height. + * @param string $style Style of rendering. See the getPathPaintOperator() function for more information. + * @param array $border_style Border style of rectangle. Array with keys among the following: *
                *
              • all: Line style of all borders. Array like for SetLineStyle().
              • *
              • L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().
              • *
              * If a key is not present or is null, the correspondent border is not drawn. Default value: default line style (empty array). - * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array). + * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array). * @public * @since 1.0 * @see SetLineStyle() @@ -11574,12 +11632,12 @@ class TCPDF { } if (!(strpos($style, 'F') === false) AND !empty($fill_color)) { // set background color - $this->SetFillColorArray($fill_color); + $this->setFillColorArray($fill_color); } if (!empty($border_style)) { if (isset($border_style['all']) AND !empty($border_style['all'])) { //set global style for border - $this->SetLineStyle($border_style['all']); + $this->setLineStyle($border_style['all']); $border_style = array(); } else { // remove stroke operator from style @@ -11621,17 +11679,17 @@ class TCPDF { * Draws a Bezier curve. * The Bezier curve is a tangent to the line between the control points at * either end of the curve. - * @param $x0 (float) Abscissa of start point. - * @param $y0 (float) Ordinate of start point. - * @param $x1 (float) Abscissa of control point 1. - * @param $y1 (float) Ordinate of control point 1. - * @param $x2 (float) Abscissa of control point 2. - * @param $y2 (float) Ordinate of control point 2. - * @param $x3 (float) Abscissa of end point. - * @param $y3 (float) Ordinate of end point. - * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information. - * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array). - * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array). + * @param float $x0 Abscissa of start point. + * @param float $y0 Ordinate of start point. + * @param float $x1 Abscissa of control point 1. + * @param float $y1 Ordinate of control point 1. + * @param float $x2 Abscissa of control point 2. + * @param float $y2 Ordinate of control point 2. + * @param float $x3 Abscissa of end point. + * @param float $y3 Ordinate of end point. + * @param string $style Style of rendering. See the getPathPaintOperator() function for more information. + * @param array $line_style Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array). + * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array). * @public * @see SetLineStyle() * @since 2.1.000 (2008-01-08) @@ -11641,11 +11699,11 @@ class TCPDF { return; } if (!(false === strpos($style, 'F')) AND isset($fill_color)) { - $this->SetFillColorArray($fill_color); + $this->setFillColorArray($fill_color); } $op = TCPDF_STATIC::getPathPaintOperator($style); if ($line_style) { - $this->SetLineStyle($line_style); + $this->setLineStyle($line_style); } $this->_outPoint($x0, $y0); $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3); @@ -11656,12 +11714,12 @@ class TCPDF { * Draws a poly-Bezier curve. * Each Bezier curve segment is a tangent to the line between the control points at * either end of the curve. - * @param $x0 (float) Abscissa of start point. - * @param $y0 (float) Ordinate of start point. - * @param $segments (float) An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3). - * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information. - * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array). - * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array). + * @param float $x0 Abscissa of start point. + * @param float $y0 Ordinate of start point. + * @param float[] $segments An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3). + * @param string $style Style of rendering. See the getPathPaintOperator() function for more information. + * @param array $line_style Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array). + * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array). * @public * @see SetLineStyle() * @since 3.0008 (2008-05-12) @@ -11671,14 +11729,14 @@ class TCPDF { return; } if (!(false === strpos($style, 'F')) AND isset($fill_color)) { - $this->SetFillColorArray($fill_color); + $this->setFillColorArray($fill_color); } $op = TCPDF_STATIC::getPathPaintOperator($style); if ($op == 'f') { $line_style = array(); } if ($line_style) { - $this->SetLineStyle($line_style); + $this->setLineStyle($line_style); } $this->_outPoint($x0, $y0); foreach ($segments as $segment) { @@ -11691,22 +11749,22 @@ class TCPDF { /** * Draws an ellipse. * An ellipse is formed from n Bezier curves. - * @param $x0 (float) Abscissa of center point. - * @param $y0 (float) Ordinate of center point. - * @param $rx (float) Horizontal radius. - * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0. - * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0. - * @param $astart: (float) Angle start of draw line. Default value: 0. - * @param $afinish: (float) Angle finish of draw line. Default value: 360. - * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information. - * @param $line_style (array) Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array). - * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array). - * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse. + * @param float $x0 Abscissa of center point. + * @param float $y0 Ordinate of center point. + * @param float $rx Horizontal radius. + * @param float $ry Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0. + * @param float $angle Angle oriented (anti-clockwise). Default value: 0. + * @param float $astart Angle start of draw line. Default value: 0. + * @param float $afinish Angle finish of draw line. Default value: 360. + * @param string $style Style of rendering. See the getPathPaintOperator() function for more information. + * @param array $line_style Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array). + * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array). + * @param integer $nc Number of curves used to draw a 90 degrees portion of ellipse. * @author Nicola Asuni * @public * @since 2.1.000 (2008-01-08) */ - public function Ellipse($x0, $y0, $rx, $ry='', $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) { + public function Ellipse($x0, $y0, $rx, $ry=0, $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) { if ($this->state != 2) { return; } @@ -11714,14 +11772,14 @@ class TCPDF { $ry = $rx; } if (!(false === strpos($style, 'F')) AND isset($fill_color)) { - $this->SetFillColorArray($fill_color); + $this->setFillColorArray($fill_color); } $op = TCPDF_STATIC::getPathPaintOperator($style); if ($op == 'f') { $line_style = array(); } if ($line_style) { - $this->SetLineStyle($line_style); + $this->setLineStyle($line_style); } $this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false); $this->_out($op); @@ -11730,18 +11788,18 @@ class TCPDF { /** * Append an elliptical arc to the current path. * An ellipse is formed from n Bezier curves. - * @param $xc (float) Abscissa of center point. - * @param $yc (float) Ordinate of center point. - * @param $rx (float) Horizontal radius. - * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0. - * @param $xang: (float) Angle between the X-axis and the major axis of the ellipse. Default value: 0. - * @param $angs: (float) Angle start of draw line. Default value: 0. - * @param $angf: (float) Angle finish of draw line. Default value: 360. - * @param $pie (boolean) if true do not mark the border point (used to draw pie sectors). - * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse. - * @param $startpoint (boolean) if true output a starting point. - * @param $ccw (boolean) if true draws in counter-clockwise. - * @param $svg (boolean) if true the angles are in svg mode (already calculated). + * @param float $xc Abscissa of center point. + * @param float $yc Ordinate of center point. + * @param float $rx Horizontal radius. + * @param float $ry Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0. + * @param float $xang Angle between the X-axis and the major axis of the ellipse. Default value: 0. + * @param float $angs Angle start of draw line. Default value: 0. + * @param float $angf Angle finish of draw line. Default value: 360. + * @param boolean $pie if true do not mark the border point (used to draw pie sectors). + * @param integer $nc Number of curves used to draw a 90 degrees portion of ellipse. + * @param boolean $startpoint if true output a starting point. + * @param boolean $ccw if true draws in counter-clockwise. + * @param boolean $svg if true the angles are in svg mode (already calculated). * @return array bounding box coordinates (x min, y min, x max, y max) * @author Nicola Asuni * @protected @@ -11866,15 +11924,15 @@ class TCPDF { /** * Draws a circle. * A circle is formed from n Bezier curves. - * @param $x0 (float) Abscissa of center point. - * @param $y0 (float) Ordinate of center point. - * @param $r (float) Radius. - * @param $angstr: (float) Angle start of draw line. Default value: 0. - * @param $angend: (float) Angle finish of draw line. Default value: 360. - * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information. - * @param $line_style (array) Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array). - * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array). - * @param $nc (integer) Number of curves used to draw a 90 degrees portion of circle. + * @param float $x0 Abscissa of center point. + * @param float $y0 Ordinate of center point. + * @param float $r Radius. + * @param float $angstr Angle start of draw line. Default value: 0. + * @param float $angend Angle finish of draw line. Default value: 360. + * @param string $style Style of rendering. See the getPathPaintOperator() function for more information. + * @param array $line_style Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array). + * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array). + * @param integer $nc Number of curves used to draw a 90 degrees portion of circle. * @public * @since 2.1.000 (2008-01-08) */ @@ -11884,15 +11942,15 @@ class TCPDF { /** * Draws a polygonal line - * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1)) - * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information. - * @param $line_style (array) Line style of polygon. Array with keys among the following: + * @param array $p Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1)) + * @param string $style Style of rendering. See the getPathPaintOperator() function for more information. + * @param array $line_style Line style of polygon. Array with keys among the following: *
                *
              • all: Line style of all lines. Array like for SetLineStyle().
              • *
              • 0 to ($np - 1): Line style of each line. Array like for SetLineStyle().
              • *
              * If a key is not present or is null, not draws the line. Default value is default line style (empty array). - * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array). + * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array). * @since 4.8.003 (2009-09-15) * @public */ @@ -11902,16 +11960,16 @@ class TCPDF { /** * Draws a polygon. - * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1)) - * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information. - * @param $line_style (array) Line style of polygon. Array with keys among the following: + * @param array $p Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1)) + * @param string $style Style of rendering. See the getPathPaintOperator() function for more information. + * @param array $line_style Line style of polygon. Array with keys among the following: *
                *
              • all: Line style of all lines. Array like for SetLineStyle().
              • *
              • 0 to ($np - 1): Line style of each line. Array like for SetLineStyle().
              • *
              * If a key is not present or is null, not draws the line. Default value is default line style (empty array). - * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array). - * @param $closed (boolean) if true the polygon is closes, otherwise will remain open + * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array). + * @param boolean $closed if true the polygon is closes, otherwise will remain open * @public * @since 2.1.000 (2008-01-08) */ @@ -11933,7 +11991,7 @@ class TCPDF { $nc += 4; } if (!(false === strpos($style, 'F')) AND isset($fill_color)) { - $this->SetFillColorArray($fill_color); + $this->setFillColorArray($fill_color); } $op = TCPDF_STATIC::getPathPaintOperator($style); if ($op == 'f') { @@ -11942,7 +12000,7 @@ class TCPDF { $draw = true; if ($line_style) { if (isset($line_style['all'])) { - $this->SetLineStyle($line_style['all']); + $this->setLineStyle($line_style['all']); } else { $draw = false; if ($op == 'B') { @@ -11962,7 +12020,7 @@ class TCPDF { if ($line_style[$line_num] != 0) { if (is_array($line_style[$line_num])) { $this->_out('S'); - $this->SetLineStyle($line_style[$line_num]); + $this->setLineStyle($line_style[$line_num]); $this->_outPoint($p[$i - 2], $p[$i - 1]); $this->_outLine($p[$i], $p[$i + 1]); $this->_out('S'); @@ -11989,21 +12047,21 @@ class TCPDF { /** * Draws a regular polygon. - * @param $x0 (float) Abscissa of center point. - * @param $y0 (float) Ordinate of center point. - * @param $r: (float) Radius of inscribed circle. - * @param $ns (integer) Number of sides. - * @param $angle (float) Angle oriented (anti-clockwise). Default value: 0. - * @param $draw_circle (boolean) Draw inscribed circle or not. Default value: false. - * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information. - * @param $line_style (array) Line style of polygon sides. Array with keys among the following: + * @param float $x0 Abscissa of center point. + * @param float $y0 Ordinate of center point. + * @param float $r Radius of inscribed circle. + * @param integer $ns Number of sides. + * @param float $angle Angle oriented (anti-clockwise). Default value: 0. + * @param boolean $draw_circle Draw inscribed circle or not. Default value: false. + * @param string $style Style of rendering. See the getPathPaintOperator() function for more information. + * @param array $line_style Line style of polygon sides. Array with keys among the following: *
                *
              • all: Line style of all sides. Array like for SetLineStyle().
              • *
              • 0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().
              • *
              * If a key is not present or is null, not draws the side. Default value is default line style (empty array). - * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array). - * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are: + * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array). + * @param string $circle_style Style of rendering of inscribed circle (if draws). Possible values are: *
                *
              • D or empty string: Draw (default).
              • *
              • F: Fill.
              • @@ -12011,8 +12069,8 @@ class TCPDF { *
              • CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).
              • *
              • CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).
              • *
              - * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array). - * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array). + * @param array $circle_outLine_style Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array). + * @param array $circle_fill_color Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array). * @public * @since 2.1.000 (2008-01-08) */ @@ -12035,23 +12093,23 @@ class TCPDF { /** * Draws a star polygon - * @param $x0 (float) Abscissa of center point. - * @param $y0 (float) Ordinate of center point. - * @param $r (float) Radius of inscribed circle. - * @param $nv (integer) Number of vertices. - * @param $ng (integer) Number of gap (if ($ng % $nv = 1) then is a regular polygon). - * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0. - * @param $draw_circle: (boolean) Draw inscribed circle or not. Default value is false. - * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information. - * @param $line_style (array) Line style of polygon sides. Array with keys among the following: + * @param float $x0 Abscissa of center point. + * @param float $y0 Ordinate of center point. + * @param float $r Radius of inscribed circle. + * @param integer $nv Number of vertices. + * @param integer $ng Number of gap (if ($ng % $nv = 1) then is a regular polygon). + * @param float $angle Angle oriented (anti-clockwise). Default value: 0. + * @param boolean $draw_circle Draw inscribed circle or not. Default value is false. + * @param string $style Style of rendering. See the getPathPaintOperator() function for more information. + * @param array $line_style Line style of polygon sides. Array with keys among the following: *
                *
              • all: Line style of all sides. Array like for * SetLineStyle().
              • *
              • 0 to (n - 1): Line style of each side. Array like for SetLineStyle().
              • *
              * If a key is not present or is null, not draws the side. Default value is default line style (empty array). - * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array). - * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are: + * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array). + * @param string $circle_style Style of rendering of inscribed circle (if draws). Possible values are: *
                *
              • D or empty string: Draw (default).
              • *
              • F: Fill.
              • @@ -12059,8 +12117,8 @@ class TCPDF { *
              • CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).
              • *
              • CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).
              • *
              - * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array). - * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array). + * @param array $circle_outLine_style Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array). + * @param array $circle_fill_color Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array). * @public * @since 2.1.000 (2008-01-08) */ @@ -12094,15 +12152,15 @@ class TCPDF { /** * Draws a rounded rectangle. - * @param $x (float) Abscissa of upper-left corner. - * @param $y (float) Ordinate of upper-left corner. - * @param $w (float) Width. - * @param $h (float) Height. - * @param $r (float) the radius of the circle used to round off the corners of the rectangle. - * @param $round_corner (string) Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111"). - * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information. - * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array). - * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array). + * @param float $x Abscissa of upper-left corner. + * @param float $y Ordinate of upper-left corner. + * @param float $w Width. + * @param float $h Height. + * @param float $r the radius of the circle used to round off the corners of the rectangle. + * @param string $round_corner Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111"). + * @param string $style Style of rendering. See the getPathPaintOperator() function for more information. + * @param array $border_style Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array). + * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array). * @public * @since 2.1.000 (2008-01-08) */ @@ -12112,16 +12170,16 @@ class TCPDF { /** * Draws a rounded rectangle. - * @param $x (float) Abscissa of upper-left corner. - * @param $y (float) Ordinate of upper-left corner. - * @param $w (float) Width. - * @param $h (float) Height. - * @param $rx (float) the x-axis radius of the ellipse used to round off the corners of the rectangle. - * @param $ry (float) the y-axis radius of the ellipse used to round off the corners of the rectangle. - * @param $round_corner (string) Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111"). - * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information. - * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array). - * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array). + * @param float $x Abscissa of upper-left corner. + * @param float $y Ordinate of upper-left corner. + * @param float $w Width. + * @param float $h Height. + * @param float $rx the x-axis radius of the ellipse used to round off the corners of the rectangle. + * @param float $ry the y-axis radius of the ellipse used to round off the corners of the rectangle. + * @param string $round_corner Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111"). + * @param string $style Style of rendering. See the getPathPaintOperator() function for more information. + * @param array $border_style Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array). + * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array). * @public * @since 4.9.019 (2010-04-22) */ @@ -12136,14 +12194,14 @@ class TCPDF { } // Rounded if (!(false === strpos($style, 'F')) AND isset($fill_color)) { - $this->SetFillColorArray($fill_color); + $this->setFillColorArray($fill_color); } $op = TCPDF_STATIC::getPathPaintOperator($style); if ($op == 'f') { $border_style = array(); } if ($border_style) { - $this->SetLineStyle($border_style); + $this->setLineStyle($border_style); } $MyArc = 4 / 3 * (sqrt(2) - 1); $this->_outPoint($x + $rx, $y); @@ -12185,13 +12243,13 @@ class TCPDF { /** * Draws a grahic arrow. - * @param $x0 (float) Abscissa of first point. - * @param $y0 (float) Ordinate of first point. - * @param $x1 (float) Abscissa of second point. - * @param $y1 (float) Ordinate of second point. - * @param $head_style (int) (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead) - * @param $arm_size (float) length of arrowhead arms - * @param $arm_angle (int) angle between an arm and the shaft + * @param float $x0 Abscissa of first point. + * @param float $y0 Ordinate of first point. + * @param float $x1 Abscissa of second point. + * @param float $y1 Ordinate of second point. + * @param int $head_style (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead) + * @param float $arm_size length of arrowhead arms + * @param int $arm_angle angle between an arm and the shaft * @author Piotr Galecki, Nicola Asuni, Andy Meier * @since 4.6.018 (2009-07-10) */ @@ -12251,11 +12309,11 @@ class TCPDF { /** * Add a Named Destination. * NOTE: destination names are unique, so only last entry will be saved. - * @param $name (string) Destination name. - * @param $y (float) Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;). - * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages. - * @param $x (float) X position in user units of the destiantion on the selected page (default = -1 = current position;). - * @return (string) Stripped named destination identifier or false in case of error. + * @param string $name Destination name. + * @param float $y Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;). + * @param int|string $page Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages. + * @param float $x X position in user units of the destiantion on the selected page (default = -1 = current position;). + * @return string|false Stripped named destination identifier or false in case of error. * @public * @author Christian Deligant, Nicola Asuni * @since 5.9.097 (2011-06-23) @@ -12298,7 +12356,7 @@ class TCPDF { /** * Return the Named Destination array. - * @return (array) Named Destination array. + * @return array Named Destination array. * @public * @author Nicola Asuni * @since 5.9.097 (2011-06-23) @@ -12329,14 +12387,14 @@ class TCPDF { /** * Adds a bookmark - alias for Bookmark(). - * @param $txt (string) Bookmark description. - * @param $level (int) Bookmark level (minimum value is 0). - * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;). - * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages. - * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic. - * @param $color (array) RGB color array (values from 0 to 255). - * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;). - * @param $link (mixed) URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name). + * @param string $txt Bookmark description. + * @param int $level Bookmark level (minimum value is 0). + * @param float $y Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;). + * @param int|string $page Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages. + * @param string $style Font style: B = Bold, I = Italic, BI = Bold + Italic. + * @param array $color RGB color array (values from 0 to 255). + * @param float $x X position in user units of the bookmark on the selected page (default = -1 = current position;). + * @param mixed $link URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name). * @public */ public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') { @@ -12345,14 +12403,14 @@ class TCPDF { /** * Adds a bookmark. - * @param $txt (string) Bookmark description. - * @param $level (int) Bookmark level (minimum value is 0). - * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;). - * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages. - * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic. - * @param $color (array) RGB color array (values from 0 to 255). - * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;). - * @param $link (mixed) URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name). + * @param string $txt Bookmark description. + * @param int $level Bookmark level (minimum value is 0). + * @param float $y Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;). + * @param int|string $page Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages. + * @param string $style Font style: B = Bold, I = Italic, BI = Bold + Italic. + * @param array $color RGB color array (values from 0 to 255). + * @param float $x X position in user units of the bookmark on the selected page (default = -1 = current position;). + * @param mixed $link URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name). * @public * @since 2.1.002 (2008-02-12) */ @@ -12543,7 +12601,7 @@ class TCPDF { /** * Adds a javascript - * @param $script (string) Javascript code + * @param string $script Javascript code * @public * @author Johannes G\FCntert, Nicola Asuni * @since 2.1.002 (2008-02-12) @@ -12554,8 +12612,8 @@ class TCPDF { /** * Adds a javascript object and return object ID - * @param $script (string) Javascript code - * @param $onload (boolean) if true executes this object when opening the document + * @param string $script Javascript code + * @param boolean $onload if true executes this object when opening the document * @return int internal object ID * @public * @author Nicola Asuni @@ -12624,13 +12682,13 @@ class TCPDF { /** * Adds a javascript form field. - * @param $type (string) field type - * @param $name (string) field name - * @param $x (int) horizontal position - * @param $y (int) vertical position - * @param $w (int) width - * @param $h (int) height - * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference. + * @param string $type field type + * @param string $name field name + * @param int $x horizontal position + * @param int $y vertical position + * @param int $w width + * @param int $h height + * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference. * @protected * @author Denis Van Nuffelen, Nicola Asuni * @since 2.1.002 (2008-02-12) @@ -12666,7 +12724,7 @@ class TCPDF { /** * Set default properties for form fields. - * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference. + * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference. * @public * @author Nicola Asuni * @since 4.8.000 (2009-09-06) @@ -12688,23 +12746,23 @@ class TCPDF { /** * Creates a text field - * @param $name (string) field name - * @param $w (float) Width of the rectangle - * @param $h (float) Height of the rectangle - * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference. - * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference. - * @param $x (float) Abscissa of the upper-left corner of the rectangle - * @param $y (float) Ordinate of the upper-left corner of the rectangle - * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered). + * @param string $name field name + * @param float $w Width of the rectangle + * @param float $h Height of the rectangle + * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference. + * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference. + * @param float|null $x Abscissa of the upper-left corner of the rectangle + * @param float|null $y Ordinate of the upper-left corner of the rectangle + * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered). * @public * @author Nicola Asuni * @since 4.8.000 (2009-09-07) */ - public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x='', $y='', $js=false) { - if ($x === '') { + public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) { + if (TCPDF_STATIC::empty_string($x)) { $x = $this->x; } - if ($y === '') { + if (TCPDF_STATIC::empty_string($y)) { $y = $this->y; } // check page for no-write regions and adapt page margins if necessary @@ -12808,24 +12866,24 @@ class TCPDF { /** * Creates a RadioButton field. - * @param $name (string) Field name. - * @param $w (int) Width of the radio button. - * @param $prop (array) Javascript field properties. Possible values are described on official Javascript for Acrobat API reference. - * @param $opt (array) Annotation parameters. Possible values are described on official PDF32000_2008 reference. - * @param $onvalue (string) Value to be returned if selected. - * @param $checked (boolean) Define the initial state. - * @param $x (float) Abscissa of the upper-left corner of the rectangle - * @param $y (float) Ordinate of the upper-left corner of the rectangle - * @param $js (boolean) If true put the field using JavaScript (requires Acrobat Writer to be rendered). + * @param string $name Field name. + * @param int $w Width of the radio button. + * @param array $prop Javascript field properties. Possible values are described on official Javascript for Acrobat API reference. + * @param array $opt Annotation parameters. Possible values are described on official PDF32000_2008 reference. + * @param string $onvalue Value to be returned if selected. + * @param boolean $checked Define the initial state. + * @param float|null $x Abscissa of the upper-left corner of the rectangle + * @param float|null $y Ordinate of the upper-left corner of the rectangle + * @param boolean $js If true put the field using JavaScript (requires Acrobat Writer to be rendered). * @public * @author Nicola Asuni * @since 4.8.000 (2009-09-07) */ - public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x='', $y='', $js=false) { - if ($x === '') { + public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x=null, $y=null, $js=false) { + if (TCPDF_STATIC::empty_string($x)) { $x = $this->x; } - if ($y === '') { + if (TCPDF_STATIC::empty_string($y)) { $y = $this->y; } // check page for no-write regions and adapt page margins if necessary @@ -12911,24 +12969,24 @@ class TCPDF { /** * Creates a List-box field - * @param $name (string) field name - * @param $w (int) width - * @param $h (int) height - * @param $values (array) array containing the list of values. - * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference. - * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference. - * @param $x (float) Abscissa of the upper-left corner of the rectangle - * @param $y (float) Ordinate of the upper-left corner of the rectangle - * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered). + * @param string $name field name + * @param int $w width + * @param int $h height + * @param array $values array containing the list of values. + * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference. + * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference. + * @param float|null $x Abscissa of the upper-left corner of the rectangle + * @param float|null $y Ordinate of the upper-left corner of the rectangle + * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered). * @public * @author Nicola Asuni * @since 4.8.000 (2009-09-07) */ - public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) { - if ($x === '') { + public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) { + if (TCPDF_STATIC::empty_string($x)) { $x = $this->x; } - if ($y === '') { + if (TCPDF_STATIC::empty_string($y)) { $y = $this->y; } // check page for no-write regions and adapt page margins if necessary @@ -12997,24 +13055,24 @@ class TCPDF { /** * Creates a Combo-box field - * @param $name (string) field name - * @param $w (int) width - * @param $h (int) height - * @param $values (array) array containing the list of values. - * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference. - * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference. - * @param $x (float) Abscissa of the upper-left corner of the rectangle - * @param $y (float) Ordinate of the upper-left corner of the rectangle - * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered). + * @param string $name field name + * @param int $w width + * @param int $h height + * @param array $values array containing the list of values. + * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference. + * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference. + * @param float|null $x Abscissa of the upper-left corner of the rectangle + * @param float|null $y Ordinate of the upper-left corner of the rectangle + * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered). * @public * @author Nicola Asuni * @since 4.8.000 (2009-09-07) */ - public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) { - if ($x === '') { + public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) { + if (TCPDF_STATIC::empty_string($x)) { $x = $this->x; } - if ($y === '') { + if (TCPDF_STATIC::empty_string($y)) { $y = $this->y; } // check page for no-write regions and adapt page margins if necessary @@ -13084,24 +13142,24 @@ class TCPDF { /** * Creates a CheckBox field - * @param $name (string) field name - * @param $w (int) width - * @param $checked (boolean) define the initial state. - * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference. - * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference. - * @param $onvalue (string) value to be returned if selected. - * @param $x (float) Abscissa of the upper-left corner of the rectangle - * @param $y (float) Ordinate of the upper-left corner of the rectangle - * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered). + * @param string $name field name + * @param int $w width + * @param boolean $checked define the initial state. + * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference. + * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference. + * @param string $onvalue value to be returned if selected. + * @param float|null $x Abscissa of the upper-left corner of the rectangle + * @param float|null $y Ordinate of the upper-left corner of the rectangle + * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered). * @public * @author Nicola Asuni * @since 4.8.000 (2009-09-07) */ - public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x='', $y='', $js=false) { - if ($x === '') { + public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x=null, $y=null, $js=false) { + if (TCPDF_STATIC::empty_string($x)) { $x = $this->x; } - if ($y === '') { + if (TCPDF_STATIC::empty_string($y)) { $y = $this->y; } // check page for no-write regions and adapt page margins if necessary @@ -13163,25 +13221,25 @@ class TCPDF { /** * Creates a button field - * @param $name (string) field name - * @param $w (int) width - * @param $h (int) height - * @param $caption (string) caption. - * @param $action (mixed) action triggered by pressing the button. Use a string to specify a javascript action. Use an array to specify a form action options as on section 12.7.5 of PDF32000_2008. - * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference. - * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference. - * @param $x (float) Abscissa of the upper-left corner of the rectangle - * @param $y (float) Ordinate of the upper-left corner of the rectangle - * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered). + * @param string $name field name + * @param int $w width + * @param int $h height + * @param string $caption caption. + * @param mixed $action action triggered by pressing the button. Use a string to specify a javascript action. Use an array to specify a form action options as on section 12.7.5 of PDF32000_2008. + * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference. + * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference. + * @param float|null $x Abscissa of the upper-left corner of the rectangle + * @param float|null $y Ordinate of the upper-left corner of the rectangle + * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered). * @public * @author Nicola Asuni * @since 4.8.000 (2009-09-07) */ - public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x='', $y='', $js=false) { - if ($x === '') { + public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) { + if (TCPDF_STATIC::empty_string($x)) { $x = $this->x; } - if ($y === '') { + if (TCPDF_STATIC::empty_string($y)) { $y = $this->y; } // check page for no-write regions and adapt page margins if necessary @@ -13214,7 +13272,7 @@ class TCPDF { 'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)), 'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)), 'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51))); - $this->SetFillColor(204); + $this->setFillColor(204); $this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M'); $this->endTemplate(); --$this->n; @@ -13420,14 +13478,14 @@ class TCPDF { * WARNING: This is experimental and currently do not work. * Check the PDF Reference 8.7.1 Transform Methods, * Table 8.105 Entries in the UR transform parameters dictionary - * @param $enable (boolean) if true enable user's rights on PDF reader - * @param $document (string) Names specifying additional document-wide usage rights for the document. The only defined value is "/FullSave", which permits a user to save the document along with modified form and/or annotation data. - * @param $annots (string) Names specifying additional annotation-related usage rights for the document. Valid names in PDF 1.5 and later are /Create/Delete/Modify/Copy/Import/Export, which permit the user to perform the named operation on annotations. - * @param $form (string) Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate - * @param $signature (string) Names specifying additional signature-related usage rights for the document. The only defined value is /Modify, which permits a user to apply a digital signature to an existing signature form field or clear a signed signature form field. - * @param $ef (string) Names specifying additional usage rights for named embedded files in the document. Valid names are /Create/Delete/Modify/Import, which permit the user to perform the named operation on named embedded files + * @param boolean $enable if true enable user's rights on PDF reader + * @param string $document Names specifying additional document-wide usage rights for the document. The only defined value is "/FullSave", which permits a user to save the document along with modified form and/or annotation data. + * @param string $annots Names specifying additional annotation-related usage rights for the document. Valid names in PDF 1.5 and later are /Create/Delete/Modify/Copy/Import/Export, which permit the user to perform the named operation on annotations. + * @param string $form Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate + * @param string $signature Names specifying additional signature-related usage rights for the document. The only defined value is /Modify, which permits a user to apply a digital signature to an existing signature form field or clear a signed signature form field. + * @param string $ef Names specifying additional usage rights for named embedded files in the document. Valid names are /Create/Delete/Modify/Import, which permit the user to perform the named operation on named embedded files Names specifying additional embedded-files-related usage rights for the document. - * @param $formex (string) Names specifying additional form-field-related usage rights. The only valid name is BarcodePlaintext, which permits text form field data to be encoded as a plaintext two-dimensional barcode. + * @param string $formex Names specifying additional form-field-related usage rights. The only valid name is BarcodePlaintext, which permits text form field data to be encoded as a plaintext two-dimensional barcode. * @public * @author Nicola Asuni * @since 2.9.000 (2008-03-26) @@ -13458,13 +13516,13 @@ class TCPDF { * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12 * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes - * @param $signing_cert (mixed) signing certificate (string or filename prefixed with 'file://') - * @param $private_key (mixed) private key (string or filename prefixed with 'file://') - * @param $private_key_password (string) password - * @param $extracerts (string) specifies the name of a file containing a bunch of extra certificates to include in the signature which can for example be used to help the recipient to verify the certificate that you used. - * @param $cert_type (int) The access permissions granted for this document. Valid values shall be: 1 = No changes to the document shall be permitted; any change to the document shall invalidate the signature; 2 = Permitted changes shall be filling in forms, instantiating page templates, and signing; other changes shall invalidate the signature; 3 = Permitted changes shall be the same as for 2, as well as annotation creation, deletion, and modification; other changes shall invalidate the signature. - * @param $info (array) array of option information: Name, Location, Reason, ContactInfo. - * @param $approval (string) Enable approval signature eg. for PDF incremental update + * @param mixed $signing_cert signing certificate (string or filename prefixed with 'file://') + * @param mixed $private_key private key (string or filename prefixed with 'file://') + * @param string $private_key_password password + * @param string $extracerts specifies the name of a file containing a bunch of extra certificates to include in the signature which can for example be used to help the recipient to verify the certificate that you used. + * @param int $cert_type The access permissions granted for this document. Valid values shall be: 1 = No changes to the document shall be permitted; any change to the document shall invalidate the signature; 2 = Permitted changes shall be filling in forms, instantiating page templates, and signing; other changes shall invalidate the signature; 3 = Permitted changes shall be the same as for 2, as well as annotation creation, deletion, and modification; other changes shall invalidate the signature. + * @param array $info array of option information: Name, Location, Reason, ContactInfo. + * @param string $approval Enable approval signature eg. for PDF incremental update * @public * @author Nicola Asuni * @since 4.6.005 (2009-04-24) @@ -13496,12 +13554,12 @@ class TCPDF { /** * Set the digital signature appearance (a cliccable rectangle area to get signature properties) - * @param $x (float) Abscissa of the upper-left corner. - * @param $y (float) Ordinate of the upper-left corner. - * @param $w (float) Width of the signature area. - * @param $h (float) Height of the signature area. - * @param $page (int) option page number (if < 0 the current page is used). - * @param $name (string) Name of the signature. + * @param float $x Abscissa of the upper-left corner. + * @param float $y Ordinate of the upper-left corner. + * @param float $w Width of the signature area. + * @param float $h Height of the signature area. + * @param int $page option page number (if < 0 the current page is used). + * @param string $name Name of the signature. * @public * @author Nicola Asuni * @since 5.3.011 (2010-06-17) @@ -13512,12 +13570,12 @@ class TCPDF { /** * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties) - * @param $x (float) Abscissa of the upper-left corner. - * @param $y (float) Ordinate of the upper-left corner. - * @param $w (float) Width of the signature area. - * @param $h (float) Height of the signature area. - * @param $page (int) option page number (if < 0 the current page is used). - * @param $name (string) Name of the signature. + * @param float $x Abscissa of the upper-left corner. + * @param float $y Ordinate of the upper-left corner. + * @param float $w Width of the signature area. + * @param float $h Height of the signature area. + * @param int $page option page number (if < 0 the current page is used). + * @param string $name Name of the signature. * @public * @author Nicola Asuni * @since 5.9.101 (2011-07-06) @@ -13529,13 +13587,13 @@ class TCPDF { /** * Get the array that defines the signature appearance (page and rectangle coordinates). - * @param $x (float) Abscissa of the upper-left corner. - * @param $y (float) Ordinate of the upper-left corner. - * @param $w (float) Width of the signature area. - * @param $h (float) Height of the signature area. - * @param $page (int) option page number (if < 0 the current page is used). - * @param $name (string) Name of the signature. - * @return (array) Array defining page and rectangle coordinates of signature appearance. + * @param float $x Abscissa of the upper-left corner. + * @param float $y Ordinate of the upper-left corner. + * @param float $w Width of the signature area. + * @param float $h Height of the signature area. + * @param int $page option page number (if < 0 the current page is used). + * @param string $name Name of the signature. + * @return array Array defining page and rectangle coordinates of signature appearance. * @protected * @author Nicola Asuni * @since 5.9.101 (2011-07-06) @@ -13564,10 +13622,10 @@ class TCPDF { * Enable document timestamping (requires the OpenSSL Library). * The trusted timestamping improve document security that means that no one should be able to change the document once it has been recorded. * Use with digital signature only! - * @param $tsa_host (string) Time Stamping Authority (TSA) server (prefixed with 'https://') - * @param $tsa_username (string) Specifies the username for TSA authorization (optional) OR specifies the TSA authorization PEM file (see: example_66.php, optional) - * @param $tsa_password (string) Specifies the password for TSA authorization (optional) - * @param $tsa_cert (string) Specifies the location of TSA certificate for authorization (optional for cURL) + * @param string $tsa_host Time Stamping Authority (TSA) server (prefixed with 'https://') + * @param string $tsa_username Specifies the username for TSA authorization (optional) OR specifies the TSA authorization PEM file (see: example_66.php, optional) + * @param string $tsa_password Specifies the password for TSA authorization (optional) + * @param string $tsa_cert Specifies the location of TSA certificate for authorization (optional for cURL) * @public * @author Richard Stockinger * @since 6.0.090 (2014-06-16) @@ -13594,8 +13652,8 @@ class TCPDF { /** * NOT YET IMPLEMENTED * Request TSA for a timestamp - * @param $signature (string) Digital signature as binary string - * @return (string) Timestamped digital signature + * @param string $signature Digital signature as binary string + * @return string Timestamped digital signature * @protected * @author Richard Stockinger * @since 6.0.090 (2014-06-16) @@ -13611,11 +13669,11 @@ class TCPDF { /** * Create a new page group. * NOTE: call this function before calling AddPage() - * @param $page (int) starting group page (leave empty for next page). + * @param int|null $page starting group page (leave empty for next page). * @public * @since 3.0.000 (2008-03-27) */ - public function startPageGroup($page='') { + public function startPageGroup($page=null) { if (empty($page)) { $page = $this->page + 1; } @@ -13624,7 +13682,7 @@ class TCPDF { /** * Set the starting page number. - * @param $num (int) Starting page number. + * @param int $num Starting page number. * @since 5.9.093 (2011-06-16) * @public */ @@ -13691,7 +13749,7 @@ class TCPDF { * Return the alias for the total number of pages in the current page group. * If the current font is unicode type, the returned string is surrounded by additional curly braces. * This alias will be replaced by the total number of pages in this group. - * @return alias of the current page group + * @return string alias of the current page group * @public * @since 3.0.000 (2008-03-27) */ @@ -13706,7 +13764,7 @@ class TCPDF { * Return the alias for the page number on the current page group. * If the current font is unicode type, the returned string is surrounded by additional curly braces. * This alias will be replaced by the page number (relative to the belonging group). - * @return alias of the current page group + * @return string alias of the current page group * @public * @since 4.5.000 (2009-01-02) */ @@ -13719,7 +13777,7 @@ class TCPDF { /** * Return the current page in the group. - * @return current page in the group + * @return int current page in the group * @public * @since 3.0.000 (2008-03-27) */ @@ -13773,10 +13831,10 @@ class TCPDF { /** * Start a new pdf layer. - * @param $name (string) Layer name (only a-z letters and numbers). Leave empty for automatic name. - * @param $print (boolean|null) Set to TRUE to print this layer, FALSE to not print and NULL to not set this option - * @param $view (boolean) Set to true to view this layer. - * @param $lock (boolean) If true lock the layer + * @param string $name Layer name (only a-z letters and numbers). Leave empty for automatic name. + * @param boolean|null $print Set to TRUE to print this layer, FALSE to not print and NULL to not set this option + * @param boolean $view Set to true to view this layer. + * @param boolean $lock If true lock the layer * @public * @since 5.9.102 (2011-07-13) */ @@ -13815,7 +13873,7 @@ class TCPDF { * Set the visibility of the successive elements. * This can be useful, for instance, to put a background * image or color that will show on screen but won't print. - * @param $v (string) visibility mode. Legal values are: all, print, screen or view. + * @param string $v visibility mode. Legal values are: all, print, screen or view. * @public * @since 3.0.000 (2008-03-27) */ @@ -13847,13 +13905,13 @@ class TCPDF { /** * Add transparency parameters to the current extgstate - * @param $parms (array) parameters - * @return the number of extgstates + * @param array $parms parameters + * @return int|void the number of extgstates * @protected * @since 3.0.000 (2008-03-27) */ protected function addExtGState($parms) { - if ($this->pdfa_mode) { + if ($this->pdfa_mode || $this->pdfa_version >= 2) { // transparencies are not allowed in PDF/A mode return; } @@ -13879,13 +13937,13 @@ class TCPDF { /** * Add an extgstate - * @param $gs (array) extgstate + * @param int $gs extgstate * @protected * @since 3.0.000 (2008-03-27) */ protected function setExtGState($gs) { - if ($this->pdfa_mode OR ($this->state != 2)) { - // transparency is not allowed in PDF/A mode + if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) { + // transparency is not allowed in PDF/A-1 mode return; } $this->_out(sprintf('/GS%d gs', $gs)); @@ -13919,13 +13977,13 @@ class TCPDF { /** * Set overprint mode for stroking (OP) and non-stroking (op) painting operations. * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008). - * @param $stroking (boolean) If true apply overprint for stroking operations. - * @param $nonstroking (boolean) If true apply overprint for painting operations other than stroking. - * @param $mode (integer) Overprint mode: (0 = each source colour component value replaces the value previously painted for the corresponding device colorant; 1 = a tint value of 0.0 for a source colour component shall leave the corresponding component of the previously painted colour unchanged). + * @param boolean $stroking If true apply overprint for stroking operations. + * @param boolean|null $nonstroking If true apply overprint for painting operations other than stroking. + * @param integer $mode Overprint mode: (0 = each source colour component value replaces the value previously painted for the corresponding device colorant; 1 = a tint value of 0.0 for a source colour component shall leave the corresponding component of the previously painted colour unchanged). * @public * @since 5.9.152 (2012-03-23) */ - public function setOverprint($stroking=true, $nonstroking='', $mode=0) { + public function setOverprint($stroking=true, $nonstroking=null, $mode=0) { if ($this->state != 2) { return; } @@ -13947,7 +14005,7 @@ class TCPDF { /** * Get the overprint mode array (OP, op, OPM). * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008). - * @return array. + * @return array * @public * @since 5.9.152 (2012-03-23) */ @@ -13957,16 +14015,16 @@ class TCPDF { /** * Set alpha for stroking (CA) and non-stroking (ca) operations. - * @param $stroking (float) Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque). - * @param $bm (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity - * @param $nonstroking (float) Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque). - * @param $ais (boolean) + * @param float $stroking Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque). + * @param string $bm blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity + * @param float|null $nonstroking Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque). + * @param boolean $ais * @public * @since 3.0.000 (2008-03-27) */ - public function setAlpha($stroking=1, $bm='Normal', $nonstroking='', $ais=false) { - if ($this->pdfa_mode) { - // transparency is not allowed in PDF/A mode + public function setAlpha($stroking=1, $bm='Normal', $nonstroking=null, $ais=false) { + if ($this->pdfa_mode && $this->pdfa_version < 2) { + // transparency is not allowed in PDF/A-1 mode return; } $stroking = floatval($stroking); @@ -13992,7 +14050,7 @@ class TCPDF { /** * Get the alpha mode array (CA, ca, BM, AIS). * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008). - * @return array. + * @return array * @public * @since 5.9.152 (2012-03-23) */ @@ -14002,7 +14060,7 @@ class TCPDF { /** * Set the default JPEG compression quality (1-100) - * @param $quality (int) JPEG quality, integer between 1 and 100 + * @param int $quality JPEG quality, integer between 1 and 100 * @public * @since 3.0.000 (2008-03-27) */ @@ -14015,7 +14073,7 @@ class TCPDF { /** * Set the default number of columns in a row for HTML tables. - * @param $cols (int) number of columns + * @param int $cols number of columns * @public * @since 3.0.014 (2008-06-04) */ @@ -14025,7 +14083,7 @@ class TCPDF { /** * Set the height of the cell (line height) respect the font height. - * @param $h (int) cell proportion respect font height (typical value = 1.25). + * @param float $h cell proportion respect font height (typical value = 1.25). * @public * @since 3.0.014 (2008-06-04) */ @@ -14036,6 +14094,7 @@ class TCPDF { /** * return the height of cell repect font height. * @public + * @return float * @since 4.0.012 (2008-07-24) */ public function getCellHeightRatio() { @@ -14044,15 +14103,18 @@ class TCPDF { /** * Set the PDF version (check PDF reference for valid values). - * @param $version (string) PDF document version. + * @param string $version PDF document version. * @public * @since 3.1.000 (2008-06-09) */ public function setPDFVersion($version='1.7') { if ($this->pdfa_mode && $this->pdfa_version == 1 ) { - // PDF/A mode + // PDF/A-1 mode $this->PDFVersion = '1.4'; - } else { + } elseif ($this->pdfa_mode && $this->pdfa_version >= 2 ) { + // PDF/A-2 mode + $this->PDFVersion = '1.7'; + } else { $this->PDFVersion = $version; } } @@ -14061,7 +14123,7 @@ class TCPDF { * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print. * (see Section 8.1 of PDF reference, "Viewer Preferences"). *
              • HideToolbar boolean (Optional) A flag specifying whether to hide the viewer application's tool bars when the document is active. Default value: false.
              • HideMenubar boolean (Optional) A flag specifying whether to hide the viewer application's menu bar when the document is active. Default value: false.
              • HideWindowUI boolean (Optional) A flag specifying whether to hide user interface elements in the document's window (such as scroll bars and navigation controls), leaving only the document's contents displayed. Default value: false.
              • FitWindow boolean (Optional) A flag specifying whether to resize the document's window to fit the size of the first displayed page. Default value: false.
              • CenterWindow boolean (Optional) A flag specifying whether to position the document's window in the center of the screen. Default value: false.
              • DisplayDocTitle boolean (Optional; PDF 1.4) A flag specifying whether the window's title bar should display the document title taken from the Title entry of the document information dictionary (see Section 10.2.1, "Document Information Dictionary"). If false, the title bar should instead display the name of the PDF file containing the document. Default value: false.
              • NonFullScreenPageMode name (Optional) The document's page mode, specifying how to display the document on exiting full-screen mode:
                • UseNone Neither document outline nor thumbnail images visible
                • UseOutlines Document outline visible
                • UseThumbs Thumbnail images visible
                • UseOC Optional content group panel visible
                This entry is meaningful only if the value of the PageMode entry in the catalog dictionary (see Section 3.6.1, "Document Catalog") is FullScreen; it is ignored otherwise. Default value: UseNone.
              • ViewArea name (Optional; PDF 1.4) The name of the page boundary representing the area of a page to be displayed when viewing the document on the screen. Valid values are (see Section 10.10.1, "Page Boundaries").:
                • MediaBox
                • CropBox (default)
                • BleedBox
                • TrimBox
                • ArtBox
              • ViewClip name (Optional; PDF 1.4) The name of the page boundary to which the contents of a page are to be clipped when viewing the document on the screen. Valid values are (see Section 10.10.1, "Page Boundaries").:
                • MediaBox
                • CropBox (default)
                • BleedBox
                • TrimBox
                • ArtBox
              • PrintArea name (Optional; PDF 1.4) The name of the page boundary representing the area of a page to be rendered when printing the document. Valid values are (see Section 10.10.1, "Page Boundaries").:
                • MediaBox
                • CropBox (default)
                • BleedBox
                • TrimBox
                • ArtBox
              • PrintClip name (Optional; PDF 1.4) The name of the page boundary to which the contents of a page are to be clipped when printing the document. Valid values are (see Section 10.10.1, "Page Boundaries").:
                • MediaBox
                • CropBox (default)
                • BleedBox
                • TrimBox
                • ArtBox
              • PrintScaling name (Optional; PDF 1.6) The page scaling option to be selected when a print dialog is displayed for this document. Valid values are:
                • None, which indicates that the print dialog should reflect no page scaling
                • AppDefault (default), which indicates that applications should use the current print scaling
              • Duplex name (Optional; PDF 1.7) The paper handling option to use when printing the file from the print dialog. The following values are valid:
                • Simplex - Print single-sided
                • DuplexFlipShortEdge - Duplex and flip on the short edge of the sheet
                • DuplexFlipLongEdge - Duplex and flip on the long edge of the sheet
                Default value: none
              • PickTrayByPDFSize boolean (Optional; PDF 1.7) A flag specifying whether the PDF page size is used to select the input paper tray. This setting influences only the preset values used to populate the print dialog presented by a PDF viewer application. If PickTrayByPDFSize is true, the check box in the print dialog associated with input paper tray is checked. Note: This setting has no effect on Mac OS systems, which do not provide the ability to pick the input tray by size.
              • PrintPageRange array (Optional; PDF 1.7) The page numbers used to initialize the print dialog box when the file is printed. The first page of the PDF file is denoted by 1. Each pair consists of the first and last pages in the sub-range. An odd number of integers causes this entry to be ignored. Negative numbers cause the entire array to be ignored. Default value: as defined by PDF viewer application
              • NumCopies integer (Optional; PDF 1.7) The number of copies to be printed when the print dialog is opened for this file. Supported values are the integers 2 through 5. Values outside this range are ignored. Default value: as defined by PDF viewer application, but typically 1
              - * @param $preferences (array) array of options. + * @param array $preferences array of options. * @author Nicola Asuni * @public * @since 3.1.000 (2008-06-09) @@ -14072,13 +14134,13 @@ class TCPDF { /** * Paints color transition registration bars - * @param $x (float) abscissa of the top left corner of the rectangle. - * @param $y (float) ordinate of the top left corner of the rectangle. - * @param $w (float) width of the rectangle. - * @param $h (float) height of the rectangle. - * @param $transition (boolean) if true prints tcolor transitions to white. - * @param $vertical (boolean) if true prints bar vertically. - * @param $colors (string) colors to print separated by comma. Valid values are: A,W,R,G,B,C,M,Y,K,RGB,CMYK,ALL,ALLSPOT,. Where: A = grayscale black, W = grayscale white, R = RGB red, G RGB green, B RGB blue, C = CMYK cyan, M = CMYK magenta, Y = CMYK yellow, K = CMYK key/black, RGB = RGB registration color, CMYK = CMYK registration color, ALL = Spot registration color, ALLSPOT = print all defined spot colors, = name of the spot color to print. + * @param float $x abscissa of the top left corner of the rectangle. + * @param float $y ordinate of the top left corner of the rectangle. + * @param float $w width of the rectangle. + * @param float $h height of the rectangle. + * @param boolean $transition if true prints tcolor transitions to white. + * @param boolean $vertical if true prints bar vertically. + * @param string $colors colors to print separated by comma. Valid values are: A,W,R,G,B,C,M,Y,K,RGB,CMYK,ALL,ALLSPOT,. Where: A = grayscale black, W = grayscale white, R = RGB red, G RGB green, B RGB blue, C = CMYK cyan, M = CMYK magenta, Y = CMYK yellow, K = CMYK key/black, RGB = RGB registration color, CMYK = CMYK registration color, ALL = Spot registration color, ALLSPOT = print all defined spot colors, = name of the spot color to print. * @author Nicola Asuni * @since 4.9.000 (2010-03-26) * @public @@ -14201,7 +14263,7 @@ class TCPDF { // color gradient $this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords); } else { - $this->SetFillColorArray($col_b); + $this->setFillColorArray($col_b); // colored rectangle $this->Rect($xb, $yb, $wb, $hb, 'F', array()); } @@ -14213,18 +14275,18 @@ class TCPDF { /** * Paints crop marks. - * @param $x (float) abscissa of the crop mark center. - * @param $y (float) ordinate of the crop mark center. - * @param $w (float) width of the crop mark. - * @param $h (float) height of the crop mark. - * @param $type (string) type of crop mark, one symbol per type separated by comma: T = TOP, F = BOTTOM, L = LEFT, R = RIGHT, TL = A = TOP-LEFT, TR = B = TOP-RIGHT, BL = C = BOTTOM-LEFT, BR = D = BOTTOM-RIGHT. - * @param $color (array) crop mark color (default spot registration color). + * @param float $x abscissa of the crop mark center. + * @param float $y ordinate of the crop mark center. + * @param float $w width of the crop mark. + * @param float $h height of the crop mark. + * @param string $type type of crop mark, one symbol per type separated by comma: T = TOP, F = BOTTOM, L = LEFT, R = RIGHT, TL = A = TOP-LEFT, TR = B = TOP-RIGHT, BL = C = BOTTOM-LEFT, BR = D = BOTTOM-RIGHT. + * @param array $color crop mark color (default spot registration color). * @author Nicola Asuni * @since 4.9.000 (2010-03-26) * @public */ public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(100,100,100,100,'All')) { - $this->SetLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color)); + $this->setLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color)); $type = strtoupper($type); $type = preg_replace('/[^A-Z\-\,]*/', '', $type); // split type in single components @@ -14284,28 +14346,28 @@ class TCPDF { /** * Paints a registration mark - * @param $x (float) abscissa of the registration mark center. - * @param $y (float) ordinate of the registration mark center. - * @param $r (float) radius of the crop mark. - * @param $double (boolean) if true print two concentric crop marks. - * @param $cola (array) crop mark color (default spot registration color 'All'). - * @param $colb (array) second crop mark color (default spot registration color 'None'). + * @param float $x abscissa of the registration mark center. + * @param float $y ordinate of the registration mark center. + * @param float $r radius of the crop mark. + * @param boolean $double if true print two concentric crop marks. + * @param array $cola crop mark color (default spot registration color 'All'). + * @param array $colb second crop mark color (default spot registration color 'None'). * @author Nicola Asuni * @since 4.9.000 (2010-03-26) * @public */ public function registrationMark($x, $y, $r, $double=false, $cola=array(100,100,100,100,'All'), $colb=array(0,0,0,0,'None')) { $line_style = array('width' => max((0.5 / $this->k),($r / 30)), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola); - $this->SetFillColorArray($cola); + $this->setFillColorArray($cola); $this->PieSector($x, $y, $r, 90, 180, 'F'); $this->PieSector($x, $y, $r, 270, 360, 'F'); $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8); if ($double) { $ri = $r * 0.5; - $this->SetFillColorArray($colb); + $this->setFillColorArray($colb); $this->PieSector($x, $y, $ri, 90, 180, 'F'); $this->PieSector($x, $y, $ri, 270, 360, 'F'); - $this->SetFillColorArray($cola); + $this->setFillColorArray($cola); $this->PieSector($x, $y, $ri, 0, 90, 'F'); $this->PieSector($x, $y, $ri, 180, 270, 'F'); $this->Circle($x, $y, $ri, 0, 360, 'C', $line_style, array(), 8); @@ -14314,9 +14376,9 @@ class TCPDF { /** * Paints a CMYK registration mark - * @param $x (float) abscissa of the registration mark center. - * @param $y (float) ordinate of the registration mark center. - * @param $r (float) radius of the crop mark. + * @param float $x abscissa of the registration mark center. + * @param float $y ordinate of the registration mark center. + * @param float $r radius of the crop mark. * @author Nicola Asuni * @since 6.0.038 (2013-09-30) * @public @@ -14329,20 +14391,20 @@ class TCPDF { // external radius $re = ($r * 1.3); // Cyan - $this->SetFillColorArray(array(100,0,0,0)); + $this->setFillColorArray(array(100,0,0,0)); $this->PieSector($x, $y, $ri, 270, 360, 'F'); // Magenta - $this->SetFillColorArray(array(0,100,0,0)); + $this->setFillColorArray(array(0,100,0,0)); $this->PieSector($x, $y, $ri, 0, 90, 'F'); // Yellow - $this->SetFillColorArray(array(0,0,100,0)); + $this->setFillColorArray(array(0,0,100,0)); $this->PieSector($x, $y, $ri, 90, 180, 'F'); // Key - black - $this->SetFillColorArray(array(0,0,0,100)); + $this->setFillColorArray(array(0,0,0,100)); $this->PieSector($x, $y, $ri, 180, 270, 'F'); // registration color $line_style = array('width' => $lw, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(100,100,100,100,'All')); - $this->SetFillColorArray(array(100,100,100,100,'All')); + $this->setFillColorArray(array(100,100,100,100,'All')); // external circle $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8); // cross lines @@ -14354,13 +14416,13 @@ class TCPDF { /** * Paints a linear colour gradient. - * @param $x (float) abscissa of the top left corner of the rectangle. - * @param $y (float) ordinate of the top left corner of the rectangle. - * @param $w (float) width of the rectangle. - * @param $h (float) height of the rectangle. - * @param $col1 (array) first color (Grayscale, RGB or CMYK components). - * @param $col2 (array) second color (Grayscale, RGB or CMYK components). - * @param $coords (array) array of the form (x1, y1, x2, y2) which defines the gradient vector (see linear_gradient_coords.jpg). The default value is from left to right (x1=0, y1=0, x2=1, y2=0). + * @param float $x abscissa of the top left corner of the rectangle. + * @param float $y ordinate of the top left corner of the rectangle. + * @param float $w width of the rectangle. + * @param float $h height of the rectangle. + * @param array $col1 first color (Grayscale, RGB or CMYK components). + * @param array $col2 second color (Grayscale, RGB or CMYK components). + * @param array $coords array of the form (x1, y1, x2, y2) which defines the gradient vector (see linear_gradient_coords.jpg). The default value is from left to right (x1=0, y1=0, x2=1, y2=0). * @author Andreas W\FCrmser, Nicola Asuni * @since 3.1.000 (2008-06-09) * @public @@ -14372,13 +14434,13 @@ class TCPDF { /** * Paints a radial colour gradient. - * @param $x (float) abscissa of the top left corner of the rectangle. - * @param $y (float) ordinate of the top left corner of the rectangle. - * @param $w (float) width of the rectangle. - * @param $h (float) height of the rectangle. - * @param $col1 (array) first color (Grayscale, RGB or CMYK components). - * @param $col2 (array) second color (Grayscale, RGB or CMYK components). - * @param $coords (array) array of the form (fx, fy, cx, cy, r) where (fx, fy) is the starting point of the gradient with color1, (cx, cy) is the center of the circle with color2, and r is the radius of the circle (see radial_gradient_coords.jpg). (fx, fy) should be inside the circle, otherwise some areas will not be defined. + * @param float $x abscissa of the top left corner of the rectangle. + * @param float $y ordinate of the top left corner of the rectangle. + * @param float $w width of the rectangle. + * @param float $h height of the rectangle. + * @param array $col1 first color (Grayscale, RGB or CMYK components). + * @param array $col2 second color (Grayscale, RGB or CMYK components). + * @param array $coords array of the form (fx, fy, cx, cy, r) where (fx, fy) is the starting point of the gradient with color1, (cx, cy) is the center of the circle with color2, and r is the radius of the circle (see radial_gradient_coords.jpg). (fx, fy) should be inside the circle, otherwise some areas will not be defined. * @author Andreas W\FCrmser, Nicola Asuni * @since 3.1.000 (2008-06-09) * @public @@ -14390,24 +14452,24 @@ class TCPDF { /** * Paints a coons patch mesh. - * @param $x (float) abscissa of the top left corner of the rectangle. - * @param $y (float) ordinate of the top left corner of the rectangle. - * @param $w (float) width of the rectangle. - * @param $h (float) height of the rectangle. - * @param $col1 (array) first color (lower left corner) (RGB components). - * @param $col2 (array) second color (lower right corner) (RGB components). - * @param $col3 (array) third color (upper right corner) (RGB components). - * @param $col4 (array) fourth color (upper left corner) (RGB components). - * @param $coords (array)
              • for one patch mesh: array(float x1, float y1, .... float x12, float y12): 12 pairs of coordinates (normally from 0 to 1) which specify the Bezier control points that define the patch. First pair is the lower left edge point, next is its right control point (control point 2). Then the other points are defined in the order: control point 1, edge point, control point 2 going counter-clockwise around the patch. Last (x12, y12) is the first edge point's left control point (control point 1).
              • for two or more patch meshes: array[number of patches]: arrays with the following keys for each patch: f: where to put that patch (0 = first patch, 1, 2, 3 = right, top and left of precedent patch - I didn't figure this out completely - just try and error ;-) points: 12 pairs of coordinates of the Bezier control points as above for the first patch, 8 pairs of coordinates for the following patches, ignoring the coordinates already defined by the precedent patch (I also didn't figure out the order of these - also: try and see what's happening) colors: must be 4 colors for the first patch, 2 colors for the following patches
              - * @param $coords_min (array) minimum value used by the coordinates. If a coordinate's value is smaller than this it will be cut to coords_min. default: 0 - * @param $coords_max (array) maximum value used by the coordinates. If a coordinate's value is greater than this it will be cut to coords_max. default: 1 - * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts. + * @param float $x abscissa of the top left corner of the rectangle. + * @param float $y ordinate of the top left corner of the rectangle. + * @param float $w width of the rectangle. + * @param float $h height of the rectangle. + * @param array $col1 first color (lower left corner) (RGB components). + * @param array $col2 second color (lower right corner) (RGB components). + * @param array $col3 third color (upper right corner) (RGB components). + * @param array $col4 fourth color (upper left corner) (RGB components). + * @param array $coords
              • for one patch mesh: array(float x1, float y1, .... float x12, float y12): 12 pairs of coordinates (normally from 0 to 1) which specify the Bezier control points that define the patch. First pair is the lower left edge point, next is its right control point (control point 2). Then the other points are defined in the order: control point 1, edge point, control point 2 going counter-clockwise around the patch. Last (x12, y12) is the first edge point's left control point (control point 1).
              • for two or more patch meshes: array[number of patches]: arrays with the following keys for each patch: f: where to put that patch (0 = first patch, 1, 2, 3 = right, top and left of precedent patch - I didn't figure this out completely - just try and error ;-) points: 12 pairs of coordinates of the Bezier control points as above for the first patch, 8 pairs of coordinates for the following patches, ignoring the coordinates already defined by the precedent patch (I also didn't figure out the order of these - also: try and see what's happening) colors: must be 4 colors for the first patch, 2 colors for the following patches
              + * @param array $coords_min minimum value used by the coordinates. If a coordinate's value is smaller than this it will be cut to coords_min. default: 0 + * @param array $coords_max maximum value used by the coordinates. If a coordinate's value is greater than this it will be cut to coords_max. default: 1 + * @param boolean $antialias A flag indicating whether to filter the shading function to prevent aliasing artifacts. * @author Andreas W\FCrmser, Nicola Asuni * @since 3.1.000 (2008-06-09) * @public */ public function CoonsPatchMesh($x, $y, $w, $h, $col1=array(), $col2=array(), $col3=array(), $col4=array(), $coords=array(0.00,0.0,0.33,0.00,0.67,0.00,1.00,0.00,1.00,0.33,1.00,0.67,1.00,1.00,0.67,1.00,0.33,1.00,0.00,1.00,0.00,0.67,0.00,0.33), $coords_min=0, $coords_max=1, $antialias=false) { - if ($this->pdfa_mode OR ($this->state != 2)) { + if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) { return; } $this->Clip($x, $y, $w, $h); @@ -14467,8 +14529,8 @@ class TCPDF { if ($patch_array[$i]['points'][$j] > $bpcd) { $patch_array[$i]['points'][$j] = $bpcd; } - $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256)); - $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] % 256)); + $this->gradients[$n]['stream'] .= chr((int) floor($patch_array[$i]['points'][$j] / 256)); + $this->gradients[$n]['stream'] .= chr((int) floor(intval($patch_array[$i]['points'][$j]) % 256)); } $count_cols = count($patch_array[$i]['colors']); for ($j=0; $j < $count_cols; ++$j) { @@ -14490,10 +14552,10 @@ class TCPDF { /** * Set a rectangular clipping area. - * @param $x (float) abscissa of the top left corner of the rectangle (or top right corner for RTL mode). - * @param $y (float) ordinate of the top left corner of the rectangle. - * @param $w (float) width of the rectangle. - * @param $h (float) height of the rectangle. + * @param float $x abscissa of the top left corner of the rectangle (or top right corner for RTL mode). + * @param float $y ordinate of the top left corner of the rectangle. + * @param float $w width of the rectangle. + * @param float $h height of the rectangle. * @author Andreas W\FCrmser, Nicola Asuni * @since 3.1.000 (2008-06-09) * @protected @@ -14516,17 +14578,17 @@ class TCPDF { /** * Output gradient. - * @param $type (int) type of gradient (1 Function-based shading; 2 Axial shading; 3 Radial shading; 4 Free-form Gouraud-shaded triangle mesh; 5 Lattice-form Gouraud-shaded triangle mesh; 6 Coons patch mesh; 7 Tensor-product patch mesh). (Not all types are currently supported) - * @param $coords (array) array of coordinates. - * @param $stops (array) array gradient color components: color = array of GRAY, RGB or CMYK color components; offset = (0 to 1) represents a location along the gradient vector; exponent = exponent of the exponential interpolation function (default = 1). - * @param $background (array) An array of colour components appropriate to the colour space, specifying a single background colour value. - * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts. + * @param int $type type of gradient (1 Function-based shading; 2 Axial shading; 3 Radial shading; 4 Free-form Gouraud-shaded triangle mesh; 5 Lattice-form Gouraud-shaded triangle mesh; 6 Coons patch mesh; 7 Tensor-product patch mesh). (Not all types are currently supported) + * @param array $coords array of coordinates. + * @param array $stops array gradient color components: color = array of GRAY, RGB or CMYK color components; offset = (0 to 1) represents a location along the gradient vector; exponent = exponent of the exponential interpolation function (default = 1). + * @param array $background An array of colour components appropriate to the colour space, specifying a single background colour value. + * @param boolean $antialias A flag indicating whether to filter the shading function to prevent aliasing artifacts. * @author Nicola Asuni * @since 3.1.000 (2008-06-09) * @public */ public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) { - if ($this->pdfa_mode OR ($this->state != 2)) { + if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) { return; } $n = count($this->gradients) + 1; @@ -14582,7 +14644,7 @@ class TCPDF { } if (isset($stop['opacity'])) { $this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity']; - if ((!$this->pdfa_mode) AND ($stop['opacity'] < 1)) { + if ((!($this->pdfa_mode && $this->pdfa_version < 2)) AND ($stop['opacity'] < 1)) { $this->gradients[$n]['transparency'] = true; } } else { @@ -14633,7 +14695,7 @@ class TCPDF { * @protected */ function _putshaders() { - if ($this->pdfa_mode) { + if ($this->pdfa_mode && $this->pdfa_version < 2) { return; } $idt = count($this->gradients); //index for transparency gradients @@ -14812,14 +14874,14 @@ class TCPDF { /** * Draw the sector of a circle. * It can be used for instance to render pie charts. - * @param $xc (float) abscissa of the center. - * @param $yc (float) ordinate of the center. - * @param $r (float) radius. - * @param $a (float) start angle (in degrees). - * @param $b (float) end angle (in degrees). - * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information. - * @param $cw: (float) indicates whether to go clockwise (default: true). - * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock). Default: 90. + * @param float $xc abscissa of the center. + * @param float $yc ordinate of the center. + * @param float $r radius. + * @param float $a start angle (in degrees). + * @param float $b end angle (in degrees). + * @param string $style Style of rendering. See the getPathPaintOperator() function for more information. + * @param float $cw indicates whether to go clockwise (default: true). + * @param float $o origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock). Default: 90. * @author Maxime Delorme, Nicola Asuni * @since 3.1.000 (2008-06-09) * @public @@ -14831,16 +14893,16 @@ class TCPDF { /** * Draw the sector of an ellipse. * It can be used for instance to render pie charts. - * @param $xc (float) abscissa of the center. - * @param $yc (float) ordinate of the center. - * @param $rx (float) the x-axis radius. - * @param $ry (float) the y-axis radius. - * @param $a (float) start angle (in degrees). - * @param $b (float) end angle (in degrees). - * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information. - * @param $cw: (float) indicates whether to go clockwise. - * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock). - * @param $nc (integer) Number of curves used to draw a 90 degrees portion of arc. + * @param float $xc abscissa of the center. + * @param float $yc ordinate of the center. + * @param float $rx the x-axis radius. + * @param float $ry the y-axis radius. + * @param float $a start angle (in degrees). + * @param float $b end angle (in degrees). + * @param string $style Style of rendering. See the getPathPaintOperator() function for more information. + * @param float $cw indicates whether to go clockwise. + * @param float $o origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock). + * @param integer $nc Number of curves used to draw a 90 degrees portion of arc. * @author Maxime Delorme, Nicola Asuni * @since 3.1.000 (2008-06-09) * @public @@ -14873,23 +14935,23 @@ class TCPDF { * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library. * Only vector drawing is supported, not text or bitmap. * Although the script was successfully tested with various AI format versions, best results are probably achieved with files that were exported in the AI3 format (tested with Illustrator CS2, Freehand MX and Photoshop CS2). - * @param $file (string) Name of the file containing the image or a '@' character followed by the EPS/AI data string. - * @param $x (float) Abscissa of the upper-left corner. - * @param $y (float) Ordinate of the upper-left corner. - * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated. - * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated. - * @param $link (mixed) URL or identifier returned by AddLink(). - * @param $useBoundingBox (boolean) specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true. - * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:
              • T: top-right for LTR or top-left for RTL
              • M: middle-right for LTR or middle-left for RTL
              • B: bottom-right for LTR or bottom-left for RTL
              • N: next line
              - * @param $palign (string) Allows to center or align the image on the current line. Possible values are:
              • L : left align
              • C : center
              • R : right align
              • '' : empty string : left for LTR or right for RTL
              - * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) - * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions. - * @param $fixoutvals (boolean) if true remove values outside the bounding box. + * @param string $file Name of the file containing the image or a '@' character followed by the EPS/AI data string. + * @param float|null $x Abscissa of the upper-left corner. + * @param float|null $y Ordinate of the upper-left corner. + * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated. + * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated. + * @param mixed $link URL or identifier returned by AddLink(). + * @param boolean $useBoundingBox specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true. + * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:
              • T: top-right for LTR or top-left for RTL
              • M: middle-right for LTR or middle-left for RTL
              • B: bottom-right for LTR or bottom-left for RTL
              • N: next line
              + * @param string $palign Allows to center or align the image on the current line. Possible values are:
              • L : left align
              • C : center
              • R : right align
              • '' : empty string : left for LTR or right for RTL
              + * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:
              • 0: no border (default)
              • 1: frame
              or a string containing some or all of the following characters (in any order):
              • L: left
              • T: top
              • R: right
              • B: bottom
              or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) + * @param boolean $fitonpage if true the image is resized to not exceed page dimensions. + * @param boolean $fixoutvals if true remove values outside the bounding box. * @author Valentin Schmidt, Nicola Asuni * @since 3.1.000 (2008-06-09) * @public */ - public function ImageEps($file, $x='', $y='', $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) { + public function ImageEps($file, $x=null, $y=null, $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) { if ($this->state != 2) { return; } @@ -14897,10 +14959,10 @@ class TCPDF { // convert EPS to raster image using GD or ImageMagick libraries return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage); } - if ($x === '') { + if (TCPDF_STATIC::empty_string($x)) { $x = $this->x; } - if ($y === '') { + if (TCPDF_STATIC::empty_string($y)) { $y = $this->y; } // check page for no-write regions and adapt page margins if necessary @@ -14909,7 +14971,7 @@ class TCPDF { if ($file[0] === '@') { // image from string $data = substr($file, 1); } else { // EPS/AI file - $data = TCPDF_STATIC::fileGetContents($file); + $data = $this->getCachedFileContents($file); } if ($data === FALSE) { $this->Error('EPS file not found: '.$file); @@ -15193,7 +15255,7 @@ class TCPDF { break; } case 'N':{ - $this->SetY($this->img_rb_y); + $this->setY($this->img_rb_y); break; } default:{ @@ -15205,7 +15267,7 @@ class TCPDF { /** * Set document barcode. - * @param $bc (string) barcode + * @param string $bc barcode * @public */ public function setBarcode($bc='') { @@ -15224,14 +15286,14 @@ class TCPDF { /** * Print a Linear Barcode. - * @param $code (string) code to print - * @param $type (string) type of barcode (see tcpdf_barcodes_1d.php for supported formats). - * @param $x (int) x position in user units (empty string = current x position) - * @param $y (int) y position in user units (empty string = current y position) - * @param $w (int) width in user units (empty string = remaining page width) - * @param $h (int) height in user units (empty string = remaining page height) - * @param $xres (float) width of the smallest bar in user units (empty string = default value = 0.4mm) - * @param $style (array) array of options:
                + * @param string $code code to print + * @param string $type type of barcode (see tcpdf_barcodes_1d.php for supported formats). + * @param float|null $x x position in user units (null = current x position) + * @param float|null $y y position in user units (null = current y position) + * @param float|null $w width in user units (null = remaining page width) + * @param float|null $h height in user units (null = remaining page height) + * @param float|null $xres width of the smallest bar in user units (null = default value = 0.4mm) + * @param array $style array of options:
                  *
                • boolean $style['border'] if true prints a border
                • *
                • int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)
                • *
                • int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)
                • @@ -15247,12 +15309,12 @@ class TCPDF { *
                • string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.
                • *
                • string $style['fitwidth'] if true reduce the width to fit the barcode width + padding. When this option is enabled the 'stretch' option is automatically disabled.
                • *
                • string $style['cellfitalign'] this option works only when 'fitwidth' is true and 'position' is unset or empty. Set the horizontal position of the containing barcode cell inside the specified rectangle: L = left; C = center; R = right.
                - * @param $align (string) Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:
                • T: top-right for LTR or top-left for RTL
                • M: middle-right for LTR or middle-left for RTL
                • B: bottom-right for LTR or bottom-left for RTL
                • N: next line
                + * @param string $align Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:
                • T: top-right for LTR or top-left for RTL
                • M: middle-right for LTR or middle-left for RTL
                • B: bottom-right for LTR or bottom-left for RTL
                • N: next line
                * @author Nicola Asuni * @since 3.1.000 (2008-06-09) * @public */ - public function write1DBarcode($code, $type, $x='', $y='', $w='', $h='', $xres='', $style=array(), $align='') { + public function write1DBarcode($code, $type, $x=null, $y=null, $w=null, $h=null, $xres=null, $style=array(), $align='') { if (TCPDF_STATIC::empty_string(trim($code))) { return; } @@ -15262,7 +15324,7 @@ class TCPDF { // create new barcode object $barcodeobj = new TCPDFBarcode($code, $type); $arrcode = $barcodeobj->getBarcodeArray(); - if (($arrcode === false) OR empty($arrcode) OR ($arrcode['maxw'] <= 0)) { + if (empty($arrcode) OR ($arrcode['maxw'] <= 0)) { $this->Error('Error in 1D barcode string'); } if ($arrcode['maxh'] <= 0) { @@ -15311,20 +15373,20 @@ class TCPDF { if (isset($style['fontsize'])) { $fontsize = $style['fontsize']; } - $this->SetFont($style['font'], '', $fontsize); + $this->setFont($style['font'], '', $fontsize); } if (!isset($style['stretchtext'])) { $style['stretchtext'] = 4; } - if ($x === '') { + if (TCPDF_STATIC::empty_string($x)) { $x = $this->x; } - if ($y === '') { + if (TCPDF_STATIC::empty_string($y)) { $y = $this->y; } // check page for no-write regions and adapt page margins if necessary list($x, $y) = $this->checkPageRegions($h, $x, $y); - if (($w === '') OR ($w <= 0)) { + if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) { if ($this->rtl) { $w = $x - $this->lMargin; } else { @@ -15408,7 +15470,7 @@ class TCPDF { } $text_height = $this->getCellHeight($fontsize / $this->k); // height - if (($h === '') OR ($h <= 0)) { + if (TCPDF_STATIC::empty_string($h) OR ($h <= 0)) { // set default height $h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height; } @@ -15418,7 +15480,7 @@ class TCPDF { if ($text_height > $h) { $fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio)); $text_height = $this->getCellHeight($fontsize / $this->k); - $this->SetFont($style['font'], '', $fontsize); + $this->setFont($style['font'], '', $fontsize); } if ($vpadding > 0) { $vpadding = (($h - $text_height) / 4); @@ -15483,8 +15545,8 @@ class TCPDF { $this->Rect($xpos_rect, $y, $w, $h, 'D'); } // set foreground color - $this->SetDrawColorArray($style['fgcolor']); - $this->SetTextColorArray($style['fgcolor']); + $this->setDrawColorArray($style['fgcolor']); + $this->setTextColorArray($style['fgcolor']); // print bars foreach ($arrcode['bcode'] as $k => $v) { $bw = ($v['w'] * $xres); @@ -15510,8 +15572,8 @@ class TCPDF { $this->x = $xpos_text; $this->y = $y + $vpadding + $barh; $cellpadding = $this->cell_padding; - $this->SetCellPadding(0); - $this->Cell($txtwidth, '', $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T'); + $this->setCellPadding(0); + $this->Cell($txtwidth, 0, $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T'); $this->cell_padding = $cellpadding; } // restore original direction @@ -15536,7 +15598,7 @@ class TCPDF { break; } case 'N':{ - $this->SetY($this->img_rb_y); + $this->setY($this->img_rb_y); break; } default:{ @@ -15548,13 +15610,13 @@ class TCPDF { /** * Print 2D Barcode. - * @param $code (string) code to print - * @param $type (string) type of barcode (see tcpdf_barcodes_2d.php for supported formats). - * @param $x (int) x position in user units - * @param $y (int) y position in user units - * @param $w (int) width in user units - * @param $h (int) height in user units - * @param $style (array) array of options:
                  + * @param string $code code to print + * @param string $type type of barcode (see tcpdf_barcodes_2d.php for supported formats). + * @param float|null $x x position in user units + * @param float|null $y y position in user units + * @param float|null $w width in user units + * @param float|null $h height in user units + * @param array $style array of options:
                    *
                  • boolean $style['border'] if true prints a border around the barcode
                  • *
                  • int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)
                  • *
                  • int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)
                  • @@ -15564,13 +15626,13 @@ class TCPDF { *
                  • array $style['fgcolor'] color array for bars and text
                  • *
                  • mixed $style['bgcolor'] color array for background or false for transparent
                  • *
                  • string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch
                  • - * @param $align (string) Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:
                    • T: top-right for LTR or top-left for RTL
                    • M: middle-right for LTR or middle-left for RTL
                    • B: bottom-right for LTR or bottom-left for RTL
                    • N: next line
                    - * @param $distort (boolean) if true distort the barcode to fit width and height, otherwise preserve aspect ratio + * @param string $align Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:
                    • T: top-right for LTR or top-left for RTL
                    • M: middle-right for LTR or middle-left for RTL
                    • B: bottom-right for LTR or bottom-left for RTL
                    • N: next line
                    + * @param boolean $distort if true distort the barcode to fit width and height, otherwise preserve aspect ratio * @author Nicola Asuni * @since 4.5.037 (2009-04-07) * @public */ - public function write2DBarcode($code, $type, $x='', $y='', $w='', $h='', $style=array(), $align='', $distort=false) { + public function write2DBarcode($code, $type, $x=null, $y=null, $w=null, $h=null, $style=array(), $align='', $distort=false) { if (TCPDF_STATIC::empty_string(trim($code))) { return; } @@ -15580,7 +15642,7 @@ class TCPDF { // create new barcode object $barcodeobj = new TCPDF2DBarcode($code, $type); $arrcode = $barcodeobj->getBarcodeArray(); - if (($arrcode === false) OR empty($arrcode) OR !isset($arrcode['num_rows']) OR ($arrcode['num_rows'] == 0) OR !isset($arrcode['num_cols']) OR ($arrcode['num_cols'] == 0)) { + if (empty($arrcode) OR !isset($arrcode['num_rows']) OR ($arrcode['num_rows'] == 0) OR !isset($arrcode['num_cols']) OR ($arrcode['num_cols'] == 0)) { $this->Error('Error in 2D barcode string'); } // set default values @@ -15621,10 +15683,10 @@ class TCPDF { if (!isset($style['module_height'])) { $style['module_height'] = 1; // height of a single module in points } - if ($x === '') { + if (TCPDF_STATIC::empty_string($x)) { $x = $this->x; } - if ($y === '') { + if (TCPDF_STATIC::empty_string($y)) { $y = $this->y; } // check page for no-write regions and adapt page margins if necessary @@ -15666,7 +15728,7 @@ class TCPDF { $h = $maxh; } // set dimensions - if ((($w === '') OR ($w <= 0)) AND (($h === '') OR ($h <= 0))) { + if ((TCPDF_STATIC::empty_string($w) OR ($w <= 0)) AND (TCPDF_STATIC::empty_string($h) OR ($h <= 0))) { $w = ($cols + $hpad) * ($mw / $this->k); $h = ($rows + $vpad) * ($mh / $this->k); } elseif (($w === '') OR ($w <= 0)) { @@ -15733,7 +15795,7 @@ class TCPDF { $this->Rect($xpos, $y, $w, $h, 'D'); } // set foreground color - $this->SetDrawColorArray($style['fgcolor']); + $this->setDrawColorArray($style['fgcolor']); // print barcode cells // for each row for ($r = 0; $r < $rows; ++$r) { @@ -15770,7 +15832,7 @@ class TCPDF { break; } case 'N':{ - $this->SetY($this->img_rb_y); + $this->setY($this->img_rb_y); break; } default:{ @@ -15836,7 +15898,7 @@ class TCPDF { /** * Returns the current font size. - * @return current font size + * @return float current font size * @public * @since 3.2.000 (2008-06-23) */ @@ -15846,7 +15908,7 @@ class TCPDF { /** * Returns the current font size in points unit. - * @return current font size in points unit + * @return int current font size in points unit * @public * @since 3.2.000 (2008-06-23) */ @@ -15876,23 +15938,23 @@ class TCPDF { /** * Cleanup HTML code (requires HTML Tidy library). - * @param $html (string) htmlcode to fix - * @param $default_css (string) CSS commands to add - * @param $tagvs (array) parameters for setHtmlVSpace method - * @param $tidy_options (array) options for tidy_parse_string function + * @param string $html htmlcode to fix + * @param string $default_css CSS commands to add + * @param array|null $tagvs parameters for setHtmlVSpace method + * @param array|null $tidy_options options for tidy_parse_string function * @return string XHTML code cleaned up * @author Nicola Asuni * @public * @since 5.9.017 (2010-11-16) * @see setHtmlVSpace() */ - public function fixHTMLCode($html, $default_css='', $tagvs='', $tidy_options='') { + public function fixHTMLCode($html, $default_css='', $tagvs=null, $tidy_options=null) { return TCPDF_STATIC::fixHTMLCode($html, $default_css, $tagvs, $tidy_options, $this->tagvspaces); } /** * Returns the border width from CSS property - * @param $width (string) border width + * @param string $width border width * @return int with in user units * @protected * @since 5.7.000 (2010-08-02) @@ -15912,7 +15974,7 @@ class TCPDF { /** * Returns the border dash style from CSS property - * @param $style (string) border style to convert + * @param string $style border style to convert * @return int sash style (return -1 in case of none or hidden border) * @protected * @since 5.7.000 (2010-08-02) @@ -15948,7 +16010,7 @@ class TCPDF { /** * Returns the border style array from CSS border properties - * @param $cssborder (string) border properties + * @param string $cssborder border properties * @return array containing border properties * @protected * @since 5.7.000 (2010-08-02) @@ -15998,8 +16060,8 @@ class TCPDF { /** * Get the internal Cell padding from CSS attribute. - * @param $csspadding (string) padding properties - * @param $width (float) width of the containing element + * @param string $csspadding padding properties + * @param float $width width of the containing element * @return array of cell paddings * @public * @since 5.9.000 (2010-10-04) @@ -16052,8 +16114,8 @@ class TCPDF { /** * Get the internal Cell margin from CSS attribute. - * @param $cssmargin (string) margin properties - * @param $width (float) width of the containing element + * @param string $cssmargin margin properties + * @param float $width width of the containing element * @return array of cell margins * @public * @since 5.9.000 (2010-10-04) @@ -16106,8 +16168,8 @@ class TCPDF { /** * Get the border-spacing from CSS attribute. - * @param $cssbspace (string) border-spacing CSS properties - * @param $width (float) width of the containing element + * @param string $cssbspace border-spacing CSS properties + * @param float $width width of the containing element * @return array of border spacings * @public * @since 5.9.010 (2010-10-27) @@ -16140,8 +16202,8 @@ class TCPDF { /** * Returns the letter-spacing value from CSS value - * @param $spacing (string) letter-spacing value - * @param $parent (float) font spacing (tracking) value of the parent element + * @param string $spacing letter-spacing value + * @param float $parent font spacing (tracking) value of the parent element * @return float quantity to increases or decreases the space between characters in a text. * @protected * @since 5.9.000 (2010-10-02) @@ -16171,8 +16233,8 @@ class TCPDF { /** * Returns the percentage of font stretching from CSS value - * @param $stretch (string) stretch mode - * @param $parent (float) stretch value of the parent element + * @param string $stretch stretch mode + * @param float $parent stretch value of the parent element * @return float font stretching percentage * @protected * @since 5.9.000 (2010-10-02) @@ -16242,10 +16304,10 @@ class TCPDF { /** * Convert HTML string containing font size value to points - * @param $val (string) String containing font size value and unit. - * @param $refsize (float) Reference font size in points. - * @param $parent_size (float) Parent font size in points. - * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt). + * @param string $val String containing font size value and unit. + * @param float $refsize Reference font size in points. + * @param float $parent_size Parent font size in points. + * @param string $defaultunit Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt). * @return float value in points * @public */ @@ -16298,7 +16360,7 @@ class TCPDF { /** * Returns the HTML DOM array. - * @param $html (string) html code + * @param string $html html code * @return array * @protected * @since 3.2.000 (2008-06-20) @@ -16308,15 +16370,15 @@ class TCPDF { $css = array(); // get CSS array defined at previous call $matches = array(); - if (preg_match_all('/([^\<]*)<\/cssarray>/isU', $html, $matches) > 0) { + if (preg_match_all('/([^\<]*?)<\/cssarray>/is', $html, $matches) > 0) { if (isset($matches[1][0])) { $css = array_merge($css, json_decode($this->unhtmlentities($matches[1][0]), true)); } - $html = preg_replace('/(.*?)<\/cssarray>/isU', '', $html); + $html = preg_replace('/(.*?)<\/cssarray>/is', '', $html); } // extract external CSS files $matches = array(); - if (preg_match_all('/]*)>/isU', $html, $matches) > 0) { + if (preg_match_all('/]*?)>/is', $html, $matches) > 0) { foreach ($matches[1] as $key => $link) { $type = array(); if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) { @@ -16328,7 +16390,7 @@ class TCPDF { $type = array(); if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) { // read CSS data file - $cssdata = TCPDF_STATIC::fileGetContents(trim($type[1])); + $cssdata = $this->getCachedFileContents(trim($type[1])); if (($cssdata !== FALSE) AND (strlen($cssdata) > 0)) { $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata)); } @@ -16339,7 +16401,7 @@ class TCPDF { } // extract style tags $matches = array(); - if (preg_match_all('/]*)>([^\<]*)<\/style>/isU', $html, $matches) > 0) { + if (preg_match_all('/]*?)>([^\<]*?)<\/style>/is', $html, $matches) > 0) { foreach ($matches[1] as $key => $media) { $type = array(); preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type); @@ -16354,8 +16416,8 @@ class TCPDF { // create a special tag to contain the CSS array (used for table content) $csstagarray = ''.htmlentities(json_encode($css)).''; // remove head and style blocks - $html = preg_replace('/]*)>(.*?)<\/head>/siU', '', $html); - $html = preg_replace('/]*)>([^\<]*)<\/style>/isU', '', $html); + $html = preg_replace('/]*?)>(.*?)<\/head>/is', '', $html); + $html = preg_replace('/]*?)>([^\<]*?)<\/style>/is', '', $html); // define block tags $blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td'); // define self-closing tags @@ -16549,7 +16611,11 @@ class TCPDF { $dom[($dom[$key]['parent'])]['content'] = str_replace('', '', $dom[($dom[$key]['parent'])]['content']); } // store header rows on a new table - if (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['thead'] === true)) { + if ( + ($dom[$key]['value'] === 'tr') + && !empty($dom[($dom[$key]['parent'])]['thead']) + && ($dom[($dom[$key]['parent'])]['thead'] === true) + ) { if (TCPDF_STATIC::empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) { $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']]; } @@ -16979,10 +17045,20 @@ class TCPDF { // rows on thead block are printed as a separate table } else { $dom[$key]['thead'] = false; + $parent = $dom[$key]['parent']; + + if (!isset($dom[$parent]['rows'])) { + $dom[$parent]['rows'] = 0; + } // store the number of rows on table element - ++$dom[($dom[$key]['parent'])]['rows']; + ++$dom[$parent]['rows']; + + if (!isset($dom[$parent]['trids'])) { + $dom[$parent]['trids'] = array(); + } + // store the TR elements IDs on table element - array_push($dom[($dom[$key]['parent'])]['trids'], $key); + array_push($dom[$parent]['trids'], $key); } } if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) { @@ -17075,6 +17151,7 @@ class TCPDF { } } } + $element = preg_replace("/&NBSP;/i", " ", $element); } $dom[$key]['value'] = stripslashes($this->unhtmlentities($element)); } @@ -17101,7 +17178,7 @@ class TCPDF { /** * Return an hash code used to ensure that the serialized data has been generated by this TCPDF instance. - * @param $data (string) serialized data + * @param string $data serialized data * @return string * @public static */ @@ -17111,7 +17188,7 @@ class TCPDF { /** * Serialize an array of parameters to be used with TCPDF tag in HTML code. - * @param $data (array) parameters array + * @param array $data parameters array * @return string containing serialized data * @public static */ @@ -17122,7 +17199,7 @@ class TCPDF { /** * Unserialize parameters to be used with TCPDF tag in HTML code. - * @param $data (string) serialized data + * @param string $data serialized data * @return array containing unserialized data * @protected static */ @@ -17142,18 +17219,18 @@ class TCPDF { * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting. * Supported tags are: a, b, blockquote, br, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, img, li, ol, p, pre, small, span, strong, sub, sup, table, tcpdf, td, th, thead, tr, tt, u, ul * NOTE: all the HTML attributes must be enclosed in double-quote. - * @param $w (float) Cell width. If 0, the cell extends up to the right margin. - * @param $h (float) Cell minimum height. The cell extends automatically if needed. - * @param $x (float) upper-left corner X coordinate - * @param $y (float) upper-left corner Y coordinate - * @param $html (string) html text to print. Default value: empty string. - * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:
                    • 0: no border (default)
                    • 1: frame
                    or a string containing some or all of the following characters (in any order):
                    • L: left
                    • T: top
                    • R: right
                    • B: bottom
                    or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) - * @param $ln (int) Indicates where the current position should go after the call. Possible values are:
                    • 0: to the right (or left for RTL language)
                    • 1: to the beginning of the next line
                    • 2: below
                    + * @param float $w Cell width. If 0, the cell extends up to the right margin. + * @param float $h Cell minimum height. The cell extends automatically if needed. + * @param float|null $x upper-left corner X coordinate + * @param float|null $y upper-left corner Y coordinate + * @param string $html html text to print. Default value: empty string. + * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:
                    • 0: no border (default)
                    • 1: frame
                    or a string containing some or all of the following characters (in any order):
                    • L: left
                    • T: top
                    • R: right
                    • B: bottom
                    or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) + * @param int $ln Indicates where the current position should go after the call. Possible values are:
                    • 0: to the right (or left for RTL language)
                    • 1: to the beginning of the next line
                    • 2: below
                    Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0. - * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false). - * @param $reseth (boolean) if true reset the last cell height (default true). - * @param $align (string) Allows to center or align the text. Possible values are:
                    • L : left align
                    • C : center
                    • R : right align
                    • '' : empty string : left for LTR or right for RTL
                    - * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width. + * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false). + * @param boolean $reseth if true reset the last cell height (default true). + * @param string $align Allows to center or align the text. Possible values are:
                    • L : left align
                    • C : center
                    • R : right align
                    • '' : empty string : left for LTR or right for RTL
                    + * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width. * @see Multicell(), writeHTML() * @public */ @@ -17166,12 +17243,12 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting. * Supported tags are: a, b, blockquote, br, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, img, li, ol, p, pre, small, span, strong, sub, sup, table, tcpdf, td, th, thead, tr, tt, u, ul * NOTE: all the HTML attributes must be enclosed in double-quote. - * @param $html (string) text to display - * @param $ln (boolean) if true add a new line after text (default = true) - * @param $fill (boolean) Indicates if the background must be painted (true) or transparent (false). - * @param $reseth (boolean) if true reset the last cell height (default false). - * @param $cell (boolean) if true add the current left (or right for RTL) padding to each Write (default false). - * @param $align (string) Allows to center or align the text. Possible values are:
                    • L : left align
                    • C : center
                    • R : right align
                    • '' : empty string : left for LTR or right for RTL
                    + * @param string $html text to display + * @param boolean $ln if true add a new line after text (default = true) + * @param boolean $fill Indicates if the background must be painted (true) or transparent (false). + * @param boolean $reseth if true reset the last cell height (default false). + * @param boolean $cell if true add the current left (or right for RTL) padding to each Write (default false). + * @param string $align Allows to center or align the text. Possible values are:
                    • L : left align
                    • C : center
                    • R : right align
                    • '' : empty string : left for LTR or right for RTL
                    * @public */ public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') { @@ -17561,7 +17638,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: $this->cell_height_ratio = $dom[$key]['line-height']; $fontaligned = true; } - $this->SetFont($fontname, $fontstyle, $fontsize); + $this->setFont($fontname, $fontstyle, $fontsize); // reset row height $this->resetLastH(); $curfontname = $fontname; @@ -17588,16 +17665,16 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: // get current position on page buffer $curpos = $this->pagelen[$startlinepage]; if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) { - $this->SetFillColorArray($dom[$key]['bgcolor']); + $this->setFillColorArray($dom[$key]['bgcolor']); $wfill = true; } else { $wfill = $fill | false; } if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) { - $this->SetTextColorArray($dom[$key]['fgcolor']); + $this->setTextColorArray($dom[$key]['fgcolor']); } if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) { - $this->SetDrawColorArray($dom[$key]['strokecolor']); + $this->setDrawColorArray($dom[$key]['strokecolor']); } if (isset($dom[$key]['align'])) { $lalign = $dom[$key]['align']; @@ -18056,9 +18133,9 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table') AND (isset($this->emptypagemrk[$this->page])) AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) { - $this->SetFont($fontname, $fontstyle, $fontsize); + $this->setFont($fontname, $fontstyle, $fontsize); if ($wfill) { - $this->SetFillColorArray($this->bgcolor); + $this->setFillColorArray($this->bgcolor); } } } // end newline @@ -18358,14 +18435,14 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: } elseif (strlen($dom[$key]['value']) > 0) { // print list-item if (!TCPDF_STATIC::empty_string($this->lispacer) AND ($this->lispacer != '^')) { - $this->SetFont($pfontname, $pfontstyle, $pfontsize); + $this->setFont($pfontname, $pfontstyle, $pfontsize); $this->resetLastH(); $minstartliney = $this->y; $maxbottomliney = ($startliney + $this->getCellHeight($this->FontSize)); if (is_numeric($pfontsize) AND ($pfontsize > 0)) { $this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize); } - $this->SetFont($curfontname, $curfontstyle, $curfontsize); + $this->setFont($curfontname, $curfontstyle, $curfontsize); $this->resetLastH(); if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) { $pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize); @@ -18726,10 +18803,10 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Process opening tags. - * @param $dom (array) html dom array - * @param $key (int) current element id - * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false). - * @return $dom array + * @param array $dom html dom array + * @param int $key current element id + * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false). + * @return array $dom * @protected */ protected function openHTMLTagHandler($dom, $key, $cell) { @@ -18823,7 +18900,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: $dom[$key]['old_cell_padding'] = $this->cell_padding; if (isset($tag['attribute']['cellpadding'])) { $pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px'); - $this->SetCellPadding($pad); + $this->setCellPadding($pad); } elseif (isset($tag['padding'])) { $this->cell_padding = $tag['padding']; } @@ -18864,9 +18941,37 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: $hrWidth = $wtmp; } $prevlinewidth = $this->GetLineWidth(); - $this->SetLineWidth($hrHeight); - $this->Line($x, $y, $x + $hrWidth, $y); - $this->SetLineWidth($prevlinewidth); + $this->setLineWidth($hrHeight); + + $lineStyle = array(); + if (isset($tag['fgcolor'])) { + $lineStyle['color'] = $tag['fgcolor']; + } + + if (isset($tag['fgcolor'])) { + $lineStyle['color'] = $tag['fgcolor']; + } + + if (isset($tag['style']['cap'])) { + $lineStyle['cap'] = $tag['style']['cap']; + } + + if (isset($tag['style']['join'])) { + $lineStyle['join'] = $tag['style']['join']; + } + + if (isset($tag['style']['dash'])) { + $lineStyle['dash'] = $tag['style']['dash']; + } + + if (isset($tag['style']['phase'])) { + $lineStyle['phase'] = $tag['style']['phase']; + } + + $lineStyle = array_filter($lineStyle); + + $this->Line($x, $y, $x + $hrWidth, $y, $lineStyle); + $this->setLineWidth($prevlinewidth); $this->addHTMLVertSpace(max($hbc, ($hrHeight / 2)), 0, $cell, !isset($dom[($key + 1)])); break; } @@ -18885,7 +18990,11 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: // data stream $imgsrc = '@'.base64_decode(substr($imgsrc, 1)); $type = ''; - } else { + } elseif ( $this->allowLocalFiles && substr($imgsrc, 0, 7) === 'file://') { + // get image type from a local file path + $imgsrc = substr($imgsrc, 7); + $type = TCPDF_IMAGES::getImageFileType($imgsrc); + } else { if (($imgsrc[0] === '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) { // fix image path $findroot = strpos($imgsrc, $_SERVER['DOCUMENT_ROOT']); @@ -18957,7 +19066,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: $lnky = 0; } $imglink = $this->AddLink(); - $this->SetLink($imglink, $lnky, $page); + $this->setLink($imglink, $lnky, $page); } } } @@ -19111,11 +19220,11 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: break; } case 'sup': { - $this->SetXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k)); + $this->setXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k)); break; } case 'sub': { - $this->SetXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k)); + $this->setXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k)); break; } case 'h1': @@ -19417,11 +19526,11 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Process closing tags. - * @param $dom (array) html dom array - * @param $key (int) current element id - * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false). - * @param $maxbottomliney (int) maximum y value of current line - * @return $dom array + * @param array $dom html dom array + * @param int $key current element id + * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false). + * @param int $maxbottomliney maximum y value of current line + * @return array $dom * @protected */ protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) { @@ -19626,7 +19735,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: $border = $cellpos['border']; } if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) { - $this->SetFillColorArray($cellpos['bgcolor']); + $this->setFillColorArray($cellpos['bgcolor']); $fill = true; } else { $fill = false; @@ -19807,7 +19916,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: } } if (!$in_table_head) { // we are not inside a thead section - $this->cell_padding = $table_el['old_cell_padding']; + $this->cell_padding = isset($table_el['old_cell_padding']) ? $table_el['old_cell_padding'] : null; // reset row height $this->resetLastH(); if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) { @@ -19843,11 +19952,11 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: break; } case 'sup': { - $this->SetXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k)); + $this->setXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k)); break; } case 'sub': { - $this->SetXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k)); + $this->setXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k)); break; } case 'div': { @@ -19964,11 +20073,11 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Add vertical spaces if needed. - * @param $hbz (string) Distance between current y and line bottom. - * @param $hb (string) The height of the break. - * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false). - * @param $firsttag (boolean) set to true when the tag is the first. - * @param $lasttag (boolean) set to true when the tag is the last. + * @param string $hbz Distance between current y and line bottom. + * @param string $hb The height of the break. + * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false). + * @param boolean $firsttag set to true when the tag is the first. + * @param boolean $lasttag set to true when the tag is the last. * @protected */ protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) { @@ -20008,8 +20117,8 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Draw an HTML block border and fill - * @param $tag (array) array of tag properties. - * @param $xmax (int) end X coordinate for border. + * @param array $tag array of tag properties. + * @param int $xmax end X coordinate for border. * @protected * @since 5.7.000 (2010-08-03) */ @@ -20035,7 +20144,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) { // get background color $old_bgcolor = $this->bgcolor; - $this->SetFillColorArray($tag['bgcolor']); + $this->setFillColorArray($tag['bgcolor']); $fill = true; } if (!$border AND !$fill) { @@ -20201,7 +20310,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: $this->page_regions = $temp_page_regions; if (isset($old_bgcolor)) { // restore background color - $this->SetFillColorArray($old_bgcolor); + $this->setFillColorArray($old_bgcolor); } // restore pointer position $this->x = $prev_x; @@ -20211,7 +20320,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set the default bullet to be used as LI bullet symbol - * @param $symbol (string) character or string to be used (legal values are: '' = automatic, '!' = auto bullet, '#' = auto numbering, 'disc', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek', 'img|type|width|height|image.ext') + * @param string $symbol character or string to be used (legal values are: '' = automatic, '!' = auto bullet, '#' = auto numbering, 'disc', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek', 'img|type|width|height|image.ext') * @public * @since 4.0.028 (2008-09-26) */ @@ -20232,13 +20341,13 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set the booklet mode for double-sided pages. - * @param $booklet (boolean) true set the booklet mode on, false otherwise. - * @param $inner (float) Inner page margin. - * @param $outer (float) Outer page margin. + * @param boolean $booklet true set the booklet mode on, false otherwise. + * @param float $inner Inner page margin. + * @param float $outer Outer page margin. * @public * @since 4.2.000 (2008-10-29) */ - public function SetBooklet($booklet=true, $inner=-1, $outer=-1) { + public function setBooklet($booklet=true, $inner=-1, $outer=-1) { $this->booklet = $booklet; if ($inner >= 0) { $this->lMargin = $inner; @@ -20250,7 +20359,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Swap the left and right margins. - * @param $reverse (boolean) if true swap left and right margins. + * @param boolean $reverse if true swap left and right margins. * @protected * @since 4.2.000 (2008-10-29) */ @@ -20274,7 +20383,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: * the second level contains 0 for opening tags or 1 for closing tags, * the third level contains the vertical space unit (h) and the number spaces to add (n). * If the h parameter is not specified, default values are used. - * @param $tagvs (array) array of tags and relative vertical spaces. + * @param array $tagvs array of tags and relative vertical spaces. * @public * @since 4.2.001 (2008-10-30) */ @@ -20284,7 +20393,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set custom width for list indentation. - * @param $width (float) width of the indentation. Use negative value to disable it. + * @param float $width width of the indentation. Use negative value to disable it. * @public * @since 4.2.007 (2008-11-12) */ @@ -20294,7 +20403,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set the top/bottom cell sides to be open or closed when the cell cross the page. - * @param $isopen (boolean) if true keeps the top/bottom border open for the cell sides that cross the page. + * @param boolean $isopen if true keeps the top/bottom border open for the cell sides that cross the page. * @public * @since 4.2.010 (2008-11-14) */ @@ -20304,8 +20413,8 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set the color and font style for HTML links. - * @param $color (array) RGB array of colors - * @param $fontstyle (string) additional font styles to add + * @param array $color RGB array of colors + * @param string $fontstyle additional font styles to add * @public * @since 4.4.003 (2008-12-09) */ @@ -20316,10 +20425,10 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Convert HTML string containing value and unit of measure to user's units or points. - * @param $htmlval (string) String containing values and unit. - * @param $refsize (string) Reference value in points. - * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt). - * @param $points (boolean) If true returns points, otherwise returns value in user's units. + * @param string $htmlval String containing values and unit. + * @param string $refsize Reference value in points. + * @param string $defaultunit Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt). + * @param boolean $points If true returns points, otherwise returns value in user's units. * @return float value in user's unit or point if $points=true * @public * @since 4.4.004 (2008-12-10) @@ -20402,9 +20511,9 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Output an HTML list bullet or ordered item symbol - * @param $listdepth (int) list nesting level - * @param $listtype (string) type of list - * @param $size (float) current font size + * @param int $listdepth list nesting level + * @param string $listtype type of list + * @param float $size current font size * @protected * @since 4.4.004 (2008-12-10) */ @@ -20599,9 +20708,9 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: $this->x = $tmpx; $this->lispacer = '^'; // restore colors - $this->SetFillColorArray($bgcolor); - $this->SetDrawColorArray($strokecolor); - $this->SettextColorArray($color); + $this->setFillColorArray($bgcolor); + $this->setDrawColorArray($strokecolor); + $this->settextColorArray($color); } /** @@ -20666,8 +20775,8 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set graphic variables. - * @param $gvars (array) array of graphic variablesto restore - * @param $extended (boolean) if true restore extended graphic variables + * @param array $gvars array of graphic variablesto restore + * @param boolean $extended if true restore extended graphic variables * @protected * @since 4.2.010 (2008-11-14) */ @@ -20727,7 +20836,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: } $this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.''); if (!TCPDF_STATIC::empty_string($this->FontFamily)) { - $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt); + $this->setFont($this->FontFamily, $this->FontStyle, $this->FontSizePt); } } @@ -20749,7 +20858,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set buffer content (always append data). - * @param $data (string) data + * @param string $data data * @protected * @since 4.5.000 (2009-01-02) */ @@ -20760,7 +20869,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Replace the buffer content - * @param $data (string) data + * @param string $data data * @protected * @since 5.5.000 (2010-06-22) */ @@ -20781,9 +20890,9 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set page buffer content. - * @param $page (int) page number - * @param $data (string) page data - * @param $append (boolean) if true append data, false replace. + * @param int $page page number + * @param string $data page data + * @param boolean $append if true append data, false replace. * @protected * @since 4.5.000 (2008-12-31) */ @@ -20802,7 +20911,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Get page buffer content. - * @param $page (int) page number + * @param int $page page number * @return string page buffer content or false in case of error * @protected * @since 4.5.000 (2008-12-31) @@ -20816,8 +20925,8 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set image buffer content. - * @param $image (string) image key - * @param $data (array) image data + * @param string $image image key + * @param array $data image data * @return int image index number * @protected * @since 4.5.000 (2008-12-31) @@ -20834,9 +20943,9 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set image buffer content for a specified sub-key. - * @param $image (string) image key - * @param $key (string) image sub-key - * @param $data (array) image data + * @param string $image image key + * @param string $key image sub-key + * @param array $data image data * @protected * @since 4.5.000 (2008-12-31) */ @@ -20849,8 +20958,8 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Get image buffer content. - * @param $image (string) image key - * @return string image buffer content or false in case of error + * @param string $image image key + * @return string|false image buffer content or false in case of error * @protected * @since 4.5.000 (2008-12-31) */ @@ -20863,8 +20972,8 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set font buffer content. - * @param $font (string) font key - * @param $data (array) font data + * @param string $font font key + * @param array $data font data * @protected * @since 4.5.000 (2009-01-02) */ @@ -20881,9 +20990,9 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set font buffer content. - * @param $font (string) font key - * @param $key (string) font sub-key - * @param $data (array) font data + * @param string $font font key + * @param string $key font sub-key + * @param mixed $data font data * @protected * @since 4.5.000 (2009-01-02) */ @@ -20896,8 +21005,8 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Get font buffer content. - * @param $font (string) font key - * @return string font buffer content or false in case of error + * @param string $font font key + * @return string|false font buffer content or false in case of error * @protected * @since 4.5.000 (2009-01-02) */ @@ -20910,9 +21019,9 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Move a page to a previous position. - * @param $frompage (int) number of the source page - * @param $topage (int) number of the destination page (must be less than $frompage) - * @return true in case of success, false in case of error. + * @param int $frompage number of the source page + * @param int $topage number of the destination page (must be less than $frompage) + * @return bool true in case of success, false in case of error. * @public * @since 4.5.000 (2009-01-02) */ @@ -21082,8 +21191,8 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Remove the specified page. - * @param $page (int) page to remove - * @return true in case of success, false in case of error. + * @param int $page page to remove + * @return bool true in case of success, false in case of error. * @public * @since 4.6.004 (2009-04-23) */ @@ -21272,8 +21381,8 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Clone the specified page to a new page. - * @param $page (int) number of page to copy (0 = current page) - * @return true in case of success, false in case of error. + * @param int $page number of page to copy (0 = current page) + * @return bool true in case of success, false in case of error. * @public * @since 4.9.015 (2010-04-20) */ @@ -21343,18 +21452,18 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: * Before calling this method you have to open the page using the addTOCPage() method. * After calling this method you have to call endTOCPage() to close the TOC page. * You can override this method to achieve different styles. - * @param $page (int) page number where this TOC should be inserted (leave empty for current page). - * @param $numbersfont (string) set the font for page numbers (please use monospaced font for better alignment). - * @param $filler (string) string used to fill the space between text and page number. - * @param $toc_name (string) name to use for TOC bookmark. - * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic. - * @param $color (array) RGB color array for bookmark title (values from 0 to 255). + * @param int|null $page page number where this TOC should be inserted (leave empty for current page). + * @param string $numbersfont set the font for page numbers (please use monospaced font for better alignment). + * @param string $filler string used to fill the space between text and page number. + * @param string $toc_name name to use for TOC bookmark. + * @param string $style Font style for title: B = Bold, I = Italic, BI = Bold + Italic. + * @param array $color RGB color array for bookmark title (values from 0 to 255). * @public * @author Nicola Asuni * @since 4.5.000 (2009-01-02) * @see addTOCPage(), endTOCPage(), addHTMLTOC() */ - public function addTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) { + public function addTOC($page=null, $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) { $fontsize = $this->FontSizePt; $fontfamily = $this->FontFamily; $fontstyle = $this->FontStyle; @@ -21382,7 +21491,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: $page = 1; } } - $this->SetFont($numbersfont, $fontstyle, $fontsize); + $this->setFont($numbersfont, $fontstyle, $fontsize); $numwidth = $this->GetStringWidth('00000'); $maxpage = 0; //used for pages on attached documents foreach ($this->outlines as $key => $outline) { @@ -21398,11 +21507,11 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: $alignnum = 'R'; } if ($outline['l'] == 0) { - $this->SetFont($fontfamily, $outline['s'].'B', $fontsize); + $this->setFont($fontfamily, $outline['s'].'B', $fontsize); } else { - $this->SetFont($fontfamily, $outline['s'], $fontsize - $outline['l']); + $this->setFont($fontfamily, $outline['s'], $fontsize - $outline['l']); } - $this->SetTextColorArray($outline['c']); + $this->setTextColorArray($outline['c']); // check for page break $this->checkPageBreak(2 * $this->getCellHeight($this->FontSize)); // set margins and X position @@ -21422,7 +21531,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: $current_page = $this->page; $current_column = $this->current_column; } - $this->SetX($x_start); + $this->setX($x_start); $indent = ($spacer * $outline['l']); if ($this->rtl) { $this->x -= $indent; @@ -21432,7 +21541,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: $this->lMargin = $this->x; } $link = $this->AddLink(); - $this->SetLink($link, $outline['y'], $outline['p']); + $this->setLink($link, $outline['y'], $outline['p']); // write the text if ($this->rtl) { $txt = ' '.$outline['t']; @@ -21445,7 +21554,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: } else { $tw = $this->w - $this->rMargin - $this->x; } - $this->SetFont($numbersfont, $fontstyle, $fontsize); + $this->setFont($numbersfont, $fontstyle, $fontsize); if (TCPDF_STATIC::empty_string($page)) { $pagenum = $outline['p']; } else { @@ -21553,18 +21662,18 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: * This method must be called after all Bookmarks were set. * Before calling this method you have to open the page using the addTOCPage() method. * After calling this method you have to call endTOCPage() to close the TOC page. - * @param $page (int) page number where this TOC should be inserted (leave empty for current page). - * @param $toc_name (string) name to use for TOC bookmark. - * @param $templates (array) array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number. - * @param $correct_align (boolean) if true correct the number alignment (numbers must be in monospaced font like courier and right aligned on LTR, or left aligned on RTL) - * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic. - * @param $color (array) RGB color array for title (values from 0 to 255). + * @param int|null $page page number where this TOC should be inserted (leave empty for current page). + * @param string $toc_name name to use for TOC bookmark. + * @param array $templates array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number. + * @param boolean $correct_align if true correct the number alignment (numbers must be in monospaced font like courier and right aligned on LTR, or left aligned on RTL) + * @param string $style Font style for title: B = Bold, I = Italic, BI = Bold + Italic. + * @param array $color RGB color array for title (values from 0 to 255). * @public * @author Nicola Asuni * @since 5.0.001 (2010-05-06) * @see addTOCPage(), endTOCPage(), addTOC() */ - public function addHTMLTOC($page='', $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) { + public function addHTMLTOC($page=null, $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) { $filler = ' '; $prev_htmlLinkColorArray = $this->htmlLinkColorArray; $prev_htmlLinkFontStyle = $this->htmlLinkFontStyle; @@ -21580,12 +21689,12 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: $dom = $this->getHtmlDomArray($html); foreach ($dom as $key => $value) { if ($value['value'] == '#TOC_PAGE_NUMBER#') { - $this->SetFont($dom[($key - 1)]['fontname']); + $this->setFont($dom[($key - 1)]['fontname']); $templates['F'.$level] = $this->isUnicodeFont(); } } } - $this->SetFont($current_font); + $this->setFont($current_font); $maxpage = 0; //used for pages on attached documents foreach ($this->outlines as $key => $outline) { // get HTML template @@ -21717,27 +21826,37 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: public function commitTransaction() { if (isset($this->objcopy)) { $this->objcopy->_destroy(true, true); + /* The unique file_id should not be used during cleanup again */ + $this->objcopy->file_id = NULL; unset($this->objcopy); } } /** * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction(). - * @param $self (boolean) if true restores current class object to previous state without the need of reassignment via the returned value. + * @param boolean $self if true restores current class object to previous state without the need of reassignment via the returned value. * @return TCPDF object. * @public * @since 4.5.029 (2009-03-19) */ public function rollbackTransaction($self=false) { if (isset($this->objcopy)) { + $objcopy = $this->objcopy; $this->_destroy(true, true); if ($self) { - $objvars = get_object_vars($this->objcopy); + $objvars = get_object_vars($objcopy); foreach ($objvars as $key => $value) { $this->$key = $value; } + $objcopy->_destroy(true, true); + /* The unique file_id should not be used during cleanup again */ + $objcopy->file_id = NULL; + unset($objcopy); + return $this; } - return $this->objcopy; + /* The unique file_id should not be used during cleanup again */ + $this->file_id = NULL; + return $objcopy; } return $this; } @@ -21746,13 +21865,13 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set multiple columns of the same size - * @param $numcols (int) number of columns (set to zero to disable columns mode) - * @param $width (int) column width - * @param $y (int) column starting Y position (leave empty for current Y position) + * @param int $numcols number of columns (set to zero to disable columns mode) + * @param int $width column width + * @param int|null $y column starting Y position (leave empty for current Y position) * @public * @since 4.9.001 (2010-03-28) */ - public function setEqualColumns($numcols=0, $width=0, $y='') { + public function setEqualColumns($numcols=0, $width=0, $y=null) { $this->columns = array(); if ($numcols < 2) { $numcols = 0; @@ -21793,7 +21912,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set columns array. * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position). - * @param $columns (array) + * @param array $columns * @public * @since 4.9.001 (2010-03-28) */ @@ -21807,12 +21926,12 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set position at a given column - * @param $col (int) column number (from 0 to getNumberOfColumns()-1); empty string = current column. + * @param int|null $col column number (from 0 to getNumberOfColumns()-1); empty string = current column. * @public * @since 4.9.001 (2010-03-28) */ - public function selectColumn($col='') { - if (is_string($col)) { + public function selectColumn($col=null) { + if (TCPDF_STATIC::empty_string($col)) { $col = $this->current_column; } elseif ($col >= $this->num_columns) { $col = 0; @@ -21906,9 +22025,9 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set Text rendering mode. - * @param $stroke (int) outline size in user units (0 = disable). - * @param $fill (boolean) if true fills the text (default). - * @param $clip (boolean) if true activate clipping mode + * @param int $stroke outline size in user units (0 = disable). + * @param boolean $fill if true fills the text (default). + * @param boolean $clip if true activate clipping mode * @public * @since 4.9.008 (2009-04-02) */ @@ -21963,7 +22082,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set parameters for drop shadow effect for text. - * @param $params (array) Array of parameters: enabled (boolean) set to true to enable shadow; depth_w (float) shadow width in user units; depth_h (float) shadow height in user units; color (array) shadow color or false to use the stroke color; opacity (float) Alpha value: real value from 0 (transparent) to 1 (opaque); blend_mode (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity. + * @param array $params Array of parameters: enabled (boolean) set to true to enable shadow; depth_w (float) shadow width in user units; depth_h (float) shadow height in user units; color (array) shadow color or false to use the stroke color; opacity (float) Alpha value: real value from 0 (transparent) to 1 (opaque); blend_mode (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity. * @since 5.9.174 (2012-07-25) * @public */ @@ -22005,7 +22124,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Return the text shadow parameters array. - * @return Array of parameters. + * @return array array of parameters. * @since 5.9.174 (2012-07-25) * @public */ @@ -22015,13 +22134,13 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Returns an array of chars containing soft hyphens. - * @param $word (array) array of chars - * @param $patterns (array) Array of hypenation patterns. - * @param $dictionary (array) Array of words to be returned without applying the hyphenation algorithm. - * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens. - * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens. - * @param $charmin (int) Minimum word length to apply the hyphenation algorithm. - * @param $charmax (int) Maximum length of broken piece of word. + * @param array $word array of chars + * @param array $patterns Array of hypenation patterns. + * @param array $dictionary Array of words to be returned without applying the hyphenation algorithm. + * @param int $leftmin Minimum number of character to leave on the left of the word without applying the hyphens. + * @param int $rightmin Minimum number of character to leave on the right of the word without applying the hyphens. + * @param int $charmin Minimum word length to apply the hyphenation algorithm. + * @param int $charmax Maximum length of broken piece of word. * @return array text with soft hyphens * @author Nicola Asuni * @since 4.9.012 (2010-04-12) @@ -22095,14 +22214,14 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Returns text with soft hyphens. - * @param $text (string) text to process - * @param $patterns (mixed) Array of hypenation patterns or a TEX file containing hypenation patterns. TEX patterns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/ - * @param $dictionary (array) Array of words to be returned without applying the hyphenation algorithm. - * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens. - * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens. - * @param $charmin (int) Minimum word length to apply the hyphenation algorithm. - * @param $charmax (int) Maximum length of broken piece of word. - * @return array text with soft hyphens + * @param string $text text to process + * @param mixed $patterns Array of hypenation patterns or a TEX file containing hypenation patterns. TEX patterns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/ + * @param array $dictionary Array of words to be returned without applying the hyphenation algorithm. + * @param int $leftmin Minimum number of character to leave on the left of the word without applying the hyphens. + * @param int $rightmin Minimum number of character to leave on the right of the word without applying the hyphens. + * @param int $charmin Minimum word length to apply the hyphenation algorithm. + * @param int $charmax Maximum length of broken piece of word. + * @return string text with soft hyphens * @author Nicola Asuni * @since 4.9.012 (2010-04-12) * @public @@ -22167,7 +22286,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Enable/disable rasterization of vector images using ImageMagick library. - * @param $mode (boolean) if true enable rasterization, false otherwise. + * @param boolean $mode if true enable rasterization, false otherwise. * @public * @since 5.0.000 (2010-04-27) */ @@ -22177,7 +22296,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Enable or disable default option for font subsetting. - * @param $enable (boolean) if true enable font subsetting by default. + * @param boolean $enable if true enable font subsetting by default. * @author Nicola Asuni * @public * @since 5.3.002 (2010-06-07) @@ -22192,7 +22311,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Return the default option for font subsetting. - * @return boolean default font subsetting state. + * @return bool default font subsetting state. * @author Nicola Asuni * @public * @since 5.3.002 (2010-06-07) @@ -22203,9 +22322,9 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Left trim the input string - * @param $str (string) string to trim - * @param $replace (string) string that replace spaces. - * @return left trimmed string + * @param string $str string to trim + * @param string $replace string that replace spaces. + * @return string left trimmed string * @author Nicola Asuni * @public * @since 5.8.000 (2010-08-11) @@ -22216,9 +22335,9 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Right trim the input string - * @param $str (string) string to trim - * @param $replace (string) string that replace spaces. - * @return right trimmed string + * @param string $str string to trim + * @param string $replace string that replace spaces. + * @return string right trimmed string * @author Nicola Asuni * @public * @since 5.8.000 (2010-08-11) @@ -22229,9 +22348,9 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Trim the input string - * @param $str (string) string to trim - * @param $replace (string) string that replace spaces. - * @return trimmed string + * @param string $str string to trim + * @param string $replace string that replace spaces. + * @return string trimmed string * @author Nicola Asuni * @public * @since 5.8.000 (2010-08-11) @@ -22244,7 +22363,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Return true if the current font is unicode type. - * @return true for unicode font, false otherwise. + * @return bool true for unicode font, false otherwise. * @author Nicola Asuni * @public * @since 5.8.002 (2010-08-14) @@ -22255,7 +22374,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Return normalized font name - * @param $fontfamily (string) property string containing font family names + * @param string $fontfamily property string containing font family names * @return string normalized font name * @author Nicola Asuni * @public @@ -22296,10 +22415,10 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: * An XObject Template is a PDF block that is a self-contained description of any sequence of graphics objects (including path objects, text objects, and sampled images). * An XObject Template may be painted multiple times, either on several pages or at several locations on the same page and produces the same results each time, subject only to the graphics state at the time it is invoked. * Note: X,Y coordinates will be reset to 0,0. - * @param $w (int) Template width in user units (empty string or zero = page width less margins). - * @param $h (int) Template height in user units (empty string or zero = page height less margins). - * @param $group (mixed) Set transparency group. Can be a boolean value or an array specifying optional parameters: 'CS' (solour space name), 'I' (boolean flag to indicate isolated group) and 'K' (boolean flag to indicate knockout group). - * @return int the XObject Template ID in case of success or false in case of error. + * @param int $w Template width in user units (empty string or zero = page width less margins). + * @param int $h Template height in user units (empty string or zero = page height less margins). + * @param mixed $group Set transparency group. Can be a boolean value or an array specifying optional parameters: 'CS' (solour space name), 'I' (boolean flag to indicate isolated group) and 'K' (boolean flag to indicate knockout group). + * @return string|false the XObject Template ID in case of success or false in case of error. * @author Nicola Asuni * @public * @since 5.8.017 (2010-08-24) @@ -22332,7 +22451,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: // set new environment $this->num_columns = 1; $this->current_column = 0; - $this->SetAutoPageBreak(false); + $this->setAutoPageBreak(false); if (($w === '') OR ($w <= 0)) { $w = $this->w - $this->lMargin - $this->rMargin; } @@ -22364,7 +22483,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: * End the current XObject Template started with startTemplate() and restore the previous graphic state. * An XObject Template is a PDF block that is a self-contained description of any sequence of graphics objects (including path objects, text objects, and sampled images). * An XObject Template may be painted multiple times, either on several pages or at several locations on the same page and produces the same results each time, subject only to the graphics state at the time it is invoked. - * @return int the XObject Template ID in case of success or false in case of error. + * @return string|false the XObject Template ID in case of success or false in case of error. * @author Nicola Asuni * @public * @since 5.8.017 (2010-08-24) @@ -22386,20 +22505,20 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: * You can print an XObject Template inside the currently opened Template. * An XObject Template is a PDF block that is a self-contained description of any sequence of graphics objects (including path objects, text objects, and sampled images). * An XObject Template may be painted multiple times, either on several pages or at several locations on the same page and produces the same results each time, subject only to the graphics state at the time it is invoked. - * @param $id (string) The ID of XObject Template to print. - * @param $x (int) X position in user units (empty string = current x position) - * @param $y (int) Y position in user units (empty string = current y position) - * @param $w (int) Width in user units (zero = remaining page width) - * @param $h (int) Height in user units (zero = remaining page height) - * @param $align (string) Indicates the alignment of the pointer next to template insertion relative to template height. The value can be:
                    • T: top-right for LTR or top-left for RTL
                    • M: middle-right for LTR or middle-left for RTL
                    • B: bottom-right for LTR or bottom-left for RTL
                    • N: next line
                    - * @param $palign (string) Allows to center or align the template on the current line. Possible values are:
                    • L : left align
                    • C : center
                    • R : right align
                    • '' : empty string : left for LTR or right for RTL
                    - * @param $fitonpage (boolean) If true the template is resized to not exceed page dimensions. + * @param string $id The ID of XObject Template to print. + * @param float|null $x X position in user units (empty string = current x position) + * @param float|null $y Y position in user units (empty string = current y position) + * @param float $w Width in user units (zero = remaining page width) + * @param float $h Height in user units (zero = remaining page height) + * @param string $align Indicates the alignment of the pointer next to template insertion relative to template height. The value can be:
                    • T: top-right for LTR or top-left for RTL
                    • M: middle-right for LTR or middle-left for RTL
                    • B: bottom-right for LTR or bottom-left for RTL
                    • N: next line
                    + * @param string $palign Allows to center or align the template on the current line. Possible values are:
                    • L : left align
                    • C : center
                    • R : right align
                    • '' : empty string : left for LTR or right for RTL
                    + * @param boolean $fitonpage If true the template is resized to not exceed page dimensions. * @author Nicola Asuni * @public * @since 5.8.017 (2010-08-24) * @see startTemplate(), endTemplate() */ - public function printTemplate($id, $x='', $y='', $w=0, $h=0, $align='', $palign='', $fitonpage=false) { + public function printTemplate($id, $x=null, $y=null, $w=0, $h=0, $align='', $palign='', $fitonpage=false) { if ($this->state != 2) { return; } @@ -22416,10 +22535,10 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: } } // set default values - if ($x === '') { + if (TCPDF_STATIC::empty_string($x)) { $x = $this->x; } - if ($y === '') { + if (TCPDF_STATIC::empty_string($y)) { $y = $this->y; } // check page for no-write regions and adapt page margins if necessary @@ -22516,7 +22635,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: break; } case 'N': { - $this->SetY($rb_y); + $this->setY($rb_y); break; } default:{ @@ -22527,7 +22646,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set the percentage of character stretching. - * @param $perc (int) percentage of stretching (100 = no stretching) + * @param int $perc percentage of stretching (100 = no stretching) * @author Nicola Asuni * @public * @since 5.9.000 (2010-09-29) @@ -22549,7 +22668,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Set the amount to increase or decrease the space between characters in a text. - * @param $spacing (float) amount to increase or decrease the space between characters in a text (0 = default spacing) + * @param float $spacing amount to increase or decrease the space between characters in a text (0 = default spacing) * @author Nicola Asuni * @public * @since 5.9.000 (2010-09-29) @@ -22586,7 +22705,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: * A no-write region is a portion of the page with a rectangular or trapezium shape that will not be covered when writing text or html code. * A region is always aligned on the left or right side of the page ad is defined using a vertical segment. * You can set multiple regions for the same page. - * @param $regions (array) array of no-write regions. For each region you can define an array as follow: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right). Omit this parameter to remove all regions. + * @param array $regions array of no-write regions. For each region you can define an array as follow: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right). Omit this parameter to remove all regions. * @author Nicola Asuni * @public * @since 5.9.003 (2010-10-13) @@ -22606,7 +22725,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: * A no-write region is a portion of the page with a rectangular or trapezium shape that will not be covered when writing text or html code. * A region is always aligned on the left or right side of the page ad is defined using a vertical segment. * You can set multiple regions for the same page. - * @param $region (array) array of a single no-write region array: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right). + * @param array $region array of a single no-write region array: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right). * @author Nicola Asuni * @public * @since 5.9.003 (2010-10-13) @@ -22625,7 +22744,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Remove a single no-write region. - * @param $key (int) region key + * @param int $key region key * @author Nicola Asuni * @public * @since 5.9.003 (2010-10-13) @@ -22641,10 +22760,10 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: * Check page for no-write regions and adapt current coordinates and page margins if necessary. * A no-write region is a portion of the page with a rectangular or trapezium shape that will not be covered when writing text or html code. * A region is always aligned on the left or right side of the page ad is defined using a vertical segment. - * @param $h (float) height of the text/image/object to print in user units - * @param $x (float) current X coordinate in user units - * @param $y (float) current Y coordinate in user units - * @return array($x, $y) + * @param float $h height of the text/image/object to print in user units + * @param float $x current X coordinate in user units + * @param float $y current Y coordinate in user units + * @return float[] array($x, $y) * @author Nicola Asuni * @protected * @since 5.9.003 (2010-10-13) @@ -22738,21 +22857,21 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Embedd a Scalable Vector Graphics (SVG) image. * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library. - * @param $file (string) Name of the SVG file or a '@' character followed by the SVG data string. - * @param $x (float) Abscissa of the upper-left corner. - * @param $y (float) Ordinate of the upper-left corner. - * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated. - * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated. - * @param $link (mixed) URL or identifier returned by AddLink(). - * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:
                    • T: top-right for LTR or top-left for RTL
                    • M: middle-right for LTR or middle-left for RTL
                    • B: bottom-right for LTR or bottom-left for RTL
                    • N: next line
                    If the alignment is an empty string, then the pointer will be restored on the starting SVG position. - * @param $palign (string) Allows to center or align the image on the current line. Possible values are:
                    • L : left align
                    • C : center
                    • R : right align
                    • '' : empty string : left for LTR or right for RTL
                    - * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:
                    • 0: no border (default)
                    • 1: frame
                    or a string containing some or all of the following characters (in any order):
                    • L: left
                    • T: top
                    • R: right
                    • B: bottom
                    or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) - * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions. + * @param string $file Name of the SVG file or a '@' character followed by the SVG data string. + * @param float|null $x Abscissa of the upper-left corner. + * @param float|null $y Ordinate of the upper-left corner. + * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated. + * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated. + * @param mixed $link URL or identifier returned by AddLink(). + * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:
                    • T: top-right for LTR or top-left for RTL
                    • M: middle-right for LTR or middle-left for RTL
                    • B: bottom-right for LTR or bottom-left for RTL
                    • N: next line
                    If the alignment is an empty string, then the pointer will be restored on the starting SVG position. + * @param string $palign Allows to center or align the image on the current line. Possible values are:
                    • L : left align
                    • C : center
                    • R : right align
                    • '' : empty string : left for LTR or right for RTL
                    + * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:
                    • 0: no border (default)
                    • 1: frame
                    or a string containing some or all of the following characters (in any order):
                    • L: left
                    • T: top
                    • R: right
                    • B: bottom
                    or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0))) + * @param boolean $fitonpage if true the image is resized to not exceed page dimensions. * @author Nicola Asuni * @since 5.0.000 (2010-05-02) * @public */ - public function ImageSVG($file, $x='', $y='', $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) { + public function ImageSVG($file, $x=null, $y=null, $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) { if ($this->state != 2) { return; } @@ -22776,15 +22895,15 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: $svgdata = substr($file, 1); } else { // SVG file $this->svgdir = dirname($file); - $svgdata = TCPDF_STATIC::fileGetContents($file); + $svgdata = $this->getCachedFileContents($file); } if ($svgdata === FALSE) { $this->Error('SVG file not found: '.$file); } - if ($x === '') { + if (TCPDF_STATIC::empty_string($x)) { $x = $this->x; } - if ($y === '') { + if (TCPDF_STATIC::empty_string($y)) { $y = $this->y; } // check page for no-write regions and adapt page margins if necessary @@ -22975,8 +23094,8 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: $page_break_mode = $this->AutoPageBreak; $page_break_margin = $this->getBreakMargin(); $cell_padding = $this->cell_padding; - $this->SetCellPadding(0); - $this->SetAutoPageBreak(false); + $this->setCellPadding(0); + $this->setAutoPageBreak(false); // save the current graphic state $this->_out('q'.$this->epsmarker); // set initial clipping mask @@ -22986,22 +23105,26 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: $f = ($this->h - $oy) * $this->k * (1 - $svgscale_y); $this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y))); // creates a new XML parser to be used by the other XML functions - $this->parser = xml_parser_create('UTF-8'); + $parser = xml_parser_create('UTF-8'); // the following function allows to use parser inside object - xml_set_object($this->parser, $this); + xml_set_object($parser, $this); // disable case-folding for this XML parser - xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0); + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); // sets the element handler functions for the XML parser - xml_set_element_handler($this->parser, 'startSVGElementHandler', 'endSVGElementHandler'); + xml_set_element_handler($parser, 'startSVGElementHandler', 'endSVGElementHandler'); // sets the character data handler function for the XML parser - xml_set_character_data_handler($this->parser, 'segSVGContentHandler'); + xml_set_character_data_handler($parser, 'segSVGContentHandler'); // start parsing an XML document - if (!xml_parse($this->parser, $svgdata)) { - $error_message = sprintf('SVG Error: %s at line %d', xml_error_string(xml_get_error_code($this->parser)), xml_get_current_line_number($this->parser)); + if (!xml_parse($parser, $svgdata)) { + $error_message = sprintf('SVG Error: %s at line %d', xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser)); $this->Error($error_message); } // free this XML parser - xml_parser_free($this->parser); + xml_parser_free($parser); + + // >= PHP 7.0.0 "explicitly unset the reference to parser to avoid memory leaks" + unset($parser); + // restore previous graphic state $this->_out($this->epsmarker.'Q'); // restore graphic vars @@ -23040,7 +23163,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: break; } case 'N':{ - $this->SetY($this->img_rb_y); + $this->setY($this->img_rb_y); break; } default:{ @@ -23062,13 +23185,13 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: } $this->endlinex = $this->img_rb_x; // restore page break - $this->SetAutoPageBreak($page_break_mode, $page_break_margin); + $this->setAutoPageBreak($page_break_mode, $page_break_margin); $this->cell_padding = $cell_padding; } /** * Convert SVG transformation matrix to PDF. - * @param $tm (array) original SVG transformation matrix + * @param array $tm original SVG transformation matrix * @return array transformation matrix * @protected * @since 5.0.000 (2010-05-02) @@ -23089,7 +23212,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Apply SVG graphic transformation matrix. - * @param $tm (array) original SVG transformation matrix + * @param array $tm original SVG transformation matrix * @protected * @since 5.0.000 (2010-05-02) */ @@ -23099,15 +23222,15 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Apply the requested SVG styles (*** TO BE COMPLETED ***) - * @param $svgstyle (array) array of SVG styles to apply - * @param $prevsvgstyle (array) array of previous SVG style - * @param $x (int) X origin of the bounding box - * @param $y (int) Y origin of the bounding box - * @param $w (int) width of the bounding box - * @param $h (int) height of the bounding box - * @param $clip_function (string) clip function - * @param $clip_params (array) array of parameters for clipping function - * @return object style + * @param array $svgstyle array of SVG styles to apply + * @param array $prevsvgstyle array of previous SVG style + * @param int $x X origin of the bounding box + * @param int $y Y origin of the bounding box + * @param int $w width of the bounding box + * @param int $h height of the bounding box + * @param string $clip_function clip function + * @param array $clip_params array of parameters for clipping function + * @return string style * @author Nicola Asuni * @since 5.0.000 (2010-05-02) * @protected @@ -23135,10 +23258,10 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: } // color $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors); - $this->SetFillColorArray($fill_color); + $this->setFillColorArray($fill_color); // text color $text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors); - $this->SetTextColorArray($text_color); + $this->setTextColorArray($text_color); // clip if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) { $top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0); @@ -23265,7 +23388,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: if ($svgstyle['fill-opacity'] != 1) { $this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false); } - $this->SetFillColorArray($fill_color); + $this->setFillColorArray($fill_color); if ($svgstyle['fill-rule'] == 'evenodd') { $objstyle .= 'F*'; } else { @@ -23288,7 +23411,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) { $stroke_style['dash'] = $svgstyle['stroke-dasharray']; } - $this->SetLineStyle($stroke_style); + $this->setLineStyle($stroke_style); $objstyle .= 'D'; } // font @@ -23383,7 +23506,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: break; } } - $this->SetFont($font_family, $font_style, $font_size); + $this->setFont($font_family, $font_style, $font_size); $this->setFontStretching($font_stretch); $this->setFontSpacing($font_spacing); return $objstyle; @@ -23391,8 +23514,8 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Draws an SVG path - * @param $d (string) attribute d of the path SVG element - * @param $style (string) Style of rendering. Possible values are: + * @param string $d attribute d of the path SVG element + * @param string $style Style of rendering. Possible values are: *
                      *
                    • D or empty string: Draw (default).
                    • *
                    • F: Fill.
                    • @@ -23418,6 +23541,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: } $paths = array(); $d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d); + $d = preg_replace('/(\.[0-9]+)(\.)/s', '\\1 \\2', $d); preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER); $x = 0; $y = 0; @@ -23767,7 +23891,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Return the tag name without the namespace - * @param $name (string) Tag name + * @param string $name Tag name * @protected */ protected function removeTagNamespace($name) { @@ -23780,10 +23904,10 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***) - * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler. - * @param $name (string) The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters. - * @param $attribs (array) The third parameter, attribs, contains an associative array with the element's attributes (if any). The keys of this array are the attribute names, the values are the attribute values. Attribute names are case-folded on the same criteria as element names. Attribute values are not case-folded. The original order of the attributes can be retrieved by walking through attribs the normal way, using each(). The first key in the array was the first attribute, and so on. - * @param $ctm (array) tranformation matrix for clipping mode (starting transformation matrix). + * @param resource|string $parser The first parameter, parser, is a reference to the XML parser calling the handler. + * @param string $name The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters. + * @param array $attribs The third parameter, attribs, contains an associative array with the element's attributes (if any). The keys of this array are the attribute names, the values are the attribute values. Attribute names are case-folded on the same criteria as element names. Attribute values are not case-folded. The original order of the attributes can be retrieved by walking through attribs the normal way, using each(). The first key in the array was the first attribute, and so on. + * @param array $ctm tranformation matrix for clipping mode (starting transformation matrix). * @author Nicola Asuni * @since 5.0.000 (2010-05-02) * @protected @@ -23978,7 +24102,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: break; } case 'linearGradient': { - if ($this->pdfa_mode) { + if ($this->pdfa_mode && $this->pdfa_version < 2) { break; } if (!isset($attribs['id'])) { @@ -24018,7 +24142,7 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: break; } case 'radialGradient': { - if ($this->pdfa_mode) { + if ($this->pdfa_mode && $this->pdfa_version < 2) { break; } if (!isset($attribs['id'])) { @@ -24452,8 +24576,8 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Sets the closing SVG element handler function for the XML parser. - * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler. - * @param $name (string) The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters. + * @param resource|string $parser The first parameter, parser, is a reference to the XML parser calling the handler. + * @param string $name The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters. * @author Nicola Asuni * @since 5.0.000 (2010-05-02) * @protected @@ -24559,8 +24683,8 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: /** * Sets the character data handler function for the XML parser. - * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler. - * @param $data (string) The second parameter, data, contains the character data as a string. + * @param resource $parser The first parameter, parser, is a reference to the XML parser calling the handler. + * @param string $data The second parameter, data, contains the character data as a string. * @author Nicola Asuni * @since 5.0.000 (2010-05-02) * @protected @@ -24571,6 +24695,33 @@ Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: // --- END SVG METHODS ----------------------------------------------------- + /** + * Keeps files in memory, so it doesn't need to downloaded everytime in a loop + * @param string $file + * @return string + */ + protected function getCachedFileContents($file) + { + if (!isset($this->fileContentCache[$file])) { + $this->fileContentCache[$file] = TCPDF_STATIC::fileGetContents($file); + } + return $this->fileContentCache[$file]; + } + + /** + * Avoid multiple calls to an external server to see if a file exists + * @param string $file + * @return bool + */ + protected function fileExists($file) + { + if (isset($this->fileContentCache[$file]) || false !== $this->getImageBuffer($file)) { + return true; + } + + return TCPDF_STATIC::file_exists($file); + } + } // END OF TCPDF CLASS //============================================================+ diff --git a/lib/combodo/tcpdf/tcpdf_barcodes_1d.php b/lib/combodo/tcpdf/tcpdf_barcodes_1d.php index 78bfc5b5bf..10a79a72ec 100644 --- a/lib/combodo/tcpdf/tcpdf_barcodes_1d.php +++ b/lib/combodo/tcpdf/tcpdf_barcodes_1d.php @@ -53,7 +53,7 @@ class TCPDFBarcode { * Array representation of barcode. * @protected */ - protected $barcode_array; + protected $barcode_array = array(); /** * This is the class constructor. @@ -66,8 +66,8 @@ class TCPDFBarcode { *
                    • $arrcode['bcode'][$k]['w'] bar width in units.
                    • *
                    • $arrcode['bcode'][$k]['h'] bar height in units.
                    • *
                    • $arrcode['bcode'][$k]['p'] bar top position (0 = top, 1 = middle)
                    - * @param $code (string) code to print - * @param $type (string) type of barcode:
                    • C39 : CODE 39 - ANSI MH10.8M-1983 - USD-3 - 3 of 9.
                    • C39+ : CODE 39 with checksum
                    • C39E : CODE 39 EXTENDED
                    • C39E+ : CODE 39 EXTENDED + CHECKSUM
                    • C93 : CODE 93 - USS-93
                    • S25 : Standard 2 of 5
                    • S25+ : Standard 2 of 5 + CHECKSUM
                    • I25 : Interleaved 2 of 5
                    • I25+ : Interleaved 2 of 5 + CHECKSUM
                    • C128 : CODE 128
                    • C128A : CODE 128 A
                    • C128B : CODE 128 B
                    • C128C : CODE 128 C
                    • EAN2 : 2-Digits UPC-Based Extension
                    • EAN5 : 5-Digits UPC-Based Extension
                    • EAN8 : EAN 8
                    • EAN13 : EAN 13
                    • UPCA : UPC-A
                    • UPCE : UPC-E
                    • MSI : MSI (Variation of Plessey code)
                    • MSI+ : MSI + CHECKSUM (modulo 11)
                    • POSTNET : POSTNET
                    • PLANET : PLANET
                    • RMS4CC : RMS4CC (Royal Mail 4-state Customer Code) - CBC (Customer Bar Code)
                    • KIX : KIX (Klant index - Customer index)
                    • IMB: Intelligent Mail Barcode - Onecode - USPS-B-3200
                    • CODABAR : CODABAR
                    • CODE11 : CODE 11
                    • PHARMA : PHARMACODE
                    • PHARMA2T : PHARMACODE TWO-TRACKS
                    + * @param string $code code to print + * @param string $type type of barcode:
                    • C39 : CODE 39 - ANSI MH10.8M-1983 - USD-3 - 3 of 9.
                    • C39+ : CODE 39 with checksum
                    • C39E : CODE 39 EXTENDED
                    • C39E+ : CODE 39 EXTENDED + CHECKSUM
                    • C93 : CODE 93 - USS-93
                    • S25 : Standard 2 of 5
                    • S25+ : Standard 2 of 5 + CHECKSUM
                    • I25 : Interleaved 2 of 5
                    • I25+ : Interleaved 2 of 5 + CHECKSUM
                    • C128 : CODE 128
                    • C128A : CODE 128 A
                    • C128B : CODE 128 B
                    • C128C : CODE 128 C
                    • EAN2 : 2-Digits UPC-Based Extension
                    • EAN5 : 5-Digits UPC-Based Extension
                    • EAN8 : EAN 8
                    • EAN13 : EAN 13
                    • UPCA : UPC-A
                    • UPCE : UPC-E
                    • MSI : MSI (Variation of Plessey code)
                    • MSI+ : MSI + CHECKSUM (modulo 11)
                    • POSTNET : POSTNET
                    • PLANET : PLANET
                    • RMS4CC : RMS4CC (Royal Mail 4-state Customer Code) - CBC (Customer Bar Code)
                    • KIX : KIX (Klant index - Customer index)
                    • IMB: Intelligent Mail Barcode - Onecode - USPS-B-3200
                    • CODABAR : CODABAR
                    • CODE11 : CODE 11
                    • PHARMA : PHARMACODE
                    • PHARMA2T : PHARMACODE TWO-TRACKS
                    * @public */ public function __construct($code, $type) { @@ -85,9 +85,9 @@ class TCPDFBarcode { /** * Send barcode as SVG image object to the standard output. - * @param $w (int) Minimum width of a single bar in user units. - * @param $h (int) Height of barcode in user units. - * @param $color (string) Foreground color (in SVG format) for bar elements (background is transparent). + * @param int $w Minimum width of a single bar in user units. + * @param int $h Height of barcode in user units. + * @param string $color Foreground color (in SVG format) for bar elements (background is transparent). * @public */ public function getBarcodeSVG($w=2, $h=30, $color='black') { @@ -105,9 +105,9 @@ class TCPDFBarcode { /** * Return a SVG string representation of barcode. - * @param $w (int) Minimum width of a single bar in user units. - * @param $h (int) Height of barcode in user units. - * @param $color (string) Foreground color (in SVG format) for bar elements (background is transparent). + * @param int $w Minimum width of a single bar in user units. + * @param int $h Height of barcode in user units. + * @param string $color Foreground color (in SVG format) for bar elements (background is transparent). * @return string SVG code. * @public */ @@ -138,9 +138,9 @@ class TCPDFBarcode { /** * Return an HTML representation of barcode. - * @param $w (int) Width of a single bar element in pixels. - * @param $h (int) Height of a single bar element in pixels. - * @param $color (string) Foreground color for bar elements (background is transparent). + * @param int $w Width of a single bar element in pixels. + * @param int $h Height of a single bar element in pixels. + * @param string $color Foreground color for bar elements (background is transparent). * @return string HTML code. * @public */ @@ -164,9 +164,9 @@ class TCPDFBarcode { /** * Send a PNG image representation of barcode (requires GD or Imagick library). - * @param $w (int) Width of a single bar element in pixels. - * @param $h (int) Height of a single bar element in pixels. - * @param $color (array) RGB (0-255) foreground color for bar elements (background is transparent). + * @param int $w Width of a single bar element in pixels. + * @param int $h Height of a single bar element in pixels. + * @param array $color RGB (0-255) foreground color for bar elements (background is transparent). * @public */ public function getBarcodePNG($w=2, $h=30, $color=array(0,0,0)) { @@ -183,10 +183,10 @@ class TCPDFBarcode { /** * Return a PNG image representation of barcode (requires GD or Imagick library). - * @param $w (int) Width of a single bar element in pixels. - * @param $h (int) Height of a single bar element in pixels. - * @param $color (array) RGB (0-255) foreground color for bar elements (background is transparent). - * @return image or false in case of error. + * @param int $w Width of a single bar element in pixels. + * @param int $h Height of a single bar element in pixels. + * @param array $color RGB (0-255) foreground color for bar elements (background is transparent). + * @return string|Imagick|false image or false in case of error. * @public */ public function getBarcodePngData($w=2, $h=30, $color=array(0,0,0)) { @@ -241,9 +241,9 @@ class TCPDFBarcode { /** * Set the barcode. - * @param $code (string) code to print - * @param $type (string) type of barcode:
                    • C39 : CODE 39 - ANSI MH10.8M-1983 - USD-3 - 3 of 9.
                    • C39+ : CODE 39 with checksum
                    • C39E : CODE 39 EXTENDED
                    • C39E+ : CODE 39 EXTENDED + CHECKSUM
                    • C93 : CODE 93 - USS-93
                    • S25 : Standard 2 of 5
                    • S25+ : Standard 2 of 5 + CHECKSUM
                    • I25 : Interleaved 2 of 5
                    • I25+ : Interleaved 2 of 5 + CHECKSUM
                    • C128 : CODE 128
                    • C128A : CODE 128 A
                    • C128B : CODE 128 B
                    • C128C : CODE 128 C
                    • EAN2 : 2-Digits UPC-Based Extension
                    • EAN5 : 5-Digits UPC-Based Extension
                    • EAN8 : EAN 8
                    • EAN13 : EAN 13
                    • UPCA : UPC-A
                    • UPCE : UPC-E
                    • MSI : MSI (Variation of Plessey code)
                    • MSI+ : MSI + CHECKSUM (modulo 11)
                    • POSTNET : POSTNET
                    • PLANET : PLANET
                    • RMS4CC : RMS4CC (Royal Mail 4-state Customer Code) - CBC (Customer Bar Code)
                    • KIX : KIX (Klant index - Customer index)
                    • IMB: Intelligent Mail Barcode - Onecode - USPS-B-3200
                    • IMBPRE: Pre-processed Intelligent Mail Barcode - Onecode - USPS-B-3200, using only F,A,D,T letters
                    • CODABAR : CODABAR
                    • CODE11 : CODE 11
                    • PHARMA : PHARMACODE
                    • PHARMA2T : PHARMACODE TWO-TRACKS
                    - * @return array barcode array + * @param string $code code to print + * @param string $type type of barcode:
                    • C39 : CODE 39 - ANSI MH10.8M-1983 - USD-3 - 3 of 9.
                    • C39+ : CODE 39 with checksum
                    • C39E : CODE 39 EXTENDED
                    • C39E+ : CODE 39 EXTENDED + CHECKSUM
                    • C93 : CODE 93 - USS-93
                    • S25 : Standard 2 of 5
                    • S25+ : Standard 2 of 5 + CHECKSUM
                    • I25 : Interleaved 2 of 5
                    • I25+ : Interleaved 2 of 5 + CHECKSUM
                    • C128 : CODE 128
                    • C128A : CODE 128 A
                    • C128B : CODE 128 B
                    • C128C : CODE 128 C
                    • EAN2 : 2-Digits UPC-Based Extension
                    • EAN5 : 5-Digits UPC-Based Extension
                    • EAN8 : EAN 8
                    • EAN13 : EAN 13
                    • UPCA : UPC-A
                    • UPCE : UPC-E
                    • MSI : MSI (Variation of Plessey code)
                    • MSI+ : MSI + CHECKSUM (modulo 11)
                    • POSTNET : POSTNET
                    • PLANET : PLANET
                    • RMS4CC : RMS4CC (Royal Mail 4-state Customer Code) - CBC (Customer Bar Code)
                    • KIX : KIX (Klant index - Customer index)
                    • IMB: Intelligent Mail Barcode - Onecode - USPS-B-3200
                    • IMBPRE: Pre-processed Intelligent Mail Barcode - Onecode - USPS-B-3200, using only F,A,D,T letters
                    • CODABAR : CODABAR
                    • CODE11 : CODE 11
                    • PHARMA : PHARMACODE
                    • PHARMA2T : PHARMACODE TWO-TRACKS
                    + * @return void * @public */ public function setBarcode($code, $type) { @@ -373,7 +373,7 @@ class TCPDFBarcode { break; } default: { - $this->barcode_array = false; + $this->barcode_array = array(); $arrcode = false; break; } @@ -384,9 +384,9 @@ class TCPDFBarcode { /** * CODE 39 - ANSI MH10.8M-1983 - USD-3 - 3 of 9. * General-purpose code in very wide use world-wide - * @param $code (string) code to represent. - * @param $extended (boolean) if true uses the extended mode. - * @param $checksum (boolean) if true add a checksum to the code. + * @param string $code code to represent. + * @param boolean $extended if true uses the extended mode. + * @param boolean $checksum if true add a checksum to the code. * @return array barcode representation. * @protected */ @@ -479,8 +479,8 @@ class TCPDFBarcode { /** * Encode a string to be used for CODE 39 Extended mode. - * @param $code (string) code to represent. - * @return encoded string. + * @param string $code code to represent. + * @return string encoded string. * @protected */ protected function encode_code39_ext($code) { @@ -530,8 +530,8 @@ class TCPDFBarcode { /** * Calculate CODE 39 checksum (modulo 43). - * @param $code (string) code to represent. - * @return char checksum. + * @param string $code code to represent. + * @return string char checksum. * @protected */ protected function checksum_code39($code) { @@ -553,7 +553,7 @@ class TCPDFBarcode { /** * CODE 93 - USS-93 * Compact code similar to Code 39 - * @param $code (string) code to represent. + * @param string $code code to represent. * @return array barcode representation. * @protected */ @@ -681,7 +681,7 @@ class TCPDFBarcode { /** * Calculate CODE 93 checksum (modulo 47). - * @param $code (string) code to represent. + * @param string $code code to represent. * @return string checksum code. * @protected */ @@ -730,7 +730,7 @@ class TCPDFBarcode { /** * Checksum for standard 2 of 5 barcodes. - * @param $code (string) code to process. + * @param string $code code to process. * @return int checksum. * @protected */ @@ -755,8 +755,8 @@ class TCPDFBarcode { * MSI. * Variation of Plessey code, with similar applications * Contains digits (0 to 9) and encodes the data only in the width of bars. - * @param $code (string) code to represent. - * @param $checksum (boolean) if true add a checksum to the code (modulo 11) + * @param string $code code to represent. + * @param boolean $checksum if true add a checksum to the code (modulo 11) * @return array barcode representation. * @protected */ @@ -814,8 +814,8 @@ class TCPDFBarcode { * Standard 2 of 5 barcodes. * Used in airline ticket marking, photofinishing * Contains digits (0 to 9) and encodes the data only in the width of bars. - * @param $code (string) code to represent. - * @param $checksum (boolean) if true add a checksum to the code + * @param string $code code to represent. + * @param boolean $checksum if true add a checksum to the code * @return array barcode representation. * @protected */ @@ -854,10 +854,9 @@ class TCPDFBarcode { } /** - * Convert binary barcode sequence to TCPDF barcode array. - * @param $seq (string) barcode as binary sequence. - * @param $bararray (array) barcode array. - * òparam array $bararray TCPDF barcode array to fill up + * Convert binary barcode sequence to WarnockPDF barcode array. + * @param string $seq barcode as binary sequence. + * @param array $bararray barcode array to fill up * @return array barcode representation. * @protected */ @@ -886,8 +885,8 @@ class TCPDFBarcode { * Interleaved 2 of 5 barcodes. * Compact numeric code, widely used in industry, air cargo * Contains digits (0 to 9) and encodes the data in the width of both bars and spaces. - * @param $code (string) code to represent. - * @param $checksum (boolean) if true add a checksum to the code + * @param string $code code to represent. + * @param boolean $checksum if true add a checksum to the code * @return array barcode representation. * @protected */ @@ -938,7 +937,7 @@ class TCPDFBarcode { } else { $t = false; // space } - $w = $seq[$j]; + $w = (float)$seq[$j]; $bararray['bcode'][$k] = array('t' => $t, 'w' => $w, 'h' => 1, 'p' => 0); $bararray['maxw'] += $w; ++$k; @@ -950,8 +949,8 @@ class TCPDFBarcode { /** * C128 barcodes. * Very capable code, excellent density, high reliability; in very wide use world-wide - * @param $code (string) code to represent. - * @param $type (string) barcode type: A, B, C or empty for automatic switch (AUTO mode) + * @param string $code code to represent. + * @param string $type barcode type: A, B, C or empty for automatic switch (AUTO mode) * @return array barcode representation. * @protected */ @@ -1271,7 +1270,7 @@ class TCPDFBarcode { } else { $t = false; // space } - $w = $seq[$j]; + $w = (float)$seq[$j]; $bararray['bcode'][] = array('t' => $t, 'w' => $w, 'h' => 1, 'p' => 0); $bararray['maxw'] += $w; } @@ -1281,7 +1280,7 @@ class TCPDFBarcode { /** * Split text code in A/B sequence for 128 code - * @param $code (string) code to split. + * @param string $code code to split. * @return array sequence * @protected */ @@ -1319,8 +1318,8 @@ class TCPDFBarcode { * EAN13: European Article Numbering international retail product code * UPC-A: Universal product code seen on almost all retail products in the USA and Canada * UPC-E: Short version of UPC symbol - * @param $code (string) code to represent. - * @param $len (string) barcode type: 6 = UPC-E, 8 = EAN8, 13 = EAN13, 12 = UPC-A + * @param string $code code to represent. + * @param string $len barcode type: 6 = UPC-E, 8 = EAN8, 13 = EAN13, 12 = UPC-A * @return array barcode representation. * @protected */ @@ -1512,8 +1511,8 @@ class TCPDFBarcode { * UPC-Based Extensions * 2-Digit Ext.: Used to indicate magazines and newspaper issue numbers * 5-Digit Ext.: Used to mark suggested retail price of books - * @param $code (string) code to represent. - * @param $len (string) barcode type: 2 = 2-Digit, 5 = 5-Digit + * @param string $code code to represent. + * @param string $len barcode type: 2 = 2-Digit, 5 = 5-Digit * @return array barcode representation. * @protected */ @@ -1587,8 +1586,8 @@ class TCPDFBarcode { /** * POSTNET and PLANET barcodes. * Used by U.S. Postal Service for automated mail sorting - * @param $code (string) zip code to represent. Must be a string containing a zip code of the form DDDDD or DDDDD-DDDD. - * @param $planet (boolean) if true print the PLANET barcode, otherwise print POSTNET + * @param string $code zip code to represent. Must be a string containing a zip code of the form DDDDD or DDDDD-DDDD. + * @param boolean $planet if true print the PLANET barcode, otherwise print POSTNET * @return array barcode representation. * @protected */ @@ -1660,8 +1659,8 @@ class TCPDFBarcode { * RMS4CC - CBC - KIX * RMS4CC (Royal Mail 4-state Customer Code) - CBC (Customer Bar Code) - KIX (Klant index - Customer index) * RM4SCC is the name of the barcode symbology used by the Royal Mail for its Cleanmail service. - * @param $code (string) code to print - * @param $kix (boolean) if true prints the KIX variation (doesn't use the start and end symbols, and the checksum) - in this case the house number must be sufficed with an X and placed at the end of the code. + * @param string $code code to print + * @param boolean $kix if true prints the KIX variation (doesn't use the start and end symbols, and the checksum) - in this case the house number must be sufficed with an X and placed at the end of the code. * @return array barcode representation. * @protected */ @@ -1812,7 +1811,7 @@ class TCPDFBarcode { /** * CODABAR barcodes. * Older code often used in library systems, sometimes in blood banks - * @param $code (string) code to represent. + * @param string $code code to represent. * @return array barcode representation. * @protected */ @@ -1856,7 +1855,7 @@ class TCPDFBarcode { } else { $t = false; // space } - $w = $seq[$j]; + $w = (float)$seq[$j]; $bararray['bcode'][$k] = array('t' => $t, 'w' => $w, 'h' => 1, 'p' => 0); $bararray['maxw'] += $w; ++$k; @@ -1868,7 +1867,7 @@ class TCPDFBarcode { /** * CODE11 barcodes. * Used primarily for labeling telecommunications equipment - * @param $code (string) code to represent. + * @param string $code code to represent. * @return array barcode representation. * @protected */ @@ -1947,7 +1946,7 @@ class TCPDFBarcode { } else { $t = false; // space } - $w = $seq[$j]; + $w = (float)$seq[$j]; $bararray['bcode'][$k] = array('t' => $t, 'w' => $w, 'h' => 1, 'p' => 0); $bararray['maxw'] += $w; ++$k; @@ -1959,7 +1958,7 @@ class TCPDFBarcode { /** * Pharmacode * Contains digits (0 to 9) - * @param $code (string) code to represent. + * @param string $code code to represent. * @return array barcode representation. * @protected */ @@ -1985,7 +1984,7 @@ class TCPDFBarcode { /** * Pharmacode two-track * Contains digits (0 to 9) - * @param $code (string) code to represent. + * @param string $code code to represent. * @return array barcode representation. * @protected */ @@ -2047,7 +2046,7 @@ class TCPDFBarcode { * (requires PHP bcmath extension) * Intelligent Mail barcode is a 65-bar code for use on mail in the United States. * The fields are described as follows:
                    • The Barcode Identifier shall be assigned by USPS to encode the presort identification that is currently printed in human readable form on the optional endorsement line (OEL) as well as for future USPS use. This shall be two digits, with the second digit in the range of 0–4. The allowable encoding ranges shall be 00–04, 10–14, 20–24, 30–34, 40–44, 50–54, 60–64, 70–74, 80–84, and 90–94.
                    • The Service Type Identifier shall be assigned by USPS for any combination of services requested on the mailpiece. The allowable encoding range shall be 000http://it2.php.net/manual/en/function.dechex.php–999. Each 3-digit value shall correspond to a particular mail class with a particular combination of service(s). Each service program, such as OneCode Confirm and OneCode ACS, shall provide the list of Service Type Identifier values.
                    • The Mailer or Customer Identifier shall be assigned by USPS as a unique, 6 or 9 digit number that identifies a business entity. The allowable encoding range for the 6 digit Mailer ID shall be 000000- 899999, while the allowable encoding range for the 9 digit Mailer ID shall be 900000000-999999999.
                    • The Serial or Sequence Number shall be assigned by the mailer for uniquely identifying and tracking mailpieces. The allowable encoding range shall be 000000000–999999999 when used with a 6 digit Mailer ID and 000000-999999 when used with a 9 digit Mailer ID. e. The Delivery Point ZIP Code shall be assigned by the mailer for routing the mailpiece. This shall replace POSTNET for routing the mailpiece to its final delivery point. The length may be 0, 5, 9, or 11 digits. The allowable encoding ranges shall be no ZIP Code, 00000–99999, 000000000–999999999, and 00000000000–99999999999.
                    - * @param $code (string) code to print, separate the ZIP (routing code) from the rest using a minus char '-' (BarcodeID_ServiceTypeID_MailerID_SerialNumber-RoutingCode) + * @param string $code code to print, separate the ZIP (routing code) from the rest using a minus char '-' (BarcodeID_ServiceTypeID_MailerID_SerialNumber-RoutingCode) * @return array barcode representation. * @protected */ @@ -2171,8 +2170,8 @@ class TCPDFBarcode { /** * IMB - Intelligent Mail Barcode - Onecode - USPS-B-3200 - * - * @param $code (string) pre-formatted IMB barcode (65 chars "FADT") + * + * @param string $code pre-formatted IMB barcode (65 chars "FADT") * @return array barcode representation. * @protected */ @@ -2223,7 +2222,7 @@ class TCPDFBarcode { /** * Convert large integer number to hexadecimal representation. * (requires PHP bcmath extension) - * @param $number (string) number to convert specified as a string + * @param string $number number to convert specified as a string * @return string hexadecimal representation */ public function dec_to_hex($number) { @@ -2247,7 +2246,7 @@ class TCPDFBarcode { /** * Convert large hexadecimal number to decimal representation (string). * (requires PHP bcmath extension) - * @param $hex (string) hexadecimal number to convert specified as a string + * @param string $hex hexadecimal number to convert specified as a string * @return string hexadecimal representation */ public function hex_to_dec($hex) { @@ -2263,7 +2262,7 @@ class TCPDFBarcode { /** * Intelligent Mail Barcode calculation of Frame Check Sequence - * @param $code_arr (string) array of hexadecimal values (13 bytes holding 102 bits right justified). + * @param string $code_arr array of hexadecimal values (13 bytes holding 102 bits right justified). * @return int 11 bit Frame Check Sequence as integer (decimal base) * @protected */ @@ -2299,7 +2298,7 @@ class TCPDFBarcode { /** * Reverse unsigned short value - * @param $num (int) value to reversr + * @param int $num value to reversr * @return int reversed value * @protected */ @@ -2315,8 +2314,8 @@ class TCPDFBarcode { /** * generate Nof13 tables used for Intelligent Mail Barcode - * @param $n (int) is the type of table: 2 for 2of13 table, 5 for 5of13table - * @param $size (int) size of table (78 for n=2 and 1287 for n=5) + * @param int $n is the type of table: 2 for 2of13 table, 5 for 5of13table + * @param int $size size of table (78 for n=2 and 1287 for n=5) * @return array requested table * @protected */ diff --git a/lib/combodo/tcpdf/tcpdf_barcodes_2d.php b/lib/combodo/tcpdf/tcpdf_barcodes_2d.php index 13e2365527..730361bd8c 100644 --- a/lib/combodo/tcpdf/tcpdf_barcodes_2d.php +++ b/lib/combodo/tcpdf/tcpdf_barcodes_2d.php @@ -53,7 +53,7 @@ class TCPDF2DBarcode { * Array representation of barcode. * @protected */ - protected $barcode_array = false; + protected $barcode_array = array(); /** * This is the class constructor. @@ -62,8 +62,8 @@ class TCPDF2DBarcode { *
                  • $arrcode['num_rows'] required number of rows
                  • *
                  • $arrcode['num_cols'] required number of columns
                  • *
                  • $arrcode['bcode'][$r][$c] value of the cell is $r row and $c column (0 = transparent, 1 = black)
                  - * @param $code (string) code to print - * @param $type (string) type of barcode:
                  • DATAMATRIX : Datamatrix (ISO/IEC 16022)
                  • PDF417 : PDF417 (ISO/IEC 15438:2006)
                  • PDF417,a,e,t,s,f,o0,o1,o2,o3,o4,o5,o6 : PDF417 with parameters: a = aspect ratio (width/height); e = error correction level (0-8); t = total number of macro segments; s = macro segment index (0-99998); f = file ID; o0 = File Name (text); o1 = Segment Count (numeric); o2 = Time Stamp (numeric); o3 = Sender (text); o4 = Addressee (text); o5 = File Size (numeric); o6 = Checksum (numeric). NOTES: Parameters t, s and f are required for a Macro Control Block, all other parametrs are optional. To use a comma character ',' on text options, replace it with the character 255: "\xff".
                  • QRCODE : QRcode Low error correction
                  • QRCODE,L : QRcode Low error correction
                  • QRCODE,M : QRcode Medium error correction
                  • QRCODE,Q : QRcode Better error correction
                  • QRCODE,H : QR-CODE Best error correction
                  • RAW: raw mode - comma-separad list of array rows
                  • RAW2: raw mode - array rows are surrounded by square parenthesis.
                  • TEST : Test matrix
                  + * @param string $code code to print + * @param string $type type of barcode:
                  • DATAMATRIX : Datamatrix (ISO/IEC 16022)
                  • PDF417 : PDF417 (ISO/IEC 15438:2006)
                  • PDF417,a,e,t,s,f,o0,o1,o2,o3,o4,o5,o6 : PDF417 with parameters: a = aspect ratio (width/height); e = error correction level (0-8); t = total number of macro segments; s = macro segment index (0-99998); f = file ID; o0 = File Name (text); o1 = Segment Count (numeric); o2 = Time Stamp (numeric); o3 = Sender (text); o4 = Addressee (text); o5 = File Size (numeric); o6 = Checksum (numeric). NOTES: Parameters t, s and f are required for a Macro Control Block, all other parameters are optional. To use a comma character ',' on text options, replace it with the character 255: "\xff".
                  • QRCODE : QRcode Low error correction
                  • QRCODE,L : QRcode Low error correction
                  • QRCODE,M : QRcode Medium error correction
                  • QRCODE,Q : QRcode Better error correction
                  • QRCODE,H : QR-CODE Best error correction
                  • RAW: raw mode - comma-separad list of array rows
                  • RAW2: raw mode - array rows are surrounded by square parenthesis.
                  • TEST : Test matrix
                  */ public function __construct($code, $type) { $this->setBarcode($code, $type); @@ -79,9 +79,9 @@ class TCPDF2DBarcode { /** * Send barcode as SVG image object to the standard output. - * @param $w (int) Width of a single rectangle element in user units. - * @param $h (int) Height of a single rectangle element in user units. - * @param $color (string) Foreground color (in SVG format) for bar elements (background is transparent). + * @param int $w Width of a single rectangle element in user units. + * @param int $h Height of a single rectangle element in user units. + * @param string $color Foreground color (in SVG format) for bar elements (background is transparent). * @public */ public function getBarcodeSVG($w=3, $h=3, $color='black') { @@ -99,9 +99,9 @@ class TCPDF2DBarcode { /** * Return a SVG string representation of barcode. - * @param $w (int) Width of a single rectangle element in user units. - * @param $h (int) Height of a single rectangle element in user units. - * @param $color (string) Foreground color (in SVG format) for bar elements (background is transparent). + * @param int $w Width of a single rectangle element in user units. + * @param int $h Height of a single rectangle element in user units. + * @param string $color Foreground color (in SVG format) for bar elements (background is transparent). * @return string SVG code. * @public */ @@ -135,9 +135,9 @@ class TCPDF2DBarcode { /** * Return an HTML representation of barcode. - * @param $w (int) Width of a single rectangle element in pixels. - * @param $h (int) Height of a single rectangle element in pixels. - * @param $color (string) Foreground color for bar elements (background is transparent). + * @param int $w Width of a single rectangle element in pixels. + * @param int $h Height of a single rectangle element in pixels. + * @param string $color Foreground color for bar elements (background is transparent). * @return string HTML code. * @public */ @@ -164,9 +164,9 @@ class TCPDF2DBarcode { /** * Send a PNG image representation of barcode (requires GD or Imagick library). - * @param $w (int) Width of a single rectangle element in pixels. - * @param $h (int) Height of a single rectangle element in pixels. - * @param $color (array) RGB (0-255) foreground color for bar elements (background is transparent). + * @param int $w Width of a single rectangle element in pixels. + * @param int $h Height of a single rectangle element in pixels. + * @param array $color RGB (0-255) foreground color for bar elements (background is transparent). * @public */ public function getBarcodePNG($w=3, $h=3, $color=array(0,0,0)) { @@ -184,10 +184,10 @@ class TCPDF2DBarcode { /** * Return a PNG image representation of barcode (requires GD or Imagick library). - * @param $w (int) Width of a single rectangle element in pixels. - * @param $h (int) Height of a single rectangle element in pixels. - * @param $color (array) RGB (0-255) foreground color for bar elements (background is transparent). - * @return image or false in case of error. + * @param int $w Width of a single rectangle element in pixels. + * @param int $h Height of a single rectangle element in pixels. + * @param array $color RGB (0-255) foreground color for bar elements (background is transparent). + * @return string|Imagick|false image or false in case of error. * @public */ public function getBarcodePngData($w=3, $h=3, $color=array(0,0,0)) { @@ -245,9 +245,9 @@ class TCPDF2DBarcode { /** * Set the barcode. - * @param $code (string) code to print - * @param $type (string) type of barcode:
                  • DATAMATRIX : Datamatrix (ISO/IEC 16022)
                  • PDF417 : PDF417 (ISO/IEC 15438:2006)
                  • PDF417,a,e,t,s,f,o0,o1,o2,o3,o4,o5,o6 : PDF417 with parameters: a = aspect ratio (width/height); e = error correction level (0-8); t = total number of macro segments; s = macro segment index (0-99998); f = file ID; o0 = File Name (text); o1 = Segment Count (numeric); o2 = Time Stamp (numeric); o3 = Sender (text); o4 = Addressee (text); o5 = File Size (numeric); o6 = Checksum (numeric). NOTES: Parameters t, s and f are required for a Macro Control Block, all other parametrs are optional. To use a comma character ',' on text options, replace it with the character 255: "\xff".
                  • QRCODE : QRcode Low error correction
                  • QRCODE,L : QRcode Low error correction
                  • QRCODE,M : QRcode Medium error correction
                  • QRCODE,Q : QRcode Better error correction
                  • QRCODE,H : QR-CODE Best error correction
                  • RAW: raw mode - comma-separad list of array rows
                  • RAW2: raw mode - array rows are surrounded by square parenthesis.
                  • TEST : Test matrix
                  - * @return array + * @param string $code code to print + * @param string $type type of barcode:
                  • DATAMATRIX : Datamatrix (ISO/IEC 16022)
                  • PDF417 : PDF417 (ISO/IEC 15438:2006)
                  • PDF417,a,e,t,s,f,o0,o1,o2,o3,o4,o5,o6 : PDF417 with parameters: a = aspect ratio (width/height); e = error correction level (0-8); t = total number of macro segments; s = macro segment index (0-99998); f = file ID; o0 = File Name (text); o1 = Segment Count (numeric); o2 = Time Stamp (numeric); o3 = Sender (text); o4 = Addressee (text); o5 = File Size (numeric); o6 = Checksum (numeric). NOTES: Parameters t, s and f are required for a Macro Control Block, all other parameters are optional. To use a comma character ',' on text options, replace it with the character 255: "\xff".
                  • QRCODE : QRcode Low error correction
                  • QRCODE,L : QRcode Low error correction
                  • QRCODE,M : QRcode Medium error correction
                  • QRCODE,Q : QRcode Better error correction
                  • QRCODE,H : QR-CODE Best error correction
                  • RAW: raw mode - comma-separad list of array rows
                  • RAW2: raw mode - array rows are surrounded by square parenthesis.
                  • TEST : Test matrix
                  + * @return void */ public function setBarcode($code, $type) { $mode = explode(',', $type); @@ -338,7 +338,7 @@ class TCPDF2DBarcode { break; } default: { - $this->barcode_array = false; + $this->barcode_array = array(); } } } diff --git a/lib/combodo/tcpdf/tcpdf_import.php b/lib/combodo/tcpdf/tcpdf_import.php index 09d726ba03..cc6fda7808 100644 --- a/lib/combodo/tcpdf/tcpdf_import.php +++ b/lib/combodo/tcpdf/tcpdf_import.php @@ -60,7 +60,7 @@ class TCPDF_IMPORT extends TCPDF { /** * Import an existing PDF document - * @param $filename (string) Filename of the PDF document to import. + * @param string $filename Filename of the PDF document to import. * @return true in case of success, false otherwise * @public * @since 1.0.000 (2011-05-24) diff --git a/lib/combodo/tcpdf/tcpdf_parser.php b/lib/combodo/tcpdf/tcpdf_parser.php index bd3d719c7a..4156230a38 100644 --- a/lib/combodo/tcpdf/tcpdf_parser.php +++ b/lib/combodo/tcpdf/tcpdf_parser.php @@ -91,8 +91,8 @@ class TCPDF_PARSER { /** * Parse a PDF document an return an array of objects. - * @param $data (string) PDF data to parse. - * @param $cfg (array) Array of configuration parameters: + * @param string $data PDF data to parse. + * @param array $cfg Array of configuration parameters: * 'die_for_errors' : if true termitate the program execution in case of error, otherwise thows an exception; * 'ignore_filter_decoding_errors' : if true ignore filter decoding errors; * 'ignore_missing_filter_decoders' : if true ignore missing filter decoding errors. @@ -130,7 +130,7 @@ class TCPDF_PARSER { /** * Set the configuration parameters. - * @param $cfg (array) Array of configuration parameters: + * @param array $cfg Array of configuration parameters: * 'die_for_errors' : if true termitate the program execution in case of error, otherwise thows an exception; * 'ignore_filter_decoding_errors' : if true ignore filter decoding errors; * 'ignore_missing_filter_decoders' : if true ignore missing filter decoding errors. @@ -150,7 +150,7 @@ class TCPDF_PARSER { /** * Return an array of parsed PDF document objects. - * @return (array) Array of parsed PDF document objects. + * @return array Array of parsed PDF document objects. * @public * @since 1.0.000 (2011-06-26) */ @@ -160,9 +160,9 @@ class TCPDF_PARSER { /** * Get Cross-Reference (xref) table and trailer data from PDF document data. - * @param $offset (int) xref offset (if know). - * @param $xref (array) previous xref array (if any). - * @return Array containing xref and trailer data. + * @param int $offset xref offset (if know). + * @param array $xref previous xref array (if any). + * @return array containing xref and trailer data. * @protected * @since 1.0.000 (2011-05-24) */ @@ -202,9 +202,9 @@ class TCPDF_PARSER { /** * Decode the Cross-Reference section - * @param $startxref (int) Offset at which the xref section starts (position of the 'xref' keyword). - * @param $xref (array) Previous xref array (if any). - * @return Array containing xref and trailer data. + * @param int $startxref Offset at which the xref section starts (position of the 'xref' keyword). + * @param array $xref Previous xref array (if any). + * @return array containing xref and trailer data. * @protected * @since 1.0.000 (2011-06-20) */ @@ -274,9 +274,9 @@ class TCPDF_PARSER { /** * Decode the Cross-Reference Stream section - * @param $startxref (int) Offset at which the xref section starts. - * @param $xref (array) Previous xref array (if any). - * @return Array containing xref and trailer data. + * @param int $startxref Offset at which the xref section starts. + * @param array $xref Previous xref array (if any). + * @return array containing xref and trailer data. * @protected * @since 1.0.003 (2013-03-16) */ @@ -489,7 +489,7 @@ class TCPDF_PARSER { /** * Get object type, raw value and offset to next object - * @param $offset (int) Object offset. + * @param int $offset Object offset. * @return array containing object type, raw value and offset to next object * @protected * @since 1.0.000 (2011-06-20) @@ -667,9 +667,9 @@ class TCPDF_PARSER { /** * Get content of indirect object. - * @param $obj_ref (string) Object number and generation number separated by underscore character. - * @param $offset (int) Object offset. - * @param $decoding (boolean) If true decode streams. + * @param string $obj_ref Object number and generation number separated by underscore character. + * @param int $offset Object offset. + * @param boolean $decoding If true decode streams. * @return array containing object data. * @protected * @since 1.0.000 (2011-05-24) @@ -712,7 +712,7 @@ class TCPDF_PARSER { /** * Get the content of object, resolving indect object reference if necessary. - * @param $obj (string) Object value. + * @param string $obj Object value. * @return array containing object data. * @protected * @since 1.0.000 (2011-06-26) @@ -734,8 +734,8 @@ class TCPDF_PARSER { /** * Decode the specified stream. - * @param $sdic (array) Stream's dictionary array. - * @param $stream (string) Stream to decode. + * @param array $sdic Stream's dictionary array. + * @param string $stream Stream to decode. * @return array containing decoded stream data and remaining filters. * @protected * @since 1.0.000 (2011-06-22) @@ -796,7 +796,7 @@ class TCPDF_PARSER { /** * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true. - * @param $msg (string) The error message + * @param string $msg The error message * @public * @since 1.0.000 (2011-05-23) */ diff --git a/lib/composer/InstalledVersions.php b/lib/composer/InstalledVersions.php index 7c5502ca43..d50e0c9fcc 100644 --- a/lib/composer/InstalledVersions.php +++ b/lib/composer/InstalledVersions.php @@ -24,8 +24,21 @@ use Composer\Semver\VersionParser; */ class InstalledVersions { + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}|array{}|null + */ private static $installed; + + /** + * @var bool|null + */ private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ private static $installedByVendor = array(); /** diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index f8ce6c4f2b..4cfba76c21 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -26,10 +26,9 @@ return array( 'ApplicationPopupMenuItem' => $baseDir . '/application/applicationextension.inc.php', 'Archive_Tar' => $vendorDir . '/pear/archive_tar/Archive/Tar.php', 'ArchivedObjectException' => $baseDir . '/application/exceptions/ArchivedObjectException.php', - 'ArithmeticError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php', - 'AssertionError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php', 'AsyncSendEmail' => $baseDir . '/core/asynctask.class.inc.php', 'AsyncTask' => $baseDir . '/core/asynctask.class.inc.php', + 'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'AttributeApplicationLanguage' => $baseDir . '/core/attributedef.class.inc.php', 'AttributeArchiveDate' => $baseDir . '/core/attributedef.class.inc.php', 'AttributeArchiveFlag' => $baseDir . '/core/attributedef.class.inc.php', @@ -100,6 +99,53 @@ return array( 'BulkExportMissingParameterException' => $baseDir . '/core/bulkexport.class.inc.php', 'BulkExportResult' => $baseDir . '/core/bulkexport.class.inc.php', 'BulkExportResultGC' => $baseDir . '/core/bulkexport.class.inc.php', + 'CAS_AuthenticationException' => $vendorDir . '/apereo/phpcas/source/CAS/AuthenticationException.php', + 'CAS_Client' => $vendorDir . '/apereo/phpcas/source/CAS/Client.php', + 'CAS_CookieJar' => $vendorDir . '/apereo/phpcas/source/CAS/CookieJar.php', + 'CAS_Exception' => $vendorDir . '/apereo/phpcas/source/CAS/Exception.php', + 'CAS_GracefullTerminationException' => $vendorDir . '/apereo/phpcas/source/CAS/GracefullTerminationException.php', + 'CAS_InvalidArgumentException' => $vendorDir . '/apereo/phpcas/source/CAS/InvalidArgumentException.php', + 'CAS_Languages_Catalan' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/Catalan.php', + 'CAS_Languages_ChineseSimplified' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/ChineseSimplified.php', + 'CAS_Languages_English' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/English.php', + 'CAS_Languages_French' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/French.php', + 'CAS_Languages_Galego' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/Galego.php', + 'CAS_Languages_German' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/German.php', + 'CAS_Languages_Greek' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/Greek.php', + 'CAS_Languages_Japanese' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/Japanese.php', + 'CAS_Languages_LanguageInterface' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/LanguageInterface.php', + 'CAS_Languages_Portuguese' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/Portuguese.php', + 'CAS_Languages_Spanish' => $vendorDir . '/apereo/phpcas/source/CAS/Languages/Spanish.php', + 'CAS_OutOfSequenceBeforeAuthenticationCallException' => $vendorDir . '/apereo/phpcas/source/CAS/OutOfSequenceBeforeAuthenticationCallException.php', + 'CAS_OutOfSequenceBeforeClientException' => $vendorDir . '/apereo/phpcas/source/CAS/OutOfSequenceBeforeClientException.php', + 'CAS_OutOfSequenceBeforeProxyException' => $vendorDir . '/apereo/phpcas/source/CAS/OutOfSequenceBeforeProxyException.php', + 'CAS_OutOfSequenceException' => $vendorDir . '/apereo/phpcas/source/CAS/OutOfSequenceException.php', + 'CAS_PGTStorage_AbstractStorage' => $vendorDir . '/apereo/phpcas/source/CAS/PGTStorage/AbstractStorage.php', + 'CAS_PGTStorage_Db' => $vendorDir . '/apereo/phpcas/source/CAS/PGTStorage/Db.php', + 'CAS_PGTStorage_File' => $vendorDir . '/apereo/phpcas/source/CAS/PGTStorage/File.php', + 'CAS_ProxiedService' => $vendorDir . '/apereo/phpcas/source/CAS/ProxiedService.php', + 'CAS_ProxiedService_Abstract' => $vendorDir . '/apereo/phpcas/source/CAS/ProxiedService/Abstract.php', + 'CAS_ProxiedService_Exception' => $vendorDir . '/apereo/phpcas/source/CAS/ProxiedService/Exception.php', + 'CAS_ProxiedService_Http' => $vendorDir . '/apereo/phpcas/source/CAS/ProxiedService/Http.php', + 'CAS_ProxiedService_Http_Abstract' => $vendorDir . '/apereo/phpcas/source/CAS/ProxiedService/Http/Abstract.php', + 'CAS_ProxiedService_Http_Get' => $vendorDir . '/apereo/phpcas/source/CAS/ProxiedService/Http/Get.php', + 'CAS_ProxiedService_Http_Post' => $vendorDir . '/apereo/phpcas/source/CAS/ProxiedService/Http/Post.php', + 'CAS_ProxiedService_Imap' => $vendorDir . '/apereo/phpcas/source/CAS/ProxiedService/Imap.php', + 'CAS_ProxiedService_Testable' => $vendorDir . '/apereo/phpcas/source/CAS/ProxiedService/Testable.php', + 'CAS_ProxyChain' => $vendorDir . '/apereo/phpcas/source/CAS/ProxyChain.php', + 'CAS_ProxyChain_AllowedList' => $vendorDir . '/apereo/phpcas/source/CAS/ProxyChain/AllowedList.php', + 'CAS_ProxyChain_Any' => $vendorDir . '/apereo/phpcas/source/CAS/ProxyChain/Any.php', + 'CAS_ProxyChain_Interface' => $vendorDir . '/apereo/phpcas/source/CAS/ProxyChain/Interface.php', + 'CAS_ProxyChain_Trusted' => $vendorDir . '/apereo/phpcas/source/CAS/ProxyChain/Trusted.php', + 'CAS_ProxyTicketException' => $vendorDir . '/apereo/phpcas/source/CAS/ProxyTicketException.php', + 'CAS_Request_AbstractRequest' => $vendorDir . '/apereo/phpcas/source/CAS/Request/AbstractRequest.php', + 'CAS_Request_CurlMultiRequest' => $vendorDir . '/apereo/phpcas/source/CAS/Request/CurlMultiRequest.php', + 'CAS_Request_CurlRequest' => $vendorDir . '/apereo/phpcas/source/CAS/Request/CurlRequest.php', + 'CAS_Request_Exception' => $vendorDir . '/apereo/phpcas/source/CAS/Request/Exception.php', + 'CAS_Request_MultiRequestInterface' => $vendorDir . '/apereo/phpcas/source/CAS/Request/MultiRequestInterface.php', + 'CAS_Request_RequestInterface' => $vendorDir . '/apereo/phpcas/source/CAS/Request/RequestInterface.php', + 'CAS_Session_PhpSession' => $vendorDir . '/apereo/phpcas/source/CAS/Session/PhpSession.php', + 'CAS_TypeMismatchException' => $vendorDir . '/apereo/phpcas/source/CAS/TypeMismatchException.php', 'CLILikeWebPage' => $baseDir . '/sources/Application/WebPage/CLILikeWebPage.php', 'CLIPage' => $baseDir . '/sources/Application/WebPage/CLIPage.php', 'CMDBChange' => $baseDir . '/core/cmdbchange.class.inc.php', @@ -299,9 +345,17 @@ return array( 'Combodo\\iTop\\Composer\\iTopComposer' => $baseDir . '/sources/Composer/iTopComposer.php', 'Combodo\\iTop\\Controller\\AjaxRenderController' => $baseDir . '/sources/Controller/AjaxRenderController.php', 'Combodo\\iTop\\Controller\\Base\\Layout\\ActivityPanelController' => $baseDir . '/sources/Controller/Base/Layout/ActivityPanelController.php', + 'Combodo\\iTop\\Controller\\OAuth\\OAuthLandingController' => $baseDir . '/sources/Controller/OAuth/OAuthLandingController.php', 'Combodo\\iTop\\Controller\\PreferencesController' => $baseDir . '/sources/Controller/PreferencesController.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientProvider' => $baseDir . '/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAbstract' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAzure' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderFactory' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderGoogle' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php', 'Combodo\\iTop\\Core\\CMDBChange\\CMDBChangeOrigin' => $baseDir . '/sources/Core/CMDBChange/CMDBChangeOrigin.php', 'Combodo\\iTop\\Core\\DbConnectionWrapper' => $baseDir . '/core/DbConnectionWrapper.php', + 'Combodo\\iTop\\Core\\Email\\EmailFactory' => $baseDir . '/sources/Core/Email/EmailFactory.php', + 'Combodo\\iTop\\Core\\Email\\iEMail' => $baseDir . '/sources/Core/Email/iEMail.php', 'Combodo\\iTop\\Core\\MetaModel\\FriendlyNameType' => $baseDir . '/sources/Core/MetaModel/FriendlyNameType.php', 'Combodo\\iTop\\Core\\MetaModel\\HierarchicalKey' => $baseDir . '/sources/Core/MetaModel/HierarchicalKey.php', 'Combodo\\iTop\\DesignDocument' => $baseDir . '/core/designdocument.class.inc.php', @@ -356,7 +410,6 @@ return array( 'Combodo\\iTop\\Renderer\\FieldRenderer' => $baseDir . '/sources/Renderer/FieldRenderer.php', 'Combodo\\iTop\\Renderer\\FormRenderer' => $baseDir . '/sources/Renderer/FormRenderer.php', 'Combodo\\iTop\\Renderer\\RenderingOutput' => $baseDir . '/sources/Renderer/RenderingOutput.php', - 'Combodo\\iTop\\TwigExtension' => $baseDir . '/application/twigextension.class.inc.php', 'CompileCSSService' => $baseDir . '/application/compilecssservice.class.inc.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'Config' => $baseDir . '/core/config.class.inc.php', @@ -376,6 +429,7 @@ return array( 'CryptEngine' => $baseDir . '/core/simplecrypt.class.inc.php', 'CustomFieldsHandler' => $baseDir . '/core/customfieldshandler.class.inc.php', 'DBObject' => $baseDir . '/core/dbobject.class.php', + 'DBObjectSearch' => $baseDir . '/core/dbobjectsearch.class.php', 'DBObjectSet' => $baseDir . '/core/dbobjectset.class.php', 'DBObjectSetComparator' => $baseDir . '/core/dbobjectset.class.php', 'DBProperty' => $baseDir . '/core/dbproperty.class.inc.php', @@ -441,10 +495,9 @@ return array( 'DisplayableGroupNode' => $baseDir . '/core/displayablegraph.class.inc.php', 'DisplayableNode' => $baseDir . '/core/displayablegraph.class.inc.php', 'DisplayableRedundancyNode' => $baseDir . '/core/displayablegraph.class.inc.php', - 'DivisionByZeroError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php', 'DownloadPage' => $baseDir . '/sources/Application/WebPage/DownloadPage.php', 'EMail' => $baseDir . '/core/email.class.inc.php', - 'Error' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/Error.php', + 'EMailLaminas' => $baseDir . '/sources/Core/Email/EmailLaminas.php', 'ErrorPage' => $baseDir . '/sources/Application/WebPage/ErrorPage.php', 'Event' => $baseDir . '/core/event.class.inc.php', 'EventIssue' => $baseDir . '/core/event.class.inc.php', @@ -472,11 +525,104 @@ return array( 'FilterFromAttribute' => $baseDir . '/core/filterdef.class.inc.php', 'FilterPrivateKey' => $baseDir . '/core/filterdef.class.inc.php', 'FindStylesheetObject' => $baseDir . '/application/findstylesheetobject.class.inc.php', + 'Firebase\\JWT\\BeforeValidException' => $vendorDir . '/firebase/php-jwt/src/BeforeValidException.php', + 'Firebase\\JWT\\CachedKeySet' => $vendorDir . '/firebase/php-jwt/src/CachedKeySet.php', + 'Firebase\\JWT\\ExpiredException' => $vendorDir . '/firebase/php-jwt/src/ExpiredException.php', + 'Firebase\\JWT\\JWK' => $vendorDir . '/firebase/php-jwt/src/JWK.php', + 'Firebase\\JWT\\JWT' => $vendorDir . '/firebase/php-jwt/src/JWT.php', + 'Firebase\\JWT\\Key' => $vendorDir . '/firebase/php-jwt/src/Key.php', + 'Firebase\\JWT\\SignatureInvalidException' => $vendorDir . '/firebase/php-jwt/src/SignatureInvalidException.php', 'FunctionExpression' => $baseDir . '/core/oql/expression.class.inc.php', 'FunctionOqlExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php', 'GraphEdge' => $baseDir . '/core/simplegraph.class.inc.php', 'GraphElement' => $baseDir . '/core/simplegraph.class.inc.php', 'GraphNode' => $baseDir . '/core/simplegraph.class.inc.php', + 'GuzzleHttp\\BodySummarizer' => $vendorDir . '/guzzlehttp/guzzle/src/BodySummarizer.php', + 'GuzzleHttp\\BodySummarizerInterface' => $vendorDir . '/guzzlehttp/guzzle/src/BodySummarizerInterface.php', + 'GuzzleHttp\\Client' => $vendorDir . '/guzzlehttp/guzzle/src/Client.php', + 'GuzzleHttp\\ClientInterface' => $vendorDir . '/guzzlehttp/guzzle/src/ClientInterface.php', + 'GuzzleHttp\\ClientTrait' => $vendorDir . '/guzzlehttp/guzzle/src/ClientTrait.php', + 'GuzzleHttp\\Cookie\\CookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/CookieJar.php', + 'GuzzleHttp\\Cookie\\CookieJarInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php', + 'GuzzleHttp\\Cookie\\FileCookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php', + 'GuzzleHttp\\Cookie\\SessionCookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php', + 'GuzzleHttp\\Cookie\\SetCookie' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/SetCookie.php', + 'GuzzleHttp\\Exception\\BadResponseException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/BadResponseException.php', + 'GuzzleHttp\\Exception\\ClientException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ClientException.php', + 'GuzzleHttp\\Exception\\ConnectException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ConnectException.php', + 'GuzzleHttp\\Exception\\GuzzleException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/GuzzleException.php', + 'GuzzleHttp\\Exception\\InvalidArgumentException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php', + 'GuzzleHttp\\Exception\\RequestException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/RequestException.php', + 'GuzzleHttp\\Exception\\ServerException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ServerException.php', + 'GuzzleHttp\\Exception\\TooManyRedirectsException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php', + 'GuzzleHttp\\Exception\\TransferException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/TransferException.php', + 'GuzzleHttp\\HandlerStack' => $vendorDir . '/guzzlehttp/guzzle/src/HandlerStack.php', + 'GuzzleHttp\\Handler\\CurlFactory' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlFactory.php', + 'GuzzleHttp\\Handler\\CurlFactoryInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php', + 'GuzzleHttp\\Handler\\CurlHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlHandler.php', + 'GuzzleHttp\\Handler\\CurlMultiHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php', + 'GuzzleHttp\\Handler\\EasyHandle' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/EasyHandle.php', + 'GuzzleHttp\\Handler\\HeaderProcessor' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php', + 'GuzzleHttp\\Handler\\MockHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/MockHandler.php', + 'GuzzleHttp\\Handler\\Proxy' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/Proxy.php', + 'GuzzleHttp\\Handler\\StreamHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/StreamHandler.php', + 'GuzzleHttp\\MessageFormatter' => $vendorDir . '/guzzlehttp/guzzle/src/MessageFormatter.php', + 'GuzzleHttp\\MessageFormatterInterface' => $vendorDir . '/guzzlehttp/guzzle/src/MessageFormatterInterface.php', + 'GuzzleHttp\\Middleware' => $vendorDir . '/guzzlehttp/guzzle/src/Middleware.php', + 'GuzzleHttp\\Pool' => $vendorDir . '/guzzlehttp/guzzle/src/Pool.php', + 'GuzzleHttp\\PrepareBodyMiddleware' => $vendorDir . '/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php', + 'GuzzleHttp\\Promise\\AggregateException' => $vendorDir . '/guzzlehttp/promises/src/AggregateException.php', + 'GuzzleHttp\\Promise\\CancellationException' => $vendorDir . '/guzzlehttp/promises/src/CancellationException.php', + 'GuzzleHttp\\Promise\\Coroutine' => $vendorDir . '/guzzlehttp/promises/src/Coroutine.php', + 'GuzzleHttp\\Promise\\Create' => $vendorDir . '/guzzlehttp/promises/src/Create.php', + 'GuzzleHttp\\Promise\\Each' => $vendorDir . '/guzzlehttp/promises/src/Each.php', + 'GuzzleHttp\\Promise\\EachPromise' => $vendorDir . '/guzzlehttp/promises/src/EachPromise.php', + 'GuzzleHttp\\Promise\\FulfilledPromise' => $vendorDir . '/guzzlehttp/promises/src/FulfilledPromise.php', + 'GuzzleHttp\\Promise\\Is' => $vendorDir . '/guzzlehttp/promises/src/Is.php', + 'GuzzleHttp\\Promise\\Promise' => $vendorDir . '/guzzlehttp/promises/src/Promise.php', + 'GuzzleHttp\\Promise\\PromiseInterface' => $vendorDir . '/guzzlehttp/promises/src/PromiseInterface.php', + 'GuzzleHttp\\Promise\\PromisorInterface' => $vendorDir . '/guzzlehttp/promises/src/PromisorInterface.php', + 'GuzzleHttp\\Promise\\RejectedPromise' => $vendorDir . '/guzzlehttp/promises/src/RejectedPromise.php', + 'GuzzleHttp\\Promise\\RejectionException' => $vendorDir . '/guzzlehttp/promises/src/RejectionException.php', + 'GuzzleHttp\\Promise\\TaskQueue' => $vendorDir . '/guzzlehttp/promises/src/TaskQueue.php', + 'GuzzleHttp\\Promise\\TaskQueueInterface' => $vendorDir . '/guzzlehttp/promises/src/TaskQueueInterface.php', + 'GuzzleHttp\\Promise\\Utils' => $vendorDir . '/guzzlehttp/promises/src/Utils.php', + 'GuzzleHttp\\Psr7\\AppendStream' => $vendorDir . '/guzzlehttp/psr7/src/AppendStream.php', + 'GuzzleHttp\\Psr7\\BufferStream' => $vendorDir . '/guzzlehttp/psr7/src/BufferStream.php', + 'GuzzleHttp\\Psr7\\CachingStream' => $vendorDir . '/guzzlehttp/psr7/src/CachingStream.php', + 'GuzzleHttp\\Psr7\\DroppingStream' => $vendorDir . '/guzzlehttp/psr7/src/DroppingStream.php', + 'GuzzleHttp\\Psr7\\Exception\\MalformedUriException' => $vendorDir . '/guzzlehttp/psr7/src/Exception/MalformedUriException.php', + 'GuzzleHttp\\Psr7\\FnStream' => $vendorDir . '/guzzlehttp/psr7/src/FnStream.php', + 'GuzzleHttp\\Psr7\\Header' => $vendorDir . '/guzzlehttp/psr7/src/Header.php', + 'GuzzleHttp\\Psr7\\HttpFactory' => $vendorDir . '/guzzlehttp/psr7/src/HttpFactory.php', + 'GuzzleHttp\\Psr7\\InflateStream' => $vendorDir . '/guzzlehttp/psr7/src/InflateStream.php', + 'GuzzleHttp\\Psr7\\LazyOpenStream' => $vendorDir . '/guzzlehttp/psr7/src/LazyOpenStream.php', + 'GuzzleHttp\\Psr7\\LimitStream' => $vendorDir . '/guzzlehttp/psr7/src/LimitStream.php', + 'GuzzleHttp\\Psr7\\Message' => $vendorDir . '/guzzlehttp/psr7/src/Message.php', + 'GuzzleHttp\\Psr7\\MessageTrait' => $vendorDir . '/guzzlehttp/psr7/src/MessageTrait.php', + 'GuzzleHttp\\Psr7\\MimeType' => $vendorDir . '/guzzlehttp/psr7/src/MimeType.php', + 'GuzzleHttp\\Psr7\\MultipartStream' => $vendorDir . '/guzzlehttp/psr7/src/MultipartStream.php', + 'GuzzleHttp\\Psr7\\NoSeekStream' => $vendorDir . '/guzzlehttp/psr7/src/NoSeekStream.php', + 'GuzzleHttp\\Psr7\\PumpStream' => $vendorDir . '/guzzlehttp/psr7/src/PumpStream.php', + 'GuzzleHttp\\Psr7\\Query' => $vendorDir . '/guzzlehttp/psr7/src/Query.php', + 'GuzzleHttp\\Psr7\\Request' => $vendorDir . '/guzzlehttp/psr7/src/Request.php', + 'GuzzleHttp\\Psr7\\Response' => $vendorDir . '/guzzlehttp/psr7/src/Response.php', + 'GuzzleHttp\\Psr7\\Rfc7230' => $vendorDir . '/guzzlehttp/psr7/src/Rfc7230.php', + 'GuzzleHttp\\Psr7\\ServerRequest' => $vendorDir . '/guzzlehttp/psr7/src/ServerRequest.php', + 'GuzzleHttp\\Psr7\\Stream' => $vendorDir . '/guzzlehttp/psr7/src/Stream.php', + 'GuzzleHttp\\Psr7\\StreamDecoratorTrait' => $vendorDir . '/guzzlehttp/psr7/src/StreamDecoratorTrait.php', + 'GuzzleHttp\\Psr7\\StreamWrapper' => $vendorDir . '/guzzlehttp/psr7/src/StreamWrapper.php', + 'GuzzleHttp\\Psr7\\UploadedFile' => $vendorDir . '/guzzlehttp/psr7/src/UploadedFile.php', + 'GuzzleHttp\\Psr7\\Uri' => $vendorDir . '/guzzlehttp/psr7/src/Uri.php', + 'GuzzleHttp\\Psr7\\UriComparator' => $vendorDir . '/guzzlehttp/psr7/src/UriComparator.php', + 'GuzzleHttp\\Psr7\\UriNormalizer' => $vendorDir . '/guzzlehttp/psr7/src/UriNormalizer.php', + 'GuzzleHttp\\Psr7\\UriResolver' => $vendorDir . '/guzzlehttp/psr7/src/UriResolver.php', + 'GuzzleHttp\\Psr7\\Utils' => $vendorDir . '/guzzlehttp/psr7/src/Utils.php', + 'GuzzleHttp\\RedirectMiddleware' => $vendorDir . '/guzzlehttp/guzzle/src/RedirectMiddleware.php', + 'GuzzleHttp\\RequestOptions' => $vendorDir . '/guzzlehttp/guzzle/src/RequestOptions.php', + 'GuzzleHttp\\RetryMiddleware' => $vendorDir . '/guzzlehttp/guzzle/src/RetryMiddleware.php', + 'GuzzleHttp\\TransferStats' => $vendorDir . '/guzzlehttp/guzzle/src/TransferStats.php', + 'GuzzleHttp\\Utils' => $vendorDir . '/guzzlehttp/guzzle/src/Utils.php', 'HTMLBulkExport' => $baseDir . '/core/htmlbulkexport.class.inc.php', 'HTMLDOMSanitizer' => $baseDir . '/core/htmlsanitizer.class.inc.php', 'HTMLNullSanitizer' => $baseDir . '/core/htmlsanitizer.class.inc.php', @@ -496,8 +642,350 @@ return array( 'ItopCounter' => $baseDir . '/core/counter.class.inc.php', 'JSButtonItem' => $baseDir . '/application/applicationextension.inc.php', 'JSPopupMenuItem' => $baseDir . '/application/applicationextension.inc.php', + 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'JsonPage' => $baseDir . '/sources/Application/WebPage/JsonPage.php', 'KeyValueStore' => $baseDir . '/core/counter.class.inc.php', + 'Laminas\\Loader\\AutoloaderFactory' => $vendorDir . '/laminas/laminas-loader/src/AutoloaderFactory.php', + 'Laminas\\Loader\\ClassMapAutoloader' => $vendorDir . '/laminas/laminas-loader/src/ClassMapAutoloader.php', + 'Laminas\\Loader\\Exception\\BadMethodCallException' => $vendorDir . '/laminas/laminas-loader/src/Exception/BadMethodCallException.php', + 'Laminas\\Loader\\Exception\\DomainException' => $vendorDir . '/laminas/laminas-loader/src/Exception/DomainException.php', + 'Laminas\\Loader\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-loader/src/Exception/ExceptionInterface.php', + 'Laminas\\Loader\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-loader/src/Exception/InvalidArgumentException.php', + 'Laminas\\Loader\\Exception\\InvalidPathException' => $vendorDir . '/laminas/laminas-loader/src/Exception/InvalidPathException.php', + 'Laminas\\Loader\\Exception\\MissingResourceNamespaceException' => $vendorDir . '/laminas/laminas-loader/src/Exception/MissingResourceNamespaceException.php', + 'Laminas\\Loader\\Exception\\PluginLoaderException' => $vendorDir . '/laminas/laminas-loader/src/Exception/PluginLoaderException.php', + 'Laminas\\Loader\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-loader/src/Exception/RuntimeException.php', + 'Laminas\\Loader\\Exception\\SecurityException' => $vendorDir . '/laminas/laminas-loader/src/Exception/SecurityException.php', + 'Laminas\\Loader\\ModuleAutoloader' => $vendorDir . '/laminas/laminas-loader/src/ModuleAutoloader.php', + 'Laminas\\Loader\\PluginClassLoader' => $vendorDir . '/laminas/laminas-loader/src/PluginClassLoader.php', + 'Laminas\\Loader\\PluginClassLocator' => $vendorDir . '/laminas/laminas-loader/src/PluginClassLocator.php', + 'Laminas\\Loader\\ShortNameLocator' => $vendorDir . '/laminas/laminas-loader/src/ShortNameLocator.php', + 'Laminas\\Loader\\SplAutoloader' => $vendorDir . '/laminas/laminas-loader/src/SplAutoloader.php', + 'Laminas\\Loader\\StandardAutoloader' => $vendorDir . '/laminas/laminas-loader/src/StandardAutoloader.php', + 'Laminas\\Mail\\Address' => $vendorDir . '/laminas/laminas-mail/src/Address.php', + 'Laminas\\Mail\\AddressList' => $vendorDir . '/laminas/laminas-mail/src/AddressList.php', + 'Laminas\\Mail\\Address\\AddressInterface' => $vendorDir . '/laminas/laminas-mail/src/Address/AddressInterface.php', + 'Laminas\\Mail\\ConfigProvider' => $vendorDir . '/laminas/laminas-mail/src/ConfigProvider.php', + 'Laminas\\Mail\\Exception\\BadMethodCallException' => $vendorDir . '/laminas/laminas-mail/src/Exception/BadMethodCallException.php', + 'Laminas\\Mail\\Exception\\DomainException' => $vendorDir . '/laminas/laminas-mail/src/Exception/DomainException.php', + 'Laminas\\Mail\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-mail/src/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-mail/src/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Exception\\OutOfBoundsException' => $vendorDir . '/laminas/laminas-mail/src/Exception/OutOfBoundsException.php', + 'Laminas\\Mail\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-mail/src/Exception/RuntimeException.php', + 'Laminas\\Mail\\Header\\AbstractAddressList' => $vendorDir . '/laminas/laminas-mail/src/Header/AbstractAddressList.php', + 'Laminas\\Mail\\Header\\Bcc' => $vendorDir . '/laminas/laminas-mail/src/Header/Bcc.php', + 'Laminas\\Mail\\Header\\Cc' => $vendorDir . '/laminas/laminas-mail/src/Header/Cc.php', + 'Laminas\\Mail\\Header\\ContentDisposition' => $vendorDir . '/laminas/laminas-mail/src/Header/ContentDisposition.php', + 'Laminas\\Mail\\Header\\ContentTransferEncoding' => $vendorDir . '/laminas/laminas-mail/src/Header/ContentTransferEncoding.php', + 'Laminas\\Mail\\Header\\ContentType' => $vendorDir . '/laminas/laminas-mail/src/Header/ContentType.php', + 'Laminas\\Mail\\Header\\Date' => $vendorDir . '/laminas/laminas-mail/src/Header/Date.php', + 'Laminas\\Mail\\Header\\Exception\\BadMethodCallException' => $vendorDir . '/laminas/laminas-mail/src/Header/Exception/BadMethodCallException.php', + 'Laminas\\Mail\\Header\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-mail/src/Header/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Header\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-mail/src/Header/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Header\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-mail/src/Header/Exception/RuntimeException.php', + 'Laminas\\Mail\\Header\\From' => $vendorDir . '/laminas/laminas-mail/src/Header/From.php', + 'Laminas\\Mail\\Header\\GenericHeader' => $vendorDir . '/laminas/laminas-mail/src/Header/GenericHeader.php', + 'Laminas\\Mail\\Header\\GenericMultiHeader' => $vendorDir . '/laminas/laminas-mail/src/Header/GenericMultiHeader.php', + 'Laminas\\Mail\\Header\\HeaderInterface' => $vendorDir . '/laminas/laminas-mail/src/Header/HeaderInterface.php', + 'Laminas\\Mail\\Header\\HeaderLoader' => $vendorDir . '/laminas/laminas-mail/src/Header/HeaderLoader.php', + 'Laminas\\Mail\\Header\\HeaderLocator' => $vendorDir . '/laminas/laminas-mail/src/Header/HeaderLocator.php', + 'Laminas\\Mail\\Header\\HeaderLocatorInterface' => $vendorDir . '/laminas/laminas-mail/src/Header/HeaderLocatorInterface.php', + 'Laminas\\Mail\\Header\\HeaderName' => $vendorDir . '/laminas/laminas-mail/src/Header/HeaderName.php', + 'Laminas\\Mail\\Header\\HeaderValue' => $vendorDir . '/laminas/laminas-mail/src/Header/HeaderValue.php', + 'Laminas\\Mail\\Header\\HeaderWrap' => $vendorDir . '/laminas/laminas-mail/src/Header/HeaderWrap.php', + 'Laminas\\Mail\\Header\\IdentificationField' => $vendorDir . '/laminas/laminas-mail/src/Header/IdentificationField.php', + 'Laminas\\Mail\\Header\\InReplyTo' => $vendorDir . '/laminas/laminas-mail/src/Header/InReplyTo.php', + 'Laminas\\Mail\\Header\\ListParser' => $vendorDir . '/laminas/laminas-mail/src/Header/ListParser.php', + 'Laminas\\Mail\\Header\\MessageId' => $vendorDir . '/laminas/laminas-mail/src/Header/MessageId.php', + 'Laminas\\Mail\\Header\\MimeVersion' => $vendorDir . '/laminas/laminas-mail/src/Header/MimeVersion.php', + 'Laminas\\Mail\\Header\\MultipleHeadersInterface' => $vendorDir . '/laminas/laminas-mail/src/Header/MultipleHeadersInterface.php', + 'Laminas\\Mail\\Header\\Received' => $vendorDir . '/laminas/laminas-mail/src/Header/Received.php', + 'Laminas\\Mail\\Header\\References' => $vendorDir . '/laminas/laminas-mail/src/Header/References.php', + 'Laminas\\Mail\\Header\\ReplyTo' => $vendorDir . '/laminas/laminas-mail/src/Header/ReplyTo.php', + 'Laminas\\Mail\\Header\\Sender' => $vendorDir . '/laminas/laminas-mail/src/Header/Sender.php', + 'Laminas\\Mail\\Header\\StructuredInterface' => $vendorDir . '/laminas/laminas-mail/src/Header/StructuredInterface.php', + 'Laminas\\Mail\\Header\\Subject' => $vendorDir . '/laminas/laminas-mail/src/Header/Subject.php', + 'Laminas\\Mail\\Header\\To' => $vendorDir . '/laminas/laminas-mail/src/Header/To.php', + 'Laminas\\Mail\\Header\\UnstructuredInterface' => $vendorDir . '/laminas/laminas-mail/src/Header/UnstructuredInterface.php', + 'Laminas\\Mail\\Headers' => $vendorDir . '/laminas/laminas-mail/src/Headers.php', + 'Laminas\\Mail\\Message' => $vendorDir . '/laminas/laminas-mail/src/Message.php', + 'Laminas\\Mail\\MessageFactory' => $vendorDir . '/laminas/laminas-mail/src/MessageFactory.php', + 'Laminas\\Mail\\Module' => $vendorDir . '/laminas/laminas-mail/src/Module.php', + 'Laminas\\Mail\\Protocol\\AbstractProtocol' => $vendorDir . '/laminas/laminas-mail/src/Protocol/AbstractProtocol.php', + 'Laminas\\Mail\\Protocol\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-mail/src/Protocol/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Protocol\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-mail/src/Protocol/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Protocol\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-mail/src/Protocol/Exception/RuntimeException.php', + 'Laminas\\Mail\\Protocol\\Imap' => $vendorDir . '/laminas/laminas-mail/src/Protocol/Imap.php', + 'Laminas\\Mail\\Protocol\\Pop3' => $vendorDir . '/laminas/laminas-mail/src/Protocol/Pop3.php', + 'Laminas\\Mail\\Protocol\\ProtocolTrait' => $vendorDir . '/laminas/laminas-mail/src/Protocol/ProtocolTrait.php', + 'Laminas\\Mail\\Protocol\\Smtp' => $vendorDir . '/laminas/laminas-mail/src/Protocol/Smtp.php', + 'Laminas\\Mail\\Protocol\\SmtpPluginManager' => $vendorDir . '/laminas/laminas-mail/src/Protocol/SmtpPluginManager.php', + 'Laminas\\Mail\\Protocol\\SmtpPluginManagerFactory' => $vendorDir . '/laminas/laminas-mail/src/Protocol/SmtpPluginManagerFactory.php', + 'Laminas\\Mail\\Protocol\\Smtp\\Auth\\Crammd5' => $vendorDir . '/laminas/laminas-mail/src/Protocol/Smtp/Auth/Crammd5.php', + 'Laminas\\Mail\\Protocol\\Smtp\\Auth\\Login' => $vendorDir . '/laminas/laminas-mail/src/Protocol/Smtp/Auth/Login.php', + 'Laminas\\Mail\\Protocol\\Smtp\\Auth\\Oauth' => $baseDir . '/sources/Core/Authentication/Client/Smtp/SmtpOAuthLogin.php', + 'Laminas\\Mail\\Protocol\\Smtp\\Auth\\Plain' => $vendorDir . '/laminas/laminas-mail/src/Protocol/Smtp/Auth/Plain.php', + 'Laminas\\Mail\\Storage' => $vendorDir . '/laminas/laminas-mail/src/Storage.php', + 'Laminas\\Mail\\Storage\\AbstractStorage' => $vendorDir . '/laminas/laminas-mail/src/Storage/AbstractStorage.php', + 'Laminas\\Mail\\Storage\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-mail/src/Storage/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Storage\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-mail/src/Storage/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Storage\\Exception\\OutOfBoundsException' => $vendorDir . '/laminas/laminas-mail/src/Storage/Exception/OutOfBoundsException.php', + 'Laminas\\Mail\\Storage\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-mail/src/Storage/Exception/RuntimeException.php', + 'Laminas\\Mail\\Storage\\Folder' => $vendorDir . '/laminas/laminas-mail/src/Storage/Folder.php', + 'Laminas\\Mail\\Storage\\Folder\\FolderInterface' => $vendorDir . '/laminas/laminas-mail/src/Storage/Folder/FolderInterface.php', + 'Laminas\\Mail\\Storage\\Folder\\Maildir' => $vendorDir . '/laminas/laminas-mail/src/Storage/Folder/Maildir.php', + 'Laminas\\Mail\\Storage\\Folder\\Mbox' => $vendorDir . '/laminas/laminas-mail/src/Storage/Folder/Mbox.php', + 'Laminas\\Mail\\Storage\\Imap' => $vendorDir . '/laminas/laminas-mail/src/Storage/Imap.php', + 'Laminas\\Mail\\Storage\\Maildir' => $vendorDir . '/laminas/laminas-mail/src/Storage/Maildir.php', + 'Laminas\\Mail\\Storage\\Mbox' => $vendorDir . '/laminas/laminas-mail/src/Storage/Mbox.php', + 'Laminas\\Mail\\Storage\\Message' => $vendorDir . '/laminas/laminas-mail/src/Storage/Message.php', + 'Laminas\\Mail\\Storage\\Message\\File' => $vendorDir . '/laminas/laminas-mail/src/Storage/Message/File.php', + 'Laminas\\Mail\\Storage\\Message\\MessageInterface' => $vendorDir . '/laminas/laminas-mail/src/Storage/Message/MessageInterface.php', + 'Laminas\\Mail\\Storage\\ParamsNormalizer' => $vendorDir . '/laminas/laminas-mail/src/Storage/ParamsNormalizer.php', + 'Laminas\\Mail\\Storage\\Part' => $vendorDir . '/laminas/laminas-mail/src/Storage/Part.php', + 'Laminas\\Mail\\Storage\\Part\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-mail/src/Storage/Part/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Storage\\Part\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-mail/src/Storage/Part/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Storage\\Part\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-mail/src/Storage/Part/Exception/RuntimeException.php', + 'Laminas\\Mail\\Storage\\Part\\File' => $vendorDir . '/laminas/laminas-mail/src/Storage/Part/File.php', + 'Laminas\\Mail\\Storage\\Part\\PartInterface' => $vendorDir . '/laminas/laminas-mail/src/Storage/Part/PartInterface.php', + 'Laminas\\Mail\\Storage\\Pop3' => $vendorDir . '/laminas/laminas-mail/src/Storage/Pop3.php', + 'Laminas\\Mail\\Storage\\Writable\\Maildir' => $vendorDir . '/laminas/laminas-mail/src/Storage/Writable/Maildir.php', + 'Laminas\\Mail\\Storage\\Writable\\WritableInterface' => $vendorDir . '/laminas/laminas-mail/src/Storage/Writable/WritableInterface.php', + 'Laminas\\Mail\\Transport\\Envelope' => $vendorDir . '/laminas/laminas-mail/src/Transport/Envelope.php', + 'Laminas\\Mail\\Transport\\Exception\\DomainException' => $vendorDir . '/laminas/laminas-mail/src/Transport/Exception/DomainException.php', + 'Laminas\\Mail\\Transport\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-mail/src/Transport/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Transport\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-mail/src/Transport/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Transport\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-mail/src/Transport/Exception/RuntimeException.php', + 'Laminas\\Mail\\Transport\\Factory' => $vendorDir . '/laminas/laminas-mail/src/Transport/Factory.php', + 'Laminas\\Mail\\Transport\\File' => $vendorDir . '/laminas/laminas-mail/src/Transport/File.php', + 'Laminas\\Mail\\Transport\\FileOptions' => $vendorDir . '/laminas/laminas-mail/src/Transport/FileOptions.php', + 'Laminas\\Mail\\Transport\\InMemory' => $vendorDir . '/laminas/laminas-mail/src/Transport/InMemory.php', + 'Laminas\\Mail\\Transport\\Sendmail' => $vendorDir . '/laminas/laminas-mail/src/Transport/Sendmail.php', + 'Laminas\\Mail\\Transport\\Smtp' => $vendorDir . '/laminas/laminas-mail/src/Transport/Smtp.php', + 'Laminas\\Mail\\Transport\\SmtpOptions' => $vendorDir . '/laminas/laminas-mail/src/Transport/SmtpOptions.php', + 'Laminas\\Mail\\Transport\\TransportInterface' => $vendorDir . '/laminas/laminas-mail/src/Transport/TransportInterface.php', + 'Laminas\\Mime\\Decode' => $vendorDir . '/laminas/laminas-mime/src/Decode.php', + 'Laminas\\Mime\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-mime/src/Exception/ExceptionInterface.php', + 'Laminas\\Mime\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-mime/src/Exception/InvalidArgumentException.php', + 'Laminas\\Mime\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-mime/src/Exception/RuntimeException.php', + 'Laminas\\Mime\\Message' => $vendorDir . '/laminas/laminas-mime/src/Message.php', + 'Laminas\\Mime\\Mime' => $vendorDir . '/laminas/laminas-mime/src/Mime.php', + 'Laminas\\Mime\\Part' => $vendorDir . '/laminas/laminas-mime/src/Part.php', + 'Laminas\\ServiceManager\\AbstractFactoryInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/AbstractFactoryInterface.php', + 'Laminas\\ServiceManager\\AbstractFactory\\ConfigAbstractFactory' => $vendorDir . '/laminas/laminas-servicemanager/src/AbstractFactory/ConfigAbstractFactory.php', + 'Laminas\\ServiceManager\\AbstractFactory\\ReflectionBasedAbstractFactory' => $vendorDir . '/laminas/laminas-servicemanager/src/AbstractFactory/ReflectionBasedAbstractFactory.php', + 'Laminas\\ServiceManager\\AbstractPluginManager' => $vendorDir . '/laminas/laminas-servicemanager/src/AbstractPluginManager.php', + 'Laminas\\ServiceManager\\Config' => $vendorDir . '/laminas/laminas-servicemanager/src/Config.php', + 'Laminas\\ServiceManager\\ConfigInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/ConfigInterface.php', + 'Laminas\\ServiceManager\\DelegatorFactoryInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/DelegatorFactoryInterface.php', + 'Laminas\\ServiceManager\\Exception\\ContainerModificationsNotAllowedException' => $vendorDir . '/laminas/laminas-servicemanager/src/Exception/ContainerModificationsNotAllowedException.php', + 'Laminas\\ServiceManager\\Exception\\CyclicAliasException' => $vendorDir . '/laminas/laminas-servicemanager/src/Exception/CyclicAliasException.php', + 'Laminas\\ServiceManager\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/Exception/ExceptionInterface.php', + 'Laminas\\ServiceManager\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-servicemanager/src/Exception/InvalidArgumentException.php', + 'Laminas\\ServiceManager\\Exception\\InvalidServiceException' => $vendorDir . '/laminas/laminas-servicemanager/src/Exception/InvalidServiceException.php', + 'Laminas\\ServiceManager\\Exception\\ServiceNotCreatedException' => $vendorDir . '/laminas/laminas-servicemanager/src/Exception/ServiceNotCreatedException.php', + 'Laminas\\ServiceManager\\Exception\\ServiceNotFoundException' => $vendorDir . '/laminas/laminas-servicemanager/src/Exception/ServiceNotFoundException.php', + 'Laminas\\ServiceManager\\FactoryInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/FactoryInterface.php', + 'Laminas\\ServiceManager\\Factory\\AbstractFactoryInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/Factory/AbstractFactoryInterface.php', + 'Laminas\\ServiceManager\\Factory\\DelegatorFactoryInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/Factory/DelegatorFactoryInterface.php', + 'Laminas\\ServiceManager\\Factory\\FactoryInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/Factory/FactoryInterface.php', + 'Laminas\\ServiceManager\\Factory\\InvokableFactory' => $vendorDir . '/laminas/laminas-servicemanager/src/Factory/InvokableFactory.php', + 'Laminas\\ServiceManager\\InitializerInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/InitializerInterface.php', + 'Laminas\\ServiceManager\\Initializer\\InitializerInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/Initializer/InitializerInterface.php', + 'Laminas\\ServiceManager\\PluginManagerInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/PluginManagerInterface.php', + 'Laminas\\ServiceManager\\Proxy\\LazyServiceFactory' => $vendorDir . '/laminas/laminas-servicemanager/src/Proxy/LazyServiceFactory.php', + 'Laminas\\ServiceManager\\ServiceLocatorInterface' => $vendorDir . '/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php', + 'Laminas\\ServiceManager\\ServiceManager' => $vendorDir . '/laminas/laminas-servicemanager/src/ServiceManager.php', + 'Laminas\\ServiceManager\\Tool\\ConfigDumper' => $vendorDir . '/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php', + 'Laminas\\ServiceManager\\Tool\\ConfigDumperCommand' => $vendorDir . '/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php', + 'Laminas\\ServiceManager\\Tool\\FactoryCreator' => $vendorDir . '/laminas/laminas-servicemanager/src/Tool/FactoryCreator.php', + 'Laminas\\ServiceManager\\Tool\\FactoryCreatorCommand' => $vendorDir . '/laminas/laminas-servicemanager/src/Tool/FactoryCreatorCommand.php', + 'Laminas\\Stdlib\\AbstractOptions' => $vendorDir . '/laminas/laminas-stdlib/src/AbstractOptions.php', + 'Laminas\\Stdlib\\ArrayObject' => $vendorDir . '/laminas/laminas-stdlib/src/ArrayObject.php', + 'Laminas\\Stdlib\\ArraySerializableInterface' => $vendorDir . '/laminas/laminas-stdlib/src/ArraySerializableInterface.php', + 'Laminas\\Stdlib\\ArrayStack' => $vendorDir . '/laminas/laminas-stdlib/src/ArrayStack.php', + 'Laminas\\Stdlib\\ArrayUtils' => $vendorDir . '/laminas/laminas-stdlib/src/ArrayUtils.php', + 'Laminas\\Stdlib\\ArrayUtils\\MergeRemoveKey' => $vendorDir . '/laminas/laminas-stdlib/src/ArrayUtils/MergeRemoveKey.php', + 'Laminas\\Stdlib\\ArrayUtils\\MergeReplaceKey' => $vendorDir . '/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKey.php', + 'Laminas\\Stdlib\\ArrayUtils\\MergeReplaceKeyInterface' => $vendorDir . '/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php', + 'Laminas\\Stdlib\\ConsoleHelper' => $vendorDir . '/laminas/laminas-stdlib/src/ConsoleHelper.php', + 'Laminas\\Stdlib\\DispatchableInterface' => $vendorDir . '/laminas/laminas-stdlib/src/DispatchableInterface.php', + 'Laminas\\Stdlib\\ErrorHandler' => $vendorDir . '/laminas/laminas-stdlib/src/ErrorHandler.php', + 'Laminas\\Stdlib\\Exception\\BadMethodCallException' => $vendorDir . '/laminas/laminas-stdlib/src/Exception/BadMethodCallException.php', + 'Laminas\\Stdlib\\Exception\\DomainException' => $vendorDir . '/laminas/laminas-stdlib/src/Exception/DomainException.php', + 'Laminas\\Stdlib\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-stdlib/src/Exception/ExceptionInterface.php', + 'Laminas\\Stdlib\\Exception\\ExtensionNotLoadedException' => $vendorDir . '/laminas/laminas-stdlib/src/Exception/ExtensionNotLoadedException.php', + 'Laminas\\Stdlib\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-stdlib/src/Exception/InvalidArgumentException.php', + 'Laminas\\Stdlib\\Exception\\LogicException' => $vendorDir . '/laminas/laminas-stdlib/src/Exception/LogicException.php', + 'Laminas\\Stdlib\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-stdlib/src/Exception/RuntimeException.php', + 'Laminas\\Stdlib\\FastPriorityQueue' => $vendorDir . '/laminas/laminas-stdlib/src/FastPriorityQueue.php', + 'Laminas\\Stdlib\\Glob' => $vendorDir . '/laminas/laminas-stdlib/src/Glob.php', + 'Laminas\\Stdlib\\Guard\\AllGuardsTrait' => $vendorDir . '/laminas/laminas-stdlib/src/Guard/AllGuardsTrait.php', + 'Laminas\\Stdlib\\Guard\\ArrayOrTraversableGuardTrait' => $vendorDir . '/laminas/laminas-stdlib/src/Guard/ArrayOrTraversableGuardTrait.php', + 'Laminas\\Stdlib\\Guard\\EmptyGuardTrait' => $vendorDir . '/laminas/laminas-stdlib/src/Guard/EmptyGuardTrait.php', + 'Laminas\\Stdlib\\Guard\\NullGuardTrait' => $vendorDir . '/laminas/laminas-stdlib/src/Guard/NullGuardTrait.php', + 'Laminas\\Stdlib\\InitializableInterface' => $vendorDir . '/laminas/laminas-stdlib/src/InitializableInterface.php', + 'Laminas\\Stdlib\\JsonSerializable' => $vendorDir . '/laminas/laminas-stdlib/src/JsonSerializable.php', + 'Laminas\\Stdlib\\Message' => $vendorDir . '/laminas/laminas-stdlib/src/Message.php', + 'Laminas\\Stdlib\\MessageInterface' => $vendorDir . '/laminas/laminas-stdlib/src/MessageInterface.php', + 'Laminas\\Stdlib\\ParameterObjectInterface' => $vendorDir . '/laminas/laminas-stdlib/src/ParameterObjectInterface.php', + 'Laminas\\Stdlib\\Parameters' => $vendorDir . '/laminas/laminas-stdlib/src/Parameters.php', + 'Laminas\\Stdlib\\ParametersInterface' => $vendorDir . '/laminas/laminas-stdlib/src/ParametersInterface.php', + 'Laminas\\Stdlib\\PriorityList' => $vendorDir . '/laminas/laminas-stdlib/src/PriorityList.php', + 'Laminas\\Stdlib\\PriorityQueue' => $vendorDir . '/laminas/laminas-stdlib/src/PriorityQueue.php', + 'Laminas\\Stdlib\\Request' => $vendorDir . '/laminas/laminas-stdlib/src/Request.php', + 'Laminas\\Stdlib\\RequestInterface' => $vendorDir . '/laminas/laminas-stdlib/src/RequestInterface.php', + 'Laminas\\Stdlib\\Response' => $vendorDir . '/laminas/laminas-stdlib/src/Response.php', + 'Laminas\\Stdlib\\ResponseInterface' => $vendorDir . '/laminas/laminas-stdlib/src/ResponseInterface.php', + 'Laminas\\Stdlib\\SplPriorityQueue' => $vendorDir . '/laminas/laminas-stdlib/src/SplPriorityQueue.php', + 'Laminas\\Stdlib\\SplQueue' => $vendorDir . '/laminas/laminas-stdlib/src/SplQueue.php', + 'Laminas\\Stdlib\\SplStack' => $vendorDir . '/laminas/laminas-stdlib/src/SplStack.php', + 'Laminas\\Stdlib\\StringUtils' => $vendorDir . '/laminas/laminas-stdlib/src/StringUtils.php', + 'Laminas\\Stdlib\\StringWrapper\\AbstractStringWrapper' => $vendorDir . '/laminas/laminas-stdlib/src/StringWrapper/AbstractStringWrapper.php', + 'Laminas\\Stdlib\\StringWrapper\\Iconv' => $vendorDir . '/laminas/laminas-stdlib/src/StringWrapper/Iconv.php', + 'Laminas\\Stdlib\\StringWrapper\\Intl' => $vendorDir . '/laminas/laminas-stdlib/src/StringWrapper/Intl.php', + 'Laminas\\Stdlib\\StringWrapper\\MbString' => $vendorDir . '/laminas/laminas-stdlib/src/StringWrapper/MbString.php', + 'Laminas\\Stdlib\\StringWrapper\\Native' => $vendorDir . '/laminas/laminas-stdlib/src/StringWrapper/Native.php', + 'Laminas\\Stdlib\\StringWrapper\\StringWrapperInterface' => $vendorDir . '/laminas/laminas-stdlib/src/StringWrapper/StringWrapperInterface.php', + 'Laminas\\Validator\\AbstractValidator' => $vendorDir . '/laminas/laminas-validator/src/AbstractValidator.php', + 'Laminas\\Validator\\Barcode' => $vendorDir . '/laminas/laminas-validator/src/Barcode.php', + 'Laminas\\Validator\\Barcode\\AbstractAdapter' => $vendorDir . '/laminas/laminas-validator/src/Barcode/AbstractAdapter.php', + 'Laminas\\Validator\\Barcode\\AdapterInterface' => $vendorDir . '/laminas/laminas-validator/src/Barcode/AdapterInterface.php', + 'Laminas\\Validator\\Barcode\\Codabar' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Codabar.php', + 'Laminas\\Validator\\Barcode\\Code128' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Code128.php', + 'Laminas\\Validator\\Barcode\\Code25' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Code25.php', + 'Laminas\\Validator\\Barcode\\Code25interleaved' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Code25interleaved.php', + 'Laminas\\Validator\\Barcode\\Code39' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Code39.php', + 'Laminas\\Validator\\Barcode\\Code39ext' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Code39ext.php', + 'Laminas\\Validator\\Barcode\\Code93' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Code93.php', + 'Laminas\\Validator\\Barcode\\Code93ext' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Code93ext.php', + 'Laminas\\Validator\\Barcode\\Ean12' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Ean12.php', + 'Laminas\\Validator\\Barcode\\Ean13' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Ean13.php', + 'Laminas\\Validator\\Barcode\\Ean14' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Ean14.php', + 'Laminas\\Validator\\Barcode\\Ean18' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Ean18.php', + 'Laminas\\Validator\\Barcode\\Ean2' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Ean2.php', + 'Laminas\\Validator\\Barcode\\Ean5' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Ean5.php', + 'Laminas\\Validator\\Barcode\\Ean8' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Ean8.php', + 'Laminas\\Validator\\Barcode\\Gtin12' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Gtin12.php', + 'Laminas\\Validator\\Barcode\\Gtin13' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Gtin13.php', + 'Laminas\\Validator\\Barcode\\Gtin14' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Gtin14.php', + 'Laminas\\Validator\\Barcode\\Identcode' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Identcode.php', + 'Laminas\\Validator\\Barcode\\Intelligentmail' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Intelligentmail.php', + 'Laminas\\Validator\\Barcode\\Issn' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Issn.php', + 'Laminas\\Validator\\Barcode\\Itf14' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Itf14.php', + 'Laminas\\Validator\\Barcode\\Leitcode' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Leitcode.php', + 'Laminas\\Validator\\Barcode\\Planet' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Planet.php', + 'Laminas\\Validator\\Barcode\\Postnet' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Postnet.php', + 'Laminas\\Validator\\Barcode\\Royalmail' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Royalmail.php', + 'Laminas\\Validator\\Barcode\\Sscc' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Sscc.php', + 'Laminas\\Validator\\Barcode\\Upca' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Upca.php', + 'Laminas\\Validator\\Barcode\\Upce' => $vendorDir . '/laminas/laminas-validator/src/Barcode/Upce.php', + 'Laminas\\Validator\\Between' => $vendorDir . '/laminas/laminas-validator/src/Between.php', + 'Laminas\\Validator\\Bitwise' => $vendorDir . '/laminas/laminas-validator/src/Bitwise.php', + 'Laminas\\Validator\\BusinessIdentifierCode' => $vendorDir . '/laminas/laminas-validator/src/BusinessIdentifierCode.php', + 'Laminas\\Validator\\Callback' => $vendorDir . '/laminas/laminas-validator/src/Callback.php', + 'Laminas\\Validator\\ConfigProvider' => $vendorDir . '/laminas/laminas-validator/src/ConfigProvider.php', + 'Laminas\\Validator\\CreditCard' => $vendorDir . '/laminas/laminas-validator/src/CreditCard.php', + 'Laminas\\Validator\\Csrf' => $vendorDir . '/laminas/laminas-validator/src/Csrf.php', + 'Laminas\\Validator\\Date' => $vendorDir . '/laminas/laminas-validator/src/Date.php', + 'Laminas\\Validator\\DateStep' => $vendorDir . '/laminas/laminas-validator/src/DateStep.php', + 'Laminas\\Validator\\Db\\AbstractDb' => $vendorDir . '/laminas/laminas-validator/src/Db/AbstractDb.php', + 'Laminas\\Validator\\Db\\NoRecordExists' => $vendorDir . '/laminas/laminas-validator/src/Db/NoRecordExists.php', + 'Laminas\\Validator\\Db\\RecordExists' => $vendorDir . '/laminas/laminas-validator/src/Db/RecordExists.php', + 'Laminas\\Validator\\Digits' => $vendorDir . '/laminas/laminas-validator/src/Digits.php', + 'Laminas\\Validator\\EmailAddress' => $vendorDir . '/laminas/laminas-validator/src/EmailAddress.php', + 'Laminas\\Validator\\Exception\\BadMethodCallException' => $vendorDir . '/laminas/laminas-validator/src/Exception/BadMethodCallException.php', + 'Laminas\\Validator\\Exception\\ExceptionInterface' => $vendorDir . '/laminas/laminas-validator/src/Exception/ExceptionInterface.php', + 'Laminas\\Validator\\Exception\\ExtensionNotLoadedException' => $vendorDir . '/laminas/laminas-validator/src/Exception/ExtensionNotLoadedException.php', + 'Laminas\\Validator\\Exception\\InvalidArgumentException' => $vendorDir . '/laminas/laminas-validator/src/Exception/InvalidArgumentException.php', + 'Laminas\\Validator\\Exception\\InvalidMagicMimeFileException' => $vendorDir . '/laminas/laminas-validator/src/Exception/InvalidMagicMimeFileException.php', + 'Laminas\\Validator\\Exception\\RuntimeException' => $vendorDir . '/laminas/laminas-validator/src/Exception/RuntimeException.php', + 'Laminas\\Validator\\Explode' => $vendorDir . '/laminas/laminas-validator/src/Explode.php', + 'Laminas\\Validator\\File\\Count' => $vendorDir . '/laminas/laminas-validator/src/File/Count.php', + 'Laminas\\Validator\\File\\Crc32' => $vendorDir . '/laminas/laminas-validator/src/File/Crc32.php', + 'Laminas\\Validator\\File\\ExcludeExtension' => $vendorDir . '/laminas/laminas-validator/src/File/ExcludeExtension.php', + 'Laminas\\Validator\\File\\ExcludeMimeType' => $vendorDir . '/laminas/laminas-validator/src/File/ExcludeMimeType.php', + 'Laminas\\Validator\\File\\Exists' => $vendorDir . '/laminas/laminas-validator/src/File/Exists.php', + 'Laminas\\Validator\\File\\Extension' => $vendorDir . '/laminas/laminas-validator/src/File/Extension.php', + 'Laminas\\Validator\\File\\FileInformationTrait' => $vendorDir . '/laminas/laminas-validator/src/File/FileInformationTrait.php', + 'Laminas\\Validator\\File\\FilesSize' => $vendorDir . '/laminas/laminas-validator/src/File/FilesSize.php', + 'Laminas\\Validator\\File\\Hash' => $vendorDir . '/laminas/laminas-validator/src/File/Hash.php', + 'Laminas\\Validator\\File\\ImageSize' => $vendorDir . '/laminas/laminas-validator/src/File/ImageSize.php', + 'Laminas\\Validator\\File\\IsCompressed' => $vendorDir . '/laminas/laminas-validator/src/File/IsCompressed.php', + 'Laminas\\Validator\\File\\IsImage' => $vendorDir . '/laminas/laminas-validator/src/File/IsImage.php', + 'Laminas\\Validator\\File\\Md5' => $vendorDir . '/laminas/laminas-validator/src/File/Md5.php', + 'Laminas\\Validator\\File\\MimeType' => $vendorDir . '/laminas/laminas-validator/src/File/MimeType.php', + 'Laminas\\Validator\\File\\NotExists' => $vendorDir . '/laminas/laminas-validator/src/File/NotExists.php', + 'Laminas\\Validator\\File\\Sha1' => $vendorDir . '/laminas/laminas-validator/src/File/Sha1.php', + 'Laminas\\Validator\\File\\Size' => $vendorDir . '/laminas/laminas-validator/src/File/Size.php', + 'Laminas\\Validator\\File\\Upload' => $vendorDir . '/laminas/laminas-validator/src/File/Upload.php', + 'Laminas\\Validator\\File\\UploadFile' => $vendorDir . '/laminas/laminas-validator/src/File/UploadFile.php', + 'Laminas\\Validator\\File\\WordCount' => $vendorDir . '/laminas/laminas-validator/src/File/WordCount.php', + 'Laminas\\Validator\\GpsPoint' => $vendorDir . '/laminas/laminas-validator/src/GpsPoint.php', + 'Laminas\\Validator\\GreaterThan' => $vendorDir . '/laminas/laminas-validator/src/GreaterThan.php', + 'Laminas\\Validator\\Hex' => $vendorDir . '/laminas/laminas-validator/src/Hex.php', + 'Laminas\\Validator\\Hostname' => $vendorDir . '/laminas/laminas-validator/src/Hostname.php', + 'Laminas\\Validator\\Iban' => $vendorDir . '/laminas/laminas-validator/src/Iban.php', + 'Laminas\\Validator\\Identical' => $vendorDir . '/laminas/laminas-validator/src/Identical.php', + 'Laminas\\Validator\\InArray' => $vendorDir . '/laminas/laminas-validator/src/InArray.php', + 'Laminas\\Validator\\Ip' => $vendorDir . '/laminas/laminas-validator/src/Ip.php', + 'Laminas\\Validator\\IsCountable' => $vendorDir . '/laminas/laminas-validator/src/IsCountable.php', + 'Laminas\\Validator\\IsInstanceOf' => $vendorDir . '/laminas/laminas-validator/src/IsInstanceOf.php', + 'Laminas\\Validator\\Isbn' => $vendorDir . '/laminas/laminas-validator/src/Isbn.php', + 'Laminas\\Validator\\Isbn\\Isbn10' => $vendorDir . '/laminas/laminas-validator/src/Isbn/Isbn10.php', + 'Laminas\\Validator\\Isbn\\Isbn13' => $vendorDir . '/laminas/laminas-validator/src/Isbn/Isbn13.php', + 'Laminas\\Validator\\LessThan' => $vendorDir . '/laminas/laminas-validator/src/LessThan.php', + 'Laminas\\Validator\\Module' => $vendorDir . '/laminas/laminas-validator/src/Module.php', + 'Laminas\\Validator\\NotEmpty' => $vendorDir . '/laminas/laminas-validator/src/NotEmpty.php', + 'Laminas\\Validator\\Regex' => $vendorDir . '/laminas/laminas-validator/src/Regex.php', + 'Laminas\\Validator\\Sitemap\\Changefreq' => $vendorDir . '/laminas/laminas-validator/src/Sitemap/Changefreq.php', + 'Laminas\\Validator\\Sitemap\\Lastmod' => $vendorDir . '/laminas/laminas-validator/src/Sitemap/Lastmod.php', + 'Laminas\\Validator\\Sitemap\\Loc' => $vendorDir . '/laminas/laminas-validator/src/Sitemap/Loc.php', + 'Laminas\\Validator\\Sitemap\\Priority' => $vendorDir . '/laminas/laminas-validator/src/Sitemap/Priority.php', + 'Laminas\\Validator\\StaticValidator' => $vendorDir . '/laminas/laminas-validator/src/StaticValidator.php', + 'Laminas\\Validator\\Step' => $vendorDir . '/laminas/laminas-validator/src/Step.php', + 'Laminas\\Validator\\StringLength' => $vendorDir . '/laminas/laminas-validator/src/StringLength.php', + 'Laminas\\Validator\\Timezone' => $vendorDir . '/laminas/laminas-validator/src/Timezone.php', + 'Laminas\\Validator\\Translator\\TranslatorAwareInterface' => $vendorDir . '/laminas/laminas-validator/src/Translator/TranslatorAwareInterface.php', + 'Laminas\\Validator\\Translator\\TranslatorInterface' => $vendorDir . '/laminas/laminas-validator/src/Translator/TranslatorInterface.php', + 'Laminas\\Validator\\UndisclosedPassword' => $vendorDir . '/laminas/laminas-validator/src/UndisclosedPassword.php', + 'Laminas\\Validator\\Uri' => $vendorDir . '/laminas/laminas-validator/src/Uri.php', + 'Laminas\\Validator\\Uuid' => $vendorDir . '/laminas/laminas-validator/src/Uuid.php', + 'Laminas\\Validator\\ValidatorChain' => $vendorDir . '/laminas/laminas-validator/src/ValidatorChain.php', + 'Laminas\\Validator\\ValidatorInterface' => $vendorDir . '/laminas/laminas-validator/src/ValidatorInterface.php', + 'Laminas\\Validator\\ValidatorPluginManager' => $vendorDir . '/laminas/laminas-validator/src/ValidatorPluginManager.php', + 'Laminas\\Validator\\ValidatorPluginManagerAwareInterface' => $vendorDir . '/laminas/laminas-validator/src/ValidatorPluginManagerAwareInterface.php', + 'Laminas\\Validator\\ValidatorPluginManagerFactory' => $vendorDir . '/laminas/laminas-validator/src/ValidatorPluginManagerFactory.php', + 'Laminas\\Validator\\ValidatorProviderInterface' => $vendorDir . '/laminas/laminas-validator/src/ValidatorProviderInterface.php', + 'League\\OAuth2\\Client\\Exception\\HostedDomainException' => $vendorDir . '/league/oauth2-google/src/Exception/HostedDomainException.php', + 'League\\OAuth2\\Client\\Grant\\AbstractGrant' => $vendorDir . '/league/oauth2-client/src/Grant/AbstractGrant.php', + 'League\\OAuth2\\Client\\Grant\\AuthorizationCode' => $vendorDir . '/league/oauth2-client/src/Grant/AuthorizationCode.php', + 'League\\OAuth2\\Client\\Grant\\ClientCredentials' => $vendorDir . '/league/oauth2-client/src/Grant/ClientCredentials.php', + 'League\\OAuth2\\Client\\Grant\\Exception\\InvalidGrantException' => $vendorDir . '/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php', + 'League\\OAuth2\\Client\\Grant\\GrantFactory' => $vendorDir . '/league/oauth2-client/src/Grant/GrantFactory.php', + 'League\\OAuth2\\Client\\Grant\\Password' => $vendorDir . '/league/oauth2-client/src/Grant/Password.php', + 'League\\OAuth2\\Client\\Grant\\RefreshToken' => $vendorDir . '/league/oauth2-client/src/Grant/RefreshToken.php', + 'League\\OAuth2\\Client\\OptionProvider\\HttpBasicAuthOptionProvider' => $vendorDir . '/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php', + 'League\\OAuth2\\Client\\OptionProvider\\OptionProviderInterface' => $vendorDir . '/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php', + 'League\\OAuth2\\Client\\OptionProvider\\PostAuthOptionProvider' => $vendorDir . '/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php', + 'League\\OAuth2\\Client\\Provider\\AbstractProvider' => $vendorDir . '/league/oauth2-client/src/Provider/AbstractProvider.php', + 'League\\OAuth2\\Client\\Provider\\Exception\\IdentityProviderException' => $vendorDir . '/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php', + 'League\\OAuth2\\Client\\Provider\\GenericProvider' => $vendorDir . '/league/oauth2-client/src/Provider/GenericProvider.php', + 'League\\OAuth2\\Client\\Provider\\GenericResourceOwner' => $vendorDir . '/league/oauth2-client/src/Provider/GenericResourceOwner.php', + 'League\\OAuth2\\Client\\Provider\\Google' => $vendorDir . '/league/oauth2-google/src/Provider/Google.php', + 'League\\OAuth2\\Client\\Provider\\GoogleUser' => $vendorDir . '/league/oauth2-google/src/Provider/GoogleUser.php', + 'League\\OAuth2\\Client\\Provider\\ResourceOwnerInterface' => $vendorDir . '/league/oauth2-client/src/Provider/ResourceOwnerInterface.php', + 'League\\OAuth2\\Client\\Token\\AccessToken' => $vendorDir . '/league/oauth2-client/src/Token/AccessToken.php', + 'League\\OAuth2\\Client\\Token\\AccessTokenInterface' => $vendorDir . '/league/oauth2-client/src/Token/AccessTokenInterface.php', + 'League\\OAuth2\\Client\\Token\\ResourceOwnerAccessTokenInterface' => $vendorDir . '/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php', + 'League\\OAuth2\\Client\\Tool\\ArrayAccessorTrait' => $vendorDir . '/league/oauth2-client/src/Tool/ArrayAccessorTrait.php', + 'League\\OAuth2\\Client\\Tool\\BearerAuthorizationTrait' => $vendorDir . '/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php', + 'League\\OAuth2\\Client\\Tool\\GuardedPropertyTrait' => $vendorDir . '/league/oauth2-client/src/Tool/GuardedPropertyTrait.php', + 'League\\OAuth2\\Client\\Tool\\MacAuthorizationTrait' => $vendorDir . '/league/oauth2-client/src/Tool/MacAuthorizationTrait.php', + 'League\\OAuth2\\Client\\Tool\\ProviderRedirectTrait' => $vendorDir . '/league/oauth2-client/src/Tool/ProviderRedirectTrait.php', + 'League\\OAuth2\\Client\\Tool\\QueryBuilderTrait' => $vendorDir . '/league/oauth2-client/src/Tool/QueryBuilderTrait.php', + 'League\\OAuth2\\Client\\Tool\\RequestFactory' => $vendorDir . '/league/oauth2-client/src/Tool/RequestFactory.php', + 'League\\OAuth2\\Client\\Tool\\RequiredParameterTrait' => $vendorDir . '/league/oauth2-client/src/Tool/RequiredParameterTrait.php', 'ListExpression' => $baseDir . '/core/oql/expression.class.inc.php', 'ListOqlExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php', 'LogAPI' => $baseDir . '/core/log.class.inc.php', @@ -532,6 +1020,7 @@ return array( 'NewObjectMenuNode' => $baseDir . '/application/menunode.class.inc.php', 'NewsroomProviderBase' => $baseDir . '/application/newsroomprovider.class.inc.php', 'NiceWebPage' => $baseDir . '/sources/Application/WebPage/NiceWebPage.php', + 'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', 'NotYetEvaluatedExpression' => $baseDir . '/core/oql/expression.class.inc.php', 'OQLActualClassTreeResolver' => $baseDir . '/core/oqlactualclasstreeresolver.class.inc.php', 'OQLClassNode' => $baseDir . '/core/oqlclassnode.class.inc.php', @@ -572,21 +1061,24 @@ return array( 'PEAR_ErrorStack' => $vendorDir . '/pear/pear-core-minimal/src/PEAR/ErrorStack.php', 'PEAR_Exception' => $vendorDir . '/pear/pear_exception/PEAR/Exception.php', 'Page' => $baseDir . '/sources/Application/WebPage/Page.php', - 'ParseError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ParseError.php', - 'Pelago\\Emogrifier' => $vendorDir . '/pelago/emogrifier/src/Emogrifier.php', - 'Pelago\\Emogrifier\\CssInliner' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/CssInliner.php', - 'Pelago\\Emogrifier\\HtmlProcessor\\AbstractHtmlProcessor' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/AbstractHtmlProcessor.php', - 'Pelago\\Emogrifier\\HtmlProcessor\\CssToAttributeConverter' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/CssToAttributeConverter.php', - 'Pelago\\Emogrifier\\HtmlProcessor\\HtmlNormalizer' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/HtmlNormalizer.php', - 'Pelago\\Emogrifier\\HtmlProcessor\\HtmlPruner' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/HtmlPruner.php', - 'Pelago\\Emogrifier\\Utilities\\ArrayIntersector' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/Utilities/ArrayIntersector.php', - 'Pelago\\Emogrifier\\Utilities\\CssConcatenator' => $vendorDir . '/pelago/emogrifier/src/Emogrifier/Utilities/CssConcatenator.php', + 'Pelago\\Emogrifier\\Caching\\SimpleStringCache' => $vendorDir . '/pelago/emogrifier/src/Caching/SimpleStringCache.php', + 'Pelago\\Emogrifier\\CssInliner' => $vendorDir . '/pelago/emogrifier/src/CssInliner.php', + 'Pelago\\Emogrifier\\Css\\CssDocument' => $vendorDir . '/pelago/emogrifier/src/Css/CssDocument.php', + 'Pelago\\Emogrifier\\Css\\StyleRule' => $vendorDir . '/pelago/emogrifier/src/Css/StyleRule.php', + 'Pelago\\Emogrifier\\HtmlProcessor\\AbstractHtmlProcessor' => $vendorDir . '/pelago/emogrifier/src/HtmlProcessor/AbstractHtmlProcessor.php', + 'Pelago\\Emogrifier\\HtmlProcessor\\CssToAttributeConverter' => $vendorDir . '/pelago/emogrifier/src/HtmlProcessor/CssToAttributeConverter.php', + 'Pelago\\Emogrifier\\HtmlProcessor\\HtmlNormalizer' => $vendorDir . '/pelago/emogrifier/src/HtmlProcessor/HtmlNormalizer.php', + 'Pelago\\Emogrifier\\HtmlProcessor\\HtmlPruner' => $vendorDir . '/pelago/emogrifier/src/HtmlProcessor/HtmlPruner.php', + 'Pelago\\Emogrifier\\Utilities\\ArrayIntersector' => $vendorDir . '/pelago/emogrifier/src/Utilities/ArrayIntersector.php', + 'Pelago\\Emogrifier\\Utilities\\CssConcatenator' => $vendorDir . '/pelago/emogrifier/src/Utilities/CssConcatenator.php', 'PhpParser\\Builder' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder.php', 'PhpParser\\BuilderFactory' => $vendorDir . '/nikic/php-parser/lib/PhpParser/BuilderFactory.php', 'PhpParser\\BuilderHelpers' => $vendorDir . '/nikic/php-parser/lib/PhpParser/BuilderHelpers.php', 'PhpParser\\Builder\\ClassConst' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/ClassConst.php', 'PhpParser\\Builder\\Class_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Class_.php', 'PhpParser\\Builder\\Declaration' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Declaration.php', + 'PhpParser\\Builder\\EnumCase' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/EnumCase.php', + 'PhpParser\\Builder\\Enum_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Enum_.php', 'PhpParser\\Builder\\FunctionLike' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php', 'PhpParser\\Builder\\Function_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Function_.php', 'PhpParser\\Builder\\Interface_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder/Interface_.php', @@ -616,6 +1108,7 @@ return array( 'PhpParser\\Lexer\\TokenEmulator\\AttributeEmulator' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php', 'PhpParser\\Lexer\\TokenEmulator\\CoaleseEqualTokenEmulator' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/CoaleseEqualTokenEmulator.php', 'PhpParser\\Lexer\\TokenEmulator\\EnumTokenEmulator' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\ExplicitOctalEmulator' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php', 'PhpParser\\Lexer\\TokenEmulator\\FlexibleDocStringEmulator' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FlexibleDocStringEmulator.php', 'PhpParser\\Lexer\\TokenEmulator\\FnTokenEmulator' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.php', 'PhpParser\\Lexer\\TokenEmulator\\KeywordEmulator' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php', @@ -643,6 +1136,7 @@ return array( 'PhpParser\\Node\\Arg' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Arg.php', 'PhpParser\\Node\\Attribute' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Attribute.php', 'PhpParser\\Node\\AttributeGroup' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php', + 'PhpParser\\Node\\ComplexType' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/ComplexType.php', 'PhpParser\\Node\\Const_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Const_.php', 'PhpParser\\Node\\Expr' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr.php', 'PhpParser\\Node\\Expr\\ArrayDimFetch' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayDimFetch.php', @@ -695,6 +1189,7 @@ return array( 'PhpParser\\Node\\Expr\\BinaryOp\\Spaceship' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php', 'PhpParser\\Node\\Expr\\BitwiseNot' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php', 'PhpParser\\Node\\Expr\\BooleanNot' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php', + 'PhpParser\\Node\\Expr\\CallLike' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php', 'PhpParser\\Node\\Expr\\Cast' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php', 'PhpParser\\Node\\Expr\\Cast\\Array_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php', 'PhpParser\\Node\\Expr\\Cast\\Bool_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.php', @@ -741,6 +1236,7 @@ return array( 'PhpParser\\Node\\Expr\\Yield_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php', 'PhpParser\\Node\\FunctionLike' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php', 'PhpParser\\Node\\Identifier' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Identifier.php', + 'PhpParser\\Node\\IntersectionType' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/IntersectionType.php', 'PhpParser\\Node\\MatchArm' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/MatchArm.php', 'PhpParser\\Node\\Name' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Name.php', 'PhpParser\\Node\\Name\\FullyQualified' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php', @@ -814,6 +1310,7 @@ return array( 'PhpParser\\Node\\Stmt\\While_' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php', 'PhpParser\\Node\\UnionType' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/UnionType.php', 'PhpParser\\Node\\VarLikeIdentifier' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php', + 'PhpParser\\Node\\VariadicPlaceholder' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Node/VariadicPlaceholder.php', 'PhpParser\\Parser' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Parser.php', 'PhpParser\\ParserAbstract' => $vendorDir . '/nikic/php-parser/lib/PhpParser/ParserAbstract.php', 'PhpParser\\ParserFactory' => $vendorDir . '/nikic/php-parser/lib/PhpParser/ParserFactory.php', @@ -823,6 +1320,7 @@ return array( 'PhpParser\\Parser\\Tokens' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Parser/Tokens.php', 'PhpParser\\PrettyPrinterAbstract' => $vendorDir . '/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php', 'PhpParser\\PrettyPrinter\\Standard' => $vendorDir . '/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php', + 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', 'PluginInstanciationManager' => $baseDir . '/core/plugininstanciationmanager.class.inc.php', 'PluginManager' => $baseDir . '/core/pluginmanager.class.inc.php', 'PortalDispatcher' => $baseDir . '/application/portaldispatcher.class.inc.php', @@ -838,6 +1336,26 @@ return array( 'Psr\\Container\\ContainerExceptionInterface' => $vendorDir . '/psr/container/src/ContainerExceptionInterface.php', 'Psr\\Container\\ContainerInterface' => $vendorDir . '/psr/container/src/ContainerInterface.php', 'Psr\\Container\\NotFoundExceptionInterface' => $vendorDir . '/psr/container/src/NotFoundExceptionInterface.php', + 'Psr\\EventDispatcher\\EventDispatcherInterface' => $vendorDir . '/psr/event-dispatcher/src/EventDispatcherInterface.php', + 'Psr\\EventDispatcher\\ListenerProviderInterface' => $vendorDir . '/psr/event-dispatcher/src/ListenerProviderInterface.php', + 'Psr\\EventDispatcher\\StoppableEventInterface' => $vendorDir . '/psr/event-dispatcher/src/StoppableEventInterface.php', + 'Psr\\Http\\Client\\ClientExceptionInterface' => $vendorDir . '/psr/http-client/src/ClientExceptionInterface.php', + 'Psr\\Http\\Client\\ClientInterface' => $vendorDir . '/psr/http-client/src/ClientInterface.php', + 'Psr\\Http\\Client\\NetworkExceptionInterface' => $vendorDir . '/psr/http-client/src/NetworkExceptionInterface.php', + 'Psr\\Http\\Client\\RequestExceptionInterface' => $vendorDir . '/psr/http-client/src/RequestExceptionInterface.php', + 'Psr\\Http\\Message\\MessageInterface' => $vendorDir . '/psr/http-message/src/MessageInterface.php', + 'Psr\\Http\\Message\\RequestFactoryInterface' => $vendorDir . '/psr/http-factory/src/RequestFactoryInterface.php', + 'Psr\\Http\\Message\\RequestInterface' => $vendorDir . '/psr/http-message/src/RequestInterface.php', + 'Psr\\Http\\Message\\ResponseFactoryInterface' => $vendorDir . '/psr/http-factory/src/ResponseFactoryInterface.php', + 'Psr\\Http\\Message\\ResponseInterface' => $vendorDir . '/psr/http-message/src/ResponseInterface.php', + 'Psr\\Http\\Message\\ServerRequestFactoryInterface' => $vendorDir . '/psr/http-factory/src/ServerRequestFactoryInterface.php', + 'Psr\\Http\\Message\\ServerRequestInterface' => $vendorDir . '/psr/http-message/src/ServerRequestInterface.php', + 'Psr\\Http\\Message\\StreamFactoryInterface' => $vendorDir . '/psr/http-factory/src/StreamFactoryInterface.php', + 'Psr\\Http\\Message\\StreamInterface' => $vendorDir . '/psr/http-message/src/StreamInterface.php', + 'Psr\\Http\\Message\\UploadedFileFactoryInterface' => $vendorDir . '/psr/http-factory/src/UploadedFileFactoryInterface.php', + 'Psr\\Http\\Message\\UploadedFileInterface' => $vendorDir . '/psr/http-message/src/UploadedFileInterface.php', + 'Psr\\Http\\Message\\UriFactoryInterface' => $vendorDir . '/psr/http-factory/src/UriFactoryInterface.php', + 'Psr\\Http\\Message\\UriInterface' => $vendorDir . '/psr/http-message/src/UriInterface.php', 'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php', 'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php', 'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php', @@ -846,11 +1364,10 @@ return array( 'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php', 'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php', 'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php', - 'Psr\\SimpleCache\\CacheException' => $vendorDir . '/psr/simple-cache/src/CacheException.php', - 'Psr\\SimpleCache\\CacheInterface' => $vendorDir . '/psr/simple-cache/src/CacheInterface.php', - 'Psr\\SimpleCache\\InvalidArgumentException' => $vendorDir . '/psr/simple-cache/src/InvalidArgumentException.php', 'QRcode' => $vendorDir . '/combodo/tcpdf/include/barcodes/qrcode.php', 'Query' => $baseDir . '/application/query.class.inc.php', + 'QueryBuilderContext' => $baseDir . '/core/querybuildercontext.class.inc.php', + 'QueryBuilderExpressions' => $baseDir . '/core/querybuilderexpressions.class.inc.php', 'QueryOQL' => $baseDir . '/application/query.class.inc.php', 'QueryReflection' => $baseDir . '/core/modelreflection.class.inc.php', 'QueryReflectionRuntime' => $baseDir . '/core/modelreflection.class.inc.php', @@ -864,6 +1381,7 @@ return array( 'RestResultWithObjects' => $baseDir . '/core/restservices.class.inc.php', 'RestResultWithRelations' => $baseDir . '/core/restservices.class.inc.php', 'RestUtils' => $baseDir . '/application/applicationextension.inc.php', + 'ReturnTypeWillChange' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', 'RotatingLogFileNameBuilder' => $baseDir . '/core/log.class.inc.php', 'RowStatus' => $baseDir . '/core/bulkchange.class.inc.php', 'RowStatus_Disappeared' => $baseDir . '/core/bulkchange.class.inc.php', @@ -879,17 +1397,72 @@ return array( 'SQLQuery' => $baseDir . '/core/sqlquery.class.inc.php', 'SQLUnionQuery' => $baseDir . '/core/sqlunionquery.class.inc.php', 'SVGDOMSanitizer' => $baseDir . '/core/htmlsanitizer.class.inc.php', + 'Sabberworm\\CSS\\CSSList\\AtRuleBlockList' => $vendorDir . '/sabberworm/php-css-parser/src/CSSList/AtRuleBlockList.php', + 'Sabberworm\\CSS\\CSSList\\CSSBlockList' => $vendorDir . '/sabberworm/php-css-parser/src/CSSList/CSSBlockList.php', + 'Sabberworm\\CSS\\CSSList\\CSSList' => $vendorDir . '/sabberworm/php-css-parser/src/CSSList/CSSList.php', + 'Sabberworm\\CSS\\CSSList\\Document' => $vendorDir . '/sabberworm/php-css-parser/src/CSSList/Document.php', + 'Sabberworm\\CSS\\CSSList\\KeyFrame' => $vendorDir . '/sabberworm/php-css-parser/src/CSSList/KeyFrame.php', + 'Sabberworm\\CSS\\Comment\\Comment' => $vendorDir . '/sabberworm/php-css-parser/src/Comment/Comment.php', + 'Sabberworm\\CSS\\Comment\\Commentable' => $vendorDir . '/sabberworm/php-css-parser/src/Comment/Commentable.php', + 'Sabberworm\\CSS\\OutputFormat' => $vendorDir . '/sabberworm/php-css-parser/src/OutputFormat.php', + 'Sabberworm\\CSS\\OutputFormatter' => $vendorDir . '/sabberworm/php-css-parser/src/OutputFormatter.php', + 'Sabberworm\\CSS\\Parser' => $vendorDir . '/sabberworm/php-css-parser/src/Parser.php', + 'Sabberworm\\CSS\\Parsing\\OutputException' => $vendorDir . '/sabberworm/php-css-parser/src/Parsing/OutputException.php', + 'Sabberworm\\CSS\\Parsing\\ParserState' => $vendorDir . '/sabberworm/php-css-parser/src/Parsing/ParserState.php', + 'Sabberworm\\CSS\\Parsing\\SourceException' => $vendorDir . '/sabberworm/php-css-parser/src/Parsing/SourceException.php', + 'Sabberworm\\CSS\\Parsing\\UnexpectedEOFException' => $vendorDir . '/sabberworm/php-css-parser/src/Parsing/UnexpectedEOFException.php', + 'Sabberworm\\CSS\\Parsing\\UnexpectedTokenException' => $vendorDir . '/sabberworm/php-css-parser/src/Parsing/UnexpectedTokenException.php', + 'Sabberworm\\CSS\\Property\\AtRule' => $vendorDir . '/sabberworm/php-css-parser/src/Property/AtRule.php', + 'Sabberworm\\CSS\\Property\\CSSNamespace' => $vendorDir . '/sabberworm/php-css-parser/src/Property/CSSNamespace.php', + 'Sabberworm\\CSS\\Property\\Charset' => $vendorDir . '/sabberworm/php-css-parser/src/Property/Charset.php', + 'Sabberworm\\CSS\\Property\\Import' => $vendorDir . '/sabberworm/php-css-parser/src/Property/Import.php', + 'Sabberworm\\CSS\\Property\\KeyframeSelector' => $vendorDir . '/sabberworm/php-css-parser/src/Property/KeyframeSelector.php', + 'Sabberworm\\CSS\\Property\\Selector' => $vendorDir . '/sabberworm/php-css-parser/src/Property/Selector.php', + 'Sabberworm\\CSS\\Renderable' => $vendorDir . '/sabberworm/php-css-parser/src/Renderable.php', + 'Sabberworm\\CSS\\RuleSet\\AtRuleSet' => $vendorDir . '/sabberworm/php-css-parser/src/RuleSet/AtRuleSet.php', + 'Sabberworm\\CSS\\RuleSet\\DeclarationBlock' => $vendorDir . '/sabberworm/php-css-parser/src/RuleSet/DeclarationBlock.php', + 'Sabberworm\\CSS\\RuleSet\\RuleSet' => $vendorDir . '/sabberworm/php-css-parser/src/RuleSet/RuleSet.php', + 'Sabberworm\\CSS\\Rule\\Rule' => $vendorDir . '/sabberworm/php-css-parser/src/Rule/Rule.php', + 'Sabberworm\\CSS\\Settings' => $vendorDir . '/sabberworm/php-css-parser/src/Settings.php', + 'Sabberworm\\CSS\\Value\\CSSFunction' => $vendorDir . '/sabberworm/php-css-parser/src/Value/CSSFunction.php', + 'Sabberworm\\CSS\\Value\\CSSString' => $vendorDir . '/sabberworm/php-css-parser/src/Value/CSSString.php', + 'Sabberworm\\CSS\\Value\\CalcFunction' => $vendorDir . '/sabberworm/php-css-parser/src/Value/CalcFunction.php', + 'Sabberworm\\CSS\\Value\\CalcRuleValueList' => $vendorDir . '/sabberworm/php-css-parser/src/Value/CalcRuleValueList.php', + 'Sabberworm\\CSS\\Value\\Color' => $vendorDir . '/sabberworm/php-css-parser/src/Value/Color.php', + 'Sabberworm\\CSS\\Value\\LineName' => $vendorDir . '/sabberworm/php-css-parser/src/Value/LineName.php', + 'Sabberworm\\CSS\\Value\\PrimitiveValue' => $vendorDir . '/sabberworm/php-css-parser/src/Value/PrimitiveValue.php', + 'Sabberworm\\CSS\\Value\\RuleValueList' => $vendorDir . '/sabberworm/php-css-parser/src/Value/RuleValueList.php', + 'Sabberworm\\CSS\\Value\\Size' => $vendorDir . '/sabberworm/php-css-parser/src/Value/Size.php', + 'Sabberworm\\CSS\\Value\\URL' => $vendorDir . '/sabberworm/php-css-parser/src/Value/URL.php', + 'Sabberworm\\CSS\\Value\\Value' => $vendorDir . '/sabberworm/php-css-parser/src/Value/Value.php', + 'Sabberworm\\CSS\\Value\\ValueList' => $vendorDir . '/sabberworm/php-css-parser/src/Value/ValueList.php', 'ScalarExpression' => $baseDir . '/core/oql/expression.class.inc.php', 'ScalarOqlExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php', 'ScssPhp\\ScssPhp\\Base\\Range' => $vendorDir . '/scssphp/scssphp/src/Base/Range.php', 'ScssPhp\\ScssPhp\\Block' => $vendorDir . '/scssphp/scssphp/src/Block.php', + 'ScssPhp\\ScssPhp\\Block\\AtRootBlock' => $vendorDir . '/scssphp/scssphp/src/Block/AtRootBlock.php', + 'ScssPhp\\ScssPhp\\Block\\CallableBlock' => $vendorDir . '/scssphp/scssphp/src/Block/CallableBlock.php', + 'ScssPhp\\ScssPhp\\Block\\ContentBlock' => $vendorDir . '/scssphp/scssphp/src/Block/ContentBlock.php', + 'ScssPhp\\ScssPhp\\Block\\DirectiveBlock' => $vendorDir . '/scssphp/scssphp/src/Block/DirectiveBlock.php', + 'ScssPhp\\ScssPhp\\Block\\EachBlock' => $vendorDir . '/scssphp/scssphp/src/Block/EachBlock.php', + 'ScssPhp\\ScssPhp\\Block\\ElseBlock' => $vendorDir . '/scssphp/scssphp/src/Block/ElseBlock.php', + 'ScssPhp\\ScssPhp\\Block\\ElseifBlock' => $vendorDir . '/scssphp/scssphp/src/Block/ElseifBlock.php', + 'ScssPhp\\ScssPhp\\Block\\ForBlock' => $vendorDir . '/scssphp/scssphp/src/Block/ForBlock.php', + 'ScssPhp\\ScssPhp\\Block\\IfBlock' => $vendorDir . '/scssphp/scssphp/src/Block/IfBlock.php', + 'ScssPhp\\ScssPhp\\Block\\MediaBlock' => $vendorDir . '/scssphp/scssphp/src/Block/MediaBlock.php', + 'ScssPhp\\ScssPhp\\Block\\NestedPropertyBlock' => $vendorDir . '/scssphp/scssphp/src/Block/NestedPropertyBlock.php', + 'ScssPhp\\ScssPhp\\Block\\WhileBlock' => $vendorDir . '/scssphp/scssphp/src/Block/WhileBlock.php', 'ScssPhp\\ScssPhp\\Cache' => $vendorDir . '/scssphp/scssphp/src/Cache.php', 'ScssPhp\\ScssPhp\\Colors' => $vendorDir . '/scssphp/scssphp/src/Colors.php', + 'ScssPhp\\ScssPhp\\CompilationResult' => $vendorDir . '/scssphp/scssphp/src/CompilationResult.php', 'ScssPhp\\ScssPhp\\Compiler' => $vendorDir . '/scssphp/scssphp/src/Compiler.php', + 'ScssPhp\\ScssPhp\\Compiler\\CachedResult' => $vendorDir . '/scssphp/scssphp/src/Compiler/CachedResult.php', 'ScssPhp\\ScssPhp\\Compiler\\Environment' => $vendorDir . '/scssphp/scssphp/src/Compiler/Environment.php', 'ScssPhp\\ScssPhp\\Exception\\CompilerException' => $vendorDir . '/scssphp/scssphp/src/Exception/CompilerException.php', 'ScssPhp\\ScssPhp\\Exception\\ParserException' => $vendorDir . '/scssphp/scssphp/src/Exception/ParserException.php', 'ScssPhp\\ScssPhp\\Exception\\RangeException' => $vendorDir . '/scssphp/scssphp/src/Exception/RangeException.php', + 'ScssPhp\\ScssPhp\\Exception\\SassException' => $vendorDir . '/scssphp/scssphp/src/Exception/SassException.php', + 'ScssPhp\\ScssPhp\\Exception\\SassScriptException' => $vendorDir . '/scssphp/scssphp/src/Exception/SassScriptException.php', 'ScssPhp\\ScssPhp\\Exception\\ServerException' => $vendorDir . '/scssphp/scssphp/src/Exception/ServerException.php', 'ScssPhp\\ScssPhp\\Formatter' => $vendorDir . '/scssphp/scssphp/src/Formatter.php', 'ScssPhp\\ScssPhp\\Formatter\\Compact' => $vendorDir . '/scssphp/scssphp/src/Formatter/Compact.php', @@ -899,19 +1472,25 @@ return array( 'ScssPhp\\ScssPhp\\Formatter\\Expanded' => $vendorDir . '/scssphp/scssphp/src/Formatter/Expanded.php', 'ScssPhp\\ScssPhp\\Formatter\\Nested' => $vendorDir . '/scssphp/scssphp/src/Formatter/Nested.php', 'ScssPhp\\ScssPhp\\Formatter\\OutputBlock' => $vendorDir . '/scssphp/scssphp/src/Formatter/OutputBlock.php', + 'ScssPhp\\ScssPhp\\Logger\\LoggerInterface' => $vendorDir . '/scssphp/scssphp/src/Logger/LoggerInterface.php', + 'ScssPhp\\ScssPhp\\Logger\\QuietLogger' => $vendorDir . '/scssphp/scssphp/src/Logger/QuietLogger.php', + 'ScssPhp\\ScssPhp\\Logger\\StreamLogger' => $vendorDir . '/scssphp/scssphp/src/Logger/StreamLogger.php', 'ScssPhp\\ScssPhp\\Node' => $vendorDir . '/scssphp/scssphp/src/Node.php', 'ScssPhp\\ScssPhp\\Node\\Number' => $vendorDir . '/scssphp/scssphp/src/Node/Number.php', + 'ScssPhp\\ScssPhp\\OutputStyle' => $vendorDir . '/scssphp/scssphp/src/OutputStyle.php', 'ScssPhp\\ScssPhp\\Parser' => $vendorDir . '/scssphp/scssphp/src/Parser.php', 'ScssPhp\\ScssPhp\\SourceMap\\Base64' => $vendorDir . '/scssphp/scssphp/src/SourceMap/Base64.php', 'ScssPhp\\ScssPhp\\SourceMap\\Base64VLQ' => $vendorDir . '/scssphp/scssphp/src/SourceMap/Base64VLQ.php', 'ScssPhp\\ScssPhp\\SourceMap\\SourceMapGenerator' => $vendorDir . '/scssphp/scssphp/src/SourceMap/SourceMapGenerator.php', 'ScssPhp\\ScssPhp\\Type' => $vendorDir . '/scssphp/scssphp/src/Type.php', 'ScssPhp\\ScssPhp\\Util' => $vendorDir . '/scssphp/scssphp/src/Util.php', + 'ScssPhp\\ScssPhp\\Util\\Path' => $vendorDir . '/scssphp/scssphp/src/Util/Path.php', + 'ScssPhp\\ScssPhp\\ValueConverter' => $vendorDir . '/scssphp/scssphp/src/ValueConverter.php', 'ScssPhp\\ScssPhp\\Version' => $vendorDir . '/scssphp/scssphp/src/Version.php', + 'ScssPhp\\ScssPhp\\Warn' => $vendorDir . '/scssphp/scssphp/src/Warn.php', 'SearchMenuNode' => $baseDir . '/application/menunode.class.inc.php', 'SecurityException' => $baseDir . '/application/exceptions/SecurityException.php', 'SeparatorPopupMenuItem' => $baseDir . '/application/applicationextension.inc.php', - 'SessionUpdateTimestampHandlerInterface' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php', 'SetupLog' => $baseDir . '/core/log.class.inc.php', 'Shortcut' => $baseDir . '/application/shortcut.class.inc.php', 'ShortcutContainerMenuNode' => $baseDir . '/application/menunode.class.inc.php', @@ -930,34 +1509,38 @@ return array( 'StimulusInternal' => $baseDir . '/core/stimulus.class.inc.php', 'StimulusUserAction' => $baseDir . '/core/stimulus.class.inc.php', 'Str' => $baseDir . '/core/MyHelpers.class.inc.php', - 'Swift_LogFileTransport' => $baseDir . '/core/email.class.inc.php', - 'Swift_Transport_LogFileTransport' => $baseDir . '/core/email.class.inc.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'Symfony\\Bridge\\Twig\\AppVariable' => $vendorDir . '/symfony/twig-bridge/AppVariable.php', 'Symfony\\Bridge\\Twig\\Command\\DebugCommand' => $vendorDir . '/symfony/twig-bridge/Command/DebugCommand.php', 'Symfony\\Bridge\\Twig\\Command\\LintCommand' => $vendorDir . '/symfony/twig-bridge/Command/LintCommand.php', 'Symfony\\Bridge\\Twig\\DataCollector\\TwigDataCollector' => $vendorDir . '/symfony/twig-bridge/DataCollector/TwigDataCollector.php', + 'Symfony\\Bridge\\Twig\\ErrorRenderer\\TwigErrorRenderer' => $vendorDir . '/symfony/twig-bridge/ErrorRenderer/TwigErrorRenderer.php', 'Symfony\\Bridge\\Twig\\Extension\\AssetExtension' => $vendorDir . '/symfony/twig-bridge/Extension/AssetExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\CodeExtension' => $vendorDir . '/symfony/twig-bridge/Extension/CodeExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\CsrfExtension' => $vendorDir . '/symfony/twig-bridge/Extension/CsrfExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\CsrfRuntime' => $vendorDir . '/symfony/twig-bridge/Extension/CsrfRuntime.php', 'Symfony\\Bridge\\Twig\\Extension\\DumpExtension' => $vendorDir . '/symfony/twig-bridge/Extension/DumpExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\ExpressionExtension' => $vendorDir . '/symfony/twig-bridge/Extension/ExpressionExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\FormExtension' => $vendorDir . '/symfony/twig-bridge/Extension/FormExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\HttpFoundationExtension' => $vendorDir . '/symfony/twig-bridge/Extension/HttpFoundationExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\HttpKernelExtension' => $vendorDir . '/symfony/twig-bridge/Extension/HttpKernelExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\HttpKernelRuntime' => $vendorDir . '/symfony/twig-bridge/Extension/HttpKernelRuntime.php', - 'Symfony\\Bridge\\Twig\\Extension\\InitRuntimeInterface' => $vendorDir . '/symfony/twig-bridge/Extension/InitRuntimeInterface.php', 'Symfony\\Bridge\\Twig\\Extension\\LogoutUrlExtension' => $vendorDir . '/symfony/twig-bridge/Extension/LogoutUrlExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension' => $vendorDir . '/symfony/twig-bridge/Extension/ProfilerExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\RoutingExtension' => $vendorDir . '/symfony/twig-bridge/Extension/RoutingExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\SecurityExtension' => $vendorDir . '/symfony/twig-bridge/Extension/SecurityExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\SerializerExtension' => $vendorDir . '/symfony/twig-bridge/Extension/SerializerExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\SerializerRuntime' => $vendorDir . '/symfony/twig-bridge/Extension/SerializerRuntime.php', 'Symfony\\Bridge\\Twig\\Extension\\StopwatchExtension' => $vendorDir . '/symfony/twig-bridge/Extension/StopwatchExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\TranslationExtension' => $vendorDir . '/symfony/twig-bridge/Extension/TranslationExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\WebLinkExtension' => $vendorDir . '/symfony/twig-bridge/Extension/WebLinkExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\WorkflowExtension' => $vendorDir . '/symfony/twig-bridge/Extension/WorkflowExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\YamlExtension' => $vendorDir . '/symfony/twig-bridge/Extension/YamlExtension.php', - 'Symfony\\Bridge\\Twig\\Form\\TwigRenderer' => $vendorDir . '/symfony/twig-bridge/Form/TwigRenderer.php', 'Symfony\\Bridge\\Twig\\Form\\TwigRendererEngine' => $vendorDir . '/symfony/twig-bridge/Form/TwigRendererEngine.php', - 'Symfony\\Bridge\\Twig\\Form\\TwigRendererEngineInterface' => $vendorDir . '/symfony/twig-bridge/Form/TwigRendererEngineInterface.php', - 'Symfony\\Bridge\\Twig\\Form\\TwigRendererInterface' => $vendorDir . '/symfony/twig-bridge/Form/TwigRendererInterface.php', + 'Symfony\\Bridge\\Twig\\Mime\\BodyRenderer' => $vendorDir . '/symfony/twig-bridge/Mime/BodyRenderer.php', + 'Symfony\\Bridge\\Twig\\Mime\\NotificationEmail' => $vendorDir . '/symfony/twig-bridge/Mime/NotificationEmail.php', + 'Symfony\\Bridge\\Twig\\Mime\\TemplatedEmail' => $vendorDir . '/symfony/twig-bridge/Mime/TemplatedEmail.php', + 'Symfony\\Bridge\\Twig\\Mime\\WrappedTemplatedEmail' => $vendorDir . '/symfony/twig-bridge/Mime/WrappedTemplatedEmail.php', 'Symfony\\Bridge\\Twig\\NodeVisitor\\Scope' => $vendorDir . '/symfony/twig-bridge/NodeVisitor/Scope.php', 'Symfony\\Bridge\\Twig\\NodeVisitor\\TranslationDefaultDomainNodeVisitor' => $vendorDir . '/symfony/twig-bridge/NodeVisitor/TranslationDefaultDomainNodeVisitor.php', 'Symfony\\Bridge\\Twig\\NodeVisitor\\TranslationNodeVisitor' => $vendorDir . '/symfony/twig-bridge/NodeVisitor/TranslationNodeVisitor.php', @@ -971,38 +1554,42 @@ return array( 'Symfony\\Bridge\\Twig\\TokenParser\\DumpTokenParser' => $vendorDir . '/symfony/twig-bridge/TokenParser/DumpTokenParser.php', 'Symfony\\Bridge\\Twig\\TokenParser\\FormThemeTokenParser' => $vendorDir . '/symfony/twig-bridge/TokenParser/FormThemeTokenParser.php', 'Symfony\\Bridge\\Twig\\TokenParser\\StopwatchTokenParser' => $vendorDir . '/symfony/twig-bridge/TokenParser/StopwatchTokenParser.php', - 'Symfony\\Bridge\\Twig\\TokenParser\\TransChoiceTokenParser' => $vendorDir . '/symfony/twig-bridge/TokenParser/TransChoiceTokenParser.php', 'Symfony\\Bridge\\Twig\\TokenParser\\TransDefaultDomainTokenParser' => $vendorDir . '/symfony/twig-bridge/TokenParser/TransDefaultDomainTokenParser.php', 'Symfony\\Bridge\\Twig\\TokenParser\\TransTokenParser' => $vendorDir . '/symfony/twig-bridge/TokenParser/TransTokenParser.php', 'Symfony\\Bridge\\Twig\\Translation\\TwigExtractor' => $vendorDir . '/symfony/twig-bridge/Translation/TwigExtractor.php', - 'Symfony\\Bridge\\Twig\\TwigEngine' => $vendorDir . '/symfony/twig-bridge/TwigEngine.php', 'Symfony\\Bridge\\Twig\\UndefinedCallableHandler' => $vendorDir . '/symfony/twig-bridge/UndefinedCallableHandler.php', 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\AbstractPhpFileCacheWarmer' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/AbstractPhpFileCacheWarmer.php', 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\AnnotationsCacheWarmer' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/AnnotationsCacheWarmer.php', - 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\ClassCacheCacheWarmer' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/ClassCacheCacheWarmer.php', + 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\CachePoolClearerCacheWarmer' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/CachePoolClearerCacheWarmer.php', + 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\ConfigBuilderCacheWarmer' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/ConfigBuilderCacheWarmer.php', 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\RouterCacheWarmer' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/RouterCacheWarmer.php', 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\SerializerCacheWarmer' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/SerializerCacheWarmer.php', - 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\TemplateFinder' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/TemplateFinder.php', - 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\TemplateFinderInterface' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/TemplateFinderInterface.php', - 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\TemplatePathsCacheWarmer' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/TemplatePathsCacheWarmer.php', 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\TranslationsCacheWarmer' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/TranslationsCacheWarmer.php', 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\ValidatorCacheWarmer' => $vendorDir . '/symfony/framework-bundle/CacheWarmer/ValidatorCacheWarmer.php', - 'Symfony\\Bundle\\FrameworkBundle\\Client' => $vendorDir . '/symfony/framework-bundle/Client.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\AboutCommand' => $vendorDir . '/symfony/framework-bundle/Command/AboutCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\AbstractConfigCommand' => $vendorDir . '/symfony/framework-bundle/Command/AbstractConfigCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\AssetsInstallCommand' => $vendorDir . '/symfony/framework-bundle/Command/AssetsInstallCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\BuildDebugContainerTrait' => $vendorDir . '/symfony/framework-bundle/Command/BuildDebugContainerTrait.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\CacheClearCommand' => $vendorDir . '/symfony/framework-bundle/Command/CacheClearCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolClearCommand' => $vendorDir . '/symfony/framework-bundle/Command/CachePoolClearCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolDeleteCommand' => $vendorDir . '/symfony/framework-bundle/Command/CachePoolDeleteCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolListCommand' => $vendorDir . '/symfony/framework-bundle/Command/CachePoolListCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolPruneCommand' => $vendorDir . '/symfony/framework-bundle/Command/CachePoolPruneCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\CacheWarmupCommand' => $vendorDir . '/symfony/framework-bundle/Command/CacheWarmupCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\ConfigDebugCommand' => $vendorDir . '/symfony/framework-bundle/Command/ConfigDebugCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\ConfigDumpReferenceCommand' => $vendorDir . '/symfony/framework-bundle/Command/ConfigDumpReferenceCommand.php', - 'Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerAwareCommand' => $vendorDir . '/symfony/framework-bundle/Command/ContainerAwareCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerDebugCommand' => $vendorDir . '/symfony/framework-bundle/Command/ContainerDebugCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerLintCommand' => $vendorDir . '/symfony/framework-bundle/Command/ContainerLintCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\DebugAutowiringCommand' => $vendorDir . '/symfony/framework-bundle/Command/DebugAutowiringCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\EventDispatcherDebugCommand' => $vendorDir . '/symfony/framework-bundle/Command/EventDispatcherDebugCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\RouterDebugCommand' => $vendorDir . '/symfony/framework-bundle/Command/RouterDebugCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\RouterMatchCommand' => $vendorDir . '/symfony/framework-bundle/Command/RouterMatchCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsDecryptToLocalCommand' => $vendorDir . '/symfony/framework-bundle/Command/SecretsDecryptToLocalCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsEncryptFromLocalCommand' => $vendorDir . '/symfony/framework-bundle/Command/SecretsEncryptFromLocalCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsGenerateKeysCommand' => $vendorDir . '/symfony/framework-bundle/Command/SecretsGenerateKeysCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsListCommand' => $vendorDir . '/symfony/framework-bundle/Command/SecretsListCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsRemoveCommand' => $vendorDir . '/symfony/framework-bundle/Command/SecretsRemoveCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsSetCommand' => $vendorDir . '/symfony/framework-bundle/Command/SecretsSetCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\TranslationDebugCommand' => $vendorDir . '/symfony/framework-bundle/Command/TranslationDebugCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\TranslationUpdateCommand' => $vendorDir . '/symfony/framework-bundle/Command/TranslationUpdateCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\WorkflowDumpCommand' => $vendorDir . '/symfony/framework-bundle/Command/WorkflowDumpCommand.php', @@ -1016,92 +1603,46 @@ return array( 'Symfony\\Bundle\\FrameworkBundle\\Console\\Descriptor\\XmlDescriptor' => $vendorDir . '/symfony/framework-bundle/Console/Descriptor/XmlDescriptor.php', 'Symfony\\Bundle\\FrameworkBundle\\Console\\Helper\\DescriptorHelper' => $vendorDir . '/symfony/framework-bundle/Console/Helper/DescriptorHelper.php', 'Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController' => $vendorDir . '/symfony/framework-bundle/Controller/AbstractController.php', - 'Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller' => $vendorDir . '/symfony/framework-bundle/Controller/Controller.php', - 'Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerNameParser' => $vendorDir . '/symfony/framework-bundle/Controller/ControllerNameParser.php', 'Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerResolver' => $vendorDir . '/symfony/framework-bundle/Controller/ControllerResolver.php', - 'Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerTrait' => $vendorDir . '/symfony/framework-bundle/Controller/ControllerTrait.php', 'Symfony\\Bundle\\FrameworkBundle\\Controller\\RedirectController' => $vendorDir . '/symfony/framework-bundle/Controller/RedirectController.php', 'Symfony\\Bundle\\FrameworkBundle\\Controller\\TemplateController' => $vendorDir . '/symfony/framework-bundle/Controller/TemplateController.php', - 'Symfony\\Bundle\\FrameworkBundle\\DataCollector\\RequestDataCollector' => $vendorDir . '/symfony/framework-bundle/DataCollector/RequestDataCollector.php', + 'Symfony\\Bundle\\FrameworkBundle\\DataCollector\\AbstractDataCollector' => $vendorDir . '/symfony/framework-bundle/DataCollector/AbstractDataCollector.php', 'Symfony\\Bundle\\FrameworkBundle\\DataCollector\\RouterDataCollector' => $vendorDir . '/symfony/framework-bundle/DataCollector/RouterDataCollector.php', + 'Symfony\\Bundle\\FrameworkBundle\\DataCollector\\TemplateAwareDataCollectorInterface' => $vendorDir . '/symfony/framework-bundle/DataCollector/TemplateAwareDataCollectorInterface.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddAnnotationsCachedReaderPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddCacheClearerPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/AddCacheClearerPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddCacheWarmerPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/AddCacheWarmerPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddConsoleCommandPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/AddConsoleCommandPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddConstraintValidatorsPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/AddConstraintValidatorsPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddDebugLogProcessorPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddExpressionLanguageProvidersPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddValidatorInitializersPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/AddValidatorInitializersPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\CacheCollectorPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/CacheCollectorPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\CachePoolClearerPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/CachePoolClearerPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\CachePoolPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/CachePoolPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\CachePoolPrunerPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/CachePoolPrunerPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\CompilerDebugDumpPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/CompilerDebugDumpPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ConfigCachePass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/ConfigCachePass.php', + 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AssetsContextPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/AssetsContextPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ContainerBuilderDebugDumpPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ControllerArgumentValueResolverPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/ControllerArgumentValueResolverPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\DataCollectorTranslatorPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\FormPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/FormPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\LoggingTranslatorPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/LoggingTranslatorPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ProfilerPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/ProfilerPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\PropertyInfoPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/PropertyInfoPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\RoutingResolverPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/RoutingResolverPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\SerializerPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/SerializerPass.php', + 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\RemoveUnusedSessionMarshallingHandlerPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\SessionPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/SessionPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TemplatingPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/TemplatingPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TranslationDumperPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/TranslationDumperPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TranslationExtractorPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/TranslationExtractorPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TranslatorPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/TranslatorPass.php', + 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TestServiceContainerRealRefPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php', + 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TestServiceContainerWeakRefPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\UnusedTagsPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/UnusedTagsPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ValidateWorkflowsPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/ValidateWorkflowsPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\WorkflowGuardListenerPass' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Configuration' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/Configuration.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\FrameworkExtension' => $vendorDir . '/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php', - 'Symfony\\Bundle\\FrameworkBundle\\EventListener\\ResolveControllerNameSubscriber' => $vendorDir . '/symfony/framework-bundle/EventListener/ResolveControllerNameSubscriber.php', - 'Symfony\\Bundle\\FrameworkBundle\\EventListener\\SessionListener' => $vendorDir . '/symfony/framework-bundle/EventListener/SessionListener.php', - 'Symfony\\Bundle\\FrameworkBundle\\EventListener\\TestSessionListener' => $vendorDir . '/symfony/framework-bundle/EventListener/TestSessionListener.php', + 'Symfony\\Bundle\\FrameworkBundle\\EventListener\\SuggestMissingPackageSubscriber' => $vendorDir . '/symfony/framework-bundle/EventListener/SuggestMissingPackageSubscriber.php', 'Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle' => $vendorDir . '/symfony/framework-bundle/FrameworkBundle.php', 'Symfony\\Bundle\\FrameworkBundle\\HttpCache\\HttpCache' => $vendorDir . '/symfony/framework-bundle/HttpCache/HttpCache.php', + 'Symfony\\Bundle\\FrameworkBundle\\KernelBrowser' => $vendorDir . '/symfony/framework-bundle/KernelBrowser.php', 'Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait' => $vendorDir . '/symfony/framework-bundle/Kernel/MicroKernelTrait.php', 'Symfony\\Bundle\\FrameworkBundle\\Routing\\AnnotatedRouteControllerLoader' => $vendorDir . '/symfony/framework-bundle/Routing/AnnotatedRouteControllerLoader.php', 'Symfony\\Bundle\\FrameworkBundle\\Routing\\DelegatingLoader' => $vendorDir . '/symfony/framework-bundle/Routing/DelegatingLoader.php', - 'Symfony\\Bundle\\FrameworkBundle\\Routing\\RedirectableUrlMatcher' => $vendorDir . '/symfony/framework-bundle/Routing/RedirectableUrlMatcher.php', + 'Symfony\\Bundle\\FrameworkBundle\\Routing\\RedirectableCompiledUrlMatcher' => $vendorDir . '/symfony/framework-bundle/Routing/RedirectableCompiledUrlMatcher.php', + 'Symfony\\Bundle\\FrameworkBundle\\Routing\\RouteLoaderInterface' => $vendorDir . '/symfony/framework-bundle/Routing/RouteLoaderInterface.php', 'Symfony\\Bundle\\FrameworkBundle\\Routing\\Router' => $vendorDir . '/symfony/framework-bundle/Routing/Router.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\DelegatingEngine' => $vendorDir . '/symfony/framework-bundle/Templating/DelegatingEngine.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\EngineInterface' => $vendorDir . '/symfony/framework-bundle/Templating/EngineInterface.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\GlobalVariables' => $vendorDir . '/symfony/framework-bundle/Templating/GlobalVariables.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\ActionsHelper' => $vendorDir . '/symfony/framework-bundle/Templating/Helper/ActionsHelper.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\AssetsHelper' => $vendorDir . '/symfony/framework-bundle/Templating/Helper/AssetsHelper.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\CodeHelper' => $vendorDir . '/symfony/framework-bundle/Templating/Helper/CodeHelper.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\FormHelper' => $vendorDir . '/symfony/framework-bundle/Templating/Helper/FormHelper.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\RequestHelper' => $vendorDir . '/symfony/framework-bundle/Templating/Helper/RequestHelper.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\RouterHelper' => $vendorDir . '/symfony/framework-bundle/Templating/Helper/RouterHelper.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\SessionHelper' => $vendorDir . '/symfony/framework-bundle/Templating/Helper/SessionHelper.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\StopwatchHelper' => $vendorDir . '/symfony/framework-bundle/Templating/Helper/StopwatchHelper.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\TranslatorHelper' => $vendorDir . '/symfony/framework-bundle/Templating/Helper/TranslatorHelper.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Loader\\FilesystemLoader' => $vendorDir . '/symfony/framework-bundle/Templating/Loader/FilesystemLoader.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Loader\\TemplateLocator' => $vendorDir . '/symfony/framework-bundle/Templating/Loader/TemplateLocator.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\PhpEngine' => $vendorDir . '/symfony/framework-bundle/Templating/PhpEngine.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\TemplateFilenameParser' => $vendorDir . '/symfony/framework-bundle/Templating/TemplateFilenameParser.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\TemplateNameParser' => $vendorDir . '/symfony/framework-bundle/Templating/TemplateNameParser.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\TemplateReference' => $vendorDir . '/symfony/framework-bundle/Templating/TemplateReference.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\TimedPhpEngine' => $vendorDir . '/symfony/framework-bundle/Templating/TimedPhpEngine.php', - 'Symfony\\Bundle\\FrameworkBundle\\Test\\ForwardCompatTestTrait' => $vendorDir . '/symfony/framework-bundle/Test/ForwardCompatTestTrait.php', - 'Symfony\\Bundle\\FrameworkBundle\\Test\\KernelTestCase' => $vendorDir . '/symfony/framework-bundle/Test/KernelTestCase.php', - 'Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestCase' => $vendorDir . '/symfony/framework-bundle/Test/WebTestCase.php', - 'Symfony\\Bundle\\FrameworkBundle\\Translation\\PhpExtractor' => $vendorDir . '/symfony/framework-bundle/Translation/PhpExtractor.php', - 'Symfony\\Bundle\\FrameworkBundle\\Translation\\PhpStringTokenParser' => $vendorDir . '/symfony/framework-bundle/Translation/PhpStringTokenParser.php', - 'Symfony\\Bundle\\FrameworkBundle\\Translation\\TranslationLoader' => $vendorDir . '/symfony/framework-bundle/Translation/TranslationLoader.php', + 'Symfony\\Bundle\\FrameworkBundle\\Secrets\\AbstractVault' => $vendorDir . '/symfony/framework-bundle/Secrets/AbstractVault.php', + 'Symfony\\Bundle\\FrameworkBundle\\Secrets\\DotenvVault' => $vendorDir . '/symfony/framework-bundle/Secrets/DotenvVault.php', + 'Symfony\\Bundle\\FrameworkBundle\\Secrets\\SodiumVault' => $vendorDir . '/symfony/framework-bundle/Secrets/SodiumVault.php', + 'Symfony\\Bundle\\FrameworkBundle\\Session\\DeprecatedSessionFactory' => $vendorDir . '/symfony/framework-bundle/Session/DeprecatedSessionFactory.php', + 'Symfony\\Bundle\\FrameworkBundle\\Session\\ServiceSessionFactory' => $vendorDir . '/symfony/framework-bundle/Session/ServiceSessionFactory.php', 'Symfony\\Bundle\\FrameworkBundle\\Translation\\Translator' => $vendorDir . '/symfony/framework-bundle/Translation/Translator.php', - 'Symfony\\Bundle\\FrameworkBundle\\Validator\\ConstraintValidatorFactory' => $vendorDir . '/symfony/framework-bundle/Validator/ConstraintValidatorFactory.php', - 'Symfony\\Bundle\\TwigBundle\\CacheWarmer\\TemplateCacheCacheWarmer' => $vendorDir . '/symfony/twig-bundle/CacheWarmer/TemplateCacheCacheWarmer.php', 'Symfony\\Bundle\\TwigBundle\\CacheWarmer\\TemplateCacheWarmer' => $vendorDir . '/symfony/twig-bundle/CacheWarmer/TemplateCacheWarmer.php', - 'Symfony\\Bundle\\TwigBundle\\Command\\DebugCommand' => $vendorDir . '/symfony/twig-bundle/Command/DebugCommand.php', 'Symfony\\Bundle\\TwigBundle\\Command\\LintCommand' => $vendorDir . '/symfony/twig-bundle/Command/LintCommand.php', - 'Symfony\\Bundle\\TwigBundle\\ContainerAwareRuntimeLoader' => $vendorDir . '/symfony/twig-bundle/ContainerAwareRuntimeLoader.php', - 'Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController' => $vendorDir . '/symfony/twig-bundle/Controller/ExceptionController.php', - 'Symfony\\Bundle\\TwigBundle\\Controller\\PreviewErrorController' => $vendorDir . '/symfony/twig-bundle/Controller/PreviewErrorController.php', - 'Symfony\\Bundle\\TwigBundle\\DependencyInjection\\Compiler\\ExceptionListenerPass' => $vendorDir . '/symfony/twig-bundle/DependencyInjection/Compiler/ExceptionListenerPass.php', 'Symfony\\Bundle\\TwigBundle\\DependencyInjection\\Compiler\\ExtensionPass' => $vendorDir . '/symfony/twig-bundle/DependencyInjection/Compiler/ExtensionPass.php', 'Symfony\\Bundle\\TwigBundle\\DependencyInjection\\Compiler\\RuntimeLoaderPass' => $vendorDir . '/symfony/twig-bundle/DependencyInjection/Compiler/RuntimeLoaderPass.php', 'Symfony\\Bundle\\TwigBundle\\DependencyInjection\\Compiler\\TwigEnvironmentPass' => $vendorDir . '/symfony/twig-bundle/DependencyInjection/Compiler/TwigEnvironmentPass.php', @@ -1109,11 +1650,9 @@ return array( 'Symfony\\Bundle\\TwigBundle\\DependencyInjection\\Configuration' => $vendorDir . '/symfony/twig-bundle/DependencyInjection/Configuration.php', 'Symfony\\Bundle\\TwigBundle\\DependencyInjection\\Configurator\\EnvironmentConfigurator' => $vendorDir . '/symfony/twig-bundle/DependencyInjection/Configurator/EnvironmentConfigurator.php', 'Symfony\\Bundle\\TwigBundle\\DependencyInjection\\TwigExtension' => $vendorDir . '/symfony/twig-bundle/DependencyInjection/TwigExtension.php', - 'Symfony\\Bundle\\TwigBundle\\Loader\\FilesystemLoader' => $vendorDir . '/symfony/twig-bundle/Loader/FilesystemLoader.php', 'Symfony\\Bundle\\TwigBundle\\TemplateIterator' => $vendorDir . '/symfony/twig-bundle/TemplateIterator.php', 'Symfony\\Bundle\\TwigBundle\\TwigBundle' => $vendorDir . '/symfony/twig-bundle/TwigBundle.php', - 'Symfony\\Bundle\\TwigBundle\\TwigEngine' => $vendorDir . '/symfony/twig-bundle/TwigEngine.php', - 'Symfony\\Bundle\\WebProfilerBundle\\Controller\\ExceptionController' => $vendorDir . '/symfony/web-profiler-bundle/Controller/ExceptionController.php', + 'Symfony\\Bundle\\WebProfilerBundle\\Controller\\ExceptionPanelController' => $vendorDir . '/symfony/web-profiler-bundle/Controller/ExceptionPanelController.php', 'Symfony\\Bundle\\WebProfilerBundle\\Controller\\ProfilerController' => $vendorDir . '/symfony/web-profiler-bundle/Controller/ProfilerController.php', 'Symfony\\Bundle\\WebProfilerBundle\\Controller\\RouterController' => $vendorDir . '/symfony/web-profiler-bundle/Controller/RouterController.php', 'Symfony\\Bundle\\WebProfilerBundle\\Csp\\ContentSecurityPolicyHandler' => $vendorDir . '/symfony/web-profiler-bundle/Csp/ContentSecurityPolicyHandler.php', @@ -1125,66 +1664,68 @@ return array( 'Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension' => $vendorDir . '/symfony/web-profiler-bundle/Twig/WebProfilerExtension.php', 'Symfony\\Bundle\\WebProfilerBundle\\WebProfilerBundle' => $vendorDir . '/symfony/web-profiler-bundle/WebProfilerBundle.php', 'Symfony\\Component\\Cache\\Adapter\\AbstractAdapter' => $vendorDir . '/symfony/cache/Adapter/AbstractAdapter.php', + 'Symfony\\Component\\Cache\\Adapter\\AbstractTagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/AbstractTagAwareAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\AdapterInterface' => $vendorDir . '/symfony/cache/Adapter/AdapterInterface.php', 'Symfony\\Component\\Cache\\Adapter\\ApcuAdapter' => $vendorDir . '/symfony/cache/Adapter/ApcuAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\ArrayAdapter' => $vendorDir . '/symfony/cache/Adapter/ArrayAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\ChainAdapter' => $vendorDir . '/symfony/cache/Adapter/ChainAdapter.php', + 'Symfony\\Component\\Cache\\Adapter\\CouchbaseBucketAdapter' => $vendorDir . '/symfony/cache/Adapter/CouchbaseBucketAdapter.php', + 'Symfony\\Component\\Cache\\Adapter\\CouchbaseCollectionAdapter' => $vendorDir . '/symfony/cache/Adapter/CouchbaseCollectionAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\DoctrineAdapter' => $vendorDir . '/symfony/cache/Adapter/DoctrineAdapter.php', + 'Symfony\\Component\\Cache\\Adapter\\DoctrineDbalAdapter' => $vendorDir . '/symfony/cache/Adapter/DoctrineDbalAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\FilesystemAdapter' => $vendorDir . '/symfony/cache/Adapter/FilesystemAdapter.php', + 'Symfony\\Component\\Cache\\Adapter\\FilesystemTagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/FilesystemTagAwareAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\MemcachedAdapter' => $vendorDir . '/symfony/cache/Adapter/MemcachedAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\NullAdapter' => $vendorDir . '/symfony/cache/Adapter/NullAdapter.php', + 'Symfony\\Component\\Cache\\Adapter\\ParameterNormalizer' => $vendorDir . '/symfony/cache/Adapter/ParameterNormalizer.php', 'Symfony\\Component\\Cache\\Adapter\\PdoAdapter' => $vendorDir . '/symfony/cache/Adapter/PdoAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\PhpArrayAdapter' => $vendorDir . '/symfony/cache/Adapter/PhpArrayAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\PhpFilesAdapter' => $vendorDir . '/symfony/cache/Adapter/PhpFilesAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\ProxyAdapter' => $vendorDir . '/symfony/cache/Adapter/ProxyAdapter.php', + 'Symfony\\Component\\Cache\\Adapter\\Psr16Adapter' => $vendorDir . '/symfony/cache/Adapter/Psr16Adapter.php', 'Symfony\\Component\\Cache\\Adapter\\RedisAdapter' => $vendorDir . '/symfony/cache/Adapter/RedisAdapter.php', - 'Symfony\\Component\\Cache\\Adapter\\SimpleCacheAdapter' => $vendorDir . '/symfony/cache/Adapter/SimpleCacheAdapter.php', + 'Symfony\\Component\\Cache\\Adapter\\RedisTagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/RedisTagAwareAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/TagAwareAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface' => $vendorDir . '/symfony/cache/Adapter/TagAwareAdapterInterface.php', 'Symfony\\Component\\Cache\\Adapter\\TraceableAdapter' => $vendorDir . '/symfony/cache/Adapter/TraceableAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\TraceableTagAwareAdapter' => $vendorDir . '/symfony/cache/Adapter/TraceableTagAwareAdapter.php', 'Symfony\\Component\\Cache\\CacheItem' => $vendorDir . '/symfony/cache/CacheItem.php', 'Symfony\\Component\\Cache\\DataCollector\\CacheDataCollector' => $vendorDir . '/symfony/cache/DataCollector/CacheDataCollector.php', + 'Symfony\\Component\\Cache\\DependencyInjection\\CacheCollectorPass' => $vendorDir . '/symfony/cache/DependencyInjection/CacheCollectorPass.php', + 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolClearerPass' => $vendorDir . '/symfony/cache/DependencyInjection/CachePoolClearerPass.php', + 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPass' => $vendorDir . '/symfony/cache/DependencyInjection/CachePoolPass.php', + 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPrunerPass' => $vendorDir . '/symfony/cache/DependencyInjection/CachePoolPrunerPass.php', 'Symfony\\Component\\Cache\\DoctrineProvider' => $vendorDir . '/symfony/cache/DoctrineProvider.php', 'Symfony\\Component\\Cache\\Exception\\CacheException' => $vendorDir . '/symfony/cache/Exception/CacheException.php', 'Symfony\\Component\\Cache\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/cache/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Cache\\Exception\\LogicException' => $vendorDir . '/symfony/cache/Exception/LogicException.php', + 'Symfony\\Component\\Cache\\LockRegistry' => $vendorDir . '/symfony/cache/LockRegistry.php', + 'Symfony\\Component\\Cache\\Marshaller\\DefaultMarshaller' => $vendorDir . '/symfony/cache/Marshaller/DefaultMarshaller.php', + 'Symfony\\Component\\Cache\\Marshaller\\DeflateMarshaller' => $vendorDir . '/symfony/cache/Marshaller/DeflateMarshaller.php', + 'Symfony\\Component\\Cache\\Marshaller\\MarshallerInterface' => $vendorDir . '/symfony/cache/Marshaller/MarshallerInterface.php', + 'Symfony\\Component\\Cache\\Marshaller\\SodiumMarshaller' => $vendorDir . '/symfony/cache/Marshaller/SodiumMarshaller.php', + 'Symfony\\Component\\Cache\\Marshaller\\TagAwareMarshaller' => $vendorDir . '/symfony/cache/Marshaller/TagAwareMarshaller.php', + 'Symfony\\Component\\Cache\\Messenger\\EarlyExpirationDispatcher' => $vendorDir . '/symfony/cache/Messenger/EarlyExpirationDispatcher.php', + 'Symfony\\Component\\Cache\\Messenger\\EarlyExpirationHandler' => $vendorDir . '/symfony/cache/Messenger/EarlyExpirationHandler.php', + 'Symfony\\Component\\Cache\\Messenger\\EarlyExpirationMessage' => $vendorDir . '/symfony/cache/Messenger/EarlyExpirationMessage.php', 'Symfony\\Component\\Cache\\PruneableInterface' => $vendorDir . '/symfony/cache/PruneableInterface.php', + 'Symfony\\Component\\Cache\\Psr16Cache' => $vendorDir . '/symfony/cache/Psr16Cache.php', 'Symfony\\Component\\Cache\\ResettableInterface' => $vendorDir . '/symfony/cache/ResettableInterface.php', - 'Symfony\\Component\\Cache\\Simple\\AbstractCache' => $vendorDir . '/symfony/cache/Simple/AbstractCache.php', - 'Symfony\\Component\\Cache\\Simple\\ApcuCache' => $vendorDir . '/symfony/cache/Simple/ApcuCache.php', - 'Symfony\\Component\\Cache\\Simple\\ArrayCache' => $vendorDir . '/symfony/cache/Simple/ArrayCache.php', - 'Symfony\\Component\\Cache\\Simple\\ChainCache' => $vendorDir . '/symfony/cache/Simple/ChainCache.php', - 'Symfony\\Component\\Cache\\Simple\\DoctrineCache' => $vendorDir . '/symfony/cache/Simple/DoctrineCache.php', - 'Symfony\\Component\\Cache\\Simple\\FilesystemCache' => $vendorDir . '/symfony/cache/Simple/FilesystemCache.php', - 'Symfony\\Component\\Cache\\Simple\\MemcachedCache' => $vendorDir . '/symfony/cache/Simple/MemcachedCache.php', - 'Symfony\\Component\\Cache\\Simple\\NullCache' => $vendorDir . '/symfony/cache/Simple/NullCache.php', - 'Symfony\\Component\\Cache\\Simple\\PdoCache' => $vendorDir . '/symfony/cache/Simple/PdoCache.php', - 'Symfony\\Component\\Cache\\Simple\\PhpArrayCache' => $vendorDir . '/symfony/cache/Simple/PhpArrayCache.php', - 'Symfony\\Component\\Cache\\Simple\\PhpFilesCache' => $vendorDir . '/symfony/cache/Simple/PhpFilesCache.php', - 'Symfony\\Component\\Cache\\Simple\\Psr6Cache' => $vendorDir . '/symfony/cache/Simple/Psr6Cache.php', - 'Symfony\\Component\\Cache\\Simple\\RedisCache' => $vendorDir . '/symfony/cache/Simple/RedisCache.php', - 'Symfony\\Component\\Cache\\Simple\\TraceableCache' => $vendorDir . '/symfony/cache/Simple/TraceableCache.php', - 'Symfony\\Component\\Cache\\Traits\\AbstractTrait' => $vendorDir . '/symfony/cache/Traits/AbstractTrait.php', - 'Symfony\\Component\\Cache\\Traits\\ApcuTrait' => $vendorDir . '/symfony/cache/Traits/ApcuTrait.php', - 'Symfony\\Component\\Cache\\Traits\\ArrayTrait' => $vendorDir . '/symfony/cache/Traits/ArrayTrait.php', - 'Symfony\\Component\\Cache\\Traits\\DoctrineTrait' => $vendorDir . '/symfony/cache/Traits/DoctrineTrait.php', + 'Symfony\\Component\\Cache\\Traits\\AbstractAdapterTrait' => $vendorDir . '/symfony/cache/Traits/AbstractAdapterTrait.php', + 'Symfony\\Component\\Cache\\Traits\\ContractsTrait' => $vendorDir . '/symfony/cache/Traits/ContractsTrait.php', 'Symfony\\Component\\Cache\\Traits\\FilesystemCommonTrait' => $vendorDir . '/symfony/cache/Traits/FilesystemCommonTrait.php', 'Symfony\\Component\\Cache\\Traits\\FilesystemTrait' => $vendorDir . '/symfony/cache/Traits/FilesystemTrait.php', - 'Symfony\\Component\\Cache\\Traits\\MemcachedTrait' => $vendorDir . '/symfony/cache/Traits/MemcachedTrait.php', - 'Symfony\\Component\\Cache\\Traits\\PdoTrait' => $vendorDir . '/symfony/cache/Traits/PdoTrait.php', - 'Symfony\\Component\\Cache\\Traits\\PhpArrayTrait' => $vendorDir . '/symfony/cache/Traits/PhpArrayTrait.php', - 'Symfony\\Component\\Cache\\Traits\\PhpFilesTrait' => $vendorDir . '/symfony/cache/Traits/PhpFilesTrait.php', 'Symfony\\Component\\Cache\\Traits\\ProxyTrait' => $vendorDir . '/symfony/cache/Traits/ProxyTrait.php', + 'Symfony\\Component\\Cache\\Traits\\RedisClusterNodeProxy' => $vendorDir . '/symfony/cache/Traits/RedisClusterNodeProxy.php', + 'Symfony\\Component\\Cache\\Traits\\RedisClusterProxy' => $vendorDir . '/symfony/cache/Traits/RedisClusterProxy.php', 'Symfony\\Component\\Cache\\Traits\\RedisProxy' => $vendorDir . '/symfony/cache/Traits/RedisProxy.php', 'Symfony\\Component\\Cache\\Traits\\RedisTrait' => $vendorDir . '/symfony/cache/Traits/RedisTrait.php', - 'Symfony\\Component\\ClassLoader\\ApcClassLoader' => $vendorDir . '/symfony/class-loader/ApcClassLoader.php', - 'Symfony\\Component\\ClassLoader\\ClassCollectionLoader' => $vendorDir . '/symfony/class-loader/ClassCollectionLoader.php', - 'Symfony\\Component\\ClassLoader\\ClassLoader' => $vendorDir . '/symfony/class-loader/ClassLoader.php', - 'Symfony\\Component\\ClassLoader\\ClassMapGenerator' => $vendorDir . '/symfony/class-loader/ClassMapGenerator.php', - 'Symfony\\Component\\ClassLoader\\MapClassLoader' => $vendorDir . '/symfony/class-loader/MapClassLoader.php', - 'Symfony\\Component\\ClassLoader\\Psr4ClassLoader' => $vendorDir . '/symfony/class-loader/Psr4ClassLoader.php', - 'Symfony\\Component\\ClassLoader\\WinCacheClassLoader' => $vendorDir . '/symfony/class-loader/WinCacheClassLoader.php', - 'Symfony\\Component\\ClassLoader\\XcacheClassLoader' => $vendorDir . '/symfony/class-loader/XcacheClassLoader.php', + 'Symfony\\Component\\Config\\Builder\\ClassBuilder' => $vendorDir . '/symfony/config/Builder/ClassBuilder.php', + 'Symfony\\Component\\Config\\Builder\\ConfigBuilderGenerator' => $vendorDir . '/symfony/config/Builder/ConfigBuilderGenerator.php', + 'Symfony\\Component\\Config\\Builder\\ConfigBuilderGeneratorInterface' => $vendorDir . '/symfony/config/Builder/ConfigBuilderGeneratorInterface.php', + 'Symfony\\Component\\Config\\Builder\\ConfigBuilderInterface' => $vendorDir . '/symfony/config/Builder/ConfigBuilderInterface.php', + 'Symfony\\Component\\Config\\Builder\\Method' => $vendorDir . '/symfony/config/Builder/Method.php', + 'Symfony\\Component\\Config\\Builder\\Property' => $vendorDir . '/symfony/config/Builder/Property.php', 'Symfony\\Component\\Config\\ConfigCache' => $vendorDir . '/symfony/config/ConfigCache.php', 'Symfony\\Component\\Config\\ConfigCacheFactory' => $vendorDir . '/symfony/config/ConfigCacheFactory.php', 'Symfony\\Component\\Config\\ConfigCacheFactoryInterface' => $vendorDir . '/symfony/config/ConfigCacheFactoryInterface.php', @@ -1194,6 +1735,7 @@ return array( 'Symfony\\Component\\Config\\Definition\\BooleanNode' => $vendorDir . '/symfony/config/Definition/BooleanNode.php', 'Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/ArrayNodeDefinition.php', 'Symfony\\Component\\Config\\Definition\\Builder\\BooleanNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/BooleanNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\BuilderAwareInterface' => $vendorDir . '/symfony/config/Definition/Builder/BuilderAwareInterface.php', 'Symfony\\Component\\Config\\Definition\\Builder\\EnumNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/EnumNodeDefinition.php', 'Symfony\\Component\\Config\\Definition\\Builder\\ExprBuilder' => $vendorDir . '/symfony/config/Definition/Builder/ExprBuilder.php', 'Symfony\\Component\\Config\\Definition\\Builder\\FloatNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/FloatNodeDefinition.php', @@ -1229,10 +1771,9 @@ return array( 'Symfony\\Component\\Config\\Definition\\PrototypedArrayNode' => $vendorDir . '/symfony/config/Definition/PrototypedArrayNode.php', 'Symfony\\Component\\Config\\Definition\\ScalarNode' => $vendorDir . '/symfony/config/Definition/ScalarNode.php', 'Symfony\\Component\\Config\\Definition\\VariableNode' => $vendorDir . '/symfony/config/Definition/VariableNode.php', - 'Symfony\\Component\\Config\\DependencyInjection\\ConfigCachePass' => $vendorDir . '/symfony/config/DependencyInjection/ConfigCachePass.php', 'Symfony\\Component\\Config\\Exception\\FileLoaderImportCircularReferenceException' => $vendorDir . '/symfony/config/Exception/FileLoaderImportCircularReferenceException.php', - 'Symfony\\Component\\Config\\Exception\\FileLoaderLoadException' => $vendorDir . '/symfony/config/Exception/FileLoaderLoadException.php', 'Symfony\\Component\\Config\\Exception\\FileLocatorFileNotFoundException' => $vendorDir . '/symfony/config/Exception/FileLocatorFileNotFoundException.php', + 'Symfony\\Component\\Config\\Exception\\LoaderLoadException' => $vendorDir . '/symfony/config/Exception/LoaderLoadException.php', 'Symfony\\Component\\Config\\FileLocator' => $vendorDir . '/symfony/config/FileLocator.php', 'Symfony\\Component\\Config\\FileLocatorInterface' => $vendorDir . '/symfony/config/FileLocatorInterface.php', 'Symfony\\Component\\Config\\Loader\\DelegatingLoader' => $vendorDir . '/symfony/config/Loader/DelegatingLoader.php', @@ -1242,6 +1783,7 @@ return array( 'Symfony\\Component\\Config\\Loader\\LoaderInterface' => $vendorDir . '/symfony/config/Loader/LoaderInterface.php', 'Symfony\\Component\\Config\\Loader\\LoaderResolver' => $vendorDir . '/symfony/config/Loader/LoaderResolver.php', 'Symfony\\Component\\Config\\Loader\\LoaderResolverInterface' => $vendorDir . '/symfony/config/Loader/LoaderResolverInterface.php', + 'Symfony\\Component\\Config\\Loader\\ParamConfigurator' => $vendorDir . '/symfony/config/Loader/ParamConfigurator.php', 'Symfony\\Component\\Config\\ResourceCheckerConfigCache' => $vendorDir . '/symfony/config/ResourceCheckerConfigCache.php', 'Symfony\\Component\\Config\\ResourceCheckerConfigCacheFactory' => $vendorDir . '/symfony/config/ResourceCheckerConfigCacheFactory.php', 'Symfony\\Component\\Config\\ResourceCheckerInterface' => $vendorDir . '/symfony/config/ResourceCheckerInterface.php', @@ -1259,14 +1801,27 @@ return array( 'Symfony\\Component\\Config\\Util\\Exception\\XmlParsingException' => $vendorDir . '/symfony/config/Util/Exception/XmlParsingException.php', 'Symfony\\Component\\Config\\Util\\XmlUtils' => $vendorDir . '/symfony/config/Util/XmlUtils.php', 'Symfony\\Component\\Console\\Application' => $vendorDir . '/symfony/console/Application.php', + 'Symfony\\Component\\Console\\Attribute\\AsCommand' => $vendorDir . '/symfony/console/Attribute/AsCommand.php', + 'Symfony\\Component\\Console\\CI\\GithubActionReporter' => $vendorDir . '/symfony/console/CI/GithubActionReporter.php', + 'Symfony\\Component\\Console\\Color' => $vendorDir . '/symfony/console/Color.php', 'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => $vendorDir . '/symfony/console/CommandLoader/CommandLoaderInterface.php', 'Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader' => $vendorDir . '/symfony/console/CommandLoader/ContainerCommandLoader.php', 'Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader' => $vendorDir . '/symfony/console/CommandLoader/FactoryCommandLoader.php', 'Symfony\\Component\\Console\\Command\\Command' => $vendorDir . '/symfony/console/Command/Command.php', + 'Symfony\\Component\\Console\\Command\\CompleteCommand' => $vendorDir . '/symfony/console/Command/CompleteCommand.php', + 'Symfony\\Component\\Console\\Command\\DumpCompletionCommand' => $vendorDir . '/symfony/console/Command/DumpCompletionCommand.php', 'Symfony\\Component\\Console\\Command\\HelpCommand' => $vendorDir . '/symfony/console/Command/HelpCommand.php', + 'Symfony\\Component\\Console\\Command\\LazyCommand' => $vendorDir . '/symfony/console/Command/LazyCommand.php', 'Symfony\\Component\\Console\\Command\\ListCommand' => $vendorDir . '/symfony/console/Command/ListCommand.php', 'Symfony\\Component\\Console\\Command\\LockableTrait' => $vendorDir . '/symfony/console/Command/LockableTrait.php', + 'Symfony\\Component\\Console\\Command\\SignalableCommandInterface' => $vendorDir . '/symfony/console/Command/SignalableCommandInterface.php', + 'Symfony\\Component\\Console\\Completion\\CompletionInput' => $vendorDir . '/symfony/console/Completion/CompletionInput.php', + 'Symfony\\Component\\Console\\Completion\\CompletionSuggestions' => $vendorDir . '/symfony/console/Completion/CompletionSuggestions.php', + 'Symfony\\Component\\Console\\Completion\\Output\\BashCompletionOutput' => $vendorDir . '/symfony/console/Completion/Output/BashCompletionOutput.php', + 'Symfony\\Component\\Console\\Completion\\Output\\CompletionOutputInterface' => $vendorDir . '/symfony/console/Completion/Output/CompletionOutputInterface.php', + 'Symfony\\Component\\Console\\Completion\\Suggestion' => $vendorDir . '/symfony/console/Completion/Suggestion.php', 'Symfony\\Component\\Console\\ConsoleEvents' => $vendorDir . '/symfony/console/ConsoleEvents.php', + 'Symfony\\Component\\Console\\Cursor' => $vendorDir . '/symfony/console/Cursor.php', 'Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => $vendorDir . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php', 'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => $vendorDir . '/symfony/console/Descriptor/ApplicationDescription.php', 'Symfony\\Component\\Console\\Descriptor\\Descriptor' => $vendorDir . '/symfony/console/Descriptor/Descriptor.php', @@ -1279,21 +1834,27 @@ return array( 'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => $vendorDir . '/symfony/console/Event/ConsoleCommandEvent.php', 'Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => $vendorDir . '/symfony/console/Event/ConsoleErrorEvent.php', 'Symfony\\Component\\Console\\Event\\ConsoleEvent' => $vendorDir . '/symfony/console/Event/ConsoleEvent.php', - 'Symfony\\Component\\Console\\Event\\ConsoleExceptionEvent' => $vendorDir . '/symfony/console/Event/ConsoleExceptionEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleSignalEvent' => $vendorDir . '/symfony/console/Event/ConsoleSignalEvent.php', 'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => $vendorDir . '/symfony/console/Event/ConsoleTerminateEvent.php', 'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => $vendorDir . '/symfony/console/Exception/CommandNotFoundException.php', 'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/console/Exception/ExceptionInterface.php', 'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/console/Exception/InvalidArgumentException.php', 'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => $vendorDir . '/symfony/console/Exception/InvalidOptionException.php', 'Symfony\\Component\\Console\\Exception\\LogicException' => $vendorDir . '/symfony/console/Exception/LogicException.php', + 'Symfony\\Component\\Console\\Exception\\MissingInputException' => $vendorDir . '/symfony/console/Exception/MissingInputException.php', + 'Symfony\\Component\\Console\\Exception\\NamespaceNotFoundException' => $vendorDir . '/symfony/console/Exception/NamespaceNotFoundException.php', 'Symfony\\Component\\Console\\Exception\\RuntimeException' => $vendorDir . '/symfony/console/Exception/RuntimeException.php', + 'Symfony\\Component\\Console\\Formatter\\NullOutputFormatter' => $vendorDir . '/symfony/console/Formatter/NullOutputFormatter.php', + 'Symfony\\Component\\Console\\Formatter\\NullOutputFormatterStyle' => $vendorDir . '/symfony/console/Formatter/NullOutputFormatterStyle.php', 'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => $vendorDir . '/symfony/console/Formatter/OutputFormatter.php', 'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterInterface.php', 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyle.php', 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleInterface.php', 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleStack.php', + 'Symfony\\Component\\Console\\Formatter\\WrappableOutputFormatterInterface' => $vendorDir . '/symfony/console/Formatter/WrappableOutputFormatterInterface.php', 'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => $vendorDir . '/symfony/console/Helper/DebugFormatterHelper.php', 'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => $vendorDir . '/symfony/console/Helper/DescriptorHelper.php', + 'Symfony\\Component\\Console\\Helper\\Dumper' => $vendorDir . '/symfony/console/Helper/Dumper.php', 'Symfony\\Component\\Console\\Helper\\FormatterHelper' => $vendorDir . '/symfony/console/Helper/FormatterHelper.php', 'Symfony\\Component\\Console\\Helper\\Helper' => $vendorDir . '/symfony/console/Helper/Helper.php', 'Symfony\\Component\\Console\\Helper\\HelperInterface' => $vendorDir . '/symfony/console/Helper/HelperInterface.php', @@ -1306,6 +1867,8 @@ return array( 'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => $vendorDir . '/symfony/console/Helper/SymfonyQuestionHelper.php', 'Symfony\\Component\\Console\\Helper\\Table' => $vendorDir . '/symfony/console/Helper/Table.php', 'Symfony\\Component\\Console\\Helper\\TableCell' => $vendorDir . '/symfony/console/Helper/TableCell.php', + 'Symfony\\Component\\Console\\Helper\\TableCellStyle' => $vendorDir . '/symfony/console/Helper/TableCellStyle.php', + 'Symfony\\Component\\Console\\Helper\\TableRows' => $vendorDir . '/symfony/console/Helper/TableRows.php', 'Symfony\\Component\\Console\\Helper\\TableSeparator' => $vendorDir . '/symfony/console/Helper/TableSeparator.php', 'Symfony\\Component\\Console\\Helper\\TableStyle' => $vendorDir . '/symfony/console/Helper/TableStyle.php', 'Symfony\\Component\\Console\\Input\\ArgvInput' => $vendorDir . '/symfony/console/Input/ArgvInput.php', @@ -1322,19 +1885,26 @@ return array( 'Symfony\\Component\\Console\\Output\\BufferedOutput' => $vendorDir . '/symfony/console/Output/BufferedOutput.php', 'Symfony\\Component\\Console\\Output\\ConsoleOutput' => $vendorDir . '/symfony/console/Output/ConsoleOutput.php', 'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => $vendorDir . '/symfony/console/Output/ConsoleOutputInterface.php', + 'Symfony\\Component\\Console\\Output\\ConsoleSectionOutput' => $vendorDir . '/symfony/console/Output/ConsoleSectionOutput.php', 'Symfony\\Component\\Console\\Output\\NullOutput' => $vendorDir . '/symfony/console/Output/NullOutput.php', 'Symfony\\Component\\Console\\Output\\Output' => $vendorDir . '/symfony/console/Output/Output.php', 'Symfony\\Component\\Console\\Output\\OutputInterface' => $vendorDir . '/symfony/console/Output/OutputInterface.php', 'Symfony\\Component\\Console\\Output\\StreamOutput' => $vendorDir . '/symfony/console/Output/StreamOutput.php', + 'Symfony\\Component\\Console\\Output\\TrimmedBufferOutput' => $vendorDir . '/symfony/console/Output/TrimmedBufferOutput.php', 'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => $vendorDir . '/symfony/console/Question/ChoiceQuestion.php', 'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => $vendorDir . '/symfony/console/Question/ConfirmationQuestion.php', 'Symfony\\Component\\Console\\Question\\Question' => $vendorDir . '/symfony/console/Question/Question.php', + 'Symfony\\Component\\Console\\SignalRegistry\\SignalRegistry' => $vendorDir . '/symfony/console/SignalRegistry/SignalRegistry.php', + 'Symfony\\Component\\Console\\SingleCommandApplication' => $vendorDir . '/symfony/console/SingleCommandApplication.php', 'Symfony\\Component\\Console\\Style\\OutputStyle' => $vendorDir . '/symfony/console/Style/OutputStyle.php', 'Symfony\\Component\\Console\\Style\\StyleInterface' => $vendorDir . '/symfony/console/Style/StyleInterface.php', 'Symfony\\Component\\Console\\Style\\SymfonyStyle' => $vendorDir . '/symfony/console/Style/SymfonyStyle.php', 'Symfony\\Component\\Console\\Terminal' => $vendorDir . '/symfony/console/Terminal.php', 'Symfony\\Component\\Console\\Tester\\ApplicationTester' => $vendorDir . '/symfony/console/Tester/ApplicationTester.php', + 'Symfony\\Component\\Console\\Tester\\CommandCompletionTester' => $vendorDir . '/symfony/console/Tester/CommandCompletionTester.php', 'Symfony\\Component\\Console\\Tester\\CommandTester' => $vendorDir . '/symfony/console/Tester/CommandTester.php', + 'Symfony\\Component\\Console\\Tester\\Constraint\\CommandIsSuccessful' => $vendorDir . '/symfony/console/Tester/Constraint/CommandIsSuccessful.php', + 'Symfony\\Component\\Console\\Tester\\TesterTrait' => $vendorDir . '/symfony/console/Tester/TesterTrait.php', 'Symfony\\Component\\CssSelector\\CssSelectorConverter' => $vendorDir . '/symfony/css-selector/CssSelectorConverter.php', 'Symfony\\Component\\CssSelector\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/css-selector/Exception/ExceptionInterface.php', 'Symfony\\Component\\CssSelector\\Exception\\ExpressionErrorException' => $vendorDir . '/symfony/css-selector/Exception/ExpressionErrorException.php', @@ -1383,72 +1953,67 @@ return array( 'Symfony\\Component\\CssSelector\\XPath\\Translator' => $vendorDir . '/symfony/css-selector/XPath/Translator.php', 'Symfony\\Component\\CssSelector\\XPath\\TranslatorInterface' => $vendorDir . '/symfony/css-selector/XPath/TranslatorInterface.php', 'Symfony\\Component\\CssSelector\\XPath\\XPathExpr' => $vendorDir . '/symfony/css-selector/XPath/XPathExpr.php', - 'Symfony\\Component\\Debug\\BufferingLogger' => $vendorDir . '/symfony/debug/BufferingLogger.php', - 'Symfony\\Component\\Debug\\Debug' => $vendorDir . '/symfony/debug/Debug.php', - 'Symfony\\Component\\Debug\\DebugClassLoader' => $vendorDir . '/symfony/debug/DebugClassLoader.php', - 'Symfony\\Component\\Debug\\ErrorHandler' => $vendorDir . '/symfony/debug/ErrorHandler.php', - 'Symfony\\Component\\Debug\\ExceptionHandler' => $vendorDir . '/symfony/debug/ExceptionHandler.php', - 'Symfony\\Component\\Debug\\Exception\\ClassNotFoundException' => $vendorDir . '/symfony/debug/Exception/ClassNotFoundException.php', - 'Symfony\\Component\\Debug\\Exception\\ContextErrorException' => $vendorDir . '/symfony/debug/Exception/ContextErrorException.php', - 'Symfony\\Component\\Debug\\Exception\\FatalErrorException' => $vendorDir . '/symfony/debug/Exception/FatalErrorException.php', - 'Symfony\\Component\\Debug\\Exception\\FatalThrowableError' => $vendorDir . '/symfony/debug/Exception/FatalThrowableError.php', - 'Symfony\\Component\\Debug\\Exception\\FlattenException' => $vendorDir . '/symfony/debug/Exception/FlattenException.php', - 'Symfony\\Component\\Debug\\Exception\\OutOfMemoryException' => $vendorDir . '/symfony/debug/Exception/OutOfMemoryException.php', - 'Symfony\\Component\\Debug\\Exception\\SilencedErrorContext' => $vendorDir . '/symfony/debug/Exception/SilencedErrorContext.php', - 'Symfony\\Component\\Debug\\Exception\\UndefinedFunctionException' => $vendorDir . '/symfony/debug/Exception/UndefinedFunctionException.php', - 'Symfony\\Component\\Debug\\Exception\\UndefinedMethodException' => $vendorDir . '/symfony/debug/Exception/UndefinedMethodException.php', - 'Symfony\\Component\\Debug\\FatalErrorHandler\\ClassNotFoundFatalErrorHandler' => $vendorDir . '/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php', - 'Symfony\\Component\\Debug\\FatalErrorHandler\\FatalErrorHandlerInterface' => $vendorDir . '/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php', - 'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedFunctionFatalErrorHandler' => $vendorDir . '/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php', - 'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedMethodFatalErrorHandler' => $vendorDir . '/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php', 'Symfony\\Component\\DependencyInjection\\Alias' => $vendorDir . '/symfony/dependency-injection/Alias.php', + 'Symfony\\Component\\DependencyInjection\\Argument\\AbstractArgument' => $vendorDir . '/symfony/dependency-injection/Argument/AbstractArgument.php', 'Symfony\\Component\\DependencyInjection\\Argument\\ArgumentInterface' => $vendorDir . '/symfony/dependency-injection/Argument/ArgumentInterface.php', 'Symfony\\Component\\DependencyInjection\\Argument\\BoundArgument' => $vendorDir . '/symfony/dependency-injection/Argument/BoundArgument.php', 'Symfony\\Component\\DependencyInjection\\Argument\\IteratorArgument' => $vendorDir . '/symfony/dependency-injection/Argument/IteratorArgument.php', + 'Symfony\\Component\\DependencyInjection\\Argument\\ReferenceSetArgumentTrait' => $vendorDir . '/symfony/dependency-injection/Argument/ReferenceSetArgumentTrait.php', 'Symfony\\Component\\DependencyInjection\\Argument\\RewindableGenerator' => $vendorDir . '/symfony/dependency-injection/Argument/RewindableGenerator.php', 'Symfony\\Component\\DependencyInjection\\Argument\\ServiceClosureArgument' => $vendorDir . '/symfony/dependency-injection/Argument/ServiceClosureArgument.php', + 'Symfony\\Component\\DependencyInjection\\Argument\\ServiceLocator' => $vendorDir . '/symfony/dependency-injection/Argument/ServiceLocator.php', + 'Symfony\\Component\\DependencyInjection\\Argument\\ServiceLocatorArgument' => $vendorDir . '/symfony/dependency-injection/Argument/ServiceLocatorArgument.php', 'Symfony\\Component\\DependencyInjection\\Argument\\TaggedIteratorArgument' => $vendorDir . '/symfony/dependency-injection/Argument/TaggedIteratorArgument.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\AsTaggedItem' => $vendorDir . '/symfony/dependency-injection/Attribute/AsTaggedItem.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\Autoconfigure' => $vendorDir . '/symfony/dependency-injection/Attribute/Autoconfigure.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\AutoconfigureTag' => $vendorDir . '/symfony/dependency-injection/Attribute/AutoconfigureTag.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\TaggedIterator' => $vendorDir . '/symfony/dependency-injection/Attribute/TaggedIterator.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\TaggedLocator' => $vendorDir . '/symfony/dependency-injection/Attribute/TaggedLocator.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\Target' => $vendorDir . '/symfony/dependency-injection/Attribute/Target.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\When' => $vendorDir . '/symfony/dependency-injection/Attribute/When.php', 'Symfony\\Component\\DependencyInjection\\ChildDefinition' => $vendorDir . '/symfony/dependency-injection/ChildDefinition.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AbstractRecursivePass' => $vendorDir . '/symfony/dependency-injection/Compiler/AbstractRecursivePass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\AliasDeprecatedPublicServicesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AnalyzeServiceReferencesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\AttributeAutoconfigurationPass' => $vendorDir . '/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AutoAliasServicePass' => $vendorDir . '/symfony/dependency-injection/Compiler/AutoAliasServicePass.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\AutowireExceptionPass' => $vendorDir . '/symfony/dependency-injection/Compiler/AutowireExceptionPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AutowirePass' => $vendorDir . '/symfony/dependency-injection/Compiler/AutowirePass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AutowireRequiredMethodsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/AutowireRequiredMethodsPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\AutowireRequiredPropertiesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/AutowireRequiredPropertiesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckArgumentsValidityPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckCircularReferencesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckCircularReferencesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckDefinitionValidityPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckExceptionOnInvalidReferenceBehaviorPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckReferenceValidityPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckTypeDeclarationsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\Compiler' => $vendorDir . '/symfony/dependency-injection/Compiler/Compiler.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface' => $vendorDir . '/symfony/dependency-injection/Compiler/CompilerPassInterface.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\DecoratorServicePass' => $vendorDir . '/symfony/dependency-injection/Compiler/DecoratorServicePass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\DefinitionErrorExceptionPass' => $vendorDir . '/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ExtensionCompilerPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ExtensionCompilerPass.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\FactoryReturnTypePass' => $vendorDir . '/symfony/dependency-injection/Compiler/FactoryReturnTypePass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\InlineServiceDefinitionsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\LoggingFormatter' => $vendorDir . '/symfony/dependency-injection/Compiler/LoggingFormatter.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\MergeExtensionConfigurationPass' => $vendorDir . '/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\PassConfig' => $vendorDir . '/symfony/dependency-injection/Compiler/PassConfig.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\PriorityTaggedServiceTrait' => $vendorDir . '/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\RegisterAutoconfigureAttributesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RegisterAutoconfigureAttributesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RegisterEnvVarProcessorsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\RegisterReverseContainerPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RegisterServiceSubscribersPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RemoveAbstractDefinitionsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RemovePrivateAliasesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RemoveUnusedDefinitionsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\RepeatablePassInterface' => $vendorDir . '/symfony/dependency-injection/Compiler/RepeatablePassInterface.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\RepeatedPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RepeatedPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ReplaceAliasByActualDefinitionPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveBindingsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveBindingsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveChildDefinitionsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveChildDefinitionsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveClassPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveClassPass.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveDefinitionTemplatesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveDefinitionTemplatesPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveDecoratorStackPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveDecoratorStackPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveEnvPlaceholdersPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveEnvPlaceholdersPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveFactoryClassPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveFactoryClassPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveHotPathPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveHotPathPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveInstanceofConditionalsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveInstanceofConditionalsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveInvalidReferencesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveNamedArgumentsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveNoPreloadPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveNoPreloadPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveParameterPlaceHoldersPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolvePrivatesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolvePrivatesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveReferencesToAliasesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php', @@ -1458,7 +2023,7 @@ return array( 'Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraph' => $vendorDir . '/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraphEdge' => $vendorDir . '/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraphNode' => $vendorDir . '/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php', - 'Symfony\\Component\\DependencyInjection\\Config\\AutowireServiceResource' => $vendorDir . '/symfony/dependency-injection/Config/AutowireServiceResource.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ValidateEnvPlaceholdersPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.php', 'Symfony\\Component\\DependencyInjection\\Config\\ContainerParametersResource' => $vendorDir . '/symfony/dependency-injection/Config/ContainerParametersResource.php', 'Symfony\\Component\\DependencyInjection\\Config\\ContainerParametersResourceChecker' => $vendorDir . '/symfony/dependency-injection/Config/ContainerParametersResourceChecker.php', 'Symfony\\Component\\DependencyInjection\\Container' => $vendorDir . '/symfony/dependency-injection/Container.php', @@ -1467,13 +2032,14 @@ return array( 'Symfony\\Component\\DependencyInjection\\ContainerBuilder' => $vendorDir . '/symfony/dependency-injection/ContainerBuilder.php', 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => $vendorDir . '/symfony/dependency-injection/ContainerInterface.php', 'Symfony\\Component\\DependencyInjection\\Definition' => $vendorDir . '/symfony/dependency-injection/Definition.php', - 'Symfony\\Component\\DependencyInjection\\DefinitionDecorator' => $vendorDir . '/symfony/dependency-injection/DefinitionDecorator.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\Dumper' => $vendorDir . '/symfony/dependency-injection/Dumper/Dumper.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\DumperInterface' => $vendorDir . '/symfony/dependency-injection/Dumper/DumperInterface.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\GraphvizDumper' => $vendorDir . '/symfony/dependency-injection/Dumper/GraphvizDumper.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\PhpDumper' => $vendorDir . '/symfony/dependency-injection/Dumper/PhpDumper.php', + 'Symfony\\Component\\DependencyInjection\\Dumper\\Preloader' => $vendorDir . '/symfony/dependency-injection/Dumper/Preloader.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\XmlDumper' => $vendorDir . '/symfony/dependency-injection/Dumper/XmlDumper.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\YamlDumper' => $vendorDir . '/symfony/dependency-injection/Dumper/YamlDumper.php', + 'Symfony\\Component\\DependencyInjection\\EnvVarLoaderInterface' => $vendorDir . '/symfony/dependency-injection/EnvVarLoaderInterface.php', 'Symfony\\Component\\DependencyInjection\\EnvVarProcessor' => $vendorDir . '/symfony/dependency-injection/EnvVarProcessor.php', 'Symfony\\Component\\DependencyInjection\\EnvVarProcessorInterface' => $vendorDir . '/symfony/dependency-injection/EnvVarProcessorInterface.php', 'Symfony\\Component\\DependencyInjection\\Exception\\AutowiringFailedException' => $vendorDir . '/symfony/dependency-injection/Exception/AutowiringFailedException.php', @@ -1482,6 +2048,7 @@ return array( 'Symfony\\Component\\DependencyInjection\\Exception\\EnvParameterException' => $vendorDir . '/symfony/dependency-injection/Exception/EnvParameterException.php', 'Symfony\\Component\\DependencyInjection\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/dependency-injection/Exception/ExceptionInterface.php', 'Symfony\\Component\\DependencyInjection\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/dependency-injection/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\InvalidParameterTypeException' => $vendorDir . '/symfony/dependency-injection/Exception/InvalidParameterTypeException.php', 'Symfony\\Component\\DependencyInjection\\Exception\\LogicException' => $vendorDir . '/symfony/dependency-injection/Exception/LogicException.php', 'Symfony\\Component\\DependencyInjection\\Exception\\OutOfBoundsException' => $vendorDir . '/symfony/dependency-injection/Exception/OutOfBoundsException.php', 'Symfony\\Component\\DependencyInjection\\Exception\\ParameterCircularReferenceException' => $vendorDir . '/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php', @@ -1504,8 +2071,10 @@ return array( 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\AbstractConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\AbstractServiceConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/AbstractServiceConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\AliasConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/AliasConfigurator.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ClosureReferenceConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/ClosureReferenceConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ContainerConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\DefaultsConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\EnvConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/EnvConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\InlineServiceConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\InstanceofConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ParametersConfigurator' => $vendorDir . '/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.php', @@ -1540,45 +2109,72 @@ return array( 'Symfony\\Component\\DependencyInjection\\Loader\\XmlFileLoader' => $vendorDir . '/symfony/dependency-injection/Loader/XmlFileLoader.php', 'Symfony\\Component\\DependencyInjection\\Loader\\YamlFileLoader' => $vendorDir . '/symfony/dependency-injection/Loader/YamlFileLoader.php', 'Symfony\\Component\\DependencyInjection\\Parameter' => $vendorDir . '/symfony/dependency-injection/Parameter.php', + 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBag' => $vendorDir . '/symfony/dependency-injection/ParameterBag/ContainerBag.php', + 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBagInterface' => $vendorDir . '/symfony/dependency-injection/ParameterBag/ContainerBagInterface.php', 'Symfony\\Component\\DependencyInjection\\ParameterBag\\EnvPlaceholderParameterBag' => $vendorDir . '/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php', 'Symfony\\Component\\DependencyInjection\\ParameterBag\\FrozenParameterBag' => $vendorDir . '/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php', 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBag' => $vendorDir . '/symfony/dependency-injection/ParameterBag/ParameterBag.php', 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBagInterface' => $vendorDir . '/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php', 'Symfony\\Component\\DependencyInjection\\Reference' => $vendorDir . '/symfony/dependency-injection/Reference.php', - 'Symfony\\Component\\DependencyInjection\\ResettableContainerInterface' => $vendorDir . '/symfony/dependency-injection/ResettableContainerInterface.php', + 'Symfony\\Component\\DependencyInjection\\ReverseContainer' => $vendorDir . '/symfony/dependency-injection/ReverseContainer.php', 'Symfony\\Component\\DependencyInjection\\ServiceLocator' => $vendorDir . '/symfony/dependency-injection/ServiceLocator.php', - 'Symfony\\Component\\DependencyInjection\\ServiceSubscriberInterface' => $vendorDir . '/symfony/dependency-injection/ServiceSubscriberInterface.php', 'Symfony\\Component\\DependencyInjection\\TaggedContainerInterface' => $vendorDir . '/symfony/dependency-injection/TaggedContainerInterface.php', 'Symfony\\Component\\DependencyInjection\\TypedReference' => $vendorDir . '/symfony/dependency-injection/TypedReference.php', 'Symfony\\Component\\DependencyInjection\\Variable' => $vendorDir . '/symfony/dependency-injection/Variable.php', + 'Symfony\\Component\\Dotenv\\Command\\DebugCommand' => $vendorDir . '/symfony/dotenv/Command/DebugCommand.php', + 'Symfony\\Component\\Dotenv\\Command\\DotenvDumpCommand' => $vendorDir . '/symfony/dotenv/Command/DotenvDumpCommand.php', 'Symfony\\Component\\Dotenv\\Dotenv' => $vendorDir . '/symfony/dotenv/Dotenv.php', 'Symfony\\Component\\Dotenv\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/dotenv/Exception/ExceptionInterface.php', 'Symfony\\Component\\Dotenv\\Exception\\FormatException' => $vendorDir . '/symfony/dotenv/Exception/FormatException.php', 'Symfony\\Component\\Dotenv\\Exception\\FormatExceptionContext' => $vendorDir . '/symfony/dotenv/Exception/FormatExceptionContext.php', 'Symfony\\Component\\Dotenv\\Exception\\PathException' => $vendorDir . '/symfony/dotenv/Exception/PathException.php', - 'Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/ContainerAwareEventDispatcher.php', + 'Symfony\\Component\\ErrorHandler\\BufferingLogger' => $vendorDir . '/symfony/error-handler/BufferingLogger.php', + 'Symfony\\Component\\ErrorHandler\\Debug' => $vendorDir . '/symfony/error-handler/Debug.php', + 'Symfony\\Component\\ErrorHandler\\DebugClassLoader' => $vendorDir . '/symfony/error-handler/DebugClassLoader.php', + 'Symfony\\Component\\ErrorHandler\\ErrorEnhancer\\ClassNotFoundErrorEnhancer' => $vendorDir . '/symfony/error-handler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php', + 'Symfony\\Component\\ErrorHandler\\ErrorEnhancer\\ErrorEnhancerInterface' => $vendorDir . '/symfony/error-handler/ErrorEnhancer/ErrorEnhancerInterface.php', + 'Symfony\\Component\\ErrorHandler\\ErrorEnhancer\\UndefinedFunctionErrorEnhancer' => $vendorDir . '/symfony/error-handler/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php', + 'Symfony\\Component\\ErrorHandler\\ErrorEnhancer\\UndefinedMethodErrorEnhancer' => $vendorDir . '/symfony/error-handler/ErrorEnhancer/UndefinedMethodErrorEnhancer.php', + 'Symfony\\Component\\ErrorHandler\\ErrorHandler' => $vendorDir . '/symfony/error-handler/ErrorHandler.php', + 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\CliErrorRenderer' => $vendorDir . '/symfony/error-handler/ErrorRenderer/CliErrorRenderer.php', + 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\ErrorRendererInterface' => $vendorDir . '/symfony/error-handler/ErrorRenderer/ErrorRendererInterface.php', + 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\HtmlErrorRenderer' => $vendorDir . '/symfony/error-handler/ErrorRenderer/HtmlErrorRenderer.php', + 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\SerializerErrorRenderer' => $vendorDir . '/symfony/error-handler/ErrorRenderer/SerializerErrorRenderer.php', + 'Symfony\\Component\\ErrorHandler\\Error\\ClassNotFoundError' => $vendorDir . '/symfony/error-handler/Error/ClassNotFoundError.php', + 'Symfony\\Component\\ErrorHandler\\Error\\FatalError' => $vendorDir . '/symfony/error-handler/Error/FatalError.php', + 'Symfony\\Component\\ErrorHandler\\Error\\OutOfMemoryError' => $vendorDir . '/symfony/error-handler/Error/OutOfMemoryError.php', + 'Symfony\\Component\\ErrorHandler\\Error\\UndefinedFunctionError' => $vendorDir . '/symfony/error-handler/Error/UndefinedFunctionError.php', + 'Symfony\\Component\\ErrorHandler\\Error\\UndefinedMethodError' => $vendorDir . '/symfony/error-handler/Error/UndefinedMethodError.php', + 'Symfony\\Component\\ErrorHandler\\Exception\\FlattenException' => $vendorDir . '/symfony/error-handler/Exception/FlattenException.php', + 'Symfony\\Component\\ErrorHandler\\Exception\\SilencedErrorContext' => $vendorDir . '/symfony/error-handler/Exception/SilencedErrorContext.php', + 'Symfony\\Component\\ErrorHandler\\Internal\\TentativeTypes' => $vendorDir . '/symfony/error-handler/Internal/TentativeTypes.php', + 'Symfony\\Component\\ErrorHandler\\ThrowableUtils' => $vendorDir . '/symfony/error-handler/ThrowableUtils.php', + 'Symfony\\Component\\EventDispatcher\\Attribute\\AsEventListener' => $vendorDir . '/symfony/event-dispatcher/Attribute/AsEventListener.php', 'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php', - 'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcherInterface' => $vendorDir . '/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php', 'Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener' => $vendorDir . '/symfony/event-dispatcher/Debug/WrappedListener.php', + 'Symfony\\Component\\EventDispatcher\\DependencyInjection\\AddEventAliasesPass' => $vendorDir . '/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php', 'Symfony\\Component\\EventDispatcher\\DependencyInjection\\RegisterListenersPass' => $vendorDir . '/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php', - 'Symfony\\Component\\EventDispatcher\\Event' => $vendorDir . '/symfony/event-dispatcher/Event.php', 'Symfony\\Component\\EventDispatcher\\EventDispatcher' => $vendorDir . '/symfony/event-dispatcher/EventDispatcher.php', 'Symfony\\Component\\EventDispatcher\\EventDispatcherInterface' => $vendorDir . '/symfony/event-dispatcher/EventDispatcherInterface.php', 'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface' => $vendorDir . '/symfony/event-dispatcher/EventSubscriberInterface.php', 'Symfony\\Component\\EventDispatcher\\GenericEvent' => $vendorDir . '/symfony/event-dispatcher/GenericEvent.php', 'Symfony\\Component\\EventDispatcher\\ImmutableEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/ImmutableEventDispatcher.php', + 'Symfony\\Component\\EventDispatcher\\LegacyEventDispatcherProxy' => $vendorDir . '/symfony/event-dispatcher/LegacyEventDispatcherProxy.php', 'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/ExceptionInterface.php', 'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => $vendorDir . '/symfony/filesystem/Exception/FileNotFoundException.php', 'Symfony\\Component\\Filesystem\\Exception\\IOException' => $vendorDir . '/symfony/filesystem/Exception/IOException.php', 'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/IOExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/filesystem/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Filesystem\\Exception\\RuntimeException' => $vendorDir . '/symfony/filesystem/Exception/RuntimeException.php', 'Symfony\\Component\\Filesystem\\Filesystem' => $vendorDir . '/symfony/filesystem/Filesystem.php', - 'Symfony\\Component\\Filesystem\\LockHandler' => $vendorDir . '/symfony/filesystem/LockHandler.php', + 'Symfony\\Component\\Filesystem\\Path' => $vendorDir . '/symfony/filesystem/Path.php', 'Symfony\\Component\\Finder\\Comparator\\Comparator' => $vendorDir . '/symfony/finder/Comparator/Comparator.php', 'Symfony\\Component\\Finder\\Comparator\\DateComparator' => $vendorDir . '/symfony/finder/Comparator/DateComparator.php', 'Symfony\\Component\\Finder\\Comparator\\NumberComparator' => $vendorDir . '/symfony/finder/Comparator/NumberComparator.php', 'Symfony\\Component\\Finder\\Exception\\AccessDeniedException' => $vendorDir . '/symfony/finder/Exception/AccessDeniedException.php', - 'Symfony\\Component\\Finder\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/finder/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Finder\\Exception\\DirectoryNotFoundException' => $vendorDir . '/symfony/finder/Exception/DirectoryNotFoundException.php', 'Symfony\\Component\\Finder\\Finder' => $vendorDir . '/symfony/finder/Finder.php', + 'Symfony\\Component\\Finder\\Gitignore' => $vendorDir . '/symfony/finder/Gitignore.php', 'Symfony\\Component\\Finder\\Glob' => $vendorDir . '/symfony/finder/Glob.php', 'Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator' => $vendorDir . '/symfony/finder/Iterator/CustomFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\DateRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/DateRangeFilterIterator.php', @@ -1587,42 +2183,49 @@ return array( 'Symfony\\Component\\Finder\\Iterator\\FileTypeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FileTypeFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\FilecontentFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilecontentFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\FilenameFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilenameFilterIterator.php', - 'Symfony\\Component\\Finder\\Iterator\\FilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\LazyIterator' => $vendorDir . '/symfony/finder/Iterator/LazyIterator.php', 'Symfony\\Component\\Finder\\Iterator\\MultiplePcreFilterIterator' => $vendorDir . '/symfony/finder/Iterator/MultiplePcreFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\PathFilterIterator' => $vendorDir . '/symfony/finder/Iterator/PathFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\RecursiveDirectoryIterator' => $vendorDir . '/symfony/finder/Iterator/RecursiveDirectoryIterator.php', 'Symfony\\Component\\Finder\\Iterator\\SizeRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/SizeRangeFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\SortableIterator' => $vendorDir . '/symfony/finder/Iterator/SortableIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\VcsIgnoredFilterIterator' => $vendorDir . '/symfony/finder/Iterator/VcsIgnoredFilterIterator.php', 'Symfony\\Component\\Finder\\SplFileInfo' => $vendorDir . '/symfony/finder/SplFileInfo.php', 'Symfony\\Component\\HttpFoundation\\AcceptHeader' => $vendorDir . '/symfony/http-foundation/AcceptHeader.php', 'Symfony\\Component\\HttpFoundation\\AcceptHeaderItem' => $vendorDir . '/symfony/http-foundation/AcceptHeaderItem.php', - 'Symfony\\Component\\HttpFoundation\\ApacheRequest' => $vendorDir . '/symfony/http-foundation/ApacheRequest.php', 'Symfony\\Component\\HttpFoundation\\BinaryFileResponse' => $vendorDir . '/symfony/http-foundation/BinaryFileResponse.php', 'Symfony\\Component\\HttpFoundation\\Cookie' => $vendorDir . '/symfony/http-foundation/Cookie.php', + 'Symfony\\Component\\HttpFoundation\\Exception\\BadRequestException' => $vendorDir . '/symfony/http-foundation/Exception/BadRequestException.php', 'Symfony\\Component\\HttpFoundation\\Exception\\ConflictingHeadersException' => $vendorDir . '/symfony/http-foundation/Exception/ConflictingHeadersException.php', + 'Symfony\\Component\\HttpFoundation\\Exception\\JsonException' => $vendorDir . '/symfony/http-foundation/Exception/JsonException.php', 'Symfony\\Component\\HttpFoundation\\Exception\\RequestExceptionInterface' => $vendorDir . '/symfony/http-foundation/Exception/RequestExceptionInterface.php', + 'Symfony\\Component\\HttpFoundation\\Exception\\SessionNotFoundException' => $vendorDir . '/symfony/http-foundation/Exception/SessionNotFoundException.php', 'Symfony\\Component\\HttpFoundation\\Exception\\SuspiciousOperationException' => $vendorDir . '/symfony/http-foundation/Exception/SuspiciousOperationException.php', 'Symfony\\Component\\HttpFoundation\\ExpressionRequestMatcher' => $vendorDir . '/symfony/http-foundation/ExpressionRequestMatcher.php', 'Symfony\\Component\\HttpFoundation\\FileBag' => $vendorDir . '/symfony/http-foundation/FileBag.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\AccessDeniedException' => $vendorDir . '/symfony/http-foundation/File/Exception/AccessDeniedException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\CannotWriteFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/CannotWriteFileException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\ExtensionFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/ExtensionFileException.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\FileException' => $vendorDir . '/symfony/http-foundation/File/Exception/FileException.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\FileNotFoundException' => $vendorDir . '/symfony/http-foundation/File/Exception/FileNotFoundException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\FormSizeFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/FormSizeFileException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\IniSizeFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/IniSizeFileException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\NoFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/NoFileException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\NoTmpDirFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/NoTmpDirFileException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\PartialFileException' => $vendorDir . '/symfony/http-foundation/File/Exception/PartialFileException.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\UnexpectedTypeException' => $vendorDir . '/symfony/http-foundation/File/Exception/UnexpectedTypeException.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\UploadException' => $vendorDir . '/symfony/http-foundation/File/Exception/UploadException.php', 'Symfony\\Component\\HttpFoundation\\File\\File' => $vendorDir . '/symfony/http-foundation/File/File.php', - 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\ExtensionGuesser' => $vendorDir . '/symfony/http-foundation/File/MimeType/ExtensionGuesser.php', - 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\ExtensionGuesserInterface' => $vendorDir . '/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php', - 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\FileBinaryMimeTypeGuesser' => $vendorDir . '/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php', - 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\FileinfoMimeTypeGuesser' => $vendorDir . '/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php', - 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\MimeTypeExtensionGuesser' => $vendorDir . '/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php', - 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\MimeTypeGuesser' => $vendorDir . '/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php', - 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\MimeTypeGuesserInterface' => $vendorDir . '/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php', 'Symfony\\Component\\HttpFoundation\\File\\Stream' => $vendorDir . '/symfony/http-foundation/File/Stream.php', 'Symfony\\Component\\HttpFoundation\\File\\UploadedFile' => $vendorDir . '/symfony/http-foundation/File/UploadedFile.php', 'Symfony\\Component\\HttpFoundation\\HeaderBag' => $vendorDir . '/symfony/http-foundation/HeaderBag.php', + 'Symfony\\Component\\HttpFoundation\\HeaderUtils' => $vendorDir . '/symfony/http-foundation/HeaderUtils.php', + 'Symfony\\Component\\HttpFoundation\\InputBag' => $vendorDir . '/symfony/http-foundation/InputBag.php', 'Symfony\\Component\\HttpFoundation\\IpUtils' => $vendorDir . '/symfony/http-foundation/IpUtils.php', 'Symfony\\Component\\HttpFoundation\\JsonResponse' => $vendorDir . '/symfony/http-foundation/JsonResponse.php', 'Symfony\\Component\\HttpFoundation\\ParameterBag' => $vendorDir . '/symfony/http-foundation/ParameterBag.php', + 'Symfony\\Component\\HttpFoundation\\RateLimiter\\AbstractRequestRateLimiter' => $vendorDir . '/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php', + 'Symfony\\Component\\HttpFoundation\\RateLimiter\\RequestRateLimiterInterface' => $vendorDir . '/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php', 'Symfony\\Component\\HttpFoundation\\RedirectResponse' => $vendorDir . '/symfony/http-foundation/RedirectResponse.php', 'Symfony\\Component\\HttpFoundation\\Request' => $vendorDir . '/symfony/http-foundation/Request.php', 'Symfony\\Component\\HttpFoundation\\RequestMatcher' => $vendorDir . '/symfony/http-foundation/RequestMatcher.php', @@ -1640,28 +2243,39 @@ return array( 'Symfony\\Component\\HttpFoundation\\Session\\Session' => $vendorDir . '/symfony/http-foundation/Session/Session.php', 'Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface' => $vendorDir . '/symfony/http-foundation/Session/SessionBagInterface.php', 'Symfony\\Component\\HttpFoundation\\Session\\SessionBagProxy' => $vendorDir . '/symfony/http-foundation/Session/SessionBagProxy.php', + 'Symfony\\Component\\HttpFoundation\\Session\\SessionFactory' => $vendorDir . '/symfony/http-foundation/Session/SessionFactory.php', + 'Symfony\\Component\\HttpFoundation\\Session\\SessionFactoryInterface' => $vendorDir . '/symfony/http-foundation/Session/SessionFactoryInterface.php', 'Symfony\\Component\\HttpFoundation\\Session\\SessionInterface' => $vendorDir . '/symfony/http-foundation/Session/SessionInterface.php', 'Symfony\\Component\\HttpFoundation\\Session\\SessionUtils' => $vendorDir . '/symfony/http-foundation/Session/SessionUtils.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\AbstractSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcacheSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\IdentityMarshaller' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MarshallingSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcachedSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MigratingSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MongoDbSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeFileSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NullSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\PdoSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\RedisSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\SessionHandlerFactory' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\StrictSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\WriteCheckSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MetadataBag' => $vendorDir . '/symfony/http-foundation/Session/Storage/MetadataBag.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockArraySessionStorage' => $vendorDir . '/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockFileSessionStorage' => $vendorDir . '/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockFileSessionStorageFactory' => $vendorDir . '/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage' => $vendorDir . '/symfony/http-foundation/Session/Storage/NativeSessionStorage.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorageFactory' => $vendorDir . '/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\PhpBridgeSessionStorage' => $vendorDir . '/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\PhpBridgeSessionStorageFactory' => $vendorDir . '/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\AbstractProxy' => $vendorDir . '/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\NativeProxy' => $vendorDir . '/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\SessionHandlerProxy' => $vendorDir . '/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\ServiceSessionFactory' => $vendorDir . '/symfony/http-foundation/Session/Storage/ServiceSessionFactory.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageFactoryInterface' => $vendorDir . '/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface' => $vendorDir . '/symfony/http-foundation/Session/Storage/SessionStorageInterface.php', 'Symfony\\Component\\HttpFoundation\\StreamedResponse' => $vendorDir . '/symfony/http-foundation/StreamedResponse.php', + 'Symfony\\Component\\HttpFoundation\\UrlHelper' => $vendorDir . '/symfony/http-foundation/UrlHelper.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\ArgumentInterface' => $vendorDir . '/symfony/http-kernel/Attribute/ArgumentInterface.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\AsController' => $vendorDir . '/symfony/http-kernel/Attribute/AsController.php', 'Symfony\\Component\\HttpKernel\\Bundle\\Bundle' => $vendorDir . '/symfony/http-kernel/Bundle/Bundle.php', 'Symfony\\Component\\HttpKernel\\Bundle\\BundleInterface' => $vendorDir . '/symfony/http-kernel/Bundle/BundleInterface.php', 'Symfony\\Component\\HttpKernel\\CacheClearer\\CacheClearerInterface' => $vendorDir . '/symfony/http-kernel/CacheClearer/CacheClearerInterface.php', @@ -1671,8 +2285,6 @@ return array( 'Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerAggregate' => $vendorDir . '/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php', 'Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerInterface' => $vendorDir . '/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php', 'Symfony\\Component\\HttpKernel\\CacheWarmer\\WarmableInterface' => $vendorDir . '/symfony/http-kernel/CacheWarmer/WarmableInterface.php', - 'Symfony\\Component\\HttpKernel\\Client' => $vendorDir . '/symfony/http-kernel/Client.php', - 'Symfony\\Component\\HttpKernel\\Config\\EnvParametersResource' => $vendorDir . '/symfony/http-kernel/Config/EnvParametersResource.php', 'Symfony\\Component\\HttpKernel\\Config\\FileLocator' => $vendorDir . '/symfony/http-kernel/Config/FileLocator.php', 'Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadata' => $vendorDir . '/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php', 'Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadataFactory' => $vendorDir . '/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php', @@ -1680,16 +2292,19 @@ return array( 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolverInterface.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\DefaultValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\NotTaggedControllerValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestAttributeValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\ServiceValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\SessionValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\TraceableValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\VariadicValueResolver' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolverInterface' => $vendorDir . '/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php', 'Symfony\\Component\\HttpKernel\\Controller\\ContainerControllerResolver' => $vendorDir . '/symfony/http-kernel/Controller/ContainerControllerResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ControllerReference' => $vendorDir . '/symfony/http-kernel/Controller/ControllerReference.php', 'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver' => $vendorDir . '/symfony/http-kernel/Controller/ControllerResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface' => $vendorDir . '/symfony/http-kernel/Controller/ControllerResolverInterface.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ErrorController' => $vendorDir . '/symfony/http-kernel/Controller/ErrorController.php', 'Symfony\\Component\\HttpKernel\\Controller\\TraceableArgumentResolver' => $vendorDir . '/symfony/http-kernel/Controller/TraceableArgumentResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\TraceableControllerResolver' => $vendorDir . '/symfony/http-kernel/Controller/TraceableControllerResolver.php', 'Symfony\\Component\\HttpKernel\\DataCollector\\AjaxDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/AjaxDataCollector.php', @@ -1705,11 +2320,9 @@ return array( 'Symfony\\Component\\HttpKernel\\DataCollector\\RequestDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/RequestDataCollector.php', 'Symfony\\Component\\HttpKernel\\DataCollector\\RouterDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/RouterDataCollector.php', 'Symfony\\Component\\HttpKernel\\DataCollector\\TimeDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/TimeDataCollector.php', - 'Symfony\\Component\\HttpKernel\\DataCollector\\Util\\ValueExporter' => $vendorDir . '/symfony/http-kernel/DataCollector/Util/ValueExporter.php', 'Symfony\\Component\\HttpKernel\\Debug\\FileLinkFormatter' => $vendorDir . '/symfony/http-kernel/Debug/FileLinkFormatter.php', 'Symfony\\Component\\HttpKernel\\Debug\\TraceableEventDispatcher' => $vendorDir . '/symfony/http-kernel/Debug/TraceableEventDispatcher.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\AddAnnotatedClassesToCachePass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php', - 'Symfony\\Component\\HttpKernel\\DependencyInjection\\AddClassesToCachePass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ConfigurableExtension' => $vendorDir . '/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ControllerArgumentValueResolverPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\Extension' => $vendorDir . '/symfony/http-kernel/DependencyInjection/Extension.php', @@ -1718,6 +2331,7 @@ return array( 'Symfony\\Component\\HttpKernel\\DependencyInjection\\LoggerPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/LoggerPass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\MergeExtensionConfigurationPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\RegisterControllerArgumentLocatorsPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\RegisterLocaleAwareServicesPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/RegisterLocaleAwareServicesPass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\RemoveEmptyControllerArgumentLocatorsPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ResettableServicePass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/ResettableServicePass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ServicesResetter' => $vendorDir . '/symfony/http-kernel/DependencyInjection/ServicesResetter.php', @@ -1725,35 +2339,37 @@ return array( 'Symfony\\Component\\HttpKernel\\EventListener\\AbstractTestSessionListener' => $vendorDir . '/symfony/http-kernel/EventListener/AbstractTestSessionListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\AddRequestFormatsListener' => $vendorDir . '/symfony/http-kernel/EventListener/AddRequestFormatsListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\DebugHandlersListener' => $vendorDir . '/symfony/http-kernel/EventListener/DebugHandlersListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\DisallowRobotsIndexingListener' => $vendorDir . '/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\DumpListener' => $vendorDir . '/symfony/http-kernel/EventListener/DumpListener.php', - 'Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener' => $vendorDir . '/symfony/http-kernel/EventListener/ExceptionListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\ErrorListener' => $vendorDir . '/symfony/http-kernel/EventListener/ErrorListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\FragmentListener' => $vendorDir . '/symfony/http-kernel/EventListener/FragmentListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\LocaleAwareListener' => $vendorDir . '/symfony/http-kernel/EventListener/LocaleAwareListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\LocaleListener' => $vendorDir . '/symfony/http-kernel/EventListener/LocaleListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\ProfilerListener' => $vendorDir . '/symfony/http-kernel/EventListener/ProfilerListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener' => $vendorDir . '/symfony/http-kernel/EventListener/ResponseListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\RouterListener' => $vendorDir . '/symfony/http-kernel/EventListener/RouterListener.php', - 'Symfony\\Component\\HttpKernel\\EventListener\\SaveSessionListener' => $vendorDir . '/symfony/http-kernel/EventListener/SaveSessionListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\SessionListener' => $vendorDir . '/symfony/http-kernel/EventListener/SessionListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\StreamedResponseListener' => $vendorDir . '/symfony/http-kernel/EventListener/StreamedResponseListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\SurrogateListener' => $vendorDir . '/symfony/http-kernel/EventListener/SurrogateListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\TestSessionListener' => $vendorDir . '/symfony/http-kernel/EventListener/TestSessionListener.php', - 'Symfony\\Component\\HttpKernel\\EventListener\\TranslatorListener' => $vendorDir . '/symfony/http-kernel/EventListener/TranslatorListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\ValidateRequestListener' => $vendorDir . '/symfony/http-kernel/EventListener/ValidateRequestListener.php', - 'Symfony\\Component\\HttpKernel\\Event\\FilterControllerArgumentsEvent' => $vendorDir . '/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent' => $vendorDir . '/symfony/http-kernel/Event/FilterControllerEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent' => $vendorDir . '/symfony/http-kernel/Event/FilterResponseEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\ControllerArgumentsEvent' => $vendorDir . '/symfony/http-kernel/Event/ControllerArgumentsEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\ControllerEvent' => $vendorDir . '/symfony/http-kernel/Event/ControllerEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent' => $vendorDir . '/symfony/http-kernel/Event/ExceptionEvent.php', 'Symfony\\Component\\HttpKernel\\Event\\FinishRequestEvent' => $vendorDir . '/symfony/http-kernel/Event/FinishRequestEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent' => $vendorDir . '/symfony/http-kernel/Event/GetResponseEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent' => $vendorDir . '/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent' => $vendorDir . '/symfony/http-kernel/Event/GetResponseForExceptionEvent.php', 'Symfony\\Component\\HttpKernel\\Event\\KernelEvent' => $vendorDir . '/symfony/http-kernel/Event/KernelEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\PostResponseEvent' => $vendorDir . '/symfony/http-kernel/Event/PostResponseEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\RequestEvent' => $vendorDir . '/symfony/http-kernel/Event/RequestEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\ResponseEvent' => $vendorDir . '/symfony/http-kernel/Event/ResponseEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\TerminateEvent' => $vendorDir . '/symfony/http-kernel/Event/TerminateEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\ViewEvent' => $vendorDir . '/symfony/http-kernel/Event/ViewEvent.php', 'Symfony\\Component\\HttpKernel\\Exception\\AccessDeniedHttpException' => $vendorDir . '/symfony/http-kernel/Exception/AccessDeniedHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\BadRequestHttpException' => $vendorDir . '/symfony/http-kernel/Exception/BadRequestHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\ConflictHttpException' => $vendorDir . '/symfony/http-kernel/Exception/ConflictHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\ControllerDoesNotReturnResponseException' => $vendorDir . '/symfony/http-kernel/Exception/ControllerDoesNotReturnResponseException.php', 'Symfony\\Component\\HttpKernel\\Exception\\GoneHttpException' => $vendorDir . '/symfony/http-kernel/Exception/GoneHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\HttpException' => $vendorDir . '/symfony/http-kernel/Exception/HttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\HttpExceptionInterface' => $vendorDir . '/symfony/http-kernel/Exception/HttpExceptionInterface.php', + 'Symfony\\Component\\HttpKernel\\Exception\\InvalidMetadataException' => $vendorDir . '/symfony/http-kernel/Exception/InvalidMetadataException.php', 'Symfony\\Component\\HttpKernel\\Exception\\LengthRequiredHttpException' => $vendorDir . '/symfony/http-kernel/Exception/LengthRequiredHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\MethodNotAllowedHttpException' => $vendorDir . '/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\NotAcceptableHttpException' => $vendorDir . '/symfony/http-kernel/Exception/NotAcceptableHttpException.php', @@ -1763,12 +2379,15 @@ return array( 'Symfony\\Component\\HttpKernel\\Exception\\ServiceUnavailableHttpException' => $vendorDir . '/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\TooManyRequestsHttpException' => $vendorDir . '/symfony/http-kernel/Exception/TooManyRequestsHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\UnauthorizedHttpException' => $vendorDir . '/symfony/http-kernel/Exception/UnauthorizedHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\UnexpectedSessionUsageException' => $vendorDir . '/symfony/http-kernel/Exception/UnexpectedSessionUsageException.php', 'Symfony\\Component\\HttpKernel\\Exception\\UnprocessableEntityHttpException' => $vendorDir . '/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\UnsupportedMediaTypeHttpException' => $vendorDir . '/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php', 'Symfony\\Component\\HttpKernel\\Fragment\\AbstractSurrogateFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php', 'Symfony\\Component\\HttpKernel\\Fragment\\EsiFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Fragment/EsiFragmentRenderer.php', 'Symfony\\Component\\HttpKernel\\Fragment\\FragmentHandler' => $vendorDir . '/symfony/http-kernel/Fragment/FragmentHandler.php', 'Symfony\\Component\\HttpKernel\\Fragment\\FragmentRendererInterface' => $vendorDir . '/symfony/http-kernel/Fragment/FragmentRendererInterface.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\FragmentUriGenerator' => $vendorDir . '/symfony/http-kernel/Fragment/FragmentUriGenerator.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\FragmentUriGeneratorInterface' => $vendorDir . '/symfony/http-kernel/Fragment/FragmentUriGeneratorInterface.php', 'Symfony\\Component\\HttpKernel\\Fragment\\HIncludeFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php', 'Symfony\\Component\\HttpKernel\\Fragment\\InlineFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Fragment/InlineFragmentRenderer.php', 'Symfony\\Component\\HttpKernel\\Fragment\\RoutableFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php', @@ -1783,7 +2402,9 @@ return array( 'Symfony\\Component\\HttpKernel\\HttpCache\\StoreInterface' => $vendorDir . '/symfony/http-kernel/HttpCache/StoreInterface.php', 'Symfony\\Component\\HttpKernel\\HttpCache\\SubRequestHandler' => $vendorDir . '/symfony/http-kernel/HttpCache/SubRequestHandler.php', 'Symfony\\Component\\HttpKernel\\HttpCache\\SurrogateInterface' => $vendorDir . '/symfony/http-kernel/HttpCache/SurrogateInterface.php', + 'Symfony\\Component\\HttpKernel\\HttpClientKernel' => $vendorDir . '/symfony/http-kernel/HttpClientKernel.php', 'Symfony\\Component\\HttpKernel\\HttpKernel' => $vendorDir . '/symfony/http-kernel/HttpKernel.php', + 'Symfony\\Component\\HttpKernel\\HttpKernelBrowser' => $vendorDir . '/symfony/http-kernel/HttpKernelBrowser.php', 'Symfony\\Component\\HttpKernel\\HttpKernelInterface' => $vendorDir . '/symfony/http-kernel/HttpKernelInterface.php', 'Symfony\\Component\\HttpKernel\\Kernel' => $vendorDir . '/symfony/http-kernel/Kernel.php', 'Symfony\\Component\\HttpKernel\\KernelEvents' => $vendorDir . '/symfony/http-kernel/KernelEvents.php', @@ -1797,45 +2418,55 @@ return array( 'Symfony\\Component\\HttpKernel\\RebootableInterface' => $vendorDir . '/symfony/http-kernel/RebootableInterface.php', 'Symfony\\Component\\HttpKernel\\TerminableInterface' => $vendorDir . '/symfony/http-kernel/TerminableInterface.php', 'Symfony\\Component\\HttpKernel\\UriSigner' => $vendorDir . '/symfony/http-kernel/UriSigner.php', + 'Symfony\\Component\\Routing\\Alias' => $vendorDir . '/symfony/routing/Alias.php', 'Symfony\\Component\\Routing\\Annotation\\Route' => $vendorDir . '/symfony/routing/Annotation/Route.php', 'Symfony\\Component\\Routing\\CompiledRoute' => $vendorDir . '/symfony/routing/CompiledRoute.php', 'Symfony\\Component\\Routing\\DependencyInjection\\RoutingResolverPass' => $vendorDir . '/symfony/routing/DependencyInjection/RoutingResolverPass.php', 'Symfony\\Component\\Routing\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/routing/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Routing\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/routing/Exception/InvalidArgumentException.php', 'Symfony\\Component\\Routing\\Exception\\InvalidParameterException' => $vendorDir . '/symfony/routing/Exception/InvalidParameterException.php', 'Symfony\\Component\\Routing\\Exception\\MethodNotAllowedException' => $vendorDir . '/symfony/routing/Exception/MethodNotAllowedException.php', 'Symfony\\Component\\Routing\\Exception\\MissingMandatoryParametersException' => $vendorDir . '/symfony/routing/Exception/MissingMandatoryParametersException.php', 'Symfony\\Component\\Routing\\Exception\\NoConfigurationException' => $vendorDir . '/symfony/routing/Exception/NoConfigurationException.php', 'Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException' => $vendorDir . '/symfony/routing/Exception/ResourceNotFoundException.php', + 'Symfony\\Component\\Routing\\Exception\\RouteCircularReferenceException' => $vendorDir . '/symfony/routing/Exception/RouteCircularReferenceException.php', 'Symfony\\Component\\Routing\\Exception\\RouteNotFoundException' => $vendorDir . '/symfony/routing/Exception/RouteNotFoundException.php', + 'Symfony\\Component\\Routing\\Exception\\RuntimeException' => $vendorDir . '/symfony/routing/Exception/RuntimeException.php', + 'Symfony\\Component\\Routing\\Generator\\CompiledUrlGenerator' => $vendorDir . '/symfony/routing/Generator/CompiledUrlGenerator.php', 'Symfony\\Component\\Routing\\Generator\\ConfigurableRequirementsInterface' => $vendorDir . '/symfony/routing/Generator/ConfigurableRequirementsInterface.php', + 'Symfony\\Component\\Routing\\Generator\\Dumper\\CompiledUrlGeneratorDumper' => $vendorDir . '/symfony/routing/Generator/Dumper/CompiledUrlGeneratorDumper.php', 'Symfony\\Component\\Routing\\Generator\\Dumper\\GeneratorDumper' => $vendorDir . '/symfony/routing/Generator/Dumper/GeneratorDumper.php', 'Symfony\\Component\\Routing\\Generator\\Dumper\\GeneratorDumperInterface' => $vendorDir . '/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php', - 'Symfony\\Component\\Routing\\Generator\\Dumper\\PhpGeneratorDumper' => $vendorDir . '/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php', 'Symfony\\Component\\Routing\\Generator\\UrlGenerator' => $vendorDir . '/symfony/routing/Generator/UrlGenerator.php', 'Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface' => $vendorDir . '/symfony/routing/Generator/UrlGeneratorInterface.php', 'Symfony\\Component\\Routing\\Loader\\AnnotationClassLoader' => $vendorDir . '/symfony/routing/Loader/AnnotationClassLoader.php', 'Symfony\\Component\\Routing\\Loader\\AnnotationDirectoryLoader' => $vendorDir . '/symfony/routing/Loader/AnnotationDirectoryLoader.php', 'Symfony\\Component\\Routing\\Loader\\AnnotationFileLoader' => $vendorDir . '/symfony/routing/Loader/AnnotationFileLoader.php', 'Symfony\\Component\\Routing\\Loader\\ClosureLoader' => $vendorDir . '/symfony/routing/Loader/ClosureLoader.php', + 'Symfony\\Component\\Routing\\Loader\\Configurator\\AliasConfigurator' => $vendorDir . '/symfony/routing/Loader/Configurator/AliasConfigurator.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\CollectionConfigurator' => $vendorDir . '/symfony/routing/Loader/Configurator/CollectionConfigurator.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\ImportConfigurator' => $vendorDir . '/symfony/routing/Loader/Configurator/ImportConfigurator.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\RouteConfigurator' => $vendorDir . '/symfony/routing/Loader/Configurator/RouteConfigurator.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\RoutingConfigurator' => $vendorDir . '/symfony/routing/Loader/Configurator/RoutingConfigurator.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\AddTrait' => $vendorDir . '/symfony/routing/Loader/Configurator/Traits/AddTrait.php', + 'Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\HostTrait' => $vendorDir . '/symfony/routing/Loader/Configurator/Traits/HostTrait.php', + 'Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\LocalizedRouteTrait' => $vendorDir . '/symfony/routing/Loader/Configurator/Traits/LocalizedRouteTrait.php', + 'Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\PrefixTrait' => $vendorDir . '/symfony/routing/Loader/Configurator/Traits/PrefixTrait.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\RouteTrait' => $vendorDir . '/symfony/routing/Loader/Configurator/Traits/RouteTrait.php', - 'Symfony\\Component\\Routing\\Loader\\DependencyInjection\\ServiceRouterLoader' => $vendorDir . '/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php', + 'Symfony\\Component\\Routing\\Loader\\ContainerLoader' => $vendorDir . '/symfony/routing/Loader/ContainerLoader.php', 'Symfony\\Component\\Routing\\Loader\\DirectoryLoader' => $vendorDir . '/symfony/routing/Loader/DirectoryLoader.php', 'Symfony\\Component\\Routing\\Loader\\GlobFileLoader' => $vendorDir . '/symfony/routing/Loader/GlobFileLoader.php', - 'Symfony\\Component\\Routing\\Loader\\ObjectRouteLoader' => $vendorDir . '/symfony/routing/Loader/ObjectRouteLoader.php', + 'Symfony\\Component\\Routing\\Loader\\ObjectLoader' => $vendorDir . '/symfony/routing/Loader/ObjectLoader.php', 'Symfony\\Component\\Routing\\Loader\\PhpFileLoader' => $vendorDir . '/symfony/routing/Loader/PhpFileLoader.php', 'Symfony\\Component\\Routing\\Loader\\XmlFileLoader' => $vendorDir . '/symfony/routing/Loader/XmlFileLoader.php', 'Symfony\\Component\\Routing\\Loader\\YamlFileLoader' => $vendorDir . '/symfony/routing/Loader/YamlFileLoader.php', - 'Symfony\\Component\\Routing\\Matcher\\Dumper\\DumperCollection' => $vendorDir . '/symfony/routing/Matcher/Dumper/DumperCollection.php', - 'Symfony\\Component\\Routing\\Matcher\\Dumper\\DumperRoute' => $vendorDir . '/symfony/routing/Matcher/Dumper/DumperRoute.php', + 'Symfony\\Component\\Routing\\Matcher\\CompiledUrlMatcher' => $vendorDir . '/symfony/routing/Matcher/CompiledUrlMatcher.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\CompiledUrlMatcherDumper' => $vendorDir . '/symfony/routing/Matcher/Dumper/CompiledUrlMatcherDumper.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\CompiledUrlMatcherTrait' => $vendorDir . '/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php', 'Symfony\\Component\\Routing\\Matcher\\Dumper\\MatcherDumper' => $vendorDir . '/symfony/routing/Matcher/Dumper/MatcherDumper.php', 'Symfony\\Component\\Routing\\Matcher\\Dumper\\MatcherDumperInterface' => $vendorDir . '/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php', - 'Symfony\\Component\\Routing\\Matcher\\Dumper\\PhpMatcherDumper' => $vendorDir . '/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php', 'Symfony\\Component\\Routing\\Matcher\\Dumper\\StaticPrefixCollection' => $vendorDir . '/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php', + 'Symfony\\Component\\Routing\\Matcher\\ExpressionLanguageProvider' => $vendorDir . '/symfony/routing/Matcher/ExpressionLanguageProvider.php', 'Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcher' => $vendorDir . '/symfony/routing/Matcher/RedirectableUrlMatcher.php', 'Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface' => $vendorDir . '/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php', 'Symfony\\Component\\Routing\\Matcher\\RequestMatcherInterface' => $vendorDir . '/symfony/routing/Matcher/RequestMatcherInterface.php', @@ -1855,6 +2486,20 @@ return array( 'Symfony\\Component\\Stopwatch\\Stopwatch' => $vendorDir . '/symfony/stopwatch/Stopwatch.php', 'Symfony\\Component\\Stopwatch\\StopwatchEvent' => $vendorDir . '/symfony/stopwatch/StopwatchEvent.php', 'Symfony\\Component\\Stopwatch\\StopwatchPeriod' => $vendorDir . '/symfony/stopwatch/StopwatchPeriod.php', + 'Symfony\\Component\\String\\AbstractString' => $vendorDir . '/symfony/string/AbstractString.php', + 'Symfony\\Component\\String\\AbstractUnicodeString' => $vendorDir . '/symfony/string/AbstractUnicodeString.php', + 'Symfony\\Component\\String\\ByteString' => $vendorDir . '/symfony/string/ByteString.php', + 'Symfony\\Component\\String\\CodePointString' => $vendorDir . '/symfony/string/CodePointString.php', + 'Symfony\\Component\\String\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/string/Exception/ExceptionInterface.php', + 'Symfony\\Component\\String\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/string/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\String\\Exception\\RuntimeException' => $vendorDir . '/symfony/string/Exception/RuntimeException.php', + 'Symfony\\Component\\String\\Inflector\\EnglishInflector' => $vendorDir . '/symfony/string/Inflector/EnglishInflector.php', + 'Symfony\\Component\\String\\Inflector\\FrenchInflector' => $vendorDir . '/symfony/string/Inflector/FrenchInflector.php', + 'Symfony\\Component\\String\\Inflector\\InflectorInterface' => $vendorDir . '/symfony/string/Inflector/InflectorInterface.php', + 'Symfony\\Component\\String\\LazyString' => $vendorDir . '/symfony/string/LazyString.php', + 'Symfony\\Component\\String\\Slugger\\AsciiSlugger' => $vendorDir . '/symfony/string/Slugger/AsciiSlugger.php', + 'Symfony\\Component\\String\\Slugger\\SluggerInterface' => $vendorDir . '/symfony/string/Slugger/SluggerInterface.php', + 'Symfony\\Component\\String\\UnicodeString' => $vendorDir . '/symfony/string/UnicodeString.php', 'Symfony\\Component\\VarDumper\\Caster\\AmqpCaster' => $vendorDir . '/symfony/var-dumper/Caster/AmqpCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\ArgsStub' => $vendorDir . '/symfony/var-dumper/Caster/ArgsStub.php', 'Symfony\\Component\\VarDumper\\Caster\\Caster' => $vendorDir . '/symfony/var-dumper/Caster/Caster.php', @@ -1865,13 +2510,23 @@ return array( 'Symfony\\Component\\VarDumper\\Caster\\DOMCaster' => $vendorDir . '/symfony/var-dumper/Caster/DOMCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\DateCaster' => $vendorDir . '/symfony/var-dumper/Caster/DateCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\DoctrineCaster' => $vendorDir . '/symfony/var-dumper/Caster/DoctrineCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\DsCaster' => $vendorDir . '/symfony/var-dumper/Caster/DsCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\DsPairStub' => $vendorDir . '/symfony/var-dumper/Caster/DsPairStub.php', 'Symfony\\Component\\VarDumper\\Caster\\EnumStub' => $vendorDir . '/symfony/var-dumper/Caster/EnumStub.php', 'Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster' => $vendorDir . '/symfony/var-dumper/Caster/ExceptionCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\FiberCaster' => $vendorDir . '/symfony/var-dumper/Caster/FiberCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\FrameStub' => $vendorDir . '/symfony/var-dumper/Caster/FrameStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\GmpCaster' => $vendorDir . '/symfony/var-dumper/Caster/GmpCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ImagineCaster' => $vendorDir . '/symfony/var-dumper/Caster/ImagineCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ImgStub' => $vendorDir . '/symfony/var-dumper/Caster/ImgStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\IntlCaster' => $vendorDir . '/symfony/var-dumper/Caster/IntlCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\LinkStub' => $vendorDir . '/symfony/var-dumper/Caster/LinkStub.php', - 'Symfony\\Component\\VarDumper\\Caster\\MongoCaster' => $vendorDir . '/symfony/var-dumper/Caster/MongoCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\MemcachedCaster' => $vendorDir . '/symfony/var-dumper/Caster/MemcachedCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\MysqliCaster' => $vendorDir . '/symfony/var-dumper/Caster/MysqliCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\PdoCaster' => $vendorDir . '/symfony/var-dumper/Caster/PdoCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\PgSqlCaster' => $vendorDir . '/symfony/var-dumper/Caster/PgSqlCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ProxyManagerCaster' => $vendorDir . '/symfony/var-dumper/Caster/ProxyManagerCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\RdKafkaCaster' => $vendorDir . '/symfony/var-dumper/Caster/RdKafkaCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\RedisCaster' => $vendorDir . '/symfony/var-dumper/Caster/RedisCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster' => $vendorDir . '/symfony/var-dumper/Caster/ReflectionCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\ResourceCaster' => $vendorDir . '/symfony/var-dumper/Caster/ResourceCaster.php', @@ -1879,6 +2534,7 @@ return array( 'Symfony\\Component\\VarDumper\\Caster\\StubCaster' => $vendorDir . '/symfony/var-dumper/Caster/StubCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster' => $vendorDir . '/symfony/var-dumper/Caster/SymfonyCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\TraceStub' => $vendorDir . '/symfony/var-dumper/Caster/TraceStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\UuidCaster' => $vendorDir . '/symfony/var-dumper/Caster/UuidCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\XmlReaderCaster' => $vendorDir . '/symfony/var-dumper/Caster/XmlReaderCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\XmlResourceCaster' => $vendorDir . '/symfony/var-dumper/Caster/XmlResourceCaster.php', 'Symfony\\Component\\VarDumper\\Cloner\\AbstractCloner' => $vendorDir . '/symfony/var-dumper/Cloner/AbstractCloner.php', @@ -1888,13 +2544,34 @@ return array( 'Symfony\\Component\\VarDumper\\Cloner\\DumperInterface' => $vendorDir . '/symfony/var-dumper/Cloner/DumperInterface.php', 'Symfony\\Component\\VarDumper\\Cloner\\Stub' => $vendorDir . '/symfony/var-dumper/Cloner/Stub.php', 'Symfony\\Component\\VarDumper\\Cloner\\VarCloner' => $vendorDir . '/symfony/var-dumper/Cloner/VarCloner.php', + 'Symfony\\Component\\VarDumper\\Command\\Descriptor\\CliDescriptor' => $vendorDir . '/symfony/var-dumper/Command/Descriptor/CliDescriptor.php', + 'Symfony\\Component\\VarDumper\\Command\\Descriptor\\DumpDescriptorInterface' => $vendorDir . '/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php', + 'Symfony\\Component\\VarDumper\\Command\\Descriptor\\HtmlDescriptor' => $vendorDir . '/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php', + 'Symfony\\Component\\VarDumper\\Command\\ServerDumpCommand' => $vendorDir . '/symfony/var-dumper/Command/ServerDumpCommand.php', 'Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper' => $vendorDir . '/symfony/var-dumper/Dumper/AbstractDumper.php', 'Symfony\\Component\\VarDumper\\Dumper\\CliDumper' => $vendorDir . '/symfony/var-dumper/Dumper/CliDumper.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\CliContextProvider' => $vendorDir . '/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\ContextProviderInterface' => $vendorDir . '/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\RequestContextProvider' => $vendorDir . '/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\SourceContextProvider' => $vendorDir . '/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextualizedDumper' => $vendorDir . '/symfony/var-dumper/Dumper/ContextualizedDumper.php', 'Symfony\\Component\\VarDumper\\Dumper\\DataDumperInterface' => $vendorDir . '/symfony/var-dumper/Dumper/DataDumperInterface.php', 'Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper' => $vendorDir . '/symfony/var-dumper/Dumper/HtmlDumper.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ServerDumper' => $vendorDir . '/symfony/var-dumper/Dumper/ServerDumper.php', 'Symfony\\Component\\VarDumper\\Exception\\ThrowingCasterException' => $vendorDir . '/symfony/var-dumper/Exception/ThrowingCasterException.php', - 'Symfony\\Component\\VarDumper\\Test\\VarDumperTestTrait' => $vendorDir . '/symfony/var-dumper/Test/VarDumperTestTrait.php', + 'Symfony\\Component\\VarDumper\\Server\\Connection' => $vendorDir . '/symfony/var-dumper/Server/Connection.php', + 'Symfony\\Component\\VarDumper\\Server\\DumpServer' => $vendorDir . '/symfony/var-dumper/Server/DumpServer.php', 'Symfony\\Component\\VarDumper\\VarDumper' => $vendorDir . '/symfony/var-dumper/VarDumper.php', + 'Symfony\\Component\\VarExporter\\Exception\\ClassNotFoundException' => $vendorDir . '/symfony/var-exporter/Exception/ClassNotFoundException.php', + 'Symfony\\Component\\VarExporter\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/var-exporter/Exception/ExceptionInterface.php', + 'Symfony\\Component\\VarExporter\\Exception\\NotInstantiableTypeException' => $vendorDir . '/symfony/var-exporter/Exception/NotInstantiableTypeException.php', + 'Symfony\\Component\\VarExporter\\Instantiator' => $vendorDir . '/symfony/var-exporter/Instantiator.php', + 'Symfony\\Component\\VarExporter\\Internal\\Exporter' => $vendorDir . '/symfony/var-exporter/Internal/Exporter.php', + 'Symfony\\Component\\VarExporter\\Internal\\Hydrator' => $vendorDir . '/symfony/var-exporter/Internal/Hydrator.php', + 'Symfony\\Component\\VarExporter\\Internal\\Reference' => $vendorDir . '/symfony/var-exporter/Internal/Reference.php', + 'Symfony\\Component\\VarExporter\\Internal\\Registry' => $vendorDir . '/symfony/var-exporter/Internal/Registry.php', + 'Symfony\\Component\\VarExporter\\Internal\\Values' => $vendorDir . '/symfony/var-exporter/Internal/Values.php', + 'Symfony\\Component\\VarExporter\\VarExporter' => $vendorDir . '/symfony/var-exporter/VarExporter.php', 'Symfony\\Component\\Yaml\\Command\\LintCommand' => $vendorDir . '/symfony/yaml/Command/LintCommand.php', 'Symfony\\Component\\Yaml\\Dumper' => $vendorDir . '/symfony/yaml/Dumper.php', 'Symfony\\Component\\Yaml\\Escaper' => $vendorDir . '/symfony/yaml/Escaper.php', @@ -1907,19 +2584,37 @@ return array( 'Symfony\\Component\\Yaml\\Tag\\TaggedValue' => $vendorDir . '/symfony/yaml/Tag/TaggedValue.php', 'Symfony\\Component\\Yaml\\Unescaper' => $vendorDir . '/symfony/yaml/Unescaper.php', 'Symfony\\Component\\Yaml\\Yaml' => $vendorDir . '/symfony/yaml/Yaml.php', - 'Symfony\\Polyfill\\Apcu\\Apcu' => $vendorDir . '/symfony/polyfill-apcu/Apcu.php', + 'Symfony\\Contracts\\Cache\\CacheInterface' => $vendorDir . '/symfony/cache-contracts/CacheInterface.php', + 'Symfony\\Contracts\\Cache\\CacheTrait' => $vendorDir . '/symfony/cache-contracts/CacheTrait.php', + 'Symfony\\Contracts\\Cache\\CallbackInterface' => $vendorDir . '/symfony/cache-contracts/CallbackInterface.php', + 'Symfony\\Contracts\\Cache\\ItemInterface' => $vendorDir . '/symfony/cache-contracts/ItemInterface.php', + 'Symfony\\Contracts\\Cache\\TagAwareCacheInterface' => $vendorDir . '/symfony/cache-contracts/TagAwareCacheInterface.php', + 'Symfony\\Contracts\\EventDispatcher\\Event' => $vendorDir . '/symfony/event-dispatcher-contracts/Event.php', + 'Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface' => $vendorDir . '/symfony/event-dispatcher-contracts/EventDispatcherInterface.php', + 'Symfony\\Contracts\\Service\\Attribute\\Required' => $vendorDir . '/symfony/service-contracts/Attribute/Required.php', + 'Symfony\\Contracts\\Service\\Attribute\\SubscribedService' => $vendorDir . '/symfony/service-contracts/Attribute/SubscribedService.php', + 'Symfony\\Contracts\\Service\\ResetInterface' => $vendorDir . '/symfony/service-contracts/ResetInterface.php', + 'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => $vendorDir . '/symfony/service-contracts/ServiceLocatorTrait.php', + 'Symfony\\Contracts\\Service\\ServiceProviderInterface' => $vendorDir . '/symfony/service-contracts/ServiceProviderInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => $vendorDir . '/symfony/service-contracts/ServiceSubscriberInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => $vendorDir . '/symfony/service-contracts/ServiceSubscriberTrait.php', + 'Symfony\\Contracts\\Translation\\LocaleAwareInterface' => $vendorDir . '/symfony/translation-contracts/LocaleAwareInterface.php', + 'Symfony\\Contracts\\Translation\\TranslatableInterface' => $vendorDir . '/symfony/translation-contracts/TranslatableInterface.php', + 'Symfony\\Contracts\\Translation\\TranslatorInterface' => $vendorDir . '/symfony/translation-contracts/TranslatorInterface.php', + 'Symfony\\Contracts\\Translation\\TranslatorTrait' => $vendorDir . '/symfony/translation-contracts/TranslatorTrait.php', 'Symfony\\Polyfill\\Ctype\\Ctype' => $vendorDir . '/symfony/polyfill-ctype/Ctype.php', + 'Symfony\\Polyfill\\Intl\\Grapheme\\Grapheme' => $vendorDir . '/symfony/polyfill-intl-grapheme/Grapheme.php', + 'Symfony\\Polyfill\\Intl\\Idn\\Idn' => $vendorDir . '/symfony/polyfill-intl-idn/Idn.php', + 'Symfony\\Polyfill\\Intl\\Idn\\Info' => $vendorDir . '/symfony/polyfill-intl-idn/Info.php', + 'Symfony\\Polyfill\\Intl\\Idn\\Resources\\unidata\\DisallowedRanges' => $vendorDir . '/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php', + 'Symfony\\Polyfill\\Intl\\Idn\\Resources\\unidata\\Regex' => $vendorDir . '/symfony/polyfill-intl-idn/Resources/unidata/Regex.php', + 'Symfony\\Polyfill\\Intl\\Normalizer\\Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Normalizer.php', 'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php', - 'Symfony\\Polyfill\\Php56\\Php56' => $vendorDir . '/symfony/polyfill-php56/Php56.php', - 'Symfony\\Polyfill\\Php70\\Php70' => $vendorDir . '/symfony/polyfill-php70/Php70.php', - 'Symfony\\Polyfill\\Util\\Binary' => $vendorDir . '/symfony/polyfill-util/Binary.php', - 'Symfony\\Polyfill\\Util\\BinaryNoFuncOverload' => $vendorDir . '/symfony/polyfill-util/BinaryNoFuncOverload.php', - 'Symfony\\Polyfill\\Util\\BinaryOnFuncOverload' => $vendorDir . '/symfony/polyfill-util/BinaryOnFuncOverload.php', - 'Symfony\\Polyfill\\Util\\TestListener' => $vendorDir . '/symfony/polyfill-util/TestListener.php', - 'Symfony\\Polyfill\\Util\\TestListenerForV5' => $vendorDir . '/symfony/polyfill-util/TestListenerForV5.php', - 'Symfony\\Polyfill\\Util\\TestListenerForV6' => $vendorDir . '/symfony/polyfill-util/TestListenerForV6.php', - 'Symfony\\Polyfill\\Util\\TestListenerForV7' => $vendorDir . '/symfony/polyfill-util/TestListenerForV7.php', - 'Symfony\\Polyfill\\Util\\TestListenerTrait' => $vendorDir . '/symfony/polyfill-util/TestListenerTrait.php', + 'Symfony\\Polyfill\\Php72\\Php72' => $vendorDir . '/symfony/polyfill-php72/Php72.php', + 'Symfony\\Polyfill\\Php73\\Php73' => $vendorDir . '/symfony/polyfill-php73/Php73.php', + 'Symfony\\Polyfill\\Php80\\Php80' => $vendorDir . '/symfony/polyfill-php80/Php80.php', + 'Symfony\\Polyfill\\Php80\\PhpToken' => $vendorDir . '/symfony/polyfill-php80/PhpToken.php', + 'Symfony\\Polyfill\\Php81\\Php81' => $vendorDir . '/symfony/polyfill-php81/Php81.php', 'SynchroExceptionNotStarted' => $baseDir . '/application/exceptions/SynchroExceptionNotStarted.php', 'System' => $vendorDir . '/pear/pear-core-minimal/src/System.php', 'TCPDF' => $vendorDir . '/combodo/tcpdf/tcpdf.php', @@ -1939,6 +2634,10 @@ return array( 'TemplateMenuNode' => $baseDir . '/application/menunode.class.inc.php', 'TemplateString' => $baseDir . '/core/templatestring.class.inc.php', 'TemplateStringPlaceholder' => $baseDir . '/core/templatestring.class.inc.php', + 'TheNetworg\\OAuth2\\Client\\Grant\\JwtBearer' => $vendorDir . '/thenetworg/oauth2-azure/src/Grant/JwtBearer.php', + 'TheNetworg\\OAuth2\\Client\\Provider\\Azure' => $vendorDir . '/thenetworg/oauth2-azure/src/Provider/Azure.php', + 'TheNetworg\\OAuth2\\Client\\Provider\\AzureResourceOwner' => $vendorDir . '/thenetworg/oauth2-azure/src/Provider/AzureResourceOwner.php', + 'TheNetworg\\OAuth2\\Client\\Token\\AccessToken' => $vendorDir . '/thenetworg/oauth2-azure/src/Token/AccessToken.php', 'ThemeHandler' => $baseDir . '/application/themehandler.class.inc.php', 'ThemeHandlerService' => $baseDir . '/application/themehandlerservice.class.inc.php', 'ToolsLog' => $baseDir . '/core/log.class.inc.php', @@ -1964,13 +2663,13 @@ return array( 'Twig\\Error\\RuntimeError' => $vendorDir . '/twig/twig/src/Error/RuntimeError.php', 'Twig\\Error\\SyntaxError' => $vendorDir . '/twig/twig/src/Error/SyntaxError.php', 'Twig\\ExpressionParser' => $vendorDir . '/twig/twig/src/ExpressionParser.php', + 'Twig\\ExtensionSet' => $vendorDir . '/twig/twig/src/ExtensionSet.php', 'Twig\\Extension\\AbstractExtension' => $vendorDir . '/twig/twig/src/Extension/AbstractExtension.php', 'Twig\\Extension\\CoreExtension' => $vendorDir . '/twig/twig/src/Extension/CoreExtension.php', 'Twig\\Extension\\DebugExtension' => $vendorDir . '/twig/twig/src/Extension/DebugExtension.php', 'Twig\\Extension\\EscaperExtension' => $vendorDir . '/twig/twig/src/Extension/EscaperExtension.php', 'Twig\\Extension\\ExtensionInterface' => $vendorDir . '/twig/twig/src/Extension/ExtensionInterface.php', 'Twig\\Extension\\GlobalsInterface' => $vendorDir . '/twig/twig/src/Extension/GlobalsInterface.php', - 'Twig\\Extension\\InitRuntimeInterface' => $vendorDir . '/twig/twig/src/Extension/InitRuntimeInterface.php', 'Twig\\Extension\\OptimizerExtension' => $vendorDir . '/twig/twig/src/Extension/OptimizerExtension.php', 'Twig\\Extension\\ProfilerExtension' => $vendorDir . '/twig/twig/src/Extension/ProfilerExtension.php', 'Twig\\Extension\\RuntimeExtensionInterface' => $vendorDir . '/twig/twig/src/Extension/RuntimeExtensionInterface.php', @@ -1981,14 +2680,13 @@ return array( 'Twig\\Lexer' => $vendorDir . '/twig/twig/src/Lexer.php', 'Twig\\Loader\\ArrayLoader' => $vendorDir . '/twig/twig/src/Loader/ArrayLoader.php', 'Twig\\Loader\\ChainLoader' => $vendorDir . '/twig/twig/src/Loader/ChainLoader.php', - 'Twig\\Loader\\ExistsLoaderInterface' => $vendorDir . '/twig/twig/src/Loader/ExistsLoaderInterface.php', 'Twig\\Loader\\FilesystemLoader' => $vendorDir . '/twig/twig/src/Loader/FilesystemLoader.php', 'Twig\\Loader\\LoaderInterface' => $vendorDir . '/twig/twig/src/Loader/LoaderInterface.php', - 'Twig\\Loader\\SourceContextLoaderInterface' => $vendorDir . '/twig/twig/src/Loader/SourceContextLoaderInterface.php', 'Twig\\Markup' => $vendorDir . '/twig/twig/src/Markup.php', 'Twig\\NodeTraverser' => $vendorDir . '/twig/twig/src/NodeTraverser.php', 'Twig\\NodeVisitor\\AbstractNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php', 'Twig\\NodeVisitor\\EscaperNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php', + 'Twig\\NodeVisitor\\MacroAutoImportNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php', 'Twig\\NodeVisitor\\NodeVisitorInterface' => $vendorDir . '/twig/twig/src/NodeVisitor/NodeVisitorInterface.php', 'Twig\\NodeVisitor\\OptimizerNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php', 'Twig\\NodeVisitor\\SafeAnalysisNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php', @@ -1997,6 +2695,7 @@ return array( 'Twig\\Node\\BlockNode' => $vendorDir . '/twig/twig/src/Node/BlockNode.php', 'Twig\\Node\\BlockReferenceNode' => $vendorDir . '/twig/twig/src/Node/BlockReferenceNode.php', 'Twig\\Node\\BodyNode' => $vendorDir . '/twig/twig/src/Node/BodyNode.php', + 'Twig\\Node\\CheckSecurityCallNode' => $vendorDir . '/twig/twig/src/Node/CheckSecurityCallNode.php', 'Twig\\Node\\CheckSecurityNode' => $vendorDir . '/twig/twig/src/Node/CheckSecurityNode.php', 'Twig\\Node\\CheckToStringNode' => $vendorDir . '/twig/twig/src/Node/CheckToStringNode.php', 'Twig\\Node\\DeprecatedNode' => $vendorDir . '/twig/twig/src/Node/DeprecatedNode.php', @@ -2030,6 +2729,7 @@ return array( 'Twig\\Node\\Expression\\Binary\\OrBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/OrBinary.php', 'Twig\\Node\\Expression\\Binary\\PowerBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/PowerBinary.php', 'Twig\\Node\\Expression\\Binary\\RangeBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/RangeBinary.php', + 'Twig\\Node\\Expression\\Binary\\SpaceshipBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php', 'Twig\\Node\\Expression\\Binary\\StartsWithBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php', 'Twig\\Node\\Expression\\Binary\\SubBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/SubBinary.php', 'Twig\\Node\\Expression\\BlockReferenceExpression' => $vendorDir . '/twig/twig/src/Node/Expression/BlockReferenceExpression.php', @@ -2058,6 +2758,7 @@ return array( 'Twig\\Node\\Expression\\Unary\\NegUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/NegUnary.php', 'Twig\\Node\\Expression\\Unary\\NotUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/NotUnary.php', 'Twig\\Node\\Expression\\Unary\\PosUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/PosUnary.php', + 'Twig\\Node\\Expression\\VariadicExpression' => $vendorDir . '/twig/twig/src/Node/Expression/VariadicExpression.php', 'Twig\\Node\\FlushNode' => $vendorDir . '/twig/twig/src/Node/FlushNode.php', 'Twig\\Node\\ForLoopNode' => $vendorDir . '/twig/twig/src/Node/ForLoopNode.php', 'Twig\\Node\\ForNode' => $vendorDir . '/twig/twig/src/Node/ForNode.php', @@ -2071,10 +2772,7 @@ return array( 'Twig\\Node\\NodeOutputInterface' => $vendorDir . '/twig/twig/src/Node/NodeOutputInterface.php', 'Twig\\Node\\PrintNode' => $vendorDir . '/twig/twig/src/Node/PrintNode.php', 'Twig\\Node\\SandboxNode' => $vendorDir . '/twig/twig/src/Node/SandboxNode.php', - 'Twig\\Node\\SandboxedPrintNode' => $vendorDir . '/twig/twig/src/Node/SandboxedPrintNode.php', 'Twig\\Node\\SetNode' => $vendorDir . '/twig/twig/src/Node/SetNode.php', - 'Twig\\Node\\SetTempNode' => $vendorDir . '/twig/twig/src/Node/SetTempNode.php', - 'Twig\\Node\\SpacelessNode' => $vendorDir . '/twig/twig/src/Node/SpacelessNode.php', 'Twig\\Node\\TextNode' => $vendorDir . '/twig/twig/src/Node/TextNode.php', 'Twig\\Node\\WithNode' => $vendorDir . '/twig/twig/src/Node/WithNode.php', 'Twig\\Parser' => $vendorDir . '/twig/twig/src/Parser.php', @@ -2109,7 +2807,6 @@ return array( 'Twig\\TokenParser\\DoTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/DoTokenParser.php', 'Twig\\TokenParser\\EmbedTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/EmbedTokenParser.php', 'Twig\\TokenParser\\ExtendsTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/ExtendsTokenParser.php', - 'Twig\\TokenParser\\FilterTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/FilterTokenParser.php', 'Twig\\TokenParser\\FlushTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/FlushTokenParser.php', 'Twig\\TokenParser\\ForTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/ForTokenParser.php', 'Twig\\TokenParser\\FromTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/FromTokenParser.php', @@ -2119,7 +2816,6 @@ return array( 'Twig\\TokenParser\\MacroTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/MacroTokenParser.php', 'Twig\\TokenParser\\SandboxTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/SandboxTokenParser.php', 'Twig\\TokenParser\\SetTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/SetTokenParser.php', - 'Twig\\TokenParser\\SpacelessTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/SpacelessTokenParser.php', 'Twig\\TokenParser\\TokenParserInterface' => $vendorDir . '/twig/twig/src/TokenParser/TokenParserInterface.php', 'Twig\\TokenParser\\UseTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/UseTokenParser.php', 'Twig\\TokenParser\\WithTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/WithTokenParser.php', @@ -2129,202 +2825,6 @@ return array( 'Twig\\TwigTest' => $vendorDir . '/twig/twig/src/TwigTest.php', 'Twig\\Util\\DeprecationCollector' => $vendorDir . '/twig/twig/src/Util/DeprecationCollector.php', 'Twig\\Util\\TemplateDirIterator' => $vendorDir . '/twig/twig/src/Util/TemplateDirIterator.php', - 'Twig_Autoloader' => $vendorDir . '/twig/twig/lib/Twig/Autoloader.php', - 'Twig_BaseNodeVisitor' => $vendorDir . '/twig/twig/lib/Twig/BaseNodeVisitor.php', - 'Twig_CacheInterface' => $vendorDir . '/twig/twig/lib/Twig/CacheInterface.php', - 'Twig_Cache_Filesystem' => $vendorDir . '/twig/twig/lib/Twig/Cache/Filesystem.php', - 'Twig_Cache_Null' => $vendorDir . '/twig/twig/lib/Twig/Cache/Null.php', - 'Twig_Compiler' => $vendorDir . '/twig/twig/lib/Twig/Compiler.php', - 'Twig_CompilerInterface' => $vendorDir . '/twig/twig/lib/Twig/CompilerInterface.php', - 'Twig_ContainerRuntimeLoader' => $vendorDir . '/twig/twig/lib/Twig/ContainerRuntimeLoader.php', - 'Twig_Environment' => $vendorDir . '/twig/twig/lib/Twig/Environment.php', - 'Twig_Error' => $vendorDir . '/twig/twig/lib/Twig/Error.php', - 'Twig_Error_Loader' => $vendorDir . '/twig/twig/lib/Twig/Error/Loader.php', - 'Twig_Error_Runtime' => $vendorDir . '/twig/twig/lib/Twig/Error/Runtime.php', - 'Twig_Error_Syntax' => $vendorDir . '/twig/twig/lib/Twig/Error/Syntax.php', - 'Twig_ExistsLoaderInterface' => $vendorDir . '/twig/twig/lib/Twig/ExistsLoaderInterface.php', - 'Twig_ExpressionParser' => $vendorDir . '/twig/twig/lib/Twig/ExpressionParser.php', - 'Twig_Extension' => $vendorDir . '/twig/twig/lib/Twig/Extension.php', - 'Twig_ExtensionInterface' => $vendorDir . '/twig/twig/lib/Twig/ExtensionInterface.php', - 'Twig_Extension_Core' => $vendorDir . '/twig/twig/lib/Twig/Extension/Core.php', - 'Twig_Extension_Debug' => $vendorDir . '/twig/twig/lib/Twig/Extension/Debug.php', - 'Twig_Extension_Escaper' => $vendorDir . '/twig/twig/lib/Twig/Extension/Escaper.php', - 'Twig_Extension_GlobalsInterface' => $vendorDir . '/twig/twig/lib/Twig/Extension/GlobalsInterface.php', - 'Twig_Extension_InitRuntimeInterface' => $vendorDir . '/twig/twig/lib/Twig/Extension/InitRuntimeInterface.php', - 'Twig_Extension_Optimizer' => $vendorDir . '/twig/twig/lib/Twig/Extension/Optimizer.php', - 'Twig_Extension_Profiler' => $vendorDir . '/twig/twig/lib/Twig/Extension/Profiler.php', - 'Twig_Extension_Sandbox' => $vendorDir . '/twig/twig/lib/Twig/Extension/Sandbox.php', - 'Twig_Extension_Staging' => $vendorDir . '/twig/twig/lib/Twig/Extension/Staging.php', - 'Twig_Extension_StringLoader' => $vendorDir . '/twig/twig/lib/Twig/Extension/StringLoader.php', - 'Twig_FactoryRuntimeLoader' => $vendorDir . '/twig/twig/lib/Twig/FactoryRuntimeLoader.php', - 'Twig_FileExtensionEscapingStrategy' => $vendorDir . '/twig/twig/lib/Twig/FileExtensionEscapingStrategy.php', - 'Twig_Filter' => $vendorDir . '/twig/twig/lib/Twig/Filter.php', - 'Twig_FilterCallableInterface' => $vendorDir . '/twig/twig/lib/Twig/FilterCallableInterface.php', - 'Twig_FilterInterface' => $vendorDir . '/twig/twig/lib/Twig/FilterInterface.php', - 'Twig_Filter_Function' => $vendorDir . '/twig/twig/lib/Twig/Filter/Function.php', - 'Twig_Filter_Method' => $vendorDir . '/twig/twig/lib/Twig/Filter/Method.php', - 'Twig_Filter_Node' => $vendorDir . '/twig/twig/lib/Twig/Filter/Node.php', - 'Twig_Function' => $vendorDir . '/twig/twig/lib/Twig/Function.php', - 'Twig_FunctionCallableInterface' => $vendorDir . '/twig/twig/lib/Twig/FunctionCallableInterface.php', - 'Twig_FunctionInterface' => $vendorDir . '/twig/twig/lib/Twig/FunctionInterface.php', - 'Twig_Function_Function' => $vendorDir . '/twig/twig/lib/Twig/Function/Function.php', - 'Twig_Function_Method' => $vendorDir . '/twig/twig/lib/Twig/Function/Method.php', - 'Twig_Function_Node' => $vendorDir . '/twig/twig/lib/Twig/Function/Node.php', - 'Twig_Lexer' => $vendorDir . '/twig/twig/lib/Twig/Lexer.php', - 'Twig_LexerInterface' => $vendorDir . '/twig/twig/lib/Twig/LexerInterface.php', - 'Twig_LoaderInterface' => $vendorDir . '/twig/twig/lib/Twig/LoaderInterface.php', - 'Twig_Loader_Array' => $vendorDir . '/twig/twig/lib/Twig/Loader/Array.php', - 'Twig_Loader_Chain' => $vendorDir . '/twig/twig/lib/Twig/Loader/Chain.php', - 'Twig_Loader_Filesystem' => $vendorDir . '/twig/twig/lib/Twig/Loader/Filesystem.php', - 'Twig_Loader_String' => $vendorDir . '/twig/twig/lib/Twig/Loader/String.php', - 'Twig_Markup' => $vendorDir . '/twig/twig/lib/Twig/Markup.php', - 'Twig_Node' => $vendorDir . '/twig/twig/lib/Twig/Node.php', - 'Twig_NodeCaptureInterface' => $vendorDir . '/twig/twig/lib/Twig/NodeCaptureInterface.php', - 'Twig_NodeInterface' => $vendorDir . '/twig/twig/lib/Twig/NodeInterface.php', - 'Twig_NodeOutputInterface' => $vendorDir . '/twig/twig/lib/Twig/NodeOutputInterface.php', - 'Twig_NodeTraverser' => $vendorDir . '/twig/twig/lib/Twig/NodeTraverser.php', - 'Twig_NodeVisitorInterface' => $vendorDir . '/twig/twig/lib/Twig/NodeVisitorInterface.php', - 'Twig_NodeVisitor_Escaper' => $vendorDir . '/twig/twig/lib/Twig/NodeVisitor/Escaper.php', - 'Twig_NodeVisitor_Optimizer' => $vendorDir . '/twig/twig/lib/Twig/NodeVisitor/Optimizer.php', - 'Twig_NodeVisitor_SafeAnalysis' => $vendorDir . '/twig/twig/lib/Twig/NodeVisitor/SafeAnalysis.php', - 'Twig_NodeVisitor_Sandbox' => $vendorDir . '/twig/twig/lib/Twig/NodeVisitor/Sandbox.php', - 'Twig_Node_AutoEscape' => $vendorDir . '/twig/twig/lib/Twig/Node/AutoEscape.php', - 'Twig_Node_Block' => $vendorDir . '/twig/twig/lib/Twig/Node/Block.php', - 'Twig_Node_BlockReference' => $vendorDir . '/twig/twig/lib/Twig/Node/BlockReference.php', - 'Twig_Node_Body' => $vendorDir . '/twig/twig/lib/Twig/Node/Body.php', - 'Twig_Node_CheckSecurity' => $vendorDir . '/twig/twig/lib/Twig/Node/CheckSecurity.php', - 'Twig_Node_Deprecated' => $vendorDir . '/twig/twig/lib/Twig/Node/Deprecated.php', - 'Twig_Node_Do' => $vendorDir . '/twig/twig/lib/Twig/Node/Do.php', - 'Twig_Node_Embed' => $vendorDir . '/twig/twig/lib/Twig/Node/Embed.php', - 'Twig_Node_Expression' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression.php', - 'Twig_Node_Expression_Array' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Array.php', - 'Twig_Node_Expression_AssignName' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/AssignName.php', - 'Twig_Node_Expression_Binary' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary.php', - 'Twig_Node_Expression_Binary_Add' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Add.php', - 'Twig_Node_Expression_Binary_And' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/And.php', - 'Twig_Node_Expression_Binary_BitwiseAnd' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseAnd.php', - 'Twig_Node_Expression_Binary_BitwiseOr' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseOr.php', - 'Twig_Node_Expression_Binary_BitwiseXor' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseXor.php', - 'Twig_Node_Expression_Binary_Concat' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Concat.php', - 'Twig_Node_Expression_Binary_Div' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Div.php', - 'Twig_Node_Expression_Binary_EndsWith' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/EndsWith.php', - 'Twig_Node_Expression_Binary_Equal' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Equal.php', - 'Twig_Node_Expression_Binary_FloorDiv' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/FloorDiv.php', - 'Twig_Node_Expression_Binary_Greater' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Greater.php', - 'Twig_Node_Expression_Binary_GreaterEqual' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/GreaterEqual.php', - 'Twig_Node_Expression_Binary_In' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/In.php', - 'Twig_Node_Expression_Binary_Less' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Less.php', - 'Twig_Node_Expression_Binary_LessEqual' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/LessEqual.php', - 'Twig_Node_Expression_Binary_Matches' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Matches.php', - 'Twig_Node_Expression_Binary_Mod' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Mod.php', - 'Twig_Node_Expression_Binary_Mul' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Mul.php', - 'Twig_Node_Expression_Binary_NotEqual' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/NotEqual.php', - 'Twig_Node_Expression_Binary_NotIn' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/NotIn.php', - 'Twig_Node_Expression_Binary_Or' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Or.php', - 'Twig_Node_Expression_Binary_Power' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Power.php', - 'Twig_Node_Expression_Binary_Range' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Range.php', - 'Twig_Node_Expression_Binary_StartsWith' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/StartsWith.php', - 'Twig_Node_Expression_Binary_Sub' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Sub.php', - 'Twig_Node_Expression_BlockReference' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/BlockReference.php', - 'Twig_Node_Expression_Call' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Call.php', - 'Twig_Node_Expression_Conditional' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Conditional.php', - 'Twig_Node_Expression_Constant' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Constant.php', - 'Twig_Node_Expression_ExtensionReference' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/ExtensionReference.php', - 'Twig_Node_Expression_Filter' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Filter.php', - 'Twig_Node_Expression_Filter_Default' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Filter/Default.php', - 'Twig_Node_Expression_Function' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Function.php', - 'Twig_Node_Expression_GetAttr' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/GetAttr.php', - 'Twig_Node_Expression_MethodCall' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/MethodCall.php', - 'Twig_Node_Expression_Name' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Name.php', - 'Twig_Node_Expression_NullCoalesce' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/NullCoalesce.php', - 'Twig_Node_Expression_Parent' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Parent.php', - 'Twig_Node_Expression_TempName' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/TempName.php', - 'Twig_Node_Expression_Test' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test.php', - 'Twig_Node_Expression_Test_Constant' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Constant.php', - 'Twig_Node_Expression_Test_Defined' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Defined.php', - 'Twig_Node_Expression_Test_Divisibleby' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Divisibleby.php', - 'Twig_Node_Expression_Test_Even' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Even.php', - 'Twig_Node_Expression_Test_Null' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Null.php', - 'Twig_Node_Expression_Test_Odd' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Odd.php', - 'Twig_Node_Expression_Test_Sameas' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Sameas.php', - 'Twig_Node_Expression_Unary' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Unary.php', - 'Twig_Node_Expression_Unary_Neg' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Unary/Neg.php', - 'Twig_Node_Expression_Unary_Not' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Unary/Not.php', - 'Twig_Node_Expression_Unary_Pos' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Unary/Pos.php', - 'Twig_Node_Flush' => $vendorDir . '/twig/twig/lib/Twig/Node/Flush.php', - 'Twig_Node_For' => $vendorDir . '/twig/twig/lib/Twig/Node/For.php', - 'Twig_Node_ForLoop' => $vendorDir . '/twig/twig/lib/Twig/Node/ForLoop.php', - 'Twig_Node_If' => $vendorDir . '/twig/twig/lib/Twig/Node/If.php', - 'Twig_Node_Import' => $vendorDir . '/twig/twig/lib/Twig/Node/Import.php', - 'Twig_Node_Include' => $vendorDir . '/twig/twig/lib/Twig/Node/Include.php', - 'Twig_Node_Macro' => $vendorDir . '/twig/twig/lib/Twig/Node/Macro.php', - 'Twig_Node_Module' => $vendorDir . '/twig/twig/lib/Twig/Node/Module.php', - 'Twig_Node_Print' => $vendorDir . '/twig/twig/lib/Twig/Node/Print.php', - 'Twig_Node_Sandbox' => $vendorDir . '/twig/twig/lib/Twig/Node/Sandbox.php', - 'Twig_Node_SandboxedPrint' => $vendorDir . '/twig/twig/lib/Twig/Node/SandboxedPrint.php', - 'Twig_Node_Set' => $vendorDir . '/twig/twig/lib/Twig/Node/Set.php', - 'Twig_Node_SetTemp' => $vendorDir . '/twig/twig/lib/Twig/Node/SetTemp.php', - 'Twig_Node_Spaceless' => $vendorDir . '/twig/twig/lib/Twig/Node/Spaceless.php', - 'Twig_Node_Text' => $vendorDir . '/twig/twig/lib/Twig/Node/Text.php', - 'Twig_Node_With' => $vendorDir . '/twig/twig/lib/Twig/Node/With.php', - 'Twig_Parser' => $vendorDir . '/twig/twig/lib/Twig/Parser.php', - 'Twig_ParserInterface' => $vendorDir . '/twig/twig/lib/Twig/ParserInterface.php', - 'Twig_Profiler_Dumper_Base' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Dumper/Base.php', - 'Twig_Profiler_Dumper_Blackfire' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Dumper/Blackfire.php', - 'Twig_Profiler_Dumper_Html' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Dumper/Html.php', - 'Twig_Profiler_Dumper_Text' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Dumper/Text.php', - 'Twig_Profiler_NodeVisitor_Profiler' => $vendorDir . '/twig/twig/lib/Twig/Profiler/NodeVisitor/Profiler.php', - 'Twig_Profiler_Node_EnterProfile' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Node/EnterProfile.php', - 'Twig_Profiler_Node_LeaveProfile' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Node/LeaveProfile.php', - 'Twig_Profiler_Profile' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Profile.php', - 'Twig_RuntimeLoaderInterface' => $vendorDir . '/twig/twig/lib/Twig/RuntimeLoaderInterface.php', - 'Twig_Sandbox_SecurityError' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityError.php', - 'Twig_Sandbox_SecurityNotAllowedFilterError' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFilterError.php', - 'Twig_Sandbox_SecurityNotAllowedFunctionError' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFunctionError.php', - 'Twig_Sandbox_SecurityNotAllowedMethodError' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedMethodError.php', - 'Twig_Sandbox_SecurityNotAllowedPropertyError' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedPropertyError.php', - 'Twig_Sandbox_SecurityNotAllowedTagError' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedTagError.php', - 'Twig_Sandbox_SecurityPolicy' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityPolicy.php', - 'Twig_Sandbox_SecurityPolicyInterface' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityPolicyInterface.php', - 'Twig_SimpleFilter' => $vendorDir . '/twig/twig/lib/Twig/SimpleFilter.php', - 'Twig_SimpleFunction' => $vendorDir . '/twig/twig/lib/Twig/SimpleFunction.php', - 'Twig_SimpleTest' => $vendorDir . '/twig/twig/lib/Twig/SimpleTest.php', - 'Twig_Source' => $vendorDir . '/twig/twig/lib/Twig/Source.php', - 'Twig_SourceContextLoaderInterface' => $vendorDir . '/twig/twig/lib/Twig/SourceContextLoaderInterface.php', - 'Twig_Template' => $vendorDir . '/twig/twig/lib/Twig/Template.php', - 'Twig_TemplateInterface' => $vendorDir . '/twig/twig/lib/Twig/TemplateInterface.php', - 'Twig_TemplateWrapper' => $vendorDir . '/twig/twig/lib/Twig/TemplateWrapper.php', - 'Twig_Test' => $vendorDir . '/twig/twig/lib/Twig/Test.php', - 'Twig_TestCallableInterface' => $vendorDir . '/twig/twig/lib/Twig/TestCallableInterface.php', - 'Twig_TestInterface' => $vendorDir . '/twig/twig/lib/Twig/TestInterface.php', - 'Twig_Token' => $vendorDir . '/twig/twig/lib/Twig/Token.php', - 'Twig_TokenParser' => $vendorDir . '/twig/twig/lib/Twig/TokenParser.php', - 'Twig_TokenParserBroker' => $vendorDir . '/twig/twig/lib/Twig/TokenParserBroker.php', - 'Twig_TokenParserBrokerInterface' => $vendorDir . '/twig/twig/lib/Twig/TokenParserBrokerInterface.php', - 'Twig_TokenParserInterface' => $vendorDir . '/twig/twig/lib/Twig/TokenParserInterface.php', - 'Twig_TokenParser_AutoEscape' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/AutoEscape.php', - 'Twig_TokenParser_Block' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Block.php', - 'Twig_TokenParser_Deprecated' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Deprecated.php', - 'Twig_TokenParser_Do' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Do.php', - 'Twig_TokenParser_Embed' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Embed.php', - 'Twig_TokenParser_Extends' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Extends.php', - 'Twig_TokenParser_Filter' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Filter.php', - 'Twig_TokenParser_Flush' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Flush.php', - 'Twig_TokenParser_For' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/For.php', - 'Twig_TokenParser_From' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/From.php', - 'Twig_TokenParser_If' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/If.php', - 'Twig_TokenParser_Import' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Import.php', - 'Twig_TokenParser_Include' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Include.php', - 'Twig_TokenParser_Macro' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Macro.php', - 'Twig_TokenParser_Sandbox' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Sandbox.php', - 'Twig_TokenParser_Set' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Set.php', - 'Twig_TokenParser_Spaceless' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Spaceless.php', - 'Twig_TokenParser_Use' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Use.php', - 'Twig_TokenParser_With' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/With.php', - 'Twig_TokenStream' => $vendorDir . '/twig/twig/lib/Twig/TokenStream.php', - 'Twig_Util_DeprecationCollector' => $vendorDir . '/twig/twig/lib/Twig/Util/DeprecationCollector.php', - 'Twig_Util_TemplateDirIterator' => $vendorDir . '/twig/twig/lib/Twig/Util/TemplateDirIterator.php', - 'TypeError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/TypeError.php', 'UIExtKeyWidget' => $baseDir . '/application/ui.extkeywidget.class.inc.php', 'UIHTMLEditorWidget' => $baseDir . '/application/ui.htmleditorwidget.class.inc.php', 'UILinksWidget' => $baseDir . '/application/ui.linkswidget.class.inc.php', @@ -2336,6 +2836,7 @@ return array( 'URLPopupMenuItem' => $baseDir . '/application/applicationextension.inc.php', 'UnaryExpression' => $baseDir . '/core/oql/expression.class.inc.php', 'UnauthenticatedWebPage' => $baseDir . '/sources/Application/WebPage/UnauthenticatedWebPage.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'UnknownClassOqlException' => $baseDir . '/core/oql/oqlinterpreter.class.inc.php', 'User' => $baseDir . '/core/userrights.class.inc.php', 'UserDashboard' => $baseDir . '/application/user.dashboard.class.inc.php', @@ -2343,6 +2844,7 @@ return array( 'UserRightException' => $baseDir . '/application/exceptions/UserRightException.php', 'UserRights' => $baseDir . '/core/userrights.class.inc.php', 'UserRightsAddOnAPI' => $baseDir . '/core/userrights.class.inc.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', 'ValueSetDefinition' => $baseDir . '/core/valuesetdef.class.inc.php', 'ValueSetEnum' => $baseDir . '/core/valuesetdef.class.inc.php', 'ValueSetEnumClasses' => $baseDir . '/core/valuesetdef.class.inc.php', @@ -2353,6 +2855,9 @@ return array( 'VariableOqlExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php', 'WebPage' => $baseDir . '/sources/Application/WebPage/WebPage.php', 'WebPageMenuNode' => $baseDir . '/application/menunode.class.inc.php', + 'Webmozart\\Assert\\Assert' => $vendorDir . '/webmozart/assert/src/Assert.php', + 'Webmozart\\Assert\\InvalidArgumentException' => $vendorDir . '/webmozart/assert/src/InvalidArgumentException.php', + 'Webmozart\\Assert\\Mixin' => $vendorDir . '/webmozart/assert/src/Mixin.php', 'WeeklyRotatingLogFileNameBuilder' => $baseDir . '/core/log.class.inc.php', 'WizardHelper' => $baseDir . '/application/wizardhelper.class.inc.php', 'XLSXWriter' => $baseDir . '/application/xlsxwriter.class.php', @@ -2419,6 +2924,7 @@ return array( 'ormStopWatch' => $baseDir . '/core/ormstopwatch.class.inc.php', 'ormStyle' => $baseDir . '/core/ormStyle.class.inc.php', 'ormTagSet' => $baseDir . '/core/ormtagset.class.inc.php', + 'phpCAS' => $vendorDir . '/apereo/phpcas/source/CAS.php', 'privUITransaction' => $baseDir . '/application/transaction.class.inc.php', 'privUITransactionFile' => $baseDir . '/application/transaction.class.inc.php', 'privUITransactionSession' => $baseDir . '/application/transaction.class.inc.php', diff --git a/lib/composer/autoload_files.php b/lib/composer/autoload_files.php index 65bc196d34..9ca87aae95 100644 --- a/lib/composer/autoload_files.php +++ b/lib/composer/autoload_files.php @@ -6,12 +6,20 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( + '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', - '5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php', - '023d27dca8066ef29e6739335ea73bad' => $vendorDir . '/symfony/polyfill-php70/bootstrap.php', - '32dcc8afd4335739640db7d200c1971d' => $vendorDir . '/symfony/polyfill-apcu/bootstrap.php', - 'bd9634f2d41831496de0d3dfe4c94881' => $vendorDir . '/symfony/polyfill-php56/bootstrap.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', + '23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php', '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', - '2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php', + 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', + 'c9d07b32a2e02bc0fc582d4f0c1b56cc' => $vendorDir . '/laminas/laminas-servicemanager/src/autoload.php', + '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', + '8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php', + 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', + 'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', + '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php', + 'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php', ); diff --git a/lib/composer/autoload_namespaces.php b/lib/composer/autoload_namespaces.php index d12922d087..1db5bf646e 100644 --- a/lib/composer/autoload_namespaces.php +++ b/lib/composer/autoload_namespaces.php @@ -6,7 +6,6 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( - 'Twig_' => array($vendorDir . '/twig/twig/lib'), 'Console' => array($vendorDir . '/pear/console_getopt'), 'Archive_Tar' => array($vendorDir . '/pear/archive_tar'), '' => array($vendorDir . '/pear/pear-core-minimal/src'), diff --git a/lib/composer/autoload_psr4.php b/lib/composer/autoload_psr4.php index 20234ce5e9..eb2c95ace5 100644 --- a/lib/composer/autoload_psr4.php +++ b/lib/composer/autoload_psr4.php @@ -6,15 +6,26 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( + 'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'), 'Twig\\' => array($vendorDir . '/twig/twig/src'), - 'Symfony\\Polyfill\\Util\\' => array($vendorDir . '/symfony/polyfill-util'), - 'Symfony\\Polyfill\\Php70\\' => array($vendorDir . '/symfony/polyfill-php70'), - 'Symfony\\Polyfill\\Php56\\' => array($vendorDir . '/symfony/polyfill-php56'), + 'TheNetworg\\OAuth2\\Client\\' => array($vendorDir . '/thenetworg/oauth2-azure/src'), + 'Symfony\\Polyfill\\Php81\\' => array($vendorDir . '/symfony/polyfill-php81'), + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'), + 'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'), 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'), + 'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'), + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'), 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), - 'Symfony\\Polyfill\\Apcu\\' => array($vendorDir . '/symfony/polyfill-apcu'), + 'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'), + 'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), + 'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'), + 'Symfony\\Contracts\\Cache\\' => array($vendorDir . '/symfony/cache-contracts'), 'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'), + 'Symfony\\Component\\VarExporter\\' => array($vendorDir . '/symfony/var-exporter'), 'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'), + 'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'), 'Symfony\\Component\\Stopwatch\\' => array($vendorDir . '/symfony/stopwatch'), 'Symfony\\Component\\Routing\\' => array($vendorDir . '/symfony/routing'), 'Symfony\\Component\\HttpKernel\\' => array($vendorDir . '/symfony/http-kernel'), @@ -22,23 +33,36 @@ return array( 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), 'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'), 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), + 'Symfony\\Component\\ErrorHandler\\' => array($vendorDir . '/symfony/error-handler'), 'Symfony\\Component\\Dotenv\\' => array($vendorDir . '/symfony/dotenv'), 'Symfony\\Component\\DependencyInjection\\' => array($vendorDir . '/symfony/dependency-injection'), - 'Symfony\\Component\\Debug\\' => array($vendorDir . '/symfony/debug'), 'Symfony\\Component\\CssSelector\\' => array($vendorDir . '/symfony/css-selector'), 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), 'Symfony\\Component\\Config\\' => array($vendorDir . '/symfony/config'), - 'Symfony\\Component\\ClassLoader\\' => array($vendorDir . '/symfony/class-loader'), 'Symfony\\Component\\Cache\\' => array($vendorDir . '/symfony/cache'), 'Symfony\\Bundle\\WebProfilerBundle\\' => array($vendorDir . '/symfony/web-profiler-bundle'), 'Symfony\\Bundle\\TwigBundle\\' => array($vendorDir . '/symfony/twig-bundle'), 'Symfony\\Bundle\\FrameworkBundle\\' => array($vendorDir . '/symfony/framework-bundle'), 'Symfony\\Bridge\\Twig\\' => array($vendorDir . '/symfony/twig-bridge'), 'ScssPhp\\ScssPhp\\' => array($vendorDir . '/scssphp/scssphp/src'), - 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), + 'Sabberworm\\CSS\\' => array($vendorDir . '/sabberworm/php-css-parser/src'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), + 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), + 'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'), 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), 'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'), - 'Pelago\\' => array($vendorDir . '/pelago/emogrifier/src'), + 'Pelago\\Emogrifier\\' => array($vendorDir . '/pelago/emogrifier/src'), + 'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src', $vendorDir . '/league/oauth2-google/src'), + 'Laminas\\Validator\\' => array($vendorDir . '/laminas/laminas-validator/src'), + 'Laminas\\Stdlib\\' => array($vendorDir . '/laminas/laminas-stdlib/src'), + 'Laminas\\ServiceManager\\' => array($vendorDir . '/laminas/laminas-servicemanager/src'), + 'Laminas\\Mime\\' => array($vendorDir . '/laminas/laminas-mime/src'), + 'Laminas\\Mail\\' => array($vendorDir . '/laminas/laminas-mail/src'), + 'Laminas\\Loader\\' => array($vendorDir . '/laminas/laminas-loader/src'), + 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'), + 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), + 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), + 'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'), ); diff --git a/lib/composer/autoload_real.php b/lib/composer/autoload_real.php index 661cd25436..cc554d8d14 100644 --- a/lib/composer/autoload_real.php +++ b/lib/composer/autoload_real.php @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b +class ComposerAutoloaderInit7f81b4a2a468a061c306af5e447a9a9f { private static $loader; @@ -24,9 +24,9 @@ class ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b require __DIR__ . '/platform_check.php'; - spl_autoload_register(array('ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInit7f81b4a2a468a061c306af5e447a9a9f', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); - spl_autoload_unregister(array('ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInit7f81b4a2a468a061c306af5e447a9a9f', 'loadClassLoader')); $includePaths = require __DIR__ . '/include_paths.php'; $includePaths[] = get_include_path(); @@ -36,7 +36,7 @@ class ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b if ($useStaticLoader) { require __DIR__ . '/autoload_static.php'; - call_user_func(\Composer\Autoload\ComposerStaticInit0018331147de7601e7552f7da8e3bb8b::getInitializer($loader)); + call_user_func(\Composer\Autoload\ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::getInitializer($loader)); } else { $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { @@ -48,19 +48,19 @@ class ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b $loader->register(true); if ($useStaticLoader) { - $includeFiles = Composer\Autoload\ComposerStaticInit0018331147de7601e7552f7da8e3bb8b::$files; + $includeFiles = Composer\Autoload\ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { - composerRequire0018331147de7601e7552f7da8e3bb8b($fileIdentifier, $file); + composerRequire7f81b4a2a468a061c306af5e447a9a9f($fileIdentifier, $file); } return $loader; } } -function composerRequire0018331147de7601e7552f7da8e3bb8b($fileIdentifier, $file) +function composerRequire7f81b4a2a468a061c306af5e447a9a9f($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { require $file; diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 2234e02458..8a22f216f6 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -4,34 +4,56 @@ namespace Composer\Autoload; -class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b +class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f { public static $files = array ( + '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', - '5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php', - '023d27dca8066ef29e6739335ea73bad' => __DIR__ . '/..' . '/symfony/polyfill-php70/bootstrap.php', - '32dcc8afd4335739640db7d200c1971d' => __DIR__ . '/..' . '/symfony/polyfill-apcu/bootstrap.php', - 'bd9634f2d41831496de0d3dfe4c94881' => __DIR__ . '/..' . '/symfony/polyfill-php56/bootstrap.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php', + '23c18046f52bef3eea034657bafda50f' => __DIR__ . '/..' . '/symfony/polyfill-php81/bootstrap.php', '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', - '2c102faa651ef8ea5874edb585946bce' => __DIR__ . '/..' . '/swiftmailer/swiftmailer/lib/swift_required.php', + 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', + 'c9d07b32a2e02bc0fc582d4f0c1b56cc' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/autoload.php', + '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', + '8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php', + 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', + 'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', + '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php', + 'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php', ); public static $prefixLengthsPsr4 = array ( + 'W' => + array ( + 'Webmozart\\Assert\\' => 17, + ), 'T' => array ( 'Twig\\' => 5, + 'TheNetworg\\OAuth2\\Client\\' => 25, ), 'S' => array ( - 'Symfony\\Polyfill\\Util\\' => 22, - 'Symfony\\Polyfill\\Php70\\' => 23, - 'Symfony\\Polyfill\\Php56\\' => 23, + 'Symfony\\Polyfill\\Php81\\' => 23, + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Polyfill\\Php73\\' => 23, + 'Symfony\\Polyfill\\Php72\\' => 23, 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33, + 'Symfony\\Polyfill\\Intl\\Idn\\' => 26, + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31, 'Symfony\\Polyfill\\Ctype\\' => 23, - 'Symfony\\Polyfill\\Apcu\\' => 22, + 'Symfony\\Contracts\\Translation\\' => 30, + 'Symfony\\Contracts\\Service\\' => 26, + 'Symfony\\Contracts\\EventDispatcher\\' => 34, + 'Symfony\\Contracts\\Cache\\' => 24, 'Symfony\\Component\\Yaml\\' => 23, + 'Symfony\\Component\\VarExporter\\' => 30, 'Symfony\\Component\\VarDumper\\' => 28, + 'Symfony\\Component\\String\\' => 25, 'Symfony\\Component\\Stopwatch\\' => 28, 'Symfony\\Component\\Routing\\' => 26, 'Symfony\\Component\\HttpKernel\\' => 29, @@ -39,68 +61,134 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\Finder\\' => 25, 'Symfony\\Component\\Filesystem\\' => 29, 'Symfony\\Component\\EventDispatcher\\' => 34, + 'Symfony\\Component\\ErrorHandler\\' => 31, 'Symfony\\Component\\Dotenv\\' => 25, 'Symfony\\Component\\DependencyInjection\\' => 38, - 'Symfony\\Component\\Debug\\' => 24, 'Symfony\\Component\\CssSelector\\' => 30, 'Symfony\\Component\\Console\\' => 26, 'Symfony\\Component\\Config\\' => 25, - 'Symfony\\Component\\ClassLoader\\' => 30, 'Symfony\\Component\\Cache\\' => 24, 'Symfony\\Bundle\\WebProfilerBundle\\' => 33, 'Symfony\\Bundle\\TwigBundle\\' => 26, 'Symfony\\Bundle\\FrameworkBundle\\' => 31, 'Symfony\\Bridge\\Twig\\' => 20, 'ScssPhp\\ScssPhp\\' => 16, + 'Sabberworm\\CSS\\' => 15, ), 'P' => array ( - 'Psr\\SimpleCache\\' => 16, 'Psr\\Log\\' => 8, + 'Psr\\Http\\Message\\' => 17, + 'Psr\\Http\\Client\\' => 16, + 'Psr\\EventDispatcher\\' => 20, 'Psr\\Container\\' => 14, 'Psr\\Cache\\' => 10, 'PhpParser\\' => 10, - 'Pelago\\' => 7, + 'Pelago\\Emogrifier\\' => 18, + ), + 'L' => + array ( + 'League\\OAuth2\\Client\\' => 21, + 'Laminas\\Validator\\' => 18, + 'Laminas\\Stdlib\\' => 15, + 'Laminas\\ServiceManager\\' => 23, + 'Laminas\\Mime\\' => 13, + 'Laminas\\Mail\\' => 13, + 'Laminas\\Loader\\' => 15, + ), + 'G' => + array ( + 'GuzzleHttp\\Psr7\\' => 16, + 'GuzzleHttp\\Promise\\' => 19, + 'GuzzleHttp\\' => 11, + ), + 'F' => + array ( + 'Firebase\\JWT\\' => 13, ), ); public static $prefixDirsPsr4 = array ( + 'Webmozart\\Assert\\' => + array ( + 0 => __DIR__ . '/..' . '/webmozart/assert/src', + ), 'Twig\\' => array ( 0 => __DIR__ . '/..' . '/twig/twig/src', ), - 'Symfony\\Polyfill\\Util\\' => + 'TheNetworg\\OAuth2\\Client\\' => array ( - 0 => __DIR__ . '/..' . '/symfony/polyfill-util', + 0 => __DIR__ . '/..' . '/thenetworg/oauth2-azure/src', ), - 'Symfony\\Polyfill\\Php70\\' => + 'Symfony\\Polyfill\\Php81\\' => array ( - 0 => __DIR__ . '/..' . '/symfony/polyfill-php70', + 0 => __DIR__ . '/..' . '/symfony/polyfill-php81', ), - 'Symfony\\Polyfill\\Php56\\' => + 'Symfony\\Polyfill\\Php80\\' => array ( - 0 => __DIR__ . '/..' . '/symfony/polyfill-php56', + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Polyfill\\Php73\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php73', + ), + 'Symfony\\Polyfill\\Php72\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php72', ), 'Symfony\\Polyfill\\Mbstring\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', ), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer', + ), + 'Symfony\\Polyfill\\Intl\\Idn\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-idn', + ), + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme', + ), 'Symfony\\Polyfill\\Ctype\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', ), - 'Symfony\\Polyfill\\Apcu\\' => + 'Symfony\\Contracts\\Translation\\' => array ( - 0 => __DIR__ . '/..' . '/symfony/polyfill-apcu', + 0 => __DIR__ . '/..' . '/symfony/translation-contracts', + ), + 'Symfony\\Contracts\\Service\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/service-contracts', + ), + 'Symfony\\Contracts\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts', + ), + 'Symfony\\Contracts\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/cache-contracts', ), 'Symfony\\Component\\Yaml\\' => array ( 0 => __DIR__ . '/..' . '/symfony/yaml', ), + 'Symfony\\Component\\VarExporter\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/var-exporter', + ), 'Symfony\\Component\\VarDumper\\' => array ( 0 => __DIR__ . '/..' . '/symfony/var-dumper', ), + 'Symfony\\Component\\String\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/string', + ), 'Symfony\\Component\\Stopwatch\\' => array ( 0 => __DIR__ . '/..' . '/symfony/stopwatch', @@ -129,6 +217,10 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b array ( 0 => __DIR__ . '/..' . '/symfony/event-dispatcher', ), + 'Symfony\\Component\\ErrorHandler\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/error-handler', + ), 'Symfony\\Component\\Dotenv\\' => array ( 0 => __DIR__ . '/..' . '/symfony/dotenv', @@ -137,10 +229,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b array ( 0 => __DIR__ . '/..' . '/symfony/dependency-injection', ), - 'Symfony\\Component\\Debug\\' => - array ( - 0 => __DIR__ . '/..' . '/symfony/debug', - ), 'Symfony\\Component\\CssSelector\\' => array ( 0 => __DIR__ . '/..' . '/symfony/css-selector', @@ -153,10 +241,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b array ( 0 => __DIR__ . '/..' . '/symfony/config', ), - 'Symfony\\Component\\ClassLoader\\' => - array ( - 0 => __DIR__ . '/..' . '/symfony/class-loader', - ), 'Symfony\\Component\\Cache\\' => array ( 0 => __DIR__ . '/..' . '/symfony/cache', @@ -181,14 +265,27 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b array ( 0 => __DIR__ . '/..' . '/scssphp/scssphp/src', ), - 'Psr\\SimpleCache\\' => + 'Sabberworm\\CSS\\' => array ( - 0 => __DIR__ . '/..' . '/psr/simple-cache/src', + 0 => __DIR__ . '/..' . '/sabberworm/php-css-parser/src', ), 'Psr\\Log\\' => array ( 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', ), + 'Psr\\Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-factory/src', + 1 => __DIR__ . '/..' . '/psr/http-message/src', + ), + 'Psr\\Http\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-client/src', + ), + 'Psr\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/event-dispatcher/src', + ), 'Psr\\Container\\' => array ( 0 => __DIR__ . '/..' . '/psr/container/src', @@ -201,20 +298,58 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b array ( 0 => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser', ), - 'Pelago\\' => + 'Pelago\\Emogrifier\\' => array ( 0 => __DIR__ . '/..' . '/pelago/emogrifier/src', ), + 'League\\OAuth2\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/league/oauth2-client/src', + 1 => __DIR__ . '/..' . '/league/oauth2-google/src', + ), + 'Laminas\\Validator\\' => + array ( + 0 => __DIR__ . '/..' . '/laminas/laminas-validator/src', + ), + 'Laminas\\Stdlib\\' => + array ( + 0 => __DIR__ . '/..' . '/laminas/laminas-stdlib/src', + ), + 'Laminas\\ServiceManager\\' => + array ( + 0 => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src', + ), + 'Laminas\\Mime\\' => + array ( + 0 => __DIR__ . '/..' . '/laminas/laminas-mime/src', + ), + 'Laminas\\Mail\\' => + array ( + 0 => __DIR__ . '/..' . '/laminas/laminas-mail/src', + ), + 'Laminas\\Loader\\' => + array ( + 0 => __DIR__ . '/..' . '/laminas/laminas-loader/src', + ), + 'GuzzleHttp\\Psr7\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', + ), + 'GuzzleHttp\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src', + ), + 'GuzzleHttp\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src', + ), + 'Firebase\\JWT\\' => + array ( + 0 => __DIR__ . '/..' . '/firebase/php-jwt/src', + ), ); public static $prefixesPsr0 = array ( - 'T' => - array ( - 'Twig_' => - array ( - 0 => __DIR__ . '/..' . '/twig/twig/lib', - ), - ), 'C' => array ( 'Console' => @@ -256,10 +391,9 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'ApplicationPopupMenuItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', 'Archive_Tar' => __DIR__ . '/..' . '/pear/archive_tar/Archive/Tar.php', 'ArchivedObjectException' => __DIR__ . '/../..' . '/application/exceptions/ArchivedObjectException.php', - 'ArithmeticError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php', - 'AssertionError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php', 'AsyncSendEmail' => __DIR__ . '/../..' . '/core/asynctask.class.inc.php', 'AsyncTask' => __DIR__ . '/../..' . '/core/asynctask.class.inc.php', + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', 'AttributeApplicationLanguage' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php', 'AttributeArchiveDate' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php', 'AttributeArchiveFlag' => __DIR__ . '/../..' . '/core/attributedef.class.inc.php', @@ -330,6 +464,53 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'BulkExportMissingParameterException' => __DIR__ . '/../..' . '/core/bulkexport.class.inc.php', 'BulkExportResult' => __DIR__ . '/../..' . '/core/bulkexport.class.inc.php', 'BulkExportResultGC' => __DIR__ . '/../..' . '/core/bulkexport.class.inc.php', + 'CAS_AuthenticationException' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/AuthenticationException.php', + 'CAS_Client' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Client.php', + 'CAS_CookieJar' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/CookieJar.php', + 'CAS_Exception' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Exception.php', + 'CAS_GracefullTerminationException' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/GracefullTerminationException.php', + 'CAS_InvalidArgumentException' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/InvalidArgumentException.php', + 'CAS_Languages_Catalan' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/Catalan.php', + 'CAS_Languages_ChineseSimplified' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/ChineseSimplified.php', + 'CAS_Languages_English' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/English.php', + 'CAS_Languages_French' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/French.php', + 'CAS_Languages_Galego' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/Galego.php', + 'CAS_Languages_German' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/German.php', + 'CAS_Languages_Greek' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/Greek.php', + 'CAS_Languages_Japanese' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/Japanese.php', + 'CAS_Languages_LanguageInterface' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/LanguageInterface.php', + 'CAS_Languages_Portuguese' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/Portuguese.php', + 'CAS_Languages_Spanish' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Languages/Spanish.php', + 'CAS_OutOfSequenceBeforeAuthenticationCallException' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/OutOfSequenceBeforeAuthenticationCallException.php', + 'CAS_OutOfSequenceBeforeClientException' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/OutOfSequenceBeforeClientException.php', + 'CAS_OutOfSequenceBeforeProxyException' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/OutOfSequenceBeforeProxyException.php', + 'CAS_OutOfSequenceException' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/OutOfSequenceException.php', + 'CAS_PGTStorage_AbstractStorage' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/PGTStorage/AbstractStorage.php', + 'CAS_PGTStorage_Db' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/PGTStorage/Db.php', + 'CAS_PGTStorage_File' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/PGTStorage/File.php', + 'CAS_ProxiedService' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxiedService.php', + 'CAS_ProxiedService_Abstract' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxiedService/Abstract.php', + 'CAS_ProxiedService_Exception' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxiedService/Exception.php', + 'CAS_ProxiedService_Http' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxiedService/Http.php', + 'CAS_ProxiedService_Http_Abstract' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxiedService/Http/Abstract.php', + 'CAS_ProxiedService_Http_Get' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxiedService/Http/Get.php', + 'CAS_ProxiedService_Http_Post' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxiedService/Http/Post.php', + 'CAS_ProxiedService_Imap' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxiedService/Imap.php', + 'CAS_ProxiedService_Testable' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxiedService/Testable.php', + 'CAS_ProxyChain' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxyChain.php', + 'CAS_ProxyChain_AllowedList' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxyChain/AllowedList.php', + 'CAS_ProxyChain_Any' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxyChain/Any.php', + 'CAS_ProxyChain_Interface' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxyChain/Interface.php', + 'CAS_ProxyChain_Trusted' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxyChain/Trusted.php', + 'CAS_ProxyTicketException' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/ProxyTicketException.php', + 'CAS_Request_AbstractRequest' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Request/AbstractRequest.php', + 'CAS_Request_CurlMultiRequest' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Request/CurlMultiRequest.php', + 'CAS_Request_CurlRequest' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Request/CurlRequest.php', + 'CAS_Request_Exception' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Request/Exception.php', + 'CAS_Request_MultiRequestInterface' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Request/MultiRequestInterface.php', + 'CAS_Request_RequestInterface' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Request/RequestInterface.php', + 'CAS_Session_PhpSession' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/Session/PhpSession.php', + 'CAS_TypeMismatchException' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS/TypeMismatchException.php', 'CLILikeWebPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/CLILikeWebPage.php', 'CLIPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/CLIPage.php', 'CMDBChange' => __DIR__ . '/../..' . '/core/cmdbchange.class.inc.php', @@ -529,9 +710,17 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Combodo\\iTop\\Composer\\iTopComposer' => __DIR__ . '/../..' . '/sources/Composer/iTopComposer.php', 'Combodo\\iTop\\Controller\\AjaxRenderController' => __DIR__ . '/../..' . '/sources/Controller/AjaxRenderController.php', 'Combodo\\iTop\\Controller\\Base\\Layout\\ActivityPanelController' => __DIR__ . '/../..' . '/sources/Controller/Base/Layout/ActivityPanelController.php', + 'Combodo\\iTop\\Controller\\OAuth\\OAuthLandingController' => __DIR__ . '/../..' . '/sources/Controller/OAuth/OAuthLandingController.php', 'Combodo\\iTop\\Controller\\PreferencesController' => __DIR__ . '/../..' . '/sources/Controller/PreferencesController.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientProvider' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAbstract' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAzure' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderFactory' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php', + 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderGoogle' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php', 'Combodo\\iTop\\Core\\CMDBChange\\CMDBChangeOrigin' => __DIR__ . '/../..' . '/sources/Core/CMDBChange/CMDBChangeOrigin.php', 'Combodo\\iTop\\Core\\DbConnectionWrapper' => __DIR__ . '/../..' . '/core/DbConnectionWrapper.php', + 'Combodo\\iTop\\Core\\Email\\EmailFactory' => __DIR__ . '/../..' . '/sources/Core/Email/EmailFactory.php', + 'Combodo\\iTop\\Core\\Email\\iEMail' => __DIR__ . '/../..' . '/sources/Core/Email/iEMail.php', 'Combodo\\iTop\\Core\\MetaModel\\FriendlyNameType' => __DIR__ . '/../..' . '/sources/Core/MetaModel/FriendlyNameType.php', 'Combodo\\iTop\\Core\\MetaModel\\HierarchicalKey' => __DIR__ . '/../..' . '/sources/Core/MetaModel/HierarchicalKey.php', 'Combodo\\iTop\\DesignDocument' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php', @@ -586,7 +775,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Combodo\\iTop\\Renderer\\FieldRenderer' => __DIR__ . '/../..' . '/sources/Renderer/FieldRenderer.php', 'Combodo\\iTop\\Renderer\\FormRenderer' => __DIR__ . '/../..' . '/sources/Renderer/FormRenderer.php', 'Combodo\\iTop\\Renderer\\RenderingOutput' => __DIR__ . '/../..' . '/sources/Renderer/RenderingOutput.php', - 'Combodo\\iTop\\TwigExtension' => __DIR__ . '/../..' . '/application/twigextension.class.inc.php', 'CompileCSSService' => __DIR__ . '/../..' . '/application/compilecssservice.class.inc.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'Config' => __DIR__ . '/../..' . '/core/config.class.inc.php', @@ -606,6 +794,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'CryptEngine' => __DIR__ . '/../..' . '/core/simplecrypt.class.inc.php', 'CustomFieldsHandler' => __DIR__ . '/../..' . '/core/customfieldshandler.class.inc.php', 'DBObject' => __DIR__ . '/../..' . '/core/dbobject.class.php', + 'DBObjectSearch' => __DIR__ . '/../..' . '/core/dbobjectsearch.class.php', 'DBObjectSet' => __DIR__ . '/../..' . '/core/dbobjectset.class.php', 'DBObjectSetComparator' => __DIR__ . '/../..' . '/core/dbobjectset.class.php', 'DBProperty' => __DIR__ . '/../..' . '/core/dbproperty.class.inc.php', @@ -671,10 +860,9 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'DisplayableGroupNode' => __DIR__ . '/../..' . '/core/displayablegraph.class.inc.php', 'DisplayableNode' => __DIR__ . '/../..' . '/core/displayablegraph.class.inc.php', 'DisplayableRedundancyNode' => __DIR__ . '/../..' . '/core/displayablegraph.class.inc.php', - 'DivisionByZeroError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php', 'DownloadPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/DownloadPage.php', 'EMail' => __DIR__ . '/../..' . '/core/email.class.inc.php', - 'Error' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/Error.php', + 'EMailLaminas' => __DIR__ . '/../..' . '/sources/Core/Email/EmailLaminas.php', 'ErrorPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/ErrorPage.php', 'Event' => __DIR__ . '/../..' . '/core/event.class.inc.php', 'EventIssue' => __DIR__ . '/../..' . '/core/event.class.inc.php', @@ -702,11 +890,104 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'FilterFromAttribute' => __DIR__ . '/../..' . '/core/filterdef.class.inc.php', 'FilterPrivateKey' => __DIR__ . '/../..' . '/core/filterdef.class.inc.php', 'FindStylesheetObject' => __DIR__ . '/../..' . '/application/findstylesheetobject.class.inc.php', + 'Firebase\\JWT\\BeforeValidException' => __DIR__ . '/..' . '/firebase/php-jwt/src/BeforeValidException.php', + 'Firebase\\JWT\\CachedKeySet' => __DIR__ . '/..' . '/firebase/php-jwt/src/CachedKeySet.php', + 'Firebase\\JWT\\ExpiredException' => __DIR__ . '/..' . '/firebase/php-jwt/src/ExpiredException.php', + 'Firebase\\JWT\\JWK' => __DIR__ . '/..' . '/firebase/php-jwt/src/JWK.php', + 'Firebase\\JWT\\JWT' => __DIR__ . '/..' . '/firebase/php-jwt/src/JWT.php', + 'Firebase\\JWT\\Key' => __DIR__ . '/..' . '/firebase/php-jwt/src/Key.php', + 'Firebase\\JWT\\SignatureInvalidException' => __DIR__ . '/..' . '/firebase/php-jwt/src/SignatureInvalidException.php', 'FunctionExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php', 'FunctionOqlExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php', 'GraphEdge' => __DIR__ . '/../..' . '/core/simplegraph.class.inc.php', 'GraphElement' => __DIR__ . '/../..' . '/core/simplegraph.class.inc.php', 'GraphNode' => __DIR__ . '/../..' . '/core/simplegraph.class.inc.php', + 'GuzzleHttp\\BodySummarizer' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/BodySummarizer.php', + 'GuzzleHttp\\BodySummarizerInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/BodySummarizerInterface.php', + 'GuzzleHttp\\Client' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Client.php', + 'GuzzleHttp\\ClientInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/ClientInterface.php', + 'GuzzleHttp\\ClientTrait' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/ClientTrait.php', + 'GuzzleHttp\\Cookie\\CookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/CookieJar.php', + 'GuzzleHttp\\Cookie\\CookieJarInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php', + 'GuzzleHttp\\Cookie\\FileCookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php', + 'GuzzleHttp\\Cookie\\SessionCookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php', + 'GuzzleHttp\\Cookie\\SetCookie' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/SetCookie.php', + 'GuzzleHttp\\Exception\\BadResponseException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/BadResponseException.php', + 'GuzzleHttp\\Exception\\ClientException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ClientException.php', + 'GuzzleHttp\\Exception\\ConnectException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ConnectException.php', + 'GuzzleHttp\\Exception\\GuzzleException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/GuzzleException.php', + 'GuzzleHttp\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php', + 'GuzzleHttp\\Exception\\RequestException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/RequestException.php', + 'GuzzleHttp\\Exception\\ServerException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ServerException.php', + 'GuzzleHttp\\Exception\\TooManyRedirectsException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php', + 'GuzzleHttp\\Exception\\TransferException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/TransferException.php', + 'GuzzleHttp\\HandlerStack' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/HandlerStack.php', + 'GuzzleHttp\\Handler\\CurlFactory' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlFactory.php', + 'GuzzleHttp\\Handler\\CurlFactoryInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php', + 'GuzzleHttp\\Handler\\CurlHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlHandler.php', + 'GuzzleHttp\\Handler\\CurlMultiHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php', + 'GuzzleHttp\\Handler\\EasyHandle' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/EasyHandle.php', + 'GuzzleHttp\\Handler\\HeaderProcessor' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php', + 'GuzzleHttp\\Handler\\MockHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/MockHandler.php', + 'GuzzleHttp\\Handler\\Proxy' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/Proxy.php', + 'GuzzleHttp\\Handler\\StreamHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/StreamHandler.php', + 'GuzzleHttp\\MessageFormatter' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/MessageFormatter.php', + 'GuzzleHttp\\MessageFormatterInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/MessageFormatterInterface.php', + 'GuzzleHttp\\Middleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Middleware.php', + 'GuzzleHttp\\Pool' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Pool.php', + 'GuzzleHttp\\PrepareBodyMiddleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php', + 'GuzzleHttp\\Promise\\AggregateException' => __DIR__ . '/..' . '/guzzlehttp/promises/src/AggregateException.php', + 'GuzzleHttp\\Promise\\CancellationException' => __DIR__ . '/..' . '/guzzlehttp/promises/src/CancellationException.php', + 'GuzzleHttp\\Promise\\Coroutine' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Coroutine.php', + 'GuzzleHttp\\Promise\\Create' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Create.php', + 'GuzzleHttp\\Promise\\Each' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Each.php', + 'GuzzleHttp\\Promise\\EachPromise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/EachPromise.php', + 'GuzzleHttp\\Promise\\FulfilledPromise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/FulfilledPromise.php', + 'GuzzleHttp\\Promise\\Is' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Is.php', + 'GuzzleHttp\\Promise\\Promise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Promise.php', + 'GuzzleHttp\\Promise\\PromiseInterface' => __DIR__ . '/..' . '/guzzlehttp/promises/src/PromiseInterface.php', + 'GuzzleHttp\\Promise\\PromisorInterface' => __DIR__ . '/..' . '/guzzlehttp/promises/src/PromisorInterface.php', + 'GuzzleHttp\\Promise\\RejectedPromise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/RejectedPromise.php', + 'GuzzleHttp\\Promise\\RejectionException' => __DIR__ . '/..' . '/guzzlehttp/promises/src/RejectionException.php', + 'GuzzleHttp\\Promise\\TaskQueue' => __DIR__ . '/..' . '/guzzlehttp/promises/src/TaskQueue.php', + 'GuzzleHttp\\Promise\\TaskQueueInterface' => __DIR__ . '/..' . '/guzzlehttp/promises/src/TaskQueueInterface.php', + 'GuzzleHttp\\Promise\\Utils' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Utils.php', + 'GuzzleHttp\\Psr7\\AppendStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/AppendStream.php', + 'GuzzleHttp\\Psr7\\BufferStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/BufferStream.php', + 'GuzzleHttp\\Psr7\\CachingStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/CachingStream.php', + 'GuzzleHttp\\Psr7\\DroppingStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/DroppingStream.php', + 'GuzzleHttp\\Psr7\\Exception\\MalformedUriException' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Exception/MalformedUriException.php', + 'GuzzleHttp\\Psr7\\FnStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/FnStream.php', + 'GuzzleHttp\\Psr7\\Header' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Header.php', + 'GuzzleHttp\\Psr7\\HttpFactory' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/HttpFactory.php', + 'GuzzleHttp\\Psr7\\InflateStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/InflateStream.php', + 'GuzzleHttp\\Psr7\\LazyOpenStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/LazyOpenStream.php', + 'GuzzleHttp\\Psr7\\LimitStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/LimitStream.php', + 'GuzzleHttp\\Psr7\\Message' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Message.php', + 'GuzzleHttp\\Psr7\\MessageTrait' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MessageTrait.php', + 'GuzzleHttp\\Psr7\\MimeType' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MimeType.php', + 'GuzzleHttp\\Psr7\\MultipartStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MultipartStream.php', + 'GuzzleHttp\\Psr7\\NoSeekStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/NoSeekStream.php', + 'GuzzleHttp\\Psr7\\PumpStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/PumpStream.php', + 'GuzzleHttp\\Psr7\\Query' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Query.php', + 'GuzzleHttp\\Psr7\\Request' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Request.php', + 'GuzzleHttp\\Psr7\\Response' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Response.php', + 'GuzzleHttp\\Psr7\\Rfc7230' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Rfc7230.php', + 'GuzzleHttp\\Psr7\\ServerRequest' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/ServerRequest.php', + 'GuzzleHttp\\Psr7\\Stream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Stream.php', + 'GuzzleHttp\\Psr7\\StreamDecoratorTrait' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/StreamDecoratorTrait.php', + 'GuzzleHttp\\Psr7\\StreamWrapper' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/StreamWrapper.php', + 'GuzzleHttp\\Psr7\\UploadedFile' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UploadedFile.php', + 'GuzzleHttp\\Psr7\\Uri' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Uri.php', + 'GuzzleHttp\\Psr7\\UriComparator' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UriComparator.php', + 'GuzzleHttp\\Psr7\\UriNormalizer' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UriNormalizer.php', + 'GuzzleHttp\\Psr7\\UriResolver' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UriResolver.php', + 'GuzzleHttp\\Psr7\\Utils' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Utils.php', + 'GuzzleHttp\\RedirectMiddleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RedirectMiddleware.php', + 'GuzzleHttp\\RequestOptions' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RequestOptions.php', + 'GuzzleHttp\\RetryMiddleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RetryMiddleware.php', + 'GuzzleHttp\\TransferStats' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/TransferStats.php', + 'GuzzleHttp\\Utils' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Utils.php', 'HTMLBulkExport' => __DIR__ . '/../..' . '/core/htmlbulkexport.class.inc.php', 'HTMLDOMSanitizer' => __DIR__ . '/../..' . '/core/htmlsanitizer.class.inc.php', 'HTMLNullSanitizer' => __DIR__ . '/../..' . '/core/htmlsanitizer.class.inc.php', @@ -726,8 +1007,350 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'ItopCounter' => __DIR__ . '/../..' . '/core/counter.class.inc.php', 'JSButtonItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', 'JSPopupMenuItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', + 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', 'JsonPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/JsonPage.php', 'KeyValueStore' => __DIR__ . '/../..' . '/core/counter.class.inc.php', + 'Laminas\\Loader\\AutoloaderFactory' => __DIR__ . '/..' . '/laminas/laminas-loader/src/AutoloaderFactory.php', + 'Laminas\\Loader\\ClassMapAutoloader' => __DIR__ . '/..' . '/laminas/laminas-loader/src/ClassMapAutoloader.php', + 'Laminas\\Loader\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/laminas/laminas-loader/src/Exception/BadMethodCallException.php', + 'Laminas\\Loader\\Exception\\DomainException' => __DIR__ . '/..' . '/laminas/laminas-loader/src/Exception/DomainException.php', + 'Laminas\\Loader\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-loader/src/Exception/ExceptionInterface.php', + 'Laminas\\Loader\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-loader/src/Exception/InvalidArgumentException.php', + 'Laminas\\Loader\\Exception\\InvalidPathException' => __DIR__ . '/..' . '/laminas/laminas-loader/src/Exception/InvalidPathException.php', + 'Laminas\\Loader\\Exception\\MissingResourceNamespaceException' => __DIR__ . '/..' . '/laminas/laminas-loader/src/Exception/MissingResourceNamespaceException.php', + 'Laminas\\Loader\\Exception\\PluginLoaderException' => __DIR__ . '/..' . '/laminas/laminas-loader/src/Exception/PluginLoaderException.php', + 'Laminas\\Loader\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-loader/src/Exception/RuntimeException.php', + 'Laminas\\Loader\\Exception\\SecurityException' => __DIR__ . '/..' . '/laminas/laminas-loader/src/Exception/SecurityException.php', + 'Laminas\\Loader\\ModuleAutoloader' => __DIR__ . '/..' . '/laminas/laminas-loader/src/ModuleAutoloader.php', + 'Laminas\\Loader\\PluginClassLoader' => __DIR__ . '/..' . '/laminas/laminas-loader/src/PluginClassLoader.php', + 'Laminas\\Loader\\PluginClassLocator' => __DIR__ . '/..' . '/laminas/laminas-loader/src/PluginClassLocator.php', + 'Laminas\\Loader\\ShortNameLocator' => __DIR__ . '/..' . '/laminas/laminas-loader/src/ShortNameLocator.php', + 'Laminas\\Loader\\SplAutoloader' => __DIR__ . '/..' . '/laminas/laminas-loader/src/SplAutoloader.php', + 'Laminas\\Loader\\StandardAutoloader' => __DIR__ . '/..' . '/laminas/laminas-loader/src/StandardAutoloader.php', + 'Laminas\\Mail\\Address' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Address.php', + 'Laminas\\Mail\\AddressList' => __DIR__ . '/..' . '/laminas/laminas-mail/src/AddressList.php', + 'Laminas\\Mail\\Address\\AddressInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Address/AddressInterface.php', + 'Laminas\\Mail\\ConfigProvider' => __DIR__ . '/..' . '/laminas/laminas-mail/src/ConfigProvider.php', + 'Laminas\\Mail\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Exception/BadMethodCallException.php', + 'Laminas\\Mail\\Exception\\DomainException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Exception/DomainException.php', + 'Laminas\\Mail\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Exception\\OutOfBoundsException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Exception/OutOfBoundsException.php', + 'Laminas\\Mail\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Exception/RuntimeException.php', + 'Laminas\\Mail\\Header\\AbstractAddressList' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/AbstractAddressList.php', + 'Laminas\\Mail\\Header\\Bcc' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Bcc.php', + 'Laminas\\Mail\\Header\\Cc' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Cc.php', + 'Laminas\\Mail\\Header\\ContentDisposition' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/ContentDisposition.php', + 'Laminas\\Mail\\Header\\ContentTransferEncoding' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/ContentTransferEncoding.php', + 'Laminas\\Mail\\Header\\ContentType' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/ContentType.php', + 'Laminas\\Mail\\Header\\Date' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Date.php', + 'Laminas\\Mail\\Header\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Exception/BadMethodCallException.php', + 'Laminas\\Mail\\Header\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Header\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Header\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Exception/RuntimeException.php', + 'Laminas\\Mail\\Header\\From' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/From.php', + 'Laminas\\Mail\\Header\\GenericHeader' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/GenericHeader.php', + 'Laminas\\Mail\\Header\\GenericMultiHeader' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/GenericMultiHeader.php', + 'Laminas\\Mail\\Header\\HeaderInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/HeaderInterface.php', + 'Laminas\\Mail\\Header\\HeaderLoader' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/HeaderLoader.php', + 'Laminas\\Mail\\Header\\HeaderLocator' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/HeaderLocator.php', + 'Laminas\\Mail\\Header\\HeaderLocatorInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/HeaderLocatorInterface.php', + 'Laminas\\Mail\\Header\\HeaderName' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/HeaderName.php', + 'Laminas\\Mail\\Header\\HeaderValue' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/HeaderValue.php', + 'Laminas\\Mail\\Header\\HeaderWrap' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/HeaderWrap.php', + 'Laminas\\Mail\\Header\\IdentificationField' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/IdentificationField.php', + 'Laminas\\Mail\\Header\\InReplyTo' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/InReplyTo.php', + 'Laminas\\Mail\\Header\\ListParser' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/ListParser.php', + 'Laminas\\Mail\\Header\\MessageId' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/MessageId.php', + 'Laminas\\Mail\\Header\\MimeVersion' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/MimeVersion.php', + 'Laminas\\Mail\\Header\\MultipleHeadersInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/MultipleHeadersInterface.php', + 'Laminas\\Mail\\Header\\Received' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Received.php', + 'Laminas\\Mail\\Header\\References' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/References.php', + 'Laminas\\Mail\\Header\\ReplyTo' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/ReplyTo.php', + 'Laminas\\Mail\\Header\\Sender' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Sender.php', + 'Laminas\\Mail\\Header\\StructuredInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/StructuredInterface.php', + 'Laminas\\Mail\\Header\\Subject' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/Subject.php', + 'Laminas\\Mail\\Header\\To' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/To.php', + 'Laminas\\Mail\\Header\\UnstructuredInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Header/UnstructuredInterface.php', + 'Laminas\\Mail\\Headers' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Headers.php', + 'Laminas\\Mail\\Message' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Message.php', + 'Laminas\\Mail\\MessageFactory' => __DIR__ . '/..' . '/laminas/laminas-mail/src/MessageFactory.php', + 'Laminas\\Mail\\Module' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Module.php', + 'Laminas\\Mail\\Protocol\\AbstractProtocol' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/AbstractProtocol.php', + 'Laminas\\Mail\\Protocol\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Protocol\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Protocol\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/Exception/RuntimeException.php', + 'Laminas\\Mail\\Protocol\\Imap' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/Imap.php', + 'Laminas\\Mail\\Protocol\\Pop3' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/Pop3.php', + 'Laminas\\Mail\\Protocol\\ProtocolTrait' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/ProtocolTrait.php', + 'Laminas\\Mail\\Protocol\\Smtp' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/Smtp.php', + 'Laminas\\Mail\\Protocol\\SmtpPluginManager' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/SmtpPluginManager.php', + 'Laminas\\Mail\\Protocol\\SmtpPluginManagerFactory' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/SmtpPluginManagerFactory.php', + 'Laminas\\Mail\\Protocol\\Smtp\\Auth\\Crammd5' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/Smtp/Auth/Crammd5.php', + 'Laminas\\Mail\\Protocol\\Smtp\\Auth\\Login' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/Smtp/Auth/Login.php', + 'Laminas\\Mail\\Protocol\\Smtp\\Auth\\Oauth' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/Smtp/SmtpOAuthLogin.php', + 'Laminas\\Mail\\Protocol\\Smtp\\Auth\\Plain' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Protocol/Smtp/Auth/Plain.php', + 'Laminas\\Mail\\Storage' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage.php', + 'Laminas\\Mail\\Storage\\AbstractStorage' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/AbstractStorage.php', + 'Laminas\\Mail\\Storage\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Storage\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Storage\\Exception\\OutOfBoundsException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Exception/OutOfBoundsException.php', + 'Laminas\\Mail\\Storage\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Exception/RuntimeException.php', + 'Laminas\\Mail\\Storage\\Folder' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Folder.php', + 'Laminas\\Mail\\Storage\\Folder\\FolderInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Folder/FolderInterface.php', + 'Laminas\\Mail\\Storage\\Folder\\Maildir' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Folder/Maildir.php', + 'Laminas\\Mail\\Storage\\Folder\\Mbox' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Folder/Mbox.php', + 'Laminas\\Mail\\Storage\\Imap' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Imap.php', + 'Laminas\\Mail\\Storage\\Maildir' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Maildir.php', + 'Laminas\\Mail\\Storage\\Mbox' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Mbox.php', + 'Laminas\\Mail\\Storage\\Message' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Message.php', + 'Laminas\\Mail\\Storage\\Message\\File' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Message/File.php', + 'Laminas\\Mail\\Storage\\Message\\MessageInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Message/MessageInterface.php', + 'Laminas\\Mail\\Storage\\ParamsNormalizer' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/ParamsNormalizer.php', + 'Laminas\\Mail\\Storage\\Part' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Part.php', + 'Laminas\\Mail\\Storage\\Part\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Part/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Storage\\Part\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Part/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Storage\\Part\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Part/Exception/RuntimeException.php', + 'Laminas\\Mail\\Storage\\Part\\File' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Part/File.php', + 'Laminas\\Mail\\Storage\\Part\\PartInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Part/PartInterface.php', + 'Laminas\\Mail\\Storage\\Pop3' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Pop3.php', + 'Laminas\\Mail\\Storage\\Writable\\Maildir' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Writable/Maildir.php', + 'Laminas\\Mail\\Storage\\Writable\\WritableInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Storage/Writable/WritableInterface.php', + 'Laminas\\Mail\\Transport\\Envelope' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/Envelope.php', + 'Laminas\\Mail\\Transport\\Exception\\DomainException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/Exception/DomainException.php', + 'Laminas\\Mail\\Transport\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/Exception/ExceptionInterface.php', + 'Laminas\\Mail\\Transport\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/Exception/InvalidArgumentException.php', + 'Laminas\\Mail\\Transport\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/Exception/RuntimeException.php', + 'Laminas\\Mail\\Transport\\Factory' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/Factory.php', + 'Laminas\\Mail\\Transport\\File' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/File.php', + 'Laminas\\Mail\\Transport\\FileOptions' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/FileOptions.php', + 'Laminas\\Mail\\Transport\\InMemory' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/InMemory.php', + 'Laminas\\Mail\\Transport\\Sendmail' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/Sendmail.php', + 'Laminas\\Mail\\Transport\\Smtp' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/Smtp.php', + 'Laminas\\Mail\\Transport\\SmtpOptions' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/SmtpOptions.php', + 'Laminas\\Mail\\Transport\\TransportInterface' => __DIR__ . '/..' . '/laminas/laminas-mail/src/Transport/TransportInterface.php', + 'Laminas\\Mime\\Decode' => __DIR__ . '/..' . '/laminas/laminas-mime/src/Decode.php', + 'Laminas\\Mime\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-mime/src/Exception/ExceptionInterface.php', + 'Laminas\\Mime\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-mime/src/Exception/InvalidArgumentException.php', + 'Laminas\\Mime\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-mime/src/Exception/RuntimeException.php', + 'Laminas\\Mime\\Message' => __DIR__ . '/..' . '/laminas/laminas-mime/src/Message.php', + 'Laminas\\Mime\\Mime' => __DIR__ . '/..' . '/laminas/laminas-mime/src/Mime.php', + 'Laminas\\Mime\\Part' => __DIR__ . '/..' . '/laminas/laminas-mime/src/Part.php', + 'Laminas\\ServiceManager\\AbstractFactoryInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/AbstractFactoryInterface.php', + 'Laminas\\ServiceManager\\AbstractFactory\\ConfigAbstractFactory' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/AbstractFactory/ConfigAbstractFactory.php', + 'Laminas\\ServiceManager\\AbstractFactory\\ReflectionBasedAbstractFactory' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/AbstractFactory/ReflectionBasedAbstractFactory.php', + 'Laminas\\ServiceManager\\AbstractPluginManager' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/AbstractPluginManager.php', + 'Laminas\\ServiceManager\\Config' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Config.php', + 'Laminas\\ServiceManager\\ConfigInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/ConfigInterface.php', + 'Laminas\\ServiceManager\\DelegatorFactoryInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/DelegatorFactoryInterface.php', + 'Laminas\\ServiceManager\\Exception\\ContainerModificationsNotAllowedException' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Exception/ContainerModificationsNotAllowedException.php', + 'Laminas\\ServiceManager\\Exception\\CyclicAliasException' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Exception/CyclicAliasException.php', + 'Laminas\\ServiceManager\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Exception/ExceptionInterface.php', + 'Laminas\\ServiceManager\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Exception/InvalidArgumentException.php', + 'Laminas\\ServiceManager\\Exception\\InvalidServiceException' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Exception/InvalidServiceException.php', + 'Laminas\\ServiceManager\\Exception\\ServiceNotCreatedException' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Exception/ServiceNotCreatedException.php', + 'Laminas\\ServiceManager\\Exception\\ServiceNotFoundException' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Exception/ServiceNotFoundException.php', + 'Laminas\\ServiceManager\\FactoryInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/FactoryInterface.php', + 'Laminas\\ServiceManager\\Factory\\AbstractFactoryInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Factory/AbstractFactoryInterface.php', + 'Laminas\\ServiceManager\\Factory\\DelegatorFactoryInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Factory/DelegatorFactoryInterface.php', + 'Laminas\\ServiceManager\\Factory\\FactoryInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Factory/FactoryInterface.php', + 'Laminas\\ServiceManager\\Factory\\InvokableFactory' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Factory/InvokableFactory.php', + 'Laminas\\ServiceManager\\InitializerInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/InitializerInterface.php', + 'Laminas\\ServiceManager\\Initializer\\InitializerInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Initializer/InitializerInterface.php', + 'Laminas\\ServiceManager\\PluginManagerInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/PluginManagerInterface.php', + 'Laminas\\ServiceManager\\Proxy\\LazyServiceFactory' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Proxy/LazyServiceFactory.php', + 'Laminas\\ServiceManager\\ServiceLocatorInterface' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php', + 'Laminas\\ServiceManager\\ServiceManager' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/ServiceManager.php', + 'Laminas\\ServiceManager\\Tool\\ConfigDumper' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php', + 'Laminas\\ServiceManager\\Tool\\ConfigDumperCommand' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php', + 'Laminas\\ServiceManager\\Tool\\FactoryCreator' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Tool/FactoryCreator.php', + 'Laminas\\ServiceManager\\Tool\\FactoryCreatorCommand' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/Tool/FactoryCreatorCommand.php', + 'Laminas\\Stdlib\\AbstractOptions' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/AbstractOptions.php', + 'Laminas\\Stdlib\\ArrayObject' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ArrayObject.php', + 'Laminas\\Stdlib\\ArraySerializableInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ArraySerializableInterface.php', + 'Laminas\\Stdlib\\ArrayStack' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ArrayStack.php', + 'Laminas\\Stdlib\\ArrayUtils' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ArrayUtils.php', + 'Laminas\\Stdlib\\ArrayUtils\\MergeRemoveKey' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ArrayUtils/MergeRemoveKey.php', + 'Laminas\\Stdlib\\ArrayUtils\\MergeReplaceKey' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKey.php', + 'Laminas\\Stdlib\\ArrayUtils\\MergeReplaceKeyInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php', + 'Laminas\\Stdlib\\ConsoleHelper' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ConsoleHelper.php', + 'Laminas\\Stdlib\\DispatchableInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/DispatchableInterface.php', + 'Laminas\\Stdlib\\ErrorHandler' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ErrorHandler.php', + 'Laminas\\Stdlib\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Exception/BadMethodCallException.php', + 'Laminas\\Stdlib\\Exception\\DomainException' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Exception/DomainException.php', + 'Laminas\\Stdlib\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Exception/ExceptionInterface.php', + 'Laminas\\Stdlib\\Exception\\ExtensionNotLoadedException' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Exception/ExtensionNotLoadedException.php', + 'Laminas\\Stdlib\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Exception/InvalidArgumentException.php', + 'Laminas\\Stdlib\\Exception\\LogicException' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Exception/LogicException.php', + 'Laminas\\Stdlib\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Exception/RuntimeException.php', + 'Laminas\\Stdlib\\FastPriorityQueue' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/FastPriorityQueue.php', + 'Laminas\\Stdlib\\Glob' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Glob.php', + 'Laminas\\Stdlib\\Guard\\AllGuardsTrait' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Guard/AllGuardsTrait.php', + 'Laminas\\Stdlib\\Guard\\ArrayOrTraversableGuardTrait' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Guard/ArrayOrTraversableGuardTrait.php', + 'Laminas\\Stdlib\\Guard\\EmptyGuardTrait' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Guard/EmptyGuardTrait.php', + 'Laminas\\Stdlib\\Guard\\NullGuardTrait' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Guard/NullGuardTrait.php', + 'Laminas\\Stdlib\\InitializableInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/InitializableInterface.php', + 'Laminas\\Stdlib\\JsonSerializable' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/JsonSerializable.php', + 'Laminas\\Stdlib\\Message' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Message.php', + 'Laminas\\Stdlib\\MessageInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/MessageInterface.php', + 'Laminas\\Stdlib\\ParameterObjectInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ParameterObjectInterface.php', + 'Laminas\\Stdlib\\Parameters' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Parameters.php', + 'Laminas\\Stdlib\\ParametersInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ParametersInterface.php', + 'Laminas\\Stdlib\\PriorityList' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/PriorityList.php', + 'Laminas\\Stdlib\\PriorityQueue' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/PriorityQueue.php', + 'Laminas\\Stdlib\\Request' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Request.php', + 'Laminas\\Stdlib\\RequestInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/RequestInterface.php', + 'Laminas\\Stdlib\\Response' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/Response.php', + 'Laminas\\Stdlib\\ResponseInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/ResponseInterface.php', + 'Laminas\\Stdlib\\SplPriorityQueue' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/SplPriorityQueue.php', + 'Laminas\\Stdlib\\SplQueue' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/SplQueue.php', + 'Laminas\\Stdlib\\SplStack' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/SplStack.php', + 'Laminas\\Stdlib\\StringUtils' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/StringUtils.php', + 'Laminas\\Stdlib\\StringWrapper\\AbstractStringWrapper' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/StringWrapper/AbstractStringWrapper.php', + 'Laminas\\Stdlib\\StringWrapper\\Iconv' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/StringWrapper/Iconv.php', + 'Laminas\\Stdlib\\StringWrapper\\Intl' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/StringWrapper/Intl.php', + 'Laminas\\Stdlib\\StringWrapper\\MbString' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/StringWrapper/MbString.php', + 'Laminas\\Stdlib\\StringWrapper\\Native' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/StringWrapper/Native.php', + 'Laminas\\Stdlib\\StringWrapper\\StringWrapperInterface' => __DIR__ . '/..' . '/laminas/laminas-stdlib/src/StringWrapper/StringWrapperInterface.php', + 'Laminas\\Validator\\AbstractValidator' => __DIR__ . '/..' . '/laminas/laminas-validator/src/AbstractValidator.php', + 'Laminas\\Validator\\Barcode' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode.php', + 'Laminas\\Validator\\Barcode\\AbstractAdapter' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/AbstractAdapter.php', + 'Laminas\\Validator\\Barcode\\AdapterInterface' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/AdapterInterface.php', + 'Laminas\\Validator\\Barcode\\Codabar' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Codabar.php', + 'Laminas\\Validator\\Barcode\\Code128' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Code128.php', + 'Laminas\\Validator\\Barcode\\Code25' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Code25.php', + 'Laminas\\Validator\\Barcode\\Code25interleaved' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Code25interleaved.php', + 'Laminas\\Validator\\Barcode\\Code39' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Code39.php', + 'Laminas\\Validator\\Barcode\\Code39ext' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Code39ext.php', + 'Laminas\\Validator\\Barcode\\Code93' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Code93.php', + 'Laminas\\Validator\\Barcode\\Code93ext' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Code93ext.php', + 'Laminas\\Validator\\Barcode\\Ean12' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Ean12.php', + 'Laminas\\Validator\\Barcode\\Ean13' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Ean13.php', + 'Laminas\\Validator\\Barcode\\Ean14' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Ean14.php', + 'Laminas\\Validator\\Barcode\\Ean18' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Ean18.php', + 'Laminas\\Validator\\Barcode\\Ean2' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Ean2.php', + 'Laminas\\Validator\\Barcode\\Ean5' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Ean5.php', + 'Laminas\\Validator\\Barcode\\Ean8' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Ean8.php', + 'Laminas\\Validator\\Barcode\\Gtin12' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Gtin12.php', + 'Laminas\\Validator\\Barcode\\Gtin13' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Gtin13.php', + 'Laminas\\Validator\\Barcode\\Gtin14' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Gtin14.php', + 'Laminas\\Validator\\Barcode\\Identcode' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Identcode.php', + 'Laminas\\Validator\\Barcode\\Intelligentmail' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Intelligentmail.php', + 'Laminas\\Validator\\Barcode\\Issn' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Issn.php', + 'Laminas\\Validator\\Barcode\\Itf14' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Itf14.php', + 'Laminas\\Validator\\Barcode\\Leitcode' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Leitcode.php', + 'Laminas\\Validator\\Barcode\\Planet' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Planet.php', + 'Laminas\\Validator\\Barcode\\Postnet' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Postnet.php', + 'Laminas\\Validator\\Barcode\\Royalmail' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Royalmail.php', + 'Laminas\\Validator\\Barcode\\Sscc' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Sscc.php', + 'Laminas\\Validator\\Barcode\\Upca' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Upca.php', + 'Laminas\\Validator\\Barcode\\Upce' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Barcode/Upce.php', + 'Laminas\\Validator\\Between' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Between.php', + 'Laminas\\Validator\\Bitwise' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Bitwise.php', + 'Laminas\\Validator\\BusinessIdentifierCode' => __DIR__ . '/..' . '/laminas/laminas-validator/src/BusinessIdentifierCode.php', + 'Laminas\\Validator\\Callback' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Callback.php', + 'Laminas\\Validator\\ConfigProvider' => __DIR__ . '/..' . '/laminas/laminas-validator/src/ConfigProvider.php', + 'Laminas\\Validator\\CreditCard' => __DIR__ . '/..' . '/laminas/laminas-validator/src/CreditCard.php', + 'Laminas\\Validator\\Csrf' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Csrf.php', + 'Laminas\\Validator\\Date' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Date.php', + 'Laminas\\Validator\\DateStep' => __DIR__ . '/..' . '/laminas/laminas-validator/src/DateStep.php', + 'Laminas\\Validator\\Db\\AbstractDb' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Db/AbstractDb.php', + 'Laminas\\Validator\\Db\\NoRecordExists' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Db/NoRecordExists.php', + 'Laminas\\Validator\\Db\\RecordExists' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Db/RecordExists.php', + 'Laminas\\Validator\\Digits' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Digits.php', + 'Laminas\\Validator\\EmailAddress' => __DIR__ . '/..' . '/laminas/laminas-validator/src/EmailAddress.php', + 'Laminas\\Validator\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Exception/BadMethodCallException.php', + 'Laminas\\Validator\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Exception/ExceptionInterface.php', + 'Laminas\\Validator\\Exception\\ExtensionNotLoadedException' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Exception/ExtensionNotLoadedException.php', + 'Laminas\\Validator\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Exception/InvalidArgumentException.php', + 'Laminas\\Validator\\Exception\\InvalidMagicMimeFileException' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Exception/InvalidMagicMimeFileException.php', + 'Laminas\\Validator\\Exception\\RuntimeException' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Exception/RuntimeException.php', + 'Laminas\\Validator\\Explode' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Explode.php', + 'Laminas\\Validator\\File\\Count' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/Count.php', + 'Laminas\\Validator\\File\\Crc32' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/Crc32.php', + 'Laminas\\Validator\\File\\ExcludeExtension' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/ExcludeExtension.php', + 'Laminas\\Validator\\File\\ExcludeMimeType' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/ExcludeMimeType.php', + 'Laminas\\Validator\\File\\Exists' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/Exists.php', + 'Laminas\\Validator\\File\\Extension' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/Extension.php', + 'Laminas\\Validator\\File\\FileInformationTrait' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/FileInformationTrait.php', + 'Laminas\\Validator\\File\\FilesSize' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/FilesSize.php', + 'Laminas\\Validator\\File\\Hash' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/Hash.php', + 'Laminas\\Validator\\File\\ImageSize' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/ImageSize.php', + 'Laminas\\Validator\\File\\IsCompressed' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/IsCompressed.php', + 'Laminas\\Validator\\File\\IsImage' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/IsImage.php', + 'Laminas\\Validator\\File\\Md5' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/Md5.php', + 'Laminas\\Validator\\File\\MimeType' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/MimeType.php', + 'Laminas\\Validator\\File\\NotExists' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/NotExists.php', + 'Laminas\\Validator\\File\\Sha1' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/Sha1.php', + 'Laminas\\Validator\\File\\Size' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/Size.php', + 'Laminas\\Validator\\File\\Upload' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/Upload.php', + 'Laminas\\Validator\\File\\UploadFile' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/UploadFile.php', + 'Laminas\\Validator\\File\\WordCount' => __DIR__ . '/..' . '/laminas/laminas-validator/src/File/WordCount.php', + 'Laminas\\Validator\\GpsPoint' => __DIR__ . '/..' . '/laminas/laminas-validator/src/GpsPoint.php', + 'Laminas\\Validator\\GreaterThan' => __DIR__ . '/..' . '/laminas/laminas-validator/src/GreaterThan.php', + 'Laminas\\Validator\\Hex' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Hex.php', + 'Laminas\\Validator\\Hostname' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Hostname.php', + 'Laminas\\Validator\\Iban' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Iban.php', + 'Laminas\\Validator\\Identical' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Identical.php', + 'Laminas\\Validator\\InArray' => __DIR__ . '/..' . '/laminas/laminas-validator/src/InArray.php', + 'Laminas\\Validator\\Ip' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Ip.php', + 'Laminas\\Validator\\IsCountable' => __DIR__ . '/..' . '/laminas/laminas-validator/src/IsCountable.php', + 'Laminas\\Validator\\IsInstanceOf' => __DIR__ . '/..' . '/laminas/laminas-validator/src/IsInstanceOf.php', + 'Laminas\\Validator\\Isbn' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Isbn.php', + 'Laminas\\Validator\\Isbn\\Isbn10' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Isbn/Isbn10.php', + 'Laminas\\Validator\\Isbn\\Isbn13' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Isbn/Isbn13.php', + 'Laminas\\Validator\\LessThan' => __DIR__ . '/..' . '/laminas/laminas-validator/src/LessThan.php', + 'Laminas\\Validator\\Module' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Module.php', + 'Laminas\\Validator\\NotEmpty' => __DIR__ . '/..' . '/laminas/laminas-validator/src/NotEmpty.php', + 'Laminas\\Validator\\Regex' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Regex.php', + 'Laminas\\Validator\\Sitemap\\Changefreq' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Sitemap/Changefreq.php', + 'Laminas\\Validator\\Sitemap\\Lastmod' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Sitemap/Lastmod.php', + 'Laminas\\Validator\\Sitemap\\Loc' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Sitemap/Loc.php', + 'Laminas\\Validator\\Sitemap\\Priority' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Sitemap/Priority.php', + 'Laminas\\Validator\\StaticValidator' => __DIR__ . '/..' . '/laminas/laminas-validator/src/StaticValidator.php', + 'Laminas\\Validator\\Step' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Step.php', + 'Laminas\\Validator\\StringLength' => __DIR__ . '/..' . '/laminas/laminas-validator/src/StringLength.php', + 'Laminas\\Validator\\Timezone' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Timezone.php', + 'Laminas\\Validator\\Translator\\TranslatorAwareInterface' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Translator/TranslatorAwareInterface.php', + 'Laminas\\Validator\\Translator\\TranslatorInterface' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Translator/TranslatorInterface.php', + 'Laminas\\Validator\\UndisclosedPassword' => __DIR__ . '/..' . '/laminas/laminas-validator/src/UndisclosedPassword.php', + 'Laminas\\Validator\\Uri' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Uri.php', + 'Laminas\\Validator\\Uuid' => __DIR__ . '/..' . '/laminas/laminas-validator/src/Uuid.php', + 'Laminas\\Validator\\ValidatorChain' => __DIR__ . '/..' . '/laminas/laminas-validator/src/ValidatorChain.php', + 'Laminas\\Validator\\ValidatorInterface' => __DIR__ . '/..' . '/laminas/laminas-validator/src/ValidatorInterface.php', + 'Laminas\\Validator\\ValidatorPluginManager' => __DIR__ . '/..' . '/laminas/laminas-validator/src/ValidatorPluginManager.php', + 'Laminas\\Validator\\ValidatorPluginManagerAwareInterface' => __DIR__ . '/..' . '/laminas/laminas-validator/src/ValidatorPluginManagerAwareInterface.php', + 'Laminas\\Validator\\ValidatorPluginManagerFactory' => __DIR__ . '/..' . '/laminas/laminas-validator/src/ValidatorPluginManagerFactory.php', + 'Laminas\\Validator\\ValidatorProviderInterface' => __DIR__ . '/..' . '/laminas/laminas-validator/src/ValidatorProviderInterface.php', + 'League\\OAuth2\\Client\\Exception\\HostedDomainException' => __DIR__ . '/..' . '/league/oauth2-google/src/Exception/HostedDomainException.php', + 'League\\OAuth2\\Client\\Grant\\AbstractGrant' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/AbstractGrant.php', + 'League\\OAuth2\\Client\\Grant\\AuthorizationCode' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/AuthorizationCode.php', + 'League\\OAuth2\\Client\\Grant\\ClientCredentials' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/ClientCredentials.php', + 'League\\OAuth2\\Client\\Grant\\Exception\\InvalidGrantException' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php', + 'League\\OAuth2\\Client\\Grant\\GrantFactory' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/GrantFactory.php', + 'League\\OAuth2\\Client\\Grant\\Password' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/Password.php', + 'League\\OAuth2\\Client\\Grant\\RefreshToken' => __DIR__ . '/..' . '/league/oauth2-client/src/Grant/RefreshToken.php', + 'League\\OAuth2\\Client\\OptionProvider\\HttpBasicAuthOptionProvider' => __DIR__ . '/..' . '/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php', + 'League\\OAuth2\\Client\\OptionProvider\\OptionProviderInterface' => __DIR__ . '/..' . '/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php', + 'League\\OAuth2\\Client\\OptionProvider\\PostAuthOptionProvider' => __DIR__ . '/..' . '/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php', + 'League\\OAuth2\\Client\\Provider\\AbstractProvider' => __DIR__ . '/..' . '/league/oauth2-client/src/Provider/AbstractProvider.php', + 'League\\OAuth2\\Client\\Provider\\Exception\\IdentityProviderException' => __DIR__ . '/..' . '/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php', + 'League\\OAuth2\\Client\\Provider\\GenericProvider' => __DIR__ . '/..' . '/league/oauth2-client/src/Provider/GenericProvider.php', + 'League\\OAuth2\\Client\\Provider\\GenericResourceOwner' => __DIR__ . '/..' . '/league/oauth2-client/src/Provider/GenericResourceOwner.php', + 'League\\OAuth2\\Client\\Provider\\Google' => __DIR__ . '/..' . '/league/oauth2-google/src/Provider/Google.php', + 'League\\OAuth2\\Client\\Provider\\GoogleUser' => __DIR__ . '/..' . '/league/oauth2-google/src/Provider/GoogleUser.php', + 'League\\OAuth2\\Client\\Provider\\ResourceOwnerInterface' => __DIR__ . '/..' . '/league/oauth2-client/src/Provider/ResourceOwnerInterface.php', + 'League\\OAuth2\\Client\\Token\\AccessToken' => __DIR__ . '/..' . '/league/oauth2-client/src/Token/AccessToken.php', + 'League\\OAuth2\\Client\\Token\\AccessTokenInterface' => __DIR__ . '/..' . '/league/oauth2-client/src/Token/AccessTokenInterface.php', + 'League\\OAuth2\\Client\\Token\\ResourceOwnerAccessTokenInterface' => __DIR__ . '/..' . '/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php', + 'League\\OAuth2\\Client\\Tool\\ArrayAccessorTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/ArrayAccessorTrait.php', + 'League\\OAuth2\\Client\\Tool\\BearerAuthorizationTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php', + 'League\\OAuth2\\Client\\Tool\\GuardedPropertyTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/GuardedPropertyTrait.php', + 'League\\OAuth2\\Client\\Tool\\MacAuthorizationTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/MacAuthorizationTrait.php', + 'League\\OAuth2\\Client\\Tool\\ProviderRedirectTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/ProviderRedirectTrait.php', + 'League\\OAuth2\\Client\\Tool\\QueryBuilderTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/QueryBuilderTrait.php', + 'League\\OAuth2\\Client\\Tool\\RequestFactory' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/RequestFactory.php', + 'League\\OAuth2\\Client\\Tool\\RequiredParameterTrait' => __DIR__ . '/..' . '/league/oauth2-client/src/Tool/RequiredParameterTrait.php', 'ListExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php', 'ListOqlExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php', 'LogAPI' => __DIR__ . '/../..' . '/core/log.class.inc.php', @@ -762,6 +1385,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'NewObjectMenuNode' => __DIR__ . '/../..' . '/application/menunode.class.inc.php', 'NewsroomProviderBase' => __DIR__ . '/../..' . '/application/newsroomprovider.class.inc.php', 'NiceWebPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/NiceWebPage.php', + 'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', 'NotYetEvaluatedExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php', 'OQLActualClassTreeResolver' => __DIR__ . '/../..' . '/core/oqlactualclasstreeresolver.class.inc.php', 'OQLClassNode' => __DIR__ . '/../..' . '/core/oqlclassnode.class.inc.php', @@ -802,21 +1426,24 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'PEAR_ErrorStack' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/PEAR/ErrorStack.php', 'PEAR_Exception' => __DIR__ . '/..' . '/pear/pear_exception/PEAR/Exception.php', 'Page' => __DIR__ . '/../..' . '/sources/Application/WebPage/Page.php', - 'ParseError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ParseError.php', - 'Pelago\\Emogrifier' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier.php', - 'Pelago\\Emogrifier\\CssInliner' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/CssInliner.php', - 'Pelago\\Emogrifier\\HtmlProcessor\\AbstractHtmlProcessor' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/AbstractHtmlProcessor.php', - 'Pelago\\Emogrifier\\HtmlProcessor\\CssToAttributeConverter' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/CssToAttributeConverter.php', - 'Pelago\\Emogrifier\\HtmlProcessor\\HtmlNormalizer' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/HtmlNormalizer.php', - 'Pelago\\Emogrifier\\HtmlProcessor\\HtmlPruner' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/HtmlProcessor/HtmlPruner.php', - 'Pelago\\Emogrifier\\Utilities\\ArrayIntersector' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/Utilities/ArrayIntersector.php', - 'Pelago\\Emogrifier\\Utilities\\CssConcatenator' => __DIR__ . '/..' . '/pelago/emogrifier/src/Emogrifier/Utilities/CssConcatenator.php', + 'Pelago\\Emogrifier\\Caching\\SimpleStringCache' => __DIR__ . '/..' . '/pelago/emogrifier/src/Caching/SimpleStringCache.php', + 'Pelago\\Emogrifier\\CssInliner' => __DIR__ . '/..' . '/pelago/emogrifier/src/CssInliner.php', + 'Pelago\\Emogrifier\\Css\\CssDocument' => __DIR__ . '/..' . '/pelago/emogrifier/src/Css/CssDocument.php', + 'Pelago\\Emogrifier\\Css\\StyleRule' => __DIR__ . '/..' . '/pelago/emogrifier/src/Css/StyleRule.php', + 'Pelago\\Emogrifier\\HtmlProcessor\\AbstractHtmlProcessor' => __DIR__ . '/..' . '/pelago/emogrifier/src/HtmlProcessor/AbstractHtmlProcessor.php', + 'Pelago\\Emogrifier\\HtmlProcessor\\CssToAttributeConverter' => __DIR__ . '/..' . '/pelago/emogrifier/src/HtmlProcessor/CssToAttributeConverter.php', + 'Pelago\\Emogrifier\\HtmlProcessor\\HtmlNormalizer' => __DIR__ . '/..' . '/pelago/emogrifier/src/HtmlProcessor/HtmlNormalizer.php', + 'Pelago\\Emogrifier\\HtmlProcessor\\HtmlPruner' => __DIR__ . '/..' . '/pelago/emogrifier/src/HtmlProcessor/HtmlPruner.php', + 'Pelago\\Emogrifier\\Utilities\\ArrayIntersector' => __DIR__ . '/..' . '/pelago/emogrifier/src/Utilities/ArrayIntersector.php', + 'Pelago\\Emogrifier\\Utilities\\CssConcatenator' => __DIR__ . '/..' . '/pelago/emogrifier/src/Utilities/CssConcatenator.php', 'PhpParser\\Builder' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder.php', 'PhpParser\\BuilderFactory' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/BuilderFactory.php', 'PhpParser\\BuilderHelpers' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/BuilderHelpers.php', 'PhpParser\\Builder\\ClassConst' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/ClassConst.php', 'PhpParser\\Builder\\Class_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Class_.php', 'PhpParser\\Builder\\Declaration' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Declaration.php', + 'PhpParser\\Builder\\EnumCase' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/EnumCase.php', + 'PhpParser\\Builder\\Enum_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Enum_.php', 'PhpParser\\Builder\\FunctionLike' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php', 'PhpParser\\Builder\\Function_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Function_.php', 'PhpParser\\Builder\\Interface_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder/Interface_.php', @@ -846,6 +1473,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'PhpParser\\Lexer\\TokenEmulator\\AttributeEmulator' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php', 'PhpParser\\Lexer\\TokenEmulator\\CoaleseEqualTokenEmulator' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/CoaleseEqualTokenEmulator.php', 'PhpParser\\Lexer\\TokenEmulator\\EnumTokenEmulator' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php', + 'PhpParser\\Lexer\\TokenEmulator\\ExplicitOctalEmulator' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php', 'PhpParser\\Lexer\\TokenEmulator\\FlexibleDocStringEmulator' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FlexibleDocStringEmulator.php', 'PhpParser\\Lexer\\TokenEmulator\\FnTokenEmulator' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.php', 'PhpParser\\Lexer\\TokenEmulator\\KeywordEmulator' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php', @@ -873,6 +1501,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'PhpParser\\Node\\Arg' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Arg.php', 'PhpParser\\Node\\Attribute' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Attribute.php', 'PhpParser\\Node\\AttributeGroup' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.php', + 'PhpParser\\Node\\ComplexType' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/ComplexType.php', 'PhpParser\\Node\\Const_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Const_.php', 'PhpParser\\Node\\Expr' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr.php', 'PhpParser\\Node\\Expr\\ArrayDimFetch' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayDimFetch.php', @@ -925,6 +1554,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'PhpParser\\Node\\Expr\\BinaryOp\\Spaceship' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php', 'PhpParser\\Node\\Expr\\BitwiseNot' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php', 'PhpParser\\Node\\Expr\\BooleanNot' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.php', + 'PhpParser\\Node\\Expr\\CallLike' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php', 'PhpParser\\Node\\Expr\\Cast' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php', 'PhpParser\\Node\\Expr\\Cast\\Array_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.php', 'PhpParser\\Node\\Expr\\Cast\\Bool_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.php', @@ -971,6 +1601,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'PhpParser\\Node\\Expr\\Yield_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.php', 'PhpParser\\Node\\FunctionLike' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php', 'PhpParser\\Node\\Identifier' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Identifier.php', + 'PhpParser\\Node\\IntersectionType' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/IntersectionType.php', 'PhpParser\\Node\\MatchArm' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/MatchArm.php', 'PhpParser\\Node\\Name' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Name.php', 'PhpParser\\Node\\Name\\FullyQualified' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.php', @@ -1044,6 +1675,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'PhpParser\\Node\\Stmt\\While_' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php', 'PhpParser\\Node\\UnionType' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/UnionType.php', 'PhpParser\\Node\\VarLikeIdentifier' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php', + 'PhpParser\\Node\\VariadicPlaceholder' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Node/VariadicPlaceholder.php', 'PhpParser\\Parser' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Parser.php', 'PhpParser\\ParserAbstract' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/ParserAbstract.php', 'PhpParser\\ParserFactory' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/ParserFactory.php', @@ -1053,6 +1685,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'PhpParser\\Parser\\Tokens' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Parser/Tokens.php', 'PhpParser\\PrettyPrinterAbstract' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php', 'PhpParser\\PrettyPrinter\\Standard' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php', + 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', 'PluginInstanciationManager' => __DIR__ . '/../..' . '/core/plugininstanciationmanager.class.inc.php', 'PluginManager' => __DIR__ . '/../..' . '/core/pluginmanager.class.inc.php', 'PortalDispatcher' => __DIR__ . '/../..' . '/application/portaldispatcher.class.inc.php', @@ -1068,6 +1701,26 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Psr\\Container\\ContainerExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerExceptionInterface.php', 'Psr\\Container\\ContainerInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerInterface.php', 'Psr\\Container\\NotFoundExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/NotFoundExceptionInterface.php', + 'Psr\\EventDispatcher\\EventDispatcherInterface' => __DIR__ . '/..' . '/psr/event-dispatcher/src/EventDispatcherInterface.php', + 'Psr\\EventDispatcher\\ListenerProviderInterface' => __DIR__ . '/..' . '/psr/event-dispatcher/src/ListenerProviderInterface.php', + 'Psr\\EventDispatcher\\StoppableEventInterface' => __DIR__ . '/..' . '/psr/event-dispatcher/src/StoppableEventInterface.php', + 'Psr\\Http\\Client\\ClientExceptionInterface' => __DIR__ . '/..' . '/psr/http-client/src/ClientExceptionInterface.php', + 'Psr\\Http\\Client\\ClientInterface' => __DIR__ . '/..' . '/psr/http-client/src/ClientInterface.php', + 'Psr\\Http\\Client\\NetworkExceptionInterface' => __DIR__ . '/..' . '/psr/http-client/src/NetworkExceptionInterface.php', + 'Psr\\Http\\Client\\RequestExceptionInterface' => __DIR__ . '/..' . '/psr/http-client/src/RequestExceptionInterface.php', + 'Psr\\Http\\Message\\MessageInterface' => __DIR__ . '/..' . '/psr/http-message/src/MessageInterface.php', + 'Psr\\Http\\Message\\RequestFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/RequestFactoryInterface.php', + 'Psr\\Http\\Message\\RequestInterface' => __DIR__ . '/..' . '/psr/http-message/src/RequestInterface.php', + 'Psr\\Http\\Message\\ResponseFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/ResponseFactoryInterface.php', + 'Psr\\Http\\Message\\ResponseInterface' => __DIR__ . '/..' . '/psr/http-message/src/ResponseInterface.php', + 'Psr\\Http\\Message\\ServerRequestFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/ServerRequestFactoryInterface.php', + 'Psr\\Http\\Message\\ServerRequestInterface' => __DIR__ . '/..' . '/psr/http-message/src/ServerRequestInterface.php', + 'Psr\\Http\\Message\\StreamFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/StreamFactoryInterface.php', + 'Psr\\Http\\Message\\StreamInterface' => __DIR__ . '/..' . '/psr/http-message/src/StreamInterface.php', + 'Psr\\Http\\Message\\UploadedFileFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/UploadedFileFactoryInterface.php', + 'Psr\\Http\\Message\\UploadedFileInterface' => __DIR__ . '/..' . '/psr/http-message/src/UploadedFileInterface.php', + 'Psr\\Http\\Message\\UriFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/UriFactoryInterface.php', + 'Psr\\Http\\Message\\UriInterface' => __DIR__ . '/..' . '/psr/http-message/src/UriInterface.php', 'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/AbstractLogger.php', 'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/Psr/Log/InvalidArgumentException.php', 'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/Psr/Log/LogLevel.php', @@ -1076,11 +1729,10 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerInterface.php', 'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerTrait.php', 'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/NullLogger.php', - 'Psr\\SimpleCache\\CacheException' => __DIR__ . '/..' . '/psr/simple-cache/src/CacheException.php', - 'Psr\\SimpleCache\\CacheInterface' => __DIR__ . '/..' . '/psr/simple-cache/src/CacheInterface.php', - 'Psr\\SimpleCache\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/simple-cache/src/InvalidArgumentException.php', 'QRcode' => __DIR__ . '/..' . '/combodo/tcpdf/include/barcodes/qrcode.php', 'Query' => __DIR__ . '/../..' . '/application/query.class.inc.php', + 'QueryBuilderContext' => __DIR__ . '/../..' . '/core/querybuildercontext.class.inc.php', + 'QueryBuilderExpressions' => __DIR__ . '/../..' . '/core/querybuilderexpressions.class.inc.php', 'QueryOQL' => __DIR__ . '/../..' . '/application/query.class.inc.php', 'QueryReflection' => __DIR__ . '/../..' . '/core/modelreflection.class.inc.php', 'QueryReflectionRuntime' => __DIR__ . '/../..' . '/core/modelreflection.class.inc.php', @@ -1094,6 +1746,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'RestResultWithObjects' => __DIR__ . '/../..' . '/core/restservices.class.inc.php', 'RestResultWithRelations' => __DIR__ . '/../..' . '/core/restservices.class.inc.php', 'RestUtils' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', + 'ReturnTypeWillChange' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php', 'RotatingLogFileNameBuilder' => __DIR__ . '/../..' . '/core/log.class.inc.php', 'RowStatus' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php', 'RowStatus_Disappeared' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php', @@ -1109,17 +1762,72 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'SQLQuery' => __DIR__ . '/../..' . '/core/sqlquery.class.inc.php', 'SQLUnionQuery' => __DIR__ . '/../..' . '/core/sqlunionquery.class.inc.php', 'SVGDOMSanitizer' => __DIR__ . '/../..' . '/core/htmlsanitizer.class.inc.php', + 'Sabberworm\\CSS\\CSSList\\AtRuleBlockList' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/CSSList/AtRuleBlockList.php', + 'Sabberworm\\CSS\\CSSList\\CSSBlockList' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/CSSList/CSSBlockList.php', + 'Sabberworm\\CSS\\CSSList\\CSSList' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/CSSList/CSSList.php', + 'Sabberworm\\CSS\\CSSList\\Document' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/CSSList/Document.php', + 'Sabberworm\\CSS\\CSSList\\KeyFrame' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/CSSList/KeyFrame.php', + 'Sabberworm\\CSS\\Comment\\Comment' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Comment/Comment.php', + 'Sabberworm\\CSS\\Comment\\Commentable' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Comment/Commentable.php', + 'Sabberworm\\CSS\\OutputFormat' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/OutputFormat.php', + 'Sabberworm\\CSS\\OutputFormatter' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/OutputFormatter.php', + 'Sabberworm\\CSS\\Parser' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Parser.php', + 'Sabberworm\\CSS\\Parsing\\OutputException' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Parsing/OutputException.php', + 'Sabberworm\\CSS\\Parsing\\ParserState' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Parsing/ParserState.php', + 'Sabberworm\\CSS\\Parsing\\SourceException' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Parsing/SourceException.php', + 'Sabberworm\\CSS\\Parsing\\UnexpectedEOFException' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Parsing/UnexpectedEOFException.php', + 'Sabberworm\\CSS\\Parsing\\UnexpectedTokenException' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Parsing/UnexpectedTokenException.php', + 'Sabberworm\\CSS\\Property\\AtRule' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Property/AtRule.php', + 'Sabberworm\\CSS\\Property\\CSSNamespace' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Property/CSSNamespace.php', + 'Sabberworm\\CSS\\Property\\Charset' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Property/Charset.php', + 'Sabberworm\\CSS\\Property\\Import' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Property/Import.php', + 'Sabberworm\\CSS\\Property\\KeyframeSelector' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Property/KeyframeSelector.php', + 'Sabberworm\\CSS\\Property\\Selector' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Property/Selector.php', + 'Sabberworm\\CSS\\Renderable' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Renderable.php', + 'Sabberworm\\CSS\\RuleSet\\AtRuleSet' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/RuleSet/AtRuleSet.php', + 'Sabberworm\\CSS\\RuleSet\\DeclarationBlock' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/RuleSet/DeclarationBlock.php', + 'Sabberworm\\CSS\\RuleSet\\RuleSet' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/RuleSet/RuleSet.php', + 'Sabberworm\\CSS\\Rule\\Rule' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Rule/Rule.php', + 'Sabberworm\\CSS\\Settings' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Settings.php', + 'Sabberworm\\CSS\\Value\\CSSFunction' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Value/CSSFunction.php', + 'Sabberworm\\CSS\\Value\\CSSString' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Value/CSSString.php', + 'Sabberworm\\CSS\\Value\\CalcFunction' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Value/CalcFunction.php', + 'Sabberworm\\CSS\\Value\\CalcRuleValueList' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Value/CalcRuleValueList.php', + 'Sabberworm\\CSS\\Value\\Color' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Value/Color.php', + 'Sabberworm\\CSS\\Value\\LineName' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Value/LineName.php', + 'Sabberworm\\CSS\\Value\\PrimitiveValue' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Value/PrimitiveValue.php', + 'Sabberworm\\CSS\\Value\\RuleValueList' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Value/RuleValueList.php', + 'Sabberworm\\CSS\\Value\\Size' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Value/Size.php', + 'Sabberworm\\CSS\\Value\\URL' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Value/URL.php', + 'Sabberworm\\CSS\\Value\\Value' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Value/Value.php', + 'Sabberworm\\CSS\\Value\\ValueList' => __DIR__ . '/..' . '/sabberworm/php-css-parser/src/Value/ValueList.php', 'ScalarExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php', 'ScalarOqlExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php', 'ScssPhp\\ScssPhp\\Base\\Range' => __DIR__ . '/..' . '/scssphp/scssphp/src/Base/Range.php', 'ScssPhp\\ScssPhp\\Block' => __DIR__ . '/..' . '/scssphp/scssphp/src/Block.php', + 'ScssPhp\\ScssPhp\\Block\\AtRootBlock' => __DIR__ . '/..' . '/scssphp/scssphp/src/Block/AtRootBlock.php', + 'ScssPhp\\ScssPhp\\Block\\CallableBlock' => __DIR__ . '/..' . '/scssphp/scssphp/src/Block/CallableBlock.php', + 'ScssPhp\\ScssPhp\\Block\\ContentBlock' => __DIR__ . '/..' . '/scssphp/scssphp/src/Block/ContentBlock.php', + 'ScssPhp\\ScssPhp\\Block\\DirectiveBlock' => __DIR__ . '/..' . '/scssphp/scssphp/src/Block/DirectiveBlock.php', + 'ScssPhp\\ScssPhp\\Block\\EachBlock' => __DIR__ . '/..' . '/scssphp/scssphp/src/Block/EachBlock.php', + 'ScssPhp\\ScssPhp\\Block\\ElseBlock' => __DIR__ . '/..' . '/scssphp/scssphp/src/Block/ElseBlock.php', + 'ScssPhp\\ScssPhp\\Block\\ElseifBlock' => __DIR__ . '/..' . '/scssphp/scssphp/src/Block/ElseifBlock.php', + 'ScssPhp\\ScssPhp\\Block\\ForBlock' => __DIR__ . '/..' . '/scssphp/scssphp/src/Block/ForBlock.php', + 'ScssPhp\\ScssPhp\\Block\\IfBlock' => __DIR__ . '/..' . '/scssphp/scssphp/src/Block/IfBlock.php', + 'ScssPhp\\ScssPhp\\Block\\MediaBlock' => __DIR__ . '/..' . '/scssphp/scssphp/src/Block/MediaBlock.php', + 'ScssPhp\\ScssPhp\\Block\\NestedPropertyBlock' => __DIR__ . '/..' . '/scssphp/scssphp/src/Block/NestedPropertyBlock.php', + 'ScssPhp\\ScssPhp\\Block\\WhileBlock' => __DIR__ . '/..' . '/scssphp/scssphp/src/Block/WhileBlock.php', 'ScssPhp\\ScssPhp\\Cache' => __DIR__ . '/..' . '/scssphp/scssphp/src/Cache.php', 'ScssPhp\\ScssPhp\\Colors' => __DIR__ . '/..' . '/scssphp/scssphp/src/Colors.php', + 'ScssPhp\\ScssPhp\\CompilationResult' => __DIR__ . '/..' . '/scssphp/scssphp/src/CompilationResult.php', 'ScssPhp\\ScssPhp\\Compiler' => __DIR__ . '/..' . '/scssphp/scssphp/src/Compiler.php', + 'ScssPhp\\ScssPhp\\Compiler\\CachedResult' => __DIR__ . '/..' . '/scssphp/scssphp/src/Compiler/CachedResult.php', 'ScssPhp\\ScssPhp\\Compiler\\Environment' => __DIR__ . '/..' . '/scssphp/scssphp/src/Compiler/Environment.php', 'ScssPhp\\ScssPhp\\Exception\\CompilerException' => __DIR__ . '/..' . '/scssphp/scssphp/src/Exception/CompilerException.php', 'ScssPhp\\ScssPhp\\Exception\\ParserException' => __DIR__ . '/..' . '/scssphp/scssphp/src/Exception/ParserException.php', 'ScssPhp\\ScssPhp\\Exception\\RangeException' => __DIR__ . '/..' . '/scssphp/scssphp/src/Exception/RangeException.php', + 'ScssPhp\\ScssPhp\\Exception\\SassException' => __DIR__ . '/..' . '/scssphp/scssphp/src/Exception/SassException.php', + 'ScssPhp\\ScssPhp\\Exception\\SassScriptException' => __DIR__ . '/..' . '/scssphp/scssphp/src/Exception/SassScriptException.php', 'ScssPhp\\ScssPhp\\Exception\\ServerException' => __DIR__ . '/..' . '/scssphp/scssphp/src/Exception/ServerException.php', 'ScssPhp\\ScssPhp\\Formatter' => __DIR__ . '/..' . '/scssphp/scssphp/src/Formatter.php', 'ScssPhp\\ScssPhp\\Formatter\\Compact' => __DIR__ . '/..' . '/scssphp/scssphp/src/Formatter/Compact.php', @@ -1129,19 +1837,25 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'ScssPhp\\ScssPhp\\Formatter\\Expanded' => __DIR__ . '/..' . '/scssphp/scssphp/src/Formatter/Expanded.php', 'ScssPhp\\ScssPhp\\Formatter\\Nested' => __DIR__ . '/..' . '/scssphp/scssphp/src/Formatter/Nested.php', 'ScssPhp\\ScssPhp\\Formatter\\OutputBlock' => __DIR__ . '/..' . '/scssphp/scssphp/src/Formatter/OutputBlock.php', + 'ScssPhp\\ScssPhp\\Logger\\LoggerInterface' => __DIR__ . '/..' . '/scssphp/scssphp/src/Logger/LoggerInterface.php', + 'ScssPhp\\ScssPhp\\Logger\\QuietLogger' => __DIR__ . '/..' . '/scssphp/scssphp/src/Logger/QuietLogger.php', + 'ScssPhp\\ScssPhp\\Logger\\StreamLogger' => __DIR__ . '/..' . '/scssphp/scssphp/src/Logger/StreamLogger.php', 'ScssPhp\\ScssPhp\\Node' => __DIR__ . '/..' . '/scssphp/scssphp/src/Node.php', 'ScssPhp\\ScssPhp\\Node\\Number' => __DIR__ . '/..' . '/scssphp/scssphp/src/Node/Number.php', + 'ScssPhp\\ScssPhp\\OutputStyle' => __DIR__ . '/..' . '/scssphp/scssphp/src/OutputStyle.php', 'ScssPhp\\ScssPhp\\Parser' => __DIR__ . '/..' . '/scssphp/scssphp/src/Parser.php', 'ScssPhp\\ScssPhp\\SourceMap\\Base64' => __DIR__ . '/..' . '/scssphp/scssphp/src/SourceMap/Base64.php', 'ScssPhp\\ScssPhp\\SourceMap\\Base64VLQ' => __DIR__ . '/..' . '/scssphp/scssphp/src/SourceMap/Base64VLQ.php', 'ScssPhp\\ScssPhp\\SourceMap\\SourceMapGenerator' => __DIR__ . '/..' . '/scssphp/scssphp/src/SourceMap/SourceMapGenerator.php', 'ScssPhp\\ScssPhp\\Type' => __DIR__ . '/..' . '/scssphp/scssphp/src/Type.php', 'ScssPhp\\ScssPhp\\Util' => __DIR__ . '/..' . '/scssphp/scssphp/src/Util.php', + 'ScssPhp\\ScssPhp\\Util\\Path' => __DIR__ . '/..' . '/scssphp/scssphp/src/Util/Path.php', + 'ScssPhp\\ScssPhp\\ValueConverter' => __DIR__ . '/..' . '/scssphp/scssphp/src/ValueConverter.php', 'ScssPhp\\ScssPhp\\Version' => __DIR__ . '/..' . '/scssphp/scssphp/src/Version.php', + 'ScssPhp\\ScssPhp\\Warn' => __DIR__ . '/..' . '/scssphp/scssphp/src/Warn.php', 'SearchMenuNode' => __DIR__ . '/../..' . '/application/menunode.class.inc.php', 'SecurityException' => __DIR__ . '/../..' . '/application/exceptions/SecurityException.php', 'SeparatorPopupMenuItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', - 'SessionUpdateTimestampHandlerInterface' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php', 'SetupLog' => __DIR__ . '/../..' . '/core/log.class.inc.php', 'Shortcut' => __DIR__ . '/../..' . '/application/shortcut.class.inc.php', 'ShortcutContainerMenuNode' => __DIR__ . '/../..' . '/application/menunode.class.inc.php', @@ -1160,34 +1874,38 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'StimulusInternal' => __DIR__ . '/../..' . '/core/stimulus.class.inc.php', 'StimulusUserAction' => __DIR__ . '/../..' . '/core/stimulus.class.inc.php', 'Str' => __DIR__ . '/../..' . '/core/MyHelpers.class.inc.php', - 'Swift_LogFileTransport' => __DIR__ . '/../..' . '/core/email.class.inc.php', - 'Swift_Transport_LogFileTransport' => __DIR__ . '/../..' . '/core/email.class.inc.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', 'Symfony\\Bridge\\Twig\\AppVariable' => __DIR__ . '/..' . '/symfony/twig-bridge/AppVariable.php', 'Symfony\\Bridge\\Twig\\Command\\DebugCommand' => __DIR__ . '/..' . '/symfony/twig-bridge/Command/DebugCommand.php', 'Symfony\\Bridge\\Twig\\Command\\LintCommand' => __DIR__ . '/..' . '/symfony/twig-bridge/Command/LintCommand.php', 'Symfony\\Bridge\\Twig\\DataCollector\\TwigDataCollector' => __DIR__ . '/..' . '/symfony/twig-bridge/DataCollector/TwigDataCollector.php', + 'Symfony\\Bridge\\Twig\\ErrorRenderer\\TwigErrorRenderer' => __DIR__ . '/..' . '/symfony/twig-bridge/ErrorRenderer/TwigErrorRenderer.php', 'Symfony\\Bridge\\Twig\\Extension\\AssetExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/AssetExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\CodeExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/CodeExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\CsrfExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/CsrfExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\CsrfRuntime' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/CsrfRuntime.php', 'Symfony\\Bridge\\Twig\\Extension\\DumpExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/DumpExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\ExpressionExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/ExpressionExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\FormExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/FormExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\HttpFoundationExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/HttpFoundationExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\HttpKernelExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/HttpKernelExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\HttpKernelRuntime' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/HttpKernelRuntime.php', - 'Symfony\\Bridge\\Twig\\Extension\\InitRuntimeInterface' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/InitRuntimeInterface.php', 'Symfony\\Bridge\\Twig\\Extension\\LogoutUrlExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/LogoutUrlExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/ProfilerExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\RoutingExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/RoutingExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\SecurityExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/SecurityExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\SerializerExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/SerializerExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\SerializerRuntime' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/SerializerRuntime.php', 'Symfony\\Bridge\\Twig\\Extension\\StopwatchExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/StopwatchExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\TranslationExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/TranslationExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\WebLinkExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/WebLinkExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\WorkflowExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/WorkflowExtension.php', 'Symfony\\Bridge\\Twig\\Extension\\YamlExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/YamlExtension.php', - 'Symfony\\Bridge\\Twig\\Form\\TwigRenderer' => __DIR__ . '/..' . '/symfony/twig-bridge/Form/TwigRenderer.php', 'Symfony\\Bridge\\Twig\\Form\\TwigRendererEngine' => __DIR__ . '/..' . '/symfony/twig-bridge/Form/TwigRendererEngine.php', - 'Symfony\\Bridge\\Twig\\Form\\TwigRendererEngineInterface' => __DIR__ . '/..' . '/symfony/twig-bridge/Form/TwigRendererEngineInterface.php', - 'Symfony\\Bridge\\Twig\\Form\\TwigRendererInterface' => __DIR__ . '/..' . '/symfony/twig-bridge/Form/TwigRendererInterface.php', + 'Symfony\\Bridge\\Twig\\Mime\\BodyRenderer' => __DIR__ . '/..' . '/symfony/twig-bridge/Mime/BodyRenderer.php', + 'Symfony\\Bridge\\Twig\\Mime\\NotificationEmail' => __DIR__ . '/..' . '/symfony/twig-bridge/Mime/NotificationEmail.php', + 'Symfony\\Bridge\\Twig\\Mime\\TemplatedEmail' => __DIR__ . '/..' . '/symfony/twig-bridge/Mime/TemplatedEmail.php', + 'Symfony\\Bridge\\Twig\\Mime\\WrappedTemplatedEmail' => __DIR__ . '/..' . '/symfony/twig-bridge/Mime/WrappedTemplatedEmail.php', 'Symfony\\Bridge\\Twig\\NodeVisitor\\Scope' => __DIR__ . '/..' . '/symfony/twig-bridge/NodeVisitor/Scope.php', 'Symfony\\Bridge\\Twig\\NodeVisitor\\TranslationDefaultDomainNodeVisitor' => __DIR__ . '/..' . '/symfony/twig-bridge/NodeVisitor/TranslationDefaultDomainNodeVisitor.php', 'Symfony\\Bridge\\Twig\\NodeVisitor\\TranslationNodeVisitor' => __DIR__ . '/..' . '/symfony/twig-bridge/NodeVisitor/TranslationNodeVisitor.php', @@ -1201,38 +1919,42 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Bridge\\Twig\\TokenParser\\DumpTokenParser' => __DIR__ . '/..' . '/symfony/twig-bridge/TokenParser/DumpTokenParser.php', 'Symfony\\Bridge\\Twig\\TokenParser\\FormThemeTokenParser' => __DIR__ . '/..' . '/symfony/twig-bridge/TokenParser/FormThemeTokenParser.php', 'Symfony\\Bridge\\Twig\\TokenParser\\StopwatchTokenParser' => __DIR__ . '/..' . '/symfony/twig-bridge/TokenParser/StopwatchTokenParser.php', - 'Symfony\\Bridge\\Twig\\TokenParser\\TransChoiceTokenParser' => __DIR__ . '/..' . '/symfony/twig-bridge/TokenParser/TransChoiceTokenParser.php', 'Symfony\\Bridge\\Twig\\TokenParser\\TransDefaultDomainTokenParser' => __DIR__ . '/..' . '/symfony/twig-bridge/TokenParser/TransDefaultDomainTokenParser.php', 'Symfony\\Bridge\\Twig\\TokenParser\\TransTokenParser' => __DIR__ . '/..' . '/symfony/twig-bridge/TokenParser/TransTokenParser.php', 'Symfony\\Bridge\\Twig\\Translation\\TwigExtractor' => __DIR__ . '/..' . '/symfony/twig-bridge/Translation/TwigExtractor.php', - 'Symfony\\Bridge\\Twig\\TwigEngine' => __DIR__ . '/..' . '/symfony/twig-bridge/TwigEngine.php', 'Symfony\\Bridge\\Twig\\UndefinedCallableHandler' => __DIR__ . '/..' . '/symfony/twig-bridge/UndefinedCallableHandler.php', 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\AbstractPhpFileCacheWarmer' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/AbstractPhpFileCacheWarmer.php', 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\AnnotationsCacheWarmer' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/AnnotationsCacheWarmer.php', - 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\ClassCacheCacheWarmer' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/ClassCacheCacheWarmer.php', + 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\CachePoolClearerCacheWarmer' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/CachePoolClearerCacheWarmer.php', + 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\ConfigBuilderCacheWarmer' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/ConfigBuilderCacheWarmer.php', 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\RouterCacheWarmer' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/RouterCacheWarmer.php', 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\SerializerCacheWarmer' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/SerializerCacheWarmer.php', - 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\TemplateFinder' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/TemplateFinder.php', - 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\TemplateFinderInterface' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/TemplateFinderInterface.php', - 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\TemplatePathsCacheWarmer' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/TemplatePathsCacheWarmer.php', 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\TranslationsCacheWarmer' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/TranslationsCacheWarmer.php', 'Symfony\\Bundle\\FrameworkBundle\\CacheWarmer\\ValidatorCacheWarmer' => __DIR__ . '/..' . '/symfony/framework-bundle/CacheWarmer/ValidatorCacheWarmer.php', - 'Symfony\\Bundle\\FrameworkBundle\\Client' => __DIR__ . '/..' . '/symfony/framework-bundle/Client.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\AboutCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/AboutCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\AbstractConfigCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/AbstractConfigCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\AssetsInstallCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/AssetsInstallCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\BuildDebugContainerTrait' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/BuildDebugContainerTrait.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\CacheClearCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CacheClearCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolClearCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CachePoolClearCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolDeleteCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CachePoolDeleteCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolListCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CachePoolListCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\CachePoolPruneCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CachePoolPruneCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\CacheWarmupCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/CacheWarmupCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\ConfigDebugCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/ConfigDebugCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\ConfigDumpReferenceCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/ConfigDumpReferenceCommand.php', - 'Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerAwareCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/ContainerAwareCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerDebugCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/ContainerDebugCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerLintCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/ContainerLintCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\DebugAutowiringCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/DebugAutowiringCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\EventDispatcherDebugCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/EventDispatcherDebugCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\RouterDebugCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/RouterDebugCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\RouterMatchCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/RouterMatchCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsDecryptToLocalCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/SecretsDecryptToLocalCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsEncryptFromLocalCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/SecretsEncryptFromLocalCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsGenerateKeysCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/SecretsGenerateKeysCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsListCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/SecretsListCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsRemoveCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/SecretsRemoveCommand.php', + 'Symfony\\Bundle\\FrameworkBundle\\Command\\SecretsSetCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/SecretsSetCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\TranslationDebugCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/TranslationDebugCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\TranslationUpdateCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/TranslationUpdateCommand.php', 'Symfony\\Bundle\\FrameworkBundle\\Command\\WorkflowDumpCommand' => __DIR__ . '/..' . '/symfony/framework-bundle/Command/WorkflowDumpCommand.php', @@ -1246,92 +1968,46 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Bundle\\FrameworkBundle\\Console\\Descriptor\\XmlDescriptor' => __DIR__ . '/..' . '/symfony/framework-bundle/Console/Descriptor/XmlDescriptor.php', 'Symfony\\Bundle\\FrameworkBundle\\Console\\Helper\\DescriptorHelper' => __DIR__ . '/..' . '/symfony/framework-bundle/Console/Helper/DescriptorHelper.php', 'Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController' => __DIR__ . '/..' . '/symfony/framework-bundle/Controller/AbstractController.php', - 'Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller' => __DIR__ . '/..' . '/symfony/framework-bundle/Controller/Controller.php', - 'Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerNameParser' => __DIR__ . '/..' . '/symfony/framework-bundle/Controller/ControllerNameParser.php', 'Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerResolver' => __DIR__ . '/..' . '/symfony/framework-bundle/Controller/ControllerResolver.php', - 'Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerTrait' => __DIR__ . '/..' . '/symfony/framework-bundle/Controller/ControllerTrait.php', 'Symfony\\Bundle\\FrameworkBundle\\Controller\\RedirectController' => __DIR__ . '/..' . '/symfony/framework-bundle/Controller/RedirectController.php', 'Symfony\\Bundle\\FrameworkBundle\\Controller\\TemplateController' => __DIR__ . '/..' . '/symfony/framework-bundle/Controller/TemplateController.php', - 'Symfony\\Bundle\\FrameworkBundle\\DataCollector\\RequestDataCollector' => __DIR__ . '/..' . '/symfony/framework-bundle/DataCollector/RequestDataCollector.php', + 'Symfony\\Bundle\\FrameworkBundle\\DataCollector\\AbstractDataCollector' => __DIR__ . '/..' . '/symfony/framework-bundle/DataCollector/AbstractDataCollector.php', 'Symfony\\Bundle\\FrameworkBundle\\DataCollector\\RouterDataCollector' => __DIR__ . '/..' . '/symfony/framework-bundle/DataCollector/RouterDataCollector.php', + 'Symfony\\Bundle\\FrameworkBundle\\DataCollector\\TemplateAwareDataCollectorInterface' => __DIR__ . '/..' . '/symfony/framework-bundle/DataCollector/TemplateAwareDataCollectorInterface.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddAnnotationsCachedReaderPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddCacheClearerPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/AddCacheClearerPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddCacheWarmerPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/AddCacheWarmerPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddConsoleCommandPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/AddConsoleCommandPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddConstraintValidatorsPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/AddConstraintValidatorsPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddDebugLogProcessorPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddExpressionLanguageProvidersPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AddValidatorInitializersPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/AddValidatorInitializersPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\CacheCollectorPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/CacheCollectorPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\CachePoolClearerPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/CachePoolClearerPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\CachePoolPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/CachePoolPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\CachePoolPrunerPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/CachePoolPrunerPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\CompilerDebugDumpPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/CompilerDebugDumpPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ConfigCachePass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/ConfigCachePass.php', + 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\AssetsContextPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/AssetsContextPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ContainerBuilderDebugDumpPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ControllerArgumentValueResolverPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/ControllerArgumentValueResolverPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\DataCollectorTranslatorPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/DataCollectorTranslatorPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\FormPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/FormPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\LoggingTranslatorPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/LoggingTranslatorPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ProfilerPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/ProfilerPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\PropertyInfoPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/PropertyInfoPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\RoutingResolverPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/RoutingResolverPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\SerializerPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/SerializerPass.php', + 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\RemoveUnusedSessionMarshallingHandlerPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\SessionPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/SessionPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TemplatingPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/TemplatingPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TranslationDumperPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/TranslationDumperPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TranslationExtractorPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/TranslationExtractorPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TranslatorPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/TranslatorPass.php', + 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TestServiceContainerRealRefPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php', + 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\TestServiceContainerWeakRefPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\UnusedTagsPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/UnusedTagsPass.php', - 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\ValidateWorkflowsPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/ValidateWorkflowsPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Compiler\\WorkflowGuardListenerPass' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Compiler/WorkflowGuardListenerPass.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\Configuration' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/Configuration.php', 'Symfony\\Bundle\\FrameworkBundle\\DependencyInjection\\FrameworkExtension' => __DIR__ . '/..' . '/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php', - 'Symfony\\Bundle\\FrameworkBundle\\EventListener\\ResolveControllerNameSubscriber' => __DIR__ . '/..' . '/symfony/framework-bundle/EventListener/ResolveControllerNameSubscriber.php', - 'Symfony\\Bundle\\FrameworkBundle\\EventListener\\SessionListener' => __DIR__ . '/..' . '/symfony/framework-bundle/EventListener/SessionListener.php', - 'Symfony\\Bundle\\FrameworkBundle\\EventListener\\TestSessionListener' => __DIR__ . '/..' . '/symfony/framework-bundle/EventListener/TestSessionListener.php', + 'Symfony\\Bundle\\FrameworkBundle\\EventListener\\SuggestMissingPackageSubscriber' => __DIR__ . '/..' . '/symfony/framework-bundle/EventListener/SuggestMissingPackageSubscriber.php', 'Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle' => __DIR__ . '/..' . '/symfony/framework-bundle/FrameworkBundle.php', 'Symfony\\Bundle\\FrameworkBundle\\HttpCache\\HttpCache' => __DIR__ . '/..' . '/symfony/framework-bundle/HttpCache/HttpCache.php', + 'Symfony\\Bundle\\FrameworkBundle\\KernelBrowser' => __DIR__ . '/..' . '/symfony/framework-bundle/KernelBrowser.php', 'Symfony\\Bundle\\FrameworkBundle\\Kernel\\MicroKernelTrait' => __DIR__ . '/..' . '/symfony/framework-bundle/Kernel/MicroKernelTrait.php', 'Symfony\\Bundle\\FrameworkBundle\\Routing\\AnnotatedRouteControllerLoader' => __DIR__ . '/..' . '/symfony/framework-bundle/Routing/AnnotatedRouteControllerLoader.php', 'Symfony\\Bundle\\FrameworkBundle\\Routing\\DelegatingLoader' => __DIR__ . '/..' . '/symfony/framework-bundle/Routing/DelegatingLoader.php', - 'Symfony\\Bundle\\FrameworkBundle\\Routing\\RedirectableUrlMatcher' => __DIR__ . '/..' . '/symfony/framework-bundle/Routing/RedirectableUrlMatcher.php', + 'Symfony\\Bundle\\FrameworkBundle\\Routing\\RedirectableCompiledUrlMatcher' => __DIR__ . '/..' . '/symfony/framework-bundle/Routing/RedirectableCompiledUrlMatcher.php', + 'Symfony\\Bundle\\FrameworkBundle\\Routing\\RouteLoaderInterface' => __DIR__ . '/..' . '/symfony/framework-bundle/Routing/RouteLoaderInterface.php', 'Symfony\\Bundle\\FrameworkBundle\\Routing\\Router' => __DIR__ . '/..' . '/symfony/framework-bundle/Routing/Router.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\DelegatingEngine' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/DelegatingEngine.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\EngineInterface' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/EngineInterface.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\GlobalVariables' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/GlobalVariables.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\ActionsHelper' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/Helper/ActionsHelper.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\AssetsHelper' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/Helper/AssetsHelper.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\CodeHelper' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/Helper/CodeHelper.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\FormHelper' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/Helper/FormHelper.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\RequestHelper' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/Helper/RequestHelper.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\RouterHelper' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/Helper/RouterHelper.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\SessionHelper' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/Helper/SessionHelper.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\StopwatchHelper' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/Helper/StopwatchHelper.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\TranslatorHelper' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/Helper/TranslatorHelper.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Loader\\FilesystemLoader' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/Loader/FilesystemLoader.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Loader\\TemplateLocator' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/Loader/TemplateLocator.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\PhpEngine' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/PhpEngine.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\TemplateFilenameParser' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/TemplateFilenameParser.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\TemplateNameParser' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/TemplateNameParser.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\TemplateReference' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/TemplateReference.php', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\TimedPhpEngine' => __DIR__ . '/..' . '/symfony/framework-bundle/Templating/TimedPhpEngine.php', - 'Symfony\\Bundle\\FrameworkBundle\\Test\\ForwardCompatTestTrait' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/ForwardCompatTestTrait.php', - 'Symfony\\Bundle\\FrameworkBundle\\Test\\KernelTestCase' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/KernelTestCase.php', - 'Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestCase' => __DIR__ . '/..' . '/symfony/framework-bundle/Test/WebTestCase.php', - 'Symfony\\Bundle\\FrameworkBundle\\Translation\\PhpExtractor' => __DIR__ . '/..' . '/symfony/framework-bundle/Translation/PhpExtractor.php', - 'Symfony\\Bundle\\FrameworkBundle\\Translation\\PhpStringTokenParser' => __DIR__ . '/..' . '/symfony/framework-bundle/Translation/PhpStringTokenParser.php', - 'Symfony\\Bundle\\FrameworkBundle\\Translation\\TranslationLoader' => __DIR__ . '/..' . '/symfony/framework-bundle/Translation/TranslationLoader.php', + 'Symfony\\Bundle\\FrameworkBundle\\Secrets\\AbstractVault' => __DIR__ . '/..' . '/symfony/framework-bundle/Secrets/AbstractVault.php', + 'Symfony\\Bundle\\FrameworkBundle\\Secrets\\DotenvVault' => __DIR__ . '/..' . '/symfony/framework-bundle/Secrets/DotenvVault.php', + 'Symfony\\Bundle\\FrameworkBundle\\Secrets\\SodiumVault' => __DIR__ . '/..' . '/symfony/framework-bundle/Secrets/SodiumVault.php', + 'Symfony\\Bundle\\FrameworkBundle\\Session\\DeprecatedSessionFactory' => __DIR__ . '/..' . '/symfony/framework-bundle/Session/DeprecatedSessionFactory.php', + 'Symfony\\Bundle\\FrameworkBundle\\Session\\ServiceSessionFactory' => __DIR__ . '/..' . '/symfony/framework-bundle/Session/ServiceSessionFactory.php', 'Symfony\\Bundle\\FrameworkBundle\\Translation\\Translator' => __DIR__ . '/..' . '/symfony/framework-bundle/Translation/Translator.php', - 'Symfony\\Bundle\\FrameworkBundle\\Validator\\ConstraintValidatorFactory' => __DIR__ . '/..' . '/symfony/framework-bundle/Validator/ConstraintValidatorFactory.php', - 'Symfony\\Bundle\\TwigBundle\\CacheWarmer\\TemplateCacheCacheWarmer' => __DIR__ . '/..' . '/symfony/twig-bundle/CacheWarmer/TemplateCacheCacheWarmer.php', 'Symfony\\Bundle\\TwigBundle\\CacheWarmer\\TemplateCacheWarmer' => __DIR__ . '/..' . '/symfony/twig-bundle/CacheWarmer/TemplateCacheWarmer.php', - 'Symfony\\Bundle\\TwigBundle\\Command\\DebugCommand' => __DIR__ . '/..' . '/symfony/twig-bundle/Command/DebugCommand.php', 'Symfony\\Bundle\\TwigBundle\\Command\\LintCommand' => __DIR__ . '/..' . '/symfony/twig-bundle/Command/LintCommand.php', - 'Symfony\\Bundle\\TwigBundle\\ContainerAwareRuntimeLoader' => __DIR__ . '/..' . '/symfony/twig-bundle/ContainerAwareRuntimeLoader.php', - 'Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController' => __DIR__ . '/..' . '/symfony/twig-bundle/Controller/ExceptionController.php', - 'Symfony\\Bundle\\TwigBundle\\Controller\\PreviewErrorController' => __DIR__ . '/..' . '/symfony/twig-bundle/Controller/PreviewErrorController.php', - 'Symfony\\Bundle\\TwigBundle\\DependencyInjection\\Compiler\\ExceptionListenerPass' => __DIR__ . '/..' . '/symfony/twig-bundle/DependencyInjection/Compiler/ExceptionListenerPass.php', 'Symfony\\Bundle\\TwigBundle\\DependencyInjection\\Compiler\\ExtensionPass' => __DIR__ . '/..' . '/symfony/twig-bundle/DependencyInjection/Compiler/ExtensionPass.php', 'Symfony\\Bundle\\TwigBundle\\DependencyInjection\\Compiler\\RuntimeLoaderPass' => __DIR__ . '/..' . '/symfony/twig-bundle/DependencyInjection/Compiler/RuntimeLoaderPass.php', 'Symfony\\Bundle\\TwigBundle\\DependencyInjection\\Compiler\\TwigEnvironmentPass' => __DIR__ . '/..' . '/symfony/twig-bundle/DependencyInjection/Compiler/TwigEnvironmentPass.php', @@ -1339,11 +2015,9 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Bundle\\TwigBundle\\DependencyInjection\\Configuration' => __DIR__ . '/..' . '/symfony/twig-bundle/DependencyInjection/Configuration.php', 'Symfony\\Bundle\\TwigBundle\\DependencyInjection\\Configurator\\EnvironmentConfigurator' => __DIR__ . '/..' . '/symfony/twig-bundle/DependencyInjection/Configurator/EnvironmentConfigurator.php', 'Symfony\\Bundle\\TwigBundle\\DependencyInjection\\TwigExtension' => __DIR__ . '/..' . '/symfony/twig-bundle/DependencyInjection/TwigExtension.php', - 'Symfony\\Bundle\\TwigBundle\\Loader\\FilesystemLoader' => __DIR__ . '/..' . '/symfony/twig-bundle/Loader/FilesystemLoader.php', 'Symfony\\Bundle\\TwigBundle\\TemplateIterator' => __DIR__ . '/..' . '/symfony/twig-bundle/TemplateIterator.php', 'Symfony\\Bundle\\TwigBundle\\TwigBundle' => __DIR__ . '/..' . '/symfony/twig-bundle/TwigBundle.php', - 'Symfony\\Bundle\\TwigBundle\\TwigEngine' => __DIR__ . '/..' . '/symfony/twig-bundle/TwigEngine.php', - 'Symfony\\Bundle\\WebProfilerBundle\\Controller\\ExceptionController' => __DIR__ . '/..' . '/symfony/web-profiler-bundle/Controller/ExceptionController.php', + 'Symfony\\Bundle\\WebProfilerBundle\\Controller\\ExceptionPanelController' => __DIR__ . '/..' . '/symfony/web-profiler-bundle/Controller/ExceptionPanelController.php', 'Symfony\\Bundle\\WebProfilerBundle\\Controller\\ProfilerController' => __DIR__ . '/..' . '/symfony/web-profiler-bundle/Controller/ProfilerController.php', 'Symfony\\Bundle\\WebProfilerBundle\\Controller\\RouterController' => __DIR__ . '/..' . '/symfony/web-profiler-bundle/Controller/RouterController.php', 'Symfony\\Bundle\\WebProfilerBundle\\Csp\\ContentSecurityPolicyHandler' => __DIR__ . '/..' . '/symfony/web-profiler-bundle/Csp/ContentSecurityPolicyHandler.php', @@ -1355,66 +2029,68 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension' => __DIR__ . '/..' . '/symfony/web-profiler-bundle/Twig/WebProfilerExtension.php', 'Symfony\\Bundle\\WebProfilerBundle\\WebProfilerBundle' => __DIR__ . '/..' . '/symfony/web-profiler-bundle/WebProfilerBundle.php', 'Symfony\\Component\\Cache\\Adapter\\AbstractAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/AbstractAdapter.php', + 'Symfony\\Component\\Cache\\Adapter\\AbstractTagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/AbstractTagAwareAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\AdapterInterface' => __DIR__ . '/..' . '/symfony/cache/Adapter/AdapterInterface.php', 'Symfony\\Component\\Cache\\Adapter\\ApcuAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/ApcuAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\ArrayAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/ArrayAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\ChainAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/ChainAdapter.php', + 'Symfony\\Component\\Cache\\Adapter\\CouchbaseBucketAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/CouchbaseBucketAdapter.php', + 'Symfony\\Component\\Cache\\Adapter\\CouchbaseCollectionAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/CouchbaseCollectionAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\DoctrineAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/DoctrineAdapter.php', + 'Symfony\\Component\\Cache\\Adapter\\DoctrineDbalAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/DoctrineDbalAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\FilesystemAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/FilesystemAdapter.php', + 'Symfony\\Component\\Cache\\Adapter\\FilesystemTagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/FilesystemTagAwareAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\MemcachedAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/MemcachedAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\NullAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/NullAdapter.php', + 'Symfony\\Component\\Cache\\Adapter\\ParameterNormalizer' => __DIR__ . '/..' . '/symfony/cache/Adapter/ParameterNormalizer.php', 'Symfony\\Component\\Cache\\Adapter\\PdoAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/PdoAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\PhpArrayAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/PhpArrayAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\PhpFilesAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/PhpFilesAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\ProxyAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/ProxyAdapter.php', + 'Symfony\\Component\\Cache\\Adapter\\Psr16Adapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/Psr16Adapter.php', 'Symfony\\Component\\Cache\\Adapter\\RedisAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/RedisAdapter.php', - 'Symfony\\Component\\Cache\\Adapter\\SimpleCacheAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/SimpleCacheAdapter.php', + 'Symfony\\Component\\Cache\\Adapter\\RedisTagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/RedisTagAwareAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\TagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/TagAwareAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\TagAwareAdapterInterface' => __DIR__ . '/..' . '/symfony/cache/Adapter/TagAwareAdapterInterface.php', 'Symfony\\Component\\Cache\\Adapter\\TraceableAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/TraceableAdapter.php', 'Symfony\\Component\\Cache\\Adapter\\TraceableTagAwareAdapter' => __DIR__ . '/..' . '/symfony/cache/Adapter/TraceableTagAwareAdapter.php', 'Symfony\\Component\\Cache\\CacheItem' => __DIR__ . '/..' . '/symfony/cache/CacheItem.php', 'Symfony\\Component\\Cache\\DataCollector\\CacheDataCollector' => __DIR__ . '/..' . '/symfony/cache/DataCollector/CacheDataCollector.php', + 'Symfony\\Component\\Cache\\DependencyInjection\\CacheCollectorPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CacheCollectorPass.php', + 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolClearerPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CachePoolClearerPass.php', + 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CachePoolPass.php', + 'Symfony\\Component\\Cache\\DependencyInjection\\CachePoolPrunerPass' => __DIR__ . '/..' . '/symfony/cache/DependencyInjection/CachePoolPrunerPass.php', 'Symfony\\Component\\Cache\\DoctrineProvider' => __DIR__ . '/..' . '/symfony/cache/DoctrineProvider.php', 'Symfony\\Component\\Cache\\Exception\\CacheException' => __DIR__ . '/..' . '/symfony/cache/Exception/CacheException.php', 'Symfony\\Component\\Cache\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/cache/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Cache\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/cache/Exception/LogicException.php', + 'Symfony\\Component\\Cache\\LockRegistry' => __DIR__ . '/..' . '/symfony/cache/LockRegistry.php', + 'Symfony\\Component\\Cache\\Marshaller\\DefaultMarshaller' => __DIR__ . '/..' . '/symfony/cache/Marshaller/DefaultMarshaller.php', + 'Symfony\\Component\\Cache\\Marshaller\\DeflateMarshaller' => __DIR__ . '/..' . '/symfony/cache/Marshaller/DeflateMarshaller.php', + 'Symfony\\Component\\Cache\\Marshaller\\MarshallerInterface' => __DIR__ . '/..' . '/symfony/cache/Marshaller/MarshallerInterface.php', + 'Symfony\\Component\\Cache\\Marshaller\\SodiumMarshaller' => __DIR__ . '/..' . '/symfony/cache/Marshaller/SodiumMarshaller.php', + 'Symfony\\Component\\Cache\\Marshaller\\TagAwareMarshaller' => __DIR__ . '/..' . '/symfony/cache/Marshaller/TagAwareMarshaller.php', + 'Symfony\\Component\\Cache\\Messenger\\EarlyExpirationDispatcher' => __DIR__ . '/..' . '/symfony/cache/Messenger/EarlyExpirationDispatcher.php', + 'Symfony\\Component\\Cache\\Messenger\\EarlyExpirationHandler' => __DIR__ . '/..' . '/symfony/cache/Messenger/EarlyExpirationHandler.php', + 'Symfony\\Component\\Cache\\Messenger\\EarlyExpirationMessage' => __DIR__ . '/..' . '/symfony/cache/Messenger/EarlyExpirationMessage.php', 'Symfony\\Component\\Cache\\PruneableInterface' => __DIR__ . '/..' . '/symfony/cache/PruneableInterface.php', + 'Symfony\\Component\\Cache\\Psr16Cache' => __DIR__ . '/..' . '/symfony/cache/Psr16Cache.php', 'Symfony\\Component\\Cache\\ResettableInterface' => __DIR__ . '/..' . '/symfony/cache/ResettableInterface.php', - 'Symfony\\Component\\Cache\\Simple\\AbstractCache' => __DIR__ . '/..' . '/symfony/cache/Simple/AbstractCache.php', - 'Symfony\\Component\\Cache\\Simple\\ApcuCache' => __DIR__ . '/..' . '/symfony/cache/Simple/ApcuCache.php', - 'Symfony\\Component\\Cache\\Simple\\ArrayCache' => __DIR__ . '/..' . '/symfony/cache/Simple/ArrayCache.php', - 'Symfony\\Component\\Cache\\Simple\\ChainCache' => __DIR__ . '/..' . '/symfony/cache/Simple/ChainCache.php', - 'Symfony\\Component\\Cache\\Simple\\DoctrineCache' => __DIR__ . '/..' . '/symfony/cache/Simple/DoctrineCache.php', - 'Symfony\\Component\\Cache\\Simple\\FilesystemCache' => __DIR__ . '/..' . '/symfony/cache/Simple/FilesystemCache.php', - 'Symfony\\Component\\Cache\\Simple\\MemcachedCache' => __DIR__ . '/..' . '/symfony/cache/Simple/MemcachedCache.php', - 'Symfony\\Component\\Cache\\Simple\\NullCache' => __DIR__ . '/..' . '/symfony/cache/Simple/NullCache.php', - 'Symfony\\Component\\Cache\\Simple\\PdoCache' => __DIR__ . '/..' . '/symfony/cache/Simple/PdoCache.php', - 'Symfony\\Component\\Cache\\Simple\\PhpArrayCache' => __DIR__ . '/..' . '/symfony/cache/Simple/PhpArrayCache.php', - 'Symfony\\Component\\Cache\\Simple\\PhpFilesCache' => __DIR__ . '/..' . '/symfony/cache/Simple/PhpFilesCache.php', - 'Symfony\\Component\\Cache\\Simple\\Psr6Cache' => __DIR__ . '/..' . '/symfony/cache/Simple/Psr6Cache.php', - 'Symfony\\Component\\Cache\\Simple\\RedisCache' => __DIR__ . '/..' . '/symfony/cache/Simple/RedisCache.php', - 'Symfony\\Component\\Cache\\Simple\\TraceableCache' => __DIR__ . '/..' . '/symfony/cache/Simple/TraceableCache.php', - 'Symfony\\Component\\Cache\\Traits\\AbstractTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/AbstractTrait.php', - 'Symfony\\Component\\Cache\\Traits\\ApcuTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/ApcuTrait.php', - 'Symfony\\Component\\Cache\\Traits\\ArrayTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/ArrayTrait.php', - 'Symfony\\Component\\Cache\\Traits\\DoctrineTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/DoctrineTrait.php', + 'Symfony\\Component\\Cache\\Traits\\AbstractAdapterTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/AbstractAdapterTrait.php', + 'Symfony\\Component\\Cache\\Traits\\ContractsTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/ContractsTrait.php', 'Symfony\\Component\\Cache\\Traits\\FilesystemCommonTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/FilesystemCommonTrait.php', 'Symfony\\Component\\Cache\\Traits\\FilesystemTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/FilesystemTrait.php', - 'Symfony\\Component\\Cache\\Traits\\MemcachedTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/MemcachedTrait.php', - 'Symfony\\Component\\Cache\\Traits\\PdoTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/PdoTrait.php', - 'Symfony\\Component\\Cache\\Traits\\PhpArrayTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/PhpArrayTrait.php', - 'Symfony\\Component\\Cache\\Traits\\PhpFilesTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/PhpFilesTrait.php', 'Symfony\\Component\\Cache\\Traits\\ProxyTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/ProxyTrait.php', + 'Symfony\\Component\\Cache\\Traits\\RedisClusterNodeProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisClusterNodeProxy.php', + 'Symfony\\Component\\Cache\\Traits\\RedisClusterProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisClusterProxy.php', 'Symfony\\Component\\Cache\\Traits\\RedisProxy' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisProxy.php', 'Symfony\\Component\\Cache\\Traits\\RedisTrait' => __DIR__ . '/..' . '/symfony/cache/Traits/RedisTrait.php', - 'Symfony\\Component\\ClassLoader\\ApcClassLoader' => __DIR__ . '/..' . '/symfony/class-loader/ApcClassLoader.php', - 'Symfony\\Component\\ClassLoader\\ClassCollectionLoader' => __DIR__ . '/..' . '/symfony/class-loader/ClassCollectionLoader.php', - 'Symfony\\Component\\ClassLoader\\ClassLoader' => __DIR__ . '/..' . '/symfony/class-loader/ClassLoader.php', - 'Symfony\\Component\\ClassLoader\\ClassMapGenerator' => __DIR__ . '/..' . '/symfony/class-loader/ClassMapGenerator.php', - 'Symfony\\Component\\ClassLoader\\MapClassLoader' => __DIR__ . '/..' . '/symfony/class-loader/MapClassLoader.php', - 'Symfony\\Component\\ClassLoader\\Psr4ClassLoader' => __DIR__ . '/..' . '/symfony/class-loader/Psr4ClassLoader.php', - 'Symfony\\Component\\ClassLoader\\WinCacheClassLoader' => __DIR__ . '/..' . '/symfony/class-loader/WinCacheClassLoader.php', - 'Symfony\\Component\\ClassLoader\\XcacheClassLoader' => __DIR__ . '/..' . '/symfony/class-loader/XcacheClassLoader.php', + 'Symfony\\Component\\Config\\Builder\\ClassBuilder' => __DIR__ . '/..' . '/symfony/config/Builder/ClassBuilder.php', + 'Symfony\\Component\\Config\\Builder\\ConfigBuilderGenerator' => __DIR__ . '/..' . '/symfony/config/Builder/ConfigBuilderGenerator.php', + 'Symfony\\Component\\Config\\Builder\\ConfigBuilderGeneratorInterface' => __DIR__ . '/..' . '/symfony/config/Builder/ConfigBuilderGeneratorInterface.php', + 'Symfony\\Component\\Config\\Builder\\ConfigBuilderInterface' => __DIR__ . '/..' . '/symfony/config/Builder/ConfigBuilderInterface.php', + 'Symfony\\Component\\Config\\Builder\\Method' => __DIR__ . '/..' . '/symfony/config/Builder/Method.php', + 'Symfony\\Component\\Config\\Builder\\Property' => __DIR__ . '/..' . '/symfony/config/Builder/Property.php', 'Symfony\\Component\\Config\\ConfigCache' => __DIR__ . '/..' . '/symfony/config/ConfigCache.php', 'Symfony\\Component\\Config\\ConfigCacheFactory' => __DIR__ . '/..' . '/symfony/config/ConfigCacheFactory.php', 'Symfony\\Component\\Config\\ConfigCacheFactoryInterface' => __DIR__ . '/..' . '/symfony/config/ConfigCacheFactoryInterface.php', @@ -1424,6 +2100,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\Config\\Definition\\BooleanNode' => __DIR__ . '/..' . '/symfony/config/Definition/BooleanNode.php', 'Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ArrayNodeDefinition.php', 'Symfony\\Component\\Config\\Definition\\Builder\\BooleanNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/BooleanNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\BuilderAwareInterface' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/BuilderAwareInterface.php', 'Symfony\\Component\\Config\\Definition\\Builder\\EnumNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/EnumNodeDefinition.php', 'Symfony\\Component\\Config\\Definition\\Builder\\ExprBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ExprBuilder.php', 'Symfony\\Component\\Config\\Definition\\Builder\\FloatNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/FloatNodeDefinition.php', @@ -1459,10 +2136,9 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\Config\\Definition\\PrototypedArrayNode' => __DIR__ . '/..' . '/symfony/config/Definition/PrototypedArrayNode.php', 'Symfony\\Component\\Config\\Definition\\ScalarNode' => __DIR__ . '/..' . '/symfony/config/Definition/ScalarNode.php', 'Symfony\\Component\\Config\\Definition\\VariableNode' => __DIR__ . '/..' . '/symfony/config/Definition/VariableNode.php', - 'Symfony\\Component\\Config\\DependencyInjection\\ConfigCachePass' => __DIR__ . '/..' . '/symfony/config/DependencyInjection/ConfigCachePass.php', 'Symfony\\Component\\Config\\Exception\\FileLoaderImportCircularReferenceException' => __DIR__ . '/..' . '/symfony/config/Exception/FileLoaderImportCircularReferenceException.php', - 'Symfony\\Component\\Config\\Exception\\FileLoaderLoadException' => __DIR__ . '/..' . '/symfony/config/Exception/FileLoaderLoadException.php', 'Symfony\\Component\\Config\\Exception\\FileLocatorFileNotFoundException' => __DIR__ . '/..' . '/symfony/config/Exception/FileLocatorFileNotFoundException.php', + 'Symfony\\Component\\Config\\Exception\\LoaderLoadException' => __DIR__ . '/..' . '/symfony/config/Exception/LoaderLoadException.php', 'Symfony\\Component\\Config\\FileLocator' => __DIR__ . '/..' . '/symfony/config/FileLocator.php', 'Symfony\\Component\\Config\\FileLocatorInterface' => __DIR__ . '/..' . '/symfony/config/FileLocatorInterface.php', 'Symfony\\Component\\Config\\Loader\\DelegatingLoader' => __DIR__ . '/..' . '/symfony/config/Loader/DelegatingLoader.php', @@ -1472,6 +2148,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\Config\\Loader\\LoaderInterface' => __DIR__ . '/..' . '/symfony/config/Loader/LoaderInterface.php', 'Symfony\\Component\\Config\\Loader\\LoaderResolver' => __DIR__ . '/..' . '/symfony/config/Loader/LoaderResolver.php', 'Symfony\\Component\\Config\\Loader\\LoaderResolverInterface' => __DIR__ . '/..' . '/symfony/config/Loader/LoaderResolverInterface.php', + 'Symfony\\Component\\Config\\Loader\\ParamConfigurator' => __DIR__ . '/..' . '/symfony/config/Loader/ParamConfigurator.php', 'Symfony\\Component\\Config\\ResourceCheckerConfigCache' => __DIR__ . '/..' . '/symfony/config/ResourceCheckerConfigCache.php', 'Symfony\\Component\\Config\\ResourceCheckerConfigCacheFactory' => __DIR__ . '/..' . '/symfony/config/ResourceCheckerConfigCacheFactory.php', 'Symfony\\Component\\Config\\ResourceCheckerInterface' => __DIR__ . '/..' . '/symfony/config/ResourceCheckerInterface.php', @@ -1489,14 +2166,27 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\Config\\Util\\Exception\\XmlParsingException' => __DIR__ . '/..' . '/symfony/config/Util/Exception/XmlParsingException.php', 'Symfony\\Component\\Config\\Util\\XmlUtils' => __DIR__ . '/..' . '/symfony/config/Util/XmlUtils.php', 'Symfony\\Component\\Console\\Application' => __DIR__ . '/..' . '/symfony/console/Application.php', + 'Symfony\\Component\\Console\\Attribute\\AsCommand' => __DIR__ . '/..' . '/symfony/console/Attribute/AsCommand.php', + 'Symfony\\Component\\Console\\CI\\GithubActionReporter' => __DIR__ . '/..' . '/symfony/console/CI/GithubActionReporter.php', + 'Symfony\\Component\\Console\\Color' => __DIR__ . '/..' . '/symfony/console/Color.php', 'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => __DIR__ . '/..' . '/symfony/console/CommandLoader/CommandLoaderInterface.php', 'Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader' => __DIR__ . '/..' . '/symfony/console/CommandLoader/ContainerCommandLoader.php', 'Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader' => __DIR__ . '/..' . '/symfony/console/CommandLoader/FactoryCommandLoader.php', 'Symfony\\Component\\Console\\Command\\Command' => __DIR__ . '/..' . '/symfony/console/Command/Command.php', + 'Symfony\\Component\\Console\\Command\\CompleteCommand' => __DIR__ . '/..' . '/symfony/console/Command/CompleteCommand.php', + 'Symfony\\Component\\Console\\Command\\DumpCompletionCommand' => __DIR__ . '/..' . '/symfony/console/Command/DumpCompletionCommand.php', 'Symfony\\Component\\Console\\Command\\HelpCommand' => __DIR__ . '/..' . '/symfony/console/Command/HelpCommand.php', + 'Symfony\\Component\\Console\\Command\\LazyCommand' => __DIR__ . '/..' . '/symfony/console/Command/LazyCommand.php', 'Symfony\\Component\\Console\\Command\\ListCommand' => __DIR__ . '/..' . '/symfony/console/Command/ListCommand.php', 'Symfony\\Component\\Console\\Command\\LockableTrait' => __DIR__ . '/..' . '/symfony/console/Command/LockableTrait.php', + 'Symfony\\Component\\Console\\Command\\SignalableCommandInterface' => __DIR__ . '/..' . '/symfony/console/Command/SignalableCommandInterface.php', + 'Symfony\\Component\\Console\\Completion\\CompletionInput' => __DIR__ . '/..' . '/symfony/console/Completion/CompletionInput.php', + 'Symfony\\Component\\Console\\Completion\\CompletionSuggestions' => __DIR__ . '/..' . '/symfony/console/Completion/CompletionSuggestions.php', + 'Symfony\\Component\\Console\\Completion\\Output\\BashCompletionOutput' => __DIR__ . '/..' . '/symfony/console/Completion/Output/BashCompletionOutput.php', + 'Symfony\\Component\\Console\\Completion\\Output\\CompletionOutputInterface' => __DIR__ . '/..' . '/symfony/console/Completion/Output/CompletionOutputInterface.php', + 'Symfony\\Component\\Console\\Completion\\Suggestion' => __DIR__ . '/..' . '/symfony/console/Completion/Suggestion.php', 'Symfony\\Component\\Console\\ConsoleEvents' => __DIR__ . '/..' . '/symfony/console/ConsoleEvents.php', + 'Symfony\\Component\\Console\\Cursor' => __DIR__ . '/..' . '/symfony/console/Cursor.php', 'Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => __DIR__ . '/..' . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php', 'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => __DIR__ . '/..' . '/symfony/console/Descriptor/ApplicationDescription.php', 'Symfony\\Component\\Console\\Descriptor\\Descriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/Descriptor.php', @@ -1509,21 +2199,27 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleCommandEvent.php', 'Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleErrorEvent.php', 'Symfony\\Component\\Console\\Event\\ConsoleEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleEvent.php', - 'Symfony\\Component\\Console\\Event\\ConsoleExceptionEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleExceptionEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleSignalEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleSignalEvent.php', 'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleTerminateEvent.php', 'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => __DIR__ . '/..' . '/symfony/console/Exception/CommandNotFoundException.php', 'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/console/Exception/ExceptionInterface.php', 'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidArgumentException.php', 'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidOptionException.php', 'Symfony\\Component\\Console\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/console/Exception/LogicException.php', + 'Symfony\\Component\\Console\\Exception\\MissingInputException' => __DIR__ . '/..' . '/symfony/console/Exception/MissingInputException.php', + 'Symfony\\Component\\Console\\Exception\\NamespaceNotFoundException' => __DIR__ . '/..' . '/symfony/console/Exception/NamespaceNotFoundException.php', 'Symfony\\Component\\Console\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/console/Exception/RuntimeException.php', + 'Symfony\\Component\\Console\\Formatter\\NullOutputFormatter' => __DIR__ . '/..' . '/symfony/console/Formatter/NullOutputFormatter.php', + 'Symfony\\Component\\Console\\Formatter\\NullOutputFormatterStyle' => __DIR__ . '/..' . '/symfony/console/Formatter/NullOutputFormatterStyle.php', 'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatter.php', 'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterInterface.php', 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyle.php', 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleInterface.php', 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleStack.php', + 'Symfony\\Component\\Console\\Formatter\\WrappableOutputFormatterInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/WrappableOutputFormatterInterface.php', 'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DebugFormatterHelper.php', 'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DescriptorHelper.php', + 'Symfony\\Component\\Console\\Helper\\Dumper' => __DIR__ . '/..' . '/symfony/console/Helper/Dumper.php', 'Symfony\\Component\\Console\\Helper\\FormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/FormatterHelper.php', 'Symfony\\Component\\Console\\Helper\\Helper' => __DIR__ . '/..' . '/symfony/console/Helper/Helper.php', 'Symfony\\Component\\Console\\Helper\\HelperInterface' => __DIR__ . '/..' . '/symfony/console/Helper/HelperInterface.php', @@ -1536,6 +2232,8 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/SymfonyQuestionHelper.php', 'Symfony\\Component\\Console\\Helper\\Table' => __DIR__ . '/..' . '/symfony/console/Helper/Table.php', 'Symfony\\Component\\Console\\Helper\\TableCell' => __DIR__ . '/..' . '/symfony/console/Helper/TableCell.php', + 'Symfony\\Component\\Console\\Helper\\TableCellStyle' => __DIR__ . '/..' . '/symfony/console/Helper/TableCellStyle.php', + 'Symfony\\Component\\Console\\Helper\\TableRows' => __DIR__ . '/..' . '/symfony/console/Helper/TableRows.php', 'Symfony\\Component\\Console\\Helper\\TableSeparator' => __DIR__ . '/..' . '/symfony/console/Helper/TableSeparator.php', 'Symfony\\Component\\Console\\Helper\\TableStyle' => __DIR__ . '/..' . '/symfony/console/Helper/TableStyle.php', 'Symfony\\Component\\Console\\Input\\ArgvInput' => __DIR__ . '/..' . '/symfony/console/Input/ArgvInput.php', @@ -1552,19 +2250,26 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\Console\\Output\\BufferedOutput' => __DIR__ . '/..' . '/symfony/console/Output/BufferedOutput.php', 'Symfony\\Component\\Console\\Output\\ConsoleOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutput.php', 'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutputInterface.php', + 'Symfony\\Component\\Console\\Output\\ConsoleSectionOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleSectionOutput.php', 'Symfony\\Component\\Console\\Output\\NullOutput' => __DIR__ . '/..' . '/symfony/console/Output/NullOutput.php', 'Symfony\\Component\\Console\\Output\\Output' => __DIR__ . '/..' . '/symfony/console/Output/Output.php', 'Symfony\\Component\\Console\\Output\\OutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/OutputInterface.php', 'Symfony\\Component\\Console\\Output\\StreamOutput' => __DIR__ . '/..' . '/symfony/console/Output/StreamOutput.php', + 'Symfony\\Component\\Console\\Output\\TrimmedBufferOutput' => __DIR__ . '/..' . '/symfony/console/Output/TrimmedBufferOutput.php', 'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ChoiceQuestion.php', 'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ConfirmationQuestion.php', 'Symfony\\Component\\Console\\Question\\Question' => __DIR__ . '/..' . '/symfony/console/Question/Question.php', + 'Symfony\\Component\\Console\\SignalRegistry\\SignalRegistry' => __DIR__ . '/..' . '/symfony/console/SignalRegistry/SignalRegistry.php', + 'Symfony\\Component\\Console\\SingleCommandApplication' => __DIR__ . '/..' . '/symfony/console/SingleCommandApplication.php', 'Symfony\\Component\\Console\\Style\\OutputStyle' => __DIR__ . '/..' . '/symfony/console/Style/OutputStyle.php', 'Symfony\\Component\\Console\\Style\\StyleInterface' => __DIR__ . '/..' . '/symfony/console/Style/StyleInterface.php', 'Symfony\\Component\\Console\\Style\\SymfonyStyle' => __DIR__ . '/..' . '/symfony/console/Style/SymfonyStyle.php', 'Symfony\\Component\\Console\\Terminal' => __DIR__ . '/..' . '/symfony/console/Terminal.php', 'Symfony\\Component\\Console\\Tester\\ApplicationTester' => __DIR__ . '/..' . '/symfony/console/Tester/ApplicationTester.php', + 'Symfony\\Component\\Console\\Tester\\CommandCompletionTester' => __DIR__ . '/..' . '/symfony/console/Tester/CommandCompletionTester.php', 'Symfony\\Component\\Console\\Tester\\CommandTester' => __DIR__ . '/..' . '/symfony/console/Tester/CommandTester.php', + 'Symfony\\Component\\Console\\Tester\\Constraint\\CommandIsSuccessful' => __DIR__ . '/..' . '/symfony/console/Tester/Constraint/CommandIsSuccessful.php', + 'Symfony\\Component\\Console\\Tester\\TesterTrait' => __DIR__ . '/..' . '/symfony/console/Tester/TesterTrait.php', 'Symfony\\Component\\CssSelector\\CssSelectorConverter' => __DIR__ . '/..' . '/symfony/css-selector/CssSelectorConverter.php', 'Symfony\\Component\\CssSelector\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/css-selector/Exception/ExceptionInterface.php', 'Symfony\\Component\\CssSelector\\Exception\\ExpressionErrorException' => __DIR__ . '/..' . '/symfony/css-selector/Exception/ExpressionErrorException.php', @@ -1613,72 +2318,67 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\CssSelector\\XPath\\Translator' => __DIR__ . '/..' . '/symfony/css-selector/XPath/Translator.php', 'Symfony\\Component\\CssSelector\\XPath\\TranslatorInterface' => __DIR__ . '/..' . '/symfony/css-selector/XPath/TranslatorInterface.php', 'Symfony\\Component\\CssSelector\\XPath\\XPathExpr' => __DIR__ . '/..' . '/symfony/css-selector/XPath/XPathExpr.php', - 'Symfony\\Component\\Debug\\BufferingLogger' => __DIR__ . '/..' . '/symfony/debug/BufferingLogger.php', - 'Symfony\\Component\\Debug\\Debug' => __DIR__ . '/..' . '/symfony/debug/Debug.php', - 'Symfony\\Component\\Debug\\DebugClassLoader' => __DIR__ . '/..' . '/symfony/debug/DebugClassLoader.php', - 'Symfony\\Component\\Debug\\ErrorHandler' => __DIR__ . '/..' . '/symfony/debug/ErrorHandler.php', - 'Symfony\\Component\\Debug\\ExceptionHandler' => __DIR__ . '/..' . '/symfony/debug/ExceptionHandler.php', - 'Symfony\\Component\\Debug\\Exception\\ClassNotFoundException' => __DIR__ . '/..' . '/symfony/debug/Exception/ClassNotFoundException.php', - 'Symfony\\Component\\Debug\\Exception\\ContextErrorException' => __DIR__ . '/..' . '/symfony/debug/Exception/ContextErrorException.php', - 'Symfony\\Component\\Debug\\Exception\\FatalErrorException' => __DIR__ . '/..' . '/symfony/debug/Exception/FatalErrorException.php', - 'Symfony\\Component\\Debug\\Exception\\FatalThrowableError' => __DIR__ . '/..' . '/symfony/debug/Exception/FatalThrowableError.php', - 'Symfony\\Component\\Debug\\Exception\\FlattenException' => __DIR__ . '/..' . '/symfony/debug/Exception/FlattenException.php', - 'Symfony\\Component\\Debug\\Exception\\OutOfMemoryException' => __DIR__ . '/..' . '/symfony/debug/Exception/OutOfMemoryException.php', - 'Symfony\\Component\\Debug\\Exception\\SilencedErrorContext' => __DIR__ . '/..' . '/symfony/debug/Exception/SilencedErrorContext.php', - 'Symfony\\Component\\Debug\\Exception\\UndefinedFunctionException' => __DIR__ . '/..' . '/symfony/debug/Exception/UndefinedFunctionException.php', - 'Symfony\\Component\\Debug\\Exception\\UndefinedMethodException' => __DIR__ . '/..' . '/symfony/debug/Exception/UndefinedMethodException.php', - 'Symfony\\Component\\Debug\\FatalErrorHandler\\ClassNotFoundFatalErrorHandler' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php', - 'Symfony\\Component\\Debug\\FatalErrorHandler\\FatalErrorHandlerInterface' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php', - 'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedFunctionFatalErrorHandler' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php', - 'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedMethodFatalErrorHandler' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php', 'Symfony\\Component\\DependencyInjection\\Alias' => __DIR__ . '/..' . '/symfony/dependency-injection/Alias.php', + 'Symfony\\Component\\DependencyInjection\\Argument\\AbstractArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/AbstractArgument.php', 'Symfony\\Component\\DependencyInjection\\Argument\\ArgumentInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ArgumentInterface.php', 'Symfony\\Component\\DependencyInjection\\Argument\\BoundArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/BoundArgument.php', 'Symfony\\Component\\DependencyInjection\\Argument\\IteratorArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/IteratorArgument.php', + 'Symfony\\Component\\DependencyInjection\\Argument\\ReferenceSetArgumentTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ReferenceSetArgumentTrait.php', 'Symfony\\Component\\DependencyInjection\\Argument\\RewindableGenerator' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/RewindableGenerator.php', 'Symfony\\Component\\DependencyInjection\\Argument\\ServiceClosureArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ServiceClosureArgument.php', + 'Symfony\\Component\\DependencyInjection\\Argument\\ServiceLocator' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ServiceLocator.php', + 'Symfony\\Component\\DependencyInjection\\Argument\\ServiceLocatorArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/ServiceLocatorArgument.php', 'Symfony\\Component\\DependencyInjection\\Argument\\TaggedIteratorArgument' => __DIR__ . '/..' . '/symfony/dependency-injection/Argument/TaggedIteratorArgument.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\AsTaggedItem' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/AsTaggedItem.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\Autoconfigure' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/Autoconfigure.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\AutoconfigureTag' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/AutoconfigureTag.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\TaggedIterator' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/TaggedIterator.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\TaggedLocator' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/TaggedLocator.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\Target' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/Target.php', + 'Symfony\\Component\\DependencyInjection\\Attribute\\When' => __DIR__ . '/..' . '/symfony/dependency-injection/Attribute/When.php', 'Symfony\\Component\\DependencyInjection\\ChildDefinition' => __DIR__ . '/..' . '/symfony/dependency-injection/ChildDefinition.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AbstractRecursivePass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AbstractRecursivePass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\AliasDeprecatedPublicServicesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AnalyzeServiceReferencesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\AttributeAutoconfigurationPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AutoAliasServicePass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AutoAliasServicePass.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\AutowireExceptionPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AutowireExceptionPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AutowirePass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AutowirePass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\AutowireRequiredMethodsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AutowireRequiredMethodsPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\AutowireRequiredPropertiesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AutowireRequiredPropertiesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckArgumentsValidityPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckCircularReferencesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckCircularReferencesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckDefinitionValidityPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckExceptionOnInvalidReferenceBehaviorPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckReferenceValidityPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckTypeDeclarationsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\Compiler' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/Compiler.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CompilerPassInterface.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\DecoratorServicePass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/DecoratorServicePass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\DefinitionErrorExceptionPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ExtensionCompilerPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ExtensionCompilerPass.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\FactoryReturnTypePass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/FactoryReturnTypePass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\InlineServiceDefinitionsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\LoggingFormatter' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/LoggingFormatter.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\MergeExtensionConfigurationPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\PassConfig' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/PassConfig.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\PriorityTaggedServiceTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\RegisterAutoconfigureAttributesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RegisterAutoconfigureAttributesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RegisterEnvVarProcessorsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\RegisterReverseContainerPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RegisterServiceSubscribersPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RemoveAbstractDefinitionsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RemovePrivateAliasesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\RemoveUnusedDefinitionsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\RepeatablePassInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RepeatablePassInterface.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\RepeatedPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RepeatedPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ReplaceAliasByActualDefinitionPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveBindingsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveBindingsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveChildDefinitionsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveChildDefinitionsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveClassPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveClassPass.php', - 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveDefinitionTemplatesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveDefinitionTemplatesPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveDecoratorStackPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveDecoratorStackPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveEnvPlaceholdersPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveEnvPlaceholdersPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveFactoryClassPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveFactoryClassPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveHotPathPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveHotPathPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveInstanceofConditionalsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveInstanceofConditionalsPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveInvalidReferencesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveNamedArgumentsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveNoPreloadPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveNoPreloadPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveParameterPlaceHoldersPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolvePrivatesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolvePrivatesPass.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveReferencesToAliasesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php', @@ -1688,7 +2388,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraph' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraphEdge' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php', 'Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraphNode' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php', - 'Symfony\\Component\\DependencyInjection\\Config\\AutowireServiceResource' => __DIR__ . '/..' . '/symfony/dependency-injection/Config/AutowireServiceResource.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ValidateEnvPlaceholdersPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.php', 'Symfony\\Component\\DependencyInjection\\Config\\ContainerParametersResource' => __DIR__ . '/..' . '/symfony/dependency-injection/Config/ContainerParametersResource.php', 'Symfony\\Component\\DependencyInjection\\Config\\ContainerParametersResourceChecker' => __DIR__ . '/..' . '/symfony/dependency-injection/Config/ContainerParametersResourceChecker.php', 'Symfony\\Component\\DependencyInjection\\Container' => __DIR__ . '/..' . '/symfony/dependency-injection/Container.php', @@ -1697,13 +2397,14 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\DependencyInjection\\ContainerBuilder' => __DIR__ . '/..' . '/symfony/dependency-injection/ContainerBuilder.php', 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/ContainerInterface.php', 'Symfony\\Component\\DependencyInjection\\Definition' => __DIR__ . '/..' . '/symfony/dependency-injection/Definition.php', - 'Symfony\\Component\\DependencyInjection\\DefinitionDecorator' => __DIR__ . '/..' . '/symfony/dependency-injection/DefinitionDecorator.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\Dumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/Dumper.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\DumperInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/DumperInterface.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\GraphvizDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/GraphvizDumper.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\PhpDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/PhpDumper.php', + 'Symfony\\Component\\DependencyInjection\\Dumper\\Preloader' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/Preloader.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\XmlDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/XmlDumper.php', 'Symfony\\Component\\DependencyInjection\\Dumper\\YamlDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/YamlDumper.php', + 'Symfony\\Component\\DependencyInjection\\EnvVarLoaderInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/EnvVarLoaderInterface.php', 'Symfony\\Component\\DependencyInjection\\EnvVarProcessor' => __DIR__ . '/..' . '/symfony/dependency-injection/EnvVarProcessor.php', 'Symfony\\Component\\DependencyInjection\\EnvVarProcessorInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/EnvVarProcessorInterface.php', 'Symfony\\Component\\DependencyInjection\\Exception\\AutowiringFailedException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/AutowiringFailedException.php', @@ -1712,6 +2413,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\DependencyInjection\\Exception\\EnvParameterException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/EnvParameterException.php', 'Symfony\\Component\\DependencyInjection\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/ExceptionInterface.php', 'Symfony\\Component\\DependencyInjection\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\InvalidParameterTypeException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/InvalidParameterTypeException.php', 'Symfony\\Component\\DependencyInjection\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/LogicException.php', 'Symfony\\Component\\DependencyInjection\\Exception\\OutOfBoundsException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/OutOfBoundsException.php', 'Symfony\\Component\\DependencyInjection\\Exception\\ParameterCircularReferenceException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php', @@ -1734,8 +2436,10 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\AbstractConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\AbstractServiceConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/AbstractServiceConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\AliasConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/AliasConfigurator.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ClosureReferenceConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/ClosureReferenceConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ContainerConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\DefaultsConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\EnvConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/EnvConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\InlineServiceConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\InstanceofConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.php', 'Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ParametersConfigurator' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.php', @@ -1770,45 +2474,72 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\DependencyInjection\\Loader\\XmlFileLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/XmlFileLoader.php', 'Symfony\\Component\\DependencyInjection\\Loader\\YamlFileLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/YamlFileLoader.php', 'Symfony\\Component\\DependencyInjection\\Parameter' => __DIR__ . '/..' . '/symfony/dependency-injection/Parameter.php', + 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBag' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/ContainerBag.php', + 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBagInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/ContainerBagInterface.php', 'Symfony\\Component\\DependencyInjection\\ParameterBag\\EnvPlaceholderParameterBag' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php', 'Symfony\\Component\\DependencyInjection\\ParameterBag\\FrozenParameterBag' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php', 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBag' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/ParameterBag.php', 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBagInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php', 'Symfony\\Component\\DependencyInjection\\Reference' => __DIR__ . '/..' . '/symfony/dependency-injection/Reference.php', - 'Symfony\\Component\\DependencyInjection\\ResettableContainerInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/ResettableContainerInterface.php', + 'Symfony\\Component\\DependencyInjection\\ReverseContainer' => __DIR__ . '/..' . '/symfony/dependency-injection/ReverseContainer.php', 'Symfony\\Component\\DependencyInjection\\ServiceLocator' => __DIR__ . '/..' . '/symfony/dependency-injection/ServiceLocator.php', - 'Symfony\\Component\\DependencyInjection\\ServiceSubscriberInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/ServiceSubscriberInterface.php', 'Symfony\\Component\\DependencyInjection\\TaggedContainerInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/TaggedContainerInterface.php', 'Symfony\\Component\\DependencyInjection\\TypedReference' => __DIR__ . '/..' . '/symfony/dependency-injection/TypedReference.php', 'Symfony\\Component\\DependencyInjection\\Variable' => __DIR__ . '/..' . '/symfony/dependency-injection/Variable.php', + 'Symfony\\Component\\Dotenv\\Command\\DebugCommand' => __DIR__ . '/..' . '/symfony/dotenv/Command/DebugCommand.php', + 'Symfony\\Component\\Dotenv\\Command\\DotenvDumpCommand' => __DIR__ . '/..' . '/symfony/dotenv/Command/DotenvDumpCommand.php', 'Symfony\\Component\\Dotenv\\Dotenv' => __DIR__ . '/..' . '/symfony/dotenv/Dotenv.php', 'Symfony\\Component\\Dotenv\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/dotenv/Exception/ExceptionInterface.php', 'Symfony\\Component\\Dotenv\\Exception\\FormatException' => __DIR__ . '/..' . '/symfony/dotenv/Exception/FormatException.php', 'Symfony\\Component\\Dotenv\\Exception\\FormatExceptionContext' => __DIR__ . '/..' . '/symfony/dotenv/Exception/FormatExceptionContext.php', 'Symfony\\Component\\Dotenv\\Exception\\PathException' => __DIR__ . '/..' . '/symfony/dotenv/Exception/PathException.php', - 'Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/ContainerAwareEventDispatcher.php', + 'Symfony\\Component\\ErrorHandler\\BufferingLogger' => __DIR__ . '/..' . '/symfony/error-handler/BufferingLogger.php', + 'Symfony\\Component\\ErrorHandler\\Debug' => __DIR__ . '/..' . '/symfony/error-handler/Debug.php', + 'Symfony\\Component\\ErrorHandler\\DebugClassLoader' => __DIR__ . '/..' . '/symfony/error-handler/DebugClassLoader.php', + 'Symfony\\Component\\ErrorHandler\\ErrorEnhancer\\ClassNotFoundErrorEnhancer' => __DIR__ . '/..' . '/symfony/error-handler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php', + 'Symfony\\Component\\ErrorHandler\\ErrorEnhancer\\ErrorEnhancerInterface' => __DIR__ . '/..' . '/symfony/error-handler/ErrorEnhancer/ErrorEnhancerInterface.php', + 'Symfony\\Component\\ErrorHandler\\ErrorEnhancer\\UndefinedFunctionErrorEnhancer' => __DIR__ . '/..' . '/symfony/error-handler/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php', + 'Symfony\\Component\\ErrorHandler\\ErrorEnhancer\\UndefinedMethodErrorEnhancer' => __DIR__ . '/..' . '/symfony/error-handler/ErrorEnhancer/UndefinedMethodErrorEnhancer.php', + 'Symfony\\Component\\ErrorHandler\\ErrorHandler' => __DIR__ . '/..' . '/symfony/error-handler/ErrorHandler.php', + 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\CliErrorRenderer' => __DIR__ . '/..' . '/symfony/error-handler/ErrorRenderer/CliErrorRenderer.php', + 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\ErrorRendererInterface' => __DIR__ . '/..' . '/symfony/error-handler/ErrorRenderer/ErrorRendererInterface.php', + 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\HtmlErrorRenderer' => __DIR__ . '/..' . '/symfony/error-handler/ErrorRenderer/HtmlErrorRenderer.php', + 'Symfony\\Component\\ErrorHandler\\ErrorRenderer\\SerializerErrorRenderer' => __DIR__ . '/..' . '/symfony/error-handler/ErrorRenderer/SerializerErrorRenderer.php', + 'Symfony\\Component\\ErrorHandler\\Error\\ClassNotFoundError' => __DIR__ . '/..' . '/symfony/error-handler/Error/ClassNotFoundError.php', + 'Symfony\\Component\\ErrorHandler\\Error\\FatalError' => __DIR__ . '/..' . '/symfony/error-handler/Error/FatalError.php', + 'Symfony\\Component\\ErrorHandler\\Error\\OutOfMemoryError' => __DIR__ . '/..' . '/symfony/error-handler/Error/OutOfMemoryError.php', + 'Symfony\\Component\\ErrorHandler\\Error\\UndefinedFunctionError' => __DIR__ . '/..' . '/symfony/error-handler/Error/UndefinedFunctionError.php', + 'Symfony\\Component\\ErrorHandler\\Error\\UndefinedMethodError' => __DIR__ . '/..' . '/symfony/error-handler/Error/UndefinedMethodError.php', + 'Symfony\\Component\\ErrorHandler\\Exception\\FlattenException' => __DIR__ . '/..' . '/symfony/error-handler/Exception/FlattenException.php', + 'Symfony\\Component\\ErrorHandler\\Exception\\SilencedErrorContext' => __DIR__ . '/..' . '/symfony/error-handler/Exception/SilencedErrorContext.php', + 'Symfony\\Component\\ErrorHandler\\Internal\\TentativeTypes' => __DIR__ . '/..' . '/symfony/error-handler/Internal/TentativeTypes.php', + 'Symfony\\Component\\ErrorHandler\\ThrowableUtils' => __DIR__ . '/..' . '/symfony/error-handler/ThrowableUtils.php', + 'Symfony\\Component\\EventDispatcher\\Attribute\\AsEventListener' => __DIR__ . '/..' . '/symfony/event-dispatcher/Attribute/AsEventListener.php', 'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php', - 'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcherInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php', 'Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener' => __DIR__ . '/..' . '/symfony/event-dispatcher/Debug/WrappedListener.php', + 'Symfony\\Component\\EventDispatcher\\DependencyInjection\\AddEventAliasesPass' => __DIR__ . '/..' . '/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php', 'Symfony\\Component\\EventDispatcher\\DependencyInjection\\RegisterListenersPass' => __DIR__ . '/..' . '/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php', - 'Symfony\\Component\\EventDispatcher\\Event' => __DIR__ . '/..' . '/symfony/event-dispatcher/Event.php', 'Symfony\\Component\\EventDispatcher\\EventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventDispatcher.php', 'Symfony\\Component\\EventDispatcher\\EventDispatcherInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventDispatcherInterface.php', 'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventSubscriberInterface.php', 'Symfony\\Component\\EventDispatcher\\GenericEvent' => __DIR__ . '/..' . '/symfony/event-dispatcher/GenericEvent.php', 'Symfony\\Component\\EventDispatcher\\ImmutableEventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/ImmutableEventDispatcher.php', + 'Symfony\\Component\\EventDispatcher\\LegacyEventDispatcherProxy' => __DIR__ . '/..' . '/symfony/event-dispatcher/LegacyEventDispatcherProxy.php', 'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/ExceptionInterface.php', 'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/FileNotFoundException.php', 'Symfony\\Component\\Filesystem\\Exception\\IOException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOException.php', 'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Filesystem\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/RuntimeException.php', 'Symfony\\Component\\Filesystem\\Filesystem' => __DIR__ . '/..' . '/symfony/filesystem/Filesystem.php', - 'Symfony\\Component\\Filesystem\\LockHandler' => __DIR__ . '/..' . '/symfony/filesystem/LockHandler.php', + 'Symfony\\Component\\Filesystem\\Path' => __DIR__ . '/..' . '/symfony/filesystem/Path.php', 'Symfony\\Component\\Finder\\Comparator\\Comparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/Comparator.php', 'Symfony\\Component\\Finder\\Comparator\\DateComparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/DateComparator.php', 'Symfony\\Component\\Finder\\Comparator\\NumberComparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/NumberComparator.php', 'Symfony\\Component\\Finder\\Exception\\AccessDeniedException' => __DIR__ . '/..' . '/symfony/finder/Exception/AccessDeniedException.php', - 'Symfony\\Component\\Finder\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/finder/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Finder\\Exception\\DirectoryNotFoundException' => __DIR__ . '/..' . '/symfony/finder/Exception/DirectoryNotFoundException.php', 'Symfony\\Component\\Finder\\Finder' => __DIR__ . '/..' . '/symfony/finder/Finder.php', + 'Symfony\\Component\\Finder\\Gitignore' => __DIR__ . '/..' . '/symfony/finder/Gitignore.php', 'Symfony\\Component\\Finder\\Glob' => __DIR__ . '/..' . '/symfony/finder/Glob.php', 'Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/CustomFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\DateRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/DateRangeFilterIterator.php', @@ -1817,42 +2548,49 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\Finder\\Iterator\\FileTypeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FileTypeFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\FilecontentFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilecontentFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\FilenameFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilenameFilterIterator.php', - 'Symfony\\Component\\Finder\\Iterator\\FilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\LazyIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/LazyIterator.php', 'Symfony\\Component\\Finder\\Iterator\\MultiplePcreFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/MultiplePcreFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\PathFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/PathFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\RecursiveDirectoryIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/RecursiveDirectoryIterator.php', 'Symfony\\Component\\Finder\\Iterator\\SizeRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/SizeRangeFilterIterator.php', 'Symfony\\Component\\Finder\\Iterator\\SortableIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/SortableIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\VcsIgnoredFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/VcsIgnoredFilterIterator.php', 'Symfony\\Component\\Finder\\SplFileInfo' => __DIR__ . '/..' . '/symfony/finder/SplFileInfo.php', 'Symfony\\Component\\HttpFoundation\\AcceptHeader' => __DIR__ . '/..' . '/symfony/http-foundation/AcceptHeader.php', 'Symfony\\Component\\HttpFoundation\\AcceptHeaderItem' => __DIR__ . '/..' . '/symfony/http-foundation/AcceptHeaderItem.php', - 'Symfony\\Component\\HttpFoundation\\ApacheRequest' => __DIR__ . '/..' . '/symfony/http-foundation/ApacheRequest.php', 'Symfony\\Component\\HttpFoundation\\BinaryFileResponse' => __DIR__ . '/..' . '/symfony/http-foundation/BinaryFileResponse.php', 'Symfony\\Component\\HttpFoundation\\Cookie' => __DIR__ . '/..' . '/symfony/http-foundation/Cookie.php', + 'Symfony\\Component\\HttpFoundation\\Exception\\BadRequestException' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/BadRequestException.php', 'Symfony\\Component\\HttpFoundation\\Exception\\ConflictingHeadersException' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/ConflictingHeadersException.php', + 'Symfony\\Component\\HttpFoundation\\Exception\\JsonException' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/JsonException.php', 'Symfony\\Component\\HttpFoundation\\Exception\\RequestExceptionInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/RequestExceptionInterface.php', + 'Symfony\\Component\\HttpFoundation\\Exception\\SessionNotFoundException' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/SessionNotFoundException.php', 'Symfony\\Component\\HttpFoundation\\Exception\\SuspiciousOperationException' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/SuspiciousOperationException.php', 'Symfony\\Component\\HttpFoundation\\ExpressionRequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/ExpressionRequestMatcher.php', 'Symfony\\Component\\HttpFoundation\\FileBag' => __DIR__ . '/..' . '/symfony/http-foundation/FileBag.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\AccessDeniedException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/AccessDeniedException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\CannotWriteFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/CannotWriteFileException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\ExtensionFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/ExtensionFileException.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\FileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/FileException.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/FileNotFoundException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\FormSizeFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/FormSizeFileException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\IniSizeFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/IniSizeFileException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\NoFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/NoFileException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\NoTmpDirFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/NoTmpDirFileException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\PartialFileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/PartialFileException.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\UnexpectedTypeException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/UnexpectedTypeException.php', 'Symfony\\Component\\HttpFoundation\\File\\Exception\\UploadException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/UploadException.php', 'Symfony\\Component\\HttpFoundation\\File\\File' => __DIR__ . '/..' . '/symfony/http-foundation/File/File.php', - 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\ExtensionGuesser' => __DIR__ . '/..' . '/symfony/http-foundation/File/MimeType/ExtensionGuesser.php', - 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\ExtensionGuesserInterface' => __DIR__ . '/..' . '/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php', - 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\FileBinaryMimeTypeGuesser' => __DIR__ . '/..' . '/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php', - 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\FileinfoMimeTypeGuesser' => __DIR__ . '/..' . '/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php', - 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\MimeTypeExtensionGuesser' => __DIR__ . '/..' . '/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php', - 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\MimeTypeGuesser' => __DIR__ . '/..' . '/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php', - 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\MimeTypeGuesserInterface' => __DIR__ . '/..' . '/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php', 'Symfony\\Component\\HttpFoundation\\File\\Stream' => __DIR__ . '/..' . '/symfony/http-foundation/File/Stream.php', 'Symfony\\Component\\HttpFoundation\\File\\UploadedFile' => __DIR__ . '/..' . '/symfony/http-foundation/File/UploadedFile.php', 'Symfony\\Component\\HttpFoundation\\HeaderBag' => __DIR__ . '/..' . '/symfony/http-foundation/HeaderBag.php', + 'Symfony\\Component\\HttpFoundation\\HeaderUtils' => __DIR__ . '/..' . '/symfony/http-foundation/HeaderUtils.php', + 'Symfony\\Component\\HttpFoundation\\InputBag' => __DIR__ . '/..' . '/symfony/http-foundation/InputBag.php', 'Symfony\\Component\\HttpFoundation\\IpUtils' => __DIR__ . '/..' . '/symfony/http-foundation/IpUtils.php', 'Symfony\\Component\\HttpFoundation\\JsonResponse' => __DIR__ . '/..' . '/symfony/http-foundation/JsonResponse.php', 'Symfony\\Component\\HttpFoundation\\ParameterBag' => __DIR__ . '/..' . '/symfony/http-foundation/ParameterBag.php', + 'Symfony\\Component\\HttpFoundation\\RateLimiter\\AbstractRequestRateLimiter' => __DIR__ . '/..' . '/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php', + 'Symfony\\Component\\HttpFoundation\\RateLimiter\\RequestRateLimiterInterface' => __DIR__ . '/..' . '/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php', 'Symfony\\Component\\HttpFoundation\\RedirectResponse' => __DIR__ . '/..' . '/symfony/http-foundation/RedirectResponse.php', 'Symfony\\Component\\HttpFoundation\\Request' => __DIR__ . '/..' . '/symfony/http-foundation/Request.php', 'Symfony\\Component\\HttpFoundation\\RequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/RequestMatcher.php', @@ -1870,28 +2608,39 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\HttpFoundation\\Session\\Session' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Session.php', 'Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/SessionBagInterface.php', 'Symfony\\Component\\HttpFoundation\\Session\\SessionBagProxy' => __DIR__ . '/..' . '/symfony/http-foundation/Session/SessionBagProxy.php', + 'Symfony\\Component\\HttpFoundation\\Session\\SessionFactory' => __DIR__ . '/..' . '/symfony/http-foundation/Session/SessionFactory.php', + 'Symfony\\Component\\HttpFoundation\\Session\\SessionFactoryInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/SessionFactoryInterface.php', 'Symfony\\Component\\HttpFoundation\\Session\\SessionInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/SessionInterface.php', 'Symfony\\Component\\HttpFoundation\\Session\\SessionUtils' => __DIR__ . '/..' . '/symfony/http-foundation/Session/SessionUtils.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\AbstractSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcacheSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\IdentityMarshaller' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MarshallingSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcachedSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MigratingSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MongoDbSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeFileSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NullSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\PdoSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\RedisSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\SessionHandlerFactory' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\StrictSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\WriteCheckSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MetadataBag' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/MetadataBag.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockArraySessionStorage' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockFileSessionStorage' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockFileSessionStorageFactory' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/NativeSessionStorage.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorageFactory' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\PhpBridgeSessionStorage' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\PhpBridgeSessionStorageFactory' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\AbstractProxy' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\NativeProxy' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\SessionHandlerProxy' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\ServiceSessionFactory' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/ServiceSessionFactory.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageFactoryInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php', 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/SessionStorageInterface.php', 'Symfony\\Component\\HttpFoundation\\StreamedResponse' => __DIR__ . '/..' . '/symfony/http-foundation/StreamedResponse.php', + 'Symfony\\Component\\HttpFoundation\\UrlHelper' => __DIR__ . '/..' . '/symfony/http-foundation/UrlHelper.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\ArgumentInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Attribute/ArgumentInterface.php', + 'Symfony\\Component\\HttpKernel\\Attribute\\AsController' => __DIR__ . '/..' . '/symfony/http-kernel/Attribute/AsController.php', 'Symfony\\Component\\HttpKernel\\Bundle\\Bundle' => __DIR__ . '/..' . '/symfony/http-kernel/Bundle/Bundle.php', 'Symfony\\Component\\HttpKernel\\Bundle\\BundleInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Bundle/BundleInterface.php', 'Symfony\\Component\\HttpKernel\\CacheClearer\\CacheClearerInterface' => __DIR__ . '/..' . '/symfony/http-kernel/CacheClearer/CacheClearerInterface.php', @@ -1901,8 +2650,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerAggregate' => __DIR__ . '/..' . '/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php', 'Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerInterface' => __DIR__ . '/..' . '/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php', 'Symfony\\Component\\HttpKernel\\CacheWarmer\\WarmableInterface' => __DIR__ . '/..' . '/symfony/http-kernel/CacheWarmer/WarmableInterface.php', - 'Symfony\\Component\\HttpKernel\\Client' => __DIR__ . '/..' . '/symfony/http-kernel/Client.php', - 'Symfony\\Component\\HttpKernel\\Config\\EnvParametersResource' => __DIR__ . '/..' . '/symfony/http-kernel/Config/EnvParametersResource.php', 'Symfony\\Component\\HttpKernel\\Config\\FileLocator' => __DIR__ . '/..' . '/symfony/http-kernel/Config/FileLocator.php', 'Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadata' => __DIR__ . '/..' . '/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php', 'Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadataFactory' => __DIR__ . '/..' . '/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php', @@ -1910,16 +2657,19 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolverInterface.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\DefaultValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\NotTaggedControllerValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestAttributeValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\RequestValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\ServiceValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\SessionValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\TraceableValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\VariadicValueResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolverInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php', 'Symfony\\Component\\HttpKernel\\Controller\\ContainerControllerResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ContainerControllerResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ControllerReference' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ControllerReference.php', 'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ControllerResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ControllerResolverInterface.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ErrorController' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ErrorController.php', 'Symfony\\Component\\HttpKernel\\Controller\\TraceableArgumentResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/TraceableArgumentResolver.php', 'Symfony\\Component\\HttpKernel\\Controller\\TraceableControllerResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/TraceableControllerResolver.php', 'Symfony\\Component\\HttpKernel\\DataCollector\\AjaxDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/AjaxDataCollector.php', @@ -1935,11 +2685,9 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\HttpKernel\\DataCollector\\RequestDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/RequestDataCollector.php', 'Symfony\\Component\\HttpKernel\\DataCollector\\RouterDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/RouterDataCollector.php', 'Symfony\\Component\\HttpKernel\\DataCollector\\TimeDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/TimeDataCollector.php', - 'Symfony\\Component\\HttpKernel\\DataCollector\\Util\\ValueExporter' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/Util/ValueExporter.php', 'Symfony\\Component\\HttpKernel\\Debug\\FileLinkFormatter' => __DIR__ . '/..' . '/symfony/http-kernel/Debug/FileLinkFormatter.php', 'Symfony\\Component\\HttpKernel\\Debug\\TraceableEventDispatcher' => __DIR__ . '/..' . '/symfony/http-kernel/Debug/TraceableEventDispatcher.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\AddAnnotatedClassesToCachePass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php', - 'Symfony\\Component\\HttpKernel\\DependencyInjection\\AddClassesToCachePass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ConfigurableExtension' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ControllerArgumentValueResolverPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\Extension' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/Extension.php', @@ -1948,6 +2696,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\HttpKernel\\DependencyInjection\\LoggerPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/LoggerPass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\MergeExtensionConfigurationPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\RegisterControllerArgumentLocatorsPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\RegisterLocaleAwareServicesPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/RegisterLocaleAwareServicesPass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\RemoveEmptyControllerArgumentLocatorsPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ResettableServicePass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/ResettableServicePass.php', 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ServicesResetter' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/ServicesResetter.php', @@ -1955,35 +2704,37 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\HttpKernel\\EventListener\\AbstractTestSessionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/AbstractTestSessionListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\AddRequestFormatsListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/AddRequestFormatsListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\DebugHandlersListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/DebugHandlersListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\DisallowRobotsIndexingListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\DumpListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/DumpListener.php', - 'Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ExceptionListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\ErrorListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ErrorListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\FragmentListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/FragmentListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\LocaleAwareListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/LocaleAwareListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\LocaleListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/LocaleListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\ProfilerListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ProfilerListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ResponseListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\RouterListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/RouterListener.php', - 'Symfony\\Component\\HttpKernel\\EventListener\\SaveSessionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/SaveSessionListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\SessionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/SessionListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\StreamedResponseListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/StreamedResponseListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\SurrogateListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/SurrogateListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\TestSessionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/TestSessionListener.php', - 'Symfony\\Component\\HttpKernel\\EventListener\\TranslatorListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/TranslatorListener.php', 'Symfony\\Component\\HttpKernel\\EventListener\\ValidateRequestListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ValidateRequestListener.php', - 'Symfony\\Component\\HttpKernel\\Event\\FilterControllerArgumentsEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/FilterControllerEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/FilterResponseEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\ControllerArgumentsEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/ControllerArgumentsEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\ControllerEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/ControllerEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\ExceptionEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/ExceptionEvent.php', 'Symfony\\Component\\HttpKernel\\Event\\FinishRequestEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/FinishRequestEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/GetResponseEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/GetResponseForExceptionEvent.php', 'Symfony\\Component\\HttpKernel\\Event\\KernelEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/KernelEvent.php', - 'Symfony\\Component\\HttpKernel\\Event\\PostResponseEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/PostResponseEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\RequestEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/RequestEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\ResponseEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/ResponseEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\TerminateEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/TerminateEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\ViewEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/ViewEvent.php', 'Symfony\\Component\\HttpKernel\\Exception\\AccessDeniedHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/AccessDeniedHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\BadRequestHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/BadRequestHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\ConflictHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/ConflictHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\ControllerDoesNotReturnResponseException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/ControllerDoesNotReturnResponseException.php', 'Symfony\\Component\\HttpKernel\\Exception\\GoneHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/GoneHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\HttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/HttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\HttpExceptionInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/HttpExceptionInterface.php', + 'Symfony\\Component\\HttpKernel\\Exception\\InvalidMetadataException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/InvalidMetadataException.php', 'Symfony\\Component\\HttpKernel\\Exception\\LengthRequiredHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/LengthRequiredHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\MethodNotAllowedHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\NotAcceptableHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/NotAcceptableHttpException.php', @@ -1993,12 +2744,15 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\HttpKernel\\Exception\\ServiceUnavailableHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\TooManyRequestsHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/TooManyRequestsHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\UnauthorizedHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/UnauthorizedHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\UnexpectedSessionUsageException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/UnexpectedSessionUsageException.php', 'Symfony\\Component\\HttpKernel\\Exception\\UnprocessableEntityHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php', 'Symfony\\Component\\HttpKernel\\Exception\\UnsupportedMediaTypeHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php', 'Symfony\\Component\\HttpKernel\\Fragment\\AbstractSurrogateFragmentRenderer' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php', 'Symfony\\Component\\HttpKernel\\Fragment\\EsiFragmentRenderer' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/EsiFragmentRenderer.php', 'Symfony\\Component\\HttpKernel\\Fragment\\FragmentHandler' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/FragmentHandler.php', 'Symfony\\Component\\HttpKernel\\Fragment\\FragmentRendererInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/FragmentRendererInterface.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\FragmentUriGenerator' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/FragmentUriGenerator.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\FragmentUriGeneratorInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/FragmentUriGeneratorInterface.php', 'Symfony\\Component\\HttpKernel\\Fragment\\HIncludeFragmentRenderer' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php', 'Symfony\\Component\\HttpKernel\\Fragment\\InlineFragmentRenderer' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/InlineFragmentRenderer.php', 'Symfony\\Component\\HttpKernel\\Fragment\\RoutableFragmentRenderer' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php', @@ -2013,7 +2767,9 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\HttpKernel\\HttpCache\\StoreInterface' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/StoreInterface.php', 'Symfony\\Component\\HttpKernel\\HttpCache\\SubRequestHandler' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/SubRequestHandler.php', 'Symfony\\Component\\HttpKernel\\HttpCache\\SurrogateInterface' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/SurrogateInterface.php', + 'Symfony\\Component\\HttpKernel\\HttpClientKernel' => __DIR__ . '/..' . '/symfony/http-kernel/HttpClientKernel.php', 'Symfony\\Component\\HttpKernel\\HttpKernel' => __DIR__ . '/..' . '/symfony/http-kernel/HttpKernel.php', + 'Symfony\\Component\\HttpKernel\\HttpKernelBrowser' => __DIR__ . '/..' . '/symfony/http-kernel/HttpKernelBrowser.php', 'Symfony\\Component\\HttpKernel\\HttpKernelInterface' => __DIR__ . '/..' . '/symfony/http-kernel/HttpKernelInterface.php', 'Symfony\\Component\\HttpKernel\\Kernel' => __DIR__ . '/..' . '/symfony/http-kernel/Kernel.php', 'Symfony\\Component\\HttpKernel\\KernelEvents' => __DIR__ . '/..' . '/symfony/http-kernel/KernelEvents.php', @@ -2027,45 +2783,55 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\HttpKernel\\RebootableInterface' => __DIR__ . '/..' . '/symfony/http-kernel/RebootableInterface.php', 'Symfony\\Component\\HttpKernel\\TerminableInterface' => __DIR__ . '/..' . '/symfony/http-kernel/TerminableInterface.php', 'Symfony\\Component\\HttpKernel\\UriSigner' => __DIR__ . '/..' . '/symfony/http-kernel/UriSigner.php', + 'Symfony\\Component\\Routing\\Alias' => __DIR__ . '/..' . '/symfony/routing/Alias.php', 'Symfony\\Component\\Routing\\Annotation\\Route' => __DIR__ . '/..' . '/symfony/routing/Annotation/Route.php', 'Symfony\\Component\\Routing\\CompiledRoute' => __DIR__ . '/..' . '/symfony/routing/CompiledRoute.php', 'Symfony\\Component\\Routing\\DependencyInjection\\RoutingResolverPass' => __DIR__ . '/..' . '/symfony/routing/DependencyInjection/RoutingResolverPass.php', 'Symfony\\Component\\Routing\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/routing/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Routing\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/routing/Exception/InvalidArgumentException.php', 'Symfony\\Component\\Routing\\Exception\\InvalidParameterException' => __DIR__ . '/..' . '/symfony/routing/Exception/InvalidParameterException.php', 'Symfony\\Component\\Routing\\Exception\\MethodNotAllowedException' => __DIR__ . '/..' . '/symfony/routing/Exception/MethodNotAllowedException.php', 'Symfony\\Component\\Routing\\Exception\\MissingMandatoryParametersException' => __DIR__ . '/..' . '/symfony/routing/Exception/MissingMandatoryParametersException.php', 'Symfony\\Component\\Routing\\Exception\\NoConfigurationException' => __DIR__ . '/..' . '/symfony/routing/Exception/NoConfigurationException.php', 'Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException' => __DIR__ . '/..' . '/symfony/routing/Exception/ResourceNotFoundException.php', + 'Symfony\\Component\\Routing\\Exception\\RouteCircularReferenceException' => __DIR__ . '/..' . '/symfony/routing/Exception/RouteCircularReferenceException.php', 'Symfony\\Component\\Routing\\Exception\\RouteNotFoundException' => __DIR__ . '/..' . '/symfony/routing/Exception/RouteNotFoundException.php', + 'Symfony\\Component\\Routing\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/routing/Exception/RuntimeException.php', + 'Symfony\\Component\\Routing\\Generator\\CompiledUrlGenerator' => __DIR__ . '/..' . '/symfony/routing/Generator/CompiledUrlGenerator.php', 'Symfony\\Component\\Routing\\Generator\\ConfigurableRequirementsInterface' => __DIR__ . '/..' . '/symfony/routing/Generator/ConfigurableRequirementsInterface.php', + 'Symfony\\Component\\Routing\\Generator\\Dumper\\CompiledUrlGeneratorDumper' => __DIR__ . '/..' . '/symfony/routing/Generator/Dumper/CompiledUrlGeneratorDumper.php', 'Symfony\\Component\\Routing\\Generator\\Dumper\\GeneratorDumper' => __DIR__ . '/..' . '/symfony/routing/Generator/Dumper/GeneratorDumper.php', 'Symfony\\Component\\Routing\\Generator\\Dumper\\GeneratorDumperInterface' => __DIR__ . '/..' . '/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php', - 'Symfony\\Component\\Routing\\Generator\\Dumper\\PhpGeneratorDumper' => __DIR__ . '/..' . '/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php', 'Symfony\\Component\\Routing\\Generator\\UrlGenerator' => __DIR__ . '/..' . '/symfony/routing/Generator/UrlGenerator.php', 'Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface' => __DIR__ . '/..' . '/symfony/routing/Generator/UrlGeneratorInterface.php', 'Symfony\\Component\\Routing\\Loader\\AnnotationClassLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/AnnotationClassLoader.php', 'Symfony\\Component\\Routing\\Loader\\AnnotationDirectoryLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/AnnotationDirectoryLoader.php', 'Symfony\\Component\\Routing\\Loader\\AnnotationFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/AnnotationFileLoader.php', 'Symfony\\Component\\Routing\\Loader\\ClosureLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/ClosureLoader.php', + 'Symfony\\Component\\Routing\\Loader\\Configurator\\AliasConfigurator' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/AliasConfigurator.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\CollectionConfigurator' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/CollectionConfigurator.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\ImportConfigurator' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/ImportConfigurator.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\RouteConfigurator' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/RouteConfigurator.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\RoutingConfigurator' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/RoutingConfigurator.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\AddTrait' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/Traits/AddTrait.php', + 'Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\HostTrait' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/Traits/HostTrait.php', + 'Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\LocalizedRouteTrait' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/Traits/LocalizedRouteTrait.php', + 'Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\PrefixTrait' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/Traits/PrefixTrait.php', 'Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\RouteTrait' => __DIR__ . '/..' . '/symfony/routing/Loader/Configurator/Traits/RouteTrait.php', - 'Symfony\\Component\\Routing\\Loader\\DependencyInjection\\ServiceRouterLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php', + 'Symfony\\Component\\Routing\\Loader\\ContainerLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/ContainerLoader.php', 'Symfony\\Component\\Routing\\Loader\\DirectoryLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/DirectoryLoader.php', 'Symfony\\Component\\Routing\\Loader\\GlobFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/GlobFileLoader.php', - 'Symfony\\Component\\Routing\\Loader\\ObjectRouteLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/ObjectRouteLoader.php', + 'Symfony\\Component\\Routing\\Loader\\ObjectLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/ObjectLoader.php', 'Symfony\\Component\\Routing\\Loader\\PhpFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/PhpFileLoader.php', 'Symfony\\Component\\Routing\\Loader\\XmlFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/XmlFileLoader.php', 'Symfony\\Component\\Routing\\Loader\\YamlFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/YamlFileLoader.php', - 'Symfony\\Component\\Routing\\Matcher\\Dumper\\DumperCollection' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/DumperCollection.php', - 'Symfony\\Component\\Routing\\Matcher\\Dumper\\DumperRoute' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/DumperRoute.php', + 'Symfony\\Component\\Routing\\Matcher\\CompiledUrlMatcher' => __DIR__ . '/..' . '/symfony/routing/Matcher/CompiledUrlMatcher.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\CompiledUrlMatcherDumper' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/CompiledUrlMatcherDumper.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\CompiledUrlMatcherTrait' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php', 'Symfony\\Component\\Routing\\Matcher\\Dumper\\MatcherDumper' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/MatcherDumper.php', 'Symfony\\Component\\Routing\\Matcher\\Dumper\\MatcherDumperInterface' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php', - 'Symfony\\Component\\Routing\\Matcher\\Dumper\\PhpMatcherDumper' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php', 'Symfony\\Component\\Routing\\Matcher\\Dumper\\StaticPrefixCollection' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php', + 'Symfony\\Component\\Routing\\Matcher\\ExpressionLanguageProvider' => __DIR__ . '/..' . '/symfony/routing/Matcher/ExpressionLanguageProvider.php', 'Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcher' => __DIR__ . '/..' . '/symfony/routing/Matcher/RedirectableUrlMatcher.php', 'Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface' => __DIR__ . '/..' . '/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php', 'Symfony\\Component\\Routing\\Matcher\\RequestMatcherInterface' => __DIR__ . '/..' . '/symfony/routing/Matcher/RequestMatcherInterface.php', @@ -2085,6 +2851,20 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\Stopwatch\\Stopwatch' => __DIR__ . '/..' . '/symfony/stopwatch/Stopwatch.php', 'Symfony\\Component\\Stopwatch\\StopwatchEvent' => __DIR__ . '/..' . '/symfony/stopwatch/StopwatchEvent.php', 'Symfony\\Component\\Stopwatch\\StopwatchPeriod' => __DIR__ . '/..' . '/symfony/stopwatch/StopwatchPeriod.php', + 'Symfony\\Component\\String\\AbstractString' => __DIR__ . '/..' . '/symfony/string/AbstractString.php', + 'Symfony\\Component\\String\\AbstractUnicodeString' => __DIR__ . '/..' . '/symfony/string/AbstractUnicodeString.php', + 'Symfony\\Component\\String\\ByteString' => __DIR__ . '/..' . '/symfony/string/ByteString.php', + 'Symfony\\Component\\String\\CodePointString' => __DIR__ . '/..' . '/symfony/string/CodePointString.php', + 'Symfony\\Component\\String\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/string/Exception/ExceptionInterface.php', + 'Symfony\\Component\\String\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/string/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\String\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/string/Exception/RuntimeException.php', + 'Symfony\\Component\\String\\Inflector\\EnglishInflector' => __DIR__ . '/..' . '/symfony/string/Inflector/EnglishInflector.php', + 'Symfony\\Component\\String\\Inflector\\FrenchInflector' => __DIR__ . '/..' . '/symfony/string/Inflector/FrenchInflector.php', + 'Symfony\\Component\\String\\Inflector\\InflectorInterface' => __DIR__ . '/..' . '/symfony/string/Inflector/InflectorInterface.php', + 'Symfony\\Component\\String\\LazyString' => __DIR__ . '/..' . '/symfony/string/LazyString.php', + 'Symfony\\Component\\String\\Slugger\\AsciiSlugger' => __DIR__ . '/..' . '/symfony/string/Slugger/AsciiSlugger.php', + 'Symfony\\Component\\String\\Slugger\\SluggerInterface' => __DIR__ . '/..' . '/symfony/string/Slugger/SluggerInterface.php', + 'Symfony\\Component\\String\\UnicodeString' => __DIR__ . '/..' . '/symfony/string/UnicodeString.php', 'Symfony\\Component\\VarDumper\\Caster\\AmqpCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/AmqpCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\ArgsStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ArgsStub.php', 'Symfony\\Component\\VarDumper\\Caster\\Caster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/Caster.php', @@ -2095,13 +2875,23 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\VarDumper\\Caster\\DOMCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DOMCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\DateCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DateCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\DoctrineCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DoctrineCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\DsCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DsCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\DsPairStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DsPairStub.php', 'Symfony\\Component\\VarDumper\\Caster\\EnumStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/EnumStub.php', 'Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ExceptionCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\FiberCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/FiberCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\FrameStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/FrameStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\GmpCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/GmpCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ImagineCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ImagineCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ImgStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ImgStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\IntlCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/IntlCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\LinkStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/LinkStub.php', - 'Symfony\\Component\\VarDumper\\Caster\\MongoCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/MongoCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\MemcachedCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/MemcachedCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\MysqliCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/MysqliCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\PdoCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/PdoCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\PgSqlCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/PgSqlCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ProxyManagerCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ProxyManagerCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\RdKafkaCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/RdKafkaCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\RedisCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/RedisCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ReflectionCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\ResourceCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ResourceCaster.php', @@ -2109,6 +2899,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\VarDumper\\Caster\\StubCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/StubCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/SymfonyCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\TraceStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/TraceStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\UuidCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/UuidCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\XmlReaderCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/XmlReaderCaster.php', 'Symfony\\Component\\VarDumper\\Caster\\XmlResourceCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/XmlResourceCaster.php', 'Symfony\\Component\\VarDumper\\Cloner\\AbstractCloner' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/AbstractCloner.php', @@ -2118,13 +2909,34 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\VarDumper\\Cloner\\DumperInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/DumperInterface.php', 'Symfony\\Component\\VarDumper\\Cloner\\Stub' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/Stub.php', 'Symfony\\Component\\VarDumper\\Cloner\\VarCloner' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/VarCloner.php', + 'Symfony\\Component\\VarDumper\\Command\\Descriptor\\CliDescriptor' => __DIR__ . '/..' . '/symfony/var-dumper/Command/Descriptor/CliDescriptor.php', + 'Symfony\\Component\\VarDumper\\Command\\Descriptor\\DumpDescriptorInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php', + 'Symfony\\Component\\VarDumper\\Command\\Descriptor\\HtmlDescriptor' => __DIR__ . '/..' . '/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php', + 'Symfony\\Component\\VarDumper\\Command\\ServerDumpCommand' => __DIR__ . '/..' . '/symfony/var-dumper/Command/ServerDumpCommand.php', 'Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/AbstractDumper.php', 'Symfony\\Component\\VarDumper\\Dumper\\CliDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/CliDumper.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\CliContextProvider' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\ContextProviderInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\RequestContextProvider' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\SourceContextProvider' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextualizedDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextualizedDumper.php', 'Symfony\\Component\\VarDumper\\Dumper\\DataDumperInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/DataDumperInterface.php', 'Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/HtmlDumper.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ServerDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ServerDumper.php', 'Symfony\\Component\\VarDumper\\Exception\\ThrowingCasterException' => __DIR__ . '/..' . '/symfony/var-dumper/Exception/ThrowingCasterException.php', - 'Symfony\\Component\\VarDumper\\Test\\VarDumperTestTrait' => __DIR__ . '/..' . '/symfony/var-dumper/Test/VarDumperTestTrait.php', + 'Symfony\\Component\\VarDumper\\Server\\Connection' => __DIR__ . '/..' . '/symfony/var-dumper/Server/Connection.php', + 'Symfony\\Component\\VarDumper\\Server\\DumpServer' => __DIR__ . '/..' . '/symfony/var-dumper/Server/DumpServer.php', 'Symfony\\Component\\VarDumper\\VarDumper' => __DIR__ . '/..' . '/symfony/var-dumper/VarDumper.php', + 'Symfony\\Component\\VarExporter\\Exception\\ClassNotFoundException' => __DIR__ . '/..' . '/symfony/var-exporter/Exception/ClassNotFoundException.php', + 'Symfony\\Component\\VarExporter\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/var-exporter/Exception/ExceptionInterface.php', + 'Symfony\\Component\\VarExporter\\Exception\\NotInstantiableTypeException' => __DIR__ . '/..' . '/symfony/var-exporter/Exception/NotInstantiableTypeException.php', + 'Symfony\\Component\\VarExporter\\Instantiator' => __DIR__ . '/..' . '/symfony/var-exporter/Instantiator.php', + 'Symfony\\Component\\VarExporter\\Internal\\Exporter' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Exporter.php', + 'Symfony\\Component\\VarExporter\\Internal\\Hydrator' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Hydrator.php', + 'Symfony\\Component\\VarExporter\\Internal\\Reference' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Reference.php', + 'Symfony\\Component\\VarExporter\\Internal\\Registry' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Registry.php', + 'Symfony\\Component\\VarExporter\\Internal\\Values' => __DIR__ . '/..' . '/symfony/var-exporter/Internal/Values.php', + 'Symfony\\Component\\VarExporter\\VarExporter' => __DIR__ . '/..' . '/symfony/var-exporter/VarExporter.php', 'Symfony\\Component\\Yaml\\Command\\LintCommand' => __DIR__ . '/..' . '/symfony/yaml/Command/LintCommand.php', 'Symfony\\Component\\Yaml\\Dumper' => __DIR__ . '/..' . '/symfony/yaml/Dumper.php', 'Symfony\\Component\\Yaml\\Escaper' => __DIR__ . '/..' . '/symfony/yaml/Escaper.php', @@ -2137,19 +2949,37 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Symfony\\Component\\Yaml\\Tag\\TaggedValue' => __DIR__ . '/..' . '/symfony/yaml/Tag/TaggedValue.php', 'Symfony\\Component\\Yaml\\Unescaper' => __DIR__ . '/..' . '/symfony/yaml/Unescaper.php', 'Symfony\\Component\\Yaml\\Yaml' => __DIR__ . '/..' . '/symfony/yaml/Yaml.php', - 'Symfony\\Polyfill\\Apcu\\Apcu' => __DIR__ . '/..' . '/symfony/polyfill-apcu/Apcu.php', + 'Symfony\\Contracts\\Cache\\CacheInterface' => __DIR__ . '/..' . '/symfony/cache-contracts/CacheInterface.php', + 'Symfony\\Contracts\\Cache\\CacheTrait' => __DIR__ . '/..' . '/symfony/cache-contracts/CacheTrait.php', + 'Symfony\\Contracts\\Cache\\CallbackInterface' => __DIR__ . '/..' . '/symfony/cache-contracts/CallbackInterface.php', + 'Symfony\\Contracts\\Cache\\ItemInterface' => __DIR__ . '/..' . '/symfony/cache-contracts/ItemInterface.php', + 'Symfony\\Contracts\\Cache\\TagAwareCacheInterface' => __DIR__ . '/..' . '/symfony/cache-contracts/TagAwareCacheInterface.php', + 'Symfony\\Contracts\\EventDispatcher\\Event' => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts/Event.php', + 'Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts/EventDispatcherInterface.php', + 'Symfony\\Contracts\\Service\\Attribute\\Required' => __DIR__ . '/..' . '/symfony/service-contracts/Attribute/Required.php', + 'Symfony\\Contracts\\Service\\Attribute\\SubscribedService' => __DIR__ . '/..' . '/symfony/service-contracts/Attribute/SubscribedService.php', + 'Symfony\\Contracts\\Service\\ResetInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ResetInterface.php', + 'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceLocatorTrait.php', + 'Symfony\\Contracts\\Service\\ServiceProviderInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceProviderInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceSubscriberInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceSubscriberTrait.php', + 'Symfony\\Contracts\\Translation\\LocaleAwareInterface' => __DIR__ . '/..' . '/symfony/translation-contracts/LocaleAwareInterface.php', + 'Symfony\\Contracts\\Translation\\TranslatableInterface' => __DIR__ . '/..' . '/symfony/translation-contracts/TranslatableInterface.php', + 'Symfony\\Contracts\\Translation\\TranslatorInterface' => __DIR__ . '/..' . '/symfony/translation-contracts/TranslatorInterface.php', + 'Symfony\\Contracts\\Translation\\TranslatorTrait' => __DIR__ . '/..' . '/symfony/translation-contracts/TranslatorTrait.php', 'Symfony\\Polyfill\\Ctype\\Ctype' => __DIR__ . '/..' . '/symfony/polyfill-ctype/Ctype.php', + 'Symfony\\Polyfill\\Intl\\Grapheme\\Grapheme' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/Grapheme.php', + 'Symfony\\Polyfill\\Intl\\Idn\\Idn' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/Idn.php', + 'Symfony\\Polyfill\\Intl\\Idn\\Info' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/Info.php', + 'Symfony\\Polyfill\\Intl\\Idn\\Resources\\unidata\\DisallowedRanges' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php', + 'Symfony\\Polyfill\\Intl\\Idn\\Resources\\unidata\\Regex' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/Resources/unidata/Regex.php', + 'Symfony\\Polyfill\\Intl\\Normalizer\\Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Normalizer.php', 'Symfony\\Polyfill\\Mbstring\\Mbstring' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/Mbstring.php', - 'Symfony\\Polyfill\\Php56\\Php56' => __DIR__ . '/..' . '/symfony/polyfill-php56/Php56.php', - 'Symfony\\Polyfill\\Php70\\Php70' => __DIR__ . '/..' . '/symfony/polyfill-php70/Php70.php', - 'Symfony\\Polyfill\\Util\\Binary' => __DIR__ . '/..' . '/symfony/polyfill-util/Binary.php', - 'Symfony\\Polyfill\\Util\\BinaryNoFuncOverload' => __DIR__ . '/..' . '/symfony/polyfill-util/BinaryNoFuncOverload.php', - 'Symfony\\Polyfill\\Util\\BinaryOnFuncOverload' => __DIR__ . '/..' . '/symfony/polyfill-util/BinaryOnFuncOverload.php', - 'Symfony\\Polyfill\\Util\\TestListener' => __DIR__ . '/..' . '/symfony/polyfill-util/TestListener.php', - 'Symfony\\Polyfill\\Util\\TestListenerForV5' => __DIR__ . '/..' . '/symfony/polyfill-util/TestListenerForV5.php', - 'Symfony\\Polyfill\\Util\\TestListenerForV6' => __DIR__ . '/..' . '/symfony/polyfill-util/TestListenerForV6.php', - 'Symfony\\Polyfill\\Util\\TestListenerForV7' => __DIR__ . '/..' . '/symfony/polyfill-util/TestListenerForV7.php', - 'Symfony\\Polyfill\\Util\\TestListenerTrait' => __DIR__ . '/..' . '/symfony/polyfill-util/TestListenerTrait.php', + 'Symfony\\Polyfill\\Php72\\Php72' => __DIR__ . '/..' . '/symfony/polyfill-php72/Php72.php', + 'Symfony\\Polyfill\\Php73\\Php73' => __DIR__ . '/..' . '/symfony/polyfill-php73/Php73.php', + 'Symfony\\Polyfill\\Php80\\Php80' => __DIR__ . '/..' . '/symfony/polyfill-php80/Php80.php', + 'Symfony\\Polyfill\\Php80\\PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/PhpToken.php', + 'Symfony\\Polyfill\\Php81\\Php81' => __DIR__ . '/..' . '/symfony/polyfill-php81/Php81.php', 'SynchroExceptionNotStarted' => __DIR__ . '/../..' . '/application/exceptions/SynchroExceptionNotStarted.php', 'System' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/System.php', 'TCPDF' => __DIR__ . '/..' . '/combodo/tcpdf/tcpdf.php', @@ -2169,6 +2999,10 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'TemplateMenuNode' => __DIR__ . '/../..' . '/application/menunode.class.inc.php', 'TemplateString' => __DIR__ . '/../..' . '/core/templatestring.class.inc.php', 'TemplateStringPlaceholder' => __DIR__ . '/../..' . '/core/templatestring.class.inc.php', + 'TheNetworg\\OAuth2\\Client\\Grant\\JwtBearer' => __DIR__ . '/..' . '/thenetworg/oauth2-azure/src/Grant/JwtBearer.php', + 'TheNetworg\\OAuth2\\Client\\Provider\\Azure' => __DIR__ . '/..' . '/thenetworg/oauth2-azure/src/Provider/Azure.php', + 'TheNetworg\\OAuth2\\Client\\Provider\\AzureResourceOwner' => __DIR__ . '/..' . '/thenetworg/oauth2-azure/src/Provider/AzureResourceOwner.php', + 'TheNetworg\\OAuth2\\Client\\Token\\AccessToken' => __DIR__ . '/..' . '/thenetworg/oauth2-azure/src/Token/AccessToken.php', 'ThemeHandler' => __DIR__ . '/../..' . '/application/themehandler.class.inc.php', 'ThemeHandlerService' => __DIR__ . '/../..' . '/application/themehandlerservice.class.inc.php', 'ToolsLog' => __DIR__ . '/../..' . '/core/log.class.inc.php', @@ -2194,13 +3028,13 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Twig\\Error\\RuntimeError' => __DIR__ . '/..' . '/twig/twig/src/Error/RuntimeError.php', 'Twig\\Error\\SyntaxError' => __DIR__ . '/..' . '/twig/twig/src/Error/SyntaxError.php', 'Twig\\ExpressionParser' => __DIR__ . '/..' . '/twig/twig/src/ExpressionParser.php', + 'Twig\\ExtensionSet' => __DIR__ . '/..' . '/twig/twig/src/ExtensionSet.php', 'Twig\\Extension\\AbstractExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/AbstractExtension.php', 'Twig\\Extension\\CoreExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/CoreExtension.php', 'Twig\\Extension\\DebugExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/DebugExtension.php', 'Twig\\Extension\\EscaperExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/EscaperExtension.php', 'Twig\\Extension\\ExtensionInterface' => __DIR__ . '/..' . '/twig/twig/src/Extension/ExtensionInterface.php', 'Twig\\Extension\\GlobalsInterface' => __DIR__ . '/..' . '/twig/twig/src/Extension/GlobalsInterface.php', - 'Twig\\Extension\\InitRuntimeInterface' => __DIR__ . '/..' . '/twig/twig/src/Extension/InitRuntimeInterface.php', 'Twig\\Extension\\OptimizerExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/OptimizerExtension.php', 'Twig\\Extension\\ProfilerExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/ProfilerExtension.php', 'Twig\\Extension\\RuntimeExtensionInterface' => __DIR__ . '/..' . '/twig/twig/src/Extension/RuntimeExtensionInterface.php', @@ -2211,14 +3045,13 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Twig\\Lexer' => __DIR__ . '/..' . '/twig/twig/src/Lexer.php', 'Twig\\Loader\\ArrayLoader' => __DIR__ . '/..' . '/twig/twig/src/Loader/ArrayLoader.php', 'Twig\\Loader\\ChainLoader' => __DIR__ . '/..' . '/twig/twig/src/Loader/ChainLoader.php', - 'Twig\\Loader\\ExistsLoaderInterface' => __DIR__ . '/..' . '/twig/twig/src/Loader/ExistsLoaderInterface.php', 'Twig\\Loader\\FilesystemLoader' => __DIR__ . '/..' . '/twig/twig/src/Loader/FilesystemLoader.php', 'Twig\\Loader\\LoaderInterface' => __DIR__ . '/..' . '/twig/twig/src/Loader/LoaderInterface.php', - 'Twig\\Loader\\SourceContextLoaderInterface' => __DIR__ . '/..' . '/twig/twig/src/Loader/SourceContextLoaderInterface.php', 'Twig\\Markup' => __DIR__ . '/..' . '/twig/twig/src/Markup.php', 'Twig\\NodeTraverser' => __DIR__ . '/..' . '/twig/twig/src/NodeTraverser.php', 'Twig\\NodeVisitor\\AbstractNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php', 'Twig\\NodeVisitor\\EscaperNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php', + 'Twig\\NodeVisitor\\MacroAutoImportNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php', 'Twig\\NodeVisitor\\NodeVisitorInterface' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/NodeVisitorInterface.php', 'Twig\\NodeVisitor\\OptimizerNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php', 'Twig\\NodeVisitor\\SafeAnalysisNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php', @@ -2227,6 +3060,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Twig\\Node\\BlockNode' => __DIR__ . '/..' . '/twig/twig/src/Node/BlockNode.php', 'Twig\\Node\\BlockReferenceNode' => __DIR__ . '/..' . '/twig/twig/src/Node/BlockReferenceNode.php', 'Twig\\Node\\BodyNode' => __DIR__ . '/..' . '/twig/twig/src/Node/BodyNode.php', + 'Twig\\Node\\CheckSecurityCallNode' => __DIR__ . '/..' . '/twig/twig/src/Node/CheckSecurityCallNode.php', 'Twig\\Node\\CheckSecurityNode' => __DIR__ . '/..' . '/twig/twig/src/Node/CheckSecurityNode.php', 'Twig\\Node\\CheckToStringNode' => __DIR__ . '/..' . '/twig/twig/src/Node/CheckToStringNode.php', 'Twig\\Node\\DeprecatedNode' => __DIR__ . '/..' . '/twig/twig/src/Node/DeprecatedNode.php', @@ -2260,6 +3094,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Twig\\Node\\Expression\\Binary\\OrBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/OrBinary.php', 'Twig\\Node\\Expression\\Binary\\PowerBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/PowerBinary.php', 'Twig\\Node\\Expression\\Binary\\RangeBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/RangeBinary.php', + 'Twig\\Node\\Expression\\Binary\\SpaceshipBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php', 'Twig\\Node\\Expression\\Binary\\StartsWithBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php', 'Twig\\Node\\Expression\\Binary\\SubBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/SubBinary.php', 'Twig\\Node\\Expression\\BlockReferenceExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/BlockReferenceExpression.php', @@ -2288,6 +3123,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Twig\\Node\\Expression\\Unary\\NegUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/NegUnary.php', 'Twig\\Node\\Expression\\Unary\\NotUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/NotUnary.php', 'Twig\\Node\\Expression\\Unary\\PosUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/PosUnary.php', + 'Twig\\Node\\Expression\\VariadicExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/VariadicExpression.php', 'Twig\\Node\\FlushNode' => __DIR__ . '/..' . '/twig/twig/src/Node/FlushNode.php', 'Twig\\Node\\ForLoopNode' => __DIR__ . '/..' . '/twig/twig/src/Node/ForLoopNode.php', 'Twig\\Node\\ForNode' => __DIR__ . '/..' . '/twig/twig/src/Node/ForNode.php', @@ -2301,10 +3137,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Twig\\Node\\NodeOutputInterface' => __DIR__ . '/..' . '/twig/twig/src/Node/NodeOutputInterface.php', 'Twig\\Node\\PrintNode' => __DIR__ . '/..' . '/twig/twig/src/Node/PrintNode.php', 'Twig\\Node\\SandboxNode' => __DIR__ . '/..' . '/twig/twig/src/Node/SandboxNode.php', - 'Twig\\Node\\SandboxedPrintNode' => __DIR__ . '/..' . '/twig/twig/src/Node/SandboxedPrintNode.php', 'Twig\\Node\\SetNode' => __DIR__ . '/..' . '/twig/twig/src/Node/SetNode.php', - 'Twig\\Node\\SetTempNode' => __DIR__ . '/..' . '/twig/twig/src/Node/SetTempNode.php', - 'Twig\\Node\\SpacelessNode' => __DIR__ . '/..' . '/twig/twig/src/Node/SpacelessNode.php', 'Twig\\Node\\TextNode' => __DIR__ . '/..' . '/twig/twig/src/Node/TextNode.php', 'Twig\\Node\\WithNode' => __DIR__ . '/..' . '/twig/twig/src/Node/WithNode.php', 'Twig\\Parser' => __DIR__ . '/..' . '/twig/twig/src/Parser.php', @@ -2339,7 +3172,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Twig\\TokenParser\\DoTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/DoTokenParser.php', 'Twig\\TokenParser\\EmbedTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/EmbedTokenParser.php', 'Twig\\TokenParser\\ExtendsTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/ExtendsTokenParser.php', - 'Twig\\TokenParser\\FilterTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/FilterTokenParser.php', 'Twig\\TokenParser\\FlushTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/FlushTokenParser.php', 'Twig\\TokenParser\\ForTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/ForTokenParser.php', 'Twig\\TokenParser\\FromTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/FromTokenParser.php', @@ -2349,7 +3181,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Twig\\TokenParser\\MacroTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/MacroTokenParser.php', 'Twig\\TokenParser\\SandboxTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/SandboxTokenParser.php', 'Twig\\TokenParser\\SetTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/SetTokenParser.php', - 'Twig\\TokenParser\\SpacelessTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/SpacelessTokenParser.php', 'Twig\\TokenParser\\TokenParserInterface' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/TokenParserInterface.php', 'Twig\\TokenParser\\UseTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/UseTokenParser.php', 'Twig\\TokenParser\\WithTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/WithTokenParser.php', @@ -2359,202 +3190,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Twig\\TwigTest' => __DIR__ . '/..' . '/twig/twig/src/TwigTest.php', 'Twig\\Util\\DeprecationCollector' => __DIR__ . '/..' . '/twig/twig/src/Util/DeprecationCollector.php', 'Twig\\Util\\TemplateDirIterator' => __DIR__ . '/..' . '/twig/twig/src/Util/TemplateDirIterator.php', - 'Twig_Autoloader' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Autoloader.php', - 'Twig_BaseNodeVisitor' => __DIR__ . '/..' . '/twig/twig/lib/Twig/BaseNodeVisitor.php', - 'Twig_CacheInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/CacheInterface.php', - 'Twig_Cache_Filesystem' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Cache/Filesystem.php', - 'Twig_Cache_Null' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Cache/Null.php', - 'Twig_Compiler' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Compiler.php', - 'Twig_CompilerInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/CompilerInterface.php', - 'Twig_ContainerRuntimeLoader' => __DIR__ . '/..' . '/twig/twig/lib/Twig/ContainerRuntimeLoader.php', - 'Twig_Environment' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Environment.php', - 'Twig_Error' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Error.php', - 'Twig_Error_Loader' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Error/Loader.php', - 'Twig_Error_Runtime' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Error/Runtime.php', - 'Twig_Error_Syntax' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Error/Syntax.php', - 'Twig_ExistsLoaderInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/ExistsLoaderInterface.php', - 'Twig_ExpressionParser' => __DIR__ . '/..' . '/twig/twig/lib/Twig/ExpressionParser.php', - 'Twig_Extension' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension.php', - 'Twig_ExtensionInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/ExtensionInterface.php', - 'Twig_Extension_Core' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Core.php', - 'Twig_Extension_Debug' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Debug.php', - 'Twig_Extension_Escaper' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Escaper.php', - 'Twig_Extension_GlobalsInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/GlobalsInterface.php', - 'Twig_Extension_InitRuntimeInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/InitRuntimeInterface.php', - 'Twig_Extension_Optimizer' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Optimizer.php', - 'Twig_Extension_Profiler' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Profiler.php', - 'Twig_Extension_Sandbox' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Sandbox.php', - 'Twig_Extension_Staging' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Staging.php', - 'Twig_Extension_StringLoader' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/StringLoader.php', - 'Twig_FactoryRuntimeLoader' => __DIR__ . '/..' . '/twig/twig/lib/Twig/FactoryRuntimeLoader.php', - 'Twig_FileExtensionEscapingStrategy' => __DIR__ . '/..' . '/twig/twig/lib/Twig/FileExtensionEscapingStrategy.php', - 'Twig_Filter' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Filter.php', - 'Twig_FilterCallableInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/FilterCallableInterface.php', - 'Twig_FilterInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/FilterInterface.php', - 'Twig_Filter_Function' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Filter/Function.php', - 'Twig_Filter_Method' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Filter/Method.php', - 'Twig_Filter_Node' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Filter/Node.php', - 'Twig_Function' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Function.php', - 'Twig_FunctionCallableInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/FunctionCallableInterface.php', - 'Twig_FunctionInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/FunctionInterface.php', - 'Twig_Function_Function' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Function/Function.php', - 'Twig_Function_Method' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Function/Method.php', - 'Twig_Function_Node' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Function/Node.php', - 'Twig_Lexer' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Lexer.php', - 'Twig_LexerInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/LexerInterface.php', - 'Twig_LoaderInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/LoaderInterface.php', - 'Twig_Loader_Array' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Loader/Array.php', - 'Twig_Loader_Chain' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Loader/Chain.php', - 'Twig_Loader_Filesystem' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Loader/Filesystem.php', - 'Twig_Loader_String' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Loader/String.php', - 'Twig_Markup' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Markup.php', - 'Twig_Node' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node.php', - 'Twig_NodeCaptureInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeCaptureInterface.php', - 'Twig_NodeInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeInterface.php', - 'Twig_NodeOutputInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeOutputInterface.php', - 'Twig_NodeTraverser' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeTraverser.php', - 'Twig_NodeVisitorInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeVisitorInterface.php', - 'Twig_NodeVisitor_Escaper' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeVisitor/Escaper.php', - 'Twig_NodeVisitor_Optimizer' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeVisitor/Optimizer.php', - 'Twig_NodeVisitor_SafeAnalysis' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeVisitor/SafeAnalysis.php', - 'Twig_NodeVisitor_Sandbox' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeVisitor/Sandbox.php', - 'Twig_Node_AutoEscape' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/AutoEscape.php', - 'Twig_Node_Block' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Block.php', - 'Twig_Node_BlockReference' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/BlockReference.php', - 'Twig_Node_Body' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Body.php', - 'Twig_Node_CheckSecurity' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/CheckSecurity.php', - 'Twig_Node_Deprecated' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Deprecated.php', - 'Twig_Node_Do' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Do.php', - 'Twig_Node_Embed' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Embed.php', - 'Twig_Node_Expression' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression.php', - 'Twig_Node_Expression_Array' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Array.php', - 'Twig_Node_Expression_AssignName' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/AssignName.php', - 'Twig_Node_Expression_Binary' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary.php', - 'Twig_Node_Expression_Binary_Add' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Add.php', - 'Twig_Node_Expression_Binary_And' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/And.php', - 'Twig_Node_Expression_Binary_BitwiseAnd' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseAnd.php', - 'Twig_Node_Expression_Binary_BitwiseOr' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseOr.php', - 'Twig_Node_Expression_Binary_BitwiseXor' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseXor.php', - 'Twig_Node_Expression_Binary_Concat' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Concat.php', - 'Twig_Node_Expression_Binary_Div' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Div.php', - 'Twig_Node_Expression_Binary_EndsWith' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/EndsWith.php', - 'Twig_Node_Expression_Binary_Equal' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Equal.php', - 'Twig_Node_Expression_Binary_FloorDiv' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/FloorDiv.php', - 'Twig_Node_Expression_Binary_Greater' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Greater.php', - 'Twig_Node_Expression_Binary_GreaterEqual' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/GreaterEqual.php', - 'Twig_Node_Expression_Binary_In' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/In.php', - 'Twig_Node_Expression_Binary_Less' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Less.php', - 'Twig_Node_Expression_Binary_LessEqual' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/LessEqual.php', - 'Twig_Node_Expression_Binary_Matches' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Matches.php', - 'Twig_Node_Expression_Binary_Mod' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Mod.php', - 'Twig_Node_Expression_Binary_Mul' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Mul.php', - 'Twig_Node_Expression_Binary_NotEqual' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/NotEqual.php', - 'Twig_Node_Expression_Binary_NotIn' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/NotIn.php', - 'Twig_Node_Expression_Binary_Or' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Or.php', - 'Twig_Node_Expression_Binary_Power' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Power.php', - 'Twig_Node_Expression_Binary_Range' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Range.php', - 'Twig_Node_Expression_Binary_StartsWith' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/StartsWith.php', - 'Twig_Node_Expression_Binary_Sub' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Sub.php', - 'Twig_Node_Expression_BlockReference' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/BlockReference.php', - 'Twig_Node_Expression_Call' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Call.php', - 'Twig_Node_Expression_Conditional' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Conditional.php', - 'Twig_Node_Expression_Constant' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Constant.php', - 'Twig_Node_Expression_ExtensionReference' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/ExtensionReference.php', - 'Twig_Node_Expression_Filter' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Filter.php', - 'Twig_Node_Expression_Filter_Default' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Filter/Default.php', - 'Twig_Node_Expression_Function' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Function.php', - 'Twig_Node_Expression_GetAttr' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/GetAttr.php', - 'Twig_Node_Expression_MethodCall' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/MethodCall.php', - 'Twig_Node_Expression_Name' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Name.php', - 'Twig_Node_Expression_NullCoalesce' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/NullCoalesce.php', - 'Twig_Node_Expression_Parent' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Parent.php', - 'Twig_Node_Expression_TempName' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/TempName.php', - 'Twig_Node_Expression_Test' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test.php', - 'Twig_Node_Expression_Test_Constant' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Constant.php', - 'Twig_Node_Expression_Test_Defined' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Defined.php', - 'Twig_Node_Expression_Test_Divisibleby' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Divisibleby.php', - 'Twig_Node_Expression_Test_Even' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Even.php', - 'Twig_Node_Expression_Test_Null' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Null.php', - 'Twig_Node_Expression_Test_Odd' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Odd.php', - 'Twig_Node_Expression_Test_Sameas' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Sameas.php', - 'Twig_Node_Expression_Unary' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Unary.php', - 'Twig_Node_Expression_Unary_Neg' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Unary/Neg.php', - 'Twig_Node_Expression_Unary_Not' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Unary/Not.php', - 'Twig_Node_Expression_Unary_Pos' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Unary/Pos.php', - 'Twig_Node_Flush' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Flush.php', - 'Twig_Node_For' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/For.php', - 'Twig_Node_ForLoop' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/ForLoop.php', - 'Twig_Node_If' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/If.php', - 'Twig_Node_Import' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Import.php', - 'Twig_Node_Include' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Include.php', - 'Twig_Node_Macro' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Macro.php', - 'Twig_Node_Module' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Module.php', - 'Twig_Node_Print' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Print.php', - 'Twig_Node_Sandbox' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Sandbox.php', - 'Twig_Node_SandboxedPrint' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/SandboxedPrint.php', - 'Twig_Node_Set' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Set.php', - 'Twig_Node_SetTemp' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/SetTemp.php', - 'Twig_Node_Spaceless' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Spaceless.php', - 'Twig_Node_Text' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Text.php', - 'Twig_Node_With' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/With.php', - 'Twig_Parser' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Parser.php', - 'Twig_ParserInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/ParserInterface.php', - 'Twig_Profiler_Dumper_Base' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Dumper/Base.php', - 'Twig_Profiler_Dumper_Blackfire' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Dumper/Blackfire.php', - 'Twig_Profiler_Dumper_Html' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Dumper/Html.php', - 'Twig_Profiler_Dumper_Text' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Dumper/Text.php', - 'Twig_Profiler_NodeVisitor_Profiler' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/NodeVisitor/Profiler.php', - 'Twig_Profiler_Node_EnterProfile' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Node/EnterProfile.php', - 'Twig_Profiler_Node_LeaveProfile' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Node/LeaveProfile.php', - 'Twig_Profiler_Profile' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Profile.php', - 'Twig_RuntimeLoaderInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/RuntimeLoaderInterface.php', - 'Twig_Sandbox_SecurityError' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityError.php', - 'Twig_Sandbox_SecurityNotAllowedFilterError' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFilterError.php', - 'Twig_Sandbox_SecurityNotAllowedFunctionError' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFunctionError.php', - 'Twig_Sandbox_SecurityNotAllowedMethodError' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedMethodError.php', - 'Twig_Sandbox_SecurityNotAllowedPropertyError' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedPropertyError.php', - 'Twig_Sandbox_SecurityNotAllowedTagError' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedTagError.php', - 'Twig_Sandbox_SecurityPolicy' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityPolicy.php', - 'Twig_Sandbox_SecurityPolicyInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityPolicyInterface.php', - 'Twig_SimpleFilter' => __DIR__ . '/..' . '/twig/twig/lib/Twig/SimpleFilter.php', - 'Twig_SimpleFunction' => __DIR__ . '/..' . '/twig/twig/lib/Twig/SimpleFunction.php', - 'Twig_SimpleTest' => __DIR__ . '/..' . '/twig/twig/lib/Twig/SimpleTest.php', - 'Twig_Source' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Source.php', - 'Twig_SourceContextLoaderInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/SourceContextLoaderInterface.php', - 'Twig_Template' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Template.php', - 'Twig_TemplateInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TemplateInterface.php', - 'Twig_TemplateWrapper' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TemplateWrapper.php', - 'Twig_Test' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Test.php', - 'Twig_TestCallableInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TestCallableInterface.php', - 'Twig_TestInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TestInterface.php', - 'Twig_Token' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Token.php', - 'Twig_TokenParser' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser.php', - 'Twig_TokenParserBroker' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParserBroker.php', - 'Twig_TokenParserBrokerInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParserBrokerInterface.php', - 'Twig_TokenParserInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParserInterface.php', - 'Twig_TokenParser_AutoEscape' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/AutoEscape.php', - 'Twig_TokenParser_Block' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Block.php', - 'Twig_TokenParser_Deprecated' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Deprecated.php', - 'Twig_TokenParser_Do' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Do.php', - 'Twig_TokenParser_Embed' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Embed.php', - 'Twig_TokenParser_Extends' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Extends.php', - 'Twig_TokenParser_Filter' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Filter.php', - 'Twig_TokenParser_Flush' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Flush.php', - 'Twig_TokenParser_For' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/For.php', - 'Twig_TokenParser_From' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/From.php', - 'Twig_TokenParser_If' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/If.php', - 'Twig_TokenParser_Import' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Import.php', - 'Twig_TokenParser_Include' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Include.php', - 'Twig_TokenParser_Macro' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Macro.php', - 'Twig_TokenParser_Sandbox' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Sandbox.php', - 'Twig_TokenParser_Set' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Set.php', - 'Twig_TokenParser_Spaceless' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Spaceless.php', - 'Twig_TokenParser_Use' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Use.php', - 'Twig_TokenParser_With' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/With.php', - 'Twig_TokenStream' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenStream.php', - 'Twig_Util_DeprecationCollector' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Util/DeprecationCollector.php', - 'Twig_Util_TemplateDirIterator' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Util/TemplateDirIterator.php', - 'TypeError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/TypeError.php', 'UIExtKeyWidget' => __DIR__ . '/../..' . '/application/ui.extkeywidget.class.inc.php', 'UIHTMLEditorWidget' => __DIR__ . '/../..' . '/application/ui.htmleditorwidget.class.inc.php', 'UILinksWidget' => __DIR__ . '/../..' . '/application/ui.linkswidget.class.inc.php', @@ -2566,6 +3201,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'URLPopupMenuItem' => __DIR__ . '/../..' . '/application/applicationextension.inc.php', 'UnaryExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php', 'UnauthenticatedWebPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/UnauthenticatedWebPage.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', 'UnknownClassOqlException' => __DIR__ . '/../..' . '/core/oql/oqlinterpreter.class.inc.php', 'User' => __DIR__ . '/../..' . '/core/userrights.class.inc.php', 'UserDashboard' => __DIR__ . '/../..' . '/application/user.dashboard.class.inc.php', @@ -2573,6 +3209,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'UserRightException' => __DIR__ . '/../..' . '/application/exceptions/UserRightException.php', 'UserRights' => __DIR__ . '/../..' . '/core/userrights.class.inc.php', 'UserRightsAddOnAPI' => __DIR__ . '/../..' . '/core/userrights.class.inc.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', 'ValueSetDefinition' => __DIR__ . '/../..' . '/core/valuesetdef.class.inc.php', 'ValueSetEnum' => __DIR__ . '/../..' . '/core/valuesetdef.class.inc.php', 'ValueSetEnumClasses' => __DIR__ . '/../..' . '/core/valuesetdef.class.inc.php', @@ -2583,6 +3220,9 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'VariableOqlExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php', 'WebPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/WebPage.php', 'WebPageMenuNode' => __DIR__ . '/../..' . '/application/menunode.class.inc.php', + 'Webmozart\\Assert\\Assert' => __DIR__ . '/..' . '/webmozart/assert/src/Assert.php', + 'Webmozart\\Assert\\InvalidArgumentException' => __DIR__ . '/..' . '/webmozart/assert/src/InvalidArgumentException.php', + 'Webmozart\\Assert\\Mixin' => __DIR__ . '/..' . '/webmozart/assert/src/Mixin.php', 'WeeklyRotatingLogFileNameBuilder' => __DIR__ . '/../..' . '/core/log.class.inc.php', 'WizardHelper' => __DIR__ . '/../..' . '/application/wizardhelper.class.inc.php', 'XLSXWriter' => __DIR__ . '/../..' . '/application/xlsxwriter.class.php', @@ -2649,6 +3289,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'ormStopWatch' => __DIR__ . '/../..' . '/core/ormstopwatch.class.inc.php', 'ormStyle' => __DIR__ . '/../..' . '/core/ormStyle.class.inc.php', 'ormTagSet' => __DIR__ . '/../..' . '/core/ormtagset.class.inc.php', + 'phpCAS' => __DIR__ . '/..' . '/apereo/phpcas/source/CAS.php', 'privUITransaction' => __DIR__ . '/../..' . '/application/transaction.class.inc.php', 'privUITransactionFile' => __DIR__ . '/../..' . '/application/transaction.class.inc.php', 'privUITransactionSession' => __DIR__ . '/../..' . '/application/transaction.class.inc.php', @@ -2658,11 +3299,11 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInit0018331147de7601e7552f7da8e3bb8b::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit0018331147de7601e7552f7da8e3bb8b::$prefixDirsPsr4; - $loader->prefixesPsr0 = ComposerStaticInit0018331147de7601e7552f7da8e3bb8b::$prefixesPsr0; - $loader->fallbackDirsPsr0 = ComposerStaticInit0018331147de7601e7552f7da8e3bb8b::$fallbackDirsPsr0; - $loader->classMap = ComposerStaticInit0018331147de7601e7552f7da8e3bb8b::$classMap; + $loader->prefixLengthsPsr4 = ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::$prefixesPsr0; + $loader->fallbackDirsPsr0 = ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::$fallbackDirsPsr0; + $loader->classMap = ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::$classMap; }, null, ClassLoader::class); } diff --git a/lib/composer/installed.json b/lib/composer/installed.json index 36d4536cdb..71821afa0b 100644 --- a/lib/composer/installed.json +++ b/lib/composer/installed.json @@ -1,18 +1,89 @@ { "packages": [ { - "name": "combodo/tcpdf", - "version": "6.3.5", - "version_normalized": "6.3.5.0", + "name": "apereo/phpcas", + "version": "1.5.0", + "version_normalized": "1.5.0.0", "source": { "type": "git", - "url": "https://github.com/combodo-itop-libs/TCPDF.git", - "reference": "aedd4b7b8cf7fcc24e617c405c9d3304150f4b94" + "url": "https://github.com/apereo/phpCAS.git", + "reference": "d6f5797fb568726f34c8e48741776d81e4a2646b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/combodo-itop-libs/TCPDF/zipball/aedd4b7b8cf7fcc24e617c405c9d3304150f4b94", - "reference": "aedd4b7b8cf7fcc24e617c405c9d3304150f4b94", + "url": "https://api.github.com/repos/apereo/phpCAS/zipball/d6f5797fb568726f34c8e48741776d81e4a2646b", + "reference": "d6f5797fb568726f34c8e48741776d81e4a2646b", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-dom": "*", + "php": ">=7.1.0", + "psr/log": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "monolog/monolog": "^1.0.0 || ^2.0.0", + "phpstan/phpstan": "^1.5", + "phpunit/phpunit": ">=7.5" + }, + "time": "2022-05-03T21:12:54+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "source/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Joachim Fritschi", + "email": "jfritschi@freenet.de", + "homepage": "https://github.com/jfritschi" + }, + { + "name": "Adam Franco", + "homepage": "https://github.com/adamfranco" + }, + { + "name": "Henry Pan", + "homepage": "https://github.com/phy25" + } + ], + "description": "Provides a simple API for authenticating users against a CAS server", + "homepage": "https://wiki.jasig.org/display/CASC/phpCAS", + "keywords": [ + "apereo", + "cas", + "jasig" + ], + "support": { + "issues": "https://github.com/apereo/phpCAS/issues", + "source": "https://github.com/apereo/phpCAS/tree/1.5.0" + }, + "install-path": "../apereo/phpcas" + }, + { + "name": "combodo/tcpdf", + "version": "6.4.4", + "version_normalized": "6.4.4.0", + "source": { + "type": "git", + "url": "https://github.com/combodo-itop-libs/TCPDF.git", + "reference": "0e31c013ccd000aa6762e9186778aa6e259ac8e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/combodo-itop-libs/TCPDF/zipball/0e31c013ccd000aa6762e9186778aa6e259ac8e8", + "reference": "0e31c013ccd000aa6762e9186778aa6e259ac8e8", "shasum": "" }, "require": { @@ -21,7 +92,7 @@ "replace": { "tecnickcom/tcpdf": "self.version" }, - "time": "2020-09-28T12:19:09+00:00", + "time": "2022-03-10T14:36:39+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -59,23 +130,1021 @@ "email": "contact@combodo.com" } ], - "description": "TCPDF fork adding requirements for iTop: Specific fonts.", + "description": "TCPDF is a PHP class for generating PDF documents and barcodes.", "homepage": "https://github.com/combodo-itop-libs/TCPDF", + "keywords": [ + "PDFD32000-2008", + "TCPDF", + "barcodes", + "datamatrix", + "pdf", + "pdf417", + "qrcode" + ], + "support": { + "source": "https://github.com/combodo-itop-libs/TCPDF/tree/6.4.4" + }, + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_donations¤cy_code=GBP&business=paypal@tecnick.com&item_name=donation%20for%20tcpdf%20project", + "type": "custom" + } + ], "install-path": "../combodo/tcpdf" }, { - "name": "nikic/php-parser", - "version": "v4.12.0", - "version_normalized": "4.12.0.0", + "name": "firebase/php-jwt", + "version": "v6.3.0", + "version_normalized": "6.3.0.0", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "6608f01670c3cc5079e18c1dab1104e002579143" + "url": "https://github.com/firebase/php-jwt.git", + "reference": "018dfc4e1da92ad8a1b90adc4893f476a3b41cb8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143", - "reference": "6608f01670c3cc5079e18c1dab1104e002579143", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/018dfc4e1da92ad8a1b90adc4893f476a3b41cb8", + "reference": "018dfc4e1da92ad8a1b90adc4893f476a3b41cb8", + "shasum": "" + }, + "require": { + "php": "^7.1||^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.5||^7.4", + "phpspec/prophecy-phpunit": "^1.1", + "phpunit/phpunit": "^7.5||^9.5", + "psr/cache": "^1.0||^2.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "time": "2022-07-15T16:48:45+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.3.0" + }, + "install-path": "../firebase/php-jwt" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.4.5", + "version_normalized": "7.4.5.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "1dd98b0564cb3f6bd16ce683cb755f94c10fbd82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/1dd98b0564cb3f6bd16ce683cb755f94c10fbd82", + "reference": "1dd98b0564cb3f6bd16ce683cb755f94c10fbd82", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.9 || ^2.4", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1 || ^2.0 || ^3.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" + }, + "time": "2022-06-20T22:16:13+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.4.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "install-path": "../guzzlehttp/guzzle" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.1", + "version_normalized": "1.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "time": "2021-10-22T20:56:57+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.5.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "install-path": "../guzzlehttp/promises" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.4.0", + "version_normalized": "2.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "13388f00956b1503577598873fffb5ae994b5737" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/13388f00956b1503577598873fffb5ae994b5737", + "reference": "13388f00956b1503577598873fffb5ae994b5737", + "shasum": "" + }, + "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" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "time": "2022-06-20T21:43:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.4.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "install-path": "../guzzlehttp/psr7" + }, + { + "name": "laminas/laminas-loader", + "version": "2.8.0", + "version_normalized": "2.8.0.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-loader.git", + "reference": "d0589ec9dd48365fd95ad10d1c906efd7711c16b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-loader/zipball/d0589ec9dd48365fd95ad10d1c906efd7711c16b", + "reference": "d0589ec9dd48365fd95ad10d1c906efd7711c16b", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-loader": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.2.1", + "phpunit/phpunit": "^9.3" + }, + "time": "2021-09-02T18:30:53+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Laminas\\Loader\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Autoloading and plugin loading strategies", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "loader" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-loader/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-loader/issues", + "rss": "https://github.com/laminas/laminas-loader/releases.atom", + "source": "https://github.com/laminas/laminas-loader" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "install-path": "../laminas/laminas-loader" + }, + { + "name": "laminas/laminas-mail", + "version": "2.16.0", + "version_normalized": "2.16.0.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-mail.git", + "reference": "1ee1a384b96c8af29ecad9b3a7adc27a150ebc49" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-mail/zipball/1ee1a384b96c8af29ecad9b3a7adc27a150ebc49", + "reference": "1ee1a384b96c8af29ecad9b3a7adc27a150ebc49", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "laminas/laminas-loader": "^2.8", + "laminas/laminas-mime": "^2.9.1", + "laminas/laminas-stdlib": "^3.6", + "laminas/laminas-validator": "^2.15", + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "symfony/polyfill-intl-idn": "^1.24.0", + "symfony/polyfill-mbstring": "^1.12.0", + "webmozart/assert": "^1.10" + }, + "conflict": { + "zendframework/zend-mail": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-crypt": "^2.6 || ^3.4", + "laminas/laminas-db": "^2.13.3", + "laminas/laminas-servicemanager": "^3.7", + "phpunit/phpunit": "^9.5.5", + "psalm/plugin-phpunit": "^0.15.1", + "symfony/process": "^5.3.7", + "vimeo/psalm": "^4.7" + }, + "suggest": { + "laminas/laminas-crypt": "Crammd5 support in SMTP Auth", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" + }, + "time": "2022-02-23T21:08:17+00:00", + "type": "library", + "extra": { + "laminas": { + "component": "Laminas\\Mail", + "config-provider": "Laminas\\Mail\\ConfigProvider" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Laminas\\Mail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Provides generalized functionality to compose and send both text and MIME-compliant multipart e-mail messages", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "mail" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-mail/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-mail/issues", + "rss": "https://github.com/laminas/laminas-mail/releases.atom", + "source": "https://github.com/laminas/laminas-mail" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "install-path": "../laminas/laminas-mail" + }, + { + "name": "laminas/laminas-mime", + "version": "2.9.1", + "version_normalized": "2.9.1.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-mime.git", + "reference": "72d21a1b4bb7086d4a4d7058c0abca180b209184" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-mime/zipball/72d21a1b4bb7086d4a4d7058c0abca180b209184", + "reference": "72d21a1b4bb7086d4a4d7058c0abca180b209184", + "shasum": "" + }, + "require": { + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-mime": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.2.1", + "laminas/laminas-mail": "^2.12", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "laminas/laminas-mail": "Laminas\\Mail component" + }, + "time": "2021-09-20T21:19:24+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Laminas\\Mime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Create and parse MIME messages and parts", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "mime" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-mime/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-mime/issues", + "rss": "https://github.com/laminas/laminas-mime/releases.atom", + "source": "https://github.com/laminas/laminas-mime" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "install-path": "../laminas/laminas-mime" + }, + { + "name": "laminas/laminas-servicemanager", + "version": "3.16.0", + "version_normalized": "3.16.0.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-servicemanager.git", + "reference": "863c66733740cd36ebf5e700f4258ef2c68a2a24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/863c66733740cd36ebf5e700f4258ef2c68a2a24", + "reference": "863c66733740cd36ebf5e700f4258ef2c68a2a24", + "shasum": "" + }, + "require": { + "laminas/laminas-stdlib": "^3.2.1", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0", + "psr/container": "^1.0" + }, + "conflict": { + "ext-psr": "*", + "laminas/laminas-code": "<3.3.1", + "zendframework/zend-code": "<3.3.1", + "zendframework/zend-servicemanager": "*" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "replace": { + "container-interop/container-interop": "^1.2.0" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.0", + "laminas/laminas-coding-standard": "~2.3.0", + "laminas/laminas-container-config-test": "^0.7", + "laminas/laminas-dependency-plugin": "^2.1.2", + "mikey179/vfsstream": "^1.6.10@alpha", + "ocramius/proxy-manager": "^2.11", + "phpbench/phpbench": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5.5", + "psalm/plugin-phpunit": "^0.17.0", + "vimeo/psalm": "^4.8" + }, + "suggest": { + "ocramius/proxy-manager": "ProxyManager ^2.1.1 to handle lazy initialization of services" + }, + "time": "2022-07-27T14:58:17+00:00", + "bin": [ + "bin/generate-deps-for-config-factory", + "bin/generate-factory-for-class" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "src/autoload.php" + ], + "psr-4": { + "Laminas\\ServiceManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Factory-Driven Dependency Injection Container", + "homepage": "https://laminas.dev", + "keywords": [ + "PSR-11", + "dependency-injection", + "di", + "dic", + "laminas", + "service-manager", + "servicemanager" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-servicemanager/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-servicemanager/issues", + "rss": "https://github.com/laminas/laminas-servicemanager/releases.atom", + "source": "https://github.com/laminas/laminas-servicemanager" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "install-path": "../laminas/laminas-servicemanager" + }, + { + "name": "laminas/laminas-stdlib", + "version": "3.12.0", + "version_normalized": "3.12.0.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-stdlib.git", + "reference": "c5aed3c798018e31fbb7b1e421b8d96bf2cda453" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/c5aed3c798018e31fbb7b1e421b8d96bf2cda453", + "reference": "c5aed3c798018e31fbb7b1e421b8d96bf2cda453", + "shasum": "" + }, + "require": { + "php": "^7.4 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-stdlib": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.3.0", + "phpbench/phpbench": "^1.2.6", + "phpstan/phpdoc-parser": "^0.5.4", + "phpunit/phpunit": "^9.5.23", + "psalm/plugin-phpunit": "^0.17.0", + "vimeo/psalm": "^4.26" + }, + "time": "2022-08-22T22:55:58+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Laminas\\Stdlib\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "SPL extensions, array utilities, error handlers, and more", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "stdlib" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-stdlib/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-stdlib/issues", + "rss": "https://github.com/laminas/laminas-stdlib/releases.atom", + "source": "https://github.com/laminas/laminas-stdlib" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "install-path": "../laminas/laminas-stdlib" + }, + { + "name": "laminas/laminas-validator", + "version": "2.23.0", + "version_normalized": "2.23.0.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-validator.git", + "reference": "6d61b6cc3b222f13807a18d9247cdfb084958b03" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/6d61b6cc3b222f13807a18d9247cdfb084958b03", + "reference": "6d61b6cc3b222f13807a18d9247cdfb084958b03", + "shasum": "" + }, + "require": { + "laminas/laminas-servicemanager": "^3.12.0", + "laminas/laminas-stdlib": "^3.10", + "php": "^7.4 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-validator": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.3.0", + "laminas/laminas-db": "^2.7", + "laminas/laminas-filter": "^2.14.0", + "laminas/laminas-http": "^2.14.2", + "laminas/laminas-i18n": "^2.15.0", + "laminas/laminas-session": "^2.12.1", + "laminas/laminas-uri": "^2.9.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5.21", + "psalm/plugin-phpunit": "^0.17.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "vimeo/psalm": "^4.24.0" + }, + "suggest": { + "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator", + "laminas/laminas-filter": "Laminas\\Filter component, required by the Digits validator", + "laminas/laminas-i18n": "Laminas\\I18n component to allow translation of validation error messages", + "laminas/laminas-i18n-resources": "Translations of validator messages", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "laminas/laminas-session": "Laminas\\Session component, ^2.8; required by the Csrf validator", + "laminas/laminas-uri": "Laminas\\Uri component, required by the Uri and Sitemap\\Loc validators", + "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators" + }, + "time": "2022-07-27T19:17:59+00:00", + "type": "library", + "extra": { + "laminas": { + "component": "Laminas\\Validator", + "config-provider": "Laminas\\Validator\\ConfigProvider" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Laminas\\Validator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "validator" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-validator/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-validator/issues", + "rss": "https://github.com/laminas/laminas-validator/releases.atom", + "source": "https://github.com/laminas/laminas-validator" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "install-path": "../laminas/laminas-validator" + }, + { + "name": "league/oauth2-client", + "version": "2.6.1", + "version_normalized": "2.6.1.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-client.git", + "reference": "2334c249907190c132364f5dae0287ab8666aa19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-client/zipball/2334c249907190c132364f5dae0287ab8666aa19", + "reference": "2334c249907190c132364f5dae0287ab8666aa19", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "paragonie/random_compat": "^1 || ^2 || ^9.99", + "php": "^5.6 || ^7.0 || ^8.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" + }, + "time": "2021-12-22T16:42:49+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + ], + "description": "OAuth 2.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "identity", + "idp", + "oauth", + "oauth2", + "single sign on" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-client/issues", + "source": "https://github.com/thephpleague/oauth2-client/tree/2.6.1" + }, + "install-path": "../league/oauth2-client" + }, + { + "name": "league/oauth2-google", + "version": "3.0.4", + "version_normalized": "3.0.4.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-google.git", + "reference": "6b79441f244040760bed5fdcd092a2bda7cf34c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-google/zipball/6b79441f244040760bed5fdcd092a2bda7cf34c6", + "reference": "6b79441f244040760bed5fdcd092a2bda7cf34c6", + "shasum": "" + }, + "require": { + "league/oauth2-client": "^2.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^2.0", + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^6.0", + "squizlabs/php_codesniffer": "^2.0" + }, + "time": "2021-01-27T16:09:03+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com", + "homepage": "http://shadowhand.me" + } + ], + "description": "Google OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "Authentication", + "authorization", + "client", + "google", + "oauth", + "oauth2" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-google/issues", + "source": "https://github.com/thephpleague/oauth2-google/tree/3.0.4" + }, + "install-path": "../league/oauth2-google" + }, + { + "name": "nikic/php-parser", + "version": "v4.14.0", + "version_normalized": "4.14.0.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", "shasum": "" }, "require": { @@ -86,7 +1155,7 @@ "ircmaxell/php-yacc": "^0.0.7", "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" }, - "time": "2021-07-21T10:44:31+00:00", + "time": "2022-05-31T20:59:12+00:00", "bin": [ "bin/php-parse" ], @@ -118,42 +1187,38 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0" }, "install-path": "../nikic/php-parser" }, { "name": "paragonie/random_compat", - "version": "v2.0.18", - "version_normalized": "2.0.18.0", + "version": "v9.99.100", + "version_normalized": "9.99.100.0", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db" + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/0a58ef6e3146256cc3dc7cc393927bcc7d1b72db", - "reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", "shasum": "" }, "require": { - "php": ">=5.2.0" + "php": ">= 7" }, "require-dev": { - "phpunit/phpunit": "4.*|5.*" + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" }, "suggest": { "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." }, - "time": "2019-01-03T20:59:08+00:00", + "time": "2020-10-15T08:29:30+00:00", "type": "library", "installation-source": "dist", - "autoload": { - "files": [ - "lib/random.php" - ] - }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" @@ -172,6 +1237,11 @@ "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" }, { @@ -305,21 +1375,25 @@ } ], "description": "More info available on: http://pear.php.net/package/Console_Getopt", + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Console_Getopt", + "source": "https://github.com/pear/Console_Getopt" + }, "install-path": "../pear/console_getopt" }, { "name": "pear/pear-core-minimal", - "version": "v1.10.10", - "version_normalized": "1.10.10.0", + "version": "v1.10.11", + "version_normalized": "1.10.11.0", "source": { "type": "git", "url": "https://github.com/pear/pear-core-minimal.git", - "reference": "625a3c429d9b2c1546438679074cac1b089116a7" + "reference": "68d0d32ada737153b7e93b8d3c710ebe70ac867d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pear/pear-core-minimal/zipball/625a3c429d9b2c1546438679074cac1b089116a7", - "reference": "625a3c429d9b2c1546438679074cac1b089116a7", + "url": "https://api.github.com/repos/pear/pear-core-minimal/zipball/68d0d32ada737153b7e93b8d3c710ebe70ac867d", + "reference": "68d0d32ada737153b7e93b8d3c710ebe70ac867d", "shasum": "" }, "require": { @@ -329,7 +1403,7 @@ "replace": { "rsky/pear-core-min": "self.version" }, - "time": "2019-11-19T19:00:24+00:00", + "time": "2021-08-10T22:31:03+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -352,6 +1426,10 @@ } ], "description": "Minimal set of PEAR core files to be used as composer dependency", + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=PEAR", + "source": "https://github.com/pear/pear-core-minimal" + }, "install-path": "../pear/pear-core-minimal" }, { @@ -418,42 +1496,42 @@ }, { "name": "pelago/emogrifier", - "version": "v3.1.0", - "version_normalized": "3.1.0.0", + "version": "v6.0.0", + "version_normalized": "6.0.0.0", "source": { "type": "git", "url": "https://github.com/MyIntervals/emogrifier.git", - "reference": "f6a5c7d44612d86c3901c93f1592f5440e6b2cd8" + "reference": "aa72d5407efac118f3896bcb995a2cba793df0ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/f6a5c7d44612d86c3901c93f1592f5440e6b2cd8", - "reference": "f6a5c7d44612d86c3901c93f1592f5440e6b2cd8", + "url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/aa72d5407efac118f3896bcb995a2cba793df0ae", + "reference": "aa72d5407efac118f3896bcb995a2cba793df0ae", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", - "php": "^5.6 || ~7.0 || ~7.1 || ~7.2 || ~7.3 || ~7.4", - "symfony/css-selector": "^2.8 || ^3.0 || ^4.0 || ^5.0" + "php": "~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0", + "sabberworm/php-css-parser": "^8.3.1", + "symfony/css-selector": "^3.4.32 || ^4.4 || ^5.3 || ^6.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.15.3", - "phpmd/phpmd": "^2.7.0", - "phpunit/phpunit": "^5.7.27", - "squizlabs/php_codesniffer": "^3.5.0" + "php-parallel-lint/php-parallel-lint": "^1.3.0", + "phpunit/phpunit": "^8.5.16", + "rawr/cross-data-providers": "^2.3.0" }, - "time": "2019-12-26T19:37:31+00:00", + "time": "2021-09-16T16:22:04+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0.x-dev" + "dev-main": "7.0.x-dev" } }, "installation-source": "dist", "autoload": { "psr-4": { - "Pelago\\": "src/" + "Pelago\\Emogrifier\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -548,29 +1626,24 @@ }, { "name": "psr/container", - "version": "1.0.0", - "version_normalized": "1.0.0.0", + "version": "1.1.2", + "version_normalized": "1.1.2.0", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.4.0" }, - "time": "2017-02-14T16:28:37+00:00", + "time": "2021-11-05T16:50:12+00:00", "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "installation-source": "dist", "autoload": { "psr-4": { @@ -584,7 +1657,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common Container Interface (PHP FIG PSR-11)", @@ -596,27 +1669,253 @@ "container-interop", "psr" ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, "install-path": "../psr/container" }, { - "name": "psr/log", - "version": "1.1.2", - "version_normalized": "1.1.2.0", + "name": "psr/event-dispatcher", + "version": "1.0.0", + "version_normalized": "1.0.0.0", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", - "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "time": "2019-01-08T18:20:26+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "install-path": "../psr/event-dispatcher" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "time": "2020-06-29T06:28:15+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "install-path": "../psr/http-client" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "time": "2019-04-30T12:38:16+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "install-path": "../psr/http-factory" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", "shasum": "" }, "require": { "php": ">=5.3.0" }, - "time": "2019-11-01T11:05:21+00:00", + "time": "2016-08-06T14:39:51+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "install-path": "../psr/http-message" + }, + { + "name": "psr/log", + "version": "1.1.4", + "version_normalized": "1.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2021-05-03T11:20:27+00:00", "type": "library", "extra": { "branch-alias": { @@ -636,7 +1935,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for logging libraries", @@ -646,37 +1945,90 @@ "psr", "psr-3" ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, "install-path": "../psr/log" }, { - "name": "psr/simple-cache", - "version": "1.0.1", - "version_normalized": "1.0.1.0", + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "version_normalized": "3.0.3.0", "source": { "type": "git", - "url": "https://github.com/php-fig/simple-cache.git", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=5.6" }, - "time": "2017-10-23T01:57:42+00:00", + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "time": "2019-03-08T08:55:37+00:00", "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "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": "sabberworm/php-css-parser", + "version": "8.4.0", + "version_normalized": "8.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sabberworm/PHP-CSS-Parser.git", + "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/e41d2140031d533348b2192a83f02d8dd8a71d30", + "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=5.6.20" + }, + "require-dev": { + "codacy/coverage": "^1.4", + "phpunit/phpunit": "^4.8.36" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" + }, + "time": "2021-12-11T13:40:54+00:00", + "type": "library", "installation-source": "dist", "autoload": { "psr-4": { - "Psr\\SimpleCache\\": "src/" + "Sabberworm\\CSS\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -685,33 +2037,35 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Raphael Schweikert" } ], - "description": "Common interfaces for simple caching", + "description": "Parser for CSS Files written in PHP", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", "keywords": [ - "cache", - "caching", - "psr", - "psr-16", - "simple-cache" + "css", + "parser", + "stylesheet" ], - "install-path": "../psr/simple-cache" + "support": { + "issues": "https://github.com/sabberworm/PHP-CSS-Parser/issues", + "source": "https://github.com/sabberworm/PHP-CSS-Parser/tree/8.4.0" + }, + "install-path": "../sabberworm/php-css-parser" }, { "name": "scssphp/scssphp", - "version": "1.0.6", - "version_normalized": "1.0.6.0", + "version": "v1.10.5", + "version_normalized": "1.10.5.0", "source": { "type": "git", "url": "https://github.com/scssphp/scssphp.git", - "reference": "5b3c9d704950d8f9637f5110c36c281ec47dc13c" + "reference": "6d44282ccf283e133ab70b6282f8e068ff2f9bf9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scssphp/scssphp/zipball/5b3c9d704950d8f9637f5110c36c281ec47dc13c", - "reference": "5b3c9d704950d8f9637f5110c36c281ec47dc13c", + "url": "https://api.github.com/repos/scssphp/scssphp/zipball/6d44282ccf283e133ab70b6282f8e068ff2f9bf9", + "reference": "6d44282ccf283e133ab70b6282f8e068ff2f9bf9", "shasum": "" }, "require": { @@ -720,16 +2074,31 @@ "php": ">=5.6.0" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.3", - "squizlabs/php_codesniffer": "~2.5", - "twbs/bootstrap": "~4.3", + "bamarni/composer-bin-plugin": "^1.4", + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.3 || ^9.4", + "sass/sass-spec": "*", + "squizlabs/php_codesniffer": "~3.5", + "symfony/phpunit-bridge": "^5.1", + "thoughtbot/bourbon": "^7.0", + "twbs/bootstrap": "~5.0", + "twbs/bootstrap4": "4.6.1", "zurb/foundation": "~6.5" }, - "time": "2019-12-12T05:00:52+00:00", + "suggest": { + "ext-iconv": "Can be used as fallback when ext-mbstring is not available", + "ext-mbstring": "For best performance, mbstring should be installed as it is faster than ext-iconv" + }, + "time": "2022-07-27T15:52:39+00:00", "bin": [ "bin/pscss" ], "type": "library", + "extra": { + "bamarni-bin": { + "forward-command": false, + "bin-links": false + } + }, "installation-source": "dist", "autoload": { "psr-4": { @@ -761,101 +2130,63 @@ "scss", "stylesheet" ], + "support": { + "issues": "https://github.com/scssphp/scssphp/issues", + "source": "https://github.com/scssphp/scssphp/tree/v1.10.5" + }, "install-path": "../scssphp/scssphp" }, - { - "name": "swiftmailer/swiftmailer", - "version": "v5.4.12", - "version_normalized": "5.4.12.0", - "source": { - "type": "git", - "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "181b89f18a90f8925ef805f950d47a7190e9b950" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/181b89f18a90f8925ef805f950d47a7190e9b950", - "reference": "181b89f18a90f8925ef805f950d47a7190e9b950", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "mockery/mockery": "~0.9.1", - "symfony/phpunit-bridge": "~3.2" - }, - "time": "2018-07-31T09:26:32+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.4-dev" - } - }, - "installation-source": "dist", - "autoload": { - "files": [ - "lib/swift_required.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Chris Corbyn" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Swiftmailer, free feature-rich PHP mailer", - "homepage": "https://swiftmailer.symfony.com", - "keywords": [ - "email", - "mail", - "mailer" - ], - "install-path": "../swiftmailer/swiftmailer" - }, { "name": "symfony/cache", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "a7a14c4832760bd1fbd31be2859ffedc9b6ff813" + "reference": "5a0fff46df349f0db3fe242263451fddf5277362" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/a7a14c4832760bd1fbd31be2859ffedc9b6ff813", - "reference": "a7a14c4832760bd1fbd31be2859ffedc9b6ff813", + "url": "https://api.github.com/repos/symfony/cache/zipball/5a0fff46df349f0db3fe242263451fddf5277362", + "reference": "5a0fff46df349f0db3fe242263451fddf5277362", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "psr/cache": "~1.0", - "psr/log": "~1.0", - "psr/simple-cache": "^1.0", - "symfony/polyfill-apcu": "~1.1" + "php": ">=7.2.5", + "psr/cache": "^1.0|^2.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^1.1.7|^2", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/var-exporter": "^4.4|^5.0|^6.0" }, "conflict": { - "symfony/var-dumper": "<3.3" + "doctrine/dbal": "<2.13.1", + "symfony/dependency-injection": "<4.4", + "symfony/http-kernel": "<4.4", + "symfony/var-dumper": "<4.4" }, "provide": { - "psr/cache-implementation": "1.0", - "psr/simple-cache-implementation": "1.0" + "psr/cache-implementation": "1.0|2.0", + "psr/simple-cache-implementation": "1.0|2.0", + "symfony/cache-implementation": "1.0|2.0" }, "require-dev": { "cache/integration-tests": "dev-master", - "doctrine/cache": "^1.6", - "doctrine/dbal": "^2.4|^3.0", - "predis/predis": "^1.0" + "doctrine/cache": "^1.6|^2.0", + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1", + "psr/simple-cache": "^1.0|^2.0", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/filesystem": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/messenger": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" }, - "time": "2020-10-24T10:57:07+00:00", + "time": "2022-07-28T15:25:17+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -880,14 +2211,14 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Cache component with PSR-6, PSR-16, and tags", + "description": "Provides an extended PSR-6, PSR-16 (and tags) implementation", "homepage": "https://symfony.com", "keywords": [ "caching", "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v3.4.47" + "source": "https://github.com/symfony/cache/tree/v5.4.11" }, "funding": [ { @@ -906,40 +2237,43 @@ "install-path": "../symfony/cache" }, { - "name": "symfony/class-loader", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "name": "symfony/cache-contracts", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", "source": { "type": "git", - "url": "https://github.com/symfony/class-loader.git", - "reference": "a22265a9f3511c0212bf79f54910ca5a77c0e92c" + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/class-loader/zipball/a22265a9f3511c0212bf79f54910ca5a77c0e92c", - "reference": "a22265a9f3511c0212bf79f54910ca5a77c0e92c", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/64be4a7acb83b6f2bf6de9a02cee6dad41277ebc", + "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" - }, - "require-dev": { - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/polyfill-apcu": "~1.1" + "php": ">=7.2.5", + "psr/cache": "^1.0|^2.0|^3.0" }, "suggest": { - "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM" + "symfony/cache-implementation": "" }, - "time": "2020-10-24T10:57:07+00:00", + "time": "2022-01-02T09:53:40+00:00", "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, "installation-source": "dist", "autoload": { "psr-4": { - "Symfony\\Component\\ClassLoader\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Contracts\\Cache\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -947,18 +2281,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony ClassLoader Component", + "description": "Generic abstractions related to caching", "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], "support": { - "source": "https://github.com/symfony/class-loader/tree/v3.4.47" + "source": "https://github.com/symfony/cache-contracts/tree/v2.5.2" }, "funding": [ { @@ -974,42 +2316,45 @@ "type": "tidelift" } ], - "install-path": "../symfony/class-loader" + "install-path": "../symfony/cache-contracts" }, { "name": "symfony/config", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "bc6b3fd3930d4b53a60b42fe2ed6fc466b75f03f" + "reference": "ec79e03125c1d2477e43dde8528535d90cc78379" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/bc6b3fd3930d4b53a60b42fe2ed6fc466b75f03f", - "reference": "bc6b3fd3930d4b53a60b42fe2ed6fc466b75f03f", + "url": "https://api.github.com/repos/symfony/config/zipball/ec79e03125c1d2477e43dde8528535d90cc78379", + "reference": "ec79e03125c1d2477e43dde8528535d90cc78379", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/filesystem": "~2.8|~3.0|~4.0", - "symfony/polyfill-ctype": "~1.8" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/filesystem": "^4.4|^5.0|^6.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22" }, "conflict": { - "symfony/dependency-injection": "<3.3", - "symfony/finder": "<3.3" + "symfony/finder": "<4.4" }, "require-dev": { - "symfony/dependency-injection": "~3.3|~4.0", - "symfony/event-dispatcher": "~3.3|~4.0", - "symfony/finder": "~3.3|~4.0", - "symfony/yaml": "~3.0|~4.0" + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/messenger": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/yaml": "^4.4|^5.0|^6.0" }, "suggest": { "symfony/yaml": "To use the yaml reference dumper" }, - "time": "2020-10-24T10:57:07+00:00", + "time": "2022-07-20T13:00:38+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1034,10 +2379,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Config Component", + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v3.4.47" + "source": "https://github.com/symfony/config/tree/v5.4.11" }, "funding": [ { @@ -1057,38 +2402,47 @@ }, { "name": "symfony/console", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "a10b1da6fc93080c180bba7219b5ff5b7518fe81" + "reference": "535846c7ee6bc4dd027ca0d93220601456734b10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/a10b1da6fc93080c180bba7219b5ff5b7518fe81", - "reference": "a10b1da6fc93080c180bba7219b5ff5b7518fe81", + "url": "https://api.github.com/repos/symfony/console/zipball/535846c7ee6bc4dd027ca0d93220601456734b10", + "reference": "535846c7ee6bc4dd027ca0d93220601456734b10", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/debug": "~2.8|~3.0|~4.0", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" }, "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/process": "<3.3" + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" }, "provide": { - "psr/log-implementation": "1.0" + "psr/log-implementation": "1.0|2.0" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~3.3|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~2.8|~3.0|~4.0", - "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.3|~4.0" + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" }, "suggest": { "psr/log": "For using the console logger", @@ -1096,7 +2450,7 @@ "symfony/lock": "", "symfony/process": "" }, - "time": "2020-10-24T10:57:07+00:00", + "time": "2022-07-22T10:42:43+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1121,10 +2475,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Console Component", + "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], "support": { - "source": "https://github.com/symfony/console/tree/v3.4.47" + "source": "https://github.com/symfony/console/tree/v5.4.11" }, "funding": [ { @@ -1144,23 +2504,24 @@ }, { "name": "symfony/css-selector", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "da3d9da2ce0026771f5fe64cb332158f1bd2bc33" + "reference": "c1681789f059ab756001052164726ae88512ae3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/da3d9da2ce0026771f5fe64cb332158f1bd2bc33", - "reference": "da3d9da2ce0026771f5fe64cb332158f1bd2bc33", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/c1681789f059ab756001052164726ae88512ae3d", + "reference": "c1681789f059ab756001052164726ae88512ae3d", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" }, - "time": "2020-10-24T10:57:07+00:00", + "time": "2022-06-27T16:58:25+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1189,10 +2550,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony CssSelector Component", + "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v3.4.47" + "source": "https://github.com/symfony/css-selector/tree/v5.4.11" }, "funding": [ { @@ -1210,109 +2571,44 @@ ], "install-path": "../symfony/css-selector" }, - { - "name": "symfony/debug", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "ab42889de57fdfcfcc0759ab102e2fd4ea72dcae" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/ab42889de57fdfcfcc0759ab102e2fd4ea72dcae", - "reference": "ab42889de57fdfcfcc0759ab102e2fd4ea72dcae", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8", - "psr/log": "~1.0" - }, - "conflict": { - "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" - }, - "require-dev": { - "symfony/http-kernel": "~2.8|~3.0|~4.0" - }, - "time": "2020-10-24T10:57:07+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "Symfony\\Component\\Debug\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Debug Component", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/debug/tree/v3.4.47" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "install-path": "../symfony/debug" - }, { "name": "symfony/dependency-injection", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "51d2a2708c6ceadad84393f8581df1dcf9e5e84b" + "reference": "a8b9251016e9476db73e25fa836904bc0bf74c62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/51d2a2708c6ceadad84393f8581df1dcf9e5e84b", - "reference": "51d2a2708c6ceadad84393f8581df1dcf9e5e84b", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a8b9251016e9476db73e25fa836904bc0bf74c62", + "reference": "a8b9251016e9476db73e25fa836904bc0bf74c62", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "psr/container": "^1.0" + "php": ">=7.2.5", + "psr/container": "^1.1.1", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22", + "symfony/service-contracts": "^1.1.6|^2" }, "conflict": { - "symfony/config": "<3.3.7", - "symfony/finder": "<3.3", - "symfony/proxy-manager-bridge": "<3.4", - "symfony/yaml": "<3.4" + "ext-psr": "<1.1|>=2", + "symfony/config": "<5.3", + "symfony/finder": "<4.4", + "symfony/proxy-manager-bridge": "<4.4", + "symfony/yaml": "<4.4.26" }, "provide": { - "psr/container-implementation": "1.0" + "psr/container-implementation": "1.0", + "symfony/service-implementation": "1.0|2.0" }, "require-dev": { - "symfony/config": "~3.3|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/yaml": "~3.4|~4.0" + "symfony/config": "^5.3|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/yaml": "^4.4.26|^5.0|^6.0" }, "suggest": { "symfony/config": "", @@ -1321,7 +2617,7 @@ "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", "symfony/yaml": "" }, - "time": "2020-10-24T10:57:07+00:00", + "time": "2022-07-20T13:00:38+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1346,10 +2642,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony DependencyInjection Component", + "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v3.4.47" + "source": "https://github.com/symfony/dependency-injection/tree/v5.4.11" }, "funding": [ { @@ -1368,27 +2664,99 @@ "install-path": "../symfony/dependency-injection" }, { - "name": "symfony/dotenv", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", "source": { "type": "git", - "url": "https://github.com/symfony/dotenv.git", - "reference": "1022723ac4f56b001d99691d96c6025dbf1404f1" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/1022723ac4f56b001d99691d96c6025dbf1404f1", - "reference": "1022723ac4f56b001d99691d96c6025dbf1404f1", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.1" + }, + "time": "2022-01-02T09:53:40+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/deprecation-contracts" + }, + { + "name": "symfony/dotenv", + "version": "v5.4.5", + "version_normalized": "5.4.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/dotenv.git", + "reference": "83a2310904a4f5d4f42526227b5a578ac82232a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/83a2310904a4f5d4f42526227b5a578ac82232a9", + "reference": "83a2310904a4f5d4f42526227b5a578ac82232a9", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3" }, "require-dev": { - "symfony/process": "^3.4.2|^4.0" + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0" }, - "time": "2020-10-24T10:57:07+00:00", + "time": "2022-02-15T17:04:12+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1421,7 +2789,7 @@ "environment" ], "support": { - "source": "https://github.com/symfony/dotenv/tree/v3.4.47" + "source": "https://github.com/symfony/dotenv/tree/v5.4.5" }, "funding": [ { @@ -1440,39 +2808,122 @@ "install-path": "../symfony/dotenv" }, { - "name": "symfony/event-dispatcher", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "name": "symfony/error-handler", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "31fde73757b6bad247c54597beef974919ec6860" + "url": "https://github.com/symfony/error-handler.git", + "reference": "f75d17cb4769eb38cd5fccbda95cd80a054d35c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/31fde73757b6bad247c54597beef974919ec6860", - "reference": "31fde73757b6bad247c54597beef974919ec6860", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/f75d17cb4769eb38cd5fccbda95cd80a054d35c8", + "reference": "f75d17cb4769eb38cd5fccbda95cd80a054d35c8", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" - }, - "conflict": { - "symfony/dependency-injection": "<3.3" + "php": ">=7.2.5", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^4.4|^5.0|^6.0" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~2.8|~3.0|~4.0", - "symfony/debug": "~3.4|~4.4", - "symfony/dependency-injection": "~3.3|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/stopwatch": "~2.8|~3.0|~4.0" + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/serializer": "^4.4|^5.0|^6.0" + }, + "time": "2022-07-29T07:37:50+00:00", + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v5.4.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/error-handler" + }, + { + "name": "symfony/event-dispatcher", + "version": "v5.4.9", + "version_normalized": "5.4.9.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", + "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/event-dispatcher-contracts": "^2|^3", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^4.4|^5.0|^6.0" }, "suggest": { "symfony/dependency-injection": "", "symfony/http-kernel": "" }, - "time": "2020-10-24T10:57:07+00:00", + "time": "2022-05-05T16:45:39+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1497,10 +2948,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony EventDispatcher Component", + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v3.4.47" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.9" }, "funding": [ { @@ -1519,25 +2970,109 @@ "install-path": "../symfony/event-dispatcher" }, { - "name": "symfony/filesystem", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "name": "symfony/event-dispatcher-contracts", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "e58d7841cddfed6e846829040dca2cca0ebbbbb3" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/e58d7841cddfed6e846829040dca2cca0ebbbbb3", - "reference": "e58d7841cddfed6e846829040dca2cca0ebbbbb3", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/f98b54df6ad059855739db6fcbc2d36995283fe1", + "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8" + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" }, - "time": "2020-10-24T10:57:07+00:00", + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "time": "2022-01-02T09:53:40+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/event-dispatcher-contracts" + }, + { + "name": "symfony/filesystem", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "6699fb0228d1bc35b12aed6dd5e7455457609ddd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/6699fb0228d1bc35b12aed6dd5e7455457609ddd", + "reference": "6699fb0228d1bc35b12aed6dd5e7455457609ddd", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" + }, + "time": "2022-07-20T13:00:38+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1562,10 +3097,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Filesystem Component", + "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v3.4.47" + "source": "https://github.com/symfony/filesystem/tree/v5.4.11" }, "funding": [ { @@ -1585,23 +3120,25 @@ }, { "name": "symfony/finder", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "b6b6ad3db3edb1b4b1c1896b1975fb684994de6e" + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/b6b6ad3db3edb1b4b1c1896b1975fb684994de6e", - "reference": "b6b6ad3db3edb1b4b1c1896b1975fb684994de6e", + "url": "https://api.github.com/repos/symfony/finder/zipball/7872a66f57caffa2916a584db1aa7f12adc76f8c", + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" }, - "time": "2020-11-16T17:02:08+00:00", + "time": "2022-07-29T07:37:50+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1626,10 +3163,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Finder Component", + "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v3.4.47" + "source": "https://github.com/symfony/finder/tree/v5.4.11" }, "funding": [ { @@ -1649,77 +3186,101 @@ }, { "name": "symfony/framework-bundle", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "6c95e747b75ddd2af61152ce93bf87299d15710e" + "reference": "a0660b602357d5c2ceaac1c9f80c5820bbff803d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/6c95e747b75ddd2af61152ce93bf87299d15710e", - "reference": "6c95e747b75ddd2af61152ce93bf87299d15710e", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/a0660b602357d5c2ceaac1c9f80c5820bbff803d", + "reference": "a0660b602357d5c2ceaac1c9f80c5820bbff803d", "shasum": "" }, "require": { "ext-xml": "*", - "php": "^5.5.9|>=7.0.8", - "symfony/cache": "~3.4.31|^4.3.4", - "symfony/class-loader": "~3.2", - "symfony/config": "^3.4.31|^4.3.4", - "symfony/debug": "~2.8|~3.0|~4.0", - "symfony/dependency-injection": "^3.4.24|^4.2.5", - "symfony/event-dispatcher": "~3.4|~4.0", - "symfony/filesystem": "~2.8|~3.0|~4.0", - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/http-foundation": "^3.4.38|^4.3", - "symfony/http-kernel": "^3.4.44|^4.3.4", + "php": ">=7.2.5", + "symfony/cache": "^5.2|^6.0", + "symfony/config": "^5.3|^6.0", + "symfony/dependency-injection": "^5.4.5|^6.0.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/error-handler": "^4.4.1|^5.0.1|^6.0", + "symfony/event-dispatcher": "^5.1|^6.0", + "symfony/filesystem": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^5.3|^6.0", + "symfony/http-kernel": "^5.4|^6.0", "symfony/polyfill-mbstring": "~1.0", - "symfony/routing": "^3.4.5|^4.0.5" + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22", + "symfony/routing": "^5.3|^6.0" }, "conflict": { - "phpdocumentor/reflection-docblock": "<3.0", - "phpdocumentor/type-resolver": "<0.2.1", - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", - "symfony/asset": "<3.3", - "symfony/console": "<3.4", - "symfony/form": "<3.4", - "symfony/property-info": "<3.3", - "symfony/serializer": "<3.3", - "symfony/stopwatch": "<3.4", - "symfony/translation": "<3.4", - "symfony/validator": "<3.4", - "symfony/workflow": "<3.3" + "doctrine/annotations": "<1.13.1", + "doctrine/cache": "<1.11", + "doctrine/persistence": "<1.3", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "phpunit/phpunit": "<5.4.3", + "symfony/asset": "<5.3", + "symfony/console": "<5.2.5", + "symfony/dom-crawler": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/form": "<5.2", + "symfony/http-client": "<4.4", + "symfony/lock": "<4.4", + "symfony/mailer": "<5.2", + "symfony/messenger": "<5.4", + "symfony/mime": "<4.4", + "symfony/property-access": "<5.3", + "symfony/property-info": "<4.4", + "symfony/security-csrf": "<5.3", + "symfony/serializer": "<5.2", + "symfony/service-contracts": ">=3.0", + "symfony/stopwatch": "<4.4", + "symfony/translation": "<5.3", + "symfony/twig-bridge": "<4.4", + "symfony/twig-bundle": "<4.4", + "symfony/validator": "<5.2", + "symfony/web-profiler-bundle": "<4.4", + "symfony/workflow": "<5.2" }, "require-dev": { - "doctrine/annotations": "~1.7", - "doctrine/cache": "~1.0", - "fig/link-util": "^1.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0", - "symfony/asset": "~3.3|~4.0", - "symfony/browser-kit": "~2.8|~3.0|~4.0", - "symfony/console": "~3.4.31|^4.3.4", - "symfony/css-selector": "~2.8|~3.0|~4.0", - "symfony/dom-crawler": "~2.8|~3.0|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/form": "^3.4.31|^4.3.4", - "symfony/lock": "~3.4|~4.0", + "doctrine/annotations": "^1.13.1", + "doctrine/cache": "^1.11|^2.0", + "doctrine/persistence": "^1.3|^2|^3", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^5.3|^6.0", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/console": "^5.4.9|^6.0.9", + "symfony/css-selector": "^4.4|^5.0|^6.0", + "symfony/dom-crawler": "^4.4.30|^5.3.7|^6.0", + "symfony/dotenv": "^5.1|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/form": "^5.2|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/mailer": "^5.2|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/notifier": "^5.4|^6.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/process": "~2.8|~3.0|~4.0", - "symfony/property-info": "~3.3|~4.0", - "symfony/security-core": "~3.2|~4.0", - "symfony/security-csrf": "^2.8.31|^3.3.13|~4.0", - "symfony/serializer": "~3.3|~4.0", - "symfony/stopwatch": "~3.4|~4.0", - "symfony/templating": "~2.8|~3.0|~4.0", - "symfony/translation": "~3.4|~4.0", - "symfony/validator": "~3.4|~4.0", - "symfony/var-dumper": "~3.3|~4.0", - "symfony/web-link": "~3.3|~4.0", - "symfony/workflow": "~3.3|~4.0", - "symfony/yaml": "~3.2|~4.0", - "twig/twig": "~1.34|~2.4" + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/property-info": "^4.4|^5.0|^6.0", + "symfony/rate-limiter": "^5.2|^6.0", + "symfony/security-bundle": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0", + "symfony/string": "^5.0|^6.0", + "symfony/translation": "^5.3|^6.0", + "symfony/twig-bundle": "^4.4|^5.0|^6.0", + "symfony/validator": "^5.2|^6.0", + "symfony/web-link": "^4.4|^5.0|^6.0", + "symfony/workflow": "^5.2|^6.0", + "symfony/yaml": "^4.4|^5.0|^6.0", + "twig/twig": "^2.10|^3.0" }, "suggest": { "ext-apcu": "For best performance of the system caches", @@ -1731,7 +3292,7 @@ "symfony/web-link": "For using web links, features such as preloading, prefetching or prerendering", "symfony/yaml": "For using the debug:config and lint:yaml commands" }, - "time": "2020-10-24T10:57:07+00:00", + "time": "2022-07-20T13:00:38+00:00", "type": "symfony-bundle", "installation-source": "dist", "autoload": { @@ -1756,10 +3317,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony FrameworkBundle", + "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v3.4.47" + "source": "https://github.com/symfony/framework-bundle/tree/v5.4.11" }, "funding": [ { @@ -1779,28 +3340,35 @@ }, { "name": "symfony/http-foundation", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "b9885fcce6fe494201da4f70a9309770e9d13dc8" + "reference": "0a5868e0999e9d47859ba3d918548ff6943e6389" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/b9885fcce6fe494201da4f70a9309770e9d13dc8", - "reference": "b9885fcce6fe494201da4f70a9309770e9d13dc8", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/0a5868e0999e9d47859ba3d918548ff6943e6389", + "reference": "0a5868e0999e9d47859ba3d918548ff6943e6389", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php70": "~1.6" + "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "symfony/expression-language": "~2.8|~3.0|~4.0" + "predis/predis": "~1.0", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/mime": "^4.4|^5.0|^6.0" }, - "time": "2020-10-24T10:57:07+00:00", + "suggest": { + "symfony/mime": "To use the file extension guesser" + }, + "time": "2022-07-20T13:00:38+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1825,10 +3393,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony HttpFoundation Component", + "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v3.4.47" + "source": "https://github.com/symfony/http-foundation/tree/v5.4.11" }, "funding": [ { @@ -1848,64 +3416,74 @@ }, { "name": "symfony/http-kernel", - "version": "v3.4.49", - "version_normalized": "3.4.49.0", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "5aa72405f5bd5583c36ed6e756acb17d3f98ac40" + "reference": "4fd590a2ef3f62560dbbf6cea511995dd77321ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/5aa72405f5bd5583c36ed6e756acb17d3f98ac40", - "reference": "5aa72405f5bd5583c36ed6e756acb17d3f98ac40", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/4fd590a2ef3f62560dbbf6cea511995dd77321ee", + "reference": "4fd590a2ef3f62560dbbf6cea511995dd77321ee", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "psr/log": "~1.0", - "symfony/debug": "^3.3.3|~4.0", - "symfony/event-dispatcher": "~2.8|~3.0|~4.0", - "symfony/http-foundation": "~3.4.12|~4.0.12|^4.1.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php56": "~1.8" + "php": ">=7.2.5", + "psr/log": "^1|^2", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^5.0|^6.0", + "symfony/http-foundation": "^5.3.7|^6.0", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16" }, "conflict": { - "symfony/config": "<2.8", - "symfony/dependency-injection": "<3.4.10|<4.0.10,>=4", - "symfony/var-dumper": "<3.3", - "twig/twig": "<1.34|<2.4,>=2" + "symfony/browser-kit": "<5.4", + "symfony/cache": "<5.0", + "symfony/config": "<5.0", + "symfony/console": "<4.4", + "symfony/dependency-injection": "<5.3", + "symfony/doctrine-bridge": "<5.0", + "symfony/form": "<5.0", + "symfony/http-client": "<5.0", + "symfony/mailer": "<5.0", + "symfony/messenger": "<5.0", + "symfony/translation": "<5.0", + "symfony/twig-bridge": "<5.0", + "symfony/validator": "<5.0", + "twig/twig": "<2.13" }, "provide": { - "psr/log-implementation": "1.0" + "psr/log-implementation": "1.0|2.0" }, "require-dev": { - "psr/cache": "~1.0", - "symfony/browser-kit": "~2.8|~3.0|~4.0", - "symfony/class-loader": "~2.8|~3.0", - "symfony/config": "~2.8|~3.0|~4.0", - "symfony/console": "~2.8|~3.0|~4.0", - "symfony/css-selector": "~2.8|~3.0|~4.0", - "symfony/dependency-injection": "^3.4.10|^4.0.10", - "symfony/dom-crawler": "~2.8|~3.0|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/process": "~2.8|~3.0|~4.0", - "symfony/routing": "~3.4|~4.0", - "symfony/stopwatch": "~2.8|~3.0|~4.0", - "symfony/templating": "~2.8|~3.0|~4.0", - "symfony/translation": "~2.8|~3.0|~4.0", - "symfony/var-dumper": "~3.3|~4.0" + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/config": "^5.0|^6.0", + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/css-selector": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.3|^6.0", + "symfony/dom-crawler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/http-client-contracts": "^1.1|^2|^3", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/routing": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0", + "symfony/translation": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2|^3", + "twig/twig": "^2.13|^3.0.4" }, "suggest": { "symfony/browser-kit": "", "symfony/config": "", "symfony/console": "", - "symfony/dependency-injection": "", - "symfony/finder": "", - "symfony/var-dumper": "" + "symfony/dependency-injection": "" }, - "time": "2021-05-19T12:06:59+00:00", + "time": "2022-07-29T12:30:22+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1930,10 +3508,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony HttpKernel Component", + "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v3.4.49" + "source": "https://github.com/symfony/http-kernel/tree/v5.4.11" }, "funding": [ { @@ -1952,28 +3530,34 @@ "install-path": "../symfony/http-kernel" }, { - "name": "symfony/polyfill-apcu", - "version": "v1.19.0", - "version_normalized": "1.19.0.0", + "name": "symfony/polyfill-ctype", + "version": "v1.26.0", + "version_normalized": "1.26.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-apcu.git", - "reference": "b44b51e7814c23bfbd793a16ead5d7ce43ed23c5" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/b44b51e7814c23bfbd793a16ead5d7ce43ed23c5", - "reference": "b44b51e7814c23bfbd793a16ead5d7ce43ed23c5", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, - "time": "2020-10-21T09:57:48+00:00", + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "time": "2022-05-24T11:49:31+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "1.19-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -1985,92 +3569,9 @@ "files": [ "bootstrap.php" ], - "psr-4": { - "Symfony\\Polyfill\\Apcu\\": "" - } - }, - "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 apcu_* functions to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "apcu", - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-apcu/tree/v1.19.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "install-path": "../symfony/polyfill-apcu" - }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.19.0", - "version_normalized": "1.19.0.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "aed596913b70fae57be53d86faa2e9ef85a2297b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/aed596913b70fae57be53d86faa2e9ef85a2297b", - "reference": "aed596913b70fae57be53d86faa2e9ef85a2297b", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "time": "2020-10-23T09:01:57+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.19-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "installation-source": "dist", - "autoload": { "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" - }, - "files": [ - "bootstrap.php" - ] + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2095,7 +3596,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.19.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" }, "funding": [ { @@ -2114,31 +3615,31 @@ "install-path": "../symfony/polyfill-ctype" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.19.0", - "version_normalized": "1.19.0.0", + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.26.0", + "version_normalized": "1.26.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "b5f7b932ee6fa802fc792eabd77c4c88084517ce" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "433d05519ce6990bf3530fba6957499d327395c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b5f7b932ee6fa802fc792eabd77c4c88084517ce", - "reference": "b5f7b932ee6fa802fc792eabd77c4c88084517ce", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", + "reference": "433d05519ce6990bf3530fba6957499d327395c2", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "suggest": { - "ext-mbstring": "For best performance" + "ext-intl": "For best performance" }, - "time": "2020-10-23T09:01:57+00:00", + "time": "2022-05-24T11:49:31+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "1.19-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2147,11 +3648,188 @@ }, "installation-source": "dist", "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, "files": [ "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-intl-grapheme" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.26.0", + "version_normalized": "1.26.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/59a8d271f00dd0e4c2e518104cc7963f655a1aa8", + "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2022-05-24T11:49:31+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-intl-idn" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.26.0", + "version_normalized": "1.26.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "time": "2022-05-24T11:49:31+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -2168,6 +3846,93 @@ "homepage": "https://symfony.com/contributors" } ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-intl-normalizer" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.26.0", + "version_normalized": "1.26.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2022-05-24T11:49:31+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ @@ -2178,7 +3943,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.19.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" }, "funding": [ { @@ -2197,29 +3962,28 @@ "install-path": "../symfony/polyfill-mbstring" }, { - "name": "symfony/polyfill-php56", - "version": "v1.19.0", - "version_normalized": "1.19.0.0", + "name": "symfony/polyfill-php72", + "version": "v1.26.0", + "version_normalized": "1.26.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "ea19621731cbd973a6702cfedef3419768bf3372" + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/ea19621731cbd973a6702cfedef3419768bf3372", - "reference": "ea19621731cbd973a6702cfedef3419768bf3372", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/bf44a9fd41feaac72b074de600314a93e2ae78e2", + "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2", "shasum": "" }, "require": { - "php": ">=5.3.3", - "symfony/polyfill-util": "~1.0" + "php": ">=7.1" }, - "time": "2020-10-23T09:01:57+00:00", + "time": "2022-05-24T11:49:31+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "1.19-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2228,12 +3992,12 @@ }, "installation-source": "dist", "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php56\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2249,7 +4013,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -2258,7 +4022,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php56/tree/v1.19.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.26.0" }, "funding": [ { @@ -2274,32 +4038,31 @@ "type": "tidelift" } ], - "install-path": "../symfony/polyfill-php56" + "install-path": "../symfony/polyfill-php72" }, { - "name": "symfony/polyfill-php70", - "version": "v1.19.0", - "version_normalized": "1.19.0.0", + "name": "symfony/polyfill-php73", + "version": "v1.26.0", + "version_normalized": "1.26.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "3fe414077251a81a1b15b1c709faf5c2fbae3d4e" + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/3fe414077251a81a1b15b1c709faf5c2fbae3d4e", - "reference": "3fe414077251a81a1b15b1c709faf5c2fbae3d4e", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", "shasum": "" }, "require": { - "paragonie/random_compat": "~1.0|~2.0|~9.99", - "php": ">=5.3.3" + "php": ">=7.1" }, - "time": "2020-10-23T09:01:57+00:00", + "time": "2022-05-24T11:49:31+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "1.19-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2308,12 +4071,12 @@ }, "installation-source": "dist", "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php70\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -2332,7 +4095,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -2341,7 +4104,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php70/tree/v1.19.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" }, "funding": [ { @@ -2357,31 +4120,31 @@ "type": "tidelift" } ], - "install-path": "../symfony/polyfill-php70" + "install-path": "../symfony/polyfill-php73" }, { - "name": "symfony/polyfill-util", - "version": "v1.19.0", - "version_normalized": "1.19.0.0", + "name": "symfony/polyfill-php80", + "version": "v1.26.0", + "version_normalized": "1.26.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-util.git", - "reference": "8df0c3e6a4b85df9a5c6f3f2f46fba5c5c47058a" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/8df0c3e6a4b85df9a5c6f3f2f46fba5c5c47058a", - "reference": "8df0c3e6a4b85df9a5c6f3f2f46fba5c5c47058a", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, - "time": "2020-10-21T09:57:48+00:00", + "time": "2022-05-10T07:21:04+00:00", "type": "library", "extra": { "branch-alias": { - "dev-main": "1.19-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2390,9 +4153,101 @@ }, "installation-source": "dist", "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Polyfill\\Util\\": "" + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php80" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.26.0", + "version_normalized": "1.26.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1", + "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-05-24T11:49:31+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2408,16 +4263,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony utilities for portability of PHP codes", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ - "compat", "compatibility", "polyfill", + "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-util/tree/v1.19.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0" }, "funding": [ { @@ -2433,48 +4288,50 @@ "type": "tidelift" } ], - "install-path": "../symfony/polyfill-util" + "install-path": "../symfony/polyfill-php81" }, { "name": "symfony/routing", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "3e522ac69cadffd8131cc2b22157fa7662331a6c" + "reference": "3e01ccd9b2a3a4167ba2b3c53612762300300226" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/3e522ac69cadffd8131cc2b22157fa7662331a6c", - "reference": "3e522ac69cadffd8131cc2b22157fa7662331a6c", + "url": "https://api.github.com/repos/symfony/routing/zipball/3e01ccd9b2a3a4167ba2b3c53612762300300226", + "reference": "3e01ccd9b2a3a4167ba2b3c53612762300300226", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" }, "conflict": { - "symfony/config": "<3.3.1", - "symfony/dependency-injection": "<3.3", - "symfony/yaml": "<3.4" + "doctrine/annotations": "<1.12", + "symfony/config": "<5.3", + "symfony/dependency-injection": "<4.4", + "symfony/yaml": "<4.4" }, "require-dev": { - "doctrine/annotations": "~1.0", - "psr/log": "~1.0", - "symfony/config": "^3.3.1|~4.0", - "symfony/dependency-injection": "~3.3|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/http-foundation": "~2.8|~3.0|~4.0", - "symfony/yaml": "~3.4|~4.0" + "doctrine/annotations": "^1.12", + "psr/log": "^1|^2|^3", + "symfony/config": "^5.3|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/yaml": "^4.4|^5.0|^6.0" }, "suggest": { - "doctrine/annotations": "For using the annotation loader", "symfony/config": "For using the all-in-one router or any loader", "symfony/expression-language": "For using expression matching", "symfony/http-foundation": "For using a Symfony Request object", "symfony/yaml": "For using the YAML loader" }, - "time": "2020-10-24T10:57:07+00:00", + "time": "2022-07-20T13:00:38+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2499,7 +4356,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Routing Component", + "description": "Maps an HTTP request to a set of configuration variables", "homepage": "https://symfony.com", "keywords": [ "router", @@ -2508,7 +4365,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v3.4.47" + "source": "https://github.com/symfony/routing/tree/v5.4.11" }, "funding": [ { @@ -2527,24 +4384,111 @@ "install-path": "../symfony/routing" }, { - "name": "symfony/stopwatch", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "name": "symfony/service-contracts", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", "source": { "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "298b81faad4ce60e94466226b2abbb8c9bca7462" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/298b81faad4ce60e94466226b2abbb8c9bca7462", - "reference": "298b81faad4ce60e94466226b2abbb8c9bca7462", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" }, - "time": "2020-10-24T10:57:07+00:00", + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "time": "2022-05-30T19:17:29+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/service-contracts" + }, + { + "name": "symfony/stopwatch", + "version": "v5.4.5", + "version_normalized": "5.4.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "4d04b5c24f3c9a1a168a131f6cbe297155bc0d30" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/4d04b5c24f3c9a1a168a131f6cbe297155bc0d30", + "reference": "4d04b5c24f3c9a1a168a131f6cbe297155bc0d30", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/service-contracts": "^1|^2|^3" + }, + "time": "2022-02-18T16:06:09+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2569,10 +4513,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Stopwatch Component", + "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v3.4.47" + "source": "https://github.com/symfony/stopwatch/tree/v5.4.5" }, "funding": [ { @@ -2591,49 +4535,236 @@ "install-path": "../symfony/stopwatch" }, { - "name": "symfony/twig-bridge", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "name": "symfony/string", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", "source": { "type": "git", - "url": "https://github.com/symfony/twig-bridge.git", - "reference": "090d19d6f1ea5b9e1d79f372785aa5e5c9cd4042" + "url": "https://github.com/symfony/string.git", + "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/090d19d6f1ea5b9e1d79f372785aa5e5c9cd4042", - "reference": "090d19d6f1ea5b9e1d79f372785aa5e5c9cd4042", + "url": "https://api.github.com/repos/symfony/string/zipball/5eb661e49ad389e4ae2b6e4df8d783a8a6548322", + "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "twig/twig": "^1.41|^2.10" + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" }, "conflict": { - "symfony/console": "<3.4", - "symfony/form": "<3.4.31|>=4.0,<4.3.4" + "symfony/translation-contracts": ">=3.0" }, "require-dev": { - "fig/link-util": "^1.0", - "symfony/asset": "~2.8|~3.0|~4.0", - "symfony/console": "~3.4|~4.0", - "symfony/dependency-injection": "~2.8|~3.0|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/form": "^3.4.31|^4.3.4", - "symfony/http-foundation": "^3.3.11|~4.0", - "symfony/http-kernel": "~3.2|~4.0", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "time": "2022-07-24T16:15:25+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/string" + }, + { + "name": "symfony/translation-contracts", + "version": "v2.5.2", + "version_normalized": "2.5.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/136b19dd05cdf0709db6537d058bcab6dd6e2dbe", + "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "time": "2022-06-27T16:58:25+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/translation-contracts" + }, + { + "name": "symfony/twig-bridge", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "63b8a50d48c9fe3d04e77307d4f1771dd848baa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/63b8a50d48c9fe3d04e77307d4f1771dd848baa8", + "reference": "63b8a50d48c9fe3d04e77307d4f1771dd848baa8", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16", + "symfony/translation-contracts": "^1.1|^2|^3", + "twig/twig": "^2.13|^3.0.4" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/console": "<5.3", + "symfony/form": "<5.3", + "symfony/http-foundation": "<5.3", + "symfony/http-kernel": "<4.4", + "symfony/translation": "<5.2", + "symfony/workflow": "<5.2" + }, + "require-dev": { + "doctrine/annotations": "^1.12", + "egulias/email-validator": "^2.1.10|^3", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^4.4|^5.0|^6.0", + "symfony/console": "^5.3|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/form": "^5.3|^6.0", + "symfony/http-foundation": "^5.3|^6.0", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/intl": "^4.4|^5.0|^6.0", + "symfony/mime": "^5.2|^6.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/routing": "~2.8|~3.0|~4.0", - "symfony/security": "^2.8.31|^3.3.13|~4.0", - "symfony/security-acl": "~2.8|~3.0", - "symfony/stopwatch": "~2.8|~3.0|~4.0", - "symfony/templating": "~2.8|~3.0|~4.0", - "symfony/translation": "~2.8|~3.0|~4.0", - "symfony/var-dumper": "~2.8.10|~3.1.4|~3.2|~4.0", - "symfony/web-link": "~3.3|~4.0", - "symfony/workflow": "~3.3|~4.0", - "symfony/yaml": "~2.8|~3.0|~4.0" + "symfony/property-info": "^4.4|^5.1|^6.0", + "symfony/routing": "^4.4|^5.0|^6.0", + "symfony/security-acl": "^2.8|^3.0", + "symfony/security-core": "^4.4|^5.0|^6.0", + "symfony/security-csrf": "^4.4|^5.0|^6.0", + "symfony/security-http": "^4.4|^5.0|^6.0", + "symfony/serializer": "^5.2|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0", + "symfony/translation": "^5.2|^6.0", + "symfony/web-link": "^4.4|^5.0|^6.0", + "symfony/workflow": "^5.2|^6.0", + "symfony/yaml": "^4.4|^5.0|^6.0", + "twig/cssinliner-extra": "^2.12|^3", + "twig/inky-extra": "^2.12|^3", + "twig/markdown-extra": "^2.12|^3" }, "suggest": { "symfony/asset": "For using the AssetExtension", @@ -2642,15 +4773,16 @@ "symfony/form": "For using the FormExtension", "symfony/http-kernel": "For using the HttpKernelExtension", "symfony/routing": "For using the RoutingExtension", - "symfony/security": "For using the SecurityExtension", + "symfony/security-core": "For using the SecurityExtension", + "symfony/security-csrf": "For using the CsrfExtension", + "symfony/security-http": "For using the LogoutUrlExtension", "symfony/stopwatch": "For using the StopwatchExtension", - "symfony/templating": "For using the TwigEngine", "symfony/translation": "For using the TranslationExtension", "symfony/var-dumper": "For using the DumpExtension", "symfony/web-link": "For using the WebLinkExtension", "symfony/yaml": "For using the YamlExtension" }, - "time": "2020-10-24T10:57:07+00:00", + "time": "2022-07-20T13:00:38+00:00", "type": "symfony-bridge", "installation-source": "dist", "autoload": { @@ -2675,10 +4807,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Twig Bridge", + "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v3.4.47" + "source": "https://github.com/symfony/twig-bridge/tree/v5.4.11" }, "funding": [ { @@ -2698,49 +4830,51 @@ }, { "name": "symfony/twig-bundle", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "version": "v5.4.8", + "version_normalized": "5.4.8.0", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "977b3096e2df96bc8a8d2329e83466cfc30c373d" + "reference": "c992b4474c3a31f3c40a1ca593d213833f91b818" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/977b3096e2df96bc8a8d2329e83466cfc30c373d", - "reference": "977b3096e2df96bc8a8d2329e83466cfc30c373d", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/c992b4474c3a31f3c40a1ca593d213833f91b818", + "reference": "c992b4474c3a31f3c40a1ca593d213833f91b818", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/config": "~3.2|~4.0", - "symfony/debug": "~2.8|~3.0|~4.0", - "symfony/http-foundation": "~2.8|~3.0|~4.0", - "symfony/http-kernel": "^3.3|~4.0", + "php": ">=7.2.5", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^5.0|^6.0", "symfony/polyfill-ctype": "~1.8", - "symfony/twig-bridge": "^3.4.3|^4.0.3", - "twig/twig": "~1.41|~2.10" + "symfony/polyfill-php80": "^1.16", + "symfony/twig-bridge": "^5.3|^6.0", + "twig/twig": "^2.13|^3.0.4" }, "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/event-dispatcher": "<3.3.1" + "symfony/dependency-injection": "<5.3", + "symfony/framework-bundle": "<5.0", + "symfony/service-contracts": ">=3.0", + "symfony/translation": "<5.0" }, "require-dev": { - "doctrine/annotations": "~1.7", - "doctrine/cache": "~1.0", - "symfony/asset": "~2.8|~3.0|~4.0", - "symfony/dependency-injection": "~3.4.24|^4.2.5", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/form": "~2.8|~3.0|~4.0", - "symfony/framework-bundle": "^3.3.11|~4.0", - "symfony/routing": "~2.8|~3.0|~4.0", - "symfony/stopwatch": "~2.8|~3.0|~4.0", - "symfony/templating": "~2.8|~3.0|~4.0", - "symfony/web-link": "~3.3|~4.0", - "symfony/yaml": "~2.8|~3.0|~4.0" + "doctrine/annotations": "^1.10.4", + "doctrine/cache": "^1.0|^2.0", + "symfony/asset": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.3|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/form": "^4.4|^5.0|^6.0", + "symfony/framework-bundle": "^5.0|^6.0", + "symfony/routing": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0", + "symfony/translation": "^5.0|^6.0", + "symfony/web-link": "^4.4|^5.0|^6.0", + "symfony/yaml": "^4.4|^5.0|^6.0" }, - "time": "2020-10-24T10:57:07+00:00", + "time": "2022-04-03T13:03:10+00:00", "type": "symfony-bundle", "installation-source": "dist", "autoload": { @@ -2765,10 +4899,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony TwigBundle", + "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bundle/tree/v3.4.47" + "source": "https://github.com/symfony/twig-bundle/tree/v5.4.8" }, "funding": [ { @@ -2788,36 +4922,44 @@ }, { "name": "symfony/var-dumper", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "0719f6cf4633a38b2c1585140998579ce23b4b7d" + "reference": "b8f306d7b8ef34fb3db3305be97ba8e088fb4861" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0719f6cf4633a38b2c1585140998579ce23b4b7d", - "reference": "0719f6cf4633a38b2c1585140998579ce23b4b7d", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8f306d7b8ef34fb3db3305be97ba8e088fb4861", + "reference": "b8f306d7b8ef34fb3db3305be97ba8e088fb4861", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16" }, "conflict": { - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<4.4" }, "require-dev": { "ext-iconv": "*", - "twig/twig": "~1.34|~2.4" + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/uid": "^5.1|^6.0", + "twig/twig": "^2.13|^3.0.4" }, "suggest": { "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", "ext-intl": "To show region name in time zone dump", - "ext-symfony_debug": "" + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" }, - "time": "2020-10-24T10:57:07+00:00", + "time": "2022-07-20T13:00:38+00:00", + "bin": [ + "Resources/bin/var-dump-server" + ], "type": "library", "installation-source": "dist", "autoload": { @@ -2845,14 +4987,14 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony mechanism for exploring and dumping PHP variables", + "description": "Provides mechanisms for walking through any arbitrary PHP variable", "homepage": "https://symfony.com", "keywords": [ "debug", "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v3.4.47" + "source": "https://github.com/symfony/var-dumper/tree/v5.4.11" }, "funding": [ { @@ -2871,44 +5013,119 @@ "install-path": "../symfony/var-dumper" }, { - "name": "symfony/web-profiler-bundle", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "name": "symfony/var-exporter", + "version": "v5.4.10", + "version_normalized": "5.4.10.0", "source": { "type": "git", - "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "ccb83b3a508f4a683e44f571f127beebdc315ff9" + "url": "https://github.com/symfony/var-exporter.git", + "reference": "8fc03ee75eeece3d9be1ef47d26d79bea1afb340" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/ccb83b3a508f4a683e44f571f127beebdc315ff9", - "reference": "ccb83b3a508f4a683e44f571f127beebdc315ff9", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/8fc03ee75eeece3d9be1ef47d26d79bea1afb340", + "reference": "8fc03ee75eeece3d9be1ef47d26d79bea1afb340", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/config": "~3.4|~4.0", - "symfony/http-kernel": "~3.4.25|^4.2.6", - "symfony/polyfill-php70": "~1.0", - "symfony/routing": "~3.4.7|~4.0", - "symfony/twig-bundle": "~3.4|~4.0", - "symfony/var-dumper": "~3.3|~4.0", - "twig/twig": "~1.34|~2.4" - }, - "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/event-dispatcher": "<3.3.1", - "symfony/framework-bundle": ">4.3.99", - "symfony/var-dumper": "<3.3" + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "symfony/browser-kit": "~3.4|~4.0", - "symfony/console": "~3.4|~4.0", - "symfony/css-selector": "~3.4|~4.0", - "symfony/framework-bundle": "~3.4|~4.0", - "symfony/stopwatch": "~3.4|~4.0" + "symfony/var-dumper": "^4.4.9|^5.0.9|^6.0" }, - "time": "2020-10-24T10:57:07+00:00", + "time": "2022-05-27T12:56:18+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v5.4.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/var-exporter" + }, + { + "name": "symfony/web-profiler-bundle", + "version": "v5.4.10", + "version_normalized": "5.4.10.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/web-profiler-bundle.git", + "reference": "f61c99d8dbd864b11935851b598f784bcff36fc7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/f61c99d8dbd864b11935851b598f784bcff36fc7", + "reference": "f61c99d8dbd864b11935851b598f784bcff36fc7", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/framework-bundle": "^5.3|^6.0", + "symfony/http-kernel": "^5.3|^6.0", + "symfony/polyfill-php80": "^1.16", + "symfony/routing": "^4.4|^5.0|^6.0", + "symfony/twig-bundle": "^4.4|^5.0|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "conflict": { + "symfony/dependency-injection": "<5.2", + "symfony/form": "<4.4", + "symfony/mailer": "<5.4", + "symfony/messenger": "<4.4" + }, + "require-dev": { + "symfony/browser-kit": "^4.4|^5.0|^6.0", + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/css-selector": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0" + }, + "time": "2022-06-06T19:10:58+00:00", "type": "symfony-bundle", "installation-source": "dist", "autoload": { @@ -2933,10 +5150,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony WebProfilerBundle", + "description": "Provides a development tool that gives detailed information about the execution of any request", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v3.4.47" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v5.4.10" }, "funding": [ { @@ -2956,33 +5173,37 @@ }, { "name": "symfony/yaml", - "version": "v3.4.47", - "version_normalized": "3.4.47.0", + "version": "v5.4.11", + "version_normalized": "5.4.11.0", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "88289caa3c166321883f67fe5130188ebbb47094" + "reference": "05d4ea560f3402c6c116afd99fdc66e60eda227e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/88289caa3c166321883f67fe5130188ebbb47094", - "reference": "88289caa3c166321883f67fe5130188ebbb47094", + "url": "https://api.github.com/repos/symfony/yaml/zipball/05d4ea560f3402c6c116afd99fdc66e60eda227e", + "reference": "05d4ea560f3402c6c116afd99fdc66e60eda227e", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<3.4" + "symfony/console": "<5.3" }, "require-dev": { - "symfony/console": "~3.4|~4.0" + "symfony/console": "^5.3|^6.0" }, "suggest": { "symfony/console": "For validating YAML files using the lint command" }, - "time": "2020-10-24T10:57:07+00:00", + "time": "2022-06-27T16:58:25+00:00", + "bin": [ + "Resources/bin/yaml-lint" + ], "type": "library", "installation-source": "dist", "autoload": { @@ -3007,10 +5228,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Yaml Component", + "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v3.4.47" + "source": "https://github.com/symfony/yaml/tree/v5.4.11" }, "funding": [ { @@ -3029,41 +5250,98 @@ "install-path": "../symfony/yaml" }, { - "name": "twig/twig", - "version": "v1.42.4", - "version_normalized": "1.42.4.0", + "name": "thenetworg/oauth2-azure", + "version": "v2.1.1", + "version_normalized": "2.1.1.0", "source": { "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "e587180584c3d2d6cb864a0454e777bb6dcb6152" + "url": "https://github.com/TheNetworg/oauth2-azure.git", + "reference": "06fb2d620fb6e6c934f632c7ec7c5ea2e978a844" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/e587180584c3d2d6cb864a0454e777bb6dcb6152", - "reference": "e587180584c3d2d6cb864a0454e777bb6dcb6152", + "url": "https://api.github.com/repos/TheNetworg/oauth2-azure/zipball/06fb2d620fb6e6c934f632c7ec7c5ea2e978a844", + "reference": "06fb2d620fb6e6c934f632c7ec7c5ea2e978a844", "shasum": "" }, "require": { - "php": ">=5.5.0", - "symfony/polyfill-ctype": "^1.8" + "ext-json": "*", + "ext-openssl": "*", + "firebase/php-jwt": "~3.0||~4.0||~5.0||~6.0", + "league/oauth2-client": "~2.0", + "php": "^7.1|^8.0" + }, + "time": "2022-06-23T10:35:36+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "TheNetworg\\OAuth2\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Hajek", + "email": "jan.hajek@thenetw.org", + "homepage": "https://thenetw.org" + } + ], + "description": "Azure Active Directory OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "keywords": [ + "SSO", + "aad", + "authorization", + "azure", + "azure active directory", + "client", + "microsoft", + "oauth", + "oauth2", + "windows azure" + ], + "support": { + "issues": "https://github.com/TheNetworg/oauth2-azure/issues", + "source": "https://github.com/TheNetworg/oauth2-azure/tree/v2.1.1" + }, + "install-path": "../thenetworg/oauth2-azure" + }, + { + "name": "twig/twig", + "version": "v3.4.2", + "version_normalized": "3.4.2.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077", + "reference": "e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { "psr/container": "^1.0", - "symfony/debug": "^3.4|^4.2", - "symfony/phpunit-bridge": "^4.4@dev|^5.0" + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" }, - "time": "2019-11-11T16:49:32+00:00", + "time": "2022-08-12T06:47:24+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "1.42-dev" + "dev-master": "3.4-dev" } }, "installation-source": "dist", "autoload": { - "psr-0": { - "Twig_": "lib/" - }, "psr-4": { "Twig\\": "src/" } @@ -3081,7 +5359,6 @@ }, { "name": "Twig Team", - "homepage": "https://twig.symfony.com/contributors", "role": "Contributors" }, { @@ -3095,13 +5372,87 @@ "keywords": [ "templating" ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.4.2" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], "install-path": "../twig/twig" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "version_normalized": "1.11.0.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "time": "2022-06-03T18:03:27+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "install-path": "../webmozart/assert" } ], "dev": true, "dev-package-names": [ "symfony/stopwatch", - "symfony/var-dumper", "symfony/web-profiler-bundle" ] } diff --git a/lib/composer/installed.php b/lib/composer/installed.php index 1e691e0f22..dee14e84b5 100644 --- a/lib/composer/installed.php +++ b/lib/composer/installed.php @@ -5,45 +5,168 @@ 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => '9ad341f73a2aa0bd50515d88e1daac99b124238f', - 'name' => '__root__', + 'reference' => '7b60c9c71af3167cc075063ce67b836b96a9e2f0', + 'name' => 'combodo/itop', 'dev' => true, ), 'versions' => array( - '__root__' => array( + 'apereo/phpcas' => array( + 'pretty_version' => '1.5.0', + 'version' => '1.5.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../apereo/phpcas', + 'aliases' => array(), + 'reference' => 'd6f5797fb568726f34c8e48741776d81e4a2646b', + 'dev_requirement' => false, + ), + 'combodo/itop' => array( 'pretty_version' => 'dev-develop', 'version' => 'dev-develop', 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => '9ad341f73a2aa0bd50515d88e1daac99b124238f', + 'reference' => '7b60c9c71af3167cc075063ce67b836b96a9e2f0', 'dev_requirement' => false, ), 'combodo/tcpdf' => array( - 'pretty_version' => '6.3.5', - 'version' => '6.3.5.0', + 'pretty_version' => '6.4.4', + 'version' => '6.4.4.0', 'type' => 'library', 'install_path' => __DIR__ . '/../combodo/tcpdf', 'aliases' => array(), - 'reference' => 'aedd4b7b8cf7fcc24e617c405c9d3304150f4b94', + 'reference' => '0e31c013ccd000aa6762e9186778aa6e259ac8e8', + 'dev_requirement' => false, + ), + 'container-interop/container-interop' => array( + 'dev_requirement' => false, + 'replaced' => array( + 0 => '^1.2.0', + ), + ), + 'firebase/php-jwt' => array( + 'pretty_version' => 'v6.3.0', + 'version' => '6.3.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../firebase/php-jwt', + 'aliases' => array(), + 'reference' => '018dfc4e1da92ad8a1b90adc4893f476a3b41cb8', + 'dev_requirement' => false, + ), + 'guzzlehttp/guzzle' => array( + 'pretty_version' => '7.4.5', + 'version' => '7.4.5.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/guzzle', + 'aliases' => array(), + 'reference' => '1dd98b0564cb3f6bd16ce683cb755f94c10fbd82', + 'dev_requirement' => false, + ), + 'guzzlehttp/promises' => array( + 'pretty_version' => '1.5.1', + 'version' => '1.5.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/promises', + 'aliases' => array(), + 'reference' => 'fe752aedc9fd8fcca3fe7ad05d419d32998a06da', + 'dev_requirement' => false, + ), + 'guzzlehttp/psr7' => array( + 'pretty_version' => '2.4.0', + 'version' => '2.4.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../guzzlehttp/psr7', + 'aliases' => array(), + 'reference' => '13388f00956b1503577598873fffb5ae994b5737', + 'dev_requirement' => false, + ), + 'laminas/laminas-loader' => array( + 'pretty_version' => '2.8.0', + 'version' => '2.8.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../laminas/laminas-loader', + 'aliases' => array(), + 'reference' => 'd0589ec9dd48365fd95ad10d1c906efd7711c16b', + 'dev_requirement' => false, + ), + 'laminas/laminas-mail' => array( + 'pretty_version' => '2.16.0', + 'version' => '2.16.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../laminas/laminas-mail', + 'aliases' => array(), + 'reference' => '1ee1a384b96c8af29ecad9b3a7adc27a150ebc49', + 'dev_requirement' => false, + ), + 'laminas/laminas-mime' => array( + 'pretty_version' => '2.9.1', + 'version' => '2.9.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../laminas/laminas-mime', + 'aliases' => array(), + 'reference' => '72d21a1b4bb7086d4a4d7058c0abca180b209184', + 'dev_requirement' => false, + ), + 'laminas/laminas-servicemanager' => array( + 'pretty_version' => '3.16.0', + 'version' => '3.16.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../laminas/laminas-servicemanager', + 'aliases' => array(), + 'reference' => '863c66733740cd36ebf5e700f4258ef2c68a2a24', + 'dev_requirement' => false, + ), + 'laminas/laminas-stdlib' => array( + 'pretty_version' => '3.12.0', + 'version' => '3.12.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../laminas/laminas-stdlib', + 'aliases' => array(), + 'reference' => 'c5aed3c798018e31fbb7b1e421b8d96bf2cda453', + 'dev_requirement' => false, + ), + 'laminas/laminas-validator' => array( + 'pretty_version' => '2.23.0', + 'version' => '2.23.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../laminas/laminas-validator', + 'aliases' => array(), + 'reference' => '6d61b6cc3b222f13807a18d9247cdfb084958b03', + 'dev_requirement' => false, + ), + 'league/oauth2-client' => array( + 'pretty_version' => '2.6.1', + 'version' => '2.6.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../league/oauth2-client', + 'aliases' => array(), + 'reference' => '2334c249907190c132364f5dae0287ab8666aa19', + 'dev_requirement' => false, + ), + 'league/oauth2-google' => array( + 'pretty_version' => '3.0.4', + 'version' => '3.0.4.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../league/oauth2-google', + 'aliases' => array(), + 'reference' => '6b79441f244040760bed5fdcd092a2bda7cf34c6', 'dev_requirement' => false, ), 'nikic/php-parser' => array( - 'pretty_version' => 'v4.12.0', - 'version' => '4.12.0.0', + 'pretty_version' => 'v4.14.0', + 'version' => '4.14.0.0', 'type' => 'library', 'install_path' => __DIR__ . '/../nikic/php-parser', 'aliases' => array(), - 'reference' => '6608f01670c3cc5079e18c1dab1104e002579143', + 'reference' => '34bea19b6e03d8153165d8f30bba4c3be86184c1', 'dev_requirement' => false, ), 'paragonie/random_compat' => array( - 'pretty_version' => 'v2.0.18', - 'version' => '2.0.18.0', + 'pretty_version' => 'v9.99.100', + 'version' => '9.99.100.0', 'type' => 'library', 'install_path' => __DIR__ . '/../paragonie/random_compat', 'aliases' => array(), - 'reference' => '0a58ef6e3146256cc3dc7cc393927bcc7d1b72db', + 'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a', 'dev_requirement' => false, ), 'pear/archive_tar' => array( @@ -65,12 +188,12 @@ 'dev_requirement' => false, ), 'pear/pear-core-minimal' => array( - 'pretty_version' => 'v1.10.10', - 'version' => '1.10.10.0', + 'pretty_version' => 'v1.10.11', + 'version' => '1.10.11.0', 'type' => 'library', 'install_path' => __DIR__ . '/../pear/pear-core-minimal', 'aliases' => array(), - 'reference' => '625a3c429d9b2c1546438679074cac1b089116a7', + 'reference' => '68d0d32ada737153b7e93b8d3c710ebe70ac867d', 'dev_requirement' => false, ), 'pear/pear_exception' => array( @@ -83,12 +206,12 @@ 'dev_requirement' => false, ), 'pelago/emogrifier' => array( - 'pretty_version' => 'v3.1.0', - 'version' => '3.1.0.0', + 'pretty_version' => 'v6.0.0', + 'version' => '6.0.0.0', 'type' => 'library', 'install_path' => __DIR__ . '/../pelago/emogrifier', 'aliases' => array(), - 'reference' => 'f6a5c7d44612d86c3901c93f1592f5440e6b2cd8', + 'reference' => 'aa72d5407efac118f3896bcb995a2cba793df0ae', 'dev_requirement' => false, ), 'psr/cache' => array( @@ -103,334 +226,512 @@ 'psr/cache-implementation' => array( 'dev_requirement' => false, 'provided' => array( - 0 => '1.0', + 0 => '1.0|2.0', ), ), 'psr/container' => array( - 'pretty_version' => '1.0.0', - 'version' => '1.0.0.0', + 'pretty_version' => '1.1.2', + 'version' => '1.1.2.0', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/container', 'aliases' => array(), - 'reference' => 'b7ce3b176482dbbc1245ebf52b181af44c2cf55f', + 'reference' => '513e0666f7216c7459170d56df27dfcefe1689ea', 'dev_requirement' => false, ), 'psr/container-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '^1.0', + 1 => '1.0', + ), + ), + 'psr/event-dispatcher' => array( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/event-dispatcher', + 'aliases' => array(), + 'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0', + 'dev_requirement' => false, + ), + 'psr/event-dispatcher-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/http-client' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-client', + 'aliases' => array(), + 'reference' => '2dfb5f6c5eff0e91e20e913f8c5452ed95b86621', + 'dev_requirement' => false, + ), + 'psr/http-client-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/http-factory' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-factory', + 'aliases' => array(), + 'reference' => '12ac7fcd07e5b077433f5f2bee95b3a771bf61be', + 'dev_requirement' => false, + ), + 'psr/http-factory-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0', + ), + ), + 'psr/http-message' => array( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/http-message', + 'aliases' => array(), + 'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363', + 'dev_requirement' => false, + ), + 'psr/http-message-implementation' => array( 'dev_requirement' => false, 'provided' => array( 0 => '1.0', ), ), 'psr/log' => array( - 'pretty_version' => '1.1.2', - 'version' => '1.1.2.0', + 'pretty_version' => '1.1.4', + 'version' => '1.1.4.0', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/log', 'aliases' => array(), - 'reference' => '446d54b4cb6bf489fc9d75f55843658e6f25d801', + 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', 'dev_requirement' => false, ), 'psr/log-implementation' => array( 'dev_requirement' => false, 'provided' => array( - 0 => '1.0', + 0 => '1.0|2.0', ), ), - 'psr/simple-cache' => array( - 'pretty_version' => '1.0.1', - 'version' => '1.0.1.0', - 'type' => 'library', - 'install_path' => __DIR__ . '/../psr/simple-cache', - 'aliases' => array(), - 'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b', - 'dev_requirement' => false, - ), 'psr/simple-cache-implementation' => array( 'dev_requirement' => false, 'provided' => array( - 0 => '1.0', + 0 => '1.0|2.0', ), ), + 'ralouphie/getallheaders' => array( + 'pretty_version' => '3.0.3', + 'version' => '3.0.3.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../ralouphie/getallheaders', + 'aliases' => array(), + 'reference' => '120b605dfeb996808c31b6477290a714d356e822', + 'dev_requirement' => false, + ), 'rsky/pear-core-min' => array( 'dev_requirement' => false, 'replaced' => array( - 0 => 'v1.10.10', + 0 => 'v1.10.11', ), ), + 'sabberworm/php-css-parser' => array( + 'pretty_version' => '8.4.0', + 'version' => '8.4.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../sabberworm/php-css-parser', + 'aliases' => array(), + 'reference' => 'e41d2140031d533348b2192a83f02d8dd8a71d30', + 'dev_requirement' => false, + ), 'scssphp/scssphp' => array( - 'pretty_version' => '1.0.6', - 'version' => '1.0.6.0', + 'pretty_version' => 'v1.10.5', + 'version' => '1.10.5.0', 'type' => 'library', 'install_path' => __DIR__ . '/../scssphp/scssphp', 'aliases' => array(), - 'reference' => '5b3c9d704950d8f9637f5110c36c281ec47dc13c', - 'dev_requirement' => false, - ), - 'swiftmailer/swiftmailer' => array( - 'pretty_version' => 'v5.4.12', - 'version' => '5.4.12.0', - 'type' => 'library', - 'install_path' => __DIR__ . '/../swiftmailer/swiftmailer', - 'aliases' => array(), - 'reference' => '181b89f18a90f8925ef805f950d47a7190e9b950', + 'reference' => '6d44282ccf283e133ab70b6282f8e068ff2f9bf9', 'dev_requirement' => false, ), 'symfony/cache' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/cache', 'aliases' => array(), - 'reference' => 'a7a14c4832760bd1fbd31be2859ffedc9b6ff813', + 'reference' => '5a0fff46df349f0db3fe242263451fddf5277362', 'dev_requirement' => false, ), - 'symfony/class-loader' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'symfony/cache-contracts' => array( + 'pretty_version' => 'v2.5.2', + 'version' => '2.5.2.0', 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/class-loader', + 'install_path' => __DIR__ . '/../symfony/cache-contracts', 'aliases' => array(), - 'reference' => 'a22265a9f3511c0212bf79f54910ca5a77c0e92c', + 'reference' => '64be4a7acb83b6f2bf6de9a02cee6dad41277ebc', 'dev_requirement' => false, ), + 'symfony/cache-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0|2.0', + ), + ), 'symfony/config' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/config', 'aliases' => array(), - 'reference' => 'bc6b3fd3930d4b53a60b42fe2ed6fc466b75f03f', + 'reference' => 'ec79e03125c1d2477e43dde8528535d90cc78379', 'dev_requirement' => false, ), 'symfony/console' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/console', 'aliases' => array(), - 'reference' => 'a10b1da6fc93080c180bba7219b5ff5b7518fe81', + 'reference' => '535846c7ee6bc4dd027ca0d93220601456734b10', 'dev_requirement' => false, ), 'symfony/css-selector' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/css-selector', 'aliases' => array(), - 'reference' => 'da3d9da2ce0026771f5fe64cb332158f1bd2bc33', - 'dev_requirement' => false, - ), - 'symfony/debug' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', - 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/debug', - 'aliases' => array(), - 'reference' => 'ab42889de57fdfcfcc0759ab102e2fd4ea72dcae', + 'reference' => 'c1681789f059ab756001052164726ae88512ae3d', 'dev_requirement' => false, ), 'symfony/dependency-injection' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/dependency-injection', 'aliases' => array(), - 'reference' => '51d2a2708c6ceadad84393f8581df1dcf9e5e84b', + 'reference' => 'a8b9251016e9476db73e25fa836904bc0bf74c62', + 'dev_requirement' => false, + ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v2.5.2', + 'version' => '2.5.2.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'reference' => 'e8b495ea28c1d97b5e0c121748d6f9b53d075c66', 'dev_requirement' => false, ), 'symfony/dotenv' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'pretty_version' => 'v5.4.5', + 'version' => '5.4.5.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/dotenv', 'aliases' => array(), - 'reference' => '1022723ac4f56b001d99691d96c6025dbf1404f1', + 'reference' => '83a2310904a4f5d4f42526227b5a578ac82232a9', + 'dev_requirement' => false, + ), + 'symfony/error-handler' => array( + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/error-handler', + 'aliases' => array(), + 'reference' => 'f75d17cb4769eb38cd5fccbda95cd80a054d35c8', 'dev_requirement' => false, ), 'symfony/event-dispatcher' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'pretty_version' => 'v5.4.9', + 'version' => '5.4.9.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/event-dispatcher', 'aliases' => array(), - 'reference' => '31fde73757b6bad247c54597beef974919ec6860', + 'reference' => '8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc', 'dev_requirement' => false, ), + 'symfony/event-dispatcher-contracts' => array( + 'pretty_version' => 'v2.5.2', + 'version' => '2.5.2.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts', + 'aliases' => array(), + 'reference' => 'f98b54df6ad059855739db6fcbc2d36995283fe1', + 'dev_requirement' => false, + ), + 'symfony/event-dispatcher-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '2.0', + ), + ), 'symfony/filesystem' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/filesystem', 'aliases' => array(), - 'reference' => 'e58d7841cddfed6e846829040dca2cca0ebbbbb3', + 'reference' => '6699fb0228d1bc35b12aed6dd5e7455457609ddd', 'dev_requirement' => false, ), 'symfony/finder' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/finder', 'aliases' => array(), - 'reference' => 'b6b6ad3db3edb1b4b1c1896b1975fb684994de6e', + 'reference' => '7872a66f57caffa2916a584db1aa7f12adc76f8c', 'dev_requirement' => false, ), 'symfony/framework-bundle' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', 'type' => 'symfony-bundle', 'install_path' => __DIR__ . '/../symfony/framework-bundle', 'aliases' => array(), - 'reference' => '6c95e747b75ddd2af61152ce93bf87299d15710e', + 'reference' => 'a0660b602357d5c2ceaac1c9f80c5820bbff803d', 'dev_requirement' => false, ), 'symfony/http-foundation' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/http-foundation', 'aliases' => array(), - 'reference' => 'b9885fcce6fe494201da4f70a9309770e9d13dc8', + 'reference' => '0a5868e0999e9d47859ba3d918548ff6943e6389', 'dev_requirement' => false, ), 'symfony/http-kernel' => array( - 'pretty_version' => 'v3.4.49', - 'version' => '3.4.49.0', + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/http-kernel', 'aliases' => array(), - 'reference' => '5aa72405f5bd5583c36ed6e756acb17d3f98ac40', - 'dev_requirement' => false, - ), - 'symfony/polyfill-apcu' => array( - 'pretty_version' => 'v1.19.0', - 'version' => '1.19.0.0', - 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/polyfill-apcu', - 'aliases' => array(), - 'reference' => 'b44b51e7814c23bfbd793a16ead5d7ce43ed23c5', + 'reference' => '4fd590a2ef3f62560dbbf6cea511995dd77321ee', 'dev_requirement' => false, ), 'symfony/polyfill-ctype' => array( - 'pretty_version' => 'v1.19.0', - 'version' => '1.19.0.0', + 'pretty_version' => 'v1.26.0', + 'version' => '1.26.0.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', 'aliases' => array(), - 'reference' => 'aed596913b70fae57be53d86faa2e9ef85a2297b', + 'reference' => '6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4', + 'dev_requirement' => false, + ), + 'symfony/polyfill-intl-grapheme' => array( + 'pretty_version' => 'v1.26.0', + 'version' => '1.26.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme', + 'aliases' => array(), + 'reference' => '433d05519ce6990bf3530fba6957499d327395c2', + 'dev_requirement' => false, + ), + 'symfony/polyfill-intl-idn' => array( + 'pretty_version' => 'v1.26.0', + 'version' => '1.26.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-intl-idn', + 'aliases' => array(), + 'reference' => '59a8d271f00dd0e4c2e518104cc7963f655a1aa8', + 'dev_requirement' => false, + ), + 'symfony/polyfill-intl-normalizer' => array( + 'pretty_version' => 'v1.26.0', + 'version' => '1.26.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer', + 'aliases' => array(), + 'reference' => '219aa369ceff116e673852dce47c3a41794c14bd', 'dev_requirement' => false, ), 'symfony/polyfill-mbstring' => array( - 'pretty_version' => 'v1.19.0', - 'version' => '1.19.0.0', + 'pretty_version' => 'v1.26.0', + 'version' => '1.26.0.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', 'aliases' => array(), - 'reference' => 'b5f7b932ee6fa802fc792eabd77c4c88084517ce', + 'reference' => '9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e', 'dev_requirement' => false, ), - 'symfony/polyfill-php56' => array( - 'pretty_version' => 'v1.19.0', - 'version' => '1.19.0.0', + 'symfony/polyfill-php72' => array( + 'pretty_version' => 'v1.26.0', + 'version' => '1.26.0.0', 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/polyfill-php56', + 'install_path' => __DIR__ . '/../symfony/polyfill-php72', 'aliases' => array(), - 'reference' => 'ea19621731cbd973a6702cfedef3419768bf3372', + 'reference' => 'bf44a9fd41feaac72b074de600314a93e2ae78e2', 'dev_requirement' => false, ), - 'symfony/polyfill-php70' => array( - 'pretty_version' => 'v1.19.0', - 'version' => '1.19.0.0', + 'symfony/polyfill-php73' => array( + 'pretty_version' => 'v1.26.0', + 'version' => '1.26.0.0', 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/polyfill-php70', + 'install_path' => __DIR__ . '/../symfony/polyfill-php73', 'aliases' => array(), - 'reference' => '3fe414077251a81a1b15b1c709faf5c2fbae3d4e', + 'reference' => 'e440d35fa0286f77fb45b79a03fedbeda9307e85', 'dev_requirement' => false, ), - 'symfony/polyfill-util' => array( - 'pretty_version' => 'v1.19.0', - 'version' => '1.19.0.0', + 'symfony/polyfill-php80' => array( + 'pretty_version' => 'v1.26.0', + 'version' => '1.26.0.0', 'type' => 'library', - 'install_path' => __DIR__ . '/../symfony/polyfill-util', + 'install_path' => __DIR__ . '/../symfony/polyfill-php80', 'aliases' => array(), - 'reference' => '8df0c3e6a4b85df9a5c6f3f2f46fba5c5c47058a', + 'reference' => 'cfa0ae98841b9e461207c13ab093d76b0fa7bace', + 'dev_requirement' => false, + ), + 'symfony/polyfill-php81' => array( + 'pretty_version' => 'v1.26.0', + 'version' => '1.26.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php81', + 'aliases' => array(), + 'reference' => '13f6d1271c663dc5ae9fb843a8f16521db7687a1', 'dev_requirement' => false, ), 'symfony/routing' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/routing', 'aliases' => array(), - 'reference' => '3e522ac69cadffd8131cc2b22157fa7662331a6c', + 'reference' => '3e01ccd9b2a3a4167ba2b3c53612762300300226', 'dev_requirement' => false, ), + 'symfony/service-contracts' => array( + 'pretty_version' => 'v2.5.2', + 'version' => '2.5.2.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/service-contracts', + 'aliases' => array(), + 'reference' => '4b426aac47d6427cc1a1d0f7e2ac724627f5966c', + 'dev_requirement' => false, + ), + 'symfony/service-implementation' => array( + 'dev_requirement' => false, + 'provided' => array( + 0 => '1.0|2.0', + ), + ), 'symfony/stopwatch' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'pretty_version' => 'v5.4.5', + 'version' => '5.4.5.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/stopwatch', 'aliases' => array(), - 'reference' => '298b81faad4ce60e94466226b2abbb8c9bca7462', + 'reference' => '4d04b5c24f3c9a1a168a131f6cbe297155bc0d30', 'dev_requirement' => true, ), + 'symfony/string' => array( + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/string', + 'aliases' => array(), + 'reference' => '5eb661e49ad389e4ae2b6e4df8d783a8a6548322', + 'dev_requirement' => false, + ), + 'symfony/translation-contracts' => array( + 'pretty_version' => 'v2.5.2', + 'version' => '2.5.2.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/translation-contracts', + 'aliases' => array(), + 'reference' => '136b19dd05cdf0709db6537d058bcab6dd6e2dbe', + 'dev_requirement' => false, + ), 'symfony/twig-bridge' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', 'type' => 'symfony-bridge', 'install_path' => __DIR__ . '/../symfony/twig-bridge', 'aliases' => array(), - 'reference' => '090d19d6f1ea5b9e1d79f372785aa5e5c9cd4042', + 'reference' => '63b8a50d48c9fe3d04e77307d4f1771dd848baa8', 'dev_requirement' => false, ), 'symfony/twig-bundle' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'pretty_version' => 'v5.4.8', + 'version' => '5.4.8.0', 'type' => 'symfony-bundle', 'install_path' => __DIR__ . '/../symfony/twig-bundle', 'aliases' => array(), - 'reference' => '977b3096e2df96bc8a8d2329e83466cfc30c373d', + 'reference' => 'c992b4474c3a31f3c40a1ca593d213833f91b818', 'dev_requirement' => false, ), 'symfony/var-dumper' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/var-dumper', 'aliases' => array(), - 'reference' => '0719f6cf4633a38b2c1585140998579ce23b4b7d', - 'dev_requirement' => true, + 'reference' => 'b8f306d7b8ef34fb3db3305be97ba8e088fb4861', + 'dev_requirement' => false, + ), + 'symfony/var-exporter' => array( + 'pretty_version' => 'v5.4.10', + 'version' => '5.4.10.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/var-exporter', + 'aliases' => array(), + 'reference' => '8fc03ee75eeece3d9be1ef47d26d79bea1afb340', + 'dev_requirement' => false, ), 'symfony/web-profiler-bundle' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'pretty_version' => 'v5.4.10', + 'version' => '5.4.10.0', 'type' => 'symfony-bundle', 'install_path' => __DIR__ . '/../symfony/web-profiler-bundle', 'aliases' => array(), - 'reference' => 'ccb83b3a508f4a683e44f571f127beebdc315ff9', + 'reference' => 'f61c99d8dbd864b11935851b598f784bcff36fc7', 'dev_requirement' => true, ), 'symfony/yaml' => array( - 'pretty_version' => 'v3.4.47', - 'version' => '3.4.47.0', + 'pretty_version' => 'v5.4.11', + 'version' => '5.4.11.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/yaml', 'aliases' => array(), - 'reference' => '88289caa3c166321883f67fe5130188ebbb47094', + 'reference' => '05d4ea560f3402c6c116afd99fdc66e60eda227e', 'dev_requirement' => false, ), 'tecnickcom/tcpdf' => array( 'dev_requirement' => false, 'replaced' => array( - 0 => '6.3.5', + 0 => '6.4.4', ), ), + 'thenetworg/oauth2-azure' => array( + 'pretty_version' => 'v2.1.1', + 'version' => '2.1.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../thenetworg/oauth2-azure', + 'aliases' => array(), + 'reference' => '06fb2d620fb6e6c934f632c7ec7c5ea2e978a844', + 'dev_requirement' => false, + ), 'twig/twig' => array( - 'pretty_version' => 'v1.42.4', - 'version' => '1.42.4.0', + 'pretty_version' => 'v3.4.2', + 'version' => '3.4.2.0', 'type' => 'library', 'install_path' => __DIR__ . '/../twig/twig', 'aliases' => array(), - 'reference' => 'e587180584c3d2d6cb864a0454e777bb6dcb6152', + 'reference' => 'e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077', + 'dev_requirement' => false, + ), + 'webmozart/assert' => array( + 'pretty_version' => '1.11.0', + 'version' => '1.11.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../webmozart/assert', + 'aliases' => array(), + 'reference' => '11cb2199493b2f8a3b53e7f19068fc6aac760991', 'dev_requirement' => false, ), ), diff --git a/lib/composer/platform_check.php b/lib/composer/platform_check.php index 861c725e0a..2e6a23f9c7 100644 --- a/lib/composer/platform_check.php +++ b/lib/composer/platform_check.php @@ -4,18 +4,19 @@ $issues = array(); -if (!(PHP_VERSION_ID >= 70103)) { - $issues[] = 'Your Composer dependencies require a PHP version ">= 7.1.3". You are running ' . PHP_VERSION . '.'; +if (!(PHP_VERSION_ID >= 70400)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.'; } $missingExtensions = array(); -extension_loaded('ctype') || $missingExtensions[] = 'ctype'; +extension_loaded('curl') || $missingExtensions[] = 'curl'; extension_loaded('dom') || $missingExtensions[] = 'dom'; extension_loaded('gd') || $missingExtensions[] = 'gd'; extension_loaded('iconv') || $missingExtensions[] = 'iconv'; extension_loaded('json') || $missingExtensions[] = 'json'; extension_loaded('libxml') || $missingExtensions[] = 'libxml'; extension_loaded('mysqli') || $missingExtensions[] = 'mysqli'; +extension_loaded('openssl') || $missingExtensions[] = 'openssl'; extension_loaded('soap') || $missingExtensions[] = 'soap'; extension_loaded('tokenizer') || $missingExtensions[] = 'tokenizer'; extension_loaded('xml') || $missingExtensions[] = 'xml'; diff --git a/lib/firebase/php-jwt/LICENSE b/lib/firebase/php-jwt/LICENSE new file mode 100644 index 0000000000..11c0146651 --- /dev/null +++ b/lib/firebase/php-jwt/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2011, Neuman Vong + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the copyright holder nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/firebase/php-jwt/README.md b/lib/firebase/php-jwt/README.md new file mode 100644 index 0000000000..fed1e9541b --- /dev/null +++ b/lib/firebase/php-jwt/README.md @@ -0,0 +1,367 @@ +![Build Status](https://github.com/firebase/php-jwt/actions/workflows/tests.yml/badge.svg) +[![Latest Stable Version](https://poser.pugx.org/firebase/php-jwt/v/stable)](https://packagist.org/packages/firebase/php-jwt) +[![Total Downloads](https://poser.pugx.org/firebase/php-jwt/downloads)](https://packagist.org/packages/firebase/php-jwt) +[![License](https://poser.pugx.org/firebase/php-jwt/license)](https://packagist.org/packages/firebase/php-jwt) + +PHP-JWT +======= +A simple library to encode and decode JSON Web Tokens (JWT) in PHP, conforming to [RFC 7519](https://tools.ietf.org/html/rfc7519). + +Installation +------------ + +Use composer to manage your dependencies and download PHP-JWT: + +```bash +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: + +```bash +composer require paragonie/sodium_compat +``` + +Example +------- +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +$key = 'example_key'; +$payload = [ + 'iss' => 'http://example.org', + 'aud' => 'http://example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +/** + * IMPORTANT: + * You must specify supported algorithms for your application. See + * https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40 + * for a list of spec-compliant algorithms. + */ +$jwt = JWT::encode($payload, $key, 'HS256'); +$decoded = JWT::decode($jwt, new Key($key, 'HS256')); + +print_r($decoded); + +/* + 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: +*/ + +$decoded_array = (array) $decoded; + +/** + * You can add a leeway to account for when there is a clock skew times between + * the signing and verifying servers. It is recommended that this leeway should + * not be bigger than a few minutes. + * + * Source: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#nbfDef + */ +JWT::$leeway = 60; // $leeway in seconds +$decoded = JWT::decode($jwt, new Key($key, 'HS256')); +``` +Example with RS256 (openssl) +---------------------------- +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +$privateKey = << 'example.org', + 'aud' => 'example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +$jwt = JWT::encode($payload, $privateKey, 'RS256'); +echo "Encode:\n" . print_r($jwt, true) . "\n"; + +$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256')); + +/* + 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: +*/ + +$decoded_array = (array) $decoded; +echo "Decode:\n" . print_r($decoded_array, true) . "\n"; +``` + +Example with a passphrase +------------------------- + +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +// Your passphrase +$passphrase = '[YOUR_PASSPHRASE]'; + +// Your private key file with 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" +$privateKey = openssl_pkey_get_private( + file_get_contents($privateKeyFile), + $passphrase +); + +$payload = [ + 'iss' => 'example.org', + 'aud' => 'example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +$jwt = JWT::encode($payload, $privateKey, 'RS256'); +echo "Encode:\n" . print_r($jwt, true) . "\n"; + +// Get public key from the private key, or pull from from a file. +$publicKey = openssl_pkey_get_details($privateKey)['key']; + +$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256')); +echo "Decode:\n" . print_r((array) $decoded, true) . "\n"; +``` + +Example with EdDSA (libsodium and Ed25519 signature) +---------------------------- +```php +use Firebase\JWT\JWT; +use Firebase\JWT\Key; + +// Public and private keys are expected to be Base64 encoded. The last +// non-empty line is used so that keys can be generated with +// sodium_crypto_sign_keypair(). The secret keys generated by other tools may +// need to be adjusted to match the input expected by libsodium. + +$keyPair = sodium_crypto_sign_keypair(); + +$privateKey = base64_encode(sodium_crypto_sign_secretkey($keyPair)); + +$publicKey = base64_encode(sodium_crypto_sign_publickey($keyPair)); + +$payload = [ + 'iss' => 'example.org', + 'aud' => 'example.com', + 'iat' => 1356999524, + 'nbf' => 1357000000 +]; + +$jwt = JWT::encode($payload, $privateKey, 'EdDSA'); +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"; +```` + +Using JWKs +---------- + +```php +use Firebase\JWT\JWK; +use Firebase\JWT\JWT; + +// Set of keys. The "keys" key is required. For example, the JSON response to +// this endpoint: https://www.gstatic.com/iap/verify/public_key-jwk +$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)); +``` + +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: + +1. The results are cached for performance. +2. If an unrecognized key is requested, the cache is refreshed, to accomodate for key rotation. +3. If rate limiting is enabled, the JWKS URI will not make more than 10 requests a second. + +```php +use Firebase\JWT\CachedKeySet; +use Firebase\JWT\JWT; + +// The URI for the JWKS you wish to cache the results from +$jwksUri = 'https://www.gstatic.com/iap/verify/public_key-jwk'; + +// Create an HTTP client (can be any PSR-7 compatible HTTP client) +$httpClient = new GuzzleHttp\Client(); + +// Create an HTTP request factory (can be any PSR-17 compatible HTTP request factory) +$httpFactory = new GuzzleHttp\Psr\HttpFactory(); + +// Create a cache item pool (can be any PSR-6 compatible cache item pool) +$cacheItemPool = Phpfastcache\CacheManager::getInstance('files'); + +$keySet = new CachedKeySet( + $jwksUri, + $httpClient, + $httpFactory, + $cacheItemPool, + null, // $expiresAfter int seconds to set the JWKS to expire + true // $rateLimit true to enable rate limit of 10 RPS on lookup of invalid keys +); + +$jwt = 'eyJhbGci...'; // Some JWT signed by a key from the $jwkUri above +$decoded = JWT::decode($jwt, $keySet); +``` + +Miscellaneous +------------- + +#### Casting to array + +The return value of `JWT::decode` is the generic PHP object `stdClass`. If you'd like to handle with arrays +instead, you can do the following: + +```php +// return type is stdClass +$decoded = JWT::decode($payload, $keys); + +// cast to array +$decoded = json_decode(json_encode($decoded), true); +``` + +Changelog +--------- + +#### 6.3.0 / 2022-07-15 + + - Added ES256 support to JWK parsing ([#399](https://github.com/firebase/php-jwt/pull/399)) + - Fixed potential caching error in `CachedKeySet` by caching jwks as strings ([#435](https://github.com/firebase/php-jwt/pull/435)) + +#### 6.2.0 / 2022-05-14 + + - Added `CachedKeySet` ([#397](https://github.com/firebase/php-jwt/pull/397)) + - Added `$defaultAlg` parameter to `JWT::parseKey` and `JWT::parseKeySet` ([#426](https://github.com/firebase/php-jwt/pull/426)). + +#### 6.1.0 / 2022-03-23 + + - Drop support for PHP 5.3, 5.4, 5.5, 5.6, and 7.0 + - Add parameter typing and return types where possible + +#### 6.0.0 / 2022-01-24 + + - **Backwards-Compatibility Breaking Changes**: See the [Release Notes](https://github.com/firebase/php-jwt/releases/tag/v6.0.0) for more information. + - New Key object to prevent key/algorithm type confusion (#365) + - Add JWK support (#273) + - Add ES256 support (#256) + - Add ES384 support (#324) + - Add Ed25519 support (#343) + +#### 5.0.0 / 2017-06-26 +- Support RS384 and RS512. + See [#117](https://github.com/firebase/php-jwt/pull/117). Thanks [@joostfaassen](https://github.com/joostfaassen)! +- Add an example for RS256 openssl. + See [#125](https://github.com/firebase/php-jwt/pull/125). Thanks [@akeeman](https://github.com/akeeman)! +- Detect invalid Base64 encoding in signature. + See [#162](https://github.com/firebase/php-jwt/pull/162). Thanks [@psignoret](https://github.com/psignoret)! +- Update `JWT::verify` to handle OpenSSL errors. + See [#159](https://github.com/firebase/php-jwt/pull/159). Thanks [@bshaffer](https://github.com/bshaffer)! +- Add `array` type hinting to `decode` method + See [#101](https://github.com/firebase/php-jwt/pull/101). Thanks [@hywak](https://github.com/hywak)! +- Add all JSON error types. + See [#110](https://github.com/firebase/php-jwt/pull/110). Thanks [@gbalduzzi](https://github.com/gbalduzzi)! +- Bugfix 'kid' not in given key list. + See [#129](https://github.com/firebase/php-jwt/pull/129). Thanks [@stampycode](https://github.com/stampycode)! +- Miscellaneous cleanup, documentation and test fixes. + See [#107](https://github.com/firebase/php-jwt/pull/107), [#115](https://github.com/firebase/php-jwt/pull/115), + [#160](https://github.com/firebase/php-jwt/pull/160), [#161](https://github.com/firebase/php-jwt/pull/161), and + [#165](https://github.com/firebase/php-jwt/pull/165). Thanks [@akeeman](https://github.com/akeeman), + [@chinedufn](https://github.com/chinedufn), and [@bshaffer](https://github.com/bshaffer)! + +#### 4.0.0 / 2016-07-17 +- Add support for late static binding. See [#88](https://github.com/firebase/php-jwt/pull/88) for details. Thanks to [@chappy84](https://github.com/chappy84)! +- Use static `$timestamp` instead of `time()` to improve unit testing. See [#93](https://github.com/firebase/php-jwt/pull/93) for details. Thanks to [@josephmcdermott](https://github.com/josephmcdermott)! +- Fixes to exceptions classes. See [#81](https://github.com/firebase/php-jwt/pull/81) for details. Thanks to [@Maks3w](https://github.com/Maks3w)! +- Fixes to PHPDoc. See [#76](https://github.com/firebase/php-jwt/pull/76) for details. Thanks to [@akeeman](https://github.com/akeeman)! + +#### 3.0.0 / 2015-07-22 +- Minimum PHP version updated from `5.2.0` to `5.3.0`. +- Add `\Firebase\JWT` namespace. See +[#59](https://github.com/firebase/php-jwt/pull/59) for details. Thanks to +[@Dashron](https://github.com/Dashron)! +- Require a non-empty key to decode and verify a JWT. See +[#60](https://github.com/firebase/php-jwt/pull/60) for details. Thanks to +[@sjones608](https://github.com/sjones608)! +- Cleaner documentation blocks in the code. See +[#62](https://github.com/firebase/php-jwt/pull/62) for details. Thanks to +[@johanderuijter](https://github.com/johanderuijter)! + +#### 2.2.0 / 2015-06-22 +- Add support for adding custom, optional JWT headers to `JWT::encode()`. See +[#53](https://github.com/firebase/php-jwt/pull/53/files) for details. Thanks to +[@mcocaro](https://github.com/mcocaro)! + +#### 2.1.0 / 2015-05-20 +- Add support for adding a leeway to `JWT:decode()` that accounts for clock skew +between signing and verifying entities. Thanks to [@lcabral](https://github.com/lcabral)! +- Add support for passing an object implementing the `ArrayAccess` interface for +`$keys` argument in `JWT::decode()`. Thanks to [@aztech-dev](https://github.com/aztech-dev)! + +#### 2.0.0 / 2015-04-01 +- **Note**: It is strongly recommended that you update to > v2.0.0 to address + known security vulnerabilities in prior versions when both symmetric and + asymmetric keys are used together. +- Update signature for `JWT::decode(...)` to require an array of supported + algorithms to use when verifying token signatures. + + +Tests +----- +Run the tests using phpunit: + +```bash +$ pear install PHPUnit +$ phpunit --configuration phpunit.xml.dist +PHPUnit 3.7.10 by Sebastian Bergmann. +..... +Time: 0 seconds, Memory: 2.50Mb +OK (5 tests, 5 assertions) +``` + +New Lines in private keys +----- + +If your private key contains `\n` characters, be sure to wrap it in double quotes `""` +and not single quotes `''` in order to properly interpret the escaped characters. + +License +------- +[3-Clause BSD](http://opensource.org/licenses/BSD-3-Clause). diff --git a/lib/firebase/php-jwt/composer.json b/lib/firebase/php-jwt/composer.json new file mode 100644 index 0000000000..2a3cb2df69 --- /dev/null +++ b/lib/firebase/php-jwt/composer.json @@ -0,0 +1,41 @@ +{ + "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", + "keywords": [ + "php", + "jwt" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "license": "BSD-3-Clause", + "require": { + "php": "^7.1||^8.0" + }, + "suggest": { + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "require-dev": { + "guzzlehttp/guzzle": "^6.5||^7.4", + "phpspec/prophecy-phpunit": "^1.1", + "phpunit/phpunit": "^7.5||^9.5", + "psr/cache": "^1.0||^2.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + } +} diff --git a/lib/firebase/php-jwt/src/BeforeValidException.php b/lib/firebase/php-jwt/src/BeforeValidException.php new file mode 100644 index 0000000000..c147852b98 --- /dev/null +++ b/lib/firebase/php-jwt/src/BeforeValidException.php @@ -0,0 +1,7 @@ + + */ +class CachedKeySet implements ArrayAccess +{ + /** + * @var string + */ + private $jwksUri; + /** + * @var ClientInterface + */ + private $httpClient; + /** + * @var RequestFactoryInterface + */ + private $httpFactory; + /** + * @var CacheItemPoolInterface + */ + private $cache; + /** + * @var ?int + */ + private $expiresAfter; + /** + * @var ?CacheItemInterface + */ + private $cacheItem; + /** + * @var array + */ + private $keySet; + /** + * @var string + */ + private $cacheKey; + /** + * @var string + */ + private $cacheKeyPrefix = 'jwks'; + /** + * @var int + */ + private $maxKeyLength = 64; + /** + * @var bool + */ + private $rateLimit; + /** + * @var string + */ + private $rateLimitCacheKey; + /** + * @var int + */ + private $maxCallsPerMinute = 10; + /** + * @var string|null + */ + private $defaultAlg; + + public function __construct( + string $jwksUri, + ClientInterface $httpClient, + RequestFactoryInterface $httpFactory, + CacheItemPoolInterface $cache, + int $expiresAfter = null, + bool $rateLimit = false, + string $defaultAlg = null + ) { + $this->jwksUri = $jwksUri; + $this->httpClient = $httpClient; + $this->httpFactory = $httpFactory; + $this->cache = $cache; + $this->expiresAfter = $expiresAfter; + $this->rateLimit = $rateLimit; + $this->defaultAlg = $defaultAlg; + $this->setCacheKeys(); + } + + /** + * @param string $keyId + * @return Key + */ + public function offsetGet($keyId): Key + { + if (!$this->keyIdExists($keyId)) { + throw new OutOfBoundsException('Key ID not found'); + } + return $this->keySet[$keyId]; + } + + /** + * @param string $keyId + * @return bool + */ + public function offsetExists($keyId): bool + { + return $this->keyIdExists($keyId); + } + + /** + * @param string $offset + * @param Key $value + */ + public function offsetSet($offset, $value): void + { + throw new LogicException('Method not implemented'); + } + + /** + * @param string $offset + */ + public function offsetUnset($offset): void + { + throw new LogicException('Method not implemented'); + } + + private function keyIdExists(string $keyId): bool + { + if (null === $this->keySet) { + $item = $this->getCacheItem(); + // Try to load keys from cache + if ($item->isHit()) { + // item found! Return it + $jwks = $item->get(); + $this->keySet = JWK::parseKeySet(json_decode($jwks, true), $this->defaultAlg); + } + } + + if (!isset($this->keySet[$keyId])) { + if ($this->rateLimitExceeded()) { + return false; + } + $request = $this->httpFactory->createRequest('get', $this->jwksUri); + $jwksResponse = $this->httpClient->sendRequest($request); + $jwks = (string) $jwksResponse->getBody(); + $this->keySet = JWK::parseKeySet(json_decode($jwks, true), $this->defaultAlg); + + if (!isset($this->keySet[$keyId])) { + return false; + } + + $item = $this->getCacheItem(); + $item->set($jwks); + if ($this->expiresAfter) { + $item->expiresAfter($this->expiresAfter); + } + $this->cache->save($item); + } + + return true; + } + + private function rateLimitExceeded(): bool + { + if (!$this->rateLimit) { + return false; + } + + $cacheItem = $this->cache->getItem($this->rateLimitCacheKey); + if (!$cacheItem->isHit()) { + $cacheItem->expiresAfter(1); // # of calls are cached each minute + } + + $callsPerMinute = (int) $cacheItem->get(); + if (++$callsPerMinute > $this->maxCallsPerMinute) { + return true; + } + $cacheItem->set($callsPerMinute); + $this->cache->save($cacheItem); + return false; + } + + private function getCacheItem(): CacheItemInterface + { + if (\is_null($this->cacheItem)) { + $this->cacheItem = $this->cache->getItem($this->cacheKey); + } + + return $this->cacheItem; + } + + private function setCacheKeys(): void + { + if (empty($this->jwksUri)) { + throw new RuntimeException('JWKS URI is empty'); + } + + // ensure we do not have illegal characters + $key = preg_replace('|[^a-zA-Z0-9_\.!]|', '', $this->jwksUri); + + // add prefix + $key = $this->cacheKeyPrefix . $key; + + // Hash keys if they exceed $maxKeyLength of 64 + if (\strlen($key) > $this->maxKeyLength) { + $key = substr(hash('sha256', $key), 0, $this->maxKeyLength); + } + + $this->cacheKey = $key; + + if ($this->rateLimit) { + // add prefix + $rateLimitKey = $this->cacheKeyPrefix . 'ratelimit' . $key; + + // Hash keys if they exceed $maxKeyLength of 64 + if (\strlen($rateLimitKey) > $this->maxKeyLength) { + $rateLimitKey = substr(hash('sha256', $rateLimitKey), 0, $this->maxKeyLength); + } + + $this->rateLimitCacheKey = $rateLimitKey; + } + } +} diff --git a/lib/firebase/php-jwt/src/ExpiredException.php b/lib/firebase/php-jwt/src/ExpiredException.php new file mode 100644 index 0000000000..81ba52d43f --- /dev/null +++ b/lib/firebase/php-jwt/src/ExpiredException.php @@ -0,0 +1,7 @@ + + * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD + * @link https://github.com/firebase/php-jwt + */ +class JWK +{ + private const OID = '1.2.840.10045.2.1'; + private const ASN1_OBJECT_IDENTIFIER = 0x06; + private const ASN1_SEQUENCE = 0x10; // also defined in JWT + private const ASN1_BIT_STRING = 0x03; + private const EC_CURVES = [ + 'P-256' => '1.2.840.10045.3.1.7', // Len: 64 + // 'P-384' => '1.3.132.0.34', // Len: 96 (not yet supported) + // 'P-521' => '1.3.132.0.35', // Len: 132 (not supported) + ]; + + /** + * Parse a set of JWK keys + * + * @param array $jwks The JSON Web Key Set as an associative array + * @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the + * JSON Web Key Set + * + * @return array An associative array of key IDs (kid) to Key objects + * + * @throws InvalidArgumentException Provided JWK Set is empty + * @throws UnexpectedValueException Provided JWK Set was invalid + * @throws DomainException OpenSSL failure + * + * @uses parseKey + */ + public static function parseKeySet(array $jwks, string $defaultAlg = null): array + { + $keys = []; + + if (!isset($jwks['keys'])) { + throw new UnexpectedValueException('"keys" member must exist in the JWK Set'); + } + + if (empty($jwks['keys'])) { + throw new InvalidArgumentException('JWK Set did not contain any keys'); + } + + foreach ($jwks['keys'] as $k => $v) { + $kid = isset($v['kid']) ? $v['kid'] : $k; + if ($key = self::parseKey($v, $defaultAlg)) { + $keys[(string) $kid] = $key; + } + } + + if (0 === \count($keys)) { + throw new UnexpectedValueException('No supported algorithms found in JWK Set'); + } + + return $keys; + } + + /** + * Parse a JWK key + * + * @param array $jwk An individual JWK + * @param string $defaultAlg The algorithm for the Key object if "alg" is not set in the + * JSON Web Key Set + * + * @return Key The key object for the JWK + * + * @throws InvalidArgumentException Provided JWK is empty + * @throws UnexpectedValueException Provided JWK was invalid + * @throws DomainException OpenSSL failure + * + * @uses createPemFromModulusAndExponent + */ + public static function parseKey(array $jwk, string $defaultAlg = null): ?Key + { + if (empty($jwk)) { + throw new InvalidArgumentException('JWK must not be empty'); + } + + if (!isset($jwk['kty'])) { + throw new UnexpectedValueException('JWK must contain a "kty" parameter'); + } + + if (!isset($jwk['alg'])) { + if (\is_null($defaultAlg)) { + // The "alg" parameter is optional in a KTY, but an algorithm is required + // for parsing in this library. Use the $defaultAlg parameter when parsing the + // key set in order to prevent this error. + // @see https://datatracker.ietf.org/doc/html/rfc7517#section-4.4 + throw new UnexpectedValueException('JWK must contain an "alg" parameter'); + } + $jwk['alg'] = $defaultAlg; + } + + switch ($jwk['kty']) { + case 'RSA': + if (!empty($jwk['d'])) { + throw new UnexpectedValueException('RSA private keys are not supported'); + } + if (!isset($jwk['n']) || !isset($jwk['e'])) { + throw new UnexpectedValueException('RSA keys must contain values for both "n" and "e"'); + } + + $pem = self::createPemFromModulusAndExponent($jwk['n'], $jwk['e']); + $publicKey = \openssl_pkey_get_public($pem); + if (false === $publicKey) { + throw new DomainException( + 'OpenSSL error: ' . \openssl_error_string() + ); + } + return new Key($publicKey, $jwk['alg']); + case 'EC': + if (isset($jwk['d'])) { + // The key is actually a private key + throw new UnexpectedValueException('Key data must be for a public key'); + } + + if (empty($jwk['crv'])) { + throw new UnexpectedValueException('crv not set'); + } + + if (!isset(self::EC_CURVES[$jwk['crv']])) { + throw new DomainException('Unrecognised or unsupported EC curve'); + } + + if (empty($jwk['x']) || empty($jwk['y'])) { + throw new UnexpectedValueException('x and y not set'); + } + + $publicKey = self::createPemFromCrvAndXYCoordinates($jwk['crv'], $jwk['x'], $jwk['y']); + return new Key($publicKey, $jwk['alg']); + default: + // Currently only RSA is supported + break; + } + + return null; + } + + /** + * Converts the EC JWK values to pem format. + * + * @param string $crv The EC curve (only P-256 is supported) + * @param string $x The EC x-coordinate + * @param string $y The EC y-coordinate + * + * @return string + */ + private static function createPemFromCrvAndXYCoordinates(string $crv, string $x, string $y): string + { + $pem = + self::encodeDER( + self::ASN1_SEQUENCE, + self::encodeDER( + self::ASN1_SEQUENCE, + self::encodeDER( + self::ASN1_OBJECT_IDENTIFIER, + self::encodeOID(self::OID) + ) + . self::encodeDER( + self::ASN1_OBJECT_IDENTIFIER, + self::encodeOID(self::EC_CURVES[$crv]) + ) + ) . + self::encodeDER( + self::ASN1_BIT_STRING, + \chr(0x00) . \chr(0x04) + . JWT::urlsafeB64Decode($x) + . JWT::urlsafeB64Decode($y) + ) + ); + + return sprintf( + "-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n", + wordwrap(base64_encode($pem), 64, "\n", true) + ); + } + + /** + * Create a public key represented in PEM format from RSA modulus and exponent information + * + * @param string $n The RSA modulus encoded in Base64 + * @param string $e The RSA exponent encoded in Base64 + * + * @return string The RSA public key represented in PEM format + * + * @uses encodeLength + */ + private static function createPemFromModulusAndExponent( + string $n, + string $e + ): string { + $mod = JWT::urlsafeB64Decode($n); + $exp = JWT::urlsafeB64Decode($e); + + $modulus = \pack('Ca*a*', 2, self::encodeLength(\strlen($mod)), $mod); + $publicExponent = \pack('Ca*a*', 2, self::encodeLength(\strlen($exp)), $exp); + + $rsaPublicKey = \pack( + 'Ca*a*a*', + 48, + self::encodeLength(\strlen($modulus) + \strlen($publicExponent)), + $modulus, + $publicExponent + ); + + // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption. + $rsaOID = \pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA + $rsaPublicKey = \chr(0) . $rsaPublicKey; + $rsaPublicKey = \chr(3) . self::encodeLength(\strlen($rsaPublicKey)) . $rsaPublicKey; + + $rsaPublicKey = \pack( + 'Ca*a*', + 48, + self::encodeLength(\strlen($rsaOID . $rsaPublicKey)), + $rsaOID . $rsaPublicKey + ); + + return "-----BEGIN PUBLIC KEY-----\r\n" . + \chunk_split(\base64_encode($rsaPublicKey), 64) . + '-----END PUBLIC KEY-----'; + } + + /** + * 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. + * + * @param int $length + * @return string + */ + private static function encodeLength(int $length): string + { + if ($length <= 0x7F) { + return \chr($length); + } + + $temp = \ltrim(\pack('N', $length), \chr(0)); + + return \pack('Ca*', 0x80 | \strlen($temp), $temp); + } + + /** + * Encodes a value into a DER object. + * Also defined in Firebase\JWT\JWT + * + * @param int $type DER tag + * @param string $value the value to encode + * @return string the encoded object + */ + private static function encodeDER(int $type, string $value): string + { + $tag_header = 0; + if ($type === self::ASN1_SEQUENCE) { + $tag_header |= 0x20; + } + + // Type + $der = \chr($tag_header | $type); + + // Length + $der .= \chr(\strlen($value)); + + return $der . $value; + } + + /** + * Encodes a string into a DER-encoded OID. + * + * @param string $oid the OID string + * @return string the binary DER-encoded OID + */ + private static function encodeOID(string $oid): string + { + $octets = explode('.', $oid); + + // Get the first octet + $first = (int) array_shift($octets); + $second = (int) array_shift($octets); + $oid = \chr($first * 40 + $second); + + // Iterate over subsequent octets + foreach ($octets as $octet) { + if ($octet == 0) { + $oid .= \chr(0x00); + continue; + } + $bin = ''; + + while ($octet) { + $bin .= \chr(0x80 | ($octet & 0x7f)); + $octet >>= 7; + } + $bin[0] = $bin[0] & \chr(0x7f); + + // Convert to big endian if necessary + if (pack('V', 65534) == pack('L', 65534)) { + $oid .= strrev($bin); + } else { + $oid .= $bin; + } + } + + return $oid; + } +} diff --git a/lib/firebase/php-jwt/src/JWT.php b/lib/firebase/php-jwt/src/JWT.php new file mode 100644 index 0000000000..9964073d5e --- /dev/null +++ b/lib/firebase/php-jwt/src/JWT.php @@ -0,0 +1,627 @@ + + * @author Anant Narayanan + * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD + * @link https://github.com/firebase/php-jwt + */ +class JWT +{ + private const ASN1_INTEGER = 0x02; + private const ASN1_SEQUENCE = 0x10; + private const ASN1_BIT_STRING = 0x03; + + /** + * When checking nbf, iat or expiration times, + * we want to provide some extra leeway time to + * account for clock skew. + * + * @var int + */ + public static $leeway = 0; + + /** + * Allow the current timestamp to be specified. + * Useful for fixing a value within unit testing. + * Will default to PHP time() value if null. + * + * @var ?int + */ + public static $timestamp = null; + + /** + * @var array + */ + public static $supported_algs = [ + 'ES384' => ['openssl', 'SHA384'], + 'ES256' => ['openssl', 'SHA256'], + 'HS256' => ['hash_hmac', 'SHA256'], + 'HS384' => ['hash_hmac', 'SHA384'], + 'HS512' => ['hash_hmac', 'SHA512'], + 'RS256' => ['openssl', 'SHA256'], + 'RS384' => ['openssl', 'SHA384'], + 'RS512' => ['openssl', 'SHA512'], + 'EdDSA' => ['sodium_crypto', 'EdDSA'], + ]; + + /** + * Decodes a JWT string into a PHP object. + * + * @param string $jwt The JWT + * @param Key|array $keyOrKeyArray The Key or associative array of key IDs (kid) to Key objects. + * If the algorithm used is asymmetric, this is the public key + * Each Key object contains an algorithm and matching key. + * Supported algorithms are 'ES384','ES256', 'HS256', 'HS384', + * 'HS512', 'RS256', 'RS384', and 'RS512' + * + * @return stdClass The JWT's payload as a PHP object + * + * @throws InvalidArgumentException Provided key/key-array was empty + * @throws DomainException Provided JWT is malformed + * @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 ExpiredException Provided JWT has since expired, as defined by the 'exp' claim + * + * @uses jsonDecode + * @uses urlsafeB64Decode + */ + public static function decode( + string $jwt, + $keyOrKeyArray + ): stdClass { + // Validate JWT + $timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp; + + if (empty($keyOrKeyArray)) { + throw new InvalidArgumentException('Key may not be empty'); + } + $tks = \explode('.', $jwt); + if (\count($tks) !== 3) { + throw new UnexpectedValueException('Wrong number of segments'); + } + list($headb64, $bodyb64, $cryptob64) = $tks; + $headerRaw = static::urlsafeB64Decode($headb64); + if (null === ($header = static::jsonDecode($headerRaw))) { + throw new UnexpectedValueException('Invalid header encoding'); + } + $payloadRaw = static::urlsafeB64Decode($bodyb64); + if (null === ($payload = static::jsonDecode($payloadRaw))) { + throw new UnexpectedValueException('Invalid claims encoding'); + } + if (\is_array($payload)) { + // prevent PHP Fatal Error in edge-cases when payload is empty array + $payload = (object) $payload; + } + if (!$payload instanceof stdClass) { + throw new UnexpectedValueException('Payload must be a JSON object'); + } + $sig = static::urlsafeB64Decode($cryptob64); + if (empty($header->alg)) { + throw new UnexpectedValueException('Empty algorithm'); + } + if (empty(static::$supported_algs[$header->alg])) { + throw new UnexpectedValueException('Algorithm not supported'); + } + + $key = self::getKey($keyOrKeyArray, property_exists($header, 'kid') ? $header->kid : null); + + // Check the algorithm + if (!self::constantTimeEquals($key->getAlgorithm(), $header->alg)) { + // See issue #351 + throw new UnexpectedValueException('Incorrect key for this algorithm'); + } + if ($header->alg === 'ES256' || $header->alg === 'ES384') { + // OpenSSL expects an ASN.1 DER sequence for ES256/ES384 signatures + $sig = self::signatureToDER($sig); + } + if (!self::verify("${headb64}.${bodyb64}", $sig, $key->getKeyMaterial(), $header->alg)) { + throw new SignatureInvalidException('Signature verification failed'); + } + + // 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) + ); + } + + // 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) + ); + } + + // Check if this token has expired. + if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { + throw new ExpiredException('Expired token'); + } + + return $payload; + } + + /** + * Converts and signs a PHP object or array into a JWT string. + * + * @param array $payload PHP array + * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key. + * @param string $alg Supported algorithms are 'ES384','ES256', 'HS256', 'HS384', + * 'HS512', 'RS256', 'RS384', and 'RS512' + * @param string $keyId + * @param array $head An array with header elements to attach + * + * @return string A signed JWT + * + * @uses jsonEncode + * @uses urlsafeB64Encode + */ + public static function encode( + array $payload, + $key, + string $alg, + string $keyId = null, + array $head = null + ): string { + $header = ['typ' => 'JWT', 'alg' => $alg]; + if ($keyId !== null) { + $header['kid'] = $keyId; + } + if (isset($head) && \is_array($head)) { + $header = \array_merge($head, $header); + } + $segments = []; + $segments[] = static::urlsafeB64Encode((string) static::jsonEncode($header)); + $segments[] = static::urlsafeB64Encode((string) static::jsonEncode($payload)); + $signing_input = \implode('.', $segments); + + $signature = static::sign($signing_input, $key, $alg); + $segments[] = static::urlsafeB64Encode($signature); + + return \implode('.', $segments); + } + + /** + * 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', 'HS256', 'HS384', + * 'HS512', 'RS256', 'RS384', and 'RS512' + * + * @return string An encrypted message + * + * @throws DomainException Unsupported algorithm or bad key was specified + */ + public static function sign( + string $msg, + $key, + string $alg + ): string { + if (empty(static::$supported_algs[$alg])) { + throw new DomainException('Algorithm not supported'); + } + list($function, $algorithm) = static::$supported_algs[$alg]; + switch ($function) { + case 'hash_hmac': + if (!\is_string($key)) { + throw new InvalidArgumentException('key must be a string when using hmac'); + } + return \hash_hmac($algorithm, $msg, $key, true); + case 'openssl': + $signature = ''; + $success = \openssl_sign($msg, $signature, $key, $algorithm); // @phpstan-ignore-line + if (!$success) { + throw new DomainException('OpenSSL unable to sign data'); + } + if ($alg === 'ES256') { + $signature = self::signatureFromDER($signature, 256); + } elseif ($alg === 'ES384') { + $signature = self::signatureFromDER($signature, 384); + } + 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)); + return sodium_crypto_sign_detached($msg, $key); + } catch (Exception $e) { + throw new DomainException($e->getMessage(), 0, $e); + } + } + + throw new DomainException('Algorithm not supported'); + } + + /** + * Verify a signature with the message, key and method. Not all methods + * are symmetric, so we must have a separate verify and sign method. + * + * @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 $alg The algorithm + * + * @return bool + * + * @throws DomainException Invalid Algorithm, bad key, or OpenSSL failure + */ + private static function verify( + string $msg, + string $signature, + $keyMaterial, + string $alg + ): bool { + if (empty(static::$supported_algs[$alg])) { + throw new DomainException('Algorithm not supported'); + } + + list($function, $algorithm) = static::$supported_algs[$alg]; + switch ($function) { + case 'openssl': + $success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm); // @phpstan-ignore-line + if ($success === 1) { + return true; + } + if ($success === 0) { + return false; + } + // returns 1 on success, 0 on failure, -1 on error. + throw new DomainException( + '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)); + return sodium_crypto_sign_verify_detached($signature, $msg, $key); + } catch (Exception $e) { + throw new DomainException($e->getMessage(), 0, $e); + } + case 'hash_hmac': + default: + if (!\is_string($keyMaterial)) { + throw new InvalidArgumentException('key must be a string when using hmac'); + } + $hash = \hash_hmac($algorithm, $msg, $keyMaterial, true); + return self::constantTimeEquals($hash, $signature); + } + } + + /** + * Decode a JSON string into a PHP object. + * + * @param string $input JSON string + * + * @return mixed The decoded JSON string + * + * @throws DomainException Provided string was invalid JSON + */ + public static function jsonDecode(string $input) + { + $obj = \json_decode($input, false, 512, JSON_BIGINT_AS_STRING); + + if ($errno = \json_last_error()) { + self::handleJsonError($errno); + } elseif ($obj === null && $input !== 'null') { + throw new DomainException('Null result with non-null input'); + } + return $obj; + } + + /** + * Encode a PHP array into a JSON string. + * + * @param array $input A PHP array + * + * @return string JSON representation of the PHP array + * + * @throws DomainException Provided object could not be encoded to valid JSON + */ + 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); + } + if ($errno = \json_last_error()) { + self::handleJsonError($errno); + } elseif ($json === 'null' && $input !== null) { + throw new DomainException('Null result with non-null input'); + } + if ($json === false) { + throw new DomainException('Provided object could not be encoded to valid JSON'); + } + return $json; + } + + /** + * Decode a string with URL-safe Base64. + * + * @param string $input A Base64 encoded string + * + * @return string A decoded string + * + * @throws InvalidArgumentException invalid base64 characters + */ + public static function urlsafeB64Decode(string $input): string + { + $remainder = \strlen($input) % 4; + if ($remainder) { + $padlen = 4 - $remainder; + $input .= \str_repeat('=', $padlen); + } + return \base64_decode(\strtr($input, '-_', '+/')); + } + + /** + * Encode a string with URL-safe Base64. + * + * @param string $input The string you want encoded + * + * @return string The base64 encode of what you passed in + */ + public static function urlsafeB64Encode(string $input): string + { + return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_')); + } + + + /** + * Determine if an algorithm has been provided for each Key + * + * @param Key|ArrayAccess|array $keyOrKeyArray + * @param string|null $kid + * + * @throws UnexpectedValueException + * + * @return Key + */ + private static function getKey( + $keyOrKeyArray, + ?string $kid + ): Key { + if ($keyOrKeyArray instanceof Key) { + return $keyOrKeyArray; + } + + if ($keyOrKeyArray instanceof CachedKeySet) { + // Skip "isset" check, as this will automatically refresh if not set + return $keyOrKeyArray[$kid]; + } + + if (empty($kid)) { + throw new UnexpectedValueException('"kid" empty, unable to lookup correct key'); + } + if (!isset($keyOrKeyArray[$kid])) { + throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key'); + } + + return $keyOrKeyArray[$kid]; + } + + /** + * @param string $left The string of known length to compare against + * @param string $right The user-supplied string + * @return bool + */ + public static function constantTimeEquals(string $left, string $right): bool + { + if (\function_exists('hash_equals')) { + return \hash_equals($left, $right); + } + $len = \min(self::safeStrlen($left), self::safeStrlen($right)); + + $status = 0; + for ($i = 0; $i < $len; $i++) { + $status |= (\ord($left[$i]) ^ \ord($right[$i])); + } + $status |= (self::safeStrlen($left) ^ self::safeStrlen($right)); + + return ($status === 0); + } + + /** + * Helper method to create a JSON error. + * + * @param int $errno An error number from json_last_error() + * + * @throws DomainException + * + * @return void + */ + private static function handleJsonError(int $errno): void + { + $messages = [ + JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON', + JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', + JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3 + ]; + throw new DomainException( + isset($messages[$errno]) + ? $messages[$errno] + : 'Unknown JSON error: ' . $errno + ); + } + + /** + * Get the number of bytes in cryptographic strings. + * + * @param string $str + * + * @return int + */ + private static function safeStrlen(string $str): int + { + if (\function_exists('mb_strlen')) { + return \mb_strlen($str, '8bit'); + } + return \strlen($str); + } + + /** + * Convert an ECDSA signature to an ASN.1 DER sequence + * + * @param string $sig The ECDSA signature to convert + * @return string The encoded DER object + */ + private static function signatureToDER(string $sig): string + { + // Separate the signature into r-value and s-value + $length = max(1, (int) (\strlen($sig) / 2)); + list($r, $s) = \str_split($sig, $length); + + // Trim leading zeros + $r = \ltrim($r, "\x00"); + $s = \ltrim($s, "\x00"); + + // Convert r-value and s-value from unsigned big-endian integers to + // signed two's complement + if (\ord($r[0]) > 0x7f) { + $r = "\x00" . $r; + } + if (\ord($s[0]) > 0x7f) { + $s = "\x00" . $s; + } + + return self::encodeDER( + self::ASN1_SEQUENCE, + self::encodeDER(self::ASN1_INTEGER, $r) . + self::encodeDER(self::ASN1_INTEGER, $s) + ); + } + + /** + * Encodes a value into a DER object. + * + * @param int $type DER tag + * @param string $value the value to encode + * + * @return string the encoded object + */ + private static function encodeDER(int $type, string $value): string + { + $tag_header = 0; + if ($type === self::ASN1_SEQUENCE) { + $tag_header |= 0x20; + } + + // Type + $der = \chr($tag_header | $type); + + // Length + $der .= \chr(\strlen($value)); + + return $der . $value; + } + + /** + * Encodes signature from a DER object. + * + * @param string $der binary signature in DER format + * @param int $keySize the number of bits in the key + * + * @return string the signature + */ + private static function signatureFromDER(string $der, int $keySize): string + { + // OpenSSL returns the ECDSA signatures as a binary ASN.1 DER SEQUENCE + list($offset, $_) = self::readDER($der); + list($offset, $r) = self::readDER($der, $offset); + list($offset, $s) = self::readDER($der, $offset); + + // Convert r-value and s-value from signed two's compliment to unsigned + // big-endian integers + $r = \ltrim($r, "\x00"); + $s = \ltrim($s, "\x00"); + + // Pad out r and s so that they are $keySize bits long + $r = \str_pad($r, $keySize / 8, "\x00", STR_PAD_LEFT); + $s = \str_pad($s, $keySize / 8, "\x00", STR_PAD_LEFT); + + return $r . $s; + } + + /** + * Reads binary DER-encoded data and decodes into a single object + * + * @param string $der the binary data in DER format + * @param int $offset the offset of the data stream containing the object + * to decode + * + * @return array{int, string|null} the new offset and the decoded object + */ + private static function readDER(string $der, int $offset = 0): array + { + $pos = $offset; + $size = \strlen($der); + $constructed = (\ord($der[$pos]) >> 5) & 0x01; + $type = \ord($der[$pos++]) & 0x1f; + + // Length + $len = \ord($der[$pos++]); + if ($len & 0x80) { + $n = $len & 0x1f; + $len = 0; + while ($n-- && $pos < $size) { + $len = ($len << 8) | \ord($der[$pos++]); + } + } + + // Value + if ($type === self::ASN1_BIT_STRING) { + $pos++; // Skip the first contents octet (padding indicator) + $data = \substr($der, $pos, $len - 1); + $pos += $len - 1; + } elseif (!$constructed) { + $data = \substr($der, $pos, $len); + $pos += $len; + } else { + $data = null; + } + + return [$pos, $data]; + } +} diff --git a/lib/firebase/php-jwt/src/Key.php b/lib/firebase/php-jwt/src/Key.php new file mode 100644 index 0000000000..00cf7f2edf --- /dev/null +++ b/lib/firebase/php-jwt/src/Key.php @@ -0,0 +1,64 @@ +keyMaterial = $keyMaterial; + $this->algorithm = $algorithm; + } + + /** + * Return the algorithm valid for this key + * + * @return string + */ + public function getAlgorithm(): string + { + return $this->algorithm; + } + + /** + * @return string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate + */ + public function getKeyMaterial() + { + return $this->keyMaterial; + } +} diff --git a/lib/firebase/php-jwt/src/SignatureInvalidException.php b/lib/firebase/php-jwt/src/SignatureInvalidException.php new file mode 100644 index 0000000000..d35dee9f18 --- /dev/null +++ b/lib/firebase/php-jwt/src/SignatureInvalidException.php @@ -0,0 +1,7 @@ += 5.5 +* Updated to use PSR-7 + * Requires immutable messages, which basically means an event based system + owned by a request instance is no longer possible. + * Utilizing the [Guzzle PSR-7 package](https://github.com/guzzle/psr7). + * Removed the dependency on `guzzlehttp/streams`. These stream abstractions + are available in the `guzzlehttp/psr7` package under the `GuzzleHttp\Psr7` + namespace. +* Added middleware and handler system + * Replaced the Guzzle event and subscriber system with a middleware system. + * No longer depends on RingPHP, but rather places the HTTP handlers directly + in Guzzle, operating on PSR-7 messages. + * Retry logic is now encapsulated in `GuzzleHttp\Middleware::retry`, which + means the `guzzlehttp/retry-subscriber` is now obsolete. + * Mocking responses is now handled using `GuzzleHttp\Handler\MockHandler`. +* Asynchronous responses + * No longer supports the `future` request option to send an async request. + Instead, use one of the `*Async` methods of a client (e.g., `requestAsync`, + `getAsync`, etc.). + * Utilizing `GuzzleHttp\Promise` instead of React's promise library to avoid + recursion required by chaining and forwarding react promises. See + https://github.com/guzzle/promises + * Added `requestAsync` and `sendAsync` to send request asynchronously. + * Added magic methods for `getAsync()`, `postAsync()`, etc. to send requests + asynchronously. +* Request options + * POST and form updates + * Added the `form_fields` and `form_files` request options. + * Removed the `GuzzleHttp\Post` namespace. + * The `body` request option no longer accepts an array for POST requests. + * The `exceptions` request option has been deprecated in favor of the + `http_errors` request options. + * The `save_to` request option has been deprecated in favor of `sink` request + option. +* Clients no longer accept an array of URI template string and variables for + URI variables. You will need to expand URI templates before passing them + into a client constructor or request method. +* Client methods `get()`, `post()`, `put()`, `patch()`, `options()`, etc. are + now magic methods that will send synchronous requests. +* Replaced `Utils.php` with plain functions in `functions.php`. +* Removed `GuzzleHttp\Collection`. +* Removed `GuzzleHttp\BatchResults`. Batched pool results are now returned as + an array. +* Removed `GuzzleHttp\Query`. Query string handling is now handled using an + associative array passed into the `query` request option. The query string + is serialized using PHP's `http_build_query`. If you need more control, you + can pass the query string in as a string. +* `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 + on the body more generic and not specific to `PostBodyInterface`. +* Reduced the number of stack frames needed to send requests. +* Nested futures are now resolved in the client rather than the RequestFsm +* Finishing state transitions is now handled in the RequestFsm rather than the + 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. +* The size of a Pool can now be dynamically adjusted using a callback. + See https://github.com/guzzle/guzzle/pull/943. +* Setting a request option to `null` when creating a request with a client will + ensure that the option is not set. This allows you to overwrite default + request options on a per-request basis. + See https://github.com/guzzle/guzzle/pull/937. +* Added the ability to limit which protocols are allowed for redirects by + specifying a `protocols` array in the `allow_redirects` request option. +* Nested futures due to retries are now resolved when waiting for synchronous + responses. See https://github.com/guzzle/guzzle/pull/947. +* `"0"` is now an allowed URI path. See + https://github.com/guzzle/guzzle/pull/935. +* `Query` no longer typehints on the `$query` argument in the constructor, + allowing for strings and arrays. +* 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 +by default where the value represents an un-encoded value to send over the +wire. A Query object then encodes the value before sending over the wire. This +means that even value query string values (e.g., ":") are url encoded. This +makes the Query class match PHP's http_build_query function. However, if you +want to send requests over the wire using valid query string characters that do +not need to be encoded, then you can provide a string to Url::setQuery() and +pass true as the second argument to specify that the query string is a raw +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 + https://github.com/guzzle/guzzle/pull/871 +* Added a `GuzzleHttp\Pool::send()` convenience method to match the docs. +* Status codes are now returned as integers. See + https://github.com/guzzle/guzzle/issues/881 +* No longer overwriting an existing `application/x-www-form-urlencoded` header + when sending POST requests, allowing for customized headers. See + https://github.com/guzzle/guzzle/issues/877 +* Improved path URL serialization. + + * No longer double percent-encoding characters in the path or query string if + they are already encoded. + * Now properly encoding the supplied path to a URL object, instead of only + encoding ' ' and '?'. + * 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 + +## 5.0.1 - 2014-10-16 + +Bugfix release. + +* Fixed an issue where connection errors still returned response object in + error and end events event though the response is unusable. This has been + corrected so that a response is not returned in the `getResponse` method of + these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867 +* 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. + +### New Features + +* Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`. +* Added a public API for creating a default HTTP adapter. +* Updated the redirect plugin to be non-blocking so that redirects are sent + concurrently. Other plugins like this can now be updated to be non-blocking. +* Added a "progress" event so that you can get upload and download progress + events. +* Added `GuzzleHttp\Pool` which implements FutureInterface and transfers + requests concurrently using a capped pool size as efficiently as possible. +* Added `hasListeners()` to EmitterInterface. +* Removed `GuzzleHttp\ClientInterface::sendAll` and marked + `GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the + recommended way). + +### Breaking changes + +The breaking changes in this release are relatively minor. The biggest thing to +look out for is that request and response objects no longer implement fluent +interfaces. + +* Removed the fluent interfaces (i.e., `return $this`) from requests, + responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`, + `GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and + `GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of + why I did this: https://ocramius.github.io/blog/fluent-interfaces-are-evil/. + This also makes the Guzzle message interfaces compatible with the current + PSR-7 message proposal. +* Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except + for the HTTP request functions from function.php, these functions are now + implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode` + moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to + `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to + `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be + `GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php + caused problems for many users: they aren't PSR-4 compliant, require an + explicit include, and needed an if-guard to ensure that the functions are not + declared multiple times. +* Rewrote adapter layer. + * Removing all classes from `GuzzleHttp\Adapter`, these are now + implemented as callables that are stored in `GuzzleHttp\Ring\Client`. + * Removed the concept of "parallel adapters". Sending requests serially or + concurrently is now handled using a single adapter. + * Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The + Transaction object now exposes the request, response, and client as public + properties. The getters and setters have been removed. +* Removed the "headers" event. This event was only useful for changing the + body a response once the headers of the response were known. You can implement + a similar behavior in a number of ways. One example might be to use a + FnStream that has access to the transaction being sent. For example, when the + first byte is written, you could check if the response headers match your + expectations, and if so, change the actual stream body that is being + written to. +* Removed the `asArray` parameter from + `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header + value as an array, then use the newly added `getHeaderAsArray()` method of + `MessageInterface`. This change makes the Guzzle interfaces compatible with + the PSR-7 interfaces. +* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add + custom request options using double-dispatch (this was an implementation + detail). Instead, you should now provide an associative array to the + constructor which is a mapping of the request option name mapping to a + function that applies the option value to a request. +* Removed the concept of "throwImmediately" from exceptions and error events. + This control mechanism was used to stop a transfer of concurrent requests + from completing. This can now be handled by throwing the exception or by + cancelling a pool of requests or each outstanding future request individually. +* Updated to "GuzzleHttp\Streams" 3.0. + * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a + `maxLen` parameter. This update makes the Guzzle streams project + compatible with the current PSR-7 proposal. + * `GuzzleHttp\Stream\Stream::__construct`, + `GuzzleHttp\Stream\Stream::factory`, and + `GuzzleHttp\Stream\Utils::create` no longer accept a size in the second + 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. +* No longer using `request_fulluri` in stream adapter proxies. +* Relative redirects are now based on the last response, not the first response. + +## 4.2.1 - 2014-08-19 + +* Ensuring that the StreamAdapter does not always add a Content-Type header +* Adding automated github releases with a phar and zip + +## 4.2.0 - 2014-08-17 + +* Now merging in default options using a case-insensitive comparison. + Closes https://github.com/guzzle/guzzle/issues/767 +* Added the ability to automatically decode `Content-Encoding` response bodies + using the `decode_content` request option. This is set to `true` by default + to decode the response body if it comes over the wire with a + `Content-Encoding`. Set this value to `false` to disable decoding the + response content, and pass a string to provide a request `Accept-Encoding` + header and turn on automatic response decoding. This feature now allows you + to pass an `Accept-Encoding` header in the headers of a request but still + disable automatic response decoding. + Closes https://github.com/guzzle/guzzle/issues/764 +* Added the ability to throw an exception immediately when transferring + requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760 +* Updating guzzlehttp/streams dependency to ~2.1 +* No longer utilizing the now deprecated namespaced methods from the stream + package. + +## 4.1.8 - 2014-08-14 + +* Fixed an issue in the CurlFactory that caused setting the `stream=false` + request option to throw an exception. + See: https://github.com/guzzle/guzzle/issues/769 +* TransactionIterator now calls rewind on the inner iterator. + See: https://github.com/guzzle/guzzle/pull/765 +* You can now set the `Content-Type` header to `multipart/form-data` + when creating POST requests to force multipart bodies. + See https://github.com/guzzle/guzzle/issues/768 + +## 4.1.7 - 2014-08-07 + +* Fixed an error in the HistoryPlugin that caused the same request and response + to be logged multiple times when an HTTP protocol error occurs. +* Ensuring that cURL does not add a default Content-Type when no Content-Type + has been supplied by the user. This prevents the adapter layer from modifying + the request that is sent over the wire after any listeners may have already + put the request in a desired state (e.g., signed the request). +* Throwing an exception when you attempt to send requests that have the + "stream" set to true in parallel using the MultiAdapter. +* Only calling curl_multi_select when there are active cURL handles. This was + previously changed and caused performance problems on some systems due to PHP + always selecting until the maximum select timeout. +* Fixed a bug where multipart/form-data POST fields were not correctly + aggregated (e.g., values with "&"). + +## 4.1.6 - 2014-08-03 + +* Added helper methods to make it easier to represent messages as strings, + including getting the start line and getting headers as a string. + +## 4.1.5 - 2014-08-02 + +* Automatically retrying cURL "Connection died, retrying a fresh connect" + errors when possible. +* cURL implementation cleanup +* Allowing multiple event subscriber listeners to be registered per event by + passing an array of arrays of listener configuration. + +## 4.1.4 - 2014-07-22 + +* Fixed a bug that caused multi-part POST requests with more than one field to + serialize incorrectly. +* Paths can now be set to "0" +* `ResponseInterface::xml` now accepts a `libxml_options` option and added a + missing default argument that was required when parsing XML response bodies. +* A `save_to` stream is now created lazily, which means that files are not + created on disk unless a request succeeds. + +## 4.1.3 - 2014-07-15 + +* Various fixes to multipart/form-data POST uploads +* Wrapping function.php in an if-statement to ensure Guzzle can be used + globally and in a Composer install +* Fixed an issue with generating and merging in events to an event array +* POST headers are only applied before sending a request to allow you to change + the query aggregator used before uploading +* Added much more robust query string parsing +* Fixed various parsing and normalization issues with URLs +* Fixing an issue where multi-valued headers were not being utilized correctly + in the StreamAdapter + +## 4.1.2 - 2014-06-18 + +* Added support for sending payloads with GET requests + +## 4.1.1 - 2014-06-08 + +* Fixed an issue related to using custom message factory options in subclasses +* Fixed an issue with nested form fields in a multi-part POST +* Fixed an issue with using the `json` request option for POST requests +* Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar` + +## 4.1.0 - 2014-05-27 + +* Added a `json` request option to easily serialize JSON payloads. +* Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON. +* Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`. +* Added the ability to provide an emitter to a client in the client constructor. +* Added the ability to persist a cookie session using $_SESSION. +* Added a trait that can be used to add event listeners to an iterator. +* Removed request method constants from RequestInterface. +* Fixed warning when invalid request start-lines are received. +* Updated MessageFactory to work with custom request option methods. +* Updated cacert bundle to latest build. + +4.0.2 (2014-04-16) +------------------ + +* Proxy requests using the StreamAdapter now properly use request_fulluri (#632) +* Added the ability to set scalars as POST fields (#628) + +## 4.0.1 - 2014-04-04 + +* The HTTP status code of a response is now set as the exception code of + RequestException objects. +* 303 redirects will now correctly switch from POST to GET requests. +* The default parallel adapter of a client now correctly uses the MultiAdapter. +* HasDataTrait now initializes the internal data array as an empty array so + that the toArray() method always returns an array. + +## 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 +* 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()`. + You can now pass a callable or an array of associative arrays where each + associative array contains the "fn", "priority", and "once" keys. + +## 4.0.0.rc-2 - 2014-03-25 + +* Removed `getConfig()` and `setConfig()` from clients to avoid confusion + around whether things like base_url, message_factory, etc. should be able to + be retrieved or modified. +* Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface +* functions.php functions were renamed using snake_case to match PHP idioms +* Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and + `GUZZLE_CURL_SELECT_TIMEOUT` environment variables +* Added the ability to specify custom `sendAll()` event priorities +* Added the ability to specify custom stream context options to the stream + adapter. +* Added a functions.php function for `get_path()` and `set_path()` +* CurlAdapter and MultiAdapter now use a callable to generate curl resources +* MockAdapter now properly reads a body and emits a `headers` event +* Updated Url class to check if a scheme and host are set before adding ":" + and "//". This allows empty Url (e.g., "") to be serialized as "". +* Parsing invalid XML no longer emits warnings +* Curl classes now properly throw AdapterExceptions +* Various performance optimizations +* Streams are created with the faster `Stream\create()` function +* Marked deprecation_proxy() as internal +* Test server is now a collection of static methods on a class + +## 4.0.0-rc.1 - 2014-03-15 + +* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40 + +## 3.8.1 - 2014-01-28 + +* Bug: Always using GET requests when redirecting from a 303 response +* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in + `Guzzle\Http\ClientInterface::setSslVerification()` +* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL +* Bug: The body of a request can now be set to `"0"` +* Sending PHP stream requests no longer forces `HTTP/1.0` +* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of + each sub-exception +* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than + clobbering everything). +* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators) +* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`. + For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`. +* Now properly escaping the regular expression delimiter when matching Cookie domains. +* Network access is now disabled when loading XML documents + +## 3.8.0 - 2013-12-05 + +* Added the ability to define a POST name for a file +* JSON response parsing now properly walks additionalProperties +* cURL error code 18 is now retried automatically in the BackoffPlugin +* Fixed a cURL error when URLs contain fragments +* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were + CurlExceptions +* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e) +* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS` +* Fixed a bug that was encountered when parsing empty header parameters +* UriTemplate now has a `setRegex()` method to match the docs +* The `debug` request parameter now checks if it is truthy rather than if it exists +* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin +* Added the ability to combine URLs using strict RFC 3986 compliance +* Command objects can now return the validation errors encountered by the command +* Various fixes to cache revalidation (#437 and 29797e5) +* Various fixes to the AsyncPlugin +* Cleaned up build scripts + +## 3.7.4 - 2013-10-02 + +* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430) +* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp + (see https://github.com/aws/aws-sdk-php/issues/147) +* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots +* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420) +* Updated the bundled cacert.pem (#419) +* OauthPlugin now supports adding authentication to headers or query string (#425) + +## 3.7.3 - 2013-09-08 + +* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and + `CommandTransferException`. +* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description +* Schemas are only injected into response models when explicitly configured. +* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of + an EntityBody. +* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator. +* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`. +* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody() +* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin +* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests +* Bug fix: Properly parsing headers that contain commas contained in quotes +* Bug fix: mimetype guessing based on a filename is now case-insensitive + +## 3.7.2 - 2013-08-02 + +* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander + See https://github.com/guzzle/guzzle/issues/371 +* Bug fix: Cookie domains are now matched correctly according to RFC 6265 + See https://github.com/guzzle/guzzle/issues/377 +* Bug fix: GET parameters are now used when calculating an OAuth signature +* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted +* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched +* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input. + See https://github.com/guzzle/guzzle/issues/379 +* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See + https://github.com/guzzle/guzzle/pull/380 +* cURL multi cleanup and optimizations + +## 3.7.1 - 2013-07-05 + +* Bug fix: Setting default options on a client now works +* Bug fix: Setting options on HEAD requests now works. See #352 +* Bug fix: Moving stream factory before send event to before building the stream. See #353 +* Bug fix: Cookies no longer match on IP addresses per RFC 6265 +* Bug fix: Correctly parsing header parameters that are in `<>` and quotes +* Added `cert` and `ssl_key` as request options +* `Host` header can now diverge from the host part of a URL if the header is set manually +* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter +* OAuth parameters are only added via the plugin if they aren't already set +* Exceptions are now thrown when a URL cannot be parsed +* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails +* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin + +## 3.7.0 - 2013-06-10 + +* See UPGRADING.md for more information on how to upgrade. +* Requests now support the ability to specify an array of $options when creating a request to more easily modify a + request. You can pass a 'request.options' configuration setting to a client to apply default request options to + every request created by a client (e.g. default query string variables, headers, curl options, etc.). +* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`. + See `Guzzle\Http\StaticClient::mount`. +* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests + created by a command (e.g. custom headers, query string variables, timeout settings, etc.). +* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the + headers of a response +* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key + (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`) +* ServiceBuilders now support storing and retrieving arbitrary data +* CachePlugin can now purge all resources for a given URI +* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource +* CachePlugin now uses the Vary header to determine if a resource is a cache hit +* `Guzzle\Http\Message\Response` now implements `\Serializable` +* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters +* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable +* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()` +* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size +* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message +* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older + Symfony users can still use the old version of Monolog. +* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`. + Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`. +* Several performance improvements to `Guzzle\Common\Collection` +* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +* Added `Guzzle\Stream\StreamInterface::isRepeatable` +* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`. +* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`. +* Removed `Guzzle\Http\ClientInterface::expandTemplate()` +* Removed `Guzzle\Http\ClientInterface::setRequestFactory()` +* Removed `Guzzle\Http\ClientInterface::getCurlMulti()` +* Removed `Guzzle\Http\Message\RequestInterface::canCache` +* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect` +* Removed `Guzzle\Http\Message\RequestInterface::isRedirect` +* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. +* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting + `Guzzle\Common\Version::$emitWarnings` to true. +* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use + `$request->getResponseBody()->isRepeatable()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use + `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. + These will work through Guzzle 4.0 +* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params]. +* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`. +* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. +* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +* Marked `Guzzle\Common\Collection::inject()` as deprecated. +* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');` +* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +* Always setting X-cache headers on cached responses +* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +* Added `CacheStorageInterface::purge($url)` +* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +## 3.6.0 - 2013-05-29 + +* ServiceDescription now implements ToArrayInterface +* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters +* Guzzle can now correctly parse incomplete URLs +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess +* Added the ability to cast Model objects to a string to view debug information. + +## 3.5.0 - 2013-05-13 + +* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times +* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove + itself from the EventDispatcher) +* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values +* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too +* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a + non-existent key +* Bug: All __call() method arguments are now required (helps with mocking frameworks) +* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference + to help with refcount based garbage collection of resources created by sending a request +* Deprecating ZF1 cache and log adapters. These will be removed in the next major version. +* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it's deprecated). Use the + HistoryPlugin for a history. +* Added a `responseBody` alias for the `response_body` location +* Refactored internals to no longer rely on Response::getRequest() +* HistoryPlugin can now be cast to a string +* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests + and responses that are sent over the wire +* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects + +## 3.4.3 - 2013-04-30 + +* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response +* Added a check to re-extract the temp cacert bundle from the phar before sending each request + +## 3.4.2 - 2013-04-29 + +* Bug fix: Stream objects now work correctly with "a" and "a+" modes +* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present +* Bug fix: AsyncPlugin no longer forces HEAD requests +* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter +* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails +* Setting a response on a request will write to the custom request body from the response body if one is specified +* LogPlugin now writes to php://output when STDERR is undefined +* Added the ability to set multiple POST files for the same key in a single call +* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default +* Added the ability to queue CurlExceptions to the MockPlugin +* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send) +* Configuration loading now allows remote files + +## 3.4.1 - 2013-04-16 + +* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti + handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost. +* Exceptions are now properly grouped when sending requests in parallel +* Redirects are now properly aggregated when a multi transaction fails +* Redirects now set the response on the original object even in the event of a failure +* Bug fix: Model names are now properly set even when using $refs +* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax +* Added support for oauth_callback in OAuth signatures +* Added support for oauth_verifier in OAuth signatures +* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection + +## 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: 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. +* Bug fix: Added `number` type to service descriptions. +* Bug fix: empty parameters are removed from an OAuth signature +* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header +* Bug fix: Fixed "array to string" error when validating a union of types in a service description +* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream +* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin. +* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs. +* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections. +* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if + the Content-Type can be determined based on the entity body or the path of the request. +* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder. +* Added support for a PSR-3 LogAdapter. +* Added a `command.after_prepare` event +* Added `oauth_callback` parameter to the OauthPlugin +* Added the ability to create a custom stream class when using a stream factory +* Added a CachingEntityBody decorator +* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized. +* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar. +* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies +* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This + means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use + POST fields or files (the latter is only used when emulating a form POST in the browser). +* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest + +## 3.3.1 - 2013-03-10 + +* Added the ability to create PHP streaming responses from HTTP requests +* Bug fix: Running any filters when parsing response headers with service descriptions +* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing +* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across + response location visitors. +* Bug fix: Removed the possibility of creating configuration files with circular dependencies +* RequestFactory::create() now uses the key of a POST file when setting the POST file name +* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set + +## 3.3.0 - 2013-03-03 + +* A large number of performance optimizations have been made +* Bug fix: Added 'wb' as a valid write mode for streams +* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned +* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()` +* BC: Removed `Guzzle\Http\Utils` class +* BC: Setting a service description on a client will no longer modify the client's command factories. +* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using + the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' +* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to + lowercase +* Operation parameter objects are now lazy loaded internally +* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses +* Added support for instantiating responseType=class responseClass classes. Classes must implement + `Guzzle\Service\Command\ResponseClassInterface` +* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These + additional properties also support locations and can be used to parse JSON responses where the outermost part of the + JSON is an array +* Added support for nested renaming of JSON models (rename sentAs to name) +* CachePlugin + * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error + * Debug headers can now added to cached response in the CachePlugin + +## 3.2.0 - 2013-02-14 + +* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients. +* URLs with no path no longer contain a "/" by default +* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url. +* BadResponseException no longer includes the full request and response message +* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface +* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface +* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription +* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list +* xmlEncoding can now be customized for the XML declaration of a XML service description operation +* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value + aggregation and no longer uses callbacks +* The URL encoding implementation of Guzzle\Http\QueryString can now be customized +* Bug fix: Filters were not always invoked for array service description parameters +* Bug fix: Redirects now use a target response body rather than a temporary response body +* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded +* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives + +## 3.1.2 - 2013-01-27 + +* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the + response body. For example, the XmlVisitor now parses the XML response into an array in the before() method. +* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent +* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444) +* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse() +* Setting default headers on a client after setting the user-agent will not erase the user-agent setting + +## 3.1.1 - 2013-01-20 + +* Adding wildcard support to Guzzle\Common\Collection::getPath() +* Adding alias support to ServiceBuilder configs +* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface + +## 3.1.0 - 2013-01-12 + +* BC: CurlException now extends from RequestException rather than BadResponseException +* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse() +* Added getData to ServiceDescriptionInterface +* Added context array to RequestInterface::setState() +* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http +* Bug: Adding required content-type when JSON request visitor adds JSON to a command +* Bug: Fixing the serialization of a service description with custom data +* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing + an array of successful and failed responses +* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection +* Added Guzzle\Http\IoEmittingEntityBody +* Moved command filtration from validators to location visitors +* Added `extends` attributes to service description parameters +* Added getModels to ServiceDescriptionInterface + +## 3.0.7 - 2012-12-19 + +* Fixing phar detection when forcing a cacert to system if null or true +* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()` +* Cleaning up `Guzzle\Common\Collection::inject` method +* Adding a response_body location to service descriptions + +## 3.0.6 - 2012-12-09 + +* CurlMulti performance improvements +* Adding setErrorResponses() to Operation +* composer.json tweaks + +## 3.0.5 - 2012-11-18 + +* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin +* Bug: Response body can now be a string containing "0" +* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert +* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs +* Added support for XML attributes in service description responses +* DefaultRequestSerializer now supports array URI parameter values for URI template expansion +* Added better mimetype guessing to requests and post files + +## 3.0.4 - 2012-11-11 + +* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value +* Bug: Cookies can now be added that have a name, domain, or value set to "0" +* Bug: Using the system cacert bundle when using the Phar +* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures +* Enhanced cookie jar de-duplication +* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added +* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies +* Added the ability to create any sort of hash for a stream rather than just an MD5 hash + +## 3.0.3 - 2012-11-04 + +* Implementing redirects in PHP rather than cURL +* Added PECL URI template extension and using as default parser if available +* Bug: Fixed Content-Length parsing of Response factory +* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams. +* Adding ToArrayInterface throughout library +* Fixing OauthPlugin to create unique nonce values per request + +## 3.0.2 - 2012-10-25 + +* Magic methods are enabled by default on clients +* Magic methods return the result of a command +* Service clients no longer require a base_url option in the factory +* Bug: Fixed an issue with URI templates where null template variables were being expanded + +## 3.0.1 - 2012-10-22 + +* Models can now be used like regular collection objects by calling filter, map, etc. +* Models no longer require a Parameter structure or initial data in the constructor +* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator` + +## 3.0.0 - 2012-10-15 + +* Rewrote service description format to be based on Swagger + * Now based on JSON schema + * Added nested input structures and nested response models + * Support for JSON and XML input and output models + * Renamed `commands` to `operations` + * Removed dot class notation + * Removed custom types +* Broke the project into smaller top-level namespaces to be more component friendly +* Removed support for XML configs and descriptions. Use arrays or JSON files. +* Removed the Validation component and Inspector +* Moved all cookie code to Guzzle\Plugin\Cookie +* Magic methods on a Guzzle\Service\Client now return the command un-executed. +* Calling getResult() or getResponse() on a command will lazily execute the command if needed. +* Now shipping with cURL's CA certs and using it by default +* Added previousResponse() method to response objects +* No longer sending Accept and Accept-Encoding headers on every request +* Only sending an Expect header by default when a payload is greater than 1MB +* Added/moved client options: + * curl.blacklist to curl.option.blacklist + * Added ssl.certificate_authority +* Added a Guzzle\Iterator component +* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin +* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin) +* Added a more robust caching plugin +* Added setBody to response objects +* Updating LogPlugin to use a more flexible MessageFormatter +* Added a completely revamped build process +* Cleaning up Collection class and removing default values from the get method +* Fixed ZF2 cache adapters + +## 2.8.8 - 2012-10-15 + +* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did + +## 2.8.7 - 2012-09-30 + +* Bug: Fixed config file aliases for JSON includes +* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests +* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload +* Bug: Hardening request and response parsing to account for missing parts +* Bug: Fixed PEAR packaging +* Bug: Fixed Request::getInfo +* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail +* Adding the ability for the namespace Iterator factory to look in multiple directories +* Added more getters/setters/removers from service descriptions +* Added the ability to remove POST fields from OAuth signatures +* OAuth plugin now supports 2-legged OAuth + +## 2.8.6 - 2012-09-05 + +* Added the ability to modify and build service descriptions +* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command +* Added a `json` parameter location +* Now allowing dot notation for classes in the CacheAdapterFactory +* Using the union of two arrays rather than an array_merge when extending service builder services and service params +* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references + in service builder config files. +* Services defined in two different config files that include one another will by default replace the previously + defined service, but you can now create services that extend themselves and merge their settings over the previous +* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like + '_default' with a default JSON configuration file. + +## 2.8.5 - 2012-08-29 + +* Bug: Suppressed empty arrays from URI templates +* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching +* Added support for HTTP responses that do not contain a reason phrase in the start-line +* AbstractCommand commands are now invokable +* Added a way to get the data used when signing an Oauth request before a request is sent + +## 2.8.4 - 2012-08-15 + +* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin +* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable. +* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream +* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream +* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5()) +* Added additional response status codes +* Removed SSL information from the default User-Agent header +* DELETE requests can now send an entity body +* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries +* Added the ability of the MockPlugin to consume mocked request bodies +* LogPlugin now exposes request and response objects in the extras array + +## 2.8.3 - 2012-07-30 + +* Bug: Fixed a case where empty POST requests were sent as GET requests +* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body +* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new +* Added multiple inheritance to service description commands +* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()` +* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything +* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles + +## 2.8.2 - 2012-07-24 + +* Bug: Query string values set to 0 are no longer dropped from the query string +* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()` +* Bug: `+` is now treated as an encoded space when parsing query strings +* QueryString and Collection performance improvements +* Allowing dot notation for class paths in filters attribute of a service descriptions + +## 2.8.1 - 2012-07-16 + +* Loosening Event Dispatcher dependency +* POST redirects can now be customized using CURLOPT_POSTREDIR + +## 2.8.0 - 2012-07-15 + +* BC: Guzzle\Http\Query + * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl) + * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding() + * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool) + * Changed the aggregation functions of QueryString to be static methods + * Can now use fromString() with querystrings that have a leading ? +* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters +* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body +* Cookies are no longer URL decoded by default +* Bug: URI template variables set to null are no longer expanded + +## 2.7.2 - 2012-07-02 + +* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser. +* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty() +* CachePlugin now allows for a custom request parameter function to check if a request can be cached +* Bug fix: CachePlugin now only caches GET and HEAD requests by default +* Bug fix: Using header glue when transferring headers over the wire +* Allowing deeply nested arrays for composite variables in URI templates +* Batch divisors can now return iterators or arrays + +## 2.7.1 - 2012-06-26 + +* Minor patch to update version number in UA string +* Updating build process + +## 2.7.0 - 2012-06-25 + +* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes. +* BC: Removed magic setX methods from commands +* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method +* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable. +* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity) +* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace +* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin +* Added the ability to set POST fields and files in a service description +* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method +* Adding a command.before_prepare event to clients +* Added BatchClosureTransfer and BatchClosureDivisor +* BatchTransferException now includes references to the batch divisor and transfer strategies +* Fixed some tests so that they pass more reliably +* Added Guzzle\Common\Log\ArrayLogAdapter + +## 2.6.6 - 2012-06-10 + +* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin +* BC: Removing Guzzle\Service\Command\CommandSet +* Adding generic batching system (replaces the batch queue plugin and command set) +* Updating ZF cache and log adapters and now using ZF's composer repository +* Bug: Setting the name of each ApiParam when creating through an ApiCommand +* Adding result_type, result_doc, deprecated, and doc_url to service descriptions +* Bug: Changed the default cookie header casing back to 'Cookie' + +## 2.6.5 - 2012-06-03 + +* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource() +* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from +* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data +* BC: Renaming methods in the CookieJarInterface +* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations +* Making the default glue for HTTP headers ';' instead of ',' +* Adding a removeValue to Guzzle\Http\Message\Header +* Adding getCookies() to request interface. +* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber() + +## 2.6.4 - 2012-05-30 + +* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class. +* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand +* Bug: Fixing magic method command calls on clients +* Bug: Email constraint only validates strings +* Bug: Aggregate POST fields when POST files are present in curl handle +* Bug: Fixing default User-Agent header +* Bug: Only appending or prepending parameters in commands if they are specified +* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes +* Allowing the use of dot notation for class namespaces when using instance_of constraint +* Added any_match validation constraint +* Added an AsyncPlugin +* Passing request object to the calculateWait method of the ExponentialBackoffPlugin +* Allowing the result of a command object to be changed +* Parsing location and type sub values when instantiating a service description rather than over and over at runtime + +## 2.6.3 - 2012-05-23 + +* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options. +* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields. +* You can now use an array of data when creating PUT request bodies in the request factory. +* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable. +* [Http] Adding support for Content-Type in multipart POST uploads per upload +* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1]) +* Adding more POST data operations for easier manipulation of POST data. +* You can now set empty POST fields. +* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files. +* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate. +* CS updates + +## 2.6.2 - 2012-05-19 + +* [Http] Better handling of nested scope requests in CurlMulti. Requests are now always prepares in the send() method rather than the addRequest() method. + +## 2.6.1 - 2012-05-19 + +* [BC] Removing 'path' support in service descriptions. Use 'uri'. +* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache. +* [BC] Removing Guzzle\Common\NullObject. Use https://github.com/mtdowling/NullObject if you need it. +* [BC] Removing Guzzle\Common\XmlElement. +* All commands, both dynamic and concrete, have ApiCommand objects. +* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits. +* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored. +* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible. + +## 2.6.0 - 2012-05-15 + +* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder +* [BC] Executing a Command returns the result of the command rather than the command +* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed. +* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args. +* [BC] Moving ResourceIterator* to Guzzle\Service\Resource +* [BC] Completely refactored ResourceIterators to iterate over a cloned command object +* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate +* [BC] Guzzle\Guzzle is now deprecated +* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject +* Adding Guzzle\Version class to give version information about Guzzle +* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate() +* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data +* ServiceDescription and ServiceBuilder are now cacheable using similar configs +* Changing the format of XML and JSON service builder configs. Backwards compatible. +* Cleaned up Cookie parsing +* Trimming the default Guzzle User-Agent header +* Adding a setOnComplete() method to Commands that is called when a command completes +* Keeping track of requests that were mocked in the MockPlugin +* Fixed a caching bug in the CacheAdapterFactory +* Inspector objects can be injected into a Command object +* Refactoring a lot of code and tests to be case insensitive when dealing with headers +* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL +* Adding the ability to set global option overrides to service builder configs +* Adding the ability to include other service builder config files from within XML and JSON files +* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method. + +## 2.5.0 - 2012-05-08 + +* Major performance improvements +* [BC] Simplifying Guzzle\Common\Collection. Please check to see if you are using features that are now deprecated. +* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component. +* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates. Use "{}" +* Added the ability to passed parameters to all requests created by a client +* Added callback functionality to the ExponentialBackoffPlugin +* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies. +* Rewinding request stream bodies when retrying requests +* Exception is thrown when JSON response body cannot be decoded +* Added configurable magic method calls to clients and commands. This is off by default. +* Fixed a defect that added a hash to every parsed URL part +* Fixed duplicate none generation for OauthPlugin. +* Emitting an event each time a client is generated by a ServiceBuilder +* Using an ApiParams object instead of a Collection for parameters of an ApiCommand +* cache.* request parameters should be renamed to params.cache.* +* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle. +* Added the ability to disable type validation of service descriptions +* ServiceDescriptions and ServiceBuilders are now Serializable diff --git a/lib/guzzlehttp/guzzle/LICENSE b/lib/guzzlehttp/guzzle/LICENSE new file mode 100644 index 0000000000..fd2375d888 --- /dev/null +++ b/lib/guzzlehttp/guzzle/LICENSE @@ -0,0 +1,27 @@ +The MIT License (MIT) + +Copyright (c) 2011 Michael Dowling +Copyright (c) 2012 Jeremy Lindblom +Copyright (c) 2014 Graham Campbell +Copyright (c) 2015 Márk Sági-Kazár +Copyright (c) 2015 Tobias Schultze +Copyright (c) 2016 Tobias Nyholm +Copyright (c) 2016 George Mponos + +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/lib/guzzlehttp/guzzle/README.md b/lib/guzzlehttp/guzzle/README.md new file mode 100644 index 0000000000..f287fa98d1 --- /dev/null +++ b/lib/guzzlehttp/guzzle/README.md @@ -0,0 +1,94 @@ +![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 is a PHP HTTP client that makes it easy to send HTTP requests and +trivial to integrate with web services. + +- Simple interface for building query strings, POST requests, streaming large + uploads, streaming large downloads, using HTTP cookies, uploading JSON data, + etc... +- 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. +- 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 + +The recommended way to install Guzzle is through +[Composer](https://getcomposer.org/). + +```bash +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 | + +[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/ + + +## 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. + +## License + +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) diff --git a/lib/guzzlehttp/guzzle/UPGRADING.md b/lib/guzzlehttp/guzzle/UPGRADING.md new file mode 100644 index 0000000000..45417a7e1f --- /dev/null +++ b/lib/guzzlehttp/guzzle/UPGRADING.md @@ -0,0 +1,1253 @@ +Guzzle Upgrade Guide +==================== + +6.0 to 7.0 +---------- + +In order to take advantage of the new features of PHP, Guzzle dropped the support +of PHP 5. The minimum supported PHP version is now PHP 7.2. Type hints and return +types for functions and methods have been added wherever possible. + +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 + +- Class `GuzzleHttp\UriTemplate` is removed. +- Class `GuzzleHttp\Exception\SeekException` is removed. +- Classes `GuzzleHttp\Exception\BadResponseException`, `GuzzleHttp\Exception\ClientException`, + `GuzzleHttp\Exception\ServerException` can no longer be initialized with an empty + Response as argument. +- Class `GuzzleHttp\Exception\ConnectException` now extends `GuzzleHttp\Exception\TransferException` + instead of `GuzzleHttp\Exception\RequestException`. +- Function `GuzzleHttp\Exception\ConnectException::getResponse()` is removed. +- Function `GuzzleHttp\Exception\ConnectException::hasResponse()` is removed. +- Constant `GuzzleHttp\ClientInterface::VERSION` is removed. Added `GuzzleHttp\ClientInterface::MAJOR_VERSION` instead. +- 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 `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. +- The `get`, `head`, `put`, `post`, `patch`, `delete`, `getAsync`, `headAsync`, `putAsync`, `postAsync`, `patchAsync`, and `deleteAsync` methods are now implemented as genuine methods on `GuzzleHttp\Client`, with strong typing. The original `__call` implementation remains unchanged for now, for maximum backwards compatibility, but won't be invoked under normal operation. +- 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 + +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. +Example: + +```php +// Before: +curl_version(); + +// After: +\curl_version(); +``` + +For the full diff you can check [here](https://github.com/guzzle/guzzle/compare/6.5.4..master). + +5.0 to 6.0 +---------- + +Guzzle now uses [PSR-7](https://www.php-fig.org/psr/psr-7/) for HTTP messages. +Due to the fact that these messages are immutable, this prompted a refactoring +of Guzzle to use a middleware based system rather than an event system. Any +HTTP message interaction (e.g., `GuzzleHttp\Message\Request`) need to be +updated to work with the new immutable PSR-7 request and response objects. Any +event listeners or subscribers need to be updated to become middleware +functions that wrap handlers (or are injected into a +`GuzzleHttp\HandlerStack`). + +- Removed `GuzzleHttp\BatchResults` +- Removed `GuzzleHttp\Collection` +- Removed `GuzzleHttp\HasDataTrait` +- Removed `GuzzleHttp\ToArrayInterface` +- The `guzzlehttp/streams` dependency has been removed. Stream functionality + is now present in the `GuzzleHttp\Psr7` namespace provided by the + `guzzlehttp/psr7` package. +- Guzzle no longer uses ReactPHP promises and now uses the + `guzzlehttp/promises` library. We use a custom promise library for three + significant reasons: + 1. React promises (at the time of writing this) are recursive. Promise + chaining and promise resolution will eventually blow the stack. Guzzle + promises are not recursive as they use a sort of trampolining technique. + Note: there has been movement in the React project to modify promises to + no longer utilize recursion. + 2. Guzzle needs to have the ability to synchronously block on a promise to + wait for a result. Guzzle promises allows this functionality (and does + not require the use of recursion). + 3. Because we need to be able to wait on a result, doing so using React + promises requires wrapping react promises with RingPHP futures. This + overhead is no longer needed, reducing stack sizes, reducing complexity, + and improving performance. +- `GuzzleHttp\Mimetypes` has been moved to a function in + `GuzzleHttp\Psr7\mimetype_from_extension` and + `GuzzleHttp\Psr7\mimetype_from_filename`. +- `GuzzleHttp\Query` and `GuzzleHttp\QueryParser` have been removed. Query + strings must now be passed into request objects as strings, or provided to + the `query` request option when creating requests with clients. The `query` + option uses PHP's `http_build_query` to convert an array to a string. If you + need a different serialization technique, you will need to pass the query + string in as a string. There are a couple helper functions that will make + working with query strings easier: `GuzzleHttp\Psr7\parse_query` and + `GuzzleHttp\Psr7\build_query`. +- Guzzle no longer has a dependency on RingPHP. Due to the use of a middleware + system based on PSR-7, using RingPHP and it's middleware system as well adds + more complexity than the benefits it provides. All HTTP handlers that were + present in RingPHP have been modified to work directly with PSR-7 messages + and placed in the `GuzzleHttp\Handler` namespace. This significantly reduces + complexity in Guzzle, removes a dependency, and improves performance. RingPHP + will be maintained for Guzzle 5 support, but will no longer be a part of + Guzzle 6. +- As Guzzle now uses a middleware based systems the event system and RingPHP + integration has been removed. Note: while the event system has been removed, + it is possible to add your own type of event system that is powered by the + middleware system. + - Removed the `Event` namespace. + - Removed the `Subscriber` namespace. + - Removed `Transaction` class + - Removed `RequestFsm` + - Removed `RingBridge` + - `GuzzleHttp\Subscriber\Cookie` is now provided by + `GuzzleHttp\Middleware::cookies` + - `GuzzleHttp\Subscriber\HttpError` is now provided by + `GuzzleHttp\Middleware::httpError` + - `GuzzleHttp\Subscriber\History` is now provided by + `GuzzleHttp\Middleware::history` + - `GuzzleHttp\Subscriber\Mock` is now provided by + `GuzzleHttp\Handler\MockHandler` + - `GuzzleHttp\Subscriber\Prepare` is now provided by + `GuzzleHttp\PrepareBodyMiddleware` + - `GuzzleHttp\Subscriber\Redirect` is now provided by + `GuzzleHttp\RedirectMiddleware` +- Guzzle now uses `Psr\Http\Message\UriInterface` (implements in + `GuzzleHttp\Psr7\Uri`) for URI support. `GuzzleHttp\Url` is now gone. +- Static functions in `GuzzleHttp\Utils` have been moved to namespaced + functions under the `GuzzleHttp` namespace. This requires either a Composer + based autoloader or you to include functions.php. +- `GuzzleHttp\ClientInterface::getDefaultOption` has been renamed to + `GuzzleHttp\ClientInterface::getConfig`. +- `GuzzleHttp\ClientInterface::setDefaultOption` has been removed. +- The `json` and `xml` methods of response objects has been removed. With the + migration to strictly adhering to PSR-7 as the interface for Guzzle messages, + adding methods to message interfaces would actually require Guzzle messages + to extend from PSR-7 messages rather then work with them directly. + +## Migrating to middleware + +The change to PSR-7 unfortunately required significant refactoring to Guzzle +due to the fact that PSR-7 messages are immutable. Guzzle 5 relied on an event +system from plugins. The event system relied on mutability of HTTP messages and +side effects in order to work. With immutable messages, you have to change your +workflow to become more about either returning a value (e.g., functional +middlewares) or setting a value on an object. Guzzle v6 has chosen the +functional middleware approach. + +Instead of using the event system to listen for things like the `before` event, +you now create a stack based middleware function that intercepts a request on +the way in and the promise of the response on the way out. This is a much +simpler and more predictable approach than the event system and works nicely +with PSR-7 middleware. Due to the use of promises, the middleware system is +also asynchronous. + +v5: + +```php +use GuzzleHttp\Event\BeforeEvent; +$client = new GuzzleHttp\Client(); +// Get the emitter and listen to the before event. +$client->getEmitter()->on('before', function (BeforeEvent $e) { + // Guzzle v5 events relied on mutation + $e->getRequest()->setHeader('X-Foo', 'Bar'); +}); +``` + +v6: + +In v6, you can modify the request before it is sent using the `mapRequest` +middleware. The idiomatic way in v6 to modify the request/response lifecycle is +to setup a handler middleware stack up front and inject the handler into a +client. + +```php +use GuzzleHttp\Middleware; +// Create a handler stack that has all of the default middlewares attached +$handler = GuzzleHttp\HandlerStack::create(); +// Push the handler onto the handler stack +$handler->push(Middleware::mapRequest(function (RequestInterface $request) { + // Notice that we have to return a request object + return $request->withHeader('X-Foo', 'Bar'); +})); +// Inject the handler into the client +$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) +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) +option is now used to send a multipart/form-data POST request. + +`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add +POST files to a multipart/form-data request. + +The `body` option no longer accepts an array to send POST requests. Please use +`multipart` or `form_params` instead. + +The `base_url` option has been renamed to `base_uri`. + +4.x to 5.0 +---------- + +## Rewritten Adapter Layer + +Guzzle now uses [RingPHP](http://ringphp.readthedocs.org/en/latest) 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 +`callable` that follows the RingPHP specification. + +## Removed Fluent Interfaces + +[Fluent interfaces were removed](https://ocramius.github.io/blog/fluent-interfaces-are-evil/) +from the following classes: + +- `GuzzleHttp\Collection` +- `GuzzleHttp\Url` +- `GuzzleHttp\Query` +- `GuzzleHttp\Post\PostBody` +- `GuzzleHttp\Cookie\SetCookie` + +## Removed functions.php + +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\get_path` -> `GuzzleHttp\Utils::getPath` +- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path` +- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however, + deprecated in favor of using `GuzzleHttp\Pool::batch()`. + +The "procedural" global client has been removed with no replacement (e.g., +`GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttp\Client` +object as a replacement. + +## `throwImmediately` has been removed + +The concept of "throwImmediately" has been removed from exceptions and error +events. This control mechanism was used to stop a transfer of concurrent +requests from completing. This can now be handled by throwing the exception or +by cancelling a pool of requests or each outstanding future request +individually. + +## headers event has been removed + +Removed the "headers" event. This event was only useful for changing the +body a response once the headers of the response were known. You can implement +a similar behavior in a number of ways. One example might be to use a +FnStream that has access to the transaction being sent. For example, when the +first byte is written, you could check if the response headers match your +expectations, and if so, change the actual stream body that is being +written to. + +## Updates to HTTP Messages + +Removed the `asArray` parameter from +`GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header +value as an array, then use the newly added `getHeaderAsArray()` method of +`MessageInterface`. This change makes the Guzzle interfaces compatible with +the PSR-7 interfaces. + +3.x to 4.0 +---------- + +## Overarching changes: + +- Now requires PHP 5.4 or greater. +- No longer requires cURL to send requests. +- Guzzle no longer wraps every exception it throws. Only exceptions that are + recoverable are now wrapped by Guzzle. +- Various namespaces have been removed or renamed. +- No longer requiring the Symfony EventDispatcher. A custom event dispatcher + based on the Symfony EventDispatcher is + now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant + speed and functionality improvements). + +Changes per Guzzle 3.x namespace are described below. + +## Batch + +The `Guzzle\Batch` namespace has been removed. This is best left to +third-parties to implement on top of Guzzle's core HTTP library. + +## Cache + +The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement +has been implemented yet, but hoping to utilize a PSR cache interface). + +## Common + +- Removed all of the wrapped exceptions. It's better to use the standard PHP + library for unrecoverable exceptions. +- `FromConfigInterface` has been removed. +- `Guzzle\Common\Version` has been removed. The VERSION constant can be found + at `GuzzleHttp\ClientInterface::VERSION`. + +### Collection + +- `getAll` has been removed. Use `toArray` to convert a collection to an array. +- `inject` has been removed. +- `keySearch` has been removed. +- `getPath` no longer supports wildcard expressions. Use something better like + JMESPath for this. +- `setPath` now supports appending to an existing array via the `[]` notation. + +### Events + +Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses +`GuzzleHttp\Event\Emitter`. + +- `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by + `GuzzleHttp\Event\EmitterInterface`. +- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by + `GuzzleHttp\Event\Emitter`. +- `Symfony\Component\EventDispatcher\Event` is replaced by + `GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in + `GuzzleHttp\Event\EventInterface`. +- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and + `HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the + event emitter of a request, client, etc. now uses the `getEmitter` method + rather than the `getDispatcher` method. + +#### Emitter + +- Use the `once()` method to add a listener that automatically removes itself + the first time it is invoked. +- Use the `listeners()` method to retrieve a list of event listeners rather than + the `getListeners()` method. +- Use `emit()` instead of `dispatch()` to emit an event from an emitter. +- Use `attach()` instead of `addSubscriber()` and `detach()` instead of + `removeSubscriber()`. + +```php +$mock = new Mock(); +// 3.x +$request->getEventDispatcher()->addSubscriber($mock); +$request->getEventDispatcher()->removeSubscriber($mock); +// 4.x +$request->getEmitter()->attach($mock); +$request->getEmitter()->detach($mock); +``` + +Use the `on()` method to add a listener rather than the `addListener()` method. + +```php +// 3.x +$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } ); +// 4.x +$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } ); +``` + +## Http + +### General changes + +- The cacert.pem certificate has been moved to `src/cacert.pem`. +- Added the concept of adapters that are used to transfer requests over the + wire. +- Simplified the event system. +- Sending requests in parallel is still possible, but batching is no longer a + concept of the HTTP layer. Instead, you must use the `complete` and `error` + events to asynchronously manage parallel request transfers. +- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`. +- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`. +- QueryAggregators have been rewritten so that they are simply callable + functions. +- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in + `functions.php` for an easy to use static client instance. +- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from + `GuzzleHttp\Exception\TransferException`. + +### Client + +Calling methods like `get()`, `post()`, `head()`, etc. no longer create and +return a request, but rather creates a request, sends the request, and returns +the response. + +```php +// 3.0 +$request = $client->get('/'); +$response = $request->send(); + +// 4.0 +$response = $client->get('/'); + +// or, to mirror the previous behavior +$request = $client->createRequest('GET', '/'); +$response = $client->send($request); +``` + +`GuzzleHttp\ClientInterface` has changed. + +- The `send` method no longer accepts more than one request. Use `sendAll` to + send multiple requests in parallel. +- `setUserAgent()` has been removed. Use a default request option instead. You + could, for example, do something like: + `$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`. +- `setSslVerification()` has been removed. Use default request options instead, + like `$client->setConfig('defaults/verify', true)`. + +`GuzzleHttp\Client` has changed. + +- The constructor now accepts only an associative array. You can include a + `base_url` string or array to use a URI template as the base URL of a client. + You can also specify a `defaults` key that is an associative array of default + request options. You can pass an `adapter` to use a custom adapter, + `batch_adapter` to use a custom adapter for sending requests in parallel, or + a `message_factory` to change the factory used to create HTTP requests and + responses. +- The client no longer emits a `client.create_request` event. +- Creating requests with a client no longer automatically utilize a URI + template. You must pass an array into a creational method (e.g., + `createRequest`, `get`, `put`, etc.) in order to expand a URI template. + +### Messages + +Messages no longer have references to their counterparts (i.e., a request no +longer has a reference to it's response, and a response no loger has a +reference to its request). This association is now managed through a +`GuzzleHttp\Adapter\TransactionInterface` object. You can get references to +these transaction objects using request events that are emitted over the +lifecycle of a request. + +#### Requests with a body + +- `GuzzleHttp\Message\EntityEnclosingRequest` and + `GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The + separation between requests that contain a body and requests that do not + contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface` + handles both use cases. +- Any method that previously accepts a `GuzzleHttp\Response` object now accept a + `GuzzleHttp\Message\ResponseInterface`. +- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to + `GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create + both requests and responses and is implemented in + `GuzzleHttp\Message\MessageFactory`. +- POST field and file methods have been removed from the request object. You + must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface` + to control the format of a POST body. Requests that are created using a + standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use + a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if + the method is POST and no body is provided. + +```php +$request = $client->createRequest('POST', '/'); +$request->getBody()->setField('foo', 'bar'); +$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r'))); +``` + +#### Headers + +- `GuzzleHttp\Message\Header` has been removed. Header values are now simply + represented by an array of values or as a string. Header values are returned + as a string by default when retrieving a header value from a message. You can + pass an optional argument of `true` to retrieve a header value as an array + of strings instead of a single concatenated string. +- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to + `GuzzleHttp\Post`. This interface has been simplified and now allows the + addition of arbitrary headers. +- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most + of the custom headers are now handled separately in specific + subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has + been updated to properly handle headers that contain parameters (like the + `Link` header). + +#### Responses + +- `GuzzleHttp\Message\Response::getInfo()` and + `GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event + system to retrieve this type of information. +- `GuzzleHttp\Message\Response::getRawHeaders()` has been removed. +- `GuzzleHttp\Message\Response::getMessage()` has been removed. +- `GuzzleHttp\Message\Response::calculateAge()` and other cache specific + methods have moved to the CacheSubscriber. +- Header specific helper functions like `getContentMd5()` have been removed. + Just use `getHeader('Content-MD5')` instead. +- `GuzzleHttp\Message\Response::setRequest()` and + `GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event + system to work with request and response objects as a transaction. +- `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the + Redirect subscriber instead. +- `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have + been removed. Use `getStatusCode()` instead. + +#### Streaming responses + +Streaming requests can now be created by a client directly, returning a +`GuzzleHttp\Message\ResponseInterface` object that contains a body stream +referencing an open PHP HTTP stream. + +```php +// 3.0 +use Guzzle\Stream\PhpStreamRequestFactory; +$request = $client->get('/'); +$factory = new PhpStreamRequestFactory(); +$stream = $factory->fromRequest($request); +$data = $stream->read(1024); + +// 4.0 +$response = $client->get('/', ['stream' => true]); +// Read some data off of the stream in the response body +$data = $response->getBody()->read(1024); +``` + +#### Redirects + +The `configureRedirects()` method has been removed in favor of a +`allow_redirects` request option. + +```php +// Standard redirects with a default of a max of 5 redirects +$request = $client->createRequest('GET', '/', ['allow_redirects' => true]); + +// Strict redirects with a custom number of redirects +$request = $client->createRequest('GET', '/', [ + 'allow_redirects' => ['max' => 5, 'strict' => true] +]); +``` + +#### EntityBody + +EntityBody interfaces and classes have been removed or moved to +`GuzzleHttp\Stream`. All classes and interfaces that once required +`GuzzleHttp\EntityBodyInterface` now require +`GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no +longer uses `GuzzleHttp\EntityBody::factory` but now uses +`GuzzleHttp\Stream\Stream::factory` or even better: +`GuzzleHttp\Stream\create()`. + +- `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface` +- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream` +- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream` +- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream` +- `Guzzle\Http\IoEmittyinEntityBody` has been removed. + +#### Request lifecycle events + +Requests previously submitted a large number of requests. The number of events +emitted over the lifecycle of a request has been significantly reduced to make +it easier to understand how to extend the behavior of a request. All events +emitted during the lifecycle of a request now emit a custom +`GuzzleHttp\Event\EventInterface` object that contains context providing +methods and a way in which to modify the transaction at that specific point in +time (e.g., intercept the request and set a response on the transaction). + +- `request.before_send` has been renamed to `before` and now emits a + `GuzzleHttp\Event\BeforeEvent` +- `request.complete` has been renamed to `complete` and now emits a + `GuzzleHttp\Event\CompleteEvent`. +- `request.sent` has been removed. Use `complete`. +- `request.success` has been removed. Use `complete`. +- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`. +- `request.exception` has been removed. Use `error`. +- `request.receive.status_line` has been removed. +- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to + maintain a status update. +- `curl.callback.write` has been removed. Use a custom `StreamInterface` to + intercept writes. +- `curl.callback.read` has been removed. Use a custom `StreamInterface` to + intercept reads. + +`headers` is a new event that is emitted after the response headers of a +request have been received before the body of the response is downloaded. This +event emits a `GuzzleHttp\Event\HeadersEvent`. + +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 +of Guzzle. + +## Iterator + +The `Guzzle\Iterator` namespace has been removed. + +- `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and + `Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of + Guzzle itself. +- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent + class is shipped with PHP 5.4. +- `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because + it's easier to just wrap an iterator in a generator that maps values. + +For a replacement of these iterators, see https://github.com/nikic/iter + +## Log + +The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The +`Guzzle\Log` namespace has been removed. Guzzle now relies on +`Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been +moved to `GuzzleHttp\Subscriber\Log\Formatter`. + +## Parser + +The `Guzzle\Parser` namespace has been removed. This was previously used to +make it possible to plug in custom parsers for cookies, messages, URI +templates, and URLs; however, this level of complexity is not needed in Guzzle +so it has been removed. + +- Cookie: Cookie parsing logic has been moved to + `GuzzleHttp\Cookie\SetCookie::fromString`. +- Message: Message parsing logic for both requests and responses has been moved + to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only + used in debugging or deserializing messages, so it doesn't make sense for + Guzzle as a library to add this level of complexity to parsing messages. +- UriTemplate: URI template parsing has been moved to + `GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL + URI template library if it is installed. +- Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously + it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary, + then developers are free to subclass `GuzzleHttp\Url`. + +## Plugin + +The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`. +Several plugins are shipping with the core Guzzle library under this namespace. + +- `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar + code has moved to `GuzzleHttp\Cookie`. +- `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin. +- `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is + received. +- `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin. +- `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before + sending. This subscriber is attached to all requests by default. +- `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin. + +The following plugins have been removed (third-parties are free to re-implement +these if needed): + +- `GuzzleHttp\Plugin\Async` has been removed. +- `GuzzleHttp\Plugin\CurlAuth` has been removed. +- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This + functionality should instead be implemented with event listeners that occur + after normal response parsing occurs in the guzzle/command package. + +The following plugins are not part of the core Guzzle package, but are provided +in separate repositories: + +- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be much simpler + to build custom retry policies using simple functions rather than various + chained classes. See: https://github.com/guzzle/retry-subscriber +- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to + https://github.com/guzzle/cache-subscriber +- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to + https://github.com/guzzle/log-subscriber +- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to + https://github.com/guzzle/message-integrity-subscriber +- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to + `GuzzleHttp\Subscriber\MockSubscriber`. +- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to + https://github.com/guzzle/oauth-subscriber + +## Service + +The service description layer of Guzzle has moved into two separate packages: + +- http://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 + guzzle/command that provides request serialization and response parsing using + Guzzle service descriptions. + +## Stream + +Stream have moved to a separate package available at +https://github.com/guzzle/streams. + +`Guzzle\Stream\StreamInterface` has been given a large update to cleanly take +on the responsibilities of `Guzzle\Http\EntityBody` and +`Guzzle\Http\EntityBodyInterface` now that they have been removed. The number +of methods implemented by the `StreamInterface` has been drastically reduced to +allow developers to more easily extend and decorate stream behavior. + +## Removed methods from StreamInterface + +- `getStream` and `setStream` have been removed to better encapsulate streams. +- `getMetadata` and `setMetadata` have been removed in favor of + `GuzzleHttp\Stream\MetadataStreamInterface`. +- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been + removed. This data is accessible when + using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`. +- `rewind` has been removed. Use `seek(0)` for a similar behavior. + +## Renamed methods + +- `detachStream` has been renamed to `detach`. +- `feof` has been renamed to `eof`. +- `ftell` has been renamed to `tell`. +- `readLine` has moved from an instance method to a static class method of + `GuzzleHttp\Stream\Stream`. + +## Metadata streams + +`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams +that contain additional metadata accessible via `getMetadata()`. +`GuzzleHttp\Stream\StreamInterface::getMetadata` and +`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed. + +## StreamRequestFactory + +The entire concept of the StreamRequestFactory has been removed. The way this +was used in Guzzle 3 broke the actual interface of sending streaming requests +(instead of getting back a Response, you got a StreamInterface). Streaming +PHP requests are now implemented through the `GuzzleHttp\Adapter\StreamAdapter`. + +3.6 to 3.7 +---------- + +### Deprecations + +- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.: + +```php +\Guzzle\Common\Version::$emitWarnings = true; +``` + +The following APIs and options have been marked as deprecated: + +- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead. +- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead. +- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated +- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. +- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8. +- Marked `Guzzle\Common\Collection::inject()` as deprecated. +- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use + `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or + `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` + +3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational +request methods. When paired with a client's configuration settings, these options allow you to specify default settings +for various aspects of a request. Because these options make other previous configuration options redundant, several +configuration options and methods of a client and AbstractCommand have been deprecated. + +- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`. +- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`. +- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')` +- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0 + + $command = $client->getCommand('foo', array( + 'command.headers' => array('Test' => '123'), + 'command.response_body' => '/path/to/file' + )); + + // Should be changed to: + + $command = $client->getCommand('foo', array( + 'command.request_options' => array( + 'headers' => array('Test' => '123'), + 'save_as' => '/path/to/file' + ) + )); + +### Interface changes + +Additions and changes (you will need to update any implementations or subclasses you may have created): + +- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`: + createRequest, head, delete, put, patch, post, options, prepareRequest +- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()` +- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface` +- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to + `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a + resource, string, or EntityBody into the $options parameter to specify the download location of the response. +- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a + default `array()` +- Added `Guzzle\Stream\StreamInterface::isRepeatable` +- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods. + +The following methods were removed from interfaces. All of these methods are still available in the concrete classes +that implement them, but you should update your code to use alternative methods: + +- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use + `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or + `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or + `$client->setDefaultOption('headers/{header_name}', 'value')`. or + `$client->setDefaultOption('headers', array('header_name' => 'value'))`. +- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`. +- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail. +- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail. +- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin. +- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin. +- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin. + +### Cache plugin breaking changes + +- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a + CacheStorageInterface. These two objects and interface will be removed in a future version. +- Always setting X-cache headers on cached responses +- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin +- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface + $request, Response $response);` +- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);` +- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);` +- Added `CacheStorageInterface::purge($url)` +- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin + $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache, + CanCacheStrategyInterface $canCache = null)` +- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)` + +3.5 to 3.6 +---------- + +* Mixed casing of headers are now forced to be a single consistent casing across all values for that header. +* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution +* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). + For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader(). + Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request. +* Specific header implementations can be created for complex headers. When a message creates a header, it uses a + HeaderFactory which can map specific headers to specific header classes. There is now a Link header and + CacheControl header implementation. +* Moved getLinks() from Response to just be used on a Link header object. + +If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the +HeaderInterface (e.g. toArray(), getAll(), etc.). + +### Interface changes + +* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate +* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti() +* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in + Guzzle\Http\Curl\RequestMediator +* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string. +* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface +* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders() + +### Removed deprecated functions + +* Removed Guzzle\Parser\ParserRegister::get(). Use getParser() +* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser(). + +### Deprecations + +* The ability to case-insensitively search for header values +* Guzzle\Http\Message\Header::hasExactHeader +* Guzzle\Http\Message\Header::raw. Use getAll() +* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object + instead. + +### Other changes + +* All response header helper functions return a string rather than mixing Header objects and strings inconsistently +* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle + directly via interfaces +* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist + but are a no-op until removed. +* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a + `Guzzle\Service\Command\ArrayCommandInterface`. +* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response + on a request while the request is still being transferred +* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess + +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. + +3.2 to 3.3 +---------- + +### Response::getEtag() quote stripping removed + +`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header + +### Removed `Guzzle\Http\Utils` + +The `Guzzle\Http\Utils` class was removed. This class was only used for testing. + +### Stream wrapper and type + +`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase. + +### curl.emit_io became emit_io + +Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the +'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io' + +3.1 to 3.2 +---------- + +### CurlMulti is no longer reused globally + +Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added +to a single client can pollute requests dispatched from other clients. + +If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the +ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is +created. + +```php +$multi = new Guzzle\Http\Curl\CurlMulti(); +$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json'); +$builder->addListener('service_builder.create_client', function ($event) use ($multi) { + $event['client']->setCurlMulti($multi); +} +}); +``` + +### No default path + +URLs no longer have a default path value of '/' if no path was specified. + +Before: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com/ +``` + +After: + +```php +$request = $client->get('http://www.foo.com'); +echo $request->getUrl(); +// >> http://www.foo.com +``` + +### Less verbose BadResponseException + +The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and +response information. You can, however, get access to the request and response object by calling `getRequest()` or +`getResponse()` on the exception object. + +### Query parameter aggregation + +Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a +setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is +responsible for handling the aggregation of multi-valued query string variables into a flattened hash. + +2.8 to 3.x +---------- + +### Guzzle\Service\Inspector + +Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig` + +**Before** + +```php +use Guzzle\Service\Inspector; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Inspector::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +**After** + +```php +use Guzzle\Common\Collection; + +class YourClient extends \Guzzle\Service\Client +{ + public static function factory($config = array()) + { + $default = array(); + $required = array('base_url', 'username', 'api_key'); + $config = Collection::fromConfig($config, $default, $required); + + $client = new self( + $config->get('base_url'), + $config->get('username'), + $config->get('api_key') + ); + $client->setConfig($config); + + $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json')); + + return $client; + } +``` + +### Convert XML Service Descriptions to JSON + +**Before** + +```xml + + + + + + Get a list of groups + + + Uses a search query to get a list of groups + + + + Create a group + + + + + Delete a group by ID + + + + + + + Update a group + + + + + + +``` + +**After** + +```json +{ + "name": "Zendesk REST API v2", + "apiVersion": "2012-12-31", + "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users", + "operations": { + "list_groups": { + "httpMethod":"GET", + "uri": "groups.json", + "summary": "Get a list of groups" + }, + "search_groups":{ + "httpMethod":"GET", + "uri": "search.json?query=\"{query} type:group\"", + "summary": "Uses a search query to get a list of groups", + "parameters":{ + "query":{ + "location": "uri", + "description":"Zendesk Search Query", + "type": "string", + "required": true + } + } + }, + "create_group": { + "httpMethod":"POST", + "uri": "groups.json", + "summary": "Create a group", + "parameters":{ + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + }, + "delete_group": { + "httpMethod":"DELETE", + "uri": "groups/{id}.json", + "summary": "Delete a group", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to delete by ID", + "type": "integer", + "required": true + } + } + }, + "get_group": { + "httpMethod":"GET", + "uri": "groups/{id}.json", + "summary": "Get a ticket", + "parameters":{ + "id":{ + "location": "uri", + "description":"Group to get by ID", + "type": "integer", + "required": true + } + } + }, + "update_group": { + "httpMethod":"PUT", + "uri": "groups/{id}.json", + "summary": "Update a group", + "parameters":{ + "id": { + "location": "uri", + "description":"Group to update by ID", + "type": "integer", + "required": true + }, + "data": { + "type": "array", + "location": "body", + "description":"Group JSON", + "filters": "json_encode", + "required": true + }, + "Content-Type":{ + "type": "string", + "location":"header", + "static": "application/json" + } + } + } +} +``` + +### Guzzle\Service\Description\ServiceDescription + +Commands are now called Operations + +**Before** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getCommands(); // @returns ApiCommandInterface[] +$sd->hasCommand($name); +$sd->getCommand($name); // @returns ApiCommandInterface|null +$sd->addCommand($command); // @param ApiCommandInterface $command +``` + +**After** + +```php +use Guzzle\Service\Description\ServiceDescription; + +$sd = new ServiceDescription(); +$sd->getOperations(); // @returns OperationInterface[] +$sd->hasOperation($name); +$sd->getOperation($name); // @returns OperationInterface|null +$sd->addOperation($operation); // @param OperationInterface $operation +``` + +### Guzzle\Common\Inflection\Inflector + +Namespace is now `Guzzle\Inflection\Inflector` + +### Guzzle\Http\Plugin + +Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below. + +### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log + +Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively. + +**Before** + +```php +use Guzzle\Common\Log\ClosureLogAdapter; +use Guzzle\Http\Plugin\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $verbosity is an integer indicating desired message verbosity level +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE); +``` + +**After** + +```php +use Guzzle\Log\ClosureLogAdapter; +use Guzzle\Log\MessageFormatter; +use Guzzle\Plugin\Log\LogPlugin; + +/** @var \Guzzle\Http\Client */ +$client; + +// $format is a string indicating desired message format -- @see MessageFormatter +$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT); +``` + +### Guzzle\Http\Plugin\CurlAuthPlugin + +Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`. + +### Guzzle\Http\Plugin\ExponentialBackoffPlugin + +Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes. + +**Before** + +```php +use Guzzle\Http\Plugin\ExponentialBackoffPlugin; + +$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge( + ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429) + )); + +$client->addSubscriber($backoffPlugin); +``` + +**After** + +```php +use Guzzle\Plugin\Backoff\BackoffPlugin; +use Guzzle\Plugin\Backoff\HttpBackoffStrategy; + +// Use convenient factory method instead -- see implementation for ideas of what +// you can do with chaining backoff strategies +$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge( + HttpBackoffStrategy::getDefaultFailureCodes(), array(429) + )); +$client->addSubscriber($backoffPlugin); +``` + +### Known Issues + +#### [BUG] Accept-Encoding header behavior changed unintentionally. + +(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e) + +In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to +properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen. +See issue #217 for a workaround, or use a version containing the fix. diff --git a/lib/guzzlehttp/guzzle/composer.json b/lib/guzzlehttp/guzzle/composer.json new file mode 100644 index 0000000000..7e043b038f --- /dev/null +++ b/lib/guzzlehttp/guzzle/composer.json @@ -0,0 +1,101 @@ +{ + "name": "guzzlehttp/guzzle", + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "framework", + "http", + "rest", + "web service", + "curl", + "client", + "HTTP client", + "PSR-7", + "PSR-18" + ], + "license": "MIT", + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "require": { + "php": "^7.2.5 || ^8.0", + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.9 || ^2.4", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "bamarni/composer-bin-plugin": "^1.4.1", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1 || ^2.0 || ^3.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" + }, + "config": { + "allow-plugins": { + "bamarni/composer-bin-plugin": true + }, + "preferred-install": "dist", + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-master": "7.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\": "tests/" + } + } +} diff --git a/lib/guzzlehttp/guzzle/src/BodySummarizer.php b/lib/guzzlehttp/guzzle/src/BodySummarizer.php new file mode 100644 index 0000000000..6eca94ef97 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/BodySummarizer.php @@ -0,0 +1,28 @@ +truncateAt = $truncateAt; + } + + /** + * Returns a summarized message body. + */ + public function summarize(MessageInterface $message): ?string + { + return $this->truncateAt === null + ? \GuzzleHttp\Psr7\Message::bodySummary($message) + : \GuzzleHttp\Psr7\Message::bodySummary($message, $this->truncateAt); + } +} diff --git a/lib/guzzlehttp/guzzle/src/BodySummarizerInterface.php b/lib/guzzlehttp/guzzle/src/BodySummarizerInterface.php new file mode 100644 index 0000000000..3e02e036e3 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/BodySummarizerInterface.php @@ -0,0 +1,13 @@ + 'http://www.foo.com/1.0/', + * 'timeout' => 0, + * 'allow_redirects' => false, + * 'proxy' => '192.168.16.1:10' + * ]); + * + * Client configuration settings include the following options: + * + * - handler: (callable) Function that transfers HTTP requests over the + * wire. The function is called with a Psr7\Http\Message\RequestInterface + * and array of transfer options, and must return a + * GuzzleHttp\Promise\PromiseInterface that is fulfilled with a + * Psr7\Http\Message\ResponseInterface on success. + * If no handler is provided, a default handler will be created + * that enables all of the request options below by attaching all of the + * default middleware to the handler. + * - base_uri: (string|UriInterface) Base URI of the client that is merged + * into relative URIs. Can be a string or instance of UriInterface. + * - **: any request option + * + * @param array $config Client configuration settings. + * + * @see \GuzzleHttp\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'); + } + + // Convert the base_uri to a UriInterface + if (isset($config['base_uri'])) { + $config['base_uri'] = Psr7\Utils::uriFor($config['base_uri']); + } + + $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. + */ + public function sendAsync(RequestInterface $request, 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')), + $options + ); + } + + /** + * Send an HTTP request. + * + * @param array $options Request options to apply to the given + * request and to the transfer. See \GuzzleHttp\RequestOptions. + * + * @throws GuzzleException + */ + public function send(RequestInterface $request, array $options = []): ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = true; + return $this->sendAsync($request, $options)->wait(); + } + + /** + * The HttpClient PSR (PSR-18) specify this method. + * + * @inheritDoc + */ + public function sendRequest(RequestInterface $request): ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = true; + $options[RequestOptions::ALLOW_REDIRECTS] = false; + $options[RequestOptions::HTTP_ERRORS] = false; + + return $this->sendAsync($request, $options)->wait(); + } + + /** + * Create and send an asynchronous HTTP request. + * + * 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. + * + * @param string $method HTTP method + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions. + */ + public function requestAsync(string $method, $uri = '', 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)) { + 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']); + + return $this->transfer($request, $options); + } + + /** + * Create and send an HTTP request. + * + * 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. + * + * @param string $method HTTP method. + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. See \GuzzleHttp\RequestOptions. + * + * @throws GuzzleException + */ + public function request(string $method, $uri = '', array $options = []): ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = true; + return $this->requestAsync($method, $uri, $options)->wait(); + } + + /** + * 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 Client::getConfig will be removed in guzzlehttp/guzzle:8.0. + */ + public function getConfig(?string $option = null) + { + return $option === null + ? $this->config + : ($this->config[$option] ?? null); + } + + private function buildUri(UriInterface $uri, array $config): UriInterface + { + if (isset($config['base_uri'])) { + $uri = Psr7\UriResolver::resolve(Psr7\Utils::uriFor($config['base_uri']), $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); + } + + return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; + } + + /** + * Configures the default options for a client. + */ + private function configureDefaults(array $config): void + { + $defaults = [ + 'allow_redirects' => RedirectMiddleware::$defaultSettings, + 'http_errors' => true, + 'decode_content' => true, + 'verify' => true, + 'cookies' => false, + 'idn_conversion' => false, + ]; + + // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set. + + // 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'))) { + $defaults['proxy']['http'] = $proxy; + } + + if ($proxy = Utils::getenv('HTTPS_PROXY')) { + $defaults['proxy']['https'] = $proxy; + } + + if ($noProxy = Utils::getenv('NO_PROXY')) { + $cleanedNoProxy = \str_replace(' ', '', $noProxy); + $defaults['proxy']['no'] = \explode(',', $cleanedNoProxy); + } + + $this->config = $config + $defaults; + + if (!empty($config['cookies']) && $config['cookies'] === true) { + $this->config['cookies'] = new CookieJar(); + } + + // Add the default user-agent header. + if (!isset($this->config['headers'])) { + $this->config['headers'] = ['User-Agent' => Utils::defaultUserAgent()]; + } else { + // Add the User-Agent header if one was not already set. + foreach (\array_keys($this->config['headers']) as $name) { + if (\strtolower($name) === 'user-agent') { + return; + } + } + $this->config['headers']['User-Agent'] = Utils::defaultUserAgent(); + } + } + + /** + * Merges default options into the array. + * + * @param array $options Options to modify by reference + */ + private function prepareDefaults(array $options): array + { + $defaults = $this->config; + + if (!empty($defaults['headers'])) { + // Default headers are only added if they are not present. + $defaults['_conditional'] = $defaults['headers']; + unset($defaults['headers']); + } + + // Special handling for headers is required as they are added as + // conditional headers and as headers passed to a request ctor. + if (\array_key_exists('headers', $options)) { + // Allows default headers to be unset. + if ($options['headers'] === null) { + $defaults['_conditional'] = []; + unset($options['headers']); + } elseif (!\is_array($options['headers'])) { + throw new InvalidArgumentException('headers must be an array'); + } + } + + // Shallow merge defaults underneath options. + $result = $options + $defaults; + + // Remove null values. + foreach ($result as $k => $v) { + if ($v === null) { + unset($result[$k]); + } + } + + return $result; + } + + /** + * 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. + */ + private function transfer(RequestInterface $request, array $options): PromiseInterface + { + $request = $this->applyOptions($request, $options); + /** @var HandlerStack $handler */ + $handler = $options['handler']; + + try { + return P\Create::promiseFor($handler($request, $options)); + } catch (\Exception $e) { + return P\Create::rejectionFor($e); + } + } + + /** + * Applies the array of request options to a request. + */ + private function applyOptions(RequestInterface $request, array &$options): RequestInterface + { + $modify = [ + 'set_headers' => [], + ]; + + 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.'); + } + $modify['set_headers'] = $options['headers']; + unset($options['headers']); + } + + 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.'); + } + $options['body'] = \http_build_query($options['form_params'], '', '&'); + unset($options['form_params']); + // 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/x-www-form-urlencoded'; + } + + if (isset($options['multipart'])) { + $options['body'] = new Psr7\MultipartStream($options['multipart']); + unset($options['multipart']); + } + + if (isset($options['json'])) { + $options['body'] = Utils::jsonEncode($options['json']); + 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 + ) { + // 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']; + } + + if (isset($options['body'])) { + if (\is_array($options['body'])) { + throw $this->invalidBody(); + } + $modify['body'] = Psr7\Utils::streamFor($options['body']); + 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)) { + $value = \http_build_query($value, '', '&', \PHP_QUERY_RFC3986); + } + if (!\is_string($value)) { + throw new InvalidArgumentException('query must be a string or array'); + } + $modify['query'] = $value; + 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'); + } + } + + $request = Psr7\Utils::modifyRequest($request, $modify); + if ($request->getBody() instanceof Psr7\MultipartStream) { + // 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(); + } + + // Merge in conditional headers if they are not present. + if (isset($options['_conditional'])) { + // 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; + } + } + $request = Psr7\Utils::modifyRequest($request, $modify); + // Don't pass this internal value along to middleware/handlers. + unset($options['_conditional']); + } + + return $request; + } + + /** + * 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.'); + } +} diff --git a/lib/guzzlehttp/guzzle/src/ClientInterface.php b/lib/guzzlehttp/guzzle/src/ClientInterface.php new file mode 100644 index 0000000000..6aaee61afc --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/ClientInterface.php @@ -0,0 +1,84 @@ +request('GET', $uri, $options); + } + + /** + * Create and send an HTTP HEAD request. + * + * 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. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function head($uri, array $options = []): ResponseInterface + { + return $this->request('HEAD', $uri, $options); + } + + /** + * Create and send an HTTP PUT request. + * + * 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. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function put($uri, array $options = []): ResponseInterface + { + return $this->request('PUT', $uri, $options); + } + + /** + * Create and send an HTTP POST request. + * + * 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. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function post($uri, array $options = []): ResponseInterface + { + return $this->request('POST', $uri, $options); + } + + /** + * Create and send an HTTP PATCH request. + * + * 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. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function patch($uri, array $options = []): ResponseInterface + { + return $this->request('PATCH', $uri, $options); + } + + /** + * Create and send an HTTP DELETE request. + * + * 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. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function delete($uri, array $options = []): ResponseInterface + { + return $this->request('DELETE', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP request. + * + * 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. + * + * @param string $method HTTP method + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + abstract public function requestAsync(string $method, $uri, array $options = []): PromiseInterface; + + /** + * Create and send an asynchronous HTTP GET request. + * + * 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. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function getAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('GET', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP HEAD request. + * + * 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. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function headAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('HEAD', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP PUT request. + * + * 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. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function putAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('PUT', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP POST request. + * + * 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. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function postAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('POST', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP PATCH request. + * + * 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. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function patchAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('PATCH', $uri, $options); + } + + /** + * Create and send an asynchronous HTTP DELETE request. + * + * 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. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function deleteAsync($uri, array $options = []): PromiseInterface + { + return $this->requestAsync('DELETE', $uri, $options); + } +} diff --git a/lib/guzzlehttp/guzzle/src/Cookie/CookieJar.php b/lib/guzzlehttp/guzzle/src/Cookie/CookieJar.php new file mode 100644 index 0000000000..6ef8e8c1df --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Cookie/CookieJar.php @@ -0,0 +1,318 @@ +strictMode = $strictMode; + + foreach ($cookieArray as $cookie) { + if (!($cookie instanceof SetCookie)) { + $cookie = new SetCookie($cookie); + } + $this->setCookie($cookie); + } + } + + /** + * Create a new Cookie jar from an associative array and domain. + * + * @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 + { + $cookieJar = new self(); + foreach ($cookies as $name => $value) { + $cookieJar->setCookie(new SetCookie([ + 'Domain' => $domain, + 'Name' => $name, + 'Value' => $value, + 'Discard' => true + ])); + } + + return $cookieJar; + } + + /** + * Evaluate if this cookie should be persisted to storage + * that survives between requests. + * + * @param SetCookie $cookie Being evaluated. + * @param bool $allowSessionCookies If we should persist session cookies + */ + public static function shouldPersist(SetCookie $cookie, bool $allowSessionCookies = false): bool + { + if ($cookie->getExpires() || $allowSessionCookies) { + if (!$cookie->getDiscard()) { + return true; + } + } + + return false; + } + + /** + * Finds and returns the cookie based on the name + * + * @param string $name cookie name to search for + * + * @return SetCookie|null cookie that was found or null if not found + */ + public function getCookieByName(string $name): ?SetCookie + { + foreach ($this->cookies as $cookie) { + if ($cookie->getName() !== null && \strcasecmp($cookie->getName(), $name) === 0) { + return $cookie; + } + } + + return null; + } + + /** + * @inheritDoc + */ + public function toArray(): array + { + return \array_map(static function (SetCookie $cookie): array { + return $cookie->toArray(); + }, $this->getIterator()->getArrayCopy()); + } + + /** + * @inheritDoc + */ + public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void + { + if (!$domain) { + $this->cookies = []; + return; + } elseif (!$path) { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie) use ($domain): bool { + return !$cookie->matchesDomain($domain); + } + ); + } elseif (!$name) { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie) use ($path, $domain): bool { + return !($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)); + } + ); + } + } + + /** + * @inheritDoc + */ + public function clearSessionCookies(): void + { + $this->cookies = \array_filter( + $this->cookies, + static function (SetCookie $cookie): bool { + return !$cookie->getDiscard() && $cookie->getExpires(); + } + ); + } + + /** + * @inheritDoc + */ + public function setCookie(SetCookie $cookie): bool + { + // If the name string is empty (but not 0), ignore the set-cookie + // string entirely. + $name = $cookie->getName(); + if (!$name && $name !== '0') { + return false; + } + + // Only allow cookies with set and valid domain, name, value + $result = $cookie->validate(); + if ($result !== true) { + if ($this->strictMode) { + throw new \RuntimeException('Invalid cookie: ' . $result); + } + $this->removeCookieIfEmpty($cookie); + return false; + } + + // Resolve conflicts with previously set cookies + 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() + ) { + 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()) { + unset($this->cookies[$i]); + continue; + } + + // The cookie exists, so no need to continue + return false; + } + + $this->cookies[] = $cookie; + + return true; + } + + public function count(): int + { + return \count($this->cookies); + } + + /** + * @return \ArrayIterator + */ + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator(\array_values($this->cookies)); + } + + public function extractCookies(RequestInterface $request, ResponseInterface $response): void + { + if ($cookieHeader = $response->getHeader('Set-Cookie')) { + foreach ($cookieHeader as $cookie) { + $sc = SetCookie::fromString($cookie); + if (!$sc->getDomain()) { + $sc->setDomain($request->getUri()->getHost()); + } + if (0 !== \strpos($sc->getPath(), '/')) { + $sc->setPath($this->getCookiePathFromRequest($request)); + } + if (!$sc->matchesDomain($request->getUri()->getHost())) { + 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); + } + } + } + + /** + * Computes cookie path following RFC 6265 section 5.1.4 + * + * @link https://tools.ietf.org/html/rfc6265#section-5.1.4 + */ + private function getCookiePathFromRequest(RequestInterface $request): string + { + $uriPath = $request->getUri()->getPath(); + if ('' === $uriPath) { + return '/'; + } + if (0 !== \strpos($uriPath, '/')) { + return '/'; + } + if ('/' === $uriPath) { + return '/'; + } + $lastSlashPos = \strrpos($uriPath, '/'); + if (0 === $lastSlashPos || false === $lastSlashPos) { + return '/'; + } + + return \substr($uriPath, 0, $lastSlashPos); + } + + public function withCookieHeader(RequestInterface $request): RequestInterface + { + $values = []; + $uri = $request->getUri(); + $scheme = $uri->getScheme(); + $host = $uri->getHost(); + $path = $uri->getPath() ?: '/'; + + foreach ($this->cookies as $cookie) { + if ($cookie->matchesPath($path) && + $cookie->matchesDomain($host) && + !$cookie->isExpired() && + (!$cookie->getSecure() || $scheme === 'https') + ) { + $values[] = $cookie->getName() . '=' + . $cookie->getValue(); + } + } + + return $values + ? $request->withHeader('Cookie', \implode('; ', $values)) + : $request; + } + + /** + * If a cookie already exists and the server asks to set it again with a + * null value, the cookie must be deleted. + */ + private function removeCookieIfEmpty(SetCookie $cookie): void + { + $cookieValue = $cookie->getValue(); + if ($cookieValue === null || $cookieValue === '') { + $this->clear( + $cookie->getDomain(), + $cookie->getPath(), + $cookie->getName() + ); + } + } +} diff --git a/lib/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php b/lib/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php new file mode 100644 index 0000000000..7df374b5be --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php @@ -0,0 +1,79 @@ + + */ +interface CookieJarInterface extends \Countable, \IteratorAggregate +{ + /** + * Create a request with added cookie headers. + * + * If no matching cookies are found in the cookie jar, then no Cookie + * header is added to the request and the same request is returned. + * + * @param RequestInterface $request Request object to modify. + * + * @return RequestInterface returns the modified request. + */ + public function withCookieHeader(RequestInterface $request): RequestInterface; + + /** + * Extract cookies from an HTTP response and store them in the CookieJar. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface $response Response that was received + */ + public function extractCookies(RequestInterface $request, ResponseInterface $response): void; + + /** + * Sets a cookie in the cookie jar. + * + * @param SetCookie $cookie Cookie to set. + * + * @return bool Returns true on success or false on failure + */ + public function setCookie(SetCookie $cookie): bool; + + /** + * Remove cookies currently held in the cookie jar. + * + * Invoking this method without arguments will empty the whole cookie jar. + * If given a $domain argument only cookies belonging to that domain will + * be removed. If given a $domain and $path argument, cookies belonging to + * the specified path within that domain are removed. If given all three + * arguments, then the cookie with the specified name, path and domain is + * removed. + * + * @param string|null $domain Clears cookies matching a domain + * @param string|null $path Clears cookies matching a domain and path + * @param string|null $name Clears cookies matching a domain, path, and name + */ + public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void; + + /** + * Discard all sessions cookies. + * + * Removes cookies that don't have an expire field or a have a discard + * field set to true. To be called when the user agent shuts down according + * to RFC 2965. + */ + public function clearSessionCookies(): void; + + /** + * Converts the cookie jar to an array. + */ + public function toArray(): array; +} diff --git a/lib/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php b/lib/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php new file mode 100644 index 0000000000..290236d543 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php @@ -0,0 +1,101 @@ +filename = $cookieFile; + $this->storeSessionCookies = $storeSessionCookies; + + if (\file_exists($cookieFile)) { + $this->load($cookieFile); + } + } + + /** + * Saves the file when shutting down + */ + public function __destruct() + { + $this->save($this->filename); + } + + /** + * Saves the cookies to a file. + * + * @param string $filename File to save + * + * @throws \RuntimeException if the file cannot be found or created + */ + public function save(string $filename): void + { + $json = []; + /** @var SetCookie $cookie */ + foreach ($this as $cookie) { + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $jsonStr = Utils::jsonEncode($json); + if (false === \file_put_contents($filename, $jsonStr, \LOCK_EX)) { + throw new \RuntimeException("Unable to save file {$filename}"); + } + } + + /** + * Load cookies from a JSON formatted file. + * + * Old cookies are kept unless overwritten by newly loaded ones. + * + * @param string $filename Cookie file to load. + * + * @throws \RuntimeException if the file cannot be loaded. + */ + public function load(string $filename): void + { + $json = \file_get_contents($filename); + if (false === $json) { + throw new \RuntimeException("Unable to load file {$filename}"); + } + if ($json === '') { + return; + } + + $data = Utils::jsonDecode($json, true); + if (\is_array($data)) { + foreach ($data as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (\is_scalar($data) && !empty($data)) { + throw new \RuntimeException("Invalid cookie file: {$filename}"); + } + } +} diff --git a/lib/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php b/lib/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php new file mode 100644 index 0000000000..5d51ca9820 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php @@ -0,0 +1,77 @@ +sessionKey = $sessionKey; + $this->storeSessionCookies = $storeSessionCookies; + $this->load(); + } + + /** + * Saves cookies to session when shutting down + */ + public function __destruct() + { + $this->save(); + } + + /** + * Save cookies to the client session + */ + public function save(): void + { + $json = []; + /** @var SetCookie $cookie */ + foreach ($this as $cookie) { + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + + $_SESSION[$this->sessionKey] = \json_encode($json); + } + + /** + * Load the contents of the client session into the data array + */ + 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)); + } + } elseif (\strlen($data)) { + throw new \RuntimeException("Invalid cookie data"); + } + } +} diff --git a/lib/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/lib/guzzlehttp/guzzle/src/Cookie/SetCookie.php new file mode 100644 index 0000000000..a613c77bf4 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Cookie/SetCookie.php @@ -0,0 +1,446 @@ + null, + 'Value' => null, + 'Domain' => null, + 'Path' => '/', + 'Max-Age' => null, + 'Expires' => null, + 'Secure' => false, + 'Discard' => false, + 'HttpOnly' => false + ]; + + /** + * @var array Cookie data + */ + private $data; + + /** + * Create a new SetCookie object from a string. + * + * @param string $cookie Set-Cookie header string + */ + public static function fromString(string $cookie): self + { + // Create the default return array + $data = self::$defaults; + // Explode the cookie string using a series of semicolons + $pieces = \array_filter(\array_map('trim', \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); + } + + // Add the cookie pieces into the parsed data array + foreach ($pieces as $part) { + $cookieParts = \explode('=', $part, 2); + $key = \trim($cookieParts[0]); + $value = isset($cookieParts[1]) + ? \trim($cookieParts[1], " \n\r\t\0\x0B") + : true; + + // Only check for non-cookies when cookies have been found + if (!isset($data['Name'])) { + $data['Name'] = $key; + $data['Value'] = $value; + } else { + foreach (\array_keys(self::$defaults) as $search) { + if (!\strcasecmp($search, $key)) { + $data[$search] = $value; + continue 2; + } + } + $data[$key] = $value; + } + } + + 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.'); + } + + $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); + } + } + + public function __toString() + { + $str = $this->data['Name'] . '=' . ($this->data['Value'] ?? '') . '; '; + foreach ($this->data as $k => $v) { + 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) . '; '; + } else { + $str .= ($v === true ? $k : "{$k}={$v}") . '; '; + } + } + } + + return \rtrim($str, '; '); + } + + public function toArray(): array + { + return $this->data; + } + + /** + * Get the cookie name. + * + * @return string + */ + public function getName() + { + return $this->data['Name']; + } + + /** + * Set the cookie name. + * + * @param string $name Cookie name + */ + public function setName($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; + } + + /** + * Get the cookie value. + * + * @return string|null + */ + public function getValue() + { + return $this->data['Value']; + } + + /** + * Set the cookie value. + * + * @param string $value Cookie value + */ + public function setValue($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; + } + + /** + * Get the domain. + * + * @return string|null + */ + public function getDomain() + { + return $this->data['Domain']; + } + + /** + * Set the domain of the cookie. + * + * @param string|null $domain + */ + public function setDomain($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 : (string) $domain; + } + + /** + * Get the path. + * + * @return string + */ + public function getPath() + { + return $this->data['Path']; + } + + /** + * Set the path of the cookie. + * + * @param string $path Path of the cookie + */ + public function setPath($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; + } + + /** + * Maximum lifetime of the cookie in seconds. + * + * @return int|null + */ + public function getMaxAge() + { + return null === $this->data['Max-Age'] ? null : (int) $this->data['Max-Age']; + } + + /** + * Set the max-age of the cookie. + * + * @param int|null $maxAge Max age of the cookie in seconds + */ + public function setMaxAge($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; + } + + /** + * The UNIX timestamp when the cookie Expires. + * + * @return string|int|null + */ + public function getExpires() + { + return $this->data['Expires']; + } + + /** + * Set the unix timestamp for which the cookie will expire. + * + * @param int|string|null $timestamp Unix timestamp or any English textual datetime description. + */ + 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__); + } + + $this->data['Expires'] = null === $timestamp ? null : (\is_numeric($timestamp) ? (int) $timestamp : \strtotime((string) $timestamp)); + } + + /** + * Get whether or not this is a secure cookie. + * + * @return bool + */ + public function getSecure() + { + return $this->data['Secure']; + } + + /** + * Set whether or not the cookie is secure. + * + * @param bool $secure Set to true or false if secure + */ + public function setSecure($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; + } + + /** + * Get whether or not this is a session cookie. + * + * @return bool|null + */ + public function getDiscard() + { + return $this->data['Discard']; + } + + /** + * Set whether or not this is a session cookie. + * + * @param bool $discard Set to true or false if this is a session cookie + */ + public function setDiscard($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; + } + + /** + * Get whether or not this is an HTTP only cookie. + * + * @return bool + */ + public function getHttpOnly() + { + return $this->data['HttpOnly']; + } + + /** + * Set whether or not this is an HTTP only cookie. + * + * @param bool $httpOnly Set to true or false if this is HTTP only + */ + public function setHttpOnly($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; + } + + /** + * Check if the cookie matches a path value. + * + * A request-path path-matches a given cookie-path if at least one of + * the following conditions holds: + * + * - The cookie-path and the request-path are identical. + * - The cookie-path is a prefix of the request-path, and the last + * character of the cookie-path is %x2F ("/"). + * - The cookie-path is a prefix of the request-path, and the first + * character of the request-path that is not included in the cookie- + * path is a %x2F ("/") character. + * + * @param string $requestPath Path to check against + */ + public function matchesPath(string $requestPath): bool + { + $cookiePath = $this->getPath(); + + // Match on exact matches or when path is the default empty "/" + if ($cookiePath === '/' || $cookiePath == $requestPath) { + return true; + } + + // Ensure that the cookie-path is a prefix of the request path. + if (0 !== \strpos($requestPath, $cookiePath)) { + return false; + } + + // Match if the last character of the cookie-path is "/" + if (\substr($cookiePath, -1, 1) === '/') { + return true; + } + + // Match if the first character not included in cookie path is "/" + return \substr($requestPath, \strlen($cookiePath), 1) === '/'; + } + + /** + * Check if the cookie matches a domain value. + * + * @param string $domain Domain to check against + */ + 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)) { + return false; + } + + return (bool) \preg_match('/\.' . \preg_quote($cookieDomain, '/') . '$/', $domain); + } + + /** + * Check if the cookie is expired. + */ + public function isExpired(): bool + { + return $this->getExpires() !== null && \time() > $this->getExpires(); + } + + /** + * Check if the cookie is valid according to RFC 6265. + * + * @return bool|string Returns true if valid or an error message if invalid + */ + public function validate() + { + $name = $this->getName(); + if ($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 + )) { + return 'Cookie name must not contain invalid characters: ASCII ' + . 'Control characters (0-31;127), space, tab and the ' + . 'following characters: ()<>@,;:\"/?={}'; + } + + // Value must not be null. 0 and empty string are valid. Empty strings + // are technically against RFC 6265, but known to happen in the wild. + $value = $this->getValue(); + if ($value === null) { + 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. + $domain = $this->getDomain(); + if ($domain === null || $domain === '') { + return 'The cookie domain must not be empty'; + } + + return true; + } +} diff --git a/lib/guzzlehttp/guzzle/src/Exception/BadResponseException.php b/lib/guzzlehttp/guzzle/src/Exception/BadResponseException.php new file mode 100644 index 0000000000..a80956c9d2 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Exception/BadResponseException.php @@ -0,0 +1,39 @@ +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/lib/guzzlehttp/guzzle/src/Exception/GuzzleException.php b/lib/guzzlehttp/guzzle/src/Exception/GuzzleException.php new file mode 100644 index 0000000000..fa3ed6998c --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Exception/GuzzleException.php @@ -0,0 +1,9 @@ +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); + } + + /** + * Factory method to create a new exception with a normalized error message + * + * @param RequestInterface $request Request sent + * @param ResponseInterface $response Response received + * @param \Throwable|null $previous Previous exception + * @param array $handlerContext Optional handler context + * @param BodySummarizerInterface|null $bodySummarizer Optional body summarizer + */ + public static function create( + RequestInterface $request, + ResponseInterface $response = null, + \Throwable $previous = null, + array $handlerContext = [], + BodySummarizerInterface $bodySummarizer = null + ): self { + if (!$response) { + return new self( + 'Error completing request', + $request, + null, + $previous, + $handlerContext + ); + } + + $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); + + // 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(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + + $summary = ($bodySummarizer ?? new BodySummarizer())->summarize($response); + + if ($summary !== null) { + $message .= ":\n{$summary}\n"; + } + + 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), '***'); + } + + return $uri; + } + + /** + * 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; + } +} diff --git a/lib/guzzlehttp/guzzle/src/Exception/ServerException.php b/lib/guzzlehttp/guzzle/src/Exception/ServerException.php new file mode 100644 index 0000000000..8055e067c0 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Exception/ServerException.php @@ -0,0 +1,10 @@ +maxHandles = $maxHandles; + } + + public function create(RequestInterface $request, array $options): EasyHandle + { + 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; + $easy->request = $request; + $easy->options = $options; + $conf = $this->getDefaultConf($easy); + $this->applyMethod($easy, $conf); + $this->applyHandlerOptions($easy, $conf); + $this->applyHeaders($easy, $conf); + unset($conf['_headers']); + + // Add handler options from the request configuration options + if (isset($options['curl'])) { + $conf = \array_replace($conf, $options['curl']); + } + + $conf[\CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy); + $easy->handle = $this->handles ? \array_pop($this->handles) : \curl_init(); + curl_setopt_array($easy->handle, $conf); + + return $easy; + } + + public function release(EasyHandle $easy): void + { + $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; + } + } + + /** + * 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 + */ + public static function finish(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): PromiseInterface + { + if (isset($easy->options['on_stats'])) { + self::invokeStats($easy); + } + + if (!$easy->response || $easy->errno) { + return self::finishError($handler, $easy, $factory); + } + + // 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(); + } + + return new FulfilledPromise($easy->response); + } + + private static function invokeStats(EasyHandle $easy): void + { + $curlStats = \curl_getinfo($easy->handle); + $curlStats['appconnect_time'] = \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME); + $stats = new TransferStats( + $easy->request, + $easy->response, + $curlStats['total_time'], + $easy->errno, + $curlStats + ); + ($easy->options['on_stats'])($stats); + } + + /** + * @param callable(RequestInterface, array): PromiseInterface $handler + */ + private static function finishError(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory): 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']; + $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); + } + + return self::createRejection($easy, $ctx); + } + + private static function createRejection(EasyHandle $easy, array $ctx): PromiseInterface + { + static $connectionErrors = [ + \CURLE_OPERATION_TIMEOUTED => true, + \CURLE_COULDNT_RESOLVE_HOST => true, + \CURLE_COULDNT_CONNECT => true, + \CURLE_SSL_CONNECT_ERROR => true, + \CURLE_GOT_NOTHING => true, + ]; + + if ($easy->createResponseException) { + return P\Create::rejectionFor( + new RequestException( + 'An error was encountered while creating the response', + $easy->request, + $easy->response, + $easy->createResponseException, + $ctx + ) + ); + } + + // 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 + ) + ); + } + + $message = \sprintf( + 'cURL error %s: %s (%s)', + $ctx['errno'], + $ctx['error'], + 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html' + ); + $uriString = (string) $easy->request->getUri(); + if ($uriString !== '' && false === \strpos($ctx['error'], $uriString)) { + $message .= \sprintf(' for %s', $uriString); + } + + // 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); + + return P\Create::rejectionFor($error); + } + + /** + * @return array + */ + private function getDefaultConf(EasyHandle $easy): array + { + $conf = [ + '_headers' => $easy->request->getHeaders(), + \CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(), + \CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''), + \CURLOPT_RETURNTRANSFER => false, + \CURLOPT_HEADER => false, + \CURLOPT_CONNECTTIMEOUT => 150, + ]; + + if (\defined('CURLOPT_PROTOCOLS')) { + $conf[\CURLOPT_PROTOCOLS] = \CURLPROTO_HTTP | \CURLPROTO_HTTPS; + } + + $version = $easy->request->getProtocolVersion(); + if ($version == 1.1) { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1; + } elseif ($version == 2.0) { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0; + } else { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0; + } + + return $conf; + } + + private function applyMethod(EasyHandle $easy, array &$conf): void + { + $body = $easy->request->getBody(); + $size = $body->getSize(); + + if ($size === null || $size > 0) { + $this->applyBody($easy->request, $easy->options, $conf); + return; + } + + $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') { + $conf[\CURLOPT_NOBODY] = true; + unset( + $conf[\CURLOPT_WRITEFUNCTION], + $conf[\CURLOPT_READFUNCTION], + $conf[\CURLOPT_FILE], + $conf[\CURLOPT_INFILE] + ); + } + } + + private function applyBody(RequestInterface $request, array $options, array &$conf): void + { + $size = $request->hasHeader('Content-Length') + ? (int) $request->getHeaderLine('Content-Length') + : null; + + // 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(); + // 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; + $this->removeHeader('Content-Length', $conf); + } + $body = $request->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + $conf[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use ($body) { + return $body->read($length); + }; + } + + // If the Expect header is not present, prevent curl from adding it + if (!$request->hasHeader('Expect')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Expect:'; + } + + // cURL sometimes adds a content-type by default. Prevent this. + if (!$request->hasHeader('Content-Type')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Content-Type:'; + } + } + + private function applyHeaders(EasyHandle $easy, array &$conf): void + { + foreach ($conf['_headers'] as $name => $values) { + foreach ($values as $value) { + $value = (string) $value; + if ($value === '') { + // cURL requires a special format for empty headers. + // See https://github.com/guzzle/guzzle/issues/1882 for more details. + $conf[\CURLOPT_HTTPHEADER][] = "$name;"; + } else { + $conf[\CURLOPT_HTTPHEADER][] = "$name: $value"; + } + } + } + + // Remove the Accept header if one was not set + if (!$easy->request->hasHeader('Accept')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Accept:'; + } + } + + /** + * Remove a header from the options array. + * + * @param string $name Case-insensitive header to remove + * @param array $options Array of options to modify + */ + private function removeHeader(string $name, array &$options): void + { + foreach (\array_keys($options['_headers']) as $key) { + if (!\strcasecmp($key, $name)) { + unset($options['_headers'][$key]); + return; + } + } + } + + private function applyHandlerOptions(EasyHandle $easy, array &$conf): void + { + $options = $easy->options; + if (isset($options['verify'])) { + if ($options['verify'] === false) { + unset($conf[\CURLOPT_CAINFO]); + $conf[\CURLOPT_SSL_VERIFYHOST] = 0; + $conf[\CURLOPT_SSL_VERIFYPEER] = false; + } else { + $conf[\CURLOPT_SSL_VERIFYHOST] = 2; + $conf[\CURLOPT_SSL_VERIFYPEER] = true; + 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']}"); + } + // 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) + ) + ) { + $conf[\CURLOPT_CAPATH] = $options['verify']; + } else { + $conf[\CURLOPT_CAINFO] = $options['verify']; + } + } + } + } + + if (!isset($options['curl'][\CURLOPT_ENCODING]) && !empty($options['decode_content'])) { + $accept = $easy->request->getHeaderLine('Accept-Encoding'); + 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. + $conf[\CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; + } + } + + if (!isset($options['sink'])) { + // Use a default temp stream if no sink was set. + $options['sink'] = \GuzzleHttp\Psr7\Utils::tryFopen('php://temp', 'w+'); + } + $sink = $options['sink']; + if (!\is_string($sink)) { + $sink = \GuzzleHttp\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)); + } else { + $sink = new LazyOpenStream($sink, 'w+'); + } + $easy->sink = $sink; + $conf[\CURLOPT_WRITEFUNCTION] = static function ($ch, $write) use ($sink): int { + return $sink->write($write); + }; + + $timeoutRequiresNoSignal = false; + if (isset($options['timeout'])) { + $timeoutRequiresNoSignal |= $options['timeout'] < 1; + $conf[\CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000; + } + + // CURL default value is CURL_IPRESOLVE_WHATEVER + if (isset($options['force_ip_resolve'])) { + if ('v4' === $options['force_ip_resolve']) { + $conf[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V4; + } elseif ('v6' === $options['force_ip_resolve']) { + $conf[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V6; + } + } + + if (isset($options['connect_timeout'])) { + $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1; + $conf[\CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000; + } + + if ($timeoutRequiresNoSignal && \strtoupper(\substr(\PHP_OS, 0, 3)) !== 'WIN') { + $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]; + } + } + } + } + + if (isset($options['cert'])) { + $cert = $options['cert']; + if (\is_array($cert)) { + $conf[\CURLOPT_SSLCERTPASSWD] = $cert[1]; + $cert = $cert[0]; + } + if (!\file_exists($cert)) { + throw new \InvalidArgumentException("SSL certificate not found: {$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); + } + $conf[\CURLOPT_SSLCERT] = $cert; + } + + 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']; + } + } + + $sslKey = $sslKey ?? $options['ssl_key']; + + if (!\file_exists($sslKey)) { + throw new \InvalidArgumentException("SSL private key not found: {$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'); + } + $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); + }; + } + + if (!empty($options['debug'])) { + $conf[\CURLOPT_STDERR] = Utils::debugResource($options['debug']); + $conf[\CURLOPT_VERBOSE] = true; + } + } + + /** + * 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 + * typically means you are sending a payload, curl encountered a + * "Connection died, retrying a fresh connect" error, tried to rewind the + * stream, and then encountered a "necessary data rewind wasn't possible" + * error, causing the request to be sent through curl_multi_info_read() + * without an error status. + * + * @param callable(RequestInterface, array): PromiseInterface $handler + */ + private static function retryFailedRewind(callable $handler, EasyHandle $easy, 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); + } + + // Retry no more than 3 times before giving up. + if (!isset($easy->options['_curl_retries'])) { + $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.'; + return self::createRejection($easy, $ctx); + } else { + $easy->options['_curl_retries']++; + } + + return $handler($easy->request, $easy->options); + } + + private function createHeaderFn(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'); + } + } else { + $onHeaders = null; + } + + return static function ($ch, $h) use ( + $onHeaders, + $easy, + &$startingResponse + ) { + $value = \trim($h); + if ($value === '') { + $startingResponse = true; + try { + $easy->createResponse(); + } catch (\Exception $e) { + $easy->createResponseException = $e; + return -1; + } + if ($onHeaders !== null) { + try { + $onHeaders($easy->response); + } catch (\Exception $e) { + // Associate the exception with the handle and trigger + // a curl header write error by returning 0. + $easy->onHeadersException = $e; + return -1; + } + } + } elseif ($startingResponse) { + $startingResponse = false; + $easy->headers = [$value]; + } else { + $easy->headers[] = $value; + } + return \strlen($h); + }; + } +} diff --git a/lib/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php b/lib/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php new file mode 100644 index 0000000000..fe57ed5d57 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php @@ -0,0 +1,25 @@ +factory = $options['handle_factory'] + ?? new CurlFactory(3); + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + if (isset($options['delay'])) { + \usleep($options['delay'] * 1000); + } + + $easy = $this->factory->create($request, $options); + \curl_exec($easy->handle); + $easy->errno = \curl_errno($easy->handle); + + return CurlFactory::finish($this, $easy, $this->factory); + } +} diff --git a/lib/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/lib/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php new file mode 100644 index 0000000000..2f5b3f69b3 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php @@ -0,0 +1,261 @@ + An array of delay times, indexed by handle id in `addRequest`. + * + * @see CurlMultiHandler::addRequest + */ + private $delays = []; + + /** + * @var array An associative array of CURLMOPT_* options and corresponding values for curl_multi_setopt() + */ + private $options = []; + + /** + * This handler accepts the following options: + * + * - handle_factory: An optional factory used to create curl handles + * - select_timeout: Optional timeout (in seconds) to block before timing + * out while selecting curl handles. Defaults to 1 second. + * - 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); + + 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; + } else { + $this->selectTimeout = 1; + } + + $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); + } + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + $easy = $this->factory->create($request, $options); + $id = (int) $easy->handle; + + $promise = new Promise( + [$this, 'execute'], + function () use ($id) { + return $this->cancel($id); + } + ); + + $this->addRequest(['easy' => $easy, 'deferred' => $promise]); + + return $promise; + } + + /** + * Ticks the curl event loop. + */ + public function tick(): void + { + // Add any delayed handles if needed. + if ($this->delays) { + $currentTime = Utils::currentTime(); + foreach ($this->delays as $id => $delay) { + if ($currentTime >= $delay) { + unset($this->delays[$id]); + \curl_multi_add_handle( + $this->_mh, + $this->handles[$id]['easy']->handle + ); + } + } + } + + // Step through the task queue which may add additional requests. + P\Utils::queue()->run(); + + if ($this->active && \curl_multi_select($this->_mh, $this->selectTimeout) === -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); + + $this->processMessages(); + } + + /** + * Runs until all outstanding connections have completed. + */ + public function execute(): void + { + $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()); + } + $this->tick(); + } + } + + private function addRequest(array $entry): void + { + $easy = $entry['easy']; + $id = (int) $easy->handle; + $this->handles[$id] = $entry; + if (empty($easy->options['delay'])) { + \curl_multi_add_handle($this->_mh, $easy->handle); + } else { + $this->delays[$id] = Utils::currentTime() + ($easy->options['delay'] / 1000); + } + } + + /** + * Cancels a handle from sending and removes references to it. + * + * @param int $id Handle ID to cancel and remove. + * + * @return bool True on success, false on failure. + */ + private function cancel($id): 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])) { + 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); + + return true; + } + + private function processMessages(): 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 (!isset($this->handles[$id])) { + // Probably was cancelled. + continue; + } + + $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) + ); + } + } + + private function timeToNext(): int + { + $currentTime = Utils::currentTime(); + $nextTime = \PHP_INT_MAX; + foreach ($this->delays as $time) { + if ($time < $nextTime) { + $nextTime = $time; + } + } + + return ((int) \max(0, $nextTime - $currentTime)) * 1000000; + } +} diff --git a/lib/guzzlehttp/guzzle/src/Handler/EasyHandle.php b/lib/guzzlehttp/guzzle/src/Handler/EasyHandle.php new file mode 100644 index 0000000000..224344d7c8 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Handler/EasyHandle.php @@ -0,0 +1,112 @@ +headers); + + $normalizedKeys = Utils::normalizeHeaderKeys($headers); + + if (!empty($this->options['decode_content']) && 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']]; + + $bodyLength = (int) $this->sink->getSize(); + if ($bodyLength) { + $headers[$normalizedKeys['content-length']] = $bodyLength; + } else { + unset($headers[$normalizedKeys['content-length']]); + } + } + } + + // Attach a response to the easy handle with the parsed headers. + $this->response = new Response( + $status, + $headers, + $this->sink, + $ver, + $reason + ); + } + + /** + * @param string $name + * + * @return void + * + * @throws \BadMethodCallException + */ + public function __get($name) + { + $msg = $name === 'handle' ? 'The EasyHandle has been released' : 'Invalid property: ' . $name; + throw new \BadMethodCallException($msg); + } +} diff --git a/lib/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php b/lib/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php new file mode 100644 index 0000000000..a0988845fd --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php @@ -0,0 +1,42 @@ +|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. + */ + public function __construct(array $queue = null, callable $onFulfilled = null, callable $onRejected = null) + { + $this->onFulfilled = $onFulfilled; + $this->onRejected = $onRejected; + + if ($queue) { + // array_values included for BC + $this->append(...array_values($queue)); + } + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + if (!$this->queue) { + throw new \OutOfBoundsException('Mock queue is empty'); + } + + if (isset($options['delay']) && \is_numeric($options['delay'])) { + \usleep((int) $options['delay'] * 1000); + } + + $this->lastRequest = $request; + $this->lastOptions = $options; + $response = \array_shift($this->queue); + + 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); + } + } + + if (\is_callable($response)) { + $response = $response($request, $options); + } + + $response = $response instanceof \Throwable + ? P\Create::rejectionFor($response) + : P\Create::promiseFor($response); + + return $response->then( + function (?ResponseInterface $value) use ($request, $options) { + $this->invokeStats($request, $options, $value); + if ($this->onFulfilled) { + ($this->onFulfilled)($value); + } + + if ($value !== null && isset($options['sink'])) { + $contents = (string) $value->getBody(); + $sink = $options['sink']; + + if (\is_resource($sink)) { + \fwrite($sink, $contents); + } elseif (\is_string($sink)) { + \file_put_contents($sink, $contents); + } elseif ($sink instanceof StreamInterface) { + $sink->write($contents); + } + } + + return $value; + }, + function ($reason) use ($request, $options) { + $this->invokeStats($request, $options, null, $reason); + if ($this->onRejected) { + ($this->onRejected)($reason); + } + return P\Create::rejectionFor($reason); + } + ); + } + + /** + * Adds one or more variadic requests, exceptions, callables, or promises + * to the queue. + * + * @param mixed ...$values + */ + public function append(...$values): void + { + foreach ($values as $value) { + if ($value instanceof ResponseInterface + || $value instanceof \Throwable + || $value instanceof PromiseInterface + || \is_callable($value) + ) { + $this->queue[] = $value; + } else { + throw new \TypeError('Expected a Response, Promise, Throwable or callable. Found ' . Utils::describeType($value)); + } + } + } + + /** + * Get the last received request. + */ + public function getLastRequest(): ?RequestInterface + { + return $this->lastRequest; + } + + /** + * Get the last received request options. + */ + public function getLastOptions(): array + { + return $this->lastOptions; + } + + /** + * Returns the number of remaining items in the queue. + */ + public function count(): int + { + return \count($this->queue); + } + + public function reset(): void + { + $this->queue = []; + } + + /** + * @param mixed $reason Promise or reason. + */ + private function invokeStats( + RequestInterface $request, + array $options, + ResponseInterface $response = null, + $reason = null + ): void { + if (isset($options['on_stats'])) { + $transferTime = $options['transfer_time'] ?? 0; + $stats = new TransferStats($request, $response, $transferTime, $reason); + ($options['on_stats'])($stats); + } + } +} diff --git a/lib/guzzlehttp/guzzle/src/Handler/Proxy.php b/lib/guzzlehttp/guzzle/src/Handler/Proxy.php new file mode 100644 index 0000000000..f045b526c5 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Handler/Proxy.php @@ -0,0 +1,51 @@ +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'); + } + + return $this->createResponse( + $request, + $options, + $this->createStream($request, $options), + $startTime + ); + } 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); + } + $this->invokeStats($options, $request, $startTime, null, $e); + + return P\Create::rejectionFor($e); + } + } + + private function invokeStats( + array $options, + RequestInterface $request, + ?float $startTime, + ResponseInterface $response = null, + \Throwable $error = null + ): void { + if (isset($options['on_stats'])) { + $stats = new TransferStats($request, $response, Utils::currentTime() - $startTime, $error, []); + ($options['on_stats'])($stats); + } + } + + /** + * @param resource $stream + */ + private function createResponse(RequestInterface $request, array $options, $stream, ?float $startTime): PromiseInterface + { + $hdrs = $this->lastHeaders; + $this->lastHeaders = []; + + 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) + ); + } + + [$stream, $headers] = $this->checkDecode($options, $headers, $stream); + $stream = Psr7\Utils::streamFor($stream); + $sink = $stream; + + if (\strcasecmp('HEAD', $request->getMethod())) { + $sink = $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) + ); + } + + 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) + ); + } + } + + // 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')); + } + + $this->invokeStats($options, $request, $startTime, $response, null); + + return new FulfilledPromise($response); + } + + private function createSink(StreamInterface $stream, array $options): StreamInterface + { + if (!empty($options['stream'])) { + return $stream; + } + + $sink = $options['sink'] ?? Psr7\Utils::tryFopen('php://temp', 'r+'); + + return \is_string($sink) ? new Psr7\LazyOpenStream($sink, 'w+') : Psr7\Utils::streamFor($sink); + } + + /** + * @param resource $stream + */ + private function checkDecode(array $options, array $headers, $stream): array + { + // Automatically decode responses when instructed. + if (!empty($options['decode_content'])) { + $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)); + $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]; + } + } + } + } + } + + return [$stream, $headers]; + } + + /** + * 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 + ); + + $sink->seek(0); + $source->close(); + + return $sink; + } + + /** + * Create a resource and check to ensure it was created successfully + * + * @param callable $callback Callable that returns stream resource + * + * @return resource + * + * @throws \RuntimeException on error + */ + private function createResource(callable $callback) + { + $errors = []; + \set_error_handler(static function ($_, $msg, $file, $line) use (&$errors): bool { + $errors[] = [ + 'message' => $msg, + 'file' => $file, + 'line' => $line + ]; + return true; + }); + + try { + $resource = $callback(); + } finally { + \restore_error_handler(); + } + + if (!$resource) { + $message = 'Error creating resource: '; + foreach ($errors as $err) { + foreach ($err as $key => $value) { + $message .= "[$key] $value" . \PHP_EOL; + } + } + throw new \RuntimeException(\trim($message)); + } + + return $resource; + } + + /** + * @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); + } + + // HTTP/1.1 streams using the PHP stream wrapper require a + // Connection: close header + if ($request->getProtocolVersion() == '1.1' + && !$request->hasHeader('Connection') + ) { + $request = $request->withHeader('Connection', 'close'); + } + + // Ensure SSL is verified by default + if (!isset($options['verify'])) { + $options['verify'] = true; + } + + $params = []; + $context = $this->getDefaultContext($request); + + if (isset($options['on_headers']) && !\is_callable($options['on_headers'])) { + 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); + } + } + } + + if (isset($options['stream_context'])) { + if (!\is_array($options['stream_context'])) { + throw new \InvalidArgumentException('stream_context must be an array'); + } + $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'); + } + + $uri = $this->resolveHost($request, $options); + + $contextResource = $this->createResource( + static function () use ($context, $params) { + return \stream_context_create($context, $params); + } + ); + + 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 ?? []; + + if (false === $resource) { + throw new ConnectException(sprintf('Connection refused for URI %s', $uri), $request, null, $context); + } + + if (isset($options['read_timeout'])) { + $readTimeout = $options['read_timeout']; + $sec = (int) $readTimeout; + $usec = ($readTimeout - $sec) * 100000; + \stream_set_timeout($resource, $sec, $usec); + } + + return $resource; + } + ); + } + + private function resolveHost(RequestInterface $request, array $options): UriInterface + { + $uri = $request->getUri(); + + if (isset($options['force_ip_resolve']) && !\filter_var($uri->getHost(), \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); + } + 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); + } + return $uri->withHost('[' . $records[0]['ipv6'] . ']'); + } + } + + return $uri; + } + + private function getDefaultContext(RequestInterface $request): array + { + $headers = ''; + foreach ($request->getHeaders() as $name => $value) { + foreach ($value as $val) { + $headers .= "$name: $val\r\n"; + } + } + + $context = [ + 'http' => [ + 'method' => $request->getMethod(), + 'header' => $headers, + 'protocol_version' => $request->getProtocolVersion(), + 'ignore_errors' => true, + 'follow_location' => 0, + ], + 'ssl' => [ + 'peer_name' => $request->getUri()->getHost(), + ], + ]; + + $body = (string) $request->getBody(); + + if (!empty($body)) { + $context['http']['content'] = $body; + // Prevent the HTTP handler from adding a Content-Type header. + if (!$request->hasHeader('Content-Type')) { + $context['http']['header'] .= "Content-Type:\r\n"; + } + } + + $context['http']['header'] = \rtrim($context['http']['header']); + + return $context; + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_proxy(RequestInterface $request, array &$options, $value, array &$params): 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 (!$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']}"; + } + } + + /** + * Parses the given proxy URL to make it compatible with the format PHP's stream context expects. + */ + private function parse_proxy(string $url): 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, + ]; + } + } + + // Return proxy as-is. + return [ + 'proxy' => $url, + 'auth' => null, + ]; + } + + /** + * @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; + } + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_verify(RequestInterface $request, array &$options, $value, array &$params): void + { + if ($value === false) { + $options['ssl']['verify_peer'] = false; + $options['ssl']['verify_peer_name'] = false; + + return; + } + + if (\is_string($value)) { + $options['ssl']['cafile'] = $value; + if (!\file_exists($value)) { + throw new \RuntimeException("SSL CA bundle not found: $value"); + } + } elseif ($value !== true) { + throw new \InvalidArgumentException('Invalid verify request option'); + } + + $options['ssl']['verify_peer'] = true; + $options['ssl']['verify_peer_name'] = true; + $options['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]; + } + + if (!\file_exists($value)) { + throw new \RuntimeException("SSL certificate not found: {$value}"); + } + + $options['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 + { + self::addNotification( + $params, + static function ($code, $a, $b, $c, $transferred, $total) use ($value) { + 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); + } + } + ); + } + + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_debug(RequestInterface $request, array &$options, $value, array &$params): void + { + if ($value === false) { + return; + } + + static $map = [ + \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', + ]; + static $args = ['severity', 'message', 'message_code', 'bytes_transferred', 'bytes_max']; + + $value = Utils::debugResource($value); + $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, "\n"); + } + ); + } + + private static function addNotification(array &$params, callable $notify): void + { + // Wrap the existing function if needed. + if (!isset($params['notification'])) { + $params['notification'] = $notify; + } else { + $params['notification'] = self::callArray([ + $params['notification'], + $notify + ]); + } + } + + private static function callArray(array $functions): callable + { + return static function (...$args) use ($functions) { + foreach ($functions as $fn) { + $fn(...$args); + } + }; + } +} diff --git a/lib/guzzlehttp/guzzle/src/HandlerStack.php b/lib/guzzlehttp/guzzle/src/HandlerStack.php new file mode 100644 index 0000000000..e0a1d11913 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/HandlerStack.php @@ -0,0 +1,275 @@ +push(Middleware::httpErrors(), 'http_errors'); + $stack->push(Middleware::redirect(), 'allow_redirects'); + $stack->push(Middleware::cookies(), 'cookies'); + $stack->push(Middleware::prepareBody(), 'prepare_body'); + + return $stack; + } + + /** + * @param (callable(RequestInterface, array): PromiseInterface)|null $handler Underlying HTTP handler. + */ + public function __construct(callable $handler = null) + { + $this->handler = $handler; + } + + /** + * Invokes the handler stack as a composed handler + * + * @return ResponseInterface|PromiseInterface + */ + public function __invoke(RequestInterface $request, 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. + */ + public function setHandler(callable $handler): void + { + $this->handler = $handler; + $this->cached = null; + } + + /** + * Returns true if the builder has a handler. + */ + public function hasHandler(): bool + { + 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. + */ + public function unshift(callable $middleware, ?string $name = null): void + { + \array_unshift($this->stack, [$middleware, $name]); + $this->cached = null; + } + + /** + * 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. + */ + public function push(callable $middleware, string $name = ''): void + { + $this->stack[] = [$middleware, $name]; + $this->cached = null; + } + + /** + * 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. + */ + public function before(string $findName, callable $middleware, string $withName = ''): void + { + $this->splice($findName, $withName, $middleware, true); + } + + /** + * 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. + */ + public function after(string $findName, callable $middleware, string $withName = ''): void + { + $this->splice($findName, $withName, $middleware, false); + } + + /** + * Remove a middleware by instance or name from the stack. + * + * @param callable|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__); + } + + $this->cached = null; + $idx = \is_callable($remove) ? 0 : 1; + $this->stack = \array_values(\array_filter( + $this->stack, + static function ($tuple) use ($idx, $remove) { + return $tuple[$idx] !== $remove; + } + )); + } + + /** + * Compose the middleware and handler into a single callable function. + * + * @return callable(RequestInterface, array): PromiseInterface + */ + public function resolve(): callable + { + if ($this->cached === null) { + if (($prev = $this->handler) === null) { + throw new \LogicException('No handler has been specified'); + } + + foreach (\array_reverse($this->stack) as $fn) { + /** @var callable(RequestInterface, array): PromiseInterface $prev */ + $prev = $fn[0]($prev); + } + + $this->cached = $prev; + } + + return $this->cached; + } + + private function findByName(string $name): int + { + foreach ($this->stack as $k => $v) { + if ($v[1] === $name) { + return $k; + } + } + + throw new \InvalidArgumentException("Middleware not found: $name"); + } + + /** + * Splices a function into the middleware list at a specific position. + */ + private function splice(string $findName, string $withName, callable $middleware, bool $before): void + { + $this->cached = null; + $idx = $this->findByName($findName); + $tuple = [$middleware, $withName]; + + if ($before) { + if ($idx === 0) { + \array_unshift($this->stack, $tuple); + } else { + $replacement = [$tuple, $this->stack[$idx]]; + \array_splice($this->stack, $idx, 1, $replacement); + } + } elseif ($idx === \count($this->stack) - 1) { + $this->stack[] = $tuple; + } else { + $replacement = [$this->stack[$idx], $tuple]; + \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/lib/guzzlehttp/guzzle/src/MessageFormatter.php b/lib/guzzlehttp/guzzle/src/MessageFormatter.php new file mode 100644 index 0000000000..da499547f1 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/MessageFormatter.php @@ -0,0 +1,198 @@ +>>>>>>>\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; + + /** + * @param string $template Log message template + */ + public function __construct(?string $template = self::CLF) + { + $this->template = $template ?: self::CLF; + } + + /** + * Returns a formatted message string. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface|null $response Response that was received + * @param \Throwable|null $error Exception that was received + */ + public function format(RequestInterface $request, ?ResponseInterface $response = null, ?\Throwable $error = null): string + { + $cache = []; + + /** @var string */ + return \preg_replace_callback( + '/{\s*([A-Za-z_\-\.0-9]+)\s*}/', + function (array $matches) use ($request, $response, $error, &$cache) { + if (isset($cache[$matches[1]])) { + return $cache[$matches[1]]; + } + + $result = ''; + switch ($matches[1]) { + case 'request': + $result = Psr7\Message::toString($request); + break; + case 'response': + $result = $response ? Psr7\Message::toString($response) : ''; + break; + case 'req_headers': + $result = \trim($request->getMethod() + . ' ' . $request->getRequestTarget()) + . ' HTTP/' . $request->getProtocolVersion() . "\r\n" + . $this->headers($request); + break; + case 'res_headers': + $result = $response ? + \sprintf( + 'HTTP/%s %d %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ) . "\r\n" . $this->headers($response) + : 'NULL'; + break; + case 'req_body': + $result = $request->getBody()->__toString(); + break; + case 'res_body': + if (!$response instanceof ResponseInterface) { + $result = 'NULL'; + break; + } + + $body = $response->getBody(); + + if (!$body->isSeekable()) { + $result = 'RESPONSE_NOT_LOGGEABLE'; + break; + } + + $result = $response->getBody()->__toString(); + break; + case 'ts': + case 'date_iso_8601': + $result = \gmdate('c'); + break; + case 'date_common_log': + $result = \date('d/M/Y:H:i:s O'); + break; + case 'method': + $result = $request->getMethod(); + break; + case 'version': + $result = $request->getProtocolVersion(); + break; + case 'uri': + case 'url': + $result = $request->getUri()->__toString(); + break; + case 'target': + $result = $request->getRequestTarget(); + break; + case 'req_version': + $result = $request->getProtocolVersion(); + break; + case 'res_version': + $result = $response + ? $response->getProtocolVersion() + : 'NULL'; + break; + case 'host': + $result = $request->getHeaderLine('Host'); + break; + case 'hostname': + $result = \gethostname(); + break; + case 'code': + $result = $response ? $response->getStatusCode() : 'NULL'; + break; + case 'phrase': + $result = $response ? $response->getReasonPhrase() : 'NULL'; + break; + case 'error': + $result = $error ? $error->getMessage() : 'NULL'; + break; + default: + // handle prefixed dynamic headers + if (\strpos($matches[1], 'req_header_') === 0) { + $result = $request->getHeaderLine(\substr($matches[1], 11)); + } elseif (\strpos($matches[1], 'res_header_') === 0) { + $result = $response + ? $response->getHeaderLine(\substr($matches[1], 11)) + : 'NULL'; + } + } + + $cache[$matches[1]] = $result; + return $result; + }, + $this->template + ); + } + + /** + * Get headers from message as string + */ + private function headers(MessageInterface $message): string + { + $result = ''; + foreach ($message->getHeaders() as $name => $values) { + $result .= $name . ': ' . \implode(', ', $values) . "\r\n"; + } + + return \trim($result); + } +} diff --git a/lib/guzzlehttp/guzzle/src/MessageFormatterInterface.php b/lib/guzzlehttp/guzzle/src/MessageFormatterInterface.php new file mode 100644 index 0000000000..a39ac248ee --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/MessageFormatterInterface.php @@ -0,0 +1,18 @@ +withCookieHeader($request); + return $handler($request, $options) + ->then( + static function (ResponseInterface $response) use ($cookieJar, $request): ResponseInterface { + $cookieJar->extractCookies($request, $response); + return $response; + } + ); + }; + }; + } + + /** + * Middleware that throws exceptions for 4xx or 5xx responses when the + * "http_errors" request option is set to true. + * + * @param BodySummarizerInterface|null $bodySummarizer The body summarizer to use in exception messages. + * + * @return callable(callable): callable Returns a function that accepts the next handler. + */ + 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) { + if (empty($options['http_errors'])) { + return $handler($request, $options); + } + return $handler($request, $options)->then( + static function (ResponseInterface $response) use ($request, $bodySummarizer) { + $code = $response->getStatusCode(); + if ($code < 400) { + return $response; + } + throw RequestException::create($request, $response, null, [], $bodySummarizer); + } + ); + }; + }; + } + + /** + * Middleware that pushes history data to an ArrayAccess container. + * + * @param array|\ArrayAccess $container Container to hold the history (by reference). + * + * @return callable(callable): callable Returns a function that accepts the next handler. + * + * @throws \InvalidArgumentException if container is not an array or ArrayAccess. + */ + public static function history(&$container): callable + { + if (!\is_array($container) && !$container instanceof \ArrayAccess) { + throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess'); + } + + return static function (callable $handler) use (&$container): callable { + return static function (RequestInterface $request, array $options) use ($handler, &$container) { + return $handler($request, $options)->then( + static function ($value) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => $value, + 'error' => null, + 'options' => $options + ]; + return $value; + }, + static function ($reason) use ($request, &$container, $options) { + $container[] = [ + 'request' => $request, + 'response' => null, + 'error' => $reason, + 'options' => $options + ]; + return P\Create::rejectionFor($reason); + } + ); + }; + }; + } + + /** + * Middleware that invokes a callback before and after sending a request. + * + * 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. + * + * @return callable Returns a function that accepts the next handler. + */ + 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) { + if ($before) { + $before($request, $options); + } + $response = $handler($request, $options); + if ($after) { + $after($request, $options, $response); + } + return $response; + }; + }; + } + + /** + * Middleware that handles request redirects. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function redirect(): callable + { + return static function (callable $handler): RedirectMiddleware { + return new RedirectMiddleware($handler); + }; + } + + /** + * Middleware that retries requests based on the boolean result of + * invoking the provided "decider" function. + * + * 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. + * + * @return callable Returns a function that accepts the next handler. + */ + 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); + }; + } + + /** + * 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|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. + */ + public static function log(LoggerInterface $logger, $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 $handler($request, $options)->then( + static function ($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; + $message = $formatter->format($request, $response, P\Create::exceptionFor($reason)); + $logger->error($message); + return P\Create::rejectionFor($reason); + } + ); + }; + }; + } + + /** + * This middleware adds a default content-type if possible, a default + * content-length or transfer-encoding header, and the expect header. + */ + public static function prepareBody(): callable + { + return static function (callable $handler): PrepareBodyMiddleware { + return new PrepareBodyMiddleware($handler); + }; + } + + /** + * 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. + */ + 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 $handler($fn($request), $options); + }; + }; + } + + /** + * Middleware that applies a map function to the resolved promise's + * response. + * + * @param callable $fn Function that accepts a ResponseInterface and + * returns a ResponseInterface. + */ + 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 $handler($request, $options)->then($fn); + }; + }; + } +} diff --git a/lib/guzzlehttp/guzzle/src/Pool.php b/lib/guzzlehttp/guzzle/src/Pool.php new file mode 100644 index 0000000000..6277c61fbc --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Pool.php @@ -0,0 +1,125 @@ + $rfn) { + if ($rfn instanceof RequestInterface) { + yield $key => $client->sendAsync($rfn, $opts); + } elseif (\is_callable($rfn)) { + yield $key => $rfn($opts); + } 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.'); + } + } + }; + + $this->each = new EachPromise($requests(), $config); + } + + /** + * Get promise + */ + public function promise(): PromiseInterface + { + return $this->each->promise(); + } + + /** + * Sends multiple requests concurrently and returns an array of responses + * and exceptions that uses the same ordering as the provided requests. + * + * IMPORTANT: This method keeps every request and response in memory, and + * 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} + * + * @return array Returns an array containing the response or an exception + * 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 + { + $res = []; + self::cmpCallback($options, 'fulfilled', $res); + self::cmpCallback($options, 'rejected', $res); + $pool = new static($client, $requests, $options); + $pool->promise()->wait(); + \ksort($res); + + return $res; + } + + /** + * Execute callback(s) + */ + private static function cmpCallback(array &$options, string $name, array &$results): void + { + if (!isset($options[$name])) { + $options[$name] = static function ($v, $k) use (&$results) { + $results[$k] = $v; + }; + } else { + $currentFn = $options[$name]; + $options[$name] = static function ($v, $k) use (&$results, $currentFn) { + $currentFn($v, $k); + $results[$k] = $v; + }; + } + } +} diff --git a/lib/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php b/lib/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php new file mode 100644 index 0000000000..7ca6283380 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php @@ -0,0 +1,104 @@ +nextHandler = $nextHandler; + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + $fn = $this->nextHandler; + + // Don't do anything if the request has no body. + if ($request->getBody()->getSize() === 0) { + return $fn($request, $options); + } + + $modify = []; + + // Add a default content-type if possible. + if (!$request->hasHeader('Content-Type')) { + if ($uri = $request->getBody()->getMetadata('uri')) { + if (is_string($uri) && $type = Psr7\MimeType::fromFilename($uri)) { + $modify['set_headers']['Content-Type'] = $type; + } + } + } + + // Add a default content-length or transfer-encoding header. + if (!$request->hasHeader('Content-Length') + && !$request->hasHeader('Transfer-Encoding') + ) { + $size = $request->getBody()->getSize(); + if ($size !== null) { + $modify['set_headers']['Content-Length'] = $size; + } else { + $modify['set_headers']['Transfer-Encoding'] = 'chunked'; + } + } + + // Add the expect header if needed. + $this->addExpectHeader($request, $options, $modify); + + return $fn(Psr7\Utils::modifyRequest($request, $modify), $options); + } + + /** + * Add expect header + */ + private function addExpectHeader(RequestInterface $request, array $options, array &$modify): void + { + // Determine if the Expect header should be used + if ($request->hasHeader('Expect')) { + return; + } + + $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; + } + + // The expect header is unconditionally enabled + if ($expect === true) { + $modify['set_headers']['Expect'] = '100-Continue'; + return; + } + + // By default, send the expect header when the payload is > 1mb + if ($expect === null) { + $expect = 1048576; + } + + // 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()) { + $modify['set_headers']['Expect'] = '100-Continue'; + } + } +} diff --git a/lib/guzzlehttp/guzzle/src/RedirectMiddleware.php b/lib/guzzlehttp/guzzle/src/RedirectMiddleware.php new file mode 100644 index 0000000000..f67d448beb --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/RedirectMiddleware.php @@ -0,0 +1,228 @@ + 5, + 'protocols' => ['http', 'https'], + 'strict' => false, + 'referer' => false, + 'track_redirects' => false, + ]; + + /** + * @var callable(RequestInterface, array): PromiseInterface + */ + private $nextHandler; + + /** + * @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 + { + $fn = $this->nextHandler; + + if (empty($options['allow_redirects'])) { + return $fn($request, $options); + } + + if ($options['allow_redirects'] === true) { + $options['allow_redirects'] = self::$defaultSettings; + } elseif (!\is_array($options['allow_redirects'])) { + 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; + } + + if (empty($options['allow_redirects']['max'])) { + return $fn($request, $options); + } + + return $fn($request, $options) + ->then(function (ResponseInterface $response) use ($request, $options) { + return $this->checkRedirect($request, $options, $response); + }); + } + + /** + * @return ResponseInterface|PromiseInterface + */ + public function checkRedirect(RequestInterface $request, array $options, ResponseInterface $response) + { + if (\strpos((string) $response->getStatusCode(), '3') !== 0 + || !$response->hasHeader('Location') + ) { + return $response; + } + + $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] + ); + } + + if (isset($options['allow_redirects']['on_redirect'])) { + ($options['allow_redirects']['on_redirect'])( + $request, + $response, + $nextRequest->getUri() + ); + } + + $promise = $this($nextRequest, $options); + + // Add headers to be able to track history of redirects. + if (!empty($options['allow_redirects']['track_redirects'])) { + return $this->withTracking( + $promise, + (string) $nextRequest->getUri(), + $response->getStatusCode() + ); + } + + return $promise; + } + + /** + * Enable tracking on promise. + */ + private function withTracking(PromiseInterface $promise, string $uri, int $statusCode): PromiseInterface + { + return $promise->then( + static function (ResponseInterface $response) use ($uri, $statusCode) { + // 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. + $historyHeader = $response->getHeader(self::HISTORY_HEADER); + $statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER); + \array_unshift($historyHeader, $uri); + \array_unshift($statusHeader, (string) $statusCode); + + return $response->withHeader(self::HISTORY_HEADER, $historyHeader) + ->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader); + } + ); + } + + /** + * Check for too many redirects. + * + * @throws TooManyRedirectsException Too many redirects. + */ + private function guardMax(RequestInterface $request, ResponseInterface $response, array &$options): void + { + $current = $options['__redirect_count'] + ?? 0; + $options['__redirect_count'] = $current + 1; + $max = $options['allow_redirects']['max']; + + if ($options['__redirect_count'] > $max) { + throw new TooManyRedirectsException("Will not follow more than {$max} redirects", $request, $response); + } + } + + public function modifyRequest(RequestInterface $request, array $options, ResponseInterface $response): RequestInterface + { + // Request modifications to apply. + $modify = []; + $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'] = ''; + } + + $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); + } + + $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. + if ($options['allow_redirects']['referer'] + && $modify['uri']->getScheme() === $request->getUri()->getScheme() + ) { + $uri = $request->getUri()->withUserInfo(''); + $modify['set_headers']['Referer'] = (string) $uri; + } else { + $modify['remove_headers'][] = 'Referer'; + } + + // Remove Authorization and Cookie headers if URI is cross-origin. + if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $modify['uri'])) { + $modify['remove_headers'][] = 'Authorization'; + $modify['remove_headers'][] = 'Cookie'; + } + + return Psr7\Utils::modifyRequest($request, $modify); + } + + /** + * Set the appropriate URL on the request based on the location header. + */ + private static function redirectUri( + RequestInterface $request, + ResponseInterface $response, + array $protocols + ): UriInterface { + $location = Psr7\UriResolver::resolve( + $request->getUri(), + new Psr7\Uri($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); + } + + return $location; + } +} diff --git a/lib/guzzlehttp/guzzle/src/RequestOptions.php b/lib/guzzlehttp/guzzle/src/RequestOptions.php new file mode 100644 index 0000000000..20b31bc207 --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/RequestOptions.php @@ -0,0 +1,264 @@ +decider = $decider; + $this->nextHandler = $nextHandler; + $this->delay = $delay ?: __CLASS__ . '::exponentialDelay'; + } + + /** + * Default exponential backoff delay function. + * + * @return int milliseconds. + */ + public static function exponentialDelay(int $retries): int + { + return (int) \pow(2, $retries - 1) * 1000; + } + + public function __invoke(RequestInterface $request, array $options): PromiseInterface + { + if (!isset($options['retries'])) { + $options['retries'] = 0; + } + + $fn = $this->nextHandler; + return $fn($request, $options) + ->then( + $this->onFulfilled($request, $options), + $this->onRejected($request, $options) + ); + } + + /** + * Execute fulfilled closure + */ + private function onFulfilled(RequestInterface $request, array $options): callable + { + return function ($value) use ($request, $options) { + if (!($this->decider)( + $options['retries'], + $request, + $value, + null + )) { + return $value; + } + return $this->doRetry($request, $options, $value); + }; + } + + /** + * Execute rejected closure + */ + private function onRejected(RequestInterface $req, array $options): callable + { + return function ($reason) use ($req, $options) { + if (!($this->decider)( + $options['retries'], + $req, + null, + $reason + )) { + return P\Create::rejectionFor($reason); + } + return $this->doRetry($req, $options); + }; + } + + private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null): PromiseInterface + { + $options['delay'] = ($this->delay)(++$options['retries'], $response); + + return $this($request, $options); + } +} diff --git a/lib/guzzlehttp/guzzle/src/TransferStats.php b/lib/guzzlehttp/guzzle/src/TransferStats.php new file mode 100644 index 0000000000..93fa334c8d --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/TransferStats.php @@ -0,0 +1,133 @@ +request = $request; + $this->response = $response; + $this->transferTime = $transferTime; + $this->handlerErrorData = $handlerErrorData; + $this->handlerStats = $handlerStats; + } + + public function getRequest(): RequestInterface + { + return $this->request; + } + + /** + * Returns the response that was received (if any). + */ + public function getResponse(): ?ResponseInterface + { + return $this->response; + } + + /** + * Returns true if a response was received. + */ + public function hasResponse(): bool + { + return $this->response !== null; + } + + /** + * Gets handler specific error data. + * + * This might be an exception, a integer representing an error code, or + * anything else. Relying on this value assumes that you know what handler + * you are using. + * + * @return mixed + */ + public function getHandlerErrorData() + { + return $this->handlerErrorData; + } + + /** + * Get the effective URI the request was sent to. + */ + public function getEffectiveUri(): UriInterface + { + return $this->request->getUri(); + } + + /** + * Get the estimated time the request was being transferred by the handler. + * + * @return float|null Time in seconds. + */ + public function getTransferTime(): ?float + { + return $this->transferTime; + } + + /** + * Gets an array of all of the handler specific transfer data. + */ + public function getHandlerStats(): array + { + return $this->handlerStats; + } + + /** + * Get a specific handler statistic from the handler by name. + * + * @param string $stat Handler specific transfer stat to retrieve. + * + * @return mixed|null + */ + public function getHandlerStat(string $stat) + { + return $this->handlerStats[$stat] ?? null; + } +} diff --git a/lib/guzzlehttp/guzzle/src/Utils.php b/lib/guzzlehttp/guzzle/src/Utils.php new file mode 100644 index 0000000000..91591da2ed --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/Utils.php @@ -0,0 +1,382 @@ +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'); + } +} diff --git a/lib/guzzlehttp/guzzle/src/functions.php b/lib/guzzlehttp/guzzle/src/functions.php new file mode 100644 index 0000000000..a70d2cbf3f --- /dev/null +++ b/lib/guzzlehttp/guzzle/src/functions.php @@ -0,0 +1,167 @@ + +Copyright (c) 2015 Graham Campbell +Copyright (c) 2017 Tobias Schultze +Copyright (c) 2020 Tobias Nyholm + +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/lib/guzzlehttp/promises/Makefile b/lib/guzzlehttp/promises/Makefile new file mode 100644 index 0000000000..8d5b3ef95e --- /dev/null +++ b/lib/guzzlehttp/promises/Makefile @@ -0,0 +1,13 @@ +all: clean test + +test: + vendor/bin/phpunit + +coverage: + vendor/bin/phpunit --coverage-html=artifacts/coverage + +view-coverage: + open artifacts/coverage/index.html + +clean: + rm -rf artifacts/* diff --git a/lib/guzzlehttp/promises/README.md b/lib/guzzlehttp/promises/README.md new file mode 100644 index 0000000000..c175fec76b --- /dev/null +++ b/lib/guzzlehttp/promises/README.md @@ -0,0 +1,547 @@ +# 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. + +- [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) + + +# Features + +- [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()`. + + +# 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; + +$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*. Resolving a promises triggers callbacks +registered with the promises'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...) + } +); + +assert('waited' === $promise->wait()); +``` + +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. + +```php +use GuzzleHttp\Promise\FulfilledPromise; + +$promise = new FulfilledPromise('value'); + +// Fulfilled callbacks are immediately invoked. +$promise->then(function ($value) { + echo $value; +}); +``` + + +## RejectedPromise + +A rejected promise can be created to represent a promise that has been +rejected. + +```php +use GuzzleHttp\Promise\RejectedPromise; + +$promise = new RejectedPromise('Error'); + +// Rejected callbacks are immediately invoked. +$promise->then(null, function ($reason) { + echo $reason; +}); +``` + + +# Promise interop + +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` | + + +## 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. + +## License + +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) diff --git a/lib/guzzlehttp/promises/composer.json b/lib/guzzlehttp/promises/composer.json new file mode 100644 index 0000000000..c959fb32b4 --- /dev/null +++ b/lib/guzzlehttp/promises/composer.json @@ -0,0 +1,58 @@ +{ + "name": "guzzlehttp/promises", + "description": "Guzzle promises library", + "keywords": ["promise"], + "license": "MIT", + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "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": { + "preferred-install": "dist", + "sort-packages": true + } +} diff --git a/lib/guzzlehttp/promises/src/AggregateException.php b/lib/guzzlehttp/promises/src/AggregateException.php new file mode 100644 index 0000000000..d2b5712b92 --- /dev/null +++ b/lib/guzzlehttp/promises/src/AggregateException.php @@ -0,0 +1,17 @@ +then(function ($v) { echo $v; }); + * + * @param callable $generatorFn Generator function to wrap into a promise. + * + * @return Promise + * + * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration + */ +final class Coroutine implements PromiseInterface +{ + /** + * @var PromiseInterface|null + */ + private $currentPromise; + + /** + * @var Generator + */ + private $generator; + + /** + * @var Promise + */ + private $result; + + public function __construct(callable $generatorFn) + { + $this->generator = $generatorFn(); + $this->result = new Promise(function () { + while (isset($this->currentPromise)) { + $this->currentPromise->wait(); + } + }); + try { + $this->nextCoroutine($this->generator->current()); + } catch (\Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + + /** + * Create a new coroutine. + * + * @return self + */ + public static function of(callable $generatorFn) + { + return new self($generatorFn); + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + return $this->result->then($onFulfilled, $onRejected); + } + + public function otherwise(callable $onRejected) + { + return $this->result->otherwise($onRejected); + } + + public function wait($unwrap = true) + { + return $this->result->wait($unwrap); + } + + public function getState() + { + return $this->result->getState(); + } + + public function resolve($value) + { + $this->result->resolve($value); + } + + public function reject($reason) + { + $this->result->reject($reason); + } + + public function cancel() + { + $this->currentPromise->cancel(); + $this->result->cancel(); + } + + private function nextCoroutine($yielded) + { + $this->currentPromise = Create::promiseFor($yielded) + ->then([$this, '_handleSuccess'], [$this, '_handleFailure']); + } + + /** + * @internal + */ + public function _handleSuccess($value) + { + unset($this->currentPromise); + try { + $next = $this->generator->send($value); + if ($this->generator->valid()) { + $this->nextCoroutine($next); + } else { + $this->result->resolve($value); + } + } catch (Exception $exception) { + $this->result->reject($exception); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + + /** + * @internal + */ + public function _handleFailure($reason) + { + unset($this->currentPromise); + 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) { + $this->result->reject($throwable); + } + } +} diff --git a/lib/guzzlehttp/promises/src/Create.php b/lib/guzzlehttp/promises/src/Create.php new file mode 100644 index 0000000000..8d038e9c1c --- /dev/null +++ b/lib/guzzlehttp/promises/src/Create.php @@ -0,0 +1,84 @@ +then([$promise, 'resolve'], [$promise, 'reject']); + return $promise; + } + + return new FulfilledPromise($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 + */ + public static function rejectionFor($reason) + { + if ($reason instanceof PromiseInterface) { + return $reason; + } + + return new RejectedPromise($reason); + } + + /** + * Create an exception for a rejected promise value. + * + * @param mixed $reason + * + * @return \Exception|\Throwable + */ + public static function exceptionFor($reason) + { + if ($reason instanceof \Exception || $reason instanceof \Throwable) { + return $reason; + } + + return new RejectionException($reason); + } + + /** + * Returns an iterator for the given value. + * + * @param mixed $value + * + * @return \Iterator + */ + public static function iterFor($value) + { + if ($value instanceof \Iterator) { + return $value; + } + + if (is_array($value)) { + return new \ArrayIterator($value); + } + + return new \ArrayIterator([$value]); + } +} diff --git a/lib/guzzlehttp/promises/src/Each.php b/lib/guzzlehttp/promises/src/Each.php new file mode 100644 index 0000000000..1dda354993 --- /dev/null +++ b/lib/guzzlehttp/promises/src/Each.php @@ -0,0 +1,90 @@ + $onFulfilled, + 'rejected' => $onRejected + ]))->promise(); + } + + /** + * Like of, 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 + */ + public static function ofLimit( + $iterable, + $concurrency, + callable $onFulfilled = null, + callable $onRejected = null + ) { + return (new EachPromise($iterable, [ + 'fulfilled' => $onFulfilled, + 'rejected' => $onRejected, + 'concurrency' => $concurrency + ]))->promise(); + } + + /** + * 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. + * + * @param mixed $iterable + * @param int|callable $concurrency + * @param callable $onFulfilled + * + * @return PromiseInterface + */ + public static function ofLimitAll( + $iterable, + $concurrency, + callable $onFulfilled = null + ) { + return each_limit( + $iterable, + $concurrency, + $onFulfilled, + function ($reason, $idx, PromiseInterface $aggregate) { + $aggregate->reject($reason); + } + ); + } +} diff --git a/lib/guzzlehttp/promises/src/EachPromise.php b/lib/guzzlehttp/promises/src/EachPromise.php new file mode 100644 index 0000000000..38ecb59b15 --- /dev/null +++ b/lib/guzzlehttp/promises/src/EachPromise.php @@ -0,0 +1,255 @@ +iterable = Create::iterFor($iterable); + + if (isset($config['concurrency'])) { + $this->concurrency = $config['concurrency']; + } + + if (isset($config['fulfilled'])) { + $this->onFulfilled = $config['fulfilled']; + } + + if (isset($config['rejected'])) { + $this->onRejected = $config['rejected']; + } + } + + /** @psalm-suppress InvalidNullableReturnType */ + public function promise() + { + if ($this->aggregate) { + return $this->aggregate; + } + + try { + $this->createPromise(); + /** @psalm-assert Promise $this->aggregate */ + $this->iterable->rewind(); + $this->refillPending(); + } catch (\Throwable $e) { + /** + * @psalm-suppress NullReference + * @phpstan-ignore-next-line + */ + $this->aggregate->reject($e); + } catch (\Exception $e) { + /** + * @psalm-suppress NullReference + * @phpstan-ignore-next-line + */ + $this->aggregate->reject($e); + } + + /** + * @psalm-suppress NullableReturnStatement + * @phpstan-ignore-next-line + */ + return $this->aggregate; + } + + private function createPromise() + { + $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)) { + return; + } + } + }); + + // Clear the references when the promise is resolved. + $clearFn = function () { + $this->iterable = $this->concurrency = $this->pending = null; + $this->onFulfilled = $this->onRejected = null; + $this->nextPendingIndex = 0; + }; + + $this->aggregate->then($clearFn, $clearFn); + } + + private function refillPending() + { + if (!$this->concurrency) { + // Add all pending promises. + 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; + $concurrency = max($concurrency - count($this->pending), 0); + // Concurrency may be set to 0 to disallow new promises. + if (!$concurrency) { + return; + } + // Add the first pending promise. + $this->addPending(); + // Note this is special handling for concurrency=1 so that we do + // not advance the iterator after adding the first promise. This + // helps work around issues with generators that might not have the + // next value to yield until promise callbacks are called. + while (--$concurrency + && $this->advanceIterator() + && $this->addPending()); + } + + private function addPending() + { + if (!$this->iterable || !$this->iterable->valid()) { + return false; + } + + $promise = Create::promiseFor($this->iterable->current()); + $key = $this->iterable->key(); + + // Iterable keys may not be unique, so we use a counter to + // guarantee uniqueness + $idx = $this->nextPendingIndex++; + + $this->pending[$idx] = $promise->then( + function ($value) use ($idx, $key) { + if ($this->onFulfilled) { + call_user_func( + $this->onFulfilled, + $value, + $key, + $this->aggregate + ); + } + $this->step($idx); + }, + function ($reason) use ($idx, $key) { + if ($this->onRejected) { + call_user_func( + $this->onRejected, + $reason, + $key, + $this->aggregate + ); + } + $this->step($idx); + } + ); + + return true; + } + + private function advanceIterator() + { + // Place a lock on the iterator so that we ensure to not recurse, + // preventing fatal generator errors. + if ($this->mutex) { + return false; + } + + $this->mutex = true; + + 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; + } + } + + private function step($idx) + { + // If the promise was already resolved, then ignore this step. + if (Is::settled($this->aggregate)) { + return; + } + + unset($this->pending[$idx]); + + // Only refill pending promises if we are not locked, preventing the + // EachPromise to recursively invoke the provided iterator, which + // cause a fatal error: "Cannot resume an already running generator" + if ($this->advanceIterator() && !$this->checkIfFinished()) { + // Add more pending promises if possible. + $this->refillPending(); + } + } + + private function checkIfFinished() + { + if (!$this->pending && !$this->iterable->valid()) { + // Resolve the promise if there's nothing left to do. + $this->aggregate->resolve(null); + return true; + } + + return false; + } +} diff --git a/lib/guzzlehttp/promises/src/FulfilledPromise.php b/lib/guzzlehttp/promises/src/FulfilledPromise.php new file mode 100644 index 0000000000..98f72a62a6 --- /dev/null +++ b/lib/guzzlehttp/promises/src/FulfilledPromise.php @@ -0,0 +1,84 @@ +value = $value; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // Return itself if there is no onFulfilled function. + if (!$onFulfilled) { + return $this; + } + + $queue = Utils::queue(); + $p = new Promise([$queue, 'run']); + $value = $this->value; + $queue->add(static function () use ($p, $value, $onFulfilled) { + if (Is::pending($p)) { + try { + $p->resolve($onFulfilled($value)); + } catch (\Throwable $e) { + $p->reject($e); + } catch (\Exception $e) { + $p->reject($e); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + return $unwrap ? $this->value : null; + } + + public function getState() + { + return self::FULFILLED; + } + + public function resolve($value) + { + if ($value !== $this->value) { + throw new \LogicException("Cannot resolve a fulfilled promise"); + } + } + + public function reject($reason) + { + throw new \LogicException("Cannot reject a fulfilled promise"); + } + + public function cancel() + { + // pass + } +} diff --git a/lib/guzzlehttp/promises/src/Is.php b/lib/guzzlehttp/promises/src/Is.php new file mode 100644 index 0000000000..c3ed8d0143 --- /dev/null +++ b/lib/guzzlehttp/promises/src/Is.php @@ -0,0 +1,46 @@ +getState() === PromiseInterface::PENDING; + } + + /** + * Returns true if a promise is fulfilled or rejected. + * + * @return bool + */ + public static function settled(PromiseInterface $promise) + { + return $promise->getState() !== PromiseInterface::PENDING; + } + + /** + * Returns true if a promise is fulfilled. + * + * @return bool + */ + public static function fulfilled(PromiseInterface $promise) + { + return $promise->getState() === PromiseInterface::FULFILLED; + } + + /** + * Returns true if a promise is rejected. + * + * @return bool + */ + public static function rejected(PromiseInterface $promise) + { + return $promise->getState() === PromiseInterface::REJECTED; + } +} diff --git a/lib/guzzlehttp/promises/src/Promise.php b/lib/guzzlehttp/promises/src/Promise.php new file mode 100644 index 0000000000..75939057b6 --- /dev/null +++ b/lib/guzzlehttp/promises/src/Promise.php @@ -0,0 +1,278 @@ +waitFn = $waitFn; + $this->cancelFn = $cancelFn; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + 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; + } + + // 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; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true) + { + $this->waitIfPending(); + + if ($this->result instanceof PromiseInterface) { + return $this->result->wait($unwrap); + } + if ($unwrap) { + if ($this->state === self::FULFILLED) { + return $this->result; + } + // It's rejected so "unwrap" and throw an exception. + throw Create::exceptionFor($this->result); + } + } + + public function getState() + { + return $this->state; + } + + public function cancel() + { + if ($this->state !== self::PENDING) { + return; + } + + $this->waitFn = $this->waitList = null; + + if ($this->cancelFn) { + $fn = $this->cancelFn; + $this->cancelFn = null; + try { + $fn(); + } catch (\Throwable $e) { + $this->reject($e); + } catch (\Exception $e) { + $this->reject($e); + } + } + + // Reject the promise only if it wasn't rejected in a then callback. + /** @psalm-suppress RedundantCondition */ + if ($this->state === self::PENDING) { + $this->reject(new CancellationException('Promise has been cancelled')); + } + } + + public function resolve($value) + { + $this->settle(self::FULFILLED, $value); + } + + public function reject($reason) + { + $this->settle(self::REJECTED, $reason); + } + + private function settle($state, $value) + { + if ($this->state !== self::PENDING) { + // Ignore calls with the same resolution. + if ($state === $this->state && $value === $this->result) { + return; + } + throw $this->state === $state + ? new \LogicException("The promise is already {$state}.") + : new \LogicException("Cannot change a {$this->state} promise to {$state}"); + } + + if ($value === $this) { + throw new \LogicException('Cannot fulfill or reject a promise with itself'); + } + + // Clear out the state of the promise but stash the handlers. + $this->state = $state; + $this->result = $value; + $handlers = $this->handlers; + $this->handlers = null; + $this->waitList = $this->waitFn = null; + $this->cancelFn = null; + + if (!$handlers) { + return; + } + + // If the value was not a settled promise or a thenable, then resolve + // it in the task queue using the correct ID. + 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) { + foreach ($handlers as $handler) { + self::callHandler($id, $value, $handler); + } + }); + } elseif ($value instanceof Promise && Is::pending($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) { + foreach ($handlers as $handler) { + self::callHandler(1, $value, $handler); + } + }, + static function ($reason) use ($handlers) { + foreach ($handlers as $handler) { + self::callHandler(2, $reason, $handler); + } + } + ); + } + } + + /** + * Call a stack of handlers using a specific callback index and value. + * + * @param int $index 1 (resolve) or 2 (reject). + * @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) + { + /** @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)) { + return; + } + + try { + if (isset($handler[$index])) { + /* + * If $f throws an exception, then $handler will be in the exception + * stack trace. Since $handler contains a reference to the callable + * itself we get a circular reference. We clear the $handler + * here to avoid that memory leak. + */ + $f = $handler[$index]; + unset($handler); + $promise->resolve($f($value)); + } elseif ($index === 1) { + // Forward resolution values as-is. + $promise->resolve($value); + } else { + // Forward rejections down the chain. + $promise->reject($value); + } + } catch (\Throwable $reason) { + $promise->reject($reason); + } catch (\Exception $reason) { + $promise->reject($reason); + } + } + + private function waitIfPending() + { + if ($this->state !== self::PENDING) { + return; + } elseif ($this->waitFn) { + $this->invokeWaitFn(); + } elseif ($this->waitList) { + $this->invokeWaitList(); + } 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.'); + } + + Utils::queue()->run(); + + /** @psalm-suppress RedundantCondition */ + if ($this->state === self::PENDING) { + $this->reject('Invoking the wait callback did not resolve the promise'); + } + } + + private function invokeWaitFn() + { + try { + $wfn = $this->waitFn; + $this->waitFn = null; + $wfn(true); + } catch (\Exception $reason) { + if ($this->state === self::PENDING) { + // The promise has not been resolved yet, so reject the promise + // with the exception. + $this->reject($reason); + } else { + // The promise was already resolved, so there's a problem in + // the application. + throw $reason; + } + } + } + + private function invokeWaitList() + { + $waitList = $this->waitList; + $this->waitList = null; + + foreach ($waitList as $result) { + do { + $result->waitIfPending(); + $result = $result->result; + } while ($result instanceof Promise); + + if ($result instanceof PromiseInterface) { + $result->wait(false); + } + } + } +} diff --git a/lib/guzzlehttp/promises/src/PromiseInterface.php b/lib/guzzlehttp/promises/src/PromiseInterface.php new file mode 100644 index 0000000000..e598331435 --- /dev/null +++ b/lib/guzzlehttp/promises/src/PromiseInterface.php @@ -0,0 +1,97 @@ +reason = $reason; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null + ) { + // If there's no onRejected callback then just return self. + if (!$onRejected) { + return $this; + } + + $queue = Utils::queue(); + $reason = $this->reason; + $p = new Promise([$queue, 'run']); + $queue->add(static function () use ($p, $reason, $onRejected) { + if (Is::pending($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); + } + } + }); + + return $p; + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, $onRejected); + } + + public function wait($unwrap = true, $defaultDelivery = null) + { + if ($unwrap) { + throw Create::exceptionFor($this->reason); + } + + return null; + } + + public function getState() + { + return self::REJECTED; + } + + public function resolve($value) + { + throw new \LogicException("Cannot resolve a rejected promise"); + } + + public function reject($reason) + { + if ($reason !== $this->reason) { + throw new \LogicException("Cannot reject a rejected promise"); + } + } + + public function cancel() + { + // pass + } +} diff --git a/lib/guzzlehttp/promises/src/RejectionException.php b/lib/guzzlehttp/promises/src/RejectionException.php new file mode 100644 index 0000000000..e2f137707d --- /dev/null +++ b/lib/guzzlehttp/promises/src/RejectionException.php @@ -0,0 +1,48 @@ +reason = $reason; + + $message = 'The promise was rejected'; + + if ($description) { + $message .= ' with reason: ' . $description; + } elseif (is_string($reason) + || (is_object($reason) && method_exists($reason, '__toString')) + ) { + $message .= ' with reason: ' . $this->reason; + } elseif ($reason instanceof \JsonSerializable) { + $message .= ' with reason: ' + . json_encode($this->reason, JSON_PRETTY_PRINT); + } + + parent::__construct($message); + } + + /** + * Returns the rejection reason. + * + * @return mixed + */ + public function getReason() + { + return $this->reason; + } +} diff --git a/lib/guzzlehttp/promises/src/TaskQueue.php b/lib/guzzlehttp/promises/src/TaskQueue.php new file mode 100644 index 0000000000..f0fba2c594 --- /dev/null +++ b/lib/guzzlehttp/promises/src/TaskQueue.php @@ -0,0 +1,67 @@ +run(); + */ +class TaskQueue implements TaskQueueInterface +{ + private $enableShutdown = true; + private $queue = []; + + public function __construct($withShutdown = true) + { + if ($withShutdown) { + register_shutdown_function(function () { + if ($this->enableShutdown) { + // Only run the tasks if an E_ERROR didn't occur. + $err = error_get_last(); + if (!$err || ($err['type'] ^ E_ERROR)) { + $this->run(); + } + } + }); + } + } + + public function isEmpty() + { + return !$this->queue; + } + + public function add(callable $task) + { + $this->queue[] = $task; + } + + public function run() + { + while ($task = array_shift($this->queue)) { + /** @var callable $task */ + $task(); + } + } + + /** + * The task queue will be run and exhausted by default when the process + * exits IFF the exit is not the result of a PHP E_ERROR error. + * + * You can disable running the automatic shutdown of the queue by calling + * this function. If you disable the task queue shutdown process, then you + * MUST either run the task queue (as a result of running your event loop + * or manually using the run() method) or wait on each outstanding promise. + * + * Note: This shutdown will occur before any destructors are triggered. + */ + public function disableShutdown() + { + $this->enableShutdown = false; + } +} diff --git a/lib/guzzlehttp/promises/src/TaskQueueInterface.php b/lib/guzzlehttp/promises/src/TaskQueueInterface.php new file mode 100644 index 0000000000..723d4d54eb --- /dev/null +++ b/lib/guzzlehttp/promises/src/TaskQueueInterface.php @@ -0,0 +1,24 @@ + + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\Utils::queue()->run(); + * } + * + * + * @param TaskQueueInterface $assign Optionally specify a new queue instance. + * + * @return TaskQueueInterface + */ + public static function queue(TaskQueueInterface $assign = null) + { + static $queue; + + if ($assign) { + $queue = $assign; + } elseif (!$queue) { + $queue = new TaskQueue(); + } + + return $queue; + } + + /** + * 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 + */ + public static function task(callable $task) + { + $queue = self::queue(); + $promise = new Promise([$queue, 'run']); + $queue->add(function () use ($task, $promise) { + try { + if (Is::pending($promise)) { + $promise->resolve($task()); + } + } catch (\Throwable $e) { + $promise->reject($e); + } catch (\Exception $e) { + $promise->reject($e); + } + }); + + return $promise; + } + + /** + * 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 + */ + public static function inspect(PromiseInterface $promise) + { + try { + return [ + 'state' => PromiseInterface::FULFILLED, + 'value' => $promise->wait() + ]; + } catch (RejectionException $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()]; + } catch (\Throwable $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; + } catch (\Exception $e) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; + } + } + + /** + * 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 + */ + public static function inspectAll($promises) + { + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = inspect($promise); + } + + return $results; + } + + /** + * 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 + */ + public static function unwrap($promises) + { + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = $promise->wait(); + } + + return $results; + } + + /** + * 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 + */ + public static function all($promises, $recursive = false) + { + $results = []; + $promise = Each::of( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = $value; + }, + function ($reason, $idx, Promise $aggregate) { + $aggregate->reject($reason); + } + )->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); + } + } + return $results; + }); + } + + return $promise; + } + + /** + * 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 + */ + public static function some($count, $promises) + { + $results = []; + $rejections = []; + + return Each::of( + $promises, + function ($value, $idx, PromiseInterface $p) use (&$results, $count) { + if (Is::settled($p)) { + return; + } + $results[$idx] = $value; + if (count($results) >= $count) { + $p->resolve(null); + } + }, + function ($reason) use (&$rejections) { + $rejections[] = $reason; + } + )->then( + function () use (&$results, &$rejections, $count) { + if (count($results) !== $count) { + throw new AggregateException( + 'Not enough promises to fulfill count', + $rejections + ); + } + ksort($results); + return array_values($results); + } + ); + } + + /** + * 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 + */ + public static function any($promises) + { + return self::some(1, $promises)->then(function ($values) { + return $values[0]; + }); + } + + /** + * 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 + */ + public static function settle($promises) + { + $results = []; + + return Each::of( + $promises, + function ($value, $idx) use (&$results) { + $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value]; + }, + function ($reason, $idx) use (&$results) { + $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason]; + } + )->then(function () use (&$results) { + ksort($results); + return $results; + }); + } +} diff --git a/lib/guzzlehttp/promises/src/functions.php b/lib/guzzlehttp/promises/src/functions.php new file mode 100644 index 0000000000..c03d39d021 --- /dev/null +++ b/lib/guzzlehttp/promises/src/functions.php @@ -0,0 +1,363 @@ + + * 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/lib/guzzlehttp/promises/src/functions_include.php b/lib/guzzlehttp/promises/src/functions_include.php new file mode 100644 index 0000000000..34cd1710aa --- /dev/null +++ b/lib/guzzlehttp/promises/src/functions_include.php @@ -0,0 +1,6 @@ +withPath('foo')->withHost('example.com')` will throw an exception + because the path of a URI with an authority must start with a slash "/" or be empty + - `(new Uri())->withScheme('http')` will return `'http://localhost'` + +### Deprecated + +- `Uri::resolve` in favor of `UriResolver::resolve` +- `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments` + +### Fixed + +- `Stream::read` when length parameter <= 0. +- `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory. +- `ServerRequest::getUriFromGlobals` when `Host` header contains port. +- Compatibility of URIs with `file` scheme and empty host. + + +## [1.3.1] - 2016-06-25 + +### Fixed + +- `Uri::__toString` for network path references, e.g. `//example.org`. +- Missing lowercase normalization for host. +- Handling of URI components in case they are `'0'` in a lot of places, + e.g. as a user info password. +- `Uri::withAddedHeader` to correctly merge headers with different case. +- Trimming of header values in `Uri::withAddedHeader`. Header values may + be surrounded by whitespace which should be ignored according to RFC 7230 + Section 3.2.4. This does not apply to header names. +- `Uri::withAddedHeader` with an array of header values. +- `Uri::resolve` when base path has no slash and handling of fragment. +- Handling of encoding in `Uri::with(out)QueryValue` so one can pass the + key/value both in encoded as well as decoded form to those methods. This is + consistent with withPath, withQuery etc. +- `ServerRequest::withoutAttribute` when attribute value is null. + + +## [1.3.0] - 2016-04-13 + +### Added + +- Remaining interfaces needed for full PSR7 compatibility + (ServerRequestInterface, UploadedFileInterface, etc.). +- Support for stream_for from scalars. + +### Changed + +- Can now extend Uri. + +### Fixed +- A bug in validating request methods by making it more permissive. + + +## [1.2.3] - 2016-02-18 + +### Fixed + +- Support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote + streams, which can sometimes return fewer bytes than requested with `fread`. +- Handling of gzipped responses with FNAME headers. + + +## [1.2.2] - 2016-01-22 + +### Added + +- Support for URIs without any authority. +- Support for HTTP 451 'Unavailable For Legal Reasons.' +- Support for using '0' as a filename. +- Support for including non-standard ports in Host headers. + + +## [1.2.1] - 2015-11-02 + +### Changes + +- Now supporting negative offsets when seeking to SEEK_END. + + +## [1.2.0] - 2015-08-15 + +### Changed + +- Body as `"0"` is now properly added to a response. +- Now allowing forward seeking in CachingStream. +- Now properly parsing HTTP requests that contain proxy targets in + `parse_request`. +- functions.php is now conditionally required. +- user-info is no longer dropped when resolving URIs. + + +## [1.1.0] - 2015-06-24 + +### Changed + +- URIs can now be relative. +- `multipart/form-data` headers are now overridden case-insensitively. +- URI paths no longer encode the following characters because they are allowed + in URIs: "(", ")", "*", "!", "'" +- A port is no longer added to a URI when the scheme is missing and no port is + present. + + +## 1.0.0 - 2015-05-19 + +Initial release. + +Currently unsupported: + +- `Psr\Http\Message\ServerRequestInterface` +- `Psr\Http\Message\UploadedFileInterface` + + + +[1.6.0]: https://github.com/guzzle/psr7/compare/1.5.2...1.6.0 +[1.5.2]: https://github.com/guzzle/psr7/compare/1.5.1...1.5.2 +[1.5.1]: https://github.com/guzzle/psr7/compare/1.5.0...1.5.1 +[1.5.0]: https://github.com/guzzle/psr7/compare/1.4.2...1.5.0 +[1.4.2]: https://github.com/guzzle/psr7/compare/1.4.1...1.4.2 +[1.4.1]: https://github.com/guzzle/psr7/compare/1.4.0...1.4.1 +[1.4.0]: https://github.com/guzzle/psr7/compare/1.3.1...1.4.0 +[1.3.1]: https://github.com/guzzle/psr7/compare/1.3.0...1.3.1 +[1.3.0]: https://github.com/guzzle/psr7/compare/1.2.3...1.3.0 +[1.2.3]: https://github.com/guzzle/psr7/compare/1.2.2...1.2.3 +[1.2.2]: https://github.com/guzzle/psr7/compare/1.2.1...1.2.2 +[1.2.1]: https://github.com/guzzle/psr7/compare/1.2.0...1.2.1 +[1.2.0]: https://github.com/guzzle/psr7/compare/1.1.0...1.2.0 +[1.1.0]: https://github.com/guzzle/psr7/compare/1.0.0...1.1.0 diff --git a/lib/guzzlehttp/psr7/LICENSE b/lib/guzzlehttp/psr7/LICENSE new file mode 100644 index 0000000000..51c7ec81cb --- /dev/null +++ b/lib/guzzlehttp/psr7/LICENSE @@ -0,0 +1,26 @@ +The MIT License (MIT) + +Copyright (c) 2015 Michael Dowling +Copyright (c) 2015 Márk Sági-Kazár +Copyright (c) 2015 Graham Campbell +Copyright (c) 2016 Tobias Schultze +Copyright (c) 2016 George Mponos +Copyright (c) 2018 Tobias Nyholm + +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/lib/guzzlehttp/psr7/README.md b/lib/guzzlehttp/psr7/README.md new file mode 100644 index 0000000000..eea803a02f --- /dev/null +++ b/lib/guzzlehttp/psr7/README.md @@ -0,0 +1,854 @@ +# PSR-7 Message Implementation + +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. + +![CI](https://github.com/guzzle/psr7/workflows/CI/badge.svg) +![Static analysis](https://github.com/guzzle/psr7/workflows/Static%20analysis/badge.svg) + + +# 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; + + 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::normalize` + +`public static function normalize(string|array $header): array` + +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 | + + +## 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. + + +## License + +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) diff --git a/lib/guzzlehttp/psr7/composer.json b/lib/guzzlehttp/psr7/composer.json new file mode 100644 index 0000000000..d2c7106cfb --- /dev/null +++ b/lib/guzzlehttp/psr7/composer.json @@ -0,0 +1,92 @@ +{ + "name": "guzzlehttp/psr7", + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "request", + "response", + "message", + "stream", + "http", + "uri", + "url", + "psr-7" + ], + "license": "MIT", + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "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" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\Psr7\\": "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "config": { + "allow-plugins": { + "bamarni/composer-bin-plugin": true + }, + "preferred-install": "dist", + "sort-packages": true + } +} diff --git a/lib/guzzlehttp/psr7/src/AppendStream.php b/lib/guzzlehttp/psr7/src/AppendStream.php new file mode 100644 index 0000000000..967925f3f5 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/AppendStream.php @@ -0,0 +1,249 @@ +addStream($stream); + } + } + + 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 ''; + } + } + + /** + * Add a stream to the AppendStream + * + * @param StreamInterface $stream Stream to append. Must be readable. + * + * @throws \InvalidArgumentException if the stream is not readable + */ + public function addStream(StreamInterface $stream): void + { + if (!$stream->isReadable()) { + throw new \InvalidArgumentException('Each stream must be readable'); + } + + // The stream is only seekable if all streams are seekable + if (!$stream->isSeekable()) { + $this->seekable = false; + } + + $this->streams[] = $stream; + } + + public function getContents(): string + { + return Utils::copyToString($this); + } + + /** + * Closes each attached stream. + */ + public function close(): void + { + $this->pos = $this->current = 0; + $this->seekable = true; + + foreach ($this->streams as $stream) { + $stream->close(); + } + + $this->streams = []; + } + + /** + * Detaches each attached stream. + * + * Returns null as it's not clear which underlying stream resource to return. + */ + public function detach() + { + $this->pos = $this->current = 0; + $this->seekable = true; + + foreach ($this->streams as $stream) { + $stream->detach(); + } + + $this->streams = []; + + return null; + } + + public function tell(): int + { + return $this->pos; + } + + /** + * Tries to calculate the size by adding the size of each stream. + * + * If any of the streams do not return a valid number, then the size of the + * append stream cannot be determined and null is returned. + */ + public function getSize(): ?int + { + $size = 0; + + foreach ($this->streams as $stream) { + $s = $stream->getSize(); + if ($s === null) { + return null; + } + $size += $s; + } + + return $size; + } + + public function eof(): bool + { + return !$this->streams || + ($this->current >= count($this->streams) - 1 && + $this->streams[$this->current]->eof()); + } + + public function rewind(): void + { + $this->seek(0); + } + + /** + * Attempts to seek to the given position. Only supports SEEK_SET. + */ + public function seek($offset, $whence = SEEK_SET): void + { + if (!$this->seekable) { + throw new \RuntimeException('This AppendStream is not seekable'); + } elseif ($whence !== SEEK_SET) { + throw new \RuntimeException('The AppendStream can only seek with SEEK_SET'); + } + + $this->pos = $this->current = 0; + + // Rewind each stream + foreach ($this->streams as $i => $stream) { + try { + $stream->rewind(); + } catch (\Exception $e) { + throw new \RuntimeException('Unable to seek stream ' + . $i . ' of the AppendStream', 0, $e); + } + } + + // Seek to the actual position by reading from each stream + while ($this->pos < $offset && !$this->eof()) { + $result = $this->read(min(8096, $offset - $this->pos)); + if ($result === '') { + break; + } + } + } + + /** + * Reads from all of the appended streams until the length is met or EOF. + */ + public function read($length): string + { + $buffer = ''; + $total = count($this->streams) - 1; + $remaining = $length; + $progressToNext = false; + + while ($remaining > 0) { + + // Progress to the next stream if needed. + if ($progressToNext || $this->streams[$this->current]->eof()) { + $progressToNext = false; + if ($this->current === $total) { + break; + } + $this->current++; + } + + $result = $this->streams[$this->current]->read($remaining); + + if ($result === '') { + $progressToNext = true; + continue; + } + + $buffer .= $result; + $remaining = $length - strlen($buffer); + } + + $this->pos += strlen($buffer); + + return $buffer; + } + + public function isReadable(): bool + { + return true; + } + + public function isWritable(): bool + { + return false; + } + + public function isSeekable(): bool + { + return $this->seekable; + } + + public function write($string): int + { + throw new \RuntimeException('Cannot write to an AppendStream'); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + return $key ? null : []; + } +} diff --git a/lib/guzzlehttp/psr7/src/BufferStream.php b/lib/guzzlehttp/psr7/src/BufferStream.php new file mode 100644 index 0000000000..21be8c0a9d --- /dev/null +++ b/lib/guzzlehttp/psr7/src/BufferStream.php @@ -0,0 +1,149 @@ +hwm = $hwm; + } + + public function __toString(): string + { + return $this->getContents(); + } + + public function getContents(): string + { + $buffer = $this->buffer; + $this->buffer = ''; + + return $buffer; + } + + public function close(): void + { + $this->buffer = ''; + } + + public function detach() + { + $this->close(); + + return null; + } + + public function getSize(): ?int + { + return strlen($this->buffer); + } + + public function isReadable(): bool + { + return true; + } + + public function isWritable(): bool + { + return true; + } + + public function isSeekable(): bool + { + return false; + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + throw new \RuntimeException('Cannot seek a BufferStream'); + } + + public function eof(): bool + { + return strlen($this->buffer) === 0; + } + + public function tell(): int + { + throw new \RuntimeException('Cannot determine the position of a BufferStream'); + } + + /** + * Reads data from the buffer. + */ + public function read($length): string + { + $currentLength = strlen($this->buffer); + + if ($length >= $currentLength) { + // No need to slice the buffer because we don't have enough data. + $result = $this->buffer; + $this->buffer = ''; + } else { + // Slice up the result to provide a subset of the buffer. + $result = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + } + + return $result; + } + + /** + * Writes data to the buffer. + */ + public function write($string): int + { + $this->buffer .= $string; + + if (strlen($this->buffer) >= $this->hwm) { + return 0; + } + + return strlen($string); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + if ($key === 'hwm') { + return $this->hwm; + } + + return $key ? null : []; + } +} diff --git a/lib/guzzlehttp/psr7/src/CachingStream.php b/lib/guzzlehttp/psr7/src/CachingStream.php new file mode 100644 index 0000000000..f34722cff5 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/CachingStream.php @@ -0,0 +1,153 @@ +remoteStream = $stream; + $this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+')); + } + + public function getSize(): ?int + { + $remoteSize = $this->remoteStream->getSize(); + + if (null === $remoteSize) { + return null; + } + + return max($this->stream->getSize(), $remoteSize); + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + if ($whence === SEEK_SET) { + $byte = $offset; + } elseif ($whence === SEEK_CUR) { + $byte = $offset + $this->tell(); + } elseif ($whence === SEEK_END) { + $size = $this->remoteStream->getSize(); + if ($size === null) { + $size = $this->cacheEntireStream(); + } + $byte = $size + $offset; + } else { + throw new \InvalidArgumentException('Invalid whence'); + } + + $diff = $byte - $this->stream->getSize(); + + if ($diff > 0) { + // 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(); + } + } else { + // We can just do a normal seek since we've already seen this byte. + $this->stream->seek($byte); + } + } + + public function read($length): string + { + // Perform a regular read on any previously read data from the buffer + $data = $this->stream->read($length); + $remaining = $length - strlen($data); + + // More data was requested so read from the remote stream + if ($remaining) { + // If data was written to the buffer in a position that would have + // 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 + ); + + if ($this->skipReadBytes) { + $len = strlen($remoteData); + $remoteData = substr($remoteData, $this->skipReadBytes); + $this->skipReadBytes = max(0, $this->skipReadBytes - $len); + } + + $data .= $remoteData; + $this->stream->write($remoteData); + } + + return $data; + } + + public function write($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(); + if ($overflow > 0) { + $this->skipReadBytes += $overflow; + } + + return $this->stream->write($string); + } + + public function eof(): bool + { + return $this->stream->eof() && $this->remoteStream->eof(); + } + + /** + * Close both the remote stream and buffer stream + */ + public function close(): void + { + $this->remoteStream->close(); + $this->stream->close(); + } + + private function cacheEntireStream(): int + { + $target = new FnStream(['write' => 'strlen']); + Utils::copyToStream($this, $target); + + return $this->tell(); + } +} diff --git a/lib/guzzlehttp/psr7/src/DroppingStream.php b/lib/guzzlehttp/psr7/src/DroppingStream.php new file mode 100644 index 0000000000..6e3d209d0e --- /dev/null +++ b/lib/guzzlehttp/psr7/src/DroppingStream.php @@ -0,0 +1,49 @@ +stream = $stream; + $this->maxLength = $maxLength; + } + + public function write($string): int + { + $diff = $this->maxLength - $this->stream->getSize(); + + // Begin returning 0 when the underlying stream is too large. + if ($diff <= 0) { + return 0; + } + + // Write the stream or a subset of the stream if needed. + if (strlen($string) < $diff) { + return $this->stream->write($string); + } + + return $this->stream->write(substr($string, 0, $diff)); + } +} diff --git a/lib/guzzlehttp/psr7/src/Exception/MalformedUriException.php b/lib/guzzlehttp/psr7/src/Exception/MalformedUriException.php new file mode 100644 index 0000000000..3a084779a8 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/Exception/MalformedUriException.php @@ -0,0 +1,14 @@ + */ + private $methods; + + /** + * @param array $methods Hash of method name to a callable. + */ + public function __construct(array $methods) + { + $this->methods = $methods; + + // Create the functions on the class + foreach ($methods as $name => $fn) { + $this->{'_fn_' . $name} = $fn; + } + } + + /** + * Lazily determine which methods are not implemented. + * + * @throws \BadMethodCallException + */ + public function __get(string $name): void + { + throw new \BadMethodCallException(str_replace('_fn_', '', $name) + . '() is not implemented in the FnStream'); + } + + /** + * The close method is called on the underlying stream only if possible. + */ + public function __destruct() + { + if (isset($this->_fn_close)) { + call_user_func($this->_fn_close); + } + } + + /** + * An unserialize would allow the __destruct to run when the unserialized value goes out of scope. + * + * @throws \LogicException + */ + public function __wakeup(): void + { + throw new \LogicException('FnStream should never be unserialized'); + } + + /** + * Adds custom functionality to an underlying stream by intercepting + * specific method calls. + * + * @param StreamInterface $stream Stream to decorate + * @param array $methods Hash of method name to a closure + * + * @return FnStream + */ + public static function decorate(StreamInterface $stream, array $methods) + { + // If any of the required methods were not provided, then simply + // proxy to the decorated stream. + foreach (array_diff(self::SLOTS, array_keys($methods)) as $diff) { + /** @var callable $callable */ + $callable = [$stream, $diff]; + $methods[$diff] = $callable; + } + + return new self($methods); + } + + 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 ''; + } + } + + public function close(): void + { + call_user_func($this->_fn_close); + } + + public function detach() + { + return call_user_func($this->_fn_detach); + } + + public function getSize(): ?int + { + return call_user_func($this->_fn_getSize); + } + + public function tell(): int + { + return call_user_func($this->_fn_tell); + } + + public function eof(): bool + { + return call_user_func($this->_fn_eof); + } + + public function isSeekable(): bool + { + return call_user_func($this->_fn_isSeekable); + } + + public function rewind(): void + { + call_user_func($this->_fn_rewind); + } + + public function seek($offset, $whence = SEEK_SET): void + { + call_user_func($this->_fn_seek, $offset, $whence); + } + + public function isWritable(): bool + { + return call_user_func($this->_fn_isWritable); + } + + public function write($string): int + { + return call_user_func($this->_fn_write, $string); + } + + public function isReadable(): bool + { + return call_user_func($this->_fn_isReadable); + } + + public function read($length): string + { + return call_user_func($this->_fn_read, $length); + } + + public function getContents(): string + { + return call_user_func($this->_fn_getContents); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + return call_user_func($this->_fn_getMetadata, $key); + } +} diff --git a/lib/guzzlehttp/psr7/src/Header.php b/lib/guzzlehttp/psr7/src/Header.php new file mode 100644 index 0000000000..10744ed455 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/Header.php @@ -0,0 +1,134 @@ +]+>|[^=]+/', $kvp, $matches)) { + $m = $matches[0]; + if (isset($m[1])) { + $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); + } else { + $part[] = trim($m[0], $trimmed); + } + } + } + if ($part) { + $params[] = $part; + } + } + } + + return $params; + } + + /** + * Converts an array of header values that may contain comma separated + * headers into an array of headers with no comma separated values. + * + * @param string|array $header Header to normalize. + * + * @deprecated Use self::splitList() instead. + */ + public static function normalize($header): array + { + $result = []; + foreach ((array) $header as $value) { + foreach (self::splitList($value) as $parsed) { + $result[] = $parsed; + } + } + + return $result; + } + + /** + * Splits a HTTP header defined to contain comma-separated list into + * each individual value. Empty values will be removed. + * + * 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'. + * + * @param string|string[] $values Header value as returned by MessageInterface::getHeader() + * + * @return string[] + */ + public static function splitList($values): array + { + if (!\is_array($values)) { + $values = [$values]; + } + + $result = []; + foreach ($values as $value) { + if (!\is_string($value)) { + throw new \TypeError('$header must either be a string or an array containing strings.'); + } + + $v = ''; + $isQuoted = false; + $isEscaped = false; + for ($i = 0, $max = \strlen($value); $i < $max; $i++) { + if ($isEscaped) { + $v .= $value[$i]; + $isEscaped = false; + + continue; + } + + if (!$isQuoted && $value[$i] === ',') { + $v = \trim($v); + if ($v !== '') { + $result[] = $v; + } + + $v = ''; + continue; + } + + if ($isQuoted && $value[$i] === '\\') { + $isEscaped = true; + $v .= $value[$i]; + + continue; + } + if ($value[$i] === '"') { + $isQuoted = !$isQuoted; + $v .= $value[$i]; + + continue; + } + + $v .= $value[$i]; + } + + $v = \trim($v); + if ($v !== '') { + $result[] = $v; + } + } + + return $result; + } +} diff --git a/lib/guzzlehttp/psr7/src/HttpFactory.php b/lib/guzzlehttp/psr7/src/HttpFactory.php new file mode 100644 index 0000000000..30be222fc0 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/HttpFactory.php @@ -0,0 +1,100 @@ +getSize(); + } + + return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType); + } + + public function createStream(string $content = ''): StreamInterface + { + return Utils::streamFor($content); + } + + public function createStreamFromFile(string $file, string $mode = 'r'): StreamInterface + { + try { + $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 $e; + } + + return Utils::streamFor($resource); + } + + public function createStreamFromResource($resource): StreamInterface + { + return Utils::streamFor($resource); + } + + public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface + { + if (empty($method)) { + if (!empty($serverParams['REQUEST_METHOD'])) { + $method = $serverParams['REQUEST_METHOD']; + } else { + throw new \InvalidArgumentException('Cannot determine HTTP method'); + } + } + + return new ServerRequest($method, $uri, [], null, '1.1', $serverParams); + } + + public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface + { + return new Response($code, [], null, '1.1', $reasonPhrase); + } + + public function createRequest(string $method, $uri): RequestInterface + { + return new Request($method, $uri); + } + + public function createUri(string $uri = ''): UriInterface + { + return new Uri($uri); + } +} diff --git a/lib/guzzlehttp/psr7/src/InflateStream.php b/lib/guzzlehttp/psr7/src/InflateStream.php new file mode 100644 index 0000000000..8e00f1c324 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/InflateStream.php @@ -0,0 +1,37 @@ + 15 + 32]); + $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource)); + } +} diff --git a/lib/guzzlehttp/psr7/src/LazyOpenStream.php b/lib/guzzlehttp/psr7/src/LazyOpenStream.php new file mode 100644 index 0000000000..5618331f13 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/LazyOpenStream.php @@ -0,0 +1,41 @@ +filename = $filename; + $this->mode = $mode; + } + + /** + * Creates the underlying stream lazily when required. + */ + protected function createStream(): StreamInterface + { + return Utils::streamFor(Utils::tryFopen($this->filename, $this->mode)); + } +} diff --git a/lib/guzzlehttp/psr7/src/LimitStream.php b/lib/guzzlehttp/psr7/src/LimitStream.php new file mode 100644 index 0000000000..fb2232557b --- /dev/null +++ b/lib/guzzlehttp/psr7/src/LimitStream.php @@ -0,0 +1,157 @@ +stream = $stream; + $this->setLimit($limit); + $this->setOffset($offset); + } + + public function eof(): bool + { + // Always return true if the underlying stream is EOF + if ($this->stream->eof()) { + return true; + } + + // No limit and the underlying stream is not at EOF + if ($this->limit === -1) { + return false; + } + + return $this->stream->tell() >= $this->offset + $this->limit; + } + + /** + * Returns the size of the limited subset of data + */ + public function getSize(): ?int + { + if (null === ($length = $this->stream->getSize())) { + return null; + } elseif ($this->limit === -1) { + return $length - $this->offset; + } else { + return min($this->limit, $length - $this->offset); + } + } + + /** + * Allow for a bounded seek on the read limited stream + */ + public function seek($offset, $whence = SEEK_SET): void + { + if ($whence !== SEEK_SET || $offset < 0) { + throw new \RuntimeException(sprintf( + 'Cannot seek to offset %s with whence %s', + $offset, + $whence + )); + } + + $offset += $this->offset; + + if ($this->limit !== -1) { + if ($offset > $this->offset + $this->limit) { + $offset = $this->offset + $this->limit; + } + } + + $this->stream->seek($offset); + } + + /** + * Give a relative tell() + */ + public function tell(): int + { + return $this->stream->tell() - $this->offset; + } + + /** + * Set the offset to start limiting from + * + * @param int $offset Offset to seek to and begin byte limiting from + * + * @throws \RuntimeException if the stream cannot be seeked. + */ + public function setOffset(int $offset): void + { + $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); + } + } + + $this->offset = $offset; + } + + /** + * Set the limit of bytes that the decorator allows to be read from the + * stream. + * + * @param int $limit Number of bytes to allow to be read from the stream. + * Use -1 for no limit. + */ + public function setLimit(int $limit): void + { + $this->limit = $limit; + } + + public function read($length): string + { + 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(); + if ($remaining > 0) { + // Only return the amount of requested data, ensuring that the byte + // limit is not exceeded + return $this->stream->read(min($remaining, $length)); + } + + return ''; + } +} diff --git a/lib/guzzlehttp/psr7/src/Message.php b/lib/guzzlehttp/psr7/src/Message.php new file mode 100644 index 0000000000..335a6dd385 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/Message.php @@ -0,0 +1,245 @@ +getMethod() . ' ' + . $message->getRequestTarget()) + . ' HTTP/' . $message->getProtocolVersion(); + if (!$message->hasHeader('host')) { + $msg .= "\r\nHost: " . $message->getUri()->getHost(); + } + } elseif ($message instanceof ResponseInterface) { + $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') { + foreach ($values as $value) { + $msg .= "\r\n{$name}: " . $value; + } + } else { + $msg .= "\r\n{$name}: " . implode(', ', $values); + } + } + + return "{$msg}\r\n\r\n" . $message->getBody(); + } + + /** + * Get a short summary of the message body. + * + * Will return `null` if the response is not printable. + * + * @param MessageInterface $message The message to get the body summary + * @param int $truncateAt The maximum allowed size of the summary + */ + public static function bodySummary(MessageInterface $message, int $truncateAt = 120): ?string + { + $body = $message->getBody(); + + if (!$body->isSeekable() || !$body->isReadable()) { + return null; + } + + $size = $body->getSize(); + + if ($size === 0) { + return null; + } + + $summary = $body->read($truncateAt); + $body->rewind(); + + if ($size > $truncateAt) { + $summary .= ' (truncated...)'; + } + + // Matches any printable character, including unicode characters: + // letters, marks, numbers, punctuation, spacing, and separators. + if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary)) { + return null; + } + + return $summary; + } + + /** + * 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`. + * + * @param MessageInterface $message Message to rewind + * + * @throws \RuntimeException + */ + public static function rewindBody(MessageInterface $message): void + { + $body = $message->getBody(); + + if ($body->tell()) { + $body->rewind(); + } + } + + /** + * 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. + * + * @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, + ]; + } + + /** + * Constructs a URI for an HTTP request message. + * + * @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, '/'); + } + + /** + * Parses a request message string into a request object. + * + * @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]); + } + + /** + * Parses a response message string into a response object. + * + * @param string $message Response message string. + */ + 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 + ); + } +} diff --git a/lib/guzzlehttp/psr7/src/MessageTrait.php b/lib/guzzlehttp/psr7/src/MessageTrait.php new file mode 100644 index 0000000000..d2dc28b602 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/MessageTrait.php @@ -0,0 +1,264 @@ + Map of all registered headers, as original name => array of values */ + private $headers = []; + + /** @var array Map of lowercase header name => original name at registration */ + private $headerNames = []; + + /** @var string */ + private $protocol = '1.1'; + + /** @var StreamInterface|null */ + private $stream; + + public function getProtocolVersion(): string + { + return $this->protocol; + } + + public function withProtocolVersion($version): MessageInterface + { + if ($this->protocol === $version) { + return $this; + } + + $new = clone $this; + $new->protocol = $version; + return $new; + } + + public function getHeaders(): array + { + return $this->headers; + } + + public function hasHeader($header): bool + { + return isset($this->headerNames[strtolower($header)]); + } + + public function getHeader($header): array + { + $header = strtolower($header); + + if (!isset($this->headerNames[$header])) { + return []; + } + + $header = $this->headerNames[$header]; + + return $this->headers[$header]; + } + + public function getHeaderLine($header): string + { + return implode(', ', $this->getHeader($header)); + } + + public function withHeader($header, $value): MessageInterface + { + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + unset($new->headers[$new->headerNames[$normalized]]); + } + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + + return $new; + } + + public function withAddedHeader($header, $value): MessageInterface + { + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $new->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + } + + return $new; + } + + public function withoutHeader($header): MessageInterface + { + $normalized = strtolower($header); + + if (!isset($this->headerNames[$normalized])) { + return $this; + } + + $header = $this->headerNames[$normalized]; + + $new = clone $this; + unset($new->headers[$header], $new->headerNames[$normalized]); + + return $new; + } + + public function getBody(): StreamInterface + { + if (!$this->stream) { + $this->stream = Utils::streamFor(''); + } + + return $this->stream; + } + + public function withBody(StreamInterface $body): MessageInterface + { + if ($body === $this->stream) { + return $this; + } + + $new = clone $this; + $new->stream = $body; + return $new; + } + + /** + * @param array $headers + */ + private function setHeaders(array $headers): void + { + $this->headerNames = $this->headers = []; + foreach ($headers as $header => $value) { + // Numeric array keys are converted to int by PHP. + $header = (string) $header; + + $this->assertHeader($header); + $value = $this->normalizeHeaderValue($value); + $normalized = strtolower($header); + if (isset($this->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $this->headers[$header] = array_merge($this->headers[$header], $value); + } else { + $this->headerNames[$normalized] = $header; + $this->headers[$header] = $value; + } + } + } + + /** + * @param mixed $value + * + * @return string[] + */ + private function normalizeHeaderValue($value): array + { + if (!is_array($value)) { + return $this->trimAndValidateHeaderValues([$value]); + } + + if (count($value) === 0) { + throw new \InvalidArgumentException('Header value can not be an empty array.'); + } + + return $this->trimAndValidateHeaderValues($value); + } + + /** + * Trims whitespace from the header values. + * + * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field. + * + * header-field = field-name ":" OWS field-value OWS + * OWS = *( SP / HTAB ) + * + * @param mixed[] $values Header values + * + * @return string[] Trimmed header values + * + * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 + */ + private function trimAndValidateHeaderValues(array $values): array + { + return array_map(function ($value) { + if (!is_scalar($value) && null !== $value) { + throw new \InvalidArgumentException(sprintf( + 'Header value must be scalar or null but %s provided.', + is_object($value) ? get_class($value) : gettype($value) + )); + } + + $trimmed = trim((string) $value, " \t"); + $this->assertValue($trimmed); + + return $trimmed; + }, array_values($values)); + } + + /** + * @see https://tools.ietf.org/html/rfc7230#section-3.2 + * + * @param mixed $header + */ + private function assertHeader($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 (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/', $header)) { + throw new \InvalidArgumentException( + sprintf( + '"%s" is not valid header name', + $header + ) + ); + } + } + + /** + * @see https://tools.ietf.org/html/rfc7230#section-3.2 + * + * field-value = *( field-content / obs-fold ) + * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + * field-vchar = VCHAR / obs-text + * VCHAR = %x21-7E + * obs-text = %x80-FF + * obs-fold = CRLF 1*( SP / HTAB ) + */ + private function assertValue(string $value): void + { + // The regular expression intentionally does not support the obs-fold production, because as + // per RFC 7230#3.2.4: + // + // 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]*$/', $value)) { + throw new \InvalidArgumentException(sprintf('"%s" is not valid header value', $value)); + } + } +} diff --git a/lib/guzzlehttp/psr7/src/MimeType.php b/lib/guzzlehttp/psr7/src/MimeType.php new file mode 100644 index 0000000000..0debbd18c3 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/MimeType.php @@ -0,0 +1,1237 @@ + 'application/vnd.1000minds.decision-model+xml', + '3dml' => 'text/vnd.in3d.3dml', + '3ds' => 'image/x-3ds', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gp', + '3gpp' => 'video/3gpp', + '3mf' => 'model/3mf', + '7z' => 'application/x-7z-compressed', + '7zip' => 'application/x-7z-compressed', + '123' => 'application/vnd.lotus-1-2-3', + 'aab' => 'application/x-authorware-bin', + 'aac' => 'audio/x-acc', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abw' => 'application/x-abiword', + 'ac' => 'application/vnd.nokia.n-gage.ac+xml', + 'ac3' => 'audio/ac3', + 'acc' => 'application/vnd.americandynamics.acc', + 'ace' => 'application/x-ace-compressed', + 'acu' => 'application/vnd.acucobol', + 'acutc' => 'application/vnd.acucorp', + 'adp' => 'audio/adpcm', + 'aep' => 'application/vnd.audiograph', + 'afm' => 'application/x-font-type1', + 'afp' => 'application/vnd.ibm.modcap', + 'age' => 'application/vnd.age', + 'ahead' => 'application/vnd.ahead.space', + 'ai' => 'application/pdf', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'air' => 'application/vnd.adobe.air-application-installer-package+zip', + 'ait' => 'application/vnd.dvb.ait', + 'ami' => 'application/vnd.amiga.ami', + 'amr' => 'audio/amr', + 'apk' => 'application/vnd.android.package-archive', + 'apng' => 'image/apng', + 'appcache' => 'text/cache-manifest', + 'application' => 'application/x-ms-application', + 'apr' => 'application/vnd.lotus-approach', + 'arc' => 'application/x-freearc', + 'arj' => 'application/x-arj', + 'asc' => 'application/pgp-signature', + 'asf' => 'video/x-ms-asf', + 'asm' => 'text/x-asm', + 'aso' => 'application/vnd.accpac.simply.aso', + 'asx' => 'video/x-ms-asf', + 'atc' => 'application/vnd.acucorp', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomdeleted' => 'application/atomdeleted+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'atx' => 'application/vnd.antix.game-component', + 'au' => 'audio/x-au', + 'avci' => 'image/avci', + 'avcs' => 'image/avcs', + 'avi' => 'video/x-msvideo', + 'avif' => 'image/avif', + 'aw' => 'application/applixware', + 'azf' => 'application/vnd.airzip.filesecure.azf', + 'azs' => 'application/vnd.airzip.filesecure.azs', + 'azv' => 'image/vnd.airzip.accelerator.azv', + 'azw' => 'application/vnd.amazon.ebook', + 'b16' => 'image/vnd.pco.b16', + 'bat' => 'application/x-msdownload', + 'bcpio' => 'application/x-bcpio', + 'bdf' => 'application/x-font-bdf', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'bdoc' => 'application/x-bdoc', + 'bed' => 'application/vnd.realvnc.bed', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'bin' => 'application/octet-stream', + 'blb' => 'application/x-blorb', + 'blorb' => 'application/x-blorb', + 'bmi' => 'application/vnd.bmi', + 'bmml' => 'application/vnd.balsamiq.bmml+xml', + 'bmp' => 'image/bmp', + 'book' => 'application/vnd.framemaker', + 'box' => 'application/vnd.previewsystems.box', + 'boz' => 'application/x-bzip2', + 'bpk' => 'application/octet-stream', + 'bpmn' => 'application/octet-stream', + 'bsp' => 'model/vnd.valve.source.compiled-map', + 'btif' => 'image/prs.btif', + 'buffer' => 'application/octet-stream', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c4d' => 'application/vnd.clonk.c4group', + 'c4f' => 'application/vnd.clonk.c4group', + 'c4g' => 'application/vnd.clonk.c4group', + 'c4p' => 'application/vnd.clonk.c4group', + 'c4u' => 'application/vnd.clonk.c4group', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', + 'cab' => 'application/vnd.ms-cab-compressed', + 'caf' => 'audio/x-caf', + 'cap' => 'application/vnd.tcpdump.pcap', + 'car' => 'application/vnd.curl.car', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cb7' => 'application/x-cbr', + 'cba' => 'application/x-cbr', + 'cbr' => 'application/x-cbr', + 'cbt' => 'application/x-cbr', + 'cbz' => 'application/x-cbr', + 'cc' => 'text/x-c', + 'cco' => 'application/x-cocoa', + 'cct' => 'application/x-director', + 'ccxml' => 'application/ccxml+xml', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cdf' => 'application/x-netcdf', + 'cdfx' => 'application/cdfx+xml', + 'cdkey' => 'application/vnd.mediastation.cdkey', + 'cdmia' => 'application/cdmi-capability', + 'cdmic' => 'application/cdmi-container', + 'cdmid' => 'application/cdmi-domain', + 'cdmio' => 'application/cdmi-object', + 'cdmiq' => 'application/cdmi-queue', + 'cdr' => 'application/cdr', + 'cdx' => 'chemical/x-cdx', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'cdy' => 'application/vnd.cinderella', + 'cer' => 'application/pkix-cert', + 'cfs' => 'application/x-cfs-compressed', + 'cgm' => 'image/cgm', + 'chat' => 'application/x-chat', + 'chm' => 'application/vnd.ms-htmlhelp', + 'chrt' => 'application/vnd.kde.kchart', + 'cif' => 'chemical/x-cif', + 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'cil' => 'application/vnd.ms-artgalry', + 'cjs' => 'application/node', + 'cla' => 'application/vnd.claymore', + 'class' => 'application/octet-stream', + 'clkk' => 'application/vnd.crick.clicker.keyboard', + 'clkp' => 'application/vnd.crick.clicker.palette', + 'clkt' => 'application/vnd.crick.clicker.template', + 'clkw' => 'application/vnd.crick.clicker.wordbank', + 'clkx' => 'application/vnd.crick.clicker', + 'clp' => 'application/x-msclip', + 'cmc' => 'application/vnd.cosmocaller', + 'cmdf' => 'chemical/x-cmdf', + 'cml' => 'chemical/x-cml', + 'cmp' => 'application/vnd.yellowriver-custom-menu', + 'cmx' => 'image/x-cmx', + 'cod' => 'application/vnd.rim.cod', + 'coffee' => 'text/coffeescript', + 'com' => 'application/x-msdownload', + 'conf' => 'text/plain', + 'cpio' => 'application/x-cpio', + 'cpl' => 'application/cpl+xml', + 'cpp' => 'text/x-c', + 'cpt' => 'application/mac-compactpro', + 'crd' => 'application/x-mscardfile', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'crx' => 'application/x-chrome-extension', + 'cryptonote' => 'application/vnd.rig.cryptonote', + 'csh' => 'application/x-csh', + 'csl' => 'application/vnd.citationstyles.style+xml', + 'csml' => 'chemical/x-csml', + 'csp' => 'application/vnd.commonspace', + 'csr' => 'application/octet-stream', + 'css' => 'text/css', + 'cst' => 'application/x-director', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'curl' => 'text/vnd.curl', + 'cww' => 'application/prs.cww', + 'cxt' => 'application/x-director', + 'cxx' => 'text/x-c', + 'dae' => 'model/vnd.collada+xml', + 'daf' => 'application/vnd.mobius.daf', + 'dart' => 'application/vnd.dart', + 'dataless' => 'application/vnd.fdsn.seed', + 'davmount' => 'application/davmount+xml', + 'dbf' => 'application/vnd.dbf', + 'dbk' => 'application/docbook+xml', + 'dcr' => 'application/x-director', + 'dcurl' => 'text/vnd.curl.dcurl', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'ddf' => 'application/vnd.syncml.dmddf+xml', + 'dds' => 'image/vnd.ms-dds', + 'deb' => 'application/x-debian-package', + 'def' => 'text/plain', + 'deploy' => 'application/octet-stream', + 'der' => 'application/x-x509-ca-cert', + 'dfac' => 'application/vnd.dreamfactory', + 'dgc' => 'application/x-dgc-compressed', + 'dic' => 'text/x-c', + 'dir' => 'application/x-director', + 'dis' => 'application/vnd.mobius.dis', + 'disposition-notification' => 'message/disposition-notification', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/octet-stream', + 'dmg' => 'application/x-apple-diskimage', + 'dmn' => 'application/octet-stream', + 'dmp' => 'application/vnd.tcpdump.pcap', + 'dms' => 'application/octet-stream', + 'dna' => 'application/vnd.dna', + 'doc' => 'application/msword', + 'docm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dot' => 'application/msword', + 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'dp' => 'application/vnd.osgi.dp', + 'dpg' => 'application/vnd.dpgraph', + 'dra' => 'audio/vnd.dra', + 'drle' => 'image/dicom-rle', + 'dsc' => 'text/prs.lines.tag', + 'dssc' => 'application/dssc+der', + 'dtb' => 'application/x-dtbook+xml', + 'dtd' => 'application/xml-dtd', + 'dts' => 'audio/vnd.dts', + 'dtshd' => 'audio/vnd.dts.hd', + 'dump' => 'application/octet-stream', + 'dvb' => 'video/vnd.dvb.file', + 'dvi' => 'application/x-dvi', + 'dwd' => 'application/atsc-dwd+xml', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'dxp' => 'application/vnd.spotfire.dxp', + 'dxr' => 'application/x-director', + 'ear' => 'application/java-archive', + 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', + 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', + 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', + 'ecma' => 'application/ecmascript', + 'edm' => 'application/vnd.novadigm.edm', + 'edx' => 'application/vnd.novadigm.edx', + 'efif' => 'application/vnd.picsel', + 'ei6' => 'application/vnd.pg.osasli', + 'elc' => 'application/octet-stream', + 'emf' => 'image/emf', + 'eml' => 'message/rfc822', + 'emma' => 'application/emma+xml', + 'emotionml' => 'application/emotionml+xml', + 'emz' => 'application/x-msmetafile', + 'eol' => 'audio/vnd.digital-winds', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'es' => 'application/ecmascript', + 'es3' => 'application/vnd.eszigno3+xml', + 'esa' => 'application/vnd.osgi.subsystem', + 'esf' => 'application/vnd.epson.esf', + 'et3' => 'application/vnd.eszigno3+xml', + 'etx' => 'text/x-setext', + 'eva' => 'application/x-eva', + 'evy' => 'application/x-envoy', + 'exe' => 'application/octet-stream', + 'exi' => 'application/exi', + 'exp' => 'application/express', + 'exr' => 'image/aces', + 'ext' => 'application/vnd.novadigm.ext', + 'ez' => 'application/andrew-inset', + 'ez2' => 'application/vnd.ezpix-album', + 'ez3' => 'application/vnd.ezpix-package', + 'f' => 'text/x-fortran', + 'f4v' => 'video/mp4', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fbs' => 'image/vnd.fastbidsheet', + 'fcdt' => 'application/vnd.adobe.formscentral.fcdt', + 'fcs' => 'application/vnd.isac.fcs', + 'fdf' => 'application/vnd.fdf', + 'fdt' => 'application/fdt+xml', + 'fe_launch' => 'application/vnd.denovo.fcselayout-link', + 'fg5' => 'application/vnd.fujitsu.oasysgp', + 'fgd' => 'application/x-director', + 'fh' => 'image/x-freehand', + 'fh4' => 'image/x-freehand', + 'fh5' => 'image/x-freehand', + 'fh7' => 'image/x-freehand', + 'fhc' => 'image/x-freehand', + 'fig' => 'application/x-xfig', + 'fits' => 'image/fits', + 'flac' => 'audio/x-flac', + 'fli' => 'video/x-fli', + 'flo' => 'application/vnd.micrografx.flo', + 'flv' => 'video/x-flv', + 'flw' => 'application/vnd.kde.kivio', + 'flx' => 'text/vnd.fmi.flexstor', + 'fly' => 'text/vnd.fly', + 'fm' => 'application/vnd.framemaker', + 'fnc' => 'application/vnd.frogans.fnc', + 'fo' => 'application/vnd.software602.filler.form+xml', + 'for' => 'text/x-fortran', + 'fpx' => 'image/vnd.fpx', + 'frame' => 'application/vnd.framemaker', + 'fsc' => 'application/vnd.fsc.weblaunch', + 'fst' => 'image/vnd.fst', + 'ftc' => 'application/vnd.fluxtime.clip', + 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'fvt' => 'video/vnd.fvt', + 'fxp' => 'application/vnd.adobe.fxp', + 'fxpl' => 'application/vnd.adobe.fxp', + 'fzs' => 'application/vnd.fuzzysheet', + 'g2w' => 'application/vnd.geoplan', + 'g3' => 'image/g3fax', + 'g3w' => 'application/vnd.geospace', + 'gac' => 'application/vnd.groove-account', + 'gam' => 'application/x-tads', + 'gbr' => 'application/rpki-ghostbusters', + 'gca' => 'application/x-gca-compressed', + 'gdl' => 'model/vnd.gdl', + 'gdoc' => 'application/vnd.google-apps.document', + 'ged' => 'text/vnd.familysearch.gedcom', + 'geo' => 'application/vnd.dynageo', + 'geojson' => 'application/geo+json', + 'gex' => 'application/vnd.geometry-explorer', + 'ggb' => 'application/vnd.geogebra.file', + 'ggt' => 'application/vnd.geogebra.tool', + 'ghf' => 'application/vnd.groove-help', + 'gif' => 'image/gif', + 'gim' => 'application/vnd.groove-identity-message', + 'glb' => 'model/gltf-binary', + 'gltf' => 'model/gltf+json', + 'gml' => 'application/gml+xml', + 'gmx' => 'application/vnd.gmx', + 'gnumeric' => 'application/x-gnumeric', + 'gpg' => 'application/gpg-keys', + 'gph' => 'application/vnd.flographit', + 'gpx' => 'application/gpx+xml', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gram' => 'application/srgs', + 'gramps' => 'application/x-gramps-xml', + 'gre' => 'application/vnd.geometry-explorer', + 'grv' => 'application/vnd.groove-injector', + 'grxml' => 'application/srgs+xml', + 'gsf' => 'application/x-font-ghostscript', + 'gsheet' => 'application/vnd.google-apps.spreadsheet', + 'gslides' => 'application/vnd.google-apps.presentation', + 'gtar' => 'application/x-gtar', + 'gtm' => 'application/vnd.groove-tool-message', + 'gtw' => 'model/vnd.gtw', + 'gv' => 'text/vnd.graphviz', + 'gxf' => 'application/gxf', + 'gxt' => 'application/vnd.geonext', + 'gz' => 'application/gzip', + 'gzip' => 'application/gzip', + 'h' => 'text/x-c', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'hal' => 'application/vnd.hal+xml', + 'hbci' => 'application/vnd.hbci', + 'hbs' => 'text/x-handlebars-template', + 'hdd' => 'application/x-virtualbox-hdd', + 'hdf' => 'application/x-hdf', + 'heic' => 'image/heic', + 'heics' => 'image/heic-sequence', + 'heif' => 'image/heif', + 'heifs' => 'image/heif-sequence', + 'hej2' => 'image/hej2k', + 'held' => 'application/atsc-held+xml', + 'hh' => 'text/x-c', + 'hjson' => 'application/hjson', + 'hlp' => 'application/winhlp', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'hqx' => 'application/mac-binhex40', + 'hsj2' => 'image/hsj2', + 'htc' => 'text/x-component', + 'htke' => 'application/vnd.kenameaapp', + 'htm' => 'text/html', + 'html' => 'text/html', + 'hvd' => 'application/vnd.yamaha.hv-dic', + 'hvp' => 'application/vnd.yamaha.hv-voice', + 'hvs' => 'application/vnd.yamaha.hv-script', + 'i2g' => 'application/vnd.intergeo', + 'icc' => 'application/vnd.iccprofile', + 'ice' => 'x-conference/x-cooltalk', + 'icm' => 'application/vnd.iccprofile', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ief' => 'image/ief', + 'ifb' => 'text/calendar', + 'ifm' => 'application/vnd.shana.informed.formdata', + 'iges' => 'model/iges', + 'igl' => 'application/vnd.igloader', + 'igm' => 'application/vnd.insors.igm', + 'igs' => 'model/iges', + 'igx' => 'application/vnd.micrografx.igx', + 'iif' => 'application/vnd.shana.informed.interchange', + 'img' => 'application/octet-stream', + 'imp' => 'application/vnd.accpac.simply.imp', + 'ims' => 'application/vnd.ms-ims', + 'in' => 'text/plain', + 'ini' => 'text/plain', + 'ink' => 'application/inkml+xml', + 'inkml' => 'application/inkml+xml', + 'install' => 'application/x-install-instructions', + 'iota' => 'application/vnd.astraea-software.iota', + 'ipfix' => 'application/ipfix', + 'ipk' => 'application/vnd.shana.informed.package', + 'irm' => 'application/vnd.ibm.rights-management', + 'irp' => 'application/vnd.irepository.package+xml', + 'iso' => 'application/x-iso9660-image', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'its' => 'application/its+xml', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jade' => 'text/jade', + 'jam' => 'application/vnd.jam', + 'jar' => 'application/java-archive', + 'jardiff' => 'application/x-java-archive-diff', + 'java' => 'text/x-java-source', + 'jhc' => 'image/jphc', + 'jisp' => 'application/vnd.jisp', + 'jls' => 'image/jls', + 'jlt' => 'application/vnd.hp-jlyt', + 'jng' => 'image/x-jng', + 'jnlp' => 'application/x-java-jnlp-file', + 'joda' => 'application/vnd.joost.joda-archive', + 'jp2' => 'image/jp2', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpf' => 'image/jpx', + 'jpg' => 'image/jpeg', + 'jpg2' => 'image/jp2', + 'jpgm' => 'video/jpm', + 'jpgv' => 'video/jpeg', + 'jph' => 'image/jph', + 'jpm' => 'video/jpm', + 'jpx' => 'image/jpx', + 'js' => 'application/javascript', + 'json' => 'application/json', + 'json5' => 'application/json5', + 'jsonld' => 'application/ld+json', + 'jsonml' => 'application/jsonml+json', + 'jsx' => 'text/jsx', + 'jxr' => 'image/jxr', + 'jxra' => 'image/jxra', + 'jxrs' => 'image/jxrs', + 'jxs' => 'image/jxs', + 'jxsc' => 'image/jxsc', + 'jxsi' => 'image/jxsi', + 'jxss' => 'image/jxss', + 'kar' => 'audio/midi', + 'karbon' => 'application/vnd.kde.karbon', + 'kdb' => 'application/octet-stream', + 'kdbx' => 'application/x-keepass2', + 'key' => 'application/x-iwork-keynote-sffkey', + 'kfo' => 'application/vnd.kde.kformula', + 'kia' => 'application/vnd.kidspiration', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kne' => 'application/vnd.kinar', + 'knp' => 'application/vnd.kinar', + 'kon' => 'application/vnd.kde.kontour', + 'kpr' => 'application/vnd.kde.kpresenter', + 'kpt' => 'application/vnd.kde.kpresenter', + 'kpxx' => 'application/vnd.ds-keypoint', + 'ksp' => 'application/vnd.kde.kspread', + 'ktr' => 'application/vnd.kahootz', + 'ktx' => 'image/ktx', + 'ktx2' => 'image/ktx2', + 'ktz' => 'application/vnd.kahootz', + 'kwd' => 'application/vnd.kde.kword', + 'kwt' => 'application/vnd.kde.kword', + 'lasxml' => 'application/vnd.las.las+xml', + 'latex' => 'application/x-latex', + 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + 'les' => 'application/vnd.hhe.lesson-player', + 'less' => 'text/less', + 'lgr' => 'application/lgr+xml', + 'lha' => 'application/octet-stream', + 'link66' => 'application/vnd.route66.link66+xml', + 'list' => 'text/plain', + 'list3820' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'litcoffee' => 'text/coffeescript', + 'lnk' => 'application/x-ms-shortcut', + 'log' => 'text/plain', + 'lostxml' => 'application/lost+xml', + 'lrf' => 'application/octet-stream', + 'lrm' => 'application/vnd.ms-lrm', + 'ltf' => 'application/vnd.frogans.ltf', + 'lua' => 'text/x-lua', + 'luac' => 'application/x-lua-bytecode', + 'lvp' => 'audio/vnd.lucent.voice', + 'lwp' => 'application/vnd.lotus-wordpro', + 'lzh' => 'application/octet-stream', + 'm1v' => 'video/mpeg', + 'm2a' => 'audio/mpeg', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'text/plain', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/x-m4a', + 'm4p' => 'application/mp4', + 'm4s' => 'video/iso.segment', + 'm4u' => 'application/vnd.mpegurl', + 'm4v' => 'video/x-m4v', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'm21' => 'application/mp21', + 'ma' => 'application/mathematica', + 'mads' => 'application/mads+xml', + 'maei' => 'application/mmt-aei+xml', + 'mag' => 'application/vnd.ecowin.chart', + 'maker' => 'application/vnd.framemaker', + 'man' => 'text/troff', + 'manifest' => 'text/cache-manifest', + 'map' => 'application/json', + 'mar' => 'application/octet-stream', + 'markdown' => 'text/markdown', + 'mathml' => 'application/mathml+xml', + 'mb' => 'application/mathematica', + 'mbk' => 'application/vnd.mobius.mbk', + 'mbox' => 'application/mbox', + 'mc1' => 'application/vnd.medcalcdata', + 'mcd' => 'application/vnd.mcd', + 'mcurl' => 'text/vnd.curl.mcurl', + 'md' => 'text/markdown', + 'mdb' => 'application/x-msaccess', + 'mdi' => 'image/vnd.ms-modi', + 'mdx' => 'text/mdx', + 'me' => 'text/troff', + 'mesh' => 'model/mesh', + 'meta4' => 'application/metalink4+xml', + 'metalink' => 'application/metalink+xml', + 'mets' => 'application/mets+xml', + 'mfm' => 'application/vnd.mfmp', + 'mft' => 'application/rpki-manifest', + 'mgp' => 'application/vnd.osgeo.mapguide.package', + 'mgz' => 'application/vnd.proteus.magazine', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mie' => 'application/x-mie', + 'mif' => 'application/vnd.mif', + 'mime' => 'message/rfc822', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mjs' => 'application/javascript', + 'mk3d' => 'video/x-matroska', + 'mka' => 'audio/x-matroska', + 'mkd' => 'text/x-markdown', + 'mks' => 'video/x-matroska', + 'mkv' => 'video/x-matroska', + 'mlp' => 'application/vnd.dolby.mlp', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'mmf' => 'application/vnd.smaf', + 'mml' => 'text/mathml', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'mng' => 'video/x-mng', + 'mny' => 'application/x-msmoney', + 'mobi' => 'application/x-mobipocket-ebook', + 'mods' => 'application/mods+xml', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4s' => 'application/mp4', + 'mp4v' => 'video/mp4', + 'mp21' => 'application/mp21', + 'mpc' => 'application/vnd.mophun.certificate', + 'mpd' => 'application/dash+xml', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpf' => 'application/media-policy-dataset+xml', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'mpga' => 'audio/mpeg', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'mpm' => 'application/vnd.blueice.multipass', + 'mpn' => 'application/vnd.mophun.application', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/vnd.ms-project', + 'mpy' => 'application/vnd.ibm.minipay', + 'mqy' => 'application/vnd.mobius.mqy', + 'mrc' => 'application/marc', + 'mrcx' => 'application/marcxml+xml', + 'ms' => 'text/troff', + 'mscml' => 'application/mediaservercontrol+xml', + 'mseed' => 'application/vnd.fdsn.mseed', + 'mseq' => 'application/vnd.mseq', + 'msf' => 'application/vnd.epson.msf', + 'msg' => 'application/vnd.ms-outlook', + 'msh' => 'model/mesh', + 'msi' => 'application/x-msdownload', + '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', + 'mus' => 'application/vnd.musician', + 'musd' => 'application/mmt-usd+xml', + 'musicxml' => 'application/vnd.recordare.musicxml+xml', + 'mvb' => 'application/x-msmediaview', + 'mvt' => 'application/vnd.mapbox-vector-tile', + 'mwf' => 'application/vnd.mfer', + 'mxf' => 'application/mxf', + 'mxl' => 'application/vnd.recordare.musicxml', + 'mxmf' => 'audio/mobile-xmf', + 'mxml' => 'application/xv+xml', + 'mxs' => 'application/vnd.triscape.mxs', + 'mxu' => 'video/vnd.mpegurl', + 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'n3' => 'text/n3', + 'nb' => 'application/mathematica', + 'nbp' => 'application/vnd.wolfram.player', + 'nc' => 'application/x-netcdf', + 'ncx' => 'application/x-dtbncx+xml', + 'nfo' => 'text/x-nfo', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + 'nitf' => 'application/vnd.nitf', + 'nlu' => 'application/vnd.neurolanguage.nlu', + 'nml' => 'application/vnd.enliven', + 'nnd' => 'application/vnd.noblenet-directory', + 'nns' => 'application/vnd.noblenet-sealer', + 'nnw' => 'application/vnd.noblenet-web', + 'npx' => 'image/vnd.net-fpx', + 'nq' => 'application/n-quads', + 'nsc' => 'application/x-conference', + 'nsf' => 'application/vnd.lotus-notes', + 'nt' => 'application/n-triples', + 'ntf' => 'application/vnd.nitf', + 'numbers' => 'application/x-iwork-numbers-sffnumbers', + 'nzb' => 'application/x-nzb', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'oas' => 'application/vnd.fujitsu.oasys', + 'obd' => 'application/x-msbinder', + 'obgx' => 'application/vnd.openblox.game+xml', + 'obj' => 'model/obj', + 'oda' => 'application/oda', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odft' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'oga' => 'audio/ogg', + 'ogex' => 'model/vnd.opengex', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'omdoc' => 'application/omdoc+xml', + 'onepkg' => 'application/onenote', + 'onetmp' => 'application/onenote', + 'onetoc' => 'application/onenote', + 'onetoc2' => 'application/onenote', + 'opf' => 'application/oebps-package+xml', + 'opml' => 'text/x-opml', + 'oprc' => 'application/vnd.palm', + 'opus' => 'audio/ogg', + 'org' => 'text/x-org', + 'osf' => 'application/vnd.yamaha.openscoreformat', + 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + 'osm' => 'application/vnd.openstreetmap.data+xml', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'otf' => 'font/otf', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'ova' => 'application/x-virtualbox-ova', + 'ovf' => 'application/x-virtualbox-ovf', + 'owl' => 'application/rdf+xml', + 'oxps' => 'application/oxps', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'p' => 'text/x-pascal', + 'p7a' => 'application/x-pkcs7-signature', + 'p7b' => 'application/x-pkcs7-certificates', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'p8' => 'application/pkcs8', + 'p10' => 'application/x-pkcs10', + 'p12' => 'application/x-pkcs12', + 'pac' => 'application/x-ns-proxy-autoconfig', + 'pages' => 'application/x-iwork-pages-sffpages', + 'pas' => 'text/x-pascal', + 'paw' => 'application/vnd.pawaafile', + 'pbd' => 'application/vnd.powerbuilder6', + 'pbm' => 'image/x-portable-bitmap', + 'pcap' => 'application/vnd.tcpdump.pcap', + 'pcf' => 'application/x-font-pcf', + 'pcl' => 'application/vnd.hp-pcl', + 'pclxl' => 'application/vnd.hp-pclxl', + 'pct' => 'image/x-pict', + 'pcurl' => 'application/vnd.curl.pcurl', + 'pcx' => 'image/x-pcx', + 'pdb' => 'application/x-pilot', + 'pde' => 'text/x-processing', + 'pdf' => 'application/pdf', + 'pem' => 'application/x-x509-user-cert', + 'pfa' => 'application/x-font-type1', + 'pfb' => 'application/x-font-type1', + 'pfm' => 'application/x-font-type1', + 'pfr' => 'application/font-tdpfr', + 'pfx' => 'application/x-pkcs12', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'pgp' => 'application/pgp', + 'phar' => 'application/octet-stream', + 'php' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'phtml' => 'application/x-httpd-php', + 'pic' => 'image/x-pict', + 'pkg' => 'application/octet-stream', + 'pki' => 'application/pkixcmp', + 'pkipath' => 'application/pkix-pkipath', + 'pkpass' => 'application/vnd.apple.pkpass', + 'pl' => 'application/x-perl', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'plc' => 'application/vnd.mobius.plc', + 'plf' => 'application/vnd.pocketlearn', + 'pls' => 'application/pls+xml', + 'pm' => 'application/x-perl', + 'pml' => 'application/vnd.ctc-posml', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'portpkg' => 'application/vnd.macports.portpkg', + 'pot' => 'application/vnd.ms-powerpoint', + 'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppa' => 'application/vnd.ms-powerpoint', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 'ppd' => 'application/vnd.cups-ppd', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/vnd.ms-powerpoint', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppt' => 'application/powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pqa' => 'application/vnd.palm', + 'prc' => 'model/prc', + 'pre' => 'application/vnd.lotus-freelance', + 'prf' => 'application/pics-rules', + 'provx' => 'application/provenance+xml', + 'ps' => 'application/postscript', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'psd' => 'application/x-photoshop', + 'psf' => 'application/x-font-linux-psf', + 'pskcxml' => 'application/pskc+xml', + 'pti' => 'image/prs.pti', + 'ptid' => 'application/vnd.pvi.ptid1', + 'pub' => 'application/x-mspublisher', + 'pvb' => 'application/vnd.3gpp.pic-bw-var', + 'pwn' => 'application/vnd.3m.post-it-notes', + 'pya' => 'audio/vnd.ms-playready.media.pya', + 'pyv' => 'video/vnd.ms-playready.media.pyv', + 'qam' => 'application/vnd.epson.quickanime', + 'qbo' => 'application/vnd.intu.qbo', + 'qfx' => 'application/vnd.intu.qfx', + 'qps' => 'application/vnd.publishare-delta-tree', + 'qt' => 'video/quicktime', + 'qwd' => 'application/vnd.quark.quarkxpress', + 'qwt' => 'application/vnd.quark.quarkxpress', + 'qxb' => 'application/vnd.quark.quarkxpress', + 'qxd' => 'application/vnd.quark.quarkxpress', + 'qxl' => 'application/vnd.quark.quarkxpress', + 'qxt' => 'application/vnd.quark.quarkxpress', + 'ra' => 'audio/x-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'raml' => 'application/raml+yaml', + 'rapd' => 'application/route-apd+xml', + 'rar' => 'application/x-rar', + 'ras' => 'image/x-cmu-raster', + 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'rdf' => 'application/rdf+xml', + 'rdz' => 'application/vnd.data-vision.rdz', + 'relo' => 'application/p2p-overlay+xml', + 'rep' => 'application/vnd.businessobjects', + 'res' => 'application/x-dtbresource+xml', + 'rgb' => 'image/x-rgb', + 'rif' => 'application/reginfo+xml', + 'rip' => 'audio/vnd.rip', + 'ris' => 'application/x-research-info-systems', + 'rl' => 'application/resource-lists+xml', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'rld' => 'application/resource-lists-diff+xml', + 'rm' => 'audio/x-pn-realaudio', + 'rmi' => 'audio/midi', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'rmvb' => 'application/vnd.rn-realmedia-vbr', + 'rnc' => 'application/relax-ng-compact-syntax', + 'rng' => 'application/xml', + 'roa' => 'application/rpki-roa', + 'roff' => 'text/troff', + 'rp9' => 'application/vnd.cloanto.rp9', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rq' => 'application/sparql-query', + 'rs' => 'application/rls-services+xml', + 'rsa' => 'application/x-pkcs7', + 'rsat' => 'application/atsc-rsat+xml', + 'rsd' => 'application/rsd+xml', + 'rsheet' => 'application/urc-ressheet+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'text/rtf', + 'rtx' => 'text/richtext', + 'run' => 'application/x-makeself', + 'rusd' => 'application/route-usd+xml', + 'rv' => 'video/vnd.rn-realvideo', + 's' => 'text/x-asm', + 's3m' => 'audio/s3m', + 'saf' => 'application/vnd.yamaha.smaf-audio', + 'sass' => 'text/x-sass', + 'sbml' => 'application/sbml+xml', + 'sc' => 'application/vnd.ibm.secure-container', + 'scd' => 'application/x-msschedule', + 'scm' => 'application/vnd.lotus-screencam', + 'scq' => 'application/scvp-cv-request', + 'scs' => 'application/scvp-cv-response', + 'scss' => 'text/x-scss', + 'scurl' => 'text/vnd.curl.scurl', + 'sda' => 'application/vnd.stardivision.draw', + 'sdc' => 'application/vnd.stardivision.calc', + 'sdd' => 'application/vnd.stardivision.impress', + 'sdkd' => 'application/vnd.solent.sdkm+xml', + 'sdkm' => 'application/vnd.solent.sdkm+xml', + 'sdp' => 'application/sdp', + 'sdw' => 'application/vnd.stardivision.writer', + 'sea' => 'application/octet-stream', + 'see' => 'application/vnd.seemail', + 'seed' => 'application/vnd.fdsn.seed', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + 'senmlx' => 'application/senml+xml', + 'sensmlx' => 'application/sensml+xml', + 'ser' => 'application/java-serialized-object', + 'setpay' => 'application/set-payment-initiation', + 'setreg' => 'application/set-registration-initiation', + 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', + 'sfs' => 'application/vnd.spotfire.sfs', + 'sfv' => 'text/x-sfv', + 'sgi' => 'image/sgi', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'shex' => 'text/shex', + 'shf' => 'application/shf+xml', + 'shtml' => 'text/html', + 'sid' => 'image/x-mrsid-image', + 'sieve' => 'application/sieve', + 'sig' => 'application/pgp-signature', + 'sil' => 'audio/silk', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sisx' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + 'siv' => 'application/sieve', + 'skd' => 'application/vnd.koan', + 'skm' => 'application/vnd.koan', + 'skp' => 'application/vnd.koan', + 'skt' => 'application/vnd.koan', + 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'slim' => 'text/slim', + 'slm' => 'text/slim', + 'sls' => 'application/route-s-tsid+xml', + 'slt' => 'application/vnd.epson.salt', + 'sm' => 'application/vnd.stepmania.stepchart', + 'smf' => 'application/vnd.stardivision.math', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'smv' => 'video/x-smv', + 'smzip' => 'application/vnd.stepmania.package', + 'snd' => 'audio/basic', + 'snf' => 'application/x-font-snf', + 'so' => 'application/octet-stream', + 'spc' => 'application/x-pkcs7-certificates', + 'spdx' => 'text/spdx', + 'spf' => 'application/vnd.yamaha.smaf-phrase', + 'spl' => 'application/x-futuresplash', + 'spot' => 'text/vnd.in3d.spot', + 'spp' => 'application/scvp-vp-response', + 'spq' => 'application/scvp-vp-request', + 'spx' => 'audio/ogg', + 'sql' => 'application/x-sql', + 'src' => 'application/x-wais-source', + 'srt' => 'application/x-subrip', + 'sru' => 'application/sru+xml', + 'srx' => 'application/sparql-results+xml', + 'ssdl' => 'application/ssdl+xml', + 'sse' => 'application/vnd.kodak-descriptor', + 'ssf' => 'application/vnd.epson.ssf', + 'ssml' => 'application/ssml+xml', + 'sst' => 'application/octet-stream', + 'st' => 'application/vnd.sailingtracker.track', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'stf' => 'application/vnd.wt.stf', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stk' => 'application/hyperstudio', + 'stl' => 'model/stl', + 'stpx' => 'model/step+xml', + 'stpxz' => 'model/step-xml+zip', + 'stpz' => 'model/step+zip', + 'str' => 'application/vnd.pg.format', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'styl' => 'text/stylus', + 'stylus' => 'text/stylus', + 'sub' => 'text/vnd.dvb.subtitle', + 'sus' => 'application/vnd.sus-calendar', + 'susp' => 'application/vnd.sus-calendar', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'svc' => 'application/vnd.dvb.service', + 'svd' => 'application/vnd.svd', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'swa' => 'application/x-director', + 'swf' => 'application/x-shockwave-flash', + 'swi' => 'application/vnd.aristanetworks.swi', + 'swidtag' => 'application/swid+xml', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 't' => 'text/troff', + 't3' => 'application/x-t3vm-image', + 't38' => 'image/t38', + 'taglet' => 'application/vnd.mynfc', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tap' => 'image/vnd.tencent.tap', + 'tar' => 'application/x-tar', + 'tcap' => 'application/vnd.3gpp2.tcap', + 'tcl' => 'application/x-tcl', + 'td' => 'application/urc-targetdesc+xml', + 'teacher' => 'application/vnd.smart.teacher', + 'tei' => 'application/tei+xml', + 'teicorpus' => 'application/tei+xml', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'text' => 'text/plain', + 'tfi' => 'application/thraud+xml', + 'tfm' => 'application/x-tex-tfm', + 'tfx' => 'image/tiff-fx', + 'tga' => 'image/x-tga', + 'tgz' => 'application/x-tar', + 'thmx' => 'application/vnd.ms-officetheme', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tk' => 'application/x-tcl', + 'tmo' => 'application/vnd.tmobile-livetv', + 'toml' => 'application/toml', + 'torrent' => 'application/x-bittorrent', + 'tpl' => 'application/vnd.groove-tool-template', + 'tpt' => 'application/vnd.trid.tpt', + 'tr' => 'text/troff', + 'tra' => 'application/vnd.trueapp', + 'trig' => 'application/trig', + 'trm' => 'application/x-msterminal', + 'ts' => 'video/mp2t', + 'tsd' => 'application/timestamped-data', + 'tsv' => 'text/tab-separated-values', + 'ttc' => 'font/collection', + 'ttf' => 'font/ttf', + 'ttl' => 'text/turtle', + 'ttml' => 'application/ttml+xml', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'txf' => 'application/vnd.mobius.txf', + 'txt' => 'text/plain', + 'u3d' => 'model/u3d', + 'u8dsn' => 'message/global-delivery-status', + 'u8hdr' => 'message/global-headers', + 'u8mdn' => 'message/global-disposition-notification', + 'u8msg' => 'message/global', + 'u32' => 'application/x-authorware-bin', + 'ubj' => 'application/ubjson', + 'udeb' => 'application/x-debian-package', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + 'ulx' => 'application/x-glulx', + 'umj' => 'application/vnd.umajin', + 'unityweb' => 'application/vnd.unity', + 'uoml' => 'application/vnd.uoml+xml', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'urls' => 'text/uri-list', + 'usdz' => 'model/vnd.usdz+zip', + 'ustar' => 'application/x-ustar', + 'utz' => 'application/vnd.uiq.theme', + 'uu' => 'text/x-uuencode', + 'uva' => 'audio/vnd.dece.audio', + 'uvd' => 'application/vnd.dece.data', + 'uvf' => 'application/vnd.dece.data', + 'uvg' => 'image/vnd.dece.graphic', + 'uvh' => 'video/vnd.dece.hd', + 'uvi' => 'image/vnd.dece.graphic', + 'uvm' => 'video/vnd.dece.mobile', + 'uvp' => 'video/vnd.dece.pd', + 'uvs' => 'video/vnd.dece.sd', + 'uvt' => 'application/vnd.dece.ttml+xml', + 'uvu' => 'video/vnd.uvvu.mp4', + 'uvv' => 'video/vnd.dece.video', + 'uvva' => 'audio/vnd.dece.audio', + 'uvvd' => 'application/vnd.dece.data', + 'uvvf' => 'application/vnd.dece.data', + 'uvvg' => 'image/vnd.dece.graphic', + 'uvvh' => 'video/vnd.dece.hd', + 'uvvi' => 'image/vnd.dece.graphic', + 'uvvm' => 'video/vnd.dece.mobile', + 'uvvp' => 'video/vnd.dece.pd', + 'uvvs' => 'video/vnd.dece.sd', + 'uvvt' => 'application/vnd.dece.ttml+xml', + 'uvvu' => 'video/vnd.uvvu.mp4', + 'uvvv' => 'video/vnd.dece.video', + 'uvvx' => 'application/vnd.dece.unspecified', + 'uvvz' => 'application/vnd.dece.zip', + 'uvx' => 'application/vnd.dece.unspecified', + 'uvz' => 'application/vnd.dece.zip', + 'vbox' => 'application/x-virtualbox-vbox', + 'vbox-extpack' => 'application/x-virtualbox-vbox-extpack', + 'vcard' => 'text/vcard', + 'vcd' => 'application/x-cdlink', + 'vcf' => 'text/x-vcard', + 'vcg' => 'application/vnd.groove-vcard', + 'vcs' => 'text/x-vcalendar', + 'vcx' => 'application/vnd.vcx', + 'vdi' => 'application/x-virtualbox-vdi', + 'vds' => 'model/vnd.sap.vds', + 'vhd' => 'application/x-virtualbox-vhd', + 'vis' => 'application/vnd.visionary', + 'viv' => 'video/vnd.vivo', + 'vlc' => 'application/videolan', + 'vmdk' => 'application/x-virtualbox-vmdk', + 'vob' => 'video/x-ms-vob', + 'vor' => 'application/vnd.stardivision.writer', + 'vox' => 'application/x-authorware-bin', + 'vrml' => 'model/vrml', + 'vsd' => 'application/vnd.visio', + 'vsf' => 'application/vnd.vsf', + 'vss' => 'application/vnd.visio', + 'vst' => 'application/vnd.visio', + 'vsw' => 'application/vnd.visio', + 'vtf' => 'image/vnd.valve.source.texture', + 'vtt' => 'text/vtt', + 'vtu' => 'model/vnd.vtu', + 'vxml' => 'application/voicexml+xml', + 'w3d' => 'application/x-director', + 'wad' => 'application/x-doom', + 'wadl' => 'application/vnd.sun.wadl+xml', + 'war' => 'application/java-archive', + 'wasm' => 'application/wasm', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'wbxml' => 'application/wbxml', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'wdp' => 'image/vnd.ms-photo', + 'weba' => 'audio/webm', + 'webapp' => 'application/x-web-app-manifest+json', + 'webm' => 'video/webm', + 'webmanifest' => 'application/manifest+json', + 'webp' => 'image/webp', + 'wg' => 'application/vnd.pmi.widget', + 'wgt' => 'application/widget', + 'wif' => 'application/watcherinfo+xml', + 'wks' => 'application/vnd.ms-works', + 'wm' => 'video/x-ms-wm', + 'wma' => 'audio/x-ms-wma', + 'wmd' => 'application/x-ms-wmd', + 'wmf' => 'image/wmf', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wmz' => 'application/x-msmetafile', + 'woff' => 'font/woff', + 'woff2' => 'font/woff2', + 'word' => 'application/msword', + 'wpd' => 'application/vnd.wordperfect', + 'wpl' => 'application/vnd.ms-wpl', + 'wps' => 'application/vnd.ms-works', + 'wqd' => 'application/vnd.wqd', + 'wri' => 'application/x-mswrite', + 'wrl' => 'model/vrml', + 'wsc' => 'message/vnd.wfa.wsc', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'wtb' => 'application/vnd.webturbo', + 'wvx' => 'video/x-ms-wvx', + 'x3d' => 'model/x3d+xml', + 'x3db' => 'model/x3d+fastinfoset', + 'x3dbz' => 'model/x3d+binary', + 'x3dv' => 'model/x3d-vrml', + 'x3dvz' => 'model/x3d+vrml', + 'x3dz' => 'model/x3d+xml', + 'x32' => 'application/x-authorware-bin', + 'x_b' => 'model/vnd.parasolid.transmit.binary', + 'x_t' => 'model/vnd.parasolid.transmit.text', + 'xaml' => 'application/xaml+xml', + 'xap' => 'application/x-silverlight-app', + 'xar' => 'application/vnd.xara', + 'xav' => 'application/xcap-att+xml', + 'xbap' => 'application/x-ms-xbap', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xbm' => 'image/x-xbitmap', + 'xca' => 'application/xcap-caps+xml', + 'xcs' => 'application/calendar+xml', + 'xdf' => 'application/xcap-diff+xml', + 'xdm' => 'application/vnd.syncml.dm+xml', + 'xdp' => 'application/vnd.adobe.xdp+xml', + 'xdssc' => 'application/dssc+xml', + 'xdw' => 'application/vnd.fujixerox.docuworks', + 'xel' => 'application/xcap-el+xml', + 'xenc' => 'application/xenc+xml', + 'xer' => 'application/patch-ops-error+xml', + 'xfdf' => 'application/vnd.adobe.xfdf', + 'xfdl' => 'application/vnd.xfdl', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xhvml' => 'application/xv+xml', + 'xif' => 'image/vnd.xiff', + 'xl' => 'application/excel', + 'xla' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlc' => 'application/vnd.ms-excel', + 'xlf' => 'application/xliff+xml', + 'xlm' => 'application/vnd.ms-excel', + 'xls' => 'application/vnd.ms-excel', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlt' => 'application/vnd.ms-excel', + 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlw' => 'application/vnd.ms-excel', + 'xm' => 'audio/xm', + 'xml' => 'application/xml', + 'xns' => 'application/xcap-ns+xml', + 'xo' => 'application/vnd.olpc-sugar', + 'xop' => 'application/xop+xml', + 'xpi' => 'application/x-xpinstall', + 'xpl' => 'application/xproc+xml', + 'xpm' => 'image/x-xpixmap', + 'xpr' => 'application/vnd.is-xpr', + 'xps' => 'application/vnd.ms-xpsdocument', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + 'xsd' => 'application/xml', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xsm' => 'application/vnd.syncml+xml', + 'xspf' => 'application/xspf+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xvm' => 'application/xv+xml', + 'xvml' => 'application/xv+xml', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-xyz', + 'xz' => 'application/x-xz', + 'yaml' => 'text/yaml', + 'yang' => 'application/yang', + 'yin' => 'application/yin+xml', + 'yml' => 'text/yaml', + 'ymp' => 'text/x-suse-ymp', + 'z' => 'application/x-compress', + 'z1' => 'application/x-zmachine', + 'z2' => 'application/x-zmachine', + 'z3' => 'application/x-zmachine', + 'z4' => 'application/x-zmachine', + 'z5' => 'application/x-zmachine', + 'z6' => 'application/x-zmachine', + 'z7' => 'application/x-zmachine', + 'z8' => 'application/x-zmachine', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'zip' => 'application/zip', + 'zir' => 'application/vnd.zul', + 'zirz' => 'application/vnd.zul', + 'zmm' => 'application/vnd.handheld-entertainment+xml', + 'zsh' => 'text/x-scriptzsh', + ]; + + /** + * Determines the mimetype of a file by looking at its extension. + * + * @link https://raw.githubusercontent.com/jshttp/mime-db/master/db.json + */ + public static function fromFilename(string $filename): ?string + { + return self::fromExtension(pathinfo($filename, PATHINFO_EXTENSION)); + } + + /** + * Maps a file extensions to a mimetype. + * + * @link https://raw.githubusercontent.com/jshttp/mime-db/master/db.json + */ + public static function fromExtension(string $extension): ?string + { + return self::MIME_TYPES[strtolower($extension)] ?? null; + } +} diff --git a/lib/guzzlehttp/psr7/src/MultipartStream.php b/lib/guzzlehttp/psr7/src/MultipartStream.php new file mode 100644 index 0000000000..3ae2c84a0e --- /dev/null +++ b/lib/guzzlehttp/psr7/src/MultipartStream.php @@ -0,0 +1,159 @@ +boundary = $boundary ?: sha1(uniqid('', true)); + $this->stream = $this->createStream($elements); + } + + public function getBoundary(): string + { + return $this->boundary; + } + + public function isWritable(): bool + { + return false; + } + + /** + * Get the headers needed before transferring the content of a POST file + * + * @param array $headers + */ + private function getHeaders(array $headers): string + { + $str = ''; + foreach ($headers as $key => $value) { + $str .= "{$key}: {$value}\r\n"; + } + + return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n"; + } + + /** + * Create the aggregate stream that will be used to upload the POST data + */ + protected function createStream(array $elements = []): StreamInterface + { + $stream = new AppendStream(); + + foreach ($elements as $element) { + if (!is_array($element)) { + throw new \UnexpectedValueException("An array is expected"); + } + $this->addElement($stream, $element); + } + + // Add the trailing boundary with CRLF + $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n")); + + return $stream; + } + + private function addElement(AppendStream $stream, array $element): void + { + foreach (['contents', 'name'] as $key) { + if (!array_key_exists($key, $element)) { + throw new \InvalidArgumentException("A '{$key}' key is required"); + } + } + + $element['contents'] = Utils::streamFor($element['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://') { + $element['filename'] = $uri; + } + } + + [$body, $headers] = $this->createElement( + $element['name'], + $element['contents'], + $element['filename'] ?? null, + $element['headers'] ?? [] + ); + + $stream->addStream(Utils::streamFor($this->getHeaders($headers))); + $stream->addStream($body); + $stream->addStream(Utils::streamFor("\r\n")); + } + + private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array + { + // Set a default content-disposition header if one was no provided + $disposition = $this->getHeader($headers, 'content-disposition'); + if (!$disposition) { + $headers['Content-Disposition'] = ($filename === '0' || $filename) + ? sprintf( + 'form-data; name="%s"; filename="%s"', + $name, + 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; + } + } + + // Set a default Content-Type if one was not supplied + $type = $this->getHeader($headers, 'content-type'); + if (!$type && ($filename === '0' || $filename)) { + if ($type = MimeType::fromFilename($filename)) { + $headers['Content-Type'] = $type; + } + } + + return [$stream, $headers]; + } + + private function getHeader(array $headers, string $key) + { + $lowercaseHeader = strtolower($key); + foreach ($headers as $k => $v) { + if (strtolower($k) === $lowercaseHeader) { + return $v; + } + } + + return null; + } +} diff --git a/lib/guzzlehttp/psr7/src/NoSeekStream.php b/lib/guzzlehttp/psr7/src/NoSeekStream.php new file mode 100644 index 0000000000..161a224f02 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/NoSeekStream.php @@ -0,0 +1,28 @@ +source = $source; + $this->size = $options['size'] ?? null; + $this->metadata = $options['metadata'] ?? []; + $this->buffer = new BufferStream(); + } + + 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 ''; + } + } + + public function close(): void + { + $this->detach(); + } + + public function detach() + { + $this->tellPos = 0; + $this->source = null; + + return null; + } + + public function getSize(): ?int + { + return $this->size; + } + + public function tell(): int + { + return $this->tellPos; + } + + public function eof(): bool + { + return $this->source === null; + } + + public function isSeekable(): bool + { + return false; + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + throw new \RuntimeException('Cannot seek a PumpStream'); + } + + public function isWritable(): bool + { + return false; + } + + public function write($string): int + { + throw new \RuntimeException('Cannot write to a PumpStream'); + } + + public function isReadable(): bool + { + return true; + } + + public function read($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; + } + + return $data; + } + + public function getContents(): string + { + $result = ''; + while (!$this->eof()) { + $result .= $this->read(1000000); + } + + return $result; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + if (!$key) { + return $this->metadata; + } + + return $this->metadata[$key] ?? null; + } + + private function pump(int $length): void + { + if ($this->source) { + do { + $data = call_user_func($this->source, $length); + if ($data === false || $data === null) { + $this->source = null; + return; + } + $this->buffer->write($data); + $length -= strlen($data); + } while ($length > 0); + } + } +} diff --git a/lib/guzzlehttp/psr7/src/Query.php b/lib/guzzlehttp/psr7/src/Query.php new file mode 100644 index 0000000000..2faab3a889 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/Query.php @@ -0,0 +1,113 @@ + '1', 'foo[b]' => '2'])`. + * + * @param string $str Query string to parse + * @param int|bool $urlEncoding How the query string is encoded + */ + public static function parse(string $str, $urlEncoding = true): array + { + $result = []; + + if ($str === '') { + return $result; + } + + if ($urlEncoding === true) { + $decoder = function ($value) { + return rawurldecode(str_replace('+', ' ', (string) $value)); + }; + } elseif ($urlEncoding === PHP_QUERY_RFC3986) { + $decoder = 'rawurldecode'; + } elseif ($urlEncoding === PHP_QUERY_RFC1738) { + $decoder = 'urldecode'; + } else { + $decoder = function ($str) { + return $str; + }; + } + + foreach (explode('&', $str) as $kvp) { + $parts = explode('=', $kvp, 2); + $key = $decoder($parts[0]); + $value = isset($parts[1]) ? $decoder($parts[1]) : null; + if (!array_key_exists($key, $result)) { + $result[$key] = $value; + } else { + if (!is_array($result[$key])) { + $result[$key] = [$result[$key]]; + } + $result[$key][] = $value; + } + } + + return $result; + } + + /** + * 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). + * + * @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. + */ + public static function build(array $params, $encoding = PHP_QUERY_RFC3986): string + { + if (!$params) { + return ''; + } + + if ($encoding === false) { + $encoder = function (string $str): string { + return $str; + }; + } elseif ($encoding === PHP_QUERY_RFC3986) { + $encoder = 'rawurlencode'; + } elseif ($encoding === PHP_QUERY_RFC1738) { + $encoder = 'urlencode'; + } else { + throw new \InvalidArgumentException('Invalid type'); + } + + $qs = ''; + foreach ($params as $k => $v) { + $k = $encoder((string) $k); + if (!is_array($v)) { + $qs .= $k; + $v = is_bool($v) ? (int) $v : $v; + if ($v !== null) { + $qs .= '=' . $encoder((string) $v); + } + $qs .= '&'; + } else { + foreach ($v as $vv) { + $qs .= $k; + $vv = is_bool($vv) ? (int) $vv : $vv; + if ($vv !== null) { + $qs .= '=' . $encoder((string) $vv); + } + $qs .= '&'; + } + } + } + + return $qs ? (string) substr($qs, 0, -1) : ''; + } +} diff --git a/lib/guzzlehttp/psr7/src/Request.php b/lib/guzzlehttp/psr7/src/Request.php new file mode 100644 index 0000000000..b17af66a2c --- /dev/null +++ b/lib/guzzlehttp/psr7/src/Request.php @@ -0,0 +1,157 @@ + $headers Request headers + * @param string|resource|StreamInterface|null $body Request body + * @param string $version Protocol version + */ + public function __construct( + string $method, + $uri, + array $headers = [], + $body = null, + string $version = '1.1' + ) { + $this->assertMethod($method); + if (!($uri instanceof UriInterface)) { + $uri = new Uri($uri); + } + + $this->method = strtoupper($method); + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $version; + + if (!isset($this->headerNames['host'])) { + $this->updateHostFromUri(); + } + + if ($body !== '' && $body !== null) { + $this->stream = Utils::streamFor($body); + } + } + + public function getRequestTarget(): string + { + if ($this->requestTarget !== null) { + return $this->requestTarget; + } + + $target = $this->uri->getPath(); + if ($target === '') { + $target = '/'; + } + if ($this->uri->getQuery() != '') { + $target .= '?' . $this->uri->getQuery(); + } + + return $target; + } + + public function withRequestTarget($requestTarget): RequestInterface + { + if (preg_match('#\s#', $requestTarget)) { + throw new InvalidArgumentException( + 'Invalid request target provided; cannot contain whitespace' + ); + } + + $new = clone $this; + $new->requestTarget = $requestTarget; + return $new; + } + + public function getMethod(): string + { + return $this->method; + } + + public function withMethod($method): RequestInterface + { + $this->assertMethod($method); + $new = clone $this; + $new->method = strtoupper($method); + return $new; + } + + public function getUri(): UriInterface + { + return $this->uri; + } + + public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface + { + if ($uri === $this->uri) { + return $this; + } + + $new = clone $this; + $new->uri = $uri; + + if (!$preserveHost || !isset($this->headerNames['host'])) { + $new->updateHostFromUri(); + } + + return $new; + } + + private function updateHostFromUri(): void + { + $host = $this->uri->getHost(); + + if ($host == '') { + return; + } + + if (($port = $this->uri->getPort()) !== null) { + $host .= ':' . $port; + } + + if (isset($this->headerNames['host'])) { + $header = $this->headerNames['host']; + } else { + $header = 'Host'; + $this->headerNames['host'] = 'Host'; + } + // Ensure Host is the first header. + // See: http://tools.ietf.org/html/rfc7230#section-5.4 + $this->headers = [$header => [$host]] + $this->headers; + } + + /** + * @param mixed $method + */ + private function assertMethod($method): void + { + if (!is_string($method) || $method === '') { + throw new InvalidArgumentException('Method must be a non-empty string.'); + } + } +} diff --git a/lib/guzzlehttp/psr7/src/Response.php b/lib/guzzlehttp/psr7/src/Response.php new file mode 100644 index 0000000000..4c6ee6f036 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/Response.php @@ -0,0 +1,160 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-status', + 208 => 'Already Reported', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Switch Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Requested range not satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 451 => 'Unavailable For Legal Reasons', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + ]; + + /** @var string */ + private $reasonPhrase; + + /** @var int */ + private $statusCode; + + /** + * @param int $status Status code + * @param array $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) + */ + public function __construct( + int $status = 200, + array $headers = [], + $body = null, + string $version = '1.1', + string $reason = null + ) { + $this->assertStatusCodeRange($status); + + $this->statusCode = $status; + + if ($body !== '' && $body !== null) { + $this->stream = Utils::streamFor($body); + } + + $this->setHeaders($headers); + if ($reason == '' && isset(self::PHRASES[$this->statusCode])) { + $this->reasonPhrase = self::PHRASES[$this->statusCode]; + } else { + $this->reasonPhrase = (string) $reason; + } + + $this->protocol = $version; + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + public function getReasonPhrase(): string + { + return $this->reasonPhrase; + } + + public function withStatus($code, $reasonPhrase = ''): ResponseInterface + { + $this->assertStatusCodeIsInteger($code); + $code = (int) $code; + $this->assertStatusCodeRange($code); + + $new = clone $this; + $new->statusCode = $code; + if ($reasonPhrase == '' && isset(self::PHRASES[$new->statusCode])) { + $reasonPhrase = self::PHRASES[$new->statusCode]; + } + $new->reasonPhrase = (string) $reasonPhrase; + return $new; + } + + /** + * @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.'); + } + } + + private function assertStatusCodeRange(int $statusCode): void + { + if ($statusCode < 100 || $statusCode >= 600) { + throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.'); + } + } +} diff --git a/lib/guzzlehttp/psr7/src/Rfc7230.php b/lib/guzzlehttp/psr7/src/Rfc7230.php new file mode 100644 index 0000000000..30224018d3 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/Rfc7230.php @@ -0,0 +1,23 @@ +@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m"; + public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)"; +} diff --git a/lib/guzzlehttp/psr7/src/ServerRequest.php b/lib/guzzlehttp/psr7/src/ServerRequest.php new file mode 100644 index 0000000000..43cbb502e8 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/ServerRequest.php @@ -0,0 +1,344 @@ + $headers Request headers + * @param string|resource|StreamInterface|null $body Request body + * @param string $version Protocol version + * @param array $serverParams Typically the $_SERVER superglobal + */ + public function __construct( + string $method, + $uri, + array $headers = [], + $body = null, + string $version = '1.1', + array $serverParams = [] + ) { + $this->serverParams = $serverParams; + + parent::__construct($method, $uri, $headers, $body, $version); + } + + /** + * Return an UploadedFile instance array. + * + * @param array $files An array which respect $_FILES structure + * + * @throws InvalidArgumentException for unrecognized values + */ + 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], + 'error' => $files['error'][$key], + 'name' => $files['name'][$key], + 'type' => $files['type'][$key], + ]; + $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); + } + + return $normalizedFiles; + } + + /** + * Return a ServerRequest populated with superglobals: + * $_GET + * $_POST + * $_COOKIE + * $_FILES + * $_SERVER + */ + 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'; + + $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]; + } + + /** + * Get a Uri populated with values from $_SERVER. + */ + 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; + } + + public function getServerParams(): array + { + return $this->serverParams; + } + + public function getUploadedFiles(): array + { + return $this->uploadedFiles; + } + + public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface + { + $new = clone $this; + $new->uploadedFiles = $uploadedFiles; + + return $new; + } + + public function getCookieParams(): array + { + return $this->cookieParams; + } + + public function withCookieParams(array $cookies): ServerRequestInterface + { + $new = clone $this; + $new->cookieParams = $cookies; + + return $new; + } + + public function getQueryParams(): array + { + return $this->queryParams; + } + + public function withQueryParams(array $query): ServerRequestInterface + { + $new = clone $this; + $new->queryParams = $query; + + return $new; + } + + /** + * {@inheritdoc} + * + * @return array|object|null + */ + public function getParsedBody() + { + return $this->parsedBody; + } + + public function withParsedBody($data): ServerRequestInterface + { + $new = clone $this; + $new->parsedBody = $data; + + return $new; + } + + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getAttribute($attribute, $default = null) + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $default; + } + + return $this->attributes[$attribute]; + } + + public function withAttribute($attribute, $value): ServerRequestInterface + { + $new = clone $this; + $new->attributes[$attribute] = $value; + + return $new; + } + + public function withoutAttribute($attribute): ServerRequestInterface + { + if (false === array_key_exists($attribute, $this->attributes)) { + return $this; + } + + $new = clone $this; + unset($new->attributes[$attribute]); + + return $new; + } +} diff --git a/lib/guzzlehttp/psr7/src/Stream.php b/lib/guzzlehttp/psr7/src/Stream.php new file mode 100644 index 0000000000..ecd31861e1 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/Stream.php @@ -0,0 +1,282 @@ +size = $options['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'); + } + + /** + * Closes the stream when the destructed + */ + public function __destruct() + { + $this->close(); + } + + 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 ''; + } + } + + public function getContents(): string + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + + return Utils::tryGetContents($this->stream); + } + + public function close(): void + { + if (isset($this->stream)) { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->detach(); + } + } + + public function detach() + { + if (!isset($this->stream)) { + return null; + } + + $result = $this->stream; + unset($this->stream); + $this->size = $this->uri = null; + $this->readable = $this->writable = $this->seekable = false; + + return $result; + } + + public function getSize(): ?int + { + if ($this->size !== null) { + return $this->size; + } + + if (!isset($this->stream)) { + return null; + } + + // Clear the stat cache if the stream has a URI + if ($this->uri) { + clearstatcache(true, $this->uri); + } + + $stats = fstat($this->stream); + if (is_array($stats) && isset($stats['size'])) { + $this->size = $stats['size']; + return $this->size; + } + + return null; + } + + public function isReadable(): bool + { + return $this->readable; + } + + public function isWritable(): bool + { + return $this->writable; + } + + public function isSeekable(): bool + { + return $this->seekable; + } + + public function eof(): bool + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + return feof($this->stream); + } + + public function tell(): int + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + + $result = ftell($this->stream); + + if ($result === false) { + throw new \RuntimeException('Unable to determine stream position'); + } + + return $result; + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + $whence = (int) $whence; + + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->seekable) { + throw new \RuntimeException('Stream is not seekable'); + } + if (fseek($this->stream, $offset, $whence) === -1) { + throw new \RuntimeException('Unable to seek to stream position ' + . $offset . ' with whence ' . var_export($whence, true)); + } + } + + public function read($length): string + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + + if (0 === $length) { + return ''; + } + + try { + $string = fread($this->stream, $length); + } catch (\Exception $e) { + throw new \RuntimeException('Unable to read from stream', 0, $e); + } + + if (false === $string) { + throw new \RuntimeException('Unable to read from stream'); + } + + return $string; + } + + public function write($string): int + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->writable) { + throw new \RuntimeException('Cannot write to a non-writable stream'); + } + + // We can't know the size after writing anything + $this->size = null; + $result = fwrite($this->stream, $string); + + if ($result === false) { + throw new \RuntimeException('Unable to write to stream'); + } + + return $result; + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + if (!isset($this->stream)) { + return $key ? null : []; + } elseif (!$key) { + return $this->customMetadata + stream_get_meta_data($this->stream); + } elseif (isset($this->customMetadata[$key])) { + return $this->customMetadata[$key]; + } + + $meta = stream_get_meta_data($this->stream); + + return $meta[$key] ?? null; + } +} diff --git a/lib/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/lib/guzzlehttp/psr7/src/StreamDecoratorTrait.php new file mode 100644 index 0000000000..56d4104d45 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/StreamDecoratorTrait.php @@ -0,0 +1,155 @@ +stream = $stream; + } + + /** + * 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) + { + if ($name === 'stream') { + $this->stream = $this->createStream(); + return $this->stream; + } + + throw new \UnexpectedValueException("$name not found on class"); + } + + 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 ''; + } + } + + public function getContents(): string + { + return Utils::copyToString($this); + } + + /** + * Allow decorators to implement custom methods + * + * @return mixed + */ + public function __call(string $method, array $args) + { + /** @var callable $callable */ + $callable = [$this->stream, $method]; + $result = call_user_func_array($callable, $args); + + // Always return the wrapped object if the result is a return $this + return $result === $this->stream ? $this : $result; + } + + public function close(): void + { + $this->stream->close(); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + return $this->stream->getMetadata($key); + } + + public function detach() + { + return $this->stream->detach(); + } + + public function getSize(): ?int + { + return $this->stream->getSize(); + } + + public function eof(): bool + { + return $this->stream->eof(); + } + + public function tell(): int + { + return $this->stream->tell(); + } + + public function isReadable(): bool + { + return $this->stream->isReadable(); + } + + public function isWritable(): bool + { + return $this->stream->isWritable(); + } + + public function isSeekable(): bool + { + return $this->stream->isSeekable(); + } + + public function rewind(): void + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET): void + { + $this->stream->seek($offset, $whence); + } + + public function read($length): string + { + return $this->stream->read($length); + } + + public function write($string): int + { + return $this->stream->write($string); + } + + /** + * Implement in subclasses to dynamically create streams when requested. + * + * @throws \BadMethodCallException + */ + protected function createStream(): StreamInterface + { + throw new \BadMethodCallException('Not implemented'); + } +} diff --git a/lib/guzzlehttp/psr7/src/StreamWrapper.php b/lib/guzzlehttp/psr7/src/StreamWrapper.php new file mode 100644 index 0000000000..2a93464035 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/StreamWrapper.php @@ -0,0 +1,175 @@ +isReadable()) { + $mode = $stream->isWritable() ? 'r+' : 'r'; + } elseif ($stream->isWritable()) { + $mode = 'w'; + } else { + throw new \InvalidArgumentException('The stream must be readable, ' + . 'writable, or both.'); + } + + return fopen('guzzle://stream', $mode, false, self::createStreamContext($stream)); + } + + /** + * Creates a stream context that can be used to open a stream as a php stream resource. + * + * @return resource + */ + public static function createStreamContext(StreamInterface $stream) + { + return stream_context_create([ + 'guzzle' => ['stream' => $stream] + ]); + } + + /** + * Registers the stream wrapper if needed + */ + public static function register(): void + { + if (!in_array('guzzle', stream_get_wrappers())) { + stream_wrapper_register('guzzle', __CLASS__); + } + } + + public function stream_open(string $path, string $mode, int $options, string &$opened_path = null): bool + { + $options = stream_context_get_options($this->context); + + if (!isset($options['guzzle']['stream'])) { + return false; + } + + $this->mode = $mode; + $this->stream = $options['guzzle']['stream']; + + return true; + } + + public function stream_read(int $count): string + { + return $this->stream->read($count); + } + + public function stream_write(string $data): int + { + return $this->stream->write($data); + } + + public function stream_tell(): int + { + return $this->stream->tell(); + } + + public function stream_eof(): bool + { + return $this->stream->eof(); + } + + public function stream_seek(int $offset, int $whence): bool + { + $this->stream->seek($offset, $whence); + + return true; + } + + /** + * @return resource|false + */ + public function stream_cast(int $cast_as) + { + $stream = clone($this->stream); + $resource = $stream->detach(); + + return $resource ?? false; + } + + /** + * @return array + */ + public function stream_stat(): array + { + static $modeMap = [ + 'r' => 33060, + 'rb' => 33060, + 'r+' => 33206, + '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, + 'blksize' => 0, + 'blocks' => 0 + ]; + } + + /** + * @return array + */ + 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, + 'blksize' => 0, + 'blocks' => 0 + ]; + } +} diff --git a/lib/guzzlehttp/psr7/src/UploadedFile.php b/lib/guzzlehttp/psr7/src/UploadedFile.php new file mode 100644 index 0000000000..b1521bcf86 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/UploadedFile.php @@ -0,0 +1,211 @@ +setError($errorStatus); + $this->size = $size; + $this->clientFilename = $clientFilename; + $this->clientMediaType = $clientMediaType; + + if ($this->isOk()) { + $this->setStreamOrFile($streamOrFile); + } + } + + /** + * Depending on the value set file or stream variable + * + * @param StreamInterface|string|resource $streamOrFile + * + * @throws InvalidArgumentException + */ + private function setStreamOrFile($streamOrFile): void + { + if (is_string($streamOrFile)) { + $this->file = $streamOrFile; + } elseif (is_resource($streamOrFile)) { + $this->stream = new Stream($streamOrFile); + } elseif ($streamOrFile instanceof StreamInterface) { + $this->stream = $streamOrFile; + } else { + throw new InvalidArgumentException( + 'Invalid stream or file provided for UploadedFile' + ); + } + } + + /** + * @throws InvalidArgumentException + */ + private function setError(int $error): void + { + if (false === in_array($error, UploadedFile::ERRORS, true)) { + throw new InvalidArgumentException( + 'Invalid error status for UploadedFile' + ); + } + + $this->error = $error; + } + + private function isStringNotEmpty($param): bool + { + return is_string($param) && false === empty($param); + } + + /** + * Return true if there is no upload error + */ + private function isOk(): bool + { + return $this->error === UPLOAD_ERR_OK; + } + + public function isMoved(): bool + { + return $this->moved; + } + + /** + * @throws RuntimeException if is moved or not ok + */ + private function validateActive(): void + { + if (false === $this->isOk()) { + throw new RuntimeException('Cannot retrieve stream due to upload error'); + } + + if ($this->isMoved()) { + throw new RuntimeException('Cannot retrieve stream after it has already been moved'); + } + } + + public function getStream(): StreamInterface + { + $this->validateActive(); + + if ($this->stream instanceof StreamInterface) { + return $this->stream; + } + + /** @var string $file */ + $file = $this->file; + + return new LazyOpenStream($file, 'r+'); + } + + public function moveTo($targetPath): void + { + $this->validateActive(); + + if (false === $this->isStringNotEmpty($targetPath)) { + throw new InvalidArgumentException( + 'Invalid path provided for move operation; must be a non-empty string' + ); + } + + if ($this->file) { + $this->moved = PHP_SAPI === 'cli' + ? rename($this->file, $targetPath) + : move_uploaded_file($this->file, $targetPath); + } else { + Utils::copyToStream( + $this->getStream(), + new LazyOpenStream($targetPath, 'w') + ); + + $this->moved = true; + } + + if (false === $this->moved) { + throw new RuntimeException( + sprintf('Uploaded file could not be moved to %s', $targetPath) + ); + } + } + + public function getSize(): ?int + { + return $this->size; + } + + public function getError(): int + { + return $this->error; + } + + public function getClientFilename(): ?string + { + return $this->clientFilename; + } + + public function getClientMediaType(): ?string + { + return $this->clientMediaType; + } +} diff --git a/lib/guzzlehttp/psr7/src/Uri.php b/lib/guzzlehttp/psr7/src/Uri.php new file mode 100644 index 0000000000..5c6416ae68 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/Uri.php @@ -0,0 +1,738 @@ + 80, + 'https' => 443, + 'ftp' => 21, + 'gopher' => 70, + 'nntp' => 119, + 'news' => 119, + 'telnet' => 23, + 'tn3270' => 23, + 'imap' => 143, + 'pop' => 110, + 'ldap' => 389, + ]; + + /** + * 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']; + + /** @var string Uri scheme. */ + private $scheme = ''; + + /** @var string Uri user info. */ + private $userInfo = ''; + + /** @var string Uri host. */ + private $host = ''; + + /** @var int|null Uri port. */ + private $port; + + /** @var string Uri path. */ + private $path = ''; + + /** @var string Uri query string. */ + private $query = ''; + + /** @var string Uri fragment. */ + private $fragment = ''; + + /** @var string|null String representation */ + private $composedComponents; + + public function __construct(string $uri = '') + { + if ($uri !== '') { + $parts = self::parse($uri); + if ($parts === false) { + throw new MalformedUriException("Unable to parse URI: $uri"); + } + $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; + } + + /** + * Composes a URI reference string from its various components. + * + * 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). + * + * @link https://tools.ietf.org/html/rfc3986#section-5.3 + */ + public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment): string + { + $uri = ''; + + // weak type checks to also accept null until we can add scalar type hints + if ($scheme != '') { + $uri .= $scheme . ':'; + } + + if ($authority != ''|| $scheme === 'file') { + $uri .= '//' . $authority; + } + + $uri .= $path; + + if ($query != '') { + $uri .= '?' . $query; + } + + if ($fragment != '') { + $uri .= '#' . $fragment; + } + + return $uri; + } + + /** + * 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. + */ + public static function isDefaultPort(UriInterface $uri): bool + { + return $uri->getPort() === null + || (isset(self::DEFAULT_PORTS[$uri->getScheme()]) && $uri->getPort() === self::DEFAULT_PORTS[$uri->getScheme()]); + } + + /** + * 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' + * + * @see Uri::isNetworkPathReference + * @see Uri::isAbsolutePathReference + * @see Uri::isRelativePathReference + * @link https://tools.ietf.org/html/rfc3986#section-4 + */ + public static function isAbsolute(UriInterface $uri): bool + { + return $uri->getScheme() !== ''; + } + + /** + * Whether the URI is a network-path reference. + * + * A relative reference that begins with two slash characters is termed an network-path reference. + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isNetworkPathReference(UriInterface $uri): bool + { + return $uri->getScheme() === '' && $uri->getAuthority() !== ''; + } + + /** + * Whether the URI is a 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 + */ + public static function isAbsolutePathReference(UriInterface $uri): bool + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && isset($uri->getPath()[0]) + && $uri->getPath()[0] === '/'; + } + + /** + * 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. + * + * @link https://tools.ietf.org/html/rfc3986#section-4.2 + */ + public static function isRelativePathReference(UriInterface $uri): bool + { + return $uri->getScheme() === '' + && $uri->getAuthority() === '' + && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/'); + } + + /** + * 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. + * + * @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 + */ + 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()) + && ($uri->getQuery() === $base->getQuery()); + } + + return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === ''; + } + + /** + * Creates a new URI with a specific query string value removed. + * + * Any existing query string values that exactly match the provided key are + * removed. + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Query string key to remove. + */ + public static function withoutQueryValue(UriInterface $uri, string $key): UriInterface + { + $result = self::getFilteredQueryString($uri, [$key]); + + return $uri->withQuery(implode('&', $result)); + } + + /** + * 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". + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Key to set. + * @param string|null $value Value to set + */ + public static function withQueryValue(UriInterface $uri, string $key, ?string $value): UriInterface + { + $result = self::getFilteredQueryString($uri, [$key]); + + $result[] = self::generateQueryString($key, $value); + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a new URI with multiple specific query string values. + * + * 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 + */ + public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface + { + $result = self::getFilteredQueryString($uri, array_keys($keyValueArray)); + + foreach ($keyValueArray as $key => $value) { + $result[] = self::generateQueryString((string) $key, $value !== null ? (string) $value : null); + } + + return $uri->withQuery(implode('&', $result)); + } + + /** + * Creates a URI from a hash of `parse_url` components. + * + * @link http://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 + { + $uri = new self(); + $uri->applyParts($parts); + $uri->validateState(); + + return $uri; + } + + public function getScheme(): string + { + return $this->scheme; + } + + public function getAuthority(): string + { + $authority = $this->host; + if ($this->userInfo !== '') { + $authority = $this->userInfo . '@' . $authority; + } + + if ($this->port !== null) { + $authority .= ':' . $this->port; + } + + return $authority; + } + + public function getUserInfo(): string + { + return $this->userInfo; + } + + public function getHost(): string + { + return $this->host; + } + + public function getPort(): ?int + { + return $this->port; + } + + public function getPath(): string + { + return $this->path; + } + + public function getQuery(): string + { + return $this->query; + } + + public function getFragment(): string + { + return $this->fragment; + } + + public function withScheme($scheme): UriInterface + { + $scheme = $this->filterScheme($scheme); + + if ($this->scheme === $scheme) { + return $this; + } + + $new = clone $this; + $new->scheme = $scheme; + $new->composedComponents = null; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withUserInfo($user, $password = null): UriInterface + { + $info = $this->filterUserInfoComponent($user); + if ($password !== null) { + $info .= ':' . $this->filterUserInfoComponent($password); + } + + if ($this->userInfo === $info) { + return $this; + } + + $new = clone $this; + $new->userInfo = $info; + $new->composedComponents = null; + $new->validateState(); + + return $new; + } + + public function withHost($host): UriInterface + { + $host = $this->filterHost($host); + + if ($this->host === $host) { + return $this; + } + + $new = clone $this; + $new->host = $host; + $new->composedComponents = null; + $new->validateState(); + + return $new; + } + + public function withPort($port): UriInterface + { + $port = $this->filterPort($port); + + if ($this->port === $port) { + return $this; + } + + $new = clone $this; + $new->port = $port; + $new->composedComponents = null; + $new->removeDefaultPort(); + $new->validateState(); + + return $new; + } + + public function withPath($path): UriInterface + { + $path = $this->filterPath($path); + + if ($this->path === $path) { + return $this; + } + + $new = clone $this; + $new->path = $path; + $new->composedComponents = null; + $new->validateState(); + + return $new; + } + + public function withQuery($query): UriInterface + { + $query = $this->filterQueryAndFragment($query); + + if ($this->query === $query) { + return $this; + } + + $new = clone $this; + $new->query = $query; + $new->composedComponents = null; + + return $new; + } + + public function withFragment($fragment): UriInterface + { + $fragment = $this->filterQueryAndFragment($fragment); + + if ($this->fragment === $fragment) { + return $this; + } + + $new = clone $this; + $new->fragment = $fragment; + $new->composedComponents = null; + + return $new; + } + + public function jsonSerialize(): string + { + return $this->__toString(); + } + + /** + * Apply parse_url parts to a URI. + * + * @param array $parts Array of parse_url parts to apply. + */ + private function applyParts(array $parts): void + { + $this->scheme = isset($parts['scheme']) + ? $this->filterScheme($parts['scheme']) + : ''; + $this->userInfo = isset($parts['user']) + ? $this->filterUserInfoComponent($parts['user']) + : ''; + $this->host = isset($parts['host']) + ? $this->filterHost($parts['host']) + : ''; + $this->port = isset($parts['port']) + ? $this->filterPort($parts['port']) + : null; + $this->path = isset($parts['path']) + ? $this->filterPath($parts['path']) + : ''; + $this->query = isset($parts['query']) + ? $this->filterQueryAndFragment($parts['query']) + : ''; + $this->fragment = isset($parts['fragment']) + ? $this->filterQueryAndFragment($parts['fragment']) + : ''; + if (isset($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 + { + if (!is_string($scheme)) { + throw new \InvalidArgumentException('Scheme must be a string'); + } + + return \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + /** + * @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 + ); + } + + /** + * @param mixed $host + * + * @throws \InvalidArgumentException If the host is invalid. + */ + private function filterHost($host): string + { + if (!is_string($host)) { + throw new \InvalidArgumentException('Host must be a string'); + } + + return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + } + + /** + * @param mixed $port + * + * @throws \InvalidArgumentException If the port is invalid. + */ + private function filterPort($port): ?int + { + if ($port === null) { + return null; + } + + $port = (int) $port; + if (0 > $port || 0xffff < $port) { + throw new \InvalidArgumentException( + sprintf('Invalid port: %d. Must be between 0 and 65535', $port) + ); + } + + return $port; + } + + /** + * @param string[] $keys + * + * @return string[] + */ + private static function getFilteredQueryString(UriInterface $uri, array $keys): array + { + $current = $uri->getQuery(); + + if ($current === '') { + return []; + } + + $decodedKeys = array_map('rawurldecode', $keys); + + return array_filter(explode('&', $current), function ($part) use ($decodedKeys) { + 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 + // (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); + } + + return $queryString; + } + + private function removeDefaultPort(): void + { + if ($this->port !== null && self::isDefaultPort($this)) { + $this->port = null; + } + } + + /** + * Filters the path of a URI + * + * @param mixed $path + * + * @throws \InvalidArgumentException If the path is invalid. + */ + private function filterPath($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 + ); + } + + /** + * 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 + { + if (!is_string($str)) { + throw new \InvalidArgumentException('Query and fragment must be a string'); + } + + return preg_replace_callback( + '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', + [$this, 'rawurlencodeMatchZero'], + $str + ); + } + + private function rawurlencodeMatchZero(array $match): string + { + return rawurlencode($match[0]); + } + + private function validateState(): void + { + if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) { + $this->host = self::HTTP_DEFAULT_HOST; + } + + if ($this->getAuthority() === '') { + if (0 === strpos($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], ':')) { + throw new MalformedUriException('A relative URI must not have a path beginning with a segment containing a colon'); + } + } elseif (isset($this->path[0]) && $this->path[0] !== '/') { + throw new MalformedUriException('The path of a URI with an authority must start with a slash "/" or be empty'); + } + } +} diff --git a/lib/guzzlehttp/psr7/src/UriComparator.php b/lib/guzzlehttp/psr7/src/UriComparator.php new file mode 100644 index 0000000000..70c582aa0f --- /dev/null +++ b/lib/guzzlehttp/psr7/src/UriComparator.php @@ -0,0 +1,52 @@ +getHost(), $modified->getHost()) !== 0) { + return true; + } + + if ($original->getScheme() !== $modified->getScheme()) { + return true; + } + + if (self::computePort($original) !== self::computePort($modified)) { + return true; + } + + return false; + } + + private static function computePort(UriInterface $uri): int + { + $port = $uri->getPort(); + + if (null !== $port) { + return $port; + } + + return 'https' === $uri->getScheme() ? 443 : 80; + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/lib/guzzlehttp/psr7/src/UriNormalizer.php b/lib/guzzlehttp/psr7/src/UriNormalizer.php new file mode 100644 index 0000000000..e12971edda --- /dev/null +++ b/lib/guzzlehttp/psr7/src/UriNormalizer.php @@ -0,0 +1,220 @@ +getPath() === '' && + ($uri->getScheme() === 'http' || $uri->getScheme() === 'https') + ) { + $uri = $uri->withPath('/'); + } + + if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') { + $uri = $uri->withHost(''); + } + + if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) { + $uri = $uri->withPort(null); + } + + if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) { + $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath())); + } + + if ($flags & self::REMOVE_DUPLICATE_SLASHES) { + $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath())); + } + + if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') { + $queryKeyValues = explode('&', $uri->getQuery()); + sort($queryKeyValues); + $uri = $uri->withQuery(implode('&', $queryKeyValues)); + } + + 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. + * + * @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 + */ + public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, int $normalizations = self::PRESERVING_NORMALIZATIONS): bool + { + return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations); + } + + private static function capitalizePercentEncoding(UriInterface $uri): UriInterface + { + $regex = '/(?:%[A-Fa-f0-9]{2})++/'; + + $callback = function (array $match) { + return strtoupper($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + 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) { + return rawurldecode($match[0]); + }; + + return + $uri->withPath( + preg_replace_callback($regex, $callback, $uri->getPath()) + )->withQuery( + preg_replace_callback($regex, $callback, $uri->getQuery()) + ); + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/lib/guzzlehttp/psr7/src/UriResolver.php b/lib/guzzlehttp/psr7/src/UriResolver.php new file mode 100644 index 0000000000..426e5c9adf --- /dev/null +++ b/lib/guzzlehttp/psr7/src/UriResolver.php @@ -0,0 +1,211 @@ +getScheme() != '') { + return $rel->withPath(self::removeDotSegments($rel->getPath())); + } + + 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 new Uri(Uri::composeComponents( + $base->getScheme(), + $targetAuthority, + $targetPath, + $targetQuery, + $rel->getFragment() + )); + } + + /** + * Returns the target URI as a relative reference from the base URI. + * + * 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. + * + * $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/'. + * + * 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() !== '') + ) { + return $target; + } + + if (Uri::isRelativePathReference($target)) { + // As the target is already highly relative we return it as-is. It would be possible to resolve + // the target with `$target = self::resolve($base, $target);` and then try make it more relative + // by removing a duplicate query. But let's not do that automatically. + return $target; + } + + if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) { + 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()) { + return $emptyPathUri->withPath(self::getRelativePath($base, $target)); + } + + if ($base->getQuery() === $target->getQuery()) { + // 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 ($target->getQuery() === '') { + $segments = explode('/', $target->getPath()); + /** @var string $lastSegment */ + $lastSegment = end($segments); + + return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment); + } + + return $emptyPathUri; + } + + private static function getRelativePath(UriInterface $base, UriInterface $target): string + { + $sourceSegments = explode('/', $base->getPath()); + $targetSegments = explode('/', $target->getPath()); + array_pop($sourceSegments); + $targetLastSegment = array_pop($targetSegments); + foreach ($sourceSegments as $i => $segment) { + if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) { + unset($sourceSegments[$i], $targetSegments[$i]); + } else { + break; + } + } + $targetSegments[] = $targetLastSegment; + $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], ':')) { + $relativePath = "./$relativePath"; + } elseif ('/' === $relativePath[0]) { + if ($base->getAuthority() != '' && $base->getPath() === '') { + // In this case an extra slash is added by resolve() automatically. So we must not add one here. + $relativePath = ".$relativePath"; + } else { + $relativePath = "./$relativePath"; + } + } + + return $relativePath; + } + + private function __construct() + { + // cannot be instantiated + } +} diff --git a/lib/guzzlehttp/psr7/src/Utils.php b/lib/guzzlehttp/psr7/src/Utils.php new file mode 100644 index 0000000000..3a4cf39464 --- /dev/null +++ b/lib/guzzlehttp/psr7/src/Utils.php @@ -0,0 +1,459 @@ + $v) { + if (!is_string($k) || !in_array(strtolower($k), $keys)) { + $result[$k] = $v; + } + } + + return $result; + } + + /** + * Copy the contents of a stream into another stream until the given number + * of bytes have been read. + * + * @param StreamInterface $source Stream to read from + * @param StreamInterface $dest Stream to write to + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ + public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void + { + $bufferSize = 8192; + + if ($maxLen === -1) { + while (!$source->eof()) { + if (!$dest->write($source->read($bufferSize))) { + break; + } + } + } else { + $remaining = $maxLen; + while ($remaining > 0 && !$source->eof()) { + $buf = $source->read(min($bufferSize, $remaining)); + $len = strlen($buf); + if (!$len) { + break; + } + $remaining -= $len; + $dest->write($buf); + } + } + } + + /** + * Copy the contents of a stream into a string until the given number of + * bytes have been read. + * + * @param StreamInterface $stream Stream to read + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ + public static function copyToString(StreamInterface $stream, int $maxLen = -1): string + { + $buffer = ''; + + if ($maxLen === -1) { + while (!$stream->eof()) { + $buf = $stream->read(1048576); + if ($buf === '') { + break; + } + $buffer .= $buf; + } + return $buffer; + } + + $len = 0; + while (!$stream->eof() && $len < $maxLen) { + $buf = $stream->read($maxLen - $len); + if ($buf === '') { + break; + } + $buffer .= $buf; + $len = strlen($buffer); + } + + return $buffer; + } + + /** + * Calculate a hash of a stream. + * + * This method reads the entire stream to calculate a rolling hash, based + * on PHP's `hash_init` functions. + * + * @param StreamInterface $stream Stream to calculate the hash for + * @param string $algo Hash algorithm (e.g. md5, crc32, etc) + * @param bool $rawOutput Whether or not to use raw output + * + * @throws \RuntimeException on error. + */ + public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string + { + $pos = $stream->tell(); + + if ($pos > 0) { + $stream->rewind(); + } + + $ctx = hash_init($algo); + while (!$stream->eof()) { + hash_update($ctx, $stream->read(1048576)); + } + + $out = hash_final($ctx, $rawOutput); + $stream->seek($pos); + + return $out; + } + + /** + * 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. + * - 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. + * + * @param RequestInterface $request Request to clone and modify. + * @param array $changes Changes to apply. + */ + public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface + { + if (!$changes) { + return $request; + } + + $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()) { + $changes['set_headers']['Host'] = $host; + + if ($port = $changes['uri']->getPort()) { + $standardPorts = ['http' => 80, 'https' => 443]; + $scheme = $changes['uri']->getScheme(); + if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { + $changes['set_headers']['Host'] .= ':' . $port; + } + } + } + $uri = $changes['uri']; + } + + if (!empty($changes['remove_headers'])) { + $headers = self::caselessRemove($changes['remove_headers'], $headers); + } + + if (!empty($changes['set_headers'])) { + $headers = self::caselessRemove(array_keys($changes['set_headers']), $headers); + $headers = $changes['set_headers'] + $headers; + } + + if (isset($changes['query'])) { + $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); + } + + return $new; + } + + return new Request( + $changes['method'] ?? $request->getMethod(), + $uri, + $headers, + $changes['body'] ?? $request->getBody(), + $changes['version'] ?? $request->getProtocolVersion() + ); + } + + /** + * Read a line from the stream up to the maximum allowed buffer length. + * + * @param StreamInterface $stream Stream to read from + * @param int|null $maxLength Maximum buffer length + */ + public static function readLine(StreamInterface $stream, ?int $maxLength = null): string + { + $buffer = ''; + $size = 0; + + while (!$stream->eof()) { + if ('' === ($byte = $stream->read(1))) { + return $buffer; + } + $buffer .= $byte; + // Break when a new line is found or the max length - 1 is reached + if ($byte === "\n" || ++$size === $maxLength - 1) { + break; + } + } + + return $buffer; + } + + /** + * 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. + * + * @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|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)) { + $stream = self::tryFopen('php://temp', 'r+'); + if ($resource !== '') { + fwrite($stream, (string) $resource); + fseek($stream, 0); + } + return new Stream($stream, $options); + } + + switch (gettype($resource)) { + case 'resource': + /* + * The 'php://input' is a special stream with quirks and inconsistencies. + * We avoid using that stream by reading it into php://temp + */ + + /** @var resource $resource */ + if ((\stream_get_meta_data($resource)['uri'] ?? '') === 'php://input') { + $stream = self::tryFopen('php://temp', 'w+'); + stream_copy_to_stream($resource, $stream); + 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; + } + $result = $resource->current(); + $resource->next(); + return $result; + }, $options); + } elseif (method_exists($resource, '__toString')) { + return self::streamFor((string) $resource, $options); + } + break; + case 'NULL': + return new Stream(self::tryFopen('php://temp', 'r+'), $options); + } + + if (is_callable($resource)) { + return new PumpStream($resource, $options); + } + + throw new \InvalidArgumentException('Invalid resource type: ' . gettype($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. + * + * @param string $filename File to open + * @param string $mode Mode used to open the file + * + * @return resource + * + * @throws \RuntimeException if the file cannot be opened + */ + public static function tryFopen(string $filename, string $mode) + { + $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 + )); + + return true; + }); + + try { + /** @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); + } + + restore_error_handler(); + + if ($ex) { + /** @var $ex \RuntimeException */ + throw $ex; + } + + return $handle; + } + + /** + * 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. + * + * @param resource $stream + * + * @throws \RuntimeException if the stream cannot be read + */ + public static function tryGetContents($stream): string + { + $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 + )); + + return true; + }); + + try { + /** @var string|false $contents */ + $contents = stream_get_contents($stream); + + if ($contents === false) { + $ex = new \RuntimeException('Unable to read stream contents'); + } + } catch (\Throwable $e) { + $ex = new \RuntimeException(sprintf( + 'Unable to read stream contents: %s', + $e->getMessage() + ), 0, $e); + } + + restore_error_handler(); + + if ($ex) { + /** @var $ex \RuntimeException */ + throw $ex; + } + + return $contents; + } + + /** + * 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. + * + * @param string|UriInterface $uri + * + * @throws \InvalidArgumentException + */ + public static function uriFor($uri): UriInterface + { + if ($uri instanceof UriInterface) { + return $uri; + } + + if (is_string($uri)) { + return new Uri($uri); + } + + throw new \InvalidArgumentException('URI must be a string or UriInterface'); + } +} diff --git a/lib/laminas/laminas-loader/.laminas-ci.json b/lib/laminas/laminas-loader/.laminas-ci.json new file mode 100644 index 0000000000..bce3fa21bb --- /dev/null +++ b/lib/laminas/laminas-loader/.laminas-ci.json @@ -0,0 +1,5 @@ +{ + "ignore_php_platform_requirements": { + "8.1": true + } +} diff --git a/lib/laminas/laminas-loader/COPYRIGHT.md b/lib/laminas/laminas-loader/COPYRIGHT.md new file mode 100644 index 0000000000..0a8cccc06b --- /dev/null +++ b/lib/laminas/laminas-loader/COPYRIGHT.md @@ -0,0 +1 @@ +Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. (https://getlaminas.org/) diff --git a/lib/laminas/laminas-loader/LICENSE.md b/lib/laminas/laminas-loader/LICENSE.md new file mode 100644 index 0000000000..10b40f1423 --- /dev/null +++ b/lib/laminas/laminas-loader/LICENSE.md @@ -0,0 +1,26 @@ +Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +- Neither the name of Laminas Foundation nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/laminas/laminas-loader/README.md b/lib/laminas/laminas-loader/README.md new file mode 100644 index 0000000000..1c9911767b --- /dev/null +++ b/lib/laminas/laminas-loader/README.md @@ -0,0 +1,13 @@ +# laminas-loader + +> This package is considered feature-complete, and is now in **security-only** maintenance mode, following a [decision by the Technical Steering Committee](https://github.com/laminas/technical-steering-committee/blob/2b55453e172a1b8c9c4c212be7cf7e7a58b9352c/meetings/minutes/2020-08-03-TSC-Minutes.md#vote-on-components-to-mark-as-security-only). +> If you have a security issue, please [follow our security reporting guidelines](https://getlaminas.org/security/). +> If you wish to take on the role of maintainer, please [nominate yourself](https://github.com/laminas/technical-steering-committee/issues/new?assignees=&labels=Nomination&template=Maintainer_Nomination.md&title=%5BNOMINATION%5D%5BMAINTAINER%5D%3A+%7Bname+of+person+being+nominated%7D) + + +[![Build Status](https://github.com/laminas/laminas-loader/workflows/Continuous%20Integration/badge.svg)](https://github.com/laminas/laminas-loader/actions?query=workflow%3A"Continuous+Integration") + +laminas-loader provides different strategies for autoloading PHP classes. + +- File issues at https://github.com/laminas/laminas-loader/issues +- Documentation is at https://docs.laminas.dev/laminas-loader/ diff --git a/lib/laminas/laminas-loader/composer.json b/lib/laminas/laminas-loader/composer.json new file mode 100644 index 0000000000..8457d67ed5 --- /dev/null +++ b/lib/laminas/laminas-loader/composer.json @@ -0,0 +1,51 @@ +{ + "name": "laminas/laminas-loader", + "description": "Autoloading and plugin loading strategies", + "license": "BSD-3-Clause", + "keywords": [ + "laminas", + "loader" + ], + "homepage": "https://laminas.dev", + "support": { + "docs": "https://docs.laminas.dev/laminas-loader/", + "issues": "https://github.com/laminas/laminas-loader/issues", + "source": "https://github.com/laminas/laminas-loader", + "rss": "https://github.com/laminas/laminas-loader/releases.atom", + "chat": "https://laminas.dev/chat", + "forum": "https://discourse.laminas.dev" + }, + "config": { + "sort-packages": true + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.2.1", + "phpunit/phpunit": "^9.3" + }, + "autoload": { + "psr-4": { + "Laminas\\Loader\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "LaminasTest\\Loader\\": "test/" + } + }, + "scripts": { + "check": [ + "@cs-check", + "@test" + ], + "cs-check": "phpcs", + "cs-fix": "phpcbf", + "test": "phpunit --colors=always", + "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" + }, + "conflict": { + "zendframework/zend-loader": "*" + } +} diff --git a/lib/laminas/laminas-loader/composer.lock b/lib/laminas/laminas-loader/composer.lock new file mode 100644 index 0000000000..19e27ed25c --- /dev/null +++ b/lib/laminas/laminas-loader/composer.lock @@ -0,0 +1,2460 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "04bc51f9c0a84aa54ea419f7c9fbee94", + "packages": [], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.1", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "fe390591e0241955f22eb9ba327d137e501c771c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/fe390591e0241955f22eb9ba327d137e501c771c", + "reference": "fe390591e0241955f22eb9ba327d137e501c771c", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "phpcompatibility/php-compatibility": "^9.0", + "sensiolabs/security-checker": "^4.1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2020-12-07T18:04:37+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-11-10T18:47:58+00:00" + }, + { + "name": "laminas/laminas-coding-standard", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-coding-standard.git", + "reference": "c953ecb1d37034f4aa326046e2c24a10fe0a2845" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-coding-standard/zipball/c953ecb1d37034f4aa326046e2c24a10fe0a2845", + "reference": "c953ecb1d37034f4aa326046e2c24a10fe0a2845", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.3 || ~8.0.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8", + "webimpress/coding-standard": "^1.1.6" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "LaminasCodingStandard\\": "src/LaminasCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Laminas Coding Standard", + "homepage": "https://laminas.dev", + "keywords": [ + "Coding Standard", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-coding-standard/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-coding-standard/issues", + "rss": "https://github.com/laminas/laminas-coding-standard/releases.atom", + "source": "https://github.com/laminas/laminas-coding-standard" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-05-17T17:39:41+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.10.2", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-11-13T09:40:50+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.12.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "6608f01670c3cc5079e18c1dab1104e002579143" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143", + "reference": "6608f01670c3cc5079e18c1dab1104e002579143", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0" + }, + "time": "2021-07-21T10:44:31+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "bae7c545bef187884426f042434e561ab1ddb182" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", + "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.1.0" + }, + "time": "2021-02-23T14:00:09+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + }, + "time": "2020-09-03T19:13:55+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + }, + "time": "2020-09-17T18:55:26+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.13.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.1", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/1.13.0" + }, + "time": "2021-03-17T13:42:18+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "f6293e1b30a2354e8428e004689671b83871edde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde", + "reference": "f6293e1b30a2354e8428e004689671b83871edde", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.10.2", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-03-28T07:26:59+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:57:25+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.5.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "ea8c2dfb1065eb35a79b3681eee6e6fb0a6f273b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea8c2dfb1065eb35a79b3681eee6e6fb0a6f273b", + "reference": "ea8c2dfb1065eb35a79b3681eee6e6fb0a6f273b", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpspec/prophecy": "^1.12.1", + "phpunit/php-code-coverage": "^9.2.3", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.5", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.3", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^2.3.4", + "sebastian/version": "^3.0.2" + }, + "require-dev": { + "ext-pdo": "*", + "phpspec/prophecy-phpunit": "^2.0.1" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ], + "files": [ + "src/Framework/Assert/Functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.9" + }, + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-08-31T06:47:40+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:49:45+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:10:38+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:52:38+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:24:23+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-11T13:31:12+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:17:30+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "2.3.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-15T12:49:02+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.0", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ffced0d2c8fa8e6cdc4d695a743271fab6c38625", + "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-04-09T00:54:41+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + }, + { + "name": "webimpress/coding-standard", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/webimpress/coding-standard.git", + "reference": "8f4a220de33f471a8101836f7ec72b852c3f9f03" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webimpress/coding-standard/zipball/8f4a220de33f471a8101836f7ec72b852c3f9f03", + "reference": "8f4a220de33f471a8101836f7ec72b852c3f9f03", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0", + "squizlabs/php_codesniffer": "^3.6" + }, + "require-dev": { + "phpunit/phpunit": "^9.5.4" + }, + "type": "phpcodesniffer-standard", + "extra": { + "dev-master": "1.2.x-dev", + "dev-develop": "1.3.x-dev" + }, + "autoload": { + "psr-4": { + "WebimpressCodingStandard\\": "src/WebimpressCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "description": "Webimpress Coding Standard", + "keywords": [ + "Coding Standard", + "PSR-2", + "phpcs", + "psr-12", + "webimpress" + ], + "support": { + "issues": "https://github.com/webimpress/coding-standard/issues", + "source": "https://github.com/webimpress/coding-standard/tree/1.2.2" + }, + "funding": [ + { + "url": "https://github.com/michalbundyra", + "type": "github" + } + ], + "time": "2021-04-12T12:51:27+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.10.0" + }, + "time": "2021-03-09T10:59:23+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "platform-dev": [], + "plugin-api-version": "2.0.0" +} diff --git a/lib/laminas/laminas-loader/phpcs.xml.dist b/lib/laminas/laminas-loader/phpcs.xml.dist new file mode 100644 index 0000000000..174a8d4c95 --- /dev/null +++ b/lib/laminas/laminas-loader/phpcs.xml.dist @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + src + test + */TestAsset/* + */_files/* + + + + + + /src/Exception/* + /src/SplAutoloader.php + /src/AutoloaderFactory.php + /src/ClassMapAutoloader.php + /src/ModuleAutoloader.php + /src/StandardAutoloader.php + + diff --git a/lib/laminas/laminas-loader/src/AutoloaderFactory.php b/lib/laminas/laminas-loader/src/AutoloaderFactory.php new file mode 100644 index 0000000000..e1dabba1a5 --- /dev/null +++ b/lib/laminas/laminas-loader/src/AutoloaderFactory.php @@ -0,0 +1,212 @@ + + * array( + * '' => $autoloaderOptions, + * ) + * + * + * The factory will then loop through and instantiate each autoloader with + * the specified options, and register each with the spl_autoloader. + * + * You may retrieve the concrete autoloader instances later using + * {@link getRegisteredAutoloaders()}. + * + * Note that the class names must be resolvable on the include_path or via + * the Laminas library, using PSR-0 rules (unless the class has already been + * loaded). + * + * @param array|Traversable $options (optional) options to use. Defaults to Laminas\Loader\StandardAutoloader + * @return void + * @throws Exception\InvalidArgumentException For invalid options. + * @throws Exception\InvalidArgumentException For unloadable autoloader classes. + * @throws Exception\DomainException For autoloader classes not implementing SplAutoloader. + */ + public static function factory($options = null) + { + if (null === $options) { + if (! isset(static::$loaders[static::STANDARD_AUTOLOADER])) { + $autoloader = static::getStandardAutoloader(); + $autoloader->register(); + static::$loaders[static::STANDARD_AUTOLOADER] = $autoloader; + } + + // Return so we don't hit the next check's exception (we're done here anyway) + return; + } + + if (! is_array($options) && ! $options instanceof Traversable) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException( + 'Options provided must be an array or Traversable' + ); + } + + foreach ($options as $class => $autoloaderOptions) { + if (! isset(static::$loaders[$class])) { + $autoloader = static::getStandardAutoloader(); + if (! class_exists($class) && ! $autoloader->autoload($class)) { + require_once 'Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException( + sprintf('Autoloader class "%s" not loaded', $class) + ); + } + + if (! is_subclass_of($class, SplAutoloader::class)) { + require_once 'Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException( + sprintf('Autoloader class %s must implement Laminas\\Loader\\SplAutoloader', $class) + ); + } + + if ($class === static::STANDARD_AUTOLOADER) { + $autoloader->setOptions($autoloaderOptions); + } else { + $autoloader = new $class($autoloaderOptions); + } + $autoloader->register(); + static::$loaders[$class] = $autoloader; + } else { + static::$loaders[$class]->setOptions($autoloaderOptions); + } + } + } + + /** + * Get a list of all autoloaders registered with the factory + * + * Returns an array of autoloader instances. + * + * @return array + */ + public static function getRegisteredAutoloaders() + { + return static::$loaders; + } + + /** + * Retrieves an autoloader by class name + * + * @param string $class + * @return SplAutoloader + * @throws Exception\InvalidArgumentException For non-registered class. + */ + public static function getRegisteredAutoloader($class) + { + if (! isset(static::$loaders[$class])) { + require_once 'Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException(sprintf('Autoloader class "%s" not loaded', $class)); + } + return static::$loaders[$class]; + } + + /** + * Unregisters all autoloaders that have been registered via the factory. + * This will NOT unregister autoloaders registered outside of the fctory. + * + * @return void + */ + public static function unregisterAutoloaders() + { + foreach (static::getRegisteredAutoloaders() as $class => $autoloader) { + spl_autoload_unregister([$autoloader, 'autoload']); + unset(static::$loaders[$class]); + } + } + + /** + * Unregister a single autoloader by class name + * + * @param string $autoloaderClass + * @return bool + */ + public static function unregisterAutoloader($autoloaderClass) + { + if (! isset(static::$loaders[$autoloaderClass])) { + return false; + } + + $autoloader = static::$loaders[$autoloaderClass]; + spl_autoload_unregister([$autoloader, 'autoload']); + unset(static::$loaders[$autoloaderClass]); + return true; + } + + /** + * Get an instance of the standard autoloader + * + * Used to attempt to resolve autoloader classes, using the + * StandardAutoloader. The instance is marked as a fallback autoloader, to + * allow resolving autoloaders not under the "Laminas" namespace. + * + * @return SplAutoloader + */ + protected static function getStandardAutoloader() + { + if (null !== static::$standardAutoloader) { + return static::$standardAutoloader; + } + + if (! class_exists(static::STANDARD_AUTOLOADER)) { + // Extract the filename from the classname + $stdAutoloader = substr(strrchr(static::STANDARD_AUTOLOADER, '\\'), 1); + require_once __DIR__ . "/$stdAutoloader.php"; + } + $loader = new StandardAutoloader(); + static::$standardAutoloader = $loader; + return static::$standardAutoloader; + } + + /** + * Checks if the object has this class as one of its parents + * + * @deprecated since laminas 2.3 requires PHP >= 5.3.23 + * + * @see https://bugs.php.net/bug.php?id=53727 + * @see https://github.com/zendframework/zf2/pull/1807 + * + * @param string $className + * @param string $type + * @return bool + */ + protected static function isSubclassOf($className, $type) + { + return is_subclass_of($className, $type); + } +} diff --git a/lib/laminas/laminas-loader/src/ClassMapAutoloader.php b/lib/laminas/laminas-loader/src/ClassMapAutoloader.php new file mode 100644 index 0000000000..b8edf3e613 --- /dev/null +++ b/lib/laminas/laminas-loader/src/ClassMapAutoloader.php @@ -0,0 +1,234 @@ +setOptions($options); + } + } + + /** + * Configure the autoloader + * + * Proxies to {@link registerAutoloadMaps()}. + * + * @param array|Traversable $options + * @return ClassMapAutoloader + */ + public function setOptions($options) + { + $this->registerAutoloadMaps($options); + return $this; + } + + /** + * Register an autoload map + * + * An autoload map may be either an associative array, or a file returning + * an associative array. + * + * An autoload map should be an associative array containing + * classname/file pairs. + * + * @param string|array $map + * @throws Exception\InvalidArgumentException + * @return ClassMapAutoloader + */ + public function registerAutoloadMap($map) + { + if (is_string($map)) { + $location = $map; + if ($this === ($map = $this->loadMapFromFile($location))) { + return $this; + } + } + + if (! is_array($map)) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException(sprintf( + 'Map file provided does not return a map. Map file: "%s"', + isset($location) && is_string($location) ? $location : 'unexpected type: ' . gettype($map) + )); + } + + $this->map = $map + $this->map; + + if (isset($location)) { + $this->mapsLoaded[] = $location; + } + + return $this; + } + + /** + * Register many autoload maps at once + * + * @param array $locations + * @throws Exception\InvalidArgumentException + * @return ClassMapAutoloader + */ + public function registerAutoloadMaps($locations) + { + if (! is_array($locations) && ! $locations instanceof Traversable) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException('Map list must be an array or implement Traversable'); + } + foreach ($locations as $location) { + $this->registerAutoloadMap($location); + } + return $this; + } + + /** + * Retrieve current autoload map + * + * @return array + */ + public function getAutoloadMap() + { + return $this->map; + } + + /** + * {@inheritDoc} + */ + public function autoload($class) + { + if (isset($this->map[$class])) { + require_once $this->map[$class]; + + return $class; + } + + return false; + } + + /** + * Register the autoloader with spl_autoload registry + * + * @return void + */ + public function register() + { + spl_autoload_register([$this, 'autoload'], true, true); + } + + /** + * Load a map from a file + * + * If the map has been previously loaded, returns the current instance; + * otherwise, returns whatever was returned by calling include() on the + * location. + * + * @param string $location + * @return ClassMapAutoloader|mixed + * @throws Exception\InvalidArgumentException For nonexistent locations. + */ + protected function loadMapFromFile($location) + { + if (! file_exists($location)) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException(sprintf( + 'Map file provided does not exist. Map file: "%s"', + is_string($location) ? $location : 'unexpected type: ' . gettype($location) + )); + } + + if (! $path = static::realPharPath($location)) { + $path = realpath($location); + } + + if (in_array($path, $this->mapsLoaded)) { + // Already loaded this map + return $this; + } + + return include $path; + } + + /** + * Resolve the real_path() to a file within a phar. + * + * @see https://bugs.php.net/bug.php?id=52769 + * + * @param string $path + * @return string + */ + public static function realPharPath($path) + { + if (! preg_match('|^phar:(/{2,3})|', $path, $match)) { + return; + } + + $prefixLength = 5 + strlen($match[1]); + $parts = explode('/', str_replace(['/', '\\'], '/', substr($path, $prefixLength))); + $parts = array_values(array_filter($parts, function ($p) { + return $p !== '' && $p !== '.'; + })); + + array_walk($parts, function ($value, $key) use (&$parts) { + if ($value === '..') { + unset($parts[$key], $parts[$key - 1]); + $parts = array_values($parts); + } + }); + + if (file_exists($realPath = str_pad('phar:', $prefixLength, '/') . implode('/', $parts))) { + return $realPath; + } + } +} diff --git a/lib/laminas/laminas-loader/src/Exception/BadMethodCallException.php b/lib/laminas/laminas-loader/src/Exception/BadMethodCallException.php new file mode 100644 index 0000000000..98f08aa7d2 --- /dev/null +++ b/lib/laminas/laminas-loader/src/Exception/BadMethodCallException.php @@ -0,0 +1,10 @@ + path */ + protected $explicitPaths = []; + + /** @var array An array of namespaceName => namespacePath */ + protected $namespacedPaths = []; + + /** @var string Will contain the absolute phar:// path to the executable when packaged as phar file */ + protected $pharBasePath = ""; + + /** @var array An array of supported phar extensions (filled on constructor) */ + protected $pharExtensions = []; + + /** @var array An array of module classes to their containing files */ + protected $moduleClassMap = []; + + /** + * Constructor + * + * Allow configuration of the autoloader via the constructor. + * + * @param null|array|Traversable $options + */ + public function __construct($options = null) + { + if (extension_loaded('phar')) { + $this->pharBasePath = Phar::running(true); + $this->pharExtensions = [ + 'phar', + 'phar.tar', + 'tar', + ]; + + // ext/zlib enabled -> phar can read gzip & zip compressed files + if (extension_loaded('zlib')) { + $this->pharExtensions[] = 'phar.gz'; + $this->pharExtensions[] = 'phar.tar.gz'; + $this->pharExtensions[] = 'tar.gz'; + + $this->pharExtensions[] = 'phar.zip'; + $this->pharExtensions[] = 'zip'; + } + + // ext/bzip2 enabled -> phar can read bz2 compressed files + if (extension_loaded('bzip2')) { + $this->pharExtensions[] = 'phar.bz2'; + $this->pharExtensions[] = 'phar.tar.bz2'; + $this->pharExtensions[] = 'tar.bz2'; + } + } + + if (null !== $options) { + $this->setOptions($options); + } + } + + /** + * Configure the autoloader + * + * In most cases, $options should be either an associative array or + * Traversable object. + * + * @param array|Traversable $options + * @return ModuleAutoloader + */ + public function setOptions($options) + { + $this->registerPaths($options); + return $this; + } + + /** + * Retrieves the class map for all loaded modules. + * + * @return array + */ + public function getModuleClassMap() + { + return $this->moduleClassMap; + } + + /** + * Sets the class map used to speed up the module autoloading. + * + * @param array $classmap + * @return ModuleAutoloader + */ + public function setModuleClassMap(array $classmap) + { + $this->moduleClassMap = $classmap; + + return $this; + } + + /** + * Autoload a class + * + * @param string $class + * @return mixed + * False [if unable to load $class] + * get_class($class) [if $class is successfully loaded] + */ + public function autoload($class) + { + // Limit scope of this autoloader + if (substr($class, -7) !== '\Module') { + return false; + } + + if (isset($this->moduleClassMap[$class])) { + require_once $this->moduleClassMap[$class]; + return $class; + } + + $moduleName = substr($class, 0, -7); + if (isset($this->explicitPaths[$moduleName])) { + $classLoaded = $this->loadModuleFromDir($this->explicitPaths[$moduleName], $class); + if ($classLoaded) { + return $classLoaded; + } + + $classLoaded = $this->loadModuleFromPhar($this->explicitPaths[$moduleName], $class); + if ($classLoaded) { + return $classLoaded; + } + } + + if (count($this->namespacedPaths) >= 1) { + foreach ($this->namespacedPaths as $namespace => $path) { + if (false === strpos($moduleName, $namespace)) { + continue; + } + + $moduleNameBuffer = str_replace($namespace . "\\", "", $moduleName); + $path .= DIRECTORY_SEPARATOR . $moduleNameBuffer . DIRECTORY_SEPARATOR; + + $classLoaded = $this->loadModuleFromDir($path, $class); + if ($classLoaded) { + return $classLoaded; + } + + $classLoaded = $this->loadModuleFromPhar($path, $class); + if ($classLoaded) { + return $classLoaded; + } + } + } + + $moduleClassPath = str_replace('\\', DIRECTORY_SEPARATOR, $moduleName); + + $pharSuffixPattern = null; + if ($this->pharExtensions) { + $pharSuffixPattern = '(' . implode('|', array_map('preg_quote', $this->pharExtensions)) . ')'; + } + + foreach ($this->paths as $path) { + $path .= $moduleClassPath; + + if ($path === '.' || substr($path, 0, 2) === './' || substr($path, 0, 2) === '.\\') { + if (! $basePath = $this->pharBasePath) { + $basePath = realpath('.'); + } + + if (false === $basePath) { + $basePath = getcwd(); + } + + $path = rtrim($basePath, '\/\\') . substr($path, 1); + } + + $classLoaded = $this->loadModuleFromDir($path, $class); + if ($classLoaded) { + return $classLoaded; + } + + // No directory with Module.php, searching for phars + if ($pharSuffixPattern) { + foreach (new GlobIterator($path . '.*') as $entry) { + if ($entry->isDir()) { + continue; + } + + if (! preg_match('#.+\.' . $pharSuffixPattern . '$#', $entry->getPathname())) { + continue; + } + + $classLoaded = $this->loadModuleFromPhar($entry->getPathname(), $class); + if ($classLoaded) { + return $classLoaded; + } + } + } + } + + return false; + } + + /** + * loadModuleFromDir + * + * @param string $dirPath + * @param string $class + * @return mixed + * False [if unable to load $class] + * get_class($class) [if $class is successfully loaded] + */ + protected function loadModuleFromDir($dirPath, $class) + { + $modulePath = $dirPath . '/Module.php'; + if (substr($modulePath, 0, 7) === 'phar://') { + $file = new PharFileInfo($modulePath); + } else { + $file = new SplFileInfo($modulePath); + } + + if ($file->isReadable() && $file->isFile()) { + // Found directory with Module.php in it + $absModulePath = $this->pharBasePath ? (string) $file : $file->getRealPath(); + require_once $absModulePath; + if (class_exists($class)) { + $this->moduleClassMap[$class] = $absModulePath; + return $class; + } + } + return false; + } + + /** + * loadModuleFromPhar + * + * @param string $pharPath + * @param string $class + * @return mixed + * False [if unable to load $class] + * get_class($class) [if $class is successfully loaded] + */ + protected function loadModuleFromPhar($pharPath, $class) + { + $pharPath = static::normalizePath($pharPath, false); + $file = new SplFileInfo($pharPath); + if (! $file->isReadable() || ! $file->isFile()) { + return false; + } + + $fileRealPath = $file->getRealPath(); + + // Phase 0: Check for executable phar with Module class in stub + if (strpos($fileRealPath, '.phar') !== false) { + // First see if the stub makes the Module class available + require_once $fileRealPath; + if (class_exists($class)) { + $this->moduleClassMap[$class] = $fileRealPath; + return $class; + } + } + + // Phase 1: Not executable phar, no stub, or stub did not provide Module class; try Module.php directly + $moduleClassFile = 'phar://' . $fileRealPath . '/Module.php'; + $moduleFile = new SplFileInfo($moduleClassFile); + if ($moduleFile->isReadable() && $moduleFile->isFile()) { + require_once $moduleClassFile; + if (class_exists($class)) { + $this->moduleClassMap[$class] = $moduleClassFile; + return $class; + } + } + + // Phase 2: Check for nested module directory within archive + // Checks for /path/to/MyModule.tar/MyModule/Module.php + // (shell-integrated zip/tar utilities wrap directories like this) + $pharBaseName = $this->pharFileToModuleName($fileRealPath); + $moduleClassFile = 'phar://' . $fileRealPath . '/' . $pharBaseName . '/Module.php'; + $moduleFile = new SplFileInfo($moduleClassFile); + if ($moduleFile->isReadable() && $moduleFile->isFile()) { + require_once $moduleClassFile; + if (class_exists($class)) { + $this->moduleClassMap[$class] = $moduleClassFile; + return $class; + } + } + + return false; + } + + /** + * Register the autoloader with spl_autoload registry + * + * @return void + */ + public function register() + { + spl_autoload_register([$this, 'autoload']); + } + + /** + * Unregister the autoloader with spl_autoload registry + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister([$this, 'autoload']); + } + + /** + * registerPaths + * + * @param array|Traversable $paths + * @throws InvalidArgumentException + * @return ModuleAutoloader + */ + public function registerPaths($paths) + { + if (! is_array($paths) && ! $paths instanceof Traversable) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException( + 'Parameter to \\Laminas\\Loader\\ModuleAutoloader\'s ' + . 'registerPaths method must be an array or ' + . 'implement the Traversable interface' + ); + } + + foreach ($paths as $module => $path) { + if (is_string($module)) { + $this->registerPath($path, $module); + } else { + $this->registerPath($path); + } + } + + return $this; + } + + /** + * registerPath + * + * @param string $path + * @param bool|string $moduleName + * @throws InvalidArgumentException + * @return ModuleAutoloader + */ + public function registerPath($path, $moduleName = false) + { + if (! is_string($path)) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException(sprintf( + 'Invalid path provided; must be a string, received %s', + gettype($path) + )); + } + if ($moduleName) { + if (in_array(substr($moduleName, -2), ['\\*', '\\%'])) { + $this->namespacedPaths[substr($moduleName, 0, -2)] = static::normalizePath($path); + } else { + $this->explicitPaths[$moduleName] = static::normalizePath($path); + } + } else { + $this->paths[] = static::normalizePath($path); + } + return $this; + } + + /** + * getPaths + * + * This is primarily for unit testing, but could have other uses. + * + * @return array + */ + public function getPaths() + { + return $this->paths; + } + + /** + * Returns the base module name from the path to a phar + * + * @param string $pharPath + * @return string + */ + protected function pharFileToModuleName($pharPath) + { + do { + $pathinfo = pathinfo($pharPath); + $pharPath = $pathinfo['filename']; + } while (isset($pathinfo['extension'])); + return $pathinfo['filename']; + } + + /** + * Normalize a path for insertion in the stack + * + * @param string $path + * @param bool $trailingSlash Whether trailing slash should be included + * @return string + */ + public static function normalizePath($path, $trailingSlash = true) + { + $path = rtrim($path, '/'); + $path = rtrim($path, '\\'); + if ($trailingSlash) { + $path .= DIRECTORY_SEPARATOR; + } + return $path; + } +} diff --git a/lib/laminas/laminas-loader/src/PluginClassLoader.php b/lib/laminas/laminas-loader/src/PluginClassLoader.php new file mode 100644 index 0000000000..0743cc7aef --- /dev/null +++ b/lib/laminas/laminas-loader/src/PluginClassLoader.php @@ -0,0 +1,223 @@ + class name pairs + * + * @var array + */ + protected $plugins = []; + + /** + * Static map allow global seeding of plugin loader + * + * @var array + */ + protected static $staticMap = []; + + /** + * Constructor + * + * @param null|array|Traversable $map If provided, seeds the loader with a map + */ + public function __construct($map = null) + { + // Merge in static overrides + if (! empty(static::$staticMap)) { + $this->registerPlugins(static::$staticMap); + } + + // Merge in constructor arguments + if ($map !== null) { + $this->registerPlugins($map); + } + } + + /** + * Add a static map of plugins + * + * A null value will clear the static map. + * + * @param null|array|Traversable $map + * @throws Exception\InvalidArgumentException + * @return void + */ + public static function addStaticMap($map) + { + if (null === $map) { + static::$staticMap = []; + return; + } + + if (! is_array($map) && ! $map instanceof Traversable) { + throw new Exception\InvalidArgumentException('Expects an array or Traversable object'); + } + foreach ($map as $key => $value) { + static::$staticMap[$key] = $value; + } + } + + /** + * Register a class to a given short name + * + * @param string $shortName + * @param string $className + * @return PluginClassLoader + */ + public function registerPlugin($shortName, $className) + { + $this->plugins[strtolower($shortName)] = $className; + return $this; + } + + /** + * Register many plugins at once + * + * If $map is a string, assumes that the map is the class name of a + * Traversable object (likely a ShortNameLocator); it will then instantiate + * this class and use it to register plugins. + * + * If $map is an array or Traversable object, it will iterate it to + * register plugin names/classes. + * + * For all other arguments, or if the string $map is not a class or not a + * Traversable class, an exception will be raised. + * + * @param string|array|Traversable $map + * @return PluginClassLoader + * @throws Exception\InvalidArgumentException + */ + public function registerPlugins($map) + { + if (is_string($map)) { + if (! class_exists($map)) { + throw new Exception\InvalidArgumentException('Map class provided is invalid'); + } + $map = new $map(); + } + if (is_array($map)) { + $map = new ArrayIterator($map); + } + if (! $map instanceof Traversable) { + throw new Exception\InvalidArgumentException('Map provided is invalid; must be traversable'); + } + + // iterator_apply doesn't work as expected with IteratorAggregate + if ($map instanceof IteratorAggregate) { + $map = $map->getIterator(); + } + + foreach ($map as $name => $class) { + if (is_int($name) || is_numeric($name)) { + if (! is_object($class) && class_exists($class)) { + $class = new $class(); + } + + if ($class instanceof Traversable) { + $this->registerPlugins($class); + continue; + } + } + + $this->registerPlugin($name, $class); + } + + return $this; + } + + /** + * Unregister a short name lookup + * + * @param mixed $shortName + * @return PluginClassLoader + */ + public function unregisterPlugin($shortName) + { + $lookup = strtolower($shortName); + if (array_key_exists($lookup, $this->plugins)) { + unset($this->plugins[$lookup]); + } + return $this; + } + + /** + * Get a list of all registered plugins + * + * @return array|Traversable + */ + public function getRegisteredPlugins() + { + return $this->plugins; + } + + /** + * Whether or not a plugin by a specific name has been registered + * + * @param string $name + * @return bool + */ + public function isLoaded($name) + { + $lookup = strtolower($name); + return isset($this->plugins[$lookup]); + } + + /** + * Return full class name for a named helper + * + * @param string $name + * @return string|false + */ + public function getClassName($name) + { + return $this->load($name); + } + + /** + * Load a helper via the name provided + * + * @param string $name + * @return string|false + */ + public function load($name) + { + if (! $this->isLoaded($name)) { + return false; + } + return $this->plugins[strtolower($name)]; + } + + /** + * Defined by IteratorAggregate + * + * Returns an instance of ArrayIterator, containing a map of + * all plugins + * + * @return ArrayIterator + */ + #[ReturnTypeWillChange] + public function getIterator() + { + return new ArrayIterator($this->plugins); + } +} diff --git a/lib/laminas/laminas-loader/src/PluginClassLocator.php b/lib/laminas/laminas-loader/src/PluginClassLocator.php new file mode 100644 index 0000000000..181b88ef81 --- /dev/null +++ b/lib/laminas/laminas-loader/src/PluginClassLocator.php @@ -0,0 +1,36 @@ + + * spl_autoload_register(array($this, 'autoload')); + * + * + * @return void + */ + public function register(); +} diff --git a/lib/laminas/laminas-loader/src/StandardAutoloader.php b/lib/laminas/laminas-loader/src/StandardAutoloader.php new file mode 100644 index 0000000000..6d468187b4 --- /dev/null +++ b/lib/laminas/laminas-loader/src/StandardAutoloader.php @@ -0,0 +1,332 @@ +setOptions($options); + } + } + + /** + * Configure autoloader + * + * Allows specifying both "namespace" and "prefix" pairs, using the + * following structure: + * + * array( + * 'namespaces' => array( + * 'Laminas' => '/path/to/Laminas/library', + * 'Doctrine' => '/path/to/Doctrine/library', + * ), + * 'prefixes' => array( + * 'Phly_' => '/path/to/Phly/library', + * ), + * 'fallback_autoloader' => true, + * ) + * + * + * @param array|Traversable $options + * @throws Exception\InvalidArgumentException + * @return StandardAutoloader + */ + public function setOptions($options) + { + if (! is_array($options) && ! $options instanceof Traversable) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException('Options must be either an array or Traversable'); + } + + foreach ($options as $type => $pairs) { + switch ($type) { + case self::AUTOREGISTER_LAMINAS: + if ($pairs) { + $this->registerNamespace('Laminas', dirname(__DIR__)); + } + break; + case self::LOAD_NS: + if (is_array($pairs) || $pairs instanceof Traversable) { + $this->registerNamespaces($pairs); + } + break; + case self::LOAD_PREFIX: + if (is_array($pairs) || $pairs instanceof Traversable) { + $this->registerPrefixes($pairs); + } + break; + case self::ACT_AS_FALLBACK: + $this->setFallbackAutoloader($pairs); + break; + default: + // ignore + } + } + return $this; + } + + /** + * Set flag indicating fallback autoloader status + * + * @param bool $flag + * @return StandardAutoloader + */ + public function setFallbackAutoloader($flag) + { + $this->fallbackAutoloaderFlag = (bool) $flag; + return $this; + } + + /** + * Is this autoloader acting as a fallback autoloader? + * + * @return bool + */ + public function isFallbackAutoloader() + { + return $this->fallbackAutoloaderFlag; + } + + /** + * Register a namespace/directory pair + * + * @param string $namespace + * @param string $directory + * @return StandardAutoloader + */ + public function registerNamespace($namespace, $directory) + { + $namespace = rtrim($namespace, self::NS_SEPARATOR) . self::NS_SEPARATOR; + $this->namespaces[$namespace] = $this->normalizeDirectory($directory); + return $this; + } + + /** + * Register many namespace/directory pairs at once + * + * @param array $namespaces + * @throws Exception\InvalidArgumentException + * @return StandardAutoloader + */ + public function registerNamespaces($namespaces) + { + if (! is_array($namespaces) && ! $namespaces instanceof Traversable) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException('Namespace pairs must be either an array or Traversable'); + } + + foreach ($namespaces as $namespace => $directory) { + $this->registerNamespace($namespace, $directory); + } + return $this; + } + + /** + * Register a prefix/directory pair + * + * @param string $prefix + * @param string $directory + * @return StandardAutoloader + */ + public function registerPrefix($prefix, $directory) + { + $prefix = rtrim($prefix, self::PREFIX_SEPARATOR) . self::PREFIX_SEPARATOR; + $this->prefixes[$prefix] = $this->normalizeDirectory($directory); + return $this; + } + + /** + * Register many namespace/directory pairs at once + * + * @param array $prefixes + * @throws Exception\InvalidArgumentException + * @return StandardAutoloader + */ + public function registerPrefixes($prefixes) + { + if (! is_array($prefixes) && ! $prefixes instanceof Traversable) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException('Prefix pairs must be either an array or Traversable'); + } + + foreach ($prefixes as $prefix => $directory) { + $this->registerPrefix($prefix, $directory); + } + return $this; + } + + /** + * Defined by Autoloadable; autoload a class + * + * @param string $class + * @return false|string + */ + public function autoload($class) + { + $isFallback = $this->isFallbackAutoloader(); + if (false !== strpos($class, self::NS_SEPARATOR)) { + if ($this->loadClass($class, self::LOAD_NS)) { + return $class; + } elseif ($isFallback) { + return $this->loadClass($class, self::ACT_AS_FALLBACK); + } + return false; + } + if (false !== strpos($class, self::PREFIX_SEPARATOR)) { + if ($this->loadClass($class, self::LOAD_PREFIX)) { + return $class; + } elseif ($isFallback) { + return $this->loadClass($class, self::ACT_AS_FALLBACK); + } + return false; + } + if ($isFallback) { + return $this->loadClass($class, self::ACT_AS_FALLBACK); + } + return false; + } + + /** + * Register the autoloader with spl_autoload + * + * @return void + */ + public function register() + { + spl_autoload_register([$this, 'autoload']); + } + + /** + * Transform the class name to a filename + * + * @param string $class + * @param string $directory + * @return string + */ + protected function transformClassNameToFilename($class, $directory) + { + // $class may contain a namespace portion, in which case we need + // to preserve any underscores in that portion. + $matches = []; + preg_match('/(?P.+\\\)?(?P[^\\\]+$)/', $class, $matches); + + $class = $matches['class'] ?? ''; + $namespace = $matches['namespace'] ?? ''; + + return $directory + . str_replace(self::NS_SEPARATOR, '/', $namespace) + . str_replace(self::PREFIX_SEPARATOR, '/', $class) + . '.php'; + } + + /** + * Load a class, based on its type (namespaced or prefixed) + * + * @param string $class + * @param string $type + * @return bool|string + * @throws Exception\InvalidArgumentException + */ + protected function loadClass($class, $type) + { + if (! in_array($type, [self::LOAD_NS, self::LOAD_PREFIX, self::ACT_AS_FALLBACK])) { + require_once __DIR__ . '/Exception/InvalidArgumentException.php'; + throw new Exception\InvalidArgumentException(); + } + + // Fallback autoloading + if ($type === self::ACT_AS_FALLBACK) { + // create filename + $filename = $this->transformClassNameToFilename($class, ''); + $resolvedName = stream_resolve_include_path($filename); + if ($resolvedName !== false) { + return include $resolvedName; + } + return false; + } + + // Namespace and/or prefix autoloading + foreach ($this->$type as $leader => $path) { + if (0 === strpos($class, $leader)) { + // Trim off leader (namespace or prefix) + $trimmedClass = substr($class, strlen($leader)); + + // create filename + $filename = $this->transformClassNameToFilename($trimmedClass, $path); + if (file_exists($filename)) { + return include $filename; + } + } + } + return false; + } + + /** + * Normalize the directory to include a trailing directory separator + * + * @param string $directory + * @return string + */ + protected function normalizeDirectory($directory) + { + $last = $directory[strlen($directory) - 1]; + if (in_array($last, ['/', '\\'])) { + $directory[strlen($directory) - 1] = DIRECTORY_SEPARATOR; + return $directory; + } + $directory .= DIRECTORY_SEPARATOR; + return $directory; + } +} diff --git a/lib/laminas/laminas-mail/.laminas-ci.json b/lib/laminas/laminas-mail/.laminas-ci.json new file mode 100644 index 0000000000..bce3fa21bb --- /dev/null +++ b/lib/laminas/laminas-mail/.laminas-ci.json @@ -0,0 +1,5 @@ +{ + "ignore_php_platform_requirements": { + "8.1": true + } +} diff --git a/lib/laminas/laminas-mail/.laminas-ci/pre-run.sh b/lib/laminas/laminas-mail/.laminas-ci/pre-run.sh new file mode 100644 index 0000000000..eed79a3b2d --- /dev/null +++ b/lib/laminas/laminas-mail/.laminas-ci/pre-run.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -euo pipefail + +INTL_EXTENSION=$(php -r 'echo sprintf("php%s.%s-intl",PHP_MAJOR_VERSION,PHP_MINOR_VERSION);') + +echo -e "Removing $INTL_EXTENSION extension:\n" +sudo apt remove "$INTL_EXTENSION" -y + +echo -e "Cleaning up:\n" +sudo apt autoclean && sudo apt autoremove + +echo -e "Installed extensions:\n" +/usr/local/bin/php-extensions-with-version.php \ No newline at end of file diff --git a/lib/laminas/laminas-mail/COPYRIGHT.md b/lib/laminas/laminas-mail/COPYRIGHT.md new file mode 100644 index 0000000000..0a8cccc06b --- /dev/null +++ b/lib/laminas/laminas-mail/COPYRIGHT.md @@ -0,0 +1 @@ +Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. (https://getlaminas.org/) diff --git a/lib/laminas/laminas-mail/LICENSE.md b/lib/laminas/laminas-mail/LICENSE.md new file mode 100644 index 0000000000..10b40f1423 --- /dev/null +++ b/lib/laminas/laminas-mail/LICENSE.md @@ -0,0 +1,26 @@ +Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +- Neither the name of Laminas Foundation nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/laminas/laminas-mail/README.md b/lib/laminas/laminas-mail/README.md new file mode 100644 index 0000000000..16e57f8351 --- /dev/null +++ b/lib/laminas/laminas-mail/README.md @@ -0,0 +1,12 @@ +# laminas-mail + +[![Build Status](https://github.com/laminas/laminas-mail/workflows/Continuous%20Integration/badge.svg)](https://github.com/laminas/laminas-mail/actions?query=workflow%3A"Continuous+Integration") + +`Laminas\Mail` provides generalized functionality to compose and send both text and +MIME-compliant multipart email messages. Mail can be sent with `Laminas\Mail` via +the `Mail\Transport\Sendmail`, `Mail\Transport\Smtp` or the `Mail\Transport\File` +transport. Of course, you can also implement your own transport by implementing +the `Mail\Transport\TransportInterface`. + +- File issues at https://github.com/laminas/laminas-mail/issues +- Documentation is at https://docs.laminas.dev/laminas-mail/ diff --git a/lib/laminas/laminas-mail/composer.json b/lib/laminas/laminas-mail/composer.json new file mode 100644 index 0000000000..96074d2345 --- /dev/null +++ b/lib/laminas/laminas-mail/composer.json @@ -0,0 +1,83 @@ +{ + "name": "laminas/laminas-mail", + "description": "Provides generalized functionality to compose and send both text and MIME-compliant multipart e-mail messages", + "keywords": [ + "laminas", + "mail" + ], + "homepage": "https://laminas.dev", + "license": "BSD-3-Clause", + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "ext-iconv": "*", + "laminas/laminas-loader": "^2.8", + "laminas/laminas-mime": "^2.9.1", + "laminas/laminas-stdlib": "^3.6", + "laminas/laminas-validator": "^2.15", + "symfony/polyfill-mbstring": "^1.12.0", + "webmozart/assert": "^1.10", + "symfony/polyfill-intl-idn": "^1.24.0" + }, + "conflict": { + "zendframework/zend-mail": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-crypt": "^2.6 || ^3.4", + "laminas/laminas-db": "^2.13.3", + "laminas/laminas-servicemanager": "^3.7", + "phpunit/phpunit": "^9.5.5", + "psalm/plugin-phpunit": "^0.15.1", + "symfony/process": "^5.3.7", + "vimeo/psalm": "^4.7" + }, + "suggest": { + "laminas/laminas-crypt": "Crammd5 support in SMTP Auth", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" + }, + "config": { + "sort-packages": true, + "allow-plugins": { + "composer/package-versions-deprecated": true + }, + "platform": { + "php": "7.3.99" + } + }, + "extra": { + "laminas": { + "component": "Laminas\\Mail", + "config-provider": "Laminas\\Mail\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Mail\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "LaminasTest\\Mail\\": "test/" + } + }, + "scripts": { + "check": [ + "@cs-check", + "@static-analysis", + "@test" + ], + "cs-check": "phpcs", + "cs-fix": "phpcbf", + "static-analysis": "psalm --shepherd --stats", + "test": "phpunit --colors=always", + "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" + }, + "support": { + "issues": "https://github.com/laminas/laminas-mail/issues", + "forum": "https://discourse.laminas.dev", + "chat": "https://laminas.dev/chat", + "source": "https://github.com/laminas/laminas-mail", + "docs": "https://docs.laminas.dev/laminas-mail/", + "rss": "https://github.com/laminas/laminas-mail/releases.atom" + } +} diff --git a/lib/laminas/laminas-mail/composer.lock b/lib/laminas/laminas-mail/composer.lock new file mode 100644 index 0000000000..b6263984b7 --- /dev/null +++ b/lib/laminas/laminas-mail/composer.lock @@ -0,0 +1,4882 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "c4e2858fd3753937b8ef20cfc5096601", + "packages": [ + { + "name": "container-interop/container-interop", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/container-interop/container-interop.git", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "shasum": "" + }, + "require": { + "psr/container": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "homepage": "https://github.com/container-interop/container-interop", + "support": { + "issues": "https://github.com/container-interop/container-interop/issues", + "source": "https://github.com/container-interop/container-interop/tree/master" + }, + "abandoned": "psr/container", + "time": "2017-02-14T19:40:03+00:00" + }, + { + "name": "laminas/laminas-loader", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-loader.git", + "reference": "d0589ec9dd48365fd95ad10d1c906efd7711c16b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-loader/zipball/d0589ec9dd48365fd95ad10d1c906efd7711c16b", + "reference": "d0589ec9dd48365fd95ad10d1c906efd7711c16b", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-loader": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.2.1", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Loader\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Autoloading and plugin loading strategies", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "loader" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-loader/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-loader/issues", + "rss": "https://github.com/laminas/laminas-loader/releases.atom", + "source": "https://github.com/laminas/laminas-loader" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-02T18:30:53+00:00" + }, + { + "name": "laminas/laminas-mime", + "version": "2.9.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-mime.git", + "reference": "72d21a1b4bb7086d4a4d7058c0abca180b209184" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-mime/zipball/72d21a1b4bb7086d4a4d7058c0abca180b209184", + "reference": "72d21a1b4bb7086d4a4d7058c0abca180b209184", + "shasum": "" + }, + "require": { + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-mime": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.2.1", + "laminas/laminas-mail": "^2.12", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "laminas/laminas-mail": "Laminas\\Mail component" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Mime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Create and parse MIME messages and parts", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "mime" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-mime/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-mime/issues", + "rss": "https://github.com/laminas/laminas-mime/releases.atom", + "source": "https://github.com/laminas/laminas-mime" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-20T21:19:24+00:00" + }, + { + "name": "laminas/laminas-stdlib", + "version": "3.7.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-stdlib.git", + "reference": "cba75fad2053bb5dc8d3e7f5e62b61d75eecfaf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/cba75fad2053bb5dc8d3e7f5e62b61d75eecfaf1", + "reference": "cba75fad2053bb5dc8d3e7f5e62b61d75eecfaf1", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-stdlib": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.3.0", + "phpbench/phpbench": "^1.0", + "phpunit/phpunit": "^9.3.7", + "psalm/plugin-phpunit": "^0.16.0", + "vimeo/psalm": "^4.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Stdlib\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "SPL extensions, array utilities, error handlers, and more", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "stdlib" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-stdlib/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-stdlib/issues", + "rss": "https://github.com/laminas/laminas-stdlib/releases.atom", + "source": "https://github.com/laminas/laminas-stdlib" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-01-10T11:18:55+00:00" + }, + { + "name": "laminas/laminas-validator", + "version": "2.15.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-validator.git", + "reference": "fbd87f30c0a27aaeeee8adb2f934c14fb6046c80" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/fbd87f30c0a27aaeeee8adb2f934c14fb6046c80", + "reference": "fbd87f30c0a27aaeeee8adb2f934c14fb6046c80", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.1", + "laminas/laminas-stdlib": "^3.6", + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-validator": "*" + }, + "require-dev": { + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~2.2.1", + "laminas/laminas-db": "^2.7", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-http": "^2.14.2", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-math": "^2.6", + "laminas/laminas-servicemanager": "^2.7.11 || ^3.0.3", + "laminas/laminas-session": "^2.8", + "laminas/laminas-uri": "^2.7", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5.5", + "psalm/plugin-phpunit": "^0.15.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "vimeo/psalm": "^4.3" + }, + "suggest": { + "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator", + "laminas/laminas-filter": "Laminas\\Filter component, required by the Digits validator", + "laminas/laminas-i18n": "Laminas\\I18n component to allow translation of validation error messages", + "laminas/laminas-i18n-resources": "Translations of validator messages", + "laminas/laminas-math": "Laminas\\Math component, required by the Csrf validator", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "laminas/laminas-session": "Laminas\\Session component, ^2.8; required by the Csrf validator", + "laminas/laminas-uri": "Laminas\\Uri component, required by the Uri and Sitemap\\Loc validators", + "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators" + }, + "type": "library", + "extra": { + "laminas": { + "component": "Laminas\\Validator", + "config-provider": "Laminas\\Validator\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Validator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "validator" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-validator/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-validator/issues", + "rss": "https://github.com/laminas/laminas-validator/releases.atom", + "source": "https://github.com/laminas/laminas-validator" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-12-02T14:23:06+00:00" + }, + { + "name": "psr/container", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.1" + }, + "time": "2021-03-05T17:36:06+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.24.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "30885182c981ab175d4d034db0f6f469898070ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", + "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.24.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-10-20T20:35:02+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.24.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "749045c69efb97c70d25d7463abba812e91f3a44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/749045c69efb97c70d25d7463abba812e91f3a44", + "reference": "749045c69efb97c70d25d7463abba812e91f3a44", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.24.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-09-14T14:02:44+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.24.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.24.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.24.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", + "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.24.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-30T18:21:41+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.24.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.24.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T09:17:38+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.10.0" + }, + "time": "2021-03-09T10:59:23+00:00" + } + ], + "packages-dev": [ + { + "name": "amphp/amp", + "version": "v2.6.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/amp.git", + "reference": "c5fc66a78ee38d7ac9195a37bacaf940eb3f65ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/amp/zipball/c5fc66a78ee38d7ac9195a37bacaf940eb3f65ae", + "reference": "c5fc66a78ee38d7ac9195a37bacaf940eb3f65ae", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^7 | ^8 | ^9", + "psalm/phar": "^3.11@dev", + "react/promise": "^2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "files": [ + "lib/functions.php", + "lib/Internal/functions.php" + ], + "psr-4": { + "Amp\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "http://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v2.6.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2021-09-23T18:43:08+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v1.8.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", + "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", + "shasum": "" + }, + "require": { + "amphp/amp": "^2", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.4", + "friendsofphp/php-cs-fixer": "^2.3", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6 || ^7 || ^8", + "psalm/phar": "^3.11.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "lib/functions.php" + ], + "psr-4": { + "Amp\\ByteStream\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "http://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2021-03-30T17:13:30+00:00" + }, + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99.4", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "b174585d1fe49ceed21928a945138948cb394600" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b174585d1fe49ceed21928a945138948cb394600", + "reference": "b174585d1fe49ceed21928a945138948cb394600", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-09-13T08:41:34+00:00" + }, + { + "name": "composer/pcre", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "3d322d715c43a1ac36c7fe215fa59336265500f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/3d322d715c43a1ac36c7fe215fa59336265500f2", + "reference": "3d322d715c43a1ac36c7fe215fa59336265500f2", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/1.0.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-12-06T15:17:27+00:00" + }, + { + "name": "composer/semver", + "version": "3.2.7", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "deac27056b57e46faf136fae7b449eeaa71661ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/deac27056b57e46faf136fae7b449eeaa71661ee", + "reference": "deac27056b57e46faf136fae7b449eeaa71661ee", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.54", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.2.7" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-01-04T09:57:54+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "12f1b79476638a5615ed00ea6adbb269cec96fd8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/12f1b79476638a5615ed00ea6adbb269cec96fd8", + "reference": "12f1b79476638a5615ed00ea6adbb269cec96fd8", + "shasum": "" + }, + "require": { + "composer/pcre": "^1", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-01-04T18:29:42+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-11-10T18:47:58+00:00" + }, + { + "name": "felixfbecker/advanced-json-rpc", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "php": "^7.1 || ^8.0", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "A more advanced JSONRPC implementation", + "support": { + "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", + "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" + }, + "time": "2021-06-11T22:34:44+00:00" + }, + { + "name": "felixfbecker/language-server-protocol", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "9d846d1f5cf101deee7a61c8ba7caa0a975cd730" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/9d846d1f5cf101deee7a61c8ba7caa0a975cd730", + "reference": "9d846d1f5cf101deee7a61c8ba7caa0a975cd730", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1", + "vimeo/psalm": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "PHP classes for the Language Server Protocol", + "keywords": [ + "language", + "microsoft", + "php", + "server" + ], + "support": { + "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/1.5.1" + }, + "time": "2021-02-22T14:02:09+00:00" + }, + { + "name": "laminas/laminas-coding-standard", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-coding-standard.git", + "reference": "08880ce2fbfe62d471cd3cb766a91da630b32539" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-coding-standard/zipball/08880ce2fbfe62d471cd3cb766a91da630b32539", + "reference": "08880ce2fbfe62d471cd3cb766a91da630b32539", + "shasum": "" + }, + "require": { + "laminas/laminas-zendframework-bridge": "^1.0", + "squizlabs/php_codesniffer": "^2.7" + }, + "replace": { + "zendframework/zend-coding-standard": "self.version" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Laminas coding standard", + "homepage": "https://laminas.dev", + "keywords": [ + "Coding Standard", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-coding-standard/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-coding-standard/issues", + "rss": "https://github.com/laminas/laminas-coding-standard/releases.atom", + "source": "https://github.com/laminas/laminas-coding-standard" + }, + "time": "2019-12-31T16:28:26+00:00" + }, + { + "name": "laminas/laminas-crypt", + "version": "3.6.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-crypt.git", + "reference": "ad2c29c289a4bc837b37a7650f5178edda0fc548" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-crypt/zipball/ad2c29c289a4bc837b37a7650f5178edda0fc548", + "reference": "ad2c29c289a4bc837b37a7650f5178edda0fc548", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.2", + "ext-mbstring": "*", + "laminas/laminas-math": "^3.4", + "laminas/laminas-stdlib": "^3.6", + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-crypt": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-openssl": "Required for most features of Laminas\\Crypt" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Crypt\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Strong cryptography tools and password hashing", + "homepage": "https://laminas.dev", + "keywords": [ + "crypt", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-crypt/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-crypt/issues", + "rss": "https://github.com/laminas/laminas-crypt/releases.atom", + "source": "https://github.com/laminas/laminas-crypt" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-12-06T01:25:27+00:00" + }, + { + "name": "laminas/laminas-db", + "version": "2.13.4", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-db.git", + "reference": "cdabb4bfa669c2c0edb0cb4e014c15b41afd3fb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-db/zipball/cdabb4bfa669c2c0edb0cb4e014c15b41afd3fb1", + "reference": "cdabb4bfa669c2c0edb0cb4e014c15b41afd3fb1", + "shasum": "" + }, + "require": { + "laminas/laminas-stdlib": "^3.6", + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-db": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.2.1", + "laminas/laminas-eventmanager": "^3.4", + "laminas/laminas-hydrator": "^3.2 || ^4.3", + "laminas/laminas-servicemanager": "^3.7", + "phpunit/phpunit": "^9.5.5" + }, + "suggest": { + "laminas/laminas-eventmanager": "Laminas\\EventManager component", + "laminas/laminas-hydrator": "(^3.2 || ^4.3) Laminas\\Hydrator component for using HydratingResultSets", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component" + }, + "type": "library", + "extra": { + "laminas": { + "component": "Laminas\\Db", + "config-provider": "Laminas\\Db\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Db\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Database abstraction layer, SQL abstraction, result set abstraction, and RowDataGateway and TableDataGateway implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "db", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-db/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-db/issues", + "rss": "https://github.com/laminas/laminas-db/releases.atom", + "source": "https://github.com/laminas/laminas-db" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-21T18:59:44+00:00" + }, + { + "name": "laminas/laminas-math", + "version": "3.5.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-math.git", + "reference": "146d8187ab247ae152e811a6704a953d43537381" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-math/zipball/146d8187ab247ae152e811a6704a953d43537381", + "reference": "146d8187ab247ae152e811a6704a953d43537381", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-math": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpunit/phpunit": "^9.5.5" + }, + "suggest": { + "ext-bcmath": "If using the bcmath functionality", + "ext-gmp": "If using the gmp functionality" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev", + "dev-develop": "3.3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Create cryptographically secure pseudo-random numbers, and manage big integers", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "math" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-math/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-math/issues", + "rss": "https://github.com/laminas/laminas-math/releases.atom", + "source": "https://github.com/laminas/laminas-math" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-12-06T02:02:07+00:00" + }, + { + "name": "laminas/laminas-servicemanager", + "version": "3.7.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-servicemanager.git", + "reference": "2b0aee477fdbd3191af7c302b93dbc5fda0626f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/2b0aee477fdbd3191af7c302b93dbc5fda0626f4", + "reference": "2b0aee477fdbd3191af7c302b93dbc5fda0626f4", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.2", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^7.3 || ~8.0.0", + "psr/container": "^1.0" + }, + "conflict": { + "laminas/laminas-code": "<3.3.1", + "zendframework/zend-code": "<3.3.1" + }, + "provide": { + "container-interop/container-interop-implementation": "^1.2", + "psr/container-implementation": "^1.0" + }, + "replace": { + "zendframework/zend-servicemanager": "^3.4.0" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.0", + "laminas/laminas-coding-standard": "~2.2.0", + "laminas/laminas-container-config-test": "^0.3", + "laminas/laminas-dependency-plugin": "^2.1.2", + "mikey179/vfsstream": "^1.6.8", + "ocramius/proxy-manager": "^2.2.3", + "phpbench/phpbench": "^1.0.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.4", + "psalm/plugin-phpunit": "^0.16.1", + "vimeo/psalm": "^4.8" + }, + "suggest": { + "ocramius/proxy-manager": "ProxyManager ^2.1.1 to handle lazy initialization of services" + }, + "bin": [ + "bin/generate-deps-for-config-factory", + "bin/generate-factory-for-class" + ], + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\ServiceManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Factory-Driven Dependency Injection Container", + "homepage": "https://laminas.dev", + "keywords": [ + "PSR-11", + "dependency-injection", + "di", + "dic", + "laminas", + "service-manager", + "servicemanager" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-servicemanager/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-servicemanager/issues", + "rss": "https://github.com/laminas/laminas-servicemanager/releases.atom", + "source": "https://github.com/laminas/laminas-servicemanager" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-07-24T19:33:07+00:00" + }, + { + "name": "laminas/laminas-zendframework-bridge", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-zendframework-bridge.git", + "reference": "88bf037259869891afce6504cacc4f8a07b24d0f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/88bf037259869891afce6504cacc4f8a07b24d0f", + "reference": "88bf037259869891afce6504cacc4f8a07b24d0f", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "psalm/plugin-phpunit": "^0.15.1", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.6" + }, + "type": "library", + "extra": { + "laminas": { + "module": "Laminas\\ZendFrameworkBridge" + } + }, + "autoload": { + "files": [ + "src/autoload.php" + ], + "psr-4": { + "Laminas\\ZendFrameworkBridge\\": "src//" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Alias legacy ZF class names to Laminas Project equivalents.", + "keywords": [ + "ZendFramework", + "autoloading", + "laminas", + "zf" + ], + "support": { + "forum": "https://discourse.laminas.dev/", + "issues": "https://github.com/laminas/laminas-zendframework-bridge/issues", + "rss": "https://github.com/laminas/laminas-zendframework-bridge/releases.atom", + "source": "https://github.com/laminas/laminas-zendframework-bridge" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-12-21T14:34:37+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.10.2", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-11-13T09:40:50+00:00" + }, + { + "name": "netresearch/jsonmapper", + "version": "v4.0.0", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", + "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v4.0.0" + }, + "time": "2020-12-01T19:48:11+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.13.2", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "210577fe3cf7badcc5814d99455df46564f3c077" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077", + "reference": "210577fe3cf7badcc5814d99455df46564f3c077", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2" + }, + "time": "2021-11-30T19:35:32+00:00" + }, + { + "name": "openlss/lib-array2xml", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/nullivex/lib-array2xml.git", + "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nullivex/lib-array2xml/zipball/a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", + "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "autoload": { + "psr-0": { + "LSS": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Bryan Tong", + "email": "bryan@nullivex.com", + "homepage": "https://www.nullivex.com" + }, + { + "name": "Tony Butler", + "email": "spudz76@gmail.com", + "homepage": "https://www.nullivex.com" + } + ], + "description": "Array2XML conversion library credit to lalit.org", + "homepage": "https://www.nullivex.com", + "keywords": [ + "array", + "array conversion", + "xml", + "xml conversion" + ], + "support": { + "issues": "https://github.com/nullivex/lib-array2xml/issues", + "source": "https://github.com/nullivex/lib-array2xml/tree/master" + }, + "time": "2019-03-29T20:06:56+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "bae7c545bef187884426f042434e561ab1ddb182" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", + "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.1.0" + }, + "time": "2021-02-23T14:00:09+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + }, + "time": "2021-10-19T17:43:47+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/93ebd0014cab80c4ea9f5e297ea48672f1b87706", + "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.0" + }, + "time": "2022-01-04T19:58:01+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.2", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0 || ^7.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" + }, + "time": "2021-12-08T12:19:24+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "d5850aaf931743067f4bfc1ae4cbd06468400687" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d5850aaf931743067f4bfc1ae4cbd06468400687", + "reference": "d5850aaf931743067f4bfc1ae4cbd06468400687", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.13.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.10" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-05T09:12:13+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.5.11", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "2406855036db1102126125537adb1406f7242fdd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2406855036db1102126125537adb1406f7242fdd", + "reference": "2406855036db1102126125537adb1406f7242fdd", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpspec/prophecy": "^1.12.1", + "phpunit/php-code-coverage": "^9.2.7", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.5", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.3", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^2.3.4", + "sebastian/version": "^3.0.2" + }, + "require-dev": { + "ext-pdo": "*", + "phpspec/prophecy-phpunit": "^2.0.1" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.11" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-25T07:07:57+00:00" + }, + { + "name": "psalm/plugin-phpunit", + "version": "0.15.2", + "source": { + "type": "git", + "url": "https://github.com/psalm/psalm-plugin-phpunit.git", + "reference": "31d15bbc0169a3c454e495e03fd8a6ccb663661b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/psalm/psalm-plugin-phpunit/zipball/31d15bbc0169a3c454e495e03fd8a6ccb663661b", + "reference": "31d15bbc0169a3c454e495e03fd8a6ccb663661b", + "shasum": "" + }, + "require": { + "composer/package-versions-deprecated": "^1.10", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "ext-simplexml": "*", + "php": "^7.1 || ^8.0", + "vimeo/psalm": "dev-master || dev-4.x || ^4.0" + }, + "conflict": { + "phpunit/phpunit": "<7.5" + }, + "require-dev": { + "codeception/codeception": "^4.0.3", + "php": "^7.3 || ^8.0", + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.3.1", + "weirdan/codeception-psalm-module": "^0.11.0", + "weirdan/prophecy-shim": "^1.0 || ^2.0" + }, + "type": "psalm-plugin", + "extra": { + "psalm": { + "pluginClass": "Psalm\\PhpUnitPlugin\\Plugin" + } + }, + "autoload": { + "psr-4": { + "Psalm\\PhpUnitPlugin\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Brown", + "email": "github@muglug.com" + } + ], + "description": "Psalm plugin for PHPUnit", + "support": { + "issues": "https://github.com/psalm/psalm-plugin-phpunit/issues", + "source": "https://github.com/psalm/psalm-plugin-phpunit/tree/0.15.2" + }, + "time": "2021-05-29T19:11:38+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:49:45+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:10:38+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:52:38+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-11-11T14:18:36+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-11T13:31:12+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:17:30+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "2.3.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-15T12:49:02+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.9.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2018-11-07T22:31:41+00:00" + }, + { + "name": "symfony/console", + "version": "v5.4.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "a2c6b7ced2eb7799a35375fb9022519282b5405e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/a2c6b7ced2eb7799a35375fb9022519282b5405e", + "reference": "a2c6b7ced2eb7799a35375fb9022519282b5405e", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.4.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-12-20T16:11:12+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "reference": "6f981ee24cf69ee7ce9736146d1c57c2780598a8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-07-12T14:48:14+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.24.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "81b86b50cf841a64252b439e738e97f4a34e2783" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", + "reference": "81b86b50cf841a64252b439e738e97f4a34e2783", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.24.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-23T21:10:46+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.24.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/cc5db0e22b3cb4111010e48785a97f670b350ca5", + "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.24.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-06-05T21:20:04+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.24.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "57b712b08eddb97c762a8caa32c84e037892d2e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/57b712b08eddb97c762a8caa32c84e037892d2e9", + "reference": "57b712b08eddb97c762a8caa32c84e037892d2e9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.24.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-09-13T13:58:33+00:00" + }, + { + "name": "symfony/process", + "version": "v5.4.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "2b3ba8722c4aaf3e88011be5e7f48710088fb5e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/2b3ba8722c4aaf3e88011be5e7f48710088fb5e4", + "reference": "2b3ba8722c4aaf3e88011be5e7f48710088fb5e4", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.4.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-12-27T21:01:00+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "reference": "1ab11b933cd6bc5464b08e81e2c5b07dec58b0fc", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-11-04T16:48:04+00:00" + }, + { + "name": "symfony/string", + "version": "v5.4.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "e6a5d5ecf6589c5247d18e0e74e30b11dfd51a3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/e6a5d5ecf6589c5247d18e0e74e30b11dfd51a3d", + "reference": "e6a5d5ecf6589c5247d18e0e74e30b11dfd51a3d", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "files": [ + "Resources/functions.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-12-16T21:52:00+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + }, + { + "name": "vimeo/psalm", + "version": "4.18.1", + "source": { + "type": "git", + "url": "https://github.com/vimeo/psalm.git", + "reference": "dda05fa913f4dc6eb3386f2f7ce5a45d37a71bcb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/dda05fa913f4dc6eb3386f2f7ce5a45d37a71bcb", + "reference": "dda05fa913f4dc6eb3386f2f7ce5a45d37a71bcb", + "shasum": "" + }, + "require": { + "amphp/amp": "^2.4.2", + "amphp/byte-stream": "^1.5", + "composer/package-versions-deprecated": "^1.8.0", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^1.1 || ^2.0 || ^3.0", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "ext-ctype": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "felixfbecker/advanced-json-rpc": "^3.0.3", + "felixfbecker/language-server-protocol": "^1.5", + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "nikic/php-parser": "^4.13", + "openlss/lib-array2xml": "^1.0", + "php": "^7.1|^8", + "sebastian/diff": "^3.0 || ^4.0", + "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0 || ^6.0", + "webmozart/path-util": "^2.3" + }, + "provide": { + "psalm/psalm": "self.version" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2", + "brianium/paratest": "^4.0||^6.0", + "ext-curl": "*", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpdocumentor/reflection-docblock": "^5", + "phpmyadmin/sql-parser": "5.1.0||dev-master", + "phpspec/prophecy": ">=1.9.0", + "phpunit/phpunit": "^9.0", + "psalm/plugin-phpunit": "^0.16", + "slevomat/coding-standard": "^7.0", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.3 || ^5.0 || ^6.0", + "weirdan/prophecy-shim": "^1.0 || ^2.0" + }, + "suggest": { + "ext-curl": "In order to send data to shepherd", + "ext-igbinary": "^2.0.5 is required, used to serialize caching data" + }, + "bin": [ + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalter" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev", + "dev-3.x": "3.x-dev", + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php", + "src/spl_object_id.php" + ], + "psr-4": { + "Psalm\\": "src/Psalm/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Brown" + } + ], + "description": "A static analysis tool for finding errors in PHP applications", + "keywords": [ + "code", + "inspection", + "php" + ], + "support": { + "issues": "https://github.com/vimeo/psalm/issues", + "source": "https://github.com/vimeo/psalm/tree/4.18.1" + }, + "time": "2022-01-08T21:21:26+00:00" + }, + { + "name": "webmozart/path-util", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/path-util.git", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "webmozart/assert": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\PathUtil\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", + "support": { + "issues": "https://github.com/webmozart/path-util/issues", + "source": "https://github.com/webmozart/path-util/tree/2.3.0" + }, + "abandoned": "symfony/filesystem", + "time": "2015-12-17T08:42:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "ext-iconv": "*" + }, + "platform-dev": [], + "platform-overrides": { + "php": "7.3.99" + }, + "plugin-api-version": "2.2.0" +} diff --git a/lib/laminas/laminas-mail/src/Address.php b/lib/laminas/laminas-mail/src/Address.php new file mode 100644 index 0000000000..7b2956f2e5 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Address.php @@ -0,0 +1,162 @@ +.*)<(?P[^>]+)>|(?P.+))$/', $address, $matches)) { + throw new Exception\InvalidArgumentException('Invalid address format'); + } + + $name = null; + if (isset($matches['name'])) { + $name = trim($matches['name']); + } + if (empty($name)) { + $name = null; + } + + if (isset($matches['namedEmail'])) { + $email = $matches['namedEmail']; + } + if (isset($matches['email'])) { + $email = $matches['email']; + } + $email = trim($email); + //trim single quotes, because outlook does add single quotes to emails sometimes which is technically not valid + $email = trim($email, '\''); + + return new static($email, $name, $comment); + } + + /** + * Constructor + * + * @param string $email + * @param null|string $name + * @param null|string $comment + * @throws Exception\InvalidArgumentException + */ + public function __construct($email, $name = null, $comment = null) + { + $emailAddressValidator = new EmailAddressValidator(Hostname::ALLOW_DNS | Hostname::ALLOW_LOCAL); + if (! is_string($email) || empty($email)) { + throw new Exception\InvalidArgumentException('Email must be a valid email address'); + } + + if (preg_match("/[\r\n]/", $email)) { + throw new Exception\InvalidArgumentException('CRLF injection detected'); + } + + if (! $emailAddressValidator->isValid($email)) { + $invalidMessages = $emailAddressValidator->getMessages(); + throw new Exception\InvalidArgumentException(array_shift($invalidMessages)); + } + + if (null !== $name) { + if (! is_string($name)) { + throw new Exception\InvalidArgumentException('Name must be a string'); + } + + if (preg_match("/[\r\n]/", $name)) { + throw new Exception\InvalidArgumentException('CRLF injection detected'); + } + + $this->name = $name; + } + + $this->email = $email; + + if (null !== $comment) { + $this->comment = $comment; + } + } + + /** + * Retrieve email + * + * @return string + */ + public function getEmail() + { + return $this->email; + } + + /** + * Retrieve name, if any + * + * @return null|string + */ + public function getName() + { + return $this->name; + } + + /** + * Retrieve comment, if any + * + * @return null|string + */ + public function getComment() + { + return $this->comment; + } + + /** + * String representation of address + * + * @return string + */ + public function toString() + { + $string = sprintf('<%s>', $this->getEmail()); + $name = $this->constructName(); + if (null === $name) { + return $string; + } + + return sprintf('%s %s', $name, $string); + } + + /** + * Constructs the name string + * + * If a comment is present, appends the comment (commented using parens) to + * the name before returning it; otherwise, returns just the name. + * + * @return null|string + */ + private function constructName() + { + $name = $this->getName(); + $comment = $this->getComment(); + + if ($comment === null || $comment === '') { + return $name; + } + + $string = sprintf('%s (%s)', $name, $comment); + return trim($string); + } +} diff --git a/lib/laminas/laminas-mail/src/Address/AddressInterface.php b/lib/laminas/laminas-mail/src/Address/AddressInterface.php new file mode 100644 index 0000000000..c75c6f75b4 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Address/AddressInterface.php @@ -0,0 +1,27 @@ +createAddress($emailOrAddress, $name); + } + + if (! $emailOrAddress instanceof Address\AddressInterface) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects an email address or %s\Address object as its first argument; received "%s"', + __METHOD__, + __NAMESPACE__, + (is_object($emailOrAddress) ? get_class($emailOrAddress) : gettype($emailOrAddress)) + )); + } + + $email = strtolower($emailOrAddress->getEmail()); + if ($this->has($email)) { + return $this; + } + + $this->addresses[$email] = $emailOrAddress; + return $this; + } + + /** + * Add many addresses at once + * + * If an email key is provided, it will be used as the email, and the value + * as the name. Otherwise, the value is passed as the sole argument to add(), + * and, as such, can be either email strings or Address\AddressInterface objects. + * + * @param array $addresses + * @throws Exception\RuntimeException + * @return AddressList + */ + public function addMany(array $addresses) + { + foreach ($addresses as $key => $value) { + if (is_int($key) || is_numeric($key)) { + $this->add($value); + continue; + } + + if (! is_string($key)) { + throw new Exception\RuntimeException(sprintf( + 'Invalid key type in provided addresses array ("%s")', + (is_object($key) ? get_class($key) : var_export($key, 1)) + )); + } + + $this->add($key, $value); + } + return $this; + } + + /** + * Add an address to the list from any valid string format, such as + * - "Laminas Dev" + * - dev@laminas.com + * + * @param string $address + * @param null|string $comment Comment associated with the address, if any. + * @throws Exception\InvalidArgumentException + * @return AddressList + */ + public function addFromString($address, $comment = null) + { + $this->add(Address::fromString($address, $comment)); + return $this; + } + + /** + * Merge another address list into this one + * + * @param AddressList $addressList + * @return AddressList + */ + public function merge(self $addressList) + { + foreach ($addressList as $address) { + $this->add($address); + } + return $this; + } + + /** + * Does the email exist in this list? + * + * @param string $email + * @return bool + */ + public function has($email) + { + $email = strtolower($email); + return isset($this->addresses[$email]); + } + + /** + * Get an address by email + * + * @param string $email + * @return bool|Address\AddressInterface + */ + public function get($email) + { + $email = strtolower($email); + if (! isset($this->addresses[$email])) { + return false; + } + + return $this->addresses[$email]; + } + + /** + * Delete an address from the list + * + * @param string $email + * @return bool + */ + public function delete($email) + { + $email = strtolower($email); + if (! isset($this->addresses[$email])) { + return false; + } + + unset($this->addresses[$email]); + return true; + } + + /** + * Return count of addresses + * + * @return int + */ + #[ReturnTypeWillChange] + public function count() + { + return count($this->addresses); + } + + /** + * Rewind iterator + * + * @return mixed the value of the first addresses element, or false if the addresses is + * empty. + * @see addresses + */ + #[ReturnTypeWillChange] + public function rewind() + { + return reset($this->addresses); + } + + /** + * Return current item in iteration + * + * @return Address + */ + #[ReturnTypeWillChange] + public function current() + { + return current($this->addresses); + } + + /** + * Return key of current item of iteration + * + * @return string + */ + #[ReturnTypeWillChange] + public function key() + { + return key($this->addresses); + } + + /** + * Move to next item + * + * @return mixed the addresses value in the next place that's pointed to by the + * internal array pointer, or false if there are no more elements. + * @see addresses + */ + #[ReturnTypeWillChange] + public function next() + { + return next($this->addresses); + } + + /** + * Is the current item of iteration valid? + * + * @return bool + */ + #[ReturnTypeWillChange] + public function valid() + { + $key = key($this->addresses); + return ($key !== null && $key !== false); + } + + /** + * Create an address object + * + * @param string $email + * @param string|null $name + * @return Address + */ + protected function createAddress($email, $name) + { + return new Address($email, $name); + } +} diff --git a/lib/laminas/laminas-mail/src/ConfigProvider.php b/lib/laminas/laminas-mail/src/ConfigProvider.php new file mode 100644 index 0000000000..d8468a3f6d --- /dev/null +++ b/lib/laminas/laminas-mail/src/ConfigProvider.php @@ -0,0 +1,36 @@ + $this->getDependencyConfig(), + ]; + } + + /** + * Retrieve dependency settings for laminas-mail package. + * + * @return array + */ + public function getDependencyConfig() + { + return [ + // Legacy Zend Framework aliases + 'aliases' => [ + \Zend\Mail\Protocol\SmtpPluginManager::class => Protocol\SmtpPluginManager::class, + ], + 'factories' => [ + Protocol\SmtpPluginManager::class => Protocol\SmtpPluginManagerFactory::class, + ], + ]; + } +} diff --git a/lib/laminas/laminas-mail/src/Exception/BadMethodCallException.php b/lib/laminas/laminas-mail/src/Exception/BadMethodCallException.php new file mode 100644 index 0000000000..4020cc8918 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Exception/BadMethodCallException.php @@ -0,0 +1,11 @@ + 'empty label', + IDNA_ERROR_LABEL_TOO_LONG => 'label too long', + IDNA_ERROR_DOMAIN_NAME_TOO_LONG => 'domain name too long', + IDNA_ERROR_LEADING_HYPHEN => 'leading hyphen', + IDNA_ERROR_TRAILING_HYPHEN => 'trailing hyphen', + IDNA_ERROR_HYPHEN_3_4 => 'consecutive hyphens', + IDNA_ERROR_LEADING_COMBINING_MARK => 'leading combining mark', + IDNA_ERROR_DISALLOWED => 'disallowed', + IDNA_ERROR_PUNYCODE => 'invalid punycode encoding', + IDNA_ERROR_LABEL_HAS_DOT => 'has dot', + IDNA_ERROR_INVALID_ACE_LABEL => 'label not in ASCII encoding', + IDNA_ERROR_BIDI => 'fails bidirectional criteria', + IDNA_ERROR_CONTEXTJ => 'one or more characters fail CONTEXTJ rule', + ]; + + /** + * @var AddressList + */ + protected $addressList; + + /** + * @var string Normalized field name + */ + protected $fieldName; + + /** + * Header encoding + * + * @var string + */ + protected $encoding = 'ASCII'; + + /** + * @var string lower case field name + */ + protected static $type; + + public static function fromString($headerLine) + { + list($fieldName, $fieldValue) = GenericHeader::splitHeaderLine($headerLine); + if (strtolower($fieldName) !== static::$type) { + throw new Exception\InvalidArgumentException(sprintf( + 'Invalid header line for "%s" string', + __CLASS__ + )); + } + + // split value on "," + $fieldValue = str_replace(Headers::FOLDING, ' ', $fieldValue); + $fieldValue = preg_replace('/[^:]+:([^;]*);/', '$1,', $fieldValue); + $values = ListParser::parse($fieldValue); + + $wasEncoded = false; + $addresses = array_map( + function ($value) use (&$wasEncoded) { + $decodedValue = HeaderWrap::mimeDecodeValue($value); + $wasEncoded = $wasEncoded || ($decodedValue !== $value); + + $value = trim($decodedValue); + + $comments = self::getComments($value); + $value = self::stripComments($value); + + $value = preg_replace( + [ + '#(?setEncoding('UTF-8'); + } + + /** @var AddressList $addressList */ + $addressList = $header->getAddressList(); + foreach ($addresses as $address) { + $addressList->add($address); + } + + return $header; + } + + public function getFieldName() + { + return $this->fieldName; + } + + /** + * Safely convert UTF-8 encoded domain name to ASCII + * @param string $domainName the UTF-8 encoded email + * @return string + */ + protected function idnToAscii($domainName): string + { + $ascii = idn_to_ascii($domainName, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $conversionInfo); + if (false !== $ascii) { + return $ascii; + } + + $messages = []; + $errors = (int) $conversionInfo['errors']; + + foreach (self::IDNA_ERROR_MAP as $flag => $message) { + if (($flag & $errors) === $flag) { + $messages[] = $message; + } + } + + throw new RuntimeException(sprintf( + 'Failed encoding domain due to errors: %s', + implode(', ', $messages) + )); + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + $emails = []; + $encoding = $this->getEncoding(); + + foreach ($this->getAddressList() as $address) { + $email = $address->getEmail(); + $name = $address->getName(); + + // quote $name if value requires so + if (! empty($name) && (false !== strpos($name, ',') || false !== strpos($name, ';'))) { + // FIXME: what if name contains double quote? + $name = sprintf('"%s"', $name); + } + + if ($format === HeaderInterface::FORMAT_ENCODED + && 'ASCII' !== $encoding + ) { + if (! empty($name)) { + $name = HeaderWrap::mimeEncodeValue($name, $encoding); + } + + if (preg_match('/^(.+)@([^@]+)$/', $email, $matches)) { + $localPart = $matches[1]; + $hostname = $this->idnToAscii($matches[2]); + $email = sprintf('%s@%s', $localPart, $hostname); + } + } + + if (empty($name)) { + $emails[] = $email; + } else { + $emails[] = sprintf('%s <%s>', $name, $email); + } + } + + // Ensure the values are valid before sending them. + if ($format !== HeaderInterface::FORMAT_RAW) { + foreach ($emails as $email) { + HeaderValue::assertValid($email); + } + } + + return implode(',' . Headers::FOLDING, $emails); + } + + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + public function getEncoding() + { + return $this->encoding; + } + + /** + * Set address list for this header + * + * @param AddressList $addressList + */ + public function setAddressList(AddressList $addressList) + { + $this->addressList = $addressList; + } + + /** + * Get address list managed by this header + * + * @return AddressList + */ + public function getAddressList() + { + if (null === $this->addressList) { + $this->setAddressList(new AddressList()); + } + return $this->addressList; + } + + public function toString() + { + $name = $this->getFieldName(); + $value = $this->getFieldValue(HeaderInterface::FORMAT_ENCODED); + return (empty($value)) ? '' : sprintf('%s: %s', $name, $value); + } + + /** + * Retrieve comments from value, if any. + * + * Supposed to be private, protected as a workaround for PHP bug 68194 + * + * @param string $value + * @return string + */ + protected static function getComments($value) + { + $matches = []; + preg_match_all( + '/\\( + (?P( + \\\\.| + [^\\\\)] + )+) + \\)/x', + $value, + $matches + ); + return isset($matches['comment']) ? implode(', ', $matches['comment']) : ''; + } + + /** + * Strip all comments from value, if any. + * + * Supposed to be private, protected as a workaround for PHP bug 68194 + * + * @param string $value + * @return string + */ + protected static function stripComments($value) + { + return preg_replace( + '/\\( + ( + \\\\.| + [^\\\\)] + )+ + \\)/x', + '', + $value + ); + } +} diff --git a/lib/laminas/laminas-mail/src/Header/Bcc.php b/lib/laminas/laminas-mail/src/Header/Bcc.php new file mode 100644 index 0000000000..0fd3a12d8f --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/Bcc.php @@ -0,0 +1,16 @@ +setDisposition($parts[0]); + + if (isset($parts[1])) { + $values = ListParser::parse(trim($parts[1]), [';', '=']); + $length = count($values); + $continuedValues = []; + + for ($i = 0; $i < $length; $i += 2) { + $value = $values[$i + 1]; + $value = trim($value, "'\" \t\n\r\0\x0B"); + $name = trim($values[$i], "'\" \t\n\r\0\x0B"); + + if (strpos($name, '*')) { + list($name, $count) = explode('*', $name); + // allow optional count: + // Content-Disposition: attachment; filename*=UTF-8''%64%61%61%6D%69%2D%6D%C3%B5%72%76%2E%6A%70%67 + if ($count === "") { + $count = 0; + } + + if (! is_numeric($count)) { + $type = gettype($count); + $value = var_export($count, 1); + throw new Exception\InvalidArgumentException(sprintf( + "Invalid header line for Content-Disposition string". + " - count expected to be numeric, got %s with value %s", + $type, + $value + )); + } + if (! isset($continuedValues[$name])) { + $continuedValues[$name] = []; + } + $continuedValues[$name][$count] = $value; + } else { + $header->setParameter($name, $value); + } + } + + foreach ($continuedValues as $name => $values) { + $value = ''; + for ($i = 0, $iMax = count($values); $i < $iMax; $i++) { + if (! isset($values[$i])) { + throw new Exception\InvalidArgumentException( + 'Invalid header line for Content-Disposition string - incomplete continuation'. + '; HeaderLine: '.$headerLine + ); + } + $value .= $values[$i]; + } + $header->setParameter($name, $value); + } + } + + return $header; + } + + /** + * @inheritDoc + */ + public function getFieldName() + { + return 'Content-Disposition'; + } + + /** + * @inheritDoc + */ + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + $result = $this->disposition; + if (empty($this->parameters)) { + return $result; + } + + foreach ($this->parameters as $attribute => $value) { + $valueIsEncoded = false; + if (HeaderInterface::FORMAT_ENCODED === $format && ! Mime::isPrintable($value)) { + $value = $this->getEncodedValue($value); + $valueIsEncoded = true; + } + + $line = sprintf('%s="%s"', $attribute, $value); + + if (strlen($line) < self::MAX_PARAMETER_LENGTH) { + $lines = explode(Headers::FOLDING, $result); + + if (count($lines) === 1) { + $existingLineLength = strlen('Content-Disposition: ' . $result); + } else { + $existingLineLength = 1 + strlen($lines[count($lines) - 1]); + } + + if ((2 + $existingLineLength + strlen($line)) <= self::MAX_PARAMETER_LENGTH) { + $result .= '; ' . $line; + } else { + $result .= ';' . Headers::FOLDING . $line; + } + } else { + // Use 'continuation' per RFC 2231 + if ($valueIsEncoded) { + $value = HeaderWrap::mimeDecodeValue($value); + } + + $i = 0; + $fullLength = mb_strlen($value, 'UTF-8'); + while ($fullLength > 0) { + $attributePart = $attribute . '*' . $i++ . '="'; + $attLen = mb_strlen($attributePart, 'UTF-8'); + + $subPos = 1; + $valuePart = ''; + while ($subPos <= $fullLength) { + $sub = mb_substr($value, 0, $subPos, 'UTF-8'); + if ($valueIsEncoded) { + $sub = $this->getEncodedValue($sub); + } + if ($attLen + mb_strlen($sub, 'UTF-8') >= self::MAX_PARAMETER_LENGTH) { + $subPos--; + break; + } + $subPos++; + $valuePart = $sub; + } + + $value = mb_substr($value, $subPos, null, 'UTF-8'); + $fullLength = mb_strlen($value, 'UTF-8'); + $result .= ';' . Headers::FOLDING . $attributePart . $valuePart . '"'; + } + } + } + + return $result; + } + + /** + * @param string $value + * @return string + */ + protected function getEncodedValue($value) + { + $configuredEncoding = $this->encoding; + $this->encoding = 'UTF-8'; + $value = HeaderWrap::wrap($value, $this); + $this->encoding = $configuredEncoding; + return $value; + } + + /** + * @inheritDoc + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + /** + * @inheritDoc + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * @inheritDoc + */ + public function toString() + { + return 'Content-Disposition: ' . $this->getFieldValue(HeaderInterface::FORMAT_ENCODED); + } + + /** + * Set the content disposition + * Expected values include 'inline', 'attachment' + * + * @param string $disposition + * @return ContentDisposition + */ + public function setDisposition($disposition) + { + $this->disposition = strtolower($disposition); + return $this; + } + + /** + * Retrieve the content disposition + * + * @return string + */ + public function getDisposition() + { + return $this->disposition; + } + + /** + * Add a parameter pair + * + * @param string $name + * @param string $value + * @return ContentDisposition + */ + public function setParameter($name, $value) + { + $name = strtolower($name); + + if (! HeaderValue::isValid($name)) { + throw new Exception\InvalidArgumentException( + 'Invalid content-disposition parameter name detected' + ); + } + // '5' here is for the quotes & equal sign in `name="value"`, + // and the space & semicolon for line folding + if ((strlen($name) + 5) >= self::MAX_PARAMETER_LENGTH) { + throw new Exception\InvalidArgumentException( + 'Invalid content-disposition parameter name detected (too long)' + ); + } + + $this->parameters[$name] = $value; + return $this; + } + + /** + * Get all parameters + * + * @return array + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Get a parameter by name + * + * @param string $name + * @return null|string + */ + public function getParameter($name) + { + $name = strtolower($name); + if (isset($this->parameters[$name])) { + return $this->parameters[$name]; + } + return null; + } + + /** + * Remove a named parameter + * + * @param string $name + * @return bool + */ + public function removeParameter($name) + { + $name = strtolower($name); + if (isset($this->parameters[$name])) { + unset($this->parameters[$name]); + return true; + } + return false; + } +} diff --git a/lib/laminas/laminas-mail/src/Header/ContentTransferEncoding.php b/lib/laminas/laminas-mail/src/Header/ContentTransferEncoding.php new file mode 100644 index 0000000000..3c80e92742 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/ContentTransferEncoding.php @@ -0,0 +1,108 @@ +setTransferEncoding($value); + + return $header; + } + + public function getFieldName() + { + return 'Content-Transfer-Encoding'; + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + return $this->transferEncoding; + } + + public function setEncoding($encoding) + { + // Header must be always in US-ASCII + return $this; + } + + public function getEncoding() + { + return 'ASCII'; + } + + public function toString() + { + return 'Content-Transfer-Encoding: ' . $this->getFieldValue(); + } + + /** + * Set the content transfer encoding + * + * @param string $transferEncoding + * @throws Exception\InvalidArgumentException + * @return $this + */ + public function setTransferEncoding($transferEncoding) + { + // Per RFC 1521, the value of the header is not case sensitive + $transferEncoding = strtolower($transferEncoding); + + if (! in_array($transferEncoding, static::$allowedTransferEncodings)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects one of "'. implode(', ', static::$allowedTransferEncodings) . '"; received "%s"', + __METHOD__, + (string) $transferEncoding + )); + } + $this->transferEncoding = $transferEncoding; + return $this; + } + + /** + * Retrieve the content transfer encoding + * + * @return string + */ + public function getTransferEncoding() + { + return $this->transferEncoding; + } +} diff --git a/lib/laminas/laminas-mail/src/Header/ContentType.php b/lib/laminas/laminas-mail/src/Header/ContentType.php new file mode 100644 index 0000000000..99fa9ecc58 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/ContentType.php @@ -0,0 +1,197 @@ +setType($parts[0]); + + if (isset($parts[1])) { + $values = ListParser::parse(trim($parts[1]), [';', '=']); + $length = count($values); + + for ($i = 0; $i < $length; $i += 2) { + $value = $values[$i + 1]; + $value = trim($value, "'\" \t\n\r\0\x0B"); + $header->addParameter($values[$i], $value); + } + } + + return $header; + } + + public function getFieldName() + { + return 'Content-Type'; + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + $prepared = $this->type; + if (empty($this->parameters)) { + return $prepared; + } + + $values = [$prepared]; + foreach ($this->parameters as $attribute => $value) { + if (HeaderInterface::FORMAT_ENCODED === $format && ! Mime::isPrintable($value)) { + $this->encoding = 'UTF-8'; + $value = HeaderWrap::wrap($value, $this); + $this->encoding = 'ASCII'; + } + + $values[] = sprintf('%s="%s"', $attribute, $value); + } + + return implode(';' . Headers::FOLDING, $values); + } + + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + public function getEncoding() + { + return $this->encoding; + } + + public function toString() + { + return 'Content-Type: ' . $this->getFieldValue(HeaderInterface::FORMAT_ENCODED); + } + + /** + * Set the content type + * + * @param string $type + * @throws Exception\InvalidArgumentException + * @return ContentType + */ + public function setType($type) + { + if (! preg_match('/^[a-z-]+\/[a-z0-9.+-]+$/i', $type)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a value in the format "type/subtype"; received "%s"', + __METHOD__, + (string) $type + )); + } + $this->type = $type; + return $this; + } + + /** + * Retrieve the content type + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Add a parameter pair + * + * @param string $name + * @param string $value + * @return ContentType + * @throws Exception\InvalidArgumentException for parameter names that do not follow RFC 2822 + * @throws Exception\InvalidArgumentException for parameter values that do not follow RFC 2822 + */ + public function addParameter($name, $value) + { + $name = trim(strtolower($name)); + $value = (string) $value; + + if (! HeaderValue::isValid($name)) { + throw new Exception\InvalidArgumentException('Invalid content-type parameter name detected'); + } + if (! HeaderWrap::canBeEncoded($value)) { + throw new Exception\InvalidArgumentException( + 'Parameter value must be composed of printable US-ASCII or UTF-8 characters.' + ); + } + + $this->parameters[$name] = $value; + return $this; + } + + /** + * Get all parameters + * + * @return array + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Get a parameter by name + * + * @param string $name + * @return null|string + */ + public function getParameter($name) + { + $name = strtolower($name); + if (isset($this->parameters[$name])) { + return $this->parameters[$name]; + } + + return null; + } + + /** + * Remove a named parameter + * + * @param string $name + * @return bool + */ + public function removeParameter($name) + { + $name = strtolower($name); + if (isset($this->parameters[$name])) { + unset($this->parameters[$name]); + return true; + } + return false; + } +} diff --git a/lib/laminas/laminas-mail/src/Header/Date.php b/lib/laminas/laminas-mail/src/Header/Date.php new file mode 100644 index 0000000000..d6d3d732b2 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/Date.php @@ -0,0 +1,63 @@ +value = $value; + } + + public function getFieldName() + { + return 'Date'; + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + return $this->value; + } + + public function setEncoding($encoding) + { + // This header must be always in US-ASCII + return $this; + } + + public function getEncoding() + { + return 'ASCII'; + } + + public function toString() + { + return 'Date: ' . $this->getFieldValue(); + } +} diff --git a/lib/laminas/laminas-mail/src/Header/Exception/BadMethodCallException.php b/lib/laminas/laminas-mail/src/Header/Exception/BadMethodCallException.php new file mode 100644 index 0000000000..7506af5f43 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/Exception/BadMethodCallException.php @@ -0,0 +1,9 @@ +setFieldName($fieldName); + + if ($fieldValue !== null) { + $this->setFieldValue($fieldValue); + } + } + + /** + * Set header name + * + * @param string $fieldName + * @return GenericHeader + * @throws Exception\InvalidArgumentException; + */ + public function setFieldName($fieldName) + { + if (! is_string($fieldName) || empty($fieldName)) { + throw new Exception\InvalidArgumentException('Header name must be a string'); + } + + // Pre-filter to normalize valid characters, change underscore to dash + $fieldName = str_replace(' ', '-', ucwords(str_replace(['_', '-'], ' ', $fieldName))); + + if (! HeaderName::isValid($fieldName)) { + throw new Exception\InvalidArgumentException( + 'Header name must be composed of printable US-ASCII characters, except colon.' + ); + } + + $this->fieldName = $fieldName; + return $this; + } + + public function getFieldName() + { + return $this->fieldName; + } + + /** + * Set header value + * + * @param string $fieldValue + * @return GenericHeader + * @throws Exception\InvalidArgumentException; + */ + public function setFieldValue($fieldValue) + { + $fieldValue = (string) $fieldValue; + + if (! HeaderWrap::canBeEncoded($fieldValue)) { + throw new Exception\InvalidArgumentException( + 'Header value must be composed of printable US-ASCII characters and valid folding sequences.' + ); + } + + $this->fieldValue = $fieldValue; + $this->encoding = null; + + return $this; + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + if (HeaderInterface::FORMAT_ENCODED === $format) { + return HeaderWrap::wrap($this->fieldValue, $this); + } + + return $this->fieldValue; + } + + public function setEncoding($encoding) + { + if ($encoding === $this->encoding) { + return $this; + } + + if ($encoding === null) { + $this->encoding = null; + return $this; + } + + $encoding = strtoupper($encoding); + if ($encoding === 'UTF-8') { + $this->encoding = $encoding; + return $this; + } + + if ($encoding === 'ASCII' && Mime::isPrintable($this->fieldValue)) { + $this->encoding = $encoding; + return $this; + } + + $this->encoding = null; + + return $this; + } + + public function getEncoding() + { + if (! $this->encoding) { + $this->encoding = Mime::isPrintable($this->fieldValue) ? 'ASCII' : 'UTF-8'; + } + + return $this->encoding; + } + + public function toString() + { + $name = $this->getFieldName(); + if (empty($name)) { + throw new Exception\RuntimeException('Header name is not set, use setFieldName()'); + } + $value = $this->getFieldValue(HeaderInterface::FORMAT_ENCODED); + + return $name . ': ' . $value; + } +} diff --git a/lib/laminas/laminas-mail/src/Header/GenericMultiHeader.php b/lib/laminas/laminas-mail/src/Header/GenericMultiHeader.php new file mode 100644 index 0000000000..9d62a63a03 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/GenericMultiHeader.php @@ -0,0 +1,49 @@ +getFieldName(); + $values = [$this->getFieldValue(HeaderInterface::FORMAT_ENCODED)]; + + foreach ($headers as $header) { + if (! $header instanceof static) { + throw new Exception\InvalidArgumentException( + 'This method toStringMultipleHeaders was expecting an array of headers of the same type' + ); + } + $values[] = $header->getFieldValue(HeaderInterface::FORMAT_ENCODED); + } + + return $name . ': ' . implode(',', $values); + } +} diff --git a/lib/laminas/laminas-mail/src/Header/HeaderInterface.php b/lib/laminas/laminas-mail/src/Header/HeaderInterface.php new file mode 100644 index 0000000000..c5e98341c8 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/HeaderInterface.php @@ -0,0 +1,69 @@ + Bcc::class, + 'cc' => Cc::class, + 'contentdisposition' => ContentDisposition::class, + 'content_disposition' => ContentDisposition::class, + 'content-disposition' => ContentDisposition::class, + 'contenttype' => ContentType::class, + 'content_type' => ContentType::class, + 'content-type' => ContentType::class, + 'contenttransferencoding' => ContentTransferEncoding::class, + 'content_transfer_encoding' => ContentTransferEncoding::class, + 'content-transfer-encoding' => ContentTransferEncoding::class, + 'date' => Date::class, + 'from' => From::class, + 'in-reply-to' => InReplyTo::class, + 'message-id' => MessageId::class, + 'mimeversion' => MimeVersion::class, + 'mime_version' => MimeVersion::class, + 'mime-version' => MimeVersion::class, + 'received' => Received::class, + 'references' => References::class, + 'replyto' => ReplyTo::class, + 'reply_to' => ReplyTo::class, + 'reply-to' => ReplyTo::class, + 'sender' => Sender::class, + 'subject' => Subject::class, + 'to' => To::class, + ]; +} diff --git a/lib/laminas/laminas-mail/src/Header/HeaderLocator.php b/lib/laminas/laminas-mail/src/Header/HeaderLocator.php new file mode 100644 index 0000000000..fcaf2774c1 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/HeaderLocator.php @@ -0,0 +1,69 @@ + Bcc::class, + 'cc' => Cc::class, + 'contentdisposition' => ContentDisposition::class, + 'content_disposition' => ContentDisposition::class, + 'content-disposition' => ContentDisposition::class, + 'contenttype' => ContentType::class, + 'content_type' => ContentType::class, + 'content-type' => ContentType::class, + 'contenttransferencoding' => ContentTransferEncoding::class, + 'content_transfer_encoding' => ContentTransferEncoding::class, + 'content-transfer-encoding' => ContentTransferEncoding::class, + 'date' => Date::class, + 'from' => From::class, + 'in-reply-to' => InReplyTo::class, + 'message-id' => MessageId::class, + 'mimeversion' => MimeVersion::class, + 'mime_version' => MimeVersion::class, + 'mime-version' => MimeVersion::class, + 'received' => Received::class, + 'references' => References::class, + 'replyto' => ReplyTo::class, + 'reply_to' => ReplyTo::class, + 'reply-to' => ReplyTo::class, + 'sender' => Sender::class, + 'subject' => Subject::class, + 'to' => To::class, + ]; + + public function get(string $name, ?string $default = null): ?string + { + $name = $this->normalizeName($name); + return $this->plugins[$name] ?? $default; + } + + public function has(string $name): bool + { + return isset($this->plugins[$this->normalizeName($name)]); + } + + public function add(string $name, string $class): void + { + $this->plugins[$this->normalizeName($name)] = $class; + } + + public function remove(string $name): void + { + unset($this->plugins[$this->normalizeName($name)]); + } + + private function normalizeName(string $name): string + { + return strtolower($name); + } +} diff --git a/lib/laminas/laminas-mail/src/Header/HeaderLocatorInterface.php b/lib/laminas/laminas-mail/src/Header/HeaderLocatorInterface.php new file mode 100644 index 0000000000..28a93dc95d --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/HeaderLocatorInterface.php @@ -0,0 +1,19 @@ + 32 && $ord < 127 && $ord !== 58) { + $result .= $name[$i]; + } + } + return $result; + } + + /** + * Determine if the header name contains any invalid characters. + * + * @param string $name + * @return bool + */ + public static function isValid($name) + { + $tot = strlen($name); + for ($i = 0; $i < $tot; $i += 1) { + $ord = ord($name[$i]); + if ($ord < 33 || $ord > 126 || $ord === 58) { + return false; + } + } + return true; + } + + /** + * Assert that the header name is valid. + * + * Raises an exception if invalid. + * + * @param string $name + * @throws Exception\RuntimeException + * @return void + */ + public static function assertValid($name) + { + if (! self::isValid($name)) { + throw new Exception\RuntimeException('Invalid header name detected'); + } + } +} diff --git a/lib/laminas/laminas-mail/src/Header/HeaderValue.php b/lib/laminas/laminas-mail/src/Header/HeaderValue.php new file mode 100644 index 0000000000..175801801e --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/HeaderValue.php @@ -0,0 +1,110 @@ + 127) { + continue; + } + + if ($ord === 13) { + if ($i + 2 >= $total) { + continue; + } + + $lf = ord($value[$i + 1]); + $sp = ord($value[$i + 2]); + + if ($lf !== 10 || $sp !== 32) { + continue; + } + + $result .= "\r\n "; + $i += 2; + continue; + } + + $result .= $value[$i]; + } + + return $result; + } + + /** + * Determine if the header value contains any invalid characters. + * + * @see http://www.rfc-base.org/txt/rfc-2822.txt (section 2.2) + * @param string $value + * @return bool + */ + public static function isValid($value) + { + $total = strlen($value); + for ($i = 0; $i < $total; $i += 1) { + $ord = ord($value[$i]); + + // bare LF means we aren't valid + if ($ord === 10 || $ord > 127) { + return false; + } + + if ($ord === 13) { + if ($i + 2 >= $total) { + return false; + } + + $lf = ord($value[$i + 1]); + $sp = ord($value[$i + 2]); + + if ($lf !== 10 || ! in_array($sp, [9, 32], true)) { + return false; + } + + // skip over the LF following this + $i += 2; + } + } + + return true; + } + + /** + * Assert that the header value is valid. + * + * Raises an exception if invalid. + * + * @param string $value + * @throws Exception\RuntimeException + * @return void + */ + public static function assertValid($value) + { + if (! self::isValid($value)) { + throw new Exception\RuntimeException('Invalid header value detected'); + } + } +} diff --git a/lib/laminas/laminas-mail/src/Header/HeaderWrap.php b/lib/laminas/laminas-mail/src/Header/HeaderWrap.php new file mode 100644 index 0000000000..483f51a532 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/HeaderWrap.php @@ -0,0 +1,155 @@ +getEncoding(); + if ($encoding == 'ASCII') { + return wordwrap($value, 78, Headers::FOLDING); + } + return static::mimeEncodeValue($value, $encoding, 78); + } + + /** + * Wrap a structured header line + * + * @param string $value + * @param StructuredInterface $header + * @return string + */ + protected static function wrapStructuredHeader($value, StructuredInterface $header) + { + $delimiter = $header->getDelimiter(); + + $length = strlen($value); + $lines = []; + $temp = ''; + for ($i = 0; $i < $length; $i++) { + $temp .= $value[$i]; + if ($value[$i] == $delimiter) { + $lines[] = $temp; + $temp = ''; + } + } + return implode(Headers::FOLDING, $lines); + } + + /** + * MIME-encode a value + * + * Performs quoted-printable encoding on a value, setting maximum + * line-length to 998. + * + * @param string $value + * @param string $encoding + * @param int $lineLength maximum line-length, by default 998 + * @return string Returns the mime encode value without the last line ending + */ + public static function mimeEncodeValue($value, $encoding, $lineLength = 998) + { + return Mime::encodeQuotedPrintableHeader($value, $encoding, $lineLength, Headers::EOL); + } + + /** + * MIME-decode a value + * + * Performs quoted-printable decoding on a value. + * + * @param string $value + * @return string Returns the mime encode value without the last line ending + */ + public static function mimeDecodeValue($value) + { + // unfold first, because iconv_mime_decode is discarding "\n" with no apparent reason + // making the resulting value no longer valid. + + // see https://tools.ietf.org/html/rfc2822#section-2.2.3 about unfolding + $parts = explode(Headers::FOLDING, $value); + $value = implode(' ', $parts); + + $decodedValue = iconv_mime_decode($value, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, 'UTF-8'); + + // imap (unlike iconv) can handle multibyte headers which are splitted across multiple line + if (self::isNotDecoded($value, $decodedValue) && extension_loaded('imap')) { + return array_reduce( + imap_mime_header_decode(imap_utf8($value)), + function ($accumulator, $headerPart) { + return $accumulator . $headerPart->text; + }, + '' + ); + } + + return $decodedValue; + } + + private static function isNotDecoded($originalValue, $value) + { + return 0 === strpos($value, '=?') + && strlen($value) - 2 === strpos($value, '?=') + && false !== strpos($originalValue, $value); + } + + /** + * Test if is possible apply MIME-encoding + * + * @param string $value + * @return bool + */ + public static function canBeEncoded($value) + { + // avoid any wrapping by specifying line length long enough + // "test" -> 4 + // "x-test: =?ISO-8859-1?B?dGVzdA==?=" -> 33 + // 8 +2 +3 +3 -> 16 + $charset = 'UTF-8'; + $lineLength = strlen($value) * 4 + strlen($charset) + 16; + + $preferences = [ + 'scheme' => 'Q', + 'input-charset' => $charset, + 'output-charset' => $charset, + 'line-length' => $lineLength, + ]; + + $encoded = iconv_mime_encode('x-test', $value, $preferences); + + return (false !== $encoded); + } +} diff --git a/lib/laminas/laminas-mail/src/Header/IdentificationField.php b/lib/laminas/laminas-mail/src/Header/IdentificationField.php new file mode 100644 index 0000000000..2c50843692 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/IdentificationField.php @@ -0,0 +1,137 @@ +setIds($messageIds); + + return $header; + } + + /** + * @param string $id + * @return string + */ + private static function trimMessageId($id) + { + return trim($id, "\t\n\r\0\x0B<>"); + } + + /** + * @return string + */ + public function getFieldName() + { + return $this->fieldName; + } + + /** + * @param bool $format + * @return string + */ + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + return implode(Headers::FOLDING, array_map(function ($id) { + return sprintf('<%s>', $id); + }, $this->messageIds)); + } + + /** + * @param string $encoding Ignored; headers of this type MUST always be in + * ASCII. + * @return static This method is a no-op, and implements a fluent interface. + */ + public function setEncoding($encoding) + { + return $this; + } + + /** + * @return string Always returns ASCII + */ + public function getEncoding() + { + return 'ASCII'; + } + + /** + * @return string + */ + public function toString() + { + return sprintf('%s: %s', $this->getFieldName(), $this->getFieldValue()); + } + + /** + * Set the message ids + * + * @param string[] $ids + * @return static This method implements a fluent interface. + */ + public function setIds($ids) + { + foreach ($ids as $id) { + if (! HeaderValue::isValid($id) + || preg_match("/[\r\n]/", $id) + ) { + throw new Exception\InvalidArgumentException('Invalid ID detected'); + } + } + + $this->messageIds = array_map([self::class, "trimMessageId"], $ids); + return $this; + } + + /** + * Retrieve the message ids + * + * @return string[] + */ + public function getIds() + { + return $this->messageIds; + } +} diff --git a/lib/laminas/laminas-mail/src/Header/InReplyTo.php b/lib/laminas/laminas-mail/src/Header/InReplyTo.php new file mode 100644 index 0000000000..6698d441ae --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/InReplyTo.php @@ -0,0 +1,9 @@ +setId($value); + + return $header; + } + + public function getFieldName() + { + return 'Message-ID'; + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + return $this->messageId; + } + + public function setEncoding($encoding) + { + // This header must be always in US-ASCII + return $this; + } + + public function getEncoding() + { + return 'ASCII'; + } + + public function toString() + { + return 'Message-ID: ' . $this->getFieldValue(); + } + + /** + * Set the message id + * + * @param string|null $id + * @return MessageId + */ + public function setId($id = null) + { + if ($id === null) { + $id = $this->createMessageId(); + } else { + $id = trim($id, '<>'); + } + + if (! HeaderValue::isValid($id) + || preg_match("/[\r\n]/", $id) + ) { + throw new Exception\InvalidArgumentException('Invalid ID detected'); + } + + $this->messageId = sprintf('<%s>', $id); + return $this; + } + + /** + * Retrieve the message id + * + * @return string + */ + public function getId() + { + return $this->messageId; + } + + /** + * Creates the Message-ID + * + * @return string + */ + public function createMessageId() + { + $time = time(); + + if (isset($_SERVER['REMOTE_ADDR'])) { + $user = $_SERVER['REMOTE_ADDR']; + } else { + $user = getmypid(); + } + + $rand = mt_rand(); + + if (isset($_SERVER["SERVER_NAME"])) { + $hostName = $_SERVER["SERVER_NAME"]; + } else { + $hostName = php_uname('n'); + } + + return sha1($time . $user . $rand) . '@' . $hostName; + } +} diff --git a/lib/laminas/laminas-mail/src/Header/MimeVersion.php b/lib/laminas/laminas-mail/src/Header/MimeVersion.php new file mode 100644 index 0000000000..435029470d --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/MimeVersion.php @@ -0,0 +1,81 @@ +\d+\.\d+)$/', $value, $matches)) { + $header->setVersion($matches['version']); + } + + return $header; + } + + public function getFieldName() + { + return 'MIME-Version'; + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + return $this->version; + } + + public function setEncoding($encoding) + { + // This header must be always in US-ASCII + return $this; + } + + public function getEncoding() + { + return 'ASCII'; + } + + public function toString() + { + return 'MIME-Version: ' . $this->getFieldValue(); + } + + /** + * Set the version string used in this header + * + * @param string $version + * @return MimeVersion + */ + public function setVersion($version) + { + if (! preg_match('/^[1-9]\d*\.\d+$/', $version)) { + throw new Exception\InvalidArgumentException('Invalid MIME-Version value detected'); + } + $this->version = $version; + return $this; + } + + /** + * Retrieve the version string for this header + * + * @return string + */ + public function getVersion() + { + return $this->version; + } +} diff --git a/lib/laminas/laminas-mail/src/Header/MultipleHeadersInterface.php b/lib/laminas/laminas-mail/src/Header/MultipleHeadersInterface.php new file mode 100644 index 0000000000..b109e68ddb --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/MultipleHeadersInterface.php @@ -0,0 +1,8 @@ +value = $value; + } + + public function getFieldName() + { + return 'Received'; + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + return $this->value; + } + + public function setEncoding($encoding) + { + // This header must be always in US-ASCII + return $this; + } + + public function getEncoding() + { + return 'ASCII'; + } + + public function toString() + { + return 'Received: ' . $this->getFieldValue(); + } + + /** + * Serialize collection of Received headers to string + * + * @param array $headers + * @throws Exception\RuntimeException + * @return string + */ + public function toStringMultipleHeaders(array $headers) + { + $strings = [$this->toString()]; + foreach ($headers as $header) { + if (! $header instanceof self) { + throw new Exception\RuntimeException( + 'The Received multiple header implementation can only accept an array of Received headers' + ); + } + $strings[] = $header->toString(); + } + return implode(Headers::EOL, $strings); + } +} diff --git a/lib/laminas/laminas-mail/src/Header/References.php b/lib/laminas/laminas-mail/src/Header/References.php new file mode 100644 index 0000000000..9b7b92fe7d --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/References.php @@ -0,0 +1,9 @@ + when a name is present + * 'name' and 'email' capture groups correspond respectively to 'display-name' and 'addr-spec' in the ABNF + * @see https://tools.ietf.org/html/rfc5322#section-3.4 + */ + $hasMatches = preg_match( + '/^(?:(?P.+)\s)?(?(name)<|[^\s]+?)(?(name)>|>?)$/', + $value, + $matches + ); + + if ($hasMatches !== 1) { + throw new Exception\InvalidArgumentException('Invalid header value for Sender string'); + } + + $senderName = trim($matches['name']); + + if (empty($senderName)) { + $senderName = null; + } + + $header->setAddress($matches['email'], $senderName); + + return $header; + } + + public function getFieldName() + { + return 'Sender'; + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + if (! $this->address instanceof Mail\Address\AddressInterface) { + return ''; + } + + $email = sprintf('<%s>', $this->address->getEmail()); + $name = $this->address->getName(); + + if (! empty($name)) { + if ($format == HeaderInterface::FORMAT_ENCODED) { + $encoding = $this->getEncoding(); + if ('ASCII' !== $encoding) { + $name = HeaderWrap::mimeEncodeValue($name, $encoding); + } + } + $email = sprintf('%s %s', $name, $email); + } + + return $email; + } + + public function setEncoding($encoding) + { + $this->encoding = $encoding; + return $this; + } + + public function getEncoding() + { + if (! $this->encoding) { + $this->encoding = Mime::isPrintable($this->getFieldValue(HeaderInterface::FORMAT_RAW)) + ? 'ASCII' + : 'UTF-8'; + } + + return $this->encoding; + } + + public function toString() + { + return 'Sender: ' . $this->getFieldValue(HeaderInterface::FORMAT_ENCODED); + } + + /** + * Set the address used in this header + * + * @param string|\Laminas\Mail\Address\AddressInterface $emailOrAddress + * @param null|string $name + * @throws Exception\InvalidArgumentException + * @return Sender + */ + public function setAddress($emailOrAddress, $name = null) + { + if (is_string($emailOrAddress)) { + $emailOrAddress = new Mail\Address($emailOrAddress, $name); + } elseif (! $emailOrAddress instanceof Mail\Address\AddressInterface) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a string or AddressInterface object; received "%s"', + __METHOD__, + (is_object($emailOrAddress) ? get_class($emailOrAddress) : gettype($emailOrAddress)) + )); + } + $this->address = $emailOrAddress; + return $this; + } + + /** + * Retrieve the internal address from this header + * + * @return \Laminas\Mail\Address\AddressInterface|null + */ + public function getAddress() + { + return $this->address; + } +} diff --git a/lib/laminas/laminas-mail/src/Header/StructuredInterface.php b/lib/laminas/laminas-mail/src/Header/StructuredInterface.php new file mode 100644 index 0000000000..f487bb3f22 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/StructuredInterface.php @@ -0,0 +1,13 @@ +setSubject($value); + + return $header; + } + + public function getFieldName() + { + return 'Subject'; + } + + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) + { + if (HeaderInterface::FORMAT_ENCODED === $format) { + return HeaderWrap::wrap($this->subject, $this); + } + + return $this->subject; + } + + public function setEncoding($encoding) + { + if ($encoding === $this->encoding) { + return $this; + } + + if ($encoding === null) { + $this->encoding = null; + return $this; + } + + $encoding = strtoupper($encoding); + if ($encoding === 'UTF-8') { + $this->encoding = $encoding; + return $this; + } + + if ($encoding === 'ASCII' && Mime::isPrintable($this->subject)) { + $this->encoding = $encoding; + return $this; + } + + $this->encoding = null; + + return $this; + } + + public function getEncoding() + { + if (! $this->encoding) { + $this->encoding = Mime::isPrintable($this->subject) ? 'ASCII' : 'UTF-8'; + } + + return $this->encoding; + } + + public function setSubject($subject) + { + $subject = (string) $subject; + + if (! HeaderWrap::canBeEncoded($subject)) { + throw new Exception\InvalidArgumentException( + 'Subject value must be composed of printable US-ASCII or UTF-8 characters.' + ); + } + + $this->subject = $subject; + $this->encoding = null; + + return $this; + } + + public function toString() + { + return 'Subject: ' . $this->getFieldValue(HeaderInterface::FORMAT_ENCODED); + } +} diff --git a/lib/laminas/laminas-mail/src/Header/To.php b/lib/laminas/laminas-mail/src/Header/To.php new file mode 100644 index 0000000000..b3f50dead7 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Header/To.php @@ -0,0 +1,9 @@ + 2) { + throw new Exception\RuntimeException('Malformed header detected'); + } + continue; + } elseif (preg_match('/^\s*$/', $line)) { + // skip empty continuation line + continue; + } + + if ($emptyLine > 1) { + throw new Exception\RuntimeException('Malformed header detected'); + } + + // check if a header name is present + if (preg_match('/^[\x21-\x39\x3B-\x7E]+:.*$/', $line)) { + if ($currentLine) { + // a header name was present, then store the current complete line + $headers->addHeaderLine($currentLine); + } + $currentLine = trim($line); + continue; + } + + // continuation: append to current line + // recover the whitespace that break the line (unfolding, rfc2822#section-2.2.3) + if (preg_match('/^\s+.*$/', $line)) { + $currentLine .= ' ' . trim($line); + continue; + } + + // Line does not match header format! + throw new Exception\RuntimeException(sprintf( + 'Line "%s" does not match header format!', + $line + )); + } + if ($currentLine) { + $headers->addHeaderLine($currentLine); + } + return $headers; + } + + /** + * Set an alternate PluginClassLocator implementation for loading header classes. + * + * @deprecated since 2.12.0 + * @todo Remove for version 3.0.0 + * @return $this + */ + public function setPluginClassLoader(PluginClassLocator $pluginClassLoader) + { + // Silenced; can be caught in custom error handlers. + @trigger_error(sprintf( + 'Since laminas/laminas-mail 2.12.0: Usage of %s is deprecated; use %s::setHeaderLocator() instead', + __METHOD__, + __CLASS__ + ), E_USER_DEPRECATED); + + $this->pluginClassLoader = $pluginClassLoader; + return $this; + } + + /** + * Return a PluginClassLocator instance for customizing headers. + * + * Lazyloads a Header\HeaderLoader if necessary. + * + * @deprecated since 2.12.0 + * @todo Remove for version 3.0.0 + * @return PluginClassLocator + */ + public function getPluginClassLoader() + { + // Silenced; can be caught in custom error handlers. + @trigger_error(sprintf( + 'Since laminas/laminas-mail 2.12.0: Usage of %s is deprecated; use %s::getHeaderLocator() instead', + __METHOD__, + __CLASS__ + ), E_USER_DEPRECATED); + + if (! $this->pluginClassLoader) { + $this->pluginClassLoader = new Header\HeaderLoader(); + } + + return $this->pluginClassLoader; + } + + /** + * Retrieve the header class locator for customizing headers. + * + * Lazyloads a Header\HeaderLocator instance if necessary. + */ + public function getHeaderLocator(): Header\HeaderLocatorInterface + { + if (! $this->headerLocator) { + $this->setHeaderLocator(new Header\HeaderLocator()); + } + return $this->headerLocator; + } + + /** + * @todo Return self when we update to 7.4 or later as minimum PHP version. + * @return $this + */ + public function setHeaderLocator(Header\HeaderLocatorInterface $headerLocator) + { + $this->headerLocator = $headerLocator; + return $this; + } + + /** + * Set the header encoding + * + * @param string $encoding + * @return Headers + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + foreach ($this as $header) { + $header->setEncoding($encoding); + } + return $this; + } + + /** + * Get the header encoding + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Add many headers at once + * + * Expects an array (or Traversable object) of type/value pairs. + * + * @param array|Traversable $headers + * @throws Exception\InvalidArgumentException + * @return Headers + */ + public function addHeaders($headers) + { + if (! is_array($headers) && ! $headers instanceof Traversable) { + throw new Exception\InvalidArgumentException(sprintf( + 'Expected array or Traversable; received "%s"', + (is_object($headers) ? get_class($headers) : gettype($headers)) + )); + } + + foreach ($headers as $name => $value) { + if (is_int($name)) { + if (is_string($value)) { + $this->addHeaderLine($value); + } elseif (is_array($value) && count($value) == 1) { + $this->addHeaderLine(key($value), current($value)); + } elseif (is_array($value) && count($value) == 2) { + $this->addHeaderLine($value[0], $value[1]); + } elseif ($value instanceof Header\HeaderInterface) { + $this->addHeader($value); + } + } elseif (is_string($name)) { + $this->addHeaderLine($name, $value); + } + } + + return $this; + } + + /** + * Add a raw header line, either in name => value, or as a single string 'name: value' + * + * This method allows for lazy-loading in that the parsing and instantiation of HeaderInterface object + * will be delayed until they are retrieved by either get() or current() + * + * @throws Exception\InvalidArgumentException + * @param string $headerFieldNameOrLine + * @param string $fieldValue optional + * @return Headers + */ + public function addHeaderLine($headerFieldNameOrLine, $fieldValue = null) + { + if (! is_string($headerFieldNameOrLine)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects its first argument to be a string; received "%s"', + __METHOD__, + (is_object($headerFieldNameOrLine) + ? get_class($headerFieldNameOrLine) + : gettype($headerFieldNameOrLine)) + )); + } + + if ($fieldValue === null) { + $headers = $this->loadHeader($headerFieldNameOrLine); + $headers = is_array($headers) ? $headers : [$headers]; + foreach ($headers as $header) { + $this->addHeader($header); + } + } elseif (is_array($fieldValue)) { + foreach ($fieldValue as $i) { + $this->addHeader(Header\GenericMultiHeader::fromString($headerFieldNameOrLine . ':' . $i)); + } + } else { + $this->addHeader(Header\GenericHeader::fromString($headerFieldNameOrLine . ':' . $fieldValue)); + } + + return $this; + } + + /** + * Add a Header\Interface to this container, for raw values see {@link addHeaderLine()} and {@link addHeaders()} + * + * @param Header\HeaderInterface $header + * @return Headers + */ + public function addHeader(Header\HeaderInterface $header) + { + $key = $this->normalizeFieldName($header->getFieldName()); + $this->headersKeys[] = $key; + $this->headers[] = $header; + if ($this->getEncoding() !== 'ASCII') { + $header->setEncoding($this->getEncoding()); + } + return $this; + } + + /** + * Remove a Header from the container + * + * @param string|Header\HeaderInterface field name or specific header instance to remove + * @return bool + */ + public function removeHeader($instanceOrFieldName) + { + if (! $instanceOrFieldName instanceof Header\HeaderInterface && ! is_string($instanceOrFieldName)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s requires a string or %s instance; received %s', + __METHOD__, + Header\HeaderInterface::class, + is_object($instanceOrFieldName) ? get_class($instanceOrFieldName) : gettype($instanceOrFieldName) + )); + } + + if ($instanceOrFieldName instanceof Header\HeaderInterface) { + $indexes = array_keys($this->headers, $instanceOrFieldName, true); + } + + if (is_string($instanceOrFieldName)) { + $key = $this->normalizeFieldName($instanceOrFieldName); + $indexes = array_keys($this->headersKeys, $key, true); + } + + if (! empty($indexes)) { + foreach ($indexes as $index) { + unset($this->headersKeys[$index]); + unset($this->headers[$index]); + } + return true; + } + + return false; + } + + /** + * Clear all headers + * + * Removes all headers from queue + * + * @return Headers + */ + public function clearHeaders() + { + $this->headers = $this->headersKeys = []; + return $this; + } + + /** + * Get all headers of a certain name/type + * + * @param string $name + * @return bool|ArrayIterator|Header\HeaderInterface Returns false if there is no headers with $name in this + * contain, an ArrayIterator if the header is a MultipleHeadersInterface instance and finally returns + * HeaderInterface for the rest of cases. + */ + public function get($name) + { + $key = $this->normalizeFieldName($name); + $results = []; + + foreach (array_keys($this->headersKeys, $key, true) as $index) { + if ($this->headers[$index] instanceof Header\GenericHeader) { + $results[] = $this->lazyLoadHeader($index); + } else { + $results[] = $this->headers[$index]; + } + } + + switch (count($results)) { + case 0: + return false; + case 1: + if ($results[0] instanceof Header\MultipleHeadersInterface) { + return new ArrayIterator($results); + } + return $results[0]; + default: + return new ArrayIterator($results); + } + } + + /** + * Test for existence of a type of header + * + * @param string $name + * @return bool + */ + public function has($name) + { + $name = $this->normalizeFieldName($name); + return in_array($name, $this->headersKeys, true); + } + + /** + * Advance the pointer for this object as an iterator + * + */ + #[ReturnTypeWillChange] + public function next() + { + next($this->headers); + } + + /** + * Return the current key for this object as an iterator + * + * @return mixed + */ + #[ReturnTypeWillChange] + public function key() + { + return key($this->headers); + } + + /** + * Is this iterator still valid? + * + * @return bool + */ + #[ReturnTypeWillChange] + public function valid() + { + return (current($this->headers) !== false); + } + + /** + * Reset the internal pointer for this object as an iterator + * + */ + #[ReturnTypeWillChange] + public function rewind() + { + reset($this->headers); + } + + /** + * Return the current value for this iterator, lazy loading it if need be + * + * @return Header\HeaderInterface + */ + #[ReturnTypeWillChange] + public function current() + { + $current = current($this->headers); + if ($current instanceof Header\GenericHeader) { + $current = $this->lazyLoadHeader(key($this->headers)); + } + return $current; + } + + /** + * Return the number of headers in this contain, if all headers have not been parsed, actual count could + * increase if MultipleHeader objects exist in the Request/Response. If you need an exact count, iterate + * + * @return int count of currently known headers + */ + #[ReturnTypeWillChange] + public function count() + { + return count($this->headers); + } + + /** + * Render all headers at once + * + * This method handles the normal iteration of headers; it is up to the + * concrete classes to prepend with the appropriate status/request line. + * + * @return string + */ + public function toString() + { + $headers = ''; + foreach ($this as $header) { + if ($str = $header->toString()) { + $headers .= $str . self::EOL; + } + } + + return $headers; + } + + /** + * Return the headers container as an array + * + * @param bool $format Return the values in Mime::Encoded or in Raw format + * @return array + * @todo determine how to produce single line headers, if they are supported + */ + public function toArray($format = Header\HeaderInterface::FORMAT_RAW) + { + $headers = []; + /* @var $header Header\HeaderInterface */ + foreach ($this->headers as $header) { + if ($header instanceof Header\MultipleHeadersInterface) { + $name = $header->getFieldName(); + if (! isset($headers[$name])) { + $headers[$name] = []; + } + $headers[$name][] = $header->getFieldValue($format); + } else { + $headers[$header->getFieldName()] = $header->getFieldValue($format); + } + } + return $headers; + } + + /** + * By calling this, it will force parsing and loading of all headers, after this count() will be accurate + * + * @return bool + */ + public function forceLoading() + { + foreach ($this as $item) { + // $item should now be loaded + } + return true; + } + + /** + * Create Header object from header line + * + * @param string $headerLine + * @return Header\HeaderInterface|Header\HeaderInterface[] + */ + public function loadHeader($headerLine) + { + list($name) = Header\GenericHeader::splitHeaderLine($headerLine); + + /** @var HeaderInterface $class */ + $class = $this->resolveHeaderClass($name); + return $class::fromString($headerLine); + } + + /** + * @param $index + * @return mixed + */ + protected function lazyLoadHeader($index) + { + $current = $this->headers[$index]; + + $key = $this->headersKeys[$index]; + + /** @var GenericHeader $class */ + $class = $this->resolveHeaderClass($key); + + $encoding = $current->getEncoding(); + $headers = $class::fromString($current->toString()); + if (is_array($headers)) { + $current = array_shift($headers); + $current->setEncoding($encoding); + $this->headers[$index] = $current; + foreach ($headers as $header) { + $header->setEncoding($encoding); + $this->headersKeys[] = $key; + $this->headers[] = $header; + } + return $current; + } + + $current = $headers; + $current->setEncoding($encoding); + $this->headers[$index] = $current; + return $current; + } + + /** + * Normalize a field name + * + * @param string $fieldName + * @return string + */ + protected function normalizeFieldName($fieldName) + { + return str_replace(['-', '_', ' ', '.'], '', strtolower($fieldName)); + } + + /** + * @param string $key + * @return string + */ + private function resolveHeaderClass($key) + { + if ($this->pluginClassLoader) { + return $this->pluginClassLoader->load($key) ?: Header\GenericHeader::class; + } + return $this->getHeaderLocator()->get($key, Header\GenericHeader::class); + } +} diff --git a/lib/laminas/laminas-mail/src/Message.php b/lib/laminas/laminas-mail/src/Message.php new file mode 100644 index 0000000000..67f429d673 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Message.php @@ -0,0 +1,576 @@ +getFrom(); + if (! $from instanceof AddressList) { + return false; + } + return (bool) count($from); + } + + /** + * Set the message encoding + * + * @param string $encoding + * @return Message + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + $this->getHeaders()->setEncoding($encoding); + return $this; + } + + /** + * Get the message encoding + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Compose headers + * + * @param Headers $headers + * @return Message + */ + public function setHeaders(Headers $headers) + { + $this->headers = $headers; + $headers->setEncoding($this->getEncoding()); + return $this; + } + + /** + * Access headers collection + * + * Lazy-loads if not already attached. + * + * @return Headers + */ + public function getHeaders() + { + if (null === $this->headers) { + $this->setHeaders(new Headers()); + $date = Header\Date::fromString('Date: ' . date('r')); + $this->headers->addHeader($date); + } + return $this->headers; + } + + /** + * Set (overwrite) From addresses + * + * @param string|Address\AddressInterface|array|AddressList|Traversable $emailOrAddressList + * @param string|null $name + * @return Message + */ + public function setFrom($emailOrAddressList, $name = null) + { + $this->clearHeaderByName('from'); + return $this->addFrom($emailOrAddressList, $name); + } + + /** + * Add a "From" address + * + * @param string|Address|array|AddressList|Traversable $emailOrAddressOrList + * @param string|null $name + * @return Message + */ + public function addFrom($emailOrAddressOrList, $name = null) + { + $addressList = $this->getFrom(); + $this->updateAddressList($addressList, $emailOrAddressOrList, $name, __METHOD__); + return $this; + } + + /** + * Retrieve list of From senders + * + * @return AddressList + */ + public function getFrom() + { + return $this->getAddressListFromHeader('from', From::class); + } + + /** + * Overwrite the address list in the To recipients + * + * @param string|Address\AddressInterface|array|AddressList|Traversable $emailOrAddressList + * @param null|string $name + * @return Message + */ + public function setTo($emailOrAddressList, $name = null) + { + $this->clearHeaderByName('to'); + return $this->addTo($emailOrAddressList, $name); + } + + /** + * Add one or more addresses to the To recipients + * + * Appends to the list. + * + * @param string|Address\AddressInterface|array|AddressList|Traversable $emailOrAddressOrList + * @param null|string $name + * @return Message + */ + public function addTo($emailOrAddressOrList, $name = null) + { + $addressList = $this->getTo(); + $this->updateAddressList($addressList, $emailOrAddressOrList, $name, __METHOD__); + return $this; + } + + /** + * Access the address list of the To header + * + * @return AddressList + */ + public function getTo() + { + return $this->getAddressListFromHeader('to', To::class); + } + + /** + * Set (overwrite) CC addresses + * + * @param string|Address\AddressInterface|array|AddressList|Traversable $emailOrAddressList + * @param string|null $name + * @return Message + */ + public function setCc($emailOrAddressList, $name = null) + { + $this->clearHeaderByName('cc'); + return $this->addCc($emailOrAddressList, $name); + } + + /** + * Add a "Cc" address + * + * @param string|Address|array|AddressList|Traversable $emailOrAddressOrList + * @param string|null $name + * @return Message + */ + public function addCc($emailOrAddressOrList, $name = null) + { + $addressList = $this->getCc(); + $this->updateAddressList($addressList, $emailOrAddressOrList, $name, __METHOD__); + return $this; + } + + /** + * Retrieve list of CC recipients + * + * @return AddressList + */ + public function getCc() + { + return $this->getAddressListFromHeader('cc', Cc::class); + } + + /** + * Set (overwrite) BCC addresses + * + * @param string|Address\AddressInterface|array|AddressList|Traversable $emailOrAddressList + * @param string|null $name + * @return Message + */ + public function setBcc($emailOrAddressList, $name = null) + { + $this->clearHeaderByName('bcc'); + return $this->addBcc($emailOrAddressList, $name); + } + + /** + * Add a "Bcc" address + * + * @param string|Address|array|AddressList|Traversable $emailOrAddressOrList + * @param string|null $name + * @return Message + */ + public function addBcc($emailOrAddressOrList, $name = null) + { + $addressList = $this->getBcc(); + $this->updateAddressList($addressList, $emailOrAddressOrList, $name, __METHOD__); + return $this; + } + + /** + * Retrieve list of BCC recipients + * + * @return AddressList + */ + public function getBcc() + { + return $this->getAddressListFromHeader('bcc', Bcc::class); + } + + /** + * Overwrite the address list in the Reply-To recipients + * + * @param string|Address\AddressInterface|array|AddressList|Traversable $emailOrAddressList + * @param null|string $name + * @return Message + */ + public function setReplyTo($emailOrAddressList, $name = null) + { + $this->clearHeaderByName('reply-to'); + return $this->addReplyTo($emailOrAddressList, $name); + } + + /** + * Add one or more addresses to the Reply-To recipients + * + * Appends to the list. + * + * @param string|Address\AddressInterface|array|AddressList|Traversable $emailOrAddressOrList + * @param null|string $name + * @return Message + */ + public function addReplyTo($emailOrAddressOrList, $name = null) + { + $addressList = $this->getReplyTo(); + $this->updateAddressList($addressList, $emailOrAddressOrList, $name, __METHOD__); + return $this; + } + + /** + * Access the address list of the Reply-To header + * + * @return AddressList + */ + public function getReplyTo() + { + return $this->getAddressListFromHeader('reply-to', ReplyTo::class); + } + + /** + * setSender + * + * @param mixed $emailOrAddress + * @param mixed $name + * @return Message + */ + public function setSender($emailOrAddress, $name = null) + { + /** @var Sender $header */ + $header = $this->getHeaderByName('sender', Sender::class); + $header->setAddress($emailOrAddress, $name); + return $this; + } + + /** + * Retrieve the sender address, if any + * + * @return null|Address\AddressInterface + */ + public function getSender() + { + $headers = $this->getHeaders(); + if (! $headers->has('sender')) { + return null; + } + + /** @var Sender $header */ + $header = $this->getHeaderByName('sender', Sender::class); + return $header->getAddress(); + } + + /** + * Set the message subject header value + * + * @param string $subject + * @return Message + */ + public function setSubject($subject) + { + $headers = $this->getHeaders(); + if (! $headers->has('subject')) { + $header = new Header\Subject(); + $headers->addHeader($header); + } else { + $header = $headers->get('subject'); + } + $header->setSubject($subject); + $header->setEncoding($this->getEncoding()); + return $this; + } + + /** + * Get the message subject header value + * + * @return null|string + */ + public function getSubject() + { + $headers = $this->getHeaders(); + if (! $headers->has('subject')) { + return; + } + $header = $headers->get('subject'); + return $header->getFieldValue(); + } + + /** + * Set the message body + * + * @param null|string|\Laminas\Mime\Message|object $body + * @throws Exception\InvalidArgumentException + * @return Message + */ + public function setBody($body) + { + if (! is_string($body) && $body !== null) { + if (! is_object($body)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a string or object argument; received "%s"', + __METHOD__, + gettype($body) + )); + } + if (! $body instanceof Mime\Message) { + if (! method_exists($body, '__toString')) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects object arguments of type %s or implementing __toString();' + . ' object of type "%s" received', + __METHOD__, + Mime\Message::class, + get_class($body) + )); + } + } + } + $this->body = $body; + + if (! $this->body instanceof Mime\Message) { + return $this; + } + + // Get headers, and set Mime-Version header + $headers = $this->getHeaders(); + $this->getHeaderByName('mime-version', MimeVersion::class); + + // Multipart content headers + if ($this->body->isMultiPart()) { + $mime = $this->body->getMime(); + + /** @var ContentType $header */ + $header = $this->getHeaderByName('content-type', ContentType::class); + $header->setType('multipart/mixed'); + $header->addParameter('boundary', $mime->boundary()); + return $this; + } + + // MIME single part headers + $parts = $this->body->getParts(); + if (! empty($parts)) { + $part = array_shift($parts); + $headers->addHeaders($part->getHeadersArray("\r\n")); + } + return $this; + } + + /** + * Return the currently set message body + * + * @return object|string|Mime\Message + */ + public function getBody() + { + return $this->body; + } + + /** + * Get the string-serialized message body text + * + * @return string + */ + public function getBodyText() + { + if ($this->body instanceof Mime\Message) { + return $this->body->generateMessage(Headers::EOL); + } + + return (string) $this->body; + } + + /** + * Retrieve a header by name + * + * If not found, instantiates one based on $headerClass. + * + * @param string $headerName + * @param string $headerClass + * @return Header\HeaderInterface|\ArrayIterator header instance or collection of headers + */ + protected function getHeaderByName($headerName, $headerClass) + { + $headers = $this->getHeaders(); + if ($headers->has($headerName)) { + $header = $headers->get($headerName); + } else { + $header = new $headerClass(); + $headers->addHeader($header); + } + return $header; + } + + /** + * Clear a header by name + * + * @param string $headerName + */ + protected function clearHeaderByName($headerName) + { + $this->getHeaders()->removeHeader($headerName); + } + + /** + * Retrieve the AddressList from a named header + * + * Used with To, From, Cc, Bcc, and ReplyTo headers. If the header does not + * exist, instantiates it. + * + * @param string $headerName + * @param string $headerClass + * @throws Exception\DomainException + * @return AddressList + */ + protected function getAddressListFromHeader($headerName, $headerClass) + { + $header = $this->getHeaderByName($headerName, $headerClass); + if (! $header instanceof Header\AbstractAddressList) { + throw new Exception\DomainException(sprintf( + 'Cannot grab address list from header of type "%s"; not an AbstractAddressList implementation', + get_class($header) + )); + } + return $header->getAddressList(); + } + + /** + * Update an address list + * + * Proxied to this from addFrom, addTo, addCc, addBcc, and addReplyTo. + * + * @param AddressList $addressList + * @param string|Address\AddressInterface|array|AddressList|Traversable $emailOrAddressOrList + * @param null|string $name + * @param string $callingMethod + * @throws Exception\InvalidArgumentException + */ + protected function updateAddressList(AddressList $addressList, $emailOrAddressOrList, $name, $callingMethod) + { + if ($emailOrAddressOrList instanceof Traversable) { + foreach ($emailOrAddressOrList as $address) { + $addressList->add($address); + } + return; + } + if (is_array($emailOrAddressOrList)) { + $addressList->addMany($emailOrAddressOrList); + return; + } + if (! is_string($emailOrAddressOrList) && ! $emailOrAddressOrList instanceof Address\AddressInterface) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a string, AddressInterface, array, AddressList, or Traversable as its first argument;' + . ' received "%s"', + $callingMethod, + (is_object($emailOrAddressOrList) ? get_class($emailOrAddressOrList) : gettype($emailOrAddressOrList)) + )); + } + + if (is_string($emailOrAddressOrList) && $name === null) { + $addressList->addFromString($emailOrAddressOrList); + return; + } + + $addressList->add($emailOrAddressOrList, $name); + } + + /** + * Serialize to string + * + * @return string + */ + public function toString() + { + $headers = $this->getHeaders(); + return $headers->toString() + . Headers::EOL + . $this->getBodyText(); + } + + /** + * Instantiate from raw message string + * + * @todo Restore body to Mime\Message + * @param string $rawMessage + * @return Message + */ + public static function fromString($rawMessage) + { + $message = new static(); + + /** @var Headers $headers */ + $headers = null; + $content = null; + Mime\Decode::splitMessage($rawMessage, $headers, $content, Headers::EOL); + if ($headers->has('mime-version')) { + // todo - restore body to mime\message + } + $message->setHeaders($headers); + $message->setBody($content); + return $message; + } +} diff --git a/lib/laminas/laminas-mail/src/MessageFactory.php b/lib/laminas/laminas-mail/src/MessageFactory.php new file mode 100644 index 0000000000..faf40fe763 --- /dev/null +++ b/lib/laminas/laminas-mail/src/MessageFactory.php @@ -0,0 +1,58 @@ + $value) { + $setter = self::getSetterMethod($key); + if (method_exists($message, $setter)) { + $message->{$setter}($value); + } + } + + return $message; + } + + /** + * Generate a setter method name based on a provided key. + * + * @param string $key + * @return string + */ + private static function getSetterMethod($key) + { + return 'set' + . str_replace( + ' ', + '', + ucwords( + strtr( + $key, + [ + '-' => ' ', + '_' => ' ', + ] + ) + ) + ); + } +} diff --git a/lib/laminas/laminas-mail/src/Module.php b/lib/laminas/laminas-mail/src/Module.php new file mode 100644 index 0000000000..652dfcb7c3 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Module.php @@ -0,0 +1,19 @@ + $provider->getDependencyConfig(), + ]; + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/AbstractProtocol.php b/lib/laminas/laminas-mail/src/Protocol/AbstractProtocol.php new file mode 100644 index 0000000000..304dfe15e8 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/AbstractProtocol.php @@ -0,0 +1,344 @@ +validHost = new Validator\ValidatorChain(); + $this->validHost->attach(new Validator\Hostname(Validator\Hostname::ALLOW_ALL)); + + if (! $this->validHost->isValid($host)) { + throw new Exception\RuntimeException(implode(', ', $this->validHost->getMessages())); + } + + $this->host = $host; + $this->port = $port; + } + + /** + * Class destructor to cleanup open resources + * + */ + public function __destruct() + { + $this->_disconnect(); + } + + /** + * Set the maximum log size + * + * @param int $maximumLog Maximum log size + */ + public function setMaximumLog($maximumLog) + { + $this->maximumLog = (int) $maximumLog; + } + + /** + * Get the maximum log size + * + * @return int the maximum log size + */ + public function getMaximumLog() + { + return $this->maximumLog; + } + + /** + * Create a connection to the remote host + * + * Concrete adapters for this class will implement their own unique connect + * scripts, using the _connect() method to create the socket resource. + */ + abstract public function connect(); + + /** + * Retrieve the last client request + * + * @return string + */ + public function getRequest() + { + return $this->request; + } + + /** + * Retrieve the last server response + * + * @return array + */ + public function getResponse() + { + return $this->response; + } + + /** + * Retrieve the transaction log + * + * @return string + */ + public function getLog() + { + return implode('', $this->log); + } + + /** + * Reset the transaction log + * + */ + public function resetLog() + { + $this->log = []; + } + + /** + * Add the transaction log + * + * @param string $value new transaction + */ + // @codingStandardsIgnoreLine PSR2.Methods.MethodDeclaration.Underscore + protected function _addLog($value) + { + if ($this->maximumLog >= 0 && count($this->log) >= $this->maximumLog) { + array_shift($this->log); + } + + $this->log[] = $value; + } + + /** + * Connect to the server using the supplied transport and target + * + * An example $remote string may be 'tcp://mail.example.com:25' or 'ssh://hostname.com:2222' + * + * @deprecated Since 1.12.0. Implementations should use the ProtocolTrait::setupSocket() method instead. + * @todo Remove for 3.0.0. + * @param string $remote Remote + * @throws Exception\RuntimeException + * @return bool + */ + // @codingStandardsIgnoreLine PSR2.Methods.MethodDeclaration.Underscore + protected function _connect($remote) + { + $errorNum = 0; + $errorStr = ''; + + // open connection + set_error_handler( + function ($error, $message = '') { + throw new Exception\RuntimeException(sprintf('Could not open socket: %s', $message), $error); + }, + E_WARNING + ); + $this->socket = stream_socket_client($remote, $errorNum, $errorStr, self::TIMEOUT_CONNECTION); + restore_error_handler(); + + if ($this->socket === false) { + if ($errorNum == 0) { + $errorStr = 'Could not open socket'; + } + throw new Exception\RuntimeException($errorStr); + } + + if (($result = stream_set_timeout($this->socket, self::TIMEOUT_CONNECTION)) === false) { + throw new Exception\RuntimeException('Could not set stream timeout'); + } + + return $result; + } + + /** + * Disconnect from remote host and free resource + * + */ + // @codingStandardsIgnoreLine PSR2.Methods.MethodDeclaration.Underscore + protected function _disconnect() + { + if (is_resource($this->socket)) { + fclose($this->socket); + } + } + + /** + * Send the given request followed by a LINEEND to the server. + * + * @param string $request + * @throws Exception\RuntimeException + * @return int|bool Number of bytes written to remote host + */ + // @codingStandardsIgnoreLine PSR2.Methods.MethodDeclaration.Underscore + protected function _send($request) + { + if (! is_resource($this->socket)) { + throw new Exception\RuntimeException('No connection has been established to ' . $this->host); + } + + $this->request = $request; + + $result = fwrite($this->socket, $request . self::EOL); + + // Save request to internal log + $this->_addLog($request . self::EOL); + + if ($result === false) { + throw new Exception\RuntimeException('Could not send request to ' . $this->host); + } + + return $result; + } + + /** + * Get a line from the stream. + * + * @param int $timeout Per-request timeout value if applicable + * @throws Exception\RuntimeException + * @return string + */ + // @codingStandardsIgnoreLine PSR2.Methods.MethodDeclaration.Underscore + protected function _receive($timeout = null) + { + if (! is_resource($this->socket)) { + throw new Exception\RuntimeException('No connection has been established to ' . $this->host); + } + + // Adapters may wish to supply per-commend timeouts according to appropriate RFC + if ($timeout !== null) { + stream_set_timeout($this->socket, $timeout); + } + + // Retrieve response + $response = fgets($this->socket, 1024); + + // Save request to internal log + $this->_addLog($response); + + // Check meta data to ensure connection is still valid + $info = stream_get_meta_data($this->socket); + + if ($info['timed_out']) { + throw new Exception\RuntimeException($this->host . ' has timed out'); + } + + if ($response === false) { + throw new Exception\RuntimeException('Could not read from ' . $this->host); + } + + return $response; + } + + /** + * Parse server response for successful codes + * + * Read the response from the stream and check for expected return code. + * Throws a Laminas\Mail\Protocol\Exception\ExceptionInterface if an unexpected code is returned. + * + * @param string|array $code One or more codes that indicate a successful response + * @param int $timeout Per-request timeout value if applicable + * @throws Exception\RuntimeException + * @return string Last line of response string + */ + // @codingStandardsIgnoreLine PSR2.Methods.MethodDeclaration.Underscore + protected function _expect($code, $timeout = null) + { + $this->response = []; + $errMsg = ''; + + if (! is_array($code)) { + $code = [$code]; + } + + do { + $this->response[] = $result = $this->_receive($timeout); + list($cmd, $more, $msg) = preg_split('/([\s-]+)/', $result, 2, PREG_SPLIT_DELIM_CAPTURE); + + if ($errMsg !== '') { + $errMsg .= ' ' . $msg; + } elseif ($cmd === null || ! in_array($cmd, $code)) { + $errMsg = $msg; + } + + // The '-' message prefix indicates an information string instead of a response string. + } while (strpos($more, '-') === 0); + + if ($errMsg !== '') { + throw new Exception\RuntimeException($errMsg); + } + + return $msg; + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/Exception/ExceptionInterface.php b/lib/laminas/laminas-mail/src/Protocol/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..05bfeee211 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/Exception/ExceptionInterface.php @@ -0,0 +1,9 @@ +setNoValidateCert($novalidatecert); + + if ($host) { + $this->connect($host, $port, $ssl); + } + } + + /** + * Public destructor + */ + public function __destruct() + { + $this->logout(); + } + + /** + * Open connection to IMAP server + * + * @param string $host hostname or IP address of IMAP server + * @param int|null $port of IMAP server, default is 143 (993 for ssl) + * @param string|bool $ssl use 'SSL', 'TLS' or false + * @throws Exception\RuntimeException + * @return void + */ + public function connect($host, $port = null, $ssl = false) + { + $transport = 'tcp'; + $isTls = false; + + if ($ssl) { + $ssl = strtolower($ssl); + } + + switch ($ssl) { + case 'ssl': + $transport = 'ssl'; + if (! $port) { + $port = 993; + } + break; + case 'tls': + $isTls = true; + // break intentionally omitted + default: + if (! $port) { + $port = 143; + } + } + + $this->socket = $this->setupSocket($transport, $host, $port, self::TIMEOUT_CONNECTION); + + if (! $this->assumedNextLine('* OK')) { + throw new Exception\RuntimeException('host doesn\'t allow connection'); + } + + if ($isTls) { + $result = $this->requestAndResponse('STARTTLS'); + $result = $result && stream_socket_enable_crypto($this->socket, true, $this->getCryptoMethod()); + if (! $result) { + throw new Exception\RuntimeException('cannot enable TLS'); + } + } + } + + /** + * get the next line from socket with error checking, but nothing else + * + * @throws Exception\RuntimeException + * @return string next line + */ + protected function nextLine() + { + $line = fgets($this->socket); + if ($line === false) { + throw new Exception\RuntimeException('cannot read - connection closed?'); + } + + return $line; + } + + /** + * get next line and assume it starts with $start. some requests give a simple + * feedback so we can quickly check if we can go on. + * + * @param string $start the first bytes we assume to be in the next line + * @return bool line starts with $start + */ + protected function assumedNextLine($start) + { + $line = $this->nextLine(); + return strpos($line, $start) === 0; + } + + /** + * get next line and split the tag. that's the normal case for a response line + * + * @param string $tag tag of line is returned by reference + * @return string next line + */ + protected function nextTaggedLine(&$tag) + { + $line = $this->nextLine(); + + // separate tag from line + list($tag, $line) = explode(' ', $line, 2); + + return $line; + } + + /** + * split a given line in tokens. a token is literal of any form or a list + * + * @param string $line line to decode + * @return array tokens, literals are returned as string, lists as array + */ + protected function decodeLine($line) + { + $tokens = []; + $stack = []; + + /* + We start to decode the response here. The understood tokens are: + literal + "literal" or also "lit\\er\"al" + {bytes}literal + (literals*) + All tokens are returned in an array. Literals in braces (the last understood + token in the list) are returned as an array of tokens. I.e. the following response: + "foo" baz {3}bar ("f\\\"oo" bar) + would be returned as: + array('foo', 'baz', 'bar', array('f\\\"oo', 'bar')); + + // TODO: add handling of '[' and ']' to parser for easier handling of response text + */ + // replace any trailing including spaces with a single space + $line = rtrim($line) . ' '; + while (($pos = strpos($line, ' ')) !== false) { + $token = substr($line, 0, $pos); + if (! strlen($token)) { + continue; + } + while ($token[0] == '(') { + array_push($stack, $tokens); + $tokens = []; + $token = substr($token, 1); + } + if ($token[0] == '"') { + if (preg_match('%^\(*"((.|\\\\|\\")*?)" *%', $line, $matches)) { + $tokens[] = $matches[1]; + $line = substr($line, strlen($matches[0])); + continue; + } + } + if ($token[0] == '{') { + $endPos = strpos($token, '}'); + $chars = substr($token, 1, $endPos - 1); + if (is_numeric($chars)) { + $token = ''; + while (strlen($token) < $chars) { + $token .= $this->nextLine(); + } + $line = ''; + if (strlen($token) > $chars) { + $line = substr($token, $chars); + $token = substr($token, 0, $chars); + } else { + $line .= $this->nextLine(); + } + $tokens[] = $token; + $line = trim($line) . ' '; + continue; + } + } + if ($stack && $token[strlen($token) - 1] == ')') { + // closing braces are not separated by spaces, so we need to count them + $braces = strlen($token); + $token = rtrim($token, ')'); + // only count braces if more than one + $braces -= strlen($token) + 1; + // only add if token had more than just closing braces + if (rtrim($token) != '') { + $tokens[] = rtrim($token); + } + $token = $tokens; + $tokens = array_pop($stack); + // special handline if more than one closing brace + while ($braces-- > 0) { + $tokens[] = $token; + $token = $tokens; + $tokens = array_pop($stack); + } + } + $tokens[] = $token; + $line = substr($line, $pos + 1); + } + + // maybe the server forgot to send some closing braces + while ($stack) { + $child = $tokens; + $tokens = array_pop($stack); + $tokens[] = $child; + } + + return $tokens; + } + + /** + * read a response "line" (could also be more than one real line if response has {..}) + * and do a simple decode + * + * @param array|string $tokens decoded tokens are returned by reference, if $dontParse + * is true the unparsed line is returned here + * @param string $wantedTag check for this tag for response code. Default '*' is + * continuation tag. + * @param bool $dontParse if true only the unparsed line is returned $tokens + * @return bool if returned tag matches wanted tag + */ + public function readLine(&$tokens = [], $wantedTag = '*', $dontParse = false) + { + $tag = null; // define $tag variable before first use + $line = $this->nextTaggedLine($tag); // get next tag + if (! $dontParse) { + $tokens = $this->decodeLine($line); + } else { + $tokens = $line; + } + + // if tag is wanted tag we might be at the end of a multiline response + return $tag == $wantedTag; + } + + /** + * read all lines of response until given tag is found (last line of response) + * + * @param string $tag the tag of your request + * @param bool $dontParse if true every line is returned unparsed instead of + * the decoded tokens + * @return null|bool|array tokens if success, false if error, null if bad request + */ + public function readResponse($tag, $dontParse = false) + { + $lines = []; + $tokens = null; // define $tokens variable before first use + while (! $this->readLine($tokens, $tag, $dontParse)) { + $lines[] = $tokens; + } + + if ($dontParse) { + // last to chars are still needed for response code + $tokens = [substr($tokens, 0, 2)]; + } + + // last line has response code + if ($tokens[0] == 'OK') { + return $lines ? $lines : true; + } elseif ($tokens[0] == 'NO') { + return false; + } + } + + /** + * send a request + * + * @param string $command your request command + * @param array $tokens additional parameters to command, use escapeString() to prepare + * @param string $tag provide a tag otherwise an autogenerated is returned + * @throws Exception\RuntimeException + */ + public function sendRequest($command, $tokens = [], &$tag = null) + { + if (! $tag) { + ++$this->tagCount; + $tag = 'TAG' . $this->tagCount; + } + + $line = $tag . ' ' . $command; + + foreach ($tokens as $token) { + if (is_array($token)) { + if (fwrite($this->socket, $line . ' ' . $token[0] . "\r\n") === false) { + throw new Exception\RuntimeException('cannot write - connection closed?'); + } + if (! $this->assumedNextLine('+ ')) { + throw new Exception\RuntimeException('cannot send literal string'); + } + $line = $token[1]; + } else { + $line .= ' ' . $token; + } + } + + if (fwrite($this->socket, $line . "\r\n") === false) { + throw new Exception\RuntimeException('cannot write - connection closed?'); + } + } + + /** + * send a request and get response at once + * + * @param string $command command as in sendRequest() + * @param array $tokens parameters as in sendRequest() + * @param bool $dontParse if true unparsed lines are returned instead of tokens + * @return mixed response as in readResponse() + */ + public function requestAndResponse($command, $tokens = [], $dontParse = false) + { + $tag = null; // define $tag variable before first use + $this->sendRequest($command, $tokens, $tag); + $response = $this->readResponse($tag, $dontParse); + + return $response; + } + + /** + * escape one or more literals i.e. for sendRequest + * + * @param string|array $string the literal/-s + * @return string|array escape literals, literals with newline ar returned + * as array('{size}', 'string'); + */ + public function escapeString($string) + { + if (func_num_args() < 2) { + if (strpos($string, "\n") !== false) { + return ['{' . strlen($string) . '}', $string]; + } + + return '"' . str_replace(['\\', '"'], ['\\\\', '\\"'], $string) . '"'; + } + + $result = []; + foreach (func_get_args() as $string) { + $result[] = $this->escapeString($string); + } + return $result; + } + + /** + * escape a list with literals or lists + * + * @param array $list list with literals or lists as PHP array + * @return string escaped list for imap + */ + public function escapeList($list) + { + $result = []; + foreach ($list as $v) { + if (! is_array($v)) { + $result[] = $v; + continue; + } + $result[] = $this->escapeList($v); + } + return '(' . implode(' ', $result) . ')'; + } + + /** + * Login to IMAP server. + * + * @param string $user username + * @param string $password password + * @return bool success + */ + public function login($user, $password) + { + return $this->requestAndResponse('LOGIN', $this->escapeString($user, $password), true); + } + + /** + * logout of imap server + * + * @return bool success + */ + public function logout() + { + $result = false; + if ($this->socket) { + try { + $result = $this->requestAndResponse('LOGOUT', [], true); + } catch (Exception\ExceptionInterface $e) { + // ignoring exception + } + fclose($this->socket); + $this->socket = null; + } + return $result; + } + + /** + * Get capabilities from IMAP server + * + * @return array list of capabilities + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + */ + public function capability() + { + $response = $this->requestAndResponse('CAPABILITY'); + + if (! $response) { + return []; + } + + $capabilities = []; + foreach ($response as $line) { + $capabilities = array_merge($capabilities, $line); + } + return $capabilities; + } + + /** + * Examine and select have the same response. The common code for both + * is in this method + * + * @param string $command can be 'EXAMINE' or 'SELECT' and this is used as command + * @param string $box which folder to change to or examine + * @return bool|array false if error, array with returned information + * otherwise (flags, exists, recent, uidvalidity) + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + */ + public function examineOrSelect($command = 'EXAMINE', $box = 'INBOX') + { + $tag = null; // define $tag variable before first use + $this->sendRequest($command, [$this->escapeString($box)], $tag); + + $result = []; + $tokens = null; // define $tokens variable before first use + while (! $this->readLine($tokens, $tag)) { + if ($tokens[0] == 'FLAGS') { + array_shift($tokens); + $result['flags'] = $tokens; + continue; + } + switch ($tokens[1]) { + case 'EXISTS': + case 'RECENT': + $result[strtolower($tokens[1])] = $tokens[0]; + break; + case '[UIDVALIDITY': + $result['uidvalidity'] = (int) $tokens[2]; + break; + default: + // ignore + } + } + + if ($tokens[0] != 'OK') { + return false; + } + return $result; + } + + /** + * change folder + * + * @param string $box change to this folder + * @return bool|array see examineOrselect() + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + */ + public function select($box = 'INBOX') + { + return $this->examineOrSelect('SELECT', $box); + } + + /** + * examine folder + * + * @param string $box examine this folder + * @return bool|array see examineOrselect() + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + */ + public function examine($box = 'INBOX') + { + return $this->examineOrSelect('EXAMINE', $box); + } + + /** + * fetch one or more items of one or more messages + * + * @param string|array $items items to fetch from message(s) as string (if only one item) + * or array of strings + * @param int|array $from message for items or start message if $to !== null + * @param int|null $to if null only one message ($from) is fetched, else it's the + * last message, INF means last message available + * @param bool $uid set to true if passing a unique id + * @throws Exception\RuntimeException + * @return string|array if only one item of one message is fetched it's returned as string + * if items of one message are fetched it's returned as (name => value) + * if one items of messages are fetched it's returned as (msgno => value) + * if items of messages are fetched it's returned as (msgno => (name => value)) + */ + public function fetch($items, $from, $to = null, $uid = false) + { + if (is_array($from)) { + $set = implode(',', $from); + } elseif ($to === null) { + $set = (int) $from; + } elseif ($to === INF) { + $set = (int) $from . ':*'; + } else { + $set = (int) $from . ':' . (int) $to; + } + + $items = (array) $items; + $itemList = $this->escapeList($items); + + $tag = null; // define $tag variable before first use + $this->sendRequest(($uid ? 'UID ' : '') . 'FETCH', [$set, $itemList], $tag); + + $result = []; + $tokens = null; // define $tokens variable before first use + while (! $this->readLine($tokens, $tag)) { + // ignore other responses + if ($tokens[1] != 'FETCH') { + continue; + } + + // find array key of UID value; try the last elements, or search for it + if ($uid) { + $count = count($tokens[2]); + if ($tokens[2][$count - 2] == 'UID') { + $uidKey = $count - 1; + } else { + $uidKey = array_search('UID', $tokens[2]) + 1; + } + } + + // ignore other messages + if ($to === null && ! is_array($from) && ($uid ? $tokens[2][$uidKey] != $from : $tokens[0] != $from)) { + continue; + } + + // if we only want one item we return that one directly + if (count($items) == 1) { + if ($tokens[2][0] == $items[0]) { + $data = $tokens[2][1]; + } elseif ($uid && $tokens[2][2] == $items[0]) { + $data = $tokens[2][3]; + } else { + // maybe the server send an other field we didn't wanted + $count = count($tokens[2]); + // we start with 2, because 0 was already checked + for ($i = 2; $i < $count; $i += 2) { + if ($tokens[2][$i] != $items[0]) { + continue; + } + $data = $tokens[2][$i + 1]; + break; + } + } + } else { + $data = []; + while (key($tokens[2]) !== null) { + $data[current($tokens[2])] = next($tokens[2]); + next($tokens[2]); + } + } + + // if we want only one message we can ignore everything else and just return + if ($to === null && ! is_array($from) && ($uid ? $tokens[2][$uidKey] == $from : $tokens[0] == $from)) { + // we still need to read all lines + while (! $this->readLine($tokens, $tag)) { + } + return $data; + } + $result[$tokens[0]] = $data; + } + + if ($to === null && ! is_array($from)) { + throw new Exception\RuntimeException('the single id was not found in response'); + } + + return $result; + } + + /** + * get mailbox list + * + * this method can't be named after the IMAP command 'LIST', as list is a reserved keyword + * + * @param string $reference mailbox reference for list + * @param string $mailbox mailbox name match with wildcards + * @return array mailboxes that matched $mailbox as array(globalName => array('delim' => .., 'flags' => ..)) + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + */ + public function listMailbox($reference = '', $mailbox = '*') + { + $result = []; + $list = $this->requestAndResponse('LIST', $this->escapeString($reference, $mailbox)); + if (! $list || $list === true) { + return $result; + } + + foreach ($list as $item) { + if (count($item) != 4 || $item[0] != 'LIST') { + continue; + } + $result[$item[3]] = ['delim' => $item[2], 'flags' => $item[1]]; + } + + return $result; + } + + /** + * set flags + * + * @param array $flags flags to set, add or remove - see $mode + * @param int $from message for items or start message if $to !== null + * @param int|null $to if null only one message ($from) is fetched, else it's the + * last message, INF means last message available + * @param string|null $mode '+' to add flags, '-' to remove flags, everything else sets the flags as given + * @param bool $silent if false the return values are the new flags for the wanted messages + * @return bool|array new flags if $silent is false, else true or false depending on success + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + */ + public function store(array $flags, $from, $to = null, $mode = null, $silent = true) + { + $item = 'FLAGS'; + if ($mode == '+' || $mode == '-') { + $item = $mode . $item; + } + if ($silent) { + $item .= '.SILENT'; + } + + $flags = $this->escapeList($flags); + $set = (int) $from; + if ($to !== null) { + $set .= ':' . ($to == INF ? '*' : (int) $to); + } + + $result = $this->requestAndResponse('STORE', [$set, $item, $flags], $silent); + + if ($silent) { + return (bool) $result; + } + + $tokens = $result; + $result = []; + foreach ($tokens as $token) { + if ($token[1] != 'FETCH' || $token[2][0] != 'FLAGS') { + continue; + } + $result[$token[0]] = $token[2][1]; + } + + return $result; + } + + /** + * append a new message to given folder + * + * @param string $folder name of target folder + * @param string $message full message content + * @param array $flags flags for new message + * @param string $date date for new message + * @return bool success + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + */ + public function append($folder, $message, $flags = null, $date = null) + { + $tokens = []; + $tokens[] = $this->escapeString($folder); + if ($flags !== null) { + $tokens[] = $this->escapeList($flags); + } + if ($date !== null) { + $tokens[] = $this->escapeString($date); + } + $tokens[] = $this->escapeString($message); + + return $this->requestAndResponse('APPEND', $tokens, true); + } + + /** + * copy message set from current folder to other folder + * + * @param string $folder destination folder + * @param $from + * @param int|null $to if null only one message ($from) is fetched, else it's the + * last message, INF means last message available + * @return bool success + */ + public function copy($folder, $from, $to = null) + { + $set = (int) $from; + if ($to !== null) { + $set .= ':' . ($to == INF ? '*' : (int) $to); + } + + return $this->requestAndResponse('COPY', [$set, $this->escapeString($folder)], true); + } + + /** + * create a new folder (and parent folders if needed) + * + * @param string $folder folder name + * @return bool success + */ + public function create($folder) + { + return $this->requestAndResponse('CREATE', [$this->escapeString($folder)], true); + } + + /** + * rename an existing folder + * + * @param string $old old name + * @param string $new new name + * @return bool success + */ + public function rename($old, $new) + { + return $this->requestAndResponse('RENAME', $this->escapeString($old, $new), true); + } + + /** + * remove a folder + * + * @param string $folder folder name + * @return bool success + */ + public function delete($folder) + { + return $this->requestAndResponse('DELETE', [$this->escapeString($folder)], true); + } + + /** + * subscribe to a folder + * + * @param string $folder folder name + * @return bool success + */ + public function subscribe($folder) + { + return $this->requestAndResponse('SUBSCRIBE', [$this->escapeString($folder)], true); + } + + /** + * permanently remove messages + * + * @return bool success + */ + public function expunge() + { + // TODO: parse response? + return $this->requestAndResponse('EXPUNGE'); + } + + /** + * send noop + * + * @return bool success + */ + public function noop() + { + // TODO: parse response + return $this->requestAndResponse('NOOP'); + } + + /** + * do a search request + * + * This method is currently marked as internal as the API might change and is not + * safe if you don't take precautions. + * + * @param array $params + * @return array message ids + */ + public function search(array $params) + { + $response = $this->requestAndResponse('SEARCH', $params); + if (! $response) { + return $response; + } + + foreach ($response as $ids) { + if ($ids[0] == 'SEARCH') { + array_shift($ids); + return $ids; + } + } + return []; + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/Pop3.php b/lib/laminas/laminas-mail/src/Protocol/Pop3.php new file mode 100644 index 0000000000..8675491a22 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/Pop3.php @@ -0,0 +1,389 @@ +setNoValidateCert($novalidatecert); + + if ($host) { + $this->connect($host, $port, $ssl); + } + } + + /** + * Public destructor + */ + public function __destruct() + { + $this->logout(); + } + + /** + * Open connection to POP3 server + * + * @param string $host hostname or IP address of POP3 server + * @param int|null $port of POP3 server, default is 110 (995 for ssl) + * @param string|bool $ssl use 'SSL', 'TLS' or false + * @throws Exception\RuntimeException + * @return string welcome message + */ + public function connect($host, $port = null, $ssl = false) + { + $transport = 'tcp'; + $isTls = false; + + if ($ssl) { + $ssl = strtolower($ssl); + } + + switch ($ssl) { + case 'ssl': + $transport = 'ssl'; + if (! $port) { + $port = 995; + } + break; + case 'tls': + $isTls = true; + // break intentionally omitted + default: + if (! $port) { + $port = 110; + } + } + + $this->socket = $this->setupSocket($transport, $host, $port, self::TIMEOUT_CONNECTION); + + $welcome = $this->readResponse(); + + strtok($welcome, '<'); + $this->timestamp = strtok('>'); + if (! strpos($this->timestamp, '@')) { + $this->timestamp = null; + } else { + $this->timestamp = '<' . $this->timestamp . '>'; + } + + if ($isTls) { + $this->request('STLS'); + $result = stream_socket_enable_crypto($this->socket, true, $this->getCryptoMethod()); + if (! $result) { + throw new Exception\RuntimeException('cannot enable TLS'); + } + } + + return $welcome; + } + + /** + * Send a request + * + * @param string $request your request without newline + * @throws Exception\RuntimeException + */ + public function sendRequest($request) + { + ErrorHandler::start(); + $result = fwrite($this->socket, $request . "\r\n"); + $error = ErrorHandler::stop(); + if (! $result) { + throw new Exception\RuntimeException('send failed - connection closed?', 0, $error); + } + } + + /** + * read a response + * + * @param bool $multiline response has multiple lines and should be read until "." + * @throws Exception\RuntimeException + * @return string response + */ + public function readResponse($multiline = false) + { + ErrorHandler::start(); + $result = fgets($this->socket); + $error = ErrorHandler::stop(); + if (! is_string($result)) { + throw new Exception\RuntimeException('read failed - connection closed?', 0, $error); + } + + $result = trim($result); + if (strpos($result, ' ')) { + list($status, $message) = explode(' ', $result, 2); + } else { + $status = $result; + $message = ''; + } + + if ($status != '+OK') { + throw new Exception\RuntimeException('last request failed'); + } + + if ($multiline) { + $message = ''; + $line = fgets($this->socket); + while ($line && rtrim($line, "\r\n") != '.') { + if ($line[0] == '.') { + $line = substr($line, 1); + } + $message .= $line; + $line = fgets($this->socket); + } + } + + return $message; + } + + /** + * Send request and get response + * + * @see sendRequest() + * @see readResponse() + * @param string $request request + * @param bool $multiline multiline response? + * @return string result from readResponse() + */ + public function request($request, $multiline = false) + { + $this->sendRequest($request); + return $this->readResponse($multiline); + } + + /** + * End communication with POP3 server (also closes socket) + */ + public function logout() + { + if ($this->socket) { + try { + $this->request('QUIT'); + } catch (Exception\ExceptionInterface $e) { + // ignore error - we're closing the socket anyway + } + + fclose($this->socket); + $this->socket = null; + } + } + + /** + * Get capabilities from POP3 server + * + * @return array list of capabilities + */ + public function capa() + { + $result = $this->request('CAPA', true); + return explode("\n", $result); + } + + /** + * Login to POP3 server. Can use APOP + * + * @param string $user username + * @param string $password password + * @param bool $tryApop should APOP be tried? + */ + public function login($user, $password, $tryApop = true) + { + if ($tryApop && $this->timestamp) { + try { + $this->request("APOP $user " . md5($this->timestamp . $password)); + return; + } catch (Exception\ExceptionInterface $e) { + // ignore + } + } + + $this->request("USER $user"); + $this->request("PASS $password"); + } + + /** + * Make STAT call for message count and size sum + * + * @param int $messages out parameter with count of messages + * @param int $octets out parameter with size in octets of messages + */ + public function status(&$messages, &$octets) + { + $messages = 0; + $octets = 0; + $result = $this->request('STAT'); + + list($messages, $octets) = explode(' ', $result); + } + + /** + * Make LIST call for size of message(s) + * + * @param int|null $msgno number of message, null for all + * @return int|array size of given message or list with array(num => size) + */ + public function getList($msgno = null) + { + if ($msgno !== null) { + $result = $this->request("LIST $msgno"); + + list(, $result) = explode(' ', $result); + return (int) $result; + } + + $result = $this->request('LIST', true); + $messages = []; + $line = strtok($result, "\n"); + while ($line) { + list($no, $size) = explode(' ', trim($line)); + $messages[(int) $no] = (int) $size; + $line = strtok("\n"); + } + + return $messages; + } + + /** + * Make UIDL call for getting a uniqueid + * + * @param int|null $msgno number of message, null for all + * @return string|array uniqueid of message or list with array(num => uniqueid) + */ + public function uniqueid($msgno = null) + { + if ($msgno !== null) { + $result = $this->request("UIDL $msgno"); + + list(, $result) = explode(' ', $result); + return $result; + } + + $result = $this->request('UIDL', true); + + $result = explode("\n", $result); + $messages = []; + foreach ($result as $line) { + if (! $line) { + continue; + } + list($no, $id) = explode(' ', trim($line), 2); + $messages[(int) $no] = $id; + } + + return $messages; + } + + /** + * Make TOP call for getting headers and maybe some body lines + * This method also sets hasTop - before it it's not known if top is supported + * + * The fallback makes normal RETR call, which retrieves the whole message. Additional + * lines are not removed. + * + * @param int $msgno number of message + * @param int $lines number of wanted body lines (empty line is inserted after header lines) + * @param bool $fallback fallback with full retrieve if top is not supported + * @throws Exception\RuntimeException + * @throws Exception\ExceptionInterface + * @return string message headers with wanted body lines + */ + public function top($msgno, $lines = 0, $fallback = false) + { + if ($this->hasTop === false) { + if ($fallback) { + return $this->retrieve($msgno); + } + + throw new Exception\RuntimeException('top not supported and no fallback wanted'); + } + $this->hasTop = true; + + $lines = (! $lines || $lines < 1) ? 0 : (int) $lines; + + try { + $result = $this->request("TOP $msgno $lines", true); + } catch (Exception\ExceptionInterface $e) { + $this->hasTop = false; + if ($fallback) { + $result = $this->retrieve($msgno); + } else { + throw $e; + } + } + + return $result; + } + + /** + * Make a RETR call for retrieving a full message with headers and body + * + * @param int $msgno message number + * @return string message + */ + public function retrieve($msgno) + { + $result = $this->request("RETR $msgno", true); + return $result; + } + + /** + * Make a NOOP call, maybe needed for keeping the server happy + */ + public function noop() + { + $this->request('NOOP'); + } + + /** + * Make a DELE count to remove a message + * + * @param $msgno + */ + public function delete($msgno) + { + $this->request("DELE $msgno"); + } + + /** + * Make RSET call, which rollbacks delete requests + */ + public function undelete() + { + $this->request('RSET'); + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/ProtocolTrait.php b/lib/laminas/laminas-mail/src/Protocol/ProtocolTrait.php new file mode 100644 index 0000000000..a562a9e914 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/ProtocolTrait.php @@ -0,0 +1,112 @@ +novalidatecert = $novalidatecert; + return $this; + } + + /** + * Should we validate SSL certificate? + * + * @return bool + */ + public function validateCert(): bool + { + return ! $this->novalidatecert; + } + + /** + * Prepare socket options + * + * @return array + */ + private function prepareSocketOptions(): array + { + return $this->novalidatecert + ? [ + 'ssl' => [ + 'verify_peer_name' => false, + 'verify_peer' => false, + ], + ] + : []; + } + + /** + * Setup connection socket + * + * @param string $host hostname or IP address of IMAP server + * @param int|null $port of IMAP server, default is 143 (993 for ssl) + * @param int $timeout timeout in seconds for initiating session + * @return resource The socket created. + * @throws Exception\RuntimeException If unable to connect to host. + */ + protected function setupSocket( + string $transport, + string $host, + ?int $port, + int $timeout + ) { + ErrorHandler::start(); + $socket = stream_socket_client( + sprintf('%s://%s:%d', $transport, $host, $port), + $errno, + $errstr, + $timeout, + STREAM_CLIENT_CONNECT, + stream_context_create($this->prepareSocketOptions()) + ); + $error = ErrorHandler::stop(); + + if (! $socket) { + throw new Exception\RuntimeException(sprintf( + 'cannot connect to host%s', + $error ? sprintf('; error = %s (errno = %d )', $error->getMessage(), $error->getCode()) : '' + ), 0, $error); + } + + if (false === stream_set_timeout($socket, $timeout)) { + throw new Exception\RuntimeException('Could not set stream timeout'); + } + + return $socket; + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/Smtp.php b/lib/laminas/laminas-mail/src/Protocol/Smtp.php new file mode 100644 index 0000000000..0bbe3ee45d --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/Smtp.php @@ -0,0 +1,514 @@ +secure = 'tls'; + break; + + case 'ssl': + $this->transport = 'ssl'; + $this->secure = 'ssl'; + if ($port === null) { + $port = 465; + } + break; + + case '': + // fall-through + case 'none': + break; + + default: + throw new Exception\InvalidArgumentException($config['ssl'] . ' is unsupported SSL type'); + } + } + + if (array_key_exists('use_complete_quit', $config)) { + $this->setUseCompleteQuit($config['use_complete_quit']); + } + + // If no port has been specified then check the master PHP ini file. Defaults to 25 if the ini setting is null. + if ($port === null) { + if (($port = ini_get('smtp_port')) == '') { + $port = 25; + } + } + + if (array_key_exists('novalidatecert', $config)) { + $this->setNoValidateCert($config['novalidatecert']); + } + + parent::__construct($host, $port); + } + + /** + * Set whether or not send QUIT command + * + * @param bool $useCompleteQuit use complete quit + * @return bool + */ + public function setUseCompleteQuit($useCompleteQuit) + { + return $this->useCompleteQuit = (bool) $useCompleteQuit; + } + + /** + * Read $data as lines terminated by "\n" + * + * @param string $data + * @param int $chunkSize + * @return Generator|string[] + * @author Elan Ruusamäe + */ + private static function chunkedReader(string $data, int $chunkSize = 4096): Generator + { + if (($fp = fopen("php://temp", "r+")) === false) { + throw new Exception\RuntimeException('cannot fopen'); + } + if (fwrite($fp, $data) === false) { + throw new Exception\RuntimeException('cannot fwrite'); + } + rewind($fp); + + $line = null; + while (($buffer = fgets($fp, $chunkSize)) !== false) { + $line .= $buffer; + + // This is optimization to avoid calling length() in a loop. + // We need to match a condition that is when: + // 1. maximum was read from fgets, which is $chunkSize-1 + // 2. last byte of the buffer is not \n + // + // to access last byte of buffer, we can do + // - $buffer[strlen($buffer)-1] + // and when maximum is read from fgets, then: + // - strlen($buffer) === $chunkSize-1 + // - strlen($buffer)-1 === $chunkSize-2 + // which means this is also true: + // - $buffer[strlen($buffer)-1] === $buffer[$chunkSize-2] + // + // the null coalesce works, as string offset can never be null + $lastByte = $buffer[$chunkSize - 2] ?? null; + + // partial read, continue loop to read again to complete the line + // compare \n first as that's usually false + if ($lastByte !== "\n" && $lastByte !== null) { + continue; + } + + yield $line; + $line = null; + } + + if ($line !== null) { + yield $line; + } + + fclose($fp); + } + + /** + * Whether or not send QUIT command + * + * @return bool + */ + public function useCompleteQuit() + { + return $this->useCompleteQuit; + } + + /** + * Connect to the server with the parameters given in the constructor. + * + * @return bool + */ + public function connect() + { + $this->socket = $this->setupSocket( + $this->transport, + $this->host, + $this->port, + self::TIMEOUT_CONNECTION + ); + return true; + } + + /** + * Initiate HELO/EHLO sequence and set flag to indicate valid smtp session + * + * @param string $host The client hostname or IP address (default: 127.0.0.1) + * @throws Exception\RuntimeException + */ + public function helo($host = '127.0.0.1') + { + // Respect RFC 2821 and disallow HELO attempts if session is already initiated. + if ($this->sess === true) { + throw new Exception\RuntimeException('Cannot issue HELO to existing session'); + } + + // Validate client hostname + if (! $this->validHost->isValid($host)) { + throw new Exception\RuntimeException(implode(', ', $this->validHost->getMessages())); + } + + // Initiate helo sequence + $this->_expect(220, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + $this->ehlo($host); + + // If a TLS session is required, commence negotiation + if ($this->secure == 'tls') { + $this->_send('STARTTLS'); + $this->_expect(220, 180); + if (! stream_socket_enable_crypto($this->socket, true, $this->getCryptoMethod())) { + throw new Exception\RuntimeException('Unable to connect via TLS'); + } + $this->ehlo($host); + } + + $this->startSession(); + $this->auth(); + } + + /** + * Returns the perceived session status + * + * @return bool + */ + public function hasSession() + { + return $this->sess; + } + + /** + * Send EHLO or HELO depending on capabilities of smtp host + * + * @param string $host The client hostname or IP address (default: 127.0.0.1) + * @throws \Exception|Exception\ExceptionInterface + */ + protected function ehlo($host) + { + // Support for older, less-compliant remote servers. Tries multiple attempts of EHLO or HELO. + try { + $this->_send('EHLO ' . $host); + $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + } catch (Exception\ExceptionInterface $e) { + $this->_send('HELO ' . $host); + $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + } + } + + /** + * Issues MAIL command + * + * @param string $from Sender mailbox + * @throws Exception\RuntimeException + */ + public function mail($from) + { + if ($this->sess !== true) { + throw new Exception\RuntimeException('A valid session has not been started'); + } + + $this->_send('MAIL FROM:<' . $from . '>'); + $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + + // Set mail to true, clear recipients and any existing data flags as per 4.1.1.2 of RFC 2821 + $this->mail = true; + $this->rcpt = false; + $this->data = false; + } + + /** + * Issues RCPT command + * + * @param string $to Receiver(s) mailbox + * @throws Exception\RuntimeException + */ + public function rcpt($to) + { + if ($this->mail !== true) { + throw new Exception\RuntimeException('No sender reverse path has been supplied'); + } + + // Set rcpt to true, as per 4.1.1.3 of RFC 2821 + $this->_send('RCPT TO:<' . $to . '>'); + $this->_expect([250, 251], 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + $this->rcpt = true; + } + + /** + * Issues DATA command + * + * @param string $data + * @throws Exception\RuntimeException + */ + public function data($data) + { + // Ensure recipients have been set + if ($this->rcpt !== true) { // Per RFC 2821 3.3 (page 18) + throw new Exception\RuntimeException('No recipient forward path has been supplied'); + } + + $this->_send('DATA'); + $this->_expect(354, 120); // Timeout set for 2 minutes as per RFC 2821 4.5.3.2 + + $reader = self::chunkedReader($data); + foreach ($reader as $line) { + $line = rtrim($line, "\r\n"); + if (isset($line[0]) && $line[0] === '.') { + // Escape lines prefixed with a '.' + $line = '.' . $line; + } + + if (strlen($line) > self::SMTP_LINE_LIMIT) { + // Long lines are "folded" by inserting "" + // https://tools.ietf.org/html/rfc5322#section-2.2.3 + // Add "-1" to stay within limits, + // because Headers::FOLDING includes a byte for space character after \r\n + $chunks = chunk_split($line, self::SMTP_LINE_LIMIT - 1, Headers::FOLDING); + $line = substr($chunks, 0, -strlen(Headers::FOLDING)); + } + + $this->_send($line); + } + + $this->_send('.'); + $this->_expect(250, 600); // Timeout set for 10 minutes as per RFC 2821 4.5.3.2 + $this->data = true; + } + + /** + * Issues the RSET command end validates answer + * + * Can be used to restore a clean smtp communication state when a + * transaction has been cancelled or commencing a new transaction. + */ + public function rset() + { + $this->_send('RSET'); + // MS ESMTP doesn't follow RFC, see https://zendframework.com/issues/browse/ZF-1377 + $this->_expect([250, 220]); + + $this->mail = false; + $this->rcpt = false; + $this->data = false; + } + + /** + * Issues the NOOP command end validates answer + * + * Not used by Laminas\Mail, could be used to keep a connection alive or check if it is still open. + * + */ + public function noop() + { + $this->_send('NOOP'); + $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + } + + /** + * Issues the VRFY command end validates answer + * + * Not used by Laminas\Mail. + * + * @param string $user User Name or eMail to verify + */ + public function vrfy($user) + { + $this->_send('VRFY ' . $user); + $this->_expect([250, 251, 252], 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + } + + /** + * Issues the QUIT command and clears the current session + * + */ + public function quit() + { + if ($this->sess) { + $this->auth = false; + + if ($this->useCompleteQuit()) { + $this->_send('QUIT'); + $this->_expect(221, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2 + } + + $this->stopSession(); + } + } + + /** + * Default authentication method + * + * This default method is implemented by AUTH adapters to properly authenticate to a remote host. + * + * @throws Exception\RuntimeException + */ + public function auth() + { + if ($this->auth === true) { + throw new Exception\RuntimeException('Already authenticated for this session'); + } + } + + /** + * Closes connection + * + */ + public function disconnect() + { + $this->_disconnect(); + } + + /** + * Disconnect from remote host and free resource + */ + // @codingStandardsIgnoreLine PSR2.Methods.MethodDeclaration.Underscore + protected function _disconnect() + { + + // Make sure the session gets closed + $this->quit(); + parent::_disconnect(); + } + + /** + * Start mail session + * + */ + protected function startSession() + { + $this->sess = true; + } + + /** + * Stop mail session + * + */ + protected function stopSession() + { + $this->sess = false; + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Crammd5.php b/lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Crammd5.php new file mode 100644 index 0000000000..e5e2c7b320 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Crammd5.php @@ -0,0 +1,132 @@ +setUsername($config['username']); + } + if (isset($config['password'])) { + $this->setPassword($config['password']); + } + } + + // Call parent with original arguments + parent::__construct($host, $port, $origConfig); + } + + /** + * Performs CRAM-MD5 authentication with supplied credentials + */ + public function auth() + { + // Ensure AUTH has not already been initiated. + parent::auth(); + + $this->_send('AUTH CRAM-MD5'); + $challenge = $this->_expect(334); + $challenge = base64_decode($challenge); + $digest = $this->hmacMd5($this->getPassword(), $challenge); + $this->_send(base64_encode($this->getUsername() . ' ' . $digest)); + $this->_expect(235); + $this->auth = true; + } + + /** + * Set value for username + * + * @param string $username + * @return Crammd5 + */ + public function setUsername($username) + { + $this->username = $username; + return $this; + } + + /** + * Get username + * + * @return string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Set value for password + * + * @param string $password + * @return Crammd5 + */ + public function setPassword($password) + { + $this->password = $password; + return $this; + } + + /** + * Get password + * + * @return string + */ + public function getPassword() + { + return $this->password; + } + + /** + * Prepare CRAM-MD5 response to server's ticket + * + * @param string $key Challenge key (usually password) + * @param string $data Challenge data + * @param int $block Length of blocks (deprecated; unused) + * @return string + */ + protected function hmacMd5($key, $data, $block = 64) + { + return Hmac::compute($key, 'md5', $data); + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Login.php b/lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Login.php new file mode 100644 index 0000000000..66225d4a5f --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Login.php @@ -0,0 +1,120 @@ +setUsername($config['username']); + } + if (isset($config['password'])) { + $this->setPassword($config['password']); + } + } + + // Call parent with original arguments + parent::__construct($host, $port, $origConfig); + } + + /** + * Perform LOGIN authentication with supplied credentials + * + */ + public function auth() + { + // Ensure AUTH has not already been initiated. + parent::auth(); + + $this->_send('AUTH LOGIN'); + $this->_expect(334); + $this->_send(base64_encode($this->getUsername())); + $this->_expect(334); + $this->_send(base64_encode($this->getPassword())); + $this->_expect(235); + $this->auth = true; + } + + /** + * Set value for username + * + * @param string $username + * @return Login + */ + public function setUsername($username) + { + $this->username = $username; + return $this; + } + + /** + * Get username + * + * @return string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Set value for password + * + * @param string $password + * @return Login + */ + public function setPassword($password) + { + $this->password = $password; + return $this; + } + + /** + * Get password + * + * @return string + */ + public function getPassword() + { + return $this->password; + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Plain.php b/lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Plain.php new file mode 100644 index 0000000000..0bd3c8a652 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/Smtp/Auth/Plain.php @@ -0,0 +1,118 @@ +setUsername($config['username']); + } + if (isset($config['password'])) { + $this->setPassword($config['password']); + } + } + + // Call parent with original arguments + parent::__construct($host, $port, $origConfig); + } + + /** + * Perform PLAIN authentication with supplied credentials + * + */ + public function auth() + { + // Ensure AUTH has not already been initiated. + parent::auth(); + + $this->_send('AUTH PLAIN'); + $this->_expect(334); + $this->_send(base64_encode("\0" . $this->getUsername() . "\0" . $this->getPassword())); + $this->_expect(235); + $this->auth = true; + } + + /** + * Set value for username + * + * @param string $username + * @return Plain + */ + public function setUsername($username) + { + $this->username = $username; + return $this; + } + + /** + * Get username + * + * @return string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Set value for password + * + * @param string $password + * @return Plain + */ + public function setPassword($password) + { + $this->password = $password; + return $this; + } + + /** + * Get password + * + * @return string + */ + public function getPassword() + { + return $this->password; + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/SmtpPluginManager.php b/lib/laminas/laminas-mail/src/Protocol/SmtpPluginManager.php new file mode 100644 index 0000000000..91c35dfa7e --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/SmtpPluginManager.php @@ -0,0 +1,108 @@ + Smtp\Auth\Crammd5::class, + 'cramMd5' => Smtp\Auth\Crammd5::class, + 'CramMd5' => Smtp\Auth\Crammd5::class, + 'cramMD5' => Smtp\Auth\Crammd5::class, + 'CramMD5' => Smtp\Auth\Crammd5::class, + 'login' => Smtp\Auth\Login::class, + 'Login' => Smtp\Auth\Login::class, + 'plain' => Smtp\Auth\Plain::class, + 'Plain' => Smtp\Auth\Plain::class, + 'smtp' => Smtp::class, + 'Smtp' => Smtp::class, + 'SMTP' => Smtp::class, + + // Legacy Zend Framework aliases + \Zend\Mail\Protocol\Smtp\Auth\Crammd5::class => Smtp\Auth\Crammd5::class, + \Zend\Mail\Protocol\Smtp\Auth\Login::class => Smtp\Auth\Login::class, + \Zend\Mail\Protocol\Smtp\Auth\Plain::class => Smtp\Auth\Plain::class, + \Zend\Mail\Protocol\Smtp::class => Smtp::class, + + // v2 normalized FQCNs + 'zendmailprotocolsmtpauthcrammd5' => Smtp\Auth\Crammd5::class, + 'zendmailprotocolsmtpauthlogin' => Smtp\Auth\Login::class, + 'zendmailprotocolsmtpauthplain' => Smtp\Auth\Plain::class, + 'zendmailprotocolsmtp' => Smtp::class, + ]; + + /** + * Service factories + * + * @var array + */ + protected $factories = [ + Smtp\Auth\Crammd5::class => InvokableFactory::class, + Smtp\Auth\Login::class => InvokableFactory::class, + Smtp\Auth\Plain::class => InvokableFactory::class, + Smtp::class => InvokableFactory::class, + + // v2 normalized service names + + 'laminasmailprotocolsmtpauthcrammd5' => InvokableFactory::class, + 'laminasmailprotocolsmtpauthlogin' => InvokableFactory::class, + 'laminasmailprotocolsmtpauthplain' => InvokableFactory::class, + 'laminasmailprotocolsmtp' => InvokableFactory::class, + ]; + + /** + * Plugins must be an instance of the Smtp class + * + * @var string + */ + protected $instanceOf = Smtp::class; + + /** + * Validate a retrieved plugin instance (v3). + * + * @param object|array $instance + * @throws InvalidServiceException + */ + public function validate($instance) + { + if (! $instance instanceof $this->instanceOf) { + throw new InvalidServiceException(sprintf( + 'Plugin of type %s is invalid; must extend %s', + (is_object($instance) ? get_class($instance) : gettype($instance)), + $this->instanceOf + )); + } + } + + /** + * Validate a retrieved plugin instance (v2). + * + * @param object $plugin + * @throws Exception\InvalidArgumentException + */ + public function validatePlugin($plugin) + { + try { + $this->validate($plugin); + } catch (InvalidServiceException $e) { + throw new Exception\InvalidArgumentException( + $e->getMessage(), + $e->getCode(), + $e + ); + } + } +} diff --git a/lib/laminas/laminas-mail/src/Protocol/SmtpPluginManagerFactory.php b/lib/laminas/laminas-mail/src/Protocol/SmtpPluginManagerFactory.php new file mode 100644 index 0000000000..0b0555fcef --- /dev/null +++ b/lib/laminas/laminas-mail/src/Protocol/SmtpPluginManagerFactory.php @@ -0,0 +1,48 @@ +creationOptions); + } + + /** + * laminas-servicemanager v2 support for invocation options. + * + * @param array $options + * @return void + */ + public function setCreationOptions(array $options) + { + $this->creationOptions = $options; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage.php b/lib/laminas/laminas-mail/src/Storage.php new file mode 100644 index 0000000000..c4f8d9fe32 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage.php @@ -0,0 +1,17 @@ + true, + 'delete' => false, + 'create' => false, + 'top' => false, + 'fetchPart' => true, + 'flags' => false, + ]; + + /** + * current iteration position + * @var int + */ + protected $iterationPos = 0; + + /** + * maximum iteration position (= message count) + * @var null|int + */ + protected $iterationMax = null; + + /** + * used message class, change it in an extended class to extend the returned message class + * @var string + */ + protected $messageClass = Message::class; + + /** + * Getter for has-properties. The standard has properties + * are: hasFolder, hasUniqueid, hasDelete, hasCreate, hasTop + * + * The valid values for the has-properties are: + * - true if a feature is supported + * - false if a feature is not supported + * - null is it's not yet known or it can't be know if a feature is supported + * + * @param string $var property name + * @throws Exception\InvalidArgumentException + * @return bool supported or not + */ + public function __get($var) + { + if (strpos($var, 'has') === 0) { + $var = strtolower(substr($var, 3)); + return isset($this->has[$var]) ? $this->has[$var] : null; + } + + throw new Exception\InvalidArgumentException($var . ' not found'); + } + + /** + * Get a full list of features supported by the specific mail lib and the server + * + * @return array list of features as array(feature_name => true|false[|null]) + */ + public function getCapabilities() + { + return $this->has; + } + + /** + * Count messages messages in current box/folder + * + * @return int number of messages + * @throws Exception\ExceptionInterface + */ + abstract public function countMessages(); + + /** + * Get a list of messages with number and size + * + * @param int $id number of message + * @return int|array size of given message of list with all messages as array(num => size) + */ + abstract public function getSize($id = 0); + + /** + * Get a message with headers and body + * + * @param $id int number of message + * @return Message + */ + abstract public function getMessage($id); + + /** + * Get raw header of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message header + * @param int $topLines include this many lines with header (after an empty line) + * @return string raw header + */ + abstract public function getRawHeader($id, $part = null, $topLines = 0); + + /** + * Get raw content of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message content + * @return string raw content + */ + abstract public function getRawContent($id, $part = null); + + /** + * Create instance with parameters + * + * @param array $params mail reader specific parameters + * @throws Exception\ExceptionInterface + */ + abstract public function __construct($params); + + /** + * Destructor calls close() and therefore closes the resource. + */ + public function __destruct() + { + $this->close(); + } + + /** + * Close resource for mail lib. If you need to control, when the resource + * is closed. Otherwise the destructor would call this. + */ + abstract public function close(); + + /** + * Keep the resource alive. + */ + abstract public function noop(); + + /** + * delete a message from current box/folder + * + * @param $id + */ + abstract public function removeMessage($id); + + /** + * get unique id for one or all messages + * + * if storage does not support unique ids it's the same as the message number + * + * @param int|null $id message number + * @return array|string message number for given message or all messages as array + * @throws Exception\ExceptionInterface + */ + abstract public function getUniqueId($id = null); + + /** + * get a message number from a unique id + * + * I.e. if you have a webmailer that supports deleting messages you should use unique ids + * as parameter and use this method to translate it to message number right before calling removeMessage() + * + * @param string $id unique id + * @return int message number + * @throws Exception\ExceptionInterface + */ + abstract public function getNumberByUniqueId($id); + + // interface implementations follows + + /** + * Countable::count() + * + * @return int + */ + #[ReturnTypeWillChange] + public function count() + { + return $this->countMessages(); + } + + /** + * ArrayAccess::offsetExists() + * + * @param int $id + * @return bool + */ + #[ReturnTypeWillChange] + public function offsetExists($id) + { + try { + if ($this->getMessage($id)) { + return true; + } + } catch (Exception\ExceptionInterface $e) { + } + + return false; + } + + /** + * ArrayAccess::offsetGet() + * + * @param int $id + * @return \Laminas\Mail\Storage\Message message object + */ + #[ReturnTypeWillChange] + public function offsetGet($id) + { + return $this->getMessage($id); + } + + /** + * ArrayAccess::offsetSet() + * + * @param mixed $id + * @param mixed $value + * @throws Exception\RuntimeException + */ + #[ReturnTypeWillChange] + public function offsetSet($id, $value) + { + throw new Exception\RuntimeException('cannot write mail messages via array access'); + } + + /** + * ArrayAccess::offsetUnset() + * + * @param int $id + * @return bool success + */ + #[ReturnTypeWillChange] + public function offsetUnset($id) + { + return $this->removeMessage($id); + } + + /** + * Iterator::rewind() + * + * Rewind always gets the new count from the storage. Thus if you use + * the interfaces and your scripts take long you should use reset() + * from time to time. + */ + #[ReturnTypeWillChange] + public function rewind() + { + $this->iterationMax = $this->countMessages(); + $this->iterationPos = 1; + } + + /** + * Iterator::current() + * + * @return Message current message + */ + #[ReturnTypeWillChange] + public function current() + { + return $this->getMessage($this->iterationPos); + } + + /** + * Iterator::key() + * + * @return int id of current position + */ + #[ReturnTypeWillChange] + public function key() + { + return $this->iterationPos; + } + + /** + * Iterator::next() + */ + #[ReturnTypeWillChange] + public function next() + { + ++$this->iterationPos; + } + + /** + * Iterator::valid() + * + * @return bool + */ + #[ReturnTypeWillChange] + public function valid() + { + if ($this->iterationMax === null) { + $this->iterationMax = $this->countMessages(); + } + return $this->iterationPos && $this->iterationPos <= $this->iterationMax; + } + + /** + * SeekableIterator::seek() + * + * @param int $pos + * @throws Exception\OutOfBoundsException + */ + #[ReturnTypeWillChange] + public function seek($pos) + { + if ($this->iterationMax === null) { + $this->iterationMax = $this->countMessages(); + } + + if ($pos > $this->iterationMax) { + throw new Exception\OutOfBoundsException('this position does not exist'); + } + $this->iterationPos = $pos; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Exception/ExceptionInterface.php b/lib/laminas/laminas-mail/src/Storage/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..856cd626aa --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Exception/ExceptionInterface.php @@ -0,0 +1,9 @@ + \Laminas\Mail\Storage\Folder folder) + * @var array + */ + protected $folders; + + /** + * local name (name of folder in parent folder) + * @var string + */ + protected $localName; + + /** + * global name (absolute name of folder) + * @var string + */ + protected $globalName; + + /** + * folder is selectable if folder is able to hold messages, otherwise it is a parent folder + * @var bool + */ + protected $selectable = true; + + /** + * create a new mail folder instance + * + * @param string $localName name of folder in current subdirectory + * @param string $globalName absolute name of folder + * @param bool $selectable if true folder holds messages, if false it's + * just a parent for subfolders (Default: true) + * @param array $folders init with given instances of Folder as subfolders + */ + public function __construct($localName, $globalName = '', $selectable = true, array $folders = []) + { + $this->localName = $localName; + $this->globalName = $globalName ? $globalName : $localName; + $this->selectable = $selectable; + $this->folders = $folders; + } + + /** + * implements RecursiveIterator::hasChildren() + * + * @return bool current element has children + */ + #[ReturnTypeWillChange] + public function hasChildren() + { + $current = $this->current(); + return $current && $current instanceof self && ! $current->isLeaf(); + } + + /** + * implements RecursiveIterator::getChildren() + * + * @return \Laminas\Mail\Storage\Folder same as self::current() + */ + #[ReturnTypeWillChange] + public function getChildren() + { + return $this->current(); + } + + /** + * implements Iterator::valid() + * + * @return bool check if there's a current element + */ + #[ReturnTypeWillChange] + public function valid() + { + return key($this->folders) !== null; + } + + /** + * implements Iterator::next() + */ + #[ReturnTypeWillChange] + public function next() + { + next($this->folders); + } + + /** + * implements Iterator::key() + * + * @return string key/local name of current element + */ + #[ReturnTypeWillChange] + public function key() + { + return key($this->folders); + } + + /** + * implements Iterator::current() + * + * @return \Laminas\Mail\Storage\Folder current folder + */ + #[ReturnTypeWillChange] + public function current() + { + return current($this->folders); + } + + /** + * implements Iterator::rewind() + */ + #[ReturnTypeWillChange] + public function rewind() + { + reset($this->folders); + } + + /** + * get subfolder named $name + * + * @param string $name wanted subfolder + * @throws Exception\InvalidArgumentException + * @return \Laminas\Mail\Storage\Folder folder named $folder + */ + public function __get($name) + { + if (! isset($this->folders[$name])) { + throw new Exception\InvalidArgumentException("no subfolder named $name"); + } + + return $this->folders[$name]; + } + + /** + * add or replace subfolder named $name + * + * @param string $name local name of subfolder + * @param \Laminas\Mail\Storage\Folder $folder instance for new subfolder + */ + public function __set($name, self $folder) + { + $this->folders[$name] = $folder; + } + + /** + * remove subfolder named $name + * + * @param string $name local name of subfolder + */ + public function __unset($name) + { + unset($this->folders[$name]); + } + + /** + * magic method for easy output of global name + * + * @return string global name of folder + */ + public function __toString() + { + return (string) $this->getGlobalName(); + } + + /** + * get local name + * + * @return string local name + */ + public function getLocalName() + { + return $this->localName; + } + + /** + * get global name + * + * @return string global name + */ + public function getGlobalName() + { + return $this->globalName; + } + + /** + * is this folder selectable? + * + * @return bool selectable + */ + public function isSelectable() + { + return $this->selectable; + } + + /** + * check if folder has no subfolder + * + * @return bool true if no subfolders + */ + public function isLeaf() + { + return empty($this->folders); + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Folder/FolderInterface.php b/lib/laminas/laminas-mail/src/Storage/Folder/FolderInterface.php new file mode 100644 index 0000000000..c26775b38b --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Folder/FolderInterface.php @@ -0,0 +1,32 @@ +rootdir = rtrim($dirname, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + + $delim = $params['delim'] ?? '.'; + $this->delim = (string) $delim; + + $folder = $params['folder'] ?? 'INBOX'; + + $this->buildFolderTree(); + $this->selectFolder((string) $folder); + $this->has['top'] = true; + $this->has['flags'] = true; + } + + /** + * find all subfolders and mbox files for folder structure + * + * Result is save in Storage\Folder instances with the root in $this->rootFolder. + * $parentFolder and $parentGlobalName are only used internally for recursion. + * + * @throws Exception\RuntimeException + */ + protected function buildFolderTree() + { + $this->rootFolder = new Storage\Folder('/', '/', false); + $this->rootFolder->INBOX = new Storage\Folder('INBOX', 'INBOX', true); + + ErrorHandler::start(E_WARNING); + $dh = opendir($this->rootdir); + $error = ErrorHandler::stop(); + if (! $dh) { + throw new Exception\RuntimeException("can't read folders in maildir", 0, $error); + } + $dirs = []; + + while (($entry = readdir($dh)) !== false) { + // maildir++ defines folders must start with . + if ($entry[0] != '.' || $entry == '.' || $entry == '..') { + continue; + } + + if ($this->isMaildir($this->rootdir . $entry)) { + $dirs[] = $entry; + } + } + closedir($dh); + + sort($dirs); + $stack = [null]; + $folderStack = [null]; + $parentFolder = $this->rootFolder; + $parent = '.'; + + foreach ($dirs as $dir) { + do { + if (strpos($dir, $parent) === 0) { + $local = substr($dir, strlen($parent)); + if (strpos($local, $this->delim) !== false) { + throw new Exception\RuntimeException('error while reading maildir'); + } + array_push($stack, $parent); + $parent = $dir . $this->delim; + $folder = new Storage\Folder($local, substr($dir, 1), true); + $parentFolder->$local = $folder; + array_push($folderStack, $parentFolder); + $parentFolder = $folder; + break; + } elseif ($stack) { + $parent = array_pop($stack); + $parentFolder = array_pop($folderStack); + } + } while ($stack); + if (! $stack) { + throw new Exception\RuntimeException('error while reading maildir'); + } + } + } + + /** + * get root folder or given folder + * + * @param string $rootFolder get folder structure for given folder, else root + * @throws \Laminas\Mail\Storage\Exception\InvalidArgumentException + * @return \Laminas\Mail\Storage\Folder root or wanted folder + */ + public function getFolders($rootFolder = null) + { + if (! $rootFolder || $rootFolder == 'INBOX') { + return $this->rootFolder; + } + + // rootdir is same as INBOX in maildir + if (strpos($rootFolder, 'INBOX' . $this->delim) === 0) { + $rootFolder = substr($rootFolder, 6); + } + $currentFolder = $this->rootFolder; + $subname = trim($rootFolder, $this->delim); + + while ($currentFolder) { + if (false !== strpos($subname, $this->delim)) { + list($entry, $subname) = explode($this->delim, $subname, 2); + } else { + $entry = $subname; + $subname = null; + } + + $currentFolder = $currentFolder->$entry; + + if (! $subname) { + break; + } + } + + if ($currentFolder->getGlobalName() != rtrim($rootFolder, $this->delim)) { + throw new Exception\InvalidArgumentException("folder $rootFolder not found"); + } + return $currentFolder; + } + + /** + * select given folder + * + * folder must be selectable! + * + * @param Storage\Folder|string $globalName global name of folder or + * instance for subfolder + * @throws Exception\RuntimeException + */ + public function selectFolder($globalName) + { + $this->currentFolder = (string) $globalName; + + // getting folder from folder tree for validation + $folder = $this->getFolders($this->currentFolder); + + try { + $this->openMaildir($this->rootdir . '.' . $folder->getGlobalName()); + } catch (Exception\ExceptionInterface $e) { + // check what went wrong + if (! $folder->isSelectable()) { + throw new Exception\RuntimeException("{$this->currentFolder} is not selectable", 0, $e); + } + // seems like file has vanished; rebuilding folder tree - but it's still an exception + $this->buildFolderTree(); + throw new Exception\RuntimeException( + 'seems like the maildir has vanished; I have rebuilt the folder tree; ' + . 'search for another folder and try again', + 0, + $e + ); + } + } + + /** + * get Storage\Folder instance for current folder + * + * @return Storage\Folder instance of current folder + */ + public function getCurrentFolder() + { + return $this->currentFolder; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Folder/Mbox.php b/lib/laminas/laminas-mail/src/Storage/Folder/Mbox.php new file mode 100644 index 0000000000..8fed65d897 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Folder/Mbox.php @@ -0,0 +1,219 @@ +rootdir = rtrim($dirname, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + $folder = $params['folder'] ?? 'INBOX'; + + $this->buildFolderTree($this->rootdir); + $this->selectFolder((string) $folder); + $this->has['top'] = true; + $this->has['uniqueid'] = false; + } + + /** + * find all subfolders and mbox files for folder structure + * + * Result is save in Storage\Folder instances with the root in $this->rootFolder. + * $parentFolder and $parentGlobalName are only used internally for recursion. + * + * @param string $currentDir call with root dir, also used for recursion. + * @param Storage\Folder|null $parentFolder used for recursion + * @param string $parentGlobalName used for recursion + * @throws Exception\InvalidArgumentException + */ + protected function buildFolderTree($currentDir, $parentFolder = null, $parentGlobalName = '') + { + if (! $parentFolder) { + $this->rootFolder = new Storage\Folder('/', '/', false); + $parentFolder = $this->rootFolder; + } + + ErrorHandler::start(E_WARNING); + $dh = opendir($currentDir); + ErrorHandler::stop(); + if (! $dh) { + throw new Exception\InvalidArgumentException("can't read dir $currentDir"); + } + while (($entry = readdir($dh)) !== false) { + // ignore hidden files for mbox + if ($entry[0] == '.') { + continue; + } + $absoluteEntry = $currentDir . $entry; + $globalName = $parentGlobalName . DIRECTORY_SEPARATOR . $entry; + if (is_file($absoluteEntry) && $this->isMboxFile($absoluteEntry)) { + $parentFolder->$entry = new Storage\Folder($entry, $globalName); + continue; + } + if (! is_dir($absoluteEntry) /* || $entry == '.' || $entry == '..' */) { + continue; + } + $folder = new Storage\Folder($entry, $globalName, false); + $parentFolder->$entry = $folder; + $this->buildFolderTree($absoluteEntry . DIRECTORY_SEPARATOR, $folder, $globalName); + } + + closedir($dh); + } + + /** + * get root folder or given folder + * + * @param string $rootFolder get folder structure for given folder, else root + * @return Storage\Folder root or wanted folder + * @throws Exception\InvalidArgumentException + */ + public function getFolders($rootFolder = null) + { + if (! $rootFolder) { + return $this->rootFolder; + } + + $currentFolder = $this->rootFolder; + $subname = trim($rootFolder, DIRECTORY_SEPARATOR); + while ($currentFolder) { + if (false !== strpos($subname, DIRECTORY_SEPARATOR)) { + list($entry, $subname) = explode(DIRECTORY_SEPARATOR, $subname, 2); + } else { + $entry = $subname; + $subname = null; + } + + $currentFolder = $currentFolder->$entry; + + if (! $subname) { + break; + } + } + + if ($currentFolder->getGlobalName() != DIRECTORY_SEPARATOR . trim($rootFolder, DIRECTORY_SEPARATOR)) { + throw new Exception\InvalidArgumentException("folder $rootFolder not found"); + } + return $currentFolder; + } + + /** + * select given folder + * + * folder must be selectable! + * + * @param Storage\Folder|string $globalName global name of folder or + * instance for subfolder + * @throws Exception\RuntimeException + */ + public function selectFolder($globalName) + { + $this->currentFolder = (string) $globalName; + + // getting folder from folder tree for validation + $folder = $this->getFolders($this->currentFolder); + + try { + $this->openMboxFile($this->rootdir . $folder->getGlobalName()); + } catch (Exception\ExceptionInterface $e) { + // check what went wrong + if (! $folder->isSelectable()) { + throw new Exception\RuntimeException("{$this->currentFolder} is not selectable", 0, $e); + } + // seems like file has vanished; rebuilding folder tree - but it's still an exception + $this->buildFolderTree($this->rootdir); + throw new Exception\RuntimeException( + 'seems like the mbox file has vanished; I have rebuilt the folder tree; ' + . 'search for another folder and try again', + 0, + $e + ); + } + } + + /** + * get Storage\Folder instance for current folder + * + * @return Storage\Folder instance of current folder + * @throws Exception\ExceptionInterface + */ + public function getCurrentFolder() + { + return $this->currentFolder; + } + + /** + * magic method for serialize() + * + * with this method you can cache the mbox class + * + * @return array name of variables + */ + public function __sleep() + { + return array_merge(parent::__sleep(), ['currentFolder', 'rootFolder', 'rootdir']); + } + + /** + * magic method for unserialize(), with this method you can cache the mbox class + */ + public function __wakeup() + { + // if cache is stall selectFolder() rebuilds the tree on error + parent::__wakeup(); + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Imap.php b/lib/laminas/laminas-mail/src/Storage/Imap.php new file mode 100644 index 0000000000..30196a5a79 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Imap.php @@ -0,0 +1,553 @@ + Mail\Storage::FLAG_PASSED, + '\Answered' => Mail\Storage::FLAG_ANSWERED, + '\Seen' => Mail\Storage::FLAG_SEEN, + '\Unseen' => Mail\Storage::FLAG_UNSEEN, + '\Deleted' => Mail\Storage::FLAG_DELETED, + '\Draft' => Mail\Storage::FLAG_DRAFT, + '\Flagged' => Mail\Storage::FLAG_FLAGGED, + ]; + + /** + * IMAP flags to search criteria + * @var array + */ + protected static $searchFlags = [ + '\Recent' => 'RECENT', + '\Answered' => 'ANSWERED', + '\Seen' => 'SEEN', + '\Unseen' => 'UNSEEN', + '\Deleted' => 'DELETED', + '\Draft' => 'DRAFT', + '\Flagged' => 'FLAGGED', + ]; + + /** + * Count messages all messages in current box + * + * @param null $flags + * @throws Exception\RuntimeException + * @throws Protocol\Exception\RuntimeException + * @return int number of messages + */ + public function countMessages($flags = null) + { + if (! $this->currentFolder) { + throw new Exception\RuntimeException('No selected folder to count'); + } + + if ($flags === null) { + return count($this->protocol->search(['ALL'])); + } + + $params = []; + foreach ((array) $flags as $flag) { + if (isset(static::$searchFlags[$flag])) { + $params[] = static::$searchFlags[$flag]; + } else { + $params[] = 'KEYWORD'; + $params[] = $this->protocol->escapeString($flag); + } + } + return count($this->protocol->search($params)); + } + + /** + * get a list of messages with number and size + * + * @param int $id number of message + * @return int|array size of given message of list with all messages as [num => size] + * @throws Protocol\Exception\RuntimeException + */ + public function getSize($id = 0) + { + if ($id) { + return $this->protocol->fetch('RFC822.SIZE', $id); + } + return $this->protocol->fetch('RFC822.SIZE', 1, INF); + } + + /** + * Fetch a message + * + * @param int $id number of message + * @return Message + * @throws Protocol\Exception\RuntimeException + */ + public function getMessage($id) + { + $data = $this->protocol->fetch(['FLAGS', 'RFC822.HEADER'], $id); + $header = $data['RFC822.HEADER']; + + $flags = []; + foreach ($data['FLAGS'] as $flag) { + $flags[] = static::$knownFlags[$flag] ?? $flag; + } + + return new $this->messageClass(['handler' => $this, 'id' => $id, 'headers' => $header, 'flags' => $flags]); + } + + /* + * Get raw header of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message header + * @param int $topLines include this many lines with header (after an empty line) + * @param int $topLines include this many lines with header (after an empty line) + * @return string raw header + * @throws Exception\RuntimeException + * @throws Protocol\Exception\RuntimeException + */ + public function getRawHeader($id, $part = null, $topLines = 0) + { + if ($part !== null) { + // TODO: implement + throw new Exception\RuntimeException('not implemented'); + } + + // TODO: toplines + return $this->protocol->fetch('RFC822.HEADER', $id); + } + + /* + * Get raw content of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message content + * @return string raw content + * @throws Protocol\Exception\RuntimeException + * @throws Exception\RuntimeException + */ + public function getRawContent($id, $part = null) + { + if ($part !== null) { + // TODO: implement + throw new Exception\RuntimeException('not implemented'); + } + + return $this->protocol->fetch('RFC822.TEXT', $id); + } + + /** + * create instance with parameters + * + * Supported parameters are + * + * - user username + * - host hostname or ip address of IMAP server [optional, default = 'localhost'] + * - password password for user 'username' [optional, default = ''] + * - port port for IMAP server [optional, default = 110] + * - ssl 'SSL' or 'TLS' for secure sockets + * - folder select this folder [optional, default = 'INBOX'] + * + * @param array|object|Protocol\Imap $params mail reader specific + * parameters or configured Imap protocol object + * @throws Exception\RuntimeException + * @throws Exception\InvalidArgumentException + * @throws Protocol\Exception\RuntimeException + */ + public function __construct($params) + { + $this->has['flags'] = true; + + if ($params instanceof Protocol\Imap) { + $this->protocol = $params; + try { + $this->selectFolder('INBOX'); + } catch (Exception\ExceptionInterface $e) { + throw new Exception\RuntimeException('cannot select INBOX, is this a valid transport?', 0, $e); + } + return; + } + + $params = ParamsNormalizer::normalizeParams($params); + + if (! isset($params['user'])) { + throw new Exception\InvalidArgumentException('need at least user in params'); + } + + $host = $params['host'] ?? 'localhost'; + $password = $params['password'] ?? ''; + $port = $params['port'] ?? null; + $ssl = $params['ssl'] ?? false; + $folder = $params['folder'] ?? 'INBOX'; + + if (null !== $port) { + $port = (int) $port; + } + + if (! is_string($ssl)) { + $ssl = (bool) $ssl; + } + + $this->protocol = new Protocol\Imap(); + + if (array_key_exists('novalidatecert', $params)) { + $this->protocol->setNoValidateCert((bool) $params['novalidatecert']); + } + + $this->protocol->connect((string) $host, $port, $ssl); + if (! $this->protocol->login((string) $params['user'], (string) $password)) { + throw new Exception\RuntimeException('cannot login, user or password wrong'); + } + $this->selectFolder((string) $folder); + } + + /** + * Close resource for mail lib. + * + * If you need to control, when the resource is closed. Otherwise the + * destructor would call this. + */ + public function close() + { + $this->currentFolder = ''; + $this->protocol->logout(); + } + + /** + * Keep the server busy. + * + * @throws Exception\RuntimeException + */ + public function noop() + { + if (! $this->protocol->noop()) { + throw new Exception\RuntimeException('could not do nothing'); + } + } + + /** + * Remove a message from server. + * + * If you're doing that from a web environment you should be careful and + * use a uniqueid as parameter if possible to identify the message. + * + * @param int $id number of message + * @throws Exception\RuntimeException + */ + public function removeMessage($id) + { + if (! $this->protocol->store([Mail\Storage::FLAG_DELETED], $id, null, '+')) { + throw new Exception\RuntimeException('cannot set deleted flag'); + } + // TODO: expunge here or at close? we can handle an error here better and are more fail safe + if (! $this->protocol->expunge()) { + throw new Exception\RuntimeException('message marked as deleted, but could not expunge'); + } + } + + /** + * get unique id for one or all messages + * + * if storage does not support unique ids it's the same as the message + * number. + * + * @param int|null $id message number + * @return array|string message number for given message or all messages as array + * @throws Protocol\Exception\RuntimeException + */ + public function getUniqueId($id = null) + { + if ($id) { + return $this->protocol->fetch('UID', $id); + } + + return $this->protocol->fetch('UID', 1, INF); + } + + /** + * get a message number from a unique id + * + * I.e. if you have a webmailer that supports deleting messages you should + * use unique ids as parameter and use this method to translate it to + * message number right before calling removeMessage() + * + * @param string $id unique id + * @throws Exception\InvalidArgumentException + * @return int message number + */ + public function getNumberByUniqueId($id) + { + // TODO: use search to find number directly + $ids = $this->getUniqueId(); + foreach ($ids as $k => $v) { + if ($v == $id) { + return $k; + } + } + + throw new Exception\InvalidArgumentException('unique id not found'); + } + + /** + * get root folder or given folder + * + * @param string $rootFolder get folder structure for given folder, else root + * @throws Exception\RuntimeException + * @throws Exception\InvalidArgumentException + * @throws Protocol\Exception\RuntimeException + * @return Folder root or wanted folder + */ + public function getFolders($rootFolder = null) + { + $folders = $this->protocol->listMailbox((string) $rootFolder); + if (! $folders) { + throw new Exception\InvalidArgumentException('folder not found'); + } + + ksort($folders, SORT_STRING); + $root = new Folder('/', '/', false); + $stack = [null]; + $folderStack = [null]; + $parentFolder = $root; + $parent = ''; + + foreach ($folders as $globalName => $data) { + do { + if (! $parent || strpos($globalName, $parent) === 0) { + $pos = strrpos($globalName, $data['delim']); + if ($pos === false) { + $localName = $globalName; + } else { + $localName = substr($globalName, $pos + 1); + } + $selectable = ! $data['flags'] || ! in_array('\\Noselect', $data['flags']); + + array_push($stack, $parent); + $parent = $globalName . $data['delim']; + $folder = new Folder($localName, $globalName, $selectable); + $parentFolder->$localName = $folder; + array_push($folderStack, $parentFolder); + $parentFolder = $folder; + $this->delimiter = $data['delim']; + break; + } elseif ($stack) { + $parent = array_pop($stack); + $parentFolder = array_pop($folderStack); + } + } while ($stack); + if (! $stack) { + throw new Exception\RuntimeException('error while constructing folder tree'); + } + } + + return $root; + } + + /** + * select given folder + * + * folder must be selectable! + * + * @param Folder|string $globalName global name of folder or instance for subfolder + * @throws Exception\RuntimeException + * @throws Protocol\Exception\RuntimeException + */ + public function selectFolder($globalName) + { + $this->currentFolder = $globalName; + if (! $this->protocol->select($this->currentFolder)) { + $this->currentFolder = ''; + throw new Exception\RuntimeException('cannot change folder, maybe it does not exist'); + } + } + + /** + * get Folder instance for current folder + * + * @return Folder instance of current folder + */ + public function getCurrentFolder() + { + return $this->currentFolder; + } + + /** + * create a new folder + * + * This method also creates parent folders if necessary. Some mail storages + * may restrict, which folder may be used as parent or which chars may be + * used in the folder name + * + * @param string $name global name of folder, local name if $parentFolder + * is set + * @param string|Folder $parentFolder parent folder for new folder, else + * root folder is parent + * @throws Exception\RuntimeException + */ + public function createFolder($name, $parentFolder = null) + { + // TODO: we assume / as the hierarchy delim - need to get that from the folder class! + if ($parentFolder instanceof Folder) { + $folder = $parentFolder->getGlobalName() . '/' . $name; + } elseif ($parentFolder !== null) { + $folder = $parentFolder . '/' . $name; + } else { + $folder = $name; + } + + if (! $this->protocol->create($folder)) { + throw new Exception\RuntimeException('cannot create folder'); + } + } + + /** + * remove a folder + * + * @param string|Folder $name name or instance of folder + * @throws Exception\RuntimeException + */ + public function removeFolder($name) + { + if ($name instanceof Folder) { + $name = $name->getGlobalName(); + } + + if (! $this->protocol->delete($name)) { + throw new Exception\RuntimeException('cannot delete folder'); + } + } + + /** + * rename and/or move folder + * + * The new name has the same restrictions as in createFolder() + * + * @param string|Folder $oldName name or instance of folder + * @param string $newName new global name of folder + * @throws Exception\RuntimeException + */ + public function renameFolder($oldName, $newName) + { + if ($oldName instanceof Folder) { + $oldName = $oldName->getGlobalName(); + } + + if (! $this->protocol->rename($oldName, $newName)) { + throw new Exception\RuntimeException('cannot rename folder'); + } + } + + /** + * append a new message to mail storage + * + * @param string $message message as string or instance of message class + * @param null|string|Folder $folder folder for new message, else current + * folder is taken + * @param null|array $flags set flags for new message, else a default set + * is used + * @throws Exception\RuntimeException + */ + public function appendMessage($message, $folder = null, $flags = null) + { + if ($folder === null) { + $folder = $this->currentFolder; + } + + if ($flags === null) { + $flags = [Mail\Storage::FLAG_SEEN]; + } + + // TODO: handle class instances for $message + if (! $this->protocol->append($folder, $message, $flags)) { + throw new Exception\RuntimeException( + 'cannot create message, please check if the folder exists and your flags' + ); + } + } + + /** + * copy an existing message + * + * @param int $id number of message + * @param string|Folder $folder name or instance of target folder + * @throws Exception\RuntimeException + */ + public function copyMessage($id, $folder) + { + if (! $this->protocol->copy($folder, $id)) { + throw new Exception\RuntimeException('cannot copy message, does the folder exist?'); + } + } + + /** + * move an existing message + * + * NOTE: IMAP has no native move command, thus it's emulated with copy and delete + * + * @param int $id number of message + * @param string|Folder $folder name or instance of target folder + * @throws Exception\RuntimeException + */ + public function moveMessage($id, $folder) + { + $this->copyMessage($id, $folder); + $this->removeMessage($id); + } + + /** + * set flags for message + * + * NOTE: this method can't set the recent flag. + * + * @param int $id number of message + * @param array $flags new flags for message + * @throws Exception\RuntimeException + */ + public function setFlags($id, $flags) + { + if (! $this->protocol->store($flags, $id)) { + throw new Exception\RuntimeException( + 'cannot set flags, have you tried to set the recent flag or special chars?' + ); + } + } + + /** + * get IMAP delimiter + * + * @return string|null + */ + public function delimiter() + { + if (! isset($this->delimiter)) { + $this->getFolders(); + } + return $this->delimiter; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Maildir.php b/lib/laminas/laminas-mail/src/Storage/Maildir.php new file mode 100644 index 0000000000..ce1c8901b9 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Maildir.php @@ -0,0 +1,430 @@ + Mail\Storage::FLAG_DRAFT, + 'F' => Mail\Storage::FLAG_FLAGGED, + 'P' => Mail\Storage::FLAG_PASSED, + 'R' => Mail\Storage::FLAG_ANSWERED, + 'S' => Mail\Storage::FLAG_SEEN, + 'T' => Mail\Storage::FLAG_DELETED, + ]; + + // TODO: getFlags($id) for fast access if headers are not needed (i.e. just setting flags)? + + /** + * Count messages all messages in current box + * + * @param mixed $flags + * @return int number of messages + */ + public function countMessages($flags = null) + { + if ($flags === null) { + return count($this->files); + } + + $count = 0; + if (! is_array($flags)) { + foreach ($this->files as $file) { + if (isset($file['flaglookup'][$flags])) { + ++$count; + } + } + return $count; + } + + $flags = array_flip($flags); + foreach ($this->files as $file) { + foreach ($flags as $flag => $v) { + if (! isset($file['flaglookup'][$flag])) { + continue 2; + } + } + ++$count; + } + return $count; + } + + /** + * Get one or all fields from file structure. Also checks if message is valid + * + * @param int $id message number + * @param string|null $field wanted field + * @throws Exception\InvalidArgumentException + * @return string|array wanted field or all fields as array + */ + protected function getFileData($id, $field = null) + { + if (! isset($this->files[$id - 1])) { + throw new Exception\InvalidArgumentException('id does not exist'); + } + + if (! $field) { + return $this->files[$id - 1]; + } + + if (! isset($this->files[$id - 1][$field])) { + throw new Exception\InvalidArgumentException('field does not exist'); + } + + return $this->files[$id - 1][$field]; + } + + /** + * Get a list of messages with number and size + * + * @param int|null $id number of message or null for all messages + * @return int|array size of given message of list with all messages as array(num => size) + */ + public function getSize($id = null) + { + if ($id !== null) { + $filedata = $this->getFileData($id); + return $filedata['size'] ?? filesize($filedata['filename']); + } + + $result = []; + foreach ($this->files as $num => $data) { + $result[$num + 1] = $data['size'] ?? filesize($data['filename']); + } + + return $result; + } + + /** + * Fetch a message + * + * @param int $id number of message + * @return \Laminas\Mail\Storage\Message\File + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function getMessage($id) + { + // TODO that's ugly, would be better to let the message class decide + if (\trim($this->messageClass, '\\') === Message\File::class + || is_subclass_of($this->messageClass, Message\File::class) + ) { + return new $this->messageClass([ + 'file' => $this->getFileData($id, 'filename'), + 'flags' => $this->getFileData($id, 'flags'), + ]); + } + + return new $this->messageClass([ + 'handler' => $this, + 'id' => $id, + 'headers' => $this->getRawHeader($id), + 'flags' => $this->getFileData($id, 'flags'), + ]); + } + + /* + * Get raw header of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message header + * @param int $topLines include this many lines with header (after an empty line) + * @throws Exception\RuntimeException + * @return string raw header + */ + public function getRawHeader($id, $part = null, $topLines = 0) + { + if ($part !== null) { + // TODO: implement + throw new Exception\RuntimeException('not implemented'); + } + + $fh = fopen($this->getFileData($id, 'filename'), 'r'); + + $content = ''; + while (! feof($fh)) { + $line = fgets($fh); + if (! trim($line)) { + break; + } + $content .= $line; + } + + fclose($fh); + return $content; + } + + /* + * Get raw content of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message content + * @throws Exception\RuntimeException + * @return string raw content + */ + public function getRawContent($id, $part = null) + { + if ($part !== null) { + // TODO: implement + throw new Exception\RuntimeException('not implemented'); + } + + $fh = fopen($this->getFileData($id, 'filename'), 'r'); + + while (! feof($fh)) { + $line = fgets($fh); + if (! trim($line)) { + break; + } + } + + $content = stream_get_contents($fh); + fclose($fh); + return $content; + } + + /** + * Create instance with parameters + * Supported parameters are: + * - dirname dirname of mbox file + * + * @param $params array|object Array, iterable object, or stdClass object + * with reader specific parameters + * @throws Exception\InvalidArgumentException + */ + public function __construct($params) + { + $params = ParamsNormalizer::normalizeParams($params); + + if (! isset($params['dirname'])) { + throw new Exception\InvalidArgumentException('no dirname provided in params'); + } + + $dirname = (string) $params['dirname'] ; + + if (! is_dir($dirname)) { + throw new Exception\InvalidArgumentException(sprintf('Maildir "%s" is not a directory', $dirname)); + } + + if (! $this->isMaildir($dirname)) { + throw new Exception\InvalidArgumentException('invalid maildir given'); + } + + $this->has['top'] = true; + $this->has['flags'] = true; + $this->openMaildir($dirname); + } + + /** + * check if a given dir is a valid maildir + * + * @param string $dirname name of dir + * @return bool dir is valid maildir + */ + protected function isMaildir($dirname) + { + if (file_exists($dirname . '/new') && ! is_dir($dirname . '/new')) { + return false; + } + if (file_exists($dirname . '/tmp') && ! is_dir($dirname . '/tmp')) { + return false; + } + return is_dir($dirname . '/cur'); + } + + /** + * open given dir as current maildir + * + * @param string $dirname name of maildir + * @throws Exception\RuntimeException + */ + protected function openMaildir($dirname) + { + if ($this->files) { + $this->close(); + } + + ErrorHandler::start(E_WARNING); + $dh = opendir($dirname . '/cur/'); + $error = ErrorHandler::stop(); + if (! $dh) { + throw new Exception\RuntimeException('cannot open maildir', 0, $error); + } + $this->getMaildirFiles($dh, $dirname . '/cur/'); + closedir($dh); + + ErrorHandler::start(E_WARNING); + $dh = opendir($dirname . '/new/'); + $error = ErrorHandler::stop(); + if (! $dh) { + throw new Exception\RuntimeException('cannot read recent mails in maildir', 0, $error); + } + + $this->getMaildirFiles($dh, $dirname . '/new/', [Mail\Storage::FLAG_RECENT]); + closedir($dh); + } + + /** + * find all files in opened dir handle and add to maildir files + * + * @param resource $dh dir handle used for search + * @param string $dirname dirname of dir in $dh + * @param array $defaultFlags default flags for given dir + */ + protected function getMaildirFiles($dh, $dirname, $defaultFlags = []) + { + while (($entry = readdir($dh)) !== false) { + if ($entry[0] == '.' || ! is_file($dirname . $entry)) { + continue; + } + + if (false !== strpos($entry, ':')) { + list($uniq, $info) = explode(':', $entry, 2); + } else { + $uniq = $entry; + $info = ''; + } + + if (false !== strpos($uniq, ',')) { + list(, $size) = explode(',', $uniq, 2); + } else { + $size = ''; + } + + if (strlen($size) >= 2 && $size[0] === 'S' && $size[1] === '=') { + $size = substr($size, 2); + } + + if (! ctype_digit($size)) { + $size = null; + } + + if (false !== strpos($info, ',')) { + list($version, $flags) = explode(',', $info, 2); + } else { + $version = $info; + $flags = ''; + } + + if ($version !== '2') { + $flags = ''; + } + + $namedFlags = $defaultFlags; + $length = strlen($flags); + for ($i = 0; $i < $length; ++$i) { + $flag = $flags[$i]; + $namedFlags[$flag] = static::$knownFlags[$flag] ?? $flag; + } + + $data = [ + 'uniq' => $uniq, + 'flags' => $namedFlags, + 'flaglookup' => array_flip($namedFlags), + 'filename' => $dirname . $entry, + ]; + if ($size !== null) { + $data['size'] = (int) $size; + } + $this->files[] = $data; + } + + \usort($this->files, function ($a, $b): int { + return \strcmp($a['filename'], $b['filename']); + }); + } + + /** + * Close resource for mail lib. If you need to control, when the resource + * is closed. Otherwise the destructor would call this. + * + */ + public function close() + { + $this->files = []; + } + + /** + * Waste some CPU cycles doing nothing. + * + * @return bool always return true + */ + public function noop() + { + return true; + } + + /** + * stub for not supported message deletion + * + * @param $id + * @throws Exception\RuntimeException + */ + public function removeMessage($id) + { + throw new Exception\RuntimeException('maildir is (currently) read-only'); + } + + /** + * get unique id for one or all messages + * + * if storage does not support unique ids it's the same as the message number + * + * @param int|null $id message number + * @return array|string message number for given message or all messages as array + */ + public function getUniqueId($id = null) + { + if ($id) { + return $this->getFileData($id, 'uniq'); + } + + $ids = []; + foreach ($this->files as $num => $file) { + $ids[$num + 1] = $file['uniq']; + } + return $ids; + } + + /** + * get a message number from a unique id + * + * I.e. if you have a webmailer that supports deleting messages you should use unique ids + * as parameter and use this method to translate it to message number right before calling removeMessage() + * + * @param string $id unique id + * @throws Exception\InvalidArgumentException + * @return int message number + */ + public function getNumberByUniqueId($id) + { + foreach ($this->files as $num => $file) { + if ($file['uniq'] == $id) { + return $num + 1; + } + } + + throw new Exception\InvalidArgumentException('unique id not found'); + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Mbox.php b/lib/laminas/laminas-mail/src/Storage/Mbox.php new file mode 100644 index 0000000000..7e2ec748fd --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Mbox.php @@ -0,0 +1,410 @@ + start, 'separator' => headersep, 'end' => end) + * @var array + */ + protected $positions; + + /** + * used message class, change it in an extended class to extend the returned message class + * @var string + */ + protected $messageClass = Message\File::class; + + /** + * end of Line for messages + * + * @var string|null + */ + protected $messageEOL; + + /** + * Count messages all messages in current box + * + * @return int number of messages + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function countMessages() + { + return count($this->positions); + } + + /** + * Get a list of messages with number and size + * + * @param int|null $id number of message or null for all messages + * @return int|array size of given message of list with all messages as array(num => size) + */ + public function getSize($id = 0) + { + if ($id) { + $pos = $this->positions[$id - 1]; + return $pos['end'] - $pos['start']; + } + + $result = []; + foreach ($this->positions as $num => $pos) { + $result[$num + 1] = $pos['end'] - $pos['start']; + } + + return $result; + } + + /** + * Get positions for mail message or throw exception if id is invalid + * + * @param int $id number of message + * @throws Exception\InvalidArgumentException + * @return array positions as in positions + */ + protected function getPos($id) + { + if (! isset($this->positions[$id - 1])) { + throw new Exception\InvalidArgumentException('id does not exist'); + } + + return $this->positions[$id - 1]; + } + + /** + * Fetch a message + * + * @param int $id number of message + * @return \Laminas\Mail\Storage\Message\File + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function getMessage($id) + { + // TODO that's ugly, would be better to let the message class decide + if (is_subclass_of($this->messageClass, Message\File::class) + || strtolower($this->messageClass) === strtolower(Message\File::class)) { + // TODO top/body lines + $messagePos = $this->getPos($id); + + $messageClassParams = [ + 'file' => $this->fh, + 'startPos' => $messagePos['start'], + 'endPos' => $messagePos['end'], + ]; + + if (isset($this->messageEOL)) { + $messageClassParams['EOL'] = $this->messageEOL; + } + + return new $this->messageClass($messageClassParams); + } + + /** @todo Uncomment once we know how to count body lines */ + // $bodyLines = 0; + + $message = $this->getRawHeader($id); + + /* Once we know how to count body lines, we should uncomment the + * following, which would append the body content to the headers. + * + if ($bodyLines) { + $message .= "\n"; + while ($bodyLines-- && ftell($this->fh) < $this->positions[$id - 1]['end']) { + $message .= fgets($this->fh); + } + } + */ + + return new $this->messageClass(['handler' => $this, 'id' => $id, 'headers' => $message]); + } + + /* + * Get raw header of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message header + * @param int $topLines include this many lines with header (after an empty line) + * @return string raw header + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function getRawHeader($id, $part = null, $topLines = 0) + { + if ($part !== null) { + // TODO: implement + throw new Exception\RuntimeException('not implemented'); + } + $messagePos = $this->getPos($id); + // TODO: toplines + return stream_get_contents($this->fh, $messagePos['separator'] - $messagePos['start'], $messagePos['start']); + } + + /* + * Get raw content of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message content + * @return string raw content + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function getRawContent($id, $part = null) + { + if ($part !== null) { + // TODO: implement + throw new Exception\RuntimeException('not implemented'); + } + $messagePos = $this->getPos($id); + return stream_get_contents($this->fh, $messagePos['end'] - $messagePos['separator'], $messagePos['separator']); + } + + /** + * Create instance with parameters + * Supported parameters are: + * - filename filename of mbox file + * + * @param $params array|object|Config mail reader specific parameters + * @throws Exception\InvalidArgumentException + */ + public function __construct($params) + { + $params = ParamsNormalizer::normalizeParams($params); + + if (! isset($params['filename'])) { + throw new Exception\InvalidArgumentException('no valid filename given in params'); + } + + if (isset($params['messageEOL'])) { + $this->messageEOL = (string) $params['messageEOL']; + } + + $this->openMboxFile((string) $params['filename']); + $this->has['top'] = true; + $this->has['uniqueid'] = false; + } + + /** + * check if given file is a mbox file + * + * if $file is a resource its file pointer is moved after the first line + * + * @param resource|string $file stream resource of name of file + * @param bool $fileIsString file is string or resource + * @return bool file is mbox file + */ + protected function isMboxFile($file, $fileIsString = true) + { + if ($fileIsString) { + ErrorHandler::start(E_WARNING); + $file = fopen($file, 'r'); + ErrorHandler::stop(); + if (! $file) { + return false; + } + } else { + fseek($file, 0); + } + + $result = false; + + $line = fgets($file) ?: ''; + if (strpos($line, 'From ') === 0) { + $result = true; + } + + if ($fileIsString) { + ErrorHandler::start(E_WARNING); + fclose($file); + ErrorHandler::stop(); + } + + return $result; + } + + /** + * open given file as current mbox file + * + * @param string $filename filename of mbox file + * @throws Exception\RuntimeException + * @throws Exception\InvalidArgumentException + */ + protected function openMboxFile($filename) + { + if ($this->fh) { + $this->close(); + } + + if (is_dir($filename)) { + throw new Exception\InvalidArgumentException('file is not a valid mbox file'); + } + + ErrorHandler::start(); + $this->fh = fopen($filename, 'r'); + $error = ErrorHandler::stop(); + if (! $this->fh) { + throw new Exception\RuntimeException('cannot open mbox file', 0, $error); + } + $this->filename = $filename; + $this->filemtime = filemtime($this->filename); + + if (! $this->isMboxFile($this->fh, false)) { + ErrorHandler::start(E_WARNING); + fclose($this->fh); + $error = ErrorHandler::stop(); + throw new Exception\InvalidArgumentException('file is not a valid mbox format', 0, $error); + } + + $messagePos = ['start' => ftell($this->fh), 'separator' => 0, 'end' => 0]; + while (($line = fgets($this->fh)) !== false) { + if (strpos($line, 'From ') === 0) { + $messagePos['end'] = ftell($this->fh) - strlen($line) - 2; // + newline + if (! $messagePos['separator']) { + $messagePos['separator'] = $messagePos['end']; + } + $this->positions[] = $messagePos; + $messagePos = ['start' => ftell($this->fh), 'separator' => 0, 'end' => 0]; + } + if (! $messagePos['separator'] && ! trim($line)) { + $messagePos['separator'] = ftell($this->fh); + } + } + + $messagePos['end'] = ftell($this->fh); + if (! $messagePos['separator']) { + $messagePos['separator'] = $messagePos['end']; + } + $this->positions[] = $messagePos; + } + + /** + * Close resource for mail lib. If you need to control, when the resource + * is closed. Otherwise the destructor would call this. + * + */ + public function close() + { + if (is_resource($this->fh)) { + fclose($this->fh); + } + $this->positions = []; + } + + /** + * Waste some CPU cycles doing nothing. + * + * @return bool always return true + */ + public function noop() + { + return true; + } + + /** + * stub for not supported message deletion + * + * @param $id + * @throws Exception\RuntimeException + */ + public function removeMessage($id) + { + throw new Exception\RuntimeException('mbox is read-only'); + } + + /** + * get unique id for one or all messages + * + * Mbox does not support unique ids (yet) - it's always the same as the message number. + * That shouldn't be a problem, because we can't change mbox files. Therefor the message + * number is save enough. + * + * @param int|null $id message number + * @return array|string message number for given message or all messages as array + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function getUniqueId($id = null) + { + if ($id) { + // check if id exists + $this->getPos($id); + return $id; + } + + $range = range(1, $this->countMessages()); + return array_combine($range, $range); + } + + /** + * get a message number from a unique id + * + * I.e. if you have a webmailer that supports deleting messages you should use unique ids + * as parameter and use this method to translate it to message number right before calling removeMessage() + * + * @param string $id unique id + * @return int message number + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function getNumberByUniqueId($id) + { + // check if id exists + $this->getPos($id); + return $id; + } + + /** + * magic method for serialize() + * + * with this method you can cache the mbox class + * + * @return array name of variables + */ + public function __sleep() + { + return ['filename', 'positions', 'filemtime']; + } + + /** + * magic method for unserialize() + * + * with this method you can cache the mbox class + * for cache validation the mtime of the mbox file is used + * + * @throws Exception\RuntimeException + */ + public function __wakeup() + { + ErrorHandler::start(); + $filemtime = filemtime($this->filename); + ErrorHandler::stop(); + if ($this->filemtime != $filemtime) { + $this->close(); + $this->openMboxFile($this->filename); + } else { + ErrorHandler::start(); + $this->fh = fopen($this->filename, 'r'); + $error = ErrorHandler::stop(); + if (! $this->fh) { + throw new Exception\RuntimeException('cannot open mbox file', 0, $error); + } + } + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Message.php b/lib/laminas/laminas-mail/src/Storage/Message.php new file mode 100644 index 0000000000..d2307aed31 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Message.php @@ -0,0 +1,80 @@ +flags = array_combine($params['flags'], $params['flags']); + } + + parent::__construct($params); + } + + /** + * return toplines as found after headers + * + * @return string toplines + */ + public function getTopLines() + { + return $this->topLines; + } + + /** + * check if flag is set + * + * @param mixed $flag a flag name, use constants defined in \Laminas\Mail\Storage + * @return bool true if set, otherwise false + */ + public function hasFlag($flag) + { + return isset($this->flags[$flag]); + } + + /** + * get all set flags + * + * @return array array with flags, key and value are the same for easy lookup + */ + public function getFlags() + { + return $this->flags; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Message/File.php b/lib/laminas/laminas-mail/src/Storage/Message/File.php new file mode 100644 index 0000000000..b5ce389903 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Message/File.php @@ -0,0 +1,64 @@ +flags = array_combine($params['flags'], $params['flags']); + } + + parent::__construct($params); + } + + /** + * return toplines as found after headers + * + * @return string toplines + */ + public function getTopLines() + { + return $this->topLines; + } + + /** + * check if flag is set + * + * @param mixed $flag a flag name, use constants defined in \Laminas\Mail\Storage + * @return bool true if set, otherwise false + */ + public function hasFlag($flag) + { + return isset($this->flags[$flag]); + } + + /** + * get all set flags + * + * @return array array with flags, key and value are the same for easy lookup + */ + public function getFlags() + { + return $this->flags; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Message/MessageInterface.php b/lib/laminas/laminas-mail/src/Storage/Message/MessageInterface.php new file mode 100644 index 0000000000..15d82273c9 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Message/MessageInterface.php @@ -0,0 +1,28 @@ + + */ + public static function normalizeParams($params): array + { + if ($params instanceof Traversable) { + $params = iterator_to_array($params); + } + + if (is_object($params)) { + $params = get_object_vars($params); + } + + if (! is_array($params)) { + throw new Exception\InvalidArgumentException(sprintf( + 'Invalid $params provided; expected array|Traversable|object, received %s', + gettype($params) + )); + } + + Assert::isMap($params, 'Expected $params to have only string keys'); + return $params; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Part.php b/lib/laminas/laminas-mail/src/Storage/Part.php new file mode 100644 index 0000000000..c1f2f46819 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Part.php @@ -0,0 +1,474 @@ + value) or string, if a content part is found it's used as toplines + * - noToplines ignore content found after headers in param 'headers' + * - content content as string + * - strict strictly parse raw content + * + * @param array $params full message with or without headers + * @throws Exception\InvalidArgumentException + */ + public function __construct(array $params) + { + if (isset($params['handler'])) { + if (! $params['handler'] instanceof AbstractStorage) { + throw new Exception\InvalidArgumentException('handler is not a valid mail handler'); + } + if (! isset($params['id'])) { + throw new Exception\InvalidArgumentException('need a message id with a handler'); + } + + $this->mail = $params['handler']; + $this->messageNum = $params['id']; + } + + $params['strict'] = $params['strict'] ?? false; + + if (isset($params['raw'])) { + Mime\Decode::splitMessage( + $params['raw'], + $this->headers, + $this->content, + Mime\Mime::LINEEND, + $params['strict'] + ); + } elseif (isset($params['headers'])) { + if (is_array($params['headers'])) { + $this->headers = new Headers(); + $this->headers->addHeaders($params['headers']); + } else { + if (empty($params['noToplines'])) { + Mime\Decode::splitMessage($params['headers'], $this->headers, $this->topLines); + } else { + $this->headers = Headers::fromString($params['headers']); + } + } + + if (isset($params['content'])) { + $this->content = $params['content']; + } + } + } + + /** + * Check if part is a multipart message + * + * @return bool if part is multipart + */ + public function isMultipart() + { + try { + return stripos($this->contentType, 'multipart/') === 0; + } catch (Exception\ExceptionInterface $e) { + return false; + } + } + + /** + * Body of part + * + * If part is multipart the raw content of this part with all sub parts is returned + * + * @throws Exception\RuntimeException + * @return string body + */ + public function getContent() + { + if ($this->content !== null) { + return $this->content; + } + + if ($this->mail) { + return $this->mail->getRawContent($this->messageNum); + } + + throw new Exception\RuntimeException('no content'); + } + + /** + * Return size of part + * + * Quite simple implemented currently (not decoding). Handle with care. + * + * @return int size + */ + public function getSize() + { + return strlen($this->getContent()); + } + + /** + * Cache content and split in parts if multipart + * + * @throws Exception\RuntimeException + * @return null + */ + protected function cacheContent() + { + // caching content if we can't fetch parts + if ($this->content === null && $this->mail) { + $this->content = $this->mail->getRawContent($this->messageNum); + } + + if (! $this->isMultipart()) { + return; + } + + // split content in parts + $boundary = $this->getHeaderField('content-type', 'boundary'); + if (! $boundary) { + throw new Exception\RuntimeException('no boundary found in content type to split message'); + } + $parts = Mime\Decode::splitMessageStruct($this->content, $boundary); + if ($parts === null) { + return; + } + $counter = 1; + foreach ($parts as $part) { + $this->parts[$counter++] = new static(['headers' => $part['header'], 'content' => $part['body']]); + } + } + + /** + * Get part of multipart message + * + * @param int $num number of part starting with 1 for first part + * @throws Exception\RuntimeException + * @return Part wanted part + */ + public function getPart($num) + { + if (isset($this->parts[$num])) { + return $this->parts[$num]; + } + + if (! $this->mail && $this->content === null) { + throw new Exception\RuntimeException('part not found'); + } + + if ($this->mail && $this->mail->hasFetchPart) { + // TODO: fetch part + // return + } + + $this->cacheContent(); + + if (! isset($this->parts[$num])) { + throw new Exception\RuntimeException('part not found'); + } + + return $this->parts[$num]; + } + + /** + * Count parts of a multipart part + * + * @return int number of sub-parts + */ + public function countParts() + { + if ($this->countParts) { + return $this->countParts; + } + + $this->countParts = count($this->parts); + if ($this->countParts) { + return $this->countParts; + } + + if ($this->mail && $this->mail->hasFetchPart) { + // TODO: fetch part + // return + } + + $this->cacheContent(); + + $this->countParts = count($this->parts); + return $this->countParts; + } + + /** + * Access headers collection + * + * Lazy-loads if not already attached. + * + * @return Headers + * @throws Exception\RuntimeException + */ + public function getHeaders() + { + if (null === $this->headers) { + if ($this->mail) { + $part = $this->mail->getRawHeader($this->messageNum); + $this->headers = Headers::fromString($part); + } else { + $this->headers = new Headers(); + } + } + if (! $this->headers instanceof Headers) { + throw new Exception\RuntimeException( + '$this->headers must be an instance of Headers' + ); + } + + return $this->headers; + } + + /** + * Get a header in specified format + * + * Internally headers that occur more than once are saved as array, all other as string. If $format + * is set to string implode is used to concat the values (with Mime::LINEEND as delim). + * + * @param string $name name of header, matches case-insensitive, but camel-case is replaced with dashes + * @param string $format change type of return value to 'string' or 'array' + * @throws Exception\InvalidArgumentException + * @return string|array|HeaderInterface|\ArrayIterator value of header in wanted or internal format + */ + public function getHeader($name, $format = null) + { + $header = $this->getHeaders()->get($name); + if ($header === false) { + $lowerName = strtolower(preg_replace('%([a-z])([A-Z])%', '\1-\2', $name)); + $header = $this->getHeaders()->get($lowerName); + if ($header === false) { + throw new Exception\InvalidArgumentException( + "Header with Name $name or $lowerName not found" + ); + } + } + + switch ($format) { + case 'string': + if ($header instanceof HeaderInterface) { + $return = $header->getFieldValue(HeaderInterface::FORMAT_RAW); + } else { + $return = trim(implode( + Mime\Mime::LINEEND, + array_map(static function ($header): string { + return $header->getFieldValue(HeaderInterface::FORMAT_RAW); + }, iterator_to_array($header)) + ), Mime\Mime::LINEEND); + } + break; + case 'array': + if ($header instanceof HeaderInterface) { + $return = [$header->getFieldValue()]; + } else { + $return = []; + foreach ($header as $h) { + $return[] = $h->getFieldValue(HeaderInterface::FORMAT_RAW); + } + } + break; + default: + $return = $header; + } + + return $return; + } + + /** + * Get a specific field from a header like content type or all fields as array + * + * If the header occurs more than once, only the value from the first header + * is returned. + * + * Throws an Exception if the requested header does not exist. If + * the specific header field does not exist, returns null. + * + * @param string $name name of header, like in getHeader() + * @param string $wantedPart the wanted part, default is first, if null an array with all parts is returned + * @param string $firstName key name for the first part + * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value) + * @throws \Laminas\Mime\Exception\RuntimeException + */ + public function getHeaderField($name, $wantedPart = '0', $firstName = '0') + { + return Mime\Decode::splitHeaderField(current($this->getHeader($name, 'array')), $wantedPart, $firstName); + } + + /** + * Getter for mail headers - name is matched in lowercase + * + * This getter is short for Part::getHeader($name, 'string') + * + * @see Part::getHeader() + * + * @param string $name header name + * @return string value of header + * @throws Exception\ExceptionInterface + */ + public function __get($name) + { + return $this->getHeader($name, 'string'); + } + + /** + * Isset magic method proxy to hasHeader + * + * This method is short syntax for Part::hasHeader($name); + * + * @see Part::hasHeader + * + * @param string + * @return bool + */ + public function __isset($name) + { + return $this->getHeaders()->has($name); + } + + /** + * magic method to get content of part + * + * @return string content + */ + public function __toString() + { + return $this->getContent(); + } + + /** + * implements RecursiveIterator::hasChildren() + * + * @return bool current element has children/is multipart + */ + #[ReturnTypeWillChange] + public function hasChildren() + { + $current = $this->current(); + return $current && $current instanceof self && $current->isMultipart(); + } + + /** + * implements RecursiveIterator::getChildren() + * + * @return Part same as self::current() + */ + #[ReturnTypeWillChange] + public function getChildren() + { + return $this->current(); + } + + /** + * implements Iterator::valid() + * + * @return bool check if there's a current element + */ + #[ReturnTypeWillChange] + public function valid() + { + if ($this->countParts === null) { + $this->countParts(); + } + return $this->iterationPos && $this->iterationPos <= $this->countParts; + } + + /** + * implements Iterator::next() + */ + #[ReturnTypeWillChange] + public function next() + { + ++$this->iterationPos; + } + + /** + * implements Iterator::key() + * + * @return string key/number of current part + */ + #[ReturnTypeWillChange] + public function key() + { + return $this->iterationPos; + } + + /** + * implements Iterator::current() + * + * @return Part current part + */ + #[ReturnTypeWillChange] + public function current() + { + return $this->getPart($this->iterationPos); + } + + /** + * implements Iterator::rewind() + */ + #[ReturnTypeWillChange] + public function rewind() + { + $this->countParts(); + $this->iterationPos = 1; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Part/Exception/ExceptionInterface.php b/lib/laminas/laminas-mail/src/Storage/Part/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..abebe1c356 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Part/Exception/ExceptionInterface.php @@ -0,0 +1,9 @@ +fh = fopen($params['file'], 'r'); + } else { + $this->fh = $params['file']; + } + if (! $this->fh) { + throw new Exception\RuntimeException('could not open file'); + } + if (isset($params['startPos'])) { + fseek($this->fh, $params['startPos']); + } + $header = ''; + $endPos = $params['endPos'] ?? null; + while (($endPos === null || ftell($this->fh) < $endPos) && trim($line = fgets($this->fh))) { + $header .= $line; + } + + if (isset($params['EOL'])) { + $this->headers = Headers::fromString($header, $params['EOL']); + } else { + $this->headers = Headers::fromString($header); + } + + $this->contentPos[0] = ftell($this->fh); + if ($endPos !== null) { + $this->contentPos[1] = $endPos; + } else { + fseek($this->fh, 0, SEEK_END); + $this->contentPos[1] = ftell($this->fh); + } + if (! $this->isMultipart()) { + return; + } + + $boundary = $this->getHeaderField('content-type', 'boundary'); + if (! $boundary) { + throw new Exception\RuntimeException('no boundary found in content type to split message'); + } + + $part = []; + $pos = $this->contentPos[0]; + fseek($this->fh, $pos); + while (! feof($this->fh) && ($endPos === null || $pos < $endPos)) { + $line = fgets($this->fh); + if ($line === false) { + if (feof($this->fh)) { + break; + } + throw new Exception\RuntimeException('error reading file'); + } + + $lastPos = $pos; + $pos = ftell($this->fh); + $line = trim($line); + + if ($line == '--' . $boundary) { + if ($part) { + // not first part + $part[1] = $lastPos; + $this->partPos[] = $part; + } + $part = [$pos]; + } elseif ($line == '--' . $boundary . '--') { + $part[1] = $lastPos; + $this->partPos[] = $part; + break; + } + } + $this->countParts = count($this->partPos); + } + + /** + * Body of part + * + * If part is multipart the raw content of this part with all sub parts is returned + * + * @param resource $stream Optional + * @return string body + */ + public function getContent($stream = null) + { + fseek($this->fh, $this->contentPos[0]); + if ($stream !== null) { + return stream_copy_to_stream($this->fh, $stream, $this->contentPos[1] - $this->contentPos[0]); + } + $length = $this->contentPos[1] - $this->contentPos[0]; + return $length < 1 ? '' : fread($this->fh, $length); + } + + /** + * Return size of part + * + * Quite simple implemented currently (not decoding). Handle with care. + * + * @return int size + */ + public function getSize() + { + return $this->contentPos[1] - $this->contentPos[0]; + } + + /** + * Get part of multipart message + * + * @param int $num number of part starting with 1 for first part + * @throws Exception\RuntimeException + * @return Part wanted part + */ + public function getPart($num) + { + --$num; + if (! isset($this->partPos[$num])) { + throw new Exception\RuntimeException('part not found'); + } + + return new static([ + 'file' => $this->fh, + 'startPos' => $this->partPos[$num][0], + 'endPos' => $this->partPos[$num][1], + ]); + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Part/PartInterface.php b/lib/laminas/laminas-mail/src/Storage/Part/PartInterface.php new file mode 100644 index 0000000000..6161112bb5 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Part/PartInterface.php @@ -0,0 +1,117 @@ + firstPart, partname => value] + * @throws Exception\ExceptionInterface + */ + public function getHeaderField($name, $wantedPart = '0', $firstName = '0'); + + /** + * Getter for mail headers - name is matched in lowercase + * + * This getter is short for PartInterface::getHeader($name, 'string') + * + * @see PartInterface::getHeader() + * @param string $name header name + * @return string value of header + * @throws Exception\ExceptionInterface + */ + public function __get($name); + + /** + * magic method to get content of part + * + * @return string content + */ + public function __toString(); +} diff --git a/lib/laminas/laminas-mail/src/Storage/Pop3.php b/lib/laminas/laminas-mail/src/Storage/Pop3.php new file mode 100644 index 0000000000..def4f97a98 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Pop3.php @@ -0,0 +1,288 @@ +protocol->status($count, $octets); + return (int) $count; + } + + /** + * get a list of messages with number and size + * + * @param int $id number of message + * @return int|array size of given message of list with all messages as array(num => size) + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + */ + public function getSize($id = 0) + { + $id = $id ? $id : null; + return $this->protocol->getList($id); + } + + /** + * Fetch a message + * + * @param int $id number of message + * @return \Laminas\Mail\Storage\Message + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + */ + public function getMessage($id) + { + $bodyLines = 0; + $message = $this->protocol->top($id, $bodyLines, true); + + return new $this->messageClass([ + 'handler' => $this, + 'id' => $id, + 'headers' => $message, + 'noToplines' => $bodyLines < 1, + ]); + } + + /* + * Get raw header of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message header + * @param int $topLines include this many lines with header (after an empty line) + * @return string raw header + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function getRawHeader($id, $part = null, $topLines = 0) + { + if ($part !== null) { + // TODO: implement + throw new Exception\RuntimeException('not implemented'); + } + + return $this->protocol->top($id, 0, true); + } + + /* + * Get raw content of message or part + * + * @param int $id number of message + * @param null|array|string $part path to part or null for message content + * @return string raw content + * @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function getRawContent($id, $part = null) + { + if ($part !== null) { + // TODO: implement + throw new Exception\RuntimeException('not implemented'); + } + + $content = $this->protocol->retrieve($id); + // TODO: find a way to avoid decoding the headers + $headers = null; // "Declare" variable since it's passed by reference + $body = null; // "Declare" variable before first usage. + Mime\Decode::splitMessage($content, $headers, $body); + return $body; + } + + /** + * create instance with parameters + * Supported parameters are + * - host hostname or ip address of POP3 server + * - user username + * - password password for user 'username' [optional, default = ''] + * - port port for POP3 server [optional, default = 110] + * - ssl 'SSL' or 'TLS' for secure sockets + * + * @param array|object|Protocol\Pop3 $params mail reader specific + * parameters or configured Pop3 protocol object + * @throws \Laminas\Mail\Storage\Exception\InvalidArgumentException + * @throws \Laminas\Mail\Protocol\Exception\RuntimeException + */ + public function __construct($params) + { + $this->has['fetchPart'] = false; + $this->has['top'] = null; + $this->has['uniqueid'] = null; + + if ($params instanceof Protocol\Pop3) { + $this->protocol = $params; + return; + } + + $params = ParamsNormalizer::normalizeParams($params); + + if (! isset($params['user'])) { + throw new Exception\InvalidArgumentException('need at least user in params'); + } + + $host = $params['host'] ?? 'localhost'; + $password = $params['password'] ?? ''; + $port = $params['port'] ?? null; + $ssl = $params['ssl'] ?? false; + + if (null !== $port) { + $port = (int) $port; + } + + if (! is_string($ssl)) { + $ssl = (bool) $ssl; + } + + $this->protocol = new Protocol\Pop3(); + + if (array_key_exists('novalidatecert', $params)) { + $this->protocol->setNoValidateCert((bool) $params['novalidatecert']); + } + + $this->protocol->connect((string) $host, $port, $ssl); + $this->protocol->login((string) $params['user'], (string) $password); + } + + /** + * Close resource for mail lib. If you need to control, when the resource + * is closed. Otherwise the destructor would call this. + */ + public function close() + { + $this->protocol->logout(); + } + + /** + * Keep the server busy. + * + * @throws \Laminas\Mail\Protocol\Exception\RuntimeException + */ + public function noop() + { + $this->protocol->noop(); + } + + /** + * Remove a message from server. If you're doing that from a web environment + * you should be careful and use a uniqueid as parameter if possible to + * identify the message. + * + * @param int $id number of message + * @throws \Laminas\Mail\Protocol\Exception\RuntimeException + */ + public function removeMessage($id) + { + $this->protocol->delete($id); + } + + /** + * get unique id for one or all messages + * + * if storage does not support unique ids it's the same as the message number + * + * @param int|null $id message number + * @return array|string message number for given message or all messages as array + * @throws \Laminas\Mail\Storage\Exception\ExceptionInterface + */ + public function getUniqueId($id = null) + { + if (! $this->hasUniqueid) { + if ($id) { + return $id; + } + $count = $this->countMessages(); + if ($count < 1) { + return []; + } + $range = range(1, $count); + return array_combine($range, $range); + } + + return $this->protocol->uniqueid($id); + } + + /** + * get a message number from a unique id + * + * I.e. if you have a webmailer that supports deleting messages you should use unique ids + * as parameter and use this method to translate it to message number right before calling removeMessage() + * + * @param string $id unique id + * @throws Exception\InvalidArgumentException + * @return int message number + */ + public function getNumberByUniqueId($id) + { + if (! $this->hasUniqueid) { + return $id; + } + + $ids = $this->getUniqueId(); + foreach ($ids as $k => $v) { + if ($v == $id) { + return $k; + } + } + + throw new Exception\InvalidArgumentException('unique id not found'); + } + + /** + * Special handling for hasTop and hasUniqueid. The headers of the first message is + * retrieved if Top wasn't needed/tried yet. + * + * @see AbstractStorage::__get() + * @param string $var + * @return string + */ + public function __get($var) + { + $result = parent::__get($var); + if ($result !== null) { + return $result; + } + + if (strtolower($var) == 'hastop') { + if ($this->protocol->hasTop === null) { + // need to make a real call, because not all server are honest in their capas + try { + $this->protocol->top(1, 0, false); + } catch (MailException\ExceptionInterface $e) { + // ignoring error + } + } + $this->has['top'] = $this->protocol->hasTop; + return $this->protocol->hasTop; + } + + if (strtolower($var) == 'hasuniqueid') { + $id = null; + try { + $id = $this->protocol->uniqueid(1); + } catch (MailException\ExceptionInterface $e) { + // ignoring error + } + $this->has['uniqueid'] = (bool) $id; + return $this->has['uniqueid']; + } + + return $result; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Writable/Maildir.php b/lib/laminas/laminas-mail/src/Storage/Writable/Maildir.php new file mode 100644 index 0000000000..a8245b5104 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Writable/Maildir.php @@ -0,0 +1,957 @@ +create) + && isset($params->dirname) + && ! file_exists($params->dirname . DIRECTORY_SEPARATOR . 'cur') + ) { + self::initMaildir($params->dirname); + } + + parent::__construct($params); + } + + /** + * create a new folder + * + * This method also creates parent folders if necessary. Some mail storages may restrict, which folder + * may be used as parent or which chars may be used in the folder name + * + * @param string $name global name of folder, local name if $parentFolder is set + * @param string|\Laminas\Mail\Storage\Folder $parentFolder parent of new folder, else root folder is parent + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + * @return string only used internally (new created maildir) + */ + public function createFolder($name, $parentFolder = null) + { + if ($parentFolder instanceof Folder) { + $folder = $parentFolder->getGlobalName() . $this->delim . $name; + } elseif ($parentFolder !== null) { + $folder = rtrim($parentFolder, $this->delim) . $this->delim . $name; + } else { + $folder = $name; + } + + $folder = trim($folder, $this->delim); + + // first we check if we try to create a folder that does exist + $exists = null; + try { + $exists = $this->getFolders($folder); + } catch (MailException\ExceptionInterface $e) { + // ok + } + if ($exists) { + throw new StorageException\RuntimeException('folder already exists'); + } + + if (strpos($folder, $this->delim . $this->delim) !== false) { + throw new StorageException\RuntimeException('invalid name - folder parts may not be empty'); + } + + if (strpos($folder, 'INBOX' . $this->delim) === 0) { + $folder = substr($folder, 6); + } + + $fulldir = $this->rootdir . '.' . $folder; + + // check if we got tricked and would create a dir outside of the rootdir or not as direct child + if (strpos($folder, DIRECTORY_SEPARATOR) !== false || strpos($folder, '/') !== false + || dirname($fulldir) . DIRECTORY_SEPARATOR != $this->rootdir + ) { + throw new StorageException\RuntimeException('invalid name - no directory separator allowed in folder name'); + } + + // has a parent folder? + $parent = null; + if (strpos($folder, $this->delim)) { + // let's see if the parent folder exists + $parent = substr($folder, 0, strrpos($folder, $this->delim)); + try { + $this->getFolders($parent); + } catch (MailException\ExceptionInterface $e) { + // does not - create parent folder + $this->createFolder($parent); + } + } + + ErrorHandler::start(); + if (! mkdir($fulldir) || ! mkdir($fulldir . DIRECTORY_SEPARATOR . 'cur')) { + $error = ErrorHandler::stop(); + throw new StorageException\RuntimeException( + 'error while creating new folder, may be created incompletely', + 0, + $error + ); + } + ErrorHandler::stop(); + + mkdir($fulldir . DIRECTORY_SEPARATOR . 'new'); + mkdir($fulldir . DIRECTORY_SEPARATOR . 'tmp'); + + $localName = $parent ? substr($folder, strlen($parent) + 1) : $folder; + $this->getFolders($parent)->$localName = new Folder($localName, $folder, true); + + return $fulldir; + } + + /** + * remove a folder + * + * @param string|Folder $name name or instance of folder + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + */ + public function removeFolder($name) + { + // TODO: This could fail in the middle of the task, which is not optimal. + // But there is no defined standard way to mark a folder as removed and there is no atomar fs-op + // to remove a directory. Also moving the folder to a/the trash folder is not possible, as + // all parent folders must be created. What we could do is add a dash to the front of the + // directory name and it should be ignored as long as other processes obey the standard. + + if ($name instanceof Folder) { + $name = $name->getGlobalName(); + } + + $name = trim($name, $this->delim); + if (strpos($name, 'INBOX' . $this->delim) === 0) { + $name = substr($name, 6); + } + + // check if folder exists and has no children + if (! $this->getFolders($name)->isLeaf()) { + throw new StorageException\RuntimeException('delete children first'); + } + + if ($name == 'INBOX' || $name == DIRECTORY_SEPARATOR || $name == '/') { + throw new StorageException\RuntimeException('wont delete INBOX'); + } + + if ($name == $this->getCurrentFolder()) { + throw new StorageException\RuntimeException('wont delete selected folder'); + } + + foreach (['tmp', 'new', 'cur', '.'] as $subdir) { + $dir = $this->rootdir . '.' . $name . DIRECTORY_SEPARATOR . $subdir; + if (! file_exists($dir)) { + continue; + } + $dh = opendir($dir); + if (! $dh) { + throw new StorageException\RuntimeException("error opening $subdir"); + } + while (($entry = readdir($dh)) !== false) { + if ($entry == '.' || $entry == '..') { + continue; + } + if (! unlink($dir . DIRECTORY_SEPARATOR . $entry)) { + throw new StorageException\RuntimeException("error cleaning $subdir"); + } + } + closedir($dh); + if ($subdir !== '.') { + if (! rmdir($dir)) { + throw new StorageException\RuntimeException("error removing $subdir"); + } + } + } + + if (! rmdir($this->rootdir . '.' . $name)) { + // at least we should try to make it a valid maildir again + mkdir($this->rootdir . '.' . $name . DIRECTORY_SEPARATOR . 'cur'); + throw new StorageException\RuntimeException("error removing maindir"); + } + + $parent = strpos($name, $this->delim) ? substr($name, 0, strrpos($name, $this->delim)) : null; + $localName = $parent ? substr($name, strlen($parent) + 1) : $name; + unset($this->getFolders($parent)->$localName); + } + + /** + * rename and/or move folder + * + * The new name has the same restrictions as in createFolder() + * + * @param string|\Laminas\Mail\Storage\Folder $oldName name or instance of folder + * @param string $newName new global name of folder + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + */ + public function renameFolder($oldName, $newName) + { + // TODO: This is also not atomar and has similar problems as removeFolder() + + if ($oldName instanceof Folder) { + $oldName = $oldName->getGlobalName(); + } + + $oldName = trim($oldName, $this->delim); + if (strpos($oldName, 'INBOX' . $this->delim) === 0) { + $oldName = substr($oldName, 6); + } + + $newName = trim($newName, $this->delim); + if (strpos($newName, 'INBOX' . $this->delim) === 0) { + $newName = substr($newName, 6); + } + + if (strpos($newName, $oldName . $this->delim) === 0) { + throw new StorageException\RuntimeException('new folder cannot be a child of old folder'); + } + + // check if folder exists and has no children + $folder = $this->getFolders($oldName); + + if ($oldName == 'INBOX' || $oldName == DIRECTORY_SEPARATOR || $oldName == '/') { + throw new StorageException\RuntimeException('wont rename INBOX'); + } + + if ($oldName == $this->getCurrentFolder()) { + throw new StorageException\RuntimeException('wont rename selected folder'); + } + + $newdir = $this->createFolder($newName); + + if (! $folder->isLeaf()) { + foreach ($folder as $k => $v) { + $this->renameFolder($v->getGlobalName(), $newName . $this->delim . $k); + } + } + + $olddir = $this->rootdir . '.' . $folder; + foreach (['tmp', 'new', 'cur'] as $subdir) { + $subdir = DIRECTORY_SEPARATOR . $subdir; + if (! file_exists($olddir . $subdir)) { + continue; + } + // using copy or moving files would be even better - but also much slower + if (! rename($olddir . $subdir, $newdir . $subdir)) { + throw new StorageException\RuntimeException('error while moving ' . $subdir); + } + } + // create a dummy if removing fails - otherwise we can't read it next time + mkdir($olddir . DIRECTORY_SEPARATOR . 'cur'); + $this->removeFolder($oldName); + } + + /** + * create a uniqueid for maildir filename + * + * This is nearly the format defined in the maildir standard. The microtime() call should already + * create a uniqueid, the pid is for multicore/-cpu machine that manage to call this function at the + * exact same time, and uname() gives us the hostname for multiple machines accessing the same storage. + * + * If someone disables posix we create a random number of the same size, so this method should also + * work on Windows - if you manage to get maildir working on Windows. + * Microtime could also be disabled, although I've never seen it. + * + * @return string new uniqueid + */ + protected function createUniqueId() + { + $id = ''; + $id .= microtime(true); + $id .= '.' . getmypid(); + $id .= '.' . php_uname('n'); + + return $id; + } + + /** + * open a temporary maildir file + * + * makes sure tmp/ exists and create a file with a unique name + * you should close the returned filehandle! + * + * @param string $folder name of current folder without leading . + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + * @return array array('dirname' => dir of maildir folder, 'uniq' => unique id, 'filename' => name of create file + * 'handle' => file opened for writing) + */ + protected function createTmpFile($folder = 'INBOX') + { + if ($folder == 'INBOX') { + $tmpdir = $this->rootdir . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR; + } else { + $tmpdir = $this->rootdir . '.' . $folder . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR; + } + if (! file_exists($tmpdir)) { + if (! mkdir($tmpdir)) { + throw new StorageException\RuntimeException('problems creating tmp dir'); + } + } + + // we should retry to create a unique id if a file with the same name exists + // to avoid a script timeout we only wait 1 second (instead of 2) and stop + // after a defined retry count + // if you change this variable take into account that it can take up to $maxTries seconds + // normally we should have a valid unique name after the first try, we're just following the "standard" here + $maxTries = 5; + for ($i = 0; $i < $maxTries; ++$i) { + $uniq = $this->createUniqueId(); + if (! file_exists($tmpdir . $uniq)) { + // here is the race condition! - as defined in the standard + // to avoid having a long time between stat()ing the file and creating it we're opening it here + // to mark the filename as taken + $fh = fopen($tmpdir . $uniq, 'w'); + if (! $fh) { + throw new StorageException\RuntimeException('could not open temp file'); + } + break; + } + sleep(1); + } + + if (! $fh) { + throw new StorageException\RuntimeException( + "tried {$maxTries} unique ids for a temp file, but all were taken - giving up" + ); + } + + return [ + 'dirname' => $this->rootdir . '.' . $folder, + 'uniq' => $uniq, + 'filename' => $tmpdir . $uniq, + 'handle' => $fh, + ]; + } + + /** + * create an info string for filenames with given flags + * + * @param array $flags wanted flags, with the reference you'll get the set + * flags with correct key (= char for flag) + * @return string info string for version 2 filenames including the leading colon + * @throws StorageException\InvalidArgumentException + */ + protected function getInfoString(&$flags) + { + // accessing keys is easier, faster and it removes duplicated flags + $wantedFlags = array_flip($flags); + if (isset($wantedFlags[Storage::FLAG_RECENT])) { + throw new StorageException\InvalidArgumentException('recent flag may not be set'); + } + + $info = ':2,'; + $flags = []; + foreach (Storage\Maildir::$knownFlags as $char => $flag) { + if (! isset($wantedFlags[$flag])) { + continue; + } + $info .= $char; + $flags[$char] = $flag; + unset($wantedFlags[$flag]); + } + + if (! empty($wantedFlags)) { + $wantedFlags = implode(', ', array_keys($wantedFlags)); + throw new StorageException\InvalidArgumentException('unknown flag(s): ' . $wantedFlags); + } + + return $info; + } + + /** + * append a new message to mail storage + * + * @param string|resource $message message as string or stream resource. + * @param null|string|Folder $folder folder for new message, else current + * folder is taken. + * @param null|array $flags set flags for new message, else a default set + * is used. + * @param bool $recent handle this mail as if recent flag has been set, + * should only be used in delivery. + * @throws StorageException\RuntimeException + */ + public function appendMessage($message, $folder = null, $flags = null, $recent = false) + { + if ($this->quota && $this->checkQuota()) { + throw new StorageException\RuntimeException('storage is over quota!'); + } + + if ($folder === null) { + $folder = $this->currentFolder; + } + + if (! ($folder instanceof Folder)) { + $folder = $this->getFolders($folder); + } + + if ($flags === null) { + $flags = [Storage::FLAG_SEEN]; + } + $info = $this->getInfoString($flags); + $tempFile = $this->createTmpFile($folder->getGlobalName()); + + // TODO: handle class instances for $message + if (is_resource($message) && get_resource_type($message) == 'stream') { + stream_copy_to_stream($message, $tempFile['handle']); + } else { + fwrite($tempFile['handle'], $message); + } + fclose($tempFile['handle']); + + // we're adding the size to the filename for maildir++ + $size = filesize($tempFile['filename']); + if ($size !== false) { + $info = ',S=' . $size . $info; + } + $newFilename = $tempFile['dirname'] . DIRECTORY_SEPARATOR; + $newFilename .= $recent ? 'new' : 'cur'; + $newFilename .= DIRECTORY_SEPARATOR . $tempFile['uniq'] . $info; + + // we're throwing any exception after removing our temp file and saving it to this variable instead + $exception = null; + + if (! link($tempFile['filename'], $newFilename)) { + $exception = new StorageException\RuntimeException('cannot link message file to final dir'); + } + + ErrorHandler::start(E_WARNING); + unlink($tempFile['filename']); + ErrorHandler::stop(); + + if ($exception) { + throw $exception; + } + + $this->files[] = [ + 'uniq' => $tempFile['uniq'], + 'flags' => $flags, + 'filename' => $newFilename, + ]; + if ($this->quota) { + $this->addQuotaEntry((int) $size, 1); + } + } + + /** + * copy an existing message + * + * @param int $id number of message + * @param string|\Laminas\Mail\Storage\Folder $folder name or instance of targer folder + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + */ + public function copyMessage($id, $folder) + { + if ($this->quota && $this->checkQuota()) { + throw new StorageException\RuntimeException('storage is over quota!'); + } + + if (! ($folder instanceof Folder)) { + $folder = $this->getFolders($folder); + } + + $filedata = $this->getFileData($id); + $oldFile = $filedata['filename']; + $flags = $filedata['flags']; + + // copied message can't be recent + while (($key = array_search(Storage::FLAG_RECENT, $flags)) !== false) { + unset($flags[$key]); + } + $info = $this->getInfoString($flags); + + // we're creating the copy as temp file before moving to cur/ + $tempFile = $this->createTmpFile($folder->getGlobalName()); + // we don't write directly to the file + fclose($tempFile['handle']); + + // we're adding the size to the filename for maildir++ + $size = filesize($oldFile); + if ($size !== false) { + $info = ',S=' . $size . $info; + } + + $newFile = $tempFile['dirname'] . DIRECTORY_SEPARATOR . 'cur' . DIRECTORY_SEPARATOR . $tempFile['uniq'] . $info; + + // we're throwing any exception after removing our temp file and saving it to this variable instead + $exception = null; + + if (! copy($oldFile, $tempFile['filename'])) { + $exception = new StorageException\RuntimeException('cannot copy message file'); + } elseif (! link($tempFile['filename'], $newFile)) { + $exception = new StorageException\RuntimeException('cannot link message file to final dir'); + } + + ErrorHandler::start(E_WARNING); + unlink($tempFile['filename']); + ErrorHandler::stop(); + + if ($exception) { + throw $exception; + } + + if ($folder->getGlobalName() == $this->currentFolder + || ($this->currentFolder == 'INBOX' && $folder->getGlobalName() == '/') + ) { + $this->files[] = [ + 'uniq' => $tempFile['uniq'], + 'flags' => $flags, + 'filename' => $newFile, + ]; + } + + if ($this->quota) { + $this->addQuotaEntry((int) $size, 1); + } + } + + /** + * move an existing message + * + * @param int $id number of message + * @param string|\Laminas\Mail\Storage\Folder $folder name or instance of targer folder + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + */ + public function moveMessage($id, $folder) + { + if (! ($folder instanceof Folder)) { + $folder = $this->getFolders($folder); + } + + if ($folder->getGlobalName() == $this->currentFolder + || ($this->currentFolder == 'INBOX' && $folder->getGlobalName() == '/') + ) { + throw new StorageException\RuntimeException('target is current folder'); + } + + $filedata = $this->getFileData($id); + $oldFile = $filedata['filename']; + $flags = $filedata['flags']; + + // moved message can't be recent + while (($key = array_search(Storage::FLAG_RECENT, $flags)) !== false) { + unset($flags[$key]); + } + $info = $this->getInfoString($flags); + + // reserving a new name + $tempFile = $this->createTmpFile($folder->getGlobalName()); + fclose($tempFile['handle']); + + // we're adding the size to the filename for maildir++ + $size = filesize($oldFile); + if ($size !== false) { + $info = ',S=' . $size . $info; + } + + $newFile = $tempFile['dirname'] . DIRECTORY_SEPARATOR . 'cur' . DIRECTORY_SEPARATOR . $tempFile['uniq'] . $info; + + // we're throwing any exception after removing our temp file and saving it to this variable instead + $exception = null; + + if (! rename($oldFile, $newFile)) { + $exception = new StorageException\RuntimeException('cannot move message file'); + } + + ErrorHandler::start(E_WARNING); + unlink($tempFile['filename']); + ErrorHandler::stop(); + + if ($exception) { + throw $exception; + } + + unset($this->files[$id - 1]); + // remove the gap + $this->files = array_values($this->files); + } + + /** + * set flags for message + * + * NOTE: this method can't set the recent flag. + * + * @param int $id number of message + * @param array $flags new flags for message + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + */ + public function setFlags($id, $flags) + { + $info = $this->getInfoString($flags); + $filedata = $this->getFileData($id); + + // NOTE: double dirname to make sure we always move to cur. if recent + // flag has been set (message is in new) it will be moved to cur. + $newFilename = dirname($filedata['filename'], 2) + . DIRECTORY_SEPARATOR + . 'cur' + . DIRECTORY_SEPARATOR + . "$filedata[uniq]$info"; + + ErrorHandler::start(); + $test = rename($filedata['filename'], $newFilename); + $error = ErrorHandler::stop(); + if (! $test) { + throw new StorageException\RuntimeException('cannot rename file', 0, $error); + } + + $filedata['flags'] = $flags; + $filedata['filename'] = $newFilename; + + $this->files[$id - 1] = $filedata; + } + + /** + * stub for not supported message deletion + * + * @param $id + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + */ + public function removeMessage($id) + { + $filename = $this->getFileData($id, 'filename'); + + if ($this->quota) { + $size = filesize($filename); + } + + ErrorHandler::start(); + $test = unlink($filename); + $error = ErrorHandler::stop(); + if (! $test) { + throw new StorageException\RuntimeException('cannot remove message', 0, $error); + } + unset($this->files[$id - 1]); + // remove the gap + $this->files = array_values($this->files); + if ($this->quota) { + $this->addQuotaEntry(0 - (int) $size, -1); + } + } + + /** + * enable/disable quota and set a quota value if wanted or needed + * + * You can enable/disable quota with true/false. If you don't have + * a MDA or want to enforce a quota value you can also set this value + * here. Use array('size' => SIZE_QUOTA, 'count' => MAX_MESSAGE) do + * define your quota. Order of these fields does matter! + * + * @param bool|array $value new quota value + */ + public function setQuota($value) + { + $this->quota = $value; + } + + /** + * get currently set quota + * + * @see \Laminas\Mail\Storage\Writable\Maildir::setQuota() + * @param bool $fromStorage + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + * @return bool|array + */ + public function getQuota($fromStorage = false) + { + if ($fromStorage) { + ErrorHandler::start(E_WARNING); + $fh = fopen($this->rootdir . 'maildirsize', 'r'); + $error = ErrorHandler::stop(); + if (! $fh) { + throw new StorageException\RuntimeException('cannot open maildirsize', 0, $error); + } + $definition = fgets($fh); + fclose($fh); + $definition = explode(',', trim($definition)); + $quota = []; + foreach ($definition as $member) { + $key = $member[strlen($member) - 1]; + if ($key == 'S' || $key == 'C') { + $key = $key == 'C' ? 'count' : 'size'; + } + $quota[$key] = substr($member, 0, -1); + } + return $quota; + } + + return $this->quota; + } + + /** + * @see http://www.inter7.com/courierimap/README.maildirquota.html "Calculating maildirsize" + * @throws \Laminas\Mail\Storage\Exception\RuntimeException + * @return array + */ + protected function calculateMaildirsize() + { + $timestamps = []; + $messages = 0; + $totalSize = 0; + + if (is_array($this->quota)) { + $quota = $this->quota; + } else { + try { + $quota = $this->getQuota(true); + } catch (StorageException\ExceptionInterface $e) { + throw new StorageException\RuntimeException('no quota definition found', 0, $e); + } + } + + $folders = new RecursiveIteratorIterator($this->getFolders(), RecursiveIteratorIterator::SELF_FIRST); + foreach ($folders as $folder) { + $subdir = $folder->getGlobalName(); + if ($subdir == 'INBOX') { + $subdir = ''; + } else { + $subdir = '.' . $subdir; + } + if ($subdir == 'Trash') { + continue; + } + + foreach (['cur', 'new'] as $subsubdir) { + $dirname = $this->rootdir . $subdir . DIRECTORY_SEPARATOR . $subsubdir . DIRECTORY_SEPARATOR; + if (! file_exists($dirname)) { + continue; + } + // NOTE: we are using mtime instead of "the latest timestamp". The latest would be atime + // and as we are accessing the directory it would make the whole calculation useless. + $timestamps[$dirname] = filemtime($dirname); + + $dh = opendir($dirname); + // NOTE: Should have been checked in constructor. Not throwing an exception here, quotas will + // therefore not be fully enforced, but next request will fail anyway, if problem persists. + if (! $dh) { + continue; + } + + while (($entry = readdir()) !== false) { + if ($entry[0] == '.' || ! is_file($dirname . $entry)) { + continue; + } + + if (strpos($entry, ',S=')) { + strtok($entry, '='); + $filesize = strtok(':'); + if (is_numeric($filesize)) { + $totalSize += $filesize; + ++$messages; + continue; + } + } + $size = filesize($dirname . $entry); + if ($size === false) { + // ignore, as we assume file got removed + continue; + } + $totalSize += $size; + ++$messages; + } + } + } + + $tmp = $this->createTmpFile(); + $fh = $tmp['handle']; + $definition = []; + foreach ($quota as $type => $value) { + if ($type == 'size' || $type == 'count') { + $type = $type == 'count' ? 'C' : 'S'; + } + $definition[] = $value . $type; + } + $definition = implode(',', $definition); + fwrite($fh, "$definition\n"); + fwrite($fh, "$totalSize $messages\n"); + fclose($fh); + rename($tmp['filename'], $this->rootdir . 'maildirsize'); + foreach ($timestamps as $dir => $timestamp) { + if ($timestamp < filemtime($dir)) { + unlink($this->rootdir . 'maildirsize'); + break; + } + } + + return [ + 'size' => $totalSize, + 'count' => $messages, + 'quota' => $quota, + ]; + } + + /** + * @see http://www.inter7.com/courierimap/README.maildirquota.html "Calculating the quota for a Maildir++" + * @param bool $forceRecalc + * @return array + */ + protected function calculateQuota($forceRecalc = false) + { + $fh = null; + $totalSize = 0; + $messages = 0; + $maildirsize = ''; + if (! $forceRecalc + && file_exists($this->rootdir . 'maildirsize') + && filesize($this->rootdir . 'maildirsize') < 5120 + ) { + $fh = fopen($this->rootdir . 'maildirsize', 'r'); + } + if ($fh) { + $maildirsize = fread($fh, 5120); + if (strlen($maildirsize) >= 5120) { + fclose($fh); + $fh = null; + $maildirsize = ''; + } + } + if (! $fh) { + $result = $this->calculateMaildirsize(); + $totalSize = $result['size']; + $messages = $result['count']; + $quota = $result['quota']; + } else { + $maildirsize = explode("\n", $maildirsize); + if (is_array($this->quota)) { + $quota = $this->quota; + } else { + $definition = explode(',', $maildirsize[0]); + $quota = []; + foreach ($definition as $member) { + $key = $member[strlen($member) - 1]; + if ($key == 'S' || $key == 'C') { + $key = $key == 'C' ? 'count' : 'size'; + } + $quota[$key] = substr($member, 0, -1); + } + } + unset($maildirsize[0]); + foreach ($maildirsize as $line) { + list($size, $count) = explode(' ', trim($line)); + $totalSize += $size; + $messages += $count; + } + } + + $overQuota = false; + $overQuota = $overQuota || (isset($quota['size']) && $totalSize > $quota['size']); + $overQuota = $overQuota || (isset($quota['count']) && $messages > $quota['count']); + // NOTE: $maildirsize equals false if it wasn't set (AKA we recalculated) or it's only + // one line, because $maildirsize[0] gets unsetted. + // Also we're using local time to calculate the 15 minute offset. Touching a file just for known the + // local time of the file storage isn't worth the hassle. + if ($overQuota && ($maildirsize || filemtime($this->rootdir . 'maildirsize') > time() - 900)) { + $result = $this->calculateMaildirsize(); + $totalSize = $result['size']; + $messages = $result['count']; + $quota = $result['quota']; + $overQuota = false; + $overQuota = $overQuota || (isset($quota['size']) && $totalSize > $quota['size']); + $overQuota = $overQuota || (isset($quota['count']) && $messages > $quota['count']); + } + + if ($fh) { + // TODO is there a safe way to keep the handle open for writing? + fclose($fh); + } + + return [ + 'size' => $totalSize, + 'count' => $messages, + 'quota' => $quota, + 'over_quota' => $overQuota, + ]; + } + + protected function addQuotaEntry($size, $count = 1) + { + if (! file_exists($this->rootdir . 'maildirsize')) { + // TODO: should get file handler from calculateQuota + } + $size = (int) $size; + $count = (int) $count; + file_put_contents($this->rootdir . 'maildirsize', "$size $count\n", FILE_APPEND); + } + + /** + * check if storage is currently over quota + * + * @see calculateQuota() + * @param bool $detailedResponse return known data of quota and current size and message count + * @param bool $forceRecalc + * @return bool|array over quota state or detailed response + */ + public function checkQuota($detailedResponse = false, $forceRecalc = false) + { + $result = $this->calculateQuota($forceRecalc); + return $detailedResponse ? $result : $result['over_quota']; + } +} diff --git a/lib/laminas/laminas-mail/src/Storage/Writable/WritableInterface.php b/lib/laminas/laminas-mail/src/Storage/Writable/WritableInterface.php new file mode 100644 index 0000000000..9a005ba3b9 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Storage/Writable/WritableInterface.php @@ -0,0 +1,86 @@ +from; + } + + /** + * Set MAIL FROM + * + * @param string $from + */ + public function setFrom($from) + { + $this->from = (string) $from; + } + + /** + * Get RCPT TO + * + * @return string|null + */ + public function getTo() + { + return $this->to; + } + + /** + * Set RCPT TO + * + * @param string $to + */ + public function setTo($to) + { + $this->to = $to; + } +} diff --git a/lib/laminas/laminas-mail/src/Transport/Exception/DomainException.php b/lib/laminas/laminas-mail/src/Transport/Exception/DomainException.php new file mode 100644 index 0000000000..d92b9d1228 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Transport/Exception/DomainException.php @@ -0,0 +1,12 @@ + File::class, + 'inmemory' => InMemory::class, + 'memory' => InMemory::class, + 'null' => InMemory::class, + 'sendmail' => Sendmail::class, + 'smtp' => Smtp::class, + ]; + + /** + * @param array $spec + * @return TransportInterface + * @throws Exception\InvalidArgumentException + * @throws Exception\DomainException + */ + public static function create($spec = []) + { + if ($spec instanceof Traversable) { + $spec = ArrayUtils::iteratorToArray($spec); + } + + if (! is_array($spec)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects an array or Traversable argument; received "%s"', + __METHOD__, + (is_object($spec) ? get_class($spec) : gettype($spec)) + )); + } + + $type = $spec['type'] ?? 'sendmail'; + + $normalizedType = strtolower($type); + + if (isset(static::$classMap[$normalizedType])) { + $type = static::$classMap[$normalizedType]; + } + + if (! class_exists($type)) { + throw new Exception\DomainException(sprintf( + '%s expects the "type" attribute to resolve to an existing class; received "%s"', + __METHOD__, + $type + )); + } + + $transport = new $type(); + + if (! $transport instanceof TransportInterface) { + throw new Exception\DomainException(sprintf( + '%s expects the "type" attribute to resolve to a valid %s instance; received "%s"', + __METHOD__, + TransportInterface::class, + $type + )); + } + + if ($transport instanceof Smtp && isset($spec['options'])) { + $transport->setOptions(new SmtpOptions($spec['options'])); + } + + if ($transport instanceof File && isset($spec['options'])) { + $transport->setOptions(new FileOptions($spec['options'])); + } + + return $transport; + } +} diff --git a/lib/laminas/laminas-mail/src/Transport/File.php b/lib/laminas/laminas-mail/src/Transport/File.php new file mode 100644 index 0000000000..7e772bb7f5 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Transport/File.php @@ -0,0 +1,90 @@ +setOptions($options); + } + + /** + * @return FileOptions + */ + public function getOptions() + { + return $this->options; + } + + /** + * Sets options + * + * @param FileOptions $options + */ + public function setOptions(FileOptions $options) + { + $this->options = $options; + } + + /** + * Saves e-mail message to a file + * + * @param Message $message + * @throws Exception\RuntimeException on not writable target directory or + * on file_put_contents() failure + */ + public function send(Message $message) + { + $options = $this->options; + $filename = $options->getCallback()($this); + $file = $options->getPath() . DIRECTORY_SEPARATOR . $filename; + $email = $message->toString(); + + if (false === file_put_contents($file, $email)) { + throw new Exception\RuntimeException(sprintf( + 'Unable to write mail to file (directory "%s")', + $options->getPath() + )); + } + + $this->lastFile = $file; + } + + /** + * Get the name of the last file written to + * + * @return string + */ + public function getLastFile() + { + return $this->lastFile; + } +} diff --git a/lib/laminas/laminas-mail/src/Transport/FileOptions.php b/lib/laminas/laminas-mail/src/Transport/FileOptions.php new file mode 100644 index 0000000000..d6a811ed88 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Transport/FileOptions.php @@ -0,0 +1,89 @@ +path = $path; + return $this; + } + + /** + * Get path + * + * If none is set, uses value from sys_get_temp_dir() + * + * @return string + */ + public function getPath() + { + if (null === $this->path) { + $this->setPath(sys_get_temp_dir()); + } + return $this->path; + } + + /** + * Set callback used to generate a file name + * + * @param callable $callback + * @throws \Laminas\Mail\Exception\InvalidArgumentException + * @return FileOptions + */ + public function setCallback($callback) + { + if (! is_callable($callback)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a valid callback; received "%s"', + __METHOD__, + (is_object($callback) ? get_class($callback) : gettype($callback)) + )); + } + $this->callback = $callback; + return $this; + } + + /** + * Get callback used to generate a file name + * + * @return callable + */ + public function getCallback() + { + if (null === $this->callback) { + $this->setCallback(function () { + return 'LaminasMail_' . time() . '_' . mt_rand() . '.eml'; + }); + } + return $this->callback; + } +} diff --git a/lib/laminas/laminas-mail/src/Transport/InMemory.php b/lib/laminas/laminas-mail/src/Transport/InMemory.php new file mode 100644 index 0000000000..49caf6adb1 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Transport/InMemory.php @@ -0,0 +1,40 @@ +lastMessage = $message; + } + + /** + * Get the last message sent. + * + * @return null|Message + */ + public function getLastMessage() + { + return $this->lastMessage; + } +} diff --git a/lib/laminas/laminas-mail/src/Transport/Sendmail.php b/lib/laminas/laminas-mail/src/Transport/Sendmail.php new file mode 100644 index 0000000000..8072820173 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Transport/Sendmail.php @@ -0,0 +1,330 @@ +setParameters($parameters); + } + $this->callable = [$this, 'mailHandler']; + } + + /** + * Set sendmail parameters + * + * Used to populate the additional_parameters argument to mail() + * + * @param null|string|array|Traversable $parameters + * @throws \Laminas\Mail\Transport\Exception\InvalidArgumentException + * @return Sendmail + */ + public function setParameters($parameters) + { + if ($parameters === null || is_string($parameters)) { + $this->parameters = $parameters; + return $this; + } + + if (! is_array($parameters) && ! $parameters instanceof Traversable) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a string, array, or Traversable object of parameters; received "%s"', + __METHOD__, + (is_object($parameters) ? get_class($parameters) : gettype($parameters)) + )); + } + + $string = ''; + foreach ($parameters as $param) { + $string .= ' ' . $param; + } + + $this->parameters = trim($string); + return $this; + } + + /** + * Set callback to use for mail + * + * Primarily for testing purposes, but could be used to curry arguments. + * + * @param callable $callable + * @throws \Laminas\Mail\Transport\Exception\InvalidArgumentException + * @return Sendmail + */ + public function setCallable($callable) + { + if (! is_callable($callable)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a callable argument; received "%s"', + __METHOD__, + (is_object($callable) ? get_class($callable) : gettype($callable)) + )); + } + $this->callable = $callable; + return $this; + } + + /** + * Send a message + * + * @param \Laminas\Mail\Message $message + */ + public function send(Mail\Message $message) + { + $to = $this->prepareRecipients($message); + $subject = $this->prepareSubject($message); + $body = $this->prepareBody($message); + $headers = $this->prepareHeaders($message); + $params = $this->prepareParameters($message); + + // On *nix platforms, we need to replace \r\n with \n + // sendmail is not an SMTP server, it is a unix command - it expects LF + if (PHP_VERSION_ID < 80000 && ! $this->isWindowsOs()) { + $to = str_replace("\r\n", "\n", $to); + $subject = str_replace("\r\n", "\n", $subject); + $body = str_replace("\r\n", "\n", $body); + $headers = str_replace("\r\n", "\n", $headers); + } + + ($this->callable)($to, $subject, $body, $headers, $params); + } + + /** + * Prepare recipients list + * + * @param \Laminas\Mail\Message $message + * @throws \Laminas\Mail\Transport\Exception\RuntimeException + * @return string + */ + protected function prepareRecipients(Mail\Message $message) + { + $headers = $message->getHeaders(); + + $hasTo = $headers->has('to'); + if (! $hasTo && ! $headers->has('cc') && ! $headers->has('bcc')) { + throw new Exception\RuntimeException( + 'Invalid email; contains no at least one of "To", "Cc", and "Bcc" header' + ); + } + + if (! $hasTo) { + return ''; + } + + /** @var Mail\Header\To $to */ + $to = $headers->get('to'); + $list = $to->getAddressList(); + if (0 == count($list)) { + throw new Exception\RuntimeException('Invalid "To" header; contains no addresses'); + } + + // If not on Windows, return normal string + if (! $this->isWindowsOs()) { + return $to->getFieldValue(HeaderInterface::FORMAT_ENCODED); + } + + // Otherwise, return list of emails + $addresses = []; + foreach ($list as $address) { + $addresses[] = $address->getEmail(); + } + $addresses = implode(', ', $addresses); + return $addresses; + } + + /** + * Prepare the subject line string + * + * @param \Laminas\Mail\Message $message + * @return string + */ + protected function prepareSubject(Mail\Message $message) + { + $headers = $message->getHeaders(); + if (! $headers->has('subject')) { + return; + } + $header = $headers->get('subject'); + return $header->getFieldValue(HeaderInterface::FORMAT_ENCODED); + } + + /** + * Prepare the body string + * + * @param \Laminas\Mail\Message $message + * @return string + */ + protected function prepareBody(Mail\Message $message) + { + if (! $this->isWindowsOs()) { + // *nix platforms can simply return the body text + return $message->getBodyText(); + } + + // On windows, lines beginning with a full stop need to be fixed + $text = $message->getBodyText(); + $text = str_replace("\n.", "\n..", $text); + return $text; + } + + /** + * Prepare the textual representation of headers + * + * @param \Laminas\Mail\Message $message + * @return string + */ + protected function prepareHeaders(Mail\Message $message) + { + // Strip the "to" and "subject" headers + $headers = clone $message->getHeaders(); + $headers->removeHeader('To'); + $headers->removeHeader('Subject'); + + /** @var Mail\Header\From $from Sanitize the From header*/ + $from = $headers->get('From'); + if ($from) { + foreach ($from->getAddressList() as $address) { + if (strpos($address->getEmail(), '\\"') !== false) { + throw new Exception\RuntimeException('Potential code injection in From header'); + } + } + } + return $headers->toString(); + } + + /** + * Prepare additional_parameters argument + * + * Basically, overrides the MAIL FROM envelope with either the Sender or + * From address. + * + * @param \Laminas\Mail\Message $message + * @return string + */ + protected function prepareParameters(Mail\Message $message) + { + if ($this->isWindowsOs()) { + return; + } + + $parameters = (string) $this->parameters; + if (preg_match('/(^| )\-f.+/', $parameters)) { + return $parameters; + } + + $sender = $message->getSender(); + if ($sender instanceof AddressInterface) { + $parameters .= ' -f' . \escapeshellarg($sender->getEmail()); + return $parameters; + } + + $from = $message->getFrom(); + if (count($from)) { + $from->rewind(); + $sender = $from->current(); + $parameters .= ' -f' . \escapeshellarg($sender->getEmail()); + return $parameters; + } + + return $parameters; + } + + /** + * Send mail using PHP native mail() + * + * @param string $to + * @param string $subject + * @param string $message + * @param string $headers + * @param $parameters + * @throws \Laminas\Mail\Transport\Exception\RuntimeException + */ + public function mailHandler($to, $subject, $message, $headers, $parameters) + { + set_error_handler([$this, 'handleMailErrors']); + if ($parameters === null) { + $result = mail($to, $subject, $message, $headers); + } else { + $result = mail($to, $subject, $message, $headers, $parameters); + } + restore_error_handler(); + + if ($this->errstr !== null || ! $result) { + $errstr = $this->errstr; + if (empty($errstr)) { + $errstr = 'Unknown error'; + } + throw new Exception\RuntimeException('Unable to send mail: ' . $errstr); + } + } + + /** + * Temporary error handler for PHP native mail(). + * + * @param int $errno + * @param string $errstr + * @param string $errfile + * @param string $errline + * @param array $errcontext + * @return bool always true + */ + public function handleMailErrors($errno, $errstr, $errfile = null, $errline = null, array $errcontext = null) + { + $this->errstr = $errstr; + return true; + } + + /** + * Is this a windows OS? + * + * @return bool + */ + protected function isWindowsOs() + { + if (! $this->operatingSystem) { + $this->operatingSystem = strtoupper(substr(PHP_OS, 0, 3)); + } + return ($this->operatingSystem == 'WIN'); + } +} diff --git a/lib/laminas/laminas-mail/src/Transport/Smtp.php b/lib/laminas/laminas-mail/src/Transport/Smtp.php new file mode 100644 index 0000000000..51e5446972 --- /dev/null +++ b/lib/laminas/laminas-mail/src/Transport/Smtp.php @@ -0,0 +1,400 @@ +setOptions($options); + } + + /** + * Set options + * + * @param SmtpOptions $options + * @return Smtp + */ + public function setOptions(SmtpOptions $options) + { + $this->options = $options; + return $this; + } + + /** + * Get options + * + * @return SmtpOptions + */ + public function getOptions() + { + return $this->options; + } + + /** + * Set options + * + * @param Envelope $envelope + */ + public function setEnvelope(Envelope $envelope) + { + $this->envelope = $envelope; + } + + /** + * Get envelope + * + * @return Envelope|null + */ + public function getEnvelope() + { + return $this->envelope; + } + + /** + * Set plugin manager for obtaining SMTP protocol connection + * + * @param Protocol\SmtpPluginManager $plugins + * @throws Exception\InvalidArgumentException + * @return Smtp + */ + public function setPluginManager(Protocol\SmtpPluginManager $plugins) + { + $this->plugins = $plugins; + return $this; + } + + /** + * Get plugin manager for loading SMTP protocol connection + * + * @return Protocol\SmtpPluginManager + */ + public function getPluginManager() + { + if (null === $this->plugins) { + $this->setPluginManager(new Protocol\SmtpPluginManager(new ServiceManager())); + } + return $this->plugins; + } + + /** + * Set the automatic disconnection when destruct + * + * @param bool $flag + * @return Smtp + */ + public function setAutoDisconnect($flag) + { + $this->autoDisconnect = (bool) $flag; + return $this; + } + + /** + * Get the automatic disconnection value + * + * @return bool + */ + public function getAutoDisconnect() + { + return $this->autoDisconnect; + } + + /** + * Return an SMTP connection + * + * @param string $name + * @param array|null $options + * @return Protocol\Smtp + */ + public function plugin($name, array $options = null) + { + return $this->getPluginManager()->get($name, $options); + } + + /** + * Class destructor to ensure all open connections are closed + */ + public function __destruct() + { + if (! $this->getConnection() instanceof Protocol\Smtp) { + return; + } + + try { + $this->getConnection()->quit(); + } catch (ProtocolException\ExceptionInterface $e) { + // ignore + } + + if ($this->autoDisconnect) { + $this->getConnection()->disconnect(); + } + } + + /** + * Sets the connection protocol instance + * + * @param Protocol\AbstractProtocol $connection + */ + public function setConnection(Protocol\AbstractProtocol $connection) + { + $this->connection = $connection; + if (($connection instanceof Protocol\Smtp) + && ($this->getOptions()->getConnectionTimeLimit() !== null) + ) { + $connection->setUseCompleteQuit(false); + } + } + + /** + * Gets the connection protocol instance + * + * @return Protocol\Smtp + */ + public function getConnection() + { + $timeLimit = $this->getOptions()->getConnectionTimeLimit(); + if ($timeLimit !== null + && $this->connectedTime !== null + && ((time() - $this->connectedTime) > $timeLimit) + ) { + $this->connection = null; + } + return $this->connection; + } + + /** + * Disconnect the connection protocol instance + * + * @return void + */ + public function disconnect() + { + if ($this->getConnection() instanceof Protocol\Smtp) { + $this->getConnection()->disconnect(); + $this->connectedTime = null; + } + } + + /** + * Send an email via the SMTP connection protocol + * + * The connection via the protocol adapter is made just-in-time to allow a + * developer to add a custom adapter if required before mail is sent. + * + * @param Message $message + * @throws Exception\RuntimeException + */ + public function send(Message $message) + { + // If sending multiple messages per session use existing adapter + $connection = $this->getConnection(); + + if (! ($connection instanceof Protocol\Smtp) || ! $connection->hasSession()) { + $connection = $this->connect(); + } else { + // Reset connection to ensure reliable transaction + $connection->rset(); + } + + // Prepare message + $from = $this->prepareFromAddress($message); + $recipients = $this->prepareRecipients($message); + $headers = $this->prepareHeaders($message); + $body = $this->prepareBody($message); + + if ((count($recipients) == 0) && (! empty($headers) || ! empty($body))) { + // Per RFC 2821 3.3 (page 18) + throw new Exception\RuntimeException( + sprintf( + '%s transport expects at least one recipient if the message has at least one header or body', + __CLASS__ + ) + ); + } + + // Set sender email address + $connection->mail($from); + + // Set recipient forward paths + foreach ($recipients as $recipient) { + $connection->rcpt($recipient); + } + + // Issue DATA command to client + $connection->data($headers . Headers::EOL . $body); + } + + /** + * Retrieve email address for envelope FROM + * + * @param Message $message + * @throws Exception\RuntimeException + * @return string + */ + protected function prepareFromAddress(Message $message) + { + if ($this->getEnvelope() && $this->getEnvelope()->getFrom()) { + return $this->getEnvelope()->getFrom(); + } + + $sender = $message->getSender(); + if ($sender instanceof Address\AddressInterface) { + return $sender->getEmail(); + } + + $from = $message->getFrom(); + if (! count($from)) { + // Per RFC 2822 3.6 + throw new Exception\RuntimeException(sprintf( + '%s transport expects either a Sender or at least one From address in the Message; none provided', + __CLASS__ + )); + } + + $from->rewind(); + $sender = $from->current(); + return $sender->getEmail(); + } + + /** + * Prepare array of email address recipients + * + * @param Message $message + * @return array + */ + protected function prepareRecipients(Message $message) + { + if ($this->getEnvelope() && $this->getEnvelope()->getTo()) { + return (array) $this->getEnvelope()->getTo(); + } + + $recipients = []; + foreach ($message->getTo() as $address) { + $recipients[] = $address->getEmail(); + } + foreach ($message->getCc() as $address) { + $recipients[] = $address->getEmail(); + } + foreach ($message->getBcc() as $address) { + $recipients[] = $address->getEmail(); + } + + $recipients = array_unique($recipients); + return $recipients; + } + + /** + * Prepare header string from message + * + * @param Message $message + * @return string + */ + protected function prepareHeaders(Message $message) + { + $headers = clone $message->getHeaders(); + $headers->removeHeader('Bcc'); + return $headers->toString(); + } + + /** + * Prepare body string from message + * + * @param Message $message + * @return string + */ + protected function prepareBody(Message $message) + { + return $message->getBodyText(); + } + + /** + * Lazy load the connection + * + * @return Protocol\Smtp + */ + protected function lazyLoadConnection() + { + // Check if authentication is required and determine required class + $options = $this->getOptions(); + $config = $options->getConnectionConfig(); + $config['host'] = $options->getHost(); + $config['port'] = $options->getPort(); + + $this->setConnection($this->plugin($options->getConnectionClass(), $config)); + + return $this->connect(); + } + + /** + * Connect the connection, and pass it helo + * + * @return Protocol\Smtp + */ + protected function connect() + { + if (! $this->connection instanceof Protocol\Smtp) { + return $this->lazyLoadConnection(); + } + + $this->connection->connect(); + + $this->connectedTime = time(); + + $this->connection->helo($this->getOptions()->getName()); + + return $this->connection; + } +} diff --git a/lib/laminas/laminas-mail/src/Transport/SmtpOptions.php b/lib/laminas/laminas-mail/src/Transport/SmtpOptions.php new file mode 100644 index 0000000000..268d2ba4ff --- /dev/null +++ b/lib/laminas/laminas-mail/src/Transport/SmtpOptions.php @@ -0,0 +1,203 @@ +name; + } + + /** + * Set the local client hostname or IP + * + * @todo hostname/IP validation + * @param string $name + * @throws \Laminas\Mail\Exception\InvalidArgumentException + * @return SmtpOptions + */ + public function setName($name) + { + if (! is_string($name) && $name !== null) { + throw new Exception\InvalidArgumentException(sprintf( + 'Name must be a string or null; argument of type "%s" provided', + (is_object($name) ? get_class($name) : gettype($name)) + )); + } + $this->name = $name; + return $this; + } + + /** + * Get connection class + * + * This should be either the class Laminas\Mail\Protocol\Smtp or a class + * extending it -- typically a class in the Laminas\Mail\Protocol\Smtp\Auth + * namespace. + * + * @return string + */ + public function getConnectionClass() + { + return $this->connectionClass; + } + + /** + * Set connection class + * + * @param string $connectionClass the value to be set + * @throws \Laminas\Mail\Exception\InvalidArgumentException + * @return SmtpOptions + */ + public function setConnectionClass($connectionClass) + { + if (! is_string($connectionClass) && $connectionClass !== null) { + throw new Exception\InvalidArgumentException(sprintf( + 'Connection class must be a string or null; argument of type "%s" provided', + (is_object($connectionClass) ? get_class($connectionClass) : gettype($connectionClass)) + )); + } + $this->connectionClass = $connectionClass; + return $this; + } + + /** + * Get connection configuration array + * + * @return array + */ + public function getConnectionConfig() + { + return $this->connectionConfig; + } + + /** + * Set connection configuration array + * + * @param array $connectionConfig + * @return SmtpOptions + */ + public function setConnectionConfig(array $connectionConfig) + { + $this->connectionConfig = $connectionConfig; + return $this; + } + + /** + * Get the host name + * + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * Set the SMTP host + * + * @todo hostname/IP validation + * @param string $host + * @return SmtpOptions + */ + public function setHost($host) + { + $this->host = (string) $host; + return $this; + } + + /** + * Get the port the SMTP server runs on + * + * @return int + */ + public function getPort() + { + return $this->port; + } + + /** + * Set the port the SMTP server runs on + * + * @param int $port + * @throws \Laminas\Mail\Exception\InvalidArgumentException + * @return SmtpOptions + */ + public function setPort($port) + { + $port = (int) $port; + if ($port < 1) { + throw new Exception\InvalidArgumentException(sprintf( + 'Port must be greater than 1; received "%d"', + $port + )); + } + $this->port = $port; + return $this; + } + + /** + * @return int|null + */ + public function getConnectionTimeLimit() + { + return $this->connectionTimeLimit; + } + + /** + * @param int|null $seconds + * @return self + */ + public function setConnectionTimeLimit($seconds) + { + $this->connectionTimeLimit = $seconds === null + ? null + : (int) $seconds; + + return $this; + } +} diff --git a/lib/laminas/laminas-mail/src/Transport/TransportInterface.php b/lib/laminas/laminas-mail/src/Transport/TransportInterface.php new file mode 100644 index 0000000000..274d44e40a --- /dev/null +++ b/lib/laminas/laminas-mail/src/Transport/TransportInterface.php @@ -0,0 +1,19 @@ + This package is considered feature-complete, and is now in **security-only** maintenance mode, following a [decision by the Technical Steering Committee](https://github.com/laminas/technical-steering-committee/blob/2b55453e172a1b8c9c4c212be7cf7e7a58b9352c/meetings/minutes/2020-08-03-TSC-Minutes.md#vote-on-components-to-mark-as-security-only). +> If you have a security issue, please [follow our security reporting guidelines](https://getlaminas.org/security/). +> If you wish to take on the role of maintainer, please [nominate yourself](https://github.com/laminas/technical-steering-committee/issues/new?assignees=&labels=Nomination&template=Maintainer_Nomination.md&title=%5BNOMINATION%5D%5BMAINTAINER%5D%3A+%7Bname+of+person+being+nominated%7D) +> +> If you are looking for an actively maintained package alternative, we recommend: +> +> - [symfony/mime](https://symfony.com/doc/current/components/mime.html) + +[![Build Status](https://github.com/laminas/laminas-mime/workflows/Continuous%20Integration/badge.svg)](https://github.com/laminas/laminas-mime/actions?query=workflow%3A"Continuous+Integration") + +`Laminas\Mime` is a support class for handling multipart MIME messages. +It is used by `Laminas\Mail` and `Laminas\Mime\Message` and may be used by applications requiring MIME support. + +## Installation + +Run the following to install this library: + +```bash +$ composer require laminas/laminas-mime +``` + +## Documentation + +Browse the documentation online at https://docs.laminas.dev/laminas-mime/ + +## Support + +- [Issues](https://github.com/laminas/laminas-mime/issues/) +- [Chat](https://laminas.dev/chat/) +- [Forum](https://discourse.laminas.dev/) diff --git a/lib/laminas/laminas-mime/composer.json b/lib/laminas/laminas-mime/composer.json new file mode 100644 index 0000000000..abd18699af --- /dev/null +++ b/lib/laminas/laminas-mime/composer.json @@ -0,0 +1,59 @@ +{ + "name": "laminas/laminas-mime", + "description": "Create and parse MIME messages and parts", + "license": "BSD-3-Clause", + "keywords": [ + "laminas", + "mime" + ], + "homepage": "https://laminas.dev", + "support": { + "docs": "https://docs.laminas.dev/laminas-mime/", + "issues": "https://github.com/laminas/laminas-mime/issues", + "source": "https://github.com/laminas/laminas-mime", + "rss": "https://github.com/laminas/laminas-mime/releases.atom", + "chat": "https://laminas.dev/chat", + "forum": "https://discourse.laminas.dev" + }, + "config": { + "sort-packages": true + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.2.1", + "laminas/laminas-mail": "^2.12", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "laminas/laminas-mail": "Laminas\\Mail component" + }, + "autoload": { + "psr-4": { + "Laminas\\Mime\\": "src/" + } + }, + "autoload-dev": { + "files": [ + "test/TestAsset/Mail/Headers.php" + ], + "psr-4": { + "LaminasTest\\Mime\\": "test/" + } + }, + "scripts": { + "check": [ + "@cs-check", + "@test" + ], + "cs-check": "phpcs", + "cs-fix": "phpcbf", + "test": "phpunit --colors=always", + "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" + }, + "conflict": { + "zendframework/zend-mime": "*" + } +} diff --git a/lib/laminas/laminas-mime/composer.lock b/lib/laminas/laminas-mime/composer.lock new file mode 100644 index 0000000000..aeeb2d3726 --- /dev/null +++ b/lib/laminas/laminas-mime/composer.lock @@ -0,0 +1,3023 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "77a8daa5c98d4a651b4fab53280b68e4", + "packages": [ + { + "name": "laminas/laminas-stdlib", + "version": "3.6.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-stdlib.git", + "reference": "c53d8537f108fac3fae652677a19735db730ba46" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/c53d8537f108fac3fae652677a19735db730ba46", + "reference": "c53d8537f108fac3fae652677a19735db730ba46", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-stdlib": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.3.0", + "phpbench/phpbench": "^0.17.1", + "phpunit/phpunit": "~9.3.7", + "psalm/plugin-phpunit": "^0.16.0", + "vimeo/psalm": "^4.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Stdlib\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "SPL extensions, array utilities, error handlers, and more", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "stdlib" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-stdlib/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-stdlib/issues", + "rss": "https://github.com/laminas/laminas-stdlib/releases.atom", + "source": "https://github.com/laminas/laminas-stdlib" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-02T16:11:32+00:00" + } + ], + "packages-dev": [ + { + "name": "container-interop/container-interop", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/container-interop/container-interop.git", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "shasum": "" + }, + "require": { + "psr/container": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "homepage": "https://github.com/container-interop/container-interop", + "support": { + "issues": "https://github.com/container-interop/container-interop/issues", + "source": "https://github.com/container-interop/container-interop/tree/master" + }, + "abandoned": "psr/container", + "time": "2017-02-14T19:40:03+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.1", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "fe390591e0241955f22eb9ba327d137e501c771c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/fe390591e0241955f22eb9ba327d137e501c771c", + "reference": "fe390591e0241955f22eb9ba327d137e501c771c", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "phpcompatibility/php-compatibility": "^9.0", + "sensiolabs/security-checker": "^4.1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2020-12-07T18:04:37+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-11-10T18:47:58+00:00" + }, + { + "name": "laminas/laminas-coding-standard", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-coding-standard.git", + "reference": "c953ecb1d37034f4aa326046e2c24a10fe0a2845" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-coding-standard/zipball/c953ecb1d37034f4aa326046e2c24a10fe0a2845", + "reference": "c953ecb1d37034f4aa326046e2c24a10fe0a2845", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.3 || ~8.0.0", + "slevomat/coding-standard": "^6.4.1", + "squizlabs/php_codesniffer": "^3.5.8", + "webimpress/coding-standard": "^1.1.6" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "LaminasCodingStandard\\": "src/LaminasCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Laminas Coding Standard", + "homepage": "https://laminas.dev", + "keywords": [ + "Coding Standard", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-coding-standard/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-coding-standard/issues", + "rss": "https://github.com/laminas/laminas-coding-standard/releases.atom", + "source": "https://github.com/laminas/laminas-coding-standard" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-05-17T17:39:41+00:00" + }, + { + "name": "laminas/laminas-loader", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-loader.git", + "reference": "d0589ec9dd48365fd95ad10d1c906efd7711c16b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-loader/zipball/d0589ec9dd48365fd95ad10d1c906efd7711c16b", + "reference": "d0589ec9dd48365fd95ad10d1c906efd7711c16b", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-loader": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.2.1", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Loader\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Autoloading and plugin loading strategies", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "loader" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-loader/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-loader/issues", + "rss": "https://github.com/laminas/laminas-loader/releases.atom", + "source": "https://github.com/laminas/laminas-loader" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-02T18:30:53+00:00" + }, + { + "name": "laminas/laminas-mail", + "version": "2.14.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-mail.git", + "reference": "180c6c7baa37cba16fe9fd34af0f346e796cf1a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-mail/zipball/180c6c7baa37cba16fe9fd34af0f346e796cf1a1", + "reference": "180c6c7baa37cba16fe9fd34af0f346e796cf1a1", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "laminas/laminas-loader": "^2.5", + "laminas/laminas-mime": "^2.5", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-validator": "^2.10.2", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^7.3 || ~8.0.0", + "symfony/polyfill-mbstring": "^1.12.0", + "true/punycode": "^2.1" + }, + "replace": { + "zendframework/zend-mail": "^2.10.0" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^3.4", + "laminas/laminas-crypt": "^2.6 || ^3.0", + "laminas/laminas-servicemanager": "^3.2.1", + "phpunit/phpunit": "^9.3", + "psalm/plugin-phpunit": "^0.15.1", + "vimeo/psalm": "^4.7" + }, + "suggest": { + "laminas/laminas-crypt": "Crammd5 support in SMTP Auth", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" + }, + "type": "library", + "extra": { + "laminas": { + "component": "Laminas\\Mail", + "config-provider": "Laminas\\Mail\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Mail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Provides generalized functionality to compose and send both text and MIME-compliant multipart e-mail messages", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "mail" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-mail/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-mail/issues", + "rss": "https://github.com/laminas/laminas-mail/releases.atom", + "source": "https://github.com/laminas/laminas-mail" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-05-20T04:00:23+00:00" + }, + { + "name": "laminas/laminas-validator", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-validator.git", + "reference": "4680bc4241cb5b3ff78954c421fe43105ca413b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/4680bc4241cb5b3ff78954c421fe43105ca413b7", + "reference": "4680bc4241cb5b3ff78954c421fe43105ca413b7", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.1", + "laminas/laminas-stdlib": "^3.3", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^7.3 || ~8.0.0" + }, + "replace": { + "zendframework/zend-validator": "^2.13.0" + }, + "require-dev": { + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~2.2.1", + "laminas/laminas-config": "^2.6", + "laminas/laminas-db": "^2.7", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-http": "^2.14.2", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-math": "^2.6", + "laminas/laminas-servicemanager": "^2.7.11 || ^3.0.3", + "laminas/laminas-session": "^2.8", + "laminas/laminas-uri": "^2.7", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.3", + "psalm/plugin-phpunit": "^0.15.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "vimeo/psalm": "^4.3" + }, + "suggest": { + "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator", + "laminas/laminas-filter": "Laminas\\Filter component, required by the Digits validator", + "laminas/laminas-i18n": "Laminas\\I18n component to allow translation of validation error messages", + "laminas/laminas-i18n-resources": "Translations of validator messages", + "laminas/laminas-math": "Laminas\\Math component, required by the Csrf validator", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "laminas/laminas-session": "Laminas\\Session component, ^2.8; required by the Csrf validator", + "laminas/laminas-uri": "Laminas\\Uri component, required by the Uri and Sitemap\\Loc validators", + "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators" + }, + "type": "library", + "extra": { + "laminas": { + "component": "Laminas\\Validator", + "config-provider": "Laminas\\Validator\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Validator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "validator" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-validator/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-validator/issues", + "rss": "https://github.com/laminas/laminas-validator/releases.atom", + "source": "https://github.com/laminas/laminas-validator" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-07-14T13:59:23+00:00" + }, + { + "name": "laminas/laminas-zendframework-bridge", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-zendframework-bridge.git", + "reference": "13af2502d9bb6f7d33be2de4b51fb68c6cdb476e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/13af2502d9bb6f7d33be2de4b51fb68c6cdb476e", + "reference": "13af2502d9bb6f7d33be2de4b51fb68c6cdb476e", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.1 || ^9.3", + "psalm/plugin-phpunit": "^0.15.1", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.6" + }, + "type": "library", + "extra": { + "laminas": { + "module": "Laminas\\ZendFrameworkBridge" + } + }, + "autoload": { + "files": [ + "src/autoload.php" + ], + "psr-4": { + "Laminas\\ZendFrameworkBridge\\": "src//" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Alias legacy ZF class names to Laminas Project equivalents.", + "keywords": [ + "ZendFramework", + "autoloading", + "laminas", + "zf" + ], + "support": { + "forum": "https://discourse.laminas.dev/", + "issues": "https://github.com/laminas/laminas-zendframework-bridge/issues", + "rss": "https://github.com/laminas/laminas-zendframework-bridge/releases.atom", + "source": "https://github.com/laminas/laminas-zendframework-bridge" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-06-24T12:49:22+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.10.2", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-11-13T09:40:50+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.12.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "6608f01670c3cc5079e18c1dab1104e002579143" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143", + "reference": "6608f01670c3cc5079e18c1dab1104e002579143", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0" + }, + "time": "2021-07-21T10:44:31+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "bae7c545bef187884426f042434e561ab1ddb182" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", + "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.1.0" + }, + "time": "2021-02-23T14:00:09+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + }, + "time": "2020-09-03T19:13:55+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + }, + "time": "2020-09-17T18:55:26+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.13.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.1", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/1.13.0" + }, + "time": "2021-03-17T13:42:18+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.4.9", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/98a088b17966bdf6ee25c8a4b634df313d8aa531", + "reference": "98a088b17966bdf6ee25c8a4b634df313d8aa531", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "consistence/coding-standard": "^3.5", + "ergebnis/composer-normalize": "^2.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^6.3", + "slevomat/coding-standard": "^4.7.2", + "symfony/process": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/master" + }, + "time": "2020-08-03T20:32:43+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "f6293e1b30a2354e8428e004689671b83871edde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde", + "reference": "f6293e1b30a2354e8428e004689671b83871edde", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.10.2", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-03-28T07:26:59+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:57:25+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.5.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "ea8c2dfb1065eb35a79b3681eee6e6fb0a6f273b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea8c2dfb1065eb35a79b3681eee6e6fb0a6f273b", + "reference": "ea8c2dfb1065eb35a79b3681eee6e6fb0a6f273b", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpspec/prophecy": "^1.12.1", + "phpunit/php-code-coverage": "^9.2.3", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.5", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.3", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^2.3.4", + "sebastian/version": "^3.0.2" + }, + "require-dev": { + "ext-pdo": "*", + "phpspec/prophecy-phpunit": "^2.0.1" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ], + "files": [ + "src/Framework/Assert/Functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.9" + }, + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-08-31T06:47:40+00:00" + }, + { + "name": "psr/container", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.1" + }, + "time": "2021-03-05T17:36:06+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:49:45+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:10:38+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:52:38+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:24:23+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-11T13:31:12+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:17:30+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "2.3.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-15T12:49:02+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "6.4.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/696dcca217d0c9da2c40d02731526c1e25b65346", + "reference": "696dcca217d0c9da2c40d02731526c1e25b65346", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.4.5 - 0.4.9", + "squizlabs/php_codesniffer": "^3.5.6" + }, + "require-dev": { + "phing/phing": "2.16.3", + "php-parallel-lint/php-parallel-lint": "1.2.0", + "phpstan/phpstan": "0.12.48", + "phpstan/phpstan-deprecation-rules": "0.12.5", + "phpstan/phpstan-phpunit": "0.12.16", + "phpstan/phpstan-strict-rules": "0.12.5", + "phpunit/phpunit": "7.5.20|8.5.5|9.4.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/6.4.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2020-10-05T12:39:37+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.6.0", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ffced0d2c8fa8e6cdc4d695a743271fab6c38625", + "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2021-04-09T00:54:41+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6", + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T12:26:48+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + }, + { + "name": "true/punycode", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/true/php-punycode.git", + "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/true/php-punycode/zipball/a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", + "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.7", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "TrueBV\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Renan Gonçalves", + "email": "renan.saddam@gmail.com" + } + ], + "description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)", + "homepage": "https://github.com/true/php-punycode", + "keywords": [ + "idna", + "punycode" + ], + "support": { + "issues": "https://github.com/true/php-punycode/issues", + "source": "https://github.com/true/php-punycode/tree/master" + }, + "time": "2016-11-16T10:37:54+00:00" + }, + { + "name": "webimpress/coding-standard", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/webimpress/coding-standard.git", + "reference": "8f4a220de33f471a8101836f7ec72b852c3f9f03" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webimpress/coding-standard/zipball/8f4a220de33f471a8101836f7ec72b852c3f9f03", + "reference": "8f4a220de33f471a8101836f7ec72b852c3f9f03", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0", + "squizlabs/php_codesniffer": "^3.6" + }, + "require-dev": { + "phpunit/phpunit": "^9.5.4" + }, + "type": "phpcodesniffer-standard", + "extra": { + "dev-master": "1.2.x-dev", + "dev-develop": "1.3.x-dev" + }, + "autoload": { + "psr-4": { + "WebimpressCodingStandard\\": "src/WebimpressCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "description": "Webimpress Coding Standard", + "keywords": [ + "Coding Standard", + "PSR-2", + "phpcs", + "psr-12", + "webimpress" + ], + "support": { + "issues": "https://github.com/webimpress/coding-standard/issues", + "source": "https://github.com/webimpress/coding-standard/tree/1.2.2" + }, + "funding": [ + { + "url": "https://github.com/michalbundyra", + "type": "github" + } + ], + "time": "2021-04-12T12:51:27+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.10.0" + }, + "time": "2021-03-09T10:59:23+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "platform-dev": [], + "plugin-api-version": "2.0.0" +} diff --git a/lib/laminas/laminas-mime/phpcs.xml.dist b/lib/laminas/laminas-mime/phpcs.xml.dist new file mode 100644 index 0000000000..7f391d80a4 --- /dev/null +++ b/lib/laminas/laminas-mime/phpcs.xml.dist @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + src + test + + + + diff --git a/lib/laminas/laminas-mime/src/Decode.php b/lib/laminas/laminas-mime/src/Decode.php new file mode 100644 index 0000000000..5afdeab2d7 --- /dev/null +++ b/lib/laminas/laminas-mime/src/Decode.php @@ -0,0 +1,240 @@ + array(name => value), 'body' => content), null if no parts found + * @throws Exception\RuntimeException + */ + public static function splitMessageStruct($message, $boundary, $EOL = Mime::LINEEND) + { + $parts = static::splitMime($message, $boundary); + if (! $parts) { + return; + } + $result = []; + $headers = null; // "Declare" variable before the first usage "for reading" + $body = null; // "Declare" variable before the first usage "for reading" + foreach ($parts as $part) { + static::splitMessage($part, $headers, $body, $EOL); + $result[] = [ + 'header' => $headers, + 'body' => $body, + ]; + } + return $result; + } + + /** + * split a message in header and body part, if no header or an + * invalid header is found $headers is empty + * + * The charset of the returned headers depend on your iconv settings. + * + * @param string|Headers $message raw message with header and optional content + * @param Headers $headers output param, headers container + * @param string $body output param, content of message + * @param string $EOL EOL string; defaults to {@link Laminas\Mime\Mime::LINEEND} + * @param bool $strict enable strict mode for parsing message + * @return null + */ + public static function splitMessage($message, &$headers, &$body, $EOL = Mime::LINEEND, $strict = false) + { + if ($message instanceof Headers) { + $message = $message->toString(); + } + // check for valid header at first line + $firstlinePos = strpos($message, "\n"); + $firstline = $firstlinePos === false ? $message : substr($message, 0, $firstlinePos); + if (! preg_match('%^[^\s]+[^:]*:%', $firstline)) { + $headers = new Headers(); + // TODO: we're ignoring \r for now - is this function fast enough and is it safe to assume noone needs \r? + $body = str_replace(["\r", "\n"], ['', $EOL], $message); + return; + } + + // see @Laminas-372, pops the first line off a message if it doesn't contain a header + if (! $strict) { + $parts = explode(':', $firstline, 2); + if (count($parts) !== 2) { + $message = substr($message, strpos($message, $EOL) + 1); + } + } + + // @todo splitMime removes "\r" sequences, which breaks valid mime + // messages as returned by many mail servers + $headersEOL = $EOL; + + // find an empty line between headers and body + // default is set new line + // @todo Maybe this is too much "magic"; we should be more strict here + if (strpos($message, $EOL . $EOL)) { + [$headers, $body] = explode($EOL . $EOL, $message, 2); + // next is the standard new line + } elseif ($EOL !== "\r\n" && strpos($message, "\r\n\r\n")) { + [$headers, $body] = explode("\r\n\r\n", $message, 2); + $headersEOL = "\r\n"; // Headers::fromString will fail with incorrect EOL + // next is the other "standard" new line + } elseif ($EOL !== "\n" && strpos($message, "\n\n")) { + [$headers, $body] = explode("\n\n", $message, 2); + $headersEOL = "\n"; + // at last resort find anything that looks like a new line + } else { + ErrorHandler::start(E_NOTICE | E_WARNING); + [$headers, $body] = preg_split("%([\r\n]+)\\1%U", $message, 2); + ErrorHandler::stop(); + } + + $headers = Headers::fromString($headers, $headersEOL); + } + + /** + * split a content type in its different parts + * + * @param string $type content-type + * @param string $wantedPart the wanted part, else an array with all parts is returned + * @return string|array wanted part or all parts as array('type' => content-type, partname => value) + */ + public static function splitContentType($type, $wantedPart = null) + { + return static::splitHeaderField($type, $wantedPart, 'type'); + } + + /** + * split a header field like content type in its different parts + * + * @param string $field header field + * @param string $wantedPart the wanted part, else an array with all parts is returned + * @param string $firstName key name for the first part + * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value) + * @throws Exception\RuntimeException + */ + public static function splitHeaderField($field, $wantedPart = null, $firstName = '0') + { + $wantedPart = strtolower($wantedPart ?? ''); + $firstName = strtolower($firstName); + + // special case - a bit optimized + if ($firstName === $wantedPart) { + $field = strtok($field, ';'); + return $field[0] === '"' ? substr($field, 1, -1) : $field; + } + + $field = $firstName . '=' . $field; + if (! preg_match_all('%([^=\s]+)\s*=\s*("[^"]+"|[^;]+)(;\s*|$)%', $field, $matches)) { + throw new Exception\RuntimeException('not a valid header field'); + } + + if ($wantedPart) { + foreach ($matches[1] as $key => $name) { + if (strcasecmp($name, $wantedPart)) { + continue; + } + if ($matches[2][$key][0] !== '"') { + return $matches[2][$key]; + } + return substr($matches[2][$key], 1, -1); + } + return; + } + + $split = []; + foreach ($matches[1] as $key => $name) { + $name = strtolower($name); + if ($matches[2][$key][0] === '"') { + $split[$name] = substr($matches[2][$key], 1, -1); + } else { + $split[$name] = $matches[2][$key]; + } + } + + return $split; + } + + /** + * decode a quoted printable encoded string + * + * The charset of the returned string depends on your iconv settings. + * + * @param string $string encoded string + * @return string decoded string + */ + public static function decodeQuotedPrintable($string) + { + return iconv_mime_decode($string, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, 'UTF-8'); + } +} diff --git a/lib/laminas/laminas-mime/src/Exception/ExceptionInterface.php b/lib/laminas/laminas-mime/src/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..762e2c0071 --- /dev/null +++ b/lib/laminas/laminas-mime/src/Exception/ExceptionInterface.php @@ -0,0 +1,7 @@ +parts; + } + + /** + * Sets the given array of Laminas\Mime\Part as the array for the message + * + * @param array $parts + * @return self + */ + public function setParts($parts) + { + $this->parts = $parts; + return $this; + } + + /** + * Append a new Laminas\Mime\Part to the current message + * + * @throws Exception\InvalidArgumentException + * @return self + */ + public function addPart(Part $part) + { + foreach ($this->getParts() as $row) { + if ($part === $row) { + throw new Exception\InvalidArgumentException(sprintf( + 'Provided part %s already defined.', + $part->getId() + )); + } + } + + $this->parts[] = $part; + return $this; + } + + /** + * Check if message needs to be sent as multipart + * MIME message or if it has only one part. + * + * @return bool + */ + public function isMultiPart() + { + return count($this->parts) > 1; + } + + /** + * Set Laminas\Mime\Mime object for the message + * + * This can be used to set the boundary specifically or to use a subclass of + * Laminas\Mime for generating the boundary. + * + * @return self + */ + public function setMime(Mime $mime) + { + $this->mime = $mime; + return $this; + } + + /** + * Returns the Laminas\Mime\Mime object in use by the message + * + * If the object was not present, it is created and returned. Can be used to + * determine the boundary used in this message. + * + * @return Mime + */ + public function getMime() + { + if ($this->mime === null) { + $this->mime = new Mime(); + } + + return $this->mime; + } + + /** + * Generate MIME-compliant message from the current configuration + * + * This can be a multipart message if more than one MIME part was added. If + * only one part is present, the content of this part is returned. If no + * part had been added, an empty string is returned. + * + * Parts are separated by the mime boundary as defined in Laminas\Mime\Mime. If + * {@link setMime()} has been called before this method, the Laminas\Mime\Mime + * object set by this call will be used. Otherwise, a new Laminas\Mime\Mime object + * is generated and used. + * + * @param string $EOL EOL string; defaults to {@link Laminas\Mime\Mime::LINEEND} + * @return string + */ + public function generateMessage($EOL = Mime::LINEEND) + { + if (! $this->isMultiPart()) { + if (empty($this->parts)) { + return ''; + } + $part = current($this->parts); + $body = $part->getContent($EOL); + } else { + $mime = $this->getMime(); + + $boundaryLine = $mime->boundaryLine($EOL); + $body = 'This is a message in Mime Format. If you see this, ' + . "your mail reader does not support this format." . $EOL; + + foreach (array_keys($this->parts) as $p) { + $body .= $boundaryLine + . $this->getPartHeaders($p, $EOL) + . $EOL + . $this->getPartContent($p, $EOL); + } + + $body .= $mime->mimeEnd($EOL); + } + + return trim($body); + } + + /** + * Get the headers of a given part as an array + * + * @param int $partnum + * @return array + */ + public function getPartHeadersArray($partnum) + { + return $this->parts[$partnum]->getHeadersArray(); + } + + /** + * Get the headers of a given part as a string + * + * @param int $partnum + * @param string $EOL + * @return string + */ + public function getPartHeaders($partnum, $EOL = Mime::LINEEND) + { + return $this->parts[$partnum]->getHeaders($EOL); + } + + /** + * Get the (encoded) content of a given part as a string + * + * @param int $partnum + * @param string $EOL + * @return string + */ + public function getPartContent($partnum, $EOL = Mime::LINEEND) + { + return $this->parts[$partnum]->getContent($EOL); + } + + /** + * Explode MIME multipart string into separate parts + * + * Parts consist of the header and the body of each MIME part. + * + * @param string $body + * @param string $boundary + * @throws Exception\RuntimeException + * @return array + */ + protected static function _disassembleMime($body, $boundary) + { + $start = 0; + $res = []; + // find every mime part limiter and cut out the + // string before it. + // the part before the first boundary string is discarded: + $p = strpos($body, '--' . $boundary . "\n", $start); + if ($p === false) { + // no parts found! + return []; + } + + // position after first boundary line + $start = $p + 3 + strlen($boundary); + + while (($p = strpos($body, '--' . $boundary . "\n", $start)) !== false) { + $res[] = substr($body, $start, $p - $start); + $start = $p + 3 + strlen($boundary); + } + + // no more parts, find end boundary + $p = strpos($body, '--' . $boundary . '--', $start); + if ($p === false) { + throw new Exception\RuntimeException('Not a valid Mime Message: End Missing'); + } + + // the remaining part also needs to be parsed: + $res[] = substr($body, $start, $p - $start); + return $res; + } + + /** + * Decodes a MIME encoded string and returns a Laminas\Mime\Message object with + * all the MIME parts set according to the given string + * + * @param string $message + * @param string $boundary Multipart boundary; if omitted, $message will be + * treated as a single part. + * @param string $EOL EOL string; defaults to {@link Laminas\Mime\Mime::LINEEND} + * @throws Exception\RuntimeException + * @return Message + */ + public static function createFromMessage($message, $boundary = null, $EOL = Mime::LINEEND) + { + if ($boundary) { + $parts = Decode::splitMessageStruct($message, $boundary, $EOL); + } else { + Decode::splitMessage($message, $headers, $body, $EOL); + $parts = [ + [ + 'header' => $headers, + 'body' => $body, + ], + ]; + } + + $res = new static(); + foreach ($parts as $part) { + // now we build a new MimePart for the current Message Part: + $properties = []; + foreach ($part['header'] as $header) { + /** @var HeaderInterface $header */ + /** + * @todo check for characterset and filename + */ + + $fieldName = $header->getFieldName(); + $fieldValue = $header->getFieldValue(); + switch (strtolower($fieldName)) { + case 'content-type': + $properties['type'] = $fieldValue; + break; + case 'content-transfer-encoding': + $properties['encoding'] = $fieldValue; + break; + case 'content-id': + $properties['id'] = trim($fieldValue, '<>'); + break; + case 'content-disposition': + $properties['disposition'] = $fieldValue; + break; + case 'content-description': + $properties['description'] = $fieldValue; + break; + case 'content-location': + $properties['location'] = $fieldValue; + break; + case 'content-language': + $properties['language'] = $fieldValue; + break; + default: + // Ignore unknown header + break; + } + } + + $body = $part['body']; + + if (isset($properties['encoding'])) { + switch ($properties['encoding']) { + case 'quoted-printable': + $body = quoted_printable_decode($body); + break; + case 'base64': + $body = base64_decode($body); + break; + } + } + + $newPart = new Part($body); + foreach ($properties as $key => $value) { + $newPart->$key = $value; + } + $res->addPart($newPart); + } + + return $res; + } +} diff --git a/lib/laminas/laminas-mime/src/Mime.php b/lib/laminas/laminas-mime/src/Mime.php new file mode 100644 index 0000000000..70f08fa08b --- /dev/null +++ b/lib/laminas/laminas-mime/src/Mime.php @@ -0,0 +1,709 @@ +[\x21\x23-\x26\x2a\x2b\x2d\x5e\5f\60\x7b-\x7ea-zA-Z0-9]+)\?(?P[\x21\x23-\x26\x2a\x2b\x2d\x5e\5f\60\x7b-\x7ea-zA-Z0-9]+)\?(?P[\x21-\x3e\x40-\x7e]+)#'; + // phpcs:enable + + /** @var null|string */ + protected $boundary; + + /** @var int */ + protected static $makeUnique = 0; + + /** + * Lookup-tables for QuotedPrintable + * + * @var string[] + */ + public static $qpKeys = [ + "\x00", + "\x01", + "\x02", + "\x03", + "\x04", + "\x05", + "\x06", + "\x07", + "\x08", + "\x09", + "\x0A", + "\x0B", + "\x0C", + "\x0D", + "\x0E", + "\x0F", + "\x10", + "\x11", + "\x12", + "\x13", + "\x14", + "\x15", + "\x16", + "\x17", + "\x18", + "\x19", + "\x1A", + "\x1B", + "\x1C", + "\x1D", + "\x1E", + "\x1F", + "\x7F", + "\x80", + "\x81", + "\x82", + "\x83", + "\x84", + "\x85", + "\x86", + "\x87", + "\x88", + "\x89", + "\x8A", + "\x8B", + "\x8C", + "\x8D", + "\x8E", + "\x8F", + "\x90", + "\x91", + "\x92", + "\x93", + "\x94", + "\x95", + "\x96", + "\x97", + "\x98", + "\x99", + "\x9A", + "\x9B", + "\x9C", + "\x9D", + "\x9E", + "\x9F", + "\xA0", + "\xA1", + "\xA2", + "\xA3", + "\xA4", + "\xA5", + "\xA6", + "\xA7", + "\xA8", + "\xA9", + "\xAA", + "\xAB", + "\xAC", + "\xAD", + "\xAE", + "\xAF", + "\xB0", + "\xB1", + "\xB2", + "\xB3", + "\xB4", + "\xB5", + "\xB6", + "\xB7", + "\xB8", + "\xB9", + "\xBA", + "\xBB", + "\xBC", + "\xBD", + "\xBE", + "\xBF", + "\xC0", + "\xC1", + "\xC2", + "\xC3", + "\xC4", + "\xC5", + "\xC6", + "\xC7", + "\xC8", + "\xC9", + "\xCA", + "\xCB", + "\xCC", + "\xCD", + "\xCE", + "\xCF", + "\xD0", + "\xD1", + "\xD2", + "\xD3", + "\xD4", + "\xD5", + "\xD6", + "\xD7", + "\xD8", + "\xD9", + "\xDA", + "\xDB", + "\xDC", + "\xDD", + "\xDE", + "\xDF", + "\xE0", + "\xE1", + "\xE2", + "\xE3", + "\xE4", + "\xE5", + "\xE6", + "\xE7", + "\xE8", + "\xE9", + "\xEA", + "\xEB", + "\xEC", + "\xED", + "\xEE", + "\xEF", + "\xF0", + "\xF1", + "\xF2", + "\xF3", + "\xF4", + "\xF5", + "\xF6", + "\xF7", + "\xF8", + "\xF9", + "\xFA", + "\xFB", + "\xFC", + "\xFD", + "\xFE", + "\xFF", + ]; + + /** @var string[] */ + public static $qpReplaceValues = [ + "=00", + "=01", + "=02", + "=03", + "=04", + "=05", + "=06", + "=07", + "=08", + "=09", + "=0A", + "=0B", + "=0C", + "=0D", + "=0E", + "=0F", + "=10", + "=11", + "=12", + "=13", + "=14", + "=15", + "=16", + "=17", + "=18", + "=19", + "=1A", + "=1B", + "=1C", + "=1D", + "=1E", + "=1F", + "=7F", + "=80", + "=81", + "=82", + "=83", + "=84", + "=85", + "=86", + "=87", + "=88", + "=89", + "=8A", + "=8B", + "=8C", + "=8D", + "=8E", + "=8F", + "=90", + "=91", + "=92", + "=93", + "=94", + "=95", + "=96", + "=97", + "=98", + "=99", + "=9A", + "=9B", + "=9C", + "=9D", + "=9E", + "=9F", + "=A0", + "=A1", + "=A2", + "=A3", + "=A4", + "=A5", + "=A6", + "=A7", + "=A8", + "=A9", + "=AA", + "=AB", + "=AC", + "=AD", + "=AE", + "=AF", + "=B0", + "=B1", + "=B2", + "=B3", + "=B4", + "=B5", + "=B6", + "=B7", + "=B8", + "=B9", + "=BA", + "=BB", + "=BC", + "=BD", + "=BE", + "=BF", + "=C0", + "=C1", + "=C2", + "=C3", + "=C4", + "=C5", + "=C6", + "=C7", + "=C8", + "=C9", + "=CA", + "=CB", + "=CC", + "=CD", + "=CE", + "=CF", + "=D0", + "=D1", + "=D2", + "=D3", + "=D4", + "=D5", + "=D6", + "=D7", + "=D8", + "=D9", + "=DA", + "=DB", + "=DC", + "=DD", + "=DE", + "=DF", + "=E0", + "=E1", + "=E2", + "=E3", + "=E4", + "=E5", + "=E6", + "=E7", + "=E8", + "=E9", + "=EA", + "=EB", + "=EC", + "=ED", + "=EE", + "=EF", + "=F0", + "=F1", + "=F2", + "=F3", + "=F4", + "=F5", + "=F6", + "=F7", + "=F8", + "=F9", + "=FA", + "=FB", + "=FC", + "=FD", + "=FE", + "=FF", + ]; + // @codingStandardsIgnoreStart + public static $qpKeysString = + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x7F\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF"; + // @codingStandardsIgnoreEnd + + /** + * Check if the given string is "printable" + * + * Checks that a string contains no unprintable characters. If this returns + * false, encode the string for secure delivery. + * + * @param string $str + * @return bool + */ + public static function isPrintable($str) + { + return strcspn($str, static::$qpKeysString) === strlen($str); + } + + /** + * Encode a given string with the QUOTED_PRINTABLE mechanism and wrap the lines. + * + * @param string $str + * @param int $lineLength Defaults to {@link LINELENGTH} + * @param string $lineEnd Defaults to {@link LINEEND} + * @return string + */ + public static function encodeQuotedPrintable( + $str, + $lineLength = self::LINELENGTH, + $lineEnd = self::LINEEND + ) { + $out = ''; + $str = self::_encodeQuotedPrintable($str); + + // Split encoded text into separate lines + $initialPtr = 0; + $strLength = strlen($str); + while ($initialPtr < $strLength) { + $continueAt = $strLength - $initialPtr; + + if ($continueAt > $lineLength) { + $continueAt = $lineLength; + } + + $chunk = substr($str, $initialPtr, $continueAt); + + // Ensure we are not splitting across an encoded character + $endingMarkerPos = strrpos($chunk, '='); + if ($endingMarkerPos !== false && $endingMarkerPos >= strlen($chunk) - 2) { + $chunk = substr($chunk, 0, $endingMarkerPos); + $continueAt = $endingMarkerPos; + } + + if (ord($chunk[0]) === 0x2E) { // 0x2E is a dot + $chunk = '=2E' . substr($chunk, 1); + } + + // copied from swiftmailer https://git.io/vAXU1 + switch (ord(substr($chunk, strlen($chunk) - 1))) { + case 0x09: // Horizontal Tab + $chunk = substr_replace($chunk, '=09', strlen($chunk) - 1, 1); + break; + case 0x20: // Space + $chunk = substr_replace($chunk, '=20', strlen($chunk) - 1, 1); + break; + } + + // Add string and continue + $out .= $chunk . '=' . $lineEnd; + $initialPtr += $continueAt; + } + + $out = rtrim($out, $lineEnd); + $out = rtrim($out, '='); + return $out; + } + + /** + * Converts a string into quoted printable format. + * + * @param string $str + * @return string + */ + // @codingStandardsIgnoreStart + private static function _encodeQuotedPrintable($str) + { + // @codingStandardsIgnoreEnd + $str = str_replace('=', '=3D', $str); + $str = str_replace(static::$qpKeys, static::$qpReplaceValues, $str); + $str = rtrim($str); + return $str; + } + + /** + * Encode a given string with the QUOTED_PRINTABLE mechanism for Mail Headers. + * + * Mail headers depend on an extended quoted printable algorithm otherwise + * a range of bugs can occur. + * + * @param string $str + * @param string $charset + * @param int $lineLength Defaults to {@link LINELENGTH} + * @param string $lineEnd Defaults to {@link LINEEND} + * @return string + */ + public static function encodeQuotedPrintableHeader( + $str, + $charset, + $lineLength = self::LINELENGTH, + $lineEnd = self::LINEEND + ) { + // Reduce line-length by the length of the required delimiter, charsets and encoding + $prefix = sprintf('=?%s?Q?', $charset); + $lineLength = $lineLength - strlen($prefix) - 3; + + $str = self::_encodeQuotedPrintable($str); + + // Mail-Header required chars have to be encoded also: + $str = str_replace(['?', ',', ' ', '_'], ['=3F', '=2C', '=20', '=5F'], $str); + + // initialize first line, we need it anyways + $lines = [0 => '']; + + // Split encoded text into separate lines + $tmp = ''; + while (strlen($str) > 0) { + $currentLine = max(count($lines) - 1, 0); + $token = static::getNextQuotedPrintableToken($str); + $substr = substr($str, strlen($token)); + $str = false === $substr ? '' : $substr; + + $tmp .= $token; + if ($token === '=20') { + // only if we have a single char token or space, we can append the + // tempstring it to the current line or start a new line if necessary. + $lineLimitReached = strlen($lines[$currentLine] . $tmp) > $lineLength; + $noCurrentLine = $lines[$currentLine] === ''; + if ($noCurrentLine && $lineLimitReached) { + $lines[$currentLine] = $tmp; + $lines[$currentLine + 1] = ''; + } elseif ($lineLimitReached) { + $lines[$currentLine + 1] = $tmp; + } else { + $lines[$currentLine] .= $tmp; + } + $tmp = ''; + } + // don't forget to append the rest to the last line + if (strlen($str) === 0) { + $lines[$currentLine] .= $tmp; + } + } + + // assemble the lines together by pre- and appending delimiters, charset, encoding. + for ($i = 0, $count = count($lines); $i < $count; $i++) { + $lines[$i] = " " . $prefix . $lines[$i] . "?="; + } + $str = trim(implode($lineEnd, $lines)); + return $str; + } + + /** + * Retrieves the first token from a quoted printable string. + * + * @param string $str + * @return string + */ + private static function getNextQuotedPrintableToken($str) + { + if (0 === strpos($str, '=')) { + $token = substr($str, 0, 3); + } else { + $token = substr($str, 0, 1); + } + return $token; + } + + /** + * Encode a given string in mail header compatible base64 encoding. + * + * @param string $str + * @param string $charset + * @param int $lineLength Defaults to {@link LINELENGTH} + * @param string $lineEnd Defaults to {@link LINEEND} + * @return string + */ + public static function encodeBase64Header( + $str, + $charset, + $lineLength = self::LINELENGTH, + $lineEnd = self::LINEEND + ) { + $prefix = '=?' . $charset . '?B?'; + $suffix = '?='; + $remainingLength = $lineLength - strlen($prefix) - strlen($suffix); + + $encodedValue = static::encodeBase64($str, $remainingLength, $lineEnd); + $encodedValue = str_replace($lineEnd, $suffix . $lineEnd . ' ' . $prefix, $encodedValue); + $encodedValue = $prefix . $encodedValue . $suffix; + return $encodedValue; + } + + /** + * Encode a given string in base64 encoding and break lines + * according to the maximum linelength. + * + * @param string $str + * @param int $lineLength Defaults to {@link LINELENGTH} + * @param string $lineEnd Defaults to {@link LINEEND} + * @return string + */ + public static function encodeBase64( + $str, + $lineLength = self::LINELENGTH, + $lineEnd = self::LINEEND + ) { + $lineLength = $lineLength - ($lineLength % 4); + return rtrim(chunk_split(base64_encode($str), $lineLength, $lineEnd)); + } + + /** + * Constructor + * + * @param null|string $boundary + * @access public + */ + public function __construct($boundary = null) + { + // This string needs to be somewhat unique + if ($boundary === null) { + $this->boundary = '=_' . md5(microtime(1) . static::$makeUnique++); + } else { + $this->boundary = $boundary; + } + } + + // phpcs:disable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps + + /** + * Encode the given string with the given encoding. + * + * @param string $str + * @param string $encoding + * @param string $EOL EOL string; defaults to {@link LINEEND} + * @return string + */ + public static function encode($str, $encoding, $EOL = self::LINEEND) + { + switch ($encoding) { + case self::ENCODING_BASE64: + return static::encodeBase64($str, self::LINELENGTH, $EOL); + + case self::ENCODING_QUOTEDPRINTABLE: + return static::encodeQuotedPrintable($str, self::LINELENGTH, $EOL); + + default: + /** + * @todo 7Bit and 8Bit is currently handled the same way. + */ + return $str; + } + } + + /** + * Return a MIME boundary + * + * @access public + * @return string + */ + public function boundary() + { + return $this->boundary; + } + + /** + * Return a MIME boundary line + * + * @param string $EOL Defaults to {@link LINEEND} + * @access public + * @return string + */ + public function boundaryLine($EOL = self::LINEEND) + { + return $EOL . '--' . $this->boundary . $EOL; + } + + /** + * Return MIME ending + * + * @param string $EOL Defaults to {@link LINEEND} + * @access public + * @return string + */ + public function mimeEnd($EOL = self::LINEEND) + { + return $EOL . '--' . $this->boundary . '--' . $EOL; + } + + /** + * Detect MIME charset + * + * Extract parts according to https://tools.ietf.org/html/rfc2047#section-2 + * + * @param string $str + * @return string + */ + public static function mimeDetectCharset($str) + { + if (preg_match(self::CHARSET_REGEX, $str, $matches)) { + return strtoupper($matches['charset']); + } + + return 'ASCII'; + } +} diff --git a/lib/laminas/laminas-mime/src/Part.php b/lib/laminas/laminas-mime/src/Part.php new file mode 100644 index 0000000000..541da9c0fa --- /dev/null +++ b/lib/laminas/laminas-mime/src/Part.php @@ -0,0 +1,549 @@ + */ + protected $filters = []; + + /** + * create a new Mime Part. + * The (unencoded) content of the Part as passed + * as a string or stream + * + * @param mixed $content String or Stream containing the content + * @throws Exception\InvalidArgumentException + */ + public function __construct($content = '') + { + $this->setContent($content); + } + + /** + * @todo error checking for setting $type + * @todo error checking for setting $encoding + */ + + /** + * Set type + * + * @param string $type + * @return self + */ + public function setType($type = Mime::TYPE_OCTETSTREAM) + { + $this->type = $type; + return $this; + } + + /** + * Get type + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Set encoding + * + * @param string $encoding + * @return self + */ + public function setEncoding($encoding = Mime::ENCODING_8BIT) + { + $this->encoding = $encoding; + return $this; + } + + /** + * Get encoding + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Set id + * + * @param string $id + * @return self + */ + public function setId($id) + { + $this->id = $id; + return $this; + } + + /** + * Get id + * + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * Set disposition + * + * @param string $disposition + * @return self + */ + public function setDisposition($disposition) + { + $this->disposition = $disposition; + return $this; + } + + /** + * Get disposition + * + * @return string + */ + public function getDisposition() + { + return $this->disposition; + } + + /** + * Set description + * + * @param string $description + * @return self + */ + public function setDescription($description) + { + $this->description = $description; + return $this; + } + + /** + * Get description + * + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * Set filename + * + * @param string $fileName + * @return self + */ + public function setFileName($fileName) + { + $this->filename = $fileName; + return $this; + } + + /** + * Get filename + * + * @return string + */ + public function getFileName() + { + return $this->filename; + } + + /** + * Set charset + * + * @param string $charset + * @return self + */ + public function setCharset($charset) + { + $this->charset = $charset; + return $this; + } + + /** + * Get charset + * + * @return string + */ + public function getCharset() + { + return $this->charset; + } + + /** + * Set boundary + * + * @param string $boundary + * @return self + */ + public function setBoundary($boundary) + { + $this->boundary = $boundary; + return $this; + } + + /** + * Get boundary + * + * @return string + */ + public function getBoundary() + { + return $this->boundary; + } + + /** + * Set location + * + * @param string $location + * @return self + */ + public function setLocation($location) + { + $this->location = $location; + return $this; + } + + /** + * Get location + * + * @return string + */ + public function getLocation() + { + return $this->location; + } + + /** + * Set language + * + * @param string $language + * @return self + */ + public function setLanguage($language) + { + $this->language = $language; + return $this; + } + + /** + * Get language + * + * @return string + */ + public function getLanguage() + { + return $this->language; + } + + /** + * Set content + * + * @param mixed $content String or Stream containing the content + * @throws Exception\InvalidArgumentException + * @return self + */ + public function setContent($content) + { + if (! is_string($content) && ! is_resource($content)) { + throw new Exception\InvalidArgumentException(sprintf( + 'Content must be string or resource; received "%s"', + is_object($content) ? get_class($content) : gettype($content) + )); + } + $this->content = $content; + if (is_resource($content)) { + $this->isStream = true; + } + + return $this; + } + + /** + * Set isStream + * + * @param bool $isStream + * @return self + */ + public function setIsStream($isStream = false) + { + $this->isStream = (bool) $isStream; + return $this; + } + + /** + * Get isStream + * + * @return bool + */ + public function getIsStream() + { + return $this->isStream; + } + + /** + * Set filters + * + * @param array $filters + * @return self + */ + public function setFilters($filters = []) + { + $this->filters = $filters; + return $this; + } + + /** + * Get Filters + * + * @return array + */ + public function getFilters() + { + return $this->filters; + } + + /** + * check if this part can be read as a stream. + * if true, getEncodedStream can be called, otherwise + * only getContent can be used to fetch the encoded + * content of the part + * + * @return bool + */ + public function isStream() + { + return $this->isStream; + } + + // phpcs:disable WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCaps + + /** + * if this was created with a stream, return a filtered stream for + * reading the content. very useful for large file attachments. + * + * @param string $EOL + * @return resource + * @throws Exception\RuntimeException If not a stream or unable to append filter. + */ + public function getEncodedStream($EOL = Mime::LINEEND) + { + if (! $this->isStream) { + throw new Exception\RuntimeException('Attempt to get a stream from a string part'); + } + + //stream_filter_remove(); // ??? is that right? + switch ($this->encoding) { + case Mime::ENCODING_QUOTEDPRINTABLE: + if (array_key_exists(Mime::ENCODING_QUOTEDPRINTABLE, $this->filters)) { + stream_filter_remove($this->filters[Mime::ENCODING_QUOTEDPRINTABLE]); + } + $filter = stream_filter_append( + $this->content, + 'convert.quoted-printable-encode', + STREAM_FILTER_READ, + [ + 'line-length' => 76, + 'line-break-chars' => $EOL, + ] + ); + $this->filters[Mime::ENCODING_QUOTEDPRINTABLE] = $filter; + if (! is_resource($filter)) { + throw new Exception\RuntimeException('Failed to append quoted-printable filter'); + } + break; + case Mime::ENCODING_BASE64: + if (array_key_exists(Mime::ENCODING_BASE64, $this->filters)) { + stream_filter_remove($this->filters[Mime::ENCODING_BASE64]); + } + $filter = stream_filter_append( + $this->content, + 'convert.base64-encode', + STREAM_FILTER_READ, + [ + 'line-length' => 76, + 'line-break-chars' => $EOL, + ] + ); + $this->filters[Mime::ENCODING_BASE64] = $filter; + if (! is_resource($filter)) { + throw new Exception\RuntimeException('Failed to append base64 filter'); + } + break; + default: + } + return $this->content; + } + + /** + * Get the Content of the current Mime Part in the given encoding. + * + * @param string $EOL + * @return string + */ + public function getContent($EOL = Mime::LINEEND) + { + if ($this->isStream) { + $encodedStream = $this->getEncodedStream($EOL); + $encodedStreamContents = stream_get_contents($encodedStream); + $streamMetaData = stream_get_meta_data($encodedStream); + + if (isset($streamMetaData['seekable']) && $streamMetaData['seekable']) { + rewind($encodedStream); + } + + return $encodedStreamContents; + } + return Mime::encode($this->content, $this->encoding, $EOL); + } + + /** + * Get the RAW unencoded content from this part + * + * @return string + */ + public function getRawContent() + { + if ($this->isStream) { + return stream_get_contents($this->content); + } + return $this->content; + } + + /** + * Create and return the array of headers for this MIME part + * + * @access public + * @param string $EOL + * @return array + */ + public function getHeadersArray($EOL = Mime::LINEEND) + { + $headers = []; + + $contentType = $this->type; + if ($this->charset) { + $contentType .= '; charset=' . $this->charset; + } + + if ($this->boundary) { + $contentType .= ';' . $EOL + . " boundary=\"" . $this->boundary . '"'; + } + + $headers[] = ['Content-Type', $contentType]; + + if ($this->encoding) { + $headers[] = ['Content-Transfer-Encoding', $this->encoding]; + } + + if ($this->id) { + $headers[] = ['Content-ID', '<' . $this->id . '>']; + } + + if ($this->disposition) { + $disposition = $this->disposition; + if ($this->filename) { + $disposition .= '; filename="' . $this->filename . '"'; + } + $headers[] = ['Content-Disposition', $disposition]; + } + + if ($this->description) { + $headers[] = ['Content-Description', $this->description]; + } + + if ($this->location) { + $headers[] = ['Content-Location', $this->location]; + } + + if ($this->language) { + $headers[] = ['Content-Language', $this->language]; + } + + return $headers; + } + + /** + * Return the headers for this part as a string + * + * @param string $EOL + * @return String + */ + public function getHeaders($EOL = Mime::LINEEND) + { + $res = ''; + foreach ($this->getHeadersArray($EOL) as $header) { + $res .= $header[0] . ': ' . $header[1] . $EOL; + } + + return $res; + } +} diff --git a/lib/laminas/laminas-servicemanager/COPYRIGHT.md b/lib/laminas/laminas-servicemanager/COPYRIGHT.md new file mode 100644 index 0000000000..0a8cccc06b --- /dev/null +++ b/lib/laminas/laminas-servicemanager/COPYRIGHT.md @@ -0,0 +1 @@ +Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. (https://getlaminas.org/) diff --git a/lib/laminas/laminas-servicemanager/LICENSE.md b/lib/laminas/laminas-servicemanager/LICENSE.md new file mode 100644 index 0000000000..10b40f1423 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/LICENSE.md @@ -0,0 +1,26 @@ +Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +- Neither the name of Laminas Foundation nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/laminas/laminas-servicemanager/README.md b/lib/laminas/laminas-servicemanager/README.md new file mode 100644 index 0000000000..794a340298 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/README.md @@ -0,0 +1,40 @@ +# laminas-servicemanager + +[![Build Status](https://github.com/laminas/laminas-servicemanager/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/laminas/laminas-servicemanager/actions/workflows/continuous-integration.yml) +[![Psalm coverage](https://shepherd.dev/github/laminas/laminas-servicemanager/coverage.svg?)](https://shepherd.dev/github/laminas/laminas-servicemanager) + +> ## 🇷🇺 Русским гражданам +> +> Мы, участники Laminas, родились и живем в разных странах. У многих из нас есть друзья, родственники и коллеги как в России, так и в Украине. Некоторые из нас родились в России. Некоторые из нас живут в России. У некоторых бабушки и дедушки сражались с фашистами во Второй мировой войне. Здесь никто не поддерживает фашизм. +> +> У одного из нас есть украинская родственница, которая спаслась из дома вместе с сыном. Поезд задержался из-за бомбежки на дороге впереди. У нас есть друзья, которые прячутся в бомбоубежищах. Мы с тревогой ждем весточки от них после воздушных налетов, которые беспорядочно наносят удары и попадают по больницам, школам, детским садам и домам. Мы не берем это из каких-либо СМИ. Мы наблюдаем это напрямую. +> +> Вы доверяете нам достаточно, чтоб использовать наши программы, и мы просим вас довериться нам вновь. Мы нуждаемся в помощи. Выходите и протестуйте против этой бесполезной войны. Остановите кровопролитие. Скажите "Нет войне!" +> +> ## 🇺🇸 To Citizens of Russia +> +> We at Laminas come from all over the world. Many of us have friends, family and colleagues in both Russia and Ukraine. Some of us were born in Russia. Some of us currently live in Russia. Some have grandparents who fought Nazis in World War II. Nobody here supports fascism. +> +> One team member has a Ukrainian relative who fled her home with her son. The train was delayed due to bombing on the road ahead. We have friends who are hiding in bomb shelters. We anxiously follow up on them after the air raids, which indiscriminately fire at hospitals, schools, kindergartens and houses. We're not taking this from any media. These are our actual experiences. +> +> You trust us enough to use our software. We ask that you trust us to say the truth on this. We need your help. Go out and protest this unnecessary war. Stop the bloodshed. Say "stop the war!" + +The Service Locator design pattern is implemented by the `Laminas\ServiceManager` +component. The Service Locator is a service/object locator, tasked with +retrieving other objects. + +- File issues at https://github.com/laminas/laminas-servicemanager/issues +- [Online documentation](https://docs.laminas.dev/laminas-servicemanager) +- [Documentation source files](docs/book/) + +## Benchmarks + +We provide scripts for benchmarking laminas-servicemanager using the +[PHPBench](https://github.com/phpbench/phpbench) framework; these can be +found in the `benchmarks/` directory. + +To execute the benchmarks you can run the following command: + +```bash +$ vendor/bin/phpbench run --report=aggregate +``` diff --git a/lib/laminas/laminas-servicemanager/bin/generate-deps-for-config-factory b/lib/laminas/laminas-servicemanager/bin/generate-deps-for-config-factory new file mode 100644 index 0000000000..8ee1105a53 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/bin/generate-deps-for-config-factory @@ -0,0 +1,22 @@ +#!/usr/bin/env php +=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + } + ], + "packages-dev": [ + { + "name": "amphp/amp", + "version": "v2.6.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/amp.git", + "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/amp/zipball/9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^7 | ^8 | ^9", + "psalm/phar": "^3.11@dev", + "react/promise": "^2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "files": [ + "lib/functions.php", + "lib/Internal/functions.php" + ], + "psr-4": { + "Amp\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "https://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v2.6.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2022-02-20T17:52:18+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v1.8.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", + "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", + "shasum": "" + }, + "require": { + "amphp/amp": "^2", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.4", + "friendsofphp/php-cs-fixer": "^2.3", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6 || ^7 || ^8", + "psalm/phar": "^3.11.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "lib/functions.php" + ], + "psr-4": { + "Amp\\ByteStream\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "http://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2021-03-30T17:13:30+00:00" + }, + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99.5", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-01-17T14:14:24+00:00" + }, + { + "name": "composer/pcre", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/e300eb6c535192decd27a85bc72a9290f0d6b3bd", + "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.0.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-02-25T20:21:48+00:00" + }, + { + "name": "composer/semver", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-04-01T19:23:25+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "ced299686f41dce890debac69273b47ffe98a40c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", + "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-02-25T21:32:43+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "doctrine/annotations", + "version": "1.13.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "648b0343343565c4a056bfc8392201385e8d89f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/648b0343343565c4a056bfc8392201385e8d89f0", + "reference": "648b0343343565c4a056bfc8392201385e8d89f0", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^6.0 || ^8.1", + "phpstan/phpstan": "^1.4.10 || ^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", + "symfony/cache": "^4.4 || ^5.2", + "vimeo/psalm": "^4.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.13.3" + }, + "time": "2022-07-02T10:48:51+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-03-03T08:28:38+00:00" + }, + { + "name": "doctrine/lexer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2022-02-28T11:07:21+00:00" + }, + { + "name": "felixfbecker/advanced-json-rpc", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "php": "^7.1 || ^8.0", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "A more advanced JSONRPC implementation", + "support": { + "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", + "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" + }, + "time": "2021-06-11T22:34:44+00:00" + }, + { + "name": "felixfbecker/language-server-protocol", + "version": "v1.5.2", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", + "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1", + "vimeo/psalm": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "PHP classes for the Language Server Protocol", + "keywords": [ + "language", + "microsoft", + "php", + "server" + ], + "support": { + "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" + }, + "time": "2022-03-02T22:36:06+00:00" + }, + { + "name": "laminas/laminas-code", + "version": "4.5.2", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-code.git", + "reference": "da01fb74c08f37e20e7ae49f1e3ee09aa401ebad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-code/zipball/da01fb74c08f37e20e7ae49f1e3ee09aa401ebad", + "reference": "da01fb74c08f37e20e7ae49f1e3ee09aa401ebad", + "shasum": "" + }, + "require": { + "php": ">=7.4, <8.2" + }, + "require-dev": { + "doctrine/annotations": "^1.13.2", + "ext-phar": "*", + "laminas/laminas-coding-standard": "^2.3.0", + "laminas/laminas-stdlib": "^3.6.1", + "phpunit/phpunit": "^9.5.10", + "psalm/plugin-phpunit": "^0.16.1", + "vimeo/psalm": "^4.13.1" + }, + "suggest": { + "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", + "laminas/laminas-stdlib": "Laminas\\Stdlib component" + }, + "type": "library", + "autoload": { + "files": [ + "polyfill/ReflectionEnumPolyfill.php" + ], + "psr-4": { + "Laminas\\Code\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Extensions to the PHP Reflection API, static code scanning, and code generation", + "homepage": "https://laminas.dev", + "keywords": [ + "code", + "laminas", + "laminasframework" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-code/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-code/issues", + "rss": "https://github.com/laminas/laminas-code/releases.atom", + "source": "https://github.com/laminas/laminas-code" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-06-06T11:26:02+00:00" + }, + { + "name": "laminas/laminas-coding-standard", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-coding-standard.git", + "reference": "bcf6e07fe4690240be7beb6d884d0b0fafa6a251" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-coding-standard/zipball/bcf6e07fe4690240be7beb6d884d0b0fafa6a251", + "reference": "bcf6e07fe4690240be7beb6d884d0b0fafa6a251", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "php": "^7.3 || ^8.0", + "slevomat/coding-standard": "^7.0", + "squizlabs/php_codesniffer": "^3.6", + "webimpress/coding-standard": "^1.2" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "LaminasCodingStandard\\": "src/LaminasCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Laminas Coding Standard", + "homepage": "https://laminas.dev", + "keywords": [ + "Coding Standard", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-coding-standard/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-coding-standard/issues", + "rss": "https://github.com/laminas/laminas-coding-standard/releases.atom", + "source": "https://github.com/laminas/laminas-coding-standard" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-05-29T15:53:59+00:00" + }, + { + "name": "laminas/laminas-container-config-test", + "version": "0.7.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-container-config-test.git", + "reference": "799bbd667c25bf5b88ff879be2ac0e33a5a15185" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-container-config-test/zipball/799bbd667c25bf5b88ff879be2ac0e33a5a15185", + "reference": "799bbd667c25bf5b88ff879be2ac0e33a5a15185", + "shasum": "" + }, + "require": { + "php": "^7.4 || ~8.0.0 || ~8.1.0", + "psr/container": "^1.0 || ^2.0" + }, + "conflict": { + "zendframework/zend-container-config-test": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "^2.3", + "phpunit/phpunit": "^9.5.21", + "psalm/plugin-phpunit": "^0.17.0", + "vimeo/psalm": "^4.24.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/TestAsset/function-factory.php", + "src/TestAsset/function-factory-with-name.php", + "src/TestAsset/function-factory.legacy.php", + "src/TestAsset/function-factory-with-name.legacy.php" + ], + "psr-4": { + "Laminas\\ContainerConfigTest\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Mezzio PSR-11 container configuration tests", + "homepage": "https://laminas.dev", + "keywords": [ + "PSR-11", + "container", + "laminas", + "mezzio", + "test" + ], + "support": { + "chat": "https://laminas.dev/chat", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-container-config-test/issues", + "rss": "https://github.com/laminas/laminas-container-config-test/releases.atom", + "source": "https://github.com/laminas/laminas-container-config-test" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-07-24T20:30:12+00:00" + }, + { + "name": "laminas/laminas-dependency-plugin", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-dependency-plugin.git", + "reference": "73cfb63ddca9d6bfedad5e0a038f6d55063975a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-dependency-plugin/zipball/73cfb63ddca9d6bfedad5e0a038f6d55063975a3", + "reference": "73cfb63ddca9d6bfedad5e0a038f6d55063975a3", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1 || ^2.0", + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "require-dev": { + "composer/composer": "^1.9 || ^2.0", + "laminas/laminas-coding-standard": "^2.2.1", + "mikey179/vfsstream": "^1.6.10@alpha", + "phpunit/phpunit": "^9.5.5", + "psalm/plugin-phpunit": "^0.15.1", + "roave/security-advisories": "dev-master", + "vimeo/psalm": "^4.5" + }, + "type": "composer-plugin", + "extra": { + "class": "Laminas\\DependencyPlugin\\DependencyRewriterPluginDelegator" + }, + "autoload": { + "psr-4": { + "Laminas\\DependencyPlugin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Replace zendframework and zfcampus packages with their Laminas Project equivalents.", + "support": { + "issues": "https://github.com/laminas/laminas-dependency-plugin/issues", + "source": "https://github.com/laminas/laminas-dependency-plugin/tree/2.2.0" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-08T17:51:35+00:00" + }, + { + "name": "mikey179/vfsstream", + "version": "v1.6.11", + "source": { + "type": "git", + "url": "https://github.com/bovigo/vfsStream.git", + "reference": "17d16a85e6c26ce1f3e2fa9ceeacdc2855db1e9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/17d16a85e6c26ce1f3e2fa9ceeacdc2855db1e9f", + "reference": "17d16a85e6c26ce1f3e2fa9ceeacdc2855db1e9f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "org\\bovigo\\vfs\\": "src/main/php" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Frank Kleine", + "homepage": "http://frankkleine.de/", + "role": "Developer" + } + ], + "description": "Virtual file system to mock the real file system in unit tests.", + "homepage": "http://vfs.bovigo.org/", + "support": { + "issues": "https://github.com/bovigo/vfsStream/issues", + "source": "https://github.com/bovigo/vfsStream/tree/master", + "wiki": "https://github.com/bovigo/vfsStream/wiki" + }, + "time": "2022-02-23T02:02:42+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2022-03-03T13:19:32+00:00" + }, + { + "name": "netresearch/jsonmapper", + "version": "v4.0.0", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", + "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v4.0.0" + }, + "time": "2020-12-01T19:48:11+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.14.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0" + }, + "time": "2022-05-31T20:59:12+00:00" + }, + { + "name": "ocramius/proxy-manager", + "version": "2.13.1", + "source": { + "type": "git", + "url": "https://github.com/Ocramius/ProxyManager.git", + "reference": "e32bc1986fb6fa318ce35e573c654e3ddfb4848d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Ocramius/ProxyManager/zipball/e32bc1986fb6fa318ce35e573c654e3ddfb4848d", + "reference": "e32bc1986fb6fa318ce35e573c654e3ddfb4848d", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.1.0", + "laminas/laminas-code": "^4.3.0", + "php": "~7.4.1 || ~8.0.0", + "webimpress/safe-writer": "^2.2.0" + }, + "conflict": { + "doctrine/annotations": "<1.6.1", + "laminas/laminas-stdlib": "<3.2.1", + "thecodingmachine/safe": "<1.3.3", + "zendframework/zend-stdlib": "<3.2.1" + }, + "require-dev": { + "codelicia/xulieta": "^0.1.6", + "doctrine/coding-standard": "^8.2.1", + "ext-phar": "*", + "infection/infection": "^0.21.5", + "nikic/php-parser": "^4.10.5", + "phpbench/phpbench": "^0.17.1 || 1.0.0-alpha2", + "phpunit/phpunit": "^9.5.4", + "slevomat/coding-standard": "^6.3.10", + "squizlabs/php_codesniffer": "^3.6.0", + "vimeo/psalm": "^4.4.1" + }, + "suggest": { + "laminas/laminas-json": "To have the JsonRpc adapter (Remote Object feature)", + "laminas/laminas-soap": "To have the Soap adapter (Remote Object feature)", + "laminas/laminas-xmlrpc": "To have the XmlRpc adapter (Remote Object feature)", + "ocramius/generated-hydrator": "To have very fast object to array to object conversion for ghost objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "ProxyManager\\": "src/ProxyManager" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.io/" + } + ], + "description": "A library providing utilities to generate, instantiate and generally operate with Object Proxies", + "homepage": "https://github.com/Ocramius/ProxyManager", + "keywords": [ + "aop", + "lazy loading", + "proxy", + "proxy pattern", + "service proxies" + ], + "support": { + "issues": "https://github.com/Ocramius/ProxyManager/issues", + "source": "https://github.com/Ocramius/ProxyManager/tree/2.13.1" + }, + "funding": [ + { + "url": "https://github.com/Ocramius", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ocramius/proxy-manager", + "type": "tidelift" + } + ], + "time": "2022-03-05T18:42:03+00:00" + }, + { + "name": "openlss/lib-array2xml", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/nullivex/lib-array2xml.git", + "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nullivex/lib-array2xml/zipball/a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", + "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "autoload": { + "psr-0": { + "LSS": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Bryan Tong", + "email": "bryan@nullivex.com", + "homepage": "https://www.nullivex.com" + }, + { + "name": "Tony Butler", + "email": "spudz76@gmail.com", + "homepage": "https://www.nullivex.com" + } + ], + "description": "Array2XML conversion library credit to lalit.org", + "homepage": "https://www.nullivex.com", + "keywords": [ + "array", + "array conversion", + "xml", + "xml conversion" + ], + "support": { + "issues": "https://github.com/nullivex/lib-array2xml/issues", + "source": "https://github.com/nullivex/lib-array2xml/tree/master" + }, + "time": "2019-03-29T20:06:56+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpbench/container", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/phpbench/container.git", + "reference": "6d555ff7174fca13f9b1ec0b4a089ed41d0ab392" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/container/zipball/6d555ff7174fca13f9b1ec0b4a089ed41d0ab392", + "reference": "6d555ff7174fca13f9b1ec0b4a089ed41d0ab392", + "shasum": "" + }, + "require": { + "psr/container": "^1.0|^2.0", + "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "phpstan/phpstan": "^0.12.52", + "phpunit/phpunit": "^8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\DependencyInjection\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "Simple, configurable, service container.", + "support": { + "issues": "https://github.com/phpbench/container/issues", + "source": "https://github.com/phpbench/container/tree/2.2.1" + }, + "time": "2022-01-25T10:17:35+00:00" + }, + { + "name": "phpbench/dom", + "version": "0.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpbench/dom.git", + "reference": "b013b717832ddbaadf2a40984b04bc66af9a7110" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/dom/zipball/b013b717832ddbaadf2a40984b04bc66af9a7110", + "reference": "b013b717832ddbaadf2a40984b04bc66af9a7110", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": "^7.2||^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.18", + "phpstan/phpstan": "^0.12.83", + "phpunit/phpunit": "^8.0||^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\Dom\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "DOM wrapper to simplify working with the PHP DOM implementation", + "support": { + "issues": "https://github.com/phpbench/dom/issues", + "source": "https://github.com/phpbench/dom/tree/0.3.2" + }, + "time": "2021-09-24T15:26:07+00:00" + }, + { + "name": "phpbench/phpbench", + "version": "1.2.6", + "source": { + "type": "git", + "url": "https://github.com/phpbench/phpbench.git", + "reference": "c30fac992e72b505a1f131790583647f4d3255c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/c30fac992e72b505a1f131790583647f4d3255c3", + "reference": "c30fac992e72b505a1f131790583647f4d3255c3", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.13", + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "ext-tokenizer": "*", + "php": "^7.3 || ^8.0", + "phpbench/container": "^2.1", + "phpbench/dom": "~0.3.1", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "seld/jsonlint": "^1.1", + "symfony/console": "^4.2 || ^5.0 || ^6.0", + "symfony/filesystem": "^4.2 || ^5.0 || ^6.0", + "symfony/finder": "^4.2 || ^5.0 || ^6.0", + "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0", + "symfony/process": "^4.2 || ^5.0 || ^6.0", + "webmozart/path-util": "^2.3" + }, + "require-dev": { + "dantleech/invoke": "^2.0", + "friendsofphp/php-cs-fixer": "^3.0", + "jangregor/phpstan-prophecy": "^0.8.1", + "phpspec/prophecy": "^1.12", + "phpstan/phpstan": "^0.12.7", + "phpunit/phpunit": "^8.5.8 || ^9.0", + "symfony/error-handler": "^5.2 || ^6.0", + "symfony/var-dumper": "^4.0 || ^5.0 || ^6.0" + }, + "suggest": { + "ext-xdebug": "For Xdebug profiling extension." + }, + "bin": [ + "bin/phpbench" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "files": [ + "lib/Report/Func/functions.php" + ], + "psr-4": { + "PhpBench\\": "lib/", + "PhpBench\\Extensions\\XDebug\\": "extensions/xdebug/lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "PHP Benchmarking Framework", + "support": { + "issues": "https://github.com/phpbench/phpbench/issues", + "source": "https://github.com/phpbench/phpbench/tree/1.2.6" + }, + "funding": [ + { + "url": "https://github.com/dantleech", + "type": "github" + } + ], + "time": "2022-07-19T19:52:39+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + }, + "time": "2021-10-19T17:43:47+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "77a32518733312af16a44300404e945338981de3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", + "reference": "77a32518733312af16a44300404e945338981de3", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" + }, + "time": "2022-03-15T21:29:03+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.2", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0 || ^7.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" + }, + "time": "2021-12-08T12:19:24+00:00" + }, + { + "name": "phpspec/prophecy-phpunit", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy-phpunit.git", + "reference": "2d7a9df55f257d2cba9b1d0c0963a54960657177" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy-phpunit/zipball/2d7a9df55f257d2cba9b1d0c0963a54960657177", + "reference": "2d7a9df55f257d2cba9b1d0c0963a54960657177", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8", + "phpspec/prophecy": "^1.3", + "phpunit/phpunit": "^9.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\PhpUnit\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christophe Coevoet", + "email": "stof@notk.org" + } + ], + "description": "Integrating the Prophecy mocking library in PHPUnit test cases", + "homepage": "http://phpspec.net", + "keywords": [ + "phpunit", + "prophecy" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy-phpunit/issues", + "source": "https://github.com/phpspec/prophecy-phpunit/tree/v2.0.1" + }, + "time": "2020-07-09T08:33:42+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.6.4", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "135607f9ccc297d6923d49c2bcf309f509413215" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/135607f9ccc297d6923d49c2bcf309f509413215", + "reference": "135607f9ccc297d6923d49c2bcf309f509413215", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.6.4" + }, + "time": "2022-06-26T13:09:08+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.15", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.13.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-03-07T09:28:20+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.5.21", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0e32b76be457de00e83213528f6bb37e2a38fcb1", + "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpspec/prophecy": "^1.12.1", + "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.5", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.3", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.0", + "sebastian/version": "^3.0.2" + }, + "require-dev": { + "phpspec/prophecy-phpunit": "^2.0.1" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.21" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-06-19T12:14:25+00:00" + }, + { + "name": "psalm/plugin-phpunit", + "version": "0.17.0", + "source": { + "type": "git", + "url": "https://github.com/psalm/psalm-plugin-phpunit.git", + "reference": "45951541beef07e93e3ad197daf01da88e85c31d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/psalm/psalm-plugin-phpunit/zipball/45951541beef07e93e3ad197daf01da88e85c31d", + "reference": "45951541beef07e93e3ad197daf01da88e85c31d", + "shasum": "" + }, + "require": { + "composer/package-versions-deprecated": "^1.10", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "ext-simplexml": "*", + "php": "^7.1 || ^8.0", + "vimeo/psalm": "dev-master || dev-4.x || ^4.5" + }, + "conflict": { + "phpunit/phpunit": "<7.5" + }, + "require-dev": { + "codeception/codeception": "^4.0.3", + "php": "^7.3 || ^8.0", + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.3.1", + "weirdan/codeception-psalm-module": "^0.11.0", + "weirdan/prophecy-shim": "^1.0 || ^2.0" + }, + "type": "psalm-plugin", + "extra": { + "psalm": { + "pluginClass": "Psalm\\PhpUnitPlugin\\Plugin" + } + }, + "autoload": { + "psr-4": { + "Psalm\\PhpUnitPlugin\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Brown", + "email": "github@muglug.com" + } + ], + "description": "Psalm plugin for PHPUnit", + "support": { + "issues": "https://github.com/psalm/psalm-plugin-phpunit/issues", + "source": "https://github.com/psalm/psalm-plugin-phpunit/tree/0.17.0" + }, + "time": "2022-06-14T17:05:57+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:49:45+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:10:38+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-04-03T09:37:03+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-11-11T14:18:36+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-02-14T08:28:10+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:17:30+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-03-15T09:54:48+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "seld/jsonlint", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "4211420d25eba80712bff236a98960ef68b866b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/4211420d25eba80712bff236a98960ef68b866b7", + "reference": "4211420d25eba80712bff236a98960ef68b866b7", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.5", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.9.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2022-04-01T13:37:23+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "aff06ae7a84e4534bf6f821dc982a93a5d477c90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/aff06ae7a84e4534bf6f821dc982a93a5d477c90", + "reference": "aff06ae7a84e4534bf6f821dc982a93a5d477c90", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.7.1", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-25T10:58:12+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.7.1", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619", + "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2022-06-18T07:21:10+00:00" + }, + { + "name": "symfony/console", + "version": "v5.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "4d671ab4ddac94ee439ea73649c69d9d200b5000" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/4d671ab4ddac94ee439ea73649c69d9d200b5000", + "reference": "4d671ab4ddac94ee439ea73649c69d9d200b5000", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.4.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-26T13:00:04+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "36a017fa4cce1eff1b8e8129ff53513abcef05ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/36a017fa4cce1eff1b8e8129ff53513abcef05ba", + "reference": "36a017fa4cce1eff1b8e8129ff53513abcef05ba", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.4.9" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-20T13:55:35+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "9b630f3427f3ebe7cd346c277a1408b00249dad9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/9b630f3427f3ebe7cd346c277a1408b00249dad9", + "reference": "9b630f3427f3ebe7cd346c277a1408b00249dad9", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-15T08:07:45+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v5.4.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "cc1147cb11af1b43f503ac18f31aa3bec213aba8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/cc1147cb11af1b43f503ac18f31aa3bec213aba8", + "reference": "cc1147cb11af1b43f503ac18f31aa3bec213aba8", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php73": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v5.4.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "433d05519ce6990bf3530fba6957499d327395c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", + "reference": "433d05519ce6990bf3530fba6957499d327395c2", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-10T07:21:04+00:00" + }, + { + "name": "symfony/process", + "version": "v5.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/597f3fff8e3e91836bb0bd38f5718b56ddbde2f3", + "reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-04-08T05:07:18+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-30T19:17:29+00:00" + }, + { + "name": "symfony/string", + "version": "v5.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "4432bc7df82a554b3e413a8570ce2fea90e94097" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/4432bc7df82a554b3e413a8570ce2fea90e94097", + "reference": "4432bc7df82a554b3e413a8570ce2fea90e94097", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-26T15:57:47+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + }, + { + "name": "vimeo/psalm", + "version": "4.24.0", + "source": { + "type": "git", + "url": "https://github.com/vimeo/psalm.git", + "reference": "06dd975cb55d36af80f242561738f16c5f58264f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/06dd975cb55d36af80f242561738f16c5f58264f", + "reference": "06dd975cb55d36af80f242561738f16c5f58264f", + "shasum": "" + }, + "require": { + "amphp/amp": "^2.4.2", + "amphp/byte-stream": "^1.5", + "composer/package-versions-deprecated": "^1.8.0", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^1.1 || ^2.0 || ^3.0", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "ext-ctype": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "felixfbecker/advanced-json-rpc": "^3.0.3", + "felixfbecker/language-server-protocol": "^1.5", + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "nikic/php-parser": "^4.13", + "openlss/lib-array2xml": "^1.0", + "php": "^7.1|^8", + "sebastian/diff": "^3.0 || ^4.0", + "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0 || ^6.0", + "symfony/polyfill-php80": "^1.25", + "webmozart/path-util": "^2.3" + }, + "provide": { + "psalm/psalm": "self.version" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2", + "brianium/paratest": "^4.0||^6.0", + "ext-curl": "*", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpdocumentor/reflection-docblock": "^5", + "phpmyadmin/sql-parser": "5.1.0||dev-master", + "phpspec/prophecy": ">=1.9.0", + "phpunit/phpunit": "^9.0", + "psalm/plugin-phpunit": "^0.16", + "slevomat/coding-standard": "^7.0", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.3 || ^5.0 || ^6.0", + "weirdan/prophecy-shim": "^1.0 || ^2.0" + }, + "suggest": { + "ext-curl": "In order to send data to shepherd", + "ext-igbinary": "^2.0.5 is required, used to serialize caching data" + }, + "bin": [ + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalter" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev", + "dev-3.x": "3.x-dev", + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php", + "src/spl_object_id.php" + ], + "psr-4": { + "Psalm\\": "src/Psalm/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Brown" + } + ], + "description": "A static analysis tool for finding errors in PHP applications", + "keywords": [ + "code", + "inspection", + "php" + ], + "support": { + "issues": "https://github.com/vimeo/psalm/issues", + "source": "https://github.com/vimeo/psalm/tree/4.24.0" + }, + "time": "2022-06-26T11:47:54+00:00" + }, + { + "name": "webimpress/coding-standard", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/webimpress/coding-standard.git", + "reference": "cd0c4b0b97440c337c1f7da17b524674ca2f9ca9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webimpress/coding-standard/zipball/cd0c4b0b97440c337c1f7da17b524674ca2f9ca9", + "reference": "cd0c4b0b97440c337c1f7da17b524674ca2f9ca9", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5.13" + }, + "type": "phpcodesniffer-standard", + "extra": { + "dev-master": "1.2.x-dev", + "dev-develop": "1.3.x-dev" + }, + "autoload": { + "psr-4": { + "WebimpressCodingStandard\\": "src/WebimpressCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "description": "Webimpress Coding Standard", + "keywords": [ + "Coding Standard", + "PSR-2", + "phpcs", + "psr-12", + "webimpress" + ], + "support": { + "issues": "https://github.com/webimpress/coding-standard/issues", + "source": "https://github.com/webimpress/coding-standard/tree/1.2.4" + }, + "funding": [ + { + "url": "https://github.com/michalbundyra", + "type": "github" + } + ], + "time": "2022-02-15T19:52:12+00:00" + }, + { + "name": "webimpress/safe-writer", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/webimpress/safe-writer.git", + "reference": "9d37cc8bee20f7cb2f58f6e23e05097eab5072e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webimpress/safe-writer/zipball/9d37cc8bee20f7cb2f58f6e23e05097eab5072e6", + "reference": "9d37cc8bee20f7cb2f58f6e23e05097eab5072e6", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5.4", + "vimeo/psalm": "^4.7", + "webimpress/coding-standard": "^1.2.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev", + "dev-develop": "2.3.x-dev", + "dev-release-1.0": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Webimpress\\SafeWriter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "description": "Tool to write files safely, to avoid race conditions", + "keywords": [ + "concurrent write", + "file writer", + "race condition", + "safe writer", + "webimpress" + ], + "support": { + "issues": "https://github.com/webimpress/safe-writer/issues", + "source": "https://github.com/webimpress/safe-writer/tree/2.2.0" + }, + "funding": [ + { + "url": "https://github.com/michalbundyra", + "type": "github" + } + ], + "time": "2021-04-19T16:34:45+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + }, + { + "name": "webmozart/path-util", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/path-util.git", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "webmozart/assert": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\PathUtil\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", + "support": { + "issues": "https://github.com/webmozart/path-util/issues", + "source": "https://github.com/webmozart/path-util/tree/2.3.0" + }, + "abandoned": "symfony/filesystem", + "time": "2015-12-17T08:42:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "mikey179/vfsstream": 15 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "~7.4.0 || ~8.0.0 || ~8.1.0" + }, + "platform-dev": [], + "platform-overrides": { + "php": "7.4.99" + }, + "plugin-api-version": "2.3.0" +} diff --git a/lib/laminas/laminas-servicemanager/src/AbstractFactory/ConfigAbstractFactory.php b/lib/laminas/laminas-servicemanager/src/AbstractFactory/ConfigAbstractFactory.php new file mode 100644 index 0000000000..b51dfdf01b --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/AbstractFactory/ConfigAbstractFactory.php @@ -0,0 +1,80 @@ +has('config')) { + return false; + } + $config = $container->get('config'); + if (! isset($config[self::class])) { + return false; + } + $dependencies = $config[self::class]; + + return is_array($dependencies) && array_key_exists($requestedName, $dependencies); + } + + /** + * {@inheritDoc} + */ + public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null) + { + if (! $container->has('config')) { + throw new ServiceNotCreatedException('Cannot find a config array in the container'); + } + + $config = $container->get('config'); + + if (! (is_array($config) || $config instanceof ArrayObject)) { + throw new ServiceNotCreatedException('Config must be an array or an instance of ArrayObject'); + } + if (! isset($config[self::class])) { + throw new ServiceNotCreatedException('Cannot find a `' . self::class . '` key in the config array'); + } + + $dependencies = $config[self::class]; + + if ( + ! is_array($dependencies) + || ! array_key_exists($requestedName, $dependencies) + || ! is_array($dependencies[$requestedName]) + ) { + throw new ServiceNotCreatedException('Service dependencies config must exist and be an array'); + } + + $serviceDependencies = $dependencies[$requestedName]; + + if ($serviceDependencies !== array_values(array_map('strval', $serviceDependencies))) { + $problem = json_encode(array_map('gettype', $serviceDependencies)); + throw new ServiceNotCreatedException( + 'Service dependencies config must be an array of strings, ' . $problem . ' given' + ); + } + + $arguments = array_map([$container, 'get'], $serviceDependencies); + + return new $requestedName(...$arguments); + } +} diff --git a/lib/laminas/laminas-servicemanager/src/AbstractFactory/ReflectionBasedAbstractFactory.php b/lib/laminas/laminas-servicemanager/src/AbstractFactory/ReflectionBasedAbstractFactory.php new file mode 100644 index 0000000000..9d6bdc4aa7 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/AbstractFactory/ReflectionBasedAbstractFactory.php @@ -0,0 +1,247 @@ + + * 'service_manager' => [ + * 'abstract_factories' => [ + * ReflectionBasedAbstractFactory::class, + * ], + * ], + * + * + * Or as a factory, mapping a class name to it: + * + * + * 'service_manager' => [ + * 'factories' => [ + * MyClassWithDependencies::class => ReflectionBasedAbstractFactory::class, + * ], + * ], + * + * + * The latter approach is more explicit, and also more performant. + * + * The factory has the following constraints/features: + * + * - A parameter named `$config` typehinted as an array will receive the + * application "config" service (i.e., the merged configuration). + * - Parameters type-hinted against array, but not named `$config` will + * be injected with an empty array. + * - Scalar parameters will result in an exception being thrown, unless + * a default value is present; if the default is present, that will be used. + * - If a service cannot be found for a given typehint, the factory will + * raise an exception detailing this. + * - Some services provided by Laminas components do not have + * entries based on their class name (for historical reasons); the + * factory allows defining a map of these class/interface names to the + * corresponding service name to allow them to resolve. + * + * `$options` passed to the factory are ignored in all cases, as we cannot + * make assumptions about which argument(s) they might replace. + * + * Based on the LazyControllerAbstractFactory from laminas-mvc. + */ +class ReflectionBasedAbstractFactory implements AbstractFactoryInterface +{ + /** + * Maps known classes/interfaces to the service that provides them; only + * required for those services with no entry based on the class/interface + * name. + * + * Extend the class if you wish to add to the list. + * + * Example: + * + * + * [ + * \Laminas\Filter\FilterPluginManager::class => 'FilterManager', + * \Laminas\Validator\ValidatorPluginManager::class => 'ValidatorManager', + * ] + * + * + * @var string[] + */ + protected $aliases = []; + + /** + * Allows overriding the internal list of aliases. These should be of the + * form `class name => well-known service name`; see the documentation for + * the `$aliases` property for details on what is accepted. + * + * @param string[] $aliases + */ + public function __construct(array $aliases = []) + { + if (! empty($aliases)) { + $this->aliases = $aliases; + } + } + + /** + * {@inheritDoc} + * + * @return DispatchableInterface + */ + public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null) + { + $reflectionClass = new ReflectionClass($requestedName); + + if (null === ($constructor = $reflectionClass->getConstructor())) { + return new $requestedName(); + } + + $reflectionParameters = $constructor->getParameters(); + + if (empty($reflectionParameters)) { + return new $requestedName(); + } + + $resolver = $container->has('config') + ? $this->resolveParameterWithConfigService($container, $requestedName) + : $this->resolveParameterWithoutConfigService($container, $requestedName); + + $parameters = array_map($resolver, $reflectionParameters); + + return new $requestedName(...$parameters); + } + + /** + * {@inheritDoc} + */ + public function canCreate(ContainerInterface $container, $requestedName) + { + return class_exists($requestedName) && $this->canCallConstructor($requestedName); + } + + private function canCallConstructor(string $requestedName): bool + { + $constructor = (new ReflectionClass($requestedName))->getConstructor(); + + return $constructor === null || $constructor->isPublic(); + } + + /** + * Resolve a parameter to a value. + * + * Returns a callback for resolving a parameter to a value, but without + * allowing mapping array `$config` arguments to the `config` service. + * + * @param string $requestedName + * @return callable + */ + private function resolveParameterWithoutConfigService(ContainerInterface $container, $requestedName) + { + /** + * @param ReflectionParameter $parameter + * @return mixed + * @throws ServiceNotFoundException If type-hinted parameter cannot be + * resolved to a service in the container. + * @psalm-suppress MissingClosureReturnType + */ + return fn(ReflectionParameter $parameter) => $this->resolveParameter($parameter, $container, $requestedName); + } + + /** + * Returns a callback for resolving a parameter to a value, including mapping 'config' arguments. + * + * Unlike resolveParameter(), this version will detect `$config` array + * arguments and have them return the 'config' service. + * + * @param string $requestedName + * @return callable + */ + private function resolveParameterWithConfigService(ContainerInterface $container, $requestedName) + { + /** + * @param ReflectionParameter $parameter + * @return mixed + * @throws ServiceNotFoundException If type-hinted parameter cannot be + * resolved to a service in the container. + */ + return function (ReflectionParameter $parameter) use ($container, $requestedName) { + if ($parameter->getName() === 'config') { + $type = $parameter->getType(); + if ($type instanceof ReflectionNamedType && $type->getName() === 'array') { + return $container->get('config'); + } + } + return $this->resolveParameter($parameter, $container, $requestedName); + }; + } + + /** + * Logic common to all parameter resolution. + * + * @param string $requestedName + * @return mixed + * @throws ServiceNotFoundException If type-hinted parameter cannot be + * resolved to a service in the container. + */ + private function resolveParameter(ReflectionParameter $parameter, ContainerInterface $container, $requestedName) + { + $type = $parameter->getType(); + $type = $type instanceof ReflectionNamedType ? $type->getName() : null; + + if ($type === 'array') { + return []; + } + + if ($type === null || (is_string($type) && ! class_exists($type) && ! interface_exists($type))) { + if (! $parameter->isDefaultValueAvailable()) { + throw new ServiceNotFoundException(sprintf( + 'Unable to create service "%s"; unable to resolve parameter "%s" ' + . 'to a class, interface, or array type', + $requestedName, + $parameter->getName() + )); + } + + return $parameter->getDefaultValue(); + } + + $type = $this->aliases[$type] ?? $type; + + if ($container->has($type)) { + return $container->get($type); + } + + if (! $parameter->isOptional()) { + throw new ServiceNotFoundException(sprintf( + 'Unable to create service "%s"; unable to resolve parameter "%s" using type hint "%s"', + $requestedName, + $parameter->getName(), + $type + )); + } + + // Type not available in container, but the value is optional and has a + // default defined. + return $parameter->getDefaultValue(); + } +} diff --git a/lib/laminas/laminas-servicemanager/src/AbstractFactoryInterface.php b/lib/laminas/laminas-servicemanager/src/AbstractFactoryInterface.php new file mode 100644 index 0000000000..2a8ff71b24 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/AbstractFactoryInterface.php @@ -0,0 +1,53 @@ + + * @psalm-import-type ServiceManagerConfiguration from ServiceManager + * @psalm-suppress PropertyNotSetInConstructor + */ +abstract class AbstractPluginManager extends ServiceManager implements PluginManagerInterface +{ + /** + * Whether or not to auto-add a FQCN as an invokable if it exists. + * + * @var bool + */ + protected $autoAddInvokableClass = true; + + /** + * An object type that the created instance must be instanced of + * + * @var null|string + * @psalm-var null|class-string + */ + protected $instanceOf; + + /** + * Sets the provided $parentLocator as the creation context for all + * factories; for $config, {@see \Laminas\ServiceManager\ServiceManager::configure()} + * for details on its accepted structure. + * + * @param null|ConfigInterface|ContainerInterface $configInstanceOrParentLocator + * @param array $config + * @psalm-param ServiceManagerConfiguration $config + */ + public function __construct($configInstanceOrParentLocator = null, array $config = []) + { + /** @psalm-suppress DocblockTypeContradiction */ + if ( + null !== $configInstanceOrParentLocator + && ! $configInstanceOrParentLocator instanceof ConfigInterface + && ! $configInstanceOrParentLocator instanceof ContainerInterface + ) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects a ConfigInterface or ContainerInterface instance as the first argument; received %s', + self::class, + is_object($configInstanceOrParentLocator) + ? get_class($configInstanceOrParentLocator) + : gettype($configInstanceOrParentLocator) + )); + } + + if ($configInstanceOrParentLocator instanceof ConfigInterface) { + trigger_error(sprintf( + 'Usage of %s as a constructor argument for %s is now deprecated', + ConfigInterface::class, + static::class + ), E_USER_DEPRECATED); + $config = $configInstanceOrParentLocator->toArray(); + } + + parent::__construct($config); + + if (! $configInstanceOrParentLocator instanceof ContainerInterface) { + trigger_error(sprintf( + '%s now expects a %s instance representing the parent container; please update your code', + __METHOD__, + ContainerInterface::class + ), E_USER_DEPRECATED); + } + + $this->creationContext = $configInstanceOrParentLocator instanceof ContainerInterface + ? $configInstanceOrParentLocator + : $this; + } + + /** + * Override configure() to validate service instances. + * + * @param array $config + * @psalm-param ServiceManagerConfiguration $config + * @return self + * @throws InvalidServiceException If an instance passed in the `services` configuration is invalid for the + * plugin manager. + * @throws ContainerModificationsNotAllowedException If the allow override flag has been toggled off, and a + * service instanceexists for a given service. + */ + public function configure(array $config) + { + if (isset($config['services'])) { + /** @psalm-suppress MixedAssignment */ + foreach ($config['services'] as $service) { + $this->validate($service); + } + } + + parent::configure($config); + + return $this; + } + + /** + * Override setService for additional plugin validation. + * + * {@inheritDoc} + * + * @param string|class-string $name + * @param InstanceType $service + */ + public function setService($name, $service) + { + $this->validate($service); + parent::setService($name, $service); + } + + /** + * @param class-string|string $name Service name of plugin to retrieve. + * @param null|array $options Options to use when creating the instance. + * @return mixed + * @psalm-return ($name is class-string ? InstanceType : mixed) + * @throws Exception\ServiceNotFoundException If the manager does not have + * a service definition for the instance, and the service is not + * auto-invokable. + * @throws InvalidServiceException If the plugin created is invalid for the + * plugin context. + */ + public function get($name, ?array $options = null) + { + if (! $this->has($name)) { + if (! $this->autoAddInvokableClass || ! class_exists($name)) { + throw new Exception\ServiceNotFoundException(sprintf( + 'A plugin by the name "%s" was not found in the plugin manager %s', + $name, + static::class + )); + } + + $this->setFactory($name, Factory\InvokableFactory::class); + } + + $instance = ! $options ? parent::get($name) : $this->build($name, $options); + $this->validate($instance); + return $instance; + } + + /** + * {@inheritDoc} + * + * @psalm-assert InstanceType $instance + */ + public function validate($instance) + { + if (method_exists($this, 'validatePlugin')) { + trigger_error(sprintf( + '%s::validatePlugin() has been deprecated as of 3.0; please define validate() instead', + static::class + ), E_USER_DEPRECATED); + $this->validatePlugin($instance); + return; + } + + if (empty($this->instanceOf) || $instance instanceof $this->instanceOf) { + return; + } + + throw new InvalidServiceException(sprintf( + 'Plugin manager "%s" expected an instance of type "%s", but "%s" was received', + self::class, + $this->instanceOf, + is_object($instance) ? get_class($instance) : gettype($instance) + )); + } + + /** + * Implemented for backwards compatibility only. + * + * Returns the creation context. + * + * @deprecated since 3.0.0. The creation context should be passed during + * instantiation instead. + * + * @return void + */ + public function setServiceLocator(ContainerInterface $container) + { + trigger_error(sprintf( + 'Usage of %s is deprecated since v3.0.0; please pass the container to the constructor instead', + __METHOD__ + ), E_USER_DEPRECATED); + $this->creationContext = $container; + } +} diff --git a/lib/laminas/laminas-servicemanager/src/Config.php b/lib/laminas/laminas-servicemanager/src/Config.php new file mode 100644 index 0000000000..8024bdc0cc --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/Config.php @@ -0,0 +1,102 @@ + */ + private array $allowedKeys = [ + 'abstract_factories' => true, + 'aliases' => true, + 'delegators' => true, + 'factories' => true, + 'initializers' => true, + 'invokables' => true, + 'lazy_services' => true, + 'services' => true, + 'shared' => true, + ]; + + /** + * @var array + * @psalm-var ServiceManagerConfigurationType + */ + protected $config = [ + 'abstract_factories' => [], + 'aliases' => [], + 'delegators' => [], + 'factories' => [], + 'initializers' => [], + 'invokables' => [], + 'lazy_services' => [], + 'services' => [], + 'shared' => [], + ]; + + /** + * @psalm-param ServiceManagerConfigurationType $config + */ + public function __construct(array $config = []) + { + // Only merge keys we're interested in + foreach (array_keys($config) as $key) { + if (! isset($this->allowedKeys[$key])) { + unset($config[$key]); + } + } + + /** @psalm-suppress ArgumentTypeCoercion */ + $this->config = $this->merge($this->config, $config); + } + + /** + * @inheritDoc + */ + public function configureServiceManager(ServiceManager $serviceManager) + { + return $serviceManager->configure($this->config); + } + + /** + * @inheritDoc + */ + public function toArray() + { + return $this->config; + } + + /** + * @psalm-param ServiceManagerConfigurationType $a + * @psalm-param ServiceManagerConfigurationType $b + * @psalm-return ServiceManagerConfigurationType + * @psalm-suppress MixedReturnTypeCoercion + */ + private function merge(array $a, array $b) + { + /** @psalm-suppress MixedReturnTypeCoercion */ + return ArrayUtils::merge($a, $b); + } +} diff --git a/lib/laminas/laminas-servicemanager/src/ConfigInterface.php b/lib/laminas/laminas-servicemanager/src/ConfigInterface.php new file mode 100644 index 0000000000..a9c8fc59c7 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/ConfigInterface.php @@ -0,0 +1,90 @@ +|Factory\AbstractFactoryInterface) + * > + * @psalm-type DelegatorsConfigurationType = array< + * string, + * array< + * array-key, + * (class-string|Factory\DelegatorFactoryInterface) + * |callable(ContainerInterface,string,callable():object,array|null):object + * > + * > + * @psalm-type FactoriesConfigurationType = array< + * string, + * (class-string|Factory\FactoryInterface) + * |callable(ContainerInterface,string,array|null):object + * > + * @psalm-type InitializersConfigurationType = array< + * array-key, + * (class-string|Initializer\InitializerInterface) + * |callable(ContainerInterface,object):void + * > + * @psalm-type LazyServicesConfigurationType = array{ + * class_map?:array, + * proxies_namespace?:non-empty-string, + * proxies_target_dir?:non-empty-string, + * write_proxy_files?:bool + * } + * @psalm-type ServiceManagerConfigurationType = array{ + * abstract_factories?: AbstractFactoriesConfigurationType, + * aliases?: array, + * delegators?: DelegatorsConfigurationType, + * factories?: FactoriesConfigurationType, + * initializers?: InitializersConfigurationType, + * invokables?: array, + * lazy_services?: LazyServicesConfigurationType, + * services?: array, + * shared?:array + * } + */ +interface ConfigInterface +{ + /** + * Configure a service manager. + * + * Implementations should pull configuration from somewhere (typically + * local properties) and pass it to a ServiceManager's withConfig() method, + * returning a new instance. + * + * @return ServiceManager + */ + public function configureServiceManager(ServiceManager $serviceManager); + + /** + * Return configuration for a service manager instance as an array. + * + * Implementations MUST return an array compatible with ServiceManager::configure, + * containing one or more of the following keys: + * + * - abstract_factories + * - aliases + * - delegators + * - factories + * - initializers + * - invokables + * - lazy_services + * - services + * - shared + * + * In other words, this should return configuration that can be used to instantiate + * a service manager or plugin manager, or pass to its `withConfig()` method. + * + * @return array + * @psalm-return ServiceManagerConfigurationType + */ + public function toArray(); +} diff --git a/lib/laminas/laminas-servicemanager/src/DelegatorFactoryInterface.php b/lib/laminas/laminas-servicemanager/src/DelegatorFactoryInterface.php new file mode 100644 index 0000000000..1b30ecf603 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/DelegatorFactoryInterface.php @@ -0,0 +1,41 @@ + ' . $cursor; + } + $cycle .= ' -> ' . $alias . "\n"; + + return new self(sprintf( + "A cycle was detected within the aliases definitions:\n%s", + $cycle + )); + } + + /** + * @param string[] $aliases map of referenced services, indexed by alias name (string) + * @return self + */ + public static function fromAliasesMap(array $aliases) + { + $detectedCycles = array_filter(array_map( + static fn($alias) => self::getCycleFor($aliases, $alias), + array_keys($aliases) + )); + + if (! $detectedCycles) { + return new self(sprintf( + "A cycle was detected within the following aliases map:\n\n%s", + self::printReferencesMap($aliases) + )); + } + + return new self(sprintf( + "Cycles were detected within the provided aliases:\n\n%s\n\n" + . "The cycle was detected in the following alias map:\n\n%s", + self::printCycles(self::deDuplicateDetectedCycles($detectedCycles)), + self::printReferencesMap($aliases) + )); + } + + /** + * Retrieves the cycle detected for the given $alias, or `null` if no cycle was detected + * + * @param string[] $aliases + * @param string $alias + * @return array|null + */ + private static function getCycleFor(array $aliases, $alias) + { + $cycleCandidate = []; + $targetName = $alias; + + while (isset($aliases[$targetName])) { + if (isset($cycleCandidate[$targetName])) { + return $cycleCandidate; + } + + $cycleCandidate[$targetName] = true; + $targetName = $aliases[$targetName]; + } + + return null; + } + + /** + * @param string[] $aliases + * @return string + */ + private static function printReferencesMap(array $aliases) + { + $map = []; + + foreach ($aliases as $alias => $reference) { + $map[] = '"' . $alias . '" => "' . $reference . '"'; + } + + return "[\n" . implode("\n", $map) . "\n]"; + } + + /** + * @param string[][] $detectedCycles + * @return string + */ + private static function printCycles(array $detectedCycles) + { + return "[\n" . implode("\n", array_map([self::class, 'printCycle'], $detectedCycles)) . "\n]"; + } + + /** + * @param string[] $detectedCycle + * @return string + * @phpcsSuppress SlevomatCodingStandard.Classes.UnusedPrivateElements.UnusedMethod + */ + private static function printCycle(array $detectedCycle) + { + $fullCycle = array_keys($detectedCycle); + $fullCycle[] = reset($fullCycle); + + return implode( + ' => ', + array_map( + static fn($cycle): string => '"' . $cycle . '"', + $fullCycle + ) + ); + } + + /** + * @param bool[][] $detectedCycles + * @return bool[][] de-duplicated + */ + private static function deDuplicateDetectedCycles(array $detectedCycles) + { + $detectedCyclesByHash = []; + + foreach ($detectedCycles as $detectedCycle) { + $cycleAliases = array_keys($detectedCycle); + + sort($cycleAliases); + + $hash = serialize($cycleAliases); + + $detectedCyclesByHash[$hash] ??= $detectedCycle; + } + + return array_values($detectedCyclesByHash); + } +} diff --git a/lib/laminas/laminas-servicemanager/src/Exception/ExceptionInterface.php b/lib/laminas/laminas-servicemanager/src/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..cccc42df50 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/Exception/ExceptionInterface.php @@ -0,0 +1,14 @@ + $options + * @return object + * @throws ServiceNotFoundException If unable to resolve the service. + * @throws ServiceNotCreatedException If an exception is raised when creating a service. + * @throws ContainerExceptionInterface If any other error occurs. + */ + public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null); +} diff --git a/lib/laminas/laminas-servicemanager/src/Factory/InvokableFactory.php b/lib/laminas/laminas-servicemanager/src/Factory/InvokableFactory.php new file mode 100644 index 0000000000..ab09e38e62 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/Factory/InvokableFactory.php @@ -0,0 +1,29 @@ + map of service names to class names */ + private array $servicesMap; + + /** + * @param array $servicesMap A map of service names to + * class names of their respective classes + */ + public function __construct(LazyLoadingValueHolderFactory $proxyFactory, array $servicesMap) + { + $this->proxyFactory = $proxyFactory; + $this->servicesMap = $servicesMap; + } + + /** + * {@inheritDoc} + * + * @param string $name + * @return VirtualProxyInterface + */ + public function __invoke(ContainerInterface $container, $name, callable $callback, ?array $options = null) + { + if (isset($this->servicesMap[$name])) { + $initializer = function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($callback) { + $proxy->setProxyInitializer(null); + $wrappedInstance = $callback(); + + return true; + }; + + return $this->proxyFactory->createProxy($this->servicesMap[$name], $initializer); + } + + throw new Exception\ServiceNotFoundException( + sprintf('The requested service "%s" was not found in the provided services map', $name) + ); + } +} diff --git a/lib/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php b/lib/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php new file mode 100644 index 0000000000..a0dc4600fc --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/ServiceLocatorInterface.php @@ -0,0 +1,30 @@ + $name + * @param null|array $options + * @return mixed + * @psalm-return ($name is class-string ? T : mixed) + * @throws Exception\ServiceNotFoundException If no factory/abstract + * factory could be found to create the instance. + * @throws Exception\ServiceNotCreatedException If factory/delegator fails + * to create the instance. + * @throws ContainerExceptionInterface If any other error occurs. + */ + public function build($name, ?array $options = null); +} diff --git a/lib/laminas/laminas-servicemanager/src/ServiceManager.php b/lib/laminas/laminas-servicemanager/src/ServiceManager.php new file mode 100644 index 0000000000..c87879c12f --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/ServiceManager.php @@ -0,0 +1,983 @@ + + */ + protected $services = []; + + /** + * Enable/disable shared instances by service name. + * + * Example configuration: + * + * 'shared' => [ + * MyService::class => true, // will be shared, even if "sharedByDefault" is false + * MyOtherService::class => false // won't be shared, even if "sharedByDefault" is true + * ] + * + * @var array + */ + protected $shared = []; + + /** + * Should the services be shared by default? + * + * @var bool + */ + protected $sharedByDefault = true; + + /** + * Service manager was already configured? + * + * @var bool + */ + protected $configured = false; + + /** + * Cached abstract factories from string. + */ + private array $cachedAbstractFactories = []; + + /** + * See {@see \Laminas\ServiceManager\ServiceManager::configure()} for details + * on what $config accepts. + * + * @param array $config + * @psalm-param ServiceManagerConfiguration $config + */ + public function __construct(array $config = []) + { + $this->creationContext = $this; + $this->configure($config); + } + + /** + * Implemented for backwards compatibility with previous plugin managers only. + * + * Returns the creation context. + * + * @deprecated since 3.0.0. Factories using 3.0 should use the container + * instance passed to the factory instead. + * + * @return ContainerInterface + */ + public function getServiceLocator() + { + trigger_error(sprintf( + 'Usage of %s is deprecated since v3.0.0; please use the container passed to the factory instead', + __METHOD__ + ), E_USER_DEPRECATED); + return $this->creationContext; + } + + /** + * {@inheritDoc} + */ + public function get($name) + { + // We start by checking if we have cached the requested service; + // this is the fastest method. + if (isset($this->services[$name])) { + return $this->services[$name]; + } + + // Determine if the service should be shared. + $sharedService = $this->shared[$name] ?? $this->sharedByDefault; + + // We achieve better performance if we can let all alias + // considerations out. + if (! $this->aliases) { + $object = $this->doCreate($name); + + // Cache the object for later, if it is supposed to be shared. + if ($sharedService) { + $this->services[$name] = $object; + } + return $object; + } + + // We now deal with requests which may be aliases. + $resolvedName = $this->aliases[$name] ?? $name; + + // Update shared service information as we checked if the alias was shared before. + if ($resolvedName !== $name) { + $sharedService = $this->shared[$resolvedName] ?? $sharedService; + } + + // The following is only true if the requested service is a shared alias. + $sharedAlias = $sharedService && isset($this->services[$resolvedName]); + + // If the alias is configured as a shared service, we are done. + if ($sharedAlias) { + $this->services[$name] = $this->services[$resolvedName]; + return $this->services[$resolvedName]; + } + + // At this point, we have to create the object. + // We use the resolved name for that. + $object = $this->doCreate($resolvedName); + + // Cache the object for later, if it is supposed to be shared. + if ($sharedService) { + $this->services[$resolvedName] = $object; + } + + // Also cache under the alias name; this allows sharing based on the + // service name used. + if ($sharedAlias) { + $this->services[$name] = $object; + } + + return $object; + } + + /** + * {@inheritDoc} + */ + public function build($name, ?array $options = null) + { + // We never cache when using "build". + $name = $this->aliases[$name] ?? $name; + return $this->doCreate($name, $options); + } + + /** + * {@inheritDoc} + * + * @param string|class-string $name + * @return bool + */ + public function has($name) + { + // Check static services and factories first to speedup the most common requests. + return $this->staticServiceOrFactoryCanCreate($name) || $this->abstractFactoryCanCreate($name); + } + + /** + * Indicate whether or not the instance is immutable. + * + * @param bool $flag + */ + public function setAllowOverride($flag) + { + $this->allowOverride = (bool) $flag; + } + + /** + * Retrieve the flag indicating immutability status. + * + * @return bool + */ + public function getAllowOverride() + { + return $this->allowOverride; + } + + /** + * @param array $config + * @psalm-param ServiceManagerConfiguration $config + * @return self + * @throws ContainerModificationsNotAllowedException If the allow + * override flag has been toggled off, and a service instance + * exists for a given service. + */ + public function configure(array $config) + { + // This is a bulk update/initial configuration, + // so we check all definitions up front. + $this->validateServiceNames($config); + + if (isset($config['services'])) { + $this->services = $config['services'] + $this->services; + } + + if (isset($config['invokables']) && ! empty($config['invokables'])) { + $newAliases = $this->createAliasesAndFactoriesForInvokables($config['invokables']); + // override existing aliases with those created by invokables to ensure + // that they are still present after merging aliases later on + $config['aliases'] = $newAliases + ($config['aliases'] ?? []); + } + + if (isset($config['factories'])) { + $this->factories = $config['factories'] + $this->factories; + } + + if (isset($config['delegators'])) { + $this->mergeDelegators($config['delegators']); + } + + if (isset($config['shared'])) { + $this->shared = $config['shared'] + $this->shared; + } + + if (! empty($config['aliases'])) { + $this->aliases = $config['aliases'] + $this->aliases; + $this->mapAliasesToTargets(); + } elseif (! $this->configured && ! empty($this->aliases)) { + $this->mapAliasesToTargets(); + } + + if (isset($config['shared_by_default'])) { + $this->sharedByDefault = $config['shared_by_default']; + } + + // If lazy service configuration was provided, reset the lazy services + // delegator factory. + if (isset($config['lazy_services']) && ! empty($config['lazy_services'])) { + /** @psalm-suppress MixedPropertyTypeCoercion */ + $this->lazyServices = ArrayUtils::merge($this->lazyServices, $config['lazy_services']); + $this->lazyServicesDelegator = null; + } + + // For abstract factories and initializers, we always directly + // instantiate them to avoid checks during service construction. + if (isset($config['abstract_factories'])) { + $abstractFactories = $config['abstract_factories']; + // $key not needed, but foreach is faster than foreach + array_values. + foreach ($abstractFactories as $key => $abstractFactory) { + $this->resolveAbstractFactoryInstance($abstractFactory); + } + } + + if (isset($config['initializers'])) { + $this->resolveInitializers($config['initializers']); + } + + $this->configured = true; + + return $this; + } + + /** + * Add an alias. + * + * @param string $alias + * @param string $target + * @throws ContainerModificationsNotAllowedException If $alias already + * exists as a service and overrides are disallowed. + */ + public function setAlias($alias, $target) + { + if (isset($this->services[$alias]) && ! $this->allowOverride) { + throw ContainerModificationsNotAllowedException::fromExistingService($alias); + } + + $this->mapAliasToTarget($alias, $target); + } + + /** + * Add an invokable class mapping. + * + * @param string $name Service name + * @param null|string $class Class to which to map; if omitted, $name is + * assumed. + * @throws ContainerModificationsNotAllowedException If $name already + * exists as a service and overrides are disallowed. + */ + public function setInvokableClass($name, $class = null) + { + if (isset($this->services[$name]) && ! $this->allowOverride) { + throw ContainerModificationsNotAllowedException::fromExistingService($name); + } + + $this->createAliasesAndFactoriesForInvokables([$name => $class ?? $name]); + } + + /** + * Specify a factory for a given service name. + * + * @param string $name Service name + * @param string|callable|Factory\FactoryInterface $factory Factory to which to map. + * phpcs:disable Generic.Files.LineLength.TooLong + * @psalm-param class-string|callable(ContainerInterface,string,array|null):object|Factory\FactoryInterface $factory + * phpcs:enable Generic.Files.LineLength.TooLong + * @return void + * @throws ContainerModificationsNotAllowedException If $name already + * exists as a service and overrides are disallowed. + */ + public function setFactory($name, $factory) + { + if (isset($this->services[$name]) && ! $this->allowOverride) { + throw ContainerModificationsNotAllowedException::fromExistingService($name); + } + + $this->factories[$name] = $factory; + } + + /** + * Create a lazy service mapping to a class. + * + * @param string $name Service name to map + * @param null|string $class Class to which to map; if not provided, $name + * will be used for the mapping. + */ + public function mapLazyService($name, $class = null) + { + $this->configure(['lazy_services' => ['class_map' => [$name => $class ?: $name]]]); + } + + /** + * Add an abstract factory for resolving services. + * + * @param string|Factory\AbstractFactoryInterface $factory Abstract factory + * instance or class name. + * @psalm-param class-string|Factory\AbstractFactoryInterface $factory + */ + public function addAbstractFactory($factory) + { + $this->resolveAbstractFactoryInstance($factory); + } + + /** + * Add a delegator for a given service. + * + * @param string $name Service name + * @param string|callable|Factory\DelegatorFactoryInterface $factory Delegator + * factory to assign. + * @psalm-param class-string + * |callable(ContainerInterface,string,callable,array|null) $factory + */ + public function addDelegator($name, $factory) + { + $this->configure(['delegators' => [$name => [$factory]]]); + } + + /** + * Add an initializer. + * + * @param string|callable|Initializer\InitializerInterface $initializer + * @psalm-param class-string + * |callable(ContainerInterface,mixed):void + * |Initializer\InitializerInterface $initializer + */ + public function addInitializer($initializer) + { + $this->configure(['initializers' => [$initializer]]); + } + + /** + * Map a service. + * + * @param string $name Service name + * @param array|object $service + * @throws ContainerModificationsNotAllowedException If $name already + * exists as a service and overrides are disallowed. + */ + public function setService($name, $service) + { + if (isset($this->services[$name]) && ! $this->allowOverride) { + throw ContainerModificationsNotAllowedException::fromExistingService($name); + } + $this->services[$name] = $service; + } + + /** + * Add a service sharing rule. + * + * @param string $name Service name + * @param bool $flag Whether or not the service should be shared. + * @throws ContainerModificationsNotAllowedException If $name already + * exists as a service and overrides are disallowed. + */ + public function setShared($name, $flag) + { + if (isset($this->services[$name]) && ! $this->allowOverride) { + throw ContainerModificationsNotAllowedException::fromExistingService($name); + } + + $this->shared[$name] = (bool) $flag; + } + + /** + * Instantiate initializers for to avoid checks during service construction. + * + * @psalm-param InitializersConfigurationType $initializers + */ + private function resolveInitializers(array $initializers): void + { + foreach ($initializers as $initializer) { + if (is_string($initializer) && class_exists($initializer)) { + $initializer = new $initializer(); + } + + if (is_callable($initializer)) { + $this->initializers[] = $initializer; + continue; + } + + throw InvalidArgumentException::fromInvalidInitializer($initializer); + } + } + + /** + * Get a factory for the given service name + * + * @psalm-return (callable(ContainerInterface,string,array|null):object)|Factory\FactoryInterface + * @throws ServiceNotFoundException + */ + private function getFactory(string $name): callable + { + $factory = $this->factories[$name] ?? null; + + $lazyLoaded = false; + if (is_string($factory) && class_exists($factory)) { + $factory = new $factory(); + $lazyLoaded = true; + } + + if (is_callable($factory)) { + if ($lazyLoaded) { + $this->factories[$name] = $factory; + } + + return $factory; + } + + // Check abstract factories + foreach ($this->abstractFactories as $abstractFactory) { + if ($abstractFactory->canCreate($this->creationContext, $name)) { + return $abstractFactory; + } + } + + throw new ServiceNotFoundException(sprintf( + 'Unable to resolve service "%s" to a factory; are you certain you provided it during configuration?', + $name + )); + } + + /** + * @return object + */ + private function createDelegatorFromName(string $name, ?array $options = null) + { + $creationCallback = function () use ($name, $options) { + // Code is inlined for performance reason, instead of abstracting the creation + $factory = $this->getFactory($name); + return $factory($this->creationContext, $name, $options); + }; + + $initialCreationContext = $this->creationContext; + + foreach ($this->delegators[$name] as $index => $delegatorFactory) { + $delegatorFactory = $this->delegators[$name][$index]; + + if ($delegatorFactory === LazyServiceFactory::class) { + $delegatorFactory = $this->createLazyServiceDelegatorFactory(); + } elseif (is_string($delegatorFactory) && class_exists($delegatorFactory)) { + $delegatorFactory = new $delegatorFactory(); + } + + $this->assertCallableDelegatorFactory($delegatorFactory); + + $this->delegators[$name][$index] = $delegatorFactory; + + $creationCallback = + /** @return object */ + static fn() => $delegatorFactory($initialCreationContext, $name, $creationCallback, $options); + } + + return $creationCallback(); + } + + /** + * Create a new instance with an already resolved name + * + * This is a highly performance sensitive method, do not modify if you have not benchmarked it carefully + * + * @return object + * @throws ServiceNotFoundException If unable to resolve the service. + * @throws ServiceNotCreatedException If an exception is raised when creating a service. + * @throws ContainerExceptionInterface If any other error occurs. + */ + private function doCreate(string $resolvedName, ?array $options = null) + { + try { + if (! isset($this->delegators[$resolvedName])) { + // Let's create the service by fetching the factory + $factory = $this->getFactory($resolvedName); + $object = $factory($this->creationContext, $resolvedName, $options); + } else { + $object = $this->createDelegatorFromName($resolvedName, $options); + } + } catch (ContainerExceptionInterface $exception) { + throw $exception; + } catch (Exception $exception) { + throw new ServiceNotCreatedException(sprintf( + 'Service with name "%s" could not be created. Reason: %s', + $resolvedName, + $exception->getMessage() + ), (int) $exception->getCode(), $exception); + } + + foreach ($this->initializers as $initializer) { + $initializer($this->creationContext, $object); + } + + return $object; + } + + /** + * Create the lazy services delegator factory. + * + * Creates the lazy services delegator factory based on the lazy_services + * configuration present. + * + * @throws ServiceNotCreatedException When the lazy service class_map configuration is missing. + */ + private function createLazyServiceDelegatorFactory(): LazyServiceFactory + { + if ($this->lazyServicesDelegator) { + return $this->lazyServicesDelegator; + } + + if (! isset($this->lazyServices['class_map'])) { + throw new ServiceNotCreatedException('Missing "class_map" config key in "lazy_services"'); + } + + $factoryConfig = new ProxyConfiguration(); + + if (isset($this->lazyServices['proxies_namespace'])) { + $factoryConfig->setProxiesNamespace($this->lazyServices['proxies_namespace']); + } + + if (isset($this->lazyServices['proxies_target_dir'])) { + $factoryConfig->setProxiesTargetDir($this->lazyServices['proxies_target_dir']); + } + + if (! isset($this->lazyServices['write_proxy_files']) || ! $this->lazyServices['write_proxy_files']) { + $factoryConfig->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); + } else { + $factoryConfig->setGeneratorStrategy(new FileWriterGeneratorStrategy( + new FileLocator($factoryConfig->getProxiesTargetDir()) + )); + } + + spl_autoload_register($factoryConfig->getProxyAutoloader()); + + $this->lazyServicesDelegator = new LazyServiceFactory( + new LazyLoadingValueHolderFactory($factoryConfig), + $this->lazyServices['class_map'] + ); + + return $this->lazyServicesDelegator; + } + + /** + * Merge delegators avoiding multiple same delegators for the same service. + * It works with strings and class instances. + * It's not possible to de-duple anonymous functions + * + * @psalm-param DelegatorsConfigurationType $config + * @psalm-return DelegatorsConfigurationType + */ + private function mergeDelegators(array $config): array + { + foreach ($config as $key => $delegators) { + if (! array_key_exists($key, $this->delegators)) { + $this->delegators[$key] = $delegators; + continue; + } + + foreach ($delegators as $delegator) { + if (! in_array($delegator, $this->delegators[$key], true)) { + $this->delegators[$key][] = $delegator; + } + } + } + + return $this->delegators; + } + + /** + * Create aliases and factories for invokable classes. + * + * If an invokable service name does not match the class it maps to, this + * creates an alias to the class (which will later be mapped as an + * invokable factory). The newly created aliases will be returned as an array. + * + * @param array $invokables + * @return array + */ + private function createAliasesAndFactoriesForInvokables(array $invokables): array + { + $newAliases = []; + + foreach ($invokables as $name => $class) { + $this->factories[$class] = Factory\InvokableFactory::class; + if ($name !== $class) { + $this->aliases[$name] = $class; + $newAliases[$name] = $class; + } + } + + return $newAliases; + } + + /** + * Determine if a service for any name provided by a service + * manager configuration(services, aliases, factories, ...) + * already exists, and if it exists, determine if is it allowed + * to get overriden. + * + * Validation in the context of this class means, that for + * a given service name we do not have a service instance + * in the cache OR override is explicitly allowed. + * + * @psalm-param ServiceManagerConfigurationType $config + * @throws ContainerModificationsNotAllowedException If any + * service key is invalid. + */ + private function validateServiceNames(array $config): void + { + if ($this->allowOverride || ! $this->configured) { + return; + } + + if (isset($config['services'])) { + foreach (array_keys($config['services']) as $service) { + if (isset($this->services[$service])) { + throw ContainerModificationsNotAllowedException::fromExistingService($service); + } + } + } + + if (isset($config['aliases'])) { + foreach (array_keys($config['aliases']) as $service) { + if (isset($this->services[$service])) { + throw ContainerModificationsNotAllowedException::fromExistingService($service); + } + } + } + + if (isset($config['invokables'])) { + foreach (array_keys($config['invokables']) as $service) { + if (isset($this->services[$service])) { + throw ContainerModificationsNotAllowedException::fromExistingService($service); + } + } + } + + if (isset($config['factories'])) { + foreach (array_keys($config['factories']) as $service) { + if (isset($this->services[$service])) { + throw ContainerModificationsNotAllowedException::fromExistingService($service); + } + } + } + + if (isset($config['delegators'])) { + foreach (array_keys($config['delegators']) as $service) { + if (isset($this->services[$service])) { + throw ContainerModificationsNotAllowedException::fromExistingService($service); + } + } + } + + if (isset($config['shared'])) { + foreach (array_keys($config['shared']) as $service) { + if (isset($this->services[$service])) { + throw ContainerModificationsNotAllowedException::fromExistingService($service); + } + } + } + + if (isset($config['lazy_services']['class_map'])) { + foreach (array_keys($config['lazy_services']['class_map']) as $service) { + if (isset($this->services[$service])) { + throw ContainerModificationsNotAllowedException::fromExistingService($service); + } + } + } + } + + /** + * Assuming that the alias name is valid (see above) resolve/add it. + * + * This is done differently from bulk mapping aliases for performance reasons, as the + * algorithms for mapping a single item efficiently are different from those of mapping + * many. + */ + private function mapAliasToTarget(string $alias, string $target): void + { + // $target is either an alias or something else + // if it is an alias, resolve it + $this->aliases[$alias] = $this->aliases[$target] ?? $target; + + // a self-referencing alias indicates a cycle + if ($alias === $this->aliases[$alias]) { + throw CyclicAliasException::fromCyclicAlias($alias, $this->aliases); + } + + // finally we have to check if existing incomplete alias definitions + // exist which can get resolved by the new alias + if (in_array($alias, $this->aliases)) { + $r = array_intersect($this->aliases, [$alias]); + // found some, resolve them + foreach ($r as $name => $service) { + $this->aliases[$name] = $target; + } + } + } + + /** + * Assuming that all provided alias keys are valid resolve them. + * + * This function maps $this->aliases in place. + * + * This algorithm is an adaptated version of Tarjans Strongly + * Connected Components. Instead of returning the strongly + * connected components (i.e. cycles in our case), we throw. + * If nodes are not strongly connected (i.e. resolvable in + * our case), they get resolved. + * + * This algorithm is fast for mass updates through configure(). + * It is not appropriate if just a single alias is added. + * + * @see mapAliasToTarget above + */ + private function mapAliasesToTargets(): void + { + $tagged = []; + foreach ($this->aliases as $alias => $target) { + if (isset($tagged[$alias])) { + continue; + } + + $tCursor = $this->aliases[$alias]; + $aCursor = $alias; + if ($aCursor === $tCursor) { + throw CyclicAliasException::fromCyclicAlias($alias, $this->aliases); + } + if (! isset($this->aliases[$tCursor])) { + continue; + } + + $stack = []; + + while (isset($this->aliases[$tCursor])) { + $stack[] = $aCursor; + if ($aCursor === $this->aliases[$tCursor]) { + throw CyclicAliasException::fromCyclicAlias($alias, $this->aliases); + } + $aCursor = $tCursor; + $tCursor = $this->aliases[$tCursor]; + } + + $tagged[$aCursor] = true; + + foreach ($stack as $alias) { + if ($alias === $tCursor) { + throw CyclicAliasException::fromCyclicAlias($alias, $this->aliases); + } + $this->aliases[$alias] = $tCursor; + $tagged[$alias] = true; + } + } + } + + /** + * Instantiate abstract factories in order to avoid checks during service construction. + * + * @param string|Factory\AbstractFactoryInterface $abstractFactory + * @psalm-param class-string|Factory\AbstractFactoryInterface $abstractFactory + */ + private function resolveAbstractFactoryInstance($abstractFactory): void + { + if (is_string($abstractFactory) && class_exists($abstractFactory)) { + // Cached string factory name + if (! isset($this->cachedAbstractFactories[$abstractFactory])) { + $this->cachedAbstractFactories[$abstractFactory] = new $abstractFactory(); + } + + $abstractFactory = $this->cachedAbstractFactories[$abstractFactory]; + } + + if (! $abstractFactory instanceof Factory\AbstractFactoryInterface) { + throw InvalidArgumentException::fromInvalidAbstractFactory($abstractFactory); + } + + $abstractFactoryObjHash = spl_object_hash($abstractFactory); + $this->abstractFactories[$abstractFactoryObjHash] = $abstractFactory; + } + + /** + * Check if a static service or factory exists for the given name. + */ + private function staticServiceOrFactoryCanCreate(string $name): bool + { + if (isset($this->services[$name]) || isset($this->factories[$name])) { + return true; + } + + $resolvedName = $this->aliases[$name] ?? $name; + if ($resolvedName !== $name) { + return $this->staticServiceOrFactoryCanCreate($resolvedName); + } + + return false; + } + + /** + * Check if an abstract factory exists that can create a service for the given name. + */ + private function abstractFactoryCanCreate(string $name): bool + { + foreach ($this->abstractFactories as $abstractFactory) { + if ($abstractFactory->canCreate($this->creationContext, $name)) { + return true; + } + } + + $resolvedName = $this->aliases[$name] ?? $name; + if ($resolvedName !== $name) { + return $this->abstractFactoryCanCreate($resolvedName); + } + + return false; + } + + /** + * @psalm-param mixed $delegatorFactory + * @psalm-assert callable(ContainerInterface,string,callable():object,array|null):object $delegatorFactory + */ + private function assertCallableDelegatorFactory($delegatorFactory): void + { + if ( + $delegatorFactory instanceof Factory\DelegatorFactoryInterface + || is_callable($delegatorFactory) + ) { + return; + } + if (is_string($delegatorFactory)) { + throw new ServiceNotCreatedException(sprintf( + 'An invalid delegator factory was registered; resolved to class or function "%s"' + . ' which does not exist; please provide a valid function name or class name resolving' + . ' to an implementation of %s', + $delegatorFactory, + DelegatorFactoryInterface::class + )); + } + throw new ServiceNotCreatedException(sprintf( + 'A non-callable delegator, "%s", was provided; expected a callable or instance of "%s"', + is_object($delegatorFactory) ? get_class($delegatorFactory) : gettype($delegatorFactory), + DelegatorFactoryInterface::class + )); + } +} diff --git a/lib/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php b/lib/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php new file mode 100644 index 0000000000..c9e6b8752c --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/Tool/ConfigDumper.php @@ -0,0 +1,263 @@ +container = $container; + } + + /** + * @param array $config + * @param string $className + * @param bool $ignoreUnresolved + * @return array + * @throws InvalidArgumentException For invalid $className. + */ + public function createDependencyConfig(array $config, $className, $ignoreUnresolved = false) + { + $this->validateClassName($className); + + $reflectionClass = new ReflectionClass($className); + + // class is an interface; do nothing + if ($reflectionClass->isInterface()) { + return $config; + } + + // class has no constructor, treat it as an invokable + if (! $reflectionClass->getConstructor()) { + return $this->createInvokable($config, $className); + } + + $constructorArguments = $reflectionClass->getConstructor()->getParameters(); + $constructorArguments = array_filter( + $constructorArguments, + static fn(ReflectionParameter $argument): bool => ! $argument->isOptional() + ); + + // has no required parameters, treat it as an invokable + if (empty($constructorArguments)) { + return $this->createInvokable($config, $className); + } + + $classConfig = []; + + foreach ($constructorArguments as $constructorArgument) { + $type = $constructorArgument->getType(); + $argumentType = null !== $type && ! $type->isBuiltin() ? $type->getName() : null; + + if ($argumentType === null) { + if ($ignoreUnresolved) { + // don't throw an exception, just return the previous config + return $config; + } + // don't throw an exception if the class is an already defined service + if ($this->container && $this->container->has($className)) { + return $config; + } + throw new InvalidArgumentException(sprintf( + 'Cannot create config for constructor argument "%s", ' + . 'it has no type hint, or non-class/interface type hint', + $constructorArgument->getName() + )); + } + $config = $this->createDependencyConfig($config, $argumentType, $ignoreUnresolved); + $classConfig[] = $argumentType; + } + + $config[ConfigAbstractFactory::class][$className] = $classConfig; + + return $config; + } + + /** + * @param string $className + * @throws InvalidArgumentException If class name is not a string or does + * not exist. + */ + private function validateClassName($className) + { + if (! is_string($className)) { + throw new InvalidArgumentException('Class name must be a string, ' . gettype($className) . ' given'); + } + + if (! class_exists($className) && ! interface_exists($className)) { + throw new InvalidArgumentException('Cannot find class or interface with name ' . $className); + } + } + + /** + * @param array $config + * @param string $className + * @return array + */ + private function createInvokable(array $config, $className) + { + $config[ConfigAbstractFactory::class][$className] = []; + return $config; + } + + /** + * @param array $config + * @return array + * @throws InvalidArgumentException If ConfigAbstractFactory configuration + * value is not an array. + */ + public function createFactoryMappingsFromConfig(array $config) + { + if (! array_key_exists(ConfigAbstractFactory::class, $config)) { + return $config; + } + + if (! is_array($config[ConfigAbstractFactory::class])) { + throw new InvalidArgumentException( + 'Config key for ' . ConfigAbstractFactory::class . ' should be an array, ' . gettype( + $config[ConfigAbstractFactory::class] + ) . ' given' + ); + } + + foreach ($config[ConfigAbstractFactory::class] as $className => $dependency) { + $config = $this->createFactoryMappings($config, $className); + } + return $config; + } + + /** + * @param array $config + * @param string $className + * @return array + */ + public function createFactoryMappings(array $config, $className) + { + $this->validateClassName($className); + + if ( + array_key_exists('service_manager', $config) + && array_key_exists('factories', $config['service_manager']) + && array_key_exists($className, $config['service_manager']['factories']) + ) { + return $config; + } + + $config['service_manager']['factories'][$className] = ConfigAbstractFactory::class; + return $config; + } + + /** + * @param array $config + * @return string + */ + public function dumpConfigFile(array $config) + { + $prepared = $this->prepareConfig($config); + return sprintf( + self::CONFIG_TEMPLATE, + static::class, + date('Y-m-d H:i:s'), + $prepared + ); + } + + /** + * @param array|Traversable $config + * @param int $indentLevel + * @return string + */ + private function prepareConfig($config, $indentLevel = 1) + { + $indent = str_repeat(' ', $indentLevel * 4); + $entries = []; + foreach ($config as $key => $value) { + $key = $this->createConfigKey($key); + $entries[] = sprintf( + '%s%s%s,', + $indent, + $key ? sprintf('%s => ', $key) : '', + $this->createConfigValue($value, $indentLevel) + ); + } + + $outerIndent = str_repeat(' ', ($indentLevel - 1) * 4); + + return sprintf( + "[\n%s\n%s]", + implode("\n", $entries), + $outerIndent + ); + } + + /** + * @param string|int|null $key + * @return null|string + */ + private function createConfigKey($key) + { + if (is_string($key) && class_exists($key)) { + return sprintf('\\%s::class', $key); + } + + if (is_int($key)) { + return null; + } + + return sprintf("'%s'", $key); + } + + /** + * @param mixed $value + * @param int $indentLevel + * @return string + */ + private function createConfigValue($value, $indentLevel) + { + if (is_array($value) || $value instanceof Traversable) { + return $this->prepareConfig($value, $indentLevel + 1); + } + + if (is_string($value) && class_exists($value)) { + return sprintf('\\%s::class', $value); + } + + return var_export($value, true); + } +} diff --git a/lib/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php b/lib/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php new file mode 100644 index 0000000000..6ce1fda83e --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/Tool/ConfigDumperCommand.php @@ -0,0 +1,248 @@ +, + * class: string, + * ignoreUnresolved: bool + * } + */ +class ConfigDumperCommand +{ + public const COMMAND_DUMP = 'dump'; + public const COMMAND_ERROR = 'error'; + public const COMMAND_HELP = 'help'; + + public const DEFAULT_SCRIPT_NAME = self::class; + + public const HELP_TEMPLATE = <<Usage: + + %s [-h|--help|help] [-i|--ignore-unresolved] + +Arguments: + + -h|--help|help This usage message + -i|--ignore-unresolved Ignore classes with unresolved direct dependencies. + Path to a config file for which to generate + configuration. If the file does not exist, it will + be created. If it does exist, it must return an + array, and the file will be updated with new + configuration. + Name of the class to reflect and for which to + generate dependency configuration. + +Reads the provided configuration file (creating it if it does not exist), +and injects it with ConfigAbstractFactory dependency configuration for +the provided class name, writing the changes back to the file. +EOH; + + private ConsoleHelper $helper; + + private string $scriptName; + + /** + * @param string $scriptName + */ + public function __construct($scriptName = self::DEFAULT_SCRIPT_NAME, ?ConsoleHelper $helper = null) + { + $this->scriptName = $scriptName; + $this->helper = $helper ?: new ConsoleHelper(); + } + + /** + * @param array $args Argument list, minus script name + * @return int Exit status + */ + public function __invoke(array $args) + { + $arguments = $this->parseArgs($args); + + switch ($arguments->command) { + case self::COMMAND_HELP: + $this->help(); + return 0; + case self::COMMAND_ERROR: + $this->helper->writeErrorMessage($arguments->message); + $this->help(STDERR); + return 1; + case self::COMMAND_DUMP: + // fall-through + default: + break; + } + + $dumper = new ConfigDumper(); + try { + $config = $dumper->createDependencyConfig( + $arguments->config, + $arguments->class, + $arguments->ignoreUnresolved + ); + } catch (Exception\InvalidArgumentException $e) { + $this->helper->writeErrorMessage(sprintf( + 'Unable to create config for "%s": %s', + $arguments->class, + $e->getMessage() + )); + $this->help(STDERR); + return 1; + } + + file_put_contents($arguments->configFile, $dumper->dumpConfigFile($config)); + + $this->helper->writeLine(sprintf( + '[DONE] Changes written to %s', + $arguments->configFile + )); + return 0; + } + + /** + * @param array $args + * @return object + */ + private function parseArgs(array $args) + { + if (! $args) { + return $this->createHelpArgument(); + } + + $arg1 = array_shift($args); + + if (in_array($arg1, ['-h', '--help', 'help'], true)) { + return $this->createHelpArgument(); + } + + $ignoreUnresolved = false; + if (in_array($arg1, ['-i', '--ignore-unresolved'], true)) { + $ignoreUnresolved = true; + $arg1 = array_shift($args); + } + + if (! $args) { + return $this->createErrorArgument('Missing class name'); + } + + $configFile = $arg1; + switch (file_exists($configFile)) { + case true: + $config = require $configFile; + + if (! is_array($config)) { + return $this->createErrorArgument(sprintf( + 'Configuration at path "%s" does not return an array.', + $configFile + )); + } + + break; + case false: + // fall-through + default: + if (! is_writable(dirname($configFile))) { + return $this->createErrorArgument(sprintf( + 'Cannot create configuration at path "%s"; not writable.', + $configFile + )); + } + + $config = []; + break; + } + + $class = array_shift($args); + + if (! class_exists($class)) { + return $this->createErrorArgument(sprintf( + 'Class "%s" does not exist or could not be autoloaded.', + $class + )); + } + + return $this->createArguments(self::COMMAND_DUMP, $configFile, $config, $class, $ignoreUnresolved); + } + + /** + * @param resource $resource Defaults to STDOUT + * @return void + */ + private function help($resource = STDOUT) + { + $this->helper->writeLine(sprintf( + self::HELP_TEMPLATE, + $this->scriptName + ), true, $resource); + } + + /** + * @param string $command + * @param string $configFile File from which config originates, and to + * which it will be written. + * @param array $config Parsed configuration. + * @param string $class Name of class to reflect. + * @param bool $ignoreUnresolved If to ignore classes with unresolved direct dependencies. + * @return ArgumentObject + */ + private function createArguments($command, $configFile, $config, $class, $ignoreUnresolved) + { + return (object) [ + 'command' => $command, + 'configFile' => $configFile, + 'config' => $config, + 'class' => $class, + 'ignoreUnresolved' => $ignoreUnresolved, + ]; + } + + /** + * @param string $message + * @return ErrorObject + */ + private function createErrorArgument($message) + { + return (object) [ + 'command' => self::COMMAND_ERROR, + 'message' => $message, + ]; + } + + /** + * @return HelpObject + */ + private function createHelpArgument() + { + return (object) [ + 'command' => self::COMMAND_HELP, + ]; + } +} diff --git a/lib/laminas/laminas-servicemanager/src/Tool/FactoryCreator.php b/lib/laminas/laminas-servicemanager/src/Tool/FactoryCreator.php new file mode 100644 index 0000000000..ae8f39847b --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/Tool/FactoryCreator.php @@ -0,0 +1,164 @@ +getClassName($className); + + return sprintf( + self::FACTORY_TEMPLATE, + preg_replace('/\\\\' . $class . '$/', '', $className), + $this->createImportStatements($className), + $class, + $class, + $class, + $this->createArgumentString($className) + ); + } + + private function getClassName(string $className): string + { + return substr($className, strrpos($className, '\\') + 1); + } + + /** + * @param string $className + * @return array + */ + private function getConstructorParameters($className) + { + $reflectionClass = new ReflectionClass($className); + + if (! $reflectionClass->getConstructor()) { + return []; + } + + $constructorParameters = $reflectionClass->getConstructor()->getParameters(); + + if (empty($constructorParameters)) { + return []; + } + + $constructorParameters = array_filter( + $constructorParameters, + function (ReflectionParameter $argument): bool { + if ($argument->isOptional()) { + return false; + } + + $type = $argument->getType(); + $class = null !== $type && ! $type->isBuiltin() ? $type->getName() : null; + + if (null === $class) { + throw new InvalidArgumentException(sprintf( + 'Cannot identify type for constructor argument "%s"; ' + . 'no type hint, or non-class/interface type hint', + $argument->getName() + )); + } + + return true; + } + ); + + if (empty($constructorParameters)) { + return []; + } + + return array_map(function (ReflectionParameter $parameter): ?string { + $type = $parameter->getType(); + return null !== $type && ! $type->isBuiltin() ? $type->getName() : null; + }, $constructorParameters); + } + + /** + * @param string $className + * @return string + */ + private function createArgumentString($className) + { + $arguments = array_map(fn(string $dependency): string + => sprintf('$container->get(\\%s::class)', $dependency), $this->getConstructorParameters($className)); + + switch (count($arguments)) { + case 0: + return ''; + case 1: + return array_shift($arguments); + default: + $argumentPad = str_repeat(' ', 12); + $closePad = str_repeat(' ', 8); + return sprintf( + "\n%s%s\n%s", + $argumentPad, + implode(",\n" . $argumentPad, $arguments), + $closePad + ); + } + } + + private function createImportStatements(string $className): string + { + $imports = array_merge(self::IMPORT_ALWAYS, [$className]); + sort($imports); + return implode("\n", array_map(static fn(string $import): string => sprintf('use %s;', $import), $imports)); + } +} diff --git a/lib/laminas/laminas-servicemanager/src/Tool/FactoryCreatorCommand.php b/lib/laminas/laminas-servicemanager/src/Tool/FactoryCreatorCommand.php new file mode 100644 index 0000000000..ec6c1fc692 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/Tool/FactoryCreatorCommand.php @@ -0,0 +1,154 @@ +Usage: + + %s [-h|--help|help] + +Arguments: + + -h|--help|help This usage message + Name of the class to reflect and for which to generate + a factory. + +Generates to STDOUT a factory for creating the specified class; this may then +be added to your application, and configured as a factory for the class. +EOH; + + private ConsoleHelper $helper; + + private string $scriptName; + + /** + * @param string $scriptName + */ + public function __construct($scriptName = self::DEFAULT_SCRIPT_NAME, ?ConsoleHelper $helper = null) + { + $this->scriptName = $scriptName; + $this->helper = $helper ?: new ConsoleHelper(); + } + + /** + * @param array $args Argument list, minus script name + * @return int Exit status + */ + public function __invoke(array $args) + { + $arguments = $this->parseArgs($args); + + switch ($arguments->command) { + case self::COMMAND_HELP: + $this->help(); + return 0; + case self::COMMAND_ERROR: + assert(is_string($arguments->message)); + $this->helper->writeErrorMessage($arguments->message); + $this->help(STDERR); + return 1; + case self::COMMAND_DUMP: + // fall-through + default: + break; + } + + $generator = new FactoryCreator(); + assert(is_string($arguments->class)); + try { + $factory = $generator->createFactory($arguments->class); + } catch (Exception\InvalidArgumentException $e) { + $this->helper->writeErrorMessage(sprintf( + 'Unable to create factory for "%s": %s', + $arguments->class, + $e->getMessage() + )); + $this->help(STDERR); + return 1; + } + + $this->helper->write($factory, false); + return 0; + } + + /** + * @param array $args + * @return ArgumentObject + */ + private function parseArgs(array $args) + { + if (! $args) { + return $this->createArguments(self::COMMAND_HELP); + } + + $arg1 = array_shift($args); + + if (in_array($arg1, ['-h', '--help', 'help'], true)) { + return $this->createArguments(self::COMMAND_HELP); + } + + $class = $arg1; + + if (! class_exists($class)) { + return $this->createArguments(self::COMMAND_ERROR, null, sprintf( + 'Class "%s" does not exist or could not be autoloaded.', + $class + )); + } + + return $this->createArguments(self::COMMAND_DUMP, $class); + } + + /** + * @param resource $resource Defaults to STDOUT + * @return void + */ + private function help($resource = STDOUT) + { + $this->helper->writeLine(sprintf( + self::HELP_TEMPLATE, + $this->scriptName + ), true, $resource); + } + + /** + * @param string $command + * @param string|null $class Name of class to reflect. + * @param string|null $error Error message, if any. + * @return ArgumentObject + */ + private function createArguments($command, $class = null, $error = null) + { + return (object) [ + 'command' => $command, + 'class' => $class, + 'message' => $error, + ]; + } +} diff --git a/lib/laminas/laminas-servicemanager/src/autoload.php b/lib/laminas/laminas-servicemanager/src/autoload.php new file mode 100644 index 0000000000..76bd64e079 --- /dev/null +++ b/lib/laminas/laminas-servicemanager/src/autoload.php @@ -0,0 +1,21 @@ + ## 🇷🇺 Русским гражданам +> +> Мы, участники Laminas, родились и живем в разных странах. У многих из нас есть друзья, родственники и коллеги как в России, так и в Украине. Некоторые из нас родились в России. Некоторые из нас живут в России. У некоторых бабушки и дедушки сражались с фашистами во Второй мировой войне. Здесь никто не поддерживает фашизм. +> +> У одного из нас есть украинская родственница, которая спаслась из дома вместе с сыном. Поезд задержался из-за бомбежки на дороге впереди. У нас есть друзья, которые прячутся в бомбоубежищах. Мы с тревогой ждем весточки от них после воздушных налетов, которые беспорядочно наносят удары и попадают по больницам, школам, детским садам и домам. Мы не берем это из каких-либо СМИ. Мы наблюдаем это напрямую. +> +> Вы доверяете нам достаточно, чтоб использовать наши программы, и мы просим вас довериться нам вновь. Мы нуждаемся в помощи. Выходите и протестуйте против этой бесполезной войны. Остановите кровопролитие. Скажите "Нет войне!" +> +> ## 🇺🇸 To Citizens of Russia +> +> We at Laminas come from all over the world. Many of us have friends, family and colleagues in both Russia and Ukraine. Some of us were born in Russia. Some of us currently live in Russia. Some have grandparents who fought Nazis in World War II. Nobody here supports fascism. +> +> One team member has a Ukrainian relative who fled her home with her son. The train was delayed due to bombing on the road ahead. We have friends who are hiding in bomb shelters. We anxiously follow up on them after the air raids, which indiscriminately fire at hospitals, schools, kindergartens and houses. We're not taking this from any media. These are our actual experiences. +> +> You trust us enough to use our software. We ask that you trust us to say the truth on this. We need your help. Go out and protest this unnecessary war. Stop the bloodshed. Say "stop the war!" + +`Laminas\Stdlib` is a set of components that implements general purpose utility +class for different scopes like: + +- array utilities functions; +- general messaging systems; +- string wrappers; +- etc. + +--- + +- File issues at https://github.com/laminas/laminas-stdlib/issues +- Documentation is at https://docs.laminas.dev/laminas-stdlib/ + +## Benchmarks + +We provide scripts for benchmarking laminas-stdlib using the +[PHPBench](https://github.com/phpbench/phpbench) framework; these can be +found in the `benchmark/` directory. + +To execute the benchmarks you can run the following command: + +```bash +$ vendor/bin/phpbench run --report=aggregate +``` diff --git a/lib/laminas/laminas-stdlib/composer.json b/lib/laminas/laminas-stdlib/composer.json new file mode 100644 index 0000000000..711b3fc954 --- /dev/null +++ b/lib/laminas/laminas-stdlib/composer.json @@ -0,0 +1,65 @@ +{ + "name": "laminas/laminas-stdlib", + "description": "SPL extensions, array utilities, error handlers, and more", + "license": "BSD-3-Clause", + "keywords": [ + "laminas", + "stdlib" + ], + "homepage": "https://laminas.dev", + "support": { + "docs": "https://docs.laminas.dev/laminas-stdlib/", + "issues": "https://github.com/laminas/laminas-stdlib/issues", + "source": "https://github.com/laminas/laminas-stdlib", + "rss": "https://github.com/laminas/laminas-stdlib/releases.atom", + "chat": "https://laminas.dev/chat", + "forum": "https://discourse.laminas.dev" + }, + "config": { + "sort-packages": true, + "platform": { + "php": "7.4.99" + }, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "extra": { + }, + "require": { + "php": "^7.4 || ~8.0.0 || ~8.1.0" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.3.0", + "phpbench/phpbench": "^1.2.6", + "phpunit/phpunit": "^9.5.23", + "psalm/plugin-phpunit": "^0.17.0", + "vimeo/psalm": "^4.26", + "phpstan/phpdoc-parser": "^0.5.4" + }, + "autoload": { + "psr-4": { + "Laminas\\Stdlib\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "LaminasTest\\Stdlib\\": "test/", + "LaminasBench\\Stdlib\\": "benchmark/" + } + }, + "scripts": { + "check": [ + "@cs-check", + "@test" + ], + "cs-check": "phpcs", + "cs-fix": "phpcbf", + "static-analysis": "psalm --shepherd --stats", + "test": "phpunit --colors=always", + "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" + }, + "conflict": { + "zendframework/zend-stdlib": "*" + } +} diff --git a/lib/laminas/laminas-stdlib/composer.lock b/lib/laminas/laminas-stdlib/composer.lock new file mode 100644 index 0000000000..c2f7beca36 --- /dev/null +++ b/lib/laminas/laminas-stdlib/composer.lock @@ -0,0 +1,4852 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "6797353dc8a3d832ed036772471afd74", + "packages": [], + "packages-dev": [ + { + "name": "amphp/amp", + "version": "v2.6.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/amp.git", + "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/amp/zipball/9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^7 | ^8 | ^9", + "psalm/phar": "^3.11@dev", + "react/promise": "^2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "files": [ + "lib/functions.php", + "lib/Internal/functions.php" + ], + "psr-4": { + "Amp\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "https://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v2.6.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2022-02-20T17:52:18+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v1.8.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", + "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", + "shasum": "" + }, + "require": { + "amphp/amp": "^2", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.4", + "friendsofphp/php-cs-fixer": "^2.3", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6 || ^7 || ^8", + "psalm/phar": "^3.11.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "lib/functions.php" + ], + "psr-4": { + "Amp\\ByteStream\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "http://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2021-03-30T17:13:30+00:00" + }, + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99.5", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-01-17T14:14:24+00:00" + }, + { + "name": "composer/pcre", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/e300eb6c535192decd27a85bc72a9290f0d6b3bd", + "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.0.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-02-25T20:21:48+00:00" + }, + { + "name": "composer/semver", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-04-01T19:23:25+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "ced299686f41dce890debac69273b47ffe98a40c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", + "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-02-25T21:32:43+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "doctrine/annotations", + "version": "1.13.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "648b0343343565c4a056bfc8392201385e8d89f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/648b0343343565c4a056bfc8392201385e8d89f0", + "reference": "648b0343343565c4a056bfc8392201385e8d89f0", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^6.0 || ^8.1", + "phpstan/phpstan": "^1.4.10 || ^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", + "symfony/cache": "^4.4 || ^5.2", + "vimeo/psalm": "^4.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.13.3" + }, + "time": "2022-07-02T10:48:51+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-03-03T08:28:38+00:00" + }, + { + "name": "doctrine/lexer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2022-02-28T11:07:21+00:00" + }, + { + "name": "felixfbecker/advanced-json-rpc", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "php": "^7.1 || ^8.0", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "A more advanced JSONRPC implementation", + "support": { + "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", + "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" + }, + "time": "2021-06-11T22:34:44+00:00" + }, + { + "name": "felixfbecker/language-server-protocol", + "version": "v1.5.2", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", + "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1", + "vimeo/psalm": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "PHP classes for the Language Server Protocol", + "keywords": [ + "language", + "microsoft", + "php", + "server" + ], + "support": { + "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" + }, + "time": "2022-03-02T22:36:06+00:00" + }, + { + "name": "laminas/laminas-coding-standard", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-coding-standard.git", + "reference": "bcf6e07fe4690240be7beb6d884d0b0fafa6a251" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-coding-standard/zipball/bcf6e07fe4690240be7beb6d884d0b0fafa6a251", + "reference": "bcf6e07fe4690240be7beb6d884d0b0fafa6a251", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "php": "^7.3 || ^8.0", + "slevomat/coding-standard": "^7.0", + "squizlabs/php_codesniffer": "^3.6", + "webimpress/coding-standard": "^1.2" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "LaminasCodingStandard\\": "src/LaminasCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Laminas Coding Standard", + "homepage": "https://laminas.dev", + "keywords": [ + "Coding Standard", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-coding-standard/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-coding-standard/issues", + "rss": "https://github.com/laminas/laminas-coding-standard/releases.atom", + "source": "https://github.com/laminas/laminas-coding-standard" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-05-29T15:53:59+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2022-03-03T13:19:32+00:00" + }, + { + "name": "netresearch/jsonmapper", + "version": "v4.0.0", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", + "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v4.0.0" + }, + "time": "2020-12-01T19:48:11+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.14.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0" + }, + "time": "2022-05-31T20:59:12+00:00" + }, + { + "name": "openlss/lib-array2xml", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/nullivex/lib-array2xml.git", + "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nullivex/lib-array2xml/zipball/a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", + "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "autoload": { + "psr-0": { + "LSS": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Bryan Tong", + "email": "bryan@nullivex.com", + "homepage": "https://www.nullivex.com" + }, + { + "name": "Tony Butler", + "email": "spudz76@gmail.com", + "homepage": "https://www.nullivex.com" + } + ], + "description": "Array2XML conversion library credit to lalit.org", + "homepage": "https://www.nullivex.com", + "keywords": [ + "array", + "array conversion", + "xml", + "xml conversion" + ], + "support": { + "issues": "https://github.com/nullivex/lib-array2xml/issues", + "source": "https://github.com/nullivex/lib-array2xml/tree/master" + }, + "time": "2019-03-29T20:06:56+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpbench/container", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/phpbench/container.git", + "reference": "6d555ff7174fca13f9b1ec0b4a089ed41d0ab392" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/container/zipball/6d555ff7174fca13f9b1ec0b4a089ed41d0ab392", + "reference": "6d555ff7174fca13f9b1ec0b4a089ed41d0ab392", + "shasum": "" + }, + "require": { + "psr/container": "^1.0|^2.0", + "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "phpstan/phpstan": "^0.12.52", + "phpunit/phpunit": "^8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\DependencyInjection\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "Simple, configurable, service container.", + "support": { + "issues": "https://github.com/phpbench/container/issues", + "source": "https://github.com/phpbench/container/tree/2.2.1" + }, + "time": "2022-01-25T10:17:35+00:00" + }, + { + "name": "phpbench/dom", + "version": "0.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpbench/dom.git", + "reference": "b013b717832ddbaadf2a40984b04bc66af9a7110" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/dom/zipball/b013b717832ddbaadf2a40984b04bc66af9a7110", + "reference": "b013b717832ddbaadf2a40984b04bc66af9a7110", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": "^7.2||^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.18", + "phpstan/phpstan": "^0.12.83", + "phpunit/phpunit": "^8.0||^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpBench\\Dom\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "DOM wrapper to simplify working with the PHP DOM implementation", + "support": { + "issues": "https://github.com/phpbench/dom/issues", + "source": "https://github.com/phpbench/dom/tree/0.3.2" + }, + "time": "2021-09-24T15:26:07+00:00" + }, + { + "name": "phpbench/phpbench", + "version": "1.2.6", + "source": { + "type": "git", + "url": "https://github.com/phpbench/phpbench.git", + "reference": "c30fac992e72b505a1f131790583647f4d3255c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpbench/phpbench/zipball/c30fac992e72b505a1f131790583647f4d3255c3", + "reference": "c30fac992e72b505a1f131790583647f4d3255c3", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.13", + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "ext-tokenizer": "*", + "php": "^7.3 || ^8.0", + "phpbench/container": "^2.1", + "phpbench/dom": "~0.3.1", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "seld/jsonlint": "^1.1", + "symfony/console": "^4.2 || ^5.0 || ^6.0", + "symfony/filesystem": "^4.2 || ^5.0 || ^6.0", + "symfony/finder": "^4.2 || ^5.0 || ^6.0", + "symfony/options-resolver": "^4.2 || ^5.0 || ^6.0", + "symfony/process": "^4.2 || ^5.0 || ^6.0", + "webmozart/path-util": "^2.3" + }, + "require-dev": { + "dantleech/invoke": "^2.0", + "friendsofphp/php-cs-fixer": "^3.0", + "jangregor/phpstan-prophecy": "^0.8.1", + "phpspec/prophecy": "^1.12", + "phpstan/phpstan": "^0.12.7", + "phpunit/phpunit": "^8.5.8 || ^9.0", + "symfony/error-handler": "^5.2 || ^6.0", + "symfony/var-dumper": "^4.0 || ^5.0 || ^6.0" + }, + "suggest": { + "ext-xdebug": "For Xdebug profiling extension." + }, + "bin": [ + "bin/phpbench" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "files": [ + "lib/Report/Func/functions.php" + ], + "psr-4": { + "PhpBench\\": "lib/", + "PhpBench\\Extensions\\XDebug\\": "extensions/xdebug/lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Leech", + "email": "daniel@dantleech.com" + } + ], + "description": "PHP Benchmarking Framework", + "support": { + "issues": "https://github.com/phpbench/phpbench/issues", + "source": "https://github.com/phpbench/phpbench/tree/1.2.6" + }, + "funding": [ + { + "url": "https://github.com/dantleech", + "type": "github" + } + ], + "time": "2022-07-19T19:52:39+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + }, + "time": "2021-10-19T17:43:47+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "77a32518733312af16a44300404e945338981de3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", + "reference": "77a32518733312af16a44300404e945338981de3", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" + }, + "time": "2022-03-15T21:29:03+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.5.6", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "fac86158ffc7392e49636f77e63684c026df43b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fac86158ffc7392e49636f77e63684c026df43b8", + "reference": "fac86158ffc7392e49636f77e63684c026df43b8", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.87", + "phpstan/phpstan-strict-rules": "^0.12.5", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/0.5.6" + }, + "time": "2021-08-31T08:08:22+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.16", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "2593003befdcc10db5e213f9f28814f5aa8ac073" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2593003befdcc10db5e213f9f28814f5aa8ac073", + "reference": "2593003befdcc10db5e213f9f28814f5aa8ac073", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.14", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.16" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-08-20T05:26:47+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.5.23", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "888556852e7e9bbeeedb9656afe46118765ade34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/888556852e7e9bbeeedb9656afe46118765ade34", + "reference": "888556852e7e9bbeeedb9656afe46118765ade34", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.5", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.3", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.0", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.23" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-08-22T14:01:36+00:00" + }, + { + "name": "psalm/plugin-phpunit", + "version": "0.17.0", + "source": { + "type": "git", + "url": "https://github.com/psalm/psalm-plugin-phpunit.git", + "reference": "45951541beef07e93e3ad197daf01da88e85c31d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/psalm/psalm-plugin-phpunit/zipball/45951541beef07e93e3ad197daf01da88e85c31d", + "reference": "45951541beef07e93e3ad197daf01da88e85c31d", + "shasum": "" + }, + "require": { + "composer/package-versions-deprecated": "^1.10", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "ext-simplexml": "*", + "php": "^7.1 || ^8.0", + "vimeo/psalm": "dev-master || dev-4.x || ^4.5" + }, + "conflict": { + "phpunit/phpunit": "<7.5" + }, + "require-dev": { + "codeception/codeception": "^4.0.3", + "php": "^7.3 || ^8.0", + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.3.1", + "weirdan/codeception-psalm-module": "^0.11.0", + "weirdan/prophecy-shim": "^1.0 || ^2.0" + }, + "type": "psalm-plugin", + "extra": { + "psalm": { + "pluginClass": "Psalm\\PhpUnitPlugin\\Plugin" + } + }, + "autoload": { + "psr-4": { + "Psalm\\PhpUnitPlugin\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Brown", + "email": "github@muglug.com" + } + ], + "description": "Psalm plugin for PHPUnit", + "support": { + "issues": "https://github.com/psalm/psalm-plugin-phpunit/issues", + "source": "https://github.com/psalm/psalm-plugin-phpunit/tree/0.17.0" + }, + "time": "2022-06-14T17:05:57+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:49:45+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:10:38+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-04-03T09:37:03+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-11-11T14:18:36+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-02-14T08:28:10+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:17:30+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-03-15T09:54:48+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "seld/jsonlint", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "4211420d25eba80712bff236a98960ef68b866b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/4211420d25eba80712bff236a98960ef68b866b7", + "reference": "4211420d25eba80712bff236a98960ef68b866b7", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.5", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.9.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2022-04-01T13:37:23+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.0.15", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "cc80e59f9b4ca642f02dc1b615c37a9afc2a0f80" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/cc80e59f9b4ca642f02dc1b615c37a9afc2a0f80", + "reference": "cc80e59f9b4ca642f02dc1b615c37a9afc2a0f80", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.5.1 - 0.5.6", + "squizlabs/php_codesniffer": "^3.6.0" + }, + "require-dev": { + "phing/phing": "2.17.0", + "php-parallel-lint/php-parallel-lint": "1.3.1", + "phpstan/phpstan": "0.12.98", + "phpstan/phpstan-deprecation-rules": "0.12.6", + "phpstan/phpstan-phpunit": "0.12.22", + "phpstan/phpstan-strict-rules": "0.12.11", + "phpunit/phpunit": "7.5.20|8.5.5|9.5.9" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.0.15" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2021-09-09T10:29:09+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.7.1", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619", + "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2022-06-18T07:21:10+00:00" + }, + { + "name": "symfony/console", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "535846c7ee6bc4dd027ca0d93220601456734b10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/535846c7ee6bc4dd027ca0d93220601456734b10", + "reference": "535846c7ee6bc4dd027ca0d93220601456734b10", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.4.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-22T10:42:43+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "6699fb0228d1bc35b12aed6dd5e7455457609ddd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/6699fb0228d1bc35b12aed6dd5e7455457609ddd", + "reference": "6699fb0228d1bc35b12aed6dd5e7455457609ddd", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.4.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-20T13:00:38+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/7872a66f57caffa2916a584db1aa7f12adc76f8c", + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.4.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-29T07:37:50+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "54f14e36aa73cb8f7261d7686691fd4d75ea2690" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/54f14e36aa73cb8f7261d7686691fd4d75ea2690", + "reference": "54f14e36aa73cb8f7261d7686691fd4d75ea2690", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php73": "~1.0", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v5.4.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-20T13:00:38+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "433d05519ce6990bf3530fba6957499d327395c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", + "reference": "433d05519ce6990bf3530fba6957499d327395c2", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-10T07:21:04+00:00" + }, + { + "name": "symfony/process", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/6e75fe6874cbc7e4773d049616ab450eff537bf1", + "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.4.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-27T16:58:25+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "191afdcb5804db960d26d8566b7e9a2843cab3a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/191afdcb5804db960d26d8566b7e9a2843cab3a0", + "reference": "191afdcb5804db960d26d8566b7e9a2843cab3a0", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "suggest": { + "psr/container": "", + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v1.1.2" + }, + "time": "2019-05-28T07:50:59+00:00" + }, + { + "name": "symfony/string", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/5eb661e49ad389e4ae2b6e4df8d783a8a6548322", + "reference": "5eb661e49ad389e4ae2b6e4df8d783a8a6548322", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-24T16:15:25+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + }, + { + "name": "vimeo/psalm", + "version": "4.26.0", + "source": { + "type": "git", + "url": "https://github.com/vimeo/psalm.git", + "reference": "6998fabb2bf528b65777bf9941920888d23c03ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/6998fabb2bf528b65777bf9941920888d23c03ac", + "reference": "6998fabb2bf528b65777bf9941920888d23c03ac", + "shasum": "" + }, + "require": { + "amphp/amp": "^2.4.2", + "amphp/byte-stream": "^1.5", + "composer/package-versions-deprecated": "^1.8.0", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^1.1 || ^2.0 || ^3.0", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "ext-ctype": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "felixfbecker/advanced-json-rpc": "^3.0.3", + "felixfbecker/language-server-protocol": "^1.5", + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "nikic/php-parser": "^4.13", + "openlss/lib-array2xml": "^1.0", + "php": "^7.1|^8", + "sebastian/diff": "^3.0 || ^4.0", + "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0 || ^6.0", + "symfony/polyfill-php80": "^1.25", + "webmozart/path-util": "^2.3" + }, + "provide": { + "psalm/psalm": "self.version" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2", + "brianium/paratest": "^4.0||^6.0", + "ext-curl": "*", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpdocumentor/reflection-docblock": "^5", + "phpmyadmin/sql-parser": "5.1.0||dev-master", + "phpspec/prophecy": ">=1.9.0", + "phpunit/phpunit": "^9.0", + "psalm/plugin-phpunit": "^0.16", + "slevomat/coding-standard": "^7.0", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.3 || ^5.0 || ^6.0", + "weirdan/prophecy-shim": "^1.0 || ^2.0" + }, + "suggest": { + "ext-curl": "In order to send data to shepherd", + "ext-igbinary": "^2.0.5 is required, used to serialize caching data" + }, + "bin": [ + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalter" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev", + "dev-3.x": "3.x-dev", + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php", + "src/spl_object_id.php" + ], + "psr-4": { + "Psalm\\": "src/Psalm/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Brown" + } + ], + "description": "A static analysis tool for finding errors in PHP applications", + "keywords": [ + "code", + "inspection", + "php" + ], + "support": { + "issues": "https://github.com/vimeo/psalm/issues", + "source": "https://github.com/vimeo/psalm/tree/4.26.0" + }, + "time": "2022-07-31T13:10:26+00:00" + }, + { + "name": "webimpress/coding-standard", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/webimpress/coding-standard.git", + "reference": "cd0c4b0b97440c337c1f7da17b524674ca2f9ca9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webimpress/coding-standard/zipball/cd0c4b0b97440c337c1f7da17b524674ca2f9ca9", + "reference": "cd0c4b0b97440c337c1f7da17b524674ca2f9ca9", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5.13" + }, + "type": "phpcodesniffer-standard", + "extra": { + "dev-master": "1.2.x-dev", + "dev-develop": "1.3.x-dev" + }, + "autoload": { + "psr-4": { + "WebimpressCodingStandard\\": "src/WebimpressCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "description": "Webimpress Coding Standard", + "keywords": [ + "Coding Standard", + "PSR-2", + "phpcs", + "psr-12", + "webimpress" + ], + "support": { + "issues": "https://github.com/webimpress/coding-standard/issues", + "source": "https://github.com/webimpress/coding-standard/tree/1.2.4" + }, + "funding": [ + { + "url": "https://github.com/michalbundyra", + "type": "github" + } + ], + "time": "2022-02-15T19:52:12+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + }, + { + "name": "webmozart/path-util", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/path-util.git", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "webmozart/assert": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\PathUtil\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", + "support": { + "issues": "https://github.com/webmozart/path-util/issues", + "source": "https://github.com/webmozart/path-util/tree/2.3.0" + }, + "abandoned": "symfony/filesystem", + "time": "2015-12-17T08:42:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^7.4 || ~8.0.0 || ~8.1.0" + }, + "platform-dev": [], + "platform-overrides": { + "php": "7.4.99" + }, + "plugin-api-version": "2.3.0" +} diff --git a/lib/laminas/laminas-stdlib/src/AbstractOptions.php b/lib/laminas/laminas-stdlib/src/AbstractOptions.php new file mode 100644 index 0000000000..d02221ab16 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/AbstractOptions.php @@ -0,0 +1,194 @@ +setFromArray($options); + } + } + + /** + * Set one or more configuration properties + * + * @param array|Traversable|AbstractOptions $options + * @throws Exception\InvalidArgumentException + * @return AbstractOptions Provides fluent interface + */ + public function setFromArray($options) + { + if ($options instanceof self) { + $options = $options->toArray(); + } + + if (! is_array($options) && ! $options instanceof Traversable) { + throw new Exception\InvalidArgumentException( + sprintf( + 'Parameter provided to %s must be an %s, %s or %s', + __METHOD__, + 'array', + 'Traversable', + self::class + ) + ); + } + + foreach ($options as $key => $value) { + $this->__set($key, $value); + } + + return $this; + } + + /** + * Cast to array + * + * @return array + */ + public function toArray() + { + $array = []; + + /** @param string[] $letters */ + $transform = static function (array $letters): string { + $letter = array_shift($letters); + return '_' . strtolower($letter); + }; + + foreach ($this as $key => $value) { + if ($key === '__strictMode__') { + continue; + } + $normalizedKey = preg_replace_callback('/([A-Z])/', $transform, $key); + $array[$normalizedKey] = $value; + } + + return $array; + } + + /** + * Set a configuration property + * + * @see ParameterObject::__set() + * + * @param string $key + * @param mixed $value + * @throws Exception\BadMethodCallException + * @return void + */ + public function __set($key, $value) + { + $setter = 'set' . str_replace('_', '', $key); + + if (is_callable([$this, $setter])) { + $this->{$setter}($value); + + return; + } + + if ($this->__strictMode__) { + throw new Exception\BadMethodCallException(sprintf( + 'The option "%s" does not have a callable "%s" ("%s") setter method which must be defined', + $key, + 'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key))), + $setter + )); + } + } + + /** + * Get a configuration property + * + * @see ParameterObject::__get() + * + * @param string $key + * @throws Exception\BadMethodCallException + * @return mixed + */ + public function __get($key) + { + $getter = 'get' . str_replace('_', '', $key); + + if (is_callable([$this, $getter])) { + return $this->{$getter}(); + } + + throw new Exception\BadMethodCallException(sprintf( + 'The option "%s" does not have a callable "%s" getter method which must be defined', + $key, + 'get' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key))) + )); + } + + /** + * Test if a configuration property is null + * + * @see ParameterObject::__isset() + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + $getter = 'get' . str_replace('_', '', $key); + + return method_exists($this, $getter) && null !== $this->__get($key); + } + + /** + * Set a configuration property to NULL + * + * @see ParameterObject::__unset() + * + * @param string $key + * @throws Exception\InvalidArgumentException + * @return void + */ + public function __unset($key) + { + try { + $this->__set($key, null); + } catch (Exception\BadMethodCallException $e) { + throw new Exception\InvalidArgumentException( + 'The class property $' . $key . ' cannot be unset as' + . ' NULL is an invalid value for it', + 0, + $e + ); + } + } +} diff --git a/lib/laminas/laminas-stdlib/src/ArrayObject.php b/lib/laminas/laminas-stdlib/src/ArrayObject.php new file mode 100644 index 0000000000..6cc195ddd9 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/ArrayObject.php @@ -0,0 +1,505 @@ +setFlags($flags); + $this->storage = $input; + $this->setIteratorClass($iteratorClass); + $this->protectedProperties = array_keys(get_object_vars($this)); + } + + /** + * Returns whether the requested key exists + * + * @param mixed $key + * @return bool + */ + public function __isset($key) + { + if ($this->flag === self::ARRAY_AS_PROPS) { + return $this->offsetExists($key); + } + + if (in_array($key, $this->protectedProperties)) { + throw new Exception\InvalidArgumentException("$key is a protected property, use a different key"); + } + + return isset($this->$key); + } + + /** + * Sets the value at the specified key to value + * + * @param mixed $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + if ($this->flag === self::ARRAY_AS_PROPS) { + $this->offsetSet($key, $value); + return; + } + + if (in_array($key, $this->protectedProperties)) { + throw new Exception\InvalidArgumentException("$key is a protected property, use a different key"); + } + + $this->$key = $value; + } + + /** + * Unsets the value at the specified key + * + * @param mixed $key + * @return void + */ + public function __unset($key) + { + if ($this->flag === self::ARRAY_AS_PROPS) { + $this->offsetUnset($key); + return; + } + + if (in_array($key, $this->protectedProperties)) { + throw new Exception\InvalidArgumentException("$key is a protected property, use a different key"); + } + + unset($this->$key); + } + + /** + * Returns the value at the specified key by reference + * + * @param mixed $key + * @return mixed + */ + public function &__get($key) + { + if ($this->flag === self::ARRAY_AS_PROPS) { + $ret = &$this->offsetGet($key); + + return $ret; + } + + if (in_array($key, $this->protectedProperties, true)) { + throw new Exception\InvalidArgumentException("$key is a protected property, use a different key"); + } + + return $this->$key; + } + + /** + * Appends the value + * + * @param mixed $value + * @return void + */ + public function append($value) + { + $this->storage[] = $value; + } + + /** + * Sort the entries by value + * + * @return void + */ + public function asort() + { + asort($this->storage); + } + + /** + * Get the number of public properties in the ArrayObject + * + * @return int + */ + #[ReturnTypeWillChange] + public function count() + { + return count($this->storage); + } + + /** + * Exchange the array for another one. + * + * @param array|ArrayObject|ArrayIterator|object $data + * @return array + */ + public function exchangeArray($data) + { + if (! is_array($data) && ! is_object($data)) { + throw new Exception\InvalidArgumentException( + 'Passed variable is not an array or object, using empty array instead' + ); + } + + if (is_object($data) && ($data instanceof self || $data instanceof \ArrayObject)) { + $data = $data->getArrayCopy(); + } + if (! is_array($data)) { + $data = (array) $data; + } + + $storage = $this->storage; + + $this->storage = $data; + + return $storage; + } + + /** + * Creates a copy of the ArrayObject. + * + * @return array + */ + public function getArrayCopy() + { + return $this->storage; + } + + /** + * Gets the behavior flags. + * + * @return int + */ + public function getFlags() + { + return $this->flag; + } + + /** + * Create a new iterator from an ArrayObject instance + * + * @return Iterator + */ + #[ReturnTypeWillChange] + public function getIterator() + { + $class = $this->iteratorClass; + + return new $class($this->storage); + } + + /** + * Gets the iterator classname for the ArrayObject. + * + * @return string + */ + public function getIteratorClass() + { + return $this->iteratorClass; + } + + /** + * Sort the entries by key + * + * @return void + */ + public function ksort() + { + ksort($this->storage); + } + + /** + * Sort an array using a case insensitive "natural order" algorithm + * + * @return void + */ + public function natcasesort() + { + natcasesort($this->storage); + } + + /** + * Sort entries using a "natural order" algorithm + * + * @return void + */ + public function natsort() + { + natsort($this->storage); + } + + /** + * Returns whether the requested key exists + * + * @param mixed $key + * @return bool + */ + #[ReturnTypeWillChange] + public function offsetExists($key) + { + return isset($this->storage[$key]); + } + + /** + * Returns the value at the specified key + * + * @param mixed $key + * @return mixed + */ + #[ReturnTypeWillChange] + public function &offsetGet($key) + { + $ret = null; + if (! $this->offsetExists($key)) { + return $ret; + } + $ret = &$this->storage[$key]; + + return $ret; + } + + /** + * Sets the value at the specified key to value + * + * @param mixed $key + * @param mixed $value + * @return void + */ + #[ReturnTypeWillChange] + public function offsetSet($key, $value) + { + $this->storage[$key] = $value; + } + + /** + * Unsets the value at the specified key + * + * @param mixed $key + * @return void + */ + #[ReturnTypeWillChange] + public function offsetUnset($key) + { + if ($this->offsetExists($key)) { + unset($this->storage[$key]); + } + } + + /** + * Serialize an ArrayObject + * + * @return string + */ + public function serialize() + { + return serialize($this->__serialize()); + } + + /** + * Magic method used for serializing of an instance. + * + * @return array + */ + public function __serialize() + { + return get_object_vars($this); + } + + /** + * Sets the behavior flags + * + * @param int $flags + * @return void + */ + public function setFlags($flags) + { + $this->flag = $flags; + } + + /** + * Sets the iterator classname for the ArrayObject + * + * @param string $class + * @return void + */ + public function setIteratorClass($class) + { + if (class_exists($class)) { + $this->iteratorClass = $class; + + return; + } + + if (strpos($class, '\\') === 0) { + $class = '\\' . $class; + if (class_exists($class)) { + $this->iteratorClass = $class; + + return; + } + } + + throw new Exception\InvalidArgumentException('The iterator class does not exist'); + } + + /** + * Sort the entries with a user-defined comparison function and maintain key association + * + * @param callable $function + * @return void + */ + public function uasort($function) + { + if (is_callable($function)) { + uasort($this->storage, $function); + } + } + + /** + * Sort the entries by keys using a user-defined comparison function + * + * @param callable $function + * @return void + */ + public function uksort($function) + { + if (is_callable($function)) { + uksort($this->storage, $function); + } + } + + /** + * Unserialize an ArrayObject + * + * @param string $data + * @return void + */ + public function unserialize($data) + { + $toUnserialize = unserialize($data); + if (! is_array($toUnserialize)) { + throw new UnexpectedValueException(sprintf( + 'Cannot deserialize %s instance; corrupt serialization data', + self::class + )); + } + + $this->__unserialize($toUnserialize); + } + + /** + * Magic method used to rebuild an instance. + * + * @param array $data Data array. + * @return void + */ + public function __unserialize($data) + { + $this->protectedProperties = array_keys(get_object_vars($this)); + + // Unserialize protected internal properties first + if (array_key_exists('flag', $data)) { + $this->setFlags((int) $data['flag']); + unset($data['flag']); + } + + if (array_key_exists('storage', $data)) { + if (! is_array($data['storage']) && ! is_object($data['storage'])) { + throw new UnexpectedValueException(sprintf( + 'Cannot deserialize %s instance: corrupt storage data; expected array or object, received %s', + self::class, + gettype($data['storage']) + )); + } + $this->exchangeArray($data['storage']); + unset($data['storage']); + } + + if (array_key_exists('iteratorClass', $data)) { + if (! is_string($data['iteratorClass'])) { + throw new UnexpectedValueException(sprintf( + 'Cannot deserialize %s instance: invalid iteratorClass; expected string, received %s', + self::class, + is_object($data['iteratorClass']) + ? get_class($data['iteratorClass']) + : gettype($data['iteratorClass']) + )); + } + $this->setIteratorClass($data['iteratorClass']); + unset($data['iteratorClass']); + } + + unset($data['protectedProperties']); + + // Unserialize array keys after resolving protected properties to ensure configuration is used. + foreach ($data as $k => $v) { + $this->__set($k, $v); + } + } +} diff --git a/lib/laminas/laminas-stdlib/src/ArraySerializableInterface.php b/lib/laminas/laminas-stdlib/src/ArraySerializableInterface.php new file mode 100644 index 0000000000..f2544535ba --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/ArraySerializableInterface.php @@ -0,0 +1,23 @@ +getArrayCopy(); + return new ArrayIterator(array_reverse($array)); + } +} diff --git a/lib/laminas/laminas-stdlib/src/ArrayUtils.php b/lib/laminas/laminas-stdlib/src/ArrayUtils.php new file mode 100644 index 0000000000..285e644dd2 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/ArrayUtils.php @@ -0,0 +1,338 @@ + + * $list = array('a', 'b', 'c', 'd'); + * $list = array( + * 0 => 'foo', + * 1 => 'bar', + * 2 => array('foo' => 'baz'), + * ); + * + * + * @param mixed $value + * @param bool $allowEmpty Is an empty list a valid list? + * @return bool + */ + public static function isList($value, $allowEmpty = false) + { + if (! is_array($value)) { + return false; + } + + if (! $value) { + return $allowEmpty; + } + + return array_values($value) === $value; + } + + /** + * Test whether an array is a hash table. + * + * An array is a hash table if: + * + * 1. Contains one or more non-integer keys, or + * 2. Integer keys are non-continuous or misaligned (not starting with 0) + * + * For example: + * + * $hash = array( + * 'foo' => 15, + * 'bar' => false, + * ); + * $hash = array( + * 1995 => 'Birth of PHP', + * 2009 => 'PHP 5.3.0', + * 2012 => 'PHP 5.4.0', + * ); + * $hash = array( + * 'formElement, + * 'options' => array( 'debug' => true ), + * ); + * + * + * @param mixed $value + * @param bool $allowEmpty Is an empty array() a valid hash table? + * @return bool + */ + public static function isHashTable($value, $allowEmpty = false) + { + if (! is_array($value)) { + return false; + } + + if (! $value) { + return $allowEmpty; + } + + return array_values($value) !== $value; + } + + /** + * Checks if a value exists in an array. + * + * Due to "foo" == 0 === TRUE with in_array when strict = false, an option + * has been added to prevent this. When $strict = 0/false, the most secure + * non-strict check is implemented. if $strict = -1, the default in_array + * non-strict behaviour is used. + * + * @param mixed $needle + * @param array $haystack + * @param int|bool $strict + * @return bool + */ + public static function inArray($needle, array $haystack, $strict = false) + { + if (! $strict) { + if (is_int($needle) || is_float($needle)) { + $needle = (string) $needle; + } + if (is_string($needle)) { + foreach ($haystack as &$h) { + if (is_int($h) || is_float($h)) { + $h = (string) $h; + } + } + } + } + + return in_array($needle, $haystack, (bool) $strict); + } + + /** + * Converts an iterator to an array. The $recursive flag, on by default, + * hints whether or not you want to do so recursively. + * + * @template TKey + * @template TValue + * @param iterable $iterator The array or Traversable object to convert + * @param bool $recursive Recursively check all nested structures + * @throws Exception\InvalidArgumentException If $iterator is not an array or a Traversable object. + * @return array + */ + public static function iteratorToArray($iterator, $recursive = true) + { + /** @psalm-suppress DocblockTypeContradiction */ + if (! is_array($iterator) && ! $iterator instanceof Traversable) { + throw new Exception\InvalidArgumentException(__METHOD__ . ' expects an array or Traversable object'); + } + + if (! $recursive) { + if (is_array($iterator)) { + return $iterator; + } + + return iterator_to_array($iterator); + } + + if ( + is_object($iterator) + && ! $iterator instanceof Iterator + && method_exists($iterator, 'toArray') + ) { + /** @psalm-var array $array */ + $array = $iterator->toArray(); + + return $array; + } + + $array = []; + foreach ($iterator as $key => $value) { + if (is_scalar($value)) { + $array[$key] = $value; + continue; + } + + if ($value instanceof Traversable) { + $array[$key] = static::iteratorToArray($value, $recursive); + continue; + } + + if (is_array($value)) { + $array[$key] = static::iteratorToArray($value, $recursive); + continue; + } + + $array[$key] = $value; + } + + /** @psalm-var array $array */ + + return $array; + } + + /** + * Merge two arrays together. + * + * If an integer key exists in both arrays and preserveNumericKeys is false, the value + * from the second array will be appended to the first array. If both values are arrays, they + * are merged together, else the value of the second array overwrites the one of the first array. + * + * @param array $a + * @param array $b + * @param bool $preserveNumericKeys + * @return array + */ + public static function merge(array $a, array $b, $preserveNumericKeys = false) + { + foreach ($b as $key => $value) { + if ($value instanceof MergeReplaceKeyInterface) { + $a[$key] = $value->getData(); + } elseif (isset($a[$key]) || array_key_exists($key, $a)) { + if ($value instanceof MergeRemoveKey) { + unset($a[$key]); + } elseif (! $preserveNumericKeys && is_int($key)) { + $a[] = $value; + } elseif (is_array($value) && is_array($a[$key])) { + $a[$key] = static::merge($a[$key], $value, $preserveNumericKeys); + } else { + $a[$key] = $value; + } + } else { + if (! $value instanceof MergeRemoveKey) { + $a[$key] = $value; + } + } + } + + return $a; + } + + /** + * @deprecated Since 3.2.0; use the native array_filter methods + * + * @param array $data + * @param callable $callback + * @param null|int $flag + * @return array + * @throws Exception\InvalidArgumentException + */ + public static function filter(array $data, $callback, $flag = null) + { + if (! is_callable($callback)) { + throw new Exception\InvalidArgumentException(sprintf( + 'Second parameter of %s must be callable', + __METHOD__ + )); + } + + return array_filter($data, $callback, $flag ?? 0); + } +} diff --git a/lib/laminas/laminas-stdlib/src/ArrayUtils/MergeRemoveKey.php b/lib/laminas/laminas-stdlib/src/ArrayUtils/MergeRemoveKey.php new file mode 100644 index 0000000000..bde0b25ae9 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/ArrayUtils/MergeRemoveKey.php @@ -0,0 +1,9 @@ +data = $data; + } + + /** + * {@inheritDoc} + */ + public function getData() + { + return $this->data; + } +} diff --git a/lib/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php b/lib/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php new file mode 100644 index 0000000000..47243fc24f --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php @@ -0,0 +1,16 @@ +message`, + * `message`) + * - Write output to a specified stream, optionally with colorization. + * - Write a line of output to a specified stream, optionally with + * colorization, using the system EOL sequence.. + * - Write an error message to STDERR. + * + * Colorization will only occur when expected sequences are discovered, and + * then, only if the console terminal allows it. + * + * Essentially, provides the bare minimum to allow you to provide messages to + * the current console. + */ +class ConsoleHelper +{ + public const COLOR_GREEN = "\033[32m"; + public const COLOR_RED = "\033[31m"; + public const COLOR_RESET = "\033[0m"; + + public const HIGHLIGHT_INFO = 'info'; + public const HIGHLIGHT_ERROR = 'error'; + + /** @psalm-var array */ + private array $highlightMap = [ + self::HIGHLIGHT_INFO => self::COLOR_GREEN, + self::HIGHLIGHT_ERROR => self::COLOR_RED, + ]; + + /** @var string Exists only for testing. */ + private string $eol = PHP_EOL; + + /** @var resource Exists only for testing. */ + private $stderr = STDERR; + + private bool $supportsColor; + + /** + * @param resource $resource + */ + public function __construct($resource = STDOUT) + { + $this->supportsColor = $this->detectColorCapabilities($resource); + } + + /** + * Colorize a string for use with the terminal. + * + * Takes strings formatted as `string` and formats them per the + * $highlightMap; if color support is disabled, simply removes the formatting + * tags. + * + * @param string $string + * @return string + */ + public function colorize($string) + { + $reset = $this->supportsColor ? self::COLOR_RESET : ''; + foreach ($this->highlightMap as $key => $color) { + $pattern = sprintf('#<%s>(.*?)#s', $key, $key); + $color = $this->supportsColor ? $color : ''; + $string = preg_replace($pattern, $color . '$1' . $reset, $string); + } + return $string; + } + + /** + * @param string $string + * @param bool $colorize Whether or not to colorize the string + * @param resource $resource Defaults to STDOUT + * @return void + */ + public function write($string, $colorize = true, $resource = STDOUT) + { + if ($colorize) { + $string = $this->colorize($string); + } + + $string = $this->formatNewlines($string); + + fwrite($resource, $string); + } + + /** + * @param string $string + * @param bool $colorize Whether or not to colorize the line + * @param resource $resource Defaults to STDOUT + * @return void + */ + public function writeLine($string, $colorize = true, $resource = STDOUT) + { + $this->write($string . $this->eol, $colorize, $resource); + } + + /** + * Emit an error message. + * + * Wraps the message in ``, and passes it to `writeLine()`, + * using STDERR as the resource; emits an additional empty line when done, + * also to STDERR. + * + * @param string $message + * @return void + */ + public function writeErrorMessage($message) + { + $this->writeLine(sprintf('%s', $message), true, $this->stderr); + $this->writeLine('', false, $this->stderr); + } + + /** + * @param resource $resource + * @return bool + */ + private function detectColorCapabilities($resource = STDOUT) + { + if ('\\' === DIRECTORY_SEPARATOR) { + // Windows + return false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + return function_exists('posix_isatty') && posix_isatty($resource); + } + + /** + * Ensure newlines are appropriate for the current terminal. + * + * @param string $string + * @return string + */ + private function formatNewlines($string) + { + $string = str_replace($this->eol, "\0PHP_EOL\0", $string); + $string = preg_replace("/(\r\n|\n|\r)/", $this->eol, $string); + return str_replace("\0PHP_EOL\0", $this->eol, $string); + } +} diff --git a/lib/laminas/laminas-stdlib/src/DispatchableInterface.php b/lib/laminas/laminas-stdlib/src/DispatchableInterface.php new file mode 100644 index 0000000000..a9b325a672 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/DispatchableInterface.php @@ -0,0 +1,15 @@ +setExtractFlags(self::EXTR_BOTH); + + $data = []; + foreach ($clone as $item) { + $data[] = $item; + } + + return $data; + } + + public function __unserialize(array $data): void + { + foreach ($data as $item) { + $this->insert($item['data'], $item['priority']); + } + } + + /** + * Insert an element in the queue with a specified priority + * + * @param mixed $value + * @param integer $priority + * @return void + */ + public function insert($value, $priority) + { + if (! is_int($priority)) { + throw new Exception\InvalidArgumentException('The priority must be an integer'); + } + $this->values[$priority][] = $value; + if (! isset($this->priorities[$priority])) { + $this->priorities[$priority] = $priority; + $this->maxPriority = $this->maxPriority === null ? $priority : max($priority, $this->maxPriority); + } + ++$this->count; + } + + /** + * Extract an element in the queue according to the priority and the + * order of insertion + * + * @return mixed + */ + public function extract() + { + if (! $this->valid()) { + return false; + } + $value = $this->current(); + $this->nextAndRemove(); + return $value; + } + + /** + * Remove an item from the queue + * + * This is different than {@link extract()}; its purpose is to dequeue an + * item. + * + * Note: this removes the first item matching the provided item found. If + * the same item has been added multiple times, it will not remove other + * instances. + * + * @param mixed $datum + * @return bool False if the item was not found, true otherwise. + */ + public function remove($datum) + { + $currentIndex = $this->index; + $currentSubIndex = $this->subIndex; + $currentPriority = $this->maxPriority; + + $this->rewind(); + while ($this->valid()) { + if (current($this->values[$this->maxPriority]) === $datum) { + $index = key($this->values[$this->maxPriority]); + unset($this->values[$this->maxPriority][$index]); + + // The `next()` method advances the internal array pointer, so we need to use the `reset()` function, + // otherwise we would lose all elements before the place the pointer points. + reset($this->values[$this->maxPriority]); + + $this->index = $currentIndex; + $this->subIndex = $currentSubIndex; + + // If the array is empty we need to destroy the unnecessary priority, + // otherwise we would end up with an incorrect value of `$this->count` + // {@see \Laminas\Stdlib\FastPriorityQueue::nextAndRemove()}. + if (empty($this->values[$this->maxPriority])) { + unset($this->values[$this->maxPriority]); + unset($this->priorities[$this->maxPriority]); + if ($this->maxPriority === $currentPriority) { + $this->subIndex = 0; + } + } + + $this->maxPriority = empty($this->priorities) ? null : max($this->priorities); + --$this->count; + return true; + } + $this->next(); + } + return false; + } + + /** + * Get the total number of elements in the queue + * + * @return int + */ + #[ReturnTypeWillChange] + public function count() + { + return $this->count; + } + + /** + * Get the current element in the queue + * + * @return mixed + */ + #[ReturnTypeWillChange] + public function current() + { + switch ($this->extractFlag) { + case self::EXTR_DATA: + return current($this->values[$this->maxPriority]); + case self::EXTR_PRIORITY: + return $this->maxPriority; + case self::EXTR_BOTH: + return [ + 'data' => current($this->values[$this->maxPriority]), + 'priority' => $this->maxPriority, + ]; + } + } + + /** + * Get the index of the current element in the queue + * + * @return int + */ + #[ReturnTypeWillChange] + public function key() + { + return $this->index; + } + + /** + * Set the iterator pointer to the next element in the queue + * removing the previous element + * + * @return void + */ + protected function nextAndRemove() + { + $key = key($this->values[$this->maxPriority]); + + if (false === next($this->values[$this->maxPriority])) { + unset($this->priorities[$this->maxPriority]); + unset($this->values[$this->maxPriority]); + $this->maxPriority = empty($this->priorities) ? null : max($this->priorities); + $this->subIndex = -1; + } else { + unset($this->values[$this->maxPriority][$key]); + } + ++$this->index; + ++$this->subIndex; + --$this->count; + } + + /** + * Set the iterator pointer to the next element in the queue + * without removing the previous element + */ + #[ReturnTypeWillChange] + public function next() + { + if (false === next($this->values[$this->maxPriority])) { + unset($this->subPriorities[$this->maxPriority]); + reset($this->values[$this->maxPriority]); + $this->maxPriority = empty($this->subPriorities) ? null : max($this->subPriorities); + $this->subIndex = -1; + } + ++$this->index; + ++$this->subIndex; + } + + /** + * Check if the current iterator is valid + * + * @return bool + */ + #[ReturnTypeWillChange] + public function valid() + { + return isset($this->values[$this->maxPriority]); + } + + /** + * Rewind the current iterator + */ + #[ReturnTypeWillChange] + public function rewind() + { + $this->subPriorities = $this->priorities; + $this->maxPriority = empty($this->priorities) ? 0 : max($this->priorities); + $this->index = 0; + $this->subIndex = 0; + } + + /** + * Serialize to an array + * + * Array will be priority => data pairs + * + * @return array + */ + public function toArray() + { + $array = []; + foreach (clone $this as $item) { + $array[] = $item; + } + return $array; + } + + /** + * Serialize + * + * @return string + */ + public function serialize() + { + return serialize($this->__serialize()); + } + + /** + * Deserialize + * + * @param string $data + * @return void + */ + public function unserialize($data) + { + $toUnserialize = unserialize($data); + if (! is_array($toUnserialize)) { + throw new UnexpectedValueException(sprintf( + 'Cannot deserialize %s instance; corrupt serialization data', + self::class + )); + } + + $this->__unserialize($toUnserialize); + } + + /** + * Set the extract flag + * + * @param integer $flag + * @return void + */ + public function setExtractFlags($flag) + { + switch ($flag) { + case self::EXTR_DATA: + case self::EXTR_PRIORITY: + case self::EXTR_BOTH: + $this->extractFlag = $flag; + break; + default: + throw new Exception\InvalidArgumentException("The extract flag specified is not valid"); + } + } + + /** + * Check if the queue is empty + * + * @return boolean + */ + public function isEmpty() + { + return empty($this->values); + } + + /** + * Does the queue contain the given datum? + * + * @param mixed $datum + * @return bool + */ + public function contains($datum) + { + foreach ($this->values as $values) { + if (in_array($datum, $values)) { + return true; + } + } + return false; + } + + /** + * Does the queue have an item with the given priority? + * + * @param int $priority + * @return bool + */ + public function hasPriority($priority) + { + return isset($this->values[$priority]); + } +} diff --git a/lib/laminas/laminas-stdlib/src/Glob.php b/lib/laminas/laminas-stdlib/src/Glob.php new file mode 100644 index 0000000000..5b5be710d4 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/Glob.php @@ -0,0 +1,226 @@ + GLOB_MARK, + self::GLOB_NOSORT => GLOB_NOSORT, + self::GLOB_NOCHECK => GLOB_NOCHECK, + self::GLOB_NOESCAPE => GLOB_NOESCAPE, + self::GLOB_BRACE => defined('GLOB_BRACE') ? GLOB_BRACE : 0, + self::GLOB_ONLYDIR => GLOB_ONLYDIR, + self::GLOB_ERR => GLOB_ERR, + ]; + + $globFlags = 0; + + foreach ($flagMap as $internalFlag => $globFlag) { + if ($flags & $internalFlag) { + $globFlags |= $globFlag; + } + } + } else { + $globFlags = 0; + } + + ErrorHandler::start(); + $res = glob($pattern, $globFlags); + $err = ErrorHandler::stop(); + if ($res === false) { + throw new Exception\RuntimeException("glob('{$pattern}', {$globFlags}) failed", 0, $err); + } + return $res; + } + + /** + * Expand braces manually, then use the system glob. + * + * @param string $pattern + * @param int $flags + * @return array + * @throws Exception\RuntimeException + */ + protected static function fallbackGlob($pattern, $flags) + { + if (! self::flagsIsEqualTo($flags, self::GLOB_BRACE)) { + return static::systemGlob($pattern, $flags); + } + + $flags &= ~self::GLOB_BRACE; + $length = strlen($pattern); + $paths = []; + + if ($flags & self::GLOB_NOESCAPE) { + $begin = strpos($pattern, '{'); + } else { + $begin = 0; + + while (true) { + if ($begin === $length) { + $begin = false; + break; + } elseif ($pattern[$begin] === '\\' && ($begin + 1) < $length) { + $begin++; + } elseif ($pattern[$begin] === '{') { + break; + } + + $begin++; + } + } + + if ($begin === false) { + return static::systemGlob($pattern, $flags); + } + + $next = static::nextBraceSub($pattern, $begin + 1, $flags); + + if ($next === null) { + return static::systemGlob($pattern, $flags); + } + + $rest = $next; + + while ($pattern[$rest] !== '}') { + $rest = static::nextBraceSub($pattern, $rest + 1, $flags); + + if ($rest === null) { + return static::systemGlob($pattern, $flags); + } + } + + $p = $begin + 1; + + while (true) { + $subPattern = substr($pattern, 0, $begin) + . substr($pattern, $p, $next - $p) + . substr($pattern, $rest + 1); + + $result = static::fallbackGlob($subPattern, $flags | self::GLOB_BRACE); + + if ($result) { + $paths = array_merge($paths, $result); + } + + if ($pattern[$next] === '}') { + break; + } + + $p = $next + 1; + $next = static::nextBraceSub($pattern, $p, $flags); + } + + return array_unique($paths); + } + + /** + * Find the end of the sub-pattern in a brace expression. + * + * @param string $pattern + * @param int $begin + * @param int $flags + * @return int|null + */ + protected static function nextBraceSub($pattern, $begin, $flags) + { + $length = strlen($pattern); + $depth = 0; + $current = $begin; + + while ($current < $length) { + $flagsEqualsNoEscape = self::flagsIsEqualTo($flags, self::GLOB_NOESCAPE); + + if ($flagsEqualsNoEscape && $pattern[$current] === '\\') { + if (++$current === $length) { + break; + } + + $current++; + } else { + if ( + ($pattern[$current] === '}' && $depth-- === 0) + || ($pattern[$current] === ',' && $depth === 0) + ) { + break; + } elseif ($pattern[$current++] === '{') { + $depth++; + } + } + } + + return $current < $length ? $current : null; + } + + /** @internal */ + public static function flagsIsEqualTo(int $flags, int $otherFlags): bool + { + return (bool) ($flags & $otherFlags); + } +} diff --git a/lib/laminas/laminas-stdlib/src/Guard/AllGuardsTrait.php b/lib/laminas/laminas-stdlib/src/Guard/AllGuardsTrait.php new file mode 100644 index 0000000000..b5abe5a62f --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/Guard/AllGuardsTrait.php @@ -0,0 +1,15 @@ +metadata[$spec] = $value; + return $this; + } + if (! is_array($spec) && ! $spec instanceof Traversable) { + throw new Exception\InvalidArgumentException(sprintf( + 'Expected a string, array, or Traversable argument in first position; received "%s"', + is_object($spec) ? get_class($spec) : gettype($spec) + )); + } + foreach ($spec as $key => $value) { + $this->metadata[$key] = $value; + } + return $this; + } + + /** + * Retrieve all metadata or a single metadatum as specified by key + * + * @param null|string|int $key + * @param null|mixed $default + * @throws Exception\InvalidArgumentException + * @return mixed + */ + public function getMetadata($key = null, $default = null) + { + if (null === $key) { + return $this->metadata; + } + + if (! is_scalar($key)) { + throw new Exception\InvalidArgumentException('Non-scalar argument provided for key'); + } + + if (array_key_exists($key, $this->metadata)) { + return $this->metadata[$key]; + } + + return $default; + } + + /** + * Set message content + * + * @param mixed $value + * @return Message + */ + public function setContent($value) + { + $this->content = $value; + return $this; + } + + /** + * Get message content + * + * @return mixed + */ + public function getContent() + { + return $this->content; + } + + /** + * @return string + */ + public function toString() + { + $request = ''; + foreach ($this->getMetadata() as $key => $value) { + $request .= sprintf( + "%s: %s\r\n", + (string) $key, + (string) $value + ); + } + $request .= "\r\n" . $this->getContent(); + return $request; + } +} diff --git a/lib/laminas/laminas-stdlib/src/MessageInterface.php b/lib/laminas/laminas-stdlib/src/MessageInterface.php new file mode 100644 index 0000000000..71a4820810 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/MessageInterface.php @@ -0,0 +1,41 @@ +exchangeArray($values); + } + + /** + * Populate from query string + * + * @param string $string + * @return void + */ + public function fromString($string) + { + $array = []; + parse_str($string, $array); + $this->fromArray($array); + } + + /** + * Serialize to native PHP array + * + * @return array + */ + public function toArray() + { + return $this->getArrayCopy(); + } + + /** + * Serialize to query string + * + * @return string + */ + public function toString() + { + return http_build_query($this->toArray()); + } + + /** + * Retrieve by key + * + * Returns null if the key does not exist. + * + * @param string $name + * @return mixed + */ + #[ReturnTypeWillChange] + public function offsetGet($name) + { + if ($this->offsetExists($name)) { + return parent::offsetGet($name); + } + + return null; + } + + /** + * @param string $name + * @param mixed $default optional default value + * @return mixed + */ + public function get($name, $default = null) + { + if ($this->offsetExists($name)) { + return parent::offsetGet($name); + } + return $default; + } + + /** + * @param string $name + * @param mixed $value + * @return Parameters + */ + public function set($name, $value) + { + $this[$name] = $value; + return $this; + } +} diff --git a/lib/laminas/laminas-stdlib/src/ParametersInterface.php b/lib/laminas/laminas-stdlib/src/ParametersInterface.php new file mode 100644 index 0000000000..8e07e070de --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/ParametersInterface.php @@ -0,0 +1,81 @@ +items[$name])) { + $this->count++; + } + + $this->sorted = false; + + $this->items[$name] = [ + 'data' => $value, + 'priority' => (int) $priority, + 'serial' => $this->serial++, + ]; + } + + /** + * @param string $name + * @param int $priority + * @return $this + * @throws Exception + */ + public function setPriority($name, $priority) + { + if (! isset($this->items[$name])) { + throw new Exception("item $name not found"); + } + + $this->items[$name]['priority'] = (int) $priority; + $this->sorted = false; + + return $this; + } + + /** + * Remove a item. + * + * @param string $name + * @return void + */ + public function remove($name) + { + if (isset($this->items[$name])) { + $this->count--; + } + + unset($this->items[$name]); + } + + /** + * Remove all items. + * + * @return void + */ + public function clear() + { + $this->items = []; + $this->serial = 0; + $this->count = 0; + $this->sorted = false; + } + + /** + * Get a item. + * + * @param string $name + * @return mixed + */ + public function get($name) + { + if (! isset($this->items[$name])) { + return; + } + + return $this->items[$name]['data']; + } + + /** + * Sort all items. + * + * @return void + */ + protected function sort() + { + if (! $this->sorted) { + uasort($this->items, [$this, 'compare']); + $this->sorted = true; + } + } + + /** + * Compare the priority of two items. + * + * @param array $item1, + * @param array $item2 + * @return int + */ + protected function compare(array $item1, array $item2) + { + return $item1['priority'] === $item2['priority'] + ? ($item1['serial'] > $item2['serial'] ? -1 : 1) * $this->isLIFO + : ($item1['priority'] > $item2['priority'] ? -1 : 1); + } + + /** + * Get/Set serial order mode + * + * @param bool|null $flag + * @return bool + */ + public function isLIFO($flag = null) + { + if ($flag !== null) { + $isLifo = $flag === true ? 1 : -1; + + if ($isLifo !== $this->isLIFO) { + $this->isLIFO = $isLifo; + $this->sorted = false; + } + } + + return 1 === $this->isLIFO; + } + + /** + * {@inheritDoc} + */ + #[ReturnTypeWillChange] + public function rewind() + { + $this->sort(); + reset($this->items); + } + + /** + * {@inheritDoc} + */ + #[ReturnTypeWillChange] + public function current() + { + $this->sorted || $this->sort(); + $node = current($this->items); + + return $node ? $node['data'] : false; + } + + /** + * {@inheritDoc} + */ + #[ReturnTypeWillChange] + public function key() + { + $this->sorted || $this->sort(); + return key($this->items); + } + + /** + * {@inheritDoc} + */ + #[ReturnTypeWillChange] + public function next() + { + $node = next($this->items); + + return $node ? $node['data'] : false; + } + + /** + * {@inheritDoc} + */ + #[ReturnTypeWillChange] + public function valid() + { + return current($this->items) !== false; + } + + /** + * @return self + */ + public function getIterator() + { + return clone $this; + } + + /** + * {@inheritDoc} + */ + #[ReturnTypeWillChange] + public function count() + { + return $this->count; + } + + /** + * Return list as array + * + * @param int $flag + * @return array + */ + public function toArray($flag = self::EXTR_DATA) + { + $this->sort(); + + if ($flag === self::EXTR_BOTH) { + return $this->items; + } + + return array_map( + static fn($item) => $flag === self::EXTR_PRIORITY ? $item['priority'] : $item['data'], + $this->items + ); + } +} diff --git a/lib/laminas/laminas-stdlib/src/PriorityQueue.php b/lib/laminas/laminas-stdlib/src/PriorityQueue.php new file mode 100644 index 0000000000..92af39223c --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/PriorityQueue.php @@ -0,0 +1,385 @@ + + */ +class PriorityQueue implements Countable, IteratorAggregate, Serializable +{ + public const EXTR_DATA = 0x00000001; + public const EXTR_PRIORITY = 0x00000002; + public const EXTR_BOTH = 0x00000003; + + /** + * Inner queue class to use for iteration + * + * @var class-string<\SplPriorityQueue> + */ + protected $queueClass = SplPriorityQueue::class; + + /** + * Actual items aggregated in the priority queue. Each item is an array + * with keys "data" and "priority". + * + * @var list + */ + protected $items = []; + + /** + * Inner queue object + * + * @var \SplPriorityQueue|null + */ + protected $queue; + + /** + * Insert an item into the queue + * + * Priority defaults to 1 (low priority) if none provided. + * + * @param T $data + * @param TPriority $priority + * @return $this + */ + public function insert($data, $priority = 1) + { + /** @psalm-var TPriority $priority */ + $priority = (int) $priority; + $this->items[] = [ + 'data' => $data, + 'priority' => $priority, + ]; + $this->getQueue()->insert($data, $priority); + return $this; + } + + /** + * Remove an item from the queue + * + * This is different than {@link extract()}; its purpose is to dequeue an + * item. + * + * This operation is potentially expensive, as it requires + * re-initialization and re-population of the inner queue. + * + * Note: this removes the first item matching the provided item found. If + * the same item has been added multiple times, it will not remove other + * instances. + * + * @param mixed $datum + * @return bool False if the item was not found, true otherwise. + */ + public function remove($datum) + { + $found = false; + $key = null; + foreach ($this->items as $key => $item) { + if ($item['data'] === $datum) { + $found = true; + break; + } + } + if ($found && $key !== null) { + unset($this->items[$key]); + $this->queue = null; + + if (! $this->isEmpty()) { + $queue = $this->getQueue(); + foreach ($this->items as $item) { + $queue->insert($item['data'], $item['priority']); + } + } + return true; + } + return false; + } + + /** + * Is the queue empty? + * + * @return bool + */ + public function isEmpty() + { + return 0 === $this->count(); + } + + /** + * How many items are in the queue? + * + * @return int + */ + #[ReturnTypeWillChange] + public function count() + { + return count($this->items); + } + + /** + * Peek at the top node in the queue, based on priority. + * + * @return T + */ + public function top() + { + $queue = clone $this->getQueue(); + + return $queue->top(); + } + + /** + * Extract a node from the inner queue and sift up + * + * @return T + */ + public function extract() + { + $value = $this->getQueue()->extract(); + + $keyToRemove = null; + $highestPriority = null; + foreach ($this->items as $key => $item) { + if ($item['data'] !== $value) { + continue; + } + + if (null === $highestPriority) { + $highestPriority = $item['priority']; + $keyToRemove = $key; + continue; + } + + if ($highestPriority >= $item['priority']) { + continue; + } + + $highestPriority = $item['priority']; + $keyToRemove = $key; + } + + if ($keyToRemove !== null) { + unset($this->items[$keyToRemove]); + } + + return $value; + } + + /** + * Retrieve the inner iterator + * + * SplPriorityQueue acts as a heap, which typically implies that as items + * are iterated, they are also removed. This does not work for situations + * where the queue may be iterated multiple times. As such, this class + * aggregates the values, and also injects an SplPriorityQueue. This method + * retrieves the inner queue object, and clones it for purposes of + * iteration. + * + * @return \SplPriorityQueue + */ + #[ReturnTypeWillChange] + public function getIterator() + { + $queue = $this->getQueue(); + return clone $queue; + } + + /** + * Serialize the data structure + * + * @return string + */ + public function serialize() + { + return serialize($this->__serialize()); + } + + /** + * Magic method used for serializing of an instance. + * + * @return list + */ + public function __serialize() + { + return $this->items; + } + + /** + * Unserialize a string into a PriorityQueue object + * + * Serialization format is compatible with {@link SplPriorityQueue} + * + * @param string $data + * @return void + */ + public function unserialize($data) + { + $toUnserialize = unserialize($data); + if (! is_array($toUnserialize)) { + throw new UnexpectedValueException(sprintf( + 'Cannot deserialize %s instance; corrupt serialization data', + self::class + )); + } + + /** @psalm-var list $toUnserialize */ + + $this->__unserialize($toUnserialize); + } + + /** + * Magic method used to rebuild an instance. + * + * @param list $data Data array. + * @return void + */ + public function __unserialize($data) + { + foreach ($data as $item) { + $this->insert($item['data'], $item['priority']); + } + } + + /** + * Serialize to an array + * By default, returns only the item data, and in the order registered (not + * sorted). You may provide one of the EXTR_* flags as an argument, allowing + * the ability to return priorities or both data and priority. + * + * @param int $flag + * @return array + * @psalm-return ($flag is self::EXTR_BOTH + * ? list + * : $flag is self::EXTR_PRIORITY + * ? list + * : list + * ) + */ + public function toArray($flag = self::EXTR_DATA) + { + switch ($flag) { + case self::EXTR_BOTH: + return $this->items; + case self::EXTR_PRIORITY: + return array_map(static fn($item) => $item['priority'], $this->items); + case self::EXTR_DATA: + default: + return array_map(static fn($item) => $item['data'], $this->items); + } + } + + /** + * Specify the internal queue class + * + * Please see {@link getIterator()} for details on the necessity of an + * internal queue class. The class provided should extend SplPriorityQueue. + * + * @param class-string<\SplPriorityQueue> $class + * @return $this + */ + public function setInternalQueueClass($class) + { + /** @psalm-suppress RedundantCastGivenDocblockType */ + $this->queueClass = (string) $class; + return $this; + } + + /** + * Does the queue contain the given datum? + * + * @param T $datum + * @return bool + */ + public function contains($datum) + { + foreach ($this->items as $item) { + if ($item['data'] === $datum) { + return true; + } + } + return false; + } + + /** + * Does the queue have an item with the given priority? + * + * @param TPriority $priority + * @return bool + */ + public function hasPriority($priority) + { + foreach ($this->items as $item) { + if ($item['priority'] === $priority) { + return true; + } + } + return false; + } + + /** + * Get the inner priority queue instance + * + * @throws Exception\DomainException + * @return \SplPriorityQueue + * @psalm-assert !null $this->queue + */ + protected function getQueue() + { + if (null === $this->queue) { + /** @psalm-suppress UnsafeInstantiation */ + $queue = new $this->queueClass(); + /** @psalm-var \SplPriorityQueue $queue */ + $this->queue = $queue; + /** @psalm-suppress DocblockTypeContradiction, MixedArgument */ + if (! $this->queue instanceof \SplPriorityQueue) { + throw new Exception\DomainException(sprintf( + 'PriorityQueue expects an internal queue of type SplPriorityQueue; received "%s"', + get_class($this->queue) + )); + } + } + + return $this->queue; + } + + /** + * Add support for deep cloning + * + * @return void + */ + public function __clone() + { + if (null !== $this->queue) { + $this->queue = clone $this->queue; + } + } +} diff --git a/lib/laminas/laminas-stdlib/src/Request.php b/lib/laminas/laminas-stdlib/src/Request.php new file mode 100644 index 0000000000..65026f6092 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/Request.php @@ -0,0 +1,10 @@ + + */ +class SplPriorityQueue extends \SplPriorityQueue implements Serializable +{ + /** @var int Seed used to ensure queue order for items of the same priority */ + protected $serial = PHP_INT_MAX; + + /** + * Insert a value with a given priority + * + * Utilizes {@var $serial} to ensure that values of equal priority are + * emitted in the same order in which they are inserted. + * + * @param TValue $datum + * @param TPriority|mixed $priority + * @return void + */ + public function insert($datum, $priority) + { + if (! is_array($priority)) { + $priority = [$priority, $this->serial--]; + } + + /** @psalm-var TPriority $priority */ + + parent::insert($datum, $priority); + } + + /** + * Serialize to an array + * + * Array will be priority => data pairs + * + * @return list + */ + public function toArray() + { + $array = []; + foreach (clone $this as $item) { + $array[] = $item; + } + return $array; + } + + /** + * Serialize + * + * @return string + */ + public function serialize() + { + return serialize($this->__serialize()); + } + + /** + * Magic method used for serializing of an instance. + * + * @return array + */ + public function __serialize() + { + $clone = clone $this; + $clone->setExtractFlags(self::EXTR_BOTH); + + $data = []; + foreach ($clone as $item) { + $data[] = $item; + } + return $data; + } + + /** + * Deserialize + * + * @param string $data + * @return void + */ + public function unserialize($data) + { + $toUnserialize = unserialize($data); + if (! is_array($toUnserialize)) { + throw new UnexpectedValueException(sprintf( + 'Cannot deserialize %s instance; corrupt serialization data', + self::class + )); + } + + $this->__unserialize($toUnserialize); + } + + /** + * Magic method used to rebuild an instance. + * + * @param array $data Data array. + * @return void + */ + public function __unserialize($data) + { + $this->serial = PHP_INT_MAX; + + foreach ($data as $item) { + if (! is_array($item)) { + throw new UnexpectedValueException(sprintf( + 'Cannot deserialize %s instance: corrupt item; expected array, received %s', + self::class, + is_object($item) ? get_class($item) : gettype($item) + )); + } + + if (! array_key_exists('data', $item)) { + throw new UnexpectedValueException(sprintf( + 'Cannot deserialize %s instance: corrupt item; missing "data" element', + self::class + )); + } + + $priority = 1; + if (array_key_exists('priority', $item)) { + $priority = (int) $item['priority']; + } + + /** @psalm-var TValue $item['data'] */ + + $this->insert($item['data'], $priority); + } + } +} diff --git a/lib/laminas/laminas-stdlib/src/SplQueue.php b/lib/laminas/laminas-stdlib/src/SplQueue.php new file mode 100644 index 0000000000..2656a856b0 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/SplQueue.php @@ -0,0 +1,94 @@ + + */ +class SplQueue extends \SplQueue implements Serializable +{ + /** + * Return an array representing the queue + * + * @return list + */ + public function toArray() + { + $array = []; + foreach ($this as $item) { + $array[] = $item; + } + return $array; + } + + /** + * Serialize + * + * @return string + */ + #[ReturnTypeWillChange] + public function serialize() + { + return serialize($this->__serialize()); + } + + /** + * Magic method used for serializing of an instance. + * + * @return list + */ + #[ReturnTypeWillChange] + public function __serialize() + { + return $this->toArray(); + } + + /** + * Unserialize + * + * @param string $data + * @return void + */ + #[ReturnTypeWillChange] + public function unserialize($data) + { + $toUnserialize = unserialize($data); + if (! is_array($toUnserialize)) { + throw new UnexpectedValueException(sprintf( + 'Cannot deserialize %s instance; corrupt serialization data', + self::class + )); + } + + $this->__unserialize($toUnserialize); + } + + /** + * Magic method used to rebuild an instance. + * + * @param array $data Data array. + * @return void + */ + #[ReturnTypeWillChange] + public function __unserialize($data) + { + foreach ($data as $item) { + $this->push($item); + } + } +} diff --git a/lib/laminas/laminas-stdlib/src/SplStack.php b/lib/laminas/laminas-stdlib/src/SplStack.php new file mode 100644 index 0000000000..52564fde8d --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/SplStack.php @@ -0,0 +1,93 @@ + + */ +class SplStack extends \SplStack implements Serializable +{ + /** + * Serialize to an array representing the stack + * + * @return list + */ + public function toArray() + { + $array = []; + foreach ($this as $item) { + $array[] = $item; + } + return $array; + } + + /** + * Serialize + * + * @return string + */ + #[ReturnTypeWillChange] + public function serialize() + { + return serialize($this->__serialize()); + } + + /** + * Magic method used for serializing of an instance. + * + * @return list + */ + #[ReturnTypeWillChange] + public function __serialize() + { + return $this->toArray(); + } + + /** + * Unserialize + * + * @param string $data + * @return void + */ + #[ReturnTypeWillChange] + public function unserialize($data) + { + $toUnserialize = unserialize($data); + if (! is_array($toUnserialize)) { + throw new UnexpectedValueException(sprintf( + 'Cannot deserialize %s instance; corrupt serialization data', + self::class + )); + } + + $this->__unserialize($toUnserialize); + } + + /** + * Magic method used to rebuild an instance. + * + * @param array $data Data array. + * @return void + */ + #[ReturnTypeWillChange] + public function __unserialize($data) + { + foreach ($data as $item) { + $this->unshift($item); + } + } +} diff --git a/lib/laminas/laminas-stdlib/src/StringUtils.php b/lib/laminas/laminas-stdlib/src/StringUtils.php new file mode 100644 index 0000000000..ffc3ad553c --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/StringUtils.php @@ -0,0 +1,213 @@ +>|null + */ + protected static $wrapperRegistry; + + /** + * A list of known single-byte character encodings (upper-case) + * + * @var string[] + */ + protected static $singleByteEncodings = [ + 'ASCII', + '7BIT', + '8BIT', + 'ISO-8859-1', + 'ISO-8859-2', + 'ISO-8859-3', + 'ISO-8859-4', + 'ISO-8859-5', + 'ISO-8859-6', + 'ISO-8859-7', + 'ISO-8859-8', + 'ISO-8859-9', + 'ISO-8859-10', + 'ISO-8859-11', + 'ISO-8859-13', + 'ISO-8859-14', + 'ISO-8859-15', + 'ISO-8859-16', + 'CP-1251', + 'CP-1252', + // TODO + ]; + + /** + * Is PCRE compiled with Unicode support? + * + * @var bool + **/ + protected static $hasPcreUnicodeSupport; + + /** + * Get registered wrapper classes + * + * @return string[] + * @psalm-return list> + */ + public static function getRegisteredWrappers() + { + if (static::$wrapperRegistry === null) { + static::$wrapperRegistry = []; + + if (extension_loaded('intl')) { + static::$wrapperRegistry[] = Intl::class; + } + + if (extension_loaded('mbstring')) { + static::$wrapperRegistry[] = MbString::class; + } + + if (extension_loaded('iconv')) { + static::$wrapperRegistry[] = Iconv::class; + } + + static::$wrapperRegistry[] = Native::class; + } + + return static::$wrapperRegistry; + } + + /** + * Register a string wrapper class + * + * @param class-string $wrapper + * @return void + */ + public static function registerWrapper($wrapper) + { + $wrapper = (string) $wrapper; + // using getRegisteredWrappers() here to ensure that the list is initialized + if (! in_array($wrapper, static::getRegisteredWrappers(), true)) { + static::$wrapperRegistry[] = $wrapper; + } + } + + /** + * Unregister a string wrapper class + * + * @param class-string $wrapper + * @return void + */ + public static function unregisterWrapper($wrapper) + { + // using getRegisteredWrappers() here to ensure that the list is initialized + $index = array_search((string) $wrapper, static::getRegisteredWrappers(), true); + if ($index !== false) { + unset(static::$wrapperRegistry[$index]); + } + } + + /** + * Reset all registered wrappers so the default wrappers will be used + * + * @return void + */ + public static function resetRegisteredWrappers() + { + static::$wrapperRegistry = null; + } + + /** + * Get the first string wrapper supporting the given character encoding + * and supports to convert into the given convert encoding. + * + * @param string $encoding Character encoding to support + * @param string|null $convertEncoding OPTIONAL character encoding to convert in + * @return StringWrapperInterface + * @throws Exception\RuntimeException If no wrapper supports given character encodings. + */ + public static function getWrapper($encoding = 'UTF-8', $convertEncoding = null) + { + foreach (static::getRegisteredWrappers() as $wrapperClass) { + if ($wrapperClass::isSupported($encoding, $convertEncoding)) { + $wrapper = new $wrapperClass($encoding, $convertEncoding); + $wrapper->setEncoding($encoding, $convertEncoding); + return $wrapper; + } + } + + throw new Exception\RuntimeException( + 'No wrapper found supporting "' . $encoding . '"' + . ($convertEncoding !== null ? ' and "' . $convertEncoding . '"' : '') + ); + } + + /** + * Get a list of all known single-byte character encodings + * + * @return string[] + */ + public static function getSingleByteEncodings() + { + return static::$singleByteEncodings; + } + + /** + * Check if a given encoding is a known single-byte character encoding + * + * @param string $encoding + * @return bool + */ + public static function isSingleByteEncoding($encoding) + { + return in_array(strtoupper($encoding), static::$singleByteEncodings); + } + + /** + * Check if a given string is valid UTF-8 encoded + * + * @param string $str + * @return bool + */ + public static function isValidUtf8($str) + { + return is_string($str) && ($str === '' || preg_match('/^./su', $str) === 1); + } + + /** + * Is PCRE compiled with Unicode support? + * + * @return bool + */ + public static function hasPcreUnicodeSupport() + { + if (static::$hasPcreUnicodeSupport === null) { + ErrorHandler::start(); + static::$hasPcreUnicodeSupport = defined('PREG_BAD_UTF8_OFFSET_ERROR') && preg_match('/\pL/u', 'a') === 1; + ErrorHandler::stop(); + } + return static::$hasPcreUnicodeSupport; + } +} diff --git a/lib/laminas/laminas-stdlib/src/StringWrapper/AbstractStringWrapper.php b/lib/laminas/laminas-stdlib/src/StringWrapper/AbstractStringWrapper.php new file mode 100644 index 0000000000..a96c7a4688 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/StringWrapper/AbstractStringWrapper.php @@ -0,0 +1,278 @@ +convertEncoding = $convertEncodingUpper; + } else { + $this->convertEncoding = null; + } + $this->encoding = $encodingUpper; + + return $this; + } + + /** + * Get the defined character encoding to work with + * + * @return null|string + * @throws Exception\LogicException If no encoding was defined. + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Get the defined character encoding to convert to + * + * @return string|null + */ + public function getConvertEncoding() + { + return $this->convertEncoding; + } + + /** + * Convert a string from defined character encoding to the defined convert encoding + * + * @param string $str + * @param bool $reverse + * @return string|false + */ + public function convert($str, $reverse = false) + { + $encoding = $this->getEncoding(); + $convertEncoding = $this->getConvertEncoding(); + if ($convertEncoding === null) { + throw new Exception\LogicException( + 'No convert encoding defined' + ); + } + + if ($encoding === $convertEncoding) { + return $str; + } + + $from = $reverse ? $convertEncoding : $encoding; + $to = $reverse ? $encoding : $convertEncoding; + throw new Exception\RuntimeException(sprintf( + 'Converting from "%s" to "%s" isn\'t supported by this string wrapper', + $from ?? '', + $to ?? '' + )); + } + + /** + * Wraps a string to a given number of characters + * + * @param string $string + * @param int $width + * @param string $break + * @param bool $cut + * @return string|false + */ + public function wordWrap($string, $width = 75, $break = "\n", $cut = false) + { + $string = (string) $string; + if ($string === '') { + return ''; + } + + $break = (string) $break; + if ($break === '') { + throw new Exception\InvalidArgumentException('Break string cannot be empty'); + } + + $width = (int) $width; + if ($width === 0 && $cut) { + throw new Exception\InvalidArgumentException('Cannot force cut when width is zero'); + } + + if (null === $this->getEncoding() || StringUtils::isSingleByteEncoding($this->getEncoding())) { + return wordwrap($string, $width, $break, $cut); + } + + $stringWidth = $this->strlen($string); + $breakWidth = $this->strlen($break); + + $result = ''; + $lastStart = $lastSpace = 0; + + for ($current = 0; $current < $stringWidth; $current++) { + $char = $this->substr($string, $current, 1); + + $possibleBreak = $char; + if ($breakWidth !== 1) { + $possibleBreak = $this->substr($string, $current, $breakWidth); + } + + if ($possibleBreak === $break) { + $result .= $this->substr($string, $lastStart, $current - $lastStart + $breakWidth); + $current += $breakWidth - 1; + $lastStart = $lastSpace = $current + 1; + continue; + } + + if ($char === ' ') { + if ($current - $lastStart >= $width) { + $result .= $this->substr($string, $lastStart, $current - $lastStart) . $break; + $lastStart = $current + 1; + } + + $lastSpace = $current; + continue; + } + + if ($current - $lastStart >= $width && $cut && $lastStart >= $lastSpace) { + $result .= $this->substr($string, $lastStart, $current - $lastStart) . $break; + $lastStart = $lastSpace = $current; + continue; + } + + if ($current - $lastStart >= $width && $lastStart < $lastSpace) { + $result .= $this->substr($string, $lastStart, $lastSpace - $lastStart) . $break; + $lastStart = $lastSpace += 1; + continue; + } + } + + if ($lastStart !== $current) { + $result .= $this->substr($string, $lastStart, $current - $lastStart); + } + + return $result; + } + + /** + * Pad a string to a certain length with another string + * + * @param string $input + * @param int $padLength + * @param string $padString + * @param int $padType + * @return string + */ + public function strPad($input, $padLength, $padString = ' ', $padType = STR_PAD_RIGHT) + { + if (null === $this->getEncoding() || StringUtils::isSingleByteEncoding($this->getEncoding())) { + return str_pad($input, $padLength, $padString, $padType); + } + + $lengthOfPadding = $padLength - $this->strlen($input); + if ($lengthOfPadding <= 0) { + return $input; + } + + $padStringLength = $this->strlen($padString); + if ($padStringLength === 0) { + return $input; + } + + $repeatCount = (int) floor($lengthOfPadding / $padStringLength); + + if ($padType === STR_PAD_BOTH) { + $repeatCountLeft = $repeatCountRight = ($repeatCount - $repeatCount % 2) / 2; + + $lastStringLength = $lengthOfPadding - 2 * $repeatCountLeft * $padStringLength; + $lastStringLeftLength = $lastStringRightLength = (int) floor($lastStringLength / 2); + $lastStringRightLength += $lastStringLength % 2; + + $lastStringLeft = $this->substr($padString, 0, $lastStringLeftLength); + $lastStringRight = $this->substr($padString, 0, $lastStringRightLength); + + return str_repeat($padString, $repeatCountLeft) . $lastStringLeft + . $input + . str_repeat($padString, $repeatCountRight) . $lastStringRight; + } + + $lastString = $this->substr($padString, 0, $lengthOfPadding % $padStringLength); + + if ($padType === STR_PAD_LEFT) { + return str_repeat($padString, $repeatCount) . $lastString . $input; + } + + return $input . str_repeat($padString, $repeatCount) . $lastString; + } +} diff --git a/lib/laminas/laminas-stdlib/src/StringWrapper/Iconv.php b/lib/laminas/laminas-stdlib/src/StringWrapper/Iconv.php new file mode 100644 index 0000000000..2f52b74e1b --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/StringWrapper/Iconv.php @@ -0,0 +1,302 @@ +getEncoding()); + } + + /** + * Returns the portion of string specified by the start and length parameters + * + * @param string $str + * @param int $offset + * @param int|null $length + * @return string|false + */ + public function substr($str, $offset = 0, $length = null) + { + $length ??= $this->strlen($str); + assert($length !== false); + + return iconv_substr($str, $offset, $length, $this->getEncoding()); + } + + /** + * Find the position of the first occurrence of a substring in a string + * + * @param string $haystack + * @param string $needle + * @param int $offset + * @return int|false + */ + public function strpos($haystack, $needle, $offset = 0) + { + $encoding = $this->getEncoding(); + assert($encoding !== null); + + return iconv_strpos($haystack, $needle, $offset, $encoding); + } + + /** + * Convert a string from defined encoding to the defined convert encoding + * + * @param string $str + * @param bool $reverse + * @return string|false + */ + public function convert($str, $reverse = false) + { + $encoding = $this->getEncoding(); + $convertEncoding = $this->getConvertEncoding(); + if ($convertEncoding === null) { + throw new Exception\LogicException( + 'No convert encoding defined' + ); + } + + if ($encoding === $convertEncoding) { + return $str; + } + + $fromEncoding = $reverse ? $convertEncoding : $encoding; + $toEncoding = $reverse ? $encoding : $convertEncoding; + + if (null === $toEncoding || null === $fromEncoding) { + return $str; + } + + // automatically add "//IGNORE" to not stop converting on invalid characters + // invalid characters triggers a notice anyway + return iconv($fromEncoding, $toEncoding . '//IGNORE', $str); + } +} diff --git a/lib/laminas/laminas-stdlib/src/StringWrapper/Intl.php b/lib/laminas/laminas-stdlib/src/StringWrapper/Intl.php new file mode 100644 index 0000000000..81a6b9a8a2 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/StringWrapper/Intl.php @@ -0,0 +1,89 @@ +getEncoding()); + } + + /** + * Returns the portion of string specified by the start and length parameters + * + * @param string $str + * @param int $offset + * @param int|null $length + * @return string|false + */ + public function substr($str, $offset = 0, $length = null) + { + return mb_substr($str, $offset, $length, $this->getEncoding()); + } + + /** + * Find the position of the first occurrence of a substring in a string + * + * @param string $haystack + * @param string $needle + * @param int $offset + * @return int|false + */ + public function strpos($haystack, $needle, $offset = 0) + { + return mb_strpos($haystack, $needle, $offset, $this->getEncoding()); + } + + /** + * Convert a string from defined encoding to the defined convert encoding + * + * @param string $str + * @param bool $reverse + * @return string|false + */ + public function convert($str, $reverse = false) + { + $encoding = $this->getEncoding(); + $convertEncoding = $this->getConvertEncoding(); + + if ($convertEncoding === null) { + throw new Exception\LogicException( + 'No convert encoding defined' + ); + } + + if ($encoding === $convertEncoding) { + return $str; + } + + $fromEncoding = $reverse ? $convertEncoding : $encoding; + $toEncoding = $reverse ? $encoding : $convertEncoding; + + return mb_convert_encoding($str, $toEncoding ?? '', $fromEncoding ?? ''); + } +} diff --git a/lib/laminas/laminas-stdlib/src/StringWrapper/Native.php b/lib/laminas/laminas-stdlib/src/StringWrapper/Native.php new file mode 100644 index 0000000000..a137d4ae1f --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/StringWrapper/Native.php @@ -0,0 +1,135 @@ +convertEncoding = $encodingUpper; + } + + if ($convertEncoding !== null) { + if ($encodingUpper !== strtoupper($convertEncoding)) { + throw new Exception\InvalidArgumentException( + 'Wrapper doesn\'t support to convert between character encodings' + ); + } + + $this->convertEncoding = $encodingUpper; + } else { + $this->convertEncoding = null; + } + $this->encoding = $encodingUpper; + + return $this; + } + + /** + * Returns the length of the given string + * + * @param string $str + * @return int|false + */ + public function strlen($str) + { + return strlen($str); + } + + /** + * Returns the portion of string specified by the start and length parameters + * + * @param string $str + * @param int $offset + * @param int|null $length + * @return string|false + */ + public function substr($str, $offset = 0, $length = null) + { + return substr($str, $offset, $length); + } + + /** + * Find the position of the first occurrence of a substring in a string + * + * @param string $haystack + * @param string $needle + * @param int $offset + * @return int|false + */ + public function strpos($haystack, $needle, $offset = 0) + { + return strpos($haystack, $needle, $offset); + } +} diff --git a/lib/laminas/laminas-stdlib/src/StringWrapper/StringWrapperInterface.php b/lib/laminas/laminas-stdlib/src/StringWrapper/StringWrapperInterface.php new file mode 100644 index 0000000000..700799f911 --- /dev/null +++ b/lib/laminas/laminas-stdlib/src/StringWrapper/StringWrapperInterface.php @@ -0,0 +1,108 @@ + ## 🇷🇺 Русским гражданам +> +> Мы, участники Laminas, родились и живем в разных странах. У многих из нас есть друзья, родственники и коллеги как в России, так и в Украине. Некоторые из нас родились в России. Некоторые из нас живут в России. У некоторых бабушки и дедушки сражались с фашистами во Второй мировой войне. Здесь никто не поддерживает фашизм. +> +> У одного из нас есть украинская родственница, которая спаслась из дома вместе с сыном. Поезд задержался из-за бомбежки на дороге впереди. У нас есть друзья, которые прячутся в бомбоубежищах. Мы с тревогой ждем весточки от них после воздушных налетов, которые беспорядочно наносят удары и попадают по больницам, школам, детским садам и домам. Мы не берем это из каких-либо СМИ. Мы наблюдаем это напрямую. +> +> Вы доверяете нам достаточно, чтоб использовать наши программы, и мы просим вас довериться нам вновь. Мы нуждаемся в помощи. Выходите и протестуйте против этой бесполезной войны. Остановите кровопролитие. Скажите "Нет войне!" +> +> ## 🇺🇸 To Citizens of Russia +> +> We at Laminas come from all over the world. Many of us have friends, family and colleagues in both Russia and Ukraine. Some of us were born in Russia. Some of us currently live in Russia. Some have grandparents who fought Nazis in World War II. Nobody here supports fascism. +> +> One team member has a Ukrainian relative who fled her home with her son. The train was delayed due to bombing on the road ahead. We have friends who are hiding in bomb shelters. We anxiously follow up on them after the air raids, which indiscriminately fire at hospitals, schools, kindergartens and houses. We're not taking this from any media. These are our actual experiences. +> +> You trust us enough to use our software. We ask that you trust us to say the truth on this. We need your help. Go out and protest this unnecessary war. Stop the bloodshed. Say "stop the war!" + +laminas-validator provides a set of commonly needed validators. It also provides a +simple validator chaining mechanism by which multiple validators may be applied +to a single datum in a user-defined order. + +## Installation + +Run the following to install this library: + +```bash +$ composer require laminas/laminas-validator +``` + +## Documentation + +Browse the documentation online at https://docs.laminas.dev/laminas-validator/ + +## Support + +* [Issues](https://github.com/laminas/laminas-validator/issues/) +* [Chat](https://laminas.dev/chat/) +* [Forum](https://discourse.laminas.dev/) diff --git a/lib/laminas/laminas-validator/bin/update_hostname_validator.php b/lib/laminas/laminas-validator/bin/update_hostname_validator.php new file mode 100644 index 0000000000..fbc4616341 --- /dev/null +++ b/lib/laminas/laminas-validator/bin/update_hostname_validator.php @@ -0,0 +1,196 @@ + $newFileContent */ +$newFileContent = []; // new file content +$insertDone = false; // becomes 'true' when we find start of $validTlds declaration +$insertFinish = false; // becomes 'true' when we find end of $validTlds declaration +$checkOnly = isset($argv[1]) ? $argv[1] === '--check-only' : false; +$response = getOfficialTLDs(); +$ianaVersion = getVersionFromString('Version', strtok($response->getBody(), "\n")); +$validatorVersion = getVersionFromString('IanaVersion', file_get_contents(LAMINAS_HOSTNAME_VALIDATOR_FILE)); + +if ($checkOnly && $ianaVersion > $validatorVersion) { + printf( + 'TLDs must be updated, please run `php bin/update_hostname_validator.php` and push your changes%s', + PHP_EOL + ); + exit(1); +} + +if ($checkOnly) { + printf('TLDs are up-to-date%s', PHP_EOL); + exit(0); +} + +foreach (file(LAMINAS_HOSTNAME_VALIDATOR_FILE) as $line) { + // Replace old version number with new one + if (preg_match('/\*\s+IanaVersion\s+\d+/', $line, $matches)) { + $newFileContent[] = sprintf(" * IanaVersion %s\n", $ianaVersion); + continue; + } + + if ($insertDone === $insertFinish) { + // Outside of $validTlds definition; keep line as-is + $newFileContent[] = $line; + } + + if ($insertFinish) { + continue; + } + + if ($insertDone) { + // Detect where the $validTlds declaration ends + if (preg_match('/^\s+\];\s*$/', $line)) { + $newFileContent[] = $line; + $insertFinish = true; + } + + continue; + } + + // Detect where the $validTlds declaration begins + if (preg_match('/^\s+protected\s+\$validTlds\s+=\s+\[\s*$/', $line)) { + $newFileContent = array_merge($newFileContent, getNewValidTlds($response->getBody())); + $insertDone = true; + } +} + +if (! $insertDone) { + printf('Error: cannot find line with "protected $validTlds"%s', PHP_EOL); + exit(1); +} + +if (!$insertFinish) { + printf('Error: cannot find end of $validTlds declaration%s', PHP_EOL); + exit(1); +} + +if (false === @file_put_contents(LAMINAS_HOSTNAME_VALIDATOR_FILE, $newFileContent)) { + printf('Error: cannot write info file "%s"%s', LAMINAS_HOSTNAME_VALIDATOR_FILE, PHP_EOL); + exit(1); +} + +printf('Validator TLD file updated.%s', PHP_EOL); +exit(0); + +/** + * Get Official TLDs + * + * @return \Laminas\Http\Response + * @throws Exception + */ +function getOfficialTLDs() +{ + $client = new Client(); + $client->setOptions([ + 'adapter' => 'Laminas\Http\Client\Adapter\Curl', + ]); + $client->setUri(IANA_URL); + $client->setMethod('GET'); + + $response = $client->send(); + if (! $response->isSuccess()) { + throw new \Exception(sprintf("Error: cannot get '%s'%s", IANA_URL, PHP_EOL)); + } + return $response; +} + +/** + * Extract the first match of a string like + * "Version 2015072300" from the given string + * + * @param string $prefix + * @param string $string + * @return string + * @throws Exception + */ +function getVersionFromString($prefix, $string) +{ + $matches = []; + if (! preg_match(sprintf('/%s\s+((\d+)?)/', $prefix), $string, $matches)) { + throw new Exception('Error: cannot get last update date'); + } + + return $matches[1]; +} + +/** + * Extract new Valid TLDs from a string containing one per line. + * + * @param string $string + * @return list + */ +function getNewValidTlds($string) +{ + $decodePunycode = getPunycodeDecoder(); + + // Get new TLDs from the list previously fetched + $newValidTlds = []; + foreach (preg_grep('/^[^#]/', preg_split("#\r?\n#", $string)) as $line) { + $newValidTlds []= sprintf( + "%s'%s',\n", + str_repeat(' ', 8), + $decodePunycode(strtolower($line)) + ); + } + + return $newValidTlds; +} + +/** + * Retrieve and return a punycode decoder. + * + * TLDs are puny encoded. + * + * We need a decodePunycode function to translate TLDs to UTF-8: + * + * - use idn_to_utf8 if available + * - otherwise, use Hostname::decodePunycode() + * + * @return callable + */ +function getPunycodeDecoder() +{ + if (function_exists('idn_to_utf8')) { + return function ($domain) { + return idn_to_utf8($domain, 0, INTL_IDNA_VARIANT_UTS46); + }; + } + + $hostnameValidator = new Hostname(); + $reflection = new ReflectionClass(get_class($hostnameValidator)); + $decodePunyCode = $reflection->getMethod('decodePunycode'); + $decodePunyCode->setAccessible(true); + + return function ($encode) use ($hostnameValidator, $decodePunyCode) { + if (strpos($encode, 'xn--') === 0) { + return $decodePunyCode->invokeArgs($hostnameValidator, [substr($encode, 4)]); + } + return $encode; + }; +} diff --git a/lib/laminas/laminas-validator/composer.json b/lib/laminas/laminas-validator/composer.json new file mode 100644 index 0000000000..3c552aa817 --- /dev/null +++ b/lib/laminas/laminas-validator/composer.json @@ -0,0 +1,91 @@ +{ + "name": "laminas/laminas-validator", + "description": "Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria", + "license": "BSD-3-Clause", + "keywords": [ + "laminas", + "validator" + ], + "homepage": "https://laminas.dev", + "support": { + "docs": "https://docs.laminas.dev/laminas-validator/", + "issues": "https://github.com/laminas/laminas-validator/issues", + "source": "https://github.com/laminas/laminas-validator", + "rss": "https://github.com/laminas/laminas-validator/releases.atom", + "chat": "https://laminas.dev/chat", + "forum": "https://discourse.laminas.dev" + }, + "config": { + "sort-packages": true, + "platform": { + "php": "7.4.99" + }, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "extra": { + "laminas": { + "component": "Laminas\\Validator", + "config-provider": "Laminas\\Validator\\ConfigProvider" + } + }, + "require": { + "php": "^7.4 || ~8.0.0 || ~8.1.0", + "laminas/laminas-servicemanager": "^3.12.0", + "laminas/laminas-stdlib": "^3.10" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.3.0", + "laminas/laminas-db": "^2.7", + "laminas/laminas-filter": "^2.14.0", + "laminas/laminas-http": "^2.14.2", + "laminas/laminas-i18n": "^2.15.0", + "laminas/laminas-session": "^2.12.1", + "laminas/laminas-uri": "^2.9.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5.21", + "psalm/plugin-phpunit": "^0.17.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "vimeo/psalm": "^4.24.0" + }, + "suggest": { + "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator", + "laminas/laminas-filter": "Laminas\\Filter component, required by the Digits validator", + "laminas/laminas-i18n": "Laminas\\I18n component to allow translation of validation error messages", + "laminas/laminas-i18n-resources": "Translations of validator messages", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "laminas/laminas-session": "Laminas\\Session component, ^2.8; required by the Csrf validator", + "laminas/laminas-uri": "Laminas\\Uri component, required by the Uri and Sitemap\\Loc validators", + "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators" + }, + "autoload": { + "psr-4": { + "Laminas\\Validator\\": "src/" + } + }, + "autoload-dev": { + "files": [ + "test/autoload.php" + ], + "psr-4": { + "LaminasTest\\Validator\\": "test/" + } + }, + "scripts": { + "check": [ + "@cs-check", + "@test" + ], + "cs-check": "phpcs", + "cs-fix": "phpcbf", + "test": "phpunit --colors=always", + "test-coverage": "phpunit --colors=always --coverage-clover clover.xml", + "static-analysis": "psalm --shepherd --stats" + }, + "conflict": { + "zendframework/zend-validator": "*" + } +} diff --git a/lib/laminas/laminas-validator/composer.lock b/lib/laminas/laminas-validator/composer.lock new file mode 100644 index 0000000000..88be7a9dac --- /dev/null +++ b/lib/laminas/laminas-validator/composer.lock @@ -0,0 +1,5217 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "12f30984a08a09a9f205c4d8f7cef8a7", + "packages": [ + { + "name": "laminas/laminas-servicemanager", + "version": "3.15.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-servicemanager.git", + "reference": "216f972b179191b14c33a79337947b63bf7808ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/216f972b179191b14c33a79337947b63bf7808ff", + "reference": "216f972b179191b14c33a79337947b63bf7808ff", + "shasum": "" + }, + "require": { + "laminas/laminas-stdlib": "^3.2.1", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0", + "psr/container": "^1.0" + }, + "conflict": { + "ext-psr": "*", + "laminas/laminas-code": "<3.3.1", + "zendframework/zend-code": "<3.3.1", + "zendframework/zend-servicemanager": "*" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "replace": { + "container-interop/container-interop": "^1.2.0" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.0", + "laminas/laminas-coding-standard": "~2.3.0", + "laminas/laminas-container-config-test": "^0.6", + "laminas/laminas-dependency-plugin": "^2.1.2", + "mikey179/vfsstream": "^1.6.10@alpha", + "ocramius/proxy-manager": "^2.11", + "phpbench/phpbench": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5.5", + "psalm/plugin-phpunit": "^0.17.0", + "vimeo/psalm": "^4.8" + }, + "suggest": { + "ocramius/proxy-manager": "ProxyManager ^2.1.1 to handle lazy initialization of services" + }, + "bin": [ + "bin/generate-deps-for-config-factory", + "bin/generate-factory-for-class" + ], + "type": "library", + "autoload": { + "files": [ + "src/autoload.php" + ], + "psr-4": { + "Laminas\\ServiceManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Factory-Driven Dependency Injection Container", + "homepage": "https://laminas.dev", + "keywords": [ + "PSR-11", + "dependency-injection", + "di", + "dic", + "laminas", + "service-manager", + "servicemanager" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-servicemanager/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-servicemanager/issues", + "rss": "https://github.com/laminas/laminas-servicemanager/releases.atom", + "source": "https://github.com/laminas/laminas-servicemanager" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-07-20T09:48:45+00:00" + }, + { + "name": "laminas/laminas-stdlib", + "version": "3.10.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-stdlib.git", + "reference": "0d669074845fc80a99add0f64025192f143ef836" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/0d669074845fc80a99add0f64025192f143ef836", + "reference": "0d669074845fc80a99add0f64025192f143ef836", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-stdlib": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.3.0", + "phpbench/phpbench": "^1.0", + "phpunit/phpunit": "^9.3.7", + "psalm/plugin-phpunit": "^0.16.0", + "vimeo/psalm": "^4.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Stdlib\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "SPL extensions, array utilities, error handlers, and more", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "stdlib" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-stdlib/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-stdlib/issues", + "rss": "https://github.com/laminas/laminas-stdlib/releases.atom", + "source": "https://github.com/laminas/laminas-stdlib" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-06-10T14:49:09+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + } + ], + "packages-dev": [ + { + "name": "amphp/amp", + "version": "v2.6.2", + "source": { + "type": "git", + "url": "https://github.com/amphp/amp.git", + "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/amp/zipball/9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^7 | ^8 | ^9", + "psalm/phar": "^3.11@dev", + "react/promise": "^2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "files": [ + "lib/functions.php", + "lib/Internal/functions.php" + ], + "psr-4": { + "Amp\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "https://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v2.6.2" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2022-02-20T17:52:18+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v1.8.1", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", + "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", + "shasum": "" + }, + "require": { + "amphp/amp": "^2", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.4", + "friendsofphp/php-cs-fixer": "^2.3", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6 || ^7 || ^8", + "psalm/phar": "^3.11.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "files": [ + "lib/functions.php" + ], + "psr-4": { + "Amp\\ByteStream\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "http://amphp.org/byte-stream", + "keywords": [ + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "support": { + "irc": "irc://irc.freenode.org/amphp", + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2021-03-30T17:13:30+00:00" + }, + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99.5", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-01-17T14:14:24+00:00" + }, + { + "name": "composer/pcre", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/e300eb6c535192decd27a85bc72a9290f0d6b3bd", + "reference": "e300eb6c535192decd27a85bc72a9290f0d6b3bd", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.0.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-02-25T20:21:48+00:00" + }, + { + "name": "composer/semver", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", + "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-04-01T19:23:25+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "ced299686f41dce890debac69273b47ffe98a40c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", + "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-02-25T21:32:43+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.2", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2022-02-04T12:51:07+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-03-03T08:28:38+00:00" + }, + { + "name": "felixfbecker/advanced-json-rpc", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "php": "^7.1 || ^8.0", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "A more advanced JSONRPC implementation", + "support": { + "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", + "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" + }, + "time": "2021-06-11T22:34:44+00:00" + }, + { + "name": "felixfbecker/language-server-protocol", + "version": "v1.5.2", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", + "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1", + "vimeo/psalm": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "PHP classes for the Language Server Protocol", + "keywords": [ + "language", + "microsoft", + "php", + "server" + ], + "support": { + "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" + }, + "time": "2022-03-02T22:36:06+00:00" + }, + { + "name": "laminas/laminas-coding-standard", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-coding-standard.git", + "reference": "bcf6e07fe4690240be7beb6d884d0b0fafa6a251" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-coding-standard/zipball/bcf6e07fe4690240be7beb6d884d0b0fafa6a251", + "reference": "bcf6e07fe4690240be7beb6d884d0b0fafa6a251", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "php": "^7.3 || ^8.0", + "slevomat/coding-standard": "^7.0", + "squizlabs/php_codesniffer": "^3.6", + "webimpress/coding-standard": "^1.2" + }, + "type": "phpcodesniffer-standard", + "autoload": { + "psr-4": { + "LaminasCodingStandard\\": "src/LaminasCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Laminas Coding Standard", + "homepage": "https://laminas.dev", + "keywords": [ + "Coding Standard", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-coding-standard/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-coding-standard/issues", + "rss": "https://github.com/laminas/laminas-coding-standard/releases.atom", + "source": "https://github.com/laminas/laminas-coding-standard" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-05-29T15:53:59+00:00" + }, + { + "name": "laminas/laminas-db", + "version": "2.15.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-db.git", + "reference": "1125ef2e55108bdfcc1f0030d3a0f9b895e09606" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-db/zipball/1125ef2e55108bdfcc1f0030d3a0f9b895e09606", + "reference": "1125ef2e55108bdfcc1f0030d3a0f9b895e09606", + "shasum": "" + }, + "require": { + "laminas/laminas-stdlib": "^3.7.1", + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-db": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.2.1", + "laminas/laminas-eventmanager": "^3.4.0", + "laminas/laminas-hydrator": "^3.2 || ^4.3", + "laminas/laminas-servicemanager": "^3.7.0", + "phpunit/phpunit": "^9.5.19" + }, + "suggest": { + "laminas/laminas-eventmanager": "Laminas\\EventManager component", + "laminas/laminas-hydrator": "(^3.2 || ^4.3) Laminas\\Hydrator component for using HydratingResultSets", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component" + }, + "type": "library", + "extra": { + "laminas": { + "component": "Laminas\\Db", + "config-provider": "Laminas\\Db\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Db\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Database abstraction layer, SQL abstraction, result set abstraction, and RowDataGateway and TableDataGateway implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "db", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-db/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-db/issues", + "rss": "https://github.com/laminas/laminas-db/releases.atom", + "source": "https://github.com/laminas/laminas-db" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-04-11T13:26:20+00:00" + }, + { + "name": "laminas/laminas-escaper", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-escaper.git", + "reference": "58af67282db37d24e584a837a94ee55b9c7552be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/58af67282db37d24e584a837a94ee55b9c7552be", + "reference": "58af67282db37d24e584a837a94ee55b9c7552be", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-mbstring": "*", + "php": "^7.4 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-escaper": "*" + }, + "require-dev": { + "infection/infection": "^0.26.6", + "laminas/laminas-coding-standard": "~2.3.0", + "maglnet/composer-require-checker": "^3.8.0", + "phpunit/phpunit": "^9.5.18", + "psalm/plugin-phpunit": "^0.16.1", + "vimeo/psalm": "^4.22.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Escaper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", + "homepage": "https://laminas.dev", + "keywords": [ + "escaper", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-escaper/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-escaper/issues", + "rss": "https://github.com/laminas/laminas-escaper/releases.atom", + "source": "https://github.com/laminas/laminas-escaper" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-03-08T20:15:36+00:00" + }, + { + "name": "laminas/laminas-eventmanager", + "version": "3.5.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-eventmanager.git", + "reference": "41f7209428f37cab9573365e361f4078209aaafa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-eventmanager/zipball/41f7209428f37cab9573365e361f4078209aaafa", + "reference": "41f7209428f37cab9573365e361f4078209aaafa", + "shasum": "" + }, + "require": { + "php": "^7.4 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "container-interop/container-interop": "<1.2", + "zendframework/zend-eventmanager": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.2.1", + "laminas/laminas-stdlib": "^3.6", + "phpbench/phpbench": "^1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5.5", + "psr/container": "^1.1.2 || ^2.0.2" + }, + "suggest": { + "laminas/laminas-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature", + "psr/container": "^1.1.2 || ^2.0.2, to use the lazy listeners feature" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\EventManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Trigger and listen to events within a PHP application", + "homepage": "https://laminas.dev", + "keywords": [ + "event", + "eventmanager", + "events", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-eventmanager/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-eventmanager/issues", + "rss": "https://github.com/laminas/laminas-eventmanager/releases.atom", + "source": "https://github.com/laminas/laminas-eventmanager" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-04-06T21:05:17+00:00" + }, + { + "name": "laminas/laminas-filter", + "version": "2.17.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-filter.git", + "reference": "7c78483f22e118fbc98be41dc24b39e8c17cdcae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-filter/zipball/7c78483f22e118fbc98be41dc24b39e8c17cdcae", + "reference": "7c78483f22e118fbc98be41dc24b39e8c17cdcae", + "shasum": "" + }, + "require": { + "laminas/laminas-servicemanager": "^3.14.0", + "laminas/laminas-stdlib": "^3.6.1", + "php": "^7.4 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "laminas/laminas-validator": "<2.10.1", + "zendframework/zend-filter": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "^2.3.0", + "laminas/laminas-crypt": "^3.5.1", + "laminas/laminas-uri": "^2.9.1", + "pear/archive_tar": "^1.4.14", + "phpspec/prophecy-phpunit": "^2.0.1", + "phpunit/phpunit": "^9.5.10", + "psalm/plugin-phpunit": "^0.17.0", + "psr/http-factory": "^1.0.1", + "vimeo/psalm": "^4.24.0" + }, + "suggest": { + "laminas/laminas-crypt": "Laminas\\Crypt component, for encryption filters", + "laminas/laminas-i18n": "Laminas\\I18n component for filters depending on i18n functionality", + "laminas/laminas-uri": "Laminas\\Uri component, for the UriNormalize filter", + "psr/http-factory-implementation": "psr/http-factory-implementation, for creating file upload instances when consuming PSR-7 in file upload filters" + }, + "type": "library", + "extra": { + "laminas": { + "component": "Laminas\\Filter", + "config-provider": "Laminas\\Filter\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Filter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Programmatically filter and normalize data and files", + "homepage": "https://laminas.dev", + "keywords": [ + "filter", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-filter/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-filter/issues", + "rss": "https://github.com/laminas/laminas-filter/releases.atom", + "source": "https://github.com/laminas/laminas-filter" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-07-17T11:58:06+00:00" + }, + { + "name": "laminas/laminas-http", + "version": "2.15.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-http.git", + "reference": "261f079c3dffcf6f123484db43c40e44c4bf1c79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-http/zipball/261f079c3dffcf6f123484db43c40e44c4bf1c79", + "reference": "261f079c3dffcf6f123484db43c40e44c4bf1c79", + "shasum": "" + }, + "require": { + "laminas/laminas-loader": "^2.8", + "laminas/laminas-stdlib": "^3.6", + "laminas/laminas-uri": "^2.9.1", + "laminas/laminas-validator": "^2.15", + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-http": "*" + }, + "require-dev": { + "ext-curl": "*", + "laminas/laminas-coding-standard": "~2.2.1", + "phpunit/phpunit": "^9.5.5" + }, + "suggest": { + "paragonie/certainty": "For automated management of cacert.pem" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Http\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Provides an easy interface for performing Hyper-Text Transfer Protocol (HTTP) requests", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "http client", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-http/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-http/issues", + "rss": "https://github.com/laminas/laminas-http/releases.atom", + "source": "https://github.com/laminas/laminas-http" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-12-03T10:17:11+00:00" + }, + { + "name": "laminas/laminas-i18n", + "version": "2.16.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-i18n.git", + "reference": "e77c04c050b7744125d26e213b2639c86d4514ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-i18n/zipball/e77c04c050b7744125d26e213b2639c86d4514ad", + "reference": "e77c04c050b7744125d26e213b2639c86d4514ad", + "shasum": "" + }, + "require": { + "ext-intl": "*", + "laminas/laminas-servicemanager": "^3.14.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "php": "^7.4 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "laminas/laminas-view": "<2.20.0", + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-i18n": "*" + }, + "require-dev": { + "laminas/laminas-cache": "^3.1.2", + "laminas/laminas-cache-storage-adapter-memory": "^2.0.0", + "laminas/laminas-cache-storage-deprecated-factory": "^1.0.0", + "laminas/laminas-coding-standard": "~2.3.0", + "laminas/laminas-config": "^3.4.0", + "laminas/laminas-eventmanager": "^3.5.0", + "laminas/laminas-filter": "^2.16.0", + "laminas/laminas-validator": "^2.17.0", + "laminas/laminas-view": "^2.21.0", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5.21", + "psalm/plugin-phpunit": "^0.17.0", + "vimeo/psalm": "^4.24.0" + }, + "suggest": { + "laminas/laminas-cache": "You should install this package to cache the translations", + "laminas/laminas-config": "You should install this package to use the INI translation format", + "laminas/laminas-eventmanager": "You should install this package to use the events in the translator", + "laminas/laminas-filter": "You should install this package to use the provided filters", + "laminas/laminas-i18n-resources": "This package provides validator and captcha translations", + "laminas/laminas-servicemanager": "You should install this package to use the translator", + "laminas/laminas-validator": "You should install this package to use the provided validators", + "laminas/laminas-view": "You should install this package to use the provided view helpers" + }, + "type": "library", + "extra": { + "laminas": { + "component": "Laminas\\I18n", + "config-provider": "Laminas\\I18n\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\I18n\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Provide translations for your application, and filter and validate internationalized values", + "homepage": "https://laminas.dev", + "keywords": [ + "i18n", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-i18n/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-i18n/issues", + "rss": "https://github.com/laminas/laminas-i18n/releases.atom", + "source": "https://github.com/laminas/laminas-i18n" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-07-12T22:15:49+00:00" + }, + { + "name": "laminas/laminas-loader", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-loader.git", + "reference": "d0589ec9dd48365fd95ad10d1c906efd7711c16b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-loader/zipball/d0589ec9dd48365fd95ad10d1c906efd7711c16b", + "reference": "d0589ec9dd48365fd95ad10d1c906efd7711c16b", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-loader": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.2.1", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Loader\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Autoloading and plugin loading strategies", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "loader" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-loader/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-loader/issues", + "rss": "https://github.com/laminas/laminas-loader/releases.atom", + "source": "https://github.com/laminas/laminas-loader" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-02T18:30:53+00:00" + }, + { + "name": "laminas/laminas-session", + "version": "2.13.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-session.git", + "reference": "9f8a6077dd22b3b253583b1be84ddd5bf6fa1ef4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-session/zipball/9f8a6077dd22b3b253583b1be84ddd5bf6fa1ef4", + "reference": "9f8a6077dd22b3b253583b1be84ddd5bf6fa1ef4", + "shasum": "" + }, + "require": { + "laminas/laminas-eventmanager": "^3.5", + "laminas/laminas-servicemanager": "^3.15.1", + "laminas/laminas-stdlib": "^3.10.1", + "php": "^7.4 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-session": "*" + }, + "require-dev": { + "laminas/laminas-cache": "^3.1.3", + "laminas/laminas-cache-storage-adapter-memory": "^2.0.0", + "laminas/laminas-coding-standard": "~2.3.0", + "laminas/laminas-db": "^2.13.4", + "laminas/laminas-http": "^2.15", + "laminas/laminas-validator": "^2.15", + "mongodb/mongodb": "~1.12.0", + "php-mock/php-mock-phpunit": "^1.1.2 || ^2.0", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5.9", + "psalm/plugin-phpunit": "^0.17.0", + "vimeo/psalm": "^4.24.0" + }, + "suggest": { + "laminas/laminas-cache": "Laminas\\Cache component", + "laminas/laminas-db": "Laminas\\Db component", + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-validator": "Laminas\\Validator component", + "mongodb/mongodb": "If you want to use the MongoDB session save handler" + }, + "type": "library", + "extra": { + "laminas": { + "component": "Laminas\\Session", + "config-provider": "Laminas\\Session\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Session\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Object-oriented interface to PHP sessions and storage", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "session" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-session/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-session/issues", + "rss": "https://github.com/laminas/laminas-session/releases.atom", + "source": "https://github.com/laminas/laminas-session" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-07-22T10:26:33+00:00" + }, + { + "name": "laminas/laminas-uri", + "version": "2.9.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-uri.git", + "reference": "7e837dc15c8fd3949df7d1213246fd7c8640032b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-uri/zipball/7e837dc15c8fd3949df7d1213246fd7c8640032b", + "reference": "7e837dc15c8fd3949df7d1213246fd7c8640032b", + "shasum": "" + }, + "require": { + "laminas/laminas-escaper": "^2.9", + "laminas/laminas-validator": "^2.15", + "php": "^7.3 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-uri": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.2.1", + "phpunit/phpunit": "^9.5.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Uri\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "A component that aids in manipulating and validating » Uniform Resource Identifiers (URIs)", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "uri" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-uri/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-uri/issues", + "rss": "https://github.com/laminas/laminas-uri/releases.atom", + "source": "https://github.com/laminas/laminas-uri" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2021-09-09T18:37:15+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2022-03-03T13:19:32+00:00" + }, + { + "name": "netresearch/jsonmapper", + "version": "v4.0.0", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", + "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v4.0.0" + }, + "time": "2020-12-01T19:48:11+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.14.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0" + }, + "time": "2022-05-31T20:59:12+00:00" + }, + { + "name": "openlss/lib-array2xml", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/nullivex/lib-array2xml.git", + "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nullivex/lib-array2xml/zipball/a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", + "reference": "a91f18a8dfc69ffabe5f9b068bc39bb202c81d90", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "autoload": { + "psr-0": { + "LSS": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Bryan Tong", + "email": "bryan@nullivex.com", + "homepage": "https://www.nullivex.com" + }, + { + "name": "Tony Butler", + "email": "spudz76@gmail.com", + "homepage": "https://www.nullivex.com" + } + ], + "description": "Array2XML conversion library credit to lalit.org", + "homepage": "https://www.nullivex.com", + "keywords": [ + "array", + "array conversion", + "xml", + "xml conversion" + ], + "support": { + "issues": "https://github.com/nullivex/lib-array2xml/issues", + "source": "https://github.com/nullivex/lib-array2xml/tree/master" + }, + "time": "2019-03-29T20:06:56+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + }, + "time": "2021-10-19T17:43:47+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "77a32518733312af16a44300404e945338981de3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", + "reference": "77a32518733312af16a44300404e945338981de3", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "psalm/phar": "^4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" + }, + "time": "2022-03-15T21:29:03+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.2", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0 || ^7.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" + }, + "time": "2021-12-08T12:19:24+00:00" + }, + { + "name": "phpspec/prophecy-phpunit", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy-phpunit.git", + "reference": "2d7a9df55f257d2cba9b1d0c0963a54960657177" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy-phpunit/zipball/2d7a9df55f257d2cba9b1d0c0963a54960657177", + "reference": "2d7a9df55f257d2cba9b1d0c0963a54960657177", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8", + "phpspec/prophecy": "^1.3", + "phpunit/phpunit": "^9.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\PhpUnit\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christophe Coevoet", + "email": "stof@notk.org" + } + ], + "description": "Integrating the Prophecy mocking library in PHPUnit test cases", + "homepage": "http://phpspec.net", + "keywords": [ + "phpunit", + "prophecy" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy-phpunit/issues", + "source": "https://github.com/phpspec/prophecy-phpunit/tree/v2.0.1" + }, + "time": "2020-07-09T08:33:42+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.6.4", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "135607f9ccc297d6923d49c2bcf309f509413215" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/135607f9ccc297d6923d49c2bcf309f509413215", + "reference": "135607f9ccc297d6923d49c2bcf309f509413215", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.6.4" + }, + "time": "2022-06-26T13:09:08+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.15", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.13.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-03-07T09:28:20+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.5.21", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0e32b76be457de00e83213528f6bb37e2a38fcb1", + "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpspec/prophecy": "^1.12.1", + "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.5", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.3", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.0", + "sebastian/version": "^3.0.2" + }, + "require-dev": { + "phpspec/prophecy-phpunit": "^2.0.1" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.21" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-06-19T12:14:25+00:00" + }, + { + "name": "psalm/plugin-phpunit", + "version": "0.17.0", + "source": { + "type": "git", + "url": "https://github.com/psalm/psalm-plugin-phpunit.git", + "reference": "45951541beef07e93e3ad197daf01da88e85c31d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/psalm/psalm-plugin-phpunit/zipball/45951541beef07e93e3ad197daf01da88e85c31d", + "reference": "45951541beef07e93e3ad197daf01da88e85c31d", + "shasum": "" + }, + "require": { + "composer/package-versions-deprecated": "^1.10", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "ext-simplexml": "*", + "php": "^7.1 || ^8.0", + "vimeo/psalm": "dev-master || dev-4.x || ^4.5" + }, + "conflict": { + "phpunit/phpunit": "<7.5" + }, + "require-dev": { + "codeception/codeception": "^4.0.3", + "php": "^7.3 || ^8.0", + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.3.1", + "weirdan/codeception-psalm-module": "^0.11.0", + "weirdan/prophecy-shim": "^1.0 || ^2.0" + }, + "type": "psalm-plugin", + "extra": { + "psalm": { + "pluginClass": "Psalm\\PhpUnitPlugin\\Plugin" + } + }, + "autoload": { + "psr-4": { + "Psalm\\PhpUnitPlugin\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Brown", + "email": "github@muglug.com" + } + ], + "description": "Psalm plugin for PHPUnit", + "support": { + "issues": "https://github.com/psalm/psalm-plugin-phpunit/issues", + "source": "https://github.com/psalm/psalm-plugin-phpunit/tree/0.17.0" + }, + "time": "2022-06-14T17:05:57+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:49:45+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:10:38+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-04-03T09:37:03+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-11-11T14:18:36+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-02-14T08:28:10+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:17:30+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-03-15T09:54:48+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "slevomat/coding-standard", + "version": "7.2.1", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "aff06ae7a84e4534bf6f821dc982a93a5d477c90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/aff06ae7a84e4534bf6f821dc982a93a5d477c90", + "reference": "aff06ae7a84e4534bf6f821dc982a93a5d477c90", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.2 || ^8.0", + "phpstan/phpdoc-parser": "^1.5.1", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phing/phing": "2.17.3", + "php-parallel-lint/php-parallel-lint": "1.3.2", + "phpstan/phpstan": "1.4.10|1.7.1", + "phpstan/phpstan-deprecation-rules": "1.0.0", + "phpstan/phpstan-phpunit": "1.0.0|1.1.1", + "phpstan/phpstan-strict-rules": "1.2.3", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.2.1" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2022-05-25T10:58:12+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.7.1", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619", + "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2022-06-18T07:21:10+00:00" + }, + { + "name": "symfony/console", + "version": "v5.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "4d671ab4ddac94ee439ea73649c69d9d200b5000" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/4d671ab4ddac94ee439ea73649c69d9d200b5000", + "reference": "4d671ab4ddac94ee439ea73649c69d9d200b5000", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.4.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-26T13:00:04+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "433d05519ce6990bf3530fba6957499d327395c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", + "reference": "433d05519ce6990bf3530fba6957499d327395c2", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-10T07:21:04+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-30T19:17:29+00:00" + }, + { + "name": "symfony/string", + "version": "v5.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "4432bc7df82a554b3e413a8570ce2fea90e94097" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/4432bc7df82a554b3e413a8570ce2fea90e94097", + "reference": "4432bc7df82a554b3e413a8570ce2fea90e94097", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-26T15:57:47+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + }, + { + "name": "vimeo/psalm", + "version": "4.24.0", + "source": { + "type": "git", + "url": "https://github.com/vimeo/psalm.git", + "reference": "06dd975cb55d36af80f242561738f16c5f58264f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/06dd975cb55d36af80f242561738f16c5f58264f", + "reference": "06dd975cb55d36af80f242561738f16c5f58264f", + "shasum": "" + }, + "require": { + "amphp/amp": "^2.4.2", + "amphp/byte-stream": "^1.5", + "composer/package-versions-deprecated": "^1.8.0", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^1.1 || ^2.0 || ^3.0", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "ext-ctype": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "felixfbecker/advanced-json-rpc": "^3.0.3", + "felixfbecker/language-server-protocol": "^1.5", + "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", + "nikic/php-parser": "^4.13", + "openlss/lib-array2xml": "^1.0", + "php": "^7.1|^8", + "sebastian/diff": "^3.0 || ^4.0", + "symfony/console": "^3.4.17 || ^4.1.6 || ^5.0 || ^6.0", + "symfony/polyfill-php80": "^1.25", + "webmozart/path-util": "^2.3" + }, + "provide": { + "psalm/psalm": "self.version" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2", + "brianium/paratest": "^4.0||^6.0", + "ext-curl": "*", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpdocumentor/reflection-docblock": "^5", + "phpmyadmin/sql-parser": "5.1.0||dev-master", + "phpspec/prophecy": ">=1.9.0", + "phpunit/phpunit": "^9.0", + "psalm/plugin-phpunit": "^0.16", + "slevomat/coding-standard": "^7.0", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.3 || ^5.0 || ^6.0", + "weirdan/prophecy-shim": "^1.0 || ^2.0" + }, + "suggest": { + "ext-curl": "In order to send data to shepherd", + "ext-igbinary": "^2.0.5 is required, used to serialize caching data" + }, + "bin": [ + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalter" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev", + "dev-3.x": "3.x-dev", + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php", + "src/spl_object_id.php" + ], + "psr-4": { + "Psalm\\": "src/Psalm/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Brown" + } + ], + "description": "A static analysis tool for finding errors in PHP applications", + "keywords": [ + "code", + "inspection", + "php" + ], + "support": { + "issues": "https://github.com/vimeo/psalm/issues", + "source": "https://github.com/vimeo/psalm/tree/4.24.0" + }, + "time": "2022-06-26T11:47:54+00:00" + }, + { + "name": "webimpress/coding-standard", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/webimpress/coding-standard.git", + "reference": "cd0c4b0b97440c337c1f7da17b524674ca2f9ca9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webimpress/coding-standard/zipball/cd0c4b0b97440c337c1f7da17b524674ca2f9ca9", + "reference": "cd0c4b0b97440c337c1f7da17b524674ca2f9ca9", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0", + "squizlabs/php_codesniffer": "^3.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5.13" + }, + "type": "phpcodesniffer-standard", + "extra": { + "dev-master": "1.2.x-dev", + "dev-develop": "1.3.x-dev" + }, + "autoload": { + "psr-4": { + "WebimpressCodingStandard\\": "src/WebimpressCodingStandard/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "description": "Webimpress Coding Standard", + "keywords": [ + "Coding Standard", + "PSR-2", + "phpcs", + "psr-12", + "webimpress" + ], + "support": { + "issues": "https://github.com/webimpress/coding-standard/issues", + "source": "https://github.com/webimpress/coding-standard/tree/1.2.4" + }, + "funding": [ + { + "url": "https://github.com/michalbundyra", + "type": "github" + } + ], + "time": "2022-02-15T19:52:12+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + }, + { + "name": "webmozart/path-util", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/path-util.git", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/path-util/zipball/d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "reference": "d939f7edc24c9a1bb9c0dee5cb05d8e859490725", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "webmozart/assert": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\PathUtil\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "A robust cross-platform utility for normalizing, comparing and modifying file paths.", + "support": { + "issues": "https://github.com/webmozart/path-util/issues", + "source": "https://github.com/webmozart/path-util/tree/2.3.0" + }, + "abandoned": "symfony/filesystem", + "time": "2015-12-17T08:42:14+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^7.4 || ~8.0.0 || ~8.1.0" + }, + "platform-dev": [], + "platform-overrides": { + "php": "7.4.99" + }, + "plugin-api-version": "2.3.0" +} diff --git a/lib/laminas/laminas-validator/renovate.json b/lib/laminas/laminas-validator/renovate.json new file mode 100644 index 0000000000..060b1d1a28 --- /dev/null +++ b/lib/laminas/laminas-validator/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "local>laminas/.github:renovate-config" + ] +} diff --git a/lib/laminas/laminas-validator/src/AbstractValidator.php b/lib/laminas/laminas-validator/src/AbstractValidator.php new file mode 100644 index 0000000000..d7f3ccd3c9 --- /dev/null +++ b/lib/laminas/laminas-validator/src/AbstractValidator.php @@ -0,0 +1,623 @@ +, + * messageTemplates: array, + * messageVariables: array, + * translator: Translator\TranslatorInterface|null, + * translatorTextDomain: string|null, + * translatorEnabled: bool, + * valueObscured: bool, + * } + * @property array $options + * @property array $messageTemplates + * @property array $messageVariables + */ +abstract class AbstractValidator implements + Translator\TranslatorAwareInterface, + ValidatorInterface +{ + /** + * The value to be validated + * + * @var mixed + */ + protected $value; + + /** + * Default translation object for all validate objects + * + * @var Translator\TranslatorInterface + */ + protected static $defaultTranslator; + + /** + * Default text domain to be used with translator + * + * @var string + */ + protected static $defaultTranslatorTextDomain = 'default'; + + /** + * Limits the maximum returned length of an error message + * + * @var int + */ + protected static $messageLength = -1; + + /** @var AbstractOptions&array */ + protected $abstractOptions = [ + 'messages' => [], // Array of validation failure messages + 'messageTemplates' => [], // Array of validation failure message templates + 'messageVariables' => [], // Array of additional variables available for validation failure messages + 'translator' => null, // Translation object to used -> Translator\TranslatorInterface + 'translatorTextDomain' => null, // Translation text domain + 'translatorEnabled' => true, // Is translation enabled? + 'valueObscured' => false, // Flag indicating whether value should be obfuscated in error messages + ]; + + /** + * Abstract constructor for all validators + * A validator should accept following parameters: + * - nothing f.e. Validator() + * - one or multiple scalar values f.e. Validator($first, $second, $third) + * - an array f.e. Validator(array($first => 'first', $second => 'second', $third => 'third')) + * - an instance of Traversable f.e. Validator($config_instance) + * + * @param array|Traversable $options + */ + public function __construct($options = null) + { + // The abstract constructor allows no scalar values + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + + /** @psalm-suppress RedundantConditionGivenDocblockType */ + if (isset($this->messageTemplates) && is_array($this->messageTemplates)) { + $this->abstractOptions['messageTemplates'] = $this->messageTemplates; + } + + /** @psalm-suppress RedundantConditionGivenDocblockType */ + if (isset($this->messageVariables) && is_array($this->messageVariables)) { + $this->abstractOptions['messageVariables'] = $this->messageVariables; + } + + if (is_array($options)) { + $this->setOptions($options); + } + } + + /** + * Returns an option + * + * @param string $option Option to be returned + * @return mixed Returned option + * @throws Exception\InvalidArgumentException + */ + public function getOption($option) + { + if (array_key_exists($option, $this->abstractOptions)) { + return $this->abstractOptions[$option]; + } + + /** @psalm-suppress RedundantConditionGivenDocblockType */ + if (isset($this->options) && array_key_exists($option, $this->options)) { + return $this->options[$option]; + } + + throw new Exception\InvalidArgumentException("Invalid option '$option'"); + } + + /** + * Returns all available options + * + * @return array Array with all available options + */ + public function getOptions() + { + $result = $this->abstractOptions; + /** @psalm-suppress RedundantConditionGivenDocblockType */ + if (isset($this->options) && is_array($this->options)) { + $result += $this->options; + } + return $result; + } + + /** + * Sets one or multiple options + * + * @param array|Traversable $options Options to set + * @return $this Provides fluid interface + * @throws Exception\InvalidArgumentException If $options is not an array or Traversable. + */ + public function setOptions($options = []) + { + /** @psalm-suppress DocblockTypeContradiction */ + if (! is_array($options) && ! $options instanceof Traversable) { + throw new Exception\InvalidArgumentException(__METHOD__ . ' expects an array or Traversable'); + } + + /** + * @psalm-suppress RedundantConditionGivenDocblockType + * @psalm-var mixed $option + */ + foreach ($options as $name => $option) { + $fname = 'set' . ucfirst($name); + $fname2 = 'is' . ucfirst($name); + if (($name !== 'setOptions') && method_exists($this, $name)) { + $this->{$name}($option); + } elseif (($fname !== 'setOptions') && method_exists($this, $fname)) { + $this->{$fname}($option); + } elseif (method_exists($this, $fname2)) { + $this->{$fname2}($option); + } elseif (isset($this->options) && is_array($this->options)) { + $this->options[$name] = $option; + } else { + $this->abstractOptions[$name] = $option; + } + } + + return $this; + } + + /** + * Returns array of validation failure messages + * + * @return array + */ + public function getMessages() + { + return array_unique($this->abstractOptions['messages'], SORT_REGULAR); + } + + /** + * Invoke as command + * + * @param mixed $value + * @return bool + */ + public function __invoke($value) + { + return $this->isValid($value); + } + + /** + * Returns an array of the names of variables that are used in constructing validation failure messages + * + * @return list + */ + public function getMessageVariables() + { + return array_keys($this->abstractOptions['messageVariables']); + } + + /** + * Returns the message templates from the validator + * + * @return array + */ + public function getMessageTemplates() + { + return $this->abstractOptions['messageTemplates']; + } + + /** + * Sets the validation failure message template for a particular key + * + * @param string $messageString + * @param string|null $messageKey OPTIONAL + * @return $this Provides a fluent interface + * @throws Exception\InvalidArgumentException + */ + public function setMessage($messageString, $messageKey = null) + { + if ($messageKey === null) { + $keys = array_keys($this->abstractOptions['messageTemplates']); + foreach ($keys as $key) { + $this->setMessage($messageString, $key); + } + return $this; + } + + if (! isset($this->abstractOptions['messageTemplates'][$messageKey])) { + throw new Exception\InvalidArgumentException("No message template exists for key '$messageKey'"); + } + + $this->abstractOptions['messageTemplates'][$messageKey] = $messageString; + return $this; + } + + /** + * Sets validation failure message templates given as an array, where the array keys are the message keys, + * and the array values are the message template strings. + * + * @param array $messages + * @return $this + */ + public function setMessages(array $messages) + { + foreach ($messages as $key => $message) { + $this->setMessage($message, $key); + } + return $this; + } + + /** + * Magic function returns the value of the requested property, if and only if it is the value or a + * message variable. + * + * @param string $property + * @return mixed + * @throws Exception\InvalidArgumentException + */ + public function __get($property) + { + if ($property === 'value') { + return $this->value; + } + + if (array_key_exists($property, $this->abstractOptions['messageVariables'])) { + /** @psalm-var mixed $result */ + $result = $this->abstractOptions['messageVariables'][$property]; + if (is_array($result)) { + return $this->{key($result)}[current($result)]; + } + return $this->{$result}; + } + + /** @psalm-suppress RedundantConditionGivenDocblockType */ + if (isset($this->messageVariables) && array_key_exists($property, $this->messageVariables)) { + /** @psalm-var mixed $result */ + $result = $this->{$this->messageVariables[$property]}; + if (is_array($result)) { + return $this->{key($result)}[current($result)]; + } + return $this->{$result}; + } + + throw new Exception\InvalidArgumentException("No property exists by the name '$property'"); + } + + /** + * Constructs and returns a validation failure message with the given message key and value. + * + * Returns null if and only if $messageKey does not correspond to an existing template. + * + * If a translator is available and a translation exists for $messageKey, + * the translation will be used. + * + * @param string $messageKey + * @param string|array|object $value + * @return null|string + */ + protected function createMessage($messageKey, $value) + { + if (! isset($this->abstractOptions['messageTemplates'][$messageKey])) { + return null; + } + + $message = $this->abstractOptions['messageTemplates'][$messageKey]; + + $message = $this->translateMessage($messageKey, $message); + + if (is_object($value)) { + $value = method_exists($value, '__toString') + ? (string) $value + : get_class($value) . ' object'; + } elseif (is_array($value)) { + $value = var_export($value, true); + } else { + /** @psalm-suppress RedundantCastGivenDocblockType $value */ + $value = (string) $value; + } + + if ($this->isValueObscured()) { + $value = str_repeat('*', strlen($value)); + } + + $message = str_replace('%value%', $value, $message); + foreach ($this->abstractOptions['messageVariables'] as $ident => $property) { + if (is_array($property)) { + $value = $this->{key($property)}[current($property)]; + if (is_array($value)) { + $value = '[' . implode(', ', $value) . ']'; + } + } else { + $value = $this->$property; + } + $message = str_replace("%$ident%", (string) $value, $message); + } + + $length = self::getMessageLength(); + if (($length > -1) && (strlen($message) > $length)) { + $message = substr($message, 0, $length - 3) . '...'; + } + + return $message; + } + + /** + * @param string|null $messageKey + * @param null|string|array|object $value OPTIONAL + * @return void + */ + protected function error($messageKey, $value = null) + { + if ($messageKey === null) { + $keys = array_keys($this->abstractOptions['messageTemplates']); + $messageKey = current($keys); + } + + if ($value === null) { + /** @psalm-var string|array|object $value */ + $value = $this->value; + } + + $message = $this->createMessage($messageKey, $value); + if (! is_string($message)) { + return; + } + + $this->abstractOptions['messages'][$messageKey] = $message; + } + + /** + * Returns the validation value + * + * @return mixed Value to be validated + */ + protected function getValue() + { + return $this->value; + } + + /** + * Sets the value to be validated and clears the messages and errors arrays + * + * @param mixed $value + * @return void + */ + protected function setValue($value) + { + $this->value = $value; + $this->abstractOptions['messages'] = []; + } + + /** + * Set flag indicating whether or not value should be obfuscated in messages + * + * @param bool $flag + * @return $this + */ + public function setValueObscured($flag) + { + /** @psalm-suppress RedundantCastGivenDocblockType */ + $this->abstractOptions['valueObscured'] = (bool) $flag; + return $this; + } + + /** + * Retrieve flag indicating whether or not value should be obfuscated in + * messages + * + * @return bool + */ + public function isValueObscured() + { + return $this->abstractOptions['valueObscured']; + } + + /** + * Set translation object + * + * @param string $textDomain (optional) + * @return $this + * @throws Exception\InvalidArgumentException + */ + public function setTranslator(?Translator\TranslatorInterface $translator = null, $textDomain = null) + { + $this->abstractOptions['translator'] = $translator; + if (null !== $textDomain) { + $this->setTranslatorTextDomain($textDomain); + } + return $this; + } + + /** + * Return translation object + * + * @return Translator\TranslatorInterface|null + */ + public function getTranslator() + { + if (! $this->isTranslatorEnabled()) { + return null; + } + + if (null === $this->abstractOptions['translator']) { + $this->abstractOptions['translator'] = self::getDefaultTranslator(); + } + + return $this->abstractOptions['translator']; + } + + /** + * Does this validator have its own specific translator? + * + * @return bool + */ + public function hasTranslator() + { + return (bool) $this->abstractOptions['translator']; + } + + /** + * Set translation text domain + * + * @param string $textDomain + * @return $this + */ + public function setTranslatorTextDomain($textDomain = 'default') + { + $this->abstractOptions['translatorTextDomain'] = $textDomain; + return $this; + } + + /** + * Return the translation text domain + * + * @return string + */ + public function getTranslatorTextDomain() + { + if (null === $this->abstractOptions['translatorTextDomain']) { + $this->abstractOptions['translatorTextDomain'] = + self::getDefaultTranslatorTextDomain(); + } + return $this->abstractOptions['translatorTextDomain']; + } + + /** + * Set default translation object for all validate objects + * + * @param string $textDomain (optional) + * @return void + * @throws Exception\InvalidArgumentException + */ + public static function setDefaultTranslator(?Translator\TranslatorInterface $translator = null, $textDomain = null) + { + static::$defaultTranslator = $translator; + if (null !== $textDomain) { + self::setDefaultTranslatorTextDomain($textDomain); + } + } + + /** + * Get default translation object for all validate objects + * + * @return Translator\TranslatorInterface|null + */ + public static function getDefaultTranslator() + { + return static::$defaultTranslator; + } + + /** + * Is there a default translation object set? + * + * @return bool + */ + public static function hasDefaultTranslator() + { + return (bool) static::$defaultTranslator; + } + + /** + * Set default translation text domain for all validate objects + * + * @param string $textDomain + * @return void + */ + public static function setDefaultTranslatorTextDomain($textDomain = 'default') + { + static::$defaultTranslatorTextDomain = $textDomain; + } + + /** + * Get default translation text domain for all validate objects + * + * @return string + */ + public static function getDefaultTranslatorTextDomain() + { + return static::$defaultTranslatorTextDomain; + } + + /** + * Indicate whether or not translation should be enabled + * + * @param bool $enabled + * @return $this + */ + public function setTranslatorEnabled($enabled = true) + { + /** @psalm-suppress RedundantCastGivenDocblockType */ + $this->abstractOptions['translatorEnabled'] = (bool) $enabled; + return $this; + } + + /** + * Is translation enabled? + * + * @return bool + */ + public function isTranslatorEnabled() + { + return $this->abstractOptions['translatorEnabled']; + } + + /** + * Returns the maximum allowed message length + * + * @return int + */ + public static function getMessageLength() + { + return static::$messageLength; + } + + /** + * Sets the maximum allowed message length + * + * @param int $length + * @return void + */ + public static function setMessageLength($length = -1) + { + static::$messageLength = $length; + } + + /** + * Translate a validation message + * + * @param string $messageKey + * @param string $message + * @return string + */ + protected function translateMessage($messageKey, $message) + { + $translator = $this->getTranslator(); + if (! $translator) { + return $message; + } + + return $translator->translate($message, $this->getTranslatorTextDomain()); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode.php b/lib/laminas/laminas-validator/src/Barcode.php new file mode 100644 index 0000000000..7ba7589af1 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode.php @@ -0,0 +1,199 @@ + */ + protected $messageTemplates = [ + self::FAILED => 'The input failed checksum validation', + self::INVALID_CHARS => 'The input contains invalid characters', + self::INVALID_LENGTH => 'The input should have a length of %length% characters', + self::INVALID => 'Invalid type given. String expected', + ]; + + /** + * Additional variables available for validation failure messages + * + * @var array> + */ + protected $messageVariables = [ + 'length' => ['options' => 'length'], + ]; + + /** @var array */ + protected $options = [ + 'adapter' => null, // Barcode adapter Laminas\Validator\Barcode\AbstractAdapter + 'options' => null, // Options for this adapter + 'length' => null, + 'useChecksum' => null, + ]; + + /** + * Constructor for barcodes + * + * @param array|string $options Options to use + */ + public function __construct($options = null) + { + if ($options === null) { + $options = []; + } + + if (is_array($options)) { + if (array_key_exists('options', $options)) { + $options['options'] = ['options' => $options['options']]; + } + } elseif ($options instanceof Traversable) { + if (property_exists($options, 'options')) { + $options['options'] = ['options' => $options['options']]; + } + } else { + $options = ['adapter' => $options]; + } + + parent::__construct($options); + } + + /** + * Returns the set adapter + * + * @return Barcode\AbstractAdapter + */ + public function getAdapter() + { + if (! $this->options['adapter'] instanceof Barcode\AdapterInterface) { + $this->setAdapter('Ean13'); + } + + return $this->options['adapter']; + } + + /** + * Sets a new barcode adapter + * + * @param string|Barcode\AbstractAdapter $adapter Barcode adapter to use + * @param array $options Options for this adapter + * @return $this + * @throws Exception\InvalidArgumentException + */ + public function setAdapter($adapter, $options = null) + { + if (is_string($adapter)) { + $adapter = ucfirst(strtolower($adapter)); + $adapter = 'Laminas\\Validator\\Barcode\\' . $adapter; + + if (! class_exists($adapter)) { + throw new Exception\InvalidArgumentException('Barcode adapter matching "' . $adapter . '" not found'); + } + + $adapter = new $adapter($options); + } + + if (! $adapter instanceof Barcode\AdapterInterface) { + throw new Exception\InvalidArgumentException( + sprintf( + 'Adapter %s does not implement Laminas\\Validator\\Barcode\\AdapterInterface', + is_object($adapter) ? get_class($adapter) : gettype($adapter) + ) + ); + } + + $this->options['adapter'] = $adapter; + + return $this; + } + + /** + * Returns the checksum option + * + * @return string + */ + public function getChecksum() + { + return $this->getAdapter()->getChecksum(); + } + + /** + * Sets if checksum should be validated, if no value is given the actual setting is returned + * + * @param null|bool $checksum + * @return Barcode\AbstractAdapter|bool + */ + public function useChecksum($checksum = null) + { + return $this->getAdapter()->useChecksum($checksum); + } + + /** + * Defined by Laminas\Validator\ValidatorInterface + * + * Returns true if and only if $value contains a valid barcode + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + $adapter = $this->getAdapter(); + $this->options['length'] = $adapter->getLength(); + $result = $adapter->hasValidLength($value); + if (! $result) { + if (is_array($this->options['length'])) { + $temp = $this->options['length']; + $this->options['length'] = ''; + foreach ($temp as $length) { + $this->options['length'] .= '/'; + $this->options['length'] .= $length; + } + + $this->options['length'] = substr($this->options['length'], 1); + } + + $this->error(self::INVALID_LENGTH); + return false; + } + + $result = $adapter->hasValidCharacters($value); + if (! $result) { + $this->error(self::INVALID_CHARS); + return false; + } + + if ($this->useChecksum(null)) { + $result = $adapter->hasValidChecksum($value); + if (! $result) { + $this->error(self::FAILED); + return false; + } + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/AbstractAdapter.php b/lib/laminas/laminas-validator/src/Barcode/AbstractAdapter.php new file mode 100644 index 0000000000..56a0164d05 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/AbstractAdapter.php @@ -0,0 +1,312 @@ + null, // Allowed barcode lengths, integer, array, string + 'characters' => null, // Allowed barcode characters + 'checksum' => null, // Callback to checksum function + 'useChecksum' => true, // Is a checksum value included?, boolean + ]; + + /** + * Checks the length of a barcode + * + * @param string $value The barcode to check for proper length + * @return bool + */ + public function hasValidLength($value) + { + if (! is_string($value)) { + return false; + } + + $fixum = strlen($value); + $length = $this->getLength(); + + if (is_array($length)) { + foreach ($length as $value) { + if ($fixum === $value) { + return true; + } + + if ($value === -1) { + return true; + } + } + + return false; + } + + if ($fixum === $length) { + return true; + } + + if ($length === -1) { + return true; + } + + if ($length === 'even') { + $count = $fixum % 2; + return 0 === $count; + } + + if ($length === 'odd') { + $count = $fixum % 2; + return 1 === $count; + } + + return false; + } + + /** + * Checks for allowed characters within the barcode + * + * @param string $value The barcode to check for allowed characters + * @return bool + */ + public function hasValidCharacters($value) + { + if (! is_string($value)) { + return false; + } + + $characters = $this->getCharacters(); + if ($characters === 128) { + for ($x = 0; $x < 128; ++$x) { + $value = str_replace(chr($x), '', $value); + } + } else { + $chars = str_split($characters); + foreach ($chars as $char) { + $value = str_replace($char, '', $value); + } + } + + if (strlen($value) > 0) { + return false; + } + + return true; + } + + /** + * Validates the checksum + * + * @param string $value The barcode to check the checksum for + * @return bool + */ + public function hasValidChecksum($value) + { + $checksum = $this->getChecksum(); + if (! empty($checksum)) { + if (method_exists($this, $checksum)) { + return $this->$checksum($value); + } + } + + return false; + } + + /** + * Returns the allowed barcode length + * + * @return int|array + */ + public function getLength() + { + return $this->options['length']; + } + + /** + * Returns the allowed characters + * + * @return int|string|array + */ + public function getCharacters() + { + return $this->options['characters']; + } + + /** + * Returns the checksum function name + * + * @return string + */ + public function getChecksum() + { + return $this->options['checksum']; + } + + /** + * Sets the checksum validation method + * + * @param callable $checksum Checksum method to call + * @return $this + */ + protected function setChecksum($checksum) + { + $this->options['checksum'] = $checksum; + return $this; + } + + /** + * Sets the checksum validation, if no value is given, the actual setting is returned + * + * @param bool $check + * @return AbstractAdapter|bool + */ + public function useChecksum($check = null) + { + if ($check === null) { + return $this->options['useChecksum']; + } + + $this->options['useChecksum'] = (bool) $check; + return $this; + } + + /** + * Sets the length of this barcode + * + * @param int|array $length + * @return $this + */ + protected function setLength($length) + { + $this->options['length'] = $length; + return $this; + } + + /** + * Sets the allowed characters of this barcode + * + * @param int $characters + * @return $this + */ + protected function setCharacters($characters) + { + $this->options['characters'] = $characters; + return $this; + } + + /** + * Validates the checksum (Modulo 10) + * GTIN implementation factor 3 + * + * @param string $value The barcode to validate + * @return bool + */ + protected function gtin($value) + { + $barcode = substr($value, 0, -1); + $sum = 0; + $length = strlen($barcode) - 1; + + for ($i = 0; $i <= $length; $i++) { + if (($i % 2) === 0) { + $sum += $barcode[$length - $i] * 3; + } else { + $sum += $barcode[$length - $i]; + } + } + + $calc = $sum % 10; + $checksum = $calc === 0 ? 0 : 10 - $calc; + + return $value[$length + 1] === (string) $checksum; + } + + /** + * Validates the checksum (Modulo 10) + * IDENTCODE implementation factors 9 and 4 + * + * @param string $value The barcode to validate + * @return bool + */ + protected function identcode($value) + { + $barcode = substr($value, 0, -1); + $sum = 0; + $length = strlen($value) - 2; + + for ($i = 0; $i <= $length; $i++) { + if (($i % 2) === 0) { + $sum += $barcode[$length - $i] * 4; + } else { + $sum += $barcode[$length - $i] * 9; + } + } + + $calc = $sum % 10; + $checksum = $calc === 0 ? 0 : 10 - $calc; + + return $value[$length + 1] === (string) $checksum; + } + + /** + * Validates the checksum (Modulo 10) + * CODE25 implementation factor 3 + * + * @param string $value The barcode to validate + * @return bool + */ + protected function code25($value) + { + $barcode = substr($value, 0, -1); + $sum = 0; + $length = strlen($barcode) - 1; + + for ($i = 0; $i <= $length; $i++) { + if (($i % 2) === 0) { + $sum += $barcode[$i] * 3; + } else { + $sum += $barcode[$i]; + } + } + + $calc = $sum % 10; + $checksum = $calc === 0 ? 0 : 10 - $calc; + + return $value[$length + 1] === (string) $checksum; + } + + /** + * Validates the checksum () + * POSTNET implementation + * + * @param string $value The barcode to validate + * @return bool + */ + protected function postnet($value) + { + $checksum = substr($value, -1, 1); + $values = str_split(substr($value, 0, -1)); + + $check = 0; + foreach ($values as $row) { + $check += $row; + } + + $check %= 10; + $check = 10 - $check; + + return (string) $check === $checksum; + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/AdapterInterface.php b/lib/laminas/laminas-validator/src/Barcode/AdapterInterface.php new file mode 100644 index 0000000000..5ccab61034 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/AdapterInterface.php @@ -0,0 +1,59 @@ +setLength(-1); + $this->setCharacters('0123456789-$:/.+ABCDTN*E'); + $this->useChecksum(false); + } + + /** + * Checks for allowed characters + * + * @see Laminas\Validator\Barcode.AbstractAdapter::checkChars() + * + * @param string $value + * @return bool + */ + public function hasValidCharacters($value) + { + if (strpbrk($value, 'ABCD')) { + $first = $value[0]; + if (! strpbrk($first, 'ABCD')) { + // Missing start char + return false; + } + + $last = substr($value, -1, 1); + if (! strpbrk($last, 'ABCD')) { + // Missing stop char + return false; + } + + $value = substr($value, 1, -1); + } elseif (strpbrk($value, 'TN*E')) { + $first = $value[0]; + if (! strpbrk($first, 'TN*E')) { + // Missing start char + return false; + } + + $last = substr($value, -1, 1); + if (! strpbrk($last, 'TN*E')) { + // Missing stop char + return false; + } + + $value = substr($value, 1, -1); + } + + $chars = $this->getCharacters(); + $this->setCharacters('0123456789-$:/.+'); + $result = parent::hasValidCharacters($value); + $this->setCharacters($chars); + return $result; + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Code128.php b/lib/laminas/laminas-validator/src/Barcode/Code128.php new file mode 100644 index 0000000000..44d7a537f0 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Code128.php @@ -0,0 +1,739 @@ +setLength(-1); + $this->setCharacters([ + 'A' => [ + 0 => ' ', + 1 => '!', + 2 => '"', + 3 => '#', + 4 => '$', + 5 => '%', + 6 => '&', + 7 => "'", + 8 => '(', + 9 => ')', + 10 => '*', + 11 => '+', + 12 => ',', + 13 => '-', + 14 => '.', + 15 => '/', + 16 => '0', + 17 => '1', + 18 => '2', + 19 => '3', + 20 => '4', + 21 => '5', + 22 => '6', + 23 => '7', + 24 => '8', + 25 => '9', + 26 => ':', + 27 => ';', + 28 => '<', + 29 => '=', + 30 => '>', + 31 => '?', + 32 => '@', + 33 => 'A', + 34 => 'B', + 35 => 'C', + 36 => 'D', + 37 => 'E', + 38 => 'F', + 39 => 'G', + 40 => 'H', + 41 => 'I', + 42 => 'J', + 43 => 'K', + 44 => 'L', + 45 => 'M', + 46 => 'N', + 47 => 'O', + 48 => 'P', + 49 => 'Q', + 50 => 'R', + 51 => 'S', + 52 => 'T', + 53 => 'U', + 54 => 'V', + 55 => 'W', + 56 => 'X', + 57 => 'Y', + 58 => 'Z', + 59 => '[', + 60 => '\\', + 61 => ']', + 62 => '^', + 63 => '_', + 64 => 0x00, + 65 => 0x01, + 66 => 0x02, + 67 => 0x03, + 68 => 0x04, + 69 => 0x05, + 70 => 0x06, + 71 => 0x07, + 72 => 0x08, + 73 => 0x09, + 74 => 0x0A, + 75 => 0x0B, + 76 => 0x0C, + 77 => 0x0D, + 78 => 0x0E, + 79 => 0x0F, + 80 => 0x10, + 81 => 0x11, + 82 => 0x12, + 83 => 0x13, + 84 => 0x14, + 85 => 0x15, + 86 => 0x16, + 87 => 0x17, + 88 => 0x18, + 89 => 0x19, + 90 => 0x1A, + 91 => 0x1B, + 92 => 0x1C, + 93 => 0x1D, + 94 => 0x1E, + 95 => 0x1F, + 96 => 'Ç', + 97 => 'ü', + 98 => 'é', + 99 => 'â', + 100 => 'ä', + 101 => 'à', + 102 => 'å', + 103 => '‡', + 104 => 'ˆ', + 105 => '‰', + 106 => 'Š', + ], + 'B' => [ + 0 => ' ', + 1 => '!', + 2 => '"', + 3 => '#', + 4 => '$', + 5 => '%', + 6 => '&', + 7 => "'", + 8 => '(', + 9 => ')', + 10 => '*', + 11 => '+', + 12 => ',', + 13 => '-', + 14 => '.', + 15 => '/', + 16 => '0', + 17 => '1', + 18 => '2', + 19 => '3', + 20 => '4', + 21 => '5', + 22 => '6', + 23 => '7', + 24 => '8', + 25 => '9', + 26 => ':', + 27 => ';', + 28 => '<', + 29 => '=', + 30 => '>', + 31 => '?', + 32 => '@', + 33 => 'A', + 34 => 'B', + 35 => 'C', + 36 => 'D', + 37 => 'E', + 38 => 'F', + 39 => 'G', + 40 => 'H', + 41 => 'I', + 42 => 'J', + 43 => 'K', + 44 => 'L', + 45 => 'M', + 46 => 'N', + 47 => 'O', + 48 => 'P', + 49 => 'Q', + 50 => 'R', + 51 => 'S', + 52 => 'T', + 53 => 'U', + 54 => 'V', + 55 => 'W', + 56 => 'X', + 57 => 'Y', + 58 => 'Z', + 59 => '[', + 60 => '\\', + 61 => ']', + 62 => '^', + 63 => '_', + 64 => '`', + 65 => 'a', + 66 => 'b', + 67 => 'c', + 68 => 'd', + 69 => 'e', + 70 => 'f', + 71 => 'g', + 72 => 'h', + 73 => 'i', + 74 => 'j', + 75 => 'k', + 76 => 'l', + 77 => 'm', + 78 => 'n', + 79 => 'o', + 80 => 'p', + 81 => 'q', + 82 => 'r', + 83 => 's', + 84 => 't', + 85 => 'u', + 86 => 'v', + 87 => 'w', + 88 => 'x', + 89 => 'y', + 90 => 'z', + 91 => '{', + 92 => '|', + 93 => '}', + 94 => '~', + 95 => 0x7F, + 96 => 'Ç', + 97 => 'ü', + 98 => 'é', + 99 => 'â', + 100 => 'ä', + 101 => 'à', + 102 => 'å', + 103 => '‡', + 104 => 'ˆ', + 105 => '‰', + 106 => 'Š', + ], + 'C' => [ + 0 => '00', + 1 => '01', + 2 => '02', + 3 => '03', + 4 => '04', + 5 => '05', + 6 => '06', + 7 => '07', + 8 => '08', + 9 => '09', + 10 => '10', + 11 => '11', + 12 => '12', + 13 => '13', + 14 => '14', + 15 => '15', + 16 => '16', + 17 => '17', + 18 => '18', + 19 => '19', + 20 => '20', + 21 => '21', + 22 => '22', + 23 => '23', + 24 => '24', + 25 => '25', + 26 => '26', + 27 => '27', + 28 => '28', + 29 => '29', + 30 => '30', + 31 => '31', + 32 => '32', + 33 => '33', + 34 => '34', + 35 => '35', + 36 => '36', + 37 => '37', + 38 => '38', + 39 => '39', + 40 => '40', + 41 => '41', + 42 => '42', + 43 => '43', + 44 => '44', + 45 => '45', + 46 => '46', + 47 => '47', + 48 => '48', + 49 => '49', + 50 => '50', + 51 => '51', + 52 => '52', + 53 => '53', + 54 => '54', + 55 => '55', + 56 => '56', + 57 => '57', + 58 => '58', + 59 => '59', + 60 => '60', + 61 => '61', + 62 => '62', + 63 => '63', + 64 => '64', + 65 => '65', + 66 => '66', + 67 => '67', + 68 => '68', + 69 => '69', + 70 => '70', + 71 => '71', + 72 => '72', + 73 => '73', + 74 => '74', + 75 => '75', + 76 => '76', + 77 => '77', + 78 => '78', + 79 => '79', + 80 => '80', + 81 => '81', + 82 => '82', + 83 => '83', + 84 => '84', + 85 => '85', + 86 => '86', + 87 => '87', + 88 => '88', + 89 => '89', + 90 => '90', + 91 => '91', + 92 => '92', + 93 => '93', + 94 => '94', + 95 => '95', + 96 => '96', + 97 => '97', + 98 => '98', + 99 => '99', + 100 => 'ä', + 101 => 'à', + 102 => 'å', + 103 => '‡', + 104 => 'ˆ', + 105 => '‰', + 106 => 'Š', + ], + ]); + $this->setChecksum('code128'); + } + + /** + * @return void + */ + public function setUtf8StringWrapper(StringWrapperInterface $utf8StringWrapper) + { + if (! $utf8StringWrapper->isSupported('UTF-8')) { + throw new Exception\InvalidArgumentException( + 'The string wrapper needs to support UTF-8 character encoding' + ); + } + $this->utf8StringWrapper = $utf8StringWrapper; + } + + /** + * Get the string wrapper supporting UTF-8 character encoding + * + * @return StringWrapperInterface + */ + public function getUtf8StringWrapper() + { + if (! $this->utf8StringWrapper) { + $this->utf8StringWrapper = StringUtils::getWrapper('UTF-8'); + } + return $this->utf8StringWrapper; + } + + /** + * Checks for allowed characters within the barcode + * + * @param string $value The barcode to check for allowed characters + * @return bool + */ + public function hasValidCharacters($value) + { + if (! is_string($value)) { + return false; + } + + // get used string wrapper for UTF-8 character encoding + $strWrapper = $this->getUtf8StringWrapper(); + + // detect starting charset + $set = $this->getCodingSet($value); + $read = $set; + if ($set !== '') { + $value = $strWrapper->substr($value, 1, null); + } + + // process barcode + while ($value !== '' && $value !== false) { + $char = $strWrapper->substr($value, 0, 1); + + switch ($char) { + // Function definition + case 'Ç': + case 'ü': + case 'å': + break; + + // Switch 1 char between A and B + case 'é': + if ($set === 'A') { + $read = 'B'; + break; + } + + if ($set === 'B') { + $read = 'A'; + break; + } + + break; + + // Switch to C + case 'â': + $set = 'C'; + $read = 'C'; + break; + + // Switch to B + case 'ä': + $set = 'B'; + $read = 'B'; + break; + + // Switch to A + case 'à': + $set = 'A'; + $read = 'A'; + break; + + // Doubled start character + case '‡': + case 'ˆ': + case '‰': + return false; + + // Chars after the stop character + case 'Š': + break 2; + + default: + // Does the char exist within the charset to read? + if ($this->ord128($char, $read) === -1) { + return false; + } + + break; + } + + $value = $strWrapper->substr($value, 1, null); + $read = $set; + } + + if ($value !== '' && is_string($value) && $strWrapper->strlen($value) !== 1) { + return false; + } + + return true; + } + + /** + * Validates the checksum () + * + * @param string $value The barcode to validate + * @return bool + */ + protected function code128($value) + { + $sum = 0; + $pos = 1; + $set = $this->getCodingSet($value); + $read = $set; + $usecheck = $this->useChecksum(null); + $strWrapper = $this->getUtf8StringWrapper(); + $char = $strWrapper->substr($value, 0, 1); + if ($char === '‡') { + $sum = 103; + } elseif ($char === 'ˆ') { + $sum = 104; + } elseif ($char === '‰') { + $sum = 105; + } elseif ($usecheck === true) { + // no start value, unable to detect a proper checksum + return false; + } + + $value = $strWrapper->substr($value, 1, null); + while ($strWrapper->strpos($value, 'Š') || ($value !== '')) { + $char = $strWrapper->substr($value, 0, 1); + if ($read === 'C') { + $char = $strWrapper->substr($value, 0, 2); + } + + switch ($char) { + // Function definition + case 'Ç': + case 'ü': + case 'å': + $sum += $pos * $this->ord128($char, $set); + break; + + case 'é': + $sum += $pos * $this->ord128($char, $set); + + if ($set === 'A') { + $read = 'B'; + break; + } + + if ($set === 'B') { + $read = 'A'; + break; + } + + break; + + // Switch to C + case 'â': + $sum += $pos * $this->ord128($char, $set); + $set = 'C'; + $read = 'C'; + break; + + // Switch to B + case 'ä': + $sum += $pos * $this->ord128($char, $set); + $set = 'B'; + $read = 'B'; + break; + + // Switch to A + case 'à': + $sum += $pos * $this->ord128($char, $set); + $set = 'A'; + $read = 'A'; + break; + + case '‡': + case 'ˆ': + case '‰': + return false; + + default: + // Does the char exist within the charset to read? + if ($this->ord128($char, $read) === -1) { + return false; + } + + $sum += $pos * $this->ord128($char, $set); + break; + } + + $value = $strWrapper->substr($value, 1); + ++$pos; + if (($strWrapper->strpos($value, 'Š') === 1) && ($strWrapper->strlen($value) === 2)) { + // break by stop and checksum char + break; + } + $read = $set; + } + + if (($strWrapper->strpos($value, 'Š') !== 1) || ($strWrapper->strlen($value) !== 2)) { + // return false if checksum is not readable and true if no startvalue is detected + return ! $usecheck; + } + + $mod = $sum % 103; + if ($strWrapper->substr($value, 0, 1) === $this->chr128($mod, $set)) { + return true; + } + + return false; + } + + /** + * Returns the coding set for a barcode + * + * @param string $value Barcode + * @return string + */ + protected function getCodingSet($value) + { + $value = $this->getUtf8StringWrapper()->substr($value, 0, 1); + switch ($value) { + case '‡': + return 'A'; + + case 'ˆ': + return 'B'; + + case '‰': + return 'C'; + } + + return ''; + } + + /** + * Internal method to return the code128 integer from an ascii value + * + * Table A + * ASCII CODE128 + * 32 to 95 == 0 to 63 + * 0 to 31 == 64 to 95 + * 128 to 138 == 96 to 106 + * + * Table B + * ASCII CODE128 + * 32 to 138 == 0 to 106 + * + * Table C + * ASCII CODE128 + * "00" to "99" == 0 to 99 + * 132 to 138 == 100 to 106 + * + * @param string $value + * @param string $set + * @return int + */ + protected function ord128($value, $set) + { + $ord = ord($value); + if ($set === 'A') { + if ($ord < 32) { + return $ord + 64; + } elseif ($ord < 96) { + return $ord - 32; + } elseif ($ord > 138) { + return -1; + } else { + return $ord - 32; + } + } elseif ($set === 'B') { + if ($ord < 32) { + return -1; + } elseif ($ord <= 138) { + return $ord - 32; + } else { + return -1; + } + } elseif ($set === 'C') { + $val = (int) $value; + if (($val >= 0) && ($val <= 99)) { + return $val; + } elseif (($ord >= 132) && ($ord <= 138)) { + return $ord - 32; + } else { + return -1; + } + } else { + if ($ord < 32) { + return $ord + 64; + } elseif ($ord <= 138) { + return $ord - 32; + } else { + return -1; + } + } + } + + /** + * Internal Method to return the ascii value from a code128 integer + * + * Table A + * ASCII CODE128 + * 32 to 95 == 0 to 63 + * 0 to 31 == 64 to 95 + * 128 to 138 == 96 to 106 + * + * Table B + * ASCII CODE128 + * 32 to 138 == 0 to 106 + * + * Table C + * ASCII CODE128 + * "00" to "99" == 0 to 99 + * 132 to 138 == 100 to 106 + * + * @param int $value + * @param string $set + * @return int|string + */ + protected function chr128($value, $set) + { + if ($set === 'A') { + if ($value < 64) { + return chr($value + 32); + } elseif ($value < 96) { + return chr($value - 64); + } elseif ($value > 106) { + return -1; + } else { + return chr($value + 32); + } + } elseif ($set === 'B') { + if ($value > 106) { + return -1; + } else { + return chr($value + 32); + } + } elseif ($set === 'C') { + if (($value >= 0) && ($value <= 9)) { + return '0' . (string) $value; + } elseif ($value <= 99) { + return (string) $value; + } elseif ($value <= 106) { + return chr($value + 32); + } else { + return -1; + } + } else { + if ($value <= 106) { + return $value + 32; + } else { + return -1; + } + } + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Code25.php b/lib/laminas/laminas-validator/src/Barcode/Code25.php new file mode 100644 index 0000000000..7134eb7cd3 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Code25.php @@ -0,0 +1,17 @@ +setLength(-1); + $this->setCharacters('0123456789'); + $this->setChecksum('code25'); + $this->useChecksum(false); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Code25interleaved.php b/lib/laminas/laminas-validator/src/Barcode/Code25interleaved.php new file mode 100644 index 0000000000..cce21b20e3 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Code25interleaved.php @@ -0,0 +1,19 @@ +setLength('even'); + $this->setCharacters('0123456789'); + $this->setChecksum('code25'); + $this->useChecksum(false); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Code39.php b/lib/laminas/laminas-validator/src/Barcode/Code39.php new file mode 100644 index 0000000000..4b101191c0 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Code39.php @@ -0,0 +1,90 @@ + 0, + '1' => 1, + '2' => 2, + '3' => 3, + '4' => 4, + '5' => 5, + '6' => 6, + '7' => 7, + '8' => 8, + '9' => 9, + 'A' => 10, + 'B' => 11, + 'C' => 12, + 'D' => 13, + 'E' => 14, + 'F' => 15, + 'G' => 16, + 'H' => 17, + 'I' => 18, + 'J' => 19, + 'K' => 20, + 'L' => 21, + 'M' => 22, + 'N' => 23, + 'O' => 24, + 'P' => 25, + 'Q' => 26, + 'R' => 27, + 'S' => 28, + 'T' => 29, + 'U' => 30, + 'V' => 31, + 'W' => 32, + 'X' => 33, + 'Y' => 34, + 'Z' => 35, + '-' => 36, + '.' => 37, + ' ' => 38, + '$' => 39, + '/' => 40, + '+' => 41, + '%' => 42, + ]; + + /** + * Constructor for this barcode adapter + */ + public function __construct() + { + $this->setLength(-1); + $this->setCharacters('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ -.$/+%'); + $this->setChecksum('code39'); + $this->useChecksum(false); + } + + /** + * Validates the checksum (Modulo 43) + * + * @param string $value The barcode to validate + * @return bool + */ + protected function code39($value) + { + $checksum = substr($value, -1, 1); + $value = str_split(substr($value, 0, -1)); + $count = 0; + foreach ($value as $char) { + $count += $this->check[$char]; + } + + $mod = $count % 43; + if ($mod === $this->check[$checksum]) { + return true; + } + + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Code39ext.php b/lib/laminas/laminas-validator/src/Barcode/Code39ext.php new file mode 100644 index 0000000000..d23123c0a2 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Code39ext.php @@ -0,0 +1,16 @@ +setLength(-1); + $this->setCharacters(128); + $this->useChecksum(false); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Code93.php b/lib/laminas/laminas-validator/src/Barcode/Code93.php new file mode 100644 index 0000000000..92905ec177 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Code93.php @@ -0,0 +1,119 @@ + 0, + '1' => 1, + '2' => 2, + '3' => 3, + '4' => 4, + '5' => 5, + '6' => 6, + '7' => 7, + '8' => 8, + '9' => 9, + 'A' => 10, + 'B' => 11, + 'C' => 12, + 'D' => 13, + 'E' => 14, + 'F' => 15, + 'G' => 16, + 'H' => 17, + 'I' => 18, + 'J' => 19, + 'K' => 20, + 'L' => 21, + 'M' => 22, + 'N' => 23, + 'O' => 24, + 'P' => 25, + 'Q' => 26, + 'R' => 27, + 'S' => 28, + 'T' => 29, + 'U' => 30, + 'V' => 31, + 'W' => 32, + 'X' => 33, + 'Y' => 34, + 'Z' => 35, + '-' => 36, + '.' => 37, + ' ' => 38, + '$' => 39, + '/' => 40, + '+' => 41, + '%' => 42, + '!' => 43, + '"' => 44, + '§' => 45, + '&' => 46, + ]; + + /** + * Constructor for this barcode adapter + */ + public function __construct() + { + $this->setLength(-1); + $this->setCharacters('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ -.$/+%'); + $this->setChecksum('code93'); + $this->useChecksum(false); + } + + /** + * Validates the checksum (Modulo CK) + * + * @param string $value The barcode to validate + * @return bool + */ + protected function code93($value) + { + $checksum = substr($value, -2, 2); + $value = str_split(substr($value, 0, -2)); + $count = 0; + $length = count($value) % 20; + foreach ($value as $char) { + if ($length === 0) { + $length = 20; + } + + $count += $this->check[$char] * $length; + --$length; + } + + $check = array_search($count % 47, $this->check); + $value[] = $check; + $count = 0; + $length = count($value) % 15; + foreach ($value as $char) { + if ($length === 0) { + $length = 15; + } + + $count += $this->check[$char] * $length; + --$length; + } + $check .= array_search($count % 47, $this->check); + + if ($check === $checksum) { + return true; + } + + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Code93ext.php b/lib/laminas/laminas-validator/src/Barcode/Code93ext.php new file mode 100644 index 0000000000..216d72285b --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Code93ext.php @@ -0,0 +1,16 @@ +setLength(-1); + $this->setCharacters(128); + $this->useChecksum(false); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Ean12.php b/lib/laminas/laminas-validator/src/Barcode/Ean12.php new file mode 100644 index 0000000000..82be6c5a55 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Ean12.php @@ -0,0 +1,16 @@ +setLength(12); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Ean13.php b/lib/laminas/laminas-validator/src/Barcode/Ean13.php new file mode 100644 index 0000000000..261a687fd1 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Ean13.php @@ -0,0 +1,16 @@ +setLength(13); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Ean14.php b/lib/laminas/laminas-validator/src/Barcode/Ean14.php new file mode 100644 index 0000000000..9be798b08c --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Ean14.php @@ -0,0 +1,16 @@ +setLength(14); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Ean18.php b/lib/laminas/laminas-validator/src/Barcode/Ean18.php new file mode 100644 index 0000000000..f2c32204e2 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Ean18.php @@ -0,0 +1,16 @@ +setLength(18); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Ean2.php b/lib/laminas/laminas-validator/src/Barcode/Ean2.php new file mode 100644 index 0000000000..a49ea9b9a5 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Ean2.php @@ -0,0 +1,16 @@ +setLength(2); + $this->setCharacters('0123456789'); + $this->useChecksum(false); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Ean5.php b/lib/laminas/laminas-validator/src/Barcode/Ean5.php new file mode 100644 index 0000000000..0433c80a29 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Ean5.php @@ -0,0 +1,18 @@ +setLength(5); + $this->setCharacters('0123456789'); + $this->useChecksum(false); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Ean8.php b/lib/laminas/laminas-validator/src/Barcode/Ean8.php new file mode 100644 index 0000000000..7f1399a463 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Ean8.php @@ -0,0 +1,35 @@ +setLength([7, 8]); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } + + /** + * Overrides parent checkLength + * + * @param string $value Value + * @return bool + */ + public function hasValidLength($value) + { + if (strlen($value) === 7) { + $this->useChecksum(false); + } else { + $this->useChecksum(true); + } + + return parent::hasValidLength($value); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Gtin12.php b/lib/laminas/laminas-validator/src/Barcode/Gtin12.php new file mode 100644 index 0000000000..f667d1f507 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Gtin12.php @@ -0,0 +1,16 @@ +setLength(12); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Gtin13.php b/lib/laminas/laminas-validator/src/Barcode/Gtin13.php new file mode 100644 index 0000000000..4f02651ecd --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Gtin13.php @@ -0,0 +1,16 @@ +setLength(13); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Gtin14.php b/lib/laminas/laminas-validator/src/Barcode/Gtin14.php new file mode 100644 index 0000000000..2ec6624cee --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Gtin14.php @@ -0,0 +1,16 @@ +setLength(14); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Identcode.php b/lib/laminas/laminas-validator/src/Barcode/Identcode.php new file mode 100644 index 0000000000..a0c4cf63ae --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Identcode.php @@ -0,0 +1,37 @@ +setLength(12); + $this->setCharacters('0123456789'); + $this->setChecksum('identcode'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Intelligentmail.php b/lib/laminas/laminas-validator/src/Barcode/Intelligentmail.php new file mode 100644 index 0000000000..d34569f1bf --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Intelligentmail.php @@ -0,0 +1,18 @@ +setLength([20, 25, 29, 31]); + $this->setCharacters('0123456789'); + $this->useChecksum(false); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Issn.php b/lib/laminas/laminas-validator/src/Barcode/Issn.php new file mode 100644 index 0000000000..73a0fcc3f3 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Issn.php @@ -0,0 +1,91 @@ +setLength([8, 13]); + $this->setCharacters('0123456789X'); + $this->setChecksum('gtin'); + } + + /** + * Allows X on length of 8 chars + * + * @param string $value The barcode to check for allowed characters + * @return bool + */ + public function hasValidCharacters($value) + { + if (strlen($value) !== 8) { + if (strpos($value, 'X') !== false) { + return false; + } + } + + return parent::hasValidCharacters($value); + } + + /** + * Validates the checksum + * + * @param string $value The barcode to check the checksum for + * @return bool + */ + public function hasValidChecksum($value) + { + if (strlen($value) === 8) { + $this->setChecksum('issn'); + } else { + $this->setChecksum('gtin'); + } + + return parent::hasValidChecksum($value); + } + + /** + * Validates the checksum () + * ISSN implementation (reversed mod11) + * + * @param string $value The barcode to validate + * @return bool + */ + protected function issn($value) + { + $checksum = substr($value, -1, 1); + $values = str_split(substr($value, 0, -1)); + $check = 0; + $multi = 8; + foreach ($values as $token) { + if ($token === 'X') { + $token = 10; + } + + $check += $token * $multi; + --$multi; + } + + $check %= 11; + $check = $check === 0 ? 0 : 11 - $check; + + if ((string) $check === $checksum) { + return true; + } + + if (($check === 10) && ($checksum === 'X')) { + return true; + } + + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Itf14.php b/lib/laminas/laminas-validator/src/Barcode/Itf14.php new file mode 100644 index 0000000000..c8683ff19d --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Itf14.php @@ -0,0 +1,16 @@ +setLength(14); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Leitcode.php b/lib/laminas/laminas-validator/src/Barcode/Leitcode.php new file mode 100644 index 0000000000..c4a0dc1071 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Leitcode.php @@ -0,0 +1,16 @@ +setLength(14); + $this->setCharacters('0123456789'); + $this->setChecksum('identcode'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Planet.php b/lib/laminas/laminas-validator/src/Barcode/Planet.php new file mode 100644 index 0000000000..e966065b95 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Planet.php @@ -0,0 +1,16 @@ +setLength([12, 14]); + $this->setCharacters('0123456789'); + $this->setChecksum('postnet'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Postnet.php b/lib/laminas/laminas-validator/src/Barcode/Postnet.php new file mode 100644 index 0000000000..f3261e138d --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Postnet.php @@ -0,0 +1,16 @@ +setLength([6, 7, 10, 12]); + $this->setCharacters('0123456789'); + $this->setChecksum('postnet'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Royalmail.php b/lib/laminas/laminas-validator/src/Barcode/Royalmail.php new file mode 100644 index 0000000000..9c3d306efe --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Royalmail.php @@ -0,0 +1,156 @@ + */ + protected $rows = [ + '0' => 1, + '1' => 1, + '2' => 1, + '3' => 1, + '4' => 1, + '5' => 1, + '6' => 2, + '7' => 2, + '8' => 2, + '9' => 2, + 'A' => 2, + 'B' => 2, + 'C' => 3, + 'D' => 3, + 'E' => 3, + 'F' => 3, + 'G' => 3, + 'H' => 3, + 'I' => 4, + 'J' => 4, + 'K' => 4, + 'L' => 4, + 'M' => 4, + 'N' => 4, + 'O' => 5, + 'P' => 5, + 'Q' => 5, + 'R' => 5, + 'S' => 5, + 'T' => 5, + 'U' => 0, + 'V' => 0, + 'W' => 0, + 'X' => 0, + 'Y' => 0, + 'Z' => 0, + ]; + + /** @var array */ + protected $columns = [ + '0' => 1, + '1' => 2, + '2' => 3, + '3' => 4, + '4' => 5, + '5' => 0, + '6' => 1, + '7' => 2, + '8' => 3, + '9' => 4, + 'A' => 5, + 'B' => 0, + 'C' => 1, + 'D' => 2, + 'E' => 3, + 'F' => 4, + 'G' => 5, + 'H' => 0, + 'I' => 1, + 'J' => 2, + 'K' => 3, + 'L' => 4, + 'M' => 5, + 'N' => 0, + 'O' => 1, + 'P' => 2, + 'Q' => 3, + 'R' => 4, + 'S' => 5, + 'T' => 0, + 'U' => 1, + 'V' => 2, + 'W' => 3, + 'X' => 4, + 'Y' => 5, + 'Z' => 0, + ]; + + /** + * Constructor for this barcode adapter + */ + public function __construct() + { + $this->setLength(-1); + $this->setCharacters('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'); + $this->setChecksum('royalmail'); + } + + /** + * Validates the checksum () + * + * @param string $value The barcode to validate + * @return bool + */ + protected function royalmail($value) + { + $checksum = substr($value, -1, 1); + $values = str_split(substr($value, 0, -1)); + $rowvalue = 0; + $colvalue = 0; + foreach ($values as $row) { + $rowvalue += $this->rows[$row]; + $colvalue += $this->columns[$row]; + } + + $rowvalue %= 6; + $colvalue %= 6; + + $rowchkvalue = array_keys($this->rows, $rowvalue); + $colchkvalue = array_keys($this->columns, $colvalue); + $intersect = array_intersect($rowchkvalue, $colchkvalue); + $chkvalue = (string) current($intersect); + + if ($chkvalue === $checksum) { + return true; + } + + return false; + } + + /** + * Allows start and stop tag within checked chars + * + * @param string $value The barcode to check for allowed characters + * @return bool + */ + public function hasValidCharacters($value) + { + if ($value[0] === '(') { + $value = substr($value, 1); + + if ($value[strlen($value) - 1] === ')') { + $value = substr($value, 0, -1); + } else { + return false; + } + } + + return parent::hasValidCharacters($value); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Sscc.php b/lib/laminas/laminas-validator/src/Barcode/Sscc.php new file mode 100644 index 0000000000..44f804233f --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Sscc.php @@ -0,0 +1,16 @@ +setLength(18); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Upca.php b/lib/laminas/laminas-validator/src/Barcode/Upca.php new file mode 100644 index 0000000000..d5d8041775 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Upca.php @@ -0,0 +1,16 @@ +setLength(12); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } +} diff --git a/lib/laminas/laminas-validator/src/Barcode/Upce.php b/lib/laminas/laminas-validator/src/Barcode/Upce.php new file mode 100644 index 0000000000..3532b42c6a --- /dev/null +++ b/lib/laminas/laminas-validator/src/Barcode/Upce.php @@ -0,0 +1,35 @@ +setLength([6, 7, 8]); + $this->setCharacters('0123456789'); + $this->setChecksum('gtin'); + } + + /** + * Overrides parent checkLength + * + * @param string $value Value + * @return bool + */ + public function hasValidLength($value) + { + if (strlen($value) !== 8) { + $this->useChecksum(false); + } else { + $this->useChecksum(true); + } + + return parent::hasValidLength($value); + } +} diff --git a/lib/laminas/laminas-validator/src/Between.php b/lib/laminas/laminas-validator/src/Between.php new file mode 100644 index 0000000000..8a34f4ae6c --- /dev/null +++ b/lib/laminas/laminas-validator/src/Between.php @@ -0,0 +1,217 @@ + + */ + protected $messageTemplates = [ + self::NOT_BETWEEN => "The input is not between '%min%' and '%max%', inclusively", + self::NOT_BETWEEN_STRICT => "The input is not strictly between '%min%' and '%max%'", + self::VALUE_NOT_NUMERIC => "The min ('%min%') and max ('%max%') values are numeric, but the input is not", + self::VALUE_NOT_STRING => "The min ('%min%') and max ('%max%') values are non-numeric strings, " + . 'but the input is not a string', + ]; + + /** + * Additional variables available for validation failure messages + * + * @var array + */ + protected $messageVariables = [ + 'min' => ['options' => 'min'], + 'max' => ['options' => 'max'], + ]; + + /** + * Options for the between validator + * + * @var array + */ + protected $options = [ + 'inclusive' => true, // Whether to do inclusive comparisons, allowing equivalence to min and/or max + 'min' => 0, + 'max' => PHP_INT_MAX, + ]; + + /** + * Sets validator options + * Accepts the following option keys: + * 'min' => scalar, minimum border + * 'max' => scalar, maximum border + * 'inclusive' => boolean, inclusive border values + * + * @param array|Traversable $options + * @throws Exception\InvalidArgumentException + */ + public function __construct($options = null) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + if (! is_array($options)) { + $temp = []; + /** @psalm-var array $options */ + $options = func_get_args(); + $temp['min'] = array_shift($options); + if (! empty($options)) { + $temp['max'] = array_shift($options); + } + + if (! empty($options)) { + $temp['inclusive'] = array_shift($options); + } + + $options = $temp; + } + + if (! array_key_exists('min', $options) || ! array_key_exists('max', $options)) { + throw new Exception\InvalidArgumentException("Missing option: 'min' and 'max' have to be given"); + } + + if ( + (isset($options['min']) && is_numeric($options['min'])) + && (isset($options['max']) && is_numeric($options['max'])) + ) { + $this->numeric = true; + } elseif ( + (isset($options['min']) && is_string($options['min'])) + && (isset($options['max']) && is_string($options['max'])) + ) { + $this->numeric = false; + } else { + throw new Exception\InvalidArgumentException( + "Invalid options: 'min' and 'max' should be of the same scalar type" + ); + } + + parent::__construct($options); + } + + /** + * Returns the min option + * + * @return mixed + */ + public function getMin() + { + return $this->options['min']; + } + + /** + * Sets the min option + * + * @param mixed $min + * @return $this Provides a fluent interface + */ + public function setMin($min) + { + $this->options['min'] = $min; + return $this; + } + + /** + * Returns the max option + * + * @return mixed + */ + public function getMax() + { + return $this->options['max']; + } + + /** + * Sets the max option + * + * @param mixed $max + * @return $this Provides a fluent interface + */ + public function setMax($max) + { + $this->options['max'] = $max; + return $this; + } + + /** + * Returns the inclusive option + * + * @return bool + */ + public function getInclusive() + { + return $this->options['inclusive']; + } + + /** + * Sets the inclusive option + * + * @param bool $inclusive + * @return $this Provides a fluent interface + */ + public function setInclusive($inclusive) + { + $this->options['inclusive'] = $inclusive; + return $this; + } + + /** + * Returns true if and only if $value is between min and max options, inclusively + * if inclusive option is true. + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + $this->setValue($value); + + if ($this->numeric && ! is_numeric($value)) { + $this->error(self::VALUE_NOT_NUMERIC); + return false; + } + if (! $this->numeric && ! is_string($value)) { + $this->error(self::VALUE_NOT_STRING); + return false; + } + + if ($this->getInclusive()) { + if ($this->getMin() > $value || $value > $this->getMax()) { + $this->error(self::NOT_BETWEEN); + return false; + } + } else { + if ($this->getMin() >= $value || $value >= $this->getMax()) { + $this->error(self::NOT_BETWEEN_STRICT); + return false; + } + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Bitwise.php b/lib/laminas/laminas-validator/src/Bitwise.php new file mode 100644 index 0000000000..d718a020f9 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Bitwise.php @@ -0,0 +1,203 @@ + + */ + protected $messageTemplates = [ + self::NOT_AND => "The input has no common bit set with '%control%'", + self::NOT_AND_STRICT => "The input doesn't have the same bits set as '%control%'", + self::NOT_XOR => "The input has common bit set with '%control%'", + self::NO_OP => "No operator was present to compare '%control%' against", + ]; + + /** + * Additional variables available for validation failure messages + * + * @var array + */ + protected $messageVariables = [ + 'control' => 'control', + ]; + + /** @var null|int */ + protected $operator; + + /** @var bool */ + protected $strict = false; + + /** + * Sets validator options + * Accepts the following option keys: + * 'control' => int + * 'operator' => + * 'strict' => bool + * + * @param array|Traversable $options + */ + public function __construct($options = null) + { + if ($options instanceof Traversable) { + $options = iterator_to_array($options); + } + + if (! is_array($options)) { + $options = func_get_args(); + + $temp['control'] = array_shift($options); + + if (! empty($options)) { + $temp['operator'] = array_shift($options); + } + + if (! empty($options)) { + $temp['strict'] = array_shift($options); + } + + $options = $temp; + } + + parent::__construct($options); + } + + /** + * Returns the control parameter. + * + * @return integer + */ + public function getControl() + { + return $this->control; + } + + /** + * Returns the operator parameter. + * + * @return null|int + */ + public function getOperator() + { + return $this->operator; + } + + /** + * Returns the strict parameter. + * + * @return boolean + */ + public function getStrict() + { + return $this->strict; + } + + /** + * Returns true if and only if $value is between min and max options, inclusively + * if inclusive option is true. + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + $this->setValue($value); + + if (self::OP_AND === $this->operator) { + if ($this->strict) { + // All the bits set in value must be set in control + $result = ($this->control & $value) === $value; + + if (! $result) { + $this->error(self::NOT_AND_STRICT); + } + + return $result; + } + + // At least one of the bits must be common between value and control + $result = (bool) ($this->control & $value); + + if (! $result) { + $this->error(self::NOT_AND); + } + + return $result; + } + + if (self::OP_XOR === $this->operator) { + // Parentheses are required due to order of operations with bitwise operations + // phpcs:ignore WebimpressCodingStandard.Formatting.RedundantParentheses.SingleEquality + $result = ($this->control ^ $value) === ($this->control | $value); + + if (! $result) { + $this->error(self::NOT_XOR); + } + + return $result; + } + + $this->error(self::NO_OP); + return false; + } + + /** + * Sets the control parameter. + * + * @param integer $control + * @return $this + */ + public function setControl($control) + { + $this->control = (int) $control; + + return $this; + } + + /** + * Sets the operator parameter. + * + * @param string $operator + * @return $this + */ + public function setOperator($operator) + { + $this->operator = $operator; + + return $this; + } + + /** + * Sets the strict parameter. + * + * @param boolean $strict + * @return $this + */ + public function setStrict($strict) + { + $this->strict = (bool) $strict; + + return $this; + } +} diff --git a/lib/laminas/laminas-validator/src/BusinessIdentifierCode.php b/lib/laminas/laminas-validator/src/BusinessIdentifierCode.php new file mode 100644 index 0000000000..f9df4feaa2 --- /dev/null +++ b/lib/laminas/laminas-validator/src/BusinessIdentifierCode.php @@ -0,0 +1,329 @@ + 'Invalid type given; string expected', + self::INVALID => 'Invalid BIC format', + self::NOT_VALID_COUNTRY => 'Invalid country code', + ]; + + /** + * @see https://www.bundesbank.de/resource/blob/749660/d2c6e00664251b4d83483c229e084e44/mL/technische-spezifikationen-scc-anhang-112018-data.pdf (page 39) + */ + private const REGEX_BIC = '/^[a-z]{4}(?[a-z]{2})[a-z2-9][a-np-z0-9]([0-9a-z]{3})?$/i'; + + /** + * List of all country codes defined by ISO 3166-1 alpha-2 + * + * @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Current_codes + * + * @var string[] + */ + private const ISO_COUNTRIES = [ + 'AD', + 'AE', + 'AF', + 'AG', + 'AI', + 'AL', + 'AM', + 'AO', + 'AQ', + 'AR', + 'AS', + 'AT', + 'AU', + 'AW', + 'AX', + 'AZ', + 'BA', + 'BB', + 'BD', + 'BE', + 'BF', + 'BG', + 'BH', + 'BI', + 'BJ', + 'BL', + 'BM', + 'BN', + 'BO', + 'BQ', + 'BQ', + 'BR', + 'BS', + 'BT', + 'BV', + 'BW', + 'BY', + 'BZ', + 'CA', + 'CC', + 'CD', + 'CF', + 'CG', + 'CH', + 'CI', + 'CK', + 'CL', + 'CM', + 'CN', + 'CO', + 'CR', + 'CU', + 'CV', + 'CW', + 'CX', + 'CY', + 'CZ', + 'DE', + 'DJ', + 'DK', + 'DM', + 'DO', + 'DZ', + 'EC', + 'EE', + 'EG', + 'EH', + 'ER', + 'ES', + 'ET', + 'FI', + 'FJ', + 'FK', + 'FM', + 'FO', + 'FR', + 'GA', + 'GB', + 'GD', + 'GE', + 'GF', + 'GG', + 'GH', + 'GI', + 'GL', + 'GM', + 'GN', + 'GP', + 'GQ', + 'GR', + 'GS', + 'GT', + 'GU', + 'GW', + 'GY', + 'HK', + 'HM', + 'HN', + 'HR', + 'HT', + 'HU', + 'ID', + 'IE', + 'IL', + 'IM', + 'IN', + 'IO', + 'IQ', + 'IR', + 'IS', + 'IT', + 'JE', + 'JM', + 'JO', + 'JP', + 'KE', + 'KG', + 'KH', + 'KI', + 'KM', + 'KN', + 'KP', + 'KR', + 'KW', + 'KY', + 'KZ', + 'LA', + 'LB', + 'LC', + 'LI', + 'LK', + 'LR', + 'LS', + 'LT', + 'LU', + 'LV', + 'LY', + 'MA', + 'MC', + 'MD', + 'ME', + 'MF', + 'MG', + 'MH', + 'MK', + 'ML', + 'MM', + 'MN', + 'MO', + 'MP', + 'MQ', + 'MR', + 'MS', + 'MT', + 'MU', + 'MV', + 'MW', + 'MX', + 'MY', + 'MZ', + 'NA', + 'NC', + 'NE', + 'NF', + 'NG', + 'NI', + 'NL', + 'NO', + 'NP', + 'NR', + 'NU', + 'NZ', + 'OM', + 'PA', + 'PE', + 'PF', + 'PG', + 'PH', + 'PK', + 'PL', + 'PM', + 'PN', + 'PR', + 'PS', + 'PT', + 'PW', + 'PY', + 'QA', + 'RE', + 'RO', + 'RS', + 'RU', + 'RW', + 'SA', + 'SB', + 'SC', + 'SD', + 'SE', + 'SG', + 'SH', + 'SI', + 'SJ', + 'SK', + 'SL', + 'SM', + 'SN', + 'SO', + 'SR', + 'SS', + 'ST', + 'SV', + 'SX', + 'SY', + 'SZ', + 'TC', + 'TD', + 'TF', + 'TG', + 'TH', + 'TJ', + 'TK', + 'TL', + 'TM', + 'TN', + 'TO', + 'TR', + 'TT', + 'TV', + 'TW', + 'TZ', + 'UA', + 'UG', + 'UM', + 'US', + 'UY', + 'UZ', + 'VA', + 'VC', + 'VE', + 'VG', + 'VI', + 'VN', + 'VU', + 'WF', + 'WS', + 'YE', + 'YT', + 'ZA', + 'ZM', + 'ZW', + ]; + + /** + * This code is the only one used by SWIFT that is not defined by ISO 3166-1 alpha-2 + * + * @see https://en.wikipedia.org/wiki/ISO_9362 + * + * @var string + */ + private const KOSOVO_EXCEPTION = 'XK'; + + /** + * @inheritDoc + */ + public function isValid($value): bool + { + if (! is_string($value)) { + $this->error(self::NOT_STRING); + return false; + } + + if ( + empty($value) + || ! preg_match(self::REGEX_BIC, $value, $matches) + ) { + $this->error(self::INVALID); + return false; + } + + if (! $this->isSwiftValidCountry($matches['country'])) { + $this->error(self::NOT_VALID_COUNTRY); + return false; + } + + return true; + } + + private function isSwiftValidCountry(string $countryCode): bool + { + $countryCode = strtoupper($countryCode); + return in_array($countryCode, self::ISO_COUNTRIES, true) + || $countryCode === self::KOSOVO_EXCEPTION; + } +} diff --git a/lib/laminas/laminas-validator/src/Callback.php b/lib/laminas/laminas-validator/src/Callback.php new file mode 100644 index 0000000000..7d2dc52a43 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Callback.php @@ -0,0 +1,150 @@ + 'The input is not valid', + self::INVALID_CALLBACK => 'An exception has been raised within the callback', + ]; + + /** + * Default options to set for the validator + * + * @var mixed + */ + protected $options = [ + 'callback' => null, // Callback in a call_user_func format, string || array + 'callbackOptions' => [], // Options for the callback + ]; + + /** + * Constructor + * + * @param array|callable $options + */ + public function __construct($options = null) + { + if (is_callable($options)) { + $options = ['callback' => $options]; + } + + parent::__construct($options); + } + + /** + * Returns the set callback + * + * @return mixed + */ + public function getCallback() + { + return $this->options['callback']; + } + + /** + * Sets the callback + * + * @param string|array|callable $callback + * @return $this Provides a fluent interface + * @throws InvalidArgumentException + */ + public function setCallback($callback) + { + if (! is_callable($callback)) { + throw new InvalidArgumentException('Invalid callback given'); + } + + $this->options['callback'] = $callback; + return $this; + } + + /** + * Returns the set options for the callback + * + * @return mixed + */ + public function getCallbackOptions() + { + return $this->options['callbackOptions']; + } + + /** + * Sets options for the callback + * + * @param mixed $options + * @return $this Provides a fluent interface + */ + public function setCallbackOptions($options) + { + $this->options['callbackOptions'] = (array) $options; + return $this; + } + + /** + * Returns true if and only if the set callback returns + * for the provided $value + * + * @param mixed $value + * @param mixed $context Additional context to provide to the callback + * @return bool + * @throws InvalidArgumentException + */ + public function isValid($value, $context = null) + { + $this->setValue($value); + + $options = $this->getCallbackOptions(); + $callback = $this->getCallback(); + if (empty($callback)) { + throw new InvalidArgumentException('No callback given'); + } + + $args = [$value]; + if (empty($options) && ! empty($context)) { + $args[] = $context; + } + if (! empty($options) && empty($context)) { + $args = array_merge($args, $options); + } + if (! empty($options) && ! empty($context)) { + $args[] = $context; + $args = array_merge($args, $options); + } + + try { + if (! call_user_func_array($callback, $args)) { + $this->error(self::INVALID_VALUE); + return false; + } + } catch (Exception $e) { + $this->error(self::INVALID_CALLBACK); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/ConfigProvider.php b/lib/laminas/laminas-validator/src/ConfigProvider.php new file mode 100644 index 0000000000..188ebc39d0 --- /dev/null +++ b/lib/laminas/laminas-validator/src/ConfigProvider.php @@ -0,0 +1,38 @@ + $this->getDependencyConfig(), + ]; + } + + /** + * Return dependency mappings for this component. + * + * @return array + */ + public function getDependencyConfig() + { + return [ + 'aliases' => [ + 'ValidatorManager' => ValidatorPluginManager::class, + + // Legacy Zend Framework aliases + 'Zend\Validator\ValidatorPluginManager' => ValidatorPluginManager::class, + ], + 'factories' => [ + ValidatorPluginManager::class => ValidatorPluginManagerFactory::class, + ], + ]; + } +} diff --git a/lib/laminas/laminas-validator/src/CreditCard.php b/lib/laminas/laminas-validator/src/CreditCard.php new file mode 100644 index 0000000000..24030ed672 --- /dev/null +++ b/lib/laminas/laminas-validator/src/CreditCard.php @@ -0,0 +1,436 @@ + 'The input seems to contain an invalid checksum', + self::CONTENT => 'The input must contain only digits', + self::INVALID => 'Invalid type given. String expected', + self::LENGTH => 'The input contains an invalid amount of digits', + self::PREFIX => 'The input is not from an allowed institute', + self::SERVICE => 'The input seems to be an invalid credit card number', + self::SERVICEFAILURE => 'An exception has been raised while validating the input', + ]; + + /** + * List of CCV names + * + * @var array + */ + protected $cardName = [ + 0 => self::AMERICAN_EXPRESS, + 1 => self::DINERS_CLUB, + 2 => self::DINERS_CLUB_US, + 3 => self::DISCOVER, + 4 => self::JCB, + 5 => self::LASER, + 6 => self::MAESTRO, + 7 => self::MASTERCARD, + 8 => self::SOLO, + 9 => self::UNIONPAY, + 10 => self::VISA, + 11 => self::MIR, + ]; + + /** + * List of allowed CCV lengths + * + * @var array + */ + protected $cardLength = [ + self::AMERICAN_EXPRESS => [15], + self::DINERS_CLUB => [14], + self::DINERS_CLUB_US => [16], + self::DISCOVER => [16, 19], + self::JCB => [15, 16], + self::LASER => [16, 17, 18, 19], + self::MAESTRO => [12, 13, 14, 15, 16, 17, 18, 19], + self::MASTERCARD => [16], + self::SOLO => [16, 18, 19], + self::UNIONPAY => [16, 17, 18, 19], + self::VISA => [13, 16, 19], + self::MIR => [13, 16], + ]; + + /** + * List of accepted CCV provider tags + * + * @var array + */ + protected $cardType = [ + self::AMERICAN_EXPRESS => ['34', '37'], + self::DINERS_CLUB => ['300', '301', '302', '303', '304', '305', '36'], + self::DINERS_CLUB_US => ['54', '55'], + self::DISCOVER => [ + '6011', + '622126', + '622127', + '622128', + '622129', + '62213', + '62214', + '62215', + '62216', + '62217', + '62218', + '62219', + '6222', + '6223', + '6224', + '6225', + '6226', + '6227', + '6228', + '62290', + '62291', + '622920', + '622921', + '622922', + '622923', + '622924', + '622925', + '644', + '645', + '646', + '647', + '648', + '649', + '65', + ], + self::JCB => ['1800', '2131', '3528', '3529', '353', '354', '355', '356', '357', '358'], + self::LASER => ['6304', '6706', '6771', '6709'], + self::MAESTRO => [ + '5018', + '5020', + '5038', + '6304', + '6759', + '6761', + '6762', + '6763', + '6764', + '6765', + '6766', + '6772', + ], + self::MASTERCARD => [ + '2221', + '2222', + '2223', + '2224', + '2225', + '2226', + '2227', + '2228', + '2229', + '223', + '224', + '225', + '226', + '227', + '228', + '229', + '23', + '24', + '25', + '26', + '271', + '2720', + '51', + '52', + '53', + '54', + '55', + ], + self::SOLO => ['6334', '6767'], + self::UNIONPAY => [ + '622126', + '622127', + '622128', + '622129', + '62213', + '62214', + '62215', + '62216', + '62217', + '62218', + '62219', + '6222', + '6223', + '6224', + '6225', + '6226', + '6227', + '6228', + '62290', + '62291', + '622920', + '622921', + '622922', + '622923', + '622924', + '622925', + ], + self::VISA => ['4'], + self::MIR => ['2200', '2201', '2202', '2203', '2204'], + ]; + + /** + * Options for this validator + * + * @var array + */ + protected $options = [ + 'service' => null, // Service callback for additional validation + 'type' => [], // CCIs which are accepted by validation + ]; + + /** + * Constructor + * + * @param string|array|Traversable $options OPTIONAL Type of CCI to allow + */ + public function __construct($options = []) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } elseif (! is_array($options)) { + $options = func_get_args(); + $temp['type'] = array_shift($options); + if (! empty($options)) { + $temp['service'] = array_shift($options); + } + + $options = $temp; + } + + if (! array_key_exists('type', $options)) { + $options['type'] = self::ALL; + } + + $this->setType($options['type']); + unset($options['type']); + + if (array_key_exists('service', $options)) { + $this->setService($options['service']); + unset($options['service']); + } + + parent::__construct($options); + } + + /** + * Returns a list of accepted CCIs + * + * @return array + */ + public function getType() + { + return $this->options['type']; + } + + /** + * Sets CCIs which are accepted by validation + * + * @param string|array $type Type to allow for validation + * @return CreditCard Provides a fluid interface + */ + public function setType($type) + { + $this->options['type'] = []; + return $this->addType($type); + } + + /** + * Adds a CCI to be accepted by validation + * + * @param string|array $type Type to allow for validation + * @return $this Provides a fluid interface + */ + public function addType($type) + { + if (is_string($type)) { + $type = [$type]; + } + + foreach ($type as $typ) { + if ($typ === self::ALL) { + $this->options['type'] = array_keys($this->cardLength); + continue; + } + + if (in_array($typ, $this->options['type'])) { + continue; + } + + $constant = 'static::' . strtoupper($typ); + if (! defined($constant) || in_array(constant($constant), $this->options['type'])) { + continue; + } + $this->options['type'][] = constant($constant); + } + + return $this; + } + + /** + * Returns the actual set service + * + * @return callable + */ + public function getService() + { + return $this->options['service']; + } + + /** + * Sets a new callback for service validation + * + * @param callable $service + * @return $this + * @throws InvalidArgumentException On invalid service callback. + */ + public function setService($service) + { + if (! is_callable($service)) { + throw new InvalidArgumentException('Invalid callback given'); + } + + $this->options['service'] = $service; + return $this; + } + + /** + * Returns true if and only if $value follows the Luhn algorithm (mod-10 checksum) + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + $this->setValue($value); + + if (! is_string($value)) { + $this->error(self::INVALID, $value); + return false; + } + + if (! ctype_digit($value)) { + $this->error(self::CONTENT, $value); + return false; + } + + $length = strlen($value); + $types = $this->getType(); + $foundp = false; + $foundl = false; + foreach ($types as $type) { + foreach ($this->cardType[$type] as $prefix) { + if (0 === strpos($value, (string) $prefix)) { + $foundp = true; + if (in_array($length, $this->cardLength[$type])) { + $foundl = true; + break 2; + } + } + } + } + + if ($foundp === false) { + $this->error(self::PREFIX, $value); + return false; + } + + if ($foundl === false) { + $this->error(self::LENGTH, $value); + return false; + } + + $sum = 0; + $weight = 2; + + for ($i = $length - 2; $i >= 0; $i--) { + $digit = $weight * $value[$i]; + $sum += floor($digit / 10) + $digit % 10; + $weight = $weight % 2 + 1; + } + + $checksum = (10 - $sum % 10) % 10; + if ((string) $checksum !== $value[$length - 1]) { + $this->error(self::CHECKSUM, $value); + return false; + } + + $service = $this->getService(); + if (! empty($service)) { + try { + $callback = new Callback($service); + $callback->setOptions($this->getType()); + if (! $callback->isValid($value)) { + $this->error(self::SERVICE, $value); + return false; + } + } catch (Exception $e) { + $this->error(self::SERVICEFAILURE, $value); + return false; + } + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Csrf.php b/lib/laminas/laminas-validator/src/Csrf.php new file mode 100644 index 0000000000..332d366bad --- /dev/null +++ b/lib/laminas/laminas-validator/src/Csrf.php @@ -0,0 +1,376 @@ + 'The form submitted did not originate from the expected site', + ]; + + /** + * Actual hash used. + * + * @var mixed + */ + protected $hash; + + /** + * Static cache of the session names to generated hashes + * + * @todo unused, left here to avoid BC breaks + * @var array + */ + protected static $hashCache; + + /** + * Name of CSRF element (used to create non-colliding hashes) + * + * @var string + */ + protected $name = 'csrf'; + + /** + * Salt for CSRF token + * + * @var string + */ + protected $salt = 'salt'; + + /** @var SessionContainer */ + protected $session; + + /** + * TTL for CSRF token + * + * @var int|null + */ + protected $timeout = 300; + + /** + * Constructor + * + * @param array|Traversable $options + */ + public function __construct($options = []) + { + parent::__construct($options); + + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + + if (! is_array($options)) { + $options = (array) $options; + } + + foreach ($options as $key => $value) { + switch (strtolower($key)) { + case 'name': + $this->setName($value); + break; + case 'salt': + $this->setSalt($value); + break; + case 'session': + $this->setSession($value); + break; + case 'timeout': + $this->setTimeout($value); + break; + default: + // ignore unknown options + break; + } + } + } + + /** + * Does the provided token match the one generated? + * + * @param string $value + * @param mixed $context + * @return bool + */ + public function isValid($value, $context = null) + { + if (! is_string($value)) { + return false; + } + + $this->setValue($value); + + $tokenId = $this->getTokenIdFromHash($value); + $hash = $this->getValidationToken($tokenId); + + $tokenFromValue = $this->getTokenFromHash($value); + $tokenFromHash = $this->getTokenFromHash($hash); + + if (! $tokenFromValue || ! $tokenFromHash || ($tokenFromValue !== $tokenFromHash)) { + $this->error(self::NOT_SAME); + return false; + } + + return true; + } + + /** + * Set CSRF name + * + * @param string $name + * @return $this + */ + public function setName($name) + { + $this->name = (string) $name; + return $this; + } + + /** + * Get CSRF name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set session container + * + * @return $this + */ + public function setSession(SessionContainer $session) + { + $this->session = $session; + if ($this->hash) { + $this->initCsrfToken(); + } + return $this; + } + + /** + * Get session container + * + * Instantiate session container if none currently exists + * + * @return SessionContainer + */ + public function getSession() + { + if (null === $this->session) { + // Using fully qualified name, to ensure polyfill class alias is used + $this->session = new SessionContainer($this->getSessionName()); + } + return $this->session; + } + + /** + * Salt for CSRF token + * + * @param string $salt + * @return $this + */ + public function setSalt($salt) + { + $this->salt = (string) $salt; + return $this; + } + + /** + * Retrieve salt for CSRF token + * + * @return string + */ + public function getSalt() + { + return $this->salt; + } + + /** + * Retrieve CSRF token + * + * If no CSRF token currently exists, or should be regenerated, + * generates one. + * + * @param bool $regenerate default false + * @return string + */ + public function getHash($regenerate = false) + { + if ((null === $this->hash) || $regenerate) { + $this->generateHash(); + } + return $this->hash; + } + + /** + * Get session namespace for CSRF token + * + * Generates a session namespace based on salt, element name, and class. + * + * @return string + */ + public function getSessionName() + { + return str_replace('\\', '_', self::class) . '_' + . $this->getSalt() . '_' + . strtr($this->getName(), ['[' => '_', ']' => '']); + } + + /** + * Set timeout for CSRF session token + * + * @param int|null $ttl + * @return $this + */ + public function setTimeout($ttl) + { + $this->timeout = $ttl !== null ? (int) $ttl : null; + return $this; + } + + /** + * Get CSRF session token timeout + * + * @return int|null + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Initialize CSRF token in session + * + * @return void + */ + protected function initCsrfToken() + { + $session = $this->getSession(); + $timeout = $this->getTimeout(); + if (null !== $timeout) { + $session->setExpirationSeconds($timeout); + } + + $hash = $this->getHash(); + $token = $this->getTokenFromHash($hash); + $tokenId = $this->getTokenIdFromHash($hash); + + if (! $session->tokenList) { + $session->tokenList = []; + } + $session->tokenList[$tokenId] = $token; + $session->hash = $hash; // @todo remove this, left for BC + } + + /** + * Generate CSRF token + * + * Generates CSRF token and stores both in {@link $hash} and element + * value. + * + * @return void + */ + protected function generateHash() + { + $token = md5($this->getSalt() . random_bytes(32) . $this->getName()); + + $this->hash = $this->formatHash($token, $this->generateTokenId()); + + $this->setValue($this->hash); + $this->initCsrfToken(); + } + + /** + * @return string + */ + protected function generateTokenId() + { + return md5(random_bytes(32)); + } + + /** + * Get validation token + * + * Retrieve token from session, if it exists. + * + * @param string $tokenId + * @return null|string + */ + protected function getValidationToken($tokenId = null) + { + $session = $this->getSession(); + + /** + * if no tokenId is passed we revert to the old behaviour + * + * @todo remove, here for BC + */ + if (! $tokenId && isset($session->hash)) { + return $session->hash; + } + + if ($tokenId && isset($session->tokenList[$tokenId])) { + return $this->formatHash($session->tokenList[$tokenId], $tokenId); + } + + return null; + } + + /** + * @return string + */ + protected function formatHash(string $token, string $tokenId) + { + return sprintf('%s-%s', $token, $tokenId); + } + + protected function getTokenFromHash(?string $hash): ?string + { + if (null === $hash) { + return null; + } + + $data = explode('-', $hash); + return $data[0] ?: null; + } + + protected function getTokenIdFromHash(string $hash): ?string + { + $data = explode('-', $hash); + + if (! isset($data[1])) { + return null; + } + + return $data[1]; + } +} diff --git a/lib/laminas/laminas-validator/src/Date.php b/lib/laminas/laminas-validator/src/Date.php new file mode 100644 index 0000000000..2d85ade247 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Date.php @@ -0,0 +1,232 @@ + 'Invalid type given. String, integer, array or DateTime expected', + self::INVALID_DATE => 'The input does not appear to be a valid date', + self::FALSEFORMAT => "The input does not fit the date format '%format%'", + ]; + + /** @var string[] */ + protected $messageVariables = [ + 'format' => 'format', + ]; + + /** @var string */ + protected $format = self::FORMAT_DEFAULT; + + /** @var bool */ + protected $strict = false; + + /** + * Sets validator options + * + * @param string|array|Traversable $options OPTIONAL + */ + public function __construct($options = []) + { + if ($options instanceof Traversable) { + $options = iterator_to_array($options); + } elseif (! is_array($options)) { + $options = func_get_args(); + $temp['format'] = array_shift($options); + $options = $temp; + } + + parent::__construct($options); + } + + /** + * Returns the format option + * + * @return string + */ + public function getFormat() + { + return $this->format; + } + + /** + * Sets the format option + * + * Format cannot be null. It will always default to 'Y-m-d', even + * if null is provided. + * + * @param string|null $format + * @return $this provides a fluent interface + * @todo validate the format + */ + public function setFormat($format = self::FORMAT_DEFAULT) + { + $this->format = empty($format) ? self::FORMAT_DEFAULT : $format; + return $this; + } + + public function setStrict(bool $strict): self + { + $this->strict = $strict; + return $this; + } + + public function isStrict(): bool + { + return $this->strict; + } + + /** + * Returns true if $value is a DateTimeInterface instance or can be converted into one. + * + * @param string|numeric|array|DateTimeInterface $value + * @return bool + */ + public function isValid($value) + { + $this->setValue($value); + + $date = $this->convertToDateTime($value); + if (! $date) { + $this->error(self::INVALID_DATE); + return false; + } + + if ($this->isStrict() && $date->format($this->getFormat()) !== $value) { + $this->error(self::FALSEFORMAT); + return false; + } + + return true; + } + + /** + * Attempts to convert an int, string, or array to a DateTime object + * + * @param string|numeric|array|DateTimeInterface $param + * @param bool $addErrors + * @return false|DateTime + */ + protected function convertToDateTime($param, $addErrors = true) + { + if ($param instanceof DateTime) { + return $param; + } + + if ($param instanceof DateTimeImmutable) { + return DateTime::createFromImmutable($param); + } + + $type = gettype($param); + switch ($type) { + case 'string': + return $this->convertString($param, $addErrors); + case 'integer': + return $this->convertInteger($param); + case 'double': + return $this->convertDouble($param); + case 'array': + return $this->convertArray($param, $addErrors); + } + + if ($addErrors) { + $this->error(self::INVALID); + } + + return false; + } + + /** + * Attempts to convert an integer into a DateTime object + * + * @param integer $value + * @return false|DateTime + */ + protected function convertInteger($value) + { + return DateTime::createFromFormat('U', (string) $value); + } + + /** + * Attempts to convert an double into a DateTime object + * + * @param double $value + * @return false|DateTime + */ + protected function convertDouble($value) + { + return DateTime::createFromFormat('U', (string) $value); + } + + /** + * Attempts to convert a string into a DateTime object + * + * @param string $value + * @param bool $addErrors + * @return false|DateTime + */ + protected function convertString($value, $addErrors = true) + { + $date = DateTime::createFromFormat($this->format, $value); + + // Invalid dates can show up as warnings (ie. "2007-02-99") + // and still return a DateTime object. + $errors = DateTime::getLastErrors(); + if ($errors['warning_count'] > 0) { + if ($addErrors) { + $this->error(self::FALSEFORMAT); + } + return false; + } + + return $date; + } + + /** + * Implodes the array into a string and proxies to {@link convertString()}. + * + * @param array $value + * @param bool $addErrors + * @return false|DateTime + * @todo enhance the implosion + */ + protected function convertArray(array $value, $addErrors = true) + { + return $this->convertString(implode('-', $value), $addErrors); + } +} diff --git a/lib/laminas/laminas-validator/src/DateStep.php b/lib/laminas/laminas-validator/src/DateStep.php new file mode 100644 index 0000000000..2ec86dac0f --- /dev/null +++ b/lib/laminas/laminas-validator/src/DateStep.php @@ -0,0 +1,525 @@ + 'Invalid type given. String, integer, array or DateTime expected', + self::INVALID_DATE => 'The input does not appear to be a valid date', + self::FALSEFORMAT => "The input does not fit the date format '%format%'", + self::NOT_STEP => 'The input is not a valid step', + ]; + + /** + * Optional base date value + * + * @var string|int|DateTimeInterface + */ + protected $baseValue = '1970-01-01T00:00:00Z'; + + /** + * Date step interval (defaults to 1 day). + * Uses the DateInterval specification. + * + * @var DateInterval + */ + protected $step; + + /** + * Optional timezone to be used when the baseValue + * and validation values do not contain timezone info + * + * @var DateTimeZone + */ + protected $timezone; + + /** + * Set default options for this instance + * + * @param string|array|Traversable $options + */ + public function __construct($options = []) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } elseif (! is_array($options)) { + $options = func_get_args(); + $temp = []; + $temp['baseValue'] = array_shift($options); + if (! empty($options)) { + $temp['step'] = array_shift($options); + } + if (! empty($options)) { + $temp['format'] = array_shift($options); + } + if (! empty($options)) { + $temp['timezone'] = array_shift($options); + } + + $options = $temp; + } + + if (! isset($options['step'])) { + $options['step'] = new DateInterval('P1D'); + } + if (! isset($options['timezone'])) { + $options['timezone'] = new DateTimeZone(date_default_timezone_get()); + } + + parent::__construct($options); + } + + /** + * Sets the base value from which the step should be computed + * + * @param string|int|DateTimeInterface $baseValue + * @return $this + */ + public function setBaseValue($baseValue) + { + $this->baseValue = $baseValue; + return $this; + } + + /** + * Returns the base value from which the step should be computed + * + * @return string|int|DateTimeInterface + */ + public function getBaseValue() + { + return $this->baseValue; + } + + /** + * Sets the step date interval + * + * @return $this + */ + public function setStep(DateInterval $step) + { + $this->step = $step; + return $this; + } + + /** + * Returns the step date interval + * + * @return DateInterval + */ + public function getStep() + { + return $this->step; + } + + /** + * Returns the timezone option + * + * @return DateTimeZone + */ + public function getTimezone() + { + return $this->timezone; + } + + /** + * Sets the timezone option + * + * @return $this + */ + public function setTimezone(DateTimeZone $timezone) + { + $this->timezone = $timezone; + return $this; + } + + /** + * Supports formats with ISO week (W) definitions + * + * @see Date::convertString() + * + * @param string $value + * @param bool $addErrors + * @return DateTime|false + */ + protected function convertString($value, $addErrors = true) + { + // Custom week format support + if ( + strpos($this->format, 'Y-\WW') === 0 + && preg_match('/^([0-9]{4})\-W([0-9]{2})/', $value, $matches) + ) { + $date = new DateTime(); + $date->setISODate((int) $matches[1], (int) $matches[2]); + } else { + $date = DateTime::createFromFormat($this->format, $value, new DateTimeZone('UTC')); + } + + // Invalid dates can show up as warnings (ie. "2007-02-99") + // and still return a DateTime object. + $errors = DateTime::getLastErrors(); + if ($errors['warning_count'] > 0) { + if ($addErrors) { + $this->error(self::FALSEFORMAT); + } + return false; + } + + return $date; + } + + /** + * Returns true if a date is within a valid step + * + * @param string|int|DateTimeInterface $value + * @return bool + * @throws Exception\InvalidArgumentException + */ + public function isValid($value) + { + if (! parent::isValid($value)) { + return false; + } + + $valueDate = $this->convertToDateTime($value, false); // avoid duplicate errors + $baseDate = $this->convertToDateTime($this->baseValue, false); + + if (false === $valueDate || false === $baseDate) { + return false; + } + + $step = $this->getStep(); + + // Same date? + // phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators.DisallowedEqualOperator + if ($valueDate == $baseDate) { + return true; + } + + // Optimization for simple intervals. + // Handle intervals of just one date or time unit. + $intervalParts = explode('|', $step->format('%y|%m|%d|%h|%i|%s')); + $intervalParts = array_map('intval', $intervalParts); + $partCounts = array_count_values($intervalParts); + + $unitKeys = ['years', 'months', 'days', 'hours', 'minutes', 'seconds']; + $intervalParts = array_combine($unitKeys, $intervalParts); + + // Get absolute time difference to avoid special cases of missing/added time + $absoluteValueDate = new DateTime($valueDate->format('Y-m-d H:i:s'), new DateTimeZone('UTC')); + $absoluteBaseDate = new DateTime($baseDate->format('Y-m-d H:i:s'), new DateTimeZone('UTC')); + + $timeDiff = $absoluteValueDate->diff($absoluteBaseDate, true); + $diffParts = array_map('intval', explode('|', $timeDiff->format('%y|%m|%d|%h|%i|%s'))); + $diffParts = array_combine($unitKeys, $diffParts); + + if (5 === $partCounts[0]) { + // Find the unit with the non-zero interval + $intervalUnit = 'days'; + $stepValue = 1; + foreach ($intervalParts as $key => $value) { + if (0 !== $value) { + $intervalUnit = $key; + $stepValue = $value; + break; + } + } + + // Check date units + if (in_array($intervalUnit, ['years', 'months', 'days'])) { + switch ($intervalUnit) { + case 'years': + if ( + 0 === $diffParts['months'] && 0 === $diffParts['days'] + && 0 === $diffParts['hours'] && 0 === $diffParts['minutes'] + && 0 === $diffParts['seconds'] + ) { + if (($diffParts['years'] % $stepValue) === 0) { + return true; + } + } + break; + case 'months': + if ( + 0 === $diffParts['days'] && 0 === $diffParts['hours'] + && 0 === $diffParts['minutes'] && 0 === $diffParts['seconds'] + ) { + $months = ($diffParts['years'] * 12) + $diffParts['months']; + if (($months % $stepValue) === 0) { + return true; + } + } + break; + case 'days': + if ( + 0 === $diffParts['hours'] && 0 === $diffParts['minutes'] + && 0 === $diffParts['seconds'] + ) { + $days = (int) $timeDiff->format('%a'); // Total days + if (($days % $stepValue) === 0) { + return true; + } + } + break; + } + $this->error(self::NOT_STEP); + return false; + } + + // Check time units + if (in_array($intervalUnit, ['hours', 'minutes', 'seconds'])) { + // Simple test if $stepValue is 1. + if (1 === $stepValue) { + if ( + 'hours' === $intervalUnit + && 0 === $diffParts['minutes'] && 0 === $diffParts['seconds'] + ) { + return true; + } elseif ('minutes' === $intervalUnit && 0 === $diffParts['seconds']) { + return true; + } elseif ('seconds' === $intervalUnit) { + return true; + } + + $this->error(self::NOT_STEP); + + return false; + } + + // Simple test for same day, when using default baseDate + if ( + $baseDate->format('Y-m-d') === $valueDate->format('Y-m-d') + && $baseDate->format('Y-m-d') === '1970-01-01' + ) { + switch ($intervalUnit) { + case 'hours': + if (0 === $diffParts['minutes'] && 0 === $diffParts['seconds']) { + if (($diffParts['hours'] % $stepValue) === 0) { + return true; + } + } + break; + case 'minutes': + if (0 === $diffParts['seconds']) { + $minutes = ($diffParts['hours'] * 60) + $diffParts['minutes']; + if (($minutes % $stepValue) === 0) { + return true; + } + } + break; + case 'seconds': + $seconds = ($diffParts['hours'] * 60 * 60) + + ($diffParts['minutes'] * 60) + + $diffParts['seconds']; + if (($seconds % $stepValue) === 0) { + return true; + } + break; + } + $this->error(self::NOT_STEP); + return false; + } + } + } + + return $this->fallbackIncrementalIterationLogic($baseDate, $valueDate, $intervalParts, $diffParts, $step); + } + + /** + * Fall back to slower (but accurate) method for complex intervals. + * Keep adding steps to the base date until a match is found + * or until the value is exceeded. + * + * This is really slow if the interval is small, especially if the + * default base date of 1/1/1970 is used. We can skip a chunk of + * iterations by starting at the lower bound of steps needed to reach + * the target + * + * @param int[] $intervalParts + * @param int[] $diffParts + * @throws Exception\InvalidArgumentException + */ + private function fallbackIncrementalIterationLogic( + DateTimeInterface $baseDate, + DateTimeInterface $valueDate, + array $intervalParts, + array $diffParts, + DateInterval $step + ): bool { + [$minSteps, $requiredIterations] = $this->computeMinStepAndRequiredIterations($intervalParts, $diffParts); + $minimumInterval = $this->computeMinimumInterval($intervalParts, $minSteps); + $isIncrementalStepping = $baseDate < $valueDate; + + if (! ($baseDate instanceof DateTime || $baseDate instanceof DateTimeImmutable)) { + throw new Exception\InvalidArgumentException(sprintf( + 'Function %s requires the baseDate to be a DateTime or DateTimeImmutable instance.', + __FUNCTION__ + )); + } + + for ($offsetIterations = 0; $offsetIterations < $requiredIterations; $offsetIterations += 1) { + if ($isIncrementalStepping) { + $baseDate = $baseDate->add($minimumInterval); + } else { + $baseDate = $baseDate->sub($minimumInterval); + } + } + + while ( + ($isIncrementalStepping && $baseDate < $valueDate) + || (! $isIncrementalStepping && $baseDate > $valueDate) + ) { + if ($isIncrementalStepping) { + $baseDate = $baseDate->add($step); + } else { + $baseDate = $baseDate->sub($step); + } + + // phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators.DisallowedEqualOperator + if ($baseDate == $valueDate) { + return true; + } + } + + $this->error(self::NOT_STEP); + + return false; + } + + /** + * Computes minimum interval to use for iterations while checking steps + * + * @param int[] $intervalParts + * @param int|float $minSteps + */ + private function computeMinimumInterval(array $intervalParts, $minSteps): DateInterval + { + return new DateInterval(sprintf( + 'P%dY%dM%dDT%dH%dM%dS', + $intervalParts['years'] * $minSteps, + $intervalParts['months'] * $minSteps, + $intervalParts['days'] * $minSteps, + $intervalParts['hours'] * $minSteps, + $intervalParts['minutes'] * $minSteps, + $intervalParts['seconds'] * $minSteps + )); + } + + /** + * @param int[] $intervalParts + * @param int[] $diffParts + * @return int[] (ordered tuple containing minimum steps and required step iterations + * @psalm-return array{0: int, 1: int} + */ + private function computeMinStepAndRequiredIterations(array $intervalParts, array $diffParts): array + { + $minSteps = $this->computeMinSteps($intervalParts, $diffParts); + + // If we use PHP_INT_MAX DateInterval::__construct falls over with a bad format error + // before we reach the max on 64 bit machines + $maxInteger = min(2 ** 31, PHP_INT_MAX); + // check for integer overflow and split $minimum interval if needed + $maximumInterval = max($intervalParts); + $requiredStepIterations = 1; + + if (($minSteps * $maximumInterval) > $maxInteger) { + $requiredStepIterations = ceil(($minSteps * $maximumInterval) / $maxInteger); + $minSteps = floor($minSteps / $requiredStepIterations); + } + + return [(int) $minSteps, $minSteps ? (int) $requiredStepIterations : 0]; + } + + /** + * Multiply the step interval by the lower bound of steps to reach the target + * + * @param int[] $intervalParts + * @param int[] $diffParts + * @return float|int + */ + private function computeMinSteps(array $intervalParts, array $diffParts) + { + $intervalMaxSeconds = $this->computeIntervalMaxSeconds($intervalParts); + + return 0 === $intervalMaxSeconds + ? 0 + : max(floor($this->computeDiffMinSeconds($diffParts) / $intervalMaxSeconds) - 1, 0); + } + + /** + * Get upper bound of the given interval in seconds + * Converts a given `$intervalParts` array into seconds + * + * @param int[] $intervalParts + */ + private function computeIntervalMaxSeconds(array $intervalParts): int + { + return ($intervalParts['years'] * 60 * 60 * 24 * 366) + + ($intervalParts['months'] * 60 * 60 * 24 * 31) + + ($intervalParts['days'] * 60 * 60 * 24) + + ($intervalParts['hours'] * 60 * 60) + + ($intervalParts['minutes'] * 60) + + $intervalParts['seconds']; + } + + /** + * Get lower bound of difference in secondss + * Converts a given `$diffParts` array into seconds + * + * @param int[] $diffParts + */ + private function computeDiffMinSeconds(array $diffParts): int + { + return ($diffParts['years'] * 60 * 60 * 24 * 365) + + ($diffParts['months'] * 60 * 60 * 24 * 28) + + ($diffParts['days'] * 60 * 60 * 24) + + ($diffParts['hours'] * 60 * 60) + + ($diffParts['minutes'] * 60) + + $diffParts['seconds']; + } +} diff --git a/lib/laminas/laminas-validator/src/Db/AbstractDb.php b/lib/laminas/laminas-validator/src/Db/AbstractDb.php new file mode 100755 index 0000000000..495cdf113b --- /dev/null +++ b/lib/laminas/laminas-validator/src/Db/AbstractDb.php @@ -0,0 +1,315 @@ + Message templates */ + protected $messageTemplates = [ + self::ERROR_NO_RECORD_FOUND => 'No record matching the input was found', + self::ERROR_RECORD_FOUND => 'A record matching the input was found', + ]; + + /** + * Select object to use. can be set, or will be auto-generated + * + * @var Select + */ + protected $select; + + /** @var string */ + protected $schema; + + /** @var string */ + protected $table = ''; + + /** @var string */ + protected $field = ''; + + /** @var mixed */ + protected $exclude; + + /** + * Provides basic configuration for use with Laminas\Validator\Db Validators + * Setting $exclude allows a single record to be excluded from matching. + * Exclude can either be a String containing a where clause, or an array with `field` and `value` keys + * to define the where clause added to the sql. + * A database adapter may optionally be supplied to avoid using the registered default adapter. + * + * The following option keys are supported: + * 'table' => The database table to validate against + * 'schema' => The schema keys + * 'field' => The field to check for a match + * 'exclude' => An optional where clause or field/value pair to exclude from the query + * 'adapter' => An optional database adapter to use + * + * @param array|Traversable|Select $options Options to use for this validator + * @throws InvalidArgumentException + */ + public function __construct($options = null) + { + parent::__construct($options); + + if ($options instanceof Select) { + $this->setSelect($options); + return; + } + + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } elseif (func_num_args() > 1) { + $options = func_get_args(); + $firstArgument = array_shift($options); + if (is_array($firstArgument)) { + $temp = ArrayUtils::iteratorToArray($firstArgument); + } else { + $temp['table'] = $firstArgument; + } + + $temp['field'] = array_shift($options); + + if (! empty($options)) { + $temp['exclude'] = array_shift($options); + } + + if (! empty($options)) { + $temp['adapter'] = array_shift($options); + } + + $options = $temp; + } + + if (! array_key_exists('table', $options) && ! array_key_exists('schema', $options)) { + throw new Exception\InvalidArgumentException('Table or Schema option missing!'); + } + + if (! array_key_exists('field', $options)) { + throw new Exception\InvalidArgumentException('Field option missing!'); + } + + if (array_key_exists('adapter', $options)) { + $this->setAdapter($options['adapter']); + } + + if (array_key_exists('exclude', $options)) { + $this->setExclude($options['exclude']); + } + + $this->setField($options['field']); + if (array_key_exists('table', $options)) { + $this->setTable($options['table']); + } + + if (array_key_exists('schema', $options)) { + $this->setSchema($options['schema']); + } + } + + /** + * Returns the set adapter + * + * @throws RuntimeException When no database adapter is defined. + * @return DbAdapter + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * Sets a new database adapter + * + * @return self Provides a fluent interface + */ + public function setAdapter(DbAdapter $adapter) + { + return $this->setDbAdapter($adapter); + } + + /** + * Returns the set exclude clause + * + * @return string|array + */ + public function getExclude() + { + return $this->exclude; + } + + /** + * Sets a new exclude clause + * + * @param string|array $exclude + * @return $this Provides a fluent interface + */ + public function setExclude($exclude) + { + $this->exclude = $exclude; + $this->select = null; + return $this; + } + + /** + * Returns the set field + * + * @return string|array + */ + public function getField() + { + return $this->field; + } + + /** + * Sets a new field + * + * @param string $field + * @return $this + */ + public function setField($field) + { + $this->field = (string) $field; + $this->select = null; + return $this; + } + + /** + * Returns the set table + * + * @return string + */ + public function getTable() + { + return $this->table; + } + + /** + * Sets a new table + * + * @param string $table + * @return $this Provides a fluent interface + */ + public function setTable($table) + { + $this->table = (string) $table; + $this->select = null; + return $this; + } + + /** + * Returns the set schema + * + * @return string + */ + public function getSchema() + { + return $this->schema; + } + + /** + * Sets a new schema + * + * @param string $schema + * @return $this Provides a fluent interface + */ + public function setSchema($schema) + { + $this->schema = $schema; + $this->select = null; + return $this; + } + + /** + * Sets the select object to be used by the validator + * + * @return $this Provides a fluent interface + */ + public function setSelect(Select $select) + { + $this->select = $select; + return $this; + } + + /** + * Gets the select object to be used by the validator. + * If no select object was supplied to the constructor, + * then it will auto-generate one from the given table, + * schema, field, and adapter options. + * + * @return Select The Select object which will be used + */ + public function getSelect() + { + if ($this->select instanceof Select) { + return $this->select; + } + + // Build select object + $select = new Select(); + $tableIdentifier = new TableIdentifier($this->table, $this->schema); + $select->from($tableIdentifier)->columns([$this->field]); + $select->where->equalTo($this->field, null); + + if ($this->exclude !== null) { + if (is_array($this->exclude)) { + $select->where->notEqualTo( + $this->exclude['field'], + $this->exclude['value'] + ); + } else { + $select->where($this->exclude); + } + } + + $this->select = $select; + + return $this->select; + } + + /** + * Run query and returns matches, or null if no matches are found. + * + * @param string $value + * @return array when matches are found. + */ + protected function query($value) + { + $sql = new Sql($this->getAdapter()); + $select = $this->getSelect(); + $statement = $sql->prepareStatementForSqlObject($select); + $parameters = $statement->getParameterContainer(); + $parameters['where1'] = $value; + $result = $statement->execute(); + + return $result->current(); + } +} diff --git a/lib/laminas/laminas-validator/src/Db/NoRecordExists.php b/lib/laminas/laminas-validator/src/Db/NoRecordExists.php new file mode 100644 index 0000000000..573a81ff8e --- /dev/null +++ b/lib/laminas/laminas-validator/src/Db/NoRecordExists.php @@ -0,0 +1,36 @@ +adapter) { + throw new Exception\RuntimeException('No database adapter present'); + } + + $valid = true; + $this->setValue($value); + + $result = $this->query($value); + if ($result) { + $valid = false; + $this->error(self::ERROR_RECORD_FOUND); + } + + return $valid; + } +} diff --git a/lib/laminas/laminas-validator/src/Db/RecordExists.php b/lib/laminas/laminas-validator/src/Db/RecordExists.php new file mode 100644 index 0000000000..bbc57db21c --- /dev/null +++ b/lib/laminas/laminas-validator/src/Db/RecordExists.php @@ -0,0 +1,36 @@ +adapter) { + throw new Exception\RuntimeException('No database adapter present'); + } + + $valid = true; + $this->setValue($value); + + $result = $this->query($value); + if (! $result) { + $valid = false; + $this->error(self::ERROR_NO_RECORD_FOUND); + } + + return $valid; + } +} diff --git a/lib/laminas/laminas-validator/src/Digits.php b/lib/laminas/laminas-validator/src/Digits.php new file mode 100644 index 0000000000..34568a5c56 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Digits.php @@ -0,0 +1,66 @@ + 'The input must contain only digits', + self::STRING_EMPTY => 'The input is an empty string', + self::INVALID => 'Invalid type given. String, integer or float expected', + ]; + + /** + * Returns true if and only if $value only contains digit characters + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value) && ! is_int($value) && ! is_float($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue((string) $value); + + if ('' === $this->getValue()) { + $this->error(self::STRING_EMPTY); + return false; + } + + if (null === static::$filter) { + static::$filter = new DigitsFilter(); + } + + if ($this->getValue() !== static::$filter->filter($this->getValue())) { + $this->error(self::NOT_DIGITS); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/EmailAddress.php b/lib/laminas/laminas-validator/src/EmailAddress.php new file mode 100644 index 0000000000..e03070deb8 --- /dev/null +++ b/lib/laminas/laminas-validator/src/EmailAddress.php @@ -0,0 +1,617 @@ + */ + protected $messageTemplates = [ + self::INVALID => "Invalid type given. String expected", + self::INVALID_FORMAT => "The input is not a valid email address. Use the basic format local-part@hostname", + self::INVALID_HOSTNAME => "'%hostname%' is not a valid hostname for the email address", + self::INVALID_MX_RECORD => "'%hostname%' does not appear to have any valid MX or A records for the email address", + self::INVALID_SEGMENT => "'%hostname%' is not in a routable network segment. The email address should not be resolved from public network", + self::DOT_ATOM => "'%localPart%' can not be matched against dot-atom format", + self::QUOTED_STRING => "'%localPart%' can not be matched against quoted-string format", + self::INVALID_LOCAL_PART => "'%localPart%' is not a valid local part for the email address", + self::LENGTH_EXCEEDED => "The input exceeds the allowed length", + ]; + + // phpcs:enable + + /** @var array */ + protected $messageVariables = [ + 'hostname' => 'hostname', + 'localPart' => 'localPart', + ]; + + /** @var string */ + protected $hostname; + + /** @var string */ + protected $localPart; + + /** + * Returns the found mx record information + * + * @var array + */ + protected $mxRecord = []; + + /** + * Internal options array + * + * @var array + */ + protected $options = [ + 'useMxCheck' => false, + 'useDeepMxCheck' => false, + 'useDomainCheck' => true, + 'allow' => Hostname::ALLOW_DNS, + 'strict' => true, + 'hostnameValidator' => null, + ]; + + /** + * Instantiates hostname validator for local use + * + * The following additional option keys are supported: + * 'hostnameValidator' => A hostname validator, see Laminas\Validator\Hostname + * 'allow' => Options for the hostname validator, see Laminas\Validator\Hostname::ALLOW_* + * 'strict' => Whether to adhere to strictest requirements in the spec + * 'useMxCheck' => If MX check should be enabled, boolean + * 'useDeepMxCheck' => If a deep MX check should be done, boolean + * + * @param array|Traversable $options OPTIONAL + */ + public function __construct($options = []) + { + if (! is_array($options)) { + $options = func_get_args(); + $temp['allow'] = array_shift($options); + if (! empty($options)) { + $temp['useMxCheck'] = array_shift($options); + } + + if (! empty($options)) { + $temp['hostnameValidator'] = array_shift($options); + } + + $options = $temp; + } + + parent::__construct($options); + } + + /** + * Sets the validation failure message template for a particular key + * Adds the ability to set messages to the attached hostname validator + * + * @param string $messageString + * @param string $messageKey OPTIONAL + * @return AbstractValidator Provides a fluent interface + */ + public function setMessage($messageString, $messageKey = null) + { + if ($messageKey === null) { + $this->getHostnameValidator()->setMessage($messageString); + parent::setMessage($messageString); + return $this; + } + + if (! isset($this->messageTemplates[$messageKey])) { + $this->getHostnameValidator()->setMessage($messageString, $messageKey); + } else { + parent::setMessage($messageString, $messageKey); + } + + return $this; + } + + /** + * Returns the set hostname validator + * + * If was not previously set then lazy load a new one + * + * @return Hostname + */ + public function getHostnameValidator() + { + if (! isset($this->options['hostnameValidator'])) { + $this->options['hostnameValidator'] = new Hostname($this->getAllow()); + } + + return $this->options['hostnameValidator']; + } + + /** + * @param Hostname $hostnameValidator OPTIONAL + * @return $this Provides a fluent interface + */ + public function setHostnameValidator(?Hostname $hostnameValidator = null) + { + $this->options['hostnameValidator'] = $hostnameValidator; + + return $this; + } + + /** + * Returns the allow option of the attached hostname validator + * + * @return int + */ + public function getAllow() + { + return $this->options['allow']; + } + + /** + * Sets the allow option of the hostname validator to use + * + * @param int $allow + * @return $this Provides a fluent interface + */ + public function setAllow($allow) + { + $this->options['allow'] = $allow; + if (isset($this->options['hostnameValidator'])) { + $this->options['hostnameValidator']->setAllow($allow); + } + + return $this; + } + + /** + * Whether MX checking via getmxrr is supported or not + * + * @return bool + */ + public function isMxSupported() + { + return function_exists('getmxrr'); + } + + /** + * Returns the set validateMx option + * + * @return bool + */ + public function getMxCheck() + { + return $this->options['useMxCheck']; + } + + /** + * Set whether we check for a valid MX record via DNS + * + * This only applies when DNS hostnames are validated + * + * @param bool $mx Set allowed to true to validate for MX records, and false to not validate them + * @return $this Fluid Interface + */ + public function useMxCheck($mx) + { + $this->options['useMxCheck'] = (bool) $mx; + return $this; + } + + /** + * Returns the set deepMxCheck option + * + * @return bool + */ + public function getDeepMxCheck() + { + return $this->options['useDeepMxCheck']; + } + + /** + * Use deep validation for MX records + * + * @param bool $deep Set deep to true to perform a deep validation process for MX records + * @return $this Fluid Interface + */ + public function useDeepMxCheck($deep) + { + $this->options['useDeepMxCheck'] = (bool) $deep; + return $this; + } + + /** + * Returns the set domainCheck option + * + * @return bool + */ + public function getDomainCheck() + { + return $this->options['useDomainCheck']; + } + + /** + * Sets if the domain should also be checked + * or only the local part of the email address + * + * @param bool $domain + * @return $this Fluid Interface + */ + public function useDomainCheck($domain = true) + { + $this->options['useDomainCheck'] = (bool) $domain; + return $this; + } + + /** + * Returns if the given host is reserved + * + * The following addresses are seen as reserved + * '0.0.0.0/8', '10.0.0.0/8', '127.0.0.0/8' + * '100.64.0.0/10' + * '172.16.0.0/12' + * '198.18.0.0/15' + * '169.254.0.0/16', '192.168.0.0/16' + * '192.0.2.0/24', '192.88.99.0/24', '198.51.100.0/24', '203.0.113.0/24' + * '224.0.0.0/4', '240.0.0.0/4' + * + * @see http://en.wikipedia.org/wiki/Reserved_IP_addresses + * + * As of RFC5753 (JAN 2010), the following blocks are no longer reserved: + * - 128.0.0.0/16 + * - 191.255.0.0/16 + * - 223.255.255.0/24 + * @see http://tools.ietf.org/html/rfc5735#page-6 + * + * As of RFC6598 (APR 2012), the following blocks are now reserved: + * - 100.64.0.0/10 + * @see http://tools.ietf.org/html/rfc6598#section-7 + * + * @param string $host + * @return bool Returns false when minimal one of the given addresses is not reserved + */ + protected function isReserved($host) + { + if (! preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $host)) { + $host = gethostbynamel($host); + } else { + $host = [$host]; + } + + if (empty($host)) { + return false; + } + + foreach ($host as $server) { + // @codingStandardsIgnoreStart + // Search for 0.0.0.0/8, 10.0.0.0/8, 127.0.0.0/8 + if (!preg_match('/^(0|10|127)(\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))){3}$/', $server) && + // Search for 100.64.0.0/10 + !preg_match('/^100\.(6[0-4]|[7-9][0-9]|1[0-1][0-9]|12[0-7])(\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))){2}$/', $server) && + // Search for 172.16.0.0/12 + !preg_match('/^172\.(1[6-9]|2[0-9]|3[0-1])(\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))){2}$/', $server) && + // Search for 198.18.0.0/15 + !preg_match('/^198\.(1[8-9])(\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))){2}$/', $server) && + // Search for 169.254.0.0/16, 192.168.0.0/16 + !preg_match('/^(169\.254|192\.168)(\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))){2}$/', $server) && + // Search for 192.0.2.0/24, 192.88.99.0/24, 198.51.100.0/24, 203.0.113.0/24 + !preg_match('/^(192\.0\.2|192\.88\.99|198\.51\.100|203\.0\.113)\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))$/', $server) && + // Search for 224.0.0.0/4, 240.0.0.0/4 + !preg_match('/^(2(2[4-9]|[3-4][0-9]|5[0-5]))(\.([0-9]|[1-9][0-9]|1([0-9][0-9])|2([0-4][0-9]|5[0-5]))){3}$/', $server) + ) { + return false; + } + // @codingStandardsIgnoreEnd + } + + return true; + } + + /** + * Internal method to validate the local part of the email address + * + * @return bool + */ + protected function validateLocalPart() + { + // First try to match the local part on the common dot-atom format + + // Dot-atom characters are: 1*atext *("." 1*atext) + // atext: ALPHA / DIGIT / and "!", "#", "$", "%", "&", "'", "*", + // "+", "-", "/", "=", "?", "^", "_", "`", "{", "|", "}", "~" + $atext = 'a-zA-Z0-9\x21\x23\x24\x25\x26\x27\x2a\x2b\x2d\x2f\x3d\x3f\x5e\x5f\x60\x7b\x7c\x7d\x7e'; + if (preg_match('/^[' . $atext . ']+(\x2e+[' . $atext . ']+)*$/', $this->localPart)) { + return true; + } + + if ($this->validateInternationalizedLocalPart($this->localPart)) { + return true; + } + + // Try quoted string format (RFC 5321 Chapter 4.1.2) + + // Quoted-string characters are: DQUOTE *(qtext/quoted-pair) DQUOTE + $qtext = '\x20-\x21\x23-\x5b\x5d-\x7e'; // %d32-33 / %d35-91 / %d93-126 + $quotedPair = '\x20-\x7e'; // %d92 %d32-126 + if (preg_match('/^"([' . $qtext . ']|\x5c[' . $quotedPair . '])*"$/', $this->localPart)) { + return true; + } + + $this->error(self::DOT_ATOM); + $this->error(self::QUOTED_STRING); + $this->error(self::INVALID_LOCAL_PART); + + return false; + } + + /** + * @param string $localPart Address local part to validate. + * @return bool + */ + protected function validateInternationalizedLocalPart($localPart) + { + if ( + extension_loaded('intl') + && false === UConverter::transcode($localPart, 'UTF-8', 'UTF-8') + ) { + // invalid utf? + return false; + } + + $atext = 'a-zA-Z0-9\x21\x23\x24\x25\x26\x27\x2a\x2b\x2d\x2f\x3d\x3f\x5e\x5f\x60\x7b\x7c\x7d\x7e'; + // RFC 6532 extends atext to include non-ascii utf + // @see https://tools.ietf.org/html/rfc6532#section-3.1 + $uatext = $atext . '\x{80}-\x{FFFF}'; + return (bool) preg_match('/^[' . $uatext . ']+(\x2e+[' . $uatext . ']+)*$/u', $localPart); + } + + /** + * Returns the found MX Record information after validation including weight for further processing + * + * @return array + */ + public function getMXRecord() + { + return $this->mxRecord; + } + + /** + * Internal method to validate the servers MX records + * + * @return bool|string[] + * @psalm-return bool|list + */ + protected function validateMXRecords() + { + $mxHosts = []; + $weight = []; + $result = getmxrr($this->hostname, $mxHosts, $weight); + if (! empty($mxHosts) && ! empty($weight)) { + $this->mxRecord = array_combine($mxHosts, $weight) ?: []; + } else { + $this->mxRecord = []; + } + + arsort($this->mxRecord); + + // Fallback to IPv4 hosts if no MX record found (RFC 2821 SS 5). + if (! $result) { + $result = gethostbynamel($this->hostname); + if (is_array($result)) { + $this->mxRecord = array_flip($result); + } + } + + if (! $result) { + $this->error(self::INVALID_MX_RECORD); + return $result; + } + + if (! $this->options['useDeepMxCheck']) { + return $result; + } + + $validAddress = false; + $reserved = true; + foreach (array_keys($this->mxRecord) as $hostname) { + $res = $this->isReserved($hostname); + if (! $res) { + $reserved = false; + } + + if (! is_string($hostname) || ! trim($hostname)) { + continue; + } + + if ( + ! $res + && (checkdnsrr($hostname, 'A') + || checkdnsrr($hostname, 'AAAA') + || checkdnsrr($hostname, 'A6')) + ) { + $validAddress = true; + break; + } + } + + if (! $validAddress) { + $result = false; + $error = $reserved ? self::INVALID_SEGMENT : self::INVALID_MX_RECORD; + $this->error($error); + } + + return $result; + } + + /** + * Internal method to validate the hostname part of the email address + * + * @return bool|string[] + * @psalm-return bool|list + */ + protected function validateHostnamePart() + { + $hostname = $this->getHostnameValidator()->setTranslator($this->getTranslator()) + ->isValid($this->hostname); + if (! $hostname) { + $this->error(self::INVALID_HOSTNAME); + // Get messages and errors from hostnameValidator + foreach ($this->getHostnameValidator()->getMessages() as $code => $message) { + $this->abstractOptions['messages'][$code] = $message; + } + } elseif ($this->options['useMxCheck']) { + // MX check on hostname + $hostname = $this->validateMXRecords(); + } + + return $hostname; + } + + /** + * Splits the given value in hostname and local part of the email address + * + * @param string $value Email address to be split + * @return bool Returns false when the email can not be split + */ + protected function splitEmailParts($value) + { + $value = is_string($value) ? $value : ''; + + // Split email address up and disallow '..' + if ( + strpos($value, '..') !== false + || ! preg_match('/^(.+)@([^@]+)$/', $value, $matches) + ) { + return false; + } + + $this->localPart = $matches[1]; + $this->hostname = $this->idnToAscii($matches[2]); + + return true; + } + + /** + * Defined by Laminas\Validator\ValidatorInterface + * + * Returns true if and only if $value is a valid email address + * according to RFC2822 + * + * @link http://www.ietf.org/rfc/rfc2822.txt RFC2822 + * @link http://www.columbia.edu/kermit/ascii.html US-ASCII characters + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $length = true; + $this->setValue($value); + + // Split email address up and disallow '..' + if (! $this->splitEmailParts($this->getValue())) { + $this->error(self::INVALID_FORMAT); + return false; + } + + if ($this->getOption('strict') && (strlen($this->localPart) > 64) || (strlen($this->hostname) > 255)) { + $length = false; + $this->error(self::LENGTH_EXCEEDED); + } + + // Match hostname part + $hostname = false; + if ($this->options['useDomainCheck']) { + $hostname = $this->validateHostnamePart(); + } + + $local = $this->validateLocalPart(); + + // If both parts valid, return true + return ($local && $length) && (! $this->options['useDomainCheck'] || $hostname); + } + + /** + * Safely convert UTF-8 encoded domain name to ASCII + * + * @param string $email the UTF-8 encoded email + * @return string + */ + protected function idnToAscii($email) + { + if (extension_loaded('intl')) { + if (defined('INTL_IDNA_VARIANT_UTS46')) { + return idn_to_ascii($email, 0, INTL_IDNA_VARIANT_UTS46) ?: $email; + } + return idn_to_ascii($email) ?: $email; + } + return $email; + } + + /** + * Safely convert ASCII encoded domain name to UTF-8 + * + * @param string $email the ASCII encoded email + * @return string + */ + protected function idnToUtf8($email) + { + if (strlen($email) === 0) { + return $email; + } + + if (extension_loaded('intl')) { + // The documentation does not clarify what kind of failure + // can happen in idn_to_utf8. One can assume if the source + // is not IDN encoded, it would fail, but it usually returns + // the source string in those cases. + // But not when the source string is long enough. + // Thus we default to source string ourselves. + if (defined('INTL_IDNA_VARIANT_UTS46')) { + return idn_to_utf8($email, 0, INTL_IDNA_VARIANT_UTS46) ?: $email; + } + return idn_to_utf8($email) ?: $email; + } + return $email; + } +} diff --git a/lib/laminas/laminas-validator/src/Exception/BadMethodCallException.php b/lib/laminas/laminas-validator/src/Exception/BadMethodCallException.php new file mode 100644 index 0000000000..48ffcba699 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Exception/BadMethodCallException.php @@ -0,0 +1,7 @@ + 'Invalid type given', + ]; + + /** @var array */ + protected $messageVariables = []; + + /** @var string */ + protected $valueDelimiter = ','; + + /** @var ValidatorInterface|null */ + protected $validator; + + /** @var bool */ + protected $breakOnFirstFailure = false; + + /** + * Sets the delimiter string that the values will be split upon + * + * @param string $delimiter + * @return $this + */ + public function setValueDelimiter($delimiter) + { + $this->valueDelimiter = $delimiter; + return $this; + } + + /** + * Returns the delimiter string that the values will be split upon + * + * @return string + */ + public function getValueDelimiter() + { + return $this->valueDelimiter; + } + + /** + * Set validator plugin manager + * + * @return void + */ + public function setValidatorPluginManager(ValidatorPluginManager $pluginManager) + { + $this->pluginManager = $pluginManager; + } + + /** + * Get validator plugin manager + * + * @return ValidatorPluginManager + */ + public function getValidatorPluginManager() + { + if (! $this->pluginManager) { + $this->pluginManager = new ValidatorPluginManager(new ServiceManager()); + } + + return $this->pluginManager; + } + + /** + * Sets the Validator for validating each value + * + * @param ValidatorInterface|ValidatorSpecification $validator + * @throws Exception\RuntimeException + * @return $this + */ + public function setValidator($validator) + { + if (is_array($validator)) { + if (! isset($validator['name'])) { + throw new Exception\RuntimeException( + 'Invalid validator specification provided; does not include "name" key' + ); + } + $name = $validator['name']; + $options = $validator['options'] ?? []; + /** @psalm-suppress MixedAssignment $validator */ + $validator = $this->getValidatorPluginManager()->get($name, $options); + } + + if (! $validator instanceof ValidatorInterface) { + throw new Exception\RuntimeException( + 'Invalid validator given' + ); + } + + $this->validator = $validator; + return $this; + } + + /** + * Gets the Validator for validating each value + * + * @return ValidatorInterface|null + */ + public function getValidator() + { + return $this->validator; + } + + /** + * Set break on first failure setting + * + * @param bool $break + * @return $this + */ + public function setBreakOnFirstFailure($break) + { + $this->breakOnFirstFailure = (bool) $break; + return $this; + } + + /** + * Get break on first failure setting + * + * @return bool + */ + public function isBreakOnFirstFailure() + { + return $this->breakOnFirstFailure; + } + + /** + * Defined by Laminas\Validator\ValidatorInterface + * + * Returns true if all values validate true + * + * @param mixed $value + * @param mixed $context Extra "context" to provide the composed validator + * @return bool + * @throws Exception\RuntimeException + */ + public function isValid($value, $context = null) + { + $this->setValue($value); + + if ($value instanceof Traversable) { + $value = ArrayUtils::iteratorToArray($value); + } + + if (is_array($value)) { + $values = $value; + } elseif (is_string($value)) { + $delimiter = $this->getValueDelimiter(); + // Skip explode if delimiter is null, + // used when value is expected to be either an + // array when multiple values and a string for + // single values (ie. MultiCheckbox form behavior) + $values = null !== $delimiter + ? explode($this->valueDelimiter, $value) + : [$value]; + } else { + $values = [$value]; + } + + $validator = $this->getValidator(); + + if (! $validator) { + throw new Exception\RuntimeException(sprintf( + '%s expects a validator to be set; none given', + __METHOD__ + )); + } + + foreach ($values as $value) { + if (! $validator->isValid($value, $context)) { + $this->abstractOptions['messages'][] = $validator->getMessages(); + + if ($this->isBreakOnFirstFailure()) { + return false; + } + } + } + + return ! $this->abstractOptions['messages']; + } +} diff --git a/lib/laminas/laminas-validator/src/File/Count.php b/lib/laminas/laminas-validator/src/File/Count.php new file mode 100644 index 0000000000..1f8ee09604 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/Count.php @@ -0,0 +1,257 @@ + "Too many files, maximum '%max%' are allowed but '%count%' are given", + self::TOO_FEW => "Too few files, minimum '%min%' are expected but '%count%' are given", + ]; + + /** @var array Error message template variables */ + protected $messageVariables = [ + 'min' => ['options' => 'min'], + 'max' => ['options' => 'max'], + 'count' => 'count', + ]; + + /** + * Actual filecount + * + * @var int + */ + protected $count; + + /** + * Internal file array + * + * @var array + */ + protected $files; + + /** + * Options for this validator + * + * @var array + */ + protected $options = [ + 'min' => null, // Minimum file count, if null there is no minimum file count + 'max' => null, // Maximum file count, if null there is no maximum file count + ]; + + /** + * Sets validator options + * + * Min limits the file count, when used with max=null it is the maximum file count + * It also accepts an array with the keys 'min' and 'max' + * + * If $options is an integer, it will be used as maximum file count + * As Array is accepts the following keys: + * 'min': Minimum filecount + * 'max': Maximum filecount + * + * @param int|array|Traversable $options Options for the adapter + */ + public function __construct($options = null) + { + if (1 < func_num_args()) { + $args = func_get_args(); + $options = [ + 'min' => array_shift($args), + 'max' => array_shift($args), + ]; + } + + if (is_string($options) || is_numeric($options)) { + $options = ['max' => $options]; + } + + parent::__construct($options); + } + + /** + * Returns the minimum file count + * + * @return int + */ + public function getMin() + { + return $this->options['min']; + } + + /** + * Sets the minimum file count + * + * @param int|array $min The minimum file count + * @return $this Provides a fluent interface + * @throws Exception\InvalidArgumentException When min is greater than max. + */ + public function setMin($min) + { + if (is_array($min) && isset($min['min'])) { + $min = $min['min']; + } + + if (! is_numeric($min)) { + throw new Exception\InvalidArgumentException('Invalid options to validator provided'); + } + + $min = (int) $min; + if (($this->getMax() !== null) && ($min > $this->getMax())) { + throw new Exception\InvalidArgumentException( + "The minimum must be less than or equal to the maximum file count, but {$min} > {$this->getMax()}" + ); + } + + $this->options['min'] = $min; + return $this; + } + + /** + * Returns the maximum file count + * + * @return int + */ + public function getMax() + { + return $this->options['max']; + } + + /** + * Sets the maximum file count + * + * @param int|array $max The maximum file count + * @return $this Provides a fluent interface + * @throws Exception\InvalidArgumentException When max is smaller than min. + */ + public function setMax($max) + { + if (is_array($max) && isset($max['max'])) { + $max = $max['max']; + } + + if (! is_numeric($max)) { + throw new Exception\InvalidArgumentException('Invalid options to validator provided'); + } + + $max = (int) $max; + if (($this->getMin() !== null) && ($max < $this->getMin())) { + throw new Exception\InvalidArgumentException( + "The maximum must be greater than or equal to the minimum file count, but {$max} < {$this->getMin()}" + ); + } + + $this->options['max'] = $max; + return $this; + } + + /** + * Adds a file for validation + * + * @param string|array $file + * @return $this + */ + public function addFile($file) + { + if (is_string($file)) { + $file = [$file]; + } + + if (is_array($file)) { + foreach ($file as $name) { + if (! isset($this->files[$name]) && ! empty($name)) { + $this->files[$name] = $name; + } + } + } + + return $this; + } + + /** + * Returns true if and only if the file count of all checked files is at least min and + * not bigger than max (when max is not null). Attention: When checking with set min you + * must give all files with the first call, otherwise you will get a false. + * + * @param string|array $value Filenames to check for count + * @param array $file File data from \Laminas\File\Transfer\Transfer + * @return bool + */ + public function isValid($value, $file = null) + { + if (($file !== null) && ! array_key_exists('destination', $file)) { + $file['destination'] = dirname($value); + } + + if (($file !== null) && array_key_exists('tmp_name', $file)) { + $value = $file['destination'] . DIRECTORY_SEPARATOR . $file['name']; + } + + if (($file === null) || ! empty($file['tmp_name'])) { + $this->addFile($value); + } + + $this->count = count($this->files); + if (($this->getMax() !== null) && ($this->count > $this->getMax())) { + return $this->throwError($file, self::TOO_MANY); + } + + if (($this->getMin() !== null) && ($this->count < $this->getMin())) { + return $this->throwError($file, self::TOO_FEW); + } + + return true; + } + + /** + * Throws an error of the given type + * + * @param string $file + * @param string $errorType + * @return false + */ + protected function throwError($file, $errorType) + { + if ($file !== null) { + if (is_array($file)) { + if (array_key_exists('name', $file)) { + $this->value = $file['name']; + } + } elseif (is_string($file)) { + $this->value = $file; + } + } + + $this->error($errorType); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/Crc32.php b/lib/laminas/laminas-validator/src/File/Crc32.php new file mode 100644 index 0000000000..94963bf1fb --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/Crc32.php @@ -0,0 +1,110 @@ + 'File does not match the given crc32 hashes', + self::NOT_DETECTED => 'A crc32 hash could not be evaluated for the given file', + self::NOT_FOUND => 'File is not readable or does not exist', + ]; + + /** + * Options for this validator + * + * @var string + */ + protected $options = [ + 'algorithm' => 'crc32', + 'hash' => null, + ]; + + /** + * Returns all set crc32 hashes + * + * @return array + */ + public function getCrc32() + { + return $this->getHash(); + } + + /** + * Sets the crc32 hash for one or multiple files + * + * @param string|array $options + * @return $this Provides a fluent interface + */ + public function setCrc32($options) + { + $this->setHash($options); + return $this; + } + + /** + * Adds the crc32 hash for one or multiple files + * + * @param string|array $options + * @return $this Provides a fluent interface + */ + public function addCrc32($options) + { + $this->addHash($options); + return $this; + } + + /** + * Returns true if and only if the given file confirms the set hash + * + * @param string|array $value Filename to check for hash + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(self::NOT_FOUND); + return false; + } + + $hashes = array_unique(array_keys($this->getHash())); + $filehash = hash_file('crc32', $fileInfo['file']); + if ($filehash === false) { + $this->error(self::NOT_DETECTED); + return false; + } + + foreach ($hashes as $hash) { + if ($filehash === $hash) { + return true; + } + } + + $this->error(self::DOES_NOT_MATCH); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/ExcludeExtension.php b/lib/laminas/laminas-validator/src/File/ExcludeExtension.php new file mode 100644 index 0000000000..47741fc266 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/ExcludeExtension.php @@ -0,0 +1,72 @@ + 'File has an incorrect extension', + self::NOT_FOUND => 'File is not readable or does not exist', + ]; + + /** + * Returns true if and only if the file extension of $value is not included in the + * set extension list + * + * @param string|array $value Real file to check for extension + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file); + + // Is file readable ? + if ( + ! $this->getAllowNonExistentFile() + && (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) + ) { + $this->error(self::NOT_FOUND); + return false; + } + + $this->setValue($fileInfo['filename']); + + $extension = substr($fileInfo['filename'], strrpos($fileInfo['filename'], '.') + 1); + $extensions = $this->getExtension(); + + if ($this->getCase() && (! in_array($extension, $extensions))) { + return true; + } elseif (! $this->getCase()) { + foreach ($extensions as $ext) { + if (strtolower($ext) === strtolower($extension)) { + $this->error(self::FALSE_EXTENSION); + return false; + } + } + + return true; + } + + $this->error(self::FALSE_EXTENSION); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/ExcludeMimeType.php b/lib/laminas/laminas-validator/src/File/ExcludeMimeType.php new file mode 100644 index 0000000000..f5a321c090 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/ExcludeMimeType.php @@ -0,0 +1,97 @@ + "File has an incorrect mimetype of '%type%'", + self::NOT_DETECTED => 'The mimetype could not be detected from the file', + self::NOT_READABLE => 'File is not readable or does not exist', + ]; + + /** + * Returns true if the mimetype of the file does not matche the given ones. Also parts + * of mimetypes can be checked. If you give for example "image" all image + * mime types will not be accepted like "image/gif", "image/jpeg" and so on. + * + * @param string|array $value Real file to check for mimetype + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file, true); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(self::NOT_READABLE); + return false; + } + + $mimefile = $this->getMagicFile(); + if (class_exists('finfo', false)) { + if (! $this->isMagicFileDisabled() && (! empty($mimefile) && empty($this->finfo))) { + $this->finfo = finfo_open(FILEINFO_MIME_TYPE, $mimefile); + } + + if (empty($this->finfo)) { + $this->finfo = finfo_open(FILEINFO_MIME_TYPE); + } + + $this->type = null; + if (! empty($this->finfo)) { + $this->type = finfo_file($this->finfo, $fileInfo['file']); + } + } + + if (empty($this->type) && $this->getHeaderCheck()) { + $this->type = $fileInfo['filetype']; + } + + if (empty($this->type)) { + $this->error(self::NOT_DETECTED); + return false; + } + + $mimetype = $this->getMimeType(true); + if (in_array($this->type, $mimetype)) { + $this->error(self::FALSE_TYPE); + return false; + } + + $types = explode('/', $this->type); + $types = array_merge($types, explode('-', $this->type)); + $types = array_merge($types, explode(';', $this->type)); + foreach ($mimetype as $mime) { + if (in_array($mime, $types)) { + $this->error(self::FALSE_TYPE); + return false; + } + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/File/Exists.php b/lib/laminas/laminas-validator/src/File/Exists.php new file mode 100644 index 0000000000..80a27c9759 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/Exists.php @@ -0,0 +1,183 @@ + 'File does not exist', + ]; + + /** + * Options for this validator + * + * @var array + */ + protected $options = [ + 'directory' => null, // internal list of directories + ]; + + /** @var array Error message template variables */ + protected $messageVariables = [ + 'directory' => ['options' => 'directory'], + ]; + + /** + * Sets validator options + * + * @param string|array|Traversable $options + */ + public function __construct($options = null) + { + if (is_string($options)) { + $options = explode(',', $options); + } + + if (is_array($options) && ! array_key_exists('directory', $options)) { + $options = ['directory' => $options]; + } + + parent::__construct($options); + } + + /** + * Returns the set file directories which are checked + * + * @param bool $asArray Returns the values as array; when false, a concatenated string is returned + * @return string|null + */ + public function getDirectory($asArray = false) + { + $asArray = (bool) $asArray; + $directory = $this->options['directory']; + if ($asArray && isset($directory)) { + $directory = explode(',', (string) $directory); + } + + return $directory; + } + + /** + * Sets the file directory which will be checked + * + * @param string|array $directory The directories to validate + * @return self Provides a fluent interface + */ + public function setDirectory($directory) + { + $this->options['directory'] = null; + $this->addDirectory($directory); + return $this; + } + + /** + * Adds the file directory which will be checked + * + * @param string|array $directory The directory to add for validation + * @return self Provides a fluent interface + * @throws Exception\InvalidArgumentException + */ + public function addDirectory($directory) + { + $directories = $this->getDirectory(true); + if (! isset($directories)) { + $directories = []; + } + + if (is_string($directory)) { + $directory = explode(',', $directory); + } elseif (! is_array($directory)) { + throw new Exception\InvalidArgumentException('Invalid options to validator provided'); + } + + foreach ($directory as $content) { + if (empty($content) || ! is_string($content)) { + continue; + } + + $directories[] = trim($content); + } + $directories = array_unique($directories); + + // Sanity check to ensure no empty values + foreach ($directories as $key => $dir) { + if (empty($dir)) { + unset($directories[$key]); + } + } + + $this->options['directory'] = ! empty($directory) + ? implode(',', $directories) : null; + + return $this; + } + + /** + * Returns true if and only if the file already exists in the set directories + * + * @param string|array $value Real file to check for existence + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file, false, true); + + $this->setValue($fileInfo['filename']); + + $check = false; + $directories = $this->getDirectory(true); + if (! isset($directories)) { + $check = true; + if (! file_exists($fileInfo['file'])) { + $this->error(self::DOES_NOT_EXIST); + return false; + } + } else { + foreach ($directories as $directory) { + if (! isset($directory) || '' === $directory) { + continue; + } + + $check = true; + if (! file_exists($directory . DIRECTORY_SEPARATOR . $fileInfo['basename'])) { + $this->error(self::DOES_NOT_EXIST); + return false; + } + } + } + + if (! $check) { + $this->error(self::DOES_NOT_EXIST); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/File/Extension.php b/lib/laminas/laminas-validator/src/File/Extension.php new file mode 100644 index 0000000000..c977d9bcd8 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/Extension.php @@ -0,0 +1,242 @@ + Error message templates */ + protected $messageTemplates = [ + self::FALSE_EXTENSION => 'File has an incorrect extension', + self::NOT_FOUND => 'File is not readable or does not exist', + ]; + + /** + * Options for this validator + * + * @var array + */ + protected $options = [ + 'case' => false, // Validate case sensitive + 'extension' => '', // List of extensions + 'allowNonExistentFile' => false, // Allow validation even if file does not exist + ]; + + /** @var array Error message template variables */ + protected $messageVariables = [ + 'extension' => ['options' => 'extension'], + ]; + + /** + * Sets validator options + * + * @param string|array|Traversable $options + */ + public function __construct($options = null) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + + $case = null; + if (1 < func_num_args()) { + $case = func_get_arg(1); + } + + if (is_array($options)) { + if (isset($options['case'])) { + $case = $options['case']; + unset($options['case']); + } + + if (! array_key_exists('extension', $options)) { + $options = ['extension' => $options]; + } + } else { + $options = ['extension' => $options]; + } + + if ($case !== null) { + $options['case'] = $case; + } + + parent::__construct($options); + } + + /** + * Returns the case option + * + * @return bool + */ + public function getCase() + { + return $this->options['case']; + } + + /** + * Sets the case to use + * + * @param bool $case + * @return $this Provides a fluent interface + */ + public function setCase($case) + { + $this->options['case'] = (bool) $case; + return $this; + } + + /** + * Returns the set file extension + * + * @return array + */ + public function getExtension() + { + if ( + ! array_key_exists('extension', $this->options) + || ! is_string($this->options['extension']) + ) { + return []; + } + + return explode(',', $this->options['extension']); + } + + /** + * Sets the file extensions + * + * @param string|array $extension The extensions to validate + * @return $this Provides a fluent interface + */ + public function setExtension($extension) + { + $this->options['extension'] = null; + $this->addExtension($extension); + return $this; + } + + /** + * Adds the file extensions + * + * @param string|array $extension The extensions to add for validation + * @return $this Provides a fluent interface + */ + public function addExtension($extension) + { + $extensions = $this->getExtension(); + if (is_string($extension)) { + $extension = explode(',', $extension); + } + + foreach ($extension as $content) { + if (empty($content) || ! is_string($content)) { + continue; + } + + $extensions[] = trim($content); + } + + $extensions = array_unique($extensions); + + // Sanity check to ensure no empty values + foreach ($extensions as $key => $ext) { + if (empty($ext)) { + unset($extensions[$key]); + } + } + + $this->options['extension'] = implode(',', $extensions); + return $this; + } + + /** + * Returns whether or not to allow validation of non-existent files. + * + * @return bool + */ + public function getAllowNonExistentFile() + { + return $this->options['allowNonExistentFile']; + } + + /** + * Sets the flag indicating whether or not to allow validation of non-existent files. + * + * @param bool $flag Whether or not to allow validation of non-existent files. + * @return $this Provides a fluent interface + */ + public function setAllowNonExistentFile($flag) + { + $this->options['allowNonExistentFile'] = (bool) $flag; + return $this; + } + + /** + * Returns true if and only if the file extension of $value is included in the + * set extension list + * + * @param string|array $value Real file to check for extension + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file); + + // Is file readable ? + if ( + ! $this->getAllowNonExistentFile() + && (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) + ) { + $this->error(self::NOT_FOUND); + return false; + } + + $this->setValue($fileInfo['filename']); + + $extension = substr($fileInfo['filename'], strrpos($fileInfo['filename'], '.') + 1); + $extensions = $this->getExtension(); + + if ($this->getCase() && (in_array($extension, $extensions))) { + return true; + } elseif (! $this->getCase()) { + foreach ($extensions as $ext) { + if (strtolower($ext) === strtolower($extension)) { + return true; + } + } + } + + $this->error(self::FALSE_EXTENSION); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/FileInformationTrait.php b/lib/laminas/laminas-validator/src/File/FileInformationTrait.php new file mode 100644 index 0000000000..295027b931 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/FileInformationTrait.php @@ -0,0 +1,164 @@ +getLegacyFileInfo($file, $hasType, $hasBasename); + } + + if (is_array($value)) { + return $this->getSapiFileInfo($value, $hasType, $hasBasename); + } + + if ($value instanceof UploadedFileInterface) { + return $this->getPsr7FileInfo($value, $hasType, $hasBasename); + } + + return $this->getFileBasedFileInfo($value, $hasType, $hasBasename); + } + + /** + * Generate file information array with legacy Laminas_File_Transfer API + * + * @param array $file File data + * @param bool $hasType Return with filetype + * @param bool $hasBasename Basename is calculated from location path + * @return array + */ + private function getLegacyFileInfo( + array $file, + $hasType = false, + $hasBasename = false + ) { + $fileInfo = []; + + $fileInfo['filename'] = $file['name']; + $fileInfo['file'] = $file['tmp_name']; + + if ($hasBasename) { + $fileInfo['basename'] = basename($fileInfo['file']); + } + + if ($hasType) { + $fileInfo['filetype'] = $file['type']; + } + + return $fileInfo; + } + + /** + * Generate file information array with SAPI + * + * @param array $file File data from SAPI + * @param bool $hasType Return with filetype + * @param bool $hasBasename Filename is calculated from location path + * @return array + */ + private function getSapiFileInfo( + array $file, + $hasType = false, + $hasBasename = false + ) { + if (! isset($file['tmp_name']) || ! isset($file['name'])) { + throw new Exception\InvalidArgumentException( + 'Value array must be in $_FILES format' + ); + } + + $fileInfo = []; + + $fileInfo['file'] = $file['tmp_name']; + $fileInfo['filename'] = $file['name']; + + if ($hasBasename) { + $fileInfo['basename'] = basename($fileInfo['file']); + } + + if ($hasType) { + $fileInfo['filetype'] = $file['type']; + } + + return $fileInfo; + } + + /** + * Generate file information array with PSR-7 UploadedFileInterface + * + * @param bool $hasType Return with filetype + * @param bool $hasBasename Filename is calculated from location path + * @return array + */ + private function getPsr7FileInfo( + UploadedFileInterface $file, + $hasType = false, + $hasBasename = false + ) { + $fileInfo = []; + + $fileInfo['file'] = $file->getStream()->getMetadata('uri'); + $fileInfo['filename'] = $file->getClientFilename(); + + if ($hasBasename) { + $fileInfo['basename'] = basename($fileInfo['file']); + } + + if ($hasType) { + $fileInfo['filetype'] = $file->getClientMediaType(); + } + + return $fileInfo; + } + + /** + * Generate file information array with base method + * + * @param string $file File path + * @param bool $hasType Return with filetype + * @param bool $hasBasename Filename is calculated from location path + * @return array + */ + private function getFileBasedFileInfo( + $file, + $hasType = false, + $hasBasename = false + ) { + $fileInfo = []; + + $fileInfo['file'] = $file; + $fileInfo['filename'] = basename($fileInfo['file']); + + if ($hasBasename) { + $fileInfo['basename'] = basename($fileInfo['file']); + } + + if ($hasType) { + $fileInfo['filetype'] = null; + } + + return $fileInfo; + } +} diff --git a/lib/laminas/laminas-validator/src/File/FilesSize.php b/lib/laminas/laminas-validator/src/File/FilesSize.php new file mode 100644 index 0000000000..25bd90250c --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/FilesSize.php @@ -0,0 +1,184 @@ + "All files in sum should have a maximum size of '%max%' but '%size%' were detected", + self::TOO_SMALL => "All files in sum should have a minimum size of '%min%' but '%size%' were detected", + self::NOT_READABLE => 'One or more files can not be read', + ]; + + /** + * Internal file array + * + * @var array + */ + protected $files; + + /** + * Sets validator options + * + * Min limits the used disk space for all files, when used with max=null it is the maximum file size + * It also accepts an array with the keys 'min' and 'max' + * + * @param int|array|Traversable $options Options for this validator + * @throws InvalidArgumentException + */ + public function __construct($options = null) + { + $this->files = []; + $this->setSize(0); + + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } elseif (is_scalar($options)) { + $options = ['max' => $options]; + } elseif (! is_array($options)) { + throw new Exception\InvalidArgumentException('Invalid options to validator provided'); + } + + if (1 < func_num_args()) { + $argv = func_get_args(); + array_shift($argv); + $options['max'] = array_shift($argv); + if (! empty($argv)) { + $options['useByteString'] = array_shift($argv); + } + } + + parent::__construct($options); + } + + /** + * Returns true if and only if the disk usage of all files is at least min and + * not bigger than max (when max is not null). + * + * @param string|array $value Real file to check for size + * @param array $file File data from \Laminas\File\Transfer\Transfer + * @return bool + */ + public function isValid($value, $file = null) + { + if (is_string($value)) { + $value = [$value]; + } elseif (is_array($value) && isset($value['tmp_name'])) { + $value = [$value]; + } + + $min = $this->getMin(true); + $max = $this->getMax(true); + $size = $this->getSize(); + foreach ($value as $files) { + if (is_array($files)) { + if (! isset($files['tmp_name']) || ! isset($files['name'])) { + throw new Exception\InvalidArgumentException( + 'Value array must be in $_FILES format' + ); + } + $file = $files; + $files = $files['tmp_name']; + } + + // Is file readable ? + if (empty($files) || false === is_readable($files)) { + $this->throwError($file, self::NOT_READABLE); + continue; + } + + if (! isset($this->files[$files])) { + $this->files[$files] = $files; + } else { + // file already counted... do not count twice + continue; + } + + // limited to 2GB files + ErrorHandler::start(); + $size += filesize($files); + ErrorHandler::stop(); + $this->size = $size; + if (($max !== null) && ($max < $size)) { + if ($this->getByteString()) { + $this->options['max'] = $this->toByteString($max); + $this->size = $this->toByteString($size); + $this->throwError($file, self::TOO_BIG); + $this->options['max'] = $max; + $this->size = $size; + } else { + $this->throwError($file, self::TOO_BIG); + } + } + } + + // Check that aggregate files are >= minimum size + if (($min !== null) && ($size < $min)) { + if ($this->getByteString()) { + $this->options['min'] = $this->toByteString($min); + $this->size = $this->toByteString($size); + $this->throwError($file, self::TOO_SMALL); + $this->options['min'] = $min; + $this->size = $size; + } else { + $this->throwError($file, self::TOO_SMALL); + } + } + + if ($this->getMessages()) { + return false; + } + + return true; + } + + /** + * Throws an error of the given type + * + * @param string $file + * @param string $errorType + * @return false + */ + protected function throwError($file, $errorType) + { + if ($file !== null) { + if (is_array($file)) { + if (array_key_exists('name', $file)) { + $this->value = $file['name']; + } + } elseif (is_string($file)) { + $this->value = $file; + } + } + + $this->error($errorType); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/Hash.php b/lib/laminas/laminas-validator/src/File/Hash.php new file mode 100644 index 0000000000..bf11570b81 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/Hash.php @@ -0,0 +1,177 @@ + 'File does not match the given hashes', + self::NOT_DETECTED => 'A hash could not be evaluated for the given file', + self::NOT_FOUND => 'File is not readable or does not exist', + ]; + + /** + * Options for this validator + * + * @var string + */ + protected $options = [ + 'algorithm' => 'crc32', + 'hash' => null, + ]; + + /** + * Sets validator options + * + * @param string|array $options + */ + public function __construct($options = null) + { + if ( + is_scalar($options) || + (is_array($options) && ! array_key_exists('hash', $options)) + ) { + $options = ['hash' => $options]; + } + + if (1 < func_num_args()) { + $options['algorithm'] = func_get_arg(1); + } + + parent::__construct($options); + } + + /** + * Returns the set hash values as array, the hash as key and the algorithm the value + * + * @return array + */ + public function getHash() + { + return $this->options['hash']; + } + + /** + * Sets the hash for one or multiple files + * + * @param string|array $options + * @return $this Provides a fluent interface + */ + public function setHash($options) + { + $this->options['hash'] = null; + $this->addHash($options); + + return $this; + } + + /** + * Adds the hash for one or multiple files + * + * @param string|array $options + * @throws Exception\InvalidArgumentException + * @return $this Provides a fluent interface + */ + public function addHash($options) + { + if (is_string($options)) { + $options = [$options]; + } elseif (! is_array($options)) { + throw new Exception\InvalidArgumentException('False parameter given'); + } + + $known = hash_algos(); + if (! isset($options['algorithm'])) { + $algorithm = $this->options['algorithm']; + } else { + $algorithm = $options['algorithm']; + unset($options['algorithm']); + } + + if (! in_array($algorithm, $known)) { + throw new Exception\InvalidArgumentException("Unknown algorithm '{$algorithm}'"); + } + + foreach ($options as $value) { + if (! is_string($value)) { + throw new Exception\InvalidArgumentException(sprintf( + 'Hash must be a string, %s received', + is_object($value) ? get_class($value) : gettype($value) + )); + } + $this->options['hash'][$value] = $algorithm; + } + + return $this; + } + + /** + * Returns true if and only if the given file confirms the set hash + * + * @param string|array $value File to check for hash + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(self::NOT_FOUND); + return false; + } + + $algos = array_unique(array_values($this->getHash())); + foreach ($algos as $algorithm) { + $filehash = hash_file($algorithm, $fileInfo['file']); + + if ($filehash === false) { + $this->error(self::NOT_DETECTED); + return false; + } + + if (isset($this->getHash()[$filehash]) && $this->getHash()[$filehash] === $algorithm) { + return true; + } + } + + $this->error(self::DOES_NOT_MATCH); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/ImageSize.php b/lib/laminas/laminas-validator/src/File/ImageSize.php new file mode 100644 index 0000000000..91f478bb7a --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/ImageSize.php @@ -0,0 +1,377 @@ + "Maximum allowed width for image should be '%maxwidth%' but '%width%' detected", + self::WIDTH_TOO_SMALL => "Minimum expected width for image should be '%minwidth%' but '%width%' detected", + self::HEIGHT_TOO_BIG => "Maximum allowed height for image should be '%maxheight%' but '%height%' detected", + self::HEIGHT_TOO_SMALL => "Minimum expected height for image should be '%minheight%' but '%height%' detected", + self::NOT_DETECTED => 'The size of image could not be detected', + self::NOT_READABLE => 'File is not readable or does not exist', + ]; + + /** @var array Error message template variables */ + protected $messageVariables = [ + 'minwidth' => ['options' => 'minWidth'], + 'maxwidth' => ['options' => 'maxWidth'], + 'minheight' => ['options' => 'minHeight'], + 'maxheight' => ['options' => 'maxHeight'], + 'width' => 'width', + 'height' => 'height', + ]; + + /** + * Detected width + * + * @var int + */ + protected $width; + + /** + * Detected height + * + * @var int + */ + protected $height; + + /** + * Options for this validator + * + * @var array + */ + protected $options = [ + 'minWidth' => null, // Minimum image width + 'maxWidth' => null, // Maximum image width + 'minHeight' => null, // Minimum image height + 'maxHeight' => null, // Maximum image height + ]; + + /** + * Sets validator options + * + * Accepts the following option keys: + * - minheight + * - minwidth + * - maxheight + * - maxwidth + * + * @param null|array|Traversable $options + */ + public function __construct($options = null) + { + if (1 < func_num_args()) { + if (! is_array($options)) { + $options = ['minWidth' => $options]; + } + + $argv = func_get_args(); + array_shift($argv); + $options['minHeight'] = array_shift($argv); + if (! empty($argv)) { + $options['maxWidth'] = array_shift($argv); + if (! empty($argv)) { + $options['maxHeight'] = array_shift($argv); + } + } + } + + parent::__construct($options); + } + + /** + * Returns the minimum allowed width + * + * @return int + */ + public function getMinWidth() + { + return $this->options['minWidth']; + } + + /** + * Sets the minimum allowed width + * + * @param int $minWidth + * @return $this Provides a fluid interface + * @throws Exception\InvalidArgumentException When minwidth is greater than maxwidth. + */ + public function setMinWidth($minWidth) + { + if (($this->getMaxWidth() !== null) && ($minWidth > $this->getMaxWidth())) { + throw new Exception\InvalidArgumentException( + 'The minimum image width must be less than or equal to the ' + . " maximum image width, but {$minWidth} > {$this->getMaxWidth()}" + ); + } + + $this->options['minWidth'] = (int) $minWidth; + return $this; + } + + /** + * Returns the maximum allowed width + * + * @return int + */ + public function getMaxWidth() + { + return $this->options['maxWidth']; + } + + /** + * Sets the maximum allowed width + * + * @param int $maxWidth + * @return $this Provides a fluid interface + * @throws Exception\InvalidArgumentException When maxwidth is less than minwidth. + */ + public function setMaxWidth($maxWidth) + { + if (($this->getMinWidth() !== null) && ($maxWidth < $this->getMinWidth())) { + throw new Exception\InvalidArgumentException( + 'The maximum image width must be greater than or equal to the ' + . "minimum image width, but {$maxWidth} < {$this->getMinWidth()}" + ); + } + + $this->options['maxWidth'] = (int) $maxWidth; + return $this; + } + + /** + * Returns the minimum allowed height + * + * @return int + */ + public function getMinHeight() + { + return $this->options['minHeight']; + } + + /** + * Sets the minimum allowed height + * + * @param int $minHeight + * @return $this Provides a fluid interface + * @throws Exception\InvalidArgumentException When minheight is greater than maxheight. + */ + public function setMinHeight($minHeight) + { + if (($this->getMaxHeight() !== null) && ($minHeight > $this->getMaxHeight())) { + throw new Exception\InvalidArgumentException( + 'The minimum image height must be less than or equal to the ' + . " maximum image height, but {$minHeight} > {$this->getMaxHeight()}" + ); + } + + $this->options['minHeight'] = (int) $minHeight; + return $this; + } + + /** + * Returns the maximum allowed height + * + * @return int + */ + public function getMaxHeight() + { + return $this->options['maxHeight']; + } + + /** + * Sets the maximum allowed height + * + * @param int $maxHeight + * @return $this Provides a fluid interface + * @throws Exception\InvalidArgumentException When maxheight is less than minheight. + */ + public function setMaxHeight($maxHeight) + { + if (($this->getMinHeight() !== null) && ($maxHeight < $this->getMinHeight())) { + throw new Exception\InvalidArgumentException( + 'The maximum image height must be greater than or equal to the ' + . "minimum image height, but {$maxHeight} < {$this->getMinHeight()}" + ); + } + + $this->options['maxHeight'] = (int) $maxHeight; + return $this; + } + + /** + * Returns the set minimum image sizes + * + * @return array + */ + public function getImageMin() + { + return ['minWidth' => $this->getMinWidth(), 'minHeight' => $this->getMinHeight()]; + } + + /** + * Returns the set maximum image sizes + * + * @return array + */ + public function getImageMax() + { + return ['maxWidth' => $this->getMaxWidth(), 'maxHeight' => $this->getMaxHeight()]; + } + + /** + * Returns the set image width sizes + * + * @return array + */ + public function getImageWidth() + { + return ['minWidth' => $this->getMinWidth(), 'maxWidth' => $this->getMaxWidth()]; + } + + /** + * Returns the set image height sizes + * + * @return array + */ + public function getImageHeight() + { + return ['minHeight' => $this->getMinHeight(), 'maxHeight' => $this->getMaxHeight()]; + } + + /** + * Sets the minimum image size + * + * @param array $options The minimum image dimensions + * @return $this Provides a fluent interface + */ + public function setImageMin($options) + { + $this->setOptions($options); + return $this; + } + + /** + * Sets the maximum image size + * + * @param array|Traversable $options The maximum image dimensions + * @return $this Provides a fluent interface + */ + public function setImageMax($options) + { + $this->setOptions($options); + return $this; + } + + /** + * Sets the minimum and maximum image width + * + * @param array $options The image width dimensions + * @return $this Provides a fluent interface + */ + public function setImageWidth($options) + { + $this->setImageMin($options); + $this->setImageMax($options); + + return $this; + } + + /** + * Sets the minimum and maximum image height + * + * @param array $options The image height dimensions + * @return $this Provides a fluent interface + */ + public function setImageHeight($options) + { + $this->setImageMin($options); + $this->setImageMax($options); + + return $this; + } + + /** + * Returns true if and only if the image size of $value is at least min and + * not bigger than max + * + * @param string|array $value Real file to check for image size + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(self::NOT_READABLE); + return false; + } + + ErrorHandler::start(); + $size = getimagesize($fileInfo['file']); + ErrorHandler::stop(); + + if (empty($size) || ($size[0] === 0) || ($size[1] === 0)) { + $this->error(self::NOT_DETECTED); + return false; + } + + $this->width = $size[0]; + $this->height = $size[1]; + if ($this->width < $this->getMinWidth()) { + $this->error(self::WIDTH_TOO_SMALL); + } + + if (($this->getMaxWidth() !== null) && ($this->getMaxWidth() < $this->width)) { + $this->error(self::WIDTH_TOO_BIG); + } + + if ($this->height < $this->getMinHeight()) { + $this->error(self::HEIGHT_TOO_SMALL); + } + + if (($this->getMaxHeight() !== null) && ($this->getMaxHeight() < $this->height)) { + $this->error(self::HEIGHT_TOO_BIG); + } + + if ($this->getMessages()) { + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/File/IsCompressed.php b/lib/laminas/laminas-validator/src/File/IsCompressed.php new file mode 100644 index 0000000000..86ce6164e1 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/IsCompressed.php @@ -0,0 +1,84 @@ + "File is not compressed, '%type%' detected", + self::NOT_DETECTED => 'The mimetype could not be detected from the file', + self::NOT_READABLE => 'File is not readable or does not exist', + ]; + + /** + * Sets validator options + * + * @param string|array|Traversable $options + */ + public function __construct($options = []) + { + // http://hul.harvard.edu/ois/systems/wax/wax-public-help/mimetypes.htm + $default = [ + 'application/arj', + 'application/gnutar', + 'application/lha', + 'application/lzx', + 'application/vnd.ms-cab-compressed', + 'application/x-ace-compressed', + 'application/x-arc', + 'application/x-archive', + 'application/x-arj', + 'application/x-bzip', + 'application/x-bzip2', + 'application/x-cab-compressed', + 'application/x-compress', + 'application/x-compressed', + 'application/x-cpio', + 'application/x-debian-package', + 'application/x-eet', + 'application/x-gzip', + 'application/x-java-pack200', + 'application/x-lha', + 'application/x-lharc', + 'application/x-lzh', + 'application/x-lzma', + 'application/x-lzx', + 'application/x-rar', + 'application/x-sit', + 'application/x-stuffit', + 'application/x-tar', + 'application/zip', + 'application/x-zip', + 'application/zoo', + 'multipart/x-gzip', + ]; + + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + + if ($options === null) { + $options = []; + } + + parent::__construct($options); + + if (! $this->getMimeType()) { + $this->setMimeType($default); + } + } +} diff --git a/lib/laminas/laminas-validator/src/File/IsImage.php b/lib/laminas/laminas-validator/src/File/IsImage.php new file mode 100644 index 0000000000..ced1927539 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/IsImage.php @@ -0,0 +1,110 @@ + "File is no image, '%type%' detected", + self::NOT_DETECTED => 'The mimetype could not be detected from the file', + self::NOT_READABLE => 'File is not readable or does not exist', + ]; + + /** + * Sets validator options + * + * @param array|Traversable|string $options + */ + public function __construct($options = []) + { + // http://www.iana.org/assignments/media-types/media-types.xhtml#image + $default = [ + 'application/cdf', + 'application/dicom', + 'application/fractals', + 'application/postscript', + 'application/vnd.hp-hpgl', + 'application/vnd.oasis.opendocument.graphics', + 'application/x-cdf', + 'application/x-cmu-raster', + 'application/x-ima', + 'application/x-inventor', + 'application/x-koan', + 'application/x-portable-anymap', + 'application/x-world-x-3dmf', + 'image/bmp', + 'image/c', + 'image/cgm', + 'image/fif', + 'image/gif', + 'image/heic', + 'image/heif', + 'image/jpeg', + 'image/jpm', + 'image/jpx', + 'image/jp2', + 'image/naplps', + 'image/pjpeg', + 'image/png', + 'image/svg', + 'image/svg+xml', + 'image/tiff', + 'image/vnd.adobe.photoshop', + 'image/vnd.djvu', + 'image/vnd.fpx', + 'image/vnd.net-fpx', + 'image/webp', + 'image/x-cmu-raster', + 'image/x-cmx', + 'image/x-coreldraw', + 'image/x-cpi', + 'image/x-emf', + 'image/x-ico', + 'image/x-icon', + 'image/x-jg', + 'image/x-ms-bmp', + 'image/x-niff', + 'image/x-pict', + 'image/x-pcx', + 'image/x-png', + 'image/x-portable-anymap', + 'image/x-portable-bitmap', + 'image/x-portable-greymap', + 'image/x-portable-pixmap', + 'image/x-quicktime', + 'image/x-rgb', + 'image/x-tiff', + 'image/x-unknown', + 'image/x-windows-bmp', + 'image/x-xpmi', + ]; + + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + + if ($options === null) { + $options = []; + } + + parent::__construct($options); + + if (! $this->getMimeType()) { + $this->setMimeType($default); + } + } +} diff --git a/lib/laminas/laminas-validator/src/File/Md5.php b/lib/laminas/laminas-validator/src/File/Md5.php new file mode 100644 index 0000000000..4d48edf4e2 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/Md5.php @@ -0,0 +1,110 @@ + 'File does not match the given md5 hashes', + self::NOT_DETECTED => 'An md5 hash could not be evaluated for the given file', + self::NOT_FOUND => 'File is not readable or does not exist', + ]; + + /** + * Options for this validator + * + * @var string + */ + protected $options = [ + 'algorithm' => 'md5', + 'hash' => null, + ]; + + /** + * Returns all set md5 hashes + * + * @return array + */ + public function getMd5() + { + return $this->getHash(); + } + + /** + * Sets the md5 hash for one or multiple files + * + * @param string|array $options + * @return Hash Provides a fluent interface + */ + public function setMd5($options) + { + $this->setHash($options); + return $this; + } + + /** + * Adds the md5 hash for one or multiple files + * + * @param string|array $options + * @return Hash Provides a fluent interface + */ + public function addMd5($options) + { + $this->addHash($options); + return $this; + } + + /** + * Returns true if and only if the given file confirms the set hash + * + * @param string|array $value Filename to check for hash + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(self::NOT_FOUND); + return false; + } + + $hashes = array_unique(array_keys($this->getHash())); + $filehash = hash_file('md5', $fileInfo['file']); + if ($filehash === false) { + $this->error(self::NOT_DETECTED); + return false; + } + + foreach ($hashes as $hash) { + if ($filehash === $hash) { + return true; + } + } + + $this->error(self::DOES_NOT_MATCH); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/MimeType.php b/lib/laminas/laminas-validator/src/File/MimeType.php new file mode 100644 index 0000000000..8c2c43a043 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/MimeType.php @@ -0,0 +1,416 @@ + "File has an incorrect mimetype of '%type%'", + self::NOT_DETECTED => 'The mimetype could not be detected from the file', + self::NOT_READABLE => 'File is not readable or does not exist', + ]; + + /** @var array */ + protected $messageVariables = [ + 'type' => 'type', + ]; + + /** @var string */ + protected $type; + + /** + * Finfo object to use + * + * @var resource + */ + protected $finfo; + + /** + * If no environment variable 'MAGIC' is set, try and autodiscover it based on common locations + * + * @var array + */ + protected $magicFiles = [ + '/usr/share/misc/magic', + '/usr/share/misc/magic.mime', + '/usr/share/misc/magic.mgc', + '/usr/share/mime/magic', + '/usr/share/mime/magic.mime', + '/usr/share/mime/magic.mgc', + '/usr/share/file/magic', + '/usr/share/file/magic.mime', + '/usr/share/file/magic.mgc', + ]; + + /** + * Options for this validator + * + * @var array + */ + protected $options = [ + 'enableHeaderCheck' => false, // Allow header check + 'disableMagicFile' => false, // Disable usage of magicfile + 'magicFile' => null, // Magicfile to use + 'mimeType' => null, // Mimetype to allow + ]; + + /** + * Sets validator options + * + * Mimetype to accept + * - NULL means default PHP usage by using the environment variable 'magic' + * - FALSE means disabling searching for mimetype, should be used for PHP 5.3 + * - A string is the mimetype file to use + * + * @param string|array|Traversable $options + */ + public function __construct($options = null) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } elseif (is_string($options)) { + $this->setMimeType($options); + $options = []; + } elseif (is_array($options)) { + if (isset($options['magicFile'])) { + $this->setMagicFile($options['magicFile']); + unset($options['magicFile']); + } + + if (isset($options['enableHeaderCheck'])) { + $this->enableHeaderCheck($options['enableHeaderCheck']); + unset($options['enableHeaderCheck']); + } + + if (array_key_exists('mimeType', $options)) { + $this->setMimeType($options['mimeType']); + unset($options['mimeType']); + } + + // Handle cases where mimetypes are interspersed with options, or + // options are simply an array of mime types + foreach (array_keys($options) as $key) { + if (! is_int($key)) { + continue; + } + $this->addMimeType($options[$key]); + unset($options[$key]); + } + } + + parent::__construct($options); + } + + /** + * Returns the actual set magicfile + * + * @return string + */ + public function getMagicFile() + { + if (null === $this->options['magicFile']) { + $magic = getenv('magic'); + if (! empty($magic)) { + $this->setMagicFile($magic); + if ($this->options['magicFile'] === null) { + $this->options['magicFile'] = false; + } + return $this->options['magicFile']; + } + + foreach ($this->magicFiles as $file) { + try { + $this->setMagicFile($file); + } catch (Exception\ExceptionInterface $e) { + // suppressing errors which are thrown due to open_basedir restrictions + continue; + } + + if ($this->options['magicFile'] !== null) { + return $this->options['magicFile']; + } + } + + if ($this->options['magicFile'] === null) { + $this->options['magicFile'] = false; + } + } + + return $this->options['magicFile']; + } + + /** + * Sets the magicfile to use + * if null, the MAGIC constant from php is used + * if the MAGIC file is erroneous, no file will be set + * if false, the default MAGIC file from PHP will be used + * + * @param string $file + * @throws Exception\RuntimeException When finfo can not read the magicfile. + * @throws Exception\InvalidArgumentException + * @throws Exception\InvalidMagicMimeFileException + * @return $this Provides fluid interface + */ + public function setMagicFile($file) + { + if ($file === false) { + $this->options['magicFile'] = false; + } elseif (empty($file)) { + $this->options['magicFile'] = null; + } elseif (! class_exists('finfo', false)) { + $this->options['magicFile'] = null; + throw new Exception\RuntimeException('Magicfile can not be set; there is no finfo extension installed'); + } elseif (! is_file($file) || ! is_readable($file)) { + throw new Exception\InvalidArgumentException(sprintf( + 'The given magicfile ("%s") could not be read', + $file + )); + } else { + ErrorHandler::start(E_NOTICE | E_WARNING); + $this->finfo = finfo_open(FILEINFO_MIME_TYPE, $file); + $error = ErrorHandler::stop(); + if (empty($this->finfo)) { + $this->finfo = null; + throw new Exception\InvalidMagicMimeFileException(sprintf( + 'The given magicfile ("%s") could not be used by ext/finfo', + $file + ), 0, $error); + } + $this->options['magicFile'] = $file; + } + + return $this; + } + + /** + * Disables usage of MagicFile + * + * @param bool $disable False disables usage of magic file; true enables it. + * @return self Provides fluid interface + */ + public function disableMagicFile($disable) + { + $this->options['disableMagicFile'] = (bool) $disable; + return $this; + } + + /** + * Is usage of MagicFile disabled? + * + * @return bool + */ + public function isMagicFileDisabled() + { + return $this->options['disableMagicFile']; + } + + /** + * Returns the Header Check option + * + * @return bool + */ + public function getHeaderCheck() + { + return $this->options['enableHeaderCheck']; + } + + /** + * Defines if the http header should be used + * Note that this is unsafe and therefor the default value is false + * + * @param bool $headerCheck + * @return $this Provides fluid interface + */ + public function enableHeaderCheck($headerCheck = true) + { + $this->options['enableHeaderCheck'] = (bool) $headerCheck; + return $this; + } + + /** + * Returns the set mimetypes + * + * @param bool $asArray Returns the values as array, when false a concatenated string is returned + * @return string|array + * @psalm-return ($asArray is true ? list : string) + */ + public function getMimeType($asArray = false) + { + $asArray = (bool) $asArray; + $mimetype = (string) $this->options['mimeType']; + if ($asArray) { + $mimetype = explode(',', $mimetype); + } + + return $mimetype; + } + + /** + * Sets the mimetypes + * + * @param string|array $mimetype The mimetypes to validate + * @return $this Provides a fluent interface + */ + public function setMimeType($mimetype) + { + $this->options['mimeType'] = null; + $this->addMimeType($mimetype); + return $this; + } + + /** + * Adds the mimetypes + * + * @param string|array $mimetype The mimetypes to add for validation + * @throws Exception\InvalidArgumentException + * @return $this Provides a fluent interface + */ + public function addMimeType($mimetype) + { + $mimetypes = $this->getMimeType(true); + + if (is_string($mimetype)) { + $mimetype = explode(',', $mimetype); + } elseif (! is_array($mimetype)) { + throw new Exception\InvalidArgumentException('Invalid options to validator provided'); + } + + if (isset($mimetype['magicFile'])) { + unset($mimetype['magicFile']); + } + + foreach ($mimetype as $content) { + if (empty($content) || ! is_string($content)) { + continue; + } + $mimetypes[] = trim($content); + } + $mimetypes = array_unique($mimetypes); + + // Sanity check to ensure no empty values + foreach ($mimetypes as $key => $mt) { + if (empty($mt)) { + unset($mimetypes[$key]); + } + } + + $this->options['mimeType'] = implode(',', $mimetypes); + + return $this; + } + + /** + * Defined by Laminas\Validator\ValidatorInterface + * + * Returns true if the mimetype of the file matches the given ones. Also parts + * of mimetypes can be checked. If you give for example "image" all image + * mime types will be accepted like "image/gif", "image/jpeg" and so on. + * + * @param string|array $value Real file to check for mimetype + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file, true); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(static::NOT_READABLE); + return false; + } + + $mimefile = $this->getMagicFile(); + if (class_exists('finfo', false)) { + if (! $this->isMagicFileDisabled() && (! empty($mimefile) && empty($this->finfo))) { + ErrorHandler::start(E_NOTICE | E_WARNING); + $this->finfo = finfo_open(FILEINFO_MIME_TYPE, $mimefile); + ErrorHandler::stop(); + } + + if (empty($this->finfo)) { + ErrorHandler::start(E_NOTICE | E_WARNING); + $this->finfo = finfo_open(FILEINFO_MIME_TYPE); + ErrorHandler::stop(); + } + + $this->type = null; + if (! empty($this->finfo)) { + $this->type = finfo_file($this->finfo, $fileInfo['file']); + unset($this->finfo); + } + } + + if (empty($this->type) && $this->getHeaderCheck()) { + $this->type = $fileInfo['filetype']; + } + + if (empty($this->type)) { + $this->error(static::NOT_DETECTED); + return false; + } + + $mimetype = $this->getMimeType(true); + if (in_array($this->type, $mimetype)) { + return true; + } + + $types = explode('/', $this->type); + $types = array_merge($types, explode('-', $this->type)); + $types = array_merge($types, explode(';', $this->type)); + foreach ($mimetype as $mime) { + if (in_array($mime, $types)) { + return true; + } + } + + $this->error(static::FALSE_TYPE); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/NotExists.php b/lib/laminas/laminas-validator/src/File/NotExists.php new file mode 100644 index 0000000000..0ed8f2f18a --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/NotExists.php @@ -0,0 +1,68 @@ + 'File exists', + ]; + + /** + * Returns true if and only if the file does not exist in the set destinations + * + * @param string|array $value Real file to check for existence + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file, false, true); + + $this->setValue($fileInfo['filename']); + + $check = false; + $directories = $this->getDirectory(true); + if (! isset($directories)) { + $check = true; + if (file_exists($fileInfo['file'])) { + $this->error(self::DOES_EXIST); + return false; + } + } else { + foreach ($directories as $directory) { + if (! isset($directory) || '' === $directory) { + continue; + } + + $check = true; + if (file_exists($directory . DIRECTORY_SEPARATOR . $fileInfo['basename'])) { + $this->error(self::DOES_EXIST); + return false; + } + } + } + + if (! $check) { + $this->error(self::DOES_EXIST); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/File/Sha1.php b/lib/laminas/laminas-validator/src/File/Sha1.php new file mode 100644 index 0000000000..da90c45b7e --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/Sha1.php @@ -0,0 +1,110 @@ + 'File does not match the given sha1 hashes', + self::NOT_DETECTED => 'A sha1 hash could not be evaluated for the given file', + self::NOT_FOUND => 'File is not readable or does not exist', + ]; + + /** + * Options for this validator + * + * @var string + */ + protected $options = [ + 'algorithm' => 'sha1', + 'hash' => null, + ]; + + /** + * Returns all set sha1 hashes + * + * @return array + */ + public function getSha1() + { + return $this->getHash(); + } + + /** + * Sets the sha1 hash for one or multiple files + * + * @param string|array $options + * @return Hash Provides a fluent interface + */ + public function setSha1($options) + { + $this->setHash($options); + return $this; + } + + /** + * Adds the sha1 hash for one or multiple files + * + * @param string|array $options + * @return Hash Provides a fluent interface + */ + public function addSha1($options) + { + $this->addHash($options); + return $this; + } + + /** + * Returns true if and only if the given file confirms the set hash + * + * @param (int|string)[]|string $value Filename to check for hash + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(self::NOT_FOUND); + return false; + } + + $hashes = array_unique(array_keys($this->getHash())); + $filehash = hash_file('sha1', $fileInfo['file']); + if ($filehash === false) { + $this->error(self::NOT_DETECTED); + return false; + } + + foreach ($hashes as $hash) { + if ($filehash === $hash) { + return true; + } + } + + $this->error(self::DOES_NOT_MATCH); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/Size.php b/lib/laminas/laminas-validator/src/File/Size.php new file mode 100644 index 0000000000..d3dd64df14 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/Size.php @@ -0,0 +1,359 @@ + "Maximum allowed size for file is '%max%' but '%size%' detected", + self::TOO_SMALL => "Minimum expected size for file is '%min%' but '%size%' detected", + self::NOT_FOUND => 'File is not readable or does not exist', + ]; + + /** @var array Error message template variables */ + protected $messageVariables = [ + 'min' => ['options' => 'min'], + 'max' => ['options' => 'max'], + 'size' => 'size', + ]; + + /** + * Detected size + * + * @var int + */ + protected $size; + + /** + * Options for this validator + * + * @var array + */ + protected $options = [ + 'min' => null, // Minimum file size, if null there is no minimum + 'max' => null, // Maximum file size, if null there is no maximum + 'useByteString' => true, // Use byte string? + ]; + + /** + * Sets validator options + * + * If $options is an integer, it will be used as maximum file size + * As Array is accepts the following keys: + * 'min': Minimum file size + * 'max': Maximum file size + * 'useByteString': Use bytestring or real size for messages + * + * @param int|array|Traversable $options Options for the adapter + */ + public function __construct($options = null) + { + if (is_string($options) || is_numeric($options)) { + $options = ['max' => $options]; + } + + if (1 < func_num_args()) { + $argv = func_get_args(); + array_shift($argv); + $options['max'] = array_shift($argv); + if (! empty($argv)) { + $options['useByteString'] = array_shift($argv); + } + } + + parent::__construct($options); + } + + /** + * Should messages return bytes as integer or as string in SI notation + * + * @param bool $byteString Use bytestring ? + * @return self + */ + public function useByteString($byteString = true) + { + $this->options['useByteString'] = (bool) $byteString; + return $this; + } + + /** + * Will bytestring be used? + * + * @return bool + */ + public function getByteString() + { + return $this->options['useByteString']; + } + + /** + * Returns the minimum file size + * + * @param bool $raw Whether or not to force return of the raw value (defaults off) + * @return int|string + */ + public function getMin($raw = false) + { + $min = $this->options['min']; + if (! $raw && $this->getByteString()) { + $min = $this->toByteString($min); + } + + return $min; + } + + /** + * Sets the minimum file size + * + * File size can be an integer or a byte string + * This includes 'B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' + * For example: 2000, 2MB, 0.2GB + * + * @param int|string $min The minimum file size + * @return $this Provides a fluent interface + * @throws Exception\InvalidArgumentException When min is greater than max. + */ + public function setMin($min) + { + if (! is_string($min) && ! is_numeric($min)) { + throw new Exception\InvalidArgumentException('Invalid options to validator provided'); + } + + $min = (int) $this->fromByteString($min); + $max = $this->getMax(true); + if (($max !== null) && ($min > $max)) { + throw new Exception\InvalidArgumentException( + "The minimum must be less than or equal to the maximum file size, but $min > $max" + ); + } + + $this->options['min'] = $min; + return $this; + } + + /** + * Returns the maximum file size + * + * @param bool $raw Whether or not to force return of the raw value (defaults off) + * @return int|string + */ + public function getMax($raw = false) + { + $max = $this->options['max']; + if (! $raw && $this->getByteString()) { + $max = $this->toByteString($max); + } + + return $max; + } + + /** + * Sets the maximum file size + * + * File size can be an integer or a byte string + * This includes 'B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' + * For example: 2000, 2MB, 0.2GB + * + * @param int|string $max The maximum file size + * @return $this Provides a fluent interface + * @throws Exception\InvalidArgumentException When max is smaller than min. + */ + public function setMax($max) + { + if (! is_string($max) && ! is_numeric($max)) { + throw new Exception\InvalidArgumentException('Invalid options to validator provided'); + } + + $max = (int) $this->fromByteString($max); + $min = $this->getMin(true); + if (($min !== null) && ($max < $min)) { + throw new Exception\InvalidArgumentException( + "The maximum must be greater than or equal to the minimum file size, but $max < $min" + ); + } + + $this->options['max'] = $max; + return $this; + } + + /** + * Retrieve current detected file size + * + * @return int + */ + protected function getSize() + { + return $this->size; + } + + /** + * Set current size + * + * @param int $size + * @return $this + */ + protected function setSize($size) + { + $this->size = $size; + return $this; + } + + /** + * Returns true if and only if the file size of $value is at least min and + * not bigger than max (when max is not null). + * + * @param string|array $value File to check for size + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(self::NOT_FOUND); + return false; + } + + // limited to 4GB files + ErrorHandler::start(); + $size = sprintf('%u', filesize($fileInfo['file'])); + ErrorHandler::stop(); + $this->size = $size; + + // Check to see if it's smaller than min size + $min = $this->getMin(true); + $max = $this->getMax(true); + if (($min !== null) && ($size < $min)) { + if ($this->getByteString()) { + $this->options['min'] = $this->toByteString($min); + $this->size = $this->toByteString($size); + $this->error(self::TOO_SMALL); + $this->options['min'] = $min; + $this->size = $size; + } else { + $this->error(self::TOO_SMALL); + } + } + + // Check to see if it's larger than max size + if (($max !== null) && ($max < $size)) { + if ($this->getByteString()) { + $this->options['max'] = $this->toByteString($max); + $this->size = $this->toByteString($size); + $this->error(self::TOO_BIG); + $this->options['max'] = $max; + $this->size = $size; + } else { + $this->error(self::TOO_BIG); + } + } + + if ($this->getMessages()) { + return false; + } + + return true; + } + + /** + * Returns the formatted size + * + * @param int $size + * @return string + */ + protected function toByteString($size) + { + $sizes = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + for ($i = 0; $size >= 1024 && $i < 9; $i++) { + $size /= 1024; + } + + return round($size, 2) . $sizes[$i]; + } + + /** + * Returns the unformatted size + * + * @param string $size + * @return float|int|string + */ + protected function fromByteString($size) + { + if (is_numeric($size)) { + return (int) $size; + } + + $type = trim(substr($size, -2, 1)); + + $value = substr($size, 0, -1); + if (! is_numeric($value)) { + $value = trim(substr($value, 0, -1)); + } + + switch (strtoupper($type)) { + case 'Y': + $value *= 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024; + break; + case 'Z': + $value *= 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024; + break; + case 'E': + $value *= 1024 * 1024 * 1024 * 1024 * 1024 * 1024; + break; + case 'P': + $value *= 1024 * 1024 * 1024 * 1024 * 1024; + break; + case 'T': + $value *= 1024 * 1024 * 1024 * 1024; + break; + case 'G': + $value *= 1024 * 1024 * 1024; + break; + case 'M': + $value *= 1024 * 1024; + break; + case 'K': + $value *= 1024; + break; + default: + break; + } + + return $value; + } +} diff --git a/lib/laminas/laminas-validator/src/File/Upload.php b/lib/laminas/laminas-validator/src/File/Upload.php new file mode 100644 index 0000000000..10a0f2f3ea --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/Upload.php @@ -0,0 +1,270 @@ + Error message templates */ + protected $messageTemplates = [ + self::INI_SIZE => "File '%value%' exceeds upload_max_filesize directive in php.ini", + self::FORM_SIZE => "File '%value%' exceeds the MAX_FILE_SIZE directive that was " + . 'specified in the HTML form', + self::PARTIAL => "File '%value%' was only partially uploaded", + self::NO_FILE => "File '%value%' was not uploaded", + self::NO_TMP_DIR => "Missing a temporary folder to store '%value%'", + self::CANT_WRITE => "Failed to write file '%value%' to disk", + self::EXTENSION => "A PHP extension stopped uploading the file '%value%'", + self::ATTACK => "File '%value%' was illegally uploaded. This could be a possible attack", + self::FILE_NOT_FOUND => "File '%value%' was not found", + self::UNKNOWN => "Unknown error while uploading file '%value%'", + ]; + + /** @var array */ + protected $options = [ + 'files' => [], + ]; + + /** + * Sets validator options + * + * The array $files must be given in syntax of Laminas\File\Transfer\Transfer to be checked + * If no files are given the $_FILES array will be used automatically. + * NOTE: This validator will only work with HTTP POST uploads! + * + * @param array|Traversable $options Array of files in syntax of \Laminas\File\Transfer\Transfer + */ + public function __construct($options = []) + { + if (is_array($options) && ! array_key_exists('files', $options)) { + $options = ['files' => $options]; + } + + parent::__construct($options); + } + + /** + * Returns the array of set files + * + * @param string $file (Optional) The file to return in detail + * @return array + * @throws Exception\InvalidArgumentException If file is not found. + */ + public function getFiles($file = null) + { + if ($file !== null) { + $return = []; + foreach ($this->options['files'] as $name => $content) { + if ($name === $file) { + $return[$file] = $this->options['files'][$name]; + } + + if ($content instanceof UploadedFileInterface) { + if ($content->getClientFilename() === $file) { + $return[$name] = $this->options['files'][$name]; + } + } elseif ($content['name'] === $file) { + $return[$name] = $this->options['files'][$name]; + } + } + + if (! $return) { + throw new Exception\InvalidArgumentException("The file '$file' was not found"); + } + + return $return; + } + + return $this->options['files']; + } + + /** + * Sets the files to be checked + * + * @param array $files The files to check in syntax of \Laminas\File\Transfer\Transfer + * @return $this Provides a fluent interface + */ + public function setFiles($files = []) + { + if ( + null === $files + || ((is_countable($files)) + && count($files) === 0) + ) { + $this->options['files'] = $_FILES; + } else { + $this->options['files'] = $files; + } + + if ($this->options['files'] === null) { + $this->options['files'] = []; + } + + foreach ($this->options['files'] as $file => $content) { + if ( + ! $content instanceof UploadedFileInterface + && ! isset($content['error']) + ) { + unset($this->options['files'][$file]); + } + } + + return $this; + } + + /** + * Returns true if and only if the file was uploaded without errors + * + * @param string $value Single file to check for upload errors, when giving null the $_FILES array + * from initialization will be used + * @param mixed $file + * @return bool + */ + public function isValid($value, $file = null) + { + $files = []; + $this->setValue($value); + if (array_key_exists($value, $this->getFiles())) { + $files = array_merge($files, $this->getFiles($value)); + } else { + foreach ($this->getFiles() as $file => $content) { + if ($content instanceof UploadedFileInterface) { + if ($content->getClientFilename() === $value) { + $files = array_merge($files, $this->getFiles($file)); + } + + // PSR cannot search by tmp_name because it does not have + // a public interface to get it, only user defined name + // from form field. + continue; + } + + if (isset($content['name']) && ($content['name'] === $value)) { + $files = array_merge($files, $this->getFiles($file)); + } + + if (isset($content['tmp_name']) && ($content['tmp_name'] === $value)) { + $files = array_merge($files, $this->getFiles($file)); + } + } + } + + if (empty($files)) { + return $this->throwError($file, self::FILE_NOT_FOUND); + } + + foreach ($files as $file => $content) { + $this->value = $file; + $error = $content instanceof UploadedFileInterface + ? $content->getError() + : $content['error']; + + switch ($error) { + case 0: + if ($content instanceof UploadedFileInterface) { + // done! + break; + } + + // For standard SAPI environments, check that the upload + // was valid + if (! is_uploaded_file($content['tmp_name'])) { + $this->throwError($content, self::ATTACK); + } + break; + + case 1: + $this->throwError($content, self::INI_SIZE); + break; + + case 2: + $this->throwError($content, self::FORM_SIZE); + break; + + case 3: + $this->throwError($content, self::PARTIAL); + break; + + case 4: + $this->throwError($content, self::NO_FILE); + break; + + case 6: + $this->throwError($content, self::NO_TMP_DIR); + break; + + case 7: + $this->throwError($content, self::CANT_WRITE); + break; + + case 8: + $this->throwError($content, self::EXTENSION); + break; + + default: + $this->throwError($content, self::UNKNOWN); + break; + } + } + + if ($this->getMessages()) { + return false; + } + + return true; + } + + /** + * Throws an error of the given type + * + * @param array|string|UploadedFileInterface $file + * @param string $errorType + * @return false + */ + protected function throwError($file, $errorType) + { + if ($file !== null) { + if (is_array($file)) { + if (array_key_exists('name', $file)) { + $this->value = $file['name']; + } + } elseif (is_string($file)) { + $this->value = $file; + } elseif ($file instanceof UploadedFileInterface) { + $this->value = $file->getClientFilename(); + } + } + + $this->error($errorType); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/File/UploadFile.php b/lib/laminas/laminas-validator/src/File/UploadFile.php new file mode 100644 index 0000000000..9d73f54afe --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/UploadFile.php @@ -0,0 +1,175 @@ + 'The uploaded file exceeds the upload_max_filesize directive in php.ini', + self::FORM_SIZE => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was ' + . 'specified in the HTML form', + self::PARTIAL => 'The uploaded file was only partially uploaded', + self::NO_FILE => 'No file was uploaded', + self::NO_TMP_DIR => 'Missing a temporary folder', + self::CANT_WRITE => 'Failed to write file to disk', + self::EXTENSION => 'A PHP extension stopped the file upload', + self::ATTACK => 'File was illegally uploaded. This could be a possible attack', + self::FILE_NOT_FOUND => 'File was not found', + self::UNKNOWN => 'Unknown error while uploading file', + ]; + + /** + * Returns true if and only if the file was uploaded without errors + * + * @param string|array|UploadedFileInterface $value File to check for upload errors + * @return bool + * @throws Exception\InvalidArgumentException + */ + public function isValid($value) + { + if (is_array($value)) { + if (! isset($value['tmp_name']) || ! isset($value['name']) || ! isset($value['error'])) { + throw new Exception\InvalidArgumentException( + 'Value array must be in $_FILES format' + ); + } + + return $this->validateUploadedFile( + $value['error'], + $value['name'], + $value['tmp_name'] + ); + } + + if ($value instanceof UploadedFileInterface) { + return $this->validatePsr7UploadedFile($value); + } + + if (is_string($value)) { + return $this->validateUploadedFile(0, basename($value), $value); + } + + $this->error(self::UNKNOWN); + return false; + } + + /** + * @param int $error UPLOAD_ERR_* constant value + * @return bool + */ + private function validateFileFromErrorCode($error) + { + switch ($error) { + case UPLOAD_ERR_OK: + return true; + + case UPLOAD_ERR_INI_SIZE: + $this->error(self::INI_SIZE); + return false; + + case UPLOAD_ERR_FORM_SIZE: + $this->error(self::FORM_SIZE); + return false; + + case UPLOAD_ERR_PARTIAL: + $this->error(self::PARTIAL); + return false; + + case UPLOAD_ERR_NO_FILE: + $this->error(self::NO_FILE); + return false; + + case UPLOAD_ERR_NO_TMP_DIR: + $this->error(self::NO_TMP_DIR); + return false; + + case UPLOAD_ERR_CANT_WRITE: + $this->error(self::CANT_WRITE); + return false; + + case UPLOAD_ERR_EXTENSION: + $this->error(self::EXTENSION); + return false; + + default: + $this->error(self::UNKNOWN); + return false; + } + } + + /** + * @param int $error UPLOAD_ERR_* constant + * @param string $filename + * @param string $uploadedFile Name of uploaded file (gen tmp_name) + * @return bool + */ + private function validateUploadedFile($error, $filename, $uploadedFile) + { + $this->setValue($filename); + + // Normal errors can be validated normally + if ($error !== UPLOAD_ERR_OK) { + return $this->validateFileFromErrorCode($error); + } + + // Did we get no name? Is the file missing? + if (empty($uploadedFile) || false === is_file($uploadedFile)) { + $this->error(self::FILE_NOT_FOUND); + return false; + } + + // Do we have an invalid upload? + if (! is_uploaded_file($uploadedFile)) { + $this->error(self::ATTACK); + return false; + } + + return true; + } + + /** + * @return bool + */ + private function validatePsr7UploadedFile(UploadedFileInterface $uploadedFile) + { + $this->setValue($uploadedFile); + return $this->validateFileFromErrorCode($uploadedFile->getError()); + } +} diff --git a/lib/laminas/laminas-validator/src/File/WordCount.php b/lib/laminas/laminas-validator/src/File/WordCount.php new file mode 100644 index 0000000000..107fb6fdd6 --- /dev/null +++ b/lib/laminas/laminas-validator/src/File/WordCount.php @@ -0,0 +1,204 @@ + "Too many words, maximum '%max%' are allowed but '%count%' were counted", + self::TOO_LESS => "Too few words, minimum '%min%' are expected but '%count%' were counted", + self::NOT_FOUND => 'File is not readable or does not exist', + ]; + + /** @var array Error message template variables */ + protected $messageVariables = [ + 'min' => ['options' => 'min'], + 'max' => ['options' => 'max'], + 'count' => 'count', + ]; + + /** + * Word count + * + * @var int + */ + protected $count; + + /** + * Options for this validator + * + * @var array + */ + protected $options = [ + 'min' => null, // Minimum word count, if null there is no minimum word count + 'max' => null, // Maximum word count, if null there is no maximum word count + ]; + + /** + * Sets validator options + * + * Min limits the word count, when used with max=null it is the maximum word count + * It also accepts an array with the keys 'min' and 'max' + * + * If $options is an integer, it will be used as maximum word count + * As Array is accepts the following keys: + * 'min': Minimum word count + * 'max': Maximum word count + * + * @param int|array|Traversable $options Options for the adapter + */ + public function __construct($options = null) + { + if (1 < func_num_args()) { + $args = func_get_args(); + $options = [ + 'min' => array_shift($args), + 'max' => array_shift($args), + ]; + } + + if (is_string($options) || is_numeric($options)) { + $options = ['max' => $options]; + } + + parent::__construct($options); + } + + /** + * Returns the minimum word count + * + * @return int + */ + public function getMin() + { + return $this->options['min']; + } + + /** + * Sets the minimum word count + * + * @param int|array $min The minimum word count + * @return $this Provides a fluent interface + * @throws Exception\InvalidArgumentException When min is greater than max. + */ + public function setMin($min) + { + if (is_array($min) && isset($min['min'])) { + $min = $min['min']; + } + + if (! is_numeric($min)) { + throw new Exception\InvalidArgumentException('Invalid options to validator provided'); + } + + $min = (int) $min; + if (($this->getMax() !== null) && ($min > $this->getMax())) { + throw new Exception\InvalidArgumentException( + "The minimum must be less than or equal to the maximum word count, but $min > {$this->getMax()}" + ); + } + + $this->options['min'] = $min; + return $this; + } + + /** + * Returns the maximum word count + * + * @return int + */ + public function getMax() + { + return $this->options['max']; + } + + /** + * Sets the maximum file count + * + * @param int|array $max The maximum word count + * @return $this Provides a fluent interface + * @throws Exception\InvalidArgumentException When max is smaller than min. + */ + public function setMax($max) + { + if (is_array($max) && isset($max['max'])) { + $max = $max['max']; + } + + if (! is_numeric($max)) { + throw new Exception\InvalidArgumentException('Invalid options to validator provided'); + } + + $max = (int) $max; + if (($this->getMin() !== null) && ($max < $this->getMin())) { + throw new Exception\InvalidArgumentException( + "The maximum must be greater than or equal to the minimum word count, but $max < {$this->getMin()}" + ); + } + + $this->options['max'] = $max; + return $this; + } + + /** + * Returns true if and only if the counted words are at least min and + * not bigger than max (when max is not null). + * + * @param string|array $value Filename to check for word count + * @param array $file File data from \Laminas\File\Transfer\Transfer (optional) + * @return bool + */ + public function isValid($value, $file = null) + { + $fileInfo = $this->getFileInfo($value, $file); + + $this->setValue($fileInfo['filename']); + + // Is file readable ? + if (empty($fileInfo['file']) || false === is_readable($fileInfo['file'])) { + $this->error(self::NOT_FOUND); + return false; + } + + $content = file_get_contents($fileInfo['file']); + $this->count = str_word_count($content); + if (($this->getMax() !== null) && ($this->count > $this->getMax())) { + $this->error(self::TOO_MUCH); + return false; + } + + if (($this->getMin() !== null) && ($this->count < $this->getMin())) { + $this->error(self::TOO_LESS); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/GpsPoint.php b/lib/laminas/laminas-validator/src/GpsPoint.php new file mode 100644 index 0000000000..7090a4590a --- /dev/null +++ b/lib/laminas/laminas-validator/src/GpsPoint.php @@ -0,0 +1,122 @@ + '%value% is out of Bounds.', + 'gpsPointConvertError' => '%value% can not converted into a Decimal Degree Value.', + 'gpsPointIncompleteCoordinate' => '%value% did not provided a complete Coordinate', + ]; + + /** + * Returns true if and only if $value meets the validation requirements + * + * If $value fails validation, then this method returns false, and + * getMessages() will return an array of messages that explain why the + * validation failed. + * + * @param mixed $value + * @return bool + * @throws Exception\RuntimeException If validation of $value is impossible. + */ + public function isValid($value) + { + if (strpos($value, ',') === false) { + $this->error(self::INCOMPLETE_COORDINATE, $value); + return false; + } + + [$lat, $long] = explode(',', $value); + + if ($this->isValidCoordinate($lat, 90.0000) && $this->isValidCoordinate($long, 180.000)) { + return true; + } + + return false; + } + + /** + * @param string $value + */ + private function isValidCoordinate($value, float $maxBoundary): bool + { + $this->value = $value; + + $value = $this->removeWhiteSpace($value); + if ($this->isDMSValue($value)) { + $value = $this->convertValue($value); + } else { + $value = $this->removeDegreeSign($value); + } + + if ($value === false || $value === null) { + $this->error(self::CONVERT_ERROR); + return false; + } + + $doubleLatitude = (double) $value; + + if ($doubleLatitude <= $maxBoundary && $doubleLatitude >= $maxBoundary * -1) { + return true; + } + + $this->error(self::OUT_OF_BOUNDS); + return false; + } + + /** + * Determines if the give value is a Degrees Minutes Second Definition + */ + private function isDMSValue(string $value): bool + { + return preg_match('/([°\'"]+[NESW])/', $value) > 0; + } + + /** + * @param string $value + * @return false|float + */ + private function convertValue($value) + { + $matches = []; + $result = preg_match_all('/(\d{1,3})°(\d{1,2})\'(\d{1,2}[\.\d]{0,6})"[NESW]/i', $value, $matches); + + if ($result === false || $result === 0) { + return false; + } + + return $matches[1][0] + $matches[2][0] / 60 + ((double) $matches[3][0]) / 3600; + } + + /** + * @param string $value + * @return string + */ + private function removeWhiteSpace($value) + { + return preg_replace('/\s/', '', $value); + } + + /** + * @param string $value + * @return string + */ + private function removeDegreeSign($value) + { + return str_replace('°', '', $value); + } +} diff --git a/lib/laminas/laminas-validator/src/GreaterThan.php b/lib/laminas/laminas-validator/src/GreaterThan.php new file mode 100644 index 0000000000..bab3846ad8 --- /dev/null +++ b/lib/laminas/laminas-validator/src/GreaterThan.php @@ -0,0 +1,154 @@ + "The input is not greater than '%min%'", + self::NOT_GREATER_INCLUSIVE => "The input is not greater than or equal to '%min%'", + ]; + + /** @var array */ + protected $messageVariables = [ + 'min' => 'min', + ]; + + /** + * Minimum value + * + * @var mixed + */ + protected $min; + + /** + * Whether to do inclusive comparisons, allowing equivalence to max + * + * If false, then strict comparisons are done, and the value may equal + * the min option + * + * @var bool + */ + protected $inclusive; + + /** + * Sets validator options + * + * @param array|Traversable $options + * @throws Exception\InvalidArgumentException + */ + public function __construct($options = null) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + if (! is_array($options)) { + $options = func_get_args(); + $temp['min'] = array_shift($options); + + if (! empty($options)) { + $temp['inclusive'] = array_shift($options); + } + + $options = $temp; + } + + if (! array_key_exists('min', $options)) { + throw new Exception\InvalidArgumentException("Missing option 'min'"); + } + + if (! array_key_exists('inclusive', $options)) { + $options['inclusive'] = false; + } + + $this->setMin($options['min']) + ->setInclusive($options['inclusive']); + + parent::__construct($options); + } + + /** + * Returns the min option + * + * @return mixed + */ + public function getMin() + { + return $this->min; + } + + /** + * Sets the min option + * + * @param mixed $min + * @return $this Provides a fluent interface + */ + public function setMin($min) + { + $this->min = $min; + return $this; + } + + /** + * Returns the inclusive option + * + * @return bool + */ + public function getInclusive() + { + return $this->inclusive; + } + + /** + * Sets the inclusive option + * + * @param bool $inclusive + * @return $this Provides a fluent interface + */ + public function setInclusive($inclusive) + { + $this->inclusive = $inclusive; + return $this; + } + + /** + * Returns true if and only if $value is greater than min option + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + $this->setValue($value); + + if ($this->inclusive) { + if ($this->min > $value) { + $this->error(self::NOT_GREATER_INCLUSIVE); + return false; + } + } else { + if ($this->min >= $value) { + $this->error(self::NOT_GREATER); + return false; + } + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Hex.php b/lib/laminas/laminas-validator/src/Hex.php new file mode 100644 index 0000000000..b00198578e --- /dev/null +++ b/lib/laminas/laminas-validator/src/Hex.php @@ -0,0 +1,45 @@ + 'Invalid type given. String expected', + self::NOT_HEX => 'The input contains non-hexadecimal characters', + ]; + + /** + * Returns true if and only if $value contains only hexadecimal digit characters + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value) && ! is_int($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + if (! ctype_xdigit((string) $value)) { + $this->error(self::NOT_HEX); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Hostname.php b/lib/laminas/laminas-validator/src/Hostname.php new file mode 100644 index 0000000000..a5fb700d52 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Hostname.php @@ -0,0 +1,2293 @@ + "The input appears to be a DNS hostname but the given punycode notation cannot be decoded", + self::INVALID => "Invalid type given. String expected", + self::INVALID_DASH => "The input appears to be a DNS hostname but contains a dash in an invalid position", + self::INVALID_HOSTNAME => "The input does not match the expected structure for a DNS hostname", + self::INVALID_HOSTNAME_SCHEMA => "The input appears to be a DNS hostname but cannot match against hostname schema for TLD '%tld%'", + self::INVALID_LOCAL_NAME => "The input does not appear to be a valid local network name", + self::INVALID_URI => "The input does not appear to be a valid URI hostname", + self::IP_ADDRESS_NOT_ALLOWED => "The input appears to be an IP address, but IP addresses are not allowed", + self::LOCAL_NAME_NOT_ALLOWED => "The input appears to be a local network name but local network names are not allowed", + self::UNDECIPHERABLE_TLD => "The input appears to be a DNS hostname but cannot extract TLD part", + self::UNKNOWN_TLD => "The input appears to be a DNS hostname but cannot match TLD against known list", + ]; + + /** @var array */ + protected $messageVariables = [ + 'tld' => 'tld', + ]; + + public const ALLOW_DNS = 1; // Allows Internet domain names (e.g., example.com) + public const ALLOW_IP = 2; // Allows IP addresses + public const ALLOW_LOCAL = 4; // Allows local network names (e.g., localhost, www.localdomain) + public const ALLOW_URI = 8; // Allows URI hostnames + public const ALLOW_ALL = 15; // Allows all types of hostnames + + /** + * Array of valid top-level-domains + * IanaVersion 2022072400 + * + * @see ftp://data.iana.org/TLD/tlds-alpha-by-domain.txt List of all TLDs by domain + * @see http://www.iana.org/domains/root/db/ Official list of supported TLDs + * + * @var string[] + */ + protected $validTlds = [ + 'aaa', + 'aarp', + 'abarth', + 'abb', + 'abbott', + 'abbvie', + 'abc', + 'able', + 'abogado', + 'abudhabi', + 'ac', + 'academy', + 'accenture', + 'accountant', + 'accountants', + 'aco', + 'actor', + 'ad', + 'adac', + 'ads', + 'adult', + 'ae', + 'aeg', + 'aero', + 'aetna', + 'af', + 'afl', + 'africa', + 'ag', + 'agakhan', + 'agency', + 'ai', + 'aig', + 'airbus', + 'airforce', + 'airtel', + 'akdn', + 'al', + 'alfaromeo', + 'alibaba', + 'alipay', + 'allfinanz', + 'allstate', + 'ally', + 'alsace', + 'alstom', + 'am', + 'amazon', + 'americanexpress', + 'americanfamily', + 'amex', + 'amfam', + 'amica', + 'amsterdam', + 'analytics', + 'android', + 'anquan', + 'anz', + 'ao', + 'aol', + 'apartments', + 'app', + 'apple', + 'aq', + 'aquarelle', + 'ar', + 'arab', + 'aramco', + 'archi', + 'army', + 'arpa', + 'art', + 'arte', + 'as', + 'asda', + 'asia', + 'associates', + 'at', + 'athleta', + 'attorney', + 'au', + 'auction', + 'audi', + 'audible', + 'audio', + 'auspost', + 'author', + 'auto', + 'autos', + 'avianca', + 'aw', + 'aws', + 'ax', + 'axa', + 'az', + 'azure', + 'ba', + 'baby', + 'baidu', + 'banamex', + 'bananarepublic', + 'band', + 'bank', + 'bar', + 'barcelona', + 'barclaycard', + 'barclays', + 'barefoot', + 'bargains', + 'baseball', + 'basketball', + 'bauhaus', + 'bayern', + 'bb', + 'bbc', + 'bbt', + 'bbva', + 'bcg', + 'bcn', + 'bd', + 'be', + 'beats', + 'beauty', + 'beer', + 'bentley', + 'berlin', + 'best', + 'bestbuy', + 'bet', + 'bf', + 'bg', + 'bh', + 'bharti', + 'bi', + 'bible', + 'bid', + 'bike', + 'bing', + 'bingo', + 'bio', + 'biz', + 'bj', + 'black', + 'blackfriday', + 'blockbuster', + 'blog', + 'bloomberg', + 'blue', + 'bm', + 'bms', + 'bmw', + 'bn', + 'bnpparibas', + 'bo', + 'boats', + 'boehringer', + 'bofa', + 'bom', + 'bond', + 'boo', + 'book', + 'booking', + 'bosch', + 'bostik', + 'boston', + 'bot', + 'boutique', + 'box', + 'br', + 'bradesco', + 'bridgestone', + 'broadway', + 'broker', + 'brother', + 'brussels', + 'bs', + 'bt', + 'bugatti', + 'build', + 'builders', + 'business', + 'buy', + 'buzz', + 'bv', + 'bw', + 'by', + 'bz', + 'bzh', + 'ca', + 'cab', + 'cafe', + 'cal', + 'call', + 'calvinklein', + 'cam', + 'camera', + 'camp', + 'cancerresearch', + 'canon', + 'capetown', + 'capital', + 'capitalone', + 'car', + 'caravan', + 'cards', + 'care', + 'career', + 'careers', + 'cars', + 'casa', + 'case', + 'cash', + 'casino', + 'cat', + 'catering', + 'catholic', + 'cba', + 'cbn', + 'cbre', + 'cbs', + 'cc', + 'cd', + 'center', + 'ceo', + 'cern', + 'cf', + 'cfa', + 'cfd', + 'cg', + 'ch', + 'chanel', + 'channel', + 'charity', + 'chase', + 'chat', + 'cheap', + 'chintai', + 'christmas', + 'chrome', + 'church', + 'ci', + 'cipriani', + 'circle', + 'cisco', + 'citadel', + 'citi', + 'citic', + 'city', + 'cityeats', + 'ck', + 'cl', + 'claims', + 'cleaning', + 'click', + 'clinic', + 'clinique', + 'clothing', + 'cloud', + 'club', + 'clubmed', + 'cm', + 'cn', + 'co', + 'coach', + 'codes', + 'coffee', + 'college', + 'cologne', + 'com', + 'comcast', + 'commbank', + 'community', + 'company', + 'compare', + 'computer', + 'comsec', + 'condos', + 'construction', + 'consulting', + 'contact', + 'contractors', + 'cooking', + 'cookingchannel', + 'cool', + 'coop', + 'corsica', + 'country', + 'coupon', + 'coupons', + 'courses', + 'cpa', + 'cr', + 'credit', + 'creditcard', + 'creditunion', + 'cricket', + 'crown', + 'crs', + 'cruise', + 'cruises', + 'cu', + 'cuisinella', + 'cv', + 'cw', + 'cx', + 'cy', + 'cymru', + 'cyou', + 'cz', + 'dabur', + 'dad', + 'dance', + 'data', + 'date', + 'dating', + 'datsun', + 'day', + 'dclk', + 'dds', + 'de', + 'deal', + 'dealer', + 'deals', + 'degree', + 'delivery', + 'dell', + 'deloitte', + 'delta', + 'democrat', + 'dental', + 'dentist', + 'desi', + 'design', + 'dev', + 'dhl', + 'diamonds', + 'diet', + 'digital', + 'direct', + 'directory', + 'discount', + 'discover', + 'dish', + 'diy', + 'dj', + 'dk', + 'dm', + 'dnp', + 'do', + 'docs', + 'doctor', + 'dog', + 'domains', + 'dot', + 'download', + 'drive', + 'dtv', + 'dubai', + 'dunlop', + 'dupont', + 'durban', + 'dvag', + 'dvr', + 'dz', + 'earth', + 'eat', + 'ec', + 'eco', + 'edeka', + 'edu', + 'education', + 'ee', + 'eg', + 'email', + 'emerck', + 'energy', + 'engineer', + 'engineering', + 'enterprises', + 'epson', + 'equipment', + 'er', + 'ericsson', + 'erni', + 'es', + 'esq', + 'estate', + 'et', + 'etisalat', + 'eu', + 'eurovision', + 'eus', + 'events', + 'exchange', + 'expert', + 'exposed', + 'express', + 'extraspace', + 'fage', + 'fail', + 'fairwinds', + 'faith', + 'family', + 'fan', + 'fans', + 'farm', + 'farmers', + 'fashion', + 'fast', + 'fedex', + 'feedback', + 'ferrari', + 'ferrero', + 'fi', + 'fiat', + 'fidelity', + 'fido', + 'film', + 'final', + 'finance', + 'financial', + 'fire', + 'firestone', + 'firmdale', + 'fish', + 'fishing', + 'fit', + 'fitness', + 'fj', + 'fk', + 'flickr', + 'flights', + 'flir', + 'florist', + 'flowers', + 'fly', + 'fm', + 'fo', + 'foo', + 'food', + 'foodnetwork', + 'football', + 'ford', + 'forex', + 'forsale', + 'forum', + 'foundation', + 'fox', + 'fr', + 'free', + 'fresenius', + 'frl', + 'frogans', + 'frontdoor', + 'frontier', + 'ftr', + 'fujitsu', + 'fun', + 'fund', + 'furniture', + 'futbol', + 'fyi', + 'ga', + 'gal', + 'gallery', + 'gallo', + 'gallup', + 'game', + 'games', + 'gap', + 'garden', + 'gay', + 'gb', + 'gbiz', + 'gd', + 'gdn', + 'ge', + 'gea', + 'gent', + 'genting', + 'george', + 'gf', + 'gg', + 'ggee', + 'gh', + 'gi', + 'gift', + 'gifts', + 'gives', + 'giving', + 'gl', + 'glass', + 'gle', + 'global', + 'globo', + 'gm', + 'gmail', + 'gmbh', + 'gmo', + 'gmx', + 'gn', + 'godaddy', + 'gold', + 'goldpoint', + 'golf', + 'goo', + 'goodyear', + 'goog', + 'google', + 'gop', + 'got', + 'gov', + 'gp', + 'gq', + 'gr', + 'grainger', + 'graphics', + 'gratis', + 'green', + 'gripe', + 'grocery', + 'group', + 'gs', + 'gt', + 'gu', + 'guardian', + 'gucci', + 'guge', + 'guide', + 'guitars', + 'guru', + 'gw', + 'gy', + 'hair', + 'hamburg', + 'hangout', + 'haus', + 'hbo', + 'hdfc', + 'hdfcbank', + 'health', + 'healthcare', + 'help', + 'helsinki', + 'here', + 'hermes', + 'hgtv', + 'hiphop', + 'hisamitsu', + 'hitachi', + 'hiv', + 'hk', + 'hkt', + 'hm', + 'hn', + 'hockey', + 'holdings', + 'holiday', + 'homedepot', + 'homegoods', + 'homes', + 'homesense', + 'honda', + 'horse', + 'hospital', + 'host', + 'hosting', + 'hot', + 'hoteles', + 'hotels', + 'hotmail', + 'house', + 'how', + 'hr', + 'hsbc', + 'ht', + 'hu', + 'hughes', + 'hyatt', + 'hyundai', + 'ibm', + 'icbc', + 'ice', + 'icu', + 'id', + 'ie', + 'ieee', + 'ifm', + 'ikano', + 'il', + 'im', + 'imamat', + 'imdb', + 'immo', + 'immobilien', + 'in', + 'inc', + 'industries', + 'infiniti', + 'info', + 'ing', + 'ink', + 'institute', + 'insurance', + 'insure', + 'int', + 'international', + 'intuit', + 'investments', + 'io', + 'ipiranga', + 'iq', + 'ir', + 'irish', + 'is', + 'ismaili', + 'ist', + 'istanbul', + 'it', + 'itau', + 'itv', + 'jaguar', + 'java', + 'jcb', + 'je', + 'jeep', + 'jetzt', + 'jewelry', + 'jio', + 'jll', + 'jm', + 'jmp', + 'jnj', + 'jo', + 'jobs', + 'joburg', + 'jot', + 'joy', + 'jp', + 'jpmorgan', + 'jprs', + 'juegos', + 'juniper', + 'kaufen', + 'kddi', + 'ke', + 'kerryhotels', + 'kerrylogistics', + 'kerryproperties', + 'kfh', + 'kg', + 'kh', + 'ki', + 'kia', + 'kids', + 'kim', + 'kinder', + 'kindle', + 'kitchen', + 'kiwi', + 'km', + 'kn', + 'koeln', + 'komatsu', + 'kosher', + 'kp', + 'kpmg', + 'kpn', + 'kr', + 'krd', + 'kred', + 'kuokgroup', + 'kw', + 'ky', + 'kyoto', + 'kz', + 'la', + 'lacaixa', + 'lamborghini', + 'lamer', + 'lancaster', + 'lancia', + 'land', + 'landrover', + 'lanxess', + 'lasalle', + 'lat', + 'latino', + 'latrobe', + 'law', + 'lawyer', + 'lb', + 'lc', + 'lds', + 'lease', + 'leclerc', + 'lefrak', + 'legal', + 'lego', + 'lexus', + 'lgbt', + 'li', + 'lidl', + 'life', + 'lifeinsurance', + 'lifestyle', + 'lighting', + 'like', + 'lilly', + 'limited', + 'limo', + 'lincoln', + 'linde', + 'link', + 'lipsy', + 'live', + 'living', + 'lk', + 'llc', + 'llp', + 'loan', + 'loans', + 'locker', + 'locus', + 'loft', + 'lol', + 'london', + 'lotte', + 'lotto', + 'love', + 'lpl', + 'lplfinancial', + 'lr', + 'ls', + 'lt', + 'ltd', + 'ltda', + 'lu', + 'lundbeck', + 'luxe', + 'luxury', + 'lv', + 'ly', + 'ma', + 'macys', + 'madrid', + 'maif', + 'maison', + 'makeup', + 'man', + 'management', + 'mango', + 'map', + 'market', + 'marketing', + 'markets', + 'marriott', + 'marshalls', + 'maserati', + 'mattel', + 'mba', + 'mc', + 'mckinsey', + 'md', + 'me', + 'med', + 'media', + 'meet', + 'melbourne', + 'meme', + 'memorial', + 'men', + 'menu', + 'merckmsd', + 'mg', + 'mh', + 'miami', + 'microsoft', + 'mil', + 'mini', + 'mint', + 'mit', + 'mitsubishi', + 'mk', + 'ml', + 'mlb', + 'mls', + 'mm', + 'mma', + 'mn', + 'mo', + 'mobi', + 'mobile', + 'moda', + 'moe', + 'moi', + 'mom', + 'monash', + 'money', + 'monster', + 'mormon', + 'mortgage', + 'moscow', + 'moto', + 'motorcycles', + 'mov', + 'movie', + 'mp', + 'mq', + 'mr', + 'ms', + 'msd', + 'mt', + 'mtn', + 'mtr', + 'mu', + 'museum', + 'music', + 'mutual', + 'mv', + 'mw', + 'mx', + 'my', + 'mz', + 'na', + 'nab', + 'nagoya', + 'name', + 'natura', + 'navy', + 'nba', + 'nc', + 'ne', + 'nec', + 'net', + 'netbank', + 'netflix', + 'network', + 'neustar', + 'new', + 'news', + 'next', + 'nextdirect', + 'nexus', + 'nf', + 'nfl', + 'ng', + 'ngo', + 'nhk', + 'ni', + 'nico', + 'nike', + 'nikon', + 'ninja', + 'nissan', + 'nissay', + 'nl', + 'no', + 'nokia', + 'northwesternmutual', + 'norton', + 'now', + 'nowruz', + 'nowtv', + 'np', + 'nr', + 'nra', + 'nrw', + 'ntt', + 'nu', + 'nyc', + 'nz', + 'obi', + 'observer', + 'office', + 'okinawa', + 'olayan', + 'olayangroup', + 'oldnavy', + 'ollo', + 'om', + 'omega', + 'one', + 'ong', + 'onl', + 'online', + 'ooo', + 'open', + 'oracle', + 'orange', + 'org', + 'organic', + 'origins', + 'osaka', + 'otsuka', + 'ott', + 'ovh', + 'pa', + 'page', + 'panasonic', + 'paris', + 'pars', + 'partners', + 'parts', + 'party', + 'passagens', + 'pay', + 'pccw', + 'pe', + 'pet', + 'pf', + 'pfizer', + 'pg', + 'ph', + 'pharmacy', + 'phd', + 'philips', + 'phone', + 'photo', + 'photography', + 'photos', + 'physio', + 'pics', + 'pictet', + 'pictures', + 'pid', + 'pin', + 'ping', + 'pink', + 'pioneer', + 'pizza', + 'pk', + 'pl', + 'place', + 'play', + 'playstation', + 'plumbing', + 'plus', + 'pm', + 'pn', + 'pnc', + 'pohl', + 'poker', + 'politie', + 'porn', + 'post', + 'pr', + 'pramerica', + 'praxi', + 'press', + 'prime', + 'pro', + 'prod', + 'productions', + 'prof', + 'progressive', + 'promo', + 'properties', + 'property', + 'protection', + 'pru', + 'prudential', + 'ps', + 'pt', + 'pub', + 'pw', + 'pwc', + 'py', + 'qa', + 'qpon', + 'quebec', + 'quest', + 'racing', + 'radio', + 're', + 'read', + 'realestate', + 'realtor', + 'realty', + 'recipes', + 'red', + 'redstone', + 'redumbrella', + 'rehab', + 'reise', + 'reisen', + 'reit', + 'reliance', + 'ren', + 'rent', + 'rentals', + 'repair', + 'report', + 'republican', + 'rest', + 'restaurant', + 'review', + 'reviews', + 'rexroth', + 'rich', + 'richardli', + 'ricoh', + 'ril', + 'rio', + 'rip', + 'ro', + 'rocher', + 'rocks', + 'rodeo', + 'rogers', + 'room', + 'rs', + 'rsvp', + 'ru', + 'rugby', + 'ruhr', + 'run', + 'rw', + 'rwe', + 'ryukyu', + 'sa', + 'saarland', + 'safe', + 'safety', + 'sakura', + 'sale', + 'salon', + 'samsclub', + 'samsung', + 'sandvik', + 'sandvikcoromant', + 'sanofi', + 'sap', + 'sarl', + 'sas', + 'save', + 'saxo', + 'sb', + 'sbi', + 'sbs', + 'sc', + 'sca', + 'scb', + 'schaeffler', + 'schmidt', + 'scholarships', + 'school', + 'schule', + 'schwarz', + 'science', + 'scot', + 'sd', + 'se', + 'search', + 'seat', + 'secure', + 'security', + 'seek', + 'select', + 'sener', + 'services', + 'ses', + 'seven', + 'sew', + 'sex', + 'sexy', + 'sfr', + 'sg', + 'sh', + 'shangrila', + 'sharp', + 'shaw', + 'shell', + 'shia', + 'shiksha', + 'shoes', + 'shop', + 'shopping', + 'shouji', + 'show', + 'showtime', + 'si', + 'silk', + 'sina', + 'singles', + 'site', + 'sj', + 'sk', + 'ski', + 'skin', + 'sky', + 'skype', + 'sl', + 'sling', + 'sm', + 'smart', + 'smile', + 'sn', + 'sncf', + 'so', + 'soccer', + 'social', + 'softbank', + 'software', + 'sohu', + 'solar', + 'solutions', + 'song', + 'sony', + 'soy', + 'spa', + 'space', + 'sport', + 'spot', + 'sr', + 'srl', + 'ss', + 'st', + 'stada', + 'staples', + 'star', + 'statebank', + 'statefarm', + 'stc', + 'stcgroup', + 'stockholm', + 'storage', + 'store', + 'stream', + 'studio', + 'study', + 'style', + 'su', + 'sucks', + 'supplies', + 'supply', + 'support', + 'surf', + 'surgery', + 'suzuki', + 'sv', + 'swatch', + 'swiss', + 'sx', + 'sy', + 'sydney', + 'systems', + 'sz', + 'tab', + 'taipei', + 'talk', + 'taobao', + 'target', + 'tatamotors', + 'tatar', + 'tattoo', + 'tax', + 'taxi', + 'tc', + 'tci', + 'td', + 'tdk', + 'team', + 'tech', + 'technology', + 'tel', + 'temasek', + 'tennis', + 'teva', + 'tf', + 'tg', + 'th', + 'thd', + 'theater', + 'theatre', + 'tiaa', + 'tickets', + 'tienda', + 'tiffany', + 'tips', + 'tires', + 'tirol', + 'tj', + 'tjmaxx', + 'tjx', + 'tk', + 'tkmaxx', + 'tl', + 'tm', + 'tmall', + 'tn', + 'to', + 'today', + 'tokyo', + 'tools', + 'top', + 'toray', + 'toshiba', + 'total', + 'tours', + 'town', + 'toyota', + 'toys', + 'tr', + 'trade', + 'trading', + 'training', + 'travel', + 'travelchannel', + 'travelers', + 'travelersinsurance', + 'trust', + 'trv', + 'tt', + 'tube', + 'tui', + 'tunes', + 'tushu', + 'tv', + 'tvs', + 'tw', + 'tz', + 'ua', + 'ubank', + 'ubs', + 'ug', + 'uk', + 'unicom', + 'university', + 'uno', + 'uol', + 'ups', + 'us', + 'uy', + 'uz', + 'va', + 'vacations', + 'vana', + 'vanguard', + 'vc', + 've', + 'vegas', + 'ventures', + 'verisign', + 'versicherung', + 'vet', + 'vg', + 'vi', + 'viajes', + 'video', + 'vig', + 'viking', + 'villas', + 'vin', + 'vip', + 'virgin', + 'visa', + 'vision', + 'viva', + 'vivo', + 'vlaanderen', + 'vn', + 'vodka', + 'volkswagen', + 'volvo', + 'vote', + 'voting', + 'voto', + 'voyage', + 'vu', + 'vuelos', + 'wales', + 'walmart', + 'walter', + 'wang', + 'wanggou', + 'watch', + 'watches', + 'weather', + 'weatherchannel', + 'webcam', + 'weber', + 'website', + 'wed', + 'wedding', + 'weibo', + 'weir', + 'wf', + 'whoswho', + 'wien', + 'wiki', + 'williamhill', + 'win', + 'windows', + 'wine', + 'winners', + 'wme', + 'wolterskluwer', + 'woodside', + 'work', + 'works', + 'world', + 'wow', + 'ws', + 'wtc', + 'wtf', + 'xbox', + 'xerox', + 'xfinity', + 'xihuan', + 'xin', + 'कॉम', + 'セール', + '佛山', + 'ಭಾರತ', + '慈善', + '集团', + '在线', + '한국', + 'ଭାରତ', + '点看', + 'คอม', + 'ভাৰত', + 'ভারত', + '八卦', + 'ישראל', + 'موقع', + 'বাংলা', + '公益', + '公司', + '香格里拉', + '网站', + '移动', + '我爱你', + 'москва', + 'қаз', + 'католик', + 'онлайн', + 'сайт', + '联通', + 'срб', + 'бг', + 'бел', + 'קום', + '时尚', + '微博', + '淡马锡', + 'ファッション', + 'орг', + 'नेट', + 'ストア', + 'アマゾン', + '삼성', + 'சிங்கப்பூர்', + '商标', + '商店', + '商城', + 'дети', + 'мкд', + 'ею', + 'ポイント', + '新闻', + '家電', + 'كوم', + '中文网', + '中信', + '中国', + '中國', + '娱乐', + '谷歌', + 'భారత్', + 'ලංකා', + '電訊盈科', + '购物', + 'クラウド', + 'ભારત', + '通販', + 'भारतम्', + 'भारत', + 'भारोत', + '网店', + 'संगठन', + '餐厅', + '网络', + 'ком', + 'укр', + '香港', + '亚马逊', + '诺基亚', + '食品', + '飞利浦', + '台湾', + '台灣', + '手机', + 'мон', + 'الجزائر', + 'عمان', + 'ارامكو', + 'ایران', + 'العليان', + 'اتصالات', + 'امارات', + 'بازار', + 'موريتانيا', + 'پاکستان', + 'الاردن', + 'بارت', + 'بھارت', + 'المغرب', + 'ابوظبي', + 'البحرين', + 'السعودية', + 'ڀارت', + 'كاثوليك', + 'سودان', + 'همراه', + 'عراق', + 'مليسيا', + '澳門', + '닷컴', + '政府', + 'شبكة', + 'بيتك', + 'عرب', + 'გე', + '机构', + '组织机构', + '健康', + 'ไทย', + 'سورية', + '招聘', + 'рус', + 'рф', + 'تونس', + '大拿', + 'ລາວ', + 'みんな', + 'グーグル', + 'ευ', + 'ελ', + '世界', + '書籍', + 'ഭാരതം', + 'ਭਾਰਤ', + '网址', + '닷넷', + 'コム', + '天主教', + '游戏', + 'vermögensberater', + 'vermögensberatung', + '企业', + '信息', + '嘉里大酒店', + '嘉里', + 'مصر', + 'قطر', + '广东', + 'இலங்கை', + 'இந்தியா', + 'հայ', + '新加坡', + 'فلسطين', + '政务', + 'xxx', + 'xyz', + 'yachts', + 'yahoo', + 'yamaxun', + 'yandex', + 'ye', + 'yodobashi', + 'yoga', + 'yokohama', + 'you', + 'youtube', + 'yt', + 'yun', + 'za', + 'zappos', + 'zara', + 'zero', + 'zip', + 'zm', + 'zone', + 'zuerich', + 'zw', + ]; + + /** + * Array for valid Idns + * + * @see http://www.iana.org/domains/idn-tables/ Official list of supported IDN Chars + * (.AC) Ascension Island http://www.nic.ac/pdf/AC-IDN-Policy.pdf + * (.AR) Argentina http://www.nic.ar/faqidn.html + * (.AS) American Samoa http://www.nic.as/idn/chars.cfm + * (.AT) Austria http://www.nic.at/en/service/technical_information/idn/charset_converter/ + * (.BIZ) International http://www.iana.org/domains/idn-tables/ + * (.BR) Brazil http://registro.br/faq/faq6.html + * (.BV) Bouvett Island http://www.norid.no/domeneregistrering/idn/idn_nyetegn.en.html + * (.CAT) Catalan http://www.iana.org/domains/idn-tables/tables/cat_ca_1.0.html + * (.CH) Switzerland https://nic.switch.ch/reg/ocView.action?res=EF6GW2JBPVTG67DLNIQXU234MN6SC33JNQQGI7L6#anhang1 + * (.CL) Chile http://www.iana.org/domains/idn-tables/tables/cl_latn_1.0.html + * (.COM) International http://www.verisign.com/information-services/naming-services/internationalized-domain-names/index.html + * (.DE) Germany https://www.denic.de/en/know-how/idn-domains/idn-character-list/ + * (.DK) Danmark http://www.dk-hostmaster.dk/index.php?id=151 + * (.EE) Estonia https://www.iana.org/domains/idn-tables/tables/pl_et-pl_1.0.html + * (.ES) Spain https://www.nic.es/media/2008-05/1210147705287.pdf + * (.FI) Finland http://www.ficora.fi/en/index/palvelut/fiverkkotunnukset/aakkostenkaytto.html + * (.GR) Greece https://grweb.ics.forth.gr/CharacterTable1_en.jsp + * (.HR) Croatia https://www.dns.hr/en/portal/files/Odluka-1,2alfanum-dijak.pdf + * (.HU) Hungary http://www.domain.hu/domain/English/szabalyzat/szabalyzat.html + * (.IL) Israel http://www.isoc.org.il/domains/il-domain-rules.html + * (.INFO) International http://www.nic.info/info/idn + * (.IO) British Indian Ocean Territory http://www.nic.io/IO-IDN-Policy.pdf + * (.IR) Iran http://www.nic.ir/Allowable_Characters_dot-iran + * (.IS) Iceland https://www.isnic.is/en/domain/rules#2 + * (.KR) Korea http://www.iana.org/domains/idn-tables/tables/kr_ko-kr_1.0.html + * (.LI) Liechtenstein https://nic.switch.ch/reg/ocView.action?res=EF6GW2JBPVTG67DLNIQXU234MN6SC33JNQQGI7L6#anhang1 + * (.LT) Lithuania http://www.domreg.lt/static/doc/public/idn_symbols-en.pdf + * (.MD) Moldova http://www.register.md/ + * (.MUSEUM) International http://www.iana.org/domains/idn-tables/tables/museum_latn_1.0.html + * (.NET) International http://www.verisign.com/information-services/naming-services/internationalized-domain-names/index.html + * (.NO) Norway http://www.norid.no/domeneregistrering/idn/idn_nyetegn.en.html + * (.NU) Niue http://www.worldnames.net/ + * (.ORG) International http://www.pir.org/index.php?db=content/FAQs&tbl=FAQs_Registrant&id=2 + * (.PE) Peru https://www.nic.pe/nuevas_politicas_faq_2.php + * (.PL) Poland http://www.dns.pl/IDN/allowed_character_sets.pdf + * (.PR) Puerto Rico http://www.nic.pr/idn_rules.asp + * (.PT) Portugal https://online.dns.pt/dns_2008/do?com=DS;8216320233;111;+PAGE(4000058)+K-CAT-CODIGO(C.125)+RCNT(100); + * (.RU) Russia http://www.iana.org/domains/idn-tables/tables/ru_ru-ru_1.0.html + * (.SA) Saudi Arabia http://www.iana.org/domains/idn-tables/tables/sa_ar_1.0.html + * (.SE) Sweden http://www.iis.se/english/IDN_campaignsite.shtml?lang=en + * (.SH) Saint Helena http://www.nic.sh/SH-IDN-Policy.pdf + * (.SJ) Svalbard and Jan Mayen http://www.norid.no/domeneregistrering/idn/idn_nyetegn.en.html + * (.TH) Thailand http://www.iana.org/domains/idn-tables/tables/th_th-th_1.0.html + * (.TM) Turkmenistan http://www.nic.tm/TM-IDN-Policy.pdf + * (.TR) Turkey https://www.nic.tr/index.php + * (.UA) Ukraine http://www.iana.org/domains/idn-tables/tables/ua_cyrl_1.2.html + * (.VE) Venice http://www.iana.org/domains/idn-tables/tables/ve_es_1.0.html + * (.VN) Vietnam http://www.vnnic.vn/english/5-6-300-2-2-04-20071115.htm#1.%20Introduction + * + * @var array> + */ + protected $validIdns = [ + 'AC' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿāăąćĉċčďđēėęěĝġģĥħīįĵķĺļľŀłńņňŋőœŕŗřśŝşšţťŧūŭůűųŵŷźżž]{1,63}$/iu'], + 'AR' => [1 => '/^[\x{002d}0-9a-zà-ãç-êìíñ-õü]{1,63}$/iu'], + 'AS' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıĵķĸĺļľłńņňŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźż]{1,63}$/iu'], + 'AT' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿœšž]{1,63}$/iu'], + 'BIZ' => 'Hostname/Biz.php', + 'BR' => [1 => '/^[\x{002d}0-9a-zà-ãçéíó-õúü]{1,63}$/iu'], + 'BV' => [1 => '/^[\x{002d}0-9a-zàáä-éêñ-ôöøüčđńŋšŧž]{1,63}$/iu'], + 'CAT' => [1 => '/^[\x{002d}0-9a-z·àç-éíïòóúü]{1,63}$/iu'], + 'CH' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿœ]{1,63}$/iu'], + 'CL' => [1 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu'], + 'CN' => 'Hostname/Cn.php', + 'COM' => 'Hostname/Com.php', + 'DE' => [1 => '/^[\x{002d}0-9a-záàăâåäãąāæćĉčċçďđéèĕêěëėęēğĝġģĥħíìĭîïĩįīıĵķĺľļłńňñņŋóòŏôöőõøōœĸŕřŗśŝšşßťţŧúùŭûůüűũųūŵýŷÿźžżðþ]{1,63}$/iu'], + 'DK' => [1 => '/^[\x{002d}0-9a-zäåæéöøü]{1,63}$/iu'], + 'EE' => [1 => '/^[\x{002d}0-9a-zäõöüšž]{1,63}$/iu'], + 'ES' => [1 => '/^[\x{002d}0-9a-zàáçèéíïñòóúü·]{1,63}$/iu'], + 'EU' => [ + 1 => '/^[\x{002d}0-9a-zà-öø-ÿ]{1,63}$/iu', + 2 => '/^[\x{002d}0-9a-zāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıĵķĺļľŀłńņňʼnŋōŏőœŕŗřśŝšťŧũūŭůűųŵŷźżž]{1,63}$/iu', + 3 => '/^[\x{002d}0-9a-zșț]{1,63}$/iu', + 4 => '/^[\x{002d}0-9a-zΐάέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώ]{1,63}$/iu', + 5 => '/^[\x{002d}0-9a-zабвгдежзийклмнопрстуфхцчшщъыьэюя]{1,63}$/iu', + 6 => '/^[\x{002d}0-9a-zἀ-ἇἐ-ἕἠ-ἧἰ-ἷὀ-ὅὐ-ὗὠ-ὧὰ-ὼώᾀ-ᾇᾐ-ᾗᾠ-ᾧᾰ-ᾴᾶᾷῂῃῄῆῇῐ-ῒΐῖῗῠ-ῧῲῳῴῶῷ]{1,63}$/iu', + ], + 'FI' => [1 => '/^[\x{002d}0-9a-zäåö]{1,63}$/iu'], + 'GR' => [1 => '/^[\x{002d}0-9a-zΆΈΉΊΌΎ-ΡΣ-ώἀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼῂῃῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲῳῴῶ-ῼ]{1,63}$/iu'], + 'HK' => 'Hostname/Cn.php', + 'HR' => [1 => '/^[\x{002d}0-9a-zžćčđš]{1,63}$/iu'], + 'HU' => [1 => '/^[\x{002d}0-9a-záéíóöúüőű]{1,63}$/iu'], + 'IL' => [ + 1 => '/^[\x{002d}0-9\x{05D0}-\x{05EA}]{1,63}$/iu', + 2 => '/^[\x{002d}0-9a-z]{1,63}$/i', + ], + 'INFO' => [ + 1 => '/^[\x{002d}0-9a-zäåæéöøü]{1,63}$/iu', + 2 => '/^[\x{002d}0-9a-záéíóöúüőű]{1,63}$/iu', + 3 => '/^[\x{002d}0-9a-záæéíðóöúýþ]{1,63}$/iu', + 4 => '/^[\x{AC00}-\x{D7A3}]{1,17}$/iu', + 5 => '/^[\x{002d}0-9a-zāčēģīķļņōŗšūž]{1,63}$/iu', + 6 => '/^[\x{002d}0-9a-ząčėęįšūųž]{1,63}$/iu', + 7 => '/^[\x{002d}0-9a-zóąćęłńśźż]{1,63}$/iu', + 8 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu', + ], + 'IO' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿăąāćĉčċďđĕěėęēğĝġģĥħĭĩįīıĵķĺľļłńňņŋŏőōœĸŕřŗśŝšşťţŧŭůűũųūŵŷźžż]{1,63}$/iu'], + 'IS' => [1 => '/^[\x{002d}0-9a-záéýúíóþæöð]{1,63}$/iu'], + 'IT' => [1 => '/^[\x{002d}0-9a-zàâäèéêëìîïòôöùûüæœçÿß-]{1,63}$/iu'], + 'JP' => 'Hostname/Jp.php', + 'KR' => [1 => '/^[\x{AC00}-\x{D7A3}]{1,17}$/iu'], + 'LI' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿœ]{1,63}$/iu'], + 'LT' => [1 => '/^[\x{002d}0-9ąčęėįšųūž]{1,63}$/iu'], + 'MD' => [1 => '/^[\x{002d}0-9ăâîşţ]{1,63}$/iu'], + 'MUSEUM' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿāăąćċčďđēėęěğġģħīįıķĺļľłńņňŋōőœŕŗřśşšţťŧūůűųŵŷźżžǎǐǒǔ\x{01E5}\x{01E7}\x{01E9}\x{01EF}ə\x{0292}ẁẃẅỳ]{1,63}$/iu'], + 'NET' => 'Hostname/Com.php', + 'NO' => [1 => '/^[\x{002d}0-9a-zàáä-éêñ-ôöøüčđńŋšŧž]{1,63}$/iu'], + 'NU' => 'Hostname/Com.php', + 'ORG' => [ + 1 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu', + 2 => '/^[\x{002d}0-9a-zóąćęłńśźż]{1,63}$/iu', + 3 => '/^[\x{002d}0-9a-záäåæéëíðóöøúüýþ]{1,63}$/iu', + 4 => '/^[\x{002d}0-9a-záéíóöúüőű]{1,63}$/iu', + 5 => '/^[\x{002d}0-9a-ząčėęįšūųž]{1,63}$/iu', + 6 => '/^[\x{AC00}-\x{D7A3}]{1,17}$/iu', + 7 => '/^[\x{002d}0-9a-zāčēģīķļņōŗšūž]{1,63}$/iu', + ], + 'PE' => [1 => '/^[\x{002d}0-9a-zñáéíóúü]{1,63}$/iu'], + 'PL' => [ + 1 => '/^[\x{002d}0-9a-zāčēģīķļņōŗšūž]{1,63}$/iu', + 2 => '/^[\x{002d}а-ик-ш\x{0450}ѓѕјљњќџ]{1,63}$/iu', + 3 => '/^[\x{002d}0-9a-zâîăşţ]{1,63}$/iu', + 4 => '/^[\x{002d}0-9а-яё\x{04C2}]{1,63}$/iu', + 5 => '/^[\x{002d}0-9a-zàáâèéêìíîòóôùúûċġħż]{1,63}$/iu', + 6 => '/^[\x{002d}0-9a-zàäåæéêòóôöøü]{1,63}$/iu', + 7 => '/^[\x{002d}0-9a-zóąćęłńśźż]{1,63}$/iu', + 8 => '/^[\x{002d}0-9a-zàáâãçéêíòóôõúü]{1,63}$/iu', + 9 => '/^[\x{002d}0-9a-zâîăşţ]{1,63}$/iu', + 10 => '/^[\x{002d}0-9a-záäéíóôúýčďĺľňŕšťž]{1,63}$/iu', + 11 => '/^[\x{002d}0-9a-zçë]{1,63}$/iu', + 12 => '/^[\x{002d}0-9а-ик-шђјљњћџ]{1,63}$/iu', + 13 => '/^[\x{002d}0-9a-zćčđšž]{1,63}$/iu', + 14 => '/^[\x{002d}0-9a-zâçöûüğış]{1,63}$/iu', + 15 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu', + 16 => '/^[\x{002d}0-9a-zäõöüšž]{1,63}$/iu', + 17 => '/^[\x{002d}0-9a-zĉĝĥĵŝŭ]{1,63}$/iu', + 18 => '/^[\x{002d}0-9a-zâäéëîô]{1,63}$/iu', + 19 => '/^[\x{002d}0-9a-zàáâäåæçèéêëìíîïðñòôöøùúûüýćčłńřśš]{1,63}$/iu', + 20 => '/^[\x{002d}0-9a-zäåæõöøüšž]{1,63}$/iu', + 21 => '/^[\x{002d}0-9a-zàáçèéìíòóùú]{1,63}$/iu', + 22 => '/^[\x{002d}0-9a-zàáéíóöúüőű]{1,63}$/iu', + 23 => '/^[\x{002d}0-9ΐά-ώ]{1,63}$/iu', + 24 => '/^[\x{002d}0-9a-zàáâåæçèéêëðóôöøüþœ]{1,63}$/iu', + 25 => '/^[\x{002d}0-9a-záäéíóöúüýčďěňřšťůž]{1,63}$/iu', + 26 => '/^[\x{002d}0-9a-z·àçèéíïòóúü]{1,63}$/iu', + 27 => '/^[\x{002d}0-9а-ъьюя\x{0450}\x{045D}]{1,63}$/iu', + 28 => '/^[\x{002d}0-9а-яёіў]{1,63}$/iu', + 29 => '/^[\x{002d}0-9a-ząčėęįšūųž]{1,63}$/iu', + 30 => '/^[\x{002d}0-9a-záäåæéëíðóöøúüýþ]{1,63}$/iu', + 31 => '/^[\x{002d}0-9a-zàâæçèéêëîïñôùûüÿœ]{1,63}$/iu', + 32 => '/^[\x{002d}0-9а-щъыьэюяёєіїґ]{1,63}$/iu', + 33 => '/^[\x{002d}0-9א-ת]{1,63}$/iu', + ], + 'PR' => [1 => '/^[\x{002d}0-9a-záéíóúñäëïüöâêîôûàèùæçœãõ]{1,63}$/iu'], + 'PT' => [1 => '/^[\x{002d}0-9a-záàâãçéêíóôõú]{1,63}$/iu'], + 'RS' => [1 => '/^[\x{002d}0-9a-zßáâäçéëíîóôöúüýăąćčďđęěĺľłńňőŕřśşšţťůűźżž]{1,63}$/iu'], + 'RU' => [1 => '/^[\x{002d}0-9а-яё]{1,63}$/iu'], + 'SA' => [1 => '/^[\x{002d}.0-9\x{0621}-\x{063A}\x{0641}-\x{064A}\x{0660}-\x{0669}]{1,63}$/iu'], + 'SE' => [1 => '/^[\x{002d}0-9a-zäåéöü]{1,63}$/iu'], + 'SH' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿăąāćĉčċďđĕěėęēğĝġģĥħĭĩįīıĵķĺľļłńňņŋŏőōœĸŕřŗśŝšşťţŧŭůűũųūŵŷźžż]{1,63}$/iu'], + 'SI' => [ + 1 => '/^[\x{002d}0-9a-zà-öø-ÿ]{1,63}$/iu', + 2 => '/^[\x{002d}0-9a-zāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıĵķĺļľŀłńņňʼnŋōŏőœŕŗřśŝšťŧũūŭůűųŵŷźżž]{1,63}$/iu', + 3 => '/^[\x{002d}0-9a-zșț]{1,63}$/iu', + ], + 'SJ' => [1 => '/^[\x{002d}0-9a-zàáä-éêñ-ôöøüčđńŋšŧž]{1,63}$/iu'], + 'TH' => [1 => '/^[\x{002d}0-9a-z\x{0E01}-\x{0E3A}\x{0E40}-\x{0E4D}\x{0E50}-\x{0E59}]{1,63}$/iu'], + 'TM' => [1 => '/^[\x{002d}0-9a-zà-öø-ÿāăąćĉċčďđēėęěĝġģĥħīįĵķĺļľŀłńņňŋőœŕŗřśŝşšţťŧūŭůűųŵŷźżž]{1,63}$/iu'], + 'TW' => 'Hostname/Cn.php', + 'TR' => [1 => '/^[\x{002d}0-9a-zğıüşöç]{1,63}$/iu'], + 'UA' => [1 => '/^[\x{002d}0-9a-zабвгдежзийклмнопрстуфхцчшщъыьэюяѐёђѓєѕіїјљњћќѝўџґӂʼ]{1,63}$/iu'], + 'VE' => [1 => '/^[\x{002d}0-9a-záéíóúüñ]{1,63}$/iu'], + 'VN' => [1 => '/^[ÀÁÂÃÈÉÊÌÍÒÓÔÕÙÚÝàáâãèéêìíòóôõùúýĂăĐđĨĩŨũƠơƯư\x{1EA0}-\x{1EF9}]{1,63}$/iu'], + 'мон' => [1 => '/^[\x{002d}0-9\x{0430}-\x{044F}]{1,63}$/iu'], + 'срб' => [1 => '/^[\x{002d}0-9а-ик-шђјљњћџ]{1,63}$/iu'], + 'сайт' => [1 => '/^[\x{002d}0-9а-яёіїѝйўґг]{1,63}$/iu'], + 'онлайн' => [1 => '/^[\x{002d}0-9а-яёіїѝйўґг]{1,63}$/iu'], + '中国' => 'Hostname/Cn.php', + '中國' => 'Hostname/Cn.php', + 'ලංකා' => [1 => '/^[\x{0d80}-\x{0dff}]{1,63}$/iu'], + '香港' => 'Hostname/Cn.php', + '台湾' => 'Hostname/Cn.php', + '台灣' => 'Hostname/Cn.php', + 'امارات' => [1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'], + 'الاردن' => [1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'], + 'السعودية' => [1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'], + 'ไทย' => [1 => '/^[\x{002d}0-9a-z\x{0E01}-\x{0E3A}\x{0E40}-\x{0E4D}\x{0E50}-\x{0E59}]{1,63}$/iu'], + 'рф' => [1 => '/^[\x{002d}0-9а-яё]{1,63}$/iu'], + 'تونس' => [1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'], + 'مصر' => [1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'], + 'இலங்கை' => [1 => '/^[\x{0b80}-\x{0bff}]{1,63}$/iu'], + 'فلسطين' => [1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'], + 'شبكة' => [1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'], + ]; + + /** @var array> */ + protected $idnLength = [ + 'BIZ' => [5 => 17, 11 => 15, 12 => 20], + 'CN' => [1 => 20], + 'COM' => [3 => 17, 5 => 20], + 'HK' => [1 => 15], + 'INFO' => [4 => 17], + 'KR' => [1 => 17], + 'NET' => [3 => 17, 5 => 20], + 'ORG' => [6 => 17], + 'TW' => [1 => 20], + 'امارات' => [1 => 30], + 'الاردن' => [1 => 30], + 'السعودية' => [1 => 30], + 'تونس' => [1 => 30], + 'مصر' => [1 => 30], + 'فلسطين' => [1 => 30], + 'شبكة' => [1 => 30], + '中国' => [1 => 20], + '中國' => [1 => 20], + '香港' => [1 => 20], + '台湾' => [1 => 20], + '台灣' => [1 => 20], + ]; + + /** @var null|false|string */ + protected $tld; + + /** + * Options for the hostname validator + * + * @var array + */ + protected $options = [ + 'allow' => self::ALLOW_DNS, // Allow these hostnames + 'useIdnCheck' => true, // Check IDN domains + 'useTldCheck' => true, // Check TLD elements + 'ipValidator' => null, // IP validator to use + ]; + + // phpcs:disable Squiz.Commenting.FunctionComment.ExtraParamComment + + /** + * Sets validator options. + * + * @see http://www.iana.org/cctld/specifications-policies-cctlds-01apr02.htm Technical Specifications for ccTLDs + * + * @param array $options OPTIONAL Array of validator options; see Hostname::$options + * @param int $allow OPTIONAL Set what types of hostname to allow (default ALLOW_DNS) + * @param bool $useIdnCheck OPTIONAL Set whether IDN domains are validated (default true) + * @param bool $useTldCheck Set whether the TLD element of a hostname is validated (default true) + * @param Ip $ipValidator OPTIONAL + */ + public function __construct($options = []) + { + // phpcs:enable + if (! is_array($options)) { + $options = func_get_args(); + $temp['allow'] = array_shift($options); + if (! empty($options)) { + $temp['useIdnCheck'] = array_shift($options); + } + + if (! empty($options)) { + $temp['useTldCheck'] = array_shift($options); + } + + if (! empty($options)) { + $temp['ipValidator'] = array_shift($options); + } + + $options = $temp; + } + + if (! array_key_exists('ipValidator', $options)) { + $options['ipValidator'] = null; + } + + parent::__construct($options); + } + + /** + * Returns the set ip validator + * + * @return Ip + */ + public function getIpValidator() + { + return $this->options['ipValidator']; + } + + /** + * @param Ip $ipValidator OPTIONAL + * @return self + */ + public function setIpValidator(?Ip $ipValidator = null) + { + if ($ipValidator === null) { + $ipValidator = new Ip(); + } + + $this->options['ipValidator'] = $ipValidator; + return $this; + } + + /** + * Returns the allow option + * + * @return int + */ + public function getAllow() + { + return $this->options['allow']; + } + + /** + * Sets the allow option + * + * @param int $allow + * @return $this Provides a fluent interface + */ + public function setAllow($allow) + { + $this->options['allow'] = $allow; + return $this; + } + + /** + * Returns the set idn option + * + * @return bool + */ + public function getIdnCheck() + { + return $this->options['useIdnCheck']; + } + + /** + * Set whether IDN domains are validated + * + * This only applies when DNS hostnames are validated + * + * @param bool $useIdnCheck Set to true to validate IDN domains + * @return $this + */ + public function useIdnCheck($useIdnCheck) + { + $this->options['useIdnCheck'] = (bool) $useIdnCheck; + return $this; + } + + /** + * Returns the set tld option + * + * @return bool + */ + public function getTldCheck() + { + return $this->options['useTldCheck']; + } + + /** + * Set whether the TLD element of a hostname is validated + * + * This only applies when DNS hostnames are validated + * + * @param bool $useTldCheck Set to true to validate TLD elements + * @return $this + */ + public function useTldCheck($useTldCheck) + { + $this->options['useTldCheck'] = (bool) $useTldCheck; + return $this; + } + + /** + * Defined by Interface + * + * Returns true if and only if the $value is a valid hostname with respect to the current allow option + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + // Check input against IP address schema + if ( + ((preg_match('/^[0-9.]*$/', $value) && strpos($value, '.') !== false) + || (preg_match('/^[0-9a-f:.]*$/i', $value) && strpos($value, ':') !== false)) + && $this->getIpValidator()->setTranslator($this->getTranslator())->isValid($value) + ) { + if (! ($this->getAllow() & self::ALLOW_IP)) { + $this->error(self::IP_ADDRESS_NOT_ALLOWED); + return false; + } + + return true; + } + + // Handle Regex compilation failure that may happen on .biz domain with has @ character, eg: tapi4457@hsoqvf.biz + // Technically, hostname with '@' character is invalid, so mark as invalid immediately + // @see https://github.com/laminas/laminas-validator/issues/8 + if (strpos($value, '@') !== false) { + $this->error(self::INVALID_HOSTNAME); + return false; + } + + // Local hostnames are allowed to be partial (ending '.') + if ($this->getAllow() & self::ALLOW_LOCAL) { + if (substr($value, -1) === '.') { + $value = substr($value, 0, -1); + if (substr($value, -1) === '.') { + // Empty hostnames (ending '..') are not allowed + $this->error(self::INVALID_LOCAL_NAME); + return false; + } + } + } + + $domainParts = explode('.', $value); + + // Prevent partial IP V4 addresses (ending '.') + if ( + count($domainParts) === 4 && preg_match('/^[0-9.a-e:.]*$/i', $value) + && $this->getIpValidator()->setTranslator($this->getTranslator())->isValid($value) + ) { + $this->error(self::INVALID_LOCAL_NAME); + } + + $utf8StrWrapper = StringUtils::getWrapper('UTF-8'); + + // Check input against DNS hostname schema + if ( + count($domainParts) > 1 + && $utf8StrWrapper->strlen($value) >= 4 + && $utf8StrWrapper->strlen($value) <= 254 + ) { + $status = false; + + do { + // First check TLD + $matches = []; + if ( + preg_match('/([^.]{2,63})$/u', end($domainParts), $matches) + || (array_key_exists(end($domainParts), $this->validIdns)) + ) { + reset($domainParts); + + // Hostname characters are: *(label dot)(label dot label); max 254 chars + // label: id-prefix [*ldh{61} id-prefix]; max 63 chars + // id-prefix: alpha / digit + // ldh: alpha / digit / dash + + $this->tld = $matches[1]; + // Decode Punycode TLD to IDN + if (strpos($this->tld, 'xn--') === 0) { + $this->tld = $this->decodePunycode(substr($this->tld, 4)); + if ($this->tld === false) { + return false; + } + } else { + $this->tld = strtoupper($this->tld); + } + + // Match TLD against known list + $removedTld = false; + if ($this->getTldCheck()) { + if ( + ! in_array(strtolower($this->tld), $this->validTlds) + && ! in_array($this->tld, $this->validTlds) + ) { + $this->error(self::UNKNOWN_TLD); + $status = false; + break; + } + // We have already validated that the TLD is fine. We don't want it to go through the below + // checks as new UTF-8 TLDs will incorrectly fail if there is no IDN regex for it. + array_pop($domainParts); + $removedTld = true; + } + + /** + * Match against IDN hostnames + * Note: Keep label regex short to avoid issues with long patterns when matching IDN hostnames + * + * @see Hostname\Interface + */ + $regexChars = [0 => '/^[a-z0-9\x2d]{1,63}$/i']; + if ($this->getIdnCheck() && isset($this->validIdns[$this->tld])) { + if (is_string($this->validIdns[$this->tld])) { + $regexChars += include __DIR__ . '/' . $this->validIdns[$this->tld]; + } else { + $regexChars += $this->validIdns[$this->tld]; + } + } + + // Check each hostname part + $check = 0; + $lastDomainPart = end($domainParts); + if (! $removedTld) { + $lastDomainPart = prev($domainParts); + } + foreach ($domainParts as $domainPart) { + // Decode Punycode domain names to IDN + if (strpos($domainPart, 'xn--') === 0) { + $domainPart = $this->decodePunycode(substr($domainPart, 4)); + if ($domainPart === false) { + return false; + } + } + + // Skip following checks if domain part is empty, as it definitely is not a valid hostname then + if ($domainPart === '') { + $this->error(self::INVALID_HOSTNAME); + $status = false; + break 2; + } + + // Check dash (-) does not start, end or appear in 3rd and 4th positions + if ( + $utf8StrWrapper->strpos($domainPart, '-') === 0 + || ($utf8StrWrapper->strlen($domainPart) > 2 + && $utf8StrWrapper->strpos($domainPart, '-', 2) === 2 + && $utf8StrWrapper->strpos($domainPart, '-', 3) === 3 + ) + || $utf8StrWrapper->substr($domainPart, -1) === '-' + ) { + $this->error(self::INVALID_DASH); + $status = false; + break 2; + } + + // Check each domain part + $checked = false; + $isSubDomain = $domainPart !== $lastDomainPart; + $partRegexChars = $isSubDomain ? ['/^[a-z0-9_\x2d]{1,63}$/i'] + $regexChars : $regexChars; + foreach ($partRegexChars as $regexKey => $regexChar) { + $status = preg_match($regexChar, $domainPart); + if ($status > 0) { + $length = 63; + if ( + array_key_exists($this->tld, $this->idnLength) + && array_key_exists($regexKey, $this->idnLength[$this->tld]) + ) { + $length = $this->idnLength[$this->tld]; + } + + if ($utf8StrWrapper->strlen($domainPart) > $length) { + $this->error(self::INVALID_HOSTNAME); + $status = false; + } else { + $checked = true; + break; + } + } + } + + if ($checked) { + ++$check; + } + } + + // If one of the labels doesn't match, the hostname is invalid + if ($check !== count($domainParts)) { + $this->error(self::INVALID_HOSTNAME_SCHEMA); + $status = false; + } + } else { + // Hostname not long enough + $this->error(self::UNDECIPHERABLE_TLD); + $status = false; + } + } while (false); + + // If the input passes as an Internet domain name, and domain names are allowed, then the hostname + // passes validation + if ($status && ($this->getAllow() & self::ALLOW_DNS)) { + return true; + } + } elseif ($this->getAllow() & self::ALLOW_DNS) { + $this->error(self::INVALID_HOSTNAME); + } + + // Check for URI Syntax (RFC3986) + if ($this->getAllow() & self::ALLOW_URI) { + if (preg_match("/^([a-zA-Z0-9-._~!$&\'()*+,;=]|%[[:xdigit:]]{2}){1,254}$/i", $value)) { + return true; + } + + $this->error(self::INVALID_URI); + } + + // Check input against local network name schema; last chance to pass validation + $regexLocal = '/^(([a-zA-Z0-9\x2d]{1,63}\x2e)*[a-zA-Z0-9\x2d]{1,63}[\x2e]{0,1}){1,254}$/'; + $status = preg_match($regexLocal, $value); + + // If the input passes as a local network name, and local network names are allowed, then the + // hostname passes validation + $allowLocal = $this->getAllow() & self::ALLOW_LOCAL; + if ($status && $allowLocal) { + return true; + } + + // If the input does not pass as a local network name, add a message + if (! $status) { + $this->error(self::INVALID_LOCAL_NAME); + } + + // If local network names are not allowed, add a message + if ($status && ! $allowLocal) { + $this->error(self::LOCAL_NAME_NOT_ALLOWED); + } + + return false; + } + + /** + * Decodes a punycode encoded string to it's original utf8 string + * Returns false in case of a decoding failure. + * + * @param string $encoded Punycode encoded string to decode + * @return string|false + */ + protected function decodePunycode($encoded) + { + if (! preg_match('/^[a-z0-9-]+$/i', $encoded)) { + // no punycode encoded string + $this->error(self::CANNOT_DECODE_PUNYCODE); + return false; + } + + $decoded = []; + $separator = strrpos($encoded, '-'); + if ($separator > 0) { + for ($x = 0; $x < $separator; ++$x) { + // prepare decoding matrix + $decoded[] = ord($encoded[$x]); + } + } + + $lengthd = count($decoded); + $lengthe = strlen($encoded); + + // decoding + $init = true; + $base = 72; + $index = 0; + $char = 0x80; + + for ($indexe = $separator ? $separator + 1 : 0; $indexe < $lengthe; ++$lengthd) { + for ($oldIndex = $index, $pos = 1, $key = 36; 1; $key += 36) { + $hex = ord($encoded[$indexe++]); + $digit = $hex - 48 < 10 ? $hex - 22 + : ($hex - 65 < 26 ? $hex - 65 + : ($hex - 97 < 26 ? $hex - 97 + : 36)); + + $index += $digit * $pos; + $tag = $key <= $base ? 1 : ($key >= $base + 26 ? 26 : $key - $base); + if ($digit < $tag) { + break; + } + + $pos = (int) ($pos * (36 - $tag)); + } + + $delta = intval($init ? ($index - $oldIndex) / 700 : ($index - $oldIndex) / 2); + $delta += intval($delta / ($lengthd + 1)); + for ($key = 0; $delta > 910 / 2; $key += 36) { + $delta = intval($delta / 35); + } + + $base = intval($key + 36 * $delta / ($delta + 38)); + $init = false; + $char += (int) ($index / ($lengthd + 1)); + $index %= $lengthd + 1; + if ($lengthd > 0) { + for ($i = $lengthd; $i > $index; $i--) { + $decoded[$i] = $decoded[$i - 1]; + } + } + + $decoded[$index++] = $char; + } + + // convert decoded ucs4 to utf8 string + foreach ($decoded as $key => $value) { + if ($value < 128) { + $decoded[$key] = chr($value); + } elseif ($value < 1 << 11) { + $decoded[$key] = chr(192 + ($value >> 6)); + $decoded[$key] .= chr(128 + ($value & 63)); + } elseif ($value < 1 << 16) { + $decoded[$key] = chr(224 + ($value >> 12)); + $decoded[$key] .= chr(128 + (($value >> 6) & 63)); + $decoded[$key] .= chr(128 + ($value & 63)); + } elseif ($value < 1 << 21) { + $decoded[$key] = chr(240 + ($value >> 18)); + $decoded[$key] .= chr(128 + (($value >> 12) & 63)); + $decoded[$key] .= chr(128 + (($value >> 6) & 63)); + $decoded[$key] .= chr(128 + ($value & 63)); + } else { + $this->error(self::CANNOT_DECODE_PUNYCODE); + return false; + } + } + + return implode($decoded); + } +} diff --git a/lib/laminas/laminas-validator/src/Hostname/Biz.php b/lib/laminas/laminas-validator/src/Hostname/Biz.php new file mode 100644 index 0000000000..f852cf1c29 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Hostname/Biz.php @@ -0,0 +1,2897 @@ + '/^[\x{002d}0-9a-zäåæéöøü]{1,63}$/iu', + 2 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu', + 3 => '/^[\x{002d}0-9a-záéíóöúüőű]{1,63}$/iu', + 4 => '/^[\x{002d}0-9a-záæéíðóöúýþ]{1,63}$/iu', + 5 => '/^[\x{AC00}-\x{D7A3}]{1,17}$/iu', + 6 => '/^[\x{002d}0-9a-ząčėęįšūųž]{1,63}$/iu', + 7 => '/^[\x{002d}0-9a-zāčēģīķļņōŗšūž]{1,63}$/iu', + 8 => '/^[\x{002d}0-9a-zàáä-éêñ-ôöøüčđńŋšŧž]{1,63}$/iu', + 9 => '/^[\x{002d}0-9a-zóąćęłńśźż]{1,63}$/iu', + 10 => '/^[\x{002d}0-9a-záàâãçéêíóôõú]{1,63}$/iu', + 11 => '/^[\x{002d}0-9a-z\x{3005}-\x{3007}\x{3041}-\x{3093}\x{309D}\x{309E}\x{30A1}-\x{30F6}\x{30FC}' + . '\x{30FD}\x{30FE}\x{4E00}\x{4E01}\x{4E03}\x{4E07}\x{4E08}\x{4E09}\x{4E0A}' + . '\x{4E0B}\x{4E0D}\x{4E0E}\x{4E10}\x{4E11}\x{4E14}\x{4E15}\x{4E16}\x{4E17}' + . '\x{4E18}\x{4E19}\x{4E1E}\x{4E21}\x{4E26}\x{4E2A}\x{4E2D}\x{4E31}\x{4E32}' + . '\x{4E36}\x{4E38}\x{4E39}\x{4E3B}\x{4E3C}\x{4E3F}\x{4E42}\x{4E43}\x{4E45}' + . '\x{4E4B}\x{4E4D}\x{4E4E}\x{4E4F}\x{4E55}\x{4E56}\x{4E57}\x{4E58}\x{4E59}' + . '\x{4E5D}\x{4E5E}\x{4E5F}\x{4E62}\x{4E71}\x{4E73}\x{4E7E}\x{4E80}\x{4E82}' + . '\x{4E85}\x{4E86}\x{4E88}\x{4E89}\x{4E8A}\x{4E8B}\x{4E8C}\x{4E8E}\x{4E91}' + . '\x{4E92}\x{4E94}\x{4E95}\x{4E98}\x{4E99}\x{4E9B}\x{4E9C}\x{4E9E}\x{4E9F}' + . '\x{4EA0}\x{4EA1}\x{4EA2}\x{4EA4}\x{4EA5}\x{4EA6}\x{4EA8}\x{4EAB}\x{4EAC}' + . '\x{4EAD}\x{4EAE}\x{4EB0}\x{4EB3}\x{4EB6}\x{4EBA}\x{4EC0}\x{4EC1}\x{4EC2}' + . '\x{4EC4}\x{4EC6}\x{4EC7}\x{4ECA}\x{4ECB}\x{4ECD}\x{4ECE}\x{4ECF}\x{4ED4}' + . '\x{4ED5}\x{4ED6}\x{4ED7}\x{4ED8}\x{4ED9}\x{4EDD}\x{4EDE}\x{4EDF}\x{4EE3}' + . '\x{4EE4}\x{4EE5}\x{4EED}\x{4EEE}\x{4EF0}\x{4EF2}\x{4EF6}\x{4EF7}\x{4EFB}' + . '\x{4F01}\x{4F09}\x{4F0A}\x{4F0D}\x{4F0E}\x{4F0F}\x{4F10}\x{4F11}\x{4F1A}' + . '\x{4F1C}\x{4F1D}\x{4F2F}\x{4F30}\x{4F34}\x{4F36}\x{4F38}\x{4F3A}\x{4F3C}' + . '\x{4F3D}\x{4F43}\x{4F46}\x{4F47}\x{4F4D}\x{4F4E}\x{4F4F}\x{4F50}\x{4F51}' + . '\x{4F53}\x{4F55}\x{4F57}\x{4F59}\x{4F5A}\x{4F5B}\x{4F5C}\x{4F5D}\x{4F5E}' + . '\x{4F69}\x{4F6F}\x{4F70}\x{4F73}\x{4F75}\x{4F76}\x{4F7B}\x{4F7C}\x{4F7F}' + . '\x{4F83}\x{4F86}\x{4F88}\x{4F8B}\x{4F8D}\x{4F8F}\x{4F91}\x{4F96}\x{4F98}' + . '\x{4F9B}\x{4F9D}\x{4FA0}\x{4FA1}\x{4FAB}\x{4FAD}\x{4FAE}\x{4FAF}\x{4FB5}' + . '\x{4FB6}\x{4FBF}\x{4FC2}\x{4FC3}\x{4FC4}\x{4FCA}\x{4FCE}\x{4FD0}\x{4FD1}' + . '\x{4FD4}\x{4FD7}\x{4FD8}\x{4FDA}\x{4FDB}\x{4FDD}\x{4FDF}\x{4FE1}\x{4FE3}' + . '\x{4FE4}\x{4FE5}\x{4FEE}\x{4FEF}\x{4FF3}\x{4FF5}\x{4FF6}\x{4FF8}\x{4FFA}' + . '\x{4FFE}\x{5005}\x{5006}\x{5009}\x{500B}\x{500D}\x{500F}\x{5011}\x{5012}' + . '\x{5014}\x{5016}\x{5019}\x{501A}\x{501F}\x{5021}\x{5023}\x{5024}\x{5025}' + . '\x{5026}\x{5028}\x{5029}\x{502A}\x{502B}\x{502C}\x{502D}\x{5036}\x{5039}' + . '\x{5043}\x{5047}\x{5048}\x{5049}\x{504F}\x{5050}\x{5055}\x{5056}\x{505A}' + . '\x{505C}\x{5065}\x{506C}\x{5072}\x{5074}\x{5075}\x{5076}\x{5078}\x{507D}' + . '\x{5080}\x{5085}\x{508D}\x{5091}\x{5098}\x{5099}\x{509A}\x{50AC}\x{50AD}' + . '\x{50B2}\x{50B3}\x{50B4}\x{50B5}\x{50B7}\x{50BE}\x{50C2}\x{50C5}\x{50C9}' + . '\x{50CA}\x{50CD}\x{50CF}\x{50D1}\x{50D5}\x{50D6}\x{50DA}\x{50DE}\x{50E3}' + . '\x{50E5}\x{50E7}\x{50ED}\x{50EE}\x{50F5}\x{50F9}\x{50FB}\x{5100}\x{5101}' + . '\x{5102}\x{5104}\x{5109}\x{5112}\x{5114}\x{5115}\x{5116}\x{5118}\x{511A}' + . '\x{511F}\x{5121}\x{512A}\x{5132}\x{5137}\x{513A}\x{513B}\x{513C}\x{513F}' + . '\x{5140}\x{5141}\x{5143}\x{5144}\x{5145}\x{5146}\x{5147}\x{5148}\x{5149}' + . '\x{514B}\x{514C}\x{514D}\x{514E}\x{5150}\x{5152}\x{5154}\x{515A}\x{515C}' + . '\x{5162}\x{5165}\x{5168}\x{5169}\x{516A}\x{516B}\x{516C}\x{516D}\x{516E}' + . '\x{5171}\x{5175}\x{5176}\x{5177}\x{5178}\x{517C}\x{5180}\x{5182}\x{5185}' + . '\x{5186}\x{5189}\x{518A}\x{518C}\x{518D}\x{518F}\x{5190}\x{5191}\x{5192}' + . '\x{5193}\x{5195}\x{5196}\x{5197}\x{5199}\x{51A0}\x{51A2}\x{51A4}\x{51A5}' + . '\x{51A6}\x{51A8}\x{51A9}\x{51AA}\x{51AB}\x{51AC}\x{51B0}\x{51B1}\x{51B2}' + . '\x{51B3}\x{51B4}\x{51B5}\x{51B6}\x{51B7}\x{51BD}\x{51C4}\x{51C5}\x{51C6}' + . '\x{51C9}\x{51CB}\x{51CC}\x{51CD}\x{51D6}\x{51DB}\x{51DC}\x{51DD}\x{51E0}' + . '\x{51E1}\x{51E6}\x{51E7}\x{51E9}\x{51EA}\x{51ED}\x{51F0}\x{51F1}\x{51F5}' + . '\x{51F6}\x{51F8}\x{51F9}\x{51FA}\x{51FD}\x{51FE}\x{5200}\x{5203}\x{5204}' + . '\x{5206}\x{5207}\x{5208}\x{520A}\x{520B}\x{520E}\x{5211}\x{5214}\x{5217}' + . '\x{521D}\x{5224}\x{5225}\x{5227}\x{5229}\x{522A}\x{522E}\x{5230}\x{5233}' + . '\x{5236}\x{5237}\x{5238}\x{5239}\x{523A}\x{523B}\x{5243}\x{5244}\x{5247}' + . '\x{524A}\x{524B}\x{524C}\x{524D}\x{524F}\x{5254}\x{5256}\x{525B}\x{525E}' + . '\x{5263}\x{5264}\x{5265}\x{5269}\x{526A}\x{526F}\x{5270}\x{5271}\x{5272}' + . '\x{5273}\x{5274}\x{5275}\x{527D}\x{527F}\x{5283}\x{5287}\x{5288}\x{5289}' + . '\x{528D}\x{5291}\x{5292}\x{5294}\x{529B}\x{529F}\x{52A0}\x{52A3}\x{52A9}' + . '\x{52AA}\x{52AB}\x{52AC}\x{52AD}\x{52B1}\x{52B4}\x{52B5}\x{52B9}\x{52BC}' + . '\x{52BE}\x{52C1}\x{52C3}\x{52C5}\x{52C7}\x{52C9}\x{52CD}\x{52D2}\x{52D5}' + . '\x{52D7}\x{52D8}\x{52D9}\x{52DD}\x{52DE}\x{52DF}\x{52E0}\x{52E2}\x{52E3}' + . '\x{52E4}\x{52E6}\x{52E7}\x{52F2}\x{52F3}\x{52F5}\x{52F8}\x{52F9}\x{52FA}' + . '\x{52FE}\x{52FF}\x{5301}\x{5302}\x{5305}\x{5306}\x{5308}\x{530D}\x{530F}' + . '\x{5310}\x{5315}\x{5316}\x{5317}\x{5319}\x{531A}\x{531D}\x{5320}\x{5321}' + . '\x{5323}\x{532A}\x{532F}\x{5331}\x{5333}\x{5338}\x{5339}\x{533A}\x{533B}' + . '\x{533F}\x{5340}\x{5341}\x{5343}\x{5345}\x{5346}\x{5347}\x{5348}\x{5349}' + . '\x{534A}\x{534D}\x{5351}\x{5352}\x{5353}\x{5354}\x{5357}\x{5358}\x{535A}' + . '\x{535C}\x{535E}\x{5360}\x{5366}\x{5369}\x{536E}\x{536F}\x{5370}\x{5371}' + . '\x{5373}\x{5374}\x{5375}\x{5377}\x{5378}\x{537B}\x{537F}\x{5382}\x{5384}' + . '\x{5396}\x{5398}\x{539A}\x{539F}\x{53A0}\x{53A5}\x{53A6}\x{53A8}\x{53A9}' + . '\x{53AD}\x{53AE}\x{53B0}\x{53B3}\x{53B6}\x{53BB}\x{53C2}\x{53C3}\x{53C8}' + . '\x{53C9}\x{53CA}\x{53CB}\x{53CC}\x{53CD}\x{53CE}\x{53D4}\x{53D6}\x{53D7}' + . '\x{53D9}\x{53DB}\x{53DF}\x{53E1}\x{53E2}\x{53E3}\x{53E4}\x{53E5}\x{53E8}' + . '\x{53E9}\x{53EA}\x{53EB}\x{53EC}\x{53ED}\x{53EE}\x{53EF}\x{53F0}\x{53F1}' + . '\x{53F2}\x{53F3}\x{53F6}\x{53F7}\x{53F8}\x{53FA}\x{5401}\x{5403}\x{5404}' + . '\x{5408}\x{5409}\x{540A}\x{540B}\x{540C}\x{540D}\x{540E}\x{540F}\x{5410}' + . '\x{5411}\x{541B}\x{541D}\x{541F}\x{5420}\x{5426}\x{5429}\x{542B}\x{542C}' + . '\x{542D}\x{542E}\x{5436}\x{5438}\x{5439}\x{543B}\x{543C}\x{543D}\x{543E}' + . '\x{5440}\x{5442}\x{5446}\x{5448}\x{5449}\x{544A}\x{544E}\x{5451}\x{545F}' + . '\x{5468}\x{546A}\x{5470}\x{5471}\x{5473}\x{5475}\x{5476}\x{5477}\x{547B}' + . '\x{547C}\x{547D}\x{5480}\x{5484}\x{5486}\x{548B}\x{548C}\x{548E}\x{548F}' + . '\x{5490}\x{5492}\x{54A2}\x{54A4}\x{54A5}\x{54A8}\x{54AB}\x{54AC}\x{54AF}' + . '\x{54B2}\x{54B3}\x{54B8}\x{54BC}\x{54BD}\x{54BE}\x{54C0}\x{54C1}\x{54C2}' + . '\x{54C4}\x{54C7}\x{54C8}\x{54C9}\x{54D8}\x{54E1}\x{54E2}\x{54E5}\x{54E6}' + . '\x{54E8}\x{54E9}\x{54ED}\x{54EE}\x{54F2}\x{54FA}\x{54FD}\x{5504}\x{5506}' + . '\x{5507}\x{550F}\x{5510}\x{5514}\x{5516}\x{552E}\x{552F}\x{5531}\x{5533}' + . '\x{5538}\x{5539}\x{553E}\x{5540}\x{5544}\x{5545}\x{5546}\x{554C}\x{554F}' + . '\x{5553}\x{5556}\x{5557}\x{555C}\x{555D}\x{5563}\x{557B}\x{557C}\x{557E}' + . '\x{5580}\x{5583}\x{5584}\x{5587}\x{5589}\x{558A}\x{558B}\x{5598}\x{5599}' + . '\x{559A}\x{559C}\x{559D}\x{559E}\x{559F}\x{55A7}\x{55A8}\x{55A9}\x{55AA}' + . '\x{55AB}\x{55AC}\x{55AE}\x{55B0}\x{55B6}\x{55C4}\x{55C5}\x{55C7}\x{55D4}' + . '\x{55DA}\x{55DC}\x{55DF}\x{55E3}\x{55E4}\x{55F7}\x{55F9}\x{55FD}\x{55FE}' + . '\x{5606}\x{5609}\x{5614}\x{5616}\x{5617}\x{5618}\x{561B}\x{5629}\x{562F}' + . '\x{5631}\x{5632}\x{5634}\x{5636}\x{5638}\x{5642}\x{564C}\x{564E}\x{5650}' + . '\x{565B}\x{5664}\x{5668}\x{566A}\x{566B}\x{566C}\x{5674}\x{5678}\x{567A}' + . '\x{5680}\x{5686}\x{5687}\x{568A}\x{568F}\x{5694}\x{56A0}\x{56A2}\x{56A5}' + . '\x{56AE}\x{56B4}\x{56B6}\x{56BC}\x{56C0}\x{56C1}\x{56C2}\x{56C3}\x{56C8}' + . '\x{56CE}\x{56D1}\x{56D3}\x{56D7}\x{56D8}\x{56DA}\x{56DB}\x{56DE}\x{56E0}' + . '\x{56E3}\x{56EE}\x{56F0}\x{56F2}\x{56F3}\x{56F9}\x{56FA}\x{56FD}\x{56FF}' + . '\x{5700}\x{5703}\x{5704}\x{5708}\x{5709}\x{570B}\x{570D}\x{570F}\x{5712}' + . '\x{5713}\x{5716}\x{5718}\x{571C}\x{571F}\x{5726}\x{5727}\x{5728}\x{572D}' + . '\x{5730}\x{5737}\x{5738}\x{573B}\x{5740}\x{5742}\x{5747}\x{574A}\x{574E}' + . '\x{574F}\x{5750}\x{5751}\x{5761}\x{5764}\x{5766}\x{5769}\x{576A}\x{577F}' + . '\x{5782}\x{5788}\x{5789}\x{578B}\x{5793}\x{57A0}\x{57A2}\x{57A3}\x{57A4}' + . '\x{57AA}\x{57B0}\x{57B3}\x{57C0}\x{57C3}\x{57C6}\x{57CB}\x{57CE}\x{57D2}' + . '\x{57D3}\x{57D4}\x{57D6}\x{57DC}\x{57DF}\x{57E0}\x{57E3}\x{57F4}\x{57F7}' + . '\x{57F9}\x{57FA}\x{57FC}\x{5800}\x{5802}\x{5805}\x{5806}\x{580A}\x{580B}' + . '\x{5815}\x{5819}\x{581D}\x{5821}\x{5824}\x{582A}\x{582F}\x{5830}\x{5831}' + . '\x{5834}\x{5835}\x{583A}\x{583D}\x{5840}\x{5841}\x{584A}\x{584B}\x{5851}' + . '\x{5852}\x{5854}\x{5857}\x{5858}\x{5859}\x{585A}\x{585E}\x{5862}\x{5869}' + . '\x{586B}\x{5870}\x{5872}\x{5875}\x{5879}\x{587E}\x{5883}\x{5885}\x{5893}' + . '\x{5897}\x{589C}\x{589F}\x{58A8}\x{58AB}\x{58AE}\x{58B3}\x{58B8}\x{58B9}' + . '\x{58BA}\x{58BB}\x{58BE}\x{58C1}\x{58C5}\x{58C7}\x{58CA}\x{58CC}\x{58D1}' + . '\x{58D3}\x{58D5}\x{58D7}\x{58D8}\x{58D9}\x{58DC}\x{58DE}\x{58DF}\x{58E4}' + . '\x{58E5}\x{58EB}\x{58EC}\x{58EE}\x{58EF}\x{58F0}\x{58F1}\x{58F2}\x{58F7}' + . '\x{58F9}\x{58FA}\x{58FB}\x{58FC}\x{58FD}\x{5902}\x{5909}\x{590A}\x{590F}' + . '\x{5910}\x{5915}\x{5916}\x{5918}\x{5919}\x{591A}\x{591B}\x{591C}\x{5922}' + . '\x{5925}\x{5927}\x{5929}\x{592A}\x{592B}\x{592C}\x{592D}\x{592E}\x{5931}' + . '\x{5932}\x{5937}\x{5938}\x{593E}\x{5944}\x{5947}\x{5948}\x{5949}\x{594E}' + . '\x{594F}\x{5950}\x{5951}\x{5954}\x{5955}\x{5957}\x{5958}\x{595A}\x{5960}' + . '\x{5962}\x{5965}\x{5967}\x{5968}\x{5969}\x{596A}\x{596C}\x{596E}\x{5973}' + . '\x{5974}\x{5978}\x{597D}\x{5981}\x{5982}\x{5983}\x{5984}\x{598A}\x{598D}' + . '\x{5993}\x{5996}\x{5999}\x{599B}\x{599D}\x{59A3}\x{59A5}\x{59A8}\x{59AC}' + . '\x{59B2}\x{59B9}\x{59BB}\x{59BE}\x{59C6}\x{59C9}\x{59CB}\x{59D0}\x{59D1}' + . '\x{59D3}\x{59D4}\x{59D9}\x{59DA}\x{59DC}\x{59E5}\x{59E6}\x{59E8}\x{59EA}' + . '\x{59EB}\x{59F6}\x{59FB}\x{59FF}\x{5A01}\x{5A03}\x{5A09}\x{5A11}\x{5A18}' + . '\x{5A1A}\x{5A1C}\x{5A1F}\x{5A20}\x{5A25}\x{5A29}\x{5A2F}\x{5A35}\x{5A36}' + . '\x{5A3C}\x{5A40}\x{5A41}\x{5A46}\x{5A49}\x{5A5A}\x{5A62}\x{5A66}\x{5A6A}' + . '\x{5A6C}\x{5A7F}\x{5A92}\x{5A9A}\x{5A9B}\x{5ABC}\x{5ABD}\x{5ABE}\x{5AC1}' + . '\x{5AC2}\x{5AC9}\x{5ACB}\x{5ACC}\x{5AD0}\x{5AD6}\x{5AD7}\x{5AE1}\x{5AE3}' + . '\x{5AE6}\x{5AE9}\x{5AFA}\x{5AFB}\x{5B09}\x{5B0B}\x{5B0C}\x{5B16}\x{5B22}' + . '\x{5B2A}\x{5B2C}\x{5B30}\x{5B32}\x{5B36}\x{5B3E}\x{5B40}\x{5B43}\x{5B45}' + . '\x{5B50}\x{5B51}\x{5B54}\x{5B55}\x{5B57}\x{5B58}\x{5B5A}\x{5B5B}\x{5B5C}' + . '\x{5B5D}\x{5B5F}\x{5B63}\x{5B64}\x{5B65}\x{5B66}\x{5B69}\x{5B6B}\x{5B70}' + . '\x{5B71}\x{5B73}\x{5B75}\x{5B78}\x{5B7A}\x{5B80}\x{5B83}\x{5B85}\x{5B87}' + . '\x{5B88}\x{5B89}\x{5B8B}\x{5B8C}\x{5B8D}\x{5B8F}\x{5B95}\x{5B97}\x{5B98}' + . '\x{5B99}\x{5B9A}\x{5B9B}\x{5B9C}\x{5B9D}\x{5B9F}\x{5BA2}\x{5BA3}\x{5BA4}' + . '\x{5BA5}\x{5BA6}\x{5BAE}\x{5BB0}\x{5BB3}\x{5BB4}\x{5BB5}\x{5BB6}\x{5BB8}' + . '\x{5BB9}\x{5BBF}\x{5BC2}\x{5BC3}\x{5BC4}\x{5BC5}\x{5BC6}\x{5BC7}\x{5BC9}' + . '\x{5BCC}\x{5BD0}\x{5BD2}\x{5BD3}\x{5BD4}\x{5BDB}\x{5BDD}\x{5BDE}\x{5BDF}' + . '\x{5BE1}\x{5BE2}\x{5BE4}\x{5BE5}\x{5BE6}\x{5BE7}\x{5BE8}\x{5BE9}\x{5BEB}' + . '\x{5BEE}\x{5BF0}\x{5BF3}\x{5BF5}\x{5BF6}\x{5BF8}\x{5BFA}\x{5BFE}\x{5BFF}' + . '\x{5C01}\x{5C02}\x{5C04}\x{5C05}\x{5C06}\x{5C07}\x{5C08}\x{5C09}\x{5C0A}' + . '\x{5C0B}\x{5C0D}\x{5C0E}\x{5C0F}\x{5C11}\x{5C13}\x{5C16}\x{5C1A}\x{5C20}' + . '\x{5C22}\x{5C24}\x{5C28}\x{5C2D}\x{5C31}\x{5C38}\x{5C39}\x{5C3A}\x{5C3B}' + . '\x{5C3C}\x{5C3D}\x{5C3E}\x{5C3F}\x{5C40}\x{5C41}\x{5C45}\x{5C46}\x{5C48}' + . '\x{5C4A}\x{5C4B}\x{5C4D}\x{5C4E}\x{5C4F}\x{5C50}\x{5C51}\x{5C53}\x{5C55}' + . '\x{5C5E}\x{5C60}\x{5C61}\x{5C64}\x{5C65}\x{5C6C}\x{5C6E}\x{5C6F}\x{5C71}' + . '\x{5C76}\x{5C79}\x{5C8C}\x{5C90}\x{5C91}\x{5C94}\x{5CA1}\x{5CA8}\x{5CA9}' + . '\x{5CAB}\x{5CAC}\x{5CB1}\x{5CB3}\x{5CB6}\x{5CB7}\x{5CB8}\x{5CBB}\x{5CBC}' + . '\x{5CBE}\x{5CC5}\x{5CC7}\x{5CD9}\x{5CE0}\x{5CE1}\x{5CE8}\x{5CE9}\x{5CEA}' + . '\x{5CED}\x{5CEF}\x{5CF0}\x{5CF6}\x{5CFA}\x{5CFB}\x{5CFD}\x{5D07}\x{5D0B}' + . '\x{5D0E}\x{5D11}\x{5D14}\x{5D15}\x{5D16}\x{5D17}\x{5D18}\x{5D19}\x{5D1A}' + . '\x{5D1B}\x{5D1F}\x{5D22}\x{5D29}\x{5D4B}\x{5D4C}\x{5D4E}\x{5D50}\x{5D52}' + . '\x{5D5C}\x{5D69}\x{5D6C}\x{5D6F}\x{5D73}\x{5D76}\x{5D82}\x{5D84}\x{5D87}' + . '\x{5D8B}\x{5D8C}\x{5D90}\x{5D9D}\x{5DA2}\x{5DAC}\x{5DAE}\x{5DB7}\x{5DBA}' + . '\x{5DBC}\x{5DBD}\x{5DC9}\x{5DCC}\x{5DCD}\x{5DD2}\x{5DD3}\x{5DD6}\x{5DDB}' + . '\x{5DDD}\x{5DDE}\x{5DE1}\x{5DE3}\x{5DE5}\x{5DE6}\x{5DE7}\x{5DE8}\x{5DEB}' + . '\x{5DEE}\x{5DF1}\x{5DF2}\x{5DF3}\x{5DF4}\x{5DF5}\x{5DF7}\x{5DFB}\x{5DFD}' + . '\x{5DFE}\x{5E02}\x{5E03}\x{5E06}\x{5E0B}\x{5E0C}\x{5E11}\x{5E16}\x{5E19}' + . '\x{5E1A}\x{5E1B}\x{5E1D}\x{5E25}\x{5E2B}\x{5E2D}\x{5E2F}\x{5E30}\x{5E33}' + . '\x{5E36}\x{5E37}\x{5E38}\x{5E3D}\x{5E40}\x{5E43}\x{5E44}\x{5E45}\x{5E47}' + . '\x{5E4C}\x{5E4E}\x{5E54}\x{5E55}\x{5E57}\x{5E5F}\x{5E61}\x{5E62}\x{5E63}' + . '\x{5E64}\x{5E72}\x{5E73}\x{5E74}\x{5E75}\x{5E76}\x{5E78}\x{5E79}\x{5E7A}' + . '\x{5E7B}\x{5E7C}\x{5E7D}\x{5E7E}\x{5E7F}\x{5E81}\x{5E83}\x{5E84}\x{5E87}' + . '\x{5E8A}\x{5E8F}\x{5E95}\x{5E96}\x{5E97}\x{5E9A}\x{5E9C}\x{5EA0}\x{5EA6}' + . '\x{5EA7}\x{5EAB}\x{5EAD}\x{5EB5}\x{5EB6}\x{5EB7}\x{5EB8}\x{5EC1}\x{5EC2}' + . '\x{5EC3}\x{5EC8}\x{5EC9}\x{5ECA}\x{5ECF}\x{5ED0}\x{5ED3}\x{5ED6}\x{5EDA}' + . '\x{5EDB}\x{5EDD}\x{5EDF}\x{5EE0}\x{5EE1}\x{5EE2}\x{5EE3}\x{5EE8}\x{5EE9}' + . '\x{5EEC}\x{5EF0}\x{5EF1}\x{5EF3}\x{5EF4}\x{5EF6}\x{5EF7}\x{5EF8}\x{5EFA}' + . '\x{5EFB}\x{5EFC}\x{5EFE}\x{5EFF}\x{5F01}\x{5F03}\x{5F04}\x{5F09}\x{5F0A}' + . '\x{5F0B}\x{5F0C}\x{5F0D}\x{5F0F}\x{5F10}\x{5F11}\x{5F13}\x{5F14}\x{5F15}' + . '\x{5F16}\x{5F17}\x{5F18}\x{5F1B}\x{5F1F}\x{5F25}\x{5F26}\x{5F27}\x{5F29}' + . '\x{5F2D}\x{5F2F}\x{5F31}\x{5F35}\x{5F37}\x{5F38}\x{5F3C}\x{5F3E}\x{5F41}' + . '\x{5F48}\x{5F4A}\x{5F4C}\x{5F4E}\x{5F51}\x{5F53}\x{5F56}\x{5F57}\x{5F59}' + . '\x{5F5C}\x{5F5D}\x{5F61}\x{5F62}\x{5F66}\x{5F69}\x{5F6A}\x{5F6B}\x{5F6C}' + . '\x{5F6D}\x{5F70}\x{5F71}\x{5F73}\x{5F77}\x{5F79}\x{5F7C}\x{5F7F}\x{5F80}' + . '\x{5F81}\x{5F82}\x{5F83}\x{5F84}\x{5F85}\x{5F87}\x{5F88}\x{5F8A}\x{5F8B}' + . '\x{5F8C}\x{5F90}\x{5F91}\x{5F92}\x{5F93}\x{5F97}\x{5F98}\x{5F99}\x{5F9E}' + . '\x{5FA0}\x{5FA1}\x{5FA8}\x{5FA9}\x{5FAA}\x{5FAD}\x{5FAE}\x{5FB3}\x{5FB4}' + . '\x{5FB9}\x{5FBC}\x{5FBD}\x{5FC3}\x{5FC5}\x{5FCC}\x{5FCD}\x{5FD6}\x{5FD7}' + . '\x{5FD8}\x{5FD9}\x{5FDC}\x{5FDD}\x{5FE0}\x{5FE4}\x{5FEB}\x{5FF0}\x{5FF1}' + . '\x{5FF5}\x{5FF8}\x{5FFB}\x{5FFD}\x{5FFF}\x{600E}\x{600F}\x{6010}\x{6012}' + . '\x{6015}\x{6016}\x{6019}\x{601B}\x{601C}\x{601D}\x{6020}\x{6021}\x{6025}' + . '\x{6026}\x{6027}\x{6028}\x{6029}\x{602A}\x{602B}\x{602F}\x{6031}\x{603A}' + . '\x{6041}\x{6042}\x{6043}\x{6046}\x{604A}\x{604B}\x{604D}\x{6050}\x{6052}' + . '\x{6055}\x{6059}\x{605A}\x{605F}\x{6060}\x{6062}\x{6063}\x{6064}\x{6065}' + . '\x{6068}\x{6069}\x{606A}\x{606B}\x{606C}\x{606D}\x{606F}\x{6070}\x{6075}' + . '\x{6077}\x{6081}\x{6083}\x{6084}\x{6089}\x{608B}\x{608C}\x{608D}\x{6092}' + . '\x{6094}\x{6096}\x{6097}\x{609A}\x{609B}\x{609F}\x{60A0}\x{60A3}\x{60A6}' + . '\x{60A7}\x{60A9}\x{60AA}\x{60B2}\x{60B3}\x{60B4}\x{60B5}\x{60B6}\x{60B8}' + . '\x{60BC}\x{60BD}\x{60C5}\x{60C6}\x{60C7}\x{60D1}\x{60D3}\x{60D8}\x{60DA}' + . '\x{60DC}\x{60DF}\x{60E0}\x{60E1}\x{60E3}\x{60E7}\x{60E8}\x{60F0}\x{60F1}' + . '\x{60F3}\x{60F4}\x{60F6}\x{60F7}\x{60F9}\x{60FA}\x{60FB}\x{6100}\x{6101}' + . '\x{6103}\x{6106}\x{6108}\x{6109}\x{610D}\x{610E}\x{610F}\x{6115}\x{611A}' + . '\x{611B}\x{611F}\x{6121}\x{6127}\x{6128}\x{612C}\x{6134}\x{613C}\x{613D}' + . '\x{613E}\x{613F}\x{6142}\x{6144}\x{6147}\x{6148}\x{614A}\x{614B}\x{614C}' + . '\x{614D}\x{614E}\x{6153}\x{6155}\x{6158}\x{6159}\x{615A}\x{615D}\x{615F}' + . '\x{6162}\x{6163}\x{6165}\x{6167}\x{6168}\x{616B}\x{616E}\x{616F}\x{6170}' + . '\x{6171}\x{6173}\x{6174}\x{6175}\x{6176}\x{6177}\x{617E}\x{6182}\x{6187}' + . '\x{618A}\x{618E}\x{6190}\x{6191}\x{6194}\x{6196}\x{6199}\x{619A}\x{61A4}' + . '\x{61A7}\x{61A9}\x{61AB}\x{61AC}\x{61AE}\x{61B2}\x{61B6}\x{61BA}\x{61BE}' + . '\x{61C3}\x{61C6}\x{61C7}\x{61C8}\x{61C9}\x{61CA}\x{61CB}\x{61CC}\x{61CD}' + . '\x{61D0}\x{61E3}\x{61E6}\x{61F2}\x{61F4}\x{61F6}\x{61F7}\x{61F8}\x{61FA}' + . '\x{61FC}\x{61FD}\x{61FE}\x{61FF}\x{6200}\x{6208}\x{6209}\x{620A}\x{620C}' + . '\x{620D}\x{620E}\x{6210}\x{6211}\x{6212}\x{6214}\x{6216}\x{621A}\x{621B}' + . '\x{621D}\x{621E}\x{621F}\x{6221}\x{6226}\x{622A}\x{622E}\x{622F}\x{6230}' + . '\x{6232}\x{6233}\x{6234}\x{6238}\x{623B}\x{623F}\x{6240}\x{6241}\x{6247}' + . '\x{6248}\x{6249}\x{624B}\x{624D}\x{624E}\x{6253}\x{6255}\x{6258}\x{625B}' + . '\x{625E}\x{6260}\x{6263}\x{6268}\x{626E}\x{6271}\x{6276}\x{6279}\x{627C}' + . '\x{627E}\x{627F}\x{6280}\x{6282}\x{6283}\x{6284}\x{6289}\x{628A}\x{6291}' + . '\x{6292}\x{6293}\x{6294}\x{6295}\x{6296}\x{6297}\x{6298}\x{629B}\x{629C}' + . '\x{629E}\x{62AB}\x{62AC}\x{62B1}\x{62B5}\x{62B9}\x{62BB}\x{62BC}\x{62BD}' + . '\x{62C2}\x{62C5}\x{62C6}\x{62C7}\x{62C8}\x{62C9}\x{62CA}\x{62CC}\x{62CD}' + . '\x{62CF}\x{62D0}\x{62D1}\x{62D2}\x{62D3}\x{62D4}\x{62D7}\x{62D8}\x{62D9}' + . '\x{62DB}\x{62DC}\x{62DD}\x{62E0}\x{62E1}\x{62EC}\x{62ED}\x{62EE}\x{62EF}' + . '\x{62F1}\x{62F3}\x{62F5}\x{62F6}\x{62F7}\x{62FE}\x{62FF}\x{6301}\x{6302}' + . '\x{6307}\x{6308}\x{6309}\x{630C}\x{6311}\x{6319}\x{631F}\x{6327}\x{6328}' + . '\x{632B}\x{632F}\x{633A}\x{633D}\x{633E}\x{633F}\x{6349}\x{634C}\x{634D}' + . '\x{634F}\x{6350}\x{6355}\x{6357}\x{635C}\x{6367}\x{6368}\x{6369}\x{636B}' + . '\x{636E}\x{6372}\x{6376}\x{6377}\x{637A}\x{637B}\x{6380}\x{6383}\x{6388}' + . '\x{6389}\x{638C}\x{638E}\x{638F}\x{6392}\x{6396}\x{6398}\x{639B}\x{639F}' + . '\x{63A0}\x{63A1}\x{63A2}\x{63A3}\x{63A5}\x{63A7}\x{63A8}\x{63A9}\x{63AA}' + . '\x{63AB}\x{63AC}\x{63B2}\x{63B4}\x{63B5}\x{63BB}\x{63BE}\x{63C0}\x{63C3}' + . '\x{63C4}\x{63C6}\x{63C9}\x{63CF}\x{63D0}\x{63D2}\x{63D6}\x{63DA}\x{63DB}' + . '\x{63E1}\x{63E3}\x{63E9}\x{63EE}\x{63F4}\x{63F6}\x{63FA}\x{6406}\x{640D}' + . '\x{640F}\x{6413}\x{6416}\x{6417}\x{641C}\x{6426}\x{6428}\x{642C}\x{642D}' + . '\x{6434}\x{6436}\x{643A}\x{643E}\x{6442}\x{644E}\x{6458}\x{6467}\x{6469}' + . '\x{646F}\x{6476}\x{6478}\x{647A}\x{6483}\x{6488}\x{6492}\x{6493}\x{6495}' + . '\x{649A}\x{649E}\x{64A4}\x{64A5}\x{64A9}\x{64AB}\x{64AD}\x{64AE}\x{64B0}' + . '\x{64B2}\x{64B9}\x{64BB}\x{64BC}\x{64C1}\x{64C2}\x{64C5}\x{64C7}\x{64CD}' + . '\x{64D2}\x{64D4}\x{64D8}\x{64DA}\x{64E0}\x{64E1}\x{64E2}\x{64E3}\x{64E6}' + . '\x{64E7}\x{64EC}\x{64EF}\x{64F1}\x{64F2}\x{64F4}\x{64F6}\x{64FA}\x{64FD}' + . '\x{64FE}\x{6500}\x{6505}\x{6518}\x{651C}\x{651D}\x{6523}\x{6524}\x{652A}' + . '\x{652B}\x{652C}\x{652F}\x{6534}\x{6535}\x{6536}\x{6537}\x{6538}\x{6539}' + . '\x{653B}\x{653E}\x{653F}\x{6545}\x{6548}\x{654D}\x{654F}\x{6551}\x{6555}' + . '\x{6556}\x{6557}\x{6558}\x{6559}\x{655D}\x{655E}\x{6562}\x{6563}\x{6566}' + . '\x{656C}\x{6570}\x{6572}\x{6574}\x{6575}\x{6577}\x{6578}\x{6582}\x{6583}' + . '\x{6587}\x{6588}\x{6589}\x{658C}\x{658E}\x{6590}\x{6591}\x{6597}\x{6599}' + . '\x{659B}\x{659C}\x{659F}\x{65A1}\x{65A4}\x{65A5}\x{65A7}\x{65AB}\x{65AC}' + . '\x{65AD}\x{65AF}\x{65B0}\x{65B7}\x{65B9}\x{65BC}\x{65BD}\x{65C1}\x{65C3}' + . '\x{65C4}\x{65C5}\x{65C6}\x{65CB}\x{65CC}\x{65CF}\x{65D2}\x{65D7}\x{65D9}' + . '\x{65DB}\x{65E0}\x{65E1}\x{65E2}\x{65E5}\x{65E6}\x{65E7}\x{65E8}\x{65E9}' + . '\x{65EC}\x{65ED}\x{65F1}\x{65FA}\x{65FB}\x{6602}\x{6603}\x{6606}\x{6607}' + . '\x{660A}\x{660C}\x{660E}\x{660F}\x{6613}\x{6614}\x{661C}\x{661F}\x{6620}' + . '\x{6625}\x{6627}\x{6628}\x{662D}\x{662F}\x{6634}\x{6635}\x{6636}\x{663C}' + . '\x{663F}\x{6641}\x{6642}\x{6643}\x{6644}\x{6649}\x{664B}\x{664F}\x{6652}' + . '\x{665D}\x{665E}\x{665F}\x{6662}\x{6664}\x{6666}\x{6667}\x{6668}\x{6669}' + . '\x{666E}\x{666F}\x{6670}\x{6674}\x{6676}\x{667A}\x{6681}\x{6683}\x{6684}' + . '\x{6687}\x{6688}\x{6689}\x{668E}\x{6691}\x{6696}\x{6697}\x{6698}\x{669D}' + . '\x{66A2}\x{66A6}\x{66AB}\x{66AE}\x{66B4}\x{66B8}\x{66B9}\x{66BC}\x{66BE}' + . '\x{66C1}\x{66C4}\x{66C7}\x{66C9}\x{66D6}\x{66D9}\x{66DA}\x{66DC}\x{66DD}' + . '\x{66E0}\x{66E6}\x{66E9}\x{66F0}\x{66F2}\x{66F3}\x{66F4}\x{66F5}\x{66F7}' + . '\x{66F8}\x{66F9}\x{66FC}\x{66FD}\x{66FE}\x{66FF}\x{6700}\x{6703}\x{6708}' + . '\x{6709}\x{670B}\x{670D}\x{670F}\x{6714}\x{6715}\x{6716}\x{6717}\x{671B}' + . '\x{671D}\x{671E}\x{671F}\x{6726}\x{6727}\x{6728}\x{672A}\x{672B}\x{672C}' + . '\x{672D}\x{672E}\x{6731}\x{6734}\x{6736}\x{6737}\x{6738}\x{673A}\x{673D}' + . '\x{673F}\x{6741}\x{6746}\x{6749}\x{674E}\x{674F}\x{6750}\x{6751}\x{6753}' + . '\x{6756}\x{6759}\x{675C}\x{675E}\x{675F}\x{6760}\x{6761}\x{6762}\x{6763}' + . '\x{6764}\x{6765}\x{676A}\x{676D}\x{676F}\x{6770}\x{6771}\x{6772}\x{6773}' + . '\x{6775}\x{6777}\x{677C}\x{677E}\x{677F}\x{6785}\x{6787}\x{6789}\x{678B}' + . '\x{678C}\x{6790}\x{6795}\x{6797}\x{679A}\x{679C}\x{679D}\x{67A0}\x{67A1}' + . '\x{67A2}\x{67A6}\x{67A9}\x{67AF}\x{67B3}\x{67B4}\x{67B6}\x{67B7}\x{67B8}' + . '\x{67B9}\x{67C1}\x{67C4}\x{67C6}\x{67CA}\x{67CE}\x{67CF}\x{67D0}\x{67D1}' + . '\x{67D3}\x{67D4}\x{67D8}\x{67DA}\x{67DD}\x{67DE}\x{67E2}\x{67E4}\x{67E7}' + . '\x{67E9}\x{67EC}\x{67EE}\x{67EF}\x{67F1}\x{67F3}\x{67F4}\x{67F5}\x{67FB}' + . '\x{67FE}\x{67FF}\x{6802}\x{6803}\x{6804}\x{6813}\x{6816}\x{6817}\x{681E}' + . '\x{6821}\x{6822}\x{6829}\x{682A}\x{682B}\x{6832}\x{6834}\x{6838}\x{6839}' + . '\x{683C}\x{683D}\x{6840}\x{6841}\x{6842}\x{6843}\x{6846}\x{6848}\x{684D}' + . '\x{684E}\x{6850}\x{6851}\x{6853}\x{6854}\x{6859}\x{685C}\x{685D}\x{685F}' + . '\x{6863}\x{6867}\x{6874}\x{6876}\x{6877}\x{687E}\x{687F}\x{6881}\x{6883}' + . '\x{6885}\x{688D}\x{688F}\x{6893}\x{6894}\x{6897}\x{689B}\x{689D}\x{689F}' + . '\x{68A0}\x{68A2}\x{68A6}\x{68A7}\x{68A8}\x{68AD}\x{68AF}\x{68B0}\x{68B1}' + . '\x{68B3}\x{68B5}\x{68B6}\x{68B9}\x{68BA}\x{68BC}\x{68C4}\x{68C6}\x{68C9}' + . '\x{68CA}\x{68CB}\x{68CD}\x{68D2}\x{68D4}\x{68D5}\x{68D7}\x{68D8}\x{68DA}' + . '\x{68DF}\x{68E0}\x{68E1}\x{68E3}\x{68E7}\x{68EE}\x{68EF}\x{68F2}\x{68F9}' + . '\x{68FA}\x{6900}\x{6901}\x{6904}\x{6905}\x{6908}\x{690B}\x{690C}\x{690D}' + . '\x{690E}\x{690F}\x{6912}\x{6919}\x{691A}\x{691B}\x{691C}\x{6921}\x{6922}' + . '\x{6923}\x{6925}\x{6926}\x{6928}\x{692A}\x{6930}\x{6934}\x{6936}\x{6939}' + . '\x{693D}\x{693F}\x{694A}\x{6953}\x{6954}\x{6955}\x{6959}\x{695A}\x{695C}' + . '\x{695D}\x{695E}\x{6960}\x{6961}\x{6962}\x{696A}\x{696B}\x{696D}\x{696E}' + . '\x{696F}\x{6973}\x{6974}\x{6975}\x{6977}\x{6978}\x{6979}\x{697C}\x{697D}' + . '\x{697E}\x{6981}\x{6982}\x{698A}\x{698E}\x{6991}\x{6994}\x{6995}\x{699B}' + . '\x{699C}\x{69A0}\x{69A7}\x{69AE}\x{69B1}\x{69B2}\x{69B4}\x{69BB}\x{69BE}' + . '\x{69BF}\x{69C1}\x{69C3}\x{69C7}\x{69CA}\x{69CB}\x{69CC}\x{69CD}\x{69CE}' + . '\x{69D0}\x{69D3}\x{69D8}\x{69D9}\x{69DD}\x{69DE}\x{69E7}\x{69E8}\x{69EB}' + . '\x{69ED}\x{69F2}\x{69F9}\x{69FB}\x{69FD}\x{69FF}\x{6A02}\x{6A05}\x{6A0A}' + . '\x{6A0B}\x{6A0C}\x{6A12}\x{6A13}\x{6A14}\x{6A17}\x{6A19}\x{6A1B}\x{6A1E}' + . '\x{6A1F}\x{6A21}\x{6A22}\x{6A23}\x{6A29}\x{6A2A}\x{6A2B}\x{6A2E}\x{6A35}' + . '\x{6A36}\x{6A38}\x{6A39}\x{6A3A}\x{6A3D}\x{6A44}\x{6A47}\x{6A48}\x{6A4B}' + . '\x{6A58}\x{6A59}\x{6A5F}\x{6A61}\x{6A62}\x{6A66}\x{6A72}\x{6A78}\x{6A7F}' + . '\x{6A80}\x{6A84}\x{6A8D}\x{6A8E}\x{6A90}\x{6A97}\x{6A9C}\x{6AA0}\x{6AA2}' + . '\x{6AA3}\x{6AAA}\x{6AAC}\x{6AAE}\x{6AB3}\x{6AB8}\x{6ABB}\x{6AC1}\x{6AC2}' + . '\x{6AC3}\x{6AD1}\x{6AD3}\x{6ADA}\x{6ADB}\x{6ADE}\x{6ADF}\x{6AE8}\x{6AEA}' + . '\x{6AFA}\x{6AFB}\x{6B04}\x{6B05}\x{6B0A}\x{6B12}\x{6B16}\x{6B1D}\x{6B1F}' + . '\x{6B20}\x{6B21}\x{6B23}\x{6B27}\x{6B32}\x{6B37}\x{6B38}\x{6B39}\x{6B3A}' + . '\x{6B3D}\x{6B3E}\x{6B43}\x{6B47}\x{6B49}\x{6B4C}\x{6B4E}\x{6B50}\x{6B53}' + . '\x{6B54}\x{6B59}\x{6B5B}\x{6B5F}\x{6B61}\x{6B62}\x{6B63}\x{6B64}\x{6B66}' + . '\x{6B69}\x{6B6A}\x{6B6F}\x{6B73}\x{6B74}\x{6B78}\x{6B79}\x{6B7B}\x{6B7F}' + . '\x{6B80}\x{6B83}\x{6B84}\x{6B86}\x{6B89}\x{6B8A}\x{6B8B}\x{6B8D}\x{6B95}' + . '\x{6B96}\x{6B98}\x{6B9E}\x{6BA4}\x{6BAA}\x{6BAB}\x{6BAF}\x{6BB1}\x{6BB2}' + . '\x{6BB3}\x{6BB4}\x{6BB5}\x{6BB7}\x{6BBA}\x{6BBB}\x{6BBC}\x{6BBF}\x{6BC0}' + . '\x{6BC5}\x{6BC6}\x{6BCB}\x{6BCD}\x{6BCE}\x{6BD2}\x{6BD3}\x{6BD4}\x{6BD8}' + . '\x{6BDB}\x{6BDF}\x{6BEB}\x{6BEC}\x{6BEF}\x{6BF3}\x{6C08}\x{6C0F}\x{6C11}' + . '\x{6C13}\x{6C14}\x{6C17}\x{6C1B}\x{6C23}\x{6C24}\x{6C34}\x{6C37}\x{6C38}' + . '\x{6C3E}\x{6C40}\x{6C41}\x{6C42}\x{6C4E}\x{6C50}\x{6C55}\x{6C57}\x{6C5A}' + . '\x{6C5D}\x{6C5E}\x{6C5F}\x{6C60}\x{6C62}\x{6C68}\x{6C6A}\x{6C70}\x{6C72}' + . '\x{6C73}\x{6C7A}\x{6C7D}\x{6C7E}\x{6C81}\x{6C82}\x{6C83}\x{6C88}\x{6C8C}' + . '\x{6C8D}\x{6C90}\x{6C92}\x{6C93}\x{6C96}\x{6C99}\x{6C9A}\x{6C9B}\x{6CA1}' + . '\x{6CA2}\x{6CAB}\x{6CAE}\x{6CB1}\x{6CB3}\x{6CB8}\x{6CB9}\x{6CBA}\x{6CBB}' + . '\x{6CBC}\x{6CBD}\x{6CBE}\x{6CBF}\x{6CC1}\x{6CC4}\x{6CC5}\x{6CC9}\x{6CCA}' + . '\x{6CCC}\x{6CD3}\x{6CD5}\x{6CD7}\x{6CD9}\x{6CDB}\x{6CDD}\x{6CE1}\x{6CE2}' + . '\x{6CE3}\x{6CE5}\x{6CE8}\x{6CEA}\x{6CEF}\x{6CF0}\x{6CF1}\x{6CF3}\x{6D0B}' + . '\x{6D0C}\x{6D12}\x{6D17}\x{6D19}\x{6D1B}\x{6D1E}\x{6D1F}\x{6D25}\x{6D29}' + . '\x{6D2A}\x{6D2B}\x{6D32}\x{6D33}\x{6D35}\x{6D36}\x{6D38}\x{6D3B}\x{6D3D}' + . '\x{6D3E}\x{6D41}\x{6D44}\x{6D45}\x{6D59}\x{6D5A}\x{6D5C}\x{6D63}\x{6D64}' + . '\x{6D66}\x{6D69}\x{6D6A}\x{6D6C}\x{6D6E}\x{6D74}\x{6D77}\x{6D78}\x{6D79}' + . '\x{6D85}\x{6D88}\x{6D8C}\x{6D8E}\x{6D93}\x{6D95}\x{6D99}\x{6D9B}\x{6D9C}' + . '\x{6DAF}\x{6DB2}\x{6DB5}\x{6DB8}\x{6DBC}\x{6DC0}\x{6DC5}\x{6DC6}\x{6DC7}' + . '\x{6DCB}\x{6DCC}\x{6DD1}\x{6DD2}\x{6DD5}\x{6DD8}\x{6DD9}\x{6DDE}\x{6DE1}' + . '\x{6DE4}\x{6DE6}\x{6DE8}\x{6DEA}\x{6DEB}\x{6DEC}\x{6DEE}\x{6DF1}\x{6DF3}' + . '\x{6DF5}\x{6DF7}\x{6DF9}\x{6DFA}\x{6DFB}\x{6E05}\x{6E07}\x{6E08}\x{6E09}' + . '\x{6E0A}\x{6E0B}\x{6E13}\x{6E15}\x{6E19}\x{6E1A}\x{6E1B}\x{6E1D}\x{6E1F}' + . '\x{6E20}\x{6E21}\x{6E23}\x{6E24}\x{6E25}\x{6E26}\x{6E29}\x{6E2B}\x{6E2C}' + . '\x{6E2D}\x{6E2E}\x{6E2F}\x{6E38}\x{6E3A}\x{6E3E}\x{6E43}\x{6E4A}\x{6E4D}' + . '\x{6E4E}\x{6E56}\x{6E58}\x{6E5B}\x{6E5F}\x{6E67}\x{6E6B}\x{6E6E}\x{6E6F}' + . '\x{6E72}\x{6E76}\x{6E7E}\x{6E7F}\x{6E80}\x{6E82}\x{6E8C}\x{6E8F}\x{6E90}' + . '\x{6E96}\x{6E98}\x{6E9C}\x{6E9D}\x{6E9F}\x{6EA2}\x{6EA5}\x{6EAA}\x{6EAF}' + . '\x{6EB2}\x{6EB6}\x{6EB7}\x{6EBA}\x{6EBD}\x{6EC2}\x{6EC4}\x{6EC5}\x{6EC9}' + . '\x{6ECB}\x{6ECC}\x{6ED1}\x{6ED3}\x{6ED4}\x{6ED5}\x{6EDD}\x{6EDE}\x{6EEC}' + . '\x{6EEF}\x{6EF2}\x{6EF4}\x{6EF7}\x{6EF8}\x{6EFE}\x{6EFF}\x{6F01}\x{6F02}' + . '\x{6F06}\x{6F09}\x{6F0F}\x{6F11}\x{6F13}\x{6F14}\x{6F15}\x{6F20}\x{6F22}' + . '\x{6F23}\x{6F2B}\x{6F2C}\x{6F31}\x{6F32}\x{6F38}\x{6F3E}\x{6F3F}\x{6F41}' + . '\x{6F45}\x{6F54}\x{6F58}\x{6F5B}\x{6F5C}\x{6F5F}\x{6F64}\x{6F66}\x{6F6D}' + . '\x{6F6E}\x{6F6F}\x{6F70}\x{6F74}\x{6F78}\x{6F7A}\x{6F7C}\x{6F80}\x{6F81}' + . '\x{6F82}\x{6F84}\x{6F86}\x{6F8E}\x{6F91}\x{6F97}\x{6FA1}\x{6FA3}\x{6FA4}' + . '\x{6FAA}\x{6FB1}\x{6FB3}\x{6FB9}\x{6FC0}\x{6FC1}\x{6FC2}\x{6FC3}\x{6FC6}' + . '\x{6FD4}\x{6FD5}\x{6FD8}\x{6FDB}\x{6FDF}\x{6FE0}\x{6FE1}\x{6FE4}\x{6FEB}' + . '\x{6FEC}\x{6FEE}\x{6FEF}\x{6FF1}\x{6FF3}\x{6FF6}\x{6FFA}\x{6FFE}\x{7001}' + . '\x{7009}\x{700B}\x{700F}\x{7011}\x{7015}\x{7018}\x{701A}\x{701B}\x{701D}' + . '\x{701E}\x{701F}\x{7026}\x{7027}\x{702C}\x{7030}\x{7032}\x{703E}\x{704C}' + . '\x{7051}\x{7058}\x{7063}\x{706B}\x{706F}\x{7070}\x{7078}\x{707C}\x{707D}' + . '\x{7089}\x{708A}\x{708E}\x{7092}\x{7099}\x{70AC}\x{70AD}\x{70AE}\x{70AF}' + . '\x{70B3}\x{70B8}\x{70B9}\x{70BA}\x{70C8}\x{70CB}\x{70CF}\x{70D9}\x{70DD}' + . '\x{70DF}\x{70F1}\x{70F9}\x{70FD}\x{7109}\x{7114}\x{7119}\x{711A}\x{711C}' + . '\x{7121}\x{7126}\x{7136}\x{713C}\x{7149}\x{714C}\x{714E}\x{7155}\x{7156}' + . '\x{7159}\x{7162}\x{7164}\x{7165}\x{7166}\x{7167}\x{7169}\x{716C}\x{716E}' + . '\x{717D}\x{7184}\x{7188}\x{718A}\x{718F}\x{7194}\x{7195}\x{7199}\x{719F}' + . '\x{71A8}\x{71AC}\x{71B1}\x{71B9}\x{71BE}\x{71C3}\x{71C8}\x{71C9}\x{71CE}' + . '\x{71D0}\x{71D2}\x{71D4}\x{71D5}\x{71D7}\x{71DF}\x{71E0}\x{71E5}\x{71E6}' + . '\x{71E7}\x{71EC}\x{71ED}\x{71EE}\x{71F5}\x{71F9}\x{71FB}\x{71FC}\x{71FF}' + . '\x{7206}\x{720D}\x{7210}\x{721B}\x{7228}\x{722A}\x{722C}\x{722D}\x{7230}' + . '\x{7232}\x{7235}\x{7236}\x{723A}\x{723B}\x{723C}\x{723D}\x{723E}\x{723F}' + . '\x{7240}\x{7246}\x{7247}\x{7248}\x{724B}\x{724C}\x{7252}\x{7258}\x{7259}' + . '\x{725B}\x{725D}\x{725F}\x{7261}\x{7262}\x{7267}\x{7269}\x{7272}\x{7274}' + . '\x{7279}\x{727D}\x{727E}\x{7280}\x{7281}\x{7282}\x{7287}\x{7292}\x{7296}' + . '\x{72A0}\x{72A2}\x{72A7}\x{72AC}\x{72AF}\x{72B2}\x{72B6}\x{72B9}\x{72C2}' + . '\x{72C3}\x{72C4}\x{72C6}\x{72CE}\x{72D0}\x{72D2}\x{72D7}\x{72D9}\x{72DB}' + . '\x{72E0}\x{72E1}\x{72E2}\x{72E9}\x{72EC}\x{72ED}\x{72F7}\x{72F8}\x{72F9}' + . '\x{72FC}\x{72FD}\x{730A}\x{7316}\x{7317}\x{731B}\x{731C}\x{731D}\x{731F}' + . '\x{7325}\x{7329}\x{732A}\x{732B}\x{732E}\x{732F}\x{7334}\x{7336}\x{7337}' + . '\x{733E}\x{733F}\x{7344}\x{7345}\x{734E}\x{734F}\x{7357}\x{7363}\x{7368}' + . '\x{736A}\x{7370}\x{7372}\x{7375}\x{7378}\x{737A}\x{737B}\x{7384}\x{7387}' + . '\x{7389}\x{738B}\x{7396}\x{73A9}\x{73B2}\x{73B3}\x{73BB}\x{73C0}\x{73C2}' + . '\x{73C8}\x{73CA}\x{73CD}\x{73CE}\x{73DE}\x{73E0}\x{73E5}\x{73EA}\x{73ED}' + . '\x{73EE}\x{73F1}\x{73F8}\x{73FE}\x{7403}\x{7405}\x{7406}\x{7409}\x{7422}' + . '\x{7425}\x{7432}\x{7433}\x{7434}\x{7435}\x{7436}\x{743A}\x{743F}\x{7441}' + . '\x{7455}\x{7459}\x{745A}\x{745B}\x{745C}\x{745E}\x{745F}\x{7460}\x{7463}' + . '\x{7464}\x{7469}\x{746A}\x{746F}\x{7470}\x{7473}\x{7476}\x{747E}\x{7483}' + . '\x{748B}\x{749E}\x{74A2}\x{74A7}\x{74B0}\x{74BD}\x{74CA}\x{74CF}\x{74D4}' + . '\x{74DC}\x{74E0}\x{74E2}\x{74E3}\x{74E6}\x{74E7}\x{74E9}\x{74EE}\x{74F0}' + . '\x{74F1}\x{74F2}\x{74F6}\x{74F7}\x{74F8}\x{7503}\x{7504}\x{7505}\x{750C}' + . '\x{750D}\x{750E}\x{7511}\x{7513}\x{7515}\x{7518}\x{751A}\x{751C}\x{751E}' + . '\x{751F}\x{7523}\x{7525}\x{7526}\x{7528}\x{752B}\x{752C}\x{7530}\x{7531}' + . '\x{7532}\x{7533}\x{7537}\x{7538}\x{753A}\x{753B}\x{753C}\x{7544}\x{7546}' + . '\x{7549}\x{754A}\x{754B}\x{754C}\x{754D}\x{754F}\x{7551}\x{7554}\x{7559}' + . '\x{755A}\x{755B}\x{755C}\x{755D}\x{7560}\x{7562}\x{7564}\x{7565}\x{7566}' + . '\x{7567}\x{7569}\x{756A}\x{756B}\x{756D}\x{7570}\x{7573}\x{7574}\x{7576}' + . '\x{7577}\x{7578}\x{757F}\x{7582}\x{7586}\x{7587}\x{7589}\x{758A}\x{758B}' + . '\x{758E}\x{758F}\x{7591}\x{7594}\x{759A}\x{759D}\x{75A3}\x{75A5}\x{75AB}' + . '\x{75B1}\x{75B2}\x{75B3}\x{75B5}\x{75B8}\x{75B9}\x{75BC}\x{75BD}\x{75BE}' + . '\x{75C2}\x{75C3}\x{75C5}\x{75C7}\x{75CA}\x{75CD}\x{75D2}\x{75D4}\x{75D5}' + . '\x{75D8}\x{75D9}\x{75DB}\x{75DE}\x{75E2}\x{75E3}\x{75E9}\x{75F0}\x{75F2}' + . '\x{75F3}\x{75F4}\x{75FA}\x{75FC}\x{75FE}\x{75FF}\x{7601}\x{7609}\x{760B}' + . '\x{760D}\x{761F}\x{7620}\x{7621}\x{7622}\x{7624}\x{7627}\x{7630}\x{7634}' + . '\x{763B}\x{7642}\x{7646}\x{7647}\x{7648}\x{764C}\x{7652}\x{7656}\x{7658}' + . '\x{765C}\x{7661}\x{7662}\x{7667}\x{7668}\x{7669}\x{766A}\x{766C}\x{7670}' + . '\x{7672}\x{7676}\x{7678}\x{767A}\x{767B}\x{767C}\x{767D}\x{767E}\x{7680}' + . '\x{7683}\x{7684}\x{7686}\x{7687}\x{7688}\x{768B}\x{768E}\x{7690}\x{7693}' + . '\x{7696}\x{7699}\x{769A}\x{76AE}\x{76B0}\x{76B4}\x{76B7}\x{76B8}\x{76B9}' + . '\x{76BA}\x{76BF}\x{76C2}\x{76C3}\x{76C6}\x{76C8}\x{76CA}\x{76CD}\x{76D2}' + . '\x{76D6}\x{76D7}\x{76DB}\x{76DC}\x{76DE}\x{76DF}\x{76E1}\x{76E3}\x{76E4}' + . '\x{76E5}\x{76E7}\x{76EA}\x{76EE}\x{76F2}\x{76F4}\x{76F8}\x{76FB}\x{76FE}' + . '\x{7701}\x{7704}\x{7707}\x{7708}\x{7709}\x{770B}\x{770C}\x{771B}\x{771E}' + . '\x{771F}\x{7720}\x{7724}\x{7725}\x{7726}\x{7729}\x{7737}\x{7738}\x{773A}' + . '\x{773C}\x{7740}\x{7747}\x{775A}\x{775B}\x{7761}\x{7763}\x{7765}\x{7766}' + . '\x{7768}\x{776B}\x{7779}\x{777E}\x{777F}\x{778B}\x{778E}\x{7791}\x{779E}' + . '\x{77A0}\x{77A5}\x{77AC}\x{77AD}\x{77B0}\x{77B3}\x{77B6}\x{77B9}\x{77BB}' + . '\x{77BC}\x{77BD}\x{77BF}\x{77C7}\x{77CD}\x{77D7}\x{77DA}\x{77DB}\x{77DC}' + . '\x{77E2}\x{77E3}\x{77E5}\x{77E7}\x{77E9}\x{77ED}\x{77EE}\x{77EF}\x{77F3}' + . '\x{77FC}\x{7802}\x{780C}\x{7812}\x{7814}\x{7815}\x{7820}\x{7825}\x{7826}' + . '\x{7827}\x{7832}\x{7834}\x{783A}\x{783F}\x{7845}\x{785D}\x{786B}\x{786C}' + . '\x{786F}\x{7872}\x{7874}\x{787C}\x{7881}\x{7886}\x{7887}\x{788C}\x{788D}' + . '\x{788E}\x{7891}\x{7893}\x{7895}\x{7897}\x{789A}\x{78A3}\x{78A7}\x{78A9}' + . '\x{78AA}\x{78AF}\x{78B5}\x{78BA}\x{78BC}\x{78BE}\x{78C1}\x{78C5}\x{78C6}' + . '\x{78CA}\x{78CB}\x{78D0}\x{78D1}\x{78D4}\x{78DA}\x{78E7}\x{78E8}\x{78EC}' + . '\x{78EF}\x{78F4}\x{78FD}\x{7901}\x{7907}\x{790E}\x{7911}\x{7912}\x{7919}' + . '\x{7926}\x{792A}\x{792B}\x{792C}\x{793A}\x{793C}\x{793E}\x{7940}\x{7941}' + . '\x{7947}\x{7948}\x{7949}\x{7950}\x{7953}\x{7955}\x{7956}\x{7957}\x{795A}' + . '\x{795D}\x{795E}\x{795F}\x{7960}\x{7962}\x{7965}\x{7968}\x{796D}\x{7977}' + . '\x{797A}\x{797F}\x{7980}\x{7981}\x{7984}\x{7985}\x{798A}\x{798D}\x{798E}' + . '\x{798F}\x{799D}\x{79A6}\x{79A7}\x{79AA}\x{79AE}\x{79B0}\x{79B3}\x{79B9}' + . '\x{79BA}\x{79BD}\x{79BE}\x{79BF}\x{79C0}\x{79C1}\x{79C9}\x{79CB}\x{79D1}' + . '\x{79D2}\x{79D5}\x{79D8}\x{79DF}\x{79E1}\x{79E3}\x{79E4}\x{79E6}\x{79E7}' + . '\x{79E9}\x{79EC}\x{79F0}\x{79FB}\x{7A00}\x{7A08}\x{7A0B}\x{7A0D}\x{7A0E}' + . '\x{7A14}\x{7A17}\x{7A18}\x{7A19}\x{7A1A}\x{7A1C}\x{7A1F}\x{7A20}\x{7A2E}' + . '\x{7A31}\x{7A32}\x{7A37}\x{7A3B}\x{7A3C}\x{7A3D}\x{7A3E}\x{7A3F}\x{7A40}' + . '\x{7A42}\x{7A43}\x{7A46}\x{7A49}\x{7A4D}\x{7A4E}\x{7A4F}\x{7A50}\x{7A57}' + . '\x{7A61}\x{7A62}\x{7A63}\x{7A69}\x{7A6B}\x{7A70}\x{7A74}\x{7A76}\x{7A79}' + . '\x{7A7A}\x{7A7D}\x{7A7F}\x{7A81}\x{7A83}\x{7A84}\x{7A88}\x{7A92}\x{7A93}' + . '\x{7A95}\x{7A96}\x{7A97}\x{7A98}\x{7A9F}\x{7AA9}\x{7AAA}\x{7AAE}\x{7AAF}' + . '\x{7AB0}\x{7AB6}\x{7ABA}\x{7ABF}\x{7AC3}\x{7AC4}\x{7AC5}\x{7AC7}\x{7AC8}' + . '\x{7ACA}\x{7ACB}\x{7ACD}\x{7ACF}\x{7AD2}\x{7AD3}\x{7AD5}\x{7AD9}\x{7ADA}' + . '\x{7ADC}\x{7ADD}\x{7ADF}\x{7AE0}\x{7AE1}\x{7AE2}\x{7AE3}\x{7AE5}\x{7AE6}' + . '\x{7AEA}\x{7AED}\x{7AEF}\x{7AF0}\x{7AF6}\x{7AF8}\x{7AF9}\x{7AFA}\x{7AFF}' + . '\x{7B02}\x{7B04}\x{7B06}\x{7B08}\x{7B0A}\x{7B0B}\x{7B0F}\x{7B11}\x{7B18}' + . '\x{7B19}\x{7B1B}\x{7B1E}\x{7B20}\x{7B25}\x{7B26}\x{7B28}\x{7B2C}\x{7B33}' + . '\x{7B35}\x{7B36}\x{7B39}\x{7B45}\x{7B46}\x{7B48}\x{7B49}\x{7B4B}\x{7B4C}' + . '\x{7B4D}\x{7B4F}\x{7B50}\x{7B51}\x{7B52}\x{7B54}\x{7B56}\x{7B5D}\x{7B65}' + . '\x{7B67}\x{7B6C}\x{7B6E}\x{7B70}\x{7B71}\x{7B74}\x{7B75}\x{7B7A}\x{7B86}' + . '\x{7B87}\x{7B8B}\x{7B8D}\x{7B8F}\x{7B92}\x{7B94}\x{7B95}\x{7B97}\x{7B98}' + . '\x{7B99}\x{7B9A}\x{7B9C}\x{7B9D}\x{7B9F}\x{7BA1}\x{7BAA}\x{7BAD}\x{7BB1}' + . '\x{7BB4}\x{7BB8}\x{7BC0}\x{7BC1}\x{7BC4}\x{7BC6}\x{7BC7}\x{7BC9}\x{7BCB}' + . '\x{7BCC}\x{7BCF}\x{7BDD}\x{7BE0}\x{7BE4}\x{7BE5}\x{7BE6}\x{7BE9}\x{7BED}' + . '\x{7BF3}\x{7BF6}\x{7BF7}\x{7C00}\x{7C07}\x{7C0D}\x{7C11}\x{7C12}\x{7C13}' + . '\x{7C14}\x{7C17}\x{7C1F}\x{7C21}\x{7C23}\x{7C27}\x{7C2A}\x{7C2B}\x{7C37}' + . '\x{7C38}\x{7C3D}\x{7C3E}\x{7C3F}\x{7C40}\x{7C43}\x{7C4C}\x{7C4D}\x{7C4F}' + . '\x{7C50}\x{7C54}\x{7C56}\x{7C58}\x{7C5F}\x{7C60}\x{7C64}\x{7C65}\x{7C6C}' + . '\x{7C73}\x{7C75}\x{7C7E}\x{7C81}\x{7C82}\x{7C83}\x{7C89}\x{7C8B}\x{7C8D}' + . '\x{7C90}\x{7C92}\x{7C95}\x{7C97}\x{7C98}\x{7C9B}\x{7C9F}\x{7CA1}\x{7CA2}' + . '\x{7CA4}\x{7CA5}\x{7CA7}\x{7CA8}\x{7CAB}\x{7CAD}\x{7CAE}\x{7CB1}\x{7CB2}' + . '\x{7CB3}\x{7CB9}\x{7CBD}\x{7CBE}\x{7CC0}\x{7CC2}\x{7CC5}\x{7CCA}\x{7CCE}' + . '\x{7CD2}\x{7CD6}\x{7CD8}\x{7CDC}\x{7CDE}\x{7CDF}\x{7CE0}\x{7CE2}\x{7CE7}' + . '\x{7CEF}\x{7CF2}\x{7CF4}\x{7CF6}\x{7CF8}\x{7CFA}\x{7CFB}\x{7CFE}\x{7D00}' + . '\x{7D02}\x{7D04}\x{7D05}\x{7D06}\x{7D0A}\x{7D0B}\x{7D0D}\x{7D10}\x{7D14}' + . '\x{7D15}\x{7D17}\x{7D18}\x{7D19}\x{7D1A}\x{7D1B}\x{7D1C}\x{7D20}\x{7D21}' + . '\x{7D22}\x{7D2B}\x{7D2C}\x{7D2E}\x{7D2F}\x{7D30}\x{7D32}\x{7D33}\x{7D35}' + . '\x{7D39}\x{7D3A}\x{7D3F}\x{7D42}\x{7D43}\x{7D44}\x{7D45}\x{7D46}\x{7D4B}' + . '\x{7D4C}\x{7D4E}\x{7D4F}\x{7D50}\x{7D56}\x{7D5B}\x{7D5E}\x{7D61}\x{7D62}' + . '\x{7D63}\x{7D66}\x{7D68}\x{7D6E}\x{7D71}\x{7D72}\x{7D73}\x{7D75}\x{7D76}' + . '\x{7D79}\x{7D7D}\x{7D89}\x{7D8F}\x{7D93}\x{7D99}\x{7D9A}\x{7D9B}\x{7D9C}' + . '\x{7D9F}\x{7DA2}\x{7DA3}\x{7DAB}\x{7DAC}\x{7DAD}\x{7DAE}\x{7DAF}\x{7DB0}' + . '\x{7DB1}\x{7DB2}\x{7DB4}\x{7DB5}\x{7DB8}\x{7DBA}\x{7DBB}\x{7DBD}\x{7DBE}' + . '\x{7DBF}\x{7DC7}\x{7DCA}\x{7DCB}\x{7DCF}\x{7DD1}\x{7DD2}\x{7DD5}\x{7DD8}' + . '\x{7DDA}\x{7DDC}\x{7DDD}\x{7DDE}\x{7DE0}\x{7DE1}\x{7DE4}\x{7DE8}\x{7DE9}' + . '\x{7DEC}\x{7DEF}\x{7DF2}\x{7DF4}\x{7DFB}\x{7E01}\x{7E04}\x{7E05}\x{7E09}' + . '\x{7E0A}\x{7E0B}\x{7E12}\x{7E1B}\x{7E1E}\x{7E1F}\x{7E21}\x{7E22}\x{7E23}' + . '\x{7E26}\x{7E2B}\x{7E2E}\x{7E31}\x{7E32}\x{7E35}\x{7E37}\x{7E39}\x{7E3A}' + . '\x{7E3B}\x{7E3D}\x{7E3E}\x{7E41}\x{7E43}\x{7E46}\x{7E4A}\x{7E4B}\x{7E4D}' + . '\x{7E54}\x{7E55}\x{7E56}\x{7E59}\x{7E5A}\x{7E5D}\x{7E5E}\x{7E66}\x{7E67}' + . '\x{7E69}\x{7E6A}\x{7E6D}\x{7E70}\x{7E79}\x{7E7B}\x{7E7C}\x{7E7D}\x{7E7F}' + . '\x{7E82}\x{7E83}\x{7E88}\x{7E89}\x{7E8C}\x{7E8E}\x{7E8F}\x{7E90}\x{7E92}' + . '\x{7E93}\x{7E94}\x{7E96}\x{7E9B}\x{7E9C}\x{7F36}\x{7F38}\x{7F3A}\x{7F45}' + . '\x{7F4C}\x{7F4D}\x{7F4E}\x{7F50}\x{7F51}\x{7F54}\x{7F55}\x{7F58}\x{7F5F}' + . '\x{7F60}\x{7F67}\x{7F68}\x{7F69}\x{7F6A}\x{7F6B}\x{7F6E}\x{7F70}\x{7F72}' + . '\x{7F75}\x{7F77}\x{7F78}\x{7F79}\x{7F82}\x{7F83}\x{7F85}\x{7F86}\x{7F87}' + . '\x{7F88}\x{7F8A}\x{7F8C}\x{7F8E}\x{7F94}\x{7F9A}\x{7F9D}\x{7F9E}\x{7FA3}' + . '\x{7FA4}\x{7FA8}\x{7FA9}\x{7FAE}\x{7FAF}\x{7FB2}\x{7FB6}\x{7FB8}\x{7FB9}' + . '\x{7FBD}\x{7FC1}\x{7FC5}\x{7FC6}\x{7FCA}\x{7FCC}\x{7FD2}\x{7FD4}\x{7FD5}' + . '\x{7FE0}\x{7FE1}\x{7FE6}\x{7FE9}\x{7FEB}\x{7FF0}\x{7FF3}\x{7FF9}\x{7FFB}' + . '\x{7FFC}\x{8000}\x{8001}\x{8003}\x{8004}\x{8005}\x{8006}\x{800B}\x{800C}' + . '\x{8010}\x{8012}\x{8015}\x{8017}\x{8018}\x{8019}\x{801C}\x{8021}\x{8028}' + . '\x{8033}\x{8036}\x{803B}\x{803D}\x{803F}\x{8046}\x{804A}\x{8052}\x{8056}' + . '\x{8058}\x{805A}\x{805E}\x{805F}\x{8061}\x{8062}\x{8068}\x{806F}\x{8070}' + . '\x{8072}\x{8073}\x{8074}\x{8076}\x{8077}\x{8079}\x{807D}\x{807E}\x{807F}' + . '\x{8084}\x{8085}\x{8086}\x{8087}\x{8089}\x{808B}\x{808C}\x{8093}\x{8096}' + . '\x{8098}\x{809A}\x{809B}\x{809D}\x{80A1}\x{80A2}\x{80A5}\x{80A9}\x{80AA}' + . '\x{80AC}\x{80AD}\x{80AF}\x{80B1}\x{80B2}\x{80B4}\x{80BA}\x{80C3}\x{80C4}' + . '\x{80C6}\x{80CC}\x{80CE}\x{80D6}\x{80D9}\x{80DA}\x{80DB}\x{80DD}\x{80DE}' + . '\x{80E1}\x{80E4}\x{80E5}\x{80EF}\x{80F1}\x{80F4}\x{80F8}\x{80FC}\x{80FD}' + . '\x{8102}\x{8105}\x{8106}\x{8107}\x{8108}\x{8109}\x{810A}\x{811A}\x{811B}' + . '\x{8123}\x{8129}\x{812F}\x{8131}\x{8133}\x{8139}\x{813E}\x{8146}\x{814B}' + . '\x{814E}\x{8150}\x{8151}\x{8153}\x{8154}\x{8155}\x{815F}\x{8165}\x{8166}' + . '\x{816B}\x{816E}\x{8170}\x{8171}\x{8174}\x{8178}\x{8179}\x{817A}\x{817F}' + . '\x{8180}\x{8182}\x{8183}\x{8188}\x{818A}\x{818F}\x{8193}\x{8195}\x{819A}' + . '\x{819C}\x{819D}\x{81A0}\x{81A3}\x{81A4}\x{81A8}\x{81A9}\x{81B0}\x{81B3}' + . '\x{81B5}\x{81B8}\x{81BA}\x{81BD}\x{81BE}\x{81BF}\x{81C0}\x{81C2}\x{81C6}' + . '\x{81C8}\x{81C9}\x{81CD}\x{81D1}\x{81D3}\x{81D8}\x{81D9}\x{81DA}\x{81DF}' + . '\x{81E0}\x{81E3}\x{81E5}\x{81E7}\x{81E8}\x{81EA}\x{81ED}\x{81F3}\x{81F4}' + . '\x{81FA}\x{81FB}\x{81FC}\x{81FE}\x{8201}\x{8202}\x{8205}\x{8207}\x{8208}' + . '\x{8209}\x{820A}\x{820C}\x{820D}\x{820E}\x{8210}\x{8212}\x{8216}\x{8217}' + . '\x{8218}\x{821B}\x{821C}\x{821E}\x{821F}\x{8229}\x{822A}\x{822B}\x{822C}' + . '\x{822E}\x{8233}\x{8235}\x{8236}\x{8237}\x{8238}\x{8239}\x{8240}\x{8247}' + . '\x{8258}\x{8259}\x{825A}\x{825D}\x{825F}\x{8262}\x{8264}\x{8266}\x{8268}' + . '\x{826A}\x{826B}\x{826E}\x{826F}\x{8271}\x{8272}\x{8276}\x{8277}\x{8278}' + . '\x{827E}\x{828B}\x{828D}\x{8292}\x{8299}\x{829D}\x{829F}\x{82A5}\x{82A6}' + . '\x{82AB}\x{82AC}\x{82AD}\x{82AF}\x{82B1}\x{82B3}\x{82B8}\x{82B9}\x{82BB}' + . '\x{82BD}\x{82C5}\x{82D1}\x{82D2}\x{82D3}\x{82D4}\x{82D7}\x{82D9}\x{82DB}' + . '\x{82DC}\x{82DE}\x{82DF}\x{82E1}\x{82E3}\x{82E5}\x{82E6}\x{82E7}\x{82EB}' + . '\x{82F1}\x{82F3}\x{82F4}\x{82F9}\x{82FA}\x{82FB}\x{8302}\x{8303}\x{8304}' + . '\x{8305}\x{8306}\x{8309}\x{830E}\x{8316}\x{8317}\x{8318}\x{831C}\x{8323}' + . '\x{8328}\x{832B}\x{832F}\x{8331}\x{8332}\x{8334}\x{8335}\x{8336}\x{8338}' + . '\x{8339}\x{8340}\x{8345}\x{8349}\x{834A}\x{834F}\x{8350}\x{8352}\x{8358}' + . '\x{8373}\x{8375}\x{8377}\x{837B}\x{837C}\x{8385}\x{8387}\x{8389}\x{838A}' + . '\x{838E}\x{8393}\x{8396}\x{839A}\x{839E}\x{839F}\x{83A0}\x{83A2}\x{83A8}' + . '\x{83AA}\x{83AB}\x{83B1}\x{83B5}\x{83BD}\x{83C1}\x{83C5}\x{83CA}\x{83CC}' + . '\x{83CE}\x{83D3}\x{83D6}\x{83D8}\x{83DC}\x{83DF}\x{83E0}\x{83E9}\x{83EB}' + . '\x{83EF}\x{83F0}\x{83F1}\x{83F2}\x{83F4}\x{83F7}\x{83FB}\x{83FD}\x{8403}' + . '\x{8404}\x{8407}\x{840B}\x{840C}\x{840D}\x{840E}\x{8413}\x{8420}\x{8422}' + . '\x{8429}\x{842A}\x{842C}\x{8431}\x{8435}\x{8438}\x{843C}\x{843D}\x{8446}' + . '\x{8449}\x{844E}\x{8457}\x{845B}\x{8461}\x{8462}\x{8463}\x{8466}\x{8469}' + . '\x{846B}\x{846C}\x{846D}\x{846E}\x{846F}\x{8471}\x{8475}\x{8477}\x{8479}' + . '\x{847A}\x{8482}\x{8484}\x{848B}\x{8490}\x{8494}\x{8499}\x{849C}\x{849F}' + . '\x{84A1}\x{84AD}\x{84B2}\x{84B8}\x{84B9}\x{84BB}\x{84BC}\x{84BF}\x{84C1}' + . '\x{84C4}\x{84C6}\x{84C9}\x{84CA}\x{84CB}\x{84CD}\x{84D0}\x{84D1}\x{84D6}' + . '\x{84D9}\x{84DA}\x{84EC}\x{84EE}\x{84F4}\x{84FC}\x{84FF}\x{8500}\x{8506}' + . '\x{8511}\x{8513}\x{8514}\x{8515}\x{8517}\x{8518}\x{851A}\x{851F}\x{8521}' + . '\x{8526}\x{852C}\x{852D}\x{8535}\x{853D}\x{8540}\x{8541}\x{8543}\x{8548}' + . '\x{8549}\x{854A}\x{854B}\x{854E}\x{8555}\x{8557}\x{8558}\x{855A}\x{8563}' + . '\x{8568}\x{8569}\x{856A}\x{856D}\x{8577}\x{857E}\x{8580}\x{8584}\x{8587}' + . '\x{8588}\x{858A}\x{8590}\x{8591}\x{8594}\x{8597}\x{8599}\x{859B}\x{859C}' + . '\x{85A4}\x{85A6}\x{85A8}\x{85A9}\x{85AA}\x{85AB}\x{85AC}\x{85AE}\x{85AF}' + . '\x{85B9}\x{85BA}\x{85C1}\x{85C9}\x{85CD}\x{85CF}\x{85D0}\x{85D5}\x{85DC}' + . '\x{85DD}\x{85E4}\x{85E5}\x{85E9}\x{85EA}\x{85F7}\x{85F9}\x{85FA}\x{85FB}' + . '\x{85FE}\x{8602}\x{8606}\x{8607}\x{860A}\x{860B}\x{8613}\x{8616}\x{8617}' + . '\x{861A}\x{8622}\x{862D}\x{862F}\x{8630}\x{863F}\x{864D}\x{864E}\x{8650}' + . '\x{8654}\x{8655}\x{865A}\x{865C}\x{865E}\x{865F}\x{8667}\x{866B}\x{8671}' + . '\x{8679}\x{867B}\x{868A}\x{868B}\x{868C}\x{8693}\x{8695}\x{86A3}\x{86A4}' + . '\x{86A9}\x{86AA}\x{86AB}\x{86AF}\x{86B0}\x{86B6}\x{86C4}\x{86C6}\x{86C7}' + . '\x{86C9}\x{86CB}\x{86CD}\x{86CE}\x{86D4}\x{86D9}\x{86DB}\x{86DE}\x{86DF}' + . '\x{86E4}\x{86E9}\x{86EC}\x{86ED}\x{86EE}\x{86EF}\x{86F8}\x{86F9}\x{86FB}' + . '\x{86FE}\x{8700}\x{8702}\x{8703}\x{8706}\x{8708}\x{8709}\x{870A}\x{870D}' + . '\x{8711}\x{8712}\x{8718}\x{871A}\x{871C}\x{8725}\x{8729}\x{8734}\x{8737}' + . '\x{873B}\x{873F}\x{8749}\x{874B}\x{874C}\x{874E}\x{8753}\x{8755}\x{8757}' + . '\x{8759}\x{875F}\x{8760}\x{8763}\x{8766}\x{8768}\x{876A}\x{876E}\x{8774}' + . '\x{8776}\x{8778}\x{877F}\x{8782}\x{878D}\x{879F}\x{87A2}\x{87AB}\x{87AF}' + . '\x{87B3}\x{87BA}\x{87BB}\x{87BD}\x{87C0}\x{87C4}\x{87C6}\x{87C7}\x{87CB}' + . '\x{87D0}\x{87D2}\x{87E0}\x{87EF}\x{87F2}\x{87F6}\x{87F7}\x{87F9}\x{87FB}' + . '\x{87FE}\x{8805}\x{880D}\x{880E}\x{880F}\x{8811}\x{8815}\x{8816}\x{8821}' + . '\x{8822}\x{8823}\x{8827}\x{8831}\x{8836}\x{8839}\x{883B}\x{8840}\x{8842}' + . '\x{8844}\x{8846}\x{884C}\x{884D}\x{8852}\x{8853}\x{8857}\x{8859}\x{885B}' + . '\x{885D}\x{885E}\x{8861}\x{8862}\x{8863}\x{8868}\x{886B}\x{8870}\x{8872}' + . '\x{8875}\x{8877}\x{887D}\x{887E}\x{887F}\x{8881}\x{8882}\x{8888}\x{888B}' + . '\x{888D}\x{8892}\x{8896}\x{8897}\x{8899}\x{889E}\x{88A2}\x{88A4}\x{88AB}' + . '\x{88AE}\x{88B0}\x{88B1}\x{88B4}\x{88B5}\x{88B7}\x{88BF}\x{88C1}\x{88C2}' + . '\x{88C3}\x{88C4}\x{88C5}\x{88CF}\x{88D4}\x{88D5}\x{88D8}\x{88D9}\x{88DC}' + . '\x{88DD}\x{88DF}\x{88E1}\x{88E8}\x{88F2}\x{88F3}\x{88F4}\x{88F8}\x{88F9}' + . '\x{88FC}\x{88FD}\x{88FE}\x{8902}\x{8904}\x{8907}\x{890A}\x{890C}\x{8910}' + . '\x{8912}\x{8913}\x{891D}\x{891E}\x{8925}\x{892A}\x{892B}\x{8936}\x{8938}' + . '\x{893B}\x{8941}\x{8943}\x{8944}\x{894C}\x{894D}\x{8956}\x{895E}\x{895F}' + . '\x{8960}\x{8964}\x{8966}\x{896A}\x{896D}\x{896F}\x{8972}\x{8974}\x{8977}' + . '\x{897E}\x{897F}\x{8981}\x{8983}\x{8986}\x{8987}\x{8988}\x{898A}\x{898B}' + . '\x{898F}\x{8993}\x{8996}\x{8997}\x{8998}\x{899A}\x{89A1}\x{89A6}\x{89A7}' + . '\x{89A9}\x{89AA}\x{89AC}\x{89AF}\x{89B2}\x{89B3}\x{89BA}\x{89BD}\x{89BF}' + . '\x{89C0}\x{89D2}\x{89DA}\x{89DC}\x{89DD}\x{89E3}\x{89E6}\x{89E7}\x{89F4}' + . '\x{89F8}\x{8A00}\x{8A02}\x{8A03}\x{8A08}\x{8A0A}\x{8A0C}\x{8A0E}\x{8A10}' + . '\x{8A13}\x{8A16}\x{8A17}\x{8A18}\x{8A1B}\x{8A1D}\x{8A1F}\x{8A23}\x{8A25}' + . '\x{8A2A}\x{8A2D}\x{8A31}\x{8A33}\x{8A34}\x{8A36}\x{8A3A}\x{8A3B}\x{8A3C}' + . '\x{8A41}\x{8A46}\x{8A48}\x{8A50}\x{8A51}\x{8A52}\x{8A54}\x{8A55}\x{8A5B}' + . '\x{8A5E}\x{8A60}\x{8A62}\x{8A63}\x{8A66}\x{8A69}\x{8A6B}\x{8A6C}\x{8A6D}' + . '\x{8A6E}\x{8A70}\x{8A71}\x{8A72}\x{8A73}\x{8A7C}\x{8A82}\x{8A84}\x{8A85}' + . '\x{8A87}\x{8A89}\x{8A8C}\x{8A8D}\x{8A91}\x{8A93}\x{8A95}\x{8A98}\x{8A9A}' + . '\x{8A9E}\x{8AA0}\x{8AA1}\x{8AA3}\x{8AA4}\x{8AA5}\x{8AA6}\x{8AA8}\x{8AAC}' + . '\x{8AAD}\x{8AB0}\x{8AB2}\x{8AB9}\x{8ABC}\x{8ABF}\x{8AC2}\x{8AC4}\x{8AC7}' + . '\x{8ACB}\x{8ACC}\x{8ACD}\x{8ACF}\x{8AD2}\x{8AD6}\x{8ADA}\x{8ADB}\x{8ADC}' + . '\x{8ADE}\x{8AE0}\x{8AE1}\x{8AE2}\x{8AE4}\x{8AE6}\x{8AE7}\x{8AEB}\x{8AED}' + . '\x{8AEE}\x{8AF1}\x{8AF3}\x{8AF7}\x{8AF8}\x{8AFA}\x{8AFE}\x{8B00}\x{8B01}' + . '\x{8B02}\x{8B04}\x{8B07}\x{8B0C}\x{8B0E}\x{8B10}\x{8B14}\x{8B16}\x{8B17}' + . '\x{8B19}\x{8B1A}\x{8B1B}\x{8B1D}\x{8B20}\x{8B21}\x{8B26}\x{8B28}\x{8B2B}' + . '\x{8B2C}\x{8B33}\x{8B39}\x{8B3E}\x{8B41}\x{8B49}\x{8B4C}\x{8B4E}\x{8B4F}' + . '\x{8B56}\x{8B58}\x{8B5A}\x{8B5B}\x{8B5C}\x{8B5F}\x{8B66}\x{8B6B}\x{8B6C}' + . '\x{8B6F}\x{8B70}\x{8B71}\x{8B72}\x{8B74}\x{8B77}\x{8B7D}\x{8B80}\x{8B83}' + . '\x{8B8A}\x{8B8C}\x{8B8E}\x{8B90}\x{8B92}\x{8B93}\x{8B96}\x{8B99}\x{8B9A}' + . '\x{8C37}\x{8C3A}\x{8C3F}\x{8C41}\x{8C46}\x{8C48}\x{8C4A}\x{8C4C}\x{8C4E}' + . '\x{8C50}\x{8C55}\x{8C5A}\x{8C61}\x{8C62}\x{8C6A}\x{8C6B}\x{8C6C}\x{8C78}' + . '\x{8C79}\x{8C7A}\x{8C7C}\x{8C82}\x{8C85}\x{8C89}\x{8C8A}\x{8C8C}\x{8C8D}' + . '\x{8C8E}\x{8C94}\x{8C98}\x{8C9D}\x{8C9E}\x{8CA0}\x{8CA1}\x{8CA2}\x{8CA7}' + . '\x{8CA8}\x{8CA9}\x{8CAA}\x{8CAB}\x{8CAC}\x{8CAD}\x{8CAE}\x{8CAF}\x{8CB0}' + . '\x{8CB2}\x{8CB3}\x{8CB4}\x{8CB6}\x{8CB7}\x{8CB8}\x{8CBB}\x{8CBC}\x{8CBD}' + . '\x{8CBF}\x{8CC0}\x{8CC1}\x{8CC2}\x{8CC3}\x{8CC4}\x{8CC7}\x{8CC8}\x{8CCA}' + . '\x{8CCD}\x{8CCE}\x{8CD1}\x{8CD3}\x{8CDA}\x{8CDB}\x{8CDC}\x{8CDE}\x{8CE0}' + . '\x{8CE2}\x{8CE3}\x{8CE4}\x{8CE6}\x{8CEA}\x{8CED}\x{8CFA}\x{8CFB}\x{8CFC}' + . '\x{8CFD}\x{8D04}\x{8D05}\x{8D07}\x{8D08}\x{8D0A}\x{8D0B}\x{8D0D}\x{8D0F}' + . '\x{8D10}\x{8D13}\x{8D14}\x{8D16}\x{8D64}\x{8D66}\x{8D67}\x{8D6B}\x{8D6D}' + . '\x{8D70}\x{8D71}\x{8D73}\x{8D74}\x{8D77}\x{8D81}\x{8D85}\x{8D8A}\x{8D99}' + . '\x{8DA3}\x{8DA8}\x{8DB3}\x{8DBA}\x{8DBE}\x{8DC2}\x{8DCB}\x{8DCC}\x{8DCF}' + . '\x{8DD6}\x{8DDA}\x{8DDB}\x{8DDD}\x{8DDF}\x{8DE1}\x{8DE3}\x{8DE8}\x{8DEA}' + . '\x{8DEB}\x{8DEF}\x{8DF3}\x{8DF5}\x{8DFC}\x{8DFF}\x{8E08}\x{8E09}\x{8E0A}' + . '\x{8E0F}\x{8E10}\x{8E1D}\x{8E1E}\x{8E1F}\x{8E2A}\x{8E30}\x{8E34}\x{8E35}' + . '\x{8E42}\x{8E44}\x{8E47}\x{8E48}\x{8E49}\x{8E4A}\x{8E4C}\x{8E50}\x{8E55}' + . '\x{8E59}\x{8E5F}\x{8E60}\x{8E63}\x{8E64}\x{8E72}\x{8E74}\x{8E76}\x{8E7C}' + . '\x{8E81}\x{8E84}\x{8E85}\x{8E87}\x{8E8A}\x{8E8B}\x{8E8D}\x{8E91}\x{8E93}' + . '\x{8E94}\x{8E99}\x{8EA1}\x{8EAA}\x{8EAB}\x{8EAC}\x{8EAF}\x{8EB0}\x{8EB1}' + . '\x{8EBE}\x{8EC5}\x{8EC6}\x{8EC8}\x{8ECA}\x{8ECB}\x{8ECC}\x{8ECD}\x{8ED2}' + . '\x{8EDB}\x{8EDF}\x{8EE2}\x{8EE3}\x{8EEB}\x{8EF8}\x{8EFB}\x{8EFC}\x{8EFD}' + . '\x{8EFE}\x{8F03}\x{8F05}\x{8F09}\x{8F0A}\x{8F0C}\x{8F12}\x{8F13}\x{8F14}' + . '\x{8F15}\x{8F19}\x{8F1B}\x{8F1C}\x{8F1D}\x{8F1F}\x{8F26}\x{8F29}\x{8F2A}' + . '\x{8F2F}\x{8F33}\x{8F38}\x{8F39}\x{8F3B}\x{8F3E}\x{8F3F}\x{8F42}\x{8F44}' + . '\x{8F45}\x{8F46}\x{8F49}\x{8F4C}\x{8F4D}\x{8F4E}\x{8F57}\x{8F5C}\x{8F5F}' + . '\x{8F61}\x{8F62}\x{8F63}\x{8F64}\x{8F9B}\x{8F9C}\x{8F9E}\x{8F9F}\x{8FA3}' + . '\x{8FA7}\x{8FA8}\x{8FAD}\x{8FAE}\x{8FAF}\x{8FB0}\x{8FB1}\x{8FB2}\x{8FB7}' + . '\x{8FBA}\x{8FBB}\x{8FBC}\x{8FBF}\x{8FC2}\x{8FC4}\x{8FC5}\x{8FCE}\x{8FD1}' + . '\x{8FD4}\x{8FDA}\x{8FE2}\x{8FE5}\x{8FE6}\x{8FE9}\x{8FEA}\x{8FEB}\x{8FED}' + . '\x{8FEF}\x{8FF0}\x{8FF4}\x{8FF7}\x{8FF8}\x{8FF9}\x{8FFA}\x{8FFD}\x{9000}' + . '\x{9001}\x{9003}\x{9005}\x{9006}\x{900B}\x{900D}\x{900E}\x{900F}\x{9010}' + . '\x{9011}\x{9013}\x{9014}\x{9015}\x{9016}\x{9017}\x{9019}\x{901A}\x{901D}' + . '\x{901E}\x{901F}\x{9020}\x{9021}\x{9022}\x{9023}\x{9027}\x{902E}\x{9031}' + . '\x{9032}\x{9035}\x{9036}\x{9038}\x{9039}\x{903C}\x{903E}\x{9041}\x{9042}' + . '\x{9045}\x{9047}\x{9049}\x{904A}\x{904B}\x{904D}\x{904E}\x{904F}\x{9050}' + . '\x{9051}\x{9052}\x{9053}\x{9054}\x{9055}\x{9056}\x{9058}\x{9059}\x{905C}' + . '\x{905E}\x{9060}\x{9061}\x{9063}\x{9065}\x{9068}\x{9069}\x{906D}\x{906E}' + . '\x{906F}\x{9072}\x{9075}\x{9076}\x{9077}\x{9078}\x{907A}\x{907C}\x{907D}' + . '\x{907F}\x{9080}\x{9081}\x{9082}\x{9083}\x{9084}\x{9087}\x{9089}\x{908A}' + . '\x{908F}\x{9091}\x{90A3}\x{90A6}\x{90A8}\x{90AA}\x{90AF}\x{90B1}\x{90B5}' + . '\x{90B8}\x{90C1}\x{90CA}\x{90CE}\x{90DB}\x{90E1}\x{90E2}\x{90E4}\x{90E8}' + . '\x{90ED}\x{90F5}\x{90F7}\x{90FD}\x{9102}\x{9112}\x{9119}\x{912D}\x{9130}' + . '\x{9132}\x{9149}\x{914A}\x{914B}\x{914C}\x{914D}\x{914E}\x{9152}\x{9154}' + . '\x{9156}\x{9158}\x{9162}\x{9163}\x{9165}\x{9169}\x{916A}\x{916C}\x{9172}' + . '\x{9173}\x{9175}\x{9177}\x{9178}\x{9182}\x{9187}\x{9189}\x{918B}\x{918D}' + . '\x{9190}\x{9192}\x{9197}\x{919C}\x{91A2}\x{91A4}\x{91AA}\x{91AB}\x{91AF}' + . '\x{91B4}\x{91B5}\x{91B8}\x{91BA}\x{91C0}\x{91C1}\x{91C6}\x{91C7}\x{91C8}' + . '\x{91C9}\x{91CB}\x{91CC}\x{91CD}\x{91CE}\x{91CF}\x{91D0}\x{91D1}\x{91D6}' + . '\x{91D8}\x{91DB}\x{91DC}\x{91DD}\x{91DF}\x{91E1}\x{91E3}\x{91E6}\x{91E7}' + . '\x{91F5}\x{91F6}\x{91FC}\x{91FF}\x{920D}\x{920E}\x{9211}\x{9214}\x{9215}' + . '\x{921E}\x{9229}\x{922C}\x{9234}\x{9237}\x{923F}\x{9244}\x{9245}\x{9248}' + . '\x{9249}\x{924B}\x{9250}\x{9257}\x{925A}\x{925B}\x{925E}\x{9262}\x{9264}' + . '\x{9266}\x{9271}\x{927E}\x{9280}\x{9283}\x{9285}\x{9291}\x{9293}\x{9295}' + . '\x{9296}\x{9298}\x{929A}\x{929B}\x{929C}\x{92AD}\x{92B7}\x{92B9}\x{92CF}' + . '\x{92D2}\x{92E4}\x{92E9}\x{92EA}\x{92ED}\x{92F2}\x{92F3}\x{92F8}\x{92FA}' + . '\x{92FC}\x{9306}\x{930F}\x{9310}\x{9318}\x{9319}\x{931A}\x{9320}\x{9322}' + . '\x{9323}\x{9326}\x{9328}\x{932B}\x{932C}\x{932E}\x{932F}\x{9332}\x{9335}' + . '\x{933A}\x{933B}\x{9344}\x{934B}\x{934D}\x{9354}\x{9356}\x{935B}\x{935C}' + . '\x{9360}\x{936C}\x{936E}\x{9375}\x{937C}\x{937E}\x{938C}\x{9394}\x{9396}' + . '\x{9397}\x{939A}\x{93A7}\x{93AC}\x{93AD}\x{93AE}\x{93B0}\x{93B9}\x{93C3}' + . '\x{93C8}\x{93D0}\x{93D1}\x{93D6}\x{93D7}\x{93D8}\x{93DD}\x{93E1}\x{93E4}' + . '\x{93E5}\x{93E8}\x{9403}\x{9407}\x{9410}\x{9413}\x{9414}\x{9418}\x{9419}' + . '\x{941A}\x{9421}\x{942B}\x{9435}\x{9436}\x{9438}\x{943A}\x{9441}\x{9444}' + . '\x{9451}\x{9452}\x{9453}\x{945A}\x{945B}\x{945E}\x{9460}\x{9462}\x{946A}' + . '\x{9470}\x{9475}\x{9477}\x{947C}\x{947D}\x{947E}\x{947F}\x{9481}\x{9577}' + . '\x{9580}\x{9582}\x{9583}\x{9587}\x{9589}\x{958A}\x{958B}\x{958F}\x{9591}' + . '\x{9593}\x{9594}\x{9596}\x{9598}\x{9599}\x{95A0}\x{95A2}\x{95A3}\x{95A4}' + . '\x{95A5}\x{95A7}\x{95A8}\x{95AD}\x{95B2}\x{95B9}\x{95BB}\x{95BC}\x{95BE}' + . '\x{95C3}\x{95C7}\x{95CA}\x{95CC}\x{95CD}\x{95D4}\x{95D5}\x{95D6}\x{95D8}' + . '\x{95DC}\x{95E1}\x{95E2}\x{95E5}\x{961C}\x{9621}\x{9628}\x{962A}\x{962E}' + . '\x{962F}\x{9632}\x{963B}\x{963F}\x{9640}\x{9642}\x{9644}\x{964B}\x{964C}' + . '\x{964D}\x{964F}\x{9650}\x{965B}\x{965C}\x{965D}\x{965E}\x{965F}\x{9662}' + . '\x{9663}\x{9664}\x{9665}\x{9666}\x{966A}\x{966C}\x{9670}\x{9672}\x{9673}' + . '\x{9675}\x{9676}\x{9677}\x{9678}\x{967A}\x{967D}\x{9685}\x{9686}\x{9688}' + . '\x{968A}\x{968B}\x{968D}\x{968E}\x{968F}\x{9694}\x{9695}\x{9697}\x{9698}' + . '\x{9699}\x{969B}\x{969C}\x{96A0}\x{96A3}\x{96A7}\x{96A8}\x{96AA}\x{96B0}' + . '\x{96B1}\x{96B2}\x{96B4}\x{96B6}\x{96B7}\x{96B8}\x{96B9}\x{96BB}\x{96BC}' + . '\x{96C0}\x{96C1}\x{96C4}\x{96C5}\x{96C6}\x{96C7}\x{96C9}\x{96CB}\x{96CC}' + . '\x{96CD}\x{96CE}\x{96D1}\x{96D5}\x{96D6}\x{96D9}\x{96DB}\x{96DC}\x{96E2}' + . '\x{96E3}\x{96E8}\x{96EA}\x{96EB}\x{96F0}\x{96F2}\x{96F6}\x{96F7}\x{96F9}' + . '\x{96FB}\x{9700}\x{9704}\x{9706}\x{9707}\x{9708}\x{970A}\x{970D}\x{970E}' + . '\x{970F}\x{9711}\x{9713}\x{9716}\x{9719}\x{971C}\x{971E}\x{9724}\x{9727}' + . '\x{972A}\x{9730}\x{9732}\x{9738}\x{9739}\x{973D}\x{973E}\x{9742}\x{9744}' + . '\x{9746}\x{9748}\x{9749}\x{9752}\x{9756}\x{9759}\x{975C}\x{975E}\x{9760}' + . '\x{9761}\x{9762}\x{9764}\x{9766}\x{9768}\x{9769}\x{976B}\x{976D}\x{9771}' + . '\x{9774}\x{9779}\x{977A}\x{977C}\x{9781}\x{9784}\x{9785}\x{9786}\x{978B}' + . '\x{978D}\x{978F}\x{9790}\x{9798}\x{979C}\x{97A0}\x{97A3}\x{97A6}\x{97A8}' + . '\x{97AB}\x{97AD}\x{97B3}\x{97B4}\x{97C3}\x{97C6}\x{97C8}\x{97CB}\x{97D3}' + . '\x{97DC}\x{97ED}\x{97EE}\x{97F2}\x{97F3}\x{97F5}\x{97F6}\x{97FB}\x{97FF}' + . '\x{9801}\x{9802}\x{9803}\x{9805}\x{9806}\x{9808}\x{980C}\x{980F}\x{9810}' + . '\x{9811}\x{9812}\x{9813}\x{9817}\x{9818}\x{981A}\x{9821}\x{9824}\x{982C}' + . '\x{982D}\x{9834}\x{9837}\x{9838}\x{983B}\x{983C}\x{983D}\x{9846}\x{984B}' + . '\x{984C}\x{984D}\x{984E}\x{984F}\x{9854}\x{9855}\x{9858}\x{985B}\x{985E}' + . '\x{9867}\x{986B}\x{986F}\x{9870}\x{9871}\x{9873}\x{9874}\x{98A8}\x{98AA}' + . '\x{98AF}\x{98B1}\x{98B6}\x{98C3}\x{98C4}\x{98C6}\x{98DB}\x{98DC}\x{98DF}' + . '\x{98E2}\x{98E9}\x{98EB}\x{98ED}\x{98EE}\x{98EF}\x{98F2}\x{98F4}\x{98FC}' + . '\x{98FD}\x{98FE}\x{9903}\x{9905}\x{9909}\x{990A}\x{990C}\x{9910}\x{9912}' + . '\x{9913}\x{9914}\x{9918}\x{991D}\x{991E}\x{9920}\x{9921}\x{9924}\x{9928}' + . '\x{992C}\x{992E}\x{993D}\x{993E}\x{9942}\x{9945}\x{9949}\x{994B}\x{994C}' + . '\x{9950}\x{9951}\x{9952}\x{9955}\x{9957}\x{9996}\x{9997}\x{9998}\x{9999}' + . '\x{99A5}\x{99A8}\x{99AC}\x{99AD}\x{99AE}\x{99B3}\x{99B4}\x{99BC}\x{99C1}' + . '\x{99C4}\x{99C5}\x{99C6}\x{99C8}\x{99D0}\x{99D1}\x{99D2}\x{99D5}\x{99D8}' + . '\x{99DB}\x{99DD}\x{99DF}\x{99E2}\x{99ED}\x{99EE}\x{99F1}\x{99F2}\x{99F8}' + . '\x{99FB}\x{99FF}\x{9A01}\x{9A05}\x{9A0E}\x{9A0F}\x{9A12}\x{9A13}\x{9A19}' + . '\x{9A28}\x{9A2B}\x{9A30}\x{9A37}\x{9A3E}\x{9A40}\x{9A42}\x{9A43}\x{9A45}' + . '\x{9A4D}\x{9A55}\x{9A57}\x{9A5A}\x{9A5B}\x{9A5F}\x{9A62}\x{9A64}\x{9A65}' + . '\x{9A69}\x{9A6A}\x{9A6B}\x{9AA8}\x{9AAD}\x{9AB0}\x{9AB8}\x{9ABC}\x{9AC0}' + . '\x{9AC4}\x{9ACF}\x{9AD1}\x{9AD3}\x{9AD4}\x{9AD8}\x{9ADE}\x{9ADF}\x{9AE2}' + . '\x{9AE3}\x{9AE6}\x{9AEA}\x{9AEB}\x{9AED}\x{9AEE}\x{9AEF}\x{9AF1}\x{9AF4}' + . '\x{9AF7}\x{9AFB}\x{9B06}\x{9B18}\x{9B1A}\x{9B1F}\x{9B22}\x{9B23}\x{9B25}' + . '\x{9B27}\x{9B28}\x{9B29}\x{9B2A}\x{9B2E}\x{9B2F}\x{9B31}\x{9B32}\x{9B3B}' + . '\x{9B3C}\x{9B41}\x{9B42}\x{9B43}\x{9B44}\x{9B45}\x{9B4D}\x{9B4E}\x{9B4F}' + . '\x{9B51}\x{9B54}\x{9B58}\x{9B5A}\x{9B6F}\x{9B74}\x{9B83}\x{9B8E}\x{9B91}' + . '\x{9B92}\x{9B93}\x{9B96}\x{9B97}\x{9B9F}\x{9BA0}\x{9BA8}\x{9BAA}\x{9BAB}' + . '\x{9BAD}\x{9BAE}\x{9BB4}\x{9BB9}\x{9BC0}\x{9BC6}\x{9BC9}\x{9BCA}\x{9BCF}' + . '\x{9BD1}\x{9BD2}\x{9BD4}\x{9BD6}\x{9BDB}\x{9BE1}\x{9BE2}\x{9BE3}\x{9BE4}' + . '\x{9BE8}\x{9BF0}\x{9BF1}\x{9BF2}\x{9BF5}\x{9C04}\x{9C06}\x{9C08}\x{9C09}' + . '\x{9C0A}\x{9C0C}\x{9C0D}\x{9C10}\x{9C12}\x{9C13}\x{9C14}\x{9C15}\x{9C1B}' + . '\x{9C21}\x{9C24}\x{9C25}\x{9C2D}\x{9C2E}\x{9C2F}\x{9C30}\x{9C32}\x{9C39}' + . '\x{9C3A}\x{9C3B}\x{9C3E}\x{9C46}\x{9C47}\x{9C48}\x{9C52}\x{9C57}\x{9C5A}' + . '\x{9C60}\x{9C67}\x{9C76}\x{9C78}\x{9CE5}\x{9CE7}\x{9CE9}\x{9CEB}\x{9CEC}' + . '\x{9CF0}\x{9CF3}\x{9CF4}\x{9CF6}\x{9D03}\x{9D06}\x{9D07}\x{9D08}\x{9D09}' + . '\x{9D0E}\x{9D12}\x{9D15}\x{9D1B}\x{9D1F}\x{9D23}\x{9D26}\x{9D28}\x{9D2A}' + . '\x{9D2B}\x{9D2C}\x{9D3B}\x{9D3E}\x{9D3F}\x{9D41}\x{9D44}\x{9D46}\x{9D48}' + . '\x{9D50}\x{9D51}\x{9D59}\x{9D5C}\x{9D5D}\x{9D5E}\x{9D60}\x{9D61}\x{9D64}' + . '\x{9D6C}\x{9D6F}\x{9D72}\x{9D7A}\x{9D87}\x{9D89}\x{9D8F}\x{9D9A}\x{9DA4}' + . '\x{9DA9}\x{9DAB}\x{9DAF}\x{9DB2}\x{9DB4}\x{9DB8}\x{9DBA}\x{9DBB}\x{9DC1}' + . '\x{9DC2}\x{9DC4}\x{9DC6}\x{9DCF}\x{9DD3}\x{9DD9}\x{9DE6}\x{9DED}\x{9DEF}' + . '\x{9DF2}\x{9DF8}\x{9DF9}\x{9DFA}\x{9DFD}\x{9E1A}\x{9E1B}\x{9E1E}\x{9E75}' + . '\x{9E78}\x{9E79}\x{9E7D}\x{9E7F}\x{9E81}\x{9E88}\x{9E8B}\x{9E8C}\x{9E91}' + . '\x{9E92}\x{9E93}\x{9E95}\x{9E97}\x{9E9D}\x{9E9F}\x{9EA5}\x{9EA6}\x{9EA9}' + . '\x{9EAA}\x{9EAD}\x{9EB8}\x{9EB9}\x{9EBA}\x{9EBB}\x{9EBC}\x{9EBE}\x{9EBF}' + . '\x{9EC4}\x{9ECC}\x{9ECD}\x{9ECE}\x{9ECF}\x{9ED0}\x{9ED2}\x{9ED4}\x{9ED8}' + . '\x{9ED9}\x{9EDB}\x{9EDC}\x{9EDD}\x{9EDE}\x{9EE0}\x{9EE5}\x{9EE8}\x{9EEF}' + . '\x{9EF4}\x{9EF6}\x{9EF7}\x{9EF9}\x{9EFB}\x{9EFC}\x{9EFD}\x{9F07}\x{9F08}' + . '\x{9F0E}\x{9F13}\x{9F15}\x{9F20}\x{9F21}\x{9F2C}\x{9F3B}\x{9F3E}\x{9F4A}' + . '\x{9F4B}\x{9F4E}\x{9F4F}\x{9F52}\x{9F54}\x{9F5F}\x{9F60}\x{9F61}\x{9F62}' + . '\x{9F63}\x{9F66}\x{9F67}\x{9F6A}\x{9F6C}\x{9F72}\x{9F76}\x{9F77}\x{9F8D}' + . '\x{9F95}\x{9F9C}\x{9F9D}\x{9FA0}]{1,15}$/iu', + 12 => '/^[\x{002d}0-9a-z\x{3447}\x{3473}\x{359E}\x{360E}\x{361A}\x{3918}\x{396E}\x{39CF}\x{39D0}' + . '\x{39DF}\x{3A73}\x{3B4E}\x{3C6E}\x{3CE0}\x{4056}\x{415F}\x{4337}\x{43AC}' + . '\x{43B1}\x{43DD}\x{44D6}\x{464C}\x{4661}\x{4723}\x{4729}\x{477C}\x{478D}' + . '\x{4947}\x{497A}\x{497D}\x{4982}\x{4983}\x{4985}\x{4986}\x{499B}\x{499F}' + . '\x{49B6}\x{49B7}\x{4C77}\x{4C9F}\x{4CA0}\x{4CA1}\x{4CA2}\x{4CA3}\x{4D13}' + . '\x{4D14}\x{4D15}\x{4D16}\x{4D17}\x{4D18}\x{4D19}\x{4DAE}\x{4E00}\x{4E01}' + . '\x{4E02}\x{4E03}\x{4E04}\x{4E05}\x{4E06}\x{4E07}\x{4E08}\x{4E09}\x{4E0A}' + . '\x{4E0B}\x{4E0C}\x{4E0D}\x{4E0E}\x{4E0F}\x{4E10}\x{4E11}\x{4E13}\x{4E14}' + . '\x{4E15}\x{4E16}\x{4E17}\x{4E18}\x{4E19}\x{4E1A}\x{4E1B}\x{4E1C}\x{4E1D}' + . '\x{4E1E}\x{4E1F}\x{4E20}\x{4E21}\x{4E22}\x{4E23}\x{4E24}\x{4E25}\x{4E26}' + . '\x{4E27}\x{4E28}\x{4E2A}\x{4E2B}\x{4E2C}\x{4E2D}\x{4E2E}\x{4E2F}\x{4E30}' + . '\x{4E31}\x{4E32}\x{4E33}\x{4E34}\x{4E35}\x{4E36}\x{4E37}\x{4E38}\x{4E39}' + . '\x{4E3A}\x{4E3B}\x{4E3C}\x{4E3D}\x{4E3E}\x{4E3F}\x{4E40}\x{4E41}\x{4E42}' + . '\x{4E43}\x{4E44}\x{4E45}\x{4E46}\x{4E47}\x{4E48}\x{4E49}\x{4E4A}\x{4E4B}' + . '\x{4E4C}\x{4E4D}\x{4E4E}\x{4E4F}\x{4E50}\x{4E51}\x{4E52}\x{4E53}\x{4E54}' + . '\x{4E56}\x{4E57}\x{4E58}\x{4E59}\x{4E5A}\x{4E5B}\x{4E5C}\x{4E5D}\x{4E5E}' + . '\x{4E5F}\x{4E60}\x{4E61}\x{4E62}\x{4E63}\x{4E64}\x{4E65}\x{4E66}\x{4E67}' + . '\x{4E69}\x{4E6A}\x{4E6B}\x{4E6C}\x{4E6D}\x{4E6E}\x{4E6F}\x{4E70}\x{4E71}' + . '\x{4E72}\x{4E73}\x{4E74}\x{4E75}\x{4E76}\x{4E77}\x{4E78}\x{4E7A}\x{4E7B}' + . '\x{4E7C}\x{4E7D}\x{4E7E}\x{4E7F}\x{4E80}\x{4E81}\x{4E82}\x{4E83}\x{4E84}' + . '\x{4E85}\x{4E86}\x{4E87}\x{4E88}\x{4E89}\x{4E8B}\x{4E8C}\x{4E8D}\x{4E8E}' + . '\x{4E8F}\x{4E90}\x{4E91}\x{4E92}\x{4E93}\x{4E94}\x{4E95}\x{4E97}\x{4E98}' + . '\x{4E99}\x{4E9A}\x{4E9B}\x{4E9C}\x{4E9D}\x{4E9E}\x{4E9F}\x{4EA0}\x{4EA1}' + . '\x{4EA2}\x{4EA4}\x{4EA5}\x{4EA6}\x{4EA7}\x{4EA8}\x{4EA9}\x{4EAA}\x{4EAB}' + . '\x{4EAC}\x{4EAD}\x{4EAE}\x{4EAF}\x{4EB0}\x{4EB1}\x{4EB2}\x{4EB3}\x{4EB4}' + . '\x{4EB5}\x{4EB6}\x{4EB7}\x{4EB8}\x{4EB9}\x{4EBA}\x{4EBB}\x{4EBD}\x{4EBE}' + . '\x{4EBF}\x{4EC0}\x{4EC1}\x{4EC2}\x{4EC3}\x{4EC4}\x{4EC5}\x{4EC6}\x{4EC7}' + . '\x{4EC8}\x{4EC9}\x{4ECA}\x{4ECB}\x{4ECD}\x{4ECE}\x{4ECF}\x{4ED0}\x{4ED1}' + . '\x{4ED2}\x{4ED3}\x{4ED4}\x{4ED5}\x{4ED6}\x{4ED7}\x{4ED8}\x{4ED9}\x{4EDA}' + . '\x{4EDB}\x{4EDC}\x{4EDD}\x{4EDE}\x{4EDF}\x{4EE0}\x{4EE1}\x{4EE2}\x{4EE3}' + . '\x{4EE4}\x{4EE5}\x{4EE6}\x{4EE8}\x{4EE9}\x{4EEA}\x{4EEB}\x{4EEC}\x{4EEF}' + . '\x{4EF0}\x{4EF1}\x{4EF2}\x{4EF3}\x{4EF4}\x{4EF5}\x{4EF6}\x{4EF7}\x{4EFB}' + . '\x{4EFD}\x{4EFF}\x{4F00}\x{4F01}\x{4F02}\x{4F03}\x{4F04}\x{4F05}\x{4F06}' + . '\x{4F08}\x{4F09}\x{4F0A}\x{4F0B}\x{4F0C}\x{4F0D}\x{4F0E}\x{4F0F}\x{4F10}' + . '\x{4F11}\x{4F12}\x{4F13}\x{4F14}\x{4F15}\x{4F17}\x{4F18}\x{4F19}\x{4F1A}' + . '\x{4F1B}\x{4F1C}\x{4F1D}\x{4F1E}\x{4F1F}\x{4F20}\x{4F21}\x{4F22}\x{4F23}' + . '\x{4F24}\x{4F25}\x{4F26}\x{4F27}\x{4F29}\x{4F2A}\x{4F2B}\x{4F2C}\x{4F2D}' + . '\x{4F2E}\x{4F2F}\x{4F30}\x{4F32}\x{4F33}\x{4F34}\x{4F36}\x{4F38}\x{4F39}' + . '\x{4F3A}\x{4F3B}\x{4F3C}\x{4F3D}\x{4F3E}\x{4F3F}\x{4F41}\x{4F42}\x{4F43}' + . '\x{4F45}\x{4F46}\x{4F47}\x{4F48}\x{4F49}\x{4F4A}\x{4F4B}\x{4F4C}\x{4F4D}' + . '\x{4F4E}\x{4F4F}\x{4F50}\x{4F51}\x{4F52}\x{4F53}\x{4F54}\x{4F55}\x{4F56}' + . '\x{4F57}\x{4F58}\x{4F59}\x{4F5A}\x{4F5B}\x{4F5C}\x{4F5D}\x{4F5E}\x{4F5F}' + . '\x{4F60}\x{4F61}\x{4F62}\x{4F63}\x{4F64}\x{4F65}\x{4F66}\x{4F67}\x{4F68}' + . '\x{4F69}\x{4F6A}\x{4F6B}\x{4F6C}\x{4F6D}\x{4F6E}\x{4F6F}\x{4F70}\x{4F72}' + . '\x{4F73}\x{4F74}\x{4F75}\x{4F76}\x{4F77}\x{4F78}\x{4F79}\x{4F7A}\x{4F7B}' + . '\x{4F7C}\x{4F7D}\x{4F7E}\x{4F7F}\x{4F80}\x{4F81}\x{4F82}\x{4F83}\x{4F84}' + . '\x{4F85}\x{4F86}\x{4F87}\x{4F88}\x{4F89}\x{4F8A}\x{4F8B}\x{4F8D}\x{4F8F}' + . '\x{4F90}\x{4F91}\x{4F92}\x{4F93}\x{4F94}\x{4F95}\x{4F96}\x{4F97}\x{4F98}' + . '\x{4F99}\x{4F9A}\x{4F9B}\x{4F9C}\x{4F9D}\x{4F9E}\x{4F9F}\x{4FA0}\x{4FA1}' + . '\x{4FA3}\x{4FA4}\x{4FA5}\x{4FA6}\x{4FA7}\x{4FA8}\x{4FA9}\x{4FAA}\x{4FAB}' + . '\x{4FAC}\x{4FAE}\x{4FAF}\x{4FB0}\x{4FB1}\x{4FB2}\x{4FB3}\x{4FB4}\x{4FB5}' + . '\x{4FB6}\x{4FB7}\x{4FB8}\x{4FB9}\x{4FBA}\x{4FBB}\x{4FBC}\x{4FBE}\x{4FBF}' + . '\x{4FC0}\x{4FC1}\x{4FC2}\x{4FC3}\x{4FC4}\x{4FC5}\x{4FC7}\x{4FC9}\x{4FCA}' + . '\x{4FCB}\x{4FCD}\x{4FCE}\x{4FCF}\x{4FD0}\x{4FD1}\x{4FD2}\x{4FD3}\x{4FD4}' + . '\x{4FD5}\x{4FD6}\x{4FD7}\x{4FD8}\x{4FD9}\x{4FDA}\x{4FDB}\x{4FDC}\x{4FDD}' + . '\x{4FDE}\x{4FDF}\x{4FE0}\x{4FE1}\x{4FE3}\x{4FE4}\x{4FE5}\x{4FE6}\x{4FE7}' + . '\x{4FE8}\x{4FE9}\x{4FEA}\x{4FEB}\x{4FEC}\x{4FED}\x{4FEE}\x{4FEF}\x{4FF0}' + . '\x{4FF1}\x{4FF2}\x{4FF3}\x{4FF4}\x{4FF5}\x{4FF6}\x{4FF7}\x{4FF8}\x{4FF9}' + . '\x{4FFA}\x{4FFB}\x{4FFE}\x{4FFF}\x{5000}\x{5001}\x{5002}\x{5003}\x{5004}' + . '\x{5005}\x{5006}\x{5007}\x{5008}\x{5009}\x{500A}\x{500B}\x{500C}\x{500D}' + . '\x{500E}\x{500F}\x{5011}\x{5012}\x{5013}\x{5014}\x{5015}\x{5016}\x{5017}' + . '\x{5018}\x{5019}\x{501A}\x{501B}\x{501C}\x{501D}\x{501E}\x{501F}\x{5020}' + . '\x{5021}\x{5022}\x{5023}\x{5024}\x{5025}\x{5026}\x{5027}\x{5028}\x{5029}' + . '\x{502A}\x{502B}\x{502C}\x{502D}\x{502E}\x{502F}\x{5030}\x{5031}\x{5032}' + . '\x{5033}\x{5035}\x{5036}\x{5037}\x{5039}\x{503A}\x{503B}\x{503C}\x{503E}' + . '\x{503F}\x{5040}\x{5041}\x{5043}\x{5044}\x{5045}\x{5046}\x{5047}\x{5048}' + . '\x{5049}\x{504A}\x{504B}\x{504C}\x{504D}\x{504E}\x{504F}\x{5051}\x{5053}' + . '\x{5054}\x{5055}\x{5056}\x{5057}\x{5059}\x{505A}\x{505B}\x{505C}\x{505D}' + . '\x{505E}\x{505F}\x{5060}\x{5061}\x{5062}\x{5063}\x{5064}\x{5065}\x{5066}' + . '\x{5067}\x{5068}\x{5069}\x{506A}\x{506B}\x{506C}\x{506D}\x{506E}\x{506F}' + . '\x{5070}\x{5071}\x{5072}\x{5073}\x{5074}\x{5075}\x{5076}\x{5077}\x{5078}' + . '\x{5079}\x{507A}\x{507B}\x{507D}\x{507E}\x{507F}\x{5080}\x{5082}\x{5083}' + . '\x{5084}\x{5085}\x{5086}\x{5087}\x{5088}\x{5089}\x{508A}\x{508B}\x{508C}' + . '\x{508D}\x{508E}\x{508F}\x{5090}\x{5091}\x{5092}\x{5094}\x{5095}\x{5096}' + . '\x{5098}\x{5099}\x{509A}\x{509B}\x{509C}\x{509D}\x{509E}\x{50A2}\x{50A3}' + . '\x{50A4}\x{50A5}\x{50A6}\x{50A7}\x{50A8}\x{50A9}\x{50AA}\x{50AB}\x{50AC}' + . '\x{50AD}\x{50AE}\x{50AF}\x{50B0}\x{50B1}\x{50B2}\x{50B3}\x{50B4}\x{50B5}' + . '\x{50B6}\x{50B7}\x{50B8}\x{50BA}\x{50BB}\x{50BC}\x{50BD}\x{50BE}\x{50BF}' + . '\x{50C0}\x{50C1}\x{50C2}\x{50C4}\x{50C5}\x{50C6}\x{50C7}\x{50C8}\x{50C9}' + . '\x{50CA}\x{50CB}\x{50CC}\x{50CD}\x{50CE}\x{50CF}\x{50D0}\x{50D1}\x{50D2}' + . '\x{50D3}\x{50D4}\x{50D5}\x{50D6}\x{50D7}\x{50D9}\x{50DA}\x{50DB}\x{50DC}' + . '\x{50DD}\x{50DE}\x{50E0}\x{50E3}\x{50E4}\x{50E5}\x{50E6}\x{50E7}\x{50E8}' + . '\x{50E9}\x{50EA}\x{50EC}\x{50ED}\x{50EE}\x{50EF}\x{50F0}\x{50F1}\x{50F2}' + . '\x{50F3}\x{50F5}\x{50F6}\x{50F8}\x{50F9}\x{50FA}\x{50FB}\x{50FC}\x{50FD}' + . '\x{50FE}\x{50FF}\x{5100}\x{5101}\x{5102}\x{5103}\x{5104}\x{5105}\x{5106}' + . '\x{5107}\x{5108}\x{5109}\x{510A}\x{510B}\x{510C}\x{510D}\x{510E}\x{510F}' + . '\x{5110}\x{5111}\x{5112}\x{5113}\x{5114}\x{5115}\x{5116}\x{5117}\x{5118}' + . '\x{5119}\x{511A}\x{511C}\x{511D}\x{511E}\x{511F}\x{5120}\x{5121}\x{5122}' + . '\x{5123}\x{5124}\x{5125}\x{5126}\x{5127}\x{5129}\x{512A}\x{512C}\x{512D}' + . '\x{512E}\x{512F}\x{5130}\x{5131}\x{5132}\x{5133}\x{5134}\x{5135}\x{5136}' + . '\x{5137}\x{5138}\x{5139}\x{513A}\x{513B}\x{513C}\x{513D}\x{513E}\x{513F}' + . '\x{5140}\x{5141}\x{5143}\x{5144}\x{5145}\x{5146}\x{5147}\x{5148}\x{5149}' + . '\x{514B}\x{514C}\x{514D}\x{514E}\x{5150}\x{5151}\x{5152}\x{5154}\x{5155}' + . '\x{5156}\x{5157}\x{5159}\x{515A}\x{515B}\x{515C}\x{515D}\x{515E}\x{515F}' + . '\x{5161}\x{5162}\x{5163}\x{5165}\x{5166}\x{5167}\x{5168}\x{5169}\x{516A}' + . '\x{516B}\x{516C}\x{516D}\x{516E}\x{516F}\x{5170}\x{5171}\x{5173}\x{5174}' + . '\x{5175}\x{5176}\x{5177}\x{5178}\x{5179}\x{517A}\x{517B}\x{517C}\x{517D}' + . '\x{517F}\x{5180}\x{5181}\x{5182}\x{5185}\x{5186}\x{5187}\x{5188}\x{5189}' + . '\x{518A}\x{518B}\x{518C}\x{518D}\x{518F}\x{5190}\x{5191}\x{5192}\x{5193}' + . '\x{5194}\x{5195}\x{5196}\x{5197}\x{5198}\x{5199}\x{519A}\x{519B}\x{519C}' + . '\x{519D}\x{519E}\x{519F}\x{51A0}\x{51A2}\x{51A4}\x{51A5}\x{51A6}\x{51A7}' + . '\x{51A8}\x{51AA}\x{51AB}\x{51AC}\x{51AE}\x{51AF}\x{51B0}\x{51B1}\x{51B2}' + . '\x{51B3}\x{51B5}\x{51B6}\x{51B7}\x{51B9}\x{51BB}\x{51BC}\x{51BD}\x{51BE}' + . '\x{51BF}\x{51C0}\x{51C1}\x{51C3}\x{51C4}\x{51C5}\x{51C6}\x{51C7}\x{51C8}' + . '\x{51C9}\x{51CA}\x{51CB}\x{51CC}\x{51CD}\x{51CE}\x{51CF}\x{51D0}\x{51D1}' + . '\x{51D4}\x{51D5}\x{51D6}\x{51D7}\x{51D8}\x{51D9}\x{51DA}\x{51DB}\x{51DC}' + . '\x{51DD}\x{51DE}\x{51E0}\x{51E1}\x{51E2}\x{51E3}\x{51E4}\x{51E5}\x{51E7}' + . '\x{51E8}\x{51E9}\x{51EA}\x{51EB}\x{51ED}\x{51EF}\x{51F0}\x{51F1}\x{51F3}' + . '\x{51F4}\x{51F5}\x{51F6}\x{51F7}\x{51F8}\x{51F9}\x{51FA}\x{51FB}\x{51FC}' + . '\x{51FD}\x{51FE}\x{51FF}\x{5200}\x{5201}\x{5202}\x{5203}\x{5204}\x{5205}' + . '\x{5206}\x{5207}\x{5208}\x{5209}\x{520A}\x{520B}\x{520C}\x{520D}\x{520E}' + . '\x{520F}\x{5210}\x{5211}\x{5212}\x{5213}\x{5214}\x{5215}\x{5216}\x{5217}' + . '\x{5218}\x{5219}\x{521A}\x{521B}\x{521C}\x{521D}\x{521E}\x{521F}\x{5220}' + . '\x{5221}\x{5222}\x{5223}\x{5224}\x{5225}\x{5226}\x{5228}\x{5229}\x{522A}' + . '\x{522B}\x{522C}\x{522D}\x{522E}\x{522F}\x{5230}\x{5231}\x{5232}\x{5233}' + . '\x{5234}\x{5235}\x{5236}\x{5237}\x{5238}\x{5239}\x{523A}\x{523B}\x{523C}' + . '\x{523D}\x{523E}\x{523F}\x{5240}\x{5241}\x{5242}\x{5243}\x{5244}\x{5245}' + . '\x{5246}\x{5247}\x{5248}\x{5249}\x{524A}\x{524B}\x{524C}\x{524D}\x{524E}' + . '\x{5250}\x{5251}\x{5252}\x{5254}\x{5255}\x{5256}\x{5257}\x{5258}\x{5259}' + . '\x{525A}\x{525B}\x{525C}\x{525D}\x{525E}\x{525F}\x{5260}\x{5261}\x{5262}' + . '\x{5263}\x{5264}\x{5265}\x{5267}\x{5268}\x{5269}\x{526A}\x{526B}\x{526C}' + . '\x{526D}\x{526E}\x{526F}\x{5270}\x{5272}\x{5273}\x{5274}\x{5275}\x{5276}' + . '\x{5277}\x{5278}\x{527A}\x{527B}\x{527C}\x{527D}\x{527E}\x{527F}\x{5280}' + . '\x{5281}\x{5282}\x{5283}\x{5284}\x{5286}\x{5287}\x{5288}\x{5289}\x{528A}' + . '\x{528B}\x{528C}\x{528D}\x{528F}\x{5290}\x{5291}\x{5292}\x{5293}\x{5294}' + . '\x{5295}\x{5296}\x{5297}\x{5298}\x{5299}\x{529A}\x{529B}\x{529C}\x{529D}' + . '\x{529E}\x{529F}\x{52A0}\x{52A1}\x{52A2}\x{52A3}\x{52A5}\x{52A6}\x{52A7}' + . '\x{52A8}\x{52A9}\x{52AA}\x{52AB}\x{52AC}\x{52AD}\x{52AE}\x{52AF}\x{52B0}' + . '\x{52B1}\x{52B2}\x{52B3}\x{52B4}\x{52B5}\x{52B6}\x{52B7}\x{52B8}\x{52B9}' + . '\x{52BA}\x{52BB}\x{52BC}\x{52BD}\x{52BE}\x{52BF}\x{52C0}\x{52C1}\x{52C2}' + . '\x{52C3}\x{52C6}\x{52C7}\x{52C9}\x{52CA}\x{52CB}\x{52CD}\x{52CF}\x{52D0}' + . '\x{52D2}\x{52D3}\x{52D5}\x{52D6}\x{52D7}\x{52D8}\x{52D9}\x{52DA}\x{52DB}' + . '\x{52DC}\x{52DD}\x{52DE}\x{52DF}\x{52E0}\x{52E2}\x{52E3}\x{52E4}\x{52E6}' + . '\x{52E7}\x{52E8}\x{52E9}\x{52EA}\x{52EB}\x{52EC}\x{52ED}\x{52EF}\x{52F0}' + . '\x{52F1}\x{52F2}\x{52F3}\x{52F4}\x{52F5}\x{52F6}\x{52F7}\x{52F8}\x{52F9}' + . '\x{52FA}\x{52FB}\x{52FC}\x{52FD}\x{52FE}\x{52FF}\x{5300}\x{5301}\x{5302}' + . '\x{5305}\x{5306}\x{5307}\x{5308}\x{5309}\x{530A}\x{530B}\x{530C}\x{530D}' + . '\x{530E}\x{530F}\x{5310}\x{5311}\x{5312}\x{5313}\x{5314}\x{5315}\x{5316}' + . '\x{5317}\x{5319}\x{531A}\x{531C}\x{531D}\x{531F}\x{5320}\x{5321}\x{5322}' + . '\x{5323}\x{5324}\x{5325}\x{5326}\x{5328}\x{532A}\x{532B}\x{532C}\x{532D}' + . '\x{532E}\x{532F}\x{5330}\x{5331}\x{5333}\x{5334}\x{5337}\x{5339}\x{533A}' + . '\x{533B}\x{533C}\x{533D}\x{533E}\x{533F}\x{5340}\x{5341}\x{5343}\x{5344}' + . '\x{5345}\x{5346}\x{5347}\x{5348}\x{5349}\x{534A}\x{534B}\x{534C}\x{534D}' + . '\x{534E}\x{534F}\x{5350}\x{5351}\x{5352}\x{5353}\x{5354}\x{5355}\x{5356}' + . '\x{5357}\x{5358}\x{5359}\x{535A}\x{535C}\x{535E}\x{535F}\x{5360}\x{5361}' + . '\x{5362}\x{5363}\x{5364}\x{5365}\x{5366}\x{5367}\x{5369}\x{536B}\x{536C}' + . '\x{536E}\x{536F}\x{5370}\x{5371}\x{5372}\x{5373}\x{5374}\x{5375}\x{5376}' + . '\x{5377}\x{5378}\x{5379}\x{537A}\x{537B}\x{537C}\x{537D}\x{537E}\x{537F}' + . '\x{5381}\x{5382}\x{5383}\x{5384}\x{5385}\x{5386}\x{5387}\x{5388}\x{5389}' + . '\x{538A}\x{538B}\x{538C}\x{538D}\x{538E}\x{538F}\x{5390}\x{5391}\x{5392}' + . '\x{5393}\x{5394}\x{5395}\x{5396}\x{5397}\x{5398}\x{5399}\x{539A}\x{539B}' + . '\x{539C}\x{539D}\x{539E}\x{539F}\x{53A0}\x{53A2}\x{53A3}\x{53A4}\x{53A5}' + . '\x{53A6}\x{53A7}\x{53A8}\x{53A9}\x{53AC}\x{53AD}\x{53AE}\x{53B0}\x{53B1}' + . '\x{53B2}\x{53B3}\x{53B4}\x{53B5}\x{53B6}\x{53B7}\x{53B8}\x{53B9}\x{53BB}' + . '\x{53BC}\x{53BD}\x{53BE}\x{53BF}\x{53C0}\x{53C1}\x{53C2}\x{53C3}\x{53C4}' + . '\x{53C6}\x{53C7}\x{53C8}\x{53C9}\x{53CA}\x{53CB}\x{53CC}\x{53CD}\x{53CE}' + . '\x{53D0}\x{53D1}\x{53D2}\x{53D3}\x{53D4}\x{53D5}\x{53D6}\x{53D7}\x{53D8}' + . '\x{53D9}\x{53DB}\x{53DC}\x{53DF}\x{53E0}\x{53E1}\x{53E2}\x{53E3}\x{53E4}' + . '\x{53E5}\x{53E6}\x{53E8}\x{53E9}\x{53EA}\x{53EB}\x{53EC}\x{53ED}\x{53EE}' + . '\x{53EF}\x{53F0}\x{53F1}\x{53F2}\x{53F3}\x{53F4}\x{53F5}\x{53F6}\x{53F7}' + . '\x{53F8}\x{53F9}\x{53FA}\x{53FB}\x{53FC}\x{53FD}\x{53FE}\x{5401}\x{5402}' + . '\x{5403}\x{5404}\x{5405}\x{5406}\x{5407}\x{5408}\x{5409}\x{540A}\x{540B}' + . '\x{540C}\x{540D}\x{540E}\x{540F}\x{5410}\x{5411}\x{5412}\x{5413}\x{5414}' + . '\x{5415}\x{5416}\x{5417}\x{5418}\x{5419}\x{541B}\x{541C}\x{541D}\x{541E}' + . '\x{541F}\x{5420}\x{5421}\x{5423}\x{5424}\x{5425}\x{5426}\x{5427}\x{5428}' + . '\x{5429}\x{542A}\x{542B}\x{542C}\x{542D}\x{542E}\x{542F}\x{5430}\x{5431}' + . '\x{5432}\x{5433}\x{5434}\x{5435}\x{5436}\x{5437}\x{5438}\x{5439}\x{543A}' + . '\x{543B}\x{543C}\x{543D}\x{543E}\x{543F}\x{5440}\x{5441}\x{5442}\x{5443}' + . '\x{5444}\x{5445}\x{5446}\x{5447}\x{5448}\x{5449}\x{544A}\x{544B}\x{544D}' + . '\x{544E}\x{544F}\x{5450}\x{5451}\x{5452}\x{5453}\x{5454}\x{5455}\x{5456}' + . '\x{5457}\x{5458}\x{5459}\x{545A}\x{545B}\x{545C}\x{545E}\x{545F}\x{5460}' + . '\x{5461}\x{5462}\x{5463}\x{5464}\x{5465}\x{5466}\x{5467}\x{5468}\x{546A}' + . '\x{546B}\x{546C}\x{546D}\x{546E}\x{546F}\x{5470}\x{5471}\x{5472}\x{5473}' + . '\x{5474}\x{5475}\x{5476}\x{5477}\x{5478}\x{5479}\x{547A}\x{547B}\x{547C}' + . '\x{547D}\x{547E}\x{547F}\x{5480}\x{5481}\x{5482}\x{5483}\x{5484}\x{5485}' + . '\x{5486}\x{5487}\x{5488}\x{5489}\x{548B}\x{548C}\x{548D}\x{548E}\x{548F}' + . '\x{5490}\x{5491}\x{5492}\x{5493}\x{5494}\x{5495}\x{5496}\x{5497}\x{5498}' + . '\x{5499}\x{549A}\x{549B}\x{549C}\x{549D}\x{549E}\x{549F}\x{54A0}\x{54A1}' + . '\x{54A2}\x{54A3}\x{54A4}\x{54A5}\x{54A6}\x{54A7}\x{54A8}\x{54A9}\x{54AA}' + . '\x{54AB}\x{54AC}\x{54AD}\x{54AE}\x{54AF}\x{54B0}\x{54B1}\x{54B2}\x{54B3}' + . '\x{54B4}\x{54B6}\x{54B7}\x{54B8}\x{54B9}\x{54BA}\x{54BB}\x{54BC}\x{54BD}' + . '\x{54BE}\x{54BF}\x{54C0}\x{54C1}\x{54C2}\x{54C3}\x{54C4}\x{54C5}\x{54C6}' + . '\x{54C7}\x{54C8}\x{54C9}\x{54CA}\x{54CB}\x{54CC}\x{54CD}\x{54CE}\x{54CF}' + . '\x{54D0}\x{54D1}\x{54D2}\x{54D3}\x{54D4}\x{54D5}\x{54D6}\x{54D7}\x{54D8}' + . '\x{54D9}\x{54DA}\x{54DB}\x{54DC}\x{54DD}\x{54DE}\x{54DF}\x{54E0}\x{54E1}' + . '\x{54E2}\x{54E3}\x{54E4}\x{54E5}\x{54E6}\x{54E7}\x{54E8}\x{54E9}\x{54EA}' + . '\x{54EB}\x{54EC}\x{54ED}\x{54EE}\x{54EF}\x{54F0}\x{54F1}\x{54F2}\x{54F3}' + . '\x{54F4}\x{54F5}\x{54F7}\x{54F8}\x{54F9}\x{54FA}\x{54FB}\x{54FC}\x{54FD}' + . '\x{54FE}\x{54FF}\x{5500}\x{5501}\x{5502}\x{5503}\x{5504}\x{5505}\x{5506}' + . '\x{5507}\x{5508}\x{5509}\x{550A}\x{550B}\x{550C}\x{550D}\x{550E}\x{550F}' + . '\x{5510}\x{5511}\x{5512}\x{5513}\x{5514}\x{5516}\x{5517}\x{551A}\x{551B}' + . '\x{551C}\x{551D}\x{551E}\x{551F}\x{5520}\x{5521}\x{5522}\x{5523}\x{5524}' + . '\x{5525}\x{5526}\x{5527}\x{5528}\x{5529}\x{552A}\x{552B}\x{552C}\x{552D}' + . '\x{552E}\x{552F}\x{5530}\x{5531}\x{5532}\x{5533}\x{5534}\x{5535}\x{5536}' + . '\x{5537}\x{5538}\x{5539}\x{553A}\x{553B}\x{553C}\x{553D}\x{553E}\x{553F}' + . '\x{5540}\x{5541}\x{5542}\x{5543}\x{5544}\x{5545}\x{5546}\x{5548}\x{5549}' + . '\x{554A}\x{554B}\x{554C}\x{554D}\x{554E}\x{554F}\x{5550}\x{5551}\x{5552}' + . '\x{5553}\x{5554}\x{5555}\x{5556}\x{5557}\x{5558}\x{5559}\x{555A}\x{555B}' + . '\x{555C}\x{555D}\x{555E}\x{555F}\x{5561}\x{5562}\x{5563}\x{5564}\x{5565}' + . '\x{5566}\x{5567}\x{5568}\x{5569}\x{556A}\x{556B}\x{556C}\x{556D}\x{556E}' + . '\x{556F}\x{5570}\x{5571}\x{5572}\x{5573}\x{5574}\x{5575}\x{5576}\x{5577}' + . '\x{5578}\x{5579}\x{557B}\x{557C}\x{557D}\x{557E}\x{557F}\x{5580}\x{5581}' + . '\x{5582}\x{5583}\x{5584}\x{5585}\x{5586}\x{5587}\x{5588}\x{5589}\x{558A}' + . '\x{558B}\x{558C}\x{558D}\x{558E}\x{558F}\x{5590}\x{5591}\x{5592}\x{5593}' + . '\x{5594}\x{5595}\x{5596}\x{5597}\x{5598}\x{5599}\x{559A}\x{559B}\x{559C}' + . '\x{559D}\x{559E}\x{559F}\x{55A0}\x{55A1}\x{55A2}\x{55A3}\x{55A4}\x{55A5}' + . '\x{55A6}\x{55A7}\x{55A8}\x{55A9}\x{55AA}\x{55AB}\x{55AC}\x{55AD}\x{55AE}' + . '\x{55AF}\x{55B0}\x{55B1}\x{55B2}\x{55B3}\x{55B4}\x{55B5}\x{55B6}\x{55B7}' + . '\x{55B8}\x{55B9}\x{55BA}\x{55BB}\x{55BC}\x{55BD}\x{55BE}\x{55BF}\x{55C0}' + . '\x{55C1}\x{55C2}\x{55C3}\x{55C4}\x{55C5}\x{55C6}\x{55C7}\x{55C8}\x{55C9}' + . '\x{55CA}\x{55CB}\x{55CC}\x{55CD}\x{55CE}\x{55CF}\x{55D0}\x{55D1}\x{55D2}' + . '\x{55D3}\x{55D4}\x{55D5}\x{55D6}\x{55D7}\x{55D8}\x{55D9}\x{55DA}\x{55DB}' + . '\x{55DC}\x{55DD}\x{55DE}\x{55DF}\x{55E1}\x{55E2}\x{55E3}\x{55E4}\x{55E5}' + . '\x{55E6}\x{55E7}\x{55E8}\x{55E9}\x{55EA}\x{55EB}\x{55EC}\x{55ED}\x{55EE}' + . '\x{55EF}\x{55F0}\x{55F1}\x{55F2}\x{55F3}\x{55F4}\x{55F5}\x{55F6}\x{55F7}' + . '\x{55F9}\x{55FA}\x{55FB}\x{55FC}\x{55FD}\x{55FE}\x{55FF}\x{5600}\x{5601}' + . '\x{5602}\x{5603}\x{5604}\x{5606}\x{5607}\x{5608}\x{5609}\x{560C}\x{560D}' + . '\x{560E}\x{560F}\x{5610}\x{5611}\x{5612}\x{5613}\x{5614}\x{5615}\x{5616}' + . '\x{5617}\x{5618}\x{5619}\x{561A}\x{561B}\x{561C}\x{561D}\x{561E}\x{561F}' + . '\x{5621}\x{5622}\x{5623}\x{5624}\x{5625}\x{5626}\x{5627}\x{5628}\x{5629}' + . '\x{562A}\x{562C}\x{562D}\x{562E}\x{562F}\x{5630}\x{5631}\x{5632}\x{5633}' + . '\x{5634}\x{5635}\x{5636}\x{5638}\x{5639}\x{563A}\x{563B}\x{563D}\x{563E}' + . '\x{563F}\x{5640}\x{5641}\x{5642}\x{5643}\x{5645}\x{5646}\x{5647}\x{5648}' + . '\x{5649}\x{564A}\x{564C}\x{564D}\x{564E}\x{564F}\x{5650}\x{5652}\x{5653}' + . '\x{5654}\x{5655}\x{5657}\x{5658}\x{5659}\x{565A}\x{565B}\x{565C}\x{565D}' + . '\x{565E}\x{5660}\x{5662}\x{5663}\x{5664}\x{5665}\x{5666}\x{5667}\x{5668}' + . '\x{5669}\x{566A}\x{566B}\x{566C}\x{566D}\x{566E}\x{566F}\x{5670}\x{5671}' + . '\x{5672}\x{5673}\x{5674}\x{5676}\x{5677}\x{5678}\x{5679}\x{567A}\x{567B}' + . '\x{567C}\x{567E}\x{567F}\x{5680}\x{5681}\x{5682}\x{5683}\x{5684}\x{5685}' + . '\x{5686}\x{5687}\x{568A}\x{568C}\x{568D}\x{568E}\x{568F}\x{5690}\x{5691}' + . '\x{5692}\x{5693}\x{5694}\x{5695}\x{5697}\x{5698}\x{5699}\x{569A}\x{569B}' + . '\x{569C}\x{569D}\x{569F}\x{56A0}\x{56A1}\x{56A3}\x{56A4}\x{56A5}\x{56A6}' + . '\x{56A7}\x{56A8}\x{56A9}\x{56AA}\x{56AB}\x{56AC}\x{56AD}\x{56AE}\x{56AF}' + . '\x{56B0}\x{56B1}\x{56B2}\x{56B3}\x{56B4}\x{56B5}\x{56B6}\x{56B7}\x{56B8}' + . '\x{56B9}\x{56BB}\x{56BC}\x{56BD}\x{56BE}\x{56BF}\x{56C0}\x{56C1}\x{56C2}' + . '\x{56C3}\x{56C4}\x{56C5}\x{56C6}\x{56C7}\x{56C8}\x{56C9}\x{56CA}\x{56CB}' + . '\x{56CC}\x{56CD}\x{56CE}\x{56D0}\x{56D1}\x{56D2}\x{56D3}\x{56D4}\x{56D5}' + . '\x{56D6}\x{56D7}\x{56D8}\x{56DA}\x{56DB}\x{56DC}\x{56DD}\x{56DE}\x{56DF}' + . '\x{56E0}\x{56E1}\x{56E2}\x{56E3}\x{56E4}\x{56E5}\x{56E7}\x{56E8}\x{56E9}' + . '\x{56EA}\x{56EB}\x{56EC}\x{56ED}\x{56EE}\x{56EF}\x{56F0}\x{56F1}\x{56F2}' + . '\x{56F3}\x{56F4}\x{56F5}\x{56F7}\x{56F9}\x{56FA}\x{56FD}\x{56FE}\x{56FF}' + . '\x{5700}\x{5701}\x{5702}\x{5703}\x{5704}\x{5706}\x{5707}\x{5708}\x{5709}' + . '\x{570A}\x{570B}\x{570C}\x{570D}\x{570E}\x{570F}\x{5710}\x{5712}\x{5713}' + . '\x{5714}\x{5715}\x{5716}\x{5718}\x{5719}\x{571A}\x{571B}\x{571C}\x{571D}' + . '\x{571E}\x{571F}\x{5720}\x{5722}\x{5723}\x{5725}\x{5726}\x{5727}\x{5728}' + . '\x{5729}\x{572A}\x{572B}\x{572C}\x{572D}\x{572E}\x{572F}\x{5730}\x{5731}' + . '\x{5732}\x{5733}\x{5734}\x{5735}\x{5736}\x{5737}\x{5738}\x{5739}\x{573A}' + . '\x{573B}\x{573C}\x{573E}\x{573F}\x{5740}\x{5741}\x{5742}\x{5744}\x{5745}' + . '\x{5746}\x{5747}\x{5749}\x{574A}\x{574B}\x{574C}\x{574D}\x{574E}\x{574F}' + . '\x{5750}\x{5751}\x{5752}\x{5753}\x{5754}\x{5757}\x{5759}\x{575A}\x{575B}' + . '\x{575C}\x{575D}\x{575E}\x{575F}\x{5760}\x{5761}\x{5762}\x{5764}\x{5765}' + . '\x{5766}\x{5767}\x{5768}\x{5769}\x{576A}\x{576B}\x{576C}\x{576D}\x{576F}' + . '\x{5770}\x{5771}\x{5772}\x{5773}\x{5774}\x{5775}\x{5776}\x{5777}\x{5779}' + . '\x{577A}\x{577B}\x{577C}\x{577D}\x{577E}\x{577F}\x{5780}\x{5782}\x{5783}' + . '\x{5784}\x{5785}\x{5786}\x{5788}\x{5789}\x{578A}\x{578B}\x{578C}\x{578D}' + . '\x{578E}\x{578F}\x{5790}\x{5791}\x{5792}\x{5793}\x{5794}\x{5795}\x{5797}' + . '\x{5798}\x{5799}\x{579A}\x{579B}\x{579C}\x{579D}\x{579E}\x{579F}\x{57A0}' + . '\x{57A1}\x{57A2}\x{57A3}\x{57A4}\x{57A5}\x{57A6}\x{57A7}\x{57A9}\x{57AA}' + . '\x{57AB}\x{57AC}\x{57AD}\x{57AE}\x{57AF}\x{57B0}\x{57B1}\x{57B2}\x{57B3}' + . '\x{57B4}\x{57B5}\x{57B6}\x{57B7}\x{57B8}\x{57B9}\x{57BA}\x{57BB}\x{57BC}' + . '\x{57BD}\x{57BE}\x{57BF}\x{57C0}\x{57C1}\x{57C2}\x{57C3}\x{57C4}\x{57C5}' + . '\x{57C6}\x{57C7}\x{57C8}\x{57C9}\x{57CB}\x{57CC}\x{57CD}\x{57CE}\x{57CF}' + . '\x{57D0}\x{57D2}\x{57D3}\x{57D4}\x{57D5}\x{57D6}\x{57D8}\x{57D9}\x{57DA}' + . '\x{57DC}\x{57DD}\x{57DF}\x{57E0}\x{57E1}\x{57E2}\x{57E3}\x{57E4}\x{57E5}' + . '\x{57E6}\x{57E7}\x{57E8}\x{57E9}\x{57EA}\x{57EB}\x{57EC}\x{57ED}\x{57EE}' + . '\x{57EF}\x{57F0}\x{57F1}\x{57F2}\x{57F3}\x{57F4}\x{57F5}\x{57F6}\x{57F7}' + . '\x{57F8}\x{57F9}\x{57FA}\x{57FB}\x{57FC}\x{57FD}\x{57FE}\x{57FF}\x{5800}' + . '\x{5801}\x{5802}\x{5803}\x{5804}\x{5805}\x{5806}\x{5807}\x{5808}\x{5809}' + . '\x{580A}\x{580B}\x{580C}\x{580D}\x{580E}\x{580F}\x{5810}\x{5811}\x{5812}' + . '\x{5813}\x{5814}\x{5815}\x{5816}\x{5819}\x{581A}\x{581B}\x{581C}\x{581D}' + . '\x{581E}\x{581F}\x{5820}\x{5821}\x{5822}\x{5823}\x{5824}\x{5825}\x{5826}' + . '\x{5827}\x{5828}\x{5829}\x{582A}\x{582B}\x{582C}\x{582D}\x{582E}\x{582F}' + . '\x{5830}\x{5831}\x{5832}\x{5833}\x{5834}\x{5835}\x{5836}\x{5837}\x{5838}' + . '\x{5839}\x{583A}\x{583B}\x{583C}\x{583D}\x{583E}\x{583F}\x{5840}\x{5842}' + . '\x{5843}\x{5844}\x{5845}\x{5846}\x{5847}\x{5848}\x{5849}\x{584A}\x{584B}' + . '\x{584C}\x{584D}\x{584E}\x{584F}\x{5851}\x{5852}\x{5853}\x{5854}\x{5855}' + . '\x{5857}\x{5858}\x{5859}\x{585A}\x{585B}\x{585C}\x{585D}\x{585E}\x{585F}' + . '\x{5861}\x{5862}\x{5863}\x{5864}\x{5865}\x{5868}\x{5869}\x{586A}\x{586B}' + . '\x{586C}\x{586D}\x{586E}\x{586F}\x{5870}\x{5871}\x{5872}\x{5873}\x{5874}' + . '\x{5875}\x{5876}\x{5878}\x{5879}\x{587A}\x{587B}\x{587C}\x{587D}\x{587E}' + . '\x{587F}\x{5880}\x{5881}\x{5882}\x{5883}\x{5884}\x{5885}\x{5886}\x{5887}' + . '\x{5888}\x{5889}\x{588A}\x{588B}\x{588C}\x{588D}\x{588E}\x{588F}\x{5890}' + . '\x{5891}\x{5892}\x{5893}\x{5894}\x{5896}\x{5897}\x{5898}\x{5899}\x{589A}' + . '\x{589B}\x{589C}\x{589D}\x{589E}\x{589F}\x{58A0}\x{58A1}\x{58A2}\x{58A3}' + . '\x{58A4}\x{58A5}\x{58A6}\x{58A7}\x{58A8}\x{58A9}\x{58AB}\x{58AC}\x{58AD}' + . '\x{58AE}\x{58AF}\x{58B0}\x{58B1}\x{58B2}\x{58B3}\x{58B4}\x{58B7}\x{58B8}' + . '\x{58B9}\x{58BA}\x{58BB}\x{58BC}\x{58BD}\x{58BE}\x{58BF}\x{58C1}\x{58C2}' + . '\x{58C5}\x{58C6}\x{58C7}\x{58C8}\x{58C9}\x{58CA}\x{58CB}\x{58CE}\x{58CF}' + . '\x{58D1}\x{58D2}\x{58D3}\x{58D4}\x{58D5}\x{58D6}\x{58D7}\x{58D8}\x{58D9}' + . '\x{58DA}\x{58DB}\x{58DD}\x{58DE}\x{58DF}\x{58E0}\x{58E2}\x{58E3}\x{58E4}' + . '\x{58E5}\x{58E7}\x{58E8}\x{58E9}\x{58EA}\x{58EB}\x{58EC}\x{58ED}\x{58EE}' + . '\x{58EF}\x{58F0}\x{58F1}\x{58F2}\x{58F3}\x{58F4}\x{58F6}\x{58F7}\x{58F8}' + . '\x{58F9}\x{58FA}\x{58FB}\x{58FC}\x{58FD}\x{58FE}\x{58FF}\x{5900}\x{5902}' + . '\x{5903}\x{5904}\x{5906}\x{5907}\x{5909}\x{590A}\x{590B}\x{590C}\x{590D}' + . '\x{590E}\x{590F}\x{5910}\x{5912}\x{5914}\x{5915}\x{5916}\x{5917}\x{5918}' + . '\x{5919}\x{591A}\x{591B}\x{591C}\x{591D}\x{591E}\x{591F}\x{5920}\x{5921}' + . '\x{5922}\x{5924}\x{5925}\x{5926}\x{5927}\x{5928}\x{5929}\x{592A}\x{592B}' + . '\x{592C}\x{592D}\x{592E}\x{592F}\x{5930}\x{5931}\x{5932}\x{5934}\x{5935}' + . '\x{5937}\x{5938}\x{5939}\x{593A}\x{593B}\x{593C}\x{593D}\x{593E}\x{593F}' + . '\x{5940}\x{5941}\x{5942}\x{5943}\x{5944}\x{5945}\x{5946}\x{5947}\x{5948}' + . '\x{5949}\x{594A}\x{594B}\x{594C}\x{594D}\x{594E}\x{594F}\x{5950}\x{5951}' + . '\x{5952}\x{5953}\x{5954}\x{5955}\x{5956}\x{5957}\x{5958}\x{595A}\x{595C}' + . '\x{595D}\x{595E}\x{595F}\x{5960}\x{5961}\x{5962}\x{5963}\x{5964}\x{5965}' + . '\x{5966}\x{5967}\x{5968}\x{5969}\x{596A}\x{596B}\x{596C}\x{596D}\x{596E}' + . '\x{596F}\x{5970}\x{5971}\x{5972}\x{5973}\x{5974}\x{5975}\x{5976}\x{5977}' + . '\x{5978}\x{5979}\x{597A}\x{597B}\x{597C}\x{597D}\x{597E}\x{597F}\x{5980}' + . '\x{5981}\x{5982}\x{5983}\x{5984}\x{5985}\x{5986}\x{5987}\x{5988}\x{5989}' + . '\x{598A}\x{598B}\x{598C}\x{598D}\x{598E}\x{598F}\x{5990}\x{5991}\x{5992}' + . '\x{5993}\x{5994}\x{5995}\x{5996}\x{5997}\x{5998}\x{5999}\x{599A}\x{599C}' + . '\x{599D}\x{599E}\x{599F}\x{59A0}\x{59A1}\x{59A2}\x{59A3}\x{59A4}\x{59A5}' + . '\x{59A6}\x{59A7}\x{59A8}\x{59A9}\x{59AA}\x{59AB}\x{59AC}\x{59AD}\x{59AE}' + . '\x{59AF}\x{59B0}\x{59B1}\x{59B2}\x{59B3}\x{59B4}\x{59B5}\x{59B6}\x{59B8}' + . '\x{59B9}\x{59BA}\x{59BB}\x{59BC}\x{59BD}\x{59BE}\x{59BF}\x{59C0}\x{59C1}' + . '\x{59C2}\x{59C3}\x{59C4}\x{59C5}\x{59C6}\x{59C7}\x{59C8}\x{59C9}\x{59CA}' + . '\x{59CB}\x{59CC}\x{59CD}\x{59CE}\x{59CF}\x{59D0}\x{59D1}\x{59D2}\x{59D3}' + . '\x{59D4}\x{59D5}\x{59D6}\x{59D7}\x{59D8}\x{59D9}\x{59DA}\x{59DB}\x{59DC}' + . '\x{59DD}\x{59DE}\x{59DF}\x{59E0}\x{59E1}\x{59E2}\x{59E3}\x{59E4}\x{59E5}' + . '\x{59E6}\x{59E8}\x{59E9}\x{59EA}\x{59EB}\x{59EC}\x{59ED}\x{59EE}\x{59EF}' + . '\x{59F0}\x{59F1}\x{59F2}\x{59F3}\x{59F4}\x{59F5}\x{59F6}\x{59F7}\x{59F8}' + . '\x{59F9}\x{59FA}\x{59FB}\x{59FC}\x{59FD}\x{59FE}\x{59FF}\x{5A00}\x{5A01}' + . '\x{5A02}\x{5A03}\x{5A04}\x{5A05}\x{5A06}\x{5A07}\x{5A08}\x{5A09}\x{5A0A}' + . '\x{5A0B}\x{5A0C}\x{5A0D}\x{5A0E}\x{5A0F}\x{5A10}\x{5A11}\x{5A12}\x{5A13}' + . '\x{5A14}\x{5A15}\x{5A16}\x{5A17}\x{5A18}\x{5A19}\x{5A1A}\x{5A1B}\x{5A1C}' + . '\x{5A1D}\x{5A1E}\x{5A1F}\x{5A20}\x{5A21}\x{5A22}\x{5A23}\x{5A25}\x{5A27}' + . '\x{5A28}\x{5A29}\x{5A2A}\x{5A2B}\x{5A2D}\x{5A2E}\x{5A2F}\x{5A31}\x{5A32}' + . '\x{5A33}\x{5A34}\x{5A35}\x{5A36}\x{5A37}\x{5A38}\x{5A39}\x{5A3A}\x{5A3B}' + . '\x{5A3C}\x{5A3D}\x{5A3E}\x{5A3F}\x{5A40}\x{5A41}\x{5A42}\x{5A43}\x{5A44}' + . '\x{5A45}\x{5A46}\x{5A47}\x{5A48}\x{5A49}\x{5A4A}\x{5A4B}\x{5A4C}\x{5A4D}' + . '\x{5A4E}\x{5A4F}\x{5A50}\x{5A51}\x{5A52}\x{5A53}\x{5A55}\x{5A56}\x{5A57}' + . '\x{5A58}\x{5A5A}\x{5A5B}\x{5A5C}\x{5A5D}\x{5A5E}\x{5A5F}\x{5A60}\x{5A61}' + . '\x{5A62}\x{5A63}\x{5A64}\x{5A65}\x{5A66}\x{5A67}\x{5A68}\x{5A69}\x{5A6A}' + . '\x{5A6B}\x{5A6C}\x{5A6D}\x{5A6E}\x{5A70}\x{5A72}\x{5A73}\x{5A74}\x{5A75}' + . '\x{5A76}\x{5A77}\x{5A78}\x{5A79}\x{5A7A}\x{5A7B}\x{5A7C}\x{5A7D}\x{5A7E}' + . '\x{5A7F}\x{5A80}\x{5A81}\x{5A82}\x{5A83}\x{5A84}\x{5A85}\x{5A86}\x{5A88}' + . '\x{5A89}\x{5A8A}\x{5A8B}\x{5A8C}\x{5A8E}\x{5A8F}\x{5A90}\x{5A91}\x{5A92}' + . '\x{5A93}\x{5A94}\x{5A95}\x{5A96}\x{5A97}\x{5A98}\x{5A99}\x{5A9A}\x{5A9B}' + . '\x{5A9C}\x{5A9D}\x{5A9E}\x{5A9F}\x{5AA0}\x{5AA1}\x{5AA2}\x{5AA3}\x{5AA4}' + . '\x{5AA5}\x{5AA6}\x{5AA7}\x{5AA8}\x{5AA9}\x{5AAA}\x{5AAC}\x{5AAD}\x{5AAE}' + . '\x{5AAF}\x{5AB0}\x{5AB1}\x{5AB2}\x{5AB3}\x{5AB4}\x{5AB5}\x{5AB6}\x{5AB7}' + . '\x{5AB8}\x{5AB9}\x{5ABA}\x{5ABB}\x{5ABC}\x{5ABD}\x{5ABE}\x{5ABF}\x{5AC0}' + . '\x{5AC1}\x{5AC2}\x{5AC3}\x{5AC4}\x{5AC5}\x{5AC6}\x{5AC7}\x{5AC8}\x{5AC9}' + . '\x{5ACA}\x{5ACB}\x{5ACC}\x{5ACD}\x{5ACE}\x{5ACF}\x{5AD1}\x{5AD2}\x{5AD4}' + . '\x{5AD5}\x{5AD6}\x{5AD7}\x{5AD8}\x{5AD9}\x{5ADA}\x{5ADB}\x{5ADC}\x{5ADD}' + . '\x{5ADE}\x{5ADF}\x{5AE0}\x{5AE1}\x{5AE2}\x{5AE3}\x{5AE4}\x{5AE5}\x{5AE6}' + . '\x{5AE7}\x{5AE8}\x{5AE9}\x{5AEA}\x{5AEB}\x{5AEC}\x{5AED}\x{5AEE}\x{5AF1}' + . '\x{5AF2}\x{5AF3}\x{5AF4}\x{5AF5}\x{5AF6}\x{5AF7}\x{5AF8}\x{5AF9}\x{5AFA}' + . '\x{5AFB}\x{5AFC}\x{5AFD}\x{5AFE}\x{5AFF}\x{5B00}\x{5B01}\x{5B02}\x{5B03}' + . '\x{5B04}\x{5B05}\x{5B06}\x{5B07}\x{5B08}\x{5B09}\x{5B0B}\x{5B0C}\x{5B0E}' + . '\x{5B0F}\x{5B10}\x{5B11}\x{5B12}\x{5B13}\x{5B14}\x{5B15}\x{5B16}\x{5B17}' + . '\x{5B18}\x{5B19}\x{5B1A}\x{5B1B}\x{5B1C}\x{5B1D}\x{5B1E}\x{5B1F}\x{5B20}' + . '\x{5B21}\x{5B22}\x{5B23}\x{5B24}\x{5B25}\x{5B26}\x{5B27}\x{5B28}\x{5B29}' + . '\x{5B2A}\x{5B2B}\x{5B2C}\x{5B2D}\x{5B2E}\x{5B2F}\x{5B30}\x{5B31}\x{5B32}' + . '\x{5B33}\x{5B34}\x{5B35}\x{5B36}\x{5B37}\x{5B38}\x{5B3A}\x{5B3B}\x{5B3C}' + . '\x{5B3D}\x{5B3E}\x{5B3F}\x{5B40}\x{5B41}\x{5B42}\x{5B43}\x{5B44}\x{5B45}' + . '\x{5B47}\x{5B48}\x{5B49}\x{5B4A}\x{5B4B}\x{5B4C}\x{5B4D}\x{5B4E}\x{5B50}' + . '\x{5B51}\x{5B53}\x{5B54}\x{5B55}\x{5B56}\x{5B57}\x{5B58}\x{5B59}\x{5B5A}' + . '\x{5B5B}\x{5B5C}\x{5B5D}\x{5B5E}\x{5B5F}\x{5B62}\x{5B63}\x{5B64}\x{5B65}' + . '\x{5B66}\x{5B67}\x{5B68}\x{5B69}\x{5B6A}\x{5B6B}\x{5B6C}\x{5B6D}\x{5B6E}' + . '\x{5B70}\x{5B71}\x{5B72}\x{5B73}\x{5B74}\x{5B75}\x{5B76}\x{5B77}\x{5B78}' + . '\x{5B7A}\x{5B7B}\x{5B7C}\x{5B7D}\x{5B7F}\x{5B80}\x{5B81}\x{5B82}\x{5B83}' + . '\x{5B84}\x{5B85}\x{5B87}\x{5B88}\x{5B89}\x{5B8A}\x{5B8B}\x{5B8C}\x{5B8D}' + . '\x{5B8E}\x{5B8F}\x{5B91}\x{5B92}\x{5B93}\x{5B94}\x{5B95}\x{5B96}\x{5B97}' + . '\x{5B98}\x{5B99}\x{5B9A}\x{5B9B}\x{5B9C}\x{5B9D}\x{5B9E}\x{5B9F}\x{5BA0}' + . '\x{5BA1}\x{5BA2}\x{5BA3}\x{5BA4}\x{5BA5}\x{5BA6}\x{5BA7}\x{5BA8}\x{5BAA}' + . '\x{5BAB}\x{5BAC}\x{5BAD}\x{5BAE}\x{5BAF}\x{5BB0}\x{5BB1}\x{5BB3}\x{5BB4}' + . '\x{5BB5}\x{5BB6}\x{5BB8}\x{5BB9}\x{5BBA}\x{5BBB}\x{5BBD}\x{5BBE}\x{5BBF}' + . '\x{5BC0}\x{5BC1}\x{5BC2}\x{5BC3}\x{5BC4}\x{5BC5}\x{5BC6}\x{5BC7}\x{5BCA}' + . '\x{5BCB}\x{5BCC}\x{5BCD}\x{5BCE}\x{5BCF}\x{5BD0}\x{5BD1}\x{5BD2}\x{5BD3}' + . '\x{5BD4}\x{5BD5}\x{5BD6}\x{5BD8}\x{5BD9}\x{5BDB}\x{5BDC}\x{5BDD}\x{5BDE}' + . '\x{5BDF}\x{5BE0}\x{5BE1}\x{5BE2}\x{5BE3}\x{5BE4}\x{5BE5}\x{5BE6}\x{5BE7}' + . '\x{5BE8}\x{5BE9}\x{5BEA}\x{5BEB}\x{5BEC}\x{5BED}\x{5BEE}\x{5BEF}\x{5BF0}' + . '\x{5BF1}\x{5BF2}\x{5BF3}\x{5BF4}\x{5BF5}\x{5BF6}\x{5BF7}\x{5BF8}\x{5BF9}' + . '\x{5BFA}\x{5BFB}\x{5BFC}\x{5BFD}\x{5BFF}\x{5C01}\x{5C03}\x{5C04}\x{5C05}' + . '\x{5C06}\x{5C07}\x{5C08}\x{5C09}\x{5C0A}\x{5C0B}\x{5C0C}\x{5C0D}\x{5C0E}' + . '\x{5C0F}\x{5C10}\x{5C11}\x{5C12}\x{5C13}\x{5C14}\x{5C15}\x{5C16}\x{5C17}' + . '\x{5C18}\x{5C19}\x{5C1A}\x{5C1C}\x{5C1D}\x{5C1E}\x{5C1F}\x{5C20}\x{5C21}' + . '\x{5C22}\x{5C24}\x{5C25}\x{5C27}\x{5C28}\x{5C2A}\x{5C2B}\x{5C2C}\x{5C2D}' + . '\x{5C2E}\x{5C2F}\x{5C30}\x{5C31}\x{5C32}\x{5C33}\x{5C34}\x{5C35}\x{5C37}' + . '\x{5C38}\x{5C39}\x{5C3A}\x{5C3B}\x{5C3C}\x{5C3D}\x{5C3E}\x{5C3F}\x{5C40}' + . '\x{5C41}\x{5C42}\x{5C43}\x{5C44}\x{5C45}\x{5C46}\x{5C47}\x{5C48}\x{5C49}' + . '\x{5C4A}\x{5C4B}\x{5C4C}\x{5C4D}\x{5C4E}\x{5C4F}\x{5C50}\x{5C51}\x{5C52}' + . '\x{5C53}\x{5C54}\x{5C55}\x{5C56}\x{5C57}\x{5C58}\x{5C59}\x{5C5B}\x{5C5C}' + . '\x{5C5D}\x{5C5E}\x{5C5F}\x{5C60}\x{5C61}\x{5C62}\x{5C63}\x{5C64}\x{5C65}' + . '\x{5C66}\x{5C67}\x{5C68}\x{5C69}\x{5C6A}\x{5C6B}\x{5C6C}\x{5C6D}\x{5C6E}' + . '\x{5C6F}\x{5C70}\x{5C71}\x{5C72}\x{5C73}\x{5C74}\x{5C75}\x{5C76}\x{5C77}' + . '\x{5C78}\x{5C79}\x{5C7A}\x{5C7B}\x{5C7C}\x{5C7D}\x{5C7E}\x{5C7F}\x{5C80}' + . '\x{5C81}\x{5C82}\x{5C83}\x{5C84}\x{5C86}\x{5C87}\x{5C88}\x{5C89}\x{5C8A}' + . '\x{5C8B}\x{5C8C}\x{5C8D}\x{5C8E}\x{5C8F}\x{5C90}\x{5C91}\x{5C92}\x{5C93}' + . '\x{5C94}\x{5C95}\x{5C96}\x{5C97}\x{5C98}\x{5C99}\x{5C9A}\x{5C9B}\x{5C9C}' + . '\x{5C9D}\x{5C9E}\x{5C9F}\x{5CA0}\x{5CA1}\x{5CA2}\x{5CA3}\x{5CA4}\x{5CA5}' + . '\x{5CA6}\x{5CA7}\x{5CA8}\x{5CA9}\x{5CAA}\x{5CAB}\x{5CAC}\x{5CAD}\x{5CAE}' + . '\x{5CAF}\x{5CB0}\x{5CB1}\x{5CB2}\x{5CB3}\x{5CB5}\x{5CB6}\x{5CB7}\x{5CB8}' + . '\x{5CBA}\x{5CBB}\x{5CBC}\x{5CBD}\x{5CBE}\x{5CBF}\x{5CC1}\x{5CC2}\x{5CC3}' + . '\x{5CC4}\x{5CC5}\x{5CC6}\x{5CC7}\x{5CC8}\x{5CC9}\x{5CCA}\x{5CCB}\x{5CCC}' + . '\x{5CCD}\x{5CCE}\x{5CCF}\x{5CD0}\x{5CD1}\x{5CD2}\x{5CD3}\x{5CD4}\x{5CD6}' + . '\x{5CD7}\x{5CD8}\x{5CD9}\x{5CDA}\x{5CDB}\x{5CDC}\x{5CDE}\x{5CDF}\x{5CE0}' + . '\x{5CE1}\x{5CE2}\x{5CE3}\x{5CE4}\x{5CE5}\x{5CE6}\x{5CE7}\x{5CE8}\x{5CE9}' + . '\x{5CEA}\x{5CEB}\x{5CEC}\x{5CED}\x{5CEE}\x{5CEF}\x{5CF0}\x{5CF1}\x{5CF2}' + . '\x{5CF3}\x{5CF4}\x{5CF6}\x{5CF7}\x{5CF8}\x{5CF9}\x{5CFA}\x{5CFB}\x{5CFC}' + . '\x{5CFD}\x{5CFE}\x{5CFF}\x{5D00}\x{5D01}\x{5D02}\x{5D03}\x{5D04}\x{5D05}' + . '\x{5D06}\x{5D07}\x{5D08}\x{5D09}\x{5D0A}\x{5D0B}\x{5D0C}\x{5D0D}\x{5D0E}' + . '\x{5D0F}\x{5D10}\x{5D11}\x{5D12}\x{5D13}\x{5D14}\x{5D15}\x{5D16}\x{5D17}' + . '\x{5D18}\x{5D19}\x{5D1A}\x{5D1B}\x{5D1C}\x{5D1D}\x{5D1E}\x{5D1F}\x{5D20}' + . '\x{5D21}\x{5D22}\x{5D23}\x{5D24}\x{5D25}\x{5D26}\x{5D27}\x{5D28}\x{5D29}' + . '\x{5D2A}\x{5D2C}\x{5D2D}\x{5D2E}\x{5D30}\x{5D31}\x{5D32}\x{5D33}\x{5D34}' + . '\x{5D35}\x{5D36}\x{5D37}\x{5D38}\x{5D39}\x{5D3A}\x{5D3C}\x{5D3D}\x{5D3E}' + . '\x{5D3F}\x{5D40}\x{5D41}\x{5D42}\x{5D43}\x{5D44}\x{5D45}\x{5D46}\x{5D47}' + . '\x{5D48}\x{5D49}\x{5D4A}\x{5D4B}\x{5D4C}\x{5D4D}\x{5D4E}\x{5D4F}\x{5D50}' + . '\x{5D51}\x{5D52}\x{5D54}\x{5D55}\x{5D56}\x{5D58}\x{5D59}\x{5D5A}\x{5D5B}' + . '\x{5D5D}\x{5D5E}\x{5D5F}\x{5D61}\x{5D62}\x{5D63}\x{5D64}\x{5D65}\x{5D66}' + . '\x{5D67}\x{5D68}\x{5D69}\x{5D6A}\x{5D6B}\x{5D6C}\x{5D6D}\x{5D6E}\x{5D6F}' + . '\x{5D70}\x{5D71}\x{5D72}\x{5D73}\x{5D74}\x{5D75}\x{5D76}\x{5D77}\x{5D78}' + . '\x{5D79}\x{5D7A}\x{5D7B}\x{5D7C}\x{5D7D}\x{5D7E}\x{5D7F}\x{5D80}\x{5D81}' + . '\x{5D82}\x{5D84}\x{5D85}\x{5D86}\x{5D87}\x{5D88}\x{5D89}\x{5D8A}\x{5D8B}' + . '\x{5D8C}\x{5D8D}\x{5D8E}\x{5D8F}\x{5D90}\x{5D91}\x{5D92}\x{5D93}\x{5D94}' + . '\x{5D95}\x{5D97}\x{5D98}\x{5D99}\x{5D9A}\x{5D9B}\x{5D9C}\x{5D9D}\x{5D9E}' + . '\x{5D9F}\x{5DA0}\x{5DA1}\x{5DA2}\x{5DA5}\x{5DA6}\x{5DA7}\x{5DA8}\x{5DA9}' + . '\x{5DAA}\x{5DAC}\x{5DAD}\x{5DAE}\x{5DAF}\x{5DB0}\x{5DB1}\x{5DB2}\x{5DB4}' + . '\x{5DB5}\x{5DB6}\x{5DB7}\x{5DB8}\x{5DBA}\x{5DBB}\x{5DBC}\x{5DBD}\x{5DBE}' + . '\x{5DBF}\x{5DC0}\x{5DC1}\x{5DC2}\x{5DC3}\x{5DC5}\x{5DC6}\x{5DC7}\x{5DC8}' + . '\x{5DC9}\x{5DCA}\x{5DCB}\x{5DCC}\x{5DCD}\x{5DCE}\x{5DCF}\x{5DD0}\x{5DD1}' + . '\x{5DD2}\x{5DD3}\x{5DD4}\x{5DD5}\x{5DD6}\x{5DD8}\x{5DD9}\x{5DDB}\x{5DDD}' + . '\x{5DDE}\x{5DDF}\x{5DE0}\x{5DE1}\x{5DE2}\x{5DE3}\x{5DE4}\x{5DE5}\x{5DE6}' + . '\x{5DE7}\x{5DE8}\x{5DE9}\x{5DEA}\x{5DEB}\x{5DEC}\x{5DED}\x{5DEE}\x{5DEF}' + . '\x{5DF0}\x{5DF1}\x{5DF2}\x{5DF3}\x{5DF4}\x{5DF5}\x{5DF7}\x{5DF8}\x{5DF9}' + . '\x{5DFA}\x{5DFB}\x{5DFC}\x{5DFD}\x{5DFE}\x{5DFF}\x{5E00}\x{5E01}\x{5E02}' + . '\x{5E03}\x{5E04}\x{5E05}\x{5E06}\x{5E07}\x{5E08}\x{5E09}\x{5E0A}\x{5E0B}' + . '\x{5E0C}\x{5E0D}\x{5E0E}\x{5E0F}\x{5E10}\x{5E11}\x{5E13}\x{5E14}\x{5E15}' + . '\x{5E16}\x{5E17}\x{5E18}\x{5E19}\x{5E1A}\x{5E1B}\x{5E1C}\x{5E1D}\x{5E1E}' + . '\x{5E1F}\x{5E20}\x{5E21}\x{5E22}\x{5E23}\x{5E24}\x{5E25}\x{5E26}\x{5E27}' + . '\x{5E28}\x{5E29}\x{5E2A}\x{5E2B}\x{5E2C}\x{5E2D}\x{5E2E}\x{5E2F}\x{5E30}' + . '\x{5E31}\x{5E32}\x{5E33}\x{5E34}\x{5E35}\x{5E36}\x{5E37}\x{5E38}\x{5E39}' + . '\x{5E3A}\x{5E3B}\x{5E3C}\x{5E3D}\x{5E3E}\x{5E40}\x{5E41}\x{5E42}\x{5E43}' + . '\x{5E44}\x{5E45}\x{5E46}\x{5E47}\x{5E49}\x{5E4A}\x{5E4B}\x{5E4C}\x{5E4D}' + . '\x{5E4E}\x{5E4F}\x{5E50}\x{5E52}\x{5E53}\x{5E54}\x{5E55}\x{5E56}\x{5E57}' + . '\x{5E58}\x{5E59}\x{5E5A}\x{5E5B}\x{5E5C}\x{5E5D}\x{5E5E}\x{5E5F}\x{5E60}' + . '\x{5E61}\x{5E62}\x{5E63}\x{5E64}\x{5E65}\x{5E66}\x{5E67}\x{5E68}\x{5E69}' + . '\x{5E6A}\x{5E6B}\x{5E6C}\x{5E6D}\x{5E6E}\x{5E6F}\x{5E70}\x{5E71}\x{5E72}' + . '\x{5E73}\x{5E74}\x{5E75}\x{5E76}\x{5E77}\x{5E78}\x{5E79}\x{5E7A}\x{5E7B}' + . '\x{5E7C}\x{5E7D}\x{5E7E}\x{5E7F}\x{5E80}\x{5E81}\x{5E82}\x{5E83}\x{5E84}' + . '\x{5E85}\x{5E86}\x{5E87}\x{5E88}\x{5E89}\x{5E8A}\x{5E8B}\x{5E8C}\x{5E8D}' + . '\x{5E8E}\x{5E8F}\x{5E90}\x{5E91}\x{5E93}\x{5E94}\x{5E95}\x{5E96}\x{5E97}' + . '\x{5E98}\x{5E99}\x{5E9A}\x{5E9B}\x{5E9C}\x{5E9D}\x{5E9E}\x{5E9F}\x{5EA0}' + . '\x{5EA1}\x{5EA2}\x{5EA3}\x{5EA4}\x{5EA5}\x{5EA6}\x{5EA7}\x{5EA8}\x{5EA9}' + . '\x{5EAA}\x{5EAB}\x{5EAC}\x{5EAD}\x{5EAE}\x{5EAF}\x{5EB0}\x{5EB1}\x{5EB2}' + . '\x{5EB3}\x{5EB4}\x{5EB5}\x{5EB6}\x{5EB7}\x{5EB8}\x{5EB9}\x{5EBB}\x{5EBC}' + . '\x{5EBD}\x{5EBE}\x{5EBF}\x{5EC1}\x{5EC2}\x{5EC3}\x{5EC4}\x{5EC5}\x{5EC6}' + . '\x{5EC7}\x{5EC8}\x{5EC9}\x{5ECA}\x{5ECB}\x{5ECC}\x{5ECD}\x{5ECE}\x{5ECF}' + . '\x{5ED0}\x{5ED1}\x{5ED2}\x{5ED3}\x{5ED4}\x{5ED5}\x{5ED6}\x{5ED7}\x{5ED8}' + . '\x{5ED9}\x{5EDA}\x{5EDB}\x{5EDC}\x{5EDD}\x{5EDE}\x{5EDF}\x{5EE0}\x{5EE1}' + . '\x{5EE2}\x{5EE3}\x{5EE4}\x{5EE5}\x{5EE6}\x{5EE7}\x{5EE8}\x{5EE9}\x{5EEA}' + . '\x{5EEC}\x{5EED}\x{5EEE}\x{5EEF}\x{5EF0}\x{5EF1}\x{5EF2}\x{5EF3}\x{5EF4}' + . '\x{5EF5}\x{5EF6}\x{5EF7}\x{5EF8}\x{5EFA}\x{5EFB}\x{5EFC}\x{5EFD}\x{5EFE}' + . '\x{5EFF}\x{5F00}\x{5F01}\x{5F02}\x{5F03}\x{5F04}\x{5F05}\x{5F06}\x{5F07}' + . '\x{5F08}\x{5F0A}\x{5F0B}\x{5F0C}\x{5F0D}\x{5F0F}\x{5F11}\x{5F12}\x{5F13}' + . '\x{5F14}\x{5F15}\x{5F16}\x{5F17}\x{5F18}\x{5F19}\x{5F1A}\x{5F1B}\x{5F1C}' + . '\x{5F1D}\x{5F1E}\x{5F1F}\x{5F20}\x{5F21}\x{5F22}\x{5F23}\x{5F24}\x{5F25}' + . '\x{5F26}\x{5F27}\x{5F28}\x{5F29}\x{5F2A}\x{5F2B}\x{5F2C}\x{5F2D}\x{5F2E}' + . '\x{5F2F}\x{5F30}\x{5F31}\x{5F32}\x{5F33}\x{5F34}\x{5F35}\x{5F36}\x{5F37}' + . '\x{5F38}\x{5F39}\x{5F3A}\x{5F3C}\x{5F3E}\x{5F3F}\x{5F40}\x{5F41}\x{5F42}' + . '\x{5F43}\x{5F44}\x{5F45}\x{5F46}\x{5F47}\x{5F48}\x{5F49}\x{5F4A}\x{5F4B}' + . '\x{5F4C}\x{5F4D}\x{5F4E}\x{5F4F}\x{5F50}\x{5F51}\x{5F52}\x{5F53}\x{5F54}' + . '\x{5F55}\x{5F56}\x{5F57}\x{5F58}\x{5F59}\x{5F5A}\x{5F5B}\x{5F5C}\x{5F5D}' + . '\x{5F5E}\x{5F5F}\x{5F60}\x{5F61}\x{5F62}\x{5F63}\x{5F64}\x{5F65}\x{5F66}' + . '\x{5F67}\x{5F68}\x{5F69}\x{5F6A}\x{5F6B}\x{5F6C}\x{5F6D}\x{5F6E}\x{5F6F}' + . '\x{5F70}\x{5F71}\x{5F72}\x{5F73}\x{5F74}\x{5F75}\x{5F76}\x{5F77}\x{5F78}' + . '\x{5F79}\x{5F7A}\x{5F7B}\x{5F7C}\x{5F7D}\x{5F7E}\x{5F7F}\x{5F80}\x{5F81}' + . '\x{5F82}\x{5F83}\x{5F84}\x{5F85}\x{5F86}\x{5F87}\x{5F88}\x{5F89}\x{5F8A}' + . '\x{5F8B}\x{5F8C}\x{5F8D}\x{5F8E}\x{5F90}\x{5F91}\x{5F92}\x{5F93}\x{5F94}' + . '\x{5F95}\x{5F96}\x{5F97}\x{5F98}\x{5F99}\x{5F9B}\x{5F9C}\x{5F9D}\x{5F9E}' + . '\x{5F9F}\x{5FA0}\x{5FA1}\x{5FA2}\x{5FA5}\x{5FA6}\x{5FA7}\x{5FA8}\x{5FA9}' + . '\x{5FAA}\x{5FAB}\x{5FAC}\x{5FAD}\x{5FAE}\x{5FAF}\x{5FB1}\x{5FB2}\x{5FB3}' + . '\x{5FB4}\x{5FB5}\x{5FB6}\x{5FB7}\x{5FB8}\x{5FB9}\x{5FBA}\x{5FBB}\x{5FBC}' + . '\x{5FBD}\x{5FBE}\x{5FBF}\x{5FC0}\x{5FC1}\x{5FC3}\x{5FC4}\x{5FC5}\x{5FC6}' + . '\x{5FC7}\x{5FC8}\x{5FC9}\x{5FCA}\x{5FCB}\x{5FCC}\x{5FCD}\x{5FCF}\x{5FD0}' + . '\x{5FD1}\x{5FD2}\x{5FD3}\x{5FD4}\x{5FD5}\x{5FD6}\x{5FD7}\x{5FD8}\x{5FD9}' + . '\x{5FDA}\x{5FDC}\x{5FDD}\x{5FDE}\x{5FE0}\x{5FE1}\x{5FE3}\x{5FE4}\x{5FE5}' + . '\x{5FE6}\x{5FE7}\x{5FE8}\x{5FE9}\x{5FEA}\x{5FEB}\x{5FED}\x{5FEE}\x{5FEF}' + . '\x{5FF0}\x{5FF1}\x{5FF2}\x{5FF3}\x{5FF4}\x{5FF5}\x{5FF6}\x{5FF7}\x{5FF8}' + . '\x{5FF9}\x{5FFA}\x{5FFB}\x{5FFD}\x{5FFE}\x{5FFF}\x{6000}\x{6001}\x{6002}' + . '\x{6003}\x{6004}\x{6005}\x{6006}\x{6007}\x{6008}\x{6009}\x{600A}\x{600B}' + . '\x{600C}\x{600D}\x{600E}\x{600F}\x{6010}\x{6011}\x{6012}\x{6013}\x{6014}' + . '\x{6015}\x{6016}\x{6017}\x{6018}\x{6019}\x{601A}\x{601B}\x{601C}\x{601D}' + . '\x{601E}\x{601F}\x{6020}\x{6021}\x{6022}\x{6024}\x{6025}\x{6026}\x{6027}' + . '\x{6028}\x{6029}\x{602A}\x{602B}\x{602C}\x{602D}\x{602E}\x{602F}\x{6030}' + . '\x{6031}\x{6032}\x{6033}\x{6034}\x{6035}\x{6036}\x{6037}\x{6038}\x{6039}' + . '\x{603A}\x{603B}\x{603C}\x{603D}\x{603E}\x{603F}\x{6040}\x{6041}\x{6042}' + . '\x{6043}\x{6044}\x{6045}\x{6046}\x{6047}\x{6048}\x{6049}\x{604A}\x{604B}' + . '\x{604C}\x{604D}\x{604E}\x{604F}\x{6050}\x{6051}\x{6052}\x{6053}\x{6054}' + . '\x{6055}\x{6057}\x{6058}\x{6059}\x{605A}\x{605B}\x{605C}\x{605D}\x{605E}' + . '\x{605F}\x{6062}\x{6063}\x{6064}\x{6065}\x{6066}\x{6067}\x{6068}\x{6069}' + . '\x{606A}\x{606B}\x{606C}\x{606D}\x{606E}\x{606F}\x{6070}\x{6072}\x{6073}' + . '\x{6075}\x{6076}\x{6077}\x{6078}\x{6079}\x{607A}\x{607B}\x{607C}\x{607D}' + . '\x{607E}\x{607F}\x{6080}\x{6081}\x{6082}\x{6083}\x{6084}\x{6085}\x{6086}' + . '\x{6087}\x{6088}\x{6089}\x{608A}\x{608B}\x{608C}\x{608D}\x{608E}\x{608F}' + . '\x{6090}\x{6092}\x{6094}\x{6095}\x{6096}\x{6097}\x{6098}\x{6099}\x{609A}' + . '\x{609B}\x{609C}\x{609D}\x{609E}\x{609F}\x{60A0}\x{60A1}\x{60A2}\x{60A3}' + . '\x{60A4}\x{60A6}\x{60A7}\x{60A8}\x{60AA}\x{60AB}\x{60AC}\x{60AD}\x{60AE}' + . '\x{60AF}\x{60B0}\x{60B1}\x{60B2}\x{60B3}\x{60B4}\x{60B5}\x{60B6}\x{60B7}' + . '\x{60B8}\x{60B9}\x{60BA}\x{60BB}\x{60BC}\x{60BD}\x{60BE}\x{60BF}\x{60C0}' + . '\x{60C1}\x{60C2}\x{60C3}\x{60C4}\x{60C5}\x{60C6}\x{60C7}\x{60C8}\x{60C9}' + . '\x{60CA}\x{60CB}\x{60CC}\x{60CD}\x{60CE}\x{60CF}\x{60D0}\x{60D1}\x{60D3}' + . '\x{60D4}\x{60D5}\x{60D7}\x{60D8}\x{60D9}\x{60DA}\x{60DB}\x{60DC}\x{60DD}' + . '\x{60DF}\x{60E0}\x{60E1}\x{60E2}\x{60E4}\x{60E6}\x{60E7}\x{60E8}\x{60E9}' + . '\x{60EA}\x{60EB}\x{60EC}\x{60ED}\x{60EE}\x{60EF}\x{60F0}\x{60F1}\x{60F2}' + . '\x{60F3}\x{60F4}\x{60F5}\x{60F6}\x{60F7}\x{60F8}\x{60F9}\x{60FA}\x{60FB}' + . '\x{60FC}\x{60FE}\x{60FF}\x{6100}\x{6101}\x{6103}\x{6104}\x{6105}\x{6106}' + . '\x{6108}\x{6109}\x{610A}\x{610B}\x{610C}\x{610D}\x{610E}\x{610F}\x{6110}' + . '\x{6112}\x{6113}\x{6114}\x{6115}\x{6116}\x{6117}\x{6118}\x{6119}\x{611A}' + . '\x{611B}\x{611C}\x{611D}\x{611F}\x{6120}\x{6122}\x{6123}\x{6124}\x{6125}' + . '\x{6126}\x{6127}\x{6128}\x{6129}\x{612A}\x{612B}\x{612C}\x{612D}\x{612E}' + . '\x{612F}\x{6130}\x{6132}\x{6134}\x{6136}\x{6137}\x{613A}\x{613B}\x{613C}' + . '\x{613D}\x{613E}\x{613F}\x{6140}\x{6141}\x{6142}\x{6143}\x{6144}\x{6145}' + . '\x{6146}\x{6147}\x{6148}\x{6149}\x{614A}\x{614B}\x{614C}\x{614D}\x{614E}' + . '\x{614F}\x{6150}\x{6151}\x{6152}\x{6153}\x{6154}\x{6155}\x{6156}\x{6157}' + . '\x{6158}\x{6159}\x{615A}\x{615B}\x{615C}\x{615D}\x{615E}\x{615F}\x{6161}' + . '\x{6162}\x{6163}\x{6164}\x{6165}\x{6166}\x{6167}\x{6168}\x{6169}\x{616A}' + . '\x{616B}\x{616C}\x{616D}\x{616E}\x{6170}\x{6171}\x{6172}\x{6173}\x{6174}' + . '\x{6175}\x{6176}\x{6177}\x{6178}\x{6179}\x{617A}\x{617C}\x{617E}\x{6180}' + . '\x{6181}\x{6182}\x{6183}\x{6184}\x{6185}\x{6187}\x{6188}\x{6189}\x{618A}' + . '\x{618B}\x{618C}\x{618D}\x{618E}\x{618F}\x{6190}\x{6191}\x{6192}\x{6193}' + . '\x{6194}\x{6195}\x{6196}\x{6198}\x{6199}\x{619A}\x{619B}\x{619D}\x{619E}' + . '\x{619F}\x{61A0}\x{61A1}\x{61A2}\x{61A3}\x{61A4}\x{61A5}\x{61A6}\x{61A7}' + . '\x{61A8}\x{61A9}\x{61AA}\x{61AB}\x{61AC}\x{61AD}\x{61AE}\x{61AF}\x{61B0}' + . '\x{61B1}\x{61B2}\x{61B3}\x{61B4}\x{61B5}\x{61B6}\x{61B7}\x{61B8}\x{61BA}' + . '\x{61BC}\x{61BD}\x{61BE}\x{61BF}\x{61C0}\x{61C1}\x{61C2}\x{61C3}\x{61C4}' + . '\x{61C5}\x{61C6}\x{61C7}\x{61C8}\x{61C9}\x{61CA}\x{61CB}\x{61CC}\x{61CD}' + . '\x{61CE}\x{61CF}\x{61D0}\x{61D1}\x{61D2}\x{61D4}\x{61D6}\x{61D7}\x{61D8}' + . '\x{61D9}\x{61DA}\x{61DB}\x{61DC}\x{61DD}\x{61DE}\x{61DF}\x{61E0}\x{61E1}' + . '\x{61E2}\x{61E3}\x{61E4}\x{61E5}\x{61E6}\x{61E7}\x{61E8}\x{61E9}\x{61EA}' + . '\x{61EB}\x{61ED}\x{61EE}\x{61F0}\x{61F1}\x{61F2}\x{61F3}\x{61F5}\x{61F6}' + . '\x{61F7}\x{61F8}\x{61F9}\x{61FA}\x{61FB}\x{61FC}\x{61FD}\x{61FE}\x{61FF}' + . '\x{6200}\x{6201}\x{6202}\x{6203}\x{6204}\x{6206}\x{6207}\x{6208}\x{6209}' + . '\x{620A}\x{620B}\x{620C}\x{620D}\x{620E}\x{620F}\x{6210}\x{6211}\x{6212}' + . '\x{6213}\x{6214}\x{6215}\x{6216}\x{6217}\x{6218}\x{6219}\x{621A}\x{621B}' + . '\x{621C}\x{621D}\x{621E}\x{621F}\x{6220}\x{6221}\x{6222}\x{6223}\x{6224}' + . '\x{6225}\x{6226}\x{6227}\x{6228}\x{6229}\x{622A}\x{622B}\x{622C}\x{622D}' + . '\x{622E}\x{622F}\x{6230}\x{6231}\x{6232}\x{6233}\x{6234}\x{6236}\x{6237}' + . '\x{6238}\x{623A}\x{623B}\x{623C}\x{623D}\x{623E}\x{623F}\x{6240}\x{6241}' + . '\x{6242}\x{6243}\x{6244}\x{6245}\x{6246}\x{6247}\x{6248}\x{6249}\x{624A}' + . '\x{624B}\x{624C}\x{624D}\x{624E}\x{624F}\x{6250}\x{6251}\x{6252}\x{6253}' + . '\x{6254}\x{6255}\x{6256}\x{6258}\x{6259}\x{625A}\x{625B}\x{625C}\x{625D}' + . '\x{625E}\x{625F}\x{6260}\x{6261}\x{6262}\x{6263}\x{6264}\x{6265}\x{6266}' + . '\x{6267}\x{6268}\x{6269}\x{626A}\x{626B}\x{626C}\x{626D}\x{626E}\x{626F}' + . '\x{6270}\x{6271}\x{6272}\x{6273}\x{6274}\x{6275}\x{6276}\x{6277}\x{6278}' + . '\x{6279}\x{627A}\x{627B}\x{627C}\x{627D}\x{627E}\x{627F}\x{6280}\x{6281}' + . '\x{6283}\x{6284}\x{6285}\x{6286}\x{6287}\x{6288}\x{6289}\x{628A}\x{628B}' + . '\x{628C}\x{628E}\x{628F}\x{6290}\x{6291}\x{6292}\x{6293}\x{6294}\x{6295}' + . '\x{6296}\x{6297}\x{6298}\x{6299}\x{629A}\x{629B}\x{629C}\x{629E}\x{629F}' + . '\x{62A0}\x{62A1}\x{62A2}\x{62A3}\x{62A4}\x{62A5}\x{62A7}\x{62A8}\x{62A9}' + . '\x{62AA}\x{62AB}\x{62AC}\x{62AD}\x{62AE}\x{62AF}\x{62B0}\x{62B1}\x{62B2}' + . '\x{62B3}\x{62B4}\x{62B5}\x{62B6}\x{62B7}\x{62B8}\x{62B9}\x{62BA}\x{62BB}' + . '\x{62BC}\x{62BD}\x{62BE}\x{62BF}\x{62C0}\x{62C1}\x{62C2}\x{62C3}\x{62C4}' + . '\x{62C5}\x{62C6}\x{62C7}\x{62C8}\x{62C9}\x{62CA}\x{62CB}\x{62CC}\x{62CD}' + . '\x{62CE}\x{62CF}\x{62D0}\x{62D1}\x{62D2}\x{62D3}\x{62D4}\x{62D5}\x{62D6}' + . '\x{62D7}\x{62D8}\x{62D9}\x{62DA}\x{62DB}\x{62DC}\x{62DD}\x{62DF}\x{62E0}' + . '\x{62E1}\x{62E2}\x{62E3}\x{62E4}\x{62E5}\x{62E6}\x{62E7}\x{62E8}\x{62E9}' + . '\x{62EB}\x{62EC}\x{62ED}\x{62EE}\x{62EF}\x{62F0}\x{62F1}\x{62F2}\x{62F3}' + . '\x{62F4}\x{62F5}\x{62F6}\x{62F7}\x{62F8}\x{62F9}\x{62FA}\x{62FB}\x{62FC}' + . '\x{62FD}\x{62FE}\x{62FF}\x{6300}\x{6301}\x{6302}\x{6303}\x{6304}\x{6305}' + . '\x{6306}\x{6307}\x{6308}\x{6309}\x{630B}\x{630C}\x{630D}\x{630E}\x{630F}' + . '\x{6310}\x{6311}\x{6312}\x{6313}\x{6314}\x{6315}\x{6316}\x{6318}\x{6319}' + . '\x{631A}\x{631B}\x{631C}\x{631D}\x{631E}\x{631F}\x{6320}\x{6321}\x{6322}' + . '\x{6323}\x{6324}\x{6325}\x{6326}\x{6327}\x{6328}\x{6329}\x{632A}\x{632B}' + . '\x{632C}\x{632D}\x{632E}\x{632F}\x{6330}\x{6332}\x{6333}\x{6334}\x{6336}' + . '\x{6338}\x{6339}\x{633A}\x{633B}\x{633C}\x{633D}\x{633E}\x{6340}\x{6341}' + . '\x{6342}\x{6343}\x{6344}\x{6345}\x{6346}\x{6347}\x{6348}\x{6349}\x{634A}' + . '\x{634B}\x{634C}\x{634D}\x{634E}\x{634F}\x{6350}\x{6351}\x{6352}\x{6353}' + . '\x{6354}\x{6355}\x{6356}\x{6357}\x{6358}\x{6359}\x{635A}\x{635C}\x{635D}' + . '\x{635E}\x{635F}\x{6360}\x{6361}\x{6362}\x{6363}\x{6364}\x{6365}\x{6366}' + . '\x{6367}\x{6368}\x{6369}\x{636A}\x{636B}\x{636C}\x{636D}\x{636E}\x{636F}' + . '\x{6370}\x{6371}\x{6372}\x{6373}\x{6374}\x{6375}\x{6376}\x{6377}\x{6378}' + . '\x{6379}\x{637A}\x{637B}\x{637C}\x{637D}\x{637E}\x{6380}\x{6381}\x{6382}' + . '\x{6383}\x{6384}\x{6385}\x{6386}\x{6387}\x{6388}\x{6389}\x{638A}\x{638C}' + . '\x{638D}\x{638E}\x{638F}\x{6390}\x{6391}\x{6392}\x{6394}\x{6395}\x{6396}' + . '\x{6397}\x{6398}\x{6399}\x{639A}\x{639B}\x{639C}\x{639D}\x{639E}\x{639F}' + . '\x{63A0}\x{63A1}\x{63A2}\x{63A3}\x{63A4}\x{63A5}\x{63A6}\x{63A7}\x{63A8}' + . '\x{63A9}\x{63AA}\x{63AB}\x{63AC}\x{63AD}\x{63AE}\x{63AF}\x{63B0}\x{63B1}' + . '\x{63B2}\x{63B3}\x{63B4}\x{63B5}\x{63B6}\x{63B7}\x{63B8}\x{63B9}\x{63BA}' + . '\x{63BC}\x{63BD}\x{63BE}\x{63BF}\x{63C0}\x{63C1}\x{63C2}\x{63C3}\x{63C4}' + . '\x{63C5}\x{63C6}\x{63C7}\x{63C8}\x{63C9}\x{63CA}\x{63CB}\x{63CC}\x{63CD}' + . '\x{63CE}\x{63CF}\x{63D0}\x{63D2}\x{63D3}\x{63D4}\x{63D5}\x{63D6}\x{63D7}' + . '\x{63D8}\x{63D9}\x{63DA}\x{63DB}\x{63DC}\x{63DD}\x{63DE}\x{63DF}\x{63E0}' + . '\x{63E1}\x{63E2}\x{63E3}\x{63E4}\x{63E5}\x{63E6}\x{63E7}\x{63E8}\x{63E9}' + . '\x{63EA}\x{63EB}\x{63EC}\x{63ED}\x{63EE}\x{63EF}\x{63F0}\x{63F1}\x{63F2}' + . '\x{63F3}\x{63F4}\x{63F5}\x{63F6}\x{63F7}\x{63F8}\x{63F9}\x{63FA}\x{63FB}' + . '\x{63FC}\x{63FD}\x{63FE}\x{63FF}\x{6400}\x{6401}\x{6402}\x{6403}\x{6404}' + . '\x{6405}\x{6406}\x{6408}\x{6409}\x{640A}\x{640B}\x{640C}\x{640D}\x{640E}' + . '\x{640F}\x{6410}\x{6411}\x{6412}\x{6413}\x{6414}\x{6415}\x{6416}\x{6417}' + . '\x{6418}\x{6419}\x{641A}\x{641B}\x{641C}\x{641D}\x{641E}\x{641F}\x{6420}' + . '\x{6421}\x{6422}\x{6423}\x{6424}\x{6425}\x{6426}\x{6427}\x{6428}\x{6429}' + . '\x{642A}\x{642B}\x{642C}\x{642D}\x{642E}\x{642F}\x{6430}\x{6431}\x{6432}' + . '\x{6433}\x{6434}\x{6435}\x{6436}\x{6437}\x{6438}\x{6439}\x{643A}\x{643D}' + . '\x{643E}\x{643F}\x{6440}\x{6441}\x{6443}\x{6444}\x{6445}\x{6446}\x{6447}' + . '\x{6448}\x{644A}\x{644B}\x{644C}\x{644D}\x{644E}\x{644F}\x{6450}\x{6451}' + . '\x{6452}\x{6453}\x{6454}\x{6455}\x{6456}\x{6457}\x{6458}\x{6459}\x{645B}' + . '\x{645C}\x{645D}\x{645E}\x{645F}\x{6460}\x{6461}\x{6462}\x{6463}\x{6464}' + . '\x{6465}\x{6466}\x{6467}\x{6468}\x{6469}\x{646A}\x{646B}\x{646C}\x{646D}' + . '\x{646E}\x{646F}\x{6470}\x{6471}\x{6472}\x{6473}\x{6474}\x{6475}\x{6476}' + . '\x{6477}\x{6478}\x{6479}\x{647A}\x{647B}\x{647C}\x{647D}\x{647F}\x{6480}' + . '\x{6481}\x{6482}\x{6483}\x{6484}\x{6485}\x{6487}\x{6488}\x{6489}\x{648A}' + . '\x{648B}\x{648C}\x{648D}\x{648E}\x{648F}\x{6490}\x{6491}\x{6492}\x{6493}' + . '\x{6494}\x{6495}\x{6496}\x{6497}\x{6498}\x{6499}\x{649A}\x{649B}\x{649C}' + . '\x{649D}\x{649E}\x{649F}\x{64A0}\x{64A2}\x{64A3}\x{64A4}\x{64A5}\x{64A6}' + . '\x{64A7}\x{64A8}\x{64A9}\x{64AA}\x{64AB}\x{64AC}\x{64AD}\x{64AE}\x{64B0}' + . '\x{64B1}\x{64B2}\x{64B3}\x{64B4}\x{64B5}\x{64B7}\x{64B8}\x{64B9}\x{64BA}' + . '\x{64BB}\x{64BC}\x{64BD}\x{64BE}\x{64BF}\x{64C0}\x{64C1}\x{64C2}\x{64C3}' + . '\x{64C4}\x{64C5}\x{64C6}\x{64C7}\x{64C9}\x{64CA}\x{64CB}\x{64CC}\x{64CD}' + . '\x{64CE}\x{64CF}\x{64D0}\x{64D1}\x{64D2}\x{64D3}\x{64D4}\x{64D6}\x{64D7}' + . '\x{64D8}\x{64D9}\x{64DA}\x{64DB}\x{64DC}\x{64DD}\x{64DE}\x{64DF}\x{64E0}' + . '\x{64E2}\x{64E3}\x{64E4}\x{64E6}\x{64E7}\x{64E8}\x{64E9}\x{64EA}\x{64EB}' + . '\x{64EC}\x{64ED}\x{64EF}\x{64F0}\x{64F1}\x{64F2}\x{64F3}\x{64F4}\x{64F6}' + . '\x{64F7}\x{64F8}\x{64FA}\x{64FB}\x{64FC}\x{64FD}\x{64FE}\x{64FF}\x{6500}' + . '\x{6501}\x{6503}\x{6504}\x{6505}\x{6506}\x{6507}\x{6508}\x{6509}\x{650B}' + . '\x{650C}\x{650D}\x{650E}\x{650F}\x{6510}\x{6511}\x{6512}\x{6513}\x{6514}' + . '\x{6515}\x{6516}\x{6517}\x{6518}\x{6519}\x{651A}\x{651B}\x{651C}\x{651D}' + . '\x{651E}\x{6520}\x{6521}\x{6522}\x{6523}\x{6524}\x{6525}\x{6526}\x{6527}' + . '\x{6529}\x{652A}\x{652B}\x{652C}\x{652D}\x{652E}\x{652F}\x{6530}\x{6531}' + . '\x{6532}\x{6533}\x{6534}\x{6535}\x{6536}\x{6537}\x{6538}\x{6539}\x{653A}' + . '\x{653B}\x{653C}\x{653D}\x{653E}\x{653F}\x{6541}\x{6543}\x{6544}\x{6545}' + . '\x{6546}\x{6547}\x{6548}\x{6549}\x{654A}\x{654B}\x{654C}\x{654D}\x{654E}' + . '\x{654F}\x{6550}\x{6551}\x{6552}\x{6553}\x{6554}\x{6555}\x{6556}\x{6557}' + . '\x{6558}\x{6559}\x{655B}\x{655C}\x{655D}\x{655E}\x{6560}\x{6561}\x{6562}' + . '\x{6563}\x{6564}\x{6565}\x{6566}\x{6567}\x{6568}\x{6569}\x{656A}\x{656B}' + . '\x{656C}\x{656E}\x{656F}\x{6570}\x{6571}\x{6572}\x{6573}\x{6574}\x{6575}' + . '\x{6576}\x{6577}\x{6578}\x{6579}\x{657A}\x{657B}\x{657C}\x{657E}\x{657F}' + . '\x{6580}\x{6581}\x{6582}\x{6583}\x{6584}\x{6585}\x{6586}\x{6587}\x{6588}' + . '\x{6589}\x{658B}\x{658C}\x{658D}\x{658E}\x{658F}\x{6590}\x{6591}\x{6592}' + . '\x{6593}\x{6594}\x{6595}\x{6596}\x{6597}\x{6598}\x{6599}\x{659B}\x{659C}' + . '\x{659D}\x{659E}\x{659F}\x{65A0}\x{65A1}\x{65A2}\x{65A3}\x{65A4}\x{65A5}' + . '\x{65A6}\x{65A7}\x{65A8}\x{65A9}\x{65AA}\x{65AB}\x{65AC}\x{65AD}\x{65AE}' + . '\x{65AF}\x{65B0}\x{65B1}\x{65B2}\x{65B3}\x{65B4}\x{65B6}\x{65B7}\x{65B8}' + . '\x{65B9}\x{65BA}\x{65BB}\x{65BC}\x{65BD}\x{65BF}\x{65C0}\x{65C1}\x{65C2}' + . '\x{65C3}\x{65C4}\x{65C5}\x{65C6}\x{65C7}\x{65CA}\x{65CB}\x{65CC}\x{65CD}' + . '\x{65CE}\x{65CF}\x{65D0}\x{65D2}\x{65D3}\x{65D4}\x{65D5}\x{65D6}\x{65D7}' + . '\x{65DA}\x{65DB}\x{65DD}\x{65DE}\x{65DF}\x{65E0}\x{65E1}\x{65E2}\x{65E3}' + . '\x{65E5}\x{65E6}\x{65E7}\x{65E8}\x{65E9}\x{65EB}\x{65EC}\x{65ED}\x{65EE}' + . '\x{65EF}\x{65F0}\x{65F1}\x{65F2}\x{65F3}\x{65F4}\x{65F5}\x{65F6}\x{65F7}' + . '\x{65F8}\x{65FA}\x{65FB}\x{65FC}\x{65FD}\x{6600}\x{6601}\x{6602}\x{6603}' + . '\x{6604}\x{6605}\x{6606}\x{6607}\x{6608}\x{6609}\x{660A}\x{660B}\x{660C}' + . '\x{660D}\x{660E}\x{660F}\x{6610}\x{6611}\x{6612}\x{6613}\x{6614}\x{6615}' + . '\x{6616}\x{6618}\x{6619}\x{661A}\x{661B}\x{661C}\x{661D}\x{661F}\x{6620}' + . '\x{6621}\x{6622}\x{6623}\x{6624}\x{6625}\x{6626}\x{6627}\x{6628}\x{6629}' + . '\x{662A}\x{662B}\x{662D}\x{662E}\x{662F}\x{6630}\x{6631}\x{6632}\x{6633}' + . '\x{6634}\x{6635}\x{6636}\x{6639}\x{663A}\x{663C}\x{663D}\x{663E}\x{6640}' + . '\x{6641}\x{6642}\x{6643}\x{6644}\x{6645}\x{6646}\x{6647}\x{6649}\x{664A}' + . '\x{664B}\x{664C}\x{664E}\x{664F}\x{6650}\x{6651}\x{6652}\x{6653}\x{6654}' + . '\x{6655}\x{6656}\x{6657}\x{6658}\x{6659}\x{665A}\x{665B}\x{665C}\x{665D}' + . '\x{665E}\x{665F}\x{6661}\x{6662}\x{6664}\x{6665}\x{6666}\x{6668}\x{6669}' + . '\x{666A}\x{666B}\x{666C}\x{666D}\x{666E}\x{666F}\x{6670}\x{6671}\x{6672}' + . '\x{6673}\x{6674}\x{6675}\x{6676}\x{6677}\x{6678}\x{6679}\x{667A}\x{667B}' + . '\x{667C}\x{667D}\x{667E}\x{667F}\x{6680}\x{6681}\x{6682}\x{6683}\x{6684}' + . '\x{6685}\x{6686}\x{6687}\x{6688}\x{6689}\x{668A}\x{668B}\x{668C}\x{668D}' + . '\x{668E}\x{668F}\x{6690}\x{6691}\x{6693}\x{6694}\x{6695}\x{6696}\x{6697}' + . '\x{6698}\x{6699}\x{669A}\x{669B}\x{669D}\x{669F}\x{66A0}\x{66A1}\x{66A2}' + . '\x{66A3}\x{66A4}\x{66A5}\x{66A6}\x{66A7}\x{66A8}\x{66A9}\x{66AA}\x{66AB}' + . '\x{66AE}\x{66AF}\x{66B0}\x{66B1}\x{66B2}\x{66B3}\x{66B4}\x{66B5}\x{66B6}' + . '\x{66B7}\x{66B8}\x{66B9}\x{66BA}\x{66BB}\x{66BC}\x{66BD}\x{66BE}\x{66BF}' + . '\x{66C0}\x{66C1}\x{66C2}\x{66C3}\x{66C4}\x{66C5}\x{66C6}\x{66C7}\x{66C8}' + . '\x{66C9}\x{66CA}\x{66CB}\x{66CC}\x{66CD}\x{66CE}\x{66CF}\x{66D1}\x{66D2}' + . '\x{66D4}\x{66D5}\x{66D6}\x{66D8}\x{66D9}\x{66DA}\x{66DB}\x{66DC}\x{66DD}' + . '\x{66DE}\x{66E0}\x{66E1}\x{66E2}\x{66E3}\x{66E4}\x{66E5}\x{66E6}\x{66E7}' + . '\x{66E8}\x{66E9}\x{66EA}\x{66EB}\x{66EC}\x{66ED}\x{66EE}\x{66F0}\x{66F1}' + . '\x{66F2}\x{66F3}\x{66F4}\x{66F5}\x{66F6}\x{66F7}\x{66F8}\x{66F9}\x{66FA}' + . '\x{66FB}\x{66FC}\x{66FE}\x{66FF}\x{6700}\x{6701}\x{6703}\x{6704}\x{6705}' + . '\x{6706}\x{6708}\x{6709}\x{670A}\x{670B}\x{670C}\x{670D}\x{670E}\x{670F}' + . '\x{6710}\x{6711}\x{6712}\x{6713}\x{6714}\x{6715}\x{6716}\x{6717}\x{6718}' + . '\x{671A}\x{671B}\x{671C}\x{671D}\x{671E}\x{671F}\x{6720}\x{6721}\x{6722}' + . '\x{6723}\x{6725}\x{6726}\x{6727}\x{6728}\x{672A}\x{672B}\x{672C}\x{672D}' + . '\x{672E}\x{672F}\x{6730}\x{6731}\x{6732}\x{6733}\x{6734}\x{6735}\x{6736}' + . '\x{6737}\x{6738}\x{6739}\x{673A}\x{673B}\x{673C}\x{673D}\x{673E}\x{673F}' + . '\x{6740}\x{6741}\x{6742}\x{6743}\x{6744}\x{6745}\x{6746}\x{6747}\x{6748}' + . '\x{6749}\x{674A}\x{674B}\x{674C}\x{674D}\x{674E}\x{674F}\x{6750}\x{6751}' + . '\x{6752}\x{6753}\x{6754}\x{6755}\x{6756}\x{6757}\x{6758}\x{6759}\x{675A}' + . '\x{675B}\x{675C}\x{675D}\x{675E}\x{675F}\x{6760}\x{6761}\x{6762}\x{6763}' + . '\x{6764}\x{6765}\x{6766}\x{6768}\x{6769}\x{676A}\x{676B}\x{676C}\x{676D}' + . '\x{676E}\x{676F}\x{6770}\x{6771}\x{6772}\x{6773}\x{6774}\x{6775}\x{6776}' + . '\x{6777}\x{6778}\x{6779}\x{677A}\x{677B}\x{677C}\x{677D}\x{677E}\x{677F}' + . '\x{6780}\x{6781}\x{6782}\x{6783}\x{6784}\x{6785}\x{6786}\x{6787}\x{6789}' + . '\x{678A}\x{678B}\x{678C}\x{678D}\x{678E}\x{678F}\x{6790}\x{6791}\x{6792}' + . '\x{6793}\x{6794}\x{6795}\x{6797}\x{6798}\x{6799}\x{679A}\x{679B}\x{679C}' + . '\x{679D}\x{679E}\x{679F}\x{67A0}\x{67A1}\x{67A2}\x{67A3}\x{67A4}\x{67A5}' + . '\x{67A6}\x{67A7}\x{67A8}\x{67AA}\x{67AB}\x{67AC}\x{67AD}\x{67AE}\x{67AF}' + . '\x{67B0}\x{67B1}\x{67B2}\x{67B3}\x{67B4}\x{67B5}\x{67B6}\x{67B7}\x{67B8}' + . '\x{67B9}\x{67BA}\x{67BB}\x{67BC}\x{67BE}\x{67C0}\x{67C1}\x{67C2}\x{67C3}' + . '\x{67C4}\x{67C5}\x{67C6}\x{67C7}\x{67C8}\x{67C9}\x{67CA}\x{67CB}\x{67CC}' + . '\x{67CD}\x{67CE}\x{67CF}\x{67D0}\x{67D1}\x{67D2}\x{67D3}\x{67D4}\x{67D6}' + . '\x{67D8}\x{67D9}\x{67DA}\x{67DB}\x{67DC}\x{67DD}\x{67DE}\x{67DF}\x{67E0}' + . '\x{67E1}\x{67E2}\x{67E3}\x{67E4}\x{67E5}\x{67E6}\x{67E7}\x{67E8}\x{67E9}' + . '\x{67EA}\x{67EB}\x{67EC}\x{67ED}\x{67EE}\x{67EF}\x{67F0}\x{67F1}\x{67F2}' + . '\x{67F3}\x{67F4}\x{67F5}\x{67F6}\x{67F7}\x{67F8}\x{67FA}\x{67FB}\x{67FC}' + . '\x{67FD}\x{67FE}\x{67FF}\x{6800}\x{6802}\x{6803}\x{6804}\x{6805}\x{6806}' + . '\x{6807}\x{6808}\x{6809}\x{680A}\x{680B}\x{680C}\x{680D}\x{680E}\x{680F}' + . '\x{6810}\x{6811}\x{6812}\x{6813}\x{6814}\x{6816}\x{6817}\x{6818}\x{6819}' + . '\x{681A}\x{681B}\x{681C}\x{681D}\x{681F}\x{6820}\x{6821}\x{6822}\x{6823}' + . '\x{6824}\x{6825}\x{6826}\x{6828}\x{6829}\x{682A}\x{682B}\x{682C}\x{682D}' + . '\x{682E}\x{682F}\x{6831}\x{6832}\x{6833}\x{6834}\x{6835}\x{6836}\x{6837}' + . '\x{6838}\x{6839}\x{683A}\x{683B}\x{683C}\x{683D}\x{683E}\x{683F}\x{6840}' + . '\x{6841}\x{6842}\x{6843}\x{6844}\x{6845}\x{6846}\x{6847}\x{6848}\x{6849}' + . '\x{684A}\x{684B}\x{684C}\x{684D}\x{684E}\x{684F}\x{6850}\x{6851}\x{6852}' + . '\x{6853}\x{6854}\x{6855}\x{6856}\x{6857}\x{685B}\x{685D}\x{6860}\x{6861}' + . '\x{6862}\x{6863}\x{6864}\x{6865}\x{6866}\x{6867}\x{6868}\x{6869}\x{686A}' + . '\x{686B}\x{686C}\x{686D}\x{686E}\x{686F}\x{6870}\x{6871}\x{6872}\x{6873}' + . '\x{6874}\x{6875}\x{6876}\x{6877}\x{6878}\x{6879}\x{687B}\x{687C}\x{687D}' + . '\x{687E}\x{687F}\x{6880}\x{6881}\x{6882}\x{6883}\x{6884}\x{6885}\x{6886}' + . '\x{6887}\x{6888}\x{6889}\x{688A}\x{688B}\x{688C}\x{688D}\x{688E}\x{688F}' + . '\x{6890}\x{6891}\x{6892}\x{6893}\x{6894}\x{6896}\x{6897}\x{6898}\x{689A}' + . '\x{689B}\x{689C}\x{689D}\x{689E}\x{689F}\x{68A0}\x{68A1}\x{68A2}\x{68A3}' + . '\x{68A4}\x{68A6}\x{68A7}\x{68A8}\x{68A9}\x{68AA}\x{68AB}\x{68AC}\x{68AD}' + . '\x{68AE}\x{68AF}\x{68B0}\x{68B1}\x{68B2}\x{68B3}\x{68B4}\x{68B5}\x{68B6}' + . '\x{68B7}\x{68B9}\x{68BB}\x{68BC}\x{68BD}\x{68BE}\x{68BF}\x{68C0}\x{68C1}' + . '\x{68C2}\x{68C4}\x{68C6}\x{68C7}\x{68C8}\x{68C9}\x{68CA}\x{68CB}\x{68CC}' + . '\x{68CD}\x{68CE}\x{68CF}\x{68D0}\x{68D1}\x{68D2}\x{68D3}\x{68D4}\x{68D5}' + . '\x{68D6}\x{68D7}\x{68D8}\x{68DA}\x{68DB}\x{68DC}\x{68DD}\x{68DE}\x{68DF}' + . '\x{68E0}\x{68E1}\x{68E3}\x{68E4}\x{68E6}\x{68E7}\x{68E8}\x{68E9}\x{68EA}' + . '\x{68EB}\x{68EC}\x{68ED}\x{68EE}\x{68EF}\x{68F0}\x{68F1}\x{68F2}\x{68F3}' + . '\x{68F4}\x{68F5}\x{68F6}\x{68F7}\x{68F8}\x{68F9}\x{68FA}\x{68FB}\x{68FC}' + . '\x{68FD}\x{68FE}\x{68FF}\x{6901}\x{6902}\x{6903}\x{6904}\x{6905}\x{6906}' + . '\x{6907}\x{6908}\x{690A}\x{690B}\x{690C}\x{690D}\x{690E}\x{690F}\x{6910}' + . '\x{6911}\x{6912}\x{6913}\x{6914}\x{6915}\x{6916}\x{6917}\x{6918}\x{6919}' + . '\x{691A}\x{691B}\x{691C}\x{691D}\x{691E}\x{691F}\x{6920}\x{6921}\x{6922}' + . '\x{6923}\x{6924}\x{6925}\x{6926}\x{6927}\x{6928}\x{6929}\x{692A}\x{692B}' + . '\x{692C}\x{692D}\x{692E}\x{692F}\x{6930}\x{6931}\x{6932}\x{6933}\x{6934}' + . '\x{6935}\x{6936}\x{6937}\x{6938}\x{6939}\x{693A}\x{693B}\x{693C}\x{693D}' + . '\x{693F}\x{6940}\x{6941}\x{6942}\x{6943}\x{6944}\x{6945}\x{6946}\x{6947}' + . '\x{6948}\x{6949}\x{694A}\x{694B}\x{694C}\x{694E}\x{694F}\x{6950}\x{6951}' + . '\x{6952}\x{6953}\x{6954}\x{6955}\x{6956}\x{6957}\x{6958}\x{6959}\x{695A}' + . '\x{695B}\x{695C}\x{695D}\x{695E}\x{695F}\x{6960}\x{6961}\x{6962}\x{6963}' + . '\x{6964}\x{6965}\x{6966}\x{6967}\x{6968}\x{6969}\x{696A}\x{696B}\x{696C}' + . '\x{696D}\x{696E}\x{696F}\x{6970}\x{6971}\x{6972}\x{6973}\x{6974}\x{6975}' + . '\x{6976}\x{6977}\x{6978}\x{6979}\x{697A}\x{697B}\x{697C}\x{697D}\x{697E}' + . '\x{697F}\x{6980}\x{6981}\x{6982}\x{6983}\x{6984}\x{6985}\x{6986}\x{6987}' + . '\x{6988}\x{6989}\x{698A}\x{698B}\x{698C}\x{698D}\x{698E}\x{698F}\x{6990}' + . '\x{6991}\x{6992}\x{6993}\x{6994}\x{6995}\x{6996}\x{6997}\x{6998}\x{6999}' + . '\x{699A}\x{699B}\x{699C}\x{699D}\x{699E}\x{69A0}\x{69A1}\x{69A3}\x{69A4}' + . '\x{69A5}\x{69A6}\x{69A7}\x{69A8}\x{69A9}\x{69AA}\x{69AB}\x{69AC}\x{69AD}' + . '\x{69AE}\x{69AF}\x{69B0}\x{69B1}\x{69B2}\x{69B3}\x{69B4}\x{69B5}\x{69B6}' + . '\x{69B7}\x{69B8}\x{69B9}\x{69BA}\x{69BB}\x{69BC}\x{69BD}\x{69BE}\x{69BF}' + . '\x{69C1}\x{69C2}\x{69C3}\x{69C4}\x{69C5}\x{69C6}\x{69C7}\x{69C8}\x{69C9}' + . '\x{69CA}\x{69CB}\x{69CC}\x{69CD}\x{69CE}\x{69CF}\x{69D0}\x{69D3}\x{69D4}' + . '\x{69D8}\x{69D9}\x{69DA}\x{69DB}\x{69DC}\x{69DD}\x{69DE}\x{69DF}\x{69E0}' + . '\x{69E1}\x{69E2}\x{69E3}\x{69E4}\x{69E5}\x{69E6}\x{69E7}\x{69E8}\x{69E9}' + . '\x{69EA}\x{69EB}\x{69EC}\x{69ED}\x{69EE}\x{69EF}\x{69F0}\x{69F1}\x{69F2}' + . '\x{69F3}\x{69F4}\x{69F5}\x{69F6}\x{69F7}\x{69F8}\x{69FA}\x{69FB}\x{69FC}' + . '\x{69FD}\x{69FE}\x{69FF}\x{6A00}\x{6A01}\x{6A02}\x{6A04}\x{6A05}\x{6A06}' + . '\x{6A07}\x{6A08}\x{6A09}\x{6A0A}\x{6A0B}\x{6A0D}\x{6A0E}\x{6A0F}\x{6A10}' + . '\x{6A11}\x{6A12}\x{6A13}\x{6A14}\x{6A15}\x{6A16}\x{6A17}\x{6A18}\x{6A19}' + . '\x{6A1A}\x{6A1B}\x{6A1D}\x{6A1E}\x{6A1F}\x{6A20}\x{6A21}\x{6A22}\x{6A23}' + . '\x{6A25}\x{6A26}\x{6A27}\x{6A28}\x{6A29}\x{6A2A}\x{6A2B}\x{6A2C}\x{6A2D}' + . '\x{6A2E}\x{6A2F}\x{6A30}\x{6A31}\x{6A32}\x{6A33}\x{6A34}\x{6A35}\x{6A36}' + . '\x{6A38}\x{6A39}\x{6A3A}\x{6A3B}\x{6A3C}\x{6A3D}\x{6A3E}\x{6A3F}\x{6A40}' + . '\x{6A41}\x{6A42}\x{6A43}\x{6A44}\x{6A45}\x{6A46}\x{6A47}\x{6A48}\x{6A49}' + . '\x{6A4B}\x{6A4C}\x{6A4D}\x{6A4E}\x{6A4F}\x{6A50}\x{6A51}\x{6A52}\x{6A54}' + . '\x{6A55}\x{6A56}\x{6A57}\x{6A58}\x{6A59}\x{6A5A}\x{6A5B}\x{6A5D}\x{6A5E}' + . '\x{6A5F}\x{6A60}\x{6A61}\x{6A62}\x{6A63}\x{6A64}\x{6A65}\x{6A66}\x{6A67}' + . '\x{6A68}\x{6A69}\x{6A6A}\x{6A6B}\x{6A6C}\x{6A6D}\x{6A6F}\x{6A71}\x{6A72}' + . '\x{6A73}\x{6A74}\x{6A75}\x{6A76}\x{6A77}\x{6A78}\x{6A79}\x{6A7A}\x{6A7B}' + . '\x{6A7C}\x{6A7D}\x{6A7E}\x{6A7F}\x{6A80}\x{6A81}\x{6A82}\x{6A83}\x{6A84}' + . '\x{6A85}\x{6A87}\x{6A88}\x{6A89}\x{6A8B}\x{6A8C}\x{6A8D}\x{6A8E}\x{6A90}' + . '\x{6A91}\x{6A92}\x{6A93}\x{6A94}\x{6A95}\x{6A96}\x{6A97}\x{6A98}\x{6A9A}' + . '\x{6A9B}\x{6A9C}\x{6A9E}\x{6A9F}\x{6AA0}\x{6AA1}\x{6AA2}\x{6AA3}\x{6AA4}' + . '\x{6AA5}\x{6AA6}\x{6AA7}\x{6AA8}\x{6AA9}\x{6AAB}\x{6AAC}\x{6AAD}\x{6AAE}' + . '\x{6AAF}\x{6AB0}\x{6AB2}\x{6AB3}\x{6AB4}\x{6AB5}\x{6AB6}\x{6AB7}\x{6AB8}' + . '\x{6AB9}\x{6ABA}\x{6ABB}\x{6ABC}\x{6ABD}\x{6ABF}\x{6AC1}\x{6AC2}\x{6AC3}' + . '\x{6AC5}\x{6AC6}\x{6AC7}\x{6ACA}\x{6ACB}\x{6ACC}\x{6ACD}\x{6ACE}\x{6ACF}' + . '\x{6AD0}\x{6AD1}\x{6AD2}\x{6AD3}\x{6AD4}\x{6AD5}\x{6AD6}\x{6AD7}\x{6AD9}' + . '\x{6ADA}\x{6ADB}\x{6ADC}\x{6ADD}\x{6ADE}\x{6ADF}\x{6AE0}\x{6AE1}\x{6AE2}' + . '\x{6AE3}\x{6AE4}\x{6AE5}\x{6AE6}\x{6AE7}\x{6AE8}\x{6AEA}\x{6AEB}\x{6AEC}' + . '\x{6AED}\x{6AEE}\x{6AEF}\x{6AF0}\x{6AF1}\x{6AF2}\x{6AF3}\x{6AF4}\x{6AF5}' + . '\x{6AF6}\x{6AF7}\x{6AF8}\x{6AF9}\x{6AFA}\x{6AFB}\x{6AFC}\x{6AFD}\x{6AFE}' + . '\x{6AFF}\x{6B00}\x{6B01}\x{6B02}\x{6B03}\x{6B04}\x{6B05}\x{6B06}\x{6B07}' + . '\x{6B08}\x{6B09}\x{6B0A}\x{6B0B}\x{6B0C}\x{6B0D}\x{6B0F}\x{6B10}\x{6B11}' + . '\x{6B12}\x{6B13}\x{6B14}\x{6B15}\x{6B16}\x{6B17}\x{6B18}\x{6B19}\x{6B1A}' + . '\x{6B1C}\x{6B1D}\x{6B1E}\x{6B1F}\x{6B20}\x{6B21}\x{6B22}\x{6B23}\x{6B24}' + . '\x{6B25}\x{6B26}\x{6B27}\x{6B28}\x{6B29}\x{6B2A}\x{6B2B}\x{6B2C}\x{6B2D}' + . '\x{6B2F}\x{6B30}\x{6B31}\x{6B32}\x{6B33}\x{6B34}\x{6B36}\x{6B37}\x{6B38}' + . '\x{6B39}\x{6B3A}\x{6B3B}\x{6B3C}\x{6B3D}\x{6B3E}\x{6B3F}\x{6B41}\x{6B42}' + . '\x{6B43}\x{6B44}\x{6B45}\x{6B46}\x{6B47}\x{6B48}\x{6B49}\x{6B4A}\x{6B4B}' + . '\x{6B4C}\x{6B4D}\x{6B4E}\x{6B4F}\x{6B50}\x{6B51}\x{6B52}\x{6B53}\x{6B54}' + . '\x{6B55}\x{6B56}\x{6B59}\x{6B5A}\x{6B5B}\x{6B5C}\x{6B5E}\x{6B5F}\x{6B60}' + . '\x{6B61}\x{6B62}\x{6B63}\x{6B64}\x{6B65}\x{6B66}\x{6B67}\x{6B69}\x{6B6A}' + . '\x{6B6B}\x{6B6D}\x{6B6F}\x{6B70}\x{6B72}\x{6B73}\x{6B74}\x{6B76}\x{6B77}' + . '\x{6B78}\x{6B79}\x{6B7A}\x{6B7B}\x{6B7C}\x{6B7E}\x{6B7F}\x{6B80}\x{6B81}' + . '\x{6B82}\x{6B83}\x{6B84}\x{6B85}\x{6B86}\x{6B87}\x{6B88}\x{6B89}\x{6B8A}' + . '\x{6B8B}\x{6B8C}\x{6B8D}\x{6B8E}\x{6B8F}\x{6B90}\x{6B91}\x{6B92}\x{6B93}' + . '\x{6B94}\x{6B95}\x{6B96}\x{6B97}\x{6B98}\x{6B99}\x{6B9A}\x{6B9B}\x{6B9C}' + . '\x{6B9D}\x{6B9E}\x{6B9F}\x{6BA0}\x{6BA1}\x{6BA2}\x{6BA3}\x{6BA4}\x{6BA5}' + . '\x{6BA6}\x{6BA7}\x{6BA8}\x{6BA9}\x{6BAA}\x{6BAB}\x{6BAC}\x{6BAD}\x{6BAE}' + . '\x{6BAF}\x{6BB0}\x{6BB2}\x{6BB3}\x{6BB4}\x{6BB5}\x{6BB6}\x{6BB7}\x{6BB9}' + . '\x{6BBA}\x{6BBB}\x{6BBC}\x{6BBD}\x{6BBE}\x{6BBF}\x{6BC0}\x{6BC1}\x{6BC2}' + . '\x{6BC3}\x{6BC4}\x{6BC5}\x{6BC6}\x{6BC7}\x{6BC8}\x{6BC9}\x{6BCA}\x{6BCB}' + . '\x{6BCC}\x{6BCD}\x{6BCE}\x{6BCF}\x{6BD0}\x{6BD1}\x{6BD2}\x{6BD3}\x{6BD4}' + . '\x{6BD5}\x{6BD6}\x{6BD7}\x{6BD8}\x{6BD9}\x{6BDA}\x{6BDB}\x{6BDC}\x{6BDD}' + . '\x{6BDE}\x{6BDF}\x{6BE0}\x{6BE1}\x{6BE2}\x{6BE3}\x{6BE4}\x{6BE5}\x{6BE6}' + . '\x{6BE7}\x{6BE8}\x{6BEA}\x{6BEB}\x{6BEC}\x{6BED}\x{6BEE}\x{6BEF}\x{6BF0}' + . '\x{6BF2}\x{6BF3}\x{6BF5}\x{6BF6}\x{6BF7}\x{6BF8}\x{6BF9}\x{6BFB}\x{6BFC}' + . '\x{6BFD}\x{6BFE}\x{6BFF}\x{6C00}\x{6C01}\x{6C02}\x{6C03}\x{6C04}\x{6C05}' + . '\x{6C06}\x{6C07}\x{6C08}\x{6C09}\x{6C0B}\x{6C0C}\x{6C0D}\x{6C0E}\x{6C0F}' + . '\x{6C10}\x{6C11}\x{6C12}\x{6C13}\x{6C14}\x{6C15}\x{6C16}\x{6C18}\x{6C19}' + . '\x{6C1A}\x{6C1B}\x{6C1D}\x{6C1E}\x{6C1F}\x{6C20}\x{6C21}\x{6C22}\x{6C23}' + . '\x{6C24}\x{6C25}\x{6C26}\x{6C27}\x{6C28}\x{6C29}\x{6C2A}\x{6C2B}\x{6C2C}' + . '\x{6C2E}\x{6C2F}\x{6C30}\x{6C31}\x{6C32}\x{6C33}\x{6C34}\x{6C35}\x{6C36}' + . '\x{6C37}\x{6C38}\x{6C3A}\x{6C3B}\x{6C3D}\x{6C3E}\x{6C3F}\x{6C40}\x{6C41}' + . '\x{6C42}\x{6C43}\x{6C44}\x{6C46}\x{6C47}\x{6C48}\x{6C49}\x{6C4A}\x{6C4B}' + . '\x{6C4C}\x{6C4D}\x{6C4E}\x{6C4F}\x{6C50}\x{6C51}\x{6C52}\x{6C53}\x{6C54}' + . '\x{6C55}\x{6C56}\x{6C57}\x{6C58}\x{6C59}\x{6C5A}\x{6C5B}\x{6C5C}\x{6C5D}' + . '\x{6C5E}\x{6C5F}\x{6C60}\x{6C61}\x{6C62}\x{6C63}\x{6C64}\x{6C65}\x{6C66}' + . '\x{6C67}\x{6C68}\x{6C69}\x{6C6A}\x{6C6B}\x{6C6D}\x{6C6F}\x{6C70}\x{6C71}' + . '\x{6C72}\x{6C73}\x{6C74}\x{6C75}\x{6C76}\x{6C77}\x{6C78}\x{6C79}\x{6C7A}' + . '\x{6C7B}\x{6C7C}\x{6C7D}\x{6C7E}\x{6C7F}\x{6C80}\x{6C81}\x{6C82}\x{6C83}' + . '\x{6C84}\x{6C85}\x{6C86}\x{6C87}\x{6C88}\x{6C89}\x{6C8A}\x{6C8B}\x{6C8C}' + . '\x{6C8D}\x{6C8E}\x{6C8F}\x{6C90}\x{6C91}\x{6C92}\x{6C93}\x{6C94}\x{6C95}' + . '\x{6C96}\x{6C97}\x{6C98}\x{6C99}\x{6C9A}\x{6C9B}\x{6C9C}\x{6C9D}\x{6C9E}' + . '\x{6C9F}\x{6CA1}\x{6CA2}\x{6CA3}\x{6CA4}\x{6CA5}\x{6CA6}\x{6CA7}\x{6CA8}' + . '\x{6CA9}\x{6CAA}\x{6CAB}\x{6CAC}\x{6CAD}\x{6CAE}\x{6CAF}\x{6CB0}\x{6CB1}' + . '\x{6CB2}\x{6CB3}\x{6CB4}\x{6CB5}\x{6CB6}\x{6CB7}\x{6CB8}\x{6CB9}\x{6CBA}' + . '\x{6CBB}\x{6CBC}\x{6CBD}\x{6CBE}\x{6CBF}\x{6CC0}\x{6CC1}\x{6CC2}\x{6CC3}' + . '\x{6CC4}\x{6CC5}\x{6CC6}\x{6CC7}\x{6CC8}\x{6CC9}\x{6CCA}\x{6CCB}\x{6CCC}' + . '\x{6CCD}\x{6CCE}\x{6CCF}\x{6CD0}\x{6CD1}\x{6CD2}\x{6CD3}\x{6CD4}\x{6CD5}' + . '\x{6CD6}\x{6CD7}\x{6CD9}\x{6CDA}\x{6CDB}\x{6CDC}\x{6CDD}\x{6CDE}\x{6CDF}' + . '\x{6CE0}\x{6CE1}\x{6CE2}\x{6CE3}\x{6CE4}\x{6CE5}\x{6CE6}\x{6CE7}\x{6CE8}' + . '\x{6CE9}\x{6CEA}\x{6CEB}\x{6CEC}\x{6CED}\x{6CEE}\x{6CEF}\x{6CF0}\x{6CF1}' + . '\x{6CF2}\x{6CF3}\x{6CF5}\x{6CF6}\x{6CF7}\x{6CF8}\x{6CF9}\x{6CFA}\x{6CFB}' + . '\x{6CFC}\x{6CFD}\x{6CFE}\x{6CFF}\x{6D00}\x{6D01}\x{6D03}\x{6D04}\x{6D05}' + . '\x{6D06}\x{6D07}\x{6D08}\x{6D09}\x{6D0A}\x{6D0B}\x{6D0C}\x{6D0D}\x{6D0E}' + . '\x{6D0F}\x{6D10}\x{6D11}\x{6D12}\x{6D13}\x{6D14}\x{6D15}\x{6D16}\x{6D17}' + . '\x{6D18}\x{6D19}\x{6D1A}\x{6D1B}\x{6D1D}\x{6D1E}\x{6D1F}\x{6D20}\x{6D21}' + . '\x{6D22}\x{6D23}\x{6D25}\x{6D26}\x{6D27}\x{6D28}\x{6D29}\x{6D2A}\x{6D2B}' + . '\x{6D2C}\x{6D2D}\x{6D2E}\x{6D2F}\x{6D30}\x{6D31}\x{6D32}\x{6D33}\x{6D34}' + . '\x{6D35}\x{6D36}\x{6D37}\x{6D38}\x{6D39}\x{6D3A}\x{6D3B}\x{6D3C}\x{6D3D}' + . '\x{6D3E}\x{6D3F}\x{6D40}\x{6D41}\x{6D42}\x{6D43}\x{6D44}\x{6D45}\x{6D46}' + . '\x{6D47}\x{6D48}\x{6D49}\x{6D4A}\x{6D4B}\x{6D4C}\x{6D4D}\x{6D4E}\x{6D4F}' + . '\x{6D50}\x{6D51}\x{6D52}\x{6D53}\x{6D54}\x{6D55}\x{6D56}\x{6D57}\x{6D58}' + . '\x{6D59}\x{6D5A}\x{6D5B}\x{6D5C}\x{6D5D}\x{6D5E}\x{6D5F}\x{6D60}\x{6D61}' + . '\x{6D62}\x{6D63}\x{6D64}\x{6D65}\x{6D66}\x{6D67}\x{6D68}\x{6D69}\x{6D6A}' + . '\x{6D6B}\x{6D6C}\x{6D6D}\x{6D6E}\x{6D6F}\x{6D70}\x{6D72}\x{6D73}\x{6D74}' + . '\x{6D75}\x{6D76}\x{6D77}\x{6D78}\x{6D79}\x{6D7A}\x{6D7B}\x{6D7C}\x{6D7D}' + . '\x{6D7E}\x{6D7F}\x{6D80}\x{6D82}\x{6D83}\x{6D84}\x{6D85}\x{6D86}\x{6D87}' + . '\x{6D88}\x{6D89}\x{6D8A}\x{6D8B}\x{6D8C}\x{6D8D}\x{6D8E}\x{6D8F}\x{6D90}' + . '\x{6D91}\x{6D92}\x{6D93}\x{6D94}\x{6D95}\x{6D97}\x{6D98}\x{6D99}\x{6D9A}' + . '\x{6D9B}\x{6D9D}\x{6D9E}\x{6D9F}\x{6DA0}\x{6DA1}\x{6DA2}\x{6DA3}\x{6DA4}' + . '\x{6DA5}\x{6DA6}\x{6DA7}\x{6DA8}\x{6DA9}\x{6DAA}\x{6DAB}\x{6DAC}\x{6DAD}' + . '\x{6DAE}\x{6DAF}\x{6DB2}\x{6DB3}\x{6DB4}\x{6DB5}\x{6DB7}\x{6DB8}\x{6DB9}' + . '\x{6DBA}\x{6DBB}\x{6DBC}\x{6DBD}\x{6DBE}\x{6DBF}\x{6DC0}\x{6DC1}\x{6DC2}' + . '\x{6DC3}\x{6DC4}\x{6DC5}\x{6DC6}\x{6DC7}\x{6DC8}\x{6DC9}\x{6DCA}\x{6DCB}' + . '\x{6DCC}\x{6DCD}\x{6DCE}\x{6DCF}\x{6DD0}\x{6DD1}\x{6DD2}\x{6DD3}\x{6DD4}' + . '\x{6DD5}\x{6DD6}\x{6DD7}\x{6DD8}\x{6DD9}\x{6DDA}\x{6DDB}\x{6DDC}\x{6DDD}' + . '\x{6DDE}\x{6DDF}\x{6DE0}\x{6DE1}\x{6DE2}\x{6DE3}\x{6DE4}\x{6DE5}\x{6DE6}' + . '\x{6DE7}\x{6DE8}\x{6DE9}\x{6DEA}\x{6DEB}\x{6DEC}\x{6DED}\x{6DEE}\x{6DEF}' + . '\x{6DF0}\x{6DF1}\x{6DF2}\x{6DF3}\x{6DF4}\x{6DF5}\x{6DF6}\x{6DF7}\x{6DF8}' + . '\x{6DF9}\x{6DFA}\x{6DFB}\x{6DFC}\x{6DFD}\x{6E00}\x{6E03}\x{6E04}\x{6E05}' + . '\x{6E07}\x{6E08}\x{6E09}\x{6E0A}\x{6E0B}\x{6E0C}\x{6E0D}\x{6E0E}\x{6E0F}' + . '\x{6E10}\x{6E11}\x{6E14}\x{6E15}\x{6E16}\x{6E17}\x{6E19}\x{6E1A}\x{6E1B}' + . '\x{6E1C}\x{6E1D}\x{6E1E}\x{6E1F}\x{6E20}\x{6E21}\x{6E22}\x{6E23}\x{6E24}' + . '\x{6E25}\x{6E26}\x{6E27}\x{6E28}\x{6E29}\x{6E2B}\x{6E2C}\x{6E2D}\x{6E2E}' + . '\x{6E2F}\x{6E30}\x{6E31}\x{6E32}\x{6E33}\x{6E34}\x{6E35}\x{6E36}\x{6E37}' + . '\x{6E38}\x{6E39}\x{6E3A}\x{6E3B}\x{6E3C}\x{6E3D}\x{6E3E}\x{6E3F}\x{6E40}' + . '\x{6E41}\x{6E42}\x{6E43}\x{6E44}\x{6E45}\x{6E46}\x{6E47}\x{6E48}\x{6E49}' + . '\x{6E4A}\x{6E4B}\x{6E4D}\x{6E4E}\x{6E4F}\x{6E50}\x{6E51}\x{6E52}\x{6E53}' + . '\x{6E54}\x{6E55}\x{6E56}\x{6E57}\x{6E58}\x{6E59}\x{6E5A}\x{6E5B}\x{6E5C}' + . '\x{6E5D}\x{6E5E}\x{6E5F}\x{6E60}\x{6E61}\x{6E62}\x{6E63}\x{6E64}\x{6E65}' + . '\x{6E66}\x{6E67}\x{6E68}\x{6E69}\x{6E6A}\x{6E6B}\x{6E6D}\x{6E6E}\x{6E6F}' + . '\x{6E70}\x{6E71}\x{6E72}\x{6E73}\x{6E74}\x{6E75}\x{6E77}\x{6E78}\x{6E79}' + . '\x{6E7E}\x{6E7F}\x{6E80}\x{6E81}\x{6E82}\x{6E83}\x{6E84}\x{6E85}\x{6E86}' + . '\x{6E87}\x{6E88}\x{6E89}\x{6E8A}\x{6E8D}\x{6E8E}\x{6E8F}\x{6E90}\x{6E91}' + . '\x{6E92}\x{6E93}\x{6E94}\x{6E96}\x{6E97}\x{6E98}\x{6E99}\x{6E9A}\x{6E9B}' + . '\x{6E9C}\x{6E9D}\x{6E9E}\x{6E9F}\x{6EA0}\x{6EA1}\x{6EA2}\x{6EA3}\x{6EA4}' + . '\x{6EA5}\x{6EA6}\x{6EA7}\x{6EA8}\x{6EA9}\x{6EAA}\x{6EAB}\x{6EAC}\x{6EAD}' + . '\x{6EAE}\x{6EAF}\x{6EB0}\x{6EB1}\x{6EB2}\x{6EB3}\x{6EB4}\x{6EB5}\x{6EB6}' + . '\x{6EB7}\x{6EB8}\x{6EB9}\x{6EBA}\x{6EBB}\x{6EBC}\x{6EBD}\x{6EBE}\x{6EBF}' + . '\x{6EC0}\x{6EC1}\x{6EC2}\x{6EC3}\x{6EC4}\x{6EC5}\x{6EC6}\x{6EC7}\x{6EC8}' + . '\x{6EC9}\x{6ECA}\x{6ECB}\x{6ECC}\x{6ECD}\x{6ECE}\x{6ECF}\x{6ED0}\x{6ED1}' + . '\x{6ED2}\x{6ED3}\x{6ED4}\x{6ED5}\x{6ED6}\x{6ED7}\x{6ED8}\x{6ED9}\x{6EDA}' + . '\x{6EDC}\x{6EDE}\x{6EDF}\x{6EE0}\x{6EE1}\x{6EE2}\x{6EE4}\x{6EE5}\x{6EE6}' + . '\x{6EE7}\x{6EE8}\x{6EE9}\x{6EEA}\x{6EEB}\x{6EEC}\x{6EED}\x{6EEE}\x{6EEF}' + . '\x{6EF0}\x{6EF1}\x{6EF2}\x{6EF3}\x{6EF4}\x{6EF5}\x{6EF6}\x{6EF7}\x{6EF8}' + . '\x{6EF9}\x{6EFA}\x{6EFB}\x{6EFC}\x{6EFD}\x{6EFE}\x{6EFF}\x{6F00}\x{6F01}' + . '\x{6F02}\x{6F03}\x{6F05}\x{6F06}\x{6F07}\x{6F08}\x{6F09}\x{6F0A}\x{6F0C}' + . '\x{6F0D}\x{6F0E}\x{6F0F}\x{6F10}\x{6F11}\x{6F12}\x{6F13}\x{6F14}\x{6F15}' + . '\x{6F16}\x{6F17}\x{6F18}\x{6F19}\x{6F1A}\x{6F1B}\x{6F1C}\x{6F1D}\x{6F1E}' + . '\x{6F1F}\x{6F20}\x{6F21}\x{6F22}\x{6F23}\x{6F24}\x{6F25}\x{6F26}\x{6F27}' + . '\x{6F28}\x{6F29}\x{6F2A}\x{6F2B}\x{6F2C}\x{6F2D}\x{6F2E}\x{6F2F}\x{6F30}' + . '\x{6F31}\x{6F32}\x{6F33}\x{6F34}\x{6F35}\x{6F36}\x{6F37}\x{6F38}\x{6F39}' + . '\x{6F3A}\x{6F3B}\x{6F3C}\x{6F3D}\x{6F3E}\x{6F3F}\x{6F40}\x{6F41}\x{6F43}' + . '\x{6F44}\x{6F45}\x{6F46}\x{6F47}\x{6F49}\x{6F4B}\x{6F4C}\x{6F4D}\x{6F4E}' + . '\x{6F4F}\x{6F50}\x{6F51}\x{6F52}\x{6F53}\x{6F54}\x{6F55}\x{6F56}\x{6F57}' + . '\x{6F58}\x{6F59}\x{6F5A}\x{6F5B}\x{6F5C}\x{6F5D}\x{6F5E}\x{6F5F}\x{6F60}' + . '\x{6F61}\x{6F62}\x{6F63}\x{6F64}\x{6F65}\x{6F66}\x{6F67}\x{6F68}\x{6F69}' + . '\x{6F6A}\x{6F6B}\x{6F6C}\x{6F6D}\x{6F6E}\x{6F6F}\x{6F70}\x{6F71}\x{6F72}' + . '\x{6F73}\x{6F74}\x{6F75}\x{6F76}\x{6F77}\x{6F78}\x{6F7A}\x{6F7B}\x{6F7C}' + . '\x{6F7D}\x{6F7E}\x{6F7F}\x{6F80}\x{6F81}\x{6F82}\x{6F83}\x{6F84}\x{6F85}' + . '\x{6F86}\x{6F87}\x{6F88}\x{6F89}\x{6F8A}\x{6F8B}\x{6F8C}\x{6F8D}\x{6F8E}' + . '\x{6F8F}\x{6F90}\x{6F91}\x{6F92}\x{6F93}\x{6F94}\x{6F95}\x{6F96}\x{6F97}' + . '\x{6F99}\x{6F9B}\x{6F9C}\x{6F9D}\x{6F9E}\x{6FA0}\x{6FA1}\x{6FA2}\x{6FA3}' + . '\x{6FA4}\x{6FA5}\x{6FA6}\x{6FA7}\x{6FA8}\x{6FA9}\x{6FAA}\x{6FAB}\x{6FAC}' + . '\x{6FAD}\x{6FAE}\x{6FAF}\x{6FB0}\x{6FB1}\x{6FB2}\x{6FB3}\x{6FB4}\x{6FB5}' + . '\x{6FB6}\x{6FB8}\x{6FB9}\x{6FBA}\x{6FBB}\x{6FBC}\x{6FBD}\x{6FBE}\x{6FBF}' + . '\x{6FC0}\x{6FC1}\x{6FC2}\x{6FC3}\x{6FC4}\x{6FC6}\x{6FC7}\x{6FC8}\x{6FC9}' + . '\x{6FCA}\x{6FCB}\x{6FCC}\x{6FCD}\x{6FCE}\x{6FCF}\x{6FD1}\x{6FD2}\x{6FD4}' + . '\x{6FD5}\x{6FD6}\x{6FD7}\x{6FD8}\x{6FD9}\x{6FDA}\x{6FDB}\x{6FDC}\x{6FDD}' + . '\x{6FDE}\x{6FDF}\x{6FE0}\x{6FE1}\x{6FE2}\x{6FE3}\x{6FE4}\x{6FE5}\x{6FE6}' + . '\x{6FE7}\x{6FE8}\x{6FE9}\x{6FEA}\x{6FEB}\x{6FEC}\x{6FED}\x{6FEE}\x{6FEF}' + . '\x{6FF0}\x{6FF1}\x{6FF2}\x{6FF3}\x{6FF4}\x{6FF6}\x{6FF7}\x{6FF8}\x{6FF9}' + . '\x{6FFA}\x{6FFB}\x{6FFC}\x{6FFE}\x{6FFF}\x{7000}\x{7001}\x{7002}\x{7003}' + . '\x{7004}\x{7005}\x{7006}\x{7007}\x{7008}\x{7009}\x{700A}\x{700B}\x{700C}' + . '\x{700D}\x{700E}\x{700F}\x{7011}\x{7012}\x{7014}\x{7015}\x{7016}\x{7017}' + . '\x{7018}\x{7019}\x{701A}\x{701B}\x{701C}\x{701D}\x{701F}\x{7020}\x{7021}' + . '\x{7022}\x{7023}\x{7024}\x{7025}\x{7026}\x{7027}\x{7028}\x{7029}\x{702A}' + . '\x{702B}\x{702C}\x{702D}\x{702E}\x{702F}\x{7030}\x{7031}\x{7032}\x{7033}' + . '\x{7034}\x{7035}\x{7036}\x{7037}\x{7038}\x{7039}\x{703A}\x{703B}\x{703C}' + . '\x{703D}\x{703E}\x{703F}\x{7040}\x{7041}\x{7042}\x{7043}\x{7044}\x{7045}' + . '\x{7046}\x{7048}\x{7049}\x{704A}\x{704C}\x{704D}\x{704F}\x{7050}\x{7051}' + . '\x{7052}\x{7053}\x{7054}\x{7055}\x{7056}\x{7057}\x{7058}\x{7059}\x{705A}' + . '\x{705B}\x{705C}\x{705D}\x{705E}\x{705F}\x{7060}\x{7061}\x{7062}\x{7063}' + . '\x{7064}\x{7065}\x{7066}\x{7067}\x{7068}\x{7069}\x{706A}\x{706B}\x{706C}' + . '\x{706D}\x{706E}\x{706F}\x{7070}\x{7071}\x{7074}\x{7075}\x{7076}\x{7077}' + . '\x{7078}\x{7079}\x{707A}\x{707C}\x{707D}\x{707E}\x{707F}\x{7080}\x{7082}' + . '\x{7083}\x{7084}\x{7085}\x{7086}\x{7087}\x{7088}\x{7089}\x{708A}\x{708B}' + . '\x{708C}\x{708E}\x{708F}\x{7090}\x{7091}\x{7092}\x{7093}\x{7094}\x{7095}' + . '\x{7096}\x{7098}\x{7099}\x{709A}\x{709C}\x{709D}\x{709E}\x{709F}\x{70A0}' + . '\x{70A1}\x{70A2}\x{70A3}\x{70A4}\x{70A5}\x{70A6}\x{70A7}\x{70A8}\x{70A9}' + . '\x{70AB}\x{70AC}\x{70AD}\x{70AE}\x{70AF}\x{70B0}\x{70B1}\x{70B3}\x{70B4}' + . '\x{70B5}\x{70B7}\x{70B8}\x{70B9}\x{70BA}\x{70BB}\x{70BC}\x{70BD}\x{70BE}' + . '\x{70BF}\x{70C0}\x{70C1}\x{70C2}\x{70C3}\x{70C4}\x{70C5}\x{70C6}\x{70C7}' + . '\x{70C8}\x{70C9}\x{70CA}\x{70CB}\x{70CC}\x{70CD}\x{70CE}\x{70CF}\x{70D0}' + . '\x{70D1}\x{70D2}\x{70D3}\x{70D4}\x{70D6}\x{70D7}\x{70D8}\x{70D9}\x{70DA}' + . '\x{70DB}\x{70DC}\x{70DD}\x{70DE}\x{70DF}\x{70E0}\x{70E1}\x{70E2}\x{70E3}' + . '\x{70E4}\x{70E5}\x{70E6}\x{70E7}\x{70E8}\x{70E9}\x{70EA}\x{70EB}\x{70EC}' + . '\x{70ED}\x{70EE}\x{70EF}\x{70F0}\x{70F1}\x{70F2}\x{70F3}\x{70F4}\x{70F5}' + . '\x{70F6}\x{70F7}\x{70F8}\x{70F9}\x{70FA}\x{70FB}\x{70FC}\x{70FD}\x{70FF}' + . '\x{7100}\x{7101}\x{7102}\x{7103}\x{7104}\x{7105}\x{7106}\x{7107}\x{7109}' + . '\x{710A}\x{710B}\x{710C}\x{710D}\x{710E}\x{710F}\x{7110}\x{7111}\x{7112}' + . '\x{7113}\x{7115}\x{7116}\x{7117}\x{7118}\x{7119}\x{711A}\x{711B}\x{711C}' + . '\x{711D}\x{711E}\x{711F}\x{7120}\x{7121}\x{7122}\x{7123}\x{7125}\x{7126}' + . '\x{7127}\x{7128}\x{7129}\x{712A}\x{712B}\x{712C}\x{712D}\x{712E}\x{712F}' + . '\x{7130}\x{7131}\x{7132}\x{7135}\x{7136}\x{7137}\x{7138}\x{7139}\x{713A}' + . '\x{713B}\x{713D}\x{713E}\x{713F}\x{7140}\x{7141}\x{7142}\x{7143}\x{7144}' + . '\x{7145}\x{7146}\x{7147}\x{7148}\x{7149}\x{714A}\x{714B}\x{714C}\x{714D}' + . '\x{714E}\x{714F}\x{7150}\x{7151}\x{7152}\x{7153}\x{7154}\x{7156}\x{7158}' + . '\x{7159}\x{715A}\x{715B}\x{715C}\x{715D}\x{715E}\x{715F}\x{7160}\x{7161}' + . '\x{7162}\x{7163}\x{7164}\x{7165}\x{7166}\x{7167}\x{7168}\x{7169}\x{716A}' + . '\x{716C}\x{716E}\x{716F}\x{7170}\x{7171}\x{7172}\x{7173}\x{7174}\x{7175}' + . '\x{7176}\x{7177}\x{7178}\x{7179}\x{717A}\x{717B}\x{717C}\x{717D}\x{717E}' + . '\x{717F}\x{7180}\x{7181}\x{7182}\x{7183}\x{7184}\x{7185}\x{7186}\x{7187}' + . '\x{7188}\x{7189}\x{718A}\x{718B}\x{718C}\x{718E}\x{718F}\x{7190}\x{7191}' + . '\x{7192}\x{7193}\x{7194}\x{7195}\x{7197}\x{7198}\x{7199}\x{719A}\x{719B}' + . '\x{719C}\x{719D}\x{719E}\x{719F}\x{71A0}\x{71A1}\x{71A2}\x{71A3}\x{71A4}' + . '\x{71A5}\x{71A7}\x{71A8}\x{71A9}\x{71AA}\x{71AC}\x{71AD}\x{71AE}\x{71AF}' + . '\x{71B0}\x{71B1}\x{71B2}\x{71B3}\x{71B4}\x{71B5}\x{71B7}\x{71B8}\x{71B9}' + . '\x{71BA}\x{71BB}\x{71BC}\x{71BD}\x{71BE}\x{71BF}\x{71C0}\x{71C1}\x{71C2}' + . '\x{71C3}\x{71C4}\x{71C5}\x{71C6}\x{71C7}\x{71C8}\x{71C9}\x{71CA}\x{71CB}' + . '\x{71CD}\x{71CE}\x{71CF}\x{71D0}\x{71D1}\x{71D2}\x{71D4}\x{71D5}\x{71D6}' + . '\x{71D7}\x{71D8}\x{71D9}\x{71DA}\x{71DB}\x{71DC}\x{71DD}\x{71DE}\x{71DF}' + . '\x{71E0}\x{71E1}\x{71E2}\x{71E3}\x{71E4}\x{71E5}\x{71E6}\x{71E7}\x{71E8}' + . '\x{71E9}\x{71EA}\x{71EB}\x{71EC}\x{71ED}\x{71EE}\x{71EF}\x{71F0}\x{71F1}' + . '\x{71F2}\x{71F4}\x{71F5}\x{71F6}\x{71F7}\x{71F8}\x{71F9}\x{71FB}\x{71FC}' + . '\x{71FD}\x{71FE}\x{71FF}\x{7201}\x{7202}\x{7203}\x{7204}\x{7205}\x{7206}' + . '\x{7207}\x{7208}\x{7209}\x{720A}\x{720C}\x{720D}\x{720E}\x{720F}\x{7210}' + . '\x{7212}\x{7213}\x{7214}\x{7216}\x{7218}\x{7219}\x{721A}\x{721B}\x{721C}' + . '\x{721D}\x{721E}\x{721F}\x{7221}\x{7222}\x{7223}\x{7226}\x{7227}\x{7228}' + . '\x{7229}\x{722A}\x{722B}\x{722C}\x{722D}\x{722E}\x{7230}\x{7231}\x{7232}' + . '\x{7233}\x{7235}\x{7236}\x{7237}\x{7238}\x{7239}\x{723A}\x{723B}\x{723C}' + . '\x{723D}\x{723E}\x{723F}\x{7240}\x{7241}\x{7242}\x{7243}\x{7244}\x{7246}' + . '\x{7247}\x{7248}\x{7249}\x{724A}\x{724B}\x{724C}\x{724D}\x{724F}\x{7251}' + . '\x{7252}\x{7253}\x{7254}\x{7256}\x{7257}\x{7258}\x{7259}\x{725A}\x{725B}' + . '\x{725C}\x{725D}\x{725E}\x{725F}\x{7260}\x{7261}\x{7262}\x{7263}\x{7264}' + . '\x{7265}\x{7266}\x{7267}\x{7268}\x{7269}\x{726A}\x{726B}\x{726C}\x{726D}' + . '\x{726E}\x{726F}\x{7270}\x{7271}\x{7272}\x{7273}\x{7274}\x{7275}\x{7276}' + . '\x{7277}\x{7278}\x{7279}\x{727A}\x{727B}\x{727C}\x{727D}\x{727E}\x{727F}' + . '\x{7280}\x{7281}\x{7282}\x{7283}\x{7284}\x{7285}\x{7286}\x{7287}\x{7288}' + . '\x{7289}\x{728A}\x{728B}\x{728C}\x{728D}\x{728E}\x{728F}\x{7290}\x{7291}' + . '\x{7292}\x{7293}\x{7294}\x{7295}\x{7296}\x{7297}\x{7298}\x{7299}\x{729A}' + . '\x{729B}\x{729C}\x{729D}\x{729E}\x{729F}\x{72A1}\x{72A2}\x{72A3}\x{72A4}' + . '\x{72A5}\x{72A6}\x{72A7}\x{72A8}\x{72A9}\x{72AA}\x{72AC}\x{72AD}\x{72AE}' + . '\x{72AF}\x{72B0}\x{72B1}\x{72B2}\x{72B3}\x{72B4}\x{72B5}\x{72B6}\x{72B7}' + . '\x{72B8}\x{72B9}\x{72BA}\x{72BB}\x{72BC}\x{72BD}\x{72BF}\x{72C0}\x{72C1}' + . '\x{72C2}\x{72C3}\x{72C4}\x{72C5}\x{72C6}\x{72C7}\x{72C8}\x{72C9}\x{72CA}' + . '\x{72CB}\x{72CC}\x{72CD}\x{72CE}\x{72CF}\x{72D0}\x{72D1}\x{72D2}\x{72D3}' + . '\x{72D4}\x{72D5}\x{72D6}\x{72D7}\x{72D8}\x{72D9}\x{72DA}\x{72DB}\x{72DC}' + . '\x{72DD}\x{72DE}\x{72DF}\x{72E0}\x{72E1}\x{72E2}\x{72E3}\x{72E4}\x{72E5}' + . '\x{72E6}\x{72E7}\x{72E8}\x{72E9}\x{72EA}\x{72EB}\x{72EC}\x{72ED}\x{72EE}' + . '\x{72EF}\x{72F0}\x{72F1}\x{72F2}\x{72F3}\x{72F4}\x{72F5}\x{72F6}\x{72F7}' + . '\x{72F8}\x{72F9}\x{72FA}\x{72FB}\x{72FC}\x{72FD}\x{72FE}\x{72FF}\x{7300}' + . '\x{7301}\x{7303}\x{7304}\x{7305}\x{7306}\x{7307}\x{7308}\x{7309}\x{730A}' + . '\x{730B}\x{730C}\x{730D}\x{730E}\x{730F}\x{7311}\x{7312}\x{7313}\x{7314}' + . '\x{7315}\x{7316}\x{7317}\x{7318}\x{7319}\x{731A}\x{731B}\x{731C}\x{731D}' + . '\x{731E}\x{7320}\x{7321}\x{7322}\x{7323}\x{7324}\x{7325}\x{7326}\x{7327}' + . '\x{7329}\x{732A}\x{732B}\x{732C}\x{732D}\x{732E}\x{7330}\x{7331}\x{7332}' + . '\x{7333}\x{7334}\x{7335}\x{7336}\x{7337}\x{7338}\x{7339}\x{733A}\x{733B}' + . '\x{733C}\x{733D}\x{733E}\x{733F}\x{7340}\x{7341}\x{7342}\x{7343}\x{7344}' + . '\x{7345}\x{7346}\x{7347}\x{7348}\x{7349}\x{734A}\x{734B}\x{734C}\x{734D}' + . '\x{734E}\x{7350}\x{7351}\x{7352}\x{7354}\x{7355}\x{7356}\x{7357}\x{7358}' + . '\x{7359}\x{735A}\x{735B}\x{735C}\x{735D}\x{735E}\x{735F}\x{7360}\x{7361}' + . '\x{7362}\x{7364}\x{7365}\x{7366}\x{7367}\x{7368}\x{7369}\x{736A}\x{736B}' + . '\x{736C}\x{736D}\x{736E}\x{736F}\x{7370}\x{7371}\x{7372}\x{7373}\x{7374}' + . '\x{7375}\x{7376}\x{7377}\x{7378}\x{7379}\x{737A}\x{737B}\x{737C}\x{737D}' + . '\x{737E}\x{737F}\x{7380}\x{7381}\x{7382}\x{7383}\x{7384}\x{7385}\x{7386}' + . '\x{7387}\x{7388}\x{7389}\x{738A}\x{738B}\x{738C}\x{738D}\x{738E}\x{738F}' + . '\x{7390}\x{7391}\x{7392}\x{7393}\x{7394}\x{7395}\x{7396}\x{7397}\x{7398}' + . '\x{7399}\x{739A}\x{739B}\x{739D}\x{739E}\x{739F}\x{73A0}\x{73A1}\x{73A2}' + . '\x{73A3}\x{73A4}\x{73A5}\x{73A6}\x{73A7}\x{73A8}\x{73A9}\x{73AA}\x{73AB}' + . '\x{73AC}\x{73AD}\x{73AE}\x{73AF}\x{73B0}\x{73B1}\x{73B2}\x{73B3}\x{73B4}' + . '\x{73B5}\x{73B6}\x{73B7}\x{73B8}\x{73B9}\x{73BA}\x{73BB}\x{73BC}\x{73BD}' + . '\x{73BE}\x{73BF}\x{73C0}\x{73C2}\x{73C3}\x{73C4}\x{73C5}\x{73C6}\x{73C7}' + . '\x{73C8}\x{73C9}\x{73CA}\x{73CB}\x{73CC}\x{73CD}\x{73CE}\x{73CF}\x{73D0}' + . '\x{73D1}\x{73D2}\x{73D3}\x{73D4}\x{73D5}\x{73D6}\x{73D7}\x{73D8}\x{73D9}' + . '\x{73DA}\x{73DB}\x{73DC}\x{73DD}\x{73DE}\x{73DF}\x{73E0}\x{73E2}\x{73E3}' + . '\x{73E5}\x{73E6}\x{73E7}\x{73E8}\x{73E9}\x{73EA}\x{73EB}\x{73EC}\x{73ED}' + . '\x{73EE}\x{73EF}\x{73F0}\x{73F1}\x{73F2}\x{73F4}\x{73F5}\x{73F6}\x{73F7}' + . '\x{73F8}\x{73F9}\x{73FA}\x{73FC}\x{73FD}\x{73FE}\x{73FF}\x{7400}\x{7401}' + . '\x{7402}\x{7403}\x{7404}\x{7405}\x{7406}\x{7407}\x{7408}\x{7409}\x{740A}' + . '\x{740B}\x{740C}\x{740D}\x{740E}\x{740F}\x{7410}\x{7411}\x{7412}\x{7413}' + . '\x{7414}\x{7415}\x{7416}\x{7417}\x{7419}\x{741A}\x{741B}\x{741C}\x{741D}' + . '\x{741E}\x{741F}\x{7420}\x{7421}\x{7422}\x{7423}\x{7424}\x{7425}\x{7426}' + . '\x{7427}\x{7428}\x{7429}\x{742A}\x{742B}\x{742C}\x{742D}\x{742E}\x{742F}' + . '\x{7430}\x{7431}\x{7432}\x{7433}\x{7434}\x{7435}\x{7436}\x{7437}\x{7438}' + . '\x{743A}\x{743B}\x{743C}\x{743D}\x{743F}\x{7440}\x{7441}\x{7442}\x{7443}' + . '\x{7444}\x{7445}\x{7446}\x{7448}\x{744A}\x{744B}\x{744C}\x{744D}\x{744E}' + . '\x{744F}\x{7450}\x{7451}\x{7452}\x{7453}\x{7454}\x{7455}\x{7456}\x{7457}' + . '\x{7459}\x{745A}\x{745B}\x{745C}\x{745D}\x{745E}\x{745F}\x{7461}\x{7462}' + . '\x{7463}\x{7464}\x{7465}\x{7466}\x{7467}\x{7468}\x{7469}\x{746A}\x{746B}' + . '\x{746C}\x{746D}\x{746E}\x{746F}\x{7470}\x{7471}\x{7472}\x{7473}\x{7474}' + . '\x{7475}\x{7476}\x{7477}\x{7478}\x{7479}\x{747A}\x{747C}\x{747D}\x{747E}' + . '\x{747F}\x{7480}\x{7481}\x{7482}\x{7483}\x{7485}\x{7486}\x{7487}\x{7488}' + . '\x{7489}\x{748A}\x{748B}\x{748C}\x{748D}\x{748E}\x{748F}\x{7490}\x{7491}' + . '\x{7492}\x{7493}\x{7494}\x{7495}\x{7497}\x{7498}\x{7499}\x{749A}\x{749B}' + . '\x{749C}\x{749E}\x{749F}\x{74A0}\x{74A1}\x{74A3}\x{74A4}\x{74A5}\x{74A6}' + . '\x{74A7}\x{74A8}\x{74A9}\x{74AA}\x{74AB}\x{74AC}\x{74AD}\x{74AE}\x{74AF}' + . '\x{74B0}\x{74B1}\x{74B2}\x{74B3}\x{74B4}\x{74B5}\x{74B6}\x{74B7}\x{74B8}' + . '\x{74B9}\x{74BA}\x{74BB}\x{74BC}\x{74BD}\x{74BE}\x{74BF}\x{74C0}\x{74C1}' + . '\x{74C2}\x{74C3}\x{74C4}\x{74C5}\x{74C6}\x{74CA}\x{74CB}\x{74CD}\x{74CE}' + . '\x{74CF}\x{74D0}\x{74D1}\x{74D2}\x{74D3}\x{74D4}\x{74D5}\x{74D6}\x{74D7}' + . '\x{74D8}\x{74D9}\x{74DA}\x{74DB}\x{74DC}\x{74DD}\x{74DE}\x{74DF}\x{74E0}' + . '\x{74E1}\x{74E2}\x{74E3}\x{74E4}\x{74E5}\x{74E6}\x{74E7}\x{74E8}\x{74E9}' + . '\x{74EA}\x{74EC}\x{74ED}\x{74EE}\x{74EF}\x{74F0}\x{74F1}\x{74F2}\x{74F3}' + . '\x{74F4}\x{74F5}\x{74F6}\x{74F7}\x{74F8}\x{74F9}\x{74FA}\x{74FB}\x{74FC}' + . '\x{74FD}\x{74FE}\x{74FF}\x{7500}\x{7501}\x{7502}\x{7503}\x{7504}\x{7505}' + . '\x{7506}\x{7507}\x{7508}\x{7509}\x{750A}\x{750B}\x{750C}\x{750D}\x{750F}' + . '\x{7510}\x{7511}\x{7512}\x{7513}\x{7514}\x{7515}\x{7516}\x{7517}\x{7518}' + . '\x{7519}\x{751A}\x{751B}\x{751C}\x{751D}\x{751E}\x{751F}\x{7521}\x{7522}' + . '\x{7523}\x{7524}\x{7525}\x{7526}\x{7527}\x{7528}\x{7529}\x{752A}\x{752B}' + . '\x{752C}\x{752D}\x{752E}\x{752F}\x{7530}\x{7531}\x{7532}\x{7533}\x{7535}' + . '\x{7536}\x{7537}\x{7538}\x{7539}\x{753A}\x{753B}\x{753C}\x{753D}\x{753E}' + . '\x{753F}\x{7540}\x{7542}\x{7543}\x{7544}\x{7545}\x{7546}\x{7547}\x{7548}' + . '\x{7549}\x{754B}\x{754C}\x{754D}\x{754E}\x{754F}\x{7550}\x{7551}\x{7553}' + . '\x{7554}\x{7556}\x{7557}\x{7558}\x{7559}\x{755A}\x{755B}\x{755C}\x{755D}' + . '\x{755F}\x{7560}\x{7562}\x{7563}\x{7564}\x{7565}\x{7566}\x{7567}\x{7568}' + . '\x{7569}\x{756A}\x{756B}\x{756C}\x{756D}\x{756E}\x{756F}\x{7570}\x{7572}' + . '\x{7574}\x{7575}\x{7576}\x{7577}\x{7578}\x{7579}\x{757C}\x{757D}\x{757E}' + . '\x{757F}\x{7580}\x{7581}\x{7582}\x{7583}\x{7584}\x{7586}\x{7587}\x{7588}' + . '\x{7589}\x{758A}\x{758B}\x{758C}\x{758D}\x{758F}\x{7590}\x{7591}\x{7592}' + . '\x{7593}\x{7594}\x{7595}\x{7596}\x{7597}\x{7598}\x{7599}\x{759A}\x{759B}' + . '\x{759C}\x{759D}\x{759E}\x{759F}\x{75A0}\x{75A1}\x{75A2}\x{75A3}\x{75A4}' + . '\x{75A5}\x{75A6}\x{75A7}\x{75A8}\x{75AA}\x{75AB}\x{75AC}\x{75AD}\x{75AE}' + . '\x{75AF}\x{75B0}\x{75B1}\x{75B2}\x{75B3}\x{75B4}\x{75B5}\x{75B6}\x{75B8}' + . '\x{75B9}\x{75BA}\x{75BB}\x{75BC}\x{75BD}\x{75BE}\x{75BF}\x{75C0}\x{75C1}' + . '\x{75C2}\x{75C3}\x{75C4}\x{75C5}\x{75C6}\x{75C7}\x{75C8}\x{75C9}\x{75CA}' + . '\x{75CB}\x{75CC}\x{75CD}\x{75CE}\x{75CF}\x{75D0}\x{75D1}\x{75D2}\x{75D3}' + . '\x{75D4}\x{75D5}\x{75D6}\x{75D7}\x{75D8}\x{75D9}\x{75DA}\x{75DB}\x{75DD}' + . '\x{75DE}\x{75DF}\x{75E0}\x{75E1}\x{75E2}\x{75E3}\x{75E4}\x{75E5}\x{75E6}' + . '\x{75E7}\x{75E8}\x{75EA}\x{75EB}\x{75EC}\x{75ED}\x{75EF}\x{75F0}\x{75F1}' + . '\x{75F2}\x{75F3}\x{75F4}\x{75F5}\x{75F6}\x{75F7}\x{75F8}\x{75F9}\x{75FA}' + . '\x{75FB}\x{75FC}\x{75FD}\x{75FE}\x{75FF}\x{7600}\x{7601}\x{7602}\x{7603}' + . '\x{7604}\x{7605}\x{7606}\x{7607}\x{7608}\x{7609}\x{760A}\x{760B}\x{760C}' + . '\x{760D}\x{760E}\x{760F}\x{7610}\x{7611}\x{7612}\x{7613}\x{7614}\x{7615}' + . '\x{7616}\x{7617}\x{7618}\x{7619}\x{761A}\x{761B}\x{761C}\x{761D}\x{761E}' + . '\x{761F}\x{7620}\x{7621}\x{7622}\x{7623}\x{7624}\x{7625}\x{7626}\x{7627}' + . '\x{7628}\x{7629}\x{762A}\x{762B}\x{762D}\x{762E}\x{762F}\x{7630}\x{7631}' + . '\x{7632}\x{7633}\x{7634}\x{7635}\x{7636}\x{7637}\x{7638}\x{7639}\x{763A}' + . '\x{763B}\x{763C}\x{763D}\x{763E}\x{763F}\x{7640}\x{7641}\x{7642}\x{7643}' + . '\x{7646}\x{7647}\x{7648}\x{7649}\x{764A}\x{764B}\x{764C}\x{764D}\x{764F}' + . '\x{7650}\x{7652}\x{7653}\x{7654}\x{7656}\x{7657}\x{7658}\x{7659}\x{765A}' + . '\x{765B}\x{765C}\x{765D}\x{765E}\x{765F}\x{7660}\x{7661}\x{7662}\x{7663}' + . '\x{7664}\x{7665}\x{7666}\x{7667}\x{7668}\x{7669}\x{766A}\x{766B}\x{766C}' + . '\x{766D}\x{766E}\x{766F}\x{7670}\x{7671}\x{7672}\x{7674}\x{7675}\x{7676}' + . '\x{7677}\x{7678}\x{7679}\x{767B}\x{767C}\x{767D}\x{767E}\x{767F}\x{7680}' + . '\x{7681}\x{7682}\x{7683}\x{7684}\x{7685}\x{7686}\x{7687}\x{7688}\x{7689}' + . '\x{768A}\x{768B}\x{768C}\x{768E}\x{768F}\x{7690}\x{7691}\x{7692}\x{7693}' + . '\x{7694}\x{7695}\x{7696}\x{7697}\x{7698}\x{7699}\x{769A}\x{769B}\x{769C}' + . '\x{769D}\x{769E}\x{769F}\x{76A0}\x{76A3}\x{76A4}\x{76A6}\x{76A7}\x{76A9}' + . '\x{76AA}\x{76AB}\x{76AC}\x{76AD}\x{76AE}\x{76AF}\x{76B0}\x{76B1}\x{76B2}' + . '\x{76B4}\x{76B5}\x{76B7}\x{76B8}\x{76BA}\x{76BB}\x{76BC}\x{76BD}\x{76BE}' + . '\x{76BF}\x{76C0}\x{76C2}\x{76C3}\x{76C4}\x{76C5}\x{76C6}\x{76C7}\x{76C8}' + . '\x{76C9}\x{76CA}\x{76CD}\x{76CE}\x{76CF}\x{76D0}\x{76D1}\x{76D2}\x{76D3}' + . '\x{76D4}\x{76D5}\x{76D6}\x{76D7}\x{76D8}\x{76DA}\x{76DB}\x{76DC}\x{76DD}' + . '\x{76DE}\x{76DF}\x{76E0}\x{76E1}\x{76E2}\x{76E3}\x{76E4}\x{76E5}\x{76E6}' + . '\x{76E7}\x{76E8}\x{76E9}\x{76EA}\x{76EC}\x{76ED}\x{76EE}\x{76EF}\x{76F0}' + . '\x{76F1}\x{76F2}\x{76F3}\x{76F4}\x{76F5}\x{76F6}\x{76F7}\x{76F8}\x{76F9}' + . '\x{76FA}\x{76FB}\x{76FC}\x{76FD}\x{76FE}\x{76FF}\x{7701}\x{7703}\x{7704}' + . '\x{7705}\x{7706}\x{7707}\x{7708}\x{7709}\x{770A}\x{770B}\x{770C}\x{770D}' + . '\x{770F}\x{7710}\x{7711}\x{7712}\x{7713}\x{7714}\x{7715}\x{7716}\x{7717}' + . '\x{7718}\x{7719}\x{771A}\x{771B}\x{771C}\x{771D}\x{771E}\x{771F}\x{7720}' + . '\x{7722}\x{7723}\x{7725}\x{7726}\x{7727}\x{7728}\x{7729}\x{772A}\x{772C}' + . '\x{772D}\x{772E}\x{772F}\x{7730}\x{7731}\x{7732}\x{7733}\x{7734}\x{7735}' + . '\x{7736}\x{7737}\x{7738}\x{7739}\x{773A}\x{773B}\x{773C}\x{773D}\x{773E}' + . '\x{7740}\x{7741}\x{7743}\x{7744}\x{7745}\x{7746}\x{7747}\x{7748}\x{7749}' + . '\x{774A}\x{774B}\x{774C}\x{774D}\x{774E}\x{774F}\x{7750}\x{7751}\x{7752}' + . '\x{7753}\x{7754}\x{7755}\x{7756}\x{7757}\x{7758}\x{7759}\x{775A}\x{775B}' + . '\x{775C}\x{775D}\x{775E}\x{775F}\x{7760}\x{7761}\x{7762}\x{7763}\x{7765}' + . '\x{7766}\x{7767}\x{7768}\x{7769}\x{776A}\x{776B}\x{776C}\x{776D}\x{776E}' + . '\x{776F}\x{7770}\x{7771}\x{7772}\x{7773}\x{7774}\x{7775}\x{7776}\x{7777}' + . '\x{7778}\x{7779}\x{777A}\x{777B}\x{777C}\x{777D}\x{777E}\x{777F}\x{7780}' + . '\x{7781}\x{7782}\x{7783}\x{7784}\x{7785}\x{7786}\x{7787}\x{7788}\x{7789}' + . '\x{778A}\x{778B}\x{778C}\x{778D}\x{778E}\x{778F}\x{7790}\x{7791}\x{7792}' + . '\x{7793}\x{7794}\x{7795}\x{7797}\x{7798}\x{7799}\x{779A}\x{779B}\x{779C}' + . '\x{779D}\x{779E}\x{779F}\x{77A0}\x{77A1}\x{77A2}\x{77A3}\x{77A5}\x{77A6}' + . '\x{77A7}\x{77A8}\x{77A9}\x{77AA}\x{77AB}\x{77AC}\x{77AD}\x{77AE}\x{77AF}' + . '\x{77B0}\x{77B1}\x{77B2}\x{77B3}\x{77B4}\x{77B5}\x{77B6}\x{77B7}\x{77B8}' + . '\x{77B9}\x{77BA}\x{77BB}\x{77BC}\x{77BD}\x{77BF}\x{77C0}\x{77C2}\x{77C3}' + . '\x{77C4}\x{77C5}\x{77C6}\x{77C7}\x{77C8}\x{77C9}\x{77CA}\x{77CB}\x{77CC}' + . '\x{77CD}\x{77CE}\x{77CF}\x{77D0}\x{77D1}\x{77D3}\x{77D4}\x{77D5}\x{77D6}' + . '\x{77D7}\x{77D8}\x{77D9}\x{77DA}\x{77DB}\x{77DC}\x{77DE}\x{77DF}\x{77E0}' + . '\x{77E1}\x{77E2}\x{77E3}\x{77E5}\x{77E7}\x{77E8}\x{77E9}\x{77EA}\x{77EB}' + . '\x{77EC}\x{77ED}\x{77EE}\x{77EF}\x{77F0}\x{77F1}\x{77F2}\x{77F3}\x{77F6}' + . '\x{77F7}\x{77F8}\x{77F9}\x{77FA}\x{77FB}\x{77FC}\x{77FD}\x{77FE}\x{77FF}' + . '\x{7800}\x{7801}\x{7802}\x{7803}\x{7804}\x{7805}\x{7806}\x{7808}\x{7809}' + . '\x{780A}\x{780B}\x{780C}\x{780D}\x{780E}\x{780F}\x{7810}\x{7811}\x{7812}' + . '\x{7813}\x{7814}\x{7815}\x{7816}\x{7817}\x{7818}\x{7819}\x{781A}\x{781B}' + . '\x{781C}\x{781D}\x{781E}\x{781F}\x{7820}\x{7821}\x{7822}\x{7823}\x{7825}' + . '\x{7826}\x{7827}\x{7828}\x{7829}\x{782A}\x{782B}\x{782C}\x{782D}\x{782E}' + . '\x{782F}\x{7830}\x{7831}\x{7832}\x{7833}\x{7834}\x{7835}\x{7837}\x{7838}' + . '\x{7839}\x{783A}\x{783B}\x{783C}\x{783D}\x{783E}\x{7840}\x{7841}\x{7843}' + . '\x{7844}\x{7845}\x{7847}\x{7848}\x{7849}\x{784A}\x{784C}\x{784D}\x{784E}' + . '\x{7850}\x{7851}\x{7852}\x{7853}\x{7854}\x{7855}\x{7856}\x{7857}\x{7858}' + . '\x{7859}\x{785A}\x{785B}\x{785C}\x{785D}\x{785E}\x{785F}\x{7860}\x{7861}' + . '\x{7862}\x{7863}\x{7864}\x{7865}\x{7866}\x{7867}\x{7868}\x{7869}\x{786A}' + . '\x{786B}\x{786C}\x{786D}\x{786E}\x{786F}\x{7870}\x{7871}\x{7872}\x{7873}' + . '\x{7874}\x{7875}\x{7877}\x{7878}\x{7879}\x{787A}\x{787B}\x{787C}\x{787D}' + . '\x{787E}\x{787F}\x{7880}\x{7881}\x{7882}\x{7883}\x{7884}\x{7885}\x{7886}' + . '\x{7887}\x{7889}\x{788A}\x{788B}\x{788C}\x{788D}\x{788E}\x{788F}\x{7890}' + . '\x{7891}\x{7892}\x{7893}\x{7894}\x{7895}\x{7896}\x{7897}\x{7898}\x{7899}' + . '\x{789A}\x{789B}\x{789C}\x{789D}\x{789E}\x{789F}\x{78A0}\x{78A1}\x{78A2}' + . '\x{78A3}\x{78A4}\x{78A5}\x{78A6}\x{78A7}\x{78A8}\x{78A9}\x{78AA}\x{78AB}' + . '\x{78AC}\x{78AD}\x{78AE}\x{78AF}\x{78B0}\x{78B1}\x{78B2}\x{78B3}\x{78B4}' + . '\x{78B5}\x{78B6}\x{78B7}\x{78B8}\x{78B9}\x{78BA}\x{78BB}\x{78BC}\x{78BD}' + . '\x{78BE}\x{78BF}\x{78C0}\x{78C1}\x{78C3}\x{78C4}\x{78C5}\x{78C6}\x{78C8}' + . '\x{78C9}\x{78CA}\x{78CB}\x{78CC}\x{78CD}\x{78CE}\x{78CF}\x{78D0}\x{78D1}' + . '\x{78D3}\x{78D4}\x{78D5}\x{78D6}\x{78D7}\x{78D8}\x{78D9}\x{78DA}\x{78DB}' + . '\x{78DC}\x{78DD}\x{78DE}\x{78DF}\x{78E0}\x{78E1}\x{78E2}\x{78E3}\x{78E4}' + . '\x{78E5}\x{78E6}\x{78E7}\x{78E8}\x{78E9}\x{78EA}\x{78EB}\x{78EC}\x{78ED}' + . '\x{78EE}\x{78EF}\x{78F1}\x{78F2}\x{78F3}\x{78F4}\x{78F5}\x{78F6}\x{78F7}' + . '\x{78F9}\x{78FA}\x{78FB}\x{78FC}\x{78FD}\x{78FE}\x{78FF}\x{7901}\x{7902}' + . '\x{7903}\x{7904}\x{7905}\x{7906}\x{7907}\x{7909}\x{790A}\x{790B}\x{790C}' + . '\x{790E}\x{790F}\x{7910}\x{7911}\x{7912}\x{7913}\x{7914}\x{7916}\x{7917}' + . '\x{7918}\x{7919}\x{791A}\x{791B}\x{791C}\x{791D}\x{791E}\x{7921}\x{7922}' + . '\x{7923}\x{7924}\x{7925}\x{7926}\x{7927}\x{7928}\x{7929}\x{792A}\x{792B}' + . '\x{792C}\x{792D}\x{792E}\x{792F}\x{7930}\x{7931}\x{7933}\x{7934}\x{7935}' + . '\x{7937}\x{7938}\x{7939}\x{793A}\x{793B}\x{793C}\x{793D}\x{793E}\x{793F}' + . '\x{7940}\x{7941}\x{7942}\x{7943}\x{7944}\x{7945}\x{7946}\x{7947}\x{7948}' + . '\x{7949}\x{794A}\x{794B}\x{794C}\x{794D}\x{794E}\x{794F}\x{7950}\x{7951}' + . '\x{7952}\x{7953}\x{7954}\x{7955}\x{7956}\x{7957}\x{7958}\x{795A}\x{795B}' + . '\x{795C}\x{795D}\x{795E}\x{795F}\x{7960}\x{7961}\x{7962}\x{7963}\x{7964}' + . '\x{7965}\x{7966}\x{7967}\x{7968}\x{7969}\x{796A}\x{796B}\x{796D}\x{796F}' + . '\x{7970}\x{7971}\x{7972}\x{7973}\x{7974}\x{7977}\x{7978}\x{7979}\x{797A}' + . '\x{797B}\x{797C}\x{797D}\x{797E}\x{797F}\x{7980}\x{7981}\x{7982}\x{7983}' + . '\x{7984}\x{7985}\x{7988}\x{7989}\x{798A}\x{798B}\x{798C}\x{798D}\x{798E}' + . '\x{798F}\x{7990}\x{7991}\x{7992}\x{7993}\x{7994}\x{7995}\x{7996}\x{7997}' + . '\x{7998}\x{7999}\x{799A}\x{799B}\x{799C}\x{799F}\x{79A0}\x{79A1}\x{79A2}' + . '\x{79A3}\x{79A4}\x{79A5}\x{79A6}\x{79A7}\x{79A8}\x{79AA}\x{79AB}\x{79AC}' + . '\x{79AD}\x{79AE}\x{79AF}\x{79B0}\x{79B1}\x{79B2}\x{79B3}\x{79B4}\x{79B5}' + . '\x{79B6}\x{79B7}\x{79B8}\x{79B9}\x{79BA}\x{79BB}\x{79BD}\x{79BE}\x{79BF}' + . '\x{79C0}\x{79C1}\x{79C2}\x{79C3}\x{79C5}\x{79C6}\x{79C8}\x{79C9}\x{79CA}' + . '\x{79CB}\x{79CD}\x{79CE}\x{79CF}\x{79D0}\x{79D1}\x{79D2}\x{79D3}\x{79D5}' + . '\x{79D6}\x{79D8}\x{79D9}\x{79DA}\x{79DB}\x{79DC}\x{79DD}\x{79DE}\x{79DF}' + . '\x{79E0}\x{79E1}\x{79E2}\x{79E3}\x{79E4}\x{79E5}\x{79E6}\x{79E7}\x{79E8}' + . '\x{79E9}\x{79EA}\x{79EB}\x{79EC}\x{79ED}\x{79EE}\x{79EF}\x{79F0}\x{79F1}' + . '\x{79F2}\x{79F3}\x{79F4}\x{79F5}\x{79F6}\x{79F7}\x{79F8}\x{79F9}\x{79FA}' + . '\x{79FB}\x{79FC}\x{79FD}\x{79FE}\x{79FF}\x{7A00}\x{7A02}\x{7A03}\x{7A04}' + . '\x{7A05}\x{7A06}\x{7A08}\x{7A0A}\x{7A0B}\x{7A0C}\x{7A0D}\x{7A0E}\x{7A0F}' + . '\x{7A10}\x{7A11}\x{7A12}\x{7A13}\x{7A14}\x{7A15}\x{7A16}\x{7A17}\x{7A18}' + . '\x{7A19}\x{7A1A}\x{7A1B}\x{7A1C}\x{7A1D}\x{7A1E}\x{7A1F}\x{7A20}\x{7A21}' + . '\x{7A22}\x{7A23}\x{7A24}\x{7A25}\x{7A26}\x{7A27}\x{7A28}\x{7A29}\x{7A2A}' + . '\x{7A2B}\x{7A2D}\x{7A2E}\x{7A2F}\x{7A30}\x{7A31}\x{7A32}\x{7A33}\x{7A34}' + . '\x{7A35}\x{7A37}\x{7A39}\x{7A3B}\x{7A3C}\x{7A3D}\x{7A3E}\x{7A3F}\x{7A40}' + . '\x{7A41}\x{7A42}\x{7A43}\x{7A44}\x{7A45}\x{7A46}\x{7A47}\x{7A48}\x{7A49}' + . '\x{7A4A}\x{7A4B}\x{7A4C}\x{7A4D}\x{7A4E}\x{7A50}\x{7A51}\x{7A52}\x{7A53}' + . '\x{7A54}\x{7A55}\x{7A56}\x{7A57}\x{7A58}\x{7A59}\x{7A5A}\x{7A5B}\x{7A5C}' + . '\x{7A5D}\x{7A5E}\x{7A5F}\x{7A60}\x{7A61}\x{7A62}\x{7A65}\x{7A66}\x{7A67}' + . '\x{7A68}\x{7A69}\x{7A6B}\x{7A6C}\x{7A6D}\x{7A6E}\x{7A70}\x{7A71}\x{7A72}' + . '\x{7A73}\x{7A74}\x{7A75}\x{7A76}\x{7A77}\x{7A78}\x{7A79}\x{7A7A}\x{7A7B}' + . '\x{7A7C}\x{7A7D}\x{7A7E}\x{7A7F}\x{7A80}\x{7A81}\x{7A83}\x{7A84}\x{7A85}' + . '\x{7A86}\x{7A87}\x{7A88}\x{7A89}\x{7A8A}\x{7A8B}\x{7A8C}\x{7A8D}\x{7A8E}' + . '\x{7A8F}\x{7A90}\x{7A91}\x{7A92}\x{7A93}\x{7A94}\x{7A95}\x{7A96}\x{7A97}' + . '\x{7A98}\x{7A99}\x{7A9C}\x{7A9D}\x{7A9E}\x{7A9F}\x{7AA0}\x{7AA1}\x{7AA2}' + . '\x{7AA3}\x{7AA4}\x{7AA5}\x{7AA6}\x{7AA7}\x{7AA8}\x{7AA9}\x{7AAA}\x{7AAB}' + . '\x{7AAC}\x{7AAD}\x{7AAE}\x{7AAF}\x{7AB0}\x{7AB1}\x{7AB2}\x{7AB3}\x{7AB4}' + . '\x{7AB5}\x{7AB6}\x{7AB7}\x{7AB8}\x{7ABA}\x{7ABE}\x{7ABF}\x{7AC0}\x{7AC1}' + . '\x{7AC4}\x{7AC5}\x{7AC7}\x{7AC8}\x{7AC9}\x{7ACA}\x{7ACB}\x{7ACC}\x{7ACD}' + . '\x{7ACE}\x{7ACF}\x{7AD0}\x{7AD1}\x{7AD2}\x{7AD3}\x{7AD4}\x{7AD5}\x{7AD6}' + . '\x{7AD8}\x{7AD9}\x{7ADB}\x{7ADC}\x{7ADD}\x{7ADE}\x{7ADF}\x{7AE0}\x{7AE1}' + . '\x{7AE2}\x{7AE3}\x{7AE4}\x{7AE5}\x{7AE6}\x{7AE7}\x{7AE8}\x{7AEA}\x{7AEB}' + . '\x{7AEC}\x{7AED}\x{7AEE}\x{7AEF}\x{7AF0}\x{7AF1}\x{7AF2}\x{7AF3}\x{7AF4}' + . '\x{7AF6}\x{7AF7}\x{7AF8}\x{7AF9}\x{7AFA}\x{7AFB}\x{7AFD}\x{7AFE}\x{7AFF}' + . '\x{7B00}\x{7B01}\x{7B02}\x{7B03}\x{7B04}\x{7B05}\x{7B06}\x{7B08}\x{7B09}' + . '\x{7B0A}\x{7B0B}\x{7B0C}\x{7B0D}\x{7B0E}\x{7B0F}\x{7B10}\x{7B11}\x{7B12}' + . '\x{7B13}\x{7B14}\x{7B15}\x{7B16}\x{7B17}\x{7B18}\x{7B19}\x{7B1A}\x{7B1B}' + . '\x{7B1C}\x{7B1D}\x{7B1E}\x{7B20}\x{7B21}\x{7B22}\x{7B23}\x{7B24}\x{7B25}' + . '\x{7B26}\x{7B28}\x{7B2A}\x{7B2B}\x{7B2C}\x{7B2D}\x{7B2E}\x{7B2F}\x{7B30}' + . '\x{7B31}\x{7B32}\x{7B33}\x{7B34}\x{7B35}\x{7B36}\x{7B37}\x{7B38}\x{7B39}' + . '\x{7B3A}\x{7B3B}\x{7B3C}\x{7B3D}\x{7B3E}\x{7B3F}\x{7B40}\x{7B41}\x{7B43}' + . '\x{7B44}\x{7B45}\x{7B46}\x{7B47}\x{7B48}\x{7B49}\x{7B4A}\x{7B4B}\x{7B4C}' + . '\x{7B4D}\x{7B4E}\x{7B4F}\x{7B50}\x{7B51}\x{7B52}\x{7B54}\x{7B55}\x{7B56}' + . '\x{7B57}\x{7B58}\x{7B59}\x{7B5A}\x{7B5B}\x{7B5C}\x{7B5D}\x{7B5E}\x{7B5F}' + . '\x{7B60}\x{7B61}\x{7B62}\x{7B63}\x{7B64}\x{7B65}\x{7B66}\x{7B67}\x{7B68}' + . '\x{7B69}\x{7B6A}\x{7B6B}\x{7B6C}\x{7B6D}\x{7B6E}\x{7B70}\x{7B71}\x{7B72}' + . '\x{7B73}\x{7B74}\x{7B75}\x{7B76}\x{7B77}\x{7B78}\x{7B79}\x{7B7B}\x{7B7C}' + . '\x{7B7D}\x{7B7E}\x{7B7F}\x{7B80}\x{7B81}\x{7B82}\x{7B83}\x{7B84}\x{7B85}' + . '\x{7B87}\x{7B88}\x{7B89}\x{7B8A}\x{7B8B}\x{7B8C}\x{7B8D}\x{7B8E}\x{7B8F}' + . '\x{7B90}\x{7B91}\x{7B93}\x{7B94}\x{7B95}\x{7B96}\x{7B97}\x{7B98}\x{7B99}' + . '\x{7B9A}\x{7B9B}\x{7B9C}\x{7B9D}\x{7B9E}\x{7B9F}\x{7BA0}\x{7BA1}\x{7BA2}' + . '\x{7BA4}\x{7BA6}\x{7BA7}\x{7BA8}\x{7BA9}\x{7BAA}\x{7BAB}\x{7BAC}\x{7BAD}' + . '\x{7BAE}\x{7BAF}\x{7BB1}\x{7BB3}\x{7BB4}\x{7BB5}\x{7BB6}\x{7BB7}\x{7BB8}' + . '\x{7BB9}\x{7BBA}\x{7BBB}\x{7BBC}\x{7BBD}\x{7BBE}\x{7BBF}\x{7BC0}\x{7BC1}' + . '\x{7BC2}\x{7BC3}\x{7BC4}\x{7BC5}\x{7BC6}\x{7BC7}\x{7BC8}\x{7BC9}\x{7BCA}' + . '\x{7BCB}\x{7BCC}\x{7BCD}\x{7BCE}\x{7BD0}\x{7BD1}\x{7BD2}\x{7BD3}\x{7BD4}' + . '\x{7BD5}\x{7BD6}\x{7BD7}\x{7BD8}\x{7BD9}\x{7BDA}\x{7BDB}\x{7BDC}\x{7BDD}' + . '\x{7BDE}\x{7BDF}\x{7BE0}\x{7BE1}\x{7BE2}\x{7BE3}\x{7BE4}\x{7BE5}\x{7BE6}' + . '\x{7BE7}\x{7BE8}\x{7BE9}\x{7BEA}\x{7BEB}\x{7BEC}\x{7BED}\x{7BEE}\x{7BEF}' + . '\x{7BF0}\x{7BF1}\x{7BF2}\x{7BF3}\x{7BF4}\x{7BF5}\x{7BF6}\x{7BF7}\x{7BF8}' + . '\x{7BF9}\x{7BFB}\x{7BFC}\x{7BFD}\x{7BFE}\x{7BFF}\x{7C00}\x{7C01}\x{7C02}' + . '\x{7C03}\x{7C04}\x{7C05}\x{7C06}\x{7C07}\x{7C08}\x{7C09}\x{7C0A}\x{7C0B}' + . '\x{7C0C}\x{7C0D}\x{7C0E}\x{7C0F}\x{7C10}\x{7C11}\x{7C12}\x{7C13}\x{7C15}' + . '\x{7C16}\x{7C17}\x{7C18}\x{7C19}\x{7C1A}\x{7C1C}\x{7C1D}\x{7C1E}\x{7C1F}' + . '\x{7C20}\x{7C21}\x{7C22}\x{7C23}\x{7C24}\x{7C25}\x{7C26}\x{7C27}\x{7C28}' + . '\x{7C29}\x{7C2A}\x{7C2B}\x{7C2C}\x{7C2D}\x{7C30}\x{7C31}\x{7C32}\x{7C33}' + . '\x{7C34}\x{7C35}\x{7C36}\x{7C37}\x{7C38}\x{7C39}\x{7C3A}\x{7C3B}\x{7C3C}' + . '\x{7C3D}\x{7C3E}\x{7C3F}\x{7C40}\x{7C41}\x{7C42}\x{7C43}\x{7C44}\x{7C45}' + . '\x{7C46}\x{7C47}\x{7C48}\x{7C49}\x{7C4A}\x{7C4B}\x{7C4C}\x{7C4D}\x{7C4E}' + . '\x{7C50}\x{7C51}\x{7C53}\x{7C54}\x{7C56}\x{7C57}\x{7C58}\x{7C59}\x{7C5A}' + . '\x{7C5B}\x{7C5C}\x{7C5E}\x{7C5F}\x{7C60}\x{7C61}\x{7C62}\x{7C63}\x{7C64}' + . '\x{7C65}\x{7C66}\x{7C67}\x{7C68}\x{7C69}\x{7C6A}\x{7C6B}\x{7C6C}\x{7C6D}' + . '\x{7C6E}\x{7C6F}\x{7C70}\x{7C71}\x{7C72}\x{7C73}\x{7C74}\x{7C75}\x{7C77}' + . '\x{7C78}\x{7C79}\x{7C7A}\x{7C7B}\x{7C7C}\x{7C7D}\x{7C7E}\x{7C7F}\x{7C80}' + . '\x{7C81}\x{7C82}\x{7C84}\x{7C85}\x{7C86}\x{7C88}\x{7C89}\x{7C8A}\x{7C8B}' + . '\x{7C8C}\x{7C8D}\x{7C8E}\x{7C8F}\x{7C90}\x{7C91}\x{7C92}\x{7C94}\x{7C95}' + . '\x{7C96}\x{7C97}\x{7C98}\x{7C99}\x{7C9B}\x{7C9C}\x{7C9D}\x{7C9E}\x{7C9F}' + . '\x{7CA0}\x{7CA1}\x{7CA2}\x{7CA3}\x{7CA4}\x{7CA5}\x{7CA6}\x{7CA7}\x{7CA8}' + . '\x{7CA9}\x{7CAA}\x{7CAD}\x{7CAE}\x{7CAF}\x{7CB0}\x{7CB1}\x{7CB2}\x{7CB3}' + . '\x{7CB4}\x{7CB5}\x{7CB6}\x{7CB7}\x{7CB8}\x{7CB9}\x{7CBA}\x{7CBB}\x{7CBC}' + . '\x{7CBD}\x{7CBE}\x{7CBF}\x{7CC0}\x{7CC1}\x{7CC2}\x{7CC3}\x{7CC4}\x{7CC5}' + . '\x{7CC6}\x{7CC7}\x{7CC8}\x{7CC9}\x{7CCA}\x{7CCB}\x{7CCC}\x{7CCD}\x{7CCE}' + . '\x{7CCF}\x{7CD0}\x{7CD1}\x{7CD2}\x{7CD4}\x{7CD5}\x{7CD6}\x{7CD7}\x{7CD8}' + . '\x{7CD9}\x{7CDC}\x{7CDD}\x{7CDE}\x{7CDF}\x{7CE0}\x{7CE2}\x{7CE4}\x{7CE7}' + . '\x{7CE8}\x{7CE9}\x{7CEA}\x{7CEB}\x{7CEC}\x{7CED}\x{7CEE}\x{7CEF}\x{7CF0}' + . '\x{7CF1}\x{7CF2}\x{7CF3}\x{7CF4}\x{7CF5}\x{7CF6}\x{7CF7}\x{7CF8}\x{7CF9}' + . '\x{7CFA}\x{7CFB}\x{7CFD}\x{7CFE}\x{7D00}\x{7D01}\x{7D02}\x{7D03}\x{7D04}' + . '\x{7D05}\x{7D06}\x{7D07}\x{7D08}\x{7D09}\x{7D0A}\x{7D0B}\x{7D0C}\x{7D0D}' + . '\x{7D0E}\x{7D0F}\x{7D10}\x{7D11}\x{7D12}\x{7D13}\x{7D14}\x{7D15}\x{7D16}' + . '\x{7D17}\x{7D18}\x{7D19}\x{7D1A}\x{7D1B}\x{7D1C}\x{7D1D}\x{7D1E}\x{7D1F}' + . '\x{7D20}\x{7D21}\x{7D22}\x{7D24}\x{7D25}\x{7D26}\x{7D27}\x{7D28}\x{7D29}' + . '\x{7D2B}\x{7D2C}\x{7D2E}\x{7D2F}\x{7D30}\x{7D31}\x{7D32}\x{7D33}\x{7D34}' + . '\x{7D35}\x{7D36}\x{7D37}\x{7D38}\x{7D39}\x{7D3A}\x{7D3B}\x{7D3C}\x{7D3D}' + . '\x{7D3E}\x{7D3F}\x{7D40}\x{7D41}\x{7D42}\x{7D43}\x{7D44}\x{7D45}\x{7D46}' + . '\x{7D47}\x{7D49}\x{7D4A}\x{7D4B}\x{7D4C}\x{7D4E}\x{7D4F}\x{7D50}\x{7D51}' + . '\x{7D52}\x{7D53}\x{7D54}\x{7D55}\x{7D56}\x{7D57}\x{7D58}\x{7D59}\x{7D5B}' + . '\x{7D5C}\x{7D5D}\x{7D5E}\x{7D5F}\x{7D60}\x{7D61}\x{7D62}\x{7D63}\x{7D65}' + . '\x{7D66}\x{7D67}\x{7D68}\x{7D69}\x{7D6A}\x{7D6B}\x{7D6C}\x{7D6D}\x{7D6E}' + . '\x{7D6F}\x{7D70}\x{7D71}\x{7D72}\x{7D73}\x{7D74}\x{7D75}\x{7D76}\x{7D77}' + . '\x{7D79}\x{7D7A}\x{7D7B}\x{7D7C}\x{7D7D}\x{7D7E}\x{7D7F}\x{7D80}\x{7D81}' + . '\x{7D83}\x{7D84}\x{7D85}\x{7D86}\x{7D87}\x{7D88}\x{7D89}\x{7D8A}\x{7D8B}' + . '\x{7D8C}\x{7D8D}\x{7D8E}\x{7D8F}\x{7D90}\x{7D91}\x{7D92}\x{7D93}\x{7D94}' + . '\x{7D96}\x{7D97}\x{7D99}\x{7D9B}\x{7D9C}\x{7D9D}\x{7D9E}\x{7D9F}\x{7DA0}' + . '\x{7DA1}\x{7DA2}\x{7DA3}\x{7DA5}\x{7DA6}\x{7DA7}\x{7DA9}\x{7DAA}\x{7DAB}' + . '\x{7DAC}\x{7DAD}\x{7DAE}\x{7DAF}\x{7DB0}\x{7DB1}\x{7DB2}\x{7DB3}\x{7DB4}' + . '\x{7DB5}\x{7DB6}\x{7DB7}\x{7DB8}\x{7DB9}\x{7DBA}\x{7DBB}\x{7DBC}\x{7DBD}' + . '\x{7DBE}\x{7DBF}\x{7DC0}\x{7DC1}\x{7DC2}\x{7DC3}\x{7DC4}\x{7DC5}\x{7DC6}' + . '\x{7DC7}\x{7DC8}\x{7DC9}\x{7DCA}\x{7DCB}\x{7DCC}\x{7DCE}\x{7DCF}\x{7DD0}' + . '\x{7DD1}\x{7DD2}\x{7DD4}\x{7DD5}\x{7DD6}\x{7DD7}\x{7DD8}\x{7DD9}\x{7DDA}' + . '\x{7DDB}\x{7DDD}\x{7DDE}\x{7DDF}\x{7DE0}\x{7DE1}\x{7DE2}\x{7DE3}\x{7DE6}' + . '\x{7DE7}\x{7DE8}\x{7DE9}\x{7DEA}\x{7DEC}\x{7DED}\x{7DEE}\x{7DEF}\x{7DF0}' + . '\x{7DF1}\x{7DF2}\x{7DF3}\x{7DF4}\x{7DF5}\x{7DF6}\x{7DF7}\x{7DF8}\x{7DF9}' + . '\x{7DFA}\x{7DFB}\x{7DFC}\x{7E00}\x{7E01}\x{7E02}\x{7E03}\x{7E04}\x{7E05}' + . '\x{7E06}\x{7E07}\x{7E08}\x{7E09}\x{7E0A}\x{7E0B}\x{7E0C}\x{7E0D}\x{7E0E}' + . '\x{7E0F}\x{7E10}\x{7E11}\x{7E12}\x{7E13}\x{7E14}\x{7E15}\x{7E16}\x{7E17}' + . '\x{7E19}\x{7E1A}\x{7E1B}\x{7E1C}\x{7E1D}\x{7E1E}\x{7E1F}\x{7E20}\x{7E21}' + . '\x{7E22}\x{7E23}\x{7E24}\x{7E25}\x{7E26}\x{7E27}\x{7E28}\x{7E29}\x{7E2A}' + . '\x{7E2B}\x{7E2C}\x{7E2D}\x{7E2E}\x{7E2F}\x{7E30}\x{7E31}\x{7E32}\x{7E33}' + . '\x{7E34}\x{7E35}\x{7E36}\x{7E37}\x{7E38}\x{7E39}\x{7E3A}\x{7E3B}\x{7E3C}' + . '\x{7E3D}\x{7E3E}\x{7E3F}\x{7E40}\x{7E41}\x{7E42}\x{7E43}\x{7E44}\x{7E45}' + . '\x{7E46}\x{7E47}\x{7E48}\x{7E49}\x{7E4C}\x{7E4D}\x{7E4E}\x{7E4F}\x{7E50}' + . '\x{7E51}\x{7E52}\x{7E53}\x{7E54}\x{7E55}\x{7E56}\x{7E57}\x{7E58}\x{7E59}' + . '\x{7E5A}\x{7E5C}\x{7E5D}\x{7E5E}\x{7E5F}\x{7E60}\x{7E61}\x{7E62}\x{7E63}' + . '\x{7E65}\x{7E66}\x{7E67}\x{7E68}\x{7E69}\x{7E6A}\x{7E6B}\x{7E6C}\x{7E6D}' + . '\x{7E6E}\x{7E6F}\x{7E70}\x{7E71}\x{7E72}\x{7E73}\x{7E74}\x{7E75}\x{7E76}' + . '\x{7E77}\x{7E78}\x{7E79}\x{7E7A}\x{7E7B}\x{7E7C}\x{7E7D}\x{7E7E}\x{7E7F}' + . '\x{7E80}\x{7E81}\x{7E82}\x{7E83}\x{7E84}\x{7E85}\x{7E86}\x{7E87}\x{7E88}' + . '\x{7E89}\x{7E8A}\x{7E8B}\x{7E8C}\x{7E8D}\x{7E8E}\x{7E8F}\x{7E90}\x{7E91}' + . '\x{7E92}\x{7E93}\x{7E94}\x{7E95}\x{7E96}\x{7E97}\x{7E98}\x{7E99}\x{7E9A}' + . '\x{7E9B}\x{7E9C}\x{7E9E}\x{7E9F}\x{7EA0}\x{7EA1}\x{7EA2}\x{7EA3}\x{7EA4}' + . '\x{7EA5}\x{7EA6}\x{7EA7}\x{7EA8}\x{7EA9}\x{7EAA}\x{7EAB}\x{7EAC}\x{7EAD}' + . '\x{7EAE}\x{7EAF}\x{7EB0}\x{7EB1}\x{7EB2}\x{7EB3}\x{7EB4}\x{7EB5}\x{7EB6}' + . '\x{7EB7}\x{7EB8}\x{7EB9}\x{7EBA}\x{7EBB}\x{7EBC}\x{7EBD}\x{7EBE}\x{7EBF}' + . '\x{7EC0}\x{7EC1}\x{7EC2}\x{7EC3}\x{7EC4}\x{7EC5}\x{7EC6}\x{7EC7}\x{7EC8}' + . '\x{7EC9}\x{7ECA}\x{7ECB}\x{7ECC}\x{7ECD}\x{7ECE}\x{7ECF}\x{7ED0}\x{7ED1}' + . '\x{7ED2}\x{7ED3}\x{7ED4}\x{7ED5}\x{7ED6}\x{7ED7}\x{7ED8}\x{7ED9}\x{7EDA}' + . '\x{7EDB}\x{7EDC}\x{7EDD}\x{7EDE}\x{7EDF}\x{7EE0}\x{7EE1}\x{7EE2}\x{7EE3}' + . '\x{7EE4}\x{7EE5}\x{7EE6}\x{7EE7}\x{7EE8}\x{7EE9}\x{7EEA}\x{7EEB}\x{7EEC}' + . '\x{7EED}\x{7EEE}\x{7EEF}\x{7EF0}\x{7EF1}\x{7EF2}\x{7EF3}\x{7EF4}\x{7EF5}' + . '\x{7EF6}\x{7EF7}\x{7EF8}\x{7EF9}\x{7EFA}\x{7EFB}\x{7EFC}\x{7EFD}\x{7EFE}' + . '\x{7EFF}\x{7F00}\x{7F01}\x{7F02}\x{7F03}\x{7F04}\x{7F05}\x{7F06}\x{7F07}' + . '\x{7F08}\x{7F09}\x{7F0A}\x{7F0B}\x{7F0C}\x{7F0D}\x{7F0E}\x{7F0F}\x{7F10}' + . '\x{7F11}\x{7F12}\x{7F13}\x{7F14}\x{7F15}\x{7F16}\x{7F17}\x{7F18}\x{7F19}' + . '\x{7F1A}\x{7F1B}\x{7F1C}\x{7F1D}\x{7F1E}\x{7F1F}\x{7F20}\x{7F21}\x{7F22}' + . '\x{7F23}\x{7F24}\x{7F25}\x{7F26}\x{7F27}\x{7F28}\x{7F29}\x{7F2A}\x{7F2B}' + . '\x{7F2C}\x{7F2D}\x{7F2E}\x{7F2F}\x{7F30}\x{7F31}\x{7F32}\x{7F33}\x{7F34}' + . '\x{7F35}\x{7F36}\x{7F37}\x{7F38}\x{7F39}\x{7F3A}\x{7F3D}\x{7F3E}\x{7F3F}' + . '\x{7F40}\x{7F42}\x{7F43}\x{7F44}\x{7F45}\x{7F47}\x{7F48}\x{7F49}\x{7F4A}' + . '\x{7F4B}\x{7F4C}\x{7F4D}\x{7F4E}\x{7F4F}\x{7F50}\x{7F51}\x{7F52}\x{7F53}' + . '\x{7F54}\x{7F55}\x{7F56}\x{7F57}\x{7F58}\x{7F5A}\x{7F5B}\x{7F5C}\x{7F5D}' + . '\x{7F5E}\x{7F5F}\x{7F60}\x{7F61}\x{7F62}\x{7F63}\x{7F64}\x{7F65}\x{7F66}' + . '\x{7F67}\x{7F68}\x{7F69}\x{7F6A}\x{7F6B}\x{7F6C}\x{7F6D}\x{7F6E}\x{7F6F}' + . '\x{7F70}\x{7F71}\x{7F72}\x{7F73}\x{7F74}\x{7F75}\x{7F76}\x{7F77}\x{7F78}' + . '\x{7F79}\x{7F7A}\x{7F7B}\x{7F7C}\x{7F7D}\x{7F7E}\x{7F7F}\x{7F80}\x{7F81}' + . '\x{7F82}\x{7F83}\x{7F85}\x{7F86}\x{7F87}\x{7F88}\x{7F89}\x{7F8A}\x{7F8B}' + . '\x{7F8C}\x{7F8D}\x{7F8E}\x{7F8F}\x{7F91}\x{7F92}\x{7F93}\x{7F94}\x{7F95}' + . '\x{7F96}\x{7F98}\x{7F9A}\x{7F9B}\x{7F9C}\x{7F9D}\x{7F9E}\x{7F9F}\x{7FA0}' + . '\x{7FA1}\x{7FA2}\x{7FA3}\x{7FA4}\x{7FA5}\x{7FA6}\x{7FA7}\x{7FA8}\x{7FA9}' + . '\x{7FAA}\x{7FAB}\x{7FAC}\x{7FAD}\x{7FAE}\x{7FAF}\x{7FB0}\x{7FB1}\x{7FB2}' + . '\x{7FB3}\x{7FB5}\x{7FB6}\x{7FB7}\x{7FB8}\x{7FB9}\x{7FBA}\x{7FBB}\x{7FBC}' + . '\x{7FBD}\x{7FBE}\x{7FBF}\x{7FC0}\x{7FC1}\x{7FC2}\x{7FC3}\x{7FC4}\x{7FC5}' + . '\x{7FC6}\x{7FC7}\x{7FC8}\x{7FC9}\x{7FCA}\x{7FCB}\x{7FCC}\x{7FCD}\x{7FCE}' + . '\x{7FCF}\x{7FD0}\x{7FD1}\x{7FD2}\x{7FD3}\x{7FD4}\x{7FD5}\x{7FD7}\x{7FD8}' + . '\x{7FD9}\x{7FDA}\x{7FDB}\x{7FDC}\x{7FDE}\x{7FDF}\x{7FE0}\x{7FE1}\x{7FE2}' + . '\x{7FE3}\x{7FE5}\x{7FE6}\x{7FE7}\x{7FE8}\x{7FE9}\x{7FEA}\x{7FEB}\x{7FEC}' + . '\x{7FED}\x{7FEE}\x{7FEF}\x{7FF0}\x{7FF1}\x{7FF2}\x{7FF3}\x{7FF4}\x{7FF5}' + . '\x{7FF6}\x{7FF7}\x{7FF8}\x{7FF9}\x{7FFA}\x{7FFB}\x{7FFC}\x{7FFD}\x{7FFE}' + . '\x{7FFF}\x{8000}\x{8001}\x{8002}\x{8003}\x{8004}\x{8005}\x{8006}\x{8007}' + . '\x{8008}\x{8009}\x{800B}\x{800C}\x{800D}\x{800E}\x{800F}\x{8010}\x{8011}' + . '\x{8012}\x{8013}\x{8014}\x{8015}\x{8016}\x{8017}\x{8018}\x{8019}\x{801A}' + . '\x{801B}\x{801C}\x{801D}\x{801E}\x{801F}\x{8020}\x{8021}\x{8022}\x{8023}' + . '\x{8024}\x{8025}\x{8026}\x{8027}\x{8028}\x{8029}\x{802A}\x{802B}\x{802C}' + . '\x{802D}\x{802E}\x{8030}\x{8031}\x{8032}\x{8033}\x{8034}\x{8035}\x{8036}' + . '\x{8037}\x{8038}\x{8039}\x{803A}\x{803B}\x{803D}\x{803E}\x{803F}\x{8041}' + . '\x{8042}\x{8043}\x{8044}\x{8045}\x{8046}\x{8047}\x{8048}\x{8049}\x{804A}' + . '\x{804B}\x{804C}\x{804D}\x{804E}\x{804F}\x{8050}\x{8051}\x{8052}\x{8053}' + . '\x{8054}\x{8055}\x{8056}\x{8057}\x{8058}\x{8059}\x{805A}\x{805B}\x{805C}' + . '\x{805D}\x{805E}\x{805F}\x{8060}\x{8061}\x{8062}\x{8063}\x{8064}\x{8065}' + . '\x{8067}\x{8068}\x{8069}\x{806A}\x{806B}\x{806C}\x{806D}\x{806E}\x{806F}' + . '\x{8070}\x{8071}\x{8072}\x{8073}\x{8074}\x{8075}\x{8076}\x{8077}\x{8078}' + . '\x{8079}\x{807A}\x{807B}\x{807C}\x{807D}\x{807E}\x{807F}\x{8080}\x{8081}' + . '\x{8082}\x{8083}\x{8084}\x{8085}\x{8086}\x{8087}\x{8089}\x{808A}\x{808B}' + . '\x{808C}\x{808D}\x{808F}\x{8090}\x{8091}\x{8092}\x{8093}\x{8095}\x{8096}' + . '\x{8097}\x{8098}\x{8099}\x{809A}\x{809B}\x{809C}\x{809D}\x{809E}\x{809F}' + . '\x{80A0}\x{80A1}\x{80A2}\x{80A3}\x{80A4}\x{80A5}\x{80A9}\x{80AA}\x{80AB}' + . '\x{80AD}\x{80AE}\x{80AF}\x{80B0}\x{80B1}\x{80B2}\x{80B4}\x{80B5}\x{80B6}' + . '\x{80B7}\x{80B8}\x{80BA}\x{80BB}\x{80BC}\x{80BD}\x{80BE}\x{80BF}\x{80C0}' + . '\x{80C1}\x{80C2}\x{80C3}\x{80C4}\x{80C5}\x{80C6}\x{80C7}\x{80C8}\x{80C9}' + . '\x{80CA}\x{80CB}\x{80CC}\x{80CD}\x{80CE}\x{80CF}\x{80D0}\x{80D1}\x{80D2}' + . '\x{80D3}\x{80D4}\x{80D5}\x{80D6}\x{80D7}\x{80D8}\x{80D9}\x{80DA}\x{80DB}' + . '\x{80DC}\x{80DD}\x{80DE}\x{80E0}\x{80E1}\x{80E2}\x{80E3}\x{80E4}\x{80E5}' + . '\x{80E6}\x{80E7}\x{80E8}\x{80E9}\x{80EA}\x{80EB}\x{80EC}\x{80ED}\x{80EE}' + . '\x{80EF}\x{80F0}\x{80F1}\x{80F2}\x{80F3}\x{80F4}\x{80F5}\x{80F6}\x{80F7}' + . '\x{80F8}\x{80F9}\x{80FA}\x{80FB}\x{80FC}\x{80FD}\x{80FE}\x{80FF}\x{8100}' + . '\x{8101}\x{8102}\x{8105}\x{8106}\x{8107}\x{8108}\x{8109}\x{810A}\x{810B}' + . '\x{810C}\x{810D}\x{810E}\x{810F}\x{8110}\x{8111}\x{8112}\x{8113}\x{8114}' + . '\x{8115}\x{8116}\x{8118}\x{8119}\x{811A}\x{811B}\x{811C}\x{811D}\x{811E}' + . '\x{811F}\x{8120}\x{8121}\x{8122}\x{8123}\x{8124}\x{8125}\x{8126}\x{8127}' + . '\x{8128}\x{8129}\x{812A}\x{812B}\x{812C}\x{812D}\x{812E}\x{812F}\x{8130}' + . '\x{8131}\x{8132}\x{8136}\x{8137}\x{8138}\x{8139}\x{813A}\x{813B}\x{813C}' + . '\x{813D}\x{813E}\x{813F}\x{8140}\x{8141}\x{8142}\x{8143}\x{8144}\x{8145}' + . '\x{8146}\x{8147}\x{8148}\x{8149}\x{814A}\x{814B}\x{814C}\x{814D}\x{814E}' + . '\x{814F}\x{8150}\x{8151}\x{8152}\x{8153}\x{8154}\x{8155}\x{8156}\x{8157}' + . '\x{8158}\x{8159}\x{815A}\x{815B}\x{815C}\x{815D}\x{815E}\x{8160}\x{8161}' + . '\x{8162}\x{8163}\x{8164}\x{8165}\x{8166}\x{8167}\x{8168}\x{8169}\x{816A}' + . '\x{816B}\x{816C}\x{816D}\x{816E}\x{816F}\x{8170}\x{8171}\x{8172}\x{8173}' + . '\x{8174}\x{8175}\x{8176}\x{8177}\x{8178}\x{8179}\x{817A}\x{817B}\x{817C}' + . '\x{817D}\x{817E}\x{817F}\x{8180}\x{8181}\x{8182}\x{8183}\x{8185}\x{8186}' + . '\x{8187}\x{8188}\x{8189}\x{818A}\x{818B}\x{818C}\x{818D}\x{818E}\x{818F}' + . '\x{8191}\x{8192}\x{8193}\x{8194}\x{8195}\x{8197}\x{8198}\x{8199}\x{819A}' + . '\x{819B}\x{819C}\x{819D}\x{819E}\x{819F}\x{81A0}\x{81A1}\x{81A2}\x{81A3}' + . '\x{81A4}\x{81A5}\x{81A6}\x{81A7}\x{81A8}\x{81A9}\x{81AA}\x{81AB}\x{81AC}' + . '\x{81AD}\x{81AE}\x{81AF}\x{81B0}\x{81B1}\x{81B2}\x{81B3}\x{81B4}\x{81B5}' + . '\x{81B6}\x{81B7}\x{81B8}\x{81B9}\x{81BA}\x{81BB}\x{81BC}\x{81BD}\x{81BE}' + . '\x{81BF}\x{81C0}\x{81C1}\x{81C2}\x{81C3}\x{81C4}\x{81C5}\x{81C6}\x{81C7}' + . '\x{81C8}\x{81C9}\x{81CA}\x{81CC}\x{81CD}\x{81CE}\x{81CF}\x{81D0}\x{81D1}' + . '\x{81D2}\x{81D4}\x{81D5}\x{81D6}\x{81D7}\x{81D8}\x{81D9}\x{81DA}\x{81DB}' + . '\x{81DC}\x{81DD}\x{81DE}\x{81DF}\x{81E0}\x{81E1}\x{81E2}\x{81E3}\x{81E5}' + . '\x{81E6}\x{81E7}\x{81E8}\x{81E9}\x{81EA}\x{81EB}\x{81EC}\x{81ED}\x{81EE}' + . '\x{81F1}\x{81F2}\x{81F3}\x{81F4}\x{81F5}\x{81F6}\x{81F7}\x{81F8}\x{81F9}' + . '\x{81FA}\x{81FB}\x{81FC}\x{81FD}\x{81FE}\x{81FF}\x{8200}\x{8201}\x{8202}' + . '\x{8203}\x{8204}\x{8205}\x{8206}\x{8207}\x{8208}\x{8209}\x{820A}\x{820B}' + . '\x{820C}\x{820D}\x{820E}\x{820F}\x{8210}\x{8211}\x{8212}\x{8214}\x{8215}' + . '\x{8216}\x{8218}\x{8219}\x{821A}\x{821B}\x{821C}\x{821D}\x{821E}\x{821F}' + . '\x{8220}\x{8221}\x{8222}\x{8223}\x{8225}\x{8226}\x{8227}\x{8228}\x{8229}' + . '\x{822A}\x{822B}\x{822C}\x{822D}\x{822F}\x{8230}\x{8231}\x{8232}\x{8233}' + . '\x{8234}\x{8235}\x{8236}\x{8237}\x{8238}\x{8239}\x{823A}\x{823B}\x{823C}' + . '\x{823D}\x{823E}\x{823F}\x{8240}\x{8242}\x{8243}\x{8244}\x{8245}\x{8246}' + . '\x{8247}\x{8248}\x{8249}\x{824A}\x{824B}\x{824C}\x{824D}\x{824E}\x{824F}' + . '\x{8250}\x{8251}\x{8252}\x{8253}\x{8254}\x{8255}\x{8256}\x{8257}\x{8258}' + . '\x{8259}\x{825A}\x{825B}\x{825C}\x{825D}\x{825E}\x{825F}\x{8260}\x{8261}' + . '\x{8263}\x{8264}\x{8266}\x{8267}\x{8268}\x{8269}\x{826A}\x{826B}\x{826C}' + . '\x{826D}\x{826E}\x{826F}\x{8270}\x{8271}\x{8272}\x{8273}\x{8274}\x{8275}' + . '\x{8276}\x{8277}\x{8278}\x{8279}\x{827A}\x{827B}\x{827C}\x{827D}\x{827E}' + . '\x{827F}\x{8280}\x{8281}\x{8282}\x{8283}\x{8284}\x{8285}\x{8286}\x{8287}' + . '\x{8288}\x{8289}\x{828A}\x{828B}\x{828D}\x{828E}\x{828F}\x{8290}\x{8291}' + . '\x{8292}\x{8293}\x{8294}\x{8295}\x{8296}\x{8297}\x{8298}\x{8299}\x{829A}' + . '\x{829B}\x{829C}\x{829D}\x{829E}\x{829F}\x{82A0}\x{82A1}\x{82A2}\x{82A3}' + . '\x{82A4}\x{82A5}\x{82A6}\x{82A7}\x{82A8}\x{82A9}\x{82AA}\x{82AB}\x{82AC}' + . '\x{82AD}\x{82AE}\x{82AF}\x{82B0}\x{82B1}\x{82B3}\x{82B4}\x{82B5}\x{82B6}' + . '\x{82B7}\x{82B8}\x{82B9}\x{82BA}\x{82BB}\x{82BC}\x{82BD}\x{82BE}\x{82BF}' + . '\x{82C0}\x{82C1}\x{82C2}\x{82C3}\x{82C4}\x{82C5}\x{82C6}\x{82C7}\x{82C8}' + . '\x{82C9}\x{82CA}\x{82CB}\x{82CC}\x{82CD}\x{82CE}\x{82CF}\x{82D0}\x{82D1}' + . '\x{82D2}\x{82D3}\x{82D4}\x{82D5}\x{82D6}\x{82D7}\x{82D8}\x{82D9}\x{82DA}' + . '\x{82DB}\x{82DC}\x{82DD}\x{82DE}\x{82DF}\x{82E0}\x{82E1}\x{82E3}\x{82E4}' + . '\x{82E5}\x{82E6}\x{82E7}\x{82E8}\x{82E9}\x{82EA}\x{82EB}\x{82EC}\x{82ED}' + . '\x{82EE}\x{82EF}\x{82F0}\x{82F1}\x{82F2}\x{82F3}\x{82F4}\x{82F5}\x{82F6}' + . '\x{82F7}\x{82F8}\x{82F9}\x{82FA}\x{82FB}\x{82FD}\x{82FE}\x{82FF}\x{8300}' + . '\x{8301}\x{8302}\x{8303}\x{8304}\x{8305}\x{8306}\x{8307}\x{8308}\x{8309}' + . '\x{830B}\x{830C}\x{830D}\x{830E}\x{830F}\x{8311}\x{8312}\x{8313}\x{8314}' + . '\x{8315}\x{8316}\x{8317}\x{8318}\x{8319}\x{831A}\x{831B}\x{831C}\x{831D}' + . '\x{831E}\x{831F}\x{8320}\x{8321}\x{8322}\x{8323}\x{8324}\x{8325}\x{8326}' + . '\x{8327}\x{8328}\x{8329}\x{832A}\x{832B}\x{832C}\x{832D}\x{832E}\x{832F}' + . '\x{8331}\x{8332}\x{8333}\x{8334}\x{8335}\x{8336}\x{8337}\x{8338}\x{8339}' + . '\x{833A}\x{833B}\x{833C}\x{833D}\x{833E}\x{833F}\x{8340}\x{8341}\x{8342}' + . '\x{8343}\x{8344}\x{8345}\x{8346}\x{8347}\x{8348}\x{8349}\x{834A}\x{834B}' + . '\x{834C}\x{834D}\x{834E}\x{834F}\x{8350}\x{8351}\x{8352}\x{8353}\x{8354}' + . '\x{8356}\x{8357}\x{8358}\x{8359}\x{835A}\x{835B}\x{835C}\x{835D}\x{835E}' + . '\x{835F}\x{8360}\x{8361}\x{8362}\x{8363}\x{8364}\x{8365}\x{8366}\x{8367}' + . '\x{8368}\x{8369}\x{836A}\x{836B}\x{836C}\x{836D}\x{836E}\x{836F}\x{8370}' + . '\x{8371}\x{8372}\x{8373}\x{8374}\x{8375}\x{8376}\x{8377}\x{8378}\x{8379}' + . '\x{837A}\x{837B}\x{837C}\x{837D}\x{837E}\x{837F}\x{8380}\x{8381}\x{8382}' + . '\x{8383}\x{8384}\x{8385}\x{8386}\x{8387}\x{8388}\x{8389}\x{838A}\x{838B}' + . '\x{838C}\x{838D}\x{838E}\x{838F}\x{8390}\x{8391}\x{8392}\x{8393}\x{8394}' + . '\x{8395}\x{8396}\x{8397}\x{8398}\x{8399}\x{839A}\x{839B}\x{839C}\x{839D}' + . '\x{839E}\x{83A0}\x{83A1}\x{83A2}\x{83A3}\x{83A4}\x{83A5}\x{83A6}\x{83A7}' + . '\x{83A8}\x{83A9}\x{83AA}\x{83AB}\x{83AC}\x{83AD}\x{83AE}\x{83AF}\x{83B0}' + . '\x{83B1}\x{83B2}\x{83B3}\x{83B4}\x{83B6}\x{83B7}\x{83B8}\x{83B9}\x{83BA}' + . '\x{83BB}\x{83BC}\x{83BD}\x{83BF}\x{83C0}\x{83C1}\x{83C2}\x{83C3}\x{83C4}' + . '\x{83C5}\x{83C6}\x{83C7}\x{83C8}\x{83C9}\x{83CA}\x{83CB}\x{83CC}\x{83CD}' + . '\x{83CE}\x{83CF}\x{83D0}\x{83D1}\x{83D2}\x{83D3}\x{83D4}\x{83D5}\x{83D6}' + . '\x{83D7}\x{83D8}\x{83D9}\x{83DA}\x{83DB}\x{83DC}\x{83DD}\x{83DE}\x{83DF}' + . '\x{83E0}\x{83E1}\x{83E2}\x{83E3}\x{83E4}\x{83E5}\x{83E7}\x{83E8}\x{83E9}' + . '\x{83EA}\x{83EB}\x{83EC}\x{83EE}\x{83EF}\x{83F0}\x{83F1}\x{83F2}\x{83F3}' + . '\x{83F4}\x{83F5}\x{83F6}\x{83F7}\x{83F8}\x{83F9}\x{83FA}\x{83FB}\x{83FC}' + . '\x{83FD}\x{83FE}\x{83FF}\x{8400}\x{8401}\x{8402}\x{8403}\x{8404}\x{8405}' + . '\x{8406}\x{8407}\x{8408}\x{8409}\x{840A}\x{840B}\x{840C}\x{840D}\x{840E}' + . '\x{840F}\x{8410}\x{8411}\x{8412}\x{8413}\x{8415}\x{8418}\x{8419}\x{841A}' + . '\x{841B}\x{841C}\x{841D}\x{841E}\x{8421}\x{8422}\x{8423}\x{8424}\x{8425}' + . '\x{8426}\x{8427}\x{8428}\x{8429}\x{842A}\x{842B}\x{842C}\x{842D}\x{842E}' + . '\x{842F}\x{8430}\x{8431}\x{8432}\x{8433}\x{8434}\x{8435}\x{8436}\x{8437}' + . '\x{8438}\x{8439}\x{843A}\x{843B}\x{843C}\x{843D}\x{843E}\x{843F}\x{8440}' + . '\x{8441}\x{8442}\x{8443}\x{8444}\x{8445}\x{8446}\x{8447}\x{8448}\x{8449}' + . '\x{844A}\x{844B}\x{844C}\x{844D}\x{844E}\x{844F}\x{8450}\x{8451}\x{8452}' + . '\x{8453}\x{8454}\x{8455}\x{8456}\x{8457}\x{8459}\x{845A}\x{845B}\x{845C}' + . '\x{845D}\x{845E}\x{845F}\x{8460}\x{8461}\x{8462}\x{8463}\x{8464}\x{8465}' + . '\x{8466}\x{8467}\x{8468}\x{8469}\x{846A}\x{846B}\x{846C}\x{846D}\x{846E}' + . '\x{846F}\x{8470}\x{8471}\x{8472}\x{8473}\x{8474}\x{8475}\x{8476}\x{8477}' + . '\x{8478}\x{8479}\x{847A}\x{847B}\x{847C}\x{847D}\x{847E}\x{847F}\x{8480}' + . '\x{8481}\x{8482}\x{8484}\x{8485}\x{8486}\x{8487}\x{8488}\x{8489}\x{848A}' + . '\x{848B}\x{848C}\x{848D}\x{848E}\x{848F}\x{8490}\x{8491}\x{8492}\x{8493}' + . '\x{8494}\x{8496}\x{8497}\x{8498}\x{8499}\x{849A}\x{849B}\x{849C}\x{849D}' + . '\x{849E}\x{849F}\x{84A0}\x{84A1}\x{84A2}\x{84A3}\x{84A4}\x{84A5}\x{84A6}' + . '\x{84A7}\x{84A8}\x{84A9}\x{84AA}\x{84AB}\x{84AC}\x{84AE}\x{84AF}\x{84B0}' + . '\x{84B1}\x{84B2}\x{84B3}\x{84B4}\x{84B5}\x{84B6}\x{84B8}\x{84B9}\x{84BA}' + . '\x{84BB}\x{84BC}\x{84BD}\x{84BE}\x{84BF}\x{84C0}\x{84C1}\x{84C2}\x{84C4}' + . '\x{84C5}\x{84C6}\x{84C7}\x{84C8}\x{84C9}\x{84CA}\x{84CB}\x{84CC}\x{84CD}' + . '\x{84CE}\x{84CF}\x{84D0}\x{84D1}\x{84D2}\x{84D3}\x{84D4}\x{84D5}\x{84D6}' + . '\x{84D7}\x{84D8}\x{84D9}\x{84DB}\x{84DC}\x{84DD}\x{84DE}\x{84DF}\x{84E0}' + . '\x{84E1}\x{84E2}\x{84E3}\x{84E4}\x{84E5}\x{84E6}\x{84E7}\x{84E8}\x{84E9}' + . '\x{84EA}\x{84EB}\x{84EC}\x{84EE}\x{84EF}\x{84F0}\x{84F1}\x{84F2}\x{84F3}' + . '\x{84F4}\x{84F5}\x{84F6}\x{84F7}\x{84F8}\x{84F9}\x{84FA}\x{84FB}\x{84FC}' + . '\x{84FD}\x{84FE}\x{84FF}\x{8500}\x{8501}\x{8502}\x{8503}\x{8504}\x{8506}' + . '\x{8507}\x{8508}\x{8509}\x{850A}\x{850B}\x{850C}\x{850D}\x{850E}\x{850F}' + . '\x{8511}\x{8512}\x{8513}\x{8514}\x{8515}\x{8516}\x{8517}\x{8518}\x{8519}' + . '\x{851A}\x{851B}\x{851C}\x{851D}\x{851E}\x{851F}\x{8520}\x{8521}\x{8522}' + . '\x{8523}\x{8524}\x{8525}\x{8526}\x{8527}\x{8528}\x{8529}\x{852A}\x{852B}' + . '\x{852C}\x{852D}\x{852E}\x{852F}\x{8530}\x{8531}\x{8534}\x{8535}\x{8536}' + . '\x{8537}\x{8538}\x{8539}\x{853A}\x{853B}\x{853C}\x{853D}\x{853E}\x{853F}' + . '\x{8540}\x{8541}\x{8542}\x{8543}\x{8544}\x{8545}\x{8546}\x{8547}\x{8548}' + . '\x{8549}\x{854A}\x{854B}\x{854D}\x{854E}\x{854F}\x{8551}\x{8552}\x{8553}' + . '\x{8554}\x{8555}\x{8556}\x{8557}\x{8558}\x{8559}\x{855A}\x{855B}\x{855C}' + . '\x{855D}\x{855E}\x{855F}\x{8560}\x{8561}\x{8562}\x{8563}\x{8564}\x{8565}' + . '\x{8566}\x{8567}\x{8568}\x{8569}\x{856A}\x{856B}\x{856C}\x{856D}\x{856E}' + . '\x{856F}\x{8570}\x{8571}\x{8572}\x{8573}\x{8574}\x{8575}\x{8576}\x{8577}' + . '\x{8578}\x{8579}\x{857A}\x{857B}\x{857C}\x{857D}\x{857E}\x{8580}\x{8581}' + . '\x{8582}\x{8583}\x{8584}\x{8585}\x{8586}\x{8587}\x{8588}\x{8589}\x{858A}' + . '\x{858B}\x{858C}\x{858D}\x{858E}\x{858F}\x{8590}\x{8591}\x{8592}\x{8594}' + . '\x{8595}\x{8596}\x{8598}\x{8599}\x{859A}\x{859B}\x{859C}\x{859D}\x{859E}' + . '\x{859F}\x{85A0}\x{85A1}\x{85A2}\x{85A3}\x{85A4}\x{85A5}\x{85A6}\x{85A7}' + . '\x{85A8}\x{85A9}\x{85AA}\x{85AB}\x{85AC}\x{85AD}\x{85AE}\x{85AF}\x{85B0}' + . '\x{85B1}\x{85B3}\x{85B4}\x{85B5}\x{85B6}\x{85B7}\x{85B8}\x{85B9}\x{85BA}' + . '\x{85BC}\x{85BD}\x{85BE}\x{85BF}\x{85C0}\x{85C1}\x{85C2}\x{85C3}\x{85C4}' + . '\x{85C5}\x{85C6}\x{85C7}\x{85C8}\x{85C9}\x{85CA}\x{85CB}\x{85CD}\x{85CE}' + . '\x{85CF}\x{85D0}\x{85D1}\x{85D2}\x{85D3}\x{85D4}\x{85D5}\x{85D6}\x{85D7}' + . '\x{85D8}\x{85D9}\x{85DA}\x{85DB}\x{85DC}\x{85DD}\x{85DE}\x{85DF}\x{85E0}' + . '\x{85E1}\x{85E2}\x{85E3}\x{85E4}\x{85E5}\x{85E6}\x{85E7}\x{85E8}\x{85E9}' + . '\x{85EA}\x{85EB}\x{85EC}\x{85ED}\x{85EF}\x{85F0}\x{85F1}\x{85F2}\x{85F4}' + . '\x{85F5}\x{85F6}\x{85F7}\x{85F8}\x{85F9}\x{85FA}\x{85FB}\x{85FD}\x{85FE}' + . '\x{85FF}\x{8600}\x{8601}\x{8602}\x{8604}\x{8605}\x{8606}\x{8607}\x{8608}' + . '\x{8609}\x{860A}\x{860B}\x{860C}\x{860F}\x{8611}\x{8612}\x{8613}\x{8614}' + . '\x{8616}\x{8617}\x{8618}\x{8619}\x{861A}\x{861B}\x{861C}\x{861E}\x{861F}' + . '\x{8620}\x{8621}\x{8622}\x{8623}\x{8624}\x{8625}\x{8626}\x{8627}\x{8628}' + . '\x{8629}\x{862A}\x{862B}\x{862C}\x{862D}\x{862E}\x{862F}\x{8630}\x{8631}' + . '\x{8632}\x{8633}\x{8634}\x{8635}\x{8636}\x{8638}\x{8639}\x{863A}\x{863B}' + . '\x{863C}\x{863D}\x{863E}\x{863F}\x{8640}\x{8641}\x{8642}\x{8643}\x{8644}' + . '\x{8645}\x{8646}\x{8647}\x{8648}\x{8649}\x{864A}\x{864B}\x{864C}\x{864D}' + . '\x{864E}\x{864F}\x{8650}\x{8651}\x{8652}\x{8653}\x{8654}\x{8655}\x{8656}' + . '\x{8658}\x{8659}\x{865A}\x{865B}\x{865C}\x{865D}\x{865E}\x{865F}\x{8660}' + . '\x{8661}\x{8662}\x{8663}\x{8664}\x{8665}\x{8666}\x{8667}\x{8668}\x{8669}' + . '\x{866A}\x{866B}\x{866C}\x{866D}\x{866E}\x{866F}\x{8670}\x{8671}\x{8672}' + . '\x{8673}\x{8674}\x{8676}\x{8677}\x{8678}\x{8679}\x{867A}\x{867B}\x{867C}' + . '\x{867D}\x{867E}\x{867F}\x{8680}\x{8681}\x{8682}\x{8683}\x{8684}\x{8685}' + . '\x{8686}\x{8687}\x{8688}\x{868A}\x{868B}\x{868C}\x{868D}\x{868E}\x{868F}' + . '\x{8690}\x{8691}\x{8693}\x{8694}\x{8695}\x{8696}\x{8697}\x{8698}\x{8699}' + . '\x{869A}\x{869B}\x{869C}\x{869D}\x{869E}\x{869F}\x{86A1}\x{86A2}\x{86A3}' + . '\x{86A4}\x{86A5}\x{86A7}\x{86A8}\x{86A9}\x{86AA}\x{86AB}\x{86AC}\x{86AD}' + . '\x{86AE}\x{86AF}\x{86B0}\x{86B1}\x{86B2}\x{86B3}\x{86B4}\x{86B5}\x{86B6}' + . '\x{86B7}\x{86B8}\x{86B9}\x{86BA}\x{86BB}\x{86BC}\x{86BD}\x{86BE}\x{86BF}' + . '\x{86C0}\x{86C1}\x{86C2}\x{86C3}\x{86C4}\x{86C5}\x{86C6}\x{86C7}\x{86C8}' + . '\x{86C9}\x{86CA}\x{86CB}\x{86CC}\x{86CE}\x{86CF}\x{86D0}\x{86D1}\x{86D2}' + . '\x{86D3}\x{86D4}\x{86D6}\x{86D7}\x{86D8}\x{86D9}\x{86DA}\x{86DB}\x{86DC}' + . '\x{86DD}\x{86DE}\x{86DF}\x{86E1}\x{86E2}\x{86E3}\x{86E4}\x{86E5}\x{86E6}' + . '\x{86E8}\x{86E9}\x{86EA}\x{86EB}\x{86EC}\x{86ED}\x{86EE}\x{86EF}\x{86F0}' + . '\x{86F1}\x{86F2}\x{86F3}\x{86F4}\x{86F5}\x{86F6}\x{86F7}\x{86F8}\x{86F9}' + . '\x{86FA}\x{86FB}\x{86FC}\x{86FE}\x{86FF}\x{8700}\x{8701}\x{8702}\x{8703}' + . '\x{8704}\x{8705}\x{8706}\x{8707}\x{8708}\x{8709}\x{870A}\x{870B}\x{870C}' + . '\x{870D}\x{870E}\x{870F}\x{8710}\x{8711}\x{8712}\x{8713}\x{8714}\x{8715}' + . '\x{8716}\x{8717}\x{8718}\x{8719}\x{871A}\x{871B}\x{871C}\x{871E}\x{871F}' + . '\x{8720}\x{8721}\x{8722}\x{8723}\x{8724}\x{8725}\x{8726}\x{8727}\x{8728}' + . '\x{8729}\x{872A}\x{872B}\x{872C}\x{872D}\x{872E}\x{8730}\x{8731}\x{8732}' + . '\x{8733}\x{8734}\x{8735}\x{8736}\x{8737}\x{8738}\x{8739}\x{873A}\x{873B}' + . '\x{873C}\x{873E}\x{873F}\x{8740}\x{8741}\x{8742}\x{8743}\x{8744}\x{8746}' + . '\x{8747}\x{8748}\x{8749}\x{874A}\x{874C}\x{874D}\x{874E}\x{874F}\x{8750}' + . '\x{8751}\x{8752}\x{8753}\x{8754}\x{8755}\x{8756}\x{8757}\x{8758}\x{8759}' + . '\x{875A}\x{875B}\x{875C}\x{875D}\x{875E}\x{875F}\x{8760}\x{8761}\x{8762}' + . '\x{8763}\x{8764}\x{8765}\x{8766}\x{8767}\x{8768}\x{8769}\x{876A}\x{876B}' + . '\x{876C}\x{876D}\x{876E}\x{876F}\x{8770}\x{8772}\x{8773}\x{8774}\x{8775}' + . '\x{8776}\x{8777}\x{8778}\x{8779}\x{877A}\x{877B}\x{877C}\x{877D}\x{877E}' + . '\x{8780}\x{8781}\x{8782}\x{8783}\x{8784}\x{8785}\x{8786}\x{8787}\x{8788}' + . '\x{8789}\x{878A}\x{878B}\x{878C}\x{878D}\x{878F}\x{8790}\x{8791}\x{8792}' + . '\x{8793}\x{8794}\x{8795}\x{8796}\x{8797}\x{8798}\x{879A}\x{879B}\x{879C}' + . '\x{879D}\x{879E}\x{879F}\x{87A0}\x{87A1}\x{87A2}\x{87A3}\x{87A4}\x{87A5}' + . '\x{87A6}\x{87A7}\x{87A8}\x{87A9}\x{87AA}\x{87AB}\x{87AC}\x{87AD}\x{87AE}' + . '\x{87AF}\x{87B0}\x{87B1}\x{87B2}\x{87B3}\x{87B4}\x{87B5}\x{87B6}\x{87B7}' + . '\x{87B8}\x{87B9}\x{87BA}\x{87BB}\x{87BC}\x{87BD}\x{87BE}\x{87BF}\x{87C0}' + . '\x{87C1}\x{87C2}\x{87C3}\x{87C4}\x{87C5}\x{87C6}\x{87C7}\x{87C8}\x{87C9}' + . '\x{87CA}\x{87CB}\x{87CC}\x{87CD}\x{87CE}\x{87CF}\x{87D0}\x{87D1}\x{87D2}' + . '\x{87D3}\x{87D4}\x{87D5}\x{87D6}\x{87D7}\x{87D8}\x{87D9}\x{87DB}\x{87DC}' + . '\x{87DD}\x{87DE}\x{87DF}\x{87E0}\x{87E1}\x{87E2}\x{87E3}\x{87E4}\x{87E5}' + . '\x{87E6}\x{87E7}\x{87E8}\x{87E9}\x{87EA}\x{87EB}\x{87EC}\x{87ED}\x{87EE}' + . '\x{87EF}\x{87F1}\x{87F2}\x{87F3}\x{87F4}\x{87F5}\x{87F6}\x{87F7}\x{87F8}' + . '\x{87F9}\x{87FA}\x{87FB}\x{87FC}\x{87FD}\x{87FE}\x{87FF}\x{8800}\x{8801}' + . '\x{8802}\x{8803}\x{8804}\x{8805}\x{8806}\x{8808}\x{8809}\x{880A}\x{880B}' + . '\x{880C}\x{880D}\x{880E}\x{880F}\x{8810}\x{8811}\x{8813}\x{8814}\x{8815}' + . '\x{8816}\x{8817}\x{8818}\x{8819}\x{881A}\x{881B}\x{881C}\x{881D}\x{881E}' + . '\x{881F}\x{8820}\x{8821}\x{8822}\x{8823}\x{8824}\x{8825}\x{8826}\x{8827}' + . '\x{8828}\x{8829}\x{882A}\x{882B}\x{882C}\x{882E}\x{882F}\x{8830}\x{8831}' + . '\x{8832}\x{8833}\x{8834}\x{8835}\x{8836}\x{8837}\x{8838}\x{8839}\x{883B}' + . '\x{883C}\x{883D}\x{883E}\x{883F}\x{8840}\x{8841}\x{8842}\x{8843}\x{8844}' + . '\x{8845}\x{8846}\x{8848}\x{8849}\x{884A}\x{884B}\x{884C}\x{884D}\x{884E}' + . '\x{884F}\x{8850}\x{8851}\x{8852}\x{8853}\x{8854}\x{8855}\x{8856}\x{8857}' + . '\x{8859}\x{885A}\x{885B}\x{885D}\x{885E}\x{8860}\x{8861}\x{8862}\x{8863}' + . '\x{8864}\x{8865}\x{8866}\x{8867}\x{8868}\x{8869}\x{886A}\x{886B}\x{886C}' + . '\x{886D}\x{886E}\x{886F}\x{8870}\x{8871}\x{8872}\x{8873}\x{8874}\x{8875}' + . '\x{8876}\x{8877}\x{8878}\x{8879}\x{887B}\x{887C}\x{887D}\x{887E}\x{887F}' + . '\x{8880}\x{8881}\x{8882}\x{8883}\x{8884}\x{8885}\x{8886}\x{8887}\x{8888}' + . '\x{8889}\x{888A}\x{888B}\x{888C}\x{888D}\x{888E}\x{888F}\x{8890}\x{8891}' + . '\x{8892}\x{8893}\x{8894}\x{8895}\x{8896}\x{8897}\x{8898}\x{8899}\x{889A}' + . '\x{889B}\x{889C}\x{889D}\x{889E}\x{889F}\x{88A0}\x{88A1}\x{88A2}\x{88A3}' + . '\x{88A4}\x{88A5}\x{88A6}\x{88A7}\x{88A8}\x{88A9}\x{88AA}\x{88AB}\x{88AC}' + . '\x{88AD}\x{88AE}\x{88AF}\x{88B0}\x{88B1}\x{88B2}\x{88B3}\x{88B4}\x{88B6}' + . '\x{88B7}\x{88B8}\x{88B9}\x{88BA}\x{88BB}\x{88BC}\x{88BD}\x{88BE}\x{88BF}' + . '\x{88C0}\x{88C1}\x{88C2}\x{88C3}\x{88C4}\x{88C5}\x{88C6}\x{88C7}\x{88C8}' + . '\x{88C9}\x{88CA}\x{88CB}\x{88CC}\x{88CD}\x{88CE}\x{88CF}\x{88D0}\x{88D1}' + . '\x{88D2}\x{88D3}\x{88D4}\x{88D5}\x{88D6}\x{88D7}\x{88D8}\x{88D9}\x{88DA}' + . '\x{88DB}\x{88DC}\x{88DD}\x{88DE}\x{88DF}\x{88E0}\x{88E1}\x{88E2}\x{88E3}' + . '\x{88E4}\x{88E5}\x{88E7}\x{88E8}\x{88EA}\x{88EB}\x{88EC}\x{88EE}\x{88EF}' + . '\x{88F0}\x{88F1}\x{88F2}\x{88F3}\x{88F4}\x{88F5}\x{88F6}\x{88F7}\x{88F8}' + . '\x{88F9}\x{88FA}\x{88FB}\x{88FC}\x{88FD}\x{88FE}\x{88FF}\x{8900}\x{8901}' + . '\x{8902}\x{8904}\x{8905}\x{8906}\x{8907}\x{8908}\x{8909}\x{890A}\x{890B}' + . '\x{890C}\x{890D}\x{890E}\x{8910}\x{8911}\x{8912}\x{8913}\x{8914}\x{8915}' + . '\x{8916}\x{8917}\x{8918}\x{8919}\x{891A}\x{891B}\x{891C}\x{891D}\x{891E}' + . '\x{891F}\x{8920}\x{8921}\x{8922}\x{8923}\x{8925}\x{8926}\x{8927}\x{8928}' + . '\x{8929}\x{892A}\x{892B}\x{892C}\x{892D}\x{892E}\x{892F}\x{8930}\x{8931}' + . '\x{8932}\x{8933}\x{8934}\x{8935}\x{8936}\x{8937}\x{8938}\x{8939}\x{893A}' + . '\x{893B}\x{893C}\x{893D}\x{893E}\x{893F}\x{8940}\x{8941}\x{8942}\x{8943}' + . '\x{8944}\x{8945}\x{8946}\x{8947}\x{8948}\x{8949}\x{894A}\x{894B}\x{894C}' + . '\x{894E}\x{894F}\x{8950}\x{8951}\x{8952}\x{8953}\x{8954}\x{8955}\x{8956}' + . '\x{8957}\x{8958}\x{8959}\x{895A}\x{895B}\x{895C}\x{895D}\x{895E}\x{895F}' + . '\x{8960}\x{8961}\x{8962}\x{8963}\x{8964}\x{8966}\x{8967}\x{8968}\x{8969}' + . '\x{896A}\x{896B}\x{896C}\x{896D}\x{896E}\x{896F}\x{8970}\x{8971}\x{8972}' + . '\x{8973}\x{8974}\x{8976}\x{8977}\x{8978}\x{8979}\x{897A}\x{897B}\x{897C}' + . '\x{897E}\x{897F}\x{8980}\x{8981}\x{8982}\x{8983}\x{8984}\x{8985}\x{8986}' + . '\x{8987}\x{8988}\x{8989}\x{898A}\x{898B}\x{898C}\x{898E}\x{898F}\x{8991}' + . '\x{8992}\x{8993}\x{8995}\x{8996}\x{8997}\x{8998}\x{899A}\x{899B}\x{899C}' + . '\x{899D}\x{899E}\x{899F}\x{89A0}\x{89A1}\x{89A2}\x{89A3}\x{89A4}\x{89A5}' + . '\x{89A6}\x{89A7}\x{89A8}\x{89AA}\x{89AB}\x{89AC}\x{89AD}\x{89AE}\x{89AF}' + . '\x{89B1}\x{89B2}\x{89B3}\x{89B5}\x{89B6}\x{89B7}\x{89B8}\x{89B9}\x{89BA}' + . '\x{89BD}\x{89BE}\x{89BF}\x{89C0}\x{89C1}\x{89C2}\x{89C3}\x{89C4}\x{89C5}' + . '\x{89C6}\x{89C7}\x{89C8}\x{89C9}\x{89CA}\x{89CB}\x{89CC}\x{89CD}\x{89CE}' + . '\x{89CF}\x{89D0}\x{89D1}\x{89D2}\x{89D3}\x{89D4}\x{89D5}\x{89D6}\x{89D7}' + . '\x{89D8}\x{89D9}\x{89DA}\x{89DB}\x{89DC}\x{89DD}\x{89DE}\x{89DF}\x{89E0}' + . '\x{89E1}\x{89E2}\x{89E3}\x{89E4}\x{89E5}\x{89E6}\x{89E7}\x{89E8}\x{89E9}' + . '\x{89EA}\x{89EB}\x{89EC}\x{89ED}\x{89EF}\x{89F0}\x{89F1}\x{89F2}\x{89F3}' + . '\x{89F4}\x{89F6}\x{89F7}\x{89F8}\x{89FA}\x{89FB}\x{89FC}\x{89FE}\x{89FF}' + . '\x{8A00}\x{8A01}\x{8A02}\x{8A03}\x{8A04}\x{8A07}\x{8A08}\x{8A09}\x{8A0A}' + . '\x{8A0B}\x{8A0C}\x{8A0D}\x{8A0E}\x{8A0F}\x{8A10}\x{8A11}\x{8A12}\x{8A13}' + . '\x{8A15}\x{8A16}\x{8A17}\x{8A18}\x{8A1A}\x{8A1B}\x{8A1C}\x{8A1D}\x{8A1E}' + . '\x{8A1F}\x{8A22}\x{8A23}\x{8A24}\x{8A25}\x{8A26}\x{8A27}\x{8A28}\x{8A29}' + . '\x{8A2A}\x{8A2C}\x{8A2D}\x{8A2E}\x{8A2F}\x{8A30}\x{8A31}\x{8A32}\x{8A34}' + . '\x{8A35}\x{8A36}\x{8A37}\x{8A38}\x{8A39}\x{8A3A}\x{8A3B}\x{8A3C}\x{8A3E}' + . '\x{8A3F}\x{8A40}\x{8A41}\x{8A42}\x{8A43}\x{8A44}\x{8A45}\x{8A46}\x{8A47}' + . '\x{8A48}\x{8A49}\x{8A4A}\x{8A4C}\x{8A4D}\x{8A4E}\x{8A4F}\x{8A50}\x{8A51}' + . '\x{8A52}\x{8A53}\x{8A54}\x{8A55}\x{8A56}\x{8A57}\x{8A58}\x{8A59}\x{8A5A}' + . '\x{8A5B}\x{8A5C}\x{8A5D}\x{8A5E}\x{8A5F}\x{8A60}\x{8A61}\x{8A62}\x{8A63}' + . '\x{8A65}\x{8A66}\x{8A67}\x{8A68}\x{8A69}\x{8A6A}\x{8A6B}\x{8A6C}\x{8A6D}' + . '\x{8A6E}\x{8A6F}\x{8A70}\x{8A71}\x{8A72}\x{8A73}\x{8A74}\x{8A75}\x{8A76}' + . '\x{8A77}\x{8A79}\x{8A7A}\x{8A7B}\x{8A7C}\x{8A7E}\x{8A7F}\x{8A80}\x{8A81}' + . '\x{8A82}\x{8A83}\x{8A84}\x{8A85}\x{8A86}\x{8A87}\x{8A89}\x{8A8A}\x{8A8B}' + . '\x{8A8C}\x{8A8D}\x{8A8E}\x{8A8F}\x{8A90}\x{8A91}\x{8A92}\x{8A93}\x{8A94}' + . '\x{8A95}\x{8A96}\x{8A97}\x{8A98}\x{8A99}\x{8A9A}\x{8A9B}\x{8A9C}\x{8A9D}' + . '\x{8A9E}\x{8AA0}\x{8AA1}\x{8AA2}\x{8AA3}\x{8AA4}\x{8AA5}\x{8AA6}\x{8AA7}' + . '\x{8AA8}\x{8AA9}\x{8AAA}\x{8AAB}\x{8AAC}\x{8AAE}\x{8AB0}\x{8AB1}\x{8AB2}' + . '\x{8AB3}\x{8AB4}\x{8AB5}\x{8AB6}\x{8AB8}\x{8AB9}\x{8ABA}\x{8ABB}\x{8ABC}' + . '\x{8ABD}\x{8ABE}\x{8ABF}\x{8AC0}\x{8AC1}\x{8AC2}\x{8AC3}\x{8AC4}\x{8AC5}' + . '\x{8AC6}\x{8AC7}\x{8AC8}\x{8AC9}\x{8ACA}\x{8ACB}\x{8ACC}\x{8ACD}\x{8ACE}' + . '\x{8ACF}\x{8AD1}\x{8AD2}\x{8AD3}\x{8AD4}\x{8AD5}\x{8AD6}\x{8AD7}\x{8AD8}' + . '\x{8AD9}\x{8ADA}\x{8ADB}\x{8ADC}\x{8ADD}\x{8ADE}\x{8ADF}\x{8AE0}\x{8AE1}' + . '\x{8AE2}\x{8AE3}\x{8AE4}\x{8AE5}\x{8AE6}\x{8AE7}\x{8AE8}\x{8AE9}\x{8AEA}' + . '\x{8AEB}\x{8AED}\x{8AEE}\x{8AEF}\x{8AF0}\x{8AF1}\x{8AF2}\x{8AF3}\x{8AF4}' + . '\x{8AF5}\x{8AF6}\x{8AF7}\x{8AF8}\x{8AF9}\x{8AFA}\x{8AFB}\x{8AFC}\x{8AFD}' + . '\x{8AFE}\x{8AFF}\x{8B00}\x{8B01}\x{8B02}\x{8B03}\x{8B04}\x{8B05}\x{8B06}' + . '\x{8B07}\x{8B08}\x{8B09}\x{8B0A}\x{8B0B}\x{8B0D}\x{8B0E}\x{8B0F}\x{8B10}' + . '\x{8B11}\x{8B12}\x{8B13}\x{8B14}\x{8B15}\x{8B16}\x{8B17}\x{8B18}\x{8B19}' + . '\x{8B1A}\x{8B1B}\x{8B1C}\x{8B1D}\x{8B1E}\x{8B1F}\x{8B20}\x{8B21}\x{8B22}' + . '\x{8B23}\x{8B24}\x{8B25}\x{8B26}\x{8B27}\x{8B28}\x{8B2A}\x{8B2B}\x{8B2C}' + . '\x{8B2D}\x{8B2E}\x{8B2F}\x{8B30}\x{8B31}\x{8B33}\x{8B34}\x{8B35}\x{8B36}' + . '\x{8B37}\x{8B39}\x{8B3A}\x{8B3B}\x{8B3C}\x{8B3D}\x{8B3E}\x{8B40}\x{8B41}' + . '\x{8B42}\x{8B43}\x{8B44}\x{8B45}\x{8B46}\x{8B47}\x{8B48}\x{8B49}\x{8B4A}' + . '\x{8B4B}\x{8B4C}\x{8B4D}\x{8B4E}\x{8B4F}\x{8B50}\x{8B51}\x{8B52}\x{8B53}' + . '\x{8B54}\x{8B55}\x{8B56}\x{8B57}\x{8B58}\x{8B59}\x{8B5A}\x{8B5B}\x{8B5C}' + . '\x{8B5D}\x{8B5E}\x{8B5F}\x{8B60}\x{8B63}\x{8B64}\x{8B65}\x{8B66}\x{8B67}' + . '\x{8B68}\x{8B6A}\x{8B6B}\x{8B6C}\x{8B6D}\x{8B6E}\x{8B6F}\x{8B70}\x{8B71}' + . '\x{8B73}\x{8B74}\x{8B76}\x{8B77}\x{8B78}\x{8B79}\x{8B7A}\x{8B7B}\x{8B7D}' + . '\x{8B7E}\x{8B7F}\x{8B80}\x{8B82}\x{8B83}\x{8B84}\x{8B85}\x{8B86}\x{8B88}' + . '\x{8B89}\x{8B8A}\x{8B8B}\x{8B8C}\x{8B8E}\x{8B90}\x{8B91}\x{8B92}\x{8B93}' + . '\x{8B94}\x{8B95}\x{8B96}\x{8B97}\x{8B98}\x{8B99}\x{8B9A}\x{8B9C}\x{8B9D}' + . '\x{8B9E}\x{8B9F}\x{8BA0}\x{8BA1}\x{8BA2}\x{8BA3}\x{8BA4}\x{8BA5}\x{8BA6}' + . '\x{8BA7}\x{8BA8}\x{8BA9}\x{8BAA}\x{8BAB}\x{8BAC}\x{8BAD}\x{8BAE}\x{8BAF}' + . '\x{8BB0}\x{8BB1}\x{8BB2}\x{8BB3}\x{8BB4}\x{8BB5}\x{8BB6}\x{8BB7}\x{8BB8}' + . '\x{8BB9}\x{8BBA}\x{8BBB}\x{8BBC}\x{8BBD}\x{8BBE}\x{8BBF}\x{8BC0}\x{8BC1}' + . '\x{8BC2}\x{8BC3}\x{8BC4}\x{8BC5}\x{8BC6}\x{8BC7}\x{8BC8}\x{8BC9}\x{8BCA}' + . '\x{8BCB}\x{8BCC}\x{8BCD}\x{8BCE}\x{8BCF}\x{8BD0}\x{8BD1}\x{8BD2}\x{8BD3}' + . '\x{8BD4}\x{8BD5}\x{8BD6}\x{8BD7}\x{8BD8}\x{8BD9}\x{8BDA}\x{8BDB}\x{8BDC}' + . '\x{8BDD}\x{8BDE}\x{8BDF}\x{8BE0}\x{8BE1}\x{8BE2}\x{8BE3}\x{8BE4}\x{8BE5}' + . '\x{8BE6}\x{8BE7}\x{8BE8}\x{8BE9}\x{8BEA}\x{8BEB}\x{8BEC}\x{8BED}\x{8BEE}' + . '\x{8BEF}\x{8BF0}\x{8BF1}\x{8BF2}\x{8BF3}\x{8BF4}\x{8BF5}\x{8BF6}\x{8BF7}' + . '\x{8BF8}\x{8BF9}\x{8BFA}\x{8BFB}\x{8BFC}\x{8BFD}\x{8BFE}\x{8BFF}\x{8C00}' + . '\x{8C01}\x{8C02}\x{8C03}\x{8C04}\x{8C05}\x{8C06}\x{8C07}\x{8C08}\x{8C09}' + . '\x{8C0A}\x{8C0B}\x{8C0C}\x{8C0D}\x{8C0E}\x{8C0F}\x{8C10}\x{8C11}\x{8C12}' + . '\x{8C13}\x{8C14}\x{8C15}\x{8C16}\x{8C17}\x{8C18}\x{8C19}\x{8C1A}\x{8C1B}' + . '\x{8C1C}\x{8C1D}\x{8C1E}\x{8C1F}\x{8C20}\x{8C21}\x{8C22}\x{8C23}\x{8C24}' + . '\x{8C25}\x{8C26}\x{8C27}\x{8C28}\x{8C29}\x{8C2A}\x{8C2B}\x{8C2C}\x{8C2D}' + . '\x{8C2E}\x{8C2F}\x{8C30}\x{8C31}\x{8C32}\x{8C33}\x{8C34}\x{8C35}\x{8C36}' + . '\x{8C37}\x{8C39}\x{8C3A}\x{8C3B}\x{8C3C}\x{8C3D}\x{8C3E}\x{8C3F}\x{8C41}' + . '\x{8C42}\x{8C43}\x{8C45}\x{8C46}\x{8C47}\x{8C48}\x{8C49}\x{8C4A}\x{8C4B}' + . '\x{8C4C}\x{8C4D}\x{8C4E}\x{8C4F}\x{8C50}\x{8C54}\x{8C55}\x{8C56}\x{8C57}' + . '\x{8C59}\x{8C5A}\x{8C5B}\x{8C5C}\x{8C5D}\x{8C5E}\x{8C5F}\x{8C60}\x{8C61}' + . '\x{8C62}\x{8C63}\x{8C64}\x{8C65}\x{8C66}\x{8C67}\x{8C68}\x{8C69}\x{8C6A}' + . '\x{8C6B}\x{8C6C}\x{8C6D}\x{8C6E}\x{8C6F}\x{8C70}\x{8C71}\x{8C72}\x{8C73}' + . '\x{8C75}\x{8C76}\x{8C77}\x{8C78}\x{8C79}\x{8C7A}\x{8C7B}\x{8C7D}\x{8C7E}' + . '\x{8C80}\x{8C81}\x{8C82}\x{8C84}\x{8C85}\x{8C86}\x{8C88}\x{8C89}\x{8C8A}' + . '\x{8C8C}\x{8C8D}\x{8C8F}\x{8C90}\x{8C91}\x{8C92}\x{8C93}\x{8C94}\x{8C95}' + . '\x{8C96}\x{8C97}\x{8C98}\x{8C99}\x{8C9A}\x{8C9C}\x{8C9D}\x{8C9E}\x{8C9F}' + . '\x{8CA0}\x{8CA1}\x{8CA2}\x{8CA3}\x{8CA4}\x{8CA5}\x{8CA7}\x{8CA8}\x{8CA9}' + . '\x{8CAA}\x{8CAB}\x{8CAC}\x{8CAD}\x{8CAE}\x{8CAF}\x{8CB0}\x{8CB1}\x{8CB2}' + . '\x{8CB3}\x{8CB4}\x{8CB5}\x{8CB6}\x{8CB7}\x{8CB8}\x{8CB9}\x{8CBA}\x{8CBB}' + . '\x{8CBC}\x{8CBD}\x{8CBE}\x{8CBF}\x{8CC0}\x{8CC1}\x{8CC2}\x{8CC3}\x{8CC4}' + . '\x{8CC5}\x{8CC6}\x{8CC7}\x{8CC8}\x{8CC9}\x{8CCA}\x{8CCC}\x{8CCE}\x{8CCF}' + . '\x{8CD0}\x{8CD1}\x{8CD2}\x{8CD3}\x{8CD4}\x{8CD5}\x{8CD7}\x{8CD9}\x{8CDA}' + . '\x{8CDB}\x{8CDC}\x{8CDD}\x{8CDE}\x{8CDF}\x{8CE0}\x{8CE1}\x{8CE2}\x{8CE3}' + . '\x{8CE4}\x{8CE5}\x{8CE6}\x{8CE7}\x{8CE8}\x{8CEA}\x{8CEB}\x{8CEC}\x{8CED}' + . '\x{8CEE}\x{8CEF}\x{8CF0}\x{8CF1}\x{8CF2}\x{8CF3}\x{8CF4}\x{8CF5}\x{8CF6}' + . '\x{8CF8}\x{8CF9}\x{8CFA}\x{8CFB}\x{8CFC}\x{8CFD}\x{8CFE}\x{8CFF}\x{8D00}' + . '\x{8D02}\x{8D03}\x{8D04}\x{8D05}\x{8D06}\x{8D07}\x{8D08}\x{8D09}\x{8D0A}' + . '\x{8D0B}\x{8D0C}\x{8D0D}\x{8D0E}\x{8D0F}\x{8D10}\x{8D13}\x{8D14}\x{8D15}' + . '\x{8D16}\x{8D17}\x{8D18}\x{8D19}\x{8D1A}\x{8D1B}\x{8D1C}\x{8D1D}\x{8D1E}' + . '\x{8D1F}\x{8D20}\x{8D21}\x{8D22}\x{8D23}\x{8D24}\x{8D25}\x{8D26}\x{8D27}' + . '\x{8D28}\x{8D29}\x{8D2A}\x{8D2B}\x{8D2C}\x{8D2D}\x{8D2E}\x{8D2F}\x{8D30}' + . '\x{8D31}\x{8D32}\x{8D33}\x{8D34}\x{8D35}\x{8D36}\x{8D37}\x{8D38}\x{8D39}' + . '\x{8D3A}\x{8D3B}\x{8D3C}\x{8D3D}\x{8D3E}\x{8D3F}\x{8D40}\x{8D41}\x{8D42}' + . '\x{8D43}\x{8D44}\x{8D45}\x{8D46}\x{8D47}\x{8D48}\x{8D49}\x{8D4A}\x{8D4B}' + . '\x{8D4C}\x{8D4D}\x{8D4E}\x{8D4F}\x{8D50}\x{8D51}\x{8D52}\x{8D53}\x{8D54}' + . '\x{8D55}\x{8D56}\x{8D57}\x{8D58}\x{8D59}\x{8D5A}\x{8D5B}\x{8D5C}\x{8D5D}' + . '\x{8D5E}\x{8D5F}\x{8D60}\x{8D61}\x{8D62}\x{8D63}\x{8D64}\x{8D65}\x{8D66}' + . '\x{8D67}\x{8D68}\x{8D69}\x{8D6A}\x{8D6B}\x{8D6C}\x{8D6D}\x{8D6E}\x{8D6F}' + . '\x{8D70}\x{8D71}\x{8D72}\x{8D73}\x{8D74}\x{8D75}\x{8D76}\x{8D77}\x{8D78}' + . '\x{8D79}\x{8D7A}\x{8D7B}\x{8D7D}\x{8D7E}\x{8D7F}\x{8D80}\x{8D81}\x{8D82}' + . '\x{8D83}\x{8D84}\x{8D85}\x{8D86}\x{8D87}\x{8D88}\x{8D89}\x{8D8A}\x{8D8B}' + . '\x{8D8C}\x{8D8D}\x{8D8E}\x{8D8F}\x{8D90}\x{8D91}\x{8D92}\x{8D93}\x{8D94}' + . '\x{8D95}\x{8D96}\x{8D97}\x{8D98}\x{8D99}\x{8D9A}\x{8D9B}\x{8D9C}\x{8D9D}' + . '\x{8D9E}\x{8D9F}\x{8DA0}\x{8DA1}\x{8DA2}\x{8DA3}\x{8DA4}\x{8DA5}\x{8DA7}' + . '\x{8DA8}\x{8DA9}\x{8DAA}\x{8DAB}\x{8DAC}\x{8DAD}\x{8DAE}\x{8DAF}\x{8DB0}' + . '\x{8DB1}\x{8DB2}\x{8DB3}\x{8DB4}\x{8DB5}\x{8DB6}\x{8DB7}\x{8DB8}\x{8DB9}' + . '\x{8DBA}\x{8DBB}\x{8DBC}\x{8DBD}\x{8DBE}\x{8DBF}\x{8DC1}\x{8DC2}\x{8DC3}' + . '\x{8DC4}\x{8DC5}\x{8DC6}\x{8DC7}\x{8DC8}\x{8DC9}\x{8DCA}\x{8DCB}\x{8DCC}' + . '\x{8DCD}\x{8DCE}\x{8DCF}\x{8DD0}\x{8DD1}\x{8DD2}\x{8DD3}\x{8DD4}\x{8DD5}' + . '\x{8DD6}\x{8DD7}\x{8DD8}\x{8DD9}\x{8DDA}\x{8DDB}\x{8DDC}\x{8DDD}\x{8DDE}' + . '\x{8DDF}\x{8DE0}\x{8DE1}\x{8DE2}\x{8DE3}\x{8DE4}\x{8DE6}\x{8DE7}\x{8DE8}' + . '\x{8DE9}\x{8DEA}\x{8DEB}\x{8DEC}\x{8DED}\x{8DEE}\x{8DEF}\x{8DF0}\x{8DF1}' + . '\x{8DF2}\x{8DF3}\x{8DF4}\x{8DF5}\x{8DF6}\x{8DF7}\x{8DF8}\x{8DF9}\x{8DFA}' + . '\x{8DFB}\x{8DFC}\x{8DFD}\x{8DFE}\x{8DFF}\x{8E00}\x{8E02}\x{8E03}\x{8E04}' + . '\x{8E05}\x{8E06}\x{8E07}\x{8E08}\x{8E09}\x{8E0A}\x{8E0C}\x{8E0D}\x{8E0E}' + . '\x{8E0F}\x{8E10}\x{8E11}\x{8E12}\x{8E13}\x{8E14}\x{8E15}\x{8E16}\x{8E17}' + . '\x{8E18}\x{8E19}\x{8E1A}\x{8E1B}\x{8E1C}\x{8E1D}\x{8E1E}\x{8E1F}\x{8E20}' + . '\x{8E21}\x{8E22}\x{8E23}\x{8E24}\x{8E25}\x{8E26}\x{8E27}\x{8E28}\x{8E29}' + . '\x{8E2A}\x{8E2B}\x{8E2C}\x{8E2D}\x{8E2E}\x{8E2F}\x{8E30}\x{8E31}\x{8E33}' + . '\x{8E34}\x{8E35}\x{8E36}\x{8E37}\x{8E38}\x{8E39}\x{8E3A}\x{8E3B}\x{8E3C}' + . '\x{8E3D}\x{8E3E}\x{8E3F}\x{8E40}\x{8E41}\x{8E42}\x{8E43}\x{8E44}\x{8E45}' + . '\x{8E47}\x{8E48}\x{8E49}\x{8E4A}\x{8E4B}\x{8E4C}\x{8E4D}\x{8E4E}\x{8E50}' + . '\x{8E51}\x{8E52}\x{8E53}\x{8E54}\x{8E55}\x{8E56}\x{8E57}\x{8E58}\x{8E59}' + . '\x{8E5A}\x{8E5B}\x{8E5C}\x{8E5D}\x{8E5E}\x{8E5F}\x{8E60}\x{8E61}\x{8E62}' + . '\x{8E63}\x{8E64}\x{8E65}\x{8E66}\x{8E67}\x{8E68}\x{8E69}\x{8E6A}\x{8E6B}' + . '\x{8E6C}\x{8E6D}\x{8E6F}\x{8E70}\x{8E71}\x{8E72}\x{8E73}\x{8E74}\x{8E76}' + . '\x{8E78}\x{8E7A}\x{8E7B}\x{8E7C}\x{8E7D}\x{8E7E}\x{8E7F}\x{8E80}\x{8E81}' + . '\x{8E82}\x{8E83}\x{8E84}\x{8E85}\x{8E86}\x{8E87}\x{8E88}\x{8E89}\x{8E8A}' + . '\x{8E8B}\x{8E8C}\x{8E8D}\x{8E8E}\x{8E8F}\x{8E90}\x{8E91}\x{8E92}\x{8E93}' + . '\x{8E94}\x{8E95}\x{8E96}\x{8E97}\x{8E98}\x{8E9A}\x{8E9C}\x{8E9D}\x{8E9E}' + . '\x{8E9F}\x{8EA0}\x{8EA1}\x{8EA3}\x{8EA4}\x{8EA5}\x{8EA6}\x{8EA7}\x{8EA8}' + . '\x{8EA9}\x{8EAA}\x{8EAB}\x{8EAC}\x{8EAD}\x{8EAE}\x{8EAF}\x{8EB0}\x{8EB1}' + . '\x{8EB2}\x{8EB4}\x{8EB5}\x{8EB8}\x{8EB9}\x{8EBA}\x{8EBB}\x{8EBC}\x{8EBD}' + . '\x{8EBE}\x{8EBF}\x{8EC0}\x{8EC2}\x{8EC3}\x{8EC5}\x{8EC6}\x{8EC7}\x{8EC8}' + . '\x{8EC9}\x{8ECA}\x{8ECB}\x{8ECC}\x{8ECD}\x{8ECE}\x{8ECF}\x{8ED0}\x{8ED1}' + . '\x{8ED2}\x{8ED3}\x{8ED4}\x{8ED5}\x{8ED6}\x{8ED7}\x{8ED8}\x{8EDA}\x{8EDB}' + . '\x{8EDC}\x{8EDD}\x{8EDE}\x{8EDF}\x{8EE0}\x{8EE1}\x{8EE4}\x{8EE5}\x{8EE6}' + . '\x{8EE7}\x{8EE8}\x{8EE9}\x{8EEA}\x{8EEB}\x{8EEC}\x{8EED}\x{8EEE}\x{8EEF}' + . '\x{8EF1}\x{8EF2}\x{8EF3}\x{8EF4}\x{8EF5}\x{8EF6}\x{8EF7}\x{8EF8}\x{8EF9}' + . '\x{8EFA}\x{8EFB}\x{8EFC}\x{8EFD}\x{8EFE}\x{8EFF}\x{8F00}\x{8F01}\x{8F02}' + . '\x{8F03}\x{8F04}\x{8F05}\x{8F06}\x{8F07}\x{8F08}\x{8F09}\x{8F0A}\x{8F0B}' + . '\x{8F0D}\x{8F0E}\x{8F10}\x{8F11}\x{8F12}\x{8F13}\x{8F14}\x{8F15}\x{8F16}' + . '\x{8F17}\x{8F18}\x{8F1A}\x{8F1B}\x{8F1C}\x{8F1D}\x{8F1E}\x{8F1F}\x{8F20}' + . '\x{8F21}\x{8F22}\x{8F23}\x{8F24}\x{8F25}\x{8F26}\x{8F27}\x{8F28}\x{8F29}' + . '\x{8F2A}\x{8F2B}\x{8F2C}\x{8F2E}\x{8F2F}\x{8F30}\x{8F31}\x{8F32}\x{8F33}' + . '\x{8F34}\x{8F35}\x{8F36}\x{8F37}\x{8F38}\x{8F39}\x{8F3B}\x{8F3C}\x{8F3D}' + . '\x{8F3E}\x{8F3F}\x{8F40}\x{8F42}\x{8F43}\x{8F44}\x{8F45}\x{8F46}\x{8F47}' + . '\x{8F48}\x{8F49}\x{8F4A}\x{8F4B}\x{8F4C}\x{8F4D}\x{8F4E}\x{8F4F}\x{8F50}' + . '\x{8F51}\x{8F52}\x{8F53}\x{8F54}\x{8F55}\x{8F56}\x{8F57}\x{8F58}\x{8F59}' + . '\x{8F5A}\x{8F5B}\x{8F5D}\x{8F5E}\x{8F5F}\x{8F60}\x{8F61}\x{8F62}\x{8F63}' + . '\x{8F64}\x{8F65}\x{8F66}\x{8F67}\x{8F68}\x{8F69}\x{8F6A}\x{8F6B}\x{8F6C}' + . '\x{8F6D}\x{8F6E}\x{8F6F}\x{8F70}\x{8F71}\x{8F72}\x{8F73}\x{8F74}\x{8F75}' + . '\x{8F76}\x{8F77}\x{8F78}\x{8F79}\x{8F7A}\x{8F7B}\x{8F7C}\x{8F7D}\x{8F7E}' + . '\x{8F7F}\x{8F80}\x{8F81}\x{8F82}\x{8F83}\x{8F84}\x{8F85}\x{8F86}\x{8F87}' + . '\x{8F88}\x{8F89}\x{8F8A}\x{8F8B}\x{8F8C}\x{8F8D}\x{8F8E}\x{8F8F}\x{8F90}' + . '\x{8F91}\x{8F92}\x{8F93}\x{8F94}\x{8F95}\x{8F96}\x{8F97}\x{8F98}\x{8F99}' + . '\x{8F9A}\x{8F9B}\x{8F9C}\x{8F9E}\x{8F9F}\x{8FA0}\x{8FA1}\x{8FA2}\x{8FA3}' + . '\x{8FA5}\x{8FA6}\x{8FA7}\x{8FA8}\x{8FA9}\x{8FAA}\x{8FAB}\x{8FAC}\x{8FAD}' + . '\x{8FAE}\x{8FAF}\x{8FB0}\x{8FB1}\x{8FB2}\x{8FB4}\x{8FB5}\x{8FB6}\x{8FB7}' + . '\x{8FB8}\x{8FB9}\x{8FBB}\x{8FBC}\x{8FBD}\x{8FBE}\x{8FBF}\x{8FC0}\x{8FC1}' + . '\x{8FC2}\x{8FC4}\x{8FC5}\x{8FC6}\x{8FC7}\x{8FC8}\x{8FC9}\x{8FCB}\x{8FCC}' + . '\x{8FCD}\x{8FCE}\x{8FCF}\x{8FD0}\x{8FD1}\x{8FD2}\x{8FD3}\x{8FD4}\x{8FD5}' + . '\x{8FD6}\x{8FD7}\x{8FD8}\x{8FD9}\x{8FDA}\x{8FDB}\x{8FDC}\x{8FDD}\x{8FDE}' + . '\x{8FDF}\x{8FE0}\x{8FE1}\x{8FE2}\x{8FE3}\x{8FE4}\x{8FE5}\x{8FE6}\x{8FE8}' + . '\x{8FE9}\x{8FEA}\x{8FEB}\x{8FEC}\x{8FED}\x{8FEE}\x{8FEF}\x{8FF0}\x{8FF1}' + . '\x{8FF2}\x{8FF3}\x{8FF4}\x{8FF5}\x{8FF6}\x{8FF7}\x{8FF8}\x{8FF9}\x{8FFA}' + . '\x{8FFB}\x{8FFC}\x{8FFD}\x{8FFE}\x{8FFF}\x{9000}\x{9001}\x{9002}\x{9003}' + . '\x{9004}\x{9005}\x{9006}\x{9007}\x{9008}\x{9009}\x{900A}\x{900B}\x{900C}' + . '\x{900D}\x{900F}\x{9010}\x{9011}\x{9012}\x{9013}\x{9014}\x{9015}\x{9016}' + . '\x{9017}\x{9018}\x{9019}\x{901A}\x{901B}\x{901C}\x{901D}\x{901E}\x{901F}' + . '\x{9020}\x{9021}\x{9022}\x{9023}\x{9024}\x{9025}\x{9026}\x{9027}\x{9028}' + . '\x{9029}\x{902B}\x{902D}\x{902E}\x{902F}\x{9030}\x{9031}\x{9032}\x{9033}' + . '\x{9034}\x{9035}\x{9036}\x{9038}\x{903A}\x{903B}\x{903C}\x{903D}\x{903E}' + . '\x{903F}\x{9041}\x{9042}\x{9043}\x{9044}\x{9045}\x{9047}\x{9048}\x{9049}' + . '\x{904A}\x{904B}\x{904C}\x{904D}\x{904E}\x{904F}\x{9050}\x{9051}\x{9052}' + . '\x{9053}\x{9054}\x{9055}\x{9056}\x{9057}\x{9058}\x{9059}\x{905A}\x{905B}' + . '\x{905C}\x{905D}\x{905E}\x{905F}\x{9060}\x{9061}\x{9062}\x{9063}\x{9064}' + . '\x{9065}\x{9066}\x{9067}\x{9068}\x{9069}\x{906A}\x{906B}\x{906C}\x{906D}' + . '\x{906E}\x{906F}\x{9070}\x{9071}\x{9072}\x{9073}\x{9074}\x{9075}\x{9076}' + . '\x{9077}\x{9078}\x{9079}\x{907A}\x{907B}\x{907C}\x{907D}\x{907E}\x{907F}' + . '\x{9080}\x{9081}\x{9082}\x{9083}\x{9084}\x{9085}\x{9086}\x{9087}\x{9088}' + . '\x{9089}\x{908A}\x{908B}\x{908C}\x{908D}\x{908E}\x{908F}\x{9090}\x{9091}' + . '\x{9092}\x{9093}\x{9094}\x{9095}\x{9096}\x{9097}\x{9098}\x{9099}\x{909A}' + . '\x{909B}\x{909C}\x{909D}\x{909E}\x{909F}\x{90A0}\x{90A1}\x{90A2}\x{90A3}' + . '\x{90A4}\x{90A5}\x{90A6}\x{90A7}\x{90A8}\x{90A9}\x{90AA}\x{90AC}\x{90AD}' + . '\x{90AE}\x{90AF}\x{90B0}\x{90B1}\x{90B2}\x{90B3}\x{90B4}\x{90B5}\x{90B6}' + . '\x{90B7}\x{90B8}\x{90B9}\x{90BA}\x{90BB}\x{90BC}\x{90BD}\x{90BE}\x{90BF}' + . '\x{90C0}\x{90C1}\x{90C2}\x{90C3}\x{90C4}\x{90C5}\x{90C6}\x{90C7}\x{90C8}' + . '\x{90C9}\x{90CA}\x{90CB}\x{90CE}\x{90CF}\x{90D0}\x{90D1}\x{90D3}\x{90D4}' + . '\x{90D5}\x{90D6}\x{90D7}\x{90D8}\x{90D9}\x{90DA}\x{90DB}\x{90DC}\x{90DD}' + . '\x{90DE}\x{90DF}\x{90E0}\x{90E1}\x{90E2}\x{90E3}\x{90E4}\x{90E5}\x{90E6}' + . '\x{90E7}\x{90E8}\x{90E9}\x{90EA}\x{90EB}\x{90EC}\x{90ED}\x{90EE}\x{90EF}' + . '\x{90F0}\x{90F1}\x{90F2}\x{90F3}\x{90F4}\x{90F5}\x{90F7}\x{90F8}\x{90F9}' + . '\x{90FA}\x{90FB}\x{90FC}\x{90FD}\x{90FE}\x{90FF}\x{9100}\x{9101}\x{9102}' + . '\x{9103}\x{9104}\x{9105}\x{9106}\x{9107}\x{9108}\x{9109}\x{910B}\x{910C}' + . '\x{910D}\x{910E}\x{910F}\x{9110}\x{9111}\x{9112}\x{9113}\x{9114}\x{9115}' + . '\x{9116}\x{9117}\x{9118}\x{9119}\x{911A}\x{911B}\x{911C}\x{911D}\x{911E}' + . '\x{911F}\x{9120}\x{9121}\x{9122}\x{9123}\x{9124}\x{9125}\x{9126}\x{9127}' + . '\x{9128}\x{9129}\x{912A}\x{912B}\x{912C}\x{912D}\x{912E}\x{912F}\x{9130}' + . '\x{9131}\x{9132}\x{9133}\x{9134}\x{9135}\x{9136}\x{9137}\x{9138}\x{9139}' + . '\x{913A}\x{913B}\x{913E}\x{913F}\x{9140}\x{9141}\x{9142}\x{9143}\x{9144}' + . '\x{9145}\x{9146}\x{9147}\x{9148}\x{9149}\x{914A}\x{914B}\x{914C}\x{914D}' + . '\x{914E}\x{914F}\x{9150}\x{9151}\x{9152}\x{9153}\x{9154}\x{9155}\x{9156}' + . '\x{9157}\x{9158}\x{915A}\x{915B}\x{915C}\x{915D}\x{915E}\x{915F}\x{9160}' + . '\x{9161}\x{9162}\x{9163}\x{9164}\x{9165}\x{9166}\x{9167}\x{9168}\x{9169}' + . '\x{916A}\x{916B}\x{916C}\x{916D}\x{916E}\x{916F}\x{9170}\x{9171}\x{9172}' + . '\x{9173}\x{9174}\x{9175}\x{9176}\x{9177}\x{9178}\x{9179}\x{917A}\x{917C}' + . '\x{917D}\x{917E}\x{917F}\x{9180}\x{9181}\x{9182}\x{9183}\x{9184}\x{9185}' + . '\x{9186}\x{9187}\x{9188}\x{9189}\x{918A}\x{918B}\x{918C}\x{918D}\x{918E}' + . '\x{918F}\x{9190}\x{9191}\x{9192}\x{9193}\x{9194}\x{9196}\x{9199}\x{919A}' + . '\x{919B}\x{919C}\x{919D}\x{919E}\x{919F}\x{91A0}\x{91A1}\x{91A2}\x{91A3}' + . '\x{91A5}\x{91A6}\x{91A7}\x{91A8}\x{91AA}\x{91AB}\x{91AC}\x{91AD}\x{91AE}' + . '\x{91AF}\x{91B0}\x{91B1}\x{91B2}\x{91B3}\x{91B4}\x{91B5}\x{91B6}\x{91B7}' + . '\x{91B9}\x{91BA}\x{91BB}\x{91BC}\x{91BD}\x{91BE}\x{91C0}\x{91C1}\x{91C2}' + . '\x{91C3}\x{91C5}\x{91C6}\x{91C7}\x{91C9}\x{91CA}\x{91CB}\x{91CC}\x{91CD}' + . '\x{91CE}\x{91CF}\x{91D0}\x{91D1}\x{91D2}\x{91D3}\x{91D4}\x{91D5}\x{91D7}' + . '\x{91D8}\x{91D9}\x{91DA}\x{91DB}\x{91DC}\x{91DD}\x{91DE}\x{91DF}\x{91E2}' + . '\x{91E3}\x{91E4}\x{91E5}\x{91E6}\x{91E7}\x{91E8}\x{91E9}\x{91EA}\x{91EB}' + . '\x{91EC}\x{91ED}\x{91EE}\x{91F0}\x{91F1}\x{91F2}\x{91F3}\x{91F4}\x{91F5}' + . '\x{91F7}\x{91F8}\x{91F9}\x{91FA}\x{91FB}\x{91FD}\x{91FE}\x{91FF}\x{9200}' + . '\x{9201}\x{9202}\x{9203}\x{9204}\x{9205}\x{9206}\x{9207}\x{9208}\x{9209}' + . '\x{920A}\x{920B}\x{920C}\x{920D}\x{920E}\x{920F}\x{9210}\x{9211}\x{9212}' + . '\x{9214}\x{9215}\x{9216}\x{9217}\x{9218}\x{9219}\x{921A}\x{921B}\x{921C}' + . '\x{921D}\x{921E}\x{9220}\x{9221}\x{9223}\x{9224}\x{9225}\x{9226}\x{9227}' + . '\x{9228}\x{9229}\x{922A}\x{922B}\x{922D}\x{922E}\x{922F}\x{9230}\x{9231}' + . '\x{9232}\x{9233}\x{9234}\x{9235}\x{9236}\x{9237}\x{9238}\x{9239}\x{923A}' + . '\x{923B}\x{923C}\x{923D}\x{923E}\x{923F}\x{9240}\x{9241}\x{9242}\x{9245}' + . '\x{9246}\x{9247}\x{9248}\x{9249}\x{924A}\x{924B}\x{924C}\x{924D}\x{924E}' + . '\x{924F}\x{9250}\x{9251}\x{9252}\x{9253}\x{9254}\x{9255}\x{9256}\x{9257}' + . '\x{9258}\x{9259}\x{925A}\x{925B}\x{925C}\x{925D}\x{925E}\x{925F}\x{9260}' + . '\x{9261}\x{9262}\x{9263}\x{9264}\x{9265}\x{9266}\x{9267}\x{9268}\x{926B}' + . '\x{926C}\x{926D}\x{926E}\x{926F}\x{9270}\x{9272}\x{9273}\x{9274}\x{9275}' + . '\x{9276}\x{9277}\x{9278}\x{9279}\x{927A}\x{927B}\x{927C}\x{927D}\x{927E}' + . '\x{927F}\x{9280}\x{9282}\x{9283}\x{9285}\x{9286}\x{9287}\x{9288}\x{9289}' + . '\x{928A}\x{928B}\x{928C}\x{928D}\x{928E}\x{928F}\x{9290}\x{9291}\x{9292}' + . '\x{9293}\x{9294}\x{9295}\x{9296}\x{9297}\x{9298}\x{9299}\x{929A}\x{929B}' + . '\x{929C}\x{929D}\x{929F}\x{92A0}\x{92A1}\x{92A2}\x{92A3}\x{92A4}\x{92A5}' + . '\x{92A6}\x{92A7}\x{92A8}\x{92A9}\x{92AA}\x{92AB}\x{92AC}\x{92AD}\x{92AE}' + . '\x{92AF}\x{92B0}\x{92B1}\x{92B2}\x{92B3}\x{92B4}\x{92B5}\x{92B6}\x{92B7}' + . '\x{92B8}\x{92B9}\x{92BA}\x{92BB}\x{92BC}\x{92BE}\x{92BF}\x{92C0}\x{92C1}' + . '\x{92C2}\x{92C3}\x{92C4}\x{92C5}\x{92C6}\x{92C7}\x{92C8}\x{92C9}\x{92CA}' + . '\x{92CB}\x{92CC}\x{92CD}\x{92CE}\x{92CF}\x{92D0}\x{92D1}\x{92D2}\x{92D3}' + . '\x{92D5}\x{92D6}\x{92D7}\x{92D8}\x{92D9}\x{92DA}\x{92DC}\x{92DD}\x{92DE}' + . '\x{92DF}\x{92E0}\x{92E1}\x{92E3}\x{92E4}\x{92E5}\x{92E6}\x{92E7}\x{92E8}' + . '\x{92E9}\x{92EA}\x{92EB}\x{92EC}\x{92ED}\x{92EE}\x{92EF}\x{92F0}\x{92F1}' + . '\x{92F2}\x{92F3}\x{92F4}\x{92F5}\x{92F6}\x{92F7}\x{92F8}\x{92F9}\x{92FA}' + . '\x{92FB}\x{92FC}\x{92FD}\x{92FE}\x{92FF}\x{9300}\x{9301}\x{9302}\x{9303}' + . '\x{9304}\x{9305}\x{9306}\x{9307}\x{9308}\x{9309}\x{930A}\x{930B}\x{930C}' + . '\x{930D}\x{930E}\x{930F}\x{9310}\x{9311}\x{9312}\x{9313}\x{9314}\x{9315}' + . '\x{9316}\x{9317}\x{9318}\x{9319}\x{931A}\x{931B}\x{931D}\x{931E}\x{931F}' + . '\x{9320}\x{9321}\x{9322}\x{9323}\x{9324}\x{9325}\x{9326}\x{9327}\x{9328}' + . '\x{9329}\x{932A}\x{932B}\x{932D}\x{932E}\x{932F}\x{9332}\x{9333}\x{9334}' + . '\x{9335}\x{9336}\x{9337}\x{9338}\x{9339}\x{933A}\x{933B}\x{933C}\x{933D}' + . '\x{933E}\x{933F}\x{9340}\x{9341}\x{9342}\x{9343}\x{9344}\x{9345}\x{9346}' + . '\x{9347}\x{9348}\x{9349}\x{934A}\x{934B}\x{934C}\x{934D}\x{934E}\x{934F}' + . '\x{9350}\x{9351}\x{9352}\x{9353}\x{9354}\x{9355}\x{9356}\x{9357}\x{9358}' + . '\x{9359}\x{935A}\x{935B}\x{935C}\x{935D}\x{935E}\x{935F}\x{9360}\x{9361}' + . '\x{9363}\x{9364}\x{9365}\x{9366}\x{9367}\x{9369}\x{936A}\x{936C}\x{936D}' + . '\x{936E}\x{9370}\x{9371}\x{9372}\x{9374}\x{9375}\x{9376}\x{9377}\x{9379}' + . '\x{937A}\x{937B}\x{937C}\x{937D}\x{937E}\x{9380}\x{9382}\x{9383}\x{9384}' + . '\x{9385}\x{9386}\x{9387}\x{9388}\x{9389}\x{938A}\x{938C}\x{938D}\x{938E}' + . '\x{938F}\x{9390}\x{9391}\x{9392}\x{9393}\x{9394}\x{9395}\x{9396}\x{9397}' + . '\x{9398}\x{9399}\x{939A}\x{939B}\x{939D}\x{939E}\x{939F}\x{93A1}\x{93A2}' + . '\x{93A3}\x{93A4}\x{93A5}\x{93A6}\x{93A7}\x{93A8}\x{93A9}\x{93AA}\x{93AC}' + . '\x{93AD}\x{93AE}\x{93AF}\x{93B0}\x{93B1}\x{93B2}\x{93B3}\x{93B4}\x{93B5}' + . '\x{93B6}\x{93B7}\x{93B8}\x{93B9}\x{93BA}\x{93BC}\x{93BD}\x{93BE}\x{93BF}' + . '\x{93C0}\x{93C1}\x{93C2}\x{93C3}\x{93C4}\x{93C5}\x{93C6}\x{93C7}\x{93C8}' + . '\x{93C9}\x{93CA}\x{93CB}\x{93CC}\x{93CD}\x{93CE}\x{93CF}\x{93D0}\x{93D1}' + . '\x{93D2}\x{93D3}\x{93D4}\x{93D5}\x{93D6}\x{93D7}\x{93D8}\x{93D9}\x{93DA}' + . '\x{93DB}\x{93DC}\x{93DD}\x{93DE}\x{93DF}\x{93E1}\x{93E2}\x{93E3}\x{93E4}' + . '\x{93E6}\x{93E7}\x{93E8}\x{93E9}\x{93EA}\x{93EB}\x{93EC}\x{93ED}\x{93EE}' + . '\x{93EF}\x{93F0}\x{93F1}\x{93F2}\x{93F4}\x{93F5}\x{93F6}\x{93F7}\x{93F8}' + . '\x{93F9}\x{93FA}\x{93FB}\x{93FC}\x{93FD}\x{93FE}\x{93FF}\x{9400}\x{9401}' + . '\x{9403}\x{9404}\x{9405}\x{9406}\x{9407}\x{9408}\x{9409}\x{940A}\x{940B}' + . '\x{940C}\x{940D}\x{940E}\x{940F}\x{9410}\x{9411}\x{9412}\x{9413}\x{9414}' + . '\x{9415}\x{9416}\x{9418}\x{9419}\x{941B}\x{941D}\x{9420}\x{9422}\x{9423}' + . '\x{9425}\x{9426}\x{9427}\x{9428}\x{9429}\x{942A}\x{942B}\x{942C}\x{942D}' + . '\x{942E}\x{942F}\x{9430}\x{9431}\x{9432}\x{9433}\x{9434}\x{9435}\x{9436}' + . '\x{9437}\x{9438}\x{9439}\x{943A}\x{943B}\x{943C}\x{943D}\x{943E}\x{943F}' + . '\x{9440}\x{9441}\x{9442}\x{9444}\x{9445}\x{9446}\x{9447}\x{9448}\x{9449}' + . '\x{944A}\x{944B}\x{944C}\x{944D}\x{944F}\x{9450}\x{9451}\x{9452}\x{9453}' + . '\x{9454}\x{9455}\x{9456}\x{9457}\x{9458}\x{9459}\x{945B}\x{945C}\x{945D}' + . '\x{945E}\x{945F}\x{9460}\x{9461}\x{9462}\x{9463}\x{9464}\x{9465}\x{9466}' + . '\x{9467}\x{9468}\x{9469}\x{946A}\x{946B}\x{946D}\x{946E}\x{946F}\x{9470}' + . '\x{9471}\x{9472}\x{9473}\x{9474}\x{9475}\x{9476}\x{9477}\x{9478}\x{9479}' + . '\x{947A}\x{947C}\x{947D}\x{947E}\x{947F}\x{9480}\x{9481}\x{9482}\x{9483}' + . '\x{9484}\x{9485}\x{9486}\x{9487}\x{9488}\x{9489}\x{948A}\x{948B}\x{948C}' + . '\x{948D}\x{948E}\x{948F}\x{9490}\x{9491}\x{9492}\x{9493}\x{9494}\x{9495}' + . '\x{9496}\x{9497}\x{9498}\x{9499}\x{949A}\x{949B}\x{949C}\x{949D}\x{949E}' + . '\x{949F}\x{94A0}\x{94A1}\x{94A2}\x{94A3}\x{94A4}\x{94A5}\x{94A6}\x{94A7}' + . '\x{94A8}\x{94A9}\x{94AA}\x{94AB}\x{94AC}\x{94AD}\x{94AE}\x{94AF}\x{94B0}' + . '\x{94B1}\x{94B2}\x{94B3}\x{94B4}\x{94B5}\x{94B6}\x{94B7}\x{94B8}\x{94B9}' + . '\x{94BA}\x{94BB}\x{94BC}\x{94BD}\x{94BE}\x{94BF}\x{94C0}\x{94C1}\x{94C2}' + . '\x{94C3}\x{94C4}\x{94C5}\x{94C6}\x{94C7}\x{94C8}\x{94C9}\x{94CA}\x{94CB}' + . '\x{94CC}\x{94CD}\x{94CE}\x{94CF}\x{94D0}\x{94D1}\x{94D2}\x{94D3}\x{94D4}' + . '\x{94D5}\x{94D6}\x{94D7}\x{94D8}\x{94D9}\x{94DA}\x{94DB}\x{94DC}\x{94DD}' + . '\x{94DE}\x{94DF}\x{94E0}\x{94E1}\x{94E2}\x{94E3}\x{94E4}\x{94E5}\x{94E6}' + . '\x{94E7}\x{94E8}\x{94E9}\x{94EA}\x{94EB}\x{94EC}\x{94ED}\x{94EE}\x{94EF}' + . '\x{94F0}\x{94F1}\x{94F2}\x{94F3}\x{94F4}\x{94F5}\x{94F6}\x{94F7}\x{94F8}' + . '\x{94F9}\x{94FA}\x{94FB}\x{94FC}\x{94FD}\x{94FE}\x{94FF}\x{9500}\x{9501}' + . '\x{9502}\x{9503}\x{9504}\x{9505}\x{9506}\x{9507}\x{9508}\x{9509}\x{950A}' + . '\x{950B}\x{950C}\x{950D}\x{950E}\x{950F}\x{9510}\x{9511}\x{9512}\x{9513}' + . '\x{9514}\x{9515}\x{9516}\x{9517}\x{9518}\x{9519}\x{951A}\x{951B}\x{951C}' + . '\x{951D}\x{951E}\x{951F}\x{9520}\x{9521}\x{9522}\x{9523}\x{9524}\x{9525}' + . '\x{9526}\x{9527}\x{9528}\x{9529}\x{952A}\x{952B}\x{952C}\x{952D}\x{952E}' + . '\x{952F}\x{9530}\x{9531}\x{9532}\x{9533}\x{9534}\x{9535}\x{9536}\x{9537}' + . '\x{9538}\x{9539}\x{953A}\x{953B}\x{953C}\x{953D}\x{953E}\x{953F}\x{9540}' + . '\x{9541}\x{9542}\x{9543}\x{9544}\x{9545}\x{9546}\x{9547}\x{9548}\x{9549}' + . '\x{954A}\x{954B}\x{954C}\x{954D}\x{954E}\x{954F}\x{9550}\x{9551}\x{9552}' + . '\x{9553}\x{9554}\x{9555}\x{9556}\x{9557}\x{9558}\x{9559}\x{955A}\x{955B}' + . '\x{955C}\x{955D}\x{955E}\x{955F}\x{9560}\x{9561}\x{9562}\x{9563}\x{9564}' + . '\x{9565}\x{9566}\x{9567}\x{9568}\x{9569}\x{956A}\x{956B}\x{956C}\x{956D}' + . '\x{956E}\x{956F}\x{9570}\x{9571}\x{9572}\x{9573}\x{9574}\x{9575}\x{9576}' + . '\x{9577}\x{957A}\x{957B}\x{957C}\x{957D}\x{957F}\x{9580}\x{9581}\x{9582}' + . '\x{9583}\x{9584}\x{9586}\x{9587}\x{9588}\x{9589}\x{958A}\x{958B}\x{958C}' + . '\x{958D}\x{958E}\x{958F}\x{9590}\x{9591}\x{9592}\x{9593}\x{9594}\x{9595}' + . '\x{9596}\x{9598}\x{9599}\x{959A}\x{959B}\x{959C}\x{959D}\x{959E}\x{959F}' + . '\x{95A1}\x{95A2}\x{95A3}\x{95A4}\x{95A5}\x{95A6}\x{95A7}\x{95A8}\x{95A9}' + . '\x{95AA}\x{95AB}\x{95AC}\x{95AD}\x{95AE}\x{95AF}\x{95B0}\x{95B1}\x{95B2}' + . '\x{95B5}\x{95B6}\x{95B7}\x{95B9}\x{95BA}\x{95BB}\x{95BC}\x{95BD}\x{95BE}' + . '\x{95BF}\x{95C0}\x{95C2}\x{95C3}\x{95C4}\x{95C5}\x{95C6}\x{95C7}\x{95C8}' + . '\x{95C9}\x{95CA}\x{95CB}\x{95CC}\x{95CD}\x{95CE}\x{95CF}\x{95D0}\x{95D1}' + . '\x{95D2}\x{95D3}\x{95D4}\x{95D5}\x{95D6}\x{95D7}\x{95D8}\x{95DA}\x{95DB}' + . '\x{95DC}\x{95DE}\x{95DF}\x{95E0}\x{95E1}\x{95E2}\x{95E3}\x{95E4}\x{95E5}' + . '\x{95E6}\x{95E7}\x{95E8}\x{95E9}\x{95EA}\x{95EB}\x{95EC}\x{95ED}\x{95EE}' + . '\x{95EF}\x{95F0}\x{95F1}\x{95F2}\x{95F3}\x{95F4}\x{95F5}\x{95F6}\x{95F7}' + . '\x{95F8}\x{95F9}\x{95FA}\x{95FB}\x{95FC}\x{95FD}\x{95FE}\x{95FF}\x{9600}' + . '\x{9601}\x{9602}\x{9603}\x{9604}\x{9605}\x{9606}\x{9607}\x{9608}\x{9609}' + . '\x{960A}\x{960B}\x{960C}\x{960D}\x{960E}\x{960F}\x{9610}\x{9611}\x{9612}' + . '\x{9613}\x{9614}\x{9615}\x{9616}\x{9617}\x{9618}\x{9619}\x{961A}\x{961B}' + . '\x{961C}\x{961D}\x{961E}\x{961F}\x{9620}\x{9621}\x{9622}\x{9623}\x{9624}' + . '\x{9627}\x{9628}\x{962A}\x{962B}\x{962C}\x{962D}\x{962E}\x{962F}\x{9630}' + . '\x{9631}\x{9632}\x{9633}\x{9634}\x{9635}\x{9636}\x{9637}\x{9638}\x{9639}' + . '\x{963A}\x{963B}\x{963C}\x{963D}\x{963F}\x{9640}\x{9641}\x{9642}\x{9643}' + . '\x{9644}\x{9645}\x{9646}\x{9647}\x{9648}\x{9649}\x{964A}\x{964B}\x{964C}' + . '\x{964D}\x{964E}\x{964F}\x{9650}\x{9651}\x{9652}\x{9653}\x{9654}\x{9655}' + . '\x{9658}\x{9659}\x{965A}\x{965B}\x{965C}\x{965D}\x{965E}\x{965F}\x{9660}' + . '\x{9661}\x{9662}\x{9663}\x{9664}\x{9666}\x{9667}\x{9668}\x{9669}\x{966A}' + . '\x{966B}\x{966C}\x{966D}\x{966E}\x{966F}\x{9670}\x{9671}\x{9672}\x{9673}' + . '\x{9674}\x{9675}\x{9676}\x{9677}\x{9678}\x{967C}\x{967D}\x{967E}\x{9680}' + . '\x{9683}\x{9684}\x{9685}\x{9686}\x{9687}\x{9688}\x{9689}\x{968A}\x{968B}' + . '\x{968D}\x{968E}\x{968F}\x{9690}\x{9691}\x{9692}\x{9693}\x{9694}\x{9695}' + . '\x{9697}\x{9698}\x{9699}\x{969B}\x{969C}\x{969E}\x{96A0}\x{96A1}\x{96A2}' + . '\x{96A3}\x{96A4}\x{96A5}\x{96A6}\x{96A7}\x{96A8}\x{96A9}\x{96AA}\x{96AC}' + . '\x{96AD}\x{96AE}\x{96B0}\x{96B1}\x{96B3}\x{96B4}\x{96B6}\x{96B7}\x{96B8}' + . '\x{96B9}\x{96BA}\x{96BB}\x{96BC}\x{96BD}\x{96BE}\x{96BF}\x{96C0}\x{96C1}' + . '\x{96C2}\x{96C3}\x{96C4}\x{96C5}\x{96C6}\x{96C7}\x{96C8}\x{96C9}\x{96CA}' + . '\x{96CB}\x{96CC}\x{96CD}\x{96CE}\x{96CF}\x{96D0}\x{96D1}\x{96D2}\x{96D3}' + . '\x{96D4}\x{96D5}\x{96D6}\x{96D7}\x{96D8}\x{96D9}\x{96DA}\x{96DB}\x{96DC}' + . '\x{96DD}\x{96DE}\x{96DF}\x{96E0}\x{96E1}\x{96E2}\x{96E3}\x{96E5}\x{96E8}' + . '\x{96E9}\x{96EA}\x{96EB}\x{96EC}\x{96ED}\x{96EE}\x{96EF}\x{96F0}\x{96F1}' + . '\x{96F2}\x{96F3}\x{96F4}\x{96F5}\x{96F6}\x{96F7}\x{96F8}\x{96F9}\x{96FA}' + . '\x{96FB}\x{96FD}\x{96FE}\x{96FF}\x{9700}\x{9701}\x{9702}\x{9703}\x{9704}' + . '\x{9705}\x{9706}\x{9707}\x{9708}\x{9709}\x{970A}\x{970B}\x{970C}\x{970D}' + . '\x{970E}\x{970F}\x{9710}\x{9711}\x{9712}\x{9713}\x{9715}\x{9716}\x{9718}' + . '\x{9719}\x{971C}\x{971D}\x{971E}\x{971F}\x{9720}\x{9721}\x{9722}\x{9723}' + . '\x{9724}\x{9725}\x{9726}\x{9727}\x{9728}\x{9729}\x{972A}\x{972B}\x{972C}' + . '\x{972D}\x{972E}\x{972F}\x{9730}\x{9731}\x{9732}\x{9735}\x{9736}\x{9738}' + . '\x{9739}\x{973A}\x{973B}\x{973C}\x{973D}\x{973E}\x{973F}\x{9742}\x{9743}' + . '\x{9744}\x{9745}\x{9746}\x{9747}\x{9748}\x{9749}\x{974A}\x{974B}\x{974C}' + . '\x{974E}\x{974F}\x{9750}\x{9751}\x{9752}\x{9753}\x{9754}\x{9755}\x{9756}' + . '\x{9758}\x{9759}\x{975A}\x{975B}\x{975C}\x{975D}\x{975E}\x{975F}\x{9760}' + . '\x{9761}\x{9762}\x{9765}\x{9766}\x{9767}\x{9768}\x{9769}\x{976A}\x{976B}' + . '\x{976C}\x{976D}\x{976E}\x{976F}\x{9770}\x{9772}\x{9773}\x{9774}\x{9776}' + . '\x{9777}\x{9778}\x{9779}\x{977A}\x{977B}\x{977C}\x{977D}\x{977E}\x{977F}' + . '\x{9780}\x{9781}\x{9782}\x{9783}\x{9784}\x{9785}\x{9786}\x{9788}\x{978A}' + . '\x{978B}\x{978C}\x{978D}\x{978E}\x{978F}\x{9790}\x{9791}\x{9792}\x{9793}' + . '\x{9794}\x{9795}\x{9796}\x{9797}\x{9798}\x{9799}\x{979A}\x{979C}\x{979D}' + . '\x{979E}\x{979F}\x{97A0}\x{97A1}\x{97A2}\x{97A3}\x{97A4}\x{97A5}\x{97A6}' + . '\x{97A7}\x{97A8}\x{97AA}\x{97AB}\x{97AC}\x{97AD}\x{97AE}\x{97AF}\x{97B2}' + . '\x{97B3}\x{97B4}\x{97B6}\x{97B7}\x{97B8}\x{97B9}\x{97BA}\x{97BB}\x{97BC}' + . '\x{97BD}\x{97BF}\x{97C1}\x{97C2}\x{97C3}\x{97C4}\x{97C5}\x{97C6}\x{97C7}' + . '\x{97C8}\x{97C9}\x{97CA}\x{97CB}\x{97CC}\x{97CD}\x{97CE}\x{97CF}\x{97D0}' + . '\x{97D1}\x{97D3}\x{97D4}\x{97D5}\x{97D6}\x{97D7}\x{97D8}\x{97D9}\x{97DA}' + . '\x{97DB}\x{97DC}\x{97DD}\x{97DE}\x{97DF}\x{97E0}\x{97E1}\x{97E2}\x{97E3}' + . '\x{97E4}\x{97E5}\x{97E6}\x{97E7}\x{97E8}\x{97E9}\x{97EA}\x{97EB}\x{97EC}' + . '\x{97ED}\x{97EE}\x{97EF}\x{97F0}\x{97F1}\x{97F2}\x{97F3}\x{97F4}\x{97F5}' + . '\x{97F6}\x{97F7}\x{97F8}\x{97F9}\x{97FA}\x{97FB}\x{97FD}\x{97FE}\x{97FF}' + . '\x{9800}\x{9801}\x{9802}\x{9803}\x{9804}\x{9805}\x{9806}\x{9807}\x{9808}' + . '\x{9809}\x{980A}\x{980B}\x{980C}\x{980D}\x{980E}\x{980F}\x{9810}\x{9811}' + . '\x{9812}\x{9813}\x{9814}\x{9815}\x{9816}\x{9817}\x{9818}\x{9819}\x{981A}' + . '\x{981B}\x{981C}\x{981D}\x{981E}\x{9820}\x{9821}\x{9822}\x{9823}\x{9824}' + . '\x{9826}\x{9827}\x{9828}\x{9829}\x{982B}\x{982D}\x{982E}\x{982F}\x{9830}' + . '\x{9831}\x{9832}\x{9834}\x{9835}\x{9836}\x{9837}\x{9838}\x{9839}\x{983B}' + . '\x{983C}\x{983D}\x{983F}\x{9840}\x{9841}\x{9843}\x{9844}\x{9845}\x{9846}' + . '\x{9848}\x{9849}\x{984A}\x{984C}\x{984D}\x{984E}\x{984F}\x{9850}\x{9851}' + . '\x{9852}\x{9853}\x{9854}\x{9855}\x{9857}\x{9858}\x{9859}\x{985A}\x{985B}' + . '\x{985C}\x{985D}\x{985E}\x{985F}\x{9860}\x{9861}\x{9862}\x{9863}\x{9864}' + . '\x{9865}\x{9867}\x{9869}\x{986A}\x{986B}\x{986C}\x{986D}\x{986E}\x{986F}' + . '\x{9870}\x{9871}\x{9872}\x{9873}\x{9874}\x{9875}\x{9876}\x{9877}\x{9878}' + . '\x{9879}\x{987A}\x{987B}\x{987C}\x{987D}\x{987E}\x{987F}\x{9880}\x{9881}' + . '\x{9882}\x{9883}\x{9884}\x{9885}\x{9886}\x{9887}\x{9888}\x{9889}\x{988A}' + . '\x{988B}\x{988C}\x{988D}\x{988E}\x{988F}\x{9890}\x{9891}\x{9892}\x{9893}' + . '\x{9894}\x{9895}\x{9896}\x{9897}\x{9898}\x{9899}\x{989A}\x{989B}\x{989C}' + . '\x{989D}\x{989E}\x{989F}\x{98A0}\x{98A1}\x{98A2}\x{98A3}\x{98A4}\x{98A5}' + . '\x{98A6}\x{98A7}\x{98A8}\x{98A9}\x{98AA}\x{98AB}\x{98AC}\x{98AD}\x{98AE}' + . '\x{98AF}\x{98B0}\x{98B1}\x{98B2}\x{98B3}\x{98B4}\x{98B5}\x{98B6}\x{98B8}' + . '\x{98B9}\x{98BA}\x{98BB}\x{98BC}\x{98BD}\x{98BE}\x{98BF}\x{98C0}\x{98C1}' + . '\x{98C2}\x{98C3}\x{98C4}\x{98C5}\x{98C6}\x{98C8}\x{98C9}\x{98CB}\x{98CC}' + . '\x{98CD}\x{98CE}\x{98CF}\x{98D0}\x{98D1}\x{98D2}\x{98D3}\x{98D4}\x{98D5}' + . '\x{98D6}\x{98D7}\x{98D8}\x{98D9}\x{98DA}\x{98DB}\x{98DC}\x{98DD}\x{98DE}' + . '\x{98DF}\x{98E0}\x{98E2}\x{98E3}\x{98E5}\x{98E6}\x{98E7}\x{98E8}\x{98E9}' + . '\x{98EA}\x{98EB}\x{98ED}\x{98EF}\x{98F0}\x{98F2}\x{98F3}\x{98F4}\x{98F5}' + . '\x{98F6}\x{98F7}\x{98F9}\x{98FA}\x{98FC}\x{98FD}\x{98FE}\x{98FF}\x{9900}' + . '\x{9901}\x{9902}\x{9903}\x{9904}\x{9905}\x{9906}\x{9907}\x{9908}\x{9909}' + . '\x{990A}\x{990B}\x{990C}\x{990D}\x{990E}\x{990F}\x{9910}\x{9911}\x{9912}' + . '\x{9913}\x{9914}\x{9915}\x{9916}\x{9917}\x{9918}\x{991A}\x{991B}\x{991C}' + . '\x{991D}\x{991E}\x{991F}\x{9920}\x{9921}\x{9922}\x{9923}\x{9924}\x{9925}' + . '\x{9926}\x{9927}\x{9928}\x{9929}\x{992A}\x{992B}\x{992C}\x{992D}\x{992E}' + . '\x{992F}\x{9930}\x{9931}\x{9932}\x{9933}\x{9934}\x{9935}\x{9936}\x{9937}' + . '\x{9938}\x{9939}\x{993A}\x{993C}\x{993D}\x{993E}\x{993F}\x{9940}\x{9941}' + . '\x{9942}\x{9943}\x{9945}\x{9946}\x{9947}\x{9948}\x{9949}\x{994A}\x{994B}' + . '\x{994C}\x{994E}\x{994F}\x{9950}\x{9951}\x{9952}\x{9953}\x{9954}\x{9955}' + . '\x{9956}\x{9957}\x{9958}\x{9959}\x{995B}\x{995C}\x{995E}\x{995F}\x{9960}' + . '\x{9961}\x{9962}\x{9963}\x{9964}\x{9965}\x{9966}\x{9967}\x{9968}\x{9969}' + . '\x{996A}\x{996B}\x{996C}\x{996D}\x{996E}\x{996F}\x{9970}\x{9971}\x{9972}' + . '\x{9973}\x{9974}\x{9975}\x{9976}\x{9977}\x{9978}\x{9979}\x{997A}\x{997B}' + . '\x{997C}\x{997D}\x{997E}\x{997F}\x{9980}\x{9981}\x{9982}\x{9983}\x{9984}' + . '\x{9985}\x{9986}\x{9987}\x{9988}\x{9989}\x{998A}\x{998B}\x{998C}\x{998D}' + . '\x{998E}\x{998F}\x{9990}\x{9991}\x{9992}\x{9993}\x{9994}\x{9995}\x{9996}' + . '\x{9997}\x{9998}\x{9999}\x{999A}\x{999B}\x{999C}\x{999D}\x{999E}\x{999F}' + . '\x{99A0}\x{99A1}\x{99A2}\x{99A3}\x{99A4}\x{99A5}\x{99A6}\x{99A7}\x{99A8}' + . '\x{99A9}\x{99AA}\x{99AB}\x{99AC}\x{99AD}\x{99AE}\x{99AF}\x{99B0}\x{99B1}' + . '\x{99B2}\x{99B3}\x{99B4}\x{99B5}\x{99B6}\x{99B7}\x{99B8}\x{99B9}\x{99BA}' + . '\x{99BB}\x{99BC}\x{99BD}\x{99BE}\x{99C0}\x{99C1}\x{99C2}\x{99C3}\x{99C4}' + . '\x{99C6}\x{99C7}\x{99C8}\x{99C9}\x{99CA}\x{99CB}\x{99CC}\x{99CD}\x{99CE}' + . '\x{99CF}\x{99D0}\x{99D1}\x{99D2}\x{99D3}\x{99D4}\x{99D5}\x{99D6}\x{99D7}' + . '\x{99D8}\x{99D9}\x{99DA}\x{99DB}\x{99DC}\x{99DD}\x{99DE}\x{99DF}\x{99E1}' + . '\x{99E2}\x{99E3}\x{99E4}\x{99E5}\x{99E7}\x{99E8}\x{99E9}\x{99EA}\x{99EC}' + . '\x{99ED}\x{99EE}\x{99EF}\x{99F0}\x{99F1}\x{99F2}\x{99F3}\x{99F4}\x{99F6}' + . '\x{99F7}\x{99F8}\x{99F9}\x{99FA}\x{99FB}\x{99FC}\x{99FD}\x{99FE}\x{99FF}' + . '\x{9A00}\x{9A01}\x{9A02}\x{9A03}\x{9A04}\x{9A05}\x{9A06}\x{9A07}\x{9A08}' + . '\x{9A09}\x{9A0A}\x{9A0B}\x{9A0C}\x{9A0D}\x{9A0E}\x{9A0F}\x{9A11}\x{9A14}' + . '\x{9A15}\x{9A16}\x{9A19}\x{9A1A}\x{9A1B}\x{9A1C}\x{9A1D}\x{9A1E}\x{9A1F}' + . '\x{9A20}\x{9A21}\x{9A22}\x{9A23}\x{9A24}\x{9A25}\x{9A26}\x{9A27}\x{9A29}' + . '\x{9A2A}\x{9A2B}\x{9A2C}\x{9A2D}\x{9A2E}\x{9A2F}\x{9A30}\x{9A31}\x{9A32}' + . '\x{9A33}\x{9A34}\x{9A35}\x{9A36}\x{9A37}\x{9A38}\x{9A39}\x{9A3A}\x{9A3C}' + . '\x{9A3D}\x{9A3E}\x{9A3F}\x{9A40}\x{9A41}\x{9A42}\x{9A43}\x{9A44}\x{9A45}' + . '\x{9A46}\x{9A47}\x{9A48}\x{9A49}\x{9A4A}\x{9A4B}\x{9A4C}\x{9A4D}\x{9A4E}' + . '\x{9A4F}\x{9A50}\x{9A52}\x{9A53}\x{9A54}\x{9A55}\x{9A56}\x{9A57}\x{9A59}' + . '\x{9A5A}\x{9A5B}\x{9A5C}\x{9A5E}\x{9A5F}\x{9A60}\x{9A61}\x{9A62}\x{9A64}' + . '\x{9A65}\x{9A66}\x{9A67}\x{9A68}\x{9A69}\x{9A6A}\x{9A6B}\x{9A6C}\x{9A6D}' + . '\x{9A6E}\x{9A6F}\x{9A70}\x{9A71}\x{9A72}\x{9A73}\x{9A74}\x{9A75}\x{9A76}' + . '\x{9A77}\x{9A78}\x{9A79}\x{9A7A}\x{9A7B}\x{9A7C}\x{9A7D}\x{9A7E}\x{9A7F}' + . '\x{9A80}\x{9A81}\x{9A82}\x{9A83}\x{9A84}\x{9A85}\x{9A86}\x{9A87}\x{9A88}' + . '\x{9A89}\x{9A8A}\x{9A8B}\x{9A8C}\x{9A8D}\x{9A8E}\x{9A8F}\x{9A90}\x{9A91}' + . '\x{9A92}\x{9A93}\x{9A94}\x{9A95}\x{9A96}\x{9A97}\x{9A98}\x{9A99}\x{9A9A}' + . '\x{9A9B}\x{9A9C}\x{9A9D}\x{9A9E}\x{9A9F}\x{9AA0}\x{9AA1}\x{9AA2}\x{9AA3}' + . '\x{9AA4}\x{9AA5}\x{9AA6}\x{9AA7}\x{9AA8}\x{9AAA}\x{9AAB}\x{9AAC}\x{9AAD}' + . '\x{9AAE}\x{9AAF}\x{9AB0}\x{9AB1}\x{9AB2}\x{9AB3}\x{9AB4}\x{9AB5}\x{9AB6}' + . '\x{9AB7}\x{9AB8}\x{9AB9}\x{9ABA}\x{9ABB}\x{9ABC}\x{9ABE}\x{9ABF}\x{9AC0}' + . '\x{9AC1}\x{9AC2}\x{9AC3}\x{9AC4}\x{9AC5}\x{9AC6}\x{9AC7}\x{9AC9}\x{9ACA}' + . '\x{9ACB}\x{9ACC}\x{9ACD}\x{9ACE}\x{9ACF}\x{9AD0}\x{9AD1}\x{9AD2}\x{9AD3}' + . '\x{9AD4}\x{9AD5}\x{9AD6}\x{9AD8}\x{9AD9}\x{9ADA}\x{9ADB}\x{9ADC}\x{9ADD}' + . '\x{9ADE}\x{9ADF}\x{9AE1}\x{9AE2}\x{9AE3}\x{9AE5}\x{9AE6}\x{9AE7}\x{9AEA}' + . '\x{9AEB}\x{9AEC}\x{9AED}\x{9AEE}\x{9AEF}\x{9AF1}\x{9AF2}\x{9AF3}\x{9AF4}' + . '\x{9AF5}\x{9AF6}\x{9AF7}\x{9AF8}\x{9AF9}\x{9AFA}\x{9AFB}\x{9AFC}\x{9AFD}' + . '\x{9AFE}\x{9AFF}\x{9B01}\x{9B03}\x{9B04}\x{9B05}\x{9B06}\x{9B07}\x{9B08}' + . '\x{9B0A}\x{9B0B}\x{9B0C}\x{9B0D}\x{9B0E}\x{9B0F}\x{9B10}\x{9B11}\x{9B12}' + . '\x{9B13}\x{9B15}\x{9B16}\x{9B17}\x{9B18}\x{9B19}\x{9B1A}\x{9B1C}\x{9B1D}' + . '\x{9B1E}\x{9B1F}\x{9B20}\x{9B21}\x{9B22}\x{9B23}\x{9B24}\x{9B25}\x{9B26}' + . '\x{9B27}\x{9B28}\x{9B29}\x{9B2A}\x{9B2B}\x{9B2C}\x{9B2D}\x{9B2E}\x{9B2F}' + . '\x{9B30}\x{9B31}\x{9B32}\x{9B33}\x{9B35}\x{9B36}\x{9B37}\x{9B38}\x{9B39}' + . '\x{9B3A}\x{9B3B}\x{9B3C}\x{9B3E}\x{9B3F}\x{9B41}\x{9B42}\x{9B43}\x{9B44}' + . '\x{9B45}\x{9B46}\x{9B47}\x{9B48}\x{9B49}\x{9B4A}\x{9B4B}\x{9B4C}\x{9B4D}' + . '\x{9B4E}\x{9B4F}\x{9B51}\x{9B52}\x{9B53}\x{9B54}\x{9B55}\x{9B56}\x{9B58}' + . '\x{9B59}\x{9B5A}\x{9B5B}\x{9B5C}\x{9B5D}\x{9B5E}\x{9B5F}\x{9B60}\x{9B61}' + . '\x{9B63}\x{9B64}\x{9B65}\x{9B66}\x{9B67}\x{9B68}\x{9B69}\x{9B6A}\x{9B6B}' + . '\x{9B6C}\x{9B6D}\x{9B6E}\x{9B6F}\x{9B70}\x{9B71}\x{9B73}\x{9B74}\x{9B75}' + . '\x{9B76}\x{9B77}\x{9B78}\x{9B79}\x{9B7A}\x{9B7B}\x{9B7C}\x{9B7D}\x{9B7E}' + . '\x{9B7F}\x{9B80}\x{9B81}\x{9B82}\x{9B83}\x{9B84}\x{9B85}\x{9B86}\x{9B87}' + . '\x{9B88}\x{9B8A}\x{9B8B}\x{9B8D}\x{9B8E}\x{9B8F}\x{9B90}\x{9B91}\x{9B92}' + . '\x{9B93}\x{9B94}\x{9B95}\x{9B96}\x{9B97}\x{9B98}\x{9B9A}\x{9B9B}\x{9B9C}' + . '\x{9B9D}\x{9B9E}\x{9B9F}\x{9BA0}\x{9BA1}\x{9BA2}\x{9BA3}\x{9BA4}\x{9BA5}' + . '\x{9BA6}\x{9BA7}\x{9BA8}\x{9BA9}\x{9BAA}\x{9BAB}\x{9BAC}\x{9BAD}\x{9BAE}' + . '\x{9BAF}\x{9BB0}\x{9BB1}\x{9BB2}\x{9BB3}\x{9BB4}\x{9BB5}\x{9BB6}\x{9BB7}' + . '\x{9BB8}\x{9BB9}\x{9BBA}\x{9BBB}\x{9BBC}\x{9BBD}\x{9BBE}\x{9BBF}\x{9BC0}' + . '\x{9BC1}\x{9BC3}\x{9BC4}\x{9BC5}\x{9BC6}\x{9BC7}\x{9BC8}\x{9BC9}\x{9BCA}' + . '\x{9BCB}\x{9BCC}\x{9BCD}\x{9BCE}\x{9BCF}\x{9BD0}\x{9BD1}\x{9BD2}\x{9BD3}' + . '\x{9BD4}\x{9BD5}\x{9BD6}\x{9BD7}\x{9BD8}\x{9BD9}\x{9BDA}\x{9BDB}\x{9BDC}' + . '\x{9BDD}\x{9BDE}\x{9BDF}\x{9BE0}\x{9BE1}\x{9BE2}\x{9BE3}\x{9BE4}\x{9BE5}' + . '\x{9BE6}\x{9BE7}\x{9BE8}\x{9BE9}\x{9BEA}\x{9BEB}\x{9BEC}\x{9BED}\x{9BEE}' + . '\x{9BEF}\x{9BF0}\x{9BF1}\x{9BF2}\x{9BF3}\x{9BF4}\x{9BF5}\x{9BF7}\x{9BF8}' + . '\x{9BF9}\x{9BFA}\x{9BFB}\x{9BFC}\x{9BFD}\x{9BFE}\x{9BFF}\x{9C02}\x{9C05}' + . '\x{9C06}\x{9C07}\x{9C08}\x{9C09}\x{9C0A}\x{9C0B}\x{9C0C}\x{9C0D}\x{9C0E}' + . '\x{9C0F}\x{9C10}\x{9C11}\x{9C12}\x{9C13}\x{9C14}\x{9C15}\x{9C16}\x{9C17}' + . '\x{9C18}\x{9C19}\x{9C1A}\x{9C1B}\x{9C1C}\x{9C1D}\x{9C1E}\x{9C1F}\x{9C20}' + . '\x{9C21}\x{9C22}\x{9C23}\x{9C24}\x{9C25}\x{9C26}\x{9C27}\x{9C28}\x{9C29}' + . '\x{9C2A}\x{9C2B}\x{9C2C}\x{9C2D}\x{9C2F}\x{9C30}\x{9C31}\x{9C32}\x{9C33}' + . '\x{9C34}\x{9C35}\x{9C36}\x{9C37}\x{9C38}\x{9C39}\x{9C3A}\x{9C3B}\x{9C3C}' + . '\x{9C3D}\x{9C3E}\x{9C3F}\x{9C40}\x{9C41}\x{9C43}\x{9C44}\x{9C45}\x{9C46}' + . '\x{9C47}\x{9C48}\x{9C49}\x{9C4A}\x{9C4B}\x{9C4C}\x{9C4D}\x{9C4E}\x{9C50}' + . '\x{9C52}\x{9C53}\x{9C54}\x{9C55}\x{9C56}\x{9C57}\x{9C58}\x{9C59}\x{9C5A}' + . '\x{9C5B}\x{9C5C}\x{9C5D}\x{9C5E}\x{9C5F}\x{9C60}\x{9C62}\x{9C63}\x{9C65}' + . '\x{9C66}\x{9C67}\x{9C68}\x{9C69}\x{9C6A}\x{9C6B}\x{9C6C}\x{9C6D}\x{9C6E}' + . '\x{9C6F}\x{9C70}\x{9C71}\x{9C72}\x{9C73}\x{9C74}\x{9C75}\x{9C77}\x{9C78}' + . '\x{9C79}\x{9C7A}\x{9C7C}\x{9C7D}\x{9C7E}\x{9C7F}\x{9C80}\x{9C81}\x{9C82}' + . '\x{9C83}\x{9C84}\x{9C85}\x{9C86}\x{9C87}\x{9C88}\x{9C89}\x{9C8A}\x{9C8B}' + . '\x{9C8C}\x{9C8D}\x{9C8E}\x{9C8F}\x{9C90}\x{9C91}\x{9C92}\x{9C93}\x{9C94}' + . '\x{9C95}\x{9C96}\x{9C97}\x{9C98}\x{9C99}\x{9C9A}\x{9C9B}\x{9C9C}\x{9C9D}' + . '\x{9C9E}\x{9C9F}\x{9CA0}\x{9CA1}\x{9CA2}\x{9CA3}\x{9CA4}\x{9CA5}\x{9CA6}' + . '\x{9CA7}\x{9CA8}\x{9CA9}\x{9CAA}\x{9CAB}\x{9CAC}\x{9CAD}\x{9CAE}\x{9CAF}' + . '\x{9CB0}\x{9CB1}\x{9CB2}\x{9CB3}\x{9CB4}\x{9CB5}\x{9CB6}\x{9CB7}\x{9CB8}' + . '\x{9CB9}\x{9CBA}\x{9CBB}\x{9CBC}\x{9CBD}\x{9CBE}\x{9CBF}\x{9CC0}\x{9CC1}' + . '\x{9CC2}\x{9CC3}\x{9CC4}\x{9CC5}\x{9CC6}\x{9CC7}\x{9CC8}\x{9CC9}\x{9CCA}' + . '\x{9CCB}\x{9CCC}\x{9CCD}\x{9CCE}\x{9CCF}\x{9CD0}\x{9CD1}\x{9CD2}\x{9CD3}' + . '\x{9CD4}\x{9CD5}\x{9CD6}\x{9CD7}\x{9CD8}\x{9CD9}\x{9CDA}\x{9CDB}\x{9CDC}' + . '\x{9CDD}\x{9CDE}\x{9CDF}\x{9CE0}\x{9CE1}\x{9CE2}\x{9CE3}\x{9CE4}\x{9CE5}' + . '\x{9CE6}\x{9CE7}\x{9CE8}\x{9CE9}\x{9CEA}\x{9CEB}\x{9CEC}\x{9CED}\x{9CEE}' + . '\x{9CEF}\x{9CF0}\x{9CF1}\x{9CF2}\x{9CF3}\x{9CF4}\x{9CF5}\x{9CF6}\x{9CF7}' + . '\x{9CF8}\x{9CF9}\x{9CFA}\x{9CFB}\x{9CFC}\x{9CFD}\x{9CFE}\x{9CFF}\x{9D00}' + . '\x{9D01}\x{9D02}\x{9D03}\x{9D04}\x{9D05}\x{9D06}\x{9D07}\x{9D08}\x{9D09}' + . '\x{9D0A}\x{9D0B}\x{9D0F}\x{9D10}\x{9D12}\x{9D13}\x{9D14}\x{9D15}\x{9D16}' + . '\x{9D17}\x{9D18}\x{9D19}\x{9D1A}\x{9D1B}\x{9D1C}\x{9D1D}\x{9D1E}\x{9D1F}' + . '\x{9D20}\x{9D21}\x{9D22}\x{9D23}\x{9D24}\x{9D25}\x{9D26}\x{9D28}\x{9D29}' + . '\x{9D2B}\x{9D2D}\x{9D2E}\x{9D2F}\x{9D30}\x{9D31}\x{9D32}\x{9D33}\x{9D34}' + . '\x{9D36}\x{9D37}\x{9D38}\x{9D39}\x{9D3A}\x{9D3B}\x{9D3D}\x{9D3E}\x{9D3F}' + . '\x{9D40}\x{9D41}\x{9D42}\x{9D43}\x{9D45}\x{9D46}\x{9D47}\x{9D48}\x{9D49}' + . '\x{9D4A}\x{9D4B}\x{9D4C}\x{9D4D}\x{9D4E}\x{9D4F}\x{9D50}\x{9D51}\x{9D52}' + . '\x{9D53}\x{9D54}\x{9D55}\x{9D56}\x{9D57}\x{9D58}\x{9D59}\x{9D5A}\x{9D5B}' + . '\x{9D5C}\x{9D5D}\x{9D5E}\x{9D5F}\x{9D60}\x{9D61}\x{9D62}\x{9D63}\x{9D64}' + . '\x{9D65}\x{9D66}\x{9D67}\x{9D68}\x{9D69}\x{9D6A}\x{9D6B}\x{9D6C}\x{9D6E}' + . '\x{9D6F}\x{9D70}\x{9D71}\x{9D72}\x{9D73}\x{9D74}\x{9D75}\x{9D76}\x{9D77}' + . '\x{9D78}\x{9D79}\x{9D7A}\x{9D7B}\x{9D7C}\x{9D7D}\x{9D7E}\x{9D7F}\x{9D80}' + . '\x{9D81}\x{9D82}\x{9D83}\x{9D84}\x{9D85}\x{9D86}\x{9D87}\x{9D88}\x{9D89}' + . '\x{9D8A}\x{9D8B}\x{9D8C}\x{9D8D}\x{9D8E}\x{9D90}\x{9D91}\x{9D92}\x{9D93}' + . '\x{9D94}\x{9D96}\x{9D97}\x{9D98}\x{9D99}\x{9D9A}\x{9D9B}\x{9D9C}\x{9D9D}' + . '\x{9D9E}\x{9D9F}\x{9DA0}\x{9DA1}\x{9DA2}\x{9DA3}\x{9DA4}\x{9DA5}\x{9DA6}' + . '\x{9DA7}\x{9DA8}\x{9DA9}\x{9DAA}\x{9DAB}\x{9DAC}\x{9DAD}\x{9DAF}\x{9DB0}' + . '\x{9DB1}\x{9DB2}\x{9DB3}\x{9DB4}\x{9DB5}\x{9DB6}\x{9DB7}\x{9DB8}\x{9DB9}' + . '\x{9DBA}\x{9DBB}\x{9DBC}\x{9DBE}\x{9DBF}\x{9DC1}\x{9DC2}\x{9DC3}\x{9DC4}' + . '\x{9DC5}\x{9DC7}\x{9DC8}\x{9DC9}\x{9DCA}\x{9DCB}\x{9DCC}\x{9DCD}\x{9DCE}' + . '\x{9DCF}\x{9DD0}\x{9DD1}\x{9DD2}\x{9DD3}\x{9DD4}\x{9DD5}\x{9DD6}\x{9DD7}' + . '\x{9DD8}\x{9DD9}\x{9DDA}\x{9DDB}\x{9DDC}\x{9DDD}\x{9DDE}\x{9DDF}\x{9DE0}' + . '\x{9DE1}\x{9DE2}\x{9DE3}\x{9DE4}\x{9DE5}\x{9DE6}\x{9DE7}\x{9DE8}\x{9DE9}' + . '\x{9DEB}\x{9DEC}\x{9DED}\x{9DEE}\x{9DEF}\x{9DF0}\x{9DF1}\x{9DF2}\x{9DF3}' + . '\x{9DF4}\x{9DF5}\x{9DF6}\x{9DF7}\x{9DF8}\x{9DF9}\x{9DFA}\x{9DFB}\x{9DFD}' + . '\x{9DFE}\x{9DFF}\x{9E00}\x{9E01}\x{9E02}\x{9E03}\x{9E04}\x{9E05}\x{9E06}' + . '\x{9E07}\x{9E08}\x{9E09}\x{9E0A}\x{9E0B}\x{9E0C}\x{9E0D}\x{9E0F}\x{9E10}' + . '\x{9E11}\x{9E12}\x{9E13}\x{9E14}\x{9E15}\x{9E17}\x{9E18}\x{9E19}\x{9E1A}' + . '\x{9E1B}\x{9E1D}\x{9E1E}\x{9E1F}\x{9E20}\x{9E21}\x{9E22}\x{9E23}\x{9E24}' + . '\x{9E25}\x{9E26}\x{9E27}\x{9E28}\x{9E29}\x{9E2A}\x{9E2B}\x{9E2C}\x{9E2D}' + . '\x{9E2E}\x{9E2F}\x{9E30}\x{9E31}\x{9E32}\x{9E33}\x{9E34}\x{9E35}\x{9E36}' + . '\x{9E37}\x{9E38}\x{9E39}\x{9E3A}\x{9E3B}\x{9E3C}\x{9E3D}\x{9E3E}\x{9E3F}' + . '\x{9E40}\x{9E41}\x{9E42}\x{9E43}\x{9E44}\x{9E45}\x{9E46}\x{9E47}\x{9E48}' + . '\x{9E49}\x{9E4A}\x{9E4B}\x{9E4C}\x{9E4D}\x{9E4E}\x{9E4F}\x{9E50}\x{9E51}' + . '\x{9E52}\x{9E53}\x{9E54}\x{9E55}\x{9E56}\x{9E57}\x{9E58}\x{9E59}\x{9E5A}' + . '\x{9E5B}\x{9E5C}\x{9E5D}\x{9E5E}\x{9E5F}\x{9E60}\x{9E61}\x{9E62}\x{9E63}' + . '\x{9E64}\x{9E65}\x{9E66}\x{9E67}\x{9E68}\x{9E69}\x{9E6A}\x{9E6B}\x{9E6C}' + . '\x{9E6D}\x{9E6E}\x{9E6F}\x{9E70}\x{9E71}\x{9E72}\x{9E73}\x{9E74}\x{9E75}' + . '\x{9E76}\x{9E77}\x{9E79}\x{9E7A}\x{9E7C}\x{9E7D}\x{9E7E}\x{9E7F}\x{9E80}' + . '\x{9E81}\x{9E82}\x{9E83}\x{9E84}\x{9E85}\x{9E86}\x{9E87}\x{9E88}\x{9E89}' + . '\x{9E8A}\x{9E8B}\x{9E8C}\x{9E8D}\x{9E8E}\x{9E91}\x{9E92}\x{9E93}\x{9E94}' + . '\x{9E96}\x{9E97}\x{9E99}\x{9E9A}\x{9E9B}\x{9E9C}\x{9E9D}\x{9E9F}\x{9EA0}' + . '\x{9EA1}\x{9EA3}\x{9EA4}\x{9EA5}\x{9EA6}\x{9EA7}\x{9EA8}\x{9EA9}\x{9EAA}' + . '\x{9EAD}\x{9EAE}\x{9EAF}\x{9EB0}\x{9EB2}\x{9EB3}\x{9EB4}\x{9EB5}\x{9EB6}' + . '\x{9EB7}\x{9EB8}\x{9EBB}\x{9EBC}\x{9EBD}\x{9EBE}\x{9EBF}\x{9EC0}\x{9EC1}' + . '\x{9EC2}\x{9EC3}\x{9EC4}\x{9EC5}\x{9EC6}\x{9EC7}\x{9EC8}\x{9EC9}\x{9ECA}' + . '\x{9ECB}\x{9ECC}\x{9ECD}\x{9ECE}\x{9ECF}\x{9ED0}\x{9ED1}\x{9ED2}\x{9ED3}' + . '\x{9ED4}\x{9ED5}\x{9ED6}\x{9ED7}\x{9ED8}\x{9ED9}\x{9EDA}\x{9EDB}\x{9EDC}' + . '\x{9EDD}\x{9EDE}\x{9EDF}\x{9EE0}\x{9EE1}\x{9EE2}\x{9EE3}\x{9EE4}\x{9EE5}' + . '\x{9EE6}\x{9EE7}\x{9EE8}\x{9EE9}\x{9EEA}\x{9EEB}\x{9EED}\x{9EEE}\x{9EEF}' + . '\x{9EF0}\x{9EF2}\x{9EF3}\x{9EF4}\x{9EF5}\x{9EF6}\x{9EF7}\x{9EF8}\x{9EF9}' + . '\x{9EFA}\x{9EFB}\x{9EFC}\x{9EFD}\x{9EFE}\x{9EFF}\x{9F00}\x{9F01}\x{9F02}' + . '\x{9F04}\x{9F05}\x{9F06}\x{9F07}\x{9F08}\x{9F09}\x{9F0A}\x{9F0B}\x{9F0C}' + . '\x{9F0D}\x{9F0E}\x{9F0F}\x{9F10}\x{9F12}\x{9F13}\x{9F15}\x{9F16}\x{9F17}' + . '\x{9F18}\x{9F19}\x{9F1A}\x{9F1B}\x{9F1C}\x{9F1D}\x{9F1E}\x{9F1F}\x{9F20}' + . '\x{9F22}\x{9F23}\x{9F24}\x{9F25}\x{9F27}\x{9F28}\x{9F29}\x{9F2A}\x{9F2B}' + . '\x{9F2C}\x{9F2D}\x{9F2E}\x{9F2F}\x{9F30}\x{9F31}\x{9F32}\x{9F33}\x{9F34}' + . '\x{9F35}\x{9F36}\x{9F37}\x{9F38}\x{9F39}\x{9F3A}\x{9F3B}\x{9F3C}\x{9F3D}' + . '\x{9F3E}\x{9F3F}\x{9F40}\x{9F41}\x{9F42}\x{9F43}\x{9F44}\x{9F46}\x{9F47}' + . '\x{9F48}\x{9F49}\x{9F4A}\x{9F4B}\x{9F4C}\x{9F4D}\x{9F4E}\x{9F4F}\x{9F50}' + . '\x{9F51}\x{9F52}\x{9F54}\x{9F55}\x{9F56}\x{9F57}\x{9F58}\x{9F59}\x{9F5A}' + . '\x{9F5B}\x{9F5C}\x{9F5D}\x{9F5E}\x{9F5F}\x{9F60}\x{9F61}\x{9F63}\x{9F64}' + . '\x{9F65}\x{9F66}\x{9F67}\x{9F68}\x{9F69}\x{9F6A}\x{9F6B}\x{9F6C}\x{9F6E}' + . '\x{9F6F}\x{9F70}\x{9F71}\x{9F72}\x{9F73}\x{9F74}\x{9F75}\x{9F76}\x{9F77}' + . '\x{9F78}\x{9F79}\x{9F7A}\x{9F7B}\x{9F7C}\x{9F7D}\x{9F7E}\x{9F7F}\x{9F80}' + . '\x{9F81}\x{9F82}\x{9F83}\x{9F84}\x{9F85}\x{9F86}\x{9F87}\x{9F88}\x{9F89}' + . '\x{9F8A}\x{9F8B}\x{9F8C}\x{9F8D}\x{9F8E}\x{9F8F}\x{9F90}\x{9F91}\x{9F92}' + . '\x{9F93}\x{9F94}\x{9F95}\x{9F96}\x{9F97}\x{9F98}\x{9F99}\x{9F9A}\x{9F9B}' + . '\x{9F9C}\x{9F9D}\x{9F9E}\x{9F9F}\x{9FA0}\x{9FA2}\x{9FA4}\x{9FA5}]{1,20}$/iu', +]; diff --git a/lib/laminas/laminas-validator/src/Hostname/Cn.php b/lib/laminas/laminas-validator/src/Hostname/Cn.php new file mode 100644 index 0000000000..d280764b07 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Hostname/Cn.php @@ -0,0 +1,2179 @@ + '/^[\x{002d}0-9a-z\x{3447}\x{3473}\x{359E}\x{360E}\x{361A}\x{3918}\x{396E}\x{39CF}\x{39D0}' + . '\x{39DF}\x{3A73}\x{3B4E}\x{3C6E}\x{3CE0}\x{4056}\x{415F}\x{4337}\x{43AC}' + . '\x{43B1}\x{43DD}\x{44D6}\x{464C}\x{4661}\x{4723}\x{4729}\x{477C}\x{478D}' + . '\x{4947}\x{497A}\x{497D}\x{4982}\x{4983}\x{4985}\x{4986}\x{499B}\x{499F}' + . '\x{49B6}\x{49B7}\x{4C77}\x{4C9F}\x{4CA0}\x{4CA1}\x{4CA2}\x{4CA3}\x{4D13}' + . '\x{4D14}\x{4D15}\x{4D16}\x{4D17}\x{4D18}\x{4D19}\x{4DAE}\x{4E00}\x{4E01}' + . '\x{4E02}\x{4E03}\x{4E04}\x{4E05}\x{4E06}\x{4E07}\x{4E08}\x{4E09}\x{4E0A}' + . '\x{4E0B}\x{4E0C}\x{4E0D}\x{4E0E}\x{4E0F}\x{4E10}\x{4E11}\x{4E13}\x{4E14}' + . '\x{4E15}\x{4E16}\x{4E17}\x{4E18}\x{4E19}\x{4E1A}\x{4E1B}\x{4E1C}\x{4E1D}' + . '\x{4E1E}\x{4E1F}\x{4E20}\x{4E21}\x{4E22}\x{4E23}\x{4E24}\x{4E25}\x{4E26}' + . '\x{4E27}\x{4E28}\x{4E2A}\x{4E2B}\x{4E2C}\x{4E2D}\x{4E2E}\x{4E2F}\x{4E30}' + . '\x{4E31}\x{4E32}\x{4E33}\x{4E34}\x{4E35}\x{4E36}\x{4E37}\x{4E38}\x{4E39}' + . '\x{4E3A}\x{4E3B}\x{4E3C}\x{4E3D}\x{4E3E}\x{4E3F}\x{4E40}\x{4E41}\x{4E42}' + . '\x{4E43}\x{4E44}\x{4E45}\x{4E46}\x{4E47}\x{4E48}\x{4E49}\x{4E4A}\x{4E4B}' + . '\x{4E4C}\x{4E4D}\x{4E4E}\x{4E4F}\x{4E50}\x{4E51}\x{4E52}\x{4E53}\x{4E54}' + . '\x{4E56}\x{4E57}\x{4E58}\x{4E59}\x{4E5A}\x{4E5B}\x{4E5C}\x{4E5D}\x{4E5E}' + . '\x{4E5F}\x{4E60}\x{4E61}\x{4E62}\x{4E63}\x{4E64}\x{4E65}\x{4E66}\x{4E67}' + . '\x{4E69}\x{4E6A}\x{4E6B}\x{4E6C}\x{4E6D}\x{4E6E}\x{4E6F}\x{4E70}\x{4E71}' + . '\x{4E72}\x{4E73}\x{4E74}\x{4E75}\x{4E76}\x{4E77}\x{4E78}\x{4E7A}\x{4E7B}' + . '\x{4E7C}\x{4E7D}\x{4E7E}\x{4E7F}\x{4E80}\x{4E81}\x{4E82}\x{4E83}\x{4E84}' + . '\x{4E85}\x{4E86}\x{4E87}\x{4E88}\x{4E89}\x{4E8B}\x{4E8C}\x{4E8D}\x{4E8E}' + . '\x{4E8F}\x{4E90}\x{4E91}\x{4E92}\x{4E93}\x{4E94}\x{4E95}\x{4E97}\x{4E98}' + . '\x{4E99}\x{4E9A}\x{4E9B}\x{4E9C}\x{4E9D}\x{4E9E}\x{4E9F}\x{4EA0}\x{4EA1}' + . '\x{4EA2}\x{4EA4}\x{4EA5}\x{4EA6}\x{4EA7}\x{4EA8}\x{4EA9}\x{4EAA}\x{4EAB}' + . '\x{4EAC}\x{4EAD}\x{4EAE}\x{4EAF}\x{4EB0}\x{4EB1}\x{4EB2}\x{4EB3}\x{4EB4}' + . '\x{4EB5}\x{4EB6}\x{4EB7}\x{4EB8}\x{4EB9}\x{4EBA}\x{4EBB}\x{4EBD}\x{4EBE}' + . '\x{4EBF}\x{4EC0}\x{4EC1}\x{4EC2}\x{4EC3}\x{4EC4}\x{4EC5}\x{4EC6}\x{4EC7}' + . '\x{4EC8}\x{4EC9}\x{4ECA}\x{4ECB}\x{4ECD}\x{4ECE}\x{4ECF}\x{4ED0}\x{4ED1}' + . '\x{4ED2}\x{4ED3}\x{4ED4}\x{4ED5}\x{4ED6}\x{4ED7}\x{4ED8}\x{4ED9}\x{4EDA}' + . '\x{4EDB}\x{4EDC}\x{4EDD}\x{4EDE}\x{4EDF}\x{4EE0}\x{4EE1}\x{4EE2}\x{4EE3}' + . '\x{4EE4}\x{4EE5}\x{4EE6}\x{4EE8}\x{4EE9}\x{4EEA}\x{4EEB}\x{4EEC}\x{4EEF}' + . '\x{4EF0}\x{4EF1}\x{4EF2}\x{4EF3}\x{4EF4}\x{4EF5}\x{4EF6}\x{4EF7}\x{4EFB}' + . '\x{4EFD}\x{4EFF}\x{4F00}\x{4F01}\x{4F02}\x{4F03}\x{4F04}\x{4F05}\x{4F06}' + . '\x{4F08}\x{4F09}\x{4F0A}\x{4F0B}\x{4F0C}\x{4F0D}\x{4F0E}\x{4F0F}\x{4F10}' + . '\x{4F11}\x{4F12}\x{4F13}\x{4F14}\x{4F15}\x{4F17}\x{4F18}\x{4F19}\x{4F1A}' + . '\x{4F1B}\x{4F1C}\x{4F1D}\x{4F1E}\x{4F1F}\x{4F20}\x{4F21}\x{4F22}\x{4F23}' + . '\x{4F24}\x{4F25}\x{4F26}\x{4F27}\x{4F29}\x{4F2A}\x{4F2B}\x{4F2C}\x{4F2D}' + . '\x{4F2E}\x{4F2F}\x{4F30}\x{4F32}\x{4F33}\x{4F34}\x{4F36}\x{4F38}\x{4F39}' + . '\x{4F3A}\x{4F3B}\x{4F3C}\x{4F3D}\x{4F3E}\x{4F3F}\x{4F41}\x{4F42}\x{4F43}' + . '\x{4F45}\x{4F46}\x{4F47}\x{4F48}\x{4F49}\x{4F4A}\x{4F4B}\x{4F4C}\x{4F4D}' + . '\x{4F4E}\x{4F4F}\x{4F50}\x{4F51}\x{4F52}\x{4F53}\x{4F54}\x{4F55}\x{4F56}' + . '\x{4F57}\x{4F58}\x{4F59}\x{4F5A}\x{4F5B}\x{4F5C}\x{4F5D}\x{4F5E}\x{4F5F}' + . '\x{4F60}\x{4F61}\x{4F62}\x{4F63}\x{4F64}\x{4F65}\x{4F66}\x{4F67}\x{4F68}' + . '\x{4F69}\x{4F6A}\x{4F6B}\x{4F6C}\x{4F6D}\x{4F6E}\x{4F6F}\x{4F70}\x{4F72}' + . '\x{4F73}\x{4F74}\x{4F75}\x{4F76}\x{4F77}\x{4F78}\x{4F79}\x{4F7A}\x{4F7B}' + . '\x{4F7C}\x{4F7D}\x{4F7E}\x{4F7F}\x{4F80}\x{4F81}\x{4F82}\x{4F83}\x{4F84}' + . '\x{4F85}\x{4F86}\x{4F87}\x{4F88}\x{4F89}\x{4F8A}\x{4F8B}\x{4F8D}\x{4F8F}' + . '\x{4F90}\x{4F91}\x{4F92}\x{4F93}\x{4F94}\x{4F95}\x{4F96}\x{4F97}\x{4F98}' + . '\x{4F99}\x{4F9A}\x{4F9B}\x{4F9C}\x{4F9D}\x{4F9E}\x{4F9F}\x{4FA0}\x{4FA1}' + . '\x{4FA3}\x{4FA4}\x{4FA5}\x{4FA6}\x{4FA7}\x{4FA8}\x{4FA9}\x{4FAA}\x{4FAB}' + . '\x{4FAC}\x{4FAE}\x{4FAF}\x{4FB0}\x{4FB1}\x{4FB2}\x{4FB3}\x{4FB4}\x{4FB5}' + . '\x{4FB6}\x{4FB7}\x{4FB8}\x{4FB9}\x{4FBA}\x{4FBB}\x{4FBC}\x{4FBE}\x{4FBF}' + . '\x{4FC0}\x{4FC1}\x{4FC2}\x{4FC3}\x{4FC4}\x{4FC5}\x{4FC7}\x{4FC9}\x{4FCA}' + . '\x{4FCB}\x{4FCD}\x{4FCE}\x{4FCF}\x{4FD0}\x{4FD1}\x{4FD2}\x{4FD3}\x{4FD4}' + . '\x{4FD5}\x{4FD6}\x{4FD7}\x{4FD8}\x{4FD9}\x{4FDA}\x{4FDB}\x{4FDC}\x{4FDD}' + . '\x{4FDE}\x{4FDF}\x{4FE0}\x{4FE1}\x{4FE3}\x{4FE4}\x{4FE5}\x{4FE6}\x{4FE7}' + . '\x{4FE8}\x{4FE9}\x{4FEA}\x{4FEB}\x{4FEC}\x{4FED}\x{4FEE}\x{4FEF}\x{4FF0}' + . '\x{4FF1}\x{4FF2}\x{4FF3}\x{4FF4}\x{4FF5}\x{4FF6}\x{4FF7}\x{4FF8}\x{4FF9}' + . '\x{4FFA}\x{4FFB}\x{4FFE}\x{4FFF}\x{5000}\x{5001}\x{5002}\x{5003}\x{5004}' + . '\x{5005}\x{5006}\x{5007}\x{5008}\x{5009}\x{500A}\x{500B}\x{500C}\x{500D}' + . '\x{500E}\x{500F}\x{5011}\x{5012}\x{5013}\x{5014}\x{5015}\x{5016}\x{5017}' + . '\x{5018}\x{5019}\x{501A}\x{501B}\x{501C}\x{501D}\x{501E}\x{501F}\x{5020}' + . '\x{5021}\x{5022}\x{5023}\x{5024}\x{5025}\x{5026}\x{5027}\x{5028}\x{5029}' + . '\x{502A}\x{502B}\x{502C}\x{502D}\x{502E}\x{502F}\x{5030}\x{5031}\x{5032}' + . '\x{5033}\x{5035}\x{5036}\x{5037}\x{5039}\x{503A}\x{503B}\x{503C}\x{503E}' + . '\x{503F}\x{5040}\x{5041}\x{5043}\x{5044}\x{5045}\x{5046}\x{5047}\x{5048}' + . '\x{5049}\x{504A}\x{504B}\x{504C}\x{504D}\x{504E}\x{504F}\x{5051}\x{5053}' + . '\x{5054}\x{5055}\x{5056}\x{5057}\x{5059}\x{505A}\x{505B}\x{505C}\x{505D}' + . '\x{505E}\x{505F}\x{5060}\x{5061}\x{5062}\x{5063}\x{5064}\x{5065}\x{5066}' + . '\x{5067}\x{5068}\x{5069}\x{506A}\x{506B}\x{506C}\x{506D}\x{506E}\x{506F}' + . '\x{5070}\x{5071}\x{5072}\x{5073}\x{5074}\x{5075}\x{5076}\x{5077}\x{5078}' + . '\x{5079}\x{507A}\x{507B}\x{507D}\x{507E}\x{507F}\x{5080}\x{5082}\x{5083}' + . '\x{5084}\x{5085}\x{5086}\x{5087}\x{5088}\x{5089}\x{508A}\x{508B}\x{508C}' + . '\x{508D}\x{508E}\x{508F}\x{5090}\x{5091}\x{5092}\x{5094}\x{5095}\x{5096}' + . '\x{5098}\x{5099}\x{509A}\x{509B}\x{509C}\x{509D}\x{509E}\x{50A2}\x{50A3}' + . '\x{50A4}\x{50A5}\x{50A6}\x{50A7}\x{50A8}\x{50A9}\x{50AA}\x{50AB}\x{50AC}' + . '\x{50AD}\x{50AE}\x{50AF}\x{50B0}\x{50B1}\x{50B2}\x{50B3}\x{50B4}\x{50B5}' + . '\x{50B6}\x{50B7}\x{50B8}\x{50BA}\x{50BB}\x{50BC}\x{50BD}\x{50BE}\x{50BF}' + . '\x{50C0}\x{50C1}\x{50C2}\x{50C4}\x{50C5}\x{50C6}\x{50C7}\x{50C8}\x{50C9}' + . '\x{50CA}\x{50CB}\x{50CC}\x{50CD}\x{50CE}\x{50CF}\x{50D0}\x{50D1}\x{50D2}' + . '\x{50D3}\x{50D4}\x{50D5}\x{50D6}\x{50D7}\x{50D9}\x{50DA}\x{50DB}\x{50DC}' + . '\x{50DD}\x{50DE}\x{50E0}\x{50E3}\x{50E4}\x{50E5}\x{50E6}\x{50E7}\x{50E8}' + . '\x{50E9}\x{50EA}\x{50EC}\x{50ED}\x{50EE}\x{50EF}\x{50F0}\x{50F1}\x{50F2}' + . '\x{50F3}\x{50F5}\x{50F6}\x{50F8}\x{50F9}\x{50FA}\x{50FB}\x{50FC}\x{50FD}' + . '\x{50FE}\x{50FF}\x{5100}\x{5101}\x{5102}\x{5103}\x{5104}\x{5105}\x{5106}' + . '\x{5107}\x{5108}\x{5109}\x{510A}\x{510B}\x{510C}\x{510D}\x{510E}\x{510F}' + . '\x{5110}\x{5111}\x{5112}\x{5113}\x{5114}\x{5115}\x{5116}\x{5117}\x{5118}' + . '\x{5119}\x{511A}\x{511C}\x{511D}\x{511E}\x{511F}\x{5120}\x{5121}\x{5122}' + . '\x{5123}\x{5124}\x{5125}\x{5126}\x{5127}\x{5129}\x{512A}\x{512C}\x{512D}' + . '\x{512E}\x{512F}\x{5130}\x{5131}\x{5132}\x{5133}\x{5134}\x{5135}\x{5136}' + . '\x{5137}\x{5138}\x{5139}\x{513A}\x{513B}\x{513C}\x{513D}\x{513E}\x{513F}' + . '\x{5140}\x{5141}\x{5143}\x{5144}\x{5145}\x{5146}\x{5147}\x{5148}\x{5149}' + . '\x{514B}\x{514C}\x{514D}\x{514E}\x{5150}\x{5151}\x{5152}\x{5154}\x{5155}' + . '\x{5156}\x{5157}\x{5159}\x{515A}\x{515B}\x{515C}\x{515D}\x{515E}\x{515F}' + . '\x{5161}\x{5162}\x{5163}\x{5165}\x{5166}\x{5167}\x{5168}\x{5169}\x{516A}' + . '\x{516B}\x{516C}\x{516D}\x{516E}\x{516F}\x{5170}\x{5171}\x{5173}\x{5174}' + . '\x{5175}\x{5176}\x{5177}\x{5178}\x{5179}\x{517A}\x{517B}\x{517C}\x{517D}' + . '\x{517F}\x{5180}\x{5181}\x{5182}\x{5185}\x{5186}\x{5187}\x{5188}\x{5189}' + . '\x{518A}\x{518B}\x{518C}\x{518D}\x{518F}\x{5190}\x{5191}\x{5192}\x{5193}' + . '\x{5194}\x{5195}\x{5196}\x{5197}\x{5198}\x{5199}\x{519A}\x{519B}\x{519C}' + . '\x{519D}\x{519E}\x{519F}\x{51A0}\x{51A2}\x{51A4}\x{51A5}\x{51A6}\x{51A7}' + . '\x{51A8}\x{51AA}\x{51AB}\x{51AC}\x{51AE}\x{51AF}\x{51B0}\x{51B1}\x{51B2}' + . '\x{51B3}\x{51B5}\x{51B6}\x{51B7}\x{51B9}\x{51BB}\x{51BC}\x{51BD}\x{51BE}' + . '\x{51BF}\x{51C0}\x{51C1}\x{51C3}\x{51C4}\x{51C5}\x{51C6}\x{51C7}\x{51C8}' + . '\x{51C9}\x{51CA}\x{51CB}\x{51CC}\x{51CD}\x{51CE}\x{51CF}\x{51D0}\x{51D1}' + . '\x{51D4}\x{51D5}\x{51D6}\x{51D7}\x{51D8}\x{51D9}\x{51DA}\x{51DB}\x{51DC}' + . '\x{51DD}\x{51DE}\x{51E0}\x{51E1}\x{51E2}\x{51E3}\x{51E4}\x{51E5}\x{51E7}' + . '\x{51E8}\x{51E9}\x{51EA}\x{51EB}\x{51ED}\x{51EF}\x{51F0}\x{51F1}\x{51F3}' + . '\x{51F4}\x{51F5}\x{51F6}\x{51F7}\x{51F8}\x{51F9}\x{51FA}\x{51FB}\x{51FC}' + . '\x{51FD}\x{51FE}\x{51FF}\x{5200}\x{5201}\x{5202}\x{5203}\x{5204}\x{5205}' + . '\x{5206}\x{5207}\x{5208}\x{5209}\x{520A}\x{520B}\x{520C}\x{520D}\x{520E}' + . '\x{520F}\x{5210}\x{5211}\x{5212}\x{5213}\x{5214}\x{5215}\x{5216}\x{5217}' + . '\x{5218}\x{5219}\x{521A}\x{521B}\x{521C}\x{521D}\x{521E}\x{521F}\x{5220}' + . '\x{5221}\x{5222}\x{5223}\x{5224}\x{5225}\x{5226}\x{5228}\x{5229}\x{522A}' + . '\x{522B}\x{522C}\x{522D}\x{522E}\x{522F}\x{5230}\x{5231}\x{5232}\x{5233}' + . '\x{5234}\x{5235}\x{5236}\x{5237}\x{5238}\x{5239}\x{523A}\x{523B}\x{523C}' + . '\x{523D}\x{523E}\x{523F}\x{5240}\x{5241}\x{5242}\x{5243}\x{5244}\x{5245}' + . '\x{5246}\x{5247}\x{5248}\x{5249}\x{524A}\x{524B}\x{524C}\x{524D}\x{524E}' + . '\x{5250}\x{5251}\x{5252}\x{5254}\x{5255}\x{5256}\x{5257}\x{5258}\x{5259}' + . '\x{525A}\x{525B}\x{525C}\x{525D}\x{525E}\x{525F}\x{5260}\x{5261}\x{5262}' + . '\x{5263}\x{5264}\x{5265}\x{5267}\x{5268}\x{5269}\x{526A}\x{526B}\x{526C}' + . '\x{526D}\x{526E}\x{526F}\x{5270}\x{5272}\x{5273}\x{5274}\x{5275}\x{5276}' + . '\x{5277}\x{5278}\x{527A}\x{527B}\x{527C}\x{527D}\x{527E}\x{527F}\x{5280}' + . '\x{5281}\x{5282}\x{5283}\x{5284}\x{5286}\x{5287}\x{5288}\x{5289}\x{528A}' + . '\x{528B}\x{528C}\x{528D}\x{528F}\x{5290}\x{5291}\x{5292}\x{5293}\x{5294}' + . '\x{5295}\x{5296}\x{5297}\x{5298}\x{5299}\x{529A}\x{529B}\x{529C}\x{529D}' + . '\x{529E}\x{529F}\x{52A0}\x{52A1}\x{52A2}\x{52A3}\x{52A5}\x{52A6}\x{52A7}' + . '\x{52A8}\x{52A9}\x{52AA}\x{52AB}\x{52AC}\x{52AD}\x{52AE}\x{52AF}\x{52B0}' + . '\x{52B1}\x{52B2}\x{52B3}\x{52B4}\x{52B5}\x{52B6}\x{52B7}\x{52B8}\x{52B9}' + . '\x{52BA}\x{52BB}\x{52BC}\x{52BD}\x{52BE}\x{52BF}\x{52C0}\x{52C1}\x{52C2}' + . '\x{52C3}\x{52C6}\x{52C7}\x{52C9}\x{52CA}\x{52CB}\x{52CD}\x{52CF}\x{52D0}' + . '\x{52D2}\x{52D3}\x{52D5}\x{52D6}\x{52D7}\x{52D8}\x{52D9}\x{52DA}\x{52DB}' + . '\x{52DC}\x{52DD}\x{52DE}\x{52DF}\x{52E0}\x{52E2}\x{52E3}\x{52E4}\x{52E6}' + . '\x{52E7}\x{52E8}\x{52E9}\x{52EA}\x{52EB}\x{52EC}\x{52ED}\x{52EF}\x{52F0}' + . '\x{52F1}\x{52F2}\x{52F3}\x{52F4}\x{52F5}\x{52F6}\x{52F7}\x{52F8}\x{52F9}' + . '\x{52FA}\x{52FB}\x{52FC}\x{52FD}\x{52FE}\x{52FF}\x{5300}\x{5301}\x{5302}' + . '\x{5305}\x{5306}\x{5307}\x{5308}\x{5309}\x{530A}\x{530B}\x{530C}\x{530D}' + . '\x{530E}\x{530F}\x{5310}\x{5311}\x{5312}\x{5313}\x{5314}\x{5315}\x{5316}' + . '\x{5317}\x{5319}\x{531A}\x{531C}\x{531D}\x{531F}\x{5320}\x{5321}\x{5322}' + . '\x{5323}\x{5324}\x{5325}\x{5326}\x{5328}\x{532A}\x{532B}\x{532C}\x{532D}' + . '\x{532E}\x{532F}\x{5330}\x{5331}\x{5333}\x{5334}\x{5337}\x{5339}\x{533A}' + . '\x{533B}\x{533C}\x{533D}\x{533E}\x{533F}\x{5340}\x{5341}\x{5343}\x{5344}' + . '\x{5345}\x{5346}\x{5347}\x{5348}\x{5349}\x{534A}\x{534B}\x{534C}\x{534D}' + . '\x{534E}\x{534F}\x{5350}\x{5351}\x{5352}\x{5353}\x{5354}\x{5355}\x{5356}' + . '\x{5357}\x{5358}\x{5359}\x{535A}\x{535C}\x{535E}\x{535F}\x{5360}\x{5361}' + . '\x{5362}\x{5363}\x{5364}\x{5365}\x{5366}\x{5367}\x{5369}\x{536B}\x{536C}' + . '\x{536E}\x{536F}\x{5370}\x{5371}\x{5372}\x{5373}\x{5374}\x{5375}\x{5376}' + . '\x{5377}\x{5378}\x{5379}\x{537A}\x{537B}\x{537C}\x{537D}\x{537E}\x{537F}' + . '\x{5381}\x{5382}\x{5383}\x{5384}\x{5385}\x{5386}\x{5387}\x{5388}\x{5389}' + . '\x{538A}\x{538B}\x{538C}\x{538D}\x{538E}\x{538F}\x{5390}\x{5391}\x{5392}' + . '\x{5393}\x{5394}\x{5395}\x{5396}\x{5397}\x{5398}\x{5399}\x{539A}\x{539B}' + . '\x{539C}\x{539D}\x{539E}\x{539F}\x{53A0}\x{53A2}\x{53A3}\x{53A4}\x{53A5}' + . '\x{53A6}\x{53A7}\x{53A8}\x{53A9}\x{53AC}\x{53AD}\x{53AE}\x{53B0}\x{53B1}' + . '\x{53B2}\x{53B3}\x{53B4}\x{53B5}\x{53B6}\x{53B7}\x{53B8}\x{53B9}\x{53BB}' + . '\x{53BC}\x{53BD}\x{53BE}\x{53BF}\x{53C0}\x{53C1}\x{53C2}\x{53C3}\x{53C4}' + . '\x{53C6}\x{53C7}\x{53C8}\x{53C9}\x{53CA}\x{53CB}\x{53CC}\x{53CD}\x{53CE}' + . '\x{53D0}\x{53D1}\x{53D2}\x{53D3}\x{53D4}\x{53D5}\x{53D6}\x{53D7}\x{53D8}' + . '\x{53D9}\x{53DB}\x{53DC}\x{53DF}\x{53E0}\x{53E1}\x{53E2}\x{53E3}\x{53E4}' + . '\x{53E5}\x{53E6}\x{53E8}\x{53E9}\x{53EA}\x{53EB}\x{53EC}\x{53ED}\x{53EE}' + . '\x{53EF}\x{53F0}\x{53F1}\x{53F2}\x{53F3}\x{53F4}\x{53F5}\x{53F6}\x{53F7}' + . '\x{53F8}\x{53F9}\x{53FA}\x{53FB}\x{53FC}\x{53FD}\x{53FE}\x{5401}\x{5402}' + . '\x{5403}\x{5404}\x{5405}\x{5406}\x{5407}\x{5408}\x{5409}\x{540A}\x{540B}' + . '\x{540C}\x{540D}\x{540E}\x{540F}\x{5410}\x{5411}\x{5412}\x{5413}\x{5414}' + . '\x{5415}\x{5416}\x{5417}\x{5418}\x{5419}\x{541B}\x{541C}\x{541D}\x{541E}' + . '\x{541F}\x{5420}\x{5421}\x{5423}\x{5424}\x{5425}\x{5426}\x{5427}\x{5428}' + . '\x{5429}\x{542A}\x{542B}\x{542C}\x{542D}\x{542E}\x{542F}\x{5430}\x{5431}' + . '\x{5432}\x{5433}\x{5434}\x{5435}\x{5436}\x{5437}\x{5438}\x{5439}\x{543A}' + . '\x{543B}\x{543C}\x{543D}\x{543E}\x{543F}\x{5440}\x{5441}\x{5442}\x{5443}' + . '\x{5444}\x{5445}\x{5446}\x{5447}\x{5448}\x{5449}\x{544A}\x{544B}\x{544D}' + . '\x{544E}\x{544F}\x{5450}\x{5451}\x{5452}\x{5453}\x{5454}\x{5455}\x{5456}' + . '\x{5457}\x{5458}\x{5459}\x{545A}\x{545B}\x{545C}\x{545E}\x{545F}\x{5460}' + . '\x{5461}\x{5462}\x{5463}\x{5464}\x{5465}\x{5466}\x{5467}\x{5468}\x{546A}' + . '\x{546B}\x{546C}\x{546D}\x{546E}\x{546F}\x{5470}\x{5471}\x{5472}\x{5473}' + . '\x{5474}\x{5475}\x{5476}\x{5477}\x{5478}\x{5479}\x{547A}\x{547B}\x{547C}' + . '\x{547D}\x{547E}\x{547F}\x{5480}\x{5481}\x{5482}\x{5483}\x{5484}\x{5485}' + . '\x{5486}\x{5487}\x{5488}\x{5489}\x{548B}\x{548C}\x{548D}\x{548E}\x{548F}' + . '\x{5490}\x{5491}\x{5492}\x{5493}\x{5494}\x{5495}\x{5496}\x{5497}\x{5498}' + . '\x{5499}\x{549A}\x{549B}\x{549C}\x{549D}\x{549E}\x{549F}\x{54A0}\x{54A1}' + . '\x{54A2}\x{54A3}\x{54A4}\x{54A5}\x{54A6}\x{54A7}\x{54A8}\x{54A9}\x{54AA}' + . '\x{54AB}\x{54AC}\x{54AD}\x{54AE}\x{54AF}\x{54B0}\x{54B1}\x{54B2}\x{54B3}' + . '\x{54B4}\x{54B6}\x{54B7}\x{54B8}\x{54B9}\x{54BA}\x{54BB}\x{54BC}\x{54BD}' + . '\x{54BE}\x{54BF}\x{54C0}\x{54C1}\x{54C2}\x{54C3}\x{54C4}\x{54C5}\x{54C6}' + . '\x{54C7}\x{54C8}\x{54C9}\x{54CA}\x{54CB}\x{54CC}\x{54CD}\x{54CE}\x{54CF}' + . '\x{54D0}\x{54D1}\x{54D2}\x{54D3}\x{54D4}\x{54D5}\x{54D6}\x{54D7}\x{54D8}' + . '\x{54D9}\x{54DA}\x{54DB}\x{54DC}\x{54DD}\x{54DE}\x{54DF}\x{54E0}\x{54E1}' + . '\x{54E2}\x{54E3}\x{54E4}\x{54E5}\x{54E6}\x{54E7}\x{54E8}\x{54E9}\x{54EA}' + . '\x{54EB}\x{54EC}\x{54ED}\x{54EE}\x{54EF}\x{54F0}\x{54F1}\x{54F2}\x{54F3}' + . '\x{54F4}\x{54F5}\x{54F7}\x{54F8}\x{54F9}\x{54FA}\x{54FB}\x{54FC}\x{54FD}' + . '\x{54FE}\x{54FF}\x{5500}\x{5501}\x{5502}\x{5503}\x{5504}\x{5505}\x{5506}' + . '\x{5507}\x{5508}\x{5509}\x{550A}\x{550B}\x{550C}\x{550D}\x{550E}\x{550F}' + . '\x{5510}\x{5511}\x{5512}\x{5513}\x{5514}\x{5516}\x{5517}\x{551A}\x{551B}' + . '\x{551C}\x{551D}\x{551E}\x{551F}\x{5520}\x{5521}\x{5522}\x{5523}\x{5524}' + . '\x{5525}\x{5526}\x{5527}\x{5528}\x{5529}\x{552A}\x{552B}\x{552C}\x{552D}' + . '\x{552E}\x{552F}\x{5530}\x{5531}\x{5532}\x{5533}\x{5534}\x{5535}\x{5536}' + . '\x{5537}\x{5538}\x{5539}\x{553A}\x{553B}\x{553C}\x{553D}\x{553E}\x{553F}' + . '\x{5540}\x{5541}\x{5542}\x{5543}\x{5544}\x{5545}\x{5546}\x{5548}\x{5549}' + . '\x{554A}\x{554B}\x{554C}\x{554D}\x{554E}\x{554F}\x{5550}\x{5551}\x{5552}' + . '\x{5553}\x{5554}\x{5555}\x{5556}\x{5557}\x{5558}\x{5559}\x{555A}\x{555B}' + . '\x{555C}\x{555D}\x{555E}\x{555F}\x{5561}\x{5562}\x{5563}\x{5564}\x{5565}' + . '\x{5566}\x{5567}\x{5568}\x{5569}\x{556A}\x{556B}\x{556C}\x{556D}\x{556E}' + . '\x{556F}\x{5570}\x{5571}\x{5572}\x{5573}\x{5574}\x{5575}\x{5576}\x{5577}' + . '\x{5578}\x{5579}\x{557B}\x{557C}\x{557D}\x{557E}\x{557F}\x{5580}\x{5581}' + . '\x{5582}\x{5583}\x{5584}\x{5585}\x{5586}\x{5587}\x{5588}\x{5589}\x{558A}' + . '\x{558B}\x{558C}\x{558D}\x{558E}\x{558F}\x{5590}\x{5591}\x{5592}\x{5593}' + . '\x{5594}\x{5595}\x{5596}\x{5597}\x{5598}\x{5599}\x{559A}\x{559B}\x{559C}' + . '\x{559D}\x{559E}\x{559F}\x{55A0}\x{55A1}\x{55A2}\x{55A3}\x{55A4}\x{55A5}' + . '\x{55A6}\x{55A7}\x{55A8}\x{55A9}\x{55AA}\x{55AB}\x{55AC}\x{55AD}\x{55AE}' + . '\x{55AF}\x{55B0}\x{55B1}\x{55B2}\x{55B3}\x{55B4}\x{55B5}\x{55B6}\x{55B7}' + . '\x{55B8}\x{55B9}\x{55BA}\x{55BB}\x{55BC}\x{55BD}\x{55BE}\x{55BF}\x{55C0}' + . '\x{55C1}\x{55C2}\x{55C3}\x{55C4}\x{55C5}\x{55C6}\x{55C7}\x{55C8}\x{55C9}' + . '\x{55CA}\x{55CB}\x{55CC}\x{55CD}\x{55CE}\x{55CF}\x{55D0}\x{55D1}\x{55D2}' + . '\x{55D3}\x{55D4}\x{55D5}\x{55D6}\x{55D7}\x{55D8}\x{55D9}\x{55DA}\x{55DB}' + . '\x{55DC}\x{55DD}\x{55DE}\x{55DF}\x{55E1}\x{55E2}\x{55E3}\x{55E4}\x{55E5}' + . '\x{55E6}\x{55E7}\x{55E8}\x{55E9}\x{55EA}\x{55EB}\x{55EC}\x{55ED}\x{55EE}' + . '\x{55EF}\x{55F0}\x{55F1}\x{55F2}\x{55F3}\x{55F4}\x{55F5}\x{55F6}\x{55F7}' + . '\x{55F9}\x{55FA}\x{55FB}\x{55FC}\x{55FD}\x{55FE}\x{55FF}\x{5600}\x{5601}' + . '\x{5602}\x{5603}\x{5604}\x{5606}\x{5607}\x{5608}\x{5609}\x{560C}\x{560D}' + . '\x{560E}\x{560F}\x{5610}\x{5611}\x{5612}\x{5613}\x{5614}\x{5615}\x{5616}' + . '\x{5617}\x{5618}\x{5619}\x{561A}\x{561B}\x{561C}\x{561D}\x{561E}\x{561F}' + . '\x{5621}\x{5622}\x{5623}\x{5624}\x{5625}\x{5626}\x{5627}\x{5628}\x{5629}' + . '\x{562A}\x{562C}\x{562D}\x{562E}\x{562F}\x{5630}\x{5631}\x{5632}\x{5633}' + . '\x{5634}\x{5635}\x{5636}\x{5638}\x{5639}\x{563A}\x{563B}\x{563D}\x{563E}' + . '\x{563F}\x{5640}\x{5641}\x{5642}\x{5643}\x{5645}\x{5646}\x{5647}\x{5648}' + . '\x{5649}\x{564A}\x{564C}\x{564D}\x{564E}\x{564F}\x{5650}\x{5652}\x{5653}' + . '\x{5654}\x{5655}\x{5657}\x{5658}\x{5659}\x{565A}\x{565B}\x{565C}\x{565D}' + . '\x{565E}\x{5660}\x{5662}\x{5663}\x{5664}\x{5665}\x{5666}\x{5667}\x{5668}' + . '\x{5669}\x{566A}\x{566B}\x{566C}\x{566D}\x{566E}\x{566F}\x{5670}\x{5671}' + . '\x{5672}\x{5673}\x{5674}\x{5676}\x{5677}\x{5678}\x{5679}\x{567A}\x{567B}' + . '\x{567C}\x{567E}\x{567F}\x{5680}\x{5681}\x{5682}\x{5683}\x{5684}\x{5685}' + . '\x{5686}\x{5687}\x{568A}\x{568C}\x{568D}\x{568E}\x{568F}\x{5690}\x{5691}' + . '\x{5692}\x{5693}\x{5694}\x{5695}\x{5697}\x{5698}\x{5699}\x{569A}\x{569B}' + . '\x{569C}\x{569D}\x{569F}\x{56A0}\x{56A1}\x{56A3}\x{56A4}\x{56A5}\x{56A6}' + . '\x{56A7}\x{56A8}\x{56A9}\x{56AA}\x{56AB}\x{56AC}\x{56AD}\x{56AE}\x{56AF}' + . '\x{56B0}\x{56B1}\x{56B2}\x{56B3}\x{56B4}\x{56B5}\x{56B6}\x{56B7}\x{56B8}' + . '\x{56B9}\x{56BB}\x{56BC}\x{56BD}\x{56BE}\x{56BF}\x{56C0}\x{56C1}\x{56C2}' + . '\x{56C3}\x{56C4}\x{56C5}\x{56C6}\x{56C7}\x{56C8}\x{56C9}\x{56CA}\x{56CB}' + . '\x{56CC}\x{56CD}\x{56CE}\x{56D0}\x{56D1}\x{56D2}\x{56D3}\x{56D4}\x{56D5}' + . '\x{56D6}\x{56D7}\x{56D8}\x{56DA}\x{56DB}\x{56DC}\x{56DD}\x{56DE}\x{56DF}' + . '\x{56E0}\x{56E1}\x{56E2}\x{56E3}\x{56E4}\x{56E5}\x{56E7}\x{56E8}\x{56E9}' + . '\x{56EA}\x{56EB}\x{56EC}\x{56ED}\x{56EE}\x{56EF}\x{56F0}\x{56F1}\x{56F2}' + . '\x{56F3}\x{56F4}\x{56F5}\x{56F7}\x{56F9}\x{56FA}\x{56FD}\x{56FE}\x{56FF}' + . '\x{5700}\x{5701}\x{5702}\x{5703}\x{5704}\x{5706}\x{5707}\x{5708}\x{5709}' + . '\x{570A}\x{570B}\x{570C}\x{570D}\x{570E}\x{570F}\x{5710}\x{5712}\x{5713}' + . '\x{5714}\x{5715}\x{5716}\x{5718}\x{5719}\x{571A}\x{571B}\x{571C}\x{571D}' + . '\x{571E}\x{571F}\x{5720}\x{5722}\x{5723}\x{5725}\x{5726}\x{5727}\x{5728}' + . '\x{5729}\x{572A}\x{572B}\x{572C}\x{572D}\x{572E}\x{572F}\x{5730}\x{5731}' + . '\x{5732}\x{5733}\x{5734}\x{5735}\x{5736}\x{5737}\x{5738}\x{5739}\x{573A}' + . '\x{573B}\x{573C}\x{573E}\x{573F}\x{5740}\x{5741}\x{5742}\x{5744}\x{5745}' + . '\x{5746}\x{5747}\x{5749}\x{574A}\x{574B}\x{574C}\x{574D}\x{574E}\x{574F}' + . '\x{5750}\x{5751}\x{5752}\x{5753}\x{5754}\x{5757}\x{5759}\x{575A}\x{575B}' + . '\x{575C}\x{575D}\x{575E}\x{575F}\x{5760}\x{5761}\x{5762}\x{5764}\x{5765}' + . '\x{5766}\x{5767}\x{5768}\x{5769}\x{576A}\x{576B}\x{576C}\x{576D}\x{576F}' + . '\x{5770}\x{5771}\x{5772}\x{5773}\x{5774}\x{5775}\x{5776}\x{5777}\x{5779}' + . '\x{577A}\x{577B}\x{577C}\x{577D}\x{577E}\x{577F}\x{5780}\x{5782}\x{5783}' + . '\x{5784}\x{5785}\x{5786}\x{5788}\x{5789}\x{578A}\x{578B}\x{578C}\x{578D}' + . '\x{578E}\x{578F}\x{5790}\x{5791}\x{5792}\x{5793}\x{5794}\x{5795}\x{5797}' + . '\x{5798}\x{5799}\x{579A}\x{579B}\x{579C}\x{579D}\x{579E}\x{579F}\x{57A0}' + . '\x{57A1}\x{57A2}\x{57A3}\x{57A4}\x{57A5}\x{57A6}\x{57A7}\x{57A9}\x{57AA}' + . '\x{57AB}\x{57AC}\x{57AD}\x{57AE}\x{57AF}\x{57B0}\x{57B1}\x{57B2}\x{57B3}' + . '\x{57B4}\x{57B5}\x{57B6}\x{57B7}\x{57B8}\x{57B9}\x{57BA}\x{57BB}\x{57BC}' + . '\x{57BD}\x{57BE}\x{57BF}\x{57C0}\x{57C1}\x{57C2}\x{57C3}\x{57C4}\x{57C5}' + . '\x{57C6}\x{57C7}\x{57C8}\x{57C9}\x{57CB}\x{57CC}\x{57CD}\x{57CE}\x{57CF}' + . '\x{57D0}\x{57D2}\x{57D3}\x{57D4}\x{57D5}\x{57D6}\x{57D8}\x{57D9}\x{57DA}' + . '\x{57DC}\x{57DD}\x{57DF}\x{57E0}\x{57E1}\x{57E2}\x{57E3}\x{57E4}\x{57E5}' + . '\x{57E6}\x{57E7}\x{57E8}\x{57E9}\x{57EA}\x{57EB}\x{57EC}\x{57ED}\x{57EE}' + . '\x{57EF}\x{57F0}\x{57F1}\x{57F2}\x{57F3}\x{57F4}\x{57F5}\x{57F6}\x{57F7}' + . '\x{57F8}\x{57F9}\x{57FA}\x{57FB}\x{57FC}\x{57FD}\x{57FE}\x{57FF}\x{5800}' + . '\x{5801}\x{5802}\x{5803}\x{5804}\x{5805}\x{5806}\x{5807}\x{5808}\x{5809}' + . '\x{580A}\x{580B}\x{580C}\x{580D}\x{580E}\x{580F}\x{5810}\x{5811}\x{5812}' + . '\x{5813}\x{5814}\x{5815}\x{5816}\x{5819}\x{581A}\x{581B}\x{581C}\x{581D}' + . '\x{581E}\x{581F}\x{5820}\x{5821}\x{5822}\x{5823}\x{5824}\x{5825}\x{5826}' + . '\x{5827}\x{5828}\x{5829}\x{582A}\x{582B}\x{582C}\x{582D}\x{582E}\x{582F}' + . '\x{5830}\x{5831}\x{5832}\x{5833}\x{5834}\x{5835}\x{5836}\x{5837}\x{5838}' + . '\x{5839}\x{583A}\x{583B}\x{583C}\x{583D}\x{583E}\x{583F}\x{5840}\x{5842}' + . '\x{5843}\x{5844}\x{5845}\x{5846}\x{5847}\x{5848}\x{5849}\x{584A}\x{584B}' + . '\x{584C}\x{584D}\x{584E}\x{584F}\x{5851}\x{5852}\x{5853}\x{5854}\x{5855}' + . '\x{5857}\x{5858}\x{5859}\x{585A}\x{585B}\x{585C}\x{585D}\x{585E}\x{585F}' + . '\x{5861}\x{5862}\x{5863}\x{5864}\x{5865}\x{5868}\x{5869}\x{586A}\x{586B}' + . '\x{586C}\x{586D}\x{586E}\x{586F}\x{5870}\x{5871}\x{5872}\x{5873}\x{5874}' + . '\x{5875}\x{5876}\x{5878}\x{5879}\x{587A}\x{587B}\x{587C}\x{587D}\x{587E}' + . '\x{587F}\x{5880}\x{5881}\x{5882}\x{5883}\x{5884}\x{5885}\x{5886}\x{5887}' + . '\x{5888}\x{5889}\x{588A}\x{588B}\x{588C}\x{588D}\x{588E}\x{588F}\x{5890}' + . '\x{5891}\x{5892}\x{5893}\x{5894}\x{5896}\x{5897}\x{5898}\x{5899}\x{589A}' + . '\x{589B}\x{589C}\x{589D}\x{589E}\x{589F}\x{58A0}\x{58A1}\x{58A2}\x{58A3}' + . '\x{58A4}\x{58A5}\x{58A6}\x{58A7}\x{58A8}\x{58A9}\x{58AB}\x{58AC}\x{58AD}' + . '\x{58AE}\x{58AF}\x{58B0}\x{58B1}\x{58B2}\x{58B3}\x{58B4}\x{58B7}\x{58B8}' + . '\x{58B9}\x{58BA}\x{58BB}\x{58BC}\x{58BD}\x{58BE}\x{58BF}\x{58C1}\x{58C2}' + . '\x{58C5}\x{58C6}\x{58C7}\x{58C8}\x{58C9}\x{58CA}\x{58CB}\x{58CE}\x{58CF}' + . '\x{58D1}\x{58D2}\x{58D3}\x{58D4}\x{58D5}\x{58D6}\x{58D7}\x{58D8}\x{58D9}' + . '\x{58DA}\x{58DB}\x{58DD}\x{58DE}\x{58DF}\x{58E0}\x{58E2}\x{58E3}\x{58E4}' + . '\x{58E5}\x{58E7}\x{58E8}\x{58E9}\x{58EA}\x{58EB}\x{58EC}\x{58ED}\x{58EE}' + . '\x{58EF}\x{58F0}\x{58F1}\x{58F2}\x{58F3}\x{58F4}\x{58F6}\x{58F7}\x{58F8}' + . '\x{58F9}\x{58FA}\x{58FB}\x{58FC}\x{58FD}\x{58FE}\x{58FF}\x{5900}\x{5902}' + . '\x{5903}\x{5904}\x{5906}\x{5907}\x{5909}\x{590A}\x{590B}\x{590C}\x{590D}' + . '\x{590E}\x{590F}\x{5910}\x{5912}\x{5914}\x{5915}\x{5916}\x{5917}\x{5918}' + . '\x{5919}\x{591A}\x{591B}\x{591C}\x{591D}\x{591E}\x{591F}\x{5920}\x{5921}' + . '\x{5922}\x{5924}\x{5925}\x{5926}\x{5927}\x{5928}\x{5929}\x{592A}\x{592B}' + . '\x{592C}\x{592D}\x{592E}\x{592F}\x{5930}\x{5931}\x{5932}\x{5934}\x{5935}' + . '\x{5937}\x{5938}\x{5939}\x{593A}\x{593B}\x{593C}\x{593D}\x{593E}\x{593F}' + . '\x{5940}\x{5941}\x{5942}\x{5943}\x{5944}\x{5945}\x{5946}\x{5947}\x{5948}' + . '\x{5949}\x{594A}\x{594B}\x{594C}\x{594D}\x{594E}\x{594F}\x{5950}\x{5951}' + . '\x{5952}\x{5953}\x{5954}\x{5955}\x{5956}\x{5957}\x{5958}\x{595A}\x{595C}' + . '\x{595D}\x{595E}\x{595F}\x{5960}\x{5961}\x{5962}\x{5963}\x{5964}\x{5965}' + . '\x{5966}\x{5967}\x{5968}\x{5969}\x{596A}\x{596B}\x{596C}\x{596D}\x{596E}' + . '\x{596F}\x{5970}\x{5971}\x{5972}\x{5973}\x{5974}\x{5975}\x{5976}\x{5977}' + . '\x{5978}\x{5979}\x{597A}\x{597B}\x{597C}\x{597D}\x{597E}\x{597F}\x{5980}' + . '\x{5981}\x{5982}\x{5983}\x{5984}\x{5985}\x{5986}\x{5987}\x{5988}\x{5989}' + . '\x{598A}\x{598B}\x{598C}\x{598D}\x{598E}\x{598F}\x{5990}\x{5991}\x{5992}' + . '\x{5993}\x{5994}\x{5995}\x{5996}\x{5997}\x{5998}\x{5999}\x{599A}\x{599C}' + . '\x{599D}\x{599E}\x{599F}\x{59A0}\x{59A1}\x{59A2}\x{59A3}\x{59A4}\x{59A5}' + . '\x{59A6}\x{59A7}\x{59A8}\x{59A9}\x{59AA}\x{59AB}\x{59AC}\x{59AD}\x{59AE}' + . '\x{59AF}\x{59B0}\x{59B1}\x{59B2}\x{59B3}\x{59B4}\x{59B5}\x{59B6}\x{59B8}' + . '\x{59B9}\x{59BA}\x{59BB}\x{59BC}\x{59BD}\x{59BE}\x{59BF}\x{59C0}\x{59C1}' + . '\x{59C2}\x{59C3}\x{59C4}\x{59C5}\x{59C6}\x{59C7}\x{59C8}\x{59C9}\x{59CA}' + . '\x{59CB}\x{59CC}\x{59CD}\x{59CE}\x{59CF}\x{59D0}\x{59D1}\x{59D2}\x{59D3}' + . '\x{59D4}\x{59D5}\x{59D6}\x{59D7}\x{59D8}\x{59D9}\x{59DA}\x{59DB}\x{59DC}' + . '\x{59DD}\x{59DE}\x{59DF}\x{59E0}\x{59E1}\x{59E2}\x{59E3}\x{59E4}\x{59E5}' + . '\x{59E6}\x{59E8}\x{59E9}\x{59EA}\x{59EB}\x{59EC}\x{59ED}\x{59EE}\x{59EF}' + . '\x{59F0}\x{59F1}\x{59F2}\x{59F3}\x{59F4}\x{59F5}\x{59F6}\x{59F7}\x{59F8}' + . '\x{59F9}\x{59FA}\x{59FB}\x{59FC}\x{59FD}\x{59FE}\x{59FF}\x{5A00}\x{5A01}' + . '\x{5A02}\x{5A03}\x{5A04}\x{5A05}\x{5A06}\x{5A07}\x{5A08}\x{5A09}\x{5A0A}' + . '\x{5A0B}\x{5A0C}\x{5A0D}\x{5A0E}\x{5A0F}\x{5A10}\x{5A11}\x{5A12}\x{5A13}' + . '\x{5A14}\x{5A15}\x{5A16}\x{5A17}\x{5A18}\x{5A19}\x{5A1A}\x{5A1B}\x{5A1C}' + . '\x{5A1D}\x{5A1E}\x{5A1F}\x{5A20}\x{5A21}\x{5A22}\x{5A23}\x{5A25}\x{5A27}' + . '\x{5A28}\x{5A29}\x{5A2A}\x{5A2B}\x{5A2D}\x{5A2E}\x{5A2F}\x{5A31}\x{5A32}' + . '\x{5A33}\x{5A34}\x{5A35}\x{5A36}\x{5A37}\x{5A38}\x{5A39}\x{5A3A}\x{5A3B}' + . '\x{5A3C}\x{5A3D}\x{5A3E}\x{5A3F}\x{5A40}\x{5A41}\x{5A42}\x{5A43}\x{5A44}' + . '\x{5A45}\x{5A46}\x{5A47}\x{5A48}\x{5A49}\x{5A4A}\x{5A4B}\x{5A4C}\x{5A4D}' + . '\x{5A4E}\x{5A4F}\x{5A50}\x{5A51}\x{5A52}\x{5A53}\x{5A55}\x{5A56}\x{5A57}' + . '\x{5A58}\x{5A5A}\x{5A5B}\x{5A5C}\x{5A5D}\x{5A5E}\x{5A5F}\x{5A60}\x{5A61}' + . '\x{5A62}\x{5A63}\x{5A64}\x{5A65}\x{5A66}\x{5A67}\x{5A68}\x{5A69}\x{5A6A}' + . '\x{5A6B}\x{5A6C}\x{5A6D}\x{5A6E}\x{5A70}\x{5A72}\x{5A73}\x{5A74}\x{5A75}' + . '\x{5A76}\x{5A77}\x{5A78}\x{5A79}\x{5A7A}\x{5A7B}\x{5A7C}\x{5A7D}\x{5A7E}' + . '\x{5A7F}\x{5A80}\x{5A81}\x{5A82}\x{5A83}\x{5A84}\x{5A85}\x{5A86}\x{5A88}' + . '\x{5A89}\x{5A8A}\x{5A8B}\x{5A8C}\x{5A8E}\x{5A8F}\x{5A90}\x{5A91}\x{5A92}' + . '\x{5A93}\x{5A94}\x{5A95}\x{5A96}\x{5A97}\x{5A98}\x{5A99}\x{5A9A}\x{5A9B}' + . '\x{5A9C}\x{5A9D}\x{5A9E}\x{5A9F}\x{5AA0}\x{5AA1}\x{5AA2}\x{5AA3}\x{5AA4}' + . '\x{5AA5}\x{5AA6}\x{5AA7}\x{5AA8}\x{5AA9}\x{5AAA}\x{5AAC}\x{5AAD}\x{5AAE}' + . '\x{5AAF}\x{5AB0}\x{5AB1}\x{5AB2}\x{5AB3}\x{5AB4}\x{5AB5}\x{5AB6}\x{5AB7}' + . '\x{5AB8}\x{5AB9}\x{5ABA}\x{5ABB}\x{5ABC}\x{5ABD}\x{5ABE}\x{5ABF}\x{5AC0}' + . '\x{5AC1}\x{5AC2}\x{5AC3}\x{5AC4}\x{5AC5}\x{5AC6}\x{5AC7}\x{5AC8}\x{5AC9}' + . '\x{5ACA}\x{5ACB}\x{5ACC}\x{5ACD}\x{5ACE}\x{5ACF}\x{5AD1}\x{5AD2}\x{5AD4}' + . '\x{5AD5}\x{5AD6}\x{5AD7}\x{5AD8}\x{5AD9}\x{5ADA}\x{5ADB}\x{5ADC}\x{5ADD}' + . '\x{5ADE}\x{5ADF}\x{5AE0}\x{5AE1}\x{5AE2}\x{5AE3}\x{5AE4}\x{5AE5}\x{5AE6}' + . '\x{5AE7}\x{5AE8}\x{5AE9}\x{5AEA}\x{5AEB}\x{5AEC}\x{5AED}\x{5AEE}\x{5AF1}' + . '\x{5AF2}\x{5AF3}\x{5AF4}\x{5AF5}\x{5AF6}\x{5AF7}\x{5AF8}\x{5AF9}\x{5AFA}' + . '\x{5AFB}\x{5AFC}\x{5AFD}\x{5AFE}\x{5AFF}\x{5B00}\x{5B01}\x{5B02}\x{5B03}' + . '\x{5B04}\x{5B05}\x{5B06}\x{5B07}\x{5B08}\x{5B09}\x{5B0B}\x{5B0C}\x{5B0E}' + . '\x{5B0F}\x{5B10}\x{5B11}\x{5B12}\x{5B13}\x{5B14}\x{5B15}\x{5B16}\x{5B17}' + . '\x{5B18}\x{5B19}\x{5B1A}\x{5B1B}\x{5B1C}\x{5B1D}\x{5B1E}\x{5B1F}\x{5B20}' + . '\x{5B21}\x{5B22}\x{5B23}\x{5B24}\x{5B25}\x{5B26}\x{5B27}\x{5B28}\x{5B29}' + . '\x{5B2A}\x{5B2B}\x{5B2C}\x{5B2D}\x{5B2E}\x{5B2F}\x{5B30}\x{5B31}\x{5B32}' + . '\x{5B33}\x{5B34}\x{5B35}\x{5B36}\x{5B37}\x{5B38}\x{5B3A}\x{5B3B}\x{5B3C}' + . '\x{5B3D}\x{5B3E}\x{5B3F}\x{5B40}\x{5B41}\x{5B42}\x{5B43}\x{5B44}\x{5B45}' + . '\x{5B47}\x{5B48}\x{5B49}\x{5B4A}\x{5B4B}\x{5B4C}\x{5B4D}\x{5B4E}\x{5B50}' + . '\x{5B51}\x{5B53}\x{5B54}\x{5B55}\x{5B56}\x{5B57}\x{5B58}\x{5B59}\x{5B5A}' + . '\x{5B5B}\x{5B5C}\x{5B5D}\x{5B5E}\x{5B5F}\x{5B62}\x{5B63}\x{5B64}\x{5B65}' + . '\x{5B66}\x{5B67}\x{5B68}\x{5B69}\x{5B6A}\x{5B6B}\x{5B6C}\x{5B6D}\x{5B6E}' + . '\x{5B70}\x{5B71}\x{5B72}\x{5B73}\x{5B74}\x{5B75}\x{5B76}\x{5B77}\x{5B78}' + . '\x{5B7A}\x{5B7B}\x{5B7C}\x{5B7D}\x{5B7F}\x{5B80}\x{5B81}\x{5B82}\x{5B83}' + . '\x{5B84}\x{5B85}\x{5B87}\x{5B88}\x{5B89}\x{5B8A}\x{5B8B}\x{5B8C}\x{5B8D}' + . '\x{5B8E}\x{5B8F}\x{5B91}\x{5B92}\x{5B93}\x{5B94}\x{5B95}\x{5B96}\x{5B97}' + . '\x{5B98}\x{5B99}\x{5B9A}\x{5B9B}\x{5B9C}\x{5B9D}\x{5B9E}\x{5B9F}\x{5BA0}' + . '\x{5BA1}\x{5BA2}\x{5BA3}\x{5BA4}\x{5BA5}\x{5BA6}\x{5BA7}\x{5BA8}\x{5BAA}' + . '\x{5BAB}\x{5BAC}\x{5BAD}\x{5BAE}\x{5BAF}\x{5BB0}\x{5BB1}\x{5BB3}\x{5BB4}' + . '\x{5BB5}\x{5BB6}\x{5BB8}\x{5BB9}\x{5BBA}\x{5BBB}\x{5BBD}\x{5BBE}\x{5BBF}' + . '\x{5BC0}\x{5BC1}\x{5BC2}\x{5BC3}\x{5BC4}\x{5BC5}\x{5BC6}\x{5BC7}\x{5BCA}' + . '\x{5BCB}\x{5BCC}\x{5BCD}\x{5BCE}\x{5BCF}\x{5BD0}\x{5BD1}\x{5BD2}\x{5BD3}' + . '\x{5BD4}\x{5BD5}\x{5BD6}\x{5BD8}\x{5BD9}\x{5BDB}\x{5BDC}\x{5BDD}\x{5BDE}' + . '\x{5BDF}\x{5BE0}\x{5BE1}\x{5BE2}\x{5BE3}\x{5BE4}\x{5BE5}\x{5BE6}\x{5BE7}' + . '\x{5BE8}\x{5BE9}\x{5BEA}\x{5BEB}\x{5BEC}\x{5BED}\x{5BEE}\x{5BEF}\x{5BF0}' + . '\x{5BF1}\x{5BF2}\x{5BF3}\x{5BF4}\x{5BF5}\x{5BF6}\x{5BF7}\x{5BF8}\x{5BF9}' + . '\x{5BFA}\x{5BFB}\x{5BFC}\x{5BFD}\x{5BFF}\x{5C01}\x{5C03}\x{5C04}\x{5C05}' + . '\x{5C06}\x{5C07}\x{5C08}\x{5C09}\x{5C0A}\x{5C0B}\x{5C0C}\x{5C0D}\x{5C0E}' + . '\x{5C0F}\x{5C10}\x{5C11}\x{5C12}\x{5C13}\x{5C14}\x{5C15}\x{5C16}\x{5C17}' + . '\x{5C18}\x{5C19}\x{5C1A}\x{5C1C}\x{5C1D}\x{5C1E}\x{5C1F}\x{5C20}\x{5C21}' + . '\x{5C22}\x{5C24}\x{5C25}\x{5C27}\x{5C28}\x{5C2A}\x{5C2B}\x{5C2C}\x{5C2D}' + . '\x{5C2E}\x{5C2F}\x{5C30}\x{5C31}\x{5C32}\x{5C33}\x{5C34}\x{5C35}\x{5C37}' + . '\x{5C38}\x{5C39}\x{5C3A}\x{5C3B}\x{5C3C}\x{5C3D}\x{5C3E}\x{5C3F}\x{5C40}' + . '\x{5C41}\x{5C42}\x{5C43}\x{5C44}\x{5C45}\x{5C46}\x{5C47}\x{5C48}\x{5C49}' + . '\x{5C4A}\x{5C4B}\x{5C4C}\x{5C4D}\x{5C4E}\x{5C4F}\x{5C50}\x{5C51}\x{5C52}' + . '\x{5C53}\x{5C54}\x{5C55}\x{5C56}\x{5C57}\x{5C58}\x{5C59}\x{5C5B}\x{5C5C}' + . '\x{5C5D}\x{5C5E}\x{5C5F}\x{5C60}\x{5C61}\x{5C62}\x{5C63}\x{5C64}\x{5C65}' + . '\x{5C66}\x{5C67}\x{5C68}\x{5C69}\x{5C6A}\x{5C6B}\x{5C6C}\x{5C6D}\x{5C6E}' + . '\x{5C6F}\x{5C70}\x{5C71}\x{5C72}\x{5C73}\x{5C74}\x{5C75}\x{5C76}\x{5C77}' + . '\x{5C78}\x{5C79}\x{5C7A}\x{5C7B}\x{5C7C}\x{5C7D}\x{5C7E}\x{5C7F}\x{5C80}' + . '\x{5C81}\x{5C82}\x{5C83}\x{5C84}\x{5C86}\x{5C87}\x{5C88}\x{5C89}\x{5C8A}' + . '\x{5C8B}\x{5C8C}\x{5C8D}\x{5C8E}\x{5C8F}\x{5C90}\x{5C91}\x{5C92}\x{5C93}' + . '\x{5C94}\x{5C95}\x{5C96}\x{5C97}\x{5C98}\x{5C99}\x{5C9A}\x{5C9B}\x{5C9C}' + . '\x{5C9D}\x{5C9E}\x{5C9F}\x{5CA0}\x{5CA1}\x{5CA2}\x{5CA3}\x{5CA4}\x{5CA5}' + . '\x{5CA6}\x{5CA7}\x{5CA8}\x{5CA9}\x{5CAA}\x{5CAB}\x{5CAC}\x{5CAD}\x{5CAE}' + . '\x{5CAF}\x{5CB0}\x{5CB1}\x{5CB2}\x{5CB3}\x{5CB5}\x{5CB6}\x{5CB7}\x{5CB8}' + . '\x{5CBA}\x{5CBB}\x{5CBC}\x{5CBD}\x{5CBE}\x{5CBF}\x{5CC1}\x{5CC2}\x{5CC3}' + . '\x{5CC4}\x{5CC5}\x{5CC6}\x{5CC7}\x{5CC8}\x{5CC9}\x{5CCA}\x{5CCB}\x{5CCC}' + . '\x{5CCD}\x{5CCE}\x{5CCF}\x{5CD0}\x{5CD1}\x{5CD2}\x{5CD3}\x{5CD4}\x{5CD6}' + . '\x{5CD7}\x{5CD8}\x{5CD9}\x{5CDA}\x{5CDB}\x{5CDC}\x{5CDE}\x{5CDF}\x{5CE0}' + . '\x{5CE1}\x{5CE2}\x{5CE3}\x{5CE4}\x{5CE5}\x{5CE6}\x{5CE7}\x{5CE8}\x{5CE9}' + . '\x{5CEA}\x{5CEB}\x{5CEC}\x{5CED}\x{5CEE}\x{5CEF}\x{5CF0}\x{5CF1}\x{5CF2}' + . '\x{5CF3}\x{5CF4}\x{5CF6}\x{5CF7}\x{5CF8}\x{5CF9}\x{5CFA}\x{5CFB}\x{5CFC}' + . '\x{5CFD}\x{5CFE}\x{5CFF}\x{5D00}\x{5D01}\x{5D02}\x{5D03}\x{5D04}\x{5D05}' + . '\x{5D06}\x{5D07}\x{5D08}\x{5D09}\x{5D0A}\x{5D0B}\x{5D0C}\x{5D0D}\x{5D0E}' + . '\x{5D0F}\x{5D10}\x{5D11}\x{5D12}\x{5D13}\x{5D14}\x{5D15}\x{5D16}\x{5D17}' + . '\x{5D18}\x{5D19}\x{5D1A}\x{5D1B}\x{5D1C}\x{5D1D}\x{5D1E}\x{5D1F}\x{5D20}' + . '\x{5D21}\x{5D22}\x{5D23}\x{5D24}\x{5D25}\x{5D26}\x{5D27}\x{5D28}\x{5D29}' + . '\x{5D2A}\x{5D2C}\x{5D2D}\x{5D2E}\x{5D30}\x{5D31}\x{5D32}\x{5D33}\x{5D34}' + . '\x{5D35}\x{5D36}\x{5D37}\x{5D38}\x{5D39}\x{5D3A}\x{5D3C}\x{5D3D}\x{5D3E}' + . '\x{5D3F}\x{5D40}\x{5D41}\x{5D42}\x{5D43}\x{5D44}\x{5D45}\x{5D46}\x{5D47}' + . '\x{5D48}\x{5D49}\x{5D4A}\x{5D4B}\x{5D4C}\x{5D4D}\x{5D4E}\x{5D4F}\x{5D50}' + . '\x{5D51}\x{5D52}\x{5D54}\x{5D55}\x{5D56}\x{5D58}\x{5D59}\x{5D5A}\x{5D5B}' + . '\x{5D5D}\x{5D5E}\x{5D5F}\x{5D61}\x{5D62}\x{5D63}\x{5D64}\x{5D65}\x{5D66}' + . '\x{5D67}\x{5D68}\x{5D69}\x{5D6A}\x{5D6B}\x{5D6C}\x{5D6D}\x{5D6E}\x{5D6F}' + . '\x{5D70}\x{5D71}\x{5D72}\x{5D73}\x{5D74}\x{5D75}\x{5D76}\x{5D77}\x{5D78}' + . '\x{5D79}\x{5D7A}\x{5D7B}\x{5D7C}\x{5D7D}\x{5D7E}\x{5D7F}\x{5D80}\x{5D81}' + . '\x{5D82}\x{5D84}\x{5D85}\x{5D86}\x{5D87}\x{5D88}\x{5D89}\x{5D8A}\x{5D8B}' + . '\x{5D8C}\x{5D8D}\x{5D8E}\x{5D8F}\x{5D90}\x{5D91}\x{5D92}\x{5D93}\x{5D94}' + . '\x{5D95}\x{5D97}\x{5D98}\x{5D99}\x{5D9A}\x{5D9B}\x{5D9C}\x{5D9D}\x{5D9E}' + . '\x{5D9F}\x{5DA0}\x{5DA1}\x{5DA2}\x{5DA5}\x{5DA6}\x{5DA7}\x{5DA8}\x{5DA9}' + . '\x{5DAA}\x{5DAC}\x{5DAD}\x{5DAE}\x{5DAF}\x{5DB0}\x{5DB1}\x{5DB2}\x{5DB4}' + . '\x{5DB5}\x{5DB6}\x{5DB7}\x{5DB8}\x{5DBA}\x{5DBB}\x{5DBC}\x{5DBD}\x{5DBE}' + . '\x{5DBF}\x{5DC0}\x{5DC1}\x{5DC2}\x{5DC3}\x{5DC5}\x{5DC6}\x{5DC7}\x{5DC8}' + . '\x{5DC9}\x{5DCA}\x{5DCB}\x{5DCC}\x{5DCD}\x{5DCE}\x{5DCF}\x{5DD0}\x{5DD1}' + . '\x{5DD2}\x{5DD3}\x{5DD4}\x{5DD5}\x{5DD6}\x{5DD8}\x{5DD9}\x{5DDB}\x{5DDD}' + . '\x{5DDE}\x{5DDF}\x{5DE0}\x{5DE1}\x{5DE2}\x{5DE3}\x{5DE4}\x{5DE5}\x{5DE6}' + . '\x{5DE7}\x{5DE8}\x{5DE9}\x{5DEA}\x{5DEB}\x{5DEC}\x{5DED}\x{5DEE}\x{5DEF}' + . '\x{5DF0}\x{5DF1}\x{5DF2}\x{5DF3}\x{5DF4}\x{5DF5}\x{5DF7}\x{5DF8}\x{5DF9}' + . '\x{5DFA}\x{5DFB}\x{5DFC}\x{5DFD}\x{5DFE}\x{5DFF}\x{5E00}\x{5E01}\x{5E02}' + . '\x{5E03}\x{5E04}\x{5E05}\x{5E06}\x{5E07}\x{5E08}\x{5E09}\x{5E0A}\x{5E0B}' + . '\x{5E0C}\x{5E0D}\x{5E0E}\x{5E0F}\x{5E10}\x{5E11}\x{5E13}\x{5E14}\x{5E15}' + . '\x{5E16}\x{5E17}\x{5E18}\x{5E19}\x{5E1A}\x{5E1B}\x{5E1C}\x{5E1D}\x{5E1E}' + . '\x{5E1F}\x{5E20}\x{5E21}\x{5E22}\x{5E23}\x{5E24}\x{5E25}\x{5E26}\x{5E27}' + . '\x{5E28}\x{5E29}\x{5E2A}\x{5E2B}\x{5E2C}\x{5E2D}\x{5E2E}\x{5E2F}\x{5E30}' + . '\x{5E31}\x{5E32}\x{5E33}\x{5E34}\x{5E35}\x{5E36}\x{5E37}\x{5E38}\x{5E39}' + . '\x{5E3A}\x{5E3B}\x{5E3C}\x{5E3D}\x{5E3E}\x{5E40}\x{5E41}\x{5E42}\x{5E43}' + . '\x{5E44}\x{5E45}\x{5E46}\x{5E47}\x{5E49}\x{5E4A}\x{5E4B}\x{5E4C}\x{5E4D}' + . '\x{5E4E}\x{5E4F}\x{5E50}\x{5E52}\x{5E53}\x{5E54}\x{5E55}\x{5E56}\x{5E57}' + . '\x{5E58}\x{5E59}\x{5E5A}\x{5E5B}\x{5E5C}\x{5E5D}\x{5E5E}\x{5E5F}\x{5E60}' + . '\x{5E61}\x{5E62}\x{5E63}\x{5E64}\x{5E65}\x{5E66}\x{5E67}\x{5E68}\x{5E69}' + . '\x{5E6A}\x{5E6B}\x{5E6C}\x{5E6D}\x{5E6E}\x{5E6F}\x{5E70}\x{5E71}\x{5E72}' + . '\x{5E73}\x{5E74}\x{5E75}\x{5E76}\x{5E77}\x{5E78}\x{5E79}\x{5E7A}\x{5E7B}' + . '\x{5E7C}\x{5E7D}\x{5E7E}\x{5E7F}\x{5E80}\x{5E81}\x{5E82}\x{5E83}\x{5E84}' + . '\x{5E85}\x{5E86}\x{5E87}\x{5E88}\x{5E89}\x{5E8A}\x{5E8B}\x{5E8C}\x{5E8D}' + . '\x{5E8E}\x{5E8F}\x{5E90}\x{5E91}\x{5E93}\x{5E94}\x{5E95}\x{5E96}\x{5E97}' + . '\x{5E98}\x{5E99}\x{5E9A}\x{5E9B}\x{5E9C}\x{5E9D}\x{5E9E}\x{5E9F}\x{5EA0}' + . '\x{5EA1}\x{5EA2}\x{5EA3}\x{5EA4}\x{5EA5}\x{5EA6}\x{5EA7}\x{5EA8}\x{5EA9}' + . '\x{5EAA}\x{5EAB}\x{5EAC}\x{5EAD}\x{5EAE}\x{5EAF}\x{5EB0}\x{5EB1}\x{5EB2}' + . '\x{5EB3}\x{5EB4}\x{5EB5}\x{5EB6}\x{5EB7}\x{5EB8}\x{5EB9}\x{5EBB}\x{5EBC}' + . '\x{5EBD}\x{5EBE}\x{5EBF}\x{5EC1}\x{5EC2}\x{5EC3}\x{5EC4}\x{5EC5}\x{5EC6}' + . '\x{5EC7}\x{5EC8}\x{5EC9}\x{5ECA}\x{5ECB}\x{5ECC}\x{5ECD}\x{5ECE}\x{5ECF}' + . '\x{5ED0}\x{5ED1}\x{5ED2}\x{5ED3}\x{5ED4}\x{5ED5}\x{5ED6}\x{5ED7}\x{5ED8}' + . '\x{5ED9}\x{5EDA}\x{5EDB}\x{5EDC}\x{5EDD}\x{5EDE}\x{5EDF}\x{5EE0}\x{5EE1}' + . '\x{5EE2}\x{5EE3}\x{5EE4}\x{5EE5}\x{5EE6}\x{5EE7}\x{5EE8}\x{5EE9}\x{5EEA}' + . '\x{5EEC}\x{5EED}\x{5EEE}\x{5EEF}\x{5EF0}\x{5EF1}\x{5EF2}\x{5EF3}\x{5EF4}' + . '\x{5EF5}\x{5EF6}\x{5EF7}\x{5EF8}\x{5EFA}\x{5EFB}\x{5EFC}\x{5EFD}\x{5EFE}' + . '\x{5EFF}\x{5F00}\x{5F01}\x{5F02}\x{5F03}\x{5F04}\x{5F05}\x{5F06}\x{5F07}' + . '\x{5F08}\x{5F0A}\x{5F0B}\x{5F0C}\x{5F0D}\x{5F0F}\x{5F11}\x{5F12}\x{5F13}' + . '\x{5F14}\x{5F15}\x{5F16}\x{5F17}\x{5F18}\x{5F19}\x{5F1A}\x{5F1B}\x{5F1C}' + . '\x{5F1D}\x{5F1E}\x{5F1F}\x{5F20}\x{5F21}\x{5F22}\x{5F23}\x{5F24}\x{5F25}' + . '\x{5F26}\x{5F27}\x{5F28}\x{5F29}\x{5F2A}\x{5F2B}\x{5F2C}\x{5F2D}\x{5F2E}' + . '\x{5F2F}\x{5F30}\x{5F31}\x{5F32}\x{5F33}\x{5F34}\x{5F35}\x{5F36}\x{5F37}' + . '\x{5F38}\x{5F39}\x{5F3A}\x{5F3C}\x{5F3E}\x{5F3F}\x{5F40}\x{5F41}\x{5F42}' + . '\x{5F43}\x{5F44}\x{5F45}\x{5F46}\x{5F47}\x{5F48}\x{5F49}\x{5F4A}\x{5F4B}' + . '\x{5F4C}\x{5F4D}\x{5F4E}\x{5F4F}\x{5F50}\x{5F51}\x{5F52}\x{5F53}\x{5F54}' + . '\x{5F55}\x{5F56}\x{5F57}\x{5F58}\x{5F59}\x{5F5A}\x{5F5B}\x{5F5C}\x{5F5D}' + . '\x{5F5E}\x{5F5F}\x{5F60}\x{5F61}\x{5F62}\x{5F63}\x{5F64}\x{5F65}\x{5F66}' + . '\x{5F67}\x{5F68}\x{5F69}\x{5F6A}\x{5F6B}\x{5F6C}\x{5F6D}\x{5F6E}\x{5F6F}' + . '\x{5F70}\x{5F71}\x{5F72}\x{5F73}\x{5F74}\x{5F75}\x{5F76}\x{5F77}\x{5F78}' + . '\x{5F79}\x{5F7A}\x{5F7B}\x{5F7C}\x{5F7D}\x{5F7E}\x{5F7F}\x{5F80}\x{5F81}' + . '\x{5F82}\x{5F83}\x{5F84}\x{5F85}\x{5F86}\x{5F87}\x{5F88}\x{5F89}\x{5F8A}' + . '\x{5F8B}\x{5F8C}\x{5F8D}\x{5F8E}\x{5F90}\x{5F91}\x{5F92}\x{5F93}\x{5F94}' + . '\x{5F95}\x{5F96}\x{5F97}\x{5F98}\x{5F99}\x{5F9B}\x{5F9C}\x{5F9D}\x{5F9E}' + . '\x{5F9F}\x{5FA0}\x{5FA1}\x{5FA2}\x{5FA5}\x{5FA6}\x{5FA7}\x{5FA8}\x{5FA9}' + . '\x{5FAA}\x{5FAB}\x{5FAC}\x{5FAD}\x{5FAE}\x{5FAF}\x{5FB1}\x{5FB2}\x{5FB3}' + . '\x{5FB4}\x{5FB5}\x{5FB6}\x{5FB7}\x{5FB8}\x{5FB9}\x{5FBA}\x{5FBB}\x{5FBC}' + . '\x{5FBD}\x{5FBE}\x{5FBF}\x{5FC0}\x{5FC1}\x{5FC3}\x{5FC4}\x{5FC5}\x{5FC6}' + . '\x{5FC7}\x{5FC8}\x{5FC9}\x{5FCA}\x{5FCB}\x{5FCC}\x{5FCD}\x{5FCF}\x{5FD0}' + . '\x{5FD1}\x{5FD2}\x{5FD3}\x{5FD4}\x{5FD5}\x{5FD6}\x{5FD7}\x{5FD8}\x{5FD9}' + . '\x{5FDA}\x{5FDC}\x{5FDD}\x{5FDE}\x{5FE0}\x{5FE1}\x{5FE3}\x{5FE4}\x{5FE5}' + . '\x{5FE6}\x{5FE7}\x{5FE8}\x{5FE9}\x{5FEA}\x{5FEB}\x{5FED}\x{5FEE}\x{5FEF}' + . '\x{5FF0}\x{5FF1}\x{5FF2}\x{5FF3}\x{5FF4}\x{5FF5}\x{5FF6}\x{5FF7}\x{5FF8}' + . '\x{5FF9}\x{5FFA}\x{5FFB}\x{5FFD}\x{5FFE}\x{5FFF}\x{6000}\x{6001}\x{6002}' + . '\x{6003}\x{6004}\x{6005}\x{6006}\x{6007}\x{6008}\x{6009}\x{600A}\x{600B}' + . '\x{600C}\x{600D}\x{600E}\x{600F}\x{6010}\x{6011}\x{6012}\x{6013}\x{6014}' + . '\x{6015}\x{6016}\x{6017}\x{6018}\x{6019}\x{601A}\x{601B}\x{601C}\x{601D}' + . '\x{601E}\x{601F}\x{6020}\x{6021}\x{6022}\x{6024}\x{6025}\x{6026}\x{6027}' + . '\x{6028}\x{6029}\x{602A}\x{602B}\x{602C}\x{602D}\x{602E}\x{602F}\x{6030}' + . '\x{6031}\x{6032}\x{6033}\x{6034}\x{6035}\x{6036}\x{6037}\x{6038}\x{6039}' + . '\x{603A}\x{603B}\x{603C}\x{603D}\x{603E}\x{603F}\x{6040}\x{6041}\x{6042}' + . '\x{6043}\x{6044}\x{6045}\x{6046}\x{6047}\x{6048}\x{6049}\x{604A}\x{604B}' + . '\x{604C}\x{604D}\x{604E}\x{604F}\x{6050}\x{6051}\x{6052}\x{6053}\x{6054}' + . '\x{6055}\x{6057}\x{6058}\x{6059}\x{605A}\x{605B}\x{605C}\x{605D}\x{605E}' + . '\x{605F}\x{6062}\x{6063}\x{6064}\x{6065}\x{6066}\x{6067}\x{6068}\x{6069}' + . '\x{606A}\x{606B}\x{606C}\x{606D}\x{606E}\x{606F}\x{6070}\x{6072}\x{6073}' + . '\x{6075}\x{6076}\x{6077}\x{6078}\x{6079}\x{607A}\x{607B}\x{607C}\x{607D}' + . '\x{607E}\x{607F}\x{6080}\x{6081}\x{6082}\x{6083}\x{6084}\x{6085}\x{6086}' + . '\x{6087}\x{6088}\x{6089}\x{608A}\x{608B}\x{608C}\x{608D}\x{608E}\x{608F}' + . '\x{6090}\x{6092}\x{6094}\x{6095}\x{6096}\x{6097}\x{6098}\x{6099}\x{609A}' + . '\x{609B}\x{609C}\x{609D}\x{609E}\x{609F}\x{60A0}\x{60A1}\x{60A2}\x{60A3}' + . '\x{60A4}\x{60A6}\x{60A7}\x{60A8}\x{60AA}\x{60AB}\x{60AC}\x{60AD}\x{60AE}' + . '\x{60AF}\x{60B0}\x{60B1}\x{60B2}\x{60B3}\x{60B4}\x{60B5}\x{60B6}\x{60B7}' + . '\x{60B8}\x{60B9}\x{60BA}\x{60BB}\x{60BC}\x{60BD}\x{60BE}\x{60BF}\x{60C0}' + . '\x{60C1}\x{60C2}\x{60C3}\x{60C4}\x{60C5}\x{60C6}\x{60C7}\x{60C8}\x{60C9}' + . '\x{60CA}\x{60CB}\x{60CC}\x{60CD}\x{60CE}\x{60CF}\x{60D0}\x{60D1}\x{60D3}' + . '\x{60D4}\x{60D5}\x{60D7}\x{60D8}\x{60D9}\x{60DA}\x{60DB}\x{60DC}\x{60DD}' + . '\x{60DF}\x{60E0}\x{60E1}\x{60E2}\x{60E4}\x{60E6}\x{60E7}\x{60E8}\x{60E9}' + . '\x{60EA}\x{60EB}\x{60EC}\x{60ED}\x{60EE}\x{60EF}\x{60F0}\x{60F1}\x{60F2}' + . '\x{60F3}\x{60F4}\x{60F5}\x{60F6}\x{60F7}\x{60F8}\x{60F9}\x{60FA}\x{60FB}' + . '\x{60FC}\x{60FE}\x{60FF}\x{6100}\x{6101}\x{6103}\x{6104}\x{6105}\x{6106}' + . '\x{6108}\x{6109}\x{610A}\x{610B}\x{610C}\x{610D}\x{610E}\x{610F}\x{6110}' + . '\x{6112}\x{6113}\x{6114}\x{6115}\x{6116}\x{6117}\x{6118}\x{6119}\x{611A}' + . '\x{611B}\x{611C}\x{611D}\x{611F}\x{6120}\x{6122}\x{6123}\x{6124}\x{6125}' + . '\x{6126}\x{6127}\x{6128}\x{6129}\x{612A}\x{612B}\x{612C}\x{612D}\x{612E}' + . '\x{612F}\x{6130}\x{6132}\x{6134}\x{6136}\x{6137}\x{613A}\x{613B}\x{613C}' + . '\x{613D}\x{613E}\x{613F}\x{6140}\x{6141}\x{6142}\x{6143}\x{6144}\x{6145}' + . '\x{6146}\x{6147}\x{6148}\x{6149}\x{614A}\x{614B}\x{614C}\x{614D}\x{614E}' + . '\x{614F}\x{6150}\x{6151}\x{6152}\x{6153}\x{6154}\x{6155}\x{6156}\x{6157}' + . '\x{6158}\x{6159}\x{615A}\x{615B}\x{615C}\x{615D}\x{615E}\x{615F}\x{6161}' + . '\x{6162}\x{6163}\x{6164}\x{6165}\x{6166}\x{6167}\x{6168}\x{6169}\x{616A}' + . '\x{616B}\x{616C}\x{616D}\x{616E}\x{6170}\x{6171}\x{6172}\x{6173}\x{6174}' + . '\x{6175}\x{6176}\x{6177}\x{6178}\x{6179}\x{617A}\x{617C}\x{617E}\x{6180}' + . '\x{6181}\x{6182}\x{6183}\x{6184}\x{6185}\x{6187}\x{6188}\x{6189}\x{618A}' + . '\x{618B}\x{618C}\x{618D}\x{618E}\x{618F}\x{6190}\x{6191}\x{6192}\x{6193}' + . '\x{6194}\x{6195}\x{6196}\x{6198}\x{6199}\x{619A}\x{619B}\x{619D}\x{619E}' + . '\x{619F}\x{61A0}\x{61A1}\x{61A2}\x{61A3}\x{61A4}\x{61A5}\x{61A6}\x{61A7}' + . '\x{61A8}\x{61A9}\x{61AA}\x{61AB}\x{61AC}\x{61AD}\x{61AE}\x{61AF}\x{61B0}' + . '\x{61B1}\x{61B2}\x{61B3}\x{61B4}\x{61B5}\x{61B6}\x{61B7}\x{61B8}\x{61BA}' + . '\x{61BC}\x{61BD}\x{61BE}\x{61BF}\x{61C0}\x{61C1}\x{61C2}\x{61C3}\x{61C4}' + . '\x{61C5}\x{61C6}\x{61C7}\x{61C8}\x{61C9}\x{61CA}\x{61CB}\x{61CC}\x{61CD}' + . '\x{61CE}\x{61CF}\x{61D0}\x{61D1}\x{61D2}\x{61D4}\x{61D6}\x{61D7}\x{61D8}' + . '\x{61D9}\x{61DA}\x{61DB}\x{61DC}\x{61DD}\x{61DE}\x{61DF}\x{61E0}\x{61E1}' + . '\x{61E2}\x{61E3}\x{61E4}\x{61E5}\x{61E6}\x{61E7}\x{61E8}\x{61E9}\x{61EA}' + . '\x{61EB}\x{61ED}\x{61EE}\x{61F0}\x{61F1}\x{61F2}\x{61F3}\x{61F5}\x{61F6}' + . '\x{61F7}\x{61F8}\x{61F9}\x{61FA}\x{61FB}\x{61FC}\x{61FD}\x{61FE}\x{61FF}' + . '\x{6200}\x{6201}\x{6202}\x{6203}\x{6204}\x{6206}\x{6207}\x{6208}\x{6209}' + . '\x{620A}\x{620B}\x{620C}\x{620D}\x{620E}\x{620F}\x{6210}\x{6211}\x{6212}' + . '\x{6213}\x{6214}\x{6215}\x{6216}\x{6217}\x{6218}\x{6219}\x{621A}\x{621B}' + . '\x{621C}\x{621D}\x{621E}\x{621F}\x{6220}\x{6221}\x{6222}\x{6223}\x{6224}' + . '\x{6225}\x{6226}\x{6227}\x{6228}\x{6229}\x{622A}\x{622B}\x{622C}\x{622D}' + . '\x{622E}\x{622F}\x{6230}\x{6231}\x{6232}\x{6233}\x{6234}\x{6236}\x{6237}' + . '\x{6238}\x{623A}\x{623B}\x{623C}\x{623D}\x{623E}\x{623F}\x{6240}\x{6241}' + . '\x{6242}\x{6243}\x{6244}\x{6245}\x{6246}\x{6247}\x{6248}\x{6249}\x{624A}' + . '\x{624B}\x{624C}\x{624D}\x{624E}\x{624F}\x{6250}\x{6251}\x{6252}\x{6253}' + . '\x{6254}\x{6255}\x{6256}\x{6258}\x{6259}\x{625A}\x{625B}\x{625C}\x{625D}' + . '\x{625E}\x{625F}\x{6260}\x{6261}\x{6262}\x{6263}\x{6264}\x{6265}\x{6266}' + . '\x{6267}\x{6268}\x{6269}\x{626A}\x{626B}\x{626C}\x{626D}\x{626E}\x{626F}' + . '\x{6270}\x{6271}\x{6272}\x{6273}\x{6274}\x{6275}\x{6276}\x{6277}\x{6278}' + . '\x{6279}\x{627A}\x{627B}\x{627C}\x{627D}\x{627E}\x{627F}\x{6280}\x{6281}' + . '\x{6283}\x{6284}\x{6285}\x{6286}\x{6287}\x{6288}\x{6289}\x{628A}\x{628B}' + . '\x{628C}\x{628E}\x{628F}\x{6290}\x{6291}\x{6292}\x{6293}\x{6294}\x{6295}' + . '\x{6296}\x{6297}\x{6298}\x{6299}\x{629A}\x{629B}\x{629C}\x{629E}\x{629F}' + . '\x{62A0}\x{62A1}\x{62A2}\x{62A3}\x{62A4}\x{62A5}\x{62A7}\x{62A8}\x{62A9}' + . '\x{62AA}\x{62AB}\x{62AC}\x{62AD}\x{62AE}\x{62AF}\x{62B0}\x{62B1}\x{62B2}' + . '\x{62B3}\x{62B4}\x{62B5}\x{62B6}\x{62B7}\x{62B8}\x{62B9}\x{62BA}\x{62BB}' + . '\x{62BC}\x{62BD}\x{62BE}\x{62BF}\x{62C0}\x{62C1}\x{62C2}\x{62C3}\x{62C4}' + . '\x{62C5}\x{62C6}\x{62C7}\x{62C8}\x{62C9}\x{62CA}\x{62CB}\x{62CC}\x{62CD}' + . '\x{62CE}\x{62CF}\x{62D0}\x{62D1}\x{62D2}\x{62D3}\x{62D4}\x{62D5}\x{62D6}' + . '\x{62D7}\x{62D8}\x{62D9}\x{62DA}\x{62DB}\x{62DC}\x{62DD}\x{62DF}\x{62E0}' + . '\x{62E1}\x{62E2}\x{62E3}\x{62E4}\x{62E5}\x{62E6}\x{62E7}\x{62E8}\x{62E9}' + . '\x{62EB}\x{62EC}\x{62ED}\x{62EE}\x{62EF}\x{62F0}\x{62F1}\x{62F2}\x{62F3}' + . '\x{62F4}\x{62F5}\x{62F6}\x{62F7}\x{62F8}\x{62F9}\x{62FA}\x{62FB}\x{62FC}' + . '\x{62FD}\x{62FE}\x{62FF}\x{6300}\x{6301}\x{6302}\x{6303}\x{6304}\x{6305}' + . '\x{6306}\x{6307}\x{6308}\x{6309}\x{630B}\x{630C}\x{630D}\x{630E}\x{630F}' + . '\x{6310}\x{6311}\x{6312}\x{6313}\x{6314}\x{6315}\x{6316}\x{6318}\x{6319}' + . '\x{631A}\x{631B}\x{631C}\x{631D}\x{631E}\x{631F}\x{6320}\x{6321}\x{6322}' + . '\x{6323}\x{6324}\x{6325}\x{6326}\x{6327}\x{6328}\x{6329}\x{632A}\x{632B}' + . '\x{632C}\x{632D}\x{632E}\x{632F}\x{6330}\x{6332}\x{6333}\x{6334}\x{6336}' + . '\x{6338}\x{6339}\x{633A}\x{633B}\x{633C}\x{633D}\x{633E}\x{6340}\x{6341}' + . '\x{6342}\x{6343}\x{6344}\x{6345}\x{6346}\x{6347}\x{6348}\x{6349}\x{634A}' + . '\x{634B}\x{634C}\x{634D}\x{634E}\x{634F}\x{6350}\x{6351}\x{6352}\x{6353}' + . '\x{6354}\x{6355}\x{6356}\x{6357}\x{6358}\x{6359}\x{635A}\x{635C}\x{635D}' + . '\x{635E}\x{635F}\x{6360}\x{6361}\x{6362}\x{6363}\x{6364}\x{6365}\x{6366}' + . '\x{6367}\x{6368}\x{6369}\x{636A}\x{636B}\x{636C}\x{636D}\x{636E}\x{636F}' + . '\x{6370}\x{6371}\x{6372}\x{6373}\x{6374}\x{6375}\x{6376}\x{6377}\x{6378}' + . '\x{6379}\x{637A}\x{637B}\x{637C}\x{637D}\x{637E}\x{6380}\x{6381}\x{6382}' + . '\x{6383}\x{6384}\x{6385}\x{6386}\x{6387}\x{6388}\x{6389}\x{638A}\x{638C}' + . '\x{638D}\x{638E}\x{638F}\x{6390}\x{6391}\x{6392}\x{6394}\x{6395}\x{6396}' + . '\x{6397}\x{6398}\x{6399}\x{639A}\x{639B}\x{639C}\x{639D}\x{639E}\x{639F}' + . '\x{63A0}\x{63A1}\x{63A2}\x{63A3}\x{63A4}\x{63A5}\x{63A6}\x{63A7}\x{63A8}' + . '\x{63A9}\x{63AA}\x{63AB}\x{63AC}\x{63AD}\x{63AE}\x{63AF}\x{63B0}\x{63B1}' + . '\x{63B2}\x{63B3}\x{63B4}\x{63B5}\x{63B6}\x{63B7}\x{63B8}\x{63B9}\x{63BA}' + . '\x{63BC}\x{63BD}\x{63BE}\x{63BF}\x{63C0}\x{63C1}\x{63C2}\x{63C3}\x{63C4}' + . '\x{63C5}\x{63C6}\x{63C7}\x{63C8}\x{63C9}\x{63CA}\x{63CB}\x{63CC}\x{63CD}' + . '\x{63CE}\x{63CF}\x{63D0}\x{63D2}\x{63D3}\x{63D4}\x{63D5}\x{63D6}\x{63D7}' + . '\x{63D8}\x{63D9}\x{63DA}\x{63DB}\x{63DC}\x{63DD}\x{63DE}\x{63DF}\x{63E0}' + . '\x{63E1}\x{63E2}\x{63E3}\x{63E4}\x{63E5}\x{63E6}\x{63E7}\x{63E8}\x{63E9}' + . '\x{63EA}\x{63EB}\x{63EC}\x{63ED}\x{63EE}\x{63EF}\x{63F0}\x{63F1}\x{63F2}' + . '\x{63F3}\x{63F4}\x{63F5}\x{63F6}\x{63F7}\x{63F8}\x{63F9}\x{63FA}\x{63FB}' + . '\x{63FC}\x{63FD}\x{63FE}\x{63FF}\x{6400}\x{6401}\x{6402}\x{6403}\x{6404}' + . '\x{6405}\x{6406}\x{6408}\x{6409}\x{640A}\x{640B}\x{640C}\x{640D}\x{640E}' + . '\x{640F}\x{6410}\x{6411}\x{6412}\x{6413}\x{6414}\x{6415}\x{6416}\x{6417}' + . '\x{6418}\x{6419}\x{641A}\x{641B}\x{641C}\x{641D}\x{641E}\x{641F}\x{6420}' + . '\x{6421}\x{6422}\x{6423}\x{6424}\x{6425}\x{6426}\x{6427}\x{6428}\x{6429}' + . '\x{642A}\x{642B}\x{642C}\x{642D}\x{642E}\x{642F}\x{6430}\x{6431}\x{6432}' + . '\x{6433}\x{6434}\x{6435}\x{6436}\x{6437}\x{6438}\x{6439}\x{643A}\x{643D}' + . '\x{643E}\x{643F}\x{6440}\x{6441}\x{6443}\x{6444}\x{6445}\x{6446}\x{6447}' + . '\x{6448}\x{644A}\x{644B}\x{644C}\x{644D}\x{644E}\x{644F}\x{6450}\x{6451}' + . '\x{6452}\x{6453}\x{6454}\x{6455}\x{6456}\x{6457}\x{6458}\x{6459}\x{645B}' + . '\x{645C}\x{645D}\x{645E}\x{645F}\x{6460}\x{6461}\x{6462}\x{6463}\x{6464}' + . '\x{6465}\x{6466}\x{6467}\x{6468}\x{6469}\x{646A}\x{646B}\x{646C}\x{646D}' + . '\x{646E}\x{646F}\x{6470}\x{6471}\x{6472}\x{6473}\x{6474}\x{6475}\x{6476}' + . '\x{6477}\x{6478}\x{6479}\x{647A}\x{647B}\x{647C}\x{647D}\x{647F}\x{6480}' + . '\x{6481}\x{6482}\x{6483}\x{6484}\x{6485}\x{6487}\x{6488}\x{6489}\x{648A}' + . '\x{648B}\x{648C}\x{648D}\x{648E}\x{648F}\x{6490}\x{6491}\x{6492}\x{6493}' + . '\x{6494}\x{6495}\x{6496}\x{6497}\x{6498}\x{6499}\x{649A}\x{649B}\x{649C}' + . '\x{649D}\x{649E}\x{649F}\x{64A0}\x{64A2}\x{64A3}\x{64A4}\x{64A5}\x{64A6}' + . '\x{64A7}\x{64A8}\x{64A9}\x{64AA}\x{64AB}\x{64AC}\x{64AD}\x{64AE}\x{64B0}' + . '\x{64B1}\x{64B2}\x{64B3}\x{64B4}\x{64B5}\x{64B7}\x{64B8}\x{64B9}\x{64BA}' + . '\x{64BB}\x{64BC}\x{64BD}\x{64BE}\x{64BF}\x{64C0}\x{64C1}\x{64C2}\x{64C3}' + . '\x{64C4}\x{64C5}\x{64C6}\x{64C7}\x{64C9}\x{64CA}\x{64CB}\x{64CC}\x{64CD}' + . '\x{64CE}\x{64CF}\x{64D0}\x{64D1}\x{64D2}\x{64D3}\x{64D4}\x{64D6}\x{64D7}' + . '\x{64D8}\x{64D9}\x{64DA}\x{64DB}\x{64DC}\x{64DD}\x{64DE}\x{64DF}\x{64E0}' + . '\x{64E2}\x{64E3}\x{64E4}\x{64E6}\x{64E7}\x{64E8}\x{64E9}\x{64EA}\x{64EB}' + . '\x{64EC}\x{64ED}\x{64EF}\x{64F0}\x{64F1}\x{64F2}\x{64F3}\x{64F4}\x{64F6}' + . '\x{64F7}\x{64F8}\x{64FA}\x{64FB}\x{64FC}\x{64FD}\x{64FE}\x{64FF}\x{6500}' + . '\x{6501}\x{6503}\x{6504}\x{6505}\x{6506}\x{6507}\x{6508}\x{6509}\x{650B}' + . '\x{650C}\x{650D}\x{650E}\x{650F}\x{6510}\x{6511}\x{6512}\x{6513}\x{6514}' + . '\x{6515}\x{6516}\x{6517}\x{6518}\x{6519}\x{651A}\x{651B}\x{651C}\x{651D}' + . '\x{651E}\x{6520}\x{6521}\x{6522}\x{6523}\x{6524}\x{6525}\x{6526}\x{6527}' + . '\x{6529}\x{652A}\x{652B}\x{652C}\x{652D}\x{652E}\x{652F}\x{6530}\x{6531}' + . '\x{6532}\x{6533}\x{6534}\x{6535}\x{6536}\x{6537}\x{6538}\x{6539}\x{653A}' + . '\x{653B}\x{653C}\x{653D}\x{653E}\x{653F}\x{6541}\x{6543}\x{6544}\x{6545}' + . '\x{6546}\x{6547}\x{6548}\x{6549}\x{654A}\x{654B}\x{654C}\x{654D}\x{654E}' + . '\x{654F}\x{6550}\x{6551}\x{6552}\x{6553}\x{6554}\x{6555}\x{6556}\x{6557}' + . '\x{6558}\x{6559}\x{655B}\x{655C}\x{655D}\x{655E}\x{6560}\x{6561}\x{6562}' + . '\x{6563}\x{6564}\x{6565}\x{6566}\x{6567}\x{6568}\x{6569}\x{656A}\x{656B}' + . '\x{656C}\x{656E}\x{656F}\x{6570}\x{6571}\x{6572}\x{6573}\x{6574}\x{6575}' + . '\x{6576}\x{6577}\x{6578}\x{6579}\x{657A}\x{657B}\x{657C}\x{657E}\x{657F}' + . '\x{6580}\x{6581}\x{6582}\x{6583}\x{6584}\x{6585}\x{6586}\x{6587}\x{6588}' + . '\x{6589}\x{658B}\x{658C}\x{658D}\x{658E}\x{658F}\x{6590}\x{6591}\x{6592}' + . '\x{6593}\x{6594}\x{6595}\x{6596}\x{6597}\x{6598}\x{6599}\x{659B}\x{659C}' + . '\x{659D}\x{659E}\x{659F}\x{65A0}\x{65A1}\x{65A2}\x{65A3}\x{65A4}\x{65A5}' + . '\x{65A6}\x{65A7}\x{65A8}\x{65A9}\x{65AA}\x{65AB}\x{65AC}\x{65AD}\x{65AE}' + . '\x{65AF}\x{65B0}\x{65B1}\x{65B2}\x{65B3}\x{65B4}\x{65B6}\x{65B7}\x{65B8}' + . '\x{65B9}\x{65BA}\x{65BB}\x{65BC}\x{65BD}\x{65BF}\x{65C0}\x{65C1}\x{65C2}' + . '\x{65C3}\x{65C4}\x{65C5}\x{65C6}\x{65C7}\x{65CA}\x{65CB}\x{65CC}\x{65CD}' + . '\x{65CE}\x{65CF}\x{65D0}\x{65D2}\x{65D3}\x{65D4}\x{65D5}\x{65D6}\x{65D7}' + . '\x{65DA}\x{65DB}\x{65DD}\x{65DE}\x{65DF}\x{65E0}\x{65E1}\x{65E2}\x{65E3}' + . '\x{65E5}\x{65E6}\x{65E7}\x{65E8}\x{65E9}\x{65EB}\x{65EC}\x{65ED}\x{65EE}' + . '\x{65EF}\x{65F0}\x{65F1}\x{65F2}\x{65F3}\x{65F4}\x{65F5}\x{65F6}\x{65F7}' + . '\x{65F8}\x{65FA}\x{65FB}\x{65FC}\x{65FD}\x{6600}\x{6601}\x{6602}\x{6603}' + . '\x{6604}\x{6605}\x{6606}\x{6607}\x{6608}\x{6609}\x{660A}\x{660B}\x{660C}' + . '\x{660D}\x{660E}\x{660F}\x{6610}\x{6611}\x{6612}\x{6613}\x{6614}\x{6615}' + . '\x{6616}\x{6618}\x{6619}\x{661A}\x{661B}\x{661C}\x{661D}\x{661F}\x{6620}' + . '\x{6621}\x{6622}\x{6623}\x{6624}\x{6625}\x{6626}\x{6627}\x{6628}\x{6629}' + . '\x{662A}\x{662B}\x{662D}\x{662E}\x{662F}\x{6630}\x{6631}\x{6632}\x{6633}' + . '\x{6634}\x{6635}\x{6636}\x{6639}\x{663A}\x{663C}\x{663D}\x{663E}\x{6640}' + . '\x{6641}\x{6642}\x{6643}\x{6644}\x{6645}\x{6646}\x{6647}\x{6649}\x{664A}' + . '\x{664B}\x{664C}\x{664E}\x{664F}\x{6650}\x{6651}\x{6652}\x{6653}\x{6654}' + . '\x{6655}\x{6656}\x{6657}\x{6658}\x{6659}\x{665A}\x{665B}\x{665C}\x{665D}' + . '\x{665E}\x{665F}\x{6661}\x{6662}\x{6664}\x{6665}\x{6666}\x{6668}\x{6669}' + . '\x{666A}\x{666B}\x{666C}\x{666D}\x{666E}\x{666F}\x{6670}\x{6671}\x{6672}' + . '\x{6673}\x{6674}\x{6675}\x{6676}\x{6677}\x{6678}\x{6679}\x{667A}\x{667B}' + . '\x{667C}\x{667D}\x{667E}\x{667F}\x{6680}\x{6681}\x{6682}\x{6683}\x{6684}' + . '\x{6685}\x{6686}\x{6687}\x{6688}\x{6689}\x{668A}\x{668B}\x{668C}\x{668D}' + . '\x{668E}\x{668F}\x{6690}\x{6691}\x{6693}\x{6694}\x{6695}\x{6696}\x{6697}' + . '\x{6698}\x{6699}\x{669A}\x{669B}\x{669D}\x{669F}\x{66A0}\x{66A1}\x{66A2}' + . '\x{66A3}\x{66A4}\x{66A5}\x{66A6}\x{66A7}\x{66A8}\x{66A9}\x{66AA}\x{66AB}' + . '\x{66AE}\x{66AF}\x{66B0}\x{66B1}\x{66B2}\x{66B3}\x{66B4}\x{66B5}\x{66B6}' + . '\x{66B7}\x{66B8}\x{66B9}\x{66BA}\x{66BB}\x{66BC}\x{66BD}\x{66BE}\x{66BF}' + . '\x{66C0}\x{66C1}\x{66C2}\x{66C3}\x{66C4}\x{66C5}\x{66C6}\x{66C7}\x{66C8}' + . '\x{66C9}\x{66CA}\x{66CB}\x{66CC}\x{66CD}\x{66CE}\x{66CF}\x{66D1}\x{66D2}' + . '\x{66D4}\x{66D5}\x{66D6}\x{66D8}\x{66D9}\x{66DA}\x{66DB}\x{66DC}\x{66DD}' + . '\x{66DE}\x{66E0}\x{66E1}\x{66E2}\x{66E3}\x{66E4}\x{66E5}\x{66E6}\x{66E7}' + . '\x{66E8}\x{66E9}\x{66EA}\x{66EB}\x{66EC}\x{66ED}\x{66EE}\x{66F0}\x{66F1}' + . '\x{66F2}\x{66F3}\x{66F4}\x{66F5}\x{66F6}\x{66F7}\x{66F8}\x{66F9}\x{66FA}' + . '\x{66FB}\x{66FC}\x{66FE}\x{66FF}\x{6700}\x{6701}\x{6703}\x{6704}\x{6705}' + . '\x{6706}\x{6708}\x{6709}\x{670A}\x{670B}\x{670C}\x{670D}\x{670E}\x{670F}' + . '\x{6710}\x{6711}\x{6712}\x{6713}\x{6714}\x{6715}\x{6716}\x{6717}\x{6718}' + . '\x{671A}\x{671B}\x{671C}\x{671D}\x{671E}\x{671F}\x{6720}\x{6721}\x{6722}' + . '\x{6723}\x{6725}\x{6726}\x{6727}\x{6728}\x{672A}\x{672B}\x{672C}\x{672D}' + . '\x{672E}\x{672F}\x{6730}\x{6731}\x{6732}\x{6733}\x{6734}\x{6735}\x{6736}' + . '\x{6737}\x{6738}\x{6739}\x{673A}\x{673B}\x{673C}\x{673D}\x{673E}\x{673F}' + . '\x{6740}\x{6741}\x{6742}\x{6743}\x{6744}\x{6745}\x{6746}\x{6747}\x{6748}' + . '\x{6749}\x{674A}\x{674B}\x{674C}\x{674D}\x{674E}\x{674F}\x{6750}\x{6751}' + . '\x{6752}\x{6753}\x{6754}\x{6755}\x{6756}\x{6757}\x{6758}\x{6759}\x{675A}' + . '\x{675B}\x{675C}\x{675D}\x{675E}\x{675F}\x{6760}\x{6761}\x{6762}\x{6763}' + . '\x{6764}\x{6765}\x{6766}\x{6768}\x{6769}\x{676A}\x{676B}\x{676C}\x{676D}' + . '\x{676E}\x{676F}\x{6770}\x{6771}\x{6772}\x{6773}\x{6774}\x{6775}\x{6776}' + . '\x{6777}\x{6778}\x{6779}\x{677A}\x{677B}\x{677C}\x{677D}\x{677E}\x{677F}' + . '\x{6780}\x{6781}\x{6782}\x{6783}\x{6784}\x{6785}\x{6786}\x{6787}\x{6789}' + . '\x{678A}\x{678B}\x{678C}\x{678D}\x{678E}\x{678F}\x{6790}\x{6791}\x{6792}' + . '\x{6793}\x{6794}\x{6795}\x{6797}\x{6798}\x{6799}\x{679A}\x{679B}\x{679C}' + . '\x{679D}\x{679E}\x{679F}\x{67A0}\x{67A1}\x{67A2}\x{67A3}\x{67A4}\x{67A5}' + . '\x{67A6}\x{67A7}\x{67A8}\x{67AA}\x{67AB}\x{67AC}\x{67AD}\x{67AE}\x{67AF}' + . '\x{67B0}\x{67B1}\x{67B2}\x{67B3}\x{67B4}\x{67B5}\x{67B6}\x{67B7}\x{67B8}' + . '\x{67B9}\x{67BA}\x{67BB}\x{67BC}\x{67BE}\x{67C0}\x{67C1}\x{67C2}\x{67C3}' + . '\x{67C4}\x{67C5}\x{67C6}\x{67C7}\x{67C8}\x{67C9}\x{67CA}\x{67CB}\x{67CC}' + . '\x{67CD}\x{67CE}\x{67CF}\x{67D0}\x{67D1}\x{67D2}\x{67D3}\x{67D4}\x{67D6}' + . '\x{67D8}\x{67D9}\x{67DA}\x{67DB}\x{67DC}\x{67DD}\x{67DE}\x{67DF}\x{67E0}' + . '\x{67E1}\x{67E2}\x{67E3}\x{67E4}\x{67E5}\x{67E6}\x{67E7}\x{67E8}\x{67E9}' + . '\x{67EA}\x{67EB}\x{67EC}\x{67ED}\x{67EE}\x{67EF}\x{67F0}\x{67F1}\x{67F2}' + . '\x{67F3}\x{67F4}\x{67F5}\x{67F6}\x{67F7}\x{67F8}\x{67FA}\x{67FB}\x{67FC}' + . '\x{67FD}\x{67FE}\x{67FF}\x{6800}\x{6802}\x{6803}\x{6804}\x{6805}\x{6806}' + . '\x{6807}\x{6808}\x{6809}\x{680A}\x{680B}\x{680C}\x{680D}\x{680E}\x{680F}' + . '\x{6810}\x{6811}\x{6812}\x{6813}\x{6814}\x{6816}\x{6817}\x{6818}\x{6819}' + . '\x{681A}\x{681B}\x{681C}\x{681D}\x{681F}\x{6820}\x{6821}\x{6822}\x{6823}' + . '\x{6824}\x{6825}\x{6826}\x{6828}\x{6829}\x{682A}\x{682B}\x{682C}\x{682D}' + . '\x{682E}\x{682F}\x{6831}\x{6832}\x{6833}\x{6834}\x{6835}\x{6836}\x{6837}' + . '\x{6838}\x{6839}\x{683A}\x{683B}\x{683C}\x{683D}\x{683E}\x{683F}\x{6840}' + . '\x{6841}\x{6842}\x{6843}\x{6844}\x{6845}\x{6846}\x{6847}\x{6848}\x{6849}' + . '\x{684A}\x{684B}\x{684C}\x{684D}\x{684E}\x{684F}\x{6850}\x{6851}\x{6852}' + . '\x{6853}\x{6854}\x{6855}\x{6856}\x{6857}\x{685B}\x{685D}\x{6860}\x{6861}' + . '\x{6862}\x{6863}\x{6864}\x{6865}\x{6866}\x{6867}\x{6868}\x{6869}\x{686A}' + . '\x{686B}\x{686C}\x{686D}\x{686E}\x{686F}\x{6870}\x{6871}\x{6872}\x{6873}' + . '\x{6874}\x{6875}\x{6876}\x{6877}\x{6878}\x{6879}\x{687B}\x{687C}\x{687D}' + . '\x{687E}\x{687F}\x{6880}\x{6881}\x{6882}\x{6883}\x{6884}\x{6885}\x{6886}' + . '\x{6887}\x{6888}\x{6889}\x{688A}\x{688B}\x{688C}\x{688D}\x{688E}\x{688F}' + . '\x{6890}\x{6891}\x{6892}\x{6893}\x{6894}\x{6896}\x{6897}\x{6898}\x{689A}' + . '\x{689B}\x{689C}\x{689D}\x{689E}\x{689F}\x{68A0}\x{68A1}\x{68A2}\x{68A3}' + . '\x{68A4}\x{68A6}\x{68A7}\x{68A8}\x{68A9}\x{68AA}\x{68AB}\x{68AC}\x{68AD}' + . '\x{68AE}\x{68AF}\x{68B0}\x{68B1}\x{68B2}\x{68B3}\x{68B4}\x{68B5}\x{68B6}' + . '\x{68B7}\x{68B9}\x{68BB}\x{68BC}\x{68BD}\x{68BE}\x{68BF}\x{68C0}\x{68C1}' + . '\x{68C2}\x{68C4}\x{68C6}\x{68C7}\x{68C8}\x{68C9}\x{68CA}\x{68CB}\x{68CC}' + . '\x{68CD}\x{68CE}\x{68CF}\x{68D0}\x{68D1}\x{68D2}\x{68D3}\x{68D4}\x{68D5}' + . '\x{68D6}\x{68D7}\x{68D8}\x{68DA}\x{68DB}\x{68DC}\x{68DD}\x{68DE}\x{68DF}' + . '\x{68E0}\x{68E1}\x{68E3}\x{68E4}\x{68E6}\x{68E7}\x{68E8}\x{68E9}\x{68EA}' + . '\x{68EB}\x{68EC}\x{68ED}\x{68EE}\x{68EF}\x{68F0}\x{68F1}\x{68F2}\x{68F3}' + . '\x{68F4}\x{68F5}\x{68F6}\x{68F7}\x{68F8}\x{68F9}\x{68FA}\x{68FB}\x{68FC}' + . '\x{68FD}\x{68FE}\x{68FF}\x{6901}\x{6902}\x{6903}\x{6904}\x{6905}\x{6906}' + . '\x{6907}\x{6908}\x{690A}\x{690B}\x{690C}\x{690D}\x{690E}\x{690F}\x{6910}' + . '\x{6911}\x{6912}\x{6913}\x{6914}\x{6915}\x{6916}\x{6917}\x{6918}\x{6919}' + . '\x{691A}\x{691B}\x{691C}\x{691D}\x{691E}\x{691F}\x{6920}\x{6921}\x{6922}' + . '\x{6923}\x{6924}\x{6925}\x{6926}\x{6927}\x{6928}\x{6929}\x{692A}\x{692B}' + . '\x{692C}\x{692D}\x{692E}\x{692F}\x{6930}\x{6931}\x{6932}\x{6933}\x{6934}' + . '\x{6935}\x{6936}\x{6937}\x{6938}\x{6939}\x{693A}\x{693B}\x{693C}\x{693D}' + . '\x{693F}\x{6940}\x{6941}\x{6942}\x{6943}\x{6944}\x{6945}\x{6946}\x{6947}' + . '\x{6948}\x{6949}\x{694A}\x{694B}\x{694C}\x{694E}\x{694F}\x{6950}\x{6951}' + . '\x{6952}\x{6953}\x{6954}\x{6955}\x{6956}\x{6957}\x{6958}\x{6959}\x{695A}' + . '\x{695B}\x{695C}\x{695D}\x{695E}\x{695F}\x{6960}\x{6961}\x{6962}\x{6963}' + . '\x{6964}\x{6965}\x{6966}\x{6967}\x{6968}\x{6969}\x{696A}\x{696B}\x{696C}' + . '\x{696D}\x{696E}\x{696F}\x{6970}\x{6971}\x{6972}\x{6973}\x{6974}\x{6975}' + . '\x{6976}\x{6977}\x{6978}\x{6979}\x{697A}\x{697B}\x{697C}\x{697D}\x{697E}' + . '\x{697F}\x{6980}\x{6981}\x{6982}\x{6983}\x{6984}\x{6985}\x{6986}\x{6987}' + . '\x{6988}\x{6989}\x{698A}\x{698B}\x{698C}\x{698D}\x{698E}\x{698F}\x{6990}' + . '\x{6991}\x{6992}\x{6993}\x{6994}\x{6995}\x{6996}\x{6997}\x{6998}\x{6999}' + . '\x{699A}\x{699B}\x{699C}\x{699D}\x{699E}\x{69A0}\x{69A1}\x{69A3}\x{69A4}' + . '\x{69A5}\x{69A6}\x{69A7}\x{69A8}\x{69A9}\x{69AA}\x{69AB}\x{69AC}\x{69AD}' + . '\x{69AE}\x{69AF}\x{69B0}\x{69B1}\x{69B2}\x{69B3}\x{69B4}\x{69B5}\x{69B6}' + . '\x{69B7}\x{69B8}\x{69B9}\x{69BA}\x{69BB}\x{69BC}\x{69BD}\x{69BE}\x{69BF}' + . '\x{69C1}\x{69C2}\x{69C3}\x{69C4}\x{69C5}\x{69C6}\x{69C7}\x{69C8}\x{69C9}' + . '\x{69CA}\x{69CB}\x{69CC}\x{69CD}\x{69CE}\x{69CF}\x{69D0}\x{69D3}\x{69D4}' + . '\x{69D8}\x{69D9}\x{69DA}\x{69DB}\x{69DC}\x{69DD}\x{69DE}\x{69DF}\x{69E0}' + . '\x{69E1}\x{69E2}\x{69E3}\x{69E4}\x{69E5}\x{69E6}\x{69E7}\x{69E8}\x{69E9}' + . '\x{69EA}\x{69EB}\x{69EC}\x{69ED}\x{69EE}\x{69EF}\x{69F0}\x{69F1}\x{69F2}' + . '\x{69F3}\x{69F4}\x{69F5}\x{69F6}\x{69F7}\x{69F8}\x{69FA}\x{69FB}\x{69FC}' + . '\x{69FD}\x{69FE}\x{69FF}\x{6A00}\x{6A01}\x{6A02}\x{6A04}\x{6A05}\x{6A06}' + . '\x{6A07}\x{6A08}\x{6A09}\x{6A0A}\x{6A0B}\x{6A0D}\x{6A0E}\x{6A0F}\x{6A10}' + . '\x{6A11}\x{6A12}\x{6A13}\x{6A14}\x{6A15}\x{6A16}\x{6A17}\x{6A18}\x{6A19}' + . '\x{6A1A}\x{6A1B}\x{6A1D}\x{6A1E}\x{6A1F}\x{6A20}\x{6A21}\x{6A22}\x{6A23}' + . '\x{6A25}\x{6A26}\x{6A27}\x{6A28}\x{6A29}\x{6A2A}\x{6A2B}\x{6A2C}\x{6A2D}' + . '\x{6A2E}\x{6A2F}\x{6A30}\x{6A31}\x{6A32}\x{6A33}\x{6A34}\x{6A35}\x{6A36}' + . '\x{6A38}\x{6A39}\x{6A3A}\x{6A3B}\x{6A3C}\x{6A3D}\x{6A3E}\x{6A3F}\x{6A40}' + . '\x{6A41}\x{6A42}\x{6A43}\x{6A44}\x{6A45}\x{6A46}\x{6A47}\x{6A48}\x{6A49}' + . '\x{6A4B}\x{6A4C}\x{6A4D}\x{6A4E}\x{6A4F}\x{6A50}\x{6A51}\x{6A52}\x{6A54}' + . '\x{6A55}\x{6A56}\x{6A57}\x{6A58}\x{6A59}\x{6A5A}\x{6A5B}\x{6A5D}\x{6A5E}' + . '\x{6A5F}\x{6A60}\x{6A61}\x{6A62}\x{6A63}\x{6A64}\x{6A65}\x{6A66}\x{6A67}' + . '\x{6A68}\x{6A69}\x{6A6A}\x{6A6B}\x{6A6C}\x{6A6D}\x{6A6F}\x{6A71}\x{6A72}' + . '\x{6A73}\x{6A74}\x{6A75}\x{6A76}\x{6A77}\x{6A78}\x{6A79}\x{6A7A}\x{6A7B}' + . '\x{6A7C}\x{6A7D}\x{6A7E}\x{6A7F}\x{6A80}\x{6A81}\x{6A82}\x{6A83}\x{6A84}' + . '\x{6A85}\x{6A87}\x{6A88}\x{6A89}\x{6A8B}\x{6A8C}\x{6A8D}\x{6A8E}\x{6A90}' + . '\x{6A91}\x{6A92}\x{6A93}\x{6A94}\x{6A95}\x{6A96}\x{6A97}\x{6A98}\x{6A9A}' + . '\x{6A9B}\x{6A9C}\x{6A9E}\x{6A9F}\x{6AA0}\x{6AA1}\x{6AA2}\x{6AA3}\x{6AA4}' + . '\x{6AA5}\x{6AA6}\x{6AA7}\x{6AA8}\x{6AA9}\x{6AAB}\x{6AAC}\x{6AAD}\x{6AAE}' + . '\x{6AAF}\x{6AB0}\x{6AB2}\x{6AB3}\x{6AB4}\x{6AB5}\x{6AB6}\x{6AB7}\x{6AB8}' + . '\x{6AB9}\x{6ABA}\x{6ABB}\x{6ABC}\x{6ABD}\x{6ABF}\x{6AC1}\x{6AC2}\x{6AC3}' + . '\x{6AC5}\x{6AC6}\x{6AC7}\x{6ACA}\x{6ACB}\x{6ACC}\x{6ACD}\x{6ACE}\x{6ACF}' + . '\x{6AD0}\x{6AD1}\x{6AD2}\x{6AD3}\x{6AD4}\x{6AD5}\x{6AD6}\x{6AD7}\x{6AD9}' + . '\x{6ADA}\x{6ADB}\x{6ADC}\x{6ADD}\x{6ADE}\x{6ADF}\x{6AE0}\x{6AE1}\x{6AE2}' + . '\x{6AE3}\x{6AE4}\x{6AE5}\x{6AE6}\x{6AE7}\x{6AE8}\x{6AEA}\x{6AEB}\x{6AEC}' + . '\x{6AED}\x{6AEE}\x{6AEF}\x{6AF0}\x{6AF1}\x{6AF2}\x{6AF3}\x{6AF4}\x{6AF5}' + . '\x{6AF6}\x{6AF7}\x{6AF8}\x{6AF9}\x{6AFA}\x{6AFB}\x{6AFC}\x{6AFD}\x{6AFE}' + . '\x{6AFF}\x{6B00}\x{6B01}\x{6B02}\x{6B03}\x{6B04}\x{6B05}\x{6B06}\x{6B07}' + . '\x{6B08}\x{6B09}\x{6B0A}\x{6B0B}\x{6B0C}\x{6B0D}\x{6B0F}\x{6B10}\x{6B11}' + . '\x{6B12}\x{6B13}\x{6B14}\x{6B15}\x{6B16}\x{6B17}\x{6B18}\x{6B19}\x{6B1A}' + . '\x{6B1C}\x{6B1D}\x{6B1E}\x{6B1F}\x{6B20}\x{6B21}\x{6B22}\x{6B23}\x{6B24}' + . '\x{6B25}\x{6B26}\x{6B27}\x{6B28}\x{6B29}\x{6B2A}\x{6B2B}\x{6B2C}\x{6B2D}' + . '\x{6B2F}\x{6B30}\x{6B31}\x{6B32}\x{6B33}\x{6B34}\x{6B36}\x{6B37}\x{6B38}' + . '\x{6B39}\x{6B3A}\x{6B3B}\x{6B3C}\x{6B3D}\x{6B3E}\x{6B3F}\x{6B41}\x{6B42}' + . '\x{6B43}\x{6B44}\x{6B45}\x{6B46}\x{6B47}\x{6B48}\x{6B49}\x{6B4A}\x{6B4B}' + . '\x{6B4C}\x{6B4D}\x{6B4E}\x{6B4F}\x{6B50}\x{6B51}\x{6B52}\x{6B53}\x{6B54}' + . '\x{6B55}\x{6B56}\x{6B59}\x{6B5A}\x{6B5B}\x{6B5C}\x{6B5E}\x{6B5F}\x{6B60}' + . '\x{6B61}\x{6B62}\x{6B63}\x{6B64}\x{6B65}\x{6B66}\x{6B67}\x{6B69}\x{6B6A}' + . '\x{6B6B}\x{6B6D}\x{6B6F}\x{6B70}\x{6B72}\x{6B73}\x{6B74}\x{6B76}\x{6B77}' + . '\x{6B78}\x{6B79}\x{6B7A}\x{6B7B}\x{6B7C}\x{6B7E}\x{6B7F}\x{6B80}\x{6B81}' + . '\x{6B82}\x{6B83}\x{6B84}\x{6B85}\x{6B86}\x{6B87}\x{6B88}\x{6B89}\x{6B8A}' + . '\x{6B8B}\x{6B8C}\x{6B8D}\x{6B8E}\x{6B8F}\x{6B90}\x{6B91}\x{6B92}\x{6B93}' + . '\x{6B94}\x{6B95}\x{6B96}\x{6B97}\x{6B98}\x{6B99}\x{6B9A}\x{6B9B}\x{6B9C}' + . '\x{6B9D}\x{6B9E}\x{6B9F}\x{6BA0}\x{6BA1}\x{6BA2}\x{6BA3}\x{6BA4}\x{6BA5}' + . '\x{6BA6}\x{6BA7}\x{6BA8}\x{6BA9}\x{6BAA}\x{6BAB}\x{6BAC}\x{6BAD}\x{6BAE}' + . '\x{6BAF}\x{6BB0}\x{6BB2}\x{6BB3}\x{6BB4}\x{6BB5}\x{6BB6}\x{6BB7}\x{6BB9}' + . '\x{6BBA}\x{6BBB}\x{6BBC}\x{6BBD}\x{6BBE}\x{6BBF}\x{6BC0}\x{6BC1}\x{6BC2}' + . '\x{6BC3}\x{6BC4}\x{6BC5}\x{6BC6}\x{6BC7}\x{6BC8}\x{6BC9}\x{6BCA}\x{6BCB}' + . '\x{6BCC}\x{6BCD}\x{6BCE}\x{6BCF}\x{6BD0}\x{6BD1}\x{6BD2}\x{6BD3}\x{6BD4}' + . '\x{6BD5}\x{6BD6}\x{6BD7}\x{6BD8}\x{6BD9}\x{6BDA}\x{6BDB}\x{6BDC}\x{6BDD}' + . '\x{6BDE}\x{6BDF}\x{6BE0}\x{6BE1}\x{6BE2}\x{6BE3}\x{6BE4}\x{6BE5}\x{6BE6}' + . '\x{6BE7}\x{6BE8}\x{6BEA}\x{6BEB}\x{6BEC}\x{6BED}\x{6BEE}\x{6BEF}\x{6BF0}' + . '\x{6BF2}\x{6BF3}\x{6BF5}\x{6BF6}\x{6BF7}\x{6BF8}\x{6BF9}\x{6BFB}\x{6BFC}' + . '\x{6BFD}\x{6BFE}\x{6BFF}\x{6C00}\x{6C01}\x{6C02}\x{6C03}\x{6C04}\x{6C05}' + . '\x{6C06}\x{6C07}\x{6C08}\x{6C09}\x{6C0B}\x{6C0C}\x{6C0D}\x{6C0E}\x{6C0F}' + . '\x{6C10}\x{6C11}\x{6C12}\x{6C13}\x{6C14}\x{6C15}\x{6C16}\x{6C18}\x{6C19}' + . '\x{6C1A}\x{6C1B}\x{6C1D}\x{6C1E}\x{6C1F}\x{6C20}\x{6C21}\x{6C22}\x{6C23}' + . '\x{6C24}\x{6C25}\x{6C26}\x{6C27}\x{6C28}\x{6C29}\x{6C2A}\x{6C2B}\x{6C2C}' + . '\x{6C2E}\x{6C2F}\x{6C30}\x{6C31}\x{6C32}\x{6C33}\x{6C34}\x{6C35}\x{6C36}' + . '\x{6C37}\x{6C38}\x{6C3A}\x{6C3B}\x{6C3D}\x{6C3E}\x{6C3F}\x{6C40}\x{6C41}' + . '\x{6C42}\x{6C43}\x{6C44}\x{6C46}\x{6C47}\x{6C48}\x{6C49}\x{6C4A}\x{6C4B}' + . '\x{6C4C}\x{6C4D}\x{6C4E}\x{6C4F}\x{6C50}\x{6C51}\x{6C52}\x{6C53}\x{6C54}' + . '\x{6C55}\x{6C56}\x{6C57}\x{6C58}\x{6C59}\x{6C5A}\x{6C5B}\x{6C5C}\x{6C5D}' + . '\x{6C5E}\x{6C5F}\x{6C60}\x{6C61}\x{6C62}\x{6C63}\x{6C64}\x{6C65}\x{6C66}' + . '\x{6C67}\x{6C68}\x{6C69}\x{6C6A}\x{6C6B}\x{6C6D}\x{6C6F}\x{6C70}\x{6C71}' + . '\x{6C72}\x{6C73}\x{6C74}\x{6C75}\x{6C76}\x{6C77}\x{6C78}\x{6C79}\x{6C7A}' + . '\x{6C7B}\x{6C7C}\x{6C7D}\x{6C7E}\x{6C7F}\x{6C80}\x{6C81}\x{6C82}\x{6C83}' + . '\x{6C84}\x{6C85}\x{6C86}\x{6C87}\x{6C88}\x{6C89}\x{6C8A}\x{6C8B}\x{6C8C}' + . '\x{6C8D}\x{6C8E}\x{6C8F}\x{6C90}\x{6C91}\x{6C92}\x{6C93}\x{6C94}\x{6C95}' + . '\x{6C96}\x{6C97}\x{6C98}\x{6C99}\x{6C9A}\x{6C9B}\x{6C9C}\x{6C9D}\x{6C9E}' + . '\x{6C9F}\x{6CA1}\x{6CA2}\x{6CA3}\x{6CA4}\x{6CA5}\x{6CA6}\x{6CA7}\x{6CA8}' + . '\x{6CA9}\x{6CAA}\x{6CAB}\x{6CAC}\x{6CAD}\x{6CAE}\x{6CAF}\x{6CB0}\x{6CB1}' + . '\x{6CB2}\x{6CB3}\x{6CB4}\x{6CB5}\x{6CB6}\x{6CB7}\x{6CB8}\x{6CB9}\x{6CBA}' + . '\x{6CBB}\x{6CBC}\x{6CBD}\x{6CBE}\x{6CBF}\x{6CC0}\x{6CC1}\x{6CC2}\x{6CC3}' + . '\x{6CC4}\x{6CC5}\x{6CC6}\x{6CC7}\x{6CC8}\x{6CC9}\x{6CCA}\x{6CCB}\x{6CCC}' + . '\x{6CCD}\x{6CCE}\x{6CCF}\x{6CD0}\x{6CD1}\x{6CD2}\x{6CD3}\x{6CD4}\x{6CD5}' + . '\x{6CD6}\x{6CD7}\x{6CD9}\x{6CDA}\x{6CDB}\x{6CDC}\x{6CDD}\x{6CDE}\x{6CDF}' + . '\x{6CE0}\x{6CE1}\x{6CE2}\x{6CE3}\x{6CE4}\x{6CE5}\x{6CE6}\x{6CE7}\x{6CE8}' + . '\x{6CE9}\x{6CEA}\x{6CEB}\x{6CEC}\x{6CED}\x{6CEE}\x{6CEF}\x{6CF0}\x{6CF1}' + . '\x{6CF2}\x{6CF3}\x{6CF5}\x{6CF6}\x{6CF7}\x{6CF8}\x{6CF9}\x{6CFA}\x{6CFB}' + . '\x{6CFC}\x{6CFD}\x{6CFE}\x{6CFF}\x{6D00}\x{6D01}\x{6D03}\x{6D04}\x{6D05}' + . '\x{6D06}\x{6D07}\x{6D08}\x{6D09}\x{6D0A}\x{6D0B}\x{6D0C}\x{6D0D}\x{6D0E}' + . '\x{6D0F}\x{6D10}\x{6D11}\x{6D12}\x{6D13}\x{6D14}\x{6D15}\x{6D16}\x{6D17}' + . '\x{6D18}\x{6D19}\x{6D1A}\x{6D1B}\x{6D1D}\x{6D1E}\x{6D1F}\x{6D20}\x{6D21}' + . '\x{6D22}\x{6D23}\x{6D25}\x{6D26}\x{6D27}\x{6D28}\x{6D29}\x{6D2A}\x{6D2B}' + . '\x{6D2C}\x{6D2D}\x{6D2E}\x{6D2F}\x{6D30}\x{6D31}\x{6D32}\x{6D33}\x{6D34}' + . '\x{6D35}\x{6D36}\x{6D37}\x{6D38}\x{6D39}\x{6D3A}\x{6D3B}\x{6D3C}\x{6D3D}' + . '\x{6D3E}\x{6D3F}\x{6D40}\x{6D41}\x{6D42}\x{6D43}\x{6D44}\x{6D45}\x{6D46}' + . '\x{6D47}\x{6D48}\x{6D49}\x{6D4A}\x{6D4B}\x{6D4C}\x{6D4D}\x{6D4E}\x{6D4F}' + . '\x{6D50}\x{6D51}\x{6D52}\x{6D53}\x{6D54}\x{6D55}\x{6D56}\x{6D57}\x{6D58}' + . '\x{6D59}\x{6D5A}\x{6D5B}\x{6D5C}\x{6D5D}\x{6D5E}\x{6D5F}\x{6D60}\x{6D61}' + . '\x{6D62}\x{6D63}\x{6D64}\x{6D65}\x{6D66}\x{6D67}\x{6D68}\x{6D69}\x{6D6A}' + . '\x{6D6B}\x{6D6C}\x{6D6D}\x{6D6E}\x{6D6F}\x{6D70}\x{6D72}\x{6D73}\x{6D74}' + . '\x{6D75}\x{6D76}\x{6D77}\x{6D78}\x{6D79}\x{6D7A}\x{6D7B}\x{6D7C}\x{6D7D}' + . '\x{6D7E}\x{6D7F}\x{6D80}\x{6D82}\x{6D83}\x{6D84}\x{6D85}\x{6D86}\x{6D87}' + . '\x{6D88}\x{6D89}\x{6D8A}\x{6D8B}\x{6D8C}\x{6D8D}\x{6D8E}\x{6D8F}\x{6D90}' + . '\x{6D91}\x{6D92}\x{6D93}\x{6D94}\x{6D95}\x{6D97}\x{6D98}\x{6D99}\x{6D9A}' + . '\x{6D9B}\x{6D9D}\x{6D9E}\x{6D9F}\x{6DA0}\x{6DA1}\x{6DA2}\x{6DA3}\x{6DA4}' + . '\x{6DA5}\x{6DA6}\x{6DA7}\x{6DA8}\x{6DA9}\x{6DAA}\x{6DAB}\x{6DAC}\x{6DAD}' + . '\x{6DAE}\x{6DAF}\x{6DB2}\x{6DB3}\x{6DB4}\x{6DB5}\x{6DB7}\x{6DB8}\x{6DB9}' + . '\x{6DBA}\x{6DBB}\x{6DBC}\x{6DBD}\x{6DBE}\x{6DBF}\x{6DC0}\x{6DC1}\x{6DC2}' + . '\x{6DC3}\x{6DC4}\x{6DC5}\x{6DC6}\x{6DC7}\x{6DC8}\x{6DC9}\x{6DCA}\x{6DCB}' + . '\x{6DCC}\x{6DCD}\x{6DCE}\x{6DCF}\x{6DD0}\x{6DD1}\x{6DD2}\x{6DD3}\x{6DD4}' + . '\x{6DD5}\x{6DD6}\x{6DD7}\x{6DD8}\x{6DD9}\x{6DDA}\x{6DDB}\x{6DDC}\x{6DDD}' + . '\x{6DDE}\x{6DDF}\x{6DE0}\x{6DE1}\x{6DE2}\x{6DE3}\x{6DE4}\x{6DE5}\x{6DE6}' + . '\x{6DE7}\x{6DE8}\x{6DE9}\x{6DEA}\x{6DEB}\x{6DEC}\x{6DED}\x{6DEE}\x{6DEF}' + . '\x{6DF0}\x{6DF1}\x{6DF2}\x{6DF3}\x{6DF4}\x{6DF5}\x{6DF6}\x{6DF7}\x{6DF8}' + . '\x{6DF9}\x{6DFA}\x{6DFB}\x{6DFC}\x{6DFD}\x{6E00}\x{6E03}\x{6E04}\x{6E05}' + . '\x{6E07}\x{6E08}\x{6E09}\x{6E0A}\x{6E0B}\x{6E0C}\x{6E0D}\x{6E0E}\x{6E0F}' + . '\x{6E10}\x{6E11}\x{6E14}\x{6E15}\x{6E16}\x{6E17}\x{6E19}\x{6E1A}\x{6E1B}' + . '\x{6E1C}\x{6E1D}\x{6E1E}\x{6E1F}\x{6E20}\x{6E21}\x{6E22}\x{6E23}\x{6E24}' + . '\x{6E25}\x{6E26}\x{6E27}\x{6E28}\x{6E29}\x{6E2B}\x{6E2C}\x{6E2D}\x{6E2E}' + . '\x{6E2F}\x{6E30}\x{6E31}\x{6E32}\x{6E33}\x{6E34}\x{6E35}\x{6E36}\x{6E37}' + . '\x{6E38}\x{6E39}\x{6E3A}\x{6E3B}\x{6E3C}\x{6E3D}\x{6E3E}\x{6E3F}\x{6E40}' + . '\x{6E41}\x{6E42}\x{6E43}\x{6E44}\x{6E45}\x{6E46}\x{6E47}\x{6E48}\x{6E49}' + . '\x{6E4A}\x{6E4B}\x{6E4D}\x{6E4E}\x{6E4F}\x{6E50}\x{6E51}\x{6E52}\x{6E53}' + . '\x{6E54}\x{6E55}\x{6E56}\x{6E57}\x{6E58}\x{6E59}\x{6E5A}\x{6E5B}\x{6E5C}' + . '\x{6E5D}\x{6E5E}\x{6E5F}\x{6E60}\x{6E61}\x{6E62}\x{6E63}\x{6E64}\x{6E65}' + . '\x{6E66}\x{6E67}\x{6E68}\x{6E69}\x{6E6A}\x{6E6B}\x{6E6D}\x{6E6E}\x{6E6F}' + . '\x{6E70}\x{6E71}\x{6E72}\x{6E73}\x{6E74}\x{6E75}\x{6E77}\x{6E78}\x{6E79}' + . '\x{6E7E}\x{6E7F}\x{6E80}\x{6E81}\x{6E82}\x{6E83}\x{6E84}\x{6E85}\x{6E86}' + . '\x{6E87}\x{6E88}\x{6E89}\x{6E8A}\x{6E8D}\x{6E8E}\x{6E8F}\x{6E90}\x{6E91}' + . '\x{6E92}\x{6E93}\x{6E94}\x{6E96}\x{6E97}\x{6E98}\x{6E99}\x{6E9A}\x{6E9B}' + . '\x{6E9C}\x{6E9D}\x{6E9E}\x{6E9F}\x{6EA0}\x{6EA1}\x{6EA2}\x{6EA3}\x{6EA4}' + . '\x{6EA5}\x{6EA6}\x{6EA7}\x{6EA8}\x{6EA9}\x{6EAA}\x{6EAB}\x{6EAC}\x{6EAD}' + . '\x{6EAE}\x{6EAF}\x{6EB0}\x{6EB1}\x{6EB2}\x{6EB3}\x{6EB4}\x{6EB5}\x{6EB6}' + . '\x{6EB7}\x{6EB8}\x{6EB9}\x{6EBA}\x{6EBB}\x{6EBC}\x{6EBD}\x{6EBE}\x{6EBF}' + . '\x{6EC0}\x{6EC1}\x{6EC2}\x{6EC3}\x{6EC4}\x{6EC5}\x{6EC6}\x{6EC7}\x{6EC8}' + . '\x{6EC9}\x{6ECA}\x{6ECB}\x{6ECC}\x{6ECD}\x{6ECE}\x{6ECF}\x{6ED0}\x{6ED1}' + . '\x{6ED2}\x{6ED3}\x{6ED4}\x{6ED5}\x{6ED6}\x{6ED7}\x{6ED8}\x{6ED9}\x{6EDA}' + . '\x{6EDC}\x{6EDE}\x{6EDF}\x{6EE0}\x{6EE1}\x{6EE2}\x{6EE4}\x{6EE5}\x{6EE6}' + . '\x{6EE7}\x{6EE8}\x{6EE9}\x{6EEA}\x{6EEB}\x{6EEC}\x{6EED}\x{6EEE}\x{6EEF}' + . '\x{6EF0}\x{6EF1}\x{6EF2}\x{6EF3}\x{6EF4}\x{6EF5}\x{6EF6}\x{6EF7}\x{6EF8}' + . '\x{6EF9}\x{6EFA}\x{6EFB}\x{6EFC}\x{6EFD}\x{6EFE}\x{6EFF}\x{6F00}\x{6F01}' + . '\x{6F02}\x{6F03}\x{6F05}\x{6F06}\x{6F07}\x{6F08}\x{6F09}\x{6F0A}\x{6F0C}' + . '\x{6F0D}\x{6F0E}\x{6F0F}\x{6F10}\x{6F11}\x{6F12}\x{6F13}\x{6F14}\x{6F15}' + . '\x{6F16}\x{6F17}\x{6F18}\x{6F19}\x{6F1A}\x{6F1B}\x{6F1C}\x{6F1D}\x{6F1E}' + . '\x{6F1F}\x{6F20}\x{6F21}\x{6F22}\x{6F23}\x{6F24}\x{6F25}\x{6F26}\x{6F27}' + . '\x{6F28}\x{6F29}\x{6F2A}\x{6F2B}\x{6F2C}\x{6F2D}\x{6F2E}\x{6F2F}\x{6F30}' + . '\x{6F31}\x{6F32}\x{6F33}\x{6F34}\x{6F35}\x{6F36}\x{6F37}\x{6F38}\x{6F39}' + . '\x{6F3A}\x{6F3B}\x{6F3C}\x{6F3D}\x{6F3E}\x{6F3F}\x{6F40}\x{6F41}\x{6F43}' + . '\x{6F44}\x{6F45}\x{6F46}\x{6F47}\x{6F49}\x{6F4B}\x{6F4C}\x{6F4D}\x{6F4E}' + . '\x{6F4F}\x{6F50}\x{6F51}\x{6F52}\x{6F53}\x{6F54}\x{6F55}\x{6F56}\x{6F57}' + . '\x{6F58}\x{6F59}\x{6F5A}\x{6F5B}\x{6F5C}\x{6F5D}\x{6F5E}\x{6F5F}\x{6F60}' + . '\x{6F61}\x{6F62}\x{6F63}\x{6F64}\x{6F65}\x{6F66}\x{6F67}\x{6F68}\x{6F69}' + . '\x{6F6A}\x{6F6B}\x{6F6C}\x{6F6D}\x{6F6E}\x{6F6F}\x{6F70}\x{6F71}\x{6F72}' + . '\x{6F73}\x{6F74}\x{6F75}\x{6F76}\x{6F77}\x{6F78}\x{6F7A}\x{6F7B}\x{6F7C}' + . '\x{6F7D}\x{6F7E}\x{6F7F}\x{6F80}\x{6F81}\x{6F82}\x{6F83}\x{6F84}\x{6F85}' + . '\x{6F86}\x{6F87}\x{6F88}\x{6F89}\x{6F8A}\x{6F8B}\x{6F8C}\x{6F8D}\x{6F8E}' + . '\x{6F8F}\x{6F90}\x{6F91}\x{6F92}\x{6F93}\x{6F94}\x{6F95}\x{6F96}\x{6F97}' + . '\x{6F99}\x{6F9B}\x{6F9C}\x{6F9D}\x{6F9E}\x{6FA0}\x{6FA1}\x{6FA2}\x{6FA3}' + . '\x{6FA4}\x{6FA5}\x{6FA6}\x{6FA7}\x{6FA8}\x{6FA9}\x{6FAA}\x{6FAB}\x{6FAC}' + . '\x{6FAD}\x{6FAE}\x{6FAF}\x{6FB0}\x{6FB1}\x{6FB2}\x{6FB3}\x{6FB4}\x{6FB5}' + . '\x{6FB6}\x{6FB8}\x{6FB9}\x{6FBA}\x{6FBB}\x{6FBC}\x{6FBD}\x{6FBE}\x{6FBF}' + . '\x{6FC0}\x{6FC1}\x{6FC2}\x{6FC3}\x{6FC4}\x{6FC6}\x{6FC7}\x{6FC8}\x{6FC9}' + . '\x{6FCA}\x{6FCB}\x{6FCC}\x{6FCD}\x{6FCE}\x{6FCF}\x{6FD1}\x{6FD2}\x{6FD4}' + . '\x{6FD5}\x{6FD6}\x{6FD7}\x{6FD8}\x{6FD9}\x{6FDA}\x{6FDB}\x{6FDC}\x{6FDD}' + . '\x{6FDE}\x{6FDF}\x{6FE0}\x{6FE1}\x{6FE2}\x{6FE3}\x{6FE4}\x{6FE5}\x{6FE6}' + . '\x{6FE7}\x{6FE8}\x{6FE9}\x{6FEA}\x{6FEB}\x{6FEC}\x{6FED}\x{6FEE}\x{6FEF}' + . '\x{6FF0}\x{6FF1}\x{6FF2}\x{6FF3}\x{6FF4}\x{6FF6}\x{6FF7}\x{6FF8}\x{6FF9}' + . '\x{6FFA}\x{6FFB}\x{6FFC}\x{6FFE}\x{6FFF}\x{7000}\x{7001}\x{7002}\x{7003}' + . '\x{7004}\x{7005}\x{7006}\x{7007}\x{7008}\x{7009}\x{700A}\x{700B}\x{700C}' + . '\x{700D}\x{700E}\x{700F}\x{7011}\x{7012}\x{7014}\x{7015}\x{7016}\x{7017}' + . '\x{7018}\x{7019}\x{701A}\x{701B}\x{701C}\x{701D}\x{701F}\x{7020}\x{7021}' + . '\x{7022}\x{7023}\x{7024}\x{7025}\x{7026}\x{7027}\x{7028}\x{7029}\x{702A}' + . '\x{702B}\x{702C}\x{702D}\x{702E}\x{702F}\x{7030}\x{7031}\x{7032}\x{7033}' + . '\x{7034}\x{7035}\x{7036}\x{7037}\x{7038}\x{7039}\x{703A}\x{703B}\x{703C}' + . '\x{703D}\x{703E}\x{703F}\x{7040}\x{7041}\x{7042}\x{7043}\x{7044}\x{7045}' + . '\x{7046}\x{7048}\x{7049}\x{704A}\x{704C}\x{704D}\x{704F}\x{7050}\x{7051}' + . '\x{7052}\x{7053}\x{7054}\x{7055}\x{7056}\x{7057}\x{7058}\x{7059}\x{705A}' + . '\x{705B}\x{705C}\x{705D}\x{705E}\x{705F}\x{7060}\x{7061}\x{7062}\x{7063}' + . '\x{7064}\x{7065}\x{7066}\x{7067}\x{7068}\x{7069}\x{706A}\x{706B}\x{706C}' + . '\x{706D}\x{706E}\x{706F}\x{7070}\x{7071}\x{7074}\x{7075}\x{7076}\x{7077}' + . '\x{7078}\x{7079}\x{707A}\x{707C}\x{707D}\x{707E}\x{707F}\x{7080}\x{7082}' + . '\x{7083}\x{7084}\x{7085}\x{7086}\x{7087}\x{7088}\x{7089}\x{708A}\x{708B}' + . '\x{708C}\x{708E}\x{708F}\x{7090}\x{7091}\x{7092}\x{7093}\x{7094}\x{7095}' + . '\x{7096}\x{7098}\x{7099}\x{709A}\x{709C}\x{709D}\x{709E}\x{709F}\x{70A0}' + . '\x{70A1}\x{70A2}\x{70A3}\x{70A4}\x{70A5}\x{70A6}\x{70A7}\x{70A8}\x{70A9}' + . '\x{70AB}\x{70AC}\x{70AD}\x{70AE}\x{70AF}\x{70B0}\x{70B1}\x{70B3}\x{70B4}' + . '\x{70B5}\x{70B7}\x{70B8}\x{70B9}\x{70BA}\x{70BB}\x{70BC}\x{70BD}\x{70BE}' + . '\x{70BF}\x{70C0}\x{70C1}\x{70C2}\x{70C3}\x{70C4}\x{70C5}\x{70C6}\x{70C7}' + . '\x{70C8}\x{70C9}\x{70CA}\x{70CB}\x{70CC}\x{70CD}\x{70CE}\x{70CF}\x{70D0}' + . '\x{70D1}\x{70D2}\x{70D3}\x{70D4}\x{70D6}\x{70D7}\x{70D8}\x{70D9}\x{70DA}' + . '\x{70DB}\x{70DC}\x{70DD}\x{70DE}\x{70DF}\x{70E0}\x{70E1}\x{70E2}\x{70E3}' + . '\x{70E4}\x{70E5}\x{70E6}\x{70E7}\x{70E8}\x{70E9}\x{70EA}\x{70EB}\x{70EC}' + . '\x{70ED}\x{70EE}\x{70EF}\x{70F0}\x{70F1}\x{70F2}\x{70F3}\x{70F4}\x{70F5}' + . '\x{70F6}\x{70F7}\x{70F8}\x{70F9}\x{70FA}\x{70FB}\x{70FC}\x{70FD}\x{70FF}' + . '\x{7100}\x{7101}\x{7102}\x{7103}\x{7104}\x{7105}\x{7106}\x{7107}\x{7109}' + . '\x{710A}\x{710B}\x{710C}\x{710D}\x{710E}\x{710F}\x{7110}\x{7111}\x{7112}' + . '\x{7113}\x{7115}\x{7116}\x{7117}\x{7118}\x{7119}\x{711A}\x{711B}\x{711C}' + . '\x{711D}\x{711E}\x{711F}\x{7120}\x{7121}\x{7122}\x{7123}\x{7125}\x{7126}' + . '\x{7127}\x{7128}\x{7129}\x{712A}\x{712B}\x{712C}\x{712D}\x{712E}\x{712F}' + . '\x{7130}\x{7131}\x{7132}\x{7135}\x{7136}\x{7137}\x{7138}\x{7139}\x{713A}' + . '\x{713B}\x{713D}\x{713E}\x{713F}\x{7140}\x{7141}\x{7142}\x{7143}\x{7144}' + . '\x{7145}\x{7146}\x{7147}\x{7148}\x{7149}\x{714A}\x{714B}\x{714C}\x{714D}' + . '\x{714E}\x{714F}\x{7150}\x{7151}\x{7152}\x{7153}\x{7154}\x{7156}\x{7158}' + . '\x{7159}\x{715A}\x{715B}\x{715C}\x{715D}\x{715E}\x{715F}\x{7160}\x{7161}' + . '\x{7162}\x{7163}\x{7164}\x{7165}\x{7166}\x{7167}\x{7168}\x{7169}\x{716A}' + . '\x{716C}\x{716E}\x{716F}\x{7170}\x{7171}\x{7172}\x{7173}\x{7174}\x{7175}' + . '\x{7176}\x{7177}\x{7178}\x{7179}\x{717A}\x{717B}\x{717C}\x{717D}\x{717E}' + . '\x{717F}\x{7180}\x{7181}\x{7182}\x{7183}\x{7184}\x{7185}\x{7186}\x{7187}' + . '\x{7188}\x{7189}\x{718A}\x{718B}\x{718C}\x{718E}\x{718F}\x{7190}\x{7191}' + . '\x{7192}\x{7193}\x{7194}\x{7195}\x{7197}\x{7198}\x{7199}\x{719A}\x{719B}' + . '\x{719C}\x{719D}\x{719E}\x{719F}\x{71A0}\x{71A1}\x{71A2}\x{71A3}\x{71A4}' + . '\x{71A5}\x{71A7}\x{71A8}\x{71A9}\x{71AA}\x{71AC}\x{71AD}\x{71AE}\x{71AF}' + . '\x{71B0}\x{71B1}\x{71B2}\x{71B3}\x{71B4}\x{71B5}\x{71B7}\x{71B8}\x{71B9}' + . '\x{71BA}\x{71BB}\x{71BC}\x{71BD}\x{71BE}\x{71BF}\x{71C0}\x{71C1}\x{71C2}' + . '\x{71C3}\x{71C4}\x{71C5}\x{71C6}\x{71C7}\x{71C8}\x{71C9}\x{71CA}\x{71CB}' + . '\x{71CD}\x{71CE}\x{71CF}\x{71D0}\x{71D1}\x{71D2}\x{71D4}\x{71D5}\x{71D6}' + . '\x{71D7}\x{71D8}\x{71D9}\x{71DA}\x{71DB}\x{71DC}\x{71DD}\x{71DE}\x{71DF}' + . '\x{71E0}\x{71E1}\x{71E2}\x{71E3}\x{71E4}\x{71E5}\x{71E6}\x{71E7}\x{71E8}' + . '\x{71E9}\x{71EA}\x{71EB}\x{71EC}\x{71ED}\x{71EE}\x{71EF}\x{71F0}\x{71F1}' + . '\x{71F2}\x{71F4}\x{71F5}\x{71F6}\x{71F7}\x{71F8}\x{71F9}\x{71FB}\x{71FC}' + . '\x{71FD}\x{71FE}\x{71FF}\x{7201}\x{7202}\x{7203}\x{7204}\x{7205}\x{7206}' + . '\x{7207}\x{7208}\x{7209}\x{720A}\x{720C}\x{720D}\x{720E}\x{720F}\x{7210}' + . '\x{7212}\x{7213}\x{7214}\x{7216}\x{7218}\x{7219}\x{721A}\x{721B}\x{721C}' + . '\x{721D}\x{721E}\x{721F}\x{7221}\x{7222}\x{7223}\x{7226}\x{7227}\x{7228}' + . '\x{7229}\x{722A}\x{722B}\x{722C}\x{722D}\x{722E}\x{7230}\x{7231}\x{7232}' + . '\x{7233}\x{7235}\x{7236}\x{7237}\x{7238}\x{7239}\x{723A}\x{723B}\x{723C}' + . '\x{723D}\x{723E}\x{723F}\x{7240}\x{7241}\x{7242}\x{7243}\x{7244}\x{7246}' + . '\x{7247}\x{7248}\x{7249}\x{724A}\x{724B}\x{724C}\x{724D}\x{724F}\x{7251}' + . '\x{7252}\x{7253}\x{7254}\x{7256}\x{7257}\x{7258}\x{7259}\x{725A}\x{725B}' + . '\x{725C}\x{725D}\x{725E}\x{725F}\x{7260}\x{7261}\x{7262}\x{7263}\x{7264}' + . '\x{7265}\x{7266}\x{7267}\x{7268}\x{7269}\x{726A}\x{726B}\x{726C}\x{726D}' + . '\x{726E}\x{726F}\x{7270}\x{7271}\x{7272}\x{7273}\x{7274}\x{7275}\x{7276}' + . '\x{7277}\x{7278}\x{7279}\x{727A}\x{727B}\x{727C}\x{727D}\x{727E}\x{727F}' + . '\x{7280}\x{7281}\x{7282}\x{7283}\x{7284}\x{7285}\x{7286}\x{7287}\x{7288}' + . '\x{7289}\x{728A}\x{728B}\x{728C}\x{728D}\x{728E}\x{728F}\x{7290}\x{7291}' + . '\x{7292}\x{7293}\x{7294}\x{7295}\x{7296}\x{7297}\x{7298}\x{7299}\x{729A}' + . '\x{729B}\x{729C}\x{729D}\x{729E}\x{729F}\x{72A1}\x{72A2}\x{72A3}\x{72A4}' + . '\x{72A5}\x{72A6}\x{72A7}\x{72A8}\x{72A9}\x{72AA}\x{72AC}\x{72AD}\x{72AE}' + . '\x{72AF}\x{72B0}\x{72B1}\x{72B2}\x{72B3}\x{72B4}\x{72B5}\x{72B6}\x{72B7}' + . '\x{72B8}\x{72B9}\x{72BA}\x{72BB}\x{72BC}\x{72BD}\x{72BF}\x{72C0}\x{72C1}' + . '\x{72C2}\x{72C3}\x{72C4}\x{72C5}\x{72C6}\x{72C7}\x{72C8}\x{72C9}\x{72CA}' + . '\x{72CB}\x{72CC}\x{72CD}\x{72CE}\x{72CF}\x{72D0}\x{72D1}\x{72D2}\x{72D3}' + . '\x{72D4}\x{72D5}\x{72D6}\x{72D7}\x{72D8}\x{72D9}\x{72DA}\x{72DB}\x{72DC}' + . '\x{72DD}\x{72DE}\x{72DF}\x{72E0}\x{72E1}\x{72E2}\x{72E3}\x{72E4}\x{72E5}' + . '\x{72E6}\x{72E7}\x{72E8}\x{72E9}\x{72EA}\x{72EB}\x{72EC}\x{72ED}\x{72EE}' + . '\x{72EF}\x{72F0}\x{72F1}\x{72F2}\x{72F3}\x{72F4}\x{72F5}\x{72F6}\x{72F7}' + . '\x{72F8}\x{72F9}\x{72FA}\x{72FB}\x{72FC}\x{72FD}\x{72FE}\x{72FF}\x{7300}' + . '\x{7301}\x{7303}\x{7304}\x{7305}\x{7306}\x{7307}\x{7308}\x{7309}\x{730A}' + . '\x{730B}\x{730C}\x{730D}\x{730E}\x{730F}\x{7311}\x{7312}\x{7313}\x{7314}' + . '\x{7315}\x{7316}\x{7317}\x{7318}\x{7319}\x{731A}\x{731B}\x{731C}\x{731D}' + . '\x{731E}\x{7320}\x{7321}\x{7322}\x{7323}\x{7324}\x{7325}\x{7326}\x{7327}' + . '\x{7329}\x{732A}\x{732B}\x{732C}\x{732D}\x{732E}\x{7330}\x{7331}\x{7332}' + . '\x{7333}\x{7334}\x{7335}\x{7336}\x{7337}\x{7338}\x{7339}\x{733A}\x{733B}' + . '\x{733C}\x{733D}\x{733E}\x{733F}\x{7340}\x{7341}\x{7342}\x{7343}\x{7344}' + . '\x{7345}\x{7346}\x{7347}\x{7348}\x{7349}\x{734A}\x{734B}\x{734C}\x{734D}' + . '\x{734E}\x{7350}\x{7351}\x{7352}\x{7354}\x{7355}\x{7356}\x{7357}\x{7358}' + . '\x{7359}\x{735A}\x{735B}\x{735C}\x{735D}\x{735E}\x{735F}\x{7360}\x{7361}' + . '\x{7362}\x{7364}\x{7365}\x{7366}\x{7367}\x{7368}\x{7369}\x{736A}\x{736B}' + . '\x{736C}\x{736D}\x{736E}\x{736F}\x{7370}\x{7371}\x{7372}\x{7373}\x{7374}' + . '\x{7375}\x{7376}\x{7377}\x{7378}\x{7379}\x{737A}\x{737B}\x{737C}\x{737D}' + . '\x{737E}\x{737F}\x{7380}\x{7381}\x{7382}\x{7383}\x{7384}\x{7385}\x{7386}' + . '\x{7387}\x{7388}\x{7389}\x{738A}\x{738B}\x{738C}\x{738D}\x{738E}\x{738F}' + . '\x{7390}\x{7391}\x{7392}\x{7393}\x{7394}\x{7395}\x{7396}\x{7397}\x{7398}' + . '\x{7399}\x{739A}\x{739B}\x{739D}\x{739E}\x{739F}\x{73A0}\x{73A1}\x{73A2}' + . '\x{73A3}\x{73A4}\x{73A5}\x{73A6}\x{73A7}\x{73A8}\x{73A9}\x{73AA}\x{73AB}' + . '\x{73AC}\x{73AD}\x{73AE}\x{73AF}\x{73B0}\x{73B1}\x{73B2}\x{73B3}\x{73B4}' + . '\x{73B5}\x{73B6}\x{73B7}\x{73B8}\x{73B9}\x{73BA}\x{73BB}\x{73BC}\x{73BD}' + . '\x{73BE}\x{73BF}\x{73C0}\x{73C2}\x{73C3}\x{73C4}\x{73C5}\x{73C6}\x{73C7}' + . '\x{73C8}\x{73C9}\x{73CA}\x{73CB}\x{73CC}\x{73CD}\x{73CE}\x{73CF}\x{73D0}' + . '\x{73D1}\x{73D2}\x{73D3}\x{73D4}\x{73D5}\x{73D6}\x{73D7}\x{73D8}\x{73D9}' + . '\x{73DA}\x{73DB}\x{73DC}\x{73DD}\x{73DE}\x{73DF}\x{73E0}\x{73E2}\x{73E3}' + . '\x{73E5}\x{73E6}\x{73E7}\x{73E8}\x{73E9}\x{73EA}\x{73EB}\x{73EC}\x{73ED}' + . '\x{73EE}\x{73EF}\x{73F0}\x{73F1}\x{73F2}\x{73F4}\x{73F5}\x{73F6}\x{73F7}' + . '\x{73F8}\x{73F9}\x{73FA}\x{73FC}\x{73FD}\x{73FE}\x{73FF}\x{7400}\x{7401}' + . '\x{7402}\x{7403}\x{7404}\x{7405}\x{7406}\x{7407}\x{7408}\x{7409}\x{740A}' + . '\x{740B}\x{740C}\x{740D}\x{740E}\x{740F}\x{7410}\x{7411}\x{7412}\x{7413}' + . '\x{7414}\x{7415}\x{7416}\x{7417}\x{7419}\x{741A}\x{741B}\x{741C}\x{741D}' + . '\x{741E}\x{741F}\x{7420}\x{7421}\x{7422}\x{7423}\x{7424}\x{7425}\x{7426}' + . '\x{7427}\x{7428}\x{7429}\x{742A}\x{742B}\x{742C}\x{742D}\x{742E}\x{742F}' + . '\x{7430}\x{7431}\x{7432}\x{7433}\x{7434}\x{7435}\x{7436}\x{7437}\x{7438}' + . '\x{743A}\x{743B}\x{743C}\x{743D}\x{743F}\x{7440}\x{7441}\x{7442}\x{7443}' + . '\x{7444}\x{7445}\x{7446}\x{7448}\x{744A}\x{744B}\x{744C}\x{744D}\x{744E}' + . '\x{744F}\x{7450}\x{7451}\x{7452}\x{7453}\x{7454}\x{7455}\x{7456}\x{7457}' + . '\x{7459}\x{745A}\x{745B}\x{745C}\x{745D}\x{745E}\x{745F}\x{7461}\x{7462}' + . '\x{7463}\x{7464}\x{7465}\x{7466}\x{7467}\x{7468}\x{7469}\x{746A}\x{746B}' + . '\x{746C}\x{746D}\x{746E}\x{746F}\x{7470}\x{7471}\x{7472}\x{7473}\x{7474}' + . '\x{7475}\x{7476}\x{7477}\x{7478}\x{7479}\x{747A}\x{747C}\x{747D}\x{747E}' + . '\x{747F}\x{7480}\x{7481}\x{7482}\x{7483}\x{7485}\x{7486}\x{7487}\x{7488}' + . '\x{7489}\x{748A}\x{748B}\x{748C}\x{748D}\x{748E}\x{748F}\x{7490}\x{7491}' + . '\x{7492}\x{7493}\x{7494}\x{7495}\x{7497}\x{7498}\x{7499}\x{749A}\x{749B}' + . '\x{749C}\x{749E}\x{749F}\x{74A0}\x{74A1}\x{74A3}\x{74A4}\x{74A5}\x{74A6}' + . '\x{74A7}\x{74A8}\x{74A9}\x{74AA}\x{74AB}\x{74AC}\x{74AD}\x{74AE}\x{74AF}' + . '\x{74B0}\x{74B1}\x{74B2}\x{74B3}\x{74B4}\x{74B5}\x{74B6}\x{74B7}\x{74B8}' + . '\x{74B9}\x{74BA}\x{74BB}\x{74BC}\x{74BD}\x{74BE}\x{74BF}\x{74C0}\x{74C1}' + . '\x{74C2}\x{74C3}\x{74C4}\x{74C5}\x{74C6}\x{74CA}\x{74CB}\x{74CD}\x{74CE}' + . '\x{74CF}\x{74D0}\x{74D1}\x{74D2}\x{74D3}\x{74D4}\x{74D5}\x{74D6}\x{74D7}' + . '\x{74D8}\x{74D9}\x{74DA}\x{74DB}\x{74DC}\x{74DD}\x{74DE}\x{74DF}\x{74E0}' + . '\x{74E1}\x{74E2}\x{74E3}\x{74E4}\x{74E5}\x{74E6}\x{74E7}\x{74E8}\x{74E9}' + . '\x{74EA}\x{74EC}\x{74ED}\x{74EE}\x{74EF}\x{74F0}\x{74F1}\x{74F2}\x{74F3}' + . '\x{74F4}\x{74F5}\x{74F6}\x{74F7}\x{74F8}\x{74F9}\x{74FA}\x{74FB}\x{74FC}' + . '\x{74FD}\x{74FE}\x{74FF}\x{7500}\x{7501}\x{7502}\x{7503}\x{7504}\x{7505}' + . '\x{7506}\x{7507}\x{7508}\x{7509}\x{750A}\x{750B}\x{750C}\x{750D}\x{750F}' + . '\x{7510}\x{7511}\x{7512}\x{7513}\x{7514}\x{7515}\x{7516}\x{7517}\x{7518}' + . '\x{7519}\x{751A}\x{751B}\x{751C}\x{751D}\x{751E}\x{751F}\x{7521}\x{7522}' + . '\x{7523}\x{7524}\x{7525}\x{7526}\x{7527}\x{7528}\x{7529}\x{752A}\x{752B}' + . '\x{752C}\x{752D}\x{752E}\x{752F}\x{7530}\x{7531}\x{7532}\x{7533}\x{7535}' + . '\x{7536}\x{7537}\x{7538}\x{7539}\x{753A}\x{753B}\x{753C}\x{753D}\x{753E}' + . '\x{753F}\x{7540}\x{7542}\x{7543}\x{7544}\x{7545}\x{7546}\x{7547}\x{7548}' + . '\x{7549}\x{754B}\x{754C}\x{754D}\x{754E}\x{754F}\x{7550}\x{7551}\x{7553}' + . '\x{7554}\x{7556}\x{7557}\x{7558}\x{7559}\x{755A}\x{755B}\x{755C}\x{755D}' + . '\x{755F}\x{7560}\x{7562}\x{7563}\x{7564}\x{7565}\x{7566}\x{7567}\x{7568}' + . '\x{7569}\x{756A}\x{756B}\x{756C}\x{756D}\x{756E}\x{756F}\x{7570}\x{7572}' + . '\x{7574}\x{7575}\x{7576}\x{7577}\x{7578}\x{7579}\x{757C}\x{757D}\x{757E}' + . '\x{757F}\x{7580}\x{7581}\x{7582}\x{7583}\x{7584}\x{7586}\x{7587}\x{7588}' + . '\x{7589}\x{758A}\x{758B}\x{758C}\x{758D}\x{758F}\x{7590}\x{7591}\x{7592}' + . '\x{7593}\x{7594}\x{7595}\x{7596}\x{7597}\x{7598}\x{7599}\x{759A}\x{759B}' + . '\x{759C}\x{759D}\x{759E}\x{759F}\x{75A0}\x{75A1}\x{75A2}\x{75A3}\x{75A4}' + . '\x{75A5}\x{75A6}\x{75A7}\x{75A8}\x{75AA}\x{75AB}\x{75AC}\x{75AD}\x{75AE}' + . '\x{75AF}\x{75B0}\x{75B1}\x{75B2}\x{75B3}\x{75B4}\x{75B5}\x{75B6}\x{75B8}' + . '\x{75B9}\x{75BA}\x{75BB}\x{75BC}\x{75BD}\x{75BE}\x{75BF}\x{75C0}\x{75C1}' + . '\x{75C2}\x{75C3}\x{75C4}\x{75C5}\x{75C6}\x{75C7}\x{75C8}\x{75C9}\x{75CA}' + . '\x{75CB}\x{75CC}\x{75CD}\x{75CE}\x{75CF}\x{75D0}\x{75D1}\x{75D2}\x{75D3}' + . '\x{75D4}\x{75D5}\x{75D6}\x{75D7}\x{75D8}\x{75D9}\x{75DA}\x{75DB}\x{75DD}' + . '\x{75DE}\x{75DF}\x{75E0}\x{75E1}\x{75E2}\x{75E3}\x{75E4}\x{75E5}\x{75E6}' + . '\x{75E7}\x{75E8}\x{75EA}\x{75EB}\x{75EC}\x{75ED}\x{75EF}\x{75F0}\x{75F1}' + . '\x{75F2}\x{75F3}\x{75F4}\x{75F5}\x{75F6}\x{75F7}\x{75F8}\x{75F9}\x{75FA}' + . '\x{75FB}\x{75FC}\x{75FD}\x{75FE}\x{75FF}\x{7600}\x{7601}\x{7602}\x{7603}' + . '\x{7604}\x{7605}\x{7606}\x{7607}\x{7608}\x{7609}\x{760A}\x{760B}\x{760C}' + . '\x{760D}\x{760E}\x{760F}\x{7610}\x{7611}\x{7612}\x{7613}\x{7614}\x{7615}' + . '\x{7616}\x{7617}\x{7618}\x{7619}\x{761A}\x{761B}\x{761C}\x{761D}\x{761E}' + . '\x{761F}\x{7620}\x{7621}\x{7622}\x{7623}\x{7624}\x{7625}\x{7626}\x{7627}' + . '\x{7628}\x{7629}\x{762A}\x{762B}\x{762D}\x{762E}\x{762F}\x{7630}\x{7631}' + . '\x{7632}\x{7633}\x{7634}\x{7635}\x{7636}\x{7637}\x{7638}\x{7639}\x{763A}' + . '\x{763B}\x{763C}\x{763D}\x{763E}\x{763F}\x{7640}\x{7641}\x{7642}\x{7643}' + . '\x{7646}\x{7647}\x{7648}\x{7649}\x{764A}\x{764B}\x{764C}\x{764D}\x{764F}' + . '\x{7650}\x{7652}\x{7653}\x{7654}\x{7656}\x{7657}\x{7658}\x{7659}\x{765A}' + . '\x{765B}\x{765C}\x{765D}\x{765E}\x{765F}\x{7660}\x{7661}\x{7662}\x{7663}' + . '\x{7664}\x{7665}\x{7666}\x{7667}\x{7668}\x{7669}\x{766A}\x{766B}\x{766C}' + . '\x{766D}\x{766E}\x{766F}\x{7670}\x{7671}\x{7672}\x{7674}\x{7675}\x{7676}' + . '\x{7677}\x{7678}\x{7679}\x{767B}\x{767C}\x{767D}\x{767E}\x{767F}\x{7680}' + . '\x{7681}\x{7682}\x{7683}\x{7684}\x{7685}\x{7686}\x{7687}\x{7688}\x{7689}' + . '\x{768A}\x{768B}\x{768C}\x{768E}\x{768F}\x{7690}\x{7691}\x{7692}\x{7693}' + . '\x{7694}\x{7695}\x{7696}\x{7697}\x{7698}\x{7699}\x{769A}\x{769B}\x{769C}' + . '\x{769D}\x{769E}\x{769F}\x{76A0}\x{76A3}\x{76A4}\x{76A6}\x{76A7}\x{76A9}' + . '\x{76AA}\x{76AB}\x{76AC}\x{76AD}\x{76AE}\x{76AF}\x{76B0}\x{76B1}\x{76B2}' + . '\x{76B4}\x{76B5}\x{76B7}\x{76B8}\x{76BA}\x{76BB}\x{76BC}\x{76BD}\x{76BE}' + . '\x{76BF}\x{76C0}\x{76C2}\x{76C3}\x{76C4}\x{76C5}\x{76C6}\x{76C7}\x{76C8}' + . '\x{76C9}\x{76CA}\x{76CD}\x{76CE}\x{76CF}\x{76D0}\x{76D1}\x{76D2}\x{76D3}' + . '\x{76D4}\x{76D5}\x{76D6}\x{76D7}\x{76D8}\x{76DA}\x{76DB}\x{76DC}\x{76DD}' + . '\x{76DE}\x{76DF}\x{76E0}\x{76E1}\x{76E2}\x{76E3}\x{76E4}\x{76E5}\x{76E6}' + . '\x{76E7}\x{76E8}\x{76E9}\x{76EA}\x{76EC}\x{76ED}\x{76EE}\x{76EF}\x{76F0}' + . '\x{76F1}\x{76F2}\x{76F3}\x{76F4}\x{76F5}\x{76F6}\x{76F7}\x{76F8}\x{76F9}' + . '\x{76FA}\x{76FB}\x{76FC}\x{76FD}\x{76FE}\x{76FF}\x{7701}\x{7703}\x{7704}' + . '\x{7705}\x{7706}\x{7707}\x{7708}\x{7709}\x{770A}\x{770B}\x{770C}\x{770D}' + . '\x{770F}\x{7710}\x{7711}\x{7712}\x{7713}\x{7714}\x{7715}\x{7716}\x{7717}' + . '\x{7718}\x{7719}\x{771A}\x{771B}\x{771C}\x{771D}\x{771E}\x{771F}\x{7720}' + . '\x{7722}\x{7723}\x{7725}\x{7726}\x{7727}\x{7728}\x{7729}\x{772A}\x{772C}' + . '\x{772D}\x{772E}\x{772F}\x{7730}\x{7731}\x{7732}\x{7733}\x{7734}\x{7735}' + . '\x{7736}\x{7737}\x{7738}\x{7739}\x{773A}\x{773B}\x{773C}\x{773D}\x{773E}' + . '\x{7740}\x{7741}\x{7743}\x{7744}\x{7745}\x{7746}\x{7747}\x{7748}\x{7749}' + . '\x{774A}\x{774B}\x{774C}\x{774D}\x{774E}\x{774F}\x{7750}\x{7751}\x{7752}' + . '\x{7753}\x{7754}\x{7755}\x{7756}\x{7757}\x{7758}\x{7759}\x{775A}\x{775B}' + . '\x{775C}\x{775D}\x{775E}\x{775F}\x{7760}\x{7761}\x{7762}\x{7763}\x{7765}' + . '\x{7766}\x{7767}\x{7768}\x{7769}\x{776A}\x{776B}\x{776C}\x{776D}\x{776E}' + . '\x{776F}\x{7770}\x{7771}\x{7772}\x{7773}\x{7774}\x{7775}\x{7776}\x{7777}' + . '\x{7778}\x{7779}\x{777A}\x{777B}\x{777C}\x{777D}\x{777E}\x{777F}\x{7780}' + . '\x{7781}\x{7782}\x{7783}\x{7784}\x{7785}\x{7786}\x{7787}\x{7788}\x{7789}' + . '\x{778A}\x{778B}\x{778C}\x{778D}\x{778E}\x{778F}\x{7790}\x{7791}\x{7792}' + . '\x{7793}\x{7794}\x{7795}\x{7797}\x{7798}\x{7799}\x{779A}\x{779B}\x{779C}' + . '\x{779D}\x{779E}\x{779F}\x{77A0}\x{77A1}\x{77A2}\x{77A3}\x{77A5}\x{77A6}' + . '\x{77A7}\x{77A8}\x{77A9}\x{77AA}\x{77AB}\x{77AC}\x{77AD}\x{77AE}\x{77AF}' + . '\x{77B0}\x{77B1}\x{77B2}\x{77B3}\x{77B4}\x{77B5}\x{77B6}\x{77B7}\x{77B8}' + . '\x{77B9}\x{77BA}\x{77BB}\x{77BC}\x{77BD}\x{77BF}\x{77C0}\x{77C2}\x{77C3}' + . '\x{77C4}\x{77C5}\x{77C6}\x{77C7}\x{77C8}\x{77C9}\x{77CA}\x{77CB}\x{77CC}' + . '\x{77CD}\x{77CE}\x{77CF}\x{77D0}\x{77D1}\x{77D3}\x{77D4}\x{77D5}\x{77D6}' + . '\x{77D7}\x{77D8}\x{77D9}\x{77DA}\x{77DB}\x{77DC}\x{77DE}\x{77DF}\x{77E0}' + . '\x{77E1}\x{77E2}\x{77E3}\x{77E5}\x{77E7}\x{77E8}\x{77E9}\x{77EA}\x{77EB}' + . '\x{77EC}\x{77ED}\x{77EE}\x{77EF}\x{77F0}\x{77F1}\x{77F2}\x{77F3}\x{77F6}' + . '\x{77F7}\x{77F8}\x{77F9}\x{77FA}\x{77FB}\x{77FC}\x{77FD}\x{77FE}\x{77FF}' + . '\x{7800}\x{7801}\x{7802}\x{7803}\x{7804}\x{7805}\x{7806}\x{7808}\x{7809}' + . '\x{780A}\x{780B}\x{780C}\x{780D}\x{780E}\x{780F}\x{7810}\x{7811}\x{7812}' + . '\x{7813}\x{7814}\x{7815}\x{7816}\x{7817}\x{7818}\x{7819}\x{781A}\x{781B}' + . '\x{781C}\x{781D}\x{781E}\x{781F}\x{7820}\x{7821}\x{7822}\x{7823}\x{7825}' + . '\x{7826}\x{7827}\x{7828}\x{7829}\x{782A}\x{782B}\x{782C}\x{782D}\x{782E}' + . '\x{782F}\x{7830}\x{7831}\x{7832}\x{7833}\x{7834}\x{7835}\x{7837}\x{7838}' + . '\x{7839}\x{783A}\x{783B}\x{783C}\x{783D}\x{783E}\x{7840}\x{7841}\x{7843}' + . '\x{7844}\x{7845}\x{7847}\x{7848}\x{7849}\x{784A}\x{784C}\x{784D}\x{784E}' + . '\x{7850}\x{7851}\x{7852}\x{7853}\x{7854}\x{7855}\x{7856}\x{7857}\x{7858}' + . '\x{7859}\x{785A}\x{785B}\x{785C}\x{785D}\x{785E}\x{785F}\x{7860}\x{7861}' + . '\x{7862}\x{7863}\x{7864}\x{7865}\x{7866}\x{7867}\x{7868}\x{7869}\x{786A}' + . '\x{786B}\x{786C}\x{786D}\x{786E}\x{786F}\x{7870}\x{7871}\x{7872}\x{7873}' + . '\x{7874}\x{7875}\x{7877}\x{7878}\x{7879}\x{787A}\x{787B}\x{787C}\x{787D}' + . '\x{787E}\x{787F}\x{7880}\x{7881}\x{7882}\x{7883}\x{7884}\x{7885}\x{7886}' + . '\x{7887}\x{7889}\x{788A}\x{788B}\x{788C}\x{788D}\x{788E}\x{788F}\x{7890}' + . '\x{7891}\x{7892}\x{7893}\x{7894}\x{7895}\x{7896}\x{7897}\x{7898}\x{7899}' + . '\x{789A}\x{789B}\x{789C}\x{789D}\x{789E}\x{789F}\x{78A0}\x{78A1}\x{78A2}' + . '\x{78A3}\x{78A4}\x{78A5}\x{78A6}\x{78A7}\x{78A8}\x{78A9}\x{78AA}\x{78AB}' + . '\x{78AC}\x{78AD}\x{78AE}\x{78AF}\x{78B0}\x{78B1}\x{78B2}\x{78B3}\x{78B4}' + . '\x{78B5}\x{78B6}\x{78B7}\x{78B8}\x{78B9}\x{78BA}\x{78BB}\x{78BC}\x{78BD}' + . '\x{78BE}\x{78BF}\x{78C0}\x{78C1}\x{78C3}\x{78C4}\x{78C5}\x{78C6}\x{78C8}' + . '\x{78C9}\x{78CA}\x{78CB}\x{78CC}\x{78CD}\x{78CE}\x{78CF}\x{78D0}\x{78D1}' + . '\x{78D3}\x{78D4}\x{78D5}\x{78D6}\x{78D7}\x{78D8}\x{78D9}\x{78DA}\x{78DB}' + . '\x{78DC}\x{78DD}\x{78DE}\x{78DF}\x{78E0}\x{78E1}\x{78E2}\x{78E3}\x{78E4}' + . '\x{78E5}\x{78E6}\x{78E7}\x{78E8}\x{78E9}\x{78EA}\x{78EB}\x{78EC}\x{78ED}' + . '\x{78EE}\x{78EF}\x{78F1}\x{78F2}\x{78F3}\x{78F4}\x{78F5}\x{78F6}\x{78F7}' + . '\x{78F9}\x{78FA}\x{78FB}\x{78FC}\x{78FD}\x{78FE}\x{78FF}\x{7901}\x{7902}' + . '\x{7903}\x{7904}\x{7905}\x{7906}\x{7907}\x{7909}\x{790A}\x{790B}\x{790C}' + . '\x{790E}\x{790F}\x{7910}\x{7911}\x{7912}\x{7913}\x{7914}\x{7916}\x{7917}' + . '\x{7918}\x{7919}\x{791A}\x{791B}\x{791C}\x{791D}\x{791E}\x{7921}\x{7922}' + . '\x{7923}\x{7924}\x{7925}\x{7926}\x{7927}\x{7928}\x{7929}\x{792A}\x{792B}' + . '\x{792C}\x{792D}\x{792E}\x{792F}\x{7930}\x{7931}\x{7933}\x{7934}\x{7935}' + . '\x{7937}\x{7938}\x{7939}\x{793A}\x{793B}\x{793C}\x{793D}\x{793E}\x{793F}' + . '\x{7940}\x{7941}\x{7942}\x{7943}\x{7944}\x{7945}\x{7946}\x{7947}\x{7948}' + . '\x{7949}\x{794A}\x{794B}\x{794C}\x{794D}\x{794E}\x{794F}\x{7950}\x{7951}' + . '\x{7952}\x{7953}\x{7954}\x{7955}\x{7956}\x{7957}\x{7958}\x{795A}\x{795B}' + . '\x{795C}\x{795D}\x{795E}\x{795F}\x{7960}\x{7961}\x{7962}\x{7963}\x{7964}' + . '\x{7965}\x{7966}\x{7967}\x{7968}\x{7969}\x{796A}\x{796B}\x{796D}\x{796F}' + . '\x{7970}\x{7971}\x{7972}\x{7973}\x{7974}\x{7977}\x{7978}\x{7979}\x{797A}' + . '\x{797B}\x{797C}\x{797D}\x{797E}\x{797F}\x{7980}\x{7981}\x{7982}\x{7983}' + . '\x{7984}\x{7985}\x{7988}\x{7989}\x{798A}\x{798B}\x{798C}\x{798D}\x{798E}' + . '\x{798F}\x{7990}\x{7991}\x{7992}\x{7993}\x{7994}\x{7995}\x{7996}\x{7997}' + . '\x{7998}\x{7999}\x{799A}\x{799B}\x{799C}\x{799F}\x{79A0}\x{79A1}\x{79A2}' + . '\x{79A3}\x{79A4}\x{79A5}\x{79A6}\x{79A7}\x{79A8}\x{79AA}\x{79AB}\x{79AC}' + . '\x{79AD}\x{79AE}\x{79AF}\x{79B0}\x{79B1}\x{79B2}\x{79B3}\x{79B4}\x{79B5}' + . '\x{79B6}\x{79B7}\x{79B8}\x{79B9}\x{79BA}\x{79BB}\x{79BD}\x{79BE}\x{79BF}' + . '\x{79C0}\x{79C1}\x{79C2}\x{79C3}\x{79C5}\x{79C6}\x{79C8}\x{79C9}\x{79CA}' + . '\x{79CB}\x{79CD}\x{79CE}\x{79CF}\x{79D0}\x{79D1}\x{79D2}\x{79D3}\x{79D5}' + . '\x{79D6}\x{79D8}\x{79D9}\x{79DA}\x{79DB}\x{79DC}\x{79DD}\x{79DE}\x{79DF}' + . '\x{79E0}\x{79E1}\x{79E2}\x{79E3}\x{79E4}\x{79E5}\x{79E6}\x{79E7}\x{79E8}' + . '\x{79E9}\x{79EA}\x{79EB}\x{79EC}\x{79ED}\x{79EE}\x{79EF}\x{79F0}\x{79F1}' + . '\x{79F2}\x{79F3}\x{79F4}\x{79F5}\x{79F6}\x{79F7}\x{79F8}\x{79F9}\x{79FA}' + . '\x{79FB}\x{79FC}\x{79FD}\x{79FE}\x{79FF}\x{7A00}\x{7A02}\x{7A03}\x{7A04}' + . '\x{7A05}\x{7A06}\x{7A08}\x{7A0A}\x{7A0B}\x{7A0C}\x{7A0D}\x{7A0E}\x{7A0F}' + . '\x{7A10}\x{7A11}\x{7A12}\x{7A13}\x{7A14}\x{7A15}\x{7A16}\x{7A17}\x{7A18}' + . '\x{7A19}\x{7A1A}\x{7A1B}\x{7A1C}\x{7A1D}\x{7A1E}\x{7A1F}\x{7A20}\x{7A21}' + . '\x{7A22}\x{7A23}\x{7A24}\x{7A25}\x{7A26}\x{7A27}\x{7A28}\x{7A29}\x{7A2A}' + . '\x{7A2B}\x{7A2D}\x{7A2E}\x{7A2F}\x{7A30}\x{7A31}\x{7A32}\x{7A33}\x{7A34}' + . '\x{7A35}\x{7A37}\x{7A39}\x{7A3B}\x{7A3C}\x{7A3D}\x{7A3E}\x{7A3F}\x{7A40}' + . '\x{7A41}\x{7A42}\x{7A43}\x{7A44}\x{7A45}\x{7A46}\x{7A47}\x{7A48}\x{7A49}' + . '\x{7A4A}\x{7A4B}\x{7A4C}\x{7A4D}\x{7A4E}\x{7A50}\x{7A51}\x{7A52}\x{7A53}' + . '\x{7A54}\x{7A55}\x{7A56}\x{7A57}\x{7A58}\x{7A59}\x{7A5A}\x{7A5B}\x{7A5C}' + . '\x{7A5D}\x{7A5E}\x{7A5F}\x{7A60}\x{7A61}\x{7A62}\x{7A65}\x{7A66}\x{7A67}' + . '\x{7A68}\x{7A69}\x{7A6B}\x{7A6C}\x{7A6D}\x{7A6E}\x{7A70}\x{7A71}\x{7A72}' + . '\x{7A73}\x{7A74}\x{7A75}\x{7A76}\x{7A77}\x{7A78}\x{7A79}\x{7A7A}\x{7A7B}' + . '\x{7A7C}\x{7A7D}\x{7A7E}\x{7A7F}\x{7A80}\x{7A81}\x{7A83}\x{7A84}\x{7A85}' + . '\x{7A86}\x{7A87}\x{7A88}\x{7A89}\x{7A8A}\x{7A8B}\x{7A8C}\x{7A8D}\x{7A8E}' + . '\x{7A8F}\x{7A90}\x{7A91}\x{7A92}\x{7A93}\x{7A94}\x{7A95}\x{7A96}\x{7A97}' + . '\x{7A98}\x{7A99}\x{7A9C}\x{7A9D}\x{7A9E}\x{7A9F}\x{7AA0}\x{7AA1}\x{7AA2}' + . '\x{7AA3}\x{7AA4}\x{7AA5}\x{7AA6}\x{7AA7}\x{7AA8}\x{7AA9}\x{7AAA}\x{7AAB}' + . '\x{7AAC}\x{7AAD}\x{7AAE}\x{7AAF}\x{7AB0}\x{7AB1}\x{7AB2}\x{7AB3}\x{7AB4}' + . '\x{7AB5}\x{7AB6}\x{7AB7}\x{7AB8}\x{7ABA}\x{7ABE}\x{7ABF}\x{7AC0}\x{7AC1}' + . '\x{7AC4}\x{7AC5}\x{7AC7}\x{7AC8}\x{7AC9}\x{7ACA}\x{7ACB}\x{7ACC}\x{7ACD}' + . '\x{7ACE}\x{7ACF}\x{7AD0}\x{7AD1}\x{7AD2}\x{7AD3}\x{7AD4}\x{7AD5}\x{7AD6}' + . '\x{7AD8}\x{7AD9}\x{7ADB}\x{7ADC}\x{7ADD}\x{7ADE}\x{7ADF}\x{7AE0}\x{7AE1}' + . '\x{7AE2}\x{7AE3}\x{7AE4}\x{7AE5}\x{7AE6}\x{7AE7}\x{7AE8}\x{7AEA}\x{7AEB}' + . '\x{7AEC}\x{7AED}\x{7AEE}\x{7AEF}\x{7AF0}\x{7AF1}\x{7AF2}\x{7AF3}\x{7AF4}' + . '\x{7AF6}\x{7AF7}\x{7AF8}\x{7AF9}\x{7AFA}\x{7AFB}\x{7AFD}\x{7AFE}\x{7AFF}' + . '\x{7B00}\x{7B01}\x{7B02}\x{7B03}\x{7B04}\x{7B05}\x{7B06}\x{7B08}\x{7B09}' + . '\x{7B0A}\x{7B0B}\x{7B0C}\x{7B0D}\x{7B0E}\x{7B0F}\x{7B10}\x{7B11}\x{7B12}' + . '\x{7B13}\x{7B14}\x{7B15}\x{7B16}\x{7B17}\x{7B18}\x{7B19}\x{7B1A}\x{7B1B}' + . '\x{7B1C}\x{7B1D}\x{7B1E}\x{7B20}\x{7B21}\x{7B22}\x{7B23}\x{7B24}\x{7B25}' + . '\x{7B26}\x{7B28}\x{7B2A}\x{7B2B}\x{7B2C}\x{7B2D}\x{7B2E}\x{7B2F}\x{7B30}' + . '\x{7B31}\x{7B32}\x{7B33}\x{7B34}\x{7B35}\x{7B36}\x{7B37}\x{7B38}\x{7B39}' + . '\x{7B3A}\x{7B3B}\x{7B3C}\x{7B3D}\x{7B3E}\x{7B3F}\x{7B40}\x{7B41}\x{7B43}' + . '\x{7B44}\x{7B45}\x{7B46}\x{7B47}\x{7B48}\x{7B49}\x{7B4A}\x{7B4B}\x{7B4C}' + . '\x{7B4D}\x{7B4E}\x{7B4F}\x{7B50}\x{7B51}\x{7B52}\x{7B54}\x{7B55}\x{7B56}' + . '\x{7B57}\x{7B58}\x{7B59}\x{7B5A}\x{7B5B}\x{7B5C}\x{7B5D}\x{7B5E}\x{7B5F}' + . '\x{7B60}\x{7B61}\x{7B62}\x{7B63}\x{7B64}\x{7B65}\x{7B66}\x{7B67}\x{7B68}' + . '\x{7B69}\x{7B6A}\x{7B6B}\x{7B6C}\x{7B6D}\x{7B6E}\x{7B70}\x{7B71}\x{7B72}' + . '\x{7B73}\x{7B74}\x{7B75}\x{7B76}\x{7B77}\x{7B78}\x{7B79}\x{7B7B}\x{7B7C}' + . '\x{7B7D}\x{7B7E}\x{7B7F}\x{7B80}\x{7B81}\x{7B82}\x{7B83}\x{7B84}\x{7B85}' + . '\x{7B87}\x{7B88}\x{7B89}\x{7B8A}\x{7B8B}\x{7B8C}\x{7B8D}\x{7B8E}\x{7B8F}' + . '\x{7B90}\x{7B91}\x{7B93}\x{7B94}\x{7B95}\x{7B96}\x{7B97}\x{7B98}\x{7B99}' + . '\x{7B9A}\x{7B9B}\x{7B9C}\x{7B9D}\x{7B9E}\x{7B9F}\x{7BA0}\x{7BA1}\x{7BA2}' + . '\x{7BA4}\x{7BA6}\x{7BA7}\x{7BA8}\x{7BA9}\x{7BAA}\x{7BAB}\x{7BAC}\x{7BAD}' + . '\x{7BAE}\x{7BAF}\x{7BB1}\x{7BB3}\x{7BB4}\x{7BB5}\x{7BB6}\x{7BB7}\x{7BB8}' + . '\x{7BB9}\x{7BBA}\x{7BBB}\x{7BBC}\x{7BBD}\x{7BBE}\x{7BBF}\x{7BC0}\x{7BC1}' + . '\x{7BC2}\x{7BC3}\x{7BC4}\x{7BC5}\x{7BC6}\x{7BC7}\x{7BC8}\x{7BC9}\x{7BCA}' + . '\x{7BCB}\x{7BCC}\x{7BCD}\x{7BCE}\x{7BD0}\x{7BD1}\x{7BD2}\x{7BD3}\x{7BD4}' + . '\x{7BD5}\x{7BD6}\x{7BD7}\x{7BD8}\x{7BD9}\x{7BDA}\x{7BDB}\x{7BDC}\x{7BDD}' + . '\x{7BDE}\x{7BDF}\x{7BE0}\x{7BE1}\x{7BE2}\x{7BE3}\x{7BE4}\x{7BE5}\x{7BE6}' + . '\x{7BE7}\x{7BE8}\x{7BE9}\x{7BEA}\x{7BEB}\x{7BEC}\x{7BED}\x{7BEE}\x{7BEF}' + . '\x{7BF0}\x{7BF1}\x{7BF2}\x{7BF3}\x{7BF4}\x{7BF5}\x{7BF6}\x{7BF7}\x{7BF8}' + . '\x{7BF9}\x{7BFB}\x{7BFC}\x{7BFD}\x{7BFE}\x{7BFF}\x{7C00}\x{7C01}\x{7C02}' + . '\x{7C03}\x{7C04}\x{7C05}\x{7C06}\x{7C07}\x{7C08}\x{7C09}\x{7C0A}\x{7C0B}' + . '\x{7C0C}\x{7C0D}\x{7C0E}\x{7C0F}\x{7C10}\x{7C11}\x{7C12}\x{7C13}\x{7C15}' + . '\x{7C16}\x{7C17}\x{7C18}\x{7C19}\x{7C1A}\x{7C1C}\x{7C1D}\x{7C1E}\x{7C1F}' + . '\x{7C20}\x{7C21}\x{7C22}\x{7C23}\x{7C24}\x{7C25}\x{7C26}\x{7C27}\x{7C28}' + . '\x{7C29}\x{7C2A}\x{7C2B}\x{7C2C}\x{7C2D}\x{7C30}\x{7C31}\x{7C32}\x{7C33}' + . '\x{7C34}\x{7C35}\x{7C36}\x{7C37}\x{7C38}\x{7C39}\x{7C3A}\x{7C3B}\x{7C3C}' + . '\x{7C3D}\x{7C3E}\x{7C3F}\x{7C40}\x{7C41}\x{7C42}\x{7C43}\x{7C44}\x{7C45}' + . '\x{7C46}\x{7C47}\x{7C48}\x{7C49}\x{7C4A}\x{7C4B}\x{7C4C}\x{7C4D}\x{7C4E}' + . '\x{7C50}\x{7C51}\x{7C53}\x{7C54}\x{7C56}\x{7C57}\x{7C58}\x{7C59}\x{7C5A}' + . '\x{7C5B}\x{7C5C}\x{7C5E}\x{7C5F}\x{7C60}\x{7C61}\x{7C62}\x{7C63}\x{7C64}' + . '\x{7C65}\x{7C66}\x{7C67}\x{7C68}\x{7C69}\x{7C6A}\x{7C6B}\x{7C6C}\x{7C6D}' + . '\x{7C6E}\x{7C6F}\x{7C70}\x{7C71}\x{7C72}\x{7C73}\x{7C74}\x{7C75}\x{7C77}' + . '\x{7C78}\x{7C79}\x{7C7A}\x{7C7B}\x{7C7C}\x{7C7D}\x{7C7E}\x{7C7F}\x{7C80}' + . '\x{7C81}\x{7C82}\x{7C84}\x{7C85}\x{7C86}\x{7C88}\x{7C89}\x{7C8A}\x{7C8B}' + . '\x{7C8C}\x{7C8D}\x{7C8E}\x{7C8F}\x{7C90}\x{7C91}\x{7C92}\x{7C94}\x{7C95}' + . '\x{7C96}\x{7C97}\x{7C98}\x{7C99}\x{7C9B}\x{7C9C}\x{7C9D}\x{7C9E}\x{7C9F}' + . '\x{7CA0}\x{7CA1}\x{7CA2}\x{7CA3}\x{7CA4}\x{7CA5}\x{7CA6}\x{7CA7}\x{7CA8}' + . '\x{7CA9}\x{7CAA}\x{7CAD}\x{7CAE}\x{7CAF}\x{7CB0}\x{7CB1}\x{7CB2}\x{7CB3}' + . '\x{7CB4}\x{7CB5}\x{7CB6}\x{7CB7}\x{7CB8}\x{7CB9}\x{7CBA}\x{7CBB}\x{7CBC}' + . '\x{7CBD}\x{7CBE}\x{7CBF}\x{7CC0}\x{7CC1}\x{7CC2}\x{7CC3}\x{7CC4}\x{7CC5}' + . '\x{7CC6}\x{7CC7}\x{7CC8}\x{7CC9}\x{7CCA}\x{7CCB}\x{7CCC}\x{7CCD}\x{7CCE}' + . '\x{7CCF}\x{7CD0}\x{7CD1}\x{7CD2}\x{7CD4}\x{7CD5}\x{7CD6}\x{7CD7}\x{7CD8}' + . '\x{7CD9}\x{7CDC}\x{7CDD}\x{7CDE}\x{7CDF}\x{7CE0}\x{7CE2}\x{7CE4}\x{7CE7}' + . '\x{7CE8}\x{7CE9}\x{7CEA}\x{7CEB}\x{7CEC}\x{7CED}\x{7CEE}\x{7CEF}\x{7CF0}' + . '\x{7CF1}\x{7CF2}\x{7CF3}\x{7CF4}\x{7CF5}\x{7CF6}\x{7CF7}\x{7CF8}\x{7CF9}' + . '\x{7CFA}\x{7CFB}\x{7CFD}\x{7CFE}\x{7D00}\x{7D01}\x{7D02}\x{7D03}\x{7D04}' + . '\x{7D05}\x{7D06}\x{7D07}\x{7D08}\x{7D09}\x{7D0A}\x{7D0B}\x{7D0C}\x{7D0D}' + . '\x{7D0E}\x{7D0F}\x{7D10}\x{7D11}\x{7D12}\x{7D13}\x{7D14}\x{7D15}\x{7D16}' + . '\x{7D17}\x{7D18}\x{7D19}\x{7D1A}\x{7D1B}\x{7D1C}\x{7D1D}\x{7D1E}\x{7D1F}' + . '\x{7D20}\x{7D21}\x{7D22}\x{7D24}\x{7D25}\x{7D26}\x{7D27}\x{7D28}\x{7D29}' + . '\x{7D2B}\x{7D2C}\x{7D2E}\x{7D2F}\x{7D30}\x{7D31}\x{7D32}\x{7D33}\x{7D34}' + . '\x{7D35}\x{7D36}\x{7D37}\x{7D38}\x{7D39}\x{7D3A}\x{7D3B}\x{7D3C}\x{7D3D}' + . '\x{7D3E}\x{7D3F}\x{7D40}\x{7D41}\x{7D42}\x{7D43}\x{7D44}\x{7D45}\x{7D46}' + . '\x{7D47}\x{7D49}\x{7D4A}\x{7D4B}\x{7D4C}\x{7D4E}\x{7D4F}\x{7D50}\x{7D51}' + . '\x{7D52}\x{7D53}\x{7D54}\x{7D55}\x{7D56}\x{7D57}\x{7D58}\x{7D59}\x{7D5B}' + . '\x{7D5C}\x{7D5D}\x{7D5E}\x{7D5F}\x{7D60}\x{7D61}\x{7D62}\x{7D63}\x{7D65}' + . '\x{7D66}\x{7D67}\x{7D68}\x{7D69}\x{7D6A}\x{7D6B}\x{7D6C}\x{7D6D}\x{7D6E}' + . '\x{7D6F}\x{7D70}\x{7D71}\x{7D72}\x{7D73}\x{7D74}\x{7D75}\x{7D76}\x{7D77}' + . '\x{7D79}\x{7D7A}\x{7D7B}\x{7D7C}\x{7D7D}\x{7D7E}\x{7D7F}\x{7D80}\x{7D81}' + . '\x{7D83}\x{7D84}\x{7D85}\x{7D86}\x{7D87}\x{7D88}\x{7D89}\x{7D8A}\x{7D8B}' + . '\x{7D8C}\x{7D8D}\x{7D8E}\x{7D8F}\x{7D90}\x{7D91}\x{7D92}\x{7D93}\x{7D94}' + . '\x{7D96}\x{7D97}\x{7D99}\x{7D9B}\x{7D9C}\x{7D9D}\x{7D9E}\x{7D9F}\x{7DA0}' + . '\x{7DA1}\x{7DA2}\x{7DA3}\x{7DA5}\x{7DA6}\x{7DA7}\x{7DA9}\x{7DAA}\x{7DAB}' + . '\x{7DAC}\x{7DAD}\x{7DAE}\x{7DAF}\x{7DB0}\x{7DB1}\x{7DB2}\x{7DB3}\x{7DB4}' + . '\x{7DB5}\x{7DB6}\x{7DB7}\x{7DB8}\x{7DB9}\x{7DBA}\x{7DBB}\x{7DBC}\x{7DBD}' + . '\x{7DBE}\x{7DBF}\x{7DC0}\x{7DC1}\x{7DC2}\x{7DC3}\x{7DC4}\x{7DC5}\x{7DC6}' + . '\x{7DC7}\x{7DC8}\x{7DC9}\x{7DCA}\x{7DCB}\x{7DCC}\x{7DCE}\x{7DCF}\x{7DD0}' + . '\x{7DD1}\x{7DD2}\x{7DD4}\x{7DD5}\x{7DD6}\x{7DD7}\x{7DD8}\x{7DD9}\x{7DDA}' + . '\x{7DDB}\x{7DDD}\x{7DDE}\x{7DDF}\x{7DE0}\x{7DE1}\x{7DE2}\x{7DE3}\x{7DE6}' + . '\x{7DE7}\x{7DE8}\x{7DE9}\x{7DEA}\x{7DEC}\x{7DED}\x{7DEE}\x{7DEF}\x{7DF0}' + . '\x{7DF1}\x{7DF2}\x{7DF3}\x{7DF4}\x{7DF5}\x{7DF6}\x{7DF7}\x{7DF8}\x{7DF9}' + . '\x{7DFA}\x{7DFB}\x{7DFC}\x{7E00}\x{7E01}\x{7E02}\x{7E03}\x{7E04}\x{7E05}' + . '\x{7E06}\x{7E07}\x{7E08}\x{7E09}\x{7E0A}\x{7E0B}\x{7E0C}\x{7E0D}\x{7E0E}' + . '\x{7E0F}\x{7E10}\x{7E11}\x{7E12}\x{7E13}\x{7E14}\x{7E15}\x{7E16}\x{7E17}' + . '\x{7E19}\x{7E1A}\x{7E1B}\x{7E1C}\x{7E1D}\x{7E1E}\x{7E1F}\x{7E20}\x{7E21}' + . '\x{7E22}\x{7E23}\x{7E24}\x{7E25}\x{7E26}\x{7E27}\x{7E28}\x{7E29}\x{7E2A}' + . '\x{7E2B}\x{7E2C}\x{7E2D}\x{7E2E}\x{7E2F}\x{7E30}\x{7E31}\x{7E32}\x{7E33}' + . '\x{7E34}\x{7E35}\x{7E36}\x{7E37}\x{7E38}\x{7E39}\x{7E3A}\x{7E3B}\x{7E3C}' + . '\x{7E3D}\x{7E3E}\x{7E3F}\x{7E40}\x{7E41}\x{7E42}\x{7E43}\x{7E44}\x{7E45}' + . '\x{7E46}\x{7E47}\x{7E48}\x{7E49}\x{7E4C}\x{7E4D}\x{7E4E}\x{7E4F}\x{7E50}' + . '\x{7E51}\x{7E52}\x{7E53}\x{7E54}\x{7E55}\x{7E56}\x{7E57}\x{7E58}\x{7E59}' + . '\x{7E5A}\x{7E5C}\x{7E5D}\x{7E5E}\x{7E5F}\x{7E60}\x{7E61}\x{7E62}\x{7E63}' + . '\x{7E65}\x{7E66}\x{7E67}\x{7E68}\x{7E69}\x{7E6A}\x{7E6B}\x{7E6C}\x{7E6D}' + . '\x{7E6E}\x{7E6F}\x{7E70}\x{7E71}\x{7E72}\x{7E73}\x{7E74}\x{7E75}\x{7E76}' + . '\x{7E77}\x{7E78}\x{7E79}\x{7E7A}\x{7E7B}\x{7E7C}\x{7E7D}\x{7E7E}\x{7E7F}' + . '\x{7E80}\x{7E81}\x{7E82}\x{7E83}\x{7E84}\x{7E85}\x{7E86}\x{7E87}\x{7E88}' + . '\x{7E89}\x{7E8A}\x{7E8B}\x{7E8C}\x{7E8D}\x{7E8E}\x{7E8F}\x{7E90}\x{7E91}' + . '\x{7E92}\x{7E93}\x{7E94}\x{7E95}\x{7E96}\x{7E97}\x{7E98}\x{7E99}\x{7E9A}' + . '\x{7E9B}\x{7E9C}\x{7E9E}\x{7E9F}\x{7EA0}\x{7EA1}\x{7EA2}\x{7EA3}\x{7EA4}' + . '\x{7EA5}\x{7EA6}\x{7EA7}\x{7EA8}\x{7EA9}\x{7EAA}\x{7EAB}\x{7EAC}\x{7EAD}' + . '\x{7EAE}\x{7EAF}\x{7EB0}\x{7EB1}\x{7EB2}\x{7EB3}\x{7EB4}\x{7EB5}\x{7EB6}' + . '\x{7EB7}\x{7EB8}\x{7EB9}\x{7EBA}\x{7EBB}\x{7EBC}\x{7EBD}\x{7EBE}\x{7EBF}' + . '\x{7EC0}\x{7EC1}\x{7EC2}\x{7EC3}\x{7EC4}\x{7EC5}\x{7EC6}\x{7EC7}\x{7EC8}' + . '\x{7EC9}\x{7ECA}\x{7ECB}\x{7ECC}\x{7ECD}\x{7ECE}\x{7ECF}\x{7ED0}\x{7ED1}' + . '\x{7ED2}\x{7ED3}\x{7ED4}\x{7ED5}\x{7ED6}\x{7ED7}\x{7ED8}\x{7ED9}\x{7EDA}' + . '\x{7EDB}\x{7EDC}\x{7EDD}\x{7EDE}\x{7EDF}\x{7EE0}\x{7EE1}\x{7EE2}\x{7EE3}' + . '\x{7EE4}\x{7EE5}\x{7EE6}\x{7EE7}\x{7EE8}\x{7EE9}\x{7EEA}\x{7EEB}\x{7EEC}' + . '\x{7EED}\x{7EEE}\x{7EEF}\x{7EF0}\x{7EF1}\x{7EF2}\x{7EF3}\x{7EF4}\x{7EF5}' + . '\x{7EF6}\x{7EF7}\x{7EF8}\x{7EF9}\x{7EFA}\x{7EFB}\x{7EFC}\x{7EFD}\x{7EFE}' + . '\x{7EFF}\x{7F00}\x{7F01}\x{7F02}\x{7F03}\x{7F04}\x{7F05}\x{7F06}\x{7F07}' + . '\x{7F08}\x{7F09}\x{7F0A}\x{7F0B}\x{7F0C}\x{7F0D}\x{7F0E}\x{7F0F}\x{7F10}' + . '\x{7F11}\x{7F12}\x{7F13}\x{7F14}\x{7F15}\x{7F16}\x{7F17}\x{7F18}\x{7F19}' + . '\x{7F1A}\x{7F1B}\x{7F1C}\x{7F1D}\x{7F1E}\x{7F1F}\x{7F20}\x{7F21}\x{7F22}' + . '\x{7F23}\x{7F24}\x{7F25}\x{7F26}\x{7F27}\x{7F28}\x{7F29}\x{7F2A}\x{7F2B}' + . '\x{7F2C}\x{7F2D}\x{7F2E}\x{7F2F}\x{7F30}\x{7F31}\x{7F32}\x{7F33}\x{7F34}' + . '\x{7F35}\x{7F36}\x{7F37}\x{7F38}\x{7F39}\x{7F3A}\x{7F3D}\x{7F3E}\x{7F3F}' + . '\x{7F40}\x{7F42}\x{7F43}\x{7F44}\x{7F45}\x{7F47}\x{7F48}\x{7F49}\x{7F4A}' + . '\x{7F4B}\x{7F4C}\x{7F4D}\x{7F4E}\x{7F4F}\x{7F50}\x{7F51}\x{7F52}\x{7F53}' + . '\x{7F54}\x{7F55}\x{7F56}\x{7F57}\x{7F58}\x{7F5A}\x{7F5B}\x{7F5C}\x{7F5D}' + . '\x{7F5E}\x{7F5F}\x{7F60}\x{7F61}\x{7F62}\x{7F63}\x{7F64}\x{7F65}\x{7F66}' + . '\x{7F67}\x{7F68}\x{7F69}\x{7F6A}\x{7F6B}\x{7F6C}\x{7F6D}\x{7F6E}\x{7F6F}' + . '\x{7F70}\x{7F71}\x{7F72}\x{7F73}\x{7F74}\x{7F75}\x{7F76}\x{7F77}\x{7F78}' + . '\x{7F79}\x{7F7A}\x{7F7B}\x{7F7C}\x{7F7D}\x{7F7E}\x{7F7F}\x{7F80}\x{7F81}' + . '\x{7F82}\x{7F83}\x{7F85}\x{7F86}\x{7F87}\x{7F88}\x{7F89}\x{7F8A}\x{7F8B}' + . '\x{7F8C}\x{7F8D}\x{7F8E}\x{7F8F}\x{7F91}\x{7F92}\x{7F93}\x{7F94}\x{7F95}' + . '\x{7F96}\x{7F98}\x{7F9A}\x{7F9B}\x{7F9C}\x{7F9D}\x{7F9E}\x{7F9F}\x{7FA0}' + . '\x{7FA1}\x{7FA2}\x{7FA3}\x{7FA4}\x{7FA5}\x{7FA6}\x{7FA7}\x{7FA8}\x{7FA9}' + . '\x{7FAA}\x{7FAB}\x{7FAC}\x{7FAD}\x{7FAE}\x{7FAF}\x{7FB0}\x{7FB1}\x{7FB2}' + . '\x{7FB3}\x{7FB5}\x{7FB6}\x{7FB7}\x{7FB8}\x{7FB9}\x{7FBA}\x{7FBB}\x{7FBC}' + . '\x{7FBD}\x{7FBE}\x{7FBF}\x{7FC0}\x{7FC1}\x{7FC2}\x{7FC3}\x{7FC4}\x{7FC5}' + . '\x{7FC6}\x{7FC7}\x{7FC8}\x{7FC9}\x{7FCA}\x{7FCB}\x{7FCC}\x{7FCD}\x{7FCE}' + . '\x{7FCF}\x{7FD0}\x{7FD1}\x{7FD2}\x{7FD3}\x{7FD4}\x{7FD5}\x{7FD7}\x{7FD8}' + . '\x{7FD9}\x{7FDA}\x{7FDB}\x{7FDC}\x{7FDE}\x{7FDF}\x{7FE0}\x{7FE1}\x{7FE2}' + . '\x{7FE3}\x{7FE5}\x{7FE6}\x{7FE7}\x{7FE8}\x{7FE9}\x{7FEA}\x{7FEB}\x{7FEC}' + . '\x{7FED}\x{7FEE}\x{7FEF}\x{7FF0}\x{7FF1}\x{7FF2}\x{7FF3}\x{7FF4}\x{7FF5}' + . '\x{7FF6}\x{7FF7}\x{7FF8}\x{7FF9}\x{7FFA}\x{7FFB}\x{7FFC}\x{7FFD}\x{7FFE}' + . '\x{7FFF}\x{8000}\x{8001}\x{8002}\x{8003}\x{8004}\x{8005}\x{8006}\x{8007}' + . '\x{8008}\x{8009}\x{800B}\x{800C}\x{800D}\x{800E}\x{800F}\x{8010}\x{8011}' + . '\x{8012}\x{8013}\x{8014}\x{8015}\x{8016}\x{8017}\x{8018}\x{8019}\x{801A}' + . '\x{801B}\x{801C}\x{801D}\x{801E}\x{801F}\x{8020}\x{8021}\x{8022}\x{8023}' + . '\x{8024}\x{8025}\x{8026}\x{8027}\x{8028}\x{8029}\x{802A}\x{802B}\x{802C}' + . '\x{802D}\x{802E}\x{8030}\x{8031}\x{8032}\x{8033}\x{8034}\x{8035}\x{8036}' + . '\x{8037}\x{8038}\x{8039}\x{803A}\x{803B}\x{803D}\x{803E}\x{803F}\x{8041}' + . '\x{8042}\x{8043}\x{8044}\x{8045}\x{8046}\x{8047}\x{8048}\x{8049}\x{804A}' + . '\x{804B}\x{804C}\x{804D}\x{804E}\x{804F}\x{8050}\x{8051}\x{8052}\x{8053}' + . '\x{8054}\x{8055}\x{8056}\x{8057}\x{8058}\x{8059}\x{805A}\x{805B}\x{805C}' + . '\x{805D}\x{805E}\x{805F}\x{8060}\x{8061}\x{8062}\x{8063}\x{8064}\x{8065}' + . '\x{8067}\x{8068}\x{8069}\x{806A}\x{806B}\x{806C}\x{806D}\x{806E}\x{806F}' + . '\x{8070}\x{8071}\x{8072}\x{8073}\x{8074}\x{8075}\x{8076}\x{8077}\x{8078}' + . '\x{8079}\x{807A}\x{807B}\x{807C}\x{807D}\x{807E}\x{807F}\x{8080}\x{8081}' + . '\x{8082}\x{8083}\x{8084}\x{8085}\x{8086}\x{8087}\x{8089}\x{808A}\x{808B}' + . '\x{808C}\x{808D}\x{808F}\x{8090}\x{8091}\x{8092}\x{8093}\x{8095}\x{8096}' + . '\x{8097}\x{8098}\x{8099}\x{809A}\x{809B}\x{809C}\x{809D}\x{809E}\x{809F}' + . '\x{80A0}\x{80A1}\x{80A2}\x{80A3}\x{80A4}\x{80A5}\x{80A9}\x{80AA}\x{80AB}' + . '\x{80AD}\x{80AE}\x{80AF}\x{80B0}\x{80B1}\x{80B2}\x{80B4}\x{80B5}\x{80B6}' + . '\x{80B7}\x{80B8}\x{80BA}\x{80BB}\x{80BC}\x{80BD}\x{80BE}\x{80BF}\x{80C0}' + . '\x{80C1}\x{80C2}\x{80C3}\x{80C4}\x{80C5}\x{80C6}\x{80C7}\x{80C8}\x{80C9}' + . '\x{80CA}\x{80CB}\x{80CC}\x{80CD}\x{80CE}\x{80CF}\x{80D0}\x{80D1}\x{80D2}' + . '\x{80D3}\x{80D4}\x{80D5}\x{80D6}\x{80D7}\x{80D8}\x{80D9}\x{80DA}\x{80DB}' + . '\x{80DC}\x{80DD}\x{80DE}\x{80E0}\x{80E1}\x{80E2}\x{80E3}\x{80E4}\x{80E5}' + . '\x{80E6}\x{80E7}\x{80E8}\x{80E9}\x{80EA}\x{80EB}\x{80EC}\x{80ED}\x{80EE}' + . '\x{80EF}\x{80F0}\x{80F1}\x{80F2}\x{80F3}\x{80F4}\x{80F5}\x{80F6}\x{80F7}' + . '\x{80F8}\x{80F9}\x{80FA}\x{80FB}\x{80FC}\x{80FD}\x{80FE}\x{80FF}\x{8100}' + . '\x{8101}\x{8102}\x{8105}\x{8106}\x{8107}\x{8108}\x{8109}\x{810A}\x{810B}' + . '\x{810C}\x{810D}\x{810E}\x{810F}\x{8110}\x{8111}\x{8112}\x{8113}\x{8114}' + . '\x{8115}\x{8116}\x{8118}\x{8119}\x{811A}\x{811B}\x{811C}\x{811D}\x{811E}' + . '\x{811F}\x{8120}\x{8121}\x{8122}\x{8123}\x{8124}\x{8125}\x{8126}\x{8127}' + . '\x{8128}\x{8129}\x{812A}\x{812B}\x{812C}\x{812D}\x{812E}\x{812F}\x{8130}' + . '\x{8131}\x{8132}\x{8136}\x{8137}\x{8138}\x{8139}\x{813A}\x{813B}\x{813C}' + . '\x{813D}\x{813E}\x{813F}\x{8140}\x{8141}\x{8142}\x{8143}\x{8144}\x{8145}' + . '\x{8146}\x{8147}\x{8148}\x{8149}\x{814A}\x{814B}\x{814C}\x{814D}\x{814E}' + . '\x{814F}\x{8150}\x{8151}\x{8152}\x{8153}\x{8154}\x{8155}\x{8156}\x{8157}' + . '\x{8158}\x{8159}\x{815A}\x{815B}\x{815C}\x{815D}\x{815E}\x{8160}\x{8161}' + . '\x{8162}\x{8163}\x{8164}\x{8165}\x{8166}\x{8167}\x{8168}\x{8169}\x{816A}' + . '\x{816B}\x{816C}\x{816D}\x{816E}\x{816F}\x{8170}\x{8171}\x{8172}\x{8173}' + . '\x{8174}\x{8175}\x{8176}\x{8177}\x{8178}\x{8179}\x{817A}\x{817B}\x{817C}' + . '\x{817D}\x{817E}\x{817F}\x{8180}\x{8181}\x{8182}\x{8183}\x{8185}\x{8186}' + . '\x{8187}\x{8188}\x{8189}\x{818A}\x{818B}\x{818C}\x{818D}\x{818E}\x{818F}' + . '\x{8191}\x{8192}\x{8193}\x{8194}\x{8195}\x{8197}\x{8198}\x{8199}\x{819A}' + . '\x{819B}\x{819C}\x{819D}\x{819E}\x{819F}\x{81A0}\x{81A1}\x{81A2}\x{81A3}' + . '\x{81A4}\x{81A5}\x{81A6}\x{81A7}\x{81A8}\x{81A9}\x{81AA}\x{81AB}\x{81AC}' + . '\x{81AD}\x{81AE}\x{81AF}\x{81B0}\x{81B1}\x{81B2}\x{81B3}\x{81B4}\x{81B5}' + . '\x{81B6}\x{81B7}\x{81B8}\x{81B9}\x{81BA}\x{81BB}\x{81BC}\x{81BD}\x{81BE}' + . '\x{81BF}\x{81C0}\x{81C1}\x{81C2}\x{81C3}\x{81C4}\x{81C5}\x{81C6}\x{81C7}' + . '\x{81C8}\x{81C9}\x{81CA}\x{81CC}\x{81CD}\x{81CE}\x{81CF}\x{81D0}\x{81D1}' + . '\x{81D2}\x{81D4}\x{81D5}\x{81D6}\x{81D7}\x{81D8}\x{81D9}\x{81DA}\x{81DB}' + . '\x{81DC}\x{81DD}\x{81DE}\x{81DF}\x{81E0}\x{81E1}\x{81E2}\x{81E3}\x{81E5}' + . '\x{81E6}\x{81E7}\x{81E8}\x{81E9}\x{81EA}\x{81EB}\x{81EC}\x{81ED}\x{81EE}' + . '\x{81F1}\x{81F2}\x{81F3}\x{81F4}\x{81F5}\x{81F6}\x{81F7}\x{81F8}\x{81F9}' + . '\x{81FA}\x{81FB}\x{81FC}\x{81FD}\x{81FE}\x{81FF}\x{8200}\x{8201}\x{8202}' + . '\x{8203}\x{8204}\x{8205}\x{8206}\x{8207}\x{8208}\x{8209}\x{820A}\x{820B}' + . '\x{820C}\x{820D}\x{820E}\x{820F}\x{8210}\x{8211}\x{8212}\x{8214}\x{8215}' + . '\x{8216}\x{8218}\x{8219}\x{821A}\x{821B}\x{821C}\x{821D}\x{821E}\x{821F}' + . '\x{8220}\x{8221}\x{8222}\x{8223}\x{8225}\x{8226}\x{8227}\x{8228}\x{8229}' + . '\x{822A}\x{822B}\x{822C}\x{822D}\x{822F}\x{8230}\x{8231}\x{8232}\x{8233}' + . '\x{8234}\x{8235}\x{8236}\x{8237}\x{8238}\x{8239}\x{823A}\x{823B}\x{823C}' + . '\x{823D}\x{823E}\x{823F}\x{8240}\x{8242}\x{8243}\x{8244}\x{8245}\x{8246}' + . '\x{8247}\x{8248}\x{8249}\x{824A}\x{824B}\x{824C}\x{824D}\x{824E}\x{824F}' + . '\x{8250}\x{8251}\x{8252}\x{8253}\x{8254}\x{8255}\x{8256}\x{8257}\x{8258}' + . '\x{8259}\x{825A}\x{825B}\x{825C}\x{825D}\x{825E}\x{825F}\x{8260}\x{8261}' + . '\x{8263}\x{8264}\x{8266}\x{8267}\x{8268}\x{8269}\x{826A}\x{826B}\x{826C}' + . '\x{826D}\x{826E}\x{826F}\x{8270}\x{8271}\x{8272}\x{8273}\x{8274}\x{8275}' + . '\x{8276}\x{8277}\x{8278}\x{8279}\x{827A}\x{827B}\x{827C}\x{827D}\x{827E}' + . '\x{827F}\x{8280}\x{8281}\x{8282}\x{8283}\x{8284}\x{8285}\x{8286}\x{8287}' + . '\x{8288}\x{8289}\x{828A}\x{828B}\x{828D}\x{828E}\x{828F}\x{8290}\x{8291}' + . '\x{8292}\x{8293}\x{8294}\x{8295}\x{8296}\x{8297}\x{8298}\x{8299}\x{829A}' + . '\x{829B}\x{829C}\x{829D}\x{829E}\x{829F}\x{82A0}\x{82A1}\x{82A2}\x{82A3}' + . '\x{82A4}\x{82A5}\x{82A6}\x{82A7}\x{82A8}\x{82A9}\x{82AA}\x{82AB}\x{82AC}' + . '\x{82AD}\x{82AE}\x{82AF}\x{82B0}\x{82B1}\x{82B3}\x{82B4}\x{82B5}\x{82B6}' + . '\x{82B7}\x{82B8}\x{82B9}\x{82BA}\x{82BB}\x{82BC}\x{82BD}\x{82BE}\x{82BF}' + . '\x{82C0}\x{82C1}\x{82C2}\x{82C3}\x{82C4}\x{82C5}\x{82C6}\x{82C7}\x{82C8}' + . '\x{82C9}\x{82CA}\x{82CB}\x{82CC}\x{82CD}\x{82CE}\x{82CF}\x{82D0}\x{82D1}' + . '\x{82D2}\x{82D3}\x{82D4}\x{82D5}\x{82D6}\x{82D7}\x{82D8}\x{82D9}\x{82DA}' + . '\x{82DB}\x{82DC}\x{82DD}\x{82DE}\x{82DF}\x{82E0}\x{82E1}\x{82E3}\x{82E4}' + . '\x{82E5}\x{82E6}\x{82E7}\x{82E8}\x{82E9}\x{82EA}\x{82EB}\x{82EC}\x{82ED}' + . '\x{82EE}\x{82EF}\x{82F0}\x{82F1}\x{82F2}\x{82F3}\x{82F4}\x{82F5}\x{82F6}' + . '\x{82F7}\x{82F8}\x{82F9}\x{82FA}\x{82FB}\x{82FD}\x{82FE}\x{82FF}\x{8300}' + . '\x{8301}\x{8302}\x{8303}\x{8304}\x{8305}\x{8306}\x{8307}\x{8308}\x{8309}' + . '\x{830B}\x{830C}\x{830D}\x{830E}\x{830F}\x{8311}\x{8312}\x{8313}\x{8314}' + . '\x{8315}\x{8316}\x{8317}\x{8318}\x{8319}\x{831A}\x{831B}\x{831C}\x{831D}' + . '\x{831E}\x{831F}\x{8320}\x{8321}\x{8322}\x{8323}\x{8324}\x{8325}\x{8326}' + . '\x{8327}\x{8328}\x{8329}\x{832A}\x{832B}\x{832C}\x{832D}\x{832E}\x{832F}' + . '\x{8331}\x{8332}\x{8333}\x{8334}\x{8335}\x{8336}\x{8337}\x{8338}\x{8339}' + . '\x{833A}\x{833B}\x{833C}\x{833D}\x{833E}\x{833F}\x{8340}\x{8341}\x{8342}' + . '\x{8343}\x{8344}\x{8345}\x{8346}\x{8347}\x{8348}\x{8349}\x{834A}\x{834B}' + . '\x{834C}\x{834D}\x{834E}\x{834F}\x{8350}\x{8351}\x{8352}\x{8353}\x{8354}' + . '\x{8356}\x{8357}\x{8358}\x{8359}\x{835A}\x{835B}\x{835C}\x{835D}\x{835E}' + . '\x{835F}\x{8360}\x{8361}\x{8362}\x{8363}\x{8364}\x{8365}\x{8366}\x{8367}' + . '\x{8368}\x{8369}\x{836A}\x{836B}\x{836C}\x{836D}\x{836E}\x{836F}\x{8370}' + . '\x{8371}\x{8372}\x{8373}\x{8374}\x{8375}\x{8376}\x{8377}\x{8378}\x{8379}' + . '\x{837A}\x{837B}\x{837C}\x{837D}\x{837E}\x{837F}\x{8380}\x{8381}\x{8382}' + . '\x{8383}\x{8384}\x{8385}\x{8386}\x{8387}\x{8388}\x{8389}\x{838A}\x{838B}' + . '\x{838C}\x{838D}\x{838E}\x{838F}\x{8390}\x{8391}\x{8392}\x{8393}\x{8394}' + . '\x{8395}\x{8396}\x{8397}\x{8398}\x{8399}\x{839A}\x{839B}\x{839C}\x{839D}' + . '\x{839E}\x{83A0}\x{83A1}\x{83A2}\x{83A3}\x{83A4}\x{83A5}\x{83A6}\x{83A7}' + . '\x{83A8}\x{83A9}\x{83AA}\x{83AB}\x{83AC}\x{83AD}\x{83AE}\x{83AF}\x{83B0}' + . '\x{83B1}\x{83B2}\x{83B3}\x{83B4}\x{83B6}\x{83B7}\x{83B8}\x{83B9}\x{83BA}' + . '\x{83BB}\x{83BC}\x{83BD}\x{83BF}\x{83C0}\x{83C1}\x{83C2}\x{83C3}\x{83C4}' + . '\x{83C5}\x{83C6}\x{83C7}\x{83C8}\x{83C9}\x{83CA}\x{83CB}\x{83CC}\x{83CD}' + . '\x{83CE}\x{83CF}\x{83D0}\x{83D1}\x{83D2}\x{83D3}\x{83D4}\x{83D5}\x{83D6}' + . '\x{83D7}\x{83D8}\x{83D9}\x{83DA}\x{83DB}\x{83DC}\x{83DD}\x{83DE}\x{83DF}' + . '\x{83E0}\x{83E1}\x{83E2}\x{83E3}\x{83E4}\x{83E5}\x{83E7}\x{83E8}\x{83E9}' + . '\x{83EA}\x{83EB}\x{83EC}\x{83EE}\x{83EF}\x{83F0}\x{83F1}\x{83F2}\x{83F3}' + . '\x{83F4}\x{83F5}\x{83F6}\x{83F7}\x{83F8}\x{83F9}\x{83FA}\x{83FB}\x{83FC}' + . '\x{83FD}\x{83FE}\x{83FF}\x{8400}\x{8401}\x{8402}\x{8403}\x{8404}\x{8405}' + . '\x{8406}\x{8407}\x{8408}\x{8409}\x{840A}\x{840B}\x{840C}\x{840D}\x{840E}' + . '\x{840F}\x{8410}\x{8411}\x{8412}\x{8413}\x{8415}\x{8418}\x{8419}\x{841A}' + . '\x{841B}\x{841C}\x{841D}\x{841E}\x{8421}\x{8422}\x{8423}\x{8424}\x{8425}' + . '\x{8426}\x{8427}\x{8428}\x{8429}\x{842A}\x{842B}\x{842C}\x{842D}\x{842E}' + . '\x{842F}\x{8430}\x{8431}\x{8432}\x{8433}\x{8434}\x{8435}\x{8436}\x{8437}' + . '\x{8438}\x{8439}\x{843A}\x{843B}\x{843C}\x{843D}\x{843E}\x{843F}\x{8440}' + . '\x{8441}\x{8442}\x{8443}\x{8444}\x{8445}\x{8446}\x{8447}\x{8448}\x{8449}' + . '\x{844A}\x{844B}\x{844C}\x{844D}\x{844E}\x{844F}\x{8450}\x{8451}\x{8452}' + . '\x{8453}\x{8454}\x{8455}\x{8456}\x{8457}\x{8459}\x{845A}\x{845B}\x{845C}' + . '\x{845D}\x{845E}\x{845F}\x{8460}\x{8461}\x{8462}\x{8463}\x{8464}\x{8465}' + . '\x{8466}\x{8467}\x{8468}\x{8469}\x{846A}\x{846B}\x{846C}\x{846D}\x{846E}' + . '\x{846F}\x{8470}\x{8471}\x{8472}\x{8473}\x{8474}\x{8475}\x{8476}\x{8477}' + . '\x{8478}\x{8479}\x{847A}\x{847B}\x{847C}\x{847D}\x{847E}\x{847F}\x{8480}' + . '\x{8481}\x{8482}\x{8484}\x{8485}\x{8486}\x{8487}\x{8488}\x{8489}\x{848A}' + . '\x{848B}\x{848C}\x{848D}\x{848E}\x{848F}\x{8490}\x{8491}\x{8492}\x{8493}' + . '\x{8494}\x{8496}\x{8497}\x{8498}\x{8499}\x{849A}\x{849B}\x{849C}\x{849D}' + . '\x{849E}\x{849F}\x{84A0}\x{84A1}\x{84A2}\x{84A3}\x{84A4}\x{84A5}\x{84A6}' + . '\x{84A7}\x{84A8}\x{84A9}\x{84AA}\x{84AB}\x{84AC}\x{84AE}\x{84AF}\x{84B0}' + . '\x{84B1}\x{84B2}\x{84B3}\x{84B4}\x{84B5}\x{84B6}\x{84B8}\x{84B9}\x{84BA}' + . '\x{84BB}\x{84BC}\x{84BD}\x{84BE}\x{84BF}\x{84C0}\x{84C1}\x{84C2}\x{84C4}' + . '\x{84C5}\x{84C6}\x{84C7}\x{84C8}\x{84C9}\x{84CA}\x{84CB}\x{84CC}\x{84CD}' + . '\x{84CE}\x{84CF}\x{84D0}\x{84D1}\x{84D2}\x{84D3}\x{84D4}\x{84D5}\x{84D6}' + . '\x{84D7}\x{84D8}\x{84D9}\x{84DB}\x{84DC}\x{84DD}\x{84DE}\x{84DF}\x{84E0}' + . '\x{84E1}\x{84E2}\x{84E3}\x{84E4}\x{84E5}\x{84E6}\x{84E7}\x{84E8}\x{84E9}' + . '\x{84EA}\x{84EB}\x{84EC}\x{84EE}\x{84EF}\x{84F0}\x{84F1}\x{84F2}\x{84F3}' + . '\x{84F4}\x{84F5}\x{84F6}\x{84F7}\x{84F8}\x{84F9}\x{84FA}\x{84FB}\x{84FC}' + . '\x{84FD}\x{84FE}\x{84FF}\x{8500}\x{8501}\x{8502}\x{8503}\x{8504}\x{8506}' + . '\x{8507}\x{8508}\x{8509}\x{850A}\x{850B}\x{850C}\x{850D}\x{850E}\x{850F}' + . '\x{8511}\x{8512}\x{8513}\x{8514}\x{8515}\x{8516}\x{8517}\x{8518}\x{8519}' + . '\x{851A}\x{851B}\x{851C}\x{851D}\x{851E}\x{851F}\x{8520}\x{8521}\x{8522}' + . '\x{8523}\x{8524}\x{8525}\x{8526}\x{8527}\x{8528}\x{8529}\x{852A}\x{852B}' + . '\x{852C}\x{852D}\x{852E}\x{852F}\x{8530}\x{8531}\x{8534}\x{8535}\x{8536}' + . '\x{8537}\x{8538}\x{8539}\x{853A}\x{853B}\x{853C}\x{853D}\x{853E}\x{853F}' + . '\x{8540}\x{8541}\x{8542}\x{8543}\x{8544}\x{8545}\x{8546}\x{8547}\x{8548}' + . '\x{8549}\x{854A}\x{854B}\x{854D}\x{854E}\x{854F}\x{8551}\x{8552}\x{8553}' + . '\x{8554}\x{8555}\x{8556}\x{8557}\x{8558}\x{8559}\x{855A}\x{855B}\x{855C}' + . '\x{855D}\x{855E}\x{855F}\x{8560}\x{8561}\x{8562}\x{8563}\x{8564}\x{8565}' + . '\x{8566}\x{8567}\x{8568}\x{8569}\x{856A}\x{856B}\x{856C}\x{856D}\x{856E}' + . '\x{856F}\x{8570}\x{8571}\x{8572}\x{8573}\x{8574}\x{8575}\x{8576}\x{8577}' + . '\x{8578}\x{8579}\x{857A}\x{857B}\x{857C}\x{857D}\x{857E}\x{8580}\x{8581}' + . '\x{8582}\x{8583}\x{8584}\x{8585}\x{8586}\x{8587}\x{8588}\x{8589}\x{858A}' + . '\x{858B}\x{858C}\x{858D}\x{858E}\x{858F}\x{8590}\x{8591}\x{8592}\x{8594}' + . '\x{8595}\x{8596}\x{8598}\x{8599}\x{859A}\x{859B}\x{859C}\x{859D}\x{859E}' + . '\x{859F}\x{85A0}\x{85A1}\x{85A2}\x{85A3}\x{85A4}\x{85A5}\x{85A6}\x{85A7}' + . '\x{85A8}\x{85A9}\x{85AA}\x{85AB}\x{85AC}\x{85AD}\x{85AE}\x{85AF}\x{85B0}' + . '\x{85B1}\x{85B3}\x{85B4}\x{85B5}\x{85B6}\x{85B7}\x{85B8}\x{85B9}\x{85BA}' + . '\x{85BC}\x{85BD}\x{85BE}\x{85BF}\x{85C0}\x{85C1}\x{85C2}\x{85C3}\x{85C4}' + . '\x{85C5}\x{85C6}\x{85C7}\x{85C8}\x{85C9}\x{85CA}\x{85CB}\x{85CD}\x{85CE}' + . '\x{85CF}\x{85D0}\x{85D1}\x{85D2}\x{85D3}\x{85D4}\x{85D5}\x{85D6}\x{85D7}' + . '\x{85D8}\x{85D9}\x{85DA}\x{85DB}\x{85DC}\x{85DD}\x{85DE}\x{85DF}\x{85E0}' + . '\x{85E1}\x{85E2}\x{85E3}\x{85E4}\x{85E5}\x{85E6}\x{85E7}\x{85E8}\x{85E9}' + . '\x{85EA}\x{85EB}\x{85EC}\x{85ED}\x{85EF}\x{85F0}\x{85F1}\x{85F2}\x{85F4}' + . '\x{85F5}\x{85F6}\x{85F7}\x{85F8}\x{85F9}\x{85FA}\x{85FB}\x{85FD}\x{85FE}' + . '\x{85FF}\x{8600}\x{8601}\x{8602}\x{8604}\x{8605}\x{8606}\x{8607}\x{8608}' + . '\x{8609}\x{860A}\x{860B}\x{860C}\x{860F}\x{8611}\x{8612}\x{8613}\x{8614}' + . '\x{8616}\x{8617}\x{8618}\x{8619}\x{861A}\x{861B}\x{861C}\x{861E}\x{861F}' + . '\x{8620}\x{8621}\x{8622}\x{8623}\x{8624}\x{8625}\x{8626}\x{8627}\x{8628}' + . '\x{8629}\x{862A}\x{862B}\x{862C}\x{862D}\x{862E}\x{862F}\x{8630}\x{8631}' + . '\x{8632}\x{8633}\x{8634}\x{8635}\x{8636}\x{8638}\x{8639}\x{863A}\x{863B}' + . '\x{863C}\x{863D}\x{863E}\x{863F}\x{8640}\x{8641}\x{8642}\x{8643}\x{8644}' + . '\x{8645}\x{8646}\x{8647}\x{8648}\x{8649}\x{864A}\x{864B}\x{864C}\x{864D}' + . '\x{864E}\x{864F}\x{8650}\x{8651}\x{8652}\x{8653}\x{8654}\x{8655}\x{8656}' + . '\x{8658}\x{8659}\x{865A}\x{865B}\x{865C}\x{865D}\x{865E}\x{865F}\x{8660}' + . '\x{8661}\x{8662}\x{8663}\x{8664}\x{8665}\x{8666}\x{8667}\x{8668}\x{8669}' + . '\x{866A}\x{866B}\x{866C}\x{866D}\x{866E}\x{866F}\x{8670}\x{8671}\x{8672}' + . '\x{8673}\x{8674}\x{8676}\x{8677}\x{8678}\x{8679}\x{867A}\x{867B}\x{867C}' + . '\x{867D}\x{867E}\x{867F}\x{8680}\x{8681}\x{8682}\x{8683}\x{8684}\x{8685}' + . '\x{8686}\x{8687}\x{8688}\x{868A}\x{868B}\x{868C}\x{868D}\x{868E}\x{868F}' + . '\x{8690}\x{8691}\x{8693}\x{8694}\x{8695}\x{8696}\x{8697}\x{8698}\x{8699}' + . '\x{869A}\x{869B}\x{869C}\x{869D}\x{869E}\x{869F}\x{86A1}\x{86A2}\x{86A3}' + . '\x{86A4}\x{86A5}\x{86A7}\x{86A8}\x{86A9}\x{86AA}\x{86AB}\x{86AC}\x{86AD}' + . '\x{86AE}\x{86AF}\x{86B0}\x{86B1}\x{86B2}\x{86B3}\x{86B4}\x{86B5}\x{86B6}' + . '\x{86B7}\x{86B8}\x{86B9}\x{86BA}\x{86BB}\x{86BC}\x{86BD}\x{86BE}\x{86BF}' + . '\x{86C0}\x{86C1}\x{86C2}\x{86C3}\x{86C4}\x{86C5}\x{86C6}\x{86C7}\x{86C8}' + . '\x{86C9}\x{86CA}\x{86CB}\x{86CC}\x{86CE}\x{86CF}\x{86D0}\x{86D1}\x{86D2}' + . '\x{86D3}\x{86D4}\x{86D6}\x{86D7}\x{86D8}\x{86D9}\x{86DA}\x{86DB}\x{86DC}' + . '\x{86DD}\x{86DE}\x{86DF}\x{86E1}\x{86E2}\x{86E3}\x{86E4}\x{86E5}\x{86E6}' + . '\x{86E8}\x{86E9}\x{86EA}\x{86EB}\x{86EC}\x{86ED}\x{86EE}\x{86EF}\x{86F0}' + . '\x{86F1}\x{86F2}\x{86F3}\x{86F4}\x{86F5}\x{86F6}\x{86F7}\x{86F8}\x{86F9}' + . '\x{86FA}\x{86FB}\x{86FC}\x{86FE}\x{86FF}\x{8700}\x{8701}\x{8702}\x{8703}' + . '\x{8704}\x{8705}\x{8706}\x{8707}\x{8708}\x{8709}\x{870A}\x{870B}\x{870C}' + . '\x{870D}\x{870E}\x{870F}\x{8710}\x{8711}\x{8712}\x{8713}\x{8714}\x{8715}' + . '\x{8716}\x{8717}\x{8718}\x{8719}\x{871A}\x{871B}\x{871C}\x{871E}\x{871F}' + . '\x{8720}\x{8721}\x{8722}\x{8723}\x{8724}\x{8725}\x{8726}\x{8727}\x{8728}' + . '\x{8729}\x{872A}\x{872B}\x{872C}\x{872D}\x{872E}\x{8730}\x{8731}\x{8732}' + . '\x{8733}\x{8734}\x{8735}\x{8736}\x{8737}\x{8738}\x{8739}\x{873A}\x{873B}' + . '\x{873C}\x{873E}\x{873F}\x{8740}\x{8741}\x{8742}\x{8743}\x{8744}\x{8746}' + . '\x{8747}\x{8748}\x{8749}\x{874A}\x{874C}\x{874D}\x{874E}\x{874F}\x{8750}' + . '\x{8751}\x{8752}\x{8753}\x{8754}\x{8755}\x{8756}\x{8757}\x{8758}\x{8759}' + . '\x{875A}\x{875B}\x{875C}\x{875D}\x{875E}\x{875F}\x{8760}\x{8761}\x{8762}' + . '\x{8763}\x{8764}\x{8765}\x{8766}\x{8767}\x{8768}\x{8769}\x{876A}\x{876B}' + . '\x{876C}\x{876D}\x{876E}\x{876F}\x{8770}\x{8772}\x{8773}\x{8774}\x{8775}' + . '\x{8776}\x{8777}\x{8778}\x{8779}\x{877A}\x{877B}\x{877C}\x{877D}\x{877E}' + . '\x{8780}\x{8781}\x{8782}\x{8783}\x{8784}\x{8785}\x{8786}\x{8787}\x{8788}' + . '\x{8789}\x{878A}\x{878B}\x{878C}\x{878D}\x{878F}\x{8790}\x{8791}\x{8792}' + . '\x{8793}\x{8794}\x{8795}\x{8796}\x{8797}\x{8798}\x{879A}\x{879B}\x{879C}' + . '\x{879D}\x{879E}\x{879F}\x{87A0}\x{87A1}\x{87A2}\x{87A3}\x{87A4}\x{87A5}' + . '\x{87A6}\x{87A7}\x{87A8}\x{87A9}\x{87AA}\x{87AB}\x{87AC}\x{87AD}\x{87AE}' + . '\x{87AF}\x{87B0}\x{87B1}\x{87B2}\x{87B3}\x{87B4}\x{87B5}\x{87B6}\x{87B7}' + . '\x{87B8}\x{87B9}\x{87BA}\x{87BB}\x{87BC}\x{87BD}\x{87BE}\x{87BF}\x{87C0}' + . '\x{87C1}\x{87C2}\x{87C3}\x{87C4}\x{87C5}\x{87C6}\x{87C7}\x{87C8}\x{87C9}' + . '\x{87CA}\x{87CB}\x{87CC}\x{87CD}\x{87CE}\x{87CF}\x{87D0}\x{87D1}\x{87D2}' + . '\x{87D3}\x{87D4}\x{87D5}\x{87D6}\x{87D7}\x{87D8}\x{87D9}\x{87DB}\x{87DC}' + . '\x{87DD}\x{87DE}\x{87DF}\x{87E0}\x{87E1}\x{87E2}\x{87E3}\x{87E4}\x{87E5}' + . '\x{87E6}\x{87E7}\x{87E8}\x{87E9}\x{87EA}\x{87EB}\x{87EC}\x{87ED}\x{87EE}' + . '\x{87EF}\x{87F1}\x{87F2}\x{87F3}\x{87F4}\x{87F5}\x{87F6}\x{87F7}\x{87F8}' + . '\x{87F9}\x{87FA}\x{87FB}\x{87FC}\x{87FD}\x{87FE}\x{87FF}\x{8800}\x{8801}' + . '\x{8802}\x{8803}\x{8804}\x{8805}\x{8806}\x{8808}\x{8809}\x{880A}\x{880B}' + . '\x{880C}\x{880D}\x{880E}\x{880F}\x{8810}\x{8811}\x{8813}\x{8814}\x{8815}' + . '\x{8816}\x{8817}\x{8818}\x{8819}\x{881A}\x{881B}\x{881C}\x{881D}\x{881E}' + . '\x{881F}\x{8820}\x{8821}\x{8822}\x{8823}\x{8824}\x{8825}\x{8826}\x{8827}' + . '\x{8828}\x{8829}\x{882A}\x{882B}\x{882C}\x{882E}\x{882F}\x{8830}\x{8831}' + . '\x{8832}\x{8833}\x{8834}\x{8835}\x{8836}\x{8837}\x{8838}\x{8839}\x{883B}' + . '\x{883C}\x{883D}\x{883E}\x{883F}\x{8840}\x{8841}\x{8842}\x{8843}\x{8844}' + . '\x{8845}\x{8846}\x{8848}\x{8849}\x{884A}\x{884B}\x{884C}\x{884D}\x{884E}' + . '\x{884F}\x{8850}\x{8851}\x{8852}\x{8853}\x{8854}\x{8855}\x{8856}\x{8857}' + . '\x{8859}\x{885A}\x{885B}\x{885D}\x{885E}\x{8860}\x{8861}\x{8862}\x{8863}' + . '\x{8864}\x{8865}\x{8866}\x{8867}\x{8868}\x{8869}\x{886A}\x{886B}\x{886C}' + . '\x{886D}\x{886E}\x{886F}\x{8870}\x{8871}\x{8872}\x{8873}\x{8874}\x{8875}' + . '\x{8876}\x{8877}\x{8878}\x{8879}\x{887B}\x{887C}\x{887D}\x{887E}\x{887F}' + . '\x{8880}\x{8881}\x{8882}\x{8883}\x{8884}\x{8885}\x{8886}\x{8887}\x{8888}' + . '\x{8889}\x{888A}\x{888B}\x{888C}\x{888D}\x{888E}\x{888F}\x{8890}\x{8891}' + . '\x{8892}\x{8893}\x{8894}\x{8895}\x{8896}\x{8897}\x{8898}\x{8899}\x{889A}' + . '\x{889B}\x{889C}\x{889D}\x{889E}\x{889F}\x{88A0}\x{88A1}\x{88A2}\x{88A3}' + . '\x{88A4}\x{88A5}\x{88A6}\x{88A7}\x{88A8}\x{88A9}\x{88AA}\x{88AB}\x{88AC}' + . '\x{88AD}\x{88AE}\x{88AF}\x{88B0}\x{88B1}\x{88B2}\x{88B3}\x{88B4}\x{88B6}' + . '\x{88B7}\x{88B8}\x{88B9}\x{88BA}\x{88BB}\x{88BC}\x{88BD}\x{88BE}\x{88BF}' + . '\x{88C0}\x{88C1}\x{88C2}\x{88C3}\x{88C4}\x{88C5}\x{88C6}\x{88C7}\x{88C8}' + . '\x{88C9}\x{88CA}\x{88CB}\x{88CC}\x{88CD}\x{88CE}\x{88CF}\x{88D0}\x{88D1}' + . '\x{88D2}\x{88D3}\x{88D4}\x{88D5}\x{88D6}\x{88D7}\x{88D8}\x{88D9}\x{88DA}' + . '\x{88DB}\x{88DC}\x{88DD}\x{88DE}\x{88DF}\x{88E0}\x{88E1}\x{88E2}\x{88E3}' + . '\x{88E4}\x{88E5}\x{88E7}\x{88E8}\x{88EA}\x{88EB}\x{88EC}\x{88EE}\x{88EF}' + . '\x{88F0}\x{88F1}\x{88F2}\x{88F3}\x{88F4}\x{88F5}\x{88F6}\x{88F7}\x{88F8}' + . '\x{88F9}\x{88FA}\x{88FB}\x{88FC}\x{88FD}\x{88FE}\x{88FF}\x{8900}\x{8901}' + . '\x{8902}\x{8904}\x{8905}\x{8906}\x{8907}\x{8908}\x{8909}\x{890A}\x{890B}' + . '\x{890C}\x{890D}\x{890E}\x{8910}\x{8911}\x{8912}\x{8913}\x{8914}\x{8915}' + . '\x{8916}\x{8917}\x{8918}\x{8919}\x{891A}\x{891B}\x{891C}\x{891D}\x{891E}' + . '\x{891F}\x{8920}\x{8921}\x{8922}\x{8923}\x{8925}\x{8926}\x{8927}\x{8928}' + . '\x{8929}\x{892A}\x{892B}\x{892C}\x{892D}\x{892E}\x{892F}\x{8930}\x{8931}' + . '\x{8932}\x{8933}\x{8934}\x{8935}\x{8936}\x{8937}\x{8938}\x{8939}\x{893A}' + . '\x{893B}\x{893C}\x{893D}\x{893E}\x{893F}\x{8940}\x{8941}\x{8942}\x{8943}' + . '\x{8944}\x{8945}\x{8946}\x{8947}\x{8948}\x{8949}\x{894A}\x{894B}\x{894C}' + . '\x{894E}\x{894F}\x{8950}\x{8951}\x{8952}\x{8953}\x{8954}\x{8955}\x{8956}' + . '\x{8957}\x{8958}\x{8959}\x{895A}\x{895B}\x{895C}\x{895D}\x{895E}\x{895F}' + . '\x{8960}\x{8961}\x{8962}\x{8963}\x{8964}\x{8966}\x{8967}\x{8968}\x{8969}' + . '\x{896A}\x{896B}\x{896C}\x{896D}\x{896E}\x{896F}\x{8970}\x{8971}\x{8972}' + . '\x{8973}\x{8974}\x{8976}\x{8977}\x{8978}\x{8979}\x{897A}\x{897B}\x{897C}' + . '\x{897E}\x{897F}\x{8980}\x{8981}\x{8982}\x{8983}\x{8984}\x{8985}\x{8986}' + . '\x{8987}\x{8988}\x{8989}\x{898A}\x{898B}\x{898C}\x{898E}\x{898F}\x{8991}' + . '\x{8992}\x{8993}\x{8995}\x{8996}\x{8997}\x{8998}\x{899A}\x{899B}\x{899C}' + . '\x{899D}\x{899E}\x{899F}\x{89A0}\x{89A1}\x{89A2}\x{89A3}\x{89A4}\x{89A5}' + . '\x{89A6}\x{89A7}\x{89A8}\x{89AA}\x{89AB}\x{89AC}\x{89AD}\x{89AE}\x{89AF}' + . '\x{89B1}\x{89B2}\x{89B3}\x{89B5}\x{89B6}\x{89B7}\x{89B8}\x{89B9}\x{89BA}' + . '\x{89BD}\x{89BE}\x{89BF}\x{89C0}\x{89C1}\x{89C2}\x{89C3}\x{89C4}\x{89C5}' + . '\x{89C6}\x{89C7}\x{89C8}\x{89C9}\x{89CA}\x{89CB}\x{89CC}\x{89CD}\x{89CE}' + . '\x{89CF}\x{89D0}\x{89D1}\x{89D2}\x{89D3}\x{89D4}\x{89D5}\x{89D6}\x{89D7}' + . '\x{89D8}\x{89D9}\x{89DA}\x{89DB}\x{89DC}\x{89DD}\x{89DE}\x{89DF}\x{89E0}' + . '\x{89E1}\x{89E2}\x{89E3}\x{89E4}\x{89E5}\x{89E6}\x{89E7}\x{89E8}\x{89E9}' + . '\x{89EA}\x{89EB}\x{89EC}\x{89ED}\x{89EF}\x{89F0}\x{89F1}\x{89F2}\x{89F3}' + . '\x{89F4}\x{89F6}\x{89F7}\x{89F8}\x{89FA}\x{89FB}\x{89FC}\x{89FE}\x{89FF}' + . '\x{8A00}\x{8A01}\x{8A02}\x{8A03}\x{8A04}\x{8A07}\x{8A08}\x{8A09}\x{8A0A}' + . '\x{8A0B}\x{8A0C}\x{8A0D}\x{8A0E}\x{8A0F}\x{8A10}\x{8A11}\x{8A12}\x{8A13}' + . '\x{8A15}\x{8A16}\x{8A17}\x{8A18}\x{8A1A}\x{8A1B}\x{8A1C}\x{8A1D}\x{8A1E}' + . '\x{8A1F}\x{8A22}\x{8A23}\x{8A24}\x{8A25}\x{8A26}\x{8A27}\x{8A28}\x{8A29}' + . '\x{8A2A}\x{8A2C}\x{8A2D}\x{8A2E}\x{8A2F}\x{8A30}\x{8A31}\x{8A32}\x{8A34}' + . '\x{8A35}\x{8A36}\x{8A37}\x{8A38}\x{8A39}\x{8A3A}\x{8A3B}\x{8A3C}\x{8A3E}' + . '\x{8A3F}\x{8A40}\x{8A41}\x{8A42}\x{8A43}\x{8A44}\x{8A45}\x{8A46}\x{8A47}' + . '\x{8A48}\x{8A49}\x{8A4A}\x{8A4C}\x{8A4D}\x{8A4E}\x{8A4F}\x{8A50}\x{8A51}' + . '\x{8A52}\x{8A53}\x{8A54}\x{8A55}\x{8A56}\x{8A57}\x{8A58}\x{8A59}\x{8A5A}' + . '\x{8A5B}\x{8A5C}\x{8A5D}\x{8A5E}\x{8A5F}\x{8A60}\x{8A61}\x{8A62}\x{8A63}' + . '\x{8A65}\x{8A66}\x{8A67}\x{8A68}\x{8A69}\x{8A6A}\x{8A6B}\x{8A6C}\x{8A6D}' + . '\x{8A6E}\x{8A6F}\x{8A70}\x{8A71}\x{8A72}\x{8A73}\x{8A74}\x{8A75}\x{8A76}' + . '\x{8A77}\x{8A79}\x{8A7A}\x{8A7B}\x{8A7C}\x{8A7E}\x{8A7F}\x{8A80}\x{8A81}' + . '\x{8A82}\x{8A83}\x{8A84}\x{8A85}\x{8A86}\x{8A87}\x{8A89}\x{8A8A}\x{8A8B}' + . '\x{8A8C}\x{8A8D}\x{8A8E}\x{8A8F}\x{8A90}\x{8A91}\x{8A92}\x{8A93}\x{8A94}' + . '\x{8A95}\x{8A96}\x{8A97}\x{8A98}\x{8A99}\x{8A9A}\x{8A9B}\x{8A9C}\x{8A9D}' + . '\x{8A9E}\x{8AA0}\x{8AA1}\x{8AA2}\x{8AA3}\x{8AA4}\x{8AA5}\x{8AA6}\x{8AA7}' + . '\x{8AA8}\x{8AA9}\x{8AAA}\x{8AAB}\x{8AAC}\x{8AAE}\x{8AB0}\x{8AB1}\x{8AB2}' + . '\x{8AB3}\x{8AB4}\x{8AB5}\x{8AB6}\x{8AB8}\x{8AB9}\x{8ABA}\x{8ABB}\x{8ABC}' + . '\x{8ABD}\x{8ABE}\x{8ABF}\x{8AC0}\x{8AC1}\x{8AC2}\x{8AC3}\x{8AC4}\x{8AC5}' + . '\x{8AC6}\x{8AC7}\x{8AC8}\x{8AC9}\x{8ACA}\x{8ACB}\x{8ACC}\x{8ACD}\x{8ACE}' + . '\x{8ACF}\x{8AD1}\x{8AD2}\x{8AD3}\x{8AD4}\x{8AD5}\x{8AD6}\x{8AD7}\x{8AD8}' + . '\x{8AD9}\x{8ADA}\x{8ADB}\x{8ADC}\x{8ADD}\x{8ADE}\x{8ADF}\x{8AE0}\x{8AE1}' + . '\x{8AE2}\x{8AE3}\x{8AE4}\x{8AE5}\x{8AE6}\x{8AE7}\x{8AE8}\x{8AE9}\x{8AEA}' + . '\x{8AEB}\x{8AED}\x{8AEE}\x{8AEF}\x{8AF0}\x{8AF1}\x{8AF2}\x{8AF3}\x{8AF4}' + . '\x{8AF5}\x{8AF6}\x{8AF7}\x{8AF8}\x{8AF9}\x{8AFA}\x{8AFB}\x{8AFC}\x{8AFD}' + . '\x{8AFE}\x{8AFF}\x{8B00}\x{8B01}\x{8B02}\x{8B03}\x{8B04}\x{8B05}\x{8B06}' + . '\x{8B07}\x{8B08}\x{8B09}\x{8B0A}\x{8B0B}\x{8B0D}\x{8B0E}\x{8B0F}\x{8B10}' + . '\x{8B11}\x{8B12}\x{8B13}\x{8B14}\x{8B15}\x{8B16}\x{8B17}\x{8B18}\x{8B19}' + . '\x{8B1A}\x{8B1B}\x{8B1C}\x{8B1D}\x{8B1E}\x{8B1F}\x{8B20}\x{8B21}\x{8B22}' + . '\x{8B23}\x{8B24}\x{8B25}\x{8B26}\x{8B27}\x{8B28}\x{8B2A}\x{8B2B}\x{8B2C}' + . '\x{8B2D}\x{8B2E}\x{8B2F}\x{8B30}\x{8B31}\x{8B33}\x{8B34}\x{8B35}\x{8B36}' + . '\x{8B37}\x{8B39}\x{8B3A}\x{8B3B}\x{8B3C}\x{8B3D}\x{8B3E}\x{8B40}\x{8B41}' + . '\x{8B42}\x{8B43}\x{8B44}\x{8B45}\x{8B46}\x{8B47}\x{8B48}\x{8B49}\x{8B4A}' + . '\x{8B4B}\x{8B4C}\x{8B4D}\x{8B4E}\x{8B4F}\x{8B50}\x{8B51}\x{8B52}\x{8B53}' + . '\x{8B54}\x{8B55}\x{8B56}\x{8B57}\x{8B58}\x{8B59}\x{8B5A}\x{8B5B}\x{8B5C}' + . '\x{8B5D}\x{8B5E}\x{8B5F}\x{8B60}\x{8B63}\x{8B64}\x{8B65}\x{8B66}\x{8B67}' + . '\x{8B68}\x{8B6A}\x{8B6B}\x{8B6C}\x{8B6D}\x{8B6E}\x{8B6F}\x{8B70}\x{8B71}' + . '\x{8B73}\x{8B74}\x{8B76}\x{8B77}\x{8B78}\x{8B79}\x{8B7A}\x{8B7B}\x{8B7D}' + . '\x{8B7E}\x{8B7F}\x{8B80}\x{8B82}\x{8B83}\x{8B84}\x{8B85}\x{8B86}\x{8B88}' + . '\x{8B89}\x{8B8A}\x{8B8B}\x{8B8C}\x{8B8E}\x{8B90}\x{8B91}\x{8B92}\x{8B93}' + . '\x{8B94}\x{8B95}\x{8B96}\x{8B97}\x{8B98}\x{8B99}\x{8B9A}\x{8B9C}\x{8B9D}' + . '\x{8B9E}\x{8B9F}\x{8BA0}\x{8BA1}\x{8BA2}\x{8BA3}\x{8BA4}\x{8BA5}\x{8BA6}' + . '\x{8BA7}\x{8BA8}\x{8BA9}\x{8BAA}\x{8BAB}\x{8BAC}\x{8BAD}\x{8BAE}\x{8BAF}' + . '\x{8BB0}\x{8BB1}\x{8BB2}\x{8BB3}\x{8BB4}\x{8BB5}\x{8BB6}\x{8BB7}\x{8BB8}' + . '\x{8BB9}\x{8BBA}\x{8BBB}\x{8BBC}\x{8BBD}\x{8BBE}\x{8BBF}\x{8BC0}\x{8BC1}' + . '\x{8BC2}\x{8BC3}\x{8BC4}\x{8BC5}\x{8BC6}\x{8BC7}\x{8BC8}\x{8BC9}\x{8BCA}' + . '\x{8BCB}\x{8BCC}\x{8BCD}\x{8BCE}\x{8BCF}\x{8BD0}\x{8BD1}\x{8BD2}\x{8BD3}' + . '\x{8BD4}\x{8BD5}\x{8BD6}\x{8BD7}\x{8BD8}\x{8BD9}\x{8BDA}\x{8BDB}\x{8BDC}' + . '\x{8BDD}\x{8BDE}\x{8BDF}\x{8BE0}\x{8BE1}\x{8BE2}\x{8BE3}\x{8BE4}\x{8BE5}' + . '\x{8BE6}\x{8BE7}\x{8BE8}\x{8BE9}\x{8BEA}\x{8BEB}\x{8BEC}\x{8BED}\x{8BEE}' + . '\x{8BEF}\x{8BF0}\x{8BF1}\x{8BF2}\x{8BF3}\x{8BF4}\x{8BF5}\x{8BF6}\x{8BF7}' + . '\x{8BF8}\x{8BF9}\x{8BFA}\x{8BFB}\x{8BFC}\x{8BFD}\x{8BFE}\x{8BFF}\x{8C00}' + . '\x{8C01}\x{8C02}\x{8C03}\x{8C04}\x{8C05}\x{8C06}\x{8C07}\x{8C08}\x{8C09}' + . '\x{8C0A}\x{8C0B}\x{8C0C}\x{8C0D}\x{8C0E}\x{8C0F}\x{8C10}\x{8C11}\x{8C12}' + . '\x{8C13}\x{8C14}\x{8C15}\x{8C16}\x{8C17}\x{8C18}\x{8C19}\x{8C1A}\x{8C1B}' + . '\x{8C1C}\x{8C1D}\x{8C1E}\x{8C1F}\x{8C20}\x{8C21}\x{8C22}\x{8C23}\x{8C24}' + . '\x{8C25}\x{8C26}\x{8C27}\x{8C28}\x{8C29}\x{8C2A}\x{8C2B}\x{8C2C}\x{8C2D}' + . '\x{8C2E}\x{8C2F}\x{8C30}\x{8C31}\x{8C32}\x{8C33}\x{8C34}\x{8C35}\x{8C36}' + . '\x{8C37}\x{8C39}\x{8C3A}\x{8C3B}\x{8C3C}\x{8C3D}\x{8C3E}\x{8C3F}\x{8C41}' + . '\x{8C42}\x{8C43}\x{8C45}\x{8C46}\x{8C47}\x{8C48}\x{8C49}\x{8C4A}\x{8C4B}' + . '\x{8C4C}\x{8C4D}\x{8C4E}\x{8C4F}\x{8C50}\x{8C54}\x{8C55}\x{8C56}\x{8C57}' + . '\x{8C59}\x{8C5A}\x{8C5B}\x{8C5C}\x{8C5D}\x{8C5E}\x{8C5F}\x{8C60}\x{8C61}' + . '\x{8C62}\x{8C63}\x{8C64}\x{8C65}\x{8C66}\x{8C67}\x{8C68}\x{8C69}\x{8C6A}' + . '\x{8C6B}\x{8C6C}\x{8C6D}\x{8C6E}\x{8C6F}\x{8C70}\x{8C71}\x{8C72}\x{8C73}' + . '\x{8C75}\x{8C76}\x{8C77}\x{8C78}\x{8C79}\x{8C7A}\x{8C7B}\x{8C7D}\x{8C7E}' + . '\x{8C80}\x{8C81}\x{8C82}\x{8C84}\x{8C85}\x{8C86}\x{8C88}\x{8C89}\x{8C8A}' + . '\x{8C8C}\x{8C8D}\x{8C8F}\x{8C90}\x{8C91}\x{8C92}\x{8C93}\x{8C94}\x{8C95}' + . '\x{8C96}\x{8C97}\x{8C98}\x{8C99}\x{8C9A}\x{8C9C}\x{8C9D}\x{8C9E}\x{8C9F}' + . '\x{8CA0}\x{8CA1}\x{8CA2}\x{8CA3}\x{8CA4}\x{8CA5}\x{8CA7}\x{8CA8}\x{8CA9}' + . '\x{8CAA}\x{8CAB}\x{8CAC}\x{8CAD}\x{8CAE}\x{8CAF}\x{8CB0}\x{8CB1}\x{8CB2}' + . '\x{8CB3}\x{8CB4}\x{8CB5}\x{8CB6}\x{8CB7}\x{8CB8}\x{8CB9}\x{8CBA}\x{8CBB}' + . '\x{8CBC}\x{8CBD}\x{8CBE}\x{8CBF}\x{8CC0}\x{8CC1}\x{8CC2}\x{8CC3}\x{8CC4}' + . '\x{8CC5}\x{8CC6}\x{8CC7}\x{8CC8}\x{8CC9}\x{8CCA}\x{8CCC}\x{8CCE}\x{8CCF}' + . '\x{8CD0}\x{8CD1}\x{8CD2}\x{8CD3}\x{8CD4}\x{8CD5}\x{8CD7}\x{8CD9}\x{8CDA}' + . '\x{8CDB}\x{8CDC}\x{8CDD}\x{8CDE}\x{8CDF}\x{8CE0}\x{8CE1}\x{8CE2}\x{8CE3}' + . '\x{8CE4}\x{8CE5}\x{8CE6}\x{8CE7}\x{8CE8}\x{8CEA}\x{8CEB}\x{8CEC}\x{8CED}' + . '\x{8CEE}\x{8CEF}\x{8CF0}\x{8CF1}\x{8CF2}\x{8CF3}\x{8CF4}\x{8CF5}\x{8CF6}' + . '\x{8CF8}\x{8CF9}\x{8CFA}\x{8CFB}\x{8CFC}\x{8CFD}\x{8CFE}\x{8CFF}\x{8D00}' + . '\x{8D02}\x{8D03}\x{8D04}\x{8D05}\x{8D06}\x{8D07}\x{8D08}\x{8D09}\x{8D0A}' + . '\x{8D0B}\x{8D0C}\x{8D0D}\x{8D0E}\x{8D0F}\x{8D10}\x{8D13}\x{8D14}\x{8D15}' + . '\x{8D16}\x{8D17}\x{8D18}\x{8D19}\x{8D1A}\x{8D1B}\x{8D1C}\x{8D1D}\x{8D1E}' + . '\x{8D1F}\x{8D20}\x{8D21}\x{8D22}\x{8D23}\x{8D24}\x{8D25}\x{8D26}\x{8D27}' + . '\x{8D28}\x{8D29}\x{8D2A}\x{8D2B}\x{8D2C}\x{8D2D}\x{8D2E}\x{8D2F}\x{8D30}' + . '\x{8D31}\x{8D32}\x{8D33}\x{8D34}\x{8D35}\x{8D36}\x{8D37}\x{8D38}\x{8D39}' + . '\x{8D3A}\x{8D3B}\x{8D3C}\x{8D3D}\x{8D3E}\x{8D3F}\x{8D40}\x{8D41}\x{8D42}' + . '\x{8D43}\x{8D44}\x{8D45}\x{8D46}\x{8D47}\x{8D48}\x{8D49}\x{8D4A}\x{8D4B}' + . '\x{8D4C}\x{8D4D}\x{8D4E}\x{8D4F}\x{8D50}\x{8D51}\x{8D52}\x{8D53}\x{8D54}' + . '\x{8D55}\x{8D56}\x{8D57}\x{8D58}\x{8D59}\x{8D5A}\x{8D5B}\x{8D5C}\x{8D5D}' + . '\x{8D5E}\x{8D5F}\x{8D60}\x{8D61}\x{8D62}\x{8D63}\x{8D64}\x{8D65}\x{8D66}' + . '\x{8D67}\x{8D68}\x{8D69}\x{8D6A}\x{8D6B}\x{8D6C}\x{8D6D}\x{8D6E}\x{8D6F}' + . '\x{8D70}\x{8D71}\x{8D72}\x{8D73}\x{8D74}\x{8D75}\x{8D76}\x{8D77}\x{8D78}' + . '\x{8D79}\x{8D7A}\x{8D7B}\x{8D7D}\x{8D7E}\x{8D7F}\x{8D80}\x{8D81}\x{8D82}' + . '\x{8D83}\x{8D84}\x{8D85}\x{8D86}\x{8D87}\x{8D88}\x{8D89}\x{8D8A}\x{8D8B}' + . '\x{8D8C}\x{8D8D}\x{8D8E}\x{8D8F}\x{8D90}\x{8D91}\x{8D92}\x{8D93}\x{8D94}' + . '\x{8D95}\x{8D96}\x{8D97}\x{8D98}\x{8D99}\x{8D9A}\x{8D9B}\x{8D9C}\x{8D9D}' + . '\x{8D9E}\x{8D9F}\x{8DA0}\x{8DA1}\x{8DA2}\x{8DA3}\x{8DA4}\x{8DA5}\x{8DA7}' + . '\x{8DA8}\x{8DA9}\x{8DAA}\x{8DAB}\x{8DAC}\x{8DAD}\x{8DAE}\x{8DAF}\x{8DB0}' + . '\x{8DB1}\x{8DB2}\x{8DB3}\x{8DB4}\x{8DB5}\x{8DB6}\x{8DB7}\x{8DB8}\x{8DB9}' + . '\x{8DBA}\x{8DBB}\x{8DBC}\x{8DBD}\x{8DBE}\x{8DBF}\x{8DC1}\x{8DC2}\x{8DC3}' + . '\x{8DC4}\x{8DC5}\x{8DC6}\x{8DC7}\x{8DC8}\x{8DC9}\x{8DCA}\x{8DCB}\x{8DCC}' + . '\x{8DCD}\x{8DCE}\x{8DCF}\x{8DD0}\x{8DD1}\x{8DD2}\x{8DD3}\x{8DD4}\x{8DD5}' + . '\x{8DD6}\x{8DD7}\x{8DD8}\x{8DD9}\x{8DDA}\x{8DDB}\x{8DDC}\x{8DDD}\x{8DDE}' + . '\x{8DDF}\x{8DE0}\x{8DE1}\x{8DE2}\x{8DE3}\x{8DE4}\x{8DE6}\x{8DE7}\x{8DE8}' + . '\x{8DE9}\x{8DEA}\x{8DEB}\x{8DEC}\x{8DED}\x{8DEE}\x{8DEF}\x{8DF0}\x{8DF1}' + . '\x{8DF2}\x{8DF3}\x{8DF4}\x{8DF5}\x{8DF6}\x{8DF7}\x{8DF8}\x{8DF9}\x{8DFA}' + . '\x{8DFB}\x{8DFC}\x{8DFD}\x{8DFE}\x{8DFF}\x{8E00}\x{8E02}\x{8E03}\x{8E04}' + . '\x{8E05}\x{8E06}\x{8E07}\x{8E08}\x{8E09}\x{8E0A}\x{8E0C}\x{8E0D}\x{8E0E}' + . '\x{8E0F}\x{8E10}\x{8E11}\x{8E12}\x{8E13}\x{8E14}\x{8E15}\x{8E16}\x{8E17}' + . '\x{8E18}\x{8E19}\x{8E1A}\x{8E1B}\x{8E1C}\x{8E1D}\x{8E1E}\x{8E1F}\x{8E20}' + . '\x{8E21}\x{8E22}\x{8E23}\x{8E24}\x{8E25}\x{8E26}\x{8E27}\x{8E28}\x{8E29}' + . '\x{8E2A}\x{8E2B}\x{8E2C}\x{8E2D}\x{8E2E}\x{8E2F}\x{8E30}\x{8E31}\x{8E33}' + . '\x{8E34}\x{8E35}\x{8E36}\x{8E37}\x{8E38}\x{8E39}\x{8E3A}\x{8E3B}\x{8E3C}' + . '\x{8E3D}\x{8E3E}\x{8E3F}\x{8E40}\x{8E41}\x{8E42}\x{8E43}\x{8E44}\x{8E45}' + . '\x{8E47}\x{8E48}\x{8E49}\x{8E4A}\x{8E4B}\x{8E4C}\x{8E4D}\x{8E4E}\x{8E50}' + . '\x{8E51}\x{8E52}\x{8E53}\x{8E54}\x{8E55}\x{8E56}\x{8E57}\x{8E58}\x{8E59}' + . '\x{8E5A}\x{8E5B}\x{8E5C}\x{8E5D}\x{8E5E}\x{8E5F}\x{8E60}\x{8E61}\x{8E62}' + . '\x{8E63}\x{8E64}\x{8E65}\x{8E66}\x{8E67}\x{8E68}\x{8E69}\x{8E6A}\x{8E6B}' + . '\x{8E6C}\x{8E6D}\x{8E6F}\x{8E70}\x{8E71}\x{8E72}\x{8E73}\x{8E74}\x{8E76}' + . '\x{8E78}\x{8E7A}\x{8E7B}\x{8E7C}\x{8E7D}\x{8E7E}\x{8E7F}\x{8E80}\x{8E81}' + . '\x{8E82}\x{8E83}\x{8E84}\x{8E85}\x{8E86}\x{8E87}\x{8E88}\x{8E89}\x{8E8A}' + . '\x{8E8B}\x{8E8C}\x{8E8D}\x{8E8E}\x{8E8F}\x{8E90}\x{8E91}\x{8E92}\x{8E93}' + . '\x{8E94}\x{8E95}\x{8E96}\x{8E97}\x{8E98}\x{8E9A}\x{8E9C}\x{8E9D}\x{8E9E}' + . '\x{8E9F}\x{8EA0}\x{8EA1}\x{8EA3}\x{8EA4}\x{8EA5}\x{8EA6}\x{8EA7}\x{8EA8}' + . '\x{8EA9}\x{8EAA}\x{8EAB}\x{8EAC}\x{8EAD}\x{8EAE}\x{8EAF}\x{8EB0}\x{8EB1}' + . '\x{8EB2}\x{8EB4}\x{8EB5}\x{8EB8}\x{8EB9}\x{8EBA}\x{8EBB}\x{8EBC}\x{8EBD}' + . '\x{8EBE}\x{8EBF}\x{8EC0}\x{8EC2}\x{8EC3}\x{8EC5}\x{8EC6}\x{8EC7}\x{8EC8}' + . '\x{8EC9}\x{8ECA}\x{8ECB}\x{8ECC}\x{8ECD}\x{8ECE}\x{8ECF}\x{8ED0}\x{8ED1}' + . '\x{8ED2}\x{8ED3}\x{8ED4}\x{8ED5}\x{8ED6}\x{8ED7}\x{8ED8}\x{8EDA}\x{8EDB}' + . '\x{8EDC}\x{8EDD}\x{8EDE}\x{8EDF}\x{8EE0}\x{8EE1}\x{8EE4}\x{8EE5}\x{8EE6}' + . '\x{8EE7}\x{8EE8}\x{8EE9}\x{8EEA}\x{8EEB}\x{8EEC}\x{8EED}\x{8EEE}\x{8EEF}' + . '\x{8EF1}\x{8EF2}\x{8EF3}\x{8EF4}\x{8EF5}\x{8EF6}\x{8EF7}\x{8EF8}\x{8EF9}' + . '\x{8EFA}\x{8EFB}\x{8EFC}\x{8EFD}\x{8EFE}\x{8EFF}\x{8F00}\x{8F01}\x{8F02}' + . '\x{8F03}\x{8F04}\x{8F05}\x{8F06}\x{8F07}\x{8F08}\x{8F09}\x{8F0A}\x{8F0B}' + . '\x{8F0D}\x{8F0E}\x{8F10}\x{8F11}\x{8F12}\x{8F13}\x{8F14}\x{8F15}\x{8F16}' + . '\x{8F17}\x{8F18}\x{8F1A}\x{8F1B}\x{8F1C}\x{8F1D}\x{8F1E}\x{8F1F}\x{8F20}' + . '\x{8F21}\x{8F22}\x{8F23}\x{8F24}\x{8F25}\x{8F26}\x{8F27}\x{8F28}\x{8F29}' + . '\x{8F2A}\x{8F2B}\x{8F2C}\x{8F2E}\x{8F2F}\x{8F30}\x{8F31}\x{8F32}\x{8F33}' + . '\x{8F34}\x{8F35}\x{8F36}\x{8F37}\x{8F38}\x{8F39}\x{8F3B}\x{8F3C}\x{8F3D}' + . '\x{8F3E}\x{8F3F}\x{8F40}\x{8F42}\x{8F43}\x{8F44}\x{8F45}\x{8F46}\x{8F47}' + . '\x{8F48}\x{8F49}\x{8F4A}\x{8F4B}\x{8F4C}\x{8F4D}\x{8F4E}\x{8F4F}\x{8F50}' + . '\x{8F51}\x{8F52}\x{8F53}\x{8F54}\x{8F55}\x{8F56}\x{8F57}\x{8F58}\x{8F59}' + . '\x{8F5A}\x{8F5B}\x{8F5D}\x{8F5E}\x{8F5F}\x{8F60}\x{8F61}\x{8F62}\x{8F63}' + . '\x{8F64}\x{8F65}\x{8F66}\x{8F67}\x{8F68}\x{8F69}\x{8F6A}\x{8F6B}\x{8F6C}' + . '\x{8F6D}\x{8F6E}\x{8F6F}\x{8F70}\x{8F71}\x{8F72}\x{8F73}\x{8F74}\x{8F75}' + . '\x{8F76}\x{8F77}\x{8F78}\x{8F79}\x{8F7A}\x{8F7B}\x{8F7C}\x{8F7D}\x{8F7E}' + . '\x{8F7F}\x{8F80}\x{8F81}\x{8F82}\x{8F83}\x{8F84}\x{8F85}\x{8F86}\x{8F87}' + . '\x{8F88}\x{8F89}\x{8F8A}\x{8F8B}\x{8F8C}\x{8F8D}\x{8F8E}\x{8F8F}\x{8F90}' + . '\x{8F91}\x{8F92}\x{8F93}\x{8F94}\x{8F95}\x{8F96}\x{8F97}\x{8F98}\x{8F99}' + . '\x{8F9A}\x{8F9B}\x{8F9C}\x{8F9E}\x{8F9F}\x{8FA0}\x{8FA1}\x{8FA2}\x{8FA3}' + . '\x{8FA5}\x{8FA6}\x{8FA7}\x{8FA8}\x{8FA9}\x{8FAA}\x{8FAB}\x{8FAC}\x{8FAD}' + . '\x{8FAE}\x{8FAF}\x{8FB0}\x{8FB1}\x{8FB2}\x{8FB4}\x{8FB5}\x{8FB6}\x{8FB7}' + . '\x{8FB8}\x{8FB9}\x{8FBB}\x{8FBC}\x{8FBD}\x{8FBE}\x{8FBF}\x{8FC0}\x{8FC1}' + . '\x{8FC2}\x{8FC4}\x{8FC5}\x{8FC6}\x{8FC7}\x{8FC8}\x{8FC9}\x{8FCB}\x{8FCC}' + . '\x{8FCD}\x{8FCE}\x{8FCF}\x{8FD0}\x{8FD1}\x{8FD2}\x{8FD3}\x{8FD4}\x{8FD5}' + . '\x{8FD6}\x{8FD7}\x{8FD8}\x{8FD9}\x{8FDA}\x{8FDB}\x{8FDC}\x{8FDD}\x{8FDE}' + . '\x{8FDF}\x{8FE0}\x{8FE1}\x{8FE2}\x{8FE3}\x{8FE4}\x{8FE5}\x{8FE6}\x{8FE8}' + . '\x{8FE9}\x{8FEA}\x{8FEB}\x{8FEC}\x{8FED}\x{8FEE}\x{8FEF}\x{8FF0}\x{8FF1}' + . '\x{8FF2}\x{8FF3}\x{8FF4}\x{8FF5}\x{8FF6}\x{8FF7}\x{8FF8}\x{8FF9}\x{8FFA}' + . '\x{8FFB}\x{8FFC}\x{8FFD}\x{8FFE}\x{8FFF}\x{9000}\x{9001}\x{9002}\x{9003}' + . '\x{9004}\x{9005}\x{9006}\x{9007}\x{9008}\x{9009}\x{900A}\x{900B}\x{900C}' + . '\x{900D}\x{900F}\x{9010}\x{9011}\x{9012}\x{9013}\x{9014}\x{9015}\x{9016}' + . '\x{9017}\x{9018}\x{9019}\x{901A}\x{901B}\x{901C}\x{901D}\x{901E}\x{901F}' + . '\x{9020}\x{9021}\x{9022}\x{9023}\x{9024}\x{9025}\x{9026}\x{9027}\x{9028}' + . '\x{9029}\x{902B}\x{902D}\x{902E}\x{902F}\x{9030}\x{9031}\x{9032}\x{9033}' + . '\x{9034}\x{9035}\x{9036}\x{9038}\x{903A}\x{903B}\x{903C}\x{903D}\x{903E}' + . '\x{903F}\x{9041}\x{9042}\x{9043}\x{9044}\x{9045}\x{9047}\x{9048}\x{9049}' + . '\x{904A}\x{904B}\x{904C}\x{904D}\x{904E}\x{904F}\x{9050}\x{9051}\x{9052}' + . '\x{9053}\x{9054}\x{9055}\x{9056}\x{9057}\x{9058}\x{9059}\x{905A}\x{905B}' + . '\x{905C}\x{905D}\x{905E}\x{905F}\x{9060}\x{9061}\x{9062}\x{9063}\x{9064}' + . '\x{9065}\x{9066}\x{9067}\x{9068}\x{9069}\x{906A}\x{906B}\x{906C}\x{906D}' + . '\x{906E}\x{906F}\x{9070}\x{9071}\x{9072}\x{9073}\x{9074}\x{9075}\x{9076}' + . '\x{9077}\x{9078}\x{9079}\x{907A}\x{907B}\x{907C}\x{907D}\x{907E}\x{907F}' + . '\x{9080}\x{9081}\x{9082}\x{9083}\x{9084}\x{9085}\x{9086}\x{9087}\x{9088}' + . '\x{9089}\x{908A}\x{908B}\x{908C}\x{908D}\x{908E}\x{908F}\x{9090}\x{9091}' + . '\x{9092}\x{9093}\x{9094}\x{9095}\x{9096}\x{9097}\x{9098}\x{9099}\x{909A}' + . '\x{909B}\x{909C}\x{909D}\x{909E}\x{909F}\x{90A0}\x{90A1}\x{90A2}\x{90A3}' + . '\x{90A4}\x{90A5}\x{90A6}\x{90A7}\x{90A8}\x{90A9}\x{90AA}\x{90AC}\x{90AD}' + . '\x{90AE}\x{90AF}\x{90B0}\x{90B1}\x{90B2}\x{90B3}\x{90B4}\x{90B5}\x{90B6}' + . '\x{90B7}\x{90B8}\x{90B9}\x{90BA}\x{90BB}\x{90BC}\x{90BD}\x{90BE}\x{90BF}' + . '\x{90C0}\x{90C1}\x{90C2}\x{90C3}\x{90C4}\x{90C5}\x{90C6}\x{90C7}\x{90C8}' + . '\x{90C9}\x{90CA}\x{90CB}\x{90CE}\x{90CF}\x{90D0}\x{90D1}\x{90D3}\x{90D4}' + . '\x{90D5}\x{90D6}\x{90D7}\x{90D8}\x{90D9}\x{90DA}\x{90DB}\x{90DC}\x{90DD}' + . '\x{90DE}\x{90DF}\x{90E0}\x{90E1}\x{90E2}\x{90E3}\x{90E4}\x{90E5}\x{90E6}' + . '\x{90E7}\x{90E8}\x{90E9}\x{90EA}\x{90EB}\x{90EC}\x{90ED}\x{90EE}\x{90EF}' + . '\x{90F0}\x{90F1}\x{90F2}\x{90F3}\x{90F4}\x{90F5}\x{90F7}\x{90F8}\x{90F9}' + . '\x{90FA}\x{90FB}\x{90FC}\x{90FD}\x{90FE}\x{90FF}\x{9100}\x{9101}\x{9102}' + . '\x{9103}\x{9104}\x{9105}\x{9106}\x{9107}\x{9108}\x{9109}\x{910B}\x{910C}' + . '\x{910D}\x{910E}\x{910F}\x{9110}\x{9111}\x{9112}\x{9113}\x{9114}\x{9115}' + . '\x{9116}\x{9117}\x{9118}\x{9119}\x{911A}\x{911B}\x{911C}\x{911D}\x{911E}' + . '\x{911F}\x{9120}\x{9121}\x{9122}\x{9123}\x{9124}\x{9125}\x{9126}\x{9127}' + . '\x{9128}\x{9129}\x{912A}\x{912B}\x{912C}\x{912D}\x{912E}\x{912F}\x{9130}' + . '\x{9131}\x{9132}\x{9133}\x{9134}\x{9135}\x{9136}\x{9137}\x{9138}\x{9139}' + . '\x{913A}\x{913B}\x{913E}\x{913F}\x{9140}\x{9141}\x{9142}\x{9143}\x{9144}' + . '\x{9145}\x{9146}\x{9147}\x{9148}\x{9149}\x{914A}\x{914B}\x{914C}\x{914D}' + . '\x{914E}\x{914F}\x{9150}\x{9151}\x{9152}\x{9153}\x{9154}\x{9155}\x{9156}' + . '\x{9157}\x{9158}\x{915A}\x{915B}\x{915C}\x{915D}\x{915E}\x{915F}\x{9160}' + . '\x{9161}\x{9162}\x{9163}\x{9164}\x{9165}\x{9166}\x{9167}\x{9168}\x{9169}' + . '\x{916A}\x{916B}\x{916C}\x{916D}\x{916E}\x{916F}\x{9170}\x{9171}\x{9172}' + . '\x{9173}\x{9174}\x{9175}\x{9176}\x{9177}\x{9178}\x{9179}\x{917A}\x{917C}' + . '\x{917D}\x{917E}\x{917F}\x{9180}\x{9181}\x{9182}\x{9183}\x{9184}\x{9185}' + . '\x{9186}\x{9187}\x{9188}\x{9189}\x{918A}\x{918B}\x{918C}\x{918D}\x{918E}' + . '\x{918F}\x{9190}\x{9191}\x{9192}\x{9193}\x{9194}\x{9196}\x{9199}\x{919A}' + . '\x{919B}\x{919C}\x{919D}\x{919E}\x{919F}\x{91A0}\x{91A1}\x{91A2}\x{91A3}' + . '\x{91A5}\x{91A6}\x{91A7}\x{91A8}\x{91AA}\x{91AB}\x{91AC}\x{91AD}\x{91AE}' + . '\x{91AF}\x{91B0}\x{91B1}\x{91B2}\x{91B3}\x{91B4}\x{91B5}\x{91B6}\x{91B7}' + . '\x{91B9}\x{91BA}\x{91BB}\x{91BC}\x{91BD}\x{91BE}\x{91C0}\x{91C1}\x{91C2}' + . '\x{91C3}\x{91C5}\x{91C6}\x{91C7}\x{91C9}\x{91CA}\x{91CB}\x{91CC}\x{91CD}' + . '\x{91CE}\x{91CF}\x{91D0}\x{91D1}\x{91D2}\x{91D3}\x{91D4}\x{91D5}\x{91D7}' + . '\x{91D8}\x{91D9}\x{91DA}\x{91DB}\x{91DC}\x{91DD}\x{91DE}\x{91DF}\x{91E2}' + . '\x{91E3}\x{91E4}\x{91E5}\x{91E6}\x{91E7}\x{91E8}\x{91E9}\x{91EA}\x{91EB}' + . '\x{91EC}\x{91ED}\x{91EE}\x{91F0}\x{91F1}\x{91F2}\x{91F3}\x{91F4}\x{91F5}' + . '\x{91F7}\x{91F8}\x{91F9}\x{91FA}\x{91FB}\x{91FD}\x{91FE}\x{91FF}\x{9200}' + . '\x{9201}\x{9202}\x{9203}\x{9204}\x{9205}\x{9206}\x{9207}\x{9208}\x{9209}' + . '\x{920A}\x{920B}\x{920C}\x{920D}\x{920E}\x{920F}\x{9210}\x{9211}\x{9212}' + . '\x{9214}\x{9215}\x{9216}\x{9217}\x{9218}\x{9219}\x{921A}\x{921B}\x{921C}' + . '\x{921D}\x{921E}\x{9220}\x{9221}\x{9223}\x{9224}\x{9225}\x{9226}\x{9227}' + . '\x{9228}\x{9229}\x{922A}\x{922B}\x{922D}\x{922E}\x{922F}\x{9230}\x{9231}' + . '\x{9232}\x{9233}\x{9234}\x{9235}\x{9236}\x{9237}\x{9238}\x{9239}\x{923A}' + . '\x{923B}\x{923C}\x{923D}\x{923E}\x{923F}\x{9240}\x{9241}\x{9242}\x{9245}' + . '\x{9246}\x{9247}\x{9248}\x{9249}\x{924A}\x{924B}\x{924C}\x{924D}\x{924E}' + . '\x{924F}\x{9250}\x{9251}\x{9252}\x{9253}\x{9254}\x{9255}\x{9256}\x{9257}' + . '\x{9258}\x{9259}\x{925A}\x{925B}\x{925C}\x{925D}\x{925E}\x{925F}\x{9260}' + . '\x{9261}\x{9262}\x{9263}\x{9264}\x{9265}\x{9266}\x{9267}\x{9268}\x{926B}' + . '\x{926C}\x{926D}\x{926E}\x{926F}\x{9270}\x{9272}\x{9273}\x{9274}\x{9275}' + . '\x{9276}\x{9277}\x{9278}\x{9279}\x{927A}\x{927B}\x{927C}\x{927D}\x{927E}' + . '\x{927F}\x{9280}\x{9282}\x{9283}\x{9285}\x{9286}\x{9287}\x{9288}\x{9289}' + . '\x{928A}\x{928B}\x{928C}\x{928D}\x{928E}\x{928F}\x{9290}\x{9291}\x{9292}' + . '\x{9293}\x{9294}\x{9295}\x{9296}\x{9297}\x{9298}\x{9299}\x{929A}\x{929B}' + . '\x{929C}\x{929D}\x{929F}\x{92A0}\x{92A1}\x{92A2}\x{92A3}\x{92A4}\x{92A5}' + . '\x{92A6}\x{92A7}\x{92A8}\x{92A9}\x{92AA}\x{92AB}\x{92AC}\x{92AD}\x{92AE}' + . '\x{92AF}\x{92B0}\x{92B1}\x{92B2}\x{92B3}\x{92B4}\x{92B5}\x{92B6}\x{92B7}' + . '\x{92B8}\x{92B9}\x{92BA}\x{92BB}\x{92BC}\x{92BE}\x{92BF}\x{92C0}\x{92C1}' + . '\x{92C2}\x{92C3}\x{92C4}\x{92C5}\x{92C6}\x{92C7}\x{92C8}\x{92C9}\x{92CA}' + . '\x{92CB}\x{92CC}\x{92CD}\x{92CE}\x{92CF}\x{92D0}\x{92D1}\x{92D2}\x{92D3}' + . '\x{92D5}\x{92D6}\x{92D7}\x{92D8}\x{92D9}\x{92DA}\x{92DC}\x{92DD}\x{92DE}' + . '\x{92DF}\x{92E0}\x{92E1}\x{92E3}\x{92E4}\x{92E5}\x{92E6}\x{92E7}\x{92E8}' + . '\x{92E9}\x{92EA}\x{92EB}\x{92EC}\x{92ED}\x{92EE}\x{92EF}\x{92F0}\x{92F1}' + . '\x{92F2}\x{92F3}\x{92F4}\x{92F5}\x{92F6}\x{92F7}\x{92F8}\x{92F9}\x{92FA}' + . '\x{92FB}\x{92FC}\x{92FD}\x{92FE}\x{92FF}\x{9300}\x{9301}\x{9302}\x{9303}' + . '\x{9304}\x{9305}\x{9306}\x{9307}\x{9308}\x{9309}\x{930A}\x{930B}\x{930C}' + . '\x{930D}\x{930E}\x{930F}\x{9310}\x{9311}\x{9312}\x{9313}\x{9314}\x{9315}' + . '\x{9316}\x{9317}\x{9318}\x{9319}\x{931A}\x{931B}\x{931D}\x{931E}\x{931F}' + . '\x{9320}\x{9321}\x{9322}\x{9323}\x{9324}\x{9325}\x{9326}\x{9327}\x{9328}' + . '\x{9329}\x{932A}\x{932B}\x{932D}\x{932E}\x{932F}\x{9332}\x{9333}\x{9334}' + . '\x{9335}\x{9336}\x{9337}\x{9338}\x{9339}\x{933A}\x{933B}\x{933C}\x{933D}' + . '\x{933E}\x{933F}\x{9340}\x{9341}\x{9342}\x{9343}\x{9344}\x{9345}\x{9346}' + . '\x{9347}\x{9348}\x{9349}\x{934A}\x{934B}\x{934C}\x{934D}\x{934E}\x{934F}' + . '\x{9350}\x{9351}\x{9352}\x{9353}\x{9354}\x{9355}\x{9356}\x{9357}\x{9358}' + . '\x{9359}\x{935A}\x{935B}\x{935C}\x{935D}\x{935E}\x{935F}\x{9360}\x{9361}' + . '\x{9363}\x{9364}\x{9365}\x{9366}\x{9367}\x{9369}\x{936A}\x{936C}\x{936D}' + . '\x{936E}\x{9370}\x{9371}\x{9372}\x{9374}\x{9375}\x{9376}\x{9377}\x{9379}' + . '\x{937A}\x{937B}\x{937C}\x{937D}\x{937E}\x{9380}\x{9382}\x{9383}\x{9384}' + . '\x{9385}\x{9386}\x{9387}\x{9388}\x{9389}\x{938A}\x{938C}\x{938D}\x{938E}' + . '\x{938F}\x{9390}\x{9391}\x{9392}\x{9393}\x{9394}\x{9395}\x{9396}\x{9397}' + . '\x{9398}\x{9399}\x{939A}\x{939B}\x{939D}\x{939E}\x{939F}\x{93A1}\x{93A2}' + . '\x{93A3}\x{93A4}\x{93A5}\x{93A6}\x{93A7}\x{93A8}\x{93A9}\x{93AA}\x{93AC}' + . '\x{93AD}\x{93AE}\x{93AF}\x{93B0}\x{93B1}\x{93B2}\x{93B3}\x{93B4}\x{93B5}' + . '\x{93B6}\x{93B7}\x{93B8}\x{93B9}\x{93BA}\x{93BC}\x{93BD}\x{93BE}\x{93BF}' + . '\x{93C0}\x{93C1}\x{93C2}\x{93C3}\x{93C4}\x{93C5}\x{93C6}\x{93C7}\x{93C8}' + . '\x{93C9}\x{93CA}\x{93CB}\x{93CC}\x{93CD}\x{93CE}\x{93CF}\x{93D0}\x{93D1}' + . '\x{93D2}\x{93D3}\x{93D4}\x{93D5}\x{93D6}\x{93D7}\x{93D8}\x{93D9}\x{93DA}' + . '\x{93DB}\x{93DC}\x{93DD}\x{93DE}\x{93DF}\x{93E1}\x{93E2}\x{93E3}\x{93E4}' + . '\x{93E6}\x{93E7}\x{93E8}\x{93E9}\x{93EA}\x{93EB}\x{93EC}\x{93ED}\x{93EE}' + . '\x{93EF}\x{93F0}\x{93F1}\x{93F2}\x{93F4}\x{93F5}\x{93F6}\x{93F7}\x{93F8}' + . '\x{93F9}\x{93FA}\x{93FB}\x{93FC}\x{93FD}\x{93FE}\x{93FF}\x{9400}\x{9401}' + . '\x{9403}\x{9404}\x{9405}\x{9406}\x{9407}\x{9408}\x{9409}\x{940A}\x{940B}' + . '\x{940C}\x{940D}\x{940E}\x{940F}\x{9410}\x{9411}\x{9412}\x{9413}\x{9414}' + . '\x{9415}\x{9416}\x{9418}\x{9419}\x{941B}\x{941D}\x{9420}\x{9422}\x{9423}' + . '\x{9425}\x{9426}\x{9427}\x{9428}\x{9429}\x{942A}\x{942B}\x{942C}\x{942D}' + . '\x{942E}\x{942F}\x{9430}\x{9431}\x{9432}\x{9433}\x{9434}\x{9435}\x{9436}' + . '\x{9437}\x{9438}\x{9439}\x{943A}\x{943B}\x{943C}\x{943D}\x{943E}\x{943F}' + . '\x{9440}\x{9441}\x{9442}\x{9444}\x{9445}\x{9446}\x{9447}\x{9448}\x{9449}' + . '\x{944A}\x{944B}\x{944C}\x{944D}\x{944F}\x{9450}\x{9451}\x{9452}\x{9453}' + . '\x{9454}\x{9455}\x{9456}\x{9457}\x{9458}\x{9459}\x{945B}\x{945C}\x{945D}' + . '\x{945E}\x{945F}\x{9460}\x{9461}\x{9462}\x{9463}\x{9464}\x{9465}\x{9466}' + . '\x{9467}\x{9468}\x{9469}\x{946A}\x{946B}\x{946D}\x{946E}\x{946F}\x{9470}' + . '\x{9471}\x{9472}\x{9473}\x{9474}\x{9475}\x{9476}\x{9477}\x{9478}\x{9479}' + . '\x{947A}\x{947C}\x{947D}\x{947E}\x{947F}\x{9480}\x{9481}\x{9482}\x{9483}' + . '\x{9484}\x{9485}\x{9486}\x{9487}\x{9488}\x{9489}\x{948A}\x{948B}\x{948C}' + . '\x{948D}\x{948E}\x{948F}\x{9490}\x{9491}\x{9492}\x{9493}\x{9494}\x{9495}' + . '\x{9496}\x{9497}\x{9498}\x{9499}\x{949A}\x{949B}\x{949C}\x{949D}\x{949E}' + . '\x{949F}\x{94A0}\x{94A1}\x{94A2}\x{94A3}\x{94A4}\x{94A5}\x{94A6}\x{94A7}' + . '\x{94A8}\x{94A9}\x{94AA}\x{94AB}\x{94AC}\x{94AD}\x{94AE}\x{94AF}\x{94B0}' + . '\x{94B1}\x{94B2}\x{94B3}\x{94B4}\x{94B5}\x{94B6}\x{94B7}\x{94B8}\x{94B9}' + . '\x{94BA}\x{94BB}\x{94BC}\x{94BD}\x{94BE}\x{94BF}\x{94C0}\x{94C1}\x{94C2}' + . '\x{94C3}\x{94C4}\x{94C5}\x{94C6}\x{94C7}\x{94C8}\x{94C9}\x{94CA}\x{94CB}' + . '\x{94CC}\x{94CD}\x{94CE}\x{94CF}\x{94D0}\x{94D1}\x{94D2}\x{94D3}\x{94D4}' + . '\x{94D5}\x{94D6}\x{94D7}\x{94D8}\x{94D9}\x{94DA}\x{94DB}\x{94DC}\x{94DD}' + . '\x{94DE}\x{94DF}\x{94E0}\x{94E1}\x{94E2}\x{94E3}\x{94E4}\x{94E5}\x{94E6}' + . '\x{94E7}\x{94E8}\x{94E9}\x{94EA}\x{94EB}\x{94EC}\x{94ED}\x{94EE}\x{94EF}' + . '\x{94F0}\x{94F1}\x{94F2}\x{94F3}\x{94F4}\x{94F5}\x{94F6}\x{94F7}\x{94F8}' + . '\x{94F9}\x{94FA}\x{94FB}\x{94FC}\x{94FD}\x{94FE}\x{94FF}\x{9500}\x{9501}' + . '\x{9502}\x{9503}\x{9504}\x{9505}\x{9506}\x{9507}\x{9508}\x{9509}\x{950A}' + . '\x{950B}\x{950C}\x{950D}\x{950E}\x{950F}\x{9510}\x{9511}\x{9512}\x{9513}' + . '\x{9514}\x{9515}\x{9516}\x{9517}\x{9518}\x{9519}\x{951A}\x{951B}\x{951C}' + . '\x{951D}\x{951E}\x{951F}\x{9520}\x{9521}\x{9522}\x{9523}\x{9524}\x{9525}' + . '\x{9526}\x{9527}\x{9528}\x{9529}\x{952A}\x{952B}\x{952C}\x{952D}\x{952E}' + . '\x{952F}\x{9530}\x{9531}\x{9532}\x{9533}\x{9534}\x{9535}\x{9536}\x{9537}' + . '\x{9538}\x{9539}\x{953A}\x{953B}\x{953C}\x{953D}\x{953E}\x{953F}\x{9540}' + . '\x{9541}\x{9542}\x{9543}\x{9544}\x{9545}\x{9546}\x{9547}\x{9548}\x{9549}' + . '\x{954A}\x{954B}\x{954C}\x{954D}\x{954E}\x{954F}\x{9550}\x{9551}\x{9552}' + . '\x{9553}\x{9554}\x{9555}\x{9556}\x{9557}\x{9558}\x{9559}\x{955A}\x{955B}' + . '\x{955C}\x{955D}\x{955E}\x{955F}\x{9560}\x{9561}\x{9562}\x{9563}\x{9564}' + . '\x{9565}\x{9566}\x{9567}\x{9568}\x{9569}\x{956A}\x{956B}\x{956C}\x{956D}' + . '\x{956E}\x{956F}\x{9570}\x{9571}\x{9572}\x{9573}\x{9574}\x{9575}\x{9576}' + . '\x{9577}\x{957A}\x{957B}\x{957C}\x{957D}\x{957F}\x{9580}\x{9581}\x{9582}' + . '\x{9583}\x{9584}\x{9586}\x{9587}\x{9588}\x{9589}\x{958A}\x{958B}\x{958C}' + . '\x{958D}\x{958E}\x{958F}\x{9590}\x{9591}\x{9592}\x{9593}\x{9594}\x{9595}' + . '\x{9596}\x{9598}\x{9599}\x{959A}\x{959B}\x{959C}\x{959D}\x{959E}\x{959F}' + . '\x{95A1}\x{95A2}\x{95A3}\x{95A4}\x{95A5}\x{95A6}\x{95A7}\x{95A8}\x{95A9}' + . '\x{95AA}\x{95AB}\x{95AC}\x{95AD}\x{95AE}\x{95AF}\x{95B0}\x{95B1}\x{95B2}' + . '\x{95B5}\x{95B6}\x{95B7}\x{95B9}\x{95BA}\x{95BB}\x{95BC}\x{95BD}\x{95BE}' + . '\x{95BF}\x{95C0}\x{95C2}\x{95C3}\x{95C4}\x{95C5}\x{95C6}\x{95C7}\x{95C8}' + . '\x{95C9}\x{95CA}\x{95CB}\x{95CC}\x{95CD}\x{95CE}\x{95CF}\x{95D0}\x{95D1}' + . '\x{95D2}\x{95D3}\x{95D4}\x{95D5}\x{95D6}\x{95D7}\x{95D8}\x{95DA}\x{95DB}' + . '\x{95DC}\x{95DE}\x{95DF}\x{95E0}\x{95E1}\x{95E2}\x{95E3}\x{95E4}\x{95E5}' + . '\x{95E6}\x{95E7}\x{95E8}\x{95E9}\x{95EA}\x{95EB}\x{95EC}\x{95ED}\x{95EE}' + . '\x{95EF}\x{95F0}\x{95F1}\x{95F2}\x{95F3}\x{95F4}\x{95F5}\x{95F6}\x{95F7}' + . '\x{95F8}\x{95F9}\x{95FA}\x{95FB}\x{95FC}\x{95FD}\x{95FE}\x{95FF}\x{9600}' + . '\x{9601}\x{9602}\x{9603}\x{9604}\x{9605}\x{9606}\x{9607}\x{9608}\x{9609}' + . '\x{960A}\x{960B}\x{960C}\x{960D}\x{960E}\x{960F}\x{9610}\x{9611}\x{9612}' + . '\x{9613}\x{9614}\x{9615}\x{9616}\x{9617}\x{9618}\x{9619}\x{961A}\x{961B}' + . '\x{961C}\x{961D}\x{961E}\x{961F}\x{9620}\x{9621}\x{9622}\x{9623}\x{9624}' + . '\x{9627}\x{9628}\x{962A}\x{962B}\x{962C}\x{962D}\x{962E}\x{962F}\x{9630}' + . '\x{9631}\x{9632}\x{9633}\x{9634}\x{9635}\x{9636}\x{9637}\x{9638}\x{9639}' + . '\x{963A}\x{963B}\x{963C}\x{963D}\x{963F}\x{9640}\x{9641}\x{9642}\x{9643}' + . '\x{9644}\x{9645}\x{9646}\x{9647}\x{9648}\x{9649}\x{964A}\x{964B}\x{964C}' + . '\x{964D}\x{964E}\x{964F}\x{9650}\x{9651}\x{9652}\x{9653}\x{9654}\x{9655}' + . '\x{9658}\x{9659}\x{965A}\x{965B}\x{965C}\x{965D}\x{965E}\x{965F}\x{9660}' + . '\x{9661}\x{9662}\x{9663}\x{9664}\x{9666}\x{9667}\x{9668}\x{9669}\x{966A}' + . '\x{966B}\x{966C}\x{966D}\x{966E}\x{966F}\x{9670}\x{9671}\x{9672}\x{9673}' + . '\x{9674}\x{9675}\x{9676}\x{9677}\x{9678}\x{967C}\x{967D}\x{967E}\x{9680}' + . '\x{9683}\x{9684}\x{9685}\x{9686}\x{9687}\x{9688}\x{9689}\x{968A}\x{968B}' + . '\x{968D}\x{968E}\x{968F}\x{9690}\x{9691}\x{9692}\x{9693}\x{9694}\x{9695}' + . '\x{9697}\x{9698}\x{9699}\x{969B}\x{969C}\x{969E}\x{96A0}\x{96A1}\x{96A2}' + . '\x{96A3}\x{96A4}\x{96A5}\x{96A6}\x{96A7}\x{96A8}\x{96A9}\x{96AA}\x{96AC}' + . '\x{96AD}\x{96AE}\x{96B0}\x{96B1}\x{96B3}\x{96B4}\x{96B6}\x{96B7}\x{96B8}' + . '\x{96B9}\x{96BA}\x{96BB}\x{96BC}\x{96BD}\x{96BE}\x{96BF}\x{96C0}\x{96C1}' + . '\x{96C2}\x{96C3}\x{96C4}\x{96C5}\x{96C6}\x{96C7}\x{96C8}\x{96C9}\x{96CA}' + . '\x{96CB}\x{96CC}\x{96CD}\x{96CE}\x{96CF}\x{96D0}\x{96D1}\x{96D2}\x{96D3}' + . '\x{96D4}\x{96D5}\x{96D6}\x{96D7}\x{96D8}\x{96D9}\x{96DA}\x{96DB}\x{96DC}' + . '\x{96DD}\x{96DE}\x{96DF}\x{96E0}\x{96E1}\x{96E2}\x{96E3}\x{96E5}\x{96E8}' + . '\x{96E9}\x{96EA}\x{96EB}\x{96EC}\x{96ED}\x{96EE}\x{96EF}\x{96F0}\x{96F1}' + . '\x{96F2}\x{96F3}\x{96F4}\x{96F5}\x{96F6}\x{96F7}\x{96F8}\x{96F9}\x{96FA}' + . '\x{96FB}\x{96FD}\x{96FE}\x{96FF}\x{9700}\x{9701}\x{9702}\x{9703}\x{9704}' + . '\x{9705}\x{9706}\x{9707}\x{9708}\x{9709}\x{970A}\x{970B}\x{970C}\x{970D}' + . '\x{970E}\x{970F}\x{9710}\x{9711}\x{9712}\x{9713}\x{9715}\x{9716}\x{9718}' + . '\x{9719}\x{971C}\x{971D}\x{971E}\x{971F}\x{9720}\x{9721}\x{9722}\x{9723}' + . '\x{9724}\x{9725}\x{9726}\x{9727}\x{9728}\x{9729}\x{972A}\x{972B}\x{972C}' + . '\x{972D}\x{972E}\x{972F}\x{9730}\x{9731}\x{9732}\x{9735}\x{9736}\x{9738}' + . '\x{9739}\x{973A}\x{973B}\x{973C}\x{973D}\x{973E}\x{973F}\x{9742}\x{9743}' + . '\x{9744}\x{9745}\x{9746}\x{9747}\x{9748}\x{9749}\x{974A}\x{974B}\x{974C}' + . '\x{974E}\x{974F}\x{9750}\x{9751}\x{9752}\x{9753}\x{9754}\x{9755}\x{9756}' + . '\x{9758}\x{9759}\x{975A}\x{975B}\x{975C}\x{975D}\x{975E}\x{975F}\x{9760}' + . '\x{9761}\x{9762}\x{9765}\x{9766}\x{9767}\x{9768}\x{9769}\x{976A}\x{976B}' + . '\x{976C}\x{976D}\x{976E}\x{976F}\x{9770}\x{9772}\x{9773}\x{9774}\x{9776}' + . '\x{9777}\x{9778}\x{9779}\x{977A}\x{977B}\x{977C}\x{977D}\x{977E}\x{977F}' + . '\x{9780}\x{9781}\x{9782}\x{9783}\x{9784}\x{9785}\x{9786}\x{9788}\x{978A}' + . '\x{978B}\x{978C}\x{978D}\x{978E}\x{978F}\x{9790}\x{9791}\x{9792}\x{9793}' + . '\x{9794}\x{9795}\x{9796}\x{9797}\x{9798}\x{9799}\x{979A}\x{979C}\x{979D}' + . '\x{979E}\x{979F}\x{97A0}\x{97A1}\x{97A2}\x{97A3}\x{97A4}\x{97A5}\x{97A6}' + . '\x{97A7}\x{97A8}\x{97AA}\x{97AB}\x{97AC}\x{97AD}\x{97AE}\x{97AF}\x{97B2}' + . '\x{97B3}\x{97B4}\x{97B6}\x{97B7}\x{97B8}\x{97B9}\x{97BA}\x{97BB}\x{97BC}' + . '\x{97BD}\x{97BF}\x{97C1}\x{97C2}\x{97C3}\x{97C4}\x{97C5}\x{97C6}\x{97C7}' + . '\x{97C8}\x{97C9}\x{97CA}\x{97CB}\x{97CC}\x{97CD}\x{97CE}\x{97CF}\x{97D0}' + . '\x{97D1}\x{97D3}\x{97D4}\x{97D5}\x{97D6}\x{97D7}\x{97D8}\x{97D9}\x{97DA}' + . '\x{97DB}\x{97DC}\x{97DD}\x{97DE}\x{97DF}\x{97E0}\x{97E1}\x{97E2}\x{97E3}' + . '\x{97E4}\x{97E5}\x{97E6}\x{97E7}\x{97E8}\x{97E9}\x{97EA}\x{97EB}\x{97EC}' + . '\x{97ED}\x{97EE}\x{97EF}\x{97F0}\x{97F1}\x{97F2}\x{97F3}\x{97F4}\x{97F5}' + . '\x{97F6}\x{97F7}\x{97F8}\x{97F9}\x{97FA}\x{97FB}\x{97FD}\x{97FE}\x{97FF}' + . '\x{9800}\x{9801}\x{9802}\x{9803}\x{9804}\x{9805}\x{9806}\x{9807}\x{9808}' + . '\x{9809}\x{980A}\x{980B}\x{980C}\x{980D}\x{980E}\x{980F}\x{9810}\x{9811}' + . '\x{9812}\x{9813}\x{9814}\x{9815}\x{9816}\x{9817}\x{9818}\x{9819}\x{981A}' + . '\x{981B}\x{981C}\x{981D}\x{981E}\x{9820}\x{9821}\x{9822}\x{9823}\x{9824}' + . '\x{9826}\x{9827}\x{9828}\x{9829}\x{982B}\x{982D}\x{982E}\x{982F}\x{9830}' + . '\x{9831}\x{9832}\x{9834}\x{9835}\x{9836}\x{9837}\x{9838}\x{9839}\x{983B}' + . '\x{983C}\x{983D}\x{983F}\x{9840}\x{9841}\x{9843}\x{9844}\x{9845}\x{9846}' + . '\x{9848}\x{9849}\x{984A}\x{984C}\x{984D}\x{984E}\x{984F}\x{9850}\x{9851}' + . '\x{9852}\x{9853}\x{9854}\x{9855}\x{9857}\x{9858}\x{9859}\x{985A}\x{985B}' + . '\x{985C}\x{985D}\x{985E}\x{985F}\x{9860}\x{9861}\x{9862}\x{9863}\x{9864}' + . '\x{9865}\x{9867}\x{9869}\x{986A}\x{986B}\x{986C}\x{986D}\x{986E}\x{986F}' + . '\x{9870}\x{9871}\x{9872}\x{9873}\x{9874}\x{9875}\x{9876}\x{9877}\x{9878}' + . '\x{9879}\x{987A}\x{987B}\x{987C}\x{987D}\x{987E}\x{987F}\x{9880}\x{9881}' + . '\x{9882}\x{9883}\x{9884}\x{9885}\x{9886}\x{9887}\x{9888}\x{9889}\x{988A}' + . '\x{988B}\x{988C}\x{988D}\x{988E}\x{988F}\x{9890}\x{9891}\x{9892}\x{9893}' + . '\x{9894}\x{9895}\x{9896}\x{9897}\x{9898}\x{9899}\x{989A}\x{989B}\x{989C}' + . '\x{989D}\x{989E}\x{989F}\x{98A0}\x{98A1}\x{98A2}\x{98A3}\x{98A4}\x{98A5}' + . '\x{98A6}\x{98A7}\x{98A8}\x{98A9}\x{98AA}\x{98AB}\x{98AC}\x{98AD}\x{98AE}' + . '\x{98AF}\x{98B0}\x{98B1}\x{98B2}\x{98B3}\x{98B4}\x{98B5}\x{98B6}\x{98B8}' + . '\x{98B9}\x{98BA}\x{98BB}\x{98BC}\x{98BD}\x{98BE}\x{98BF}\x{98C0}\x{98C1}' + . '\x{98C2}\x{98C3}\x{98C4}\x{98C5}\x{98C6}\x{98C8}\x{98C9}\x{98CB}\x{98CC}' + . '\x{98CD}\x{98CE}\x{98CF}\x{98D0}\x{98D1}\x{98D2}\x{98D3}\x{98D4}\x{98D5}' + . '\x{98D6}\x{98D7}\x{98D8}\x{98D9}\x{98DA}\x{98DB}\x{98DC}\x{98DD}\x{98DE}' + . '\x{98DF}\x{98E0}\x{98E2}\x{98E3}\x{98E5}\x{98E6}\x{98E7}\x{98E8}\x{98E9}' + . '\x{98EA}\x{98EB}\x{98ED}\x{98EF}\x{98F0}\x{98F2}\x{98F3}\x{98F4}\x{98F5}' + . '\x{98F6}\x{98F7}\x{98F9}\x{98FA}\x{98FC}\x{98FD}\x{98FE}\x{98FF}\x{9900}' + . '\x{9901}\x{9902}\x{9903}\x{9904}\x{9905}\x{9906}\x{9907}\x{9908}\x{9909}' + . '\x{990A}\x{990B}\x{990C}\x{990D}\x{990E}\x{990F}\x{9910}\x{9911}\x{9912}' + . '\x{9913}\x{9914}\x{9915}\x{9916}\x{9917}\x{9918}\x{991A}\x{991B}\x{991C}' + . '\x{991D}\x{991E}\x{991F}\x{9920}\x{9921}\x{9922}\x{9923}\x{9924}\x{9925}' + . '\x{9926}\x{9927}\x{9928}\x{9929}\x{992A}\x{992B}\x{992C}\x{992D}\x{992E}' + . '\x{992F}\x{9930}\x{9931}\x{9932}\x{9933}\x{9934}\x{9935}\x{9936}\x{9937}' + . '\x{9938}\x{9939}\x{993A}\x{993C}\x{993D}\x{993E}\x{993F}\x{9940}\x{9941}' + . '\x{9942}\x{9943}\x{9945}\x{9946}\x{9947}\x{9948}\x{9949}\x{994A}\x{994B}' + . '\x{994C}\x{994E}\x{994F}\x{9950}\x{9951}\x{9952}\x{9953}\x{9954}\x{9955}' + . '\x{9956}\x{9957}\x{9958}\x{9959}\x{995B}\x{995C}\x{995E}\x{995F}\x{9960}' + . '\x{9961}\x{9962}\x{9963}\x{9964}\x{9965}\x{9966}\x{9967}\x{9968}\x{9969}' + . '\x{996A}\x{996B}\x{996C}\x{996D}\x{996E}\x{996F}\x{9970}\x{9971}\x{9972}' + . '\x{9973}\x{9974}\x{9975}\x{9976}\x{9977}\x{9978}\x{9979}\x{997A}\x{997B}' + . '\x{997C}\x{997D}\x{997E}\x{997F}\x{9980}\x{9981}\x{9982}\x{9983}\x{9984}' + . '\x{9985}\x{9986}\x{9987}\x{9988}\x{9989}\x{998A}\x{998B}\x{998C}\x{998D}' + . '\x{998E}\x{998F}\x{9990}\x{9991}\x{9992}\x{9993}\x{9994}\x{9995}\x{9996}' + . '\x{9997}\x{9998}\x{9999}\x{999A}\x{999B}\x{999C}\x{999D}\x{999E}\x{999F}' + . '\x{99A0}\x{99A1}\x{99A2}\x{99A3}\x{99A4}\x{99A5}\x{99A6}\x{99A7}\x{99A8}' + . '\x{99A9}\x{99AA}\x{99AB}\x{99AC}\x{99AD}\x{99AE}\x{99AF}\x{99B0}\x{99B1}' + . '\x{99B2}\x{99B3}\x{99B4}\x{99B5}\x{99B6}\x{99B7}\x{99B8}\x{99B9}\x{99BA}' + . '\x{99BB}\x{99BC}\x{99BD}\x{99BE}\x{99C0}\x{99C1}\x{99C2}\x{99C3}\x{99C4}' + . '\x{99C6}\x{99C7}\x{99C8}\x{99C9}\x{99CA}\x{99CB}\x{99CC}\x{99CD}\x{99CE}' + . '\x{99CF}\x{99D0}\x{99D1}\x{99D2}\x{99D3}\x{99D4}\x{99D5}\x{99D6}\x{99D7}' + . '\x{99D8}\x{99D9}\x{99DA}\x{99DB}\x{99DC}\x{99DD}\x{99DE}\x{99DF}\x{99E1}' + . '\x{99E2}\x{99E3}\x{99E4}\x{99E5}\x{99E7}\x{99E8}\x{99E9}\x{99EA}\x{99EC}' + . '\x{99ED}\x{99EE}\x{99EF}\x{99F0}\x{99F1}\x{99F2}\x{99F3}\x{99F4}\x{99F6}' + . '\x{99F7}\x{99F8}\x{99F9}\x{99FA}\x{99FB}\x{99FC}\x{99FD}\x{99FE}\x{99FF}' + . '\x{9A00}\x{9A01}\x{9A02}\x{9A03}\x{9A04}\x{9A05}\x{9A06}\x{9A07}\x{9A08}' + . '\x{9A09}\x{9A0A}\x{9A0B}\x{9A0C}\x{9A0D}\x{9A0E}\x{9A0F}\x{9A11}\x{9A14}' + . '\x{9A15}\x{9A16}\x{9A19}\x{9A1A}\x{9A1B}\x{9A1C}\x{9A1D}\x{9A1E}\x{9A1F}' + . '\x{9A20}\x{9A21}\x{9A22}\x{9A23}\x{9A24}\x{9A25}\x{9A26}\x{9A27}\x{9A29}' + . '\x{9A2A}\x{9A2B}\x{9A2C}\x{9A2D}\x{9A2E}\x{9A2F}\x{9A30}\x{9A31}\x{9A32}' + . '\x{9A33}\x{9A34}\x{9A35}\x{9A36}\x{9A37}\x{9A38}\x{9A39}\x{9A3A}\x{9A3C}' + . '\x{9A3D}\x{9A3E}\x{9A3F}\x{9A40}\x{9A41}\x{9A42}\x{9A43}\x{9A44}\x{9A45}' + . '\x{9A46}\x{9A47}\x{9A48}\x{9A49}\x{9A4A}\x{9A4B}\x{9A4C}\x{9A4D}\x{9A4E}' + . '\x{9A4F}\x{9A50}\x{9A52}\x{9A53}\x{9A54}\x{9A55}\x{9A56}\x{9A57}\x{9A59}' + . '\x{9A5A}\x{9A5B}\x{9A5C}\x{9A5E}\x{9A5F}\x{9A60}\x{9A61}\x{9A62}\x{9A64}' + . '\x{9A65}\x{9A66}\x{9A67}\x{9A68}\x{9A69}\x{9A6A}\x{9A6B}\x{9A6C}\x{9A6D}' + . '\x{9A6E}\x{9A6F}\x{9A70}\x{9A71}\x{9A72}\x{9A73}\x{9A74}\x{9A75}\x{9A76}' + . '\x{9A77}\x{9A78}\x{9A79}\x{9A7A}\x{9A7B}\x{9A7C}\x{9A7D}\x{9A7E}\x{9A7F}' + . '\x{9A80}\x{9A81}\x{9A82}\x{9A83}\x{9A84}\x{9A85}\x{9A86}\x{9A87}\x{9A88}' + . '\x{9A89}\x{9A8A}\x{9A8B}\x{9A8C}\x{9A8D}\x{9A8E}\x{9A8F}\x{9A90}\x{9A91}' + . '\x{9A92}\x{9A93}\x{9A94}\x{9A95}\x{9A96}\x{9A97}\x{9A98}\x{9A99}\x{9A9A}' + . '\x{9A9B}\x{9A9C}\x{9A9D}\x{9A9E}\x{9A9F}\x{9AA0}\x{9AA1}\x{9AA2}\x{9AA3}' + . '\x{9AA4}\x{9AA5}\x{9AA6}\x{9AA7}\x{9AA8}\x{9AAA}\x{9AAB}\x{9AAC}\x{9AAD}' + . '\x{9AAE}\x{9AAF}\x{9AB0}\x{9AB1}\x{9AB2}\x{9AB3}\x{9AB4}\x{9AB5}\x{9AB6}' + . '\x{9AB7}\x{9AB8}\x{9AB9}\x{9ABA}\x{9ABB}\x{9ABC}\x{9ABE}\x{9ABF}\x{9AC0}' + . '\x{9AC1}\x{9AC2}\x{9AC3}\x{9AC4}\x{9AC5}\x{9AC6}\x{9AC7}\x{9AC9}\x{9ACA}' + . '\x{9ACB}\x{9ACC}\x{9ACD}\x{9ACE}\x{9ACF}\x{9AD0}\x{9AD1}\x{9AD2}\x{9AD3}' + . '\x{9AD4}\x{9AD5}\x{9AD6}\x{9AD8}\x{9AD9}\x{9ADA}\x{9ADB}\x{9ADC}\x{9ADD}' + . '\x{9ADE}\x{9ADF}\x{9AE1}\x{9AE2}\x{9AE3}\x{9AE5}\x{9AE6}\x{9AE7}\x{9AEA}' + . '\x{9AEB}\x{9AEC}\x{9AED}\x{9AEE}\x{9AEF}\x{9AF1}\x{9AF2}\x{9AF3}\x{9AF4}' + . '\x{9AF5}\x{9AF6}\x{9AF7}\x{9AF8}\x{9AF9}\x{9AFA}\x{9AFB}\x{9AFC}\x{9AFD}' + . '\x{9AFE}\x{9AFF}\x{9B01}\x{9B03}\x{9B04}\x{9B05}\x{9B06}\x{9B07}\x{9B08}' + . '\x{9B0A}\x{9B0B}\x{9B0C}\x{9B0D}\x{9B0E}\x{9B0F}\x{9B10}\x{9B11}\x{9B12}' + . '\x{9B13}\x{9B15}\x{9B16}\x{9B17}\x{9B18}\x{9B19}\x{9B1A}\x{9B1C}\x{9B1D}' + . '\x{9B1E}\x{9B1F}\x{9B20}\x{9B21}\x{9B22}\x{9B23}\x{9B24}\x{9B25}\x{9B26}' + . '\x{9B27}\x{9B28}\x{9B29}\x{9B2A}\x{9B2B}\x{9B2C}\x{9B2D}\x{9B2E}\x{9B2F}' + . '\x{9B30}\x{9B31}\x{9B32}\x{9B33}\x{9B35}\x{9B36}\x{9B37}\x{9B38}\x{9B39}' + . '\x{9B3A}\x{9B3B}\x{9B3C}\x{9B3E}\x{9B3F}\x{9B41}\x{9B42}\x{9B43}\x{9B44}' + . '\x{9B45}\x{9B46}\x{9B47}\x{9B48}\x{9B49}\x{9B4A}\x{9B4B}\x{9B4C}\x{9B4D}' + . '\x{9B4E}\x{9B4F}\x{9B51}\x{9B52}\x{9B53}\x{9B54}\x{9B55}\x{9B56}\x{9B58}' + . '\x{9B59}\x{9B5A}\x{9B5B}\x{9B5C}\x{9B5D}\x{9B5E}\x{9B5F}\x{9B60}\x{9B61}' + . '\x{9B63}\x{9B64}\x{9B65}\x{9B66}\x{9B67}\x{9B68}\x{9B69}\x{9B6A}\x{9B6B}' + . '\x{9B6C}\x{9B6D}\x{9B6E}\x{9B6F}\x{9B70}\x{9B71}\x{9B73}\x{9B74}\x{9B75}' + . '\x{9B76}\x{9B77}\x{9B78}\x{9B79}\x{9B7A}\x{9B7B}\x{9B7C}\x{9B7D}\x{9B7E}' + . '\x{9B7F}\x{9B80}\x{9B81}\x{9B82}\x{9B83}\x{9B84}\x{9B85}\x{9B86}\x{9B87}' + . '\x{9B88}\x{9B8A}\x{9B8B}\x{9B8D}\x{9B8E}\x{9B8F}\x{9B90}\x{9B91}\x{9B92}' + . '\x{9B93}\x{9B94}\x{9B95}\x{9B96}\x{9B97}\x{9B98}\x{9B9A}\x{9B9B}\x{9B9C}' + . '\x{9B9D}\x{9B9E}\x{9B9F}\x{9BA0}\x{9BA1}\x{9BA2}\x{9BA3}\x{9BA4}\x{9BA5}' + . '\x{9BA6}\x{9BA7}\x{9BA8}\x{9BA9}\x{9BAA}\x{9BAB}\x{9BAC}\x{9BAD}\x{9BAE}' + . '\x{9BAF}\x{9BB0}\x{9BB1}\x{9BB2}\x{9BB3}\x{9BB4}\x{9BB5}\x{9BB6}\x{9BB7}' + . '\x{9BB8}\x{9BB9}\x{9BBA}\x{9BBB}\x{9BBC}\x{9BBD}\x{9BBE}\x{9BBF}\x{9BC0}' + . '\x{9BC1}\x{9BC3}\x{9BC4}\x{9BC5}\x{9BC6}\x{9BC7}\x{9BC8}\x{9BC9}\x{9BCA}' + . '\x{9BCB}\x{9BCC}\x{9BCD}\x{9BCE}\x{9BCF}\x{9BD0}\x{9BD1}\x{9BD2}\x{9BD3}' + . '\x{9BD4}\x{9BD5}\x{9BD6}\x{9BD7}\x{9BD8}\x{9BD9}\x{9BDA}\x{9BDB}\x{9BDC}' + . '\x{9BDD}\x{9BDE}\x{9BDF}\x{9BE0}\x{9BE1}\x{9BE2}\x{9BE3}\x{9BE4}\x{9BE5}' + . '\x{9BE6}\x{9BE7}\x{9BE8}\x{9BE9}\x{9BEA}\x{9BEB}\x{9BEC}\x{9BED}\x{9BEE}' + . '\x{9BEF}\x{9BF0}\x{9BF1}\x{9BF2}\x{9BF3}\x{9BF4}\x{9BF5}\x{9BF7}\x{9BF8}' + . '\x{9BF9}\x{9BFA}\x{9BFB}\x{9BFC}\x{9BFD}\x{9BFE}\x{9BFF}\x{9C02}\x{9C05}' + . '\x{9C06}\x{9C07}\x{9C08}\x{9C09}\x{9C0A}\x{9C0B}\x{9C0C}\x{9C0D}\x{9C0E}' + . '\x{9C0F}\x{9C10}\x{9C11}\x{9C12}\x{9C13}\x{9C14}\x{9C15}\x{9C16}\x{9C17}' + . '\x{9C18}\x{9C19}\x{9C1A}\x{9C1B}\x{9C1C}\x{9C1D}\x{9C1E}\x{9C1F}\x{9C20}' + . '\x{9C21}\x{9C22}\x{9C23}\x{9C24}\x{9C25}\x{9C26}\x{9C27}\x{9C28}\x{9C29}' + . '\x{9C2A}\x{9C2B}\x{9C2C}\x{9C2D}\x{9C2F}\x{9C30}\x{9C31}\x{9C32}\x{9C33}' + . '\x{9C34}\x{9C35}\x{9C36}\x{9C37}\x{9C38}\x{9C39}\x{9C3A}\x{9C3B}\x{9C3C}' + . '\x{9C3D}\x{9C3E}\x{9C3F}\x{9C40}\x{9C41}\x{9C43}\x{9C44}\x{9C45}\x{9C46}' + . '\x{9C47}\x{9C48}\x{9C49}\x{9C4A}\x{9C4B}\x{9C4C}\x{9C4D}\x{9C4E}\x{9C50}' + . '\x{9C52}\x{9C53}\x{9C54}\x{9C55}\x{9C56}\x{9C57}\x{9C58}\x{9C59}\x{9C5A}' + . '\x{9C5B}\x{9C5C}\x{9C5D}\x{9C5E}\x{9C5F}\x{9C60}\x{9C62}\x{9C63}\x{9C65}' + . '\x{9C66}\x{9C67}\x{9C68}\x{9C69}\x{9C6A}\x{9C6B}\x{9C6C}\x{9C6D}\x{9C6E}' + . '\x{9C6F}\x{9C70}\x{9C71}\x{9C72}\x{9C73}\x{9C74}\x{9C75}\x{9C77}\x{9C78}' + . '\x{9C79}\x{9C7A}\x{9C7C}\x{9C7D}\x{9C7E}\x{9C7F}\x{9C80}\x{9C81}\x{9C82}' + . '\x{9C83}\x{9C84}\x{9C85}\x{9C86}\x{9C87}\x{9C88}\x{9C89}\x{9C8A}\x{9C8B}' + . '\x{9C8C}\x{9C8D}\x{9C8E}\x{9C8F}\x{9C90}\x{9C91}\x{9C92}\x{9C93}\x{9C94}' + . '\x{9C95}\x{9C96}\x{9C97}\x{9C98}\x{9C99}\x{9C9A}\x{9C9B}\x{9C9C}\x{9C9D}' + . '\x{9C9E}\x{9C9F}\x{9CA0}\x{9CA1}\x{9CA2}\x{9CA3}\x{9CA4}\x{9CA5}\x{9CA6}' + . '\x{9CA7}\x{9CA8}\x{9CA9}\x{9CAA}\x{9CAB}\x{9CAC}\x{9CAD}\x{9CAE}\x{9CAF}' + . '\x{9CB0}\x{9CB1}\x{9CB2}\x{9CB3}\x{9CB4}\x{9CB5}\x{9CB6}\x{9CB7}\x{9CB8}' + . '\x{9CB9}\x{9CBA}\x{9CBB}\x{9CBC}\x{9CBD}\x{9CBE}\x{9CBF}\x{9CC0}\x{9CC1}' + . '\x{9CC2}\x{9CC3}\x{9CC4}\x{9CC5}\x{9CC6}\x{9CC7}\x{9CC8}\x{9CC9}\x{9CCA}' + . '\x{9CCB}\x{9CCC}\x{9CCD}\x{9CCE}\x{9CCF}\x{9CD0}\x{9CD1}\x{9CD2}\x{9CD3}' + . '\x{9CD4}\x{9CD5}\x{9CD6}\x{9CD7}\x{9CD8}\x{9CD9}\x{9CDA}\x{9CDB}\x{9CDC}' + . '\x{9CDD}\x{9CDE}\x{9CDF}\x{9CE0}\x{9CE1}\x{9CE2}\x{9CE3}\x{9CE4}\x{9CE5}' + . '\x{9CE6}\x{9CE7}\x{9CE8}\x{9CE9}\x{9CEA}\x{9CEB}\x{9CEC}\x{9CED}\x{9CEE}' + . '\x{9CEF}\x{9CF0}\x{9CF1}\x{9CF2}\x{9CF3}\x{9CF4}\x{9CF5}\x{9CF6}\x{9CF7}' + . '\x{9CF8}\x{9CF9}\x{9CFA}\x{9CFB}\x{9CFC}\x{9CFD}\x{9CFE}\x{9CFF}\x{9D00}' + . '\x{9D01}\x{9D02}\x{9D03}\x{9D04}\x{9D05}\x{9D06}\x{9D07}\x{9D08}\x{9D09}' + . '\x{9D0A}\x{9D0B}\x{9D0F}\x{9D10}\x{9D12}\x{9D13}\x{9D14}\x{9D15}\x{9D16}' + . '\x{9D17}\x{9D18}\x{9D19}\x{9D1A}\x{9D1B}\x{9D1C}\x{9D1D}\x{9D1E}\x{9D1F}' + . '\x{9D20}\x{9D21}\x{9D22}\x{9D23}\x{9D24}\x{9D25}\x{9D26}\x{9D28}\x{9D29}' + . '\x{9D2B}\x{9D2D}\x{9D2E}\x{9D2F}\x{9D30}\x{9D31}\x{9D32}\x{9D33}\x{9D34}' + . '\x{9D36}\x{9D37}\x{9D38}\x{9D39}\x{9D3A}\x{9D3B}\x{9D3D}\x{9D3E}\x{9D3F}' + . '\x{9D40}\x{9D41}\x{9D42}\x{9D43}\x{9D45}\x{9D46}\x{9D47}\x{9D48}\x{9D49}' + . '\x{9D4A}\x{9D4B}\x{9D4C}\x{9D4D}\x{9D4E}\x{9D4F}\x{9D50}\x{9D51}\x{9D52}' + . '\x{9D53}\x{9D54}\x{9D55}\x{9D56}\x{9D57}\x{9D58}\x{9D59}\x{9D5A}\x{9D5B}' + . '\x{9D5C}\x{9D5D}\x{9D5E}\x{9D5F}\x{9D60}\x{9D61}\x{9D62}\x{9D63}\x{9D64}' + . '\x{9D65}\x{9D66}\x{9D67}\x{9D68}\x{9D69}\x{9D6A}\x{9D6B}\x{9D6C}\x{9D6E}' + . '\x{9D6F}\x{9D70}\x{9D71}\x{9D72}\x{9D73}\x{9D74}\x{9D75}\x{9D76}\x{9D77}' + . '\x{9D78}\x{9D79}\x{9D7A}\x{9D7B}\x{9D7C}\x{9D7D}\x{9D7E}\x{9D7F}\x{9D80}' + . '\x{9D81}\x{9D82}\x{9D83}\x{9D84}\x{9D85}\x{9D86}\x{9D87}\x{9D88}\x{9D89}' + . '\x{9D8A}\x{9D8B}\x{9D8C}\x{9D8D}\x{9D8E}\x{9D90}\x{9D91}\x{9D92}\x{9D93}' + . '\x{9D94}\x{9D96}\x{9D97}\x{9D98}\x{9D99}\x{9D9A}\x{9D9B}\x{9D9C}\x{9D9D}' + . '\x{9D9E}\x{9D9F}\x{9DA0}\x{9DA1}\x{9DA2}\x{9DA3}\x{9DA4}\x{9DA5}\x{9DA6}' + . '\x{9DA7}\x{9DA8}\x{9DA9}\x{9DAA}\x{9DAB}\x{9DAC}\x{9DAD}\x{9DAF}\x{9DB0}' + . '\x{9DB1}\x{9DB2}\x{9DB3}\x{9DB4}\x{9DB5}\x{9DB6}\x{9DB7}\x{9DB8}\x{9DB9}' + . '\x{9DBA}\x{9DBB}\x{9DBC}\x{9DBE}\x{9DBF}\x{9DC1}\x{9DC2}\x{9DC3}\x{9DC4}' + . '\x{9DC5}\x{9DC7}\x{9DC8}\x{9DC9}\x{9DCA}\x{9DCB}\x{9DCC}\x{9DCD}\x{9DCE}' + . '\x{9DCF}\x{9DD0}\x{9DD1}\x{9DD2}\x{9DD3}\x{9DD4}\x{9DD5}\x{9DD6}\x{9DD7}' + . '\x{9DD8}\x{9DD9}\x{9DDA}\x{9DDB}\x{9DDC}\x{9DDD}\x{9DDE}\x{9DDF}\x{9DE0}' + . '\x{9DE1}\x{9DE2}\x{9DE3}\x{9DE4}\x{9DE5}\x{9DE6}\x{9DE7}\x{9DE8}\x{9DE9}' + . '\x{9DEB}\x{9DEC}\x{9DED}\x{9DEE}\x{9DEF}\x{9DF0}\x{9DF1}\x{9DF2}\x{9DF3}' + . '\x{9DF4}\x{9DF5}\x{9DF6}\x{9DF7}\x{9DF8}\x{9DF9}\x{9DFA}\x{9DFB}\x{9DFD}' + . '\x{9DFE}\x{9DFF}\x{9E00}\x{9E01}\x{9E02}\x{9E03}\x{9E04}\x{9E05}\x{9E06}' + . '\x{9E07}\x{9E08}\x{9E09}\x{9E0A}\x{9E0B}\x{9E0C}\x{9E0D}\x{9E0F}\x{9E10}' + . '\x{9E11}\x{9E12}\x{9E13}\x{9E14}\x{9E15}\x{9E17}\x{9E18}\x{9E19}\x{9E1A}' + . '\x{9E1B}\x{9E1D}\x{9E1E}\x{9E1F}\x{9E20}\x{9E21}\x{9E22}\x{9E23}\x{9E24}' + . '\x{9E25}\x{9E26}\x{9E27}\x{9E28}\x{9E29}\x{9E2A}\x{9E2B}\x{9E2C}\x{9E2D}' + . '\x{9E2E}\x{9E2F}\x{9E30}\x{9E31}\x{9E32}\x{9E33}\x{9E34}\x{9E35}\x{9E36}' + . '\x{9E37}\x{9E38}\x{9E39}\x{9E3A}\x{9E3B}\x{9E3C}\x{9E3D}\x{9E3E}\x{9E3F}' + . '\x{9E40}\x{9E41}\x{9E42}\x{9E43}\x{9E44}\x{9E45}\x{9E46}\x{9E47}\x{9E48}' + . '\x{9E49}\x{9E4A}\x{9E4B}\x{9E4C}\x{9E4D}\x{9E4E}\x{9E4F}\x{9E50}\x{9E51}' + . '\x{9E52}\x{9E53}\x{9E54}\x{9E55}\x{9E56}\x{9E57}\x{9E58}\x{9E59}\x{9E5A}' + . '\x{9E5B}\x{9E5C}\x{9E5D}\x{9E5E}\x{9E5F}\x{9E60}\x{9E61}\x{9E62}\x{9E63}' + . '\x{9E64}\x{9E65}\x{9E66}\x{9E67}\x{9E68}\x{9E69}\x{9E6A}\x{9E6B}\x{9E6C}' + . '\x{9E6D}\x{9E6E}\x{9E6F}\x{9E70}\x{9E71}\x{9E72}\x{9E73}\x{9E74}\x{9E75}' + . '\x{9E76}\x{9E77}\x{9E79}\x{9E7A}\x{9E7C}\x{9E7D}\x{9E7E}\x{9E7F}\x{9E80}' + . '\x{9E81}\x{9E82}\x{9E83}\x{9E84}\x{9E85}\x{9E86}\x{9E87}\x{9E88}\x{9E89}' + . '\x{9E8A}\x{9E8B}\x{9E8C}\x{9E8D}\x{9E8E}\x{9E91}\x{9E92}\x{9E93}\x{9E94}' + . '\x{9E96}\x{9E97}\x{9E99}\x{9E9A}\x{9E9B}\x{9E9C}\x{9E9D}\x{9E9F}\x{9EA0}' + . '\x{9EA1}\x{9EA3}\x{9EA4}\x{9EA5}\x{9EA6}\x{9EA7}\x{9EA8}\x{9EA9}\x{9EAA}' + . '\x{9EAD}\x{9EAE}\x{9EAF}\x{9EB0}\x{9EB2}\x{9EB3}\x{9EB4}\x{9EB5}\x{9EB6}' + . '\x{9EB7}\x{9EB8}\x{9EBB}\x{9EBC}\x{9EBD}\x{9EBE}\x{9EBF}\x{9EC0}\x{9EC1}' + . '\x{9EC2}\x{9EC3}\x{9EC4}\x{9EC5}\x{9EC6}\x{9EC7}\x{9EC8}\x{9EC9}\x{9ECA}' + . '\x{9ECB}\x{9ECC}\x{9ECD}\x{9ECE}\x{9ECF}\x{9ED0}\x{9ED1}\x{9ED2}\x{9ED3}' + . '\x{9ED4}\x{9ED5}\x{9ED6}\x{9ED7}\x{9ED8}\x{9ED9}\x{9EDA}\x{9EDB}\x{9EDC}' + . '\x{9EDD}\x{9EDE}\x{9EDF}\x{9EE0}\x{9EE1}\x{9EE2}\x{9EE3}\x{9EE4}\x{9EE5}' + . '\x{9EE6}\x{9EE7}\x{9EE8}\x{9EE9}\x{9EEA}\x{9EEB}\x{9EED}\x{9EEE}\x{9EEF}' + . '\x{9EF0}\x{9EF2}\x{9EF3}\x{9EF4}\x{9EF5}\x{9EF6}\x{9EF7}\x{9EF8}\x{9EF9}' + . '\x{9EFA}\x{9EFB}\x{9EFC}\x{9EFD}\x{9EFE}\x{9EFF}\x{9F00}\x{9F01}\x{9F02}' + . '\x{9F04}\x{9F05}\x{9F06}\x{9F07}\x{9F08}\x{9F09}\x{9F0A}\x{9F0B}\x{9F0C}' + . '\x{9F0D}\x{9F0E}\x{9F0F}\x{9F10}\x{9F12}\x{9F13}\x{9F15}\x{9F16}\x{9F17}' + . '\x{9F18}\x{9F19}\x{9F1A}\x{9F1B}\x{9F1C}\x{9F1D}\x{9F1E}\x{9F1F}\x{9F20}' + . '\x{9F22}\x{9F23}\x{9F24}\x{9F25}\x{9F27}\x{9F28}\x{9F29}\x{9F2A}\x{9F2B}' + . '\x{9F2C}\x{9F2D}\x{9F2E}\x{9F2F}\x{9F30}\x{9F31}\x{9F32}\x{9F33}\x{9F34}' + . '\x{9F35}\x{9F36}\x{9F37}\x{9F38}\x{9F39}\x{9F3A}\x{9F3B}\x{9F3C}\x{9F3D}' + . '\x{9F3E}\x{9F3F}\x{9F40}\x{9F41}\x{9F42}\x{9F43}\x{9F44}\x{9F46}\x{9F47}' + . '\x{9F48}\x{9F49}\x{9F4A}\x{9F4B}\x{9F4C}\x{9F4D}\x{9F4E}\x{9F4F}\x{9F50}' + . '\x{9F51}\x{9F52}\x{9F54}\x{9F55}\x{9F56}\x{9F57}\x{9F58}\x{9F59}\x{9F5A}' + . '\x{9F5B}\x{9F5C}\x{9F5D}\x{9F5E}\x{9F5F}\x{9F60}\x{9F61}\x{9F63}\x{9F64}' + . '\x{9F65}\x{9F66}\x{9F67}\x{9F68}\x{9F69}\x{9F6A}\x{9F6B}\x{9F6C}\x{9F6E}' + . '\x{9F6F}\x{9F70}\x{9F71}\x{9F72}\x{9F73}\x{9F74}\x{9F75}\x{9F76}\x{9F77}' + . '\x{9F78}\x{9F79}\x{9F7A}\x{9F7B}\x{9F7C}\x{9F7D}\x{9F7E}\x{9F7F}\x{9F80}' + . '\x{9F81}\x{9F82}\x{9F83}\x{9F84}\x{9F85}\x{9F86}\x{9F87}\x{9F88}\x{9F89}' + . '\x{9F8A}\x{9F8B}\x{9F8C}\x{9F8D}\x{9F8E}\x{9F8F}\x{9F90}\x{9F91}\x{9F92}' + . '\x{9F93}\x{9F94}\x{9F95}\x{9F96}\x{9F97}\x{9F98}\x{9F99}\x{9F9A}\x{9F9B}' + . '\x{9F9C}\x{9F9D}\x{9F9E}\x{9F9F}\x{9FA0}\x{9FA2}\x{9FA4}\x{9FA5}]{1,20}$/iu', +]; diff --git a/lib/laminas/laminas-validator/src/Hostname/Com.php b/lib/laminas/laminas-validator/src/Hostname/Com.php new file mode 100644 index 0000000000..5db58df61c --- /dev/null +++ b/lib/laminas/laminas-validator/src/Hostname/Com.php @@ -0,0 +1,176 @@ + '/^[\x{002d}0-9\x{0400}-\x{052f}]{1,63}$/iu', + 2 => '/^[\x{002d}0-9\x{0370}-\x{03ff}]{1,63}$/iu', + 3 => '/^[\x{002d}0-9a-z\x{ac00}-\x{d7a3}]{1,17}$/iu', + // @codingStandardsIgnoreStart + 4 => '/^[\x{002d}0-9a-z·à-öø-ÿāăąćĉċčďđēĕėęěĝğġģĥħĩīĭįıĵķĸĺļľłńņňŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷźżž]{1,63}$/iu', + // @codingStandardsIgnoreEnd + 5 => '/^[\x{002d}0-9A-Za-z\x{3400}-\x{3401}\x{3404}-\x{3406}\x{340C}\x{3416}\x{341C}' +. '\x{3421}\x{3424}\x{3428}-\x{3429}\x{342B}-\x{342E}\x{3430}-\x{3434}\x{3436}' +. '\x{3438}-\x{343C}\x{343E}\x{3441}-\x{3445}\x{3447}\x{3449}-\x{3451}\x{3453}' +. '\x{3457}-\x{345F}\x{3463}-\x{3467}\x{346E}-\x{3471}\x{3473}-\x{3477}\x{3479}-\x{348E}\x{3491}-\x{3497}' +. '\x{3499}-\x{34A1}\x{34A4}-\x{34AD}\x{34AF}-\x{34B0}\x{34B2}-\x{34BF}\x{34C2}-\x{34C5}\x{34C7}-\x{34CC}' +. '\x{34CE}-\x{34D1}\x{34D3}-\x{34D8}\x{34DA}-\x{34E4}\x{34E7}-\x{34E9}\x{34EC}-\x{34EF}\x{34F1}-\x{34FE}' +. '\x{3500}-\x{3507}\x{350A}-\x{3513}\x{3515}\x{3517}-\x{351A}\x{351C}-\x{351E}\x{3520}-\x{352A}' +. '\x{352C}-\x{3552}\x{3554}-\x{355C}\x{355E}-\x{3567}\x{3569}-\x{3573}\x{3575}-\x{357C}\x{3580}-\x{3588}' +. '\x{358F}-\x{3598}\x{359E}-\x{35AB}\x{35B4}-\x{35CD}\x{35D0}\x{35D3}-\x{35DC}\x{35E2}-\x{35ED}' +. '\x{35F0}-\x{35F6}\x{35FB}-\x{3602}\x{3605}-\x{360E}\x{3610}-\x{3611}\x{3613}-\x{3616}\x{3619}-\x{362D}' +. '\x{362F}-\x{3634}\x{3636}-\x{363B}\x{363F}-\x{3645}\x{3647}-\x{364B}\x{364D}-\x{3653}\x{3655}' +. '\x{3659}-\x{365E}\x{3660}-\x{3665}\x{3667}-\x{367C}\x{367E}\x{3680}-\x{3685}\x{3687}' +. '\x{3689}-\x{3690}\x{3692}-\x{3698}\x{369A}\x{369C}-\x{36AE}\x{36B0}-\x{36BF}\x{36C1}-\x{36C5}' +. '\x{36C9}-\x{36CA}\x{36CD}-\x{36DE}\x{36E1}-\x{36E2}\x{36E5}-\x{36FE}\x{3701}-\x{3713}\x{3715}-\x{371E}' +. '\x{3720}-\x{372C}\x{372E}-\x{3745}\x{3747}-\x{3748}\x{374A}\x{374C}-\x{3759}\x{375B}-\x{3760}' +. '\x{3762}-\x{3767}\x{3769}-\x{3772}\x{3774}-\x{378C}\x{378F}-\x{379C}\x{379F}\x{37A1}-\x{37AD}' +. '\x{37AF}-\x{37B7}\x{37B9}-\x{37C1}\x{37C3}-\x{37C5}\x{37C7}-\x{37D4}\x{37D6}-\x{37E0}\x{37E2}' +. '\x{37E5}-\x{37ED}\x{37EF}-\x{37F6}\x{37F8}-\x{3802}\x{3804}-\x{381D}\x{3820}-\x{3822}\x{3825}-\x{382A}' +. '\x{382D}-\x{382F}\x{3831}-\x{3832}\x{3834}-\x{384C}\x{384E}-\x{3860}\x{3862}-\x{3863}\x{3865}-\x{386B}' +. '\x{386D}-\x{3886}\x{3888}-\x{38A1}\x{38A3}\x{38A5}-\x{38AA}\x{38AC}\x{38AE}-\x{38B0}' +. '\x{38B2}-\x{38B6}\x{38B8}\x{38BA}-\x{38BE}\x{38C0}-\x{38C9}\x{38CB}-\x{38D4}\x{38D8}-\x{38E0}' +. '\x{38E2}-\x{38E6}\x{38EB}-\x{38ED}\x{38EF}-\x{38F2}\x{38F5}-\x{38F7}\x{38FA}-\x{38FF}\x{3901}-\x{392A}' +. '\x{392C}\x{392E}-\x{393B}\x{393E}-\x{3956}\x{395A}-\x{3969}\x{396B}-\x{397A}\x{397C}-\x{3987}' +. '\x{3989}-\x{3998}\x{399A}-\x{39B0}\x{39B2}\x{39B4}-\x{39D0}\x{39D2}-\x{39DA}\x{39DE}-\x{39DF}' +. '\x{39E1}-\x{39EF}\x{39F1}-\x{3A17}\x{3A19}-\x{3A2A}\x{3A2D}-\x{3A40}\x{3A43}-\x{3A4E}\x{3A50}' +. '\x{3A52}-\x{3A5E}\x{3A60}-\x{3A6D}\x{3A6F}-\x{3A77}\x{3A79}-\x{3A82}\x{3A84}-\x{3A85}\x{3A87}-\x{3A89}' +. '\x{3A8B}-\x{3A8F}\x{3A91}-\x{3A93}\x{3A95}-\x{3A96}\x{3A9A}\x{3A9C}-\x{3AA6}\x{3AA8}-\x{3AA9}' +. '\x{3AAB}-\x{3AB1}\x{3AB4}-\x{3ABC}\x{3ABE}-\x{3AC5}\x{3ACA}-\x{3ACB}\x{3ACD}-\x{3AD5}\x{3AD7}-\x{3AE1}' +. '\x{3AE4}-\x{3AE7}\x{3AE9}-\x{3AEC}\x{3AEE}-\x{3AFD}\x{3B01}-\x{3B10}\x{3B12}-\x{3B15}\x{3B17}-\x{3B1E}' +. '\x{3B20}-\x{3B23}\x{3B25}-\x{3B27}\x{3B29}-\x{3B36}\x{3B38}-\x{3B39}\x{3B3B}-\x{3B3C}\x{3B3F}' +. '\x{3B41}-\x{3B44}\x{3B47}-\x{3B4C}\x{3B4E}\x{3B51}-\x{3B55}\x{3B58}-\x{3B62}\x{3B68}-\x{3B72}' +. '\x{3B78}-\x{3B88}\x{3B8B}-\x{3B9F}\x{3BA1}\x{3BA3}-\x{3BBA}\x{3BBC}\x{3BBF}-\x{3BD0}' +. '\x{3BD3}-\x{3BE6}\x{3BEA}-\x{3BFB}\x{3BFE}-\x{3C12}\x{3C14}-\x{3C1B}\x{3C1D}-\x{3C37}\x{3C39}-\x{3C4F}' +. '\x{3C52}\x{3C54}-\x{3C5C}\x{3C5E}-\x{3C68}\x{3C6A}-\x{3C76}\x{3C78}-\x{3C8F}\x{3C91}-\x{3CA8}' +. '\x{3CAA}-\x{3CAD}\x{3CAF}-\x{3CBE}\x{3CC0}-\x{3CC8}\x{3CCA}-\x{3CD3}\x{3CD6}-\x{3CE0}\x{3CE4}-\x{3CEE}' +. '\x{3CF3}-\x{3D0A}\x{3D0E}-\x{3D1E}\x{3D20}-\x{3D21}\x{3D25}-\x{3D38}\x{3D3B}-\x{3D46}\x{3D4A}-\x{3D59}' +. '\x{3D5D}-\x{3D7B}\x{3D7D}-\x{3D81}\x{3D84}-\x{3D88}\x{3D8C}-\x{3D8F}\x{3D91}-\x{3D98}\x{3D9A}-\x{3D9C}' +. '\x{3D9E}-\x{3DA1}\x{3DA3}-\x{3DB0}\x{3DB2}-\x{3DB5}\x{3DB9}-\x{3DBC}\x{3DBE}-\x{3DCB}\x{3DCD}-\x{3DDB}' +. '\x{3DDF}-\x{3DE8}\x{3DEB}-\x{3DF0}\x{3DF3}-\x{3DF9}\x{3DFB}-\x{3DFC}\x{3DFE}-\x{3E05}\x{3E08}-\x{3E33}' +. '\x{3E35}-\x{3E3E}\x{3E40}-\x{3E47}\x{3E49}-\x{3E67}\x{3E6B}-\x{3E6F}\x{3E71}-\x{3E85}\x{3E87}-\x{3E8C}' +. '\x{3E8E}-\x{3E98}\x{3E9A}-\x{3EA1}\x{3EA3}-\x{3EAE}\x{3EB0}-\x{3EB5}\x{3EB7}-\x{3EBA}\x{3EBD}' +. '\x{3EBF}-\x{3EC4}\x{3EC7}-\x{3ECE}\x{3ED1}-\x{3ED7}\x{3ED9}-\x{3EDA}\x{3EDD}-\x{3EE3}\x{3EE7}-\x{3EE8}' +. '\x{3EEB}-\x{3EF2}\x{3EF5}-\x{3EFF}\x{3F01}-\x{3F02}\x{3F04}-\x{3F07}\x{3F09}-\x{3F44}\x{3F46}-\x{3F4E}' +. '\x{3F50}-\x{3F53}\x{3F55}-\x{3F72}\x{3F74}-\x{3F75}\x{3F77}-\x{3F7B}\x{3F7D}-\x{3FB0}\x{3FB6}-\x{3FBF}' +. '\x{3FC1}-\x{3FCF}\x{3FD1}-\x{3FD3}\x{3FD5}-\x{3FDF}\x{3FE1}-\x{400B}\x{400D}-\x{401C}\x{401E}-\x{4024}' +. '\x{4027}-\x{403F}\x{4041}-\x{4060}\x{4062}-\x{4069}\x{406B}-\x{408A}\x{408C}-\x{40A7}\x{40A9}-\x{40B4}' +. '\x{40B6}-\x{40C2}\x{40C7}-\x{40CF}\x{40D1}-\x{40DE}\x{40E0}-\x{40E7}\x{40E9}-\x{40EE}\x{40F0}-\x{40FB}' +. '\x{40FD}-\x{4109}\x{410B}-\x{4115}\x{4118}-\x{411D}\x{411F}-\x{4122}\x{4124}-\x{4133}\x{4136}-\x{4138}' +. '\x{413A}-\x{4148}\x{414A}-\x{4169}\x{416C}-\x{4185}\x{4188}-\x{418B}\x{418D}-\x{41AD}\x{41AF}-\x{41B3}' +. '\x{41B5}-\x{41C3}\x{41C5}-\x{41C9}\x{41CB}-\x{41F2}\x{41F5}-\x{41FE}\x{4200}-\x{4227}\x{422A}-\x{4246}' +. '\x{4248}-\x{4263}\x{4265}-\x{428B}\x{428D}-\x{42A1}\x{42A3}-\x{42C4}\x{42C8}-\x{42DC}\x{42DE}-\x{430A}' +. '\x{430C}-\x{4335}\x{4337}\x{4342}-\x{435F}\x{4361}-\x{439A}\x{439C}-\x{439D}\x{439F}-\x{43A4}' +. '\x{43A6}-\x{43EC}\x{43EF}-\x{4405}\x{4407}-\x{4429}\x{442B}-\x{4455}\x{4457}-\x{4468}\x{446A}-\x{446D}' +. '\x{446F}-\x{4476}\x{4479}-\x{447D}\x{447F}-\x{4486}\x{4488}-\x{4490}\x{4492}-\x{4498}\x{449A}-\x{44AD}' +. '\x{44B0}-\x{44BD}\x{44C1}-\x{44D3}\x{44D6}-\x{44E7}\x{44EA}\x{44EC}-\x{44FA}\x{44FC}-\x{4541}' +. '\x{4543}-\x{454F}\x{4551}-\x{4562}\x{4564}-\x{4575}\x{4577}-\x{45AB}\x{45AD}-\x{45BD}\x{45BF}-\x{45D5}' +. '\x{45D7}-\x{45EC}\x{45EE}-\x{45F2}\x{45F4}-\x{45FA}\x{45FC}-\x{461A}\x{461C}-\x{461D}\x{461F}-\x{4631}' +. '\x{4633}-\x{4649}\x{464C}\x{464E}-\x{4652}\x{4654}-\x{466A}\x{466C}-\x{4675}\x{4677}-\x{467A}' +. '\x{467C}-\x{4694}\x{4696}-\x{46A3}\x{46A5}-\x{46AB}\x{46AD}-\x{46D2}\x{46D4}-\x{4723}\x{4729}-\x{4732}' +. '\x{4734}-\x{4758}\x{475A}\x{475C}-\x{478B}\x{478D}\x{4791}-\x{47B1}\x{47B3}-\x{47F1}' +. '\x{47F3}-\x{480B}\x{480D}-\x{4815}\x{4817}-\x{4839}\x{483B}-\x{4870}\x{4872}-\x{487A}\x{487C}-\x{487F}' +. '\x{4883}-\x{488E}\x{4890}-\x{4896}\x{4899}-\x{48A2}\x{48A4}-\x{48B9}\x{48BB}-\x{48C8}\x{48CA}-\x{48D1}' +. '\x{48D3}-\x{48E5}\x{48E7}-\x{48F2}\x{48F4}-\x{48FF}\x{4901}-\x{4922}\x{4924}-\x{4928}\x{492A}-\x{4931}' +. '\x{4933}-\x{495B}\x{495D}-\x{4978}\x{497A}\x{497D}\x{4982}-\x{4983}\x{4985}-\x{49A8}' +. '\x{49AA}-\x{49AF}\x{49B1}-\x{49B7}\x{49B9}-\x{49BD}\x{49C1}-\x{49C7}\x{49C9}-\x{49CE}\x{49D0}-\x{49E8}' +. '\x{49EA}\x{49EC}\x{49EE}-\x{4A19}\x{4A1B}-\x{4A43}\x{4A45}-\x{4A4D}\x{4A4F}-\x{4A9E}' +. '\x{4AA0}-\x{4AA9}\x{4AAB}-\x{4B4E}\x{4B50}-\x{4B5B}\x{4B5D}-\x{4B69}\x{4B6B}-\x{4BC2}\x{4BC6}-\x{4BE8}' +. '\x{4BEA}-\x{4BFA}\x{4BFC}-\x{4C06}\x{4C08}-\x{4C2D}\x{4C2F}-\x{4C32}\x{4C34}-\x{4C35}\x{4C37}-\x{4C69}' +. '\x{4C6B}-\x{4C73}\x{4C75}-\x{4C86}\x{4C88}-\x{4C97}\x{4C99}-\x{4C9C}\x{4C9F}-\x{4CA3}\x{4CA5}-\x{4CB5}' +. '\x{4CB7}-\x{4CF8}\x{4CFA}-\x{4D27}\x{4D29}-\x{4DAC}\x{4DAE}-\x{4DB1}\x{4DB3}-\x{4DB5}\x{4E00}-\x{4E54}' +. '\x{4E56}-\x{4E89}\x{4E8B}-\x{4EEC}\x{4EEE}-\x{4FAC}\x{4FAE}-\x{503C}\x{503E}-\x{51E5}\x{51E7}-\x{5270}' +. '\x{5272}-\x{56A1}\x{56A3}-\x{5840}\x{5842}-\x{58B5}\x{58B7}-\x{58CB}\x{58CD}-\x{5BC8}\x{5BCA}-\x{5C01}' +. '\x{5C03}-\x{5C25}\x{5C27}-\x{5D5B}\x{5D5D}-\x{5F08}\x{5F0A}-\x{61F3}\x{61F5}-\x{63BA}\x{63BC}-\x{6441}' +. '\x{6443}-\x{657C}\x{657E}-\x{663E}\x{6640}-\x{66FC}\x{66FE}-\x{6728}\x{672A}-\x{6766}\x{6768}-\x{67A8}' +. '\x{67AA}-\x{685B}\x{685D}-\x{685E}\x{6860}-\x{68B9}\x{68BB}-\x{6AC8}\x{6ACA}-\x{6BB0}\x{6BB2}-\x{6C16}' +. '\x{6C18}-\x{6D9B}\x{6D9D}-\x{6E12}\x{6E14}-\x{6E8B}\x{6E8D}-\x{704D}\x{704F}-\x{7113}\x{7115}-\x{713B}' +. '\x{713D}-\x{7154}\x{7156}-\x{729F}\x{72A1}-\x{731E}\x{7320}-\x{7362}\x{7364}-\x{7533}\x{7535}-\x{7551}' +. '\x{7553}-\x{7572}\x{7574}-\x{75E8}\x{75EA}-\x{7679}\x{767B}-\x{783E}\x{7840}-\x{7A62}\x{7A64}-\x{7AC2}' +. '\x{7AC4}-\x{7B06}\x{7B08}-\x{7B79}\x{7B7B}-\x{7BCE}\x{7BD0}-\x{7D99}\x{7D9B}-\x{7E49}\x{7E4C}-\x{8132}' +. '\x{8134}\x{8136}-\x{81D2}\x{81D4}-\x{8216}\x{8218}-\x{822D}\x{822F}-\x{83B4}\x{83B6}-\x{841F}' +. '\x{8421}-\x{86CC}\x{86CE}-\x{874A}\x{874C}-\x{877E}\x{8780}-\x{8A32}\x{8A34}-\x{8B71}\x{8B73}-\x{8B8E}' +. '\x{8B90}-\x{8DE4}\x{8DE6}-\x{8E9A}\x{8E9C}-\x{8EE1}\x{8EE4}-\x{8F0B}\x{8F0D}-\x{8FB9}\x{8FBB}-\x{9038}' +. '\x{903A}-\x{9196}\x{9198}-\x{91A3}\x{91A5}-\x{91B7}\x{91B9}-\x{91C7}\x{91C9}-\x{91E0}\x{91E2}-\x{91FB}' +. '\x{91FD}-\x{922B}\x{922D}-\x{9270}\x{9272}-\x{9420}\x{9422}-\x{9664}\x{9666}-\x{9679}\x{967B}-\x{9770}' +. '\x{9772}-\x{982B}\x{982D}-\x{98ED}\x{98EF}-\x{99C4}\x{99C6}-\x{9A11}\x{9A14}-\x{9A27}\x{9A29}-\x{9D0D}' +. '\x{9D0F}-\x{9D2B}\x{9D2D}-\x{9D8E}\x{9D90}-\x{9DC5}\x{9DC7}-\x{9E77}\x{9E79}-\x{9EB8}\x{9EBB}-\x{9F20}' +. '\x{9F22}-\x{9F61}\x{9F63}-\x{9FA5}\x{FA28}]{1,20}$/iu', + 6 => '/^[\x{002d}0-9A-Za-z]{1,63}$/iu', + 7 => '/^[\x{00A1}-\x{00FF}]{1,63}$/iu', + 8 => '/^[\x{0100}-\x{017f}]{1,63}$/iu', + 9 => '/^[\x{0180}-\x{024f}]{1,63}$/iu', + 10 => '/^[\x{0250}-\x{02af}]{1,63}$/iu', + 11 => '/^[\x{02b0}-\x{02ff}]{1,63}$/iu', + 12 => '/^[\x{0300}-\x{036f}]{1,63}$/iu', + 13 => '/^[\x{0370}-\x{03ff}]{1,63}$/iu', + 14 => '/^[\x{0400}-\x{04ff}]{1,63}$/iu', + 15 => '/^[\x{0500}-\x{052f}]{1,63}$/iu', + 16 => '/^[\x{0530}-\x{058F}]{1,63}$/iu', + 17 => '/^[\x{0590}-\x{05FF}]{1,63}$/iu', + 18 => '/^[\x{0600}-\x{06FF}]{1,63}$/iu', + 19 => '/^[\x{0700}-\x{074F}]{1,63}$/iu', + 20 => '/^[\x{0780}-\x{07BF}]{1,63}$/iu', + 21 => '/^[\x{0900}-\x{097F}]{1,63}$/iu', + 22 => '/^[\x{0980}-\x{09FF}]{1,63}$/iu', + 23 => '/^[\x{0A00}-\x{0A7F}]{1,63}$/iu', + 24 => '/^[\x{0A80}-\x{0AFF}]{1,63}$/iu', + 25 => '/^[\x{0B00}-\x{0B7F}]{1,63}$/iu', + 26 => '/^[\x{0B80}-\x{0BFF}]{1,63}$/iu', + 27 => '/^[\x{0C00}-\x{0C7F}]{1,63}$/iu', + 28 => '/^[\x{0C80}-\x{0CFF}]{1,63}$/iu', + 29 => '/^[\x{0D00}-\x{0D7F}]{1,63}$/iu', + 30 => '/^[\x{0D80}-\x{0DFF}]{1,63}$/iu', + 31 => '/^[\x{0E00}-\x{0E7F}]{1,63}$/iu', + 32 => '/^[\x{0E80}-\x{0EFF}]{1,63}$/iu', + 33 => '/^[\x{0F00}-\x{0FFF}]{1,63}$/iu', + 34 => '/^[\x{1000}-\x{109F}]{1,63}$/iu', + 35 => '/^[\x{10A0}-\x{10FF}]{1,63}$/iu', + 36 => '/^[\x{1100}-\x{11FF}]{1,63}$/iu', + 37 => '/^[\x{1200}-\x{137F}]{1,63}$/iu', + 38 => '/^[\x{13A0}-\x{13FF}]{1,63}$/iu', + 39 => '/^[\x{1400}-\x{167F}]{1,63}$/iu', + 40 => '/^[\x{1680}-\x{169F}]{1,63}$/iu', + 41 => '/^[\x{16A0}-\x{16FF}]{1,63}$/iu', + 42 => '/^[\x{1700}-\x{171F}]{1,63}$/iu', + 43 => '/^[\x{1720}-\x{173F}]{1,63}$/iu', + 44 => '/^[\x{1740}-\x{175F}]{1,63}$/iu', + 45 => '/^[\x{1760}-\x{177F}]{1,63}$/iu', + 46 => '/^[\x{1780}-\x{17FF}]{1,63}$/iu', + 47 => '/^[\x{1800}-\x{18AF}]{1,63}$/iu', + 48 => '/^[\x{1E00}-\x{1EFF}]{1,63}$/iu', + 49 => '/^[\x{1F00}-\x{1FFF}]{1,63}$/iu', + 50 => '/^[\x{2070}-\x{209F}]{1,63}$/iu', + 51 => '/^[\x{2100}-\x{214F}]{1,63}$/iu', + 52 => '/^[\x{2150}-\x{218F}]{1,63}$/iu', + 53 => '/^[\x{2460}-\x{24FF}]{1,63}$/iu', + 54 => '/^[\x{2E80}-\x{2EFF}]{1,63}$/iu', + 55 => '/^[\x{2F00}-\x{2FDF}]{1,63}$/iu', + 56 => '/^[\x{2FF0}-\x{2FFF}]{1,63}$/iu', + 57 => '/^[\x{3040}-\x{309F}]{1,63}$/iu', + 58 => '/^[\x{30A0}-\x{30FF}]{1,63}$/iu', + 59 => '/^[\x{3100}-\x{312F}]{1,63}$/iu', + 60 => '/^[\x{3130}-\x{318F}]{1,63}$/iu', + 61 => '/^[\x{3190}-\x{319F}]{1,63}$/iu', + 62 => '/^[\x{31A0}-\x{31BF}]{1,63}$/iu', + 63 => '/^[\x{31F0}-\x{31FF}]{1,63}$/iu', + 64 => '/^[\x{3200}-\x{32FF}]{1,63}$/iu', + 65 => '/^[\x{3300}-\x{33FF}]{1,63}$/iu', + 66 => '/^[\x{3400}-\x{4DBF}]{1,63}$/iu', + 67 => '/^[\x{4E00}-\x{9FFF}]{1,63}$/iu', + 68 => '/^[\x{A000}-\x{A48F}]{1,63}$/iu', + 69 => '/^[\x{A490}-\x{A4CF}]{1,63}$/iu', + 70 => '/^[\x{AC00}-\x{D7AF}]{1,63}$/iu', + 73 => '/^[\x{F900}-\x{FAFF}]{1,63}$/iu', + 74 => '/^[\x{FB00}-\x{FB4F}]{1,63}$/iu', + 75 => '/^[\x{FB50}-\x{FDFF}]{1,63}$/iu', + 76 => '/^[\x{FE20}-\x{FE2F}]{1,63}$/iu', + 77 => '/^[\x{FE70}-\x{FEFF}]{1,63}$/iu', + 78 => '/^[\x{FF00}-\x{FFEF}]{1,63}$/iu', + 79 => '/^[\x{20000}-\x{2A6DF}]{1,63}$/iu', + 80 => '/^[\x{2F800}-\x{2FA1F}]{1,63}$/iu', +]; diff --git a/lib/laminas/laminas-validator/src/Hostname/Jp.php b/lib/laminas/laminas-validator/src/Hostname/Jp.php new file mode 100644 index 0000000000..8520f6a1d7 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Hostname/Jp.php @@ -0,0 +1,719 @@ + '/^[\x{002d}0-9a-z\x{3005}-\x{3007}\x{3041}-\x{3093}\x{309D}\x{309E}' + . '\x{30A1}-\x{30F6}\x{30FC}' + . '\x{30FD}\x{30FE}\x{4E00}\x{4E01}\x{4E03}\x{4E07}\x{4E08}\x{4E09}\x{4E0A}' + . '\x{4E0B}\x{4E0D}\x{4E0E}\x{4E10}\x{4E11}\x{4E14}\x{4E15}\x{4E16}\x{4E17}' + . '\x{4E18}\x{4E19}\x{4E1E}\x{4E21}\x{4E26}\x{4E2A}\x{4E2D}\x{4E31}\x{4E32}' + . '\x{4E36}\x{4E38}\x{4E39}\x{4E3B}\x{4E3C}\x{4E3F}\x{4E42}\x{4E43}\x{4E45}' + . '\x{4E4B}\x{4E4D}\x{4E4E}\x{4E4F}\x{4E55}\x{4E56}\x{4E57}\x{4E58}\x{4E59}' + . '\x{4E5D}\x{4E5E}\x{4E5F}\x{4E62}\x{4E71}\x{4E73}\x{4E7E}\x{4E80}\x{4E82}' + . '\x{4E85}\x{4E86}\x{4E88}\x{4E89}\x{4E8A}\x{4E8B}\x{4E8C}\x{4E8E}\x{4E91}' + . '\x{4E92}\x{4E94}\x{4E95}\x{4E98}\x{4E99}\x{4E9B}\x{4E9C}\x{4E9E}\x{4E9F}' + . '\x{4EA0}\x{4EA1}\x{4EA2}\x{4EA4}\x{4EA5}\x{4EA6}\x{4EA8}\x{4EAB}\x{4EAC}' + . '\x{4EAD}\x{4EAE}\x{4EB0}\x{4EB3}\x{4EB6}\x{4EBA}\x{4EC0}\x{4EC1}\x{4EC2}' + . '\x{4EC4}\x{4EC6}\x{4EC7}\x{4ECA}\x{4ECB}\x{4ECD}\x{4ECE}\x{4ECF}\x{4ED4}' + . '\x{4ED5}\x{4ED6}\x{4ED7}\x{4ED8}\x{4ED9}\x{4EDD}\x{4EDE}\x{4EDF}\x{4EE3}' + . '\x{4EE4}\x{4EE5}\x{4EED}\x{4EEE}\x{4EF0}\x{4EF2}\x{4EF6}\x{4EF7}\x{4EFB}' + . '\x{4F01}\x{4F09}\x{4F0A}\x{4F0D}\x{4F0E}\x{4F0F}\x{4F10}\x{4F11}\x{4F1A}' + . '\x{4F1C}\x{4F1D}\x{4F2F}\x{4F30}\x{4F34}\x{4F36}\x{4F38}\x{4F3A}\x{4F3C}' + . '\x{4F3D}\x{4F43}\x{4F46}\x{4F47}\x{4F4D}\x{4F4E}\x{4F4F}\x{4F50}\x{4F51}' + . '\x{4F53}\x{4F55}\x{4F57}\x{4F59}\x{4F5A}\x{4F5B}\x{4F5C}\x{4F5D}\x{4F5E}' + . '\x{4F69}\x{4F6F}\x{4F70}\x{4F73}\x{4F75}\x{4F76}\x{4F7B}\x{4F7C}\x{4F7F}' + . '\x{4F83}\x{4F86}\x{4F88}\x{4F8B}\x{4F8D}\x{4F8F}\x{4F91}\x{4F96}\x{4F98}' + . '\x{4F9B}\x{4F9D}\x{4FA0}\x{4FA1}\x{4FAB}\x{4FAD}\x{4FAE}\x{4FAF}\x{4FB5}' + . '\x{4FB6}\x{4FBF}\x{4FC2}\x{4FC3}\x{4FC4}\x{4FCA}\x{4FCE}\x{4FD0}\x{4FD1}' + . '\x{4FD4}\x{4FD7}\x{4FD8}\x{4FDA}\x{4FDB}\x{4FDD}\x{4FDF}\x{4FE1}\x{4FE3}' + . '\x{4FE4}\x{4FE5}\x{4FEE}\x{4FEF}\x{4FF3}\x{4FF5}\x{4FF6}\x{4FF8}\x{4FFA}' + . '\x{4FFE}\x{5005}\x{5006}\x{5009}\x{500B}\x{500D}\x{500F}\x{5011}\x{5012}' + . '\x{5014}\x{5016}\x{5019}\x{501A}\x{501F}\x{5021}\x{5023}\x{5024}\x{5025}' + . '\x{5026}\x{5028}\x{5029}\x{502A}\x{502B}\x{502C}\x{502D}\x{5036}\x{5039}' + . '\x{5043}\x{5047}\x{5048}\x{5049}\x{504F}\x{5050}\x{5055}\x{5056}\x{505A}' + . '\x{505C}\x{5065}\x{506C}\x{5072}\x{5074}\x{5075}\x{5076}\x{5078}\x{507D}' + . '\x{5080}\x{5085}\x{508D}\x{5091}\x{5098}\x{5099}\x{509A}\x{50AC}\x{50AD}' + . '\x{50B2}\x{50B3}\x{50B4}\x{50B5}\x{50B7}\x{50BE}\x{50C2}\x{50C5}\x{50C9}' + . '\x{50CA}\x{50CD}\x{50CF}\x{50D1}\x{50D5}\x{50D6}\x{50DA}\x{50DE}\x{50E3}' + . '\x{50E5}\x{50E7}\x{50ED}\x{50EE}\x{50F5}\x{50F9}\x{50FB}\x{5100}\x{5101}' + . '\x{5102}\x{5104}\x{5109}\x{5112}\x{5114}\x{5115}\x{5116}\x{5118}\x{511A}' + . '\x{511F}\x{5121}\x{512A}\x{5132}\x{5137}\x{513A}\x{513B}\x{513C}\x{513F}' + . '\x{5140}\x{5141}\x{5143}\x{5144}\x{5145}\x{5146}\x{5147}\x{5148}\x{5149}' + . '\x{514B}\x{514C}\x{514D}\x{514E}\x{5150}\x{5152}\x{5154}\x{515A}\x{515C}' + . '\x{5162}\x{5165}\x{5168}\x{5169}\x{516A}\x{516B}\x{516C}\x{516D}\x{516E}' + . '\x{5171}\x{5175}\x{5176}\x{5177}\x{5178}\x{517C}\x{5180}\x{5182}\x{5185}' + . '\x{5186}\x{5189}\x{518A}\x{518C}\x{518D}\x{518F}\x{5190}\x{5191}\x{5192}' + . '\x{5193}\x{5195}\x{5196}\x{5197}\x{5199}\x{51A0}\x{51A2}\x{51A4}\x{51A5}' + . '\x{51A6}\x{51A8}\x{51A9}\x{51AA}\x{51AB}\x{51AC}\x{51B0}\x{51B1}\x{51B2}' + . '\x{51B3}\x{51B4}\x{51B5}\x{51B6}\x{51B7}\x{51BD}\x{51C4}\x{51C5}\x{51C6}' + . '\x{51C9}\x{51CB}\x{51CC}\x{51CD}\x{51D6}\x{51DB}\x{51DC}\x{51DD}\x{51E0}' + . '\x{51E1}\x{51E6}\x{51E7}\x{51E9}\x{51EA}\x{51ED}\x{51F0}\x{51F1}\x{51F5}' + . '\x{51F6}\x{51F8}\x{51F9}\x{51FA}\x{51FD}\x{51FE}\x{5200}\x{5203}\x{5204}' + . '\x{5206}\x{5207}\x{5208}\x{520A}\x{520B}\x{520E}\x{5211}\x{5214}\x{5217}' + . '\x{521D}\x{5224}\x{5225}\x{5227}\x{5229}\x{522A}\x{522E}\x{5230}\x{5233}' + . '\x{5236}\x{5237}\x{5238}\x{5239}\x{523A}\x{523B}\x{5243}\x{5244}\x{5247}' + . '\x{524A}\x{524B}\x{524C}\x{524D}\x{524F}\x{5254}\x{5256}\x{525B}\x{525E}' + . '\x{5263}\x{5264}\x{5265}\x{5269}\x{526A}\x{526F}\x{5270}\x{5271}\x{5272}' + . '\x{5273}\x{5274}\x{5275}\x{527D}\x{527F}\x{5283}\x{5287}\x{5288}\x{5289}' + . '\x{528D}\x{5291}\x{5292}\x{5294}\x{529B}\x{529F}\x{52A0}\x{52A3}\x{52A9}' + . '\x{52AA}\x{52AB}\x{52AC}\x{52AD}\x{52B1}\x{52B4}\x{52B5}\x{52B9}\x{52BC}' + . '\x{52BE}\x{52C1}\x{52C3}\x{52C5}\x{52C7}\x{52C9}\x{52CD}\x{52D2}\x{52D5}' + . '\x{52D7}\x{52D8}\x{52D9}\x{52DD}\x{52DE}\x{52DF}\x{52E0}\x{52E2}\x{52E3}' + . '\x{52E4}\x{52E6}\x{52E7}\x{52F2}\x{52F3}\x{52F5}\x{52F8}\x{52F9}\x{52FA}' + . '\x{52FE}\x{52FF}\x{5301}\x{5302}\x{5305}\x{5306}\x{5308}\x{530D}\x{530F}' + . '\x{5310}\x{5315}\x{5316}\x{5317}\x{5319}\x{531A}\x{531D}\x{5320}\x{5321}' + . '\x{5323}\x{532A}\x{532F}\x{5331}\x{5333}\x{5338}\x{5339}\x{533A}\x{533B}' + . '\x{533F}\x{5340}\x{5341}\x{5343}\x{5345}\x{5346}\x{5347}\x{5348}\x{5349}' + . '\x{534A}\x{534D}\x{5351}\x{5352}\x{5353}\x{5354}\x{5357}\x{5358}\x{535A}' + . '\x{535C}\x{535E}\x{5360}\x{5366}\x{5369}\x{536E}\x{536F}\x{5370}\x{5371}' + . '\x{5373}\x{5374}\x{5375}\x{5377}\x{5378}\x{537B}\x{537F}\x{5382}\x{5384}' + . '\x{5396}\x{5398}\x{539A}\x{539F}\x{53A0}\x{53A5}\x{53A6}\x{53A8}\x{53A9}' + . '\x{53AD}\x{53AE}\x{53B0}\x{53B3}\x{53B6}\x{53BB}\x{53C2}\x{53C3}\x{53C8}' + . '\x{53C9}\x{53CA}\x{53CB}\x{53CC}\x{53CD}\x{53CE}\x{53D4}\x{53D6}\x{53D7}' + . '\x{53D9}\x{53DB}\x{53DF}\x{53E1}\x{53E2}\x{53E3}\x{53E4}\x{53E5}\x{53E8}' + . '\x{53E9}\x{53EA}\x{53EB}\x{53EC}\x{53ED}\x{53EE}\x{53EF}\x{53F0}\x{53F1}' + . '\x{53F2}\x{53F3}\x{53F6}\x{53F7}\x{53F8}\x{53FA}\x{5401}\x{5403}\x{5404}' + . '\x{5408}\x{5409}\x{540A}\x{540B}\x{540C}\x{540D}\x{540E}\x{540F}\x{5410}' + . '\x{5411}\x{541B}\x{541D}\x{541F}\x{5420}\x{5426}\x{5429}\x{542B}\x{542C}' + . '\x{542D}\x{542E}\x{5436}\x{5438}\x{5439}\x{543B}\x{543C}\x{543D}\x{543E}' + . '\x{5440}\x{5442}\x{5446}\x{5448}\x{5449}\x{544A}\x{544E}\x{5451}\x{545F}' + . '\x{5468}\x{546A}\x{5470}\x{5471}\x{5473}\x{5475}\x{5476}\x{5477}\x{547B}' + . '\x{547C}\x{547D}\x{5480}\x{5484}\x{5486}\x{548B}\x{548C}\x{548E}\x{548F}' + . '\x{5490}\x{5492}\x{54A2}\x{54A4}\x{54A5}\x{54A8}\x{54AB}\x{54AC}\x{54AF}' + . '\x{54B2}\x{54B3}\x{54B8}\x{54BC}\x{54BD}\x{54BE}\x{54C0}\x{54C1}\x{54C2}' + . '\x{54C4}\x{54C7}\x{54C8}\x{54C9}\x{54D8}\x{54E1}\x{54E2}\x{54E5}\x{54E6}' + . '\x{54E8}\x{54E9}\x{54ED}\x{54EE}\x{54F2}\x{54FA}\x{54FD}\x{5504}\x{5506}' + . '\x{5507}\x{550F}\x{5510}\x{5514}\x{5516}\x{552E}\x{552F}\x{5531}\x{5533}' + . '\x{5538}\x{5539}\x{553E}\x{5540}\x{5544}\x{5545}\x{5546}\x{554C}\x{554F}' + . '\x{5553}\x{5556}\x{5557}\x{555C}\x{555D}\x{5563}\x{557B}\x{557C}\x{557E}' + . '\x{5580}\x{5583}\x{5584}\x{5587}\x{5589}\x{558A}\x{558B}\x{5598}\x{5599}' + . '\x{559A}\x{559C}\x{559D}\x{559E}\x{559F}\x{55A7}\x{55A8}\x{55A9}\x{55AA}' + . '\x{55AB}\x{55AC}\x{55AE}\x{55B0}\x{55B6}\x{55C4}\x{55C5}\x{55C7}\x{55D4}' + . '\x{55DA}\x{55DC}\x{55DF}\x{55E3}\x{55E4}\x{55F7}\x{55F9}\x{55FD}\x{55FE}' + . '\x{5606}\x{5609}\x{5614}\x{5616}\x{5617}\x{5618}\x{561B}\x{5629}\x{562F}' + . '\x{5631}\x{5632}\x{5634}\x{5636}\x{5638}\x{5642}\x{564C}\x{564E}\x{5650}' + . '\x{565B}\x{5664}\x{5668}\x{566A}\x{566B}\x{566C}\x{5674}\x{5678}\x{567A}' + . '\x{5680}\x{5686}\x{5687}\x{568A}\x{568F}\x{5694}\x{56A0}\x{56A2}\x{56A5}' + . '\x{56AE}\x{56B4}\x{56B6}\x{56BC}\x{56C0}\x{56C1}\x{56C2}\x{56C3}\x{56C8}' + . '\x{56CE}\x{56D1}\x{56D3}\x{56D7}\x{56D8}\x{56DA}\x{56DB}\x{56DE}\x{56E0}' + . '\x{56E3}\x{56EE}\x{56F0}\x{56F2}\x{56F3}\x{56F9}\x{56FA}\x{56FD}\x{56FF}' + . '\x{5700}\x{5703}\x{5704}\x{5708}\x{5709}\x{570B}\x{570D}\x{570F}\x{5712}' + . '\x{5713}\x{5716}\x{5718}\x{571C}\x{571F}\x{5726}\x{5727}\x{5728}\x{572D}' + . '\x{5730}\x{5737}\x{5738}\x{573B}\x{5740}\x{5742}\x{5747}\x{574A}\x{574E}' + . '\x{574F}\x{5750}\x{5751}\x{5761}\x{5764}\x{5766}\x{5769}\x{576A}\x{577F}' + . '\x{5782}\x{5788}\x{5789}\x{578B}\x{5793}\x{57A0}\x{57A2}\x{57A3}\x{57A4}' + . '\x{57AA}\x{57B0}\x{57B3}\x{57C0}\x{57C3}\x{57C6}\x{57CB}\x{57CE}\x{57D2}' + . '\x{57D3}\x{57D4}\x{57D6}\x{57DC}\x{57DF}\x{57E0}\x{57E3}\x{57F4}\x{57F7}' + . '\x{57F9}\x{57FA}\x{57FC}\x{5800}\x{5802}\x{5805}\x{5806}\x{580A}\x{580B}' + . '\x{5815}\x{5819}\x{581D}\x{5821}\x{5824}\x{582A}\x{582F}\x{5830}\x{5831}' + . '\x{5834}\x{5835}\x{583A}\x{583D}\x{5840}\x{5841}\x{584A}\x{584B}\x{5851}' + . '\x{5852}\x{5854}\x{5857}\x{5858}\x{5859}\x{585A}\x{585E}\x{5862}\x{5869}' + . '\x{586B}\x{5870}\x{5872}\x{5875}\x{5879}\x{587E}\x{5883}\x{5885}\x{5893}' + . '\x{5897}\x{589C}\x{589F}\x{58A8}\x{58AB}\x{58AE}\x{58B3}\x{58B8}\x{58B9}' + . '\x{58BA}\x{58BB}\x{58BE}\x{58C1}\x{58C5}\x{58C7}\x{58CA}\x{58CC}\x{58D1}' + . '\x{58D3}\x{58D5}\x{58D7}\x{58D8}\x{58D9}\x{58DC}\x{58DE}\x{58DF}\x{58E4}' + . '\x{58E5}\x{58EB}\x{58EC}\x{58EE}\x{58EF}\x{58F0}\x{58F1}\x{58F2}\x{58F7}' + . '\x{58F9}\x{58FA}\x{58FB}\x{58FC}\x{58FD}\x{5902}\x{5909}\x{590A}\x{590F}' + . '\x{5910}\x{5915}\x{5916}\x{5918}\x{5919}\x{591A}\x{591B}\x{591C}\x{5922}' + . '\x{5925}\x{5927}\x{5929}\x{592A}\x{592B}\x{592C}\x{592D}\x{592E}\x{5931}' + . '\x{5932}\x{5937}\x{5938}\x{593E}\x{5944}\x{5947}\x{5948}\x{5949}\x{594E}' + . '\x{594F}\x{5950}\x{5951}\x{5954}\x{5955}\x{5957}\x{5958}\x{595A}\x{5960}' + . '\x{5962}\x{5965}\x{5967}\x{5968}\x{5969}\x{596A}\x{596C}\x{596E}\x{5973}' + . '\x{5974}\x{5978}\x{597D}\x{5981}\x{5982}\x{5983}\x{5984}\x{598A}\x{598D}' + . '\x{5993}\x{5996}\x{5999}\x{599B}\x{599D}\x{59A3}\x{59A5}\x{59A8}\x{59AC}' + . '\x{59B2}\x{59B9}\x{59BB}\x{59BE}\x{59C6}\x{59C9}\x{59CB}\x{59D0}\x{59D1}' + . '\x{59D3}\x{59D4}\x{59D9}\x{59DA}\x{59DC}\x{59E5}\x{59E6}\x{59E8}\x{59EA}' + . '\x{59EB}\x{59F6}\x{59FB}\x{59FF}\x{5A01}\x{5A03}\x{5A09}\x{5A11}\x{5A18}' + . '\x{5A1A}\x{5A1C}\x{5A1F}\x{5A20}\x{5A25}\x{5A29}\x{5A2F}\x{5A35}\x{5A36}' + . '\x{5A3C}\x{5A40}\x{5A41}\x{5A46}\x{5A49}\x{5A5A}\x{5A62}\x{5A66}\x{5A6A}' + . '\x{5A6C}\x{5A7F}\x{5A92}\x{5A9A}\x{5A9B}\x{5ABC}\x{5ABD}\x{5ABE}\x{5AC1}' + . '\x{5AC2}\x{5AC9}\x{5ACB}\x{5ACC}\x{5AD0}\x{5AD6}\x{5AD7}\x{5AE1}\x{5AE3}' + . '\x{5AE6}\x{5AE9}\x{5AFA}\x{5AFB}\x{5B09}\x{5B0B}\x{5B0C}\x{5B16}\x{5B22}' + . '\x{5B2A}\x{5B2C}\x{5B30}\x{5B32}\x{5B36}\x{5B3E}\x{5B40}\x{5B43}\x{5B45}' + . '\x{5B50}\x{5B51}\x{5B54}\x{5B55}\x{5B57}\x{5B58}\x{5B5A}\x{5B5B}\x{5B5C}' + . '\x{5B5D}\x{5B5F}\x{5B63}\x{5B64}\x{5B65}\x{5B66}\x{5B69}\x{5B6B}\x{5B70}' + . '\x{5B71}\x{5B73}\x{5B75}\x{5B78}\x{5B7A}\x{5B80}\x{5B83}\x{5B85}\x{5B87}' + . '\x{5B88}\x{5B89}\x{5B8B}\x{5B8C}\x{5B8D}\x{5B8F}\x{5B95}\x{5B97}\x{5B98}' + . '\x{5B99}\x{5B9A}\x{5B9B}\x{5B9C}\x{5B9D}\x{5B9F}\x{5BA2}\x{5BA3}\x{5BA4}' + . '\x{5BA5}\x{5BA6}\x{5BAE}\x{5BB0}\x{5BB3}\x{5BB4}\x{5BB5}\x{5BB6}\x{5BB8}' + . '\x{5BB9}\x{5BBF}\x{5BC2}\x{5BC3}\x{5BC4}\x{5BC5}\x{5BC6}\x{5BC7}\x{5BC9}' + . '\x{5BCC}\x{5BD0}\x{5BD2}\x{5BD3}\x{5BD4}\x{5BDB}\x{5BDD}\x{5BDE}\x{5BDF}' + . '\x{5BE1}\x{5BE2}\x{5BE4}\x{5BE5}\x{5BE6}\x{5BE7}\x{5BE8}\x{5BE9}\x{5BEB}' + . '\x{5BEE}\x{5BF0}\x{5BF3}\x{5BF5}\x{5BF6}\x{5BF8}\x{5BFA}\x{5BFE}\x{5BFF}' + . '\x{5C01}\x{5C02}\x{5C04}\x{5C05}\x{5C06}\x{5C07}\x{5C08}\x{5C09}\x{5C0A}' + . '\x{5C0B}\x{5C0D}\x{5C0E}\x{5C0F}\x{5C11}\x{5C13}\x{5C16}\x{5C1A}\x{5C20}' + . '\x{5C22}\x{5C24}\x{5C28}\x{5C2D}\x{5C31}\x{5C38}\x{5C39}\x{5C3A}\x{5C3B}' + . '\x{5C3C}\x{5C3D}\x{5C3E}\x{5C3F}\x{5C40}\x{5C41}\x{5C45}\x{5C46}\x{5C48}' + . '\x{5C4A}\x{5C4B}\x{5C4D}\x{5C4E}\x{5C4F}\x{5C50}\x{5C51}\x{5C53}\x{5C55}' + . '\x{5C5E}\x{5C60}\x{5C61}\x{5C64}\x{5C65}\x{5C6C}\x{5C6E}\x{5C6F}\x{5C71}' + . '\x{5C76}\x{5C79}\x{5C8C}\x{5C90}\x{5C91}\x{5C94}\x{5CA1}\x{5CA8}\x{5CA9}' + . '\x{5CAB}\x{5CAC}\x{5CB1}\x{5CB3}\x{5CB6}\x{5CB7}\x{5CB8}\x{5CBB}\x{5CBC}' + . '\x{5CBE}\x{5CC5}\x{5CC7}\x{5CD9}\x{5CE0}\x{5CE1}\x{5CE8}\x{5CE9}\x{5CEA}' + . '\x{5CED}\x{5CEF}\x{5CF0}\x{5CF6}\x{5CFA}\x{5CFB}\x{5CFD}\x{5D07}\x{5D0B}' + . '\x{5D0E}\x{5D11}\x{5D14}\x{5D15}\x{5D16}\x{5D17}\x{5D18}\x{5D19}\x{5D1A}' + . '\x{5D1B}\x{5D1F}\x{5D22}\x{5D29}\x{5D4B}\x{5D4C}\x{5D4E}\x{5D50}\x{5D52}' + . '\x{5D5C}\x{5D69}\x{5D6C}\x{5D6F}\x{5D73}\x{5D76}\x{5D82}\x{5D84}\x{5D87}' + . '\x{5D8B}\x{5D8C}\x{5D90}\x{5D9D}\x{5DA2}\x{5DAC}\x{5DAE}\x{5DB7}\x{5DBA}' + . '\x{5DBC}\x{5DBD}\x{5DC9}\x{5DCC}\x{5DCD}\x{5DD2}\x{5DD3}\x{5DD6}\x{5DDB}' + . '\x{5DDD}\x{5DDE}\x{5DE1}\x{5DE3}\x{5DE5}\x{5DE6}\x{5DE7}\x{5DE8}\x{5DEB}' + . '\x{5DEE}\x{5DF1}\x{5DF2}\x{5DF3}\x{5DF4}\x{5DF5}\x{5DF7}\x{5DFB}\x{5DFD}' + . '\x{5DFE}\x{5E02}\x{5E03}\x{5E06}\x{5E0B}\x{5E0C}\x{5E11}\x{5E16}\x{5E19}' + . '\x{5E1A}\x{5E1B}\x{5E1D}\x{5E25}\x{5E2B}\x{5E2D}\x{5E2F}\x{5E30}\x{5E33}' + . '\x{5E36}\x{5E37}\x{5E38}\x{5E3D}\x{5E40}\x{5E43}\x{5E44}\x{5E45}\x{5E47}' + . '\x{5E4C}\x{5E4E}\x{5E54}\x{5E55}\x{5E57}\x{5E5F}\x{5E61}\x{5E62}\x{5E63}' + . '\x{5E64}\x{5E72}\x{5E73}\x{5E74}\x{5E75}\x{5E76}\x{5E78}\x{5E79}\x{5E7A}' + . '\x{5E7B}\x{5E7C}\x{5E7D}\x{5E7E}\x{5E7F}\x{5E81}\x{5E83}\x{5E84}\x{5E87}' + . '\x{5E8A}\x{5E8F}\x{5E95}\x{5E96}\x{5E97}\x{5E9A}\x{5E9C}\x{5EA0}\x{5EA6}' + . '\x{5EA7}\x{5EAB}\x{5EAD}\x{5EB5}\x{5EB6}\x{5EB7}\x{5EB8}\x{5EC1}\x{5EC2}' + . '\x{5EC3}\x{5EC8}\x{5EC9}\x{5ECA}\x{5ECF}\x{5ED0}\x{5ED3}\x{5ED6}\x{5EDA}' + . '\x{5EDB}\x{5EDD}\x{5EDF}\x{5EE0}\x{5EE1}\x{5EE2}\x{5EE3}\x{5EE8}\x{5EE9}' + . '\x{5EEC}\x{5EF0}\x{5EF1}\x{5EF3}\x{5EF4}\x{5EF6}\x{5EF7}\x{5EF8}\x{5EFA}' + . '\x{5EFB}\x{5EFC}\x{5EFE}\x{5EFF}\x{5F01}\x{5F03}\x{5F04}\x{5F09}\x{5F0A}' + . '\x{5F0B}\x{5F0C}\x{5F0D}\x{5F0F}\x{5F10}\x{5F11}\x{5F13}\x{5F14}\x{5F15}' + . '\x{5F16}\x{5F17}\x{5F18}\x{5F1B}\x{5F1F}\x{5F25}\x{5F26}\x{5F27}\x{5F29}' + . '\x{5F2D}\x{5F2F}\x{5F31}\x{5F35}\x{5F37}\x{5F38}\x{5F3C}\x{5F3E}\x{5F41}' + . '\x{5F48}\x{5F4A}\x{5F4C}\x{5F4E}\x{5F51}\x{5F53}\x{5F56}\x{5F57}\x{5F59}' + . '\x{5F5C}\x{5F5D}\x{5F61}\x{5F62}\x{5F66}\x{5F69}\x{5F6A}\x{5F6B}\x{5F6C}' + . '\x{5F6D}\x{5F70}\x{5F71}\x{5F73}\x{5F77}\x{5F79}\x{5F7C}\x{5F7F}\x{5F80}' + . '\x{5F81}\x{5F82}\x{5F83}\x{5F84}\x{5F85}\x{5F87}\x{5F88}\x{5F8A}\x{5F8B}' + . '\x{5F8C}\x{5F90}\x{5F91}\x{5F92}\x{5F93}\x{5F97}\x{5F98}\x{5F99}\x{5F9E}' + . '\x{5FA0}\x{5FA1}\x{5FA8}\x{5FA9}\x{5FAA}\x{5FAD}\x{5FAE}\x{5FB3}\x{5FB4}' + . '\x{5FB9}\x{5FBC}\x{5FBD}\x{5FC3}\x{5FC5}\x{5FCC}\x{5FCD}\x{5FD6}\x{5FD7}' + . '\x{5FD8}\x{5FD9}\x{5FDC}\x{5FDD}\x{5FE0}\x{5FE4}\x{5FEB}\x{5FF0}\x{5FF1}' + . '\x{5FF5}\x{5FF8}\x{5FFB}\x{5FFD}\x{5FFF}\x{600E}\x{600F}\x{6010}\x{6012}' + . '\x{6015}\x{6016}\x{6019}\x{601B}\x{601C}\x{601D}\x{6020}\x{6021}\x{6025}' + . '\x{6026}\x{6027}\x{6028}\x{6029}\x{602A}\x{602B}\x{602F}\x{6031}\x{603A}' + . '\x{6041}\x{6042}\x{6043}\x{6046}\x{604A}\x{604B}\x{604D}\x{6050}\x{6052}' + . '\x{6055}\x{6059}\x{605A}\x{605F}\x{6060}\x{6062}\x{6063}\x{6064}\x{6065}' + . '\x{6068}\x{6069}\x{606A}\x{606B}\x{606C}\x{606D}\x{606F}\x{6070}\x{6075}' + . '\x{6077}\x{6081}\x{6083}\x{6084}\x{6089}\x{608B}\x{608C}\x{608D}\x{6092}' + . '\x{6094}\x{6096}\x{6097}\x{609A}\x{609B}\x{609F}\x{60A0}\x{60A3}\x{60A6}' + . '\x{60A7}\x{60A9}\x{60AA}\x{60B2}\x{60B3}\x{60B4}\x{60B5}\x{60B6}\x{60B8}' + . '\x{60BC}\x{60BD}\x{60C5}\x{60C6}\x{60C7}\x{60D1}\x{60D3}\x{60D8}\x{60DA}' + . '\x{60DC}\x{60DF}\x{60E0}\x{60E1}\x{60E3}\x{60E7}\x{60E8}\x{60F0}\x{60F1}' + . '\x{60F3}\x{60F4}\x{60F6}\x{60F7}\x{60F9}\x{60FA}\x{60FB}\x{6100}\x{6101}' + . '\x{6103}\x{6106}\x{6108}\x{6109}\x{610D}\x{610E}\x{610F}\x{6115}\x{611A}' + . '\x{611B}\x{611F}\x{6121}\x{6127}\x{6128}\x{612C}\x{6134}\x{613C}\x{613D}' + . '\x{613E}\x{613F}\x{6142}\x{6144}\x{6147}\x{6148}\x{614A}\x{614B}\x{614C}' + . '\x{614D}\x{614E}\x{6153}\x{6155}\x{6158}\x{6159}\x{615A}\x{615D}\x{615F}' + . '\x{6162}\x{6163}\x{6165}\x{6167}\x{6168}\x{616B}\x{616E}\x{616F}\x{6170}' + . '\x{6171}\x{6173}\x{6174}\x{6175}\x{6176}\x{6177}\x{617E}\x{6182}\x{6187}' + . '\x{618A}\x{618E}\x{6190}\x{6191}\x{6194}\x{6196}\x{6199}\x{619A}\x{61A4}' + . '\x{61A7}\x{61A9}\x{61AB}\x{61AC}\x{61AE}\x{61B2}\x{61B6}\x{61BA}\x{61BE}' + . '\x{61C3}\x{61C6}\x{61C7}\x{61C8}\x{61C9}\x{61CA}\x{61CB}\x{61CC}\x{61CD}' + . '\x{61D0}\x{61E3}\x{61E6}\x{61F2}\x{61F4}\x{61F6}\x{61F7}\x{61F8}\x{61FA}' + . '\x{61FC}\x{61FD}\x{61FE}\x{61FF}\x{6200}\x{6208}\x{6209}\x{620A}\x{620C}' + . '\x{620D}\x{620E}\x{6210}\x{6211}\x{6212}\x{6214}\x{6216}\x{621A}\x{621B}' + . '\x{621D}\x{621E}\x{621F}\x{6221}\x{6226}\x{622A}\x{622E}\x{622F}\x{6230}' + . '\x{6232}\x{6233}\x{6234}\x{6238}\x{623B}\x{623F}\x{6240}\x{6241}\x{6247}' + . '\x{6248}\x{6249}\x{624B}\x{624D}\x{624E}\x{6253}\x{6255}\x{6258}\x{625B}' + . '\x{625E}\x{6260}\x{6263}\x{6268}\x{626E}\x{6271}\x{6276}\x{6279}\x{627C}' + . '\x{627E}\x{627F}\x{6280}\x{6282}\x{6283}\x{6284}\x{6289}\x{628A}\x{6291}' + . '\x{6292}\x{6293}\x{6294}\x{6295}\x{6296}\x{6297}\x{6298}\x{629B}\x{629C}' + . '\x{629E}\x{62AB}\x{62AC}\x{62B1}\x{62B5}\x{62B9}\x{62BB}\x{62BC}\x{62BD}' + . '\x{62C2}\x{62C5}\x{62C6}\x{62C7}\x{62C8}\x{62C9}\x{62CA}\x{62CC}\x{62CD}' + . '\x{62CF}\x{62D0}\x{62D1}\x{62D2}\x{62D3}\x{62D4}\x{62D7}\x{62D8}\x{62D9}' + . '\x{62DB}\x{62DC}\x{62DD}\x{62E0}\x{62E1}\x{62EC}\x{62ED}\x{62EE}\x{62EF}' + . '\x{62F1}\x{62F3}\x{62F5}\x{62F6}\x{62F7}\x{62FE}\x{62FF}\x{6301}\x{6302}' + . '\x{6307}\x{6308}\x{6309}\x{630C}\x{6311}\x{6319}\x{631F}\x{6327}\x{6328}' + . '\x{632B}\x{632F}\x{633A}\x{633D}\x{633E}\x{633F}\x{6349}\x{634C}\x{634D}' + . '\x{634F}\x{6350}\x{6355}\x{6357}\x{635C}\x{6367}\x{6368}\x{6369}\x{636B}' + . '\x{636E}\x{6372}\x{6376}\x{6377}\x{637A}\x{637B}\x{6380}\x{6383}\x{6388}' + . '\x{6389}\x{638C}\x{638E}\x{638F}\x{6392}\x{6396}\x{6398}\x{639B}\x{639F}' + . '\x{63A0}\x{63A1}\x{63A2}\x{63A3}\x{63A5}\x{63A7}\x{63A8}\x{63A9}\x{63AA}' + . '\x{63AB}\x{63AC}\x{63B2}\x{63B4}\x{63B5}\x{63BB}\x{63BE}\x{63C0}\x{63C3}' + . '\x{63C4}\x{63C6}\x{63C9}\x{63CF}\x{63D0}\x{63D2}\x{63D6}\x{63DA}\x{63DB}' + . '\x{63E1}\x{63E3}\x{63E9}\x{63EE}\x{63F4}\x{63F6}\x{63FA}\x{6406}\x{640D}' + . '\x{640F}\x{6413}\x{6416}\x{6417}\x{641C}\x{6426}\x{6428}\x{642C}\x{642D}' + . '\x{6434}\x{6436}\x{643A}\x{643E}\x{6442}\x{644E}\x{6458}\x{6467}\x{6469}' + . '\x{646F}\x{6476}\x{6478}\x{647A}\x{6483}\x{6488}\x{6492}\x{6493}\x{6495}' + . '\x{649A}\x{649E}\x{64A4}\x{64A5}\x{64A9}\x{64AB}\x{64AD}\x{64AE}\x{64B0}' + . '\x{64B2}\x{64B9}\x{64BB}\x{64BC}\x{64C1}\x{64C2}\x{64C5}\x{64C7}\x{64CD}' + . '\x{64D2}\x{64D4}\x{64D8}\x{64DA}\x{64E0}\x{64E1}\x{64E2}\x{64E3}\x{64E6}' + . '\x{64E7}\x{64EC}\x{64EF}\x{64F1}\x{64F2}\x{64F4}\x{64F6}\x{64FA}\x{64FD}' + . '\x{64FE}\x{6500}\x{6505}\x{6518}\x{651C}\x{651D}\x{6523}\x{6524}\x{652A}' + . '\x{652B}\x{652C}\x{652F}\x{6534}\x{6535}\x{6536}\x{6537}\x{6538}\x{6539}' + . '\x{653B}\x{653E}\x{653F}\x{6545}\x{6548}\x{654D}\x{654F}\x{6551}\x{6555}' + . '\x{6556}\x{6557}\x{6558}\x{6559}\x{655D}\x{655E}\x{6562}\x{6563}\x{6566}' + . '\x{656C}\x{6570}\x{6572}\x{6574}\x{6575}\x{6577}\x{6578}\x{6582}\x{6583}' + . '\x{6587}\x{6588}\x{6589}\x{658C}\x{658E}\x{6590}\x{6591}\x{6597}\x{6599}' + . '\x{659B}\x{659C}\x{659F}\x{65A1}\x{65A4}\x{65A5}\x{65A7}\x{65AB}\x{65AC}' + . '\x{65AD}\x{65AF}\x{65B0}\x{65B7}\x{65B9}\x{65BC}\x{65BD}\x{65C1}\x{65C3}' + . '\x{65C4}\x{65C5}\x{65C6}\x{65CB}\x{65CC}\x{65CF}\x{65D2}\x{65D7}\x{65D9}' + . '\x{65DB}\x{65E0}\x{65E1}\x{65E2}\x{65E5}\x{65E6}\x{65E7}\x{65E8}\x{65E9}' + . '\x{65EC}\x{65ED}\x{65F1}\x{65FA}\x{65FB}\x{6602}\x{6603}\x{6606}\x{6607}' + . '\x{660A}\x{660C}\x{660E}\x{660F}\x{6613}\x{6614}\x{661C}\x{661F}\x{6620}' + . '\x{6625}\x{6627}\x{6628}\x{662D}\x{662F}\x{6634}\x{6635}\x{6636}\x{663C}' + . '\x{663F}\x{6641}\x{6642}\x{6643}\x{6644}\x{6649}\x{664B}\x{664F}\x{6652}' + . '\x{665D}\x{665E}\x{665F}\x{6662}\x{6664}\x{6666}\x{6667}\x{6668}\x{6669}' + . '\x{666E}\x{666F}\x{6670}\x{6674}\x{6676}\x{667A}\x{6681}\x{6683}\x{6684}' + . '\x{6687}\x{6688}\x{6689}\x{668E}\x{6691}\x{6696}\x{6697}\x{6698}\x{669D}' + . '\x{66A2}\x{66A6}\x{66AB}\x{66AE}\x{66B4}\x{66B8}\x{66B9}\x{66BC}\x{66BE}' + . '\x{66C1}\x{66C4}\x{66C7}\x{66C9}\x{66D6}\x{66D9}\x{66DA}\x{66DC}\x{66DD}' + . '\x{66E0}\x{66E6}\x{66E9}\x{66F0}\x{66F2}\x{66F3}\x{66F4}\x{66F5}\x{66F7}' + . '\x{66F8}\x{66F9}\x{66FC}\x{66FD}\x{66FE}\x{66FF}\x{6700}\x{6703}\x{6708}' + . '\x{6709}\x{670B}\x{670D}\x{670F}\x{6714}\x{6715}\x{6716}\x{6717}\x{671B}' + . '\x{671D}\x{671E}\x{671F}\x{6726}\x{6727}\x{6728}\x{672A}\x{672B}\x{672C}' + . '\x{672D}\x{672E}\x{6731}\x{6734}\x{6736}\x{6737}\x{6738}\x{673A}\x{673D}' + . '\x{673F}\x{6741}\x{6746}\x{6749}\x{674E}\x{674F}\x{6750}\x{6751}\x{6753}' + . '\x{6756}\x{6759}\x{675C}\x{675E}\x{675F}\x{6760}\x{6761}\x{6762}\x{6763}' + . '\x{6764}\x{6765}\x{676A}\x{676D}\x{676F}\x{6770}\x{6771}\x{6772}\x{6773}' + . '\x{6775}\x{6777}\x{677C}\x{677E}\x{677F}\x{6785}\x{6787}\x{6789}\x{678B}' + . '\x{678C}\x{6790}\x{6795}\x{6797}\x{679A}\x{679C}\x{679D}\x{67A0}\x{67A1}' + . '\x{67A2}\x{67A6}\x{67A9}\x{67AF}\x{67B3}\x{67B4}\x{67B6}\x{67B7}\x{67B8}' + . '\x{67B9}\x{67C1}\x{67C4}\x{67C6}\x{67CA}\x{67CE}\x{67CF}\x{67D0}\x{67D1}' + . '\x{67D3}\x{67D4}\x{67D8}\x{67DA}\x{67DD}\x{67DE}\x{67E2}\x{67E4}\x{67E7}' + . '\x{67E9}\x{67EC}\x{67EE}\x{67EF}\x{67F1}\x{67F3}\x{67F4}\x{67F5}\x{67FB}' + . '\x{67FE}\x{67FF}\x{6802}\x{6803}\x{6804}\x{6813}\x{6816}\x{6817}\x{681E}' + . '\x{6821}\x{6822}\x{6829}\x{682A}\x{682B}\x{6832}\x{6834}\x{6838}\x{6839}' + . '\x{683C}\x{683D}\x{6840}\x{6841}\x{6842}\x{6843}\x{6846}\x{6848}\x{684D}' + . '\x{684E}\x{6850}\x{6851}\x{6853}\x{6854}\x{6859}\x{685C}\x{685D}\x{685F}' + . '\x{6863}\x{6867}\x{6874}\x{6876}\x{6877}\x{687E}\x{687F}\x{6881}\x{6883}' + . '\x{6885}\x{688D}\x{688F}\x{6893}\x{6894}\x{6897}\x{689B}\x{689D}\x{689F}' + . '\x{68A0}\x{68A2}\x{68A6}\x{68A7}\x{68A8}\x{68AD}\x{68AF}\x{68B0}\x{68B1}' + . '\x{68B3}\x{68B5}\x{68B6}\x{68B9}\x{68BA}\x{68BC}\x{68C4}\x{68C6}\x{68C9}' + . '\x{68CA}\x{68CB}\x{68CD}\x{68D2}\x{68D4}\x{68D5}\x{68D7}\x{68D8}\x{68DA}' + . '\x{68DF}\x{68E0}\x{68E1}\x{68E3}\x{68E7}\x{68EE}\x{68EF}\x{68F2}\x{68F9}' + . '\x{68FA}\x{6900}\x{6901}\x{6904}\x{6905}\x{6908}\x{690B}\x{690C}\x{690D}' + . '\x{690E}\x{690F}\x{6912}\x{6919}\x{691A}\x{691B}\x{691C}\x{6921}\x{6922}' + . '\x{6923}\x{6925}\x{6926}\x{6928}\x{692A}\x{6930}\x{6934}\x{6936}\x{6939}' + . '\x{693D}\x{693F}\x{694A}\x{6953}\x{6954}\x{6955}\x{6959}\x{695A}\x{695C}' + . '\x{695D}\x{695E}\x{6960}\x{6961}\x{6962}\x{696A}\x{696B}\x{696D}\x{696E}' + . '\x{696F}\x{6973}\x{6974}\x{6975}\x{6977}\x{6978}\x{6979}\x{697C}\x{697D}' + . '\x{697E}\x{6981}\x{6982}\x{698A}\x{698E}\x{6991}\x{6994}\x{6995}\x{699B}' + . '\x{699C}\x{69A0}\x{69A7}\x{69AE}\x{69B1}\x{69B2}\x{69B4}\x{69BB}\x{69BE}' + . '\x{69BF}\x{69C1}\x{69C3}\x{69C7}\x{69CA}\x{69CB}\x{69CC}\x{69CD}\x{69CE}' + . '\x{69D0}\x{69D3}\x{69D8}\x{69D9}\x{69DD}\x{69DE}\x{69E7}\x{69E8}\x{69EB}' + . '\x{69ED}\x{69F2}\x{69F9}\x{69FB}\x{69FD}\x{69FF}\x{6A02}\x{6A05}\x{6A0A}' + . '\x{6A0B}\x{6A0C}\x{6A12}\x{6A13}\x{6A14}\x{6A17}\x{6A19}\x{6A1B}\x{6A1E}' + . '\x{6A1F}\x{6A21}\x{6A22}\x{6A23}\x{6A29}\x{6A2A}\x{6A2B}\x{6A2E}\x{6A35}' + . '\x{6A36}\x{6A38}\x{6A39}\x{6A3A}\x{6A3D}\x{6A44}\x{6A47}\x{6A48}\x{6A4B}' + . '\x{6A58}\x{6A59}\x{6A5F}\x{6A61}\x{6A62}\x{6A66}\x{6A72}\x{6A78}\x{6A7F}' + . '\x{6A80}\x{6A84}\x{6A8D}\x{6A8E}\x{6A90}\x{6A97}\x{6A9C}\x{6AA0}\x{6AA2}' + . '\x{6AA3}\x{6AAA}\x{6AAC}\x{6AAE}\x{6AB3}\x{6AB8}\x{6ABB}\x{6AC1}\x{6AC2}' + . '\x{6AC3}\x{6AD1}\x{6AD3}\x{6ADA}\x{6ADB}\x{6ADE}\x{6ADF}\x{6AE8}\x{6AEA}' + . '\x{6AFA}\x{6AFB}\x{6B04}\x{6B05}\x{6B0A}\x{6B12}\x{6B16}\x{6B1D}\x{6B1F}' + . '\x{6B20}\x{6B21}\x{6B23}\x{6B27}\x{6B32}\x{6B37}\x{6B38}\x{6B39}\x{6B3A}' + . '\x{6B3D}\x{6B3E}\x{6B43}\x{6B47}\x{6B49}\x{6B4C}\x{6B4E}\x{6B50}\x{6B53}' + . '\x{6B54}\x{6B59}\x{6B5B}\x{6B5F}\x{6B61}\x{6B62}\x{6B63}\x{6B64}\x{6B66}' + . '\x{6B69}\x{6B6A}\x{6B6F}\x{6B73}\x{6B74}\x{6B78}\x{6B79}\x{6B7B}\x{6B7F}' + . '\x{6B80}\x{6B83}\x{6B84}\x{6B86}\x{6B89}\x{6B8A}\x{6B8B}\x{6B8D}\x{6B95}' + . '\x{6B96}\x{6B98}\x{6B9E}\x{6BA4}\x{6BAA}\x{6BAB}\x{6BAF}\x{6BB1}\x{6BB2}' + . '\x{6BB3}\x{6BB4}\x{6BB5}\x{6BB7}\x{6BBA}\x{6BBB}\x{6BBC}\x{6BBF}\x{6BC0}' + . '\x{6BC5}\x{6BC6}\x{6BCB}\x{6BCD}\x{6BCE}\x{6BD2}\x{6BD3}\x{6BD4}\x{6BD8}' + . '\x{6BDB}\x{6BDF}\x{6BEB}\x{6BEC}\x{6BEF}\x{6BF3}\x{6C08}\x{6C0F}\x{6C11}' + . '\x{6C13}\x{6C14}\x{6C17}\x{6C1B}\x{6C23}\x{6C24}\x{6C34}\x{6C37}\x{6C38}' + . '\x{6C3E}\x{6C40}\x{6C41}\x{6C42}\x{6C4E}\x{6C50}\x{6C55}\x{6C57}\x{6C5A}' + . '\x{6C5D}\x{6C5E}\x{6C5F}\x{6C60}\x{6C62}\x{6C68}\x{6C6A}\x{6C70}\x{6C72}' + . '\x{6C73}\x{6C7A}\x{6C7D}\x{6C7E}\x{6C81}\x{6C82}\x{6C83}\x{6C88}\x{6C8C}' + . '\x{6C8D}\x{6C90}\x{6C92}\x{6C93}\x{6C96}\x{6C99}\x{6C9A}\x{6C9B}\x{6CA1}' + . '\x{6CA2}\x{6CAB}\x{6CAE}\x{6CB1}\x{6CB3}\x{6CB8}\x{6CB9}\x{6CBA}\x{6CBB}' + . '\x{6CBC}\x{6CBD}\x{6CBE}\x{6CBF}\x{6CC1}\x{6CC4}\x{6CC5}\x{6CC9}\x{6CCA}' + . '\x{6CCC}\x{6CD3}\x{6CD5}\x{6CD7}\x{6CD9}\x{6CDB}\x{6CDD}\x{6CE1}\x{6CE2}' + . '\x{6CE3}\x{6CE5}\x{6CE8}\x{6CEA}\x{6CEF}\x{6CF0}\x{6CF1}\x{6CF3}\x{6D0B}' + . '\x{6D0C}\x{6D12}\x{6D17}\x{6D19}\x{6D1B}\x{6D1E}\x{6D1F}\x{6D25}\x{6D29}' + . '\x{6D2A}\x{6D2B}\x{6D32}\x{6D33}\x{6D35}\x{6D36}\x{6D38}\x{6D3B}\x{6D3D}' + . '\x{6D3E}\x{6D41}\x{6D44}\x{6D45}\x{6D59}\x{6D5A}\x{6D5C}\x{6D63}\x{6D64}' + . '\x{6D66}\x{6D69}\x{6D6A}\x{6D6C}\x{6D6E}\x{6D74}\x{6D77}\x{6D78}\x{6D79}' + . '\x{6D85}\x{6D88}\x{6D8C}\x{6D8E}\x{6D93}\x{6D95}\x{6D99}\x{6D9B}\x{6D9C}' + . '\x{6DAF}\x{6DB2}\x{6DB5}\x{6DB8}\x{6DBC}\x{6DC0}\x{6DC5}\x{6DC6}\x{6DC7}' + . '\x{6DCB}\x{6DCC}\x{6DD1}\x{6DD2}\x{6DD5}\x{6DD8}\x{6DD9}\x{6DDE}\x{6DE1}' + . '\x{6DE4}\x{6DE6}\x{6DE8}\x{6DEA}\x{6DEB}\x{6DEC}\x{6DEE}\x{6DF1}\x{6DF3}' + . '\x{6DF5}\x{6DF7}\x{6DF9}\x{6DFA}\x{6DFB}\x{6E05}\x{6E07}\x{6E08}\x{6E09}' + . '\x{6E0A}\x{6E0B}\x{6E13}\x{6E15}\x{6E19}\x{6E1A}\x{6E1B}\x{6E1D}\x{6E1F}' + . '\x{6E20}\x{6E21}\x{6E23}\x{6E24}\x{6E25}\x{6E26}\x{6E29}\x{6E2B}\x{6E2C}' + . '\x{6E2D}\x{6E2E}\x{6E2F}\x{6E38}\x{6E3A}\x{6E3E}\x{6E43}\x{6E4A}\x{6E4D}' + . '\x{6E4E}\x{6E56}\x{6E58}\x{6E5B}\x{6E5F}\x{6E67}\x{6E6B}\x{6E6E}\x{6E6F}' + . '\x{6E72}\x{6E76}\x{6E7E}\x{6E7F}\x{6E80}\x{6E82}\x{6E8C}\x{6E8F}\x{6E90}' + . '\x{6E96}\x{6E98}\x{6E9C}\x{6E9D}\x{6E9F}\x{6EA2}\x{6EA5}\x{6EAA}\x{6EAF}' + . '\x{6EB2}\x{6EB6}\x{6EB7}\x{6EBA}\x{6EBD}\x{6EC2}\x{6EC4}\x{6EC5}\x{6EC9}' + . '\x{6ECB}\x{6ECC}\x{6ED1}\x{6ED3}\x{6ED4}\x{6ED5}\x{6EDD}\x{6EDE}\x{6EEC}' + . '\x{6EEF}\x{6EF2}\x{6EF4}\x{6EF7}\x{6EF8}\x{6EFE}\x{6EFF}\x{6F01}\x{6F02}' + . '\x{6F06}\x{6F09}\x{6F0F}\x{6F11}\x{6F13}\x{6F14}\x{6F15}\x{6F20}\x{6F22}' + . '\x{6F23}\x{6F2B}\x{6F2C}\x{6F31}\x{6F32}\x{6F38}\x{6F3E}\x{6F3F}\x{6F41}' + . '\x{6F45}\x{6F54}\x{6F58}\x{6F5B}\x{6F5C}\x{6F5F}\x{6F64}\x{6F66}\x{6F6D}' + . '\x{6F6E}\x{6F6F}\x{6F70}\x{6F74}\x{6F78}\x{6F7A}\x{6F7C}\x{6F80}\x{6F81}' + . '\x{6F82}\x{6F84}\x{6F86}\x{6F8E}\x{6F91}\x{6F97}\x{6FA1}\x{6FA3}\x{6FA4}' + . '\x{6FAA}\x{6FB1}\x{6FB3}\x{6FB9}\x{6FC0}\x{6FC1}\x{6FC2}\x{6FC3}\x{6FC6}' + . '\x{6FD4}\x{6FD5}\x{6FD8}\x{6FDB}\x{6FDF}\x{6FE0}\x{6FE1}\x{6FE4}\x{6FEB}' + . '\x{6FEC}\x{6FEE}\x{6FEF}\x{6FF1}\x{6FF3}\x{6FF6}\x{6FFA}\x{6FFE}\x{7001}' + . '\x{7009}\x{700B}\x{700F}\x{7011}\x{7015}\x{7018}\x{701A}\x{701B}\x{701D}' + . '\x{701E}\x{701F}\x{7026}\x{7027}\x{702C}\x{7030}\x{7032}\x{703E}\x{704C}' + . '\x{7051}\x{7058}\x{7063}\x{706B}\x{706F}\x{7070}\x{7078}\x{707C}\x{707D}' + . '\x{7089}\x{708A}\x{708E}\x{7092}\x{7099}\x{70AC}\x{70AD}\x{70AE}\x{70AF}' + . '\x{70B3}\x{70B8}\x{70B9}\x{70BA}\x{70C8}\x{70CB}\x{70CF}\x{70D9}\x{70DD}' + . '\x{70DF}\x{70F1}\x{70F9}\x{70FD}\x{7109}\x{7114}\x{7119}\x{711A}\x{711C}' + . '\x{7121}\x{7126}\x{7136}\x{713C}\x{7149}\x{714C}\x{714E}\x{7155}\x{7156}' + . '\x{7159}\x{7162}\x{7164}\x{7165}\x{7166}\x{7167}\x{7169}\x{716C}\x{716E}' + . '\x{717D}\x{7184}\x{7188}\x{718A}\x{718F}\x{7194}\x{7195}\x{7199}\x{719F}' + . '\x{71A8}\x{71AC}\x{71B1}\x{71B9}\x{71BE}\x{71C3}\x{71C8}\x{71C9}\x{71CE}' + . '\x{71D0}\x{71D2}\x{71D4}\x{71D5}\x{71D7}\x{71DF}\x{71E0}\x{71E5}\x{71E6}' + . '\x{71E7}\x{71EC}\x{71ED}\x{71EE}\x{71F5}\x{71F9}\x{71FB}\x{71FC}\x{71FF}' + . '\x{7206}\x{720D}\x{7210}\x{721B}\x{7228}\x{722A}\x{722C}\x{722D}\x{7230}' + . '\x{7232}\x{7235}\x{7236}\x{723A}\x{723B}\x{723C}\x{723D}\x{723E}\x{723F}' + . '\x{7240}\x{7246}\x{7247}\x{7248}\x{724B}\x{724C}\x{7252}\x{7258}\x{7259}' + . '\x{725B}\x{725D}\x{725F}\x{7261}\x{7262}\x{7267}\x{7269}\x{7272}\x{7274}' + . '\x{7279}\x{727D}\x{727E}\x{7280}\x{7281}\x{7282}\x{7287}\x{7292}\x{7296}' + . '\x{72A0}\x{72A2}\x{72A7}\x{72AC}\x{72AF}\x{72B2}\x{72B6}\x{72B9}\x{72C2}' + . '\x{72C3}\x{72C4}\x{72C6}\x{72CE}\x{72D0}\x{72D2}\x{72D7}\x{72D9}\x{72DB}' + . '\x{72E0}\x{72E1}\x{72E2}\x{72E9}\x{72EC}\x{72ED}\x{72F7}\x{72F8}\x{72F9}' + . '\x{72FC}\x{72FD}\x{730A}\x{7316}\x{7317}\x{731B}\x{731C}\x{731D}\x{731F}' + . '\x{7325}\x{7329}\x{732A}\x{732B}\x{732E}\x{732F}\x{7334}\x{7336}\x{7337}' + . '\x{733E}\x{733F}\x{7344}\x{7345}\x{734E}\x{734F}\x{7357}\x{7363}\x{7368}' + . '\x{736A}\x{7370}\x{7372}\x{7375}\x{7378}\x{737A}\x{737B}\x{7384}\x{7387}' + . '\x{7389}\x{738B}\x{7396}\x{73A9}\x{73B2}\x{73B3}\x{73BB}\x{73C0}\x{73C2}' + . '\x{73C8}\x{73CA}\x{73CD}\x{73CE}\x{73DE}\x{73E0}\x{73E5}\x{73EA}\x{73ED}' + . '\x{73EE}\x{73F1}\x{73F8}\x{73FE}\x{7403}\x{7405}\x{7406}\x{7409}\x{7422}' + . '\x{7425}\x{7432}\x{7433}\x{7434}\x{7435}\x{7436}\x{743A}\x{743F}\x{7441}' + . '\x{7455}\x{7459}\x{745A}\x{745B}\x{745C}\x{745E}\x{745F}\x{7460}\x{7463}' + . '\x{7464}\x{7469}\x{746A}\x{746F}\x{7470}\x{7473}\x{7476}\x{747E}\x{7483}' + . '\x{748B}\x{749E}\x{74A2}\x{74A7}\x{74B0}\x{74BD}\x{74CA}\x{74CF}\x{74D4}' + . '\x{74DC}\x{74E0}\x{74E2}\x{74E3}\x{74E6}\x{74E7}\x{74E9}\x{74EE}\x{74F0}' + . '\x{74F1}\x{74F2}\x{74F6}\x{74F7}\x{74F8}\x{7503}\x{7504}\x{7505}\x{750C}' + . '\x{750D}\x{750E}\x{7511}\x{7513}\x{7515}\x{7518}\x{751A}\x{751C}\x{751E}' + . '\x{751F}\x{7523}\x{7525}\x{7526}\x{7528}\x{752B}\x{752C}\x{7530}\x{7531}' + . '\x{7532}\x{7533}\x{7537}\x{7538}\x{753A}\x{753B}\x{753C}\x{7544}\x{7546}' + . '\x{7549}\x{754A}\x{754B}\x{754C}\x{754D}\x{754F}\x{7551}\x{7554}\x{7559}' + . '\x{755A}\x{755B}\x{755C}\x{755D}\x{7560}\x{7562}\x{7564}\x{7565}\x{7566}' + . '\x{7567}\x{7569}\x{756A}\x{756B}\x{756D}\x{7570}\x{7573}\x{7574}\x{7576}' + . '\x{7577}\x{7578}\x{757F}\x{7582}\x{7586}\x{7587}\x{7589}\x{758A}\x{758B}' + . '\x{758E}\x{758F}\x{7591}\x{7594}\x{759A}\x{759D}\x{75A3}\x{75A5}\x{75AB}' + . '\x{75B1}\x{75B2}\x{75B3}\x{75B5}\x{75B8}\x{75B9}\x{75BC}\x{75BD}\x{75BE}' + . '\x{75C2}\x{75C3}\x{75C5}\x{75C7}\x{75CA}\x{75CD}\x{75D2}\x{75D4}\x{75D5}' + . '\x{75D8}\x{75D9}\x{75DB}\x{75DE}\x{75E2}\x{75E3}\x{75E9}\x{75F0}\x{75F2}' + . '\x{75F3}\x{75F4}\x{75FA}\x{75FC}\x{75FE}\x{75FF}\x{7601}\x{7609}\x{760B}' + . '\x{760D}\x{761F}\x{7620}\x{7621}\x{7622}\x{7624}\x{7627}\x{7630}\x{7634}' + . '\x{763B}\x{7642}\x{7646}\x{7647}\x{7648}\x{764C}\x{7652}\x{7656}\x{7658}' + . '\x{765C}\x{7661}\x{7662}\x{7667}\x{7668}\x{7669}\x{766A}\x{766C}\x{7670}' + . '\x{7672}\x{7676}\x{7678}\x{767A}\x{767B}\x{767C}\x{767D}\x{767E}\x{7680}' + . '\x{7683}\x{7684}\x{7686}\x{7687}\x{7688}\x{768B}\x{768E}\x{7690}\x{7693}' + . '\x{7696}\x{7699}\x{769A}\x{76AE}\x{76B0}\x{76B4}\x{76B7}\x{76B8}\x{76B9}' + . '\x{76BA}\x{76BF}\x{76C2}\x{76C3}\x{76C6}\x{76C8}\x{76CA}\x{76CD}\x{76D2}' + . '\x{76D6}\x{76D7}\x{76DB}\x{76DC}\x{76DE}\x{76DF}\x{76E1}\x{76E3}\x{76E4}' + . '\x{76E5}\x{76E7}\x{76EA}\x{76EE}\x{76F2}\x{76F4}\x{76F8}\x{76FB}\x{76FE}' + . '\x{7701}\x{7704}\x{7707}\x{7708}\x{7709}\x{770B}\x{770C}\x{771B}\x{771E}' + . '\x{771F}\x{7720}\x{7724}\x{7725}\x{7726}\x{7729}\x{7737}\x{7738}\x{773A}' + . '\x{773C}\x{7740}\x{7747}\x{775A}\x{775B}\x{7761}\x{7763}\x{7765}\x{7766}' + . '\x{7768}\x{776B}\x{7779}\x{777E}\x{777F}\x{778B}\x{778E}\x{7791}\x{779E}' + . '\x{77A0}\x{77A5}\x{77AC}\x{77AD}\x{77B0}\x{77B3}\x{77B6}\x{77B9}\x{77BB}' + . '\x{77BC}\x{77BD}\x{77BF}\x{77C7}\x{77CD}\x{77D7}\x{77DA}\x{77DB}\x{77DC}' + . '\x{77E2}\x{77E3}\x{77E5}\x{77E7}\x{77E9}\x{77ED}\x{77EE}\x{77EF}\x{77F3}' + . '\x{77FC}\x{7802}\x{780C}\x{7812}\x{7814}\x{7815}\x{7820}\x{7825}\x{7826}' + . '\x{7827}\x{7832}\x{7834}\x{783A}\x{783F}\x{7845}\x{785D}\x{786B}\x{786C}' + . '\x{786F}\x{7872}\x{7874}\x{787C}\x{7881}\x{7886}\x{7887}\x{788C}\x{788D}' + . '\x{788E}\x{7891}\x{7893}\x{7895}\x{7897}\x{789A}\x{78A3}\x{78A7}\x{78A9}' + . '\x{78AA}\x{78AF}\x{78B5}\x{78BA}\x{78BC}\x{78BE}\x{78C1}\x{78C5}\x{78C6}' + . '\x{78CA}\x{78CB}\x{78D0}\x{78D1}\x{78D4}\x{78DA}\x{78E7}\x{78E8}\x{78EC}' + . '\x{78EF}\x{78F4}\x{78FD}\x{7901}\x{7907}\x{790E}\x{7911}\x{7912}\x{7919}' + . '\x{7926}\x{792A}\x{792B}\x{792C}\x{793A}\x{793C}\x{793E}\x{7940}\x{7941}' + . '\x{7947}\x{7948}\x{7949}\x{7950}\x{7953}\x{7955}\x{7956}\x{7957}\x{795A}' + . '\x{795D}\x{795E}\x{795F}\x{7960}\x{7962}\x{7965}\x{7968}\x{796D}\x{7977}' + . '\x{797A}\x{797F}\x{7980}\x{7981}\x{7984}\x{7985}\x{798A}\x{798D}\x{798E}' + . '\x{798F}\x{799D}\x{79A6}\x{79A7}\x{79AA}\x{79AE}\x{79B0}\x{79B3}\x{79B9}' + . '\x{79BA}\x{79BD}\x{79BE}\x{79BF}\x{79C0}\x{79C1}\x{79C9}\x{79CB}\x{79D1}' + . '\x{79D2}\x{79D5}\x{79D8}\x{79DF}\x{79E1}\x{79E3}\x{79E4}\x{79E6}\x{79E7}' + . '\x{79E9}\x{79EC}\x{79F0}\x{79FB}\x{7A00}\x{7A08}\x{7A0B}\x{7A0D}\x{7A0E}' + . '\x{7A14}\x{7A17}\x{7A18}\x{7A19}\x{7A1A}\x{7A1C}\x{7A1F}\x{7A20}\x{7A2E}' + . '\x{7A31}\x{7A32}\x{7A37}\x{7A3B}\x{7A3C}\x{7A3D}\x{7A3E}\x{7A3F}\x{7A40}' + . '\x{7A42}\x{7A43}\x{7A46}\x{7A49}\x{7A4D}\x{7A4E}\x{7A4F}\x{7A50}\x{7A57}' + . '\x{7A61}\x{7A62}\x{7A63}\x{7A69}\x{7A6B}\x{7A70}\x{7A74}\x{7A76}\x{7A79}' + . '\x{7A7A}\x{7A7D}\x{7A7F}\x{7A81}\x{7A83}\x{7A84}\x{7A88}\x{7A92}\x{7A93}' + . '\x{7A95}\x{7A96}\x{7A97}\x{7A98}\x{7A9F}\x{7AA9}\x{7AAA}\x{7AAE}\x{7AAF}' + . '\x{7AB0}\x{7AB6}\x{7ABA}\x{7ABF}\x{7AC3}\x{7AC4}\x{7AC5}\x{7AC7}\x{7AC8}' + . '\x{7ACA}\x{7ACB}\x{7ACD}\x{7ACF}\x{7AD2}\x{7AD3}\x{7AD5}\x{7AD9}\x{7ADA}' + . '\x{7ADC}\x{7ADD}\x{7ADF}\x{7AE0}\x{7AE1}\x{7AE2}\x{7AE3}\x{7AE5}\x{7AE6}' + . '\x{7AEA}\x{7AED}\x{7AEF}\x{7AF0}\x{7AF6}\x{7AF8}\x{7AF9}\x{7AFA}\x{7AFF}' + . '\x{7B02}\x{7B04}\x{7B06}\x{7B08}\x{7B0A}\x{7B0B}\x{7B0F}\x{7B11}\x{7B18}' + . '\x{7B19}\x{7B1B}\x{7B1E}\x{7B20}\x{7B25}\x{7B26}\x{7B28}\x{7B2C}\x{7B33}' + . '\x{7B35}\x{7B36}\x{7B39}\x{7B45}\x{7B46}\x{7B48}\x{7B49}\x{7B4B}\x{7B4C}' + . '\x{7B4D}\x{7B4F}\x{7B50}\x{7B51}\x{7B52}\x{7B54}\x{7B56}\x{7B5D}\x{7B65}' + . '\x{7B67}\x{7B6C}\x{7B6E}\x{7B70}\x{7B71}\x{7B74}\x{7B75}\x{7B7A}\x{7B86}' + . '\x{7B87}\x{7B8B}\x{7B8D}\x{7B8F}\x{7B92}\x{7B94}\x{7B95}\x{7B97}\x{7B98}' + . '\x{7B99}\x{7B9A}\x{7B9C}\x{7B9D}\x{7B9F}\x{7BA1}\x{7BAA}\x{7BAD}\x{7BB1}' + . '\x{7BB4}\x{7BB8}\x{7BC0}\x{7BC1}\x{7BC4}\x{7BC6}\x{7BC7}\x{7BC9}\x{7BCB}' + . '\x{7BCC}\x{7BCF}\x{7BDD}\x{7BE0}\x{7BE4}\x{7BE5}\x{7BE6}\x{7BE9}\x{7BED}' + . '\x{7BF3}\x{7BF6}\x{7BF7}\x{7C00}\x{7C07}\x{7C0D}\x{7C11}\x{7C12}\x{7C13}' + . '\x{7C14}\x{7C17}\x{7C1F}\x{7C21}\x{7C23}\x{7C27}\x{7C2A}\x{7C2B}\x{7C37}' + . '\x{7C38}\x{7C3D}\x{7C3E}\x{7C3F}\x{7C40}\x{7C43}\x{7C4C}\x{7C4D}\x{7C4F}' + . '\x{7C50}\x{7C54}\x{7C56}\x{7C58}\x{7C5F}\x{7C60}\x{7C64}\x{7C65}\x{7C6C}' + . '\x{7C73}\x{7C75}\x{7C7E}\x{7C81}\x{7C82}\x{7C83}\x{7C89}\x{7C8B}\x{7C8D}' + . '\x{7C90}\x{7C92}\x{7C95}\x{7C97}\x{7C98}\x{7C9B}\x{7C9F}\x{7CA1}\x{7CA2}' + . '\x{7CA4}\x{7CA5}\x{7CA7}\x{7CA8}\x{7CAB}\x{7CAD}\x{7CAE}\x{7CB1}\x{7CB2}' + . '\x{7CB3}\x{7CB9}\x{7CBD}\x{7CBE}\x{7CC0}\x{7CC2}\x{7CC5}\x{7CCA}\x{7CCE}' + . '\x{7CD2}\x{7CD6}\x{7CD8}\x{7CDC}\x{7CDE}\x{7CDF}\x{7CE0}\x{7CE2}\x{7CE7}' + . '\x{7CEF}\x{7CF2}\x{7CF4}\x{7CF6}\x{7CF8}\x{7CFA}\x{7CFB}\x{7CFE}\x{7D00}' + . '\x{7D02}\x{7D04}\x{7D05}\x{7D06}\x{7D0A}\x{7D0B}\x{7D0D}\x{7D10}\x{7D14}' + . '\x{7D15}\x{7D17}\x{7D18}\x{7D19}\x{7D1A}\x{7D1B}\x{7D1C}\x{7D20}\x{7D21}' + . '\x{7D22}\x{7D2B}\x{7D2C}\x{7D2E}\x{7D2F}\x{7D30}\x{7D32}\x{7D33}\x{7D35}' + . '\x{7D39}\x{7D3A}\x{7D3F}\x{7D42}\x{7D43}\x{7D44}\x{7D45}\x{7D46}\x{7D4B}' + . '\x{7D4C}\x{7D4E}\x{7D4F}\x{7D50}\x{7D56}\x{7D5B}\x{7D5E}\x{7D61}\x{7D62}' + . '\x{7D63}\x{7D66}\x{7D68}\x{7D6E}\x{7D71}\x{7D72}\x{7D73}\x{7D75}\x{7D76}' + . '\x{7D79}\x{7D7D}\x{7D89}\x{7D8F}\x{7D93}\x{7D99}\x{7D9A}\x{7D9B}\x{7D9C}' + . '\x{7D9F}\x{7DA2}\x{7DA3}\x{7DAB}\x{7DAC}\x{7DAD}\x{7DAE}\x{7DAF}\x{7DB0}' + . '\x{7DB1}\x{7DB2}\x{7DB4}\x{7DB5}\x{7DB8}\x{7DBA}\x{7DBB}\x{7DBD}\x{7DBE}' + . '\x{7DBF}\x{7DC7}\x{7DCA}\x{7DCB}\x{7DCF}\x{7DD1}\x{7DD2}\x{7DD5}\x{7DD8}' + . '\x{7DDA}\x{7DDC}\x{7DDD}\x{7DDE}\x{7DE0}\x{7DE1}\x{7DE4}\x{7DE8}\x{7DE9}' + . '\x{7DEC}\x{7DEF}\x{7DF2}\x{7DF4}\x{7DFB}\x{7E01}\x{7E04}\x{7E05}\x{7E09}' + . '\x{7E0A}\x{7E0B}\x{7E12}\x{7E1B}\x{7E1E}\x{7E1F}\x{7E21}\x{7E22}\x{7E23}' + . '\x{7E26}\x{7E2B}\x{7E2E}\x{7E31}\x{7E32}\x{7E35}\x{7E37}\x{7E39}\x{7E3A}' + . '\x{7E3B}\x{7E3D}\x{7E3E}\x{7E41}\x{7E43}\x{7E46}\x{7E4A}\x{7E4B}\x{7E4D}' + . '\x{7E54}\x{7E55}\x{7E56}\x{7E59}\x{7E5A}\x{7E5D}\x{7E5E}\x{7E66}\x{7E67}' + . '\x{7E69}\x{7E6A}\x{7E6D}\x{7E70}\x{7E79}\x{7E7B}\x{7E7C}\x{7E7D}\x{7E7F}' + . '\x{7E82}\x{7E83}\x{7E88}\x{7E89}\x{7E8C}\x{7E8E}\x{7E8F}\x{7E90}\x{7E92}' + . '\x{7E93}\x{7E94}\x{7E96}\x{7E9B}\x{7E9C}\x{7F36}\x{7F38}\x{7F3A}\x{7F45}' + . '\x{7F4C}\x{7F4D}\x{7F4E}\x{7F50}\x{7F51}\x{7F54}\x{7F55}\x{7F58}\x{7F5F}' + . '\x{7F60}\x{7F67}\x{7F68}\x{7F69}\x{7F6A}\x{7F6B}\x{7F6E}\x{7F70}\x{7F72}' + . '\x{7F75}\x{7F77}\x{7F78}\x{7F79}\x{7F82}\x{7F83}\x{7F85}\x{7F86}\x{7F87}' + . '\x{7F88}\x{7F8A}\x{7F8C}\x{7F8E}\x{7F94}\x{7F9A}\x{7F9D}\x{7F9E}\x{7FA3}' + . '\x{7FA4}\x{7FA8}\x{7FA9}\x{7FAE}\x{7FAF}\x{7FB2}\x{7FB6}\x{7FB8}\x{7FB9}' + . '\x{7FBD}\x{7FC1}\x{7FC5}\x{7FC6}\x{7FCA}\x{7FCC}\x{7FD2}\x{7FD4}\x{7FD5}' + . '\x{7FE0}\x{7FE1}\x{7FE6}\x{7FE9}\x{7FEB}\x{7FF0}\x{7FF3}\x{7FF9}\x{7FFB}' + . '\x{7FFC}\x{8000}\x{8001}\x{8003}\x{8004}\x{8005}\x{8006}\x{800B}\x{800C}' + . '\x{8010}\x{8012}\x{8015}\x{8017}\x{8018}\x{8019}\x{801C}\x{8021}\x{8028}' + . '\x{8033}\x{8036}\x{803B}\x{803D}\x{803F}\x{8046}\x{804A}\x{8052}\x{8056}' + . '\x{8058}\x{805A}\x{805E}\x{805F}\x{8061}\x{8062}\x{8068}\x{806F}\x{8070}' + . '\x{8072}\x{8073}\x{8074}\x{8076}\x{8077}\x{8079}\x{807D}\x{807E}\x{807F}' + . '\x{8084}\x{8085}\x{8086}\x{8087}\x{8089}\x{808B}\x{808C}\x{8093}\x{8096}' + . '\x{8098}\x{809A}\x{809B}\x{809D}\x{80A1}\x{80A2}\x{80A5}\x{80A9}\x{80AA}' + . '\x{80AC}\x{80AD}\x{80AF}\x{80B1}\x{80B2}\x{80B4}\x{80BA}\x{80C3}\x{80C4}' + . '\x{80C6}\x{80CC}\x{80CE}\x{80D6}\x{80D9}\x{80DA}\x{80DB}\x{80DD}\x{80DE}' + . '\x{80E1}\x{80E4}\x{80E5}\x{80EF}\x{80F1}\x{80F4}\x{80F8}\x{80FC}\x{80FD}' + . '\x{8102}\x{8105}\x{8106}\x{8107}\x{8108}\x{8109}\x{810A}\x{811A}\x{811B}' + . '\x{8123}\x{8129}\x{812F}\x{8131}\x{8133}\x{8139}\x{813E}\x{8146}\x{814B}' + . '\x{814E}\x{8150}\x{8151}\x{8153}\x{8154}\x{8155}\x{815F}\x{8165}\x{8166}' + . '\x{816B}\x{816E}\x{8170}\x{8171}\x{8174}\x{8178}\x{8179}\x{817A}\x{817F}' + . '\x{8180}\x{8182}\x{8183}\x{8188}\x{818A}\x{818F}\x{8193}\x{8195}\x{819A}' + . '\x{819C}\x{819D}\x{81A0}\x{81A3}\x{81A4}\x{81A8}\x{81A9}\x{81B0}\x{81B3}' + . '\x{81B5}\x{81B8}\x{81BA}\x{81BD}\x{81BE}\x{81BF}\x{81C0}\x{81C2}\x{81C6}' + . '\x{81C8}\x{81C9}\x{81CD}\x{81D1}\x{81D3}\x{81D8}\x{81D9}\x{81DA}\x{81DF}' + . '\x{81E0}\x{81E3}\x{81E5}\x{81E7}\x{81E8}\x{81EA}\x{81ED}\x{81F3}\x{81F4}' + . '\x{81FA}\x{81FB}\x{81FC}\x{81FE}\x{8201}\x{8202}\x{8205}\x{8207}\x{8208}' + . '\x{8209}\x{820A}\x{820C}\x{820D}\x{820E}\x{8210}\x{8212}\x{8216}\x{8217}' + . '\x{8218}\x{821B}\x{821C}\x{821E}\x{821F}\x{8229}\x{822A}\x{822B}\x{822C}' + . '\x{822E}\x{8233}\x{8235}\x{8236}\x{8237}\x{8238}\x{8239}\x{8240}\x{8247}' + . '\x{8258}\x{8259}\x{825A}\x{825D}\x{825F}\x{8262}\x{8264}\x{8266}\x{8268}' + . '\x{826A}\x{826B}\x{826E}\x{826F}\x{8271}\x{8272}\x{8276}\x{8277}\x{8278}' + . '\x{827E}\x{828B}\x{828D}\x{8292}\x{8299}\x{829D}\x{829F}\x{82A5}\x{82A6}' + . '\x{82AB}\x{82AC}\x{82AD}\x{82AF}\x{82B1}\x{82B3}\x{82B8}\x{82B9}\x{82BB}' + . '\x{82BD}\x{82C5}\x{82D1}\x{82D2}\x{82D3}\x{82D4}\x{82D7}\x{82D9}\x{82DB}' + . '\x{82DC}\x{82DE}\x{82DF}\x{82E1}\x{82E3}\x{82E5}\x{82E6}\x{82E7}\x{82EB}' + . '\x{82F1}\x{82F3}\x{82F4}\x{82F9}\x{82FA}\x{82FB}\x{8302}\x{8303}\x{8304}' + . '\x{8305}\x{8306}\x{8309}\x{830E}\x{8316}\x{8317}\x{8318}\x{831C}\x{8323}' + . '\x{8328}\x{832B}\x{832F}\x{8331}\x{8332}\x{8334}\x{8335}\x{8336}\x{8338}' + . '\x{8339}\x{8340}\x{8345}\x{8349}\x{834A}\x{834F}\x{8350}\x{8352}\x{8358}' + . '\x{8373}\x{8375}\x{8377}\x{837B}\x{837C}\x{8385}\x{8387}\x{8389}\x{838A}' + . '\x{838E}\x{8393}\x{8396}\x{839A}\x{839E}\x{839F}\x{83A0}\x{83A2}\x{83A8}' + . '\x{83AA}\x{83AB}\x{83B1}\x{83B5}\x{83BD}\x{83C1}\x{83C5}\x{83CA}\x{83CC}' + . '\x{83CE}\x{83D3}\x{83D6}\x{83D8}\x{83DC}\x{83DF}\x{83E0}\x{83E9}\x{83EB}' + . '\x{83EF}\x{83F0}\x{83F1}\x{83F2}\x{83F4}\x{83F7}\x{83FB}\x{83FD}\x{8403}' + . '\x{8404}\x{8407}\x{840B}\x{840C}\x{840D}\x{840E}\x{8413}\x{8420}\x{8422}' + . '\x{8429}\x{842A}\x{842C}\x{8431}\x{8435}\x{8438}\x{843C}\x{843D}\x{8446}' + . '\x{8449}\x{844E}\x{8457}\x{845B}\x{8461}\x{8462}\x{8463}\x{8466}\x{8469}' + . '\x{846B}\x{846C}\x{846D}\x{846E}\x{846F}\x{8471}\x{8475}\x{8477}\x{8479}' + . '\x{847A}\x{8482}\x{8484}\x{848B}\x{8490}\x{8494}\x{8499}\x{849C}\x{849F}' + . '\x{84A1}\x{84AD}\x{84B2}\x{84B8}\x{84B9}\x{84BB}\x{84BC}\x{84BF}\x{84C1}' + . '\x{84C4}\x{84C6}\x{84C9}\x{84CA}\x{84CB}\x{84CD}\x{84D0}\x{84D1}\x{84D6}' + . '\x{84D9}\x{84DA}\x{84EC}\x{84EE}\x{84F4}\x{84FC}\x{84FF}\x{8500}\x{8506}' + . '\x{8511}\x{8513}\x{8514}\x{8515}\x{8517}\x{8518}\x{851A}\x{851F}\x{8521}' + . '\x{8526}\x{852C}\x{852D}\x{8535}\x{853D}\x{8540}\x{8541}\x{8543}\x{8548}' + . '\x{8549}\x{854A}\x{854B}\x{854E}\x{8555}\x{8557}\x{8558}\x{855A}\x{8563}' + . '\x{8568}\x{8569}\x{856A}\x{856D}\x{8577}\x{857E}\x{8580}\x{8584}\x{8587}' + . '\x{8588}\x{858A}\x{8590}\x{8591}\x{8594}\x{8597}\x{8599}\x{859B}\x{859C}' + . '\x{85A4}\x{85A6}\x{85A8}\x{85A9}\x{85AA}\x{85AB}\x{85AC}\x{85AE}\x{85AF}' + . '\x{85B9}\x{85BA}\x{85C1}\x{85C9}\x{85CD}\x{85CF}\x{85D0}\x{85D5}\x{85DC}' + . '\x{85DD}\x{85E4}\x{85E5}\x{85E9}\x{85EA}\x{85F7}\x{85F9}\x{85FA}\x{85FB}' + . '\x{85FE}\x{8602}\x{8606}\x{8607}\x{860A}\x{860B}\x{8613}\x{8616}\x{8617}' + . '\x{861A}\x{8622}\x{862D}\x{862F}\x{8630}\x{863F}\x{864D}\x{864E}\x{8650}' + . '\x{8654}\x{8655}\x{865A}\x{865C}\x{865E}\x{865F}\x{8667}\x{866B}\x{8671}' + . '\x{8679}\x{867B}\x{868A}\x{868B}\x{868C}\x{8693}\x{8695}\x{86A3}\x{86A4}' + . '\x{86A9}\x{86AA}\x{86AB}\x{86AF}\x{86B0}\x{86B6}\x{86C4}\x{86C6}\x{86C7}' + . '\x{86C9}\x{86CB}\x{86CD}\x{86CE}\x{86D4}\x{86D9}\x{86DB}\x{86DE}\x{86DF}' + . '\x{86E4}\x{86E9}\x{86EC}\x{86ED}\x{86EE}\x{86EF}\x{86F8}\x{86F9}\x{86FB}' + . '\x{86FE}\x{8700}\x{8702}\x{8703}\x{8706}\x{8708}\x{8709}\x{870A}\x{870D}' + . '\x{8711}\x{8712}\x{8718}\x{871A}\x{871C}\x{8725}\x{8729}\x{8734}\x{8737}' + . '\x{873B}\x{873F}\x{8749}\x{874B}\x{874C}\x{874E}\x{8753}\x{8755}\x{8757}' + . '\x{8759}\x{875F}\x{8760}\x{8763}\x{8766}\x{8768}\x{876A}\x{876E}\x{8774}' + . '\x{8776}\x{8778}\x{877F}\x{8782}\x{878D}\x{879F}\x{87A2}\x{87AB}\x{87AF}' + . '\x{87B3}\x{87BA}\x{87BB}\x{87BD}\x{87C0}\x{87C4}\x{87C6}\x{87C7}\x{87CB}' + . '\x{87D0}\x{87D2}\x{87E0}\x{87EF}\x{87F2}\x{87F6}\x{87F7}\x{87F9}\x{87FB}' + . '\x{87FE}\x{8805}\x{880D}\x{880E}\x{880F}\x{8811}\x{8815}\x{8816}\x{8821}' + . '\x{8822}\x{8823}\x{8827}\x{8831}\x{8836}\x{8839}\x{883B}\x{8840}\x{8842}' + . '\x{8844}\x{8846}\x{884C}\x{884D}\x{8852}\x{8853}\x{8857}\x{8859}\x{885B}' + . '\x{885D}\x{885E}\x{8861}\x{8862}\x{8863}\x{8868}\x{886B}\x{8870}\x{8872}' + . '\x{8875}\x{8877}\x{887D}\x{887E}\x{887F}\x{8881}\x{8882}\x{8888}\x{888B}' + . '\x{888D}\x{8892}\x{8896}\x{8897}\x{8899}\x{889E}\x{88A2}\x{88A4}\x{88AB}' + . '\x{88AE}\x{88B0}\x{88B1}\x{88B4}\x{88B5}\x{88B7}\x{88BF}\x{88C1}\x{88C2}' + . '\x{88C3}\x{88C4}\x{88C5}\x{88CF}\x{88D4}\x{88D5}\x{88D8}\x{88D9}\x{88DC}' + . '\x{88DD}\x{88DF}\x{88E1}\x{88E8}\x{88F2}\x{88F3}\x{88F4}\x{88F8}\x{88F9}' + . '\x{88FC}\x{88FD}\x{88FE}\x{8902}\x{8904}\x{8907}\x{890A}\x{890C}\x{8910}' + . '\x{8912}\x{8913}\x{891D}\x{891E}\x{8925}\x{892A}\x{892B}\x{8936}\x{8938}' + . '\x{893B}\x{8941}\x{8943}\x{8944}\x{894C}\x{894D}\x{8956}\x{895E}\x{895F}' + . '\x{8960}\x{8964}\x{8966}\x{896A}\x{896D}\x{896F}\x{8972}\x{8974}\x{8977}' + . '\x{897E}\x{897F}\x{8981}\x{8983}\x{8986}\x{8987}\x{8988}\x{898A}\x{898B}' + . '\x{898F}\x{8993}\x{8996}\x{8997}\x{8998}\x{899A}\x{89A1}\x{89A6}\x{89A7}' + . '\x{89A9}\x{89AA}\x{89AC}\x{89AF}\x{89B2}\x{89B3}\x{89BA}\x{89BD}\x{89BF}' + . '\x{89C0}\x{89D2}\x{89DA}\x{89DC}\x{89DD}\x{89E3}\x{89E6}\x{89E7}\x{89F4}' + . '\x{89F8}\x{8A00}\x{8A02}\x{8A03}\x{8A08}\x{8A0A}\x{8A0C}\x{8A0E}\x{8A10}' + . '\x{8A13}\x{8A16}\x{8A17}\x{8A18}\x{8A1B}\x{8A1D}\x{8A1F}\x{8A23}\x{8A25}' + . '\x{8A2A}\x{8A2D}\x{8A31}\x{8A33}\x{8A34}\x{8A36}\x{8A3A}\x{8A3B}\x{8A3C}' + . '\x{8A41}\x{8A46}\x{8A48}\x{8A50}\x{8A51}\x{8A52}\x{8A54}\x{8A55}\x{8A5B}' + . '\x{8A5E}\x{8A60}\x{8A62}\x{8A63}\x{8A66}\x{8A69}\x{8A6B}\x{8A6C}\x{8A6D}' + . '\x{8A6E}\x{8A70}\x{8A71}\x{8A72}\x{8A73}\x{8A7C}\x{8A82}\x{8A84}\x{8A85}' + . '\x{8A87}\x{8A89}\x{8A8C}\x{8A8D}\x{8A91}\x{8A93}\x{8A95}\x{8A98}\x{8A9A}' + . '\x{8A9E}\x{8AA0}\x{8AA1}\x{8AA3}\x{8AA4}\x{8AA5}\x{8AA6}\x{8AA8}\x{8AAC}' + . '\x{8AAD}\x{8AB0}\x{8AB2}\x{8AB9}\x{8ABC}\x{8ABF}\x{8AC2}\x{8AC4}\x{8AC7}' + . '\x{8ACB}\x{8ACC}\x{8ACD}\x{8ACF}\x{8AD2}\x{8AD6}\x{8ADA}\x{8ADB}\x{8ADC}' + . '\x{8ADE}\x{8AE0}\x{8AE1}\x{8AE2}\x{8AE4}\x{8AE6}\x{8AE7}\x{8AEB}\x{8AED}' + . '\x{8AEE}\x{8AF1}\x{8AF3}\x{8AF7}\x{8AF8}\x{8AFA}\x{8AFE}\x{8B00}\x{8B01}' + . '\x{8B02}\x{8B04}\x{8B07}\x{8B0C}\x{8B0E}\x{8B10}\x{8B14}\x{8B16}\x{8B17}' + . '\x{8B19}\x{8B1A}\x{8B1B}\x{8B1D}\x{8B20}\x{8B21}\x{8B26}\x{8B28}\x{8B2B}' + . '\x{8B2C}\x{8B33}\x{8B39}\x{8B3E}\x{8B41}\x{8B49}\x{8B4C}\x{8B4E}\x{8B4F}' + . '\x{8B56}\x{8B58}\x{8B5A}\x{8B5B}\x{8B5C}\x{8B5F}\x{8B66}\x{8B6B}\x{8B6C}' + . '\x{8B6F}\x{8B70}\x{8B71}\x{8B72}\x{8B74}\x{8B77}\x{8B7D}\x{8B80}\x{8B83}' + . '\x{8B8A}\x{8B8C}\x{8B8E}\x{8B90}\x{8B92}\x{8B93}\x{8B96}\x{8B99}\x{8B9A}' + . '\x{8C37}\x{8C3A}\x{8C3F}\x{8C41}\x{8C46}\x{8C48}\x{8C4A}\x{8C4C}\x{8C4E}' + . '\x{8C50}\x{8C55}\x{8C5A}\x{8C61}\x{8C62}\x{8C6A}\x{8C6B}\x{8C6C}\x{8C78}' + . '\x{8C79}\x{8C7A}\x{8C7C}\x{8C82}\x{8C85}\x{8C89}\x{8C8A}\x{8C8C}\x{8C8D}' + . '\x{8C8E}\x{8C94}\x{8C98}\x{8C9D}\x{8C9E}\x{8CA0}\x{8CA1}\x{8CA2}\x{8CA7}' + . '\x{8CA8}\x{8CA9}\x{8CAA}\x{8CAB}\x{8CAC}\x{8CAD}\x{8CAE}\x{8CAF}\x{8CB0}' + . '\x{8CB2}\x{8CB3}\x{8CB4}\x{8CB6}\x{8CB7}\x{8CB8}\x{8CBB}\x{8CBC}\x{8CBD}' + . '\x{8CBF}\x{8CC0}\x{8CC1}\x{8CC2}\x{8CC3}\x{8CC4}\x{8CC7}\x{8CC8}\x{8CCA}' + . '\x{8CCD}\x{8CCE}\x{8CD1}\x{8CD3}\x{8CDA}\x{8CDB}\x{8CDC}\x{8CDE}\x{8CE0}' + . '\x{8CE2}\x{8CE3}\x{8CE4}\x{8CE6}\x{8CEA}\x{8CED}\x{8CFA}\x{8CFB}\x{8CFC}' + . '\x{8CFD}\x{8D04}\x{8D05}\x{8D07}\x{8D08}\x{8D0A}\x{8D0B}\x{8D0D}\x{8D0F}' + . '\x{8D10}\x{8D13}\x{8D14}\x{8D16}\x{8D64}\x{8D66}\x{8D67}\x{8D6B}\x{8D6D}' + . '\x{8D70}\x{8D71}\x{8D73}\x{8D74}\x{8D77}\x{8D81}\x{8D85}\x{8D8A}\x{8D99}' + . '\x{8DA3}\x{8DA8}\x{8DB3}\x{8DBA}\x{8DBE}\x{8DC2}\x{8DCB}\x{8DCC}\x{8DCF}' + . '\x{8DD6}\x{8DDA}\x{8DDB}\x{8DDD}\x{8DDF}\x{8DE1}\x{8DE3}\x{8DE8}\x{8DEA}' + . '\x{8DEB}\x{8DEF}\x{8DF3}\x{8DF5}\x{8DFC}\x{8DFF}\x{8E08}\x{8E09}\x{8E0A}' + . '\x{8E0F}\x{8E10}\x{8E1D}\x{8E1E}\x{8E1F}\x{8E2A}\x{8E30}\x{8E34}\x{8E35}' + . '\x{8E42}\x{8E44}\x{8E47}\x{8E48}\x{8E49}\x{8E4A}\x{8E4C}\x{8E50}\x{8E55}' + . '\x{8E59}\x{8E5F}\x{8E60}\x{8E63}\x{8E64}\x{8E72}\x{8E74}\x{8E76}\x{8E7C}' + . '\x{8E81}\x{8E84}\x{8E85}\x{8E87}\x{8E8A}\x{8E8B}\x{8E8D}\x{8E91}\x{8E93}' + . '\x{8E94}\x{8E99}\x{8EA1}\x{8EAA}\x{8EAB}\x{8EAC}\x{8EAF}\x{8EB0}\x{8EB1}' + . '\x{8EBE}\x{8EC5}\x{8EC6}\x{8EC8}\x{8ECA}\x{8ECB}\x{8ECC}\x{8ECD}\x{8ED2}' + . '\x{8EDB}\x{8EDF}\x{8EE2}\x{8EE3}\x{8EEB}\x{8EF8}\x{8EFB}\x{8EFC}\x{8EFD}' + . '\x{8EFE}\x{8F03}\x{8F05}\x{8F09}\x{8F0A}\x{8F0C}\x{8F12}\x{8F13}\x{8F14}' + . '\x{8F15}\x{8F19}\x{8F1B}\x{8F1C}\x{8F1D}\x{8F1F}\x{8F26}\x{8F29}\x{8F2A}' + . '\x{8F2F}\x{8F33}\x{8F38}\x{8F39}\x{8F3B}\x{8F3E}\x{8F3F}\x{8F42}\x{8F44}' + . '\x{8F45}\x{8F46}\x{8F49}\x{8F4C}\x{8F4D}\x{8F4E}\x{8F57}\x{8F5C}\x{8F5F}' + . '\x{8F61}\x{8F62}\x{8F63}\x{8F64}\x{8F9B}\x{8F9C}\x{8F9E}\x{8F9F}\x{8FA3}' + . '\x{8FA7}\x{8FA8}\x{8FAD}\x{8FAE}\x{8FAF}\x{8FB0}\x{8FB1}\x{8FB2}\x{8FB7}' + . '\x{8FBA}\x{8FBB}\x{8FBC}\x{8FBF}\x{8FC2}\x{8FC4}\x{8FC5}\x{8FCE}\x{8FD1}' + . '\x{8FD4}\x{8FDA}\x{8FE2}\x{8FE5}\x{8FE6}\x{8FE9}\x{8FEA}\x{8FEB}\x{8FED}' + . '\x{8FEF}\x{8FF0}\x{8FF4}\x{8FF7}\x{8FF8}\x{8FF9}\x{8FFA}\x{8FFD}\x{9000}' + . '\x{9001}\x{9003}\x{9005}\x{9006}\x{900B}\x{900D}\x{900E}\x{900F}\x{9010}' + . '\x{9011}\x{9013}\x{9014}\x{9015}\x{9016}\x{9017}\x{9019}\x{901A}\x{901D}' + . '\x{901E}\x{901F}\x{9020}\x{9021}\x{9022}\x{9023}\x{9027}\x{902E}\x{9031}' + . '\x{9032}\x{9035}\x{9036}\x{9038}\x{9039}\x{903C}\x{903E}\x{9041}\x{9042}' + . '\x{9045}\x{9047}\x{9049}\x{904A}\x{904B}\x{904D}\x{904E}\x{904F}\x{9050}' + . '\x{9051}\x{9052}\x{9053}\x{9054}\x{9055}\x{9056}\x{9058}\x{9059}\x{905C}' + . '\x{905E}\x{9060}\x{9061}\x{9063}\x{9065}\x{9068}\x{9069}\x{906D}\x{906E}' + . '\x{906F}\x{9072}\x{9075}\x{9076}\x{9077}\x{9078}\x{907A}\x{907C}\x{907D}' + . '\x{907F}\x{9080}\x{9081}\x{9082}\x{9083}\x{9084}\x{9087}\x{9089}\x{908A}' + . '\x{908F}\x{9091}\x{90A3}\x{90A6}\x{90A8}\x{90AA}\x{90AF}\x{90B1}\x{90B5}' + . '\x{90B8}\x{90C1}\x{90CA}\x{90CE}\x{90DB}\x{90E1}\x{90E2}\x{90E4}\x{90E8}' + . '\x{90ED}\x{90F5}\x{90F7}\x{90FD}\x{9102}\x{9112}\x{9119}\x{912D}\x{9130}' + . '\x{9132}\x{9149}\x{914A}\x{914B}\x{914C}\x{914D}\x{914E}\x{9152}\x{9154}' + . '\x{9156}\x{9158}\x{9162}\x{9163}\x{9165}\x{9169}\x{916A}\x{916C}\x{9172}' + . '\x{9173}\x{9175}\x{9177}\x{9178}\x{9182}\x{9187}\x{9189}\x{918B}\x{918D}' + . '\x{9190}\x{9192}\x{9197}\x{919C}\x{91A2}\x{91A4}\x{91AA}\x{91AB}\x{91AF}' + . '\x{91B4}\x{91B5}\x{91B8}\x{91BA}\x{91C0}\x{91C1}\x{91C6}\x{91C7}\x{91C8}' + . '\x{91C9}\x{91CB}\x{91CC}\x{91CD}\x{91CE}\x{91CF}\x{91D0}\x{91D1}\x{91D6}' + . '\x{91D8}\x{91DB}\x{91DC}\x{91DD}\x{91DF}\x{91E1}\x{91E3}\x{91E6}\x{91E7}' + . '\x{91F5}\x{91F6}\x{91FC}\x{91FF}\x{920D}\x{920E}\x{9211}\x{9214}\x{9215}' + . '\x{921E}\x{9229}\x{922C}\x{9234}\x{9237}\x{923F}\x{9244}\x{9245}\x{9248}' + . '\x{9249}\x{924B}\x{9250}\x{9257}\x{925A}\x{925B}\x{925E}\x{9262}\x{9264}' + . '\x{9266}\x{9271}\x{927E}\x{9280}\x{9283}\x{9285}\x{9291}\x{9293}\x{9295}' + . '\x{9296}\x{9298}\x{929A}\x{929B}\x{929C}\x{92AD}\x{92B7}\x{92B9}\x{92CF}' + . '\x{92D2}\x{92E4}\x{92E9}\x{92EA}\x{92ED}\x{92F2}\x{92F3}\x{92F8}\x{92FA}' + . '\x{92FC}\x{9306}\x{930F}\x{9310}\x{9318}\x{9319}\x{931A}\x{9320}\x{9322}' + . '\x{9323}\x{9326}\x{9328}\x{932B}\x{932C}\x{932E}\x{932F}\x{9332}\x{9335}' + . '\x{933A}\x{933B}\x{9344}\x{934B}\x{934D}\x{9354}\x{9356}\x{935B}\x{935C}' + . '\x{9360}\x{936C}\x{936E}\x{9375}\x{937C}\x{937E}\x{938C}\x{9394}\x{9396}' + . '\x{9397}\x{939A}\x{93A7}\x{93AC}\x{93AD}\x{93AE}\x{93B0}\x{93B9}\x{93C3}' + . '\x{93C8}\x{93D0}\x{93D1}\x{93D6}\x{93D7}\x{93D8}\x{93DD}\x{93E1}\x{93E4}' + . '\x{93E5}\x{93E8}\x{9403}\x{9407}\x{9410}\x{9413}\x{9414}\x{9418}\x{9419}' + . '\x{941A}\x{9421}\x{942B}\x{9435}\x{9436}\x{9438}\x{943A}\x{9441}\x{9444}' + . '\x{9451}\x{9452}\x{9453}\x{945A}\x{945B}\x{945E}\x{9460}\x{9462}\x{946A}' + . '\x{9470}\x{9475}\x{9477}\x{947C}\x{947D}\x{947E}\x{947F}\x{9481}\x{9577}' + . '\x{9580}\x{9582}\x{9583}\x{9587}\x{9589}\x{958A}\x{958B}\x{958F}\x{9591}' + . '\x{9593}\x{9594}\x{9596}\x{9598}\x{9599}\x{95A0}\x{95A2}\x{95A3}\x{95A4}' + . '\x{95A5}\x{95A7}\x{95A8}\x{95AD}\x{95B2}\x{95B9}\x{95BB}\x{95BC}\x{95BE}' + . '\x{95C3}\x{95C7}\x{95CA}\x{95CC}\x{95CD}\x{95D4}\x{95D5}\x{95D6}\x{95D8}' + . '\x{95DC}\x{95E1}\x{95E2}\x{95E5}\x{961C}\x{9621}\x{9628}\x{962A}\x{962E}' + . '\x{962F}\x{9632}\x{963B}\x{963F}\x{9640}\x{9642}\x{9644}\x{964B}\x{964C}' + . '\x{964D}\x{964F}\x{9650}\x{965B}\x{965C}\x{965D}\x{965E}\x{965F}\x{9662}' + . '\x{9663}\x{9664}\x{9665}\x{9666}\x{966A}\x{966C}\x{9670}\x{9672}\x{9673}' + . '\x{9675}\x{9676}\x{9677}\x{9678}\x{967A}\x{967D}\x{9685}\x{9686}\x{9688}' + . '\x{968A}\x{968B}\x{968D}\x{968E}\x{968F}\x{9694}\x{9695}\x{9697}\x{9698}' + . '\x{9699}\x{969B}\x{969C}\x{96A0}\x{96A3}\x{96A7}\x{96A8}\x{96AA}\x{96B0}' + . '\x{96B1}\x{96B2}\x{96B4}\x{96B6}\x{96B7}\x{96B8}\x{96B9}\x{96BB}\x{96BC}' + . '\x{96C0}\x{96C1}\x{96C4}\x{96C5}\x{96C6}\x{96C7}\x{96C9}\x{96CB}\x{96CC}' + . '\x{96CD}\x{96CE}\x{96D1}\x{96D5}\x{96D6}\x{96D9}\x{96DB}\x{96DC}\x{96E2}' + . '\x{96E3}\x{96E8}\x{96EA}\x{96EB}\x{96F0}\x{96F2}\x{96F6}\x{96F7}\x{96F9}' + . '\x{96FB}\x{9700}\x{9704}\x{9706}\x{9707}\x{9708}\x{970A}\x{970D}\x{970E}' + . '\x{970F}\x{9711}\x{9713}\x{9716}\x{9719}\x{971C}\x{971E}\x{9724}\x{9727}' + . '\x{972A}\x{9730}\x{9732}\x{9738}\x{9739}\x{973D}\x{973E}\x{9742}\x{9744}' + . '\x{9746}\x{9748}\x{9749}\x{9752}\x{9756}\x{9759}\x{975C}\x{975E}\x{9760}' + . '\x{9761}\x{9762}\x{9764}\x{9766}\x{9768}\x{9769}\x{976B}\x{976D}\x{9771}' + . '\x{9774}\x{9779}\x{977A}\x{977C}\x{9781}\x{9784}\x{9785}\x{9786}\x{978B}' + . '\x{978D}\x{978F}\x{9790}\x{9798}\x{979C}\x{97A0}\x{97A3}\x{97A6}\x{97A8}' + . '\x{97AB}\x{97AD}\x{97B3}\x{97B4}\x{97C3}\x{97C6}\x{97C8}\x{97CB}\x{97D3}' + . '\x{97DC}\x{97ED}\x{97EE}\x{97F2}\x{97F3}\x{97F5}\x{97F6}\x{97FB}\x{97FF}' + . '\x{9801}\x{9802}\x{9803}\x{9805}\x{9806}\x{9808}\x{980C}\x{980F}\x{9810}' + . '\x{9811}\x{9812}\x{9813}\x{9817}\x{9818}\x{981A}\x{9821}\x{9824}\x{982C}' + . '\x{982D}\x{9834}\x{9837}\x{9838}\x{983B}\x{983C}\x{983D}\x{9846}\x{984B}' + . '\x{984C}\x{984D}\x{984E}\x{984F}\x{9854}\x{9855}\x{9858}\x{985B}\x{985E}' + . '\x{9867}\x{986B}\x{986F}\x{9870}\x{9871}\x{9873}\x{9874}\x{98A8}\x{98AA}' + . '\x{98AF}\x{98B1}\x{98B6}\x{98C3}\x{98C4}\x{98C6}\x{98DB}\x{98DC}\x{98DF}' + . '\x{98E2}\x{98E9}\x{98EB}\x{98ED}\x{98EE}\x{98EF}\x{98F2}\x{98F4}\x{98FC}' + . '\x{98FD}\x{98FE}\x{9903}\x{9905}\x{9909}\x{990A}\x{990C}\x{9910}\x{9912}' + . '\x{9913}\x{9914}\x{9918}\x{991D}\x{991E}\x{9920}\x{9921}\x{9924}\x{9928}' + . '\x{992C}\x{992E}\x{993D}\x{993E}\x{9942}\x{9945}\x{9949}\x{994B}\x{994C}' + . '\x{9950}\x{9951}\x{9952}\x{9955}\x{9957}\x{9996}\x{9997}\x{9998}\x{9999}' + . '\x{99A5}\x{99A8}\x{99AC}\x{99AD}\x{99AE}\x{99B3}\x{99B4}\x{99BC}\x{99C1}' + . '\x{99C4}\x{99C5}\x{99C6}\x{99C8}\x{99D0}\x{99D1}\x{99D2}\x{99D5}\x{99D8}' + . '\x{99DB}\x{99DD}\x{99DF}\x{99E2}\x{99ED}\x{99EE}\x{99F1}\x{99F2}\x{99F8}' + . '\x{99FB}\x{99FF}\x{9A01}\x{9A05}\x{9A0E}\x{9A0F}\x{9A12}\x{9A13}\x{9A19}' + . '\x{9A28}\x{9A2B}\x{9A30}\x{9A37}\x{9A3E}\x{9A40}\x{9A42}\x{9A43}\x{9A45}' + . '\x{9A4D}\x{9A55}\x{9A57}\x{9A5A}\x{9A5B}\x{9A5F}\x{9A62}\x{9A64}\x{9A65}' + . '\x{9A69}\x{9A6A}\x{9A6B}\x{9AA8}\x{9AAD}\x{9AB0}\x{9AB8}\x{9ABC}\x{9AC0}' + . '\x{9AC4}\x{9ACF}\x{9AD1}\x{9AD3}\x{9AD4}\x{9AD8}\x{9ADE}\x{9ADF}\x{9AE2}' + . '\x{9AE3}\x{9AE6}\x{9AEA}\x{9AEB}\x{9AED}\x{9AEE}\x{9AEF}\x{9AF1}\x{9AF4}' + . '\x{9AF7}\x{9AFB}\x{9B06}\x{9B18}\x{9B1A}\x{9B1F}\x{9B22}\x{9B23}\x{9B25}' + . '\x{9B27}\x{9B28}\x{9B29}\x{9B2A}\x{9B2E}\x{9B2F}\x{9B31}\x{9B32}\x{9B3B}' + . '\x{9B3C}\x{9B41}\x{9B42}\x{9B43}\x{9B44}\x{9B45}\x{9B4D}\x{9B4E}\x{9B4F}' + . '\x{9B51}\x{9B54}\x{9B58}\x{9B5A}\x{9B6F}\x{9B74}\x{9B83}\x{9B8E}\x{9B91}' + . '\x{9B92}\x{9B93}\x{9B96}\x{9B97}\x{9B9F}\x{9BA0}\x{9BA8}\x{9BAA}\x{9BAB}' + . '\x{9BAD}\x{9BAE}\x{9BB4}\x{9BB9}\x{9BC0}\x{9BC6}\x{9BC9}\x{9BCA}\x{9BCF}' + . '\x{9BD1}\x{9BD2}\x{9BD4}\x{9BD6}\x{9BDB}\x{9BE1}\x{9BE2}\x{9BE3}\x{9BE4}' + . '\x{9BE8}\x{9BF0}\x{9BF1}\x{9BF2}\x{9BF5}\x{9C04}\x{9C06}\x{9C08}\x{9C09}' + . '\x{9C0A}\x{9C0C}\x{9C0D}\x{9C10}\x{9C12}\x{9C13}\x{9C14}\x{9C15}\x{9C1B}' + . '\x{9C21}\x{9C24}\x{9C25}\x{9C2D}\x{9C2E}\x{9C2F}\x{9C30}\x{9C32}\x{9C39}' + . '\x{9C3A}\x{9C3B}\x{9C3E}\x{9C46}\x{9C47}\x{9C48}\x{9C52}\x{9C57}\x{9C5A}' + . '\x{9C60}\x{9C67}\x{9C76}\x{9C78}\x{9CE5}\x{9CE7}\x{9CE9}\x{9CEB}\x{9CEC}' + . '\x{9CF0}\x{9CF3}\x{9CF4}\x{9CF6}\x{9D03}\x{9D06}\x{9D07}\x{9D08}\x{9D09}' + . '\x{9D0E}\x{9D12}\x{9D15}\x{9D1B}\x{9D1F}\x{9D23}\x{9D26}\x{9D28}\x{9D2A}' + . '\x{9D2B}\x{9D2C}\x{9D3B}\x{9D3E}\x{9D3F}\x{9D41}\x{9D44}\x{9D46}\x{9D48}' + . '\x{9D50}\x{9D51}\x{9D59}\x{9D5C}\x{9D5D}\x{9D5E}\x{9D60}\x{9D61}\x{9D64}' + . '\x{9D6C}\x{9D6F}\x{9D72}\x{9D7A}\x{9D87}\x{9D89}\x{9D8F}\x{9D9A}\x{9DA4}' + . '\x{9DA9}\x{9DAB}\x{9DAF}\x{9DB2}\x{9DB4}\x{9DB8}\x{9DBA}\x{9DBB}\x{9DC1}' + . '\x{9DC2}\x{9DC4}\x{9DC6}\x{9DCF}\x{9DD3}\x{9DD9}\x{9DE6}\x{9DED}\x{9DEF}' + . '\x{9DF2}\x{9DF8}\x{9DF9}\x{9DFA}\x{9DFD}\x{9E1A}\x{9E1B}\x{9E1E}\x{9E75}' + . '\x{9E78}\x{9E79}\x{9E7D}\x{9E7F}\x{9E81}\x{9E88}\x{9E8B}\x{9E8C}\x{9E91}' + . '\x{9E92}\x{9E93}\x{9E95}\x{9E97}\x{9E9D}\x{9E9F}\x{9EA5}\x{9EA6}\x{9EA9}' + . '\x{9EAA}\x{9EAD}\x{9EB8}\x{9EB9}\x{9EBA}\x{9EBB}\x{9EBC}\x{9EBE}\x{9EBF}' + . '\x{9EC4}\x{9ECC}\x{9ECD}\x{9ECE}\x{9ECF}\x{9ED0}\x{9ED2}\x{9ED4}\x{9ED8}' + . '\x{9ED9}\x{9EDB}\x{9EDC}\x{9EDD}\x{9EDE}\x{9EE0}\x{9EE5}\x{9EE8}\x{9EEF}' + . '\x{9EF4}\x{9EF6}\x{9EF7}\x{9EF9}\x{9EFB}\x{9EFC}\x{9EFD}\x{9F07}\x{9F08}' + . '\x{9F0E}\x{9F13}\x{9F15}\x{9F20}\x{9F21}\x{9F2C}\x{9F3B}\x{9F3E}\x{9F4A}' + . '\x{9F4B}\x{9F4E}\x{9F4F}\x{9F52}\x{9F54}\x{9F5F}\x{9F60}\x{9F61}\x{9F62}' + . '\x{9F63}\x{9F66}\x{9F67}\x{9F6A}\x{9F6C}\x{9F72}\x{9F76}\x{9F77}\x{9F8D}' + . '\x{9F95}\x{9F9C}\x{9F9D}\x{9FA0}]{1,15}$/iu', +]; diff --git a/lib/laminas/laminas-validator/src/Iban.php b/lib/laminas/laminas-validator/src/Iban.php new file mode 100644 index 0000000000..65f23054fc --- /dev/null +++ b/lib/laminas/laminas-validator/src/Iban.php @@ -0,0 +1,364 @@ + + */ + protected $messageTemplates = [ + self::NOTSUPPORTED => 'Unknown country within the IBAN', + self::SEPANOTSUPPORTED => 'Countries outside the Single Euro Payments Area (SEPA) are not supported', + self::FALSEFORMAT => 'The input has a false IBAN format', + self::CHECKFAILED => 'The input has failed the IBAN check', + ]; + + /** + * Optional country code by ISO 3166-1 + * + * @var string|null + */ + protected $countryCode; + + /** + * Optionally allow IBAN codes from non-SEPA countries. Defaults to true + * + * @var bool + */ + protected $allowNonSepa = true; + + /** + * The SEPA country codes + * + * @var string[] ISO 3166-1 codes + */ + protected static $sepaCountries = [ + 'AT', + 'BE', + 'BG', + 'CY', + 'CZ', + 'DK', + 'FO', + 'GL', + 'EE', + 'FI', + 'FR', + 'DE', + 'GI', + 'GR', + 'HU', + 'IS', + 'IE', + 'IT', + 'LV', + 'LI', + 'LT', + 'LU', + 'MT', + 'MC', + 'NL', + 'NO', + 'PL', + 'PT', + 'RO', + 'SK', + 'SI', + 'ES', + 'SE', + 'CH', + 'GB', + 'SM', + 'HR', + ]; + + /** + * IBAN regexes by country code + * + * @var array + */ + protected static $ibanRegex = [ + 'AD' => 'AD[0-9]{2}[0-9]{4}[0-9]{4}[A-Z0-9]{12}', + 'AE' => 'AE[0-9]{2}[0-9]{3}[0-9]{16}', + 'AL' => 'AL[0-9]{2}[0-9]{8}[A-Z0-9]{16}', + 'AT' => 'AT[0-9]{2}[0-9]{5}[0-9]{11}', + 'AZ' => 'AZ[0-9]{2}[A-Z]{4}[A-Z0-9]{20}', + 'BA' => 'BA[0-9]{2}[0-9]{3}[0-9]{3}[0-9]{8}[0-9]{2}', + 'BE' => 'BE[0-9]{2}[0-9]{3}[0-9]{7}[0-9]{2}', + 'BG' => 'BG[0-9]{2}[A-Z]{4}[0-9]{4}[0-9]{2}[A-Z0-9]{8}', + 'BH' => 'BH[0-9]{2}[A-Z]{4}[A-Z0-9]{14}', + 'BR' => 'BR[0-9]{2}[0-9]{8}[0-9]{5}[0-9]{10}[A-Z][A-Z0-9]', + 'BY' => 'BY[0-9]{2}[A-Z0-9]{4}[0-9]{4}[A-Z0-9]{16}', + 'CH' => 'CH[0-9]{2}[0-9]{5}[A-Z0-9]{12}', + 'CR' => 'CR[0-9]{2}[0-9]{3}[0-9]{14}', + 'CY' => 'CY[0-9]{2}[0-9]{3}[0-9]{5}[A-Z0-9]{16}', + 'CZ' => 'CZ[0-9]{2}[0-9]{20}', + 'DE' => 'DE[0-9]{2}[0-9]{8}[0-9]{10}', + 'DO' => 'DO[0-9]{2}[A-Z0-9]{4}[0-9]{20}', + 'DK' => 'DK[0-9]{2}[0-9]{14}', + 'EE' => 'EE[0-9]{2}[0-9]{2}[0-9]{2}[0-9]{11}[0-9]{1}', + 'ES' => 'ES[0-9]{2}[0-9]{4}[0-9]{4}[0-9]{1}[0-9]{1}[0-9]{10}', + 'FI' => 'FI[0-9]{2}[0-9]{6}[0-9]{7}[0-9]{1}', + 'FO' => 'FO[0-9]{2}[0-9]{4}[0-9]{9}[0-9]{1}', + 'FR' => 'FR[0-9]{2}[0-9]{5}[0-9]{5}[A-Z0-9]{11}[0-9]{2}', + 'GB' => 'GB[0-9]{2}[A-Z]{4}[0-9]{6}[0-9]{8}', + 'GE' => 'GE[0-9]{2}[A-Z]{2}[0-9]{16}', + 'GI' => 'GI[0-9]{2}[A-Z]{4}[A-Z0-9]{15}', + 'GL' => 'GL[0-9]{2}[0-9]{4}[0-9]{9}[0-9]{1}', + 'GR' => 'GR[0-9]{2}[0-9]{3}[0-9]{4}[A-Z0-9]{16}', + 'GT' => 'GT[0-9]{2}[A-Z0-9]{4}[A-Z0-9]{20}', + 'HR' => 'HR[0-9]{2}[0-9]{7}[0-9]{10}', + 'HU' => 'HU[0-9]{2}[0-9]{3}[0-9]{4}[0-9]{1}[0-9]{15}[0-9]{1}', + 'IE' => 'IE[0-9]{2}[A-Z]{4}[0-9]{6}[0-9]{8}', + 'IL' => 'IL[0-9]{2}[0-9]{3}[0-9]{3}[0-9]{13}', + 'IS' => 'IS[0-9]{2}[0-9]{4}[0-9]{2}[0-9]{6}[0-9]{10}', + 'IT' => 'IT[0-9]{2}[A-Z]{1}[0-9]{5}[0-9]{5}[A-Z0-9]{12}', + 'KW' => 'KW[0-9]{2}[A-Z]{4}[0-9]{22}', + 'KZ' => 'KZ[0-9]{2}[0-9]{3}[A-Z0-9]{13}', + 'LB' => 'LB[0-9]{2}[0-9]{4}[A-Z0-9]{20}', + 'LI' => 'LI[0-9]{2}[0-9]{5}[A-Z0-9]{12}', + 'LT' => 'LT[0-9]{2}[0-9]{5}[0-9]{11}', + 'LU' => 'LU[0-9]{2}[0-9]{3}[A-Z0-9]{13}', + 'LV' => 'LV[0-9]{2}[A-Z]{4}[A-Z0-9]{13}', + 'MC' => 'MC[0-9]{2}[0-9]{5}[0-9]{5}[A-Z0-9]{11}[0-9]{2}', + 'MD' => 'MD[0-9]{2}[A-Z0-9]{20}', + 'ME' => 'ME[0-9]{2}[0-9]{3}[0-9]{13}[0-9]{2}', + 'MK' => 'MK[0-9]{2}[0-9]{3}[A-Z0-9]{10}[0-9]{2}', + 'MR' => 'MR13[0-9]{5}[0-9]{5}[0-9]{11}[0-9]{2}', + 'MT' => 'MT[0-9]{2}[A-Z]{4}[0-9]{5}[A-Z0-9]{18}', + 'MU' => 'MU[0-9]{2}[A-Z]{4}[0-9]{2}[0-9]{2}[0-9]{12}[0-9]{3}[A-Z]{3}', + 'NL' => 'NL[0-9]{2}[A-Z]{4}[0-9]{10}', + 'NO' => 'NO[0-9]{2}[0-9]{4}[0-9]{6}[0-9]{1}', + 'UA' => 'UA[0-9]{2}[0-9]{6}[0-9]{19}', + 'PK' => 'PK[0-9]{2}[A-Z]{4}[A-Z0-9]{16}', + 'PL' => 'PL[0-9]{2}[0-9]{8}[0-9]{16}', + 'PS' => 'PS[0-9]{2}[A-Z]{4}[A-Z0-9]{21}', + 'PT' => 'PT[0-9]{2}[0-9]{4}[0-9]{4}[0-9]{11}[0-9]{2}', + 'RO' => 'RO[0-9]{2}[A-Z]{4}[A-Z0-9]{16}', + 'RS' => 'RS[0-9]{2}[0-9]{3}[0-9]{13}[0-9]{2}', + 'SA' => 'SA[0-9]{2}[0-9]{2}[A-Z0-9]{18}', + 'SE' => 'SE[0-9]{2}[0-9]{3}[0-9]{16}[0-9]{1}', + 'SI' => 'SI[0-9]{2}[0-9]{5}[0-9]{8}[0-9]{2}', + 'SK' => 'SK[0-9]{2}[0-9]{4}[0-9]{6}[0-9]{10}', + 'SM' => 'SM[0-9]{2}[A-Z]{1}[0-9]{5}[0-9]{5}[A-Z0-9]{12}', + 'TN' => 'TN59[0-9]{2}[0-9]{3}[0-9]{13}[0-9]{2}', + 'TR' => 'TR[0-9]{2}[0-9]{5}[A-Z0-9]{1}[A-Z0-9]{16}', + 'VG' => 'VG[0-9]{2}[A-Z]{4}[0-9]{16}', + ]; + + /** + * Sets validator options + * + * @param array|Traversable $options OPTIONAL + */ + public function __construct($options = []) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + + if (array_key_exists('country_code', $options)) { + $this->setCountryCode($options['country_code']); + } + + if (array_key_exists('allow_non_sepa', $options)) { + $this->setAllowNonSepa($options['allow_non_sepa']); + } + + parent::__construct($options); + } + + /** + * Returns the optional country code by ISO 3166-1 + * + * @return string|null + */ + public function getCountryCode() + { + return $this->countryCode; + } + + /** + * Sets an optional country code by ISO 3166-1 + * + * @param string|null $countryCode + * @return $this provides a fluent interface + * @throws Exception\InvalidArgumentException + */ + public function setCountryCode($countryCode = null) + { + if ($countryCode !== null) { + $countryCode = (string) $countryCode; + + if (! isset(static::$ibanRegex[$countryCode])) { + throw new Exception\InvalidArgumentException( + "Country code '{$countryCode}' invalid by ISO 3166-1 or not supported" + ); + } + } + + $this->countryCode = $countryCode; + return $this; + } + + /** + * Returns the optional allow non-sepa countries setting + * + * @return bool + */ + public function allowNonSepa() + { + return $this->allowNonSepa; + } + + /** + * Sets the optional allow non-sepa countries setting + * + * @param bool $allowNonSepa + * @return $this provides a fluent interface + */ + public function setAllowNonSepa($allowNonSepa) + { + $this->allowNonSepa = (bool) $allowNonSepa; + return $this; + } + + /** + * Returns true if $value is a valid IBAN + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::FALSEFORMAT); + return false; + } + + $value = str_replace(' ', '', strtoupper($value)); + $this->setValue($value); + + $countryCode = $this->getCountryCode(); + if ($countryCode === null) { + $countryCode = substr($value, 0, 2); + } + + if (! array_key_exists($countryCode, static::$ibanRegex)) { + $this->setValue($countryCode); + $this->error(self::NOTSUPPORTED); + return false; + } + + if (! $this->allowNonSepa && ! in_array($countryCode, static::$sepaCountries)) { + $this->setValue($countryCode); + $this->error(self::SEPANOTSUPPORTED); + return false; + } + + if (! preg_match('/^' . static::$ibanRegex[$countryCode] . '$/', $value)) { + $this->error(self::FALSEFORMAT); + return false; + } + + $format = substr($value, 4) . substr($value, 0, 4); + $format = str_replace( + [ + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z', + ], + [ + '10', + '11', + '12', + '13', + '14', + '15', + '16', + '17', + '18', + '19', + '20', + '21', + '22', + '23', + '24', + '25', + '26', + '27', + '28', + '29', + '30', + '31', + '32', + '33', + '34', + '35', + ], + $format + ); + + $temp = intval(substr($format, 0, 1)); + $len = strlen($format); + for ($x = 1; $x < $len; ++$x) { + $temp *= 10; + $temp += intval(substr($format, $x, 1)); + $temp %= 97; + } + + if ($temp !== 1) { + $this->error(self::CHECKFAILED); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Identical.php b/lib/laminas/laminas-validator/src/Identical.php new file mode 100644 index 0000000000..4bf3c358c2 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Identical.php @@ -0,0 +1,221 @@ + 'The two given tokens do not match', + self::MISSING_TOKEN => 'No token was provided to match against', + ]; + + /** @var array */ + protected $messageVariables = [ + 'token' => 'tokenString', + ]; + + /** + * Original token against which to validate + * + * @var null|string + */ + protected $tokenString; + + /** @var null|string */ + protected $token; + + /** @var bool */ + protected $strict = true; + + /** @var bool */ + protected $literal = false; + + /** + * Sets validator options + * + * @param mixed $token + */ + public function __construct($token = null) + { + if ($token instanceof Traversable) { + $token = ArrayUtils::iteratorToArray($token); + } + + if (is_array($token) && array_key_exists('token', $token)) { + if (array_key_exists('strict', $token)) { + $this->setStrict($token['strict']); + } + + if (array_key_exists('literal', $token)) { + $this->setLiteral($token['literal']); + } + + $this->setToken($token['token']); + } elseif (null !== $token) { + $this->setToken($token); + } + + parent::__construct(is_array($token) ? $token : null); + } + + /** + * Retrieve token + * + * @return mixed + */ + public function getToken() + { + return $this->token; + } + + /** + * Set token against which to compare + * + * @param mixed $token + * @return $this + */ + public function setToken($token) + { + $this->tokenString = is_array($token) ? var_export($token, true) : (string) $token; + $this->token = $token; + return $this; + } + + /** + * Returns the strict parameter + * + * @return bool + */ + public function getStrict() + { + return $this->strict; + } + + /** + * Sets the strict parameter + * + * @param bool $strict + * @return $this + */ + public function setStrict($strict) + { + $this->strict = (bool) $strict; + return $this; + } + + /** + * Returns the literal parameter + * + * @return bool + */ + public function getLiteral() + { + return $this->literal; + } + + /** + * Sets the literal parameter + * + * @param bool $literal + * @return $this + */ + public function setLiteral($literal) + { + $this->literal = (bool) $literal; + return $this; + } + + /** + * Returns true if and only if a token has been set and the provided value + * matches that token. + * + * @param mixed $value + * @param null|array|ArrayAccess $context + * @throws Exception\InvalidArgumentException If context is not array or ArrayObject. + * @return bool + */ + public function isValid($value, $context = null) + { + $this->setValue($value); + + $token = $this->getToken(); + + if (! $this->getLiteral() && $context !== null) { + if (! is_array($context) && ! $context instanceof ArrayAccess) { + throw new Exception\InvalidArgumentException(sprintf( + 'Context passed to %s must be array, ArrayObject or null; received "%s"', + __METHOD__, + is_object($context) ? get_class($context) : gettype($context) + )); + } + + if (is_array($token)) { + while (is_array($token)) { + $key = key($token); + if (! isset($context[$key])) { + break; + } + $context = $context[$key]; + $token = $token[$key]; + } + } + + // if $token is an array it means the above loop didn't went all the way down to the leaf, + // so the $token structure doesn't match the $context structure + if ( + is_array($token) + || (! is_int($token) && ! is_string($token)) + || ! isset($context[$token]) + ) { + $token = $this->getToken(); + } else { + $token = $context[$token]; + } + } + + if ($token === null) { + $this->error(self::MISSING_TOKEN); + return false; + } + + $strict = $this->getStrict(); + if ( + ($strict && ($value !== $token)) + // phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators.DisallowedNotEqualOperator + || (! $strict && ($value != $token)) + ) { + $this->error(self::NOT_SAME); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/InArray.php b/lib/laminas/laminas-validator/src/InArray.php new file mode 100644 index 0000000000..e1289c4ccb --- /dev/null +++ b/lib/laminas/laminas-validator/src/InArray.php @@ -0,0 +1,253 @@ + */ + protected $messageTemplates = [ + self::NOT_IN_ARRAY => 'The input was not found in the haystack', + ]; + + /** + * Haystack of possible values + * + * @var array + */ + protected $haystack; + + /** + * Type of strict check to be used. Due to "foo" == 0 === TRUE with in_array when strict = false, + * an option has been added to prevent this. When $strict = 0/false, the most + * secure non-strict check is implemented. if $strict = -1, the default in_array non-strict + * behaviour is used + * + * @var int + */ + protected $strict = self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY; + + /** + * Whether a recursive search should be done + * + * @var bool + */ + protected $recursive = false; + + /** + * Returns the haystack option + * + * @return mixed + * @throws Exception\RuntimeException If haystack option is not set. + */ + public function getHaystack() + { + if ($this->haystack === null) { + throw new Exception\RuntimeException('haystack option is mandatory'); + } + return $this->haystack; + } + + /** + * Sets the haystack option + * + * @param mixed $haystack + * @return $this Provides a fluent interface + */ + public function setHaystack(array $haystack) + { + $this->haystack = $haystack; + return $this; + } + + /** + * Returns the strict option + * + * @return bool|int + */ + public function getStrict() + { + // To keep BC with new strict modes + if ( + $this->strict === self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY + || $this->strict === self::COMPARE_STRICT + ) { + return (bool) $this->strict; + } + return $this->strict; + } + + /** + * Sets the strict option mode + * InArray::COMPARE_STRICT + * InArray::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY + * InArray::COMPARE_NOT_STRICT + * + * @param int|bool $strict + * @return $this Provides a fluent interface + * @throws Exception\InvalidArgumentException + */ + public function setStrict($strict) + { + if (is_bool($strict)) { + $strict = $strict ? self::COMPARE_STRICT : self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY; + } + + $checkTypes = [ + self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY, // 0 + self::COMPARE_STRICT, // 1 + self::COMPARE_NOT_STRICT, // -1 + ]; + + // validate strict value + if (! in_array($strict, $checkTypes)) { + throw new Exception\InvalidArgumentException('Strict option must be one of the COMPARE_ constants'); + } + + $this->strict = $strict; + return $this; + } + + /** + * Returns the recursive option + * + * @return bool + */ + public function getRecursive() + { + return $this->recursive; + } + + /** + * Sets the recursive option + * + * @param bool $recursive + * @return $this Provides a fluent interface + */ + public function setRecursive($recursive) + { + $this->recursive = (bool) $recursive; + return $this; + } + + /** + * Returns true if and only if $value is contained in the haystack option. If the strict + * option is true, then the type of $value is also checked. + * + * @param mixed $value + * See {@link http://php.net/manual/function.in-array.php#104501} + * @return bool + */ + public function isValid($value) + { + // we create a copy of the haystack in case we need to modify it + $haystack = $this->getHaystack(); + + // if the input is a string or float, and vulnerability protection is on + // we type cast the input to a string + if ( + self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY === $this->strict + && (is_int($value) || is_float($value)) + ) { + $value = (string) $value; + } + + $this->setValue($value); + + if ($this->getRecursive()) { + $iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($haystack)); + foreach ($iterator as $element) { + if (self::COMPARE_STRICT === $this->strict) { + if ($element === $value) { + return true; + } + + continue; + } + + // add protection to prevent string to int vuln's + $el = $element; + if ( + self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY === $this->strict + && is_string($value) && (is_int($el) || is_float($el)) + ) { + $el = (string) $el; + } + + // phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators.DisallowedEqualOperator + if ($el == $value) { + return true; + } + } + + $this->error(self::NOT_IN_ARRAY); + return false; + } + + /** + * If the check is not strict, then, to prevent "asdf" being converted to 0 + * and returning a false positive if 0 is in haystack, we type cast + * the haystack to strings. To prevent "56asdf" == 56 === TRUE we also + * type cast values like 56 to strings as well. + * + * This occurs only if the input is a string and a haystack member is an int + */ + if ( + self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY === $this->strict + && is_string($value) + ) { + foreach ($haystack as &$h) { + if (is_int($h) || is_float($h)) { + $h = (string) $h; + } + } + + if (in_array($value, $haystack, (bool) $this->strict)) { + return true; + } + + $this->error(self::NOT_IN_ARRAY); + return false; + } + + if (in_array($value, $haystack, self::COMPARE_STRICT === $this->strict)) { + return true; + } + + if (self::COMPARE_NOT_STRICT === $this->strict) { + return true; + } + + $this->error(self::NOT_IN_ARRAY); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/Ip.php b/lib/laminas/laminas-validator/src/Ip.php new file mode 100644 index 0000000000..969faa5efa --- /dev/null +++ b/lib/laminas/laminas-validator/src/Ip.php @@ -0,0 +1,193 @@ + 'Invalid type given. String expected', + self::NOT_IP_ADDRESS => 'The input does not appear to be a valid IP address', + ]; + + /** + * Internal options + * + * @var array + */ + protected $options = [ + 'allowipv4' => true, // Enable IPv4 Validation + 'allowipv6' => true, // Enable IPv6 Validation + 'allowipvfuture' => false, // Enable IPvFuture Validation + 'allowliteral' => true, // Enable IPs in literal format (only IPv6 and IPvFuture) + ]; + + /** + * Sets the options for this validator + * + * @param array|Traversable $options + * @throws Exception\InvalidArgumentException If there is any kind of IP allowed or $options is not an array + * or Traversable. + * @return AbstractValidator + */ + public function setOptions($options = []) + { + parent::setOptions($options); + + if (! $this->options['allowipv4'] && ! $this->options['allowipv6'] && ! $this->options['allowipvfuture']) { + throw new Exception\InvalidArgumentException('Nothing to validate. Check your options'); + } + + return $this; + } + + /** + * Returns true if and only if $value is a valid IP address + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + + if ($this->options['allowipv4'] && $this->validateIPv4($value)) { + return true; + } else { + if ((bool) $this->options['allowliteral']) { + static $regex = '/^\[(.*)\]$/'; + if ((bool) preg_match($regex, $value, $matches)) { + $value = $matches[1]; + } + } + + if ( + ($this->options['allowipv6'] && $this->validateIPv6($value)) || + ($this->options['allowipvfuture'] && $this->validateIPvFuture($value)) + ) { + return true; + } + } + $this->error(self::NOT_IP_ADDRESS); + return false; + } + + /** + * Validates an IPv4 address + * + * @param string $value + * @return bool + */ + protected function validateIPv4($value) + { + if (preg_match('/^([01]{8}\.){3}[01]{8}\z/i', $value)) { + // binary format 00000000.00000000.00000000.00000000 + $value = bindec(substr($value, 0, 8)) . '.' . bindec(substr($value, 9, 8)) . '.' + . bindec(substr($value, 18, 8)) . '.' . bindec(substr($value, 27, 8)); + } elseif (preg_match('/^([0-9]{3}\.){3}[0-9]{3}\z/i', $value)) { + // octet format 777.777.777.777 + $value = (int) substr($value, 0, 3) . '.' . (int) substr($value, 4, 3) . '.' + . (int) substr($value, 8, 3) . '.' . (int) substr($value, 12, 3); + } elseif (preg_match('/^([0-9a-f]{2}\.){3}[0-9a-f]{2}\z/i', $value)) { + // hex format ff.ff.ff.ff + $value = hexdec(substr($value, 0, 2)) . '.' . hexdec(substr($value, 3, 2)) . '.' + . hexdec(substr($value, 6, 2)) . '.' . hexdec(substr($value, 9, 2)); + } + + $ip2long = ip2long($value); + if ($ip2long === false) { + return false; + } + + return $value === long2ip($ip2long); + } + + /** + * Validates an IPv6 address + * + * @param string $value Value to check against + * @return bool|int True when $value is a valid ipv6 address False otherwise + */ + protected function validateIPv6($value) + { + if (strlen($value) < 3) { + return $value === '::'; + } + + if (strpos($value, '.')) { + $lastcolon = strrpos($value, ':'); + if (! ($lastcolon && $this->validateIPv4(substr($value, $lastcolon + 1)))) { + return false; + } + + $value = substr($value, 0, $lastcolon) . ':0:0'; + } + + if (strpos($value, '::') === false) { + return preg_match('/\A(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}\z/i', $value); + } + + $colonCount = substr_count($value, ':'); + if ($colonCount < 8) { + return preg_match('/\A(?::|(?:[a-f0-9]{1,4}:)+):(?:(?:[a-f0-9]{1,4}:)*[a-f0-9]{1,4})?\z/i', $value); + } + + // special case with ending or starting double colon + if ($colonCount === 8) { + return preg_match('/\A(?:::)?(?:[a-f0-9]{1,4}:){6}[a-f0-9]{1,4}(?:::)?\z/i', $value); + } + + return false; + } + + /** + * Validates an IPvFuture address. + * + * IPvFuture is loosely defined in the Section 3.2.2 of RFC 3986 + * + * @param string $value Value to check against + * @return bool True when $value is a valid IPvFuture address + * False otherwise + */ + protected function validateIPvFuture($value) + { + /* + * ABNF: + * IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," + * / ";" / "=" + */ + static $regex = '/^v([[:xdigit:]]+)\.[[:alnum:]\-\._~!\$&\'\(\)\*\+,;=:]+$/'; + + $result = (bool) preg_match($regex, $value, $matches); + + /* + * "As such, implementations must not provide the version flag for the + * existing IPv4 and IPv6 literal address forms described below." + */ + return $result && $matches[1] !== '4' && $matches[1] !== '6'; + } +} diff --git a/lib/laminas/laminas-validator/src/IsCountable.php b/lib/laminas/laminas-validator/src/IsCountable.php new file mode 100644 index 0000000000..e62ad6556d --- /dev/null +++ b/lib/laminas/laminas-validator/src/IsCountable.php @@ -0,0 +1,203 @@ + 'The input must be an array or an instance of \\Countable', + self::NOT_EQUALS => "The input count must equal '%count%'", + self::GREATER_THAN => "The input count must be less than '%max%', inclusively", + self::LESS_THAN => "The input count must be greater than '%min%', inclusively", + ]; + + /** + * Additional variables available for validation failure messages + * + * @var array + */ + protected $messageVariables = [ + 'count' => ['options' => 'count'], + 'min' => ['options' => 'min'], + 'max' => ['options' => 'max'], + ]; + + /** + * Options for the between validator + * + * @var array + */ + protected $options = [ + 'count' => null, + 'min' => null, + 'max' => null, + ]; + + /** + * @param array|Traversable $options + * @return $this Provides fluid interface + */ + public function setOptions($options = []) + { + foreach (['count', 'min', 'max'] as $option) { + if (! is_array($options) || ! isset($options[$option])) { + continue; + } + + $method = sprintf('set%s', ucfirst($option)); + $this->$method($options[$option]); + unset($options[$option]); + } + + return parent::setOptions($options); + } + + /** + * Returns true if and only if $value is countable (and the count validates against optional values). + * + * @param iterable $value + * @return bool + */ + public function isValid($value) + { + if (! is_countable($value)) { + $this->error(self::NOT_COUNTABLE); + return false; + } + + $count = count($value); + + if (is_numeric($this->getCount())) { + if ($count !== $this->getCount()) { + $this->error(self::NOT_EQUALS); + return false; + } + + return true; + } + + if (is_numeric($this->getMax()) && $count > $this->getMax()) { + $this->error(self::GREATER_THAN); + return false; + } + + if (is_numeric($this->getMin()) && $count < $this->getMin()) { + $this->error(self::LESS_THAN); + return false; + } + + return true; + } + + /** + * Returns the count option + * + * @return mixed + */ + public function getCount() + { + return $this->options['count']; + } + + /** + * Returns the min option + * + * @return mixed + */ + public function getMin() + { + return $this->options['min']; + } + + /** + * Returns the max option + * + * @return mixed + */ + public function getMax() + { + return $this->options['max']; + } + + /** + * @param mixed $value + * @return void + * @throws Exception\InvalidArgumentException If either a min or max option + * was previously set. + */ + private function setCount($value) + { + if (isset($this->options['min']) || isset($this->options['max'])) { + throw new Exception\InvalidArgumentException( + 'Cannot set count; conflicts with either a min or max option previously set' + ); + } + $this->options['count'] = $value; + } + + /** + * @param mixed $value + * @return void + * @throws Exception\InvalidArgumentException If either a count or max option + * was previously set. + */ + private function setMin($value) + { + if (isset($this->options['count'])) { + throw new Exception\InvalidArgumentException( + 'Cannot set count; conflicts with either a count option previously set' + ); + } + $this->options['min'] = $value; + } + + /** + * @param mixed $value + * @return void + * @throws Exception\InvalidArgumentException If either a count or min option + * was previously set. + */ + private function setMax($value) + { + if (isset($this->options['count'])) { + throw new Exception\InvalidArgumentException( + 'Cannot set count; conflicts with either a count option previously set' + ); + } + $this->options['max'] = $value; + } +} diff --git a/lib/laminas/laminas-validator/src/IsInstanceOf.php b/lib/laminas/laminas-validator/src/IsInstanceOf.php new file mode 100644 index 0000000000..e6009afb3f --- /dev/null +++ b/lib/laminas/laminas-validator/src/IsInstanceOf.php @@ -0,0 +1,103 @@ + "The input is not an instance of '%className%'", + ]; + + /** + * Additional variables available for validation failure messages + * + * @var array + */ + protected $messageVariables = [ + 'className' => 'className', + ]; + + /** @var string */ + protected $className; + + /** + * Sets validator options + * + * @param array|Traversable $options + * @throws Exception\InvalidArgumentException + */ + public function __construct($options = null) + { + if ($options instanceof Traversable) { + $options = iterator_to_array($options); + } + + // If argument is not an array, consider first argument as class name + if (! is_array($options)) { + $options = func_get_args(); + + $tmpOptions = []; + $tmpOptions['className'] = array_shift($options); + + $options = $tmpOptions; + } + + if (! array_key_exists('className', $options)) { + throw new Exception\InvalidArgumentException('Missing option "className"'); + } + + parent::__construct($options); + } + + /** + * Get class name + * + * @return string + */ + public function getClassName() + { + return $this->className; + } + + /** + * Set class name + * + * @param string $className + * @return $this + */ + public function setClassName($className) + { + $this->className = $className; + return $this; + } + + /** + * Returns true if $value is instance of $this->className + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + if ($value instanceof $this->className) { + return true; + } + $this->error(self::NOT_INSTANCE_OF); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/Isbn.php b/lib/laminas/laminas-validator/src/Isbn.php new file mode 100644 index 0000000000..d5d11876bb --- /dev/null +++ b/lib/laminas/laminas-validator/src/Isbn.php @@ -0,0 +1,189 @@ + 'Invalid type given. String or integer expected', + self::NO_ISBN => 'The input is not a valid ISBN number', + ]; + + /** @var array */ + protected $options = [ + 'type' => self::AUTO, // Allowed type + 'separator' => '', // Separator character + ]; + + /** + * Detect input format. + * + * @return null|string + */ + protected function detectFormat() + { + // prepare separator and pattern list + $sep = quotemeta($this->getSeparator()); + $patterns = []; + $lengths = []; + $type = $this->getType(); + + // check for ISBN-10 + if ($type === self::ISBN10 || $type === self::AUTO) { + if (empty($sep)) { + $pattern = '/^[0-9]{9}[0-9X]{1}$/'; + $length = 10; + } else { + $pattern = "/^[0-9]{1,7}[{$sep}]{1}[0-9]{1,7}[{$sep}]{1}[0-9]{1,7}[{$sep}]{1}[0-9X]{1}$/"; + $length = 13; + } + + $patterns[$pattern] = self::ISBN10; + $lengths[$pattern] = $length; + } + + // check for ISBN-13 + if ($type === self::ISBN13 || $type === self::AUTO) { + if (empty($sep)) { + $pattern = '/^[0-9]{13}$/'; + $length = 13; + } else { + // @codingStandardsIgnoreStart + $pattern = "/^[0-9]{1,9}[{$sep}]{1}[0-9]{1,5}[{$sep}]{1}[0-9]{1,9}[{$sep}]{1}[0-9]{1,9}[{$sep}]{1}[0-9]{1}$/"; + // @codingStandardsIgnoreEnd + $length = 17; + } + + $patterns[$pattern] = self::ISBN13; + $lengths[$pattern] = $length; + } + + // check pattern list + foreach ($patterns as $pattern => $type) { + if ((strlen($this->getValue()) === $lengths[$pattern]) && preg_match($pattern, $this->getValue())) { + return $type; + } + } + + return null; + } + + /** + * Returns true if and only if $value is a valid ISBN. + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value) && ! is_int($value)) { + $this->error(self::INVALID); + return false; + } + + $value = (string) $value; + $this->setValue($value); + + switch ($this->detectFormat()) { + case self::ISBN10: + $isbn = new Isbn\Isbn10(); + break; + + case self::ISBN13: + $isbn = new Isbn\Isbn13(); + break; + + default: + $this->error(self::NO_ISBN); + return false; + } + + $value = str_replace($this->getSeparator(), '', $value); + $checksum = $isbn->getChecksum($value); + + // validate + if (substr($this->getValue(), -1) !== (string) $checksum) { + $this->error(self::NO_ISBN); + return false; + } + return true; + } + + /** + * Set separator characters. + * + * It is allowed only empty string, hyphen and space. + * + * @param string $separator + * @throws Exception\InvalidArgumentException When $separator is not valid. + * @return $this Provides a fluent interface + */ + public function setSeparator($separator) + { + // check separator + if (! in_array($separator, ['-', ' ', ''])) { + throw new Exception\InvalidArgumentException('Invalid ISBN separator.'); + } + + $this->options['separator'] = $separator; + return $this; + } + + /** + * Get separator characters. + * + * @return string + */ + public function getSeparator() + { + return $this->options['separator']; + } + + /** + * Set allowed ISBN type. + * + * @param string $type + * @throws Exception\InvalidArgumentException When $type is not valid. + * @return $this Provides a fluent interface + */ + public function setType($type) + { + // check type + if (! in_array($type, [self::AUTO, self::ISBN10, self::ISBN13])) { + throw new Exception\InvalidArgumentException('Invalid ISBN type'); + } + + $this->options['type'] = $type; + return $this; + } + + /** + * Get allowed ISBN type. + * + * @return string + */ + public function getType() + { + return $this->options['type']; + } +} diff --git a/lib/laminas/laminas-validator/src/Isbn/Isbn10.php b/lib/laminas/laminas-validator/src/Isbn/Isbn10.php new file mode 100755 index 0000000000..4bcc6570d3 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Isbn/Isbn10.php @@ -0,0 +1,54 @@ +sum($value); + return $this->checksum($sum); + } + + /** + * Calculate the value sum. + * + * @param string $value + * @return int + */ + private function sum($value) + { + $sum = 0; + + for ($i = 0; $i < 9; $i++) { + $sum += (10 - $i) * (int) $value[$i]; + } + + return $sum; + } + + /** + * Calculate the checksum for the value's sum. + * + * @param int $sum + * @return int|string + */ + private function checksum($sum) + { + $checksum = 11 - ($sum % 11); + + if ($checksum === 11) { + return '0'; + } + + if ($checksum === 10) { + return 'X'; + } + + return $checksum; + } +} diff --git a/lib/laminas/laminas-validator/src/Isbn/Isbn13.php b/lib/laminas/laminas-validator/src/Isbn/Isbn13.php new file mode 100755 index 0000000000..c5aaf494e5 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Isbn/Isbn13.php @@ -0,0 +1,55 @@ +sum($value); + return $this->checksum($sum); + } + + /** + * Calculate the value sum. + * + * @param string $value + * @return int + */ + private function sum($value) + { + $sum = 0; + + for ($i = 0; $i < 12; $i++) { + if ($i % 2 === 0) { + $sum += (int) $value[$i]; + continue; + } + + $sum += 3 * (int) $value[$i]; + } + + return $sum; + } + + /** + * Calculate the checksum for the value's sum. + * + * @param int $sum + * @return int|string + */ + private function checksum($sum) + { + $checksum = 10 - ($sum % 10); + + if ($checksum === 10) { + return '0'; + } + + return $checksum; + } +} diff --git a/lib/laminas/laminas-validator/src/LessThan.php b/lib/laminas/laminas-validator/src/LessThan.php new file mode 100644 index 0000000000..c2a6fccc97 --- /dev/null +++ b/lib/laminas/laminas-validator/src/LessThan.php @@ -0,0 +1,159 @@ + "The input is not less than '%max%'", + self::NOT_LESS_INCLUSIVE => "The input is not less or equal than '%max%'", + ]; + + /** + * Additional variables available for validation failure messages + * + * @var array + */ + protected $messageVariables = [ + 'max' => 'max', + ]; + + /** + * Maximum value + * + * @var mixed + */ + protected $max; + + /** + * Whether to do inclusive comparisons, allowing equivalence to max + * + * If false, then strict comparisons are done, and the value may equal + * the max option + * + * @var bool + */ + protected $inclusive; + + /** + * Sets validator options + * + * @param array|Traversable $options + * @throws Exception\InvalidArgumentException + */ + public function __construct($options = null) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + if (! is_array($options)) { + $options = func_get_args(); + $temp['max'] = array_shift($options); + + if (! empty($options)) { + $temp['inclusive'] = array_shift($options); + } + + $options = $temp; + } + + if (! array_key_exists('max', $options)) { + throw new Exception\InvalidArgumentException("Missing option 'max'"); + } + + if (! array_key_exists('inclusive', $options)) { + $options['inclusive'] = false; + } + + $this->setMax($options['max']) + ->setInclusive($options['inclusive']); + + parent::__construct($options); + } + + /** + * Returns the max option + * + * @return mixed + */ + public function getMax() + { + return $this->max; + } + + /** + * Sets the max option + * + * @param mixed $max + * @return $this Provides a fluent interface + */ + public function setMax($max) + { + $this->max = $max; + return $this; + } + + /** + * Returns the inclusive option + * + * @return bool + */ + public function getInclusive() + { + return $this->inclusive; + } + + /** + * Sets the inclusive option + * + * @param bool $inclusive + * @return $this Provides a fluent interface + */ + public function setInclusive($inclusive) + { + $this->inclusive = $inclusive; + return $this; + } + + /** + * Returns true if and only if $value is less than max option, inclusively + * when the inclusive option is true + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + $this->setValue($value); + + if ($this->inclusive) { + if ($value > $this->max) { + $this->error(self::NOT_LESS_INCLUSIVE); + return false; + } + } else { + if ($value >= $this->max) { + $this->error(self::NOT_LESS); + return false; + } + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Module.php b/lib/laminas/laminas-validator/src/Module.php new file mode 100644 index 0000000000..d95016ee63 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Module.php @@ -0,0 +1,43 @@ + $provider->getDependencyConfig(), + ]; + } + + /** + * Register a specification for the ValidatorManager with the ServiceListener. + * + * @param ModuleManager $moduleManager + * @return void + */ + public function init($moduleManager) + { + $event = $moduleManager->getEvent(); + $container = $event->getParam('ServiceManager'); + $serviceListener = $container->get('ServiceListener'); + + $serviceListener->addServiceManager( + 'ValidatorManager', + 'validators', + ValidatorProviderInterface::class, + 'getValidatorConfig' + ); + } +} diff --git a/lib/laminas/laminas-validator/src/NotEmpty.php b/lib/laminas/laminas-validator/src/NotEmpty.php new file mode 100644 index 0000000000..b342974f77 --- /dev/null +++ b/lib/laminas/laminas-validator/src/NotEmpty.php @@ -0,0 +1,306 @@ + */ + protected $constants = [ + self::BOOLEAN => 'boolean', + self::INTEGER => 'integer', + self::FLOAT => 'float', + self::STRING => 'string', + self::ZERO => 'zero', + self::EMPTY_ARRAY => 'array', + self::NULL => 'null', + self::PHP => 'php', + self::SPACE => 'space', + self::OBJECT => 'object', + self::OBJECT_STRING => 'objectstring', + self::OBJECT_COUNT => 'objectcount', + self::ALL => 'all', + ]; + + /** + * Default value for types; value = 0b000111101001 + * + * @var array + */ + protected $defaultType = [ + self::OBJECT, + self::SPACE, + self::NULL, + self::EMPTY_ARRAY, + self::STRING, + self::BOOLEAN, + ]; + + /** @var array */ + protected $messageTemplates = [ + self::IS_EMPTY => "Value is required and can't be empty", + self::INVALID => 'Invalid type given. String, integer, float, boolean or array expected', + ]; + + /** + * Options for this validator + * + * @var array + */ + protected $options = []; + + /** + * Constructor + * + * @param array|Traversable|int $options OPTIONAL + */ + public function __construct($options = null) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + + if (! is_array($options)) { + $options = func_get_args(); + $temp = []; + if (! empty($options)) { + $temp['type'] = array_shift($options); + } + + $options = $temp; + } + + if (! isset($options['type'])) { + if (($type = $this->calculateTypeValue($options)) !== 0) { + $options['type'] = $type; + } else { + $options['type'] = $this->defaultType; + } + } + + parent::__construct($options); + } + + /** + * Returns the set types + * + * @return int + */ + public function getType() + { + return $this->options['type']; + } + + /** + * @return false|int|string + */ + public function getDefaultType() + { + return $this->calculateTypeValue($this->defaultType); + } + + /** + * @param array|int|string $type + * @return false|int|string + */ + protected function calculateTypeValue($type) + { + if (is_array($type)) { + $detected = 0; + foreach ($type as $value) { + if (is_int($value)) { + $detected |= $value; + } elseif (in_array($value, $this->constants, true)) { + $detected |= (int) array_search($value, $this->constants, true); + } + } + + $type = $detected; + } elseif (is_string($type) && in_array($type, $this->constants, true)) { + $type = array_search($type, $this->constants, true); + } + + return $type; + } + + /** + * Set the types + * + * @param int|int[] $type + * @throws Exception\InvalidArgumentException + * @return $this + */ + public function setType($type = null) + { + $type = $this->calculateTypeValue($type); + + if (! is_int($type) || ($type < 0) || ($type > self::ALL)) { + throw new Exception\InvalidArgumentException('Unknown type'); + } + + $this->options['type'] = $type; + + return $this; + } + + /** + * Returns true if and only if $value is not an empty value. + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + if ( + $value !== null + && ! is_string($value) + && ! is_int($value) + && ! is_float($value) + && ! is_bool($value) + && ! is_array($value) + && ! is_object($value) + ) { + $this->error(self::INVALID); + return false; + } + + $type = $this->getType(); + $this->setValue($value); + $object = false; + + // OBJECT_COUNT (countable object) + if ($type & self::OBJECT_COUNT) { + $object = true; + + if (is_object($value) && $value instanceof Countable && (count($value) === 0)) { + $this->error(self::IS_EMPTY); + return false; + } + } + + // OBJECT_STRING (object's toString) + if ($type & self::OBJECT_STRING) { + $object = true; + + if ( + (is_object($value) && ! method_exists($value, '__toString')) + || (is_object($value) && method_exists($value, '__toString') && (string) $value === '') + ) { + $this->error(self::IS_EMPTY); + return false; + } + } + + // OBJECT (object) + // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf + if ($type & self::OBJECT) { + // fall through, objects are always not empty + } elseif ($object === false) { + // object not allowed but object given -> return false + if (is_object($value)) { + $this->error(self::IS_EMPTY); + return false; + } + } + + // SPACE (' ') + if ($type & self::SPACE) { + if (is_string($value) && (preg_match('/^\s+$/s', $value))) { + $this->error(self::IS_EMPTY); + return false; + } + } + + // NULL (null) + if ($type & self::NULL) { + if ($value === null) { + $this->error(self::IS_EMPTY); + return false; + } + } + + // EMPTY_ARRAY (array()) + if ($type & self::EMPTY_ARRAY) { + if ($value === []) { + $this->error(self::IS_EMPTY); + return false; + } + } + + // ZERO ('0') + if ($type & self::ZERO) { + if ($value === '0') { + $this->error(self::IS_EMPTY); + return false; + } + } + + // STRING ('') + if ($type & self::STRING) { + if ($value === '') { + $this->error(self::IS_EMPTY); + return false; + } + } + + // FLOAT (0.0) + if ($type & self::FLOAT) { + if ($value === 0.0) { + $this->error(self::IS_EMPTY); + return false; + } + } + + // INTEGER (0) + if ($type & self::INTEGER) { + if ($value === 0) { + $this->error(self::IS_EMPTY); + return false; + } + } + + // BOOLEAN (false) + if ($type & self::BOOLEAN) { + if ($value === false) { + $this->error(self::IS_EMPTY); + return false; + } + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Regex.php b/lib/laminas/laminas-validator/src/Regex.php new file mode 100644 index 0000000000..d022fe3e9d --- /dev/null +++ b/lib/laminas/laminas-validator/src/Regex.php @@ -0,0 +1,137 @@ + 'Invalid type given. String, integer or float expected', + self::NOT_MATCH => "The input does not match against pattern '%pattern%'", + self::ERROROUS => "There was an internal error while using the pattern '%pattern%'", + ]; + + /** @var array */ + protected $messageVariables = [ + 'pattern' => 'pattern', + ]; + + /** + * Regular expression pattern + * + * @var string + */ + protected $pattern; + + /** + * Sets validator options + * + * @param string|array|Traversable $pattern + * @throws Exception\InvalidArgumentException On missing 'pattern' parameter. + */ + public function __construct($pattern) + { + if (is_string($pattern)) { + $this->setPattern($pattern); + parent::__construct([]); + return; + } + + if ($pattern instanceof Traversable) { + $pattern = ArrayUtils::iteratorToArray($pattern); + } + + if (! is_array($pattern)) { + throw new Exception\InvalidArgumentException('Invalid options provided to constructor'); + } + + if (! array_key_exists('pattern', $pattern)) { + throw new Exception\InvalidArgumentException("Missing option 'pattern'"); + } + + $this->setPattern($pattern['pattern']); + unset($pattern['pattern']); + parent::__construct($pattern); + } + + /** + * Returns the pattern option + * + * @return string + */ + public function getPattern() + { + return $this->pattern; + } + + /** + * Sets the pattern option + * + * @param string $pattern + * @throws Exception\InvalidArgumentException If there is a fatal error in pattern matching. + * @return $this Provides a fluent interface + */ + public function setPattern($pattern) + { + ErrorHandler::start(); + $this->pattern = (string) $pattern; + $status = preg_match($this->pattern, 'Test'); + $error = ErrorHandler::stop(); + + if (false === $status) { + throw new Exception\InvalidArgumentException( + "Internal error parsing the pattern '{$this->pattern}'", + 0, + $error + ); + } + + return $this; + } + + /** + * Returns true if and only if $value matches against the pattern option + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value) && ! is_int($value) && ! is_float($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + + ErrorHandler::start(); + $status = preg_match($this->pattern, $value); + ErrorHandler::stop(); + if (false === $status) { + $this->error(self::ERROROUS); + return false; + } + + if (! $status) { + $this->error(self::NOT_MATCH); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Sitemap/Changefreq.php b/lib/laminas/laminas-validator/src/Sitemap/Changefreq.php new file mode 100644 index 0000000000..af1cac18e1 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Sitemap/Changefreq.php @@ -0,0 +1,72 @@ + value + * + * @link http://www.sitemaps.org/protocol.php Sitemaps XML format + */ +class Changefreq extends AbstractValidator +{ + /** + * Validation key for not valid + */ + public const NOT_VALID = 'sitemapChangefreqNotValid'; + public const INVALID = 'sitemapChangefreqInvalid'; + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $messageTemplates = [ + self::NOT_VALID => 'The input is not a valid sitemap changefreq', + self::INVALID => 'Invalid type given. String expected', + ]; + + /** + * Valid change frequencies + * + * @var array + */ + protected $changeFreqs = [ + 'always', + 'hourly', + 'daily', + 'weekly', + 'monthly', + 'yearly', + 'never', + ]; + + /** + * Validates if a string is valid as a sitemap changefreq + * + * @link http://www.sitemaps.org/protocol.php#changefreqdef + * + * @param string $value value to validate + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + + if (! in_array($value, $this->changeFreqs, true)) { + $this->error(self::NOT_VALID); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Sitemap/Lastmod.php b/lib/laminas/laminas-validator/src/Sitemap/Lastmod.php new file mode 100644 index 0000000000..2f6884e4c9 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Sitemap/Lastmod.php @@ -0,0 +1,69 @@ + value + * + * @link http://www.sitemaps.org/protocol.php Sitemaps XML format + */ +class Lastmod extends AbstractValidator +{ + // phpcs:disable Generic.Files.LineLength.TooLong + + /** + * Regular expression to use when validating + */ + public const LASTMOD_REGEX = '/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])(T([0-1][0-9]|2[0-3])(:[0-5][0-9])(:[0-5][0-9])?(\\+|-)([0-1][0-9]|2[0-3]):[0-5][0-9])?$/'; + + // phpcs:enable + + /** + * Validation key for not valid + */ + public const NOT_VALID = 'sitemapLastmodNotValid'; + public const INVALID = 'sitemapLastmodInvalid'; + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $messageTemplates = [ + self::NOT_VALID => 'The input is not a valid sitemap lastmod', + self::INVALID => 'Invalid type given. String expected', + ]; + + /** + * Validates if a string is valid as a sitemap lastmod + * + * @link http://www.sitemaps.org/protocol.php#lastmoddef + * + * @param string $value value to validate + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + ErrorHandler::start(); + $result = preg_match(self::LASTMOD_REGEX, $value); + ErrorHandler::stop(); + if ($result !== 1) { + $this->error(self::NOT_VALID); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Sitemap/Loc.php b/lib/laminas/laminas-validator/src/Sitemap/Loc.php new file mode 100644 index 0000000000..447a92d4de --- /dev/null +++ b/lib/laminas/laminas-validator/src/Sitemap/Loc.php @@ -0,0 +1,58 @@ + value + * + * @link http://www.sitemaps.org/protocol.php Sitemaps XML format + * @see Laminas\Uri\Uri + */ +class Loc extends AbstractValidator +{ + /** + * Validation key for not valid + */ + public const NOT_VALID = 'sitemapLocNotValid'; + public const INVALID = 'sitemapLocInvalid'; + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $messageTemplates = [ + self::NOT_VALID => 'The input is not a valid sitemap location', + self::INVALID => 'Invalid type given. String expected', + ]; + + /** + * Validates if a string is valid as a sitemap location + * + * @link http://www.sitemaps.org/protocol.php#locdef + * + * @param string $value value to validate + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + $uri = Uri\UriFactory::factory($value); + if (! $uri->isValid()) { + $this->error(self::NOT_VALID); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Sitemap/Priority.php b/lib/laminas/laminas-validator/src/Sitemap/Priority.php new file mode 100644 index 0000000000..7bf6c02b82 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Sitemap/Priority.php @@ -0,0 +1,56 @@ + value + * + * @link http://www.sitemaps.org/protocol.php Sitemaps XML format + */ +class Priority extends AbstractValidator +{ + /** + * Validation key for not valid + */ + public const NOT_VALID = 'sitemapPriorityNotValid'; + public const INVALID = 'sitemapPriorityInvalid'; + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $messageTemplates = [ + self::NOT_VALID => 'The input is not a valid sitemap priority', + self::INVALID => 'Invalid type given. Numeric string, integer or float expected', + ]; + + /** + * Validates if a string is valid as a sitemap priority + * + * @link http://www.sitemaps.org/protocol.php#prioritydef + * + * @param string $value value to validate + * @return bool + */ + public function isValid($value) + { + if (! is_numeric($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + $value = (float) $value; + if ($value < 0 || $value > 1) { + $this->error(self::NOT_VALID); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/StaticValidator.php b/lib/laminas/laminas-validator/src/StaticValidator.php new file mode 100644 index 0000000000..aba395d4a7 --- /dev/null +++ b/lib/laminas/laminas-validator/src/StaticValidator.php @@ -0,0 +1,71 @@ +configure(['shared_by_default' => false]); + } else { + $plugins->setShareByDefault(false); + } + } + static::$plugins = $plugins; + } + + /** + * Get plugin manager for locating validators + * + * @return ValidatorPluginManager + */ + public static function getPluginManager() + { + if (! static::$plugins instanceof ValidatorPluginManager) { + $plugins = new ValidatorPluginManager(new ServiceManager()); + static::setPluginManager($plugins); + + return $plugins; + } + return static::$plugins; + } + + /** + * @param mixed $value + * @param class-string $classBaseName + * @param array $options OPTIONAL associative array of options to pass as + * the sole argument to the validator constructor. + * @return bool + * @throws Exception\InvalidArgumentException For an invalid $options argument. + */ + public static function execute($value, $classBaseName, array $options = []) + { + if ($options && array_values($options) === $options) { + throw new Exception\InvalidArgumentException( + 'Invalid options provided via $options argument; must be an associative array' + ); + } + + $plugins = static::getPluginManager(); + + $validator = $plugins->get($classBaseName, $options); + return $validator->isValid($value); + } +} diff --git a/lib/laminas/laminas-validator/src/Step.php b/lib/laminas/laminas-validator/src/Step.php new file mode 100644 index 0000000000..1892f52094 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Step.php @@ -0,0 +1,176 @@ + 'Invalid value given. Scalar expected', + self::NOT_STEP => 'The input is not a valid step', + ]; + + /** @var mixed */ + protected $baseValue = 0; + + /** @var mixed */ + protected $step = 1; + + /** + * Set default options for this instance + * + * @param array $options + */ + public function __construct($options = []) + { + if ($options instanceof Traversable) { + $options = iterator_to_array($options); + } elseif (! is_array($options)) { + $options = func_get_args(); + $temp['baseValue'] = array_shift($options); + if (! empty($options)) { + $temp['step'] = array_shift($options); + } + + $options = $temp; + } + + if (isset($options['baseValue'])) { + $this->setBaseValue($options['baseValue']); + } + if (isset($options['step'])) { + $this->setStep($options['step']); + } + + parent::__construct($options); + } + + /** + * Sets the base value from which the step should be computed + * + * @param mixed $baseValue + * @return $this + */ + public function setBaseValue($baseValue) + { + $this->baseValue = $baseValue; + return $this; + } + + /** + * Returns the base value from which the step should be computed + * + * @return string + */ + public function getBaseValue() + { + return $this->baseValue; + } + + /** + * Sets the step value + * + * @param mixed $step + * @return $this + */ + public function setStep($step) + { + $this->step = (float) $step; + return $this; + } + + /** + * Returns the step value + * + * @return string + */ + public function getStep() + { + return $this->step; + } + + /** + * Returns true if $value is a scalar and a valid step value + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + if (! is_numeric($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + + $substract = $this->sub($value, $this->baseValue); + + $fmod = $this->fmod($substract, $this->step); + + if ($fmod !== 0.0 && $fmod !== $this->step) { + $this->error(self::NOT_STEP); + return false; + } + + return true; + } + + /** + * replaces the internal fmod function which give wrong results on many cases + * + * @param int|float $x + * @param int|float $y + * @return float + */ + protected function fmod($x, $y) + { + if ($y === 0.0 || $y === 0) { + return 1.0; + } + + //find the maximum precision from both input params to give accurate results + $precision = $this->getPrecision($x) + $this->getPrecision($y); + + return round($x - $y * floor($x / $y), $precision); + } + + /** + * replaces the internal substraction operation which give wrong results on some cases + * + * @param float $x + * @param float $y + * @return float + */ + private function sub($x, $y) + { + $precision = $this->getPrecision($x) + $this->getPrecision($y); + return round($x - $y, $precision); + } + + /** + * @param float $float + * @return int + */ + private function getPrecision($float) + { + $segment = substr($float, strpos($float, '.') + 1); + return $segment ? strlen($segment) : 0; + } +} diff --git a/lib/laminas/laminas-validator/src/StringLength.php b/lib/laminas/laminas-validator/src/StringLength.php new file mode 100644 index 0000000000..64edff34b0 --- /dev/null +++ b/lib/laminas/laminas-validator/src/StringLength.php @@ -0,0 +1,233 @@ + */ + protected $messageTemplates = [ + self::INVALID => 'Invalid type given. String expected', + self::TOO_SHORT => 'The input is less than %min% characters long', + self::TOO_LONG => 'The input is more than %max% characters long', + ]; + + /** @var array> */ + protected $messageVariables = [ + 'min' => ['options' => 'min'], + 'max' => ['options' => 'max'], + 'length' => ['options' => 'length'], + ]; + + /** @var array */ + protected $options = [ + 'min' => 0, // Minimum length + 'max' => null, // Maximum length, null if there is no length limitation + 'encoding' => 'UTF-8', // Encoding to use + 'length' => 0, // Actual length + ]; + + /** @var null|StringWrapperInterface */ + protected $stringWrapper; + + /** + * Sets validator options + * + * @param int|array|Traversable $options + */ + public function __construct($options = []) + { + if (! is_array($options)) { + $options = func_get_args(); + $temp['min'] = array_shift($options); + if (! empty($options)) { + $temp['max'] = array_shift($options); + } + + if (! empty($options)) { + $temp['encoding'] = array_shift($options); + } + + $options = $temp; + } + + parent::__construct($options); + } + + /** + * Returns the min option + * + * @return int + */ + public function getMin() + { + return $this->options['min']; + } + + /** + * Sets the min option + * + * @param int $min + * @throws Exception\InvalidArgumentException + * @return $this Provides a fluent interface + */ + public function setMin($min) + { + if (null !== $this->getMax() && $min > $this->getMax()) { + throw new Exception\InvalidArgumentException( + "The minimum must be less than or equal to the maximum length, but {$min} > {$this->getMax()}" + ); + } + + $this->options['min'] = max(0, (int) $min); + return $this; + } + + /** + * Returns the max option + * + * @return int|null + */ + public function getMax() + { + return $this->options['max']; + } + + /** + * Sets the max option + * + * @param int|null $max + * @throws Exception\InvalidArgumentException + * @return $this Provides a fluent interface + */ + public function setMax($max) + { + if (null === $max) { + $this->options['max'] = null; + } elseif ($max < $this->getMin()) { + throw new Exception\InvalidArgumentException( + "The maximum must be greater than or equal to the minimum length, but {$max} < {$this->getMin()}" + ); + } else { + $this->options['max'] = (int) $max; + } + + return $this; + } + + /** + * Get the string wrapper to detect the string length + * + * @return StringWrapper + */ + public function getStringWrapper() + { + if (! $this->stringWrapper) { + $this->stringWrapper = StringUtils::getWrapper($this->getEncoding()); + } + return $this->stringWrapper; + } + + /** + * Set the string wrapper to detect the string length + * + * @return void + */ + public function setStringWrapper(StringWrapper $stringWrapper) + { + $stringWrapper->setEncoding($this->getEncoding()); + $this->stringWrapper = $stringWrapper; + } + + /** + * Returns the actual encoding + * + * @return string + */ + public function getEncoding() + { + return $this->options['encoding']; + } + + /** + * Sets a new encoding to use + * + * @param string $encoding + * @return $this + * @throws Exception\InvalidArgumentException + */ + public function setEncoding($encoding) + { + $this->stringWrapper = StringUtils::getWrapper($encoding); + $this->options['encoding'] = $encoding; + return $this; + } + + /** + * Returns the length option + * + * @return int + */ + private function getLength() + { + return $this->options['length']; + } + + /** + * Sets the length option + * + * @param int $length + * @return $this Provides a fluent interface + */ + private function setLength($length) + { + $this->options['length'] = (int) $length; + return $this; + } + + /** + * Returns true if and only if the string length of $value is at least the min option and + * no greater than the max option (when the max option is not null). + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $this->setValue($value); + + $this->setLength($this->getStringWrapper()->strlen($value)); + if ($this->getLength() < $this->getMin()) { + $this->error(self::TOO_SHORT); + } + + if (null !== $this->getMax() && $this->getMax() < $this->getLength()) { + $this->error(self::TOO_LONG); + } + + if ($this->getMessages()) { + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/Timezone.php b/lib/laminas/laminas-validator/src/Timezone.php new file mode 100644 index 0000000000..e262da2253 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Timezone.php @@ -0,0 +1,173 @@ + 'location', + self::ABBREVIATION => 'abbreviation', + ]; + + /** + * Default value for types; value = 3 + * + * @var array + */ + protected $defaultType = [ + self::LOCATION, + self::ABBREVIATION, + ]; + + /** @var array */ + protected $messageTemplates = [ + self::INVALID => 'Invalid timezone given.', + self::INVALID_TIMEZONE_LOCATION => 'Invalid timezone location given.', + self::INVALID_TIMEZONE_ABBREVIATION => 'Invalid timezone abbreviation given.', + ]; + + /** + * Options for this validator + * + * @var array + */ + protected $options = []; + + /** + * Constructor + * + * @param array|int $options OPTIONAL + */ + public function __construct($options = []) + { + $opts['type'] = $this->defaultType; + + if (is_array($options)) { + if (array_key_exists('type', $options)) { + $opts['type'] = $options['type']; + } + } elseif (! empty($options)) { + $opts['type'] = $options; + } + + // setType called by parent constructor then setOptions method + parent::__construct($opts); + } + + /** + * Set the types + * + * @param int|array $type + * @return void + * @throws Exception\InvalidArgumentException + */ + public function setType($type = null) + { + $type = $this->calculateTypeValue($type); + + if (! is_int($type) || ($type < 1) || ($type > self::ALL)) { + throw new Exception\InvalidArgumentException(sprintf( + 'Unknown type "%s" provided', + is_string($type) || is_int($type) + ? $type + : (is_object($type) ? get_class($type) : gettype($type)) + )); + } + + $this->options['type'] = $type; + } + + /** + * Returns true if timezone location or timezone abbreviations is correct. + * + * @param mixed $value + * @return bool + */ + public function isValid($value) + { + if ($value !== null && ! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $type = $this->options['type']; + $this->setValue($value); + + switch (true) { + // Check in locations and abbreviations + case ($type & self::LOCATION) && ($type & self::ABBREVIATION): + $abbrs = DateTimeZone::listAbbreviations(); + $locations = DateTimeZone::listIdentifiers(); + + if (! array_key_exists($value, $abbrs) && ! in_array($value, $locations)) { + $this->error(self::INVALID); + return false; + } + break; + + // Check only in locations + case $type & self::LOCATION: + $locations = DateTimeZone::listIdentifiers(); + + if (! in_array($value, $locations)) { + $this->error(self::INVALID_TIMEZONE_LOCATION); + return false; + } + break; + + // Check only in abbreviations + case $type & self::ABBREVIATION: + $abbrs = DateTimeZone::listAbbreviations(); + + if (! array_key_exists($value, $abbrs)) { + $this->error(self::INVALID_TIMEZONE_ABBREVIATION); + return false; + } + break; + } + + return true; + } + + /** + * @param array|int|string $type + * @return float|int + */ + protected function calculateTypeValue($type) + { + $types = (array) $type; + $detected = 0; + + foreach ($types as $value) { + if (is_int($value)) { + $detected |= $value; + } elseif (false !== array_search($value, $this->constants)) { + $detected |= array_search($value, $this->constants); + } + } + + return $detected; + } +} diff --git a/lib/laminas/laminas-validator/src/Translator/TranslatorAwareInterface.php b/lib/laminas/laminas-validator/src/Translator/TranslatorAwareInterface.php new file mode 100644 index 0000000000..9702abc8c4 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Translator/TranslatorAwareInterface.php @@ -0,0 +1,62 @@ + */ + protected $messageTemplates = [ + self::PASSWORD_BREACHED => 'The provided password was found in previous breaches, please create another password', + self::NOT_A_STRING => 'The provided password is not a string, please provide a correct password', + ]; + + private ClientInterface $httpClient; + + private RequestFactoryInterface $makeHttpRequest; + + public function __construct(ClientInterface $httpClient, RequestFactoryInterface $makeHttpRequest) + { + parent::__construct(); + + $this->httpClient = $httpClient; + $this->makeHttpRequest = $makeHttpRequest; + } + + /** + * @inheritDoc + */ + public function isValid($value): bool + { + if (! is_string($value)) { + $this->error(self::NOT_A_STRING); + return false; + } + + if ($this->isPwnedPassword($value)) { + $this->error(self::PASSWORD_BREACHED); + return false; + } + + return true; + } + + private function isPwnedPassword(string $password): bool + { + $sha1Hash = $this->hashPassword($password); + $rangeHash = $this->getRangeHash($sha1Hash); + $hashList = $this->retrieveHashList($rangeHash); + + return $this->hashInResponse($sha1Hash, $hashList); + } + + /** + * We use a SHA1 hashed password for checking it against + * the breached data set of HIBP. + */ + private function hashPassword(string $password): string + { + $hashedPassword = sha1($password); + + return strtoupper($hashedPassword); + } + + /** + * Creates a hash range that will be send to HIBP API + * applying K-Anonymity + * + * @see https://www.troyhunt.com/enhancing-pwned-passwords-privacy-by-exclusively-supporting-anonymity/ + */ + private function getRangeHash(string $passwordHash): string + { + return substr($passwordHash, self::HIBP_K_ANONYMITY_HASH_RANGE_BASE, self::HIBP_K_ANONYMITY_HASH_RANGE_LENGTH); + } + + /** + * Making a connection to the HIBP API to retrieve a + * list of hashes that all have the same range as we + * provided. + * + * @throws ClientExceptionInterface + */ + private function retrieveHashList(string $passwordRange): string + { + $request = $this->makeHttpRequest->createRequest( + 'GET', + self::HIBP_API_URI . '/range/' . $passwordRange + ); + + $response = $this->httpClient->sendRequest($request); + return (string) $response->getBody(); + } + + /** + * Checks if the password is in the response from HIBP + */ + private function hashInResponse(string $sha1Hash, string $resultStream): bool + { + $data = explode("\r\n", $resultStream); + $hashes = array_filter($data, static function ($value) use ($sha1Hash) { + [$hash] = explode(':', $value); + + return strcmp($hash, substr($sha1Hash, self::HIBP_K_ANONYMITY_HASH_RANGE_LENGTH)) === 0; + }); + + return $hashes !== []; + } +} diff --git a/lib/laminas/laminas-validator/src/Uri.php b/lib/laminas/laminas-validator/src/Uri.php new file mode 100644 index 0000000000..ed2d3612ee --- /dev/null +++ b/lib/laminas/laminas-validator/src/Uri.php @@ -0,0 +1,185 @@ + */ + protected $messageTemplates = [ + self::INVALID => 'Invalid type given. String expected', + self::NOT_URI => 'The input does not appear to be a valid Uri', + ]; + + /** @var UriHandler */ + protected $uriHandler; + + /** @var bool */ + protected $allowRelative = true; + + /** @var bool */ + protected $allowAbsolute = true; + + /** + * Sets default option values for this instance + * + * @param array|Traversable $options + */ + public function __construct($options = []) + { + if ($options instanceof Traversable) { + $options = iterator_to_array($options); + } elseif (! is_array($options)) { + $options = func_get_args(); + $temp['uriHandler'] = array_shift($options); + if (! empty($options)) { + $temp['allowRelative'] = array_shift($options); + } + if (! empty($options)) { + $temp['allowAbsolute'] = array_shift($options); + } + + $options = $temp; + } + + if (isset($options['uriHandler'])) { + $this->setUriHandler($options['uriHandler']); + } + if (isset($options['allowRelative'])) { + $this->setAllowRelative($options['allowRelative']); + } + if (isset($options['allowAbsolute'])) { + $this->setAllowAbsolute($options['allowAbsolute']); + } + + parent::__construct($options); + } + + /** + * @throws InvalidArgumentException + * @return UriHandler + */ + public function getUriHandler() + { + if (null === $this->uriHandler) { + // Lazy load the base Uri handler + $this->uriHandler = new UriHandler(); + } elseif (is_string($this->uriHandler) && class_exists($this->uriHandler)) { + // Instantiate string Uri handler that references a class + $this->uriHandler = new $this->uriHandler(); + } + return $this->uriHandler; + } + + /** + * @param UriHandler|string $uriHandler + * @throws InvalidArgumentException + * @return $this + */ + public function setUriHandler($uriHandler) + { + if (! is_a($uriHandler, UriHandler::class, true)) { + throw new InvalidArgumentException(sprintf( + 'Expecting a subclass name or instance of %s as $uriHandler', + UriHandler::class + )); + } + + $this->uriHandler = $uriHandler; + return $this; + } + + /** + * Returns the allowAbsolute option + * + * @return bool + */ + public function getAllowAbsolute() + { + return $this->allowAbsolute; + } + + /** + * Sets the allowAbsolute option + * + * @param bool $allowAbsolute + * @return $this + */ + public function setAllowAbsolute($allowAbsolute) + { + $this->allowAbsolute = (bool) $allowAbsolute; + return $this; + } + + /** + * Returns the allowRelative option + * + * @return bool + */ + public function getAllowRelative() + { + return $this->allowRelative; + } + + /** + * Sets the allowRelative option + * + * @param bool $allowRelative + * @return $this + */ + public function setAllowRelative($allowRelative) + { + $this->allowRelative = (bool) $allowRelative; + return $this; + } + + /** + * Returns true if and only if $value validates as a Uri + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::INVALID); + return false; + } + + $uriHandler = $this->getUriHandler(); + try { + $uriHandler->parse($value); + if ($uriHandler->isValid()) { + // It will either be a valid absolute or relative URI + if ( + ($this->allowRelative && $this->allowAbsolute) + || ($this->allowAbsolute && $uriHandler->isAbsolute()) + || ($this->allowRelative && $uriHandler->isValidRelative()) + ) { + return true; + } + } + } catch (UriException $ex) { + // Error parsing URI, it must be invalid + } + + $this->error(self::NOT_URI); + return false; + } +} diff --git a/lib/laminas/laminas-validator/src/Uuid.php b/lib/laminas/laminas-validator/src/Uuid.php new file mode 100644 index 0000000000..77ecdd8b33 --- /dev/null +++ b/lib/laminas/laminas-validator/src/Uuid.php @@ -0,0 +1,58 @@ + 'Invalid type given; string expected', + self::INVALID => 'Invalid UUID format', + ]; + + /** + * Returns true if and only if $value meets the validation requirements. + * + * If $value fails validation, then this method returns false, and + * getMessages() will return an array of messages that explain why the + * validation failed. + * + * @param mixed $value + * @return bool + * @throws Exception\RuntimeException If validation of $value is impossible. + */ + public function isValid($value) + { + if (! is_string($value)) { + $this->error(self::NOT_STRING); + return false; + } + + $this->setValue($value); + + if ( + empty($value) + || $value !== '00000000-0000-0000-0000-000000000000' + && ! preg_match(self::REGEX_UUID, $value) + ) { + $this->error(self::INVALID); + return false; + } + + return true; + } +} diff --git a/lib/laminas/laminas-validator/src/ValidatorChain.php b/lib/laminas/laminas-validator/src/ValidatorChain.php new file mode 100644 index 0000000000..798c52f2b3 --- /dev/null +++ b/lib/laminas/laminas-validator/src/ValidatorChain.php @@ -0,0 +1,334 @@ +|null */ + protected $plugins; + + /** + * Validator chain + * + * @var PriorityQueue + */ + protected $validators; + + /** + * Array of validation failure messages + * + * @var array + */ + protected $messages = []; + + /** + * Initialize validator chain + */ + public function __construct() + { + /** @psalm-suppress InvalidPropertyAssignmentValue */ + $this->validators = new PriorityQueue(); + } + + /** + * Return the count of attached validators + * + * @return int + */ + #[ReturnTypeWillChange] + public function count() + { + return count($this->validators); + } + + /** + * Get plugin manager instance + * + * @return ValidatorPluginManager + */ + public function getPluginManager() + { + if (! $this->plugins) { + $this->setPluginManager(new ValidatorPluginManager(new ServiceManager())); + } + return $this->plugins; + } + + /** + * Set plugin manager instance + * + * @param ValidatorPluginManager $plugins Plugin manager + * @psalm-assert ValidatorPluginManager $this->plugins + * @return $this + */ + public function setPluginManager(ValidatorPluginManager $plugins) + { + $this->plugins = $plugins; + return $this; + } + + /** + * Retrieve a validator by name + * + * @param string|class-string $name Name of validator to return + * @param null|array $options Options to pass to validator constructor + * (if not already instantiated) + * @return ValidatorInterface + * @template T of ValidatorInterface + * @psalm-param string|class-string $name + * @psalm-return ValidatorInterface + */ + public function plugin($name, ?array $options = null) + { + $plugins = $this->getPluginManager(); + return $plugins->get($name, $options); + } + + /** + * Attach a validator to the end of the chain + * If $breakChainOnFailure is true, then if the validator fails, the next validator in the chain, + * if one exists, will not be executed. + * + * @param bool $breakChainOnFailure + * @param int $priority Priority at which to enqueue validator; defaults to + * 1 (higher executes earlier) + * @return $this + * @throws Exception\InvalidArgumentException + */ + public function attach( + ValidatorInterface $validator, + $breakChainOnFailure = false, + $priority = self::DEFAULT_PRIORITY + ) { + /** @psalm-suppress RedundantCastGivenDocblockType */ + $this->validators->insert( + [ + 'instance' => $validator, + 'breakChainOnFailure' => (bool) $breakChainOnFailure, + ], + $priority + ); + + return $this; + } + + /** + * Proxy to attach() to keep BC + * + * @deprecated Please use attach() + * + * @param bool $breakChainOnFailure + * @param int $priority + * @return ValidatorChain Provides a fluent interface + */ + public function addValidator( + ValidatorInterface $validator, + $breakChainOnFailure = false, + $priority = self::DEFAULT_PRIORITY + ) { + return $this->attach($validator, $breakChainOnFailure, $priority); + } + + /** + * Adds a validator to the beginning of the chain + * + * If $breakChainOnFailure is true, then if the validator fails, the next validator in the chain, + * if one exists, will not be executed. + * + * @param bool $breakChainOnFailure + * @return $this Provides a fluent interface + */ + public function prependValidator(ValidatorInterface $validator, $breakChainOnFailure = false) + { + $priority = self::DEFAULT_PRIORITY; + + if (! $this->validators->isEmpty()) { + $extractedNodes = $this->validators->toArray(PriorityQueue::EXTR_PRIORITY); + rsort($extractedNodes, SORT_NUMERIC); + $priority = $extractedNodes[0] + 1; + } + + /** @psalm-suppress RedundantCastGivenDocblockType */ + $this->validators->insert( + [ + 'instance' => $validator, + 'breakChainOnFailure' => (bool) $breakChainOnFailure, + ], + $priority + ); + return $this; + } + + /** + * Use the plugin manager to add a validator by name + * + * @param string|class-string $name + * @param array $options + * @param bool $breakChainOnFailure + * @param int $priority + * @return $this + */ + public function attachByName($name, $options = [], $breakChainOnFailure = false, $priority = self::DEFAULT_PRIORITY) + { + if (isset($options['break_chain_on_failure'])) { + $breakChainOnFailure = (bool) $options['break_chain_on_failure']; + } + + if (isset($options['breakchainonfailure'])) { + $breakChainOnFailure = (bool) $options['breakchainonfailure']; + } + + $this->attach($this->plugin($name, $options), $breakChainOnFailure, $priority); + + return $this; + } + + /** + * Proxy to attachByName() to keep BC + * + * @deprecated Please use attachByName() + * + * @param string $name + * @param array $options + * @param bool $breakChainOnFailure + * @return ValidatorChain + */ + public function addByName($name, $options = [], $breakChainOnFailure = false) + { + return $this->attachByName($name, $options, $breakChainOnFailure); + } + + /** + * Use the plugin manager to prepend a validator by name + * + * @param string|class-string $name + * @param array $options + * @param bool $breakChainOnFailure + * @return $this + */ + public function prependByName($name, $options = [], $breakChainOnFailure = false) + { + $validator = $this->plugin($name, $options); + $this->prependValidator($validator, $breakChainOnFailure); + return $this; + } + + /** + * Returns true if and only if $value passes all validations in the chain + * + * Validators are run in the order in which they were added to the chain (FIFO). + * + * @param mixed $value + * @param mixed $context Extra "context" to provide the validator + * @return bool + */ + public function isValid($value, $context = null) + { + $this->messages = []; + $result = true; + foreach ($this->validators as $element) { + $validator = $element['instance']; + assert($validator instanceof ValidatorInterface); + if ($validator->isValid($value, $context)) { + continue; + } + $result = false; + $messages = $validator->getMessages(); + $this->messages = array_replace($this->messages, $messages); + if ($element['breakChainOnFailure']) { + break; + } + } + return $result; + } + + /** + * Merge the validator chain with the one given in parameter + * + * @return $this + */ + public function merge(ValidatorChain $validatorChain) + { + foreach ($validatorChain->validators->toArray(PriorityQueue::EXTR_BOTH) as $item) { + $this->attach($item['data']['instance'], $item['data']['breakChainOnFailure'], $item['priority']); + } + + return $this; + } + + /** + * Returns array of validation failure messages + * + * @return array + */ + public function getMessages() + { + return $this->messages; + } + + /** + * Get all the validators + * + * @return list + */ + public function getValidators() + { + return $this->validators->toArray(PriorityQueue::EXTR_DATA); + } + + /** + * Invoke chain as command + * + * @param mixed $value + * @return bool + */ + public function __invoke($value) + { + return $this->isValid($value); + } + + /** + * Deep clone handling + */ + public function __clone() + { + $this->validators = clone $this->validators; + } + + /** + * Prepare validator chain for serialization + * + * Plugin manager (property 'plugins') cannot + * be serialized. On wakeup the property remains unset + * and next invocation to getPluginManager() sets + * the default plugin manager instance (ValidatorPluginManager). + * + * @return array + */ + public function __sleep() + { + return ['validators', 'messages']; + } +} diff --git a/lib/laminas/laminas-validator/src/ValidatorInterface.php b/lib/laminas/laminas-validator/src/ValidatorInterface.php new file mode 100644 index 0000000000..af83fbd0aa --- /dev/null +++ b/lib/laminas/laminas-validator/src/ValidatorInterface.php @@ -0,0 +1,39 @@ +, + * priority?: int, + * break_chain_on_failure?: bool, + * options?: array, + * } + */ +interface ValidatorInterface +{ + /** + * Returns true if and only if $value meets the validation requirements + * + * If $value fails validation, then this method returns false, and + * getMessages() will return an array of messages that explain why the + * validation failed. + * + * @param mixed $value + * @return bool + * @throws Exception\RuntimeException If validation of $value is impossible. + */ + public function isValid($value); + + /** + * Returns an array of messages that explain why the most recent isValid() + * call returned false. The array keys are validation failure message identifiers, + * and the array values are the corresponding human-readable message strings. + * + * If isValid() was never called or if the most recent isValid() call + * returned true, then this method returns an empty array. + * + * @return array + */ + public function getMessages(); +} diff --git a/lib/laminas/laminas-validator/src/ValidatorPluginManager.php b/lib/laminas/laminas-validator/src/ValidatorPluginManager.php new file mode 100644 index 0000000000..58517ebc4c --- /dev/null +++ b/lib/laminas/laminas-validator/src/ValidatorPluginManager.php @@ -0,0 +1,657 @@ + + */ +class ValidatorPluginManager extends AbstractPluginManager +{ + /** + * Default set of aliases + * + * @var array + * @psalm-suppress UndefinedClass + */ + protected $aliases = [ + 'alnum' => I18nValidator\Alnum::class, + 'Alnum' => I18nValidator\Alnum::class, + 'alpha' => I18nValidator\Alpha::class, + 'Alpha' => I18nValidator\Alpha::class, + 'barcode' => Barcode::class, + 'Barcode' => Barcode::class, + 'between' => Between::class, + 'Between' => Between::class, + 'BIC' => BusinessIdentifierCode::class, + 'bic' => BusinessIdentifierCode::class, + 'bitwise' => Bitwise::class, + 'Bitwise' => Bitwise::class, + 'BusinessIdentifierCode' => BusinessIdentifierCode::class, + 'businessidentifiercode' => BusinessIdentifierCode::class, + 'callback' => Callback::class, + 'Callback' => Callback::class, + 'creditcard' => CreditCard::class, + 'creditCard' => CreditCard::class, + 'CreditCard' => CreditCard::class, + 'csrf' => Csrf::class, + 'Csrf' => Csrf::class, + 'date' => Date::class, + 'Date' => Date::class, + 'datestep' => DateStep::class, + 'dateStep' => DateStep::class, + 'DateStep' => DateStep::class, + 'datetime' => I18nValidator\DateTime::class, + 'dateTime' => I18nValidator\DateTime::class, + 'DateTime' => I18nValidator\DateTime::class, + 'dbnorecordexists' => Db\NoRecordExists::class, + 'dbNoRecordExists' => Db\NoRecordExists::class, + 'DbNoRecordExists' => Db\NoRecordExists::class, + 'dbrecordexists' => Db\RecordExists::class, + 'dbRecordExists' => Db\RecordExists::class, + 'DbRecordExists' => Db\RecordExists::class, + 'digits' => Digits::class, + 'Digits' => Digits::class, + 'emailaddress' => EmailAddress::class, + 'emailAddress' => EmailAddress::class, + 'EmailAddress' => EmailAddress::class, + 'explode' => Explode::class, + 'Explode' => Explode::class, + 'filecount' => File\Count::class, + 'fileCount' => File\Count::class, + 'FileCount' => File\Count::class, + 'filecrc32' => File\Crc32::class, + 'fileCrc32' => File\Crc32::class, + 'FileCrc32' => File\Crc32::class, + 'fileexcludeextension' => File\ExcludeExtension::class, + 'fileExcludeExtension' => File\ExcludeExtension::class, + 'FileExcludeExtension' => File\ExcludeExtension::class, + 'fileexcludemimetype' => File\ExcludeMimeType::class, + 'fileExcludeMimeType' => File\ExcludeMimeType::class, + 'FileExcludeMimeType' => File\ExcludeMimeType::class, + 'fileexists' => File\Exists::class, + 'fileExists' => File\Exists::class, + 'FileExists' => File\Exists::class, + 'fileextension' => File\Extension::class, + 'fileExtension' => File\Extension::class, + 'FileExtension' => File\Extension::class, + 'filefilessize' => File\FilesSize::class, + 'fileFilesSize' => File\FilesSize::class, + 'FileFilesSize' => File\FilesSize::class, + 'filehash' => File\Hash::class, + 'fileHash' => File\Hash::class, + 'FileHash' => File\Hash::class, + 'fileimagesize' => File\ImageSize::class, + 'fileImageSize' => File\ImageSize::class, + 'FileImageSize' => File\ImageSize::class, + 'fileiscompressed' => File\IsCompressed::class, + 'fileIsCompressed' => File\IsCompressed::class, + 'FileIsCompressed' => File\IsCompressed::class, + 'fileisimage' => File\IsImage::class, + 'fileIsImage' => File\IsImage::class, + 'FileIsImage' => File\IsImage::class, + 'filemd5' => File\Md5::class, + 'fileMd5' => File\Md5::class, + 'FileMd5' => File\Md5::class, + 'filemimetype' => File\MimeType::class, + 'fileMimeType' => File\MimeType::class, + 'FileMimeType' => File\MimeType::class, + 'filenotexists' => File\NotExists::class, + 'fileNotExists' => File\NotExists::class, + 'FileNotExists' => File\NotExists::class, + 'filesha1' => File\Sha1::class, + 'fileSha1' => File\Sha1::class, + 'FileSha1' => File\Sha1::class, + 'filesize' => File\Size::class, + 'fileSize' => File\Size::class, + 'FileSize' => File\Size::class, + 'fileupload' => File\Upload::class, + 'fileUpload' => File\Upload::class, + 'FileUpload' => File\Upload::class, + 'fileuploadfile' => File\UploadFile::class, + 'fileUploadFile' => File\UploadFile::class, + 'FileUploadFile' => File\UploadFile::class, + 'filewordcount' => File\WordCount::class, + 'fileWordCount' => File\WordCount::class, + 'FileWordCount' => File\WordCount::class, + 'float' => I18nValidator\IsFloat::class, + 'Float' => I18nValidator\IsFloat::class, + 'gpspoint' => GpsPoint::class, + 'gpsPoint' => GpsPoint::class, + 'GpsPoint' => GpsPoint::class, + 'greaterthan' => GreaterThan::class, + 'greaterThan' => GreaterThan::class, + 'GreaterThan' => GreaterThan::class, + 'hex' => Hex::class, + 'Hex' => Hex::class, + 'hostname' => Hostname::class, + 'Hostname' => Hostname::class, + 'iban' => Iban::class, + 'Iban' => Iban::class, + 'identical' => Identical::class, + 'Identical' => Identical::class, + 'inarray' => InArray::class, + 'inArray' => InArray::class, + 'InArray' => InArray::class, + 'int' => I18nValidator\IsInt::class, + 'Int' => I18nValidator\IsInt::class, + 'ip' => Ip::class, + 'Ip' => Ip::class, + 'isbn' => Isbn::class, + 'Isbn' => Isbn::class, + 'isCountable' => IsCountable::class, + 'IsCountable' => IsCountable::class, + 'iscountable' => IsCountable::class, + 'isfloat' => I18nValidator\IsFloat::class, + 'isFloat' => I18nValidator\IsFloat::class, + 'IsFloat' => I18nValidator\IsFloat::class, + 'isinstanceof' => IsInstanceOf::class, + 'isInstanceOf' => IsInstanceOf::class, + 'IsInstanceOf' => IsInstanceOf::class, + 'isint' => I18nValidator\IsInt::class, + 'isInt' => I18nValidator\IsInt::class, + 'IsInt' => I18nValidator\IsInt::class, + 'lessthan' => LessThan::class, + 'lessThan' => LessThan::class, + 'LessThan' => LessThan::class, + 'notempty' => NotEmpty::class, + 'notEmpty' => NotEmpty::class, + 'NotEmpty' => NotEmpty::class, + 'phonenumber' => I18nValidator\PhoneNumber::class, + 'phoneNumber' => I18nValidator\PhoneNumber::class, + 'PhoneNumber' => I18nValidator\PhoneNumber::class, + 'postcode' => I18nValidator\PostCode::class, + 'postCode' => I18nValidator\PostCode::class, + 'PostCode' => I18nValidator\PostCode::class, + 'regex' => Regex::class, + 'Regex' => Regex::class, + 'sitemapchangefreq' => Sitemap\Changefreq::class, + 'sitemapChangefreq' => Sitemap\Changefreq::class, + 'SitemapChangefreq' => Sitemap\Changefreq::class, + 'sitemaplastmod' => Sitemap\Lastmod::class, + 'sitemapLastmod' => Sitemap\Lastmod::class, + 'SitemapLastmod' => Sitemap\Lastmod::class, + 'sitemaploc' => Sitemap\Loc::class, + 'sitemapLoc' => Sitemap\Loc::class, + 'SitemapLoc' => Sitemap\Loc::class, + 'sitemappriority' => Sitemap\Priority::class, + 'sitemapPriority' => Sitemap\Priority::class, + 'SitemapPriority' => Sitemap\Priority::class, + 'stringlength' => StringLength::class, + 'stringLength' => StringLength::class, + 'StringLength' => StringLength::class, + 'step' => Step::class, + 'Step' => Step::class, + 'timezone' => Timezone::class, + 'Timezone' => Timezone::class, + 'uri' => Uri::class, + 'Uri' => Uri::class, + 'uuid' => Uuid::class, + 'Uuid' => Uuid::class, + + // Legacy Zend Framework aliases + Alnum::class => I18nValidator\Alnum::class, + Alpha::class => I18nValidator\Alpha::class, + \Zend\Validator\Barcode::class => Barcode::class, + \Zend\Validator\Between::class => Between::class, + \Zend\Validator\Bitwise::class => Bitwise::class, + \Zend\Validator\Callback::class => Callback::class, + \Zend\Validator\CreditCard::class => CreditCard::class, + \Zend\Validator\Csrf::class => Csrf::class, + \Zend\Validator\DateStep::class => DateStep::class, + \Zend\Validator\Date::class => Date::class, + DateTime::class => I18nValidator\DateTime::class, + NoRecordExists::class => Db\NoRecordExists::class, + RecordExists::class => Db\RecordExists::class, + \Zend\Validator\Digits::class => Digits::class, + \Zend\Validator\EmailAddress::class => EmailAddress::class, + \Zend\Validator\Explode::class => Explode::class, + Count::class => File\Count::class, + Crc32::class => File\Crc32::class, + ExcludeExtension::class => File\ExcludeExtension::class, + ExcludeMimeType::class => File\ExcludeMimeType::class, + Exists::class => File\Exists::class, + Extension::class => File\Extension::class, + FilesSize::class => File\FilesSize::class, + Hash::class => File\Hash::class, + ImageSize::class => File\ImageSize::class, + IsCompressed::class => File\IsCompressed::class, + IsImage::class => File\IsImage::class, + Md5::class => File\Md5::class, + MimeType::class => File\MimeType::class, + NotExists::class => File\NotExists::class, + Sha1::class => File\Sha1::class, + Size::class => File\Size::class, + Upload::class => File\Upload::class, + UploadFile::class => File\UploadFile::class, + WordCount::class => File\WordCount::class, + IsFloat::class => I18nValidator\IsFloat::class, + \Zend\Validator\GpsPoint::class => GpsPoint::class, + \Zend\Validator\GreaterThan::class => GreaterThan::class, + \Zend\Validator\Hex::class => Hex::class, + \Zend\Validator\Hostname::class => Hostname::class, + \Zend\Validator\Iban::class => Iban::class, + \Zend\Validator\Identical::class => Identical::class, + \Zend\Validator\InArray::class => InArray::class, + IsInt::class => I18nValidator\IsInt::class, + \Zend\Validator\Ip::class => Ip::class, + \Zend\Validator\Isbn::class => Isbn::class, + \Zend\Validator\IsInstanceOf::class => IsInstanceOf::class, + \Zend\Validator\LessThan::class => LessThan::class, + \Zend\Validator\NotEmpty::class => NotEmpty::class, + PhoneNumber::class => I18nValidator\PhoneNumber::class, + PostCode::class => I18nValidator\PostCode::class, + \Zend\Validator\Regex::class => Regex::class, + Changefreq::class => Sitemap\Changefreq::class, + Lastmod::class => Sitemap\Lastmod::class, + Loc::class => Sitemap\Loc::class, + Priority::class => Sitemap\Priority::class, + \Zend\Validator\StringLength::class => StringLength::class, + \Zend\Validator\Step::class => Step::class, + \Zend\Validator\Timezone::class => Timezone::class, + \Zend\Validator\Uri::class => Uri::class, + \Zend\Validator\Uuid::class => Uuid::class, + + // v2 normalized FQCNs + 'zendvalidatorbarcode' => Barcode::class, + 'zendvalidatorbetween' => Between::class, + 'zendvalidatorbitwise' => Bitwise::class, + 'zendvalidatorcallback' => Callback::class, + 'zendvalidatorcreditcard' => CreditCard::class, + 'zendvalidatorcsrf' => Csrf::class, + 'zendvalidatordatestep' => DateStep::class, + 'zendvalidatordate' => Date::class, + 'zendvalidatordbnorecordexists' => Db\NoRecordExists::class, + 'zendvalidatordbrecordexists' => Db\RecordExists::class, + 'zendvalidatordigits' => Digits::class, + 'zendvalidatoremailaddress' => EmailAddress::class, + 'zendvalidatorexplode' => Explode::class, + 'zendvalidatorfilecount' => File\Count::class, + 'zendvalidatorfilecrc32' => File\Crc32::class, + 'zendvalidatorfileexcludeextension' => File\ExcludeExtension::class, + 'zendvalidatorfileexcludemimetype' => File\ExcludeMimeType::class, + 'zendvalidatorfileexists' => File\Exists::class, + 'zendvalidatorfileextension' => File\Extension::class, + 'zendvalidatorfilefilessize' => File\FilesSize::class, + 'zendvalidatorfilehash' => File\Hash::class, + 'zendvalidatorfileimagesize' => File\ImageSize::class, + 'zendvalidatorfileiscompressed' => File\IsCompressed::class, + 'zendvalidatorfileisimage' => File\IsImage::class, + 'zendvalidatorfilemd5' => File\Md5::class, + 'zendvalidatorfilemimetype' => File\MimeType::class, + 'zendvalidatorfilenotexists' => File\NotExists::class, + 'zendvalidatorfilesha1' => File\Sha1::class, + 'zendvalidatorfilesize' => File\Size::class, + 'zendvalidatorfileupload' => File\Upload::class, + 'zendvalidatorfileuploadfile' => File\UploadFile::class, + 'zendvalidatorfilewordcount' => File\WordCount::class, + 'zendvalidatorgpspoint' => GpsPoint::class, + 'zendvalidatorgreaterthan' => GreaterThan::class, + 'zendvalidatorhex' => Hex::class, + 'zendvalidatorhostname' => Hostname::class, + 'zendi18nvalidatoralnum' => I18nValidator\Alnum::class, + 'zendi18nvalidatoralpha' => I18nValidator\Alpha::class, + 'zendi18nvalidatordatetime' => I18nValidator\DateTime::class, + 'zendi18nvalidatorisfloat' => I18nValidator\IsFloat::class, + 'zendi18nvalidatorisint' => I18nValidator\IsInt::class, + 'zendi18nvalidatorphonenumber' => I18nValidator\PhoneNumber::class, + 'zendi18nvalidatorpostcode' => I18nValidator\PostCode::class, + 'zendvalidatoriban' => Iban::class, + 'zendvalidatoridentical' => Identical::class, + 'zendvalidatorinarray' => InArray::class, + 'zendvalidatorip' => Ip::class, + 'zendvalidatorisbn' => Isbn::class, + 'zendvalidatorisinstanceof' => IsInstanceOf::class, + 'zendvalidatorlessthan' => LessThan::class, + 'zendvalidatornotempty' => NotEmpty::class, + 'zendvalidatorregex' => Regex::class, + 'zendvalidatorsitemapchangefreq' => Sitemap\Changefreq::class, + 'zendvalidatorsitemaplastmod' => Sitemap\Lastmod::class, + 'zendvalidatorsitemaploc' => Sitemap\Loc::class, + 'zendvalidatorsitemappriority' => Sitemap\Priority::class, + 'zendvalidatorstringlength' => StringLength::class, + 'zendvalidatorstep' => Step::class, + 'zendvalidatortimezone' => Timezone::class, + 'zendvalidatoruri' => Uri::class, + 'zendvalidatoruuid' => Uuid::class, + ]; + + /** + * Default set of factories + * + * @var FactoriesConfigurationType + */ + protected $factories = [ + I18nValidator\Alnum::class => InvokableFactory::class, + I18nValidator\Alpha::class => InvokableFactory::class, + Barcode::class => InvokableFactory::class, + Between::class => InvokableFactory::class, + Bitwise::class => InvokableFactory::class, + BusinessIdentifierCode::class => InvokableFactory::class, + Callback::class => InvokableFactory::class, + CreditCard::class => InvokableFactory::class, + Csrf::class => InvokableFactory::class, + DateStep::class => InvokableFactory::class, + Date::class => InvokableFactory::class, + I18nValidator\DateTime::class => InvokableFactory::class, + Db\NoRecordExists::class => InvokableFactory::class, + Db\RecordExists::class => InvokableFactory::class, + Digits::class => InvokableFactory::class, + EmailAddress::class => InvokableFactory::class, + Explode::class => InvokableFactory::class, + File\Count::class => InvokableFactory::class, + File\Crc32::class => InvokableFactory::class, + File\ExcludeExtension::class => InvokableFactory::class, + File\ExcludeMimeType::class => InvokableFactory::class, + File\Exists::class => InvokableFactory::class, + File\Extension::class => InvokableFactory::class, + File\FilesSize::class => InvokableFactory::class, + File\Hash::class => InvokableFactory::class, + File\ImageSize::class => InvokableFactory::class, + File\IsCompressed::class => InvokableFactory::class, + File\IsImage::class => InvokableFactory::class, + File\Md5::class => InvokableFactory::class, + File\MimeType::class => InvokableFactory::class, + File\NotExists::class => InvokableFactory::class, + File\Sha1::class => InvokableFactory::class, + File\Size::class => InvokableFactory::class, + File\Upload::class => InvokableFactory::class, + File\UploadFile::class => InvokableFactory::class, + File\WordCount::class => InvokableFactory::class, + I18nValidator\IsFloat::class => InvokableFactory::class, + GpsPoint::class => InvokableFactory::class, + GreaterThan::class => InvokableFactory::class, + Hex::class => InvokableFactory::class, + Hostname::class => InvokableFactory::class, + Iban::class => InvokableFactory::class, + Identical::class => InvokableFactory::class, + InArray::class => InvokableFactory::class, + I18nValidator\IsInt::class => InvokableFactory::class, + Ip::class => InvokableFactory::class, + Isbn::class => InvokableFactory::class, + IsCountable::class => InvokableFactory::class, + IsInstanceOf::class => InvokableFactory::class, + LessThan::class => InvokableFactory::class, + NotEmpty::class => InvokableFactory::class, + I18nValidator\PhoneNumber::class => InvokableFactory::class, + I18nValidator\PostCode::class => InvokableFactory::class, + Regex::class => InvokableFactory::class, + Sitemap\Changefreq::class => InvokableFactory::class, + Sitemap\Lastmod::class => InvokableFactory::class, + Sitemap\Loc::class => InvokableFactory::class, + Sitemap\Priority::class => InvokableFactory::class, + StringLength::class => InvokableFactory::class, + Step::class => InvokableFactory::class, + Timezone::class => InvokableFactory::class, + Uri::class => InvokableFactory::class, + Uuid::class => InvokableFactory::class, + + // v2 canonical FQCNs + 'laminasvalidatorbarcodecode25interleaved' => InvokableFactory::class, + 'laminasvalidatorbarcodecode25' => InvokableFactory::class, + 'laminasvalidatorbarcodecode39ext' => InvokableFactory::class, + 'laminasvalidatorbarcodecode39' => InvokableFactory::class, + 'laminasvalidatorbarcodecode93ext' => InvokableFactory::class, + 'laminasvalidatorbarcodecode93' => InvokableFactory::class, + 'laminasvalidatorbarcodeean12' => InvokableFactory::class, + 'laminasvalidatorbarcodeean13' => InvokableFactory::class, + 'laminasvalidatorbarcodeean14' => InvokableFactory::class, + 'laminasvalidatorbarcodeean18' => InvokableFactory::class, + 'laminasvalidatorbarcodeean2' => InvokableFactory::class, + 'laminasvalidatorbarcodeean5' => InvokableFactory::class, + 'laminasvalidatorbarcodeean8' => InvokableFactory::class, + 'laminasvalidatorbarcodegtin12' => InvokableFactory::class, + 'laminasvalidatorbarcodegtin13' => InvokableFactory::class, + 'laminasvalidatorbarcodegtin14' => InvokableFactory::class, + 'laminasvalidatorbarcodeidentcode' => InvokableFactory::class, + 'laminasvalidatorbarcodeintelligentmail' => InvokableFactory::class, + 'laminasvalidatorbarcodeissn' => InvokableFactory::class, + 'laminasvalidatorbarcodeitf14' => InvokableFactory::class, + 'laminasvalidatorbarcodeleitcode' => InvokableFactory::class, + 'laminasvalidatorbarcodeplanet' => InvokableFactory::class, + 'laminasvalidatorbarcodepostnet' => InvokableFactory::class, + 'laminasvalidatorbarcoderoyalmail' => InvokableFactory::class, + 'laminasvalidatorbarcodesscc' => InvokableFactory::class, + 'laminasvalidatorbarcodeupca' => InvokableFactory::class, + 'laminasvalidatorbarcodeupce' => InvokableFactory::class, + 'laminasvalidatorbarcode' => InvokableFactory::class, + 'laminasvalidatorbetween' => InvokableFactory::class, + 'laminasvalidatorbitwise' => InvokableFactory::class, + 'laminasvalidatorcallback' => InvokableFactory::class, + 'laminasvalidatorcreditcard' => InvokableFactory::class, + 'laminasvalidatorcsrf' => InvokableFactory::class, + 'laminasvalidatordatestep' => InvokableFactory::class, + 'laminasvalidatordate' => InvokableFactory::class, + 'laminasvalidatordbnorecordexists' => InvokableFactory::class, + 'laminasvalidatordbrecordexists' => InvokableFactory::class, + 'laminasvalidatordigits' => InvokableFactory::class, + 'laminasvalidatoremailaddress' => InvokableFactory::class, + 'laminasvalidatorexplode' => InvokableFactory::class, + 'laminasvalidatorfilecount' => InvokableFactory::class, + 'laminasvalidatorfilecrc32' => InvokableFactory::class, + 'laminasvalidatorfileexcludeextension' => InvokableFactory::class, + 'laminasvalidatorfileexcludemimetype' => InvokableFactory::class, + 'laminasvalidatorfileexists' => InvokableFactory::class, + 'laminasvalidatorfileextension' => InvokableFactory::class, + 'laminasvalidatorfilefilessize' => InvokableFactory::class, + 'laminasvalidatorfilehash' => InvokableFactory::class, + 'laminasvalidatorfileimagesize' => InvokableFactory::class, + 'laminasvalidatorfileiscompressed' => InvokableFactory::class, + 'laminasvalidatorfileisimage' => InvokableFactory::class, + 'laminasvalidatorfilemd5' => InvokableFactory::class, + 'laminasvalidatorfilemimetype' => InvokableFactory::class, + 'laminasvalidatorfilenotexists' => InvokableFactory::class, + 'laminasvalidatorfilesha1' => InvokableFactory::class, + 'laminasvalidatorfilesize' => InvokableFactory::class, + 'laminasvalidatorfileupload' => InvokableFactory::class, + 'laminasvalidatorfileuploadfile' => InvokableFactory::class, + 'laminasvalidatorfilewordcount' => InvokableFactory::class, + 'laminasvalidatorgpspoint' => InvokableFactory::class, + 'laminasvalidatorgreaterthan' => InvokableFactory::class, + 'laminasvalidatorhex' => InvokableFactory::class, + 'laminasvalidatorhostname' => InvokableFactory::class, + 'laminasi18nvalidatoralnum' => InvokableFactory::class, + 'laminasi18nvalidatoralpha' => InvokableFactory::class, + 'laminasi18nvalidatordatetime' => InvokableFactory::class, + 'laminasi18nvalidatorisfloat' => InvokableFactory::class, + 'laminasi18nvalidatorisint' => InvokableFactory::class, + 'laminasi18nvalidatorphonenumber' => InvokableFactory::class, + 'laminasi18nvalidatorpostcode' => InvokableFactory::class, + 'laminasvalidatoriban' => InvokableFactory::class, + 'laminasvalidatoridentical' => InvokableFactory::class, + 'laminasvalidatorinarray' => InvokableFactory::class, + 'laminasvalidatorip' => InvokableFactory::class, + 'laminasvalidatorisbn' => InvokableFactory::class, + 'laminasvalidatoriscountable' => InvokableFactory::class, + 'laminasvalidatorisinstanceof' => InvokableFactory::class, + 'laminasvalidatorlessthan' => InvokableFactory::class, + 'laminasvalidatornotempty' => InvokableFactory::class, + 'laminasvalidatorregex' => InvokableFactory::class, + 'laminasvalidatorsitemapchangefreq' => InvokableFactory::class, + 'laminasvalidatorsitemaplastmod' => InvokableFactory::class, + 'laminasvalidatorsitemaploc' => InvokableFactory::class, + 'laminasvalidatorsitemappriority' => InvokableFactory::class, + 'laminasvalidatorstringlength' => InvokableFactory::class, + 'laminasvalidatorstep' => InvokableFactory::class, + 'laminasvalidatortimezone' => InvokableFactory::class, + 'laminasvalidatoruri' => InvokableFactory::class, + 'laminasvalidatoruuid' => InvokableFactory::class, + ]; + + /** + * Whether or not to share by default; default to false (v2) + * + * @var bool + */ + protected $shareByDefault = false; + + /** + * Whether or not to share by default; default to false (v3) + * + * @var bool + */ + protected $sharedByDefault = false; + + /** + * Default instance type + * + * @var string + */ + protected $instanceOf = ValidatorInterface::class; + + /** + * Constructor + * + * After invoking parent constructor, add an initializer to inject the + * attached translator, if any, to the currently requested helper. + * + * {@inheritDoc} + * + * @param ServiceManagerConfiguration $v3config + */ + public function __construct($configOrContainerInstance = null, array $v3config = []) + { + parent::__construct($configOrContainerInstance, $v3config); + + $this->addInitializer([$this, 'injectTranslator']); + $this->addInitializer([$this, 'injectValidatorPluginManager']); + } + + /** + * Validate plugin instance + * + * {@inheritDoc} + */ + public function validate($instance) + { + if (! $instance instanceof $this->instanceOf) { + throw new InvalidServiceException(sprintf( + '%s expects only to create instances of %s; %s is invalid', + static::class, + $this->instanceOf, + is_object($instance) ? get_class($instance) : gettype($instance) + )); + } + } + + /** + * For v2 compatibility: validate plugin instance. + * + * Proxies to `validate()`. + * + * @param mixed $plugin + * @return void + * @throws Exception\RuntimeException + */ + public function validatePlugin($plugin) + { + try { + $this->validate($plugin); + } catch (InvalidServiceException $e) { + throw new Exception\RuntimeException(sprintf( + 'Plugin of type %s is invalid; must implement %s', + is_object($plugin) ? get_class($plugin) : gettype($plugin), + ValidatorInterface::class + ), $e->getCode(), $e); + } + } + + /** + * Inject a validator instance with the registered translator + * + * @param ContainerInterface|object $first + * @param ContainerInterface|object $second + * @return void + */ + public function injectTranslator($first, $second) + { + if ($first instanceof ContainerInterface) { + $container = $first; + $validator = $second; + } else { + $container = $second; + $validator = $first; + } + + // V2 means we pull it from the parent container + if ($container === $this && method_exists($container, 'getServiceLocator') && $container->getServiceLocator()) { + $container = $container->getServiceLocator(); + } + + if ($validator instanceof Translator\TranslatorAwareInterface) { + if ($container && $container->has('MvcTranslator')) { + $validator->setTranslator($container->get('MvcTranslator')); + } + } + } + + /** + * Inject a validator plugin manager + * + * @param ContainerInterface|object $first + * @param ContainerInterface|object $second + * @return void + */ + public function injectValidatorPluginManager($first, $second) + { + if ($first instanceof ContainerInterface) { + $validator = $second; + } else { + $validator = $first; + } + if ($validator instanceof ValidatorPluginManagerAwareInterface) { + $validator->setValidatorPluginManager($this); + } + } +} diff --git a/lib/laminas/laminas-validator/src/ValidatorPluginManagerAwareInterface.php b/lib/laminas/laminas-validator/src/ValidatorPluginManagerAwareInterface.php new file mode 100644 index 0000000000..d30cc7e794 --- /dev/null +++ b/lib/laminas/laminas-validator/src/ValidatorPluginManagerAwareInterface.php @@ -0,0 +1,18 @@ +has('ServiceListener')) { + return $pluginManager; + } + + // If we do not have a config service, nothing more to do + if (! $container->has('config')) { + return $pluginManager; + } + + $config = $container->get('config'); + + // If we do not have validators configuration, nothing more to do + if (! isset($config['validators']) || ! is_array($config['validators'])) { + return $pluginManager; + } + + // Wire service configuration for validators + (new Config($config['validators']))->configureServiceManager($pluginManager); + + return $pluginManager; + } + + /** + * {@inheritDoc} + * + * @param string|null $name + * @param string|null $requestedName + * @return ValidatorPluginManager + */ + public function createService(ServiceLocatorInterface $container, $name = null, $requestedName = null) + { + return $this($container, $requestedName ?: ValidatorPluginManager::class, $this->creationOptions); + } + + /** + * laminas-servicemanager v2 support for invocation options. + * + * @param ServiceManagerConfiguration $options + * @return void + */ + public function setCreationOptions(array $options) + { + $this->creationOptions = $options; + } +} diff --git a/lib/laminas/laminas-validator/src/ValidatorProviderInterface.php b/lib/laminas/laminas-validator/src/ValidatorProviderInterface.php new file mode 100644 index 0000000000..fc6fbb2981 --- /dev/null +++ b/lib/laminas/laminas-validator/src/ValidatorProviderInterface.php @@ -0,0 +1,24 @@ + + +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/lib/league/oauth2-client/README.md b/lib/league/oauth2-client/README.md new file mode 100644 index 0000000000..f35d53e8a9 --- /dev/null +++ b/lib/league/oauth2-client/README.md @@ -0,0 +1,58 @@ +# OAuth 2.0 Client + +This package provides a base for integrating with [OAuth 2.0](http://oauth.net/2/) service providers. + +[![Gitter Chat](https://img.shields.io/badge/gitter-join_chat-brightgreen.svg?style=flat-square)](https://gitter.im/thephpleague/oauth2-client) +[![Source Code](https://img.shields.io/badge/source-thephpleague/oauth2--client-blue.svg?style=flat-square)](https://github.com/thephpleague/oauth2-client) +[![Latest Version](https://img.shields.io/github/release/thephpleague/oauth2-client.svg?style=flat-square)](https://github.com/thephpleague/oauth2-client/releases) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://github.com/thephpleague/oauth2-client/blob/master/LICENSE) +[![Build Status](https://img.shields.io/github/workflow/status/thephpleague/oauth2-client/CI?label=CI&logo=github&style=flat-square)](https://github.com/thephpleague/oauth2-client/actions?query=workflow%3ACI) +[![Codecov Code Coverage](https://img.shields.io/codecov/c/gh/thephpleague/oauth2-client?label=codecov&logo=codecov&style=flat-square)](https://codecov.io/gh/thephpleague/oauth2-client) +[![Total Downloads](https://img.shields.io/packagist/dt/league/oauth2-client.svg?style=flat-square)](https://packagist.org/packages/league/oauth2-client) + +--- + +The OAuth 2.0 login flow, seen commonly around the web in the form of "Connect with Facebook/Google/etc." buttons, is a common integration added to web applications, but it can be tricky and tedious to do right. To help, we've created the `league/oauth2-client` package, which provides a base for integrating with various OAuth 2.0 providers, without overburdening your application with the concerns of [RFC 6749](http://tools.ietf.org/html/rfc6749). + +This OAuth 2.0 client library will work with any OAuth 2.0 provider that conforms to the OAuth 2.0 Authorization Framework. Out-of-the-box, we provide a `GenericProvider` class to connect to any service provider that uses [Bearer tokens](http://tools.ietf.org/html/rfc6750). See our [basic usage guide](https://oauth2-client.thephpleague.com/usage/) for examples using `GenericProvider`. + +Many service providers provide additional functionality above and beyond the OAuth 2.0 specification. For this reason, you may extend and wrap this library to support additional behavior. There are already many [official](https://oauth2-client.thephpleague.com/providers/league/) and [third-party](https://oauth2-client.thephpleague.com/providers/thirdparty/) provider clients available (e.g., Facebook, GitHub, Google, Instagram, LinkedIn, etc.). If your provider isn't in the list, feel free to add it. + +This package is compliant with [PSR-1][], [PSR-2][], [PSR-4][], and [PSR-7][]. If you notice compliance oversights, please send a patch via pull request. If you're interested in contributing to this library, please take a look at our [contributing guidelines](https://github.com/thephpleague/oauth2-client/blob/master/CONTRIBUTING.md). + +## Requirements + +We support the following versions of PHP: + +* 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 + +We provide a list of [official PHP League provider clients](https://oauth2-client.thephpleague.com/providers/league/), as well as [third-party provider clients](https://oauth2-client.thephpleague.com/providers/thirdparty/). + +To build your own provider client, please refer to "[Implementing a Provider Client](https://oauth2-client.thephpleague.com/providers/implementing/)." + +## Usage + +For usage and code examples, check out our [basic usage guide](https://oauth2-client.thephpleague.com/usage/). + +## Contributing + +Please see [our contributing guidelines](https://github.com/thephpleague/oauth2-client/blob/master/CONTRIBUTING.md) for details. + +## License + +The MIT License (MIT). Please see [LICENSE](https://github.com/thephpleague/oauth2-client/blob/master/LICENSE) for more information. + + +[PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md +[PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md +[PSR-4]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md +[PSR-7]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-7-http-message.md diff --git a/lib/league/oauth2-client/composer.json b/lib/league/oauth2-client/composer.json new file mode 100644 index 0000000000..59201f48fb --- /dev/null +++ b/lib/league/oauth2-client/composer.json @@ -0,0 +1,58 @@ +{ + "name": "league/oauth2-client", + "description": "OAuth 2.0 Client Library", + "license": "MIT", + "config": { + "sort-packages": true + }, + "require": { + "php": "^5.6 || ^7.0 || ^8.0", + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "paragonie/random_compat": "^1 || ^2 || ^9.99" + }, + "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" + }, + "keywords": [ + "oauth", + "oauth2", + "authorization", + "authentication", + "idp", + "identity", + "sso", + "single sign on" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Woody Gilk", + "homepage": "https://github.com/shadowhand", + "role": "Contributor" + } + + ], + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "League\\OAuth2\\Client\\Test\\": "test/src/" + } + }, + "extra": { + "branch-alias": { + "dev-2.x": "2.0.x-dev" + } + } +} diff --git a/lib/league/oauth2-client/src/Grant/AbstractGrant.php b/lib/league/oauth2-client/src/Grant/AbstractGrant.php new file mode 100644 index 0000000000..2c0244ba3d --- /dev/null +++ b/lib/league/oauth2-client/src/Grant/AbstractGrant.php @@ -0,0 +1,80 @@ + + * @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\Grant; + +use League\OAuth2\Client\Tool\RequiredParameterTrait; + +/** + * Represents a type of authorization grant. + * + * An authorization grant is a credential representing the resource + * owner's authorization (to access its protected resources) used by the + * client to obtain an access token. OAuth 2.0 defines four + * grant types -- authorization code, implicit, resource owner password + * credentials, and client credentials -- as well as an extensibility + * mechanism for defining additional types. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3 Authorization Grant (RFC 6749, §1.3) + */ +abstract class AbstractGrant +{ + use RequiredParameterTrait; + + /** + * Returns the name of this grant, eg. 'grant_name', which is used as the + * grant type when encoding URL query parameters. + * + * @return string + */ + abstract protected function getName(); + + /** + * Returns a list of all required request parameters. + * + * @return array + */ + abstract protected function getRequiredRequestParameters(); + + /** + * Returns this grant's name as its string representation. This allows for + * string interpolation when building URL query parameters. + * + * @return string + */ + public function __toString() + { + return $this->getName(); + } + + /** + * Prepares an access token request's parameters by checking that all + * required parameters are set, then merging with any given defaults. + * + * @param array $defaults + * @param array $options + * @return array + */ + public function prepareRequestParameters(array $defaults, array $options) + { + $defaults['grant_type'] = $this->getName(); + + $required = $this->getRequiredRequestParameters(); + $provided = array_merge($defaults, $options); + + $this->checkRequiredParameters($required, $provided); + + return $provided; + } +} diff --git a/lib/league/oauth2-client/src/Grant/AuthorizationCode.php b/lib/league/oauth2-client/src/Grant/AuthorizationCode.php new file mode 100644 index 0000000000..c49460c02d --- /dev/null +++ b/lib/league/oauth2-client/src/Grant/AuthorizationCode.php @@ -0,0 +1,41 @@ + + * @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\Grant; + +/** + * Represents an authorization code grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3.1 Authorization Code (RFC 6749, §1.3.1) + */ +class AuthorizationCode extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'authorization_code'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return [ + 'code', + ]; + } +} diff --git a/lib/league/oauth2-client/src/Grant/ClientCredentials.php b/lib/league/oauth2-client/src/Grant/ClientCredentials.php new file mode 100644 index 0000000000..dc78c4fdab --- /dev/null +++ b/lib/league/oauth2-client/src/Grant/ClientCredentials.php @@ -0,0 +1,39 @@ + + * @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\Grant; + +/** + * Represents a client credentials grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3.4 Client Credentials (RFC 6749, §1.3.4) + */ +class ClientCredentials extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'client_credentials'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return []; + } +} diff --git a/lib/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php b/lib/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php new file mode 100644 index 0000000000..c3c4e677b4 --- /dev/null +++ b/lib/league/oauth2-client/src/Grant/Exception/InvalidGrantException.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\Grant\Exception; + +use InvalidArgumentException; + +/** + * Exception thrown if the grant does not extend from AbstractGrant. + * + * @see League\OAuth2\Client\Grant\AbstractGrant + */ +class InvalidGrantException extends InvalidArgumentException +{ +} diff --git a/lib/league/oauth2-client/src/Grant/GrantFactory.php b/lib/league/oauth2-client/src/Grant/GrantFactory.php new file mode 100644 index 0000000000..71990e83db --- /dev/null +++ b/lib/league/oauth2-client/src/Grant/GrantFactory.php @@ -0,0 +1,104 @@ + + * @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\Grant; + +use League\OAuth2\Client\Grant\Exception\InvalidGrantException; + +/** + * Represents a factory used when retrieving an authorization grant type. + */ +class GrantFactory +{ + /** + * @var array + */ + protected $registry = []; + + /** + * Defines a grant singleton in the registry. + * + * @param string $name + * @param AbstractGrant $grant + * @return self + */ + public function setGrant($name, AbstractGrant $grant) + { + $this->registry[$name] = $grant; + + return $this; + } + + /** + * Returns a grant singleton by name. + * + * If the grant has not be registered, a default grant will be loaded. + * + * @param string $name + * @return AbstractGrant + */ + public function getGrant($name) + { + if (empty($this->registry[$name])) { + $this->registerDefaultGrant($name); + } + + return $this->registry[$name]; + } + + /** + * Registers a default grant singleton by name. + * + * @param string $name + * @return self + */ + protected function registerDefaultGrant($name) + { + // PascalCase the grant. E.g: 'authorization_code' becomes 'AuthorizationCode' + $class = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $name))); + $class = 'League\\OAuth2\\Client\\Grant\\' . $class; + + $this->checkGrant($class); + + return $this->setGrant($name, new $class); + } + + /** + * Determines if a variable is a valid grant. + * + * @param mixed $class + * @return boolean + */ + public function isGrant($class) + { + return is_subclass_of($class, AbstractGrant::class); + } + + /** + * Checks if a variable is a valid grant. + * + * @throws InvalidGrantException + * @param mixed $class + * @return void + */ + public function checkGrant($class) + { + if (!$this->isGrant($class)) { + throw new InvalidGrantException(sprintf( + 'Grant "%s" must extend AbstractGrant', + is_object($class) ? get_class($class) : $class + )); + } + } +} diff --git a/lib/league/oauth2-client/src/Grant/Password.php b/lib/league/oauth2-client/src/Grant/Password.php new file mode 100644 index 0000000000..6543b2ebd1 --- /dev/null +++ b/lib/league/oauth2-client/src/Grant/Password.php @@ -0,0 +1,42 @@ + + * @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\Grant; + +/** + * Represents a resource owner password credentials grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3.3 Resource Owner Password Credentials (RFC 6749, §1.3.3) + */ +class Password extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'password'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return [ + 'username', + 'password', + ]; + } +} diff --git a/lib/league/oauth2-client/src/Grant/RefreshToken.php b/lib/league/oauth2-client/src/Grant/RefreshToken.php new file mode 100644 index 0000000000..8192182301 --- /dev/null +++ b/lib/league/oauth2-client/src/Grant/RefreshToken.php @@ -0,0 +1,41 @@ + + * @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\Grant; + +/** + * Represents a refresh token grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-6 Refreshing an Access Token (RFC 6749, §6) + */ +class RefreshToken extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'refresh_token'; + } + + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return [ + 'refresh_token', + ]; + } +} diff --git a/lib/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php b/lib/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php new file mode 100644 index 0000000000..3da4065682 --- /dev/null +++ b/lib/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php @@ -0,0 +1,42 @@ + + * @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\OptionProvider; + +use InvalidArgumentException; + +/** + * Add http basic auth into access token request options + * @link https://tools.ietf.org/html/rfc6749#section-2.3.1 + */ +class HttpBasicAuthOptionProvider extends PostAuthOptionProvider +{ + /** + * @inheritdoc + */ + public function getAccessTokenOptions($method, array $params) + { + if (empty($params['client_id']) || empty($params['client_secret'])) { + throw new InvalidArgumentException('clientId and clientSecret are required for http basic auth'); + } + + $encodedCredentials = base64_encode(sprintf('%s:%s', $params['client_id'], $params['client_secret'])); + unset($params['client_id'], $params['client_secret']); + + $options = parent::getAccessTokenOptions($method, $params); + $options['headers']['Authorization'] = 'Basic ' . $encodedCredentials; + + return $options; + } +} diff --git a/lib/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php b/lib/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php new file mode 100644 index 0000000000..1126d25aa0 --- /dev/null +++ b/lib/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php @@ -0,0 +1,30 @@ + + * @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\OptionProvider; + +/** + * Interface for access token options provider + */ +interface OptionProviderInterface +{ + /** + * Builds request options used for requesting an access token. + * + * @param string $method + * @param array $params + * @return array + */ + public function getAccessTokenOptions($method, array $params); +} diff --git a/lib/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php b/lib/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php new file mode 100644 index 0000000000..12d920ecf9 --- /dev/null +++ b/lib/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php @@ -0,0 +1,51 @@ + + * @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\OptionProvider; + +use League\OAuth2\Client\Provider\AbstractProvider; +use League\OAuth2\Client\Tool\QueryBuilderTrait; + +/** + * Provide options for access token + */ +class PostAuthOptionProvider implements OptionProviderInterface +{ + use QueryBuilderTrait; + + /** + * @inheritdoc + */ + public function getAccessTokenOptions($method, array $params) + { + $options = ['headers' => ['content-type' => 'application/x-www-form-urlencoded']]; + + if ($method === AbstractProvider::METHOD_POST) { + $options['body'] = $this->getAccessTokenBody($params); + } + + return $options; + } + + /** + * Returns the request body for requesting an access token. + * + * @param array $params + * @return string + */ + protected function getAccessTokenBody(array $params) + { + return $this->buildQueryString($params); + } +} diff --git a/lib/league/oauth2-client/src/Provider/AbstractProvider.php b/lib/league/oauth2-client/src/Provider/AbstractProvider.php new file mode 100644 index 0000000000..d1679998ca --- /dev/null +++ b/lib/league/oauth2-client/src/Provider/AbstractProvider.php @@ -0,0 +1,843 @@ + + * @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\Provider; + +use GuzzleHttp\Client as HttpClient; +use GuzzleHttp\ClientInterface as HttpClientInterface; +use GuzzleHttp\Exception\BadResponseException; +use League\OAuth2\Client\Grant\AbstractGrant; +use League\OAuth2\Client\Grant\GrantFactory; +use League\OAuth2\Client\OptionProvider\OptionProviderInterface; +use League\OAuth2\Client\OptionProvider\PostAuthOptionProvider; +use League\OAuth2\Client\Provider\Exception\IdentityProviderException; +use League\OAuth2\Client\Token\AccessToken; +use League\OAuth2\Client\Token\AccessTokenInterface; +use League\OAuth2\Client\Tool\ArrayAccessorTrait; +use League\OAuth2\Client\Tool\GuardedPropertyTrait; +use League\OAuth2\Client\Tool\QueryBuilderTrait; +use League\OAuth2\Client\Tool\RequestFactory; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use UnexpectedValueException; + +/** + * Represents a service provider (authorization server). + * + * @link http://tools.ietf.org/html/rfc6749#section-1.1 Roles (RFC 6749, §1.1) + */ +abstract class AbstractProvider +{ + use ArrayAccessorTrait; + use GuardedPropertyTrait; + use QueryBuilderTrait; + + /** + * @var string Key used in a token response to identify the resource owner. + */ + const ACCESS_TOKEN_RESOURCE_OWNER_ID = null; + + /** + * @var string HTTP method used to fetch access tokens. + */ + const METHOD_GET = 'GET'; + + /** + * @var string HTTP method used to fetch access tokens. + */ + const METHOD_POST = 'POST'; + + /** + * @var string + */ + protected $clientId; + + /** + * @var string + */ + protected $clientSecret; + + /** + * @var string + */ + protected $redirectUri; + + /** + * @var string + */ + protected $state; + + /** + * @var GrantFactory + */ + protected $grantFactory; + + /** + * @var RequestFactory + */ + protected $requestFactory; + + /** + * @var HttpClientInterface + */ + protected $httpClient; + + /** + * @var OptionProviderInterface + */ + protected $optionProvider; + + /** + * Constructs an OAuth 2.0 service 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 + * override this provider's default behavior. Collaborators include + * `grantFactory`, `requestFactory`, and `httpClient`. + * Individual providers may introduce more collaborators, as needed. + */ + public function __construct(array $options = [], array $collaborators = []) + { + // We'll let the GuardedPropertyTrait handle mass assignment of incoming + // options, skipping any blacklisted properties defined in the provider + $this->fillProperties($options); + + if (empty($collaborators['grantFactory'])) { + $collaborators['grantFactory'] = new GrantFactory(); + } + $this->setGrantFactory($collaborators['grantFactory']); + + if (empty($collaborators['requestFactory'])) { + $collaborators['requestFactory'] = new RequestFactory(); + } + $this->setRequestFactory($collaborators['requestFactory']); + + if (empty($collaborators['httpClient'])) { + $client_options = $this->getAllowedClientOptions($options); + + $collaborators['httpClient'] = new HttpClient( + array_intersect_key($options, array_flip($client_options)) + ); + } + $this->setHttpClient($collaborators['httpClient']); + + if (empty($collaborators['optionProvider'])) { + $collaborators['optionProvider'] = new PostAuthOptionProvider(); + } + $this->setOptionProvider($collaborators['optionProvider']); + } + + /** + * Returns the list of options that can be passed to the HttpClient + * + * @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. + * @return array The options to pass to the HttpClient constructor + */ + protected function getAllowedClientOptions(array $options) + { + $client_options = ['timeout', 'proxy']; + + // Only allow turning off ssl verification if it's for a proxy + if (!empty($options['proxy'])) { + $client_options[] = 'verify'; + } + + return $client_options; + } + + /** + * Sets the grant factory instance. + * + * @param GrantFactory $factory + * @return self + */ + public function setGrantFactory(GrantFactory $factory) + { + $this->grantFactory = $factory; + + return $this; + } + + /** + * Returns the current grant factory instance. + * + * @return GrantFactory + */ + public function getGrantFactory() + { + return $this->grantFactory; + } + + /** + * Sets the request factory instance. + * + * @param RequestFactory $factory + * @return self + */ + public function setRequestFactory(RequestFactory $factory) + { + $this->requestFactory = $factory; + + return $this; + } + + /** + * Returns the request factory instance. + * + * @return RequestFactory + */ + public function getRequestFactory() + { + return $this->requestFactory; + } + + /** + * Sets the HTTP client instance. + * + * @param HttpClientInterface $client + * @return self + */ + public function setHttpClient(HttpClientInterface $client) + { + $this->httpClient = $client; + + return $this; + } + + /** + * Returns the HTTP client instance. + * + * @return HttpClientInterface + */ + public function getHttpClient() + { + return $this->httpClient; + } + + /** + * Sets the option provider instance. + * + * @param OptionProviderInterface $provider + * @return self + */ + public function setOptionProvider(OptionProviderInterface $provider) + { + $this->optionProvider = $provider; + + return $this; + } + + /** + * Returns the option provider instance. + * + * @return OptionProviderInterface + */ + public function getOptionProvider() + { + return $this->optionProvider; + } + + /** + * Returns the current value of the state parameter. + * + * This can be accessed by the redirect handler during authorization. + * + * @return string + */ + public function getState() + { + return $this->state; + } + + /** + * Returns the base URL for authorizing a client. + * + * Eg. https://oauth.service.com/authorize + * + * @return string + */ + abstract public function getBaseAuthorizationUrl(); + + /** + * Returns the base URL for requesting an access token. + * + * Eg. https://oauth.service.com/token + * + * @param array $params + * @return string + */ + abstract public function getBaseAccessTokenUrl(array $params); + + /** + * Returns the URL for requesting the resource owner's details. + * + * @param AccessToken $token + * @return string + */ + abstract public function getResourceOwnerDetailsUrl(AccessToken $token); + + /** + * Returns a new random string to use as the state parameter in an + * authorization flow. + * + * @param int $length Length of the random string to be generated. + * @return string + */ + protected function getRandomState($length = 32) + { + // Converting bytes to hex will always double length. Hence, we can reduce + // the amount of bytes by half to produce the correct length. + return bin2hex(random_bytes($length / 2)); + } + + /** + * Returns the default scopes used by this provider. + * + * This should only be the scopes that are required to request the details + * of the resource owner, rather than all the available scopes. + * + * @return array + */ + abstract protected function getDefaultScopes(); + + /** + * Returns the string that should be used to separate scopes when building + * the URL for requesting an access token. + * + * @return string Scope separator, defaults to ',' + */ + protected function getScopeSeparator() + { + return ','; + } + + /** + * Returns authorization parameters based on provided options. + * + * @param array $options + * @return array Authorization parameters + */ + protected function getAuthorizationParameters(array $options) + { + if (empty($options['state'])) { + $options['state'] = $this->getRandomState(); + } + + if (empty($options['scope'])) { + $options['scope'] = $this->getDefaultScopes(); + } + + $options += [ + 'response_type' => 'code', + 'approval_prompt' => 'auto' + ]; + + if (is_array($options['scope'])) { + $separator = $this->getScopeSeparator(); + $options['scope'] = implode($separator, $options['scope']); + } + + // Store the state as it may need to be accessed later on. + $this->state = $options['state']; + + // Business code layer might set a different redirect_uri parameter + // depending on the context, leave it as-is + if (!isset($options['redirect_uri'])) { + $options['redirect_uri'] = $this->redirectUri; + } + + $options['client_id'] = $this->clientId; + + return $options; + } + + /** + * Builds the authorization URL's query string. + * + * @param array $params Query parameters + * @return string Query string + */ + protected function getAuthorizationQuery(array $params) + { + return $this->buildQueryString($params); + } + + /** + * Builds the authorization URL. + * + * @param array $options + * @return string Authorization URL + */ + public function getAuthorizationUrl(array $options = []) + { + $base = $this->getBaseAuthorizationUrl(); + $params = $this->getAuthorizationParameters($options); + $query = $this->getAuthorizationQuery($params); + + return $this->appendQuery($base, $query); + } + + /** + * Redirects the client for authorization. + * + * @param array $options + * @param callable|null $redirectHandler + * @return mixed + */ + public function authorize( + array $options = [], + callable $redirectHandler = null + ) { + $url = $this->getAuthorizationUrl($options); + if ($redirectHandler) { + return $redirectHandler($url, $this); + } + + // @codeCoverageIgnoreStart + header('Location: ' . $url); + exit; + // @codeCoverageIgnoreEnd + } + + /** + * Appends a query string to a URL. + * + * @param string $url The URL to append the query to + * @param string $query The HTTP query string + * @return string The resulting URL + */ + protected function appendQuery($url, $query) + { + $query = trim($query, '?&'); + + if ($query) { + $glue = strstr($url, '?') === false ? '?' : '&'; + return $url . $glue . $query; + } + + return $url; + } + + /** + * Returns the method to use when requesting an access token. + * + * @return string HTTP method + */ + protected function getAccessTokenMethod() + { + return self::METHOD_POST; + } + + /** + * Returns the key used in the access token response to identify the resource owner. + * + * @return string|null Resource owner identifier key + */ + protected function getAccessTokenResourceOwnerId() + { + return static::ACCESS_TOKEN_RESOURCE_OWNER_ID; + } + + /** + * Builds the access token URL's query string. + * + * @param array $params Query parameters + * @return string Query string + */ + protected function getAccessTokenQuery(array $params) + { + return $this->buildQueryString($params); + } + + /** + * Checks that a provided grant is valid, or attempts to produce one if the + * provided grant is a string. + * + * @param AbstractGrant|string $grant + * @return AbstractGrant + */ + protected function verifyGrant($grant) + { + if (is_string($grant)) { + return $this->grantFactory->getGrant($grant); + } + + $this->grantFactory->checkGrant($grant); + return $grant; + } + + /** + * Returns the full URL to use when requesting an access token. + * + * @param array $params Query parameters + * @return string + */ + protected function getAccessTokenUrl(array $params) + { + $url = $this->getBaseAccessTokenUrl($params); + + if ($this->getAccessTokenMethod() === self::METHOD_GET) { + $query = $this->getAccessTokenQuery($params); + return $this->appendQuery($url, $query); + } + + return $url; + } + + /** + * Returns a prepared request for requesting an access token. + * + * @param array $params Query string parameters + * @return RequestInterface + */ + protected function getAccessTokenRequest(array $params) + { + $method = $this->getAccessTokenMethod(); + $url = $this->getAccessTokenUrl($params); + $options = $this->optionProvider->getAccessTokenOptions($this->getAccessTokenMethod(), $params); + + return $this->getRequest($method, $url, $options); + } + + /** + * Requests an access token using a specified grant and option set. + * + * @param mixed $grant + * @param array $options + * @throws IdentityProviderException + * @return AccessTokenInterface + */ + public function getAccessToken($grant, array $options = []) + { + $grant = $this->verifyGrant($grant); + + $params = [ + 'client_id' => $this->clientId, + 'client_secret' => $this->clientSecret, + 'redirect_uri' => $this->redirectUri, + ]; + + $params = $grant->prepareRequestParameters($params, $options); + $request = $this->getAccessTokenRequest($params); + $response = $this->getParsedResponse($request); + if (false === is_array($response)) { + throw new UnexpectedValueException( + 'Invalid response received from Authorization Server. Expected JSON.' + ); + } + $prepared = $this->prepareAccessTokenResponse($response); + $token = $this->createAccessToken($prepared, $grant); + + return $token; + } + + /** + * Returns a PSR-7 request instance that is not authenticated. + * + * @param string $method + * @param string $url + * @param array $options + * @return RequestInterface + */ + public function getRequest($method, $url, array $options = []) + { + return $this->createRequest($method, $url, null, $options); + } + + /** + * Returns an authenticated PSR-7 request instance. + * + * @param string $method + * @param string $url + * @param AccessTokenInterface|string $token + * @param array $options Any of "headers", "body", and "protocolVersion". + * @return RequestInterface + */ + public function getAuthenticatedRequest($method, $url, $token, array $options = []) + { + return $this->createRequest($method, $url, $token, $options); + } + + /** + * Creates a PSR-7 request instance. + * + * @param string $method + * @param string $url + * @param AccessTokenInterface|string|null $token + * @param array $options + * @return RequestInterface + */ + protected function createRequest($method, $url, $token, array $options) + { + $defaults = [ + 'headers' => $this->getHeaders($token), + ]; + + $options = array_merge_recursive($defaults, $options); + $factory = $this->getRequestFactory(); + + return $factory->getRequestWithOptions($method, $url, $options); + } + + /** + * Sends a request instance and returns a response instance. + * + * WARNING: This method does not attempt to catch exceptions caused by HTTP + * errors! It is recommended to wrap this method in a try/catch block. + * + * @param RequestInterface $request + * @return ResponseInterface + */ + public function getResponse(RequestInterface $request) + { + return $this->getHttpClient()->send($request); + } + + /** + * Sends a request and returns the parsed response. + * + * @param RequestInterface $request + * @throws IdentityProviderException + * @return mixed + */ + public function getParsedResponse(RequestInterface $request) + { + try { + $response = $this->getResponse($request); + } catch (BadResponseException $e) { + $response = $e->getResponse(); + } + + $parsed = $this->parseResponse($response); + + $this->checkResponse($response, $parsed); + + return $parsed; + } + + /** + * Attempts to parse a JSON response. + * + * @param string $content JSON content from response body + * @return array Parsed JSON data + * @throws UnexpectedValueException if the content could not be parsed + */ + protected function parseJson($content) + { + $content = json_decode($content, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new UnexpectedValueException(sprintf( + "Failed to parse JSON response: %s", + json_last_error_msg() + )); + } + + return $content; + } + + /** + * Returns the content type header of a response. + * + * @param ResponseInterface $response + * @return string Semi-colon separated join of content-type headers. + */ + protected function getContentType(ResponseInterface $response) + { + return join(';', (array) $response->getHeader('content-type')); + } + + /** + * Parses the response according to its content-type header. + * + * @throws UnexpectedValueException + * @param ResponseInterface $response + * @return array + */ + protected function parseResponse(ResponseInterface $response) + { + $content = (string) $response->getBody(); + $type = $this->getContentType($response); + + if (strpos($type, 'urlencoded') !== false) { + parse_str($content, $parsed); + return $parsed; + } + + // Attempt to parse the string as JSON regardless of content type, + // since some providers use non-standard content types. Only throw an + // exception if the JSON could not be parsed when it was expected to. + try { + return $this->parseJson($content); + } catch (UnexpectedValueException $e) { + if (strpos($type, 'json') !== false) { + throw $e; + } + + if ($response->getStatusCode() == 500) { + throw new UnexpectedValueException( + 'An OAuth server error was encountered that did not contain a JSON body', + 0, + $e + ); + } + + return $content; + } + } + + /** + * Checks a provider response for errors. + * + * @throws IdentityProviderException + * @param ResponseInterface $response + * @param array|string $data Parsed response data + * @return void + */ + abstract protected function checkResponse(ResponseInterface $response, $data); + + /** + * Prepares an parsed access token response for a grant. + * + * Custom mapping of expiration, etc should be done here. Always call the + * parent method when overloading this method. + * + * @param mixed $result + * @return array + */ + protected function prepareAccessTokenResponse(array $result) + { + if ($this->getAccessTokenResourceOwnerId() !== null) { + $result['resource_owner_id'] = $this->getValueByKey( + $result, + $this->getAccessTokenResourceOwnerId() + ); + } + return $result; + } + + /** + * Creates an access token from a response. + * + * The grant that was used to fetch the response can be used to provide + * additional context. + * + * @param array $response + * @param AbstractGrant $grant + * @return AccessTokenInterface + */ + protected function createAccessToken(array $response, AbstractGrant $grant) + { + return new AccessToken($response); + } + + /** + * Generates a resource owner object from a successful resource owner + * details request. + * + * @param array $response + * @param AccessToken $token + * @return ResourceOwnerInterface + */ + abstract protected function createResourceOwner(array $response, AccessToken $token); + + /** + * Requests and returns the resource owner of given access token. + * + * @param AccessToken $token + * @return ResourceOwnerInterface + */ + public function getResourceOwner(AccessToken $token) + { + $response = $this->fetchResourceOwnerDetails($token); + + return $this->createResourceOwner($response, $token); + } + + /** + * Requests resource owner details. + * + * @param AccessToken $token + * @return mixed + */ + protected function fetchResourceOwnerDetails(AccessToken $token) + { + $url = $this->getResourceOwnerDetailsUrl($token); + + $request = $this->getAuthenticatedRequest(self::METHOD_GET, $url, $token); + + $response = $this->getParsedResponse($request); + + if (false === is_array($response)) { + throw new UnexpectedValueException( + 'Invalid response received from Authorization Server. Expected JSON.' + ); + } + + return $response; + } + + /** + * Returns the default headers used by this provider. + * + * Typically this is used to set 'Accept' or 'Content-Type' headers. + * + * @return array + */ + protected function getDefaultHeaders() + { + return []; + } + + /** + * Returns the authorization headers used by this provider. + * + * Typically this is "Bearer" or "MAC". For more information see: + * http://tools.ietf.org/html/rfc6749#section-7.1 + * + * No default is provided, providers must overload this method to activate + * authorization headers. + * + * @param mixed|null $token Either a string or an access token instance + * @return array + */ + protected function getAuthorizationHeaders($token = null) + { + return []; + } + + /** + * Returns all headers used by this provider for a request. + * + * The request will be authenticated if an access token is provided. + * + * @param mixed|null $token object or string + * @return array + */ + public function getHeaders($token = null) + { + if ($token) { + return array_merge( + $this->getDefaultHeaders(), + $this->getAuthorizationHeaders($token) + ); + } + + return $this->getDefaultHeaders(); + } +} diff --git a/lib/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php b/lib/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php new file mode 100644 index 0000000000..52b7e0353c --- /dev/null +++ b/lib/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php @@ -0,0 +1,48 @@ + + * @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\Provider\Exception; + +/** + * Exception thrown if the provider response contains errors. + */ +class IdentityProviderException extends \Exception +{ + /** + * @var mixed + */ + protected $response; + + /** + * @param string $message + * @param int $code + * @param array|string $response The response body + */ + public function __construct($message, $code, $response) + { + $this->response = $response; + + parent::__construct($message, $code); + } + + /** + * Returns the exception's response body. + * + * @return array|string + */ + public function getResponseBody() + { + return $this->response; + } +} diff --git a/lib/league/oauth2-client/src/Provider/GenericProvider.php b/lib/league/oauth2-client/src/Provider/GenericProvider.php new file mode 100644 index 0000000000..74393ffda5 --- /dev/null +++ b/lib/league/oauth2-client/src/Provider/GenericProvider.php @@ -0,0 +1,233 @@ + + * @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\Provider; + +use InvalidArgumentException; +use League\OAuth2\Client\Provider\Exception\IdentityProviderException; +use League\OAuth2\Client\Token\AccessToken; +use League\OAuth2\Client\Tool\BearerAuthorizationTrait; +use Psr\Http\Message\ResponseInterface; + +/** + * Represents a generic service provider that may be used to interact with any + * OAuth 2.0 service provider, using Bearer token authentication. + */ +class GenericProvider extends AbstractProvider +{ + use BearerAuthorizationTrait; + + /** + * @var string + */ + private $urlAuthorize; + + /** + * @var string + */ + private $urlAccessToken; + + /** + * @var string + */ + private $urlResourceOwnerDetails; + + /** + * @var string + */ + private $accessTokenMethod; + + /** + * @var string + */ + private $accessTokenResourceOwnerId; + + /** + * @var array|null + */ + private $scopes = null; + + /** + * @var string + */ + private $scopeSeparator; + + /** + * @var string + */ + private $responseError = 'error'; + + /** + * @var string + */ + private $responseCode; + + /** + * @var string + */ + private $responseResourceOwnerId = 'id'; + + /** + * @param array $options + * @param array $collaborators + */ + public function __construct(array $options = [], array $collaborators = []) + { + $this->assertRequiredOptions($options); + + $possible = $this->getConfigurableOptions(); + $configured = array_intersect_key($options, array_flip($possible)); + + foreach ($configured as $key => $value) { + $this->$key = $value; + } + + // Remove all options that are only used locally + $options = array_diff_key($options, $configured); + + parent::__construct($options, $collaborators); + } + + /** + * Returns all options that can be configured. + * + * @return array + */ + protected function getConfigurableOptions() + { + return array_merge($this->getRequiredOptions(), [ + 'accessTokenMethod', + 'accessTokenResourceOwnerId', + 'scopeSeparator', + 'responseError', + 'responseCode', + 'responseResourceOwnerId', + 'scopes', + ]); + } + + /** + * Returns all options that are required. + * + * @return array + */ + protected function getRequiredOptions() + { + return [ + 'urlAuthorize', + 'urlAccessToken', + 'urlResourceOwnerDetails', + ]; + } + + /** + * Verifies that all required options have been passed. + * + * @param array $options + * @return void + * @throws InvalidArgumentException + */ + private function assertRequiredOptions(array $options) + { + $missing = array_diff_key(array_flip($this->getRequiredOptions()), $options); + + if (!empty($missing)) { + throw new InvalidArgumentException( + 'Required options not defined: ' . implode(', ', array_keys($missing)) + ); + } + } + + /** + * @inheritdoc + */ + public function getBaseAuthorizationUrl() + { + return $this->urlAuthorize; + } + + /** + * @inheritdoc + */ + public function getBaseAccessTokenUrl(array $params) + { + return $this->urlAccessToken; + } + + /** + * @inheritdoc + */ + public function getResourceOwnerDetailsUrl(AccessToken $token) + { + return $this->urlResourceOwnerDetails; + } + + /** + * @inheritdoc + */ + public function getDefaultScopes() + { + return $this->scopes; + } + + /** + * @inheritdoc + */ + protected function getAccessTokenMethod() + { + return $this->accessTokenMethod ?: parent::getAccessTokenMethod(); + } + + /** + * @inheritdoc + */ + protected function getAccessTokenResourceOwnerId() + { + return $this->accessTokenResourceOwnerId ?: parent::getAccessTokenResourceOwnerId(); + } + + /** + * @inheritdoc + */ + protected function getScopeSeparator() + { + return $this->scopeSeparator ?: parent::getScopeSeparator(); + } + + /** + * @inheritdoc + */ + protected function checkResponse(ResponseInterface $response, $data) + { + if (!empty($data[$this->responseError])) { + $error = $data[$this->responseError]; + if (!is_string($error)) { + $error = var_export($error, true); + } + $code = $this->responseCode && !empty($data[$this->responseCode])? $data[$this->responseCode] : 0; + if (!is_int($code)) { + $code = intval($code); + } + throw new IdentityProviderException($error, $code, $data); + } + } + + /** + * @inheritdoc + */ + protected function createResourceOwner(array $response, AccessToken $token) + { + return new GenericResourceOwner($response, $this->responseResourceOwnerId); + } +} diff --git a/lib/league/oauth2-client/src/Provider/GenericResourceOwner.php b/lib/league/oauth2-client/src/Provider/GenericResourceOwner.php new file mode 100644 index 0000000000..f876614851 --- /dev/null +++ b/lib/league/oauth2-client/src/Provider/GenericResourceOwner.php @@ -0,0 +1,61 @@ + + * @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\Provider; + +/** + * Represents a generic resource owner for use with the GenericProvider. + */ +class GenericResourceOwner implements ResourceOwnerInterface +{ + /** + * @var array + */ + protected $response; + + /** + * @var string + */ + protected $resourceOwnerId; + + /** + * @param array $response + * @param string $resourceOwnerId + */ + public function __construct(array $response, $resourceOwnerId) + { + $this->response = $response; + $this->resourceOwnerId = $resourceOwnerId; + } + + /** + * Returns the identifier of the authorized resource owner. + * + * @return mixed + */ + public function getId() + { + return $this->response[$this->resourceOwnerId]; + } + + /** + * Returns the raw resource owner response. + * + * @return array + */ + public function toArray() + { + return $this->response; + } +} diff --git a/lib/league/oauth2-client/src/Provider/ResourceOwnerInterface.php b/lib/league/oauth2-client/src/Provider/ResourceOwnerInterface.php new file mode 100644 index 0000000000..828442425f --- /dev/null +++ b/lib/league/oauth2-client/src/Provider/ResourceOwnerInterface.php @@ -0,0 +1,36 @@ + + * @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\Provider; + +/** + * Classes implementing `ResourceOwnerInterface` may be used to represent + * the resource owner authenticated with a service provider. + */ +interface ResourceOwnerInterface +{ + /** + * Returns the identifier of the authorized resource owner. + * + * @return mixed + */ + public function getId(); + + /** + * Return all of the owner details available as an array. + * + * @return array + */ + public function toArray(); +} diff --git a/lib/league/oauth2-client/src/Token/AccessToken.php b/lib/league/oauth2-client/src/Token/AccessToken.php new file mode 100644 index 0000000000..81533c3072 --- /dev/null +++ b/lib/league/oauth2-client/src/Token/AccessToken.php @@ -0,0 +1,243 @@ + + * @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; + +use InvalidArgumentException; +use RuntimeException; + +/** + * Represents an access token. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.4 Access Token (RFC 6749, §1.4) + */ +class AccessToken implements AccessTokenInterface, ResourceOwnerAccessTokenInterface +{ + /** + * @var string + */ + protected $accessToken; + + /** + * @var int + */ + protected $expires; + + /** + * @var string + */ + protected $refreshToken; + + /** + * @var string + */ + protected $resourceOwnerId; + + /** + * @var array + */ + protected $values = []; + + /** + * @var int + */ + private static $timeNow; + + /** + * Set the time now. This should only be used for testing purposes. + * + * @param int $timeNow the time in seconds since epoch + * @return void + */ + public static function setTimeNow($timeNow) + { + self::$timeNow = $timeNow; + } + + /** + * Reset the time now if it was set for test purposes. + * + * @return void + */ + public static function resetTimeNow() + { + self::$timeNow = null; + } + + /** + * @return int + */ + public function getTimeNow() + { + return self::$timeNow ? self::$timeNow : time(); + } + + /** + * Constructs an access token. + * + * @param array $options An array of options returned by the service provider + * in the access token request. The `access_token` option is required. + * @throws InvalidArgumentException if `access_token` is not provided in `$options`. + */ + public function __construct(array $options = []) + { + if (empty($options['access_token'])) { + throw new InvalidArgumentException('Required option not passed: "access_token"'); + } + + $this->accessToken = $options['access_token']; + + if (!empty($options['resource_owner_id'])) { + $this->resourceOwnerId = $options['resource_owner_id']; + } + + if (!empty($options['refresh_token'])) { + $this->refreshToken = $options['refresh_token']; + } + + // We need to know when the token expires. Show preference to + // 'expires_in' since it is defined in RFC6749 Section 5.1. + // Defer to 'expires' if it is provided instead. + if (isset($options['expires_in'])) { + if (!is_numeric($options['expires_in'])) { + throw new \InvalidArgumentException('expires_in value must be an integer'); + } + + $this->expires = $options['expires_in'] != 0 ? $this->getTimeNow() + $options['expires_in'] : 0; + } 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']; + + if (!$this->isExpirationTimestamp($expires)) { + $expires += $this->getTimeNow(); + } + + $this->expires = $expires; + } + + // Capture any additional values that might exist in the token but are + // not part of the standard response. Vendors will sometimes pass + // additional user data this way. + $this->values = array_diff_key($options, array_flip([ + 'access_token', + 'resource_owner_id', + 'refresh_token', + 'expires_in', + 'expires', + ])); + } + + /** + * Check if a value is an expiration timestamp or second value. + * + * @param integer $value + * @return bool + */ + protected function isExpirationTimestamp($value) + { + // If the given value is larger than the original OAuth 2 draft date, + // assume that it is meant to be a (possible expired) timestamp. + $oauth2InceptionDate = 1349067600; // 2012-10-01 + return ($value > $oauth2InceptionDate); + } + + /** + * @inheritdoc + */ + public function getToken() + { + return $this->accessToken; + } + + /** + * @inheritdoc + */ + public function getRefreshToken() + { + return $this->refreshToken; + } + + /** + * @inheritdoc + */ + public function getExpires() + { + return $this->expires; + } + + /** + * @inheritdoc + */ + public function getResourceOwnerId() + { + return $this->resourceOwnerId; + } + + /** + * @inheritdoc + */ + public function hasExpired() + { + $expires = $this->getExpires(); + + if (empty($expires)) { + throw new RuntimeException('"expires" is not set on the token'); + } + + return $expires < time(); + } + + /** + * @inheritdoc + */ + public function getValues() + { + return $this->values; + } + + /** + * @inheritdoc + */ + public function __toString() + { + return (string) $this->getToken(); + } + + /** + * @inheritdoc + */ + public function jsonSerialize() + { + $parameters = $this->values; + + if ($this->accessToken) { + $parameters['access_token'] = $this->accessToken; + } + + if ($this->refreshToken) { + $parameters['refresh_token'] = $this->refreshToken; + } + + if ($this->expires) { + $parameters['expires'] = $this->expires; + } + + if ($this->resourceOwnerId) { + $parameters['resource_owner_id'] = $this->resourceOwnerId; + } + + return $parameters; + } +} diff --git a/lib/league/oauth2-client/src/Token/AccessTokenInterface.php b/lib/league/oauth2-client/src/Token/AccessTokenInterface.php new file mode 100644 index 0000000000..5fd219ffca --- /dev/null +++ b/lib/league/oauth2-client/src/Token/AccessTokenInterface.php @@ -0,0 +1,74 @@ + + * @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; + +use JsonSerializable; +use ReturnTypeWillChange; +use RuntimeException; + +interface AccessTokenInterface extends JsonSerializable +{ + /** + * Returns the access token string of this instance. + * + * @return string + */ + public function getToken(); + + /** + * Returns the refresh token, if defined. + * + * @return string|null + */ + public function getRefreshToken(); + + /** + * Returns the expiration timestamp in seconds, if defined. + * + * @return integer|null + */ + public function getExpires(); + + /** + * Checks if this token has expired. + * + * @return boolean true if the token has expired, false otherwise. + * @throws RuntimeException if 'expires' is not set on the token. + */ + public function hasExpired(); + + /** + * Returns additional vendor values stored in the token. + * + * @return array + */ + public function getValues(); + + /** + * Returns a string representation of the access token + * + * @return string + */ + public function __toString(); + + /** + * Returns an array of parameters to serialize when this is serialized with + * json_encode(). + * + * @return array + */ + #[ReturnTypeWillChange] + public function jsonSerialize(); +} diff --git a/lib/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php b/lib/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php new file mode 100644 index 0000000000..51e4ce4139 --- /dev/null +++ b/lib/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php @@ -0,0 +1,25 @@ + + * @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 ResourceOwnerAccessTokenInterface extends AccessTokenInterface +{ + /** + * Returns the resource owner identifier, if defined. + * + * @return string|null + */ + public function getResourceOwnerId(); +} diff --git a/lib/league/oauth2-client/src/Tool/ArrayAccessorTrait.php b/lib/league/oauth2-client/src/Tool/ArrayAccessorTrait.php new file mode 100644 index 0000000000..a18198cf30 --- /dev/null +++ b/lib/league/oauth2-client/src/Tool/ArrayAccessorTrait.php @@ -0,0 +1,52 @@ + + * @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\Tool; + +/** + * Provides generic array navigation tools. + */ +trait ArrayAccessorTrait +{ + /** + * Returns a value by key using dot notation. + * + * @param array $data + * @param string $key + * @param mixed|null $default + * @return mixed + */ + private function getValueByKey(array $data, $key, $default = null) + { + if (!is_string($key) || empty($key) || !count($data)) { + return $default; + } + + if (strpos($key, '.') !== false) { + $keys = explode('.', $key); + + foreach ($keys as $innerKey) { + if (!is_array($data) || !array_key_exists($innerKey, $data)) { + return $default; + } + + $data = $data[$innerKey]; + } + + return $data; + } + + return array_key_exists($key, $data) ? $data[$key] : $default; + } +} diff --git a/lib/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php b/lib/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php new file mode 100644 index 0000000000..081c7c861e --- /dev/null +++ b/lib/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php @@ -0,0 +1,36 @@ + + * @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\Tool; + +use League\OAuth2\Client\Token\AccessTokenInterface; + +/** + * Enables `Bearer` header authorization for providers. + * + * @link http://tools.ietf.org/html/rfc6750 Bearer Token Usage (RFC 6750) + */ +trait BearerAuthorizationTrait +{ + /** + * Returns authorization headers for the 'bearer' grant. + * + * @param AccessTokenInterface|string|null $token Either a string or an access token instance + * @return array + */ + protected function getAuthorizationHeaders($token = null) + { + return ['Authorization' => 'Bearer ' . $token]; + } +} diff --git a/lib/league/oauth2-client/src/Tool/GuardedPropertyTrait.php b/lib/league/oauth2-client/src/Tool/GuardedPropertyTrait.php new file mode 100644 index 0000000000..02c9ba5fb6 --- /dev/null +++ b/lib/league/oauth2-client/src/Tool/GuardedPropertyTrait.php @@ -0,0 +1,70 @@ + + * @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\Tool; + +/** + * Provides support for blacklisting explicit properties from the + * mass assignment behavior. + */ +trait GuardedPropertyTrait +{ + /** + * The properties that aren't mass assignable. + * + * @var array + */ + protected $guarded = []; + + /** + * Attempts to mass assign the given options to explicitly defined properties, + * skipping over any properties that are defined in the guarded array. + * + * @param array $options + * @return mixed + */ + protected function fillProperties(array $options = []) + { + if (isset($options['guarded'])) { + unset($options['guarded']); + } + + foreach ($options as $option => $value) { + if (property_exists($this, $option) && !$this->isGuarded($option)) { + $this->{$option} = $value; + } + } + } + + /** + * Returns current guarded properties. + * + * @return array + */ + public function getGuarded() + { + return $this->guarded; + } + + /** + * Determines if the given property is guarded. + * + * @param string $property + * @return bool + */ + public function isGuarded($property) + { + return in_array($property, $this->getGuarded()); + } +} diff --git a/lib/league/oauth2-client/src/Tool/MacAuthorizationTrait.php b/lib/league/oauth2-client/src/Tool/MacAuthorizationTrait.php new file mode 100644 index 0000000000..f8dcd77c54 --- /dev/null +++ b/lib/league/oauth2-client/src/Tool/MacAuthorizationTrait.php @@ -0,0 +1,83 @@ + + * @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\Tool; + +use League\OAuth2\Client\Token\AccessToken; +use League\OAuth2\Client\Token\AccessTokenInterface; + +/** + * Enables `MAC` header authorization for providers. + * + * @link http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-05 Message Authentication Code (MAC) Tokens + */ +trait MacAuthorizationTrait +{ + /** + * Returns the id of this token for MAC generation. + * + * @param AccessToken $token + * @return string + */ + abstract protected function getTokenId(AccessToken $token); + + /** + * Returns the MAC signature for the current request. + * + * @param string $id + * @param integer $ts + * @param string $nonce + * @return string + */ + abstract protected function getMacSignature($id, $ts, $nonce); + + /** + * Returns a new random string to use as the state parameter in an + * authorization flow. + * + * @param int $length Length of the random string to be generated. + * @return string + */ + abstract protected function getRandomState($length = 32); + + /** + * Returns the authorization headers for the 'mac' grant. + * + * @param AccessTokenInterface|string|null $token Either a string or an access token instance + * @return array + * @codeCoverageIgnore + * + * @todo This is currently untested and provided only as an example. If you + * complete the implementation, please create a pull request for + * https://github.com/thephpleague/oauth2-client + */ + protected function getAuthorizationHeaders($token = null) + { + if ($token === null) { + return []; + } + + $ts = time(); + $id = $this->getTokenId($token); + $nonce = $this->getRandomState(16); + $mac = $this->getMacSignature($id, $ts, $nonce); + + $parts = []; + foreach (compact('id', 'ts', 'nonce', 'mac') as $key => $value) { + $parts[] = sprintf('%s="%s"', $key, $value); + } + + return ['Authorization' => 'MAC ' . implode(', ', $parts)]; + } +} diff --git a/lib/league/oauth2-client/src/Tool/ProviderRedirectTrait.php b/lib/league/oauth2-client/src/Tool/ProviderRedirectTrait.php new file mode 100644 index 0000000000..f81b511f9e --- /dev/null +++ b/lib/league/oauth2-client/src/Tool/ProviderRedirectTrait.php @@ -0,0 +1,122 @@ +redirectLimit) { + $attempts++; + $response = $this->getHttpClient()->send($request, [ + 'allow_redirects' => false + ]); + + if ($this->isRedirect($response)) { + $redirectUrl = new Uri($response->getHeader('Location')[0]); + $request = $request->withUri($redirectUrl); + } else { + break; + } + } + + return $response; + } + + /** + * Returns the HTTP client instance. + * + * @return GuzzleHttp\ClientInterface + */ + abstract public function getHttpClient(); + + /** + * Retrieves current redirect limit. + * + * @return integer + */ + public function getRedirectLimit() + { + return $this->redirectLimit; + } + + /** + * Determines if a given response is a redirect. + * + * @param ResponseInterface $response + * + * @return boolean + */ + protected function isRedirect(ResponseInterface $response) + { + $statusCode = $response->getStatusCode(); + + return $statusCode > 300 && $statusCode < 400 && $response->hasHeader('Location'); + } + + /** + * Sends a request instance and returns a response instance. + * + * WARNING: This method does not attempt to catch exceptions caused by HTTP + * errors! It is recommended to wrap this method in a try/catch block. + * + * @param RequestInterface $request + * @return ResponseInterface + */ + public function getResponse(RequestInterface $request) + { + try { + $response = $this->followRequestRedirects($request); + } catch (BadResponseException $e) { + $response = $e->getResponse(); + } + + return $response; + } + + /** + * Updates the redirect limit. + * + * @param integer $limit + * @return League\OAuth2\Client\Provider\AbstractProvider + * @throws InvalidArgumentException + */ + public function setRedirectLimit($limit) + { + if (!is_int($limit)) { + throw new InvalidArgumentException('redirectLimit must be an integer.'); + } + + if ($limit < 1) { + throw new InvalidArgumentException('redirectLimit must be greater than or equal to one.'); + } + + $this->redirectLimit = $limit; + + return $this; + } +} diff --git a/lib/league/oauth2-client/src/Tool/QueryBuilderTrait.php b/lib/league/oauth2-client/src/Tool/QueryBuilderTrait.php new file mode 100644 index 0000000000..bdda3e79ef --- /dev/null +++ b/lib/league/oauth2-client/src/Tool/QueryBuilderTrait.php @@ -0,0 +1,33 @@ + + * @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\Tool; + +/** + * Provides a standard way to generate query strings. + */ +trait QueryBuilderTrait +{ + /** + * Build a query string from an array. + * + * @param array $params + * + * @return string + */ + protected function buildQueryString(array $params) + { + return http_build_query($params, '', '&', \PHP_QUERY_RFC3986); + } +} diff --git a/lib/league/oauth2-client/src/Tool/RequestFactory.php b/lib/league/oauth2-client/src/Tool/RequestFactory.php new file mode 100644 index 0000000000..1af434297f --- /dev/null +++ b/lib/league/oauth2-client/src/Tool/RequestFactory.php @@ -0,0 +1,87 @@ + + * @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\Tool; + +use GuzzleHttp\Psr7\Request; + +/** + * Used to produce PSR-7 Request instances. + * + * @link https://github.com/guzzle/guzzle/pull/1101 + */ +class RequestFactory +{ + /** + * Creates a PSR-7 Request instance. + * + * @param null|string $method HTTP method for the request. + * @param null|string $uri URI for the request. + * @param array $headers Headers for the message. + * @param string|resource|StreamInterface $body Message body. + * @param string $version HTTP protocol version. + * + * @return Request + */ + public function getRequest( + $method, + $uri, + array $headers = [], + $body = null, + $version = '1.1' + ) { + return new Request($method, $uri, $headers, $body, $version); + } + + /** + * Parses simplified options. + * + * @param array $options Simplified options. + * + * @return array Extended options for use with getRequest. + */ + protected function parseOptions(array $options) + { + // Should match default values for getRequest + $defaults = [ + 'headers' => [], + 'body' => null, + 'version' => '1.1', + ]; + + return array_merge($defaults, $options); + } + + /** + * Creates a request using a simplified array of options. + * + * @param null|string $method + * @param null|string $uri + * @param array $options + * + * @return Request + */ + public function getRequestWithOptions($method, $uri, array $options = []) + { + $options = $this->parseOptions($options); + + return $this->getRequest( + $method, + $uri, + $options['headers'], + $options['body'], + $options['version'] + ); + } +} diff --git a/lib/league/oauth2-client/src/Tool/RequiredParameterTrait.php b/lib/league/oauth2-client/src/Tool/RequiredParameterTrait.php new file mode 100644 index 0000000000..47da977177 --- /dev/null +++ b/lib/league/oauth2-client/src/Tool/RequiredParameterTrait.php @@ -0,0 +1,56 @@ + + * @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\Tool; + +use BadMethodCallException; + +/** + * Provides functionality to check for required parameters. + */ +trait RequiredParameterTrait +{ + /** + * Checks for a required parameter in a hash. + * + * @throws BadMethodCallException + * @param string $name + * @param array $params + * @return void + */ + private function checkRequiredParameter($name, array $params) + { + if (!isset($params[$name])) { + throw new BadMethodCallException(sprintf( + 'Required parameter not passed: "%s"', + $name + )); + } + } + + /** + * Checks for multiple required parameters in a hash. + * + * @throws InvalidArgumentException + * @param array $names + * @param array $params + * @return void + */ + private function checkRequiredParameters(array $names, array $params) + { + foreach ($names as $name) { + $this->checkRequiredParameter($name, $params); + } + } +} diff --git a/lib/league/oauth2-google/CHANGELOG.md b/lib/league/oauth2-google/CHANGELOG.md new file mode 100644 index 0000000000..18b1a9a6ec --- /dev/null +++ b/lib/league/oauth2-google/CHANGELOG.md @@ -0,0 +1,72 @@ +OAuth 2.0 Google Provider Changelog + +## 3.0.4 - 2021-01-27 + +### Fixed + +- Correct OAuth endpoint, #94 by @Slamdunk + +## 3.0.3 - 2020-07-24 + +### Fixed + +- Remove the `approval_prompt` from default parameters, #90 + +## 3.0.2 - 2019-11-16 + +### Fixed + +- Allow for `family_name` to be undefined in user information, #79 by @majkel89 + +## 3.0.1 - 2018-12-28 + +### Fixed + +- Correct conflict handling for prompt option, #69 by @mxdpeep + +## 3.0.0 - 2018-12-23 + +### Changed + +- Update to latest version of Google OAuth +- Use only OpenID Connect for user details + +### Fixed + +- Correct handling of selecting from multiple user accounts, #45 +- Prevent conflict when using prompt option, #42 + +### Added + +- Add "locale" to user details, #60 +- Support additional scopes at construction + +### Removed + +- Dropped support for Google+ user details, #34 and #63 + +## 2.2.0 - 2018-03-19 + +### Added + +- Hosted domain validation, #54 by @pradtke + +## 2.1.0 - 2018-03-09 + +### Added + +- OpenID Connect support, #48 by @pradtke + +## 2.0.0 - 2017-01-24 + +### Added + +- PHP 7.1 support + +### Removed + +- Dropped PHP 5.5 support + +## 1.0.0 - 2015-08-12 + +- Initial release diff --git a/lib/league/oauth2-google/CONTRIBUTING.md b/lib/league/oauth2-google/CONTRIBUTING.md new file mode 100644 index 0000000000..84554556ec --- /dev/null +++ b/lib/league/oauth2-google/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing + +Contributions are **welcome** and will be fully **credited**. + +We accept contributions via Pull Requests on [Github](https://github.com/thephpleague/oauth2-google). + + +## 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. + +- **Consider our release cycle** - We try to follow SemVer. Randomly breaking public APIs is not an option. + +- **Create topic branches** - Don't ask us to pull from your master branch. + +- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. + +- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting. + +- **Ensure tests pass!** - Please run the tests (see below) before submitting your pull request, and make sure they pass. We won't accept a patch until all tests pass. + +- **Ensure no coding standards violations** - Please run PHP Code Sniffer using the PSR-2 standard (see below) before submitting your pull request. A violation will cause the build to fail, so please make sure there are no violations. We can't accept a patch if the build fails. + + +## Running Tests + +```sh +composer test +``` + + +## Running PHP Code Sniffer + +```sh +composer check +``` + +**Happy coding**! diff --git a/lib/league/oauth2-google/LICENSE b/lib/league/oauth2-google/LICENSE new file mode 100644 index 0000000000..6d451561ed --- /dev/null +++ b/lib/league/oauth2-google/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Woody Gilk + +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/lib/league/oauth2-google/README.md b/lib/league/oauth2-google/README.md new file mode 100644 index 0000000000..df69dcd122 --- /dev/null +++ b/lib/league/oauth2-google/README.md @@ -0,0 +1,242 @@ +# Google Provider for OAuth 2.0 Client + +[![Join the chat](https://img.shields.io/badge/gitter-join-1DCE73.svg)](https://gitter.im/thephpleague/oauth2-google) +[![Build Status](https://img.shields.io/travis/thephpleague/oauth2-google.svg)](https://travis-ci.org/thephpleague/oauth2-google) +[![Code Coverage](https://img.shields.io/coveralls/thephpleague/oauth2-google.svg)](https://coveralls.io/r/thephpleague/oauth2-google) +[![Code Quality](https://img.shields.io/scrutinizer/g/thephpleague/oauth2-google.svg)](https://scrutinizer-ci.com/g/thephpleague/oauth2-google/) +[![License](https://img.shields.io/packagist/l/league/oauth2-google.svg)](https://github.com/thephpleague/oauth2-google/blob/master/LICENSE) +[![Latest Stable Version](https://img.shields.io/packagist/v/league/oauth2-google.svg)](https://packagist.org/packages/league/oauth2-google) + +This package provides Google OAuth 2.0 support for the PHP League's [OAuth 2.0 Client](https://github.com/thephpleague/oauth2-client). + +This package is compliant with [PSR-1][], [PSR-2][] and [PSR-4][]. If you notice compliance oversights, please send +a patch via pull request. + +[PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md +[PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md +[PSR-4]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md + +## Requirements + +The following versions of PHP are supported. + +* PHP 7.0 +* PHP 7.1 +* PHP 7.2 +* PHP 7.3 +* PHP 7.4 + +This package uses [OpenID Connect][openid-connect] to authenticate users with +Google accounts. + +To use this package, it will be necessary to have a Google client ID and client +secret. These are referred to as `{google-client-id}` and `{google-client-secret}` +in the documentation. + +Please follow the [Google instructions][oauth-setup] to create the required credentials. + +[openid-connect]: https://developers.google.com/identity/protocols/OpenIDConnect +[oauth-setup]: https://developers.google.com/identity/protocols/OpenIDConnect#registeringyourapp + +## Installation + +To install, use composer: + +```sh +composer require league/oauth2-google +``` + +## Usage + +### Authorization Code Flow + +```php +require __DIR__ . '/vendor/autoload.php'; + +use League\OAuth2\Client\Provider\Google; + +session_start(); // Remove if session.auto_start=1 in php.ini + +$provider = new Google([ + 'clientId' => '{google-client-id}', + 'clientSecret' => '{google-client-secret}', + 'redirectUri' => 'https://example.com/callback-url', + 'hostedDomain' => 'example.com', // optional; used to restrict access to users on your G Suite/Google Apps for Business accounts +]); + +if (!empty($_GET['error'])) { + + // Got an error, probably user denied access + exit('Got error: ' . htmlspecialchars($_GET['error'], ENT_QUOTES, 'UTF-8')); + +} elseif (empty($_GET['code'])) { + + // If we don't have an authorization code then get one + $authUrl = $provider->getAuthorizationUrl(); + $_SESSION['oauth2state'] = $provider->getState(); + header('Location: ' . $authUrl); + exit; + +} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { + + // State is invalid, possible CSRF attack in progress + unset($_SESSION['oauth2state']); + exit('Invalid state'); + +} else { + + // Try to get an access token (using the authorization code grant) + $token = $provider->getAccessToken('authorization_code', [ + 'code' => $_GET['code'] + ]); + + // Optional: Now you have a token you can look up a users profile data + try { + + // We got an access token, let's now get the owner details + $ownerDetails = $provider->getResourceOwner($token); + + // Use these details to create a new profile + printf('Hello %s!', $ownerDetails->getFirstName()); + + } catch (Exception $e) { + + // Failed to get user details + exit('Something went wrong: ' . $e->getMessage()); + + } + + // Use this to interact with an API on the users behalf + echo $token->getToken(); + + // Use this to get a new access token if the old one expires + echo $token->getRefreshToken(); + + // Unix timestamp at which the access token expires + echo $token->getExpires(); +} +``` + +#### Available Options + +The `Google` provider has the following [options][auth-params]: + +- `accessType` to use online or offline access +- `hostedDomain` to authenticate G Suite users +- `prompt` to modify the prompt that the user will see +- `scopes` to request access to additional user information + +[auth-params]: https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters + +#### Accessing Token JWT + +Google provides a [JSON Web Token][jwt] (JWT) with all access tokens. This token +[contains basic information][openid-jwt] about the authenticated user. The JWT +can be accessed from the `id_token` value of the access token: + +```php +/** @var League\OAuth2\Client\Token\AccessToken $token */ +$values = $token->getValues(); + +/** @var string */ +$jwt = $values['id_token']; +``` + +Parsing the JWT will require a [JWT parser][jwt-parsers]. Refer to parser +documentation for instructions. + +[jwt]: https://jwt.io/ +[openid-jwt]: https://developers.google.com/identity/protocols/OpenIDConnect#obtainuserinfo +[jwt-parsers]: https://packagist.org/search/?q=jwt + +### Refreshing a Token + +Refresh tokens are only provided to applications which request offline access. You can specify offline access by setting the `accessType` option in your provider: + +```php +use League\OAuth2\Client\Provider\Google; + +$provider = new Google([ + 'clientId' => '{google-client-id}', + 'clientSecret' => '{google-client-secret}', + 'redirectUri' => 'https://example.com/callback-url', + 'accessType' => 'offline', +]); +``` + +It is important to note that the refresh token is only returned on the first request after this it will be `null`. You should securely store the refresh token when it is returned: + +```php +$token = $provider->getAccessToken('authorization_code', [ + 'code' => $code +]); + +// persist the token in a database +$refreshToken = $token->getRefreshToken(); +``` + +If you ever need to get a new refresh token you can request one by forcing the consent prompt: + +```php +$authUrl = $provider->getAuthorizationUrl(['prompt' => 'consent']); +``` + +Now you have everything you need to refresh an access token using a refresh token: + +```php +use League\OAuth2\Client\Provider\Google; +use League\OAuth2\Client\Grant\RefreshToken; + +$provider = new Google([ + 'clientId' => '{google-client-id}', + 'clientSecret' => '{google-client-secret}', + 'redirectUri' => 'https://example.com/callback-url', +]); + +$grant = new RefreshToken(); +$token = $provider->getAccessToken($grant, ['refresh_token' => $refreshToken]); +``` + +## Scopes + +Additional [scopes][scopes] can be set by using the `scope` parameter when +generating the authorization URL: + +```php +$authorizationUrl = $provider->getAuthorizationUrl([ + 'scope' => [ + 'scope-url-here' + ], +]); +``` + +[scopes]: https://developers.google.com/identity/protocols/googlescopes + +## Testing + +Tests can be run with: + +```sh +composer test +``` + +Style checks can be run with: + +```sh +composer check +``` + +## Contributing + +Please see [CONTRIBUTING](https://github.com/thephpleague/oauth2-google/blob/master/CONTRIBUTING.md) for details. + + +## Credits + +- [Woody Gilk](https://github.com/shadowhand) +- [All Contributors](https://github.com/thephpleague/oauth2-google/contributors) + + +## License + +The MIT License (MIT). Please see [License File](https://github.com/thephpleague/oauth2-google/blob/master/LICENSE) for more information. diff --git a/lib/league/oauth2-google/composer.json b/lib/league/oauth2-google/composer.json new file mode 100644 index 0000000000..f34a1cec9e --- /dev/null +++ b/lib/league/oauth2-google/composer.json @@ -0,0 +1,44 @@ +{ + "name": "league/oauth2-google", + "description": "Google OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "license": "MIT", + "authors": [ + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com", + "homepage": "http://shadowhand.me" + } + ], + "keywords": [ + "oauth", + "oauth2", + "client", + "authorization", + "authentication", + "google" + ], + "minimum-stability": "stable", + "require": { + "league/oauth2-client": "^2.0" + }, + "require-dev": { + "eloquent/phony-phpunit": "^2.0", + "phpunit/phpunit": "^6.0", + "php-coveralls/php-coveralls": "^2.1", + "squizlabs/php_codesniffer": "^2.0" + }, + "autoload": { + "psr-4": { + "League\\OAuth2\\Client\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "League\\OAuth2\\Client\\Test\\": "test/src/" + } + }, + "scripts": { + "test": "phpunit", + "check": "phpcs src --standard=psr2 -sp" + } +} diff --git a/lib/league/oauth2-google/examples/index.php b/lib/league/oauth2-google/examples/index.php new file mode 100644 index 0000000000..9138437770 --- /dev/null +++ b/lib/league/oauth2-google/examples/index.php @@ -0,0 +1,35 @@ +getAuthorizationUrl(); + $_SESSION['oauth2state'] = $provider->getState(); + header('Location: ' . $authUrl); + exit; + +} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { + + // State is invalid, possible CSRF attack in progress + unset($_SESSION['oauth2state']); + exit('Invalid state'); + +} else { + + // Try to get an access token (using the authorization code grant) + $token = $provider->getAccessToken('authorization_code', [ + 'code' => $_GET['code'] + ]); + + $_SESSION['token'] = serialize($token); + + // Optional: Now you have a token you can look up a users profile data + header('Location: /user.php'); +} diff --git a/lib/league/oauth2-google/examples/provider.php b/lib/league/oauth2-google/examples/provider.php new file mode 100644 index 0000000000..4001f68570 --- /dev/null +++ b/lib/league/oauth2-google/examples/provider.php @@ -0,0 +1,24 @@ +getResourceOwner($token); + + // Use these details to create a new profile + printf('Hello %s!
                  ', $userDetails->getFirstname()); +} catch (Exception $e) { + // Failed to get user details + exit('Something went wrong: ' . $e->getMessage()); +} + +// Use this to interact with an API on the users behalf +echo "Token is: ", $token->getToken(), "
                  "; + +// Use this to get a new access token if the old one expires +echo "Refresh token is: ", $token->getRefreshToken(), "
                  "; + +// Number of seconds until the access token will expire, and need refreshing +echo "Expires at ", date('r', $token->getExpires()), "
                  "; + +// Allow the user to logout +echo 'Logout
                  '; diff --git a/lib/league/oauth2-google/phpunit.xml.dist b/lib/league/oauth2-google/phpunit.xml.dist new file mode 100644 index 0000000000..7f37586aab --- /dev/null +++ b/lib/league/oauth2-google/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + ./test + + + + + src/ + + + + + + + + diff --git a/lib/league/oauth2-google/src/Exception/HostedDomainException.php b/lib/league/oauth2-google/src/Exception/HostedDomainException.php new file mode 100644 index 0000000000..b38a41580f --- /dev/null +++ b/lib/league/oauth2-google/src/Exception/HostedDomainException.php @@ -0,0 +1,15 @@ +hostedDomain) { + $options['hd'] = $this->hostedDomain; + } + + if (empty($options['access_type']) && $this->accessType) { + $options['access_type'] = $this->accessType; + } + + if (empty($options['prompt']) && $this->prompt) { + $options['prompt'] = $this->prompt; + } + + // Default scopes MUST be included for OpenID Connect. + // Additional scopes MAY be added by constructor or option. + $scopes = array_merge($this->getDefaultScopes(), $this->scopes); + + if (!empty($options['scope'])) { + $scopes = array_merge($scopes, $options['scope']); + } + + $options['scope'] = array_unique($scopes); + + $options = parent::getAuthorizationParameters($options); + + // The "approval_prompt" MUST be removed as it is not supported by Google, use "prompt" instead: + // https://developers.google.com/identity/protocols/oauth2/openid-connect#prompt + unset($options['approval_prompt']); + + return $options; + } + + protected function getDefaultScopes() + { + // "openid" MUST be the first scope in the list. + return [ + 'openid', + 'email', + 'profile', + ]; + } + + protected function getScopeSeparator() + { + return ' '; + } + + protected function checkResponse(ResponseInterface $response, $data) + { + // @codeCoverageIgnoreStart + if (empty($data['error'])) { + return; + } + // @codeCoverageIgnoreEnd + + $code = 0; + $error = $data['error']; + + if (is_array($error)) { + $code = $error['code']; + $error = $error['message']; + } + + throw new IdentityProviderException($error, $code, $data); + } + + protected function createResourceOwner(array $response, AccessToken $token) + { + $user = new GoogleUser($response); + + $this->assertMatchingDomain($user->getHostedDomain()); + + return $user; + } + + /** + * @throws HostedDomainException If the domain does not match the configured domain. + */ + protected function assertMatchingDomain($hostedDomain) + { + if ($this->hostedDomain === null) { + // No hosted domain configured. + return; + } + + if ($this->hostedDomain === '*' && $hostedDomain) { + // Any hosted domain is allowed. + return; + } + + if ($this->hostedDomain === $hostedDomain) { + // Hosted domain is correct. + return; + } + + throw HostedDomainException::notMatchingDomain($this->hostedDomain); + } +} diff --git a/lib/league/oauth2-google/src/Provider/GoogleUser.php b/lib/league/oauth2-google/src/Provider/GoogleUser.php new file mode 100644 index 0000000000..1100b1dbed --- /dev/null +++ b/lib/league/oauth2-google/src/Provider/GoogleUser.php @@ -0,0 +1,112 @@ +response = $response; + } + + public function getId() + { + return $this->response['sub']; + } + + /** + * Get preferred display name. + * + * @return string + */ + public function getName() + { + return $this->response['name']; + } + + /** + * Get preferred first name. + * + * @return string|null + */ + public function getFirstName() + { + return $this->getResponseValue('given_name'); + } + + /** + * Get preferred last name. + * + * @return string|null + */ + public function getLastName() + { + return $this->getResponseValue('family_name'); + } + + /** + * Get locale. + * + * @return string|null + */ + public function getLocale() + { + return $this->getResponseValue('locale'); + } + + /** + * Get email address. + * + * @return string|null + */ + public function getEmail() + { + return $this->getResponseValue('email'); + } + + /** + * Get hosted domain. + * + * @return string|null + */ + public function getHostedDomain() + { + return $this->getResponseValue('hd'); + } + + /** + * Get avatar image URL. + * + * @return string|null + */ + public function getAvatar() + { + return $this->getResponseValue('picture'); + } + + /** + * Get user data as an array. + * + * @return array + */ + public function toArray() + { + return $this->response; + } + + private function getResponseValue($key) + { + if (array_key_exists($key, $this->response)) { + return $this->response[$key]; + } + return null; + } +} diff --git a/lib/necolas/normalize.css/.editorconfig b/lib/necolas/normalize.css/.editorconfig deleted file mode 100644 index 4039ff1115..0000000000 --- a/lib/necolas/normalize.css/.editorconfig +++ /dev/null @@ -1,12 +0,0 @@ -root = true - -[*] -charset = utf-8 -end_of_line = lf -indent_size = 2 -indent_style = space -insert_final_newline = true -trim_trailing_whitespace = true - -[*.md] -trim_trailing_whitespace = false diff --git a/lib/necolas/normalize.css/.gitattributes b/lib/necolas/normalize.css/.gitattributes deleted file mode 100644 index 5e26201fb1..0000000000 --- a/lib/necolas/normalize.css/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -normalize.css linguist-vendored=false -test.html linguist-vendored diff --git a/lib/necolas/normalize.css/.gitignore b/lib/necolas/normalize.css/.gitignore deleted file mode 100644 index 3c3629e647..0000000000 --- a/lib/necolas/normalize.css/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules diff --git a/lib/necolas/normalize.css/.travis.yml b/lib/necolas/normalize.css/.travis.yml deleted file mode 100644 index 833d09d149..0000000000 --- a/lib/necolas/normalize.css/.travis.yml +++ /dev/null @@ -1,3 +0,0 @@ -language: node_js -node_js: - - stable diff --git a/lib/necolas/normalize.css/CHANGELOG.md b/lib/necolas/normalize.css/CHANGELOG.md deleted file mode 100644 index 922f6e38c8..0000000000 --- a/lib/necolas/normalize.css/CHANGELOG.md +++ /dev/null @@ -1,175 +0,0 @@ -# Changes to normalize.css - -### 8.0.1 (November 4, 2018) - -* Fix regression in IE rendering of `main` element. - -### 8.0.0 (February 2, 2018) - -* Remove support for older browsers Android 4, lte IE 9, lte Safari 7. -* Don't remove search input cancel button in Chrome/Safari. -* Form inputs inherit `font-family`. -* Fix text decoration in Safari 8+. - -### 7.0.0 (May 2, 2017) - -* Revert changes in `body` and form elements styles introduced by v6 - -### 6.0.0 (March 26, 2017) - -* Remove all opinionated rules -* Correct document heading comment -* Update `abbr[title]` support - -### 5.0.0 (October 3, 2016) - -* Add normalized sections not already present from - https://html.spec.whatwg.org/multipage/. -* Move unsorted rules into their respective sections. -* Update the `summary` style in all browsers. -* Remove `::placeholder` styles due to a bug in Edge. -* More explicitly define font resets on form controls. -* Remove the `optgroup` normalization needed by the previous font reset. -* Update text-size-adjust documentation
 for IE on Windows Phone -* Update OS X reference to macOS -* Update the semver strategy. - -### 4.2.0 (June 30, 2016) - -* Correct the `line-height` in all browsers. -* Restore `optgroup` font inheritance. -* Update normalize.css heading. - -### 4.1.1 (April 12, 2016) - -* Update normalize.css heading. - -### 4.1.0 (April 11, 2016) - -* Normalize placeholders in Chrome, Edge, and Safari. -* Normalize `text-decoration-skip` property in Safari. -* Normalize file select buttons. -* Normalize search input outlines in Safari. -* Limit Firefox focus normalizations to buttons. -* Restore `main` to package.json. -* Restore proper overflow to certain `select` elements. -* Remove opinionated cursor styles on buttons. -* Update stylelint configuration. -* Update tests. - -### 4.0.0 (March 19, 2016) - -* Add the correct font weight for `b` and `strong` in Chrome, Edge, and Safari. -* Correct inconsistent `overflow` for `hr` in Edge and IE. -* Correct inconsistent `box-sizing` for `hr` in Firefox. -* Correct inconsistent `text-decoration` and `border-bottom` for `abbr[title]` - in Chrome, Edge, Firefox IE, Opera, and Safari. -* Correct inheritance and scaling of `font-size` for preformatted text. -* Correct `legend` text wrapping not present in Edge and IE. -* Remove unnecessary normalization of `line-height` for `input`. -* Remove unnecessary normalization of `color` for form controls. -* Remove unnecessary `box-sizing` for `input[type="search"]` in Chrome, Edge, - Firefox, IE, and Safari. -* Remove opinionated table resets. -* Remove opinionated `pre` overflow. -* Remove selector weight from some input selectors. -* Update normalization of `border-style` for `img`. -* Update normalization of `color` inheritance for `legend`. -* Update normalization of `background-color` for `mark`. -* Update normalization of `outline` for `:-moz-focusring` removed by a previous - normalization in Firefox. -* Update opinionated style of `outline-width` for `a:active` and `a:hover`. -* Update comments to identify opinionated styles. -* Update comments to specify browser/versions affected by all changes. -* Update comments to use one voice. - ---- - -### 3.0.3 (March 30, 2015) - -* Remove unnecessary vendor prefixes. -* Add `main` property. - -### 3.0.2 (October 4, 2014) - -* Only alter `background-color` of links in IE 10. -* Add `menu` element to HTML5 display definitions. - -### 3.0.1 (March 27, 2014) - -* Add package.json for npm support. - -### 3.0.0 (January 28, 2014) - -### 3.0.0-rc.1 (January 26, 2014) - -* Explicit tests for each normalization. -* Fix i18n for `q` element. -* Fix `pre` text formatting and overflow. -* Fix vertical alignment of `progress`. -* Address `button` overflow in IE 8/9/10. -* Revert `textarea` alignment modification. -* Fix number input button cursor in Chrome on OS X. -* Remove `a:focus` outline normalization. -* Fix `figure` margin normalization. -* Normalize `optgroup`. -* Remove default table cell padding. -* Set correct display for `progress` in IE 8/9. -* Fix `font` and `color` inheritance for forms. - ---- - -### 2.1.3 (August 26, 2013) - -* Fix component.json. -* Remove the gray background color from active links in IE 10. - -### 2.1.2 (May 11, 2013) - -* Revert root `color` and `background` normalizations. - -### 2.1.1 (April 8, 2013) - -* Normalize root `color` and `background` to counter the effects of system - color schemes. - -### 2.1.0 (January 21, 2013) - -* Normalize `text-transform` for `button` and `select`. -* Normalize `h1` margin when within HTML5 sectioning elements. -* Normalize `hr` element. -* Remove unnecessary `pre` styles. -* Add `main` element to HTML5 display definitions. -* Fix cursor style for disabled button `input`. - -### 2.0.1 (August 20, 2012) - -* Remove stray IE 6/7 `inline-block` hack from HTML5 display settings. - -### 2.0.0 (August 19, 2012) - -* Remove legacy browser form normalizations. -* Remove all list normalizations. -* Add `quotes` normalizations. -* Remove all heading normalizations except `h1` font size. -* Form elements automatically inherit `font-family` from ancestor. -* Drop support for IE 6/7, Firefox < 4, and Safari < 5. - ---- - -### 1.0.1 (August 19, 2012) - -* Adjust `small` font size normalization. - -### 1.0.0 (August 14, 2012) - -(Only the notable changes since public release) - -* Add MIT License. -* Hide `audio` elements without controls in iOS 5. -* Normalize heading margins and font size. -* Move font-family normalization from `body` to `html`. -* Remove scrollbar normalization. -* Remove excess padding from checkbox and radio inputs in IE 7. -* Add IE9 correction for SVG overflow. -* Add fix for legend not inheriting color in IE 6/7/8/9. diff --git a/lib/necolas/normalize.css/CONTRIBUTING.md b/lib/necolas/normalize.css/CONTRIBUTING.md deleted file mode 100644 index fa84fa9561..0000000000 --- a/lib/necolas/normalize.css/CONTRIBUTING.md +++ /dev/null @@ -1,197 +0,0 @@ -# Contributing to normalize.css - -Please take a moment to review this document in order to make the contribution -process easy and effective for everyone involved. - -Following these guidelines helps to communicate that you respect the time of -the developers managing and developing this open source project. In return, -they should reciprocate that respect in addressing your issue or assessing -patches and features. - - -## Using the issue tracker - -The issue tracker is the preferred channel for [bug reports](#bugs), -[features requests](#features) and [submitting pull -requests](#pull-requests), but please respect the following restrictions: - -* Please **do not** use the issue tracker for personal support requests. - -* Please **do not** derail or troll issues. Keep the discussion on topic and - respect the opinions of others. - - -## Bug reports - -A bug is a _demonstrable problem_ that is caused by the code in the repository. -Good bug reports are extremely helpful - thank you! - -Guidelines for bug reports: - -1. **Use the GitHub issue search** – check if the issue has already been - reported. - -2. **Check if the issue has been fixed** – try to reproduce it using the - latest `master` branch in the repository. - -3. **Isolate the problem** – create a live example (e.g., on - [Codepen](http://codepen.io)) of a [reduced test - case](http://css-tricks.com/6263-reduced-test-cases/). - -A good bug report shouldn't leave others needing to chase you up for more -information. Please try to be as detailed as possible in your report. What is -your environment? What steps will reproduce the issue? What browser(s) and OS -experience the problem? What would you expect to be the outcome? All these -details will help people to fix any potential bugs. - -Example: - -> Short and descriptive example bug report title -> -> A summary of the issue and the browser/OS environment in which it occurs. If -> suitable, include the steps required to reproduce the bug. -> -> 1. This is the first step -> 2. This is the second step -> 3. Further steps, etc. -> -> `` - a link to the reduced test case -> -> Any other information you want to share that is relevant to the issue being -> reported. This might include the lines of code that you have identified as -> causing the bug, and potential solutions (and your opinions on their -> merits). - - -## Feature requests - -Feature requests are welcome. But take a moment to find out whether your idea -fits with the scope and aims of the project. It's up to *you* to make a strong -case to convince the project's developers of the merits of this feature. Please -provide as much detail and context as possible. - - -## Pull requests - -Good pull requests - patches, improvements, new features - are a fantastic -help. They should remain focused in scope and avoid containing unrelated -commits. - -**Please ask first** before embarking on any significant work, otherwise you -risk spending a lot of time working on something that the project's developers -might not want to merge into the project. - -Please adhere to the coding conventions used throughout a project (whitespace, -accurate comments, etc.) and any other requirements (such as test coverage). - -Follow this process if you'd like your work considered for inclusion in the -project: - -1. [Fork](https://help.github.com/articles/fork-a-repo/) the project, clone your - fork, and configure the remotes: - - ```bash - # Clone your fork of the repo into the current directory - git clone https://github.com//normalize.css - # Navigate to the newly cloned directory - cd normalize.css - # Assign the original repo to a remote called "upstream" - git remote add upstream https://github.com/necolas/normalize.css - ``` - -2. If you cloned a while ago, get the latest changes from upstream: - - ```bash - git checkout master - git pull upstream master - ``` - -3. Never work directly on `master`. Create a new topic branch (off the latest - version of `master`) to contain your feature, change, or fix: - - ```bash - git checkout -b - ``` - -4. Commit your changes in logical chunks. Please adhere to these [git commit - message conventions](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) - or your code is unlikely be merged into the main project. Use Git's - [interactive rebase](https://help.github.com/articles/interactive-rebase) - feature to tidy up your commits before making them public. - - Be sure to add a test to the `test.html` file if appropriate, and test - your change in all supported browsers. - -5. Locally rebase the upstream development branch into your topic branch: - - ```bash - git pull --rebase upstream master - ``` - -6. Push your topic branch up to your fork: - - ```bash - git push origin - ``` - -10. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) - with a clear title and description. - -**IMPORTANT**: By submitting a patch, you agree to allow the project owner to -license your work under the same license as that used by the project. - -### CSS Conventions - -Keep the CSS file as readable as possible by following these guidelines: - -- Comments are short and to the point. -- Comments without a number reference the entire rule. -- Comments describe the selector when the selector does not make the - normalization obvious. -- Comments begin with “Correct the...” when they deal with less obvious side - effects. -- Rules are sorted by cascade, specificity, and then alphabetic order. -- Selectors are sorted by specificity and then alphabetic order. -- `in browser` applies to all versions. -- `in browser v-` applies to all versions up to and including the version. -- `in browser v+` applies to all versions after and including the version. -- `in browser v-v` applies to all versions including and between the versions. - - -## Maintainers - -If you have commit access, please follow this process for merging patches and -cutting new releases. - -### Accepting patches - -1. Check that a patch is within the scope and philosophy of the project. -2. Check that a patch has any necessary tests and a proper, descriptive commit - message. -3. Test the patch locally. -4. Do not use GitHub's merge button. Apply the patch to `master` locally - (either via `git am` or by checking the whole branch out). Amend minor - problems with the author's original commit if necessary. Then push to GitHub. - -### Releasing a new version - -1. Include all new functional changes in the CHANGELOG. -2. Use a dedicated commit to increment the version. The version needs to be - added to the CHANGELOG (inc. date), the `package.json`, and `normalize.css` - files. -3. The commit message must be of `v0.0.0` format. -4. Create an annotated tag for the version: `git tag -m "v0.0.0" 0.0.0`. -5. Push the changes and tags to GitHub: `git push --tags origin master` -6. Checkout the `gh-pages` branch and follow the instructions in the README. - -### Semver strategy - -[Semver](http://semver.org/) is a widely accepted method for deciding how -version numbers are incremented in a project. Versions are written as -MAJOR.MINOR.PATCH. - -Any change to CSS rules whatsoever is considered backwards-breaking and will -result in a new **major** release. No changes to CSS rules can add -functionality in a backwards-compatible manner, therefore no changes are -considered **minor**. Others changes with no impact on rendering are considered -backwards-compatible and will result in a new **patch** release. diff --git a/lib/necolas/normalize.css/LICENSE.md b/lib/necolas/normalize.css/LICENSE.md deleted file mode 100644 index 43b5ddcc90..0000000000 --- a/lib/necolas/normalize.css/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -# The MIT License (MIT) - -Copyright © Nicolas Gallagher and Jonathan Neal - -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/lib/necolas/normalize.css/README.md b/lib/necolas/normalize.css/README.md deleted file mode 100644 index 71954f2306..0000000000 --- a/lib/necolas/normalize.css/README.md +++ /dev/null @@ -1,102 +0,0 @@ -# normalize.css - -Normalize Logo - -> A modern alternative to CSS resets - -[![npm][npm-image]][npm-url] [![license][license-image]][license-url] -[![changelog][changelog-image]][changelog-url] -[![gitter][gitter-image]][gitter-url] - - -**NPM** - -```sh -npm install --save normalize.css -``` - -**CDN** - -See https://yarnpkg.com/en/package/normalize.css - -**Download** - -See https://necolas.github.io/normalize.css/latest/normalize.css - - -## What does it do? - -* Preserves useful defaults, unlike many CSS resets. -* Normalizes styles for a wide range of elements. -* Corrects bugs and common browser inconsistencies. -* Improves usability with subtle modifications. -* Explains what code does using detailed comments. - - -## Browser support - -* Chrome -* Edge -* Firefox ESR+ -* Internet Explorer 10+ -* Safari 8+ -* Opera - - -## Extended details and known issues - -Additional detail and explanation of the esoteric parts of normalize.css. - -#### `pre, code, kbd, samp` - -The `font-family: monospace, monospace` hack fixes the inheritance and scaling -of font-size for preformatted text. The duplication of `monospace` is -intentional. [Source](https://en.wikipedia.org/wiki/User:Davidgothberg/Test59). - -#### `sub, sup` - -Normally, using `sub` or `sup` affects the line-box height of text in all -browsers. [Source](https://gist.github.com/413930). - -#### `select` - -By default, Chrome on OS X and Safari on OS X allow very limited styling of -`select`, unless a border property is set. The default font weight on `optgroup` -elements cannot safely be changed in Chrome on OSX and Safari on OS X. - -#### `[type="checkbox"]` - -It is recommended that you do not style checkbox and radio inputs as Firefox's -implementation does not respect box-sizing, padding, or width. - -#### `[type="number"]` - -Certain font size values applied to number inputs cause the cursor style of the -decrement button to change from `default` to `text`. - -#### `[type="search"]` - -The search input is not fully stylable by default. In Chrome and Safari on -OSX/iOS you can't control `font`, `padding`, `border`, or `background`. In -Chrome and Safari on Windows you can't control `border` properly. It will apply -`border-width` but will only show a border color (which cannot be controlled) -for the outer 1px of that border. Applying `-webkit-appearance: textfield` -addresses these issues without removing the benefits of search inputs (e.g. -showing past searches). - -## Contributing - -Please read the [contribution guidelines](CONTRIBUTING.md) in order to make the -contribution process easy and effective for everyone involved. - - -[changelog-image]: https://img.shields.io/badge/changelog-md-blue.svg?style=flat-square -[changelog-url]: CHANGELOG.md -[license-image]: https://img.shields.io/npm/l/normalize.css.svg?style=flat-square -[license-url]: LICENSE.md -[npm-image]: https://img.shields.io/npm/v/normalize.css.svg?style=flat-square -[npm-url]: https://www.npmjs.com/package/normalize.css -[gitter-image]: https://img.shields.io/badge/chat-gitter-blue.svg?style=flat-square -[gitter-url]: https://gitter.im/necolas/normalize.css diff --git a/lib/necolas/normalize.css/normalize.css b/lib/necolas/normalize.css/normalize.css deleted file mode 100644 index 192eb9ce43..0000000000 --- a/lib/necolas/normalize.css/normalize.css +++ /dev/null @@ -1,349 +0,0 @@ -/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ - -/* Document - ========================================================================== */ - -/** - * 1. Correct the line height in all browsers. - * 2. Prevent adjustments of font size after orientation changes in iOS. - */ - -html { - line-height: 1.15; /* 1 */ - -webkit-text-size-adjust: 100%; /* 2 */ -} - -/* Sections - ========================================================================== */ - -/** - * Remove the margin in all browsers. - */ - -body { - margin: 0; -} - -/** - * Render the `main` element consistently in IE. - */ - -main { - display: block; -} - -/** - * Correct the font size and margin on `h1` elements within `section` and - * `article` contexts in Chrome, Firefox, and Safari. - */ - -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -/* Grouping content - ========================================================================== */ - -/** - * 1. Add the correct box sizing in Firefox. - * 2. Show the overflow in Edge and IE. - */ - -hr { - box-sizing: content-box; /* 1 */ - height: 0; /* 1 */ - overflow: visible; /* 2 */ -} - -/** - * 1. Correct the inheritance and scaling of font size in all browsers. - * 2. Correct the odd `em` font sizing in all browsers. - */ - -pre { - font-family: monospace, monospace; /* 1 */ - font-size: 1em; /* 2 */ -} - -/* Text-level semantics - ========================================================================== */ - -/** - * Remove the gray background on active links in IE 10. - */ - -a { - background-color: transparent; -} - -/** - * 1. Remove the bottom border in Chrome 57- - * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. - */ - -abbr[title] { - border-bottom: none; /* 1 */ - text-decoration: underline; /* 2 */ - text-decoration: underline dotted; /* 2 */ -} - -/** - * Add the correct font weight in Chrome, Edge, and Safari. - */ - -b, -strong { - font-weight: bolder; -} - -/** - * 1. Correct the inheritance and scaling of font size in all browsers. - * 2. Correct the odd `em` font sizing in all browsers. - */ - -code, -kbd, -samp { - font-family: monospace, monospace; /* 1 */ - font-size: 1em; /* 2 */ -} - -/** - * Add the correct font size in all browsers. - */ - -small { - font-size: 80%; -} - -/** - * Prevent `sub` and `sup` elements from affecting the line height in - * all browsers. - */ - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -sub { - bottom: -0.25em; -} - -sup { - top: -0.5em; -} - -/* Embedded content - ========================================================================== */ - -/** - * Remove the border on images inside links in IE 10. - */ - -img { - border-style: none; -} - -/* Forms - ========================================================================== */ - -/** - * 1. Change the font styles in all browsers. - * 2. Remove the margin in Firefox and Safari. - */ - -button, -input, -optgroup, -select, -textarea { - font-family: inherit; /* 1 */ - font-size: 100%; /* 1 */ - line-height: 1.15; /* 1 */ - margin: 0; /* 2 */ -} - -/** - * Show the overflow in IE. - * 1. Show the overflow in Edge. - */ - -button, -input { /* 1 */ - overflow: visible; -} - -/** - * Remove the inheritance of text transform in Edge, Firefox, and IE. - * 1. Remove the inheritance of text transform in Firefox. - */ - -button, -select { /* 1 */ - text-transform: none; -} - -/** - * Correct the inability to style clickable types in iOS and Safari. - */ - -button, -[type="button"], -[type="reset"], -[type="submit"] { - -webkit-appearance: button; -} - -/** - * Remove the inner border and padding in Firefox. - */ - -button::-moz-focus-inner, -[type="button"]::-moz-focus-inner, -[type="reset"]::-moz-focus-inner, -[type="submit"]::-moz-focus-inner { - border-style: none; - padding: 0; -} - -/** - * Restore the focus styles unset by the previous rule. - */ - -button:-moz-focusring, -[type="button"]:-moz-focusring, -[type="reset"]:-moz-focusring, -[type="submit"]:-moz-focusring { - outline: 1px dotted ButtonText; -} - -/** - * Correct the padding in Firefox. - */ - -fieldset { - padding: 0.35em 0.75em 0.625em; -} - -/** - * 1. Correct the text wrapping in Edge and IE. - * 2. Correct the color inheritance from `fieldset` elements in IE. - * 3. Remove the padding so developers are not caught out when they zero out - * `fieldset` elements in all browsers. - */ - -legend { - box-sizing: border-box; /* 1 */ - color: inherit; /* 2 */ - display: table; /* 1 */ - max-width: 100%; /* 1 */ - padding: 0; /* 3 */ - white-space: normal; /* 1 */ -} - -/** - * Add the correct vertical alignment in Chrome, Firefox, and Opera. - */ - -progress { - vertical-align: baseline; -} - -/** - * Remove the default vertical scrollbar in IE 10+. - */ - -textarea { - overflow: auto; -} - -/** - * 1. Add the correct box sizing in IE 10. - * 2. Remove the padding in IE 10. - */ - -[type="checkbox"], -[type="radio"] { - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Correct the cursor style of increment and decrement buttons in Chrome. - */ - -[type="number"]::-webkit-inner-spin-button, -[type="number"]::-webkit-outer-spin-button { - height: auto; -} - -/** - * 1. Correct the odd appearance in Chrome and Safari. - * 2. Correct the outline style in Safari. - */ - -[type="search"] { - -webkit-appearance: textfield; /* 1 */ - outline-offset: -2px; /* 2 */ -} - -/** - * Remove the inner padding in Chrome and Safari on macOS. - */ - -[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -/** - * 1. Correct the inability to style clickable types in iOS and Safari. - * 2. Change font properties to `inherit` in Safari. - */ - -::-webkit-file-upload-button { - -webkit-appearance: button; /* 1 */ - font: inherit; /* 2 */ -} - -/* Interactive - ========================================================================== */ - -/* - * Add the correct display in Edge, IE 10+, and Firefox. - */ - -details { - display: block; -} - -/* - * Add the correct display in all browsers. - */ - -summary { - display: list-item; -} - -/* Misc - ========================================================================== */ - -/** - * Add the correct display in IE 10+. - */ - -template { - display: none; -} - -/** - * Add the correct display in IE 10. - */ - -[hidden] { - display: none; -} diff --git a/lib/necolas/normalize.css/package-lock.json b/lib/necolas/normalize.css/package-lock.json deleted file mode 100644 index 7f8590b4d9..0000000000 --- a/lib/necolas/normalize.css/package-lock.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "normalize.css", - "version": "8.0.1", - "lockfileVersion": 1 -} diff --git a/lib/necolas/normalize.css/package.json b/lib/necolas/normalize.css/package.json deleted file mode 100644 index 668bda8a40..0000000000 --- a/lib/necolas/normalize.css/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "normalize.css", - "version": "8.0.1", - "description": "A modern alternative to CSS resets", - "main": "normalize.css", - "style": "normalize.css", - "files": [ - "LICENSE.md", - "normalize.css" - ], - "repository": "necolas/normalize.css", - "license": "MIT", - "bugs": "https://github.com/necolas/normalize.css/issues", - "homepage": "https://necolas.github.io/normalize.css" -} diff --git a/lib/necolas/normalize.css/test.html b/lib/necolas/normalize.css/test.html deleted file mode 100644 index 615b08326d..0000000000 --- a/lib/necolas/normalize.css/test.html +++ /dev/null @@ -1,441 +0,0 @@ - - - - -Normalize CSS: UI tests - - - - -
                  - . - -

                  Normalize.css: UI tests

                  - -

                  html

                  -

                  should have a line height of 1.15

                  -
                  - abcdefghijklmnopqrstuvwxyz -
                  - -

                  body

                  -

                  should have no margin (opinionated)

                  -
                  - (there should be no red background visible on this page) -
                  - -

                  - article, aside, details, - figure, figcaption, footer, - header, main, - menu, nav, section, - summary -

                  -

                  should render as block

                  -
                  -
                  article
                  - -
                  - summary - details -
                  -
                  - figure -
                  figcaption
                  -
                  -
                  footer
                  -
                  header
                  -
                  main
                  -
                • menu
                • - -
                  section
                  -
                  - -

                  audio, canvas, progress, video

                  -

                  should render as inline-block and baseline-aligned

                  -
                  - - canvas - progress - -
                  - -

                  audio:not([controls]), template, [hidden]

                  -

                  should not display

                  -
                  - - - -
                  - -

                  a

                  -

                  should have a transparent background when active

                  - -

                  should not skip underlines

                  - -

                  should not have a focus outline when both focused and hovered (opinionated)

                  - - -

                  abbr[title]

                  -

                  should have a dotted underline with a solid underline as a fallback

                  -
                  - abbr -
                  - -

                  b, strong

                  -

                  should have bolder font-weight

                  -
                  - b - strong -
                  - -

                  dfn

                  -

                  should have italic font-style

                  -
                  - dfn -
                  - -

                  h1

                  -

                  should not change size within an article

                  -
                  -

                  Heading (control)

                  -
                  -

                  Heading (in article)

                  -
                  -
                  -

                  should not change size within a section

                  -
                  -

                  Heading (control)

                  -
                  -

                  Heading (in section)

                  -
                  -
                  - -

                  mark

                  -

                  should have a yellow background

                  -
                  - mark -
                  - -

                  small

                  -

                  should render equally small in all browsers

                  -
                  - control. small. -
                  - -

                  sub and sup

                  -

                  should not affect a line's visual line-height

                  -
                  -

                  control.

                  -

                  control. sub.

                  -

                  control. sup.

                  -
                  - -

                  img

                  -

                  should not have a border when wrapped in an anchor

                  - - -

                  svg

                  -

                  should not overflow

                  -
                  - - - -
                  - -

                  code, kbd, pre, samp

                  -

                  should render text at the same absolute size as normal text

                  -
                  - span: abcdefghijklmnopqrstuvwxyz.
                  - code: abcdefghijklmnopqrstuvwxyz.
                  - kbd: abcdefghijklmnopqrstuvwxyz.
                  - samp: abcdefghijklmnopqrstuvwxyz. -
                  pre: abcdefghijklmnopqrstuvwxyz.
                  -
                  - -

                  figure

                  -

                  should have margins

                  -
                  -
                  - -
                  -
                  - -

                  hr

                  -

                  should have a content-box box model

                  -
                  -
                  -
                  - -

                  button, input, optgroup, select, textarea

                  -

                  should inherit font-size from ancestor

                  -
                  -
                  -
                  -
                  - -
                  -

                  should not have margins

                  -
                  - - - - - -
                  - -

                  button

                  -

                  should have visible overflow

                  -
                  - - -
                  - -

                  button, select

                  -

                  should not inherit text-transform

                  -
                  - - -
                  - -

                  button and button-style input

                  -

                  should be styleable

                  -
                  - -

                  -

                  -

                  -

                  -

                  -

                  -
                  - -

                  disabled button and input

                  -

                  should have default cursor style

                  -
                  -

                  -

                  -

                  -

                  -
                  - -

                  button, input

                  -

                  should not have extra inner padding in Firefox

                  -
                  - -

                  -

                  -

                  -

                  -
                  - -

                  fieldset

                  -

                  should have consistent border, padding, and margin

                  -
                  -
                  -
                  -
                  -
                  - -

                  legend

                  -

                  should inherit color

                  -
                  -
                  - legend -
                  -
                  -

                  should not have padding

                  -
                  -
                  - legend -
                  -
                  -

                  should wrap text

                  -
                  -
                  - Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et me. -
                  -
                  - -

                  textarea

                  -

                  should not have a scrollbar unless overflowing

                  -
                  - -
                  - -

                  [type="checkbox"], [type="radio"]

                  -

                  should have a border-box box model

                  -
                  - - - -
                  -

                  should not have padding

                  -
                  - - -
                  - -

                  [type="number"]

                  -

                  should display a default cursor for the decrement button's click target in Chrome

                  -
                  - -
                  - -

                  [type="search"]

                  -

                  should be styleable

                  -
                  - -
                  -

                  should reference inherited color

                  -
                  - -
                  - -
                  diff --git a/lib/nikic/php-parser/.editorconfig b/lib/nikic/php-parser/.editorconfig deleted file mode 100644 index 9c76d07083..0000000000 --- a/lib/nikic/php-parser/.editorconfig +++ /dev/null @@ -1,9 +0,0 @@ -root = true - -[*.y] -charset = utf-8 -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true -indent_style = space -indent_size = 4 diff --git a/lib/nikic/php-parser/README.md b/lib/nikic/php-parser/README.md index e5b26bf5c2..708cdfcbd7 100644 --- a/lib/nikic/php-parser/README.md +++ b/lib/nikic/php-parser/README.md @@ -3,10 +3,10 @@ PHP Parser [![Coverage Status](https://coveralls.io/repos/github/nikic/PHP-Parser/badge.svg?branch=master)](https://coveralls.io/github/nikic/PHP-Parser?branch=master) -This is a PHP 5.2 to PHP 8.0 parser written in PHP. Its purpose is to simplify static code analysis and +This is a PHP 5.2 to PHP 8.1 parser written in PHP. Its purpose is to simplify static code analysis and manipulation. -[**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.0). +[**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.1). [Documentation for version 3.x][doc_3_x] (unsupported; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2). diff --git a/lib/nikic/php-parser/grammar/php5.y b/lib/nikic/php-parser/grammar/php5.y index f9e7e7dd16..a62e9a310c 100644 --- a/lib/nikic/php-parser/grammar/php5.y +++ b/lib/nikic/php-parser/grammar/php5.y @@ -689,9 +689,7 @@ array_expr: scalar_dereference: array_expr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } - | T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']' - { $attrs = attributes(); $attrs['kind'] = strKind($1); - $$ = Expr\ArrayDimFetch[new Scalar\String_(Scalar\String_::parse($1), $attrs), $3]; } + | T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[Scalar\String_::fromString($1, attributes()), $3]; } | constant '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } | scalar_dereference '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } /* alternative array syntax missing intentionally */ @@ -793,10 +791,8 @@ ctor_arguments: common_scalar: T_LNUMBER { $$ = $this->parseLNumber($1, attributes(), true); } - | T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; } - | T_CONSTANT_ENCAPSED_STRING - { $attrs = attributes(); $attrs['kind'] = strKind($1); - $$ = new Scalar\String_(Scalar\String_::parse($1, false), $attrs); } + | T_DNUMBER { $$ = Scalar\DNumber::fromString($1, attributes()); } + | T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes(), false); } | T_LINE { $$ = Scalar\MagicConst\Line[]; } | T_FILE { $$ = Scalar\MagicConst\File[]; } | T_DIR { $$ = Scalar\MagicConst\Dir[]; } diff --git a/lib/nikic/php-parser/grammar/php7.y b/lib/nikic/php-parser/grammar/php7.y index d9a450379a..087bc7392e 100644 --- a/lib/nikic/php-parser/grammar/php7.y +++ b/lib/nikic/php-parser/grammar/php7.y @@ -41,12 +41,12 @@ semi_reserved: | T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC | T_READONLY ; -identifier_ex: +identifier_maybe_reserved: T_STRING { $$ = Node\Identifier[$1]; } | semi_reserved { $$ = Node\Identifier[$1]; } ; -identifier: +identifier_not_reserved: T_STRING { $$ = Node\Identifier[$1]; } ; @@ -181,14 +181,14 @@ non_empty_inline_use_declarations: unprefixed_use_declaration: namespace_name { $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); } - | namespace_name T_AS identifier + | namespace_name T_AS identifier_not_reserved { $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); } ; use_declaration: legacy_namespace_name { $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); } - | legacy_namespace_name T_AS identifier + | legacy_namespace_name T_AS identifier_not_reserved { $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); } ; @@ -208,7 +208,7 @@ non_empty_constant_declaration_list: ; constant_declaration: - identifier '=' expr { $$ = Node\Const_[$1, $3]; } + identifier_not_reserved '=' expr { $$ = Node\Const_[$1, $3]; } ; class_const_list: @@ -221,7 +221,7 @@ non_empty_class_const_list: ; class_const: - identifier_ex '=' expr { $$ = Node\Const_[$1, $3]; } + identifier_maybe_reserved '=' expr { $$ = Node\Const_[$1, $3]; } ; inner_statement_list_ex: @@ -289,8 +289,8 @@ non_empty_statement: | T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; } | T_TRY '{' inner_statement_list '}' catches optional_finally { $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); } - | T_GOTO identifier semi { $$ = Stmt\Goto_[$2]; } - | identifier ':' { $$ = Stmt\Label[$1]; } + | T_GOTO identifier_not_reserved semi { $$ = Stmt\Goto_[$2]; } + | identifier_not_reserved ':' { $$ = Stmt\Label[$1]; } | error { $$ = array(); /* means: no statement */ } ; @@ -351,22 +351,22 @@ block_or_error: ; function_declaration_statement: - T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error + T_FUNCTION optional_ref identifier_not_reserved '(' parameter_list ')' optional_return_type block_or_error { $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; } - | attributes T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error + | attributes T_FUNCTION optional_ref identifier_not_reserved '(' parameter_list ')' optional_return_type block_or_error { $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; } ; class_declaration_statement: - optional_attributes class_entry_type identifier extends_from implements_list '{' class_statement_list '}' + optional_attributes class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}' { $$ = Stmt\Class_[$3, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]]; $this->checkClass($$, #3); } - | optional_attributes T_INTERFACE identifier interface_extends_list '{' class_statement_list '}' + | optional_attributes T_INTERFACE identifier_not_reserved interface_extends_list '{' class_statement_list '}' { $$ = Stmt\Interface_[$3, ['extends' => $4, 'stmts' => $6, 'attrGroups' => $1]]; $this->checkInterface($$, #3); } - | optional_attributes T_TRAIT identifier '{' class_statement_list '}' + | optional_attributes T_TRAIT identifier_not_reserved '{' class_statement_list '}' { $$ = Stmt\Trait_[$3, ['stmts' => $5, 'attrGroups' => $1]]; } - | optional_attributes T_ENUM identifier enum_scalar_type implements_list '{' class_statement_list '}' + | optional_attributes T_ENUM identifier_not_reserved enum_scalar_type implements_list '{' class_statement_list '}' { $$ = Stmt\Enum_[$3, ['scalarType' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]]; $this->checkEnum($$, #3); } ; @@ -382,8 +382,18 @@ enum_case_expr: class_entry_type: T_CLASS { $$ = 0; } - | T_ABSTRACT T_CLASS { $$ = Stmt\Class_::MODIFIER_ABSTRACT; } - | T_FINAL T_CLASS { $$ = Stmt\Class_::MODIFIER_FINAL; } + | class_modifiers T_CLASS { $$ = $1; } +; + +class_modifiers: + class_modifier { $$ = $1; } + | class_modifiers class_modifier { $this->checkClassModifier($1, $2, #2); $$ = $1 | $2; } +; + +class_modifier: + T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; } + | T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; } + | T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; } ; extends_from: @@ -436,7 +446,7 @@ non_empty_declare_list: ; declare_list_element: - identifier '=' expr { $$ = Stmt\DeclareDeclare[$1, $3]; } + identifier_not_reserved '=' expr { $$ = Stmt\DeclareDeclare[$1, $3]; } ; switch_case_list: @@ -530,24 +540,29 @@ non_empty_parameter_list: | non_empty_parameter_list ',' parameter { push($1, $3); } ; -optional_visibility_modifier: +optional_property_modifiers: /* empty */ { $$ = 0; } - | T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; } + | optional_property_modifiers property_modifier + { $this->checkModifier($1, $2, #2); $$ = $1 | $2; } +; + +property_modifier: + T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; } | T_PROTECTED { $$ = Stmt\Class_::MODIFIER_PROTECTED; } | T_PRIVATE { $$ = Stmt\Class_::MODIFIER_PRIVATE; } | T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; } ; parameter: - optional_attributes optional_visibility_modifier optional_type_without_static + optional_attributes optional_property_modifiers optional_type_without_static optional_arg_ref optional_ellipsis plain_variable { $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1); $this->checkParam($$); } - | optional_attributes optional_visibility_modifier optional_type_without_static + | optional_attributes optional_property_modifiers optional_type_without_static optional_arg_ref optional_ellipsis plain_variable '=' expr { $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1); $this->checkParam($$); } - | optional_attributes optional_visibility_modifier optional_type_without_static + | optional_attributes optional_property_modifiers optional_type_without_static optional_arg_ref optional_ellipsis error { $$ = new Node\Param(Expr\Error[], null, $3, $4, $5, attributes(), $2, $1); } ; @@ -556,6 +571,7 @@ type_expr: type { $$ = $1; } | '?' type { $$ = Node\NullableType[$2]; } | union_type { $$ = Node\UnionType[$1]; } + | intersection_type { $$ = Node\IntersectionType[$1]; } ; type: @@ -579,10 +595,24 @@ union_type_without_static: | union_type_without_static '|' type_without_static { push($1, $3); } ; +intersection_type: + type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type { init($1, $3); } + | intersection_type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type + { push($1, $3); } +; + +intersection_type_without_static: + type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static + { init($1, $3); } + | intersection_type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static + { push($1, $3); } +; + type_expr_without_static: type_without_static { $$ = $1; } | '?' type_without_static { $$ = Node\NullableType[$2]; } | union_type_without_static { $$ = Node\UnionType[$1]; } + | intersection_type_without_static { $$ = Node\IntersectionType[$1]; } ; optional_type_without_static: @@ -599,6 +629,11 @@ optional_return_type: argument_list: '(' ')' { $$ = array(); } | '(' non_empty_argument_list optional_comma ')' { $$ = $2; } + | '(' variadic_placeholder ')' { init($2); } +; + +variadic_placeholder: + T_ELLIPSIS { $$ = Node\VariadicPlaceholder[]; } ; non_empty_argument_list: @@ -610,7 +645,7 @@ argument: expr { $$ = Node\Arg[$1, false, false]; } | ampersand variable { $$ = Node\Arg[$2, true, false]; } | T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; } - | identifier_ex ':' expr + | identifier_maybe_reserved ':' expr { $$ = new Node\Arg($3, false, false, attributes(), $1); } ; @@ -659,11 +694,12 @@ class_statement: | optional_attributes method_modifiers T_CONST class_const_list semi { $$ = new Stmt\ClassConst($4, $2, attributes(), $1); $this->checkClassConst($$, #2); } - | optional_attributes method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body + | optional_attributes method_modifiers T_FUNCTION optional_ref identifier_maybe_reserved '(' parameter_list ')' + optional_return_type method_body { $$ = Stmt\ClassMethod[$5, ['type' => $2, 'byRef' => $4, 'params' => $7, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]]; $this->checkClassMethod($$, #2); } | T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; } - | optional_attributes T_CASE identifier enum_case_expr semi + | optional_attributes T_CASE identifier_maybe_reserved enum_case_expr semi { $$ = Stmt\EnumCase[$3, $4, $1]; } | error { $$ = null; /* will be skipped */ } ; @@ -681,22 +717,22 @@ trait_adaptation_list: trait_adaptation: trait_method_reference_fully_qualified T_INSTEADOF class_name_list ';' { $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; } - | trait_method_reference T_AS member_modifier identifier_ex ';' + | trait_method_reference T_AS member_modifier identifier_maybe_reserved ';' { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; } | trait_method_reference T_AS member_modifier ';' { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; } - | trait_method_reference T_AS identifier ';' + | trait_method_reference T_AS identifier_not_reserved ';' { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; } | trait_method_reference T_AS reserved_non_modifiers_identifier ';' { $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; } ; trait_method_reference_fully_qualified: - name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = array($1, $3); } + name T_PAAMAYIM_NEKUDOTAYIM identifier_maybe_reserved { $$ = array($1, $3); } ; trait_method_reference: trait_method_reference_fully_qualified { $$ = $1; } - | identifier_ex { $$ = array(null, $1); } + | identifier_maybe_reserved { $$ = array(null, $1); } ; method_body: @@ -969,7 +1005,7 @@ constant: ; class_constant: - class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex + class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_maybe_reserved { $$ = Expr\ClassConstFetch[$1, $3]; } /* We interpret an isolated FOO:: as an unfinished class constant fetch. It could also be an unfinished static property fetch or unfinished scoped call. */ @@ -988,9 +1024,7 @@ dereferencable_scalar: { $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG; $$ = new Expr\Array_($3, $attrs); } | array_short_syntax { $$ = $1; } - | T_CONSTANT_ENCAPSED_STRING - { $attrs = attributes(); $attrs['kind'] = strKind($1); - $$ = new Scalar\String_(Scalar\String_::parse($1), $attrs); } + | T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes()); } | '"' encaps_list '"' { $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); } @@ -998,7 +1032,7 @@ dereferencable_scalar: scalar: T_LNUMBER { $$ = $this->parseLNumber($1, attributes()); } - | T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; } + | T_DNUMBER { $$ = Scalar\DNumber::fromString($1, attributes()); } | dereferencable_scalar { $$ = $1; } | constant { $$ = $1; } | class_constant { $$ = $1; } @@ -1088,13 +1122,13 @@ new_variable: ; member_name: - identifier_ex { $$ = $1; } + identifier_maybe_reserved { $$ = $1; } | '{' expr '}' { $$ = $2; } | simple_variable { $$ = $1; } ; property_name: - identifier { $$ = $1; } + identifier_not_reserved { $$ = $1; } | '{' expr '}' { $$ = $2; } | simple_variable { $$ = $1; } | error { $$ = Expr\Error[]; $this->errorState = 2; } @@ -1149,8 +1183,10 @@ encaps_str_varname: encaps_var: plain_variable { $$ = $1; } | plain_variable '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; } - | plain_variable T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; } - | plain_variable T_NULLSAFE_OBJECT_OPERATOR identifier { $$ = Expr\NullsafePropertyFetch[$1, $3]; } + | plain_variable T_OBJECT_OPERATOR identifier_not_reserved + { $$ = Expr\PropertyFetch[$1, $3]; } + | plain_variable T_NULLSAFE_OBJECT_OPERATOR identifier_not_reserved + { $$ = Expr\NullsafePropertyFetch[$1, $3]; } | T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; } | T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; } | T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}' diff --git a/lib/nikic/php-parser/grammar/phpyLang.php b/lib/nikic/php-parser/grammar/phpyLang.php index 1a9808dcf5..663c2a144c 100644 --- a/lib/nikic/php-parser/grammar/phpyLang.php +++ b/lib/nikic/php-parser/grammar/phpyLang.php @@ -128,14 +128,6 @@ function resolveMacros($code) { . ' else { ' . $args[0] . ' = null; }'; } - if ('strKind' === $name) { - assertArgs(1, $args, $name); - - return '(' . $args[0] . '[0] === "\'" || (' . $args[0] . '[1] === "\'" && ' - . '(' . $args[0] . '[0] === \'b\' || ' . $args[0] . '[0] === \'B\')) ' - . '? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED)'; - } - if ('prependLeadingComments' === $name) { assertArgs(1, $args, $name); diff --git a/lib/nikic/php-parser/lib/PhpParser/Builder/Class_.php b/lib/nikic/php-parser/lib/PhpParser/Builder/Class_.php index 87e2901a9a..35b54d0418 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Builder/Class_.php +++ b/lib/nikic/php-parser/lib/PhpParser/Builder/Class_.php @@ -67,7 +67,7 @@ class Class_ extends Declaration * @return $this The builder instance (for fluid interface) */ public function makeAbstract() { - $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT); + $this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT); return $this; } @@ -78,7 +78,13 @@ class Class_ extends Declaration * @return $this The builder instance (for fluid interface) */ public function makeFinal() { - $this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL); + $this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_FINAL); + + return $this; + } + + public function makeReadonly() { + $this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_READONLY); return $this; } diff --git a/lib/nikic/php-parser/lib/PhpParser/Builder/EnumCase.php b/lib/nikic/php-parser/lib/PhpParser/Builder/EnumCase.php new file mode 100644 index 0000000000..02fa83e624 --- /dev/null +++ b/lib/nikic/php-parser/lib/PhpParser/Builder/EnumCase.php @@ -0,0 +1,85 @@ +name = $name; + } + + /** + * Sets the value. + * + * @param Node\Expr|string|int $value + * + * @return $this + */ + public function setValue($value) { + $this->value = BuilderHelpers::normalizeValue($value); + + return $this; + } + + /** + * Sets doc comment for the constant. + * + * @param PhpParser\Comment\Doc|string $docComment Doc comment to set + * + * @return $this The builder instance (for fluid interface) + */ + public function setDocComment($docComment) { + $this->attributes = [ + 'comments' => [BuilderHelpers::normalizeDocComment($docComment)] + ]; + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built enum case node. + * + * @return Stmt\EnumCase The built constant node + */ + public function getNode(): PhpParser\Node { + return new Stmt\EnumCase( + $this->name, + $this->value, + $this->attributes, + $this->attributeGroups + ); + } +} diff --git a/lib/nikic/php-parser/lib/PhpParser/Builder/Enum_.php b/lib/nikic/php-parser/lib/PhpParser/Builder/Enum_.php new file mode 100644 index 0000000000..be7eef95f5 --- /dev/null +++ b/lib/nikic/php-parser/lib/PhpParser/Builder/Enum_.php @@ -0,0 +1,117 @@ +name = $name; + } + + /** + * Sets the scalar type. + * + * @param string|Identifier $type + * + * @return $this + */ + public function setScalarType($scalarType) { + $this->scalarType = BuilderHelpers::normalizeType($scalarType); + + return $this; + } + + /** + * Implements one or more interfaces. + * + * @param Name|string ...$interfaces Names of interfaces to implement + * + * @return $this The builder instance (for fluid interface) + */ + public function implement(...$interfaces) { + foreach ($interfaces as $interface) { + $this->implements[] = BuilderHelpers::normalizeName($interface); + } + + return $this; + } + + /** + * Adds a statement. + * + * @param Stmt|PhpParser\Builder $stmt The statement to add + * + * @return $this The builder instance (for fluid interface) + */ + public function addStmt($stmt) { + $stmt = BuilderHelpers::normalizeNode($stmt); + + $targets = [ + Stmt\TraitUse::class => &$this->uses, + Stmt\EnumCase::class => &$this->enumCases, + Stmt\ClassConst::class => &$this->constants, + Stmt\ClassMethod::class => &$this->methods, + ]; + + $class = \get_class($stmt); + if (!isset($targets[$class])) { + throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType())); + } + + $targets[$class][] = $stmt; + + return $this; + } + + /** + * Adds an attribute group. + * + * @param Node\Attribute|Node\AttributeGroup $attribute + * + * @return $this The builder instance (for fluid interface) + */ + public function addAttribute($attribute) { + $this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute); + + return $this; + } + + /** + * Returns the built class node. + * + * @return Stmt\Enum_ The built enum node + */ + public function getNode() : PhpParser\Node { + return new Stmt\Enum_($this->name, [ + 'scalarType' => $this->scalarType, + 'implements' => $this->implements, + 'stmts' => array_merge($this->uses, $this->enumCases, $this->constants, $this->methods), + 'attrGroups' => $this->attributeGroups, + ], $this->attributes); + } +} diff --git a/lib/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php b/lib/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php index 8e7db399d3..98ea9d3366 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php +++ b/lib/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.php @@ -61,8 +61,7 @@ abstract class FunctionLike extends Declaration /** * Sets the return type for PHP 7. * - * @param string|Node\Name|Node\NullableType $type One of array, callable, string, int, float, - * bool, iterable, or a class/interface name. + * @param string|Node\Name|Node\Identifier|Node\ComplexType $type * * @return $this The builder instance (for fluid interface) */ diff --git a/lib/nikic/php-parser/lib/PhpParser/Builder/Param.php b/lib/nikic/php-parser/lib/PhpParser/Builder/Param.php index 0ea91683c0..de9aae7e5e 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Builder/Param.php +++ b/lib/nikic/php-parser/lib/PhpParser/Builder/Param.php @@ -47,7 +47,7 @@ class Param implements PhpParser\Builder /** * Sets type for the parameter. * - * @param string|Node\Name|Node\NullableType|Node\UnionType $type Parameter type + * @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type * * @return $this The builder instance (for fluid interface) */ @@ -63,7 +63,7 @@ class Param implements PhpParser\Builder /** * Sets type for the parameter. * - * @param string|Node\Name|Node\NullableType|Node\UnionType $type Parameter type + * @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type * * @return $this The builder instance (for fluid interface) * diff --git a/lib/nikic/php-parser/lib/PhpParser/Builder/Property.php b/lib/nikic/php-parser/lib/PhpParser/Builder/Property.php index 90ee4b0ba8..68e318565e 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Builder/Property.php +++ b/lib/nikic/php-parser/lib/PhpParser/Builder/Property.php @@ -7,8 +7,8 @@ use PhpParser\BuilderHelpers; use PhpParser\Node; use PhpParser\Node\Identifier; use PhpParser\Node\Name; -use PhpParser\Node\NullableType; use PhpParser\Node\Stmt; +use PhpParser\Node\ComplexType; class Property implements PhpParser\Builder { @@ -119,7 +119,7 @@ class Property implements PhpParser\Builder /** * Sets the property type for PHP 7.4+. * - * @param string|Name|NullableType|Identifier $type + * @param string|Name|Identifier|ComplexType $type * * @return $this */ diff --git a/lib/nikic/php-parser/lib/PhpParser/BuilderFactory.php b/lib/nikic/php-parser/lib/PhpParser/BuilderFactory.php index 6a6b7b07b5..fef2579b3e 100644 --- a/lib/nikic/php-parser/lib/PhpParser/BuilderFactory.php +++ b/lib/nikic/php-parser/lib/PhpParser/BuilderFactory.php @@ -71,6 +71,17 @@ class BuilderFactory return new Builder\Trait_($name); } + /** + * Creates an enum builder. + * + * @param string $name Name of the enum + * + * @return Builder\Enum_ The created enum builder + */ + public function enum(string $name) : Builder\Enum_ { + return new Builder\Enum_($name); + } + /** * Creates a trait use builder. * @@ -188,6 +199,17 @@ class BuilderFactory return new Builder\ClassConst($name, $value); } + /** + * Creates an enum case builder. + * + * @param string|Identifier $name Name + * + * @return Builder\EnumCase The created use const builder + */ + public function enumCase($name) : Builder\EnumCase { + return new Builder\EnumCase($name); + } + /** * Creates node a for a literal value. * @@ -311,7 +333,7 @@ class BuilderFactory public function constFetch($name) : Expr\ConstFetch { return new Expr\ConstFetch(BuilderHelpers::normalizeName($name)); } - + /** * Creates a property fetch node. * diff --git a/lib/nikic/php-parser/lib/PhpParser/BuilderHelpers.php b/lib/nikic/php-parser/lib/PhpParser/BuilderHelpers.php index c6d8f16136..b8839db322 100644 --- a/lib/nikic/php-parser/lib/PhpParser/BuilderHelpers.php +++ b/lib/nikic/php-parser/lib/PhpParser/BuilderHelpers.php @@ -2,13 +2,13 @@ namespace PhpParser; +use PhpParser\Node\ComplexType; use PhpParser\Node\Expr; use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PhpParser\Node\NullableType; use PhpParser\Node\Scalar; use PhpParser\Node\Stmt; -use PhpParser\Node\UnionType; /** * This class defines helpers used in the implementation of builders. Don't use it directly. @@ -104,29 +104,6 @@ final class BuilderHelpers * @return Name The normalized name */ public static function normalizeName($name) : Name { - return self::normalizeNameCommon($name, false); - } - - /** - * Normalizes a name: Converts string names to Name nodes, while also allowing expressions. - * - * @param Expr|Name|string $name The name to normalize - * - * @return Name|Expr The normalized name or expression - */ - public static function normalizeNameOrExpr($name) { - return self::normalizeNameCommon($name, true); - } - - /** - * Normalizes a name: Converts string names to Name nodes, optionally allowing expressions. - * - * @param Expr|Name|string $name The name to normalize - * @param bool $allowExpr Whether to also allow expressions - * - * @return Name|Expr The normalized name, or expression (if allowed) - */ - private static function normalizeNameCommon($name, bool $allowExpr) { if ($name instanceof Name) { return $name; } @@ -147,16 +124,28 @@ final class BuilderHelpers return new Name($name); } - if ($allowExpr) { - if ($name instanceof Expr) { - return $name; - } + throw new \LogicException('Name must be a string or an instance of Node\Name'); + } + + /** + * Normalizes a name: Converts string names to Name nodes, while also allowing expressions. + * + * @param Expr|Name|string $name The name to normalize + * + * @return Name|Expr The normalized name or expression + */ + public static function normalizeNameOrExpr($name) { + if ($name instanceof Expr) { + return $name; + } + + if (!is_string($name) && !($name instanceof Name)) { throw new \LogicException( 'Name must be a string or an instance of Node\Name or Node\Expr' ); } - throw new \LogicException('Name must be a string or an instance of Node\Name'); + return self::normalizeName($name); } /** @@ -165,18 +154,18 @@ final class BuilderHelpers * In particular, builtin types become Identifiers, custom types become Names and nullables * are wrapped in NullableType nodes. * - * @param string|Name|Identifier|NullableType|UnionType $type The type to normalize + * @param string|Name|Identifier|ComplexType $type The type to normalize * - * @return Name|Identifier|NullableType|UnionType The normalized type + * @return Name|Identifier|ComplexType The normalized type */ public static function normalizeType($type) { if (!is_string($type)) { if ( !$type instanceof Name && !$type instanceof Identifier && - !$type instanceof NullableType && !$type instanceof UnionType + !$type instanceof ComplexType ) { throw new \LogicException( - 'Type must be a string, or an instance of Name, Identifier, NullableType or UnionType' + 'Type must be a string, or an instance of Name, Identifier or ComplexType' ); } return $type; @@ -321,4 +310,13 @@ final class BuilderHelpers Stmt\Class_::verifyModifier($modifiers, $modifier); return $modifiers | $modifier; } + + /** + * Adds a modifier and returns new modifier bitmask. + * @return int New modifiers + */ + public static function addClassModifier(int $existingModifiers, int $modifierToSet) : int { + Stmt\Class_::verifyClassModifier($existingModifiers, $modifierToSet); + return $existingModifiers | $modifierToSet; + } } diff --git a/lib/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php b/lib/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php index 7f02e6f245..7131c3d255 100644 --- a/lib/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php +++ b/lib/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php @@ -2,6 +2,7 @@ namespace PhpParser; +use function array_merge; use PhpParser\Node\Expr; use PhpParser\Node\Scalar; @@ -150,6 +151,8 @@ class ConstExprEvaluator foreach ($expr->items as $item) { if (null !== $item->key) { $array[$this->evaluate($item->key)] = $this->evaluate($item->value); + } elseif ($item->unpack) { + $array = array_merge($array, $this->evaluate($item->value)); } else { $array[] = $this->evaluate($item->value); } diff --git a/lib/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php b/lib/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php index a8f4e334a7..5c56e026bb 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php +++ b/lib/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php @@ -8,6 +8,7 @@ use PhpParser\Lexer; use PhpParser\Lexer\TokenEmulator\AttributeEmulator; use PhpParser\Lexer\TokenEmulator\EnumTokenEmulator; use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator; +use PhpParser\Lexer\TokenEmulator\ExplicitOctalEmulator; use PhpParser\Lexer\TokenEmulator\FlexibleDocStringEmulator; use PhpParser\Lexer\TokenEmulator\FnTokenEmulator; use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator; @@ -36,7 +37,7 @@ class Emulative extends Lexer /** * @param mixed[] $options Lexer options. In addition to the usual options, * accepts a 'phpVersion' string that specifies the - * version to emulated. Defaults to newest supported. + * version to emulate. Defaults to newest supported. */ public function __construct(array $options = []) { @@ -55,6 +56,7 @@ class Emulative extends Lexer new AttributeEmulator(), new EnumTokenEmulator(), new ReadonlyTokenEmulator(), + new ExplicitOctalEmulator(), ]; // Collect emulators that are relevant for the PHP version we're running diff --git a/lib/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php b/lib/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php new file mode 100644 index 0000000000..f5f6805b80 --- /dev/null +++ b/lib/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php @@ -0,0 +1,44 @@ +resolveIntegerOrFloatToken($tokens[$i + 1][1]); + array_splice($tokens, $i, 2, [ + [$tokenKind, '0' . $tokens[$i + 1][1], $tokens[$i][2]], + ]); + $c--; + } + } + return $tokens; + } + + private function resolveIntegerOrFloatToken(string $str): int + { + $str = substr($str, 1); + $str = str_replace('_', '', $str); + $num = octdec($str); + return is_float($num) ? \T_DNUMBER : \T_LNUMBER; + } + + public function reverseEmulate(string $code, array $tokens): array { + // Explicit octals were not legal code previously, don't bother. + return $tokens; + } +} \ No newline at end of file diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Arg.php b/lib/nikic/php-parser/lib/PhpParser/Node/Arg.php index b25b0904a2..bcf130e68c 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Arg.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Arg.php @@ -2,6 +2,7 @@ namespace PhpParser\Node; +use PhpParser\Node\VariadicPlaceholder; use PhpParser\NodeAbstract; class Arg extends NodeAbstract diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/ComplexType.php b/lib/nikic/php-parser/lib/PhpParser/Node/ComplexType.php new file mode 100644 index 0000000000..9505532ae9 --- /dev/null +++ b/lib/nikic/php-parser/lib/PhpParser/Node/ComplexType.php @@ -0,0 +1,14 @@ +params = $subNodes['params'] ?? []; $returnType = $subNodes['returnType'] ?? null; $this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType; - $this->expr = $subNodes['expr'] ?? null; + $this->expr = $subNodes['expr']; $this->attrGroups = $subNodes['attrGroups'] ?? []; } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php new file mode 100644 index 0000000000..78e1cf3494 --- /dev/null +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php @@ -0,0 +1,39 @@ + + */ + abstract public function getRawArgs(): array; + + /** + * Returns whether this call expression is actually a first class callable. + */ + public function isFirstClassCallable(): bool { + foreach ($this->getRawArgs() as $arg) { + if ($arg instanceof VariadicPlaceholder) { + return true; + } + } + return false; + } + + /** + * Assert that this is not a first-class callable and return only ordinary Args. + * + * @return Arg[] + */ + public function getArgs(): array { + assert(!$this->isFirstClassCallable()); + return $this->getRawArgs(); + } +} \ No newline at end of file diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php index 56e621f252..56ddea6aa5 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.php @@ -16,7 +16,7 @@ class Closure extends Expr implements FunctionLike public $params; /** @var ClosureUse[] use()s */ public $uses; - /** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */ + /** @var null|Node\Identifier|Node\Name|Node\ComplexType Return type */ public $returnType; /** @var Node\Stmt[] Statements */ public $stmts; diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php index 1e8afa5596..2de4d0dd57 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.php @@ -5,19 +5,19 @@ namespace PhpParser\Node\Expr; use PhpParser\Node; use PhpParser\Node\Expr; -class FuncCall extends Expr +class FuncCall extends CallLike { /** @var Node\Name|Expr Function name */ public $name; - /** @var Node\Arg[] Arguments */ + /** @var array Arguments */ public $args; /** * Constructs a function call node. * - * @param Node\Name|Expr $name Function name - * @param Node\Arg[] $args Arguments - * @param array $attributes Additional attributes + * @param Node\Name|Expr $name Function name + * @param array $args Arguments + * @param array $attributes Additional attributes */ public function __construct($name, array $args = [], array $attributes = []) { $this->attributes = $attributes; @@ -32,4 +32,8 @@ class FuncCall extends Expr public function getType() : string { return 'Expr_FuncCall'; } + + public function getRawArgs(): array { + return $this->args; + } } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php index bd81bb43f6..49ca483565 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.php @@ -5,23 +5,24 @@ namespace PhpParser\Node\Expr; use PhpParser\Node\Arg; use PhpParser\Node\Expr; use PhpParser\Node\Identifier; +use PhpParser\Node\VariadicPlaceholder; -class MethodCall extends Expr +class MethodCall extends CallLike { /** @var Expr Variable holding object */ public $var; /** @var Identifier|Expr Method name */ public $name; - /** @var Arg[] Arguments */ + /** @var array Arguments */ public $args; /** * Constructs a function call node. * - * @param Expr $var Variable holding object - * @param string|Identifier|Expr $name Method name - * @param Arg[] $args Arguments - * @param array $attributes Additional attributes + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes */ public function __construct(Expr $var, $name, array $args = [], array $attributes = []) { $this->attributes = $attributes; @@ -37,4 +38,8 @@ class MethodCall extends Expr public function getType() : string { return 'Expr_MethodCall'; } + + public function getRawArgs(): array { + return $this->args; + } } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php index c86f0c6015..e2bb64928d 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/New_.php @@ -3,20 +3,22 @@ namespace PhpParser\Node\Expr; use PhpParser\Node; +use PhpParser\Node\Arg; use PhpParser\Node\Expr; +use PhpParser\Node\VariadicPlaceholder; -class New_ extends Expr +class New_ extends CallLike { /** @var Node\Name|Expr|Node\Stmt\Class_ Class name */ public $class; - /** @var Node\Arg[] Arguments */ + /** @var array Arguments */ public $args; /** * Constructs a function call node. * * @param Node\Name|Expr|Node\Stmt\Class_ $class Class name (or class node for anonymous classes) - * @param Node\Arg[] $args Arguments + * @param array $args Arguments * @param array $attributes Additional attributes */ public function __construct($class, array $args = [], array $attributes = []) { @@ -32,4 +34,8 @@ class New_ extends Expr public function getType() : string { return 'Expr_New'; } + + public function getRawArgs(): array { + return $this->args; + } } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php index 361e446227..07a571fd8f 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.php @@ -5,23 +5,24 @@ namespace PhpParser\Node\Expr; use PhpParser\Node\Arg; use PhpParser\Node\Expr; use PhpParser\Node\Identifier; +use PhpParser\Node\VariadicPlaceholder; -class NullsafeMethodCall extends Expr +class NullsafeMethodCall extends CallLike { /** @var Expr Variable holding object */ public $var; /** @var Identifier|Expr Method name */ public $name; - /** @var Arg[] Arguments */ + /** @var array Arguments */ public $args; /** * Constructs a nullsafe method call node. * - * @param Expr $var Variable holding object - * @param string|Identifier|Expr $name Method name - * @param Arg[] $args Arguments - * @param array $attributes Additional attributes + * @param Expr $var Variable holding object + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes */ public function __construct(Expr $var, $name, array $args = [], array $attributes = []) { $this->attributes = $attributes; @@ -37,4 +38,8 @@ class NullsafeMethodCall extends Expr public function getType() : string { return 'Expr_NullsafeMethodCall'; } + + public function getRawArgs(): array { + return $this->args; + } } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php index 9883f5af51..d0d099c472 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.php @@ -3,25 +3,27 @@ namespace PhpParser\Node\Expr; use PhpParser\Node; +use PhpParser\Node\Arg; use PhpParser\Node\Expr; use PhpParser\Node\Identifier; +use PhpParser\Node\VariadicPlaceholder; -class StaticCall extends Expr +class StaticCall extends CallLike { /** @var Node\Name|Expr Class name */ public $class; /** @var Identifier|Expr Method name */ public $name; - /** @var Node\Arg[] Arguments */ + /** @var array Arguments */ public $args; /** * Constructs a static method call node. * - * @param Node\Name|Expr $class Class name - * @param string|Identifier|Expr $name Method name - * @param Node\Arg[] $args Arguments - * @param array $attributes Additional attributes + * @param Node\Name|Expr $class Class name + * @param string|Identifier|Expr $name Method name + * @param array $args Arguments + * @param array $attributes Additional attributes */ public function __construct($class, $name, array $args = [], array $attributes = []) { $this->attributes = $attributes; @@ -37,4 +39,8 @@ class StaticCall extends Expr public function getType() : string { return 'Expr_StaticCall'; } + + public function getRawArgs(): array { + return $this->args; + } } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php b/lib/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php index bbcf53e55f..5a825e7311 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/FunctionLike.php @@ -23,7 +23,7 @@ interface FunctionLike extends Node /** * Get the declared return type or null * - * @return null|Identifier|Name|NullableType|UnionType + * @return null|Identifier|Name|ComplexType */ public function getReturnType(); diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/IntersectionType.php b/lib/nikic/php-parser/lib/PhpParser/Node/IntersectionType.php new file mode 100644 index 0000000000..9208e1392d --- /dev/null +++ b/lib/nikic/php-parser/lib/PhpParser/Node/IntersectionType.php @@ -0,0 +1,30 @@ +attributes = $attributes; + $this->types = $types; + } + + public function getSubNodeNames() : array { + return ['types']; + } + + public function getType() : string { + return 'IntersectionType'; + } +} diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/NullableType.php b/lib/nikic/php-parser/lib/PhpParser/Node/NullableType.php index 36463657e9..d68e26a38f 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/NullableType.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/NullableType.php @@ -2,9 +2,7 @@ namespace PhpParser\Node; -use PhpParser\NodeAbstract; - -class NullableType extends NodeAbstract +class NullableType extends ComplexType { /** @var Identifier|Name Type */ public $type; diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Param.php b/lib/nikic/php-parser/lib/PhpParser/Node/Param.php index 315b5f24f6..1e90b79441 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Param.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Param.php @@ -6,7 +6,7 @@ use PhpParser\NodeAbstract; class Param extends NodeAbstract { - /** @var null|Identifier|Name|NullableType|UnionType Type declaration */ + /** @var null|Identifier|Name|ComplexType Type declaration */ public $type; /** @var bool Whether parameter is passed by reference */ public $byRef; @@ -24,14 +24,14 @@ class Param extends NodeAbstract /** * Constructs a parameter node. * - * @param Expr\Variable|Expr\Error $var Parameter variable - * @param null|Expr $default Default value - * @param null|string|Identifier|Name|NullableType|UnionType $type Type declaration - * @param bool $byRef Whether is passed by reference - * @param bool $variadic Whether this is a variadic argument - * @param array $attributes Additional attributes - * @param int $flags Optional visibility flags - * @param AttributeGroup[] $attrGroups PHP attribute groups + * @param Expr\Variable|Expr\Error $var Parameter variable + * @param null|Expr $default Default value + * @param null|string|Identifier|Name|ComplexType $type Type declaration + * @param bool $byRef Whether is passed by reference + * @param bool $variadic Whether this is a variadic argument + * @param array $attributes Additional attributes + * @param int $flags Optional visibility flags + * @param AttributeGroup[] $attrGroups PHP attribute groups */ public function __construct( $var, Expr $default = null, $type = null, diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php b/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php index 29ce0dd401..d4796d65bb 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php @@ -24,6 +24,17 @@ class DNumber extends Scalar return ['value']; } + /** + * @param mixed[] $attributes + */ + public static function fromString(string $str, array $attributes = []): DNumber + { + $attributes['rawValue'] = $str; + $float = self::parse($str); + + return new DNumber($float, $attributes); + } + /** * @internal * @@ -63,7 +74,7 @@ class DNumber extends Scalar // dec return (float) $str; } - + public function getType() : string { return 'Scalar_DNumber'; } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php b/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php index b33943547e..2cc2b22c8e 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.php @@ -41,6 +41,8 @@ class LNumber extends Scalar * @return LNumber The constructed LNumber, including kind attribute */ public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false) : LNumber { + $attributes['rawValue'] = $str; + $str = str_replace('_', '', $str); if ('0' !== $str[0] || '0' === $str) { @@ -62,11 +64,16 @@ class LNumber extends Scalar throw new Error('Invalid numeric literal', $attributes); } + // Strip optional explicit octal prefix. + if ('o' === $str[1] || 'O' === $str[1]) { + $str = substr($str, 2); + } + // use intval instead of octdec to get proper cutting behavior with malformed numbers $attributes['kind'] = LNumber::KIND_OCT; return new LNumber(intval($str, 8), $attributes); } - + public function getType() : string { return 'Scalar_LNumber'; } diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php b/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php index 8a6d93a474..6690a16bfb 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.php @@ -42,6 +42,22 @@ class String_ extends Scalar return ['value']; } + /** + * @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes + */ + public static function fromString(string $str, array $attributes = [], bool $parseUnicodeEscape = true): self + { + $attributes['kind'] = ($str[0] === "'" || ($str[1] === "'" && ($str[0] === 'b' || $str[0] === 'B'))) + ? Scalar\String_::KIND_SINGLE_QUOTED + : Scalar\String_::KIND_DOUBLE_QUOTED; + + $attributes['rawValue'] = $str; + + $string = self::parse($str, $parseUnicodeEscape); + + return new self($string, $attributes); + } + /** * @internal * diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php b/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php index 840c4f67ec..2fa4e861b3 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.php @@ -4,9 +4,6 @@ namespace PhpParser\Node\Stmt; use PhpParser\Node; -/** - * @property Node\Name $namespacedName Namespaced name (if using NameResolver) - */ abstract class ClassLike extends Node\Stmt { /** @var Node\Identifier|null Name */ @@ -16,6 +13,9 @@ abstract class ClassLike extends Node\Stmt /** @var Node\AttributeGroup[] PHP attribute groups */ public $attrGroups; + /** @var Node\Name|null Namespaced name (if using NameResolver) */ + public $namespacedName; + /** * @return TraitUse[] */ diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php b/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php index 92157fab26..09b877a929 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.php @@ -15,7 +15,7 @@ class ClassMethod extends Node\Stmt implements FunctionLike public $name; /** @var Node\Param[] Parameters */ public $params; - /** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */ + /** @var null|Node\Identifier|Node\Name|Node\ComplexType Return type */ public $returnType; /** @var Node\Stmt[]|null Statements */ public $stmts; diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php b/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php index b290aaf6df..52ed6c6cd6 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php @@ -68,6 +68,10 @@ class Class_ extends ClassLike return (bool) ($this->flags & self::MODIFIER_FINAL); } + public function isReadonly() : bool { + return (bool) ($this->flags & self::MODIFIER_READONLY); + } + /** * Whether the class is anonymous. * @@ -77,6 +81,27 @@ class Class_ extends ClassLike return null === $this->name; } + /** + * @internal + */ + public static function verifyClassModifier($a, $b) { + if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) { + throw new Error('Multiple abstract modifiers are not allowed'); + } + + if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) { + throw new Error('Multiple final modifiers are not allowed'); + } + + if ($a & self::MODIFIER_READONLY && $b & self::MODIFIER_READONLY) { + throw new Error('Multiple readonly modifiers are not allowed'); + } + + if ($a & 48 && $b & 48) { + throw new Error('Cannot use the final modifier on an abstract class'); + } + } + /** * @internal */ diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php b/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php index f08481fae1..c2ccae24ee 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.php @@ -5,9 +5,6 @@ namespace PhpParser\Node\Stmt; use PhpParser\Node; use PhpParser\Node\FunctionLike; -/** - * @property Node\Name $namespacedName Namespaced name (if using NameResolver) - */ class Function_ extends Node\Stmt implements FunctionLike { /** @var bool Whether function returns by reference */ @@ -16,13 +13,16 @@ class Function_ extends Node\Stmt implements FunctionLike public $name; /** @var Node\Param[] Parameters */ public $params; - /** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */ + /** @var null|Node\Identifier|Node\Name|Node\ComplexType Return type */ public $returnType; /** @var Node\Stmt[] Statements */ public $stmts; /** @var Node\AttributeGroup[] PHP attribute groups */ public $attrGroups; + /** @var Node\Name|null Namespaced name (if using NameResolver) */ + public $namespacedName; + /** * Constructs a function node. * diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/Property.php b/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/Property.php index 4f04805d26..bc781bbffc 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/Property.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/Stmt/Property.php @@ -3,10 +3,9 @@ namespace PhpParser\Node\Stmt; use PhpParser\Node; +use PhpParser\Node\ComplexType; use PhpParser\Node\Identifier; use PhpParser\Node\Name; -use PhpParser\Node\NullableType; -use PhpParser\Node\UnionType; class Property extends Node\Stmt { @@ -14,7 +13,7 @@ class Property extends Node\Stmt public $flags; /** @var PropertyProperty[] Properties */ public $props; - /** @var null|Identifier|Name|NullableType|UnionType Type declaration */ + /** @var null|Identifier|Name|ComplexType Type declaration */ public $type; /** @var Node\AttributeGroup[] PHP attribute groups */ public $attrGroups; @@ -22,11 +21,11 @@ class Property extends Node\Stmt /** * Constructs a class property list node. * - * @param int $flags Modifiers - * @param PropertyProperty[] $props Properties - * @param array $attributes Additional attributes - * @param null|string|Identifier|Name|NullableType|UnionType $type Type declaration - * @param Node\AttributeGroup[] $attrGroups PHP attribute groups + * @param int $flags Modifiers + * @param PropertyProperty[] $props Properties + * @param array $attributes Additional attributes + * @param null|string|Identifier|Name|ComplexType $type Type declaration + * @param Node\AttributeGroup[] $attrGroups PHP attribute groups */ public function __construct(int $flags, array $props, array $attributes = [], $type = null, array $attrGroups = []) { $this->attributes = $attributes; diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/UnionType.php b/lib/nikic/php-parser/lib/PhpParser/Node/UnionType.php index c8f45235d6..61c2d81062 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Node/UnionType.php +++ b/lib/nikic/php-parser/lib/PhpParser/Node/UnionType.php @@ -2,9 +2,7 @@ namespace PhpParser\Node; -use PhpParser\NodeAbstract; - -class UnionType extends NodeAbstract +class UnionType extends ComplexType { /** @var (Identifier|Name)[] Types */ public $types; diff --git a/lib/nikic/php-parser/lib/PhpParser/Node/VariadicPlaceholder.php b/lib/nikic/php-parser/lib/PhpParser/Node/VariadicPlaceholder.php new file mode 100644 index 0000000000..403a24df2f --- /dev/null +++ b/lib/nikic/php-parser/lib/PhpParser/Node/VariadicPlaceholder.php @@ -0,0 +1,27 @@ +attributes = $attributes; + } + + public function getType(): string { + return 'VariadicPlaceholder'; + } + + public function getSubNodeNames(): array { + return []; + } +} \ No newline at end of file diff --git a/lib/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php b/lib/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php index 79bbc4577a..8e259c57b6 100644 --- a/lib/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php +++ b/lib/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.php @@ -189,7 +189,7 @@ class NameResolver extends NodeVisitorAbstract $node->type = $this->resolveType($node->type); return $node; } - if ($node instanceof Node\UnionType) { + if ($node instanceof Node\UnionType || $node instanceof Node\IntersectionType) { foreach ($node->types as &$type) { $type = $this->resolveType($type); } diff --git a/lib/nikic/php-parser/lib/PhpParser/Parser/Php5.php b/lib/nikic/php-parser/lib/PhpParser/Parser/Php5.php index c62adfd2ce..d9c8fe0494 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Parser/Php5.php +++ b/lib/nikic/php-parser/lib/PhpParser/Parser/Php5.php @@ -2147,8 +2147,7 @@ class Php5 extends \PhpParser\ParserAbstract $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); }, 392 => function ($stackPos) { - $attrs = $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$stackPos-(4-1)][0] === "'" || ($this->semStack[$stackPos-(4-1)][1] === "'" && ($this->semStack[$stackPos-(4-1)][0] === 'b' || $this->semStack[$stackPos-(4-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED); - $this->semValue = new Expr\ArrayDimFetch(new Scalar\String_(Scalar\String_::parse($this->semStack[$stackPos-(4-1)]), $attrs), $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + $this->semValue = new Expr\ArrayDimFetch(Scalar\String_::fromString($this->semStack[$stackPos-(4-1)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes), $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); }, 393 => function ($stackPos) { $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); @@ -2275,11 +2274,10 @@ class Php5 extends \PhpParser\ParserAbstract $this->semValue = $this->parseLNumber($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes, true); }, 434 => function ($stackPos) { - $this->semValue = new Scalar\DNumber(Scalar\DNumber::parse($this->semStack[$stackPos-(1-1)]), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + $this->semValue = Scalar\DNumber::fromString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); }, 435 => function ($stackPos) { - $attrs = $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$stackPos-(1-1)][0] === "'" || ($this->semStack[$stackPos-(1-1)][1] === "'" && ($this->semStack[$stackPos-(1-1)][0] === 'b' || $this->semStack[$stackPos-(1-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED); - $this->semValue = new Scalar\String_(Scalar\String_::parse($this->semStack[$stackPos-(1-1)], false), $attrs); + $this->semValue = Scalar\String_::fromString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes, false); }, 436 => function ($stackPos) { $this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); diff --git a/lib/nikic/php-parser/lib/PhpParser/Parser/Php7.php b/lib/nikic/php-parser/lib/PhpParser/Parser/Php7.php index 75fc06db7f..71ba0187ee 100644 --- a/lib/nikic/php-parser/lib/PhpParser/Parser/Php7.php +++ b/lib/nikic/php-parser/lib/PhpParser/Parser/Php7.php @@ -18,16 +18,16 @@ use PhpParser\Node\Stmt; class Php7 extends \PhpParser\ParserAbstract { protected $tokenToSymbolMapSize = 396; - protected $actionTableSize = 1187; - protected $gotoTableSize = 579; + protected $actionTableSize = 1189; + protected $gotoTableSize = 611; protected $invalidSymbol = 168; protected $errorSymbol = 1; protected $defaultAction = -32766; protected $unexpectedTokenRule = 32767; - protected $YY2TBLSTATE = 415; - protected $numNonLeafStates = 702; + protected $YY2TBLSTATE = 421; + protected $numNonLeafStates = 709; protected $symbolToName = array( "EOF", @@ -244,322 +244,323 @@ class Php7 extends \PhpParser\ParserAbstract ); protected $action = array( - 131, 132, 133, 563, 134, 135, 0, 714, 715, 716, - 136, 36, 977, 976, 975, 978,-32766,-32766,-32766,-32767, - -32767,-32767,-32767, 100, 101, 102, 103, 104, 1051, 1052, - 1053, 1050, 1049, 1048, 1054, 708, 707,-32766,-32766,-32766, + 132, 133, 134, 568, 135, 136, 0, 721, 722, 723, + 137, 37, 921, 448, 449, 450,-32766,-32766,-32766,-32767, + -32767,-32767,-32767, 101, 102, 103, 104, 105, 1071, 1072, + 1073, 1070, 1069, 1068, 1074, 715, 714,-32766,-32766,-32766, -32766,-32766,-32766,-32766,-32766,-32766,-32767,-32767,-32767,-32767, - -32767, 539, 540, 903, 2, 717,-32766,-32766,-32766, 988, - 989, -88, 914, 440, 441, 442, 365, 366, 462, 265, - 137, 391, 721, 722, 723, 724, 409,-32766, 415,-32766, - -32766,-32766,-32766,-32766, -305, 725, 726, 727, 728, 729, - 730, 731, 732, 733, 734, 735, 755, 564, 756, 757, - 758, 759, 747, 748, 331, 332, 750, 751, 736, 737, - 738, 740, 741, 742, 341, 782, 783, 784, 785, 786, - 787, 743, 744, 565, 566, 776, 767, 765, 766, 779, - 762, 763, 981, 415, 567, 568, 761, 569, 570, 571, - 572, 573, 574, -193, -566, 535, 485, 790, 764, 575, - 576, -566, 138,-32766,-32766,-32766, 131, 132, 133, 563, - 134, 135, 1002, 714, 715, 716, 136, 36, 1043,-32766, - -32766,-32766, 799, -86,-32766, 1276,-32766,-32766,-32766,-32766, - -32766,-32766,-32766, 1051, 1052, 1053, 1050, 1049, 1048, 1054, - -32766, 708, 707,-32766,-32766,-32766, 1241, 238, 463,-32766, - -32766,-32766,-32766,-32766,-32766, 883, 1213, 125, 1176, 1175, - 1177, 717, 801, 689,-32766, 1029,-32766,-32766,-32766,-32766, - -32766, -192,-32766,-32766,-32766, 265, 137, 391, 721, 722, - 723, 724, 883, 945, 415, 680, 12, 34, 247, -86, - -305, 725, 726, 727, 728, 729, 730, 731, 732, 733, - 734, 735, 755, 564, 756, 757, 758, 759, 747, 748, - 331, 332, 750, 751, 736, 737, 738, 740, 741, 742, - 341, 782, 783, 784, 785, 786, 787, 743, 744, 565, - 566, 776, 767, 765, 766, 779, 762, 763, 873, 585, - 567, 568, 761, 569, 570, 571, 572, 573, 574, -193, - 81, 82, 83, -566, 764, 575, 576, -566, 138, 739, - 709, 710, 711, 712, 713, 873, 714, 715, 716, 752, - 753, 35, 33, 84, 85, 86, 87, 88, 89, 90, - 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, - 101, 102, 103, 104, 105, 106, 107, -264, 267,-32766, - -32766,-32766,-32766, 105, 106, 107, 80, 267, 127, 1001, - 108, 946, 314, 885, 717, 675, 367, 366, 143, 108, - 800,-32766, 1027,-32766,-32766, 148, 409, -192, 718, 719, - 720, 721, 722, 723, 724, 237, 1181, 788, 276, -517, - 885, 315, 675, 149, 725, 726, 727, 728, 729, 730, - 731, 732, 733, 734, 735, 755, 778, 756, 757, 758, - 759, 747, 748, 749, 777, 750, 751, 736, 737, 738, - 740, 741, 742, 781, 782, 783, 784, 785, 786, 787, - 743, 744, 745, 746, 776, 767, 765, 766, 779, 762, - 763,-32766,-32766, 754, 760, 761, 768, 769, 771, 770, - 772, 773, 251, -517, -517, 448, 449, 764, 775, 774, - 48, 49, 50, 494, 51, 52, 795, 799, -517, 591, - 53, 54, -111, 55, 986, 708, 707, -111, 792, -111, - -517, 298, -523, 986, 294, 631, 24, -111, -111, -111, - -111, -111, -111, -111, -111, 988, 989, 300, 1286, 1261, - -343, 1287, -343, 1174, 988, 989, 1260, 312, 56, 57, - -32766,-32766,-32766, -111, 58, 1201, 59, 244, 245, 60, - 61, 62, 63, 64, 65, 66, 67, -516, 26, 266, - 68, 429, 495, -319, 647, 648, 1207, 1208, 496, 1172, - 799, 1181, 796, 287, 1205, 40, 23, 497, 73, 498, - 328, 499, 314, 500, 794, 329, 501, 502, 826, 677, - 827, 42, 43, 430, 362, 361, 883, 44, 503, 147, - 394, -16, -557, 353, 327, 355, -557, 1181, 1176, 1175, - 1177, -518, 504, 505, 506, 359, -515, 1257, 47, 363, - 364, -516, -516, 374, 507, 508, 799, 1195, 1196, 1197, - 1198, 1192, 1193, 286, -563, 425, -516, 798, 151, 1199, - 1194, -563, 426, 1176, 1175, 1177, 287, 883, -516, 427, - -522, 69, 799, 310, 311, 314, 30, 109, 110, 111, - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, - -153, -153, -153, 368, 369, -518, -518, 681, 428, 873, - -515, -515, 290, 291, 883, -153, 805, -153, 799, -153, - -518, -153, 708, 707, 152, -515, 790, 358, -111, 1088, - 1090, 360, -518, 153, 883, 139, 376, -515, 11, 126, - -515, 314, -111, -111, 682, 155, 279, -520, 102, 103, - 104, 31, 859, -111, -111, -111, -111, 46, 287,-32766, - 873, 623, 624, 73, 684, 1174, 826, 314, 827, 1028, - -79, 700,-32766,-32766,-32766, 122,-32766, 123,-32766, 128, - -32766, 708, 707,-32766, 885, 883, 675, -153,-32766,-32766, - -32766, 708, 707, 129,-32766,-32766, 142, 873, 156, 73, - -32766, 406, 157, 314, -515, -515, 158, 140, 159,-32766, - -75, -520, -520, 314, 26, 691, -73, 873, -72, -515, - -71, 288, 289, -563, -70, -69, 799, -563,-32766, -68, - 1205, -515,-32766, -67, 1174, 885, -66, 675, -520, 72, - -47,-32766,-32766,-32766, -18,-32766, 146,-32766, 124,-32766, - 268, 275,-32766, 988, 989, 690, -51,-32766,-32766,-32766, - 693, 882, 145,-32766,-32766, 899, 108, 277, 873,-32766, - 406, 278, 931, 280, 675, 281, 321, 144,-32766, 267, - 507, 508, 799, 1195, 1196, 1197, 1198, 1192, 1193, 655, - 130, 790, 885, 1288, 675, 1199, 1194, 543, 1058,-32766, - 650, 13,-32766, 537, 632, 1174, 424, 71, 621, 915, - 311, 314,-32766,-32766,-32766, 668,-32766, 637,-32766,-32766, - -32766, 293, 1212,-32766, 916, 445, 638, 549,-32766,-32766, - -32766, 473, -481,-32766,-32766,-32766, -4, 883, -551, 1174, - -32766, 406, 651, 885, 589, 675,-32766,-32766,-32766,-32766, - -32766, 295,-32766, 901,-32766, 0, 798,-32766, 0, 0, - 0, 0,-32766,-32766,-32766,-32766, 0, 292,-32766,-32766, - 0, 1174, 0, 0,-32766, 406, 299, 0,-32766,-32766, - -32766, 0,-32766,-32766,-32766, 1214,-32766, 0, 0,-32766, - 0, 287, -471, 468,-32766,-32766,-32766,-32766, 7, 15, - -32766,-32766, 357, 1174, 555, 38,-32766, 406, 1202, 883, - -32766,-32766,-32766, 39,-32766,-32766,-32766, 697,-32766, 698, - 873,-32766, 864, 955, 932, 939,-32766,-32766,-32766, 929, - 940, 862,-32766,-32766, 927, 1032, 1035, 1036,-32766, 406, - 1033, 1034, 360, 1040, 420, 883, 810,-32766, 1227, 285, - 1245, 694, 1279, -111, -111, 626, 860, 32, 309, 356, - 676, 679, 683, 818, -111, -111, -111, -111, 685, 686, - -32766, 687, 688, 692, 678, 1206, 1174, 1283, 1285, 821, - 820, 829, 908,-32766,-32766,-32766, 9,-32766, 947,-32766, - 828,-32766, 873, 1284,-32766, 885, 907, 675, -4,-32766, - -32766,-32766, 909, 906, 1160,-32766,-32766, 892, -242, -242, - -242,-32766, 406, 902, 360, 26, 890, 937, 938, 1282, - -32766, 1239, 1228, 1246, 1252, -111, -111, 799, 873, 1255, - -267, 1205, -549, -523, -522, 859, -111, -111, -111, -111, - -521, 1, 27, 28, -241, -241, -241, 37, 41, 45, - 360, 70, 74, 75, 76, 77, 78, 79, 141, 0, - 150, -111, -111, 154, 243, 316, 342, 885, 343, 675, - -242, 859, -111, -111, -111, -111, 344, 345, 346, 347, - 348, 349, 508, 350, 1195, 1196, 1197, 1198, 1192, 1193, - 351, 352, 354, 421, 0, -265, 1199, 1194, -264, 17, - 18, 19, 20, 885, 22, 675, -241, 393, 71, 314, - 464, 311, 314, 465, 472, 475, 476, 477, 478, 482, - 483, 484, 492, 662, 1185, 1128, 1203, 1003, 1164, -269, - -103, 16, 21, 25, 284, 392, 582, 586, 613, 667, - 1132, 1180, 1129, 1258, 0, -485, 1145 + -32767, 371, 372, 240, 2, 724,-32766,-32766,-32766, 1001, + 1002, 415, 956,-32766,-32766,-32766, 373, 372, 12, 267, + 138, 397, 728, 729, 730, 731, 415,-32766, 421,-32766, + -32766,-32766,-32766,-32766,-32766, 732, 733, 734, 735, 736, + 737, 738, 739, 740, 741, 742, 762, 569, 763, 764, + 765, 766, 754, 755, 337, 338, 757, 758, 743, 744, + 745, 747, 748, 749, 347, 789, 790, 791, 792, 793, + 794, 750, 751, 570, 571, 783, 774, 772, 773, 786, + 769, 770, 284, 421, 572, 573, 768, 574, 575, 576, + 577, 578, 579, 597, -579,-32766,-32766, 797, 771, 580, + 581, -579, 139,-32766,-32766,-32766, 132, 133, 134, 568, + 135, 136, 1020, 721, 722, 723, 137, 37,-32766,-32766, + -32766, 542, 1306, 126,-32766, 1307,-32766,-32766,-32766,-32766, + -32766,-32766,-32766, 1071, 1072, 1073, 1070, 1069, 1068, 1074, + 957, 715, 714, -318, 993, 1261,-32766,-32766,-32766, -576, + 106, 107, 108, -268, 270, 890, -576, 910, 1196, 1195, + 1197, 724,-32766,-32766,-32766, 1049, 109,-32766,-32766,-32766, + -32766, 989, 988, 987, 990, 267, 138, 397, 728, 729, + 730, 731, 1233,-32766, 421,-32766,-32766,-32766,-32766, 1001, + 1002, 732, 733, 734, 735, 736, 737, 738, 739, 740, + 741, 742, 762, 569, 763, 764, 765, 766, 754, 755, + 337, 338, 757, 758, 743, 744, 745, 747, 748, 749, + 347, 789, 790, 791, 792, 793, 794, 750, 751, 570, + 571, 783, 774, 772, 773, 786, 769, 770, 880, 321, + 572, 573, 768, 574, 575, 576, 577, 578, 579,-32766, + 82, 83, 84, -579, 771, 580, 581, -579, 148, 746, + 716, 717, 718, 719, 720, 1281, 721, 722, 723, 759, + 760, 36, 1280, 85, 86, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, + 102, 103, 104, 105, 106, 107, 108, 999, 270, -318, + -32766,-32766,-32766, 456, 457, 81, -193, 808, -576, 1019, + 109, 320, -576, 892, 724, 681, 802, 695, 1001, 1002, + 591,-32766, 1047,-32766,-32766,-32766, 715, 714, 725, 726, + 727, 728, 729, 730, 731, -192, -86, 795, 279, -530, + 284,-32766,-32766,-32766, 732, 733, 734, 735, 736, 737, + 738, 739, 740, 741, 742, 762, 785, 763, 764, 765, + 766, 754, 755, 756, 784, 757, 758, 743, 744, 745, + 747, 748, 749, 788, 789, 790, 791, 792, 793, 794, + 750, 751, 752, 753, 783, 774, 772, 773, 786, 769, + 770, 470, 803, 761, 767, 768, 775, 776, 778, 777, + 779, 780, -86, -530, -530, 637, 25, 771, 782, 781, + 49, 50, 51, 501, 52, 53, 239, 34, -530, 890, + 54, 55, -111, 56, 999, 128,-32766, -111, 1201, -111, + -530, -570, -536, 890, 300, -570, 144, -111, -111, -111, + -111, -111, -111, -111, -111, 1001, 1002, 1001, 1002, 686, + 1201, 925, 926, 1194, 806, 890, 927, 1296, 57, 58, + 799, 253, -193, 687, 59, 807, 60, 246, 247, 61, + 62, 63, 64, 65, 66, 67, 68, 304, 27, 268, + 69, 437, 502, -332, 306, 688, 1227, 1228, 503, 1192, + 806, -192, 318, 890, 1225, 41, 24, 504, 334, 505, + 14, 506, 880, 507, 653, 654, 508, 509, 280, 806, + 281, 43, 44, 438, 368, 367, 880, 45, 510, 35, + 249, 471, 1063, 359, 333, 103, 104, 105, 1196, 1195, + 1197, 806, 511, 512, 513, 335, 801, 1221, 880, 361, + 285, 683, 286, 365, 514, 515, 380, 1215, 1216, 1217, + 1218, 1212, 1213, 292, 433, -111, 715, 714, 434, 1219, + 1214, 149, 400, 1196, 1195, 1197, 293, -153, -153, -153, + -356, 70, -356, 316, 317, 320, 880, 892, -531, 681, + 435, 1048, -153, 707, -153, 293, -153, 1277, -153, 27, + 74, 892, 436, 681, 320, 369, 370, 833, 366, 834, + -529, 806, 382, 812, 11, 1225, 833, 150, 834, -111, + -111, 151, 74, 942, -111, 681, 320, 153, 806, 866, + -111, -111, -111, -111, 31, 110, 111, 112, 113, 114, + 115, 116, 117, 118, 119, 120, 121, 122, 715, 714, + 374, 375, -531, -531, 890, 154, 805, 155, -4, 890, + 157, 892, -88, 681, -153, 514, 515, -531, 1215, 1216, + 1217, 1218, 1212, 1213, -529, -529, 797, 1108, 1110, -531, + 1219, 1214, 715, 714, 690,-32766, 629, 630, -528, -529, + 32, 1194, 72, 123, 124, 317, 320, 129,-32766,-32766, + -32766, -529,-32766, -535,-32766, 130,-32766, 140, 143,-32766, + 158, 159, 160, 320,-32766,-32766,-32766, 161, -528,-32766, + -32766,-32766, -79, 282, -75, 1194,-32766, 412, -73, 27, + -72, -71,-32766,-32766,-32766,-32766,-32766, 880,-32766, 287, + -32766, 806, 880,-32766, -70, 1225, -69, -68,-32766,-32766, + -32766, -67, -528, -528,-32766,-32766, -66, 141, -47, -18, + -32766, 412, 147, 320, 366, 73, 428, -528, 271,-32766, + 278, 291, -51, 696, 699, -111, -111, 1201, -533, -528, + -111, 889, -528, -528, 48, 825, -111, -111, -111, -111, + 146, 327, 283, 270, 288, 109, 515, -528, 1215, 1216, + 1217, 1218, 1212, 1213, 131, 906, 661, -16, 9, -528, + 1219, 1214, 892, 797, 681,-32766, 145, 892, 1308, 681, + -4, 1194, 72,-32766, 638, 317, 320, 806,-32766,-32766, + -32766, 1078,-32766, 544,-32766, 627,-32766, 13, 656,-32766, + 548, 298, -533, -533,-32766,-32766,-32766,-32766, 296, 297, + -32766,-32766, 674, 1194, 643, 890,-32766, 412, 806, 453, + -32766,-32766,-32766, 364,-32766,-32766,-32766, 481,-32766, -533, + -32766,-32766, 47, -494, 890, 127,-32766,-32766,-32766,-32766, + 644, 657,-32766,-32766, 305, 1194, 890, 805,-32766, 412, + 1222, 301,-32766,-32766,-32766, 0,-32766,-32766,-32766, 432, + -32766, 299, 922,-32766, -111, 293, 554, 476,-32766,-32766, + -32766,-32766, 1232, -484,-32766,-32766, 697, 1194, 560, 908, + -32766, 412, 595, 817,-32766,-32766,-32766, 7,-32766,-32766, + -32766, 1234,-32766, 16, 293,-32766, 294, 295, 880, 74, + -32766,-32766,-32766, 320, 363, 39,-32766,-32766, 40, 704, + 705, 871,-32766, 412, -246, -246, -246, 880, 966, 943, + 366,-32766, 950, 125, 1247, 940, 951, 869, 938, 880, + 1052, -111, -111, -245, -245, -245, -111, 1055, 1056, 366, + 1053, 866, -111, -111, -111, -111, 1054, 1060, 701, 1265, + -111, -111, 1299, 632, -564, -111, 33, 315, -271, 362, + 866, -111, -111, -111, -111, 682, 685, 689, 691, 692, + 693, 694,-32766, 892, 698, 681, -246, 684, 1194, 867, + 1303, 1305, 828, 827, 836,-32766,-32766,-32766, 915,-32766, + 958,-32766, 892,-32766, 681, -245,-32766, 835, 1304, 914, + 916,-32766,-32766,-32766, 892, 913, 681,-32766,-32766, 1180, + 899, 909, 897,-32766, 412, 948, 949, 1302, 1259, 1248, + 1266, 1272,-32766, 1275, -269, -562, -536, -535, -534, 1, + 28, 29, 38, 42, 46, 71, 75, 76, 77, 78, + 79, 80, 142, 152, 156, 245, 322, 348, 349, 350, + 351, 352, 353, 354, 355, 356, 357, 358, 360, 429, + 0, -268, 0, 18, 19, 20, 21, 23, 399, 472, + 473, 480, 483, 484, 485, 486, 490, 491, 492, 499, + 668, 1205, 1148, 1223, 1022, 1021, 1184, -273, -103, 17, + 22, 26, 290, 398, 588, 592, 619, 673, 1152, 1200, + 1149, 1278, 0, -498, 1165, 0, 1226, 0, 320 ); protected $actionCheck = array( 2, 3, 4, 5, 6, 7, 0, 9, 10, 11, - 12, 13, 119, 120, 121, 122, 9, 10, 11, 44, + 12, 13, 128, 129, 130, 131, 9, 10, 11, 44, 45, 46, 47, 48, 49, 50, 51, 52, 116, 117, 118, 119, 120, 121, 122, 37, 38, 30, 116, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, - 43, 117, 118, 1, 8, 57, 9, 10, 11, 137, - 138, 31, 128, 129, 130, 131, 106, 107, 31, 71, + 43, 106, 107, 14, 8, 57, 9, 10, 11, 137, + 138, 116, 31, 9, 10, 11, 106, 107, 8, 71, 72, 73, 74, 75, 76, 77, 116, 30, 80, 32, - 33, 34, 35, 36, 8, 87, 88, 89, 90, 91, + 33, 34, 35, 36, 30, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, - 132, 133, 1, 80, 136, 137, 138, 139, 140, 141, - 142, 143, 144, 8, 1, 85, 101, 80, 150, 151, + 132, 133, 30, 80, 136, 137, 138, 139, 140, 141, + 142, 143, 144, 51, 1, 9, 10, 80, 150, 151, 152, 8, 154, 9, 10, 11, 2, 3, 4, 5, - 6, 7, 164, 9, 10, 11, 12, 13, 123, 9, - 10, 11, 82, 31, 30, 85, 32, 33, 34, 35, + 6, 7, 164, 9, 10, 11, 12, 13, 9, 10, + 11, 85, 80, 14, 30, 83, 32, 33, 34, 35, 36, 37, 38, 116, 117, 118, 119, 120, 121, 122, - 30, 37, 38, 9, 10, 11, 1, 14, 161, 9, - 10, 11, 9, 10, 11, 1, 146, 14, 155, 156, - 157, 57, 1, 161, 30, 162, 32, 33, 34, 35, - 30, 8, 32, 33, 34, 71, 72, 73, 74, 75, - 76, 77, 1, 31, 80, 31, 8, 147, 148, 97, - 164, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 159, 37, 38, 8, 1, 1, 9, 10, 11, 1, + 53, 54, 55, 164, 57, 1, 8, 1, 155, 156, + 157, 57, 9, 10, 11, 162, 69, 30, 116, 32, + 33, 119, 120, 121, 122, 71, 72, 73, 74, 75, + 76, 77, 146, 30, 80, 32, 33, 34, 35, 137, + 138, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, - 126, 127, 128, 129, 130, 131, 132, 133, 84, 1, - 136, 137, 138, 139, 140, 141, 142, 143, 144, 164, + 126, 127, 128, 129, 130, 131, 132, 133, 84, 70, + 136, 137, 138, 139, 140, 141, 142, 143, 144, 9, 9, 10, 11, 160, 150, 151, 152, 164, 154, 2, - 3, 4, 5, 6, 7, 84, 9, 10, 11, 12, + 3, 4, 5, 6, 7, 1, 9, 10, 11, 12, 13, 30, 8, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55, 164, 57, 9, - 9, 10, 11, 53, 54, 55, 161, 57, 8, 1, - 69, 159, 167, 159, 57, 161, 106, 107, 8, 69, - 159, 30, 1, 32, 33, 14, 116, 164, 71, 72, - 73, 74, 75, 76, 77, 97, 1, 80, 30, 70, - 159, 70, 161, 14, 87, 88, 89, 90, 91, 92, + 49, 50, 51, 52, 53, 54, 55, 116, 57, 164, + 9, 10, 11, 134, 135, 161, 8, 1, 160, 1, + 69, 167, 164, 159, 57, 161, 80, 161, 137, 138, + 1, 30, 1, 32, 33, 34, 37, 38, 71, 72, + 73, 74, 75, 76, 77, 8, 31, 80, 30, 70, + 30, 9, 10, 11, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, - 133, 9, 10, 136, 137, 138, 139, 140, 141, 142, - 143, 144, 8, 134, 135, 134, 135, 150, 151, 152, - 2, 3, 4, 5, 6, 7, 80, 82, 149, 51, - 12, 13, 101, 15, 116, 37, 38, 106, 80, 108, - 161, 8, 163, 116, 113, 75, 76, 116, 117, 118, - 119, 120, 121, 122, 123, 137, 138, 8, 80, 1, - 106, 83, 108, 80, 137, 138, 8, 8, 50, 51, - 9, 10, 11, 128, 56, 1, 58, 59, 60, 61, - 62, 63, 64, 65, 66, 67, 68, 70, 70, 71, - 72, 73, 74, 162, 75, 76, 78, 79, 80, 116, - 82, 1, 156, 158, 86, 87, 88, 89, 163, 91, - 8, 93, 167, 95, 156, 8, 98, 99, 106, 161, - 108, 103, 104, 105, 106, 107, 1, 109, 110, 101, - 102, 31, 160, 115, 116, 8, 164, 1, 155, 156, - 157, 70, 124, 125, 126, 8, 70, 1, 70, 106, - 107, 134, 135, 8, 136, 137, 82, 139, 140, 141, - 142, 143, 144, 145, 1, 8, 149, 155, 14, 151, - 152, 8, 8, 155, 156, 157, 158, 1, 161, 8, - 163, 163, 82, 165, 166, 167, 16, 17, 18, 19, - 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 75, 76, 77, 106, 107, 134, 135, 31, 8, 84, - 134, 135, 134, 135, 1, 90, 8, 92, 82, 94, - 149, 96, 37, 38, 14, 149, 80, 149, 128, 59, - 60, 106, 161, 14, 1, 161, 106, 161, 108, 161, - 70, 167, 117, 118, 31, 14, 30, 70, 50, 51, - 52, 14, 127, 128, 129, 130, 131, 70, 158, 74, - 84, 111, 112, 163, 31, 80, 106, 167, 108, 159, - 31, 161, 87, 88, 89, 16, 91, 16, 93, 16, - 95, 37, 38, 98, 159, 1, 161, 162, 103, 104, - 105, 37, 38, 16, 109, 110, 16, 84, 16, 163, - 115, 116, 16, 167, 134, 135, 16, 161, 16, 124, - 31, 134, 135, 167, 70, 31, 31, 84, 31, 149, - 31, 134, 135, 160, 31, 31, 82, 164, 74, 31, - 86, 161, 116, 31, 80, 159, 31, 161, 161, 154, - 31, 87, 88, 89, 31, 91, 31, 93, 161, 95, - 31, 31, 98, 137, 138, 31, 31, 103, 104, 105, - 31, 31, 31, 109, 110, 38, 69, 35, 84, 115, - 116, 35, 159, 35, 161, 35, 35, 70, 124, 57, - 136, 137, 82, 139, 140, 141, 142, 143, 144, 77, - 31, 80, 159, 83, 161, 151, 152, 89, 82, 74, - 94, 97, 85, 85, 90, 80, 128, 163, 113, 128, - 166, 167, 87, 88, 89, 92, 91, 96, 93, 116, - 95, 133, 146, 98, 128, 97, 100, 153, 103, 104, - 105, 97, 149, 74, 109, 110, 0, 1, 163, 80, - 115, 116, 100, 159, 153, 161, 87, 88, 89, 124, - 91, 114, 93, 154, 95, -1, 155, 98, -1, -1, - -1, -1, 103, 104, 105, 74, -1, 132, 109, 110, - -1, 80, -1, -1, 115, 116, 132, -1, 87, 88, - 89, -1, 91, 124, 93, 146, 95, -1, -1, 98, - -1, 158, 149, 102, 103, 104, 105, 74, 149, 149, - 109, 110, 149, 80, 81, 159, 115, 116, 160, 1, - 87, 88, 89, 159, 91, 124, 93, 159, 95, 159, - 84, 98, 159, 159, 159, 159, 103, 104, 105, 159, - 159, 159, 109, 110, 159, 159, 159, 159, 115, 116, - 159, 159, 106, 159, 108, 1, 160, 124, 160, 113, - 160, 162, 160, 117, 118, 160, 162, 161, 161, 161, - 161, 161, 161, 127, 128, 129, 130, 131, 161, 161, - 74, 161, 161, 161, 161, 166, 80, 162, 162, 162, - 162, 162, 162, 87, 88, 89, 150, 91, 162, 93, - 162, 95, 84, 162, 98, 159, 162, 161, 162, 103, - 104, 105, 162, 162, 162, 109, 110, 162, 100, 101, - 102, 115, 116, 162, 106, 70, 162, 162, 162, 162, - 124, 162, 162, 162, 162, 117, 118, 82, 84, 162, - 164, 86, 163, 163, 163, 127, 128, 129, 130, 131, - 163, 163, 163, 163, 100, 101, 102, 163, 163, 163, - 106, 163, 163, 163, 163, 163, 163, 163, 163, -1, - 163, 117, 118, 163, 163, 163, 163, 159, 163, 161, - 162, 127, 128, 129, 130, 131, 163, 163, 163, 163, - 163, 163, 137, 163, 139, 140, 141, 142, 143, 144, - 163, 163, 163, 163, -1, 164, 151, 152, 164, 164, - 164, 164, 164, 159, 164, 161, 162, 164, 163, 167, - 164, 166, 167, 164, 164, 164, 164, 164, 164, 164, + 133, 31, 156, 136, 137, 138, 139, 140, 141, 142, + 143, 144, 97, 134, 135, 75, 76, 150, 151, 152, + 2, 3, 4, 5, 6, 7, 97, 8, 149, 1, + 12, 13, 101, 15, 116, 8, 116, 106, 1, 108, + 161, 160, 163, 1, 113, 164, 8, 116, 117, 118, + 119, 120, 121, 122, 123, 137, 138, 137, 138, 31, + 1, 117, 118, 80, 82, 1, 122, 85, 50, 51, + 80, 8, 164, 31, 56, 159, 58, 59, 60, 61, + 62, 63, 64, 65, 66, 67, 68, 8, 70, 71, + 72, 73, 74, 162, 8, 31, 78, 79, 80, 116, + 82, 164, 8, 1, 86, 87, 88, 89, 8, 91, + 101, 93, 84, 95, 75, 76, 98, 99, 35, 82, + 37, 103, 104, 105, 106, 107, 84, 109, 110, 147, + 148, 161, 123, 115, 116, 50, 51, 52, 155, 156, + 157, 82, 124, 125, 126, 8, 156, 1, 84, 8, + 35, 161, 37, 8, 136, 137, 8, 139, 140, 141, + 142, 143, 144, 145, 8, 128, 37, 38, 8, 151, + 152, 101, 102, 155, 156, 157, 158, 75, 76, 77, + 106, 163, 108, 165, 166, 167, 84, 159, 70, 161, + 8, 159, 90, 161, 92, 158, 94, 1, 96, 70, + 163, 159, 8, 161, 167, 106, 107, 106, 106, 108, + 70, 82, 106, 8, 108, 86, 106, 14, 108, 117, + 118, 14, 163, 159, 122, 161, 167, 14, 82, 127, + 128, 129, 130, 131, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 37, 38, + 106, 107, 134, 135, 1, 14, 155, 14, 0, 1, + 14, 159, 31, 161, 162, 136, 137, 149, 139, 140, + 141, 142, 143, 144, 134, 135, 80, 59, 60, 161, + 151, 152, 37, 38, 31, 74, 111, 112, 70, 149, + 14, 80, 163, 16, 16, 166, 167, 16, 87, 88, + 89, 161, 91, 163, 93, 16, 95, 161, 16, 98, + 16, 16, 16, 167, 103, 104, 105, 16, 70, 74, + 109, 110, 31, 35, 31, 80, 115, 116, 31, 70, + 31, 31, 87, 88, 89, 124, 91, 84, 93, 35, + 95, 82, 84, 98, 31, 86, 31, 31, 103, 104, + 105, 31, 134, 135, 109, 110, 31, 161, 31, 31, + 115, 116, 31, 167, 106, 154, 108, 149, 31, 124, + 31, 113, 31, 31, 31, 117, 118, 1, 70, 161, + 122, 31, 134, 135, 70, 127, 128, 129, 130, 131, + 31, 35, 37, 57, 37, 69, 137, 149, 139, 140, + 141, 142, 143, 144, 31, 38, 77, 31, 150, 161, + 151, 152, 159, 80, 161, 74, 70, 159, 83, 161, + 162, 80, 163, 85, 90, 166, 167, 82, 87, 88, + 89, 82, 91, 85, 93, 113, 95, 97, 94, 98, + 89, 132, 134, 135, 103, 104, 105, 74, 134, 135, + 109, 110, 92, 80, 96, 1, 115, 116, 82, 97, + 87, 88, 89, 149, 91, 124, 93, 97, 95, 161, + 116, 98, 70, 149, 1, 161, 103, 104, 105, 74, + 100, 100, 109, 110, 132, 80, 1, 155, 115, 116, + 160, 114, 87, 88, 89, -1, 91, 124, 93, 128, + 95, 133, 128, 98, 128, 158, 153, 102, 103, 104, + 105, 74, 146, 149, 109, 110, 31, 80, 81, 154, + 115, 116, 153, 160, 87, 88, 89, 149, 91, 124, + 93, 146, 95, 149, 158, 98, 134, 135, 84, 163, + 103, 104, 105, 167, 149, 159, 109, 110, 159, 159, + 159, 159, 115, 116, 100, 101, 102, 84, 159, 159, + 106, 124, 159, 161, 160, 159, 159, 159, 159, 84, + 159, 117, 118, 100, 101, 102, 122, 159, 159, 106, + 159, 127, 128, 129, 130, 131, 159, 159, 162, 160, + 117, 118, 160, 160, 163, 122, 161, 161, 164, 161, + 127, 128, 129, 130, 131, 161, 161, 161, 161, 161, + 161, 161, 74, 159, 161, 161, 162, 161, 80, 162, + 162, 162, 162, 162, 162, 87, 88, 89, 162, 91, + 162, 93, 159, 95, 161, 162, 98, 162, 162, 162, + 162, 103, 104, 105, 159, 162, 161, 109, 110, 162, + 162, 162, 162, 115, 116, 162, 162, 162, 162, 162, + 162, 162, 124, 162, 164, 163, 163, 163, 163, 163, + 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, + 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, + 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, + -1, 164, -1, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, - 164, 164, 164, 164, -1, 165, 165 + 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, + 164, 164, -1, 165, 165, -1, 166, -1, 167 ); protected $actionBase = array( - 0, -2, 154, 565, 876, 948, 984, 514, 53, 398, - 822, 307, 307, 67, 307, 307, 616, 673, 673, 724, - 673, 204, 653, 231, 231, 231, 625, 625, 625, 625, - 694, 694, 831, 831, 863, 799, 765, 936, 936, 936, - 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, - 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, - 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, - 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, - 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, - 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, - 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, - 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, - 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, - 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, - 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, - 936, 936, 936, 936, 936, 936, 936, 936, 936, 936, - 211, 202, 288, 677, 1010, 1016, 1012, 1017, 1008, 1007, - 1011, 1013, 1018, 897, 899, 771, 900, 901, 902, 907, - 1014, 835, 1009, 1015, 291, 291, 291, 291, 291, 291, + 0, -2, 154, 542, 698, 894, 913, 586, 53, 430, + 867, 307, 307, 67, 307, 307, 307, 482, 693, 693, + 925, 693, 468, 504, 204, 204, 204, 651, 651, 651, + 651, 685, 685, 845, 845, 877, 813, 781, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, + 978, 978, 356, 31, 369, 716, 1008, 1014, 1010, 1015, + 1006, 1005, 1009, 1011, 1016, 935, 936, 799, 937, 938, + 939, 941, 1012, 873, 1007, 1013, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, - 340, 193, 432, 501, 501, 501, 501, 501, 501, 501, - 501, 501, 501, 501, 501, 501, 501, 501, 501, 501, - 501, 501, 501, 160, 160, 160, 341, 684, 684, 190, - 184, 610, 47, 985, 985, 985, 985, 985, 985, 985, - 985, 985, 985, 144, 144, 7, 7, 7, 7, 7, - 371, -25, -25, -25, -25, 540, 385, 576, 358, 45, - 394, 638, 638, 656, 656, 367, 367, 367, 367, -78, - -78, -78, -66, 319, 457, 452, 60, 423, 586, 586, - 586, 586, 423, 423, 423, 423, 779, 849, 423, 423, - 423, 511, 516, 516, 518, 300, 300, 300, 516, 600, - 758, 90, 600, 90, 195, 418, 743, -40, 260, 412, - -107, 743, 617, 627, 603, 143, 741, 483, 741, 1006, - 757, 749, 719, 824, 853, 1019, 766, 895, 782, 896, - 321, 679, 1005, 1005, 1005, 1005, 1005, 1005, 1005, 1005, - 1005, 1005, 1005, 982, 438, 1006, 386, 982, 982, 982, - 438, 438, 438, 438, 438, 438, 438, 438, 438, 438, - 590, 386, 410, 459, 386, 781, 438, 211, 783, 211, - 211, 211, 211, 943, 211, 211, 211, 211, 211, 211, - 956, 753, 37, 211, 202, 52, 52, 550, 131, 52, - 52, 52, 52, 211, 211, 211, 483, 762, 714, 537, - 731, 213, 762, 762, 762, 142, 76, 183, 135, 570, - 751, 751, 756, 918, 918, 751, 740, 751, 756, 926, - 751, 918, 773, 350, 597, 542, 577, 604, 918, 473, - 751, 751, 751, 751, 611, 751, 444, 360, 751, 751, - 775, 760, 784, 46, 918, 918, 918, 784, 567, 728, - 728, 728, 798, 800, 735, 759, 499, 489, 648, 314, - 767, 759, 759, 751, 585, 735, 759, 735, 759, 739, - 759, 759, 759, 735, 759, 751, 740, 547, 759, 722, - 640, 228, 759, 6, 928, 929, 30, 930, 924, 931, - 970, 932, 933, 839, 941, 925, 934, 920, 919, 770, - 699, 701, 789, 723, 917, 737, 737, 737, 910, 737, - 737, 737, 737, 737, 737, 737, 737, 699, 788, 793, - 718, 748, 945, 703, 717, 716, 834, 1020, 1021, 721, - 736, 943, 1000, 935, 786, 720, 980, 953, 829, 837, - 954, 955, 983, 1001, 1002, 855, 747, 856, 857, 826, - 957, 840, 737, 928, 933, 925, 934, 920, 919, 745, - 742, 734, 738, 733, 729, 725, 727, 755, 909, 715, - 828, 956, 911, 699, 830, 975, 836, 986, 989, 838, - 768, 750, 832, 858, 958, 960, 967, 841, 1003, 794, - 976, 906, 990, 774, 859, 991, 992, 993, 994, 860, - 847, 848, 850, 803, 754, 971, 761, 866, 361, 778, - 780, 969, 379, 942, 851, 868, 871, 995, 996, 997, - 874, 937, 804, 977, 746, 978, 974, 805, 806, 594, - 772, 776, 650, 659, 880, 881, 882, 940, 764, 752, - 810, 811, 1004, 885, 671, 812, 726, 891, 999, 730, - 732, 763, 852, 790, 777, 744, 968, 769, 815, 894, - 816, 817, 818, 998, 821, 0, 0, 0, 0, 0, + 291, 291, 290, 159, 136, 382, 382, 382, 382, 382, + 382, 382, 382, 382, 382, 382, 382, 382, 382, 382, + 382, 382, 382, 382, 382, 54, 54, 54, 187, 569, + 569, 341, 203, 658, 47, 699, 699, 699, 699, 699, + 699, 699, 699, 699, 699, 144, 144, 7, 7, 7, + 7, 7, 371, -25, -25, -25, -25, 816, 477, 102, + 499, 358, 449, 514, 525, 525, 360, -116, 231, 231, + 231, 231, 231, 231, -78, -78, -78, -78, -78, 319, + 580, 541, 86, 423, 636, 636, 636, 636, 423, 423, + 423, 423, 825, 1020, 423, 423, 423, 558, 688, 688, + 754, 147, 147, 147, 688, 550, 788, 422, 550, 422, + 194, 92, 794, -55, -40, 321, 814, 794, 748, 842, + 198, 143, 772, 539, 772, 1004, 778, 767, 733, 868, + 896, 1017, 820, 933, 821, 934, 219, 731, 1003, 1003, + 1003, 1003, 1003, 1003, 1003, 1003, 1003, 1003, 1003, 1021, + 339, 1004, 286, 1021, 1021, 1021, 339, 339, 339, 339, + 339, 339, 339, 339, 339, 339, 615, 286, 380, 479, + 286, 796, 339, 356, 804, 356, 356, 356, 356, 964, + 356, 356, 356, 356, 356, 356, 969, 768, 410, 356, + 31, 206, 206, 472, 193, 206, 206, 206, 206, 356, + 356, 356, 539, 776, 793, 584, 809, 377, 776, 776, + 776, 355, 185, 39, 348, 555, 523, 546, 773, 773, + 789, 946, 946, 773, 785, 773, 789, 951, 773, 946, + 787, 467, 596, 540, 585, 600, 946, 519, 773, 773, + 773, 773, 622, 773, 503, 478, 773, 773, 749, 779, + 792, 46, 946, 946, 946, 792, 581, 808, 808, 808, + 830, 831, 762, 777, 534, 526, 645, 459, 807, 777, + 777, 773, 588, 762, 777, 762, 777, 805, 777, 777, + 777, 762, 777, 785, 577, 777, 734, 634, 60, 777, + 6, 952, 953, 671, 954, 949, 955, 976, 956, 957, + 884, 962, 950, 958, 948, 947, 790, 717, 718, 818, + 764, 945, 766, 766, 766, 943, 766, 766, 766, 766, + 766, 766, 766, 766, 717, 770, 835, 811, 791, 965, + 721, 729, 806, 897, 1018, 1019, 964, 997, 959, 826, + 732, 983, 966, 866, 876, 967, 968, 984, 998, 999, + 898, 786, 899, 900, 803, 970, 885, 766, 952, 957, + 950, 958, 948, 947, 765, 760, 755, 756, 753, 740, + 737, 739, 771, 1000, 942, 871, 844, 969, 944, 717, + 869, 979, 875, 985, 986, 878, 802, 775, 872, 901, + 971, 972, 973, 886, 1001, 829, 980, 874, 987, 810, + 902, 988, 989, 990, 991, 906, 887, 888, 889, 832, + 774, 940, 798, 908, 643, 744, 797, 975, 647, 963, + 890, 915, 916, 992, 993, 994, 917, 960, 839, 981, + 784, 982, 977, 840, 843, 653, 728, 795, 681, 683, + 918, 923, 927, 961, 782, 769, 846, 847, 1002, 928, + 686, 848, 735, 929, 996, 736, 741, 800, 893, 824, + 817, 780, 974, 783, 849, 930, 851, 858, 859, 995, + 861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 458, 458, 458, 458, 458, 458, 307, - 307, 307, 307, 0, 0, 307, 0, 0, 458, 458, + 458, 458, 458, 458, 458, 458, 307, 307, 307, 307, + 0, 0, 307, 0, 0, 0, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, @@ -574,173 +575,177 @@ class Php7 extends \PhpParser\ParserAbstract 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, - 458, 458, 291, 291, 291, 291, 291, 291, 291, 291, + 458, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, - 291, 291, 291, 291, 291, 291, 0, 0, 0, 0, + 291, 291, 291, 291, 291, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 291, 291, 291, 291, 291, 291, 291, 291, + 0, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, 291, - 291, 291, 291, 291, 291, 291, 291, 291, 291, 423, - 423, 291, 291, 0, 291, 423, 423, 423, 423, 423, - 423, 423, 423, 423, 423, 291, 291, 291, 291, 291, - 291, 291, 773, 300, 300, 300, 300, 423, 423, 423, - 423, -88, -88, 300, 300, 423, 423, 423, 423, 423, - 423, 423, 423, 423, 0, 0, 0, 386, 90, 0, - 740, 740, 740, 740, 0, 0, 0, 0, 90, 90, + 291, 291, 291, 291, 291, 291, 291, 291, 423, 423, + 291, 291, 0, 291, 423, 423, 423, 423, 423, 423, + 423, 423, 423, 423, 291, 291, 291, 291, 291, 291, + 291, 787, 147, 147, 147, 147, 423, 423, 423, 423, + 423, -88, -88, 147, 147, 423, 384, 423, 423, 423, + 423, 423, 423, 423, 423, 423, 423, 423, 0, 0, + 286, 422, 0, 785, 785, 785, 785, 0, 0, 0, + 0, 422, 422, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 286, 422, 0, 286, 0, 785, + 785, 423, 787, 787, 314, 384, 423, 0, 0, 0, + 0, 286, 785, 286, 339, 422, 339, 339, 206, 356, + 314, 510, 510, 510, 510, 0, 539, 787, 787, 787, + 787, 787, 787, 787, 787, 787, 787, 787, 785, 0, + 787, 0, 785, 785, 785, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 386, 90, 0, 386, 0, 740, 740, 423, 773, - 773, 498, 0, 423, 0, 0, 0, 0, 386, 740, - 386, 438, 90, 438, 438, 52, 211, 498, 468, 468, - 468, 468, 0, 483, 773, 773, 773, 773, 773, 773, - 773, 773, 773, 773, 773, 740, 0, 773, 0, 740, - 740, 740, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 740, 0, 0, - 918, 0, 0, 0, 0, 751, 0, 0, 0, 0, - 0, 0, 751, 926, 0, 0, 0, 0, 0, 0, - 740, 0, 0, 0, 0, 0, 0, 0, 0, 737, - 768, 0, 768, 0, 737, 737, 737 + 785, 0, 0, 946, 0, 0, 0, 0, 773, 0, + 0, 0, 0, 0, 0, 773, 951, 0, 0, 0, + 0, 0, 0, 785, 0, 0, 0, 0, 0, 0, + 0, 0, 766, 802, 0, 802, 0, 766, 766, 766 ); protected $actionDefault = array( 3,32767, 103,32767,32767,32767,32767,32767,32767,32767, 32767,32767, 101,32767,32767,32767,32767,32767,32767,32767, - 32767,32767,32767,32767,32767,32767, 569, 569, 569, 569, - 32767,32767, 246, 103,32767,32767, 445, 363, 363, 363, - 32767,32767, 513, 513, 513, 513, 513, 513,32767,32767, - 32767,32767,32767,32767, 445,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 582, 582, 582, + 582,32767,32767, 250, 103,32767,32767, 458, 376, 376, + 376,32767,32767, 526, 526, 526, 526, 526, 526,32767, + 32767,32767,32767,32767,32767, 458,32767,32767,32767,32767, 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, - 32767,32767,32767,32767,32767,32767,32767, 101,32767,32767, - 32767, 37, 7, 8, 10, 11, 50, 17,32767,32767, - 32767,32767,32767, 103,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767, 101,32767, + 32767,32767, 37, 7, 8, 10, 11, 50, 17, 314, + 32767,32767,32767,32767, 103,32767,32767,32767,32767,32767, 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, - 32767,32767,32767, 562,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767, 575,32767,32767,32767,32767, 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, - 32767,32767,32767,32767, 449, 428, 429, 431, 432, 362, - 514, 568, 304, 565, 361, 146, 316, 306, 234, 307, - 250, 450, 251, 451, 454, 455, 211, 278, 358, 150, - 392, 446, 394, 444, 448, 393, 368, 373, 374, 375, - 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, - 366, 367, 447, 425, 424, 423, 390,32767,32767, 391, - 395, 365, 398,32767,32767,32767,32767,32767,32767,32767, - 32767, 103,32767, 396, 397, 414, 415, 412, 413, 416, - 32767, 417, 418, 419, 420,32767,32767,32767,32767, 342, - 340, 405, 406, 295, 295,32767,32767,32767,32767,32767, - 32767,32767,32767, 507, 422,32767,32767,32767,32767,32767, - 32767,32767,32767,32767,32767,32767,32767,32767, 103,32767, - 101, 509, 387, 389, 477, 400, 401, 399, 369,32767, - 484,32767, 103, 486,32767,32767,32767, 112,32767,32767, - 272,32767, 508,32767, 515, 515,32767, 470, 101, 194, - 32767, 194, 194,32767,32767,32767,32767,32767,32767,32767, - 576, 470, 111, 111, 111, 111, 111, 111, 111, 111, - 111, 111, 111,32767, 194, 111,32767,32767,32767, 101, - 194, 194, 194, 194, 194, 194, 194, 194, 194, 194, - 189,32767, 260, 262, 103, 530, 194,32767, 489,32767, + 32767,32767,32767,32767,32767,32767, 462, 441, 442, 444, + 445, 375, 527, 581, 317, 578, 374, 146, 329, 319, + 238, 320, 254, 463, 255, 464, 467, 468, 211, 283, + 371, 150, 405, 459, 407, 457, 461, 406, 381, 386, + 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, + 397, 398, 379, 380, 460, 438, 437, 436, 403,32767, + 32767, 404, 408, 378, 411,32767,32767,32767,32767,32767, + 32767,32767,32767, 103,32767, 409, 410, 427, 428, 425, + 426, 429,32767, 430, 431, 432, 433,32767,32767, 306, + 32767,32767, 355, 353, 418, 419, 306,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767, 520, + 435,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767, 103,32767, 101, 522, 400, 402, + 490, 413, 414, 412, 382,32767, 497,32767, 103, 499, + 32767,32767,32767, 112,32767,32767,32767,32767, 521,32767, + 528, 528,32767, 483, 101, 194,32767, 194, 194,32767, + 32767,32767,32767,32767,32767,32767, 589, 483, 111, 111, + 111, 111, 111, 111, 111, 111, 111, 111, 111,32767, + 194, 111,32767,32767,32767, 101, 194, 194, 194, 194, + 194, 194, 194, 194, 194, 194, 189,32767, 264, 266, + 103, 543, 194,32767, 502,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 495,32767,32767, 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, - 32767, 482,32767,32767,32767,32767,32767,32767,32767,32767, - 32767,32767,32767,32767,32767,32767, 470, 410, 139,32767, - 139, 515, 402, 403, 404, 472, 515, 515, 515,32767, - 32767,32767,32767, 487, 487, 101, 101, 101, 101, 482, - 32767,32767, 112, 100, 100, 100, 100, 100, 104, 102, - 32767,32767,32767,32767, 100,32767, 102, 102,32767,32767, - 217, 208, 215, 102,32767, 534, 535, 215, 102, 219, - 219, 219, 239, 239, 461, 297, 102, 100, 102, 102, - 196, 297, 297,32767, 102, 461, 297, 461, 297, 198, - 297, 297, 297, 461, 297,32767,32767, 102, 297, 210, - 100, 100, 297,32767,32767,32767, 472,32767,32767,32767, + 32767,32767, 483, 423, 139,32767, 139, 528, 415, 416, + 417, 485, 528, 528, 528, 302, 285,32767,32767,32767, + 32767, 500, 500, 101, 101, 101, 101, 495,32767,32767, + 112, 100, 100, 100, 100, 100, 104, 102,32767,32767, + 32767,32767, 100,32767, 102, 102,32767,32767, 221, 208, + 219, 102,32767, 547, 548, 219, 102, 223, 223, 223, + 243, 243, 474, 308, 102, 100, 102, 102, 196, 308, + 308,32767, 102, 474, 308, 474, 308, 198, 308, 308, + 308, 474, 308,32767, 102, 308, 210, 100, 100, 308, + 32767,32767,32767, 485,32767,32767,32767,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767, 515,32767, + 532, 545, 421, 422, 424, 530, 446, 447, 448, 449, + 450, 451, 452, 454, 577,32767, 489,32767,32767,32767, + 32767, 328, 587,32767, 587,32767,32767,32767,32767,32767, 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, - 32767, 502,32767, 519, 532, 408, 409, 411, 517, 433, - 434, 435, 436, 437, 438, 439, 441, 564,32767, 476, - 32767,32767,32767,32767, 315, 574,32767, 574,32767,32767, + 588,32767, 528,32767,32767,32767,32767, 420, 9, 76, + 43, 44, 52, 58, 506, 507, 508, 509, 503, 504, + 510, 505,32767,32767, 511, 553,32767,32767, 529, 580, + 32767,32767,32767,32767,32767,32767, 139,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767, 515,32767, 137, 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, - 32767,32767,32767,32767,32767, 575,32767, 515,32767,32767, - 32767,32767, 407, 9, 76, 43, 44, 52, 58, 493, - 494, 495, 496, 490, 491, 497, 492,32767, 498, 540, - 32767,32767, 516, 567,32767,32767,32767,32767,32767,32767, - 139,32767,32767,32767,32767,32767,32767,32767,32767,32767, - 32767, 502,32767, 137,32767,32767,32767,32767,32767,32767, - 32767,32767,32767,32767,32767, 515,32767,32767,32767, 292, - 294,32767,32767,32767,32767,32767,32767,32767,32767,32767, - 32767,32767,32767,32767,32767,32767, 515,32767,32767,32767, - 280, 282,32767,32767,32767,32767,32767,32767,32767,32767, - 32767,32767,32767,32767,32767,32767, 277,32767,32767, 357, - 32767,32767,32767,32767, 336,32767,32767,32767,32767,32767, - 32767,32767,32767,32767,32767, 152, 152, 3, 3, 318, - 152, 152, 152, 318, 152, 318, 318, 318, 152, 152, - 152, 152, 152, 152, 184, 254, 257, 239, 239, 152, - 328, 152 + 32767, 528,32767,32767,32767, 304, 305,32767,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767, 528,32767,32767,32767, 287, 288,32767,32767, + 32767,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767,32767, 282,32767,32767, 370,32767,32767,32767,32767, + 349,32767,32767,32767,32767,32767,32767,32767,32767,32767, + 32767, 152, 152, 3, 3, 331, 152, 152, 152, 331, + 152, 331, 331, 331, 152, 152, 152, 152, 152, 152, + 276, 184, 258, 261, 243, 243, 152, 341, 152 ); protected $goto = array( - 192, 192, 663, 417, 636, 911, 983, 990, 991, 411, - 302, 303, 324, 557, 308, 416, 325, 418, 615, 1005, - 671, 317, 317, 317, 317, 163, 163, 163, 163, 216, - 193, 189, 189, 173, 175, 211, 189, 189, 189, 189, - 189, 190, 190, 190, 190, 190, 190, 184, 185, 186, - 187, 188, 213, 211, 214, 515, 516, 407, 517, 519, - 520, 521, 522, 523, 524, 525, 526, 1074, 164, 165, - 166, 191, 167, 168, 169, 162, 170, 171, 172, 174, - 210, 212, 215, 233, 236, 239, 240, 242, 253, 254, - 255, 256, 257, 258, 259, 261, 262, 263, 264, 271, - 272, 305, 306, 307, 412, 413, 414, 562, 217, 218, - 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, - 229, 230, 231, 176, 232, 177, 194, 195, 196, 234, - 184, 185, 186, 187, 188, 213, 1074, 197, 178, 179, - 180, 198, 194, 181, 235, 199, 161, 200, 201, 182, - 202, 203, 204, 183, 205, 206, 207, 208, 209, 819, - 579, 601, 601, 541, 532, 815, 816, 1204, 1204, 1204, - 1204, 1204, 1204, 1204, 1204, 1204, 1204, 954, 928, 928, - 926, 928, 695, 817, 531, 963, 958, 381, 385, 542, - 580, 584, 383, 532, 541, 550, 551, 390, 560, 581, - 595, 596, 824, 793, 872, 867, 868, 881, 14, 825, - 869, 822, 870, 871, 823, 480, 850, 481, 875, 527, - 527, 527, 527, 488, 583, 1222, 1222, 791, 1026, 1022, - 1023, 1222, 1222, 1222, 1222, 1222, 1222, 1222, 1222, 1222, - 1222, 1220, 1220, 812, 812, 599, 633, 1220, 1220, 1220, - 1220, 1220, 1220, 1220, 1220, 1220, 1220, 313, 297, 1173, - 1173, 1173, 987, 282, 282, 282, 282, 987, 987, 987, - 987, 987, 987, 987, 987, 987, 447, 447, 432, 249, - 249, 1173, 554, 432, 432, 447, 1173, 1173, 1173, 1173, - 1046, 1047, 1173, 1173, 1173, 1254, 1254, 1254, 1254, 337, - 797, 1272, 1272, 930, 246, 246, 246, 246, 248, 250, - 888, 335, 876, 340, 877, 889, 1249, 1250, 1272, 518, - 518, 832, 1262, 340, 340, 518, 518, 518, 518, 518, - 518, 518, 518, 518, 518, 1275, 844, 340, 340, 831, - 340, 797, 1289, 797, 630, 1169, 644, 645, 646, 529, - 529, 529, 611, 612, 534, 1273, 1273, 340, 812, 951, - 432, 432, 432, 432, 432, 432, 432, 432, 432, 432, - 432, 438, 1273, 432, 558, 593, 924, 924, 924, 924, - 379, 635, 438, 918, 925, 553, 1123, 1154, 904, 422, - 547, 1155, 1158, 905, 1159, 548, 326, 594, 1170, 696, - 614, 616, 1233, 634, 410, 807, 590, 653, 657, 965, - 661, 669, 961, 456, 1247, 1248, 809, 837, 1244, 1244, - 1244, 1171, 1230, 1231, 5, 656, 6, 533, 545, 389, - 968, 968, 533, 1166, 545, 834, 973, 382, 922, 397, - 670, 1256, 1256, 1256, 1256, 1011, 699, 561, 450, 451, - 452, 846, 842, 534, 457, 1280, 1281, 619, 619, 1015, - 1057, 395, 396, 995, 992, 993, 642, 1240, 643, 935, - 399, 400, 401, 461, 654, 0, 0, 0, 402, 0, - 840, 0, 333, 578, 1039, 0, 674, 660, 660, 0, - 666, 1037, 489, 588, 602, 605, 606, 607, 608, 627, - 628, 629, 673, 0, 0, 0, 1013, 893, 1062, 0, - 1242, 1242, 1013, 1168, 598, 252, 252, 0, 0, 970, - 269, 845, 833, 1010, 1014, 530, 530, 836, 0, 639, - 949, 933, 0, 338, 339, 830, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1165, 0, - 0, 0, 0, 0, 923, 0, 0, 0, 0, 0, + 194, 194, 669, 423, 642, 883, 839, 884, 1025, 417, + 308, 309, 330, 562, 314, 422, 331, 424, 621, 823, + 677, 851, 824, 585, 838, 857, 165, 165, 165, 165, + 218, 195, 191, 191, 175, 177, 213, 191, 191, 191, + 191, 191, 192, 192, 192, 192, 192, 192, 186, 187, + 188, 189, 190, 215, 213, 216, 522, 523, 413, 524, + 526, 527, 528, 529, 530, 531, 532, 533, 1094, 166, + 167, 168, 193, 169, 170, 171, 164, 172, 173, 174, + 176, 212, 214, 217, 235, 238, 241, 242, 244, 255, + 256, 257, 258, 259, 260, 261, 263, 264, 265, 266, + 274, 275, 311, 312, 313, 418, 419, 420, 567, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, + 230, 231, 232, 233, 178, 234, 179, 196, 197, 198, + 236, 186, 187, 188, 189, 190, 215, 1094, 199, 180, + 181, 182, 200, 196, 183, 237, 201, 199, 163, 202, + 203, 184, 204, 205, 206, 185, 207, 208, 209, 210, + 211, 323, 323, 323, 323, 826, 607, 607, 800, 546, + 539, 1189, 1224, 1224, 1224, 1224, 1224, 1224, 1224, 1224, + 1224, 1224, 1242, 1242, 343, 464, 1267, 1268, 1242, 1242, + 1242, 1242, 1242, 1242, 1242, 1242, 1242, 1242, 389, 539, + 546, 555, 556, 396, 565, 587, 601, 602, 831, 798, + 879, 874, 875, 888, 15, 832, 876, 829, 877, 878, + 830, 455, 455, 941, 882, 804, 1190, 251, 251, 559, + 455, 1240, 1240, 814, 1046, 1042, 1043, 1240, 1240, 1240, + 1240, 1240, 1240, 1240, 1240, 1240, 1240, 605, 639, 1191, + 1250, 1251, 341, 248, 248, 248, 248, 250, 252, 819, + 819, 1193, 1193, 1000, 1193, 1000, 804, 416, 804, 596, + 1000, 1282, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, + 1000, 1000, 1000, 1264, 1264, 962, 1264, 1193, 488, 5, + 489, 6, 1193, 1193, 1193, 1193, 495, 385, 1193, 1193, + 1193, 1274, 1274, 1274, 1274, 277, 277, 277, 277, 558, + 1276, 1276, 1276, 1276, 1066, 1067, 895, 346, 553, 319, + 303, 896, 703, 620, 622, 641, 640, 346, 346, 1143, + 659, 663, 976, 667, 675, 972, 1260, 430, 1292, 1292, + 332, 346, 346, 816, 346, 636, 1309, 650, 651, 652, + 844, 536, 536, 924, 536, 1292, 525, 525, 541, 1269, + 1270, 346, 525, 525, 525, 525, 525, 525, 525, 525, + 525, 525, 1295, 617, 618, 1033, 819, 446, 395, 1262, + 1262, 1033, 935, 935, 935, 935, 563, 599, 446, 929, + 936, 933, 403, 676, 822, 1186, 552, 534, 534, 534, + 534, 841, 589, 600, 984, 1031, 1253, 965, 939, 939, + 937, 939, 702, 465, 538, 974, 969, 344, 345, 706, + 440, 900, 1082, 853, 946, 440, 440, 1035, 604, 662, + 469, 1293, 1293, 981, 1077, 540, 550, 0, 0, 0, + 540, 843, 550, 645, 960, 388, 1174, 911, 1293, 837, + 1175, 1178, 912, 1179, 0, 566, 458, 459, 460, 541, + 849, 1185, 0, 1300, 1301, 254, 254, 401, 402, 0, + 0, 0, 648, 0, 649, 0, 405, 406, 407, 0, + 660, 0, 0, 408, 0, 0, 0, 339, 847, 594, + 608, 611, 612, 613, 614, 633, 634, 635, 679, 918, + 995, 1003, 1007, 1004, 1008, 0, 440, 440, 440, 440, + 440, 440, 440, 440, 440, 440, 440, 0, 1188, 440, + 852, 840, 1030, 1034, 584, 1059, 0, 680, 666, 666, + 944, 496, 672, 1057, 387, 391, 547, 586, 590, 425, + 0, 0, 0, 0, 0, 0, 425, 0, 0, 0, + 0, 0, 0, 934, 1012, 1005, 1009, 1006, 1010, 0, + 0, 0, 0, 0, 272, 0, 0, 0, 0, 537, + 537, 0, 0, 0, 0, 1075, 856, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1055, 849 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 979, + 979 ); protected $gotoCheck = array( - 42, 42, 72, 65, 65, 87, 87, 87, 87, 65, - 65, 65, 65, 65, 65, 65, 65, 65, 65, 115, - 9, 23, 23, 23, 23, 42, 42, 42, 42, 42, + 42, 42, 72, 65, 65, 64, 35, 64, 121, 65, + 65, 65, 65, 65, 65, 65, 65, 65, 65, 26, + 9, 35, 27, 124, 35, 45, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, @@ -753,91 +758,95 @@ class Php7 extends \PhpParser\ParserAbstract 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, - 42, 42, 42, 42, 42, 42, 42, 42, 42, 15, - 118, 104, 104, 75, 75, 25, 26, 104, 104, 104, - 104, 104, 104, 104, 104, 104, 104, 25, 25, 25, - 25, 25, 25, 27, 25, 25, 25, 58, 58, 58, - 58, 58, 75, 75, 75, 75, 75, 75, 75, 75, - 75, 75, 15, 7, 15, 15, 15, 15, 75, 15, - 15, 15, 15, 15, 15, 143, 45, 143, 15, 103, - 103, 103, 103, 143, 103, 156, 156, 6, 15, 15, - 15, 156, 156, 156, 156, 156, 156, 156, 156, 156, - 156, 157, 157, 22, 22, 55, 55, 157, 157, 157, - 157, 157, 157, 157, 157, 157, 157, 155, 155, 72, - 72, 72, 72, 24, 24, 24, 24, 72, 72, 72, - 72, 72, 72, 72, 72, 72, 137, 137, 23, 5, - 5, 72, 158, 23, 23, 137, 72, 72, 72, 72, - 132, 132, 72, 72, 72, 9, 9, 9, 9, 93, - 12, 168, 168, 49, 5, 5, 5, 5, 5, 5, - 72, 165, 64, 14, 64, 72, 164, 164, 168, 159, - 159, 35, 167, 14, 14, 159, 159, 159, 159, 159, - 159, 159, 159, 159, 159, 168, 35, 14, 14, 35, - 14, 12, 14, 12, 84, 20, 84, 84, 84, 19, - 19, 19, 83, 83, 14, 169, 169, 14, 22, 99, - 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, - 23, 19, 169, 23, 2, 2, 19, 19, 19, 19, - 61, 63, 19, 19, 19, 100, 139, 78, 78, 108, - 9, 78, 78, 78, 78, 48, 29, 9, 20, 48, - 48, 48, 14, 48, 13, 20, 13, 48, 48, 48, - 48, 48, 48, 162, 162, 162, 18, 39, 118, 118, - 118, 20, 20, 20, 46, 14, 46, 9, 9, 28, - 103, 103, 9, 148, 9, 37, 106, 9, 89, 89, - 89, 118, 118, 118, 118, 117, 95, 9, 9, 9, - 9, 41, 9, 14, 145, 9, 9, 111, 111, 120, - 135, 80, 80, 111, 111, 111, 80, 118, 80, 92, - 80, 80, 80, 82, 80, -1, -1, -1, 80, -1, - 9, -1, 80, 8, 8, -1, 8, 8, 8, -1, - 8, 8, 9, 79, 79, 79, 79, 79, 79, 79, - 79, 79, 79, -1, -1, -1, 118, 17, 17, -1, - 118, 118, 118, 14, 17, 5, 5, -1, -1, 17, - 24, 16, 16, 16, 16, 24, 24, 17, -1, 17, - 17, 16, -1, 93, 93, 17, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, 17, -1, - -1, -1, -1, -1, 16, -1, -1, -1, -1, -1, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 23, 23, 23, 23, 15, 106, 106, 7, 75, + 75, 20, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 162, 162, 95, 168, 168, 168, 162, 162, + 162, 162, 162, 162, 162, 162, 162, 162, 75, 75, + 75, 75, 75, 75, 75, 75, 75, 75, 15, 6, + 15, 15, 15, 15, 75, 15, 15, 15, 15, 15, + 15, 143, 143, 49, 15, 12, 20, 5, 5, 164, + 143, 163, 163, 20, 15, 15, 15, 163, 163, 163, + 163, 163, 163, 163, 163, 163, 163, 55, 55, 20, + 20, 20, 171, 5, 5, 5, 5, 5, 5, 22, + 22, 72, 72, 72, 72, 72, 12, 13, 12, 13, + 72, 173, 72, 72, 72, 72, 72, 72, 72, 72, + 72, 72, 72, 124, 124, 101, 124, 72, 149, 46, + 149, 46, 72, 72, 72, 72, 149, 61, 72, 72, + 72, 9, 9, 9, 9, 24, 24, 24, 24, 102, + 124, 124, 124, 124, 138, 138, 72, 14, 48, 161, + 161, 72, 48, 48, 48, 63, 48, 14, 14, 145, + 48, 48, 48, 48, 48, 48, 124, 111, 174, 174, + 29, 14, 14, 18, 14, 84, 14, 84, 84, 84, + 39, 19, 19, 90, 19, 174, 165, 165, 14, 170, + 170, 14, 165, 165, 165, 165, 165, 165, 165, 165, + 165, 165, 174, 83, 83, 124, 22, 19, 28, 124, + 124, 124, 19, 19, 19, 19, 2, 2, 19, 19, + 19, 91, 91, 91, 25, 154, 9, 105, 105, 105, + 105, 37, 105, 9, 108, 123, 14, 25, 25, 25, + 25, 25, 25, 151, 25, 25, 25, 95, 95, 97, + 23, 17, 17, 41, 94, 23, 23, 126, 17, 14, + 82, 175, 175, 17, 141, 9, 9, -1, -1, -1, + 9, 17, 9, 17, 17, 9, 78, 78, 175, 17, + 78, 78, 78, 78, -1, 9, 9, 9, 9, 14, + 9, 17, -1, 9, 9, 5, 5, 80, 80, -1, + -1, -1, 80, -1, 80, -1, 80, 80, 80, -1, + 80, -1, -1, 80, -1, -1, -1, 80, 9, 79, + 79, 79, 79, 79, 79, 79, 79, 79, 79, 87, + 87, 87, 87, 87, 87, -1, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, -1, 14, 23, + 16, 16, 16, 16, 8, 8, -1, 8, 8, 8, + 16, 8, 8, 8, 58, 58, 58, 58, 58, 115, + -1, -1, -1, -1, -1, -1, 115, -1, -1, -1, + -1, -1, -1, 16, 115, 115, 115, 115, 115, -1, + -1, -1, -1, -1, 24, -1, -1, -1, -1, 24, + 24, -1, -1, -1, -1, 16, 16, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, 16, 16 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 105, + 105 ); protected $gotoBase = array( - 0, 0, -303, 0, 0, 278, 214, 194, 476, 7, - 0, 0, 15, 78, 27, -175, 87, 61, 118, 84, - -33, 0, -74, 18, 260, 161, 162, 179, 103, 111, - 0, 0, 0, 0, 0, -35, 0, 107, 0, 105, - 0, 26, -1, 0, 0, 204, -275, 0, -281, 281, - 0, 0, 0, 0, 0, 207, 0, 0, 144, 0, - 0, 340, 0, 143, 294, -234, 0, 0, 0, 0, - 0, 0, -6, 0, 0, -168, 0, 0, -8, 150, - -10, 0, 16, -108, -339, 0, 0, -270, 0, 145, - 0, 0, 42, -164, 0, 52, 0, 0, 0, 326, - 344, 0, 0, 193, -76, 0, 81, 0, 115, 0, - 0, 184, 0, 0, 0, 17, 0, 86, 153, 0, - 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 21, 0, 0, 32, 0, 244, 0, 119, - 0, 0, 0, -260, 0, 30, 0, 0, 79, 0, - 0, 0, 0, 0, 0, -53, -12, 4, 255, 82, - 0, 0, 124, 0, -41, 283, 0, 293, 5, 59, - 0, 0 + 0, 0, -297, 0, 0, 226, 196, 159, 517, 7, + 0, 0, -66, -65, 25, -175, 78, -33, 39, 84, + -213, 0, -64, 158, 302, 390, 15, 18, 46, 49, + 0, 0, 0, 0, 0, -356, 0, 67, 0, 32, + 0, -10, -1, 0, 0, 13, -417, 0, -364, 200, + 0, 0, 0, 0, 0, 208, 0, 0, 490, 0, + 0, 256, 0, 85, -14, -236, 0, 0, 0, 0, + 0, 0, -6, 0, 0, -168, 0, 0, 45, 140, + -12, 0, -35, -95, -344, 0, 0, 221, 0, 0, + 27, 92, 0, 0, -11, -287, 0, 19, 0, 0, + 0, 251, 267, 0, 0, 370, -73, 0, 43, 0, + 0, 61, 0, 0, 0, 270, 0, 0, 0, 0, + 0, 6, 0, 40, 16, 0, -7, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, + 0, -2, 0, 188, 0, 59, 0, 0, 0, -195, + 0, -19, 0, 0, 35, 0, 0, 0, 0, 0, + 0, 3, -57, -8, 201, 117, 0, 0, -110, 0, + -4, 223, 0, 241, 36, 129, 0, 0 ); protected $gotoDefault = array( - -32768, 493, 703, 4, 704, 897, 780, 789, 577, 509, - 672, 334, 603, 408, 1238, 874, 1061, 559, 808, 1182, - 1190, 439, 811, 318, 320, 856, 857, 858, 386, 371, - 377, 384, 625, 604, 474, 843, 435, 835, 466, 838, - 434, 847, 160, 405, 491, 851, 3, 853, 536, 884, - 372, 861, 373, 649, 863, 544, 865, 866, 380, 387, - 388, 1066, 552, 600, 878, 241, 546, 879, 370, 880, - 887, 375, 378, 658, 446, 486, 479, 398, 1041, 587, - 622, 443, 460, 610, 609, 597, 459, 640, 403, 920, - 467, 444, 934, 336, 942, 701, 1073, 617, 469, 950, - 618, 957, 960, 510, 511, 458, 972, 273, 470, 1000, - 641, 985, 620, 998, 453, 1004, 436, 1012, 1226, 437, - 1016, 260, 1019, 274, 404, 419, 1024, 1025, 8, 1031, - 664, 665, 10, 270, 490, 1056, 659, 433, 1072, 423, - 1142, 1144, 538, 471, 1162, 1161, 652, 487, 1167, 1229, - 431, 512, 454, 304, 513, 296, 322, 301, 528, 283, - 323, 514, 455, 1235, 1243, 319, 29, 1263, 1274, 330, - 556, 592 + -32768, 500, 710, 4, 711, 904, 787, 796, 582, 516, + 678, 340, 609, 414, 1258, 881, 1081, 564, 815, 1202, + 1210, 447, 818, 324, 700, 863, 864, 865, 392, 377, + 383, 390, 631, 610, 482, 850, 443, 842, 474, 845, + 442, 854, 162, 411, 498, 858, 3, 860, 543, 891, + 378, 868, 379, 655, 870, 549, 872, 873, 386, 393, + 394, 1086, 557, 606, 885, 243, 551, 886, 376, 887, + 894, 381, 384, 664, 454, 493, 487, 404, 1061, 593, + 628, 451, 468, 616, 615, 603, 467, 426, 409, 326, + 923, 931, 475, 452, 945, 342, 953, 708, 1093, 623, + 477, 961, 624, 968, 971, 517, 518, 466, 983, 269, + 986, 478, 1018, 646, 647, 998, 625, 626, 1016, 461, + 583, 1024, 444, 1032, 1246, 445, 1036, 262, 1039, 276, + 410, 427, 1044, 1045, 8, 1051, 670, 671, 10, 273, + 497, 1076, 665, 441, 1092, 431, 1162, 1164, 545, 479, + 1182, 1181, 658, 494, 1187, 1249, 439, 519, 462, 310, + 520, 302, 328, 307, 535, 289, 329, 521, 463, 1255, + 1263, 325, 30, 1283, 1294, 336, 561, 598 ); protected $ruleToNonTerminal = array( @@ -862,22 +871,23 @@ class Php7 extends \PhpParser\ParserAbstract 49, 49, 25, 25, 68, 68, 71, 71, 70, 69, 69, 62, 74, 74, 75, 75, 76, 76, 77, 77, 78, 78, 26, 26, 27, 27, 27, 27, 86, 86, - 88, 88, 81, 81, 81, 82, 82, 85, 85, 83, - 83, 89, 90, 90, 56, 56, 64, 64, 67, 67, - 67, 66, 91, 91, 92, 57, 57, 57, 57, 93, - 93, 94, 94, 95, 95, 96, 97, 97, 98, 98, - 99, 99, 54, 54, 50, 50, 101, 52, 52, 102, - 51, 51, 53, 53, 63, 63, 63, 63, 79, 79, - 105, 105, 107, 107, 107, 107, 107, 106, 106, 106, - 109, 109, 109, 87, 87, 111, 111, 111, 110, 110, - 112, 112, 113, 113, 113, 108, 108, 80, 80, 80, - 20, 20, 114, 114, 115, 115, 115, 115, 59, 116, - 116, 117, 60, 119, 119, 120, 120, 121, 121, 84, - 122, 122, 122, 122, 122, 122, 127, 127, 128, 128, - 129, 129, 129, 129, 129, 130, 131, 131, 126, 126, - 123, 123, 125, 125, 133, 133, 132, 132, 132, 132, - 132, 132, 132, 124, 134, 134, 136, 135, 135, 61, - 100, 137, 137, 55, 55, 42, 42, 42, 42, 42, + 88, 88, 81, 81, 89, 89, 90, 90, 90, 82, + 82, 85, 85, 83, 83, 91, 92, 92, 56, 56, + 64, 64, 67, 67, 67, 66, 93, 93, 94, 57, + 57, 57, 57, 95, 95, 96, 96, 97, 97, 98, + 99, 99, 100, 100, 101, 101, 54, 54, 50, 50, + 103, 52, 52, 104, 51, 51, 53, 53, 63, 63, + 63, 63, 79, 79, 107, 107, 109, 109, 110, 110, + 110, 110, 108, 108, 108, 112, 112, 112, 112, 87, + 87, 115, 115, 115, 113, 113, 116, 116, 114, 114, + 117, 117, 118, 118, 118, 118, 111, 111, 80, 80, + 80, 20, 20, 20, 120, 119, 119, 121, 121, 121, + 121, 59, 122, 122, 123, 60, 125, 125, 126, 126, + 127, 127, 84, 128, 128, 128, 128, 128, 128, 133, + 133, 134, 134, 135, 135, 135, 135, 135, 136, 137, + 137, 132, 132, 129, 129, 131, 131, 139, 139, 138, + 138, 138, 138, 138, 138, 138, 130, 140, 140, 142, + 141, 141, 61, 102, 143, 143, 55, 55, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, @@ -886,20 +896,21 @@ class Php7 extends \PhpParser\ParserAbstract 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, - 42, 42, 42, 42, 42, 42, 42, 42, 144, 138, - 138, 143, 143, 146, 147, 147, 148, 149, 149, 149, - 19, 19, 72, 72, 72, 72, 139, 139, 139, 139, - 151, 151, 140, 140, 142, 142, 142, 145, 145, 156, - 156, 156, 156, 156, 156, 156, 156, 156, 157, 157, - 104, 159, 159, 159, 159, 141, 141, 141, 141, 141, - 141, 141, 141, 58, 58, 154, 154, 154, 154, 160, - 160, 150, 150, 150, 161, 161, 161, 161, 161, 161, - 73, 73, 65, 65, 65, 65, 118, 118, 118, 118, - 164, 163, 153, 153, 153, 153, 153, 153, 153, 152, - 152, 152, 162, 162, 162, 162, 103, 158, 166, 166, - 165, 165, 167, 167, 167, 167, 167, 167, 167, 167, - 155, 155, 155, 155, 169, 170, 168, 168, 168, 168, - 168, 168, 168, 168, 171, 171, 171, 171 + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 150, 144, 144, 149, 149, 152, 153, 153, 154, + 155, 155, 155, 19, 19, 72, 72, 72, 72, 145, + 145, 145, 145, 157, 157, 146, 146, 148, 148, 148, + 151, 151, 162, 162, 162, 162, 162, 162, 162, 162, + 162, 163, 163, 106, 165, 165, 165, 165, 147, 147, + 147, 147, 147, 147, 147, 147, 58, 58, 160, 160, + 160, 160, 166, 166, 156, 156, 156, 167, 167, 167, + 167, 167, 167, 73, 73, 65, 65, 65, 65, 124, + 124, 124, 124, 170, 169, 159, 159, 159, 159, 159, + 159, 159, 158, 158, 158, 168, 168, 168, 168, 105, + 164, 172, 172, 171, 171, 173, 173, 173, 173, 173, + 173, 173, 173, 161, 161, 161, 161, 175, 176, 174, + 174, 174, 174, 174, 174, 174, 174, 177, 177, 177, + 177 ); protected $ruleToLength = array( @@ -924,44 +935,46 @@ class Php7 extends \PhpParser\ParserAbstract 2, 1, 1, 1, 0, 2, 1, 3, 8, 0, 4, 2, 1, 3, 0, 1, 0, 1, 0, 1, 3, 1, 8, 9, 8, 7, 6, 8, 0, 2, - 0, 2, 1, 2, 2, 0, 2, 0, 2, 0, - 2, 2, 1, 3, 1, 4, 1, 4, 1, 1, - 4, 2, 1, 3, 3, 3, 4, 4, 5, 0, - 2, 4, 3, 1, 1, 7, 0, 2, 1, 3, - 3, 4, 1, 4, 0, 2, 5, 0, 2, 6, - 0, 2, 0, 3, 1, 2, 1, 1, 2, 0, - 1, 3, 0, 1, 1, 1, 1, 6, 8, 6, - 1, 2, 1, 1, 1, 1, 1, 1, 3, 3, - 3, 3, 1, 2, 1, 0, 1, 0, 2, 2, - 2, 4, 1, 3, 1, 2, 2, 3, 2, 3, - 1, 1, 2, 3, 1, 1, 3, 2, 0, 1, - 5, 5, 10, 3, 5, 1, 1, 3, 0, 2, - 4, 5, 4, 4, 4, 3, 1, 1, 1, 1, - 1, 1, 0, 1, 1, 2, 1, 1, 1, 1, - 1, 1, 1, 2, 1, 3, 1, 1, 3, 2, - 2, 3, 1, 0, 1, 1, 3, 3, 3, 4, - 1, 1, 2, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, + 0, 2, 1, 2, 1, 2, 1, 1, 1, 0, + 2, 0, 2, 0, 2, 2, 1, 3, 1, 4, + 1, 4, 1, 1, 4, 2, 1, 3, 3, 3, + 4, 4, 5, 0, 2, 4, 3, 1, 1, 7, + 0, 2, 1, 3, 3, 4, 1, 4, 0, 2, + 5, 0, 2, 6, 0, 2, 0, 3, 1, 2, + 1, 1, 2, 0, 1, 3, 0, 2, 1, 1, + 1, 1, 6, 8, 6, 1, 2, 1, 1, 1, + 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, + 3, 3, 1, 2, 1, 1, 0, 1, 0, 2, + 2, 2, 4, 3, 1, 1, 3, 1, 2, 2, + 3, 2, 3, 1, 1, 2, 3, 1, 1, 3, + 2, 0, 1, 5, 5, 10, 3, 5, 1, 1, + 3, 0, 2, 4, 5, 4, 4, 4, 3, 1, + 1, 1, 1, 1, 1, 0, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 3, 1, + 1, 3, 2, 2, 3, 1, 0, 1, 1, 3, + 3, 3, 4, 1, 1, 2, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, + 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, - 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 5, 4, 3, 4, 4, 2, 2, - 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 1, 3, 2, 1, 2, 4, 2, 2, - 8, 9, 8, 9, 9, 10, 9, 10, 8, 3, - 2, 0, 4, 2, 1, 3, 2, 2, 2, 4, - 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, - 1, 1, 0, 3, 0, 1, 1, 0, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, - 3, 4, 1, 1, 3, 1, 1, 1, 1, 1, - 3, 2, 3, 0, 1, 1, 3, 1, 1, 1, - 1, 1, 3, 1, 1, 4, 4, 1, 4, 4, - 0, 1, 1, 1, 3, 3, 1, 4, 2, 2, - 1, 3, 1, 4, 4, 3, 3, 3, 3, 1, - 3, 1, 1, 3, 1, 1, 4, 1, 1, 1, - 3, 1, 1, 2, 1, 3, 4, 3, 2, 0, - 2, 2, 1, 2, 1, 1, 1, 4, 3, 3, - 3, 3, 6, 3, 1, 1, 2, 1 + 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 5, 4, 3, 4, + 4, 2, 2, 4, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 1, 3, 2, 1, 2, + 4, 2, 2, 8, 9, 8, 9, 9, 10, 9, + 10, 8, 3, 2, 0, 4, 2, 1, 3, 2, + 2, 2, 4, 1, 1, 1, 1, 1, 1, 1, + 1, 3, 1, 1, 1, 0, 3, 0, 1, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 3, 3, 3, 4, 1, 1, 3, 1, 1, + 1, 1, 1, 3, 2, 3, 0, 1, 1, 3, + 1, 1, 1, 1, 1, 3, 1, 1, 4, 4, + 1, 4, 4, 0, 1, 1, 1, 3, 3, 1, + 4, 2, 2, 1, 3, 1, 4, 4, 3, 3, + 3, 3, 1, 3, 1, 1, 3, 1, 1, 4, + 1, 1, 1, 3, 1, 1, 2, 1, 3, 4, + 3, 2, 0, 2, 2, 1, 2, 1, 1, 1, + 4, 3, 3, 3, 3, 6, 3, 1, 1, 2, + 1 ); protected function initReduceCallbacks() { @@ -1634,940 +1647,939 @@ class Php7 extends \PhpParser\ParserAbstract $this->semValue = 0; }, 213 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; + $this->semValue = $this->semStack[$stackPos-(2-1)]; }, 214 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_FINAL; + $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 215 => function ($stackPos) { - $this->semValue = null; + $this->checkClassModifier($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $this->semValue = $this->semStack[$stackPos-(2-1)] | $this->semStack[$stackPos-(2-2)]; }, 216 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(2-2)]; + $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; }, 217 => function ($stackPos) { - $this->semValue = array(); + $this->semValue = Stmt\Class_::MODIFIER_FINAL; }, 218 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(2-2)]; + $this->semValue = Stmt\Class_::MODIFIER_READONLY; }, 219 => function ($stackPos) { - $this->semValue = array(); + $this->semValue = null; }, 220 => function ($stackPos) { $this->semValue = $this->semStack[$stackPos-(2-2)]; }, 221 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(2-1)]; + $this->semValue = array(); }, 222 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(1-1)]); + $this->semValue = $this->semStack[$stackPos-(2-2)]; }, 223 => function ($stackPos) { - $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + $this->semValue = array(); }, 224 => function ($stackPos) { - $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + $this->semValue = $this->semStack[$stackPos-(2-2)]; }, 225 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(4-2)]; + $this->semValue = $this->semStack[$stackPos-(2-1)]; }, 226 => function ($stackPos) { - $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + $this->semValue = array($this->semStack[$stackPos-(1-1)]); }, 227 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(4-2)]; + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; }, 228 => function ($stackPos) { $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); }, 229 => function ($stackPos) { - $this->semValue = null; + $this->semValue = $this->semStack[$stackPos-(4-2)]; }, 230 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(4-2)]; - }, - 231 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(2-1)]; - }, - 232 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(1-1)]); - }, - 233 => function ($stackPos) { - $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; - }, - 234 => function ($stackPos) { - $this->semValue = new Stmt\DeclareDeclare($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 235 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(3-2)]; - }, - 236 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(4-3)]; - }, - 237 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(4-2)]; - }, - 238 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(5-3)]; - }, - 239 => function ($stackPos) { - $this->semValue = array(); - }, - 240 => function ($stackPos) { - $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; - }, - 241 => function ($stackPos) { - $this->semValue = new Stmt\Case_($this->semStack[$stackPos-(4-2)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); - }, - 242 => function ($stackPos) { - $this->semValue = new Stmt\Case_(null, $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 243 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, - 244 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, - 245 => function ($stackPos) { - $this->semValue = new Expr\Match_($this->semStack[$stackPos-(7-3)], $this->semStack[$stackPos-(7-6)], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); - }, - 246 => function ($stackPos) { - $this->semValue = []; - }, - 247 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(2-1)]; - }, - 248 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(1-1)]); - }, - 249 => function ($stackPos) { - $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; - }, - 250 => function ($stackPos) { - $this->semValue = new Node\MatchArm($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 251 => function ($stackPos) { - $this->semValue = new Node\MatchArm(null, $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); - }, - 252 => function ($stackPos) { $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); }, - 253 => function ($stackPos) { + 231 => function ($stackPos) { $this->semValue = $this->semStack[$stackPos-(4-2)]; }, - 254 => function ($stackPos) { - $this->semValue = array(); + 232 => function ($stackPos) { + $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); }, - 255 => function ($stackPos) { - $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; - }, - 256 => function ($stackPos) { - $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos-(5-3)], is_array($this->semStack[$stackPos-(5-5)]) ? $this->semStack[$stackPos-(5-5)] : array($this->semStack[$stackPos-(5-5)]), $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); - }, - 257 => function ($stackPos) { - $this->semValue = array(); - }, - 258 => function ($stackPos) { - $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; - }, - 259 => function ($stackPos) { - $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-6)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); - }, - 260 => function ($stackPos) { + 233 => function ($stackPos) { $this->semValue = null; }, - 261 => function ($stackPos) { - $this->semValue = new Stmt\Else_(is_array($this->semStack[$stackPos-(2-2)]) ? $this->semStack[$stackPos-(2-2)] : array($this->semStack[$stackPos-(2-2)]), $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + 234 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; }, - 262 => function ($stackPos) { - $this->semValue = null; - }, - 263 => function ($stackPos) { - $this->semValue = new Stmt\Else_($this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 264 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(1-1)], false); - }, - 265 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(2-2)], true); - }, - 266 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(1-1)], false); - }, - 267 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(1-1)], false); - }, - 268 => function ($stackPos) { + 235 => function ($stackPos) { $this->semValue = $this->semStack[$stackPos-(2-1)]; }, - 269 => function ($stackPos) { - $this->semValue = array(); - }, - 270 => function ($stackPos) { + 236 => function ($stackPos) { $this->semValue = array($this->semStack[$stackPos-(1-1)]); }, - 271 => function ($stackPos) { + 237 => function ($stackPos) { $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; }, + 238 => function ($stackPos) { + $this->semValue = new Stmt\DeclareDeclare($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 239 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 240 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-3)]; + }, + 241 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 242 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(5-3)]; + }, + 243 => function ($stackPos) { + $this->semValue = array(); + }, + 244 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 245 => function ($stackPos) { + $this->semValue = new Stmt\Case_($this->semStack[$stackPos-(4-2)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 246 => function ($stackPos) { + $this->semValue = new Stmt\Case_(null, $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 247 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 248 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 249 => function ($stackPos) { + $this->semValue = new Expr\Match_($this->semStack[$stackPos-(7-3)], $this->semStack[$stackPos-(7-6)], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes); + }, + 250 => function ($stackPos) { + $this->semValue = []; + }, + 251 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 252 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 253 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 254 => function ($stackPos) { + $this->semValue = new Node\MatchArm($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 255 => function ($stackPos) { + $this->semValue = new Node\MatchArm(null, $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 256 => function ($stackPos) { + $this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]); + }, + 257 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 258 => function ($stackPos) { + $this->semValue = array(); + }, + 259 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 260 => function ($stackPos) { + $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos-(5-3)], is_array($this->semStack[$stackPos-(5-5)]) ? $this->semStack[$stackPos-(5-5)] : array($this->semStack[$stackPos-(5-5)]), $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 261 => function ($stackPos) { + $this->semValue = array(); + }, + 262 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 263 => function ($stackPos) { + $this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-6)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 264 => function ($stackPos) { + $this->semValue = null; + }, + 265 => function ($stackPos) { + $this->semValue = new Stmt\Else_(is_array($this->semStack[$stackPos-(2-2)]) ? $this->semStack[$stackPos-(2-2)] : array($this->semStack[$stackPos-(2-2)]), $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 266 => function ($stackPos) { + $this->semValue = null; + }, + 267 => function ($stackPos) { + $this->semValue = new Stmt\Else_($this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 268 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)], false); + }, + 269 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(2-2)], true); + }, + 270 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)], false); + }, + 271 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)], false); + }, 272 => function ($stackPos) { - $this->semValue = 0; + $this->semValue = $this->semStack[$stackPos-(2-1)]; }, 273 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_PUBLIC; + $this->semValue = array(); }, 274 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_PROTECTED; + $this->semValue = array($this->semStack[$stackPos-(1-1)]); }, 275 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_PRIVATE; + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; }, 276 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_READONLY; + $this->semValue = 0; }, 277 => function ($stackPos) { + $this->checkModifier($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $this->semValue = $this->semStack[$stackPos-(2-1)] | $this->semStack[$stackPos-(2-2)]; + }, + 278 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PUBLIC; + }, + 279 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PROTECTED; + }, + 280 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PRIVATE; + }, + 281 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_READONLY; + }, + 282 => function ($stackPos) { $this->semValue = new Node\Param($this->semStack[$stackPos-(6-6)], null, $this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-4)], $this->semStack[$stackPos-(6-5)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes, $this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-1)]); $this->checkParam($this->semValue); }, - 278 => function ($stackPos) { + 283 => function ($stackPos) { $this->semValue = new Node\Param($this->semStack[$stackPos-(8-6)], $this->semStack[$stackPos-(8-8)], $this->semStack[$stackPos-(8-3)], $this->semStack[$stackPos-(8-4)], $this->semStack[$stackPos-(8-5)], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes, $this->semStack[$stackPos-(8-2)], $this->semStack[$stackPos-(8-1)]); $this->checkParam($this->semValue); }, - 279 => function ($stackPos) { + 284 => function ($stackPos) { $this->semValue = new Node\Param(new Expr\Error($this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes), null, $this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-4)], $this->semStack[$stackPos-(6-5)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes, $this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-1)]); }, - 280 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; - }, - 281 => function ($stackPos) { - $this->semValue = new Node\NullableType($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); - }, - 282 => function ($stackPos) { - $this->semValue = new Node\UnionType($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); - }, - 283 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; - }, - 284 => function ($stackPos) { - $this->semValue = new Node\Name('static', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); - }, 285 => function ($stackPos) { - $this->semValue = $this->handleBuiltinTypes($this->semStack[$stackPos-(1-1)]); + $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 286 => function ($stackPos) { - $this->semValue = new Node\Identifier('array', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); - }, - 287 => function ($stackPos) { - $this->semValue = new Node\Identifier('callable', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); - }, - 288 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]); - }, - 289 => function ($stackPos) { - $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; - }, - 290 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]); - }, - 291 => function ($stackPos) { - $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; - }, - 292 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; - }, - 293 => function ($stackPos) { $this->semValue = new Node\NullableType($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, - 294 => function ($stackPos) { + 287 => function ($stackPos) { $this->semValue = new Node\UnionType($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); }, + 288 => function ($stackPos) { + $this->semValue = new Node\IntersectionType($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 289 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 290 => function ($stackPos) { + $this->semValue = new Node\Name('static', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 291 => function ($stackPos) { + $this->semValue = $this->handleBuiltinTypes($this->semStack[$stackPos-(1-1)]); + }, + 292 => function ($stackPos) { + $this->semValue = new Node\Identifier('array', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 293 => function ($stackPos) { + $this->semValue = new Node\Identifier('callable', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 294 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]); + }, 295 => function ($stackPos) { - $this->semValue = null; + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; }, 296 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; + $this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]); }, 297 => function ($stackPos) { - $this->semValue = null; + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; }, 298 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(2-2)]; + $this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]); }, 299 => function ($stackPos) { - $this->semValue = null; + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; }, 300 => function ($stackPos) { - $this->semValue = array(); + $this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]); }, 301 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(4-2)]; + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; }, 302 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(1-1)]); - }, - 303 => function ($stackPos) { - $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; - }, - 304 => function ($stackPos) { - $this->semValue = new Node\Arg($this->semStack[$stackPos-(1-1)], false, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); - }, - 305 => function ($stackPos) { - $this->semValue = new Node\Arg($this->semStack[$stackPos-(2-2)], true, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); - }, - 306 => function ($stackPos) { - $this->semValue = new Node\Arg($this->semStack[$stackPos-(2-2)], false, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); - }, - 307 => function ($stackPos) { - $this->semValue = new Node\Arg($this->semStack[$stackPos-(3-3)], false, false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->semStack[$stackPos-(3-1)]); - }, - 308 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(2-1)]; - }, - 309 => function ($stackPos) { - $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; - }, - 310 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(1-1)]); - }, - 311 => function ($stackPos) { $this->semValue = $this->semStack[$stackPos-(1-1)]; }, - 312 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(2-1)]; + 303 => function ($stackPos) { + $this->semValue = new Node\NullableType($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, - 313 => function ($stackPos) { - $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + 304 => function ($stackPos) { + $this->semValue = new Node\UnionType($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); }, - 314 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(1-1)]); + 305 => function ($stackPos) { + $this->semValue = new Node\IntersectionType($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); }, - 315 => function ($stackPos) { - $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(1-1)], null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + 306 => function ($stackPos) { + $this->semValue = null; }, - 316 => function ($stackPos) { - $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + 307 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; }, - 317 => function ($stackPos) { - if ($this->semStack[$stackPos-(2-2)] !== null) { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; } + 308 => function ($stackPos) { + $this->semValue = null; }, - 318 => function ($stackPos) { + 309 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-2)]; + }, + 310 => function ($stackPos) { + $this->semValue = null; + }, + 311 => function ($stackPos) { $this->semValue = array(); }, + 312 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(4-2)]; + }, + 313 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(3-2)]); + }, + 314 => function ($stackPos) { + $this->semValue = new Node\VariadicPlaceholder($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 315 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 316 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 317 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos-(1-1)], false, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 318 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos-(2-2)], true, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, 319 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos-(2-2)], false, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 320 => function ($stackPos) { + $this->semValue = new Node\Arg($this->semStack[$stackPos-(3-3)], false, false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->semStack[$stackPos-(3-1)]); + }, + 321 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 322 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 323 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 324 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 325 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 326 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 327 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 328 => function ($stackPos) { + $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(1-1)], null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 329 => function ($stackPos) { + $this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 330 => function ($stackPos) { + if ($this->semStack[$stackPos-(2-2)] !== null) { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; } + }, + 331 => function ($stackPos) { + $this->semValue = array(); + }, + 332 => function ($stackPos) { $startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; }; if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)]; }, - 320 => function ($stackPos) { + 333 => function ($stackPos) { $this->semValue = new Stmt\Property($this->semStack[$stackPos-(5-2)], $this->semStack[$stackPos-(5-4)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes, $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-1)]); $this->checkProperty($this->semValue, $stackPos-(5-2)); }, - 321 => function ($stackPos) { + 334 => function ($stackPos) { $this->semValue = new Stmt\ClassConst($this->semStack[$stackPos-(5-4)], $this->semStack[$stackPos-(5-2)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes, $this->semStack[$stackPos-(5-1)]); $this->checkClassConst($this->semValue, $stackPos-(5-2)); }, - 322 => function ($stackPos) { + 335 => function ($stackPos) { $this->semValue = new Stmt\ClassMethod($this->semStack[$stackPos-(10-5)], ['type' => $this->semStack[$stackPos-(10-2)], 'byRef' => $this->semStack[$stackPos-(10-4)], 'params' => $this->semStack[$stackPos-(10-7)], 'returnType' => $this->semStack[$stackPos-(10-9)], 'stmts' => $this->semStack[$stackPos-(10-10)], 'attrGroups' => $this->semStack[$stackPos-(10-1)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes); $this->checkClassMethod($this->semValue, $stackPos-(10-2)); }, - 323 => function ($stackPos) { + 336 => function ($stackPos) { $this->semValue = new Stmt\TraitUse($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, - 324 => function ($stackPos) { + 337 => function ($stackPos) { $this->semValue = new Stmt\EnumCase($this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-4)], $this->semStack[$stackPos-(5-1)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); }, - 325 => function ($stackPos) { + 338 => function ($stackPos) { $this->semValue = null; /* will be skipped */ }, - 326 => function ($stackPos) { - $this->semValue = array(); - }, - 327 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(3-2)]; - }, - 328 => function ($stackPos) { - $this->semValue = array(); - }, - 329 => function ($stackPos) { - $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; - }, - 330 => function ($stackPos) { - $this->semValue = new Stmt\TraitUseAdaptation\Precedence($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); - }, - 331 => function ($stackPos) { - $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(5-1)][0], $this->semStack[$stackPos-(5-1)][1], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-4)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); - }, - 332 => function ($stackPos) { - $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], $this->semStack[$stackPos-(4-3)], null, $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); - }, - 333 => function ($stackPos) { - $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); - }, - 334 => function ($stackPos) { - $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); - }, - 335 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]); - }, - 336 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; - }, - 337 => function ($stackPos) { - $this->semValue = array(null, $this->semStack[$stackPos-(1-1)]); - }, - 338 => function ($stackPos) { - $this->semValue = null; - }, 339 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; + $this->semValue = array(); }, 340 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; - }, - 341 => function ($stackPos) { - $this->semValue = 0; - }, - 342 => function ($stackPos) { - $this->semValue = 0; - }, - 343 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; - }, - 344 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; - }, - 345 => function ($stackPos) { - $this->checkModifier($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $this->semValue = $this->semStack[$stackPos-(2-1)] | $this->semStack[$stackPos-(2-2)]; - }, - 346 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_PUBLIC; - }, - 347 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_PROTECTED; - }, - 348 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_PRIVATE; - }, - 349 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_STATIC; - }, - 350 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; - }, - 351 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_FINAL; - }, - 352 => function ($stackPos) { - $this->semValue = Stmt\Class_::MODIFIER_READONLY; - }, - 353 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(2-1)]; - }, - 354 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(1-1)]); - }, - 355 => function ($stackPos) { - $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; - }, - 356 => function ($stackPos) { - $this->semValue = new Node\VarLikeIdentifier(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); - }, - 357 => function ($stackPos) { - $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos-(1-1)], null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); - }, - 358 => function ($stackPos) { - $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 359 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(2-1)]; - }, - 360 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(2-1)]; - }, - 361 => function ($stackPos) { - $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; - }, - 362 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(1-1)]); - }, - 363 => function ($stackPos) { - $this->semValue = array(); - }, - 364 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; - }, - 365 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; - }, - 366 => function ($stackPos) { - $this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 367 => function ($stackPos) { - $this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 368 => function ($stackPos) { - $this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 369 => function ($stackPos) { - $this->semValue = new Expr\AssignRef($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); - }, - 370 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; - }, - 371 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; - }, - 372 => function ($stackPos) { - $this->semValue = new Expr\Clone_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); - }, - 373 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 374 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 375 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 376 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 377 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 378 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 379 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 380 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 381 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 382 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 383 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 384 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 385 => function ($stackPos) { - $this->semValue = new Expr\AssignOp\Coalesce($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 386 => function ($stackPos) { - $this->semValue = new Expr\PostInc($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); - }, - 387 => function ($stackPos) { - $this->semValue = new Expr\PreInc($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); - }, - 388 => function ($stackPos) { - $this->semValue = new Expr\PostDec($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); - }, - 389 => function ($stackPos) { - $this->semValue = new Expr\PreDec($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); - }, - 390 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 391 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 392 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 393 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 394 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 395 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 396 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 397 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 398 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 399 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 400 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 401 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 402 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 403 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 404 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 405 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 406 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 407 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 408 => function ($stackPos) { - $this->semValue = new Expr\UnaryPlus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); - }, - 409 => function ($stackPos) { - $this->semValue = new Expr\UnaryMinus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); - }, - 410 => function ($stackPos) { - $this->semValue = new Expr\BooleanNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); - }, - 411 => function ($stackPos) { - $this->semValue = new Expr\BitwiseNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); - }, - 412 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 413 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 414 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 415 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 416 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Spaceship($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 417 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 418 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 419 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 420 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 421 => function ($stackPos) { - $this->semValue = new Expr\Instanceof_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 422 => function ($stackPos) { $this->semValue = $this->semStack[$stackPos-(3-2)]; }, + 341 => function ($stackPos) { + $this->semValue = array(); + }, + 342 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 343 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Precedence($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 344 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(5-1)][0], $this->semStack[$stackPos-(5-1)][1], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-4)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 345 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], $this->semStack[$stackPos-(4-3)], null, $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 346 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 347 => function ($stackPos) { + $this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 348 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]); + }, + 349 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 350 => function ($stackPos) { + $this->semValue = array(null, $this->semStack[$stackPos-(1-1)]); + }, + 351 => function ($stackPos) { + $this->semValue = null; + }, + 352 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 353 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 354 => function ($stackPos) { + $this->semValue = 0; + }, + 355 => function ($stackPos) { + $this->semValue = 0; + }, + 356 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 357 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 358 => function ($stackPos) { + $this->checkModifier($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $this->semValue = $this->semStack[$stackPos-(2-1)] | $this->semStack[$stackPos-(2-2)]; + }, + 359 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PUBLIC; + }, + 360 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PROTECTED; + }, + 361 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_PRIVATE; + }, + 362 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_STATIC; + }, + 363 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_ABSTRACT; + }, + 364 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_FINAL; + }, + 365 => function ($stackPos) { + $this->semValue = Stmt\Class_::MODIFIER_READONLY; + }, + 366 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 367 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 368 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 369 => function ($stackPos) { + $this->semValue = new Node\VarLikeIdentifier(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 370 => function ($stackPos) { + $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos-(1-1)], null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 371 => function ($stackPos) { + $this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 372 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 373 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, + 374 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 375 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 376 => function ($stackPos) { + $this->semValue = array(); + }, + 377 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 378 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 379 => function ($stackPos) { + $this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 380 => function ($stackPos) { + $this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 381 => function ($stackPos) { + $this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 382 => function ($stackPos) { + $this->semValue = new Expr\AssignRef($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 383 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 384 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 385 => function ($stackPos) { + $this->semValue = new Expr\Clone_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 386 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 387 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 388 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 389 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 390 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 391 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 392 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 393 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 394 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 395 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 396 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 397 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 398 => function ($stackPos) { + $this->semValue = new Expr\AssignOp\Coalesce($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 399 => function ($stackPos) { + $this->semValue = new Expr\PostInc($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 400 => function ($stackPos) { + $this->semValue = new Expr\PreInc($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 401 => function ($stackPos) { + $this->semValue = new Expr\PostDec($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 402 => function ($stackPos) { + $this->semValue = new Expr\PreDec($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 403 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 404 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 405 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 406 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 407 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 408 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 409 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 410 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 411 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 412 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 413 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 414 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 415 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 416 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 417 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 418 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 419 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 420 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 421 => function ($stackPos) { + $this->semValue = new Expr\UnaryPlus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 422 => function ($stackPos) { + $this->semValue = new Expr\UnaryMinus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, 423 => function ($stackPos) { - $this->semValue = new Expr\Ternary($this->semStack[$stackPos-(5-1)], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + $this->semValue = new Expr\BooleanNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, 424 => function ($stackPos) { - $this->semValue = new Expr\Ternary($this->semStack[$stackPos-(4-1)], null, $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + $this->semValue = new Expr\BitwiseNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, 425 => function ($stackPos) { - $this->semValue = new Expr\BinaryOp\Coalesce($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + $this->semValue = new Expr\BinaryOp\Identical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, 426 => function ($stackPos) { - $this->semValue = new Expr\Isset_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + $this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, 427 => function ($stackPos) { - $this->semValue = new Expr\Empty_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + $this->semValue = new Expr\BinaryOp\Equal($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, 428 => function ($stackPos) { - $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + $this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, 429 => function ($stackPos) { - $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + $this->semValue = new Expr\BinaryOp\Spaceship($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, 430 => function ($stackPos) { - $this->semValue = new Expr\Eval_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + $this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, 431 => function ($stackPos) { - $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + $this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, 432 => function ($stackPos) { - $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + $this->semValue = new Expr\BinaryOp\Greater($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, 433 => function ($stackPos) { - $this->semValue = new Expr\Cast\Int_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + $this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, 434 => function ($stackPos) { + $this->semValue = new Expr\Instanceof_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 435 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 436 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos-(5-1)], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes); + }, + 437 => function ($stackPos) { + $this->semValue = new Expr\Ternary($this->semStack[$stackPos-(4-1)], null, $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 438 => function ($stackPos) { + $this->semValue = new Expr\BinaryOp\Coalesce($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 439 => function ($stackPos) { + $this->semValue = new Expr\Isset_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 440 => function ($stackPos) { + $this->semValue = new Expr\Empty_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 441 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 442 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 443 => function ($stackPos) { + $this->semValue = new Expr\Eval_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 444 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 445 => function ($stackPos) { + $this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 446 => function ($stackPos) { + $this->semValue = new Expr\Cast\Int_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 447 => function ($stackPos) { $attrs = $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes; $attrs['kind'] = $this->getFloatCastKind($this->semStack[$stackPos-(2-1)]); $this->semValue = new Expr\Cast\Double($this->semStack[$stackPos-(2-2)], $attrs); }, - 435 => function ($stackPos) { + 448 => function ($stackPos) { $this->semValue = new Expr\Cast\String_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, - 436 => function ($stackPos) { + 449 => function ($stackPos) { $this->semValue = new Expr\Cast\Array_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, - 437 => function ($stackPos) { + 450 => function ($stackPos) { $this->semValue = new Expr\Cast\Object_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, - 438 => function ($stackPos) { + 451 => function ($stackPos) { $this->semValue = new Expr\Cast\Bool_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, - 439 => function ($stackPos) { + 452 => function ($stackPos) { $this->semValue = new Expr\Cast\Unset_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, - 440 => function ($stackPos) { + 453 => function ($stackPos) { $attrs = $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes; $attrs['kind'] = strtolower($this->semStack[$stackPos-(2-1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE; $this->semValue = new Expr\Exit_($this->semStack[$stackPos-(2-2)], $attrs); }, - 441 => function ($stackPos) { + 454 => function ($stackPos) { $this->semValue = new Expr\ErrorSuppress($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, - 442 => function ($stackPos) { + 455 => function ($stackPos) { $this->semValue = $this->semStack[$stackPos-(1-1)]; }, - 443 => function ($stackPos) { + 456 => function ($stackPos) { $this->semValue = new Expr\ShellExec($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, - 444 => function ($stackPos) { + 457 => function ($stackPos) { $this->semValue = new Expr\Print_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, - 445 => function ($stackPos) { + 458 => function ($stackPos) { $this->semValue = new Expr\Yield_(null, null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); }, - 446 => function ($stackPos) { + 459 => function ($stackPos) { $this->semValue = new Expr\Yield_($this->semStack[$stackPos-(2-2)], null, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, - 447 => function ($stackPos) { + 460 => function ($stackPos) { $this->semValue = new Expr\Yield_($this->semStack[$stackPos-(4-4)], $this->semStack[$stackPos-(4-2)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); }, - 448 => function ($stackPos) { + 461 => function ($stackPos) { $this->semValue = new Expr\YieldFrom($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, - 449 => function ($stackPos) { + 462 => function ($stackPos) { $this->semValue = new Expr\Throw_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, - 450 => function ($stackPos) { + 463 => function ($stackPos) { $this->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $this->semStack[$stackPos-(8-2)], 'params' => $this->semStack[$stackPos-(8-4)], 'returnType' => $this->semStack[$stackPos-(8-6)], 'expr' => $this->semStack[$stackPos-(8-8)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes); }, - 451 => function ($stackPos) { + 464 => function ($stackPos) { $this->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-5)], 'returnType' => $this->semStack[$stackPos-(9-7)], 'expr' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); }, - 452 => function ($stackPos) { + 465 => function ($stackPos) { $this->semValue = new Expr\Closure(['static' => false, 'byRef' => $this->semStack[$stackPos-(8-2)], 'params' => $this->semStack[$stackPos-(8-4)], 'uses' => $this->semStack[$stackPos-(8-6)], 'returnType' => $this->semStack[$stackPos-(8-7)], 'stmts' => $this->semStack[$stackPos-(8-8)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes); }, - 453 => function ($stackPos) { + 466 => function ($stackPos) { $this->semValue = new Expr\Closure(['static' => true, 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-5)], 'uses' => $this->semStack[$stackPos-(9-7)], 'returnType' => $this->semStack[$stackPos-(9-8)], 'stmts' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); }, - 454 => function ($stackPos) { + 467 => function ($stackPos) { $this->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-5)], 'returnType' => $this->semStack[$stackPos-(9-7)], 'expr' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => $this->semStack[$stackPos-(9-1)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); }, - 455 => function ($stackPos) { + 468 => function ($stackPos) { $this->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $this->semStack[$stackPos-(10-4)], 'params' => $this->semStack[$stackPos-(10-6)], 'returnType' => $this->semStack[$stackPos-(10-8)], 'expr' => $this->semStack[$stackPos-(10-10)], 'attrGroups' => $this->semStack[$stackPos-(10-1)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes); }, - 456 => function ($stackPos) { + 469 => function ($stackPos) { $this->semValue = new Expr\Closure(['static' => false, 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-5)], 'uses' => $this->semStack[$stackPos-(9-7)], 'returnType' => $this->semStack[$stackPos-(9-8)], 'stmts' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => $this->semStack[$stackPos-(9-1)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes); }, - 457 => function ($stackPos) { + 470 => function ($stackPos) { $this->semValue = new Expr\Closure(['static' => true, 'byRef' => $this->semStack[$stackPos-(10-4)], 'params' => $this->semStack[$stackPos-(10-6)], 'uses' => $this->semStack[$stackPos-(10-8)], 'returnType' => $this->semStack[$stackPos-(10-9)], 'stmts' => $this->semStack[$stackPos-(10-10)], 'attrGroups' => $this->semStack[$stackPos-(10-1)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes); }, - 458 => function ($stackPos) { + 471 => function ($stackPos) { $this->semValue = array(new Stmt\Class_(null, ['type' => 0, 'extends' => $this->semStack[$stackPos-(8-4)], 'implements' => $this->semStack[$stackPos-(8-5)], 'stmts' => $this->semStack[$stackPos-(8-7)], 'attrGroups' => $this->semStack[$stackPos-(8-1)]], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes), $this->semStack[$stackPos-(8-3)]); $this->checkClass($this->semValue[0], -1); }, - 459 => function ($stackPos) { + 472 => function ($stackPos) { $this->semValue = new Expr\New_($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, - 460 => function ($stackPos) { + 473 => function ($stackPos) { list($class, $ctorArgs) = $this->semStack[$stackPos-(2-2)]; $this->semValue = new Expr\New_($class, $ctorArgs, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, - 461 => function ($stackPos) { - $this->semValue = array(); - }, - 462 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(4-3)]; - }, - 463 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(2-1)]; - }, - 464 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(1-1)]); - }, - 465 => function ($stackPos) { - $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; - }, - 466 => function ($stackPos) { - $this->semValue = new Expr\ClosureUse($this->semStack[$stackPos-(2-2)], $this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); - }, - 467 => function ($stackPos) { - $this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); - }, - 468 => function ($stackPos) { - $this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); - }, - 469 => function ($stackPos) { - $this->semValue = new Expr\StaticCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); - }, - 470 => function ($stackPos) { - $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); - }, - 471 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; - }, - 472 => function ($stackPos) { - $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); - }, - 473 => function ($stackPos) { - $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); - }, 474 => function ($stackPos) { - $this->semValue = new Name\FullyQualified(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + $this->semValue = array(); }, 475 => function ($stackPos) { - $this->semValue = new Name\Relative(substr($this->semStack[$stackPos-(1-1)], 10), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + $this->semValue = $this->semStack[$stackPos-(4-3)]; }, 476 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; + $this->semValue = $this->semStack[$stackPos-(2-1)]; }, 477 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; + $this->semValue = array($this->semStack[$stackPos-(1-1)]); }, 478 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(3-2)]; + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; }, 479 => function ($stackPos) { - $this->semValue = new Expr\Error($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->errorState = 2; + $this->semValue = new Expr\ClosureUse($this->semStack[$stackPos-(2-2)], $this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, 480 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; + $this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, 481 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; + $this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, 482 => function ($stackPos) { - $this->semValue = null; + $this->semValue = new Expr\StaticCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); }, 483 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(3-2)]; + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); }, 484 => function ($stackPos) { - $this->semValue = array(); - }, - 485 => function ($stackPos) { - $this->semValue = array(new Scalar\EncapsedStringPart(Scalar\String_::parseEscapeSequences($this->semStack[$stackPos-(1-1)], '`'), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes)); - }, - 486 => function ($stackPos) { - foreach ($this->semStack[$stackPos-(1-1)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', true); } }; $this->semValue = $this->semStack[$stackPos-(1-1)]; - }, - 487 => function ($stackPos) { - $this->semValue = array(); - }, - 488 => function ($stackPos) { $this->semValue = $this->semStack[$stackPos-(1-1)]; }, + 485 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 486 => function ($stackPos) { + $this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 487 => function ($stackPos) { + $this->semValue = new Name\FullyQualified(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 488 => function ($stackPos) { + $this->semValue = new Name\Relative(substr($this->semStack[$stackPos-(1-1)], 10), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, 489 => function ($stackPos) { - $this->semValue = new Expr\ConstFetch($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 490 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 491 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\File($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + $this->semValue = $this->semStack[$stackPos-(3-2)]; }, 492 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Dir($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + $this->semValue = new Expr\Error($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->errorState = 2; }, 493 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Class_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 494 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Trait_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 495 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Method($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + $this->semValue = null; }, 496 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Function_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + $this->semValue = $this->semStack[$stackPos-(3-2)]; }, 497 => function ($stackPos) { - $this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + $this->semValue = array(); }, 498 => function ($stackPos) { - $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + $this->semValue = array(new Scalar\EncapsedStringPart(Scalar\String_::parseEscapeSequences($this->semStack[$stackPos-(1-1)], '`'), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes)); }, 499 => function ($stackPos) { - $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos-(3-1)], new Expr\Error($this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)]), $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->errorState = 2; + foreach ($this->semStack[$stackPos-(1-1)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', true); } }; $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 500 => function ($stackPos) { + $this->semValue = array(); + }, + 501 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 502 => function ($stackPos) { + $this->semValue = new Expr\ConstFetch($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 503 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 504 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\File($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 505 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Dir($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 506 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Class_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 507 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Trait_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 508 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Method($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 509 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Function_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 510 => function ($stackPos) { + $this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 511 => function ($stackPos) { + $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 512 => function ($stackPos) { + $this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos-(3-1)], new Expr\Error($this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)]), $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->errorState = 2; + }, + 513 => function ($stackPos) { $attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_SHORT; $this->semValue = new Expr\Array_($this->semStack[$stackPos-(3-2)], $attrs); }, - 501 => function ($stackPos) { + 514 => function ($stackPos) { $attrs = $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_LONG; $this->semValue = new Expr\Array_($this->semStack[$stackPos-(4-3)], $attrs); }, - 502 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; - }, - 503 => function ($stackPos) { - $attrs = $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$stackPos-(1-1)][0] === "'" || ($this->semStack[$stackPos-(1-1)][1] === "'" && ($this->semStack[$stackPos-(1-1)][0] === 'b' || $this->semStack[$stackPos-(1-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED); - $this->semValue = new Scalar\String_(Scalar\String_::parse($this->semStack[$stackPos-(1-1)]), $attrs); - }, - 504 => function ($stackPos) { - $attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; - foreach ($this->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', true); } }; $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos-(3-2)], $attrs); - }, - 505 => function ($stackPos) { - $this->semValue = $this->parseLNumber($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); - }, - 506 => function ($stackPos) { - $this->semValue = new Scalar\DNumber(Scalar\DNumber::parse($this->semStack[$stackPos-(1-1)]), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); - }, - 507 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; - }, - 508 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; - }, - 509 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; - }, - 510 => function ($stackPos) { - $this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true); - }, - 511 => function ($stackPos) { - $this->semValue = $this->parseDocString($this->semStack[$stackPos-(2-1)], '', $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(2-2)] + $this->endAttributeStack[$stackPos-(2-2)], true); - }, - 512 => function ($stackPos) { - $this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true); - }, - 513 => function ($stackPos) { - $this->semValue = null; - }, - 514 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; - }, 515 => function ($stackPos) { $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 516 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(3-2)]; + $this->semValue = Scalar\String_::fromString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); }, 517 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; + $attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED; + foreach ($this->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', true); } }; $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos-(3-2)], $attrs); }, 518 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; + $this->semValue = $this->parseLNumber($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); }, 519 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; + $this->semValue = Scalar\DNumber::fromString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); }, 520 => function ($stackPos) { $this->semValue = $this->semStack[$stackPos-(1-1)]; @@ -2576,31 +2588,31 @@ class Php7 extends \PhpParser\ParserAbstract $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 522 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(3-2)]; + $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 523 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; + $this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true); }, 524 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; + $this->semValue = $this->parseDocString($this->semStack[$stackPos-(2-1)], '', $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(2-2)] + $this->endAttributeStack[$stackPos-(2-2)], true); }, 525 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + $this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true); }, 526 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + $this->semValue = null; }, 527 => function ($stackPos) { $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 528 => function ($stackPos) { - $this->semValue = new Expr\MethodCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 529 => function ($stackPos) { - $this->semValue = new Expr\NullsafeMethodCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + $this->semValue = $this->semStack[$stackPos-(3-2)]; }, 530 => function ($stackPos) { - $this->semValue = null; + $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 531 => function ($stackPos) { $this->semValue = $this->semStack[$stackPos-(1-1)]; @@ -2612,165 +2624,204 @@ class Php7 extends \PhpParser\ParserAbstract $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 534 => function ($stackPos) { - $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 535 => function ($stackPos) { - $this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + $this->semValue = $this->semStack[$stackPos-(3-2)]; }, 536 => function ($stackPos) { $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 537 => function ($stackPos) { - $this->semValue = new Expr\Variable($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); - }, - 538 => function ($stackPos) { - $this->semValue = new Expr\Variable($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); - }, - 539 => function ($stackPos) { - $this->semValue = new Expr\Variable(new Expr\Error($this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes), $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); $this->errorState = 2; - }, - 540 => function ($stackPos) { - $var = $this->semStack[$stackPos-(1-1)]->name; $this->semValue = \is_string($var) ? new Node\VarLikeIdentifier($var, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes) : $var; - }, - 541 => function ($stackPos) { - $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 542 => function ($stackPos) { $this->semValue = $this->semStack[$stackPos-(1-1)]; }, - 543 => function ($stackPos) { + 538 => function ($stackPos) { $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); }, + 539 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 540 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 541 => function ($stackPos) { + $this->semValue = new Expr\MethodCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 542 => function ($stackPos) { + $this->semValue = new Expr\NullsafeMethodCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 543 => function ($stackPos) { + $this->semValue = null; + }, 544 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 545 => function ($stackPos) { - $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 546 => function ($stackPos) { - $this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 547 => function ($stackPos) { - $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, 548 => function ($stackPos) { - $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + $this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, 549 => function ($stackPos) { $this->semValue = $this->semStack[$stackPos-(1-1)]; }, 550 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(3-2)]; + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); }, 551 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); }, 552 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; + $this->semValue = new Expr\Variable(new Expr\Error($this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes), $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); $this->errorState = 2; }, 553 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(3-2)]; + $var = $this->semStack[$stackPos-(1-1)]->name; $this->semValue = \is_string($var) ? new Node\VarLikeIdentifier($var, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes) : $var; }, 554 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, 555 => function ($stackPos) { - $this->semValue = new Expr\Error($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->errorState = 2; - }, - 556 => function ($stackPos) { - $this->semValue = new Expr\List_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); - }, - 557 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos-(1-1)]; $end = count($this->semValue)-1; if ($this->semValue[$end] === null) array_pop($this->semValue); - }, - 558 => function ($stackPos) { - $this->semValue = $this->semStack[$stackPos]; - }, - 559 => function ($stackPos) { - /* do nothing -- prevent default action of $$=$this->semStack[$1]. See $551. */ - }, - 560 => function ($stackPos) { - $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; - }, - 561 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(1-1)]); - }, - 562 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); - }, - 563 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); - }, - 564 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); - }, - 565 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 566 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(4-4)], $this->semStack[$stackPos-(4-1)], true, $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); - }, - 567 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); - }, - 568 => function ($stackPos) { - $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); - }, - 569 => function ($stackPos) { - $this->semValue = null; - }, - 570 => function ($stackPos) { - $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; - }, - 571 => function ($stackPos) { - $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; - }, - 572 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(1-1)]); - }, - 573 => function ($stackPos) { - $this->semValue = array($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]); - }, - 574 => function ($stackPos) { - $this->semValue = new Scalar\EncapsedStringPart($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); - }, - 575 => function ($stackPos) { - $this->semValue = new Expr\Variable($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); - }, - 576 => function ($stackPos) { $this->semValue = $this->semStack[$stackPos-(1-1)]; }, - 577 => function ($stackPos) { + 556 => function ($stackPos) { $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); }, - 578 => function ($stackPos) { + 557 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 558 => function ($stackPos) { $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, - 579 => function ($stackPos) { + 559 => function ($stackPos) { $this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, - 580 => function ($stackPos) { - $this->semValue = new Expr\Variable($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + 560 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, - 581 => function ($stackPos) { - $this->semValue = new Expr\Variable($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + 561 => function ($stackPos) { + $this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); }, - 582 => function ($stackPos) { - $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-4)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + 562 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; }, - 583 => function ($stackPos) { + 563 => function ($stackPos) { $this->semValue = $this->semStack[$stackPos-(3-2)]; }, + 564 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 565 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 566 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 567 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 568 => function ($stackPos) { + $this->semValue = new Expr\Error($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->errorState = 2; + }, + 569 => function ($stackPos) { + $this->semValue = new Expr\List_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 570 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; $end = count($this->semValue)-1; if ($this->semValue[$end] === null) array_pop($this->semValue); + }, + 571 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos]; + }, + 572 => function ($stackPos) { + /* do nothing -- prevent default action of $$=$this->semStack[$1]. See $551. */ + }, + 573 => function ($stackPos) { + $this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)]; + }, + 574 => function ($stackPos) { + $this->semValue = array($this->semStack[$stackPos-(1-1)]); + }, + 575 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 576 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 577 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 578 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 579 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(4-4)], $this->semStack[$stackPos-(4-1)], true, $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 580 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 581 => function ($stackPos) { + $this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 582 => function ($stackPos) { + $this->semValue = null; + }, + 583 => function ($stackPos) { + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; + }, 584 => function ($stackPos) { - $this->semValue = new Scalar\String_($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; }, 585 => function ($stackPos) { - $this->semValue = $this->parseNumString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + $this->semValue = array($this->semStack[$stackPos-(1-1)]); }, 586 => function ($stackPos) { - $this->semValue = $this->parseNumString('-' . $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + $this->semValue = array($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]); }, 587 => function ($stackPos) { + $this->semValue = new Scalar\EncapsedStringPart($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 588 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 589 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(1-1)]; + }, + 590 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); + }, + 591 => function ($stackPos) { + $this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 592 => function ($stackPos) { + $this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 593 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 594 => function ($stackPos) { + $this->semValue = new Expr\Variable($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); + }, + 595 => function ($stackPos) { + $this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-4)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); + }, + 596 => function ($stackPos) { + $this->semValue = $this->semStack[$stackPos-(3-2)]; + }, + 597 => function ($stackPos) { + $this->semValue = new Scalar\String_($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 598 => function ($stackPos) { + $this->semValue = $this->parseNumString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); + }, + 599 => function ($stackPos) { + $this->semValue = $this->parseNumString('-' . $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); + }, + 600 => function ($stackPos) { $this->semValue = $this->semStack[$stackPos-(1-1)]; }, ]; diff --git a/lib/nikic/php-parser/lib/PhpParser/ParserAbstract.php b/lib/nikic/php-parser/lib/PhpParser/ParserAbstract.php index 5ee5a64bbd..d485d78de4 100644 --- a/lib/nikic/php-parser/lib/PhpParser/ParserAbstract.php +++ b/lib/nikic/php-parser/lib/PhpParser/ParserAbstract.php @@ -875,6 +875,15 @@ abstract class ParserAbstract implements Parser return $attributes; } + protected function checkClassModifier($a, $b, $modifierPos) { + try { + Class_::verifyClassModifier($a, $b); + } catch (Error $error) { + $error->setAttributes($this->getAttributesAt($modifierPos)); + $this->emitError($error); + } + } + protected function checkModifier($a, $b, $modifierPos) { // Jumping through some hoops here because verifyModifier() is also used elsewhere try { diff --git a/lib/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php b/lib/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php index 62d1f34c10..bb70de6595 100644 --- a/lib/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php +++ b/lib/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php @@ -33,6 +33,10 @@ class Standard extends PrettyPrinterAbstract . $this->p($node->value); } + protected function pVariadicPlaceholder(Node\VariadicPlaceholder $node) { + return '...'; + } + protected function pConst(Node\Const_ $node) { return $node->name . ' = ' . $this->p($node->value); } @@ -45,6 +49,10 @@ class Standard extends PrettyPrinterAbstract return $this->pImplode($node->types, '|'); } + protected function pIntersectionType(Node\IntersectionType $node) { + return $this->pImplode($node->types, '&'); + } + protected function pIdentifier(Node\Identifier $node) { return $node->name; } diff --git a/lib/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php b/lib/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php index 8a3565f1cf..2c7fc3070c 100644 --- a/lib/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php +++ b/lib/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.php @@ -824,7 +824,11 @@ abstract class PrettyPrinterAbstract return null; } - if ($insertStr === ', ' && $this->isMultiline($origNodes)) { + // We go multiline if the original code was multiline, + // or if it's an array item with a comment above it. + if ($insertStr === ', ' && + ($this->isMultiline($origNodes) || $arrItem->getComments()) + ) { $insertStr = ','; $insertNewline = true; } @@ -842,11 +846,11 @@ abstract class PrettyPrinterAbstract $this->setIndentLevel($lastElemIndentLevel); if ($insertNewline) { + $result .= $insertStr . $this->nl; $comments = $arrItem->getComments(); if ($comments) { - $result .= $this->nl . $this->pComments($comments); + $result .= $this->pComments($comments) . $this->nl; } - $result .= $insertStr . $this->nl; } else { $result .= $insertStr; } @@ -1343,6 +1347,7 @@ abstract class PrettyPrinterAbstract //'Scalar_Encapsed->parts' => '', 'Stmt_Catch->types' => '|', 'UnionType->types' => '|', + 'IntersectionType->types' => '&', 'Stmt_If->elseifs' => ' ', 'Stmt_TryCatch->catches' => ' ', diff --git a/lib/paragonie/random_compat/build-phar.sh b/lib/paragonie/random_compat/build-phar.sh new file mode 100644 index 0000000000..b4a5ba31cc --- /dev/null +++ b/lib/paragonie/random_compat/build-phar.sh @@ -0,0 +1,5 @@ +#!/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/lib/paragonie/random_compat/composer.json b/lib/paragonie/random_compat/composer.json index 34f1381d57..f2b9c4e510 100644 --- a/lib/paragonie/random_compat/composer.json +++ b/lib/paragonie/random_compat/composer.json @@ -22,17 +22,13 @@ "source": "https://github.com/paragonie/random_compat" }, "require": { - "php": ">=5.2.0" + "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." - }, - "autoload": { - "files": [ - "lib/random.php" - ] } } diff --git a/lib/paragonie/random_compat/lib/byte_safe_strings.php b/lib/paragonie/random_compat/lib/byte_safe_strings.php deleted file mode 100644 index ef24488f9e..0000000000 --- a/lib/paragonie/random_compat/lib/byte_safe_strings.php +++ /dev/null @@ -1,195 +0,0 @@ - RandomCompat_strlen($binary_string)) { - return ''; - } - - return (string) mb_substr( - (string) $binary_string, - (int) $start, - (int) $length, - '8bit' - ); - } - - } else { - - /** - * substr() implementation that isn't brittle to mbstring.func_overload - * - * This version just uses the default substr() - * - * @param string $binary_string - * @param int $start - * @param int|null $length (optional) - * - * @throws TypeError - * - * @return string - */ - function RandomCompat_substr($binary_string, $start, $length = null) - { - if (!is_string($binary_string)) { - throw new TypeError( - 'RandomCompat_substr(): First argument should be a string' - ); - } - - if (!is_int($start)) { - throw new TypeError( - 'RandomCompat_substr(): Second argument should be an integer' - ); - } - - if ($length !== null) { - if (!is_int($length)) { - throw new TypeError( - 'RandomCompat_substr(): Third argument should be an integer, or omitted' - ); - } - - return (string) substr( - (string )$binary_string, - (int) $start, - (int) $length - ); - } - - return (string) substr( - (string) $binary_string, - (int) $start - ); - } - } -} diff --git a/lib/paragonie/random_compat/lib/cast_to_int.php b/lib/paragonie/random_compat/lib/cast_to_int.php deleted file mode 100644 index 1b1bbfe8de..0000000000 --- a/lib/paragonie/random_compat/lib/cast_to_int.php +++ /dev/null @@ -1,77 +0,0 @@ - operators might accidentally let a float - * through. - * - * @param int|float $number The number we want to convert to an int - * @param bool $fail_open Set to true to not throw an exception - * - * @return float|int - * @psalm-suppress InvalidReturnType - * - * @throws TypeError - */ - function RandomCompat_intval($number, $fail_open = false) - { - if (is_int($number) || is_float($number)) { - $number += 0; - } elseif (is_numeric($number)) { - /** @psalm-suppress InvalidOperand */ - $number += 0; - } - /** @var int|float $number */ - - if ( - is_float($number) - && - $number > ~PHP_INT_MAX - && - $number < PHP_INT_MAX - ) { - $number = (int) $number; - } - - if (is_int($number)) { - return (int) $number; - } elseif (!$fail_open) { - throw new TypeError( - 'Expected an integer.' - ); - } - return $number; - } -} diff --git a/lib/paragonie/random_compat/lib/error_polyfill.php b/lib/paragonie/random_compat/lib/error_polyfill.php deleted file mode 100644 index c02c5c8b4c..0000000000 --- a/lib/paragonie/random_compat/lib/error_polyfill.php +++ /dev/null @@ -1,49 +0,0 @@ -= 70000) { - return; -} - -if (!defined('RANDOM_COMPAT_READ_BUFFER')) { - define('RANDOM_COMPAT_READ_BUFFER', 8); -} - -$RandomCompatDIR = dirname(__FILE__); - -require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'byte_safe_strings.php'; -require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'cast_to_int.php'; -require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'error_polyfill.php'; - -if (!is_callable('random_bytes')) { - /** - * PHP 5.2.0 - 5.6.x way to implement random_bytes() - * - * We use conditional statements here to define the function in accordance - * to the operating environment. It's a micro-optimization. - * - * In order of preference: - * 1. Use libsodium if available. - * 2. fread() /dev/urandom if available (never on Windows) - * 3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM) - * 4. COM('CAPICOM.Utilities.1')->GetRandom() - * - * See RATIONALE.md for our reasoning behind this particular order - */ - if (extension_loaded('libsodium')) { - // See random_bytes_libsodium.php - if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) { - require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_libsodium.php'; - } elseif (method_exists('Sodium', 'randombytes_buf')) { - require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_libsodium_legacy.php'; - } - } - - /** - * Reading directly from /dev/urandom: - */ - if (DIRECTORY_SEPARATOR === '/') { - // DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast - // way to exclude Windows. - $RandomCompatUrandom = true; - $RandomCompat_basedir = ini_get('open_basedir'); - - if (!empty($RandomCompat_basedir)) { - $RandomCompat_open_basedir = explode( - PATH_SEPARATOR, - strtolower($RandomCompat_basedir) - ); - $RandomCompatUrandom = (array() !== array_intersect( - array('/dev', '/dev/', '/dev/urandom'), - $RandomCompat_open_basedir - )); - $RandomCompat_open_basedir = null; - } - - if ( - !is_callable('random_bytes') - && - $RandomCompatUrandom - && - @is_readable('/dev/urandom') - ) { - // Error suppression on is_readable() in case of an open_basedir - // or safe_mode failure. All we care about is whether or not we - // can read it at this point. If the PHP environment is going to - // panic over trying to see if the file can be read in the first - // place, that is not helpful to us here. - - // See random_bytes_dev_urandom.php - require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_dev_urandom.php'; - } - // Unset variables after use - $RandomCompat_basedir = null; - } else { - $RandomCompatUrandom = false; - } - - /** - * mcrypt_create_iv() - * - * We only want to use mcypt_create_iv() if: - * - * - random_bytes() hasn't already been defined - * - the mcrypt extensions is loaded - * - One of these two conditions is true: - * - We're on Windows (DIRECTORY_SEPARATOR !== '/') - * - We're not on Windows and /dev/urandom is readabale - * (i.e. we're not in a chroot jail) - * - Special case: - * - If we're not on Windows, but the PHP version is between - * 5.6.10 and 5.6.12, we don't want to use mcrypt. It will - * hang indefinitely. This is bad. - * - If we're on Windows, we want to use PHP >= 5.3.7 or else - * we get insufficient entropy errors. - */ - if ( - !is_callable('random_bytes') - && - // Windows on PHP < 5.3.7 is broken, but non-Windows is not known to be. - (DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307) - && - // Prevent this code from hanging indefinitely on non-Windows; - // see https://bugs.php.net/bug.php?id=69833 - ( - DIRECTORY_SEPARATOR !== '/' || - (PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613) - ) - && - extension_loaded('mcrypt') - ) { - // See random_bytes_mcrypt.php - require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_mcrypt.php'; - } - $RandomCompatUrandom = null; - - /** - * This is a Windows-specific fallback, for when the mcrypt extension - * isn't loaded. - */ - if ( - !is_callable('random_bytes') - && - extension_loaded('com_dotnet') - && - class_exists('COM') - ) { - $RandomCompat_disabled_classes = preg_split( - '#\s*,\s*#', - strtolower(ini_get('disable_classes')) - ); - - if (!in_array('com', $RandomCompat_disabled_classes)) { - try { - $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1'); - if (method_exists($RandomCompatCOMtest, 'GetRandom')) { - // See random_bytes_com_dotnet.php - require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_com_dotnet.php'; - } - } catch (com_exception $e) { - // Don't try to use it. - } - } - $RandomCompat_disabled_classes = null; - $RandomCompatCOMtest = null; - } - - /** - * throw new Exception - */ - if (!is_callable('random_bytes')) { - /** - * We don't have any more options, so let's throw an exception right now - * and hope the developer won't let it fail silently. - * - * @param mixed $length - * @psalm-suppress InvalidReturnType - * @throws Exception - * @return string - */ - function random_bytes($length) - { - unset($length); // Suppress "variable not used" warnings. - throw new Exception( - 'There is no suitable CSPRNG installed on your system' - ); - return ''; - } - } -} - -if (!is_callable('random_int')) { - require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_int.php'; -} - -$RandomCompatDIR = null; +// NOP diff --git a/lib/paragonie/random_compat/lib/random_bytes_com_dotnet.php b/lib/paragonie/random_compat/lib/random_bytes_com_dotnet.php deleted file mode 100644 index 537d02b27a..0000000000 --- a/lib/paragonie/random_compat/lib/random_bytes_com_dotnet.php +++ /dev/null @@ -1,91 +0,0 @@ -GetRandom($bytes, 0)); - if (RandomCompat_strlen($buf) >= $bytes) { - /** - * Return our random entropy buffer here: - */ - return (string) RandomCompat_substr($buf, 0, $bytes); - } - ++$execCount; - } while ($execCount < $bytes); - - /** - * If we reach here, PHP has failed us. - */ - throw new Exception( - 'Could not gather sufficient random data' - ); - } -} diff --git a/lib/paragonie/random_compat/lib/random_bytes_dev_urandom.php b/lib/paragonie/random_compat/lib/random_bytes_dev_urandom.php deleted file mode 100644 index c4e31ccbbb..0000000000 --- a/lib/paragonie/random_compat/lib/random_bytes_dev_urandom.php +++ /dev/null @@ -1,190 +0,0 @@ - $st */ - $st = fstat($fp); - if (($st['mode'] & 0170000) !== 020000) { - fclose($fp); - $fp = false; - } - } - } - - if (is_resource($fp)) { - /** - * stream_set_read_buffer() does not exist in HHVM - * - * If we don't set the stream's read buffer to 0, PHP will - * internally buffer 8192 bytes, which can waste entropy - * - * stream_set_read_buffer returns 0 on success - */ - if (is_callable('stream_set_read_buffer')) { - stream_set_read_buffer($fp, RANDOM_COMPAT_READ_BUFFER); - } - if (is_callable('stream_set_chunk_size')) { - stream_set_chunk_size($fp, RANDOM_COMPAT_READ_BUFFER); - } - } - } - - try { - /** @var int $bytes */ - $bytes = RandomCompat_intval($bytes); - } catch (TypeError $ex) { - throw new TypeError( - 'random_bytes(): $bytes must be an integer' - ); - } - - if ($bytes < 1) { - throw new Error( - 'Length must be greater than 0' - ); - } - - /** - * This if() block only runs if we managed to open a file handle - * - * It does not belong in an else {} block, because the above - * if (empty($fp)) line is logic that should only be run once per - * page load. - */ - if (is_resource($fp)) { - /** - * @var int - */ - $remaining = $bytes; - - /** - * @var string|bool - */ - $buf = ''; - - /** - * We use fread() in a loop to protect against partial reads - */ - do { - /** - * @var string|bool - */ - $read = fread($fp, $remaining); - if (!is_string($read)) { - /** - * We cannot safely read from the file. Exit the - * do-while loop and trigger the exception condition - * - * @var string|bool - */ - $buf = false; - break; - } - /** - * Decrease the number of bytes returned from remaining - */ - $remaining -= RandomCompat_strlen($read); - /** - * @var string $buf - */ - $buf .= $read; - } while ($remaining > 0); - - /** - * Is our result valid? - * @var string|bool $buf - */ - if (is_string($buf)) { - if (RandomCompat_strlen($buf) === $bytes) { - /** - * Return our random entropy buffer here: - */ - return $buf; - } - } - } - - /** - * If we reach here, PHP has failed us. - */ - throw new Exception( - 'Error reading from source device' - ); - } -} diff --git a/lib/paragonie/random_compat/lib/random_bytes_libsodium.php b/lib/paragonie/random_compat/lib/random_bytes_libsodium.php deleted file mode 100644 index 2e56290182..0000000000 --- a/lib/paragonie/random_compat/lib/random_bytes_libsodium.php +++ /dev/null @@ -1,91 +0,0 @@ - 2147483647) { - $buf = ''; - for ($i = 0; $i < $bytes; $i += 1073741824) { - $n = ($bytes - $i) > 1073741824 - ? 1073741824 - : $bytes - $i; - $buf .= \Sodium\randombytes_buf($n); - } - } else { - /** @var string|bool $buf */ - $buf = \Sodium\randombytes_buf($bytes); - } - - if (is_string($buf)) { - if (RandomCompat_strlen($buf) === $bytes) { - return $buf; - } - } - - /** - * If we reach here, PHP has failed us. - */ - throw new Exception( - 'Could not gather sufficient random data' - ); - } -} diff --git a/lib/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php b/lib/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php deleted file mode 100644 index f78b2199a2..0000000000 --- a/lib/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php +++ /dev/null @@ -1,93 +0,0 @@ - 2147483647) { - for ($i = 0; $i < $bytes; $i += 1073741824) { - $n = ($bytes - $i) > 1073741824 - ? 1073741824 - : $bytes - $i; - $buf .= Sodium::randombytes_buf((int) $n); - } - } else { - $buf .= Sodium::randombytes_buf((int) $bytes); - } - - if (is_string($buf)) { - if (RandomCompat_strlen($buf) === $bytes) { - return $buf; - } - } - - /** - * If we reach here, PHP has failed us. - */ - throw new Exception( - 'Could not gather sufficient random data' - ); - } -} diff --git a/lib/paragonie/random_compat/lib/random_bytes_mcrypt.php b/lib/paragonie/random_compat/lib/random_bytes_mcrypt.php deleted file mode 100644 index 0b13fa73c5..0000000000 --- a/lib/paragonie/random_compat/lib/random_bytes_mcrypt.php +++ /dev/null @@ -1,79 +0,0 @@ - operators might accidentally let a float - * through. - */ - - try { - /** @var int $min */ - $min = RandomCompat_intval($min); - } catch (TypeError $ex) { - throw new TypeError( - 'random_int(): $min must be an integer' - ); - } - - try { - /** @var int $max */ - $max = RandomCompat_intval($max); - } catch (TypeError $ex) { - throw new TypeError( - 'random_int(): $max must be an integer' - ); - } - - /** - * Now that we've verified our weak typing system has given us an integer, - * let's validate the logic then we can move forward with generating random - * integers along a given range. - */ - if ($min > $max) { - throw new Error( - 'Minimum value must be less than or equal to the maximum value' - ); - } - - if ($max === $min) { - return (int) $min; - } - - /** - * Initialize variables to 0 - * - * We want to store: - * $bytes => the number of random bytes we need - * $mask => an integer bitmask (for use with the &) operator - * so we can minimize the number of discards - */ - $attempts = $bits = $bytes = $mask = $valueShift = 0; - /** @var int $attempts */ - /** @var int $bits */ - /** @var int $bytes */ - /** @var int $mask */ - /** @var int $valueShift */ - - /** - * At this point, $range is a positive number greater than 0. It might - * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to - * a float and we will lose some precision. - * - * @var int|float $range - */ - $range = $max - $min; - - /** - * Test for integer overflow: - */ - if (!is_int($range)) { - - /** - * Still safely calculate wider ranges. - * Provided by @CodesInChaos, @oittaa - * - * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435 - * - * We use ~0 as a mask in this case because it generates all 1s - * - * @ref https://eval.in/400356 (32-bit) - * @ref http://3v4l.org/XX9r5 (64-bit) - */ - $bytes = PHP_INT_SIZE; - /** @var int $mask */ - $mask = ~0; - - } else { - - /** - * $bits is effectively ceil(log($range, 2)) without dealing with - * type juggling - */ - while ($range > 0) { - if ($bits % 8 === 0) { - ++$bytes; - } - ++$bits; - $range >>= 1; - /** @var int $mask */ - $mask = $mask << 1 | 1; - } - $valueShift = $min; - } - - /** @var int $val */ - $val = 0; - /** - * Now that we have our parameters set up, let's begin generating - * random integers until one falls between $min and $max - */ - /** @psalm-suppress RedundantCondition */ - do { - /** - * The rejection probability is at most 0.5, so this corresponds - * to a failure probability of 2^-128 for a working RNG - */ - if ($attempts > 128) { - throw new Exception( - 'random_int: RNG is broken - too many rejections' - ); - } - - /** - * Let's grab the necessary number of random bytes - */ - $randomByteString = random_bytes($bytes); - - /** - * Let's turn $randomByteString into an integer - * - * This uses bitwise operators (<< and |) to build an integer - * out of the values extracted from ord() - * - * Example: [9F] | [6D] | [32] | [0C] => - * 159 + 27904 + 3276800 + 201326592 => - * 204631455 - */ - $val &= 0; - for ($i = 0; $i < $bytes; ++$i) { - $val |= ord($randomByteString[$i]) << ($i * 8); - } - /** @var int $val */ - - /** - * Apply mask - */ - $val &= $mask; - $val += $valueShift; - - ++$attempts; - /** - * If $val overflows to a floating point number, - * ... or is larger than $max, - * ... or smaller than $min, - * then try again. - */ - } while (!is_int($val) || $val > $max || $val < $min); - - return (int) $val; - } -} diff --git a/lib/paragonie/random_compat/other/build_phar.php b/lib/paragonie/random_compat/other/build_phar.php new file mode 100644 index 0000000000..70ef4b2ed8 --- /dev/null +++ b/lib/paragonie/random_compat/other/build_phar.php @@ -0,0 +1,57 @@ +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/lib/paragonie/random_compat/psalm-autoload.php b/lib/paragonie/random_compat/psalm-autoload.php new file mode 100644 index 0000000000..d71d1b818c --- /dev/null +++ b/lib/paragonie/random_compat/psalm-autoload.php @@ -0,0 +1,9 @@ + + + + + + + + + + + + + + + diff --git a/lib/pear/pear-core-minimal/src/OS/Guess.php b/lib/pear/pear-core-minimal/src/OS/Guess.php index d5aa295c3b..88cd659102 100644 --- a/lib/pear/pear-core-minimal/src/OS/Guess.php +++ b/lib/pear/pear-core-minimal/src/OS/Guess.php @@ -4,14 +4,14 @@ * * PHP versions 4 and 5 * - * @category pear - * @package PEAR - * @author Stig Bakken - * @author Gregory Beaver - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @link http://pear.php.net/package/PEAR - * @since File available since PEAR 0.1 + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Gregory Beaver + * @copyright 1997-2009 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @link http://pear.php.net/package/PEAR + * @since File available since PEAR 0.1 */ // {{{ uname examples @@ -80,15 +80,15 @@ * * This class uses php_uname() to grok information about the current OS * - * @category pear - * @package PEAR - * @author Stig Bakken - * @author Gregory Beaver - * @copyright 1997-2009 The Authors - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: @package_version@ - * @link http://pear.php.net/package/PEAR - * @since Class available since Release 0.1 + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Gregory Beaver + * @copyright 1997-2020 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @since Class available since Release 0.1 */ class OS_Guess { @@ -138,13 +138,9 @@ class OS_Guess $release = "$parts[3].$parts[2]"; break; case 'Windows' : - switch ($parts[1]) { - case '95/98': - $release = '9x'; - break; - default: - $release = $parts[1]; - break; + $release = $parts[1]; + if ($release == '95/98') { + $release = '9x'; } $cpu = 'i386'; break; @@ -157,18 +153,10 @@ class OS_Guess $sysname = 'darwin'; $nodename = $parts[2]; $release = $parts[3]; - if ($cpu == 'Macintosh') { - if ($parts[$n - 2] == 'Power') { - $cpu = 'powerpc'; - } - } + $cpu = $this->_determineIfPowerpc($cpu, $parts); break; case 'Darwin' : - if ($cpu == 'Macintosh') { - if ($parts[$n - 2] == 'Power') { - $cpu = 'powerpc'; - } - } + $cpu = $this->_determineIfPowerpc($cpu, $parts); $release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]); break; default: @@ -187,6 +175,15 @@ class OS_Guess return array($sysname, $release, $cpu, $extra, $nodename); } + function _determineIfPowerpc($cpu, $parts) + { + $n = count($parts); + if ($cpu == 'Macintosh' && $parts[$n - 2] == 'Power') { + $cpu = 'powerpc'; + } + return $cpu; + } + function _detectGlibcVersion() { static $glibc = false; @@ -196,74 +193,29 @@ class OS_Guess $major = $minor = 0; include_once "System.php"; - if (@is_link('/lib64/libc.so.6')) { - // Let's try reading the libc.so.6 symlink - if (preg_match('/^libc-(.*)\.so$/', basename(readlink('/lib64/libc.so.6')), $matches)) { - list($major, $minor) = explode('.', $matches[1]); - } - } else if (@is_link('/lib/libc.so.6')) { - // Let's try reading the libc.so.6 symlink - if (preg_match('/^libc-(.*)\.so$/', basename(readlink('/lib/libc.so.6')), $matches)) { - list($major, $minor) = explode('.', $matches[1]); + // Let's try reading possible libc.so.6 symlinks + $libcs = array( + '/lib64/libc.so.6', + '/lib/libc.so.6', + '/lib/i386-linux-gnu/libc.so.6' + ); + $versions = array(); + foreach ($libcs as $file) { + $versions = $this->_readGlibCVersionFromSymlink($file); + if ($versions != []) { + list($major, $minor) = $versions; + break; } } + // Use glibc's header file to // get major and minor version number: - if (!($major && $minor) && - @file_exists('/usr/include/features.h') && - @is_readable('/usr/include/features.h')) { - if (!@file_exists('/usr/bin/cpp') || !@is_executable('/usr/bin/cpp')) { - $features_file = fopen('/usr/include/features.h', 'rb'); - while (!feof($features_file)) { - $line = fgets($features_file, 8192); - if (!$line || (strpos($line, '#define') === false)) { - continue; - } - if (strpos($line, '__GLIBC__')) { - // major version number #define __GLIBC__ version - $line = preg_split('/\s+/', $line); - $glibc_major = trim($line[2]); - if (isset($glibc_minor)) { - break; - } - continue; - } - - if (strpos($line, '__GLIBC_MINOR__')) { - // got the minor version number - // #define __GLIBC_MINOR__ version - $line = preg_split('/\s+/', $line); - $glibc_minor = trim($line[2]); - if (isset($glibc_major)) { - break; - } - continue; - } - } - fclose($features_file); - if (!isset($glibc_major) || !isset($glibc_minor)) { - return $glibc = ''; - } - return $glibc = 'glibc' . trim($glibc_major) . "." . trim($glibc_minor) ; - } // no cpp - - $tmpfile = System::mktemp("glibctest"); - $fp = fopen($tmpfile, "w"); - fwrite($fp, "#include \n__GLIBC__ __GLIBC_MINOR__\n"); - fclose($fp); - $cpp = popen("/usr/bin/cpp $tmpfile", "r"); - while ($line = fgets($cpp, 1024)) { - if ($line[0] == '#' || trim($line) == '') { - continue; - } - - if (list($major, $minor) = explode(' ', trim($line))) { - break; - } - } - pclose($cpp); - unlink($tmpfile); - } // features.h + if (!($major && $minor)) { + $versions = $this->_readGlibCVersionFromFeaturesHeaderFile(); + } + if (is_array($versions) && $versions != []) { + list($major, $minor) = $versions; + } if (!($major && $minor)) { return $glibc = ''; @@ -272,6 +224,102 @@ class OS_Guess return $glibc = "glibc{$major}.{$minor}"; } + function _readGlibCVersionFromSymlink($file) + { + $versions = array(); + if (@is_link($file) + && (preg_match('/^libc-(.*)\.so$/', basename(readlink($file)), $matches)) + ) { + $versions = explode('.', $matches[1]); + } + return $versions; + } + + + function _readGlibCVersionFromFeaturesHeaderFile() + { + $features_header_file = '/usr/include/features.h'; + if (!(@file_exists($features_header_file) + && @is_readable($features_header_file)) + ) { + return array(); + } + if (!@file_exists('/usr/bin/cpp') || !@is_executable('/usr/bin/cpp')) { + return $this-_parseFeaturesHeaderFile($features_header_file); + } // no cpp + + return $this->_fromGlibCTest(); + } + + function _parseFeaturesHeaderFile($features_header_file) + { + $features_file = fopen($features_header_file, 'rb'); + while (!feof($features_file)) { + $line = fgets($features_file, 8192); + if (!$this->_IsADefinition($line)) { + continue; + } + if (strpos($line, '__GLIBC__')) { + // major version number #define __GLIBC__ version + $line = preg_split('/\s+/', $line); + $glibc_major = trim($line[2]); + if (isset($glibc_minor)) { + break; + } + continue; + } + + if (strpos($line, '__GLIBC_MINOR__')) { + // got the minor version number + // #define __GLIBC_MINOR__ version + $line = preg_split('/\s+/', $line); + $glibc_minor = trim($line[2]); + if (isset($glibc_major)) { + break; + } + } + } + fclose($features_file); + if (!isset($glibc_major) || !isset($glibc_minor)) { + return array(); + } + return array(trim($glibc_major), trim($glibc_minor)); + } + + function _IsADefinition($line) + { + if ($line === false) { + return false; + } + return strpos(trim($line), '#define') !== false; + } + + function _fromGlibCTest() + { + $major = null; + $minor = null; + + $tmpfile = System::mktemp("glibctest"); + $fp = fopen($tmpfile, "w"); + fwrite($fp, "#include \n__GLIBC__ __GLIBC_MINOR__\n"); + fclose($fp); + $cpp = popen("/usr/bin/cpp $tmpfile", "r"); + while ($line = fgets($cpp, 1024)) { + if ($line[0] == '#' || trim($line) == '') { + continue; + } + + if (list($major, $minor) = explode(' ', trim($line))) { + break; + } + } + pclose($cpp); + unlink($tmpfile); + if ($major !== null && $minor !== null) { + return [$major, $minor]; + } + } + function getSignature() { if (empty($this->extra)) { @@ -328,12 +376,16 @@ class OS_Guess function _matchFragment($fragment, $value) { if (strcspn($fragment, '*?') < strlen($fragment)) { - $reg = '/^' . str_replace(array('*', '?', '/'), array('.*', '.', '\\/'), $fragment) . '\\z/'; + $expression = str_replace( + array('*', '?', '/'), + array('.*', '.', '\\/'), + $fragment + ); + $reg = '/^' . $expression . '\\z/'; return preg_match($reg, $value); } return ($fragment == '*' || !strcasecmp($fragment, $value)); } - } /* * Local Variables: diff --git a/lib/pear/pear-core-minimal/src/System.php b/lib/pear/pear-core-minimal/src/System.php index cf8f379935..a7ef465984 100644 --- a/lib/pear/pear-core-minimal/src/System.php +++ b/lib/pear/pear-core-minimal/src/System.php @@ -530,7 +530,9 @@ class System // It's possible to run a .bat on Windows that is_executable // would return false for. The is_executable check is meaningless... if (OS_WINDOWS) { - return $file; + if (file_exists($file)) { + return $file; + } } else { if (is_executable($file)) { return $file; diff --git a/lib/pelago/emogrifier/CHANGELOG.md b/lib/pelago/emogrifier/CHANGELOG.md index 6ee477312a..8e44e6e90c 100644 --- a/lib/pelago/emogrifier/CHANGELOG.md +++ b/lib/pelago/emogrifier/CHANGELOG.md @@ -10,266 +10,255 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Changed ### Deprecated +- Support for PHP 7.3 will be removed in Emogrifier 8.0. ### Removed ### Fixed +## 6.0.0 + +### Added +- Test with Symfony 6-dev (#1109) +- Add support for PHP 8.1 (#1103) +- Add a dedicated class for caching (#1097) +- Allow installation together with Symfony 6 (#1065) +- Support more file types in the `.editorconfig` (#1035) +- Set `align` attribute of `` elements with `CssToAttributeConverter` (#1008) + +### Changed +- Use `sabberworm/php-css-parser` to parse the CSS (#1015) +- Also check the unit test code with Psalm (#1003) + +### Deprecated +- Support for PHP 7.2 will be removed in Emogrifier 7.0. + +### Removed +- Remove a redundant CSS data cache (#1018) +- Drop support for Symfony 5.1 and 5.2 (#972, #1104) +- Drop support for PHP 7.1 (#967) + +### Fixed +- Allow `@import` after ignored invalid `@charset` (@1081) +- Allow line feeds within `` tag (#987) + +## 5.0.1 + +### Changed +- Switch the default branch from `master` to `main` (#951) + +### Fixed +- Ignore `http-equiv` `Content-Type` in `` (#961) +- Allow "Content-Type" in content (#959) + +## 5.0.0 + +### Added +- Add an `.editorconfig` file (#940) +- Support PHP 8.0 (#926) +- Run the CI build once a week (#933) +- Move more development tools to PHIVE (#894, #907) + +### Changed +- Automatically add a backslash for global functions (#909) +- Update the development tools (#898, #895) +- Upgrade to PHPUnit 7.5 (#888) +- Enforce constant visibility (#892) +- Rename the PHPCS configuration file (#891, #896) +- Make use of PHP 7.1 language features (#883) + +### Deprecated +- Support for PHP 7.1 will be removed in Emogrifier 6.0. + +### Removed +- Drop support for Symfony 4.3 and 5.0 (#936) +- Stop checking `tests/` with Psalm (#885) +- Drop support for PHP 7.0 (#880) + +### Fixed +- Fix a nonsensical code example in the README (#920, #935) +- Remove `!important` from `style` attributes also when uppercase, mixed case or + having whitespace after `!` (#911) +- Copy rules using `:...of-type` without a type to the ` - - - $content - - -EOF; - } - - private function formatClass($class) - { - $parts = explode('\\', $class); - - return sprintf('%s', $class, array_pop($parts)); - } - - private function formatPath($path, $line) - { - $file = $this->escapeHtml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path); - $fmt = $this->fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); - - if (!$fmt) { - return sprintf('in %s%s', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : ''); - } - - if (\is_string($fmt)) { - $i = strpos($f = $fmt, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: \strlen($f); - $fmt = [substr($f, 0, $i)] + preg_split('/&([^>]++)>/', substr($f, $i), -1, \PREG_SPLIT_DELIM_CAPTURE); - - for ($i = 1; isset($fmt[$i]); ++$i) { - if (0 === strpos($path, $k = $fmt[$i++])) { - $path = substr_replace($path, $fmt[$i], 0, \strlen($k)); - break; - } - } - - $link = strtr($fmt[0], ['%f' => $path, '%l' => $line]); - } else { - try { - $link = $fmt->format($path, $line); - } catch (\Exception $e) { - return sprintf('in %s%s', $this->escapeHtml($path), $file, 0 < $line ? ' line '.$line : ''); - } - } - - return sprintf('in %s
                  %s', $this->escapeHtml($link), $file, 0 < $line ? ' line '.$line : ''); - } - - /** - * Formats an array as a string. - * - * @param array $args The argument array - * - * @return string - */ - private function formatArgs(array $args) - { - $result = []; - foreach ($args as $key => $item) { - if ('object' === $item[0]) { - $formattedValue = sprintf('object(%s)', $this->formatClass($item[1])); - } elseif ('array' === $item[0]) { - $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); - } elseif ('null' === $item[0]) { - $formattedValue = 'null'; - } elseif ('boolean' === $item[0]) { - $formattedValue = ''.strtolower(var_export($item[1], true)).''; - } elseif ('resource' === $item[0]) { - $formattedValue = 'resource'; - } else { - $formattedValue = str_replace("\n", '', $this->escapeHtml(var_export($item[1], true))); - } - - $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escapeHtml($key), $formattedValue); - } - - return implode(', ', $result); - } - - /** - * HTML-encodes a string. - */ - private function escapeHtml($str) - { - return htmlspecialchars($str, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset); - } - - private function getSymfonyGhostAsSvg() - { - return ''; - } -} diff --git a/lib/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php b/lib/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php deleted file mode 100644 index b1216fe7ae..0000000000 --- a/lib/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php +++ /dev/null @@ -1,198 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug\FatalErrorHandler; - -use Composer\Autoload\ClassLoader as ComposerClassLoader; -use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader; -use Symfony\Component\Debug\DebugClassLoader; -use Symfony\Component\Debug\Exception\ClassNotFoundException; -use Symfony\Component\Debug\Exception\FatalErrorException; - -/** - * ErrorHandler for classes that do not exist. - * - * @author Fabien Potencier - */ -class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface -{ - /** - * {@inheritdoc} - */ - public function handleError(array $error, FatalErrorException $exception) - { - if (!preg_match('/^(Class|Interface|Trait) [\'"]([^\'"]+)[\'"] not found$/', $error['message'], $matches)) { - return null; - } - $typeName = strtolower($matches[1]); - $fullyQualifiedClassName = $matches[2]; - - if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) { - $className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1); - $namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex); - $message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix); - $tail = ' for another namespace?'; - } else { - $className = $fullyQualifiedClassName; - $message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className); - $tail = '?'; - } - - if ($candidates = $this->getClassCandidates($className)) { - $tail = array_pop($candidates).'"?'; - if ($candidates) { - $tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail; - } else { - $tail = ' for "'.$tail; - } - } - $message .= "\nDid you forget a \"use\" statement".$tail; - - return new ClassNotFoundException($message, $exception); - } - - /** - * Tries to guess the full namespace for a given class name. - * - * By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer - * autoloader (that should cover all common cases). - * - * @param string $class A class name (without its namespace) - * - * @return array An array of possible fully qualified class names - */ - private function getClassCandidates($class) - { - if (!\is_array($functions = spl_autoload_functions())) { - return []; - } - - // find Symfony and Composer autoloaders - $classes = []; - - foreach ($functions as $function) { - if (!\is_array($function)) { - continue; - } - // get class loaders wrapped by DebugClassLoader - if ($function[0] instanceof DebugClassLoader) { - $function = $function[0]->getClassLoader(); - - if (!\is_array($function)) { - continue; - } - } - - if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader) { - foreach ($function[0]->getPrefixes() as $prefix => $paths) { - foreach ($paths as $path) { - $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix)); - } - } - } - if ($function[0] instanceof ComposerClassLoader) { - foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) { - foreach ($paths as $path) { - $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix)); - } - } - } - } - - return array_unique($classes); - } - - /** - * @param string $path - * @param string $class - * @param string $prefix - * - * @return array - */ - private function findClassInPath($path, $class, $prefix) - { - if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) { - return []; - } - - $classes = []; - $filename = $class.'.php'; - foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { - if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) { - $classes[] = $class; - } - } - - return $classes; - } - - /** - * @param string $path - * @param string $file - * @param string $prefix - * - * @return string|null - */ - private function convertFileToClass($path, $file, $prefix) - { - $candidates = [ - // namespaced class - $namespacedClass = str_replace([$path.\DIRECTORY_SEPARATOR, '.php', '/'], ['', '', '\\'], $file), - // namespaced class (with target dir) - $prefix.$namespacedClass, - // namespaced class (with target dir and separator) - $prefix.'\\'.$namespacedClass, - // PEAR class - str_replace('\\', '_', $namespacedClass), - // PEAR class (with target dir) - str_replace('\\', '_', $prefix.$namespacedClass), - // PEAR class (with target dir and separator) - str_replace('\\', '_', $prefix.'\\'.$namespacedClass), - ]; - - if ($prefix) { - $candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); }); - } - - // We cannot use the autoloader here as most of them use require; but if the class - // is not found, the new autoloader call will require the file again leading to a - // "cannot redeclare class" error. - foreach ($candidates as $candidate) { - if ($this->classExists($candidate)) { - return $candidate; - } - } - - try { - require_once $file; - } catch (\Throwable $e) { - return null; - } - - foreach ($candidates as $candidate) { - if ($this->classExists($candidate)) { - return $candidate; - } - } - - return null; - } - - /** - * @param string $class - * - * @return bool - */ - private function classExists($class) - { - return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false); - } -} diff --git a/lib/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php b/lib/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php deleted file mode 100644 index 6b87eb30a1..0000000000 --- a/lib/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug\FatalErrorHandler; - -use Symfony\Component\Debug\Exception\FatalErrorException; - -/** - * Attempts to convert fatal errors to exceptions. - * - * @author Fabien Potencier - */ -interface FatalErrorHandlerInterface -{ - /** - * Attempts to convert an error into an exception. - * - * @param array $error An array as returned by error_get_last() - * @param FatalErrorException $exception A FatalErrorException instance - * - * @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise - */ - public function handleError(array $error, FatalErrorException $exception); -} diff --git a/lib/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php b/lib/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php deleted file mode 100644 index 77fc7aa261..0000000000 --- a/lib/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php +++ /dev/null @@ -1,84 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug\FatalErrorHandler; - -use Symfony\Component\Debug\Exception\FatalErrorException; -use Symfony\Component\Debug\Exception\UndefinedFunctionException; - -/** - * ErrorHandler for undefined functions. - * - * @author Fabien Potencier - */ -class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface -{ - /** - * {@inheritdoc} - */ - public function handleError(array $error, FatalErrorException $exception) - { - $messageLen = \strlen($error['message']); - $notFoundSuffix = '()'; - $notFoundSuffixLen = \strlen($notFoundSuffix); - if ($notFoundSuffixLen > $messageLen) { - return null; - } - - if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) { - return null; - } - - $prefix = 'Call to undefined function '; - $prefixLen = \strlen($prefix); - if (0 !== strpos($error['message'], $prefix)) { - return null; - } - - $fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen); - if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) { - $functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1); - $namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex); - $message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix); - } else { - $functionName = $fullyQualifiedFunctionName; - $message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName); - } - - $candidates = []; - foreach (get_defined_functions() as $type => $definedFunctionNames) { - foreach ($definedFunctionNames as $definedFunctionName) { - if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) { - $definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1); - } else { - $definedFunctionNameBasename = $definedFunctionName; - } - - if ($definedFunctionNameBasename === $functionName) { - $candidates[] = '\\'.$definedFunctionName; - } - } - } - - if ($candidates) { - sort($candidates); - $last = array_pop($candidates).'"?'; - if ($candidates) { - $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last; - } else { - $candidates = '"'.$last; - } - $message .= "\nDid you mean to call ".$candidates; - } - - return new UndefinedFunctionException($message, $exception); - } -} diff --git a/lib/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php b/lib/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php deleted file mode 100644 index ff2843b681..0000000000 --- a/lib/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug\FatalErrorHandler; - -use Symfony\Component\Debug\Exception\FatalErrorException; -use Symfony\Component\Debug\Exception\UndefinedMethodException; - -/** - * ErrorHandler for undefined methods. - * - * @author Grégoire Pineau - */ -class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface -{ - /** - * {@inheritdoc} - */ - public function handleError(array $error, FatalErrorException $exception) - { - preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $error['message'], $matches); - if (!$matches) { - return null; - } - - $className = $matches[1]; - $methodName = $matches[2]; - - $message = sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className); - - if (!class_exists($className) || null === $methods = get_class_methods($className)) { - // failed to get the class or its methods on which an unknown method was called (for example on an anonymous class) - return new UndefinedMethodException($message, $exception); - } - - $candidates = []; - foreach ($methods as $definedMethodName) { - $lev = levenshtein($methodName, $definedMethodName); - if ($lev <= \strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) { - $candidates[] = $definedMethodName; - } - } - - if ($candidates) { - sort($candidates); - $last = array_pop($candidates).'"?'; - if ($candidates) { - $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last; - } else { - $candidates = '"'.$last; - } - - $message .= "\nDid you mean to call ".$candidates; - } - - return new UndefinedMethodException($message, $exception); - } -} diff --git a/lib/symfony/debug/LICENSE b/lib/symfony/debug/LICENSE deleted file mode 100644 index 9e936ec044..0000000000 --- a/lib/symfony/debug/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2004-2020 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/lib/symfony/debug/README.md b/lib/symfony/debug/README.md deleted file mode 100644 index f0878df3fa..0000000000 --- a/lib/symfony/debug/README.md +++ /dev/null @@ -1,25 +0,0 @@ -Debug Component -=============== - -The Debug component provides tools to ease debugging PHP code. - -Getting Started ---------------- - -``` -$ composer install symfony/debug -``` - -```php -use Symfony\Component\Debug\Debug; - -Debug::enable(); -``` - -Resources ---------- - - * [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/lib/symfony/debug/Resources/ext/README.md b/lib/symfony/debug/Resources/ext/README.md deleted file mode 100644 index 2df00ecd29..0000000000 --- a/lib/symfony/debug/Resources/ext/README.md +++ /dev/null @@ -1,134 +0,0 @@ -Symfony Debug Extension for PHP 5 -================================= - -This extension publishes several functions to help building powerful debugging tools. -It is compatible with PHP 5.3, 5.4, 5.5 and 5.6; with ZTS and non-ZTS modes. -It is not required thus not provided for PHP 7. - -symfony_zval_info() -------------------- - -- exposes zval_hash/refcounts, allowing e.g. efficient exploration of arbitrary structures in PHP, -- does work with references, preventing memory copying. - -Its behavior is about the same as: - -```php - gettype($array[$key]), - 'zval_hash' => /* hashed memory address of $array[$key] */, - 'zval_refcount' => /* internal zval refcount of $array[$key] */, - 'zval_isref' => /* is_ref status of $array[$key] */, - ]; - - switch ($info['type']) { - case 'object': - $info += [ - 'object_class' => get_class($array[$key]), - 'object_refcount' => /* internal object refcount of $array[$key] */, - 'object_hash' => spl_object_hash($array[$key]), - 'object_handle' => /* internal object handle $array[$key] */, - ]; - break; - - case 'resource': - $info += [ - 'resource_handle' => (int) $array[$key], - 'resource_type' => get_resource_type($array[$key]), - 'resource_refcount' => /* internal resource refcount of $array[$key] */, - ]; - break; - - case 'array': - $info += [ - 'array_count' => count($array[$key]), - ]; - break; - - case 'string': - $info += [ - 'strlen' => strlen($array[$key]), - ]; - break; - } - - return $info; -} -``` - -symfony_debug_backtrace() -------------------------- - -This function works like debug_backtrace(), except that it can fetch the full backtrace in case of fatal errors: - -```php -function foo() { fatal(); } -function bar() { foo(); } - -function sd() { var_dump(symfony_debug_backtrace()); } - -register_shutdown_function('sd'); - -bar(); - -/* Will output -Fatal error: Call to undefined function fatal() in foo.php on line 42 -array(3) { - [0]=> - array(2) { - ["function"]=> - string(2) "sd" - ["args"]=> - array(0) { - } - } - [1]=> - array(4) { - ["file"]=> - string(7) "foo.php" - ["line"]=> - int(1) - ["function"]=> - string(3) "foo" - ["args"]=> - array(0) { - } - } - [2]=> - array(4) { - ["file"]=> - string(102) "foo.php" - ["line"]=> - int(2) - ["function"]=> - string(3) "bar" - ["args"]=> - array(0) { - } - } -} -*/ -``` - -Usage ------ - -To enable the extension from source, run: - -``` - phpize - ./configure - make - sudo make install -``` diff --git a/lib/symfony/debug/Resources/ext/config.m4 b/lib/symfony/debug/Resources/ext/config.m4 deleted file mode 100644 index 3c56047150..0000000000 --- a/lib/symfony/debug/Resources/ext/config.m4 +++ /dev/null @@ -1,63 +0,0 @@ -dnl $Id$ -dnl config.m4 for extension symfony_debug - -dnl Comments in this file start with the string 'dnl'. -dnl Remove where necessary. This file will not work -dnl without editing. - -dnl If your extension references something external, use with: - -dnl PHP_ARG_WITH(symfony_debug, for symfony_debug support, -dnl Make sure that the comment is aligned: -dnl [ --with-symfony_debug Include symfony_debug support]) - -dnl Otherwise use enable: - -PHP_ARG_ENABLE(symfony_debug, whether to enable symfony_debug support, -dnl Make sure that the comment is aligned: -[ --enable-symfony_debug Enable symfony_debug support]) - -if test "$PHP_SYMFONY_DEBUG" != "no"; then - dnl Write more examples of tests here... - - dnl # --with-symfony_debug -> check with-path - dnl SEARCH_PATH="/usr/local /usr" # you might want to change this - dnl SEARCH_FOR="/include/symfony_debug.h" # you most likely want to change this - dnl if test -r $PHP_SYMFONY_DEBUG/$SEARCH_FOR; then # path given as parameter - dnl SYMFONY_DEBUG_DIR=$PHP_SYMFONY_DEBUG - dnl else # search default path list - dnl AC_MSG_CHECKING([for symfony_debug files in default path]) - dnl for i in $SEARCH_PATH ; do - dnl if test -r $i/$SEARCH_FOR; then - dnl SYMFONY_DEBUG_DIR=$i - dnl AC_MSG_RESULT(found in $i) - dnl fi - dnl done - dnl fi - dnl - dnl if test -z "$SYMFONY_DEBUG_DIR"; then - dnl AC_MSG_RESULT([not found]) - dnl AC_MSG_ERROR([Please reinstall the symfony_debug distribution]) - dnl fi - - dnl # --with-symfony_debug -> add include path - dnl PHP_ADD_INCLUDE($SYMFONY_DEBUG_DIR/include) - - dnl # --with-symfony_debug -> check for lib and symbol presence - dnl LIBNAME=symfony_debug # you may want to change this - dnl LIBSYMBOL=symfony_debug # you most likely want to change this - - dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, - dnl [ - dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $SYMFONY_DEBUG_DIR/lib, SYMFONY_DEBUG_SHARED_LIBADD) - dnl AC_DEFINE(HAVE_SYMFONY_DEBUGLIB,1,[ ]) - dnl ],[ - dnl AC_MSG_ERROR([wrong symfony_debug lib version or lib not found]) - dnl ],[ - dnl -L$SYMFONY_DEBUG_DIR/lib -lm - dnl ]) - dnl - dnl PHP_SUBST(SYMFONY_DEBUG_SHARED_LIBADD) - - PHP_NEW_EXTENSION(symfony_debug, symfony_debug.c, $ext_shared) -fi diff --git a/lib/symfony/debug/Resources/ext/config.w32 b/lib/symfony/debug/Resources/ext/config.w32 deleted file mode 100644 index 487e691389..0000000000 --- a/lib/symfony/debug/Resources/ext/config.w32 +++ /dev/null @@ -1,13 +0,0 @@ -// $Id$ -// vim:ft=javascript - -// If your extension references something external, use ARG_WITH -// ARG_WITH("symfony_debug", "for symfony_debug support", "no"); - -// Otherwise, use ARG_ENABLE -// ARG_ENABLE("symfony_debug", "enable symfony_debug support", "no"); - -if (PHP_SYMFONY_DEBUG != "no") { - EXTENSION("symfony_debug", "symfony_debug.c"); -} - diff --git a/lib/symfony/debug/Resources/ext/php_symfony_debug.h b/lib/symfony/debug/Resources/ext/php_symfony_debug.h deleted file mode 100644 index 26d0e8c012..0000000000 --- a/lib/symfony/debug/Resources/ext/php_symfony_debug.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -#ifndef PHP_SYMFONY_DEBUG_H -#define PHP_SYMFONY_DEBUG_H - -extern zend_module_entry symfony_debug_module_entry; -#define phpext_symfony_debug_ptr &symfony_debug_module_entry - -#define PHP_SYMFONY_DEBUG_VERSION "2.7" - -#ifdef PHP_WIN32 -# define PHP_SYMFONY_DEBUG_API __declspec(dllexport) -#elif defined(__GNUC__) && __GNUC__ >= 4 -# define PHP_SYMFONY_DEBUG_API __attribute__ ((visibility("default"))) -#else -# define PHP_SYMFONY_DEBUG_API -#endif - -#ifdef ZTS -#include "TSRM.h" -#endif - -ZEND_BEGIN_MODULE_GLOBALS(symfony_debug) - intptr_t req_rand_init; - void (*old_error_cb)(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args); - zval *debug_bt; -ZEND_END_MODULE_GLOBALS(symfony_debug) - -PHP_MINIT_FUNCTION(symfony_debug); -PHP_MSHUTDOWN_FUNCTION(symfony_debug); -PHP_RINIT_FUNCTION(symfony_debug); -PHP_RSHUTDOWN_FUNCTION(symfony_debug); -PHP_MINFO_FUNCTION(symfony_debug); -PHP_GINIT_FUNCTION(symfony_debug); -PHP_GSHUTDOWN_FUNCTION(symfony_debug); - -PHP_FUNCTION(symfony_zval_info); -PHP_FUNCTION(symfony_debug_backtrace); - -static char *_symfony_debug_memory_address_hash(void * TSRMLS_DC); -static const char *_symfony_debug_zval_type(zval *); -static const char* _symfony_debug_get_resource_type(long TSRMLS_DC); -static int _symfony_debug_get_resource_refcount(long TSRMLS_DC); - -void symfony_debug_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args); - -#ifdef ZTS -#define SYMFONY_DEBUG_G(v) TSRMG(symfony_debug_globals_id, zend_symfony_debug_globals *, v) -#else -#define SYMFONY_DEBUG_G(v) (symfony_debug_globals.v) -#endif - -#endif /* PHP_SYMFONY_DEBUG_H */ diff --git a/lib/symfony/debug/Resources/ext/symfony_debug.c b/lib/symfony/debug/Resources/ext/symfony_debug.c deleted file mode 100644 index 0d7cb60232..0000000000 --- a/lib/symfony/debug/Resources/ext/symfony_debug.c +++ /dev/null @@ -1,283 +0,0 @@ -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include "php.h" -#ifdef ZTS -#include "TSRM.h" -#endif -#include "php_ini.h" -#include "ext/standard/info.h" -#include "php_symfony_debug.h" -#include "ext/standard/php_rand.h" -#include "ext/standard/php_lcg.h" -#include "ext/spl/php_spl.h" -#include "Zend/zend_gc.h" -#include "Zend/zend_builtin_functions.h" -#include "Zend/zend_extensions.h" /* for ZEND_EXTENSION_API_NO */ -#include "ext/standard/php_array.h" -#include "Zend/zend_interfaces.h" -#include "SAPI.h" - -#define IS_PHP_53 ZEND_EXTENSION_API_NO == 220090626 - -ZEND_DECLARE_MODULE_GLOBALS(symfony_debug) - -ZEND_BEGIN_ARG_INFO_EX(symfony_zval_arginfo, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_ARRAY_INFO(0, array, 0) - ZEND_ARG_INFO(0, options) -ZEND_END_ARG_INFO() - -const zend_function_entry symfony_debug_functions[] = { - PHP_FE(symfony_zval_info, symfony_zval_arginfo) - PHP_FE(symfony_debug_backtrace, NULL) - PHP_FE_END -}; - -PHP_FUNCTION(symfony_debug_backtrace) -{ - if (zend_parse_parameters_none() == FAILURE) { - return; - } -#if IS_PHP_53 - zend_fetch_debug_backtrace(return_value, 1, 0 TSRMLS_CC); -#else - zend_fetch_debug_backtrace(return_value, 1, 0, 0 TSRMLS_CC); -#endif - - if (!SYMFONY_DEBUG_G(debug_bt)) { - return; - } - - php_array_merge(Z_ARRVAL_P(return_value), Z_ARRVAL_P(SYMFONY_DEBUG_G(debug_bt)), 0 TSRMLS_CC); -} - -PHP_FUNCTION(symfony_zval_info) -{ - zval *key = NULL, *arg = NULL; - zval **data = NULL; - HashTable *array = NULL; - long options = 0; - - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zh|l", &key, &array, &options) == FAILURE) { - return; - } - - switch (Z_TYPE_P(key)) { - case IS_STRING: - if (zend_symtable_find(array, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, (void **)&data) == FAILURE) { - return; - } - break; - case IS_LONG: - if (zend_hash_index_find(array, Z_LVAL_P(key), (void **)&data)) { - return; - } - break; - } - - arg = *data; - - array_init(return_value); - - add_assoc_string(return_value, "type", (char *)_symfony_debug_zval_type(arg), 1); - add_assoc_stringl(return_value, "zval_hash", _symfony_debug_memory_address_hash((void *)arg TSRMLS_CC), 16, 0); - add_assoc_long(return_value, "zval_refcount", Z_REFCOUNT_P(arg)); - add_assoc_bool(return_value, "zval_isref", (zend_bool)Z_ISREF_P(arg)); - - if (Z_TYPE_P(arg) == IS_OBJECT) { - char hash[33] = {0}; - - php_spl_object_hash(arg, (char *)hash TSRMLS_CC); - add_assoc_stringl(return_value, "object_class", (char *)Z_OBJCE_P(arg)->name, Z_OBJCE_P(arg)->name_length, 1); - add_assoc_long(return_value, "object_refcount", EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(arg)].bucket.obj.refcount); - add_assoc_string(return_value, "object_hash", hash, 1); - add_assoc_long(return_value, "object_handle", Z_OBJ_HANDLE_P(arg)); - } else if (Z_TYPE_P(arg) == IS_ARRAY) { - add_assoc_long(return_value, "array_count", zend_hash_num_elements(Z_ARRVAL_P(arg))); - } else if(Z_TYPE_P(arg) == IS_RESOURCE) { - add_assoc_long(return_value, "resource_handle", Z_LVAL_P(arg)); - add_assoc_string(return_value, "resource_type", (char *)_symfony_debug_get_resource_type(Z_LVAL_P(arg) TSRMLS_CC), 1); - add_assoc_long(return_value, "resource_refcount", _symfony_debug_get_resource_refcount(Z_LVAL_P(arg) TSRMLS_CC)); - } else if (Z_TYPE_P(arg) == IS_STRING) { - add_assoc_long(return_value, "strlen", Z_STRLEN_P(arg)); - } -} - -void symfony_debug_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args) -{ - TSRMLS_FETCH(); - zval *retval; - - switch (type) { - case E_ERROR: - case E_PARSE: - case E_CORE_ERROR: - case E_CORE_WARNING: - case E_COMPILE_ERROR: - case E_COMPILE_WARNING: - ALLOC_INIT_ZVAL(retval); -#if IS_PHP_53 - zend_fetch_debug_backtrace(retval, 1, 0 TSRMLS_CC); -#else - zend_fetch_debug_backtrace(retval, 1, 0, 0 TSRMLS_CC); -#endif - SYMFONY_DEBUG_G(debug_bt) = retval; - } - - SYMFONY_DEBUG_G(old_error_cb)(type, error_filename, error_lineno, format, args); -} - -static const char* _symfony_debug_get_resource_type(long rsid TSRMLS_DC) -{ - const char *res_type; - res_type = zend_rsrc_list_get_rsrc_type(rsid TSRMLS_CC); - - if (!res_type) { - return "Unknown"; - } - - return res_type; -} - -static int _symfony_debug_get_resource_refcount(long rsid TSRMLS_DC) -{ - zend_rsrc_list_entry *le; - - if (zend_hash_index_find(&EG(regular_list), rsid, (void **) &le)==SUCCESS) { - return le->refcount; - } - - return 0; -} - -static char *_symfony_debug_memory_address_hash(void *address TSRMLS_DC) -{ - char *result = NULL; - intptr_t address_rand; - - if (!SYMFONY_DEBUG_G(req_rand_init)) { - if (!BG(mt_rand_is_seeded)) { - php_mt_srand(GENERATE_SEED() TSRMLS_CC); - } - SYMFONY_DEBUG_G(req_rand_init) = (intptr_t)php_mt_rand(TSRMLS_C); - } - - address_rand = (intptr_t)address ^ SYMFONY_DEBUG_G(req_rand_init); - - spprintf(&result, 17, "%016zx", address_rand); - - return result; -} - -static const char *_symfony_debug_zval_type(zval *zv) -{ - switch (Z_TYPE_P(zv)) { - case IS_NULL: - return "NULL"; - break; - - case IS_BOOL: - return "boolean"; - break; - - case IS_LONG: - return "integer"; - break; - - case IS_DOUBLE: - return "double"; - break; - - case IS_STRING: - return "string"; - break; - - case IS_ARRAY: - return "array"; - break; - - case IS_OBJECT: - return "object"; - - case IS_RESOURCE: - return "resource"; - - default: - return "unknown type"; - } -} - -zend_module_entry symfony_debug_module_entry = { - STANDARD_MODULE_HEADER, - "symfony_debug", - symfony_debug_functions, - PHP_MINIT(symfony_debug), - PHP_MSHUTDOWN(symfony_debug), - PHP_RINIT(symfony_debug), - PHP_RSHUTDOWN(symfony_debug), - PHP_MINFO(symfony_debug), - PHP_SYMFONY_DEBUG_VERSION, - PHP_MODULE_GLOBALS(symfony_debug), - PHP_GINIT(symfony_debug), - PHP_GSHUTDOWN(symfony_debug), - NULL, - STANDARD_MODULE_PROPERTIES_EX -}; - -#ifdef COMPILE_DL_SYMFONY_DEBUG -ZEND_GET_MODULE(symfony_debug) -#endif - -PHP_GINIT_FUNCTION(symfony_debug) -{ - memset(symfony_debug_globals, 0 , sizeof(*symfony_debug_globals)); -} - -PHP_GSHUTDOWN_FUNCTION(symfony_debug) -{ - -} - -PHP_MINIT_FUNCTION(symfony_debug) -{ - SYMFONY_DEBUG_G(old_error_cb) = zend_error_cb; - zend_error_cb = symfony_debug_error_cb; - - return SUCCESS; -} - -PHP_MSHUTDOWN_FUNCTION(symfony_debug) -{ - zend_error_cb = SYMFONY_DEBUG_G(old_error_cb); - - return SUCCESS; -} - -PHP_RINIT_FUNCTION(symfony_debug) -{ - return SUCCESS; -} - -PHP_RSHUTDOWN_FUNCTION(symfony_debug) -{ - return SUCCESS; -} - -PHP_MINFO_FUNCTION(symfony_debug) -{ - php_info_print_table_start(); - php_info_print_table_header(2, "Symfony Debug support", "enabled"); - php_info_print_table_header(2, "Symfony Debug version", PHP_SYMFONY_DEBUG_VERSION); - php_info_print_table_end(); -} diff --git a/lib/symfony/debug/composer.json b/lib/symfony/debug/composer.json deleted file mode 100644 index 223d2bab36..0000000000 --- a/lib/symfony/debug/composer.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "symfony/debug", - "type": "library", - "description": "Symfony Debug Component", - "keywords": [], - "homepage": "https://symfony.com", - "license": "MIT", - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "require": { - "php": "^5.5.9|>=7.0.8", - "psr/log": "~1.0" - }, - "conflict": { - "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" - }, - "require-dev": { - "symfony/http-kernel": "~2.8|~3.0|~4.0" - }, - "autoload": { - "psr-4": { "Symfony\\Component\\Debug\\": "" }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "minimum-stability": "dev" -} diff --git a/lib/symfony/debug/phpunit.xml.dist b/lib/symfony/debug/phpunit.xml.dist deleted file mode 100644 index a51bbff935..0000000000 --- a/lib/symfony/debug/phpunit.xml.dist +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - ./Tests/ - - - ./Resources/ext/tests/ - - - - - - ./ - - ./Tests - ./vendor - - - - diff --git a/lib/symfony/dependency-injection/.gitignore b/lib/symfony/dependency-injection/.gitignore deleted file mode 100644 index c49a5d8df5..0000000000 --- a/lib/symfony/dependency-injection/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/lib/symfony/dependency-injection/Alias.php b/lib/symfony/dependency-injection/Alias.php index de14c5ea95..71bfef51e4 100644 --- a/lib/symfony/dependency-injection/Alias.php +++ b/lib/symfony/dependency-injection/Alias.php @@ -11,21 +11,20 @@ namespace Symfony\Component\DependencyInjection; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + class Alias { + private const DEFAULT_DEPRECATION_TEMPLATE = 'The "%alias_id%" service alias is deprecated. You should stop using it, as it will be removed in the future.'; + private $id; private $public; - private $private; + private $deprecation = []; - /** - * @param string $id Alias identifier - * @param bool $public If this alias is public - */ - public function __construct($id, $public = true) + public function __construct(string $id, bool $public = false) { - $this->id = (string) $id; + $this->id = $id; $this->public = $public; - $this->private = 2 > \func_num_args(); } /** @@ -41,14 +40,11 @@ class Alias /** * Sets if this Alias is public. * - * @param bool $boolean If this Alias should be public - * * @return $this */ - public function setPublic($boolean) + public function setPublic(bool $boolean) { - $this->public = (bool) $boolean; - $this->private = false; + $this->public = $boolean; return $this; } @@ -56,20 +52,15 @@ class Alias /** * Sets if this Alias is private. * - * When set, the "private" state has a higher precedence than "public". - * In version 3.4, a "private" alias always remains publicly accessible, - * but triggers a deprecation notice when accessed from the container, - * so that the alias can be made really private in 4.0. - * - * @param bool $boolean - * * @return $this + * + * @deprecated since Symfony 5.2, use setPublic() instead */ - public function setPrivate($boolean) + public function setPrivate(bool $boolean) { - $this->private = (bool) $boolean; + trigger_deprecation('symfony/dependency-injection', '5.2', 'The "%s()" method is deprecated, use "setPublic()" instead.', __METHOD__); - return $this; + return $this->setPublic(!$boolean); } /** @@ -79,13 +70,89 @@ class Alias */ public function isPrivate() { - return $this->private; + return !$this->public; + } + + /** + * Whether this alias is deprecated, that means it should not be referenced + * anymore. + * + * @param string $package The name of the composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The deprecation message to use + * + * @return $this + * + * @throws InvalidArgumentException when the message template is invalid + */ + public function setDeprecated(/* string $package, string $version, string $message */) + { + $args = \func_get_args(); + + if (\func_num_args() < 3) { + trigger_deprecation('symfony/dependency-injection', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__); + + $status = $args[0] ?? true; + + if (!$status) { + trigger_deprecation('symfony/dependency-injection', '5.1', 'Passing a null message to un-deprecate a node is deprecated.'); + } + + $message = (string) ($args[1] ?? null); + $package = $version = ''; + } else { + $status = true; + $package = (string) $args[0]; + $version = (string) $args[1]; + $message = (string) $args[2]; + } + + if ('' !== $message) { + if (preg_match('#[\r\n]|\*/#', $message)) { + throw new InvalidArgumentException('Invalid characters found in deprecation template.'); + } + + if (!str_contains($message, '%alias_id%')) { + throw new InvalidArgumentException('The deprecation template must contain the "%alias_id%" placeholder.'); + } + } + + $this->deprecation = $status ? ['package' => $package, 'version' => $version, 'message' => $message ?: self::DEFAULT_DEPRECATION_TEMPLATE] : []; + + return $this; + } + + public function isDeprecated(): bool + { + return (bool) $this->deprecation; + } + + /** + * @deprecated since Symfony 5.1, use "getDeprecation()" instead. + */ + public function getDeprecationMessage(string $id): string + { + trigger_deprecation('symfony/dependency-injection', '5.1', 'The "%s()" method is deprecated, use "getDeprecation()" instead.', __METHOD__); + + return $this->getDeprecation($id)['message']; + } + + /** + * @param string $id Service id relying on this definition + */ + public function getDeprecation(string $id): array + { + return [ + 'package' => $this->deprecation['package'], + 'version' => $this->deprecation['version'], + 'message' => str_replace('%alias_id%', $id, $this->deprecation['message']), + ]; } /** * Returns the Id of this alias. * - * @return string The alias id + * @return string */ public function __toString() { diff --git a/lib/symfony/dependency-injection/Argument/AbstractArgument.php b/lib/symfony/dependency-injection/Argument/AbstractArgument.php new file mode 100644 index 0000000000..3ba5ff33ba --- /dev/null +++ b/lib/symfony/dependency-injection/Argument/AbstractArgument.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 Symfony\Component\DependencyInjection\Argument; + +/** + * Represents an abstract service argument, which have to be set by a compiler pass or a DI extension. + */ +final class AbstractArgument +{ + private $text; + private $context; + + public function __construct(string $text = '') + { + $this->text = trim($text, '. '); + } + + public function setContext(string $context): void + { + $this->context = $context.' is abstract'.('' === $this->text ? '' : ': '); + } + + public function getText(): string + { + return $this->text; + } + + public function getTextWithContext(): string + { + return $this->context.$this->text.'.'; + } +} diff --git a/lib/symfony/dependency-injection/Argument/BoundArgument.php b/lib/symfony/dependency-injection/Argument/BoundArgument.php index a20698440c..c2afe2cfa2 100644 --- a/lib/symfony/dependency-injection/Argument/BoundArgument.php +++ b/lib/symfony/dependency-injection/Argument/BoundArgument.php @@ -16,24 +16,36 @@ namespace Symfony\Component\DependencyInjection\Argument; */ final class BoundArgument implements ArgumentInterface { + public const SERVICE_BINDING = 0; + public const DEFAULTS_BINDING = 1; + public const INSTANCEOF_BINDING = 2; + private static $sequence = 0; private $value; private $identifier; private $used; + private $type; + private $file; - public function __construct($value) + public function __construct($value, bool $trackUsage = true, int $type = 0, string $file = null) { $this->value = $value; - $this->identifier = ++self::$sequence; + if ($trackUsage) { + $this->identifier = ++self::$sequence; + } else { + $this->used = true; + } + $this->type = $type; + $this->file = $file; } /** * {@inheritdoc} */ - public function getValues() + public function getValues(): array { - return [$this->value, $this->identifier, $this->used]; + return [$this->value, $this->identifier, $this->used, $this->type, $this->file]; } /** @@ -41,6 +53,10 @@ final class BoundArgument implements ArgumentInterface */ public function setValues(array $values) { - list($this->value, $this->identifier, $this->used) = $values; + if (5 === \count($values)) { + [$this->value, $this->identifier, $this->used, $this->type, $this->file] = $values; + } else { + [$this->value, $this->identifier, $this->used] = $values; + } } } diff --git a/lib/symfony/dependency-injection/Argument/IteratorArgument.php b/lib/symfony/dependency-injection/Argument/IteratorArgument.php index 2d796d2d8f..d413678a14 100644 --- a/lib/symfony/dependency-injection/Argument/IteratorArgument.php +++ b/lib/symfony/dependency-injection/Argument/IteratorArgument.php @@ -11,9 +11,6 @@ namespace Symfony\Component\DependencyInjection\Argument; -use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; -use Symfony\Component\DependencyInjection\Reference; - /** * Represents a collection of values to lazily iterate over. * @@ -21,35 +18,5 @@ use Symfony\Component\DependencyInjection\Reference; */ class IteratorArgument implements ArgumentInterface { - private $values; - - /** - * @param Reference[] $values - */ - public function __construct(array $values) - { - $this->setValues($values); - } - - /** - * @return array The values to lazily iterate over - */ - public function getValues() - { - return $this->values; - } - - /** - * @param Reference[] $values The service references to lazily iterate over - */ - public function setValues(array $values) - { - foreach ($values as $k => $v) { - if (null !== $v && !$v instanceof Reference) { - throw new InvalidArgumentException(sprintf('An IteratorArgument must hold only Reference instances, "%s" given.', \is_object($v) ? \get_class($v) : \gettype($v))); - } - } - - $this->values = $values; - } + use ReferenceSetArgumentTrait; } diff --git a/lib/symfony/dependency-injection/Argument/ReferenceSetArgumentTrait.php b/lib/symfony/dependency-injection/Argument/ReferenceSetArgumentTrait.php new file mode 100644 index 0000000000..150c9bf572 --- /dev/null +++ b/lib/symfony/dependency-injection/Argument/ReferenceSetArgumentTrait.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Titouan Galopin + * @author Nicolas Grekas + */ +trait ReferenceSetArgumentTrait +{ + private $values; + + /** + * @param Reference[] $values + */ + public function __construct(array $values) + { + $this->setValues($values); + } + + /** + * @return Reference[] + */ + public function getValues() + { + return $this->values; + } + + /** + * @param Reference[] $values The service references to put in the set + */ + public function setValues(array $values) + { + foreach ($values as $k => $v) { + if (null !== $v && !$v instanceof Reference) { + throw new InvalidArgumentException(sprintf('A "%s" must hold only Reference instances, "%s" given.', __CLASS__, get_debug_type($v))); + } + } + + $this->values = $values; + } +} diff --git a/lib/symfony/dependency-injection/Argument/RewindableGenerator.php b/lib/symfony/dependency-injection/Argument/RewindableGenerator.php index b00a36c34f..41fec786fd 100644 --- a/lib/symfony/dependency-injection/Argument/RewindableGenerator.php +++ b/lib/symfony/dependency-injection/Argument/RewindableGenerator.php @@ -28,14 +28,14 @@ class RewindableGenerator implements \IteratorAggregate, \Countable $this->count = $count; } - public function getIterator() + public function getIterator(): \Traversable { $g = $this->generator; return $g(); } - public function count() + public function count(): int { if (\is_callable($count = $this->count)) { $this->count = $count(); diff --git a/lib/symfony/dependency-injection/Argument/ServiceLocator.php b/lib/symfony/dependency-injection/Argument/ServiceLocator.php new file mode 100644 index 0000000000..bc138fe239 --- /dev/null +++ b/lib/symfony/dependency-injection/Argument/ServiceLocator.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +use Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class ServiceLocator extends BaseServiceLocator +{ + private $factory; + private $serviceMap; + private $serviceTypes; + + public function __construct(\Closure $factory, array $serviceMap, array $serviceTypes = null) + { + $this->factory = $factory; + $this->serviceMap = $serviceMap; + $this->serviceTypes = $serviceTypes; + parent::__construct($serviceMap); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function get(string $id) + { + return isset($this->serviceMap[$id]) ? ($this->factory)(...$this->serviceMap[$id]) : parent::get($id); + } + + /** + * {@inheritdoc} + */ + public function getProvidedServices(): array + { + return $this->serviceTypes ?? $this->serviceTypes = array_map(function () { return '?'; }, $this->serviceMap); + } +} diff --git a/lib/symfony/dependency-injection/Argument/ServiceLocatorArgument.php b/lib/symfony/dependency-injection/Argument/ServiceLocatorArgument.php new file mode 100644 index 0000000000..fcbf478c62 --- /dev/null +++ b/lib/symfony/dependency-injection/Argument/ServiceLocatorArgument.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Argument; + +use Symfony\Component\DependencyInjection\Reference; + +/** + * Represents a closure acting as a service locator. + * + * @author Nicolas Grekas + */ +class ServiceLocatorArgument implements ArgumentInterface +{ + use ReferenceSetArgumentTrait; + + private $taggedIteratorArgument; + + /** + * @param Reference[]|TaggedIteratorArgument $values + */ + public function __construct($values = []) + { + if ($values instanceof TaggedIteratorArgument) { + $this->taggedIteratorArgument = $values; + $this->values = []; + } else { + $this->setValues($values); + } + } + + public function getTaggedIteratorArgument(): ?TaggedIteratorArgument + { + return $this->taggedIteratorArgument; + } +} diff --git a/lib/symfony/dependency-injection/Argument/TaggedIteratorArgument.php b/lib/symfony/dependency-injection/Argument/TaggedIteratorArgument.php index f00e533919..1ba8de790b 100644 --- a/lib/symfony/dependency-injection/Argument/TaggedIteratorArgument.php +++ b/lib/symfony/dependency-injection/Argument/TaggedIteratorArgument.php @@ -19,19 +19,55 @@ namespace Symfony\Component\DependencyInjection\Argument; class TaggedIteratorArgument extends IteratorArgument { private $tag; + private $indexAttribute; + private $defaultIndexMethod; + private $defaultPriorityMethod; + private $needsIndexes = false; /** - * @param string $tag + * @param string $tag The name of the tag identifying the target services + * @param string|null $indexAttribute The name of the attribute that defines the key referencing each service in the tagged collection + * @param string|null $defaultIndexMethod The static method that should be called to get each service's key when their tag doesn't define the previous attribute + * @param bool $needsIndexes Whether indexes are required and should be generated when computing the map + * @param string|null $defaultPriorityMethod The static method that should be called to get each service's priority when their tag doesn't define the "priority" attribute */ - public function __construct($tag) + public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $needsIndexes = false, string $defaultPriorityMethod = null) { parent::__construct([]); - $this->tag = (string) $tag; + if (null === $indexAttribute && $needsIndexes) { + $indexAttribute = preg_match('/[^.]++$/', $tag, $m) ? $m[0] : $tag; + } + + $this->tag = $tag; + $this->indexAttribute = $indexAttribute; + $this->defaultIndexMethod = $defaultIndexMethod ?: ($indexAttribute ? 'getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute))).'Name' : null); + $this->needsIndexes = $needsIndexes; + $this->defaultPriorityMethod = $defaultPriorityMethod ?: ($indexAttribute ? 'getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute))).'Priority' : null); } public function getTag() { return $this->tag; } + + public function getIndexAttribute(): ?string + { + return $this->indexAttribute; + } + + public function getDefaultIndexMethod(): ?string + { + return $this->defaultIndexMethod; + } + + public function needsIndexes(): bool + { + return $this->needsIndexes; + } + + public function getDefaultPriorityMethod(): ?string + { + return $this->defaultPriorityMethod; + } } diff --git a/lib/symfony/dependency-injection/Attribute/AsTaggedItem.php b/lib/symfony/dependency-injection/Attribute/AsTaggedItem.php new file mode 100644 index 0000000000..2320336338 --- /dev/null +++ b/lib/symfony/dependency-injection/Attribute/AsTaggedItem.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +/** + * An attribute to tell under which index and priority a service class should be found in tagged iterators/locators. + * + * @author Nicolas Grekas + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class AsTaggedItem +{ + public function __construct( + public ?string $index = null, + public ?int $priority = null, + ) { + } +} diff --git a/lib/symfony/dependency-injection/Attribute/Autoconfigure.php b/lib/symfony/dependency-injection/Attribute/Autoconfigure.php new file mode 100644 index 0000000000..abab040101 --- /dev/null +++ b/lib/symfony/dependency-injection/Attribute/Autoconfigure.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +/** + * An attribute to tell how a base type should be autoconfigured. + * + * @author Nicolas Grekas + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +class Autoconfigure +{ + public function __construct( + public ?array $tags = null, + public ?array $calls = null, + public ?array $bind = null, + public bool|string|null $lazy = null, + public ?bool $public = null, + public ?bool $shared = null, + public ?bool $autowire = null, + public ?array $properties = null, + public array|string|null $configurator = null, + ) { + } +} diff --git a/lib/symfony/dependency-injection/Attribute/AutoconfigureTag.php b/lib/symfony/dependency-injection/Attribute/AutoconfigureTag.php new file mode 100644 index 0000000000..ed5807ca02 --- /dev/null +++ b/lib/symfony/dependency-injection/Attribute/AutoconfigureTag.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +/** + * An attribute to tell how a base type should be tagged. + * + * @author Nicolas Grekas + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] +class AutoconfigureTag extends Autoconfigure +{ + public function __construct(string $name = null, array $attributes = []) + { + parent::__construct( + tags: [ + [$name ?? 0 => $attributes], + ] + ); + } +} diff --git a/lib/symfony/dependency-injection/Attribute/TaggedIterator.php b/lib/symfony/dependency-injection/Attribute/TaggedIterator.php new file mode 100644 index 0000000000..d498f46470 --- /dev/null +++ b/lib/symfony/dependency-injection/Attribute/TaggedIterator.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\DependencyInjection\Attribute; + +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class TaggedIterator +{ + public function __construct( + public string $tag, + public ?string $indexAttribute = null, + public ?string $defaultIndexMethod = null, + public ?string $defaultPriorityMethod = null, + ) { + } +} diff --git a/lib/symfony/dependency-injection/Attribute/TaggedLocator.php b/lib/symfony/dependency-injection/Attribute/TaggedLocator.php new file mode 100644 index 0000000000..4617e0f51d --- /dev/null +++ b/lib/symfony/dependency-injection/Attribute/TaggedLocator.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\DependencyInjection\Attribute; + +#[\Attribute(\Attribute::TARGET_PARAMETER)] +class TaggedLocator +{ + public function __construct( + public string $tag, + public ?string $indexAttribute = null, + public ?string $defaultIndexMethod = null, + public ?string $defaultPriorityMethod = null, + ) { + } +} diff --git a/lib/symfony/dependency-injection/Attribute/Target.php b/lib/symfony/dependency-injection/Attribute/Target.php new file mode 100644 index 0000000000..a7a4d8b5f7 --- /dev/null +++ b/lib/symfony/dependency-injection/Attribute/Target.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +/** + * An attribute to tell how a dependency is used and hint named autowiring aliases. + * + * @author Nicolas Grekas + */ +#[\Attribute(\Attribute::TARGET_PARAMETER)] +final class Target +{ + /** + * @var string + */ + public $name; + + public function __construct(string $name) + { + $this->name = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $name)))); + } + + public static function parseName(\ReflectionParameter $parameter): string + { + if (80000 > \PHP_VERSION_ID || !$target = $parameter->getAttributes(self::class)[0] ?? null) { + return $parameter->name; + } + + $name = $target->newInstance()->name; + + if (!preg_match('/^[a-zA-Z_\x7f-\xff]/', $name)) { + if (($function = $parameter->getDeclaringFunction()) instanceof \ReflectionMethod) { + $function = $function->class.'::'.$function->name; + } else { + $function = $function->name; + } + + throw new InvalidArgumentException(sprintf('Invalid #[Target] name "%s" on parameter "$%s" of "%s()": the first character must be a letter.', $name, $parameter->name, $function)); + } + + return $name; + } +} diff --git a/lib/symfony/dependency-injection/Attribute/When.php b/lib/symfony/dependency-injection/Attribute/When.php new file mode 100644 index 0000000000..60b7af04b6 --- /dev/null +++ b/lib/symfony/dependency-injection/Attribute/When.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Attribute; + +/** + * An attribute to tell under which environement this class should be registered as a service. + * + * @author Nicolas Grekas + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_FUNCTION | \Attribute::IS_REPEATABLE)] +class When +{ + public function __construct( + public string $env, + ) { + } +} diff --git a/lib/symfony/dependency-injection/CHANGELOG.md b/lib/symfony/dependency-injection/CHANGELOG.md index a004161b96..88a6df8df3 100644 --- a/lib/symfony/dependency-injection/CHANGELOG.md +++ b/lib/symfony/dependency-injection/CHANGELOG.md @@ -1,6 +1,193 @@ CHANGELOG ========= +5.4 +--- + * Add `$defaultIndexMethod` and `$defaultPriorityMethod` to `TaggedIterator` and `TaggedLocator` attributes + * Add `service_closure()` to the PHP-DSL + * Add support for autoconfigurable attributes on methods, properties and parameters + * Make auto-aliases private by default + * Add support for autowiring union and intersection types + +5.3 +--- + + * Add `ServicesConfigurator::remove()` in the PHP-DSL + * Add `%env(not:...)%` processor to negate boolean values + * Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8 + * Add `#[AsTaggedItem]` attribute for defining the index and priority of classes found in tagged iterators/locators + * Add autoconfigurable attributes + * Add support for autowiring tagged iterators and locators via attributes on PHP 8 + * Add support for per-env configuration in XML and Yaml loaders + * Add `ContainerBuilder::willBeAvailable()` to help with conditional configuration + * Add support an integer return value for default_index_method + * Add `#[When(env: 'foo')]` to skip autoregistering a class when the env doesn't match + * Add `env()` and `EnvConfigurator` in the PHP-DSL + * Add support for `ConfigBuilder` in the `PhpFileLoader` + * Add `ContainerConfigurator::env()` to get the current environment + * Add `#[Target]` to tell how a dependency is used and hint named autowiring aliases + +5.2.0 +----- + + * added `param()` and `abstract_arg()` in the PHP-DSL + * deprecated `Definition::setPrivate()` and `Alias::setPrivate()`, use `setPublic()` instead + * added support for the `#[Required]` attribute + +5.1.0 +----- + + * deprecated `inline()` in favor of `inline_service()` and `ref()` in favor of `service()` when using the PHP-DSL + * allow decorators to reference their decorated service using the special `.inner` id + * added support to autowire public typed properties in php 7.4 + * added support for defining method calls, a configurator, and property setters in `InlineServiceConfigurator` + * added possibility to define abstract service arguments + * allowed mixing "parent" and instanceof-conditionals/defaults/bindings + * updated the signature of method `Definition::setDeprecated()` to `Definition::setDeprecation(string $package, string $version, string $message)` + * updated the signature of method `Alias::setDeprecated()` to `Alias::setDeprecation(string $package, string $version, string $message)` + * updated the signature of method `DeprecateTrait::deprecate()` to `DeprecateTrait::deprecation(string $package, string $version, string $message)` + * deprecated the `Psr\Container\ContainerInterface` and `Symfony\Component\DependencyInjection\ContainerInterface` aliases of the `service_container` service, + configure them explicitly instead + * added class `Symfony\Component\DependencyInjection\Dumper\Preloader` to help with preloading on PHP 7.4+ + * added tags `container.preload`/`.no_preload` to declare extra classes to preload/services to not preload + * allowed loading and dumping tags with an attribute named "name" + * deprecated `Definition::getDeprecationMessage()`, use `Definition::getDeprecation()` instead + * deprecated `Alias::getDeprecationMessage()`, use `Alias::getDeprecation()` instead + * added support of PHP8 static return type for withers + * added `AliasDeprecatedPublicServicesPass` to deprecate public services to private + +5.0.0 +----- + + * removed support for auto-discovered extension configuration class which does not implement `ConfigurationInterface` + * removed support for non-string default env() parameters + * moved `ServiceSubscriberInterface` to the `Symfony\Contracts\Service` namespace + * removed `RepeatedPass` and `RepeatablePassInterface` + * removed support for short factory/configurator syntax from `YamlFileLoader` + * removed `ResettableContainerInterface`, use `ResetInterface` instead + * added argument `$returnsClone` to `Definition::addMethodCall()` + * removed `tagged`, use `tagged_iterator` instead + +4.4.0 +----- + + * added `CheckTypeDeclarationsPass` to check injected parameters type during compilation + * added support for opcache.preload by generating a preloading script in the cache folder + * added support for dumping the container in one file instead of many files + * deprecated support for short factories and short configurators in Yaml + * added `tagged_iterator` alias for `tagged` which might be deprecated in a future version + * deprecated passing an instance of `Symfony\Component\DependencyInjection\Parameter` as class name to `Symfony\Component\DependencyInjection\Definition` + * added support for binding iterable and tagged services + * made singly-implemented interfaces detection be scoped by file + * added ability to define a static priority method for tagged service + * added support for improved syntax to define method calls in Yaml + * made the `%env(base64:...)%` processor able to decode base64url + * added ability to choose behavior of decorations on non existent decorated services + +4.3.0 +----- + + * added `%env(trim:...)%` processor to trim a string value + * added `%env(default:param_name:...)%` processor to fallback to a parameter or to null when using `%env(default::...)%` + * added `%env(url:...)%` processor to convert an URL or DNS into an array of components + * added `%env(query_string:...)%` processor to convert a query string into an array of key values + * added support for deprecating aliases + * made `ContainerParametersResource` final and not implement `Serializable` anymore + * added `ReverseContainer`: a container that turns services back to their ids + * added ability to define an index for a tagged collection + * added ability to define an index for services in an injected service locator argument + * made `ServiceLocator` implement `ServiceProviderInterface` + * deprecated support for non-string default env() parameters + * added `%env(require:...)%` processor to `require()` a PHP file and use the value returned from it + +4.2.0 +----- + + * added `ContainerBuilder::registerAliasForArgument()` to support autowiring by type+name + * added support for binding by type+name + * added `ServiceSubscriberTrait` to ease implementing `ServiceSubscriberInterface` using methods' return types + * added `ServiceLocatorArgument` and `!service_locator` config tag for creating optimized service-locators + * added support for autoconfiguring bindings + * added `%env(key:...)%` processor to fetch a specific key from an array + * deprecated `ServiceSubscriberInterface`, use the same interface from the `Symfony\Contracts\Service` namespace instead + * deprecated `ResettableContainerInterface`, use `Symfony\Contracts\Service\ResetInterface` instead + +4.1.0 +----- + + * added support for variadics in named arguments + * added PSR-11 `ContainerBagInterface` and its `ContainerBag` implementation to access parameters as-a-service + * added support for service's decorators autowiring + * deprecated the `TypedReference::canBeAutoregistered()` and `TypedReference::getRequiringClass()` methods + * environment variables are validated when used in extension configuration + * deprecated support for auto-discovered extension configuration class which does not implement `ConfigurationInterface` + +4.0.0 +----- + + * Relying on service auto-registration while autowiring is not supported anymore. + Explicitly inject your dependencies or create services whose ids are + their fully-qualified class name. + + Before: + + ```php + namespace App\Controller; + + use App\Mailer; + + class DefaultController + { + public function __construct(Mailer $mailer) { + // ... + } + + // ... + } + ``` + ```yml + services: + App\Controller\DefaultController: + autowire: true + ``` + + After: + + ```php + // same PHP code + ``` + ```yml + services: + App\Controller\DefaultController: + autowire: true + + # or + # App\Controller\DefaultController: + # arguments: { $mailer: "@App\Mailer" } + + App\Mailer: + autowire: true + ``` + * removed autowiring services based on the types they implement + * added a third `$methodName` argument to the `getProxyFactoryCode()` method + of the `DumperInterface` + * removed support for autowiring types + * removed `Container::isFrozen` + * removed support for dumping an ucompiled container in `PhpDumper` + * removed support for generating a dumped `Container` without populating the method map + * removed support for case insensitive service identifiers + * removed the `DefinitionDecorator` class, replaced by `ChildDefinition` + * removed the `AutowireServiceResource` class and related `AutowirePass::createResourceForClass()` method + * removed `LoggingFormatter`, `Compiler::getLoggingFormatter()` and `addLogMessage()` class and methods, use the `ContainerBuilder::log()` method instead + * removed `FactoryReturnTypePass` + * removed `ContainerBuilder::addClassResource()`, use the `addObjectResource()` or the `getReflectionClass()` method instead. + * removed support for top-level anonymous services + * removed silent behavior for unused attributes and elements + * removed support for setting and accessing private services in `Container` + * removed support for setting pre-defined services in `Container` + * removed support for case insensitivity of parameter names + * removed `AutowireExceptionPass` and `AutowirePass::getAutowiringExceptions()`, use `Definition::addError()` and the `DefinitionErrorExceptionPass` instead + 3.4.0 ----- @@ -16,7 +203,6 @@ CHANGELOG * added `TaggedIteratorArgument` with YAML (`!tagged foo`) and XML (``) support * deprecated `AutowireExceptionPass` and `AutowirePass::getAutowiringExceptions()`, use `Definition::addError()` and the `DefinitionErrorExceptionPass` instead - 3.3.0 ----- @@ -85,8 +271,8 @@ CHANGELOG 2.5.0 ----- -* added DecoratorServicePass and a way to override a service definition (Definition::setDecoratedService()) -* deprecated SimpleXMLElement class. + * added DecoratorServicePass and a way to override a service definition (Definition::setDecoratedService()) + * deprecated SimpleXMLElement class. 2.4.0 ----- diff --git a/lib/symfony/dependency-injection/ChildDefinition.php b/lib/symfony/dependency-injection/ChildDefinition.php index 123b387475..5c648ba612 100644 --- a/lib/symfony/dependency-injection/ChildDefinition.php +++ b/lib/symfony/dependency-injection/ChildDefinition.php @@ -11,7 +11,6 @@ namespace Symfony\Component\DependencyInjection; -use Symfony\Component\DependencyInjection\Exception\BadMethodCallException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException; @@ -27,10 +26,9 @@ class ChildDefinition extends Definition /** * @param string $parent The id of Definition instance to decorate */ - public function __construct($parent) + public function __construct(string $parent) { $this->parent = $parent; - $this->setPrivate(false); } /** @@ -46,11 +44,9 @@ class ChildDefinition extends Definition /** * Sets the Definition to inherit from. * - * @param string $parent - * * @return $this */ - public function setParent($parent) + public function setParent(string $parent) { $this->parent = $parent; @@ -65,7 +61,7 @@ class ChildDefinition extends Definition * * @param int|string $index * - * @return mixed The argument value + * @return mixed * * @throws OutOfBoundsException When the argument does not exist */ @@ -97,7 +93,7 @@ class ChildDefinition extends Definition { if (\is_int($index)) { $this->arguments['index_'.$index] = $value; - } elseif (0 === strpos($index, '$')) { + } elseif (str_starts_with($index, '$')) { $this->arguments[$index] = $value; } else { throw new InvalidArgumentException('The argument must be an existing index or the name of a constructor\'s parameter.'); @@ -105,22 +101,4 @@ class ChildDefinition extends Definition return $this; } - - /** - * @internal - */ - public function setAutoconfigured($autoconfigured) - { - throw new BadMethodCallException('A ChildDefinition cannot be autoconfigured.'); - } - - /** - * @internal - */ - public function setInstanceofConditionals(array $instanceof) - { - throw new BadMethodCallException('A ChildDefinition cannot have instanceof conditionals set on it.'); - } } - -class_alias(ChildDefinition::class, DefinitionDecorator::class); diff --git a/lib/symfony/dependency-injection/Compiler/AbstractRecursivePass.php b/lib/symfony/dependency-injection/Compiler/AbstractRecursivePass.php index 863bab4731..362c5f5718 100644 --- a/lib/symfony/dependency-injection/Compiler/AbstractRecursivePass.php +++ b/lib/symfony/dependency-injection/Compiler/AbstractRecursivePass.php @@ -12,10 +12,14 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\ExpressionLanguage; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ExpressionLanguage\Expression; /** * @author Nicolas Grekas @@ -28,6 +32,10 @@ abstract class AbstractRecursivePass implements CompilerPassInterface protected $container; protected $currentId; + private $processExpressions = false; + private $expressionLanguage; + private $inExpression = false; + /** * {@inheritdoc} */ @@ -42,15 +50,29 @@ abstract class AbstractRecursivePass implements CompilerPassInterface } } + protected function enableExpressionProcessing() + { + $this->processExpressions = true; + } + + protected function inExpression(bool $reset = true): bool + { + $inExpression = $this->inExpression; + if ($reset) { + $this->inExpression = false; + } + + return $inExpression; + } + /** * Processes a value found in a definition tree. * * @param mixed $value - * @param bool $isRoot * - * @return mixed The processed value + * @return mixed */ - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { if (\is_array($value)) { foreach ($value as $k => $v) { @@ -63,6 +85,8 @@ abstract class AbstractRecursivePass implements CompilerPassInterface } } elseif ($value instanceof ArgumentInterface) { $value->setValues($this->processValue($value->getValues())); + } elseif ($value instanceof Expression && $this->processExpressions) { + $this->getExpressionLanguage()->compile((string) $value, ['this' => 'container']); } elseif ($value instanceof Definition) { $value->setArguments($this->processValue($value->getArguments())); $value->setProperties($this->processValue($value->getProperties())); @@ -81,13 +105,11 @@ abstract class AbstractRecursivePass implements CompilerPassInterface } /** - * @param bool $required - * * @return \ReflectionFunctionAbstract|null * * @throws RuntimeException */ - protected function getConstructor(Definition $definition, $required) + protected function getConstructor(Definition $definition, bool $required) { if ($definition->isSynthetic()) { return null; @@ -106,23 +128,36 @@ abstract class AbstractRecursivePass implements CompilerPassInterface } if ($factory) { - list($class, $method) = $factory; - if ($class instanceof Reference) { - $class = $this->container->findDefinition((string) $class)->getClass(); - } elseif (null === $class) { - $class = $definition->getClass(); - } + [$class, $method] = $factory; + if ('__construct' === $method) { throw new RuntimeException(sprintf('Invalid service "%s": "__construct()" cannot be used as a factory method.', $this->currentId)); } + if ($class instanceof Reference) { + $factoryDefinition = $this->container->findDefinition((string) $class); + while ((null === $class = $factoryDefinition->getClass()) && $factoryDefinition instanceof ChildDefinition) { + $factoryDefinition = $this->container->findDefinition($factoryDefinition->getParent()); + } + } elseif ($class instanceof Definition) { + $class = $class->getClass(); + } elseif (null === $class) { + $class = $definition->getClass(); + } + return $this->getReflectionMethod(new Definition($class), $method); } - $class = $definition->getClass(); + while ((null === $class = $definition->getClass()) && $definition instanceof ChildDefinition) { + $definition = $this->container->findDefinition($definition->getParent()); + } try { if (!$r = $this->container->getReflectionClass($class)) { + if (null === $class) { + throw new RuntimeException(sprintf('Invalid service "%s": the class is not set.', $this->currentId)); + } + throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $this->currentId, $class)); } } catch (\ReflectionException $e) { @@ -140,19 +175,21 @@ abstract class AbstractRecursivePass implements CompilerPassInterface } /** - * @param string $method - * * @throws RuntimeException * * @return \ReflectionFunctionAbstract */ - protected function getReflectionMethod(Definition $definition, $method) + protected function getReflectionMethod(Definition $definition, string $method) { if ('__construct' === $method) { return $this->getConstructor($definition, true); } - if (!$class = $definition->getClass()) { + while ((null === $class = $definition->getClass()) && $definition instanceof ChildDefinition) { + $definition = $this->container->findDefinition($definition->getParent()); + } + + if (null === $class) { throw new RuntimeException(sprintf('Invalid service "%s": the class is not set.', $this->currentId)); } @@ -161,6 +198,10 @@ abstract class AbstractRecursivePass implements CompilerPassInterface } if (!$r->hasMethod($method)) { + if ($r->hasMethod('__call') && ($r = $r->getMethod('__call')) && $r->isPublic()) { + return new \ReflectionMethod(static function (...$arguments) {}, '__invoke'); + } + throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" does not exist.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method)); } @@ -171,4 +212,31 @@ abstract class AbstractRecursivePass implements CompilerPassInterface return $r; } + + private function getExpressionLanguage(): ExpressionLanguage + { + if (null === $this->expressionLanguage) { + if (!class_exists(ExpressionLanguage::class)) { + throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); + } + + $providers = $this->container->getExpressionLanguageProviders(); + $this->expressionLanguage = new ExpressionLanguage(null, $providers, function (string $arg): string { + if ('""' === substr_replace($arg, '', 1, -1)) { + $id = stripcslashes(substr($arg, 1, -1)); + $this->inExpression = true; + $arg = $this->processValue(new Reference($id)); + $this->inExpression = false; + if (!$arg instanceof Reference) { + throw new RuntimeException(sprintf('"%s::processValue()" must return a Reference when processing an expression, "%s" returned for service("%s").', static::class, get_debug_type($arg), $id)); + } + $arg = sprintf('"%s"', $arg); + } + + return sprintf('$this->get(%s)', $arg); + }); + } + + return $this->expressionLanguage; + } } diff --git a/lib/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php b/lib/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php new file mode 100644 index 0000000000..8d3fefe750 --- /dev/null +++ b/lib/symfony/dependency-injection/Compiler/AliasDeprecatedPublicServicesPass.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; + +final class AliasDeprecatedPublicServicesPass extends AbstractRecursivePass +{ + private $tagName; + + private $aliases = []; + + public function __construct(string $tagName = 'container.private') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->tagName = $tagName; + } + + /** + * {@inheritdoc} + */ + protected function processValue($value, bool $isRoot = false) + { + if ($value instanceof Reference && isset($this->aliases[$id = (string) $value])) { + return new Reference($this->aliases[$id], $value->getInvalidBehavior()); + } + + return parent::processValue($value, $isRoot); + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) { + if (null === $package = $tags[0]['package'] ?? null) { + throw new InvalidArgumentException(sprintf('The "package" attribute is mandatory for the "%s" tag on the "%s" service.', $this->tagName, $id)); + } + + if (null === $version = $tags[0]['version'] ?? null) { + throw new InvalidArgumentException(sprintf('The "version" attribute is mandatory for the "%s" tag on the "%s" service.', $this->tagName, $id)); + } + + $definition = $container->getDefinition($id); + if (!$definition->isPublic() || $definition->isPrivate()) { + continue; + } + + $container + ->setAlias($id, $aliasId = '.'.$this->tagName.'.'.$id) + ->setPublic(true) + ->setDeprecated($package, $version, 'Accessing the "%alias_id%" service directly from the container is deprecated, use dependency injection instead.'); + + $container->setDefinition($aliasId, $definition); + + $this->aliases[$id] = $aliasId; + } + + parent::process($container); + } +} diff --git a/lib/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php b/lib/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php index bff9d42079..b23303581d 100644 --- a/lib/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php +++ b/lib/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php @@ -12,13 +12,11 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\Exception\RuntimeException; -use Symfony\Component\DependencyInjection\ExpressionLanguage; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\ExpressionLanguage\Expression; /** * Run this pass before passes that need to know more about the relation of @@ -28,32 +26,28 @@ use Symfony\Component\ExpressionLanguage\Expression; * retrieve the graph in other passes from the compiler. * * @author Johannes M. Schmitt + * @author Nicolas Grekas */ -class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements RepeatablePassInterface +class AnalyzeServiceReferencesPass extends AbstractRecursivePass { private $graph; private $currentDefinition; private $onlyConstructorArguments; private $hasProxyDumper; private $lazy; - private $expressionLanguage; private $byConstructor; + private $byFactory; + private $definitions; + private $aliases; /** * @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls */ - public function __construct($onlyConstructorArguments = false, $hasProxyDumper = true) + public function __construct(bool $onlyConstructorArguments = false, bool $hasProxyDumper = true) { - $this->onlyConstructorArguments = (bool) $onlyConstructorArguments; - $this->hasProxyDumper = (bool) $hasProxyDumper; - } - - /** - * {@inheritdoc} - */ - public function setRepeatedPass(RepeatedPass $repeatedPass) - { - // no-op for BC + $this->onlyConstructorArguments = $onlyConstructorArguments; + $this->hasProxyDumper = $hasProxyDumper; + $this->enableExpressionProcessing(); } /** @@ -66,34 +60,37 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe $this->graph->clear(); $this->lazy = false; $this->byConstructor = false; + $this->byFactory = false; + $this->definitions = $container->getDefinitions(); + $this->aliases = $container->getAliases(); - foreach ($container->getAliases() as $id => $alias) { + foreach ($this->aliases as $id => $alias) { $targetId = $this->getDefinitionId((string) $alias); - $this->graph->connect($id, $alias, $targetId, $this->getDefinition($targetId), null); + $this->graph->connect($id, $alias, $targetId, null !== $targetId ? $this->container->getDefinition($targetId) : null, null); } - parent::process($container); + try { + parent::process($container); + } finally { + $this->aliases = $this->definitions = []; + } } - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { $lazy = $this->lazy; + $inExpression = $this->inExpression(); if ($value instanceof ArgumentInterface) { - $this->lazy = true; + $this->lazy = !$this->byFactory || !$value instanceof IteratorArgument; parent::processValue($value->getValues()); $this->lazy = $lazy; return $value; } - if ($value instanceof Expression) { - $this->getExpressionLanguage()->compile((string) $value, ['this' => 'container']); - - return $value; - } if ($value instanceof Reference) { $targetId = $this->getDefinitionId((string) $value); - $targetDefinition = $this->getDefinition($targetId); + $targetDefinition = null !== $targetId ? $this->container->getDefinition($targetId) : null; $this->graph->connect( $this->currentId, @@ -106,6 +103,18 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe $this->byConstructor ); + if ($inExpression) { + $this->graph->connect( + '.internal.reference_in_expression', + null, + $targetId, + $targetDefinition, + $value, + $this->lazy || ($targetDefinition && $targetDefinition->isLazy()), + true + ); + } + return $value; } if (!$value instanceof Definition) { @@ -123,13 +132,47 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe $byConstructor = $this->byConstructor; $this->byConstructor = $isRoot || $byConstructor; + + $byFactory = $this->byFactory; + $this->byFactory = true; $this->processValue($value->getFactory()); + $this->byFactory = $byFactory; $this->processValue($value->getArguments()); + + $properties = $value->getProperties(); + $setters = $value->getMethodCalls(); + + // Any references before a "wither" are part of the constructor-instantiation graph + $lastWitherIndex = null; + foreach ($setters as $k => $call) { + if ($call[2] ?? false) { + $lastWitherIndex = $k; + } + } + + if (null !== $lastWitherIndex) { + $this->processValue($properties); + $setters = $properties = []; + + foreach ($value->getMethodCalls() as $k => $call) { + if (null === $lastWitherIndex) { + $setters[] = $call; + continue; + } + + if ($lastWitherIndex === $k) { + $lastWitherIndex = null; + } + + $this->processValue($call); + } + } + $this->byConstructor = $byConstructor; if (!$this->onlyConstructorArguments) { - $this->processValue($value->getProperties()); - $this->processValue($value->getMethodCalls()); + $this->processValue($properties); + $this->processValue($setters); $this->processValue($value->getConfigurator()); } $this->lazy = $lazy; @@ -137,56 +180,12 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe return $value; } - /** - * Returns a service definition given the full name or an alias. - * - * @param string $id A full id or alias for a service definition - * - * @return Definition|null The definition related to the supplied id - */ - private function getDefinition($id) + private function getDefinitionId(string $id): ?string { - return null === $id ? null : $this->container->getDefinition($id); - } - - private function getDefinitionId($id) - { - while ($this->container->hasAlias($id)) { - $id = (string) $this->container->getAlias($id); + while (isset($this->aliases[$id])) { + $id = (string) $this->aliases[$id]; } - if (!$this->container->hasDefinition($id)) { - return null; - } - - return $this->container->normalizeId($id); - } - - private function getExpressionLanguage() - { - if (null === $this->expressionLanguage) { - if (!class_exists(ExpressionLanguage::class)) { - throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); - } - - $providers = $this->container->getExpressionLanguageProviders(); - $this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) { - if ('""' === substr_replace($arg, '', 1, -1)) { - $id = stripcslashes(substr($arg, 1, -1)); - $id = $this->getDefinitionId($id); - - $this->graph->connect( - $this->currentId, - $this->currentDefinition, - $id, - $this->getDefinition($id) - ); - } - - return sprintf('$this->get(%s)', $arg); - }); - } - - return $this->expressionLanguage; + return isset($this->definitions[$id]) ? $id : null; } } diff --git a/lib/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php b/lib/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php new file mode 100644 index 0000000000..4db7185cf5 --- /dev/null +++ b/lib/symfony/dependency-injection/Compiler/AttributeAutoconfigurationPass.php @@ -0,0 +1,168 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; + +/** + * @author Alexander M. Turek + */ +final class AttributeAutoconfigurationPass extends AbstractRecursivePass +{ + private $classAttributeConfigurators = []; + private $methodAttributeConfigurators = []; + private $propertyAttributeConfigurators = []; + private $parameterAttributeConfigurators = []; + + public function process(ContainerBuilder $container): void + { + if (80000 > \PHP_VERSION_ID || !$container->getAutoconfiguredAttributes()) { + return; + } + + foreach ($container->getAutoconfiguredAttributes() as $attributeName => $callable) { + $callableReflector = new \ReflectionFunction(\Closure::fromCallable($callable)); + if ($callableReflector->getNumberOfParameters() <= 2) { + $this->classAttributeConfigurators[$attributeName] = $callable; + continue; + } + + $reflectorParameter = $callableReflector->getParameters()[2]; + $parameterType = $reflectorParameter->getType(); + $types = []; + if ($parameterType instanceof \ReflectionUnionType) { + foreach ($parameterType->getTypes() as $type) { + $types[] = $type->getName(); + } + } elseif ($parameterType instanceof \ReflectionNamedType) { + $types[] = $parameterType->getName(); + } else { + throw new LogicException(sprintf('Argument "$%s" of attribute autoconfigurator should have a type, use one or more of "\ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter|\Reflector" in "%s" on line "%d".', $reflectorParameter->getName(), $callableReflector->getFileName(), $callableReflector->getStartLine())); + } + + try { + $attributeReflector = new \ReflectionClass($attributeName); + } catch (\ReflectionException $e) { + continue; + } + + $targets = $attributeReflector->getAttributes(\Attribute::class)[0] ?? 0; + $targets = $targets ? $targets->getArguments()[0] ?? -1 : 0; + + foreach (['class', 'method', 'property', 'parameter'] as $symbol) { + if (['Reflector'] !== $types) { + if (!\in_array('Reflection'.ucfirst($symbol), $types, true)) { + continue; + } + if (!($targets & \constant('Attribute::TARGET_'.strtoupper($symbol)))) { + throw new LogicException(sprintf('Invalid type "Reflection%s" on argument "$%s": attribute "%s" cannot target a '.$symbol.' in "%s" on line "%d".', ucfirst($symbol), $reflectorParameter->getName(), $attributeName, $callableReflector->getFileName(), $callableReflector->getStartLine())); + } + } + $this->{$symbol.'AttributeConfigurators'}[$attributeName] = $callable; + } + } + + parent::process($container); + } + + protected function processValue($value, bool $isRoot = false) + { + if (!$value instanceof Definition + || !$value->isAutoconfigured() + || $value->isAbstract() + || $value->hasTag('container.ignore_attributes') + || !($classReflector = $this->container->getReflectionClass($value->getClass(), false)) + ) { + return parent::processValue($value, $isRoot); + } + + $instanceof = $value->getInstanceofConditionals(); + $conditionals = $instanceof[$classReflector->getName()] ?? new ChildDefinition(''); + + if ($this->classAttributeConfigurators) { + foreach ($classReflector->getAttributes() as $attribute) { + if ($configurator = $this->classAttributeConfigurators[$attribute->getName()] ?? null) { + $configurator($conditionals, $attribute->newInstance(), $classReflector); + } + } + } + + if ($this->parameterAttributeConfigurators) { + try { + $constructorReflector = $this->getConstructor($value, false); + } catch (RuntimeException $e) { + $constructorReflector = null; + } + + if ($constructorReflector) { + foreach ($constructorReflector->getParameters() as $parameterReflector) { + foreach ($parameterReflector->getAttributes() as $attribute) { + if ($configurator = $this->parameterAttributeConfigurators[$attribute->getName()] ?? null) { + $configurator($conditionals, $attribute->newInstance(), $parameterReflector); + } + } + } + } + } + + if ($this->methodAttributeConfigurators || $this->parameterAttributeConfigurators) { + foreach ($classReflector->getMethods(\ReflectionMethod::IS_PUBLIC) as $methodReflector) { + if ($methodReflector->isStatic() || $methodReflector->isConstructor() || $methodReflector->isDestructor()) { + continue; + } + + if ($this->methodAttributeConfigurators) { + foreach ($methodReflector->getAttributes() as $attribute) { + if ($configurator = $this->methodAttributeConfigurators[$attribute->getName()] ?? null) { + $configurator($conditionals, $attribute->newInstance(), $methodReflector); + } + } + } + + if ($this->parameterAttributeConfigurators) { + foreach ($methodReflector->getParameters() as $parameterReflector) { + foreach ($parameterReflector->getAttributes() as $attribute) { + if ($configurator = $this->parameterAttributeConfigurators[$attribute->getName()] ?? null) { + $configurator($conditionals, $attribute->newInstance(), $parameterReflector); + } + } + } + } + } + } + + if ($this->propertyAttributeConfigurators) { + foreach ($classReflector->getProperties(\ReflectionProperty::IS_PUBLIC) as $propertyReflector) { + if ($propertyReflector->isStatic()) { + continue; + } + + foreach ($propertyReflector->getAttributes() as $attribute) { + if ($configurator = $this->propertyAttributeConfigurators[$attribute->getName()] ?? null) { + $configurator($conditionals, $attribute->newInstance(), $propertyReflector); + } + } + } + } + + if (!isset($instanceof[$classReflector->getName()]) && new ChildDefinition('') != $conditionals) { + $instanceof[$classReflector->getName()] = $conditionals; + $value->setInstanceofConditionals($instanceof); + } + + return parent::processValue($value, $isRoot); + } +} diff --git a/lib/symfony/dependency-injection/Compiler/AutoAliasServicePass.php b/lib/symfony/dependency-injection/Compiler/AutoAliasServicePass.php index 03420683a2..b150e70e6d 100644 --- a/lib/symfony/dependency-injection/Compiler/AutoAliasServicePass.php +++ b/lib/symfony/dependency-injection/Compiler/AutoAliasServicePass.php @@ -20,6 +20,8 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; */ class AutoAliasServicePass implements CompilerPassInterface { + private $privateAliases = []; + /** * {@inheritdoc} */ @@ -33,9 +35,26 @@ class AutoAliasServicePass implements CompilerPassInterface $aliasId = $container->getParameterBag()->resolveValue($tag['format']); if ($container->hasDefinition($aliasId) || $container->hasAlias($aliasId)) { - $container->setAlias($serviceId, new Alias($aliasId, true)); + $alias = new Alias($aliasId, $container->getDefinition($serviceId)->isPublic()); + $container->setAlias($serviceId, $alias); + + if (!$alias->isPublic()) { + $alias->setPublic(true); + $this->privateAliases[] = $alias; + } } } } } + + /** + * @internal to be removed in Symfony 6.0 + */ + public function getPrivateAliases(): array + { + $privateAliases = $this->privateAliases; + $this->privateAliases = []; + + return $privateAliases; + } } diff --git a/lib/symfony/dependency-injection/Compiler/AutowireExceptionPass.php b/lib/symfony/dependency-injection/Compiler/AutowireExceptionPass.php deleted file mode 100644 index 6a755025e2..0000000000 --- a/lib/symfony/dependency-injection/Compiler/AutowireExceptionPass.php +++ /dev/null @@ -1,74 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DependencyInjection\Compiler; - -@trigger_error('The '.__NAMESPACE__.'\AutowireExceptionPass class is deprecated since Symfony 3.4 and will be removed in 4.0. Use the DefinitionErrorExceptionPass class instead.', \E_USER_DEPRECATED); - -use Symfony\Component\DependencyInjection\ContainerBuilder; - -/** - * Throws autowire exceptions from AutowirePass for definitions that still exist. - * - * @deprecated since version 3.4, will be removed in 4.0. - * - * @author Ryan Weaver - */ -class AutowireExceptionPass implements CompilerPassInterface -{ - private $autowirePass; - private $inlineServicePass; - - public function __construct(AutowirePass $autowirePass, InlineServiceDefinitionsPass $inlineServicePass) - { - $this->autowirePass = $autowirePass; - $this->inlineServicePass = $inlineServicePass; - } - - public function process(ContainerBuilder $container) - { - // the pass should only be run once - if (null === $this->autowirePass || null === $this->inlineServicePass) { - return; - } - - $inlinedIds = $this->inlineServicePass->getInlinedServiceIds(); - $exceptions = $this->autowirePass->getAutowiringExceptions(); - - // free up references - $this->autowirePass = null; - $this->inlineServicePass = null; - - foreach ($exceptions as $exception) { - if ($this->doesServiceExistInTheContainer($exception->getServiceId(), $container, $inlinedIds)) { - throw $exception; - } - } - } - - private function doesServiceExistInTheContainer($serviceId, ContainerBuilder $container, array $inlinedIds) - { - if ($container->hasDefinition($serviceId)) { - return true; - } - - // was the service inlined? Of so, does its parent service exist? - if (isset($inlinedIds[$serviceId])) { - foreach ($inlinedIds[$serviceId] as $parentId) { - if ($this->doesServiceExistInTheContainer($parentId, $container, $inlinedIds)) { - return true; - } - } - } - - return false; - } -} diff --git a/lib/symfony/dependency-injection/Compiler/AutowirePass.php b/lib/symfony/dependency-injection/Compiler/AutowirePass.php index b1dae2a4f4..c2b80770c8 100644 --- a/lib/symfony/dependency-injection/Compiler/AutowirePass.php +++ b/lib/symfony/dependency-injection/Compiler/AutowirePass.php @@ -12,7 +12,11 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\Config\Resource\ClassExistenceResource; -use Symfony\Component\DependencyInjection\Config\AutowireServiceResource; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; +use Symfony\Component\DependencyInjection\Attribute\TaggedLocator; +use Symfony\Component\DependencyInjection\Attribute\Target; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException; @@ -28,33 +32,28 @@ use Symfony\Component\DependencyInjection\TypedReference; */ class AutowirePass extends AbstractRecursivePass { - private $definedTypes = []; private $types; private $ambiguousServiceTypes; - private $autowired = []; + private $autowiringAliases; private $lastFailure; private $throwOnAutowiringException; - private $autowiringExceptions = []; - private $strictMode; + private $decoratedClass; + private $decoratedId; + private $methodCalls; + private $defaultArgument; + private $getPreviousValue; + private $decoratedMethodIndex; + private $decoratedMethodArgumentIndex; + private $typesClone; + private $combinedAliases; - /** - * @param bool $throwOnAutowireException Errors can be retrieved via Definition::getErrors() - */ - public function __construct($throwOnAutowireException = true) + public function __construct(bool $throwOnAutowireException = true) { $this->throwOnAutowiringException = $throwOnAutowireException; - } - - /** - * @deprecated since version 3.4, to be removed in 4.0. - * - * @return AutowiringFailedException[] - */ - public function getAutowiringExceptions() - { - @trigger_error('Calling AutowirePass::getAutowiringExceptions() is deprecated since Symfony 3.4 and will be removed in 4.0. Use Definition::getErrors() instead.', \E_USER_DEPRECATED); - - return $this->autowiringExceptions; + $this->defaultArgument = new class() { + public $value; + public $names; + }; } /** @@ -62,46 +61,28 @@ class AutowirePass extends AbstractRecursivePass */ public function process(ContainerBuilder $container) { - // clear out any possibly stored exceptions from before - $this->autowiringExceptions = []; - $this->strictMode = $container->hasParameter('container.autowiring.strict_mode') && $container->getParameter('container.autowiring.strict_mode'); + $this->populateCombinedAliases($container); try { + $this->typesClone = clone $this; parent::process($container); } finally { - $this->definedTypes = []; - $this->types = null; - $this->ambiguousServiceTypes = null; - $this->autowired = []; + $this->decoratedClass = null; + $this->decoratedId = null; + $this->methodCalls = null; + $this->defaultArgument->names = null; + $this->getPreviousValue = null; + $this->decoratedMethodIndex = null; + $this->decoratedMethodArgumentIndex = null; + $this->typesClone = null; + $this->combinedAliases = []; } } - /** - * Creates a resource to help know if this service has changed. - * - * @return AutowireServiceResource - * - * @deprecated since version 3.3, to be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead. - */ - public static function createResourceForClass(\ReflectionClass $reflectionClass) - { - @trigger_error('The '.__METHOD__.'() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead.', \E_USER_DEPRECATED); - - $metadata = []; - - foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) { - if (!$reflectionMethod->isStatic()) { - $metadata[$reflectionMethod->name] = self::getResourceMetadataForMethod($reflectionMethod); - } - } - - return new AutowireServiceResource($reflectionClass->name, $reflectionClass->getFileName(), $metadata); - } - /** * {@inheritdoc} */ - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { try { return $this->doProcessValue($value, $isRoot); @@ -110,20 +91,30 @@ class AutowirePass extends AbstractRecursivePass throw $e; } - $this->autowiringExceptions[] = $e; - $this->container->getDefinition($this->currentId)->addError($e->getMessage()); + $this->container->getDefinition($this->currentId)->addError($e->getMessageCallback() ?? $e->getMessage()); return parent::processValue($value, $isRoot); } } - private function doProcessValue($value, $isRoot = false) + /** + * @return mixed + */ + private function doProcessValue($value, bool $isRoot = false) { if ($value instanceof TypedReference) { - if ($ref = $this->getAutowiredReference($value, $value->getRequiringClass() ? sprintf('for "%s" in "%s"', $value->getType(), $value->getRequiringClass()) : '')) { + if ($ref = $this->getAutowiredReference($value, true)) { return $ref; } - $this->container->log($this, $this->createTypeNotFoundMessage($value, 'it')); + if (ContainerBuilder::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) { + $message = $this->createTypeNotFoundMessageCallback($value, 'it'); + + // since the error message varies by referenced id and $this->currentId, so should the id of the dummy errored definition + $this->container->register($id = sprintf('.errored.%s.%s', $this->currentId, (string) $value), $value->getType()) + ->addError($message); + + return new TypedReference($id, $value->getType(), $value->getInvalidBehavior(), $value->getName()); + } } $value = parent::processValue($value, $isRoot); @@ -136,7 +127,7 @@ class AutowirePass extends AbstractRecursivePass return $value; } - $methodCalls = $value->getMethodCalls(); + $this->methodCalls = $value->getMethodCalls(); try { $constructor = $this->getConstructor($value, false); @@ -145,33 +136,41 @@ class AutowirePass extends AbstractRecursivePass } if ($constructor) { - array_unshift($methodCalls, [$constructor, $value->getArguments()]); + array_unshift($this->methodCalls, [$constructor, $value->getArguments()]); } - $methodCalls = $this->autowireCalls($reflectionClass, $methodCalls); + $checkAttributes = 80000 <= \PHP_VERSION_ID && !$value->hasTag('container.ignore_attributes'); + $this->methodCalls = $this->autowireCalls($reflectionClass, $isRoot, $checkAttributes); if ($constructor) { - list(, $arguments) = array_shift($methodCalls); + [, $arguments] = array_shift($this->methodCalls); if ($arguments !== $value->getArguments()) { $value->setArguments($arguments); } } - if ($methodCalls !== $value->getMethodCalls()) { - $value->setMethodCalls($methodCalls); + if ($this->methodCalls !== $value->getMethodCalls()) { + $value->setMethodCalls($this->methodCalls); } return $value; } - /** - * @return array - */ - private function autowireCalls(\ReflectionClass $reflectionClass, array $methodCalls) + private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot, bool $checkAttributes): array { - foreach ($methodCalls as $i => $call) { - list($method, $arguments) = $call; + $this->decoratedId = null; + $this->decoratedClass = null; + $this->getPreviousValue = null; + + if ($isRoot && ($definition = $this->container->getDefinition($this->currentId)) && null !== ($this->decoratedId = $definition->innerServiceId) && $this->container->has($this->decoratedId)) { + $this->decoratedClass = $this->container->findDefinition($this->decoratedId)->getClass(); + } + + $patchedIndexes = []; + + foreach ($this->methodCalls as $i => $call) { + [$method, $arguments] = $call; if ($method instanceof \ReflectionFunctionAbstract) { $reflectionMethod = $method; @@ -187,39 +186,86 @@ class AutowirePass extends AbstractRecursivePass } } - $arguments = $this->autowireMethod($reflectionMethod, $arguments); + $arguments = $this->autowireMethod($reflectionMethod, $arguments, $checkAttributes, $i); if ($arguments !== $call[1]) { - $methodCalls[$i][1] = $arguments; + $this->methodCalls[$i][1] = $arguments; + $patchedIndexes[] = $i; } } - return $methodCalls; + // use named arguments to skip complex default values + foreach ($patchedIndexes as $i) { + $namedArguments = null; + $arguments = $this->methodCalls[$i][1]; + + foreach ($arguments as $j => $value) { + if ($namedArguments && !$value instanceof $this->defaultArgument) { + unset($arguments[$j]); + $arguments[$namedArguments[$j]] = $value; + } + if ($namedArguments || !$value instanceof $this->defaultArgument) { + continue; + } + + if (\PHP_VERSION_ID >= 80100 && (\is_array($value->value) ? $value->value : \is_object($value->value))) { + unset($arguments[$j]); + $namedArguments = $value->names; + } else { + $arguments[$j] = $value->value; + } + } + + $this->methodCalls[$i][1] = $arguments; + } + + return $this->methodCalls; } /** * Autowires the constructor or a method. * - * @return array The autowired arguments - * * @throws AutowiringFailedException */ - private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments) + private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments, bool $checkAttributes, int $methodIndex): array { $class = $reflectionMethod instanceof \ReflectionMethod ? $reflectionMethod->class : $this->currentId; $method = $reflectionMethod->name; $parameters = $reflectionMethod->getParameters(); - if (method_exists('ReflectionMethod', 'isVariadic') && $reflectionMethod->isVariadic()) { + if ($reflectionMethod->isVariadic()) { array_pop($parameters); } + $this->defaultArgument->names = new \ArrayObject(); foreach ($parameters as $index => $parameter) { + $this->defaultArgument->names[$index] = $parameter->name; + if (\array_key_exists($index, $arguments) && '' !== $arguments[$index]) { continue; } $type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, true); + if ($checkAttributes) { + foreach ($parameter->getAttributes() as $attribute) { + if (TaggedIterator::class === $attribute->getName()) { + $attribute = $attribute->newInstance(); + $arguments[$index] = new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute, $attribute->defaultIndexMethod, false, $attribute->defaultPriorityMethod); + break; + } + + if (TaggedLocator::class === $attribute->getName()) { + $attribute = $attribute->newInstance(); + $arguments[$index] = new ServiceLocatorArgument(new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute, $attribute->defaultIndexMethod, true, $attribute->defaultPriorityMethod)); + break; + } + } + + if ('' !== ($arguments[$index] ?? '')) { + continue; + } + } + if (!$type) { if (isset($arguments[$index])) { continue; @@ -231,38 +277,62 @@ class AutowirePass extends AbstractRecursivePass // be false when isOptional() returns true. If the // argument *is* optional, allow it to be missing if ($parameter->isOptional()) { - continue; + --$index; + break; } $type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, false); - $type = $type ? sprintf('is type-hinted "%s"', $type) : 'has no type-hint'; + $type = $type ? sprintf('is type-hinted "%s"', ltrim($type, '\\')) : 'has no type-hint'; throw new AutowiringFailedException($this->currentId, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s()" %s, you should configure its value explicitly.', $this->currentId, $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method, $type)); } // specifically pass the default value - $arguments[$index] = $parameter->getDefaultValue(); + $arguments[$index] = clone $this->defaultArgument; + $arguments[$index]->value = $parameter->getDefaultValue(); continue; } - if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, !$parameter->isOptional() ? $class : ''), 'for '.sprintf('argument "$%s" of method "%s()"', $parameter->name, $class.'::'.$method))) { - $failureMessage = $this->createTypeNotFoundMessage($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method)); + $getValue = function () use ($type, $parameter, $class, $method) { + if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, Target::parseName($parameter)), true)) { + $failureMessage = $this->createTypeNotFoundMessageCallback($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method)); - if ($parameter->isDefaultValueAvailable()) { - $value = $parameter->getDefaultValue(); - } elseif (!$parameter->allowsNull()) { - throw new AutowiringFailedException($this->currentId, $failureMessage); + if ($parameter->isDefaultValueAvailable()) { + $value = clone $this->defaultArgument; + $value->value = $parameter->getDefaultValue(); + } elseif (!$parameter->allowsNull()) { + throw new AutowiringFailedException($this->currentId, $failureMessage); + } + } + + return $value; + }; + + if ($this->decoratedClass && $isDecorated = is_a($this->decoratedClass, $type, true)) { + if ($this->getPreviousValue) { + // The inner service is injected only if there is only 1 argument matching the type of the decorated class + // across all arguments of all autowired methods. + // If a second matching argument is found, the default behavior is restored. + + $getPreviousValue = $this->getPreviousValue; + $this->methodCalls[$this->decoratedMethodIndex][1][$this->decoratedMethodArgumentIndex] = $getPreviousValue(); + $this->decoratedClass = null; // Prevent further checks + } else { + $arguments[$index] = new TypedReference($this->decoratedId, $this->decoratedClass); + $this->getPreviousValue = $getValue; + $this->decoratedMethodIndex = $methodIndex; + $this->decoratedMethodArgumentIndex = $index; + + continue; } - $this->container->log($this, $failureMessage); } - $arguments[$index] = $value; + $arguments[$index] = $getValue(); } if ($parameters && !isset($arguments[++$index])) { while (0 <= --$index) { - $parameter = $parameters[$index]; - if (!$parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== $arguments[$index]) { + if (!$arguments[$index] instanceof $this->defaultArgument) { break; } unset($arguments[$index]); @@ -277,48 +347,49 @@ class AutowirePass extends AbstractRecursivePass } /** - * @return TypedReference|null A reference to the service matching the given type, if any + * Returns a reference to the service matching the given type, if any. */ - private function getAutowiredReference(TypedReference $reference, $deprecationMessage) + private function getAutowiredReference(TypedReference $reference, bool $filterType): ?TypedReference { $this->lastFailure = null; $type = $reference->getType(); - if ($type !== $this->container->normalizeId($reference) || ($this->container->has($type) && !$this->container->findDefinition($type)->isAbstract())) { + if ($type !== (string) $reference) { return $reference; } - if (null === $this->types) { - $this->populateAvailableTypes($this->strictMode); + if ($filterType && false !== $m = strpbrk($type, '&|')) { + $types = array_diff(explode($m[0], $type), ['int', 'string', 'array', 'bool', 'float', 'iterable', 'object', 'callable', 'null']); + + sort($types); + + $type = implode($m[0], $types); } - if (isset($this->definedTypes[$type])) { - return new TypedReference($this->types[$type], $type); - } - - if (!$this->strictMode && isset($this->types[$type])) { - $message = 'Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won\'t be supported in version 4.0.'; - if ($aliasSuggestion = $this->getAliasesSuggestionForType($type = $reference->getType(), $deprecationMessage)) { - $message .= ' '.$aliasSuggestion; - } else { - $message .= sprintf(' You should %s the "%s" service to "%s" instead.', isset($this->types[$this->types[$type]]) ? 'alias' : 'rename (or alias)', $this->types[$type], $type); + if (null !== $name = $reference->getName()) { + if ($this->container->has($alias = $type.' $'.$name) && !$this->container->findDefinition($alias)->isAbstract()) { + return new TypedReference($alias, $type, $reference->getInvalidBehavior()); } - @trigger_error($message, \E_USER_DEPRECATED); + if (null !== ($alias = $this->combinedAliases[$alias] ?? null) && !$this->container->findDefinition($alias)->isAbstract()) { + return new TypedReference($alias, $type, $reference->getInvalidBehavior()); + } - return new TypedReference($this->types[$type], $type); + if ($this->container->has($name) && !$this->container->findDefinition($name)->isAbstract()) { + foreach ($this->container->getAliases() + $this->combinedAliases as $id => $alias) { + if ($name === (string) $alias && str_starts_with($id, $type.' $')) { + return new TypedReference($name, $type, $reference->getInvalidBehavior()); + } + } + } } - if (!$reference->canBeAutoregistered() || isset($this->types[$type]) || isset($this->ambiguousServiceTypes[$type])) { - return null; + if ($this->container->has($type) && !$this->container->findDefinition($type)->isAbstract()) { + return new TypedReference($type, $type, $reference->getInvalidBehavior()); } - if (isset($this->autowired[$type])) { - return $this->autowired[$type] ? new TypedReference($this->autowired[$type], $type) : null; - } - - if (!$this->strictMode) { - return $this->createAutowiredDefinition($type); + if (null !== ($alias = $this->combinedAliases[$type] ?? null) && !$this->container->findDefinition($alias)->isAbstract()) { + return new TypedReference($alias, $type, $reference->getInvalidBehavior()); } return null; @@ -327,41 +398,32 @@ class AutowirePass extends AbstractRecursivePass /** * Populates the list of available types. */ - private function populateAvailableTypes($onlyAutowiringTypes = false) + private function populateAvailableTypes(ContainerBuilder $container) { $this->types = []; - if (!$onlyAutowiringTypes) { - $this->ambiguousServiceTypes = []; + $this->ambiguousServiceTypes = []; + $this->autowiringAliases = []; + + foreach ($container->getDefinitions() as $id => $definition) { + $this->populateAvailableType($container, $id, $definition); } - foreach ($this->container->getDefinitions() as $id => $definition) { - $this->populateAvailableType($id, $definition, $onlyAutowiringTypes); + foreach ($container->getAliases() as $id => $alias) { + $this->populateAutowiringAlias($id); } } /** * Populates the list of available types for a given definition. - * - * @param string $id */ - private function populateAvailableType($id, Definition $definition, $onlyAutowiringTypes) + private function populateAvailableType(ContainerBuilder $container, string $id, Definition $definition) { // Never use abstract services if ($definition->isAbstract()) { return; } - foreach ($definition->getAutowiringTypes(false) as $type) { - $this->definedTypes[$type] = true; - $this->types[$type] = $id; - unset($this->ambiguousServiceTypes[$type]); - } - - if ($onlyAutowiringTypes) { - return; - } - - if (preg_match('/^\d+_[^~]++~[._a-zA-Z\d]{7}$/', $id) || $definition->isDeprecated() || !$reflectionClass = $this->container->getReflectionClass($definition->getClass(), false)) { + if ('' === $id || '.' === $id[0] || $definition->isDeprecated() || !$reflectionClass = $container->getReflectionClass($definition->getClass(), false)) { return; } @@ -372,20 +434,15 @@ class AutowirePass extends AbstractRecursivePass do { $this->set($reflectionClass->name, $id); } while ($reflectionClass = $reflectionClass->getParentClass()); + + $this->populateAutowiringAlias($id); } /** * Associates a type and a service id if applicable. - * - * @param string $type - * @param string $id */ - private function set($type, $id) + private function set(string $type, string $id) { - if (isset($this->definedTypes[$type])) { - return; - } - // is this already a type/class that is known to match multiple services? if (isset($this->ambiguousServiceTypes[$type])) { $this->ambiguousServiceTypes[$type][] = $id; @@ -408,62 +465,24 @@ class AutowirePass extends AbstractRecursivePass $this->ambiguousServiceTypes[$type][] = $id; } - /** - * Registers a definition for the type if possible or throws an exception. - * - * @param string $type - * - * @return TypedReference|null A reference to the registered definition - */ - private function createAutowiredDefinition($type) + private function createTypeNotFoundMessageCallback(TypedReference $reference, string $label): \Closure { - if (!($typeHint = $this->container->getReflectionClass($type, false)) || !$typeHint->isInstantiable()) { - return null; + if (null === $this->typesClone->container) { + $this->typesClone->container = new ContainerBuilder($this->container->getParameterBag()); + $this->typesClone->container->setAliases($this->container->getAliases()); + $this->typesClone->container->setDefinitions($this->container->getDefinitions()); + $this->typesClone->container->setResourceTracking(false); } - $currentId = $this->currentId; - $this->currentId = $type; - $this->autowired[$type] = $argumentId = sprintf('autowired.%s', $type); - $argumentDefinition = new Definition($type); - $argumentDefinition->setPublic(false); - $argumentDefinition->setAutowired(true); - try { - $originalThrowSetting = $this->throwOnAutowiringException; - $this->throwOnAutowiringException = true; - $this->processValue($argumentDefinition, true); - $this->container->setDefinition($argumentId, $argumentDefinition); - } catch (AutowiringFailedException $e) { - $this->autowired[$type] = false; - $this->lastFailure = $e->getMessage(); - $this->container->log($this, $this->lastFailure); - - return null; - } finally { - $this->throwOnAutowiringException = $originalThrowSetting; - $this->currentId = $currentId; - } - - @trigger_error(sprintf('Relying on service auto-registration for type "%s" is deprecated since Symfony 3.4 and won\'t be supported in 4.0. Create a service named "%s" instead.', $type, $type), \E_USER_DEPRECATED); - - $this->container->log($this, sprintf('Type "%s" has been auto-registered for service "%s".', $type, $this->currentId)); - - return new TypedReference($argumentId, $type); + return (function () use ($reference, $label, $currentId) { + return $this->createTypeNotFoundMessage($reference, $label, $currentId); + })->bindTo($this->typesClone); } - private function createTypeNotFoundMessage(TypedReference $reference, $label) + private function createTypeNotFoundMessage(TypedReference $reference, string $label, string $currentId): string { - $trackResources = $this->container->isTrackingResources(); - $this->container->setResourceTracking(false); - try { - if ($r = $this->container->getReflectionClass($type = $reference->getType(), false)) { - $alternatives = $this->createTypeAlternatives($reference); - } - } finally { - $this->container->setResourceTracking($trackResources); - } - - if (!$r) { + if (!$r = $this->container->getReflectionClass($type = $reference->getType(), false)) { // either $type does not exist or a parent class does not exist try { $resource = new ClassExistenceResource($type, false); @@ -476,6 +495,7 @@ class AutowirePass extends AbstractRecursivePass $message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ? sprintf('is missing a parent class (%s)', $parentMsg) : 'was not found'); } else { + $alternatives = $this->createTypeAlternatives($this->container, $reference); $message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists'; $message = sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $alternatives); @@ -484,7 +504,7 @@ class AutowirePass extends AbstractRecursivePass } } - $message = sprintf('Cannot autowire service "%s": %s %s', $this->currentId, $label, $message); + $message = sprintf('Cannot autowire service "%s": %s %s', $currentId, $label, $message); if (null !== $this->lastFailure) { $message = $this->lastFailure."\n".$message; @@ -494,22 +514,27 @@ class AutowirePass extends AbstractRecursivePass return $message; } - private function createTypeAlternatives(TypedReference $reference) + private function createTypeAlternatives(ContainerBuilder $container, TypedReference $reference): string { // try suggesting available aliases first - if ($message = $this->getAliasesSuggestionForType($type = $reference->getType())) { + if ($message = $this->getAliasesSuggestionForType($container, $type = $reference->getType())) { return ' '.$message; } if (null === $this->ambiguousServiceTypes) { - $this->populateAvailableTypes(); + $this->populateAvailableTypes($container); } - if (isset($this->ambiguousServiceTypes[$type])) { + $servicesAndAliases = $container->getServiceIds(); + if (null !== ($autowiringAliases = $this->autowiringAliases[$type] ?? null) && !isset($autowiringAliases[''])) { + return sprintf(' Available autowiring aliases for this %s are: "$%s".', class_exists($type, false) ? 'class' : 'interface', implode('", "$', $autowiringAliases)); + } + + if (!$container->has($type) && false !== $key = array_search(strtolower($type), array_map('strtolower', $servicesAndAliases))) { + return sprintf(' Did you mean "%s"?', $servicesAndAliases[$key]); + } elseif (isset($this->ambiguousServiceTypes[$type])) { $message = sprintf('one of these existing services: "%s"', implode('", "', $this->ambiguousServiceTypes[$type])); } elseif (isset($this->types[$type])) { $message = sprintf('the existing "%s" service', $this->types[$type]); - } elseif ($reference->getRequiringClass() && !$reference->canBeAutoregistered() && !$this->strictMode) { - return ' It cannot be auto-registered because it is from a different root namespace.'; } else { return ''; } @@ -517,52 +542,17 @@ class AutowirePass extends AbstractRecursivePass return sprintf(' You should maybe alias this %s to %s.', class_exists($type, false) ? 'class' : 'interface', $message); } - /** - * @deprecated since version 3.3, to be removed in 4.0. - */ - private static function getResourceMetadataForMethod(\ReflectionMethod $method) - { - $methodArgumentsMetadata = []; - foreach ($method->getParameters() as $parameter) { - try { - if (method_exists($parameter, 'getType')) { - $type = $parameter->getType(); - if ($type && !$type->isBuiltin()) { - $class = new \ReflectionClass($type instanceof \ReflectionNamedType ? $type->getName() : (string) $type); - } else { - $class = null; - } - } else { - $class = $parameter->getClass(); - } - } catch (\ReflectionException $e) { - // type-hint is against a non-existent class - $class = false; - } - - $isVariadic = method_exists($parameter, 'isVariadic') && $parameter->isVariadic(); - $methodArgumentsMetadata[] = [ - 'class' => $class, - 'isOptional' => $parameter->isOptional(), - 'defaultValue' => ($parameter->isOptional() && !$isVariadic) ? $parameter->getDefaultValue() : null, - ]; - } - - return $methodArgumentsMetadata; - } - - private function getAliasesSuggestionForType($type, $extraContext = null) + private function getAliasesSuggestionForType(ContainerBuilder $container, string $type): ?string { $aliases = []; foreach (class_parents($type) + class_implements($type) as $parent) { - if ($this->container->has($parent) && !$this->container->findDefinition($parent)->isAbstract()) { + if ($container->has($parent) && !$container->findDefinition($parent)->isAbstract()) { $aliases[] = $parent; } } - $extraContext = $extraContext ? ' '.$extraContext : ''; if (1 < $len = \count($aliases)) { - $message = sprintf('Try changing the type-hint%s to one of its parents: ', $extraContext); + $message = 'Try changing the type-hint to one of its parents: '; for ($i = 0, --$len; $i < $len; ++$i) { $message .= sprintf('%s "%s", ', class_exists($aliases[$i], false) ? 'class' : 'interface', $aliases[$i]); } @@ -572,9 +562,64 @@ class AutowirePass extends AbstractRecursivePass } if ($aliases) { - return sprintf('Try changing the type-hint%s to "%s" instead.', $extraContext, $aliases[0]); + return sprintf('Try changing the type-hint to "%s" instead.', $aliases[0]); } return null; } + + private function populateAutowiringAlias(string $id): void + { + if (!preg_match('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))^((?&V)(?:\\\\(?&V))*+)(?: \$((?&V)))?$/', $id, $m)) { + return; + } + + $type = $m[2]; + $name = $m[3] ?? ''; + + if (class_exists($type, false) || interface_exists($type, false)) { + $this->autowiringAliases[$type][$name] = $name; + } + } + + private function populateCombinedAliases(ContainerBuilder $container): void + { + $this->combinedAliases = []; + $reverseAliases = []; + + foreach ($container->getAliases() as $id => $alias) { + if (!preg_match('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))^((?&V)(?:\\\\(?&V))*+)(?: \$((?&V)))?$/', $id, $m)) { + continue; + } + + $type = $m[2]; + $name = $m[3] ?? ''; + $reverseAliases[(string) $alias][$name][] = $type; + } + + foreach ($reverseAliases as $alias => $names) { + foreach ($names as $name => $types) { + if (2 > $count = \count($types)) { + continue; + } + sort($types); + $i = 1 << $count; + + // compute the powerset of the list of types + while ($i--) { + $set = []; + for ($j = 0; $j < $count; ++$j) { + if ($i & (1 << $j)) { + $set[] = $types[$j]; + } + } + + if (2 <= \count($set)) { + $this->combinedAliases[implode('&', $set).('' === $name ? '' : ' $'.$name)] = $alias; + $this->combinedAliases[implode('|', $set).('' === $name ? '' : ' $'.$name)] = $alias; + } + } + } + } + } } diff --git a/lib/symfony/dependency-injection/Compiler/AutowireRequiredMethodsPass.php b/lib/symfony/dependency-injection/Compiler/AutowireRequiredMethodsPass.php index efb9df7b94..5c255cfb60 100644 --- a/lib/symfony/dependency-injection/Compiler/AutowireRequiredMethodsPass.php +++ b/lib/symfony/dependency-injection/Compiler/AutowireRequiredMethodsPass.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Contracts\Service\Attribute\Required; /** * Looks for definitions with autowiring enabled and registers their corresponding "@required" methods as setters. @@ -23,7 +24,7 @@ class AutowireRequiredMethodsPass extends AbstractRecursivePass /** * {@inheritdoc} */ - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { $value = parent::processValue($value, $isRoot); @@ -35,8 +36,9 @@ class AutowireRequiredMethodsPass extends AbstractRecursivePass } $alreadyCalledMethods = []; + $withers = []; - foreach ($value->getMethodCalls() as list($method)) { + foreach ($value->getMethodCalls() as [$method]) { $alreadyCalledMethods[strtolower($method)] = true; } @@ -48,9 +50,21 @@ class AutowireRequiredMethodsPass extends AbstractRecursivePass } while (true) { + if (\PHP_VERSION_ID >= 80000 && $r->getAttributes(Required::class)) { + if ($this->isWither($r, $r->getDocComment() ?: '')) { + $withers[] = [$r->name, [], true]; + } else { + $value->addMethodCall($r->name, []); + } + break; + } if (false !== $doc = $r->getDocComment()) { if (false !== stripos($doc, '@required') && preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) { - $value->addMethodCall($reflectionMethod->name); + if ($this->isWither($reflectionMethod, $doc)) { + $withers[] = [$reflectionMethod->name, [], true]; + } else { + $value->addMethodCall($reflectionMethod->name, []); + } break; } if (false === stripos($doc, '@inheritdoc') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+(?:\{@inheritdoc\}|@inheritdoc)(?:\s|\*/$)#i', $doc)) { @@ -65,6 +79,31 @@ class AutowireRequiredMethodsPass extends AbstractRecursivePass } } + if ($withers) { + // Prepend withers to prevent creating circular loops + $setters = $value->getMethodCalls(); + $value->setMethodCalls($withers); + foreach ($setters as $call) { + $value->addMethodCall($call[0], $call[1], $call[2] ?? false); + } + } + return $value; } + + private function isWither(\ReflectionMethod $reflectionMethod, string $doc): bool + { + $match = preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@return\s++(static|\$this)[\s\*]#i', $doc, $matches); + if ($match && 'static' === $matches[1]) { + return true; + } + + if ($match && '$this' === $matches[1]) { + return false; + } + + $reflectionType = $reflectionMethod->hasReturnType() ? $reflectionMethod->getReturnType() : null; + + return $reflectionType instanceof \ReflectionNamedType && 'static' === $reflectionType->getName(); + } } diff --git a/lib/symfony/dependency-injection/Compiler/AutowireRequiredPropertiesPass.php b/lib/symfony/dependency-injection/Compiler/AutowireRequiredPropertiesPass.php new file mode 100644 index 0000000000..52024b8074 --- /dev/null +++ b/lib/symfony/dependency-injection/Compiler/AutowireRequiredPropertiesPass.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Contracts\Service\Attribute\Required; + +/** + * Looks for definitions with autowiring enabled and registers their corresponding "@required" properties. + * + * @author Sebastien Morel (Plopix) + * @author Nicolas Grekas + */ +class AutowireRequiredPropertiesPass extends AbstractRecursivePass +{ + /** + * {@inheritdoc} + */ + protected function processValue($value, bool $isRoot = false) + { + if (\PHP_VERSION_ID < 70400) { + return $value; + } + $value = parent::processValue($value, $isRoot); + + if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) { + return $value; + } + if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) { + return $value; + } + + $properties = $value->getProperties(); + foreach ($reflectionClass->getProperties() as $reflectionProperty) { + if (!($type = $reflectionProperty->getType()) instanceof \ReflectionNamedType) { + continue; + } + if ((\PHP_VERSION_ID < 80000 || !$reflectionProperty->getAttributes(Required::class)) + && ((false === $doc = $reflectionProperty->getDocComment()) || false === stripos($doc, '@required') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) + ) { + continue; + } + if (\array_key_exists($name = $reflectionProperty->getName(), $properties)) { + continue; + } + + $type = $type->getName(); + $value->setProperty($name, new TypedReference($type, $type, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $name)); + } + + return $value; + } +} diff --git a/lib/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php b/lib/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php index 30a6f524ad..93808b201d 100644 --- a/lib/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php +++ b/lib/symfony/dependency-injection/Compiler/CheckArgumentsValidityPass.php @@ -24,7 +24,7 @@ class CheckArgumentsValidityPass extends AbstractRecursivePass { private $throwExceptions; - public function __construct($throwExceptions = true) + public function __construct(bool $throwExceptions = true) { $this->throwExceptions = $throwExceptions; } @@ -32,14 +32,20 @@ class CheckArgumentsValidityPass extends AbstractRecursivePass /** * {@inheritdoc} */ - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { if (!$value instanceof Definition) { return parent::processValue($value, $isRoot); } $i = 0; + $hasNamedArgs = false; foreach ($value->getArguments() as $k => $v) { + if (\PHP_VERSION_ID >= 80000 && preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $k)) { + $hasNamedArgs = true; + continue; + } + if ($k !== $i++) { if (!\is_int($k)) { $msg = sprintf('Invalid constructor argument for service "%s": integer expected but found string "%s". Check your service definition.', $this->currentId, $k); @@ -57,11 +63,27 @@ class CheckArgumentsValidityPass extends AbstractRecursivePass throw new RuntimeException($msg); } } + + if ($hasNamedArgs) { + $msg = sprintf('Invalid constructor argument for service "%s": cannot use positional argument after named argument. Check your service definition.', $this->currentId); + $value->addError($msg); + if ($this->throwExceptions) { + throw new RuntimeException($msg); + } + + break; + } } foreach ($value->getMethodCalls() as $methodCall) { $i = 0; + $hasNamedArgs = false; foreach ($methodCall[1] as $k => $v) { + if (\PHP_VERSION_ID >= 80000 && preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $k)) { + $hasNamedArgs = true; + continue; + } + if ($k !== $i++) { if (!\is_int($k)) { $msg = sprintf('Invalid argument for method call "%s" of service "%s": integer expected but found string "%s". Check your service definition.', $methodCall[0], $this->currentId, $k); @@ -79,6 +101,16 @@ class CheckArgumentsValidityPass extends AbstractRecursivePass throw new RuntimeException($msg); } } + + if ($hasNamedArgs) { + $msg = sprintf('Invalid argument for method call "%s" of service "%s": cannot use positional argument after named argument. Check your service definition.', $methodCall[0], $this->currentId); + $value->addError($msg); + if ($this->throwExceptions) { + throw new RuntimeException($msg); + } + + break; + } } } diff --git a/lib/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php b/lib/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php index 4b6d277fe9..68c42ae481 100644 --- a/lib/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php +++ b/lib/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php @@ -14,6 +14,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\EnvParameterException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Loader\FileLoader; /** * This pass validates each definition individually only taking the information @@ -38,17 +39,17 @@ class CheckDefinitionValidityPass implements CompilerPassInterface { foreach ($container->getDefinitions() as $id => $definition) { // synthetic service is public - if ($definition->isSynthetic() && !($definition->isPublic() || $definition->isPrivate())) { + if ($definition->isSynthetic() && !$definition->isPublic()) { throw new RuntimeException(sprintf('A synthetic service ("%s") must be public.', $id)); } // non-synthetic, non-abstract service has class - if (!$definition->isAbstract() && !$definition->isSynthetic() && !$definition->getClass()) { + if (!$definition->isAbstract() && !$definition->isSynthetic() && !$definition->getClass() && !$definition->hasTag('container.service_locator') && (!$definition->getFactory() || !preg_match(FileLoader::ANONYMOUS_ID_REGEXP, $id))) { if ($definition->getFactory()) { throw new RuntimeException(sprintf('Please add the class to service "%s" even if it is constructed by a factory since we might need to add method calls based on compile-time checks.', $id)); } if (class_exists($id) || interface_exists($id, false)) { - if (0 === strpos($id, '\\') && 1 < substr_count($id, '\\')) { + if (str_starts_with($id, '\\') && 1 < substr_count($id, '\\')) { throw new RuntimeException(sprintf('The definition for "%s" has no class attribute, and appears to reference a class or interface. Please specify the class attribute explicitly or remove the leading backslash by renaming the service to "%s" to get rid of this error.', $id, substr($id, 1))); } @@ -62,7 +63,7 @@ class CheckDefinitionValidityPass implements CompilerPassInterface foreach ($definition->getTags() as $name => $tags) { foreach ($tags as $attributes) { foreach ($attributes as $attribute => $value) { - if (!is_scalar($value) && null !== $value) { + if (!\is_scalar($value) && null !== $value) { throw new RuntimeException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s".', $id, $name, $attribute)); } } diff --git a/lib/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php b/lib/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php index 77b35f1866..fd3173831d 100644 --- a/lib/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php +++ b/lib/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\Reference; @@ -22,15 +23,83 @@ use Symfony\Component\DependencyInjection\Reference; */ class CheckExceptionOnInvalidReferenceBehaviorPass extends AbstractRecursivePass { - protected function processValue($value, $isRoot = false) + private $serviceLocatorContextIds = []; + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $this->serviceLocatorContextIds = []; + foreach ($container->findTaggedServiceIds('container.service_locator_context') as $id => $tags) { + $this->serviceLocatorContextIds[$id] = $tags[0]['id']; + $container->getDefinition($id)->clearTag('container.service_locator_context'); + } + + try { + return parent::process($container); + } finally { + $this->serviceLocatorContextIds = []; + } + } + + protected function processValue($value, bool $isRoot = false) { if (!$value instanceof Reference) { return parent::processValue($value, $isRoot); } - if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior() && !$this->container->has($id = (string) $value)) { - throw new ServiceNotFoundException($id, $this->currentId); + if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $value->getInvalidBehavior() || $this->container->has($id = (string) $value)) { + return $value; } - return $value; + $currentId = $this->currentId; + $graph = $this->container->getCompiler()->getServiceReferenceGraph(); + + if (isset($this->serviceLocatorContextIds[$currentId])) { + $currentId = $this->serviceLocatorContextIds[$currentId]; + $locator = $this->container->getDefinition($this->currentId)->getFactory()[0]; + + foreach ($locator->getArgument(0) as $k => $v) { + if ($v->getValues()[0] === $value) { + if ($k !== $id) { + $currentId = $k.'" in the container provided to "'.$currentId; + } + throw new ServiceNotFoundException($id, $currentId, null, $this->getAlternatives($id)); + } + } + } + + if ('.' === $currentId[0] && $graph->hasNode($currentId)) { + foreach ($graph->getNode($currentId)->getInEdges() as $edge) { + if (!$edge->getValue() instanceof Reference || ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $edge->getValue()->getInvalidBehavior()) { + continue; + } + $sourceId = $edge->getSourceNode()->getId(); + + if ('.' !== $sourceId[0]) { + $currentId = $sourceId; + break; + } + } + } + + throw new ServiceNotFoundException($id, $currentId, null, $this->getAlternatives($id)); + } + + private function getAlternatives(string $id): array + { + $alternatives = []; + foreach ($this->container->getServiceIds() as $knownId) { + if ('' === $knownId || '.' === $knownId[0]) { + continue; + } + + $lev = levenshtein($id, $knownId); + if ($lev <= \strlen($id) / 3 || false !== strpos($knownId, $id)) { + $alternatives[] = $knownId; + } + } + + return $alternatives; } } diff --git a/lib/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php b/lib/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php index 8f2a3bdf70..0349ef7616 100644 --- a/lib/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php +++ b/lib/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php @@ -25,7 +25,7 @@ use Symfony\Component\DependencyInjection\Reference; */ class CheckReferenceValidityPass extends AbstractRecursivePass { - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { if ($isRoot && $value instanceof Definition && ($value->isSynthetic() || $value->isAbstract())) { return $value; diff --git a/lib/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php b/lib/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php new file mode 100644 index 0000000000..b7ec85cefb --- /dev/null +++ b/lib/symfony/dependency-injection/Compiler/CheckTypeDeclarationsPass.php @@ -0,0 +1,329 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\InvalidParameterTypeException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\ExpressionLanguage; +use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\ExpressionLanguage\Expression; + +/** + * Checks whether injected parameters are compatible with type declarations. + * + * This pass should be run after all optimization passes. + * + * It can be added either: + * * before removing passes to check all services even if they are not currently used, + * * after removing passes to check only services are used in the app. + * + * @author Nicolas Grekas + * @author Julien Maulny + */ +final class CheckTypeDeclarationsPass extends AbstractRecursivePass +{ + private const SCALAR_TYPES = [ + 'int' => true, + 'float' => true, + 'bool' => true, + 'string' => true, + ]; + + private const BUILTIN_TYPES = [ + 'array' => true, + 'bool' => true, + 'callable' => true, + 'float' => true, + 'int' => true, + 'iterable' => true, + 'object' => true, + 'string' => true, + ]; + + private $autoload; + private $skippedIds; + + private $expressionLanguage; + + /** + * @param bool $autoload Whether services who's class in not loaded should be checked or not. + * Defaults to false to save loading code during compilation. + * @param array $skippedIds An array indexed by the service ids to skip + */ + public function __construct(bool $autoload = false, array $skippedIds = []) + { + $this->autoload = $autoload; + $this->skippedIds = $skippedIds; + } + + /** + * {@inheritdoc} + */ + protected function processValue($value, bool $isRoot = false) + { + if (isset($this->skippedIds[$this->currentId])) { + return $value; + } + + if (!$value instanceof Definition || $value->hasErrors() || $value->isDeprecated()) { + return parent::processValue($value, $isRoot); + } + + if (!$this->autoload) { + if (!$class = $value->getClass()) { + return parent::processValue($value, $isRoot); + } + if (!class_exists($class, false) && !interface_exists($class, false)) { + return parent::processValue($value, $isRoot); + } + } + + if (ServiceLocator::class === $value->getClass()) { + return parent::processValue($value, $isRoot); + } + + if ($constructor = $this->getConstructor($value, false)) { + $this->checkTypeDeclarations($value, $constructor, $value->getArguments()); + } + + foreach ($value->getMethodCalls() as $methodCall) { + try { + $reflectionMethod = $this->getReflectionMethod($value, $methodCall[0]); + } catch (RuntimeException $e) { + if ($value->getFactory()) { + continue; + } + + throw $e; + } + + $this->checkTypeDeclarations($value, $reflectionMethod, $methodCall[1]); + } + + return parent::processValue($value, $isRoot); + } + + /** + * @throws InvalidArgumentException When not enough parameters are defined for the method + */ + private function checkTypeDeclarations(Definition $checkedDefinition, \ReflectionFunctionAbstract $reflectionFunction, array $values): void + { + $numberOfRequiredParameters = $reflectionFunction->getNumberOfRequiredParameters(); + + if (\count($values) < $numberOfRequiredParameters) { + throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": "%s::%s()" requires %d arguments, %d passed.', $this->currentId, $reflectionFunction->class, $reflectionFunction->name, $numberOfRequiredParameters, \count($values))); + } + + $reflectionParameters = $reflectionFunction->getParameters(); + $checksCount = min($reflectionFunction->getNumberOfParameters(), \count($values)); + + $envPlaceholderUniquePrefix = $this->container->getParameterBag() instanceof EnvPlaceholderParameterBag ? $this->container->getParameterBag()->getEnvPlaceholderUniquePrefix() : null; + + for ($i = 0; $i < $checksCount; ++$i) { + if (!$reflectionParameters[$i]->hasType() || $reflectionParameters[$i]->isVariadic()) { + continue; + } + + $this->checkType($checkedDefinition, $values[$i], $reflectionParameters[$i], $envPlaceholderUniquePrefix); + } + + if ($reflectionFunction->isVariadic() && ($lastParameter = end($reflectionParameters))->hasType()) { + $variadicParameters = \array_slice($values, $lastParameter->getPosition()); + + foreach ($variadicParameters as $variadicParameter) { + $this->checkType($checkedDefinition, $variadicParameter, $lastParameter, $envPlaceholderUniquePrefix); + } + } + } + + /** + * @throws InvalidParameterTypeException When a parameter is not compatible with the declared type + */ + private function checkType(Definition $checkedDefinition, $value, \ReflectionParameter $parameter, ?string $envPlaceholderUniquePrefix, \ReflectionType $reflectionType = null): void + { + $reflectionType = $reflectionType ?? $parameter->getType(); + + if ($reflectionType instanceof \ReflectionUnionType) { + foreach ($reflectionType->getTypes() as $t) { + try { + $this->checkType($checkedDefinition, $value, $parameter, $envPlaceholderUniquePrefix, $t); + + return; + } catch (InvalidParameterTypeException $e) { + } + } + + throw new InvalidParameterTypeException($this->currentId, $e->getCode(), $parameter); + } + if ($reflectionType instanceof \ReflectionIntersectionType) { + foreach ($reflectionType->getTypes() as $t) { + $this->checkType($checkedDefinition, $value, $parameter, $envPlaceholderUniquePrefix, $t); + } + + return; + } + if (!$reflectionType instanceof \ReflectionNamedType) { + return; + } + + $type = $reflectionType->getName(); + + if ($value instanceof Reference) { + if (!$this->container->has($value = (string) $value)) { + return; + } + + if ('service_container' === $value && is_a($type, Container::class, true)) { + return; + } + + $value = $this->container->findDefinition($value); + } + + if ('self' === $type) { + $type = $parameter->getDeclaringClass()->getName(); + } + + if ('static' === $type) { + $type = $checkedDefinition->getClass(); + } + + $class = null; + + if ($value instanceof Definition) { + if ($value->getFactory()) { + return; + } + + $class = $value->getClass(); + + if ($class && isset(self::BUILTIN_TYPES[strtolower($class)])) { + $class = strtolower($class); + } elseif (!$class || (!$this->autoload && !class_exists($class, false) && !interface_exists($class, false))) { + return; + } + } elseif ($value instanceof Parameter) { + $value = $this->container->getParameter($value); + } elseif ($value instanceof Expression) { + try { + $value = $this->getExpressionLanguage()->evaluate($value, ['container' => $this->container]); + } catch (\Exception $e) { + // If a service from the expression cannot be fetched from the container, we skip the validation. + return; + } + } elseif (\is_string($value)) { + if ('%' === ($value[0] ?? '') && preg_match('/^%([^%]+)%$/', $value, $match)) { + $value = $this->container->getParameter(substr($value, 1, -1)); + } + + if ($envPlaceholderUniquePrefix && \is_string($value) && str_contains($value, 'env_')) { + // If the value is an env placeholder that is either mixed with a string or with another env placeholder, then its resolved value will always be a string, so we don't need to resolve it. + // We don't need to change the value because it is already a string. + if ('' === preg_replace('/'.$envPlaceholderUniquePrefix.'_\w+_[a-f0-9]{32}/U', '', $value, -1, $c) && 1 === $c) { + try { + $value = $this->container->resolveEnvPlaceholders($value, true); + } catch (\Exception $e) { + // If an env placeholder cannot be resolved, we skip the validation. + return; + } + } + } + } + + if (null === $value && $parameter->allowsNull()) { + return; + } + + if (null === $class) { + if ($value instanceof IteratorArgument) { + $class = RewindableGenerator::class; + } elseif ($value instanceof ServiceClosureArgument) { + $class = \Closure::class; + } elseif ($value instanceof ServiceLocatorArgument) { + $class = ServiceLocator::class; + } elseif (\is_object($value)) { + $class = \get_class($value); + } else { + $class = \gettype($value); + $class = ['integer' => 'int', 'double' => 'float', 'boolean' => 'bool'][$class] ?? $class; + } + } + + if (isset(self::SCALAR_TYPES[$type]) && isset(self::SCALAR_TYPES[$class])) { + return; + } + + if ('string' === $type && method_exists($class, '__toString')) { + return; + } + + if ('callable' === $type && (\Closure::class === $class || method_exists($class, '__invoke'))) { + return; + } + + if ('callable' === $type && \is_array($value) && isset($value[0]) && ($value[0] instanceof Reference || $value[0] instanceof Definition || \is_string($value[0]))) { + return; + } + + if ('iterable' === $type && (\is_array($value) || 'array' === $class || is_subclass_of($class, \Traversable::class))) { + return; + } + + if ($type === $class) { + return; + } + + if ('object' === $type && !isset(self::BUILTIN_TYPES[$class])) { + return; + } + + if ('mixed' === $type) { + return; + } + + if (is_a($class, $type, true)) { + return; + } + + if ('false' === $type) { + if (false === $value) { + return; + } + } elseif ($reflectionType->isBuiltin()) { + $checkFunction = sprintf('is_%s', $type); + if ($checkFunction($value)) { + return; + } + } + + throw new InvalidParameterTypeException($this->currentId, \is_object($value) ? $class : get_debug_type($value), $parameter); + } + + private function getExpressionLanguage(): ExpressionLanguage + { + if (null === $this->expressionLanguage) { + $this->expressionLanguage = new ExpressionLanguage(null, $this->container->getExpressionLanguageProviders()); + } + + return $this->expressionLanguage; + } +} diff --git a/lib/symfony/dependency-injection/Compiler/Compiler.php b/lib/symfony/dependency-injection/Compiler/Compiler.php index 0eb9d03664..4c5d003f0f 100644 --- a/lib/symfony/dependency-injection/Compiler/Compiler.php +++ b/lib/symfony/dependency-injection/Compiler/Compiler.php @@ -23,7 +23,6 @@ class Compiler { private $passConfig; private $log = []; - private $loggingFormatter; private $serviceReferenceGraph; public function __construct() @@ -33,9 +32,7 @@ class Compiler } /** - * Returns the PassConfig. - * - * @return PassConfig The PassConfig instance + * @return PassConfig */ public function getPassConfig() { @@ -43,77 +40,24 @@ class Compiler } /** - * Returns the ServiceReferenceGraph. - * - * @return ServiceReferenceGraph The ServiceReferenceGraph instance + * @return ServiceReferenceGraph */ public function getServiceReferenceGraph() { return $this->serviceReferenceGraph; } - /** - * Returns the logging formatter which can be used by compilation passes. - * - * @return LoggingFormatter - * - * @deprecated since version 3.3, to be removed in 4.0. Use the ContainerBuilder::log() method instead. - */ - public function getLoggingFormatter() + public function addPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0) { - if (null === $this->loggingFormatter) { - @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the ContainerBuilder::log() method instead.', __METHOD__), \E_USER_DEPRECATED); - - $this->loggingFormatter = new LoggingFormatter(); - } - - return $this->loggingFormatter; - } - - /** - * Adds a pass to the PassConfig. - * - * @param CompilerPassInterface $pass A compiler pass - * @param string $type The type of the pass - */ - public function addPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION/*, int $priority = 0*/) - { - if (\func_num_args() >= 3) { - $priority = func_get_arg(2); - } else { - if (__CLASS__ !== static::class) { - $r = new \ReflectionMethod($this, __FUNCTION__); - if (__CLASS__ !== $r->getDeclaringClass()->getName()) { - @trigger_error(sprintf('Method %s() will have a third `int $priority = 0` argument in version 4.0. Not defining it is deprecated since Symfony 3.2.', __METHOD__), \E_USER_DEPRECATED); - } - } - - $priority = 0; - } - $this->passConfig->addPass($pass, $type, $priority); } - /** - * Adds a log message. - * - * @param string $string The log message - * - * @deprecated since version 3.3, to be removed in 4.0. Use the ContainerBuilder::log() method instead. - */ - public function addLogMessage($string) - { - @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the ContainerBuilder::log() method instead.', __METHOD__), \E_USER_DEPRECATED); - - $this->log[] = $string; - } - /** * @final */ - public function log(CompilerPassInterface $pass, $message) + public function log(CompilerPassInterface $pass, string $message) { - if (false !== strpos($message, "\n")) { + if (str_contains($message, "\n")) { $message = str_replace("\n", "\n".\get_class($pass).': ', trim($message)); } @@ -121,9 +65,7 @@ class Compiler } /** - * Returns the log. - * - * @return array Log array + * @return array */ public function getLog() { diff --git a/lib/symfony/dependency-injection/Compiler/DecoratorServicePass.php b/lib/symfony/dependency-injection/Compiler/DecoratorServicePass.php index bf5f91578f..9170249178 100644 --- a/lib/symfony/dependency-injection/Compiler/DecoratorServicePass.php +++ b/lib/symfony/dependency-injection/Compiler/DecoratorServicePass.php @@ -13,6 +13,10 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\Reference; /** * Overwrites a service but keeps the overridden one. @@ -21,8 +25,19 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; * @author Fabien Potencier * @author Diego Saint Esteben */ -class DecoratorServicePass implements CompilerPassInterface +class DecoratorServicePass extends AbstractRecursivePass { + private $innerId = '.inner'; + + public function __construct(?string $innerId = '.inner') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->innerId = $innerId; + } + public function process(ContainerBuilder $container) { $definitions = new \SplPriorityQueue(); @@ -36,8 +51,14 @@ class DecoratorServicePass implements CompilerPassInterface } $decoratingDefinitions = []; - foreach ($definitions as list($id, $definition)) { - list($inner, $renamedId) = $definition->getDecoratedService(); + $tagsToKeep = $container->hasParameter('container.behavior_describing_tags') + ? $container->getParameter('container.behavior_describing_tags') + : ['container.do_not_inline', 'container.service_locator', 'container.service_subscriber']; + + foreach ($definitions as [$id, $definition]) { + $decoratedService = $definition->getDecoratedService(); + [$inner, $renamedId] = $decoratedService; + $invalidBehavior = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; $definition->setDecoratedService(null); @@ -45,37 +66,68 @@ class DecoratorServicePass implements CompilerPassInterface $renamedId = $id.'.inner'; } + $this->currentId = $renamedId; + $this->processValue($definition); + + $definition->innerServiceId = $renamedId; + $definition->decorationOnInvalid = $invalidBehavior; + // we create a new alias/service for the service we are replacing // to be able to reference it in the new one if ($container->hasAlias($inner)) { $alias = $container->getAlias($inner); $public = $alias->isPublic(); - $private = $alias->isPrivate(); - $container->setAlias($renamedId, new Alias($container->normalizeId($alias), false)); - } else { + $container->setAlias($renamedId, new Alias((string) $alias, false)); + $decoratedDefinition = $container->findDefinition($alias); + } elseif ($container->hasDefinition($inner)) { $decoratedDefinition = $container->getDefinition($inner); $public = $decoratedDefinition->isPublic(); - $private = $decoratedDefinition->isPrivate(); $decoratedDefinition->setPublic(false); $container->setDefinition($renamedId, $decoratedDefinition); $decoratingDefinitions[$inner] = $decoratedDefinition; + } elseif (ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $invalidBehavior) { + $container->removeDefinition($id); + continue; + } elseif (ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) { + $public = $definition->isPublic(); + $decoratedDefinition = null; + } else { + throw new ServiceNotFoundException($inner, $id); + } + + if ($decoratedDefinition && $decoratedDefinition->isSynthetic()) { + throw new InvalidArgumentException(sprintf('A synthetic service cannot be decorated: service "%s" cannot decorate "%s".', $id, $inner)); } if (isset($decoratingDefinitions[$inner])) { $decoratingDefinition = $decoratingDefinitions[$inner]; - $definition->setTags(array_merge($decoratingDefinition->getTags(), $definition->getTags())); - $autowiringTypes = $decoratingDefinition->getAutowiringTypes(false); - if ($types = array_merge($autowiringTypes, $definition->getAutowiringTypes(false))) { - $definition->setAutowiringTypes($types); - } - $decoratingDefinition->setTags([]); - if ($autowiringTypes) { - $decoratingDefinition->setAutowiringTypes([]); + + $decoratingTags = $decoratingDefinition->getTags(); + $resetTags = []; + + // Behavior-describing tags must not be transferred out to decorators + foreach ($tagsToKeep as $containerTag) { + if (isset($decoratingTags[$containerTag])) { + $resetTags[$containerTag] = $decoratingTags[$containerTag]; + unset($decoratingTags[$containerTag]); + } } + + $definition->setTags(array_merge($decoratingTags, $definition->getTags())); + $decoratingDefinition->setTags($resetTags); $decoratingDefinitions[$inner] = $definition; } - $container->setAlias($inner, $id)->setPublic($public)->setPrivate($private); + $container->setAlias($inner, $id)->setPublic($public); } } + + protected function processValue($value, bool $isRoot = false) + { + if ($value instanceof Reference && $this->innerId === (string) $value) { + return new Reference($this->currentId, $value->getInvalidBehavior()); + } + + return parent::processValue($value, $isRoot); + } } diff --git a/lib/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php b/lib/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php index 73b5d1d57d..5e7ba3173e 100644 --- a/lib/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php +++ b/lib/symfony/dependency-injection/Compiler/DefinitionErrorExceptionPass.php @@ -11,8 +11,10 @@ namespace Symfony\Component\DependencyInjection\Compiler; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; /** * Throws an exception for any Definitions that have errors and still exist. @@ -24,12 +26,27 @@ class DefinitionErrorExceptionPass extends AbstractRecursivePass /** * {@inheritdoc} */ - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { - if (!$value instanceof Definition || empty($value->getErrors())) { + if (!$value instanceof Definition || !$value->hasErrors()) { return parent::processValue($value, $isRoot); } + if ($isRoot && !$value->isPublic()) { + $graph = $this->container->getCompiler()->getServiceReferenceGraph(); + $runtimeException = false; + foreach ($graph->getNode($this->currentId)->getInEdges() as $edge) { + if (!$edge->getValue() instanceof Reference || ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE !== $edge->getValue()->getInvalidBehavior()) { + $runtimeException = false; + break; + } + $runtimeException = true; + } + if ($runtimeException) { + return parent::processValue($value, $isRoot); + } + } + // only show the first error so the user can focus on it $errors = $value->getErrors(); $message = reset($errors); diff --git a/lib/symfony/dependency-injection/Compiler/FactoryReturnTypePass.php b/lib/symfony/dependency-injection/Compiler/FactoryReturnTypePass.php deleted file mode 100644 index 67575c03f3..0000000000 --- a/lib/symfony/dependency-injection/Compiler/FactoryReturnTypePass.php +++ /dev/null @@ -1,112 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DependencyInjection\Compiler; - -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\Reference; - -/** - * @author Guilhem N. - * - * @deprecated since version 3.3, to be removed in 4.0. - */ -class FactoryReturnTypePass implements CompilerPassInterface -{ - private $resolveClassPass; - - public function __construct(ResolveClassPass $resolveClassPass = null) - { - if (null === $resolveClassPass) { - @trigger_error('The '.__CLASS__.' class is deprecated since Symfony 3.3 and will be removed in 4.0.', \E_USER_DEPRECATED); - } - $this->resolveClassPass = $resolveClassPass; - } - - /** - * {@inheritdoc} - */ - public function process(ContainerBuilder $container) - { - // works only since php 7.0 and hhvm 3.11 - if (!method_exists(\ReflectionMethod::class, 'getReturnType')) { - return; - } - $resolveClassPassChanges = null !== $this->resolveClassPass ? $this->resolveClassPass->getChanges() : []; - - foreach ($container->getDefinitions() as $id => $definition) { - $this->updateDefinition($container, $id, $definition, $resolveClassPassChanges); - } - } - - private function updateDefinition(ContainerBuilder $container, $id, Definition $definition, array $resolveClassPassChanges, array $previous = []) - { - // circular reference - if (isset($previous[$id])) { - return; - } - - $factory = $definition->getFactory(); - if (null === $factory || (!isset($resolveClassPassChanges[$id]) && null !== $definition->getClass())) { - return; - } - - $class = null; - if (\is_string($factory)) { - try { - $m = new \ReflectionFunction($factory); - if (false !== $m->getFileName() && file_exists($m->getFileName())) { - $container->fileExists($m->getFileName()); - } - } catch (\ReflectionException $e) { - return; - } - } else { - if ($factory[0] instanceof Reference) { - $previous[$id] = true; - $factoryId = $container->normalizeId($factory[0]); - $factoryDefinition = $container->findDefinition($factoryId); - $this->updateDefinition($container, $factoryId, $factoryDefinition, $resolveClassPassChanges, $previous); - $class = $factoryDefinition->getClass(); - } else { - $class = $factory[0]; - } - - if (!$m = $container->getReflectionClass($class, false)) { - return; - } - try { - $m = $m->getMethod($factory[1]); - } catch (\ReflectionException $e) { - return; - } - } - - $returnType = $m->getReturnType(); - if (null !== $returnType && !$returnType->isBuiltin()) { - $returnType = $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; - if (null !== $class) { - $declaringClass = $m->getDeclaringClass()->getName(); - if ('self' === strtolower($returnType)) { - $returnType = $declaringClass; - } elseif ('parent' === strtolower($returnType)) { - $returnType = get_parent_class($declaringClass) ?: null; - } - } - - if (null !== $returnType && (!isset($resolveClassPassChanges[$id]) || $returnType !== $resolveClassPassChanges[$id])) { - @trigger_error(sprintf('Relying on its factory\'s return-type to define the class of service "%s" is deprecated since Symfony 3.3 and won\'t work in 4.0. Set the "class" attribute to "%s" on the service definition instead.', $id, $returnType), \E_USER_DEPRECATED); - } - $definition->setClass($returnType); - } - } -} diff --git a/lib/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php b/lib/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php index 9d8a02e7bd..2285f8ea5b 100644 --- a/lib/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php +++ b/lib/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Reference; @@ -21,42 +22,95 @@ use Symfony\Component\DependencyInjection\Reference; * * @author Johannes M. Schmitt */ -class InlineServiceDefinitionsPass extends AbstractRecursivePass implements RepeatablePassInterface +class InlineServiceDefinitionsPass extends AbstractRecursivePass { + private $analyzingPass; private $cloningIds = []; - private $inlinedServiceIds = []; + private $connectedIds = []; + private $notInlinedIds = []; + private $inlinedIds = []; + private $notInlinableIds = []; + private $graph; - /** - * {@inheritdoc} - */ - public function setRepeatedPass(RepeatedPass $repeatedPass) + public function __construct(AnalyzeServiceReferencesPass $analyzingPass = null) { - // no-op for BC + $this->analyzingPass = $analyzingPass; } - /** - * Returns an array of all services inlined by this pass. - * - * The key is the inlined service id and its value is the list of services it was inlined into. - * - * @deprecated since version 3.4, to be removed in 4.0. - * - * @return array - */ - public function getInlinedServiceIds() + public function process(ContainerBuilder $container) { - @trigger_error('Calling InlineServiceDefinitionsPass::getInlinedServiceIds() is deprecated since Symfony 3.4 and will be removed in 4.0.', \E_USER_DEPRECATED); + $this->container = $container; + if ($this->analyzingPass) { + $analyzedContainer = new ContainerBuilder(); + $analyzedContainer->setAliases($container->getAliases()); + $analyzedContainer->setDefinitions($container->getDefinitions()); + foreach ($container->getExpressionLanguageProviders() as $provider) { + $analyzedContainer->addExpressionLanguageProvider($provider); + } + } else { + $analyzedContainer = $container; + } + try { + $remainingInlinedIds = []; + $this->connectedIds = $this->notInlinedIds = $container->getDefinitions(); + do { + if ($this->analyzingPass) { + $analyzedContainer->setDefinitions(array_intersect_key($analyzedContainer->getDefinitions(), $this->connectedIds)); + $this->analyzingPass->process($analyzedContainer); + } + $this->graph = $analyzedContainer->getCompiler()->getServiceReferenceGraph(); + $notInlinedIds = $this->notInlinedIds; + $this->connectedIds = $this->notInlinedIds = $this->inlinedIds = []; - return $this->inlinedServiceIds; + foreach ($analyzedContainer->getDefinitions() as $id => $definition) { + if (!$this->graph->hasNode($id)) { + continue; + } + foreach ($this->graph->getNode($id)->getOutEdges() as $edge) { + if (isset($notInlinedIds[$edge->getSourceNode()->getId()])) { + $this->currentId = $id; + $this->processValue($definition, true); + break; + } + } + } + + foreach ($this->inlinedIds as $id => $isPublicOrNotShared) { + if ($isPublicOrNotShared) { + $remainingInlinedIds[$id] = $id; + } else { + $container->removeDefinition($id); + $analyzedContainer->removeDefinition($id); + } + } + } while ($this->inlinedIds && $this->analyzingPass); + + foreach ($remainingInlinedIds as $id) { + if (isset($this->notInlinableIds[$id])) { + continue; + } + + $definition = $container->getDefinition($id); + + if (!$definition->isShared() && !$definition->isPublic()) { + $container->removeDefinition($id); + } + } + } finally { + $this->container = null; + $this->connectedIds = $this->notInlinedIds = $this->inlinedIds = []; + $this->notInlinableIds = []; + $this->graph = null; + } } /** * {@inheritdoc} */ - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { if ($value instanceof ArgumentInterface) { - // Reference found in ArgumentInterface::getValues() are not inlineable + // References found in ArgumentInterface::getValues() are not inlineable return $value; } @@ -67,18 +121,23 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass implements Repe $value = clone $value; } - if (!$value instanceof Reference || !$this->container->hasDefinition($id = $this->container->normalizeId($value))) { + if (!$value instanceof Reference) { return parent::processValue($value, $isRoot); + } elseif (!$this->container->hasDefinition($id = (string) $value)) { + return $value; } $definition = $this->container->getDefinition($id); - if (!$this->isInlineableDefinition($id, $definition, $this->container->getCompiler()->getServiceReferenceGraph())) { + if (!$this->isInlineableDefinition($id, $definition)) { + $this->notInlinableIds[$id] = true; + return $value; } $this->container->log($this, sprintf('Inlined service "%s" to "%s".', $id, $this->currentId)); - $this->inlinedServiceIds[$id][] = $this->currentId; + $this->inlinedIds[$id] = $definition->isPublic() || !$definition->isShared(); + $this->notInlinedIds[$this->currentId] = true; if ($definition->isShared()) { return $definition; @@ -101,53 +160,64 @@ class InlineServiceDefinitionsPass extends AbstractRecursivePass implements Repe /** * Checks if the definition is inlineable. - * - * @return bool If the definition is inlineable */ - private function isInlineableDefinition($id, Definition $definition, ServiceReferenceGraph $graph) + private function isInlineableDefinition(string $id, Definition $definition): bool { - if ($definition->getErrors() || $definition->isDeprecated() || $definition->isLazy() || $definition->isSynthetic()) { + if ($definition->hasErrors() || $definition->isDeprecated() || $definition->isLazy() || $definition->isSynthetic() || $definition->hasTag('container.do_not_inline')) { return false; } if (!$definition->isShared()) { + if (!$this->graph->hasNode($id)) { + return true; + } + + foreach ($this->graph->getNode($id)->getInEdges() as $edge) { + $srcId = $edge->getSourceNode()->getId(); + $this->connectedIds[$srcId] = true; + if ($edge->isWeak() || $edge->isLazy()) { + return !$this->connectedIds[$id] = true; + } + } + return true; } - if ($definition->isPublic() || $definition->isPrivate()) { + if ($definition->isPublic()) { return false; } - if (!$graph->hasNode($id)) { + if (!$this->graph->hasNode($id)) { return true; } if ($this->currentId == $id) { return false; } + $this->connectedIds[$id] = true; - $ids = []; - $isReferencedByConstructor = false; - foreach ($graph->getNode($id)->getInEdges() as $edge) { - $isReferencedByConstructor = $isReferencedByConstructor || $edge->isReferencedByConstructor(); + $srcIds = []; + $srcCount = 0; + foreach ($this->graph->getNode($id)->getInEdges() as $edge) { + $srcId = $edge->getSourceNode()->getId(); + $this->connectedIds[$srcId] = true; if ($edge->isWeak() || $edge->isLazy()) { return false; } - $ids[] = $edge->getSourceNode()->getId(); + $srcIds[$srcId] = true; + ++$srcCount; } - if (!$ids) { - return true; - } + if (1 !== \count($srcIds)) { + $this->notInlinedIds[$id] = true; - if (\count(array_unique($ids)) > 1) { return false; } - if (\count($ids) > 1 && \is_array($factory = $definition->getFactory()) && ($factory[0] instanceof Reference || $factory[0] instanceof Definition)) { + if ($srcCount > 1 && \is_array($factory = $definition->getFactory()) && ($factory[0] instanceof Reference || $factory[0] instanceof Definition)) { return false; } - return $this->container->getDefinition($ids[0])->isShared(); + return $this->container->getDefinition($srcId)->isShared(); } } diff --git a/lib/symfony/dependency-injection/Compiler/LoggingFormatter.php b/lib/symfony/dependency-injection/Compiler/LoggingFormatter.php deleted file mode 100644 index 0d91f00f7e..0000000000 --- a/lib/symfony/dependency-injection/Compiler/LoggingFormatter.php +++ /dev/null @@ -1,54 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DependencyInjection\Compiler; - -@trigger_error('The '.__NAMESPACE__.'\LoggingFormatter class is deprecated since Symfony 3.3 and will be removed in 4.0. Use the ContainerBuilder::log() method instead.', \E_USER_DEPRECATED); - -/** - * Used to format logging messages during the compilation. - * - * @author Johannes M. Schmitt - * - * @deprecated since version 3.3, to be removed in 4.0. Use the ContainerBuilder::log() method instead. - */ -class LoggingFormatter -{ - public function formatRemoveService(CompilerPassInterface $pass, $id, $reason) - { - return $this->format($pass, sprintf('Removed service "%s"; reason: %s.', $id, $reason)); - } - - public function formatInlineService(CompilerPassInterface $pass, $id, $target) - { - return $this->format($pass, sprintf('Inlined service "%s" to "%s".', $id, $target)); - } - - public function formatUpdateReference(CompilerPassInterface $pass, $serviceId, $oldDestId, $newDestId) - { - return $this->format($pass, sprintf('Changed reference of service "%s" previously pointing to "%s" to "%s".', $serviceId, $oldDestId, $newDestId)); - } - - public function formatResolveInheritance(CompilerPassInterface $pass, $childId, $parentId) - { - return $this->format($pass, sprintf('Resolving inheritance for "%s" (parent: %s).', $childId, $parentId)); - } - - public function formatUnusedAutowiringPatterns(CompilerPassInterface $pass, $id, array $patterns) - { - return $this->format($pass, sprintf('Autowiring\'s patterns "%s" for service "%s" don\'t match any method.', implode('", "', $patterns), $id)); - } - - public function format(CompilerPassInterface $pass, $message) - { - return sprintf('%s: %s', \get_class($pass), $message); - } -} diff --git a/lib/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php b/lib/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php index caa1fd2251..9dc39314cb 100644 --- a/lib/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php +++ b/lib/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; +use Symfony\Component\Config\Definition\BaseNode; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; @@ -37,6 +38,7 @@ class MergeExtensionConfigurationPass implements CompilerPassInterface $definitions = $container->getDefinitions(); $aliases = $container->getAliases(); $exprLangProviders = $container->getExpressionLanguageProviders(); + $configAvailable = class_exists(BaseNode::class); foreach ($container->getExtensions() as $extension) { if ($extension instanceof PrependExtensionInterface) { @@ -53,6 +55,9 @@ class MergeExtensionConfigurationPass implements CompilerPassInterface if ($resolvingBag instanceof EnvPlaceholderParameterBag && $extension instanceof Extension) { // create a dedicated bag so that we can track env vars per-extension $resolvingBag = new MergeExtensionConfigurationParameterBag($resolvingBag); + if ($configAvailable) { + BaseNode::setPlaceholderUniquePrefix($resolvingBag->getEnvPlaceholderUniquePrefix()); + } } $config = $resolvingBag->resolveValue($config); @@ -128,9 +133,14 @@ class MergeExtensionConfigurationParameterBag extends EnvPlaceholderParameterBag /** * {@inheritdoc} */ - public function getEnvPlaceholders() + public function getEnvPlaceholders(): array { - return null !== $this->processedEnvPlaceholders ? $this->processedEnvPlaceholders : parent::getEnvPlaceholders(); + return $this->processedEnvPlaceholders ?? parent::getEnvPlaceholders(); + } + + public function getUnusedEnvPlaceholders(): array + { + return null === $this->processedEnvPlaceholders ? [] : array_diff_key(parent::getEnvPlaceholders(), $this->processedEnvPlaceholders); } } @@ -153,9 +163,9 @@ class MergeExtensionConfigurationContainerBuilder extends ContainerBuilder /** * {@inheritdoc} */ - public function addCompilerPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION/*, int $priority = 0*/) + public function addCompilerPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0): self { - throw new LogicException(sprintf('You cannot add compiler pass "%s" from extension "%s". Compiler passes must be registered before the container is compiled.', \get_class($pass), $this->extensionClass)); + throw new LogicException(sprintf('You cannot add compiler pass "%s" from extension "%s". Compiler passes must be registered before the container is compiled.', get_debug_type($pass), $this->extensionClass)); } /** @@ -163,13 +173,13 @@ class MergeExtensionConfigurationContainerBuilder extends ContainerBuilder */ public function registerExtension(ExtensionInterface $extension) { - throw new LogicException(sprintf('You cannot register extension "%s" from "%s". Extensions must be registered before the container is compiled.', \get_class($extension), $this->extensionClass)); + throw new LogicException(sprintf('You cannot register extension "%s" from "%s". Extensions must be registered before the container is compiled.', get_debug_type($extension), $this->extensionClass)); } /** * {@inheritdoc} */ - public function compile($resolveEnvPlaceholders = false) + public function compile(bool $resolveEnvPlaceholders = false) { throw new LogicException(sprintf('Cannot compile the container in extension "%s".', $this->extensionClass)); } @@ -191,7 +201,7 @@ class MergeExtensionConfigurationContainerBuilder extends ContainerBuilder } foreach ($bag->getEnvPlaceholders() as $env => $placeholders) { - if (false === strpos($env, ':')) { + if (!str_contains($env, ':')) { continue; } foreach ($placeholders as $placeholder) { diff --git a/lib/symfony/dependency-injection/Compiler/PassConfig.php b/lib/symfony/dependency-injection/Compiler/PassConfig.php index d95b21988c..9f9a56edd6 100644 --- a/lib/symfony/dependency-injection/Compiler/PassConfig.php +++ b/lib/symfony/dependency-injection/Compiler/PassConfig.php @@ -22,11 +22,11 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; */ class PassConfig { - const TYPE_AFTER_REMOVING = 'afterRemoving'; - const TYPE_BEFORE_OPTIMIZATION = 'beforeOptimization'; - const TYPE_BEFORE_REMOVING = 'beforeRemoving'; - const TYPE_OPTIMIZE = 'optimization'; - const TYPE_REMOVE = 'removing'; + public const TYPE_AFTER_REMOVING = 'afterRemoving'; + public const TYPE_BEFORE_OPTIMIZATION = 'beforeOptimization'; + public const TYPE_BEFORE_REMOVING = 'beforeRemoving'; + public const TYPE_OPTIMIZE = 'optimization'; + public const TYPE_REMOVE = 'removing'; private $mergePass; private $afterRemovingPasses = []; @@ -41,7 +41,9 @@ class PassConfig $this->beforeOptimizationPasses = [ 100 => [ - $resolveClassPass = new ResolveClassPass(), + new ResolveClassPass(), + new RegisterAutoconfigureAttributesPass(), + new AttributeAutoconfigurationPass(), new ResolveInstanceofConditionalsPass(), new RegisterEnvVarProcessorsPass(), ], @@ -49,18 +51,22 @@ class PassConfig ]; $this->optimizationPasses = [[ + $autoAliasServicePass = new AutoAliasServicePass(), + new ValidateEnvPlaceholdersPass(), + new ResolveDecoratorStackPass(), new ResolveChildDefinitionsPass(), - new ServiceLocatorTagPass(), new RegisterServiceSubscribersPass(), - new DecoratorServicePass(), new ResolveParameterPlaceHoldersPass(false, false), new ResolveFactoryClassPass(), - new FactoryReturnTypePass($resolveClassPass), - new CheckDefinitionValidityPass(), new ResolveNamedArgumentsPass(), new AutowireRequiredMethodsPass(), + new AutowireRequiredPropertiesPass(), new ResolveBindingsPass(), + new ServiceLocatorTagPass(), + new DecoratorServicePass(), + new CheckDefinitionValidityPass(), new AutowirePass(false), + new ServiceLocatorTagPass(), new ResolveTaggedIteratorArgumentPass(), new ResolveServiceSubscribersPass(), new ResolveReferencesToAliasesPass(), @@ -71,25 +77,22 @@ class PassConfig new CheckArgumentsValidityPass(false), ]]; - $this->beforeRemovingPasses = [ - -100 => [ - new ResolvePrivatesPass(), - ], - ]; - $this->removingPasses = [[ new RemovePrivateAliasesPass(), - new ReplaceAliasByActualDefinitionPass(), + (new ReplaceAliasByActualDefinitionPass())->setAutoAliasServicePass($autoAliasServicePass), new RemoveAbstractDefinitionsPass(), - new RepeatedPass([ - new AnalyzeServiceReferencesPass(), - new InlineServiceDefinitionsPass(), - new AnalyzeServiceReferencesPass(), - new RemoveUnusedDefinitionsPass(), - ]), - new DefinitionErrorExceptionPass(), + new RemoveUnusedDefinitionsPass(), + new AnalyzeServiceReferencesPass(), new CheckExceptionOnInvalidReferenceBehaviorPass(), + new InlineServiceDefinitionsPass(new AnalyzeServiceReferencesPass()), + new AnalyzeServiceReferencesPass(), + new DefinitionErrorExceptionPass(), + ]]; + + $this->afterRemovingPasses = [[ new ResolveHotPathPass(), + new ResolveNoPreloadPass(), + new AliasDeprecatedPublicServicesPass(), ]]; } @@ -113,26 +116,10 @@ class PassConfig /** * Adds a pass. * - * @param CompilerPassInterface $pass A Compiler pass - * @param string $type The pass type - * * @throws InvalidArgumentException when a pass type doesn't exist */ - public function addPass(CompilerPassInterface $pass, $type = self::TYPE_BEFORE_OPTIMIZATION/*, int $priority = 0*/) + public function addPass(CompilerPassInterface $pass, string $type = self::TYPE_BEFORE_OPTIMIZATION, int $priority = 0) { - if (\func_num_args() >= 3) { - $priority = func_get_arg(2); - } else { - if (__CLASS__ !== static::class) { - $r = new \ReflectionMethod($this, __FUNCTION__); - if (__CLASS__ !== $r->getDeclaringClass()->getName()) { - @trigger_error(sprintf('Method %s() will have a third `int $priority = 0` argument in version 4.0. Not defining it is deprecated since Symfony 3.2.', __METHOD__), \E_USER_DEPRECATED); - } - } - - $priority = 0; - } - $property = $type.'Passes'; if (!isset($this->$property)) { throw new InvalidArgumentException(sprintf('Invalid type "%s".', $type)); @@ -268,7 +255,7 @@ class PassConfig * * @return CompilerPassInterface[] */ - private function sortPasses(array $passes) + private function sortPasses(array $passes): array { if (0 === \count($passes)) { return []; @@ -277,6 +264,6 @@ class PassConfig krsort($passes); // Flatten the array - return \call_user_func_array('array_merge', $passes); + return array_merge(...$passes); } } diff --git a/lib/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.php b/lib/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.php index c7e12536ea..8c4d841f5a 100644 --- a/lib/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.php +++ b/lib/symfony/dependency-injection/Compiler/PriorityTaggedServiceTrait.php @@ -11,8 +11,12 @@ namespace Symfony\Component\DependencyInjection\Compiler; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; /** * Trait that allows a generic method to find and sort service by priority option in the tag. @@ -31,24 +35,137 @@ trait PriorityTaggedServiceTrait * @see https://bugs.php.net/53710 * @see https://bugs.php.net/60926 * - * @param string $tagName + * @param string|TaggedIteratorArgument $tagName * * @return Reference[] */ - private function findAndSortTaggedServices($tagName, ContainerBuilder $container) + private function findAndSortTaggedServices($tagName, ContainerBuilder $container): array { + $indexAttribute = $defaultIndexMethod = $needsIndexes = $defaultPriorityMethod = null; + + if ($tagName instanceof TaggedIteratorArgument) { + $indexAttribute = $tagName->getIndexAttribute(); + $defaultIndexMethod = $tagName->getDefaultIndexMethod(); + $needsIndexes = $tagName->needsIndexes(); + $defaultPriorityMethod = $tagName->getDefaultPriorityMethod() ?? 'getDefaultPriority'; + $tagName = $tagName->getTag(); + } + + $i = 0; $services = []; foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) { - $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0; - $services[$priority][] = new Reference($serviceId); + $defaultPriority = null; + $defaultIndex = null; + $definition = $container->getDefinition($serviceId); + $class = $definition->getClass(); + $class = $container->getParameterBag()->resolveValue($class) ?: null; + $checkTaggedItem = !$definition->hasTag(80000 <= \PHP_VERSION_ID && $definition->isAutoconfigured() ? 'container.ignore_attributes' : $tagName); + + foreach ($attributes as $attribute) { + $index = $priority = null; + + if (isset($attribute['priority'])) { + $priority = $attribute['priority']; + } elseif (null === $defaultPriority && $defaultPriorityMethod && $class) { + $defaultPriority = PriorityTaggedServiceUtil::getDefault($container, $serviceId, $class, $defaultPriorityMethod, $tagName, 'priority', $checkTaggedItem); + } + $priority = $priority ?? $defaultPriority ?? $defaultPriority = 0; + + if (null === $indexAttribute && !$defaultIndexMethod && !$needsIndexes) { + $services[] = [$priority, ++$i, null, $serviceId, null]; + continue 2; + } + + if (null !== $indexAttribute && isset($attribute[$indexAttribute])) { + $index = $attribute[$indexAttribute]; + } elseif (null === $defaultIndex && $defaultPriorityMethod && $class) { + $defaultIndex = PriorityTaggedServiceUtil::getDefault($container, $serviceId, $class, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute, $checkTaggedItem); + } + $index = $index ?? $defaultIndex ?? $defaultIndex = $serviceId; + + $services[] = [$priority, ++$i, $index, $serviceId, $class]; + } } - if ($services) { - krsort($services); - $services = \call_user_func_array('array_merge', $services); + uasort($services, static function ($a, $b) { return $b[0] <=> $a[0] ?: $a[1] <=> $b[1]; }); + + $refs = []; + foreach ($services as [, , $index, $serviceId, $class]) { + if (!$class) { + $reference = new Reference($serviceId); + } elseif ($index === $serviceId) { + $reference = new TypedReference($serviceId, $class); + } else { + $reference = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $index); + } + + if (null === $index) { + $refs[] = $reference; + } else { + $refs[$index] = $reference; + } } - return $services; + return $refs; + } +} + +/** + * @internal + */ +class PriorityTaggedServiceUtil +{ + /** + * @return string|int|null + */ + public static function getDefault(ContainerBuilder $container, string $serviceId, string $class, string $defaultMethod, string $tagName, ?string $indexAttribute, bool $checkTaggedItem) + { + if (!($r = $container->getReflectionClass($class)) || (!$checkTaggedItem && !$r->hasMethod($defaultMethod))) { + return null; + } + + if ($checkTaggedItem && !$r->hasMethod($defaultMethod)) { + foreach ($r->getAttributes(AsTaggedItem::class) as $attribute) { + return 'priority' === $indexAttribute ? $attribute->newInstance()->priority : $attribute->newInstance()->index; + } + + return null; + } + + if (null !== $indexAttribute) { + $service = $class !== $serviceId ? sprintf('service "%s"', $serviceId) : 'on the corresponding service'; + $message = [sprintf('Either method "%s::%s()" should ', $class, $defaultMethod), sprintf(' or tag "%s" on %s is missing attribute "%s".', $tagName, $service, $indexAttribute)]; + } else { + $message = [sprintf('Method "%s::%s()" should ', $class, $defaultMethod), '.']; + } + + if (!($rm = $r->getMethod($defaultMethod))->isStatic()) { + throw new InvalidArgumentException(implode('be static', $message)); + } + + if (!$rm->isPublic()) { + throw new InvalidArgumentException(implode('be public', $message)); + } + + $default = $rm->invoke(null); + + if ('priority' === $indexAttribute) { + if (!\is_int($default)) { + throw new InvalidArgumentException(implode(sprintf('return int (got "%s")', get_debug_type($default)), $message)); + } + + return $default; + } + + if (\is_int($default)) { + $default = (string) $default; + } + + if (!\is_string($default)) { + throw new InvalidArgumentException(implode(sprintf('return string|int (got "%s")', get_debug_type($default)), $message)); + } + + return $default; } } diff --git a/lib/symfony/dependency-injection/Compiler/RegisterAutoconfigureAttributesPass.php b/lib/symfony/dependency-injection/Compiler/RegisterAutoconfigureAttributesPass.php new file mode 100644 index 0000000000..cc3b117a4d --- /dev/null +++ b/lib/symfony/dependency-injection/Compiler/RegisterAutoconfigureAttributesPass.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; + +/** + * Reads #[Autoconfigure] attributes on definitions that are autoconfigured + * and don't have the "container.ignore_attributes" tag. + * + * @author Nicolas Grekas + */ +final class RegisterAutoconfigureAttributesPass implements CompilerPassInterface +{ + private static $registerForAutoconfiguration; + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + if (80000 > \PHP_VERSION_ID) { + return; + } + + foreach ($container->getDefinitions() as $id => $definition) { + if ($this->accept($definition) && $class = $container->getReflectionClass($definition->getClass(), false)) { + $this->processClass($container, $class); + } + } + } + + public function accept(Definition $definition): bool + { + return 80000 <= \PHP_VERSION_ID && $definition->isAutoconfigured() && !$definition->hasTag('container.ignore_attributes'); + } + + public function processClass(ContainerBuilder $container, \ReflectionClass $class) + { + foreach ($class->getAttributes(Autoconfigure::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + self::registerForAutoconfiguration($container, $class, $attribute); + } + } + + private static function registerForAutoconfiguration(ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute) + { + if (self::$registerForAutoconfiguration) { + return (self::$registerForAutoconfiguration)($container, $class, $attribute); + } + + $parseDefinitions = new \ReflectionMethod(YamlFileLoader::class, 'parseDefinitions'); + $parseDefinitions->setAccessible(true); + $yamlLoader = $parseDefinitions->getDeclaringClass()->newInstanceWithoutConstructor(); + + self::$registerForAutoconfiguration = static function (ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute) use ($parseDefinitions, $yamlLoader) { + $attribute = (array) $attribute->newInstance(); + + foreach ($attribute['tags'] ?? [] as $i => $tag) { + if (\is_array($tag) && [0] === array_keys($tag)) { + $attribute['tags'][$i] = [$class->name => $tag[0]]; + } + } + + $parseDefinitions->invoke( + $yamlLoader, + [ + 'services' => [ + '_instanceof' => [ + $class->name => [$container->registerForAutoconfiguration($class->name)] + $attribute, + ], + ], + ], + $class->getFileName(), + false + ); + }; + + return (self::$registerForAutoconfiguration)($container, $class, $attribute); + } +} diff --git a/lib/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php b/lib/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php index b4d0d05506..251889ebed 100644 --- a/lib/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php +++ b/lib/symfony/dependency-injection/Compiler/RegisterEnvVarProcessorsPass.php @@ -11,14 +11,12 @@ namespace Symfony\Component\DependencyInjection\Compiler; -use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\EnvVarProcessor; use Symfony\Component\DependencyInjection\EnvVarProcessorInterface; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\ServiceLocator; /** * Creates the container.env_var_processors_locator service. @@ -27,7 +25,7 @@ use Symfony\Component\DependencyInjection\ServiceLocator; */ class RegisterEnvVarProcessorsPass implements CompilerPassInterface { - private static $allowedTypes = ['array', 'bool', 'float', 'int', 'string']; + private const ALLOWED_TYPES = ['array', 'bool', 'float', 'int', 'string']; public function process(ContainerBuilder $container) { @@ -41,7 +39,7 @@ class RegisterEnvVarProcessorsPass implements CompilerPassInterface throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EnvVarProcessorInterface::class)); } foreach ($class::getProvidedTypes() as $prefix => $type) { - $processors[$prefix] = new ServiceClosureArgument(new Reference($id)); + $processors[$prefix] = new Reference($id); $types[$prefix] = self::validateProvidedTypes($type, $class); } } @@ -56,20 +54,19 @@ class RegisterEnvVarProcessorsPass implements CompilerPassInterface } if ($processors) { - $container->register('container.env_var_processors_locator', ServiceLocator::class) + $container->setAlias('container.env_var_processors_locator', (string) ServiceLocatorTagPass::register($container, $processors)) ->setPublic(true) - ->setArguments([$processors]) ; } } - private static function validateProvidedTypes($types, $class) + private static function validateProvidedTypes(string $types, string $class): array { $types = explode('|', $types); foreach ($types as $type) { - if (!\in_array($type, self::$allowedTypes)) { - throw new InvalidArgumentException(sprintf('Invalid type "%s" returned by "%s::getProvidedTypes()", expected one of "%s".', $type, $class, implode('", "', self::$allowedTypes))); + if (!\in_array($type, self::ALLOWED_TYPES)) { + throw new InvalidArgumentException(sprintf('Invalid type "%s" returned by "%s::getProvidedTypes()", expected one of "%s".', $type, $class, implode('", "', self::ALLOWED_TYPES))); } } diff --git a/lib/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php b/lib/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php new file mode 100644 index 0000000000..c5eb9bf08b --- /dev/null +++ b/lib/symfony/dependency-injection/Compiler/RegisterReverseContainerPass.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Nicolas Grekas + */ +class RegisterReverseContainerPass implements CompilerPassInterface +{ + private $beforeRemoving; + private $serviceId; + private $tagName; + + public function __construct(bool $beforeRemoving, string $serviceId = 'reverse_container', string $tagName = 'container.reversible') + { + if (1 < \func_num_args()) { + trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->beforeRemoving = $beforeRemoving; + $this->serviceId = $serviceId; + $this->tagName = $tagName; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->serviceId)) { + return; + } + + $refType = $this->beforeRemoving ? ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE : ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + $services = []; + foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) { + $services[$id] = new Reference($id, $refType); + } + + if ($this->beforeRemoving) { + // prevent inlining of the reverse container + $services[$this->serviceId] = new Reference($this->serviceId, $refType); + } + $locator = $container->getDefinition($this->serviceId)->getArgument(1); + + if ($locator instanceof Reference) { + $locator = $container->getDefinition((string) $locator); + } + if ($locator instanceof Definition) { + foreach ($services as $id => $ref) { + $services[$id] = new ServiceClosureArgument($ref); + } + $locator->replaceArgument(0, $services); + } else { + $locator->setValues($services); + } + } +} diff --git a/lib/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php b/lib/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php index bf1387c04e..2a458ad120 100644 --- a/lib/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php +++ b/lib/symfony/dependency-injection/Compiler/RegisterServiceSubscribersPass.php @@ -11,12 +11,17 @@ namespace Symfony\Component\DependencyInjection\Compiler; +use Psr\Container\ContainerInterface as PsrContainerInterface; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Contracts\Service\ServiceProviderInterface; +use Symfony\Contracts\Service\ServiceSubscriberInterface; /** * Compiler pass to register tagged services that require a service locator. @@ -25,7 +30,7 @@ use Symfony\Component\DependencyInjection\TypedReference; */ class RegisterServiceSubscribersPass extends AbstractRecursivePass { - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { if (!$value instanceof Definition || $value->isAbstract() || $value->isSynthetic() || !$value->hasTag('container.service_subscriber')) { return parent::processValue($value, $isRoot); @@ -63,29 +68,47 @@ class RegisterServiceSubscribersPass extends AbstractRecursivePass throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $this->currentId, ServiceSubscriberInterface::class)); } $class = $r->name; - + $replaceDeprecatedSession = $this->container->has('.session.deprecated') && $r->isSubclassOf(AbstractController::class); $subscriberMap = []; - $declaringClass = (new \ReflectionMethod($class, 'getSubscribedServices'))->class; foreach ($class::getSubscribedServices() as $key => $type) { - if (!\is_string($type) || !preg_match('/^\??[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+$/', $type)) { - throw new InvalidArgumentException(sprintf('"%s::getSubscribedServices()" must return valid PHP types for service "%s" key "%s", "%s" returned.', $class, $this->currentId, $key, \is_string($type) ? $type : \gettype($type))); + if (!\is_string($type) || !preg_match('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))(?(DEFINE)(?(?&cn)(?:\\\\(?&cn))*+))^\??(?&fqcn)(?:(?:\|(?&fqcn))*+|(?:&(?&fqcn))*+)$/', $type)) { + throw new InvalidArgumentException(sprintf('"%s::getSubscribedServices()" must return valid PHP types for service "%s" key "%s", "%s" returned.', $class, $this->currentId, $key, \is_string($type) ? $type : get_debug_type($type))); } if ($optionalBehavior = '?' === $type[0]) { $type = substr($type, 1); $optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; } - if (\is_int($key)) { + if (\is_int($name = $key)) { $key = $type; + $name = null; } if (!isset($serviceMap[$key])) { if (!$autowire) { throw new InvalidArgumentException(sprintf('Service "%s" misses a "container.service_subscriber" tag with "key"/"id" attributes corresponding to entry "%s" as returned by "%s::getSubscribedServices()".', $this->currentId, $key, $class)); } + if ($replaceDeprecatedSession && SessionInterface::class === $type) { + // This prevents triggering the deprecation when building the container + // Should be removed in Symfony 6.0 + $type = '.session.deprecated'; + } $serviceMap[$key] = new Reference($type); } - $subscriberMap[$key] = new TypedReference($this->container->normalizeId($serviceMap[$key]), $type, $declaringClass, $optionalBehavior ?: ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); + if ($name) { + if (false !== $i = strpos($name, '::get')) { + $name = lcfirst(substr($name, 5 + $i)); + } elseif (str_contains($name, '::')) { + $name = null; + } + } + + if (null !== $name && !$this->container->has($name) && !$this->container->has($type.' $'.$name)) { + $camelCaseName = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $name)))); + $name = $this->container->has($type.' $'.$camelCaseName) ? $camelCaseName : $name; + } + + $subscriberMap[$key] = new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior ?: ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $name); unset($serviceMap[$key]); } @@ -94,7 +117,14 @@ class RegisterServiceSubscribersPass extends AbstractRecursivePass throw new InvalidArgumentException(sprintf('Service %s not exist in the map returned by "%s::getSubscribedServices()" for service "%s".', $message, $class, $this->currentId)); } - $value->addTag('container.service_subscriber.locator', ['id' => (string) ServiceLocatorTagPass::register($this->container, $subscriberMap, $this->currentId)]); + $locatorRef = ServiceLocatorTagPass::register($this->container, $subscriberMap, $this->currentId); + + $value->addTag('container.service_subscriber.locator', ['id' => (string) $locatorRef]); + + $value->setBindings([ + PsrContainerInterface::class => new BoundArgument($locatorRef, false), + ServiceProviderInterface::class => new BoundArgument($locatorRef, false), + ] + $value->getBindings()); return parent::processValue($value); } diff --git a/lib/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php b/lib/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php index 03d9e1d8a5..75b36d227e 100644 --- a/lib/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php +++ b/lib/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php @@ -28,7 +28,7 @@ class RemovePrivateAliasesPass implements CompilerPassInterface public function process(ContainerBuilder $container) { foreach ($container->getAliases() as $id => $alias) { - if ($alias->isPublic() || $alias->isPrivate()) { + if ($alias->isPublic()) { continue; } diff --git a/lib/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php b/lib/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php index a1013f66c0..cf1a3ddc7a 100644 --- a/lib/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php +++ b/lib/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php @@ -12,74 +12,79 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; /** * Removes unused service definitions from the container. * * @author Johannes M. Schmitt + * @author Nicolas Grekas */ -class RemoveUnusedDefinitionsPass implements RepeatablePassInterface +class RemoveUnusedDefinitionsPass extends AbstractRecursivePass { - private $repeatedPass; - - /** - * {@inheritdoc} - */ - public function setRepeatedPass(RepeatedPass $repeatedPass) - { - $this->repeatedPass = $repeatedPass; - } + private $connectedIds = []; /** * Processes the ContainerBuilder to remove unused definitions. */ public function process(ContainerBuilder $container) { - $graph = $container->getCompiler()->getServiceReferenceGraph(); + try { + $this->enableExpressionProcessing(); + $this->container = $container; + $connectedIds = []; + $aliases = $container->getAliases(); - $hasChanged = false; - foreach ($container->getDefinitions() as $id => $definition) { - if ($definition->isPublic() || $definition->isPrivate()) { - continue; + foreach ($aliases as $id => $alias) { + if ($alias->isPublic()) { + $this->connectedIds[] = (string) $aliases[$id]; + } } - if ($graph->hasNode($id)) { - $edges = $graph->getNode($id)->getInEdges(); - $referencingAliases = []; - $sourceIds = []; - foreach ($edges as $edge) { - if ($edge->isWeak()) { - continue; - } - $node = $edge->getSourceNode(); - $sourceIds[] = $node->getId(); + foreach ($container->getDefinitions() as $id => $definition) { + if ($definition->isPublic()) { + $connectedIds[$id] = true; + $this->processValue($definition); + } + } - if ($node->isAlias()) { - $referencingAliases[] = $node->getValue(); + while ($this->connectedIds) { + $ids = $this->connectedIds; + $this->connectedIds = []; + foreach ($ids as $id) { + if (!isset($connectedIds[$id]) && $container->hasDefinition($id)) { + $connectedIds[$id] = true; + $this->processValue($container->getDefinition($id)); } } - $isReferenced = (\count(array_unique($sourceIds)) - \count($referencingAliases)) > 0; - } else { - $referencingAliases = []; - $isReferenced = false; } - if (1 === \count($referencingAliases) && false === $isReferenced) { - $container->setDefinition((string) reset($referencingAliases), $definition); - $definition->setPublic(!$definition->isPrivate()); - $definition->setPrivate(reset($referencingAliases)->isPrivate()); - $container->removeDefinition($id); - $container->log($this, sprintf('Removed service "%s"; reason: replaces alias %s.', $id, reset($referencingAliases))); - } elseif (0 === \count($referencingAliases) && false === $isReferenced) { - $container->removeDefinition($id); - $container->resolveEnvPlaceholders(serialize($definition)); - $container->log($this, sprintf('Removed service "%s"; reason: unused.', $id)); - $hasChanged = true; + foreach ($container->getDefinitions() as $id => $definition) { + if (!isset($connectedIds[$id])) { + $container->removeDefinition($id); + $container->resolveEnvPlaceholders(!$definition->hasErrors() ? serialize($definition) : $definition); + $container->log($this, sprintf('Removed service "%s"; reason: unused.', $id)); + } } - } - - if ($hasChanged) { - $this->repeatedPass->setRepeat(); + } finally { + $this->container = null; + $this->connectedIds = []; } } + + /** + * {@inheritdoc} + */ + protected function processValue($value, bool $isRoot = false) + { + if (!$value instanceof Reference) { + return parent::processValue($value, $isRoot); + } + + if (ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior()) { + $this->connectedIds[] = (string) $value; + } + + return $value; + } } diff --git a/lib/symfony/dependency-injection/Compiler/RepeatablePassInterface.php b/lib/symfony/dependency-injection/Compiler/RepeatablePassInterface.php deleted file mode 100644 index 2b88bfb917..0000000000 --- a/lib/symfony/dependency-injection/Compiler/RepeatablePassInterface.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DependencyInjection\Compiler; - -/** - * Interface that must be implemented by passes that are run as part of an - * RepeatedPass. - * - * @author Johannes M. Schmitt - */ -interface RepeatablePassInterface extends CompilerPassInterface -{ - public function setRepeatedPass(RepeatedPass $repeatedPass); -} diff --git a/lib/symfony/dependency-injection/Compiler/RepeatedPass.php b/lib/symfony/dependency-injection/Compiler/RepeatedPass.php deleted file mode 100644 index 3da1a0d5be..0000000000 --- a/lib/symfony/dependency-injection/Compiler/RepeatedPass.php +++ /dev/null @@ -1,79 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DependencyInjection\Compiler; - -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; - -/** - * A pass that might be run repeatedly. - * - * @author Johannes M. Schmitt - */ -class RepeatedPass implements CompilerPassInterface -{ - /** - * @var bool - */ - private $repeat = false; - - private $passes; - - /** - * @param RepeatablePassInterface[] $passes An array of RepeatablePassInterface objects - * - * @throws InvalidArgumentException when the passes don't implement RepeatablePassInterface - */ - public function __construct(array $passes) - { - foreach ($passes as $pass) { - if (!$pass instanceof RepeatablePassInterface) { - throw new InvalidArgumentException('$passes must be an array of RepeatablePassInterface.'); - } - - $pass->setRepeatedPass($this); - } - - $this->passes = $passes; - } - - /** - * Process the repeatable passes that run more than once. - */ - public function process(ContainerBuilder $container) - { - do { - $this->repeat = false; - foreach ($this->passes as $pass) { - $pass->process($container); - } - } while ($this->repeat); - } - - /** - * Sets if the pass should repeat. - */ - public function setRepeat() - { - $this->repeat = true; - } - - /** - * Returns the passes. - * - * @return RepeatablePassInterface[] An array of RepeatablePassInterface objects - */ - public function getPasses() - { - return $this->passes; - } -} diff --git a/lib/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php b/lib/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php index 472bf9415a..bb2ba0d540 100644 --- a/lib/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php +++ b/lib/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php @@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\Reference; /** @@ -24,6 +25,19 @@ use Symfony\Component\DependencyInjection\Reference; class ReplaceAliasByActualDefinitionPass extends AbstractRecursivePass { private $replacements; + private $autoAliasServicePass; + + /** + * @internal to be removed in Symfony 6.0 + * + * @return $this + */ + public function setAutoAliasServicePass(AutoAliasServicePass $autoAliasServicePass): self + { + $this->autoAliasServicePass = $autoAliasServicePass; + + return $this; + } /** * Process the Container to replace aliases with service definitions. @@ -35,15 +49,25 @@ class ReplaceAliasByActualDefinitionPass extends AbstractRecursivePass // First collect all alias targets that need to be replaced $seenAliasTargets = []; $replacements = []; + + $privateAliases = $this->autoAliasServicePass ? $this->autoAliasServicePass->getPrivateAliases() : []; + foreach ($privateAliases as $target) { + $target->setDeprecated('symfony/dependency-injection', '5.4', 'Accessing the "%alias_id%" service directly from the container is deprecated, use dependency injection instead.'); + } + foreach ($container->getAliases() as $definitionId => $target) { - $targetId = $container->normalizeId($target); + $targetId = (string) $target; // Special case: leave this target alone if ('service_container' === $targetId) { continue; } - // Check if target needs to be replaces + // Check if target needs to be replaced if (isset($replacements[$targetId])) { - $container->setAlias($definitionId, $replacements[$targetId])->setPublic($target->isPublic())->setPrivate($target->isPrivate()); + $container->setAlias($definitionId, $replacements[$targetId])->setPublic($target->isPublic()); + + if ($target->isDeprecated()) { + $container->getAlias($definitionId)->setDeprecated(...array_values($target->getDeprecation('%alias_id%'))); + } } // No need to process the same target twice if (isset($seenAliasTargets[$targetId])) { @@ -53,18 +77,25 @@ class ReplaceAliasByActualDefinitionPass extends AbstractRecursivePass $seenAliasTargets[$targetId] = true; try { $definition = $container->getDefinition($targetId); - } catch (InvalidArgumentException $e) { - throw new InvalidArgumentException(sprintf('Unable to replace alias "%s" with actual definition "%s".', $definitionId, $targetId), null, $e); + } catch (ServiceNotFoundException $e) { + if ('' !== $e->getId() && '@' === $e->getId()[0]) { + throw new ServiceNotFoundException($e->getId(), $e->getSourceId(), null, [substr($e->getId(), 1)]); + } + + throw $e; } - if ($definition->isPublic() || $definition->isPrivate()) { + if ($definition->isPublic()) { continue; } // Remove private definition and schedule for replacement - $definition->setPublic(!$target->isPrivate()); - $definition->setPrivate($target->isPrivate()); + $definition->setPublic($target->isPublic()); $container->setDefinition($definitionId, $definition); $container->removeDefinition($targetId); $replacements[$targetId] = $definitionId; + + if ($target->isPublic() && $target->isDeprecated()) { + $definition->addTag('container.private', $target->getDeprecation('%service_id%')); + } } $this->replacements = $replacements; @@ -75,9 +106,9 @@ class ReplaceAliasByActualDefinitionPass extends AbstractRecursivePass /** * {@inheritdoc} */ - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { - if ($value instanceof Reference && isset($this->replacements[$referenceId = $this->container->normalizeId($value)])) { + if ($value instanceof Reference && isset($this->replacements[$referenceId = (string) $value])) { // Perform the replacement $newId = $this->replacements[$referenceId]; $value = new Reference($newId, $value->getInvalidBehavior()); diff --git a/lib/symfony/dependency-injection/Compiler/ResolveBindingsPass.php b/lib/symfony/dependency-injection/Compiler/ResolveBindingsPass.php index 065dbb4b40..5bc379153a 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveBindingsPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveBindingsPass.php @@ -12,6 +12,9 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Argument\BoundArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; +use Symfony\Component\DependencyInjection\Attribute\Target; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; @@ -39,8 +42,39 @@ class ResolveBindingsPass extends AbstractRecursivePass try { parent::process($container); - foreach ($this->unusedBindings as list($key, $serviceId)) { - $message = sprintf('Unused binding "%s" in service "%s".', $key, $serviceId); + foreach ($this->unusedBindings as [$key, $serviceId, $bindingType, $file]) { + $argumentType = $argumentName = $message = null; + + if (str_contains($key, ' ')) { + [$argumentType, $argumentName] = explode(' ', $key, 2); + } elseif ('$' === $key[0]) { + $argumentName = $key; + } else { + $argumentType = $key; + } + + if ($argumentType) { + $message .= sprintf('of type "%s" ', $argumentType); + } + + if ($argumentName) { + $message .= sprintf('named "%s" ', $argumentName); + } + + if (BoundArgument::DEFAULTS_BINDING === $bindingType) { + $message .= 'under "_defaults"'; + } elseif (BoundArgument::INSTANCEOF_BINDING === $bindingType) { + $message .= 'under "_instanceof"'; + } else { + $message .= sprintf('for service "%s"', $serviceId); + } + + if ($file) { + $message .= sprintf(' in file "%s"', $file); + } + + $message = sprintf('A binding is configured for an argument %s, but no corresponding argument has been found. It may be unused and should be removed, or it may have a typo.', $message); + if ($this->errorMessages) { $message .= sprintf("\nCould be related to%s:", 1 < \count($this->errorMessages) ? ' one of' : ''); } @@ -59,11 +93,16 @@ class ResolveBindingsPass extends AbstractRecursivePass /** * {@inheritdoc} */ - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { - if ($value instanceof TypedReference && $value->getType() === $this->container->normalizeId($value)) { + if ($value instanceof TypedReference && $value->getType() === (string) $value) { // Already checked $bindings = $this->container->getDefinition($this->currentId)->getBindings(); + $name = $value->getName(); + + if (isset($name, $bindings[$name = $value.' $'.$name])) { + return $this->getBindingValue($bindings[$name]); + } if (isset($bindings[$value->getType()])) { return $this->getBindingValue($bindings[$value->getType()]); @@ -76,21 +115,32 @@ class ResolveBindingsPass extends AbstractRecursivePass return parent::processValue($value, $isRoot); } + $bindingNames = []; + foreach ($bindings as $key => $binding) { - list($bindingValue, $bindingId, $used) = $binding->getValues(); + [$bindingValue, $bindingId, $used, $bindingType, $file] = $binding->getValues(); if ($used) { $this->usedBindings[$bindingId] = true; unset($this->unusedBindings[$bindingId]); } elseif (!isset($this->usedBindings[$bindingId])) { - $this->unusedBindings[$bindingId] = [$key, $this->currentId]; + $this->unusedBindings[$bindingId] = [$key, $this->currentId, $bindingType, $file]; } - if (isset($key[0]) && '$' === $key[0]) { + if (preg_match('/^(?:(?:array|bool|float|int|string|iterable|([^ $]++)) )\$/', $key, $m)) { + $bindingNames[substr($key, \strlen($m[0]))] = $binding; + } + + if (!isset($m[1])) { continue; } - if (null !== $bindingValue && !$bindingValue instanceof Reference && !$bindingValue instanceof Definition) { - throw new InvalidArgumentException(sprintf('Invalid value for binding key "%s" for service "%s": expected null, an instance of "%s" or an instance of "%s", "%s" given.', $key, $this->currentId, Reference::class, Definition::class, \gettype($bindingValue))); + if (is_subclass_of($m[1], \UnitEnum::class)) { + $bindingNames[substr($key, \strlen($m[0]))] = $binding; + continue; + } + + if (null !== $bindingValue && !$bindingValue instanceof Reference && !$bindingValue instanceof Definition && !$bindingValue instanceof TaggedIteratorArgument && !$bindingValue instanceof ServiceLocatorArgument) { + throw new InvalidArgumentException(sprintf('Invalid value for binding key "%s" for service "%s": expected "%s", "%s", "%s", "%s" or null, "%s" given.', $key, $this->currentId, Reference::class, Definition::class, TaggedIteratorArgument::class, ServiceLocatorArgument::class, get_debug_type($bindingValue))); } } @@ -112,7 +162,7 @@ class ResolveBindingsPass extends AbstractRecursivePass } foreach ($calls as $i => $call) { - list($method, $arguments) = $call; + [$method, $arguments] = $call; if ($method instanceof \ReflectionFunctionAbstract) { $reflectionMethod = $method; @@ -132,19 +182,32 @@ class ResolveBindingsPass extends AbstractRecursivePass continue; } - if (\array_key_exists('$'.$parameter->name, $bindings)) { - $arguments[$key] = $this->getBindingValue($bindings['$'.$parameter->name]); + $typeHint = ProxyHelper::getTypeHint($reflectionMethod, $parameter); + $name = Target::parseName($parameter); + + if ($typeHint && \array_key_exists($k = ltrim($typeHint, '\\').' $'.$name, $bindings)) { + $arguments[$key] = $this->getBindingValue($bindings[$k]); continue; } - $typeHint = ProxyHelper::getTypeHint($reflectionMethod, $parameter, true); + if (\array_key_exists('$'.$name, $bindings)) { + $arguments[$key] = $this->getBindingValue($bindings['$'.$name]); - if (!isset($bindings[$typeHint])) { continue; } - $arguments[$key] = $this->getBindingValue($bindings[$typeHint]); + if ($typeHint && '\\' === $typeHint[0] && isset($bindings[$typeHint = substr($typeHint, 1)])) { + $arguments[$key] = $this->getBindingValue($bindings[$typeHint]); + + continue; + } + + if (isset($bindingNames[$name]) || isset($bindingNames[$parameter->name])) { + $bindingKey = array_search($binding, $bindings, true); + $argumentType = substr($bindingKey, 0, strpos($bindingKey, ' ')); + $this->errorMessages[] = sprintf('Did you forget to add the type "%s" to argument "$%s" of method "%s::%s()"?', $argumentType, $parameter->name, $reflectionMethod->class, $reflectionMethod->name); + } } if ($arguments !== $call[1]) { @@ -154,7 +217,7 @@ class ResolveBindingsPass extends AbstractRecursivePass } if ($constructor) { - list(, $arguments) = array_pop($calls); + [, $arguments] = array_pop($calls); if ($arguments !== $value->getArguments()) { $value->setArguments($arguments); @@ -168,9 +231,12 @@ class ResolveBindingsPass extends AbstractRecursivePass return parent::processValue($value, $isRoot); } + /** + * @return mixed + */ private function getBindingValue(BoundArgument $binding) { - list($bindingValue, $bindingId) = $binding->getValues(); + [$bindingValue, $bindingId] = $binding->getValues(); $this->usedBindings[$bindingId] = true; unset($this->unusedBindings[$bindingId]); diff --git a/lib/symfony/dependency-injection/Compiler/ResolveChildDefinitionsPass.php b/lib/symfony/dependency-injection/Compiler/ResolveChildDefinitionsPass.php index 539395a437..aefd2294a5 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveChildDefinitionsPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveChildDefinitionsPass.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\ExceptionInterface; use Symfony\Component\DependencyInjection\Exception\RuntimeException; @@ -28,7 +29,7 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass { private $currentPath; - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { if (!$value instanceof Definition) { return parent::processValue($value, $isRoot); @@ -52,11 +53,9 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass /** * Resolves the definition. * - * @return Definition - * * @throws RuntimeException When the definition is invalid */ - private function resolveDefinition(ChildDefinition $definition) + private function resolveDefinition(ChildDefinition $definition): Definition { try { return $this->doResolveDefinition($definition); @@ -71,7 +70,7 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass } } - private function doResolveDefinition(ChildDefinition $definition) + private function doResolveDefinition(ChildDefinition $definition): Definition { if (!$this->container->has($parent = $definition->getParent())) { throw new RuntimeException(sprintf('Parent definition "%s" does not exist.', $parent)); @@ -102,11 +101,9 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass $def->setArguments($parentDef->getArguments()); $def->setMethodCalls($parentDef->getMethodCalls()); $def->setProperties($parentDef->getProperties()); - if ($parentDef->getAutowiringTypes(false)) { - $def->setAutowiringTypes($parentDef->getAutowiringTypes(false)); - } if ($parentDef->isDeprecated()) { - $def->setDeprecated(true, $parentDef->getDeprecationMessage('%service_id%')); + $deprecation = $parentDef->getDeprecation('%service_id%'); + $def->setDeprecated($deprecation['package'], $deprecation['version'], $deprecation['message']); } $def->setFactory($parentDef->getFactory()); $def->setConfigurator($parentDef->getConfigurator()); @@ -118,6 +115,8 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass $def->setBindings($definition->getBindings() + $parentDef->getBindings()); + $def->setSynthetic($definition->isSynthetic()); + // overwrite with values specified in the decorator $changes = $definition->getChanges(); if (isset($changes['class'])) { @@ -135,13 +134,18 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass if (isset($changes['public'])) { $def->setPublic($definition->isPublic()); } else { - $def->setPrivate($definition->isPrivate() || $parentDef->isPrivate()); + $def->setPublic($parentDef->isPublic()); } if (isset($changes['lazy'])) { $def->setLazy($definition->isLazy()); } if (isset($changes['deprecated'])) { - $def->setDeprecated($definition->isDeprecated(), $definition->getDeprecationMessage('%service_id%')); + if ($definition->isDeprecated()) { + $deprecation = $definition->getDeprecation('%service_id%'); + $def->setDeprecated($deprecation['package'], $deprecation['version'], $deprecation['message']); + } else { + $def->setDeprecated(false); + } } if (isset($changes['autowired'])) { $def->setAutowired($definition->isAutowired()); @@ -154,7 +158,7 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass if (null === $decoratedService) { $def->setDecoratedService($decoratedService); } else { - $def->setDecoratedService($decoratedService[0], $decoratedService[1], $decoratedService[2]); + $def->setDecoratedService($decoratedService[0], $decoratedService[1], $decoratedService[2], $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE); } } @@ -162,7 +166,7 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass foreach ($definition->getArguments() as $k => $v) { if (is_numeric($k)) { $def->addArgument($v); - } elseif (0 === strpos($k, 'index_')) { + } elseif (str_starts_with($k, 'index_')) { $def->replaceArgument((int) substr($k, \strlen('index_')), $v); } else { $def->setArgument($k, $v); @@ -179,10 +183,8 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass $def->setMethodCalls(array_merge($def->getMethodCalls(), $calls)); } - // merge autowiring types - foreach ($definition->getAutowiringTypes(false) as $autowiringType) { - $def->addAutowiringType($autowiringType); - } + $def->addError($parentDef); + $def->addError($definition); // these attributes are always taken from the child $def->setAbstract($definition->isAbstract()); @@ -191,8 +193,12 @@ class ResolveChildDefinitionsPass extends AbstractRecursivePass // and it's not legal on an instanceof $def->setAutoconfigured($definition->isAutoconfigured()); + if (!$def->hasTag('proxy')) { + foreach ($parentDef->getTag('proxy') as $v) { + $def->addTag('proxy', $v); + } + } + return $def; } } - -class_alias(ResolveChildDefinitionsPass::class, ResolveDefinitionTemplatesPass::class); diff --git a/lib/symfony/dependency-injection/Compiler/ResolveClassPass.php b/lib/symfony/dependency-injection/Compiler/ResolveClassPass.php index b1c1b4f884..e67a2a8ed7 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveClassPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveClassPass.php @@ -20,8 +20,6 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; */ class ResolveClassPass implements CompilerPassInterface { - private $changes = []; - /** * {@inheritdoc} */ @@ -33,24 +31,10 @@ class ResolveClassPass implements CompilerPassInterface } if (preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)++$/', $id)) { if ($definition instanceof ChildDefinition && !class_exists($id)) { - throw new InvalidArgumentException(sprintf('Service definition "%s" has a parent but no class, and its name looks like a FQCN. Either the class is missing or you want to inherit it from the parent service. To resolve this ambiguity, please rename this service to a non-FQCN (e.g. using dots), or create the missing class.', $id)); + throw new InvalidArgumentException(sprintf('Service definition "%s" has a parent but no class, and its name looks like an FQCN. Either the class is missing or you want to inherit it from the parent service. To resolve this ambiguity, please rename this service to a non-FQCN (e.g. using dots), or create the missing class.', $id)); } - $this->changes[strtolower($id)] = $id; $definition->setClass($id); } } } - - /** - * @internal - * - * @deprecated since 3.3, to be removed in 4.0. - */ - public function getChanges() - { - $changes = $this->changes; - $this->changes = []; - - return $changes; - } } diff --git a/lib/symfony/dependency-injection/Compiler/ResolveDecoratorStackPass.php b/lib/symfony/dependency-injection/Compiler/ResolveDecoratorStackPass.php new file mode 100644 index 0000000000..4914b3ac9d --- /dev/null +++ b/lib/symfony/dependency-injection/Compiler/ResolveDecoratorStackPass.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Nicolas Grekas + */ +class ResolveDecoratorStackPass implements CompilerPassInterface +{ + private $tag; + + public function __construct(string $tag = 'container.stack') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->tag = $tag; + } + + public function process(ContainerBuilder $container) + { + $stacks = []; + + foreach ($container->findTaggedServiceIds($this->tag) as $id => $tags) { + $definition = $container->getDefinition($id); + + if (!$definition instanceof ChildDefinition) { + throw new InvalidArgumentException(sprintf('Invalid service "%s": only definitions with a "parent" can have the "%s" tag.', $id, $this->tag)); + } + + if (!$stack = $definition->getArguments()) { + throw new InvalidArgumentException(sprintf('Invalid service "%s": the stack of decorators is empty.', $id)); + } + + $stacks[$id] = $stack; + } + + if (!$stacks) { + return; + } + + $resolvedDefinitions = []; + + foreach ($container->getDefinitions() as $id => $definition) { + if (!isset($stacks[$id])) { + $resolvedDefinitions[$id] = $definition; + continue; + } + + foreach (array_reverse($this->resolveStack($stacks, [$id]), true) as $k => $v) { + $resolvedDefinitions[$k] = $v; + } + + $alias = $container->setAlias($id, $k); + + if ($definition->getChanges()['public'] ?? false) { + $alias->setPublic($definition->isPublic()); + } + + if ($definition->isDeprecated()) { + $alias->setDeprecated(...array_values($definition->getDeprecation('%alias_id%'))); + } + } + + $container->setDefinitions($resolvedDefinitions); + } + + private function resolveStack(array $stacks, array $path): array + { + $definitions = []; + $id = end($path); + $prefix = '.'.$id.'.'; + + if (!isset($stacks[$id])) { + return [$id => new ChildDefinition($id)]; + } + + if (key($path) !== $searchKey = array_search($id, $path)) { + throw new ServiceCircularReferenceException($id, \array_slice($path, $searchKey)); + } + + foreach ($stacks[$id] as $k => $definition) { + if ($definition instanceof ChildDefinition && isset($stacks[$definition->getParent()])) { + $path[] = $definition->getParent(); + $definition = unserialize(serialize($definition)); // deep clone + } elseif ($definition instanceof Definition) { + $definitions[$decoratedId = $prefix.$k] = $definition; + continue; + } elseif ($definition instanceof Reference || $definition instanceof Alias) { + $path[] = (string) $definition; + } else { + throw new InvalidArgumentException(sprintf('Invalid service "%s": unexpected value of type "%s" found in the stack of decorators.', $id, get_debug_type($definition))); + } + + $p = $prefix.$k; + + foreach ($this->resolveStack($stacks, $path) as $k => $v) { + $definitions[$decoratedId = $p.$k] = $definition instanceof ChildDefinition ? $definition->setParent($k) : new ChildDefinition($k); + $definition = null; + } + array_pop($path); + } + + if (1 === \count($path)) { + foreach ($definitions as $k => $definition) { + $definition->setPublic(false)->setTags([])->setDecoratedService($decoratedId); + } + $definition->setDecoratedService(null); + } + + return $definitions; + } +} diff --git a/lib/symfony/dependency-injection/Compiler/ResolveDefinitionTemplatesPass.php b/lib/symfony/dependency-injection/Compiler/ResolveDefinitionTemplatesPass.php deleted file mode 100644 index 79fca8d5ec..0000000000 --- a/lib/symfony/dependency-injection/Compiler/ResolveDefinitionTemplatesPass.php +++ /dev/null @@ -1,29 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DependencyInjection\Compiler; - -@trigger_error('The '.__NAMESPACE__.'\ResolveDefinitionTemplatesPass class is deprecated since Symfony 3.4 and will be removed in 4.0. Use the ResolveChildDefinitionsPass class instead.', \E_USER_DEPRECATED); - -class_exists(ResolveChildDefinitionsPass::class); - -if (false) { - /** - * This definition decorates another definition. - * - * @author Johannes M. Schmitt - * - * @deprecated The ResolveDefinitionTemplatesPass class is deprecated since version 3.4 and will be removed in 4.0. Use the ResolveChildDefinitionsPass class instead. - */ - class ResolveDefinitionTemplatesPass extends AbstractRecursivePass - { - } -} diff --git a/lib/symfony/dependency-injection/Compiler/ResolveEnvPlaceholdersPass.php b/lib/symfony/dependency-injection/Compiler/ResolveEnvPlaceholdersPass.php index 9e1edd4d31..ea52b14592 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveEnvPlaceholdersPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveEnvPlaceholdersPass.php @@ -18,7 +18,7 @@ use Symfony\Component\DependencyInjection\Definition; */ class ResolveEnvPlaceholdersPass extends AbstractRecursivePass { - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { if (\is_string($value)) { return $this->container->resolveEnvPlaceholders($value, true); diff --git a/lib/symfony/dependency-injection/Compiler/ResolveFactoryClassPass.php b/lib/symfony/dependency-injection/Compiler/ResolveFactoryClassPass.php index 848da7f2bd..23f535b71d 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveFactoryClassPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveFactoryClassPass.php @@ -22,7 +22,7 @@ class ResolveFactoryClassPass extends AbstractRecursivePass /** * {@inheritdoc} */ - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { if ($value instanceof Definition && \is_array($factory = $value->getFactory()) && null === $factory[0]) { if (null === $class = $value->getClass()) { diff --git a/lib/symfony/dependency-injection/Compiler/ResolveHotPathPass.php b/lib/symfony/dependency-injection/Compiler/ResolveHotPathPass.php index 4e025113ac..dee2dc6bef 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveHotPathPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveHotPathPass.php @@ -26,8 +26,12 @@ class ResolveHotPathPass extends AbstractRecursivePass private $tagName; private $resolvedIds = []; - public function __construct($tagName = 'container.hot_path') + public function __construct(string $tagName = 'container.hot_path') { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + $this->tagName = $tagName; } @@ -47,19 +51,34 @@ class ResolveHotPathPass extends AbstractRecursivePass /** * {@inheritdoc} */ - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { if ($value instanceof ArgumentInterface) { return $value; } - if ($value instanceof Definition && $isRoot && (isset($this->resolvedIds[$this->currentId]) || !$value->hasTag($this->tagName) || $value->isDeprecated())) { - return $value->isDeprecated() ? $value->clearTag($this->tagName) : $value; + + if ($value instanceof Definition && $isRoot) { + if ($value->isDeprecated()) { + return $value->clearTag($this->tagName); + } + + $this->resolvedIds[$this->currentId] = true; + + if (!$value->hasTag($this->tagName)) { + return $value; + } } - if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->has($id = $this->container->normalizeId($value))) { - $definition = $this->container->findDefinition($id); - if (!$definition->hasTag($this->tagName) && !$definition->isDeprecated()) { - $this->resolvedIds[$id] = true; - $definition->addTag($this->tagName); + + if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->hasDefinition($id = (string) $value)) { + $definition = $this->container->getDefinition($id); + + if ($definition->isDeprecated() || $definition->hasTag($this->tagName)) { + return $value; + } + + $definition->addTag($this->tagName); + + if (isset($this->resolvedIds[$id])) { parent::processValue($definition, false); } diff --git a/lib/symfony/dependency-injection/Compiler/ResolveInstanceofConditionalsPass.php b/lib/symfony/dependency-injection/Compiler/ResolveInstanceofConditionalsPass.php index 6268ed9ed0..b211b84e13 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveInstanceofConditionalsPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveInstanceofConditionalsPass.php @@ -33,21 +33,24 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface if ($definition->getArguments()) { throw new InvalidArgumentException(sprintf('Autoconfigured instanceof for type "%s" defines arguments but these are not supported and should be removed.', $interface)); } - if ($definition->getMethodCalls()) { - throw new InvalidArgumentException(sprintf('Autoconfigured instanceof for type "%s" defines method calls but these are not supported and should be removed.', $interface)); - } + } + + $tagsToKeep = []; + + if ($container->hasParameter('container.behavior_describing_tags')) { + $tagsToKeep = $container->getParameter('container.behavior_describing_tags'); } foreach ($container->getDefinitions() as $id => $definition) { - if ($definition instanceof ChildDefinition) { - // don't apply "instanceof" to children: it will be applied to their parent - continue; - } - $container->setDefinition($id, $this->processDefinition($container, $id, $definition)); + $container->setDefinition($id, $this->processDefinition($container, $id, $definition, $tagsToKeep)); + } + + if ($container->hasParameter('container.behavior_describing_tags')) { + $container->getParameterBag()->remove('container.behavior_describing_tags'); } } - private function processDefinition(ContainerBuilder $container, $id, Definition $definition) + private function processDefinition(ContainerBuilder $container, string $id, Definition $definition, array $tagsToKeep): Definition { $instanceofConditionals = $definition->getInstanceofConditionals(); $autoconfiguredInstanceof = $definition->isAutoconfigured() ? $container->getAutoconfiguredInstanceof() : []; @@ -62,12 +65,15 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface $conditionals = $this->mergeConditionals($autoconfiguredInstanceof, $instanceofConditionals, $container); $definition->setInstanceofConditionals([]); - $parent = $shared = null; + $shared = null; $instanceofTags = []; + $instanceofCalls = []; + $instanceofBindings = []; $reflectionClass = null; + $parent = $definition instanceof ChildDefinition ? $definition->getParent() : null; foreach ($conditionals as $interface => $instanceofDefs) { - if ($interface !== $class && !(null === $reflectionClass ? $reflectionClass = ($container->getReflectionClass($class, false) ?: false) : $reflectionClass)) { + if ($interface !== $class && !($reflectionClass ?? $reflectionClass = $container->getReflectionClass($class, false) ?: false)) { continue; } @@ -78,11 +84,19 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface foreach ($instanceofDefs as $key => $instanceofDef) { /** @var ChildDefinition $instanceofDef */ $instanceofDef = clone $instanceofDef; - $instanceofDef->setAbstract(true)->setParent($parent ?: 'abstract.instanceof.'.$id); - $parent = 'instanceof.'.$interface.'.'.$key.'.'.$id; + $instanceofDef->setAbstract(true)->setParent($parent ?: '.abstract.instanceof.'.$id); + $parent = '.instanceof.'.$interface.'.'.$key.'.'.$id; $container->setDefinition($parent, $instanceofDef); $instanceofTags[] = $instanceofDef->getTags(); + $instanceofBindings = $instanceofDef->getBindings() + $instanceofBindings; + + foreach ($instanceofDef->getMethodCalls() as $methodCall) { + $instanceofCalls[] = $methodCall; + } + $instanceofDef->setTags([]); + $instanceofDef->setMethodCalls([]); + $instanceofDef->setBindings([]); if (isset($instanceofDef->getChanges()['shared'])) { $shared = $instanceofDef->isShared(); @@ -92,13 +106,16 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface if ($parent) { $bindings = $definition->getBindings(); - $abstract = $container->setDefinition('abstract.instanceof.'.$id, $definition); - - // cast Definition to ChildDefinition + $abstract = $container->setDefinition('.abstract.instanceof.'.$id, $definition); $definition->setBindings([]); $definition = serialize($definition); - $definition = substr_replace($definition, '53', 2, 2); - $definition = substr_replace($definition, 'Child', 44, 0); + + if (Definition::class === \get_class($abstract)) { + // cast Definition to ChildDefinition + $definition = substr_replace($definition, '53', 2, 2); + $definition = substr_replace($definition, 'Child', 44, 0); + } + /** @var ChildDefinition $definition */ $definition = unserialize($definition); $definition->setParent($parent); @@ -106,19 +123,23 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface $definition->setShared($shared); } + // Don't add tags to service decorators $i = \count($instanceofTags); while (0 <= --$i) { foreach ($instanceofTags[$i] as $k => $v) { - foreach ($v as $v) { - if ($definition->hasTag($k) && \in_array($v, $definition->getTag($k))) { - continue; + if (null === $definition->getDecoratedService() || \in_array($k, $tagsToKeep, true)) { + foreach ($v as $v) { + if ($definition->hasTag($k) && \in_array($v, $definition->getTag($k))) { + continue; + } + $definition->addTag($k, $v); } - $definition->addTag($k, $v); } } } - $definition->setBindings($bindings); + $definition->setMethodCalls(array_merge($instanceofCalls, $definition->getMethodCalls())); + $definition->setBindings($bindings + $instanceofBindings); // reset fields with "merge" behavior $abstract @@ -133,7 +154,7 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface return $definition; } - private function mergeConditionals(array $autoconfiguredInstanceof, array $instanceofConditionals, ContainerBuilder $container) + private function mergeConditionals(array $autoconfiguredInstanceof, array $instanceofConditionals, ContainerBuilder $container): array { // make each value an array of ChildDefinition $conditionals = array_map(function ($childDef) { return [$childDef]; }, $autoconfiguredInstanceof); diff --git a/lib/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php b/lib/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php index f1a1475aeb..948de421f7 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php @@ -17,7 +17,9 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; /** * Emulates the invalid behavior if the reference is not found within the @@ -29,6 +31,7 @@ class ResolveInvalidReferencesPass implements CompilerPassInterface { private $container; private $signalingException; + private $currentId; /** * Process the ContainerBuilder to resolve invalid references. @@ -39,7 +42,9 @@ class ResolveInvalidReferencesPass implements CompilerPassInterface $this->signalingException = new RuntimeException('Invalid reference.'); try { - $this->processValue($container->getDefinitions(), 1); + foreach ($container->getDefinitions() as $this->currentId => $definition) { + $this->processValue($definition); + } } finally { $this->container = $this->signalingException = null; } @@ -48,9 +53,11 @@ class ResolveInvalidReferencesPass implements CompilerPassInterface /** * Processes arguments to determine invalid references. * + * @return mixed + * * @throws RuntimeException When an invalid reference is found */ - private function processValue($value, $rootLevel = 0, $level = 0) + private function processValue($value, int $rootLevel = 0, int $level = 0) { if ($value instanceof ServiceClosureArgument) { $value->setValues($this->processValue($value->getValues(), 1, 1)); @@ -90,11 +97,29 @@ class ResolveInvalidReferencesPass implements CompilerPassInterface $value = array_values($value); } } elseif ($value instanceof Reference) { - if ($this->container->has($value)) { + if ($this->container->has($id = (string) $value)) { return $value; } + + $currentDefinition = $this->container->getDefinition($this->currentId); + + // resolve decorated service behavior depending on decorator service + if ($currentDefinition->innerServiceId === $id && ContainerInterface::NULL_ON_INVALID_REFERENCE === $currentDefinition->decorationOnInvalid) { + return null; + } + $invalidBehavior = $value->getInvalidBehavior(); + if (ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior && $value instanceof TypedReference && !$this->container->has($id)) { + $e = new ServiceNotFoundException($id, $this->currentId); + + // since the error message varies by $id and $this->currentId, so should the id of the dummy errored definition + $this->container->register($id = sprintf('.errored.%s.%s', $this->currentId, $id), $value->getType()) + ->addError($e->getMessage()); + + return new TypedReference($id, $value->getType(), $value->getInvalidBehavior()); + } + // resolve invalid behavior if (ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) { $value = null; diff --git a/lib/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php b/lib/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php index 225014f1e4..c1c5748e8d 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveNamedArgumentsPass.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; @@ -26,8 +27,12 @@ class ResolveNamedArgumentsPass extends AbstractRecursivePass /** * {@inheritdoc} */ - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { + if ($value instanceof AbstractArgument && $value->getText().'.' === $value->getTextWithContext()) { + $value->setContext(sprintf('A value found in service "%s"', $this->currentId)); + } + if (!$value instanceof Definition) { return parent::processValue($value, $isRoot); } @@ -36,11 +41,15 @@ class ResolveNamedArgumentsPass extends AbstractRecursivePass $calls[] = ['__construct', $value->getArguments()]; foreach ($calls as $i => $call) { - list($method, $arguments) = $call; + [$method, $arguments] = $call; $parameters = null; $resolvedArguments = []; foreach ($arguments as $key => $argument) { + if ($argument instanceof AbstractArgument && $argument->getText().'.' === $argument->getTextWithContext()) { + $argument->setContext(sprintf('Argument '.(\is_int($key) ? 1 + $key : '"%3$s"').' of '.('__construct' === $method ? 'service "%s"' : 'method call "%s::%s()"'), $this->currentId, $method, $key)); + } + if (\is_int($key)) { $resolvedArguments[$key] = $argument; continue; @@ -53,10 +62,20 @@ class ResolveNamedArgumentsPass extends AbstractRecursivePass $parameters = $r->getParameters(); } + if (isset($key[0]) && '$' !== $key[0] && !class_exists($key) && !interface_exists($key, false)) { + throw new InvalidArgumentException(sprintf('Invalid service "%s": did you forget to add the "$" prefix to argument "%s"?', $this->currentId, $key)); + } + if (isset($key[0]) && '$' === $key[0]) { foreach ($parameters as $j => $p) { if ($key === '$'.$p->name) { - $resolvedArguments[$j] = $argument; + if ($p->isVariadic() && \is_array($argument)) { + foreach ($argument as $variadicArgument) { + $resolvedArguments[$j++] = $variadicArgument; + } + } else { + $resolvedArguments[$j] = $argument; + } continue 2; } @@ -66,7 +85,7 @@ class ResolveNamedArgumentsPass extends AbstractRecursivePass } if (null !== $argument && !$argument instanceof Reference && !$argument instanceof Definition) { - throw new InvalidArgumentException(sprintf('Invalid service "%s": the value of argument "%s" of method "%s()" must be null, an instance of "%s" or an instance of "%s", "%s" given.', $this->currentId, $key, $class !== $this->currentId ? $class.'::'.$method : $method, Reference::class, Definition::class, \gettype($argument))); + throw new InvalidArgumentException(sprintf('Invalid service "%s": the value of argument "%s" of method "%s()" must be null, an instance of "%s" or an instance of "%s", "%s" given.', $this->currentId, $key, $class !== $this->currentId ? $class.'::'.$method : $method, Reference::class, Definition::class, get_debug_type($argument))); } $typeFound = false; @@ -88,7 +107,7 @@ class ResolveNamedArgumentsPass extends AbstractRecursivePass } } - list(, $arguments) = array_pop($calls); + [, $arguments] = array_pop($calls); if ($arguments !== $value->getArguments()) { $value->setArguments($arguments); @@ -97,6 +116,12 @@ class ResolveNamedArgumentsPass extends AbstractRecursivePass $value->setMethodCalls($calls); } + foreach ($value->getProperties() as $key => $argument) { + if ($argument instanceof AbstractArgument && $argument->getText().'.' === $argument->getTextWithContext()) { + $argument->setContext(sprintf('Property "%s" of service "%s"', $key, $this->currentId)); + } + } + return parent::processValue($value, $isRoot); } } diff --git a/lib/symfony/dependency-injection/Compiler/ResolveNoPreloadPass.php b/lib/symfony/dependency-injection/Compiler/ResolveNoPreloadPass.php new file mode 100644 index 0000000000..016be55b3d --- /dev/null +++ b/lib/symfony/dependency-injection/Compiler/ResolveNoPreloadPass.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Propagate the "container.no_preload" tag. + * + * @author Nicolas Grekas + */ +class ResolveNoPreloadPass extends AbstractRecursivePass +{ + private const DO_PRELOAD_TAG = '.container.do_preload'; + + private $tagName; + private $resolvedIds = []; + + public function __construct(string $tagName = 'container.no_preload') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->tagName = $tagName; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $this->container = $container; + + try { + foreach ($container->getDefinitions() as $id => $definition) { + if ($definition->isPublic() && !$definition->isPrivate() && !isset($this->resolvedIds[$id])) { + $this->resolvedIds[$id] = true; + $this->processValue($definition, true); + } + } + + foreach ($container->getAliases() as $alias) { + if ($alias->isPublic() && !$alias->isPrivate() && !isset($this->resolvedIds[$id = (string) $alias]) && $container->hasDefinition($id)) { + $this->resolvedIds[$id] = true; + $this->processValue($container->getDefinition($id), true); + } + } + } finally { + $this->resolvedIds = []; + $this->container = null; + } + + foreach ($container->getDefinitions() as $definition) { + if ($definition->hasTag(self::DO_PRELOAD_TAG)) { + $definition->clearTag(self::DO_PRELOAD_TAG); + } elseif (!$definition->isDeprecated() && !$definition->hasErrors()) { + $definition->addTag($this->tagName); + } + } + } + + /** + * {@inheritdoc} + */ + protected function processValue($value, bool $isRoot = false) + { + if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->hasDefinition($id = (string) $value)) { + $definition = $this->container->getDefinition($id); + + if (!isset($this->resolvedIds[$id]) && (!$definition->isPublic() || $definition->isPrivate())) { + $this->resolvedIds[$id] = true; + $this->processValue($definition, true); + } + + return $value; + } + + if (!$value instanceof Definition) { + return parent::processValue($value, $isRoot); + } + + if ($value->hasTag($this->tagName) || $value->isDeprecated() || $value->hasErrors()) { + return $value; + } + + if ($isRoot) { + $value->addTag(self::DO_PRELOAD_TAG); + } + + return parent::processValue($value, $isRoot); + } +} diff --git a/lib/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php b/lib/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php index 32eb6a3a76..0099a3bbc7 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php @@ -60,7 +60,7 @@ class ResolveParameterPlaceHoldersPass extends AbstractRecursivePass $this->bag = null; } - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { if (\is_string($value)) { try { @@ -85,6 +85,11 @@ class ResolveParameterPlaceHoldersPass extends AbstractRecursivePass if (isset($changes['file'])) { $value->setFile($this->bag->resolveValue($value->getFile())); } + $tags = $value->getTags(); + if (isset($tags['proxy'])) { + $tags['proxy'] = $this->bag->resolveValue($tags['proxy']); + $value->setTags($tags); + } } $value = parent::processValue($value, $isRoot); diff --git a/lib/symfony/dependency-injection/Compiler/ResolvePrivatesPass.php b/lib/symfony/dependency-injection/Compiler/ResolvePrivatesPass.php index 1bd993458a..b63e3f5c2f 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolvePrivatesPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolvePrivatesPass.php @@ -11,10 +11,14 @@ namespace Symfony\Component\DependencyInjection\Compiler; +trigger_deprecation('symfony/dependency-injection', '5.2', 'The "%s" class is deprecated.', ResolvePrivatesPass::class); + use Symfony\Component\DependencyInjection\ContainerBuilder; /** * @author Nicolas Grekas + * + * @deprecated since Symfony 5.2 */ class ResolvePrivatesPass implements CompilerPassInterface { diff --git a/lib/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php b/lib/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php index 2559dcf10c..e59893ff71 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php @@ -30,9 +30,11 @@ class ResolveReferencesToAliasesPass extends AbstractRecursivePass parent::process($container); foreach ($container->getAliases() as $id => $alias) { - $aliasId = $container->normalizeId($alias); + $aliasId = (string) $alias; + $this->currentId = $id; + if ($aliasId !== $defId = $this->getDefinitionId($aliasId, $container)) { - $container->setAlias($id, $defId)->setPublic($alias->isPublic())->setPrivate($alias->isPrivate()); + $container->setAlias($id, $defId)->setPublic($alias->isPublic()); } } } @@ -40,36 +42,42 @@ class ResolveReferencesToAliasesPass extends AbstractRecursivePass /** * {@inheritdoc} */ - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { - if ($value instanceof Reference) { - $defId = $this->getDefinitionId($id = $this->container->normalizeId($value), $this->container); + if (!$value instanceof Reference) { + return parent::processValue($value, $isRoot); + } - if ($defId !== $id) { - return new Reference($defId, $value->getInvalidBehavior()); + $defId = $this->getDefinitionId($id = (string) $value, $this->container); + + return $defId !== $id ? new Reference($defId, $value->getInvalidBehavior()) : $value; + } + + private function getDefinitionId(string $id, ContainerBuilder $container): string + { + if (!$container->hasAlias($id)) { + return $id; + } + + $alias = $container->getAlias($id); + + if ($alias->isDeprecated()) { + $referencingDefinition = $container->hasDefinition($this->currentId) ? $container->getDefinition($this->currentId) : $container->getAlias($this->currentId); + if (!$referencingDefinition->isDeprecated()) { + $deprecation = $alias->getDeprecation($id); + trigger_deprecation($deprecation['package'], $deprecation['version'], rtrim($deprecation['message'], '. ').'. It is being referenced by the "%s" '.($container->hasDefinition($this->currentId) ? 'service.' : 'alias.'), $this->currentId); } } - return parent::processValue($value); - } - - /** - * Resolves an alias into a definition id. - * - * @param string $id The definition or alias id to resolve - * - * @return string The definition id with aliases resolved - */ - private function getDefinitionId($id, ContainerBuilder $container) - { $seen = []; - while ($container->hasAlias($id)) { + do { if (isset($seen[$id])) { throw new ServiceCircularReferenceException($id, array_merge(array_keys($seen), [$id])); } + $seen[$id] = true; - $id = $container->normalizeId($container->getAlias($id)); - } + $id = (string) $container->getAlias($id); + } while ($container->hasAlias($id)); return $id; } diff --git a/lib/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php b/lib/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php index ccc80a443e..518c03d7e7 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveServiceSubscribersPass.php @@ -14,6 +14,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Psr\Container\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Contracts\Service\ServiceProviderInterface; /** * Compiler pass to inject their service locator to service subscribers. @@ -24,9 +25,9 @@ class ResolveServiceSubscribersPass extends AbstractRecursivePass { private $serviceLocator; - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { - if ($value instanceof Reference && $this->serviceLocator && ContainerInterface::class === $this->container->normalizeId($value)) { + if ($value instanceof Reference && $this->serviceLocator && \in_array((string) $value, [ContainerInterface::class, ServiceProviderInterface::class], true)) { return new Reference($this->serviceLocator); } diff --git a/lib/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php b/lib/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php index 009cee9bf5..48a034a847 100644 --- a/lib/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php +++ b/lib/symfony/dependency-injection/Compiler/ResolveTaggedIteratorArgumentPass.php @@ -25,13 +25,13 @@ class ResolveTaggedIteratorArgumentPass extends AbstractRecursivePass /** * {@inheritdoc} */ - protected function processValue($value, $isRoot = false) + protected function processValue($value, bool $isRoot = false) { if (!$value instanceof TaggedIteratorArgument) { return parent::processValue($value, $isRoot); } - $value->setValues($this->findAndSortTaggedServices($value->getTag(), $this->container)); + $value->setValues($this->findAndSortTaggedServices($value, $this->container)); return $value; } diff --git a/lib/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.php b/lib/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.php index a7427c5a5b..faa7b57e45 100644 --- a/lib/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.php +++ b/lib/symfony/dependency-injection/Compiler/ServiceLocatorTagPass.php @@ -13,6 +13,8 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; @@ -26,8 +28,18 @@ use Symfony\Component\DependencyInjection\ServiceLocator; */ final class ServiceLocatorTagPass extends AbstractRecursivePass { - protected function processValue($value, $isRoot = false) + use PriorityTaggedServiceTrait; + + protected function processValue($value, bool $isRoot = false) { + if ($value instanceof ServiceLocatorArgument) { + if ($value->getTaggedIteratorArgument()) { + $value->setValues($this->findAndSortTaggedServices($value->getTaggedIteratorArgument(), $this->container)); + } + + return self::register($this->container, $value->getValues()); + } + if (!$value instanceof Definition || !$value->hasTag('container.service_locator')) { return parent::processValue($value, $isRoot); } @@ -36,36 +48,41 @@ final class ServiceLocatorTagPass extends AbstractRecursivePass $value->setClass(ServiceLocator::class); } - $arguments = $value->getArguments(); - if (!isset($arguments[0]) || !\is_array($arguments[0])) { + $services = $value->getArguments()[0] ?? null; + + if ($services instanceof TaggedIteratorArgument) { + $services = $this->findAndSortTaggedServices($services, $this->container); + } + + if (!\is_array($services)) { throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": an array of references is expected as first argument when the "container.service_locator" tag is set.', $this->currentId)); } $i = 0; - foreach ($arguments[0] as $k => $v) { + foreach ($services as $k => $v) { if ($v instanceof ServiceClosureArgument) { continue; } if (!$v instanceof Reference) { - throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": an array of references is expected as first argument when the "container.service_locator" tag is set, "%s" found for key "%s".', $this->currentId, \is_object($v) ? \get_class($v) : \gettype($v), $k)); + throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": an array of references is expected as first argument when the "container.service_locator" tag is set, "%s" found for key "%s".', $this->currentId, get_debug_type($v), $k)); } if ($i === $k) { - unset($arguments[0][$k]); + unset($services[$k]); $k = (string) $v; ++$i; } elseif (\is_int($k)) { $i = null; } - $arguments[0][$k] = new ServiceClosureArgument($v); + $services[$k] = new ServiceClosureArgument($v); } - ksort($arguments[0]); + ksort($services); - $value->setArguments($arguments); + $value->setArgument(0, $services); - $id = 'service_locator.'.ContainerBuilder::hash($value); + $id = '.service_locator.'.ContainerBuilder::hash($value); if ($isRoot) { if ($id !== $this->currentId) { @@ -82,30 +99,25 @@ final class ServiceLocatorTagPass extends AbstractRecursivePass /** * @param Reference[] $refMap - * @param string|null $callerId - * - * @return Reference */ - public static function register(ContainerBuilder $container, array $refMap, $callerId = null) + public static function register(ContainerBuilder $container, array $refMap, string $callerId = null): Reference { foreach ($refMap as $id => $ref) { if (!$ref instanceof Reference) { - throw new InvalidArgumentException(sprintf('Invalid service locator definition: only services can be referenced, "%s" found for key "%s". Inject parameter values using constructors instead.', \is_object($ref) ? \get_class($ref) : \gettype($ref), $id)); + throw new InvalidArgumentException(sprintf('Invalid service locator definition: only services can be referenced, "%s" found for key "%s". Inject parameter values using constructors instead.', get_debug_type($ref), $id)); } $refMap[$id] = new ServiceClosureArgument($ref); } - ksort($refMap); $locator = (new Definition(ServiceLocator::class)) ->addArgument($refMap) - ->setPublic(false) ->addTag('container.service_locator'); if (null !== $callerId && $container->hasDefinition($callerId)) { $locator->setBindings($container->getDefinition($callerId)->getBindings()); } - if (!$container->hasDefinition($id = 'service_locator.'.ContainerBuilder::hash($locator))) { + if (!$container->hasDefinition($id = '.service_locator.'.ContainerBuilder::hash($locator))) { $container->setDefinition($id, $locator); } @@ -115,8 +127,8 @@ final class ServiceLocatorTagPass extends AbstractRecursivePass // to have them specialized per consumer service, we use a cloning factory // to derivate customized instances from the prototype one. $container->register($id .= '.'.$callerId, ServiceLocator::class) - ->setPublic(false) ->setFactory([new Reference($locatorId), 'withContext']) + ->addTag('container.service_locator_context', ['id' => $callerId]) ->addArgument($callerId) ->addArgument(new Reference('service_container')); } diff --git a/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php b/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php index e419e297e8..1225514c24 100644 --- a/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php +++ b/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; /** * This is a directed graph of your services. @@ -21,7 +22,7 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; * * @author Johannes M. Schmitt * - * @final since version 3.4 + * @final */ class ServiceReferenceGraph { @@ -30,14 +31,7 @@ class ServiceReferenceGraph */ private $nodes = []; - /** - * Checks if the graph has a specific node. - * - * @param string $id Id to check - * - * @return bool - */ - public function hasNode($id) + public function hasNode(string $id): bool { return isset($this->nodes[$id]); } @@ -45,13 +39,9 @@ class ServiceReferenceGraph /** * Gets a node by identifier. * - * @param string $id The id to retrieve - * - * @return ServiceReferenceGraphNode - * * @throws InvalidArgumentException if no node matches the supplied identifier */ - public function getNode($id) + public function getNode(string $id): ServiceReferenceGraphNode { if (!isset($this->nodes[$id])) { throw new InvalidArgumentException(sprintf('There is no node with id "%s".', $id)); @@ -65,7 +55,7 @@ class ServiceReferenceGraph * * @return ServiceReferenceGraphNode[] */ - public function getNodes() + public function getNodes(): array { return $this->nodes; } @@ -83,19 +73,9 @@ class ServiceReferenceGraph /** * Connects 2 nodes together in the Graph. - * - * @param string $sourceId - * @param mixed $sourceValue - * @param string $destId - * @param mixed $destValue - * @param string $reference */ - public function connect($sourceId, $sourceValue, $destId, $destValue = null, $reference = null/*, bool $lazy = false, bool $weak = false, bool $byConstructor = false*/) + public function connect(?string $sourceId, $sourceValue, ?string $destId, $destValue = null, Reference $reference = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false) { - $lazy = \func_num_args() >= 6 ? func_get_arg(5) : false; - $weak = \func_num_args() >= 7 ? func_get_arg(6) : false; - $byConstructor = \func_num_args() >= 8 ? func_get_arg(7) : false; - if (null === $sourceId || null === $destId) { return; } @@ -108,15 +88,7 @@ class ServiceReferenceGraph $destNode->addInEdge($edge); } - /** - * Creates a graph node. - * - * @param string $id - * @param mixed $value - * - * @return ServiceReferenceGraphNode - */ - private function createNode($id, $value) + private function createNode(string $id, $value): ServiceReferenceGraphNode { if (isset($this->nodes[$id]) && $this->nodes[$id]->getValue() === $value) { return $this->nodes[$id]; diff --git a/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php b/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php index 911e7a5f5f..986145606c 100644 --- a/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php +++ b/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php @@ -27,13 +27,7 @@ class ServiceReferenceGraphEdge private $weak; private $byConstructor; - /** - * @param mixed $value - * @param bool $lazy - * @param bool $weak - * @param bool $byConstructor - */ - public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, $value = null, $lazy = false, $weak = false, $byConstructor = false) + public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, $value = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false) { $this->sourceNode = $sourceNode; $this->destNode = $destNode; @@ -46,7 +40,7 @@ class ServiceReferenceGraphEdge /** * Returns the value of the edge. * - * @return string + * @return mixed */ public function getValue() { diff --git a/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php b/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php index 50140b5fff..ba96da233d 100644 --- a/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php +++ b/lib/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php @@ -32,7 +32,7 @@ class ServiceReferenceGraphNode * @param string $id The node identifier * @param mixed $value The node value */ - public function __construct($id, $value) + public function __construct(string $id, $value) { $this->id = $id; $this->value = $value; @@ -51,7 +51,7 @@ class ServiceReferenceGraphNode /** * Checks if the value of this node is an Alias. * - * @return bool True if the value is an Alias instance + * @return bool */ public function isAlias() { @@ -61,7 +61,7 @@ class ServiceReferenceGraphNode /** * Checks if the value of this node is a Definition. * - * @return bool True if the value is a Definition instance + * @return bool */ public function isDefinition() { @@ -101,7 +101,7 @@ class ServiceReferenceGraphNode /** * Returns the value of this Node. * - * @return mixed The value + * @return mixed */ public function getValue() { diff --git a/lib/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.php b/lib/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.php new file mode 100644 index 0000000000..23bfe59b07 --- /dev/null +++ b/lib/symfony/dependency-injection/Compiler/ValidateEnvPlaceholdersPass.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\Config\Definition\BaseNode; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; +use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; + +/** + * Validates environment variable placeholders used in extension configuration with dummy values. + * + * @author Roland Franssen + */ +class ValidateEnvPlaceholdersPass implements CompilerPassInterface +{ + private const TYPE_FIXTURES = ['array' => [], 'bool' => false, 'float' => 0.0, 'int' => 0, 'string' => '']; + + private $extensionConfig = []; + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $this->extensionConfig = []; + + if (!class_exists(BaseNode::class) || !$extensions = $container->getExtensions()) { + return; + } + + $resolvingBag = $container->getParameterBag(); + if (!$resolvingBag instanceof EnvPlaceholderParameterBag) { + return; + } + + $defaultBag = new ParameterBag($resolvingBag->all()); + $envTypes = $resolvingBag->getProvidedTypes(); + foreach ($resolvingBag->getEnvPlaceholders() + $resolvingBag->getUnusedEnvPlaceholders() as $env => $placeholders) { + $values = []; + if (false === $i = strpos($env, ':')) { + $default = $defaultBag->has("env($env)") ? $defaultBag->get("env($env)") : self::TYPE_FIXTURES['string']; + $defaultType = null !== $default ? get_debug_type($default) : 'string'; + $values[$defaultType] = $default; + } else { + $prefix = substr($env, 0, $i); + foreach ($envTypes[$prefix] ?? ['string'] as $type) { + $values[$type] = self::TYPE_FIXTURES[$type] ?? null; + } + } + foreach ($placeholders as $placeholder) { + BaseNode::setPlaceholder($placeholder, $values); + } + } + + $processor = new Processor(); + + foreach ($extensions as $name => $extension) { + if (!($extension instanceof ConfigurationExtensionInterface || $extension instanceof ConfigurationInterface) + || !$config = array_filter($container->getExtensionConfig($name)) + ) { + // this extension has no semantic configuration or was not called + continue; + } + + $config = $resolvingBag->resolveValue($config); + + if ($extension instanceof ConfigurationInterface) { + $configuration = $extension; + } elseif (null === $configuration = $extension->getConfiguration($config, $container)) { + continue; + } + + $this->extensionConfig[$name] = $processor->processConfiguration($configuration, $config); + } + + $resolvingBag->clearUnusedEnvPlaceholders(); + } + + /** + * @internal + */ + public function getExtensionConfig(): array + { + try { + return $this->extensionConfig; + } finally { + $this->extensionConfig = []; + } + } +} diff --git a/lib/symfony/dependency-injection/Config/AutowireServiceResource.php b/lib/symfony/dependency-injection/Config/AutowireServiceResource.php deleted file mode 100644 index 68c1e3f6a2..0000000000 --- a/lib/symfony/dependency-injection/Config/AutowireServiceResource.php +++ /dev/null @@ -1,88 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DependencyInjection\Config; - -@trigger_error('The '.__NAMESPACE__.'\AutowireServiceResource class is deprecated since Symfony 3.3 and will be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead.', \E_USER_DEPRECATED); - -use Symfony\Component\Config\Resource\SelfCheckingResourceInterface; -use Symfony\Component\DependencyInjection\Compiler\AutowirePass; - -/** - * @deprecated since version 3.3, to be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead. - */ -class AutowireServiceResource implements SelfCheckingResourceInterface, \Serializable -{ - private $class; - private $filePath; - private $autowiringMetadata = []; - - public function __construct($class, $path, array $autowiringMetadata) - { - $this->class = $class; - $this->filePath = $path; - $this->autowiringMetadata = $autowiringMetadata; - } - - public function isFresh($timestamp) - { - if (!file_exists($this->filePath)) { - return false; - } - - // has the file *not* been modified? Definitely fresh - if (@filemtime($this->filePath) <= $timestamp) { - return true; - } - - try { - $reflectionClass = new \ReflectionClass($this->class); - } catch (\ReflectionException $e) { - // the class does not exist anymore! - return false; - } - - return (array) $this === (array) AutowirePass::createResourceForClass($reflectionClass); - } - - public function __toString() - { - return 'service.autowire.'.$this->class; - } - - /** - * @internal - */ - public function serialize() - { - return serialize([$this->class, $this->filePath, $this->autowiringMetadata]); - } - - /** - * @internal - */ - public function unserialize($serialized) - { - if (\PHP_VERSION_ID >= 70000) { - list($this->class, $this->filePath, $this->autowiringMetadata) = unserialize($serialized, ['allowed_classes' => false]); - } else { - list($this->class, $this->filePath, $this->autowiringMetadata) = unserialize($serialized); - } - } - - /** - * @deprecated Implemented for compatibility with Symfony 2.8 - */ - public function getResource() - { - return $this->filePath; - } -} diff --git a/lib/symfony/dependency-injection/Config/ContainerParametersResource.php b/lib/symfony/dependency-injection/Config/ContainerParametersResource.php index 7560c3356d..c10398bcb0 100644 --- a/lib/symfony/dependency-injection/Config/ContainerParametersResource.php +++ b/lib/symfony/dependency-injection/Config/ContainerParametersResource.php @@ -17,8 +17,10 @@ use Symfony\Component\Config\Resource\ResourceInterface; * Tracks container parameters. * * @author Maxime Steinhausser + * + * @final */ -class ContainerParametersResource implements ResourceInterface, \Serializable +class ContainerParametersResource implements ResourceInterface { private $parameters; @@ -30,34 +32,12 @@ class ContainerParametersResource implements ResourceInterface, \Serializable $this->parameters = $parameters; } - /** - * {@inheritdoc} - */ - public function __toString() + public function __toString(): string { return 'container_parameters_'.md5(serialize($this->parameters)); } - /** - * @internal - */ - public function serialize() - { - return serialize($this->parameters); - } - - /** - * @internal - */ - public function unserialize($serialized) - { - $this->parameters = unserialize($serialized); - } - - /** - * @return array Tracked parameters - */ - public function getParameters() + public function getParameters(): array { return $this->parameters; } diff --git a/lib/symfony/dependency-injection/Config/ContainerParametersResourceChecker.php b/lib/symfony/dependency-injection/Config/ContainerParametersResourceChecker.php index 6ed77e3fd2..2f2affaa5b 100644 --- a/lib/symfony/dependency-injection/Config/ContainerParametersResourceChecker.php +++ b/lib/symfony/dependency-injection/Config/ContainerParametersResourceChecker.php @@ -39,7 +39,7 @@ class ContainerParametersResourceChecker implements ResourceCheckerInterface /** * {@inheritdoc} */ - public function isFresh(ResourceInterface $resource, $timestamp) + public function isFresh(ResourceInterface $resource, int $timestamp) { foreach ($resource->getParameters() as $key => $value) { if (!$this->container->hasParameter($key) || $this->container->getParameter($key) !== $value) { diff --git a/lib/symfony/dependency-injection/Container.php b/lib/symfony/dependency-injection/Container.php index e9d53023d9..6f61eb8691 100644 --- a/lib/symfony/dependency-injection/Container.php +++ b/lib/symfony/dependency-injection/Container.php @@ -11,14 +11,22 @@ namespace Symfony\Component\DependencyInjection; +use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; +use Symfony\Component\DependencyInjection\Argument\ServiceLocator as ArgumentServiceLocator; use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(RewindableGenerator::class); +class_exists(ArgumentServiceLocator::class); /** * Container is a dependency injection container. @@ -37,35 +45,26 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; * @author Fabien Potencier * @author Johannes M. Schmitt */ -class Container implements ResettableContainerInterface +class Container implements ContainerInterface, ResetInterface { protected $parameterBag; protected $services = []; + protected $privates = []; protected $fileMap = []; protected $methodMap = []; + protected $factories = []; protected $aliases = []; protected $loading = []; protected $resolving = []; protected $syntheticIds = []; - /** - * @internal - */ - protected $privates = []; - - /** - * @internal - */ - protected $normalizedIds = []; - - private $underscoreMap = ['_' => '', '.' => '_', '\\' => '_']; private $envCache = []; private $compiled = false; private $getEnv; public function __construct(ParameterBagInterface $parameterBag = null) { - $this->parameterBag = $parameterBag ?: new EnvPlaceholderParameterBag(); + $this->parameterBag = $parameterBag ?? new EnvPlaceholderParameterBag(); } /** @@ -95,24 +94,10 @@ class Container implements ResettableContainerInterface return $this->compiled; } - /** - * Returns true if the container parameter bag are frozen. - * - * @deprecated since version 3.3, to be removed in 4.0. - * - * @return bool true if the container parameter bag are frozen, false otherwise - */ - public function isFrozen() - { - @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the isCompiled() method instead.', __METHOD__), \E_USER_DEPRECATED); - - return $this->parameterBag instanceof FrozenParameterBag; - } - /** * Gets the service container parameter bag. * - * @return ParameterBagInterface A ParameterBagInterface instance + * @return ParameterBagInterface */ public function getParameterBag() { @@ -122,25 +107,19 @@ class Container implements ResettableContainerInterface /** * Gets a parameter. * - * @param string $name The parameter name - * - * @return mixed The parameter value + * @return array|bool|string|int|float|\UnitEnum|null * * @throws InvalidArgumentException if the parameter is not defined */ - public function getParameter($name) + public function getParameter(string $name) { return $this->parameterBag->get($name); } /** - * Checks if a parameter exists. - * - * @param string $name The parameter name - * - * @return bool The presence of parameter in container + * @return bool */ - public function hasParameter($name) + public function hasParameter(string $name) { return $this->parameterBag->has($name); } @@ -148,10 +127,10 @@ class Container implements ResettableContainerInterface /** * Sets a parameter. * - * @param string $name The parameter name - * @param mixed $value The parameter value + * @param string $name The parameter name + * @param array|bool|string|int|float|\UnitEnum|null $value The parameter value */ - public function setParameter($name, $value) + public function setParameter(string $name, $value) { $this->parameterBag->set($name, $value); } @@ -161,11 +140,8 @@ class Container implements ResettableContainerInterface * * Setting a synthetic service to null resets it: has() returns false and get() * behaves in the same way as if the service was never created. - * - * @param string $id The service identifier - * @param object|null $service The service instance */ - public function set($id, $service) + public function set(string $id, ?object $service) { // Runs the internal initializer; used by the dumped container to include always-needed files if (isset($this->privates['service_container']) && $this->privates['service_container'] instanceof \Closure) { @@ -174,27 +150,20 @@ class Container implements ResettableContainerInterface $initialize(); } - $id = $this->normalizeId($id); - if ('service_container' === $id) { throw new InvalidArgumentException('You cannot set service "service_container".'); } - if (isset($this->privates[$id]) || !(isset($this->fileMap[$id]) || isset($this->methodMap[$id]))) { - if (!isset($this->privates[$id]) && !isset($this->getRemovedIds()[$id])) { + if (!(isset($this->fileMap[$id]) || isset($this->methodMap[$id]))) { + if (isset($this->syntheticIds[$id]) || !isset($this->getRemovedIds()[$id])) { // no-op } elseif (null === $service) { - @trigger_error(sprintf('The "%s" service is private, unsetting it is deprecated since Symfony 3.2 and will fail in 4.0.', $id), \E_USER_DEPRECATED); - unset($this->privates[$id]); + throw new InvalidArgumentException(sprintf('The "%s" service is private, you cannot unset it.', $id)); } else { - @trigger_error(sprintf('The "%s" service is private, replacing it is deprecated since Symfony 3.2 and will fail in 4.0.', $id), \E_USER_DEPRECATED); + throw new InvalidArgumentException(sprintf('The "%s" service is private, you cannot replace it.', $id)); } } elseif (isset($this->services[$id])) { - if (null === $service) { - @trigger_error(sprintf('The "%s" service is already initialized, unsetting it is deprecated since Symfony 3.3 and will fail in 4.0.', $id), \E_USER_DEPRECATED); - } else { - @trigger_error(sprintf('The "%s" service is already initialized, replacing it is deprecated since Symfony 3.3 and will fail in 4.0.', $id), \E_USER_DEPRECATED); - } + throw new InvalidArgumentException(sprintf('The "%s" service is already initialized, you cannot replace it.', $id)); } if (isset($this->aliases[$id])) { @@ -215,55 +184,27 @@ class Container implements ResettableContainerInterface * * @param string $id The service identifier * - * @return bool true if the service is defined, false otherwise + * @return bool */ - public function has($id) + public function has(string $id) { - for ($i = 2;;) { - if (isset($this->privates[$id])) { - @trigger_error(sprintf('The "%s" service is private, checking for its existence is deprecated since Symfony 3.2 and will fail in 4.0.', $id), \E_USER_DEPRECATED); - } - if (isset($this->aliases[$id])) { - $id = $this->aliases[$id]; - } - if (isset($this->services[$id])) { - return true; - } - if ('service_container' === $id) { - return true; - } - - if (isset($this->fileMap[$id]) || isset($this->methodMap[$id])) { - return true; - } - - if (--$i && $id !== $normalizedId = $this->normalizeId($id)) { - $id = $normalizedId; - continue; - } - - // We only check the convention-based factory in a compiled container (i.e. a child class other than a ContainerBuilder, - // and only when the dumper has not generated the method map (otherwise the method map is considered to be fully populated by the dumper) - if (!$this->methodMap && !$this instanceof ContainerBuilder && __CLASS__ !== static::class && method_exists($this, 'get'.strtr($id, $this->underscoreMap).'Service')) { - @trigger_error('Generating a dumped container without populating the method map is deprecated since Symfony 3.2 and will be unsupported in 4.0. Update your dumper to generate the method map.', \E_USER_DEPRECATED); - - return true; - } - - return false; + if (isset($this->aliases[$id])) { + $id = $this->aliases[$id]; } + if (isset($this->services[$id])) { + return true; + } + if ('service_container' === $id) { + return true; + } + + return isset($this->fileMap[$id]) || isset($this->methodMap[$id]); } /** * Gets a service. * - * If a service is defined both through a set() method and - * with a get{$id}Service() method, the former has always precedence. - * - * @param string $id The service identifier - * @param int $invalidBehavior The behavior when the service does not exist - * - * @return object|null The associated service + * @return object|null * * @throws ServiceCircularReferenceException When a circular reference is detected * @throws ServiceNotFoundException When the service is not defined @@ -271,59 +212,38 @@ class Container implements ResettableContainerInterface * * @see Reference */ - public function get($id, $invalidBehavior = /* self::EXCEPTION_ON_INVALID_REFERENCE */ 1) + public function get(string $id, int $invalidBehavior = /* self::EXCEPTION_ON_INVALID_REFERENCE */ 1) { - // Attempt to retrieve the service by checking first aliases then - // available services. Service IDs are case insensitive, however since - // this method can be called thousands of times during a request, avoid - // calling $this->normalizeId($id) unless necessary. - for ($i = 2;;) { - if (isset($this->privates[$id])) { - @trigger_error(sprintf('The "%s" service is private, getting it from the container is deprecated since Symfony 3.2 and will fail in 4.0. You should either make the service public, or stop using the container directly and use dependency injection instead.', $id), \E_USER_DEPRECATED); - } - if (isset($this->aliases[$id])) { - $id = $this->aliases[$id]; + return $this->services[$id] + ?? $this->services[$id = $this->aliases[$id] ?? $id] + ?? ('service_container' === $id ? $this : ($this->factories[$id] ?? [$this, 'make'])($id, $invalidBehavior)); + } + + /** + * Creates a service. + * + * As a separate method to allow "get()" to use the really fast `??` operator. + */ + private function make(string $id, int $invalidBehavior) + { + if (isset($this->loading[$id])) { + throw new ServiceCircularReferenceException($id, array_merge(array_keys($this->loading), [$id])); + } + + $this->loading[$id] = true; + + try { + if (isset($this->fileMap[$id])) { + return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->load($this->fileMap[$id]); + } elseif (isset($this->methodMap[$id])) { + return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->{$this->methodMap[$id]}(); } + } catch (\Exception $e) { + unset($this->services[$id]); - // Re-use shared service instance if it exists. - if (isset($this->services[$id])) { - return $this->services[$id]; - } - if ('service_container' === $id) { - return $this; - } - - if (isset($this->loading[$id])) { - throw new ServiceCircularReferenceException($id, array_merge(array_keys($this->loading), [$id])); - } - - $this->loading[$id] = true; - - try { - if (isset($this->fileMap[$id])) { - return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->load($this->fileMap[$id]); - } elseif (isset($this->methodMap[$id])) { - return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->{$this->methodMap[$id]}(); - } elseif (--$i && $id !== $normalizedId = $this->normalizeId($id)) { - unset($this->loading[$id]); - $id = $normalizedId; - continue; - } elseif (!$this->methodMap && !$this instanceof ContainerBuilder && __CLASS__ !== static::class && method_exists($this, $method = 'get'.strtr($id, $this->underscoreMap).'Service')) { - // We only check the convention-based factory in a compiled container (i.e. a child class other than a ContainerBuilder, - // and only when the dumper has not generated the method map (otherwise the method map is considered to be fully populated by the dumper) - @trigger_error('Generating a dumped container without populating the method map is deprecated since Symfony 3.2 and will be unsupported in 4.0. Update your dumper to generate the method map.', \E_USER_DEPRECATED); - - return /* self::IGNORE_ON_UNINITIALIZED_REFERENCE */ 4 === $invalidBehavior ? null : $this->{$method}(); - } - - break; - } catch (\Exception $e) { - unset($this->services[$id]); - - throw $e; - } finally { - unset($this->loading[$id]); - } + throw $e; + } finally { + unset($this->loading[$id]); } if (/* self::EXCEPTION_ON_INVALID_REFERENCE */ 1 === $invalidBehavior) { @@ -339,31 +259,28 @@ class Container implements ResettableContainerInterface $alternatives = []; foreach ($this->getServiceIds() as $knownId) { + if ('' === $knownId || '.' === $knownId[0]) { + continue; + } $lev = levenshtein($id, $knownId); - if ($lev <= \strlen($id) / 3 || false !== strpos($knownId, $id)) { + if ($lev <= \strlen($id) / 3 || str_contains($knownId, $id)) { $alternatives[] = $knownId; } } throw new ServiceNotFoundException($id, null, null, $alternatives); } + + return null; } /** * Returns true if the given service has actually been initialized. * - * @param string $id The service identifier - * - * @return bool true if service has already been initialized, false otherwise + * @return bool */ - public function initialized($id) + public function initialized(string $id) { - $id = $this->normalizeId($id); - - if (isset($this->privates[$id])) { - @trigger_error(sprintf('Checking for the initialization of the "%s" private service is deprecated since Symfony 3.4 and won\'t be supported anymore in Symfony 4.0.', $id), \E_USER_DEPRECATED); - } - if (isset($this->aliases[$id])) { $id = $this->aliases[$id]; } @@ -380,32 +297,28 @@ class Container implements ResettableContainerInterface */ public function reset() { - $this->services = []; + $services = $this->services + $this->privates; + $this->services = $this->factories = $this->privates = []; + + foreach ($services as $service) { + try { + if ($service instanceof ResetInterface) { + $service->reset(); + } + } catch (\Throwable $e) { + continue; + } + } } /** * Gets all service ids. * - * @return string[] An array of all defined service ids + * @return string[] */ public function getServiceIds() { - $ids = []; - - if (!$this->methodMap && !$this instanceof ContainerBuilder && __CLASS__ !== static::class) { - // We only check the convention-based factory in a compiled container (i.e. a child class other than a ContainerBuilder, - // and only when the dumper has not generated the method map (otherwise the method map is considered to be fully populated by the dumper) - @trigger_error('Generating a dumped container without populating the method map is deprecated since Symfony 3.2 and will be unsupported in 4.0. Update your dumper to generate the method map.', \E_USER_DEPRECATED); - - foreach (get_class_methods($this) as $method) { - if (preg_match('/^get(.+)Service$/', $method, $match)) { - $ids[] = self::underscore($match[1]); - } - } - } - $ids[] = 'service_container'; - - return array_map('strval', array_unique(array_merge($ids, array_keys($this->methodMap), array_keys($this->fileMap), array_keys($this->aliases), array_keys($this->services)))); + return array_map('strval', array_unique(array_merge(['service_container'], array_keys($this->fileMap), array_keys($this->methodMap), array_keys($this->aliases), array_keys($this->services)))); } /** @@ -421,11 +334,9 @@ class Container implements ResettableContainerInterface /** * Camelizes a string. * - * @param string $id A string to camelize - * - * @return string The camelized string + * @return string */ - public static function camelize($id) + public static function camelize(string $id) { return strtr(ucwords(strtr($id, ['_' => ' ', '.' => '_ ', '\\' => '_ '])), [' ' => '']); } @@ -433,11 +344,9 @@ class Container implements ResettableContainerInterface /** * A string to underscore. * - * @param string $id The string to underscore - * - * @return string The underscored string + * @return string */ - public static function underscore($id) + public static function underscore(string $id) { return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], str_replace('_', '.', $id))); } @@ -445,7 +354,7 @@ class Container implements ResettableContainerInterface /** * Creates a service by requiring its factory file. */ - protected function load($file) + protected function load(string $file) { return require $file; } @@ -453,13 +362,11 @@ class Container implements ResettableContainerInterface /** * Fetches a variable from the environment. * - * @param string $name The name of the environment variable - * - * @return mixed The value to use for the provided environment variable name + * @return mixed * * @throws EnvNotFoundException When the environment variable is not found and has no default value */ - protected function getEnv($name) + protected function getEnv(string $name) { if (isset($this->resolving[$envName = "env($name)"])) { throw new ParameterCircularReferenceException(array_keys($this->resolving)); @@ -471,9 +378,7 @@ class Container implements ResettableContainerInterface $this->set($id, new ServiceLocator([])); } if (!$this->getEnv) { - $this->getEnv = new \ReflectionMethod($this, __FUNCTION__); - $this->getEnv->setAccessible(true); - $this->getEnv = $this->getEnv->getClosure($this); + $this->getEnv = \Closure::fromCallable([$this, 'getEnv']); } $processors = $this->get($id); @@ -495,29 +400,32 @@ class Container implements ResettableContainerInterface } /** - * Returns the case sensitive id used at registration time. + * @param string|false $registry + * @param string|bool $load * - * @param string $id - * - * @return string + * @return mixed * * @internal */ - public function normalizeId($id) + final protected function getService($registry, string $id, ?string $method, $load) { - if (!\is_string($id)) { - $id = (string) $id; + if ('service_container' === $id) { + return $this; } - if (isset($this->normalizedIds[$normalizedId = strtolower($id)])) { - $normalizedId = $this->normalizedIds[$normalizedId]; - if ($id !== $normalizedId) { - @trigger_error(sprintf('Service identifiers will be made case sensitive in Symfony 4.0. Using "%s" instead of "%s" is deprecated since Symfony 3.3.', $id, $normalizedId), \E_USER_DEPRECATED); - } - } else { - $normalizedId = $this->normalizedIds[$normalizedId] = $id; + if (\is_string($load)) { + throw new RuntimeException($load); + } + if (null === $method) { + return false !== $registry ? $this->{$registry}[$id] ?? null : null; + } + if (false !== $registry) { + return $this->{$registry}[$id] ?? $this->{$registry}[$id] = $load ? $this->load($method) : $this->{$method}(); + } + if (!$load) { + return $this->{$method}(); } - return $normalizedId; + return ($factory = $this->factories[$id] ?? $this->factories['service_container'][$id] ?? null) ? $factory() : $this->load($method); } private function __clone() diff --git a/lib/symfony/dependency-injection/ContainerBuilder.php b/lib/symfony/dependency-injection/ContainerBuilder.php index 97617cb04e..485630a748 100644 --- a/lib/symfony/dependency-injection/ContainerBuilder.php +++ b/lib/symfony/dependency-injection/ContainerBuilder.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection; +use Composer\InstalledVersions; use Psr\Container\ContainerInterface as PsrContainerInterface; use Symfony\Component\Config\Resource\ClassExistenceResource; use Symfony\Component\Config\Resource\ComposerResource; @@ -20,9 +21,13 @@ use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\Resource\GlobResource; use Symfony\Component\Config\Resource\ReflectionClassResource; use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocator; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Attribute\Target; use Symfony\Component\DependencyInjection\Compiler\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PassConfig; @@ -39,7 +44,6 @@ use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInst use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; -use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher; use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; @@ -51,30 +55,33 @@ use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; class ContainerBuilder extends Container implements TaggedContainerInterface { /** - * @var ExtensionInterface[] + * @var array */ private $extensions = []; /** - * @var ExtensionInterface[] + * @var array */ private $extensionsByNs = []; /** - * @var Definition[] + * @var array */ private $definitions = []; /** - * @var Alias[] + * @var array */ private $aliasDefinitions = []; /** - * @var ResourceInterface[] + * @var array */ private $resources = []; + /** + * @var array>> + */ private $extensionConfigs = []; /** @@ -82,6 +89,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface */ private $compiler; + /** + * @var bool + */ private $trackResources; /** @@ -119,13 +129,27 @@ class ContainerBuilder extends Container implements TaggedContainerInterface */ private $vendors; + /** + * @var array + */ private $autoconfiguredInstanceof = []; + /** + * @var array + */ + private $autoconfiguredAttributes = []; + + /** + * @var array + */ private $removedIds = []; + /** + * @var array + */ private $removedBindingIds = []; - private static $internalTypes = [ + private const INTERNAL_TYPES = [ 'int' => true, 'float' => true, 'string' => true, @@ -143,14 +167,14 @@ class ContainerBuilder extends Container implements TaggedContainerInterface { parent::__construct($parameterBag); - $this->trackResources = interface_exists('Symfony\Component\Config\Resource\ResourceInterface'); + $this->trackResources = interface_exists(ResourceInterface::class); $this->setDefinition('service_container', (new Definition(ContainerInterface::class))->setSynthetic(true)->setPublic(true)); - $this->setAlias(PsrContainerInterface::class, new Alias('service_container', false)); - $this->setAlias(ContainerInterface::class, new Alias('service_container', false)); + $this->setAlias(PsrContainerInterface::class, new Alias('service_container', false))->setDeprecated('symfony/dependency-injection', '5.1', $deprecationMessage = 'The "%alias_id%" autowiring alias is deprecated. Define it explicitly in your app if you want to keep using it.'); + $this->setAlias(ContainerInterface::class, new Alias('service_container', false))->setDeprecated('symfony/dependency-injection', '5.1', $deprecationMessage); } /** - * @var \ReflectionClass[] a list of class reflectors + * @var array */ private $classReflectors; @@ -159,18 +183,16 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * If you are not using the loaders and therefore don't want * to depend on the Config component, set this flag to false. - * - * @param bool $track True if you want to track resources, false otherwise */ - public function setResourceTracking($track) + public function setResourceTracking(bool $track) { - $this->trackResources = (bool) $track; + $this->trackResources = $track; } /** * Checks if resources are tracked. * - * @return bool true If resources are tracked, false otherwise + * @return bool */ public function isTrackingResources() { @@ -197,13 +219,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Returns an extension by alias or namespace. * - * @param string $name An alias or a namespace - * - * @return ExtensionInterface An extension instance + * @return ExtensionInterface * * @throws LogicException if the extension is not registered */ - public function getExtension($name) + public function getExtension(string $name) { if (isset($this->extensions[$name])) { return $this->extensions[$name]; @@ -219,7 +239,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Returns all registered extensions. * - * @return ExtensionInterface[] An array of ExtensionInterface + * @return array */ public function getExtensions() { @@ -229,11 +249,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Checks if we have an extension. * - * @param string $name The name of the extension - * - * @return bool If the extension exists + * @return bool */ - public function hasExtension($name) + public function hasExtension(string $name) { return isset($this->extensions[$name]) || isset($this->extensionsByNs[$name]); } @@ -241,7 +259,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Returns an array of resources loaded to build this configuration. * - * @return ResourceInterface[] An array of resources + * @return ResourceInterface[] */ public function getResources() { @@ -269,7 +287,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Sets the resources for this configuration. * - * @param ResourceInterface[] $resources An array of resources + * @param array $resources * * @return $this */ @@ -325,39 +343,20 @@ class ContainerBuilder extends Container implements TaggedContainerInterface return $this; } - /** - * Adds the given class hierarchy as resources. - * - * @return $this - * - * @deprecated since version 3.3, to be removed in 4.0. Use addObjectResource() or getReflectionClass() instead. - */ - public function addClassResource(\ReflectionClass $class) - { - @trigger_error('The '.__METHOD__.'() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the addObjectResource() or the getReflectionClass() method instead.', \E_USER_DEPRECATED); - - return $this->addObjectResource($class->name); - } - /** * Retrieves the requested reflection class and registers it for resource tracking. * - * @param string $class - * @param bool $throw - * - * @return \ReflectionClass|null - * * @throws \ReflectionException when a parent class/interface/trait is not found and $throw is true * * @final */ - public function getReflectionClass($class, $throw = true) + public function getReflectionClass(?string $class, bool $throw = true): ?\ReflectionClass { if (!$class = $this->getParameterBag()->resolveValue($class)) { return null; } - if (isset(self::$internalTypes[$class])) { + if (isset(self::INTERNAL_TYPES[$class])) { return null; } @@ -380,7 +379,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface if ($this->trackResources) { if (!$classReflector) { - $this->addResource($resource ?: new ClassExistenceResource($class, false)); + $this->addResource($resource ?? new ClassExistenceResource($class, false)); } elseif (!$classReflector->isInternal()) { $path = $classReflector->getFileName(); @@ -401,11 +400,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * @param bool|string $trackContents Whether to track contents of the given resource. If a string is passed, * it will be used as pattern for tracking contents of the requested directory * - * @return bool - * * @final */ - public function fileExists($path, $trackContents = true) + public function fileExists(string $path, $trackContents = true): bool { $exists = file_exists($path); @@ -435,27 +432,23 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Loads the configuration for an extension. * - * @param string $extension The extension alias or namespace - * @param array $values An array of values that customizes the extension + * @param string $extension The extension alias or namespace + * @param array|null $values An array of values that customizes the extension * * @return $this * * @throws BadMethodCallException When this ContainerBuilder is compiled * @throws \LogicException if the extension is not registered */ - public function loadFromExtension($extension, array $values = null) + public function loadFromExtension(string $extension, array $values = null) { if ($this->isCompiled()) { throw new BadMethodCallException('Cannot load from an extension on a compiled container.'); } - if (\func_num_args() < 2) { - $values = []; - } - $namespace = $this->getExtension($extension)->getAlias(); - $this->extensionConfigs[$namespace][] = $values; + $this->extensionConfigs[$namespace][] = $values ?? []; return $this; } @@ -463,27 +456,13 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Adds a compiler pass. * - * @param CompilerPassInterface $pass A compiler pass - * @param string $type The type of compiler pass - * @param int $priority Used to sort the passes + * @param string $type The type of compiler pass + * @param int $priority Used to sort the passes * * @return $this */ - public function addCompilerPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION/*, int $priority = 0*/) + public function addCompilerPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0) { - if (\func_num_args() >= 3) { - $priority = func_get_arg(2); - } else { - if (__CLASS__ !== static::class) { - $r = new \ReflectionMethod($this, __FUNCTION__); - if (__CLASS__ !== $r->getDeclaringClass()->getName()) { - @trigger_error(sprintf('Method %s() will have a third `int $priority = 0` argument in version 4.0. Not defining it is deprecated since Symfony 3.2.', __METHOD__), \E_USER_DEPRECATED); - } - } - - $priority = 0; - } - $this->getCompiler()->addPass($pass, $type, $priority); $this->addObjectResource($pass); @@ -494,7 +473,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Returns the compiler pass config which can then be modified. * - * @return PassConfig The compiler pass config + * @return PassConfig */ public function getCompilerPassConfig() { @@ -504,7 +483,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Returns the compiler. * - * @return Compiler The compiler + * @return Compiler */ public function getCompiler() { @@ -518,15 +497,10 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Sets a service. * - * @param string $id The service identifier - * @param object|null $service The service instance - * * @throws BadMethodCallException When this ContainerBuilder is compiled */ - public function set($id, $service) + public function set(string $id, ?object $service) { - $id = $this->normalizeId($id); - if ($this->isCompiled() && (isset($this->definitions[$id]) && !$this->definitions[$id]->isSynthetic())) { // setting a synthetic service on a compiled container is alright throw new BadMethodCallException(sprintf('Setting service "%s" for an unknown or non-synthetic service definition on a compiled container is not allowed.', $id)); @@ -539,12 +513,10 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Removes a service definition. - * - * @param string $id The service identifier */ - public function removeDefinition($id) + public function removeDefinition(string $id) { - if (isset($this->definitions[$id = $this->normalizeId($id)])) { + if (isset($this->definitions[$id])) { unset($this->definitions[$id]); $this->removedIds[$id] = true; } @@ -555,22 +527,15 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @param string $id The service identifier * - * @return bool true if the service is defined, false otherwise + * @return bool */ - public function has($id) + public function has(string $id) { - $id = $this->normalizeId($id); - return isset($this->definitions[$id]) || isset($this->aliasDefinitions[$id]) || parent::has($id); } /** - * Gets a service. - * - * @param string $id The service identifier - * @param int $invalidBehavior The behavior when the service does not exist - * - * @return object|null The associated service + * @return object|null * * @throws InvalidArgumentException when no definitions are available * @throws ServiceCircularReferenceException When a circular reference is detected @@ -579,19 +544,17 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @see Reference */ - public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) + public function get(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { - if ($this->isCompiled() && isset($this->removedIds[$id = $this->normalizeId($id)])) { - @trigger_error(sprintf('Fetching the "%s" private service or alias is deprecated since Symfony 3.4 and will fail in 4.0. Make it public instead.', $id), \E_USER_DEPRECATED); + if ($this->isCompiled() && isset($this->removedIds[$id]) && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $invalidBehavior) { + return parent::get($id); } return $this->doGet($id, $invalidBehavior); } - private function doGet($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, array &$inlineServices = null, $isConstructorArgument = false) + private function doGet(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, array &$inlineServices = null, bool $isConstructorArgument = false) { - $id = $this->normalizeId($id); - if (isset($inlineServices[$id])) { return $inlineServices[$id]; } @@ -613,19 +576,30 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } if (!isset($this->definitions[$id]) && isset($this->aliasDefinitions[$id])) { - return $this->doGet((string) $this->aliasDefinitions[$id], $invalidBehavior, $inlineServices, $isConstructorArgument); + $alias = $this->aliasDefinitions[$id]; + + if ($alias->isDeprecated()) { + $deprecation = $alias->getDeprecation($id); + trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']); + } + + return $this->doGet((string) $alias, $invalidBehavior, $inlineServices, $isConstructorArgument); } try { $definition = $this->getDefinition($id); } catch (ServiceNotFoundException $e) { - if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) { + if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $invalidBehavior) { return null; } throw $e; } + if ($definition->hasErrors() && $e = $definition->getErrors()) { + throw new RuntimeException(reset($e)); + } + if ($isConstructorArgument) { $this->loading[$id] = true; } @@ -708,16 +682,22 @@ class ContainerBuilder extends Container implements TaggedContainerInterface $this->autoconfiguredInstanceof[$interface] = $childDefinition; } + + foreach ($container->getAutoconfiguredAttributes() as $attribute => $configurator) { + if (isset($this->autoconfiguredAttributes[$attribute])) { + throw new InvalidArgumentException(sprintf('"%s" has already been autoconfigured and merge() does not support merging autoconfiguration for the same attribute.', $attribute)); + } + + $this->autoconfiguredAttributes[$attribute] = $configurator; + } } /** * Returns the configuration array for the given extension. * - * @param string $name The name of the extension - * - * @return array An array of configuration + * @return array> */ - public function getExtensionConfig($name) + public function getExtensionConfig(string $name) { if (!isset($this->extensionConfigs[$name])) { $this->extensionConfigs[$name] = []; @@ -729,10 +709,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Prepends a config array to the configs of the given extension. * - * @param string $name The name of the extension - * @param array $config The config to set + * @param array $config */ - public function prependExtensionConfig($name, array $config) + public function prependExtensionConfig(string $name, array $config) { if (!isset($this->extensionConfigs[$name])) { $this->extensionConfigs[$name] = []; @@ -760,19 +739,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * Set to "true" when you want to use the current ContainerBuilder * directly, keep to "false" when the container is dumped instead. */ - public function compile(/*$resolveEnvPlaceholders = false*/) + public function compile(bool $resolveEnvPlaceholders = false) { - if (1 <= \func_num_args()) { - $resolveEnvPlaceholders = func_get_arg(0); - } else { - if (__CLASS__ !== static::class) { - $r = new \ReflectionMethod($this, __FUNCTION__); - if (__CLASS__ !== $r->getDeclaringClass()->getName() && (1 > $r->getNumberOfParameters() || 'resolveEnvPlaceholders' !== $r->getParameters()[0]->name)) { - @trigger_error(sprintf('The %s::compile() method expects a first "$resolveEnvPlaceholders" argument since Symfony 3.3. It will be made mandatory in 4.0.', static::class), \E_USER_DEPRECATED); - } - } - $resolveEnvPlaceholders = false; - } $compiler = $this->getCompiler(); if ($this->trackResources) { @@ -824,7 +792,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Gets removed service or alias ids. * - * @return array + * @return array */ public function getRemovedIds() { @@ -833,6 +801,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Adds the service aliases. + * + * @param array $aliases */ public function addAliases(array $aliases) { @@ -843,6 +813,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Sets the service aliases. + * + * @param array $aliases */ public function setAliases(array $aliases) { @@ -861,22 +833,20 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * @throws InvalidArgumentException if the id is not a string or an Alias * @throws InvalidArgumentException if the alias is for itself */ - public function setAlias($alias, $id) + public function setAlias(string $alias, $id) { - $alias = $this->normalizeId($alias); - - if ('' === $alias || '\\' === substr($alias, -1) || \strlen($alias) !== strcspn($alias, "\0\r\n'")) { + if ('' === $alias || '\\' === $alias[-1] || \strlen($alias) !== strcspn($alias, "\0\r\n'")) { throw new InvalidArgumentException(sprintf('Invalid alias id: "%s".', $alias)); } if (\is_string($id)) { - $id = new Alias($this->normalizeId($id)); + $id = new Alias($id); } elseif (!$id instanceof Alias) { throw new InvalidArgumentException('$id must be a string, or an Alias object.'); } if ($alias === (string) $id) { - throw new InvalidArgumentException(sprintf('An alias can not reference itself, got a circular reference on "%s".', $alias)); + throw new InvalidArgumentException(sprintf('An alias cannot reference itself, got a circular reference on "%s".', $alias)); } unset($this->definitions[$alias], $this->removedIds[$alias]); @@ -884,35 +854,24 @@ class ContainerBuilder extends Container implements TaggedContainerInterface return $this->aliasDefinitions[$alias] = $id; } - /** - * Removes an alias. - * - * @param string $alias The alias to remove - */ - public function removeAlias($alias) + public function removeAlias(string $alias) { - if (isset($this->aliasDefinitions[$alias = $this->normalizeId($alias)])) { + if (isset($this->aliasDefinitions[$alias])) { unset($this->aliasDefinitions[$alias]); $this->removedIds[$alias] = true; } } /** - * Returns true if an alias exists under the given identifier. - * - * @param string $id The service identifier - * - * @return bool true if the alias exists, false otherwise + * @return bool */ - public function hasAlias($id) + public function hasAlias(string $id) { - return isset($this->aliasDefinitions[$this->normalizeId($id)]); + return isset($this->aliasDefinitions[$id]); } /** - * Gets all defined aliases. - * - * @return Alias[] An array of aliases + * @return array */ public function getAliases() { @@ -920,18 +879,12 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } /** - * Gets an alias. - * - * @param string $id The service identifier - * - * @return Alias An Alias instance + * @return Alias * * @throws InvalidArgumentException if the alias does not exist */ - public function getAlias($id) + public function getAlias(string $id) { - $id = $this->normalizeId($id); - if (!isset($this->aliasDefinitions[$id])) { throw new InvalidArgumentException(sprintf('The service alias "%s" does not exist.', $id)); } @@ -945,12 +898,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * This methods allows for simple registration of service definition * with a fluid interface. * - * @param string $id The service identifier - * @param string|null $class The service class - * - * @return Definition A Definition instance + * @return Definition */ - public function register($id, $class = null) + public function register(string $id, string $class = null) { return $this->setDefinition($id, new Definition($class)); } @@ -961,12 +911,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * This method implements a shortcut for using setDefinition() with * an autowired definition. * - * @param string $id The service identifier - * @param string|null $class The service class - * - * @return Definition The created definition + * @return Definition */ - public function autowire($id, $class = null) + public function autowire(string $id, string $class = null) { return $this->setDefinition($id, (new Definition($class))->setAutowired(true)); } @@ -974,7 +921,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Adds the service definitions. * - * @param Definition[] $definitions An array of service definitions + * @param array $definitions */ public function addDefinitions(array $definitions) { @@ -986,7 +933,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Sets the service definitions. * - * @param Definition[] $definitions An array of service definitions + * @param array $definitions */ public function setDefinitions(array $definitions) { @@ -997,7 +944,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Gets all service definitions. * - * @return Definition[] An array of Definition instances + * @return array */ public function getDefinitions() { @@ -1007,22 +954,17 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Sets a service definition. * - * @param string $id The service identifier - * @param Definition $definition A Definition instance - * - * @return Definition the service definition + * @return Definition * * @throws BadMethodCallException When this ContainerBuilder is compiled */ - public function setDefinition($id, Definition $definition) + public function setDefinition(string $id, Definition $definition) { if ($this->isCompiled()) { throw new BadMethodCallException('Adding definition to a compiled container is not allowed.'); } - $id = $this->normalizeId($id); - - if ('' === $id || '\\' === substr($id, -1) || \strlen($id) !== strcspn($id, "\0\r\n'")) { + if ('' === $id || '\\' === $id[-1] || \strlen($id) !== strcspn($id, "\0\r\n'")) { throw new InvalidArgumentException(sprintf('Invalid service id: "%s".', $id)); } @@ -1034,28 +976,22 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Returns true if a service definition exists under the given identifier. * - * @param string $id The service identifier - * - * @return bool true if the service definition exists, false otherwise + * @return bool */ - public function hasDefinition($id) + public function hasDefinition(string $id) { - return isset($this->definitions[$this->normalizeId($id)]); + return isset($this->definitions[$id]); } /** * Gets a service definition. * - * @param string $id The service identifier - * - * @return Definition A Definition instance + * @return Definition * * @throws ServiceNotFoundException if the service definition does not exist */ - public function getDefinition($id) + public function getDefinition(string $id) { - $id = $this->normalizeId($id); - if (!isset($this->definitions[$id])) { throw new ServiceNotFoundException($id); } @@ -1068,16 +1004,12 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * The method "unaliases" recursively to return a Definition instance. * - * @param string $id The service identifier or alias - * - * @return Definition A Definition instance + * @return Definition * * @throws ServiceNotFoundException if the service definition does not exist */ - public function findDefinition($id) + public function findDefinition(string $id) { - $id = $this->normalizeId($id); - $seen = []; while (isset($this->aliasDefinitions[$id])) { $id = (string) $this->aliasDefinitions[$id]; @@ -1099,17 +1031,13 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Creates a service for a service definition. * - * @param Definition $definition A service definition instance - * @param string $id The service identifier - * @param bool $tryProxy Whether to try proxying the service with a lazy proxy - * - * @return mixed The service described by the service definition + * @return mixed * * @throws RuntimeException When the factory definition is incomplete * @throws RuntimeException When the service is a synthetic service * @throws InvalidArgumentException When configure callable is not callable */ - private function createService(Definition $definition, array &$inlineServices, $isConstructorArgument = false, $id = null, $tryProxy = true) + private function createService(Definition $definition, array &$inlineServices, bool $isConstructorArgument = false, string $id = null, bool $tryProxy = true) { if (null === $id && isset($inlineServices[$h = spl_object_hash($definition)])) { return $inlineServices[$h]; @@ -1124,7 +1052,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } if ($definition->isDeprecated()) { - @trigger_error($definition->getDeprecationMessage($id), \E_USER_DEPRECATED); + $deprecation = $definition->getDeprecation($id); + trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']); } if ($tryProxy && $definition->isLazy() && !$tryProxy = !($proxy = $this->proxyInstantiator) || $proxy instanceof RealServiceInstantiator) { @@ -1161,30 +1090,34 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } if (null !== $factory) { - $service = \call_user_func_array($factory, $arguments); + $service = $factory(...$arguments); if (!$definition->isDeprecated() && \is_array($factory) && \is_string($factory[0])) { $r = new \ReflectionClass($factory[0]); if (0 < strpos($r->getDocComment(), "\n * @deprecated ")) { - @trigger_error(sprintf('The "%s" service relies on the deprecated "%s" factory class. It should either be deprecated or its factory upgraded.', $id, $r->name), \E_USER_DEPRECATED); + trigger_deprecation('', '', 'The "%s" service relies on the deprecated "%s" factory class. It should either be deprecated or its factory upgraded.', $id, $r->name); } } } else { - $r = new \ReflectionClass($class = $parameterBag->resolveValue($definition->getClass())); + $r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass())); $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs(array_values($arguments)); - // don't trigger deprecations for internal uses - // @deprecated since version 3.3, to be removed in 4.0 along with the deprecated class - $deprecationAllowlist = ['event_dispatcher' => ContainerAwareEventDispatcher::class]; - if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ") && (!isset($deprecationAllowlist[$id]) || $deprecationAllowlist[$id] !== $class)) { - @trigger_error(sprintf('The "%s" service relies on the deprecated "%s" class. It should either be deprecated or its implementation upgraded.', $id, $r->name), \E_USER_DEPRECATED); + if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ")) { + trigger_deprecation('', '', 'The "%s" service relies on the deprecated "%s" class. It should either be deprecated or its implementation upgraded.', $id, $r->name); } } - if ($tryProxy || !$definition->isLazy()) { - // share only if proxying failed, or if not a proxy + $lastWitherIndex = null; + foreach ($definition->getMethodCalls() as $k => $call) { + if ($call[2] ?? false) { + $lastWitherIndex = $k; + } + } + + if (null === $lastWitherIndex && ($tryProxy || !$definition->isLazy())) { + // share only if proxying failed, or if not a proxy, and if no withers are found $this->shareService($definition, $service, $id, $inlineServices); } @@ -1193,8 +1126,13 @@ class ContainerBuilder extends Container implements TaggedContainerInterface $service->$name = $value; } - foreach ($definition->getMethodCalls() as $call) { - $this->callMethod($service, $call, $inlineServices); + foreach ($definition->getMethodCalls() as $k => $call) { + $service = $this->callMethod($service, $call, $inlineServices); + + if ($lastWitherIndex === $k && ($tryProxy || !$definition->isLazy())) { + // share only if proxying failed, or if not a proxy, and this is the last wither + $this->shareService($definition, $service, $id, $inlineServices); + } } if ($callable = $definition->getConfigurator()) { @@ -1209,10 +1147,10 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } if (!\is_callable($callable)) { - throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', \get_class($service))); + throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', get_debug_type($service))); } - \call_user_func($callable, $service); + $callable($service); } return $service; @@ -1221,7 +1159,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Replaces service references by the real service instance and evaluates expressions. * - * @param mixed $value A value + * @param mixed $value * * @return mixed The same value with all service references replaced by * the real service instances and all expressions evaluated @@ -1231,7 +1169,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface return $this->doResolveServices($value); } - private function doResolveServices($value, array &$inlineServices = [], $isConstructorArgument = false) + private function doResolveServices($value, array &$inlineServices = [], bool $isConstructorArgument = false) { if (\is_array($value)) { foreach ($value as $k => $v) { @@ -1243,7 +1181,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface return $this->resolveServices($reference); }; } elseif ($value instanceof IteratorArgument) { - $value = new RewindableGenerator(function () use ($value) { + $value = new RewindableGenerator(function () use ($value, &$inlineServices) { foreach ($value->getValues() as $k => $v) { foreach (self::getServiceConditionals($v) as $s) { if (!$this->has($s)) { @@ -1251,14 +1189,14 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } } foreach (self::getInitializedConditionals($v) as $s) { - if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) { + if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE, $inlineServices)) { continue 2; } } - yield $k => $this->resolveServices($v); + yield $k => $this->doResolveServices($v, $inlineServices); } - }, function () use ($value) { + }, function () use ($value): int { $count = 0; foreach ($value->getValues() as $v) { foreach (self::getServiceConditionals($v) as $s) { @@ -1277,6 +1215,15 @@ class ContainerBuilder extends Container implements TaggedContainerInterface return $count; }); + } elseif ($value instanceof ServiceLocatorArgument) { + $refs = $types = []; + foreach ($value->getValues() as $k => $v) { + if ($v) { + $refs[$k] = [$v]; + $types[$k] = $v instanceof TypedReference ? $v->getType() : '?'; + } + } + $value = new ServiceLocator(\Closure::fromCallable([$this, 'resolveServices']), $refs, $types); } elseif ($value instanceof Reference) { $value = $this->doGet((string) $value, $value->getInvalidBehavior(), $inlineServices, $isConstructorArgument); } elseif ($value instanceof Definition) { @@ -1285,6 +1232,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface $value = $this->getParameter((string) $value); } elseif ($value instanceof Expression) { $value = $this->getExpressionLanguage()->evaluate($value, ['container' => $this]); + } elseif ($value instanceof AbstractArgument) { + throw new RuntimeException($value->getTextWithContext()); } return $value; @@ -1304,12 +1253,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * } * } * - * @param string $name - * @param bool $throwOnAbstract - * - * @return array An array of tags with the tagged service as key, holding a list of attribute arrays + * @return array An array of tags with the tagged service as key, holding a list of attribute arrays */ - public function findTaggedServiceIds($name, $throwOnAbstract = false) + public function findTaggedServiceIds(string $name, bool $throwOnAbstract = false) { $this->usedTags[] = $name; $tags = []; @@ -1328,22 +1274,22 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Returns all tags the defined services use. * - * @return array An array of tags + * @return string[] */ public function findTags() { $tags = []; foreach ($this->getDefinitions() as $id => $definition) { - $tags = array_merge(array_keys($definition->getTags()), $tags); + $tags[] = array_keys($definition->getTags()); } - return array_unique($tags); + return array_unique(array_merge([], ...$tags)); } /** * Returns all tags not queried by findTaggedServiceIds. * - * @return string[] An array of tags + * @return string[] */ public function findUnusedTags() { @@ -1366,11 +1312,9 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Returns a ChildDefinition that will be used for autoconfiguring the interface/class. * - * @param string $interface The class or interface to match - * * @return ChildDefinition */ - public function registerForAutoconfiguration($interface) + public function registerForAutoconfiguration(string $interface) { if (!isset($this->autoconfiguredInstanceof[$interface])) { $this->autoconfiguredInstanceof[$interface] = new ChildDefinition(''); @@ -1379,16 +1323,61 @@ class ContainerBuilder extends Container implements TaggedContainerInterface return $this->autoconfiguredInstanceof[$interface]; } + /** + * Registers an attribute that will be used for autoconfiguring annotated classes. + * + * The third argument passed to the callable is the reflector of the + * class/method/property/parameter that the attribute targets. Using one or many of + * \ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter as a type-hint + * for this argument allows filtering which attributes should be passed to the callable. + * + * @template T + * + * @param class-string $attributeClass + * @param callable(ChildDefinition, T, \Reflector): void $configurator + */ + public function registerAttributeForAutoconfiguration(string $attributeClass, callable $configurator): void + { + $this->autoconfiguredAttributes[$attributeClass] = $configurator; + } + + /** + * Registers an autowiring alias that only binds to a specific argument name. + * + * The argument name is derived from $name if provided (from $id otherwise) + * using camel case: "foo.bar" or "foo_bar" creates an alias bound to + * "$fooBar"-named arguments with $type as type-hint. Such arguments will + * receive the service $id when autowiring is used. + */ + public function registerAliasForArgument(string $id, string $type, string $name = null): Alias + { + $name = (new Target($name ?? $id))->name; + + if (!preg_match('/^[a-zA-Z_\x7f-\xff]/', $name)) { + throw new InvalidArgumentException(sprintf('Invalid argument name "%s" for service "%s": the first character must be a letter.', $name, $id)); + } + + return $this->setAlias($type.' $'.$name, $id); + } + /** * Returns an array of ChildDefinition[] keyed by interface. * - * @return ChildDefinition[] + * @return array */ public function getAutoconfiguredInstanceof() { return $this->autoconfiguredInstanceof; } + /** + * @return array + */ + public function getAutoconfiguredAttributes(): array + { + return $this->autoconfiguredAttributes; + } + /** * Resolves env parameter placeholders in a string or an array. * @@ -1411,6 +1400,10 @@ class ContainerBuilder extends Container implements TaggedContainerInterface $value = $bag->resolveValue($value); } + if ($value instanceof Definition) { + $value = (array) $value; + } + if (\is_array($value)) { $result = []; foreach ($value as $k => $v) { @@ -1420,7 +1413,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface return $result; } - if (!\is_string($value) || 38 > \strlen($value)) { + if (!\is_string($value) || 38 > \strlen($value) || !preg_match('/env[_(]/i', $value)) { return $value; } $envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders; @@ -1439,7 +1432,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface $completed = true; } else { if (!\is_string($resolved) && !is_numeric($resolved)) { - throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "env(%s)" of type "%s" inside string value "%s".', $env, \gettype($resolved), $this->resolveEnvPlaceholders($value))); + throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "env(%s)" of type "%s" inside string value "%s".', $env, get_debug_type($resolved), $this->resolveEnvPlaceholders($value))); } $value = str_ireplace($placeholder, $resolved, $value); } @@ -1475,50 +1468,62 @@ class ContainerBuilder extends Container implements TaggedContainerInterface return $this->envCounters; } - /** - * @internal - */ - public function getNormalizedIds() - { - $normalizedIds = []; - - foreach ($this->normalizedIds as $k => $v) { - if ($v !== (string) $k) { - $normalizedIds[$k] = $v; - } - } - - return $normalizedIds; - } - /** * @final */ - public function log(CompilerPassInterface $pass, $message) + public function log(CompilerPassInterface $pass, string $message) { $this->getCompiler()->log($pass, $this->resolveEnvPlaceholders($message)); } /** - * {@inheritdoc} + * Checks whether a class is available and will remain available in the "no-dev" mode of Composer. + * + * When parent packages are provided and if any of them is in dev-only mode, + * the class will be considered available even if it is also in dev-only mode. */ - public function normalizeId($id) + final public static function willBeAvailable(string $package, string $class, array $parentPackages): bool { - if (!\is_string($id)) { - $id = (string) $id; + $skipDeprecation = 3 < \func_num_args() && func_get_arg(3); + $hasRuntimeApi = class_exists(InstalledVersions::class); + + if (!$hasRuntimeApi && !$skipDeprecation) { + trigger_deprecation('symfony/dependency-injection', '5.4', 'Calling "%s" when dependencies have been installed with Composer 1 is deprecated. Consider upgrading to Composer 2.', __METHOD__); } - return isset($this->definitions[$id]) || isset($this->aliasDefinitions[$id]) || isset($this->removedIds[$id]) ? $id : parent::normalizeId($id); + if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { + return false; + } + + if (!$hasRuntimeApi || !InstalledVersions::isInstalled($package) || InstalledVersions::isInstalled($package, false)) { + return true; + } + + // the package is installed but in dev-mode only, check if this applies to one of the parent packages too + + $rootPackage = InstalledVersions::getRootPackage()['name'] ?? ''; + + if ('symfony/symfony' === $rootPackage) { + return true; + } + + foreach ($parentPackages as $parentPackage) { + if ($rootPackage === $parentPackage || (InstalledVersions::isInstalled($parentPackage) && !InstalledVersions::isInstalled($parentPackage, false))) { + return true; + } + } + + return false; } /** * Gets removed binding ids. * - * @return array + * @return array * * @internal */ - public function getRemovedBindingIds() + public function getRemovedBindingIds(): array { return $this->removedBindingIds; } @@ -1526,15 +1531,13 @@ class ContainerBuilder extends Container implements TaggedContainerInterface /** * Removes bindings for a service. * - * @param string $id The service identifier - * * @internal */ - public function removeBindings($id) + public function removeBindings(string $id) { if ($this->hasDefinition($id)) { foreach ($this->getDefinition($id)->getBindings() as $key => $binding) { - list(, $bindingId) = $binding->getValues(); + [, $bindingId] = $binding->getValues(); $this->removedBindingIds[(int) $bindingId] = true; } } @@ -1545,11 +1548,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @param mixed $value An array of conditionals to return * - * @return array An array of Service conditionals + * @return string[] * - * @internal since version 3.4 + * @internal */ - public static function getServiceConditionals($value) + public static function getServiceConditionals($value): array { $services = []; @@ -1569,11 +1572,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface * * @param mixed $value An array of conditionals to return * - * @return array An array of uninitialized conditionals + * @return string[] * * @internal */ - public static function getInitializedConditionals($value) + public static function getInitializedConditionals($value): array { $services = []; @@ -1599,13 +1602,13 @@ class ContainerBuilder extends Container implements TaggedContainerInterface { $hash = substr(base64_encode(hash('sha256', serialize($value), true)), 0, 7); - return str_replace(['/', '+'], ['.', '_'], strtolower($hash)); + return str_replace(['/', '+'], ['.', '_'], $hash); } /** * {@inheritdoc} */ - protected function getEnv($name) + protected function getEnv(string $name) { $value = parent::getEnv($name); $bag = $this->getParameterBag(); @@ -1614,11 +1617,15 @@ class ContainerBuilder extends Container implements TaggedContainerInterface return $value; } - foreach ($bag->getEnvPlaceholders() as $env => $placeholders) { - if (isset($placeholders[$value])) { - $bag = new ParameterBag($bag->all()); + $envPlaceholders = $bag->getEnvPlaceholders(); + if (isset($envPlaceholders[$name][$value])) { + $bag = new ParameterBag($bag->all()); - return $bag->unescapeValue($bag->get("env($name)")); + return $bag->unescapeValue($bag->get("env($name)")); + } + foreach ($envPlaceholders as $env => $placeholders) { + if (isset($placeholders[$value])) { + return $this->getEnv($env); } } @@ -1630,31 +1637,32 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } } - private function callMethod($service, $call, array &$inlineServices) + private function callMethod(object $service, array $call, array &$inlineServices) { foreach (self::getServiceConditionals($call[1]) as $s) { if (!$this->has($s)) { - return; + return $service; } } foreach (self::getInitializedConditionals($call[1]) as $s) { if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE, $inlineServices)) { - return; + return $service; } } - \call_user_func_array([$service, $call[0]], $this->doResolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])), $inlineServices)); + $result = $service->{$call[0]}(...$this->doResolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])), $inlineServices)); + + return empty($call[2]) ? $service : $result; } /** * Shares a given service in the container. * - * @param mixed $service - * @param string|null $id + * @param mixed $service */ - private function shareService(Definition $definition, $service, $id, array &$inlineServices) + private function shareService(Definition $definition, $service, ?string $id, array &$inlineServices) { - $inlineServices[null !== $id ? $id : spl_object_hash($definition)] = $service; + $inlineServices[$id ?? spl_object_hash($definition)] = $service; if (null !== $id && $definition->isShared()) { $this->services[$id] = $service; @@ -1662,11 +1670,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface } } - private function getExpressionLanguage() + private function getExpressionLanguage(): ExpressionLanguage { if (null === $this->expressionLanguage) { - if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { - throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + if (!class_exists(\Symfony\Component\ExpressionLanguage\ExpressionLanguage::class)) { + throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); } $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); } @@ -1674,17 +1682,17 @@ class ContainerBuilder extends Container implements TaggedContainerInterface return $this->expressionLanguage; } - private function inVendors($path) + private function inVendors(string $path): bool { if (null === $this->vendors) { - $resource = new ComposerResource(); - $this->vendors = $resource->getVendors(); - $this->addResource($resource); + $this->vendors = (new ComposerResource())->getVendors(); } $path = realpath($path) ?: $path; foreach ($this->vendors as $vendor) { - if (0 === strpos($path, $vendor) && false !== strpbrk(substr($path, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) { + if (str_starts_with($path, $vendor) && false !== strpbrk(substr($path, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) { + $this->addResource(new FileResource($vendor.'/composer/installed.json')); + return true; } } diff --git a/lib/symfony/dependency-injection/ContainerInterface.php b/lib/symfony/dependency-injection/ContainerInterface.php index c5ab4c2eda..3a44b9967a 100644 --- a/lib/symfony/dependency-injection/ContainerInterface.php +++ b/lib/symfony/dependency-injection/ContainerInterface.php @@ -24,18 +24,16 @@ use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; */ interface ContainerInterface extends PsrContainerInterface { - const EXCEPTION_ON_INVALID_REFERENCE = 1; - const NULL_ON_INVALID_REFERENCE = 2; - const IGNORE_ON_INVALID_REFERENCE = 3; - const IGNORE_ON_UNINITIALIZED_REFERENCE = 4; + public const RUNTIME_EXCEPTION_ON_INVALID_REFERENCE = 0; + public const EXCEPTION_ON_INVALID_REFERENCE = 1; + public const NULL_ON_INVALID_REFERENCE = 2; + public const IGNORE_ON_INVALID_REFERENCE = 3; + public const IGNORE_ON_UNINITIALIZED_REFERENCE = 4; /** * Sets a service. - * - * @param string $id The service identifier - * @param object|null $service The service instance */ - public function set($id, $service); + public function set(string $id, ?object $service); /** * Gets a service. @@ -43,58 +41,44 @@ interface ContainerInterface extends PsrContainerInterface * @param string $id The service identifier * @param int $invalidBehavior The behavior when the service does not exist * - * @return object|null The associated service + * @return object|null * * @throws ServiceCircularReferenceException When a circular reference is detected * @throws ServiceNotFoundException When the service is not defined * * @see Reference */ - public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE); + public function get(string $id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE); /** - * Returns true if the given service is defined. - * - * @param string $id The service identifier - * - * @return bool true if the service is defined, false otherwise + * @return bool */ - public function has($id); + public function has(string $id); /** * Check for whether or not a service has been initialized. * - * @param string $id - * - * @return bool true if the service has been initialized, false otherwise + * @return bool */ - public function initialized($id); + public function initialized(string $id); /** - * Gets a parameter. - * - * @param string $name The parameter name - * - * @return mixed The parameter value + * @return array|bool|string|int|float|\UnitEnum|null * * @throws InvalidArgumentException if the parameter is not defined */ - public function getParameter($name); + public function getParameter(string $name); /** - * Checks if a parameter exists. - * - * @param string $name The parameter name - * - * @return bool The presence of parameter in container + * @return bool */ - public function hasParameter($name); + public function hasParameter(string $name); /** * Sets a parameter. * - * @param string $name The parameter name - * @param mixed $value The parameter value + * @param string $name The parameter name + * @param array|bool|string|int|float|\UnitEnum|null $value The parameter value */ - public function setParameter($name, $value); + public function setParameter(string $name, $value); } diff --git a/lib/symfony/dependency-injection/Definition.php b/lib/symfony/dependency-injection/Definition.php index c3a94f5c3c..7fc6752554 100644 --- a/lib/symfony/dependency-injection/Definition.php +++ b/lib/symfony/dependency-injection/Definition.php @@ -22,39 +22,46 @@ use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException; */ class Definition { + private const DEFAULT_DEPRECATION_TEMPLATE = 'The "%service_id%" service is deprecated. You should stop using it, as it will be removed in the future.'; + private $class; private $file; private $factory; private $shared = true; - private $deprecated = false; - private $deprecationTemplate; + private $deprecation = []; private $properties = []; private $calls = []; private $instanceof = []; private $autoconfigured = false; private $configurator; private $tags = []; - private $public = true; - private $private = true; + private $public = false; private $synthetic = false; private $abstract = false; private $lazy = false; private $decoratedService; private $autowired = false; - private $autowiringTypes = []; private $changes = []; private $bindings = []; private $errors = []; protected $arguments = []; - private static $defaultDeprecationTemplate = 'The "%service_id%" service is deprecated. You should stop using it, as it will soon be removed.'; + /** + * @internal + * + * Used to store the name of the inner id when using service decoration together with autowiring + */ + public $innerServiceId; /** - * @param string|null $class The service class - * @param array $arguments An array of arguments to pass to the service constructor + * @internal + * + * Used to store the behavior to follow when using service decoration and the decorated service is invalid */ - public function __construct($class = null, array $arguments = []) + public $decorationOnInvalid; + + public function __construct(string $class = null, array $arguments = []) { if (null !== $class) { $this->setClass($class); @@ -65,7 +72,7 @@ class Definition /** * Returns all changes tracked for the Definition object. * - * @return array An array of changes for this Definition + * @return array */ public function getChanges() { @@ -89,7 +96,7 @@ class Definition /** * Sets a factory. * - * @param string|array $factory A PHP function or an array containing a class/Reference and a method to call + * @param string|array|Reference|null $factory A PHP function, reference or an array containing a class/Reference and a method to call * * @return $this */ @@ -97,8 +104,10 @@ class Definition { $this->changes['factory'] = true; - if (\is_string($factory) && false !== strpos($factory, '::')) { + if (\is_string($factory) && str_contains($factory, '::')) { $factory = explode('::', $factory, 2); + } elseif ($factory instanceof Reference) { + $factory = [$factory, '__invoke']; } $this->factory = $factory; @@ -121,13 +130,12 @@ class Definition * * @param string|null $id The decorated service id, use null to remove decoration * @param string|null $renamedId The new decorated service id - * @param int $priority The priority of decoration * * @return $this * * @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals */ - public function setDecoratedService($id, $renamedId = null, $priority = 0) + public function setDecoratedService(?string $id, string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { if ($renamedId && $id === $renamedId) { throw new InvalidArgumentException(sprintf('The decorated service inner name for "%s" must be different than the service name itself.', $id)); @@ -138,7 +146,11 @@ class Definition if (null === $id) { $this->decoratedService = null; } else { - $this->decoratedService = [$id, $renamedId, (int) $priority]; + $this->decoratedService = [$id, $renamedId, $priority]; + + if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) { + $this->decoratedService[] = $invalidBehavior; + } } return $this; @@ -157,11 +169,9 @@ class Definition /** * Sets the service class. * - * @param string $class The service class - * * @return $this */ - public function setClass($class) + public function setClass(?string $class) { $this->changes['class'] = true; @@ -173,7 +183,7 @@ class Definition /** * Gets the service class. * - * @return string|null The service class + * @return string|null */ public function getClass() { @@ -217,12 +227,11 @@ class Definition /** * Sets a specific property. * - * @param string $name - * @param mixed $value + * @param mixed $value * * @return $this */ - public function setProperty($name, $value) + public function setProperty(string $name, $value) { $this->properties[$name] = $value; @@ -256,15 +265,15 @@ class Definition public function replaceArgument($index, $argument) { if (0 === \count($this->arguments)) { - throw new OutOfBoundsException('Cannot replace arguments if none have been configured yet.'); + throw new OutOfBoundsException(sprintf('Cannot replace arguments for class "%s" if none have been configured yet.', $this->class)); } if (\is_int($index) && ($index < 0 || $index > \count($this->arguments) - 1)) { - throw new OutOfBoundsException(sprintf('The index "%d" is not in the range [0, %d].', $index, \count($this->arguments) - 1)); + throw new OutOfBoundsException(sprintf('The index "%d" is not in the range [0, %d] of the arguments of class "%s".', $index, \count($this->arguments) - 1, $this->class)); } if (!\array_key_exists($index, $this->arguments)) { - throw new OutOfBoundsException(sprintf('The argument "%s" doesn\'t exist.', $index)); + throw new OutOfBoundsException(sprintf('The argument "%s" doesn\'t exist in class "%s".', $index, $this->class)); } $this->arguments[$index] = $argument; @@ -290,7 +299,7 @@ class Definition /** * Gets the arguments to pass to the service constructor/factory method. * - * @return array The array of arguments + * @return array */ public function getArguments() { @@ -302,14 +311,14 @@ class Definition * * @param int|string $index * - * @return mixed The argument value + * @return mixed * * @throws OutOfBoundsException When the argument does not exist */ public function getArgument($index) { if (!\array_key_exists($index, $this->arguments)) { - throw new OutOfBoundsException(sprintf('The argument "%s" doesn\'t exist.', $index)); + throw new OutOfBoundsException(sprintf('The argument "%s" doesn\'t exist in class "%s".', $index, $this->class)); } return $this->arguments[$index]; @@ -324,7 +333,7 @@ class Definition { $this->calls = []; foreach ($calls as $call) { - $this->addMethodCall($call[0], $call[1]); + $this->addMethodCall($call[0], $call[1], $call[2] ?? false); } return $this; @@ -333,19 +342,20 @@ class Definition /** * Adds a method to call after service initialization. * - * @param string $method The method name to call - * @param array $arguments An array of arguments to pass to the method call + * @param string $method The method name to call + * @param array $arguments An array of arguments to pass to the method call + * @param bool $returnsClone Whether the call returns the service instance or not * * @return $this * * @throws InvalidArgumentException on empty $method param */ - public function addMethodCall($method, array $arguments = []) + public function addMethodCall(string $method, array $arguments = [], bool $returnsClone = false) { if (empty($method)) { throw new InvalidArgumentException('Method name cannot be empty.'); } - $this->calls[] = [$method, $arguments]; + $this->calls[] = $returnsClone ? [$method, $arguments, true] : [$method, $arguments]; return $this; } @@ -353,16 +363,13 @@ class Definition /** * Removes a method to call after service initialization. * - * @param string $method The method name to remove - * * @return $this */ - public function removeMethodCall($method) + public function removeMethodCall(string $method) { foreach ($this->calls as $i => $call) { if ($call[0] === $method) { unset($this->calls[$i]); - break; } } @@ -372,11 +379,9 @@ class Definition /** * Check if the current definition has a given method to call after service initialization. * - * @param string $method The method name to search for - * * @return bool */ - public function hasMethodCall($method) + public function hasMethodCall(string $method) { foreach ($this->calls as $call) { if ($call[0] === $method) { @@ -390,7 +395,7 @@ class Definition /** * Gets the methods to call after service initialization. * - * @return array An array of method calls + * @return array */ public function getMethodCalls() { @@ -424,11 +429,9 @@ class Definition /** * Sets whether or not instanceof conditionals should be prepended with a global set. * - * @param bool $autoconfigured - * * @return $this */ - public function setAutoconfigured($autoconfigured) + public function setAutoconfigured(bool $autoconfigured) { $this->changes['autoconfigured'] = true; @@ -460,7 +463,7 @@ class Definition /** * Returns all tags. * - * @return array An array of tags + * @return array */ public function getTags() { @@ -470,24 +473,19 @@ class Definition /** * Gets a tag by name. * - * @param string $name The tag name - * - * @return array An array of attributes + * @return array */ - public function getTag($name) + public function getTag(string $name) { - return isset($this->tags[$name]) ? $this->tags[$name] : []; + return $this->tags[$name] ?? []; } /** * Adds a tag for this definition. * - * @param string $name The tag name - * @param array $attributes An array of attributes - * * @return $this */ - public function addTag($name, array $attributes = []) + public function addTag(string $name, array $attributes = []) { $this->tags[$name][] = $attributes; @@ -497,11 +495,9 @@ class Definition /** * Whether this definition has a tag with the given name. * - * @param string $name - * * @return bool */ - public function hasTag($name) + public function hasTag(string $name) { return isset($this->tags[$name]); } @@ -509,11 +505,9 @@ class Definition /** * Clears all tags for a given name. * - * @param string $name The tag name - * * @return $this */ - public function clearTag($name) + public function clearTag(string $name) { unset($this->tags[$name]); @@ -535,11 +529,9 @@ class Definition /** * Sets a file to require before creating the service. * - * @param string $file A full pathname to include - * * @return $this */ - public function setFile($file) + public function setFile(?string $file) { $this->changes['file'] = true; @@ -551,7 +543,7 @@ class Definition /** * Gets the file to require before creating the service. * - * @return string|null The full pathname to include + * @return string|null */ public function getFile() { @@ -561,15 +553,13 @@ class Definition /** * Sets if the service must be shared or not. * - * @param bool $shared Whether the service must be shared or not - * * @return $this */ - public function setShared($shared) + public function setShared(bool $shared) { $this->changes['shared'] = true; - $this->shared = (bool) $shared; + $this->shared = $shared; return $this; } @@ -587,16 +577,13 @@ class Definition /** * Sets the visibility of this service. * - * @param bool $boolean - * * @return $this */ - public function setPublic($boolean) + public function setPublic(bool $boolean) { $this->changes['public'] = true; - $this->public = (bool) $boolean; - $this->private = false; + $this->public = $boolean; return $this; } @@ -614,20 +601,15 @@ class Definition /** * Sets if this service is private. * - * When set, the "private" state has a higher precedence than "public". - * In version 3.4, a "private" service always remains publicly accessible, - * but triggers a deprecation notice when accessed from the container, - * so that the service can be made really private in 4.0. - * - * @param bool $boolean - * * @return $this + * + * @deprecated since Symfony 5.2, use setPublic() instead */ - public function setPrivate($boolean) + public function setPrivate(bool $boolean) { - $this->private = (bool) $boolean; + trigger_deprecation('symfony/dependency-injection', '5.2', 'The "%s()" method is deprecated, use "setPublic()" instead.', __METHOD__); - return $this; + return $this->setPublic(!$boolean); } /** @@ -637,21 +619,19 @@ class Definition */ public function isPrivate() { - return $this->private; + return !$this->public; } /** * Sets the lazy flag of this service. * - * @param bool $lazy - * * @return $this */ - public function setLazy($lazy) + public function setLazy(bool $lazy) { $this->changes['lazy'] = true; - $this->lazy = (bool) $lazy; + $this->lazy = $lazy; return $this; } @@ -670,13 +650,15 @@ class Definition * Sets whether this definition is synthetic, that is not constructed by the * container, but dynamically injected. * - * @param bool $boolean - * * @return $this */ - public function setSynthetic($boolean) + public function setSynthetic(bool $boolean) { - $this->synthetic = (bool) $boolean; + $this->synthetic = $boolean; + + if (!isset($this->changes['public'])) { + $this->setPublic(true); + } return $this; } @@ -696,13 +678,11 @@ class Definition * Whether this definition is abstract, that means it merely serves as a * template for other definitions. * - * @param bool $boolean - * * @return $this */ - public function setAbstract($boolean) + public function setAbstract(bool $boolean) { - $this->abstract = (bool) $boolean; + $this->abstract = $boolean; return $this; } @@ -722,30 +702,48 @@ class Definition * Whether this definition is deprecated, that means it should not be called * anymore. * - * @param bool $status - * @param string $template Template message to use if the definition is deprecated + * @param string $package The name of the composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The deprecation message to use * * @return $this * * @throws InvalidArgumentException when the message template is invalid */ - public function setDeprecated($status = true, $template = null) + public function setDeprecated(/* string $package, string $version, string $message */) { - if (null !== $template) { - if (preg_match('#[\r\n]|\*/#', $template)) { + $args = \func_get_args(); + + if (\func_num_args() < 3) { + trigger_deprecation('symfony/dependency-injection', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__); + + $status = $args[0] ?? true; + + if (!$status) { + trigger_deprecation('symfony/dependency-injection', '5.1', 'Passing a null message to un-deprecate a node is deprecated.'); + } + + $message = (string) ($args[1] ?? null); + $package = $version = ''; + } else { + $status = true; + $package = (string) $args[0]; + $version = (string) $args[1]; + $message = (string) $args[2]; + } + + if ('' !== $message) { + if (preg_match('#[\r\n]|\*/#', $message)) { throw new InvalidArgumentException('Invalid characters found in deprecation template.'); } - if (false === strpos($template, '%service_id%')) { + if (!str_contains($message, '%service_id%')) { throw new InvalidArgumentException('The deprecation template must contain the "%service_id%" placeholder.'); } - - $this->deprecationTemplate = $template; } $this->changes['deprecated'] = true; - - $this->deprecated = (bool) $status; + $this->deprecation = $status ? ['package' => $package, 'version' => $version, 'message' => $message ?: self::DEFAULT_DEPRECATION_TEMPLATE] : []; return $this; } @@ -758,25 +756,41 @@ class Definition */ public function isDeprecated() { - return $this->deprecated; + return (bool) $this->deprecation; } /** * Message to use if this definition is deprecated. * + * @deprecated since Symfony 5.1, use "getDeprecation()" instead. + * * @param string $id Service id relying on this definition * * @return string */ - public function getDeprecationMessage($id) + public function getDeprecationMessage(string $id) { - return str_replace('%service_id%', $id, $this->deprecationTemplate ?: self::$defaultDeprecationTemplate); + trigger_deprecation('symfony/dependency-injection', '5.1', 'The "%s()" method is deprecated, use "getDeprecation()" instead.', __METHOD__); + + return $this->getDeprecation($id)['message']; + } + + /** + * @param string $id Service id relying on this definition + */ + public function getDeprecation(string $id): array + { + return [ + 'package' => $this->deprecation['package'], + 'version' => $this->deprecation['version'], + 'message' => str_replace('%service_id%', $id, $this->deprecation['message']), + ]; } /** * Sets a configurator to call after the service is fully initialized. * - * @param string|array $configurator A PHP callable + * @param string|array|Reference|null $configurator A PHP function, reference or an array containing a class/Reference and a method to call * * @return $this */ @@ -784,8 +798,10 @@ class Definition { $this->changes['configurator'] = true; - if (\is_string($configurator) && false !== strpos($configurator, '::')) { + if (\is_string($configurator) && str_contains($configurator, '::')) { $configurator = explode('::', $configurator, 2); + } elseif ($configurator instanceof Reference) { + $configurator = [$configurator, '__invoke']; } $this->configurator = $configurator; @@ -796,35 +812,13 @@ class Definition /** * Gets the configurator to call after the service is fully initialized. * - * @return callable|array|null + * @return string|array|null */ public function getConfigurator() { return $this->configurator; } - /** - * Sets types that will default to this definition. - * - * @param string[] $types - * - * @return $this - * - * @deprecated since version 3.3, to be removed in 4.0. - */ - public function setAutowiringTypes(array $types) - { - @trigger_error('Autowiring-types are deprecated since Symfony 3.3 and will be removed in 4.0. Use aliases instead.', \E_USER_DEPRECATED); - - $this->autowiringTypes = []; - - foreach ($types as $type) { - $this->autowiringTypes[$type] = true; - } - - return $this; - } - /** * Is the definition autowired? * @@ -838,91 +832,21 @@ class Definition /** * Enables/disables autowiring. * - * @param bool $autowired - * * @return $this */ - public function setAutowired($autowired) + public function setAutowired(bool $autowired) { $this->changes['autowired'] = true; - $this->autowired = (bool) $autowired; + $this->autowired = $autowired; return $this; } - /** - * Gets autowiring types that will default to this definition. - * - * @return string[] - * - * @deprecated since version 3.3, to be removed in 4.0. - */ - public function getAutowiringTypes(/*$triggerDeprecation = true*/) - { - if (1 > \func_num_args() || func_get_arg(0)) { - @trigger_error('Autowiring-types are deprecated since Symfony 3.3 and will be removed in 4.0. Use aliases instead.', \E_USER_DEPRECATED); - } - - return array_keys($this->autowiringTypes); - } - - /** - * Adds a type that will default to this definition. - * - * @param string $type - * - * @return $this - * - * @deprecated since version 3.3, to be removed in 4.0. - */ - public function addAutowiringType($type) - { - @trigger_error(sprintf('Autowiring-types are deprecated since Symfony 3.3 and will be removed in 4.0. Use aliases instead for "%s".', $type), \E_USER_DEPRECATED); - - $this->autowiringTypes[$type] = true; - - return $this; - } - - /** - * Removes a type. - * - * @param string $type - * - * @return $this - * - * @deprecated since version 3.3, to be removed in 4.0. - */ - public function removeAutowiringType($type) - { - @trigger_error(sprintf('Autowiring-types are deprecated since Symfony 3.3 and will be removed in 4.0. Use aliases instead for "%s".', $type), \E_USER_DEPRECATED); - - unset($this->autowiringTypes[$type]); - - return $this; - } - - /** - * Will this definition default for the given type? - * - * @param string $type - * - * @return bool - * - * @deprecated since version 3.3, to be removed in 4.0. - */ - public function hasAutowiringType($type) - { - @trigger_error(sprintf('Autowiring-types are deprecated since Symfony 3.3 and will be removed in 4.0. Use aliases instead for "%s".', $type), \E_USER_DEPRECATED); - - return isset($this->autowiringTypes[$type]); - } - /** * Gets bindings. * - * @return array + * @return BoundArgument[] */ public function getBindings() { @@ -941,6 +865,10 @@ class Definition public function setBindings(array $bindings) { foreach ($bindings as $key => $binding) { + if (0 < strpos($key, '$') && $key !== $k = preg_replace('/[ \t]*\$/', ' $', $key)) { + unset($bindings[$key]); + $bindings[$key = $k] = $binding; + } if (!$binding instanceof BoundArgument) { $bindings[$key] = new BoundArgument($binding); } @@ -954,11 +882,19 @@ class Definition /** * Add an error that occurred when building this Definition. * - * @param string $error + * @param string|\Closure|self $error + * + * @return $this */ public function addError($error) { - $this->errors[] = $error; + if ($error instanceof self) { + $this->errors = array_merge($this->errors, $error->errors); + } else { + $this->errors[] = $error; + } + + return $this; } /** @@ -968,6 +904,19 @@ class Definition */ public function getErrors() { + foreach ($this->errors as $i => $error) { + if ($error instanceof \Closure) { + $this->errors[$i] = (string) $error(); + } elseif (!\is_string($error)) { + $this->errors[$i] = (string) $error; + } + } + return $this->errors; } + + public function hasErrors(): bool + { + return (bool) $this->errors; + } } diff --git a/lib/symfony/dependency-injection/DefinitionDecorator.php b/lib/symfony/dependency-injection/DefinitionDecorator.php deleted file mode 100644 index 4753c2aa93..0000000000 --- a/lib/symfony/dependency-injection/DefinitionDecorator.php +++ /dev/null @@ -1,29 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DependencyInjection; - -@trigger_error('The '.__NAMESPACE__.'\DefinitionDecorator class is deprecated since Symfony 3.3 and will be removed in 4.0. Use the Symfony\Component\DependencyInjection\ChildDefinition class instead.', \E_USER_DEPRECATED); - -class_exists(ChildDefinition::class); - -if (false) { - /** - * This definition decorates another definition. - * - * @author Johannes M. Schmitt - * - * @deprecated The DefinitionDecorator class is deprecated since version 3.3 and will be removed in 4.0. Use the Symfony\Component\DependencyInjection\ChildDefinition class instead. - */ - class DefinitionDecorator extends Definition - { - } -} diff --git a/lib/symfony/dependency-injection/Dumper/DumperInterface.php b/lib/symfony/dependency-injection/Dumper/DumperInterface.php index 8abc19250f..a00f30bf11 100644 --- a/lib/symfony/dependency-injection/Dumper/DumperInterface.php +++ b/lib/symfony/dependency-injection/Dumper/DumperInterface.php @@ -21,7 +21,7 @@ interface DumperInterface /** * Dumps the service container. * - * @return string|array The representation of the service container + * @return string|array */ public function dump(array $options = []); } diff --git a/lib/symfony/dependency-injection/Dumper/GraphvizDumper.php b/lib/symfony/dependency-injection/Dumper/GraphvizDumper.php index 0591e024f5..e8ca838345 100644 --- a/lib/symfony/dependency-injection/Dumper/GraphvizDumper.php +++ b/lib/symfony/dependency-injection/Dumper/GraphvizDumper.php @@ -54,7 +54,7 @@ class GraphvizDumper extends Dumper * * node.definition: The default options for services that are defined via service definition instances * * node.missing: The default options for missing services * - * @return string The dot representation of the service container + * @return string */ public function dump(array $options = []) { @@ -84,12 +84,7 @@ class GraphvizDumper extends Dumper return $this->container->resolveEnvPlaceholders($this->startDot().$this->addNodes().$this->addEdges().$this->endDot(), '__ENV_%s__'); } - /** - * Returns all nodes. - * - * @return string A string representation of all nodes - */ - private function addNodes() + private function addNodes(): string { $code = ''; foreach ($this->nodes as $id => $node) { @@ -101,12 +96,7 @@ class GraphvizDumper extends Dumper return $code; } - /** - * Returns all edges. - * - * @return string A string representation of all edges - */ - private function addEdges() + private function addEdges(): string { $code = ''; foreach ($this->edges as $id => $edges) { @@ -120,15 +110,8 @@ class GraphvizDumper extends Dumper /** * Finds all edges belonging to a specific service id. - * - * @param string $id The service id used to find edges - * @param array $arguments An array of arguments - * @param bool $required - * @param string $name - * - * @return array An array of edges */ - private function findEdges($id, array $arguments, $required, $name, $lazy = false) + private function findEdges(string $id, array $arguments, bool $required, string $name, bool $lazy = false): array { $edges = []; foreach ($arguments as $argument) { @@ -147,31 +130,25 @@ class GraphvizDumper extends Dumper $lazyEdge = $lazy || $this->container->getDefinition((string) $argument)->isLazy(); } - $edges[] = ['name' => $name, 'required' => $required, 'to' => $argument, 'lazy' => $lazyEdge]; + $edges[] = [['name' => $name, 'required' => $required, 'to' => $argument, 'lazy' => $lazyEdge]]; } elseif ($argument instanceof ArgumentInterface) { - $edges = array_merge($edges, $this->findEdges($id, $argument->getValues(), $required, $name, true)); + $edges[] = $this->findEdges($id, $argument->getValues(), $required, $name, true); } elseif ($argument instanceof Definition) { - $edges = array_merge($edges, - $this->findEdges($id, $argument->getArguments(), $required, ''), - $this->findEdges($id, $argument->getProperties(), false, '') - ); + $edges[] = $this->findEdges($id, $argument->getArguments(), $required, ''); + $edges[] = $this->findEdges($id, $argument->getProperties(), false, ''); + foreach ($argument->getMethodCalls() as $call) { - $edges = array_merge($edges, $this->findEdges($id, $call[1], false, $call[0].'()')); + $edges[] = $this->findEdges($id, $call[1], false, $call[0].'()'); } } elseif (\is_array($argument)) { - $edges = array_merge($edges, $this->findEdges($id, $argument, $required, $name, $lazy)); + $edges[] = $this->findEdges($id, $argument, $required, $name, $lazy); } } - return $edges; + return array_merge([], ...$edges); } - /** - * Finds all nodes. - * - * @return array An array of all nodes - */ - private function findNodes() + private function findNodes(): array { $nodes = []; @@ -206,7 +183,7 @@ class GraphvizDumper extends Dumper return $nodes; } - private function cloneContainer() + private function cloneContainer(): ContainerBuilder { $parameterBag = new ParameterBag($this->container->getParameterBag()->all()); @@ -221,12 +198,7 @@ class GraphvizDumper extends Dumper return $container; } - /** - * Returns the start dot. - * - * @return string The string representation of a start dot - */ - private function startDot() + private function startDot(): string { return sprintf("digraph sc {\n %s\n node [%s];\n edge [%s];\n\n", $this->addOptions($this->options['graph']), @@ -235,24 +207,12 @@ class GraphvizDumper extends Dumper ); } - /** - * Returns the end dot. - * - * @return string - */ - private function endDot() + private function endDot(): string { return "}\n"; } - /** - * Adds attributes. - * - * @param array $attributes An array of attributes - * - * @return string A comma separated list of attributes - */ - private function addAttributes(array $attributes) + private function addAttributes(array $attributes): string { $code = []; foreach ($attributes as $k => $v) { @@ -262,14 +222,7 @@ class GraphvizDumper extends Dumper return $code ? ', '.implode(', ', $code) : ''; } - /** - * Adds options. - * - * @param array $options An array of options - * - * @return string A space separated list of options - */ - private function addOptions(array $options) + private function addOptions(array $options): string { $code = []; foreach ($options as $k => $v) { @@ -279,26 +232,12 @@ class GraphvizDumper extends Dumper return implode(' ', $code); } - /** - * Dotizes an identifier. - * - * @param string $id The identifier to dotize - * - * @return string A dotized string - */ - private function dotize($id) + private function dotize(string $id): string { - return strtolower(preg_replace('/\W/i', '_', $id)); + return preg_replace('/\W/i', '_', $id); } - /** - * Compiles an array of aliases for a specified service id. - * - * @param string $id A service id - * - * @return array An array of aliases - */ - private function getAliases($id) + private function getAliases(string $id): array { $aliases = []; foreach ($this->container->getAliases() as $alias => $origin) { diff --git a/lib/symfony/dependency-injection/Dumper/PhpDumper.php b/lib/symfony/dependency-injection/Dumper/PhpDumper.php index 8605d755b3..bd63d0689e 100644 --- a/lib/symfony/dependency-injection/Dumper/PhpDumper.php +++ b/lib/symfony/dependency-injection/Dumper/PhpDumper.php @@ -11,26 +11,36 @@ namespace Symfony\Component\DependencyInjection\Dumper; +use Composer\Autoload\ClassLoader; +use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocator; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass; use Symfony\Component\DependencyInjection\Compiler\CheckCircularReferencesPass; +use Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraphNode; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\EnvParameterException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\ExpressionLanguage; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper; +use Symfony\Component\DependencyInjection\Loader\FileLoader; use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator; use Symfony\Component\DependencyInjection\TypedReference; use Symfony\Component\DependencyInjection\Variable; +use Symfony\Component\ErrorHandler\DebugClassLoader; use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\HttpKernel\Kernel; @@ -45,19 +55,22 @@ class PhpDumper extends Dumper /** * Characters that might appear in the generated variable name as first character. */ - const FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz'; + public const FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz'; /** * Characters that might appear in the generated variable name as any but the first character. */ - const NON_FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_'; + public const NON_FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_'; + /** + * @var \SplObjectStorage|null + */ private $definitionVariables; private $referenceVariables; private $variableCount; private $inlinedDefinitions; private $serviceCalls; - private $reservedVariables = ['instance', 'class', 'this']; + private $reservedVariables = ['instance', 'class', 'this', 'container']; private $expressionLanguage; private $targetDirRegex; private $targetDirMaxMatches; @@ -67,9 +80,19 @@ class PhpDumper extends Dumper private $namespace; private $asFiles; private $hotPathTag; + private $preloadTags; + private $inlineFactories; private $inlineRequires; private $inlinedRequires = []; private $circularReferences = []; + private $singleUsePrivateIds = []; + private $preload = []; + private $addThrow = false; + private $addGetService = false; + private $locatedIds = []; + private $serviceLocatorTag; + private $exportedVariables = []; + private $baseClass; /** * @var ProxyDumper @@ -82,7 +105,7 @@ class PhpDumper extends Dumper public function __construct(ContainerBuilder $container) { if (!$container->isCompiled()) { - @trigger_error('Dumping an uncompiled ContainerBuilder is deprecated since Symfony 3.3 and will not be supported anymore in 4.0. Compile the container beforehand.', \E_USER_DEPRECATED); + throw new LogicException('Cannot dump an uncompiled container.'); } parent::__construct($container); @@ -112,8 +135,10 @@ class PhpDumper extends Dumper */ public function dump(array $options = []) { + $this->locatedIds = []; $this->targetDirRegex = null; $this->inlinedRequires = []; + $this->exportedVariables = []; $options = array_merge([ 'class' => 'ProjectServiceContainer', 'base_class' => 'Container', @@ -121,22 +146,30 @@ class PhpDumper extends Dumper 'as_files' => false, 'debug' => true, 'hot_path_tag' => 'container.hot_path', + 'preload_tags' => ['container.preload', 'container.no_preload'], + 'inline_factories_parameter' => 'container.dumper.inline_factories', 'inline_class_loader_parameter' => 'container.dumper.inline_class_loader', + 'preload_classes' => [], + 'service_locator_tag' => 'container.service_locator', 'build_time' => time(), ], $options); + $this->addThrow = $this->addGetService = false; $this->namespace = $options['namespace']; $this->asFiles = $options['as_files']; $this->hotPathTag = $options['hot_path_tag']; - $this->inlineRequires = $options['inline_class_loader_parameter'] && $this->container->hasParameter($options['inline_class_loader_parameter']) && $this->container->getParameter($options['inline_class_loader_parameter']); + $this->preloadTags = $options['preload_tags']; + $this->inlineFactories = $this->asFiles && $options['inline_factories_parameter'] && $this->container->hasParameter($options['inline_factories_parameter']) && $this->container->getParameter($options['inline_factories_parameter']); + $this->inlineRequires = $options['inline_class_loader_parameter'] && ($this->container->hasParameter($options['inline_class_loader_parameter']) ? $this->container->getParameter($options['inline_class_loader_parameter']) : (\PHP_VERSION_ID < 70400 || $options['debug'])); + $this->serviceLocatorTag = $options['service_locator_tag']; - if (0 !== strpos($baseClass = $options['base_class'], '\\') && 'Container' !== $baseClass) { + if (!str_starts_with($baseClass = $options['base_class'], '\\') && 'Container' !== $baseClass) { $baseClass = sprintf('%s\%s', $options['namespace'] ? '\\'.$options['namespace'] : '', $baseClass); - $baseClassWithNamespace = $baseClass; + $this->baseClass = $baseClass; } elseif ('Container' === $baseClass) { - $baseClassWithNamespace = Container::class; + $this->baseClass = Container::class; } else { - $baseClassWithNamespace = $baseClass; + $this->baseClass = $baseClass; } $this->initializeMethodNamesMap('Container' === $baseClass ? Container::class : $baseClass); @@ -154,20 +187,7 @@ class PhpDumper extends Dumper } } - (new AnalyzeServiceReferencesPass(false, !$this->getProxyDumper() instanceof NullDumper))->process($this->container); - $checkedNodes = []; - $this->circularReferences = []; - foreach ($this->container->getCompiler()->getServiceReferenceGraph()->getNodes() as $id => $node) { - if (!$node->getValue() instanceof Definition) { - continue; - } - if (!isset($checkedNodes[$id])) { - $this->analyzeCircularReferences($id, $node->getOutEdges(), $checkedNodes); - } - } - $this->container->getCompiler()->getServiceReferenceGraph()->clear(); - $checkedNodes = []; - + $this->analyzeReferences(); $this->docStar = $options['debug'] ? '*' : ''; if (!empty($options['file']) && is_dir($dir = \dirname($options['file']))) { @@ -195,25 +215,53 @@ class PhpDumper extends Dumper } } + $proxyClasses = $this->inlineFactories ? $this->generateProxyClasses() : null; + + if ($options['preload_classes']) { + $this->preload = array_combine($options['preload_classes'], $options['preload_classes']); + } + $code = - $this->startClass($options['class'], $baseClass, $baseClassWithNamespace). - $this->addServices(). - $this->addDefaultParametersMethod(). - $this->endClass() + $this->startClass($options['class'], $baseClass, $this->inlineFactories && $proxyClasses). + $this->addServices($services). + $this->addDeprecatedAliases(). + $this->addDefaultParametersMethod() ; + $proxyClasses = $proxyClasses ?? $this->generateProxyClasses(); + + if ($this->addGetService) { + $code = preg_replace( + "/(\r?\n\r?\n public function __construct.+?\\{\r?\n)/s", + "\n protected \$getService;$1 \$this->getService = \\Closure::fromCallable([\$this, 'getService']);\n", + $code, + 1 + ); + } + if ($this->asFiles) { - $fileStart = <<docStar} + * @internal This class has been auto-generated by the Symfony Dependency Injection Component. + */ +class %s extends {$options['class']} +{%s} EOF; $files = []; - - if ($ids = array_keys($this->container->getRemovedIds())) { + $preloadedFiles = []; + $ids = $this->container->getRemovedIds(); + foreach ($this->container->getDefinitions() as $id => $definition) { + if (!$definition->isPublic()) { + $ids[$id] = true; + } + } + if ($ids = array_keys($ids)) { sort($ids); $c = "generateServiceFiles() as $file => $c) { - $files[$file] = $fileStart.$c; + if (!$this->inlineFactories) { + foreach ($this->generateServiceFiles($services) as $file => [$c, $preload]) { + $files[$file] = sprintf($fileTemplate, substr($file, 0, -4), $c); + + if ($preload) { + $preloadedFiles[$file] = $file; + } + } + foreach ($proxyClasses as $file => $c) { + $files[$file] = "generateProxyClasses() as $file => $c) { - $files[$file] = "endClass(); + + if ($this->inlineFactories && $proxyClasses) { + $files['proxy-classes.php'] = " $c) { - $code["Container{$hash}/{$file}"] = $c; + $code["Container{$hash}/{$file}"] = substr_replace($c, "namespace ? "\nnamespace {$this->namespace};\n" : ''; $time = $options['build_time']; $id = hash('crc32', $hash.$time); + $this->asFiles = false; + + if ($this->preload && null !== $autoloadFile = $this->getAutoloadFile()) { + $autoloadFile = trim($this->export($autoloadFile), '()\\'); + + $preloadedFiles = array_reverse($preloadedFiles); + if ('' !== $preloadedFiles = implode("';\nrequire __DIR__.'/", $preloadedFiles)) { + $preloadedFiles = "require __DIR__.'/$preloadedFiles';\n"; + } + + $code[$options['class'].'.preload.php'] = <<= 7.4 when preloading is desired + +use Symfony\Component\DependencyInjection\Dumper\Preloader; + +if (in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) { + return; +} + +require $autoloadFile; +(require __DIR__.'/{$options['class']}.php')->set(\\Container{$hash}\\{$options['class']}::class, null); +$preloadedFiles +\$classes = []; + +EOF; + + foreach ($this->preload as $class) { + if (!$class || str_contains($class, '$') || \in_array($class, ['int', 'float', 'string', 'bool', 'resource', 'object', 'array', 'null', 'callable', 'iterable', 'mixed', 'void'], true)) { + continue; + } + if (!(class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) || ((new \ReflectionClass($class))->isUserDefined() && !\in_array($class, ['Attribute', 'JsonException', 'ReturnTypeWillChange', 'Stringable', 'UnhandledMatchError', 'ValueError'], true))) { + $code[$options['class'].'.preload.php'] .= sprintf("\$classes[] = '%s';\n", $class); + } + } + + $code[$options['class'].'.preload.php'] .= <<<'EOF' + +$preloaded = Preloader::preload($classes); + +EOF; + } $code[$options['class'].'.php'] = <<generateProxyClasses() as $c) { + $code .= $this->endClass(); + foreach ($proxyClasses as $c) { $code .= $c; } } @@ -274,6 +387,9 @@ EOF; $this->targetDirRegex = null; $this->inlinedRequires = []; $this->circularReferences = []; + $this->locatedIds = []; + $this->exportedVariables = []; + $this->preload = []; $unusedEnvs = []; foreach ($this->container->getEnvCounters() as $env => $use) { @@ -290,10 +406,8 @@ EOF; /** * Retrieves the currently set proxy dumper or instantiates one. - * - * @return ProxyDumper */ - private function getProxyDumper() + private function getProxyDumper(): ProxyDumper { if (!$this->proxyDumper) { $this->proxyDumper = new NullDumper(); @@ -302,58 +416,99 @@ EOF; return $this->proxyDumper; } - private function analyzeCircularReferences($sourceId, array $edges, &$checkedNodes, &$currentPath = [], $byConstructor = true) + private function analyzeReferences() { - $checkedNodes[$sourceId] = true; - $currentPath[$sourceId] = $byConstructor; + (new AnalyzeServiceReferencesPass(false, !$this->getProxyDumper() instanceof NullDumper))->process($this->container); + $checkedNodes = []; + $this->circularReferences = []; + $this->singleUsePrivateIds = []; + foreach ($this->container->getCompiler()->getServiceReferenceGraph()->getNodes() as $id => $node) { + if (!$node->getValue() instanceof Definition) { + continue; + } + if ($this->isSingleUsePrivateNode($node)) { + $this->singleUsePrivateIds[$id] = $id; + } + + $this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes); + } + + $this->container->getCompiler()->getServiceReferenceGraph()->clear(); + $this->singleUsePrivateIds = array_diff_key($this->singleUsePrivateIds, $this->circularReferences); + } + + private function collectCircularReferences(string $sourceId, array $edges, array &$checkedNodes, array &$loops = [], array $path = [], bool $byConstructor = true): void + { + $path[$sourceId] = $byConstructor; + $checkedNodes[$sourceId] = true; foreach ($edges as $edge) { $node = $edge->getDestNode(); $id = $node->getId(); + if ($sourceId === $id || !$node->getValue() instanceof Definition || $edge->isLazy() || $edge->isWeak()) { + continue; + } - if (!$node->getValue() instanceof Definition || $sourceId === $id || $edge->isLazy() || $edge->isWeak()) { - // no-op - } elseif (isset($currentPath[$id])) { - $this->addCircularReferences($id, $currentPath, $edge->isReferencedByConstructor()); + if (isset($path[$id])) { + $loop = null; + $loopByConstructor = $edge->isReferencedByConstructor(); + $pathInLoop = [$id, []]; + foreach ($path as $k => $pathByConstructor) { + if (null !== $loop) { + $loop[] = $k; + $pathInLoop[1][$k] = $pathByConstructor; + $loops[$k][] = &$pathInLoop; + $loopByConstructor = $loopByConstructor && $pathByConstructor; + } elseif ($k === $id) { + $loop = []; + } + } + $this->addCircularReferences($id, $loop, $loopByConstructor); } elseif (!isset($checkedNodes[$id])) { - $this->analyzeCircularReferences($id, $node->getOutEdges(), $checkedNodes, $currentPath, $edge->isReferencedByConstructor()); - } elseif (isset($this->circularReferences[$id])) { - $this->connectCircularReferences($id, $currentPath, $edge->isReferencedByConstructor()); + $this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes, $loops, $path, $edge->isReferencedByConstructor()); + } elseif (isset($loops[$id])) { + // we already had detected loops for this edge + // let's check if we have a common ancestor in one of the detected loops + foreach ($loops[$id] as [$first, $loopPath]) { + if (!isset($path[$first])) { + continue; + } + // We have a common ancestor, let's fill the current path + $fillPath = null; + foreach ($loopPath as $k => $pathByConstructor) { + if (null !== $fillPath) { + $fillPath[$k] = $pathByConstructor; + } elseif ($k === $id) { + $fillPath = $path; + $fillPath[$k] = $pathByConstructor; + } + } + + // we can now build the loop + $loop = null; + $loopByConstructor = $edge->isReferencedByConstructor(); + foreach ($fillPath as $k => $pathByConstructor) { + if (null !== $loop) { + $loop[] = $k; + $loopByConstructor = $loopByConstructor && $pathByConstructor; + } elseif ($k === $first) { + $loop = []; + } + } + $this->addCircularReferences($first, $loop, true); + break; + } } } - unset($currentPath[$sourceId]); + unset($path[$sourceId]); } - private function connectCircularReferences($sourceId, &$currentPath, $byConstructor, &$subPath = []) + private function addCircularReferences(string $sourceId, array $currentPath, bool $byConstructor) { - $currentPath[$sourceId] = $subPath[$sourceId] = $byConstructor; - - foreach ($this->circularReferences[$sourceId] as $id => $byConstructor) { - if (isset($currentPath[$id])) { - $this->addCircularReferences($id, $currentPath, $byConstructor); - } elseif (!isset($subPath[$id]) && isset($this->circularReferences[$id])) { - $this->connectCircularReferences($id, $currentPath, $byConstructor, $subPath); - } - } - unset($currentPath[$sourceId], $subPath[$sourceId]); - } - - private function addCircularReferences($id, $currentPath, $byConstructor) - { - $currentPath[$id] = $byConstructor; - $circularRefs = []; - - foreach (array_reverse($currentPath) as $parentId => $v) { - $byConstructor = $byConstructor && $v; - $circularRefs[] = $parentId; - - if ($parentId === $id) { - break; - } - } - - $currentId = $id; - foreach ($circularRefs as $parentId) { + $currentId = $sourceId; + $currentPath = array_reverse($currentPath); + $currentPath[] = $currentId; + foreach ($currentPath as $parentId) { if (empty($this->circularReferences[$parentId][$currentId])) { $this->circularReferences[$parentId][$currentId] = $byConstructor; } @@ -362,7 +517,7 @@ EOF; } } - private function collectLineage($class, array &$lineage) + private function collectLineage(string $class, array &$lineage) { if (isset($lineage[$class])) { return; @@ -370,17 +525,19 @@ EOF; if (!$r = $this->container->getReflectionClass($class, false)) { return; } - if ($this->container instanceof $class) { + if (is_a($class, $this->baseClass, true)) { return; } $file = $r->getFileName(); - if (') : eval()\'d code' === substr($file, -17)) { + if (str_ends_with($file, ') : eval()\'d code')) { $file = substr($file, 0, strrpos($file, '(', -17)); } if (!$file || $this->doExport($file) === $exportedFile = $this->export($file)) { return; } + $lineage[$class] = substr($exportedFile, 1, -1); + if ($parent = $r->getParentClass()) { $this->collectLineage($parent->name, $lineage); } @@ -393,14 +550,16 @@ EOF; $this->collectLineage($parent->name, $lineage); } + unset($lineage[$class]); $lineage[$class] = substr($exportedFile, 1, -1); } - private function generateProxyClasses() + private function generateProxyClasses(): array { + $proxyClasses = []; $alreadyGenerated = []; $definitions = $this->container->getDefinitions(); - $strip = '' === $this->docStar && method_exists('Symfony\Component\HttpKernel\Kernel', 'stripComments'); + $strip = '' === $this->docStar && method_exists(Kernel::class, 'stripComments'); $proxyDumper = $this->getProxyDumper(); ksort($definitions); foreach ($definitions as $definition) { @@ -416,39 +575,62 @@ EOF; if ("\n" === $proxyCode = "\n".$proxyDumper->getProxyCode($definition)) { continue; } + + if ($this->inlineRequires) { + $lineage = []; + $this->collectLineage($class, $lineage); + + $code = ''; + foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) { + if ($this->inlineFactories) { + $this->inlinedRequires[$file] = true; + } + $code .= sprintf("include_once %s;\n", $file); + } + + $proxyCode = $code.$proxyCode; + } + if ($strip) { $proxyCode = " $proxyCode; + + $proxyClass = explode(' ', $this->inlineRequires ? substr($proxyCode, \strlen($code)) : $proxyCode, 3)[1]; + + if ($this->asFiles || $this->namespace) { + $proxyCode .= "\nif (!\\class_exists('$proxyClass', false)) {\n \\class_alias(__NAMESPACE__.'\\\\$proxyClass', '$proxyClass', false);\n}\n"; + } + + $proxyClasses[$proxyClass.'.php'] = $proxyCode; } + + return $proxyClasses; } - /** - * Generates the require_once statement for service includes. - * - * @return string - */ - private function addServiceInclude($cId, Definition $definition) + private function addServiceInclude(string $cId, Definition $definition): string { $code = ''; - if ($this->inlineRequires && !$this->isHotPath($definition)) { + if ($this->inlineRequires && (!$this->isHotPath($definition) || $this->getProxyDumper()->isProxyCandidate($definition))) { $lineage = []; foreach ($this->inlinedDefinitions as $def) { - if (!$def->isDeprecated() && \is_string($class = \is_array($factory = $def->getFactory()) && \is_string($factory[0]) ? $factory[0] : $def->getClass())) { - $this->collectLineage($class, $lineage); + if (!$def->isDeprecated()) { + foreach ($this->getClasses($def, $cId) as $class) { + $this->collectLineage($class, $lineage); + } } } - foreach ($this->serviceCalls as $id => list($callCount, $behavior)) { + foreach ($this->serviceCalls as $id => [$callCount, $behavior]) { if ('service_container' !== $id && $id !== $cId && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $behavior && $this->container->has($id) && $this->isTrivialInstance($def = $this->container->findDefinition($id)) - && \is_string($class = \is_array($factory = $def->getFactory()) && \is_string($factory[0]) ? $factory[0] : $def->getClass()) ) { - $this->collectLineage($class, $lineage); + foreach ($this->getClasses($def, $cId) as $class) { + $this->collectLineage($class, $lineage); + } } } @@ -459,7 +641,9 @@ EOF; foreach ($this->inlinedDefinitions as $def) { if ($file = $def->getFile()) { - $code .= sprintf(" include_once %s;\n", $this->dumpValue($file)); + $file = $this->dumpValue($file); + $file = '(' === $file[0] ? substr($file, 1, -1) : $file; + $code .= sprintf(" include_once %s;\n", $file); } } @@ -471,29 +655,29 @@ EOF; } /** - * Generates the service instance. - * - * @param string $id - * @param bool $isSimpleInstance - * - * @return string - * * @throws InvalidArgumentException * @throws RuntimeException */ - private function addServiceInstance($id, Definition $definition, $isSimpleInstance) + private function addServiceInstance(string $id, Definition $definition, bool $isSimpleInstance): string { $class = $this->dumpValue($definition->getClass()); - if (0 === strpos($class, "'") && false === strpos($class, '$') && !preg_match('/^\'(?:\\\{2})?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) { + if (str_starts_with($class, "'") && !str_contains($class, '$') && !preg_match('/^\'(?:\\\{2})?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) { throw new InvalidArgumentException(sprintf('"%s" is not a valid class name for the "%s" service.', $class, $id)); } $isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition); $instantiation = ''; - if (!$isProxyCandidate && $definition->isShared()) { - $instantiation = sprintf('$this->services[%s] = %s', $this->doExport($id), $isSimpleInstance ? '' : '$instance'); + $lastWitherIndex = null; + foreach ($definition->getMethodCalls() as $k => $call) { + if ($call[2] ?? false) { + $lastWitherIndex = $k; + } + } + + if (!$isProxyCandidate && $definition->isShared() && !isset($this->singleUsePrivateIds[$id]) && null === $lastWitherIndex) { + $instantiation = sprintf('$this->%s[%s] = %s', $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $this->doExport($id), $isSimpleInstance ? '' : '$instance'); } elseif (!$isSimpleInstance) { $instantiation = '$instance'; } @@ -505,16 +689,14 @@ EOF; $instantiation .= ' = '; } - return $this->addNewInstance($definition, $return, $instantiation, $id); + return $this->addNewInstance($definition, ' '.$return.$instantiation, $id); } - /** - * Checks if the definition is a trivial instance. - * - * @return bool - */ - private function isTrivialInstance(Definition $definition) + private function isTrivialInstance(Definition $definition): bool { + if ($definition->hasErrors()) { + return true; + } if ($definition->isSynthetic() || $definition->getFile() || $definition->getMethodCalls() || $definition->getProperties() || $definition->getConfigurator()) { return false; } @@ -537,13 +719,13 @@ EOF; if ($v instanceof Reference && $this->container->has($id = (string) $v) && $this->container->findDefinition($id)->isSynthetic()) { continue; } - if (!is_scalar($v) || $this->dumpValue($v) !== $this->dumpValue($v, false)) { + if (!\is_scalar($v) || $this->dumpValue($v) !== $this->dumpValue($v, false)) { return false; } } } elseif ($arg instanceof Reference && $this->container->has($id = (string) $arg) && $this->container->findDefinition($id)->isSynthetic()) { continue; - } elseif (!is_scalar($arg) || $this->dumpValue($arg) !== $this->dumpValue($arg, false)) { + } elseif (!\is_scalar($arg) || $this->dumpValue($arg) !== $this->dumpValue($arg, false)) { return false; } } @@ -551,29 +733,38 @@ EOF; return true; } - /** - * Adds method calls to a service definition. - * - * @param string $variableName - * - * @return string - */ - private function addServiceMethodCalls(Definition $definition, $variableName = 'instance') + private function addServiceMethodCalls(Definition $definition, string $variableName, ?string $sharedNonLazyId): string { + $lastWitherIndex = null; + foreach ($definition->getMethodCalls() as $k => $call) { + if ($call[2] ?? false) { + $lastWitherIndex = $k; + } + } + $calls = ''; - foreach ($definition->getMethodCalls() as $call) { + foreach ($definition->getMethodCalls() as $k => $call) { $arguments = []; - foreach ($call[1] as $value) { - $arguments[] = $this->dumpValue($value); + foreach ($call[1] as $i => $value) { + $arguments[] = (\is_string($i) ? $i.': ' : '').$this->dumpValue($value); } - $calls .= $this->wrapServiceConditionals($call[1], sprintf(" \$%s->%s(%s);\n", $variableName, $call[0], implode(', ', $arguments))); + $witherAssignation = ''; + + if ($call[2] ?? false) { + if (null !== $sharedNonLazyId && $lastWitherIndex === $k) { + $witherAssignation = sprintf('$this->%s[\'%s\'] = ', $definition->isPublic() ? 'services' : 'privates', $sharedNonLazyId); + } + $witherAssignation .= sprintf('$%s = ', $variableName); + } + + $calls .= $this->wrapServiceConditionals($call[1], sprintf(" %s\$%s->%s(%s);\n", $witherAssignation, $variableName, $call[0], implode(', ', $arguments))); } return $calls; } - private function addServiceProperties(Definition $definition, $variableName = 'instance') + private function addServiceProperties(Definition $definition, string $variableName = 'instance'): string { $code = ''; foreach ($definition->getProperties() as $name => $value) { @@ -583,14 +774,7 @@ EOF; return $code; } - /** - * Adds configurator definition. - * - * @param string $variableName - * - * @return string - */ - private function addServiceConfigurator(Definition $definition, $variableName = 'instance') + private function addServiceConfigurator(Definition $definition, string $variableName = 'instance'): string { if (!$callable = $definition->getConfigurator()) { return ''; @@ -604,30 +788,22 @@ EOF; } $class = $this->dumpValue($callable[0]); - // If the class is a string we can optimize call_user_func away - if (0 === strpos($class, "'") && false === strpos($class, '$')) { + // If the class is a string we can optimize away + if (str_starts_with($class, "'") && !str_contains($class, '$')) { return sprintf(" %s::%s(\$%s);\n", $this->dumpLiteralClass($class), $callable[1], $variableName); } - if (0 === strpos($class, 'new ')) { + if (str_starts_with($class, 'new ')) { return sprintf(" (%s)->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); } - return sprintf(" \\call_user_func([%s, '%s'], \$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); + return sprintf(" [%s, '%s'](\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); } return sprintf(" %s(\$%s);\n", $callable, $variableName); } - /** - * Adds a service. - * - * @param string $id - * @param string &$file - * - * @return string - */ - private function addService($id, Definition $definition, &$file = null) + private function addService(string $id, Definition $definition): array { $this->definitionVariables = new \SplObjectStorage(); $this->referenceVariables = []; @@ -638,7 +814,7 @@ EOF; if ($class = $definition->getClass()) { $class = $class instanceof Parameter ? '%'.$class.'%' : $this->container->resolveEnvPlaceholders($class); - $return[] = sprintf(0 === strpos($class, '%') ? '@return object A %1$s instance' : '@return \%s', ltrim($class, '\\')); + $return[] = sprintf(str_starts_with($class, '%') ? '@return object A %1$s instance' : '@return \%s', ltrim($class, '\\')); } elseif ($definition->getFactory()) { $factory = $definition->getFactory(); if (\is_string($factory)) { @@ -651,11 +827,12 @@ EOF; } if ($definition->isDeprecated()) { - if ($return && 0 === strpos($return[\count($return) - 1], '@return')) { + if ($return && str_starts_with($return[\count($return) - 1], '@return')) { $return[] = ''; } - $return[] = sprintf('@deprecated %s', $definition->getDeprecationMessage($id)); + $deprecation = $definition->getDeprecation($id); + $return[] = sprintf('@deprecated %s', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '').$deprecation['message']); } $return = str_replace("\n * \n", "\n *\n", implode("\n * ", $return)); @@ -664,64 +841,117 @@ EOF; $shared = $definition->isShared() ? ' shared' : ''; $public = $definition->isPublic() ? 'public' : 'private'; $autowired = $definition->isAutowired() ? ' autowired' : ''; + $asFile = $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition); + $methodName = $this->generateMethodName($id); - if ($definition->isLazy()) { + if ($asFile || $definition->isLazy()) { $lazyInitialization = '$lazyLoad = true'; } else { $lazyInitialization = ''; } - $asFile = $this->asFiles && $definition->isShared() && !$this->isHotPath($definition); - $methodName = $this->generateMethodName($id); - if ($asFile) { - $file = $methodName.'.php'; - $code = " // Returns the $public '$id'$shared$autowired service.\n\n"; - } else { - $code = <<docStar} * Gets the $public '$id'$shared$autowired service. * * $return EOF; - $code = str_replace('*/', ' ', $code).<<serviceCalls = []; - $this->inlinedDefinitions = $this->getDefinitionsFromArguments([$definition], null, $this->serviceCalls); - - $code .= $this->addServiceInclude($id, $definition); - - if ($this->getProxyDumper()->isProxyCandidate($definition)) { - $factoryCode = $asFile ? "\$this->load('%s.php', false)" : '$this->%s(false)'; - $code .= $this->getProxyDumper()->getProxyFactoryCode($definition, $id, sprintf($factoryCode, $methodName, $this->doExport($id))); - } - - if ($definition->isDeprecated()) { - $code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", $this->export($definition->getDeprecationMessage($id))); - } - - $code .= $this->addInlineService($id, $definition); if ($asFile) { - $code = implode("\n", array_map(function ($line) { return $line ? substr($line, 8) : $line; }, explode("\n", $code))); + $file = $methodName.'.php'; + $code = str_replace("protected function {$methodName}(", 'public static function do($container, ', $code); } else { - $code .= " }\n"; + $file = null; } + if ($definition->hasErrors() && $e = $definition->getErrors()) { + $this->addThrow = true; + + $code .= sprintf(" \$this->throw(%s);\n", $this->export(reset($e))); + } else { + $this->serviceCalls = []; + $this->inlinedDefinitions = $this->getDefinitionsFromArguments([$definition], null, $this->serviceCalls); + + if ($definition->isDeprecated()) { + $deprecation = $definition->getDeprecation($id); + $code .= sprintf(" trigger_deprecation(%s, %s, %s);\n\n", $this->export($deprecation['package']), $this->export($deprecation['version']), $this->export($deprecation['message'])); + } elseif ($definition->hasTag($this->hotPathTag) || !$definition->hasTag($this->preloadTags[1])) { + foreach ($this->inlinedDefinitions as $def) { + foreach ($this->getClasses($def, $id) as $class) { + $this->preload[$class] = $class; + } + } + } + + if (!$definition->isShared()) { + $factory = sprintf('$this->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id)); + } + + if ($isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition)) { + if (!$definition->isShared()) { + $code .= sprintf(' %s = %1$s ?? ', $factory); + + if ($asFile) { + $code .= "function () {\n"; + $code .= " return self::do(\$container);\n"; + $code .= " };\n\n"; + } else { + $code .= sprintf("\\Closure::fromCallable([\$this, '%s']);\n\n", $methodName); + } + } + + $factoryCode = $asFile ? 'self::do($container, false)' : sprintf('$this->%s(false)', $methodName); + $factoryCode = $this->getProxyDumper()->getProxyFactoryCode($definition, $id, $factoryCode); + $code .= $asFile ? preg_replace('/function \(([^)]*+)\)( {|:)/', 'function (\1) use ($container)\2', $factoryCode) : $factoryCode; + } + + $c = $this->addServiceInclude($id, $definition); + + if ('' !== $c && $isProxyCandidate && !$definition->isShared()) { + $c = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $c))); + $code .= " static \$include = true;\n\n"; + $code .= " if (\$include) {\n"; + $code .= $c; + $code .= " \$include = false;\n"; + $code .= " }\n\n"; + } else { + $code .= $c; + } + + $c = $this->addInlineService($id, $definition); + + if (!$isProxyCandidate && !$definition->isShared()) { + $c = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $c))); + $lazyloadInitialization = $definition->isLazy() ? '$lazyLoad = true' : ''; + + $c = sprintf(" %s = function (%s) {\n%s };\n\n return %1\$s();\n", $factory, $lazyloadInitialization, $c); + } + + $code .= $c; + } + + if ($asFile) { + $code = str_replace('$this', '$container', $code); + $code = preg_replace('/function \(([^)]*+)\)( {|:)/', 'function (\1) use ($container)\2', $code); + } + + $code .= " }\n"; + $this->definitionVariables = $this->inlinedDefinitions = null; $this->referenceVariables = $this->serviceCalls = null; - return $code; + return [$file, $code]; } - private function addInlineVariables($id, Definition $definition, array $arguments, $forConstructor) + private function addInlineVariables(string $id, Definition $definition, array $arguments, bool $forConstructor): string { $code = ''; @@ -729,7 +959,7 @@ EOF; if (\is_array($argument)) { $code .= $this->addInlineVariables($id, $definition, $argument, $forConstructor); } elseif ($argument instanceof Reference) { - $code .= $this->addInlineReference($id, $definition, $this->container->normalizeId($argument), $forConstructor); + $code .= $this->addInlineReference($id, $definition, $argument, $forConstructor); } elseif ($argument instanceof Definition) { $code .= $this->addInlineService($id, $definition, $argument, $forConstructor); } @@ -738,13 +968,13 @@ EOF; return $code; } - private function addInlineReference($id, Definition $definition, $targetId, $forConstructor) + private function addInlineReference(string $id, Definition $definition, string $targetId, bool $forConstructor): string { while ($this->container->hasAlias($targetId)) { $targetId = (string) $this->container->getAlias($targetId); } - list($callCount, $behavior) = $this->serviceCalls[$targetId]; + [$callCount, $behavior] = $this->serviceCalls[$targetId]; if ($id === $targetId) { return $this->addInlineService($id, $definition, $definition); @@ -754,6 +984,10 @@ EOF; return ''; } + if ($this->container->hasDefinition($targetId) && ($def = $this->container->getDefinition($targetId)) && !$def->isShared()) { + return ''; + } + $hasSelfRef = isset($this->circularReferences[$id][$targetId]) && !isset($this->definitionVariables[$definition]); if ($hasSelfRef && !$forConstructor && !$forConstructor = !$this->circularReferences[$id][$targetId]) { @@ -784,19 +1018,19 @@ EOF; EOTXT , - 'services', + $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $this->doExport($id) ); return $code; } - private function addInlineService($id, Definition $definition, Definition $inlineDef = null, $forConstructor = true) + private function addInlineService(string $id, Definition $definition, Definition $inlineDef = null, bool $forConstructor = true): string { $code = ''; if ($isSimpleInstance = $isRootInstance = null === $inlineDef) { - foreach ($this->serviceCalls as $targetId => list($callCount, $behavior, $byConstructor)) { + foreach ($this->serviceCalls as $targetId => [$callCount, $behavior, $byConstructor]) { if ($byConstructor && isset($this->circularReferences[$id][$targetId]) && !$this->circularReferences[$id][$targetId]) { $code .= $this->addInlineReference($id, $definition, $targetId, $forConstructor); } @@ -827,7 +1061,7 @@ EOTXT if ('instance' === $name) { $code .= $this->addServiceInstance($id, $definition, $isSimpleInstance); } else { - $code .= $this->addNewInstance($inlineDef, '$'.$name, ' = ', $id); + $code .= $this->addNewInstance($inlineDef, ' $'.$name.' = ', $id); } if ('' !== $inline = $this->addInlineVariables($id, $definition, $arguments, false)) { @@ -837,7 +1071,7 @@ EOTXT } $code .= $this->addServiceProperties($inlineDef, $name); - $code .= $this->addServiceMethodCalls($inlineDef, $name); + $code .= $this->addServiceMethodCalls($inlineDef, $name, !$this->getProxyDumper()->isProxyCandidate($inlineDef) && $inlineDef->isShared() && !isset($this->singleUsePrivateIds[$id]) ? $id : null); $code .= $this->addServiceConfigurator($inlineDef, $name); } @@ -848,54 +1082,69 @@ EOTXT return $code; } - /** - * Adds multiple services. - * - * @return string - */ - private function addServices() + private function addServices(array &$services = null): string { $publicServices = $privateServices = ''; $definitions = $this->container->getDefinitions(); ksort($definitions); foreach ($definitions as $id => $definition) { - if ($definition->isSynthetic() || ($this->asFiles && $definition->isShared() && !$this->isHotPath($definition))) { + if (!$definition->isSynthetic()) { + $services[$id] = $this->addService($id, $definition); + } elseif ($definition->hasTag($this->hotPathTag) || !$definition->hasTag($this->preloadTags[1])) { + $services[$id] = null; + + foreach ($this->getClasses($definition, $id) as $class) { + $this->preload[$class] = $class; + } + } + } + + foreach ($definitions as $id => $definition) { + if (!([$file, $code] = $services[$id]) || null !== $file) { continue; } if ($definition->isPublic()) { - $publicServices .= $this->addService($id, $definition); - } else { - $privateServices .= $this->addService($id, $definition); + $publicServices .= $code; + } elseif (!$this->isTrivialInstance($definition) || isset($this->locatedIds[$id])) { + $privateServices .= $code; } } return $publicServices.$privateServices; } - private function generateServiceFiles() + private function generateServiceFiles(array $services): iterable { $definitions = $this->container->getDefinitions(); ksort($definitions); foreach ($definitions as $id => $definition) { - if (!$definition->isSynthetic() && $definition->isShared() && !$this->isHotPath($definition)) { - $code = $this->addService($id, $definition, $file); - yield $file => $code; + if (([$file, $code] = $services[$id]) && null !== $file && ($definition->isPublic() || !$this->isTrivialInstance($definition) || isset($this->locatedIds[$id]))) { + yield $file => [$code, $definition->hasTag($this->hotPathTag) || !$definition->hasTag($this->preloadTags[1]) && !$definition->isDeprecated() && !$definition->hasErrors()]; } } } - private function addNewInstance(Definition $definition, $return, $instantiation, $id) + private function addNewInstance(Definition $definition, string $return = '', string $id = null): string { - $class = $this->dumpValue($definition->getClass()); - $return = ' '.$return.$instantiation; + $tail = $return ? ";\n" : ''; + + if (BaseServiceLocator::class === $definition->getClass() && $definition->hasTag($this->serviceLocatorTag)) { + $arguments = []; + foreach ($definition->getArgument(0) as $k => $argument) { + $arguments[$k] = $argument->getValues()[0]; + } + + return $return.$this->dumpValue(new ServiceLocatorArgument($arguments)).$tail; + } $arguments = []; - foreach ($definition->getArguments() as $value) { - $arguments[] = $this->dumpValue($value); + foreach ($definition->getArguments() as $i => $value) { + $arguments[] = (\is_string($i) ? $i.': ' : '').$this->dumpValue($value); } if (null !== $definition->getFactory()) { $callable = $definition->getFactory(); + if (\is_array($callable)) { if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $callable[1])) { throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s).', $callable[1] ?: 'n/a')); @@ -903,48 +1152,38 @@ EOTXT if ($callable[0] instanceof Reference || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))) { - return $return.sprintf("%s->%s(%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : ''); + return $return.sprintf('%s->%s(%s)', $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; } $class = $this->dumpValue($callable[0]); - // If the class is a string we can optimize call_user_func away - if (0 === strpos($class, "'") && false === strpos($class, '$')) { + // If the class is a string we can optimize away + if (str_starts_with($class, "'") && !str_contains($class, '$')) { if ("''" === $class) { - throw new RuntimeException(sprintf('Cannot dump definition: The "%s" service is defined to be created by a factory but is missing the service reference, did you forget to define the factory service id or class?', $id)); + throw new RuntimeException(sprintf('Cannot dump definition: "%s" service is defined to be created by a factory but is missing the service reference, did you forget to define the factory service id or class?', $id ? 'The "'.$id.'"' : 'inline')); } - return $return.sprintf("%s::%s(%s);\n", $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : ''); + return $return.sprintf('%s::%s(%s)', $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; } - if (0 === strpos($class, 'new ')) { - return $return.sprintf("(%s)->%s(%s);\n", $class, $callable[1], $arguments ? implode(', ', $arguments) : ''); + if (str_starts_with($class, 'new ')) { + return $return.sprintf('(%s)->%s(%s)', $class, $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; } - return $return.sprintf("\\call_user_func([%s, '%s']%s);\n", $class, $callable[1], $arguments ? ', '.implode(', ', $arguments) : ''); + return $return.sprintf("[%s, '%s'](%s)", $class, $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; } - return $return.sprintf("%s(%s);\n", $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : ''); + return $return.sprintf('%s(%s)', $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : '').$tail; } - if (false !== strpos($class, '$')) { - return sprintf(" \$class = %s;\n\n%snew \$class(%s);\n", $class, $return, implode(', ', $arguments)); + if (null === $class = $definition->getClass()) { + throw new RuntimeException('Cannot dump definitions which have no class nor factory.'); } - return $return.sprintf("new %s(%s);\n", $this->dumpLiteralClass($class), implode(', ', $arguments)); + return $return.sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments)).$tail; } - /** - * Adds the class headers. - * - * @param string $class Class name - * @param string $baseClass The name of the base class - * @param string $baseClassWithNamespace Fully qualified base class name - * - * @return string - */ - private function startClass($class, $baseClass, $baseClassWithNamespace) + private function startClass(string $class, string $baseClass, bool $hasProxyClasses): string { - $bagClass = $this->container->isCompiled() ? 'use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;' : 'use Symfony\Component\DependencyInjection\ParameterBag\\ParameterBag;'; $namespaceLine = !$this->asFiles && $this->namespace ? "\nnamespace {$this->namespace};\n" : ''; $code = <<docStar} - * This class has been auto-generated - * by the Symfony Dependency Injection Component. - * - * @final since Symfony 3.3 + * @internal This class has been auto-generated by the Symfony Dependency Injection Component. */ class $class extends $baseClass { - private \$parameters = []; - private \$targetDirs = []; + protected \$parameters = []; public function __construct() { EOF; - if (null !== $this->targetDirRegex) { - $dir = $this->asFiles ? '$this->targetDirs[0] = \\dirname($containerDir)' : '__DIR__'; - $code .= <<targetDirMaxMatches}; ++\$i) { - \$this->targetDirs[\$i] = \$dir = \\dirname(\$dir); - } - -EOF; - } if ($this->asFiles) { - $code = str_replace('$parameters', "\$buildParameters;\n private \$containerDir;\n private \$parameters", $code); + $code = str_replace('$parameters = []', "\$containerDir;\n protected \$parameters = [];\n private \$buildParameters", $code); $code = str_replace('__construct()', '__construct(array $buildParameters = [], $containerDir = __DIR__)', $code); $code .= " \$this->buildParameters = \$buildParameters;\n"; $code .= " \$this->containerDir = \$containerDir;\n"; + + if (null !== $this->targetDirRegex) { + $code = str_replace('$parameters = []', "\$targetDir;\n protected \$parameters = []", $code); + $code .= ' $this->targetDir = \\dirname($containerDir);'."\n"; + } } - if ($this->container->isCompiled()) { - if (Container::class !== $baseClassWithNamespace) { - $r = $this->container->getReflectionClass($baseClassWithNamespace, false); - if (null !== $r - && (null !== $constructor = $r->getConstructor()) - && 0 === $constructor->getNumberOfRequiredParameters() - && Container::class !== $constructor->getDeclaringClass()->name - ) { - $code .= " parent::__construct();\n"; - $code .= " \$this->parameterBag = null;\n\n"; - } + if (Container::class !== $this->baseClass) { + $r = $this->container->getReflectionClass($this->baseClass, false); + if (null !== $r + && (null !== $constructor = $r->getConstructor()) + && 0 === $constructor->getNumberOfRequiredParameters() + && Container::class !== $constructor->getDeclaringClass()->name + ) { + $code .= " parent::__construct();\n"; + $code .= " \$this->parameterBag = null;\n\n"; } - - if ($this->container->getParameterBag()->all()) { - $code .= " \$this->parameters = \$this->getDefaultParameters();\n\n"; - } - - $code .= " \$this->services = [];\n"; - } else { - $arguments = $this->container->getParameterBag()->all() ? 'new ParameterBag($this->getDefaultParameters())' : null; - $code .= " parent::__construct($arguments);\n"; } - $code .= $this->addNormalizedIds(); + if ($this->container->getParameterBag()->all()) { + $code .= " \$this->parameters = \$this->getDefaultParameters();\n\n"; + } + $code .= " \$this->services = \$this->privates = [];\n"; + $code .= $this->addSyntheticIds(); $code .= $this->addMethodMap(); - $code .= $this->asFiles ? $this->addFileMap() : ''; - $code .= $this->addPrivateServices(); + $code .= $this->asFiles && !$this->inlineFactories ? $this->addFileMap() : ''; $code .= $this->addAliases(); - $code .= $this->addInlineRequires(); - $code .= <<<'EOF' + $code .= $this->addInlineRequires($hasProxyClasses); + $code .= <<addRemovedIds(); - if ($this->container->isCompiled()) { - $code .= <<asFiles && !$this->inlineFactories) { + $code .= <<<'EOF' - public function compile() + protected function load($file, $lazyLoad = true) { - throw new LogicException('You cannot compile a dumped container that was already compiled.'); - } - - public function isCompiled() - { - return true; - } - - public function isFrozen() - { - @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the isCompiled() method instead.', __METHOD__), E_USER_DEPRECATED); - - return true; - } - -EOF; + if (class_exists($class = __NAMESPACE__.'\\'.$file, false)) { + return $class::do($this, $lazyLoad); } - if ($this->asFiles) { - $code .= <<containerDir.\\DIRECTORY_SEPARATOR.\$file; + $service = require $this->containerDir.\DIRECTORY_SEPARATOR.$file; + + return class_exists($class, false) ? $class::do($this, $lazyLoad) : $service; } EOF; @@ -1065,16 +1287,13 @@ EOF; if (!$proxyDumper->isProxyCandidate($definition)) { continue; } - if ($this->asFiles) { - $proxyLoader = '$this->load("{$class}.php")'; - } elseif ($this->namespace) { - $proxyLoader = 'class_alias("'.$this->namespace.'\\\\{$class}", $class, false)'; + + if ($this->asFiles && !$this->inlineFactories) { + $proxyLoader = "class_exists(\$class, false) || require __DIR__.'/'.\$class.'.php';\n\n "; } else { $proxyLoader = ''; } - if ($proxyLoader) { - $proxyLoader = "class_exists(\$class, false) || {$proxyLoader};\n\n "; - } + $code .= <<container->getNormalizedIds(); - ksort($normalizedIds); - foreach ($normalizedIds as $id => $normalizedId) { - if ($this->container->has($normalizedId)) { - $code .= ' '.$this->doExport($id).' => '.$this->doExport($normalizedId).",\n"; - } - } - - return $code ? " \$this->normalizedIds = [\n".$code." ];\n" : ''; - } - - /** - * Adds the syntheticIds definition. - * - * @return string - */ - private function addSyntheticIds() + private function addSyntheticIds(): string { $code = ''; $definitions = $this->container->getDefinitions(); @@ -1127,14 +1322,15 @@ EOF; return $code ? " \$this->syntheticIds = [\n{$code} ];\n" : ''; } - /** - * Adds the removedIds definition. - * - * @return string - */ - private function addRemovedIds() + private function addRemovedIds(): string { - if (!$ids = $this->container->getRemovedIds()) { + $ids = $this->container->getRemovedIds(); + foreach ($this->container->getDefinitions() as $id => $definition) { + if (!$definition->isPublic()) { + $ids[$id] = true; + } + } + if (!$ids) { return ''; } if ($this->asFiles) { @@ -1144,7 +1340,7 @@ EOF; $ids = array_keys($ids); sort($ids); foreach ($ids as $id) { - if (preg_match('/^\d+_[^~]++~[._a-zA-Z\d]{7}$/', $id)) { + if (preg_match(FileLoader::ANONYMOUS_ID_REGEXP, $id)) { continue; } $code .= ' '.$this->doExport($id)." => true,\n"; @@ -1155,7 +1351,7 @@ EOF; return <<container->getDefinitions(); ksort($definitions); foreach ($definitions as $id => $definition) { - if (!$definition->isSynthetic() && (!$this->asFiles || !$definition->isShared() || $this->isHotPath($definition))) { + if (!$definition->isSynthetic() && $definition->isPublic() && (!$this->asFiles || $this->inlineFactories || $this->isHotPath($definition))) { $code .= ' '.$this->doExport($id).' => '.$this->doExport($this->generateMethodName($id)).",\n"; } } + $aliases = $this->container->getAliases(); + foreach ($aliases as $alias => $id) { + if (!$id->isDeprecated()) { + continue; + } + $code .= ' '.$this->doExport($alias).' => '.$this->doExport($this->generateMethodName($alias)).",\n"; + } + return $code ? " \$this->methodMap = [\n{$code} ];\n" : ''; } - /** - * Adds the fileMap property definition. - * - * @return string - */ - private function addFileMap() + private function addFileMap(): string { $code = ''; $definitions = $this->container->getDefinitions(); ksort($definitions); foreach ($definitions as $id => $definition) { - if (!$definition->isSynthetic() && $definition->isShared() && !$this->isHotPath($definition)) { - $code .= sprintf(" %s => '%s.php',\n", $this->doExport($id), $this->generateMethodName($id)); + if (!$definition->isSynthetic() && $definition->isPublic() && !$this->isHotPath($definition)) { + $code .= sprintf(" %s => '%s',\n", $this->doExport($id), $this->generateMethodName($id)); } } return $code ? " \$this->fileMap = [\n{$code} ];\n" : ''; } - /** - * Adds the privates property definition. - * - * @return string - */ - private function addPrivateServices() - { - $code = ''; - - $aliases = $this->container->getAliases(); - ksort($aliases); - foreach ($aliases as $id => $alias) { - if ($alias->isPrivate()) { - $code .= ' '.$this->doExport($id)." => true,\n"; - } - } - - $definitions = $this->container->getDefinitions(); - ksort($definitions); - foreach ($definitions as $id => $definition) { - if (!$definition->isPublic()) { - $code .= ' '.$this->doExport($id)." => true,\n"; - } - } - - if (empty($code)) { - return ''; - } - - $out = " \$this->privates = [\n"; - $out .= $code; - $out .= " ];\n"; - - return $out; - } - - /** - * Adds the aliases property definition. - * - * @return string - */ - private function addAliases() + private function addAliases(): string { if (!$aliases = $this->container->getAliases()) { - return $this->container->isCompiled() ? "\n \$this->aliases = [];\n" : ''; + return "\n \$this->aliases = [];\n"; } $code = " \$this->aliases = [\n"; ksort($aliases); foreach ($aliases as $alias => $id) { - $id = $this->container->normalizeId($id); + if ($id->isDeprecated()) { + continue; + } + + $id = (string) $id; while (isset($aliases[$id])) { - $id = $this->container->normalizeId($aliases[$id]); + $id = (string) $aliases[$id]; } $code .= ' '.$this->doExport($alias).' => '.$this->doExport($id).",\n"; } @@ -1261,20 +1418,58 @@ EOF; return $code." ];\n"; } - private function addInlineRequires() + private function addDeprecatedAliases(): string { - if (!$this->hotPathTag || !$this->inlineRequires) { - return ''; + $code = ''; + $aliases = $this->container->getAliases(); + foreach ($aliases as $alias => $definition) { + if (!$definition->isDeprecated()) { + continue; + } + $public = $definition->isPublic() ? 'public' : 'private'; + $id = (string) $definition; + $methodNameAlias = $this->generateMethodName($alias); + $idExported = $this->export($id); + $deprecation = $definition->getDeprecation($alias); + $packageExported = $this->export($deprecation['package']); + $versionExported = $this->export($deprecation['version']); + $messageExported = $this->export($deprecation['message']); + $code .= <<docStar} + * Gets the $public '$alias' alias. + * + * @return object The "$id" service. + */ + protected function {$methodNameAlias}() + { + trigger_deprecation($packageExported, $versionExported, $messageExported); + + return \$this->get($idExported); + } + +EOF; } - $lineage = []; + return $code; + } - foreach ($this->container->findTaggedServiceIds($this->hotPathTag) as $id => $tags) { + private function addInlineRequires(bool $hasProxyClasses): string + { + $lineage = []; + $hotPathServices = $this->hotPathTag && $this->inlineRequires ? $this->container->findTaggedServiceIds($this->hotPathTag) : []; + + foreach ($hotPathServices as $id => $tags) { $definition = $this->container->getDefinition($id); + + if ($this->getProxyDumper()->isProxyCandidate($definition)) { + continue; + } + $inlinedDefinitions = $this->getDefinitionsFromArguments([$definition]); foreach ($inlinedDefinitions as $def) { - if (\is_string($class = \is_array($factory = $def->getFactory()) && \is_string($factory[0]) ? $factory[0] : $def->getClass())) { + foreach ($this->getClasses($def, $id) as $class) { $this->collectLineage($class, $lineage); } } @@ -1289,15 +1484,14 @@ EOF; } } + if ($hasProxyClasses) { + $code .= "\n include_once __DIR__.'/proxy-classes.php';"; + } + return $code ? sprintf("\n \$this->privates['service_container'] = function () {%s\n };\n", $code) : ''; } - /** - * Adds default parameters method. - * - * @return string - */ - private function addDefaultParametersMethod() + private function addDefaultParametersMethod(): string { if (!$this->container->getParameterBag()->all()) { return ''; @@ -1305,43 +1499,36 @@ EOF; $php = []; $dynamicPhp = []; - $normalizedParams = []; foreach ($this->container->getParameterBag()->all() as $key => $value) { if ($key !== $resolvedKey = $this->container->resolveEnvPlaceholders($key)) { throw new InvalidArgumentException(sprintf('Parameter name cannot use env parameters: "%s".', $resolvedKey)); } - if ($key !== $lcKey = strtolower($key)) { - $normalizedParams[] = sprintf(' %s => %s,', $this->export($lcKey), $this->export($key)); - } - $export = $this->exportParameters([$value]); + $hasEnum = false; + $export = $this->exportParameters([$value], '', 12, $hasEnum); $export = explode('0 => ', substr(rtrim($export, " ]\n"), 2, -1), 2); - if (preg_match("/\\\$this->(?:getEnv\('(?:\w++:)*+\w++'\)|targetDirs\[\d++\])/", $export[1])) { + if ($hasEnum || preg_match("/\\\$this->(?:getEnv\('(?:[-.\w]*+:)*+\w++'\)|targetDir\.'')/", $export[1])) { $dynamicPhp[$key] = sprintf('%scase %s: $value = %s; break;', $export[0], $this->export($key), $export[1]); } else { $php[] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]); } } - $parameters = sprintf("[\n%s\n%s]", implode("\n", $php), str_repeat(' ', 8)); - $code = ''; - if ($this->container->isCompiled()) { - $code .= <<<'EOF' + $code = <<<'EOF' - public function getParameter($name) + /** + * @return array|bool|float|int|string|\UnitEnum|null + */ + public function getParameter(string $name) { - $name = (string) $name; if (isset($this->buildParameters[$name])) { return $this->buildParameters[$name]; } - if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters))) { - $name = $this->normalizeParameterName($name); - if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters))) { - throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name)); - } + if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters))) { + throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name)); } if (isset($this->loadedDynamicParameters[$name])) { return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); @@ -1350,23 +1537,21 @@ EOF; return $this->parameters[$name]; } - public function hasParameter($name) + public function hasParameter(string $name): bool { - $name = (string) $name; if (isset($this->buildParameters[$name])) { return true; } - $name = $this->normalizeParameterName($name); - return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters); + return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters); } - public function setParameter($name, $value) + public function setParameter(string $name, $value): void { throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); } - public function getParameterBag() + public function getParameterBag(): ParameterBagInterface { if (null === $this->parameterBag) { $parameters = $this->parameters; @@ -1383,13 +1568,13 @@ EOF; } EOF; - if (!$this->asFiles) { - $code = preg_replace('/^.*buildParameters.*\n.*\n.*\n/m', '', $code); - } + if (!$this->asFiles) { + $code = preg_replace('/^.*buildParameters.*\n.*\n.*\n\n?/m', '', $code); + } - if ($dynamicPhp) { - $loadedDynamicParameters = $this->exportParameters(array_combine(array_keys($dynamicPhp), array_fill(0, \count($dynamicPhp), false)), '', 8); - $getDynamicParameter = <<<'EOF' + if ($dynamicPhp) { + $loadedDynamicParameters = $this->exportParameters(array_combine(array_keys($dynamicPhp), array_fill(0, \count($dynamicPhp), false)), '', 8); + $getDynamicParameter = <<<'EOF' switch ($name) { %s default: throw new InvalidArgumentException(sprintf('The dynamic parameter "%%s" must be defined.', $name)); @@ -1398,64 +1583,23 @@ EOF; return $this->dynamicParameters[$name] = $value; EOF; - $getDynamicParameter = sprintf($getDynamicParameter, implode("\n", $dynamicPhp)); - } else { - $loadedDynamicParameters = '[]'; - $getDynamicParameter = str_repeat(' ', 8).'throw new InvalidArgumentException(sprintf(\'The dynamic parameter "%s" must be defined.\', $name));'; - } - - $code .= <<docStar} - * Computes a dynamic parameter. - * - * @param string \$name The name of the dynamic parameter to load - * - * @return mixed The value of the dynamic parameter - * - * @throws InvalidArgumentException When the dynamic parameter does not exist - */ - private function getDynamicParameter(\$name) - { -{$getDynamicParameter} - } - - -EOF; - - $code .= ' private $normalizedParameterNames = '.($normalizedParams ? sprintf("[\n%s\n ];", implode("\n", $normalizedParams)) : '[];')."\n"; - $code .= <<<'EOF' - - private function normalizeParameterName($name) - { - if (isset($this->normalizedParameterNames[$normalizedName = strtolower($name)]) || isset($this->parameters[$normalizedName]) || array_key_exists($normalizedName, $this->parameters)) { - $normalizedName = isset($this->normalizedParameterNames[$normalizedName]) ? $this->normalizedParameterNames[$normalizedName] : $normalizedName; - if ((string) $name !== $normalizedName) { - @trigger_error(sprintf('Parameter names will be made case sensitive in Symfony 4.0. Using "%s" instead of "%s" is deprecated since Symfony 3.4.', $name, $normalizedName), E_USER_DEPRECATED); - } + $getDynamicParameter = sprintf($getDynamicParameter, implode("\n", $dynamicPhp)); } else { - $normalizedName = $this->normalizedParameterNames[$normalizedName] = (string) $name; - } - - return $normalizedName; - } - -EOF; - } elseif ($dynamicPhp) { - throw new RuntimeException('You cannot dump a not-frozen container with dynamic parameters.'); + $loadedDynamicParameters = '[]'; + $getDynamicParameter = str_repeat(' ', 8).'throw new InvalidArgumentException(sprintf(\'The dynamic parameter "%s" must be defined.\', $name));'; } $code .= <<docStar} - * Gets the default parameters. - * - * @return array An array of the default parameters - */ - protected function getDefaultParameters() + private \$loadedDynamicParameters = {$loadedDynamicParameters}; + private \$dynamicParameters = []; + + private function getDynamicParameter(string \$name) + { +{$getDynamicParameter} + } + + protected function getDefaultParameters(): array { return $parameters; } @@ -1466,23 +1610,16 @@ EOF; } /** - * Exports parameters. - * - * @param string $path - * @param int $indent - * - * @return string - * * @throws InvalidArgumentException */ - private function exportParameters(array $parameters, $path = '', $indent = 12) + private function exportParameters(array $parameters, string $path = '', int $indent = 12, bool &$hasEnum = false): string { $php = []; foreach ($parameters as $key => $value) { if (\is_array($value)) { - $value = $this->exportParameters($value, $path.'/'.$key, $indent + 4); + $value = $this->exportParameters($value, $path.'/'.$key, $indent + 4, $hasEnum); } elseif ($value instanceof ArgumentInterface) { - throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain special arguments. "%s" found in "%s".', \get_class($value), $path.'/'.$key)); + throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain special arguments. "%s" found in "%s".', get_debug_type($value), $path.'/'.$key)); } elseif ($value instanceof Variable) { throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain variable references. Variable "%s" found in "%s".', $value, $path.'/'.$key)); } elseif ($value instanceof Definition) { @@ -1491,6 +1628,9 @@ EOF; throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain references to other services (reference to service "%s" found in "%s").', $value, $path.'/'.$key)); } elseif ($value instanceof Expression) { throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain expressions. Expression "%s" found in "%s".', $value, $path.'/'.$key)); + } elseif ($value instanceof \UnitEnum) { + $hasEnum = true; + $value = sprintf('\%s::%s', \get_class($value), $value->name); } else { $value = $this->export($value); } @@ -1501,28 +1641,27 @@ EOF; return sprintf("[\n%s\n%s]", implode("\n", $php), str_repeat(' ', $indent - 4)); } - /** - * Ends the class definition. - * - * @return string - */ - private function endClass() + private function endClass(): string { + if ($this->addThrow) { + return <<<'EOF' + + protected function throw($message) + { + throw new RuntimeException($message); + } +} + +EOF; + } + return <<<'EOF' } EOF; } - /** - * Wraps the service conditionals. - * - * @param string $value - * @param string $code - * - * @return string - */ - private function wrapServiceConditionals($value, $code) + private function wrapServiceConditionals($value, string $code): string { if (!$condition = $this->getServiceConditionals($value)) { return $code; @@ -1534,21 +1673,14 @@ EOF; return sprintf(" if (%s) {\n%s }\n", $condition, $code); } - /** - * Get the conditions to execute for conditional services. - * - * @param string $value - * - * @return string|null - */ - private function getServiceConditionals($value) + private function getServiceConditionals($value): string { $conditions = []; foreach (ContainerBuilder::getInitializedConditionals($value) as $service) { if (!$this->container->hasDefinition($service)) { return 'false'; } - $conditions[] = sprintf('isset($this->services[%s])', $this->doExport($service)); + $conditions[] = sprintf('isset($this->%s[%s])', $this->container->getDefinition($service)->isPublic() ? 'services' : 'privates', $this->doExport($service)); } foreach (ContainerBuilder::getServiceConditionals($value) as $service) { if ($this->container->hasDefinition($service) && !$this->container->getDefinition($service)->isPublic()) { @@ -1565,7 +1697,7 @@ EOF; return implode(' && ', $conditions); } - private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage $definitions = null, array &$calls = [], $byConstructor = null) + private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage $definitions = null, array &$calls = [], bool $byConstructor = null): \SplObjectStorage { if (null === $definitions) { $definitions = new \SplObjectStorage(); @@ -1575,7 +1707,7 @@ EOF; if (\is_array($argument)) { $this->getDefinitionsFromArguments($argument, $definitions, $calls, $byConstructor); } elseif ($argument instanceof Reference) { - $id = $this->container->normalizeId($argument); + $id = (string) $argument; while ($this->container->hasAlias($id)) { $id = (string) $this->container->getAlias($id); @@ -1605,16 +1737,9 @@ EOF; } /** - * Dumps values. - * - * @param mixed $value - * @param bool $interpolate - * - * @return string - * * @throws RuntimeException */ - private function dumpValue($value, $interpolate = true) + private function dumpValue($value, bool $interpolate = true): string { if (\is_array($value)) { if ($value && $interpolate && false !== $param = array_search($value, $this->container->getParameterBag()->all(), true)) { @@ -1635,13 +1760,14 @@ EOF; $value = $value->getValues()[0]; $code = $this->dumpValue($value, $interpolate); + $returnedType = ''; if ($value instanceof TypedReference) { - $code = sprintf('$f = function (\\%s $v%s) { return $v; }; return $f(%s);', $value->getType(), ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $value->getInvalidBehavior() ? ' = null' : '', $code); - } else { - $code = sprintf('return %s;', $code); + $returnedType = sprintf(': %s\%s', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $value->getInvalidBehavior() ? '' : '?', str_replace(['|', '&'], ['|\\', '&\\'], $value->getType())); } - return sprintf("function () {\n %s\n }", $code); + $code = sprintf('return %s;', $code); + + return sprintf("function ()%s {\n %s\n }", $returnedType, $code); } if ($value instanceof IteratorArgument) { @@ -1673,10 +1799,43 @@ EOF; return implode("\n", $code); } + + if ($value instanceof ServiceLocatorArgument) { + $serviceMap = ''; + $serviceTypes = ''; + foreach ($value->getValues() as $k => $v) { + if (!$v) { + continue; + } + $id = (string) $v; + while ($this->container->hasAlias($id)) { + $id = (string) $this->container->getAlias($id); + } + $definition = $this->container->getDefinition($id); + $load = !($definition->hasErrors() && $e = $definition->getErrors()) ? $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) : reset($e); + $serviceMap .= sprintf("\n %s => [%s, %s, %s, %s],", + $this->export($k), + $this->export($definition->isShared() ? ($definition->isPublic() ? 'services' : 'privates') : false), + $this->doExport($id), + $this->export(ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $v->getInvalidBehavior() && !\is_string($load) ? $this->generateMethodName($id) : null), + $this->export($load) + ); + $serviceTypes .= sprintf("\n %s => %s,", $this->export($k), $this->export($v instanceof TypedReference ? $v->getType() : '?')); + $this->locatedIds[$id] = true; + } + $this->addGetService = true; + + return sprintf('new \%s($this->getService, [%s%s], [%s%s])', ServiceLocator::class, $serviceMap, $serviceMap ? "\n " : '', $serviceTypes, $serviceTypes ? "\n " : ''); + } } finally { - list($this->definitionVariables, $this->referenceVariables) = $scope; + [$this->definitionVariables, $this->referenceVariables] = $scope; } } elseif ($value instanceof Definition) { + if ($value->hasErrors() && $e = $value->getErrors()) { + $this->addThrow = true; + + return sprintf('$this->throw(%s)', $this->export(reset($e))); + } if (null !== $this->definitionVariables && $this->definitionVariables->contains($value)) { return $this->dumpValue($this->definitionVariables[$value], $interpolate); } @@ -1690,54 +1849,11 @@ EOF; throw new RuntimeException('Cannot dump definitions which have a configurator.'); } - $arguments = []; - foreach ($value->getArguments() as $argument) { - $arguments[] = $this->dumpValue($argument); - } - - if (null !== $value->getFactory()) { - $factory = $value->getFactory(); - - if (\is_string($factory)) { - return sprintf('%s(%s)', $this->dumpLiteralClass($this->dumpValue($factory)), implode(', ', $arguments)); - } - - if (\is_array($factory)) { - if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $factory[1])) { - throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s).', $factory[1] ?: 'n/a')); - } - - $class = $this->dumpValue($factory[0]); - if (\is_string($factory[0])) { - return sprintf('%s::%s(%s)', $this->dumpLiteralClass($class), $factory[1], implode(', ', $arguments)); - } - - if ($factory[0] instanceof Definition) { - if (0 === strpos($class, 'new ')) { - return sprintf('(%s)->%s(%s)', $class, $factory[1], implode(', ', $arguments)); - } - - return sprintf("\\call_user_func([%s, '%s']%s)", $class, $factory[1], \count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); - } - - if ($factory[0] instanceof Reference) { - return sprintf('%s->%s(%s)', $class, $factory[1], implode(', ', $arguments)); - } - } - - throw new RuntimeException('Cannot dump definition because of invalid factory.'); - } - - $class = $value->getClass(); - if (null === $class) { - throw new RuntimeException('Cannot dump definitions which have no class nor factory.'); - } - - return sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments)); + return $this->addNewInstance($value); } elseif ($value instanceof Variable) { return '$'.$value; } elseif ($value instanceof Reference) { - $id = $this->container->normalizeId($value); + $id = (string) $value; while ($this->container->hasAlias($id)) { $id = (string) $this->container->getAlias($id); @@ -1766,6 +1882,10 @@ EOF; return $code; } + } elseif ($value instanceof \UnitEnum) { + return sprintf('\%s::%s', \get_class($value), $value->name); + } elseif ($value instanceof AbstractArgument) { + throw new RuntimeException($value->getTextWithContext()); } elseif (\is_object($value) || \is_resource($value)) { throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.'); } @@ -1776,38 +1896,25 @@ EOF; /** * Dumps a string to a literal (aka PHP Code) class value. * - * @param string $class - * - * @return string - * * @throws RuntimeException */ - private function dumpLiteralClass($class) + private function dumpLiteralClass(string $class): string { - if (false !== strpos($class, '$')) { + if (str_contains($class, '$')) { return sprintf('${($_ = %s) && false ?: "_"}', $class); } - if (0 !== strpos($class, "'") || !preg_match('/^\'(?:\\\{2})?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) { + if (!str_starts_with($class, "'") || !preg_match('/^\'(?:\\\{2})?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) { throw new RuntimeException(sprintf('Cannot dump definition because of invalid class name (%s).', $class ?: 'n/a')); } $class = substr(str_replace('\\\\', '\\', $class), 1, -1); - return 0 === strpos($class, '\\') ? $class : '\\'.$class; + return str_starts_with($class, '\\') ? $class : '\\'.$class; } - /** - * Dumps a parameter. - * - * @param string $name - * - * @return string - */ - private function dumpParameter($name) + private function dumpParameter(string $name): string { - $name = (string) $name; - - if ($this->container->isCompiled() && $this->container->hasParameter($name)) { + if ($this->container->hasParameter($name)) { $value = $this->container->getParameter($name); $dumpedValue = $this->dumpValue($value, false); @@ -1815,7 +1922,7 @@ EOF; return $dumpedValue; } - if (!preg_match("/\\\$this->(?:getEnv\('(?:\w++:)*+\w++'\)|targetDirs\[\d++\])/", $dumpedValue)) { + if (!preg_match("/\\\$this->(?:getEnv\('(?:[-.\w]*+:)*+\w++'\)|targetDir\.'')/", $dumpedValue)) { return sprintf('$this->parameters[%s]', $this->doExport($name)); } } @@ -1823,20 +1930,11 @@ EOF; return sprintf('$this->getParameter(%s)', $this->doExport($name)); } - /** - * Gets a service call. - * - * @param string $id - * @param Reference $reference - * - * @return string - */ - private function getServiceCall($id, Reference $reference = null) + private function getServiceCall(string $id, Reference $reference = null): string { while ($this->container->hasAlias($id)) { $id = (string) $this->container->getAlias($id); } - $id = $this->container->normalizeId($id); if ('service_container' === $id) { return '$this'; @@ -1851,35 +1949,47 @@ EOF; return $code; } } elseif ($this->isTrivialInstance($definition)) { - $code = substr($this->addNewInstance($definition, '', '', $id), 8, -2); - if ($definition->isShared()) { - $code = sprintf('$this->services[%s] = %s', $this->doExport($id), $code); + if ($definition->hasErrors() && $e = $definition->getErrors()) { + $this->addThrow = true; + + return sprintf('$this->throw(%s)', $this->export(reset($e))); + } + $code = $this->addNewInstance($definition, '', $id); + if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) { + $code = sprintf('$this->%s[%s] = %s', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code); } $code = "($code)"; - } elseif ($this->asFiles && $definition->isShared() && !$this->isHotPath($definition)) { - $code = sprintf("\$this->load('%s.php')", $this->generateMethodName($id)); } else { - $code = sprintf('$this->%s()', $this->generateMethodName($id)); + $code = $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) ? "\$this->load('%s')" : '$this->%s()'; + $code = sprintf($code, $this->generateMethodName($id)); + + if (!$definition->isShared()) { + $factory = sprintf('$this->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id)); + $code = sprintf('(isset(%s) ? %1$s() : %s)', $factory, $code); + } } - } elseif (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) { + if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) { + $code = sprintf('($this->%s[%s] ?? %s)', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code); + } + + return $code; + } + if (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) { return 'null'; - } elseif (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) { + } + if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $reference->getInvalidBehavior()) { $code = sprintf('$this->get(%s, /* ContainerInterface::NULL_ON_INVALID_REFERENCE */ %d)', $this->doExport($id), ContainerInterface::NULL_ON_INVALID_REFERENCE); } else { $code = sprintf('$this->get(%s)', $this->doExport($id)); } - // The following is PHP 5.5 syntax for what could be written as "(\$this->services['$id'] ?? $code)" on PHP>=7.0 - - return sprintf("\${(\$_ = isset(\$this->services[%s]) ? \$this->services[%1\$s] : %s) && false ?: '_'}", $this->doExport($id), $code); + return sprintf('($this->services[%s] ?? %s)', $this->doExport($id), $code); } /** * Initializes the method names map to avoid conflicts with the Container methods. - * - * @param string $class the container base class */ - private function initializeMethodNamesMap($class) + private function initializeMethodNamesMap(string $class) { $this->serviceIdToMethodNameMap = []; $this->usedMethodNames = []; @@ -1892,15 +2002,9 @@ EOF; } /** - * Convert a service id to a valid PHP method name. - * - * @param string $id - * - * @return string - * * @throws InvalidArgumentException */ - private function generateMethodName($id) + private function generateMethodName(string $id): string { if (isset($this->serviceIdToMethodNameMap[$id])) { return $this->serviceIdToMethodNameMap[$id]; @@ -1923,12 +2027,7 @@ EOF; return $methodName; } - /** - * Returns the next name to use. - * - * @return string - */ - private function getNextVariableName() + private function getNextVariableName(): string { $firstChars = self::FIRST_CHARS; $firstCharsLength = \strlen($firstChars); @@ -1961,11 +2060,11 @@ EOF; } } - private function getExpressionLanguage() + private function getExpressionLanguage(): ExpressionLanguage { if (null === $this->expressionLanguage) { - if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { - throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + if (!class_exists(\Symfony\Component\ExpressionLanguage\ExpressionLanguage::class)) { + throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); } $providers = $this->container->getExpressionLanguageProviders(); $this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) { @@ -1988,23 +2087,55 @@ EOF; return $this->expressionLanguage; } - private function isHotPath(Definition $definition) + private function isHotPath(Definition $definition): bool { return $this->hotPathTag && $definition->hasTag($this->hotPathTag) && !$definition->isDeprecated(); } + private function isSingleUsePrivateNode(ServiceReferenceGraphNode $node): bool + { + if ($node->getValue()->isPublic()) { + return false; + } + $ids = []; + foreach ($node->getInEdges() as $edge) { + if (!$value = $edge->getSourceNode()->getValue()) { + continue; + } + if ($edge->isLazy() || !$value instanceof Definition || !$value->isShared()) { + return false; + } + $ids[$edge->getSourceNode()->getId()] = true; + } + + return 1 === \count($ids); + } + + /** + * @return mixed + */ private function export($value) { if (null !== $this->targetDirRegex && \is_string($value) && preg_match($this->targetDirRegex, $value, $matches, \PREG_OFFSET_CAPTURE)) { $suffix = $matches[0][1] + \strlen($matches[0][0]); $matches[0][1] += \strlen($matches[1][0]); $prefix = $matches[0][1] ? $this->doExport(substr($value, 0, $matches[0][1]), true).'.' : ''; - $suffix = isset($value[$suffix]) ? '.'.$this->doExport(substr($value, $suffix), true) : ''; + + if ('\\' === \DIRECTORY_SEPARATOR && isset($value[$suffix])) { + $cookie = '\\'.random_int(100000, \PHP_INT_MAX); + $suffix = '.'.$this->doExport(str_replace('\\', $cookie, substr($value, $suffix)), true); + $suffix = str_replace('\\'.$cookie, "'.\\DIRECTORY_SEPARATOR.'", $suffix); + } else { + $suffix = isset($value[$suffix]) ? '.'.$this->doExport(substr($value, $suffix), true) : ''; + } + $dirname = $this->asFiles ? '$this->containerDir' : '__DIR__'; $offset = 2 + $this->targetDirMaxMatches - \count($matches); - if ($this->asFiles || 0 < $offset) { - $dirname = sprintf('$this->targetDirs[%d]', $offset); + if (0 < $offset) { + $dirname = sprintf('\dirname(__DIR__, %d)', $offset + (int) $this->asFiles); + } elseif ($this->asFiles) { + $dirname = "\$this->targetDir.''"; // empty string concatenation on purpose } if ($prefix || $suffix) { @@ -2017,19 +2148,34 @@ EOF; return $this->doExport($value, true); } - private function doExport($value, $resolveEnv = false) + /** + * @return mixed + */ + private function doExport($value, bool $resolveEnv = false) { - if (\is_string($value) && false !== strpos($value, "\n")) { + $shouldCacheValue = $resolveEnv && \is_string($value); + if ($shouldCacheValue && isset($this->exportedVariables[$value])) { + return $this->exportedVariables[$value]; + } + if (\is_string($value) && str_contains($value, "\n")) { $cleanParts = explode("\n", $value); $cleanParts = array_map(function ($part) { return var_export($part, true); }, $cleanParts); $export = implode('."\n".', $cleanParts); } else { $export = var_export($value, true); } + if ($this->asFiles) { + if (false !== strpos($export, '$this')) { + $export = str_replace('$this', "$'.'this", $export); + } + if (false !== strpos($export, 'function () {')) { + $export = str_replace('function () {', "function ('.') {", $export); + } + } if ($resolveEnv && "'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$this->getEnv('string:%s').'")) { $export = $resolvedExport; - if (".''" === substr($export, -3)) { + if (str_ends_with($export, ".''")) { $export = substr($export, 0, -3); if ("'" === $export[1]) { $export = substr_replace($export, '', 18, 7); @@ -2040,6 +2186,76 @@ EOF; } } + if ($shouldCacheValue) { + $this->exportedVariables[$value] = $export; + } + return $export; } + + private function getAutoloadFile(): ?string + { + $file = null; + + foreach (spl_autoload_functions() as $autoloader) { + if (!\is_array($autoloader)) { + continue; + } + + if ($autoloader[0] instanceof DebugClassLoader || $autoloader[0] instanceof LegacyDebugClassLoader) { + $autoloader = $autoloader[0]->getClassLoader(); + } + + if (!\is_array($autoloader) || !$autoloader[0] instanceof ClassLoader || !$autoloader[0]->findFile(__CLASS__)) { + continue; + } + + foreach (get_declared_classes() as $class) { + if (str_starts_with($class, 'ComposerAutoloaderInit') && $class::getLoader() === $autoloader[0]) { + $file = \dirname((new \ReflectionClass($class))->getFileName(), 2).'/autoload.php'; + + if (null !== $this->targetDirRegex && preg_match($this->targetDirRegex.'A', $file)) { + return $file; + } + } + } + } + + return $file; + } + + private function getClasses(Definition $definition, string $id): array + { + $classes = []; + + while ($definition instanceof Definition) { + foreach ($definition->getTag($this->preloadTags[0]) as $tag) { + if (!isset($tag['class'])) { + throw new InvalidArgumentException(sprintf('Missing attribute "class" on tag "%s" for service "%s".', $this->preloadTags[0], $id)); + } + + $classes[] = trim($tag['class'], '\\'); + } + + if ($class = $definition->getClass()) { + $classes[] = trim($class, '\\'); + } + $factory = $definition->getFactory(); + + if (!\is_array($factory)) { + $factory = [$factory]; + } + + if (\is_string($factory[0])) { + if (false !== $i = strrpos($factory[0], '::')) { + $factory[0] = substr($factory[0], 0, $i); + } + $classes[] = trim($factory[0], '\\'); + } + + $definition = $factory[0]; + } + + return $classes; + } } diff --git a/lib/symfony/dependency-injection/Dumper/Preloader.php b/lib/symfony/dependency-injection/Dumper/Preloader.php new file mode 100644 index 0000000000..c61b08ebc2 --- /dev/null +++ b/lib/symfony/dependency-injection/Dumper/Preloader.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Dumper; + +/** + * @author Nicolas Grekas + */ +final class Preloader +{ + public static function append(string $file, array $list): void + { + if (!file_exists($file)) { + throw new \LogicException(sprintf('File "%s" does not exist.', $file)); + } + + $cacheDir = \dirname($file); + $classes = []; + + foreach ($list as $item) { + if (0 === strpos($item, $cacheDir)) { + file_put_contents($file, sprintf("require_once __DIR__.%s;\n", var_export(strtr(substr($item, \strlen($cacheDir)), \DIRECTORY_SEPARATOR, '/'), true)), \FILE_APPEND); + continue; + } + + $classes[] = sprintf("\$classes[] = %s;\n", var_export($item, true)); + } + + file_put_contents($file, sprintf("\n\$classes = [];\n%s\$preloaded = Preloader::preload(\$classes, \$preloaded);\n", implode('', $classes)), \FILE_APPEND); + } + + public static function preload(array $classes, array $preloaded = []): array + { + set_error_handler(function ($t, $m, $f, $l) { + if (error_reporting() & $t) { + if (__FILE__ !== $f) { + throw new \ErrorException($m, 0, $t, $f, $l); + } + + throw new \ReflectionException($m); + } + }); + + $prev = []; + + try { + while ($prev !== $classes) { + $prev = $classes; + foreach ($classes as $c) { + if (!isset($preloaded[$c])) { + self::doPreload($c, $preloaded); + } + } + $classes = array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits()); + } + } finally { + restore_error_handler(); + } + + return $preloaded; + } + + private static function doPreload(string $class, array &$preloaded): void + { + if (isset($preloaded[$class]) || \in_array($class, ['self', 'static', 'parent'], true)) { + return; + } + + $preloaded[$class] = true; + + try { + if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { + return; + } + + $r = new \ReflectionClass($class); + + if ($r->isInternal()) { + return; + } + + $r->getConstants(); + $r->getDefaultProperties(); + + if (\PHP_VERSION_ID >= 70400) { + foreach ($r->getProperties(\ReflectionProperty::IS_PUBLIC) as $p) { + self::preloadType($p->getType(), $preloaded); + } + } + + foreach ($r->getMethods(\ReflectionMethod::IS_PUBLIC) as $m) { + foreach ($m->getParameters() as $p) { + if ($p->isDefaultValueAvailable() && $p->isDefaultValueConstant()) { + $c = $p->getDefaultValueConstantName(); + + if ($i = strpos($c, '::')) { + self::doPreload(substr($c, 0, $i), $preloaded); + } + } + + self::preloadType($p->getType(), $preloaded); + } + + self::preloadType($m->getReturnType(), $preloaded); + } + } catch (\Throwable $e) { + // ignore missing classes + } + } + + private static function preloadType(?\ReflectionType $t, array &$preloaded): void + { + if (!$t) { + return; + } + + foreach (($t instanceof \ReflectionUnionType || $t instanceof \ReflectionIntersectionType) ? $t->getTypes() : [$t] as $t) { + if (!$t->isBuiltin()) { + self::doPreload($t instanceof \ReflectionNamedType ? $t->getName() : $t, $preloaded); + } + } + } +} diff --git a/lib/symfony/dependency-injection/Dumper/XmlDumper.php b/lib/symfony/dependency-injection/Dumper/XmlDumper.php index eff421ec4e..4f7b16d5f1 100644 --- a/lib/symfony/dependency-injection/Dumper/XmlDumper.php +++ b/lib/symfony/dependency-injection/Dumper/XmlDumper.php @@ -12,8 +12,10 @@ namespace Symfony\Component\DependencyInjection\Dumper; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; @@ -38,7 +40,7 @@ class XmlDumper extends Dumper /** * Dumps the service container as an XML string. * - * @return string An xml string representing of the service container + * @return string */ public function dump(array $options = []) { @@ -83,17 +85,14 @@ class XmlDumper extends Dumper if (\count($methodcall[1])) { $this->convertParameters($methodcall[1], 'argument', $call); } + if ($methodcall[2] ?? false) { + $call->setAttribute('returns-clone', 'true'); + } $parent->appendChild($call); } } - /** - * Adds a service. - * - * @param Definition $definition - * @param string $id - */ - private function addService($definition, $id, \DOMElement $parent) + private function addService(Definition $definition, ?string $id, \DOMElement $parent) { $service = $this->document->createElement('service'); if (null !== $id) { @@ -109,8 +108,8 @@ class XmlDumper extends Dumper if (!$definition->isShared()) { $service->setAttribute('shared', 'false'); } - if (!$definition->isPrivate()) { - $service->setAttribute('public', $definition->isPublic() ? 'true' : 'false'); + if ($definition->isPublic()) { + $service->setAttribute('public', 'true'); } if ($definition->isSynthetic()) { $service->setAttribute('synthetic', 'true'); @@ -118,9 +117,15 @@ class XmlDumper extends Dumper if ($definition->isLazy()) { $service->setAttribute('lazy', 'true'); } - if (null !== $decorated = $definition->getDecoratedService()) { - list($decorated, $renamedId, $priority) = $decorated; + if (null !== $decoratedService = $definition->getDecoratedService()) { + [$decorated, $renamedId, $priority] = $decoratedService; $service->setAttribute('decorates', $decorated); + + $decorationOnInvalid = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + if (\in_array($decorationOnInvalid, [ContainerInterface::IGNORE_ON_INVALID_REFERENCE, ContainerInterface::NULL_ON_INVALID_REFERENCE], true)) { + $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE === $decorationOnInvalid ? 'null' : 'ignore'; + $service->setAttribute('decoration-on-invalid', $invalidBehavior); + } if (null !== $renamedId) { $service->setAttribute('decoration-inner-name', $renamedId); } @@ -132,9 +137,13 @@ class XmlDumper extends Dumper foreach ($definition->getTags() as $name => $tags) { foreach ($tags as $attributes) { $tag = $this->document->createElement('tag'); - $tag->setAttribute('name', $name); + if (!\array_key_exists('name', $attributes)) { + $tag->setAttribute('name', $name); + } else { + $tag->appendChild($this->document->createTextNode($name)); + } foreach ($attributes as $key => $value) { - $tag->setAttribute($key, $value); + $tag->setAttribute($key, $value ?? ''); } $service->appendChild($tag); } @@ -174,8 +183,11 @@ class XmlDumper extends Dumper } if ($definition->isDeprecated()) { + $deprecation = $definition->getDeprecation('%service_id%'); $deprecated = $this->document->createElement('deprecated'); - $deprecated->appendChild($this->document->createTextNode($definition->getDeprecationMessage('%service_id%'))); + $deprecated->appendChild($this->document->createTextNode($definition->getDeprecation('%service_id%')['message'])); + $deprecated->setAttribute('package', $deprecation['package']); + $deprecated->setAttribute('version', $deprecation['version']); $service->appendChild($deprecated); } @@ -184,13 +196,6 @@ class XmlDumper extends Dumper $service->setAttribute('autowire', 'true'); } - foreach ($definition->getAutowiringTypes(false) as $autowiringTypeValue) { - $autowiringType = $this->document->createElement('autowiring-type'); - $autowiringType->appendChild($this->document->createTextNode($autowiringTypeValue)); - - $service->appendChild($autowiringType); - } - if ($definition->isAutoconfigured()) { $service->setAttribute('autoconfigure', 'true'); } @@ -217,19 +222,25 @@ class XmlDumper extends Dumper $parent->appendChild($service); } - /** - * Adds a service alias. - * - * @param string $alias - */ - private function addServiceAlias($alias, Alias $id, \DOMElement $parent) + private function addServiceAlias(string $alias, Alias $id, \DOMElement $parent) { $service = $this->document->createElement('service'); $service->setAttribute('id', $alias); $service->setAttribute('alias', $id); - if (!$id->isPrivate()) { - $service->setAttribute('public', $id->isPublic() ? 'true' : 'false'); + if ($id->isPublic()) { + $service->setAttribute('public', 'true'); } + + if ($id->isDeprecated()) { + $deprecation = $id->getDeprecation('%alias_id%'); + $deprecated = $this->document->createElement('deprecated'); + $deprecated->appendChild($this->document->createTextNode($deprecation['message'])); + $deprecated->setAttribute('package', $deprecation['package']); + $deprecated->setAttribute('version', $deprecation['version']); + + $service->appendChild($deprecated); + } + $parent->appendChild($service); } @@ -255,35 +266,44 @@ class XmlDumper extends Dumper $parent->appendChild($services); } - /** - * Converts parameters. - * - * @param string $type - * @param string $keyAttribute - */ - private function convertParameters(array $parameters, $type, \DOMElement $parent, $keyAttribute = 'key') + private function convertParameters(array $parameters, string $type, \DOMElement $parent, string $keyAttribute = 'key') { - $withKeys = array_keys($parameters) !== range(0, \count($parameters) - 1); + $withKeys = !array_is_list($parameters); foreach ($parameters as $key => $value) { $element = $this->document->createElement($type); if ($withKeys) { $element->setAttribute($keyAttribute, $key); } - if ($value instanceof ServiceClosureArgument) { - $value = $value->getValues()[0]; - } - if (\is_array($value)) { + if (\is_array($tag = $value)) { $element->setAttribute('type', 'collection'); $this->convertParameters($value, $type, $element, 'key'); - } elseif ($value instanceof TaggedIteratorArgument) { - $element->setAttribute('type', 'tagged'); - $element->setAttribute('tag', $value->getTag()); + } elseif ($value instanceof TaggedIteratorArgument || ($value instanceof ServiceLocatorArgument && $tag = $value->getTaggedIteratorArgument())) { + $element->setAttribute('type', $value instanceof TaggedIteratorArgument ? 'tagged_iterator' : 'tagged_locator'); + $element->setAttribute('tag', $tag->getTag()); + + if (null !== $tag->getIndexAttribute()) { + $element->setAttribute('index-by', $tag->getIndexAttribute()); + + if (null !== $tag->getDefaultIndexMethod()) { + $element->setAttribute('default-index-method', $tag->getDefaultIndexMethod()); + } + if (null !== $tag->getDefaultPriorityMethod()) { + $element->setAttribute('default-priority-method', $tag->getDefaultPriorityMethod()); + } + } } elseif ($value instanceof IteratorArgument) { $element->setAttribute('type', 'iterator'); $this->convertParameters($value->getValues(), $type, $element, 'key'); - } elseif ($value instanceof Reference) { + } elseif ($value instanceof ServiceLocatorArgument) { + $element->setAttribute('type', 'service_locator'); + $this->convertParameters($value->getValues(), $type, $element, 'key'); + } elseif ($value instanceof Reference || $value instanceof ServiceClosureArgument) { $element->setAttribute('type', 'service'); + if ($value instanceof ServiceClosureArgument) { + $element->setAttribute('type', 'service_closure'); + $value = $value->getValues()[0]; + } $element->setAttribute('id', (string) $value); $behavior = $value->getInvalidBehavior(); if (ContainerInterface::NULL_ON_INVALID_REFERENCE == $behavior) { @@ -300,6 +320,17 @@ class XmlDumper extends Dumper $element->setAttribute('type', 'expression'); $text = $this->document->createTextNode(self::phpToXml((string) $value)); $element->appendChild($text); + } elseif (\is_string($value) && !preg_match('/^[^\x00-\x08\x0B\x0E-\x1A\x1C-\x1F\x7F]*+$/u', $value)) { + $element->setAttribute('type', 'binary'); + $text = $this->document->createTextNode(self::phpToXml(base64_encode($value))); + $element->appendChild($text); + } elseif ($value instanceof \UnitEnum) { + $element->setAttribute('type', 'constant'); + $element->appendChild($this->document->createTextNode(self::phpToXml($value))); + } elseif ($value instanceof AbstractArgument) { + $element->setAttribute('type', 'abstract'); + $text = $this->document->createTextNode(self::phpToXml($value->getText())); + $element->appendChild($text); } else { if (\in_array($value, ['null', 'true', 'false'], true)) { $element->setAttribute('type', 'string'); @@ -318,10 +349,8 @@ class XmlDumper extends Dumper /** * Escapes arguments. - * - * @return array */ - private function escape(array $arguments) + private function escape(array $arguments): array { $args = []; foreach ($arguments as $k => $v) { @@ -342,11 +371,9 @@ class XmlDumper extends Dumper * * @param mixed $value Value to convert * - * @return string - * * @throws RuntimeException When trying to dump object or resource */ - public static function phpToXml($value) + public static function phpToXml($value): string { switch (true) { case null === $value: @@ -357,6 +384,8 @@ class XmlDumper extends Dumper return 'false'; case $value instanceof Parameter: return '%'.$value.'%'; + case $value instanceof \UnitEnum: + return sprintf('%s::%s', \get_class($value), $value->name); case \is_object($value) || \is_resource($value): throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.'); default: diff --git a/lib/symfony/dependency-injection/Dumper/YamlDumper.php b/lib/symfony/dependency-injection/Dumper/YamlDumper.php index 1e795c7daf..823eb97b0e 100644 --- a/lib/symfony/dependency-injection/Dumper/YamlDumper.php +++ b/lib/symfony/dependency-injection/Dumper/YamlDumper.php @@ -12,12 +12,15 @@ namespace Symfony\Component\DependencyInjection\Dumper; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\Reference; @@ -39,12 +42,12 @@ class YamlDumper extends Dumper /** * Dumps the service container as an YAML string. * - * @return string A YAML string representing of the service container + * @return string */ public function dump(array $options = []) { - if (!class_exists('Symfony\Component\Yaml\Dumper')) { - throw new RuntimeException('Unable to dump the container as the Symfony Yaml Component is not installed.'); + if (!class_exists(\Symfony\Component\Yaml\Dumper::class)) { + throw new LogicException('Unable to dump the container as the Symfony Yaml Component is not installed.'); } if (null === $this->dumper) { @@ -54,14 +57,7 @@ class YamlDumper extends Dumper return $this->container->resolveEnvPlaceholders($this->addParameters()."\n".$this->addServices()); } - /** - * Adds a service. - * - * @param string $id - * - * @return string - */ - private function addService($id, Definition $definition) + private function addService(string $id, Definition $definition): string { $code = " $id:\n"; if ($class = $definition->getClass()) { @@ -83,9 +79,9 @@ class YamlDumper extends Dumper foreach ($attributes as $key => $value) { $att[] = sprintf('%s: %s', $this->dumper->dump($key), $this->dumper->dump($value)); } - $att = $att ? ', '.implode(', ', $att) : ''; + $att = $att ? ': { '.implode(', ', $att).' }' : ''; - $tagsCode .= sprintf(" - { name: %s%s }\n", $this->dumper->dump($name), $att); + $tagsCode .= sprintf(" - %s%s\n", $this->dumper->dump($name), $att); } } if ($tagsCode) { @@ -101,21 +97,18 @@ class YamlDumper extends Dumper } if ($definition->isDeprecated()) { - $code .= sprintf(" deprecated: %s\n", $this->dumper->dump($definition->getDeprecationMessage('%service_id%'))); + $code .= " deprecated:\n"; + foreach ($definition->getDeprecation('%service_id%') as $key => $value) { + if ('' !== $value) { + $code .= sprintf(" %s: %s\n", $key, $this->dumper->dump($value)); + } + } } if ($definition->isAutowired()) { $code .= " autowire: true\n"; } - $autowiringTypesCode = ''; - foreach ($definition->getAutowiringTypes(false) as $autowiringType) { - $autowiringTypesCode .= sprintf(" - %s\n", $this->dumper->dump($autowiringType)); - } - if ($autowiringTypesCode) { - $code .= sprintf(" autowiring_types:\n%s", $autowiringTypesCode); - } - if ($definition->isAutoconfigured()) { $code .= " autoconfigure: true\n"; } @@ -144,8 +137,8 @@ class YamlDumper extends Dumper $code .= " shared: false\n"; } - if (null !== $decorated = $definition->getDecoratedService()) { - list($decorated, $renamedId, $priority) = $decorated; + if (null !== $decoratedService = $definition->getDecoratedService()) { + [$decorated, $renamedId, $priority] = $decoratedService; $code .= sprintf(" decorates: %s\n", $decorated); if (null !== $renamedId) { $code .= sprintf(" decoration_inner_name: %s\n", $renamedId); @@ -153,6 +146,12 @@ class YamlDumper extends Dumper if (0 !== $priority) { $code .= sprintf(" decoration_priority: %s\n", $priority); } + + $decorationOnInvalid = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + if (\in_array($decorationOnInvalid, [ContainerInterface::IGNORE_ON_INVALID_REFERENCE, ContainerInterface::NULL_ON_INVALID_REFERENCE])) { + $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE === $decorationOnInvalid ? 'null' : 'ignore'; + $code .= sprintf(" decoration_on_invalid: %s\n", $invalidBehavior); + } } if ($callable = $definition->getFactory()) { @@ -166,28 +165,32 @@ class YamlDumper extends Dumper return $code; } - /** - * Adds a service alias. - * - * @param string $alias - * - * @return string - */ - private function addServiceAlias($alias, Alias $id) + private function addServiceAlias(string $alias, Alias $id): string { - if ($id->isPrivate()) { + $deprecated = ''; + + if ($id->isDeprecated()) { + $deprecated = " deprecated:\n"; + + foreach ($id->getDeprecation('%alias_id%') as $key => $value) { + if ('' !== $value) { + $deprecated .= sprintf(" %s: %s\n", $key, $value); + } + } + } + + if (!$id->isDeprecated() && $id->isPrivate()) { return sprintf(" %s: '@%s'\n", $alias, $id); } - return sprintf(" %s:\n alias: %s\n public: %s\n", $alias, $id, $id->isPublic() ? 'true' : 'false'); + if ($id->isPublic()) { + $deprecated = " public: true\n".$deprecated; + } + + return sprintf(" %s:\n alias: %s\n%s", $alias, $id, $deprecated); } - /** - * Adds services. - * - * @return string - */ - private function addServices() + private function addServices(): string { if (!$this->container->getDefinitions()) { return ''; @@ -209,12 +212,7 @@ class YamlDumper extends Dumper return $code; } - /** - * Adds parameters. - * - * @return string - */ - private function addParameters() + private function addParameters(): string { if (!$this->container->getParameterBag()->all()) { return ''; @@ -229,6 +227,8 @@ class YamlDumper extends Dumper * Dumps callable to YAML format. * * @param mixed $callable + * + * @return mixed */ private function dumpCallable($callable) { @@ -246,8 +246,6 @@ class YamlDumper extends Dumper /** * Dumps the value to YAML format. * - * @param mixed $value - * * @return mixed * * @throws RuntimeException When trying to dump object or resource @@ -256,15 +254,38 @@ class YamlDumper extends Dumper { if ($value instanceof ServiceClosureArgument) { $value = $value->getValues()[0]; + + return new TaggedValue('service_closure', $this->getServiceCall((string) $value, $value)); } if ($value instanceof ArgumentInterface) { - if ($value instanceof TaggedIteratorArgument) { - return new TaggedValue('tagged', $value->getTag()); + $tag = $value; + + if ($value instanceof TaggedIteratorArgument || ($value instanceof ServiceLocatorArgument && $tag = $value->getTaggedIteratorArgument())) { + if (null === $tag->getIndexAttribute()) { + $content = $tag->getTag(); + } else { + $content = [ + 'tag' => $tag->getTag(), + 'index_by' => $tag->getIndexAttribute(), + ]; + + if (null !== $tag->getDefaultIndexMethod()) { + $content['default_index_method'] = $tag->getDefaultIndexMethod(); + } + if (null !== $tag->getDefaultPriorityMethod()) { + $content['default_priority_method'] = $tag->getDefaultPriorityMethod(); + } + } + + return new TaggedValue($value instanceof TaggedIteratorArgument ? 'tagged_iterator' : 'tagged_locator', $content); } + if ($value instanceof IteratorArgument) { $tag = 'iterator'; + } elseif ($value instanceof ServiceLocatorArgument) { + $tag = 'service_locator'; } else { - throw new RuntimeException(sprintf('Unspecified Yaml tag for type "%s".', \get_class($value))); + throw new RuntimeException(sprintf('Unspecified Yaml tag for type "%s".', get_debug_type($value))); } return new TaggedValue($tag, $this->dumpValue($value->getValues())); @@ -285,6 +306,10 @@ class YamlDumper extends Dumper return $this->getExpressionCall((string) $value); } elseif ($value instanceof Definition) { return new TaggedValue('service', (new Parser())->parse("_:\n".$this->addService('_', $value), Yaml::PARSE_CUSTOM_TAGS)['_']['_']); + } elseif ($value instanceof \UnitEnum) { + return new TaggedValue('php/const', sprintf('%s::%s', \get_class($value), $value->name)); + } elseif ($value instanceof AbstractArgument) { + return new TaggedValue('abstract', $value->getText()); } elseif (\is_object($value) || \is_resource($value)) { throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.'); } @@ -292,18 +317,11 @@ class YamlDumper extends Dumper return $value; } - /** - * Gets the service call. - * - * @param string $id - * @param Reference $reference - * - * @return string - */ - private function getServiceCall($id, Reference $reference = null) + private function getServiceCall(string $id, Reference $reference = null): string { if (null !== $reference) { switch ($reference->getInvalidBehavior()) { + case ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE: break; case ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE: break; case ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE: return sprintf('@!%s', $id); default: return sprintf('@?%s', $id); @@ -313,37 +331,23 @@ class YamlDumper extends Dumper return sprintf('@%s', $id); } - /** - * Gets parameter call. - * - * @param string $id - * - * @return string - */ - private function getParameterCall($id) + private function getParameterCall(string $id): string { return sprintf('%%%s%%', $id); } - private function getExpressionCall($expression) + private function getExpressionCall(string $expression): string { return sprintf('@=%s', $expression); } - /** - * Prepares parameters. - * - * @param bool $escape - * - * @return array - */ - private function prepareParameters(array $parameters, $escape = true) + private function prepareParameters(array $parameters, bool $escape = true): array { $filtered = []; foreach ($parameters as $key => $value) { if (\is_array($value)) { $value = $this->prepareParameters($value, $escape); - } elseif ($value instanceof Reference || \is_string($value) && 0 === strpos($value, '@')) { + } elseif ($value instanceof Reference || \is_string($value) && str_starts_with($value, '@')) { $value = '@'.$value; } @@ -353,12 +357,7 @@ class YamlDumper extends Dumper return $escape ? $this->escape($filtered) : $filtered; } - /** - * Escapes arguments. - * - * @return array - */ - private function escape(array $arguments) + private function escape(array $arguments): array { $args = []; foreach ($arguments as $k => $v) { diff --git a/lib/symfony/dependency-injection/EnvVarLoaderInterface.php b/lib/symfony/dependency-injection/EnvVarLoaderInterface.php new file mode 100644 index 0000000000..0c547f8a5f --- /dev/null +++ b/lib/symfony/dependency-injection/EnvVarLoaderInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * EnvVarLoaderInterface objects return key/value pairs that are added to the list of available env vars. + * + * @author Nicolas Grekas + */ +interface EnvVarLoaderInterface +{ + /** + * @return string[] Key/value pairs that can be accessed using the regular "%env()%" syntax + */ + public function loadEnvVars(): array; +} diff --git a/lib/symfony/dependency-injection/EnvVarProcessor.php b/lib/symfony/dependency-injection/EnvVarProcessor.php index 065673dc19..feb51fff3b 100644 --- a/lib/symfony/dependency-injection/EnvVarProcessor.php +++ b/lib/symfony/dependency-injection/EnvVarProcessor.php @@ -11,8 +11,8 @@ namespace Symfony\Component\DependencyInjection; -use Symfony\Component\Config\Util\XmlUtils; use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException; +use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; /** @@ -21,10 +21,16 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; class EnvVarProcessor implements EnvVarProcessorInterface { private $container; + private $loaders; + private $loadedVars = []; - public function __construct(ContainerInterface $container) + /** + * @param EnvVarLoaderInterface[] $loaders + */ + public function __construct(ContainerInterface $container, \Traversable $loaders = null) { $this->container = $container; + $this->loaders = $loaders ?? new \ArrayIterator(); } /** @@ -35,53 +41,150 @@ class EnvVarProcessor implements EnvVarProcessorInterface return [ 'base64' => 'string', 'bool' => 'bool', + 'not' => 'bool', 'const' => 'bool|int|float|string|array', + 'csv' => 'array', 'file' => 'string', 'float' => 'float', 'int' => 'int', 'json' => 'array', + 'key' => 'bool|int|float|string|array', + 'url' => 'array', + 'query_string' => 'array', 'resolve' => 'string', + 'default' => 'bool|int|float|string|array', 'string' => 'string', + 'trim' => 'string', + 'require' => 'bool|int|float|string|array', ]; } /** * {@inheritdoc} */ - public function getEnv($prefix, $name, \Closure $getEnv) + public function getEnv(string $prefix, string $name, \Closure $getEnv) { $i = strpos($name, ':'); - if ('file' === $prefix) { - if (!is_scalar($file = $getEnv($name))) { - throw new RuntimeException(sprintf('Invalid file name: env var "%s" is non-scalar.', $name)); - } - if (!file_exists($file)) { - throw new RuntimeException(sprintf('Env "file:%s" not found: "%s" does not exist.', $name, $file)); + if ('key' === $prefix) { + if (false === $i) { + throw new RuntimeException(sprintf('Invalid env "key:%s": a key specifier should be provided.', $name)); } - return file_get_contents($file); + $next = substr($name, $i + 1); + $key = substr($name, 0, $i); + $array = $getEnv($next); + + if (!\is_array($array)) { + throw new RuntimeException(sprintf('Resolved value of "%s" did not result in an array value.', $next)); + } + + if (!isset($array[$key]) && !\array_key_exists($key, $array)) { + throw new EnvNotFoundException(sprintf('Key "%s" not found in %s (resolved from "%s").', $key, json_encode($array), $next)); + } + + return $array[$key]; + } + + if ('default' === $prefix) { + if (false === $i) { + throw new RuntimeException(sprintf('Invalid env "default:%s": a fallback parameter should be provided.', $name)); + } + + $next = substr($name, $i + 1); + $default = substr($name, 0, $i); + + if ('' !== $default && !$this->container->hasParameter($default)) { + throw new RuntimeException(sprintf('Invalid env fallback in "default:%s": parameter "%s" not found.', $name, $default)); + } + + try { + $env = $getEnv($next); + + if ('' !== $env && null !== $env) { + return $env; + } + } catch (EnvNotFoundException $e) { + // no-op + } + + return '' === $default ? null : $this->container->getParameter($default); + } + + if ('file' === $prefix || 'require' === $prefix) { + if (!\is_scalar($file = $getEnv($name))) { + throw new RuntimeException(sprintf('Invalid file name: env var "%s" is non-scalar.', $name)); + } + if (!is_file($file)) { + throw new EnvNotFoundException(sprintf('File "%s" not found (resolved from "%s").', $file, $name)); + } + + if ('file' === $prefix) { + return file_get_contents($file); + } else { + return require $file; + } } if (false !== $i || 'string' !== $prefix) { - if (null === $env = $getEnv($name)) { - return null; - } + $env = $getEnv($name); } elseif (isset($_ENV[$name])) { $env = $_ENV[$name]; - } elseif (isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')) { + } elseif (isset($_SERVER[$name]) && !str_starts_with($name, 'HTTP_')) { $env = $_SERVER[$name]; } elseif (false === ($env = getenv($name)) || null === $env) { // null is a possible value because of thread safety issues - if (!$this->container->hasParameter("env($name)")) { - throw new EnvNotFoundException($name); + foreach ($this->loadedVars as $vars) { + if (false !== $env = ($vars[$name] ?? false)) { + break; + } } - if (null === $env = $this->container->getParameter("env($name)")) { - return null; + if (false === $env || null === $env) { + $loaders = $this->loaders; + $this->loaders = new \ArrayIterator(); + + try { + $i = 0; + $ended = true; + $count = $loaders instanceof \Countable ? $loaders->count() : 0; + foreach ($loaders as $loader) { + if (\count($this->loadedVars) > $i++) { + continue; + } + $this->loadedVars[] = $vars = $loader->loadEnvVars(); + if (false !== $env = $vars[$name] ?? false) { + $ended = false; + break; + } + } + if ($ended || $count === $i) { + $loaders = $this->loaders; + } + } catch (ParameterCircularReferenceException $e) { + // skip loaders that need an env var that is not defined + } finally { + $this->loaders = $loaders; + } + } + + if (false === $env || null === $env) { + if (!$this->container->hasParameter("env($name)")) { + throw new EnvNotFoundException(sprintf('Environment variable not found: "%s".', $name)); + } + + $env = $this->container->getParameter("env($name)"); } } - if (!is_scalar($env)) { + if (null === $env) { + if (!isset($this->getProvidedTypes()[$prefix])) { + throw new RuntimeException(sprintf('Unsupported env var prefix "%s".', $prefix)); + } + + return null; + } + + if (!\is_scalar($env)) { throw new RuntimeException(sprintf('Non-scalar env var "%s" cannot be cast to "%s".', $name, $prefix)); } @@ -89,12 +192,14 @@ class EnvVarProcessor implements EnvVarProcessorInterface return (string) $env; } - if ('bool' === $prefix) { - return (bool) self::phpize($env); + if (\in_array($prefix, ['bool', 'not'], true)) { + $env = (bool) (filter_var($env, \FILTER_VALIDATE_BOOLEAN) ?: filter_var($env, \FILTER_VALIDATE_INT) ?: filter_var($env, \FILTER_VALIDATE_FLOAT)); + + return 'not' === $prefix ? !$env : $env; } if ('int' === $prefix) { - if (!is_numeric($env = self::phpize($env))) { + if (false === $env = filter_var($env, \FILTER_VALIDATE_INT) ?: filter_var($env, \FILTER_VALIDATE_FLOAT)) { throw new RuntimeException(sprintf('Non-numeric env var "%s" cannot be cast to int.', $name)); } @@ -102,7 +207,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface } if ('float' === $prefix) { - if (!is_numeric($env = self::phpize($env))) { + if (false === $env = filter_var($env, \FILTER_VALIDATE_FLOAT)) { throw new RuntimeException(sprintf('Non-numeric env var "%s" cannot be cast to float.', $name)); } @@ -118,7 +223,7 @@ class EnvVarProcessor implements EnvVarProcessorInterface } if ('base64' === $prefix) { - return base64_decode($env); + return base64_decode(strtr($env, '-_', '+/')); } if ('json' === $prefix) { @@ -128,36 +233,72 @@ class EnvVarProcessor implements EnvVarProcessorInterface throw new RuntimeException(sprintf('Invalid JSON in env var "%s": ', $name).json_last_error_msg()); } - if (!\is_array($env)) { - throw new RuntimeException(sprintf('Invalid JSON env var "%s": array expected, "%s" given.', $name, \gettype($env))); + if (null !== $env && !\is_array($env)) { + throw new RuntimeException(sprintf('Invalid JSON env var "%s": array or null expected, "%s" given.', $name, get_debug_type($env))); } return $env; } + if ('url' === $prefix) { + $parsedEnv = parse_url($env); + + if (false === $parsedEnv) { + throw new RuntimeException(sprintf('Invalid URL in env var "%s".', $name)); + } + if (!isset($parsedEnv['scheme'], $parsedEnv['host'])) { + throw new RuntimeException(sprintf('Invalid URL env var "%s": schema and host expected, "%s" given.', $name, $env)); + } + $parsedEnv += [ + 'port' => null, + 'user' => null, + 'pass' => null, + 'path' => null, + 'query' => null, + 'fragment' => null, + ]; + + // remove the '/' separator + $parsedEnv['path'] = '/' === ($parsedEnv['path'] ?? '/') ? '' : substr($parsedEnv['path'], 1); + + return $parsedEnv; + } + + if ('query_string' === $prefix) { + $queryString = parse_url($env, \PHP_URL_QUERY) ?: $env; + parse_str($queryString, $result); + + return $result; + } + if ('resolve' === $prefix) { - return preg_replace_callback('/%%|%([^%\s]+)%/', function ($match) use ($name) { + return preg_replace_callback('/%%|%([^%\s]+)%/', function ($match) use ($name, $getEnv) { if (!isset($match[1])) { return '%'; } - $value = $this->container->getParameter($match[1]); - if (!is_scalar($value)) { - throw new RuntimeException(sprintf('Parameter "%s" found when resolving env var "%s" must be scalar, "%s" given.', $match[1], $name, \gettype($value))); + + if (str_starts_with($match[1], 'env(') && str_ends_with($match[1], ')') && 'env()' !== $match[1]) { + $value = $getEnv(substr($match[1], 4, -1)); + } else { + $value = $this->container->getParameter($match[1]); + } + + if (!\is_scalar($value)) { + throw new RuntimeException(sprintf('Parameter "%s" found when resolving env var "%s" must be scalar, "%s" given.', $match[1], $name, get_debug_type($value))); } return $value; }, $env); } - throw new RuntimeException(sprintf('Unsupported env var prefix "%s".', $prefix)); - } - - private static function phpize($value) - { - if (!class_exists(XmlUtils::class)) { - throw new RuntimeException('The Symfony Config component is required to cast env vars to "bool", "int" or "float".'); + if ('csv' === $prefix) { + return str_getcsv($env, ',', '"', \PHP_VERSION_ID >= 70400 ? '' : '\\'); } - return XmlUtils::phpize($value); + if ('trim' === $prefix) { + return trim($env); + } + + throw new RuntimeException(sprintf('Unsupported env var prefix "%s" for env name "%s".', $prefix, $name)); } } diff --git a/lib/symfony/dependency-injection/EnvVarProcessorInterface.php b/lib/symfony/dependency-injection/EnvVarProcessorInterface.php index 654fe55e98..d3275fe290 100644 --- a/lib/symfony/dependency-injection/EnvVarProcessorInterface.php +++ b/lib/symfony/dependency-injection/EnvVarProcessorInterface.php @@ -31,7 +31,7 @@ interface EnvVarProcessorInterface * * @throws RuntimeException on error */ - public function getEnv($prefix, $name, \Closure $getEnv); + public function getEnv(string $prefix, string $name, \Closure $getEnv); /** * @return string[] The PHP-types managed by getEnv(), keyed by prefixes diff --git a/lib/symfony/dependency-injection/Exception/AutowiringFailedException.php b/lib/symfony/dependency-injection/Exception/AutowiringFailedException.php index 145cd8cbdc..0006f5621c 100644 --- a/lib/symfony/dependency-injection/Exception/AutowiringFailedException.php +++ b/lib/symfony/dependency-injection/Exception/AutowiringFailedException.php @@ -17,12 +17,54 @@ namespace Symfony\Component\DependencyInjection\Exception; class AutowiringFailedException extends RuntimeException { private $serviceId; + private $messageCallback; - public function __construct($serviceId, $message = '', $code = 0, \Exception $previous = null) + public function __construct(string $serviceId, $message = '', int $code = 0, \Throwable $previous = null) { $this->serviceId = $serviceId; - parent::__construct($message, $code, $previous); + if ($message instanceof \Closure + && (\function_exists('xdebug_is_enabled') ? xdebug_is_enabled() : \function_exists('xdebug_info')) + ) { + $message = $message(); + } + + if (!$message instanceof \Closure) { + parent::__construct($message, $code, $previous); + + return; + } + + $this->messageCallback = $message; + parent::__construct('', $code, $previous); + + $this->message = new class($this->message, $this->messageCallback) { + private $message; + private $messageCallback; + + public function __construct(&$message, &$messageCallback) + { + $this->message = &$message; + $this->messageCallback = &$messageCallback; + } + + public function __toString(): string + { + $messageCallback = $this->messageCallback; + $this->messageCallback = null; + + try { + return $this->message = $messageCallback(); + } catch (\Throwable $e) { + return $this->message = $e->getMessage(); + } + } + }; + } + + public function getMessageCallback(): ?\Closure + { + return $this->messageCallback; } public function getServiceId() diff --git a/lib/symfony/dependency-injection/Exception/EnvNotFoundException.php b/lib/symfony/dependency-injection/Exception/EnvNotFoundException.php index 577095e88b..04ac84800a 100644 --- a/lib/symfony/dependency-injection/Exception/EnvNotFoundException.php +++ b/lib/symfony/dependency-injection/Exception/EnvNotFoundException.php @@ -18,8 +18,4 @@ namespace Symfony\Component\DependencyInjection\Exception; */ class EnvNotFoundException extends InvalidArgumentException { - public function __construct($name) - { - parent::__construct(sprintf('Environment variable not found: "%s".', $name)); - } } diff --git a/lib/symfony/dependency-injection/Exception/EnvParameterException.php b/lib/symfony/dependency-injection/Exception/EnvParameterException.php index 3839a4633b..48b5e486ae 100644 --- a/lib/symfony/dependency-injection/Exception/EnvParameterException.php +++ b/lib/symfony/dependency-injection/Exception/EnvParameterException.php @@ -18,7 +18,7 @@ namespace Symfony\Component\DependencyInjection\Exception; */ class EnvParameterException extends InvalidArgumentException { - public function __construct(array $envs, \Exception $previous = null, $message = 'Incompatible use of dynamic environment variables "%s" found in parameters.') + public function __construct(array $envs, \Throwable $previous = null, string $message = 'Incompatible use of dynamic environment variables "%s" found in parameters.') { parent::__construct(sprintf($message, implode('", "', $envs)), 0, $previous); } diff --git a/lib/symfony/dependency-injection/Exception/ExceptionInterface.php b/lib/symfony/dependency-injection/Exception/ExceptionInterface.php index 5bec478695..6202df76e7 100644 --- a/lib/symfony/dependency-injection/Exception/ExceptionInterface.php +++ b/lib/symfony/dependency-injection/Exception/ExceptionInterface.php @@ -19,6 +19,6 @@ use Psr\Container\ContainerExceptionInterface; * @author Fabien Potencier * @author Bulat Shakirzyanov */ -interface ExceptionInterface extends ContainerExceptionInterface +interface ExceptionInterface extends ContainerExceptionInterface, \Throwable { } diff --git a/lib/symfony/dependency-injection/Exception/InvalidParameterTypeException.php b/lib/symfony/dependency-injection/Exception/InvalidParameterTypeException.php new file mode 100644 index 0000000000..2a11626fe2 --- /dev/null +++ b/lib/symfony/dependency-injection/Exception/InvalidParameterTypeException.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 Symfony\Component\DependencyInjection\Exception; + +/** + * Thrown when trying to inject a parameter into a constructor/method with an incompatible type. + * + * @author Nicolas Grekas + * @author Julien Maulny + */ +class InvalidParameterTypeException extends InvalidArgumentException +{ + public function __construct(string $serviceId, string $type, \ReflectionParameter $parameter) + { + $acceptedType = $parameter->getType(); + $acceptedType = $acceptedType instanceof \ReflectionNamedType ? $acceptedType->getName() : (string) $acceptedType; + $this->code = $type; + + $function = $parameter->getDeclaringFunction(); + $functionName = $function instanceof \ReflectionMethod + ? sprintf('%s::%s', $function->getDeclaringClass()->getName(), $function->getName()) + : $function->getName(); + + parent::__construct(sprintf('Invalid definition for service "%s": argument %d of "%s()" accepts "%s", "%s" passed.', $serviceId, 1 + $parameter->getPosition(), $functionName, $acceptedType, $type)); + } +} diff --git a/lib/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php b/lib/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php index 29151765dc..2450ccb5c7 100644 --- a/lib/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php +++ b/lib/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php @@ -20,7 +20,7 @@ class ParameterCircularReferenceException extends RuntimeException { private $parameters; - public function __construct($parameters, \Exception $previous = null) + public function __construct(array $parameters, \Throwable $previous = null) { parent::__construct(sprintf('Circular reference detected for parameter "%s" ("%s" > "%s").', $parameters[0], implode('" > "', $parameters), $parameters[0]), 0, $previous); diff --git a/lib/symfony/dependency-injection/Exception/ParameterNotFoundException.php b/lib/symfony/dependency-injection/Exception/ParameterNotFoundException.php index b08f2e8559..5d38310141 100644 --- a/lib/symfony/dependency-injection/Exception/ParameterNotFoundException.php +++ b/lib/symfony/dependency-injection/Exception/ParameterNotFoundException.php @@ -11,12 +11,14 @@ namespace Symfony\Component\DependencyInjection\Exception; +use Psr\Container\NotFoundExceptionInterface; + /** * This exception is thrown when a non-existent parameter is used. * * @author Fabien Potencier */ -class ParameterNotFoundException extends InvalidArgumentException +class ParameterNotFoundException extends InvalidArgumentException implements NotFoundExceptionInterface { private $key; private $sourceId; @@ -25,14 +27,14 @@ class ParameterNotFoundException extends InvalidArgumentException private $nonNestedAlternative; /** - * @param string $key The requested parameter key - * @param string $sourceId The service id that references the non-existent parameter - * @param string $sourceKey The parameter key that references the non-existent parameter - * @param \Exception $previous The previous exception - * @param string[] $alternatives Some parameter name alternatives - * @param string|null $nonNestedAlternative The alternative parameter name when the user expected dot notation for nested parameters + * @param string $key The requested parameter key + * @param string|null $sourceId The service id that references the non-existent parameter + * @param string|null $sourceKey The parameter key that references the non-existent parameter + * @param \Throwable|null $previous The previous exception + * @param string[] $alternatives Some parameter name alternatives + * @param string|null $nonNestedAlternative The alternative parameter name when the user expected dot notation for nested parameters */ - public function __construct($key, $sourceId = null, $sourceKey = null, \Exception $previous = null, array $alternatives = [], $nonNestedAlternative = null) + public function __construct(string $key, string $sourceId = null, string $sourceKey = null, \Throwable $previous = null, array $alternatives = [], string $nonNestedAlternative = null) { $this->key = $key; $this->sourceId = $sourceId; @@ -82,14 +84,14 @@ class ParameterNotFoundException extends InvalidArgumentException return $this->sourceKey; } - public function setSourceId($sourceId) + public function setSourceId(?string $sourceId) { $this->sourceId = $sourceId; $this->updateRepr(); } - public function setSourceKey($sourceKey) + public function setSourceKey(?string $sourceKey) { $this->sourceKey = $sourceKey; diff --git a/lib/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php b/lib/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php index 26e3fb34bf..a38671bcf2 100644 --- a/lib/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php +++ b/lib/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php @@ -21,7 +21,7 @@ class ServiceCircularReferenceException extends RuntimeException private $serviceId; private $path; - public function __construct($serviceId, array $path, \Exception $previous = null) + public function __construct(string $serviceId, array $path, \Throwable $previous = null) { parent::__construct(sprintf('Circular reference detected for service "%s", path: "%s".', $serviceId, implode(' -> ', $path)), 0, $previous); diff --git a/lib/symfony/dependency-injection/Exception/ServiceNotFoundException.php b/lib/symfony/dependency-injection/Exception/ServiceNotFoundException.php index 280dabf33f..f91afae397 100644 --- a/lib/symfony/dependency-injection/Exception/ServiceNotFoundException.php +++ b/lib/symfony/dependency-injection/Exception/ServiceNotFoundException.php @@ -24,7 +24,7 @@ class ServiceNotFoundException extends InvalidArgumentException implements NotFo private $sourceId; private $alternatives; - public function __construct($id, $sourceId = null, \Exception $previous = null, array $alternatives = [], $msg = null) + public function __construct(string $id, string $sourceId = null, \Throwable $previous = null, array $alternatives = [], string $msg = null) { if (null !== $msg) { // no-op diff --git a/lib/symfony/dependency-injection/ExpressionLanguage.php b/lib/symfony/dependency-injection/ExpressionLanguage.php index 0c1780b8b1..961c737e8d 100644 --- a/lib/symfony/dependency-injection/ExpressionLanguage.php +++ b/lib/symfony/dependency-injection/ExpressionLanguage.php @@ -11,8 +11,13 @@ namespace Symfony\Component\DependencyInjection; +use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage; +if (!class_exists(BaseExpressionLanguage::class)) { + return; +} + /** * Adds some function to the default ExpressionLanguage. * @@ -25,7 +30,7 @@ class ExpressionLanguage extends BaseExpressionLanguage /** * {@inheritdoc} */ - public function __construct($cache = null, array $providers = [], callable $serviceCompiler = null) + public function __construct(CacheItemPoolInterface $cache = null, array $providers = [], callable $serviceCompiler = null) { // prepend the default provider to let users override it easily array_unshift($providers, new ExpressionLanguageProvider($serviceCompiler)); diff --git a/lib/symfony/dependency-injection/Extension/ConfigurationExtensionInterface.php b/lib/symfony/dependency-injection/Extension/ConfigurationExtensionInterface.php index c3bd8423ba..a42967f4da 100644 --- a/lib/symfony/dependency-injection/Extension/ConfigurationExtensionInterface.php +++ b/lib/symfony/dependency-injection/Extension/ConfigurationExtensionInterface.php @@ -24,7 +24,7 @@ interface ConfigurationExtensionInterface /** * Returns extension configuration. * - * @return ConfigurationInterface|null The configuration or null + * @return ConfigurationInterface|null */ public function getConfiguration(array $config, ContainerBuilder $container); } diff --git a/lib/symfony/dependency-injection/Extension/Extension.php b/lib/symfony/dependency-injection/Extension/Extension.php index 00fa9dc8da..ef6c1aaa3b 100644 --- a/lib/symfony/dependency-injection/Extension/Extension.php +++ b/lib/symfony/dependency-injection/Extension/Extension.php @@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\BadMethodCallException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; /** * Provides useful features shared by many extensions. @@ -59,14 +60,14 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn * * This can be overridden in a sub-class to specify the alias manually. * - * @return string The alias + * @return string * * @throws BadMethodCallException When the extension name does not follow conventions */ public function getAlias() { $className = static::class; - if ('Extension' != substr($className, -9)) { + if (!str_ends_with($className, 'Extension')) { throw new BadMethodCallException('This extension does not follow the naming convention; you must overwrite the getAlias() method.'); } $classBaseName = substr(strrchr($className, '\\'), 1, -9); @@ -81,21 +82,29 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn { $class = static::class; - if (false !== strpos($class, "\0")) { + if (str_contains($class, "\0")) { return null; // ignore anonymous classes } $class = substr_replace($class, '\Configuration', strrpos($class, '\\')); $class = $container->getReflectionClass($class); - $constructor = $class ? $class->getConstructor() : null; - return $class && (!$constructor || !$constructor->getNumberOfRequiredParameters()) ? $class->newInstance() : null; + if (!$class) { + return null; + } + + if (!$class->implementsInterface(ConfigurationInterface::class)) { + throw new LogicException(sprintf('The extension configuration class "%s" must implement "%s".', $class->getName(), ConfigurationInterface::class)); + } + + if (!($constructor = $class->getConstructor()) || !$constructor->getNumberOfRequiredParameters()) { + return $class->newInstance(); + } + + return null; } - /** - * @return array - */ - final protected function processConfiguration(ConfigurationInterface $configuration, array $configs) + final protected function processConfiguration(ConfigurationInterface $configuration, array $configs): array { $processor = new Processor(); @@ -105,7 +114,7 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn /** * @internal */ - final public function getProcessedConfigs() + final public function getProcessedConfigs(): array { try { return $this->processedConfigs; @@ -115,7 +124,7 @@ abstract class Extension implements ExtensionInterface, ConfigurationExtensionIn } /** - * @return bool Whether the configuration is enabled + * @return bool * * @throws InvalidArgumentException When the config is not enableable */ diff --git a/lib/symfony/dependency-injection/Extension/ExtensionInterface.php b/lib/symfony/dependency-injection/Extension/ExtensionInterface.php index 6a7a2cf023..f2373ed5ea 100644 --- a/lib/symfony/dependency-injection/Extension/ExtensionInterface.php +++ b/lib/symfony/dependency-injection/Extension/ExtensionInterface.php @@ -30,7 +30,7 @@ interface ExtensionInterface /** * Returns the namespace to be used for this extension (XML namespace). * - * @return string The XML namespace + * @return string */ public function getNamespace(); @@ -46,7 +46,7 @@ interface ExtensionInterface * * This alias is also the mandatory prefix to use when using YAML. * - * @return string The alias + * @return string */ public function getAlias(); } diff --git a/lib/symfony/dependency-injection/LICENSE b/lib/symfony/dependency-injection/LICENSE index 9e936ec044..88bf75bb4d 100644 --- a/lib/symfony/dependency-injection/LICENSE +++ b/lib/symfony/dependency-injection/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2020 Fabien Potencier +Copyright (c) 2004-2022 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/lib/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php b/lib/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php index 417ab908a3..a9d78115dd 100644 --- a/lib/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php +++ b/lib/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php @@ -25,12 +25,10 @@ interface InstantiatorInterface /** * Instantiates a proxy object. * - * @param ContainerInterface $container The container from which the service is being requested - * @param Definition $definition The definition of the requested service - * @param string $id Identifier of the requested service - * @param callable $realInstantiator Zero-argument callback that is capable of producing the real service instance + * @param string $id Identifier of the requested service + * @param callable $realInstantiator Zero-argument callback that is capable of producing the real service instance * * @return object */ - public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator); + public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator); } diff --git a/lib/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php b/lib/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php index 532e768684..1696e7a904 100644 --- a/lib/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php +++ b/lib/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php @@ -26,8 +26,8 @@ class RealServiceInstantiator implements InstantiatorInterface /** * {@inheritdoc} */ - public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator) + public function instantiateProxy(ContainerInterface $container, Definition $definition, string $id, callable $realInstantiator) { - return \call_user_func($realInstantiator); + return $realInstantiator(); } } diff --git a/lib/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php b/lib/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php index 3946eafafd..351560d292 100644 --- a/lib/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php +++ b/lib/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php @@ -30,11 +30,9 @@ interface DumperInterface /** * Generates the code to be used to instantiate a proxy in the dumped factory code. * - * @param string $id Service identifier - * * @return string */ - public function getProxyFactoryCode(Definition $definition, $id/**, $factoryCode = null */); + public function getProxyFactoryCode(Definition $definition, string $id, string $factoryCode); /** * Generates the code for the lazy proxy. diff --git a/lib/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php b/lib/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php index 67f9fae94d..7e0f14c32b 100644 --- a/lib/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php +++ b/lib/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php @@ -18,14 +18,14 @@ use Symfony\Component\DependencyInjection\Definition; * * @author Marco Pivetta * - * @final since version 3.3 + * @final */ class NullDumper implements DumperInterface { /** * {@inheritdoc} */ - public function isProxyCandidate(Definition $definition) + public function isProxyCandidate(Definition $definition): bool { return false; } @@ -33,7 +33,7 @@ class NullDumper implements DumperInterface /** * {@inheritdoc} */ - public function getProxyFactoryCode(Definition $definition, $id, $factoryCode = null) + public function getProxyFactoryCode(Definition $definition, string $id, string $factoryCode): string { return ''; } @@ -41,7 +41,7 @@ class NullDumper implements DumperInterface /** * {@inheritdoc} */ - public function getProxyCode(Definition $definition) + public function getProxyCode(Definition $definition): string { return ''; } diff --git a/lib/symfony/dependency-injection/LazyProxy/ProxyHelper.php b/lib/symfony/dependency-injection/LazyProxy/ProxyHelper.php index bfa65f56f0..f33011ad1d 100644 --- a/lib/symfony/dependency-injection/LazyProxy/ProxyHelper.php +++ b/lib/symfony/dependency-injection/LazyProxy/ProxyHelper.php @@ -21,42 +21,59 @@ class ProxyHelper /** * @return string|null The FQCN or builtin name of the type hint, or null when the type hint references an invalid self|parent context */ - public static function getTypeHint(\ReflectionFunctionAbstract $r, \ReflectionParameter $p = null, $noBuiltin = false) + public static function getTypeHint(\ReflectionFunctionAbstract $r, \ReflectionParameter $p = null, bool $noBuiltin = false): ?string { if ($p instanceof \ReflectionParameter) { - if (method_exists($p, 'getType')) { - $type = $p->getType(); - } elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $p, $type)) { - $name = $type = $type[1]; - - if ('callable' === $name || 'array' === $name) { - return $noBuiltin ? null : $name; - } - } + $type = $p->getType(); } else { - $type = method_exists($r, 'getReturnType') ? $r->getReturnType() : null; + $type = $r->getReturnType(); } if (!$type) { return null; } + return self::getTypeHintForType($type, $r, $noBuiltin); + } + + private static function getTypeHintForType(\ReflectionType $type, \ReflectionFunctionAbstract $r, bool $noBuiltin): ?string + { $types = []; + $glue = '|'; + if ($type instanceof \ReflectionUnionType) { + $reflectionTypes = $type->getTypes(); + } elseif ($type instanceof \ReflectionIntersectionType) { + $reflectionTypes = $type->getTypes(); + $glue = '&'; + } elseif ($type instanceof \ReflectionNamedType) { + $reflectionTypes = [$type]; + } else { + return null; + } - foreach ($type instanceof \ReflectionUnionType ? $type->getTypes() : [$type] as $type) { - $name = $type instanceof \ReflectionNamedType ? $type->getName() : (string) $type; + foreach ($reflectionTypes as $type) { + if ($type instanceof \ReflectionIntersectionType) { + $typeHint = self::getTypeHintForType($type, $r, $noBuiltin); + if (null === $typeHint) { + return null; + } - if (!\is_string($type) && $type->isBuiltin()) { + $types[] = sprintf('(%s)', $typeHint); + + continue; + } + + if ($type->isBuiltin()) { if (!$noBuiltin) { - $types[] = $name; + $types[] = $type->getName(); } continue; } - $lcName = strtolower($name); + $lcName = strtolower($type->getName()); $prefix = $noBuiltin ? '' : '\\'; if ('self' !== $lcName && 'parent' !== $lcName) { - $types[] = '' !== $prefix ? $prefix.$name : $name; + $types[] = $prefix.$type->getName(); continue; } if (!$r instanceof \ReflectionMethod) { @@ -69,6 +86,8 @@ class ProxyHelper } } - return $types ? implode('|', $types) : null; + sort($types); + + return $types ? implode($glue, $types) : null; } } diff --git a/lib/symfony/dependency-injection/Loader/ClosureLoader.php b/lib/symfony/dependency-injection/Loader/ClosureLoader.php index 183cacc4d6..fe2b91a2a4 100644 --- a/lib/symfony/dependency-injection/Loader/ClosureLoader.php +++ b/lib/symfony/dependency-injection/Loader/ClosureLoader.php @@ -25,23 +25,24 @@ class ClosureLoader extends Loader { private $container; - public function __construct(ContainerBuilder $container) + public function __construct(ContainerBuilder $container, string $env = null) { $this->container = $container; + parent::__construct($env); } /** * {@inheritdoc} */ - public function load($resource, $type = null) + public function load($resource, string $type = null) { - \call_user_func($resource, $this->container); + return $resource($this->container, $this->env); } /** * {@inheritdoc} */ - public function supports($resource, $type = null) + public function supports($resource, string $type = null) { return $resource instanceof \Closure; } diff --git a/lib/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php index 428683ef4c..6de2d67641 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/AbstractConfigurator.php @@ -11,7 +11,10 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\Component\Config\Loader\ParamConfigurator; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Parameter; @@ -20,20 +23,38 @@ use Symfony\Component\ExpressionLanguage\Expression; abstract class AbstractConfigurator { - const FACTORY = 'unknown'; + public const FACTORY = 'unknown'; + + /** + * @var callable(mixed, bool)|null + */ + public static $valuePreProcessor; /** @internal */ protected $definition; - public function __call($method, $args) + public function __call(string $method, array $args) { if (method_exists($this, 'set'.$method)) { - return \call_user_func_array([$this, 'set'.$method], $args); + return $this->{'set'.$method}(...$args); } throw new \BadMethodCallException(sprintf('Call to undefined method "%s::%s()".', static::class, $method)); } + /** + * @return array + */ + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + /** * Checks that a value is valid, optionally replacing Definition and Reference configurators by their configure value. * @@ -49,11 +70,17 @@ abstract class AbstractConfigurator $value[$k] = static::processValue($v, $allowServices); } - return $value; + return self::$valuePreProcessor ? (self::$valuePreProcessor)($value, $allowServices) : $value; + } + + if (self::$valuePreProcessor) { + $value = (self::$valuePreProcessor)($value, $allowServices); } if ($value instanceof ReferenceConfigurator) { - return new Reference($value->id, $value->invalidBehavior); + $reference = new Reference($value->id, $value->invalidBehavior); + + return $value instanceof ClosureReferenceConfigurator ? new ServiceClosureArgument($reference) : $reference; } if ($value instanceof InlineServiceConfigurator) { @@ -63,25 +90,30 @@ abstract class AbstractConfigurator return $def; } + if ($value instanceof ParamConfigurator) { + return (string) $value; + } + if ($value instanceof self) { throw new InvalidArgumentException(sprintf('"%s()" can be used only at the root of service configuration files.', $value::FACTORY)); } switch (true) { case null === $value: - case is_scalar($value): + case \is_scalar($value): return $value; case $value instanceof ArgumentInterface: case $value instanceof Definition: case $value instanceof Expression: case $value instanceof Parameter: + case $value instanceof AbstractArgument: case $value instanceof Reference: if ($allowServices) { return $value; } } - throw new InvalidArgumentException(sprintf('Cannot use values of type "%s" in service configuration files.', \is_object($value) ? \get_class($value) : \gettype($value))); + throw new InvalidArgumentException(sprintf('Cannot use values of type "%s" in service configuration files.', get_debug_type($value))); } } diff --git a/lib/symfony/dependency-injection/Loader/Configurator/AbstractServiceConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/AbstractServiceConfigurator.php index 0a565787fd..96d6fd75a7 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/AbstractServiceConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/AbstractServiceConfigurator.php @@ -20,7 +20,7 @@ abstract class AbstractServiceConfigurator extends AbstractConfigurator protected $id; private $defaultTags = []; - public function __construct(ServicesConfigurator $parent, Definition $definition, $id = null, array $defaultTags = []) + public function __construct(ServicesConfigurator $parent, Definition $definition, string $id = null, array $defaultTags = []) { $this->parent = $parent; $this->definition = $definition; @@ -32,8 +32,8 @@ abstract class AbstractServiceConfigurator extends AbstractConfigurator { // default tags should be added last foreach ($this->defaultTags as $name => $attributes) { - foreach ($attributes as $attributes) { - $this->definition->addTag($name, $attributes); + foreach ($attributes as $attribute) { + $this->definition->addTag($name, $attribute); } } $this->defaultTags = []; @@ -41,13 +41,8 @@ abstract class AbstractServiceConfigurator extends AbstractConfigurator /** * Registers a service. - * - * @param string $id - * @param string|null $class - * - * @return ServiceConfigurator */ - final public function set($id, $class = null) + final public function set(?string $id, string $class = null): ServiceConfigurator { $this->__destruct(); @@ -56,13 +51,8 @@ abstract class AbstractServiceConfigurator extends AbstractConfigurator /** * Creates an alias. - * - * @param string $id - * @param string $referencedId - * - * @return AliasConfigurator */ - final public function alias($id, $referencedId) + final public function alias(string $id, string $referencedId): AliasConfigurator { $this->__destruct(); @@ -71,13 +61,8 @@ abstract class AbstractServiceConfigurator extends AbstractConfigurator /** * Registers a PSR-4 namespace using a glob pattern. - * - * @param string $namespace - * @param string $resource - * - * @return PrototypeConfigurator */ - final public function load($namespace, $resource) + final public function load(string $namespace, string $resource): PrototypeConfigurator { $this->__destruct(); @@ -87,13 +72,9 @@ abstract class AbstractServiceConfigurator extends AbstractConfigurator /** * Gets an already defined service definition. * - * @param string $id - * - * @return ServiceConfigurator - * * @throws ServiceNotFoundException if the service definition does not exist */ - final public function get($id) + final public function get(string $id): ServiceConfigurator { $this->__destruct(); @@ -101,14 +82,31 @@ abstract class AbstractServiceConfigurator extends AbstractConfigurator } /** - * Registers a service. - * - * @param string $id - * @param string|null $class - * - * @return ServiceConfigurator + * Removes an already defined service definition or alias. */ - final public function __invoke($id, $class = null) + final public function remove(string $id): ServicesConfigurator + { + $this->__destruct(); + + return $this->parent->remove($id); + } + + /** + * Registers a stack of decorator services. + * + * @param InlineServiceConfigurator[]|ReferenceConfigurator[] $services + */ + final public function stack(string $id, array $services): AliasConfigurator + { + $this->__destruct(); + + return $this->parent->stack($id, $services); + } + + /** + * Registers a service. + */ + final public function __invoke(string $id, string $class = null): ServiceConfigurator { $this->__destruct(); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/AliasConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/AliasConfigurator.php index cb00f58c04..650a9568df 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/AliasConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/AliasConfigurator.php @@ -18,10 +18,11 @@ use Symfony\Component\DependencyInjection\Alias; */ class AliasConfigurator extends AbstractServiceConfigurator { - const FACTORY = 'alias'; - + use Traits\DeprecateTrait; use Traits\PublicTrait; + public const FACTORY = 'alias'; + public function __construct(ServicesConfigurator $parent, Alias $alias) { $this->parent = $parent; diff --git a/lib/symfony/dependency-injection/Loader/Configurator/ClosureReferenceConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/ClosureReferenceConfigurator.php new file mode 100644 index 0000000000..ba83d91ef0 --- /dev/null +++ b/lib/symfony/dependency-injection/Loader/Configurator/ClosureReferenceConfigurator.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +class ClosureReferenceConfigurator extends ReferenceConfigurator +{ +} diff --git a/lib/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php index f2e5ccf8b0..ac6fdb6d00 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php @@ -11,11 +11,15 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\Component\Config\Loader\ParamConfigurator; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\ExpressionLanguage\Expression; @@ -24,112 +28,185 @@ use Symfony\Component\ExpressionLanguage\Expression; */ class ContainerConfigurator extends AbstractConfigurator { - const FACTORY = 'container'; + public const FACTORY = 'container'; private $container; private $loader; private $instanceof; private $path; private $file; + private $anonymousCount = 0; + private $env; - public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, $path, $file) + public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, string $path, string $file, string $env = null) { $this->container = $container; $this->loader = $loader; $this->instanceof = &$instanceof; $this->path = $path; $this->file = $file; + $this->env = $env; } - final public function extension($namespace, array $config) + final public function extension(string $namespace, array $config) { if (!$this->container->hasExtension($namespace)) { - $extensions = array_filter(array_map(function ($ext) { return $ext->getAlias(); }, $this->container->getExtensions())); + $extensions = array_filter(array_map(function (ExtensionInterface $ext) { return $ext->getAlias(); }, $this->container->getExtensions())); throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in "%s"). Looked for namespace "%s", found "%s".', $namespace, $this->file, $namespace, $extensions ? implode('", "', $extensions) : 'none')); } $this->container->loadFromExtension($namespace, static::processValue($config)); } - final public function import($resource, $type = null, $ignoreErrors = false) + final public function import(string $resource, string $type = null, $ignoreErrors = false) { $this->loader->setCurrentDir(\dirname($this->path)); $this->loader->import($resource, $type, $ignoreErrors, $this->file); } - /** - * @return ParametersConfigurator - */ - final public function parameters() + final public function parameters(): ParametersConfigurator { return new ParametersConfigurator($this->container); } - /** - * @return ServicesConfigurator - */ - final public function services() + final public function services(): ServicesConfigurator { - return new ServicesConfigurator($this->container, $this->loader, $this->instanceof); + return new ServicesConfigurator($this->container, $this->loader, $this->instanceof, $this->path, $this->anonymousCount); } + + /** + * Get the current environment to be able to write conditional configuration. + */ + final public function env(): ?string + { + return $this->env; + } + + /** + * @return static + */ + final public function withPath(string $path): self + { + $clone = clone $this; + $clone->path = $clone->file = $path; + $clone->loader->setCurrentDir(\dirname($path)); + + return $clone; + } +} + +/** + * Creates a parameter. + */ +function param(string $name): ParamConfigurator +{ + return new ParamConfigurator($name); } /** * Creates a service reference. * - * @param string $id - * - * @return ReferenceConfigurator + * @deprecated since Symfony 5.1, use service() instead. */ -function ref($id) +function ref(string $id): ReferenceConfigurator { + trigger_deprecation('symfony/dependency-injection', '5.1', '"%s()" is deprecated, use "service()" instead.', __FUNCTION__); + return new ReferenceConfigurator($id); } +/** + * Creates a reference to a service. + */ +function service(string $serviceId): ReferenceConfigurator +{ + return new ReferenceConfigurator($serviceId); +} + /** * Creates an inline service. * - * @param string|null $class - * - * @return InlineServiceConfigurator + * @deprecated since Symfony 5.1, use inline_service() instead. */ -function inline($class = null) +function inline(string $class = null): InlineServiceConfigurator +{ + trigger_deprecation('symfony/dependency-injection', '5.1', '"%s()" is deprecated, use "inline_service()" instead.', __FUNCTION__); + + return new InlineServiceConfigurator(new Definition($class)); +} + +/** + * Creates an inline service. + */ +function inline_service(string $class = null): InlineServiceConfigurator { return new InlineServiceConfigurator(new Definition($class)); } +/** + * Creates a service locator. + * + * @param ReferenceConfigurator[] $values + */ +function service_locator(array $values): ServiceLocatorArgument +{ + return new ServiceLocatorArgument(AbstractConfigurator::processValue($values, true)); +} + /** * Creates a lazy iterator. * * @param ReferenceConfigurator[] $values - * - * @return IteratorArgument */ -function iterator(array $values) +function iterator(array $values): IteratorArgument { return new IteratorArgument(AbstractConfigurator::processValue($values, true)); } /** * Creates a lazy iterator by tag name. - * - * @param string $tag - * - * @return TaggedIteratorArgument */ -function tagged($tag) +function tagged_iterator(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, string $defaultPriorityMethod = null): TaggedIteratorArgument { - return new TaggedIteratorArgument($tag); + return new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod); +} + +/** + * Creates a service locator by tag name. + */ +function tagged_locator(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, string $defaultPriorityMethod = null): ServiceLocatorArgument +{ + return new ServiceLocatorArgument(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, true, $defaultPriorityMethod)); } /** * Creates an expression. - * - * @param string $expression an expression - * - * @return Expression */ -function expr($expression) +function expr(string $expression): Expression { return new Expression($expression); } + +/** + * Creates an abstract argument. + */ +function abstract_arg(string $description): AbstractArgument +{ + return new AbstractArgument($description); +} + +/** + * Creates an environment variable reference. + */ +function env(string $name): EnvConfigurator +{ + return new EnvConfigurator($name); +} + +/** + * Creates a closure service reference. + */ +function service_closure(string $serviceId): ClosureReferenceConfigurator +{ + return new ClosureReferenceConfigurator($serviceId); +} diff --git a/lib/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.php index 662ba95d1b..e0b42750d5 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/DefaultsConfigurator.php @@ -11,40 +11,45 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; /** * @author Nicolas Grekas - * - * @method InstanceofConfigurator instanceof(string $fqcn) */ class DefaultsConfigurator extends AbstractServiceConfigurator { - const FACTORY = 'defaults'; - use Traits\AutoconfigureTrait; use Traits\AutowireTrait; use Traits\BindTrait; use Traits\PublicTrait; + public const FACTORY = 'defaults'; + + private $path; + + public function __construct(ServicesConfigurator $parent, Definition $definition, string $path = null) + { + parent::__construct($parent, $definition, null, []); + + $this->path = $path; + } + /** * Adds a tag for this definition. * - * @param string $name The tag name - * @param array $attributes An array of attributes - * * @return $this * * @throws InvalidArgumentException when an invalid tag name or attribute is provided */ - final public function tag($name, array $attributes = []) + final public function tag(string $name, array $attributes = []): self { - if (!\is_string($name) || '' === $name) { + if ('' === $name) { throw new InvalidArgumentException('The tag name in "_defaults" must be a non-empty string.'); } foreach ($attributes as $attribute => $value) { - if (!is_scalar($value) && null !== $value) { + if (null !== $value && !\is_scalar($value)) { throw new InvalidArgumentException(sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type.', $name, $attribute)); } } @@ -56,12 +61,8 @@ class DefaultsConfigurator extends AbstractServiceConfigurator /** * Defines an instanceof-conditional to be applied to following service definitions. - * - * @param string $fqcn - * - * @return InstanceofConfigurator */ - final protected function setInstanceof($fqcn) + final public function instanceof(string $fqcn): InstanceofConfigurator { return $this->parent->instanceof($fqcn); } diff --git a/lib/symfony/dependency-injection/Loader/Configurator/EnvConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/EnvConfigurator.php new file mode 100644 index 0000000000..d1864f564a --- /dev/null +++ b/lib/symfony/dependency-injection/Loader/Configurator/EnvConfigurator.php @@ -0,0 +1,224 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Config\Loader\ParamConfigurator; + +class EnvConfigurator extends ParamConfigurator +{ + /** + * @var string[] + */ + private $stack; + + public function __construct(string $name) + { + $this->stack = explode(':', $name); + } + + public function __toString(): string + { + return '%env('.implode(':', $this->stack).')%'; + } + + /** + * @return $this + */ + public function __call(string $name, array $arguments): self + { + $processor = strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], '\1_\2', $name)); + + $this->custom($processor, ...$arguments); + + return $this; + } + + /** + * @return $this + */ + public function custom(string $processor, ...$args): self + { + array_unshift($this->stack, $processor, ...$args); + + return $this; + } + + /** + * @return $this + */ + public function base64(): self + { + array_unshift($this->stack, 'base64'); + + return $this; + } + + /** + * @return $this + */ + public function bool(): self + { + array_unshift($this->stack, 'bool'); + + return $this; + } + + /** + * @return $this + */ + public function not(): self + { + array_unshift($this->stack, 'not'); + + return $this; + } + + /** + * @return $this + */ + public function const(): self + { + array_unshift($this->stack, 'const'); + + return $this; + } + + /** + * @return $this + */ + public function csv(): self + { + array_unshift($this->stack, 'csv'); + + return $this; + } + + /** + * @return $this + */ + public function file(): self + { + array_unshift($this->stack, 'file'); + + return $this; + } + + /** + * @return $this + */ + public function float(): self + { + array_unshift($this->stack, 'float'); + + return $this; + } + + /** + * @return $this + */ + public function int(): self + { + array_unshift($this->stack, 'int'); + + return $this; + } + + /** + * @return $this + */ + public function json(): self + { + array_unshift($this->stack, 'json'); + + return $this; + } + + /** + * @return $this + */ + public function key(string $key): self + { + array_unshift($this->stack, 'key', $key); + + return $this; + } + + /** + * @return $this + */ + public function url(): self + { + array_unshift($this->stack, 'url'); + + return $this; + } + + /** + * @return $this + */ + public function queryString(): self + { + array_unshift($this->stack, 'query_string'); + + return $this; + } + + /** + * @return $this + */ + public function resolve(): self + { + array_unshift($this->stack, 'resolve'); + + return $this; + } + + /** + * @return $this + */ + public function default(string $fallbackParam): self + { + array_unshift($this->stack, 'default', $fallbackParam); + + return $this; + } + + /** + * @return $this + */ + public function string(): self + { + array_unshift($this->stack, 'string'); + + return $this; + } + + /** + * @return $this + */ + public function trim(): self + { + array_unshift($this->stack, 'trim'); + + return $this; + } + + /** + * @return $this + */ + public function require(): self + { + array_unshift($this->stack, 'require'); + + return $this; + } +} diff --git a/lib/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.php index 362b374e55..da90a0a4ce 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/InlineServiceConfigurator.php @@ -18,17 +18,24 @@ use Symfony\Component\DependencyInjection\Definition; */ class InlineServiceConfigurator extends AbstractConfigurator { - const FACTORY = 'inline'; - use Traits\ArgumentTrait; use Traits\AutowireTrait; use Traits\BindTrait; + use Traits\CallTrait; + use Traits\ConfiguratorTrait; use Traits\FactoryTrait; use Traits\FileTrait; use Traits\LazyTrait; use Traits\ParentTrait; + use Traits\PropertyTrait; use Traits\TagTrait; + public const FACTORY = 'service'; + + private $id = '[inline]'; + private $allowParent = true; + private $path = null; + public function __construct(Definition $definition) { $this->definition = $definition; diff --git a/lib/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.php index 629874d19c..fbba62304d 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/InstanceofConfigurator.php @@ -11,16 +11,15 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\Component\DependencyInjection\Definition; + /** * @author Nicolas Grekas - * - * @method InstanceofConfigurator instanceof(string $fqcn) */ class InstanceofConfigurator extends AbstractServiceConfigurator { - const FACTORY = 'instanceof'; - use Traits\AutowireTrait; + use Traits\BindTrait; use Traits\CallTrait; use Traits\ConfiguratorTrait; use Traits\LazyTrait; @@ -29,14 +28,21 @@ class InstanceofConfigurator extends AbstractServiceConfigurator use Traits\ShareTrait; use Traits\TagTrait; + public const FACTORY = 'instanceof'; + + private $path; + + public function __construct(ServicesConfigurator $parent, Definition $definition, string $id, string $path = null) + { + parent::__construct($parent, $definition, $id, []); + + $this->path = $path; + } + /** * Defines an instanceof-conditional to be applied to following service definitions. - * - * @param string $fqcn - * - * @return self */ - final protected function setInstanceof($fqcn) + final public function instanceof(string $fqcn): self { return $this->parent->instanceof($fqcn); } diff --git a/lib/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.php index 9585b1a4b5..f0cf177d7b 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/ParametersConfigurator.php @@ -12,13 +12,15 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\ExpressionLanguage\Expression; /** * @author Nicolas Grekas */ class ParametersConfigurator extends AbstractConfigurator { - const FACTORY = 'parameters'; + public const FACTORY = 'parameters'; private $container; @@ -30,13 +32,14 @@ class ParametersConfigurator extends AbstractConfigurator /** * Creates a parameter. * - * @param string $name - * @param mixed $value - * * @return $this */ - final public function set($name, $value) + final public function set(string $name, $value): self { + if ($value instanceof Expression) { + throw new InvalidArgumentException(sprintf('Using an expression in parameter "%s" is not allowed.', $name)); + } + $this->container->setParameter($name, static::processValue($value, true)); return $this; @@ -45,12 +48,9 @@ class ParametersConfigurator extends AbstractConfigurator /** * Creates a parameter. * - * @param string $name - * @param mixed $value - * * @return $this */ - final public function __invoke($name, $value) + final public function __invoke(string $name, $value): self { return $this->set($name, $value); } diff --git a/lib/symfony/dependency-injection/Loader/Configurator/PrototypeConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/PrototypeConfigurator.php index 3d844798d4..e1b3702aaf 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/PrototypeConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/PrototypeConfigurator.php @@ -19,8 +19,6 @@ use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; */ class PrototypeConfigurator extends AbstractServiceConfigurator { - const FACTORY = 'load'; - use Traits\AbstractTrait; use Traits\ArgumentTrait; use Traits\AutoconfigureTrait; @@ -37,12 +35,14 @@ class PrototypeConfigurator extends AbstractServiceConfigurator use Traits\ShareTrait; use Traits\TagTrait; + public const FACTORY = 'load'; + private $loader; private $resource; - private $exclude; + private $excludes; private $allowParent; - public function __construct(ServicesConfigurator $parent, PhpFileLoader $loader, Definition $defaults, $namespace, $resource, $allowParent) + public function __construct(ServicesConfigurator $parent, PhpFileLoader $loader, Definition $defaults, string $namespace, string $resource, bool $allowParent) { $definition = new Definition(); if (!$defaults->isPublic() || !$defaults->isPrivate()) { @@ -66,21 +66,21 @@ class PrototypeConfigurator extends AbstractServiceConfigurator parent::__destruct(); if ($this->loader) { - $this->loader->registerClasses($this->definition, $this->id, $this->resource, $this->exclude); + $this->loader->registerClasses($this->definition, $this->id, $this->resource, $this->excludes); } $this->loader = null; } /** - * Excludes files from registration using a glob pattern. + * Excludes files from registration using glob patterns. * - * @param string $exclude + * @param string[]|string $excludes * * @return $this */ - final public function exclude($exclude) + final public function exclude($excludes): self { - $this->exclude = $exclude; + $this->excludes = (array) $excludes; return $this; } diff --git a/lib/symfony/dependency-injection/Loader/Configurator/ReferenceConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/ReferenceConfigurator.php index 1585c0872a..fa042538ce 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/ReferenceConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/ReferenceConfigurator.php @@ -24,7 +24,7 @@ class ReferenceConfigurator extends AbstractConfigurator /** @internal */ protected $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; - public function __construct($id) + public function __construct(string $id) { $this->id = $id; } @@ -32,7 +32,7 @@ class ReferenceConfigurator extends AbstractConfigurator /** * @return $this */ - final public function ignoreOnInvalid() + final public function ignoreOnInvalid(): self { $this->invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; @@ -42,7 +42,7 @@ class ReferenceConfigurator extends AbstractConfigurator /** * @return $this */ - final public function nullOnInvalid() + final public function nullOnInvalid(): self { $this->invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; @@ -52,13 +52,16 @@ class ReferenceConfigurator extends AbstractConfigurator /** * @return $this */ - final public function ignoreOnUninitialized() + final public function ignoreOnUninitialized(): self { $this->invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE; return $this; } + /** + * @return string + */ public function __toString() { return $this->id; diff --git a/lib/symfony/dependency-injection/Loader/Configurator/ServiceConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/ServiceConfigurator.php index 897dedaac5..932ecd3515 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/ServiceConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/ServiceConfigurator.php @@ -11,7 +11,6 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; -use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; @@ -20,8 +19,6 @@ use Symfony\Component\DependencyInjection\Definition; */ class ServiceConfigurator extends AbstractServiceConfigurator { - const FACTORY = 'services'; - use Traits\AbstractTrait; use Traits\ArgumentTrait; use Traits\AutoconfigureTrait; @@ -42,29 +39,34 @@ class ServiceConfigurator extends AbstractServiceConfigurator use Traits\SyntheticTrait; use Traits\TagTrait; + public const FACTORY = 'services'; + private $container; private $instanceof; private $allowParent; + private $path; + private $destructed = false; - public function __construct(ContainerBuilder $container, array $instanceof, $allowParent, ServicesConfigurator $parent, Definition $definition, $id, array $defaultTags) + public function __construct(ContainerBuilder $container, array $instanceof, bool $allowParent, ServicesConfigurator $parent, Definition $definition, ?string $id, array $defaultTags, string $path = null) { $this->container = $container; $this->instanceof = $instanceof; $this->allowParent = $allowParent; + $this->path = $path; parent::__construct($parent, $definition, $id, $defaultTags); } public function __destruct() { + if ($this->destructed) { + return; + } + $this->destructed = true; + parent::__destruct(); $this->container->removeBindings($this->id); - - if (!$this->definition instanceof ChildDefinition) { - $this->container->setDefinition($this->id, $this->definition->setInstanceofConditionals($this->instanceof)); - } else { - $this->container->setDefinition($this->id, $this->definition); - } + $this->container->setDefinition($this->id, $this->definition->setInstanceofConditionals($this->instanceof)); } } diff --git a/lib/symfony/dependency-injection/Loader/Configurator/ServicesConfigurator.php b/lib/symfony/dependency-injection/Loader/Configurator/ServicesConfigurator.php index b6ccbc63b4..388251e26a 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/ServicesConfigurator.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/ServicesConfigurator.php @@ -15,93 +15,104 @@ use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; /** * @author Nicolas Grekas - * - * @method InstanceofConfigurator instanceof($fqcn) */ class ServicesConfigurator extends AbstractConfigurator { - const FACTORY = 'services'; + public const FACTORY = 'services'; private $defaults; private $container; private $loader; private $instanceof; + private $path; + private $anonymousHash; + private $anonymousCount; - public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof) + public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, string $path = null, int &$anonymousCount = 0) { $this->defaults = new Definition(); $this->container = $container; $this->loader = $loader; $this->instanceof = &$instanceof; + $this->path = $path; + $this->anonymousHash = ContainerBuilder::hash($path ?: mt_rand()); + $this->anonymousCount = &$anonymousCount; $instanceof = []; } /** * Defines a set of defaults for following service definitions. - * - * @return DefaultsConfigurator */ - final public function defaults() + final public function defaults(): DefaultsConfigurator { - return new DefaultsConfigurator($this, $this->defaults = new Definition()); + return new DefaultsConfigurator($this, $this->defaults = new Definition(), $this->path); } /** * Defines an instanceof-conditional to be applied to following service definitions. - * - * @param string $fqcn - * - * @return InstanceofConfigurator */ - final protected function setInstanceof($fqcn) + final public function instanceof(string $fqcn): InstanceofConfigurator { $this->instanceof[$fqcn] = $definition = new ChildDefinition(''); - return new InstanceofConfigurator($this, $definition, $fqcn); + return new InstanceofConfigurator($this, $definition, $fqcn, $this->path); } /** * Registers a service. * - * @param string $id - * @param string|null $class - * - * @return ServiceConfigurator + * @param string|null $id The service id, or null to create an anonymous service + * @param string|null $class The class of the service, or null when $id is also the class name */ - final public function set($id, $class = null) + final public function set(?string $id, string $class = null): ServiceConfigurator { $defaults = $this->defaults; - $allowParent = !$defaults->getChanges() && empty($this->instanceof); - $definition = new Definition(); - if (!$defaults->isPublic() || !$defaults->isPrivate()) { + + if (null === $id) { + if (!$class) { + throw new \LogicException('Anonymous services must have a class name.'); + } + + $id = sprintf('.%d_%s', ++$this->anonymousCount, preg_replace('/^.*\\\\/', '', $class).'~'.$this->anonymousHash); + } elseif (!$defaults->isPublic() || !$defaults->isPrivate()) { $definition->setPublic($defaults->isPublic() && !$defaults->isPrivate()); } + $definition->setAutowired($defaults->isAutowired()); $definition->setAutoconfigured($defaults->isAutoconfigured()); // deep clone, to avoid multiple process of the same instance in the passes $definition->setBindings(unserialize(serialize($defaults->getBindings()))); $definition->setChanges([]); - $configurator = new ServiceConfigurator($this->container, $this->instanceof, $allowParent, $this, $definition, $id, $defaults->getTags()); + $configurator = new ServiceConfigurator($this->container, $this->instanceof, true, $this, $definition, $id, $defaults->getTags(), $this->path); return null !== $class ? $configurator->class($class) : $configurator; } /** - * Creates an alias. + * Removes an already defined service definition or alias. * - * @param string $id - * @param string $referencedId - * - * @return AliasConfigurator + * @return $this */ - final public function alias($id, $referencedId) + final public function remove(string $id): self + { + $this->container->removeDefinition($id); + $this->container->removeAlias($id); + + return $this; + } + + /** + * Creates an alias. + */ + final public function alias(string $id, string $referencedId): AliasConfigurator { $ref = static::processValue($referencedId, true); $alias = new Alias((string) $ref); @@ -115,46 +126,67 @@ class ServicesConfigurator extends AbstractConfigurator /** * Registers a PSR-4 namespace using a glob pattern. - * - * @param string $namespace - * @param string $resource - * - * @return PrototypeConfigurator */ - final public function load($namespace, $resource) + final public function load(string $namespace, string $resource): PrototypeConfigurator { - $allowParent = !$this->defaults->getChanges() && empty($this->instanceof); - - return new PrototypeConfigurator($this, $this->loader, $this->defaults, $namespace, $resource, $allowParent); + return new PrototypeConfigurator($this, $this->loader, $this->defaults, $namespace, $resource, true); } /** * Gets an already defined service definition. * - * @param string $id - * - * @return ServiceConfigurator - * * @throws ServiceNotFoundException if the service definition does not exist */ - final public function get($id) + final public function get(string $id): ServiceConfigurator { - $allowParent = !$this->defaults->getChanges() && empty($this->instanceof); $definition = $this->container->getDefinition($id); - return new ServiceConfigurator($this->container, $definition->getInstanceofConditionals(), $allowParent, $this, $definition, $id, []); + return new ServiceConfigurator($this->container, $definition->getInstanceofConditionals(), true, $this, $definition, $id, []); + } + + /** + * Registers a stack of decorator services. + * + * @param InlineServiceConfigurator[]|ReferenceConfigurator[] $services + */ + final public function stack(string $id, array $services): AliasConfigurator + { + foreach ($services as $i => $service) { + if ($service instanceof InlineServiceConfigurator) { + $definition = $service->definition->setInstanceofConditionals($this->instanceof); + + $changes = $definition->getChanges(); + $definition->setAutowired((isset($changes['autowired']) ? $definition : $this->defaults)->isAutowired()); + $definition->setAutoconfigured((isset($changes['autoconfigured']) ? $definition : $this->defaults)->isAutoconfigured()); + $definition->setBindings(array_merge($this->defaults->getBindings(), $definition->getBindings())); + $definition->setChanges($changes); + + $services[$i] = $definition; + } elseif (!$service instanceof ReferenceConfigurator) { + throw new InvalidArgumentException(sprintf('"%s()" expects a list of definitions as returned by "%s()" or "%s()", "%s" given at index "%s" for service "%s".', __METHOD__, InlineServiceConfigurator::FACTORY, ReferenceConfigurator::FACTORY, $service instanceof AbstractConfigurator ? $service::FACTORY.'()' : get_debug_type($service), $i, $id)); + } + } + + $alias = $this->alias($id, ''); + $alias->definition = $this->set($id) + ->parent('') + ->args($services) + ->tag('container.stack') + ->definition; + + return $alias; } /** * Registers a service. - * - * @param string $id - * @param string|null $class - * - * @return ServiceConfigurator */ - final public function __invoke($id, $class = null) + final public function __invoke(string $id, string $class = null): ServiceConfigurator { return $this->set($id, $class); } + + public function __destruct() + { + $this->loader->registerAliasesForSinglyImplementedInterfaces(); + } } diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/AbstractTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/AbstractTrait.php index f69a7a5be1..82ba21d7bd 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/AbstractTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/AbstractTrait.php @@ -11,20 +11,15 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; -/** - * @method $this abstract(bool $abstract = true) - */ trait AbstractTrait { /** * Whether this definition is abstract, that means it merely serves as a * template for other definitions. * - * @param bool $abstract - * * @return $this */ - final protected function setAbstract($abstract = true) + final public function abstract(bool $abstract = true): self { $this->definition->setAbstract($abstract); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ArgumentTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ArgumentTrait.php index 7ec8c51d47..5c9a475609 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ArgumentTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ArgumentTrait.php @@ -16,11 +16,9 @@ trait ArgumentTrait /** * Sets the arguments to pass to the service constructor/factory method. * - * @param array $arguments An array of arguments - * * @return $this */ - final public function args(array $arguments) + final public function args(array $arguments): self { $this->definition->setArguments(static::processValue($arguments, true)); @@ -35,7 +33,7 @@ trait ArgumentTrait * * @return $this */ - final public function arg($key, $value) + final public function arg($key, $value): self { $this->definition->setArgument($key, static::processValue($value, true)); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/AutoconfigureTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/AutoconfigureTrait.php index 42a692353e..9eab22cfef 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/AutoconfigureTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/AutoconfigureTrait.php @@ -11,7 +11,6 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; -use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; trait AutoconfigureTrait @@ -19,17 +18,12 @@ trait AutoconfigureTrait /** * Sets whether or not instanceof conditionals should be prepended with a global set. * - * @param bool $autoconfigured - * * @return $this * * @throws InvalidArgumentException when a parent is already set */ - final public function autoconfigure($autoconfigured = true) + final public function autoconfigure(bool $autoconfigured = true): self { - if ($autoconfigured && $this->definition instanceof ChildDefinition) { - throw new InvalidArgumentException(sprintf('The service "%s" cannot have a "parent" and also have "autoconfigure". Try disabling autoconfiguration for the service.', $this->id)); - } $this->definition->setAutoconfigured($autoconfigured); return $this; diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/AutowireTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/AutowireTrait.php index 3d4b2e854b..2837a0201d 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/AutowireTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/AutowireTrait.php @@ -16,11 +16,9 @@ trait AutowireTrait /** * Enables/disables autowiring. * - * @param bool $autowired - * * @return $this */ - final public function autowire($autowired = true) + final public function autowire(bool $autowired = true): self { $this->definition->setAutowired($autowired); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/BindTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/BindTrait.php index 4511ed659d..3021e07088 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/BindTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/BindTrait.php @@ -11,8 +11,9 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; -use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; -use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Argument\BoundArgument; +use Symfony\Component\DependencyInjection\Loader\Configurator\DefaultsConfigurator; +use Symfony\Component\DependencyInjection\Loader\Configurator\InstanceofConfigurator; trait BindTrait { @@ -23,19 +24,17 @@ trait BindTrait * injected in the matching parameters (of the constructor, of methods * called and of controller actions). * - * @param string $nameOrFqcn A parameter name with its "$" prefix, or a FQCN + * @param string $nameOrFqcn A parameter name with its "$" prefix, or an FQCN * @param mixed $valueOrRef The value or reference to bind * * @return $this */ - final public function bind($nameOrFqcn, $valueOrRef) + final public function bind(string $nameOrFqcn, $valueOrRef): self { $valueOrRef = static::processValue($valueOrRef, true); - if (isset($nameOrFqcn[0]) && '$' !== $nameOrFqcn[0] && !$valueOrRef instanceof Reference) { - throw new InvalidArgumentException(sprintf('Invalid binding for service "%s": named arguments must start with a "$", and FQCN must map to references. Neither applies to binding "%s".', $this->id, $nameOrFqcn)); - } $bindings = $this->definition->getBindings(); - $bindings[$nameOrFqcn] = $valueOrRef; + $type = $this instanceof DefaultsConfigurator ? BoundArgument::DEFAULTS_BINDING : ($this instanceof InstanceofConfigurator ? BoundArgument::INSTANCEOF_BINDING : BoundArgument::SERVICE_BINDING); + $bindings[$nameOrFqcn] = new BoundArgument($valueOrRef, true, $type, $this->path ?? null); $this->definition->setBindings($bindings); return $this; diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/CallTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/CallTrait.php index 8e6b17a19d..28f92d274f 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/CallTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/CallTrait.php @@ -18,16 +18,17 @@ trait CallTrait /** * Adds a method to call after service initialization. * - * @param string $method The method name to call - * @param array $arguments An array of arguments to pass to the method call + * @param string $method The method name to call + * @param array $arguments An array of arguments to pass to the method call + * @param bool $returnsClone Whether the call returns the service instance or not * * @return $this * * @throws InvalidArgumentException on empty $method param */ - final public function call($method, array $arguments = []) + final public function call(string $method, array $arguments = [], bool $returnsClone = false): self { - $this->definition->addMethodCall($method, static::processValue($arguments, true)); + $this->definition->addMethodCall($method, static::processValue($arguments, true), $returnsClone); return $this; } diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ClassTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ClassTrait.php index ae5b1c0a3d..20da791aaa 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ClassTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ClassTrait.php @@ -11,19 +11,14 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; -/** - * @method $this class(string $class) - */ trait ClassTrait { /** * Sets the service class. * - * @param string $class The service class - * * @return $this */ - final protected function setClass($class) + final public function class(?string $class): self { $this->definition->setClass($class); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ConfiguratorTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ConfiguratorTrait.php index a38283b0e7..25d363c9a6 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ConfiguratorTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ConfiguratorTrait.php @@ -20,7 +20,7 @@ trait ConfiguratorTrait * * @return $this */ - final public function configurator($configurator) + final public function configurator($configurator): self { $this->definition->setConfigurator(static::processValue($configurator, true)); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/DecorateTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/DecorateTrait.php index 173ad15f06..b3a1ae1b54 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/DecorateTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/DecorateTrait.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; trait DecorateTrait @@ -18,17 +19,15 @@ trait DecorateTrait /** * Sets the service that this service is decorating. * - * @param string|null $id The decorated service id, use null to remove decoration - * @param string|null $renamedId The new decorated service id - * @param int $priority The priority of decoration + * @param string|null $id The decorated service id, use null to remove decoration * * @return $this * * @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals */ - final public function decorate($id, $renamedId = null, $priority = 0) + final public function decorate(?string $id, string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): self { - $this->definition->setDecoratedService($id, $renamedId, $priority); + $this->definition->setDecoratedService($id, $renamedId, $priority, $invalidBehavior); return $this; } diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/DeprecateTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/DeprecateTrait.php index b14a6557ee..ea77e456d8 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/DeprecateTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/DeprecateTrait.php @@ -18,15 +18,30 @@ trait DeprecateTrait /** * Whether this definition is deprecated, that means it should not be called anymore. * - * @param string $template Template message to use if the definition is deprecated + * @param string $package The name of the composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The deprecation message to use * * @return $this * * @throws InvalidArgumentException when the message template is invalid */ - final public function deprecate($template = null) + final public function deprecate(/* string $package, string $version, string $message */): self { - $this->definition->setDeprecated(true, $template); + $args = \func_get_args(); + $package = $version = $message = ''; + + if (\func_num_args() < 3) { + trigger_deprecation('symfony/dependency-injection', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__); + + $message = (string) ($args[0] ?? null); + } else { + $package = (string) $args[0]; + $version = (string) $args[1]; + $message = (string) $args[2]; + } + + $this->definition->setDeprecated($package, $version, $message); return $this; } diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/FactoryTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/FactoryTrait.php index 0d50fb747f..1286ba4c1e 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/FactoryTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/FactoryTrait.php @@ -12,22 +12,23 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator; trait FactoryTrait { /** * Sets a factory. * - * @param string|array $factory A PHP callable reference + * @param string|array|ReferenceConfigurator $factory A PHP callable reference * * @return $this */ - final public function factory($factory) + final public function factory($factory): self { if (\is_string($factory) && 1 === substr_count($factory, ':')) { $factoryParts = explode(':', $factory); - throw new InvalidArgumentException(sprintf('Invalid factory "%s": the `service:method` notation is not available when using PHP-based DI configuration. Use "[ref(\'%s\'), \'%s\']" instead.', $factory, $factoryParts[0], $factoryParts[1])); + throw new InvalidArgumentException(sprintf('Invalid factory "%s": the "service:method" notation is not available when using PHP-based DI configuration. Use "[service(\'%s\'), \'%s\']" instead.', $factory, $factoryParts[0], $factoryParts[1])); } $this->definition->setFactory(static::processValue($factory, true)); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/FileTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/FileTrait.php index 895f5304c3..5f42aef8fd 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/FileTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/FileTrait.php @@ -16,11 +16,9 @@ trait FileTrait /** * Sets a file to require before creating the service. * - * @param string $file A full pathname to include - * * @return $this */ - final public function file($file) + final public function file(string $file): self { $this->definition->setFile($file); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/LazyTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/LazyTrait.php index d7ea8b27f5..2829defb59 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/LazyTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/LazyTrait.php @@ -16,13 +16,16 @@ trait LazyTrait /** * Sets the lazy flag of this service. * - * @param bool $lazy + * @param bool|string $lazy A FQCN to derivate the lazy proxy from or `true` to make it extend from the definition's class * * @return $this */ - final public function lazy($lazy = true) + final public function lazy($lazy = true): self { - $this->definition->setLazy($lazy); + $this->definition->setLazy((bool) $lazy); + if (\is_string($lazy)) { + $this->definition->addTag('proxy', ['interface' => $lazy]); + } return $this; } diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ParentTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ParentTrait.php index 43f1223e32..37194e50e6 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ParentTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ParentTrait.php @@ -14,21 +14,16 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; -/** - * @method $this parent(string $parent) - */ trait ParentTrait { /** * Sets the Definition to inherit from. * - * @param string $parent - * * @return $this * * @throws InvalidArgumentException when parent cannot be set */ - final protected function setParent($parent) + final public function parent(string $parent): self { if (!$this->allowParent) { throw new InvalidArgumentException(sprintf('A parent cannot be defined when either "_instanceof" or "_defaults" are also defined for service prototype "%s".', $this->id)); @@ -36,10 +31,6 @@ trait ParentTrait if ($this->definition instanceof ChildDefinition) { $this->definition->setParent($parent); - } elseif ($this->definition->isAutoconfigured()) { - throw new InvalidArgumentException(sprintf('The service "%s" cannot have a "parent" and also have "autoconfigure". Try disabling autoconfiguration for the service.', $this->id)); - } elseif ($this->definition->getBindings()) { - throw new InvalidArgumentException(sprintf('The service "%s" cannot have a "parent" and also "bind" arguments.', $this->id)); } else { // cast Definition to ChildDefinition $definition = serialize($this->definition); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/PropertyTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/PropertyTrait.php index d5d938708e..10fdcfb82f 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/PropertyTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/PropertyTrait.php @@ -16,12 +16,9 @@ trait PropertyTrait /** * Sets a specific property. * - * @param string $name - * @param mixed $value - * * @return $this */ - final public function property($name, $value) + final public function property(string $name, $value): self { $this->definition->setProperty($name, static::processValue($value, true)); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/PublicTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/PublicTrait.php index 8f7f79f1cc..f15756c1b5 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/PublicTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/PublicTrait.php @@ -11,16 +11,12 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits; -/** - * @method $this public() - * @method $this private() - */ trait PublicTrait { /** * @return $this */ - final protected function setPublic() + final public function public(): self { $this->definition->setPublic(true); @@ -30,7 +26,7 @@ trait PublicTrait /** * @return $this */ - final protected function setPrivate() + final public function private(): self { $this->definition->setPublic(false); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ShareTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ShareTrait.php index 1c2f97b597..16fde0f294 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/ShareTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/ShareTrait.php @@ -16,11 +16,9 @@ trait ShareTrait /** * Sets if the service must be shared or not. * - * @param bool $shared Whether the service must be shared or not - * * @return $this */ - final public function share($shared = true) + final public function share(bool $shared = true): self { $this->definition->setShared($shared); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/SyntheticTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/SyntheticTrait.php index 81eceff43d..cb08b1133a 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/SyntheticTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/SyntheticTrait.php @@ -17,11 +17,9 @@ trait SyntheticTrait * Sets whether this definition is synthetic, that is not constructed by the * container, but dynamically injected. * - * @param bool $synthetic - * * @return $this */ - final public function synthetic($synthetic = true) + final public function synthetic(bool $synthetic = true): self { $this->definition->setSynthetic($synthetic); diff --git a/lib/symfony/dependency-injection/Loader/Configurator/Traits/TagTrait.php b/lib/symfony/dependency-injection/Loader/Configurator/Traits/TagTrait.php index d17339f88b..ba9f8afa90 100644 --- a/lib/symfony/dependency-injection/Loader/Configurator/Traits/TagTrait.php +++ b/lib/symfony/dependency-injection/Loader/Configurator/Traits/TagTrait.php @@ -18,19 +18,16 @@ trait TagTrait /** * Adds a tag for this definition. * - * @param string $name The tag name - * @param array $attributes An array of attributes - * * @return $this */ - final public function tag($name, array $attributes = []) + final public function tag(string $name, array $attributes = []): self { - if (!\is_string($name) || '' === $name) { + if ('' === $name) { throw new InvalidArgumentException(sprintf('The tag name for service "%s" must be a non-empty string.', $this->id)); } foreach ($attributes as $attribute => $value) { - if (!is_scalar($value) && null !== $value) { + if (!\is_scalar($value) && null !== $value) { throw new InvalidArgumentException(sprintf('A tag attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s".', $this->id, $name, $attribute)); } } diff --git a/lib/symfony/dependency-injection/Loader/DirectoryLoader.php b/lib/symfony/dependency-injection/Loader/DirectoryLoader.php index a57cac3b5e..b4e9a5917c 100644 --- a/lib/symfony/dependency-injection/Loader/DirectoryLoader.php +++ b/lib/symfony/dependency-injection/Loader/DirectoryLoader.php @@ -21,7 +21,7 @@ class DirectoryLoader extends FileLoader /** * {@inheritdoc} */ - public function load($file, $type = null) + public function load($file, string $type = null) { $file = rtrim($file, '/'); $path = $this->locator->locate($file); @@ -38,17 +38,19 @@ class DirectoryLoader extends FileLoader $this->import($dir, null, false, $path); } } + + return null; } /** * {@inheritdoc} */ - public function supports($resource, $type = null) + public function supports($resource, string $type = null) { if ('directory' === $type) { return true; } - return null === $type && \is_string($resource) && '/' === substr($resource, -1); + return null === $type && \is_string($resource) && str_ends_with($resource, '/'); } } diff --git a/lib/symfony/dependency-injection/Loader/FileLoader.php b/lib/symfony/dependency-injection/Loader/FileLoader.php index 749dd4d06b..f5f78e30f0 100644 --- a/lib/symfony/dependency-injection/Loader/FileLoader.php +++ b/lib/symfony/dependency-injection/Loader/FileLoader.php @@ -11,10 +11,15 @@ namespace Symfony\Component\DependencyInjection\Loader; +use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException; +use Symfony\Component\Config\Exception\LoaderLoadException; use Symfony\Component\Config\FileLocatorInterface; use Symfony\Component\Config\Loader\FileLoader as BaseFileLoader; +use Symfony\Component\Config\Loader\Loader; use Symfony\Component\Config\Resource\GlobResource; +use Symfony\Component\DependencyInjection\Attribute\When; use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\RegisterAutoconfigureAttributesPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; @@ -26,43 +31,98 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; */ abstract class FileLoader extends BaseFileLoader { + public const ANONYMOUS_ID_REGEXP = '/^\.\d+_[^~]*+~[._a-zA-Z\d]{7}$/'; + protected $container; protected $isLoadingInstanceof = false; protected $instanceof = []; + protected $interfaces = []; + protected $singlyImplemented = []; + protected $autoRegisterAliasesForSinglyImplementedInterfaces = true; - public function __construct(ContainerBuilder $container, FileLocatorInterface $locator) + public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, string $env = null) { $this->container = $container; - parent::__construct($locator); + parent::__construct($locator, $env); + } + + /** + * {@inheritdoc} + * + * @param bool|string $ignoreErrors Whether errors should be ignored; pass "not_found" to ignore only when the loaded resource is not found + */ + public function import($resource, string $type = null, $ignoreErrors = false, string $sourceResource = null, $exclude = null) + { + $args = \func_get_args(); + + if ($ignoreNotFound = 'not_found' === $ignoreErrors) { + $args[2] = false; + } elseif (!\is_bool($ignoreErrors)) { + throw new \TypeError(sprintf('Invalid argument $ignoreErrors provided to "%s::import()": boolean or "not_found" expected, "%s" given.', static::class, get_debug_type($ignoreErrors))); + } + + try { + return parent::import(...$args); + } catch (LoaderLoadException $e) { + if (!$ignoreNotFound || !($prev = $e->getPrevious()) instanceof FileLocatorFileNotFoundException) { + throw $e; + } + + foreach ($prev->getTrace() as $frame) { + if ('import' === ($frame['function'] ?? null) && is_a($frame['class'] ?? '', Loader::class, true)) { + break; + } + } + + if (__FILE__ !== $frame['file']) { + throw $e; + } + } + + return null; } /** * Registers a set of classes as services using PSR-4 for discovery. * - * @param Definition $prototype A definition to use as template - * @param string $namespace The namespace prefix of classes in the scanned directory - * @param string $resource The directory to look for classes, glob-patterns allowed - * @param string $exclude A globed path of files to exclude + * @param Definition $prototype A definition to use as template + * @param string $namespace The namespace prefix of classes in the scanned directory + * @param string $resource The directory to look for classes, glob-patterns allowed + * @param string|string[]|null $exclude A globbed path of files to exclude or an array of globbed paths of files to exclude */ - public function registerClasses(Definition $prototype, $namespace, $resource, $exclude = null) + public function registerClasses(Definition $prototype, string $namespace, string $resource, $exclude = null) { - if ('\\' !== substr($namespace, -1)) { + if (!str_ends_with($namespace, '\\')) { throw new InvalidArgumentException(sprintf('Namespace prefix must end with a "\\": "%s".', $namespace)); } if (!preg_match('/^(?:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+\\\\)++$/', $namespace)) { throw new InvalidArgumentException(sprintf('Namespace is not a valid PSR-4 prefix: "%s".', $namespace)); } - $classes = $this->findClasses($namespace, $resource, $exclude); + $autoconfigureAttributes = new RegisterAutoconfigureAttributesPass(); + $autoconfigureAttributes = $autoconfigureAttributes->accept($prototype) ? $autoconfigureAttributes : null; + $classes = $this->findClasses($namespace, $resource, (array) $exclude, $autoconfigureAttributes); // prepare for deep cloning $serializedPrototype = serialize($prototype); - $interfaces = []; - $singlyImplemented = []; foreach ($classes as $class => $errorMessage) { + if (null === $errorMessage && $autoconfigureAttributes && $this->env) { + $r = $this->container->getReflectionClass($class); + $attribute = null; + foreach ($r->getAttributes(When::class) as $attribute) { + if ($this->env === $attribute->newInstance()->env) { + $attribute = null; + break; + } + } + if (null !== $attribute) { + continue; + } + } + if (interface_exists($class, false)) { - $interfaces[] = $class; + $this->interfaces[] = $class; } else { $this->setDefinition($class, $definition = unserialize($serializedPrototype)); if (null !== $errorMessage) { @@ -71,64 +131,71 @@ abstract class FileLoader extends BaseFileLoader continue; } foreach (class_implements($class, false) as $interface) { - $singlyImplemented[$interface] = isset($singlyImplemented[$interface]) ? false : $class; + $this->singlyImplemented[$interface] = ($this->singlyImplemented[$interface] ?? $class) !== $class ? false : $class; } } } - foreach ($interfaces as $interface) { - if (!empty($singlyImplemented[$interface])) { - $this->container->setAlias($interface, $singlyImplemented[$interface]) - ->setPublic(false); + + if ($this->autoRegisterAliasesForSinglyImplementedInterfaces) { + $this->registerAliasesForSinglyImplementedInterfaces(); + } + } + + public function registerAliasesForSinglyImplementedInterfaces() + { + foreach ($this->interfaces as $interface) { + if (!empty($this->singlyImplemented[$interface]) && !$this->container->has($interface)) { + $this->container->setAlias($interface, $this->singlyImplemented[$interface]); } } + + $this->interfaces = $this->singlyImplemented = []; } /** * Registers a definition in the container with its instanceof-conditionals. - * - * @param string $id */ - protected function setDefinition($id, Definition $definition) + protected function setDefinition(string $id, Definition $definition) { $this->container->removeBindings($id); if ($this->isLoadingInstanceof) { if (!$definition instanceof ChildDefinition) { - throw new InvalidArgumentException(sprintf('Invalid type definition "%s": ChildDefinition expected, "%s" given.', $id, \get_class($definition))); + throw new InvalidArgumentException(sprintf('Invalid type definition "%s": ChildDefinition expected, "%s" given.', $id, get_debug_type($definition))); } $this->instanceof[$id] = $definition; } else { - $this->container->setDefinition($id, $definition instanceof ChildDefinition ? $definition : $definition->setInstanceofConditionals($this->instanceof)); + $this->container->setDefinition($id, $definition->setInstanceofConditionals($this->instanceof)); } } - private function findClasses($namespace, $pattern, $excludePattern) + private function findClasses(string $namespace, string $pattern, array $excludePatterns, ?RegisterAutoconfigureAttributesPass $autoconfigureAttributes): array { $parameterBag = $this->container->getParameterBag(); $excludePaths = []; $excludePrefix = null; - if ($excludePattern) { - $excludePattern = $parameterBag->unescapeValue($parameterBag->resolveValue($excludePattern)); - foreach ($this->glob($excludePattern, true, $resource, true) as $path => $info) { + $excludePatterns = $parameterBag->unescapeValue($parameterBag->resolveValue($excludePatterns)); + foreach ($excludePatterns as $excludePattern) { + foreach ($this->glob($excludePattern, true, $resource, true, true) as $path => $info) { if (null === $excludePrefix) { $excludePrefix = $resource->getPrefix(); } - // normalize Windows slashes - $excludePaths[str_replace('\\', '/', $path)] = true; + // normalize Windows slashes and remove trailing slashes + $excludePaths[rtrim(str_replace('\\', '/', $path), '/')] = true; } } $pattern = $parameterBag->unescapeValue($parameterBag->resolveValue($pattern)); $classes = []; - $extRegexp = \defined('HHVM_VERSION') ? '/\\.(?:php|hh)$/' : '/\\.php$/'; + $extRegexp = '/\\.php$/'; $prefixLen = null; - foreach ($this->glob($pattern, true, $resource) as $path => $info) { + foreach ($this->glob($pattern, true, $resource, false, false, $excludePaths) as $path => $info) { if (null === $prefixLen) { $prefixLen = \strlen($resource->getPrefix()); - if ($excludePrefix && 0 !== strpos($excludePrefix, $resource->getPrefix())) { + if ($excludePrefix && !str_starts_with($excludePrefix, $resource->getPrefix())) { throw new InvalidArgumentException(sprintf('Invalid "exclude" pattern when importing classes for "%s": make sure your "exclude" pattern (%s) is a subset of the "resource" pattern (%s).', $namespace, $excludePattern, $pattern)); } } @@ -160,6 +227,10 @@ abstract class FileLoader extends BaseFileLoader if ($r->isInstantiable() || $r->isInterface()) { $classes[$class] = null; } + + if ($autoconfigureAttributes && !$r->isInstantiable()) { + $autoconfigureAttributes->processClass($this->container, $r); + } } // track only for new & removed files diff --git a/lib/symfony/dependency-injection/Loader/GlobFileLoader.php b/lib/symfony/dependency-injection/Loader/GlobFileLoader.php index 4b25610efe..e38aaf43be 100644 --- a/lib/symfony/dependency-injection/Loader/GlobFileLoader.php +++ b/lib/symfony/dependency-injection/Loader/GlobFileLoader.php @@ -21,19 +21,21 @@ class GlobFileLoader extends FileLoader /** * {@inheritdoc} */ - public function load($resource, $type = null) + public function load($resource, string $type = null) { foreach ($this->glob($resource, false, $globResource) as $path => $info) { $this->import($path); } $this->container->addResource($globResource); + + return null; } /** * {@inheritdoc} */ - public function supports($resource, $type = null) + public function supports($resource, string $type = null) { return 'glob' === $type; } diff --git a/lib/symfony/dependency-injection/Loader/IniFileLoader.php b/lib/symfony/dependency-injection/Loader/IniFileLoader.php index 6c07c67345..d88d7a6307 100644 --- a/lib/symfony/dependency-injection/Loader/IniFileLoader.php +++ b/lib/symfony/dependency-injection/Loader/IniFileLoader.php @@ -24,7 +24,7 @@ class IniFileLoader extends FileLoader /** * {@inheritdoc} */ - public function load($resource, $type = null) + public function load($resource, string $type = null) { $path = $this->locator->locate($resource); @@ -44,12 +44,20 @@ class IniFileLoader extends FileLoader $this->container->setParameter($key, $this->phpize($value)); } } + + if ($this->env && \is_array($result['parameters@'.$this->env] ?? null)) { + foreach ($result['parameters@'.$this->env] as $key => $value) { + $this->container->setParameter($key, $this->phpize($value)); + } + } + + return null; } /** * {@inheritdoc} */ - public function supports($resource, $type = null) + public function supports($resource, string $type = null) { if (!\is_string($resource)) { return false; @@ -66,8 +74,10 @@ class IniFileLoader extends FileLoader * Note that the following features are not supported: * * strings with escaped quotes are not supported "foo\"bar"; * * string concatenation ("foo" "bar"). + * + * @return mixed */ - private function phpize($value) + private function phpize(string $value) { // trim on the right as comments removal keep whitespaces if ($value !== $v = rtrim($value)) { diff --git a/lib/symfony/dependency-injection/Loader/PhpFileLoader.php b/lib/symfony/dependency-injection/Loader/PhpFileLoader.php index ddb671ff26..3815b28f00 100644 --- a/lib/symfony/dependency-injection/Loader/PhpFileLoader.php +++ b/lib/symfony/dependency-injection/Loader/PhpFileLoader.php @@ -11,6 +11,16 @@ namespace Symfony\Component\DependencyInjection\Loader; +use Symfony\Component\Config\Builder\ConfigBuilderGenerator; +use Symfony\Component\Config\Builder\ConfigBuilderGeneratorInterface; +use Symfony\Component\Config\Builder\ConfigBuilderInterface; +use Symfony\Component\Config\FileLocatorInterface; +use Symfony\Component\DependencyInjection\Attribute\When; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; /** @@ -23,10 +33,19 @@ use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigura */ class PhpFileLoader extends FileLoader { + protected $autoRegisterAliasesForSinglyImplementedInterfaces = false; + private $generator; + + public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, string $env = null, ConfigBuilderGeneratorInterface $generator = null) + { + parent::__construct($container, $locator, $env); + $this->generator = $generator; + } + /** * {@inheritdoc} */ - public function load($resource, $type = null) + public function load($resource, string $type = null) { // the container and loader variables are exposed to the included file below $container = $this->container; @@ -37,21 +56,28 @@ class PhpFileLoader extends FileLoader $this->container->fileExists($path); // the closure forbids access to the private scope in the included file - $load = \Closure::bind(function ($path) use ($container, $loader, $resource, $type) { + $load = \Closure::bind(function ($path, $env) use ($container, $loader, $resource, $type) { return include $path; }, $this, ProtectedPhpFileLoader::class); - $callback = $load($path); + try { + $callback = $load($path, $this->env); - if ($callback instanceof \Closure) { - $callback(new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource), $this->container, $this); + if (\is_object($callback) && \is_callable($callback)) { + $this->executeCallback($callback, new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource, $this->env), $path); + } + } finally { + $this->instanceof = []; + $this->registerAliasesForSinglyImplementedInterfaces(); } + + return null; } /** * {@inheritdoc} */ - public function supports($resource, $type = null) + public function supports($resource, string $type = null) { if (!\is_string($resource)) { return false; @@ -63,6 +89,118 @@ class PhpFileLoader extends FileLoader return 'php' === $type; } + + /** + * Resolve the parameters to the $callback and execute it. + */ + private function executeCallback(callable $callback, ContainerConfigurator $containerConfigurator, string $path) + { + if (!$callback instanceof \Closure) { + $callback = \Closure::fromCallable($callback); + } + + $arguments = []; + $configBuilders = []; + $r = new \ReflectionFunction($callback); + + if (\PHP_VERSION_ID >= 80000) { + $attribute = null; + foreach ($r->getAttributes(When::class) as $attribute) { + if ($this->env === $attribute->newInstance()->env) { + $attribute = null; + break; + } + } + if (null !== $attribute) { + return; + } + } + + foreach ($r->getParameters() as $parameter) { + $reflectionType = $parameter->getType(); + if (!$reflectionType instanceof \ReflectionNamedType) { + throw new \InvalidArgumentException(sprintf('Could not resolve argument "$%s" for "%s". You must typehint it (for example with "%s" or "%s").', $parameter->getName(), $path, ContainerConfigurator::class, ContainerBuilder::class)); + } + $type = $reflectionType->getName(); + + switch ($type) { + case ContainerConfigurator::class: + $arguments[] = $containerConfigurator; + break; + case ContainerBuilder::class: + $arguments[] = $this->container; + break; + case FileLoader::class: + case self::class: + $arguments[] = $this; + break; + default: + try { + $configBuilder = $this->configBuilder($type); + } catch (InvalidArgumentException|\LogicException $e) { + throw new \InvalidArgumentException(sprintf('Could not resolve argument "%s" for "%s".', $type.' $'.$parameter->getName(), $path), 0, $e); + } + $configBuilders[] = $configBuilder; + $arguments[] = $configBuilder; + } + } + + // Force load ContainerConfigurator to make env(), param() etc available. + class_exists(ContainerConfigurator::class); + + $callback(...$arguments); + + /** @var ConfigBuilderInterface $configBuilder */ + foreach ($configBuilders as $configBuilder) { + $containerConfigurator->extension($configBuilder->getExtensionAlias(), $configBuilder->toArray()); + } + } + + /** + * @param string $namespace FQCN string for a class implementing ConfigBuilderInterface + */ + private function configBuilder(string $namespace): ConfigBuilderInterface + { + if (!class_exists(ConfigBuilderGenerator::class)) { + throw new \LogicException('You cannot use the config builder as the Config component is not installed. Try running "composer require symfony/config".'); + } + + if (null === $this->generator) { + throw new \LogicException('You cannot use the ConfigBuilders without providing a class implementing ConfigBuilderGeneratorInterface.'); + } + + // If class exists and implements ConfigBuilderInterface + if (class_exists($namespace) && is_subclass_of($namespace, ConfigBuilderInterface::class)) { + return new $namespace(); + } + + // If it does not start with Symfony\Config\ we dont know how to handle this + if ('Symfony\\Config\\' !== substr($namespace, 0, 15)) { + throw new InvalidArgumentException(sprintf('Could not find or generate class "%s".', $namespace)); + } + + // Try to get the extension alias + $alias = Container::underscore(substr($namespace, 15, -6)); + + if (false !== strpos($alias, '\\')) { + throw new InvalidArgumentException('You can only use "root" ConfigBuilders from "Symfony\\Config\\" namespace. Nested classes like "Symfony\\Config\\Framework\\CacheConfig" cannot be used.'); + } + + if (!$this->container->hasExtension($alias)) { + $extensions = array_filter(array_map(function (ExtensionInterface $ext) { return $ext->getAlias(); }, $this->container->getExtensions())); + throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s". Looked for namespace "%s", found "%s".', $namespace, $alias, $extensions ? implode('", "', $extensions) : 'none')); + } + + $extension = $this->container->getExtension($alias); + if (!$extension instanceof ConfigurationExtensionInterface) { + throw new \LogicException(sprintf('You cannot use the config builder for "%s" because the extension does not implement "%s".', $namespace, ConfigurationExtensionInterface::class)); + } + + $configuration = $extension->getConfiguration([], $this->container); + $loader = $this->generator->build($configuration); + + return $loader(); + } } /** diff --git a/lib/symfony/dependency-injection/Loader/XmlFileLoader.php b/lib/symfony/dependency-injection/Loader/XmlFileLoader.php index 0ff54ee66e..73b0f0deb3 100644 --- a/lib/symfony/dependency-injection/Loader/XmlFileLoader.php +++ b/lib/symfony/dependency-injection/Loader/XmlFileLoader.php @@ -13,8 +13,11 @@ namespace Symfony\Component\DependencyInjection\Loader; use Symfony\Component\Config\Util\XmlUtils; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -22,6 +25,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\ExpressionLanguage\Expression; @@ -32,12 +36,14 @@ use Symfony\Component\ExpressionLanguage\Expression; */ class XmlFileLoader extends FileLoader { - const NS = 'http://symfony.com/schema/dic/services'; + public const NS = 'http://symfony.com/schema/dic/services'; + + protected $autoRegisterAliasesForSinglyImplementedInterfaces = false; /** * {@inheritdoc} */ - public function load($resource, $type = null) + public function load($resource, string $type = null) { $path = $this->locator->locate($resource); @@ -45,32 +51,54 @@ class XmlFileLoader extends FileLoader $this->container->fileExists($path); - $defaults = $this->getServiceDefaults($xml, $path); + $this->loadXml($xml, $path); + + if ($this->env) { + $xpath = new \DOMXPath($xml); + $xpath->registerNamespace('container', self::NS); + foreach ($xpath->query(sprintf('//container:when[@env="%s"]', $this->env)) ?: [] as $root) { + $env = $this->env; + $this->env = null; + try { + $this->loadXml($xml, $path, $root); + } finally { + $this->env = $env; + } + } + } + + return null; + } + + private function loadXml(\DOMDocument $xml, string $path, \DOMNode $root = null): void + { + $defaults = $this->getServiceDefaults($xml, $path, $root); // anonymous services - $this->processAnonymousServices($xml, $path, $defaults); + $this->processAnonymousServices($xml, $path, $root); // imports - $this->parseImports($xml, $path); + $this->parseImports($xml, $path, $root); // parameters - $this->parseParameters($xml, $path); + $this->parseParameters($xml, $path, $root); // extensions - $this->loadFromExtensions($xml); + $this->loadFromExtensions($xml, $root); // services try { - $this->parseDefinitions($xml, $path, $defaults); + $this->parseDefinitions($xml, $path, $defaults, $root); } finally { $this->instanceof = []; + $this->registerAliasesForSinglyImplementedInterfaces(); } } /** * {@inheritdoc} */ - public function supports($resource, $type = null) + public function supports($resource, string $type = null) { if (!\is_string($resource)) { return false; @@ -83,66 +111,80 @@ class XmlFileLoader extends FileLoader return 'xml' === $type; } - /** - * Parses parameters. - * - * @param string $file - */ - private function parseParameters(\DOMDocument $xml, $file) + private function parseParameters(\DOMDocument $xml, string $file, \DOMNode $root = null) { - if ($parameters = $this->getChildren($xml->documentElement, 'parameters')) { + if ($parameters = $this->getChildren($root ?? $xml->documentElement, 'parameters')) { $this->container->getParameterBag()->add($this->getArgumentsAsPhp($parameters[0], 'parameter', $file)); } } - /** - * Parses imports. - * - * @param string $file - */ - private function parseImports(\DOMDocument $xml, $file) + private function parseImports(\DOMDocument $xml, string $file, \DOMNode $root = null) { $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); - if (false === $imports = $xpath->query('//container:imports/container:import')) { + if (false === $imports = $xpath->query('.//container:imports/container:import', $root)) { return; } $defaultDirectory = \dirname($file); foreach ($imports as $import) { $this->setCurrentDir($defaultDirectory); - $this->import($import->getAttribute('resource'), XmlUtils::phpize($import->getAttribute('type')) ?: null, (bool) XmlUtils::phpize($import->getAttribute('ignore-errors')), $file); + $this->import($import->getAttribute('resource'), XmlUtils::phpize($import->getAttribute('type')) ?: null, XmlUtils::phpize($import->getAttribute('ignore-errors')) ?: false, $file); } } - /** - * Parses multiple definitions. - * - * @param string $file - */ - private function parseDefinitions(\DOMDocument $xml, $file, $defaults) + private function parseDefinitions(\DOMDocument $xml, string $file, Definition $defaults, \DOMNode $root = null) { $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); - if (false === $services = $xpath->query('//container:services/container:service|//container:services/container:prototype')) { + if (false === $services = $xpath->query('.//container:services/container:service|.//container:services/container:prototype|.//container:services/container:stack', $root)) { return; } $this->setCurrentDir(\dirname($file)); $this->instanceof = []; $this->isLoadingInstanceof = true; - $instanceof = $xpath->query('//container:services/container:instanceof'); + $instanceof = $xpath->query('.//container:services/container:instanceof', $root); foreach ($instanceof as $service) { - $this->setDefinition((string) $service->getAttribute('id'), $this->parseDefinition($service, $file, [])); + $this->setDefinition((string) $service->getAttribute('id'), $this->parseDefinition($service, $file, new Definition())); } $this->isLoadingInstanceof = false; foreach ($services as $service) { - if (null !== $definition = $this->parseDefinition($service, $file, $defaults)) { + if ('stack' === $service->tagName) { + $service->setAttribute('parent', '-'); + $definition = $this->parseDefinition($service, $file, $defaults) + ->setTags(array_merge_recursive(['container.stack' => [[]]], $defaults->getTags())) + ; + $this->setDefinition($id = (string) $service->getAttribute('id'), $definition); + $stack = []; + + foreach ($this->getChildren($service, 'service') as $k => $frame) { + $k = $frame->getAttribute('id') ?: $k; + $frame->setAttribute('id', $id.'" at index "'.$k); + + if ($alias = $frame->getAttribute('alias')) { + $this->validateAlias($frame, $file); + $stack[$k] = new Reference($alias); + } else { + $stack[$k] = $this->parseDefinition($frame, $file, $defaults) + ->setInstanceofConditionals($this->instanceof); + } + } + + $definition->setArguments($stack); + } elseif (null !== $definition = $this->parseDefinition($service, $file, $defaults)) { if ('prototype' === $service->tagName) { - $this->registerClasses($definition, (string) $service->getAttribute('namespace'), (string) $service->getAttribute('resource'), (string) $service->getAttribute('exclude')); + $excludes = array_column($this->getChildren($service, 'exclude'), 'nodeValue'); + if ($service->hasAttribute('exclude')) { + if (\count($excludes) > 0) { + throw new InvalidArgumentException('You cannot use both the attribute "exclude" and tags at the same time.'); + } + $excludes = [$service->getAttribute('exclude')]; + } + $this->registerClasses($definition, (string) $service->getAttribute('namespace'), (string) $service->getAttribute('resource'), $excludes); } else { $this->setDefinition((string) $service->getAttribute('id'), $definition); } @@ -150,60 +192,49 @@ class XmlFileLoader extends FileLoader } } - /** - * Get service defaults. - * - * @return array - */ - private function getServiceDefaults(\DOMDocument $xml, $file) + private function getServiceDefaults(\DOMDocument $xml, string $file, \DOMNode $root = null): Definition { $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); - if (null === $defaultsNode = $xpath->query('//container:services/container:defaults')->item(0)) { - return []; - } - $defaults = [ - 'tags' => $this->getChildren($defaultsNode, 'tag'), - 'bind' => array_map(function ($v) { return new BoundArgument($v); }, $this->getArgumentsAsPhp($defaultsNode, 'bind', $file)), - ]; - - foreach ($defaults['tags'] as $tag) { - if ('' === $tag->getAttribute('name')) { - throw new InvalidArgumentException(sprintf('The tag name for tag "" in "%s" must be a non-empty string.', $file)); - } + if (null === $defaultsNode = $xpath->query('.//container:services/container:defaults', $root)->item(0)) { + return new Definition(); } - if ($defaultsNode->hasAttribute('autowire')) { - $defaults['autowire'] = XmlUtils::phpize($defaultsNode->getAttribute('autowire')); - } - if ($defaultsNode->hasAttribute('public')) { - $defaults['public'] = XmlUtils::phpize($defaultsNode->getAttribute('public')); - } - if ($defaultsNode->hasAttribute('autoconfigure')) { - $defaults['autoconfigure'] = XmlUtils::phpize($defaultsNode->getAttribute('autoconfigure')); - } + $defaultsNode->setAttribute('id', ''); - return $defaults; + return $this->parseDefinition($defaultsNode, $file, new Definition()); } /** * Parses an individual Definition. - * - * @param string $file - * - * @return Definition|null */ - private function parseDefinition(\DOMElement $service, $file, array $defaults) + private function parseDefinition(\DOMElement $service, string $file, Definition $defaults): ?Definition { if ($alias = $service->getAttribute('alias')) { $this->validateAlias($service, $file); - $this->container->setAlias((string) $service->getAttribute('id'), $alias = new Alias($alias)); + $this->container->setAlias($service->getAttribute('id'), $alias = new Alias($alias)); if ($publicAttr = $service->getAttribute('public')) { $alias->setPublic(XmlUtils::phpize($publicAttr)); - } elseif (isset($defaults['public'])) { - $alias->setPublic($defaults['public']); + } elseif ($defaults->getChanges()['public'] ?? false) { + $alias->setPublic($defaults->isPublic()); + } + + if ($deprecated = $this->getChildren($service, 'deprecated')) { + $message = $deprecated[0]->nodeValue ?: ''; + $package = $deprecated[0]->getAttribute('package') ?: ''; + $version = $deprecated[0]->getAttribute('version') ?: ''; + + if (!$deprecated[0]->hasAttribute('package')) { + trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the node "deprecated" in "%s" is deprecated.', $file); + } + + if (!$deprecated[0]->hasAttribute('version')) { + trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the node "deprecated" in "%s" is deprecated.', $file); + } + + $alias->setDeprecated($package, $version, $message); } return null; @@ -212,49 +243,29 @@ class XmlFileLoader extends FileLoader if ($this->isLoadingInstanceof) { $definition = new ChildDefinition(''); } elseif ($parent = $service->getAttribute('parent')) { - if (!empty($this->instanceof)) { - throw new InvalidArgumentException(sprintf('The service "%s" cannot use the "parent" option in the same file where "instanceof" configuration is defined as using both is not supported. Move your child definitions to a separate file.', $service->getAttribute('id'))); - } - - foreach ($defaults as $k => $v) { - if ('tags' === $k) { - // since tags are never inherited from parents, there is no confusion - // thus we can safely add them as defaults to ChildDefinition - continue; - } - if ('bind' === $k) { - if ($defaults['bind']) { - throw new InvalidArgumentException(sprintf('Bound values on service "%s" cannot be inherited from "defaults" when a "parent" is set. Move your child definitions to a separate file.', $service->getAttribute('id'))); - } - - continue; - } - if (!$service->hasAttribute($k)) { - throw new InvalidArgumentException(sprintf('Attribute "%s" on service "%s" cannot be inherited from "defaults" when a "parent" is set. Move your child definitions to a separate file or define this attribute explicitly.', $k, $service->getAttribute('id'))); - } - } - $definition = new ChildDefinition($parent); } else { $definition = new Definition(); - - if (isset($defaults['public'])) { - $definition->setPublic($defaults['public']); - } - if (isset($defaults['autowire'])) { - $definition->setAutowired($defaults['autowire']); - } - if (isset($defaults['autoconfigure'])) { - $definition->setAutoconfigured($defaults['autoconfigure']); - } - - $definition->setChanges([]); } - foreach (['class', 'public', 'shared', 'synthetic', 'lazy', 'abstract'] as $key) { + if ($defaults->getChanges()['public'] ?? false) { + $definition->setPublic($defaults->isPublic()); + } + $definition->setAutowired($defaults->isAutowired()); + $definition->setAutoconfigured($defaults->isAutoconfigured()); + $definition->setChanges([]); + + foreach (['class', 'public', 'shared', 'synthetic', 'abstract'] as $key) { if ($value = $service->getAttribute($key)) { $method = 'set'.$key; - $definition->$method(XmlUtils::phpize($value)); + $definition->$method($value = XmlUtils::phpize($value)); + } + } + + if ($value = $service->getAttribute('lazy')) { + $definition->setLazy((bool) $value = XmlUtils::phpize($value)); + if (\is_string($value)) { + $definition->addTag('proxy', ['interface' => $value]); } } @@ -263,11 +274,7 @@ class XmlFileLoader extends FileLoader } if ($value = $service->getAttribute('autoconfigure')) { - if (!$definition instanceof ChildDefinition) { - $definition->setAutoconfigured(XmlUtils::phpize($value)); - } elseif ($value = XmlUtils::phpize($value)) { - throw new InvalidArgumentException(sprintf('The service "%s" cannot have a "parent" and also have "autoconfigure". Try setting autoconfigure="false" for the service.', $service->getAttribute('id'))); - } + $definition->setAutoconfigured(XmlUtils::phpize($value)); } if ($files = $this->getChildren($service, 'file')) { @@ -275,7 +282,19 @@ class XmlFileLoader extends FileLoader } if ($deprecated = $this->getChildren($service, 'deprecated')) { - $definition->setDeprecated(true, $deprecated[0]->nodeValue ?: null); + $message = $deprecated[0]->nodeValue ?: ''; + $package = $deprecated[0]->getAttribute('package') ?: ''; + $version = $deprecated[0]->getAttribute('version') ?: ''; + + if ('' === $package) { + trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the node "deprecated" in "%s" is deprecated.', $file); + } + + if ('' === $version) { + trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the node "deprecated" in "%s" is deprecated.', $file); + } + + $definition->setDeprecated($package, $version, $message); } $definition->setArguments($this->getArgumentsAsPhp($service, 'argument', $file, $definition instanceof ChildDefinition)); @@ -292,7 +311,7 @@ class XmlFileLoader extends FileLoader $class = $factory->hasAttribute('class') ? $factory->getAttribute('class') : null; } - $definition->setFactory([$class, $factory->getAttribute('method')]); + $definition->setFactory([$class, $factory->getAttribute('method') ?: '__invoke']); } } @@ -307,73 +326,80 @@ class XmlFileLoader extends FileLoader $class = $configurator->getAttribute('class'); } - $definition->setConfigurator([$class, $configurator->getAttribute('method')]); + $definition->setConfigurator([$class, $configurator->getAttribute('method') ?: '__invoke']); } } foreach ($this->getChildren($service, 'call') as $call) { - $definition->addMethodCall($call->getAttribute('method'), $this->getArgumentsAsPhp($call, 'argument', $file)); + $definition->addMethodCall($call->getAttribute('method'), $this->getArgumentsAsPhp($call, 'argument', $file), XmlUtils::phpize($call->getAttribute('returns-clone'))); } $tags = $this->getChildren($service, 'tag'); - if (!empty($defaults['tags'])) { - $tags = array_merge($tags, $defaults['tags']); - } - foreach ($tags as $tag) { $parameters = []; + $tagName = $tag->nodeValue; foreach ($tag->attributes as $name => $node) { - if ('name' === $name) { + if ('name' === $name && '' === $tagName) { continue; } - if (false !== strpos($name, '-') && false === strpos($name, '_') && !\array_key_exists($normalizedName = str_replace('-', '_', $name), $parameters)) { + if (str_contains($name, '-') && !str_contains($name, '_') && !\array_key_exists($normalizedName = str_replace('-', '_', $name), $parameters)) { $parameters[$normalizedName] = XmlUtils::phpize($node->nodeValue); } // keep not normalized key $parameters[$name] = XmlUtils::phpize($node->nodeValue); } - if ('' === $tag->getAttribute('name')) { - throw new InvalidArgumentException(sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', (string) $service->getAttribute('id'), $file)); + if ('' === $tagName && '' === $tagName = $tag->getAttribute('name')) { + throw new InvalidArgumentException(sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', $service->getAttribute('id'), $file)); } - $definition->addTag($tag->getAttribute('name'), $parameters); + $definition->addTag($tagName, $parameters); } - foreach ($this->getChildren($service, 'autowiring-type') as $type) { - $definition->addAutowiringType($type->textContent); - } + $definition->setTags(array_merge_recursive($definition->getTags(), $defaults->getTags())); $bindings = $this->getArgumentsAsPhp($service, 'bind', $file); - if (isset($defaults['bind'])) { - // deep clone, to avoid multiple process of the same instance in the passes - $bindings = array_merge(unserialize(serialize($defaults['bind'])), $bindings); + $bindingType = $this->isLoadingInstanceof ? BoundArgument::INSTANCEOF_BINDING : BoundArgument::SERVICE_BINDING; + foreach ($bindings as $argument => $value) { + $bindings[$argument] = new BoundArgument($value, true, $bindingType, $file); } + + // deep clone, to avoid multiple process of the same instance in the passes + $bindings = array_merge(unserialize(serialize($defaults->getBindings())), $bindings); + if ($bindings) { $definition->setBindings($bindings); } - if ($value = $service->getAttribute('decorates')) { + if ($decorates = $service->getAttribute('decorates')) { + $decorationOnInvalid = $service->getAttribute('decoration-on-invalid') ?: 'exception'; + if ('exception' === $decorationOnInvalid) { + $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + } elseif ('ignore' === $decorationOnInvalid) { + $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + } elseif ('null' === $decorationOnInvalid) { + $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; + } else { + throw new InvalidArgumentException(sprintf('Invalid value "%s" for attribute "decoration-on-invalid" on service "%s". Did you mean "exception", "ignore" or "null" in "%s"?', $decorationOnInvalid, $service->getAttribute('id'), $file)); + } + $renameId = $service->hasAttribute('decoration-inner-name') ? $service->getAttribute('decoration-inner-name') : null; $priority = $service->hasAttribute('decoration-priority') ? $service->getAttribute('decoration-priority') : 0; - $definition->setDecoratedService($value, $renameId, $priority); + + $definition->setDecoratedService($decorates, $renameId, $priority, $invalidBehavior); } return $definition; } /** - * Parses a XML file to a \DOMDocument. - * - * @param string $file Path to a file - * - * @return \DOMDocument + * Parses an XML file to a \DOMDocument. * * @throws InvalidArgumentException When loading of XML file returns error */ - private function parseFileToDOM($file) + private function parseFileToDOM(string $file): \DOMDocument { try { $dom = XmlUtils::loadFile($file, [$this, 'validateSchema']); @@ -388,11 +414,8 @@ class XmlFileLoader extends FileLoader /** * Processes anonymous services. - * - * @param string $file - * @param array $defaults */ - private function processAnonymousServices(\DOMDocument $xml, $file, $defaults) + private function processAnonymousServices(\DOMDocument $xml, string $file, \DOMNode $root = null) { $definitions = []; $count = 0; @@ -402,15 +425,15 @@ class XmlFileLoader extends FileLoader $xpath->registerNamespace('container', self::NS); // anonymous services as arguments/properties - if (false !== $nodes = $xpath->query('//container:argument[@type="service"][not(@id)]|//container:property[@type="service"][not(@id)]|//container:bind[not(@id)]|//container:factory[not(@service)]|//container:configurator[not(@service)]')) { + if (false !== $nodes = $xpath->query('.//container:argument[@type="service"][not(@id)]|.//container:property[@type="service"][not(@id)]|.//container:bind[not(@id)]|.//container:factory[not(@service)]|.//container:configurator[not(@service)]', $root)) { foreach ($nodes as $node) { if ($services = $this->getChildren($node, 'service')) { // give it a unique name - $id = sprintf('%d_%s', ++$count, preg_replace('/^.*\\\\/', '', $services[0]->getAttribute('class')).$suffix); + $id = sprintf('.%d_%s', ++$count, preg_replace('/^.*\\\\/', '', $services[0]->getAttribute('class')).$suffix); $node->setAttribute('id', $id); $node->setAttribute('service', $id); - $definitions[$id] = [$services[0], $file, false]; + $definitions[$id] = [$services[0], $file]; $services[0]->setAttribute('id', $id); // anonymous services are always private @@ -421,41 +444,22 @@ class XmlFileLoader extends FileLoader } // anonymous services "in the wild" - if (false !== $nodes = $xpath->query('//container:services/container:service[not(@id)]')) { + if (false !== $nodes = $xpath->query('.//container:services/container:service[not(@id)]', $root)) { foreach ($nodes as $node) { - @trigger_error(sprintf('Top-level anonymous services are deprecated since Symfony 3.4, the "id" attribute will be required in version 4.0 in %s at line %d.', $file, $node->getLineNo()), \E_USER_DEPRECATED); - - // give it a unique name - $id = sprintf('%d_%s', ++$count, preg_replace('/^.*\\\\/', '', $node->getAttribute('class')).$suffix); - $node->setAttribute('id', $id); - $definitions[$id] = [$node, $file, true]; + throw new InvalidArgumentException(sprintf('Top-level services must have "id" attribute, none found in "%s" at line %d.', $file, $node->getLineNo())); } } // resolve definitions uksort($definitions, 'strnatcmp'); - foreach (array_reverse($definitions) as $id => list($domElement, $file, $wild)) { - if (null !== $definition = $this->parseDefinition($domElement, $file, $wild ? $defaults : [])) { + foreach (array_reverse($definitions) as $id => [$domElement, $file]) { + if (null !== $definition = $this->parseDefinition($domElement, $file, new Definition())) { $this->setDefinition($id, $definition); } - - if (true === $wild) { - $tmpDomElement = new \DOMElement('_services', null, self::NS); - $domElement->parentNode->replaceChild($tmpDomElement, $domElement); - $tmpDomElement->setAttribute('id', $id); - } } } - /** - * Returns arguments as valid php types. - * - * @param string $name - * @param string $file - * - * @return mixed - */ - private function getArgumentsAsPhp(\DOMElement $node, $name, $file, $isChildDefinition = false) + private function getArgumentsAsPhp(\DOMElement $node, string $name, string $file, bool $isChildDefinition = false): array { $arguments = []; foreach ($this->getChildren($node, $name) as $arg) { @@ -491,15 +495,12 @@ class XmlFileLoader extends FileLoader if ('' === $arg->getAttribute('id')) { throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="service" has no or empty "id" attribute in "%s".', $name, $file)); } - if ($arg->hasAttribute('strict')) { - @trigger_error(sprintf('The "strict" attribute used when referencing the "%s" service is deprecated since Symfony 3.3 and will be removed in 4.0.', $arg->getAttribute('id')), \E_USER_DEPRECATED); - } $arguments[$key] = new Reference($arg->getAttribute('id'), $invalidBehavior); break; case 'expression': if (!class_exists(Expression::class)) { - throw new \LogicException(sprintf('The type="expression" attribute cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".')); + throw new \LogicException('The type="expression" attribute cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'); } $arguments[$key] = new Expression($arg->nodeValue); @@ -515,11 +516,45 @@ class XmlFileLoader extends FileLoader throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="iterator" only accepts collections of type="service" references in "%s".', $name, $file)); } break; - case 'tagged': - if (!$arg->getAttribute('tag')) { - throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="tagged" has no or empty "tag" attribute in "%s".', $name, $file)); + case 'service_closure': + if ('' === $arg->getAttribute('id')) { + throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="service_closure" has no or empty "id" attribute in "%s".', $name, $file)); } - $arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag')); + + $arguments[$key] = new ServiceClosureArgument(new Reference($arg->getAttribute('id'), $invalidBehavior)); + break; + case 'service_locator': + $arg = $this->getArgumentsAsPhp($arg, $name, $file); + try { + $arguments[$key] = new ServiceLocatorArgument($arg); + } catch (InvalidArgumentException $e) { + throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="service_locator" only accepts maps of type="service" references in "%s".', $name, $file)); + } + break; + case 'tagged': + case 'tagged_iterator': + case 'tagged_locator': + $type = $arg->getAttribute('type'); + $forLocator = 'tagged_locator' === $type; + + if (!$arg->getAttribute('tag')) { + throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="%s" has no or empty "tag" attribute in "%s".', $name, $type, $file)); + } + + $arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by') ?: null, $arg->getAttribute('default-index-method') ?: null, $forLocator, $arg->getAttribute('default-priority-method') ?: null); + + if ($forLocator) { + $arguments[$key] = new ServiceLocatorArgument($arguments[$key]); + } + break; + case 'binary': + if (false === $value = base64_decode($arg->nodeValue)) { + throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="binary" is not a valid base64 encoded string.', $name)); + } + $arguments[$key] = $value; + break; + case 'abstract': + $arguments[$key] = new AbstractArgument($arg->nodeValue); break; case 'string': $arguments[$key] = $arg->nodeValue; @@ -538,11 +573,9 @@ class XmlFileLoader extends FileLoader /** * Get child elements by name. * - * @param mixed $name - * * @return \DOMElement[] */ - private function getChildren(\DOMNode $node, $name) + private function getChildren(\DOMNode $node, string $name): array { $children = []; foreach ($node->childNodes as $child) { @@ -577,7 +610,7 @@ class XmlFileLoader extends FileLoader $path = str_replace([$ns, str_replace('http://', 'https://', $ns)], str_replace('\\', '/', $extension->getXsdValidationBasePath()).'/', $items[$i + 1]); if (!is_file($path)) { - throw new RuntimeException(sprintf('Extension "%s" references a non-existent XSD file "%s".', \get_class($extension), $path)); + throw new RuntimeException(sprintf('Extension "%s" references a non-existent XSD file "%s".', get_debug_type($extension), $path)); } $schemaLocations[$items[$i]] = $path; @@ -600,6 +633,8 @@ class XmlFileLoader extends FileLoader array_shift($parts); $locationstart = 'phar:///'; } + } elseif ('\\' === \DIRECTORY_SEPARATOR && str_starts_with($location, '\\\\')) { + $locationstart = ''; } $drive = '\\' === \DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; $location = $locationstart.$drive.implode('/', array_map('rawurlencode', $parts)); @@ -620,14 +655,13 @@ $imports EOF ; - if (\LIBXML_VERSION < 20900) { + if ($this->shouldEnableEntityLoader()) { $disableEntities = libxml_disable_entity_loader(false); $valid = @$dom->schemaValidateSource($source); libxml_disable_entity_loader($disableEntities); } else { $valid = @$dom->schemaValidateSource($source); } - foreach ($tmpfiles as $tmpfile) { @unlink($tmpfile); } @@ -635,22 +669,50 @@ EOF return $valid; } - /** - * Validates an alias. - * - * @param string $file - */ - private function validateAlias(\DOMElement $alias, $file) + private function shouldEnableEntityLoader(): bool + { + // Version prior to 8.0 can be enabled without deprecation + if (\PHP_VERSION_ID < 80000) { + return true; + } + + static $dom, $schema; + if (null === $dom) { + $dom = new \DOMDocument(); + $dom->loadXML(''); + + $tmpfile = tempnam(sys_get_temp_dir(), 'symfony'); + register_shutdown_function(static function () use ($tmpfile) { + @unlink($tmpfile); + }); + $schema = ' + + +'; + file_put_contents($tmpfile, ' + + + +'); + } + + return !@$dom->schemaValidateSource($schema); + } + + private function validateAlias(\DOMElement $alias, string $file) { foreach ($alias->attributes as $name => $node) { if (!\in_array($name, ['alias', 'id', 'public'])) { - @trigger_error(sprintf('Using the attribute "%s" is deprecated for the service "%s" which is defined as an alias in "%s". Allowed attributes for service aliases are "alias", "id" and "public". The XmlFileLoader will raise an exception in Symfony 4.0, instead of silently ignoring unsupported attributes.', $name, $alias->getAttribute('id'), $file), \E_USER_DEPRECATED); + throw new InvalidArgumentException(sprintf('Invalid attribute "%s" defined for alias "%s" in "%s".', $name, $alias->getAttribute('id'), $file)); } } foreach ($alias->childNodes as $child) { - if ($child instanceof \DOMElement && self::NS === $child->namespaceURI) { - @trigger_error(sprintf('Using the element "%s" is deprecated for the service "%s" which is defined as an alias in "%s". The XmlFileLoader will raise an exception in Symfony 4.0, instead of silently ignoring unsupported elements.', $child->localName, $alias->getAttribute('id'), $file), \E_USER_DEPRECATED); + if (!$child instanceof \DOMElement || self::NS !== $child->namespaceURI) { + continue; + } + if (!\in_array($child->localName, ['deprecated'], true)) { + throw new InvalidArgumentException(sprintf('Invalid child element "%s" defined for alias "%s" in "%s".', $child->localName, $alias->getAttribute('id'), $file)); } } } @@ -658,11 +720,9 @@ EOF /** * Validates an extension. * - * @param string $file - * * @throws InvalidArgumentException When no extension is found corresponding to a tag */ - private function validateExtensions(\DOMDocument $dom, $file) + private function validateExtensions(\DOMDocument $dom, string $file) { foreach ($dom->documentElement->childNodes as $node) { if (!$node instanceof \DOMElement || 'http://symfony.com/schema/dic/services' === $node->namespaceURI) { @@ -671,8 +731,8 @@ EOF // can it be handled by an extension? if (!$this->container->hasExtension($node->namespaceURI)) { - $extensionNamespaces = array_filter(array_map(function ($ext) { return $ext->getNamespace(); }, $this->container->getExtensions())); - throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in "%s"). Looked for namespace "%s", found "%s".', $node->tagName, $file, $node->namespaceURI, $extensionNamespaces ? sprintf('"%s"', implode('", "', $extensionNamespaces)) : 'none')); + $extensionNamespaces = array_filter(array_map(function (ExtensionInterface $ext) { return $ext->getNamespace(); }, $this->container->getExtensions())); + throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in "%s"). Looked for namespace "%s", found "%s".', $node->tagName, $file, $node->namespaceURI, $extensionNamespaces ? implode('", "', $extensionNamespaces) : 'none')); } } } diff --git a/lib/symfony/dependency-injection/Loader/YamlFileLoader.php b/lib/symfony/dependency-injection/Loader/YamlFileLoader.php index e598bc4ce4..8756e89ed1 100644 --- a/lib/symfony/dependency-injection/Loader/YamlFileLoader.php +++ b/lib/symfony/dependency-injection/Loader/YamlFileLoader.php @@ -12,9 +12,11 @@ namespace Symfony\Component\DependencyInjection\Loader; use Symfony\Component\DependencyInjection\Alias; -use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -22,6 +24,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\ExpressionLanguage\Expression; use Symfony\Component\Yaml\Exception\ParseException; @@ -36,7 +39,7 @@ use Symfony\Component\Yaml\Yaml; */ class YamlFileLoader extends FileLoader { - private static $serviceKeywords = [ + private const SERVICE_KEYWORDS = [ 'alias' => 'alias', 'parent' => 'parent', 'class' => 'class', @@ -56,13 +59,13 @@ class YamlFileLoader extends FileLoader 'decorates' => 'decorates', 'decoration_inner_name' => 'decoration_inner_name', 'decoration_priority' => 'decoration_priority', + 'decoration_on_invalid' => 'decoration_on_invalid', 'autowire' => 'autowire', - 'autowiring_types' => 'autowiring_types', 'autoconfigure' => 'autoconfigure', 'bind' => 'bind', ]; - private static $prototypeKeywords = [ + private const PROTOTYPE_KEYWORDS = [ 'resource' => 'resource', 'namespace' => 'namespace', 'exclude' => 'exclude', @@ -83,7 +86,7 @@ class YamlFileLoader extends FileLoader 'bind' => 'bind', ]; - private static $instanceofKeywords = [ + private const INSTANCEOF_KEYWORDS = [ 'shared' => 'shared', 'lazy' => 'lazy', 'public' => 'public', @@ -92,9 +95,10 @@ class YamlFileLoader extends FileLoader 'calls' => 'calls', 'tags' => 'tags', 'autowire' => 'autowire', + 'bind' => 'bind', ]; - private static $defaultsKeywords = [ + private const DEFAULTS_KEYWORDS = [ 'public' => 'public', 'tags' => 'tags', 'autowire' => 'autowire', @@ -107,10 +111,12 @@ class YamlFileLoader extends FileLoader private $anonymousServicesCount; private $anonymousServicesSuffix; + protected $autoRegisterAliasesForSinglyImplementedInterfaces = false; + /** * {@inheritdoc} */ - public function load($resource, $type = null) + public function load($resource, string $type = null) { $path = $this->locator->locate($resource); @@ -120,9 +126,31 @@ class YamlFileLoader extends FileLoader // empty file if (null === $content) { - return; + return null; } + $this->loadContent($content, $path); + + // per-env configuration + if ($this->env && isset($content['when@'.$this->env])) { + if (!\is_array($content['when@'.$this->env])) { + throw new InvalidArgumentException(sprintf('The "when@%s" key should contain an array in "%s". Check your YAML syntax.', $this->env, $path)); + } + + $env = $this->env; + $this->env = null; + try { + $this->loadContent($content['when@'.$env], $path); + } finally { + $this->env = $env; + } + } + + return null; + } + + private function loadContent(array $content, string $path) + { // imports $this->parseImports($content, $path); @@ -148,13 +176,14 @@ class YamlFileLoader extends FileLoader $this->parseDefinitions($content, $path); } finally { $this->instanceof = []; + $this->registerAliasesForSinglyImplementedInterfaces(); } } /** * {@inheritdoc} */ - public function supports($resource, $type = null) + public function supports($resource, string $type = null) { if (!\is_string($resource)) { return false; @@ -167,12 +196,7 @@ class YamlFileLoader extends FileLoader return \in_array($type, ['yaml', 'yml'], true); } - /** - * Parses all imports. - * - * @param string $file - */ - private function parseImports(array $content, $file) + private function parseImports(array $content, string $file) { if (!isset($content['imports'])) { return; @@ -192,16 +216,11 @@ class YamlFileLoader extends FileLoader } $this->setCurrentDir($defaultDirectory); - $this->import($import['resource'], isset($import['type']) ? $import['type'] : null, isset($import['ignore_errors']) ? (bool) $import['ignore_errors'] : false, $file); + $this->import($import['resource'], $import['type'] ?? null, $import['ignore_errors'] ?? false, $file); } } - /** - * Parses definitions. - * - * @param string $file - */ - private function parseDefinitions(array $content, $file) + private function parseDefinitions(array $content, string $file, bool $trackBindings = true) { if (!isset($content['services'])) { return; @@ -216,7 +235,7 @@ class YamlFileLoader extends FileLoader unset($content['services']['_instanceof']); if (!\is_array($instanceof)) { - throw new InvalidArgumentException(sprintf('Service "_instanceof" key must be an array, "%s" given in "%s".', \gettype($instanceof), $file)); + throw new InvalidArgumentException(sprintf('Service "_instanceof" key must be an array, "%s" given in "%s".', get_debug_type($instanceof), $file)); } $this->instanceof = []; $this->isLoadingInstanceof = true; @@ -224,28 +243,24 @@ class YamlFileLoader extends FileLoader if (!$service || !\is_array($service)) { throw new InvalidArgumentException(sprintf('Type definition "%s" must be a non-empty array within "_instanceof" in "%s". Check your YAML syntax.', $id, $file)); } - if (\is_string($service) && 0 === strpos($service, '@')) { + if (\is_string($service) && str_starts_with($service, '@')) { throw new InvalidArgumentException(sprintf('Type definition "%s" cannot be an alias within "_instanceof" in "%s". Check your YAML syntax.', $id, $file)); } - $this->parseDefinition($id, $service, $file, []); + $this->parseDefinition($id, $service, $file, [], false, $trackBindings); } } $this->isLoadingInstanceof = false; $defaults = $this->parseDefaults($content, $file); foreach ($content['services'] as $id => $service) { - $this->parseDefinition($id, $service, $file, $defaults); + $this->parseDefinition($id, $service, $file, $defaults, false, $trackBindings); } } /** - * @param string $file - * - * @return array - * * @throws InvalidArgumentException */ - private function parseDefaults(array &$content, $file) + private function parseDefaults(array &$content, string $file): array { if (!\array_key_exists('_defaults', $content['services'])) { return []; @@ -254,12 +269,12 @@ class YamlFileLoader extends FileLoader unset($content['services']['_defaults']); if (!\is_array($defaults)) { - throw new InvalidArgumentException(sprintf('Service "_defaults" key must be an array, "%s" given in "%s".', \gettype($defaults), $file)); + throw new InvalidArgumentException(sprintf('Service "_defaults" key must be an array, "%s" given in "%s".', get_debug_type($defaults), $file)); } foreach ($defaults as $key => $default) { - if (!isset(self::$defaultsKeywords[$key])) { - throw new InvalidArgumentException(sprintf('The configuration key "%s" cannot be used to define a default value in "%s". Allowed keys are "%s".', $key, $file, implode('", "', self::$defaultsKeywords))); + if (!isset(self::DEFAULTS_KEYWORDS[$key])) { + throw new InvalidArgumentException(sprintf('The configuration key "%s" cannot be used to define a default value in "%s". Allowed keys are "%s".', $key, $file, implode('", "', self::DEFAULTS_KEYWORDS))); } } @@ -273,18 +288,23 @@ class YamlFileLoader extends FileLoader $tag = ['name' => $tag]; } - if (!isset($tag['name'])) { - throw new InvalidArgumentException(sprintf('A "tags" entry in "_defaults" is missing a "name" key in "%s".', $file)); + if (1 === \count($tag) && \is_array(current($tag))) { + $name = key($tag); + $tag = current($tag); + } else { + if (!isset($tag['name'])) { + throw new InvalidArgumentException(sprintf('A "tags" entry in "_defaults" is missing a "name" key in "%s".', $file)); + } + $name = $tag['name']; + unset($tag['name']); } - $name = $tag['name']; - unset($tag['name']); if (!\is_string($name) || '' === $name) { throw new InvalidArgumentException(sprintf('The tag name in "_defaults" must be a non-empty string in "%s".', $file)); } foreach ($tag as $attribute => $value) { - if (!is_scalar($value) && null !== $value) { + if (!\is_scalar($value) && null !== $value) { throw new InvalidArgumentException(sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type in "%s". Check your YAML syntax.', $name, $attribute, $file)); } } @@ -296,19 +316,18 @@ class YamlFileLoader extends FileLoader throw new InvalidArgumentException(sprintf('Parameter "bind" in "_defaults" must be an array in "%s". Check your YAML syntax.', $file)); } - $defaults['bind'] = array_map(function ($v) { return new BoundArgument($v); }, $this->resolveServices($defaults['bind'], $file)); + foreach ($this->resolveServices($defaults['bind'], $file) as $argument => $value) { + $defaults['bind'][$argument] = new BoundArgument($value, true, BoundArgument::DEFAULTS_BINDING, $file); + } } return $defaults; } - /** - * @return bool - */ - private function isUsingShortSyntax(array $service) + private function isUsingShortSyntax(array $service): bool { foreach ($service as $key => $value) { - if (\is_string($key) && ('' === $key || '$' !== $key[0])) { + if (\is_string($key) && ('' === $key || ('$' !== $key[0] && !str_contains($key, '\\')))) { return false; } } @@ -319,24 +338,24 @@ class YamlFileLoader extends FileLoader /** * Parses a definition. * - * @param string $id - * @param array|string $service - * @param string $file + * @param array|string|null $service * * @throws InvalidArgumentException When tags are invalid */ - private function parseDefinition($id, $service, $file, array $defaults) + private function parseDefinition(string $id, $service, string $file, array $defaults, bool $return = false, bool $trackBindings = true) { if (preg_match('/^_[a-zA-Z0-9_]*$/', $id)) { - @trigger_error(sprintf('Service names that start with an underscore are deprecated since Symfony 3.3 and will be reserved in 4.0. Rename the "%s" service or define it in XML instead.', $id), \E_USER_DEPRECATED); + throw new InvalidArgumentException(sprintf('Service names that start with an underscore are reserved. Rename the "%s" service or define it in XML instead.', $id)); } - if (\is_string($service) && 0 === strpos($service, '@')) { - $this->container->setAlias($id, $alias = new Alias(substr($service, 1))); + + if (\is_string($service) && str_starts_with($service, '@')) { + $alias = new Alias(substr($service, 1)); + if (isset($defaults['public'])) { $alias->setPublic($defaults['public']); } - return; + return $return ? $alias : $this->container->setAlias($id, $alias); } if (\is_array($service) && $this->isUsingShortSyntax($service)) { @@ -348,13 +367,58 @@ class YamlFileLoader extends FileLoader } if (!\is_array($service)) { - throw new InvalidArgumentException(sprintf('A service definition must be an array or a string starting with "@" but "%s" found for service "%s" in "%s". Check your YAML syntax.', \gettype($service), $id, $file)); + throw new InvalidArgumentException(sprintf('A service definition must be an array or a string starting with "@" but "%s" found for service "%s" in "%s". Check your YAML syntax.', get_debug_type($service), $id, $file)); } + if (isset($service['stack'])) { + if (!\is_array($service['stack'])) { + throw new InvalidArgumentException(sprintf('A stack must be an array of definitions, "%s" given for service "%s" in "%s". Check your YAML syntax.', get_debug_type($service), $id, $file)); + } + + $stack = []; + + foreach ($service['stack'] as $k => $frame) { + if (\is_array($frame) && 1 === \count($frame) && !isset(self::SERVICE_KEYWORDS[key($frame)])) { + $frame = [ + 'class' => key($frame), + 'arguments' => current($frame), + ]; + } + + if (\is_array($frame) && isset($frame['stack'])) { + throw new InvalidArgumentException(sprintf('Service stack "%s" cannot contain another stack in "%s".', $id, $file)); + } + + $definition = $this->parseDefinition($id.'" at index "'.$k, $frame, $file, $defaults, true); + + if ($definition instanceof Definition) { + $definition->setInstanceofConditionals($this->instanceof); + } + + $stack[$k] = $definition; + } + + if ($diff = array_diff(array_keys($service), ['stack', 'public', 'deprecated'])) { + throw new InvalidArgumentException(sprintf('Invalid attribute "%s"; supported ones are "public" and "deprecated" for service "%s" in "%s". Check your YAML syntax.', implode('", "', $diff), $id, $file)); + } + + $service = [ + 'parent' => '', + 'arguments' => $stack, + 'tags' => ['container.stack'], + 'public' => $service['public'] ?? null, + 'deprecated' => $service['deprecated'] ?? null, + ]; + } + + $definition = isset($service[0]) && $service[0] instanceof Definition ? array_shift($service) : null; + $return = null === $definition ? $return : true; + $this->checkDefinition($id, $service, $file); if (isset($service['alias'])) { - $this->container->setAlias($id, $alias = new Alias($service['alias'])); + $alias = new Alias($service['alias']); + if (isset($service['public'])) { $alias->setPublic($service['public']); } elseif (isset($defaults['public'])) { @@ -362,52 +426,54 @@ class YamlFileLoader extends FileLoader } foreach ($service as $key => $value) { - if (!\in_array($key, ['alias', 'public'])) { - @trigger_error(sprintf('The configuration key "%s" is unsupported for the service "%s" which is defined as an alias in "%s". Allowed configuration keys for service aliases are "alias" and "public". The YamlFileLoader will raise an exception in Symfony 4.0, instead of silently ignoring unsupported attributes.', $key, $id, $file), \E_USER_DEPRECATED); + if (!\in_array($key, ['alias', 'public', 'deprecated'])) { + throw new InvalidArgumentException(sprintf('The configuration key "%s" is unsupported for the service "%s" which is defined as an alias in "%s". Allowed configuration keys for service aliases are "alias", "public" and "deprecated".', $key, $id, $file)); + } + + if ('deprecated' === $key) { + $deprecation = \is_array($value) ? $value : ['message' => $value]; + + if (!isset($deprecation['package'])) { + trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the "deprecated" option in "%s" is deprecated.', $file); + } + + if (!isset($deprecation['version'])) { + trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the "deprecated" option in "%s" is deprecated.', $file); + } + + $alias->setDeprecated($deprecation['package'] ?? '', $deprecation['version'] ?? '', $deprecation['message'] ?? ''); } } - return; + return $return ? $alias : $this->container->setAlias($id, $alias); } - if ($this->isLoadingInstanceof) { + if (null !== $definition) { + // no-op + } elseif ($this->isLoadingInstanceof) { $definition = new ChildDefinition(''); } elseif (isset($service['parent'])) { - if (!empty($this->instanceof)) { - throw new InvalidArgumentException(sprintf('The service "%s" cannot use the "parent" option in the same file where "_instanceof" configuration is defined as using both is not supported. Move your child definitions to a separate file.', $id)); - } - - foreach ($defaults as $k => $v) { - if ('tags' === $k) { - // since tags are never inherited from parents, there is no confusion - // thus we can safely add them as defaults to ChildDefinition - continue; - } - if ('bind' === $k) { - throw new InvalidArgumentException(sprintf('Attribute "bind" on service "%s" cannot be inherited from "_defaults" when a "parent" is set. Move your child definitions to a separate file.', $id)); - } - if (!isset($service[$k])) { - throw new InvalidArgumentException(sprintf('Attribute "%s" on service "%s" cannot be inherited from "_defaults" when a "parent" is set. Move your child definitions to a separate file or define this attribute explicitly.', $k, $id)); - } + if ('' !== $service['parent'] && '@' === $service['parent'][0]) { + throw new InvalidArgumentException(sprintf('The value of the "parent" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s").', $id, $service['parent'], substr($service['parent'], 1))); } $definition = new ChildDefinition($service['parent']); } else { $definition = new Definition(); - - if (isset($defaults['public'])) { - $definition->setPublic($defaults['public']); - } - if (isset($defaults['autowire'])) { - $definition->setAutowired($defaults['autowire']); - } - if (isset($defaults['autoconfigure'])) { - $definition->setAutoconfigured($defaults['autoconfigure']); - } - - $definition->setChanges([]); } + if (isset($defaults['public'])) { + $definition->setPublic($defaults['public']); + } + if (isset($defaults['autowire'])) { + $definition->setAutowired($defaults['autowire']); + } + if (isset($defaults['autoconfigure'])) { + $definition->setAutoconfigured($defaults['autoconfigure']); + } + + $definition->setChanges([]); + if (isset($service['class'])) { $definition->setClass($service['class']); } @@ -421,7 +487,10 @@ class YamlFileLoader extends FileLoader } if (isset($service['lazy'])) { - $definition->setLazy($service['lazy']); + $definition->setLazy((bool) $service['lazy']); + if (\is_string($service['lazy'])) { + $definition->addTag('proxy', ['interface' => $service['lazy']]); + } } if (isset($service['public'])) { @@ -432,8 +501,18 @@ class YamlFileLoader extends FileLoader $definition->setAbstract($service['abstract']); } - if (\array_key_exists('deprecated', $service)) { - $definition->setDeprecated(true, $service['deprecated']); + if (isset($service['deprecated'])) { + $deprecation = \is_array($service['deprecated']) ? $service['deprecated'] : ['message' => $service['deprecated']]; + + if (!isset($deprecation['package'])) { + trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "package" of the "deprecated" option in "%s" is deprecated.', $file); + } + + if (!isset($deprecation['version'])) { + trigger_deprecation('symfony/dependency-injection', '5.1', 'Not setting the attribute "version" of the "deprecated" option in "%s" is deprecated.', $file); + } + + $definition->setDeprecated($deprecation['package'] ?? '', $deprecation['version'] ?? '', $deprecation['message'] ?? ''); } if (isset($service['factory'])) { @@ -461,23 +540,53 @@ class YamlFileLoader extends FileLoader throw new InvalidArgumentException(sprintf('Parameter "calls" must be an array for service "%s" in "%s". Check your YAML syntax.', $id, $file)); } - foreach ($service['calls'] as $call) { - if (isset($call['method'])) { + foreach ($service['calls'] as $k => $call) { + if (!\is_array($call) && (!\is_string($k) || !$call instanceof TaggedValue)) { + throw new InvalidArgumentException(sprintf('Invalid method call for service "%s": expected map or array, "%s" given in "%s".', $id, $call instanceof TaggedValue ? '!'.$call->getTag() : get_debug_type($call), $file)); + } + + if (\is_string($k)) { + throw new InvalidArgumentException(sprintf('Invalid method call for service "%s", did you forgot a leading dash before "%s: ..." in "%s"?', $id, $k, $file)); + } + + if (isset($call['method']) && \is_string($call['method'])) { $method = $call['method']; - $args = isset($call['arguments']) ? $this->resolveServices($call['arguments'], $file) : []; + $args = $call['arguments'] ?? []; + $returnsClone = $call['returns_clone'] ?? false; } else { - $method = $call[0]; - $args = isset($call[1]) ? $this->resolveServices($call[1], $file) : []; + if (1 === \count($call) && \is_string(key($call))) { + $method = key($call); + $args = $call[$method]; + + if ($args instanceof TaggedValue) { + if ('returns_clone' !== $args->getTag()) { + throw new InvalidArgumentException(sprintf('Unsupported tag "!%s", did you mean "!returns_clone" for service "%s" in "%s"?', $args->getTag(), $id, $file)); + } + + $returnsClone = true; + $args = $args->getValue(); + } else { + $returnsClone = false; + } + } elseif (empty($call[0])) { + throw new InvalidArgumentException(sprintf('Invalid call for service "%s": the method must be defined as the first index of an array or as the only key of a map in "%s".', $id, $file)); + } else { + $method = $call[0]; + $args = $call[1] ?? []; + $returnsClone = $call[2] ?? false; + } } if (!\is_array($args)) { throw new InvalidArgumentException(sprintf('The second parameter for function call "%s" must be an array of its arguments for service "%s" in "%s". Check your YAML syntax.', $method, $id, $file)); } - $definition->addMethodCall($method, $args); + + $args = $this->resolveServices($args, $file); + $definition->addMethodCall($method, $args, $returnsClone); } } - $tags = isset($service['tags']) ? $service['tags'] : []; + $tags = $service['tags'] ?? []; if (!\is_array($tags)) { throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in "%s". Check your YAML syntax.', $id, $file)); } @@ -491,18 +600,23 @@ class YamlFileLoader extends FileLoader $tag = ['name' => $tag]; } - if (!isset($tag['name'])) { - throw new InvalidArgumentException(sprintf('A "tags" entry is missing a "name" key for service "%s" in "%s".', $id, $file)); + if (1 === \count($tag) && \is_array(current($tag))) { + $name = key($tag); + $tag = current($tag); + } else { + if (!isset($tag['name'])) { + throw new InvalidArgumentException(sprintf('A "tags" entry is missing a "name" key for service "%s" in "%s".', $id, $file)); + } + $name = $tag['name']; + unset($tag['name']); } - $name = $tag['name']; - unset($tag['name']); if (!\is_string($name) || '' === $name) { throw new InvalidArgumentException(sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', $id, $file)); } foreach ($tag as $attribute => $value) { - if (!is_scalar($value) && null !== $value) { + if (!\is_scalar($value) && null !== $value) { throw new InvalidArgumentException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in "%s". Check your YAML syntax.', $id, $name, $attribute, $file)); } } @@ -510,41 +624,38 @@ class YamlFileLoader extends FileLoader $definition->addTag($name, $tag); } - if (isset($service['decorates'])) { - if ('' !== $service['decorates'] && '@' === $service['decorates'][0]) { - throw new InvalidArgumentException(sprintf('The value of the "decorates" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s").', $id, $service['decorates'], substr($service['decorates'], 1))); + if (null !== $decorates = $service['decorates'] ?? null) { + if ('' !== $decorates && '@' === $decorates[0]) { + throw new InvalidArgumentException(sprintf('The value of the "decorates" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s").', $id, $service['decorates'], substr($decorates, 1))); } - $renameId = isset($service['decoration_inner_name']) ? $service['decoration_inner_name'] : null; - $priority = isset($service['decoration_priority']) ? $service['decoration_priority'] : 0; - $definition->setDecoratedService($service['decorates'], $renameId, $priority); + $decorationOnInvalid = \array_key_exists('decoration_on_invalid', $service) ? $service['decoration_on_invalid'] : 'exception'; + if ('exception' === $decorationOnInvalid) { + $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + } elseif ('ignore' === $decorationOnInvalid) { + $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + } elseif (null === $decorationOnInvalid) { + $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; + } elseif ('null' === $decorationOnInvalid) { + throw new InvalidArgumentException(sprintf('Invalid value "%s" for attribute "decoration_on_invalid" on service "%s". Did you mean null (without quotes) in "%s"?', $decorationOnInvalid, $id, $file)); + } else { + throw new InvalidArgumentException(sprintf('Invalid value "%s" for attribute "decoration_on_invalid" on service "%s". Did you mean "exception", "ignore" or null in "%s"?', $decorationOnInvalid, $id, $file)); + } + + $renameId = $service['decoration_inner_name'] ?? null; + $priority = $service['decoration_priority'] ?? 0; + + $definition->setDecoratedService($decorates, $renameId, $priority, $invalidBehavior); } if (isset($service['autowire'])) { $definition->setAutowired($service['autowire']); } - if (isset($service['autowiring_types'])) { - if (\is_string($service['autowiring_types'])) { - $definition->addAutowiringType($service['autowiring_types']); - } else { - if (!\is_array($service['autowiring_types'])) { - throw new InvalidArgumentException(sprintf('Parameter "autowiring_types" must be a string or an array for service "%s" in "%s". Check your YAML syntax.', $id, $file)); - } - - foreach ($service['autowiring_types'] as $autowiringType) { - if (!\is_string($autowiringType)) { - throw new InvalidArgumentException(sprintf('A "autowiring_types" attribute must be of type string for service "%s" in "%s". Check your YAML syntax.', $id, $file)); - } - - $definition->addAutowiringType($autowiringType); - } - } - } - if (isset($defaults['bind']) || isset($service['bind'])) { // deep clone, to avoid multiple process of the same instance in the passes - $bindings = isset($defaults['bind']) ? unserialize(serialize($defaults['bind'])) : []; + $bindings = $definition->getBindings(); + $bindings += isset($defaults['bind']) ? unserialize(serialize($defaults['bind'])) : []; if (isset($service['bind'])) { if (!\is_array($service['bind'])) { @@ -552,29 +663,39 @@ class YamlFileLoader extends FileLoader } $bindings = array_merge($bindings, $this->resolveServices($service['bind'], $file)); + $bindingType = $this->isLoadingInstanceof ? BoundArgument::INSTANCEOF_BINDING : BoundArgument::SERVICE_BINDING; + foreach ($bindings as $argument => $value) { + if (!$value instanceof BoundArgument) { + $bindings[$argument] = new BoundArgument($value, $trackBindings, $bindingType, $file); + } + } } $definition->setBindings($bindings); } if (isset($service['autoconfigure'])) { - if (!$definition instanceof ChildDefinition) { - $definition->setAutoconfigured($service['autoconfigure']); - } elseif ($service['autoconfigure']) { - throw new InvalidArgumentException(sprintf('The service "%s" cannot have a "parent" and also have "autoconfigure". Try setting "autoconfigure: false" for the service.', $id)); - } + $definition->setAutoconfigured($service['autoconfigure']); } if (\array_key_exists('namespace', $service) && !\array_key_exists('resource', $service)) { throw new InvalidArgumentException(sprintf('A "resource" attribute must be set when the "namespace" attribute is set for service "%s" in "%s". Check your YAML syntax.', $id, $file)); } + if ($return) { + if (\array_key_exists('resource', $service)) { + throw new InvalidArgumentException(sprintf('Invalid "resource" attribute found for service "%s" in "%s". Check your YAML syntax.', $id, $file)); + } + + return $definition; + } + if (\array_key_exists('resource', $service)) { if (!\is_string($service['resource'])) { throw new InvalidArgumentException(sprintf('A "resource" attribute must be of type string for service "%s" in "%s". Check your YAML syntax.', $id, $file)); } - $exclude = isset($service['exclude']) ? $service['exclude'] : null; - $namespace = isset($service['namespace']) ? $service['namespace'] : $id; + $exclude = $service['exclude'] ?? null; + $namespace = $service['namespace'] ?? $id; $this->registerClasses($definition, $namespace, $service['resource'], $exclude); } else { $this->setDefinition($id, $definition); @@ -584,26 +705,21 @@ class YamlFileLoader extends FileLoader /** * Parses a callable. * - * @param string|array $callable A callable - * @param string $parameter A parameter (e.g. 'factory' or 'configurator') - * @param string $id A service identifier - * @param string $file A parsed file + * @param string|array $callable A callable reference * * @throws InvalidArgumentException When errors occur * - * @return string|array A parsed callable + * @return string|array|Reference */ - private function parseCallable($callable, $parameter, $id, $file) + private function parseCallable($callable, string $parameter, string $id, string $file) { if (\is_string($callable)) { if ('' !== $callable && '@' === $callable[0]) { - throw new InvalidArgumentException(sprintf('The value of the "%s" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s").', $parameter, $id, $callable, substr($callable, 1))); - } + if (!str_contains($callable, ':')) { + return [$this->resolveServices($callable, $file), '__invoke']; + } - if (false !== strpos($callable, ':') && false === strpos($callable, '::')) { - $parts = explode(':', $callable); - - return [$this->resolveServices('@'.$parts[0], $file), $parts[1]]; + throw new InvalidArgumentException(sprintf('The value of the "%s" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s" in "%s").', $parameter, $id, $callable, substr($callable, 1), $file)); } return $callable; @@ -627,15 +743,13 @@ class YamlFileLoader extends FileLoader /** * Loads a YAML file. * - * @param string $file - * - * @return array The file content + * @return array|null * * @throws InvalidArgumentException when the given file is not a local file or when it does not exist */ - protected function loadFile($file) + protected function loadFile(string $file) { - if (!class_exists('Symfony\Component\Yaml\Parser')) { + if (!class_exists(\Symfony\Component\Yaml\Parser::class)) { throw new RuntimeException('Unable to load YAML config files as the Symfony Yaml Component is not installed.'); } @@ -643,7 +757,7 @@ class YamlFileLoader extends FileLoader throw new InvalidArgumentException(sprintf('This is not a local file "%s".', $file)); } - if (!file_exists($file)) { + if (!is_file($file)) { throw new InvalidArgumentException(sprintf('The file "%s" does not exist.', $file)); } @@ -651,18 +765,10 @@ class YamlFileLoader extends FileLoader $this->yamlParser = new YamlParser(); } - $prevErrorHandler = set_error_handler(function ($level, $message, $script, $line) use ($file, &$prevErrorHandler) { - $message = \E_USER_DEPRECATED === $level ? preg_replace('/ on line \d+/', ' in "'.$file.'"$0', $message) : $message; - - return $prevErrorHandler ? $prevErrorHandler($level, $message, $script, $line) : false; - }); - try { $configuration = $this->yamlParser->parseFile($file, Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS); } catch (ParseException $e) { throw new InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML: ', $file).$e->getMessage(), 0, $e); - } finally { - restore_error_handler(); } return $this->validate($configuration, $file); @@ -671,14 +777,9 @@ class YamlFileLoader extends FileLoader /** * Validates a YAML file. * - * @param mixed $content - * @param string $file - * - * @return array - * * @throws InvalidArgumentException When service file is not valid */ - private function validate($content, $file) + private function validate($content, string $file): ?array { if (null === $content) { return $content; @@ -689,12 +790,12 @@ class YamlFileLoader extends FileLoader } foreach ($content as $namespace => $data) { - if (\in_array($namespace, ['imports', 'parameters', 'services'])) { + if (\in_array($namespace, ['imports', 'parameters', 'services']) || 0 === strpos($namespace, 'when@')) { continue; } if (!$this->container->hasExtension($namespace)) { - $extensionNamespaces = array_filter(array_map(function ($ext) { return $ext->getAlias(); }, $this->container->getExtensions())); + $extensionNamespaces = array_filter(array_map(function (ExtensionInterface $ext) { return $ext->getAlias(); }, $this->container->getExtensions())); throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in "%s"). Looked for namespace "%s", found "%s".', $namespace, $file, $namespace, $extensionNamespaces ? sprintf('"%s"', implode('", "', $extensionNamespaces)) : 'none')); } } @@ -703,15 +804,9 @@ class YamlFileLoader extends FileLoader } /** - * Resolves services. - * - * @param mixed $value - * @param string $file - * @param bool $isParameter - * - * @return array|string|Reference|ArgumentInterface + * @return mixed */ - private function resolveServices($value, $file, $isParameter = false) + private function resolveServices($value, string $file, bool $isParameter = false) { if ($value instanceof TaggedValue) { $argument = $value->getValue(); @@ -726,12 +821,48 @@ class YamlFileLoader extends FileLoader throw new InvalidArgumentException(sprintf('"!iterator" tag only accepts arrays of "@service" references in "%s".', $file)); } } - if ('tagged' === $value->getTag()) { - if (!\is_string($argument) || !$argument) { - throw new InvalidArgumentException(sprintf('"!tagged" tag only accepts non empty string in "%s".', $file)); + if ('service_closure' === $value->getTag()) { + $argument = $this->resolveServices($argument, $file, $isParameter); + + if (!$argument instanceof Reference) { + throw new InvalidArgumentException(sprintf('"!service_closure" tag only accepts service references in "%s".', $file)); } - return new TaggedIteratorArgument($argument); + return new ServiceClosureArgument($argument); + } + if ('service_locator' === $value->getTag()) { + if (!\is_array($argument)) { + throw new InvalidArgumentException(sprintf('"!service_locator" tag only accepts maps in "%s".', $file)); + } + + $argument = $this->resolveServices($argument, $file, $isParameter); + + try { + return new ServiceLocatorArgument($argument); + } catch (InvalidArgumentException $e) { + throw new InvalidArgumentException(sprintf('"!service_locator" tag only accepts maps of "@service" references in "%s".', $file)); + } + } + if (\in_array($value->getTag(), ['tagged', 'tagged_iterator', 'tagged_locator'], true)) { + $forLocator = 'tagged_locator' === $value->getTag(); + + if (\is_array($argument) && isset($argument['tag']) && $argument['tag']) { + if ($diff = array_diff(array_keys($argument), ['tag', 'index_by', 'default_index_method', 'default_priority_method'])) { + throw new InvalidArgumentException(sprintf('"!%s" tag contains unsupported key "%s"; supported ones are "tag", "index_by", "default_index_method", and "default_priority_method".', $value->getTag(), implode('", "', $diff))); + } + + $argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null, $forLocator, $argument['default_priority_method'] ?? null); + } elseif (\is_string($argument) && $argument) { + $argument = new TaggedIteratorArgument($argument, null, null, $forLocator); + } else { + throw new InvalidArgumentException(sprintf('"!%s" tags only accept a non empty string or an array with a key "tag" in "%s".', $value->getTag(), $file)); + } + + if ($forLocator) { + $argument = new ServiceLocatorArgument($argument); + } + + return $argument; } if ('service' === $value->getTag()) { if ($isParameter) { @@ -743,20 +874,23 @@ class YamlFileLoader extends FileLoader $instanceof = $this->instanceof; $this->instanceof = []; - $id = sprintf('%d_%s', ++$this->anonymousServicesCount, preg_replace('/^.*\\\\/', '', isset($argument['class']) ? $argument['class'] : '').$this->anonymousServicesSuffix); + $id = sprintf('.%d_%s', ++$this->anonymousServicesCount, preg_replace('/^.*\\\\/', '', $argument['class'] ?? '').$this->anonymousServicesSuffix); $this->parseDefinition($id, $argument, $file, []); if (!$this->container->hasDefinition($id)) { throw new InvalidArgumentException(sprintf('Creating an alias using the tag "!service" is not allowed in "%s".', $file)); } - $this->container->getDefinition($id)->setPublic(false); + $this->container->getDefinition($id); $this->isLoadingInstanceof = $isLoadingInstanceof; $this->instanceof = $instanceof; return new Reference($id); } + if ('abstract' === $value->getTag()) { + return new AbstractArgument($value->getValue()); + } throw new InvalidArgumentException(sprintf('Unsupported tag "!%s".', $value->getTag())); } @@ -765,20 +899,24 @@ class YamlFileLoader extends FileLoader foreach ($value as $k => $v) { $value[$k] = $this->resolveServices($v, $file, $isParameter); } - } elseif (\is_string($value) && 0 === strpos($value, '@=')) { + } elseif (\is_string($value) && str_starts_with($value, '@=')) { + if ($isParameter) { + throw new InvalidArgumentException(sprintf('Using expressions in parameters is not allowed in "%s".', $file)); + } + if (!class_exists(Expression::class)) { - throw new \LogicException(sprintf('The "@=" expression syntax cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".')); + throw new \LogicException('The "@=" expression syntax cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'); } return new Expression(substr($value, 2)); - } elseif (\is_string($value) && 0 === strpos($value, '@')) { - if (0 === strpos($value, '@@')) { + } elseif (\is_string($value) && str_starts_with($value, '@')) { + if (str_starts_with($value, '@@')) { $value = substr($value, 1); $invalidBehavior = null; - } elseif (0 === strpos($value, '@!')) { + } elseif (str_starts_with($value, '@!')) { $value = substr($value, 2); $invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE; - } elseif (0 === strpos($value, '@?')) { + } elseif (str_starts_with($value, '@?')) { $value = substr($value, 2); $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; } else { @@ -786,11 +924,6 @@ class YamlFileLoader extends FileLoader $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; } - if ('=' === substr($value, -1)) { - @trigger_error(sprintf('The "=" suffix that used to disable strict references in Symfony 2.x is deprecated since Symfony 3.3 and will be unsupported in 4.0. Remove it in "%s".', $value), \E_USER_DEPRECATED); - $value = substr($value, 0, -1); - } - if (null !== $invalidBehavior) { $value = new Reference($value, $invalidBehavior); } @@ -799,13 +932,10 @@ class YamlFileLoader extends FileLoader return $value; } - /** - * Loads from Extensions. - */ private function loadFromExtensions(array $content) { foreach ($content as $namespace => $values) { - if (\in_array($namespace, ['imports', 'parameters', 'services'])) { + if (\in_array($namespace, ['imports', 'parameters', 'services']) || 0 === strpos($namespace, 'when@')) { continue; } @@ -817,30 +947,19 @@ class YamlFileLoader extends FileLoader } } - /** - * Checks the keywords used to define a service. - * - * @param string $id The service name - * @param array $definition The service definition to check - * @param string $file The loaded YAML file - */ - private function checkDefinition($id, array $definition, $file) + private function checkDefinition(string $id, array $definition, string $file) { - if ($throw = $this->isLoadingInstanceof) { - $keywords = self::$instanceofKeywords; - } elseif ($throw = (isset($definition['resource']) || isset($definition['namespace']))) { - $keywords = self::$prototypeKeywords; + if ($this->isLoadingInstanceof) { + $keywords = self::INSTANCEOF_KEYWORDS; + } elseif (isset($definition['resource']) || isset($definition['namespace'])) { + $keywords = self::PROTOTYPE_KEYWORDS; } else { - $keywords = self::$serviceKeywords; + $keywords = self::SERVICE_KEYWORDS; } foreach ($definition as $key => $value) { if (!isset($keywords[$key])) { - if ($throw) { - throw new InvalidArgumentException(sprintf('The configuration key "%s" is unsupported for definition "%s" in "%s". Allowed configuration keys are "%s".', $key, $id, $file, implode('", "', $keywords))); - } - - @trigger_error(sprintf('The configuration key "%s" is unsupported for service definition "%s" in "%s". Allowed configuration keys are "%s". The YamlFileLoader object will raise an exception instead in Symfony 4.0 when detecting an unsupported service configuration key.', $key, $id, $file, implode('", "', $keywords)), \E_USER_DEPRECATED); + throw new InvalidArgumentException(sprintf('The configuration key "%s" is unsupported for definition "%s" in "%s". Allowed configuration keys are "%s".', $key, $id, $file, implode('", "', $keywords))); } } } diff --git a/lib/symfony/dependency-injection/Loader/schema/dic/services/services-1.0.xsd b/lib/symfony/dependency-injection/Loader/schema/dic/services/services-1.0.xsd index 3a55a7df67..3c30002542 100644 --- a/lib/symfony/dependency-injection/Loader/schema/dic/services/services-1.0.xsd +++ b/lib/symfony/dependency-injection/Loader/schema/dic/services/services-1.0.xsd @@ -37,9 +37,31 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -57,6 +79,7 @@ + @@ -78,7 +101,7 @@ ]]> - + @@ -113,11 +136,10 @@ - + - @@ -125,11 +147,12 @@ - + + @@ -142,11 +165,12 @@ + - + @@ -156,27 +180,50 @@ - + + - + + + + + + + + + + - - + + + + + + + + + + + + + + + @@ -207,7 +254,6 @@ - @@ -221,6 +267,7 @@ + @@ -233,8 +280,10 @@ - + + + @@ -242,6 +291,7 @@ + @@ -249,18 +299,32 @@ + + + + + + + + + + + + + + @@ -273,6 +337,14 @@ + + + + + + + + diff --git a/lib/symfony/dependency-injection/Parameter.php b/lib/symfony/dependency-injection/Parameter.php index cac6f6c4c2..e182e12404 100644 --- a/lib/symfony/dependency-injection/Parameter.php +++ b/lib/symfony/dependency-injection/Parameter.php @@ -20,19 +20,16 @@ class Parameter { private $id; - /** - * @param string $id The parameter key - */ - public function __construct($id) + public function __construct(string $id) { $this->id = $id; } /** - * @return string The parameter key + * @return string */ public function __toString() { - return (string) $this->id; + return $this->id; } } diff --git a/lib/symfony/dependency-injection/ParameterBag/ContainerBag.php b/lib/symfony/dependency-injection/ParameterBag/ContainerBag.php new file mode 100644 index 0000000000..54aaa556b0 --- /dev/null +++ b/lib/symfony/dependency-injection/ParameterBag/ContainerBag.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\ParameterBag; + +use Symfony\Component\DependencyInjection\Container; + +/** + * @author Nicolas Grekas + */ +class ContainerBag extends FrozenParameterBag implements ContainerBagInterface +{ + private $container; + + public function __construct(Container $container) + { + $this->container = $container; + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->container->getParameterBag()->all(); + } + + /** + * {@inheritdoc} + * + * @return array|bool|string|int|float|\UnitEnum|null + */ + public function get(string $name) + { + return $this->container->getParameter($name); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function has(string $name) + { + return $this->container->hasParameter($name); + } +} diff --git a/lib/symfony/dependency-injection/ParameterBag/ContainerBagInterface.php b/lib/symfony/dependency-injection/ParameterBag/ContainerBagInterface.php new file mode 100644 index 0000000000..f8380ac974 --- /dev/null +++ b/lib/symfony/dependency-injection/ParameterBag/ContainerBagInterface.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\ParameterBag; + +use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; + +/** + * ContainerBagInterface is the interface implemented by objects that manage service container parameters. + * + * @author Nicolas Grekas + */ +interface ContainerBagInterface extends ContainerInterface +{ + /** + * Gets the service container parameters. + * + * @return array + */ + public function all(); + + /** + * Replaces parameter placeholders (%name%) by their values. + * + * @param mixed $value A value + * + * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist + */ + public function resolveValue($value); + + /** + * Escape parameter placeholders %. + * + * @param mixed $value + * + * @return mixed + */ + public function escapeValue($value); + + /** + * Unescape parameter placeholders %. + * + * @param mixed $value + * + * @return mixed + */ + public function unescapeValue($value); +} diff --git a/lib/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php b/lib/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php index c4e369b010..67b8aeeb13 100644 --- a/lib/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php +++ b/lib/symfony/dependency-injection/ParameterBag/EnvPlaceholderParameterBag.php @@ -19,15 +19,19 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; */ class EnvPlaceholderParameterBag extends ParameterBag { + private $envPlaceholderUniquePrefix; private $envPlaceholders = []; + private $unusedEnvPlaceholders = []; private $providedTypes = []; + private static $counter = 0; + /** * {@inheritdoc} */ - public function get($name) + public function get(string $name) { - if (0 === strpos($name, 'env(') && ')' === substr($name, -1) && 'env()' !== $name) { + if (str_starts_with($name, 'env(') && str_ends_with($name, ')') && 'env()' !== $name) { $env = substr($name, 4, -1); if (isset($this->envPlaceholders[$env])) { @@ -35,20 +39,20 @@ class EnvPlaceholderParameterBag extends ParameterBag return $placeholder; // return first result } } - if (!preg_match('/^(?:\w++:)*+\w++$/', $env)) { - throw new InvalidArgumentException(sprintf('Invalid "%s" name: only "word" characters are allowed.', $name)); - } - - if ($this->has($name)) { - $defaultValue = parent::get($name); - - if (null !== $defaultValue && !is_scalar($defaultValue)) { - throw new RuntimeException(sprintf('The default value of an env() parameter must be scalar or null, but "%s" given to "%s".', \gettype($defaultValue), $name)); + if (isset($this->unusedEnvPlaceholders[$env])) { + foreach ($this->unusedEnvPlaceholders[$env] as $placeholder) { + return $placeholder; // return first result } } + if (!preg_match('/^(?:[-.\w]*+:)*+\w++$/', $env)) { + throw new InvalidArgumentException(sprintf('Invalid %s name: only "word" characters are allowed.', $name)); + } + if ($this->has($name) && null !== ($defaultValue = parent::get($name)) && !\is_string($defaultValue)) { + throw new RuntimeException(sprintf('The default value of an env() parameter must be a string or null, but "%s" given to "%s".', get_debug_type($defaultValue), $name)); + } - $uniqueName = md5($name.uniqid(mt_rand(), true)); - $placeholder = sprintf('env_%s_%s', str_replace(':', '_', $env), $uniqueName); + $uniqueName = md5($name.'_'.self::$counter++); + $placeholder = sprintf('%s_%s_%s', $this->getEnvPlaceholderUniquePrefix(), strtr($env, ':-.', '___'), $uniqueName); $this->envPlaceholders[$env][$placeholder] = $placeholder; return $placeholder; @@ -57,6 +61,20 @@ class EnvPlaceholderParameterBag extends ParameterBag return parent::get($name); } + /** + * Gets the common env placeholder prefix for env vars created by this bag. + */ + public function getEnvPlaceholderUniquePrefix(): string + { + if (null === $this->envPlaceholderUniquePrefix) { + $reproducibleEntropy = unserialize(serialize($this->parameters)); + array_walk_recursive($reproducibleEntropy, function (&$v) { $v = null; }); + $this->envPlaceholderUniquePrefix = 'env_'.substr(md5(serialize($reproducibleEntropy)), -16); + } + + return $this->envPlaceholderUniquePrefix; + } + /** * Returns the map of env vars used in the resolved parameter values to their placeholders. * @@ -67,6 +85,16 @@ class EnvPlaceholderParameterBag extends ParameterBag return $this->envPlaceholders; } + public function getUnusedEnvPlaceholders(): array + { + return $this->unusedEnvPlaceholders; + } + + public function clearUnusedEnvPlaceholders() + { + $this->unusedEnvPlaceholders = []; + } + /** * Merges the env placeholders of another EnvPlaceholderParameterBag. */ @@ -79,6 +107,14 @@ class EnvPlaceholderParameterBag extends ParameterBag $this->envPlaceholders[$env] += $placeholders; } } + + if ($newUnusedPlaceholders = $bag->getUnusedEnvPlaceholders()) { + $this->unusedEnvPlaceholders += $newUnusedPlaceholders; + + foreach ($newUnusedPlaceholders as $env => $placeholders) { + $this->unusedEnvPlaceholders[$env] += $placeholders; + } + } } /** @@ -110,13 +146,8 @@ class EnvPlaceholderParameterBag extends ParameterBag parent::resolve(); foreach ($this->envPlaceholders as $env => $placeholders) { - if (!$this->has($name = "env($env)")) { - continue; - } - if (is_numeric($default = $this->parameters[$name])) { - $this->parameters[$name] = (string) $default; - } elseif (null !== $default && !is_scalar($default)) { - throw new RuntimeException(sprintf('The default value of env parameter "%s" must be scalar or null, "%s" given.', $env, \gettype($default))); + if ($this->has($name = "env($env)") && null !== ($default = $this->parameters[$name]) && !\is_string($default)) { + throw new RuntimeException(sprintf('The default value of env parameter "%s" must be a string or null, "%s" given.', $env, get_debug_type($default))); } } } diff --git a/lib/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php b/lib/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php index a5199937e2..5a4aaf8b2a 100644 --- a/lib/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php +++ b/lib/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php @@ -53,7 +53,7 @@ class FrozenParameterBag extends ParameterBag /** * {@inheritdoc} */ - public function set($name, $value) + public function set(string $name, $value) { throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); } @@ -61,7 +61,7 @@ class FrozenParameterBag extends ParameterBag /** * {@inheritdoc} */ - public function remove($name) + public function remove(string $name) { throw new LogicException('Impossible to call remove() on a frozen ParameterBag.'); } diff --git a/lib/symfony/dependency-injection/ParameterBag/ParameterBag.php b/lib/symfony/dependency-injection/ParameterBag/ParameterBag.php index 24dc8035fe..b1bf77568d 100644 --- a/lib/symfony/dependency-injection/ParameterBag/ParameterBag.php +++ b/lib/symfony/dependency-injection/ParameterBag/ParameterBag.php @@ -25,18 +25,13 @@ class ParameterBag implements ParameterBagInterface protected $parameters = []; protected $resolved = false; - private $normalizedNames = []; - - /** - * @param array $parameters An array of parameters - */ public function __construct(array $parameters = []) { $this->add($parameters); } /** - * Clears all parameters. + * {@inheritdoc} */ public function clear() { @@ -44,9 +39,7 @@ class ParameterBag implements ParameterBagInterface } /** - * Adds parameters to the service container parameters. - * - * @param array $parameters An array of parameters + * {@inheritdoc} */ public function add(array $parameters) { @@ -66,10 +59,8 @@ class ParameterBag implements ParameterBagInterface /** * {@inheritdoc} */ - public function get($name) + public function get(string $name) { - $name = $this->normalizeName($name); - if (!\array_key_exists($name, $this->parameters)) { if (!$name) { throw new ParameterNotFoundException($name); @@ -78,13 +69,13 @@ class ParameterBag implements ParameterBagInterface $alternatives = []; foreach ($this->parameters as $key => $parameterValue) { $lev = levenshtein($name, $key); - if ($lev <= \strlen($name) / 3 || false !== strpos($key, $name)) { + if ($lev <= \strlen($name) / 3 || str_contains($key, $name)) { $alternatives[] = $key; } } $nonNestedAlternative = null; - if (!\count($alternatives) && false !== strpos($name, '.')) { + if (!\count($alternatives) && str_contains($name, '.')) { $namePartsLength = array_map('strlen', explode('.', $name)); $key = substr($name, 0, -1 * (1 + array_pop($namePartsLength))); while (\count($namePartsLength)) { @@ -106,32 +97,27 @@ class ParameterBag implements ParameterBagInterface } /** - * Sets a service container parameter. - * - * @param string $name The parameter name - * @param mixed $value The parameter value + * {@inheritdoc} */ - public function set($name, $value) + public function set(string $name, $value) { - $this->parameters[$this->normalizeName($name)] = $value; + $this->parameters[$name] = $value; } /** * {@inheritdoc} */ - public function has($name) + public function has(string $name) { - return \array_key_exists($this->normalizeName($name), $this->parameters); + return \array_key_exists($name, $this->parameters); } /** - * Removes a parameter. - * - * @param string $name The parameter name + * {@inheritdoc} */ - public function remove($name) + public function remove(string $name) { - unset($this->parameters[$this->normalizeName($name)]); + unset($this->parameters[$name]); } /** @@ -165,7 +151,7 @@ class ParameterBag implements ParameterBagInterface * @param mixed $value A value * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) * - * @return mixed The resolved value + * @return mixed * * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist * @throws ParameterCircularReferenceException if a circular reference if detected @@ -192,29 +178,27 @@ class ParameterBag implements ParameterBagInterface /** * Resolves parameters inside a string. * - * @param string $value The string to resolve - * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) + * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) * - * @return mixed The resolved string + * @return mixed * * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist * @throws ParameterCircularReferenceException if a circular reference if detected * @throws RuntimeException when a given parameter has a type problem */ - public function resolveString($value, array $resolving = []) + public function resolveString(string $value, array $resolving = []) { // we do this to deal with non string values (Boolean, integer, ...) // as the preg_replace_callback throw an exception when trying // a non-string in a parameter value if (preg_match('/^%([^%\s]+)%$/', $value, $match)) { $key = $match[1]; - $lcKey = strtolower($key); // strtolower() to be removed in 4.0 - if (isset($resolving[$lcKey])) { + if (isset($resolving[$key])) { throw new ParameterCircularReferenceException(array_keys($resolving)); } - $resolving[$lcKey] = true; + $resolving[$key] = true; return $this->resolved ? $this->get($key) : $this->resolveValue($this->get($key), $resolving); } @@ -226,19 +210,18 @@ class ParameterBag implements ParameterBagInterface } $key = $match[1]; - $lcKey = strtolower($key); // strtolower() to be removed in 4.0 - if (isset($resolving[$lcKey])) { + if (isset($resolving[$key])) { throw new ParameterCircularReferenceException(array_keys($resolving)); } $resolved = $this->get($key); if (!\is_string($resolved) && !is_numeric($resolved)) { - throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "%s" of type "%s" inside string value "%s".', $key, \gettype($resolved), $value)); + throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "%s" of type "%s" inside string value "%s".', $key, get_debug_type($resolved), $value)); } $resolved = (string) $resolved; - $resolving[$lcKey] = true; + $resolving[$key] = true; return $this->isResolved() ? $resolved : $this->resolveString($resolved, $resolving); }, $value); @@ -290,18 +273,4 @@ class ParameterBag implements ParameterBagInterface return $value; } - - private function normalizeName($name) - { - if (isset($this->normalizedNames[$normalizedName = strtolower($name)])) { - $normalizedName = $this->normalizedNames[$normalizedName]; - if ((string) $name !== $normalizedName) { - @trigger_error(sprintf('Parameter names will be made case sensitive in Symfony 4.0. Using "%s" instead of "%s" is deprecated since Symfony 3.4.', $name, $normalizedName), \E_USER_DEPRECATED); - } - } else { - $normalizedName = $this->normalizedNames[$normalizedName] = (string) $name; - } - - return $normalizedName; - } } diff --git a/lib/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php b/lib/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php index 7386df0648..808a0fa423 100644 --- a/lib/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php +++ b/lib/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php @@ -15,7 +15,7 @@ use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; /** - * ParameterBagInterface. + * ParameterBagInterface is the interface implemented by objects that manage service container parameters. * * @author Fabien Potencier */ @@ -24,62 +24,53 @@ interface ParameterBagInterface /** * Clears all parameters. * - * @throws LogicException if the ParameterBagInterface can not be cleared + * @throws LogicException if the ParameterBagInterface cannot be cleared */ public function clear(); /** * Adds parameters to the service container parameters. * - * @param array $parameters An array of parameters - * - * @throws LogicException if the parameter can not be added + * @throws LogicException if the parameter cannot be added */ public function add(array $parameters); /** * Gets the service container parameters. * - * @return array An array of parameters + * @return array */ public function all(); /** * Gets a service container parameter. * - * @param string $name The parameter name - * - * @return mixed The parameter value + * @return array|bool|string|int|float|\UnitEnum|null * * @throws ParameterNotFoundException if the parameter is not defined */ - public function get($name); + public function get(string $name); /** * Removes a parameter. - * - * @param string $name The parameter name */ - public function remove($name); + public function remove(string $name); /** * Sets a service container parameter. * - * @param string $name The parameter name - * @param mixed $value The parameter value + * @param array|bool|string|int|float|\UnitEnum|null $value The parameter value * - * @throws LogicException if the parameter can not be set + * @throws LogicException if the parameter cannot be set */ - public function set($name, $value); + public function set(string $name, $value); /** * Returns true if a parameter name is defined. * - * @param string $name The parameter name - * - * @return bool true if the parameter name is defined, false otherwise + * @return bool */ - public function has($name); + public function has(string $name); /** * Replaces parameter placeholders (%name%) by their values for all parameters. diff --git a/lib/symfony/dependency-injection/README.md b/lib/symfony/dependency-injection/README.md index cb2d4a11c5..fa6719a799 100644 --- a/lib/symfony/dependency-injection/README.md +++ b/lib/symfony/dependency-injection/README.md @@ -7,8 +7,8 @@ way objects are constructed in your application. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/dependency_injection.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) + * [Documentation](https://symfony.com/doc/current/components/dependency_injection.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/lib/symfony/dependency-injection/Reference.php b/lib/symfony/dependency-injection/Reference.php index 82906d2b75..7f7b32cc6f 100644 --- a/lib/symfony/dependency-injection/Reference.php +++ b/lib/symfony/dependency-injection/Reference.php @@ -21,20 +21,14 @@ class Reference private $id; private $invalidBehavior; - /** - * @param string $id The service identifier - * @param int $invalidBehavior The behavior when the service does not exist - * - * @see Container - */ - public function __construct($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) + public function __construct(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { - $this->id = (string) $id; + $this->id = $id; $this->invalidBehavior = $invalidBehavior; } /** - * @return string The service identifier + * @return string */ public function __toString() { diff --git a/lib/symfony/dependency-injection/ResettableContainerInterface.php b/lib/symfony/dependency-injection/ResettableContainerInterface.php deleted file mode 100644 index b74e676245..0000000000 --- a/lib/symfony/dependency-injection/ResettableContainerInterface.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DependencyInjection; - -/** - * ResettableContainerInterface defines additional resetting functionality - * for containers, allowing to release shared services when the container is - * not needed anymore. - * - * @author Christophe Coevoet - */ -interface ResettableContainerInterface extends ContainerInterface -{ - /** - * Resets shared services from the container. - * - * The container is not intended to be used again after being reset in a normal workflow. This method is - * meant as a way to release references for ref-counting. - * A subsequent call to ContainerInterface::get will recreate a new instance of the shared service. - */ - public function reset(); -} diff --git a/lib/symfony/dependency-injection/ReverseContainer.php b/lib/symfony/dependency-injection/ReverseContainer.php new file mode 100644 index 0000000000..280e9e2dd5 --- /dev/null +++ b/lib/symfony/dependency-injection/ReverseContainer.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 Symfony\Component\DependencyInjection; + +use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; + +/** + * Turns public and "container.reversible" services back to their ids. + * + * @author Nicolas Grekas + */ +final class ReverseContainer +{ + private $serviceContainer; + private $reversibleLocator; + private $tagName; + private $getServiceId; + + public function __construct(Container $serviceContainer, ContainerInterface $reversibleLocator, string $tagName = 'container.reversible') + { + $this->serviceContainer = $serviceContainer; + $this->reversibleLocator = $reversibleLocator; + $this->tagName = $tagName; + $this->getServiceId = \Closure::bind(function (object $service): ?string { + return array_search($service, $this->services, true) ?: array_search($service, $this->privates, true) ?: null; + }, $serviceContainer, Container::class); + } + + /** + * Returns the id of the passed object when it exists as a service. + * + * To be reversible, services need to be either public or be tagged with "container.reversible". + */ + public function getId(object $service): ?string + { + if ($this->serviceContainer === $service) { + return 'service_container'; + } + + if (null === $id = ($this->getServiceId)($service)) { + return null; + } + + if ($this->serviceContainer->has($id) || $this->reversibleLocator->has($id)) { + return $id; + } + + return null; + } + + /** + * @throws ServiceNotFoundException When the service is not reversible + */ + public function getService(string $id): object + { + if ($this->serviceContainer->has($id)) { + return $this->serviceContainer->get($id); + } + + if ($this->reversibleLocator->has($id)) { + return $this->reversibleLocator->get($id); + } + + if (isset($this->serviceContainer->getRemovedIds()[$id])) { + throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service is private and cannot be accessed by reference. You should either make it public, or tag it as "%s".', $id, $this->tagName)); + } + + // will throw a ServiceNotFoundException + $this->serviceContainer->get($id); + } +} diff --git a/lib/symfony/dependency-injection/ServiceLocator.php b/lib/symfony/dependency-injection/ServiceLocator.php index 80be44ebaa..4be0d6f721 100644 --- a/lib/symfony/dependency-injection/ServiceLocator.php +++ b/lib/symfony/dependency-injection/ServiceLocator.php @@ -11,71 +11,68 @@ namespace Symfony\Component\DependencyInjection; -use Psr\Container\ContainerInterface as PsrContainerInterface; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Contracts\Service\ServiceLocatorTrait; +use Symfony\Contracts\Service\ServiceProviderInterface; +use Symfony\Contracts\Service\ServiceSubscriberInterface; /** * @author Robin Chalas * @author Nicolas Grekas */ -class ServiceLocator implements PsrContainerInterface +class ServiceLocator implements ServiceProviderInterface { - private $factories; - private $loading = []; + use ServiceLocatorTrait { + get as private doGet; + } + private $externalId; private $container; - /** - * @param callable[] $factories - */ - public function __construct(array $factories) - { - $this->factories = $factories; - } - /** * {@inheritdoc} + * + * @return mixed */ - public function has($id) + public function get(string $id) { - return isset($this->factories[$id]); - } - - /** - * {@inheritdoc} - */ - public function get($id) - { - if (!isset($this->factories[$id])) { - throw new ServiceNotFoundException($id, end($this->loading) ?: null, null, [], $this->createServiceNotFoundMessage($id)); + if (!$this->externalId) { + return $this->doGet($id); } - if (isset($this->loading[$id])) { - $ids = array_values($this->loading); - $ids = \array_slice($this->loading, array_search($id, $ids)); - $ids[] = $id; - - throw new ServiceCircularReferenceException($id, $ids); - } - - $this->loading[$id] = $id; try { - return $this->factories[$id](); - } finally { - unset($this->loading[$id]); + return $this->doGet($id); + } catch (RuntimeException $e) { + $what = sprintf('service "%s" required by "%s"', $id, $this->externalId); + $message = preg_replace('/service "\.service_locator\.[^"]++"/', $what, $e->getMessage()); + + if ($e->getMessage() === $message) { + $message = sprintf('Cannot resolve %s: %s', $what, $message); + } + + $r = new \ReflectionProperty($e, 'message'); + $r->setAccessible(true); + $r->setValue($e, $message); + + throw $e; } } - public function __invoke($id) + public function __invoke(string $id) { return isset($this->factories[$id]) ? $this->get($id) : null; } /** * @internal + * + * @return static */ - public function withContext($externalId, Container $container) + public function withContext(string $externalId, Container $container): self { $locator = clone $this; $locator->externalId = $externalId; @@ -84,14 +81,16 @@ class ServiceLocator implements PsrContainerInterface return $locator; } - private function createServiceNotFoundMessage($id) + private function createNotFoundException(string $id): NotFoundExceptionInterface { if ($this->loading) { - return sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $this->formatAlternatives()); + $msg = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $this->formatAlternatives()); + + return new ServiceNotFoundException($id, end($this->loading) ?: null, null, [], $msg); } - $class = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 3); - $class = isset($class[2]['object']) ? \get_class($class[2]['object']) : null; + $class = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 4); + $class = isset($class[3]['object']) ? \get_class($class[3]['object']) : null; $externalId = $this->externalId ?: $class; $msg = []; @@ -127,10 +126,15 @@ class ServiceLocator implements PsrContainerInterface $msg[] = 'Try using dependency injection instead.'; } - return implode(' ', $msg); + return new ServiceNotFoundException($id, end($this->loading) ?: null, null, [], implode(' ', $msg)); } - private function formatAlternatives(array $alternatives = null, $separator = 'and') + private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface + { + return new ServiceCircularReferenceException($id, $path); + } + + private function formatAlternatives(array $alternatives = null, string $separator = 'and'): string { $format = '"%s"%s'; if (null === $alternatives) { diff --git a/lib/symfony/dependency-injection/TaggedContainerInterface.php b/lib/symfony/dependency-injection/TaggedContainerInterface.php index 90b297fff2..25d5a098f6 100644 --- a/lib/symfony/dependency-injection/TaggedContainerInterface.php +++ b/lib/symfony/dependency-injection/TaggedContainerInterface.php @@ -23,7 +23,7 @@ interface TaggedContainerInterface extends ContainerInterface * * @param string $name The tag name * - * @return array An array of tags + * @return array */ - public function findTaggedServiceIds($name); + public function findTaggedServiceIds(string $name); } diff --git a/lib/symfony/dependency-injection/TypedReference.php b/lib/symfony/dependency-injection/TypedReference.php index aad78e806b..4099a0059b 100644 --- a/lib/symfony/dependency-injection/TypedReference.php +++ b/lib/symfony/dependency-injection/TypedReference.php @@ -19,19 +19,19 @@ namespace Symfony\Component\DependencyInjection; class TypedReference extends Reference { private $type; - private $requiringClass; + private $name; /** - * @param string $id The service identifier - * @param string $type The PHP type of the identified service - * @param string $requiringClass The class of the service that requires the referenced type - * @param int $invalidBehavior The behavior when the service does not exist + * @param string $id The service identifier + * @param string $type The PHP type of the identified service + * @param int $invalidBehavior The behavior when the service does not exist + * @param string|null $name The name of the argument targeting the service */ - public function __construct($id, $type, $requiringClass = '', $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) + public function __construct(string $id, string $type, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, string $name = null) { + $this->name = $type === $id ? $name : null; parent::__construct($id, $invalidBehavior); $this->type = $type; - $this->requiringClass = $requiringClass; } public function getType() @@ -39,13 +39,8 @@ class TypedReference extends Reference return $this->type; } - public function getRequiringClass() + public function getName(): ?string { - return $this->requiringClass; - } - - public function canBeAutoregistered() - { - return $this->requiringClass && (false !== $i = strpos($this->type, '\\')) && 0 === strncasecmp($this->type, $this->requiringClass, 1 + $i); + return $this->name; } } diff --git a/lib/symfony/dependency-injection/Variable.php b/lib/symfony/dependency-injection/Variable.php index 9654ee4ddc..21d33ebb28 100644 --- a/lib/symfony/dependency-injection/Variable.php +++ b/lib/symfony/dependency-injection/Variable.php @@ -28,14 +28,14 @@ class Variable { private $name; - /** - * @param string $name - */ - public function __construct($name) + public function __construct(string $name) { $this->name = $name; } + /** + * @return string + */ public function __toString() { return $this->name; diff --git a/lib/symfony/dependency-injection/composer.json b/lib/symfony/dependency-injection/composer.json index eee41ce022..cb891c7906 100644 --- a/lib/symfony/dependency-injection/composer.json +++ b/lib/symfony/dependency-injection/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/dependency-injection", "type": "library", - "description": "Symfony DependencyInjection Component", + "description": "Allows you to standardize and centralize the way objects are constructed in your application", "keywords": [], "homepage": "https://symfony.com", "license": "MIT", @@ -16,13 +16,17 @@ } ], "require": { - "php": "^5.5.9|>=7.0.8", - "psr/container": "^1.0" + "php": ">=7.2.5", + "psr/container": "^1.1.1", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22", + "symfony/service-contracts": "^1.1.6|^2" }, "require-dev": { - "symfony/yaml": "~3.4|~4.0", - "symfony/config": "~3.3|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0" + "symfony/yaml": "^4.4.26|^5.0|^6.0", + "symfony/config": "^5.3|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0" }, "suggest": { "symfony/yaml": "", @@ -32,13 +36,15 @@ "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them" }, "conflict": { - "symfony/config": "<3.3.7", - "symfony/finder": "<3.3", - "symfony/proxy-manager-bridge": "<3.4", - "symfony/yaml": "<3.4" + "ext-psr": "<1.1|>=2", + "symfony/config": "<5.3", + "symfony/finder": "<4.4", + "symfony/proxy-manager-bridge": "<4.4", + "symfony/yaml": "<4.4.26" }, "provide": { - "psr/container-implementation": "1.0" + "psr/container-implementation": "1.0", + "symfony/service-implementation": "1.0|2.0" }, "autoload": { "psr-4": { "Symfony\\Component\\DependencyInjection\\": "" }, diff --git a/lib/symfony/dependency-injection/phpunit.xml.dist b/lib/symfony/dependency-injection/phpunit.xml.dist deleted file mode 100644 index 21dee2a801..0000000000 --- a/lib/symfony/dependency-injection/phpunit.xml.dist +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - ./Tests/ - - - - - - ./ - - ./Resources - ./Tests - ./vendor - - - - diff --git a/lib/symfony/config/.gitignore b/lib/symfony/deprecation-contracts/.gitignore similarity index 100% rename from lib/symfony/config/.gitignore rename to lib/symfony/deprecation-contracts/.gitignore diff --git a/lib/symfony/deprecation-contracts/CHANGELOG.md b/lib/symfony/deprecation-contracts/CHANGELOG.md new file mode 100644 index 0000000000..7932e26132 --- /dev/null +++ b/lib/symfony/deprecation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/lib/symfony/deprecation-contracts/LICENSE b/lib/symfony/deprecation-contracts/LICENSE new file mode 100644 index 0000000000..406242ff28 --- /dev/null +++ b/lib/symfony/deprecation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-2022 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/lib/symfony/deprecation-contracts/README.md b/lib/symfony/deprecation-contracts/README.md new file mode 100644 index 0000000000..4957933a6c --- /dev/null +++ b/lib/symfony/deprecation-contracts/README.md @@ -0,0 +1,26 @@ +Symfony Deprecation Contracts +============================= + +A generic function and convention to trigger deprecation notices. + +This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices. + +By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component, +the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments. + +The function requires at least 3 arguments: + - the name of the Composer package that is triggering the deprecation + - the version of the package that introduced the deprecation + - the message of the deprecation + - more arguments can be provided: they will be inserted in the message using `printf()` formatting + +Example: +```php +trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin'); +``` + +This will generate the following message: +`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` + +While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty +`function trigger_deprecation() {}` in your application. diff --git a/lib/symfony/deprecation-contracts/composer.json b/lib/symfony/deprecation-contracts/composer.json new file mode 100644 index 0000000000..cc7cc12372 --- /dev/null +++ b/lib/symfony/deprecation-contracts/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/deprecation-contracts", + "type": "library", + "description": "A generic function and convention to trigger deprecation notices", + "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.1" + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/lib/symfony/deprecation-contracts/function.php b/lib/symfony/deprecation-contracts/function.php new file mode 100644 index 0000000000..d4371504a0 --- /dev/null +++ b/lib/symfony/deprecation-contracts/function.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (!function_exists('trigger_deprecation')) { + /** + * Triggers a silenced deprecation notice. + * + * @param string $package The name of the Composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The message of the deprecation + * @param mixed ...$args Values to insert in the message using printf() formatting + * + * @author Nicolas Grekas + */ + function trigger_deprecation(string $package, string $version, string $message, ...$args): void + { + @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); + } +} diff --git a/lib/symfony/dotenv/.gitignore b/lib/symfony/dotenv/.gitignore deleted file mode 100644 index c49a5d8df5..0000000000 --- a/lib/symfony/dotenv/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/lib/symfony/dotenv/CHANGELOG.md b/lib/symfony/dotenv/CHANGELOG.md index f04cc1bdf7..3cf07eecc8 100644 --- a/lib/symfony/dotenv/CHANGELOG.md +++ b/lib/symfony/dotenv/CHANGELOG.md @@ -1,6 +1,38 @@ CHANGELOG ========= +5.4 +--- + + * Add `dotenv:dump` command to compile the contents of the .env files into a PHP-optimized file called `.env.local.php` + * Add `debug:dotenv` command to list all dotenv files with variables and values + * Add `$overrideExistingVars` on `Dotenv::bootEnv()` and `Dotenv::loadEnv()` + +5.1.0 +----- + + * added `Dotenv::bootEnv()` to check for `.env.local.php` before calling `Dotenv::loadEnv()` + * added `Dotenv::setProdEnvs()` and `Dotenv::usePutenv()` + * made Dotenv's constructor accept `$envKey` and `$debugKey` arguments, to define + the name of the env vars that configure the env name and debug settings + * deprecated passing `$usePutenv` argument to Dotenv's constructor + +5.0.0 +----- + + * using `putenv()` is disabled by default + +4.3.0 +----- + + * deprecated use of `putenv()` by default. This feature will be opted-in with a constructor argument to `Dotenv` + +4.2.0 +----- + + * added `Dotenv::overload()` and `$overrideExistingVars` as optional parameter of `Dotenv::populate()` + * added `Dotenv::loadEnv()` to load a .env file and its corresponding .env.local, .env.$env and .env.$env.local files if they exist + 3.3.0 ----- diff --git a/lib/symfony/dotenv/Command/DebugCommand.php b/lib/symfony/dotenv/Command/DebugCommand.php new file mode 100644 index 0000000000..8ceb1fd484 --- /dev/null +++ b/lib/symfony/dotenv/Command/DebugCommand.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Dotenv\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Dotenv\Dotenv; + +/** + * A console command to debug current dotenv files with variables and values. + * + * @author Christopher Hertel + */ +final class DebugCommand extends Command +{ + protected static $defaultName = 'debug:dotenv'; + protected static $defaultDescription = 'Lists all dotenv files with variables and values'; + + private $kernelEnvironment; + private $projectDirectory; + + public function __construct(string $kernelEnvironment, string $projectDirectory) + { + $this->kernelEnvironment = $kernelEnvironment; + $this->projectDirectory = $projectDirectory; + + parent::__construct(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $io->title('Dotenv Variables & Files'); + + if (!\array_key_exists('SYMFONY_DOTENV_VARS', $_SERVER)) { + $io->error('Dotenv component is not initialized.'); + + return 1; + } + + $envFiles = $this->getEnvFiles(); + $availableFiles = array_filter($envFiles, function (string $file) { + return is_file($this->getFilePath($file)); + }); + + if (\in_array('.env.local.php', $availableFiles, true)) { + $io->warning('Due to existing dump file (.env.local.php) all other dotenv files are skipped.'); + } + + if (is_file($this->getFilePath('.env')) && is_file($this->getFilePath('.env.dist'))) { + $io->warning('The file .env.dist gets skipped due to the existence of .env.'); + } + + $io->section('Scanned Files (in descending priority)'); + $io->listing(array_map(static function (string $envFile) use ($availableFiles) { + return \in_array($envFile, $availableFiles, true) + ? sprintf('✓ %s', $envFile) + : sprintf('⨯ %s', $envFile); + }, $envFiles)); + + $io->section('Variables'); + $io->table( + array_merge(['Variable', 'Value'], $availableFiles), + $this->getVariables($availableFiles) + ); + + $io->comment('Note real values might be different between web and CLI.'); + + return 0; + } + + private function getVariables(array $envFiles): array + { + $vars = explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? ''); + sort($vars); + + $output = []; + $fileValues = []; + foreach ($vars as $var) { + $realValue = $_SERVER[$var]; + $varDetails = [$var, $realValue]; + foreach ($envFiles as $envFile) { + $values = $fileValues[$envFile] ?? $fileValues[$envFile] = $this->loadValues($envFile); + + $varString = $values[$var] ?? 'n/a'; + $shortenedVar = $this->getHelper('formatter')->truncate($varString, 30); + $varDetails[] = $varString === $realValue ? ''.$shortenedVar.'' : $shortenedVar; + } + + $output[] = $varDetails; + } + + return $output; + } + + private function getEnvFiles(): array + { + $files = [ + '.env.local.php', + sprintf('.env.%s.local', $this->kernelEnvironment), + sprintf('.env.%s', $this->kernelEnvironment), + ]; + + if ('test' !== $this->kernelEnvironment) { + $files[] = '.env.local'; + } + + if (!is_file($this->getFilePath('.env')) && is_file($this->getFilePath('.env.dist'))) { + $files[] = '.env.dist'; + } else { + $files[] = '.env'; + } + + return $files; + } + + private function getFilePath(string $file): string + { + return $this->projectDirectory.\DIRECTORY_SEPARATOR.$file; + } + + private function loadValues(string $file): array + { + $filePath = $this->getFilePath($file); + + if (str_ends_with($filePath, '.php')) { + return include $filePath; + } + + return (new Dotenv())->parse(file_get_contents($filePath)); + } +} diff --git a/lib/symfony/dotenv/Command/DotenvDumpCommand.php b/lib/symfony/dotenv/Command/DotenvDumpCommand.php new file mode 100644 index 0000000000..44110543f9 --- /dev/null +++ b/lib/symfony/dotenv/Command/DotenvDumpCommand.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Dotenv\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; +use Symfony\Component\Dotenv\Dotenv; + +/** + * A console command to compile .env files into a PHP-optimized file called .env.local.php. + * + * @internal + */ +#[Autoconfigure(bind: ['$projectDir' => '%kernel.project_dir%', '$defaultEnv' => '%kernel.environment%'])] +final class DotenvDumpCommand extends Command +{ + protected static $defaultName = 'dotenv:dump'; + protected static $defaultDescription = 'Compiles .env files to .env.local.php'; + + private $projectDir; + private $defaultEnv; + + public function __construct(string $projectDir, string $defaultEnv = null) + { + $this->projectDir = $projectDir; + $this->defaultEnv = $defaultEnv; + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setDefinition([ + new InputArgument('env', null === $this->defaultEnv ? InputArgument::REQUIRED : InputArgument::OPTIONAL, 'The application environment to dump .env files for - e.g. "prod".'), + ]) + ->addOption('empty', null, InputOption::VALUE_NONE, 'Ignore the content of .env files') + ->setHelp(<<<'EOT' +The %command.name% command compiles .env files into a PHP-optimized file called .env.local.php. + + %command.full_name% +EOT + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $config = []; + if (is_file($projectDir = $this->projectDir)) { + $config = ['dotenv_path' => basename($projectDir)]; + $projectDir = \dirname($projectDir); + } + + $composerFile = $projectDir.'/composer.json'; + $config += (is_file($composerFile) ? json_decode(file_get_contents($composerFile), true) : [])['extra']['runtime'] ?? []; + $dotenvPath = $projectDir.'/'.($config['dotenv_path'] ?? '.env'); + $env = $input->getArgument('env') ?? $this->defaultEnv; + $envKey = $config['env_var_name'] ?? 'APP_ENV'; + + if ($input->getOption('empty')) { + $vars = [$envKey => $env]; + } else { + $vars = $this->loadEnv($dotenvPath, $env, $config); + $env = $vars[$envKey]; + } + + $vars = var_export($vars, true); + $vars = <<writeln(sprintf('Successfully dumped .env files in .env.local.php for the %s environment.', $env)); + + return 0; + } + + private function loadEnv(string $dotenvPath, string $env, array $config): array + { + $dotenv = new Dotenv(); + $envKey = $config['env_var_name'] ?? 'APP_ENV'; + $testEnvs = $config['test_envs'] ?? ['test']; + + $globalsBackup = [$_SERVER, $_ENV]; + unset($_SERVER[$envKey]); + $_ENV = [$envKey => $env]; + $_SERVER['SYMFONY_DOTENV_VARS'] = implode(',', array_keys($_SERVER)); + + try { + $dotenv->loadEnv($dotenvPath, null, 'dev', $testEnvs); + unset($_ENV['SYMFONY_DOTENV_VARS']); + + return $_ENV; + } finally { + [$_SERVER, $_ENV] = $globalsBackup; + } + } +} diff --git a/lib/symfony/dotenv/Dotenv.php b/lib/symfony/dotenv/Dotenv.php index 63424e165d..2c76d52c80 100644 --- a/lib/symfony/dotenv/Dotenv.php +++ b/lib/symfony/dotenv/Dotenv.php @@ -21,12 +21,13 @@ use Symfony\Component\Process\Process; * Manages .env files. * * @author Fabien Potencier + * @author Kévin Dunglas */ final class Dotenv { - const VARNAME_REGEX = '(?i:[A-Z][A-Z0-9_]*+)'; - const STATE_VARNAME = 0; - const STATE_VALUE = 1; + public const VARNAME_REGEX = '(?i:[A-Z][A-Z0-9_]*+)'; + public const STATE_VARNAME = 0; + public const STATE_VALUE = 1; private $path; private $cursor; @@ -34,47 +35,175 @@ final class Dotenv private $data; private $end; private $values; + private $envKey; + private $debugKey; + private $prodEnvs = ['prod']; + private $usePutenv = false; + + /** + * @param string $envKey + */ + public function __construct($envKey = 'APP_ENV', string $debugKey = 'APP_DEBUG') + { + if (\in_array($envKey = (string) $envKey, ['1', ''], true)) { + trigger_deprecation('symfony/dotenv', '5.1', 'Passing a boolean to the constructor of "%s" is deprecated, use "Dotenv::usePutenv()".', __CLASS__); + $this->usePutenv = (bool) $envKey; + $envKey = 'APP_ENV'; + } + + $this->envKey = $envKey; + $this->debugKey = $debugKey; + } + + /** + * @return $this + */ + public function setProdEnvs(array $prodEnvs): self + { + $this->prodEnvs = $prodEnvs; + + return $this; + } + + /** + * @param bool $usePutenv If `putenv()` should be used to define environment variables or not. + * Beware that `putenv()` is not thread safe, that's why this setting defaults to false + * + * @return $this + */ + public function usePutenv(bool $usePutenv = true): self + { + $this->usePutenv = $usePutenv; + + return $this; + } /** * Loads one or several .env files. * - * @param string $path A file to load + * @param string $path A file to load + * @param string[] ...$extraPaths A list of additional files to load * * @throws FormatException when a file has a syntax error * @throws PathException when a file does not exist or is not readable */ - public function load($path/*, ...$paths*/) + public function load(string $path, string ...$extraPaths): void { - // func_get_args() to be replaced by a variadic argument for Symfony 4.0 - foreach (\func_get_args() as $path) { - if (!is_readable($path) || is_dir($path)) { - throw new PathException($path); - } + $this->doLoad(false, \func_get_args()); + } - $this->populate($this->parse(file_get_contents($path), $path)); + /** + * Loads a .env file and the corresponding .env.local, .env.$env and .env.$env.local files if they exist. + * + * .env.local is always ignored in test env because tests should produce the same results for everyone. + * .env.dist is loaded when it exists and .env is not found. + * + * @param string $path A file to load + * @param string $envKey|null The name of the env vars that defines the app env + * @param string $defaultEnv The app env to use when none is defined + * @param array $testEnvs A list of app envs for which .env.local should be ignored + * + * @throws FormatException when a file has a syntax error + * @throws PathException when a file does not exist or is not readable + */ + public function loadEnv(string $path, string $envKey = null, string $defaultEnv = 'dev', array $testEnvs = ['test'], bool $overrideExistingVars = false): void + { + $k = $envKey ?? $this->envKey; + + if (is_file($path) || !is_file($p = "$path.dist")) { + $this->doLoad($overrideExistingVars, [$path]); + } else { + $this->doLoad($overrideExistingVars, [$p]); } + + if (null === $env = $_SERVER[$k] ?? $_ENV[$k] ?? null) { + $this->populate([$k => $env = $defaultEnv], $overrideExistingVars); + } + + if (!\in_array($env, $testEnvs, true) && is_file($p = "$path.local")) { + $this->doLoad($overrideExistingVars, [$p]); + $env = $_SERVER[$k] ?? $_ENV[$k] ?? $env; + } + + if ('local' === $env) { + return; + } + + if (is_file($p = "$path.$env")) { + $this->doLoad($overrideExistingVars, [$p]); + } + + if (is_file($p = "$path.$env.local")) { + $this->doLoad($overrideExistingVars, [$p]); + } + } + + /** + * Loads env vars from .env.local.php if the file exists or from the other .env files otherwise. + * + * This method also configures the APP_DEBUG env var according to the current APP_ENV. + * + * See method loadEnv() for rules related to .env files. + */ + public function bootEnv(string $path, string $defaultEnv = 'dev', array $testEnvs = ['test'], bool $overrideExistingVars = false): void + { + $p = $path.'.local.php'; + $env = is_file($p) ? include $p : null; + $k = $this->envKey; + + if (\is_array($env) && ($overrideExistingVars || !isset($env[$k]) || ($_SERVER[$k] ?? $_ENV[$k] ?? $env[$k]) === $env[$k])) { + $this->populate($env, $overrideExistingVars); + } else { + $this->loadEnv($path, $k, $defaultEnv, $testEnvs, $overrideExistingVars); + } + + $_SERVER += $_ENV; + + $k = $this->debugKey; + $debug = $_SERVER[$k] ?? !\in_array($_SERVER[$this->envKey], $this->prodEnvs, true); + $_SERVER[$k] = $_ENV[$k] = (int) $debug || (!\is_bool($debug) && filter_var($debug, \FILTER_VALIDATE_BOOLEAN)) ? '1' : '0'; + } + + /** + * Loads one or several .env files and enables override existing vars. + * + * @param string $path A file to load + * @param string[] ...$extraPaths A list of additional files to load + * + * @throws FormatException when a file has a syntax error + * @throws PathException when a file does not exist or is not readable + */ + public function overload(string $path, string ...$extraPaths): void + { + $this->doLoad(true, \func_get_args()); } /** * Sets values as environment variables (via putenv, $_ENV, and $_SERVER). * - * Note that existing environment variables are not overridden. - * - * @param array $values An array of env variables + * @param array $values An array of env variables + * @param bool $overrideExistingVars true when existing environment variables must be overridden */ - public function populate($values) + public function populate(array $values, bool $overrideExistingVars = false): void { $updateLoadedVars = false; - $loadedVars = array_flip(explode(',', isset($_SERVER['SYMFONY_DOTENV_VARS']) ? $_SERVER['SYMFONY_DOTENV_VARS'] : (isset($_ENV['SYMFONY_DOTENV_VARS']) ? $_ENV['SYMFONY_DOTENV_VARS'] : ''))); + $loadedVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? '')); foreach ($values as $name => $value) { $notHttpName = 0 !== strpos($name, 'HTTP_'); + if (isset($_SERVER[$name]) && $notHttpName && !isset($_ENV[$name])) { + $_ENV[$name] = $_SERVER[$name]; + } + // don't check existence with getenv() because of thread safety issues - if (!isset($loadedVars[$name]) && (isset($_ENV[$name]) || (isset($_SERVER[$name]) && $notHttpName))) { + if (!isset($loadedVars[$name]) && !$overrideExistingVars && isset($_ENV[$name])) { continue; } - putenv("$name=$value"); + if ($this->usePutenv) { + putenv("$name=$value"); + } + $_ENV[$name] = $value; if ($notHttpName) { $_SERVER[$name] = $value; @@ -88,7 +217,11 @@ final class Dotenv if ($updateLoadedVars) { unset($loadedVars['']); $loadedVars = implode(',', array_keys($loadedVars)); - putenv('SYMFONY_DOTENV_VARS='.$_ENV['SYMFONY_DOTENV_VARS'] = $_SERVER['SYMFONY_DOTENV_VARS'] = $loadedVars); + $_ENV['SYMFONY_DOTENV_VARS'] = $_SERVER['SYMFONY_DOTENV_VARS'] = $loadedVars; + + if ($this->usePutenv) { + putenv('SYMFONY_DOTENV_VARS='.$loadedVars); + } } } @@ -98,11 +231,9 @@ final class Dotenv * @param string $data The data to be parsed * @param string $path The original file name where data where stored (used for more meaningful error messages) * - * @return array An array of env variables - * * @throws FormatException when a file has a syntax error */ - public function parse($data, $path = '.env') + public function parse(string $data, string $path = '.env'): array { $this->path = $path; $this->data = str_replace(["\r\n", "\r"], "\n", $data); @@ -142,7 +273,7 @@ final class Dotenv } } - private function lexVarname() + private function lexVarname(): string { // var name + optional export if (!preg_match('/(export[ \t]++)?('.self::VARNAME_REGEX.')/A', $this->data, $matches, 0, $this->cursor)) { @@ -170,7 +301,7 @@ final class Dotenv return $matches[2]; } - private function lexValue() + private function lexValue(): string { if (preg_match('/[ \t]*+(?:#.*)?$/Am', $this->data, $matches, 0, $this->cursor)) { $this->moveCursor($matches[0]); @@ -183,7 +314,7 @@ final class Dotenv throw $this->createFormatException('Whitespace are not supported before the value'); } - $loadedVars = array_flip(explode(',', isset($_SERVER['SYMFONY_DOTENV_VARS']) ? $_SERVER['SYMFONY_DOTENV_VARS'] : (isset($_ENV['SYMFONY_DOTENV_VARS']) ? $_ENV['SYMFONY_DOTENV_VARS'] : ''))); + $loadedVars = array_flip(explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? ($_ENV['SYMFONY_DOTENV_VARS'] ?? ''))); unset($loadedVars['']); $v = ''; @@ -216,9 +347,6 @@ final class Dotenv throw $this->createFormatException('Missing quote to end the value'); } } - if ("\n" === $this->data[$this->cursor]) { - throw $this->createFormatException('Missing quote to end the value'); - } ++$this->cursor; $value = str_replace(['\\"', '\r', '\n'], ['"', "\r", "\n"], $value); $resolvedValue = $value; @@ -266,7 +394,7 @@ final class Dotenv return $v; } - private function lexNestedExpression() + private function lexNestedExpression(): string { ++$this->cursor; $value = ''; @@ -299,7 +427,7 @@ final class Dotenv } } - private function resolveCommands($value, $loadedVars) + private function resolveCommands(string $value, array $loadedVars): string { if (false === strpos($value, '$')) { return $value; @@ -328,8 +456,12 @@ final class Dotenv throw new \LogicException('Resolving commands requires the Symfony Process component.'); } - $process = new Process('echo '.$matches[0]); - $process->inheritEnvironmentVariables(true); + $process = method_exists(Process::class, 'fromShellCommandline') ? Process::fromShellCommandline('echo '.$matches[0]) : new Process('echo '.$matches[0]); + + if (!method_exists(Process::class, 'fromShellCommandline') && method_exists(Process::class, 'inheritEnvironmentVariables')) { + // Symfony 3.4 does not inherit env vars by default: + $process->inheritEnvironmentVariables(); + } $env = []; foreach ($this->values as $name => $value) { @@ -349,7 +481,7 @@ final class Dotenv }, $value); } - private function resolveVariables($value, array $loadedVars) + private function resolveVariables(string $value, array $loadedVars): string { if (false === strpos($value, '$')) { return $value; @@ -362,6 +494,7 @@ final class Dotenv (?!\() # no opening parenthesis (?P\{)? # optional brace (?P'.self::VARNAME_REGEX.')? # var name + (?P:[-=][^\}]++)? # optional default value (?P\})? # optional closing brace /x'; @@ -393,6 +526,19 @@ final class Dotenv $value = (string) getenv($name); } + if ('' === $value && isset($matches['default_value']) && '' !== $matches['default_value']) { + $unsupportedChars = strpbrk($matches['default_value'], '\'"{$'); + if (false !== $unsupportedChars) { + throw $this->createFormatException(sprintf('Unsupported character "%s" found in the default value of variable "$%s".', $unsupportedChars[0], $name)); + } + + $value = substr($matches['default_value'], 2); + + if ('=' === $matches['default_value'][1]) { + $this->values[$name] = $value; + } + } + if (!$matches['opening_brace'] && isset($matches['closing_brace'])) { $value .= '}'; } @@ -403,14 +549,25 @@ final class Dotenv return $value; } - private function moveCursor($text) + private function moveCursor(string $text) { $this->cursor += \strlen($text); $this->lineno += substr_count($text, "\n"); } - private function createFormatException($message) + private function createFormatException(string $message): FormatException { return new FormatException($message, new FormatExceptionContext($this->data, $this->path, $this->lineno, $this->cursor)); } + + private function doLoad(bool $overrideExistingVars, array $paths): void + { + foreach ($paths as $path) { + if (!is_readable($path) || is_dir($path)) { + throw new PathException($path); + } + + $this->populate($this->parse(file_get_contents($path), $path), $overrideExistingVars); + } + } } diff --git a/lib/symfony/dotenv/Exception/ExceptionInterface.php b/lib/symfony/dotenv/Exception/ExceptionInterface.php index 90509f7db5..140a93f966 100644 --- a/lib/symfony/dotenv/Exception/ExceptionInterface.php +++ b/lib/symfony/dotenv/Exception/ExceptionInterface.php @@ -16,6 +16,6 @@ namespace Symfony\Component\Dotenv\Exception; * * @author Fabien Potencier */ -interface ExceptionInterface +interface ExceptionInterface extends \Throwable { } diff --git a/lib/symfony/dotenv/Exception/FormatException.php b/lib/symfony/dotenv/Exception/FormatException.php index 26f1442b69..3ac77e592d 100644 --- a/lib/symfony/dotenv/Exception/FormatException.php +++ b/lib/symfony/dotenv/Exception/FormatException.php @@ -20,14 +20,14 @@ final class FormatException extends \LogicException implements ExceptionInterfac { private $context; - public function __construct($message, FormatExceptionContext $context, $code = 0, \Exception $previous = null) + public function __construct(string $message, FormatExceptionContext $context, int $code = 0, \Throwable $previous = null) { $this->context = $context; parent::__construct(sprintf("%s in \"%s\" at line %d.\n%s", $message, $context->getPath(), $context->getLineno(), $context->getDetails()), $code, $previous); } - public function getContext() + public function getContext(): FormatExceptionContext { return $this->context; } diff --git a/lib/symfony/dotenv/Exception/FormatExceptionContext.php b/lib/symfony/dotenv/Exception/FormatExceptionContext.php index f4d3cea0f2..96d902fc8f 100644 --- a/lib/symfony/dotenv/Exception/FormatExceptionContext.php +++ b/lib/symfony/dotenv/Exception/FormatExceptionContext.php @@ -21,7 +21,7 @@ final class FormatExceptionContext private $lineno; private $cursor; - public function __construct($data, $path, $lineno, $cursor) + public function __construct(string $data, string $path, int $lineno, int $cursor) { $this->data = $data; $this->path = $path; @@ -29,17 +29,17 @@ final class FormatExceptionContext $this->cursor = $cursor; } - public function getPath() + public function getPath(): string { return $this->path; } - public function getLineno() + public function getLineno(): int { return $this->lineno; } - public function getDetails() + public function getDetails(): string { $before = str_replace("\n", '\n', substr($this->data, max(0, $this->cursor - 20), min(20, $this->cursor))); $after = str_replace("\n", '\n', substr($this->data, $this->cursor, 20)); diff --git a/lib/symfony/dotenv/Exception/PathException.php b/lib/symfony/dotenv/Exception/PathException.php index ac4d540ff4..4a4d717222 100644 --- a/lib/symfony/dotenv/Exception/PathException.php +++ b/lib/symfony/dotenv/Exception/PathException.php @@ -18,7 +18,7 @@ namespace Symfony\Component\Dotenv\Exception; */ final class PathException extends \RuntimeException implements ExceptionInterface { - public function __construct($path, $code = 0, \Exception $previous = null) + public function __construct(string $path, int $code = 0, \Throwable $previous = null) { parent::__construct(sprintf('Unable to read the "%s" environment file.', $path), $code, $previous); } diff --git a/lib/symfony/dotenv/LICENSE b/lib/symfony/dotenv/LICENSE index a7ec708018..7fa9539054 100644 --- a/lib/symfony/dotenv/LICENSE +++ b/lib/symfony/dotenv/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016-2020 Fabien Potencier +Copyright (c) 2016-2022 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/lib/symfony/dotenv/README.md b/lib/symfony/dotenv/README.md index 244ed7700a..08c90fcc88 100644 --- a/lib/symfony/dotenv/README.md +++ b/lib/symfony/dotenv/README.md @@ -2,13 +2,35 @@ Dotenv Component ================ Symfony Dotenv parses `.env` files to make environment variables stored in them -accessible via `getenv()`, `$_ENV`, or `$_SERVER`. +accessible via `$_SERVER` or `$_ENV`. + +Getting Started +--------------- + +``` +$ composer require symfony/dotenv +``` + +```php +use Symfony\Component\Dotenv\Dotenv; + +$dotenv = new Dotenv(); +$dotenv->load(__DIR__.'/.env'); + +// you can also load several files +$dotenv->load(__DIR__.'/.env', __DIR__.'/.env.dev'); + +// overwrites existing env variables +$dotenv->overload(__DIR__.'/.env'); + +// loads .env, .env.local, and .env.$APP_ENV.local or .env.$APP_ENV +$dotenv->loadEnv(__DIR__.'/.env'); +``` Resources --------- - * [Documentation](https://symfony.com/doc/current/components/dotenv.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) + * [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/lib/symfony/dotenv/composer.json b/lib/symfony/dotenv/composer.json index be720a08ed..bec034f87a 100644 --- a/lib/symfony/dotenv/composer.json +++ b/lib/symfony/dotenv/composer.json @@ -16,10 +16,12 @@ } ], "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3" }, "require-dev": { - "symfony/process": "^3.4.2|^4.0" + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0" }, "autoload": { "psr-4": { "Symfony\\Component\\Dotenv\\": "" }, diff --git a/lib/symfony/dotenv/phpunit.xml.dist b/lib/symfony/dotenv/phpunit.xml.dist deleted file mode 100644 index b1caee3c4d..0000000000 --- a/lib/symfony/dotenv/phpunit.xml.dist +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - ./Tests/ - - - - - - ./ - - ./Tests - ./vendor - - - - diff --git a/lib/symfony/error-handler/BufferingLogger.php b/lib/symfony/error-handler/BufferingLogger.php new file mode 100644 index 0000000000..cfd55c61f3 --- /dev/null +++ b/lib/symfony/error-handler/BufferingLogger.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler; + +use Psr\Log\AbstractLogger; + +/** + * A buffering logger that stacks logs for later. + * + * @author Nicolas Grekas + */ +class BufferingLogger extends AbstractLogger +{ + private $logs = []; + + public function log($level, $message, array $context = []): void + { + $this->logs[] = [$level, $message, $context]; + } + + public function cleanLogs(): array + { + $logs = $this->logs; + $this->logs = []; + + return $logs; + } + + /** + * @return array + */ + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + foreach ($this->logs as [$level, $message, $context]) { + if (false !== strpos($message, '{')) { + foreach ($context as $key => $val) { + if (null === $val || \is_scalar($val) || (\is_object($val) && \is_callable([$val, '__toString']))) { + $message = str_replace("{{$key}}", $val, $message); + } elseif ($val instanceof \DateTimeInterface) { + $message = str_replace("{{$key}}", $val->format(\DateTime::RFC3339), $message); + } elseif (\is_object($val)) { + $message = str_replace("{{$key}}", '[object '.\get_class($val).']', $message); + } else { + $message = str_replace("{{$key}}", '['.\gettype($val).']', $message); + } + } + } + + error_log(sprintf('%s [%s] %s', date(\DateTime::RFC3339), $level, $message)); + } + } +} diff --git a/lib/symfony/error-handler/CHANGELOG.md b/lib/symfony/error-handler/CHANGELOG.md new file mode 100644 index 0000000000..2976566a1f --- /dev/null +++ b/lib/symfony/error-handler/CHANGELOG.md @@ -0,0 +1,24 @@ +CHANGELOG +========= + +5.4 +--- + + * Make `DebugClassLoader` trigger deprecation notices on missing return types + * Add `SYMFONY_PATCH_TYPE_DECLARATIONS='force=2'` mode to `DebugClassLoader` to turn annotations into native return types + +5.2.0 +----- + + * added the ability to set `HtmlErrorRenderer::$template` to a custom template to render when not in debug mode. + +5.1.0 +----- + + * The `HtmlErrorRenderer` and `SerializerErrorRenderer` add `X-Debug-Exception` and `X-Debug-Exception-File` headers in debug mode. + +4.4.0 +----- + + * added the component + * added `ErrorHandler::call()` method utility to turn any PHP error into `\ErrorException` diff --git a/lib/symfony/error-handler/Debug.php b/lib/symfony/error-handler/Debug.php new file mode 100644 index 0000000000..343a35a77b --- /dev/null +++ b/lib/symfony/error-handler/Debug.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 Symfony\Component\ErrorHandler; + +/** + * Registers all the debug tools. + * + * @author Fabien Potencier + */ +class Debug +{ + public static function enable(): ErrorHandler + { + error_reporting(-1); + + if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { + ini_set('display_errors', 0); + } elseif (!filter_var(\ini_get('log_errors'), \FILTER_VALIDATE_BOOLEAN) || \ini_get('error_log')) { + // CLI - display errors only if they're not already logged to STDERR + ini_set('display_errors', 1); + } + + @ini_set('zend.assertions', 1); + ini_set('assert.active', 1); + ini_set('assert.warning', 0); + ini_set('assert.exception', 1); + + DebugClassLoader::enable(); + + return ErrorHandler::register(new ErrorHandler(new BufferingLogger(), true)); + } +} diff --git a/lib/symfony/error-handler/DebugClassLoader.php b/lib/symfony/error-handler/DebugClassLoader.php new file mode 100644 index 0000000000..6e4adeab05 --- /dev/null +++ b/lib/symfony/error-handler/DebugClassLoader.php @@ -0,0 +1,1211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler; + +use Composer\InstalledVersions; +use Doctrine\Common\Persistence\Proxy as LegacyProxy; +use Doctrine\Persistence\Proxy; +use Mockery\MockInterface; +use Phake\IMock; +use PHPUnit\Framework\MockObject\Matcher\StatelessInvocation; +use PHPUnit\Framework\MockObject\MockObject; +use Prophecy\Prophecy\ProphecySubjectInterface; +use ProxyManager\Proxy\ProxyInterface; +use Symfony\Component\ErrorHandler\Internal\TentativeTypes; + +/** + * Autoloader checking if the class is really defined in the file found. + * + * The ClassLoader will wrap all registered autoloaders + * and will throw an exception if a file is found but does + * not declare the class. + * + * It can also patch classes to turn docblocks into actual return types. + * This behavior is controlled by the SYMFONY_PATCH_TYPE_DECLARATIONS env var, + * which is a url-encoded array with the follow parameters: + * - "force": any value enables deprecation notices - can be any of: + * - "phpdoc" to patch only docblock annotations + * - "2" to add all possible return types + * - "1" to add return types but only to tests/final/internal/private methods + * - "php": the target version of PHP - e.g. "7.1" doesn't generate "object" types + * - "deprecations": "1" to trigger a deprecation notice when a child class misses a + * return type while the parent declares an "@return" annotation + * + * Note that patching doesn't care about any coding style so you'd better to run + * php-cs-fixer after, with rules "phpdoc_trim_consecutive_blank_line_separation" + * and "no_superfluous_phpdoc_tags" enabled typically. + * + * @author Fabien Potencier + * @author Christophe Coevoet + * @author Nicolas Grekas + * @author Guilhem Niot + */ +class DebugClassLoader +{ + private const SPECIAL_RETURN_TYPES = [ + 'void' => 'void', + 'null' => 'null', + 'resource' => 'resource', + 'boolean' => 'bool', + 'true' => 'bool', + 'false' => 'false', + 'integer' => 'int', + 'array' => 'array', + 'bool' => 'bool', + 'callable' => 'callable', + 'float' => 'float', + 'int' => 'int', + 'iterable' => 'iterable', + 'object' => 'object', + 'string' => 'string', + 'self' => 'self', + 'parent' => 'parent', + 'mixed' => 'mixed', + 'static' => 'static', + '$this' => 'static', + 'list' => 'array', + 'class-string' => 'string', + ]; + + private const BUILTIN_RETURN_TYPES = [ + 'void' => true, + 'array' => true, + 'false' => true, + 'bool' => true, + 'callable' => true, + 'float' => true, + 'int' => true, + 'iterable' => true, + 'object' => true, + 'string' => true, + 'self' => true, + 'parent' => true, + 'mixed' => true, + 'static' => true, + ]; + + private const MAGIC_METHODS = [ + '__isset' => 'bool', + '__sleep' => 'array', + '__toString' => 'string', + '__debugInfo' => 'array', + '__serialize' => 'array', + ]; + + private $classLoader; + private $isFinder; + private $loaded = []; + private $patchTypes; + + private static $caseCheck; + private static $checkedClasses = []; + private static $final = []; + private static $finalMethods = []; + private static $deprecated = []; + private static $internal = []; + private static $internalMethods = []; + private static $annotatedParameters = []; + private static $darwinCache = ['/' => ['/', []]]; + private static $method = []; + private static $returnTypes = []; + private static $methodTraits = []; + private static $fileOffsets = []; + + public function __construct(callable $classLoader) + { + $this->classLoader = $classLoader; + $this->isFinder = \is_array($classLoader) && method_exists($classLoader[0], 'findFile'); + parse_str(getenv('SYMFONY_PATCH_TYPE_DECLARATIONS') ?: '', $this->patchTypes); + $this->patchTypes += [ + 'force' => null, + 'php' => \PHP_MAJOR_VERSION.'.'.\PHP_MINOR_VERSION, + 'deprecations' => \PHP_VERSION_ID >= 70400, + ]; + + if ('phpdoc' === $this->patchTypes['force']) { + $this->patchTypes['force'] = 'docblock'; + } + + if (!isset(self::$caseCheck)) { + $file = is_file(__FILE__) ? __FILE__ : rtrim(realpath('.'), \DIRECTORY_SEPARATOR); + $i = strrpos($file, \DIRECTORY_SEPARATOR); + $dir = substr($file, 0, 1 + $i); + $file = substr($file, 1 + $i); + $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file); + $test = realpath($dir.$test); + + if (false === $test || false === $i) { + // filesystem is case sensitive + self::$caseCheck = 0; + } elseif (substr($test, -\strlen($file)) === $file) { + // filesystem is case insensitive and realpath() normalizes the case of characters + self::$caseCheck = 1; + } elseif ('Darwin' === \PHP_OS_FAMILY) { + // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters + self::$caseCheck = 2; + } else { + // filesystem case checks failed, fallback to disabling them + self::$caseCheck = 0; + } + } + } + + public function getClassLoader(): callable + { + return $this->classLoader; + } + + /** + * Wraps all autoloaders. + */ + public static function enable(): void + { + // Ensures we don't hit https://bugs.php.net/42098 + class_exists(\Symfony\Component\ErrorHandler\ErrorHandler::class); + class_exists(\Psr\Log\LogLevel::class); + + if (!\is_array($functions = spl_autoload_functions())) { + return; + } + + foreach ($functions as $function) { + spl_autoload_unregister($function); + } + + foreach ($functions as $function) { + if (!\is_array($function) || !$function[0] instanceof self) { + $function = [new static($function), 'loadClass']; + } + + spl_autoload_register($function); + } + } + + /** + * Disables the wrapping. + */ + public static function disable(): void + { + if (!\is_array($functions = spl_autoload_functions())) { + return; + } + + foreach ($functions as $function) { + spl_autoload_unregister($function); + } + + foreach ($functions as $function) { + if (\is_array($function) && $function[0] instanceof self) { + $function = $function[0]->getClassLoader(); + } + + spl_autoload_register($function); + } + } + + public static function checkClasses(): bool + { + if (!\is_array($functions = spl_autoload_functions())) { + return false; + } + + $loader = null; + + foreach ($functions as $function) { + if (\is_array($function) && $function[0] instanceof self) { + $loader = $function[0]; + break; + } + } + + if (null === $loader) { + return false; + } + + static $offsets = [ + 'get_declared_interfaces' => 0, + 'get_declared_traits' => 0, + 'get_declared_classes' => 0, + ]; + + foreach ($offsets as $getSymbols => $i) { + $symbols = $getSymbols(); + + for (; $i < \count($symbols); ++$i) { + if (!is_subclass_of($symbols[$i], MockObject::class) + && !is_subclass_of($symbols[$i], ProphecySubjectInterface::class) + && !is_subclass_of($symbols[$i], Proxy::class) + && !is_subclass_of($symbols[$i], ProxyInterface::class) + && !is_subclass_of($symbols[$i], LegacyProxy::class) + && !is_subclass_of($symbols[$i], MockInterface::class) + && !is_subclass_of($symbols[$i], IMock::class) + ) { + $loader->checkClass($symbols[$i]); + } + } + + $offsets[$getSymbols] = $i; + } + + return true; + } + + public function findFile(string $class): ?string + { + return $this->isFinder ? ($this->classLoader[0]->findFile($class) ?: null) : null; + } + + /** + * Loads the given class or interface. + * + * @throws \RuntimeException + */ + public function loadClass(string $class): void + { + $e = error_reporting(error_reporting() | \E_PARSE | \E_ERROR | \E_CORE_ERROR | \E_COMPILE_ERROR); + + try { + if ($this->isFinder && !isset($this->loaded[$class])) { + $this->loaded[$class] = true; + if (!$file = $this->classLoader[0]->findFile($class) ?: '') { + // no-op + } elseif (\function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file)) { + include $file; + + return; + } elseif (false === include $file) { + return; + } + } else { + ($this->classLoader)($class); + $file = ''; + } + } finally { + error_reporting($e); + } + + $this->checkClass($class, $file); + } + + private function checkClass(string $class, string $file = null): void + { + $exists = null === $file || class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false); + + if (null !== $file && $class && '\\' === $class[0]) { + $class = substr($class, 1); + } + + if ($exists) { + if (isset(self::$checkedClasses[$class])) { + return; + } + self::$checkedClasses[$class] = true; + + $refl = new \ReflectionClass($class); + if (null === $file && $refl->isInternal()) { + return; + } + $name = $refl->getName(); + + if ($name !== $class && 0 === strcasecmp($name, $class)) { + throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name)); + } + + $deprecations = $this->checkAnnotations($refl, $name); + + foreach ($deprecations as $message) { + @trigger_error($message, \E_USER_DEPRECATED); + } + } + + if (!$file) { + return; + } + + if (!$exists) { + if (false !== strpos($class, '/')) { + throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class)); + } + + throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file)); + } + + if (self::$caseCheck && $message = $this->checkCase($refl, $file, $class)) { + throw new \RuntimeException(sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', $message[0], $message[1], $message[2])); + } + } + + public function checkAnnotations(\ReflectionClass $refl, string $class): array + { + if ( + 'Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV7' === $class + || 'Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV6' === $class + ) { + return []; + } + $deprecations = []; + + $className = false !== strpos($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class; + + // Don't trigger deprecations for classes in the same vendor + if ($class !== $className) { + $vendor = preg_match('/^namespace ([^;\\\\\s]++)[;\\\\]/m', @file_get_contents($refl->getFileName()), $vendor) ? $vendor[1].'\\' : ''; + $vendorLen = \strlen($vendor); + } elseif (2 > $vendorLen = 1 + (strpos($class, '\\') ?: strpos($class, '_'))) { + $vendorLen = 0; + $vendor = ''; + } else { + $vendor = str_replace('_', '\\', substr($class, 0, $vendorLen)); + } + + $parent = get_parent_class($class) ?: null; + self::$returnTypes[$class] = []; + $classIsTemplate = false; + + // Detect annotations on the class + if ($doc = $this->parsePhpDoc($refl)) { + $classIsTemplate = isset($doc['template']); + + foreach (['final', 'deprecated', 'internal'] as $annotation) { + if (null !== $description = $doc[$annotation][0] ?? null) { + self::${$annotation}[$class] = '' !== $description ? ' '.$description.(preg_match('/[.!]$/', $description) ? '' : '.') : '.'; + } + } + + if ($refl->isInterface() && isset($doc['method'])) { + foreach ($doc['method'] as $name => [$static, $returnType, $signature, $description]) { + self::$method[$class][] = [$class, $static, $returnType, $name.$signature, $description]; + + if ('' !== $returnType) { + $this->setReturnType($returnType, $refl->name, $name, $refl->getFileName(), $parent); + } + } + } + } + + $parentAndOwnInterfaces = $this->getOwnInterfaces($class, $parent); + if ($parent) { + $parentAndOwnInterfaces[$parent] = $parent; + + if (!isset(self::$checkedClasses[$parent])) { + $this->checkClass($parent); + } + + if (isset(self::$final[$parent])) { + $deprecations[] = sprintf('The "%s" class is considered final%s It may change without further notice as of its next major version. You should not extend it from "%s".', $parent, self::$final[$parent], $className); + } + } + + // Detect if the parent is annotated + foreach ($parentAndOwnInterfaces + class_uses($class, false) as $use) { + if (!isset(self::$checkedClasses[$use])) { + $this->checkClass($use); + } + if (isset(self::$deprecated[$use]) && strncmp($vendor, str_replace('_', '\\', $use), $vendorLen) && !isset(self::$deprecated[$class])) { + $type = class_exists($class, false) ? 'class' : (interface_exists($class, false) ? 'interface' : 'trait'); + $verb = class_exists($use, false) || interface_exists($class, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses'); + + $deprecations[] = sprintf('The "%s" %s %s "%s" that is deprecated%s', $className, $type, $verb, $use, self::$deprecated[$use]); + } + if (isset(self::$internal[$use]) && strncmp($vendor, str_replace('_', '\\', $use), $vendorLen)) { + $deprecations[] = sprintf('The "%s" %s is considered internal%s It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $className); + } + if (isset(self::$method[$use])) { + if ($refl->isAbstract()) { + if (isset(self::$method[$class])) { + self::$method[$class] = array_merge(self::$method[$class], self::$method[$use]); + } else { + self::$method[$class] = self::$method[$use]; + } + } elseif (!$refl->isInterface()) { + if (!strncmp($vendor, str_replace('_', '\\', $use), $vendorLen) + && 0 === strpos($className, 'Symfony\\') + && (!class_exists(InstalledVersions::class) + || 'symfony/symfony' !== InstalledVersions::getRootPackage()['name']) + ) { + // skip "same vendor" @method deprecations for Symfony\* classes unless symfony/symfony is being tested + continue; + } + $hasCall = $refl->hasMethod('__call'); + $hasStaticCall = $refl->hasMethod('__callStatic'); + foreach (self::$method[$use] as [$interface, $static, $returnType, $name, $description]) { + if ($static ? $hasStaticCall : $hasCall) { + continue; + } + $realName = substr($name, 0, strpos($name, '(')); + if (!$refl->hasMethod($realName) || !($methodRefl = $refl->getMethod($realName))->isPublic() || ($static && !$methodRefl->isStatic()) || (!$static && $methodRefl->isStatic())) { + $deprecations[] = sprintf('Class "%s" should implement method "%s::%s%s"%s', $className, ($static ? 'static ' : '').$interface, $name, $returnType ? ': '.$returnType : '', null === $description ? '.' : ': '.$description); + } + } + } + } + } + + if (trait_exists($class)) { + $file = $refl->getFileName(); + + foreach ($refl->getMethods() as $method) { + if ($method->getFileName() === $file) { + self::$methodTraits[$file][$method->getStartLine()] = $class; + } + } + + return $deprecations; + } + + // Inherit @final, @internal, @param and @return annotations for methods + self::$finalMethods[$class] = []; + self::$internalMethods[$class] = []; + self::$annotatedParameters[$class] = []; + foreach ($parentAndOwnInterfaces as $use) { + foreach (['finalMethods', 'internalMethods', 'annotatedParameters', 'returnTypes'] as $property) { + if (isset(self::${$property}[$use])) { + self::${$property}[$class] = self::${$property}[$class] ? self::${$property}[$use] + self::${$property}[$class] : self::${$property}[$use]; + } + } + + if (null !== (TentativeTypes::RETURN_TYPES[$use] ?? null)) { + foreach (TentativeTypes::RETURN_TYPES[$use] as $method => $returnType) { + $returnType = explode('|', $returnType); + foreach ($returnType as $i => $t) { + if ('?' !== $t && !isset(self::BUILTIN_RETURN_TYPES[$t])) { + $returnType[$i] = '\\'.$t; + } + } + $returnType = implode('|', $returnType); + + self::$returnTypes[$class] += [$method => [$returnType, 0 === strpos($returnType, '?') ? substr($returnType, 1).'|null' : $returnType, $use, '']]; + } + } + } + + foreach ($refl->getMethods() as $method) { + if ($method->class !== $class) { + continue; + } + + if (null === $ns = self::$methodTraits[$method->getFileName()][$method->getStartLine()] ?? null) { + $ns = $vendor; + $len = $vendorLen; + } elseif (2 > $len = 1 + (strpos($ns, '\\') ?: strpos($ns, '_'))) { + $len = 0; + $ns = ''; + } else { + $ns = str_replace('_', '\\', substr($ns, 0, $len)); + } + + if ($parent && isset(self::$finalMethods[$parent][$method->name])) { + [$declaringClass, $message] = self::$finalMethods[$parent][$method->name]; + $deprecations[] = sprintf('The "%s::%s()" method is considered final%s It may change without further notice as of its next major version. You should not extend it from "%s".', $declaringClass, $method->name, $message, $className); + } + + if (isset(self::$internalMethods[$class][$method->name])) { + [$declaringClass, $message] = self::$internalMethods[$class][$method->name]; + if (strncmp($ns, $declaringClass, $len)) { + $deprecations[] = sprintf('The "%s::%s()" method is considered internal%s It may change without further notice. You should not extend it from "%s".', $declaringClass, $method->name, $message, $className); + } + } + + // To read method annotations + $doc = $this->parsePhpDoc($method); + + if (($classIsTemplate || isset($doc['template'])) && $method->hasReturnType()) { + unset($doc['return']); + } + + if (isset(self::$annotatedParameters[$class][$method->name])) { + $definedParameters = []; + foreach ($method->getParameters() as $parameter) { + $definedParameters[$parameter->name] = true; + } + + foreach (self::$annotatedParameters[$class][$method->name] as $parameterName => $deprecation) { + if (!isset($definedParameters[$parameterName]) && !isset($doc['param'][$parameterName])) { + $deprecations[] = sprintf($deprecation, $className); + } + } + } + + $forcePatchTypes = $this->patchTypes['force']; + + if ($canAddReturnType = null !== $forcePatchTypes && false === strpos($method->getFileName(), \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR)) { + if ('void' !== (self::MAGIC_METHODS[$method->name] ?? 'void')) { + $this->patchTypes['force'] = $forcePatchTypes ?: 'docblock'; + } + + $canAddReturnType = 2 === (int) $forcePatchTypes + || false !== stripos($method->getFileName(), \DIRECTORY_SEPARATOR.'Tests'.\DIRECTORY_SEPARATOR) + || $refl->isFinal() + || $method->isFinal() + || $method->isPrivate() + || ('.' === (self::$internal[$class] ?? null) && !$refl->isAbstract()) + || '.' === (self::$final[$class] ?? null) + || '' === ($doc['final'][0] ?? null) + || '' === ($doc['internal'][0] ?? null) + ; + } + + if (null !== ($returnType = self::$returnTypes[$class][$method->name] ?? null) && 'docblock' === $this->patchTypes['force'] && !$method->hasReturnType() && isset(TentativeTypes::RETURN_TYPES[$returnType[2]][$method->name])) { + $this->patchReturnTypeWillChange($method); + } + + if (null !== ($returnType ?? $returnType = self::MAGIC_METHODS[$method->name] ?? null) && !$method->hasReturnType() && !isset($doc['return'])) { + [$normalizedType, $returnType, $declaringClass, $declaringFile] = \is_string($returnType) ? [$returnType, $returnType, '', ''] : $returnType; + + if ($canAddReturnType && 'docblock' !== $this->patchTypes['force']) { + $this->patchMethod($method, $returnType, $declaringFile, $normalizedType); + } + if (!isset($doc['deprecated']) && strncmp($ns, $declaringClass, $len)) { + if ('docblock' === $this->patchTypes['force']) { + $this->patchMethod($method, $returnType, $declaringFile, $normalizedType); + } elseif ('' !== $declaringClass && $this->patchTypes['deprecations']) { + $deprecations[] = sprintf('Method "%s::%s()" might add "%s" as a native return type declaration in the future. Do the same in %s "%s" now to avoid errors or add an explicit @return annotation to suppress this message.', $declaringClass, $method->name, $normalizedType, interface_exists($declaringClass) ? 'implementation' : 'child class', $className); + } + } + } + + if (!$doc) { + $this->patchTypes['force'] = $forcePatchTypes; + + continue; + } + + if (isset($doc['return']) || 'void' !== (self::MAGIC_METHODS[$method->name] ?? 'void')) { + $this->setReturnType($doc['return'] ?? self::MAGIC_METHODS[$method->name], $method->class, $method->name, $method->getFileName(), $parent, $method->getReturnType()); + + if (isset(self::$returnTypes[$class][$method->name][0]) && $canAddReturnType) { + $this->fixReturnStatements($method, self::$returnTypes[$class][$method->name][0]); + } + + if ($method->isPrivate()) { + unset(self::$returnTypes[$class][$method->name]); + } + } + + $this->patchTypes['force'] = $forcePatchTypes; + + if ($method->isPrivate()) { + continue; + } + + $finalOrInternal = false; + + foreach (['final', 'internal'] as $annotation) { + if (null !== $description = $doc[$annotation][0] ?? null) { + self::${$annotation.'Methods'}[$class][$method->name] = [$class, '' !== $description ? ' '.$description.(preg_match('/[[:punct:]]$/', $description) ? '' : '.') : '.']; + $finalOrInternal = true; + } + } + + if ($finalOrInternal || $method->isConstructor() || !isset($doc['param']) || StatelessInvocation::class === $class) { + continue; + } + if (!isset(self::$annotatedParameters[$class][$method->name])) { + $definedParameters = []; + foreach ($method->getParameters() as $parameter) { + $definedParameters[$parameter->name] = true; + } + } + foreach ($doc['param'] as $parameterName => $parameterType) { + if (!isset($definedParameters[$parameterName])) { + self::$annotatedParameters[$class][$method->name][$parameterName] = sprintf('The "%%s::%s()" method will require a new "%s$%s" argument in the next major version of its %s "%s", not defining it is deprecated.', $method->name, $parameterType ? $parameterType.' ' : '', $parameterName, interface_exists($className) ? 'interface' : 'parent class', $className); + } + } + } + + return $deprecations; + } + + public function checkCase(\ReflectionClass $refl, string $file, string $class): ?array + { + $real = explode('\\', $class.strrchr($file, '.')); + $tail = explode(\DIRECTORY_SEPARATOR, str_replace('/', \DIRECTORY_SEPARATOR, $file)); + + $i = \count($tail) - 1; + $j = \count($real) - 1; + + while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) { + --$i; + --$j; + } + + array_splice($tail, 0, $i + 1); + + if (!$tail) { + return null; + } + + $tail = \DIRECTORY_SEPARATOR.implode(\DIRECTORY_SEPARATOR, $tail); + $tailLen = \strlen($tail); + $real = $refl->getFileName(); + + if (2 === self::$caseCheck) { + $real = $this->darwinRealpath($real); + } + + if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true) + && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false) + ) { + return [substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1)]; + } + + return null; + } + + /** + * `realpath` on MacOSX doesn't normalize the case of characters. + */ + private function darwinRealpath(string $real): string + { + $i = 1 + strrpos($real, '/'); + $file = substr($real, $i); + $real = substr($real, 0, $i); + + if (isset(self::$darwinCache[$real])) { + $kDir = $real; + } else { + $kDir = strtolower($real); + + if (isset(self::$darwinCache[$kDir])) { + $real = self::$darwinCache[$kDir][0]; + } else { + $dir = getcwd(); + + if (!@chdir($real)) { + return $real.$file; + } + + $real = getcwd().'/'; + chdir($dir); + + $dir = $real; + $k = $kDir; + $i = \strlen($dir) - 1; + while (!isset(self::$darwinCache[$k])) { + self::$darwinCache[$k] = [$dir, []]; + self::$darwinCache[$dir] = &self::$darwinCache[$k]; + + while ('/' !== $dir[--$i]) { + } + $k = substr($k, 0, ++$i); + $dir = substr($dir, 0, $i--); + } + } + } + + $dirFiles = self::$darwinCache[$kDir][1]; + + if (!isset($dirFiles[$file]) && ') : eval()\'d code' === substr($file, -17)) { + // Get the file name from "file_name.php(123) : eval()'d code" + $file = substr($file, 0, strrpos($file, '(', -17)); + } + + if (isset($dirFiles[$file])) { + return $real.$dirFiles[$file]; + } + + $kFile = strtolower($file); + + if (!isset($dirFiles[$kFile])) { + foreach (scandir($real, 2) as $f) { + if ('.' !== $f[0]) { + $dirFiles[$f] = $f; + if ($f === $file) { + $kFile = $k = $file; + } elseif ($f !== $k = strtolower($f)) { + $dirFiles[$k] = $f; + } + } + } + self::$darwinCache[$kDir][1] = $dirFiles; + } + + return $real.$dirFiles[$kFile]; + } + + /** + * `class_implements` includes interfaces from the parents so we have to manually exclude them. + * + * @return string[] + */ + private function getOwnInterfaces(string $class, ?string $parent): array + { + $ownInterfaces = class_implements($class, false); + + if ($parent) { + foreach (class_implements($parent, false) as $interface) { + unset($ownInterfaces[$interface]); + } + } + + foreach ($ownInterfaces as $interface) { + foreach (class_implements($interface) as $interface) { + unset($ownInterfaces[$interface]); + } + } + + return $ownInterfaces; + } + + private function setReturnType(string $types, string $class, string $method, string $filename, ?string $parent, \ReflectionType $returnType = null): void + { + if ('__construct' === $method) { + return; + } + + if ($nullable = 0 === strpos($types, 'null|')) { + $types = substr($types, 5); + } elseif ($nullable = '|null' === substr($types, -5)) { + $types = substr($types, 0, -5); + } + $arrayType = ['array' => 'array']; + $typesMap = []; + $glue = false !== strpos($types, '&') ? '&' : '|'; + foreach (explode($glue, $types) as $t) { + $t = self::SPECIAL_RETURN_TYPES[strtolower($t)] ?? $t; + $typesMap[$this->normalizeType($t, $class, $parent, $returnType)][$t] = $t; + } + + if (isset($typesMap['array'])) { + if (isset($typesMap['Traversable']) || isset($typesMap['\Traversable'])) { + $typesMap['iterable'] = $arrayType !== $typesMap['array'] ? $typesMap['array'] : ['iterable']; + unset($typesMap['array'], $typesMap['Traversable'], $typesMap['\Traversable']); + } elseif ($arrayType !== $typesMap['array'] && isset(self::$returnTypes[$class][$method]) && !$returnType) { + return; + } + } + + if (isset($typesMap['array']) && isset($typesMap['iterable'])) { + if ($arrayType !== $typesMap['array']) { + $typesMap['iterable'] = $typesMap['array']; + } + unset($typesMap['array']); + } + + $iterable = $object = true; + foreach ($typesMap as $n => $t) { + if ('null' !== $n) { + $iterable = $iterable && (\in_array($n, ['array', 'iterable']) || false !== strpos($n, 'Iterator')); + $object = $object && (\in_array($n, ['callable', 'object', '$this', 'static']) || !isset(self::SPECIAL_RETURN_TYPES[$n])); + } + } + + $phpTypes = []; + $docTypes = []; + + foreach ($typesMap as $n => $t) { + if ('null' === $n) { + $nullable = true; + continue; + } + + $docTypes[] = $t; + + if ('mixed' === $n || 'void' === $n) { + $nullable = false; + $phpTypes = ['' => $n]; + continue; + } + + if ('resource' === $n) { + // there is no native type for "resource" + return; + } + + if (!isset($phpTypes[''])) { + $phpTypes[] = $n; + } + } + $docTypes = array_merge([], ...$docTypes); + + if (!$phpTypes) { + return; + } + + if (1 < \count($phpTypes)) { + if ($iterable && '8.0' > $this->patchTypes['php']) { + $phpTypes = $docTypes = ['iterable']; + } elseif ($object && 'object' === $this->patchTypes['force']) { + $phpTypes = $docTypes = ['object']; + } elseif ('8.0' > $this->patchTypes['php']) { + // ignore multi-types return declarations + return; + } + } + + $phpType = sprintf($nullable ? (1 < \count($phpTypes) ? '%s|null' : '?%s') : '%s', implode($glue, $phpTypes)); + $docType = sprintf($nullable ? '%s|null' : '%s', implode($glue, $docTypes)); + + self::$returnTypes[$class][$method] = [$phpType, $docType, $class, $filename]; + } + + private function normalizeType(string $type, string $class, ?string $parent, ?\ReflectionType $returnType): string + { + if (isset(self::SPECIAL_RETURN_TYPES[$lcType = strtolower($type)])) { + if ('parent' === $lcType = self::SPECIAL_RETURN_TYPES[$lcType]) { + $lcType = null !== $parent ? '\\'.$parent : 'parent'; + } elseif ('self' === $lcType) { + $lcType = '\\'.$class; + } + + return $lcType; + } + + // We could resolve "use" statements to return the FQDN + // but this would be too expensive for a runtime checker + + if ('[]' !== substr($type, -2)) { + return $type; + } + + if ($returnType instanceof \ReflectionNamedType) { + $type = $returnType->getName(); + + if ('mixed' !== $type) { + return isset(self::SPECIAL_RETURN_TYPES[$type]) ? $type : '\\'.$type; + } + } + + return 'array'; + } + + /** + * Utility method to add #[ReturnTypeWillChange] where php triggers deprecations. + */ + private function patchReturnTypeWillChange(\ReflectionMethod $method) + { + if (\PHP_VERSION_ID >= 80000 && \count($method->getAttributes(\ReturnTypeWillChange::class))) { + return; + } + + if (!is_file($file = $method->getFileName())) { + return; + } + + $fileOffset = self::$fileOffsets[$file] ?? 0; + + $code = file($file); + + $startLine = $method->getStartLine() + $fileOffset - 2; + + if (false !== stripos($code[$startLine], 'ReturnTypeWillChange')) { + return; + } + + $code[$startLine] .= " #[\\ReturnTypeWillChange]\n"; + self::$fileOffsets[$file] = 1 + $fileOffset; + file_put_contents($file, $code); + } + + /** + * Utility method to add @return annotations to the Symfony code-base where it triggers self-deprecations. + */ + private function patchMethod(\ReflectionMethod $method, string $returnType, string $declaringFile, string $normalizedType) + { + static $patchedMethods = []; + static $useStatements = []; + + if (!is_file($file = $method->getFileName()) || isset($patchedMethods[$file][$startLine = $method->getStartLine()])) { + return; + } + + $patchedMethods[$file][$startLine] = true; + $fileOffset = self::$fileOffsets[$file] ?? 0; + $startLine += $fileOffset - 2; + if ($nullable = '|null' === substr($returnType, -5)) { + $returnType = substr($returnType, 0, -5); + } + $glue = false !== strpos($returnType, '&') ? '&' : '|'; + $returnType = explode($glue, $returnType); + $code = file($file); + + foreach ($returnType as $i => $type) { + if (preg_match('/((?:\[\])+)$/', $type, $m)) { + $type = substr($type, 0, -\strlen($m[1])); + $format = '%s'.$m[1]; + } else { + $format = null; + } + + if (isset(self::SPECIAL_RETURN_TYPES[$type]) || ('\\' === $type[0] && !$p = strrpos($type, '\\', 1))) { + continue; + } + + [$namespace, $useOffset, $useMap] = $useStatements[$file] ?? $useStatements[$file] = self::getUseStatements($file); + + if ('\\' !== $type[0]) { + [$declaringNamespace, , $declaringUseMap] = $useStatements[$declaringFile] ?? $useStatements[$declaringFile] = self::getUseStatements($declaringFile); + + $p = strpos($type, '\\', 1); + $alias = $p ? substr($type, 0, $p) : $type; + + if (isset($declaringUseMap[$alias])) { + $type = '\\'.$declaringUseMap[$alias].($p ? substr($type, $p) : ''); + } else { + $type = '\\'.$declaringNamespace.$type; + } + + $p = strrpos($type, '\\', 1); + } + + $alias = substr($type, 1 + $p); + $type = substr($type, 1); + + if (!isset($useMap[$alias]) && (class_exists($c = $namespace.$alias) || interface_exists($c) || trait_exists($c))) { + $useMap[$alias] = $c; + } + + if (!isset($useMap[$alias])) { + $useStatements[$file][2][$alias] = $type; + $code[$useOffset] = "use $type;\n".$code[$useOffset]; + ++$fileOffset; + } elseif ($useMap[$alias] !== $type) { + $alias .= 'FIXME'; + $useStatements[$file][2][$alias] = $type; + $code[$useOffset] = "use $type as $alias;\n".$code[$useOffset]; + ++$fileOffset; + } + + $returnType[$i] = null !== $format ? sprintf($format, $alias) : $alias; + } + + if ('docblock' === $this->patchTypes['force'] || ('object' === $normalizedType && '7.1' === $this->patchTypes['php'])) { + $returnType = implode($glue, $returnType).($nullable ? '|null' : ''); + + if (false !== strpos($code[$startLine], '#[')) { + --$startLine; + } + + if ($method->getDocComment()) { + $code[$startLine] = " * @return $returnType\n".$code[$startLine]; + } else { + $code[$startLine] .= <<fixReturnStatements($method, $normalizedType); + } + + private static function getUseStatements(string $file): array + { + $namespace = ''; + $useMap = []; + $useOffset = 0; + + if (!is_file($file)) { + return [$namespace, $useOffset, $useMap]; + } + + $file = file($file); + + for ($i = 0; $i < \count($file); ++$i) { + if (preg_match('/^(class|interface|trait|abstract) /', $file[$i])) { + break; + } + + if (0 === strpos($file[$i], 'namespace ')) { + $namespace = substr($file[$i], \strlen('namespace '), -2).'\\'; + $useOffset = $i + 2; + } + + if (0 === strpos($file[$i], 'use ')) { + $useOffset = $i; + + for (; 0 === strpos($file[$i], 'use '); ++$i) { + $u = explode(' as ', substr($file[$i], 4, -2), 2); + + if (1 === \count($u)) { + $p = strrpos($u[0], '\\'); + $useMap[substr($u[0], false !== $p ? 1 + $p : 0)] = $u[0]; + } else { + $useMap[$u[1]] = $u[0]; + } + } + + break; + } + } + + return [$namespace, $useOffset, $useMap]; + } + + private function fixReturnStatements(\ReflectionMethod $method, string $returnType) + { + if ('docblock' !== $this->patchTypes['force']) { + if ('7.1' === $this->patchTypes['php'] && 'object' === ltrim($returnType, '?')) { + return; + } + + if ('7.4' > $this->patchTypes['php'] && $method->hasReturnType()) { + return; + } + + if ('8.0' > $this->patchTypes['php'] && (false !== strpos($returnType, '|') || \in_array($returnType, ['mixed', 'static'], true))) { + return; + } + + if ('8.1' > $this->patchTypes['php'] && false !== strpos($returnType, '&')) { + return; + } + } + + if (!is_file($file = $method->getFileName())) { + return; + } + + $fixedCode = $code = file($file); + $i = (self::$fileOffsets[$file] ?? 0) + $method->getStartLine(); + + if ('?' !== $returnType && 'docblock' !== $this->patchTypes['force']) { + $fixedCode[$i - 1] = preg_replace('/\)(?::[^;\n]++)?(;?\n)/', "): $returnType\\1", $code[$i - 1]); + } + + $end = $method->isGenerator() ? $i : $method->getEndLine(); + for (; $i < $end; ++$i) { + if ('void' === $returnType) { + $fixedCode[$i] = str_replace(' return null;', ' return;', $code[$i]); + } elseif ('mixed' === $returnType || '?' === $returnType[0]) { + $fixedCode[$i] = str_replace(' return;', ' return null;', $code[$i]); + } else { + $fixedCode[$i] = str_replace(' return;', " return $returnType!?;", $code[$i]); + } + } + + if ($fixedCode !== $code) { + file_put_contents($file, $fixedCode); + } + } + + /** + * @param \ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflector + */ + private function parsePhpDoc(\Reflector $reflector): array + { + if (!$doc = $reflector->getDocComment()) { + return []; + } + + $tagName = ''; + $tagContent = ''; + + $tags = []; + + foreach (explode("\n", substr($doc, 3, -2)) as $line) { + $line = ltrim($line); + $line = ltrim($line, '*'); + + if ('' === $line = trim($line)) { + if ('' !== $tagName) { + $tags[$tagName][] = $tagContent; + } + $tagName = $tagContent = ''; + continue; + } + + if ('@' === $line[0]) { + if ('' !== $tagName) { + $tags[$tagName][] = $tagContent; + $tagContent = ''; + } + + if (preg_match('{^@([-a-zA-Z0-9_:]++)(\s|$)}', $line, $m)) { + $tagName = $m[1]; + $tagContent = str_replace("\t", ' ', ltrim(substr($line, 2 + \strlen($tagName)))); + } else { + $tagName = ''; + } + } elseif ('' !== $tagName) { + $tagContent .= ' '.str_replace("\t", ' ', $line); + } + } + + if ('' !== $tagName) { + $tags[$tagName][] = $tagContent; + } + + foreach ($tags['method'] ?? [] as $i => $method) { + unset($tags['method'][$i]); + + $parts = preg_split('{(\s++|\((?:[^()]*+|(?R))*\)(?: *: *[^ ]++)?|<(?:[^<>]*+|(?R))*>|\{(?:[^{}]*+|(?R))*\})}', $method, -1, \PREG_SPLIT_DELIM_CAPTURE); + $returnType = ''; + $static = 'static' === $parts[0]; + + for ($i = $static ? 2 : 0; null !== $p = $parts[$i] ?? null; $i += 2) { + if (\in_array($p, ['', '|', '&', 'callable'], true) || \in_array(substr($returnType, -1), ['|', '&'], true)) { + $returnType .= trim($parts[$i - 1] ?? '').$p; + continue; + } + + $signature = '(' === ($parts[$i + 1][0] ?? '(') ? $parts[$i + 1] ?? '()' : null; + + if (null === $signature && '' === $returnType) { + $returnType = $p; + continue; + } + + if ($static && 2 === $i) { + $static = false; + $returnType = 'static'; + } + + if (\in_array($description = trim(implode('', \array_slice($parts, 2 + $i))), ['', '.'], true)) { + $description = null; + } elseif (!preg_match('/[.!]$/', $description)) { + $description .= '.'; + } + + $tags['method'][$p] = [$static, $returnType, $signature ?? '()', $description]; + break; + } + } + + foreach ($tags['param'] ?? [] as $i => $param) { + unset($tags['param'][$i]); + + if (\strlen($param) !== strcspn($param, '<{(')) { + $param = preg_replace('{\(([^()]*+|(?R))*\)(?: *: *[^ ]++)?|<([^<>]*+|(?R))*>|\{([^{}]*+|(?R))*\}}', '', $param); + } + + if (false === $i = strpos($param, '$')) { + continue; + } + + $type = 0 === $i ? '' : rtrim(substr($param, 0, $i), ' &'); + $param = substr($param, 1 + $i, (strpos($param, ' ', $i) ?: (1 + $i + \strlen($param))) - $i - 1); + + $tags['param'][$param] = $type; + } + + foreach (['var', 'return'] as $k) { + if (null === $v = $tags[$k][0] ?? null) { + continue; + } + if (\strlen($v) !== strcspn($v, '<{(')) { + $v = preg_replace('{\(([^()]*+|(?R))*\)(?: *: *[^ ]++)?|<([^<>]*+|(?R))*>|\{([^{}]*+|(?R))*\}}', '', $v); + } + + $tags[$k] = substr($v, 0, strpos($v, ' ') ?: \strlen($v)) ?: null; + } + + return $tags; + } +} diff --git a/lib/symfony/error-handler/Error/ClassNotFoundError.php b/lib/symfony/error-handler/Error/ClassNotFoundError.php new file mode 100644 index 0000000000..443fba2c3b --- /dev/null +++ b/lib/symfony/error-handler/Error/ClassNotFoundError.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\ErrorHandler\Error; + +class ClassNotFoundError extends \Error +{ + /** + * {@inheritdoc} + */ + public function __construct(string $message, \Throwable $previous) + { + parent::__construct($message, $previous->getCode(), $previous->getPrevious()); + + foreach ([ + 'file' => $previous->getFile(), + 'line' => $previous->getLine(), + 'trace' => $previous->getTrace(), + ] as $property => $value) { + $refl = new \ReflectionProperty(\Error::class, $property); + $refl->setAccessible(true); + $refl->setValue($this, $value); + } + } +} diff --git a/lib/symfony/error-handler/Error/FatalError.php b/lib/symfony/error-handler/Error/FatalError.php new file mode 100644 index 0000000000..57fc690e26 --- /dev/null +++ b/lib/symfony/error-handler/Error/FatalError.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\ErrorHandler\Error; + +class FatalError extends \Error +{ + private $error; + + /** + * {@inheritdoc} + * + * @param array $error An array as returned by error_get_last() + */ + public function __construct(string $message, int $code, array $error, int $traceOffset = null, bool $traceArgs = true, array $trace = null) + { + parent::__construct($message, $code); + + $this->error = $error; + + if (null !== $trace) { + if (!$traceArgs) { + foreach ($trace as &$frame) { + unset($frame['args'], $frame['this'], $frame); + } + } + } elseif (null !== $traceOffset) { + if (\function_exists('xdebug_get_function_stack') && $trace = @xdebug_get_function_stack()) { + if (0 < $traceOffset) { + array_splice($trace, -$traceOffset); + } + + foreach ($trace as &$frame) { + if (!isset($frame['type'])) { + // XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695 + if (isset($frame['class'])) { + $frame['type'] = '::'; + } + } elseif ('dynamic' === $frame['type']) { + $frame['type'] = '->'; + } elseif ('static' === $frame['type']) { + $frame['type'] = '::'; + } + + // XDebug also has a different name for the parameters array + if (!$traceArgs) { + unset($frame['params'], $frame['args']); + } elseif (isset($frame['params']) && !isset($frame['args'])) { + $frame['args'] = $frame['params']; + unset($frame['params']); + } + } + + unset($frame); + $trace = array_reverse($trace); + } else { + $trace = []; + } + } + + foreach ([ + 'file' => $error['file'], + 'line' => $error['line'], + 'trace' => $trace, + ] as $property => $value) { + if (null !== $value) { + $refl = new \ReflectionProperty(\Error::class, $property); + $refl->setAccessible(true); + $refl->setValue($this, $value); + } + } + } + + /** + * {@inheritdoc} + */ + public function getError(): array + { + return $this->error; + } +} diff --git a/lib/symfony/error-handler/Error/OutOfMemoryError.php b/lib/symfony/error-handler/Error/OutOfMemoryError.php new file mode 100644 index 0000000000..d685c3d369 --- /dev/null +++ b/lib/symfony/error-handler/Error/OutOfMemoryError.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\Error; + +class OutOfMemoryError extends FatalError +{ +} diff --git a/lib/symfony/error-handler/Error/UndefinedFunctionError.php b/lib/symfony/error-handler/Error/UndefinedFunctionError.php new file mode 100644 index 0000000000..b57dd1579d --- /dev/null +++ b/lib/symfony/error-handler/Error/UndefinedFunctionError.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\ErrorHandler\Error; + +class UndefinedFunctionError extends \Error +{ + /** + * {@inheritdoc} + */ + public function __construct(string $message, \Throwable $previous) + { + parent::__construct($message, $previous->getCode(), $previous->getPrevious()); + + foreach ([ + 'file' => $previous->getFile(), + 'line' => $previous->getLine(), + 'trace' => $previous->getTrace(), + ] as $property => $value) { + $refl = new \ReflectionProperty(\Error::class, $property); + $refl->setAccessible(true); + $refl->setValue($this, $value); + } + } +} diff --git a/lib/symfony/error-handler/Error/UndefinedMethodError.php b/lib/symfony/error-handler/Error/UndefinedMethodError.php new file mode 100644 index 0000000000..adc8731f36 --- /dev/null +++ b/lib/symfony/error-handler/Error/UndefinedMethodError.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\ErrorHandler\Error; + +class UndefinedMethodError extends \Error +{ + /** + * {@inheritdoc} + */ + public function __construct(string $message, \Throwable $previous) + { + parent::__construct($message, $previous->getCode(), $previous->getPrevious()); + + foreach ([ + 'file' => $previous->getFile(), + 'line' => $previous->getLine(), + 'trace' => $previous->getTrace(), + ] as $property => $value) { + $refl = new \ReflectionProperty(\Error::class, $property); + $refl->setAccessible(true); + $refl->setValue($this, $value); + } + } +} diff --git a/lib/symfony/error-handler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php b/lib/symfony/error-handler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php new file mode 100644 index 0000000000..f85d275151 --- /dev/null +++ b/lib/symfony/error-handler/ErrorEnhancer/ClassNotFoundErrorEnhancer.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\ErrorEnhancer; + +use Composer\Autoload\ClassLoader; +use Symfony\Component\ErrorHandler\DebugClassLoader; +use Symfony\Component\ErrorHandler\Error\ClassNotFoundError; +use Symfony\Component\ErrorHandler\Error\FatalError; + +/** + * @author Fabien Potencier + */ +class ClassNotFoundErrorEnhancer implements ErrorEnhancerInterface +{ + /** + * {@inheritdoc} + */ + public function enhance(\Throwable $error): ?\Throwable + { + // Some specific versions of PHP produce a fatal error when extending a not found class. + $message = !$error instanceof FatalError ? $error->getMessage() : $error->getError()['message']; + if (!preg_match('/^(Class|Interface|Trait) [\'"]([^\'"]+)[\'"] not found$/', $message, $matches)) { + return null; + } + $typeName = strtolower($matches[1]); + $fullyQualifiedClassName = $matches[2]; + + if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) { + $className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1); + $namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex); + $message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix); + $tail = ' for another namespace?'; + } else { + $className = $fullyQualifiedClassName; + $message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className); + $tail = '?'; + } + + if ($candidates = $this->getClassCandidates($className)) { + $tail = array_pop($candidates).'"?'; + if ($candidates) { + $tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail; + } else { + $tail = ' for "'.$tail; + } + } + $message .= "\nDid you forget a \"use\" statement".$tail; + + return new ClassNotFoundError($message, $error); + } + + /** + * Tries to guess the full namespace for a given class name. + * + * By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer + * autoloader (that should cover all common cases). + * + * @param string $class A class name (without its namespace) + * + * Returns an array of possible fully qualified class names + */ + private function getClassCandidates(string $class): array + { + if (!\is_array($functions = spl_autoload_functions())) { + return []; + } + + // find Symfony and Composer autoloaders + $classes = []; + + foreach ($functions as $function) { + if (!\is_array($function)) { + continue; + } + // get class loaders wrapped by DebugClassLoader + if ($function[0] instanceof DebugClassLoader) { + $function = $function[0]->getClassLoader(); + + if (!\is_array($function)) { + continue; + } + } + + if ($function[0] instanceof ClassLoader) { + foreach ($function[0]->getPrefixes() as $prefix => $paths) { + foreach ($paths as $path) { + $classes[] = $this->findClassInPath($path, $class, $prefix); + } + } + + foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) { + foreach ($paths as $path) { + $classes[] = $this->findClassInPath($path, $class, $prefix); + } + } + } + } + + return array_unique(array_merge([], ...$classes)); + } + + private function findClassInPath(string $path, string $class, string $prefix): array + { + if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) { + return []; + } + + $classes = []; + $filename = $class.'.php'; + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) { + $classes[] = $class; + } + } + + return $classes; + } + + private function convertFileToClass(string $path, string $file, string $prefix): ?string + { + $candidates = [ + // namespaced class + $namespacedClass = str_replace([$path.\DIRECTORY_SEPARATOR, '.php', '/'], ['', '', '\\'], $file), + // namespaced class (with target dir) + $prefix.$namespacedClass, + // namespaced class (with target dir and separator) + $prefix.'\\'.$namespacedClass, + // PEAR class + str_replace('\\', '_', $namespacedClass), + // PEAR class (with target dir) + str_replace('\\', '_', $prefix.$namespacedClass), + // PEAR class (with target dir and separator) + str_replace('\\', '_', $prefix.'\\'.$namespacedClass), + ]; + + if ($prefix) { + $candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); }); + } + + // We cannot use the autoloader here as most of them use require; but if the class + // is not found, the new autoloader call will require the file again leading to a + // "cannot redeclare class" error. + foreach ($candidates as $candidate) { + if ($this->classExists($candidate)) { + return $candidate; + } + } + + try { + require_once $file; + } catch (\Throwable $e) { + return null; + } + + foreach ($candidates as $candidate) { + if ($this->classExists($candidate)) { + return $candidate; + } + } + + return null; + } + + private function classExists(string $class): bool + { + return class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false); + } +} diff --git a/lib/symfony/error-handler/ErrorEnhancer/ErrorEnhancerInterface.php b/lib/symfony/error-handler/ErrorEnhancer/ErrorEnhancerInterface.php new file mode 100644 index 0000000000..7c3f4ef940 --- /dev/null +++ b/lib/symfony/error-handler/ErrorEnhancer/ErrorEnhancerInterface.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 Symfony\Component\ErrorHandler\ErrorEnhancer; + +interface ErrorEnhancerInterface +{ + /** + * Returns an \Throwable instance if the class is able to improve the error, null otherwise. + */ + public function enhance(\Throwable $error): ?\Throwable; +} diff --git a/lib/symfony/error-handler/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php b/lib/symfony/error-handler/ErrorEnhancer/UndefinedFunctionErrorEnhancer.php new file mode 100644 index 0000000000..f4c49c2856 --- /dev/null +++ b/lib/symfony/error-handler/ErrorEnhancer/UndefinedFunctionErrorEnhancer.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 Symfony\Component\ErrorHandler\ErrorEnhancer; + +use Symfony\Component\ErrorHandler\Error\FatalError; +use Symfony\Component\ErrorHandler\Error\UndefinedFunctionError; + +/** + * @author Fabien Potencier + */ +class UndefinedFunctionErrorEnhancer implements ErrorEnhancerInterface +{ + /** + * {@inheritdoc} + */ + public function enhance(\Throwable $error): ?\Throwable + { + if ($error instanceof FatalError) { + return null; + } + + $message = $error->getMessage(); + $messageLen = \strlen($message); + $notFoundSuffix = '()'; + $notFoundSuffixLen = \strlen($notFoundSuffix); + if ($notFoundSuffixLen > $messageLen) { + return null; + } + + if (0 !== substr_compare($message, $notFoundSuffix, -$notFoundSuffixLen)) { + return null; + } + + $prefix = 'Call to undefined function '; + $prefixLen = \strlen($prefix); + if (0 !== strpos($message, $prefix)) { + return null; + } + + $fullyQualifiedFunctionName = substr($message, $prefixLen, -$notFoundSuffixLen); + if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) { + $functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1); + $namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex); + $message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix); + } else { + $functionName = $fullyQualifiedFunctionName; + $message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName); + } + + $candidates = []; + foreach (get_defined_functions() as $type => $definedFunctionNames) { + foreach ($definedFunctionNames as $definedFunctionName) { + if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) { + $definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1); + } else { + $definedFunctionNameBasename = $definedFunctionName; + } + + if ($definedFunctionNameBasename === $functionName) { + $candidates[] = '\\'.$definedFunctionName; + } + } + } + + if ($candidates) { + sort($candidates); + $last = array_pop($candidates).'"?'; + if ($candidates) { + $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last; + } else { + $candidates = '"'.$last; + } + $message .= "\nDid you mean to call ".$candidates; + } + + return new UndefinedFunctionError($message, $error); + } +} diff --git a/lib/symfony/error-handler/ErrorEnhancer/UndefinedMethodErrorEnhancer.php b/lib/symfony/error-handler/ErrorEnhancer/UndefinedMethodErrorEnhancer.php new file mode 100644 index 0000000000..c4355f92ce --- /dev/null +++ b/lib/symfony/error-handler/ErrorEnhancer/UndefinedMethodErrorEnhancer.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\ErrorEnhancer; + +use Symfony\Component\ErrorHandler\Error\FatalError; +use Symfony\Component\ErrorHandler\Error\UndefinedMethodError; + +/** + * @author Grégoire Pineau + */ +class UndefinedMethodErrorEnhancer implements ErrorEnhancerInterface +{ + /** + * {@inheritdoc} + */ + public function enhance(\Throwable $error): ?\Throwable + { + if ($error instanceof FatalError) { + return null; + } + + $message = $error->getMessage(); + preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $message, $matches); + if (!$matches) { + return null; + } + + $className = $matches[1]; + $methodName = $matches[2]; + + $message = sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className); + + if ('' === $methodName || !class_exists($className) || null === $methods = get_class_methods($className)) { + // failed to get the class or its methods on which an unknown method was called (for example on an anonymous class) + return new UndefinedMethodError($message, $error); + } + + $candidates = []; + foreach ($methods as $definedMethodName) { + $lev = levenshtein($methodName, $definedMethodName); + if ($lev <= \strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) { + $candidates[] = $definedMethodName; + } + } + + if ($candidates) { + sort($candidates); + $last = array_pop($candidates).'"?'; + if ($candidates) { + $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last; + } else { + $candidates = '"'.$last; + } + + $message .= "\nDid you mean to call ".$candidates; + } + + return new UndefinedMethodError($message, $error); + } +} diff --git a/lib/symfony/error-handler/ErrorHandler.php b/lib/symfony/error-handler/ErrorHandler.php new file mode 100644 index 0000000000..003040242f --- /dev/null +++ b/lib/symfony/error-handler/ErrorHandler.php @@ -0,0 +1,798 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use Symfony\Component\ErrorHandler\Error\FatalError; +use Symfony\Component\ErrorHandler\Error\OutOfMemoryError; +use Symfony\Component\ErrorHandler\ErrorEnhancer\ClassNotFoundErrorEnhancer; +use Symfony\Component\ErrorHandler\ErrorEnhancer\ErrorEnhancerInterface; +use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedFunctionErrorEnhancer; +use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedMethodErrorEnhancer; +use Symfony\Component\ErrorHandler\ErrorRenderer\CliErrorRenderer; +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; + +/** + * A generic ErrorHandler for the PHP engine. + * + * Provides five bit fields that control how errors are handled: + * - thrownErrors: errors thrown as \ErrorException + * - loggedErrors: logged errors, when not @-silenced + * - scopedErrors: errors thrown or logged with their local context + * - tracedErrors: errors logged with their stack trace + * - screamedErrors: never @-silenced errors + * + * Each error level can be logged by a dedicated PSR-3 logger object. + * Screaming only applies to logging. + * Throwing takes precedence over logging. + * Uncaught exceptions are logged as E_ERROR. + * E_DEPRECATED and E_USER_DEPRECATED levels never throw. + * E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw. + * Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so. + * As errors have a performance cost, repeated errors are all logged, so that the developer + * can see them and weight them as more important to fix than others of the same level. + * + * @author Nicolas Grekas + * @author Grégoire Pineau + * + * @final + */ +class ErrorHandler +{ + private $levels = [ + \E_DEPRECATED => 'Deprecated', + \E_USER_DEPRECATED => 'User Deprecated', + \E_NOTICE => 'Notice', + \E_USER_NOTICE => 'User Notice', + \E_STRICT => 'Runtime Notice', + \E_WARNING => 'Warning', + \E_USER_WARNING => 'User Warning', + \E_COMPILE_WARNING => 'Compile Warning', + \E_CORE_WARNING => 'Core Warning', + \E_USER_ERROR => 'User Error', + \E_RECOVERABLE_ERROR => 'Catchable Fatal Error', + \E_COMPILE_ERROR => 'Compile Error', + \E_PARSE => 'Parse Error', + \E_ERROR => 'Error', + \E_CORE_ERROR => 'Core Error', + ]; + + private $loggers = [ + \E_DEPRECATED => [null, LogLevel::INFO], + \E_USER_DEPRECATED => [null, LogLevel::INFO], + \E_NOTICE => [null, LogLevel::WARNING], + \E_USER_NOTICE => [null, LogLevel::WARNING], + \E_STRICT => [null, LogLevel::WARNING], + \E_WARNING => [null, LogLevel::WARNING], + \E_USER_WARNING => [null, LogLevel::WARNING], + \E_COMPILE_WARNING => [null, LogLevel::WARNING], + \E_CORE_WARNING => [null, LogLevel::WARNING], + \E_USER_ERROR => [null, LogLevel::CRITICAL], + \E_RECOVERABLE_ERROR => [null, LogLevel::CRITICAL], + \E_COMPILE_ERROR => [null, LogLevel::CRITICAL], + \E_PARSE => [null, LogLevel::CRITICAL], + \E_ERROR => [null, LogLevel::CRITICAL], + \E_CORE_ERROR => [null, LogLevel::CRITICAL], + ]; + + private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED + private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED + private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE + private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE + private $loggedErrors = 0; + private $configureException; + private $debug; + + private $isRecursive = 0; + private $isRoot = false; + private $exceptionHandler; + private $bootstrappingLogger; + + private static $reservedMemory; + private static $toStringException; + private static $silencedErrorCache = []; + private static $silencedErrorCount = 0; + private static $exitCode = 0; + + /** + * Registers the error handler. + */ + public static function register(self $handler = null, bool $replace = true): self + { + if (null === self::$reservedMemory) { + self::$reservedMemory = str_repeat('x', 32768); + register_shutdown_function(__CLASS__.'::handleFatalError'); + } + + if ($handlerIsNew = null === $handler) { + $handler = new static(); + } + + if (null === $prev = set_error_handler([$handler, 'handleError'])) { + restore_error_handler(); + // Specifying the error types earlier would expose us to https://bugs.php.net/63206 + set_error_handler([$handler, 'handleError'], $handler->thrownErrors | $handler->loggedErrors); + $handler->isRoot = true; + } + + if ($handlerIsNew && \is_array($prev) && $prev[0] instanceof self) { + $handler = $prev[0]; + $replace = false; + } + if (!$replace && $prev) { + restore_error_handler(); + $handlerIsRegistered = \is_array($prev) && $handler === $prev[0]; + } else { + $handlerIsRegistered = true; + } + if (\is_array($prev = set_exception_handler([$handler, 'handleException'])) && $prev[0] instanceof self) { + restore_exception_handler(); + if (!$handlerIsRegistered) { + $handler = $prev[0]; + } elseif ($handler !== $prev[0] && $replace) { + set_exception_handler([$handler, 'handleException']); + $p = $prev[0]->setExceptionHandler(null); + $handler->setExceptionHandler($p); + $prev[0]->setExceptionHandler($p); + } + } else { + $handler->setExceptionHandler($prev ?? [$handler, 'renderException']); + } + + $handler->throwAt(\E_ALL & $handler->thrownErrors, true); + + return $handler; + } + + /** + * Calls a function and turns any PHP error into \ErrorException. + * + * @return mixed What $function(...$arguments) returns + * + * @throws \ErrorException When $function(...$arguments) triggers a PHP error + */ + public static function call(callable $function, ...$arguments) + { + set_error_handler(static function (int $type, string $message, string $file, int $line) { + if (__FILE__ === $file) { + $trace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 3); + $file = $trace[2]['file'] ?? $file; + $line = $trace[2]['line'] ?? $line; + } + + throw new \ErrorException($message, 0, $type, $file, $line); + }); + + try { + return $function(...$arguments); + } finally { + restore_error_handler(); + } + } + + public function __construct(BufferingLogger $bootstrappingLogger = null, bool $debug = false) + { + if ($bootstrappingLogger) { + $this->bootstrappingLogger = $bootstrappingLogger; + $this->setDefaultLogger($bootstrappingLogger); + } + $traceReflector = new \ReflectionProperty(\Exception::class, 'trace'); + $traceReflector->setAccessible(true); + $this->configureException = \Closure::bind(static function ($e, $trace, $file = null, $line = null) use ($traceReflector) { + $traceReflector->setValue($e, $trace); + $e->file = $file ?? $e->file; + $e->line = $line ?? $e->line; + }, null, new class() extends \Exception { + }); + $this->debug = $debug; + } + + /** + * Sets a logger to non assigned errors levels. + * + * @param LoggerInterface $logger A PSR-3 logger to put as default for the given levels + * @param array|int|null $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants + * @param bool $replace Whether to replace or not any existing logger + */ + public function setDefaultLogger(LoggerInterface $logger, $levels = \E_ALL, bool $replace = false): void + { + $loggers = []; + + if (\is_array($levels)) { + foreach ($levels as $type => $logLevel) { + if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) { + $loggers[$type] = [$logger, $logLevel]; + } + } + } else { + if (null === $levels) { + $levels = \E_ALL; + } + foreach ($this->loggers as $type => $log) { + if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) { + $log[0] = $logger; + $loggers[$type] = $log; + } + } + } + + $this->setLoggers($loggers); + } + + /** + * Sets a logger for each error level. + * + * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map + * + * @return array The previous map + * + * @throws \InvalidArgumentException + */ + public function setLoggers(array $loggers): array + { + $prevLogged = $this->loggedErrors; + $prev = $this->loggers; + $flush = []; + + foreach ($loggers as $type => $log) { + if (!isset($prev[$type])) { + throw new \InvalidArgumentException('Unknown error type: '.$type); + } + if (!\is_array($log)) { + $log = [$log]; + } elseif (!\array_key_exists(0, $log)) { + throw new \InvalidArgumentException('No logger provided.'); + } + if (null === $log[0]) { + $this->loggedErrors &= ~$type; + } elseif ($log[0] instanceof LoggerInterface) { + $this->loggedErrors |= $type; + } else { + throw new \InvalidArgumentException('Invalid logger provided.'); + } + $this->loggers[$type] = $log + $prev[$type]; + + if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) { + $flush[$type] = $type; + } + } + $this->reRegister($prevLogged | $this->thrownErrors); + + if ($flush) { + foreach ($this->bootstrappingLogger->cleanLogs() as $log) { + $type = ThrowableUtils::getSeverity($log[2]['exception']); + if (!isset($flush[$type])) { + $this->bootstrappingLogger->log($log[0], $log[1], $log[2]); + } elseif ($this->loggers[$type][0]) { + $this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]); + } + } + } + + return $prev; + } + + /** + * Sets a user exception handler. + * + * @param callable(\Throwable $e)|null $handler + * + * @return callable|null The previous exception handler + */ + public function setExceptionHandler(?callable $handler): ?callable + { + $prev = $this->exceptionHandler; + $this->exceptionHandler = $handler; + + return $prev; + } + + /** + * Sets the PHP error levels that throw an exception when a PHP error occurs. + * + * @param int $levels A bit field of E_* constants for thrown errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function throwAt(int $levels, bool $replace = false): int + { + $prev = $this->thrownErrors; + $this->thrownErrors = ($levels | \E_RECOVERABLE_ERROR | \E_USER_ERROR) & ~\E_USER_DEPRECATED & ~\E_DEPRECATED; + if (!$replace) { + $this->thrownErrors |= $prev; + } + $this->reRegister($prev | $this->loggedErrors); + + return $prev; + } + + /** + * Sets the PHP error levels for which local variables are preserved. + * + * @param int $levels A bit field of E_* constants for scoped errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function scopeAt(int $levels, bool $replace = false): int + { + $prev = $this->scopedErrors; + $this->scopedErrors = $levels; + if (!$replace) { + $this->scopedErrors |= $prev; + } + + return $prev; + } + + /** + * Sets the PHP error levels for which the stack trace is preserved. + * + * @param int $levels A bit field of E_* constants for traced errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function traceAt(int $levels, bool $replace = false): int + { + $prev = $this->tracedErrors; + $this->tracedErrors = $levels; + if (!$replace) { + $this->tracedErrors |= $prev; + } + + return $prev; + } + + /** + * Sets the error levels where the @-operator is ignored. + * + * @param int $levels A bit field of E_* constants for screamed errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function screamAt(int $levels, bool $replace = false): int + { + $prev = $this->screamedErrors; + $this->screamedErrors = $levels; + if (!$replace) { + $this->screamedErrors |= $prev; + } + + return $prev; + } + + /** + * Re-registers as a PHP error handler if levels changed. + */ + private function reRegister(int $prev): void + { + if ($prev !== ($this->thrownErrors | $this->loggedErrors)) { + $handler = set_error_handler('is_int'); + $handler = \is_array($handler) ? $handler[0] : null; + restore_error_handler(); + if ($handler === $this) { + restore_error_handler(); + if ($this->isRoot) { + set_error_handler([$this, 'handleError'], $this->thrownErrors | $this->loggedErrors); + } else { + set_error_handler([$this, 'handleError']); + } + } + } + } + + /** + * Handles errors by filtering then logging them according to the configured bit fields. + * + * @return bool Returns false when no handling happens so that the PHP engine can handle the error itself + * + * @throws \ErrorException When $this->thrownErrors requests so + * + * @internal + */ + public function handleError(int $type, string $message, string $file, int $line): bool + { + if (\PHP_VERSION_ID >= 70300 && \E_WARNING === $type && '"' === $message[0] && false !== strpos($message, '" targeting switch is equivalent to "break')) { + $type = \E_DEPRECATED; + } + + // Level is the current error reporting level to manage silent error. + $level = error_reporting(); + $silenced = 0 === ($level & $type); + // Strong errors are not authorized to be silenced. + $level |= \E_RECOVERABLE_ERROR | \E_USER_ERROR | \E_DEPRECATED | \E_USER_DEPRECATED; + $log = $this->loggedErrors & $type; + $throw = $this->thrownErrors & $type & $level; + $type &= $level | $this->screamedErrors; + + // Never throw on warnings triggered by assert() + if (\E_WARNING === $type && 'a' === $message[0] && 0 === strncmp($message, 'assert(): ', 10)) { + $throw = 0; + } + + if (!$type || (!$log && !$throw)) { + return false; + } + + $logMessage = $this->levels[$type].': '.$message; + + if (null !== self::$toStringException) { + $errorAsException = self::$toStringException; + self::$toStringException = null; + } elseif (!$throw && !($type & $level)) { + if (!isset(self::$silencedErrorCache[$id = $file.':'.$line])) { + $lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 5), $type, $file, $line, false) : []; + $errorAsException = new SilencedErrorContext($type, $file, $line, isset($lightTrace[1]) ? [$lightTrace[0]] : $lightTrace); + } elseif (isset(self::$silencedErrorCache[$id][$message])) { + $lightTrace = null; + $errorAsException = self::$silencedErrorCache[$id][$message]; + ++$errorAsException->count; + } else { + $lightTrace = []; + $errorAsException = null; + } + + if (100 < ++self::$silencedErrorCount) { + self::$silencedErrorCache = $lightTrace = []; + self::$silencedErrorCount = 1; + } + if ($errorAsException) { + self::$silencedErrorCache[$id][$message] = $errorAsException; + } + if (null === $lightTrace) { + return true; + } + } else { + if (false !== strpos($message, '@anonymous')) { + $backtrace = debug_backtrace(false, 5); + + for ($i = 1; isset($backtrace[$i]); ++$i) { + if (isset($backtrace[$i]['function'], $backtrace[$i]['args'][0]) + && ('trigger_error' === $backtrace[$i]['function'] || 'user_error' === $backtrace[$i]['function']) + ) { + if ($backtrace[$i]['args'][0] !== $message) { + $message = $this->parseAnonymousClass($backtrace[$i]['args'][0]); + $logMessage = $this->levels[$type].': '.$message; + } + + break; + } + } + } + + $errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line); + + if ($throw || $this->tracedErrors & $type) { + $backtrace = $errorAsException->getTrace(); + $lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw); + ($this->configureException)($errorAsException, $lightTrace, $file, $line); + } else { + ($this->configureException)($errorAsException, []); + $backtrace = []; + } + } + + if ($throw) { + if (\PHP_VERSION_ID < 70400 && \E_USER_ERROR & $type) { + for ($i = 1; isset($backtrace[$i]); ++$i) { + if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function']) + && '__toString' === $backtrace[$i]['function'] + && '->' === $backtrace[$i]['type'] + && !isset($backtrace[$i - 1]['class']) + && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function']) + ) { + // Here, we know trigger_error() has been called from __toString(). + // PHP triggers a fatal error when throwing from __toString(). + // A small convention allows working around the limitation: + // given a caught $e exception in __toString(), quitting the method with + // `return trigger_error($e, E_USER_ERROR);` allows this error handler + // to make $e get through the __toString() barrier. + + $context = 4 < \func_num_args() ? (func_get_arg(4) ?: []) : []; + + foreach ($context as $e) { + if ($e instanceof \Throwable && $e->__toString() === $message) { + self::$toStringException = $e; + + return true; + } + } + + // Display the original error message instead of the default one. + $this->handleException($errorAsException); + + // Stop the process by giving back the error to the native handler. + return false; + } + } + } + + throw $errorAsException; + } + + if ($this->isRecursive) { + $log = 0; + } else { + if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404)) { + $currentErrorHandler = set_error_handler('is_int'); + restore_error_handler(); + } + + try { + $this->isRecursive = true; + $level = ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG; + $this->loggers[$type][0]->log($level, $logMessage, $errorAsException ? ['exception' => $errorAsException] : []); + } finally { + $this->isRecursive = false; + + if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404)) { + set_error_handler($currentErrorHandler); + } + } + } + + return !$silenced && $type && $log; + } + + /** + * Handles an exception by logging then forwarding it to another handler. + * + * @internal + */ + public function handleException(\Throwable $exception) + { + $handlerException = null; + + if (!$exception instanceof FatalError) { + self::$exitCode = 255; + + $type = ThrowableUtils::getSeverity($exception); + } else { + $type = $exception->getError()['type']; + } + + if ($this->loggedErrors & $type) { + if (false !== strpos($message = $exception->getMessage(), "@anonymous\0")) { + $message = $this->parseAnonymousClass($message); + } + + if ($exception instanceof FatalError) { + $message = 'Fatal '.$message; + } elseif ($exception instanceof \Error) { + $message = 'Uncaught Error: '.$message; + } elseif ($exception instanceof \ErrorException) { + $message = 'Uncaught '.$message; + } else { + $message = 'Uncaught Exception: '.$message; + } + + try { + $this->loggers[$type][0]->log($this->loggers[$type][1], $message, ['exception' => $exception]); + } catch (\Throwable $handlerException) { + } + } + + if (!$exception instanceof OutOfMemoryError) { + foreach ($this->getErrorEnhancers() as $errorEnhancer) { + if ($e = $errorEnhancer->enhance($exception)) { + $exception = $e; + break; + } + } + } + + $exceptionHandler = $this->exceptionHandler; + $this->exceptionHandler = [$this, 'renderException']; + + if (null === $exceptionHandler || $exceptionHandler === $this->exceptionHandler) { + $this->exceptionHandler = null; + } + + try { + if (null !== $exceptionHandler) { + return $exceptionHandler($exception); + } + $handlerException = $handlerException ?: $exception; + } catch (\Throwable $handlerException) { + } + if ($exception === $handlerException && null === $this->exceptionHandler) { + self::$reservedMemory = null; // Disable the fatal error handler + throw $exception; // Give back $exception to the native handler + } + + $loggedErrors = $this->loggedErrors; + if ($exception === $handlerException) { + $this->loggedErrors &= ~$type; + } + + try { + $this->handleException($handlerException); + } finally { + $this->loggedErrors = $loggedErrors; + } + } + + /** + * Shutdown registered function for handling PHP fatal errors. + * + * @param array|null $error An array as returned by error_get_last() + * + * @internal + */ + public static function handleFatalError(array $error = null): void + { + if (null === self::$reservedMemory) { + return; + } + + $handler = self::$reservedMemory = null; + $handlers = []; + $previousHandler = null; + $sameHandlerLimit = 10; + + while (!\is_array($handler) || !$handler[0] instanceof self) { + $handler = set_exception_handler('is_int'); + restore_exception_handler(); + + if (!$handler) { + break; + } + restore_exception_handler(); + + if ($handler !== $previousHandler) { + array_unshift($handlers, $handler); + $previousHandler = $handler; + } elseif (0 === --$sameHandlerLimit) { + $handler = null; + break; + } + } + foreach ($handlers as $h) { + set_exception_handler($h); + } + if (!$handler) { + return; + } + if ($handler !== $h) { + $handler[0]->setExceptionHandler($h); + } + $handler = $handler[0]; + $handlers = []; + + if ($exit = null === $error) { + $error = error_get_last(); + } + + if ($error && $error['type'] &= \E_PARSE | \E_ERROR | \E_CORE_ERROR | \E_COMPILE_ERROR) { + // Let's not throw anymore but keep logging + $handler->throwAt(0, true); + $trace = $error['backtrace'] ?? null; + + if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) { + $fatalError = new OutOfMemoryError($handler->levels[$error['type']].': '.$error['message'], 0, $error, 2, false, $trace); + } else { + $fatalError = new FatalError($handler->levels[$error['type']].': '.$error['message'], 0, $error, 2, true, $trace); + } + } else { + $fatalError = null; + } + + try { + if (null !== $fatalError) { + self::$exitCode = 255; + $handler->handleException($fatalError); + } + } catch (FatalError $e) { + // Ignore this re-throw + } + + if ($exit && self::$exitCode) { + $exitCode = self::$exitCode; + register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); }); + } + } + + /** + * Renders the given exception. + * + * As this method is mainly called during boot where nothing is yet available, + * the output is always either HTML or CLI depending where PHP runs. + */ + private function renderException(\Throwable $exception): void + { + $renderer = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? new CliErrorRenderer() : new HtmlErrorRenderer($this->debug); + + $exception = $renderer->render($exception); + + if (!headers_sent()) { + http_response_code($exception->getStatusCode()); + + foreach ($exception->getHeaders() as $name => $value) { + header($name.': '.$value, false); + } + } + + echo $exception->getAsString(); + } + + /** + * Override this method if you want to define more error enhancers. + * + * @return ErrorEnhancerInterface[] + */ + protected function getErrorEnhancers(): iterable + { + return [ + new UndefinedFunctionErrorEnhancer(), + new UndefinedMethodErrorEnhancer(), + new ClassNotFoundErrorEnhancer(), + ]; + } + + /** + * Cleans the trace by removing function arguments and the frames added by the error handler and DebugClassLoader. + */ + private function cleanTrace(array $backtrace, int $type, string &$file, int &$line, bool $throw): array + { + $lightTrace = $backtrace; + + for ($i = 0; isset($backtrace[$i]); ++$i) { + if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { + $lightTrace = \array_slice($lightTrace, 1 + $i); + break; + } + } + if (\E_USER_DEPRECATED === $type) { + for ($i = 0; isset($lightTrace[$i]); ++$i) { + if (!isset($lightTrace[$i]['file'], $lightTrace[$i]['line'], $lightTrace[$i]['function'])) { + continue; + } + if (!isset($lightTrace[$i]['class']) && 'trigger_deprecation' === $lightTrace[$i]['function']) { + $file = $lightTrace[$i]['file']; + $line = $lightTrace[$i]['line']; + $lightTrace = \array_slice($lightTrace, 1 + $i); + break; + } + } + } + if (class_exists(DebugClassLoader::class, false)) { + for ($i = \count($lightTrace) - 2; 0 < $i; --$i) { + if (DebugClassLoader::class === ($lightTrace[$i]['class'] ?? null)) { + array_splice($lightTrace, --$i, 2); + } + } + } + if (!($throw || $this->scopedErrors & $type)) { + for ($i = 0; isset($lightTrace[$i]); ++$i) { + unset($lightTrace[$i]['args'], $lightTrace[$i]['object']); + } + } + + return $lightTrace; + } + + /** + * Parse the error message by removing the anonymous class notation + * and using the parent class instead if possible. + */ + private function parseAnonymousClass(string $message): string + { + return preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', static function ($m) { + return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; + }, $message); + } +} diff --git a/lib/symfony/error-handler/ErrorRenderer/CliErrorRenderer.php b/lib/symfony/error-handler/ErrorRenderer/CliErrorRenderer.php new file mode 100644 index 0000000000..5c0f6a7dce --- /dev/null +++ b/lib/symfony/error-handler/ErrorRenderer/CliErrorRenderer.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\ErrorRenderer; + +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +// Help opcache.preload discover always-needed symbols +class_exists(CliDumper::class); + +/** + * @author Nicolas Grekas + */ +class CliErrorRenderer implements ErrorRendererInterface +{ + /** + * {@inheritdoc} + */ + public function render(\Throwable $exception): FlattenException + { + $cloner = new VarCloner(); + $dumper = new class() extends CliDumper { + protected function supportsColors(): bool + { + $outputStream = $this->outputStream; + $this->outputStream = fopen('php://stdout', 'w'); + + try { + return parent::supportsColors(); + } finally { + $this->outputStream = $outputStream; + } + } + }; + + return FlattenException::createFromThrowable($exception) + ->setAsString($dumper->dump($cloner->cloneVar($exception), true)); + } +} diff --git a/lib/symfony/error-handler/ErrorRenderer/ErrorRendererInterface.php b/lib/symfony/error-handler/ErrorRenderer/ErrorRendererInterface.php new file mode 100644 index 0000000000..aba196603f --- /dev/null +++ b/lib/symfony/error-handler/ErrorRenderer/ErrorRendererInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\ErrorRenderer; + +use Symfony\Component\ErrorHandler\Exception\FlattenException; + +/** + * Formats an exception to be used as response content. + * + * @author Yonel Ceruto + */ +interface ErrorRendererInterface +{ + /** + * Renders a Throwable as a FlattenException. + */ + public function render(\Throwable $exception): FlattenException; +} diff --git a/lib/symfony/error-handler/ErrorRenderer/HtmlErrorRenderer.php b/lib/symfony/error-handler/ErrorRenderer/HtmlErrorRenderer.php new file mode 100644 index 0000000000..b8a9aeda61 --- /dev/null +++ b/lib/symfony/error-handler/ErrorRenderer/HtmlErrorRenderer.php @@ -0,0 +1,367 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\ErrorRenderer; + +use Psr\Log\LoggerInterface; +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; + +/** + * @author Yonel Ceruto + */ +class HtmlErrorRenderer implements ErrorRendererInterface +{ + private const GHOST_ADDONS = [ + '02-14' => self::GHOST_HEART, + '02-29' => self::GHOST_PLUS, + '10-18' => self::GHOST_GIFT, + ]; + + private const GHOST_GIFT = 'M124.00534057617188,5.3606138080358505 C124.40059661865234,4.644828304648399 125.1237564086914,3.712414965033531 123.88127899169922,3.487462028861046 C123.53517150878906,3.3097832053899765 123.18894958496094,2.9953975528478622 122.8432846069336,3.345616325736046 C122.07421112060547,3.649444565176964 121.40750122070312,4.074306473135948 122.2164306640625,4.869479164481163 C122.57514953613281,5.3830065578222275 122.90142822265625,6.503447040915489 123.3077621459961,6.626829609274864 C123.55027770996094,6.210384353995323 123.7774658203125,5.785196766257286 124.00534057617188,5.3606138080358505 zM122.30630493164062,7.336987480521202 C121.60028076171875,6.076864704489708 121.03211975097656,4.72498320043087 120.16796875,3.562500938773155 C119.11695098876953,2.44033907353878 117.04605865478516,2.940566048026085 116.57544708251953,4.387995228171349 C115.95028686523438,5.819030746817589 117.2991714477539,7.527640804648399 118.826171875,7.348545059561729 C119.98493194580078,7.367936596274376 121.15027618408203,7.420116886496544 122.30630493164062,7.336987480521202 zM128.1732177734375,7.379541382193565 C129.67486572265625,7.17823551595211 130.53842163085938,5.287807449698448 129.68344116210938,4.032590612769127 C128.92578125,2.693056806921959 126.74605560302734,2.6463639587163925 125.98509216308594,4.007616028189659 C125.32617950439453,5.108129009604454 124.75428009033203,6.258124336600304 124.14962768554688,7.388818249106407 C125.48638916015625,7.465229496359825 126.8357162475586,7.447416767477989 128.1732177734375,7.379541382193565 zM130.6601104736328,8.991325363516808 C131.17202758789062,8.540884003043175 133.1543731689453,8.009847149252892 131.65304565429688,7.582054600119591 C131.2811279296875,7.476506695151329 130.84751892089844,6.99234913289547 130.5132598876953,7.124847874045372 C129.78744506835938,8.02728746831417 128.67140197753906,8.55669592320919 127.50616455078125,8.501235947012901 C127.27806091308594,8.576229080557823 126.11459350585938,8.38720129430294 126.428955078125,8.601900085806847 C127.25099182128906,9.070617660880089 128.0523223876953,9.579657539725304 128.902587890625,9.995706543326378 C129.49813842773438,9.678531631827354 130.0761260986328,9.329126343131065 130.6601104736328,8.991325363516808 zM118.96446990966797,9.246344551444054 C119.4022445678711,8.991325363516808 119.84001922607422,8.736305221915245 120.27779388427734,8.481284126639366 C118.93965911865234,8.414779648184776 117.40827941894531,8.607666000723839 116.39698791503906,7.531384453177452 C116.11186981201172,7.212117180228233 115.83845520019531,6.846597656607628 115.44329071044922,7.248530372977257 C114.96995544433594,7.574637398123741 113.5140609741211,7.908811077475548 114.63501739501953,8.306883797049522 C115.61112976074219,8.883499130606651 116.58037567138672,9.474181160330772 117.58061218261719,10.008124336600304 C118.05723571777344,9.784612640738487 118.50651550292969,9.5052699893713 118.96446990966797,9.246344551444054 zM125.38018035888672,12.091858848929405 C125.9474868774414,11.636047348380089 127.32159423828125,11.201767906546593 127.36749267578125,10.712632164359093 C126.08487701416016,9.974547371268272 124.83960723876953,9.152772888541222 123.49772644042969,8.528907760977745 C123.03594207763672,8.353693947196007 122.66152954101562,8.623294815421104 122.28982543945312,8.857431396842003 C121.19065856933594,9.51122473180294 120.06505584716797,10.12446115911007 119.00167083740234,10.835315689444542 C120.39238739013672,11.69529627263546 121.79983520507812,12.529837593436241 123.22095489501953,13.338589653372765 C123.94580841064453,12.932025894522667 124.66128540039062,12.508862480521202 125.38018035888672,12.091858848929405 zM131.07164001464844,13.514615997672081 C131.66018676757812,13.143282875418663 132.2487335205078,12.771927818655968 132.8372802734375,12.400571808218956 C132.8324737548828,11.156818374991417 132.8523406982422,9.912529930472374 132.81829833984375,8.669195160269737 C131.63046264648438,9.332009300589561 130.45948791503906,10.027913078665733 129.30828857421875,10.752535805106163 C129.182373046875,12.035354599356651 129.24623107910156,13.33940313756466 129.27359008789062,14.628684982657433 C129.88104248046875,14.27079389989376 130.4737548828125,13.888019546866417 131.07164001464844,13.514640793204308 zM117.26847839355469,12.731024727225304 C117.32825469970703,11.67083452641964 117.45709991455078,10.46224020421505 116.17853546142578,10.148179039359093 C115.37110900878906,9.77159021794796 114.25194549560547,8.806716904044151 113.62991333007812,8.81639002263546 C113.61052703857422,10.0110072940588 113.62078857421875,11.20585821568966 113.61869049072266,12.400571808218956 C114.81139373779297,13.144886955618858 115.98292541503906,13.925040230154991 117.20137023925781,14.626662239432335 C117.31951141357422,14.010867103934288 117.24227905273438,13.35805033147335 117.26847839355469,12.731024727225304 zM125.80937957763672,16.836034759879112 C126.51483917236328,16.390663132071495 127.22030639648438,15.945291504263878 127.92576599121094,15.49991987645626 C127.92250061035156,14.215868934988976 127.97560119628906,12.929980263113976 127.91757202148438,11.647302612662315 C127.14225769042969,11.869626984000206 126.25550079345703,12.556857094168663 125.43866729736328,12.983742699027061 C124.82704162597656,13.342005714774132 124.21542358398438,13.700271591544151 123.60379028320312,14.05853746831417 C123.61585235595703,15.429577812552452 123.57081604003906,16.803131088614464 123.64839172363281,18.172149643301964 C124.37957000732422,17.744937881827354 125.09130859375,17.284801468253136 125.80937957763672,16.836034759879112 zM122.8521499633789,16.115344032645226 C122.8521499633789,15.429741844534874 122.8521499633789,14.744139656424522 122.8521499633789,14.05853746831417 C121.43595123291016,13.230924591422081 120.02428436279297,12.395455345511436 118.60256958007812,11.577354416251183 C118.52394104003906,12.888403877615929 118.56887817382812,14.204405769705772 118.55702209472656,15.517732605338097 C119.97289276123047,16.4041957706213 121.37410736083984,17.314891800284386 122.80789947509766,18.172149643301964 C122.86368560791016,17.488990768790245 122.84332275390625,16.800363525748253 122.8521499633789,16.115344032645226 zM131.10684204101562,18.871450409293175 C131.68399047851562,18.48711584508419 132.2611541748047,18.10278509557247 132.8383026123047,17.718475326895714 C132.81423950195312,16.499977096915245 132.89776611328125,15.264989838004112 132.77627563476562,14.05993078649044 C131.5760040283203,14.744719490408897 130.41763305664062,15.524359688162804 129.23875427246094,16.255397781729698 C129.26707458496094,17.516149505972862 129.18060302734375,18.791316971182823 129.3108367919922,20.041303619742393 C129.91973876953125,19.667551025748253 130.51010131835938,19.264152511954308 131.10684204101562,18.871450409293175 zM117.2557373046875,18.188333496451378 C117.25104522705078,17.549470886588097 117.24633026123047,16.91058538854122 117.24163055419922,16.271720871329308 C116.04924774169922,15.525708183646202 114.87187957763672,14.75476549565792 113.66158294677734,14.038097366690636 C113.5858383178711,15.262084946036339 113.62901306152344,16.49083898961544 113.61761474609375,17.717010483145714 C114.82051086425781,18.513254150748253 116.00987243652344,19.330610260367393 117.22888946533203,20.101993545889854 C117.27559661865234,19.466014847159386 117.25241088867188,18.825733169913292 117.2557373046875,18.188333496451378 zM125.8398666381836,22.38675306737423 C126.54049682617188,21.921453461050987 127.24110412597656,21.456151947379112 127.94172668457031,20.99083136022091 C127.94009399414062,19.693386062979698 127.96646118164062,18.395381912589073 127.93160247802734,17.098379120230675 C126.50540924072266,17.97775076329708 125.08877563476562,18.873308166861534 123.68258666992188,19.78428266942501 C123.52366638183594,21.03710363805294 123.626708984375,22.32878302037716 123.62647247314453,23.595300659537315 C124.06291198730469,23.86113165318966 125.1788101196289,22.68297766149044 125.8398666381836,22.38675306737423 zM122.8521499633789,21.83134649693966 C122.76741790771484,20.936696991324425 123.21651458740234,19.67745779454708 122.0794677734375,19.330633148550987 C120.93280029296875,18.604360565543175 119.7907485961914,17.870157226920128 118.62899780273438,17.16818617284298 C118.45966339111328,18.396427139639854 118.63676452636719,19.675991043448448 118.50668334960938,20.919256195425987 C119.89984130859375,21.92635916173458 121.32942199707031,22.88914106786251 122.78502655029297,23.803510650992393 C122.90177917480469,23.1627406924963 122.82917022705078,22.48402212560177 122.8521499633789,21.83134649693966 zM117.9798355102539,21.59483526647091 C116.28416442871094,20.46288488805294 114.58848571777344,19.330957397818565 112.892822265625,18.199007019400597 C112.89473724365234,14.705654129385948 112.84647369384766,11.211485847830772 112.90847778320312,7.718807205557823 C113.7575912475586,7.194885239005089 114.66117858886719,6.765397056937218 115.5350341796875,6.284702762961388 C114.97061157226562,4.668964847922325 115.78496551513672,2.7054970115423203 117.42159271240234,2.1007001250982285 C118.79354095458984,1.537783369421959 120.44731903076172,2.0457767099142075 121.32200622558594,3.23083733022213 C121.95732116699219,2.9050118774175644 122.59264373779297,2.5791852325201035 123.22796630859375,2.253336176276207 C123.86669921875,2.5821153968572617 124.50543975830078,2.9108948558568954 125.1441650390625,3.23967407643795 C126.05941009521484,2.154020771384239 127.62747192382812,1.5344576686620712 128.986328125,2.1429056972265244 C130.61741638183594,2.716217741370201 131.50650024414062,4.675290569663048 130.9215545654297,6.2884936183691025 C131.8018341064453,6.78548763692379 132.7589111328125,7.1738648265600204 133.5660400390625,7.780336365103722 C133.60182189941406,11.252970680594444 133.56637573242188,14.726140961050987 133.5631103515625,18.199007019400597 C130.18914794921875,20.431867584586143 126.86984252929688,22.74994657933712 123.44108581542969,24.897907242178917 C122.44406127929688,24.897628769278526 121.5834732055664,23.815067276358604 120.65831756591797,23.37616156041622 C119.76387023925781,22.784828171133995 118.87168884277344,22.19007681310177 117.9798355102539,21.59483526647091 z'; + private const GHOST_HEART = 'M125.91386369681868,8.305165958366445 C128.95033202169043,-0.40540639102854037 140.8469835342744,8.305165958366445 125.91386369681868,19.504526138305664 C110.98208663272044,8.305165958366445 122.87795231771452,-0.40540639102854037 125.91386369681868,8.305165958366445 z'; + private const GHOST_PLUS = 'M111.36824226379395,8.969108581542969 L118.69175148010254,8.969108581542969 L118.69175148010254,1.6455793380737305 L126.20429420471191,1.6455793380737305 L126.20429420471191,8.969108581542969 L133.52781105041504,8.969108581542969 L133.52781105041504,16.481630325317383 L126.20429420471191,16.481630325317383 L126.20429420471191,23.805158615112305 L118.69175148010254,23.805158615112305 L118.69175148010254,16.481630325317383 L111.36824226379395,16.481630325317383 z'; + + private $debug; + private $charset; + private $fileLinkFormat; + private $projectDir; + private $outputBuffer; + private $logger; + + private static $template = 'views/error.html.php'; + + /** + * @param bool|callable $debug The debugging mode as a boolean or a callable that should return it + * @param string|FileLinkFormatter|null $fileLinkFormat + * @param bool|callable $outputBuffer The output buffer as a string or a callable that should return it + */ + public function __construct($debug = false, string $charset = null, $fileLinkFormat = null, string $projectDir = null, $outputBuffer = '', LoggerInterface $logger = null) + { + if (!\is_bool($debug) && !\is_callable($debug)) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a boolean or a callable, "%s" given.', __METHOD__, \gettype($debug))); + } + + if (!\is_string($outputBuffer) && !\is_callable($outputBuffer)) { + throw new \TypeError(sprintf('Argument 5 passed to "%s()" must be a string or a callable, "%s" given.', __METHOD__, \gettype($outputBuffer))); + } + + $this->debug = $debug; + $this->charset = $charset ?: (\ini_get('default_charset') ?: 'UTF-8'); + $this->fileLinkFormat = $fileLinkFormat ?: (\ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format')); + $this->projectDir = $projectDir; + $this->outputBuffer = $outputBuffer; + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function render(\Throwable $exception): FlattenException + { + $headers = ['Content-Type' => 'text/html; charset='.$this->charset]; + if (\is_bool($this->debug) ? $this->debug : ($this->debug)($exception)) { + $headers['X-Debug-Exception'] = rawurlencode($exception->getMessage()); + $headers['X-Debug-Exception-File'] = rawurlencode($exception->getFile()).':'.$exception->getLine(); + } + + $exception = FlattenException::createFromThrowable($exception, null, $headers); + + return $exception->setAsString($this->renderException($exception)); + } + + /** + * Gets the HTML content associated with the given exception. + */ + public function getBody(FlattenException $exception): string + { + return $this->renderException($exception, 'views/exception.html.php'); + } + + /** + * Gets the stylesheet associated with the given exception. + */ + public function getStylesheet(): string + { + if (!$this->debug) { + return $this->include('assets/css/error.css'); + } + + return $this->include('assets/css/exception.css'); + } + + public static function isDebug(RequestStack $requestStack, bool $debug): \Closure + { + return static function () use ($requestStack, $debug): bool { + if (!$request = $requestStack->getCurrentRequest()) { + return $debug; + } + + return $debug && $request->attributes->getBoolean('showException', true); + }; + } + + public static function getAndCleanOutputBuffer(RequestStack $requestStack): \Closure + { + return static function () use ($requestStack): string { + if (!$request = $requestStack->getCurrentRequest()) { + return ''; + } + + $startObLevel = $request->headers->get('X-Php-Ob-Level', -1); + + if (ob_get_level() <= $startObLevel) { + return ''; + } + + Response::closeOutputBuffers($startObLevel + 1, true); + + return ob_get_clean(); + }; + } + + private function renderException(FlattenException $exception, string $debugTemplate = 'views/exception_full.html.php'): string + { + $debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($exception); + $statusText = $this->escape($exception->getStatusText()); + $statusCode = $this->escape($exception->getStatusCode()); + + if (!$debug) { + return $this->include(self::$template, [ + 'statusText' => $statusText, + 'statusCode' => $statusCode, + ]); + } + + $exceptionMessage = $this->escape($exception->getMessage()); + + return $this->include($debugTemplate, [ + 'exception' => $exception, + 'exceptionMessage' => $exceptionMessage, + 'statusText' => $statusText, + 'statusCode' => $statusCode, + 'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null, + 'currentContent' => \is_string($this->outputBuffer) ? $this->outputBuffer : ($this->outputBuffer)(), + ]); + } + + /** + * Formats an array as a string. + */ + private function formatArgs(array $args): string + { + $result = []; + foreach ($args as $key => $item) { + if ('object' === $item[0]) { + $formattedValue = sprintf('object(%s)', $this->abbrClass($item[1])); + } elseif ('array' === $item[0]) { + $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); + } elseif ('null' === $item[0]) { + $formattedValue = 'null'; + } elseif ('boolean' === $item[0]) { + $formattedValue = ''.strtolower(var_export($item[1], true)).''; + } elseif ('resource' === $item[0]) { + $formattedValue = 'resource'; + } else { + $formattedValue = str_replace("\n", '', $this->escape(var_export($item[1], true))); + } + + $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escape($key), $formattedValue); + } + + return implode(', ', $result); + } + + private function formatArgsAsText(array $args) + { + return strip_tags($this->formatArgs($args)); + } + + private function escape(string $string): string + { + return htmlspecialchars($string, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset); + } + + private function abbrClass(string $class): string + { + $parts = explode('\\', $class); + $short = array_pop($parts); + + return sprintf('%s', $class, $short); + } + + private function getFileRelative(string $file): ?string + { + $file = str_replace('\\', '/', $file); + + if (null !== $this->projectDir && 0 === strpos($file, $this->projectDir)) { + return ltrim(substr($file, \strlen($this->projectDir)), '/'); + } + + return null; + } + + /** + * Returns the link for a given file/line pair. + * + * @return string|false + */ + private function getFileLink(string $file, int $line) + { + if ($fmt = $this->fileLinkFormat) { + return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line); + } + + return false; + } + + /** + * Formats a file path. + * + * @param string $file An absolute file path + * @param int $line The line number + * @param string $text Use this text for the link rather than the file path + */ + private function formatFile(string $file, int $line, string $text = null): string + { + $file = trim($file); + + if (null === $text) { + $text = $file; + if (null !== $rel = $this->getFileRelative($text)) { + $rel = explode('/', $rel, 2); + $text = sprintf('%s%s', $this->projectDir, $rel[0], '/'.($rel[1] ?? '')); + } + } + + if (0 < $line) { + $text .= ' at line '.$line; + } + + if (false !== $link = $this->getFileLink($file, $line)) { + return sprintf('%s', $this->escape($link), $text); + } + + return $text; + } + + /** + * Returns an excerpt of a code file around the given line number. + * + * @param string $file A file path + * @param int $line The selected line number + * @param int $srcContext The number of displayed lines around or -1 for the whole file + */ + private function fileExcerpt(string $file, int $line, int $srcContext = 3): string + { + if (is_file($file) && is_readable($file)) { + // highlight_file could throw warnings + // see https://bugs.php.net/25725 + $code = @highlight_file($file, true); + // remove main code/span tags + $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); + // split multiline spans + $code = preg_replace_callback('#]++)>((?:[^<]*+
                  )++[^<]*+)
                  #', function ($m) { + return "".str_replace('
                  ', "

                  ", $m[2]).''; + }, $code); + $content = explode('
                  ', $code); + + $lines = []; + if (0 > $srcContext) { + $srcContext = \count($content); + } + + for ($i = max($line - $srcContext, 1), $max = min($line + $srcContext, \count($content)); $i <= $max; ++$i) { + $lines[] = ''.$this->fixCodeMarkup($content[$i - 1]).''; + } + + return '
                    '.implode("\n", $lines).'
                  '; + } + + return ''; + } + + private function fixCodeMarkup(string $line) + { + // ending tag from previous line + $opening = strpos($line, ''); + if (false !== $closing && (false === $opening || $closing < $opening)) { + $line = substr_replace($line, '', $closing, 7); + } + + // missing tag at the end of line + $opening = strrpos($line, ''); + if (false !== $opening && (false === $closing || $closing < $opening)) { + $line .= ''; + } + + return trim($line); + } + + private function formatFileFromText(string $text) + { + return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) { + return 'in '.$this->formatFile($match[2], $match[3]); + }, $text); + } + + private function formatLogMessage(string $message, array $context) + { + if ($context && false !== strpos($message, '{')) { + $replacements = []; + foreach ($context as $key => $val) { + if (\is_scalar($val)) { + $replacements['{'.$key.'}'] = $val; + } + } + + if ($replacements) { + $message = strtr($message, $replacements); + } + } + + return $this->escape($message); + } + + private function addElementToGhost(): string + { + if (!isset(self::GHOST_ADDONS[date('m-d')])) { + return ''; + } + + return ''; + } + + private function include(string $name, array $context = []): string + { + extract($context, \EXTR_SKIP); + ob_start(); + + include is_file(\dirname(__DIR__).'/Resources/'.$name) ? \dirname(__DIR__).'/Resources/'.$name : $name; + + return trim(ob_get_clean()); + } + + /** + * Allows overriding the default non-debug template. + * + * @param string $template path to the custom template file to render + */ + public static function setTemplate(string $template): void + { + self::$template = $template; + } +} diff --git a/lib/symfony/error-handler/ErrorRenderer/SerializerErrorRenderer.php b/lib/symfony/error-handler/ErrorRenderer/SerializerErrorRenderer.php new file mode 100644 index 0000000000..cec8e4d413 --- /dev/null +++ b/lib/symfony/error-handler/ErrorRenderer/SerializerErrorRenderer.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 Symfony\Component\ErrorHandler\ErrorRenderer; + +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Serializer\Exception\NotEncodableValueException; +use Symfony\Component\Serializer\SerializerInterface; + +/** + * Formats an exception using Serializer for rendering. + * + * @author Nicolas Grekas + */ +class SerializerErrorRenderer implements ErrorRendererInterface +{ + private $serializer; + private $format; + private $fallbackErrorRenderer; + private $debug; + + /** + * @param string|callable(FlattenException) $format The format as a string or a callable that should return it + * formats not supported by Request::getMimeTypes() should be given as mime types + * @param bool|callable $debug The debugging mode as a boolean or a callable that should return it + */ + public function __construct(SerializerInterface $serializer, $format, ErrorRendererInterface $fallbackErrorRenderer = null, $debug = false) + { + if (!\is_string($format) && !\is_callable($format)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be a string or a callable, "%s" given.', __METHOD__, \gettype($format))); + } + + if (!\is_bool($debug) && !\is_callable($debug)) { + throw new \TypeError(sprintf('Argument 4 passed to "%s()" must be a boolean or a callable, "%s" given.', __METHOD__, \gettype($debug))); + } + + $this->serializer = $serializer; + $this->format = $format; + $this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer(); + $this->debug = $debug; + } + + /** + * {@inheritdoc} + */ + public function render(\Throwable $exception): FlattenException + { + $headers = []; + $debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($exception); + if ($debug) { + $headers['X-Debug-Exception'] = rawurlencode($exception->getMessage()); + $headers['X-Debug-Exception-File'] = rawurlencode($exception->getFile()).':'.$exception->getLine(); + } + + $flattenException = FlattenException::createFromThrowable($exception, null, $headers); + + try { + $format = \is_string($this->format) ? $this->format : ($this->format)($flattenException); + $headers = [ + 'Content-Type' => Request::getMimeTypes($format)[0] ?? $format, + 'Vary' => 'Accept', + ]; + + return $flattenException->setAsString($this->serializer->serialize($flattenException, $format, [ + 'exception' => $exception, + 'debug' => $debug, + ])) + ->setHeaders($flattenException->getHeaders() + $headers); + } catch (NotEncodableValueException $e) { + return $this->fallbackErrorRenderer->render($exception); + } + } + + public static function getPreferredFormat(RequestStack $requestStack): \Closure + { + return static function () use ($requestStack) { + if (!$request = $requestStack->getCurrentRequest()) { + throw new NotEncodableValueException(); + } + + return $request->getPreferredFormat(); + }; + } +} diff --git a/lib/symfony/error-handler/Exception/FlattenException.php b/lib/symfony/error-handler/Exception/FlattenException.php new file mode 100644 index 0000000000..262dae62bc --- /dev/null +++ b/lib/symfony/error-handler/Exception/FlattenException.php @@ -0,0 +1,427 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\Exception; + +use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; + +/** + * FlattenException wraps a PHP Error or Exception to be able to serialize it. + * + * Basically, this class removes all objects from the trace. + * + * @author Fabien Potencier + */ +class FlattenException +{ + /** @var string */ + private $message; + + /** @var int|string */ + private $code; + + /** @var self|null */ + private $previous; + + /** @var array */ + private $trace; + + /** @var string */ + private $traceAsString; + + /** @var string */ + private $class; + + /** @var int */ + private $statusCode; + + /** @var string */ + private $statusText; + + /** @var array */ + private $headers; + + /** @var string */ + private $file; + + /** @var int */ + private $line; + + /** @var string|null */ + private $asString; + + /** + * @return static + */ + public static function create(\Exception $exception, int $statusCode = null, array $headers = []): self + { + return static::createFromThrowable($exception, $statusCode, $headers); + } + + /** + * @return static + */ + public static function createFromThrowable(\Throwable $exception, int $statusCode = null, array $headers = []): self + { + $e = new static(); + $e->setMessage($exception->getMessage()); + $e->setCode($exception->getCode()); + + if ($exception instanceof HttpExceptionInterface) { + $statusCode = $exception->getStatusCode(); + $headers = array_merge($headers, $exception->getHeaders()); + } elseif ($exception instanceof RequestExceptionInterface) { + $statusCode = 400; + } + + if (null === $statusCode) { + $statusCode = 500; + } + + if (class_exists(Response::class) && isset(Response::$statusTexts[$statusCode])) { + $statusText = Response::$statusTexts[$statusCode]; + } else { + $statusText = 'Whoops, looks like something went wrong.'; + } + + $e->setStatusText($statusText); + $e->setStatusCode($statusCode); + $e->setHeaders($headers); + $e->setTraceFromThrowable($exception); + $e->setClass(\get_class($exception)); + $e->setFile($exception->getFile()); + $e->setLine($exception->getLine()); + + $previous = $exception->getPrevious(); + + if ($previous instanceof \Throwable) { + $e->setPrevious(static::createFromThrowable($previous)); + } + + return $e; + } + + public function toArray(): array + { + $exceptions = []; + foreach (array_merge([$this], $this->getAllPrevious()) as $exception) { + $exceptions[] = [ + 'message' => $exception->getMessage(), + 'class' => $exception->getClass(), + 'trace' => $exception->getTrace(), + ]; + } + + return $exceptions; + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + /** + * @return $this + */ + public function setStatusCode(int $code): self + { + $this->statusCode = $code; + + return $this; + } + + public function getHeaders(): array + { + return $this->headers; + } + + /** + * @return $this + */ + public function setHeaders(array $headers): self + { + $this->headers = $headers; + + return $this; + } + + public function getClass(): string + { + return $this->class; + } + + /** + * @return $this + */ + public function setClass(string $class): self + { + $this->class = false !== strpos($class, "@anonymous\0") ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : $class; + + return $this; + } + + public function getFile(): string + { + return $this->file; + } + + /** + * @return $this + */ + public function setFile(string $file): self + { + $this->file = $file; + + return $this; + } + + public function getLine(): int + { + return $this->line; + } + + /** + * @return $this + */ + public function setLine(int $line): self + { + $this->line = $line; + + return $this; + } + + public function getStatusText(): string + { + return $this->statusText; + } + + /** + * @return $this + */ + public function setStatusText(string $statusText): self + { + $this->statusText = $statusText; + + return $this; + } + + public function getMessage(): string + { + return $this->message; + } + + /** + * @return $this + */ + public function setMessage(string $message): self + { + if (false !== strpos($message, "@anonymous\0")) { + $message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) { + return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; + }, $message); + } + + $this->message = $message; + + return $this; + } + + /** + * @return int|string int most of the time (might be a string with PDOException) + */ + public function getCode() + { + return $this->code; + } + + /** + * @param int|string $code + * + * @return $this + */ + public function setCode($code): self + { + $this->code = $code; + + return $this; + } + + public function getPrevious(): ?self + { + return $this->previous; + } + + /** + * @return $this + */ + public function setPrevious(?self $previous): self + { + $this->previous = $previous; + + return $this; + } + + /** + * @return self[] + */ + public function getAllPrevious(): array + { + $exceptions = []; + $e = $this; + while ($e = $e->getPrevious()) { + $exceptions[] = $e; + } + + return $exceptions; + } + + public function getTrace(): array + { + return $this->trace; + } + + /** + * @return $this + */ + public function setTraceFromThrowable(\Throwable $throwable): self + { + $this->traceAsString = $throwable->getTraceAsString(); + + return $this->setTrace($throwable->getTrace(), $throwable->getFile(), $throwable->getLine()); + } + + /** + * @return $this + */ + public function setTrace(array $trace, ?string $file, ?int $line): self + { + $this->trace = []; + $this->trace[] = [ + 'namespace' => '', + 'short_class' => '', + 'class' => '', + 'type' => '', + 'function' => '', + 'file' => $file, + 'line' => $line, + 'args' => [], + ]; + foreach ($trace as $entry) { + $class = ''; + $namespace = ''; + if (isset($entry['class'])) { + $parts = explode('\\', $entry['class']); + $class = array_pop($parts); + $namespace = implode('\\', $parts); + } + + $this->trace[] = [ + 'namespace' => $namespace, + 'short_class' => $class, + 'class' => $entry['class'] ?? '', + 'type' => $entry['type'] ?? '', + 'function' => $entry['function'] ?? null, + 'file' => $entry['file'] ?? null, + 'line' => $entry['line'] ?? null, + 'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : [], + ]; + } + + return $this; + } + + private function flattenArgs(array $args, int $level = 0, int &$count = 0): array + { + $result = []; + foreach ($args as $key => $value) { + if (++$count > 1e4) { + return ['array', '*SKIPPED over 10000 entries*']; + } + if ($value instanceof \__PHP_Incomplete_Class) { + $result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)]; + } elseif (\is_object($value)) { + $result[$key] = ['object', \get_class($value)]; + } elseif (\is_array($value)) { + if ($level > 10) { + $result[$key] = ['array', '*DEEP NESTED ARRAY*']; + } else { + $result[$key] = ['array', $this->flattenArgs($value, $level + 1, $count)]; + } + } elseif (null === $value) { + $result[$key] = ['null', null]; + } elseif (\is_bool($value)) { + $result[$key] = ['boolean', $value]; + } elseif (\is_int($value)) { + $result[$key] = ['integer', $value]; + } elseif (\is_float($value)) { + $result[$key] = ['float', $value]; + } elseif (\is_resource($value)) { + $result[$key] = ['resource', get_resource_type($value)]; + } else { + $result[$key] = ['string', (string) $value]; + } + } + + return $result; + } + + private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value): string + { + $array = new \ArrayObject($value); + + return $array['__PHP_Incomplete_Class_Name']; + } + + public function getTraceAsString(): string + { + return $this->traceAsString; + } + + /** + * @return $this + */ + public function setAsString(?string $asString): self + { + $this->asString = $asString; + + return $this; + } + + public function getAsString(): string + { + if (null !== $this->asString) { + return $this->asString; + } + + $message = ''; + $next = false; + + foreach (array_reverse(array_merge([$this], $this->getAllPrevious())) as $exception) { + if ($next) { + $message .= 'Next '; + } else { + $next = true; + } + $message .= $exception->getClass(); + + if ('' != $exception->getMessage()) { + $message .= ': '.$exception->getMessage(); + } + + $message .= ' in '.$exception->getFile().':'.$exception->getLine(). + "\nStack trace:\n".$exception->getTraceAsString()."\n\n"; + } + + return rtrim($message); + } +} diff --git a/lib/symfony/debug/Exception/SilencedErrorContext.php b/lib/symfony/error-handler/Exception/SilencedErrorContext.php similarity index 75% rename from lib/symfony/debug/Exception/SilencedErrorContext.php rename to lib/symfony/error-handler/Exception/SilencedErrorContext.php index 2bacfd5c9b..18defc72ce 100644 --- a/lib/symfony/debug/Exception/SilencedErrorContext.php +++ b/lib/symfony/error-handler/Exception/SilencedErrorContext.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Debug\Exception; +namespace Symfony\Component\ErrorHandler\Exception; /** * Data Object that represents a Silenced Error. @@ -25,7 +25,7 @@ class SilencedErrorContext implements \JsonSerializable private $line; private $trace; - public function __construct($severity, $file, $line, array $trace = [], $count = 1) + public function __construct(int $severity, string $file, int $line, array $trace = [], int $count = 1) { $this->severity = $severity; $this->file = $file; @@ -34,27 +34,27 @@ class SilencedErrorContext implements \JsonSerializable $this->count = $count; } - public function getSeverity() + public function getSeverity(): int { return $this->severity; } - public function getFile() + public function getFile(): string { return $this->file; } - public function getLine() + public function getLine(): int { return $this->line; } - public function getTrace() + public function getTrace(): array { return $this->trace; } - public function jsonSerialize() + public function jsonSerialize(): array { return [ 'severity' => $this->severity, diff --git a/lib/symfony/error-handler/Internal/TentativeTypes.php b/lib/symfony/error-handler/Internal/TentativeTypes.php new file mode 100644 index 0000000000..2168a1c075 --- /dev/null +++ b/lib/symfony/error-handler/Internal/TentativeTypes.php @@ -0,0 +1,1642 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\Internal; + +/** + * This class has been generated by extract-tentative-return-types.php. + * + * @internal + */ +class TentativeTypes +{ + public const RETURN_TYPES = [ + 'CURLFile' => [ + 'getFilename' => 'string', + 'getMimeType' => 'string', + 'getPostFilename' => 'string', + 'setMimeType' => 'void', + 'setPostFilename' => 'void', + ], + 'DateTimeInterface' => [ + 'format' => 'string', + 'getTimezone' => 'DateTimeZone|false', + 'getOffset' => 'int', + 'getTimestamp' => 'int', + 'diff' => 'DateInterval', + '__wakeup' => 'void', + ], + 'DateTime' => [ + '__wakeup' => 'void', + '__set_state' => 'DateTime', + 'createFromImmutable' => 'static', + 'createFromFormat' => 'DateTime|false', + 'getLastErrors' => 'array|false', + 'format' => 'string', + 'modify' => 'DateTime|false', + 'add' => 'DateTime', + 'sub' => 'DateTime', + 'getTimezone' => 'DateTimeZone|false', + 'setTimezone' => 'DateTime', + 'getOffset' => 'int', + 'setTime' => 'DateTime', + 'setDate' => 'DateTime', + 'setISODate' => 'DateTime', + 'setTimestamp' => 'DateTime', + 'getTimestamp' => 'int', + 'diff' => 'DateInterval', + ], + 'DateTimeImmutable' => [ + '__wakeup' => 'void', + '__set_state' => 'DateTimeImmutable', + 'createFromFormat' => 'DateTimeImmutable|false', + 'getLastErrors' => 'array|false', + 'format' => 'string', + 'getTimezone' => 'DateTimeZone|false', + 'getOffset' => 'int', + 'getTimestamp' => 'int', + 'diff' => 'DateInterval', + 'modify' => 'DateTimeImmutable|false', + 'add' => 'DateTimeImmutable', + 'sub' => 'DateTimeImmutable', + 'setTimezone' => 'DateTimeImmutable', + 'setTime' => 'DateTimeImmutable', + 'setDate' => 'DateTimeImmutable', + 'setISODate' => 'DateTimeImmutable', + 'setTimestamp' => 'DateTimeImmutable', + 'createFromMutable' => 'static', + ], + 'DateTimeZone' => [ + 'getName' => 'string', + 'getOffset' => 'int', + 'getTransitions' => 'array|false', + 'getLocation' => 'array|false', + 'listAbbreviations' => 'array', + 'listIdentifiers' => 'array', + '__wakeup' => 'void', + '__set_state' => 'DateTimeZone', + ], + 'DateInterval' => [ + 'createFromDateString' => 'DateInterval|false', + 'format' => 'string', + '__wakeup' => 'void', + '__set_state' => 'DateInterval', + ], + 'DatePeriod' => [ + 'getStartDate' => 'DateTimeInterface', + 'getEndDate' => '?DateTimeInterface', + 'getDateInterval' => 'DateInterval', + 'getRecurrences' => '?int', + '__wakeup' => 'void', + '__set_state' => 'DatePeriod', + ], + 'DOMNode' => [ + 'C14N' => 'string|false', + 'C14NFile' => 'int|false', + 'getLineNo' => 'int', + 'getNodePath' => '?string', + 'hasAttributes' => 'bool', + 'hasChildNodes' => 'bool', + 'isDefaultNamespace' => 'bool', + 'isSameNode' => 'bool', + 'isSupported' => 'bool', + 'lookupNamespaceURI' => '?string', + 'lookupPrefix' => '?string', + 'normalize' => 'void', + ], + 'DOMImplementation' => [ + 'getFeature' => 'never', + 'hasFeature' => 'bool', + ], + 'DOMDocumentFragment' => [ + 'appendXML' => 'bool', + ], + 'DOMNodeList' => [ + 'count' => 'int', + ], + 'DOMCharacterData' => [ + 'appendData' => 'bool', + 'insertData' => 'bool', + 'deleteData' => 'bool', + 'replaceData' => 'bool', + ], + 'DOMAttr' => [ + 'isId' => 'bool', + ], + 'DOMElement' => [ + 'getAttribute' => 'string', + 'getAttributeNS' => 'string', + 'getElementsByTagName' => 'DOMNodeList', + 'getElementsByTagNameNS' => 'DOMNodeList', + 'hasAttribute' => 'bool', + 'hasAttributeNS' => 'bool', + 'removeAttribute' => 'bool', + 'removeAttributeNS' => 'void', + 'setAttributeNS' => 'void', + 'setIdAttribute' => 'void', + 'setIdAttributeNS' => 'void', + 'setIdAttributeNode' => 'void', + ], + 'DOMDocument' => [ + 'createComment' => 'DOMComment', + 'createDocumentFragment' => 'DOMDocumentFragment', + 'createTextNode' => 'DOMText', + 'getElementById' => '?DOMElement', + 'getElementsByTagName' => 'DOMNodeList', + 'getElementsByTagNameNS' => 'DOMNodeList', + 'normalizeDocument' => 'void', + 'registerNodeClass' => 'bool', + 'save' => 'int|false', + 'saveHTML' => 'string|false', + 'saveHTMLFile' => 'int|false', + 'saveXML' => 'string|false', + 'schemaValidate' => 'bool', + 'schemaValidateSource' => 'bool', + 'relaxNGValidate' => 'bool', + 'relaxNGValidateSource' => 'bool', + 'validate' => 'bool', + 'xinclude' => 'int|false', + ], + 'DOMText' => [ + 'isWhitespaceInElementContent' => 'bool', + 'isElementContentWhitespace' => 'bool', + ], + 'DOMNamedNodeMap' => [ + 'getNamedItem' => '?DOMNode', + 'getNamedItemNS' => '?DOMNode', + 'item' => '?DOMNode', + 'count' => 'int', + ], + 'DOMXPath' => [ + 'evaluate' => 'mixed', + 'query' => 'mixed', + 'registerNamespace' => 'bool', + 'registerPhpFunctions' => 'void', + ], + 'finfo' => [ + 'file' => 'string|false', + 'buffer' => 'string|false', + ], + 'IntlPartsIterator' => [ + 'getBreakIterator' => 'IntlBreakIterator', + 'getRuleStatus' => 'int', + ], + 'IntlBreakIterator' => [ + 'createCharacterInstance' => '?IntlBreakIterator', + 'createCodePointInstance' => 'IntlCodePointBreakIterator', + 'createLineInstance' => '?IntlBreakIterator', + 'createSentenceInstance' => '?IntlBreakIterator', + 'createTitleInstance' => '?IntlBreakIterator', + 'createWordInstance' => '?IntlBreakIterator', + 'current' => 'int', + 'first' => 'int', + 'following' => 'int', + 'getErrorCode' => 'int', + 'getErrorMessage' => 'string', + 'getLocale' => 'string|false', + 'getPartsIterator' => 'IntlPartsIterator', + 'getText' => '?string', + 'isBoundary' => 'bool', + 'last' => 'int', + 'next' => 'int', + 'preceding' => 'int', + 'previous' => 'int', + 'setText' => '?bool', + ], + 'IntlRuleBasedBreakIterator' => [ + 'getBinaryRules' => 'string|false', + 'getRules' => 'string|false', + 'getRuleStatus' => 'int', + 'getRuleStatusVec' => 'array|false', + ], + 'IntlCodePointBreakIterator' => [ + 'getLastCodePoint' => 'int', + ], + 'IntlCalendar' => [ + 'createInstance' => '?IntlCalendar', + 'equals' => 'bool', + 'fieldDifference' => 'int|false', + 'add' => 'bool', + 'after' => 'bool', + 'before' => 'bool', + 'fromDateTime' => '?IntlCalendar', + 'get' => 'int|false', + 'getActualMaximum' => 'int|false', + 'getActualMinimum' => 'int|false', + 'getAvailableLocales' => 'array', + 'getDayOfWeekType' => 'int|false', + 'getErrorCode' => 'int|false', + 'getErrorMessage' => 'string|false', + 'getFirstDayOfWeek' => 'int|false', + 'getGreatestMinimum' => 'int|false', + 'getKeywordValuesForLocale' => 'IntlIterator|false', + 'getLeastMaximum' => 'int|false', + 'getLocale' => 'string|false', + 'getMaximum' => 'int|false', + 'getMinimalDaysInFirstWeek' => 'int|false', + 'getMinimum' => 'int|false', + 'getNow' => 'float', + 'getRepeatedWallTimeOption' => 'int', + 'getSkippedWallTimeOption' => 'int', + 'getTime' => 'float|false', + 'getTimeZone' => 'IntlTimeZone|false', + 'getType' => 'string', + 'getWeekendTransition' => 'int|false', + 'inDaylightTime' => 'bool', + 'isEquivalentTo' => 'bool', + 'isLenient' => 'bool', + 'isWeekend' => 'bool', + 'roll' => 'bool', + 'isSet' => 'bool', + 'setTime' => 'bool', + 'setTimeZone' => 'bool', + 'toDateTime' => 'DateTime|false', + ], + 'IntlGregorianCalendar' => [ + 'setGregorianChange' => 'bool', + 'getGregorianChange' => 'float', + 'isLeapYear' => 'bool', + ], + 'Collator' => [ + 'create' => '?Collator', + 'compare' => 'int|false', + 'sort' => 'bool', + 'sortWithSortKeys' => 'bool', + 'asort' => 'bool', + 'getAttribute' => 'int|false', + 'setAttribute' => 'bool', + 'getStrength' => 'int', + 'getLocale' => 'string|false', + 'getErrorCode' => 'int|false', + 'getErrorMessage' => 'string|false', + 'getSortKey' => 'string|false', + ], + 'IntlIterator' => [ + 'current' => 'mixed', + 'key' => 'mixed', + 'next' => 'void', + 'rewind' => 'void', + 'valid' => 'bool', + ], + 'UConverter' => [ + 'convert' => 'string|false', + 'fromUCallback' => 'string|int|array|null', + 'getAliases' => 'array|false|null', + 'getAvailable' => 'array', + 'getDestinationEncoding' => 'string|false|null', + 'getDestinationType' => 'int|false|null', + 'getErrorCode' => 'int', + 'getErrorMessage' => '?string', + 'getSourceEncoding' => 'string|false|null', + 'getSourceType' => 'int|false|null', + 'getStandards' => '?array', + 'getSubstChars' => 'string|false|null', + 'reasonText' => 'string', + 'setDestinationEncoding' => 'bool', + 'setSourceEncoding' => 'bool', + 'setSubstChars' => 'bool', + 'toUCallback' => 'string|int|array|null', + 'transcode' => 'string|false', + ], + 'IntlDateFormatter' => [ + 'create' => '?IntlDateFormatter', + 'getDateType' => 'int|false', + 'getTimeType' => 'int|false', + 'getCalendar' => 'int|false', + 'setCalendar' => 'bool', + 'getTimeZoneId' => 'string|false', + 'getCalendarObject' => 'IntlCalendar|false|null', + 'getTimeZone' => 'IntlTimeZone|false', + 'setTimeZone' => '?bool', + 'setPattern' => 'bool', + 'getPattern' => 'string|false', + 'getLocale' => 'string|false', + 'setLenient' => 'void', + 'isLenient' => 'bool', + 'format' => 'string|false', + 'formatObject' => 'string|false', + 'parse' => 'int|float|false', + 'localtime' => 'array|false', + 'getErrorCode' => 'int', + 'getErrorMessage' => 'string', + ], + 'NumberFormatter' => [ + 'create' => '?NumberFormatter', + 'format' => 'string|false', + 'parse' => 'int|float|false', + 'formatCurrency' => 'string|false', + 'parseCurrency' => 'float|false', + 'setAttribute' => 'bool', + 'getAttribute' => 'int|float|false', + 'setTextAttribute' => 'bool', + 'getTextAttribute' => 'string|false', + 'setSymbol' => 'bool', + 'getSymbol' => 'string|false', + 'setPattern' => 'bool', + 'getPattern' => 'string|false', + 'getLocale' => 'string|false', + 'getErrorCode' => 'int', + 'getErrorMessage' => 'string', + ], + 'Locale' => [ + 'getDefault' => 'string', + 'getPrimaryLanguage' => '?string', + 'getScript' => '?string', + 'getRegion' => '?string', + 'getKeywords' => 'array|false|null', + 'getDisplayScript' => 'string|false', + 'getDisplayRegion' => 'string|false', + 'getDisplayName' => 'string|false', + 'getDisplayLanguage' => 'string|false', + 'getDisplayVariant' => 'string|false', + 'composeLocale' => 'string|false', + 'parseLocale' => '?array', + 'getAllVariants' => '?array', + 'filterMatches' => '?bool', + 'lookup' => '?string', + 'canonicalize' => '?string', + 'acceptFromHttp' => 'string|false', + ], + 'MessageFormatter' => [ + 'create' => '?MessageFormatter', + 'format' => 'string|false', + 'formatMessage' => 'string|false', + 'parse' => 'array|false', + 'parseMessage' => 'array|false', + 'setPattern' => 'bool', + 'getPattern' => 'string|false', + 'getLocale' => 'string', + 'getErrorCode' => 'int', + 'getErrorMessage' => 'string', + ], + 'Normalizer' => [ + 'normalize' => 'string|false', + 'isNormalized' => 'bool', + 'getRawDecomposition' => '?string', + ], + 'ResourceBundle' => [ + 'create' => '?ResourceBundle', + 'get' => 'mixed', + 'count' => 'int', + 'getLocales' => 'array|false', + 'getErrorCode' => 'int', + 'getErrorMessage' => 'string', + ], + 'Spoofchecker' => [ + 'isSuspicious' => 'bool', + 'areConfusable' => 'bool', + 'setAllowedLocales' => 'void', + 'setChecks' => 'void', + 'setRestrictionLevel' => 'void', + ], + 'IntlTimeZone' => [ + 'countEquivalentIDs' => 'int|false', + 'createDefault' => 'IntlTimeZone', + 'createEnumeration' => 'IntlIterator|false', + 'createTimeZone' => '?IntlTimeZone', + 'createTimeZoneIDEnumeration' => 'IntlIterator|false', + 'fromDateTimeZone' => '?IntlTimeZone', + 'getCanonicalID' => 'string|false', + 'getDisplayName' => 'string|false', + 'getDSTSavings' => 'int', + 'getEquivalentID' => 'string|false', + 'getErrorCode' => 'int|false', + 'getErrorMessage' => 'string|false', + 'getGMT' => 'IntlTimeZone', + 'getID' => 'string|false', + 'getOffset' => 'bool', + 'getRawOffset' => 'int', + 'getRegion' => 'string|false', + 'getTZDataVersion' => 'string|false', + 'getUnknown' => 'IntlTimeZone', + 'getWindowsID' => 'string|false', + 'getIDForWindowsID' => 'string|false', + 'hasSameRules' => 'bool', + 'toDateTimeZone' => 'DateTimeZone|false', + 'useDaylightTime' => 'bool', + ], + 'Transliterator' => [ + 'create' => '?Transliterator', + 'createFromRules' => '?Transliterator', + 'createInverse' => '?Transliterator', + 'listIDs' => 'array|false', + 'transliterate' => 'string|false', + 'getErrorCode' => 'int|false', + 'getErrorMessage' => 'string|false', + ], + 'IntlChar' => [ + 'hasBinaryProperty' => '?bool', + 'charAge' => '?array', + 'charDigitValue' => '?int', + 'charDirection' => '?int', + 'charFromName' => '?int', + 'charMirror' => 'int|string|null', + 'charName' => '?string', + 'charType' => '?int', + 'chr' => '?string', + 'digit' => 'int|false|null', + 'enumCharNames' => '?bool', + 'enumCharTypes' => 'void', + 'foldCase' => 'int|string|null', + 'forDigit' => 'int', + 'getBidiPairedBracket' => 'int|string|null', + 'getBlockCode' => '?int', + 'getCombiningClass' => '?int', + 'getFC_NFKC_Closure' => 'string|false|null', + 'getIntPropertyMaxValue' => 'int', + 'getIntPropertyMinValue' => 'int', + 'getIntPropertyValue' => '?int', + 'getNumericValue' => '?float', + 'getPropertyEnum' => 'int', + 'getPropertyName' => 'string|false', + 'getPropertyValueEnum' => 'int', + 'getPropertyValueName' => 'string|false', + 'getUnicodeVersion' => 'array', + 'isalnum' => '?bool', + 'isalpha' => '?bool', + 'isbase' => '?bool', + 'isblank' => '?bool', + 'iscntrl' => '?bool', + 'isdefined' => '?bool', + 'isdigit' => '?bool', + 'isgraph' => '?bool', + 'isIDIgnorable' => '?bool', + 'isIDPart' => '?bool', + 'isIDStart' => '?bool', + 'isISOControl' => '?bool', + 'isJavaIDPart' => '?bool', + 'isJavaIDStart' => '?bool', + 'isJavaSpaceChar' => '?bool', + 'islower' => '?bool', + 'isMirrored' => '?bool', + 'isprint' => '?bool', + 'ispunct' => '?bool', + 'isspace' => '?bool', + 'istitle' => '?bool', + 'isUAlphabetic' => '?bool', + 'isULowercase' => '?bool', + 'isupper' => '?bool', + 'isUUppercase' => '?bool', + 'isUWhiteSpace' => '?bool', + 'isWhitespace' => '?bool', + 'isxdigit' => '?bool', + 'ord' => '?int', + 'tolower' => 'int|string|null', + 'totitle' => 'int|string|null', + 'toupper' => 'int|string|null', + ], + 'JsonSerializable' => [ + 'jsonSerialize' => 'mixed', + ], + 'mysqli' => [ + 'autocommit' => 'bool', + 'begin_transaction' => 'bool', + 'change_user' => 'bool', + 'character_set_name' => 'string', + 'commit' => 'bool', + 'connect' => 'bool', + 'dump_debug_info' => 'bool', + 'get_charset' => '?object', + 'get_client_info' => 'string', + 'get_connection_stats' => 'array', + 'get_server_info' => 'string', + 'get_warnings' => 'mysqli_warning|false', + 'kill' => 'bool', + 'multi_query' => 'bool', + 'more_results' => 'bool', + 'next_result' => 'bool', + 'ping' => 'bool', + 'poll' => 'int|false', + 'prepare' => 'mysqli_stmt|false', + 'query' => 'mysqli_result|bool', + 'real_connect' => 'bool', + 'real_escape_string' => 'string', + 'reap_async_query' => 'mysqli_result|bool', + 'escape_string' => 'string', + 'real_query' => 'bool', + 'release_savepoint' => 'bool', + 'rollback' => 'bool', + 'savepoint' => 'bool', + 'select_db' => 'bool', + 'set_charset' => 'bool', + 'options' => 'bool', + 'set_opt' => 'bool', + 'stat' => 'string|false', + 'stmt_init' => 'mysqli_stmt|false', + 'store_result' => 'mysqli_result|false', + 'thread_safe' => 'bool', + 'use_result' => 'mysqli_result|false', + 'refresh' => 'bool', + ], + 'mysqli_result' => [ + 'close' => 'void', + 'free' => 'void', + 'data_seek' => 'bool', + 'fetch_field' => 'object|false', + 'fetch_fields' => 'array', + 'fetch_field_direct' => 'object|false', + 'fetch_all' => 'array', + 'fetch_array' => 'array|null|false', + 'fetch_assoc' => 'array|null|false', + 'fetch_object' => 'object|null|false', + 'fetch_row' => 'array|null|false', + 'field_seek' => 'bool', + 'free_result' => 'void', + ], + 'mysqli_stmt' => [ + 'attr_get' => 'int', + 'attr_set' => 'bool', + 'bind_param' => 'bool', + 'bind_result' => 'bool', + 'data_seek' => 'void', + 'execute' => 'bool', + 'fetch' => '?bool', + 'get_warnings' => 'mysqli_warning|false', + 'result_metadata' => 'mysqli_result|false', + 'more_results' => 'bool', + 'next_result' => 'bool', + 'num_rows' => 'int|string', + 'send_long_data' => 'bool', + 'free_result' => 'void', + 'reset' => 'bool', + 'prepare' => 'bool', + 'store_result' => 'bool', + 'get_result' => 'mysqli_result|false', + ], + 'OCILob' => [ + 'save' => 'bool', + 'import' => 'bool', + 'saveFile' => 'bool', + 'load' => 'string|false', + 'read' => 'string|false', + 'eof' => 'bool', + 'tell' => 'int|false', + 'rewind' => 'bool', + 'seek' => 'bool', + 'size' => 'int|false', + 'write' => 'int|false', + 'append' => 'bool', + 'truncate' => 'bool', + 'erase' => 'int|false', + 'flush' => 'bool', + 'setBuffering' => 'bool', + 'getBuffering' => 'bool', + 'writeToFile' => 'bool', + 'export' => 'bool', + 'writeTemporary' => 'bool', + 'close' => 'bool', + 'free' => 'bool', + ], + 'OCICollection' => [ + 'free' => 'bool', + 'append' => 'bool', + 'getElem' => 'string|float|null|false', + 'assign' => 'bool', + 'assignElem' => 'bool', + 'size' => 'int|false', + 'max' => 'int|false', + 'trim' => 'bool', + ], + 'PDO' => [ + 'beginTransaction' => 'bool', + 'commit' => 'bool', + 'errorCode' => '?string', + 'errorInfo' => 'array', + 'exec' => 'int|false', + 'getAttribute' => 'mixed', + 'getAvailableDrivers' => 'array', + 'inTransaction' => 'bool', + 'lastInsertId' => 'string|false', + 'prepare' => 'PDOStatement|false', + 'query' => 'PDOStatement|false', + 'quote' => 'string|false', + 'rollBack' => 'bool', + 'setAttribute' => 'bool', + ], + 'PDOStatement' => [ + 'bindColumn' => 'bool', + 'bindParam' => 'bool', + 'bindValue' => 'bool', + 'closeCursor' => 'bool', + 'columnCount' => 'int', + 'debugDumpParams' => '?bool', + 'errorCode' => '?string', + 'errorInfo' => 'array', + 'execute' => 'bool', + 'fetch' => 'mixed', + 'fetchAll' => 'array', + 'fetchColumn' => 'mixed', + 'fetchObject' => 'object|false', + 'getAttribute' => 'mixed', + 'getColumnMeta' => 'array|false', + 'nextRowset' => 'bool', + 'rowCount' => 'int', + 'setAttribute' => 'bool', + ], + 'PDO_PGSql_Ext' => [ + 'pgsqlCopyFromArray' => 'bool', + 'pgsqlCopyFromFile' => 'bool', + 'pgsqlCopyToArray' => 'array|false', + 'pgsqlCopyToFile' => 'bool', + 'pgsqlLOBCreate' => 'string|false', + 'pgsqlLOBUnlink' => 'bool', + 'pgsqlGetNotify' => 'array|false', + 'pgsqlGetPid' => 'int', + ], + 'PDO_SQLite_Ext' => [ + 'sqliteCreateFunction' => 'bool', + 'sqliteCreateAggregate' => 'bool', + 'sqliteCreateCollation' => 'bool', + ], + 'Phar' => [ + 'addEmptyDir' => 'void', + 'addFile' => 'void', + 'addFromString' => 'void', + 'buildFromDirectory' => 'array', + 'buildFromIterator' => 'array', + 'compressFiles' => 'void', + 'compress' => '?Phar', + 'decompress' => '?Phar', + 'convertToExecutable' => '?Phar', + 'convertToData' => '?PharData', + 'count' => 'int', + 'extractTo' => 'bool', + 'getAlias' => '?string', + 'getPath' => 'string', + 'getMetadata' => 'mixed', + 'getModified' => 'bool', + 'getSignature' => 'array|false', + 'getStub' => 'string', + 'getVersion' => 'string', + 'hasMetadata' => 'bool', + 'isBuffering' => 'bool', + 'isCompressed' => 'int|false', + 'isFileFormat' => 'bool', + 'isWritable' => 'bool', + 'offsetExists' => 'bool', + 'offsetGet' => 'SplFileInfo', + 'offsetSet' => 'void', + 'offsetUnset' => 'void', + 'setAlias' => 'bool', + 'setDefaultStub' => 'bool', + 'setMetadata' => 'void', + 'setSignatureAlgorithm' => 'void', + 'startBuffering' => 'void', + 'stopBuffering' => 'void', + ], + 'PharData' => [ + 'addEmptyDir' => 'void', + 'addFile' => 'void', + 'addFromString' => 'void', + 'buildFromDirectory' => 'array', + 'buildFromIterator' => 'array', + 'compressFiles' => 'void', + 'compress' => '?PharData', + 'decompress' => '?PharData', + 'convertToExecutable' => '?Phar', + 'convertToData' => '?PharData', + 'count' => 'int', + 'extractTo' => 'bool', + 'getAlias' => '?string', + 'getPath' => 'string', + 'getMetadata' => 'mixed', + 'getModified' => 'bool', + 'getSignature' => 'array|false', + 'getStub' => 'string', + 'getVersion' => 'string', + 'hasMetadata' => 'bool', + 'isBuffering' => 'bool', + 'isCompressed' => 'int|false', + 'isFileFormat' => 'bool', + 'isWritable' => 'bool', + 'offsetExists' => 'bool', + 'offsetGet' => 'SplFileInfo', + 'offsetSet' => 'void', + 'offsetUnset' => 'void', + 'setAlias' => 'bool', + 'setDefaultStub' => 'bool', + 'setMetadata' => 'void', + 'setSignatureAlgorithm' => 'void', + 'startBuffering' => 'void', + 'stopBuffering' => 'void', + ], + 'PharFileInfo' => [ + 'chmod' => 'void', + 'getCompressedSize' => 'int', + 'getCRC32' => 'int', + 'getContent' => 'string', + 'getMetadata' => 'mixed', + 'getPharFlags' => 'int', + 'hasMetadata' => 'bool', + 'isCompressed' => 'bool', + 'isCRCChecked' => 'bool', + 'setMetadata' => 'void', + ], + 'Reflection' => [ + 'getModifierNames' => 'array', + ], + 'ReflectionFunctionAbstract' => [ + 'inNamespace' => 'bool', + 'isClosure' => 'bool', + 'isDeprecated' => 'bool', + 'isInternal' => 'bool', + 'isUserDefined' => 'bool', + 'isGenerator' => 'bool', + 'isVariadic' => 'bool', + 'isStatic' => 'bool', + 'getClosureThis' => '?object', + 'getClosureScopeClass' => '?ReflectionClass', + 'getDocComment' => 'string|false', + 'getEndLine' => 'int|false', + 'getExtension' => '?ReflectionExtension', + 'getExtensionName' => 'string|false', + 'getFileName' => 'string|false', + 'getName' => 'string', + 'getNamespaceName' => 'string', + 'getNumberOfParameters' => 'int', + 'getNumberOfRequiredParameters' => 'int', + 'getParameters' => 'array', + 'getShortName' => 'string', + 'getStartLine' => 'int|false', + 'getStaticVariables' => 'array', + 'returnsReference' => 'bool', + 'hasReturnType' => 'bool', + 'getReturnType' => '?ReflectionType', + ], + 'ReflectionFunction' => [ + 'isDisabled' => 'bool', + 'invoke' => 'mixed', + 'invokeArgs' => 'mixed', + 'getClosure' => 'Closure', + 'getExecutingLine' => 'int', + 'getExecutingFile' => 'string', + 'getTrace' => 'array', + 'getFunction' => 'ReflectionFunctionAbstract', + 'getThis' => '?object', + 'getExecutingGenerator' => 'Generator', + ], + 'ReflectionMethod' => [ + 'isPublic' => 'bool', + 'isPrivate' => 'bool', + 'isProtected' => 'bool', + 'isAbstract' => 'bool', + 'isFinal' => 'bool', + 'isConstructor' => 'bool', + 'isDestructor' => 'bool', + 'getClosure' => 'Closure', + 'getModifiers' => 'int', + 'invoke' => 'mixed', + 'invokeArgs' => 'mixed', + 'getDeclaringClass' => 'ReflectionClass', + 'getPrototype' => 'ReflectionMethod', + 'setAccessible' => 'void', + ], + 'ReflectionClass' => [ + 'getName' => 'string', + 'isInternal' => 'bool', + 'isUserDefined' => 'bool', + 'isAnonymous' => 'bool', + 'isInstantiable' => 'bool', + 'isCloneable' => 'bool', + 'getFileName' => 'string|false', + 'getStartLine' => 'int|false', + 'getEndLine' => 'int|false', + 'getDocComment' => 'string|false', + 'getConstructor' => '?ReflectionMethod', + 'hasMethod' => 'bool', + 'getMethod' => 'ReflectionMethod', + 'getMethods' => 'array', + 'hasProperty' => 'bool', + 'getProperty' => 'ReflectionProperty', + 'getProperties' => 'array', + 'hasConstant' => 'bool', + 'getConstants' => 'array', + 'getReflectionConstants' => 'array', + 'getConstant' => 'mixed', + 'getReflectionConstant' => 'ReflectionClassConstant|false', + 'getInterfaces' => 'array', + 'getInterfaceNames' => 'array', + 'isInterface' => 'bool', + 'getTraits' => 'array', + 'getTraitNames' => 'array', + 'getTraitAliases' => 'array', + 'isTrait' => 'bool', + 'isAbstract' => 'bool', + 'isFinal' => 'bool', + 'getModifiers' => 'int', + 'isInstance' => 'bool', + 'newInstance' => 'object', + 'newInstanceWithoutConstructor' => 'object', + 'newInstanceArgs' => '?object', + 'getParentClass' => 'ReflectionClass|false', + 'isSubclassOf' => 'bool', + 'getStaticProperties' => '?array', + 'getStaticPropertyValue' => 'mixed', + 'setStaticPropertyValue' => 'void', + 'getDefaultProperties' => 'array', + 'isIterable' => 'bool', + 'isIterateable' => 'bool', + 'implementsInterface' => 'bool', + 'getExtension' => '?ReflectionExtension', + 'getExtensionName' => 'string|false', + 'inNamespace' => 'bool', + 'getNamespaceName' => 'string', + 'getShortName' => 'string', + ], + 'ReflectionProperty' => [ + 'getName' => 'string', + 'getValue' => 'mixed', + 'setValue' => 'void', + 'isInitialized' => 'bool', + 'isPublic' => 'bool', + 'isPrivate' => 'bool', + 'isProtected' => 'bool', + 'isStatic' => 'bool', + 'isDefault' => 'bool', + 'getModifiers' => 'int', + 'getDeclaringClass' => 'ReflectionClass', + 'getDocComment' => 'string|false', + 'setAccessible' => 'void', + 'getType' => '?ReflectionType', + 'hasType' => 'bool', + 'getDefaultValue' => 'mixed', + ], + 'ReflectionClassConstant' => [ + 'getName' => 'string', + 'getValue' => 'mixed', + 'isPublic' => 'bool', + 'isPrivate' => 'bool', + 'isProtected' => 'bool', + 'getModifiers' => 'int', + 'getDeclaringClass' => 'ReflectionClass', + 'getDocComment' => 'string|false', + ], + 'ReflectionParameter' => [ + 'getName' => 'string', + 'isPassedByReference' => 'bool', + 'canBePassedByValue' => 'bool', + 'getDeclaringFunction' => 'ReflectionFunctionAbstract', + 'getDeclaringClass' => '?ReflectionClass', + 'getClass' => '?ReflectionClass', + 'hasType' => 'bool', + 'getType' => '?ReflectionType', + 'isArray' => 'bool', + 'isCallable' => 'bool', + 'allowsNull' => 'bool', + 'getPosition' => 'int', + 'isOptional' => 'bool', + 'isDefaultValueAvailable' => 'bool', + 'getDefaultValue' => 'mixed', + 'isDefaultValueConstant' => 'bool', + 'getDefaultValueConstantName' => '?string', + 'isVariadic' => 'bool', + ], + 'ReflectionType' => [ + 'allowsNull' => 'bool', + ], + 'ReflectionNamedType' => [ + 'getName' => 'string', + 'isBuiltin' => 'bool', + ], + 'ReflectionExtension' => [ + 'getName' => 'string', + 'getVersion' => '?string', + 'getFunctions' => 'array', + 'getConstants' => 'array', + 'getINIEntries' => 'array', + 'getClasses' => 'array', + 'getClassNames' => 'array', + 'getDependencies' => 'array', + 'info' => 'void', + 'isPersistent' => 'bool', + 'isTemporary' => 'bool', + ], + 'ReflectionZendExtension' => [ + 'getName' => 'string', + 'getVersion' => 'string', + 'getAuthor' => 'string', + 'getURL' => 'string', + 'getCopyright' => 'string', + ], + 'SessionHandlerInterface' => [ + 'open' => 'bool', + 'close' => 'bool', + 'read' => 'string|false', + 'write' => 'bool', + 'destroy' => 'bool', + 'gc' => 'int|false', + ], + 'SessionIdInterface' => [ + 'create_sid' => 'string', + ], + 'SessionUpdateTimestampHandlerInterface' => [ + 'validateId' => 'bool', + 'updateTimestamp' => 'bool', + ], + 'SessionHandler' => [ + 'open' => 'bool', + 'close' => 'bool', + 'read' => 'string|false', + 'write' => 'bool', + 'destroy' => 'bool', + 'gc' => 'int|false', + 'create_sid' => 'string', + ], + 'SimpleXMLElement' => [ + 'xpath' => 'array|null|false', + 'registerXPathNamespace' => 'bool', + 'asXML' => 'string|bool', + 'saveXML' => 'string|bool', + 'getNamespaces' => 'array', + 'getDocNamespaces' => 'array|false', + 'children' => '?SimpleXMLElement', + 'attributes' => '?SimpleXMLElement', + 'addChild' => '?SimpleXMLElement', + 'addAttribute' => 'void', + 'getName' => 'string', + 'count' => 'int', + 'rewind' => 'void', + 'valid' => 'bool', + 'current' => 'SimpleXMLElement', + 'key' => 'string', + 'next' => 'void', + 'hasChildren' => 'bool', + 'getChildren' => '?SimpleXMLElement', + ], + 'SNMP' => [ + 'close' => 'bool', + 'setSecurity' => 'bool', + 'get' => 'mixed', + 'getnext' => 'mixed', + 'walk' => 'array|false', + 'set' => 'bool', + 'getErrno' => 'int', + 'getError' => 'string', + ], + 'SoapServer' => [ + 'fault' => 'void', + 'addSoapHeader' => 'void', + 'setPersistence' => 'void', + 'setClass' => 'void', + 'setObject' => 'void', + 'getFunctions' => 'array', + 'addFunction' => 'void', + 'handle' => 'void', + ], + 'SoapClient' => [ + '__call' => 'mixed', + '__soapCall' => 'mixed', + '__getFunctions' => '?array', + '__getTypes' => '?array', + '__getLastRequest' => '?string', + '__getLastResponse' => '?string', + '__getLastRequestHeaders' => '?string', + '__getLastResponseHeaders' => '?string', + '__doRequest' => '?string', + '__setCookie' => 'void', + '__getCookies' => 'array', + '__setSoapHeaders' => 'bool', + '__setLocation' => '?string', + ], + 'ArrayObject' => [ + 'offsetExists' => 'bool', + 'offsetGet' => 'mixed', + 'offsetSet' => 'void', + 'offsetUnset' => 'void', + 'append' => 'void', + 'getArrayCopy' => 'array', + 'count' => 'int', + 'getFlags' => 'int', + 'setFlags' => 'void', + 'asort' => 'bool', + 'ksort' => 'bool', + 'uasort' => 'bool', + 'uksort' => 'bool', + 'natsort' => 'bool', + 'natcasesort' => 'bool', + 'unserialize' => 'void', + 'serialize' => 'string', + '__serialize' => 'array', + '__unserialize' => 'void', + 'getIterator' => 'Iterator', + 'exchangeArray' => 'array', + 'setIteratorClass' => 'void', + 'getIteratorClass' => 'string', + '__debugInfo' => 'array', + ], + 'ArrayIterator' => [ + 'offsetExists' => 'bool', + 'offsetGet' => 'mixed', + 'offsetSet' => 'void', + 'offsetUnset' => 'void', + 'append' => 'void', + 'getArrayCopy' => 'array', + 'count' => 'int', + 'getFlags' => 'int', + 'setFlags' => 'void', + 'asort' => 'bool', + 'ksort' => 'bool', + 'uasort' => 'bool', + 'uksort' => 'bool', + 'natsort' => 'bool', + 'natcasesort' => 'bool', + 'unserialize' => 'void', + 'serialize' => 'string', + '__serialize' => 'array', + '__unserialize' => 'void', + 'rewind' => 'void', + 'current' => 'mixed', + 'key' => 'string|int|null', + 'next' => 'void', + 'valid' => 'bool', + 'seek' => 'void', + '__debugInfo' => 'array', + ], + 'RecursiveArrayIterator' => [ + 'hasChildren' => 'bool', + 'getChildren' => '?RecursiveArrayIterator', + ], + 'SplFileInfo' => [ + 'getPath' => 'string', + 'getFilename' => 'string', + 'getExtension' => 'string', + 'getBasename' => 'string', + 'getPathname' => 'string', + 'getPerms' => 'int|false', + 'getInode' => 'int|false', + 'getSize' => 'int|false', + 'getOwner' => 'int|false', + 'getGroup' => 'int|false', + 'getATime' => 'int|false', + 'getMTime' => 'int|false', + 'getCTime' => 'int|false', + 'getType' => 'string|false', + 'isWritable' => 'bool', + 'isReadable' => 'bool', + 'isExecutable' => 'bool', + 'isFile' => 'bool', + 'isDir' => 'bool', + 'isLink' => 'bool', + 'getLinkTarget' => 'string|false', + 'getRealPath' => 'string|false', + 'getFileInfo' => 'SplFileInfo', + 'getPathInfo' => '?SplFileInfo', + 'openFile' => 'SplFileObject', + 'setFileClass' => 'void', + 'setInfoClass' => 'void', + '__debugInfo' => 'array', + '_bad_state_ex' => 'void', + ], + 'DirectoryIterator' => [ + 'getFilename' => 'string', + 'getExtension' => 'string', + 'getBasename' => 'string', + 'isDot' => 'bool', + 'rewind' => 'void', + 'valid' => 'bool', + 'key' => 'mixed', + 'current' => 'mixed', + 'next' => 'void', + 'seek' => 'void', + ], + 'FilesystemIterator' => [ + 'rewind' => 'void', + 'key' => 'string', + 'current' => 'string|SplFileInfo|FilesystemIterator', + 'getFlags' => 'int', + 'setFlags' => 'void', + ], + 'RecursiveDirectoryIterator' => [ + 'hasChildren' => 'bool', + 'getChildren' => 'RecursiveDirectoryIterator', + 'getSubPath' => 'string', + 'getSubPathname' => 'string', + ], + 'GlobIterator' => [ + 'count' => 'int', + ], + 'SplFileObject' => [ + 'rewind' => 'void', + 'eof' => 'bool', + 'valid' => 'bool', + 'fgets' => 'string', + 'fread' => 'string|false', + 'fgetcsv' => 'array|false', + 'fputcsv' => 'int|false', + 'setCsvControl' => 'void', + 'getCsvControl' => 'array', + 'flock' => 'bool', + 'fflush' => 'bool', + 'ftell' => 'int|false', + 'fseek' => 'int', + 'fgetc' => 'string|false', + 'fpassthru' => 'int', + 'fscanf' => 'array|int|null', + 'fwrite' => 'int|false', + 'fstat' => 'array', + 'ftruncate' => 'bool', + 'current' => 'string|array|false', + 'key' => 'int', + 'next' => 'void', + 'setFlags' => 'void', + 'getFlags' => 'int', + 'setMaxLineLen' => 'void', + 'getMaxLineLen' => 'int', + 'hasChildren' => 'false', + 'getChildren' => 'null', + 'seek' => 'void', + 'getCurrentLine' => 'string', + ], + 'SplDoublyLinkedList' => [ + 'add' => 'void', + 'pop' => 'mixed', + 'shift' => 'mixed', + 'push' => 'void', + 'unshift' => 'void', + 'top' => 'mixed', + 'bottom' => 'mixed', + '__debugInfo' => 'array', + 'count' => 'int', + 'isEmpty' => 'bool', + 'setIteratorMode' => 'int', + 'getIteratorMode' => 'int', + 'offsetExists' => 'bool', + 'offsetGet' => 'mixed', + 'offsetSet' => 'void', + 'offsetUnset' => 'void', + 'rewind' => 'void', + 'current' => 'mixed', + 'key' => 'int', + 'prev' => 'void', + 'next' => 'void', + 'valid' => 'bool', + 'unserialize' => 'void', + 'serialize' => 'string', + '__serialize' => 'array', + '__unserialize' => 'void', + ], + 'SplQueue' => [ + 'enqueue' => 'void', + 'dequeue' => 'mixed', + ], + 'SplFixedArray' => [ + '__wakeup' => 'void', + 'count' => 'int', + 'toArray' => 'array', + 'fromArray' => 'SplFixedArray', + 'getSize' => 'int', + 'offsetExists' => 'bool', + 'offsetGet' => 'mixed', + 'offsetSet' => 'void', + 'offsetUnset' => 'void', + ], + 'SplPriorityQueue' => [ + 'compare' => 'int', + 'setExtractFlags' => 'int', + 'top' => 'mixed', + 'extract' => 'mixed', + 'count' => 'int', + 'isEmpty' => 'bool', + 'rewind' => 'void', + 'current' => 'mixed', + 'key' => 'int', + 'next' => 'void', + 'valid' => 'bool', + 'isCorrupted' => 'bool', + 'getExtractFlags' => 'int', + '__debugInfo' => 'array', + ], + 'SplHeap' => [ + 'extract' => 'mixed', + 'insert' => 'bool', + 'top' => 'mixed', + 'count' => 'int', + 'isEmpty' => 'bool', + 'rewind' => 'void', + 'current' => 'mixed', + 'key' => 'int', + 'next' => 'void', + 'valid' => 'bool', + 'recoverFromCorruption' => 'bool', + 'compare' => 'int', + 'isCorrupted' => 'bool', + '__debugInfo' => 'array', + ], + 'SplMinHeap' => [ + 'compare' => 'int', + ], + 'SplMaxHeap' => [ + 'compare' => 'int', + ], + 'EmptyIterator' => [ + 'current' => 'never', + 'next' => 'void', + 'key' => 'never', + 'valid' => 'false', + 'rewind' => 'void', + ], + 'CallbackFilterIterator' => [ + 'accept' => 'bool', + ], + 'RecursiveCallbackFilterIterator' => [ + 'hasChildren' => 'bool', + 'getChildren' => 'RecursiveCallbackFilterIterator', + ], + 'RecursiveIterator' => [ + 'hasChildren' => 'bool', + 'getChildren' => '?RecursiveIterator', + ], + 'RecursiveIteratorIterator' => [ + 'rewind' => 'void', + 'valid' => 'bool', + 'key' => 'mixed', + 'current' => 'mixed', + 'next' => 'void', + 'getDepth' => 'int', + 'getSubIterator' => '?RecursiveIterator', + 'getInnerIterator' => 'RecursiveIterator', + 'beginIteration' => 'void', + 'endIteration' => 'void', + 'callHasChildren' => 'bool', + 'callGetChildren' => '?RecursiveIterator', + 'beginChildren' => 'void', + 'endChildren' => 'void', + 'nextElement' => 'void', + 'setMaxDepth' => 'void', + 'getMaxDepth' => 'int|false', + ], + 'OuterIterator' => [ + 'getInnerIterator' => '?Iterator', + ], + 'IteratorIterator' => [ + 'getInnerIterator' => '?Iterator', + 'rewind' => 'void', + 'valid' => 'bool', + 'key' => 'mixed', + 'current' => 'mixed', + 'next' => 'void', + ], + 'FilterIterator' => [ + 'accept' => 'bool', + 'rewind' => 'void', + 'next' => 'void', + ], + 'RecursiveFilterIterator' => [ + 'hasChildren' => 'bool', + 'getChildren' => '?RecursiveFilterIterator', + ], + 'ParentIterator' => [ + 'accept' => 'bool', + ], + 'SeekableIterator' => [ + 'seek' => 'void', + ], + 'LimitIterator' => [ + 'rewind' => 'void', + 'valid' => 'bool', + 'next' => 'void', + 'seek' => 'int', + 'getPosition' => 'int', + ], + 'CachingIterator' => [ + 'rewind' => 'void', + 'valid' => 'bool', + 'next' => 'void', + 'hasNext' => 'bool', + 'getFlags' => 'int', + 'setFlags' => 'void', + 'offsetGet' => 'mixed', + 'offsetSet' => 'void', + 'offsetUnset' => 'void', + 'offsetExists' => 'bool', + 'getCache' => 'array', + 'count' => 'int', + ], + 'RecursiveCachingIterator' => [ + 'hasChildren' => 'bool', + 'getChildren' => '?RecursiveCachingIterator', + ], + 'NoRewindIterator' => [ + 'rewind' => 'void', + 'valid' => 'bool', + 'key' => 'mixed', + 'current' => 'mixed', + 'next' => 'void', + ], + 'AppendIterator' => [ + 'append' => 'void', + 'rewind' => 'void', + 'valid' => 'bool', + 'current' => 'mixed', + 'next' => 'void', + 'getIteratorIndex' => '?int', + 'getArrayIterator' => 'ArrayIterator', + ], + 'InfiniteIterator' => [ + 'next' => 'void', + ], + 'RegexIterator' => [ + 'accept' => 'bool', + 'getMode' => 'int', + 'setMode' => 'void', + 'getFlags' => 'int', + 'setFlags' => 'void', + 'getRegex' => 'string', + 'getPregFlags' => 'int', + 'setPregFlags' => 'void', + ], + 'RecursiveRegexIterator' => [ + 'accept' => 'bool', + 'hasChildren' => 'bool', + 'getChildren' => 'RecursiveRegexIterator', + ], + 'RecursiveTreeIterator' => [ + 'key' => 'mixed', + 'current' => 'mixed', + 'getPrefix' => 'string', + 'setPostfix' => 'void', + 'setPrefixPart' => 'void', + 'getEntry' => 'string', + 'getPostfix' => 'string', + ], + 'SplObserver' => [ + 'update' => 'void', + ], + 'SplSubject' => [ + 'attach' => 'void', + 'detach' => 'void', + 'notify' => 'void', + ], + 'SplObjectStorage' => [ + 'attach' => 'void', + 'detach' => 'void', + 'contains' => 'bool', + 'addAll' => 'int', + 'removeAll' => 'int', + 'removeAllExcept' => 'int', + 'getInfo' => 'mixed', + 'setInfo' => 'void', + 'count' => 'int', + 'rewind' => 'void', + 'valid' => 'bool', + 'key' => 'int', + 'current' => 'object', + 'next' => 'void', + 'unserialize' => 'void', + 'serialize' => 'string', + 'offsetExists' => 'bool', + 'offsetGet' => 'mixed', + 'offsetSet' => 'void', + 'offsetUnset' => 'void', + 'getHash' => 'string', + '__serialize' => 'array', + '__unserialize' => 'void', + '__debugInfo' => 'array', + ], + 'MultipleIterator' => [ + 'getFlags' => 'int', + 'setFlags' => 'void', + 'attachIterator' => 'void', + 'detachIterator' => 'void', + 'containsIterator' => 'bool', + 'countIterators' => 'int', + 'rewind' => 'void', + 'valid' => 'bool', + 'key' => 'array', + 'current' => 'array', + 'next' => 'void', + '__debugInfo' => 'array', + ], + 'SQLite3' => [ + 'open' => 'void', + 'version' => 'array', + 'lastInsertRowID' => 'int', + 'lastErrorCode' => 'int', + 'lastExtendedErrorCode' => 'int', + 'lastErrorMsg' => 'string', + 'changes' => 'int', + 'busyTimeout' => 'bool', + 'loadExtension' => 'bool', + 'backup' => 'bool', + 'escapeString' => 'string', + 'prepare' => 'SQLite3Stmt|false', + 'exec' => 'bool', + 'query' => 'SQLite3Result|false', + 'querySingle' => 'mixed', + 'createFunction' => 'bool', + 'createAggregate' => 'bool', + 'createCollation' => 'bool', + 'enableExceptions' => 'bool', + 'enableExtendedResultCodes' => 'bool', + 'setAuthorizer' => 'bool', + ], + 'SQLite3Stmt' => [ + 'bindParam' => 'bool', + 'bindValue' => 'bool', + 'clear' => 'bool', + 'close' => 'bool', + 'execute' => 'SQLite3Result|false', + 'getSQL' => 'string|false', + 'paramCount' => 'int', + 'readOnly' => 'bool', + 'reset' => 'bool', + ], + 'SQLite3Result' => [ + 'numColumns' => 'int', + 'columnName' => 'string|false', + 'columnType' => 'int|false', + 'fetchArray' => 'array|false', + 'reset' => 'bool', + ], + 'Directory' => [ + 'close' => 'void', + 'rewind' => 'void', + 'read' => 'string|false', + ], + 'php_user_filter' => [ + 'filter' => 'int', + 'onCreate' => 'bool', + 'onClose' => 'void', + ], + 'tidy' => [ + 'getOpt' => 'string|int|bool', + 'cleanRepair' => 'bool', + 'parseFile' => 'bool', + 'parseString' => 'bool', + 'repairString' => 'string|false', + 'repairFile' => 'string|false', + 'diagnose' => 'bool', + 'getRelease' => 'string', + 'getConfig' => 'array', + 'getStatus' => 'int', + 'getHtmlVer' => 'int', + 'getOptDoc' => 'string|false', + 'isXhtml' => 'bool', + 'isXml' => 'bool', + 'root' => '?tidyNode', + 'head' => '?tidyNode', + 'html' => '?tidyNode', + 'body' => '?tidyNode', + ], + 'XMLReader' => [ + 'getAttribute' => '?string', + 'getAttributeNo' => '?string', + 'getAttributeNs' => '?string', + 'getParserProperty' => 'bool', + 'isValid' => 'bool', + 'lookupNamespace' => '?string', + 'moveToAttribute' => 'bool', + 'moveToAttributeNo' => 'bool', + 'moveToAttributeNs' => 'bool', + 'moveToElement' => 'bool', + 'moveToFirstAttribute' => 'bool', + 'moveToNextAttribute' => 'bool', + 'read' => 'bool', + 'next' => 'bool', + 'readInnerXml' => 'string', + 'readOuterXml' => 'string', + 'readString' => 'string', + 'setSchema' => 'bool', + 'setParserProperty' => 'bool', + 'setRelaxNGSchema' => 'bool', + 'setRelaxNGSchemaSource' => 'bool', + 'expand' => 'DOMNode|false', + ], + 'XMLWriter' => [ + 'openUri' => 'bool', + 'openMemory' => 'bool', + 'setIndent' => 'bool', + 'setIndentString' => 'bool', + 'startComment' => 'bool', + 'endComment' => 'bool', + 'startAttribute' => 'bool', + 'endAttribute' => 'bool', + 'writeAttribute' => 'bool', + 'startAttributeNs' => 'bool', + 'writeAttributeNs' => 'bool', + 'startElement' => 'bool', + 'endElement' => 'bool', + 'fullEndElement' => 'bool', + 'startElementNs' => 'bool', + 'writeElement' => 'bool', + 'writeElementNs' => 'bool', + 'startPi' => 'bool', + 'endPi' => 'bool', + 'writePi' => 'bool', + 'startCdata' => 'bool', + 'endCdata' => 'bool', + 'writeCdata' => 'bool', + 'text' => 'bool', + 'writeRaw' => 'bool', + 'startDocument' => 'bool', + 'endDocument' => 'bool', + 'writeComment' => 'bool', + 'startDtd' => 'bool', + 'endDtd' => 'bool', + 'writeDtd' => 'bool', + 'startDtdElement' => 'bool', + 'endDtdElement' => 'bool', + 'writeDtdElement' => 'bool', + 'startDtdAttlist' => 'bool', + 'endDtdAttlist' => 'bool', + 'writeDtdAttlist' => 'bool', + 'startDtdEntity' => 'bool', + 'endDtdEntity' => 'bool', + 'writeDtdEntity' => 'bool', + 'outputMemory' => 'string', + 'flush' => 'string|int', + ], + 'XSLTProcessor' => [ + 'importStylesheet' => 'bool', + 'transformToDoc' => 'DOMDocument|false', + 'transformToUri' => 'int', + 'transformToXml' => 'string|null|false', + 'setParameter' => 'bool', + 'getParameter' => 'string|false', + 'removeParameter' => 'bool', + 'hasExsltSupport' => 'bool', + 'registerPHPFunctions' => 'void', + 'setSecurityPrefs' => 'int', + 'getSecurityPrefs' => 'int', + ], + 'ZipArchive' => [ + 'open' => 'bool|int', + 'setPassword' => 'bool', + 'close' => 'bool', + 'count' => 'int', + 'getStatusString' => 'string', + 'addEmptyDir' => 'bool', + 'addFromString' => 'bool', + 'addFile' => 'bool', + 'replaceFile' => 'bool', + 'addGlob' => 'array|false', + 'addPattern' => 'array|false', + 'renameIndex' => 'bool', + 'renameName' => 'bool', + 'setArchiveComment' => 'bool', + 'getArchiveComment' => 'string|false', + 'setCommentIndex' => 'bool', + 'setCommentName' => 'bool', + 'setMtimeIndex' => 'bool', + 'setMtimeName' => 'bool', + 'getCommentIndex' => 'string|false', + 'getCommentName' => 'string|false', + 'deleteIndex' => 'bool', + 'deleteName' => 'bool', + 'statName' => 'array|false', + 'statIndex' => 'array|false', + 'locateName' => 'int|false', + 'getNameIndex' => 'string|false', + 'unchangeArchive' => 'bool', + 'unchangeAll' => 'bool', + 'unchangeIndex' => 'bool', + 'unchangeName' => 'bool', + 'extractTo' => 'bool', + 'getFromName' => 'string|false', + 'getFromIndex' => 'string|false', + 'setExternalAttributesName' => 'bool', + 'setExternalAttributesIndex' => 'bool', + 'getExternalAttributesName' => 'bool', + 'getExternalAttributesIndex' => 'bool', + 'setCompressionName' => 'bool', + 'setCompressionIndex' => 'bool', + 'setEncryptionName' => 'bool', + 'setEncryptionIndex' => 'bool', + 'registerProgressCallback' => 'bool', + 'registerCancelCallback' => 'bool', + ], + 'Exception' => [ + '__wakeup' => 'void', + ], + 'Error' => [ + '__wakeup' => 'void', + ], + 'IteratorAggregate' => [ + 'getIterator' => 'Traversable', + ], + 'Iterator' => [ + 'current' => 'mixed', + 'next' => 'void', + 'key' => 'mixed', + 'valid' => 'bool', + 'rewind' => 'void', + ], + 'ArrayAccess' => [ + 'offsetExists' => 'bool', + 'offsetGet' => 'mixed', + 'offsetSet' => 'void', + 'offsetUnset' => 'void', + ], + 'Countable' => [ + 'count' => 'int', + ], + ]; +} diff --git a/lib/symfony/error-handler/LICENSE b/lib/symfony/error-handler/LICENSE new file mode 100644 index 0000000000..9c907a46a6 --- /dev/null +++ b/lib/symfony/error-handler/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2019-2022 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/lib/symfony/error-handler/README.md b/lib/symfony/error-handler/README.md new file mode 100644 index 0000000000..12c0bfa6d6 --- /dev/null +++ b/lib/symfony/error-handler/README.md @@ -0,0 +1,44 @@ +ErrorHandler Component +====================== + +The ErrorHandler component provides tools to manage errors and ease debugging PHP code. + +Getting Started +--------------- + +``` +$ composer require symfony/error-handler +``` + +```php +use Symfony\Component\ErrorHandler\Debug; +use Symfony\Component\ErrorHandler\ErrorHandler; +use Symfony\Component\ErrorHandler\DebugClassLoader; + +Debug::enable(); + +// or enable only one feature +//ErrorHandler::register(); +//DebugClassLoader::enable(); + +// If you want a custom generic template when debug is not enabled +// HtmlErrorRenderer::setTemplate('/path/to/custom/error.html.php'); + +$data = ErrorHandler::call(static function () use ($filename, $datetimeFormat) { + // if any code executed inside this anonymous function fails, a PHP exception + // will be thrown, even if the code uses the '@' PHP silence operator + $data = json_decode(file_get_contents($filename), true); + $data['read_at'] = date($datetimeFormat); + file_put_contents($filename, json_encode($data)); + + return $data; +}); +``` + +Resources +--------- + + * [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/lib/symfony/error-handler/Resources/assets/css/error.css b/lib/symfony/error-handler/Resources/assets/css/error.css new file mode 100644 index 0000000000..332d81876c --- /dev/null +++ b/lib/symfony/error-handler/Resources/assets/css/error.css @@ -0,0 +1,4 @@ +body { background-color: #fff; color: #222; font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; margin: 0; } +.container { margin: 30px; max-width: 600px; } +h1 { color: #dc3545; font-size: 24px; } +h2 { font-size: 18px; } diff --git a/lib/symfony/error-handler/Resources/assets/css/exception.css b/lib/symfony/error-handler/Resources/assets/css/exception.css new file mode 100644 index 0000000000..7cb3206da2 --- /dev/null +++ b/lib/symfony/error-handler/Resources/assets/css/exception.css @@ -0,0 +1,252 @@ +/* This file is based on WebProfilerBundle/Resources/views/Profiler/profiler.css.twig. + If you make any change in this file, verify the same change is needed in the other file. */ +:root { + --font-sans-serif: Helvetica, Arial, sans-serif; + --page-background: #f9f9f9; + --color-text: #222; + /* when updating any of these colors, do the same in toolbar.css.twig */ + --color-success: #4f805d; + --color-warning: #a46a1f; + --color-error: #b0413e; + --color-muted: #999; + --tab-background: #fff; + --tab-color: #444; + --tab-active-background: #666; + --tab-active-color: #fafafa; + --tab-disabled-background: #f5f5f5; + --tab-disabled-color: #999; + --metric-value-background: #fff; + --metric-value-color: inherit; + --metric-unit-color: #999; + --metric-label-background: #e0e0e0; + --metric-label-color: inherit; + --table-border: #e0e0e0; + --table-background: #fff; + --table-header: #e0e0e0; + --trace-selected-background: #F7E5A1; + --tree-active-background: #F7E5A1; + --exception-title-color: var(--base-2); + --shadow: 0px 0px 1px rgba(128, 128, 128, .2); + --border: 1px solid #e0e0e0; + --background-error: var(--color-error); + --highlight-comment: #969896; + --highlight-default: #222222; + --highlight-keyword: #a71d5d; + --highlight-string: #183691; + --base-0: #fff; + --base-1: #f5f5f5; + --base-2: #e0e0e0; + --base-3: #ccc; + --base-4: #666; + --base-5: #444; + --base-6: #222; +} + +.theme-dark { + --page-background: #36393e; + --color-text: #e0e0e0; + --color-muted: #777; + --color-error: #d43934; + --tab-background: #555; + --tab-color: #ccc; + --tab-active-background: #888; + --tab-active-color: #fafafa; + --tab-disabled-background: var(--page-background); + --tab-disabled-color: #777; + --metric-value-background: #555; + --metric-value-color: inherit; + --metric-unit-color: #999; + --metric-label-background: #777; + --metric-label-color: #e0e0e0; + --trace-selected-background: #71663acc; + --table-border: #444; + --table-background: #333; + --table-header: #555; + --info-background: rgba(79, 148, 195, 0.5); + --tree-active-background: var(--metric-label-background); + --exception-title-color: var(--base-2); + --shadow: 0px 0px 1px rgba(32, 32, 32, .2); + --border: 1px solid #666; + --background-error: #b0413e; + --highlight-comment: #dedede; + --highlight-default: var(--base-6); + --highlight-keyword: #ff413c; + --highlight-string: #70a6fd; + --base-0: #2e3136; + --base-1: #444; + --base-2: #666; + --base-3: #666; + --base-4: #666; + --base-5: #e0e0e0; + --base-6: #f5f5f5; + --card-label-background: var(--tab-active-background); + --card-label-color: var(--tab-active-color); +} + +html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0} + +html { + /* always display the vertical scrollbar to avoid jumps when toggling contents */ + overflow-y: scroll; +} +body { background-color: var(--page-background); color: var(--base-6); font: 14px/1.4 Helvetica, Arial, sans-serif; padding-bottom: 45px; } + +a { cursor: pointer; text-decoration: none; } +a:hover { text-decoration: underline; } +abbr[title] { border-bottom: none; cursor: help; text-decoration: none; } + +code, pre { font: 13px/1.5 Consolas, Monaco, Menlo, "Ubuntu Mono", "Liberation Mono", monospace; } + +table, tr, th, td { background: var(--base-0); border-collapse: collapse; vertical-align: top; } +table { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; } +table th, table td { border: solid var(--base-2); border-width: 1px 0; padding: 8px 10px; } +table th { background-color: var(--base-2); font-weight: bold; text-align: left; } + +.m-t-5 { margin-top: 5px; } +.hidden-xs-down { display: none; } +.block { display: block; } +.full-width { width: 100%; } +.hidden { display: none; } +.prewrap { white-space: pre-wrap; } +.nowrap { white-space: nowrap; } +.newline { display: block; } +.break-long-words { word-wrap: break-word; overflow-wrap: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; min-width: 0; } +.text-small { font-size: 12px !important; } +.text-muted { color: #999; } +.text-bold { font-weight: bold; } +.empty { border: 4px dashed var(--base-2); color: #999; margin: 1em 0; padding: .5em 2em; } + +.status-success { background: rgba(94, 151, 110, 0.3); } +.status-warning { background: rgba(240, 181, 24, 0.3); } +.status-error { background: rgba(176, 65, 62, 0.2); } +.status-success td, .status-warning td, .status-error td { background: transparent; } +tr.status-error td, tr.status-warning td { border-bottom: 1px solid var(--base-2); border-top: 1px solid var(--base-2); } +.status-warning .colored { color: #A46A1F; } +.status-error .colored { color: var(--color-error); } + +.sf-toggle { cursor: pointer; position: relative; } +.sf-toggle-content { -moz-transition: display .25s ease; -webkit-transition: display .25s ease; transition: display .25s ease; } +.sf-toggle-content.sf-toggle-hidden { display: none; } +.sf-toggle-content.sf-toggle-visible { display: block; } +thead.sf-toggle-content.sf-toggle-visible, tbody.sf-toggle-content.sf-toggle-visible { display: table-row-group; } +.sf-toggle-off .icon-close, .sf-toggle-on .icon-open { display: none; } +.sf-toggle-off .icon-open, .sf-toggle-on .icon-close { display: block; } + +.tab-navigation { margin: 0 0 1em 0; padding: 0; } +.tab-navigation li { background: var(--tab-background); border: 1px solid var(--table-border); color: var(--tab-color); cursor: pointer; display: inline-block; font-size: 16px; margin: 0 0 0 -1px; padding: .5em .75em; z-index: 1; } +.tab-navigation li .badge { background-color: var(--base-1); color: var(--base-4); display: inline-block; font-size: 14px; font-weight: bold; margin-left: 8px; min-width: 10px; padding: 1px 6px; text-align: center; white-space: nowrap; } +.tab-navigation li.disabled { background: var(--tab-disabled-background); color: var(--tab-disabled-color); } +.tab-navigation li.active { background: var(--tab-active-background); color: var(--tab-active-color); z-index: 1100; } +.tab-navigation li.active .badge { background-color: var(--base-5); color: var(--base-2); } +.tab-content > *:first-child { margin-top: 0; } +.tab-navigation li .badge.status-warning { background: var(--color-warning); color: #FFF; } +.tab-navigation li .badge.status-error { background: var(--background-error); color: #FFF; } +.sf-tabs .tab:not(:first-child) { display: none; } + +[data-filters] { position: relative; } +[data-filtered] { cursor: pointer; } +[data-filtered]:after { content: '\00a0\25BE'; } +[data-filtered]:hover .filter-list li { display: inline-flex; } +[class*="filter-hidden-"] { display: none; } +.filter-list { position: absolute; border: var(--border); box-shadow: var(--shadow); margin: 0; padding: 0; display: flex; flex-direction: column; } +.filter-list :after { content: ''; } +.filter-list li { + background: var(--tab-disabled-background); + border-bottom: var(--border); + color: var(--tab-disabled-color); + display: none; + list-style: none; + margin: 0; + padding: 5px 10px; + text-align: left; + font-weight: normal; +} +.filter-list li.active { + background: var(--tab-background); + color: var(--tab-color); +} +.filter-list li.last-active { + background: var(--tab-active-background); + color: var(--tab-active-color); +} + +.filter-list-level li { cursor: s-resize; } +.filter-list-level li.active { cursor: n-resize; } +.filter-list-level li.last-active { cursor: default; } +.filter-list-level li.last-active:before { content: '\2714\00a0'; } +.filter-list-choice li:before { content: '\2714\00a0'; color: transparent; } +.filter-list-choice li.active:before { color: unset; } + +.container { max-width: 1024px; margin: 0 auto; padding: 0 15px; } +.container::after { content: ""; display: table; clear: both; } + +header { background-color: #222; color: rgba(255, 255, 255, 0.75); font-size: 13px; height: 33px; line-height: 33px; padding: 0; } +header .container { display: flex; justify-content: space-between; } +.logo { flex: 1; font-size: 13px; font-weight: normal; margin: 0; padding: 0; } +.logo svg { height: 18px; width: 18px; opacity: .8; vertical-align: -5px; } + +.help-link { margin-left: 15px; } +.help-link a { color: inherit; } +.help-link .icon svg { height: 15px; width: 15px; opacity: .7; vertical-align: -2px; } +.help-link a:hover { color: #EEE; text-decoration: none; } +.help-link a:hover svg { opacity: .9; } + +.exception-summary { background: var(--background-error); border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 15px; } +.exception-metadata { background: rgba(0, 0, 0, 0.1); padding: 7px 0; } +.exception-metadata .container { display: flex; flex-direction: row; justify-content: space-between; } +.exception-metadata h2, .exception-metadata h2 > a { color: rgba(255, 255, 255, 0.8); font-size: 13px; font-weight: 400; margin: 0; } +.exception-http small { font-size: 13px; opacity: .7; } +.exception-hierarchy { flex: 1; } +.exception-hierarchy .icon { margin: 0 3px; opacity: .7; } +.exception-hierarchy .icon svg { height: 13px; width: 13px; vertical-align: -2px; } + +.exception-without-message .exception-message-wrapper { display: none; } +.exception-message-wrapper .container { display: flex; align-items: flex-start; min-height: 70px; padding: 10px 15px 8px; } +.exception-message { flex-grow: 1; } +.exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; } +.exception-message.long { font-size: 18px; } +.exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; } +.exception-message a:hover { border-bottom-color: #ffffff; } + +.exception-illustration { flex-basis: 111px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; } + +.trace + .trace { margin-top: 30px; } +.trace-head { background-color: var(--base-2); padding: 10px; position: relative; } +.trace-head .trace-class { color: var(--base-6); font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; } +.trace-head .trace-namespace { color: #999; display: block; font-size: 13px; } +.trace-head .icon { position: absolute; right: 0; top: 0; } +.trace-head .icon svg { fill: var(--base-5); height: 24px; width: 24px; } + +.trace-details { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; table-layout: fixed; } + +.trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; } + +.trace-line { position: relative; padding-top: 8px; padding-bottom: 8px; } +.trace-line + .trace-line { border-top: var(--border); } +.trace-line:hover { background: var(--base-1); } +.trace-line a { color: var(--base-6); } +.trace-line .icon { opacity: .4; position: absolute; left: 10px; } +.trace-line .icon svg { fill: var(--base-5); height: 16px; width: 16px; } +.trace-line .icon.icon-copy { left: auto; top: auto; padding-left: 5px; display: none } +.trace-line:hover .icon.icon-copy:not(.hidden) { display: inline-block } +.trace-line-header { padding-left: 36px; padding-right: 10px; } + +.trace-file-path, .trace-file-path a { color: var(--base-6); font-size: 13px; } +.trace-class { color: var(--color-error); } +.trace-type { padding: 0 2px; } +.trace-method { color: var(--color-error); font-weight: bold; } +.trace-arguments { color: #777; font-weight: normal; padding-left: 2px; } + +.trace-code { background: var(--base-0); font-size: 12px; margin: 10px 10px 2px 10px; padding: 10px; overflow-x: auto; white-space: nowrap; } +.trace-code ol { margin: 0; float: left; } +.trace-code li { color: #969896; margin: 0; padding-left: 10px; float: left; width: 100%; } +.trace-code li + li { margin-top: 5px; } +.trace-code li.selected { background: var(--trace-selected-background); margin-top: 2px; } +.trace-code li code { color: var(--base-6); white-space: nowrap; } + +.trace-as-text .stacktrace { line-height: 1.8; margin: 0 0 15px; white-space: pre-wrap; } + +@media (min-width: 575px) { + .hidden-xs-down { display: initial; } + .help-link { margin-left: 30px; } +} diff --git a/lib/symfony/error-handler/Resources/assets/css/exception_full.css b/lib/symfony/error-handler/Resources/assets/css/exception_full.css new file mode 100644 index 0000000000..fa77cb3249 --- /dev/null +++ b/lib/symfony/error-handler/Resources/assets/css/exception_full.css @@ -0,0 +1,128 @@ +.sf-reset .traces { + padding-bottom: 14px; +} +.sf-reset .traces li { + font-size: 12px; + color: #868686; + padding: 5px 4px; + list-style-type: decimal; + margin-left: 20px; +} +.sf-reset #logs .traces li.error { + font-style: normal; + color: #AA3333; + background: #f9ecec; +} +.sf-reset #logs .traces li.warning { + font-style: normal; + background: #ffcc00; +} +/* fix for Opera not liking empty
                • */ +.sf-reset .traces li:after { + content: "\00A0"; +} +.sf-reset .trace { + border: 1px solid #D3D3D3; + padding: 10px; + overflow: auto; + margin: 10px 0 20px; +} +.sf-reset .block-exception { + -moz-border-radius: 16px; + -webkit-border-radius: 16px; + border-radius: 16px; + margin-bottom: 20px; + background-color: #f6f6f6; + border: 1px solid #dfdfdf; + padding: 30px 28px; + word-wrap: break-word; + overflow: hidden; +} +.sf-reset .block-exception div { + color: #313131; + font-size: 10px; +} +.sf-reset .block-exception-detected .illustration-exception, +.sf-reset .block-exception-detected .text-exception { + float: left; +} +.sf-reset .block-exception-detected .illustration-exception { + width: 152px; +} +.sf-reset .block-exception-detected .text-exception { + width: 670px; + padding: 30px 44px 24px 46px; + position: relative; +} +.sf-reset .text-exception .open-quote, +.sf-reset .text-exception .close-quote { + font-family: Arial, Helvetica, sans-serif; + position: absolute; + color: #C9C9C9; + font-size: 8em; +} +.sf-reset .open-quote { + top: 0; + left: 0; +} +.sf-reset .close-quote { + bottom: -0.5em; + right: 50px; +} +.sf-reset .block-exception p { + font-family: Arial, Helvetica, sans-serif; +} +.sf-reset .block-exception p a, +.sf-reset .block-exception p a:hover { + color: #565656; +} +.sf-reset .logs h2 { + float: left; + width: 654px; +} +.sf-reset .error-count, .sf-reset .support { + float: right; + width: 170px; + text-align: right; +} +.sf-reset .error-count span { + display: inline-block; + background-color: #aacd4e; + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + border-radius: 6px; + padding: 4px; + color: white; + margin-right: 2px; + font-size: 11px; + font-weight: bold; +} + +.sf-reset .support a { + display: inline-block; + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + border-radius: 6px; + padding: 4px; + color: #000000; + margin-right: 2px; + font-size: 11px; + font-weight: bold; +} + +.sf-reset .toggle { + vertical-align: middle; +} +.sf-reset .linked ul, +.sf-reset .linked li { + display: inline; +} +.sf-reset #output-content { + color: #000; + font-size: 12px; +} +.sf-reset #traces-text pre { + white-space: pre; + font-size: 12px; + font-family: monospace; +} diff --git a/lib/symfony/twig-bundle/Resources/views/images/chevron-right.svg b/lib/symfony/error-handler/Resources/assets/images/chevron-right.svg similarity index 100% rename from lib/symfony/twig-bundle/Resources/views/images/chevron-right.svg rename to lib/symfony/error-handler/Resources/assets/images/chevron-right.svg diff --git a/lib/symfony/twig-bundle/Resources/views/images/favicon.png.base64 b/lib/symfony/error-handler/Resources/assets/images/favicon.png.base64 similarity index 100% rename from lib/symfony/twig-bundle/Resources/views/images/favicon.png.base64 rename to lib/symfony/error-handler/Resources/assets/images/favicon.png.base64 diff --git a/lib/symfony/twig-bundle/Resources/views/images/icon-book.svg b/lib/symfony/error-handler/Resources/assets/images/icon-book.svg similarity index 100% rename from lib/symfony/twig-bundle/Resources/views/images/icon-book.svg rename to lib/symfony/error-handler/Resources/assets/images/icon-book.svg diff --git a/lib/symfony/error-handler/Resources/assets/images/icon-copy.svg b/lib/symfony/error-handler/Resources/assets/images/icon-copy.svg new file mode 100644 index 0000000000..844a4f99e3 --- /dev/null +++ b/lib/symfony/error-handler/Resources/assets/images/icon-copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/symfony/twig-bundle/Resources/views/images/icon-minus-square-o.svg b/lib/symfony/error-handler/Resources/assets/images/icon-minus-square-o.svg similarity index 100% rename from lib/symfony/twig-bundle/Resources/views/images/icon-minus-square-o.svg rename to lib/symfony/error-handler/Resources/assets/images/icon-minus-square-o.svg diff --git a/lib/symfony/twig-bundle/Resources/views/images/icon-minus-square.svg b/lib/symfony/error-handler/Resources/assets/images/icon-minus-square.svg similarity index 100% rename from lib/symfony/twig-bundle/Resources/views/images/icon-minus-square.svg rename to lib/symfony/error-handler/Resources/assets/images/icon-minus-square.svg diff --git a/lib/symfony/twig-bundle/Resources/views/images/icon-plus-square-o.svg b/lib/symfony/error-handler/Resources/assets/images/icon-plus-square-o.svg similarity index 100% rename from lib/symfony/twig-bundle/Resources/views/images/icon-plus-square-o.svg rename to lib/symfony/error-handler/Resources/assets/images/icon-plus-square-o.svg diff --git a/lib/symfony/twig-bundle/Resources/views/images/icon-plus-square.svg b/lib/symfony/error-handler/Resources/assets/images/icon-plus-square.svg similarity index 100% rename from lib/symfony/twig-bundle/Resources/views/images/icon-plus-square.svg rename to lib/symfony/error-handler/Resources/assets/images/icon-plus-square.svg diff --git a/lib/symfony/twig-bundle/Resources/views/images/icon-support.svg b/lib/symfony/error-handler/Resources/assets/images/icon-support.svg similarity index 100% rename from lib/symfony/twig-bundle/Resources/views/images/icon-support.svg rename to lib/symfony/error-handler/Resources/assets/images/icon-support.svg diff --git a/lib/symfony/error-handler/Resources/assets/images/symfony-ghost.svg.php b/lib/symfony/error-handler/Resources/assets/images/symfony-ghost.svg.php new file mode 100644 index 0000000000..4b2f9c1b9b --- /dev/null +++ b/lib/symfony/error-handler/Resources/assets/images/symfony-ghost.svg.php @@ -0,0 +1 @@ +addElementToGhost(); ?> diff --git a/lib/symfony/twig-bundle/Resources/views/images/symfony-logo.svg b/lib/symfony/error-handler/Resources/assets/images/symfony-logo.svg similarity index 100% rename from lib/symfony/twig-bundle/Resources/views/images/symfony-logo.svg rename to lib/symfony/error-handler/Resources/assets/images/symfony-logo.svg diff --git a/lib/symfony/error-handler/Resources/assets/js/exception.js b/lib/symfony/error-handler/Resources/assets/js/exception.js new file mode 100644 index 0000000000..a85409da3c --- /dev/null +++ b/lib/symfony/error-handler/Resources/assets/js/exception.js @@ -0,0 +1,297 @@ +/* This file is based on WebProfilerBundle/Resources/views/Profiler/base_js.html.twig. + If you make any change in this file, verify the same change is needed in the other file. */ +/* .tab'); + var tabNavigation = document.createElement('ul'); + tabNavigation.className = 'tab-navigation'; + + var selectedTabId = 'tab-' + i + '-0'; /* select the first tab by default */ + for (var j = 0; j < tabs.length; j++) { + var tabId = 'tab-' + i + '-' + j; + var tabTitle = tabs[j].querySelector('.tab-title').innerHTML; + + var tabNavigationItem = document.createElement('li'); + tabNavigationItem.setAttribute('data-tab-id', tabId); + if (hasClass(tabs[j], 'active')) { selectedTabId = tabId; } + if (hasClass(tabs[j], 'disabled')) { addClass(tabNavigationItem, 'disabled'); } + tabNavigationItem.innerHTML = tabTitle; + tabNavigation.appendChild(tabNavigationItem); + + var tabContent = tabs[j].querySelector('.tab-content'); + tabContent.parentElement.setAttribute('id', tabId); + } + + tabGroups[i].insertBefore(tabNavigation, tabGroups[i].firstChild); + addClass(document.querySelector('[data-tab-id="' + selectedTabId + '"]'), 'active'); + } + + /* display the active tab and add the 'click' event listeners */ + for (i = 0; i < tabGroups.length; i++) { + tabNavigation = tabGroups[i].querySelectorAll(':scope >.tab-navigation li'); + + for (j = 0; j < tabNavigation.length; j++) { + tabId = tabNavigation[j].getAttribute('data-tab-id'); + document.getElementById(tabId).querySelector('.tab-title').className = 'hidden'; + + if (hasClass(tabNavigation[j], 'active')) { + document.getElementById(tabId).className = 'block'; + } else { + document.getElementById(tabId).className = 'hidden'; + } + + tabNavigation[j].addEventListener('click', function(e) { + var activeTab = e.target || e.srcElement; + + /* needed because when the tab contains HTML contents, user can click */ + /* on any of those elements instead of their parent '
                • ' element */ + while (activeTab.tagName.toLowerCase() !== 'li') { + activeTab = activeTab.parentNode; + } + + /* get the full list of tabs through the parent of the active tab element */ + var tabNavigation = activeTab.parentNode.children; + for (var k = 0; k < tabNavigation.length; k++) { + var tabId = tabNavigation[k].getAttribute('data-tab-id'); + document.getElementById(tabId).className = 'hidden'; + removeClass(tabNavigation[k], 'active'); + } + + addClass(activeTab, 'active'); + var activeTabId = activeTab.getAttribute('data-tab-id'); + document.getElementById(activeTabId).className = 'block'; + }); + } + + tabGroups[i].setAttribute('data-processed', 'true'); + } + }, + + createToggles: function() { + var toggles = document.querySelectorAll('.sf-toggle:not([data-processed=true])'); + + for (var i = 0; i < toggles.length; i++) { + var elementSelector = toggles[i].getAttribute('data-toggle-selector'); + var element = document.querySelector(elementSelector); + + addClass(element, 'sf-toggle-content'); + + if (toggles[i].hasAttribute('data-toggle-initial') && toggles[i].getAttribute('data-toggle-initial') == 'display') { + addClass(toggles[i], 'sf-toggle-on'); + addClass(element, 'sf-toggle-visible'); + } else { + addClass(toggles[i], 'sf-toggle-off'); + addClass(element, 'sf-toggle-hidden'); + } + + addEventListener(toggles[i], 'click', function(e) { + e.preventDefault(); + + if ('' !== window.getSelection().toString()) { + /* Don't do anything on text selection */ + return; + } + + var toggle = e.target || e.srcElement; + + /* needed because when the toggle contains HTML contents, user can click */ + /* on any of those elements instead of their parent '.sf-toggle' element */ + while (!hasClass(toggle, 'sf-toggle')) { + toggle = toggle.parentNode; + } + + var element = document.querySelector(toggle.getAttribute('data-toggle-selector')); + + toggleClass(toggle, 'sf-toggle-on'); + toggleClass(toggle, 'sf-toggle-off'); + toggleClass(element, 'sf-toggle-hidden'); + toggleClass(element, 'sf-toggle-visible'); + + /* the toggle doesn't change its contents when clicking on it */ + if (!toggle.hasAttribute('data-toggle-alt-content')) { + return; + } + + if (!toggle.hasAttribute('data-toggle-original-content')) { + toggle.setAttribute('data-toggle-original-content', toggle.innerHTML); + } + + var currentContent = toggle.innerHTML; + var originalContent = toggle.getAttribute('data-toggle-original-content'); + var altContent = toggle.getAttribute('data-toggle-alt-content'); + toggle.innerHTML = currentContent !== altContent ? altContent : originalContent; + }); + + /* Prevents from disallowing clicks on links inside toggles */ + var toggleLinks = toggles[i].querySelectorAll('a'); + for (var j = 0; j < toggleLinks.length; j++) { + addEventListener(toggleLinks[j], 'click', function(e) { + e.stopPropagation(); + }); + } + + /* Prevents from disallowing clicks on "copy to clipboard" elements inside toggles */ + var copyToClipboardElements = toggles[i].querySelectorAll('span[data-clipboard-text]'); + for (var k = 0; k < copyToClipboardElements.length; k++) { + addEventListener(copyToClipboardElements[k], 'click', function(e) { + e.stopPropagation(); + }); + } + + toggles[i].setAttribute('data-processed', 'true'); + } + }, + + createFilters: function() { + document.querySelectorAll('[data-filters] [data-filter]').forEach(function (filter) { + var filters = filter.closest('[data-filters]'), + type = 'choice', + name = filter.dataset.filter, + ucName = name.charAt(0).toUpperCase()+name.slice(1), + list = document.createElement('ul'), + values = filters.dataset['filter'+ucName] || filters.querySelectorAll('[data-filter-'+name+']'), + labels = {}, + defaults = null, + indexed = {}, + processed = {}; + if (typeof values === 'string') { + type = 'level'; + labels = values.split(','); + values = values.toLowerCase().split(','); + defaults = values.length - 1; + } + addClass(list, 'filter-list'); + addClass(list, 'filter-list-'+type); + values.forEach(function (value, i) { + if (value instanceof HTMLElement) { + value = value.dataset['filter'+ucName]; + } + if (value in processed) { + return; + } + var option = document.createElement('li'), + label = i in labels ? labels[i] : value, + active = false, + matches; + if ('' === label) { + option.innerHTML = '(none)'; + } else { + option.innerText = label; + } + option.dataset.filter = value; + option.setAttribute('title', 1 === (matches = filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').length) ? 'Matches 1 row' : 'Matches '+matches+' rows'); + indexed[value] = i; + list.appendChild(option); + addEventListener(option, 'click', function () { + if ('choice' === type) { + filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) { + if (option.dataset.filter === row.dataset['filter'+ucName]) { + toggleClass(row, 'filter-hidden-'+name); + } + }); + toggleClass(option, 'active'); + } else if ('level' === type) { + if (i === this.parentNode.querySelectorAll('.active').length - 1) { + return; + } + this.parentNode.querySelectorAll('li').forEach(function (currentOption, j) { + if (j <= i) { + addClass(currentOption, 'active'); + if (i === j) { + addClass(currentOption, 'last-active'); + } else { + removeClass(currentOption, 'last-active'); + } + } else { + removeClass(currentOption, 'active'); + removeClass(currentOption, 'last-active'); + } + }); + filters.querySelectorAll('[data-filter-'+name+']').forEach(function (row) { + if (i < indexed[row.dataset['filter'+ucName]]) { + addClass(row, 'filter-hidden-'+name); + } else { + removeClass(row, 'filter-hidden-'+name); + } + }); + } + }); + if ('choice' === type) { + active = null === defaults || 0 <= defaults.indexOf(value); + } else if ('level' === type) { + active = i <= defaults; + if (active && i === defaults) { + addClass(option, 'last-active'); + } + } + if (active) { + addClass(option, 'active'); + } else { + filters.querySelectorAll('[data-filter-'+name+'="'+value+'"]').forEach(function (row) { + toggleClass(row, 'filter-hidden-'+name); + }); + } + processed[value] = true; + }); + + if (1 < list.childNodes.length) { + filter.appendChild(list); + filter.dataset.filtered = ''; + } + }); + } + }; + })(); + + Sfjs.addEventListener(document, 'DOMContentLoaded', function() { + Sfjs.createTabs(); + Sfjs.createToggles(); + Sfjs.createFilters(); + }); +} +/*]]>*/ diff --git a/lib/symfony/error-handler/Resources/bin/extract-tentative-return-types.php b/lib/symfony/error-handler/Resources/bin/extract-tentative-return-types.php new file mode 100644 index 0000000000..cc98f58b58 --- /dev/null +++ b/lib/symfony/error-handler/Resources/bin/extract-tentative-return-types.php @@ -0,0 +1,80 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +// Run from the root of the php-src repository, this script generates +// a table with all the methods that have a tentative return type. +// +// Usage: find -name *.stub.php | sort | /path/to/extract-tentative-return-types.php > /path/to/TentativeTypes.php + +echo << + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\Internal; + +/** + * This class has been generated by extract-tentative-return-types.php. + * + * @internal + */ +class TentativeTypes +{ + public const RETURN_TYPES = [ + +EOPHP; + +while (false !== $file = fgets(\STDIN)) { + $code = file_get_contents(substr($file, 0, -1)); + + if (!str_contains($code, '@tentative-return-type')) { + continue; + } + + $code = preg_split('{^\s*(?:(?:abstract )?class|interface|trait) ([^\s]++)}m', $code, -1, \PREG_SPLIT_DELIM_CAPTURE); + + if (1 === count($code)) { + continue; + } + + for ($i = 1; null !== $class = $code[$i] ?? null; $i += 2) { + $methods = $code[1 + $i]; + + if (!str_contains($methods, '@tentative-return-type')) { + continue; + } + + echo " '$class' => [\n"; + + preg_replace_callback('{@tentative-return-type.*?[\s]function ([^(]++)[^)]++\)\s*+:\s*+([^\n;\{]++)}s', function ($m) { + $m[2] = str_replace(' ', '', $m[2]); + echo " '$m[1]' => '$m[2]',\n"; + + return ''; + }, $methods); + + echo " ],\n"; + } +} + +echo << + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\in_array('-h', $argv) || \in_array('--help', $argv)) { + echo implode(PHP_EOL, [ + ' Patches type declarations based on "@return" PHPDoc and triggers deprecations for', + ' incompatible method declarations.', + '', + ' This assists you to make your package compatible with Symfony 6, but it can be used', + ' for any class/package.', + '', + ' Available configuration via environment variables:', + ' SYMFONY_PATCH_TYPE_DECLARATIONS', + ' An url-encoded string to change the behavior of the script. Available parameters:', + ' - "force": any value enables deprecation notices - can be any of:', + ' - "phpdoc" to patch only docblock annotations', + ' - "2" to add all possible return types', + ' - "1" to add return types but only to tests/final/internal/private methods', + ' - "php": the target version of PHP - e.g. "7.1" doesn\'t generate "object" types', + ' - "deprecations": "1" to trigger a deprecation notice when a child class misses a', + ' return type while the parent declares an "@return" annotation', + '', + ' SYMFONY_PATCH_TYPE_EXCLUDE', + ' A regex matched against the full path to the class - any match will be excluded', + '', + ' Example: "SYMFONY_PATCH_TYPE_DECLARATIONS=php=7.4 ./patch-type-declarations"', + ]); + exit; +} + +if (false === getenv('SYMFONY_PATCH_TYPE_DECLARATIONS')) { + putenv('SYMFONY_PATCH_TYPE_DECLARATIONS=force=2'); + echo 'No SYMFONY_PATCH_TYPE_DECLARATIONS env var set, patching type declarations in all methods (run the command with "-h" for more information).'.PHP_EOL; +} + +if (is_file($autoload = __DIR__.'/../../../../autoload.php')) { + // noop +} elseif (is_file($autoload = __DIR__.'/../../../../../../../autoload.php')) { + // noop +} else { + echo PHP_EOL.' /!\ Cannot find the Composer autoloader, did you forget to run "composer install"?'.PHP_EOL; + exit(1); +} + +if (is_file($phpunitAutoload = dirname($autoload).'/bin/.phpunit/phpunit/vendor/autoload.php')) { + require $phpunitAutoload; +} + +$loader = require $autoload; + +Symfony\Component\ErrorHandler\DebugClassLoader::enable(); + +$deprecations = []; +set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$deprecations) { + if (\E_USER_DEPRECATED !== $type) { + return; + } + + [,,,,, $class,] = explode('"', $msg); + $deprecations[$class][] = $msg; +}); + +$exclude = getenv('SYMFONY_PATCH_TYPE_EXCLUDE') ?: null; +foreach ($loader->getClassMap() as $class => $file) { + if (false !== strpos($file = realpath($file), \DIRECTORY_SEPARATOR.'vendor'.\DIRECTORY_SEPARATOR)) { + continue; + } + + if ($exclude && preg_match($exclude, $file)) { + continue; + } + + class_exists($class); +} + +Symfony\Component\ErrorHandler\DebugClassLoader::checkClasses(); + +foreach ($deprecations as $class => $classDeprecations) { + echo $class.' ('.\count($classDeprecations).')'.PHP_EOL; + echo implode(PHP_EOL, $classDeprecations).PHP_EOL.PHP_EOL; +} + +if ($deprecations && false !== strpos(getenv('SYMFONY_PATCH_TYPE_DECLARATIONS') ?? '', 'force')) { + echo 'These deprecations might be fixed by the patch script, run this again to check for type deprecations.'.PHP_EOL; +} diff --git a/lib/symfony/error-handler/Resources/views/error.html.php b/lib/symfony/error-handler/Resources/views/error.html.php new file mode 100644 index 0000000000..5416d03c8c --- /dev/null +++ b/lib/symfony/error-handler/Resources/views/error.html.php @@ -0,0 +1,20 @@ + + + + + + An Error Occurred: <?= $statusText; ?> + + + +
                  +

                  Oops! An Error Occurred

                  +

                  The server returned a " ".

                  + +

                  + Something is broken. Please let us know what you were doing when this error occurred. + We will fix it as soon as possible. Sorry for any inconvenience caused. +

                  +
                  + + diff --git a/lib/symfony/error-handler/Resources/views/exception.html.php b/lib/symfony/error-handler/Resources/views/exception.html.php new file mode 100644 index 0000000000..c3e7a8674e --- /dev/null +++ b/lib/symfony/error-handler/Resources/views/exception.html.php @@ -0,0 +1,116 @@ +
                  + + +
                  +
                  +

                  formatFileFromText(nl2br($exceptionMessage)); ?>

                  + +
                  + include('assets/images/symfony-ghost.svg.php'); ?> +
                  +
                  +
                  +
                  + +
                  +
                  +
                  + toArray(); + $exceptionWithUserCode = []; + $exceptionAsArrayCount = count($exceptionAsArray); + $last = $exceptionAsArrayCount - 1; + foreach ($exceptionAsArray as $i => $e) { + foreach ($e['trace'] as $trace) { + if ($trace['file'] && false === mb_strpos($trace['file'], '/vendor/') && false === mb_strpos($trace['file'], '/var/cache/') && $i < $last) { + $exceptionWithUserCode[] = $i; + } + } + } + ?> +

                  + 1) { ?> + Exceptions + + Exception + +

                  + +
                  + $e) { + echo $this->include('views/traces.html.php', [ + 'exception' => $e, + 'index' => $i + 1, + 'expand' => in_array($i, $exceptionWithUserCode, true) || ([] === $exceptionWithUserCode && 0 === $i), + ]); + } + ?> +
                  +
                  + + +
                  +

                  + Logs + countErrors()) { ?>countErrors(); ?> +

                  + +
                  + getLogs()) { ?> + include('views/logs.html.php', ['logs' => $logger->getLogs()]); ?> + +
                  +

                  No log messages

                  +
                  + +
                  +
                  + + +
                  +

                  + 1) { ?> + Stack Traces + + Stack Trace + +

                  + +
                  + $e) { + echo $this->include('views/traces_text.html.php', [ + 'exception' => $e, + 'index' => $i + 1, + 'numExceptions' => $exceptionAsArrayCount, + ]); + } + ?> +
                  +
                  + + +
                  +

                  Output content

                  + +
                  + +
                  +
                  + +
                  +
                  diff --git a/lib/symfony/error-handler/Resources/views/exception_full.html.php b/lib/symfony/error-handler/Resources/views/exception_full.html.php new file mode 100644 index 0000000000..04f0fd5798 --- /dev/null +++ b/lib/symfony/error-handler/Resources/views/exception_full.html.php @@ -0,0 +1,42 @@ + + + + + + + + <?= $_message; ?> + + + + + + + + +
                  +
                  +

                  include('assets/images/symfony-logo.svg'); ?> Symfony Exception

                  + + +
                  +
                  + + + include('views/exception.html.php', $context); ?> + + + + + diff --git a/lib/symfony/error-handler/Resources/views/logs.html.php b/lib/symfony/error-handler/Resources/views/logs.html.php new file mode 100644 index 0000000000..ea6e727b8c --- /dev/null +++ b/lib/symfony/error-handler/Resources/views/logs.html.php @@ -0,0 +1,45 @@ + + + + + + + + + + + + = 400) { + $status = 'error'; + } elseif ($log['priority'] >= 300) { + $status = 'warning'; + } else { + $severity = 0; + if (($exception = $log['context']['exception'] ?? null) instanceof \ErrorException || $exception instanceof \Symfony\Component\ErrorHandler\Exception\SilencedErrorContext) { + $severity = $exception->getSeverity(); + } + $status = \E_DEPRECATED === $severity || \E_USER_DEPRECATED === $severity ? 'warning' : 'normal'; + } ?> + data-filter-channel="escape($log['channel']); ?>"> + + + + + + + + +
                  LevelChannelMessage
                  + escape($log['priorityName']); ?> + + + escape($log['channel']); ?> + + formatLogMessage($log['message'], $log['context']); ?> + +
                  escape(json_encode($log['context'], \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES)); ?>
                  + +
                  diff --git a/lib/symfony/error-handler/Resources/views/trace.html.php b/lib/symfony/error-handler/Resources/views/trace.html.php new file mode 100644 index 0000000000..8dfdb4ec8e --- /dev/null +++ b/lib/symfony/error-handler/Resources/views/trace.html.php @@ -0,0 +1,43 @@ +
                  + + include('assets/images/icon-minus-square.svg'); ?> + include('assets/images/icon-plus-square.svg'); ?> + + + + abbrClass($trace['class']); ?>(formatArgs($trace['args']); ?>) + + + + getFileLink($trace['file'], $lineNumber); + $filePath = strtr(strip_tags($this->formatFile($trace['file'], $lineNumber)), [' at line '.$lineNumber => '']); + $filePathParts = explode(\DIRECTORY_SEPARATOR, $filePath); + ?> + + in + + + + + + + + (line ) + + + +
                  + +
                  + fileExcerpt($trace['file'], $trace['line'], 5), [ + '#DD0000' => 'var(--highlight-string)', + '#007700' => 'var(--highlight-keyword)', + '#0000BB' => 'var(--highlight-default)', + '#FF8000' => 'var(--highlight-comment)', + ]); ?> +
                  + diff --git a/lib/symfony/error-handler/Resources/views/traces.html.php b/lib/symfony/error-handler/Resources/views/traces.html.php new file mode 100644 index 0000000000..f64d917138 --- /dev/null +++ b/lib/symfony/error-handler/Resources/views/traces.html.php @@ -0,0 +1,51 @@ +
                  +
                  +
                  +
                  + include('assets/images/icon-minus-square-o.svg'); ?> + include('assets/images/icon-plus-square-o.svg'); ?> + + +
                  + +

                  + + + + +

                  + + 1) { ?> +

                  escape($exception['message']); ?>

                  + +
                  +
                  + +
                  + $trace) { + $isVendorTrace = $trace['file'] && (false !== mb_strpos($trace['file'], '/vendor/') || false !== mb_strpos($trace['file'], '/var/cache/')); + $displayCodeSnippet = $isFirstUserCode && !$isVendorTrace; + if ($displayCodeSnippet) { + $isFirstUserCode = false; + } ?> +
                  + include('views/trace.html.php', [ + 'prefix' => $index, + 'i' => $i, + 'trace' => $trace, + 'style' => $isVendorTrace ? 'compact' : ($displayCodeSnippet ? 'expanded' : ''), + ]); ?> +
                  + +
                  +
                  +
                  diff --git a/lib/symfony/error-handler/Resources/views/traces_text.html.php b/lib/symfony/error-handler/Resources/views/traces_text.html.php new file mode 100644 index 0000000000..6b478402c8 --- /dev/null +++ b/lib/symfony/error-handler/Resources/views/traces_text.html.php @@ -0,0 +1,43 @@ + + + + + + + + + + + + +
                  +
                  + 1) { ?> + [/] + + + include('assets/images/icon-minus-square-o.svg'); ?> + include('assets/images/icon-plus-square-o.svg'); ?> +
                  +
                  + +
                  +escape($exception['class']).":\n";
                  +                    if ($exception['message']) {
                  +                        echo $this->escape($exception['message'])."\n";
                  +                    }
                  +
                  +                    foreach ($exception['trace'] as $trace) {
                  +                        echo "\n  ";
                  +                        if ($trace['function']) {
                  +                            echo $this->escape('at '.$trace['class'].$trace['type'].$trace['function']).'('.(isset($trace['args']) ? $this->formatArgsAsText($trace['args']) : '').')';
                  +                        }
                  +                        if ($trace['file'] && $trace['line']) {
                  +                            echo($trace['function'] ? "\n     (" : 'at ').strtr(strip_tags($this->formatFile($trace['file'], $trace['line'])), [' at line '.$trace['line'] => '']).':'.$trace['line'].($trace['function'] ? ')' : '');
                  +                        }
                  +                    }
                  +?>
                  +                
                  + +
                  diff --git a/lib/symfony/error-handler/ThrowableUtils.php b/lib/symfony/error-handler/ThrowableUtils.php new file mode 100644 index 0000000000..18d04988ac --- /dev/null +++ b/lib/symfony/error-handler/ThrowableUtils.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler; + +use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; + +/** + * @internal + */ +class ThrowableUtils +{ + /** + * @param SilencedErrorContext|\Throwable + */ + public static function getSeverity($throwable): int + { + if ($throwable instanceof \ErrorException || $throwable instanceof SilencedErrorContext) { + return $throwable->getSeverity(); + } + + if ($throwable instanceof \ParseError) { + return \E_PARSE; + } + + if ($throwable instanceof \TypeError) { + return \E_RECOVERABLE_ERROR; + } + + return \E_ERROR; + } +} diff --git a/lib/symfony/error-handler/composer.json b/lib/symfony/error-handler/composer.json new file mode 100644 index 0000000000..bc0d88e9d6 --- /dev/null +++ b/lib/symfony/error-handler/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/error-handler", + "type": "library", + "description": "Provides tools to manage errors and ease debugging PHP code", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "require-dev": { + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/serializer": "^4.4|^5.0|^6.0", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\ErrorHandler\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "minimum-stability": "dev" +} diff --git a/lib/symfony/console/.gitignore b/lib/symfony/event-dispatcher-contracts/.gitignore similarity index 100% rename from lib/symfony/console/.gitignore rename to lib/symfony/event-dispatcher-contracts/.gitignore diff --git a/lib/symfony/event-dispatcher-contracts/CHANGELOG.md b/lib/symfony/event-dispatcher-contracts/CHANGELOG.md new file mode 100644 index 0000000000..7932e26132 --- /dev/null +++ b/lib/symfony/event-dispatcher-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/lib/symfony/event-dispatcher-contracts/Event.php b/lib/symfony/event-dispatcher-contracts/Event.php new file mode 100644 index 0000000000..46dcb2ba06 --- /dev/null +++ b/lib/symfony/event-dispatcher-contracts/Event.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\EventDispatcher; + +use Psr\EventDispatcher\StoppableEventInterface; + +/** + * Event is the base class for classes containing event data. + * + * This class contains no event data. It is used by events that do not pass + * state information to an event handler when an event is raised. + * + * You can call the method stopPropagation() to abort the execution of + * further listeners in your event listener. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * @author Nicolas Grekas + */ +class Event implements StoppableEventInterface +{ + private $propagationStopped = false; + + /** + * {@inheritdoc} + */ + public function isPropagationStopped(): bool + { + return $this->propagationStopped; + } + + /** + * Stops the propagation of the event to further event listeners. + * + * If multiple event listeners are connected to the same event, no + * further event listener will be triggered once any trigger calls + * stopPropagation(). + */ + public function stopPropagation(): void + { + $this->propagationStopped = true; + } +} diff --git a/lib/symfony/event-dispatcher-contracts/EventDispatcherInterface.php b/lib/symfony/event-dispatcher-contracts/EventDispatcherInterface.php new file mode 100644 index 0000000000..351dc51312 --- /dev/null +++ b/lib/symfony/event-dispatcher-contracts/EventDispatcherInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\EventDispatcher; + +use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; + +/** + * Allows providing hooks on domain-specific lifecycles by dispatching events. + */ +interface EventDispatcherInterface extends PsrEventDispatcherInterface +{ + /** + * Dispatches an event to all registered listeners. + * + * @param object $event The event to pass to the event handlers/listeners + * @param string|null $eventName The name of the event to dispatch. If not supplied, + * the class of $event should be used instead. + * + * @return object The passed $event MUST be returned + */ + public function dispatch(object $event, string $eventName = null): object; +} diff --git a/lib/symfony/event-dispatcher-contracts/LICENSE b/lib/symfony/event-dispatcher-contracts/LICENSE new file mode 100644 index 0000000000..74cdc2dbf6 --- /dev/null +++ b/lib/symfony/event-dispatcher-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2022 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/lib/symfony/event-dispatcher-contracts/README.md b/lib/symfony/event-dispatcher-contracts/README.md new file mode 100644 index 0000000000..b1ab4c00ce --- /dev/null +++ b/lib/symfony/event-dispatcher-contracts/README.md @@ -0,0 +1,9 @@ +Symfony EventDispatcher Contracts +================================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful - and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/lib/symfony/event-dispatcher-contracts/composer.json b/lib/symfony/event-dispatcher-contracts/composer.json new file mode 100644 index 0000000000..660df81a06 --- /dev/null +++ b/lib/symfony/event-dispatcher-contracts/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/event-dispatcher-contracts", + "type": "library", + "description": "Generic abstractions related to dispatching event", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "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.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\EventDispatcher\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/lib/symfony/event-dispatcher/.gitignore b/lib/symfony/event-dispatcher/.gitignore deleted file mode 100644 index c49a5d8df5..0000000000 --- a/lib/symfony/event-dispatcher/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/lib/symfony/event-dispatcher/Attribute/AsEventListener.php b/lib/symfony/event-dispatcher/Attribute/AsEventListener.php new file mode 100644 index 0000000000..bb931b82dc --- /dev/null +++ b/lib/symfony/event-dispatcher/Attribute/AsEventListener.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Attribute; + +/** + * Service tag to autoconfigure event listeners. + * + * @author Alexander M. Turek + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class AsEventListener +{ + public function __construct( + public ?string $event = null, + public ?string $method = null, + public int $priority = 0, + public ?string $dispatcher = null, + ) { + } +} diff --git a/lib/symfony/event-dispatcher/CHANGELOG.md b/lib/symfony/event-dispatcher/CHANGELOG.md index c6aa5389ac..0f98598950 100644 --- a/lib/symfony/event-dispatcher/CHANGELOG.md +++ b/lib/symfony/event-dispatcher/CHANGELOG.md @@ -1,24 +1,73 @@ CHANGELOG ========= +5.4 +--- + + * Allow `#[AsEventListener]` attribute on methods + +5.3 +--- + + * Add `#[AsEventListener]` attribute for declaring listeners on PHP 8 + +5.1.0 +----- + + * The `LegacyEventDispatcherProxy` class has been deprecated. + * Added an optional `dispatcher` attribute to the listener and subscriber tags in `RegisterListenerPass`. + +5.0.0 +----- + + * The signature of the `EventDispatcherInterface::dispatch()` method has been changed to `dispatch($event, string $eventName = null): object`. + * The `Event` class has been removed in favor of `Symfony\Contracts\EventDispatcher\Event`. + * The `TraceableEventDispatcherInterface` has been removed. + * The `WrappedListener` class is now final. + +4.4.0 +----- + + * `AddEventAliasesPass` has been added, allowing applications and bundles to extend the event alias mapping used by `RegisterListenersPass`. + * Made the `event` attribute of the `kernel.event_listener` tag optional for FQCN events. + +4.3.0 +----- + + * The signature of the `EventDispatcherInterface::dispatch()` method should be updated to `dispatch($event, string $eventName = null)`, not doing so is deprecated + * deprecated the `Event` class, use `Symfony\Contracts\EventDispatcher\Event` instead + +4.1.0 +----- + + * added support for invokable event listeners tagged with `kernel.event_listener` by default + * The `TraceableEventDispatcher::getOrphanedEvents()` method has been added. + * The `TraceableEventDispatcherInterface` has been deprecated. + +4.0.0 +----- + + * removed the `ContainerAwareEventDispatcher` class + * added the `reset()` method to the `TraceableEventDispatcherInterface` + 3.4.0 ----- - * Implementing `TraceableEventDispatcherInterface` without the `reset()` method has been deprecated. + * Implementing `TraceableEventDispatcherInterface` without the `reset()` method has been deprecated. 3.3.0 ----- - * The ContainerAwareEventDispatcher class has been deprecated. Use EventDispatcher with closure factories instead. + * The ContainerAwareEventDispatcher class has been deprecated. Use EventDispatcher with closure factories instead. 3.0.0 ----- - * The method `getListenerPriority($eventName, $listener)` has been added to the - `EventDispatcherInterface`. - * The methods `Event::setDispatcher()`, `Event::getDispatcher()`, `Event::setName()` - and `Event::getName()` have been removed. - The event dispatcher and the event name are passed to the listener call. + * The method `getListenerPriority($eventName, $listener)` has been added to the + `EventDispatcherInterface`. + * The methods `Event::setDispatcher()`, `Event::getDispatcher()`, `Event::setName()` + and `Event::getName()` have been removed. + The event dispatcher and the event name are passed to the listener call. 2.5.0 ----- diff --git a/lib/symfony/event-dispatcher/ContainerAwareEventDispatcher.php b/lib/symfony/event-dispatcher/ContainerAwareEventDispatcher.php deleted file mode 100644 index cbb9b12a6a..0000000000 --- a/lib/symfony/event-dispatcher/ContainerAwareEventDispatcher.php +++ /dev/null @@ -1,198 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\EventDispatcher; - -use PHPUnit\Framework\MockObject\MockObject; -use Symfony\Component\DependencyInjection\ContainerInterface; - -/** - * Lazily loads listeners and subscribers from the dependency injection - * container. - * - * @author Fabien Potencier - * @author Bernhard Schussek - * @author Jordan Alliot - * - * @deprecated since 3.3, to be removed in 4.0. Use EventDispatcher with closure factories instead. - */ -class ContainerAwareEventDispatcher extends EventDispatcher -{ - private $container; - - /** - * The service IDs of the event listeners and subscribers. - */ - private $listenerIds = []; - - /** - * The services registered as listeners. - */ - private $listeners = []; - - public function __construct(ContainerInterface $container) - { - $this->container = $container; - - $class = static::class; - if ($this instanceof \PHPUnit_Framework_MockObject_MockObject || $this instanceof MockObject || $this instanceof \Prophecy\Doubler\DoubleInterface) { - $class = get_parent_class($class); - } - if (__CLASS__ !== $class) { - @trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), \E_USER_DEPRECATED); - } - } - - /** - * Adds a service as event listener. - * - * @param string $eventName Event for which the listener is added - * @param array $callback The service ID of the listener service & the method - * name that has to be called - * @param int $priority The higher this value, the earlier an event listener - * will be triggered in the chain. - * Defaults to 0. - * - * @throws \InvalidArgumentException - */ - public function addListenerService($eventName, $callback, $priority = 0) - { - @trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), \E_USER_DEPRECATED); - - if (!\is_array($callback) || 2 !== \count($callback)) { - throw new \InvalidArgumentException('Expected an ["service", "method"] argument.'); - } - - $this->listenerIds[$eventName][] = [$callback[0], $callback[1], $priority]; - } - - public function removeListener($eventName, $listener) - { - $this->lazyLoad($eventName); - - if (isset($this->listenerIds[$eventName])) { - foreach ($this->listenerIds[$eventName] as $i => list($serviceId, $method)) { - $key = $serviceId.'.'.$method; - if (isset($this->listeners[$eventName][$key]) && $listener === [$this->listeners[$eventName][$key], $method]) { - unset($this->listeners[$eventName][$key]); - if (empty($this->listeners[$eventName])) { - unset($this->listeners[$eventName]); - } - unset($this->listenerIds[$eventName][$i]); - if (empty($this->listenerIds[$eventName])) { - unset($this->listenerIds[$eventName]); - } - } - } - } - - parent::removeListener($eventName, $listener); - } - - /** - * {@inheritdoc} - */ - public function hasListeners($eventName = null) - { - if (null === $eventName) { - return $this->listenerIds || $this->listeners || parent::hasListeners(); - } - - if (isset($this->listenerIds[$eventName])) { - return true; - } - - return parent::hasListeners($eventName); - } - - /** - * {@inheritdoc} - */ - public function getListeners($eventName = null) - { - if (null === $eventName) { - foreach ($this->listenerIds as $serviceEventName => $args) { - $this->lazyLoad($serviceEventName); - } - } else { - $this->lazyLoad($eventName); - } - - return parent::getListeners($eventName); - } - - /** - * {@inheritdoc} - */ - public function getListenerPriority($eventName, $listener) - { - $this->lazyLoad($eventName); - - return parent::getListenerPriority($eventName, $listener); - } - - /** - * Adds a service as event subscriber. - * - * @param string $serviceId The service ID of the subscriber service - * @param string $class The service's class name (which must implement EventSubscriberInterface) - */ - public function addSubscriberService($serviceId, $class) - { - @trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), \E_USER_DEPRECATED); - - foreach ($class::getSubscribedEvents() as $eventName => $params) { - if (\is_string($params)) { - $this->listenerIds[$eventName][] = [$serviceId, $params, 0]; - } elseif (\is_string($params[0])) { - $this->listenerIds[$eventName][] = [$serviceId, $params[0], isset($params[1]) ? $params[1] : 0]; - } else { - foreach ($params as $listener) { - $this->listenerIds[$eventName][] = [$serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0]; - } - } - } - } - - public function getContainer() - { - @trigger_error('The '.__METHOD__.'() method is deprecated since Symfony 3.3 as its class will be removed in 4.0. Inject the container or the services you need in your listeners/subscribers instead.', \E_USER_DEPRECATED); - - return $this->container; - } - - /** - * Lazily loads listeners for this event from the dependency injection - * container. - * - * @param string $eventName The name of the event to dispatch. The name of - * the event is the name of the method that is - * invoked on listeners. - */ - protected function lazyLoad($eventName) - { - if (isset($this->listenerIds[$eventName])) { - foreach ($this->listenerIds[$eventName] as list($serviceId, $method, $priority)) { - $listener = $this->container->get($serviceId); - - $key = $serviceId.'.'.$method; - if (!isset($this->listeners[$eventName][$key])) { - $this->addListener($eventName, [$listener, $method], $priority); - } elseif ($this->listeners[$eventName][$key] !== $listener) { - parent::removeListener($eventName, [$this->listeners[$eventName][$key], $method]); - $this->addListener($eventName, [$listener, $method], $priority); - } - - $this->listeners[$eventName][$key] = $listener; - } - } - } -} diff --git a/lib/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/lib/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php index 017459723d..acfbf619c4 100644 --- a/lib/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php +++ b/lib/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php @@ -11,11 +11,14 @@ namespace Symfony\Component\EventDispatcher\Debug; +use Psr\EventDispatcher\StoppableEventInterface; use Psr\Log\LoggerInterface; -use Symfony\Component\EventDispatcher\Event; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Contracts\Service\ResetInterface; /** * Collects some data about event listeners. @@ -24,27 +27,35 @@ use Symfony\Component\Stopwatch\Stopwatch; * * @author Fabien Potencier */ -class TraceableEventDispatcher implements TraceableEventDispatcherInterface +class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterface { protected $logger; protected $stopwatch; + /** + * @var \SplObjectStorage + */ private $callStack; private $dispatcher; private $wrappedListeners; + private $orphanedEvents; + private $requestStack; + private $currentRequestHash = ''; - public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null) + public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null, RequestStack $requestStack = null) { $this->dispatcher = $dispatcher; $this->stopwatch = $stopwatch; $this->logger = $logger; $this->wrappedListeners = []; + $this->orphanedEvents = []; + $this->requestStack = $requestStack; } /** * {@inheritdoc} */ - public function addListener($eventName, $listener, $priority = 0) + public function addListener(string $eventName, $listener, int $priority = 0) { $this->dispatcher->addListener($eventName, $listener, $priority); } @@ -60,11 +71,11 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface /** * {@inheritdoc} */ - public function removeListener($eventName, $listener) + public function removeListener(string $eventName, $listener) { if (isset($this->wrappedListeners[$eventName])) { foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { - if ($wrappedListener->getWrappedListener() === $listener) { + if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) { $listener = $wrappedListener; unset($this->wrappedListeners[$eventName][$index]); break; @@ -86,7 +97,7 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface /** * {@inheritdoc} */ - public function getListeners($eventName = null) + public function getListeners(string $eventName = null) { return $this->dispatcher->getListeners($eventName); } @@ -94,13 +105,13 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface /** * {@inheritdoc} */ - public function getListenerPriority($eventName, $listener) + public function getListenerPriority(string $eventName, $listener) { // we might have wrapped listeners for the event (if called while dispatching) // in that case get the priority by wrapper if (isset($this->wrappedListeners[$eventName])) { - foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { - if ($wrappedListener->getWrappedListener() === $listener) { + foreach ($this->wrappedListeners[$eventName] as $wrappedListener) { + if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) { return $this->dispatcher->getListenerPriority($eventName, $wrappedListener); } } @@ -112,7 +123,7 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface /** * {@inheritdoc} */ - public function hasListeners($eventName = null) + public function hasListeners(string $eventName = null) { return $this->dispatcher->hasListeners($eventName); } @@ -120,36 +131,37 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface /** * {@inheritdoc} */ - public function dispatch($eventName, Event $event = null) + public function dispatch(object $event, string $eventName = null): object { + $eventName = $eventName ?? \get_class($event); + if (null === $this->callStack) { $this->callStack = new \SplObjectStorage(); } - if (null === $event) { - $event = new Event(); - } + $currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : ''; - if (null !== $this->logger && $event->isPropagationStopped()) { + if (null !== $this->logger && $event instanceof StoppableEventInterface && $event->isPropagationStopped()) { $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName)); } $this->preProcess($eventName); try { - $this->preDispatch($eventName, $event); + $this->beforeDispatch($eventName, $event); try { $e = $this->stopwatch->start($eventName, 'section'); try { - $this->dispatcher->dispatch($eventName, $event); + $this->dispatcher->dispatch($event, $eventName); } finally { if ($e->isStarted()) { $e->stop(); } } } finally { - $this->postDispatch($eventName, $event); + $this->afterDispatch($eventName, $event); } } finally { + $this->currentRequestHash = $currentRequestHash; $this->postProcess($eventName); } @@ -157,28 +169,30 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface } /** - * {@inheritdoc} + * @return array */ - public function getCalledListeners() + public function getCalledListeners(Request $request = null) { if (null === $this->callStack) { return []; } + $hash = $request ? spl_object_hash($request) : null; $called = []; foreach ($this->callStack as $listener) { - list($eventName) = $this->callStack->getInfo(); - - $called[] = $listener->getInfo($eventName); + [$eventName, $requestHash] = $this->callStack->getInfo(); + if (null === $hash || $hash === $requestHash) { + $called[] = $listener->getInfo($eventName); + } } return $called; } /** - * {@inheritdoc} + * @return array */ - public function getNotCalledListeners() + public function getNotCalledListeners(Request $request = null) { try { $allListeners = $this->getListeners(); @@ -191,11 +205,16 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface return []; } + $hash = $request ? spl_object_hash($request) : null; $calledListeners = []; if (null !== $this->callStack) { foreach ($this->callStack as $calledListener) { - $calledListeners[] = $calledListener->getWrappedListener(); + [, $requestHash] = $this->callStack->getInfo(); + + if (null === $hash || $hash === $requestHash) { + $calledListeners[] = $calledListener->getWrappedListener(); + } } } @@ -216,9 +235,24 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface return $notCalled; } + public function getOrphanedEvents(Request $request = null): array + { + if ($request) { + return $this->orphanedEvents[spl_object_hash($request)] ?? []; + } + + if (!$this->orphanedEvents) { + return []; + } + + return array_merge(...array_values($this->orphanedEvents)); + } + public function reset() { $this->callStack = null; + $this->orphanedEvents = []; + $this->currentRequestHash = ''; } /** @@ -229,44 +263,44 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface * * @return mixed */ - public function __call($method, $arguments) + public function __call(string $method, array $arguments) { - return \call_user_func_array([$this->dispatcher, $method], $arguments); + return $this->dispatcher->{$method}(...$arguments); } /** * Called before dispatching the event. - * - * @param string $eventName The event name - * @param Event $event The event */ - protected function preDispatch($eventName, Event $event) + protected function beforeDispatch(string $eventName, object $event) { } /** * Called after dispatching the event. - * - * @param string $eventName The event name - * @param Event $event The event */ - protected function postDispatch($eventName, Event $event) + protected function afterDispatch(string $eventName, object $event) { } - private function preProcess($eventName) + private function preProcess(string $eventName): void { + if (!$this->dispatcher->hasListeners($eventName)) { + $this->orphanedEvents[$this->currentRequestHash][] = $eventName; + + return; + } + foreach ($this->dispatcher->getListeners($eventName) as $listener) { $priority = $this->getListenerPriority($eventName, $listener); $wrappedListener = new WrappedListener($listener instanceof WrappedListener ? $listener->getWrappedListener() : $listener, null, $this->stopwatch, $this); $this->wrappedListeners[$eventName][] = $wrappedListener; $this->dispatcher->removeListener($eventName, $listener); $this->dispatcher->addListener($eventName, $wrappedListener, $priority); - $this->callStack->attach($wrappedListener, [$eventName]); + $this->callStack->attach($wrappedListener, [$eventName, $this->currentRequestHash]); } } - private function postProcess($eventName) + private function postProcess(string $eventName): void { unset($this->wrappedListeners[$eventName]); $skipped = false; diff --git a/lib/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php b/lib/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php deleted file mode 100644 index f0212753be..0000000000 --- a/lib/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\EventDispatcher\Debug; - -use Symfony\Component\EventDispatcher\EventDispatcherInterface; - -/** - * @author Fabien Potencier - * - * @method reset() Resets the trace. - */ -interface TraceableEventDispatcherInterface extends EventDispatcherInterface -{ - /** - * Gets the called listeners. - * - * @return array An array of called listeners - */ - public function getCalledListeners(); - - /** - * Gets the not called listeners. - * - * @return array An array of not called listeners - */ - public function getNotCalledListeners(); -} diff --git a/lib/symfony/event-dispatcher/Debug/WrappedListener.php b/lib/symfony/event-dispatcher/Debug/WrappedListener.php index de2b850953..3916716ec0 100644 --- a/lib/symfony/event-dispatcher/Debug/WrappedListener.php +++ b/lib/symfony/event-dispatcher/Debug/WrappedListener.php @@ -11,7 +11,7 @@ namespace Symfony\Component\EventDispatcher\Debug; -use Symfony\Component\EventDispatcher\Event; +use Psr\EventDispatcher\StoppableEventInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\VarDumper\Caster\ClassStub; @@ -19,9 +19,10 @@ use Symfony\Component\VarDumper\Caster\ClassStub; /** * @author Fabien Potencier */ -class WrappedListener +final class WrappedListener { private $listener; + private $optimizedListener; private $name; private $called; private $stoppedPropagation; @@ -32,20 +33,21 @@ class WrappedListener private $priority; private static $hasClassStub; - public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null) + public function __construct($listener, ?string $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null) { $this->listener = $listener; + $this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? \Closure::fromCallable($listener) : null); $this->stopwatch = $stopwatch; $this->dispatcher = $dispatcher; $this->called = false; $this->stoppedPropagation = false; if (\is_array($listener)) { - $this->name = \is_object($listener[0]) ? \get_class($listener[0]) : $listener[0]; + $this->name = \is_object($listener[0]) ? get_debug_type($listener[0]) : $listener[0]; $this->pretty = $this->name.'::'.$listener[1]; } elseif ($listener instanceof \Closure) { $r = new \ReflectionFunction($listener); - if (false !== strpos($r->name, '{closure}')) { + if (str_contains($r->name, '{closure}')) { $this->pretty = $this->name = 'closure'; } elseif ($class = $r->getClosureScopeClass()) { $this->name = $class->name; @@ -56,7 +58,7 @@ class WrappedListener } elseif (\is_string($listener)) { $this->pretty = $this->name = $listener; } else { - $this->name = \get_class($listener); + $this->name = get_debug_type($listener); $this->pretty = $this->name.'::__invoke'; } @@ -74,22 +76,22 @@ class WrappedListener return $this->listener; } - public function wasCalled() + public function wasCalled(): bool { return $this->called; } - public function stoppedPropagation() + public function stoppedPropagation(): bool { return $this->stoppedPropagation; } - public function getPretty() + public function getPretty(): string { return $this->pretty; } - public function getInfo($eventName) + public function getInfo(string $eventName): array { if (null === $this->stub) { $this->stub = self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->listener) : $this->pretty.'()'; @@ -103,7 +105,7 @@ class WrappedListener ]; } - public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher) + public function __invoke(object $event, string $eventName, EventDispatcherInterface $dispatcher): void { $dispatcher = $this->dispatcher ?: $dispatcher; @@ -112,13 +114,13 @@ class WrappedListener $e = $this->stopwatch->start($this->name, 'event_listener'); - \call_user_func($this->listener, $event, $eventName, $dispatcher); + ($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher); if ($e->isStarted()) { $e->stop(); } - if ($event->isPropagationStopped()) { + if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) { $this->stoppedPropagation = true; } } diff --git a/lib/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php b/lib/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php new file mode 100644 index 0000000000..6e7292b4a1 --- /dev/null +++ b/lib/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * This pass allows bundles to extend the list of event aliases. + * + * @author Alexander M. Turek + */ +class AddEventAliasesPass implements CompilerPassInterface +{ + private $eventAliases; + private $eventAliasesParameter; + + public function __construct(array $eventAliases, string $eventAliasesParameter = 'event_dispatcher.event_aliases') + { + if (1 < \func_num_args()) { + trigger_deprecation('symfony/event-dispatcher', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->eventAliases = $eventAliases; + $this->eventAliasesParameter = $eventAliasesParameter; + } + + public function process(ContainerBuilder $container): void + { + $eventAliases = $container->hasParameter($this->eventAliasesParameter) ? $container->getParameter($this->eventAliasesParameter) : []; + + $container->setParameter( + $this->eventAliasesParameter, + array_merge($eventAliases, $this->eventAliases) + ); + } +} diff --git a/lib/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/lib/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php index 2951c1ee45..8eabe7d741 100644 --- a/lib/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php +++ b/lib/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php @@ -18,6 +18,7 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Contracts\EventDispatcher\Event; /** * Compiler pass to register tagged services for an event dispatcher. @@ -27,26 +28,51 @@ class RegisterListenersPass implements CompilerPassInterface protected $dispatcherService; protected $listenerTag; protected $subscriberTag; + protected $eventAliasesParameter; private $hotPathEvents = []; - private $hotPathTagName; + private $hotPathTagName = 'container.hot_path'; + private $noPreloadEvents = []; + private $noPreloadTagName = 'container.no_preload'; - /** - * @param string $dispatcherService Service name of the event dispatcher in processed container - * @param string $listenerTag Tag name used for listener - * @param string $subscriberTag Tag name used for subscribers - */ - public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'kernel.event_listener', $subscriberTag = 'kernel.event_subscriber') + public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber', string $eventAliasesParameter = 'event_dispatcher.event_aliases') { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/event-dispatcher', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + $this->dispatcherService = $dispatcherService; $this->listenerTag = $listenerTag; $this->subscriberTag = $subscriberTag; + $this->eventAliasesParameter = $eventAliasesParameter; } - public function setHotPathEvents(array $hotPathEvents, $tagName = 'container.hot_path') + /** + * @return $this + */ + public function setHotPathEvents(array $hotPathEvents) { $this->hotPathEvents = array_flip($hotPathEvents); - $this->hotPathTagName = $tagName; + + if (1 < \func_num_args()) { + trigger_deprecation('symfony/event-dispatcher', '5.4', 'Configuring "$tagName" in "%s" is deprecated.', __METHOD__); + $this->hotPathTagName = func_get_arg(1); + } + + return $this; + } + + /** + * @return $this + */ + public function setNoPreloadEvents(array $noPreloadEvents): self + { + $this->noPreloadEvents = array_flip($noPreloadEvents); + + if (1 < \func_num_args()) { + trigger_deprecation('symfony/event-dispatcher', '5.4', 'Configuring "$tagName" in "%s" is deprecated.', __METHOD__); + $this->noPreloadTagName = func_get_arg(1); + } return $this; } @@ -57,35 +83,65 @@ class RegisterListenersPass implements CompilerPassInterface return; } - $definition = $container->findDefinition($this->dispatcherService); + $aliases = []; + + if ($container->hasParameter($this->eventAliasesParameter)) { + $aliases = $container->getParameter($this->eventAliasesParameter); + } + + $globalDispatcherDefinition = $container->findDefinition($this->dispatcherService); foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) { + $noPreload = 0; + foreach ($events as $event) { - $priority = isset($event['priority']) ? $event['priority'] : 0; + $priority = $event['priority'] ?? 0; if (!isset($event['event'])) { - throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag)); + if ($container->getDefinition($id)->hasTag($this->subscriberTag)) { + continue; + } + + $event['method'] = $event['method'] ?? '__invoke'; + $event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']); } + $event['event'] = $aliases[$event['event']] ?? $event['event']; + if (!isset($event['method'])) { $event['method'] = 'on'.preg_replace_callback([ - '/(?<=\b)[a-z]/i', + '/(?<=\b|_)[a-z]/i', '/[^a-z0-9]/i', ], function ($matches) { return strtoupper($matches[0]); }, $event['event']); $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); + + if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method']) && $r->hasMethod('__invoke')) { + $event['method'] = '__invoke'; + } } - $definition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]); + $dispatcherDefinition = $globalDispatcherDefinition; + if (isset($event['dispatcher'])) { + $dispatcherDefinition = $container->getDefinition($event['dispatcher']); + } + + $dispatcherDefinition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]); if (isset($this->hotPathEvents[$event['event']])) { $container->getDefinition($id)->addTag($this->hotPathTagName); + } elseif (isset($this->noPreloadEvents[$event['event']])) { + ++$noPreload; } } + + if ($noPreload && \count($events) === $noPreload) { + $container->getDefinition($id)->addTag($this->noPreloadTagName); + } } $extractingDispatcher = new ExtractingEventDispatcher(); - foreach ($container->findTaggedServiceIds($this->subscriberTag, true) as $id => $attributes) { + foreach ($container->findTaggedServiceIds($this->subscriberTag, true) as $id => $tags) { $def = $container->getDefinition($id); // We must assume that the class value has been correctly filled, even if the service is created by a factory @@ -99,19 +155,59 @@ class RegisterListenersPass implements CompilerPassInterface } $class = $r->name; + $dispatcherDefinitions = []; + foreach ($tags as $attributes) { + if (!isset($attributes['dispatcher']) || isset($dispatcherDefinitions[$attributes['dispatcher']])) { + continue; + } + + $dispatcherDefinitions[$attributes['dispatcher']] = $container->getDefinition($attributes['dispatcher']); + } + + if (!$dispatcherDefinitions) { + $dispatcherDefinitions = [$globalDispatcherDefinition]; + } + + $noPreload = 0; + ExtractingEventDispatcher::$aliases = $aliases; ExtractingEventDispatcher::$subscriber = $class; $extractingDispatcher->addSubscriber($extractingDispatcher); foreach ($extractingDispatcher->listeners as $args) { $args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]]; - $definition->addMethodCall('addListener', $args); + foreach ($dispatcherDefinitions as $dispatcherDefinition) { + $dispatcherDefinition->addMethodCall('addListener', $args); + } if (isset($this->hotPathEvents[$args[0]])) { $container->getDefinition($id)->addTag($this->hotPathTagName); + } elseif (isset($this->noPreloadEvents[$args[0]])) { + ++$noPreload; } } + if ($noPreload && \count($extractingDispatcher->listeners) === $noPreload) { + $container->getDefinition($id)->addTag($this->noPreloadTagName); + } $extractingDispatcher->listeners = []; + ExtractingEventDispatcher::$aliases = []; } } + + private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): string + { + if ( + null === ($class = $container->getDefinition($id)->getClass()) + || !($r = $container->getReflectionClass($class, false)) + || !$r->hasMethod($method) + || 1 > ($m = $r->getMethod($method))->getNumberOfParameters() + || !($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType + || $type->isBuiltin() + || Event::class === ($name = $type->getName()) + ) { + throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag)); + } + + return $name; + } } /** @@ -121,17 +217,22 @@ class ExtractingEventDispatcher extends EventDispatcher implements EventSubscrib { public $listeners = []; + public static $aliases = []; public static $subscriber; - public function addListener($eventName, $listener, $priority = 0) + public function addListener(string $eventName, $listener, int $priority = 0) { $this->listeners[] = [$eventName, $listener[1], $priority]; } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { - $callback = [self::$subscriber, 'getSubscribedEvents']; + $events = []; - return $callback(); + foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) { + $events[self::$aliases[$eventName] ?? $eventName] = $params; + } + + return $events; } } diff --git a/lib/symfony/event-dispatcher/Event.php b/lib/symfony/event-dispatcher/Event.php deleted file mode 100644 index 9c56b2f55b..0000000000 --- a/lib/symfony/event-dispatcher/Event.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\EventDispatcher; - -/** - * Event is the base class for classes containing event data. - * - * This class contains no event data. It is used by events that do not pass - * state information to an event handler when an event is raised. - * - * You can call the method stopPropagation() to abort the execution of - * further listeners in your event listener. - * - * @author Guilherme Blanco - * @author Jonathan Wage - * @author Roman Borschel - * @author Bernhard Schussek - */ -class Event -{ - /** - * @var bool Whether no further event listeners should be triggered - */ - private $propagationStopped = false; - - /** - * Returns whether further event listeners should be triggered. - * - * @see Event::stopPropagation() - * - * @return bool Whether propagation was already stopped for this event - */ - public function isPropagationStopped() - { - return $this->propagationStopped; - } - - /** - * Stops the propagation of the event to further event listeners. - * - * If multiple event listeners are connected to the same event, no - * further event listener will be triggered once any trigger calls - * stopPropagation(). - */ - public function stopPropagation() - { - $this->propagationStopped = true; - } -} diff --git a/lib/symfony/event-dispatcher/EventDispatcher.php b/lib/symfony/event-dispatcher/EventDispatcher.php index 207790f06b..8fe8fb5c29 100644 --- a/lib/symfony/event-dispatcher/EventDispatcher.php +++ b/lib/symfony/event-dispatcher/EventDispatcher.php @@ -11,6 +11,9 @@ namespace Symfony\Component\EventDispatcher; +use Psr\EventDispatcher\StoppableEventInterface; +use Symfony\Component\EventDispatcher\Debug\WrappedListener; + /** * The EventDispatcherInterface is the central point of Symfony's event listener system. * @@ -30,18 +33,30 @@ class EventDispatcher implements EventDispatcherInterface { private $listeners = []; private $sorted = []; + private $optimized; + + public function __construct() + { + if (__CLASS__ === static::class) { + $this->optimized = []; + } + } /** * {@inheritdoc} */ - public function dispatch($eventName, Event $event = null) + public function dispatch(object $event, string $eventName = null): object { - if (null === $event) { - $event = new Event(); + $eventName = $eventName ?? \get_class($event); + + if (null !== $this->optimized) { + $listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName)); + } else { + $listeners = $this->getListeners($eventName); } - if ($listeners = $this->getListeners($eventName)) { - $this->doDispatch($listeners, $eventName, $event); + if ($listeners) { + $this->callListeners($listeners, $eventName, $event); } return $event; @@ -50,7 +65,7 @@ class EventDispatcher implements EventDispatcherInterface /** * {@inheritdoc} */ - public function getListeners($eventName = null) + public function getListeners(string $eventName = null) { if (null !== $eventName) { if (empty($this->listeners[$eventName])) { @@ -76,23 +91,24 @@ class EventDispatcher implements EventDispatcherInterface /** * {@inheritdoc} */ - public function getListenerPriority($eventName, $listener) + public function getListenerPriority(string $eventName, $listener) { if (empty($this->listeners[$eventName])) { return null; } - if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) { + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { $listener[0] = $listener[0](); + $listener[1] = $listener[1] ?? '__invoke'; } - foreach ($this->listeners[$eventName] as $priority => $listeners) { - foreach ($listeners as $k => $v) { - if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure) { + foreach ($this->listeners[$eventName] as $priority => &$listeners) { + foreach ($listeners as &$v) { + if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) { $v[0] = $v[0](); - $this->listeners[$eventName][$priority][$k] = $v; + $v[1] = $v[1] ?? '__invoke'; } - if ($v === $listener) { + if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) { return $priority; } } @@ -104,7 +120,7 @@ class EventDispatcher implements EventDispatcherInterface /** * {@inheritdoc} */ - public function hasListeners($eventName = null) + public function hasListeners(string $eventName = null) { if (null !== $eventName) { return !empty($this->listeners[$eventName]); @@ -122,40 +138,38 @@ class EventDispatcher implements EventDispatcherInterface /** * {@inheritdoc} */ - public function addListener($eventName, $listener, $priority = 0) + public function addListener(string $eventName, $listener, int $priority = 0) { $this->listeners[$eventName][$priority][] = $listener; - unset($this->sorted[$eventName]); + unset($this->sorted[$eventName], $this->optimized[$eventName]); } /** * {@inheritdoc} */ - public function removeListener($eventName, $listener) + public function removeListener(string $eventName, $listener) { if (empty($this->listeners[$eventName])) { return; } - if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) { + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { $listener[0] = $listener[0](); + $listener[1] = $listener[1] ?? '__invoke'; } - foreach ($this->listeners[$eventName] as $priority => $listeners) { - foreach ($listeners as $k => $v) { - if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure) { + foreach ($this->listeners[$eventName] as $priority => &$listeners) { + foreach ($listeners as $k => &$v) { + if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) { $v[0] = $v[0](); + $v[1] = $v[1] ?? '__invoke'; } - if ($v === $listener) { - unset($listeners[$k], $this->sorted[$eventName]); - } else { - $listeners[$k] = $v; + if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) { + unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]); } } - if ($listeners) { - $this->listeners[$eventName][$priority] = $listeners; - } else { + if (!$listeners) { unset($this->listeners[$eventName][$priority]); } } @@ -170,10 +184,10 @@ class EventDispatcher implements EventDispatcherInterface if (\is_string($params)) { $this->addListener($eventName, [$subscriber, $params]); } elseif (\is_string($params[0])) { - $this->addListener($eventName, [$subscriber, $params[0]], isset($params[1]) ? $params[1] : 0); + $this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0); } else { foreach ($params as $listener) { - $this->addListener($eventName, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0); + $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0); } } } @@ -203,36 +217,64 @@ class EventDispatcher implements EventDispatcherInterface * * @param callable[] $listeners The event listeners * @param string $eventName The name of the event to dispatch - * @param Event $event The event object to pass to the event handlers/listeners + * @param object $event The event object to pass to the event handlers/listeners */ - protected function doDispatch($listeners, $eventName, Event $event) + protected function callListeners(iterable $listeners, string $eventName, object $event) { + $stoppable = $event instanceof StoppableEventInterface; + foreach ($listeners as $listener) { - if ($event->isPropagationStopped()) { + if ($stoppable && $event->isPropagationStopped()) { break; } - \call_user_func($listener, $event, $eventName, $this); + $listener($event, $eventName, $this); } } /** * Sorts the internal list of listeners for the given event by priority. - * - * @param string $eventName The name of the event */ - private function sortListeners($eventName) + private function sortListeners(string $eventName) { krsort($this->listeners[$eventName]); $this->sorted[$eventName] = []; - foreach ($this->listeners[$eventName] as $priority => $listeners) { - foreach ($listeners as $k => $listener) { - if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) { + foreach ($this->listeners[$eventName] as &$listeners) { + foreach ($listeners as $k => &$listener) { + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { $listener[0] = $listener[0](); - $this->listeners[$eventName][$priority][$k] = $listener; + $listener[1] = $listener[1] ?? '__invoke'; } $this->sorted[$eventName][] = $listener; } } } + + /** + * Optimizes the internal list of listeners for the given event by priority. + */ + private function optimizeListeners(string $eventName): array + { + krsort($this->listeners[$eventName]); + $this->optimized[$eventName] = []; + + foreach ($this->listeners[$eventName] as &$listeners) { + foreach ($listeners as &$listener) { + $closure = &$this->optimized[$eventName][]; + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) { + $closure = static function (...$args) use (&$listener, &$closure) { + if ($listener[0] instanceof \Closure) { + $listener[0] = $listener[0](); + $listener[1] = $listener[1] ?? '__invoke'; + } + ($closure = \Closure::fromCallable($listener))(...$args); + }; + } else { + $closure = $listener instanceof \Closure || $listener instanceof WrappedListener ? $listener : \Closure::fromCallable($listener); + } + } + } + + return $this->optimized[$eventName]; + } } diff --git a/lib/symfony/event-dispatcher/EventDispatcherInterface.php b/lib/symfony/event-dispatcher/EventDispatcherInterface.php index bde753a12f..cc324e1c61 100644 --- a/lib/symfony/event-dispatcher/EventDispatcherInterface.php +++ b/lib/symfony/event-dispatcher/EventDispatcherInterface.php @@ -11,6 +11,8 @@ namespace Symfony\Component\EventDispatcher; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface; + /** * The EventDispatcherInterface is the central point of Symfony's event listener system. * Listeners are registered on the manager and events are dispatched through the @@ -18,30 +20,15 @@ namespace Symfony\Component\EventDispatcher; * * @author Bernhard Schussek */ -interface EventDispatcherInterface +interface EventDispatcherInterface extends ContractsEventDispatcherInterface { - /** - * Dispatches an event to all registered listeners. - * - * @param string $eventName The name of the event to dispatch. The name of - * the event is the name of the method that is - * invoked on listeners. - * @param Event|null $event The event to pass to the event handlers/listeners - * If not supplied, an empty Event instance is created - * - * @return Event - */ - public function dispatch($eventName, Event $event = null); - /** * Adds an event listener that listens on the specified events. * - * @param string $eventName The event to listen on - * @param callable $listener The listener - * @param int $priority The higher this value, the earlier an event - * listener will be triggered in the chain (defaults to 0) + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) */ - public function addListener($eventName, $listener, $priority = 0); + public function addListener(string $eventName, callable $listener, int $priority = 0); /** * Adds an event subscriber. @@ -53,41 +40,31 @@ interface EventDispatcherInterface /** * Removes an event listener from the specified events. - * - * @param string $eventName The event to remove a listener from - * @param callable $listener The listener to remove */ - public function removeListener($eventName, $listener); + public function removeListener(string $eventName, callable $listener); public function removeSubscriber(EventSubscriberInterface $subscriber); /** * Gets the listeners of a specific event or all listeners sorted by descending priority. * - * @param string|null $eventName The name of the event - * - * @return array The event listeners for the specified event, or all event listeners by event name + * @return array */ - public function getListeners($eventName = null); + public function getListeners(string $eventName = null); /** * Gets the listener priority for a specific event. * * Returns null if the event or the listener does not exist. * - * @param string $eventName The name of the event - * @param callable $listener The listener - * - * @return int|null The event listener priority + * @return int|null */ - public function getListenerPriority($eventName, $listener); + public function getListenerPriority(string $eventName, callable $listener); /** * Checks whether an event has any registered listeners. * - * @param string|null $eventName The name of the event - * - * @return bool true if the specified event has any listeners, false otherwise + * @return bool */ - public function hasListeners($eventName = null); + public function hasListeners(string $eventName = null); } diff --git a/lib/symfony/event-dispatcher/EventSubscriberInterface.php b/lib/symfony/event-dispatcher/EventSubscriberInterface.php index 741590b1bf..2085e428e9 100644 --- a/lib/symfony/event-dispatcher/EventSubscriberInterface.php +++ b/lib/symfony/event-dispatcher/EventSubscriberInterface.php @@ -43,7 +43,7 @@ interface EventSubscriberInterface * The code must not depend on runtime state as it will only be called at compile time. * All logic depending on runtime state must be put into the individual methods handling the events. * - * @return array The event names to listen to + * @return array> */ public static function getSubscribedEvents(); } diff --git a/lib/symfony/event-dispatcher/GenericEvent.php b/lib/symfony/event-dispatcher/GenericEvent.php index f005e3a3db..b32a301ae9 100644 --- a/lib/symfony/event-dispatcher/GenericEvent.php +++ b/lib/symfony/event-dispatcher/GenericEvent.php @@ -11,12 +11,17 @@ namespace Symfony\Component\EventDispatcher; +use Symfony\Contracts\EventDispatcher\Event; + /** * Event encapsulation class. * * Encapsulates events thus decoupling the observer from the subject they encapsulate. * * @author Drak + * + * @implements \ArrayAccess + * @implements \IteratorAggregate */ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate { @@ -38,7 +43,7 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate /** * Getter for subject property. * - * @return mixed The observer subject + * @return mixed */ public function getSubject() { @@ -48,13 +53,11 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate /** * Get argument by key. * - * @param string $key Key - * - * @return mixed Contents of array key + * @return mixed * * @throws \InvalidArgumentException if key is not found */ - public function getArgument($key) + public function getArgument(string $key) { if ($this->hasArgument($key)) { return $this->arguments[$key]; @@ -66,12 +69,11 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate /** * Add argument to event. * - * @param string $key Argument name - * @param mixed $value Value + * @param mixed $value Value * * @return $this */ - public function setArgument($key, $value) + public function setArgument(string $key, $value) { $this->arguments[$key] = $value; @@ -91,8 +93,6 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate /** * Set args property. * - * @param array $args Arguments - * * @return $this */ public function setArguments(array $args = []) @@ -105,11 +105,9 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate /** * Has argument. * - * @param string $key Key of arguments array - * * @return bool */ - public function hasArgument($key) + public function hasArgument(string $key) { return \array_key_exists($key, $this->arguments); } @@ -123,6 +121,7 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate * * @throws \InvalidArgumentException if key does not exist in $this->args */ + #[\ReturnTypeWillChange] public function offsetGet($key) { return $this->getArgument($key); @@ -133,7 +132,10 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate * * @param string $key Array key to set * @param mixed $value Value + * + * @return void */ + #[\ReturnTypeWillChange] public function offsetSet($key, $value) { $this->setArgument($key, $value); @@ -143,7 +145,10 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate * ArrayAccess for unset argument. * * @param string $key Array key + * + * @return void */ + #[\ReturnTypeWillChange] public function offsetUnset($key) { if ($this->hasArgument($key)) { @@ -158,6 +163,7 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate * * @return bool */ + #[\ReturnTypeWillChange] public function offsetExists($key) { return $this->hasArgument($key); @@ -166,8 +172,9 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate /** * IteratorAggregate for iterating over the object like an array. * - * @return \ArrayIterator + * @return \ArrayIterator */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->arguments); diff --git a/lib/symfony/event-dispatcher/ImmutableEventDispatcher.php b/lib/symfony/event-dispatcher/ImmutableEventDispatcher.php index b3cf56c502..568d79c3a2 100644 --- a/lib/symfony/event-dispatcher/ImmutableEventDispatcher.php +++ b/lib/symfony/event-dispatcher/ImmutableEventDispatcher.php @@ -28,15 +28,15 @@ class ImmutableEventDispatcher implements EventDispatcherInterface /** * {@inheritdoc} */ - public function dispatch($eventName, Event $event = null) + public function dispatch(object $event, string $eventName = null): object { - return $this->dispatcher->dispatch($eventName, $event); + return $this->dispatcher->dispatch($event, $eventName); } /** * {@inheritdoc} */ - public function addListener($eventName, $listener, $priority = 0) + public function addListener(string $eventName, $listener, int $priority = 0) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } @@ -52,7 +52,7 @@ class ImmutableEventDispatcher implements EventDispatcherInterface /** * {@inheritdoc} */ - public function removeListener($eventName, $listener) + public function removeListener(string $eventName, $listener) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); } @@ -68,7 +68,7 @@ class ImmutableEventDispatcher implements EventDispatcherInterface /** * {@inheritdoc} */ - public function getListeners($eventName = null) + public function getListeners(string $eventName = null) { return $this->dispatcher->getListeners($eventName); } @@ -76,7 +76,7 @@ class ImmutableEventDispatcher implements EventDispatcherInterface /** * {@inheritdoc} */ - public function getListenerPriority($eventName, $listener) + public function getListenerPriority(string $eventName, $listener) { return $this->dispatcher->getListenerPriority($eventName, $listener); } @@ -84,7 +84,7 @@ class ImmutableEventDispatcher implements EventDispatcherInterface /** * {@inheritdoc} */ - public function hasListeners($eventName = null) + public function hasListeners(string $eventName = null) { return $this->dispatcher->hasListeners($eventName); } diff --git a/lib/symfony/event-dispatcher/LICENSE b/lib/symfony/event-dispatcher/LICENSE index 9e936ec044..88bf75bb4d 100644 --- a/lib/symfony/event-dispatcher/LICENSE +++ b/lib/symfony/event-dispatcher/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2020 Fabien Potencier +Copyright (c) 2004-2022 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/lib/symfony/event-dispatcher/LegacyEventDispatcherProxy.php b/lib/symfony/event-dispatcher/LegacyEventDispatcherProxy.php new file mode 100644 index 0000000000..6e17c8fcc9 --- /dev/null +++ b/lib/symfony/event-dispatcher/LegacyEventDispatcherProxy.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; + +trigger_deprecation('symfony/event-dispatcher', '5.1', '%s is deprecated, use the event dispatcher without the proxy.', LegacyEventDispatcherProxy::class); + +/** + * A helper class to provide BC/FC with the legacy signature of EventDispatcherInterface::dispatch(). + * + * @author Nicolas Grekas + * + * @deprecated since Symfony 5.1 + */ +final class LegacyEventDispatcherProxy +{ + public static function decorate(?EventDispatcherInterface $dispatcher): ?EventDispatcherInterface + { + return $dispatcher; + } +} diff --git a/lib/symfony/event-dispatcher/README.md b/lib/symfony/event-dispatcher/README.md index e0d38eed01..dcdb68d218 100644 --- a/lib/symfony/event-dispatcher/README.md +++ b/lib/symfony/event-dispatcher/README.md @@ -8,8 +8,8 @@ them. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/event_dispatcher.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) + * [Documentation](https://symfony.com/doc/current/components/event_dispatcher.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/lib/symfony/event-dispatcher/composer.json b/lib/symfony/event-dispatcher/composer.json index 408022f6bf..32b42e4084 100644 --- a/lib/symfony/event-dispatcher/composer.json +++ b/lib/symfony/event-dispatcher/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/event-dispatcher", "type": "library", - "description": "Symfony EventDispatcher Component", + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "keywords": [], "homepage": "https://symfony.com", "license": "MIT", @@ -16,18 +16,27 @@ } ], "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/event-dispatcher-contracts": "^2|^3", + "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "symfony/dependency-injection": "~3.3|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/config": "~2.8|~3.0|~4.0", - "symfony/debug": "~3.4|~4.4", - "symfony/stopwatch": "~2.8|~3.0|~4.0", - "psr/log": "~1.0" + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^4.4|^5.0|^6.0", + "psr/log": "^1|^2|^3" }, "conflict": { - "symfony/dependency-injection": "<3.3" + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" }, "suggest": { "symfony/dependency-injection": "", diff --git a/lib/symfony/event-dispatcher/phpunit.xml.dist b/lib/symfony/event-dispatcher/phpunit.xml.dist deleted file mode 100644 index f2eb1692cd..0000000000 --- a/lib/symfony/event-dispatcher/phpunit.xml.dist +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - ./Tests/ - - - - - - ./ - - ./Resources - ./Tests - ./vendor - - - - diff --git a/lib/symfony/filesystem/.gitignore b/lib/symfony/filesystem/.gitignore deleted file mode 100644 index c49a5d8df5..0000000000 --- a/lib/symfony/filesystem/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/lib/symfony/filesystem/CHANGELOG.md b/lib/symfony/filesystem/CHANGELOG.md index d01f5f45e1..fcb7170ca5 100644 --- a/lib/symfony/filesystem/CHANGELOG.md +++ b/lib/symfony/filesystem/CHANGELOG.md @@ -1,6 +1,35 @@ CHANGELOG ========= +5.4 +--- + + * Add `Path` class + * Add `$lock` argument to `Filesystem::appendToFile()` + +5.0.0 +----- + + * `Filesystem::dumpFile()` and `appendToFile()` don't accept arrays anymore + +4.4.0 +----- + + * support for passing a `null` value to `Filesystem::isAbsolutePath()` is deprecated and will be removed in 5.0 + * `tempnam()` now accepts a third argument `$suffix`. + +4.3.0 +----- + + * support for passing arrays to `Filesystem::dumpFile()` is deprecated and will be removed in 5.0 + * support for passing arrays to `Filesystem::appendToFile()` is deprecated and will be removed in 5.0 + +4.0.0 +----- + + * removed `LockHandler` + * Support for passing relative paths to `Filesystem::makePathRelative()` has been removed. + 3.4.0 ----- diff --git a/lib/symfony/filesystem/Exception/ExceptionInterface.php b/lib/symfony/filesystem/Exception/ExceptionInterface.php index 8f4f10aac7..fc438d9f31 100644 --- a/lib/symfony/filesystem/Exception/ExceptionInterface.php +++ b/lib/symfony/filesystem/Exception/ExceptionInterface.php @@ -16,6 +16,6 @@ namespace Symfony\Component\Filesystem\Exception; * * @author Romain Neutron */ -interface ExceptionInterface +interface ExceptionInterface extends \Throwable { } diff --git a/lib/symfony/filesystem/Exception/FileNotFoundException.php b/lib/symfony/filesystem/Exception/FileNotFoundException.php index bcc8fe81fc..48b6408095 100644 --- a/lib/symfony/filesystem/Exception/FileNotFoundException.php +++ b/lib/symfony/filesystem/Exception/FileNotFoundException.php @@ -19,7 +19,7 @@ namespace Symfony\Component\Filesystem\Exception; */ class FileNotFoundException extends IOException { - public function __construct($message = null, $code = 0, \Exception $previous = null, $path = null) + public function __construct(string $message = null, int $code = 0, \Throwable $previous = null, string $path = null) { if (null === $message) { if (null === $path) { diff --git a/lib/symfony/filesystem/Exception/IOException.php b/lib/symfony/filesystem/Exception/IOException.php index 144e0e602b..fea26e4ddc 100644 --- a/lib/symfony/filesystem/Exception/IOException.php +++ b/lib/symfony/filesystem/Exception/IOException.php @@ -22,7 +22,7 @@ class IOException extends \RuntimeException implements IOExceptionInterface { private $path; - public function __construct($message, $code = 0, \Exception $previous = null, $path = null) + public function __construct(string $message, int $code = 0, \Throwable $previous = null, string $path = null) { $this->path = $path; diff --git a/lib/symfony/filesystem/Exception/IOExceptionInterface.php b/lib/symfony/filesystem/Exception/IOExceptionInterface.php index f9d4644a87..42829ab6c2 100644 --- a/lib/symfony/filesystem/Exception/IOExceptionInterface.php +++ b/lib/symfony/filesystem/Exception/IOExceptionInterface.php @@ -21,7 +21,7 @@ interface IOExceptionInterface extends ExceptionInterface /** * Returns the associated path for the exception. * - * @return string|null The path + * @return string|null */ public function getPath(); } diff --git a/lib/symfony/filesystem/Exception/InvalidArgumentException.php b/lib/symfony/filesystem/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000..abadc20029 --- /dev/null +++ b/lib/symfony/filesystem/Exception/InvalidArgumentException.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 Symfony\Component\Filesystem\Exception; + +/** + * @author Christian Flothmann + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/lib/symfony/filesystem/Exception/RuntimeException.php b/lib/symfony/filesystem/Exception/RuntimeException.php new file mode 100644 index 0000000000..a7512dca73 --- /dev/null +++ b/lib/symfony/filesystem/Exception/RuntimeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * @author Théo Fidry + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/lib/symfony/filesystem/Filesystem.php b/lib/symfony/filesystem/Filesystem.php index 96b2e96009..fafd364925 100644 --- a/lib/symfony/filesystem/Filesystem.php +++ b/lib/symfony/filesystem/Filesystem.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Filesystem; use Symfony\Component\Filesystem\Exception\FileNotFoundException; +use Symfony\Component\Filesystem\Exception\InvalidArgumentException; use Symfony\Component\Filesystem\Exception\IOException; /** @@ -30,14 +31,10 @@ class Filesystem * If the target file is newer, it is overwritten only when the * $overwriteNewerFiles option is set to true. * - * @param string $originFile The original filename - * @param string $targetFile The target filename - * @param bool $overwriteNewerFiles If true, target files newer than origin files are overwritten - * * @throws FileNotFoundException When originFile doesn't exist * @throws IOException When copy fails */ - public function copy($originFile, $targetFile, $overwriteNewerFiles = false) + public function copy(string $originFile, string $targetFile, bool $overwriteNewerFiles = false) { $originIsLocal = stream_is_local($originFile) || 0 === stripos($originFile, 'file://'); if ($originIsLocal && !is_file($originFile)) { @@ -53,13 +50,13 @@ class Filesystem if ($doCopy) { // https://bugs.php.net/64634 - if (false === $source = @fopen($originFile, 'r')) { - throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading.', $originFile, $targetFile), 0, null, $originFile); + if (!$source = self::box('fopen', $originFile, 'r')) { + throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading: ', $originFile, $targetFile).self::$lastError, 0, null, $originFile); } // Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default - if (false === $target = @fopen($targetFile, 'w', null, stream_context_create(['ftp' => ['overwrite' => true]]))) { - throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing.', $originFile, $targetFile), 0, null, $originFile); + if (!$target = self::box('fopen', $targetFile, 'w', false, stream_context_create(['ftp' => ['overwrite' => true]]))) { + throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing: ', $originFile, $targetFile).self::$lastError, 0, null, $originFile); } $bytesCopied = stream_copy_to_stream($source, $target); @@ -73,7 +70,7 @@ class Filesystem if ($originIsLocal) { // Like `cp`, preserve executable permission bits - @chmod($targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111)); + self::box('chmod', $targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111)); if ($bytesCopied !== $bytesOrigin = filesize($originFile)) { throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile); @@ -86,25 +83,18 @@ class Filesystem * Creates a directory recursively. * * @param string|iterable $dirs The directory path - * @param int $mode The directory mode * * @throws IOException On any directory creation failure */ - public function mkdir($dirs, $mode = 0777) + public function mkdir($dirs, int $mode = 0777) { foreach ($this->toIterable($dirs) as $dir) { if (is_dir($dir)) { continue; } - if (!self::box('mkdir', $dir, $mode, true)) { - if (!is_dir($dir)) { - // The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one - if (self::$lastError) { - throw new IOException(sprintf('Failed to create "%s": ', $dir).self::$lastError, 0, null, $dir); - } - throw new IOException(sprintf('Failed to create "%s".', $dir), 0, null, $dir); - } + if (!self::box('mkdir', $dir, $mode, true) && !is_dir($dir)) { + throw new IOException(sprintf('Failed to create "%s": ', $dir).self::$lastError, 0, null, $dir); } } } @@ -114,7 +104,7 @@ class Filesystem * * @param string|iterable $files A filename, an array of files, or a \Traversable instance to check * - * @return bool true if the file exists, false otherwise + * @return bool */ public function exists($files) { @@ -142,12 +132,11 @@ class Filesystem * * @throws IOException When touch fails */ - public function touch($files, $time = null, $atime = null) + public function touch($files, int $time = null, int $atime = null) { foreach ($this->toIterable($files) as $file) { - $touch = $time ? @touch($file, $time, $atime) : @touch($file); - if (true !== $touch) { - throw new IOException(sprintf('Failed to touch "%s".', $file), 0, null, $file); + if (!($time ? self::box('touch', $file, $time, $atime) : self::box('touch', $file))) { + throw new IOException(sprintf('Failed to touch "%s": ', $file).self::$lastError, 0, null, $file); } } } @@ -166,6 +155,12 @@ class Filesystem } elseif (!\is_array($files)) { $files = [$files]; } + + self::doRemove($files, false); + } + + private static function doRemove(array $files, bool $isRecursive): void + { $files = array_reverse($files); foreach ($files as $file) { if (is_link($file)) { @@ -174,12 +169,37 @@ class Filesystem throw new IOException(sprintf('Failed to remove symlink "%s": ', $file).self::$lastError); } } elseif (is_dir($file)) { - $this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS)); + if (!$isRecursive) { + $tmpName = \dirname(realpath($file)).'/.'.strrev(strtr(base64_encode(random_bytes(2)), '/=', '-.')); - if (!self::box('rmdir', $file) && file_exists($file)) { - throw new IOException(sprintf('Failed to remove directory "%s": ', $file).self::$lastError); + if (file_exists($tmpName)) { + try { + self::doRemove([$tmpName], true); + } catch (IOException $e) { + } + } + + if (!file_exists($tmpName) && self::box('rename', $file, $tmpName)) { + $origFile = $file; + $file = $tmpName; + } else { + $origFile = null; + } } - } elseif (!self::box('unlink', $file) && (false !== strpos(self::$lastError, 'Permission denied') || file_exists($file))) { + + $files = new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS); + self::doRemove(iterator_to_array($files, true), true); + + if (!self::box('rmdir', $file) && file_exists($file) && !$isRecursive) { + $lastError = self::$lastError; + + if (null !== $origFile && self::box('rename', $file, $origFile)) { + $file = $origFile; + } + + throw new IOException(sprintf('Failed to remove directory "%s": ', $file).$lastError); + } + } elseif (!self::box('unlink', $file) && (str_contains(self::$lastError, 'Permission denied') || file_exists($file))) { throw new IOException(sprintf('Failed to remove file "%s": ', $file).self::$lastError); } } @@ -195,11 +215,11 @@ class Filesystem * * @throws IOException When the change fails */ - public function chmod($files, $mode, $umask = 0000, $recursive = false) + public function chmod($files, int $mode, int $umask = 0000, bool $recursive = false) { foreach ($this->toIterable($files) as $file) { - if ((\PHP_VERSION_ID < 80000 || \is_int($mode)) && true !== @chmod($file, $mode & ~$umask)) { - throw new IOException(sprintf('Failed to chmod file "%s".', $file), 0, null, $file); + if ((\PHP_VERSION_ID < 80000 || \is_int($mode)) && !self::box('chmod', $file, $mode & ~$umask)) { + throw new IOException(sprintf('Failed to chmod file "%s": ', $file).self::$lastError, 0, null, $file); } if ($recursive && is_dir($file) && !is_link($file)) { $this->chmod(new \FilesystemIterator($file), $mode, $umask, true); @@ -216,19 +236,19 @@ class Filesystem * * @throws IOException When the change fails */ - public function chown($files, $user, $recursive = false) + public function chown($files, $user, bool $recursive = false) { foreach ($this->toIterable($files) as $file) { if ($recursive && is_dir($file) && !is_link($file)) { $this->chown(new \FilesystemIterator($file), $user, true); } if (is_link($file) && \function_exists('lchown')) { - if (true !== @lchown($file, $user)) { - throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file); + if (!self::box('lchown', $file, $user)) { + throw new IOException(sprintf('Failed to chown file "%s": ', $file).self::$lastError, 0, null, $file); } } else { - if (true !== @chown($file, $user)) { - throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file); + if (!self::box('chown', $file, $user)) { + throw new IOException(sprintf('Failed to chown file "%s": ', $file).self::$lastError, 0, null, $file); } } } @@ -243,19 +263,19 @@ class Filesystem * * @throws IOException When the change fails */ - public function chgrp($files, $group, $recursive = false) + public function chgrp($files, $group, bool $recursive = false) { foreach ($this->toIterable($files) as $file) { if ($recursive && is_dir($file) && !is_link($file)) { $this->chgrp(new \FilesystemIterator($file), $group, true); } if (is_link($file) && \function_exists('lchgrp')) { - if (true !== @lchgrp($file, $group) || (\defined('HHVM_VERSION') && !posix_getgrnam($group))) { - throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file); + if (!self::box('lchgrp', $file, $group)) { + throw new IOException(sprintf('Failed to chgrp file "%s": ', $file).self::$lastError, 0, null, $file); } } else { - if (true !== @chgrp($file, $group)) { - throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file); + if (!self::box('chgrp', $file, $group)) { + throw new IOException(sprintf('Failed to chgrp file "%s": ', $file).self::$lastError, 0, null, $file); } } } @@ -264,21 +284,17 @@ class Filesystem /** * Renames a file or a directory. * - * @param string $origin The origin filename or directory - * @param string $target The new filename or directory - * @param bool $overwrite Whether to overwrite the target if it already exists - * * @throws IOException When target file or directory already exists * @throws IOException When origin cannot be renamed */ - public function rename($origin, $target, $overwrite = false) + public function rename(string $origin, string $target, bool $overwrite = false) { // we check that target does not exist if (!$overwrite && $this->isReadable($target)) { throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target); } - if (true !== @rename($origin, $target)) { + if (!self::box('rename', $origin, $target)) { if (is_dir($origin)) { // See https://bugs.php.net/54097 & https://php.net/rename#113943 $this->mirror($origin, $target, null, ['override' => $overwrite, 'delete' => $overwrite]); @@ -286,20 +302,16 @@ class Filesystem return; } - throw new IOException(sprintf('Cannot rename "%s" to "%s".', $origin, $target), 0, null, $target); + throw new IOException(sprintf('Cannot rename "%s" to "%s": ', $origin, $target).self::$lastError, 0, null, $target); } } /** * Tells whether a file exists and is readable. * - * @param string $filename Path to the file - * - * @return bool - * * @throws IOException When windows path is longer than 258 characters */ - private function isReadable($filename) + private function isReadable(string $filename): bool { $maxPathLength = \PHP_MAXPATHLEN - 2; @@ -313,14 +325,12 @@ class Filesystem /** * Creates a symbolic link or copy a directory. * - * @param string $originDir The origin directory path - * @param string $targetDir The symbolic link name - * @param bool $copyOnWindows Whether to copy files if on Windows - * * @throws IOException When symlink fails */ - public function symlink($originDir, $targetDir, $copyOnWindows = false) + public function symlink(string $originDir, string $targetDir, bool $copyOnWindows = false) { + self::assertFunctionExists('symlink'); + if ('\\' === \DIRECTORY_SEPARATOR) { $originDir = strtr($originDir, '/', '\\'); $targetDir = strtr($targetDir, '/', '\\'); @@ -349,14 +359,15 @@ class Filesystem /** * Creates a hard link, or several hard links to a file. * - * @param string $originFile The original file * @param string|string[] $targetFiles The target file(s) * * @throws FileNotFoundException When original file is missing or not a file * @throws IOException When link fails, including if link already exists */ - public function hardlink($originFile, $targetFiles) + public function hardlink(string $originFile, $targetFiles) { + self::assertFunctionExists('link'); + if (!$this->exists($originFile)) { throw new FileNotFoundException(null, 0, null, $originFile); } @@ -380,18 +391,16 @@ class Filesystem } /** - * @param string $origin - * @param string $target * @param string $linkType Name of the link type, typically 'symbolic' or 'hard' */ - private function linkException($origin, $target, $linkType) + private function linkException(string $origin, string $target, string $linkType) { if (self::$lastError) { - if ('\\' === \DIRECTORY_SEPARATOR && false !== strpos(self::$lastError, 'error code(1314)')) { + if ('\\' === \DIRECTORY_SEPARATOR && str_contains(self::$lastError, 'error code(1314)')) { throw new IOException(sprintf('Unable to create "%s" link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType), 0, null, $target); } } - throw new IOException(sprintf('Failed to create "%s" link from "%s" to "%s".', $linkType, $origin, $target), 0, null, $target); + throw new IOException(sprintf('Failed to create "%s" link from "%s" to "%s": ', $linkType, $origin, $target).self::$lastError, 0, null, $target); } /** @@ -405,12 +414,9 @@ class Filesystem * - if $path does not exist, returns null * - if $path exists, returns its absolute fully resolved final version * - * @param string $path A filesystem path - * @param bool $canonicalize Whether or not to return a canonicalized path - * * @return string|null */ - public function readlink($path, $canonicalize = false) + public function readlink(string $path, bool $canonicalize = false) { if (!$canonicalize && !is_link($path)) { return null; @@ -421,14 +427,14 @@ class Filesystem return null; } - if ('\\' === \DIRECTORY_SEPARATOR) { + if ('\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70410) { $path = readlink($path); } return realpath($path); } - if ('\\' === \DIRECTORY_SEPARATOR) { + if ('\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70400) { return realpath($path); } @@ -438,15 +444,16 @@ class Filesystem /** * Given an existing path, convert it to a path relative to a given starting path. * - * @param string $endPath Absolute path of target - * @param string $startPath Absolute path where traversal begins - * - * @return string Path of target relative to starting path + * @return string */ - public function makePathRelative($endPath, $startPath) + public function makePathRelative(string $endPath, string $startPath) { - if (!$this->isAbsolutePath($endPath) || !$this->isAbsolutePath($startPath)) { - @trigger_error(sprintf('Support for passing relative paths to %s() is deprecated since Symfony 3.4 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); + if (!$this->isAbsolutePath($startPath)) { + throw new InvalidArgumentException(sprintf('The start path "%s" is not absolute.', $startPath)); + } + + if (!$this->isAbsolutePath($endPath)) { + throw new InvalidArgumentException(sprintf('The end path "%s" is not absolute.', $endPath)); } // Normalize separators on Windows @@ -461,11 +468,11 @@ class Filesystem : [$path, null]; }; - $splitPath = function ($path, $absolute) { + $splitPath = function ($path) { $result = []; foreach (explode('/', trim($path, '/')) as $segment) { - if ('..' === $segment && ($absolute || \count($result))) { + if ('..' === $segment) { array_pop($result); } elseif ('.' !== $segment && '' !== $segment) { $result[] = $segment; @@ -475,11 +482,11 @@ class Filesystem return $result; }; - list($endPath, $endDriveLetter) = $splitDriveLetter($endPath); - list($startPath, $startDriveLetter) = $splitDriveLetter($startPath); + [$endPath, $endDriveLetter] = $splitDriveLetter($endPath); + [$startPath, $startDriveLetter] = $splitDriveLetter($startPath); - $startPathArr = $splitPath($startPath, static::isAbsolutePath($startPath)); - $endPathArr = $splitPath($endPath, static::isAbsolutePath($endPath)); + $startPathArr = $splitPath($startPath); + $endPathArr = $splitPath($endPath); if ($endDriveLetter && $startDriveLetter && $endDriveLetter != $startDriveLetter) { // End path is on another drive, so no relative path exists @@ -518,23 +525,25 @@ class Filesystem * - existing files in the target directory will be overwritten, except if they are newer (see the `override` option) * - files in the target directory that do not exist in the source directory will not be deleted (see the `delete` option) * - * @param string $originDir The origin directory - * @param string $targetDir The target directory - * @param \Traversable|null $iterator Iterator that filters which files and directories to copy, if null a recursive iterator is created - * @param array $options An array of boolean options - * Valid options are: - * - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false) - * - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false) - * - $options['delete'] Whether to delete files that are not in the source directory (defaults to false) + * @param \Traversable|null $iterator Iterator that filters which files and directories to copy, if null a recursive iterator is created + * @param array $options An array of boolean options + * Valid options are: + * - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false) + * - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false) + * - $options['delete'] Whether to delete files that are not in the source directory (defaults to false) * * @throws IOException When file type is unknown */ - public function mirror($originDir, $targetDir, \Traversable $iterator = null, $options = []) + public function mirror(string $originDir, string $targetDir, \Traversable $iterator = null, array $options = []) { $targetDir = rtrim($targetDir, '/\\'); $originDir = rtrim($originDir, '/\\'); $originDirLen = \strlen($originDir); + if (!$this->exists($originDir)) { + throw new IOException(sprintf('The origin directory specified "%s" was not found.', $originDir), 0, null, $originDir); + } + // Iterate in destination folder to remove obsolete entries if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) { $deleteIterator = $iterator; @@ -551,41 +560,32 @@ class Filesystem } } - $copyOnWindows = false; - if (isset($options['copy_on_windows'])) { - $copyOnWindows = $options['copy_on_windows']; - } + $copyOnWindows = $options['copy_on_windows'] ?? false; if (null === $iterator) { $flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS; $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST); } - if ($this->exists($originDir)) { - $this->mkdir($targetDir); - } + $this->mkdir($targetDir); + $filesCreatedWhileMirroring = []; foreach ($iterator as $file) { - $target = $targetDir.substr($file->getPathname(), $originDirLen); + if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || isset($filesCreatedWhileMirroring[$file->getRealPath()])) { + continue; + } - if ($copyOnWindows) { - if (is_file($file)) { - $this->copy($file, $target, isset($options['override']) ? $options['override'] : false); - } elseif (is_dir($file)) { - $this->mkdir($target); - } else { - throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file); - } + $target = $targetDir.substr($file->getPathname(), $originDirLen); + $filesCreatedWhileMirroring[$target] = true; + + if (!$copyOnWindows && is_link($file)) { + $this->symlink($file->getLinkTarget(), $target); + } elseif (is_dir($file)) { + $this->mkdir($target); + } elseif (is_file($file)) { + $this->copy($file, $target, $options['override'] ?? false); } else { - if (is_link($file)) { - $this->symlink($file->getLinkTarget(), $target); - } elseif (is_dir($file)) { - $this->mkdir($target); - } elseif (is_file($file)) { - $this->copy($file, $target, isset($options['override']) ? $options['override'] : false); - } else { - throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file); - } + throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file); } } } @@ -593,13 +593,11 @@ class Filesystem /** * Returns whether the file path is an absolute path. * - * @param string $file A file path - * * @return bool */ - public function isAbsolutePath($file) + public function isAbsolutePath(string $file) { - return '' !== (string) $file && (strspn($file, '/\\', 0, 1) + return '' !== $file && (strspn($file, '/\\', 0, 1) || (\strlen($file) > 3 && ctype_alpha($file[0]) && ':' === $file[1] && strspn($file, '/\\', 2, 1) @@ -611,22 +609,21 @@ class Filesystem /** * Creates a temporary file with support for custom stream wrappers. * - * @param string $dir The directory where the temporary filename will be created * @param string $prefix The prefix of the generated temporary filename * Note: Windows uses only the first three characters of prefix + * @param string $suffix The suffix of the generated temporary filename * * @return string The new temporary filename (with path), or throw an exception on failure */ - public function tempnam($dir, $prefix) + public function tempnam(string $dir, string $prefix/* , string $suffix = '' */) { - list($scheme, $hierarchy) = $this->getSchemeAndHierarchy($dir); + $suffix = \func_num_args() > 2 ? func_get_arg(2) : ''; + [$scheme, $hierarchy] = $this->getSchemeAndHierarchy($dir); // If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem - if (null === $scheme || 'file' === $scheme || 'gs' === $scheme) { - $tmpFile = @tempnam($hierarchy, $prefix); - + if ((null === $scheme || 'file' === $scheme || 'gs' === $scheme) && '' === $suffix) { // If tempnam failed or no scheme return the filename otherwise prepend the scheme - if (false !== $tmpFile) { + if ($tmpFile = self::box('tempnam', $hierarchy, $prefix)) { if (null !== $scheme && 'gs' !== $scheme) { return $scheme.'://'.$tmpFile; } @@ -634,140 +631,138 @@ class Filesystem return $tmpFile; } - throw new IOException('A temporary file could not be created.'); + throw new IOException('A temporary file could not be created: '.self::$lastError); } // Loop until we create a valid temp file or have reached 10 attempts for ($i = 0; $i < 10; ++$i) { // Create a unique filename - $tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true); + $tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true).$suffix; // Use fopen instead of file_exists as some streams do not support stat // Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability - $handle = @fopen($tmpFile, 'x+'); - - // If unsuccessful restart the loop - if (false === $handle) { + if (!$handle = self::box('fopen', $tmpFile, 'x+')) { continue; } // Close the file if it was successfully opened - @fclose($handle); + self::box('fclose', $handle); return $tmpFile; } - throw new IOException('A temporary file could not be created.'); + throw new IOException('A temporary file could not be created: '.self::$lastError); } /** * Atomically dumps content into a file. * - * @param string $filename The file to be written to - * @param string $content The data to write into the file + * @param string|resource $content The data to write into the file * * @throws IOException if the file cannot be written to */ - public function dumpFile($filename, $content) + public function dumpFile(string $filename, $content) { + if (\is_array($content)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__)); + } + $dir = \dirname($filename); if (!is_dir($dir)) { $this->mkdir($dir); } - if (!is_writable($dir)) { - throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir); - } - // Will create a temp file with 0600 access rights // when the filesystem supports chmod. $tmpFile = $this->tempnam($dir, basename($filename)); - if (false === @file_put_contents($tmpFile, $content)) { - throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename); + try { + if (false === self::box('file_put_contents', $tmpFile, $content)) { + throw new IOException(sprintf('Failed to write file "%s": ', $filename).self::$lastError, 0, null, $filename); + } + + self::box('chmod', $tmpFile, file_exists($filename) ? fileperms($filename) : 0666 & ~umask()); + + $this->rename($tmpFile, $filename, true); + } finally { + if (file_exists($tmpFile)) { + self::box('unlink', $tmpFile); + } } - - @chmod($tmpFile, file_exists($filename) ? fileperms($filename) : 0666 & ~umask()); - - $this->rename($tmpFile, $filename, true); } /** * Appends content to an existing file. * - * @param string $filename The file to which to append content - * @param string $content The content to append + * @param string|resource $content The content to append + * @param bool $lock Whether the file should be locked when writing to it * * @throws IOException If the file is not writable */ - public function appendToFile($filename, $content) + public function appendToFile(string $filename, $content/* , bool $lock = false */) { + if (\is_array($content)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__)); + } + $dir = \dirname($filename); if (!is_dir($dir)) { $this->mkdir($dir); } - if (!is_writable($dir)) { - throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir); - } + $lock = \func_num_args() > 2 && func_get_arg(2); - if (false === @file_put_contents($filename, $content, \FILE_APPEND)) { - throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename); + if (false === self::box('file_put_contents', $filename, $content, \FILE_APPEND | ($lock ? \LOCK_EX : 0))) { + throw new IOException(sprintf('Failed to write file "%s": ', $filename).self::$lastError, 0, null, $filename); } } - /** - * @param mixed $files - * - * @return array|\Traversable - */ - private function toIterable($files) + private function toIterable($files): iterable { - return \is_array($files) || $files instanceof \Traversable ? $files : [$files]; + return is_iterable($files) ? $files : [$files]; } /** * Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> [file, tmp]). - * - * @param string $filename The filename to be parsed - * - * @return array The filename scheme and hierarchical part */ - private function getSchemeAndHierarchy($filename) + private function getSchemeAndHierarchy(string $filename): array { $components = explode('://', $filename, 2); return 2 === \count($components) ? [$components[0], $components[1]] : [null, $components[0]]; } + private static function assertFunctionExists(string $func): void + { + if (!\function_exists($func)) { + throw new IOException(sprintf('Unable to perform filesystem operation because the "%s()" function has been disabled.', $func)); + } + } + /** - * @param callable $func + * @param mixed ...$args * * @return mixed */ - private static function box($func) + private static function box(string $func, ...$args) { + self::assertFunctionExists($func); + self::$lastError = null; set_error_handler(__CLASS__.'::handleError'); try { - $result = \call_user_func_array($func, \array_slice(\func_get_args(), 1)); + return $func(...$args); + } finally { restore_error_handler(); - - return $result; - } catch (\Throwable $e) { - } catch (\Exception $e) { } - restore_error_handler(); - - throw $e; } /** * @internal */ - public static function handleError($type, $msg) + public static function handleError(int $type, string $msg) { self::$lastError = $msg; } diff --git a/lib/symfony/filesystem/LICENSE b/lib/symfony/filesystem/LICENSE index 9e936ec044..88bf75bb4d 100644 --- a/lib/symfony/filesystem/LICENSE +++ b/lib/symfony/filesystem/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2020 Fabien Potencier +Copyright (c) 2004-2022 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/lib/symfony/filesystem/LockHandler.php b/lib/symfony/filesystem/LockHandler.php deleted file mode 100644 index 2aacfa719b..0000000000 --- a/lib/symfony/filesystem/LockHandler.php +++ /dev/null @@ -1,121 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Filesystem; - -use Symfony\Component\Filesystem\Exception\IOException; -use Symfony\Component\Lock\Store\FlockStore; -use Symfony\Component\Lock\Store\SemaphoreStore; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use %s or %s instead.', LockHandler::class, SemaphoreStore::class, FlockStore::class), \E_USER_DEPRECATED); - -/** - * LockHandler class provides a simple abstraction to lock anything by means of - * a file lock. - * - * A locked file is created based on the lock name when calling lock(). Other - * lock handlers will not be able to lock the same name until it is released - * (explicitly by calling release() or implicitly when the instance holding the - * lock is destroyed). - * - * @author Grégoire Pineau - * @author Romain Neutron - * @author Nicolas Grekas - * - * @deprecated since version 3.4, to be removed in 4.0. Use Symfony\Component\Lock\Store\SemaphoreStore or Symfony\Component\Lock\Store\FlockStore instead. - */ -class LockHandler -{ - private $file; - private $handle; - - /** - * @param string $name The lock name - * @param string|null $lockPath The directory to store the lock. Default values will use temporary directory - * - * @throws IOException If the lock directory could not be created or is not writable - */ - public function __construct($name, $lockPath = null) - { - $lockPath = $lockPath ?: sys_get_temp_dir(); - - if (!is_dir($lockPath)) { - $fs = new Filesystem(); - $fs->mkdir($lockPath); - } - - if (!is_writable($lockPath)) { - throw new IOException(sprintf('The directory "%s" is not writable.', $lockPath), 0, null, $lockPath); - } - - $this->file = sprintf('%s/sf.%s.%s.lock', $lockPath, preg_replace('/[^a-z0-9\._-]+/i', '-', $name), hash('sha256', $name)); - } - - /** - * Lock the resource. - * - * @param bool $blocking Wait until the lock is released - * - * @return bool Returns true if the lock was acquired, false otherwise - * - * @throws IOException If the lock file could not be created or opened - */ - public function lock($blocking = false) - { - if ($this->handle) { - return true; - } - - $error = null; - - // Silence error reporting - set_error_handler(function ($errno, $msg) use (&$error) { - $error = $msg; - }); - - if (!$this->handle = fopen($this->file, 'r+') ?: fopen($this->file, 'r')) { - if ($this->handle = fopen($this->file, 'x')) { - chmod($this->file, 0666); - } elseif (!$this->handle = fopen($this->file, 'r+') ?: fopen($this->file, 'r')) { - usleep(100); // Give some time for chmod() to complete - $this->handle = fopen($this->file, 'r+') ?: fopen($this->file, 'r'); - } - } - restore_error_handler(); - - if (!$this->handle) { - throw new IOException($error, 0, null, $this->file); - } - - // On Windows, even if PHP doc says the contrary, LOCK_NB works, see - // https://bugs.php.net/54129 - if (!flock($this->handle, \LOCK_EX | ($blocking ? 0 : \LOCK_NB))) { - fclose($this->handle); - $this->handle = null; - - return false; - } - - return true; - } - - /** - * Release the resource. - */ - public function release() - { - if ($this->handle) { - flock($this->handle, \LOCK_UN | \LOCK_NB); - fclose($this->handle); - $this->handle = null; - } - } -} diff --git a/lib/symfony/filesystem/Path.php b/lib/symfony/filesystem/Path.php new file mode 100644 index 0000000000..0bbd5b4772 --- /dev/null +++ b/lib/symfony/filesystem/Path.php @@ -0,0 +1,819 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem; + +use Symfony\Component\Filesystem\Exception\InvalidArgumentException; +use Symfony\Component\Filesystem\Exception\RuntimeException; + +/** + * Contains utility methods for handling path strings. + * + * The methods in this class are able to deal with both UNIX and Windows paths + * with both forward and backward slashes. All methods return normalized parts + * containing only forward slashes and no excess "." and ".." segments. + * + * @author Bernhard Schussek + * @author Thomas Schulz + * @author Théo Fidry + */ +final class Path +{ + /** + * The number of buffer entries that triggers a cleanup operation. + */ + private const CLEANUP_THRESHOLD = 1250; + + /** + * The buffer size after the cleanup operation. + */ + private const CLEANUP_SIZE = 1000; + + /** + * Buffers input/output of {@link canonicalize()}. + * + * @var array + */ + private static $buffer = []; + + /** + * @var int + */ + private static $bufferSize = 0; + + /** + * Canonicalizes the given path. + * + * During normalization, all slashes are replaced by forward slashes ("/"). + * Furthermore, all "." and ".." segments are removed as far as possible. + * ".." segments at the beginning of relative paths are not removed. + * + * ```php + * echo Path::canonicalize("\symfony\puli\..\css\style.css"); + * // => /symfony/css/style.css + * + * echo Path::canonicalize("../css/./style.css"); + * // => ../css/style.css + * ``` + * + * This method is able to deal with both UNIX and Windows paths. + */ + public static function canonicalize(string $path): string + { + if ('' === $path) { + return ''; + } + + // This method is called by many other methods in this class. Buffer + // the canonicalized paths to make up for the severe performance + // decrease. + if (isset(self::$buffer[$path])) { + return self::$buffer[$path]; + } + + // Replace "~" with user's home directory. + if ('~' === $path[0]) { + $path = self::getHomeDirectory().mb_substr($path, 1); + } + + $path = self::normalize($path); + + [$root, $pathWithoutRoot] = self::split($path); + + $canonicalParts = self::findCanonicalParts($root, $pathWithoutRoot); + + // Add the root directory again + self::$buffer[$path] = $canonicalPath = $root.implode('/', $canonicalParts); + ++self::$bufferSize; + + // Clean up regularly to prevent memory leaks + if (self::$bufferSize > self::CLEANUP_THRESHOLD) { + self::$buffer = \array_slice(self::$buffer, -self::CLEANUP_SIZE, null, true); + self::$bufferSize = self::CLEANUP_SIZE; + } + + return $canonicalPath; + } + + /** + * Normalizes the given path. + * + * During normalization, all slashes are replaced by forward slashes ("/"). + * Contrary to {@link canonicalize()}, this method does not remove invalid + * or dot path segments. Consequently, it is much more efficient and should + * be used whenever the given path is known to be a valid, absolute system + * path. + * + * This method is able to deal with both UNIX and Windows paths. + */ + public static function normalize(string $path): string + { + return str_replace('\\', '/', $path); + } + + /** + * Returns the directory part of the path. + * + * This method is similar to PHP's dirname(), but handles various cases + * where dirname() returns a weird result: + * + * - dirname() does not accept backslashes on UNIX + * - dirname("C:/symfony") returns "C:", not "C:/" + * - dirname("C:/") returns ".", not "C:/" + * - dirname("C:") returns ".", not "C:/" + * - dirname("symfony") returns ".", not "" + * - dirname() does not canonicalize the result + * + * This method fixes these shortcomings and behaves like dirname() + * otherwise. + * + * The result is a canonical path. + * + * @return string The canonical directory part. Returns the root directory + * if the root directory is passed. Returns an empty string + * if a relative path is passed that contains no slashes. + * Returns an empty string if an empty string is passed. + */ + public static function getDirectory(string $path): string + { + if ('' === $path) { + return ''; + } + + $path = self::canonicalize($path); + + // Maintain scheme + if (false !== ($schemeSeparatorPosition = mb_strpos($path, '://'))) { + $scheme = mb_substr($path, 0, $schemeSeparatorPosition + 3); + $path = mb_substr($path, $schemeSeparatorPosition + 3); + } else { + $scheme = ''; + } + + if (false === ($dirSeparatorPosition = strrpos($path, '/'))) { + return ''; + } + + // Directory equals root directory "/" + if (0 === $dirSeparatorPosition) { + return $scheme.'/'; + } + + // Directory equals Windows root "C:/" + if (2 === $dirSeparatorPosition && ctype_alpha($path[0]) && ':' === $path[1]) { + return $scheme.mb_substr($path, 0, 3); + } + + return $scheme.mb_substr($path, 0, $dirSeparatorPosition); + } + + /** + * Returns canonical path of the user's home directory. + * + * Supported operating systems: + * + * - UNIX + * - Windows8 and upper + * + * If your operating system or environment isn't supported, an exception is thrown. + * + * The result is a canonical path. + * + * @throws RuntimeException If your operating system or environment isn't supported + */ + public static function getHomeDirectory(): string + { + // For UNIX support + if (getenv('HOME')) { + return self::canonicalize(getenv('HOME')); + } + + // For >= Windows8 support + if (getenv('HOMEDRIVE') && getenv('HOMEPATH')) { + return self::canonicalize(getenv('HOMEDRIVE').getenv('HOMEPATH')); + } + + throw new RuntimeException("Cannot find the home directory path: Your environment or operating system isn't supported."); + } + + /** + * Returns the root directory of a path. + * + * The result is a canonical path. + * + * @return string The canonical root directory. Returns an empty string if + * the given path is relative or empty. + */ + public static function getRoot(string $path): string + { + if ('' === $path) { + return ''; + } + + // Maintain scheme + if (false !== ($schemeSeparatorPosition = strpos($path, '://'))) { + $scheme = substr($path, 0, $schemeSeparatorPosition + 3); + $path = substr($path, $schemeSeparatorPosition + 3); + } else { + $scheme = ''; + } + + $firstCharacter = $path[0]; + + // UNIX root "/" or "\" (Windows style) + if ('/' === $firstCharacter || '\\' === $firstCharacter) { + return $scheme.'/'; + } + + $length = mb_strlen($path); + + // Windows root + if ($length > 1 && ':' === $path[1] && ctype_alpha($firstCharacter)) { + // Special case: "C:" + if (2 === $length) { + return $scheme.$path.'/'; + } + + // Normal case: "C:/ or "C:\" + if ('/' === $path[2] || '\\' === $path[2]) { + return $scheme.$firstCharacter.$path[1].'/'; + } + } + + return ''; + } + + /** + * Returns the file name without the extension from a file path. + * + * @param string|null $extension if specified, only that extension is cut + * off (may contain leading dot) + */ + public static function getFilenameWithoutExtension(string $path, string $extension = null): string + { + if ('' === $path) { + return ''; + } + + if (null !== $extension) { + // remove extension and trailing dot + return rtrim(basename($path, $extension), '.'); + } + + return pathinfo($path, \PATHINFO_FILENAME); + } + + /** + * Returns the extension from a file path (without leading dot). + * + * @param bool $forceLowerCase forces the extension to be lower-case + */ + public static function getExtension(string $path, bool $forceLowerCase = false): string + { + if ('' === $path) { + return ''; + } + + $extension = pathinfo($path, \PATHINFO_EXTENSION); + + if ($forceLowerCase) { + $extension = self::toLower($extension); + } + + return $extension; + } + + /** + * Returns whether the path has an (or the specified) extension. + * + * @param string $path the path string + * @param string|string[]|null $extensions if null or not provided, checks if + * an extension exists, otherwise + * checks for the specified extension + * or array of extensions (with or + * without leading dot) + * @param bool $ignoreCase whether to ignore case-sensitivity + */ + public static function hasExtension(string $path, $extensions = null, bool $ignoreCase = false): bool + { + if ('' === $path) { + return false; + } + + $actualExtension = self::getExtension($path, $ignoreCase); + + // Only check if path has any extension + if ([] === $extensions || null === $extensions) { + return '' !== $actualExtension; + } + + if (\is_string($extensions)) { + $extensions = [$extensions]; + } + + foreach ($extensions as $key => $extension) { + if ($ignoreCase) { + $extension = self::toLower($extension); + } + + // remove leading '.' in extensions array + $extensions[$key] = ltrim($extension, '.'); + } + + return \in_array($actualExtension, $extensions, true); + } + + /** + * Changes the extension of a path string. + * + * @param string $path The path string with filename.ext to change. + * @param string $extension new extension (with or without leading dot) + * + * @return string the path string with new file extension + */ + public static function changeExtension(string $path, string $extension): string + { + if ('' === $path) { + return ''; + } + + $actualExtension = self::getExtension($path); + $extension = ltrim($extension, '.'); + + // No extension for paths + if ('/' === mb_substr($path, -1)) { + return $path; + } + + // No actual extension in path + if (empty($actualExtension)) { + return $path.('.' === mb_substr($path, -1) ? '' : '.').$extension; + } + + return mb_substr($path, 0, -mb_strlen($actualExtension)).$extension; + } + + public static function isAbsolute(string $path): bool + { + if ('' === $path) { + return false; + } + + // Strip scheme + if (false !== ($schemeSeparatorPosition = mb_strpos($path, '://'))) { + $path = mb_substr($path, $schemeSeparatorPosition + 3); + } + + $firstCharacter = $path[0]; + + // UNIX root "/" or "\" (Windows style) + if ('/' === $firstCharacter || '\\' === $firstCharacter) { + return true; + } + + // Windows root + if (mb_strlen($path) > 1 && ctype_alpha($firstCharacter) && ':' === $path[1]) { + // Special case: "C:" + if (2 === mb_strlen($path)) { + return true; + } + + // Normal case: "C:/ or "C:\" + if ('/' === $path[2] || '\\' === $path[2]) { + return true; + } + } + + return false; + } + + public static function isRelative(string $path): bool + { + return !self::isAbsolute($path); + } + + /** + * Turns a relative path into an absolute path in canonical form. + * + * Usually, the relative path is appended to the given base path. Dot + * segments ("." and "..") are removed/collapsed and all slashes turned + * into forward slashes. + * + * ```php + * echo Path::makeAbsolute("../style.css", "/symfony/puli/css"); + * // => /symfony/puli/style.css + * ``` + * + * If an absolute path is passed, that path is returned unless its root + * directory is different than the one of the base path. In that case, an + * exception is thrown. + * + * ```php + * Path::makeAbsolute("/style.css", "/symfony/puli/css"); + * // => /style.css + * + * Path::makeAbsolute("C:/style.css", "C:/symfony/puli/css"); + * // => C:/style.css + * + * Path::makeAbsolute("C:/style.css", "/symfony/puli/css"); + * // InvalidArgumentException + * ``` + * + * If the base path is not an absolute path, an exception is thrown. + * + * The result is a canonical path. + * + * @param string $basePath an absolute base path + * + * @throws InvalidArgumentException if the base path is not absolute or if + * the given path is an absolute path with + * a different root than the base path + */ + public static function makeAbsolute(string $path, string $basePath): string + { + if ('' === $basePath) { + throw new InvalidArgumentException(sprintf('The base path must be a non-empty string. Got: "%s".', $basePath)); + } + + if (!self::isAbsolute($basePath)) { + throw new InvalidArgumentException(sprintf('The base path "%s" is not an absolute path.', $basePath)); + } + + if (self::isAbsolute($path)) { + return self::canonicalize($path); + } + + if (false !== ($schemeSeparatorPosition = mb_strpos($basePath, '://'))) { + $scheme = mb_substr($basePath, 0, $schemeSeparatorPosition + 3); + $basePath = mb_substr($basePath, $schemeSeparatorPosition + 3); + } else { + $scheme = ''; + } + + return $scheme.self::canonicalize(rtrim($basePath, '/\\').'/'.$path); + } + + /** + * Turns a path into a relative path. + * + * The relative path is created relative to the given base path: + * + * ```php + * echo Path::makeRelative("/symfony/style.css", "/symfony/puli"); + * // => ../style.css + * ``` + * + * If a relative path is passed and the base path is absolute, the relative + * path is returned unchanged: + * + * ```php + * Path::makeRelative("style.css", "/symfony/puli/css"); + * // => style.css + * ``` + * + * If both paths are relative, the relative path is created with the + * assumption that both paths are relative to the same directory: + * + * ```php + * Path::makeRelative("style.css", "symfony/puli/css"); + * // => ../../../style.css + * ``` + * + * If both paths are absolute, their root directory must be the same, + * otherwise an exception is thrown: + * + * ```php + * Path::makeRelative("C:/symfony/style.css", "/symfony/puli"); + * // InvalidArgumentException + * ``` + * + * If the passed path is absolute, but the base path is not, an exception + * is thrown as well: + * + * ```php + * Path::makeRelative("/symfony/style.css", "symfony/puli"); + * // InvalidArgumentException + * ``` + * + * If the base path is not an absolute path, an exception is thrown. + * + * The result is a canonical path. + * + * @throws InvalidArgumentException if the base path is not absolute or if + * the given path has a different root + * than the base path + */ + public static function makeRelative(string $path, string $basePath): string + { + $path = self::canonicalize($path); + $basePath = self::canonicalize($basePath); + + [$root, $relativePath] = self::split($path); + [$baseRoot, $relativeBasePath] = self::split($basePath); + + // If the base path is given as absolute path and the path is already + // relative, consider it to be relative to the given absolute path + // already + if ('' === $root && '' !== $baseRoot) { + // If base path is already in its root + if ('' === $relativeBasePath) { + $relativePath = ltrim($relativePath, './\\'); + } + + return $relativePath; + } + + // If the passed path is absolute, but the base path is not, we + // cannot generate a relative path + if ('' !== $root && '' === $baseRoot) { + throw new InvalidArgumentException(sprintf('The absolute path "%s" cannot be made relative to the relative path "%s". You should provide an absolute base path instead.', $path, $basePath)); + } + + // Fail if the roots of the two paths are different + if ($baseRoot && $root !== $baseRoot) { + throw new InvalidArgumentException(sprintf('The path "%s" cannot be made relative to "%s", because they have different roots ("%s" and "%s").', $path, $basePath, $root, $baseRoot)); + } + + if ('' === $relativeBasePath) { + return $relativePath; + } + + // Build a "../../" prefix with as many "../" parts as necessary + $parts = explode('/', $relativePath); + $baseParts = explode('/', $relativeBasePath); + $dotDotPrefix = ''; + + // Once we found a non-matching part in the prefix, we need to add + // "../" parts for all remaining parts + $match = true; + + foreach ($baseParts as $index => $basePart) { + if ($match && isset($parts[$index]) && $basePart === $parts[$index]) { + unset($parts[$index]); + + continue; + } + + $match = false; + $dotDotPrefix .= '../'; + } + + return rtrim($dotDotPrefix.implode('/', $parts), '/'); + } + + /** + * Returns whether the given path is on the local filesystem. + */ + public static function isLocal(string $path): bool + { + return '' !== $path && false === mb_strpos($path, '://'); + } + + /** + * Returns the longest common base path in canonical form of a set of paths or + * `null` if the paths are on different Windows partitions. + * + * Dot segments ("." and "..") are removed/collapsed and all slashes turned + * into forward slashes. + * + * ```php + * $basePath = Path::getLongestCommonBasePath( + * '/symfony/css/style.css', + * '/symfony/css/..' + * ); + * // => /symfony + * ``` + * + * The root is returned if no common base path can be found: + * + * ```php + * $basePath = Path::getLongestCommonBasePath( + * '/symfony/css/style.css', + * '/puli/css/..' + * ); + * // => / + * ``` + * + * If the paths are located on different Windows partitions, `null` is + * returned. + * + * ```php + * $basePath = Path::getLongestCommonBasePath( + * 'C:/symfony/css/style.css', + * 'D:/symfony/css/..' + * ); + * // => null + * ``` + */ + public static function getLongestCommonBasePath(string ...$paths): ?string + { + [$bpRoot, $basePath] = self::split(self::canonicalize(reset($paths))); + + for (next($paths); null !== key($paths) && '' !== $basePath; next($paths)) { + [$root, $path] = self::split(self::canonicalize(current($paths))); + + // If we deal with different roots (e.g. C:/ vs. D:/), it's time + // to quit + if ($root !== $bpRoot) { + return null; + } + + // Make the base path shorter until it fits into path + while (true) { + if ('.' === $basePath) { + // No more base paths + $basePath = ''; + + // next path + continue 2; + } + + // Prevent false positives for common prefixes + // see isBasePath() + if (0 === mb_strpos($path.'/', $basePath.'/')) { + // next path + continue 2; + } + + $basePath = \dirname($basePath); + } + } + + return $bpRoot.$basePath; + } + + /** + * Joins two or more path strings into a canonical path. + */ + public static function join(string ...$paths): string + { + $finalPath = null; + $wasScheme = false; + + foreach ($paths as $path) { + if ('' === $path) { + continue; + } + + if (null === $finalPath) { + // For first part we keep slashes, like '/top', 'C:\' or 'phar://' + $finalPath = $path; + $wasScheme = (false !== mb_strpos($path, '://')); + continue; + } + + // Only add slash if previous part didn't end with '/' or '\' + if (!\in_array(mb_substr($finalPath, -1), ['/', '\\'])) { + $finalPath .= '/'; + } + + // If first part included a scheme like 'phar://' we allow \current part to start with '/', otherwise trim + $finalPath .= $wasScheme ? $path : ltrim($path, '/'); + $wasScheme = false; + } + + if (null === $finalPath) { + return ''; + } + + return self::canonicalize($finalPath); + } + + /** + * Returns whether a path is a base path of another path. + * + * Dot segments ("." and "..") are removed/collapsed and all slashes turned + * into forward slashes. + * + * ```php + * Path::isBasePath('/symfony', '/symfony/css'); + * // => true + * + * Path::isBasePath('/symfony', '/symfony'); + * // => true + * + * Path::isBasePath('/symfony', '/symfony/..'); + * // => false + * + * Path::isBasePath('/symfony', '/puli'); + * // => false + * ``` + */ + public static function isBasePath(string $basePath, string $ofPath): bool + { + $basePath = self::canonicalize($basePath); + $ofPath = self::canonicalize($ofPath); + + // Append slashes to prevent false positives when two paths have + // a common prefix, for example /base/foo and /base/foobar. + // Don't append a slash for the root "/", because then that root + // won't be discovered as common prefix ("//" is not a prefix of + // "/foobar/"). + return 0 === mb_strpos($ofPath.'/', rtrim($basePath, '/').'/'); + } + + /** + * @return non-empty-string[] + */ + private static function findCanonicalParts(string $root, string $pathWithoutRoot): array + { + $parts = explode('/', $pathWithoutRoot); + + $canonicalParts = []; + + // Collapse "." and "..", if possible + foreach ($parts as $part) { + if ('.' === $part || '' === $part) { + continue; + } + + // Collapse ".." with the previous part, if one exists + // Don't collapse ".." if the previous part is also ".." + if ('..' === $part && \count($canonicalParts) > 0 && '..' !== $canonicalParts[\count($canonicalParts) - 1]) { + array_pop($canonicalParts); + + continue; + } + + // Only add ".." prefixes for relative paths + if ('..' !== $part || '' === $root) { + $canonicalParts[] = $part; + } + } + + return $canonicalParts; + } + + /** + * Splits a canonical path into its root directory and the remainder. + * + * If the path has no root directory, an empty root directory will be + * returned. + * + * If the root directory is a Windows style partition, the resulting root + * will always contain a trailing slash. + * + * list ($root, $path) = Path::split("C:/symfony") + * // => ["C:/", "symfony"] + * + * list ($root, $path) = Path::split("C:") + * // => ["C:/", ""] + * + * @return array{string, string} an array with the root directory and the remaining relative path + */ + private static function split(string $path): array + { + if ('' === $path) { + return ['', '']; + } + + // Remember scheme as part of the root, if any + if (false !== ($schemeSeparatorPosition = mb_strpos($path, '://'))) { + $root = mb_substr($path, 0, $schemeSeparatorPosition + 3); + $path = mb_substr($path, $schemeSeparatorPosition + 3); + } else { + $root = ''; + } + + $length = mb_strlen($path); + + // Remove and remember root directory + if (0 === mb_strpos($path, '/')) { + $root .= '/'; + $path = $length > 1 ? mb_substr($path, 1) : ''; + } elseif ($length > 1 && ctype_alpha($path[0]) && ':' === $path[1]) { + if (2 === $length) { + // Windows special case: "C:" + $root .= $path.'/'; + $path = ''; + } elseif ('/' === $path[2]) { + // Windows normal case: "C:/".. + $root .= mb_substr($path, 0, 3); + $path = $length > 3 ? mb_substr($path, 3) : ''; + } + } + + return [$root, $path]; + } + + private static function toLower(string $string): string + { + if (false !== $encoding = mb_detect_encoding($string)) { + return mb_strtolower($string, $encoding); + } + + return strtolower($string, $encoding); + } + + private function __construct() + { + } +} diff --git a/lib/symfony/filesystem/README.md b/lib/symfony/filesystem/README.md index cb03d43c15..f2f6d45f73 100644 --- a/lib/symfony/filesystem/README.md +++ b/lib/symfony/filesystem/README.md @@ -6,8 +6,8 @@ The Filesystem component provides basic utilities for the filesystem. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/filesystem.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) + * [Documentation](https://symfony.com/doc/current/components/filesystem.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/lib/symfony/filesystem/composer.json b/lib/symfony/filesystem/composer.json index ee48b0b238..e756104cd5 100644 --- a/lib/symfony/filesystem/composer.json +++ b/lib/symfony/filesystem/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/filesystem", "type": "library", - "description": "Symfony Filesystem Component", + "description": "Provides basic utilities for the filesystem", "keywords": [], "homepage": "https://symfony.com", "license": "MIT", @@ -16,8 +16,10 @@ } ], "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8" + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" }, "autoload": { "psr-4": { "Symfony\\Component\\Filesystem\\": "" }, diff --git a/lib/symfony/filesystem/phpunit.xml.dist b/lib/symfony/filesystem/phpunit.xml.dist deleted file mode 100644 index 5515fff1ac..0000000000 --- a/lib/symfony/filesystem/phpunit.xml.dist +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - ./Tests/ - - - - - - ./ - - ./Tests - ./vendor - - - - diff --git a/lib/symfony/finder/.gitignore b/lib/symfony/finder/.gitignore deleted file mode 100644 index c49a5d8df5..0000000000 --- a/lib/symfony/finder/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/lib/symfony/finder/CHANGELOG.md b/lib/symfony/finder/CHANGELOG.md index 53c34be860..6a44e87c2e 100644 --- a/lib/symfony/finder/CHANGELOG.md +++ b/lib/symfony/finder/CHANGELOG.md @@ -1,6 +1,38 @@ CHANGELOG ========= +5.4.0 +----- + + * Deprecate `Comparator::setTarget()` and `Comparator::setOperator()` + * Add a constructor to `Comparator` that allows setting target and operator + * Finder's iterator has now `Symfony\Component\Finder\SplFileInfo` inner type specified + * Add recursive .gitignore files support + +5.0.0 +----- + + * added `$useNaturalSort` argument to `Finder::sortByName()` + +4.3.0 +----- + + * added Finder::ignoreVCSIgnored() to ignore files based on rules listed in .gitignore + +4.2.0 +----- + + * added $useNaturalSort option to Finder::sortByName() method + * the `Finder::sortByName()` method will have a new `$useNaturalSort` + argument in version 5.0, not defining it is deprecated + * added `Finder::reverseSorting()` to reverse the sorting + +4.0.0 +----- + + * removed `ExceptionInterface` + * removed `Symfony\Component\Finder\Iterator\FilterIterator` + 3.4.0 ----- diff --git a/lib/symfony/finder/Comparator/Comparator.php b/lib/symfony/finder/Comparator/Comparator.php index 6aee21cf09..3af551f4cc 100644 --- a/lib/symfony/finder/Comparator/Comparator.php +++ b/lib/symfony/finder/Comparator/Comparator.php @@ -12,8 +12,6 @@ namespace Symfony\Component\Finder\Comparator; /** - * Comparator. - * * @author Fabien Potencier */ class Comparator @@ -21,30 +19,44 @@ class Comparator private $target; private $operator = '=='; + public function __construct(string $target = null, string $operator = '==') + { + if (null === $target) { + trigger_deprecation('symfony/finder', '5.4', 'Constructing a "%s" without setting "$target" is deprecated.', __CLASS__); + } + + $this->target = $target; + $this->doSetOperator($operator); + } + /** * Gets the target value. * - * @return string The target value + * @return string */ public function getTarget() { + if (null === $this->target) { + trigger_deprecation('symfony/finder', '5.4', 'Calling "%s" without initializing the target is deprecated.', __METHOD__); + } + return $this->target; } /** - * Sets the target value. - * - * @param string $target The target value + * @deprecated set the target via the constructor instead */ - public function setTarget($target) + public function setTarget(string $target) { + trigger_deprecation('symfony/finder', '5.4', '"%s" is deprecated. Set the target via the constructor instead.', __METHOD__); + $this->target = $target; } /** * Gets the comparison operator. * - * @return string The operator + * @return string */ public function getOperator() { @@ -54,21 +66,15 @@ class Comparator /** * Sets the comparison operator. * - * @param string $operator A valid operator - * * @throws \InvalidArgumentException + * + * @deprecated set the operator via the constructor instead */ - public function setOperator($operator) + public function setOperator(string $operator) { - if (!$operator) { - $operator = '=='; - } + trigger_deprecation('symfony/finder', '5.4', '"%s" is deprecated. Set the operator via the constructor instead.', __METHOD__); - if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { - throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); - } - - $this->operator = $operator; + $this->doSetOperator('' === $operator ? '==' : $operator); } /** @@ -80,6 +86,10 @@ class Comparator */ public function test($test) { + if (null === $this->target) { + trigger_deprecation('symfony/finder', '5.4', 'Calling "%s" without initializing the target is deprecated.', __METHOD__); + } + switch ($this->operator) { case '>': return $test > $this->target; @@ -95,4 +105,13 @@ class Comparator return $test == $this->target; } + + private function doSetOperator(string $operator): void + { + if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { + throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); + } + + $this->operator = $operator; + } } diff --git a/lib/symfony/finder/Comparator/DateComparator.php b/lib/symfony/finder/Comparator/DateComparator.php index 3de43ef4b8..8f651e1483 100644 --- a/lib/symfony/finder/Comparator/DateComparator.php +++ b/lib/symfony/finder/Comparator/DateComparator.php @@ -23,7 +23,7 @@ class DateComparator extends Comparator * * @throws \InvalidArgumentException If the test is not understood */ - public function __construct($test) + public function __construct(string $test) { if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) { throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test)); @@ -36,7 +36,7 @@ class DateComparator extends Comparator throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); } - $operator = isset($matches[1]) ? $matches[1] : '=='; + $operator = $matches[1] ?? '=='; if ('since' === $operator || 'after' === $operator) { $operator = '>'; } @@ -45,7 +45,6 @@ class DateComparator extends Comparator $operator = '<'; } - $this->setOperator($operator); - $this->setTarget($target); + parent::__construct($target, $operator); } } diff --git a/lib/symfony/finder/Comparator/NumberComparator.php b/lib/symfony/finder/Comparator/NumberComparator.php index f62c0e5740..ff85d9677b 100644 --- a/lib/symfony/finder/Comparator/NumberComparator.php +++ b/lib/symfony/finder/Comparator/NumberComparator.php @@ -39,10 +39,10 @@ class NumberComparator extends Comparator * * @throws \InvalidArgumentException If the test is not understood */ - public function __construct($test) + public function __construct(?string $test) { - if (!preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) { - throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test)); + if (null === $test || !preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) { + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test ?? 'null')); } $target = $matches[2]; @@ -73,7 +73,6 @@ class NumberComparator extends Comparator } } - $this->setTarget($target); - $this->setOperator(isset($matches[1]) ? $matches[1] : '=='); + parent::__construct($target, $matches[1] ?: '=='); } } diff --git a/lib/symfony/finder/Exception/DirectoryNotFoundException.php b/lib/symfony/finder/Exception/DirectoryNotFoundException.php new file mode 100644 index 0000000000..c6cc0f2736 --- /dev/null +++ b/lib/symfony/finder/Exception/DirectoryNotFoundException.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 Symfony\Component\Finder\Exception; + +/** + * @author Andreas Erhard + */ +class DirectoryNotFoundException extends \InvalidArgumentException +{ +} diff --git a/lib/symfony/finder/Exception/ExceptionInterface.php b/lib/symfony/finder/Exception/ExceptionInterface.php deleted file mode 100644 index 161e9686d2..0000000000 --- a/lib/symfony/finder/Exception/ExceptionInterface.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Exception; - -/** - * @author Jean-François Simon - * - * @deprecated since 3.3, to be removed in 4.0. - */ -interface ExceptionInterface -{ - /** - * @return \Symfony\Component\Finder\Adapter\AdapterInterface - */ - public function getAdapter(); -} diff --git a/lib/symfony/finder/Finder.php b/lib/symfony/finder/Finder.php index 186cb32298..8cc564cd67 100644 --- a/lib/symfony/finder/Finder.php +++ b/lib/symfony/finder/Finder.php @@ -13,12 +13,14 @@ namespace Symfony\Component\Finder; use Symfony\Component\Finder\Comparator\DateComparator; use Symfony\Component\Finder\Comparator\NumberComparator; +use Symfony\Component\Finder\Exception\DirectoryNotFoundException; use Symfony\Component\Finder\Iterator\CustomFilterIterator; use Symfony\Component\Finder\Iterator\DateRangeFilterIterator; use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator; use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator; use Symfony\Component\Finder\Iterator\FilecontentFilterIterator; use Symfony\Component\Finder\Iterator\FilenameFilterIterator; +use Symfony\Component\Finder\Iterator\LazyIterator; use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator; use Symfony\Component\Finder\Iterator\SortableIterator; @@ -34,11 +36,14 @@ use Symfony\Component\Finder\Iterator\SortableIterator; * $finder = Finder::create()->files()->name('*.php')->in(__DIR__); * * @author Fabien Potencier + * + * @implements \IteratorAggregate */ class Finder implements \IteratorAggregate, \Countable { - const IGNORE_VCS_FILES = 1; - const IGNORE_DOT_FILES = 2; + public const IGNORE_VCS_FILES = 1; + public const IGNORE_DOT_FILES = 2; + public const IGNORE_VCS_IGNORED_FILES = 4; private $mode = 0; private $names = []; @@ -48,6 +53,7 @@ class Finder implements \IteratorAggregate, \Countable private $depths = []; private $sizes = []; private $followLinks = false; + private $reverseSorting = false; private $sort = false; private $ignore = 0; private $dirs = []; @@ -107,17 +113,20 @@ class Finder implements \IteratorAggregate, \Countable * * $finder->depth('> 1') // the Finder will start matching at level 1. * $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point. + * $finder->depth(['>= 1', '< 3']) * - * @param string|int $level The depth level expression + * @param string|int|string[]|int[] $levels The depth level expression or an array of depth levels * * @return $this * * @see DepthRangeFilterIterator * @see NumberComparator */ - public function depth($level) + public function depth($levels) { - $this->depths[] = new Comparator\NumberComparator($level); + foreach ((array) $levels as $level) { + $this->depths[] = new Comparator\NumberComparator($level); + } return $this; } @@ -131,8 +140,9 @@ class Finder implements \IteratorAggregate, \Countable * $finder->date('until 2 days ago'); * $finder->date('> now - 2 hours'); * $finder->date('>= 2005-10-15'); + * $finder->date(['>= 2005-10-15', '<= 2006-05-27']); * - * @param string $date A date range string + * @param string|string[] $dates A date range string or an array of date ranges * * @return $this * @@ -140,9 +150,11 @@ class Finder implements \IteratorAggregate, \Countable * @see DateRangeFilterIterator * @see DateComparator */ - public function date($date) + public function date($dates) { - $this->dates[] = new Comparator\DateComparator($date); + foreach ((array) $dates as $date) { + $this->dates[] = new Comparator\DateComparator($date); + } return $this; } @@ -155,16 +167,17 @@ class Finder implements \IteratorAggregate, \Countable * $finder->name('*.php') * $finder->name('/\.php$/') // same as above * $finder->name('test.php') + * $finder->name(['test.py', 'test.php']) * - * @param string $pattern A pattern (a regexp, a glob, or a string) + * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns * * @return $this * * @see FilenameFilterIterator */ - public function name($pattern) + public function name($patterns) { - $this->names[] = $pattern; + $this->names = array_merge($this->names, (array) $patterns); return $this; } @@ -172,15 +185,15 @@ class Finder implements \IteratorAggregate, \Countable /** * Adds rules that files must not match. * - * @param string $pattern A pattern (a regexp, a glob, or a string) + * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns * * @return $this * * @see FilenameFilterIterator */ - public function notName($pattern) + public function notName($patterns) { - $this->notNames[] = $pattern; + $this->notNames = array_merge($this->notNames, (array) $patterns); return $this; } @@ -192,16 +205,17 @@ class Finder implements \IteratorAggregate, \Countable * * $finder->contains('Lorem ipsum') * $finder->contains('/Lorem ipsum/i') + * $finder->contains(['dolor', '/ipsum/i']) * - * @param string $pattern A pattern (string or regexp) + * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns * * @return $this * * @see FilecontentFilterIterator */ - public function contains($pattern) + public function contains($patterns) { - $this->contains[] = $pattern; + $this->contains = array_merge($this->contains, (array) $patterns); return $this; } @@ -213,16 +227,17 @@ class Finder implements \IteratorAggregate, \Countable * * $finder->notContains('Lorem ipsum') * $finder->notContains('/Lorem ipsum/i') + * $finder->notContains(['lorem', '/dolor/i']) * - * @param string $pattern A pattern (string or regexp) + * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns * * @return $this * * @see FilecontentFilterIterator */ - public function notContains($pattern) + public function notContains($patterns) { - $this->notContains[] = $pattern; + $this->notContains = array_merge($this->notContains, (array) $patterns); return $this; } @@ -234,18 +249,19 @@ class Finder implements \IteratorAggregate, \Countable * * $finder->path('some/special/dir') * $finder->path('/some\/special\/dir/') // same as above + * $finder->path(['some dir', 'another/dir']) * * Use only / as dirname separator. * - * @param string $pattern A pattern (a regexp or a string) + * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns * * @return $this * * @see FilenameFilterIterator */ - public function path($pattern) + public function path($patterns) { - $this->paths[] = $pattern; + $this->paths = array_merge($this->paths, (array) $patterns); return $this; } @@ -257,18 +273,19 @@ class Finder implements \IteratorAggregate, \Countable * * $finder->notPath('some/special/dir') * $finder->notPath('/some\/special\/dir/') // same as above + * $finder->notPath(['some/file.txt', 'another/file.log']) * * Use only / as dirname separator. * - * @param string $pattern A pattern (a regexp or a string) + * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns * * @return $this * * @see FilenameFilterIterator */ - public function notPath($pattern) + public function notPath($patterns) { - $this->notPaths[] = $pattern; + $this->notPaths = array_merge($this->notPaths, (array) $patterns); return $this; } @@ -279,17 +296,20 @@ class Finder implements \IteratorAggregate, \Countable * $finder->size('> 10K'); * $finder->size('<= 1Ki'); * $finder->size(4); + * $finder->size(['> 10K', '< 20K']) * - * @param string|int $size A size range string or an integer + * @param string|int|string[]|int[] $sizes A size range string or an integer or an array of size ranges * * @return $this * * @see SizeRangeFilterIterator * @see NumberComparator */ - public function size($size) + public function size($sizes) { - $this->sizes[] = new Comparator\NumberComparator($size); + foreach ((array) $sizes as $size) { + $this->sizes[] = new Comparator\NumberComparator($size); + } return $this; } @@ -319,13 +339,11 @@ class Finder implements \IteratorAggregate, \Countable * * This option is enabled by default. * - * @param bool $ignoreDotFiles Whether to exclude "hidden" files or not - * * @return $this * * @see ExcludeDirectoryFilterIterator */ - public function ignoreDotFiles($ignoreDotFiles) + public function ignoreDotFiles(bool $ignoreDotFiles) { if ($ignoreDotFiles) { $this->ignore |= static::IGNORE_DOT_FILES; @@ -341,13 +359,11 @@ class Finder implements \IteratorAggregate, \Countable * * This option is enabled by default. * - * @param bool $ignoreVCS Whether to exclude VCS files or not - * * @return $this * * @see ExcludeDirectoryFilterIterator */ - public function ignoreVCS($ignoreVCS) + public function ignoreVCS(bool $ignoreVCS) { if ($ignoreVCS) { $this->ignore |= static::IGNORE_VCS_FILES; @@ -358,6 +374,24 @@ class Finder implements \IteratorAggregate, \Countable return $this; } + /** + * Forces Finder to obey .gitignore and ignore files based on rules listed there. + * + * This option is disabled by default. + * + * @return $this + */ + public function ignoreVCSIgnored(bool $ignoreVCSIgnored) + { + if ($ignoreVCSIgnored) { + $this->ignore |= static::IGNORE_VCS_IGNORED_FILES; + } else { + $this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES; + } + + return $this; + } + /** * Adds VCS patterns. * @@ -401,9 +435,9 @@ class Finder implements \IteratorAggregate, \Countable * * @see SortableIterator */ - public function sortByName() + public function sortByName(bool $useNaturalSort = false) { - $this->sort = Iterator\SortableIterator::SORT_BY_NAME; + $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME; return $this; } @@ -442,6 +476,18 @@ class Finder implements \IteratorAggregate, \Countable return $this; } + /** + * Reverses the sorting. + * + * @return $this + */ + public function reverseSorting() + { + $this->reverseSorting = true; + + return $this; + } + /** * Sorts files and directories by the last inode changed time. * @@ -514,13 +560,11 @@ class Finder implements \IteratorAggregate, \Countable * * By default, scanning unreadable directories content throws an AccessDeniedException. * - * @param bool $ignore - * * @return $this */ - public function ignoreUnreadableDirs($ignore = true) + public function ignoreUnreadableDirs(bool $ignore = true) { - $this->ignoreUnreadableDirs = (bool) $ignore; + $this->ignoreUnreadableDirs = $ignore; return $this; } @@ -532,7 +576,7 @@ class Finder implements \IteratorAggregate, \Countable * * @return $this * - * @throws \InvalidArgumentException if one of the directories does not exist + * @throws DirectoryNotFoundException if one of the directories does not exist */ public function in($dirs) { @@ -540,16 +584,16 @@ class Finder implements \IteratorAggregate, \Countable foreach ((array) $dirs as $dir) { if (is_dir($dir)) { - $resolvedDirs[] = $this->normalizeDir($dir); + $resolvedDirs[] = [$this->normalizeDir($dir)]; } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) { sort($glob); - $resolvedDirs = array_merge($resolvedDirs, array_map([$this, 'normalizeDir'], $glob)); + $resolvedDirs[] = array_map([$this, 'normalizeDir'], $glob); } else { - throw new \InvalidArgumentException(sprintf('The "%s" directory does not exist.', $dir)); + throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir)); } } - $this->dirs = array_merge($this->dirs, $resolvedDirs); + $this->dirs = array_merge($this->dirs, ...$resolvedDirs); return $this; } @@ -559,10 +603,11 @@ class Finder implements \IteratorAggregate, \Countable * * This method implements the IteratorAggregate interface. * - * @return \Iterator|SplFileInfo[] An iterator + * @return \Iterator * * @throws \LogicException if the in() method has not been called */ + #[\ReturnTypeWillChange] public function getIterator() { if (0 === \count($this->dirs) && 0 === \count($this->iterators)) { @@ -570,18 +615,30 @@ class Finder implements \IteratorAggregate, \Countable } if (1 === \count($this->dirs) && 0 === \count($this->iterators)) { - return $this->searchInDirectory($this->dirs[0]); + $iterator = $this->searchInDirectory($this->dirs[0]); + + if ($this->sort || $this->reverseSorting) { + $iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); + } + + return $iterator; } $iterator = new \AppendIterator(); foreach ($this->dirs as $dir) { - $iterator->append($this->searchInDirectory($dir)); + $iterator->append(new \IteratorIterator(new LazyIterator(function () use ($dir) { + return $this->searchInDirectory($dir); + }))); } foreach ($this->iterators as $it) { $iterator->append($it); } + if ($this->sort || $this->reverseSorting) { + $iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator(); + } + return $iterator; } @@ -590,22 +647,21 @@ class Finder implements \IteratorAggregate, \Countable * * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array. * - * @param iterable $iterator - * * @return $this * * @throws \InvalidArgumentException when the given argument is not iterable */ - public function append($iterator) + public function append(iterable $iterator) { if ($iterator instanceof \IteratorAggregate) { $this->iterators[] = $iterator->getIterator(); } elseif ($iterator instanceof \Iterator) { $this->iterators[] = $iterator; - } elseif ($iterator instanceof \Traversable || \is_array($iterator)) { + } elseif (is_iterable($iterator)) { $it = new \ArrayIterator(); foreach ($iterator as $file) { - $it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file)); + $file = $file instanceof \SplFileInfo ? $file : new \SplFileInfo($file); + $it[$file->getPathname()] = $file; } $this->iterators[] = $it; } else { @@ -634,17 +690,13 @@ class Finder implements \IteratorAggregate, \Countable * * @return int */ + #[\ReturnTypeWillChange] public function count() { return iterator_count($this->getIterator()); } - /** - * @param string $dir - * - * @return \Iterator - */ - private function searchInDirectory($dir) + private function searchInDirectory(string $dir): \Iterator { $exclude = $this->exclude; $notPaths = $this->notPaths; @@ -725,9 +777,8 @@ class Finder implements \IteratorAggregate, \Countable $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths); } - if ($this->sort) { - $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); - $iterator = $iteratorAggregate->getIterator(); + if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) { + $iterator = new Iterator\VcsIgnoredFilterIterator($iterator, $dir); } return $iterator; @@ -737,12 +788,8 @@ class Finder implements \IteratorAggregate, \Countable * Normalizes given directory names by removing trailing slashes. * * Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper - * - * @param string $dir - * - * @return string */ - private function normalizeDir($dir) + private function normalizeDir(string $dir): string { if ('/' === $dir) { return $dir; diff --git a/lib/symfony/finder/Gitignore.php b/lib/symfony/finder/Gitignore.php new file mode 100644 index 0000000000..d42cca1dca --- /dev/null +++ b/lib/symfony/finder/Gitignore.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\Finder; + +/** + * Gitignore matches against text. + * + * @author Michael Voříšek + * @author Ahmed Abdou + */ +class Gitignore +{ + /** + * Returns a regexp which is the equivalent of the gitignore pattern. + * + * Format specification: https://git-scm.com/docs/gitignore#_pattern_format + */ + public static function toRegex(string $gitignoreFileContent): string + { + return self::buildRegex($gitignoreFileContent, false); + } + + public static function toRegexMatchingNegatedPatterns(string $gitignoreFileContent): string + { + return self::buildRegex($gitignoreFileContent, true); + } + + private static function buildRegex(string $gitignoreFileContent, bool $inverted): string + { + $gitignoreFileContent = preg_replace('~(? + * + * @extends \FilterIterator */ -class CustomFilterIterator extends FilterIterator +class CustomFilterIterator extends \FilterIterator { private $filters = []; /** - * @param \Iterator $iterator The Iterator to filter - * @param callable[] $filters An array of PHP callbacks + * @param \Iterator $iterator The Iterator to filter + * @param callable[] $filters An array of PHP callbacks * * @throws \InvalidArgumentException */ @@ -44,14 +46,15 @@ class CustomFilterIterator extends FilterIterator /** * Filters the iterator values. * - * @return bool true if the value should be kept, false otherwise + * @return bool */ + #[\ReturnTypeWillChange] public function accept() { $fileinfo = $this->current(); foreach ($this->filters as $filter) { - if (false === \call_user_func($filter, $fileinfo)) { + if (false === $filter($fileinfo)) { return false; } } diff --git a/lib/symfony/finder/Iterator/DateRangeFilterIterator.php b/lib/symfony/finder/Iterator/DateRangeFilterIterator.php index 8a47321a3c..f592e1913b 100644 --- a/lib/symfony/finder/Iterator/DateRangeFilterIterator.php +++ b/lib/symfony/finder/Iterator/DateRangeFilterIterator.php @@ -17,14 +17,16 @@ use Symfony\Component\Finder\Comparator\DateComparator; * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates). * * @author Fabien Potencier + * + * @extends \FilterIterator */ -class DateRangeFilterIterator extends FilterIterator +class DateRangeFilterIterator extends \FilterIterator { private $comparators = []; /** - * @param \Iterator $iterator The Iterator to filter - * @param DateComparator[] $comparators An array of DateComparator instances + * @param \Iterator $iterator + * @param DateComparator[] $comparators */ public function __construct(\Iterator $iterator, array $comparators) { @@ -36,8 +38,9 @@ class DateRangeFilterIterator extends FilterIterator /** * Filters the iterator values. * - * @return bool true if the value should be kept, false otherwise + * @return bool */ + #[\ReturnTypeWillChange] public function accept() { $fileinfo = $this->current(); diff --git a/lib/symfony/finder/Iterator/DepthRangeFilterIterator.php b/lib/symfony/finder/Iterator/DepthRangeFilterIterator.php index d9bbeb48f1..f593a3f082 100644 --- a/lib/symfony/finder/Iterator/DepthRangeFilterIterator.php +++ b/lib/symfony/finder/Iterator/DepthRangeFilterIterator.php @@ -15,17 +15,22 @@ namespace Symfony\Component\Finder\Iterator; * DepthRangeFilterIterator limits the directory depth. * * @author Fabien Potencier + * + * @template-covariant TKey + * @template-covariant TValue + * + * @extends \FilterIterator */ -class DepthRangeFilterIterator extends FilterIterator +class DepthRangeFilterIterator extends \FilterIterator { private $minDepth = 0; /** - * @param \RecursiveIteratorIterator $iterator The Iterator to filter - * @param int $minDepth The min depth - * @param int $maxDepth The max depth + * @param \RecursiveIteratorIterator<\RecursiveIterator> $iterator The Iterator to filter + * @param int $minDepth The min depth + * @param int $maxDepth The max depth */ - public function __construct(\RecursiveIteratorIterator $iterator, $minDepth = 0, $maxDepth = \PHP_INT_MAX) + public function __construct(\RecursiveIteratorIterator $iterator, int $minDepth = 0, int $maxDepth = \PHP_INT_MAX) { $this->minDepth = $minDepth; $iterator->setMaxDepth(\PHP_INT_MAX === $maxDepth ? -1 : $maxDepth); @@ -36,8 +41,9 @@ class DepthRangeFilterIterator extends FilterIterator /** * Filters the iterator values. * - * @return bool true if the value should be kept, false otherwise + * @return bool */ + #[\ReturnTypeWillChange] public function accept() { return $this->getInnerIterator()->getDepth() >= $this->minDepth; diff --git a/lib/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php b/lib/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php index 60bc4e814c..d9e182c17a 100644 --- a/lib/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php +++ b/lib/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php @@ -15,8 +15,11 @@ namespace Symfony\Component\Finder\Iterator; * ExcludeDirectoryFilterIterator filters out directories. * * @author Fabien Potencier + * + * @extends \FilterIterator + * @implements \RecursiveIterator */ -class ExcludeDirectoryFilterIterator extends FilterIterator implements \RecursiveIterator +class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator { private $iterator; private $isRecursive; @@ -34,7 +37,7 @@ class ExcludeDirectoryFilterIterator extends FilterIterator implements \Recursiv $patterns = []; foreach ($directories as $directory) { $directory = rtrim($directory, '/'); - if (!$this->isRecursive || false !== strpos($directory, '/')) { + if (!$this->isRecursive || str_contains($directory, '/')) { $patterns[] = preg_quote($directory, '#'); } else { $this->excludedDirs[$directory] = true; @@ -50,8 +53,9 @@ class ExcludeDirectoryFilterIterator extends FilterIterator implements \Recursiv /** * Filters the iterator values. * - * @return bool True if the value should be kept, false otherwise + * @return bool */ + #[\ReturnTypeWillChange] public function accept() { if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) { @@ -68,11 +72,19 @@ class ExcludeDirectoryFilterIterator extends FilterIterator implements \Recursiv return true; } + /** + * @return bool + */ + #[\ReturnTypeWillChange] public function hasChildren() { return $this->isRecursive && $this->iterator->hasChildren(); } + /** + * @return self + */ + #[\ReturnTypeWillChange] public function getChildren() { $children = new self($this->iterator->getChildren(), []); diff --git a/lib/symfony/finder/Iterator/FileTypeFilterIterator.php b/lib/symfony/finder/Iterator/FileTypeFilterIterator.php index e9811d4a03..793ae3509a 100644 --- a/lib/symfony/finder/Iterator/FileTypeFilterIterator.php +++ b/lib/symfony/finder/Iterator/FileTypeFilterIterator.php @@ -15,11 +15,13 @@ namespace Symfony\Component\Finder\Iterator; * FileTypeFilterIterator only keeps files, directories, or both. * * @author Fabien Potencier + * + * @extends \FilterIterator */ -class FileTypeFilterIterator extends FilterIterator +class FileTypeFilterIterator extends \FilterIterator { - const ONLY_FILES = 1; - const ONLY_DIRECTORIES = 2; + public const ONLY_FILES = 1; + public const ONLY_DIRECTORIES = 2; private $mode; @@ -27,7 +29,7 @@ class FileTypeFilterIterator extends FilterIterator * @param \Iterator $iterator The Iterator to filter * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) */ - public function __construct(\Iterator $iterator, $mode) + public function __construct(\Iterator $iterator, int $mode) { $this->mode = $mode; @@ -37,8 +39,9 @@ class FileTypeFilterIterator extends FilterIterator /** * Filters the iterator values. * - * @return bool true if the value should be kept, false otherwise + * @return bool */ + #[\ReturnTypeWillChange] public function accept() { $fileinfo = $this->current(); diff --git a/lib/symfony/finder/Iterator/FilecontentFilterIterator.php b/lib/symfony/finder/Iterator/FilecontentFilterIterator.php index 81594b8774..79f8c29d3f 100644 --- a/lib/symfony/finder/Iterator/FilecontentFilterIterator.php +++ b/lib/symfony/finder/Iterator/FilecontentFilterIterator.php @@ -16,14 +16,17 @@ namespace Symfony\Component\Finder\Iterator; * * @author Fabien Potencier * @author Włodzimierz Gajda + * + * @extends MultiplePcreFilterIterator */ class FilecontentFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. * - * @return bool true if the value should be kept, false otherwise + * @return bool */ + #[\ReturnTypeWillChange] public function accept() { if (!$this->matchRegexps && !$this->noMatchRegexps) { @@ -49,9 +52,9 @@ class FilecontentFilterIterator extends MultiplePcreFilterIterator * * @param string $str Pattern: string or regexp * - * @return string regexp corresponding to a given string or regexp + * @return string */ - protected function toRegex($str) + protected function toRegex(string $str) { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } diff --git a/lib/symfony/finder/Iterator/FilenameFilterIterator.php b/lib/symfony/finder/Iterator/FilenameFilterIterator.php index e168cd8ffa..77b3b24193 100644 --- a/lib/symfony/finder/Iterator/FilenameFilterIterator.php +++ b/lib/symfony/finder/Iterator/FilenameFilterIterator.php @@ -17,14 +17,17 @@ use Symfony\Component\Finder\Glob; * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). * * @author Fabien Potencier + * + * @extends MultiplePcreFilterIterator */ class FilenameFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. * - * @return bool true if the value should be kept, false otherwise + * @return bool */ + #[\ReturnTypeWillChange] public function accept() { return $this->isAccepted($this->current()->getFilename()); @@ -38,9 +41,9 @@ class FilenameFilterIterator extends MultiplePcreFilterIterator * * @param string $str Pattern: glob or regexp * - * @return string regexp corresponding to a given glob or regexp + * @return string */ - protected function toRegex($str) + protected function toRegex(string $str) { return $this->isRegex($str) ? $str : Glob::toRegex($str); } diff --git a/lib/symfony/finder/Iterator/FilterIterator.php b/lib/symfony/finder/Iterator/FilterIterator.php deleted file mode 100644 index c16dd8fa9e..0000000000 --- a/lib/symfony/finder/Iterator/FilterIterator.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Finder\Iterator; - -/** - * This iterator just overrides the rewind method in order to correct a PHP bug, - * which existed before version 5.5.23/5.6.7. - * - * @see https://bugs.php.net/68557 - * - * @author Alex Bogomazov - * - * @deprecated since 3.4, to be removed in 4.0. - */ -abstract class FilterIterator extends \FilterIterator -{ - /** - * This is a workaround for the problem with \FilterIterator leaving inner \FilesystemIterator in wrong state after - * rewind in some cases. - * - * @see FilterIterator::rewind() - */ - public function rewind() - { - if (\PHP_VERSION_ID > 50607 || (\PHP_VERSION_ID > 50523 && \PHP_VERSION_ID < 50600)) { - parent::rewind(); - - return; - } - - $iterator = $this; - while ($iterator instanceof \OuterIterator) { - $innerIterator = $iterator->getInnerIterator(); - - if ($innerIterator instanceof RecursiveDirectoryIterator) { - // this condition is necessary for iterators to work properly with non-local filesystems like ftp - if ($innerIterator->isRewindable()) { - $innerIterator->next(); - $innerIterator->rewind(); - } - } elseif ($innerIterator instanceof \FilesystemIterator) { - $innerIterator->next(); - $innerIterator->rewind(); - } - - $iterator = $innerIterator; - } - - parent::rewind(); - } -} diff --git a/lib/symfony/finder/Iterator/LazyIterator.php b/lib/symfony/finder/Iterator/LazyIterator.php new file mode 100644 index 0000000000..32cc37ff14 --- /dev/null +++ b/lib/symfony/finder/Iterator/LazyIterator.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * @author Jérémy Derussé + * + * @internal + */ +class LazyIterator implements \IteratorAggregate +{ + private $iteratorFactory; + + public function __construct(callable $iteratorFactory) + { + $this->iteratorFactory = $iteratorFactory; + } + + public function getIterator(): \Traversable + { + yield from ($this->iteratorFactory)(); + } +} diff --git a/lib/symfony/finder/Iterator/MultiplePcreFilterIterator.php b/lib/symfony/finder/Iterator/MultiplePcreFilterIterator.php index ee365a58b0..564765d8f2 100644 --- a/lib/symfony/finder/Iterator/MultiplePcreFilterIterator.php +++ b/lib/symfony/finder/Iterator/MultiplePcreFilterIterator.php @@ -15,16 +15,21 @@ namespace Symfony\Component\Finder\Iterator; * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings). * * @author Fabien Potencier + * + * @template-covariant TKey + * @template-covariant TValue + * + * @extends \FilterIterator */ -abstract class MultiplePcreFilterIterator extends FilterIterator +abstract class MultiplePcreFilterIterator extends \FilterIterator { protected $matchRegexps = []; protected $noMatchRegexps = []; /** * @param \Iterator $iterator The Iterator to filter - * @param array $matchPatterns An array of patterns that need to match - * @param array $noMatchPatterns An array of patterns that need to not match + * @param string[] $matchPatterns An array of patterns that need to match + * @param string[] $noMatchPatterns An array of patterns that need to not match */ public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) { @@ -46,11 +51,9 @@ abstract class MultiplePcreFilterIterator extends FilterIterator * Such case can be handled by child classes before calling the method if they want to * apply a different behavior. * - * @param string $string The string to be matched against filters - * * @return bool */ - protected function isAccepted($string) + protected function isAccepted(string $string) { // should at least not match one rule to exclude foreach ($this->noMatchRegexps as $regex) { @@ -77,13 +80,17 @@ abstract class MultiplePcreFilterIterator extends FilterIterator /** * Checks whether the string is a regex. * - * @param string $str - * - * @return bool Whether the given string is a regex + * @return bool */ - protected function isRegex($str) + protected function isRegex(string $str) { - if (preg_match('/^(.{3,}?)[imsxuADU]*$/', $str, $m)) { + $availableModifiers = 'imsxuADU'; + + if (\PHP_VERSION_ID >= 80200) { + $availableModifiers .= 'n'; + } + + if (preg_match('/^(.{3,}?)['.$availableModifiers.']*$/', $str, $m)) { $start = substr($m[1], 0, 1); $end = substr($m[1], -1); @@ -104,9 +111,7 @@ abstract class MultiplePcreFilterIterator extends FilterIterator /** * Converts string into regexp. * - * @param string $str Pattern - * - * @return string regexp corresponding to a given string + * @return string */ - abstract protected function toRegex($str); + abstract protected function toRegex(string $str); } diff --git a/lib/symfony/finder/Iterator/PathFilterIterator.php b/lib/symfony/finder/Iterator/PathFilterIterator.php index 3fda557be3..7974c4ee31 100644 --- a/lib/symfony/finder/Iterator/PathFilterIterator.php +++ b/lib/symfony/finder/Iterator/PathFilterIterator.php @@ -16,14 +16,17 @@ namespace Symfony\Component\Finder\Iterator; * * @author Fabien Potencier * @author Włodzimierz Gajda + * + * @extends MultiplePcreFilterIterator */ class PathFilterIterator extends MultiplePcreFilterIterator { /** * Filters the iterator values. * - * @return bool true if the value should be kept, false otherwise + * @return bool */ + #[\ReturnTypeWillChange] public function accept() { $filename = $this->current()->getRelativePathname(); @@ -47,9 +50,9 @@ class PathFilterIterator extends MultiplePcreFilterIterator * * @param string $str Pattern: regexp or dirname * - * @return string regexp corresponding to a given string or regexp + * @return string */ - protected function toRegex($str) + protected function toRegex(string $str) { return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; } diff --git a/lib/symfony/finder/Iterator/RecursiveDirectoryIterator.php b/lib/symfony/finder/Iterator/RecursiveDirectoryIterator.php index 63764d407d..27589cdd5f 100644 --- a/lib/symfony/finder/Iterator/RecursiveDirectoryIterator.php +++ b/lib/symfony/finder/Iterator/RecursiveDirectoryIterator.php @@ -37,13 +37,9 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator private $directorySeparator = '/'; /** - * @param string $path - * @param int $flags - * @param bool $ignoreUnreadableDirs - * * @throws \RuntimeException */ - public function __construct($path, $flags, $ignoreUnreadableDirs = false) + public function __construct(string $path, int $flags, bool $ignoreUnreadableDirs = false) { if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) { throw new \RuntimeException('This iterator only support returning current as fileinfo.'); @@ -60,14 +56,15 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator /** * Return an instance of SplFileInfo with support for relative paths. * - * @return SplFileInfo File information + * @return SplFileInfo */ + #[\ReturnTypeWillChange] public function current() { // the logic here avoids redoing the same work in all iterations if (null === $subPathname = $this->subPath) { - $subPathname = $this->subPath = (string) $this->getSubPath(); + $subPathname = $this->subPath = $this->getSubPath(); } if ('' !== $subPathname) { $subPathname .= $this->directorySeparator; @@ -82,10 +79,35 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator } /** - * @return \RecursiveIterator + * @param bool $allowLinks + * + * @return bool + */ + #[\ReturnTypeWillChange] + public function hasChildren($allowLinks = false) + { + $hasChildren = parent::hasChildren($allowLinks); + + if (!$hasChildren || !$this->ignoreUnreadableDirs) { + return $hasChildren; + } + + try { + parent::getChildren(); + + return true; + } catch (\UnexpectedValueException $e) { + // If directory is unreadable and finder is set to ignore it, skip children + return false; + } + } + + /** + * @return \RecursiveDirectoryIterator * * @throws AccessDeniedException */ + #[\ReturnTypeWillChange] public function getChildren() { try { @@ -102,36 +124,29 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator return $children; } catch (\UnexpectedValueException $e) { - if ($this->ignoreUnreadableDirs) { - // If directory is unreadable and finder is set to ignore it, a fake empty content is returned. - return new \RecursiveArrayIterator([]); - } else { - throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); - } + throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); } } /** * Do nothing for non rewindable stream. + * + * @return void */ + #[\ReturnTypeWillChange] public function rewind() { if (false === $this->isRewindable()) { return; } - // @see https://bugs.php.net/68557 - if (\PHP_VERSION_ID < 50523 || \PHP_VERSION_ID >= 50600 && \PHP_VERSION_ID < 50607) { - parent::next(); - } - parent::rewind(); } /** * Checks if the stream is rewindable. * - * @return bool true when the stream is rewindable, false otherwise + * @return bool */ public function isRewindable() { @@ -139,11 +154,6 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator return $this->rewindable; } - // workaround for an HHVM bug, should be removed when https://github.com/facebook/hhvm/issues/7281 is fixed - if ('' === $this->getPath()) { - return $this->rewindable = false; - } - if (false !== $stream = @opendir($this->getPath())) { $infos = stream_get_meta_data($stream); closedir($stream); diff --git a/lib/symfony/finder/Iterator/SizeRangeFilterIterator.php b/lib/symfony/finder/Iterator/SizeRangeFilterIterator.php index 4e521c8c90..575bf29b7e 100644 --- a/lib/symfony/finder/Iterator/SizeRangeFilterIterator.php +++ b/lib/symfony/finder/Iterator/SizeRangeFilterIterator.php @@ -17,14 +17,16 @@ use Symfony\Component\Finder\Comparator\NumberComparator; * SizeRangeFilterIterator filters out files that are not in the given size range. * * @author Fabien Potencier + * + * @extends \FilterIterator */ -class SizeRangeFilterIterator extends FilterIterator +class SizeRangeFilterIterator extends \FilterIterator { private $comparators = []; /** - * @param \Iterator $iterator The Iterator to filter - * @param NumberComparator[] $comparators An array of NumberComparator instances + * @param \Iterator $iterator + * @param NumberComparator[] $comparators */ public function __construct(\Iterator $iterator, array $comparators) { @@ -36,8 +38,9 @@ class SizeRangeFilterIterator extends FilterIterator /** * Filters the iterator values. * - * @return bool true if the value should be kept, false otherwise + * @return bool */ + #[\ReturnTypeWillChange] public function accept() { $fileinfo = $this->current(); diff --git a/lib/symfony/finder/Iterator/SortableIterator.php b/lib/symfony/finder/Iterator/SortableIterator.php index e67997d117..9afde5c250 100644 --- a/lib/symfony/finder/Iterator/SortableIterator.php +++ b/lib/symfony/finder/Iterator/SortableIterator.php @@ -15,65 +15,89 @@ namespace Symfony\Component\Finder\Iterator; * SortableIterator applies a sort on a given Iterator. * * @author Fabien Potencier + * + * @implements \IteratorAggregate */ class SortableIterator implements \IteratorAggregate { - const SORT_BY_NAME = 1; - const SORT_BY_TYPE = 2; - const SORT_BY_ACCESSED_TIME = 3; - const SORT_BY_CHANGED_TIME = 4; - const SORT_BY_MODIFIED_TIME = 5; + public const SORT_BY_NONE = 0; + public const SORT_BY_NAME = 1; + public const SORT_BY_TYPE = 2; + public const SORT_BY_ACCESSED_TIME = 3; + public const SORT_BY_CHANGED_TIME = 4; + public const SORT_BY_MODIFIED_TIME = 5; + public const SORT_BY_NAME_NATURAL = 6; private $iterator; private $sort; /** - * @param \Traversable $iterator The Iterator to filter - * @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback) + * @param \Traversable $iterator + * @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback) * * @throws \InvalidArgumentException */ - public function __construct(\Traversable $iterator, $sort) + public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = false) { $this->iterator = $iterator; + $order = $reverseOrder ? -1 : 1; if (self::SORT_BY_NAME === $sort) { - $this->sort = static function ($a, $b) { - return strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_NAME_NATURAL === $sort) { + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); }; } elseif (self::SORT_BY_TYPE === $sort) { - $this->sort = static function ($a, $b) { + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { if ($a->isDir() && $b->isFile()) { - return -1; + return -$order; } elseif ($a->isFile() && $b->isDir()) { - return 1; + return $order; } - return strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); }; } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { - $this->sort = static function ($a, $b) { - return $a->getATime() - $b->getATime(); + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * ($a->getATime() - $b->getATime()); }; } elseif (self::SORT_BY_CHANGED_TIME === $sort) { - $this->sort = static function ($a, $b) { - return $a->getCTime() - $b->getCTime(); + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * ($a->getCTime() - $b->getCTime()); }; } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { - $this->sort = static function ($a, $b) { - return $a->getMTime() - $b->getMTime(); + $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) { + return $order * ($a->getMTime() - $b->getMTime()); }; + } elseif (self::SORT_BY_NONE === $sort) { + $this->sort = $order; } elseif (\is_callable($sort)) { - $this->sort = $sort; + $this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : $sort; } else { throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); } } + /** + * @return \Traversable + */ + #[\ReturnTypeWillChange] public function getIterator() { + if (1 === $this->sort) { + return $this->iterator; + } + $array = iterator_to_array($this->iterator, true); - uasort($array, $this->sort); + + if (-1 === $this->sort) { + $array = array_reverse($array); + } else { + uasort($array, $this->sort); + } return new \ArrayIterator($array); } diff --git a/lib/symfony/finder/Iterator/VcsIgnoredFilterIterator.php b/lib/symfony/finder/Iterator/VcsIgnoredFilterIterator.php new file mode 100644 index 0000000000..e27158cbd1 --- /dev/null +++ b/lib/symfony/finder/Iterator/VcsIgnoredFilterIterator.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Gitignore; + +final class VcsIgnoredFilterIterator extends \FilterIterator +{ + /** + * @var string + */ + private $baseDir; + + /** + * @var array + */ + private $gitignoreFilesCache = []; + + /** + * @var array + */ + private $ignoredPathsCache = []; + + public function __construct(\Iterator $iterator, string $baseDir) + { + $this->baseDir = $this->normalizePath($baseDir); + + parent::__construct($iterator); + } + + public function accept(): bool + { + $file = $this->current(); + + $fileRealPath = $this->normalizePath($file->getRealPath()); + + return !$this->isIgnored($fileRealPath); + } + + private function isIgnored(string $fileRealPath): bool + { + if (is_dir($fileRealPath) && !str_ends_with($fileRealPath, '/')) { + $fileRealPath .= '/'; + } + + if (isset($this->ignoredPathsCache[$fileRealPath])) { + return $this->ignoredPathsCache[$fileRealPath]; + } + + $ignored = false; + + foreach ($this->parentsDirectoryDownward($fileRealPath) as $parentDirectory) { + if ($this->isIgnored($parentDirectory)) { + // rules in ignored directories are ignored, no need to check further. + break; + } + + $fileRelativePath = substr($fileRealPath, \strlen($parentDirectory) + 1); + + if (null === $regexps = $this->readGitignoreFile("{$parentDirectory}/.gitignore")) { + continue; + } + + [$exclusionRegex, $inclusionRegex] = $regexps; + + if (preg_match($exclusionRegex, $fileRelativePath)) { + $ignored = true; + + continue; + } + + if (preg_match($inclusionRegex, $fileRelativePath)) { + $ignored = false; + } + } + + return $this->ignoredPathsCache[$fileRealPath] = $ignored; + } + + /** + * @return list + */ + private function parentsDirectoryDownward(string $fileRealPath): array + { + $parentDirectories = []; + + $parentDirectory = $fileRealPath; + + while (true) { + $newParentDirectory = \dirname($parentDirectory); + + // dirname('/') = '/' + if ($newParentDirectory === $parentDirectory) { + break; + } + + $parentDirectory = $newParentDirectory; + + if (0 !== strpos($parentDirectory, $this->baseDir)) { + break; + } + + $parentDirectories[] = $parentDirectory; + } + + return array_reverse($parentDirectories); + } + + /** + * @return array{0: string, 1: string}|null + */ + private function readGitignoreFile(string $path): ?array + { + if (\array_key_exists($path, $this->gitignoreFilesCache)) { + return $this->gitignoreFilesCache[$path]; + } + + if (!file_exists($path)) { + return $this->gitignoreFilesCache[$path] = null; + } + + if (!is_file($path) || !is_readable($path)) { + throw new \RuntimeException("The \"ignoreVCSIgnored\" option cannot be used by the Finder as the \"{$path}\" file is not readable."); + } + + $gitignoreFileContent = file_get_contents($path); + + return $this->gitignoreFilesCache[$path] = [ + Gitignore::toRegex($gitignoreFileContent), + Gitignore::toRegexMatchingNegatedPatterns($gitignoreFileContent), + ]; + } + + private function normalizePath(string $path): string + { + if ('\\' === \DIRECTORY_SEPARATOR) { + return str_replace('\\', '/', $path); + } + + return $path; + } +} diff --git a/lib/symfony/finder/LICENSE b/lib/symfony/finder/LICENSE index 9e936ec044..88bf75bb4d 100644 --- a/lib/symfony/finder/LICENSE +++ b/lib/symfony/finder/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2020 Fabien Potencier +Copyright (c) 2004-2022 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/lib/symfony/finder/README.md b/lib/symfony/finder/README.md index 0b19c75257..22bdeb9bcf 100644 --- a/lib/symfony/finder/README.md +++ b/lib/symfony/finder/README.md @@ -7,8 +7,8 @@ interface. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/finder.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) + * [Documentation](https://symfony.com/doc/current/components/finder.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/lib/symfony/finder/SplFileInfo.php b/lib/symfony/finder/SplFileInfo.php index 0f4e025b22..11604a2efa 100644 --- a/lib/symfony/finder/SplFileInfo.php +++ b/lib/symfony/finder/SplFileInfo.php @@ -26,7 +26,7 @@ class SplFileInfo extends \SplFileInfo * @param string $relativePath The relative path * @param string $relativePathname The relative path name */ - public function __construct($file, $relativePath, $relativePathname) + public function __construct(string $file, string $relativePath, string $relativePathname) { parent::__construct($file); $this->relativePath = $relativePath; @@ -38,7 +38,7 @@ class SplFileInfo extends \SplFileInfo * * This path does not contain the file name. * - * @return string the relative path + * @return string */ public function getRelativePath() { @@ -50,25 +50,35 @@ class SplFileInfo extends \SplFileInfo * * This path contains the file name. * - * @return string the relative path name + * @return string */ public function getRelativePathname() { return $this->relativePathname; } + public function getFilenameWithoutExtension(): string + { + $filename = $this->getFilename(); + + return pathinfo($filename, \PATHINFO_FILENAME); + } + /** * Returns the contents of the file. * - * @return string the contents of the file + * @return string * * @throws \RuntimeException */ public function getContents() { set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); - $content = file_get_contents($this->getPathname()); - restore_error_handler(); + try { + $content = file_get_contents($this->getPathname()); + } finally { + restore_error_handler(); + } if (false === $content) { throw new \RuntimeException($error); } diff --git a/lib/symfony/finder/composer.json b/lib/symfony/finder/composer.json index b0895524be..ef19911da1 100644 --- a/lib/symfony/finder/composer.json +++ b/lib/symfony/finder/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/finder", "type": "library", - "description": "Symfony Finder Component", + "description": "Finds files and directories via an intuitive fluent interface", "keywords": [], "homepage": "https://symfony.com", "license": "MIT", @@ -16,7 +16,9 @@ } ], "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" }, "autoload": { "psr-4": { "Symfony\\Component\\Finder\\": "" }, diff --git a/lib/symfony/finder/phpunit.xml.dist b/lib/symfony/finder/phpunit.xml.dist deleted file mode 100644 index 078847af96..0000000000 --- a/lib/symfony/finder/phpunit.xml.dist +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - ./Tests/ - - - - - - ./ - - ./Tests - ./vendor - - - - diff --git a/lib/symfony/framework-bundle/.gitignore b/lib/symfony/framework-bundle/.gitignore deleted file mode 100644 index c49a5d8df5..0000000000 --- a/lib/symfony/framework-bundle/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/lib/symfony/framework-bundle/CHANGELOG.md b/lib/symfony/framework-bundle/CHANGELOG.md index f04942dbc0..ea913ef985 100644 --- a/lib/symfony/framework-bundle/CHANGELOG.md +++ b/lib/symfony/framework-bundle/CHANGELOG.md @@ -1,6 +1,227 @@ CHANGELOG ========= +5.4 +--- + + * Add `set_locale_from_accept_language` config option to automatically set the request locale based on the `Accept-Language` + HTTP request header and the `framework.enabled_locales` config option + * Add `set_content_language_from_locale` config option to automatically set the `Content-Language` HTTP response header based on the Request locale + * Deprecate the `framework.translator.enabled_locales`, use `framework.enabled_locales` instead + * Add autowiring alias for `HttpCache\StoreInterface` + * Add the ability to enable the profiler using a request query parameter, body parameter or attribute + * Deprecate the `AdapterInterface` autowiring alias, use `CacheItemPoolInterface` instead + * Deprecate the public `profiler` service to private + * Deprecate `get()`, `has()`, `getDoctrine()`, and `dispatchMessage()` in `AbstractController`, use method/constructor injection instead + * Deprecate the `cache.adapter.doctrine` service + * Add support for resetting container services after each messenger message + * Add `configureContainer()`, `configureRoutes()`, `getConfigDir()` and `getBundlesPath()` to `MicroKernelTrait` + * Add support for configuring log level, and status code by exception class + * Bind the `default_context` parameter onto serializer's encoders and normalizers + * Add support for `statusCode` default parameter when loading a template directly from route using the `Symfony\Bundle\FrameworkBundle\Controller\TemplateController` controller + * Deprecate `translation:update` command, use `translation:extract` instead + * Add `PhpStanExtractor` support for the PropertyInfo component + * Add `cache.adapter.doctrine_dbal` service to replace `cache.adapter.pdo` when a Doctrine DBAL connection is used. + +5.3 +--- + + * Deprecate the `session.storage` alias and `session.storage.*` services, use the `session.storage.factory` alias and `session.storage.factory.*` services instead + * Deprecate the `framework.session.storage_id` configuration option, use the `framework.session.storage_factory_id` configuration option instead + * Deprecate the `session` service and the `SessionInterface` alias, use the `Request::getSession()` or the new `RequestStack::getSession()` methods instead + * Add `AbstractController::renderForm()` to render a form and set the appropriate HTTP status code + * Add support for configuring PHP error level to log levels + * Add the `dispatcher` option to `debug:event-dispatcher` + * Add the `event_dispatcher.dispatcher` tag + * Add `assertResponseFormatSame()` in `BrowserKitAssertionsTrait` + * Add support for configuring UUID factory services + * Add tag `assets.package` to register asset packages + * Add support to use a PSR-6 compatible cache for Doctrine annotations + * Deprecate all other values than "none", "php_array" and "file" for `framework.annotation.cache` + * Add `KernelTestCase::getContainer()` as the best way to get a container in tests + * Rename the container parameter `profiler_listener.only_master_requests` to `profiler_listener.only_main_requests` + * Add service `fragment.uri_generator` to generate the URI of a fragment + * Deprecate registering workflow services as public + * Deprecate option `--xliff-version` of the `translation:update` command, use e.g. `--format=xlf20` instead + * Deprecate option `--output-format` of the `translation:update` command, use e.g. `--format=xlf20` instead + +5.2.0 +----- + + * Added `framework.http_cache` configuration tree + * Added `framework.trusted_proxies` and `framework.trusted_headers` configuration options + * Deprecated the public `form.factory`, `form.type.file`, `translator`, `security.csrf.token_manager`, `serializer`, + `cache_clearer`, `filesystem` and `validator` services to private. + * Added `TemplateAwareDataCollectorInterface` and `AbstractDataCollector` to simplify custom data collector creation and leverage autoconfiguration + * Add `cache.adapter.redis_tag_aware` tag to use `RedisCacheAwareAdapter` + * added `framework.http_client.retry_failing` configuration tree + * added `assertCheckboxChecked()` and `assertCheckboxNotChecked()` in `WebTestCase` + * added `assertFormValue()` and `assertNoFormValue()` in `WebTestCase` + * Added "--as-tree=3" option to `translation:update` command to dump messages as a tree-like structure. The given value defines the level where to switch to inline YAML + * Deprecated the `lock.RESOURCE_NAME` and `lock.RESOURCE_NAME.store` services and the `lock`, `LockInterface`, `lock.store` and `PersistingStoreInterface` aliases, use `lock.RESOURCE_NAME.factory`, `lock.factory` or `LockFactory` instead. + +5.1.0 +----- + * Removed `--no-backup` option from `translation:update` command (broken since `5.0.0`) + * Added link to source for controllers registered as named services + * Added link to source on controller on `router:match`/`debug:router` (when `framework.ide` is configured) + * Added the `framework.router.default_uri` configuration option to configure the default `RequestContext` + * Made `MicroKernelTrait::configureContainer()` compatible with `ContainerConfigurator` + * Added a new `mailer.message_bus` option to configure or disable the message bus to use to send mails. + * Added flex-compatible default implementation for `MicroKernelTrait::registerBundles()` + * Deprecated passing a `RouteCollectionBuilder` to `MicroKernelTrait::configureRoutes()`, type-hint `RoutingConfigurator` instead + * The `TemplateController` now accepts context argument + * Deprecated *not* setting the "framework.router.utf8" configuration option as it will default to `true` in Symfony 6.0 + * Added tag `routing.expression_language_function` to define functions available in route conditions + * Added `debug:container --deprecations` option to see compile-time deprecations. + * Made `BrowserKitAssertionsTrait` report the original error message in case of a failure + * Added ability for `config:dump-reference` and `debug:config` to dump and debug kernel container extension configuration. + * Deprecated `session.attribute_bag` service and `session.flash_bag` service. + +5.0.0 +----- + + * Removed support to load translation resources from the legacy directories `src/Resources/translations/` and `src/Resources//translations/` + * Removed `ControllerNameParser`. + * Removed `ResolveControllerNameSubscriber` + * Removed support for `bundle:controller:action` to reference controllers. Use `serviceOrFqcn::method` instead + * Removed support for PHP templating, use Twig instead + * Removed `Controller`, use `AbstractController` instead + * Removed `Client`, use `KernelBrowser` instead + * Removed `ContainerAwareCommand`, use dependency injection instead + * Removed the `validation.strict_email` option, use `validation.email_validation_mode` instead + * Removed the `cache.app.simple` service and its corresponding PSR-16 autowiring alias + * Removed cache-related compiler passes and `RequestDataCollector` + * Removed the `translator.selector` and `session.save_listener` services + * Removed `SecurityUserValueResolver`, use `UserValueResolver` instead + * Removed `routing.loader.service`. + * Service route loaders must be tagged with `routing.route_loader`. + * Added `slugger` service and `SluggerInterface` alias + * Removed the `lock.store.flock`, `lock.store.semaphore`, `lock.store.memcached.abstract` and `lock.store.redis.abstract` services. + * Removed the `router.cache_class_prefix` parameter. + +4.4.0 +----- + + * Added `lint:container` command to check that services wiring matches type declarations + * Added `MailerAssertionsTrait` + * Deprecated support for `templating` engine in `TemplateController`, use Twig instead + * Deprecated the `$parser` argument of `ControllerResolver::__construct()` and `DelegatingLoader::__construct()` + * Deprecated the `controller_name_converter` and `resolve_controller_name_subscriber` services + * The `ControllerResolver` and `DelegatingLoader` classes have been marked as `final` + * Added support for configuring chained cache pools + * Deprecated calling `WebTestCase::createClient()` while a kernel has been booted, ensure the kernel is shut down before calling the method + * Deprecated `routing.loader.service`, use `routing.loader.container` instead. + * Not tagging service route loaders with `routing.route_loader` has been deprecated. + * Overriding the methods `KernelTestCase::tearDown()` and `WebTestCase::tearDown()` without the `void` return-type is deprecated. + * Added new `error_controller` configuration to handle system exceptions + * Added sort option for `translation:update` command. + * [BC Break] The `framework.messenger.routing.senders` config key is not deeply merged anymore. + * Added `secrets:*` commands to deal with secrets seamlessly. + * Made `framework.session.handler_id` accept a DSN + * Marked the `RouterDataCollector` class as `@final`. + * [BC Break] The `framework.messenger.buses..middleware` config key is not deeply merged anymore. + * Moved `MailerAssertionsTrait` in `KernelTestCase` + +4.3.0 +----- + + * Deprecated the `framework.templating` option, configure the Twig bundle instead. + * Added `WebTestAssertionsTrait` (included by default in `WebTestCase`) + * Renamed `Client` to `KernelBrowser` + * Not passing the project directory to the constructor of the `AssetsInstallCommand` is deprecated. This argument will + be mandatory in 5.0. + * Deprecated the "Psr\SimpleCache\CacheInterface" / "cache.app.simple" service, use "Symfony\Contracts\Cache\CacheInterface" / "cache.app" instead + * Added the ability to specify a custom `serializer` option for each + transport under`framework.messenger.transports`. + * Added the `RegisterLocaleAwareServicesPass` and configured the `LocaleAwareListener` + * [BC Break] When using Messenger, the default transport changed from + using Symfony's serializer service to use `PhpSerializer`, which uses + PHP's native `serialize()` and `unserialize()` functions. To use the + original serialization method, set the `framework.messenger.default_serializer` + config option to `messenger.transport.symfony_serializer`. Or set the + `serializer` option under one specific `transport`. + * [BC Break] The `framework.messenger.serializer` config key changed to + `framework.messenger.default_serializer`, which holds the string service + id and `framework.messenger.symfony_serializer`, which configures the + options if you're using Symfony's serializer. + * [BC Break] Removed the `framework.messenger.routing.send_and_handle` configuration. + Instead of setting it to true, configure a `SyncTransport` and route messages to it. + * Added information about deprecated aliases in `debug:autowiring` + * Added php ini session options `sid_length` and `sid_bits_per_character` + to the `session` section of the configuration + * Added support for Translator paths, Twig paths in translation commands. + * Added support for PHP files with translations in translation commands. + * Added support for boolean container parameters within routes. + * Added the `messenger:setup-transports` command to setup messenger transports + * Added a `InMemoryTransport` to Messenger. Use it with a DSN starting with `in-memory://`. + * Added `framework.property_access.throw_exception_on_invalid_property_path` config option. + * Added `cache:pool:list` command to list all available cache pools. + +4.2.0 +----- + + * Added a `AbstractController::addLink()` method to add Link headers to the current response + * Allowed configuring taggable cache pools via a new `framework.cache.pools.tags` option (bool|service-id) + * Allowed configuring PDO-based cache pools via a new `cache.adapter.pdo` abstract service + * Deprecated auto-injection of the container in AbstractController instances, register them as service subscribers instead + * Deprecated processing of services tagged `security.expression_language_provider` in favor of a new `AddExpressionLanguageProvidersPass` in SecurityBundle. + * Deprecated the `Symfony\Bundle\FrameworkBundle\Controller\Controller` class in favor of `Symfony\Bundle\FrameworkBundle\Controller\AbstractController`. + * Enabled autoconfiguration for `Psr\Log\LoggerAwareInterface` + * Added new "auto" mode for `framework.session.cookie_secure` to turn it on when HTTPS is used + * Removed the `framework.messenger.encoder` and `framework.messenger.decoder` options. Use the `framework.messenger.serializer.id` option to replace the Messenger serializer. + * Deprecated the `ContainerAwareCommand` class in favor of `Symfony\Component\Console\Command\Command` + * Made `debug:container` and `debug:autowiring` ignore backslashes in service ids + * Deprecated the `Templating\Helper\TranslatorHelper::transChoice()` method, use the `trans()` one instead with a `%count%` parameter + * Deprecated `CacheCollectorPass`. Use `Symfony\Component\Cache\DependencyInjection\CacheCollectorPass` instead. + * Deprecated `CachePoolClearerPass`. Use `Symfony\Component\Cache\DependencyInjection\CachePoolClearerPass` instead. + * Deprecated `CachePoolPass`. Use `Symfony\Component\Cache\DependencyInjection\CachePoolPass` instead. + * Deprecated `CachePoolPrunerPass`. Use `Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass` instead. + * Deprecated support for legacy translations directories `src/Resources/translations/` and `src/Resources//translations/`, use `translations/` instead. + * Deprecated support for the legacy directory structure in `translation:update` and `debug:translation` commands. + +4.1.0 +----- + + * Allowed to pass an optional `LoggerInterface $logger` instance to the `Router` + * Added a new `parameter_bag` service with related autowiring aliases to access parameters as-a-service + * Allowed the `Router` to work with any PSR-11 container + * Added option in workflow dump command to label graph with a custom label + * Using a `RouterInterface` that does not implement the `WarmableInterface` is deprecated. + * Warming up a router in `RouterCacheWarmer` that does not implement the `WarmableInterface` is deprecated and will not + be supported anymore in 5.0. + * The `RequestDataCollector` class has been deprecated. Use the `Symfony\Component\HttpKernel\DataCollector\RequestDataCollector` class instead. + * The `RedirectController` class allows for 307/308 HTTP status codes + * Deprecated `bundle:controller:action` syntax to reference controllers. Use `serviceOrFqcn::method` instead where `serviceOrFqcn` + is either the service ID or the FQCN of the controller. + * Deprecated `Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser` + * The `container.service_locator` tag of `ServiceLocator`s is now autoconfigured. + * Add the ability to search a route in `debug:router`. + * Add the ability to use SameSite cookies for sessions. + +4.0.0 +----- + + * The default `type` option of the `framework.workflows.*` configuration entries is `state_machine` + * removed `AddConsoleCommandPass`, `AddConstraintValidatorsPass`, + `AddValidatorInitializersPass`, `CompilerDebugDumpPass`, `ConfigCachePass`, + `ControllerArgumentValueResolverPass`, `FormPass`, `PropertyInfoPass`, + `RoutingResolverPass`, `SerializerPass`, `ValidateWorkflowsPass` + * made `Translator::__construct()` `$defaultLocale` argument required + * removed `SessionListener`, `TestSessionListener` + * Removed `cache:clear` warmup part along with the `--no-optional-warmers` option + * Removed core form types services registration when unnecessary + * Removed `framework.serializer.cache` option and `serializer.mapping.cache.apc`, `serializer.mapping.cache.doctrine.apc` services + * Removed `ConstraintValidatorFactory` + * Removed class parameters related to routing + * Removed absolute template paths support in the template name parser + * Removed support of the `KERNEL_DIR` environment variable with `KernelTestCase::getKernelClass()`. + * Removed the `KernelTestCase::getPhpUnitXmlDir()` and `KernelTestCase::getPhpUnitCliConfigArgument()` methods. + * Removed the "framework.validation.cache" configuration option. Configure the "cache.validator" service under "framework.cache.pools" instead. + * Removed `PhpStringTokenParser`, use `Symfony\Component\Translation\Extractor\PhpStringTokenParser` instead. + * Removed `PhpExtractor`, use `Symfony\Component\Translation\Extractor\PhpExtractor` instead. + * Removed the `use_strict_mode` session option, it's is now enabled by default + 3.4.0 ----- diff --git a/lib/symfony/framework-bundle/CacheWarmer/AbstractPhpFileCacheWarmer.php b/lib/symfony/framework-bundle/CacheWarmer/AbstractPhpFileCacheWarmer.php index aca75b5071..17e066045a 100644 --- a/lib/symfony/framework-bundle/CacheWarmer/AbstractPhpFileCacheWarmer.php +++ b/lib/symfony/framework-bundle/CacheWarmer/AbstractPhpFileCacheWarmer.php @@ -11,30 +11,22 @@ namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; -use Psr\Cache\CacheItemPoolInterface; -use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\NullAdapter; use Symfony\Component\Cache\Adapter\PhpArrayAdapter; -use Symfony\Component\Cache\Adapter\ProxyAdapter; use Symfony\Component\Config\Resource\ClassExistenceResource; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface { private $phpArrayFile; - private $fallbackPool; /** - * @param string $phpArrayFile The PHP file where metadata are cached - * @param CacheItemPoolInterface $fallbackPool The pool where runtime-discovered metadata are cached + * @param string $phpArrayFile The PHP file where metadata are cached */ - public function __construct($phpArrayFile, CacheItemPoolInterface $fallbackPool) + public function __construct(string $phpArrayFile) { $this->phpArrayFile = $phpArrayFile; - if (!$fallbackPool instanceof AdapterInterface) { - $fallbackPool = new ProxyAdapter($fallbackPool); - } - $this->fallbackPool = $fallbackPool; } /** @@ -47,15 +39,17 @@ abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface /** * {@inheritdoc} + * + * @return string[] A list of classes to preload on PHP 7.4+ */ - public function warmUp($cacheDir) + public function warmUp(string $cacheDir) { $arrayAdapter = new ArrayAdapter(); spl_autoload_register([ClassExistenceResource::class, 'throwOnRequiredClass']); try { if (!$this->doWarmUp($cacheDir, $arrayAdapter)) { - return; + return []; } } finally { spl_autoload_unregister([ClassExistenceResource::class, 'throwOnRequiredClass']); @@ -66,24 +60,21 @@ abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface // so here we un-serialize the values first $values = array_map(function ($val) { return null !== $val ? unserialize($val) : null; }, $arrayAdapter->getValues()); - $this->warmUpPhpArrayAdapter(new PhpArrayAdapter($this->phpArrayFile, $this->fallbackPool), $values); - - foreach ($values as $k => $v) { - $item = $this->fallbackPool->getItem($k); - $this->fallbackPool->saveDeferred($item->set($v)); - } - $this->fallbackPool->commit(); + return $this->warmUpPhpArrayAdapter(new PhpArrayAdapter($this->phpArrayFile, new NullAdapter()), $values); } + /** + * @return string[] A list of classes to preload on PHP 7.4+ + */ protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values) { - $phpArrayAdapter->warmUp($values); + return (array) $phpArrayAdapter->warmUp($values); } /** * @internal */ - final protected function ignoreAutoloadException($class, \Exception $exception) + final protected function ignoreAutoloadException(string $class, \Exception $exception): void { try { ClassExistenceResource::throwOnRequiredClass($class, $exception); @@ -92,9 +83,7 @@ abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface } /** - * @param string $cacheDir - * * @return bool false if there is nothing to warm-up */ - abstract protected function doWarmUp($cacheDir, ArrayAdapter $arrayAdapter); + abstract protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter); } diff --git a/lib/symfony/framework-bundle/CacheWarmer/AnnotationsCacheWarmer.php b/lib/symfony/framework-bundle/CacheWarmer/AnnotationsCacheWarmer.php index 3859e07f4d..5504400179 100644 --- a/lib/symfony/framework-bundle/CacheWarmer/AnnotationsCacheWarmer.php +++ b/lib/symfony/framework-bundle/CacheWarmer/AnnotationsCacheWarmer.php @@ -12,11 +12,10 @@ namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; use Doctrine\Common\Annotations\AnnotationException; -use Doctrine\Common\Annotations\CachedReader; +use Doctrine\Common\Annotations\PsrCachedReader; use Doctrine\Common\Annotations\Reader; -use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; -use Symfony\Component\Cache\DoctrineProvider; +use Symfony\Component\Cache\Adapter\PhpArrayAdapter; /** * Warms up annotation caches for classes found in composer's autoload class map @@ -31,13 +30,11 @@ class AnnotationsCacheWarmer extends AbstractPhpFileCacheWarmer private $debug; /** - * @param string $phpArrayFile The PHP file where annotations are cached - * @param CacheItemPoolInterface $fallbackPool The pool where runtime-discovered annotations are cached - * @param bool $debug Run in debug mode + * @param string $phpArrayFile The PHP file where annotations are cached */ - public function __construct(Reader $annotationReader, $phpArrayFile, CacheItemPoolInterface $fallbackPool, $excludeRegexp = null, $debug = false) + public function __construct(Reader $annotationReader, string $phpArrayFile, string $excludeRegexp = null, bool $debug = false) { - parent::__construct($phpArrayFile, $fallbackPool); + parent::__construct($phpArrayFile); $this->annotationReader = $annotationReader; $this->excludeRegexp = $excludeRegexp; $this->debug = $debug; @@ -46,7 +43,7 @@ class AnnotationsCacheWarmer extends AbstractPhpFileCacheWarmer /** * {@inheritdoc} */ - protected function doWarmUp($cacheDir, ArrayAdapter $arrayAdapter) + protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter) { $annotatedClassPatterns = $cacheDir.'/annotations.map'; @@ -55,7 +52,7 @@ class AnnotationsCacheWarmer extends AbstractPhpFileCacheWarmer } $annotatedClasses = include $annotatedClassPatterns; - $reader = new CachedReader($this->annotationReader, new DoctrineProvider($arrayAdapter), $this->debug); + $reader = new PsrCachedReader($this->annotationReader, $arrayAdapter, $this->debug); foreach ($annotatedClasses as $class) { if (null !== $this->excludeRegexp && preg_match($this->excludeRegexp, $class)) { @@ -71,7 +68,18 @@ class AnnotationsCacheWarmer extends AbstractPhpFileCacheWarmer return true; } - private function readAllComponents(Reader $reader, $class) + /** + * @return string[] A list of classes to preload on PHP 7.4+ + */ + protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values) + { + // make sure we don't cache null values + $values = array_filter($values, function ($val) { return null !== $val; }); + + return parent::warmUpPhpArrayAdapter($phpArrayAdapter, $values); + } + + private function readAllComponents(Reader $reader, string $class) { $reflectionClass = new \ReflectionClass($class); diff --git a/lib/symfony/framework-bundle/CacheWarmer/CachePoolClearerCacheWarmer.php b/lib/symfony/framework-bundle/CacheWarmer/CachePoolClearerCacheWarmer.php new file mode 100644 index 0000000000..79bca24033 --- /dev/null +++ b/lib/symfony/framework-bundle/CacheWarmer/CachePoolClearerCacheWarmer.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; + +use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; + +/** + * Clears the cache pools when warming up the cache. + * + * Do not use in production! + * + * @author Teoh Han Hui + * + * @internal + */ +final class CachePoolClearerCacheWarmer implements CacheWarmerInterface +{ + private $poolClearer; + private $pools; + + /** + * @param string[] $pools + */ + public function __construct(Psr6CacheClearer $poolClearer, array $pools = []) + { + $this->poolClearer = $poolClearer; + $this->pools = $pools; + } + + /** + * {@inheritdoc} + * + * @return string[] + */ + public function warmUp(string $cacheDirectory): array + { + foreach ($this->pools as $pool) { + if ($this->poolClearer->hasPool($pool)) { + $this->poolClearer->clearPool($pool); + } + } + + return []; + } + + /** + * {@inheritdoc} + */ + public function isOptional(): bool + { + // optional cache warmers are not run when handling the request + return false; + } +} diff --git a/lib/symfony/framework-bundle/CacheWarmer/ClassCacheCacheWarmer.php b/lib/symfony/framework-bundle/CacheWarmer/ClassCacheCacheWarmer.php deleted file mode 100644 index 4a6bee4128..0000000000 --- a/lib/symfony/framework-bundle/CacheWarmer/ClassCacheCacheWarmer.php +++ /dev/null @@ -1,67 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; - -use Symfony\Component\ClassLoader\ClassCollectionLoader; -use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; - -/** - * Generates the Class Cache (classes.php) file. - * - * @author Tugdual Saunier - * - * @deprecated since version 3.3, to be removed in 4.0. - */ -class ClassCacheCacheWarmer implements CacheWarmerInterface -{ - private $declaredClasses; - - public function __construct(array $declaredClasses = null) - { - if (\PHP_VERSION_ID >= 70000) { - @trigger_error('The '.__CLASS__.' class is deprecated since Symfony 3.3 and will be removed in 4.0.', \E_USER_DEPRECATED); - } - - $this->declaredClasses = $declaredClasses; - } - - /** - * Warms up the cache. - * - * @param string $cacheDir The cache directory - */ - public function warmUp($cacheDir) - { - $classmap = $cacheDir.'/classes.map'; - - if (!is_file($classmap)) { - return; - } - - if (file_exists($cacheDir.'/classes.php')) { - return; - } - $declared = null !== $this->declaredClasses ? $this->declaredClasses : array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits()); - - ClassCollectionLoader::inline(include($classmap), $cacheDir.'/classes.php', $declared); - } - - /** - * Checks whether this warmer is optional or not. - * - * @return bool always true - */ - public function isOptional() - { - return true; - } -} diff --git a/lib/symfony/framework-bundle/CacheWarmer/ConfigBuilderCacheWarmer.php b/lib/symfony/framework-bundle/CacheWarmer/ConfigBuilderCacheWarmer.php new file mode 100644 index 0000000000..ed20bbcb64 --- /dev/null +++ b/lib/symfony/framework-bundle/CacheWarmer/ConfigBuilderCacheWarmer.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Config\Builder\ConfigBuilderGenerator; +use Symfony\Component\Config\Builder\ConfigBuilderGeneratorInterface; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * Generate all config builders. + * + * @author Tobias Nyholm + */ +class ConfigBuilderCacheWarmer implements CacheWarmerInterface +{ + private $kernel; + private $logger; + + public function __construct(KernelInterface $kernel, LoggerInterface $logger = null) + { + $this->kernel = $kernel; + $this->logger = $logger; + } + + /** + * {@inheritdoc} + * + * @return string[] + */ + public function warmUp(string $cacheDir) + { + $generator = new ConfigBuilderGenerator($cacheDir); + + foreach ($this->kernel->getBundles() as $bundle) { + $extension = $bundle->getContainerExtension(); + if (null === $extension) { + continue; + } + + try { + $this->dumpExtension($extension, $generator); + } catch (\Exception $e) { + if ($this->logger) { + $this->logger->warning('Failed to generate ConfigBuilder for extension {extensionClass}.', ['exception' => $e, 'extensionClass' => \get_class($extension)]); + } + } + } + + // No need to preload anything + return []; + } + + private function dumpExtension(ExtensionInterface $extension, ConfigBuilderGeneratorInterface $generator): void + { + $configuration = null; + if ($extension instanceof ConfigurationInterface) { + $configuration = $extension; + } elseif ($extension instanceof ConfigurationExtensionInterface) { + $configuration = $extension->getConfiguration([], new ContainerBuilder($this->kernel->getContainer()->getParameterBag())); + } + + if (!$configuration) { + return; + } + + $generator->build($configuration); + } + + /** + * {@inheritdoc} + */ + public function isOptional() + { + return true; + } +} diff --git a/lib/symfony/framework-bundle/CacheWarmer/RouterCacheWarmer.php b/lib/symfony/framework-bundle/CacheWarmer/RouterCacheWarmer.php index 5106f0c2e1..6cdf176bb3 100644 --- a/lib/symfony/framework-bundle/CacheWarmer/RouterCacheWarmer.php +++ b/lib/symfony/framework-bundle/CacheWarmer/RouterCacheWarmer.php @@ -12,56 +12,46 @@ namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; use Psr\Container\ContainerInterface; -use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; use Symfony\Component\Routing\RouterInterface; +use Symfony\Contracts\Service\ServiceSubscriberInterface; /** * Generates the router matcher and generator classes. * * @author Fabien Potencier * - * @final since version 3.4 + * @final */ class RouterCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface { - protected $router; + private $container; - /** - * @param ContainerInterface $container - */ - public function __construct($container) + public function __construct(ContainerInterface $container) { // As this cache warmer is optional, dependencies should be lazy-loaded, that's why a container should be injected. - if ($container instanceof ContainerInterface) { - $this->router = $container->get('router'); // For BC, the $router property must be populated in the constructor - } elseif ($container instanceof RouterInterface) { - $this->router = $container; - @trigger_error(sprintf('Using a "%s" as first argument of %s is deprecated since Symfony 3.4 and will be unsupported in version 4.0. Use a %s instead.', RouterInterface::class, __CLASS__, ContainerInterface::class), \E_USER_DEPRECATED); - } else { - throw new \InvalidArgumentException(sprintf('"%s" only accepts instance of Psr\Container\ContainerInterface as first argument.', __CLASS__)); - } + $this->container = $container; } /** - * Warms up the cache. - * - * @param string $cacheDir The cache directory + * {@inheritdoc} */ - public function warmUp($cacheDir) + public function warmUp(string $cacheDir): array { - if ($this->router instanceof WarmableInterface) { - $this->router->warmUp($cacheDir); + $router = $this->container->get('router'); + + if ($router instanceof WarmableInterface) { + return (array) $router->warmUp($cacheDir); } + + throw new \LogicException(sprintf('The router "%s" cannot be warmed up because it does not implement "%s".', get_debug_type($router), WarmableInterface::class)); } /** - * Checks whether this warmer is optional or not. - * - * @return bool always true + * {@inheritdoc} */ - public function isOptional() + public function isOptional(): bool { return true; } @@ -69,7 +59,7 @@ class RouterCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterf /** * {@inheritdoc} */ - public static function getSubscribedServices() + public static function getSubscribedServices(): array { return [ 'router' => RouterInterface::class, diff --git a/lib/symfony/framework-bundle/CacheWarmer/SerializerCacheWarmer.php b/lib/symfony/framework-bundle/CacheWarmer/SerializerCacheWarmer.php index e74206c865..0ada0ffc94 100644 --- a/lib/symfony/framework-bundle/CacheWarmer/SerializerCacheWarmer.php +++ b/lib/symfony/framework-bundle/CacheWarmer/SerializerCacheWarmer.php @@ -12,7 +12,6 @@ namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; use Doctrine\Common\Annotations\AnnotationException; -use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; @@ -31,20 +30,19 @@ class SerializerCacheWarmer extends AbstractPhpFileCacheWarmer private $loaders; /** - * @param LoaderInterface[] $loaders The serializer metadata loaders - * @param string $phpArrayFile The PHP file where metadata are cached - * @param CacheItemPoolInterface $fallbackPool The pool where runtime-discovered metadata are cached + * @param LoaderInterface[] $loaders The serializer metadata loaders + * @param string $phpArrayFile The PHP file where metadata are cached */ - public function __construct(array $loaders, $phpArrayFile, CacheItemPoolInterface $fallbackPool) + public function __construct(array $loaders, string $phpArrayFile) { - parent::__construct($phpArrayFile, $fallbackPool); + parent::__construct($phpArrayFile); $this->loaders = $loaders; } /** * {@inheritdoc} */ - protected function doWarmUp($cacheDir, ArrayAdapter $arrayAdapter) + protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter) { if (!class_exists(CacheClassMetadataFactory::class) || !method_exists(XmlFileLoader::class, 'getMappedClasses') || !method_exists(YamlFileLoader::class, 'getMappedClasses')) { return false; @@ -72,7 +70,7 @@ class SerializerCacheWarmer extends AbstractPhpFileCacheWarmer * * @return XmlFileLoader[]|YamlFileLoader[] */ - private function extractSupportedLoaders(array $loaders) + private function extractSupportedLoaders(array $loaders): array { $supportedLoaders = []; diff --git a/lib/symfony/framework-bundle/CacheWarmer/TemplateFinder.php b/lib/symfony/framework-bundle/CacheWarmer/TemplateFinder.php deleted file mode 100644 index e3eea295e6..0000000000 --- a/lib/symfony/framework-bundle/CacheWarmer/TemplateFinder.php +++ /dev/null @@ -1,111 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; - -use Symfony\Component\Finder\Finder; -use Symfony\Component\HttpKernel\Bundle\BundleInterface; -use Symfony\Component\HttpKernel\KernelInterface; -use Symfony\Component\Templating\TemplateNameParserInterface; -use Symfony\Component\Templating\TemplateReferenceInterface; - -/** - * Finds all the templates. - * - * @author Victor Berchet - */ -class TemplateFinder implements TemplateFinderInterface -{ - private $kernel; - private $parser; - private $rootDir; - private $templates; - - /** - * @param KernelInterface $kernel A KernelInterface instance - * @param TemplateNameParserInterface $parser A TemplateNameParserInterface instance - * @param string $rootDir The directory where global templates can be stored - */ - public function __construct(KernelInterface $kernel, TemplateNameParserInterface $parser, $rootDir) - { - $this->kernel = $kernel; - $this->parser = $parser; - $this->rootDir = $rootDir; - } - - /** - * Find all the templates in the bundle and in the kernel Resources folder. - * - * @return TemplateReferenceInterface[] - */ - public function findAllTemplates() - { - if (null !== $this->templates) { - return $this->templates; - } - - $templates = []; - - foreach ($this->kernel->getBundles() as $bundle) { - $templates = array_merge($templates, $this->findTemplatesInBundle($bundle)); - } - - $templates = array_merge($templates, $this->findTemplatesInFolder($this->rootDir.'/views')); - - return $this->templates = $templates; - } - - /** - * Find templates in the given directory. - * - * @param string $dir The folder where to look for templates - * - * @return TemplateReferenceInterface[] - */ - private function findTemplatesInFolder($dir) - { - $templates = []; - - if (is_dir($dir)) { - $finder = new Finder(); - foreach ($finder->files()->followLinks()->in($dir) as $file) { - $template = $this->parser->parse($file->getRelativePathname()); - if (false !== $template) { - $templates[] = $template; - } - } - } - - return $templates; - } - - /** - * Find templates in the given bundle. - * - * @param BundleInterface $bundle The bundle where to look for templates - * - * @return TemplateReferenceInterface[] - */ - private function findTemplatesInBundle(BundleInterface $bundle) - { - $name = $bundle->getName(); - $templates = array_unique(array_merge( - $this->findTemplatesInFolder($bundle->getPath().'/Resources/views'), - $this->findTemplatesInFolder($this->rootDir.'/'.$name.'/views') - )); - - foreach ($templates as $i => $template) { - $templates[$i] = $template->set('bundle', $name); - } - - return $templates; - } -} diff --git a/lib/symfony/framework-bundle/CacheWarmer/TemplateFinderInterface.php b/lib/symfony/framework-bundle/CacheWarmer/TemplateFinderInterface.php deleted file mode 100644 index 433ed8f548..0000000000 --- a/lib/symfony/framework-bundle/CacheWarmer/TemplateFinderInterface.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; - -/** - * Interface for finding all the templates. - * - * @author Victor Berchet - */ -interface TemplateFinderInterface -{ - /** - * Find all the templates. - * - * @return array An array of templates of type TemplateReferenceInterface - */ - public function findAllTemplates(); -} diff --git a/lib/symfony/framework-bundle/CacheWarmer/TemplatePathsCacheWarmer.php b/lib/symfony/framework-bundle/CacheWarmer/TemplatePathsCacheWarmer.php deleted file mode 100644 index 6662a1808d..0000000000 --- a/lib/symfony/framework-bundle/CacheWarmer/TemplatePathsCacheWarmer.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; - -use Symfony\Bundle\FrameworkBundle\Templating\Loader\TemplateLocator; -use Symfony\Component\Filesystem\Filesystem; -use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmer; - -/** - * Computes the association between template names and their paths on the disk. - * - * @author Fabien Potencier - */ -class TemplatePathsCacheWarmer extends CacheWarmer -{ - protected $finder; - protected $locator; - - public function __construct(TemplateFinderInterface $finder, TemplateLocator $locator) - { - $this->finder = $finder; - $this->locator = $locator; - } - - /** - * Warms up the cache. - * - * @param string $cacheDir The cache directory - */ - public function warmUp($cacheDir) - { - $filesystem = new Filesystem(); - $templates = []; - - foreach ($this->finder->findAllTemplates() as $template) { - $templates[$template->getLogicalName()] = rtrim($filesystem->makePathRelative($this->locator->locate($template), $cacheDir), '/'); - } - - $templates = str_replace("' => '", "' => __DIR__.'/", var_export($templates, true)); - - $this->writeCacheFile($cacheDir.'/templates.php', sprintf("container = $container; - } elseif ($container instanceof TranslatorInterface) { - $this->translator = $container; - @trigger_error(sprintf('Using a "%s" as first argument of %s is deprecated since Symfony 3.4 and will be unsupported in version 4.0. Use a %s instead.', TranslatorInterface::class, __CLASS__, ContainerInterface::class), \E_USER_DEPRECATED); - } else { - throw new \InvalidArgumentException(sprintf('"%s" only accepts instance of Psr\Container\ContainerInterface as first argument.', __CLASS__)); - } + $this->container = $container; } /** * {@inheritdoc} + * + * @return string[] */ - public function warmUp($cacheDir) + public function warmUp(string $cacheDir) { if (null === $this->translator) { $this->translator = $this->container->get('translator'); } if ($this->translator instanceof WarmableInterface) { - $this->translator->warmUp($cacheDir); + return (array) $this->translator->warmUp($cacheDir); } + + return []; } /** diff --git a/lib/symfony/framework-bundle/CacheWarmer/ValidatorCacheWarmer.php b/lib/symfony/framework-bundle/CacheWarmer/ValidatorCacheWarmer.php index 93c9eda6f1..3c6d582c43 100644 --- a/lib/symfony/framework-bundle/CacheWarmer/ValidatorCacheWarmer.php +++ b/lib/symfony/framework-bundle/CacheWarmer/ValidatorCacheWarmer.php @@ -12,16 +12,14 @@ namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; use Doctrine\Common\Annotations\AnnotationException; -use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\PhpArrayAdapter; -use Symfony\Component\Validator\Mapping\Cache\Psr6Cache; use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory; use Symfony\Component\Validator\Mapping\Loader\LoaderChain; use Symfony\Component\Validator\Mapping\Loader\LoaderInterface; use Symfony\Component\Validator\Mapping\Loader\XmlFileLoader; use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader; -use Symfony\Component\Validator\ValidatorBuilderInterface; +use Symfony\Component\Validator\ValidatorBuilder; /** * Warms up XML and YAML validator metadata. @@ -33,26 +31,25 @@ class ValidatorCacheWarmer extends AbstractPhpFileCacheWarmer private $validatorBuilder; /** - * @param string $phpArrayFile The PHP file where metadata are cached - * @param CacheItemPoolInterface $fallbackPool The pool where runtime-discovered metadata are cached + * @param string $phpArrayFile The PHP file where metadata are cached */ - public function __construct(ValidatorBuilderInterface $validatorBuilder, $phpArrayFile, CacheItemPoolInterface $fallbackPool) + public function __construct(ValidatorBuilder $validatorBuilder, string $phpArrayFile) { - parent::__construct($phpArrayFile, $fallbackPool); + parent::__construct($phpArrayFile); $this->validatorBuilder = $validatorBuilder; } /** * {@inheritdoc} */ - protected function doWarmUp($cacheDir, ArrayAdapter $arrayAdapter) + protected function doWarmUp(string $cacheDir, ArrayAdapter $arrayAdapter) { if (!method_exists($this->validatorBuilder, 'getLoaders')) { return false; } $loaders = $this->validatorBuilder->getLoaders(); - $metadataFactory = new LazyLoadingMetadataFactory(new LoaderChain($loaders), new Psr6Cache($arrayAdapter)); + $metadataFactory = new LazyLoadingMetadataFactory(new LoaderChain($loaders), $arrayAdapter); foreach ($this->extractSupportedLoaders($loaders) as $loader) { foreach ($loader->getMappedClasses() as $mappedClass) { @@ -71,10 +68,15 @@ class ValidatorCacheWarmer extends AbstractPhpFileCacheWarmer return true; } + /** + * @return string[] A list of classes to preload on PHP 7.4+ + */ protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values) { // make sure we don't cache null values - parent::warmUpPhpArrayAdapter($phpArrayAdapter, array_filter($values)); + $values = array_filter($values, function ($val) { return null !== $val; }); + + return parent::warmUpPhpArrayAdapter($phpArrayAdapter, $values); } /** @@ -82,7 +84,7 @@ class ValidatorCacheWarmer extends AbstractPhpFileCacheWarmer * * @return XmlFileLoader[]|YamlFileLoader[] */ - private function extractSupportedLoaders(array $loaders) + private function extractSupportedLoaders(array $loaders): array { $supportedLoaders = []; diff --git a/lib/symfony/framework-bundle/Client.php b/lib/symfony/framework-bundle/Client.php deleted file mode 100644 index 6c75b5ab14..0000000000 --- a/lib/symfony/framework-bundle/Client.php +++ /dev/null @@ -1,206 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle; - -use Symfony\Component\BrowserKit\CookieJar; -use Symfony\Component\BrowserKit\History; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Client as BaseClient; -use Symfony\Component\HttpKernel\KernelInterface; -use Symfony\Component\HttpKernel\Profiler\Profile as HttpProfile; - -/** - * Client simulates a browser and makes requests to a Kernel object. - * - * @author Fabien Potencier - */ -class Client extends BaseClient -{ - private $hasPerformedRequest = false; - private $profiler = false; - private $reboot = true; - - /** - * {@inheritdoc} - */ - public function __construct(KernelInterface $kernel, array $server = [], History $history = null, CookieJar $cookieJar = null) - { - parent::__construct($kernel, $server, $history, $cookieJar); - } - - /** - * Returns the container. - * - * @return ContainerInterface|null Returns null when the Kernel has been shutdown or not started yet - */ - public function getContainer() - { - return $this->kernel->getContainer(); - } - - /** - * Returns the kernel. - * - * @return KernelInterface - */ - public function getKernel() - { - return $this->kernel; - } - - /** - * Gets the profile associated with the current Response. - * - * @return HttpProfile|false A Profile instance - */ - public function getProfile() - { - if (null === $this->response || !$this->kernel->getContainer()->has('profiler')) { - return false; - } - - return $this->kernel->getContainer()->get('profiler')->loadProfileFromResponse($this->response); - } - - /** - * Enables the profiler for the very next request. - * - * If the profiler is not enabled, the call to this method does nothing. - */ - public function enableProfiler() - { - if ($this->kernel->getContainer()->has('profiler')) { - $this->profiler = true; - } - } - - /** - * Disables kernel reboot between requests. - * - * By default, the Client reboots the Kernel for each request. This method - * allows to keep the same kernel across requests. - */ - public function disableReboot() - { - $this->reboot = false; - } - - /** - * Enables kernel reboot between requests. - */ - public function enableReboot() - { - $this->reboot = true; - } - - /** - * {@inheritdoc} - * - * @param Request $request A Request instance - * - * @return Response A Response instance - */ - protected function doRequest($request) - { - // avoid shutting down the Kernel if no request has been performed yet - // WebTestCase::createClient() boots the Kernel but do not handle a request - if ($this->hasPerformedRequest && $this->reboot) { - $this->kernel->shutdown(); - } else { - $this->hasPerformedRequest = true; - } - - if ($this->profiler) { - $this->profiler = false; - - $this->kernel->boot(); - $this->kernel->getContainer()->get('profiler')->enable(); - } - - return parent::doRequest($request); - } - - /** - * {@inheritdoc} - * - * @param Request $request A Request instance - * - * @return Response A Response instance - */ - protected function doRequestInProcess($request) - { - $response = parent::doRequestInProcess($request); - - $this->profiler = false; - - return $response; - } - - /** - * Returns the script to execute when the request must be insulated. - * - * It assumes that the autoloader is named 'autoload.php' and that it is - * stored in the same directory as the kernel (this is the case for the - * Symfony Standard Edition). If this is not your case, create your own - * client and override this method. - * - * @param Request $request A Request instance - * - * @return string The script content - */ - protected function getScript($request) - { - $kernel = var_export(serialize($this->kernel), true); - $request = var_export(serialize($request), true); - $errorReporting = error_reporting(); - - $requires = ''; - foreach (get_declared_classes() as $class) { - if (0 === strpos($class, 'ComposerAutoloaderInit')) { - $r = new \ReflectionClass($class); - $file = \dirname(\dirname($r->getFileName())).'/autoload.php'; - if (file_exists($file)) { - $requires .= 'require_once '.var_export($file, true).";\n"; - } - } - } - - if (!$requires) { - throw new \RuntimeException('Composer autoloader not found.'); - } - - $requires .= 'require_once '.var_export((new \ReflectionObject($this->kernel))->getFileName(), true).";\n"; - - $profilerCode = ''; - if ($this->profiler) { - $profilerCode = '$kernel->getContainer()->get(\'profiler\')->enable();'; - } - - $code = <<boot(); -$profilerCode - -\$request = unserialize($request); -EOF; - - return $code.$this->getHandleScript(); - } -} diff --git a/lib/symfony/framework-bundle/Command/AboutCommand.php b/lib/symfony/framework-bundle/Command/AboutCommand.php index 7d4206a18c..e9660e55bd 100644 --- a/lib/symfony/framework-bundle/Command/AboutCommand.php +++ b/lib/symfony/framework-bundle/Command/AboutCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\Helper; use Symfony\Component\Console\Helper\TableSeparator; use Symfony\Component\Console\Input\InputInterface; @@ -24,11 +25,12 @@ use Symfony\Component\HttpKernel\KernelInterface; * * @author Roland Franssen * - * @final since version 3.4 + * @final */ -class AboutCommand extends ContainerAwareCommand +class AboutCommand extends Command { protected static $defaultName = 'about'; + protected static $defaultDescription = 'Display information about the current project'; /** * {@inheritdoc} @@ -36,15 +38,12 @@ class AboutCommand extends ContainerAwareCommand protected function configure() { $this - ->setDescription('Displays information about the current project') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOT' The %command.name% command displays information about the current Symfony project. The PHP section displays important configuration that could affect your application. The values might be different between web and CLI. - -The Environment section displays the current environment variables managed by Symfony Dotenv. It will not -be shown if no variables were found. The values might be different between web and CLI. EOT ) ; @@ -53,90 +52,85 @@ EOT /** * {@inheritdoc} */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); - /** @var $kernel KernelInterface */ + /** @var KernelInterface $kernel */ $kernel = $this->getApplication()->getKernel(); + if (method_exists($kernel, 'getBuildDir')) { + $buildDir = $kernel->getBuildDir(); + } else { + $buildDir = $kernel->getCacheDir(); + } + $rows = [ ['Symfony'], new TableSeparator(), ['Version', Kernel::VERSION], - ['End of maintenance', Kernel::END_OF_MAINTENANCE.(self::isExpired(Kernel::END_OF_MAINTENANCE) ? ' Expired' : '')], - ['End of life', Kernel::END_OF_LIFE.(self::isExpired(Kernel::END_OF_LIFE) ? ' Expired' : '')], + ['Long-Term Support', 4 === Kernel::MINOR_VERSION ? 'Yes' : 'No'], + ['End of maintenance', Kernel::END_OF_MAINTENANCE.(self::isExpired(Kernel::END_OF_MAINTENANCE) ? ' Expired' : ' ('.self::daysBeforeExpiration(Kernel::END_OF_MAINTENANCE).')')], + ['End of life', Kernel::END_OF_LIFE.(self::isExpired(Kernel::END_OF_LIFE) ? ' Expired' : ' ('.self::daysBeforeExpiration(Kernel::END_OF_LIFE).')')], new TableSeparator(), ['Kernel'], new TableSeparator(), ['Type', \get_class($kernel)], - ['Name', $kernel->getName()], ['Environment', $kernel->getEnvironment()], ['Debug', $kernel->isDebug() ? 'true' : 'false'], ['Charset', $kernel->getCharset()], - ['Root directory', self::formatPath($kernel->getRootDir(), $kernel->getProjectDir())], ['Cache directory', self::formatPath($kernel->getCacheDir(), $kernel->getProjectDir()).' ('.self::formatFileSize($kernel->getCacheDir()).')'], + ['Build directory', self::formatPath($buildDir, $kernel->getProjectDir()).' ('.self::formatFileSize($buildDir).')'], ['Log directory', self::formatPath($kernel->getLogDir(), $kernel->getProjectDir()).' ('.self::formatFileSize($kernel->getLogDir()).')'], new TableSeparator(), ['PHP'], new TableSeparator(), ['Version', \PHP_VERSION], ['Architecture', (\PHP_INT_SIZE * 8).' bits'], - ['Intl locale', class_exists('Locale', false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a'], + ['Intl locale', class_exists(\Locale::class, false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a'], ['Timezone', date_default_timezone_get().' ('.(new \DateTime())->format(\DateTime::W3C).')'], - ['OPcache', \extension_loaded('Zend OPcache') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'], - ['APCu', \extension_loaded('apcu') && filter_var(ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'], + ['OPcache', \extension_loaded('Zend OPcache') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'], + ['APCu', \extension_loaded('apcu') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false'], ['Xdebug', \extension_loaded('xdebug') ? 'true' : 'false'], ]; - if ($dotenv = self::getDotenvVars()) { - $rows = array_merge($rows, [ - new TableSeparator(), - ['Environment (.env)'], - new TableSeparator(), - ], array_map(function ($value, $name) { - return [$name, $value]; - }, $dotenv, array_keys($dotenv))); - } - $io->table([], $rows); + + return 0; } - private static function formatPath($path, $baseDir = null) + private static function formatPath(string $path, string $baseDir): string { - return null !== $baseDir ? preg_replace('~^'.preg_quote($baseDir, '~').'~', '.', $path) : $path; + return preg_replace('~^'.preg_quote($baseDir, '~').'~', '.', $path); } - private static function formatFileSize($path) + private static function formatFileSize(string $path): string { if (is_file($path)) { $size = filesize($path) ?: 0; } else { $size = 0; foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS | \RecursiveDirectoryIterator::FOLLOW_SYMLINKS)) as $file) { - $size += $file->getSize(); + if ($file->isReadable()) { + $size += $file->getSize(); + } } } return Helper::formatMemory($size); } - private static function isExpired($date) + private static function isExpired(string $date): bool { $date = \DateTime::createFromFormat('d/m/Y', '01/'.$date); return false !== $date && new \DateTime() > $date->modify('last day of this month 23:59:59'); } - private static function getDotenvVars() + private static function daysBeforeExpiration(string $date): string { - $vars = []; - foreach (explode(',', getenv('SYMFONY_DOTENV_VARS')) as $name) { - if ('' !== $name && false !== $value = getenv($name)) { - $vars[$name] = $value; - } - } + $date = \DateTime::createFromFormat('d/m/Y', '01/'.$date); - return $vars; + return (new \DateTime())->diff($date->modify('last day of this month 23:59:59'))->format('in %R%a days'); } } diff --git a/lib/symfony/framework-bundle/Command/AbstractConfigCommand.php b/lib/symfony/framework-bundle/Command/AbstractConfigCommand.php index 3e5b4698cf..f0d5a98148 100644 --- a/lib/symfony/framework-bundle/Command/AbstractConfigCommand.php +++ b/lib/symfony/framework-bundle/Command/AbstractConfigCommand.php @@ -16,6 +16,7 @@ use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\StyleInterface; +use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; /** @@ -59,11 +60,27 @@ abstract class AbstractConfigCommand extends ContainerDebugCommand /** * @return ExtensionInterface */ - protected function findExtension($name) + protected function findExtension(string $name) { $bundles = $this->initializeBundles(); $minScore = \INF; + $kernel = $this->getApplication()->getKernel(); + if ($kernel instanceof ExtensionInterface && ($kernel instanceof ConfigurationInterface || $kernel instanceof ConfigurationExtensionInterface)) { + if ($name === $kernel->getAlias()) { + return $kernel; + } + + if ($kernel->getAlias()) { + $distance = levenshtein($name, $kernel->getAlias()); + + if ($distance < $minScore) { + $guess = $kernel->getAlias(); + $minScore = $distance; + } + } + } + foreach ($bundles as $bundle) { if ($name === $bundle->getName()) { if (!$bundle->getContainerExtension()) { @@ -79,24 +96,24 @@ abstract class AbstractConfigCommand extends ContainerDebugCommand $guess = $bundle->getName(); $minScore = $distance; } + } - $extension = $bundle->getContainerExtension(); + $container = $this->getContainerBuilder($kernel); - if ($extension) { - if ($name === $extension->getAlias()) { - return $extension; - } + if ($container->hasExtension($name)) { + return $container->getExtension($name); + } - $distance = levenshtein($name, $extension->getAlias()); + foreach ($container->getExtensions() as $extension) { + $distance = levenshtein($name, $extension->getAlias()); - if ($distance < $minScore) { - $guess = $extension->getAlias(); - $minScore = $distance; - } + if ($distance < $minScore) { + $guess = $extension->getAlias(); + $minScore = $distance; } } - if ('Bundle' !== substr($name, -6)) { + if (!str_ends_with($name, 'Bundle')) { $message = sprintf('No extensions with configuration available for "%s".', $name); } else { $message = sprintf('No extension with alias "%s" is enabled.', $name); @@ -116,7 +133,7 @@ abstract class AbstractConfigCommand extends ContainerDebugCommand } if (!$configuration instanceof ConfigurationInterface) { - throw new \LogicException(sprintf('Configuration class "%s" should implement ConfigurationInterface in order to be dumpable.', \get_class($configuration))); + throw new \LogicException(sprintf('Configuration class "%s" should implement ConfigurationInterface in order to be dumpable.', get_debug_type($configuration))); } } @@ -124,8 +141,9 @@ abstract class AbstractConfigCommand extends ContainerDebugCommand { // Re-build bundle manually to initialize DI extensions that can be extended by other bundles in their build() method // as this method is not called when the container is loaded from the cache. - $container = $this->getContainerBuilder(); - $bundles = $this->getApplication()->getKernel()->getBundles(); + $kernel = $this->getApplication()->getKernel(); + $container = $this->getContainerBuilder($kernel); + $bundles = $kernel->getBundles(); foreach ($bundles as $bundle) { if ($extension = $bundle->getContainerExtension()) { $container->registerExtension($extension); diff --git a/lib/symfony/framework-bundle/Command/AssetsInstallCommand.php b/lib/symfony/framework-bundle/Command/AssetsInstallCommand.php index 394fedadf8..870e686de9 100644 --- a/lib/symfony/framework-bundle/Command/AssetsInstallCommand.php +++ b/lib/symfony/framework-bundle/Command/AssetsInstallCommand.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -22,6 +23,7 @@ use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; use Symfony\Component\HttpKernel\Bundle\BundleInterface; +use Symfony\Component\HttpKernel\KernelInterface; /** * Command that places bundle web assets into a given directory. @@ -29,34 +31,26 @@ use Symfony\Component\HttpKernel\Bundle\BundleInterface; * @author Fabien Potencier * @author Gábor Egyed * - * @final since version 3.4 + * @final */ -class AssetsInstallCommand extends ContainerAwareCommand +class AssetsInstallCommand extends Command { - const METHOD_COPY = 'copy'; - const METHOD_ABSOLUTE_SYMLINK = 'absolute symlink'; - const METHOD_RELATIVE_SYMLINK = 'relative symlink'; + public const METHOD_COPY = 'copy'; + public const METHOD_ABSOLUTE_SYMLINK = 'absolute symlink'; + public const METHOD_RELATIVE_SYMLINK = 'relative symlink'; protected static $defaultName = 'assets:install'; + protected static $defaultDescription = 'Install bundle\'s web assets under a public directory'; private $filesystem; + private $projectDir; - /** - * @param Filesystem $filesystem - */ - public function __construct($filesystem = null) + public function __construct(Filesystem $filesystem, string $projectDir) { - if (!$filesystem instanceof Filesystem) { - @trigger_error(sprintf('%s() expects an instance of "%s" as first argument since Symfony 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.', __METHOD__, Filesystem::class), \E_USER_DEPRECATED); - - parent::__construct($filesystem); - - return; - } - parent::__construct(); $this->filesystem = $filesystem; + $this->projectDir = $projectDir; } /** @@ -68,9 +62,10 @@ class AssetsInstallCommand extends ContainerAwareCommand ->setDefinition([ new InputArgument('target', InputArgument::OPTIONAL, 'The target directory', null), ]) - ->addOption('symlink', null, InputOption::VALUE_NONE, 'Symlinks the assets instead of copying it') + ->addOption('symlink', null, InputOption::VALUE_NONE, 'Symlink the assets instead of copying them') ->addOption('relative', null, InputOption::VALUE_NONE, 'Make relative symlinks') - ->setDescription('Installs bundles web assets under a public directory') + ->addOption('no-cleanup', null, InputOption::VALUE_NONE, 'Do not remove the assets of the bundles that no longer exist') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOT' The %command.name% command installs bundle assets into a given directory (e.g. the public directory). @@ -97,32 +92,20 @@ EOT /** * {@inheritdoc} */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { - // BC to be removed in 4.0 - if (null === $this->filesystem) { - $this->filesystem = $this->getContainer()->get('filesystem'); - $baseDir = $this->getContainer()->getParameter('kernel.project_dir'); - } - + /** @var KernelInterface $kernel */ $kernel = $this->getApplication()->getKernel(); - $targetArg = rtrim($input->getArgument('target'), '/'); - + $targetArg = rtrim($input->getArgument('target') ?? '', '/'); if (!$targetArg) { - $targetArg = $this->getPublicDirectory($this->getContainer()); + $targetArg = $this->getPublicDirectory($kernel->getContainer()); } if (!is_dir($targetArg)) { - $targetArg = (isset($baseDir) ? $baseDir : $kernel->getContainer()->getParameter('kernel.project_dir')).'/'.$targetArg; + $targetArg = $kernel->getProjectDir().'/'.$targetArg; if (!is_dir($targetArg)) { - // deprecated, logic to be removed in 4.0 - // this allows the commands to work out of the box with web/ and public/ - if (is_dir(\dirname($targetArg).'/web')) { - $targetArg = \dirname($targetArg).'/web'; - } else { - throw new InvalidArgumentException(sprintf('The target directory "%s" does not exist.', $targetArg)); - } + throw new InvalidArgumentException(sprintf('The target directory "%s" does not exist.', $targetArg)); } } @@ -150,7 +133,7 @@ EOT $validAssetDirs = []; /** @var BundleInterface $bundle */ foreach ($kernel->getBundles() as $bundle) { - if (!is_dir($originDir = $bundle->getPath().'/Resources/public')) { + if (!is_dir($originDir = $bundle->getPath().'/Resources/public') && !is_dir($originDir = $bundle->getPath().'/public')) { continue; } @@ -190,7 +173,7 @@ EOT } } // remove the assets of the bundles that no longer exist - if (is_dir($bundlesDir)) { + if (!$input->getOption('no-cleanup') && is_dir($bundlesDir)) { $dirsToRemove = Finder::create()->depth(0)->directories()->exclude($validAssetDirs)->in($bundlesDir); $this->filesystem->remove($dirsToRemove); } @@ -215,13 +198,8 @@ EOT * Try to create relative symlink. * * Falling back to absolute symlink and finally hard copy. - * - * @param string $originDir - * @param string $targetDir - * - * @return string */ - private function relativeSymlinkWithFallback($originDir, $targetDir) + private function relativeSymlinkWithFallback(string $originDir, string $targetDir): string { try { $this->symlink($originDir, $targetDir, true); @@ -237,13 +215,8 @@ EOT * Try to create absolute symlink. * * Falling back to hard copy. - * - * @param string $originDir - * @param string $targetDir - * - * @return string */ - private function absoluteSymlinkWithFallback($originDir, $targetDir) + private function absoluteSymlinkWithFallback(string $originDir, string $targetDir): string { try { $this->symlink($originDir, $targetDir); @@ -259,13 +232,9 @@ EOT /** * Creates symbolic link. * - * @param string $originDir - * @param string $targetDir - * @param bool $relative - * - * @throws IOException if link can not be created + * @throws IOException if link cannot be created */ - private function symlink($originDir, $targetDir, $relative = false) + private function symlink(string $originDir, string $targetDir, bool $relative = false) { if ($relative) { $this->filesystem->mkdir(\dirname($targetDir)); @@ -279,13 +248,8 @@ EOT /** * Copies origin to target. - * - * @param string $originDir - * @param string $targetDir - * - * @return string */ - private function hardCopy($originDir, $targetDir) + private function hardCopy(string $originDir, string $targetDir): string { $this->filesystem->mkdir($targetDir, 0777); // We use a custom iterator to ignore VCS files @@ -294,15 +258,15 @@ EOT return self::METHOD_COPY; } - private function getPublicDirectory(ContainerInterface $container) + private function getPublicDirectory(ContainerInterface $container): string { $defaultPublicDir = 'public'; - if (!$container->hasParameter('kernel.project_dir')) { + if (null === $this->projectDir && !$container->hasParameter('kernel.project_dir')) { return $defaultPublicDir; } - $composerFilePath = $container->getParameter('kernel.project_dir').'/composer.json'; + $composerFilePath = ($this->projectDir ?? $container->getParameter('kernel.project_dir')).'/composer.json'; if (!file_exists($composerFilePath)) { return $defaultPublicDir; @@ -310,10 +274,6 @@ EOT $composerConfig = json_decode(file_get_contents($composerFilePath), true); - if (isset($composerConfig['extra']['public-dir'])) { - return $composerConfig['extra']['public-dir']; - } - - return $defaultPublicDir; + return $composerConfig['extra']['public-dir'] ?? $defaultPublicDir; } } diff --git a/lib/symfony/framework-bundle/Command/BuildDebugContainerTrait.php b/lib/symfony/framework-bundle/Command/BuildDebugContainerTrait.php new file mode 100644 index 0000000000..785027dbc8 --- /dev/null +++ b/lib/symfony/framework-bundle/Command/BuildDebugContainerTrait.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * @internal + * + * @author Robin Chalas + * @author Nicolas Grekas + */ +trait BuildDebugContainerTrait +{ + protected $containerBuilder; + + /** + * Loads the ContainerBuilder from the cache. + * + * @throws \LogicException + */ + protected function getContainerBuilder(KernelInterface $kernel): ContainerBuilder + { + if ($this->containerBuilder) { + return $this->containerBuilder; + } + + if (!$kernel->isDebug() || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) { + $buildContainer = \Closure::bind(function () { + $this->initializeBundles(); + + return $this->buildContainer(); + }, $kernel, \get_class($kernel)); + $container = $buildContainer(); + $container->getCompilerPassConfig()->setRemovingPasses([]); + $container->getCompilerPassConfig()->setAfterRemovingPasses([]); + $container->compile(); + } else { + (new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump')); + $locatorPass = new ServiceLocatorTagPass(); + $locatorPass->process($container); + + $container->getCompilerPassConfig()->setBeforeOptimizationPasses([]); + $container->getCompilerPassConfig()->setOptimizationPasses([]); + $container->getCompilerPassConfig()->setBeforeRemovingPasses([]); + } + + return $this->containerBuilder = $container; + } +} diff --git a/lib/symfony/framework-bundle/Command/CacheClearCommand.php b/lib/symfony/framework-bundle/Command/CacheClearCommand.php index aa8541ea1b..b0d5565398 100644 --- a/lib/symfony/framework-bundle/Command/CacheClearCommand.php +++ b/lib/symfony/framework-bundle/Command/CacheClearCommand.php @@ -11,17 +11,18 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\RuntimeException; 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\DependencyInjection\Dumper\Preloader; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; -use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\HttpKernel\RebootableInterface; /** @@ -30,33 +31,22 @@ use Symfony\Component\HttpKernel\RebootableInterface; * @author Francis Besset * @author Fabien Potencier * - * @final since version 3.4 + * @final */ -class CacheClearCommand extends ContainerAwareCommand +class CacheClearCommand extends Command { protected static $defaultName = 'cache:clear'; + protected static $defaultDescription = 'Clear the cache'; private $cacheClearer; private $filesystem; - private $warning; - /** - * @param CacheClearerInterface $cacheClearer - */ - public function __construct($cacheClearer = null, Filesystem $filesystem = null) + public function __construct(CacheClearerInterface $cacheClearer, Filesystem $filesystem = null) { - if (!$cacheClearer instanceof CacheClearerInterface) { - @trigger_error(sprintf('%s() expects an instance of "%s" as first argument since Symfony 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.', __METHOD__, CacheClearerInterface::class), \E_USER_DEPRECATED); - - parent::__construct($cacheClearer); - - return; - } - parent::__construct(); $this->cacheClearer = $cacheClearer; - $this->filesystem = $filesystem ?: new Filesystem(); + $this->filesystem = $filesystem ?? new Filesystem(); } /** @@ -69,9 +59,9 @@ class CacheClearCommand extends ContainerAwareCommand new InputOption('no-warmup', '', InputOption::VALUE_NONE, 'Do not warm up the cache'), new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'), ]) - ->setDescription('Clears the cache') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' -The %command.name% command clears the application cache for a given environment +The %command.name% command clears and warms up the application cache for a given environment and debug mode: php %command.full_name% --env=dev @@ -84,98 +74,122 @@ EOF /** * {@inheritdoc} */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { - // BC to be removed in 4.0 - if (null === $this->cacheClearer) { - $this->cacheClearer = $this->getContainer()->get('cache_clearer'); - $this->filesystem = $this->getContainer()->get('filesystem'); - $realCacheDir = $this->getContainer()->getParameter('kernel.cache_dir'); - } - $fs = $this->filesystem; $io = new SymfonyStyle($input, $output); $kernel = $this->getApplication()->getKernel(); - $realCacheDir = isset($realCacheDir) ? $realCacheDir : $kernel->getContainer()->getParameter('kernel.cache_dir'); + $realCacheDir = $kernel->getContainer()->getParameter('kernel.cache_dir'); + $realBuildDir = $kernel->getContainer()->hasParameter('kernel.build_dir') ? $kernel->getContainer()->getParameter('kernel.build_dir') : $realCacheDir; // the old cache dir name must not be longer than the real one to avoid exceeding // the maximum length of a directory or file path within it (esp. Windows MAX_PATH) - $oldCacheDir = substr($realCacheDir, 0, -1).('~' === substr($realCacheDir, -1) ? '+' : '~'); + $oldCacheDir = substr($realCacheDir, 0, -1).(str_ends_with($realCacheDir, '~') ? '+' : '~'); $fs->remove($oldCacheDir); if (!is_writable($realCacheDir)) { throw new RuntimeException(sprintf('Unable to write in the "%s" directory.', $realCacheDir)); } + $useBuildDir = $realBuildDir !== $realCacheDir; + $oldBuildDir = substr($realBuildDir, 0, -1).('~' === substr($realBuildDir, -1) ? '+' : '~'); + if ($useBuildDir) { + $fs->remove($oldBuildDir); + + if (!is_writable($realBuildDir)) { + throw new RuntimeException(sprintf('Unable to write in the "%s" directory.', $realBuildDir)); + } + + if ($this->isNfs($realCacheDir)) { + $fs->remove($realCacheDir); + } else { + $fs->rename($realCacheDir, $oldCacheDir); + } + $fs->mkdir($realCacheDir); + } + $io->comment(sprintf('Clearing the cache for the %s environment with debug %s', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); + if ($useBuildDir) { + $this->cacheClearer->clear($realBuildDir); + } $this->cacheClearer->clear($realCacheDir); // The current event dispatcher is stale, let's not use it anymore $this->getApplication()->setDispatcher(new EventDispatcher()); - $containerDir = new \ReflectionObject($kernel->getContainer()); - $containerDir = basename(\dirname($containerDir->getFileName())); + $containerFile = (new \ReflectionObject($kernel->getContainer()))->getFileName(); + $containerDir = basename(\dirname($containerFile)); // the warmup cache dir name must have the same length as the real one // to avoid the many problems in serialized resources files - $warmupDir = substr($realCacheDir, 0, -1).('_' === substr($realCacheDir, -1) ? '-' : '_'); + $warmupDir = substr($realBuildDir, 0, -1).('_' === substr($realBuildDir, -1) ? '-' : '_'); if ($output->isVerbose() && $fs->exists($warmupDir)) { $io->comment('Clearing outdated warmup directory...'); } $fs->remove($warmupDir); - $fs->mkdir($warmupDir); - if (!$input->getOption('no-warmup')) { + if ($_SERVER['REQUEST_TIME'] <= filemtime($containerFile) && filemtime($containerFile) <= time()) { if ($output->isVerbose()) { - $io->comment('Warming up cache...'); + $io->comment('Cache is fresh.'); } - $this->warmup($warmupDir, $realCacheDir, !$input->getOption('no-optional-warmers')); - - if ($this->warning) { - @trigger_error($this->warning, \E_USER_DEPRECATED); - $io->warning($this->warning); - $this->warning = null; - } - } - - if (!$fs->exists($warmupDir.'/'.$containerDir)) { - $fs->rename($realCacheDir.'/'.$containerDir, $warmupDir.'/'.$containerDir); - touch($warmupDir.'/'.$containerDir.'.legacy'); - } - - if ('/' === \DIRECTORY_SEPARATOR && $mounts = @file('/proc/mounts')) { - foreach ($mounts as $mount) { - $mount = \array_slice(explode(' ', $mount), 1, -3); - if (!\in_array(array_pop($mount), ['vboxsf', 'nfs'])) { - continue; + if (!$input->getOption('no-warmup') && !$input->getOption('no-optional-warmers')) { + if ($output->isVerbose()) { + $io->comment('Warming up optional cache...'); } - $mount = implode(' ', $mount).'/'; + $warmer = $kernel->getContainer()->get('cache_warmer'); + // non optional warmers already ran during container compilation + $warmer->enableOnlyOptionalWarmers(); + $preload = (array) $warmer->warmUp($realCacheDir); - if (0 === strpos($realCacheDir, $mount)) { - $io->note('For better performances, you should move the cache and log directories to a non-shared folder of the VM.'); - $oldCacheDir = false; - break; + if ($preload && file_exists($preloadFile = $realCacheDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) { + Preloader::append($preloadFile, $preload); } } - } - - if ($oldCacheDir) { - $fs->rename($realCacheDir, $oldCacheDir); } else { - $fs->remove($realCacheDir); - } - $fs->rename($warmupDir, $realCacheDir); + $fs->mkdir($warmupDir); - if ($output->isVerbose()) { - $io->comment('Removing old cache directory...'); - } + if (!$input->getOption('no-warmup')) { + if ($output->isVerbose()) { + $io->comment('Warming up cache...'); + } + $this->warmup($warmupDir, $realCacheDir, !$input->getOption('no-optional-warmers')); + } + + if (!$fs->exists($warmupDir.'/'.$containerDir)) { + $fs->rename($realBuildDir.'/'.$containerDir, $warmupDir.'/'.$containerDir); + touch($warmupDir.'/'.$containerDir.'.legacy'); + } + + if ($this->isNfs($realBuildDir)) { + $io->note('For better performances, you should move the cache and log directories to a non-shared folder of the VM.'); + $fs->remove($realBuildDir); + } else { + $fs->rename($realBuildDir, $oldBuildDir); + } + + $fs->rename($warmupDir, $realBuildDir); - try { - $fs->remove($oldCacheDir); - } catch (IOException $e) { if ($output->isVerbose()) { - $io->warning($e->getMessage()); + $io->comment('Removing old build and cache directory...'); + } + + if ($useBuildDir) { + try { + $fs->remove($oldBuildDir); + } catch (IOException $e) { + if ($output->isVerbose()) { + $io->warning($e->getMessage()); + } + } + } + + try { + $fs->remove($oldCacheDir); + } catch (IOException $e) { + if ($output->isVerbose()) { + $io->warning($e->getMessage()); + } } } @@ -184,177 +198,64 @@ EOF } $io->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully cleared.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); + + return 0; } - /** - * @param string $warmupDir - * @param string $realCacheDir - * @param bool $enableOptionalWarmers - */ - protected function warmup($warmupDir, $realCacheDir, $enableOptionalWarmers = true) + private function isNfs(string $dir): bool + { + static $mounts = null; + + if (null === $mounts) { + $mounts = []; + if ('/' === \DIRECTORY_SEPARATOR && $files = @file('/proc/mounts')) { + foreach ($files as $mount) { + $mount = \array_slice(explode(' ', $mount), 1, -3); + if (!\in_array(array_pop($mount), ['vboxsf', 'nfs'])) { + continue; + } + $mounts[] = implode(' ', $mount).'/'; + } + } + } + foreach ($mounts as $mount) { + if (0 === strpos($dir, $mount)) { + return true; + } + } + + return false; + } + + private function warmup(string $warmupDir, string $realBuildDir, bool $enableOptionalWarmers = true) { // create a temporary kernel - $realKernel = $this->getApplication()->getKernel(); - if ($realKernel instanceof RebootableInterface) { - $realKernel->reboot($warmupDir); - $tempKernel = $realKernel; - } else { - $this->warning = 'Calling "cache:clear" with a kernel that does not implement "Symfony\Component\HttpKernel\RebootableInterface" is deprecated since Symfony 3.4 and will be unsupported in 4.0.'; - $realKernelClass = \get_class($realKernel); - $namespace = ''; - if (false !== $pos = strrpos($realKernelClass, '\\')) { - $namespace = substr($realKernelClass, 0, $pos); - $realKernelClass = substr($realKernelClass, $pos + 1); - } - $tempKernel = $this->getTempKernel($realKernel, $namespace, $realKernelClass, $warmupDir); - $tempKernel->boot(); - - $tempKernelReflection = new \ReflectionObject($tempKernel); - $tempKernelFile = $tempKernelReflection->getFileName(); + $kernel = $this->getApplication()->getKernel(); + if (!$kernel instanceof RebootableInterface) { + throw new \LogicException('Calling "cache:clear" with a kernel that does not implement "Symfony\Component\HttpKernel\RebootableInterface" is not supported.'); } + $kernel->reboot($warmupDir); // warmup temporary dir - $warmer = $tempKernel->getContainer()->get('cache_warmer'); if ($enableOptionalWarmers) { - $warmer->enableOptionalWarmers(); + $warmer = $kernel->getContainer()->get('cache_warmer'); + // non optional warmers already ran during container compilation + $warmer->enableOnlyOptionalWarmers(); + $preload = (array) $warmer->warmUp($warmupDir); + + if ($preload && file_exists($preloadFile = $warmupDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) { + Preloader::append($preloadFile, $preload); + } } - $warmer->warmUp($warmupDir); // fix references to cached files with the real cache directory name $search = [$warmupDir, str_replace('\\', '\\\\', $warmupDir)]; - $replace = str_replace('\\', '/', $realCacheDir); + $replace = str_replace('\\', '/', $realBuildDir); foreach (Finder::create()->files()->in($warmupDir) as $file) { $content = str_replace($search, $replace, file_get_contents($file), $count); if ($count) { file_put_contents($file, $content); } } - - if ($realKernel instanceof RebootableInterface) { - return; - } - - // fix references to the Kernel in .meta files - $safeTempKernel = str_replace('\\', '\\\\', \get_class($tempKernel)); - $realKernelFQN = \get_class($realKernel); - - foreach (Finder::create()->files()->depth('<3')->name('*.meta')->in($warmupDir) as $file) { - file_put_contents($file, preg_replace( - '/(C\:\d+\:)"'.$safeTempKernel.'"/', - sprintf('$1"%s"', $realKernelFQN), - file_get_contents($file) - )); - } - - // fix references to container's class - $tempContainerClass = $tempKernel->getContainerClass(); - $realContainerClass = $tempKernel->getRealContainerClass(); - foreach (Finder::create()->files()->depth('<2')->name($tempContainerClass.'*')->in($warmupDir) as $file) { - $content = str_replace($tempContainerClass, $realContainerClass, file_get_contents($file)); - file_put_contents($file, $content); - rename($file, str_replace(\DIRECTORY_SEPARATOR.$tempContainerClass, \DIRECTORY_SEPARATOR.$realContainerClass, $file)); - } - if (is_dir($tempContainerDir = $warmupDir.'/'.\get_class($tempKernel->getContainer()))) { - foreach (Finder::create()->files()->in($tempContainerDir) as $file) { - $content = str_replace($tempContainerClass, $realContainerClass, file_get_contents($file)); - file_put_contents($file, $content); - } - } - - // remove temp kernel file after cache warmed up - @unlink($tempKernelFile); - } - - /** - * @param string $namespace - * @param string $parentClass - * @param string $warmupDir - * - * @return KernelInterface - */ - protected function getTempKernel(KernelInterface $parent, $namespace, $parentClass, $warmupDir) - { - $projectDir = ''; - $cacheDir = var_export($warmupDir, true); - $rootDir = var_export(realpath($parent->getRootDir()), true); - $logDir = var_export(realpath($parent->getLogDir()), true); - // the temp kernel class name must have the same length than the real one - // to avoid the many problems in serialized resources files - $class = substr($parentClass, 0, -1).'_'; - // the temp container class must be changed too - $container = $parent->getContainer(); - $realContainerClass = var_export($container->hasParameter('kernel.container_class') ? $container->getParameter('kernel.container_class') : \get_class($parent->getContainer()), true); - $containerClass = substr_replace($realContainerClass, '_', -2, 1); - - if (method_exists($parent, 'getProjectDir')) { - $projectDir = var_export(realpath($parent->getProjectDir()), true); - $projectDir = <<getResources(); - \$filteredResources = []; - foreach (\$resources as \$resource) { - if ((string) \$resource !== __FILE__) { - \$filteredResources[] = \$resource; - } - } - - \$container->setResources(\$filteredResources); - - return \$container; - } - } -} -EOF; - $this->filesystem->mkdir($warmupDir); - file_put_contents($file = $warmupDir.'/kernel.tmp', $code); - require_once $file; - $class = "$namespace\\$class"; - - return new $class($parent->getEnvironment(), $parent->isDebug()); } } diff --git a/lib/symfony/framework-bundle/Command/CachePoolClearCommand.php b/lib/symfony/framework-bundle/Command/CachePoolClearCommand.php index 13a619630e..b72924dfa7 100644 --- a/lib/symfony/framework-bundle/Command/CachePoolClearCommand.php +++ b/lib/symfony/framework-bundle/Command/CachePoolClearCommand.php @@ -12,6 +12,9 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -24,28 +27,23 @@ use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; * * @author Nicolas Grekas */ -final class CachePoolClearCommand extends ContainerAwareCommand +final class CachePoolClearCommand extends Command { protected static $defaultName = 'cache:pool:clear'; + protected static $defaultDescription = 'Clear cache pools'; private $poolClearer; + private $poolNames; /** - * @param Psr6CacheClearer $poolClearer + * @param string[]|null $poolNames */ - public function __construct($poolClearer = null) + public function __construct(Psr6CacheClearer $poolClearer, array $poolNames = null) { - if (!$poolClearer instanceof Psr6CacheClearer) { - @trigger_error(sprintf('%s() expects an instance of "%s" as first argument since Symfony 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.', __METHOD__, Psr6CacheClearer::class), \E_USER_DEPRECATED); - - parent::__construct($poolClearer); - - return; - } - parent::__construct(); $this->poolClearer = $poolClearer; + $this->poolNames = $poolNames; } /** @@ -57,7 +55,7 @@ final class CachePoolClearCommand extends ContainerAwareCommand ->setDefinition([ new InputArgument('pools', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'A list of cache pools or cache pool clearers'), ]) - ->setDescription('Clears cache pools') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command clears the given cache pools or cache pool clearers. @@ -70,14 +68,8 @@ EOF /** * {@inheritdoc} */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { - // BC to be removed in 4.0 - if (null === $this->poolClearer) { - $this->poolClearer = $this->getContainer()->get('cache.global_clearer'); - $cacheDir = $this->getContainer()->getParameter('kernel.cache_dir'); - } - $io = new SymfonyStyle($input, $output); $kernel = $this->getApplication()->getKernel(); $pools = []; @@ -101,19 +93,39 @@ EOF foreach ($clearers as $id => $clearer) { $io->comment(sprintf('Calling cache clearer: %s', $id)); - $clearer->clear(isset($cacheDir) ? $cacheDir : $kernel->getContainer()->getParameter('kernel.cache_dir')); + $clearer->clear($kernel->getContainer()->getParameter('kernel.cache_dir')); } + $failure = false; foreach ($pools as $id => $pool) { $io->comment(sprintf('Clearing cache pool: %s', $id)); if ($pool instanceof CacheItemPoolInterface) { - $pool->clear(); + if (!$pool->clear()) { + $io->warning(sprintf('Cache pool "%s" could not be cleared.', $pool)); + $failure = true; + } } else { - $this->poolClearer->clearPool($id); + if (false === $this->poolClearer->clearPool($id)) { + $io->warning(sprintf('Cache pool "%s" could not be cleared.', $pool)); + $failure = true; + } } } + if ($failure) { + return 1; + } + $io->success('Cache was successfully cleared.'); + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if (\is_array($this->poolNames) && $input->mustSuggestArgumentValuesFor('pools')) { + $suggestions->suggestValues($this->poolNames); + } } } diff --git a/lib/symfony/framework-bundle/Command/CachePoolDeleteCommand.php b/lib/symfony/framework-bundle/Command/CachePoolDeleteCommand.php new file mode 100644 index 0000000000..b36d48cfe3 --- /dev/null +++ b/lib/symfony/framework-bundle/Command/CachePoolDeleteCommand.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; + +/** + * Delete an item from a cache pool. + * + * @author Pierre du Plessis + */ +final class CachePoolDeleteCommand extends Command +{ + protected static $defaultName = 'cache:pool:delete'; + protected static $defaultDescription = 'Delete an item from a cache pool'; + + private $poolClearer; + private $poolNames; + + /** + * @param string[]|null $poolNames + */ + public function __construct(Psr6CacheClearer $poolClearer, array $poolNames = null) + { + parent::__construct(); + + $this->poolClearer = $poolClearer; + $this->poolNames = $poolNames; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setDefinition([ + new InputArgument('pool', InputArgument::REQUIRED, 'The cache pool from which to delete an item'), + new InputArgument('key', InputArgument::REQUIRED, 'The cache key to delete from the pool'), + ]) + ->setDescription(self::$defaultDescription) + ->setHelp(<<<'EOF' +The %command.name% deletes an item from a given cache pool. + + %command.full_name% +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $pool = $input->getArgument('pool'); + $key = $input->getArgument('key'); + $cachePool = $this->poolClearer->getPool($pool); + + if (!$cachePool->hasItem($key)) { + $io->note(sprintf('Cache item "%s" does not exist in cache pool "%s".', $key, $pool)); + + return 0; + } + + if (!$cachePool->deleteItem($key)) { + throw new \Exception(sprintf('Cache item "%s" could not be deleted.', $key)); + } + + $io->success(sprintf('Cache item "%s" was successfully deleted.', $key)); + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if (\is_array($this->poolNames) && $input->mustSuggestArgumentValuesFor('pool')) { + $suggestions->suggestValues($this->poolNames); + } + } +} diff --git a/lib/symfony/framework-bundle/Command/CachePoolListCommand.php b/lib/symfony/framework-bundle/Command/CachePoolListCommand.php new file mode 100644 index 0000000000..0ad33241de --- /dev/null +++ b/lib/symfony/framework-bundle/Command/CachePoolListCommand.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 Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * List available cache pools. + * + * @author Tobias Nyholm + */ +final class CachePoolListCommand extends Command +{ + protected static $defaultName = 'cache:pool:list'; + protected static $defaultDescription = 'List available cache pools'; + + private $poolNames; + + /** + * @param string[] $poolNames + */ + public function __construct(array $poolNames) + { + parent::__construct(); + + $this->poolNames = $poolNames; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setDescription(self::$defaultDescription) + ->setHelp(<<<'EOF' +The %command.name% command lists all available cache pools. +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + + $io->table(['Pool name'], array_map(function ($pool) { + return [$pool]; + }, $this->poolNames)); + + return 0; + } +} diff --git a/lib/symfony/framework-bundle/Command/CachePoolPruneCommand.php b/lib/symfony/framework-bundle/Command/CachePoolPruneCommand.php index deee376945..8d10352942 100644 --- a/lib/symfony/framework-bundle/Command/CachePoolPruneCommand.php +++ b/lib/symfony/framework-bundle/Command/CachePoolPruneCommand.php @@ -25,13 +25,14 @@ use Symfony\Component\Console\Style\SymfonyStyle; final class CachePoolPruneCommand extends Command { protected static $defaultName = 'cache:pool:prune'; + protected static $defaultDescription = 'Prune cache pools'; private $pools; /** - * @param iterable|PruneableInterface[] $pools + * @param iterable $pools */ - public function __construct($pools) + public function __construct(iterable $pools) { parent::__construct(); @@ -44,7 +45,7 @@ final class CachePoolPruneCommand extends Command protected function configure() { $this - ->setDescription('Prunes cache pools') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command deletes all expired items from all pruneable pools. @@ -57,7 +58,7 @@ EOF /** * {@inheritdoc} */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); @@ -67,5 +68,7 @@ EOF } $io->success('Successfully pruned cache pool(s).'); + + return 0; } } diff --git a/lib/symfony/framework-bundle/Command/CacheWarmupCommand.php b/lib/symfony/framework-bundle/Command/CacheWarmupCommand.php index 720a028891..3529c54572 100644 --- a/lib/symfony/framework-bundle/Command/CacheWarmupCommand.php +++ b/lib/symfony/framework-bundle/Command/CacheWarmupCommand.php @@ -11,10 +11,12 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Command\Command; 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\DependencyInjection\Dumper\Preloader; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; /** @@ -22,27 +24,17 @@ use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; * * @author Fabien Potencier * - * @final since version 3.4 + * @final */ -class CacheWarmupCommand extends ContainerAwareCommand +class CacheWarmupCommand extends Command { protected static $defaultName = 'cache:warmup'; + protected static $defaultDescription = 'Warm up an empty cache'; private $cacheWarmer; - /** - * @param CacheWarmerAggregate $cacheWarmer - */ - public function __construct($cacheWarmer = null) + public function __construct(CacheWarmerAggregate $cacheWarmer) { - if (!$cacheWarmer instanceof CacheWarmerAggregate) { - @trigger_error(sprintf('Passing a command name as the first argument of "%s()" is deprecated since Symfony 3.4 and support for it will be removed in 4.0. If the command was registered by convention, make it a service instead.', __METHOD__), \E_USER_DEPRECATED); - - parent::__construct($cacheWarmer); - - return; - } - parent::__construct(); $this->cacheWarmer = $cacheWarmer; @@ -57,7 +49,7 @@ class CacheWarmupCommand extends ContainerAwareCommand ->setDefinition([ new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'), ]) - ->setDescription('Warms up an empty cache') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command warms up the cache. @@ -76,14 +68,8 @@ EOF /** * {@inheritdoc} */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { - // BC to be removed in 4.0 - if (null === $this->cacheWarmer) { - $this->cacheWarmer = $this->getContainer()->get('cache_warmer'); - $cacheDir = $this->getContainer()->getParameter('kernel.cache_dir'); - } - $io = new SymfonyStyle($input, $output); $kernel = $this->getApplication()->getKernel(); @@ -93,8 +79,14 @@ EOF $this->cacheWarmer->enableOptionalWarmers(); } - $this->cacheWarmer->warmUp(isset($cacheDir) ? $cacheDir : $kernel->getContainer()->getParameter('kernel.cache_dir')); + $preload = $this->cacheWarmer->warmUp($cacheDir = $kernel->getContainer()->getParameter('kernel.cache_dir')); + + if ($preload && file_exists($preloadFile = $cacheDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) { + Preloader::append($preloadFile, $preload); + } $io->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully warmed.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true))); + + return 0; } } diff --git a/lib/symfony/framework-bundle/Command/ConfigDebugCommand.php b/lib/symfony/framework-bundle/Command/ConfigDebugCommand.php index adea7dbdbf..12e501baa1 100644 --- a/lib/symfony/framework-bundle/Command/ConfigDebugCommand.php +++ b/lib/symfony/framework-bundle/Command/ConfigDebugCommand.php @@ -11,12 +11,19 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\DependencyInjection\Compiler\ValidateEnvPlaceholdersPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\Yaml\Yaml; /** @@ -24,11 +31,12 @@ use Symfony\Component\Yaml\Yaml; * * @author Grégoire Pineau * - * @final since version 3.4 + * @final */ class ConfigDebugCommand extends AbstractConfigCommand { protected static $defaultName = 'debug:config'; + protected static $defaultDescription = 'Dump the current configuration for an extension'; /** * {@inheritdoc} @@ -40,7 +48,7 @@ class ConfigDebugCommand extends AbstractConfigCommand new InputArgument('name', InputArgument::OPTIONAL, 'The bundle name or the extension alias'), new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'), ]) - ->setDescription('Dumps the current configuration for an extension') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command dumps the current configuration for an extension/bundle. @@ -62,41 +70,42 @@ EOF /** * {@inheritdoc} */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $errorIo = $io->getErrorStyle(); if (null === $name = $input->getArgument('name')) { $this->listBundles($errorIo); + + $kernel = $this->getApplication()->getKernel(); + if ($kernel instanceof ExtensionInterface + && ($kernel instanceof ConfigurationInterface || $kernel instanceof ConfigurationExtensionInterface) + && $kernel->getAlias() + ) { + $errorIo->table(['Kernel Extension'], [[$kernel->getAlias()]]); + } + $errorIo->comment('Provide the name of a bundle as the first argument of this command to dump its configuration. (e.g. debug:config FrameworkBundle)'); $errorIo->comment('For dumping a specific option, add its path as the second argument of this command. (e.g. debug:config FrameworkBundle serializer to dump the framework.serializer configuration)'); - return; + return 0; } $extension = $this->findExtension($name); + $extensionAlias = $extension->getAlias(); $container = $this->compileContainer(); - $extensionAlias = $extension->getAlias(); - $configs = $container->getExtensionConfig($extensionAlias); - $configuration = $extension->getConfiguration($configs, $container); - - $this->validateConfiguration($extension, $configuration); - - $configs = $container->resolveEnvPlaceholders($container->getParameterBag()->resolveValue($configs)); - - $processor = new Processor(); - $config = $container->resolveEnvPlaceholders($container->getParameterBag()->resolveValue($processor->processConfiguration($configuration, $configs))); + $config = $this->getConfig($extension, $container); if (null === $path = $input->getArgument('path')) { $io->title( - sprintf('Current configuration for %s', ($name === $extensionAlias ? sprintf('extension with alias "%s"', $extensionAlias) : sprintf('"%s"', $name))) + sprintf('Current configuration for %s', $name === $extensionAlias ? sprintf('extension with alias "%s"', $extensionAlias) : sprintf('"%s"', $name)) ); $io->writeln(Yaml::dump([$extensionAlias => $config], 10)); - return; + return 0; } try { @@ -110,9 +119,11 @@ EOF $io->title(sprintf('Current configuration for "%s.%s"', $extensionAlias, $path)); $io->writeln(Yaml::dump($config, 10)); + + return 0; } - private function compileContainer() + private function compileContainer(): ContainerBuilder { $kernel = clone $this->getApplication()->getKernel(); $kernel->boot(); @@ -128,13 +139,11 @@ EOF /** * Iterate over configuration until the last step of the given path. * - * @param array $config A bundle configuration - * * @throws LogicException If the configuration does not exist * * @return mixed */ - private function getConfigForPath(array $config, $path, $alias) + private function getConfigForPath(array $config, string $path, string $alias) { $steps = explode('.', $path); @@ -148,4 +157,84 @@ EOF return $config; } + + private function getConfigForExtension(ExtensionInterface $extension, ContainerBuilder $container): array + { + $extensionAlias = $extension->getAlias(); + + $extensionConfig = []; + foreach ($container->getCompilerPassConfig()->getPasses() as $pass) { + if ($pass instanceof ValidateEnvPlaceholdersPass) { + $extensionConfig = $pass->getExtensionConfig(); + break; + } + } + + if (isset($extensionConfig[$extensionAlias])) { + return $extensionConfig[$extensionAlias]; + } + + // Fall back to default config if the extension has one + + if (!$extension instanceof ConfigurationExtensionInterface) { + throw new \LogicException(sprintf('The extension with alias "%s" does not have configuration.', $extensionAlias)); + } + + $configs = $container->getExtensionConfig($extensionAlias); + $configuration = $extension->getConfiguration($configs, $container); + $this->validateConfiguration($extension, $configuration); + + return (new Processor())->processConfiguration($configuration, $configs); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('name')) { + $suggestions->suggestValues($this->getAvailableBundles(!preg_match('/^[A-Z]/', $input->getCompletionValue()))); + + return; + } + + if ($input->mustSuggestArgumentValuesFor('path') && null !== $name = $input->getArgument('name')) { + try { + $config = $this->getConfig($this->findExtension($name), $this->compileContainer()); + $paths = array_keys(self::buildPathsCompletion($config)); + $suggestions->suggestValues($paths); + } catch (LogicException $e) { + } + } + } + + private function getAvailableBundles(bool $alias): array + { + $availableBundles = []; + foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) { + $availableBundles[] = $alias ? $bundle->getContainerExtension()->getAlias() : $bundle->getName(); + } + + return $availableBundles; + } + + private function getConfig(ExtensionInterface $extension, ContainerBuilder $container) + { + return $container->resolveEnvPlaceholders( + $container->getParameterBag()->resolveValue( + $this->getConfigForExtension($extension, $container) + ) + ); + } + + private static function buildPathsCompletion(array $paths, string $prefix = ''): array + { + $completionPaths = []; + foreach ($paths as $key => $values) { + if (\is_array($values)) { + $completionPaths = $completionPaths + self::buildPathsCompletion($values, $prefix.$key.'.'); + } else { + $completionPaths[$prefix.$key] = null; + } + } + + return $completionPaths; + } } diff --git a/lib/symfony/framework-bundle/Command/ConfigDumpReferenceCommand.php b/lib/symfony/framework-bundle/Command/ConfigDumpReferenceCommand.php index a4d2764b11..7a56ec5abe 100644 --- a/lib/symfony/framework-bundle/Command/ConfigDumpReferenceCommand.php +++ b/lib/symfony/framework-bundle/Command/ConfigDumpReferenceCommand.php @@ -11,14 +11,20 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\Dumper\XmlReferenceDumper; use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; 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\DependencyInjection\Extension\ConfigurationExtensionInterface; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\Yaml\Yaml; /** * A console command for dumping available configuration reference. @@ -27,11 +33,12 @@ use Symfony\Component\Console\Style\SymfonyStyle; * @author Wouter J * @author Grégoire Pineau * - * @final since version 3.4 + * @final */ class ConfigDumpReferenceCommand extends AbstractConfigCommand { protected static $defaultName = 'config:dump-reference'; + protected static $defaultDescription = 'Dump the default configuration for an extension'; /** * {@inheritdoc} @@ -44,7 +51,7 @@ class ConfigDumpReferenceCommand extends AbstractConfigCommand new InputArgument('path', InputArgument::OPTIONAL, 'The configuration option path'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (yaml or xml)', 'yaml'), ]) - ->setDescription('Dumps the default configuration for an extension') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command dumps the default configuration for an extension/bundle. @@ -74,28 +81,48 @@ EOF * * @throws \LogicException */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $errorIo = $io->getErrorStyle(); if (null === $name = $input->getArgument('name')) { $this->listBundles($errorIo); + + $kernel = $this->getApplication()->getKernel(); + if ($kernel instanceof ExtensionInterface + && ($kernel instanceof ConfigurationInterface || $kernel instanceof ConfigurationExtensionInterface) + && $kernel->getAlias() + ) { + $errorIo->table(['Kernel Extension'], [[$kernel->getAlias()]]); + } + $errorIo->comment([ 'Provide the name of a bundle as the first argument of this command to dump its default configuration. (e.g. config:dump-reference FrameworkBundle)', 'For dumping a specific option, add its path as the second argument of this command. (e.g. config:dump-reference FrameworkBundle profiler.matcher to dump the framework.profiler.matcher configuration)', ]); - return null; + return 0; } $extension = $this->findExtension($name); - $configuration = $extension->getConfiguration([], $this->getContainerBuilder()); + if ($extension instanceof ConfigurationInterface) { + $configuration = $extension; + } else { + $configuration = $extension->getConfiguration([], $this->getContainerBuilder($this->getApplication()->getKernel())); + } $this->validateConfiguration($extension, $configuration); $format = $input->getOption('format'); + + if ('yaml' === $format && !class_exists(Yaml::class)) { + $errorIo->error('Setting the "format" option to "yaml" requires the Symfony Yaml component. Try running "composer install symfony/yaml" or use "--format=xml" instead.'); + + return 1; + } + $path = $input->getArgument('path'); if (null !== $path && 'yaml' !== $format) { @@ -130,6 +157,34 @@ EOF $io->writeln(null === $path ? $dumper->dump($configuration, $extension->getNamespace()) : $dumper->dumpAtPath($configuration, $path)); - return null; + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('name')) { + $suggestions->suggestValues($this->getAvailableBundles()); + } + + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormatOptions()); + } + } + + private function getAvailableBundles(): array + { + $bundles = []; + + foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) { + $bundles[] = $bundle->getName(); + $bundles[] = $bundle->getContainerExtension()->getAlias(); + } + + return $bundles; + } + + private function getAvailableFormatOptions(): array + { + return ['yaml', 'xml']; } } diff --git a/lib/symfony/framework-bundle/Command/ContainerAwareCommand.php b/lib/symfony/framework-bundle/Command/ContainerAwareCommand.php deleted file mode 100644 index 2c84e7f72f..0000000000 --- a/lib/symfony/framework-bundle/Command/ContainerAwareCommand.php +++ /dev/null @@ -1,56 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Command; - -use Symfony\Component\Console\Command\Command; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; - -/** - * Command. - * - * @author Fabien Potencier - */ -abstract class ContainerAwareCommand extends Command implements ContainerAwareInterface -{ - /** - * @var ContainerInterface|null - */ - private $container; - - /** - * @return ContainerInterface - * - * @throws \LogicException - */ - protected function getContainer() - { - if (null === $this->container) { - $application = $this->getApplication(); - if (null === $application) { - throw new \LogicException('The container cannot be retrieved as the application instance is not yet set.'); - } - - $this->container = $application->getKernel()->getContainer(); - } - - return $this->container; - } - - /** - * {@inheritdoc} - */ - public function setContainer(ContainerInterface $container = null) - { - $this->container = $container; - } -} diff --git a/lib/symfony/framework-bundle/Command/ContainerDebugCommand.php b/lib/symfony/framework-bundle/Command/ContainerDebugCommand.php index edbb526f0c..8dfebe4ae8 100644 --- a/lib/symfony/framework-bundle/Command/ContainerDebugCommand.php +++ b/lib/symfony/framework-bundle/Command/ContainerDebugCommand.php @@ -12,17 +12,17 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper; -use Symfony\Component\Config\ConfigCache; -use Symfony\Component\Config\FileLocator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; 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\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; /** @@ -30,16 +30,14 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; * * @author Ryan Weaver * - * @internal since version 3.4 + * @internal */ -class ContainerDebugCommand extends ContainerAwareCommand +class ContainerDebugCommand extends Command { - protected static $defaultName = 'debug:container'; + use BuildDebugContainerTrait; - /** - * @var ContainerBuilder|null - */ - protected $containerBuilder; + protected static $defaultName = 'debug:container'; + protected static $defaultDescription = 'Display current services for an application'; /** * {@inheritdoc} @@ -49,34 +47,48 @@ class ContainerDebugCommand extends ContainerAwareCommand $this ->setDefinition([ new InputArgument('name', InputArgument::OPTIONAL, 'A service name (foo)'), - new InputOption('show-private', null, InputOption::VALUE_NONE, 'Used to show public *and* private services'), - new InputOption('show-arguments', null, InputOption::VALUE_NONE, 'Used to show arguments in services'), - new InputOption('tag', null, InputOption::VALUE_REQUIRED, 'Shows all services with a specific tag'), - new InputOption('tags', null, InputOption::VALUE_NONE, 'Displays tagged services for an application'), - new InputOption('parameter', null, InputOption::VALUE_REQUIRED, 'Displays a specific parameter for an application'), - new InputOption('parameters', null, InputOption::VALUE_NONE, 'Displays parameters for an application'), - new InputOption('types', null, InputOption::VALUE_NONE, 'Displays types (classes/interfaces) available in the container'), + new InputOption('show-arguments', null, InputOption::VALUE_NONE, 'Show arguments in services'), + new InputOption('show-hidden', null, InputOption::VALUE_NONE, 'Show hidden (internal) services'), + new InputOption('tag', null, InputOption::VALUE_REQUIRED, 'Show all services with a specific tag'), + new InputOption('tags', null, InputOption::VALUE_NONE, 'Display tagged services for an application'), + new InputOption('parameter', null, InputOption::VALUE_REQUIRED, 'Display a specific parameter for an application'), + new InputOption('parameters', null, InputOption::VALUE_NONE, 'Display parameters for an application'), + new InputOption('types', null, InputOption::VALUE_NONE, 'Display types (classes/interfaces) available in the container'), + new InputOption('env-var', null, InputOption::VALUE_REQUIRED, 'Display a specific environment variable used in the container'), + new InputOption('env-vars', null, InputOption::VALUE_NONE, 'Display environment variables used in the container'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'), + new InputOption('deprecations', null, InputOption::VALUE_NONE, 'Display deprecations generated when compiling and warming up the container'), ]) - ->setDescription('Displays current services for an application') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command displays all configured public services: php %command.full_name% +To see deprecations generated during container compilation and cache warmup, use the --deprecations option: + + php %command.full_name% --deprecations + To get specific information about a service, specify its name: php %command.full_name% validator +To get specific information about a service including all its arguments, use the --show-arguments flag: + + php %command.full_name% validator --show-arguments + To see available types that can be used for autowiring, use the --types flag: php %command.full_name% --types -By default, private services are hidden. You can display all services by -using the --show-private flag: +To see environment variables used by the container, use the --env-vars flag: - php %command.full_name% --show-private + php %command.full_name% --env-vars + +Display a specific environment variable by specifying its name with the --env-var option: + + php %command.full_name% --env-var=APP_ENV Use the --tags option to display tagged public services grouped by tag: @@ -94,6 +106,11 @@ Display a specific parameter by specifying its name with the --parameterphp %command.full_name% --parameter=kernel.debug +By default, internal services are hidden. You can display them +using the --show-hidden flag: + + php %command.full_name% --show-hidden + EOF ) ; @@ -102,16 +119,21 @@ EOF /** * {@inheritdoc} */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $errorIo = $io->getErrorStyle(); $this->validateInput($input); - $object = $this->getContainerBuilder(); + $kernel = $this->getApplication()->getKernel(); + $object = $this->getContainerBuilder($kernel); - if ($input->getOption('types')) { - $options = ['show_private' => true]; + if ($input->getOption('env-vars')) { + $options = ['env-vars' => true]; + } elseif ($envVar = $input->getOption('env-var')) { + $options = ['env-vars' => true, 'name' => $envVar]; + } elseif ($input->getOption('types')) { + $options = []; $options['filter'] = [$this, 'filterToServiceTypes']; } elseif ($input->getOption('parameters')) { $parameters = []; @@ -123,33 +145,89 @@ EOF } elseif ($parameter = $input->getOption('parameter')) { $options = ['parameter' => $parameter]; } elseif ($input->getOption('tags')) { - $options = ['group_by' => 'tags', 'show_private' => $input->getOption('show-private')]; + $options = ['group_by' => 'tags']; } elseif ($tag = $input->getOption('tag')) { - $options = ['tag' => $tag, 'show_private' => $input->getOption('show-private')]; + $options = ['tag' => $tag]; } elseif ($name = $input->getArgument('name')) { - $name = $this->findProperServiceName($input, $errorIo, $object, $name); + $name = $this->findProperServiceName($input, $errorIo, $object, $name, $input->getOption('show-hidden')); $options = ['id' => $name]; + } elseif ($input->getOption('deprecations')) { + $options = ['deprecations' => true]; } else { - $options = ['show_private' => $input->getOption('show-private')]; + $options = []; } $helper = new DescriptorHelper(); $options['format'] = $input->getOption('format'); $options['show_arguments'] = $input->getOption('show-arguments'); + $options['show_hidden'] = $input->getOption('show-hidden'); $options['raw_text'] = $input->getOption('raw'); $options['output'] = $io; - $options['is_debug'] = $this->getApplication()->getKernel()->isDebug(); - $helper->describe($io, $object, $options); + $options['is_debug'] = $kernel->isDebug(); - if (!$input->getArgument('name') && !$input->getOption('tag') && !$input->getOption('parameter') && $input->isInteractive()) { + try { + $helper->describe($io, $object, $options); + + if (isset($options['id']) && isset($kernel->getContainer()->getRemovedIds()[$options['id']])) { + $errorIo->note(sprintf('The "%s" service or alias has been removed or inlined when the container was compiled.', $options['id'])); + } + } catch (ServiceNotFoundException $e) { + if ('' !== $e->getId() && '@' === $e->getId()[0]) { + throw new ServiceNotFoundException($e->getId(), $e->getSourceId(), null, [substr($e->getId(), 1)]); + } + + throw $e; + } + + if (!$input->getArgument('name') && !$input->getOption('tag') && !$input->getOption('parameter') && !$input->getOption('env-vars') && !$input->getOption('env-var') && $input->isInteractive()) { if ($input->getOption('tags')) { $errorIo->comment('To search for a specific tag, re-run this command with a search term. (e.g. debug:container --tag=form.type)'); } elseif ($input->getOption('parameters')) { $errorIo->comment('To search for a specific parameter, re-run this command with a search term. (e.g. debug:container --parameter=kernel.debug)'); - } else { + } elseif (!$input->getOption('deprecations')) { $errorIo->comment('To search for a specific service, re-run this command with a search term. (e.g. debug:container log)'); } } + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('format')) { + $helper = new DescriptorHelper(); + $suggestions->suggestValues($helper->getFormats()); + + return; + } + + $kernel = $this->getApplication()->getKernel(); + $object = $this->getContainerBuilder($kernel); + + if ($input->mustSuggestArgumentValuesFor('name') + && !$input->getOption('tag') && !$input->getOption('tags') + && !$input->getOption('parameter') && !$input->getOption('parameters') + && !$input->getOption('env-var') && !$input->getOption('env-vars') + && !$input->getOption('types') && !$input->getOption('deprecations') + ) { + $suggestions->suggestValues($this->findServiceIdsContaining( + $object, + $input->getCompletionValue(), + (bool) $input->getOption('show-hidden') + )); + + return; + } + + if ($input->mustSuggestOptionValuesFor('tag')) { + $suggestions->suggestValues($object->findTags()); + + return; + } + + if ($input->mustSuggestOptionValuesFor('parameter')) { + $suggestions->suggestValues(array_keys($object->getParameterBag()->all())); + } } /** @@ -170,94 +248,66 @@ EOF $name = $input->getArgument('name'); if ((null !== $name) && ($optionsCount > 0)) { - throw new InvalidArgumentException('The options tags, tag, parameters & parameter can not be combined with the service name argument.'); + throw new InvalidArgumentException('The options tags, tag, parameters & parameter cannot be combined with the service name argument.'); } elseif ((null === $name) && $optionsCount > 1) { - throw new InvalidArgumentException('The options tags, tag, parameters & parameter can not be combined together.'); + throw new InvalidArgumentException('The options tags, tag, parameters & parameter cannot be combined together.'); } } - /** - * Loads the ContainerBuilder from the cache. - * - * @return ContainerBuilder - * - * @throws \LogicException - */ - protected function getContainerBuilder() + private function findProperServiceName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $builder, string $name, bool $showHidden): string { - if ($this->containerBuilder) { - return $this->containerBuilder; - } + $name = ltrim($name, '\\'); - $kernel = $this->getApplication()->getKernel(); - - if (!$kernel->isDebug() || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) { - $buildContainer = \Closure::bind(function () { return $this->buildContainer(); }, $kernel, \get_class($kernel)); - $container = $buildContainer(); - $container->getCompilerPassConfig()->setRemovingPasses([]); - $container->getCompilerPassConfig()->setAfterRemovingPasses([]); - $container->compile(); - } else { - (new XmlFileLoader($container = new ContainerBuilder(), new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump')); - $locatorPass = new ServiceLocatorTagPass(); - $locatorPass->process($container); - } - - return $this->containerBuilder = $container; - } - - private function findProperServiceName(InputInterface $input, SymfonyStyle $io, ContainerBuilder $builder, $name) - { if ($builder->has($name) || !$input->isInteractive()) { return $name; } - $matchingServices = $this->findServiceIdsContaining($builder, $name); + $matchingServices = $this->findServiceIdsContaining($builder, $name, $showHidden); if (empty($matchingServices)) { throw new InvalidArgumentException(sprintf('No services found that match "%s".', $name)); } - $default = 1 === \count($matchingServices) ? $matchingServices[0] : null; - - return $io->choice('Select one of the following services to display its information', $matchingServices, $default); - } - - private function findServiceIdsContaining(ContainerBuilder $builder, $name) - { - $serviceIds = $builder->getServiceIds(); - $foundServiceIds = []; - foreach ($serviceIds as $serviceId) { - if (false === stripos($serviceId, $name)) { - continue; - } - $foundServiceIds[] = $serviceId; + if (1 === \count($matchingServices)) { + return $matchingServices[0]; } - return $foundServiceIds; + return $io->choice('Select one of the following services to display its information', $matchingServices); + } + + private function findServiceIdsContaining(ContainerBuilder $builder, string $name, bool $showHidden): array + { + $serviceIds = $builder->getServiceIds(); + $foundServiceIds = $foundServiceIdsIgnoringBackslashes = []; + foreach ($serviceIds as $serviceId) { + if (!$showHidden && str_starts_with($serviceId, '.')) { + continue; + } + if (false !== stripos(str_replace('\\', '', $serviceId), $name)) { + $foundServiceIdsIgnoringBackslashes[] = $serviceId; + } + if ('' === $name || false !== stripos($serviceId, $name)) { + $foundServiceIds[] = $serviceId; + } + } + + return $foundServiceIds ?: $foundServiceIdsIgnoringBackslashes; } /** * @internal */ - public function filterToServiceTypes($serviceId) + public function filterToServiceTypes(string $serviceId): bool { // filter out things that could not be valid class names - if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+$/', $serviceId)) { + if (!preg_match('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))^(?&V)(?:\\\\(?&V))*+(?: \$(?&V))?$/', $serviceId)) { return false; } // if the id has a \, assume it is a class - if (false !== strpos($serviceId, '\\')) { + if (str_contains($serviceId, '\\')) { return true; } - try { - new \ReflectionClass($serviceId); - - return true; - } catch (\ReflectionException $e) { - // the service id is not a valid class/interface - return false; - } + return class_exists($serviceId) || interface_exists($serviceId, false); } } diff --git a/lib/symfony/framework-bundle/Command/ContainerLintCommand.php b/lib/symfony/framework-bundle/Command/ContainerLintCommand.php new file mode 100644 index 0000000000..337f1f4203 --- /dev/null +++ b/lib/symfony/framework-bundle/Command/ContainerLintCommand.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 Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\DependencyInjection\Compiler\CheckTypeDeclarationsPass; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; +use Symfony\Component\HttpKernel\Kernel; + +final class ContainerLintCommand extends Command +{ + protected static $defaultName = 'lint:container'; + protected static $defaultDescription = 'Ensure that arguments injected into services match type declarations'; + + /** + * @var ContainerBuilder + */ + private $containerBuilder; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setDescription(self::$defaultDescription) + ->setHelp('This command parses service definitions and ensures that injected values match the type declarations of each services\' class.') + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $errorIo = $io->getErrorStyle(); + + try { + $container = $this->getContainerBuilder(); + } catch (RuntimeException $e) { + $errorIo->error($e->getMessage()); + + return 2; + } + + $container->setParameter('container.build_time', time()); + + try { + $container->compile(); + } catch (InvalidArgumentException $e) { + $errorIo->error($e->getMessage()); + + return 1; + } + + $io->success('The container was linted successfully: all services are injected with values that are compatible with their type declarations.'); + + return 0; + } + + private function getContainerBuilder(): ContainerBuilder + { + if ($this->containerBuilder) { + return $this->containerBuilder; + } + + $kernel = $this->getApplication()->getKernel(); + $kernelContainer = $kernel->getContainer(); + + if (!$kernel->isDebug() || !(new ConfigCache($kernelContainer->getParameter('debug.container.dump'), true))->isFresh()) { + if (!$kernel instanceof Kernel) { + throw new RuntimeException(sprintf('This command does not support the application kernel: "%s" does not extend "%s".', get_debug_type($kernel), Kernel::class)); + } + + $buildContainer = \Closure::bind(function (): ContainerBuilder { + $this->initializeBundles(); + + return $this->buildContainer(); + }, $kernel, \get_class($kernel)); + $container = $buildContainer(); + + $skippedIds = []; + } else { + if (!$kernelContainer instanceof Container) { + throw new RuntimeException(sprintf('This command does not support the application container: "%s" does not extend "%s".', get_debug_type($kernelContainer), Container::class)); + } + + (new XmlFileLoader($container = new ContainerBuilder($parameterBag = new EnvPlaceholderParameterBag()), new FileLocator()))->load($kernelContainer->getParameter('debug.container.dump')); + + $refl = new \ReflectionProperty($parameterBag, 'resolved'); + $refl->setAccessible(true); + $refl->setValue($parameterBag, true); + + $skippedIds = []; + foreach ($container->getServiceIds() as $serviceId) { + if (str_starts_with($serviceId, '.errored.')) { + $skippedIds[$serviceId] = true; + } + } + + $container->getCompilerPassConfig()->setBeforeOptimizationPasses([]); + $container->getCompilerPassConfig()->setOptimizationPasses([]); + $container->getCompilerPassConfig()->setBeforeRemovingPasses([]); + } + + $container->setParameter('container.build_hash', 'lint_container'); + $container->setParameter('container.build_id', 'lint_container'); + + $container->addCompilerPass(new CheckTypeDeclarationsPass(true, $skippedIds), PassConfig::TYPE_AFTER_REMOVING, -100); + + return $this->containerBuilder = $container; + } +} diff --git a/lib/symfony/framework-bundle/Command/DebugAutowiringCommand.php b/lib/symfony/framework-bundle/Command/DebugAutowiringCommand.php index 84c87521cb..e1e3c95341 100644 --- a/lib/symfony/framework-bundle/Command/DebugAutowiringCommand.php +++ b/lib/symfony/framework-bundle/Command/DebugAutowiringCommand.php @@ -11,10 +11,16 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Bundle\FrameworkBundle\Console\Descriptor\Descriptor; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; use Symfony\Component\Console\Input\InputArgument; 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\HttpKernel\Debug\FileLinkFormatter; /** * A console command for autowiring information. @@ -26,6 +32,17 @@ use Symfony\Component\Console\Style\SymfonyStyle; class DebugAutowiringCommand extends ContainerDebugCommand { protected static $defaultName = 'debug:autowiring'; + protected static $defaultDescription = 'List classes/interfaces you can use for autowiring'; + + private $supportsHref; + private $fileLinkFormatter; + + public function __construct(string $name = null, FileLinkFormatter $fileLinkFormatter = null) + { + $this->supportsHref = method_exists(OutputFormatterStyle::class, 'setHref'); + $this->fileLinkFormatter = $fileLinkFormatter; + parent::__construct($name); + } /** * {@inheritdoc} @@ -35,10 +52,11 @@ class DebugAutowiringCommand extends ContainerDebugCommand $this ->setDefinition([ new InputArgument('search', InputArgument::OPTIONAL, 'A search filter'), + new InputOption('all', null, InputOption::VALUE_NONE, 'Show also services that are not aliased'), ]) - ->setDescription('Lists classes/interfaces you can use for autowiring') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' -The %command.name% command displays all classes and interfaces that +The %command.name% command displays the classes and interfaces that you can use as type-hints for autowiring: php %command.full_name% @@ -55,18 +73,20 @@ EOF /** * {@inheritdoc} */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $errorIo = $io->getErrorStyle(); - $builder = $this->getContainerBuilder(); + $builder = $this->getContainerBuilder($this->getApplication()->getKernel()); $serviceIds = $builder->getServiceIds(); $serviceIds = array_filter($serviceIds, [$this, 'filterToServiceTypes']); if ($search = $input->getArgument('search')) { - $serviceIds = array_filter($serviceIds, function ($serviceId) use ($search) { - return false !== stripos($serviceId, $search); + $searchNormalized = preg_replace('/[^a-zA-Z0-9\x7f-\xff $]++/', '', $search); + + $serviceIds = array_filter($serviceIds, function ($serviceId) use ($searchNormalized) { + return false !== stripos(str_replace('\\', '', $serviceId), $searchNormalized) && !str_starts_with($serviceId, '.'); }); if (empty($serviceIds)) { @@ -76,24 +96,82 @@ EOF } } - asort($serviceIds); + uasort($serviceIds, 'strnatcmp'); - $io->title('Autowirable Services'); + $io->title('Autowirable Types'); $io->text('The following classes & interfaces can be used as type-hints when autowiring:'); if ($search) { $io->text(sprintf('(only showing classes/interfaces matching %s)', $search)); } - $io->newLine(); - $tableRows = []; + $hasAlias = []; + $all = $input->getOption('all'); + $previousId = '-'; + $serviceIdsNb = 0; foreach ($serviceIds as $serviceId) { - $tableRows[] = [sprintf('%s', $serviceId)]; - if ($builder->hasAlias($serviceId)) { - $tableRows[] = [sprintf(' alias to %s', $builder->getAlias($serviceId))]; + $text = []; + $resolvedServiceId = $serviceId; + if (!str_starts_with($serviceId, $previousId)) { + $text[] = ''; + if ('' !== $description = Descriptor::getClassDescription($serviceId, $resolvedServiceId)) { + if (isset($hasAlias[$serviceId])) { + continue; + } + $text[] = $description; + } + $previousId = $serviceId.' $'; } + + $serviceLine = sprintf('%s', $serviceId); + if ($this->supportsHref && '' !== $fileLink = $this->getFileLink($serviceId)) { + $serviceLine = sprintf('%s', $fileLink, $serviceId); + } + + if ($builder->hasAlias($serviceId)) { + $hasAlias[$serviceId] = true; + $serviceAlias = $builder->getAlias($serviceId); + $serviceLine .= ' ('.$serviceAlias.')'; + + if ($serviceAlias->isDeprecated()) { + $serviceLine .= ' - deprecated'; + } + } elseif (!$all) { + ++$serviceIdsNb; + continue; + } + $text[] = $serviceLine; + $io->text($text); } - $io->table([], $tableRows); + $io->newLine(); - return null; + if (0 < $serviceIdsNb) { + $io->text(sprintf('%s more concrete service%s would be displayed when adding the "--all" option.', $serviceIdsNb, $serviceIdsNb > 1 ? 's' : '')); + } + if ($all) { + $io->text('Pro-tip: use interfaces in your type-hints instead of classes to benefit from the dependency inversion principle.'); + } + + $io->newLine(); + + return 0; + } + + private function getFileLink(string $class): string + { + if (null === $this->fileLinkFormatter + || (null === $r = $this->getContainerBuilder($this->getApplication()->getKernel())->getReflectionClass($class, false))) { + return ''; + } + + return (string) $this->fileLinkFormatter->format($r->getFileName(), $r->getStartLine()); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('search')) { + $builder = $this->getContainerBuilder($this->getApplication()->getKernel()); + + $suggestions->suggestValues(array_filter($builder->getServiceIds(), [$this, 'filterToServiceTypes'])); + } } } diff --git a/lib/symfony/framework-bundle/Command/EventDispatcherDebugCommand.php b/lib/symfony/framework-bundle/Command/EventDispatcherDebugCommand.php index c5d85ab2a4..cfc9ae2fbe 100644 --- a/lib/symfony/framework-bundle/Command/EventDispatcherDebugCommand.php +++ b/lib/symfony/framework-bundle/Command/EventDispatcherDebugCommand.php @@ -11,42 +11,39 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Psr\Container\ContainerInterface; use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Input\InputArgument; 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\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\Service\ServiceProviderInterface; /** * A console command for retrieving information about event dispatcher. * * @author Matthieu Auger * - * @final since version 3.4 + * @final */ -class EventDispatcherDebugCommand extends ContainerAwareCommand +class EventDispatcherDebugCommand extends Command { + private const DEFAULT_DISPATCHER = 'event_dispatcher'; + protected static $defaultName = 'debug:event-dispatcher'; - private $dispatcher; + protected static $defaultDescription = 'Display configured listeners for an application'; + private $dispatchers; - /** - * @param EventDispatcherInterface $dispatcher - */ - public function __construct($dispatcher = null) + public function __construct(ContainerInterface $dispatchers) { - if (!$dispatcher instanceof EventDispatcherInterface) { - @trigger_error(sprintf('%s() expects an instance of "%s" as first argument since Symfony 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.', __METHOD__, EventDispatcherInterface::class), \E_USER_DEPRECATED); - - parent::__construct($dispatcher); - - return; - } - parent::__construct(); - $this->dispatcher = $dispatcher; + $this->dispatchers = $dispatchers; } /** @@ -56,11 +53,12 @@ class EventDispatcherDebugCommand extends ContainerAwareCommand { $this ->setDefinition([ - new InputArgument('event', InputArgument::OPTIONAL, 'An event name'), + new InputArgument('event', InputArgument::OPTIONAL, 'An event name or a part of the event name'), + new InputOption('dispatcher', null, InputOption::VALUE_REQUIRED, 'To view events of a specific event dispatcher', self::DEFAULT_DISPATCHER), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw description'), ]) - ->setDescription('Displays configured listeners for an application') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command displays all configured listeners: @@ -79,42 +77,88 @@ EOF * * @throws \LogicException */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { - // BC to be removed in 4.0 - if (null === $this->dispatcher) { - $this->dispatcher = $this->getEventDispatcher(); - } - $io = new SymfonyStyle($input, $output); $options = []; + $dispatcherServiceName = $input->getOption('dispatcher'); + if (!$this->dispatchers->has($dispatcherServiceName)) { + $io->getErrorStyle()->error(sprintf('Event dispatcher "%s" is not available.', $dispatcherServiceName)); + + return 1; + } + + $dispatcher = $this->dispatchers->get($dispatcherServiceName); + if ($event = $input->getArgument('event')) { - if (!$this->dispatcher->hasListeners($event)) { - $io->getErrorStyle()->warning(sprintf('The event "%s" does not have any registered listeners.', $event)); + if ($dispatcher->hasListeners($event)) { + $options = ['event' => $event]; + } else { + // if there is no direct match, try find partial matches + $events = $this->searchForEvent($dispatcher, $event); + if (0 === \count($events)) { + $io->getErrorStyle()->warning(sprintf('The event "%s" does not have any registered listeners.', $event)); - return; + return 0; + } elseif (1 === \count($events)) { + $options = ['event' => $events[array_key_first($events)]]; + } else { + $options = ['events' => $events]; + } } - - $options = ['event' => $event]; } $helper = new DescriptorHelper(); + + if (self::DEFAULT_DISPATCHER !== $dispatcherServiceName) { + $options['dispatcher_service_name'] = $dispatcherServiceName; + } + $options['format'] = $input->getOption('format'); $options['raw_text'] = $input->getOption('raw'); $options['output'] = $io; - $helper->describe($io, $this->dispatcher, $options); + $helper->describe($io, $dispatcher, $options); + + return 0; } - /** - * Loads the Event Dispatcher from the container. - * - * BC to removed in 4.0 - * - * @return EventDispatcherInterface - */ - protected function getEventDispatcher() + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void { - return $this->getContainer()->get('event_dispatcher'); + if ($input->mustSuggestArgumentValuesFor('event')) { + $dispatcherServiceName = $input->getOption('dispatcher'); + if ($this->dispatchers->has($dispatcherServiceName)) { + $dispatcher = $this->dispatchers->get($dispatcherServiceName); + $suggestions->suggestValues(array_keys($dispatcher->getListeners())); + } + + return; + } + + if ($input->mustSuggestOptionValuesFor('dispatcher')) { + if ($this->dispatchers instanceof ServiceProviderInterface) { + $suggestions->suggestValues(array_keys($this->dispatchers->getProvidedServices())); + } + + return; + } + + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues((new DescriptorHelper())->getFormats()); + } + } + + private function searchForEvent(EventDispatcherInterface $dispatcher, string $needle): array + { + $output = []; + $lcNeedle = strtolower($needle); + $allEvents = array_keys($dispatcher->getListeners()); + foreach ($allEvents as $event) { + if (str_contains(strtolower($event), $lcNeedle)) { + $output[] = $event; + } + } + + return $output; } } diff --git a/lib/symfony/framework-bundle/Command/RouterDebugCommand.php b/lib/symfony/framework-bundle/Command/RouterDebugCommand.php index 13792f2b32..cf929f9879 100644 --- a/lib/symfony/framework-bundle/Command/RouterDebugCommand.php +++ b/lib/symfony/framework-bundle/Command/RouterDebugCommand.php @@ -12,15 +12,17 @@ namespace Symfony\Bundle\FrameworkBundle\Command; use Symfony\Bundle\FrameworkBundle\Console\Helper\DescriptorHelper; -use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; 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\DependencyInjection\Exception\ServiceNotFoundException; -use Symfony\Component\Routing\Route; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\RouterInterface; /** @@ -29,50 +31,23 @@ use Symfony\Component\Routing\RouterInterface; * @author Fabien Potencier * @author Tobias Schultze * - * @final since version 3.4 + * @final */ -class RouterDebugCommand extends ContainerAwareCommand +class RouterDebugCommand extends Command { + use BuildDebugContainerTrait; + protected static $defaultName = 'debug:router'; + protected static $defaultDescription = 'Display current routes for an application'; private $router; + private $fileLinkFormatter; - /** - * @param RouterInterface $router - */ - public function __construct($router = null) + public function __construct(RouterInterface $router, FileLinkFormatter $fileLinkFormatter = null) { - if (!$router instanceof RouterInterface) { - @trigger_error(sprintf('%s() expects an instance of "%s" as first argument since Symfony 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.', __METHOD__, RouterInterface::class), \E_USER_DEPRECATED); - - parent::__construct($router); - - return; - } - parent::__construct(); $this->router = $router; - } - - /** - * {@inheritdoc} - * - * BC to be removed in 4.0 - */ - public function isEnabled() - { - if (null !== $this->router) { - return parent::isEnabled(); - } - if (!$this->getContainer()->has('router')) { - return false; - } - $router = $this->getContainer()->get('router'); - if (!$router instanceof RouterInterface) { - return false; - } - - return parent::isEnabled(); + $this->fileLinkFormatter = $fileLinkFormatter; } /** @@ -87,7 +62,7 @@ class RouterDebugCommand extends ContainerAwareCommand new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw route(s)'), ]) - ->setDescription('Displays current routes for an application') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% displays the configured routes: @@ -101,87 +76,101 @@ EOF /** * {@inheritdoc} * - * @throws \InvalidArgumentException When route does not exist + * @throws InvalidArgumentException When route does not exist */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { - // BC to be removed in 4.0 - if (null === $this->router) { - $this->router = $this->getContainer()->get('router'); - } - $io = new SymfonyStyle($input, $output); $name = $input->getArgument('name'); - $helper = new DescriptorHelper(); + $helper = new DescriptorHelper($this->fileLinkFormatter); $routes = $this->router->getRouteCollection(); + $container = null; + if ($this->fileLinkFormatter) { + $container = function () { + return $this->getContainerBuilder($this->getApplication()->getKernel()); + }; + } if ($name) { - if (!$route = $routes->get($name)) { - throw new InvalidArgumentException(sprintf('The route "%s" does not exist.', $name)); + $route = $routes->get($name); + $matchingRoutes = $this->findRouteNameContaining($name, $routes); + + if (!$input->isInteractive() && !$route && \count($matchingRoutes) > 1) { + $helper->describe($io, $this->findRouteContaining($name, $routes), [ + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + 'show_controllers' => $input->getOption('show-controllers'), + 'output' => $io, + ]); + + return 0; } - $callable = $this->extractCallable($route); + if (!$route && $matchingRoutes) { + $default = 1 === \count($matchingRoutes) ? $matchingRoutes[0] : null; + $name = $io->choice('Select one of the matching routes', $matchingRoutes, $default); + $route = $routes->get($name); + } + + if (!$route) { + throw new InvalidArgumentException(sprintf('The route "%s" does not exist.', $name)); + } $helper->describe($io, $route, [ 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'name' => $name, 'output' => $io, - 'callable' => $callable, + 'container' => $container, ]); } else { - foreach ($routes as $route) { - $this->convertController($route); - } - $helper->describe($io, $routes, [ 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'show_controllers' => $input->getOption('show-controllers'), 'output' => $io, + 'container' => $container, ]); } + + return 0; } - private function convertController(Route $route) + private function findRouteNameContaining(string $name, RouteCollection $routes): array { - if ($route->hasDefault('_controller')) { - $nameParser = new ControllerNameParser($this->getApplication()->getKernel()); - try { - $route->setDefault('_controller', $nameParser->build($route->getDefault('_controller'))); - } catch (\InvalidArgumentException $e) { - } - } - } - - /** - * @return callable|null - */ - private function extractCallable(Route $route) - { - if (!$route->hasDefault('_controller')) { - return null; - } - - $controller = $route->getDefault('_controller'); - - if (1 === substr_count($controller, ':')) { - list($service, $method) = explode(':', $controller); - try { - return sprintf('%s::%s', \get_class($this->getApplication()->getKernel()->getContainer()->get($service)), $method); - } catch (ServiceNotFoundException $e) { + $foundRoutesNames = []; + foreach ($routes as $routeName => $route) { + if (false !== stripos($routeName, $name)) { + $foundRoutesNames[] = $routeName; } } - $nameParser = new ControllerNameParser($this->getApplication()->getKernel()); - try { - $shortNotation = $nameParser->build($controller); - $route->setDefault('_controller', $shortNotation); + return $foundRoutesNames; + } - return $controller; - } catch (\InvalidArgumentException $e) { + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('name')) { + $suggestions->suggestValues(array_keys($this->router->getRouteCollection()->all())); + + return; } - return null; + if ($input->mustSuggestOptionValuesFor('format')) { + $helper = new DescriptorHelper(); + $suggestions->suggestValues($helper->getFormats()); + } + } + + private function findRouteContaining(string $name, RouteCollection $routes): RouteCollection + { + $foundRoutes = new RouteCollection(); + foreach ($routes as $routeName => $route) { + if (false !== stripos($routeName, $name)) { + $foundRoutes->add($routeName, $route); + } + } + + return $foundRoutes; } } diff --git a/lib/symfony/framework-bundle/Command/RouterMatchCommand.php b/lib/symfony/framework-bundle/Command/RouterMatchCommand.php index b7ac3ef8e6..6cceb945dd 100644 --- a/lib/symfony/framework-bundle/Command/RouterMatchCommand.php +++ b/lib/symfony/framework-bundle/Command/RouterMatchCommand.php @@ -11,12 +11,14 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputArgument; 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\ExpressionLanguage\ExpressionFunctionProviderInterface; use Symfony\Component\Routing\Matcher\TraceableUrlMatcher; use Symfony\Component\Routing\RouterInterface; @@ -25,51 +27,25 @@ use Symfony\Component\Routing\RouterInterface; * * @author Fabien Potencier * - * @final since version 3.4 + * @final */ -class RouterMatchCommand extends ContainerAwareCommand +class RouterMatchCommand extends Command { protected static $defaultName = 'router:match'; + protected static $defaultDescription = 'Help debug routes by simulating a path info match'; private $router; + private $expressionLanguageProviders; /** - * @param RouterInterface $router + * @param iterable $expressionLanguageProviders */ - public function __construct($router = null) + public function __construct(RouterInterface $router, iterable $expressionLanguageProviders = []) { - if (!$router instanceof RouterInterface) { - @trigger_error(sprintf('%s() expects an instance of "%s" as first argument since Symfony 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.', __METHOD__, RouterInterface::class), \E_USER_DEPRECATED); - - parent::__construct($router); - - return; - } - parent::__construct(); $this->router = $router; - } - - /** - * {@inheritdoc} - * - * BC to be removed in 4.0 - */ - public function isEnabled() - { - if (null !== $this->router) { - return parent::isEnabled(); - } - if (!$this->getContainer()->has('router')) { - return false; - } - $router = $this->getContainer()->get('router'); - if (!$router instanceof RouterInterface) { - return false; - } - - return parent::isEnabled(); + $this->expressionLanguageProviders = $expressionLanguageProviders; } /** @@ -80,11 +56,11 @@ class RouterMatchCommand extends ContainerAwareCommand $this ->setDefinition([ new InputArgument('path_info', InputArgument::REQUIRED, 'A path info'), - new InputOption('method', null, InputOption::VALUE_REQUIRED, 'Sets the HTTP method'), - new InputOption('scheme', null, InputOption::VALUE_REQUIRED, 'Sets the URI scheme (usually http or https)'), - new InputOption('host', null, InputOption::VALUE_REQUIRED, 'Sets the URI host'), + new InputOption('method', null, InputOption::VALUE_REQUIRED, 'Set the HTTP method'), + new InputOption('scheme', null, InputOption::VALUE_REQUIRED, 'Set the URI scheme (usually http or https)'), + new InputOption('host', null, InputOption::VALUE_REQUIRED, 'Set the URI host'), ]) - ->setDescription('Helps debug routes by simulating a path info match') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% shows which routes match a given request and which don't and for what reason: @@ -102,13 +78,8 @@ EOF /** * {@inheritdoc} */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { - // BC to be removed in 4.0 - if (null === $this->router) { - $this->router = $this->getContainer()->get('router'); - } - $io = new SymfonyStyle($input, $output); $context = $this->router->getContext(); @@ -123,6 +94,9 @@ EOF } $matcher = new TraceableUrlMatcher($this->router->getRouteCollection(), $context); + foreach ($this->expressionLanguageProviders as $provider) { + $matcher->addExpressionLanguageProvider($provider); + } $traces = $matcher->getTraces($input->getArgument('path_info')); @@ -150,6 +124,6 @@ EOF return 1; } - return null; + return 0; } } diff --git a/lib/symfony/framework-bundle/Command/SecretsDecryptToLocalCommand.php b/lib/symfony/framework-bundle/Command/SecretsDecryptToLocalCommand.php new file mode 100644 index 0000000000..0e07d88fa3 --- /dev/null +++ b/lib/symfony/framework-bundle/Command/SecretsDecryptToLocalCommand.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class SecretsDecryptToLocalCommand extends Command +{ + protected static $defaultName = 'secrets:decrypt-to-local'; + protected static $defaultDescription = 'Decrypt all secrets and stores them in the local vault'; + + private $vault; + private $localVault; + + public function __construct(AbstractVault $vault, AbstractVault $localVault = null) + { + $this->vault = $vault; + $this->localVault = $localVault; + + parent::__construct(); + } + + protected function configure() + { + $this + ->setDescription(self::$defaultDescription) + ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force overriding of secrets that already exist in the local vault') + ->setHelp(<<<'EOF' +The %command.name% command decrypts all secrets and copies them in the local vault. + + %command.full_name% + +When the option --force is provided, secrets that already exist in the local vault are overriden. + + %command.full_name% --force +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + if (null === $this->localVault) { + $io->error('The local vault is disabled.'); + + return 1; + } + + $secrets = $this->vault->list(true); + + $io->comment(sprintf('%d secret%s found in the vault.', \count($secrets), 1 !== \count($secrets) ? 's' : '')); + + $skipped = 0; + if (!$input->getOption('force')) { + foreach ($this->localVault->list() as $k => $v) { + if (isset($secrets[$k])) { + ++$skipped; + unset($secrets[$k]); + } + } + } + + if ($skipped > 0) { + $io->warning([ + sprintf('%d secret%s already overridden in the local vault and will be skipped.', $skipped, 1 !== $skipped ? 's are' : ' is'), + 'Use the --force flag to override these.', + ]); + } + + foreach ($secrets as $k => $v) { + if (null === $v) { + $io->error($this->vault->getLastMessage() ?? sprintf('Secret "%s" has been skipped as there was an error reading it.', $k)); + continue; + } + + $this->localVault->seal($k, $v); + $io->note($this->localVault->getLastMessage()); + } + + return 0; + } +} diff --git a/lib/symfony/framework-bundle/Command/SecretsEncryptFromLocalCommand.php b/lib/symfony/framework-bundle/Command/SecretsEncryptFromLocalCommand.php new file mode 100644 index 0000000000..79f51c51ad --- /dev/null +++ b/lib/symfony/framework-bundle/Command/SecretsEncryptFromLocalCommand.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\Bundle\FrameworkBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class SecretsEncryptFromLocalCommand extends Command +{ + protected static $defaultName = 'secrets:encrypt-from-local'; + protected static $defaultDescription = 'Encrypt all local secrets to the vault'; + + private $vault; + private $localVault; + + public function __construct(AbstractVault $vault, AbstractVault $localVault = null) + { + $this->vault = $vault; + $this->localVault = $localVault; + + parent::__construct(); + } + + protected function configure() + { + $this + ->setDescription(self::$defaultDescription) + ->setHelp(<<<'EOF' +The %command.name% command encrypts all locally overridden secrets to the vault. + + %command.full_name% +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + if (null === $this->localVault) { + $io->error('The local vault is disabled.'); + + return 1; + } + + foreach ($this->vault->list(true) as $name => $value) { + $localValue = $this->localVault->reveal($name); + + if (null !== $localValue && $value !== $localValue) { + $this->vault->seal($name, $localValue); + } elseif (null !== $message = $this->localVault->getLastMessage()) { + $io->error($message); + + return 1; + } + } + + return 0; + } +} diff --git a/lib/symfony/framework-bundle/Command/SecretsGenerateKeysCommand.php b/lib/symfony/framework-bundle/Command/SecretsGenerateKeysCommand.php new file mode 100644 index 0000000000..a9440b4c8f --- /dev/null +++ b/lib/symfony/framework-bundle/Command/SecretsGenerateKeysCommand.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @author Tobias Schultze + * @author Jérémy Derussé + * @author Nicolas Grekas + * + * @internal + */ +final class SecretsGenerateKeysCommand extends Command +{ + protected static $defaultName = 'secrets:generate-keys'; + protected static $defaultDescription = 'Generate new encryption keys'; + + private $vault; + private $localVault; + + public function __construct(AbstractVault $vault, AbstractVault $localVault = null) + { + $this->vault = $vault; + $this->localVault = $localVault; + + parent::__construct(); + } + + protected function configure() + { + $this + ->setDescription(self::$defaultDescription) + ->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.') + ->addOption('rotate', 'r', InputOption::VALUE_NONE, 'Re-encrypt existing secrets with the newly generated keys.') + ->setHelp(<<<'EOF' +The %command.name% command generates a new encryption key. + + %command.full_name% + +If encryption keys already exist, the command must be called with +the --rotate option in order to override those keys and re-encrypt +existing secrets. + + %command.full_name% --rotate +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + $vault = $input->getOption('local') ? $this->localVault : $this->vault; + + if (null === $vault) { + $io->success('The local vault is disabled.'); + + return 1; + } + + if (!$input->getOption('rotate')) { + if ($vault->generateKeys()) { + $io->success($vault->getLastMessage()); + + if ($this->vault === $vault) { + $io->caution('DO NOT COMMIT THE DECRYPTION KEY FOR THE PROD ENVIRONMENT⚠️'); + } + + return 0; + } + + $io->warning($vault->getLastMessage()); + + return 1; + } + + $secrets = []; + foreach ($vault->list(true) as $name => $value) { + if (null === $value) { + $io->error($vault->getLastMessage()); + + return 1; + } + + $secrets[$name] = $value; + } + + if (!$vault->generateKeys(true)) { + $io->warning($vault->getLastMessage()); + + return 1; + } + + $io->success($vault->getLastMessage()); + + if ($secrets) { + foreach ($secrets as $name => $value) { + $vault->seal($name, $value); + } + + $io->comment('Existing secrets have been rotated to the new keys.'); + } + + if ($this->vault === $vault) { + $io->caution('DO NOT COMMIT THE DECRYPTION KEY FOR THE PROD ENVIRONMENT⚠️'); + } + + return 0; + } +} diff --git a/lib/symfony/framework-bundle/Command/SecretsListCommand.php b/lib/symfony/framework-bundle/Command/SecretsListCommand.php new file mode 100644 index 0000000000..0b13e0cf21 --- /dev/null +++ b/lib/symfony/framework-bundle/Command/SecretsListCommand.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Dumper; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @author Tobias Schultze + * @author Jérémy Derussé + * @author Nicolas Grekas + * + * @internal + */ +final class SecretsListCommand extends Command +{ + protected static $defaultName = 'secrets:list'; + protected static $defaultDescription = 'List all secrets'; + + private $vault; + private $localVault; + + public function __construct(AbstractVault $vault, AbstractVault $localVault = null) + { + $this->vault = $vault; + $this->localVault = $localVault; + + parent::__construct(); + } + + protected function configure() + { + $this + ->setDescription(self::$defaultDescription) + ->addOption('reveal', 'r', InputOption::VALUE_NONE, 'Display decrypted values alongside names') + ->setHelp(<<<'EOF' +The %command.name% command list all stored secrets. + + %command.full_name% + +When the option --reveal is provided, the decrypted secrets are also displayed. + + %command.full_name% --reveal +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + + $io->comment('Use "%env()%" to reference a secret in a config file.'); + + if (!$reveal = $input->getOption('reveal')) { + $io->comment(sprintf('To reveal the secrets run php %s %s --reveal', $_SERVER['PHP_SELF'], $this->getName())); + } + + $secrets = $this->vault->list($reveal); + $localSecrets = null !== $this->localVault ? $this->localVault->list($reveal) : null; + + $rows = []; + + $dump = new Dumper($output); + $dump = static function (?string $v) use ($dump) { + return null === $v ? '******' : $dump($v); + }; + + foreach ($secrets as $name => $value) { + $rows[$name] = [$name, $dump($value)]; + } + + if (null !== $message = $this->vault->getLastMessage()) { + $io->comment($message); + } + + foreach ($localSecrets ?? [] as $name => $value) { + if (isset($rows[$name])) { + $rows[$name][] = $dump($value); + } + } + + if (null !== $this->localVault && null !== $message = $this->localVault->getLastMessage()) { + $io->comment($message); + } + + (new SymfonyStyle($input, $output)) + ->table(['Secret', 'Value'] + (null !== $localSecrets ? [2 => 'Local Value'] : []), $rows); + + $io->comment("Local values override secret values.\nUse secrets:set --local to define them."); + + return 0; + } +} diff --git a/lib/symfony/framework-bundle/Command/SecretsRemoveCommand.php b/lib/symfony/framework-bundle/Command/SecretsRemoveCommand.php new file mode 100644 index 0000000000..0451ef300f --- /dev/null +++ b/lib/symfony/framework-bundle/Command/SecretsRemoveCommand.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @author Jérémy Derussé + * @author Nicolas Grekas + * + * @internal + */ +final class SecretsRemoveCommand extends Command +{ + protected static $defaultName = 'secrets:remove'; + protected static $defaultDescription = 'Remove a secret from the vault'; + + private $vault; + private $localVault; + + public function __construct(AbstractVault $vault, AbstractVault $localVault = null) + { + $this->vault = $vault; + $this->localVault = $localVault; + + parent::__construct(); + } + + protected function configure() + { + $this + ->setDescription(self::$defaultDescription) + ->addArgument('name', InputArgument::REQUIRED, 'The name of the secret') + ->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.') + ->setHelp(<<<'EOF' +The %command.name% command removes a secret from the vault. + + %command.full_name% +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output); + $vault = $input->getOption('local') ? $this->localVault : $this->vault; + + if (null === $vault) { + $io->success('The local vault is disabled.'); + + return 1; + } + + if ($vault->remove($name = $input->getArgument('name'))) { + $io->success($vault->getLastMessage() ?? 'Secret was removed from the vault.'); + } else { + $io->comment($vault->getLastMessage() ?? 'Secret was not found in the vault.'); + } + + if ($this->vault === $vault && null !== $this->localVault->reveal($name)) { + $io->comment('Note that this secret is overridden in the local vault.'); + } + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if (!$input->mustSuggestArgumentValuesFor('name')) { + return; + } + + $vaultKeys = array_keys($this->vault->list(false)); + if ($input->getOption('local')) { + if (null === $this->localVault) { + return; + } + $vaultKeys = array_intersect($vaultKeys, array_keys($this->localVault->list(false))); + } + + $suggestions->suggestValues($vaultKeys); + } +} diff --git a/lib/symfony/framework-bundle/Command/SecretsSetCommand.php b/lib/symfony/framework-bundle/Command/SecretsSetCommand.php new file mode 100644 index 0000000000..412247da70 --- /dev/null +++ b/lib/symfony/framework-bundle/Command/SecretsSetCommand.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Secrets\AbstractVault; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * @author Tobias Schultze + * @author Jérémy Derussé + * @author Nicolas Grekas + * + * @internal + */ +final class SecretsSetCommand extends Command +{ + protected static $defaultName = 'secrets:set'; + protected static $defaultDescription = 'Set a secret in the vault'; + + private $vault; + private $localVault; + + public function __construct(AbstractVault $vault, AbstractVault $localVault = null) + { + $this->vault = $vault; + $this->localVault = $localVault; + + parent::__construct(); + } + + protected function configure() + { + $this + ->setDescription(self::$defaultDescription) + ->addArgument('name', InputArgument::REQUIRED, 'The name of the secret') + ->addArgument('file', InputArgument::OPTIONAL, 'A file where to read the secret from or "-" for reading from STDIN') + ->addOption('local', 'l', InputOption::VALUE_NONE, 'Update the local vault.') + ->addOption('random', 'r', InputOption::VALUE_OPTIONAL, 'Generate a random value.', false) + ->setHelp(<<<'EOF' +The %command.name% command stores a secret in the vault. + + %command.full_name% + +To reference secrets in services.yaml or any other config +files, use "%env()%". + +By default, the secret value should be entered interactively. +Alternatively, provide a file where to read the secret from: + + php %command.full_name% filename + +Use "-" as a file name to read from STDIN: + + cat filename | php %command.full_name% - + +Use --local to override secrets for local needs. +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $errOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output; + $io = new SymfonyStyle($input, $errOutput); + $name = $input->getArgument('name'); + $vault = $input->getOption('local') ? $this->localVault : $this->vault; + + if (null === $vault) { + $io->error('The local vault is disabled.'); + + return 1; + } + + if ($this->localVault === $vault && !\array_key_exists($name, $this->vault->list())) { + $io->error(sprintf('Secret "%s" does not exist in the vault, you cannot override it locally.', $name)); + + return 1; + } + + if (0 < $random = $input->getOption('random') ?? 16) { + $value = strtr(substr(base64_encode(random_bytes($random)), 0, $random), '+/', '-_'); + } elseif (!$file = $input->getArgument('file')) { + $value = $io->askHidden('Please type the secret value'); + + if (null === $value) { + $io->warning('No value provided: using empty string'); + $value = ''; + } + } elseif ('-' === $file) { + $value = file_get_contents('php://stdin'); + } elseif (is_file($file) && is_readable($file)) { + $value = file_get_contents($file); + } elseif (!is_file($file)) { + throw new \InvalidArgumentException(sprintf('File not found: "%s".', $file)); + } elseif (!is_readable($file)) { + throw new \InvalidArgumentException(sprintf('File is not readable: "%s".', $file)); + } + + if ($vault->generateKeys()) { + $io->success($vault->getLastMessage()); + + if ($this->vault === $vault) { + $io->caution('DO NOT COMMIT THE DECRYPTION KEY FOR THE PROD ENVIRONMENT⚠️'); + } + } + + $vault->seal($name, $value); + + $io->success($vault->getLastMessage() ?? 'Secret was successfully stored in the vault.'); + + if (0 < $random) { + $errOutput->write(' // The generated random value is: '); + $output->write($value); + $errOutput->writeln(''); + $io->newLine(); + } + + if ($this->vault === $vault && null !== $this->localVault->reveal($name)) { + $io->comment('Note that this secret is overridden in the local vault.'); + } + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('name')) { + $suggestions->suggestValues(array_keys($this->vault->list(false))); + } + } +} diff --git a/lib/symfony/framework-bundle/Command/TranslationDebugCommand.php b/lib/symfony/framework-bundle/Command/TranslationDebugCommand.php index 75000e12cd..006fd25055 100644 --- a/lib/symfony/framework-bundle/Command/TranslationDebugCommand.php +++ b/lib/symfony/framework-bundle/Command/TranslationDebugCommand.php @@ -11,6 +11,9 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -25,7 +28,7 @@ use Symfony\Component\Translation\LoggingTranslator; use Symfony\Component\Translation\MessageCatalogue; use Symfony\Component\Translation\Reader\TranslationReaderInterface; use Symfony\Component\Translation\Translator; -use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; /** * Helps finding unused or missing translation messages in a given locale @@ -33,32 +36,32 @@ use Symfony\Component\Translation\TranslatorInterface; * * @author Florian Voutzinos * - * @final since version 3.4 + * @final */ -class TranslationDebugCommand extends ContainerAwareCommand +class TranslationDebugCommand extends Command { - const MESSAGE_MISSING = 0; - const MESSAGE_UNUSED = 1; - const MESSAGE_EQUALS_FALLBACK = 2; + public const EXIT_CODE_GENERAL_ERROR = 64; + public const EXIT_CODE_MISSING = 65; + public const EXIT_CODE_UNUSED = 66; + public const EXIT_CODE_FALLBACK = 68; + public const MESSAGE_MISSING = 0; + public const MESSAGE_UNUSED = 1; + public const MESSAGE_EQUALS_FALLBACK = 2; protected static $defaultName = 'debug:translation'; + protected static $defaultDescription = 'Display translation messages information'; private $translator; private $reader; private $extractor; private $defaultTransPath; private $defaultViewsPath; + private $transPaths; + private $codePaths; + private $enabledLocales; - public function __construct($translator = null, TranslationReaderInterface $reader = null, ExtractorInterface $extractor = null, $defaultTransPath = null, $defaultViewsPath = null) + public function __construct(TranslatorInterface $translator, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = []) { - if (!$translator instanceof TranslatorInterface) { - @trigger_error(sprintf('%s() expects an instance of "%s" as first argument since Symfony 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.', __METHOD__, TranslatorInterface::class), \E_USER_DEPRECATED); - - parent::__construct($translator); - - return; - } - parent::__construct(); $this->translator = $translator; @@ -66,6 +69,9 @@ class TranslationDebugCommand extends ContainerAwareCommand $this->extractor = $extractor; $this->defaultTransPath = $defaultTransPath; $this->defaultViewsPath = $defaultViewsPath; + $this->transPaths = $transPaths; + $this->codePaths = $codePaths; + $this->enabledLocales = $enabledLocales; } /** @@ -78,11 +84,11 @@ class TranslationDebugCommand extends ContainerAwareCommand new InputArgument('locale', InputArgument::REQUIRED, 'The locale'), new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages'), new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'The messages domain'), - new InputOption('only-missing', null, InputOption::VALUE_NONE, 'Displays only missing messages'), - new InputOption('only-unused', null, InputOption::VALUE_NONE, 'Displays only unused messages'), + new InputOption('only-missing', null, InputOption::VALUE_NONE, 'Display only missing messages'), + new InputOption('only-unused', null, InputOption::VALUE_NONE, 'Display only unused messages'), new InputOption('all', null, InputOption::VALUE_NONE, 'Load messages from all registered bundles'), ]) - ->setDescription('Displays translation messages information') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command helps finding unused or missing translation messages and comparing them with the fallback ones by inspecting the @@ -119,70 +125,42 @@ EOF /** * {@inheritdoc} - * - * BC to be removed in 4.0 */ - public function isEnabled() + protected function execute(InputInterface $input, OutputInterface $output): int { - if (null !== $this->translator) { - return parent::isEnabled(); - } - if (!class_exists('Symfony\Component\Translation\Translator')) { - return false; - } - - return parent::isEnabled(); - } - - /** - * {@inheritdoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - // BC to be removed in 4.0 - if (null === $this->translator) { - $this->translator = $this->getContainer()->get('translator'); - $this->reader = $this->getContainer()->get('translation.reader'); - $this->extractor = $this->getContainer()->get('translation.extractor'); - $this->defaultTransPath = $this->getContainer()->getParameter('translator.default_path'); - $this->defaultViewsPath = $this->getContainer()->getParameter('twig.default_path'); - } - $io = new SymfonyStyle($input, $output); $locale = $input->getArgument('locale'); $domain = $input->getOption('domain'); + + $exitCode = self::SUCCESS; + /** @var KernelInterface $kernel */ $kernel = $this->getApplication()->getKernel(); // Define Root Paths - $transPaths = [$kernel->getRootDir().'/Resources/translations']; - if ($this->defaultTransPath) { - $transPaths[] = $this->defaultTransPath; - } - $viewsPaths = [$kernel->getRootDir().'/Resources/views']; - if ($this->defaultViewsPath) { - $viewsPaths[] = $this->defaultViewsPath; - } + $transPaths = $this->getRootTransPaths(); + $codePaths = $this->getRootCodePaths($kernel); // Override with provided Bundle info if (null !== $input->getArgument('bundle')) { try { $bundle = $kernel->getBundle($input->getArgument('bundle')); - $transPaths = [$bundle->getPath().'/Resources/translations']; + $bundleDir = $bundle->getPath(); + $transPaths = [is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundleDir.'/translations']; + $codePaths = [is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' : $bundleDir.'/templates']; if ($this->defaultTransPath) { - $transPaths[] = $this->defaultTransPath.'/'.$bundle->getName(); + $transPaths[] = $this->defaultTransPath; } - $transPaths[] = sprintf('%s/Resources/%s/translations', $kernel->getRootDir(), $bundle->getName()); - $viewsPaths = [$bundle->getPath().'/Resources/views']; if ($this->defaultViewsPath) { - $viewsPaths[] = $this->defaultViewsPath.'/bundles/'.$bundle->getName(); + $codePaths[] = $this->defaultViewsPath; } - $viewsPaths[] = sprintf('%s/Resources/%s/views', $kernel->getRootDir(), $bundle->getName()); } catch (\InvalidArgumentException $e) { // such a bundle does not exist, so treat the argument as path - $transPaths = [$input->getArgument('bundle').'/Resources/translations']; - $viewsPaths = [$input->getArgument('bundle').'/Resources/views']; + $path = $input->getArgument('bundle'); + + $transPaths = [$path.'/translations']; + $codePaths = [$path.'/templates']; if (!is_dir($transPaths[0])) { throw new InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0])); @@ -190,21 +168,14 @@ EOF } } elseif ($input->getOption('all')) { foreach ($kernel->getBundles() as $bundle) { - $transPaths[] = $bundle->getPath().'/Resources/translations'; - if ($this->defaultTransPath) { - $transPaths[] = $this->defaultTransPath.'/'.$bundle->getName(); - } - $transPaths[] = sprintf('%s/Resources/%s/translations', $kernel->getRootDir(), $bundle->getName()); - $viewsPaths[] = $bundle->getPath().'/Resources/views'; - if ($this->defaultViewsPath) { - $viewsPaths[] = $this->defaultViewsPath.'/bundles/'.$bundle->getName(); - } - $viewsPaths[] = sprintf('%s/Resources/%s/views', $kernel->getRootDir(), $bundle->getName()); + $bundleDir = $bundle->getPath(); + $transPaths[] = is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundle->getPath().'/translations'; + $codePaths[] = is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' : $bundle->getPath().'/templates'; } } // Extract used messages - $extractedCatalogue = $this->extractMessages($locale, $viewsPaths); + $extractedCatalogue = $this->extractMessages($locale, $codePaths); // Load defined messages $currentCatalogue = $this->loadCurrentMessages($locale, $transPaths); @@ -226,7 +197,7 @@ EOF $io->getErrorStyle()->warning($outputMessage); - return; + return self::EXIT_CODE_GENERAL_ERROR; } // Load the fallback catalogues @@ -247,13 +218,22 @@ EOF if ($extractedCatalogue->defines($messageId, $domain)) { if (!$currentCatalogue->defines($messageId, $domain)) { $states[] = self::MESSAGE_MISSING; + + if (!$input->getOption('only-unused')) { + $exitCode = $exitCode | self::EXIT_CODE_MISSING; + } } } elseif ($currentCatalogue->defines($messageId, $domain)) { $states[] = self::MESSAGE_UNUSED; + + if (!$input->getOption('only-missing')) { + $exitCode = $exitCode | self::EXIT_CODE_UNUSED; + } } - if (!\in_array(self::MESSAGE_UNUSED, $states) && true === $input->getOption('only-unused') - || !\in_array(self::MESSAGE_MISSING, $states) && true === $input->getOption('only-missing')) { + if (!\in_array(self::MESSAGE_UNUSED, $states) && $input->getOption('only-unused') + || !\in_array(self::MESSAGE_MISSING, $states) && $input->getOption('only-missing') + ) { continue; } @@ -261,6 +241,8 @@ EOF if ($fallbackCatalogue->defines($messageId, $domain) && $value === $fallbackCatalogue->get($messageId, $domain)) { $states[] = self::MESSAGE_EQUALS_FALLBACK; + $exitCode = $exitCode | self::EXIT_CODE_FALLBACK; + break; } } @@ -275,9 +257,49 @@ EOF } $io->table($headers, $rows); + + return $exitCode; } - private function formatState($state) + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('locale')) { + $suggestions->suggestValues($this->enabledLocales); + + return; + } + + /** @var KernelInterface $kernel */ + $kernel = $this->getApplication()->getKernel(); + + if ($input->mustSuggestArgumentValuesFor('bundle')) { + $availableBundles = []; + foreach ($kernel->getBundles() as $bundle) { + $availableBundles[] = $bundle->getName(); + + if ($extension = $bundle->getContainerExtension()) { + $availableBundles[] = $extension->getAlias(); + } + } + + $suggestions->suggestValues($availableBundles); + + return; + } + + if ($input->mustSuggestOptionValuesFor('domain')) { + $locale = $input->getArgument('locale'); + + $mergeOperation = new MergeOperation( + $this->extractMessages($locale, $this->getRootCodePaths($kernel)), + $this->loadCurrentMessages($locale, $this->getRootTransPaths()) + ); + + $suggestions->suggestValues($mergeOperation->getDomains()); + } + } + + private function formatState(int $state): string { if (self::MESSAGE_MISSING === $state) { return ' missing '; @@ -294,7 +316,7 @@ EOF return $state; } - private function formatStates(array $states) + private function formatStates(array $states): string { $result = []; foreach ($states as $state) { @@ -304,12 +326,12 @@ EOF return implode(' ', $result); } - private function formatId($id) + private function formatId(string $id): string { return sprintf('%s', $id); } - private function sanitizeString($string, $length = 40) + private function sanitizeString(string $string, int $length = 40): string { $string = trim(preg_replace('/\s+/', ' ', $string)); @@ -324,17 +346,11 @@ EOF return $string; } - /** - * @param string $locale - * @param array $transPaths - * - * @return MessageCatalogue - */ - private function extractMessages($locale, $transPaths) + private function extractMessages(string $locale, array $transPaths): MessageCatalogue { $extractedCatalogue = new MessageCatalogue($locale); foreach ($transPaths as $path) { - if (is_dir($path)) { + if (is_dir($path) || is_file($path)) { $this->extractor->extract($path, $extractedCatalogue); } } @@ -342,13 +358,7 @@ EOF return $extractedCatalogue; } - /** - * @param string $locale - * @param array $transPaths - * - * @return MessageCatalogue - */ - private function loadCurrentMessages($locale, $transPaths) + private function loadCurrentMessages(string $locale, array $transPaths): MessageCatalogue { $currentCatalogue = new MessageCatalogue($locale); foreach ($transPaths as $path) { @@ -361,12 +371,9 @@ EOF } /** - * @param string $locale - * @param array $transPaths - * * @return MessageCatalogue[] */ - private function loadFallbackCatalogues($locale, $transPaths) + private function loadFallbackCatalogues(string $locale, array $transPaths): array { $fallbackCatalogues = []; if ($this->translator instanceof Translator || $this->translator instanceof DataCollectorTranslator || $this->translator instanceof LoggingTranslator) { @@ -387,4 +394,25 @@ EOF return $fallbackCatalogues; } + + private function getRootTransPaths(): array + { + $transPaths = $this->transPaths; + if ($this->defaultTransPath) { + $transPaths[] = $this->defaultTransPath; + } + + return $transPaths; + } + + private function getRootCodePaths(KernelInterface $kernel): array + { + $codePaths = $this->codePaths; + $codePaths[] = $kernel->getProjectDir().'/src'; + if ($this->defaultViewsPath) { + $codePaths[] = $this->defaultViewsPath; + } + + return $codePaths; + } } diff --git a/lib/symfony/framework-bundle/Command/TranslationUpdateCommand.php b/lib/symfony/framework-bundle/Command/TranslationUpdateCommand.php index 33d5563c8c..de9332615e 100644 --- a/lib/symfony/framework-bundle/Command/TranslationUpdateCommand.php +++ b/lib/symfony/framework-bundle/Command/TranslationUpdateCommand.php @@ -11,10 +11,14 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\HttpKernel\KernelInterface; @@ -22,6 +26,7 @@ use Symfony\Component\Translation\Catalogue\MergeOperation; use Symfony\Component\Translation\Catalogue\TargetOperation; use Symfony\Component\Translation\Extractor\ExtractorInterface; use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueInterface; use Symfony\Component\Translation\Reader\TranslationReaderInterface; use Symfony\Component\Translation\Writer\TranslationWriterInterface; @@ -31,11 +36,20 @@ use Symfony\Component\Translation\Writer\TranslationWriterInterface; * * @author Michel Salib * - * @final since version 3.4 + * @final */ -class TranslationUpdateCommand extends ContainerAwareCommand +class TranslationUpdateCommand extends Command { - protected static $defaultName = 'translation:update'; + private const ASC = 'asc'; + private const DESC = 'desc'; + private const SORT_ORDERS = [self::ASC, self::DESC]; + private const FORMATS = [ + 'xlf12' => ['xlf', '1.2'], + 'xlf20' => ['xlf', '2.0'], + ]; + + protected static $defaultName = 'translation:extract|translation:update'; + protected static $defaultDescription = 'Extract missing translations keys from code to translation files.'; private $writer; private $reader; @@ -43,25 +57,12 @@ class TranslationUpdateCommand extends ContainerAwareCommand private $defaultLocale; private $defaultTransPath; private $defaultViewsPath; + private $transPaths; + private $codePaths; + private $enabledLocales; - /** - * @param TranslationWriterInterface $writer - * @param TranslationReaderInterface $reader - * @param ExtractorInterface $extractor - * @param string $defaultLocale - * @param string $defaultTransPath - * @param string $defaultViewsPath - */ - public function __construct($writer = null, TranslationReaderInterface $reader = null, ExtractorInterface $extractor = null, $defaultLocale = null, $defaultTransPath = null, $defaultViewsPath = null) + public function __construct(TranslationWriterInterface $writer, TranslationReaderInterface $reader, ExtractorInterface $extractor, string $defaultLocale, string $defaultTransPath = null, string $defaultViewsPath = null, array $transPaths = [], array $codePaths = [], array $enabledLocales = []) { - if (!$writer instanceof TranslationWriterInterface) { - @trigger_error(sprintf('%s() expects an instance of "%s" as first argument since Symfony 3.4. Not passing it is deprecated and will throw a TypeError in 4.0.', __METHOD__, TranslationWriterInterface::class), \E_USER_DEPRECATED); - - parent::__construct($writer); - - return; - } - parent::__construct(); $this->writer = $writer; @@ -70,6 +71,9 @@ class TranslationUpdateCommand extends ContainerAwareCommand $this->defaultLocale = $defaultLocale; $this->defaultTransPath = $defaultTransPath; $this->defaultViewsPath = $defaultViewsPath; + $this->transPaths = $transPaths; + $this->codePaths = $codePaths; + $this->enabledLocales = $enabledLocales; } /** @@ -82,29 +86,45 @@ class TranslationUpdateCommand extends ContainerAwareCommand new InputArgument('locale', InputArgument::REQUIRED, 'The locale'), new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages'), new InputOption('prefix', null, InputOption::VALUE_OPTIONAL, 'Override the default prefix', '__'), - new InputOption('no-prefix', null, InputOption::VALUE_NONE, '[DEPRECATED] If set, no prefix is added to the translations'), - new InputOption('output-format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format', 'yaml'), + new InputOption('output-format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format (deprecated)'), + new InputOption('format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format', 'xlf12'), new InputOption('dump-messages', null, InputOption::VALUE_NONE, 'Should the messages be dumped in the console'), - new InputOption('force', null, InputOption::VALUE_NONE, 'Should the update be done'), - new InputOption('no-backup', null, InputOption::VALUE_NONE, 'Should backup be disabled'), + new InputOption('force', null, InputOption::VALUE_NONE, 'Should the extract be done'), new InputOption('clean', null, InputOption::VALUE_NONE, 'Should clean not found messages'), - new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'Specify the domain to update'), + new InputOption('domain', null, InputOption::VALUE_OPTIONAL, 'Specify the domain to extract'), + new InputOption('xliff-version', null, InputOption::VALUE_OPTIONAL, 'Override the default xliff version (deprecated)'), + new InputOption('sort', null, InputOption::VALUE_OPTIONAL, 'Return list of messages sorted alphabetically', 'asc'), + new InputOption('as-tree', null, InputOption::VALUE_OPTIONAL, 'Dump the messages as a tree-like structure: The given value defines the level where to switch to inline YAML'), ]) - ->setDescription('Updates the translation file') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command extracts translation strings from templates -of a given bundle or the default translations directory. It can display them or merge the new ones into the translation files. +of a given bundle or the default translations directory. It can display them or merge +the new ones into the translation files. When new translation strings are found it can automatically add a prefix to the translation message. Example running against a Bundle (AcmeBundle) + php %command.full_name% --dump-messages en AcmeBundle php %command.full_name% --force --prefix="new_" fr AcmeBundle Example running against default messages directory + php %command.full_name% --dump-messages en php %command.full_name% --force --prefix="new_" fr + +You can sort the output with the --sort flag: + + php %command.full_name% --dump-messages --sort=asc en AcmeBundle + php %command.full_name% --dump-messages --sort=desc fr + +You can dump a tree-like structure using the yaml format with --as-tree flag: + + php %command.full_name% --force --format=yaml --as-tree=3 en AcmeBundle + php %command.full_name% --force --format=yaml --sort=asc --as-tree=3 fr + EOF ) ; @@ -112,34 +132,14 @@ EOF /** * {@inheritdoc} - * - * BC to be removed in 4.0 */ - public function isEnabled() + protected function execute(InputInterface $input, OutputInterface $output): int { - if (null !== $this->writer) { - return parent::isEnabled(); - } - if (!class_exists('Symfony\Component\Translation\Translator')) { - return false; - } + $io = new SymfonyStyle($input, $output); + $errorIo = $output instanceof ConsoleOutputInterface ? new SymfonyStyle($input, $output->getErrorOutput()) : $io; - return parent::isEnabled(); - } - - /** - * {@inheritdoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - // BC to be removed in 4.0 - if (null === $this->writer) { - $this->writer = $this->getContainer()->get('translation.writer'); - $this->reader = $this->getContainer()->get('translation.reader'); - $this->extractor = $this->getContainer()->get('translation.extractor'); - $this->defaultLocale = $this->getContainer()->getParameter('kernel.default_locale'); - $this->defaultTransPath = $this->getContainer()->getParameter('translator.default_path'); - $this->defaultViewsPath = $this->getContainer()->getParameter('twig.default_path'); + if ('translation:update' === $input->getFirstArgument()) { + $errorIo->caution('Command "translation:update" is deprecated since version 5.4 and will be removed in Symfony 6.0. Use "translation:extract" instead.'); } $io = new SymfonyStyle($input, $output); @@ -152,47 +152,58 @@ EOF return 1; } + $format = $input->getOption('output-format') ?: $input->getOption('format'); + $xliffVersion = $input->getOption('xliff-version') ?? '1.2'; + + if ($input->getOption('xliff-version')) { + $errorIo->warning(sprintf('The "--xliff-version" option is deprecated since version 5.3, use "--format=xlf%d" instead.', 10 * $xliffVersion)); + } + + if ($input->getOption('output-format')) { + $errorIo->warning(sprintf('The "--output-format" option is deprecated since version 5.3, use "--format=xlf%d" instead.', 10 * $xliffVersion)); + } + + if (\in_array($format, array_keys(self::FORMATS), true)) { + [$format, $xliffVersion] = self::FORMATS[$format]; + } + // check format $supportedFormats = $this->writer->getFormats(); - if (!\in_array($input->getOption('output-format'), $supportedFormats)) { - $errorIo->error(['Wrong output format', 'Supported formats are: '.implode(', ', $supportedFormats).'.']); + if (!\in_array($format, $supportedFormats, true)) { + $errorIo->error(['Wrong output format', 'Supported formats are: '.implode(', ', $supportedFormats).', xlf12 and xlf20.']); return 1; } + /** @var KernelInterface $kernel */ $kernel = $this->getApplication()->getKernel(); // Define Root Paths - $transPaths = [$kernel->getRootDir().'/Resources/translations']; - if ($this->defaultTransPath) { - $transPaths[] = $this->defaultTransPath; - } - $viewsPaths = [$kernel->getRootDir().'/Resources/views']; - if ($this->defaultViewsPath) { - $viewsPaths[] = $this->defaultViewsPath; - } + $transPaths = $this->getRootTransPaths(); + $codePaths = $this->getRootCodePaths($kernel); + $currentName = 'default directory'; // Override with provided Bundle info if (null !== $input->getArgument('bundle')) { try { $foundBundle = $kernel->getBundle($input->getArgument('bundle')); - $transPaths = [$foundBundle->getPath().'/Resources/translations']; + $bundleDir = $foundBundle->getPath(); + $transPaths = [is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundleDir.'/translations']; + $codePaths = [is_dir($bundleDir.'/Resources/views') ? $bundleDir.'/Resources/views' : $bundleDir.'/templates']; if ($this->defaultTransPath) { - $transPaths[] = $this->defaultTransPath.'/'.$foundBundle->getName(); + $transPaths[] = $this->defaultTransPath; } - $transPaths[] = sprintf('%s/Resources/%s/translations', $kernel->getRootDir(), $foundBundle->getName()); - $viewsPaths = [$foundBundle->getPath().'/Resources/views']; if ($this->defaultViewsPath) { - $viewsPaths[] = $this->defaultViewsPath.'/bundles/'.$foundBundle->getName(); + $codePaths[] = $this->defaultViewsPath; } - $viewsPaths[] = sprintf('%s/Resources/%s/views', $kernel->getRootDir(), $foundBundle->getName()); $currentName = $foundBundle->getName(); } catch (\InvalidArgumentException $e) { // such a bundle does not exist, so treat the argument as path - $transPaths = [$input->getArgument('bundle').'/Resources/translations']; - $viewsPaths = [$input->getArgument('bundle').'/Resources/views']; - $currentName = $transPaths[0]; + $path = $input->getArgument('bundle'); + + $transPaths = [$path.'/translations']; + $codePaths = [$path.'/templates']; if (!is_dir($transPaths[0])) { throw new InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0])); @@ -203,30 +214,11 @@ EOF $io->title('Translation Messages Extractor and Dumper'); $io->comment(sprintf('Generating "%s" translation files for "%s"', $input->getArgument('locale'), $currentName)); - // load any messages from templates - $extractedCatalogue = new MessageCatalogue($input->getArgument('locale')); $io->comment('Parsing templates...'); - $prefix = $input->getOption('prefix'); - // @deprecated since version 3.4, to be removed in 4.0 along with the --no-prefix option - if ($input->getOption('no-prefix')) { - @trigger_error('The "--no-prefix" option is deprecated since Symfony 3.4 and will be removed in 4.0. Use the "--prefix" option with an empty string as value instead.', \E_USER_DEPRECATED); - $prefix = ''; - } - $this->extractor->setPrefix($prefix); - foreach ($viewsPaths as $path) { - if (is_dir($path)) { - $this->extractor->extract($path, $extractedCatalogue); - } - } + $extractedCatalogue = $this->extractMessages($input->getArgument('locale'), $codePaths, $input->getOption('prefix')); - // load any existing messages from the translation files - $currentCatalogue = new MessageCatalogue($input->getArgument('locale')); $io->comment('Loading translation files...'); - foreach ($transPaths as $path) { - if (is_dir($path)) { - $this->reader->read($path, $currentCatalogue); - } - } + $currentCatalogue = $this->loadCurrentMessages($input->getArgument('locale'), $transPaths); if (null !== $domain = $input->getOption('domain')) { $currentCatalogue = $this->filterCatalogue($currentCatalogue, $domain); @@ -242,11 +234,13 @@ EOF if (!\count($operation->getDomains())) { $errorIo->warning('No translation messages were found.'); - return null; + return 0; } $resultMessage = 'Translation files were successfully updated'; + $operation->moveMessagesToIntlDomainsIfPossible('new'); + // show compiled list of messages if (true === $input->getOption('dump-messages')) { $extractedMessagesCount = 0; @@ -267,23 +261,34 @@ EOF $domainMessagesCount = \count($list); + if ($sort = $input->getOption('sort')) { + $sort = strtolower($sort); + if (!\in_array($sort, self::SORT_ORDERS, true)) { + $errorIo->error(['Wrong sort order', 'Supported formats are: '.implode(', ', self::SORT_ORDERS).'.']); + + return 1; + } + + if (self::DESC === $sort) { + rsort($list); + } else { + sort($list); + } + } + $io->section(sprintf('Messages extracted for domain "%s" (%d message%s)', $domain, $domainMessagesCount, $domainMessagesCount > 1 ? 's' : '')); $io->listing($list); $extractedMessagesCount += $domainMessagesCount; } - if ('xlf' == $input->getOption('output-format')) { - $io->comment('Xliff output version is 1.2'); + if ('xlf' === $format) { + $io->comment(sprintf('Xliff output version is %s', $xliffVersion)); } $resultMessage = sprintf('%d message%s successfully extracted', $extractedMessagesCount, $extractedMessagesCount > 1 ? 's were' : ' was'); } - if (true === $input->getOption('no-backup')) { - $this->writer->disableBackup(); - } - // save the files if (true === $input->getOption('force')) { $io->comment('Writing files...'); @@ -299,7 +304,7 @@ EOF $bundleTransPath = end($transPaths); } - $this->writer->write($operation->getResult(), $input->getOption('output-format'), ['path' => $bundleTransPath, 'default_locale' => $this->defaultLocale]); + $this->writer->write($operation->getResult(), $format, ['path' => $bundleTransPath, 'default_locale' => $this->defaultLocale, 'xliff_version' => $xliffVersion, 'as_tree' => $input->getOption('as-tree'), 'inline' => $input->getOption('as-tree') ?? 0]); if (true === $input->getOption('dump-messages')) { $resultMessage .= ' and translation files were updated'; @@ -308,19 +313,87 @@ EOF $io->success($resultMessage.'.'); - return null; + return 0; } - private function filterCatalogue(MessageCatalogue $catalogue, $domain) + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('locale')) { + $suggestions->suggestValues($this->enabledLocales); + + return; + } + + /** @var KernelInterface $kernel */ + $kernel = $this->getApplication()->getKernel(); + if ($input->mustSuggestArgumentValuesFor('bundle')) { + $bundles = []; + + foreach ($kernel->getBundles() as $bundle) { + $bundles[] = $bundle->getName(); + if ($bundle->getContainerExtension()) { + $bundles[] = $bundle->getContainerExtension()->getAlias(); + } + } + + $suggestions->suggestValues($bundles); + + return; + } + + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues(array_merge( + $this->writer->getFormats(), + array_keys(self::FORMATS) + )); + + return; + } + + if ($input->mustSuggestOptionValuesFor('domain') && $locale = $input->getArgument('locale')) { + $extractedCatalogue = $this->extractMessages($locale, $this->getRootCodePaths($kernel), $input->getOption('prefix')); + + $currentCatalogue = $this->loadCurrentMessages($locale, $this->getRootTransPaths()); + + // process catalogues + $operation = $input->getOption('clean') + ? new TargetOperation($currentCatalogue, $extractedCatalogue) + : new MergeOperation($currentCatalogue, $extractedCatalogue); + + $suggestions->suggestValues($operation->getDomains()); + + return; + } + + if ($input->mustSuggestOptionValuesFor('sort')) { + $suggestions->suggestValues(self::SORT_ORDERS); + } + } + + private function filterCatalogue(MessageCatalogue $catalogue, string $domain): MessageCatalogue { $filteredCatalogue = new MessageCatalogue($catalogue->getLocale()); - if ($messages = $catalogue->all($domain)) { + // extract intl-icu messages only + $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX; + if ($intlMessages = $catalogue->all($intlDomain)) { + $filteredCatalogue->add($intlMessages, $intlDomain); + } + + // extract all messages and subtract intl-icu messages + if ($messages = array_diff($catalogue->all($domain), $intlMessages)) { $filteredCatalogue->add($messages, $domain); } foreach ($catalogue->getResources() as $resource) { $filteredCatalogue->addResource($resource); } + + if ($metadata = $catalogue->getMetadata('', $intlDomain)) { + foreach ($metadata as $k => $v) { + $filteredCatalogue->setMetadata($k, $v, $intlDomain); + } + } + if ($metadata = $catalogue->getMetadata('', $domain)) { foreach ($metadata as $k => $v) { $filteredCatalogue->setMetadata($k, $v, $domain); @@ -329,4 +402,50 @@ EOF return $filteredCatalogue; } + + private function extractMessages(string $locale, array $transPaths, string $prefix): MessageCatalogue + { + $extractedCatalogue = new MessageCatalogue($locale); + $this->extractor->setPrefix($prefix); + foreach ($transPaths as $path) { + if (is_dir($path) || is_file($path)) { + $this->extractor->extract($path, $extractedCatalogue); + } + } + + return $extractedCatalogue; + } + + private function loadCurrentMessages(string $locale, array $transPaths): MessageCatalogue + { + $currentCatalogue = new MessageCatalogue($locale); + foreach ($transPaths as $path) { + if (is_dir($path)) { + $this->reader->read($path, $currentCatalogue); + } + } + + return $currentCatalogue; + } + + private function getRootTransPaths(): array + { + $transPaths = $this->transPaths; + if ($this->defaultTransPath) { + $transPaths[] = $this->defaultTransPath; + } + + return $transPaths; + } + + private function getRootCodePaths(KernelInterface $kernel): array + { + $codePaths = $this->codePaths; + $codePaths[] = $kernel->getProjectDir().'/src'; + if ($this->defaultViewsPath) { + $codePaths[] = $this->defaultViewsPath; + } + + return $codePaths; + } } diff --git a/lib/symfony/framework-bundle/Command/WorkflowDumpCommand.php b/lib/symfony/framework-bundle/Command/WorkflowDumpCommand.php index fa11f792dd..89d29b9815 100644 --- a/lib/symfony/framework-bundle/Command/WorkflowDumpCommand.php +++ b/lib/symfony/framework-bundle/Command/WorkflowDumpCommand.php @@ -11,22 +11,49 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Workflow\Definition; use Symfony\Component\Workflow\Dumper\GraphvizDumper; +use Symfony\Component\Workflow\Dumper\MermaidDumper; +use Symfony\Component\Workflow\Dumper\PlantUmlDumper; use Symfony\Component\Workflow\Dumper\StateMachineGraphvizDumper; use Symfony\Component\Workflow\Marking; /** * @author Grégoire Pineau * - * @final since version 3.4 + * @final */ -class WorkflowDumpCommand extends ContainerAwareCommand +class WorkflowDumpCommand extends Command { protected static $defaultName = 'workflow:dump'; + protected static $defaultDescription = 'Dump a workflow'; + /** + * string is the service id. + * + * @var array + */ + private $workflows = []; + + private const DUMP_FORMAT_OPTIONS = [ + 'puml', + 'mermaid', + 'dot', + ]; + + public function __construct(array $workflows) + { + parent::__construct(); + + $this->workflows = $workflows; + } /** * {@inheritdoc} @@ -37,14 +64,17 @@ class WorkflowDumpCommand extends ContainerAwareCommand ->setDefinition([ new InputArgument('name', InputArgument::REQUIRED, 'A workflow name'), new InputArgument('marking', InputArgument::IS_ARRAY, 'A marking (a list of places)'), + new InputOption('label', 'l', InputOption::VALUE_REQUIRED, 'Label a graph'), + new InputOption('dump-format', null, InputOption::VALUE_REQUIRED, 'The dump format ['.implode('|', self::DUMP_FORMAT_OPTIONS).']', 'dot'), ]) - ->setDescription('Dump a workflow') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command dumps the graphical representation of a -workflow in DOT format - - %command.full_name% | dot -Tpng > workflow.png +workflow in different formats +DOT: %command.full_name% | dot -Tpng > workflow.png +PUML: %command.full_name% --dump-format=puml | java -jar plantuml.jar -p > workflow.png +MERMAID: %command.full_name% --dump-format=mermaid | mmdc -o workflow.svg EOF ) ; @@ -53,18 +83,38 @@ EOF /** * {@inheritdoc} */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { - $container = $this->getApplication()->getKernel()->getContainer(); - $serviceId = $input->getArgument('name'); - if ($container->has('workflow.'.$serviceId)) { - $workflow = $container->get('workflow.'.$serviceId); - $dumper = new GraphvizDumper(); - } elseif ($container->has('state_machine.'.$serviceId)) { - $workflow = $container->get('state_machine.'.$serviceId); - $dumper = new StateMachineGraphvizDumper(); - } else { - throw new InvalidArgumentException(sprintf('No service found for "workflow.%1$s" nor "state_machine.%1$s".', $serviceId)); + $workflowName = $input->getArgument('name'); + + $workflow = null; + + if (isset($this->workflows['workflow.'.$workflowName])) { + $workflow = $this->workflows['workflow.'.$workflowName]; + $type = 'workflow'; + } elseif (isset($this->workflows['state_machine.'.$workflowName])) { + $workflow = $this->workflows['state_machine.'.$workflowName]; + $type = 'state_machine'; + } + + if (null === $workflow) { + throw new InvalidArgumentException(sprintf('No service found for "workflow.%1$s" nor "state_machine.%1$s".', $workflowName)); + } + + switch ($input->getOption('dump-format')) { + case 'puml': + $transitionType = 'workflow' === $type ? PlantUmlDumper::WORKFLOW_TRANSITION : PlantUmlDumper::STATEMACHINE_TRANSITION; + $dumper = new PlantUmlDumper($transitionType); + break; + + case 'mermaid': + $transitionType = 'workflow' === $type ? MermaidDumper::TRANSITION_TYPE_WORKFLOW : MermaidDumper::TRANSITION_TYPE_STATEMACHINE; + $dumper = new MermaidDumper($transitionType); + break; + + case 'dot': + default: + $dumper = ('workflow' === $type) ? new GraphvizDumper() : new StateMachineGraphvizDumper(); } $marking = new Marking(); @@ -73,6 +123,26 @@ EOF $marking->mark($place); } - $output->writeln($dumper->dump($workflow->getDefinition(), $marking)); + $options = [ + 'name' => $workflowName, + 'nofooter' => true, + 'graph' => [ + 'label' => $input->getOption('label'), + ], + ]; + $output->writeln($dumper->dump($workflow, $marking, $options)); + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('name')) { + $suggestions->suggestValues(array_keys($this->workflows)); + } + + if ($input->mustSuggestOptionValuesFor('dump-format')) { + $suggestions->suggestValues(self::DUMP_FORMAT_OPTIONS); + } } } diff --git a/lib/symfony/framework-bundle/Command/XliffLintCommand.php b/lib/symfony/framework-bundle/Command/XliffLintCommand.php index d00337d09f..b1f631739c 100644 --- a/lib/symfony/framework-bundle/Command/XliffLintCommand.php +++ b/lib/symfony/framework-bundle/Command/XliffLintCommand.php @@ -20,35 +20,28 @@ use Symfony\Component\Translation\Command\XliffLintCommand as BaseLintCommand; * @author Robin Chalas * @author Javier Eguiluz * - * @final since version 3.4 + * @final */ class XliffLintCommand extends BaseLintCommand { protected static $defaultName = 'lint:xliff'; + protected static $defaultDescription = 'Lints an XLIFF file and outputs encountered errors'; - public function __construct($name = null, $directoryIteratorProvider = null, $isReadableProvider = null) + public function __construct() { - if (\func_num_args()) { - @trigger_error(sprintf('Passing a constructor argument in "%s()" is deprecated since Symfony 3.4 and will be removed in 4.0. If the command was registered by convention, make it a service instead.', __METHOD__), \E_USER_DEPRECATED); - } + $directoryIteratorProvider = function ($directory, $default) { + if (!is_dir($directory)) { + $directory = $this->getApplication()->getKernel()->locateResource($directory); + } - if (null === $directoryIteratorProvider) { - $directoryIteratorProvider = function ($directory, $default) { - if (!is_dir($directory)) { - $directory = $this->getApplication()->getKernel()->locateResource($directory); - } + return $default($directory); + }; - return $default($directory); - }; - } + $isReadableProvider = function ($fileOrDirectory, $default) { + return str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory); + }; - if (null === $isReadableProvider) { - $isReadableProvider = function ($fileOrDirectory, $default) { - return 0 === strpos($fileOrDirectory, '@') || $default($fileOrDirectory); - }; - } - - parent::__construct($name, $directoryIteratorProvider, $isReadableProvider); + parent::__construct(null, $directoryIteratorProvider, $isReadableProvider); } /** @@ -65,6 +58,6 @@ Or find all files in a bundle: php %command.full_name% @AcmeDemoBundle EOF - ); + ); } } diff --git a/lib/symfony/framework-bundle/Command/YamlLintCommand.php b/lib/symfony/framework-bundle/Command/YamlLintCommand.php index fbd847a244..3a432f2758 100644 --- a/lib/symfony/framework-bundle/Command/YamlLintCommand.php +++ b/lib/symfony/framework-bundle/Command/YamlLintCommand.php @@ -19,35 +19,28 @@ use Symfony\Component\Yaml\Command\LintCommand as BaseLintCommand; * @author Grégoire Pineau * @author Robin Chalas * - * @final since version 3.4 + * @final */ class YamlLintCommand extends BaseLintCommand { protected static $defaultName = 'lint:yaml'; + protected static $defaultDescription = 'Lint a YAML file and outputs encountered errors'; - public function __construct($name = null, $directoryIteratorProvider = null, $isReadableProvider = null) + public function __construct() { - if (\func_num_args()) { - @trigger_error(sprintf('Passing a constructor argument in "%s()" is deprecated since Symfony 3.4 and will be removed in 4.0. If the command was registered by convention, make it a service instead.', __METHOD__), \E_USER_DEPRECATED); - } + $directoryIteratorProvider = function ($directory, $default) { + if (!is_dir($directory)) { + $directory = $this->getApplication()->getKernel()->locateResource($directory); + } - if (null === $directoryIteratorProvider) { - $directoryIteratorProvider = function ($directory, $default) { - if (!is_dir($directory)) { - $directory = $this->getApplication()->getKernel()->locateResource($directory); - } + return $default($directory); + }; - return $default($directory); - }; - } + $isReadableProvider = function ($fileOrDirectory, $default) { + return str_starts_with($fileOrDirectory, '@') || $default($fileOrDirectory); + }; - if (null === $isReadableProvider) { - $isReadableProvider = function ($fileOrDirectory, $default) { - return 0 === strpos($fileOrDirectory, '@') || $default($fileOrDirectory); - }; - } - - parent::__construct($name, $directoryIteratorProvider, $isReadableProvider); + parent::__construct(null, $directoryIteratorProvider, $isReadableProvider); } /** diff --git a/lib/symfony/framework-bundle/Console/Application.php b/lib/symfony/framework-bundle/Console/Application.php index 513c6eb662..55510b9d59 100644 --- a/lib/symfony/framework-bundle/Console/Application.php +++ b/lib/symfony/framework-bundle/Console/Application.php @@ -13,12 +13,12 @@ namespace Symfony\Bundle\FrameworkBundle\Console; use Symfony\Component\Console\Application as BaseApplication; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\ListCommand; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\Debug\Exception\FatalThrowableError; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\Kernel; @@ -41,19 +41,29 @@ class Application extends BaseApplication $inputDefinition = $this->getDefinition(); $inputDefinition->addOption(new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', $kernel->getEnvironment())); - $inputDefinition->addOption(new InputOption('--no-debug', null, InputOption::VALUE_NONE, 'Switches off debug mode.')); + $inputDefinition->addOption(new InputOption('--no-debug', null, InputOption::VALUE_NONE, 'Switch off debug mode.')); } /** * Gets the Kernel associated with this Console. * - * @return KernelInterface A KernelInterface instance + * @return KernelInterface */ public function getKernel() { return $this->kernel; } + /** + * {@inheritdoc} + */ + public function reset() + { + if ($this->kernel->getContainer()->has('services_resetter')) { + $this->kernel->getContainer()->get('services_resetter')->reset(); + } + } + /** * Runs the current application. * @@ -61,16 +71,14 @@ class Application extends BaseApplication */ public function doRun(InputInterface $input, OutputInterface $output) { - $this->kernel->boot(); - - $this->setDispatcher($this->kernel->getContainer()->get('event_dispatcher')); - $this->registerCommands(); if ($this->registrationErrors) { $this->renderRegistrationErrors($input, $output); } + $this->setDispatcher($this->kernel->getContainer()->get('event_dispatcher')); + return parent::doRun($input, $output); } @@ -79,17 +87,29 @@ class Application extends BaseApplication */ protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) { - if ($this->registrationErrors) { - $this->renderRegistrationErrors($input, $output); + if (!$command instanceof ListCommand) { + if ($this->registrationErrors) { + $this->renderRegistrationErrors($input, $output); + $this->registrationErrors = []; + } + + return parent::doRunCommand($command, $input, $output); } - return parent::doRunCommand($command, $input, $output); + $returnCode = parent::doRunCommand($command, $input, $output); + + if ($this->registrationErrors) { + $this->renderRegistrationErrors($input, $output); + $this->registrationErrors = []; + } + + return $returnCode; } /** * {@inheritdoc} */ - public function find($name) + public function find(string $name) { $this->registerCommands(); @@ -99,7 +119,7 @@ class Application extends BaseApplication /** * {@inheritdoc} */ - public function get($name) + public function get(string $name) { $this->registerCommands(); @@ -115,7 +135,7 @@ class Application extends BaseApplication /** * {@inheritdoc} */ - public function all($namespace = null) + public function all(string $namespace = null) { $this->registerCommands(); @@ -127,7 +147,7 @@ class Application extends BaseApplication */ public function getLongVersion() { - return parent::getLongVersion().sprintf(' (kernel: %s, env: %s, debug: %s)', $this->kernel->getName(), $this->kernel->getEnvironment(), $this->kernel->isDebug() ? 'true' : 'false'); + return parent::getLongVersion().sprintf(' (env: %s, debug: %s) #StandWithUkraine https://sf.to/ukraine', $this->kernel->getEnvironment(), $this->kernel->isDebug() ? 'true' : 'false'); } public function add(Command $command) @@ -153,10 +173,8 @@ class Application extends BaseApplication if ($bundle instanceof Bundle) { try { $bundle->registerCommands($this); - } catch (\Exception $e) { - $this->registrationErrors[] = $e; } catch (\Throwable $e) { - $this->registrationErrors[] = new FatalThrowableError($e); + $this->registrationErrors[] = $e; } } } @@ -171,10 +189,8 @@ class Application extends BaseApplication if (!isset($lazyCommandIds[$id])) { try { $this->add($container->get($id)); - } catch (\Exception $e) { - $this->registrationErrors[] = $e; } catch (\Throwable $e) { - $this->registrationErrors[] = new FatalThrowableError($e); + $this->registrationErrors[] = $e; } } } @@ -190,9 +206,7 @@ class Application extends BaseApplication (new SymfonyStyle($input, $output))->warning('Some commands could not be registered:'); foreach ($this->registrationErrors as $error) { - $this->doRenderException($error, $output); + $this->doRenderThrowable($error, $output); } - - $this->registrationErrors = []; } } diff --git a/lib/symfony/framework-bundle/Console/Descriptor/Descriptor.php b/lib/symfony/framework-bundle/Console/Descriptor/Descriptor.php index a28ca356d8..537d6d08c3 100644 --- a/lib/symfony/framework-bundle/Console/Descriptor/Descriptor.php +++ b/lib/symfony/framework-bundle/Console/Descriptor/Descriptor.php @@ -11,10 +11,12 @@ namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; +use Symfony\Component\Config\Resource\ClassExistenceResource; use Symfony\Component\Console\Descriptor\DescriptorInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -50,6 +52,9 @@ abstract class Descriptor implements DescriptorInterface case $object instanceof ParameterBag: $this->describeContainerParameters($object, $options); break; + case $object instanceof ContainerBuilder && !empty($options['env-vars']): + $this->describeContainerEnvVars($this->getContainerEnvVars($object), $options); + break; case $object instanceof ContainerBuilder && isset($options['group_by']) && 'tags' === $options['group_by']: $this->describeContainerTags($object, $options); break; @@ -59,6 +64,9 @@ abstract class Descriptor implements DescriptorInterface case $object instanceof ContainerBuilder && isset($options['parameter']): $this->describeContainerParameter($object->resolveEnvPlaceholders($object->getParameter($options['parameter'])), $options); break; + case $object instanceof ContainerBuilder && isset($options['deprecations']): + $this->describeContainerDeprecations($object, $options); + break; case $object instanceof ContainerBuilder: $this->describeContainerServices($object, $options); break; @@ -75,27 +83,16 @@ abstract class Descriptor implements DescriptorInterface $this->describeCallable($object, $options); break; default: - throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', \get_class($object))); + throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object))); } } - /** - * Returns the output. - * - * @return OutputInterface The output - */ - protected function getOutput() + protected function getOutput(): OutputInterface { return $this->output; } - /** - * Writes content to output. - * - * @param string $content - * @param bool $decorated - */ - protected function write($content, $decorated = false) + protected function write(string $content, bool $decorated = false) { $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); } @@ -116,7 +113,7 @@ abstract class Descriptor implements DescriptorInterface * * @param Definition|Alias|object $service */ - abstract protected function describeContainerService($service, array $options = [], ContainerBuilder $builder = null); + abstract protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null); /** * Describes container services. @@ -126,12 +123,16 @@ abstract class Descriptor implements DescriptorInterface */ abstract protected function describeContainerServices(ContainerBuilder $builder, array $options = []); + abstract protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []): void; + abstract protected function describeContainerDefinition(Definition $definition, array $options = []); abstract protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null); abstract protected function describeContainerParameter($parameter, array $options = []); + abstract protected function describeContainerEnvVars(array $envs, array $options = []); + /** * Describes event dispatcher listeners. * @@ -151,11 +152,13 @@ abstract class Descriptor implements DescriptorInterface * Formats a value as string. * * @param mixed $value - * - * @return string */ - protected function formatValue($value) + protected function formatValue($value): string { + if ($value instanceof \UnitEnum) { + return ltrim(var_export($value, true), '\\'); + } + if (\is_object($value)) { return sprintf('object(%s)', \get_class($value)); } @@ -171,11 +174,23 @@ abstract class Descriptor implements DescriptorInterface * Formats a parameter. * * @param mixed $value - * - * @return string */ - protected function formatParameter($value) + protected function formatParameter($value): string { + if ($value instanceof \UnitEnum) { + return ltrim(var_export($value, true), '\\'); + } + + // Recursively search for enum values, so we can replace it + // before json_encode (which will not display anything for \UnitEnum otherwise) + if (\is_array($value)) { + array_walk_recursive($value, static function (&$value) { + if ($value instanceof \UnitEnum) { + $value = ltrim(var_export($value, true), '\\'); + } + }); + } + if (\is_bool($value) || \is_array($value) || (null === $value)) { $jsonString = json_encode($value); @@ -190,11 +205,9 @@ abstract class Descriptor implements DescriptorInterface } /** - * @param string $serviceId - * * @return mixed */ - protected function resolveServiceDefinition(ContainerBuilder $builder, $serviceId) + protected function resolveServiceDefinition(ContainerBuilder $builder, string $serviceId) { if ($builder->hasDefinition($serviceId)) { return $builder->getDefinition($serviceId); @@ -205,16 +218,15 @@ abstract class Descriptor implements DescriptorInterface return $builder->getAlias($serviceId); } + if ('service_container' === $serviceId) { + return (new Definition(ContainerInterface::class))->setPublic(true)->setSynthetic(true); + } + // the service has been injected in some special way, just return the service return $builder->get($serviceId); } - /** - * @param bool $showPrivate - * - * @return array - */ - protected function findDefinitionsByTag(ContainerBuilder $builder, $showPrivate) + protected function findDefinitionsByTag(ContainerBuilder $builder, bool $showHidden): array { $definitions = []; $tags = $builder->findTags(); @@ -224,7 +236,7 @@ abstract class Descriptor implements DescriptorInterface foreach ($builder->findTaggedServiceIds($tag) as $serviceId => $attributes) { $definition = $this->resolveServiceDefinition($builder, $serviceId); - if (!$definition instanceof Definition || !$showPrivate && !$definition->isPublic()) { + if ($showHidden xor '.' === ($serviceId[0] ?? null)) { continue; } @@ -253,4 +265,116 @@ abstract class Descriptor implements DescriptorInterface return $serviceIds; } + + protected function sortTaggedServicesByPriority(array $services): array + { + $maxPriority = []; + foreach ($services as $service => $tags) { + $maxPriority[$service] = \PHP_INT_MIN; + foreach ($tags as $tag) { + $currentPriority = $tag['priority'] ?? 0; + if ($maxPriority[$service] < $currentPriority) { + $maxPriority[$service] = $currentPriority; + } + } + } + uasort($maxPriority, function ($a, $b) { + return $b <=> $a; + }); + + return array_keys($maxPriority); + } + + protected function sortTagsByPriority(array $tags): array + { + $sortedTags = []; + foreach ($tags as $tagName => $tag) { + $sortedTags[$tagName] = $this->sortByPriority($tag); + } + + return $sortedTags; + } + + protected function sortByPriority(array $tag): array + { + usort($tag, function ($a, $b) { + return ($b['priority'] ?? 0) <=> ($a['priority'] ?? 0); + }); + + return $tag; + } + + public static function getClassDescription(string $class, string &$resolvedClass = null): string + { + $resolvedClass = $class; + try { + $resource = new ClassExistenceResource($class, false); + + // isFresh() will explode ONLY if a parent class/trait does not exist + $resource->isFresh(0); + + $r = new \ReflectionClass($class); + $resolvedClass = $r->name; + + if ($docComment = $r->getDocComment()) { + $docComment = preg_split('#\n\s*\*\s*[\n@]#', substr($docComment, 3, -2), 2)[0]; + + return trim(preg_replace('#\s*\n\s*\*\s*#', ' ', $docComment)); + } + } catch (\ReflectionException $e) { + } + + return ''; + } + + private function getContainerEnvVars(ContainerBuilder $container): array + { + if (!$container->hasParameter('debug.container.dump')) { + return []; + } + + if (!is_file($container->getParameter('debug.container.dump'))) { + return []; + } + + $file = file_get_contents($container->getParameter('debug.container.dump')); + preg_match_all('{%env\(((?:\w++:)*+\w++)\)%}', $file, $envVars); + $envVars = array_unique($envVars[1]); + + $bag = $container->getParameterBag(); + $getDefaultParameter = function (string $name) { + return parent::get($name); + }; + $getDefaultParameter = $getDefaultParameter->bindTo($bag, \get_class($bag)); + + $getEnvReflection = new \ReflectionMethod($container, 'getEnv'); + $getEnvReflection->setAccessible(true); + + $envs = []; + + foreach ($envVars as $env) { + $processor = 'string'; + if (false !== $i = strrpos($name = $env, ':')) { + $name = substr($env, $i + 1); + $processor = substr($env, 0, $i); + } + $defaultValue = ($hasDefault = $container->hasParameter("env($name)")) ? $getDefaultParameter("env($name)") : null; + if (false === ($runtimeValue = $_ENV[$name] ?? $_SERVER[$name] ?? getenv($name))) { + $runtimeValue = null; + } + $processedValue = ($hasRuntime = null !== $runtimeValue) || $hasDefault ? $getEnvReflection->invoke($container, $env) : null; + $envs["$name$processor"] = [ + 'name' => $name, + 'processor' => $processor, + 'default_available' => $hasDefault, + 'default_value' => $defaultValue, + 'runtime_available' => $hasRuntime, + 'runtime_value' => $runtimeValue, + 'processed_value' => $processedValue, + ]; + } + ksort($envs); + + return array_values($envs); + } } diff --git a/lib/symfony/framework-bundle/Console/Descriptor/JsonDescriptor.php b/lib/symfony/framework-bundle/Console/Descriptor/JsonDescriptor.php index 0427f8e616..3e282ac10a 100644 --- a/lib/symfony/framework-bundle/Console/Descriptor/JsonDescriptor.php +++ b/lib/symfony/framework-bundle/Console/Descriptor/JsonDescriptor.php @@ -11,7 +11,10 @@ namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -51,10 +54,10 @@ class JsonDescriptor extends Descriptor protected function describeContainerTags(ContainerBuilder $builder, array $options = []) { - $showPrivate = isset($options['show_private']) && $options['show_private']; + $showHidden = isset($options['show_hidden']) && $options['show_hidden']; $data = []; - foreach ($this->findDefinitionsByTag($builder, $showPrivate) as $tag => $definitions) { + foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) { $data[$tag] = []; foreach ($definitions as $definition) { $data[$tag][] = $this->getContainerDefinitionData($definition, true); @@ -64,10 +67,7 @@ class JsonDescriptor extends Descriptor $this->writeData($data, $options); } - /** - * {@inheritdoc} - */ - protected function describeContainerService($service, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null) { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); @@ -82,13 +82,12 @@ class JsonDescriptor extends Descriptor } } - /** - * {@inheritdoc} - */ protected function describeContainerServices(ContainerBuilder $builder, array $options = []) { - $serviceIds = isset($options['tag']) && $options['tag'] ? array_keys($builder->findTaggedServiceIds($options['tag'])) : $builder->getServiceIds(); - $showPrivate = isset($options['show_private']) && $options['show_private']; + $serviceIds = isset($options['tag']) && $options['tag'] + ? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($options['tag'])) + : $this->sortServiceIds($builder->getServiceIds()); + $showHidden = isset($options['show_hidden']) && $options['show_hidden']; $omitTags = isset($options['omit_tags']) && $options['omit_tags']; $showArguments = isset($options['show_arguments']) && $options['show_arguments']; $data = ['definitions' => [], 'aliases' => [], 'services' => []]; @@ -97,17 +96,17 @@ class JsonDescriptor extends Descriptor $serviceIds = array_filter($serviceIds, $options['filter']); } - foreach ($this->sortServiceIds($serviceIds) as $serviceId) { + foreach ($serviceIds as $serviceId) { $service = $this->resolveServiceDefinition($builder, $serviceId); + if ($showHidden xor '.' === ($serviceId[0] ?? null)) { + continue; + } + if ($service instanceof Alias) { - if ($showPrivate || ($service->isPublic() && !$service->isPrivate())) { - $data['aliases'][$serviceId] = $this->getContainerAliasData($service); - } + $data['aliases'][$serviceId] = $this->getContainerAliasData($service); } elseif ($service instanceof Definition) { - if (($showPrivate || ($service->isPublic() && !$service->isPrivate()))) { - $data['definitions'][$serviceId] = $this->getContainerDefinitionData($service, $omitTags, $showArguments); - } + $data['definitions'][$serviceId] = $this->getContainerDefinitionData($service, $omitTags, $showArguments); } else { $data['services'][$serviceId] = \get_class($service); } @@ -135,17 +134,11 @@ class JsonDescriptor extends Descriptor ); } - /** - * {@inheritdoc} - */ protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) { - $this->writeData($this->getEventDispatcherListenersData($eventDispatcher, \array_key_exists('event', $options) ? $options['event'] : null), $options); + $this->writeData($this->getEventDispatcherListenersData($eventDispatcher, $options), $options); } - /** - * {@inheritdoc} - */ protected function describeCallable($callable, array $options = []) { $this->writeData($this->getCallableData($callable), $options); @@ -153,27 +146,58 @@ class JsonDescriptor extends Descriptor protected function describeContainerParameter($parameter, array $options = []) { - $key = isset($options['parameter']) ? $options['parameter'] : ''; + $key = $options['parameter'] ?? ''; $this->writeData([$key => $parameter], $options); } - /** - * Writes data as json. - */ + protected function describeContainerEnvVars(array $envs, array $options = []) + { + throw new LogicException('Using the JSON format to debug environment variables is not supported.'); + } + + protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []): void + { + $containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $builder->getParameter('kernel.build_dir'), $builder->getParameter('kernel.container_class')); + if (!file_exists($containerDeprecationFilePath)) { + throw new RuntimeException('The deprecation file does not exist, please try warming the cache first.'); + } + + $logs = unserialize(file_get_contents($containerDeprecationFilePath)); + + $formattedLogs = []; + $remainingCount = 0; + foreach ($logs as $log) { + $formattedLogs[] = [ + 'message' => $log['message'], + 'file' => $log['file'], + 'line' => $log['line'], + 'count' => $log['count'], + ]; + $remainingCount += $log['count']; + } + + $this->writeData(['remainingCount' => $remainingCount, 'deprecations' => $formattedLogs], $options); + } + private function writeData(array $data, array $options) { - $flags = isset($options['json_encoding']) ? $options['json_encoding'] : 0; + $flags = $options['json_encoding'] ?? 0; + + // Recursively search for enum values, so we can replace it + // before json_encode (which will not display anything for \UnitEnum otherwise) + array_walk_recursive($data, static function (&$value) { + if ($value instanceof \UnitEnum) { + $value = ltrim(var_export($value, true), '\\'); + } + }); $this->write(json_encode($data, $flags | \JSON_PRETTY_PRINT)."\n"); } - /** - * @return array - */ - protected function getRouteData(Route $route) + protected function getRouteData(Route $route): array { - return [ + $data = [ 'path' => $route->getPath(), 'pathRegex' => $route->compile()->getRegex(), 'host' => '' !== $route->getHost() ? $route->getHost() : 'ANY', @@ -185,14 +209,15 @@ class JsonDescriptor extends Descriptor 'requirements' => $route->getRequirements() ?: 'NO CUSTOM', 'options' => $route->getOptions(), ]; + + if ('' !== $route->getCondition()) { + $data['condition'] = $route->getCondition(); + } + + return $data; } - /** - * @param bool $omitTags - * - * @return array - */ - private function getContainerDefinitionData(Definition $definition, $omitTags = false, $showArguments = false) + private function getContainerDefinitionData(Definition $definition, bool $omitTags = false, bool $showArguments = false): array { $data = [ 'class' => (string) $definition->getClass(), @@ -205,11 +230,8 @@ class JsonDescriptor extends Descriptor 'autoconfigure' => $definition->isAutoconfigured(), ]; - // forward compatibility with DependencyInjection component in version 4.0 - if (method_exists($definition, 'getAutowiringTypes')) { - foreach ($definition->getAutowiringTypes(false) as $autowiringType) { - $data['autowiring_types'][] = $autowiringType; - } + if ('' !== $classDescription = $this->getClassDescription((string) $definition->getClass())) { + $data['description'] = $classDescription; } if ($showArguments) { @@ -243,7 +265,7 @@ class JsonDescriptor extends Descriptor if (!$omitTags) { $data['tags'] = []; - foreach ($definition->getTags() as $tagName => $tagData) { + foreach ($this->sortTagsByPriority($definition->getTags()) as $tagName => $tagData) { foreach ($tagData as $parameters) { $data['tags'][] = ['name' => $tagName, 'parameters' => $parameters]; } @@ -253,10 +275,7 @@ class JsonDescriptor extends Descriptor return $data; } - /** - * @return array - */ - private function getContainerAliasData(Alias $alias) + private function getContainerAliasData(Alias $alias): array { return [ 'service' => (string) $alias, @@ -264,23 +283,19 @@ class JsonDescriptor extends Descriptor ]; } - /** - * @param string|null $event - * - * @return array - */ - private function getEventDispatcherListenersData(EventDispatcherInterface $eventDispatcher, $event = null) + private function getEventDispatcherListenersData(EventDispatcherInterface $eventDispatcher, array $options): array { $data = []; + $event = \array_key_exists('event', $options) ? $options['event'] : null; - $registeredListeners = $eventDispatcher->getListeners($event); if (null !== $event) { - foreach ($registeredListeners as $listener) { + foreach ($eventDispatcher->getListeners($event) as $listener) { $l = $this->getCallableData($listener); $l['priority'] = $eventDispatcher->getListenerPriority($event, $listener); $data[] = $l; } } else { + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { @@ -295,12 +310,7 @@ class JsonDescriptor extends Descriptor return $data; } - /** - * @param callable $callable - * - * @return array - */ - private function getCallableData($callable) + private function getCallableData($callable): array { $data = []; @@ -311,7 +321,7 @@ class JsonDescriptor extends Descriptor $data['name'] = $callable[1]; $data['class'] = \get_class($callable[0]); } else { - if (0 !== strpos($callable[1], 'parent::')) { + if (!str_starts_with($callable[1], 'parent::')) { $data['name'] = $callable[1]; $data['class'] = $callable[0]; $data['static'] = true; @@ -329,7 +339,7 @@ class JsonDescriptor extends Descriptor if (\is_string($callable)) { $data['type'] = 'function'; - if (false === strpos($callable, '::')) { + if (!str_contains($callable, '::')) { $data['name'] = $callable; } else { $callableParts = explode('::', $callable); @@ -346,7 +356,7 @@ class JsonDescriptor extends Descriptor $data['type'] = 'closure'; $r = new \ReflectionFunction($callable); - if (false !== strpos($r->name, '{closure}')) { + if (str_contains($r->name, '{closure}')) { return $data; } $data['name'] = $r->name; @@ -371,7 +381,7 @@ class JsonDescriptor extends Descriptor throw new \InvalidArgumentException('Callable is not describable.'); } - private function describeValue($value, $omitTags, $showArguments) + private function describeValue($value, bool $omitTags, bool $showArguments) { if (\is_array($value)) { $data = []; @@ -393,6 +403,10 @@ class JsonDescriptor extends Descriptor ]; } + if ($value instanceof AbstractArgument) { + return ['type' => 'abstract', 'text' => $value->getText()]; + } + if ($value instanceof ArgumentInterface) { return $this->describeValue($value->getValues(), $omitTags, $showArguments); } diff --git a/lib/symfony/framework-bundle/Console/Descriptor/MarkdownDescriptor.php b/lib/symfony/framework-bundle/Console/Descriptor/MarkdownDescriptor.php index 5ebaed0248..ad1a804ce7 100644 --- a/lib/symfony/framework-bundle/Console/Descriptor/MarkdownDescriptor.php +++ b/lib/symfony/framework-bundle/Console/Descriptor/MarkdownDescriptor.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; @@ -54,6 +56,10 @@ class MarkdownDescriptor extends Descriptor ."\n".'- Requirements: '.($route->getRequirements() ? $this->formatRouterConfig($route->getRequirements()) : 'NO CUSTOM') ."\n".'- Options: '.$this->formatRouterConfig($route->getOptions()); + if ('' !== $route->getCondition()) { + $output .= "\n".'- Condition: '.$route->getCondition(); + } + $this->write(isset($options['name']) ? $options['name']."\n".str_repeat('-', \strlen($options['name']))."\n\n".$output : $output); @@ -70,10 +76,10 @@ class MarkdownDescriptor extends Descriptor protected function describeContainerTags(ContainerBuilder $builder, array $options = []) { - $showPrivate = isset($options['show_private']) && $options['show_private']; + $showHidden = isset($options['show_hidden']) && $options['show_hidden']; $this->write("Container tags\n=============="); - foreach ($this->findDefinitionsByTag($builder, $showPrivate) as $tag => $definitions) { + foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) { $this->write("\n\n".$tag."\n".str_repeat('-', \strlen($tag))); foreach ($definitions as $serviceId => $definition) { $this->write("\n\n"); @@ -82,10 +88,7 @@ class MarkdownDescriptor extends Descriptor } } - /** - * {@inheritdoc} - */ - protected function describeContainerService($service, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null) { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); @@ -102,20 +105,46 @@ class MarkdownDescriptor extends Descriptor } } - /** - * {@inheritdoc} - */ + protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []): void + { + $containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $builder->getParameter('kernel.build_dir'), $builder->getParameter('kernel.container_class')); + if (!file_exists($containerDeprecationFilePath)) { + throw new RuntimeException('The deprecation file does not exist, please try warming the cache first.'); + } + + $logs = unserialize(file_get_contents($containerDeprecationFilePath)); + if (0 === \count($logs)) { + $this->write("## There are no deprecations in the logs!\n"); + + return; + } + + $formattedLogs = []; + $remainingCount = 0; + foreach ($logs as $log) { + $formattedLogs[] = sprintf("- %sx: \"%s\" in %s:%s\n", $log['count'], $log['message'], $log['file'], $log['line']); + $remainingCount += $log['count']; + } + + $this->write(sprintf("## Remaining deprecations (%s)\n\n", $remainingCount)); + foreach ($formattedLogs as $formattedLog) { + $this->write($formattedLog); + } + } + protected function describeContainerServices(ContainerBuilder $builder, array $options = []) { - $showPrivate = isset($options['show_private']) && $options['show_private']; + $showHidden = isset($options['show_hidden']) && $options['show_hidden']; - $title = $showPrivate ? 'Public and private services' : 'Public services'; + $title = $showHidden ? 'Hidden services' : 'Services'; if (isset($options['tag'])) { $title .= ' with tag `'.$options['tag'].'`'; } $this->write($title."\n".str_repeat('=', \strlen($title))); - $serviceIds = isset($options['tag']) && $options['tag'] ? array_keys($builder->findTaggedServiceIds($options['tag'])) : $builder->getServiceIds(); + $serviceIds = isset($options['tag']) && $options['tag'] + ? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($options['tag'])) + : $this->sortServiceIds($builder->getServiceIds()); $showArguments = isset($options['show_arguments']) && $options['show_arguments']; $services = ['definitions' => [], 'aliases' => [], 'services' => []]; @@ -123,17 +152,17 @@ class MarkdownDescriptor extends Descriptor $serviceIds = array_filter($serviceIds, $options['filter']); } - foreach ($this->sortServiceIds($serviceIds) as $serviceId) { + foreach ($serviceIds as $serviceId) { $service = $this->resolveServiceDefinition($builder, $serviceId); + if ($showHidden xor '.' === ($serviceId[0] ?? null)) { + continue; + } + if ($service instanceof Alias) { - if ($showPrivate || ($service->isPublic() && !$service->isPrivate())) { - $services['aliases'][$serviceId] = $service; - } + $services['aliases'][$serviceId] = $service; } elseif ($service instanceof Definition) { - if (($showPrivate || ($service->isPublic() && !$service->isPrivate()))) { - $services['definitions'][$serviceId] = $service; - } + $services['definitions'][$serviceId] = $service; } else { $services['services'][$serviceId] = $service; } @@ -166,7 +195,13 @@ class MarkdownDescriptor extends Descriptor protected function describeContainerDefinition(Definition $definition, array $options = []) { - $output = '- Class: `'.$definition->getClass().'`' + $output = ''; + + if ('' !== $classDescription = $this->getClassDescription((string) $definition->getClass())) { + $output .= '- Description: `'.$classDescription.'`'."\n"; + } + + $output .= '- Class: `'.$definition->getClass().'`' ."\n".'- Public: '.($definition->isPublic() && !$definition->isPrivate() ? 'yes' : 'no') ."\n".'- Synthetic: '.($definition->isSynthetic() ? 'yes' : 'no') ."\n".'- Lazy: '.($definition->isLazy() ? 'yes' : 'no') @@ -176,13 +211,6 @@ class MarkdownDescriptor extends Descriptor ."\n".'- Autoconfigured: '.($definition->isAutoconfigured() ? 'yes' : 'no') ; - // forward compatibility with DependencyInjection component in version 4.0 - if (method_exists($definition, 'getAutowiringTypes')) { - foreach ($definition->getAutowiringTypes(false) as $autowiringType) { - $output .= "\n".'- Autowiring Type: `'.$autowiringType.'`'; - } - } - if (isset($options['show_arguments']) && $options['show_arguments']) { $output .= "\n".'- Arguments: '.($definition->getArguments() ? 'yes' : 'no'); } @@ -212,7 +240,7 @@ class MarkdownDescriptor extends Descriptor } if (!(isset($options['omit_tags']) && $options['omit_tags'])) { - foreach ($definition->getTags() as $tagName => $tagData) { + foreach ($this->sortTagsByPriority($definition->getTags()) as $tagName => $tagData) { foreach ($tagData as $parameters) { $output .= "\n".'- Tag: `'.$tagName.'`'; foreach ($parameters as $name => $value) { @@ -251,21 +279,32 @@ class MarkdownDescriptor extends Descriptor $this->write(isset($options['parameter']) ? sprintf("%s\n%s\n\n%s", $options['parameter'], str_repeat('=', \strlen($options['parameter'])), $this->formatParameter($parameter)) : $parameter); } - /** - * {@inheritdoc} - */ + protected function describeContainerEnvVars(array $envs, array $options = []) + { + throw new LogicException('Using the markdown format to debug environment variables is not supported.'); + } + protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) { - $event = \array_key_exists('event', $options) ? $options['event'] : null; + $event = $options['event'] ?? null; + $dispatcherServiceName = $options['dispatcher_service_name'] ?? null; $title = 'Registered listeners'; + + if (null !== $dispatcherServiceName) { + $title .= sprintf(' of event dispatcher "%s"', $dispatcherServiceName); + } + if (null !== $event) { $title .= sprintf(' for event `%s` ordered by descending priority', $event); + $registeredListeners = $eventDispatcher->getListeners($event); + } else { + // Try to see if "events" exists + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); } $this->write(sprintf('# %s', $title)."\n"); - $registeredListeners = $eventDispatcher->getListeners($event); if (null !== $event) { foreach ($registeredListeners as $order => $listener) { $this->write("\n".sprintf('## Listener %d', $order + 1)."\n"); @@ -287,9 +326,6 @@ class MarkdownDescriptor extends Descriptor } } - /** - * {@inheritdoc} - */ protected function describeCallable($callable, array $options = []) { $string = ''; @@ -301,7 +337,7 @@ class MarkdownDescriptor extends Descriptor $string .= "\n".sprintf('- Name: `%s`', $callable[1]); $string .= "\n".sprintf('- Class: `%s`', \get_class($callable[0])); } else { - if (0 !== strpos($callable[1], 'parent::')) { + if (!str_starts_with($callable[1], 'parent::')) { $string .= "\n".sprintf('- Name: `%s`', $callable[1]); $string .= "\n".sprintf('- Class: `%s`', $callable[0]); $string .= "\n- Static: yes"; @@ -319,7 +355,7 @@ class MarkdownDescriptor extends Descriptor if (\is_string($callable)) { $string .= "\n- Type: `function`"; - if (false === strpos($callable, '::')) { + if (!str_contains($callable, '::')) { $string .= "\n".sprintf('- Name: `%s`', $callable); } else { $callableParts = explode('::', $callable); @@ -336,7 +372,7 @@ class MarkdownDescriptor extends Descriptor $string .= "\n- Type: `closure`"; $r = new \ReflectionFunction($callable); - if (false !== strpos($r->name, '{closure}')) { + if (str_contains($r->name, '{closure}')) { return $this->write($string."\n"); } $string .= "\n".sprintf('- Name: `%s`', $r->name); @@ -361,10 +397,7 @@ class MarkdownDescriptor extends Descriptor throw new \InvalidArgumentException('Callable is not describable.'); } - /** - * @return string - */ - private function formatRouterConfig(array $array) + private function formatRouterConfig(array $array): string { if (!$array) { return 'NONE'; diff --git a/lib/symfony/framework-bundle/Console/Descriptor/TextDescriptor.php b/lib/symfony/framework-bundle/Console/Descriptor/TextDescriptor.php index a32a2128e2..ac22fa731d 100644 --- a/lib/symfony/framework-bundle/Console/Descriptor/TextDescriptor.php +++ b/lib/symfony/framework-bundle/Console/Descriptor/TextDescriptor.php @@ -12,17 +12,21 @@ namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Dumper; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; @@ -33,6 +37,13 @@ use Symfony\Component\Routing\RouteCollection; */ class TextDescriptor extends Descriptor { + private $fileLinkFormatter; + + public function __construct(FileLinkFormatter $fileLinkFormatter = null) + { + $this->fileLinkFormatter = $fileLinkFormatter; + } + protected function describeRouteCollection(RouteCollection $routes, array $options = []) { $showControllers = isset($options['show_controllers']) && $options['show_controllers']; @@ -44,17 +55,18 @@ class TextDescriptor extends Descriptor $tableRows = []; foreach ($routes->all() as $name => $route) { + $controller = $route->getDefault('_controller'); + $row = [ $name, $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY', $route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY', '' !== $route->getHost() ? $route->getHost() : 'ANY', - $route->getPath(), + $this->formatControllerLink($controller, $route->getPath(), $options['container'] ?? null), ]; if ($showControllers) { - $controller = $route->getDefault('_controller'); - $row[] = $controller ? $this->formatCallable($controller) : ''; + $row[] = $controller ? $this->formatControllerLink($controller, $this->formatCallable($controller), $options['container'] ?? null) : ''; } $tableRows[] = $row; @@ -71,22 +83,28 @@ class TextDescriptor extends Descriptor protected function describeRoute(Route $route, array $options = []) { + $defaults = $route->getDefaults(); + if (isset($defaults['_controller'])) { + $defaults['_controller'] = $this->formatControllerLink($defaults['_controller'], $this->formatCallable($defaults['_controller']), $options['container'] ?? null); + } + $tableHeaders = ['Property', 'Value']; $tableRows = [ - ['Route Name', isset($options['name']) ? $options['name'] : ''], + ['Route Name', $options['name'] ?? ''], ['Path', $route->getPath()], ['Path Regex', $route->compile()->getRegex()], - ['Host', ('' !== $route->getHost() ? $route->getHost() : 'ANY')], - ['Host Regex', ('' !== $route->getHost() ? $route->compile()->getHostRegex() : '')], - ['Scheme', ($route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY')], - ['Method', ($route->getMethods() ? implode('|', $route->getMethods()) : 'ANY')], - ['Requirements', ($route->getRequirements() ? $this->formatRouterConfig($route->getRequirements()) : 'NO CUSTOM')], + ['Host', '' !== $route->getHost() ? $route->getHost() : 'ANY'], + ['Host Regex', '' !== $route->getHost() ? $route->compile()->getHostRegex() : ''], + ['Scheme', $route->getSchemes() ? implode('|', $route->getSchemes()) : 'ANY'], + ['Method', $route->getMethods() ? implode('|', $route->getMethods()) : 'ANY'], + ['Requirements', $route->getRequirements() ? $this->formatRouterConfig($route->getRequirements()) : 'NO CUSTOM'], ['Class', \get_class($route)], - ['Defaults', $this->formatRouterConfig($route->getDefaults())], + ['Defaults', $this->formatRouterConfig($defaults)], ['Options', $this->formatRouterConfig($route->getOptions())], ]; - if (isset($options['callable'])) { - $tableRows[] = ['Callable', $options['callable']]; + + if ('' !== $route->getCondition()) { + $tableRows[] = ['Condition', $route->getCondition()]; } $table = new Table($this->getOutput()); @@ -109,24 +127,21 @@ class TextDescriptor extends Descriptor protected function describeContainerTags(ContainerBuilder $builder, array $options = []) { - $showPrivate = isset($options['show_private']) && $options['show_private']; + $showHidden = isset($options['show_hidden']) && $options['show_hidden']; - if ($showPrivate) { - $options['output']->title('Symfony Container Public and Private Tags'); + if ($showHidden) { + $options['output']->title('Symfony Container Hidden Tags'); } else { - $options['output']->title('Symfony Container Public Tags'); + $options['output']->title('Symfony Container Tags'); } - foreach ($this->findDefinitionsByTag($builder, $showPrivate) as $tag => $definitions) { + foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) { $options['output']->section(sprintf('"%s" tag', $tag)); $options['output']->listing(array_keys($definitions)); } } - /** - * {@inheritdoc} - */ - protected function describeContainerService($service, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null) { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); @@ -141,24 +156,21 @@ class TextDescriptor extends Descriptor $options['output']->table( ['Service ID', 'Class'], [ - [isset($options['id']) ? $options['id'] : '-', \get_class($service)], + [$options['id'] ?? '-', \get_class($service)], ] ); } } - /** - * {@inheritdoc} - */ protected function describeContainerServices(ContainerBuilder $builder, array $options = []) { - $showPrivate = isset($options['show_private']) && $options['show_private']; - $showTag = isset($options['tag']) ? $options['tag'] : null; + $showHidden = isset($options['show_hidden']) && $options['show_hidden']; + $showTag = $options['tag'] ?? null; - if ($showPrivate) { - $title = 'Symfony Container Public and Private Services'; + if ($showHidden) { + $title = 'Symfony Container Hidden Services'; } else { - $title = 'Symfony Container Public Services'; + $title = 'Symfony Container Services'; } if ($showTag) { @@ -167,7 +179,9 @@ class TextDescriptor extends Descriptor $options['output']->title($title); - $serviceIds = isset($options['tag']) && $options['tag'] ? array_keys($builder->findTaggedServiceIds($options['tag'])) : $builder->getServiceIds(); + $serviceIds = isset($options['tag']) && $options['tag'] + ? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($options['tag'])) + : $this->sortServiceIds($builder->getServiceIds()); $maxTags = []; if (isset($options['filter'])) { @@ -176,12 +190,14 @@ class TextDescriptor extends Descriptor foreach ($serviceIds as $key => $serviceId) { $definition = $this->resolveServiceDefinition($builder, $serviceId); + + // filter out hidden services unless shown explicitly + if ($showHidden xor '.' === ($serviceId[0] ?? null)) { + unset($serviceIds[$key]); + continue; + } + if ($definition instanceof Definition) { - // filter out private services unless shown explicitly - if (!$showPrivate && (!$definition->isPublic() || $definition->isPrivate())) { - unset($serviceIds[$key]); - continue; - } if ($showTag) { $tags = $definition->getTag($showTag); foreach ($tags as $tag) { @@ -195,11 +211,6 @@ class TextDescriptor extends Descriptor } } } - } elseif ($definition instanceof Alias) { - if (!$showPrivate && (!$definition->isPublic() || $definition->isPrivate())) { - unset($serviceIds[$key]); - continue; - } } } @@ -209,21 +220,21 @@ class TextDescriptor extends Descriptor $tableHeaders = array_merge(['Service ID'], $tagsNames, ['Class name']); $tableRows = []; $rawOutput = isset($options['raw_text']) && $options['raw_text']; - foreach ($this->sortServiceIds($serviceIds) as $serviceId) { + foreach ($serviceIds as $serviceId) { $definition = $this->resolveServiceDefinition($builder, $serviceId); $styledServiceId = $rawOutput ? $serviceId : sprintf('%s', OutputFormatter::escape($serviceId)); if ($definition instanceof Definition) { if ($showTag) { - foreach ($definition->getTag($showTag) as $key => $tag) { + foreach ($this->sortByPriority($definition->getTag($showTag)) as $key => $tag) { $tagValues = []; foreach ($tagsNames as $tagName) { - $tagValues[] = isset($tag[$tagName]) ? $tag[$tagName] : ''; + $tagValues[] = $tag[$tagName] ?? ''; } if (0 === $key) { $tableRows[] = array_merge([$serviceId], $tagValues, [$definition->getClass()]); } else { - $tableRows[] = array_merge([' "'], $tagValues, ['']); + $tableRows[] = array_merge([' (same service as previous, another tag)'], $tagValues, ['']); } } } else { @@ -246,9 +257,13 @@ class TextDescriptor extends Descriptor $options['output']->title(sprintf('Information for Service "%s"', $options['id'])); } + if ('' !== $classDescription = $this->getClassDescription((string) $definition->getClass())) { + $options['output']->text($classDescription."\n"); + } + $tableHeaders = ['Option', 'Value']; - $tableRows[] = ['Service ID', isset($options['id']) ? $options['id'] : '-']; + $tableRows[] = ['Service ID', $options['id'] ?? '-']; $tableRows[] = ['Class', $definition->getClass() ?: '-']; $omitTags = isset($options['omit_tags']) && $options['omit_tags']; @@ -291,11 +306,6 @@ class TextDescriptor extends Descriptor $tableRows[] = ['Autowired', $definition->isAutowired() ? 'yes' : 'no']; $tableRows[] = ['Autoconfigured', $definition->isAutoconfigured() ? 'yes' : 'no']; - // forward compatibility with DependencyInjection component in version 4.0 - if (method_exists($definition, 'getAutowiringTypes') && $autowiringTypes = $definition->getAutowiringTypes(false)) { - $tableRows[] = ['Autowiring Types', implode(', ', $autowiringTypes)]; - } - if ($definition->getFile()) { $tableRows[] = ['Required File', $definition->getFile() ?: '-']; } @@ -330,8 +340,18 @@ class TextDescriptor extends Descriptor } else { $argumentsInformation[] = sprintf('Iterator (%d element(s))', \count($argument->getValues())); } + + foreach ($argument->getValues() as $ref) { + $argumentsInformation[] = sprintf('- Service(%s)', $ref); + } + } elseif ($argument instanceof ServiceLocatorArgument) { + $argumentsInformation[] = sprintf('Service locator (%d element(s))', \count($argument->getValues())); } elseif ($argument instanceof Definition) { $argumentsInformation[] = 'Inlined Service'; + } elseif ($argument instanceof \UnitEnum) { + $argumentsInformation[] = ltrim(var_export($argument, true), '\\'); + } elseif ($argument instanceof AbstractArgument) { + $argumentsInformation[] = sprintf('Abstract argument (%s)', $argument->getText()); } else { $argumentsInformation[] = \is_array($argument) ? sprintf('Array (%d element(s))', \count($argument)) : $argument; } @@ -343,9 +363,39 @@ class TextDescriptor extends Descriptor $options['output']->table($tableHeaders, $tableRows); } + protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []): void + { + $containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $builder->getParameter('kernel.build_dir'), $builder->getParameter('kernel.container_class')); + if (!file_exists($containerDeprecationFilePath)) { + $options['output']->warning('The deprecation file does not exist, please try warming the cache first.'); + + return; + } + + $logs = unserialize(file_get_contents($containerDeprecationFilePath)); + if (0 === \count($logs)) { + $options['output']->success('There are no deprecations in the logs!'); + + return; + } + + $formattedLogs = []; + $remainingCount = 0; + foreach ($logs as $log) { + $formattedLogs[] = sprintf("%sx: %s\n in %s:%s", $log['count'], $log['message'], $log['file'], $log['line']); + $remainingCount += $log['count']; + } + $options['output']->title(sprintf('Remaining deprecations (%s)', $remainingCount)); + $options['output']->listing($formattedLogs); + } + protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null) { - $options['output']->comment(sprintf('This service is an alias for the service %s', (string) $alias)); + if ($alias->isPublic() && !$alias->isPrivate()) { + $options['output']->comment(sprintf('This service is a public alias for the service %s', (string) $alias)); + } else { + $options['output']->comment(sprintf('This service is a private alias for the service %s', (string) $alias)); + } if (!$builder) { return; @@ -364,22 +414,89 @@ class TextDescriptor extends Descriptor ]); } - /** - * {@inheritdoc} - */ + protected function describeContainerEnvVars(array $envs, array $options = []) + { + $dump = new Dumper($this->output); + $options['output']->title('Symfony Container Environment Variables'); + + if (null !== $name = $options['name'] ?? null) { + $options['output']->comment('Displaying detailed environment variable usage matching '.$name); + + $matches = false; + foreach ($envs as $env) { + if ($name === $env['name'] || false !== stripos($env['name'], $name)) { + $matches = true; + $options['output']->section('%env('.$env['processor'].':'.$env['name'].')%'); + $options['output']->table([], [ + ['Default value', $env['default_available'] ? $dump($env['default_value']) : 'n/a'], + ['Real value', $env['runtime_available'] ? $dump($env['runtime_value']) : 'n/a'], + ['Processed value', $env['default_available'] || $env['runtime_available'] ? $dump($env['processed_value']) : 'n/a'], + ]); + } + } + + if (!$matches) { + $options['output']->block('None of the environment variables match this name.'); + } else { + $options['output']->comment('Note real values might be different between web and CLI.'); + } + + return; + } + + if (!$envs) { + $options['output']->block('No environment variables are being used.'); + + return; + } + + $rows = []; + $missing = []; + foreach ($envs as $env) { + if (isset($rows[$env['name']])) { + continue; + } + + $rows[$env['name']] = [ + $env['name'], + $env['default_available'] ? $dump($env['default_value']) : 'n/a', + $env['runtime_available'] ? $dump($env['runtime_value']) : 'n/a', + ]; + if (!$env['default_available'] && !$env['runtime_available']) { + $missing[$env['name']] = true; + } + } + + $options['output']->table(['Name', 'Default value', 'Real value'], $rows); + $options['output']->comment('Note real values might be different between web and CLI.'); + + if ($missing) { + $options['output']->warning('The following variables are missing:'); + $options['output']->listing(array_keys($missing)); + } + } + protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) { - $event = \array_key_exists('event', $options) ? $options['event'] : null; + $event = $options['event'] ?? null; + $dispatcherServiceName = $options['dispatcher_service_name'] ?? null; + + $title = 'Registered Listeners'; + + if (null !== $dispatcherServiceName) { + $title .= sprintf(' of Event Dispatcher "%s"', $dispatcherServiceName); + } if (null !== $event) { - $title = sprintf('Registered Listeners for "%s" Event', $event); + $title .= sprintf(' for "%s" Event', $event); + $registeredListeners = $eventDispatcher->getListeners($event); } else { - $title = 'Registered Listeners Grouped by Event'; + $title .= ' Grouped by Event'; + // Try to see if "events" exists + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); } $options['output']->title($title); - - $registeredListeners = $eventDispatcher->getListeners($event); if (null !== $event) { $this->renderEventListenerTable($eventDispatcher, $event, $registeredListeners, $options['output']); } else { @@ -391,15 +508,12 @@ class TextDescriptor extends Descriptor } } - /** - * {@inheritdoc} - */ protected function describeCallable($callable, array $options = []) { $this->writeText($this->formatCallable($callable), $options); } - private function renderEventListenerTable(EventDispatcherInterface $eventDispatcher, $event, array $eventListeners, SymfonyStyle $io) + private function renderEventListenerTable(EventDispatcherInterface $eventDispatcher, string $event, array $eventListeners, SymfonyStyle $io) { $tableHeaders = ['Order', 'Callable', 'Priority']; $tableRows = []; @@ -411,10 +525,7 @@ class TextDescriptor extends Descriptor $io->table($tableHeaders, $tableRows); } - /** - * @return string - */ - private function formatRouterConfig(array $config) + private function formatRouterConfig(array $config): string { if (empty($config)) { return 'NONE'; @@ -430,12 +541,61 @@ class TextDescriptor extends Descriptor return trim($configAsString); } - /** - * @param callable $callable - * - * @return string - */ - private function formatCallable($callable) + private function formatControllerLink($controller, string $anchorText, callable $getContainer = null): string + { + if (null === $this->fileLinkFormatter) { + return $anchorText; + } + + try { + if (null === $controller) { + return $anchorText; + } elseif (\is_array($controller)) { + $r = new \ReflectionMethod($controller[0], $controller[1]); + } elseif ($controller instanceof \Closure) { + $r = new \ReflectionFunction($controller); + } elseif (method_exists($controller, '__invoke')) { + $r = new \ReflectionMethod($controller, '__invoke'); + } elseif (!\is_string($controller)) { + return $anchorText; + } elseif (str_contains($controller, '::')) { + $r = new \ReflectionMethod($controller); + } else { + $r = new \ReflectionFunction($controller); + } + } catch (\ReflectionException $e) { + if (\is_array($controller)) { + $controller = implode('::', $controller); + } + + $id = $controller; + $method = '__invoke'; + + if ($pos = strpos($controller, '::')) { + $id = substr($controller, 0, $pos); + $method = substr($controller, $pos + 2); + } + + if (!$getContainer || !($container = $getContainer()) || !$container->has($id)) { + return $anchorText; + } + + try { + $r = new \ReflectionMethod($container->findDefinition($id)->getClass(), $method); + } catch (\ReflectionException $e) { + return $anchorText; + } + } + + $fileLink = $this->fileLinkFormatter->format($r->getFileName(), $r->getStartLine()); + if ($fileLink) { + return sprintf('%s', $fileLink, $anchorText); + } + + return $anchorText; + } + + private function formatCallable($callable): string { if (\is_array($callable)) { if (\is_object($callable[0])) { @@ -451,7 +611,7 @@ class TextDescriptor extends Descriptor if ($callable instanceof \Closure) { $r = new \ReflectionFunction($callable); - if (false !== strpos($r->name, '{closure}')) { + if (str_contains($r->name, '{closure}')) { return 'Closure()'; } if ($class = $r->getClosureScopeClass()) { @@ -468,10 +628,7 @@ class TextDescriptor extends Descriptor throw new \InvalidArgumentException('Callable is not describable.'); } - /** - * @param string $content - */ - private function writeText($content, array $options = []) + private function writeText(string $content, array $options = []) { $this->write( isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, diff --git a/lib/symfony/framework-bundle/Console/Descriptor/XmlDescriptor.php b/lib/symfony/framework-bundle/Console/Descriptor/XmlDescriptor.php index 02f71b98bd..62e8fa50cd 100644 --- a/lib/symfony/framework-bundle/Console/Descriptor/XmlDescriptor.php +++ b/lib/symfony/framework-bundle/Console/Descriptor/XmlDescriptor.php @@ -11,9 +11,13 @@ namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Argument\AbstractArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; @@ -36,7 +40,7 @@ class XmlDescriptor extends Descriptor protected function describeRoute(Route $route, array $options = []) { - $this->writeDocument($this->getRouteDocument($route, isset($options['name']) ? $options['name'] : null)); + $this->writeDocument($this->getRouteDocument($route, $options['name'] ?? null)); } protected function describeContainerParameters(ParameterBag $parameters, array $options = []) @@ -46,13 +50,10 @@ class XmlDescriptor extends Descriptor protected function describeContainerTags(ContainerBuilder $builder, array $options = []) { - $this->writeDocument($this->getContainerTagsDocument($builder, isset($options['show_private']) && $options['show_private'])); + $this->writeDocument($this->getContainerTagsDocument($builder, isset($options['show_hidden']) && $options['show_hidden'])); } - /** - * {@inheritdoc} - */ - protected function describeContainerService($service, array $options = [], ContainerBuilder $builder = null) + protected function describeContainerService(object $service, array $options = [], ContainerBuilder $builder = null) { if (!isset($options['id'])) { throw new \InvalidArgumentException('An "id" option must be provided.'); @@ -61,23 +62,20 @@ class XmlDescriptor extends Descriptor $this->writeDocument($this->getContainerServiceDocument($service, $options['id'], $builder, isset($options['show_arguments']) && $options['show_arguments'])); } - /** - * {@inheritdoc} - */ protected function describeContainerServices(ContainerBuilder $builder, array $options = []) { - $this->writeDocument($this->getContainerServicesDocument($builder, isset($options['tag']) ? $options['tag'] : null, isset($options['show_private']) && $options['show_private'], isset($options['show_arguments']) && $options['show_arguments'], isset($options['filter']) ? $options['filter'] : null)); + $this->writeDocument($this->getContainerServicesDocument($builder, $options['tag'] ?? null, isset($options['show_hidden']) && $options['show_hidden'], isset($options['show_arguments']) && $options['show_arguments'], $options['filter'] ?? null)); } protected function describeContainerDefinition(Definition $definition, array $options = []) { - $this->writeDocument($this->getContainerDefinitionDocument($definition, isset($options['id']) ? $options['id'] : null, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'])); + $this->writeDocument($this->getContainerDefinitionDocument($definition, $options['id'] ?? null, isset($options['omit_tags']) && $options['omit_tags'], isset($options['show_arguments']) && $options['show_arguments'])); } protected function describeContainerAlias(Alias $alias, array $options = [], ContainerBuilder $builder = null) { $dom = new \DOMDocument('1.0', 'UTF-8'); - $dom->appendChild($dom->importNode($this->getContainerAliasDocument($alias, isset($options['id']) ? $options['id'] : null)->childNodes->item(0), true)); + $dom->appendChild($dom->importNode($this->getContainerAliasDocument($alias, $options['id'] ?? null)->childNodes->item(0), true)); if (!$builder) { $this->writeDocument($dom); @@ -90,17 +88,11 @@ class XmlDescriptor extends Descriptor $this->writeDocument($dom); } - /** - * {@inheritdoc} - */ protected function describeEventDispatcherListeners(EventDispatcherInterface $eventDispatcher, array $options = []) { - $this->writeDocument($this->getEventDispatcherListenersDocument($eventDispatcher, \array_key_exists('event', $options) ? $options['event'] : null)); + $this->writeDocument($this->getEventDispatcherListenersDocument($eventDispatcher, $options)); } - /** - * {@inheritdoc} - */ protected function describeCallable($callable, array $options = []) { $this->writeDocument($this->getCallableDocument($callable)); @@ -111,19 +103,46 @@ class XmlDescriptor extends Descriptor $this->writeDocument($this->getContainerParameterDocument($parameter, $options)); } - /** - * Writes DOM document. - */ + protected function describeContainerEnvVars(array $envs, array $options = []) + { + throw new LogicException('Using the XML format to debug environment variables is not supported.'); + } + + protected function describeContainerDeprecations(ContainerBuilder $builder, array $options = []): void + { + $containerDeprecationFilePath = sprintf('%s/%sDeprecations.log', $builder->getParameter('kernel.build_dir'), $builder->getParameter('kernel.container_class')); + if (!file_exists($containerDeprecationFilePath)) { + throw new RuntimeException('The deprecation file does not exist, please try warming the cache first.'); + } + + $logs = unserialize(file_get_contents($containerDeprecationFilePath)); + + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($deprecationsXML = $dom->createElement('deprecations')); + + $formattedLogs = []; + $remainingCount = 0; + foreach ($logs as $log) { + $deprecationsXML->appendChild($deprecationXML = $dom->createElement('deprecation')); + $deprecationXML->setAttribute('count', $log['count']); + $deprecationXML->appendChild($dom->createElement('message', $log['message'])); + $deprecationXML->appendChild($dom->createElement('file', $log['file'])); + $deprecationXML->appendChild($dom->createElement('line', $log['line'])); + $remainingCount += $log['count']; + } + + $deprecationsXML->setAttribute('remainingCount', $remainingCount); + + $this->writeDocument($dom); + } + private function writeDocument(\DOMDocument $dom) { $dom->formatOutput = true; $this->write($dom->saveXML()); } - /** - * @return \DOMDocument - */ - private function getRouteCollectionDocument(RouteCollection $routes) + private function getRouteCollectionDocument(RouteCollection $routes): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($routesXML = $dom->createElement('routes')); @@ -136,12 +155,7 @@ class XmlDescriptor extends Descriptor return $dom; } - /** - * @param string|null $name - * - * @return \DOMDocument - */ - private function getRouteDocument(Route $route, $name = null) + private function getRouteDocument(Route $route, string $name = null): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($routeXML = $dom->createElement('route')); @@ -201,13 +215,15 @@ class XmlDescriptor extends Descriptor } } + if ('' !== $route->getCondition()) { + $routeXML->appendChild($conditionXML = $dom->createElement('condition')); + $conditionXML->appendChild(new \DOMText($route->getCondition())); + } + return $dom; } - /** - * @return \DOMDocument - */ - private function getContainerParametersDocument(ParameterBag $parameters) + private function getContainerParametersDocument(ParameterBag $parameters): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($parametersXML = $dom->createElement('parameters')); @@ -221,17 +237,12 @@ class XmlDescriptor extends Descriptor return $dom; } - /** - * @param bool $showPrivate - * - * @return \DOMDocument - */ - private function getContainerTagsDocument(ContainerBuilder $builder, $showPrivate = false) + private function getContainerTagsDocument(ContainerBuilder $builder, bool $showHidden = false): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($containerXML = $dom->createElement('container')); - foreach ($this->findDefinitionsByTag($builder, $showPrivate) as $tag => $definitions) { + foreach ($this->findDefinitionsByTag($builder, $showHidden) as $tag => $definitions) { $containerXML->appendChild($tagXML = $dom->createElement('tag')); $tagXML->setAttribute('name', $tag); @@ -244,14 +255,7 @@ class XmlDescriptor extends Descriptor return $dom; } - /** - * @param mixed $service - * @param string $id - * @param bool $showArguments - * - * @return \DOMDocument - */ - private function getContainerServiceDocument($service, $id, ContainerBuilder $builder = null, $showArguments = false) + private function getContainerServiceDocument(object $service, string $id, ContainerBuilder $builder = null, bool $showArguments = false): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); @@ -271,29 +275,22 @@ class XmlDescriptor extends Descriptor return $dom; } - /** - * @param string|null $tag - * @param bool $showPrivate - * @param bool $showArguments - * @param callable $filter - * - * @return \DOMDocument - */ - private function getContainerServicesDocument(ContainerBuilder $builder, $tag = null, $showPrivate = false, $showArguments = false, $filter = null) + private function getContainerServicesDocument(ContainerBuilder $builder, string $tag = null, bool $showHidden = false, bool $showArguments = false, callable $filter = null): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($containerXML = $dom->createElement('container')); - $serviceIds = $tag ? array_keys($builder->findTaggedServiceIds($tag)) : $builder->getServiceIds(); - + $serviceIds = $tag + ? $this->sortTaggedServicesByPriority($builder->findTaggedServiceIds($tag)) + : $this->sortServiceIds($builder->getServiceIds()); if ($filter) { $serviceIds = array_filter($serviceIds, $filter); } - foreach ($this->sortServiceIds($serviceIds) as $serviceId) { + foreach ($serviceIds as $serviceId) { $service = $this->resolveServiceDefinition($builder, $serviceId); - if (($service instanceof Definition || $service instanceof Alias) && !($showPrivate || ($service->isPublic() && !$service->isPrivate()))) { + if ($showHidden xor '.' === ($serviceId[0] ?? null)) { continue; } @@ -304,13 +301,7 @@ class XmlDescriptor extends Descriptor return $dom; } - /** - * @param string|null $id - * @param bool $omitTags - * - * @return \DOMDocument - */ - private function getContainerDefinitionDocument(Definition $definition, $id = null, $omitTags = false, $showArguments = false) + private function getContainerDefinitionDocument(Definition $definition, string $id = null, bool $omitTags = false, bool $showArguments = false): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($serviceXML = $dom->createElement('definition')); @@ -319,7 +310,12 @@ class XmlDescriptor extends Descriptor $serviceXML->setAttribute('id', $id); } - $serviceXML->setAttribute('class', $definition->getClass()); + if ('' !== $classDescription = $this->getClassDescription((string) $definition->getClass())) { + $serviceXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createCDATASection($classDescription)); + } + + $serviceXML->setAttribute('class', $definition->getClass() ?? ''); if ($factory = $definition->getFactory()) { $serviceXML->appendChild($factoryXML = $dom->createElement('factory')); @@ -345,7 +341,7 @@ class XmlDescriptor extends Descriptor $serviceXML->setAttribute('abstract', $definition->isAbstract() ? 'true' : 'false'); $serviceXML->setAttribute('autowired', $definition->isAutowired() ? 'true' : 'false'); $serviceXML->setAttribute('autoconfigured', $definition->isAutoconfigured() ? 'true' : 'false'); - $serviceXML->setAttribute('file', $definition->getFile()); + $serviceXML->setAttribute('file', $definition->getFile() ?? ''); $calls = $definition->getMethodCalls(); if (\count($calls) > 0) { @@ -353,6 +349,9 @@ class XmlDescriptor extends Descriptor foreach ($calls as $callData) { $callsXML->appendChild($callXML = $dom->createElement('call')); $callXML->setAttribute('method', $callData[0]); + if ($callData[2] ?? false) { + $callXML->setAttribute('returns-clone', 'true'); + } } } @@ -363,7 +362,7 @@ class XmlDescriptor extends Descriptor } if (!$omitTags) { - if ($tags = $definition->getTags()) { + if ($tags = $this->sortTagsByPriority($definition->getTags())) { $serviceXML->appendChild($tagsXML = $dom->createElement('tags')); foreach ($tags as $tagName => $tagData) { foreach ($tagData as $parameters) { @@ -385,7 +384,7 @@ class XmlDescriptor extends Descriptor /** * @return \DOMNode[] */ - private function getArgumentNodes(array $arguments, \DOMDocument $dom) + private function getArgumentNodes(array $arguments, \DOMDocument $dom): array { $nodes = []; @@ -403,20 +402,26 @@ class XmlDescriptor extends Descriptor if ($argument instanceof Reference) { $argumentXML->setAttribute('type', 'service'); $argumentXML->setAttribute('id', (string) $argument); - } elseif ($argument instanceof IteratorArgument) { - $argumentXML->setAttribute('type', 'iterator'); + } elseif ($argument instanceof IteratorArgument || $argument instanceof ServiceLocatorArgument) { + $argumentXML->setAttribute('type', $argument instanceof IteratorArgument ? 'iterator' : 'service_locator'); foreach ($this->getArgumentNodes($argument->getValues(), $dom) as $childArgumentXML) { $argumentXML->appendChild($childArgumentXML); } } elseif ($argument instanceof Definition) { $argumentXML->appendChild($dom->importNode($this->getContainerDefinitionDocument($argument, null, false, true)->childNodes->item(0), true)); + } elseif ($argument instanceof AbstractArgument) { + $argumentXML->setAttribute('type', 'abstract'); + $argumentXML->appendChild(new \DOMText($argument->getText())); } elseif (\is_array($argument)) { $argumentXML->setAttribute('type', 'collection'); foreach ($this->getArgumentNodes($argument, $dom) as $childArgumentXML) { $argumentXML->appendChild($childArgumentXML); } + } elseif ($argument instanceof \UnitEnum) { + $argumentXML->setAttribute('type', 'constant'); + $argumentXML->appendChild(new \DOMText(ltrim(var_export($argument, true), '\\'))); } else { $argumentXML->appendChild(new \DOMText($argument)); } @@ -427,12 +432,7 @@ class XmlDescriptor extends Descriptor return $nodes; } - /** - * @param string|null $id - * - * @return \DOMDocument - */ - private function getContainerAliasDocument(Alias $alias, $id = null) + private function getContainerAliasDocument(Alias $alias, string $id = null): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($aliasXML = $dom->createElement('alias')); @@ -447,10 +447,7 @@ class XmlDescriptor extends Descriptor return $dom; } - /** - * @return \DOMDocument - */ - private function getContainerParameterDocument($parameter, $options = []) + private function getContainerParameterDocument($parameter, array $options = []): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($parameterXML = $dom->createElement('parameter')); @@ -464,20 +461,18 @@ class XmlDescriptor extends Descriptor return $dom; } - /** - * @param string|null $event - * - * @return \DOMDocument - */ - private function getEventDispatcherListenersDocument(EventDispatcherInterface $eventDispatcher, $event = null) + private function getEventDispatcherListenersDocument(EventDispatcherInterface $eventDispatcher, array $options): \DOMDocument { + $event = \array_key_exists('event', $options) ? $options['event'] : null; $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($eventDispatcherXML = $dom->createElement('event-dispatcher')); - $registeredListeners = $eventDispatcher->getListeners($event); if (null !== $event) { + $registeredListeners = $eventDispatcher->getListeners($event); $this->appendEventListenerDocument($eventDispatcher, $event, $eventDispatcherXML, $registeredListeners); } else { + // Try to see if "events" exists + $registeredListeners = \array_key_exists('events', $options) ? array_combine($options['events'], array_map(function ($event) use ($eventDispatcher) { return $eventDispatcher->getListeners($event); }, $options['events'])) : $eventDispatcher->getListeners(); ksort($registeredListeners); foreach ($registeredListeners as $eventListened => $eventListeners) { @@ -491,7 +486,7 @@ class XmlDescriptor extends Descriptor return $dom; } - private function appendEventListenerDocument(EventDispatcherInterface $eventDispatcher, $event, \DOMElement $element, array $eventListeners) + private function appendEventListenerDocument(EventDispatcherInterface $eventDispatcher, string $event, \DOMElement $element, array $eventListeners) { foreach ($eventListeners as $listener) { $callableXML = $this->getCallableDocument($listener); @@ -501,12 +496,7 @@ class XmlDescriptor extends Descriptor } } - /** - * @param callable $callable - * - * @return \DOMDocument - */ - private function getCallableDocument($callable) + private function getCallableDocument($callable): \DOMDocument { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($callableXML = $dom->createElement('callable')); @@ -518,7 +508,7 @@ class XmlDescriptor extends Descriptor $callableXML->setAttribute('name', $callable[1]); $callableXML->setAttribute('class', \get_class($callable[0])); } else { - if (0 !== strpos($callable[1], 'parent::')) { + if (!str_starts_with($callable[1], 'parent::')) { $callableXML->setAttribute('name', $callable[1]); $callableXML->setAttribute('class', $callable[0]); $callableXML->setAttribute('static', 'true'); @@ -536,7 +526,7 @@ class XmlDescriptor extends Descriptor if (\is_string($callable)) { $callableXML->setAttribute('type', 'function'); - if (false === strpos($callable, '::')) { + if (!str_contains($callable, '::')) { $callableXML->setAttribute('name', $callable); } else { $callableParts = explode('::', $callable); @@ -553,7 +543,7 @@ class XmlDescriptor extends Descriptor $callableXML->setAttribute('type', 'closure'); $r = new \ReflectionFunction($callable); - if (false !== strpos($r->name, '{closure}')) { + if (str_contains($r->name, '{closure}')) { return $dom; } $callableXML->setAttribute('name', $r->name); diff --git a/lib/symfony/framework-bundle/Console/Helper/DescriptorHelper.php b/lib/symfony/framework-bundle/Console/Helper/DescriptorHelper.php index 475c22ca31..1f17c99942 100644 --- a/lib/symfony/framework-bundle/Console/Helper/DescriptorHelper.php +++ b/lib/symfony/framework-bundle/Console/Helper/DescriptorHelper.php @@ -16,6 +16,7 @@ use Symfony\Bundle\FrameworkBundle\Console\Descriptor\MarkdownDescriptor; use Symfony\Bundle\FrameworkBundle\Console\Descriptor\TextDescriptor; use Symfony\Bundle\FrameworkBundle\Console\Descriptor\XmlDescriptor; use Symfony\Component\Console\Helper\DescriptorHelper as BaseDescriptorHelper; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; /** * @author Jean-François Simon @@ -24,10 +25,10 @@ use Symfony\Component\Console\Helper\DescriptorHelper as BaseDescriptorHelper; */ class DescriptorHelper extends BaseDescriptorHelper { - public function __construct() + public function __construct(FileLinkFormatter $fileLinkFormatter = null) { $this - ->register('txt', new TextDescriptor()) + ->register('txt', new TextDescriptor($fileLinkFormatter)) ->register('xml', new XmlDescriptor()) ->register('json', new JsonDescriptor()) ->register('md', new MarkdownDescriptor()) diff --git a/lib/symfony/framework-bundle/Controller/AbstractController.php b/lib/symfony/framework-bundle/Controller/AbstractController.php index 106ecc87d7..f6eff3f61c 100644 --- a/lib/symfony/framework-bundle/Controller/AbstractController.php +++ b/lib/symfony/framework-bundle/Controller/AbstractController.php @@ -11,41 +11,60 @@ namespace Symfony\Bundle\FrameworkBundle\Controller; -use Doctrine\Common\Persistence\ManagerRegistry as LegacyManagerRegistry; use Doctrine\Persistence\ManagerRegistry; use Psr\Container\ContainerInterface; -use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; +use Psr\Link\LinkInterface; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormFactoryInterface; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Serializer\SerializerInterface; -use Symfony\Component\Templating\EngineInterface; +use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener; +use Symfony\Component\WebLink\GenericLinkProvider; +use Symfony\Contracts\Service\ServiceSubscriberInterface; use Twig\Environment; /** - * Provides common features needed in controllers. + * Provides shortcuts for HTTP-related features in controllers. * * @author Fabien Potencier */ abstract class AbstractController implements ServiceSubscriberInterface { - use ControllerTrait; - /** * @var ContainerInterface */ protected $container; /** - * @internal * @required */ - public function setContainer(ContainerInterface $container) + public function setContainer(ContainerInterface $container): ?ContainerInterface { $previous = $this->container; $this->container = $container; @@ -53,6 +72,20 @@ abstract class AbstractController implements ServiceSubscriberInterface return $previous; } + /** + * Gets a container parameter by its name. + * + * @return array|bool|float|int|string|\UnitEnum|null + */ + protected function getParameter(string $name) + { + if (!$this->container->has('parameter_bag')) { + throw new ServiceNotFoundException('parameter_bag.', null, null, [], sprintf('The "%s::getParameter()" method is missing a parameter bag to work properly. Did you forget to register your controller as a service subscriber? This can be fixed either by using autoconfiguration or by manually wiring a "parameter_bag" in the service locator passed to the controller.', static::class)); + } + + return $this->container->get('parameter_bag')->get($name); + } + public static function getSubscribedServices() { return [ @@ -62,12 +95,382 @@ abstract class AbstractController implements ServiceSubscriberInterface 'serializer' => '?'.SerializerInterface::class, 'session' => '?'.SessionInterface::class, 'security.authorization_checker' => '?'.AuthorizationCheckerInterface::class, - 'templating' => '?'.EngineInterface::class, 'twig' => '?'.Environment::class, - 'doctrine' => '?'.(interface_exists(ManagerRegistry::class) ? ManagerRegistry::class : LegacyManagerRegistry::class), + 'doctrine' => '?'.ManagerRegistry::class, // to be removed in 6.0 'form.factory' => '?'.FormFactoryInterface::class, 'security.token_storage' => '?'.TokenStorageInterface::class, 'security.csrf.token_manager' => '?'.CsrfTokenManagerInterface::class, + 'parameter_bag' => '?'.ContainerBagInterface::class, + 'message_bus' => '?'.MessageBusInterface::class, // to be removed in 6.0 + 'messenger.default_bus' => '?'.MessageBusInterface::class, // to be removed in 6.0 ]; } + + /** + * Returns true if the service id is defined. + * + * @deprecated since Symfony 5.4, use method or constructor injection in your controller instead + */ + protected function has(string $id): bool + { + trigger_deprecation('symfony/framework-bundle', '5.4', 'Method "%s()" is deprecated, use method or constructor injection in your controller instead.', __METHOD__); + + return $this->container->has($id); + } + + /** + * Gets a container service by its id. + * + * @return object The service + * + * @deprecated since Symfony 5.4, use method or constructor injection in your controller instead + */ + protected function get(string $id): object + { + trigger_deprecation('symfony/framework-bundle', '5.4', 'Method "%s()" is deprecated, use method or constructor injection in your controller instead.', __METHOD__); + + return $this->container->get($id); + } + + /** + * Generates a URL from the given parameters. + * + * @see UrlGeneratorInterface + */ + protected function generateUrl(string $route, array $parameters = [], int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string + { + return $this->container->get('router')->generate($route, $parameters, $referenceType); + } + + /** + * Forwards the request to another controller. + * + * @param string $controller The controller name (a string like Bundle\BlogBundle\Controller\PostController::indexAction) + */ + protected function forward(string $controller, array $path = [], array $query = []): Response + { + $request = $this->container->get('request_stack')->getCurrentRequest(); + $path['_controller'] = $controller; + $subRequest = $request->duplicate($query, null, $path); + + return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST); + } + + /** + * Returns a RedirectResponse to the given URL. + */ + protected function redirect(string $url, int $status = 302): RedirectResponse + { + return new RedirectResponse($url, $status); + } + + /** + * Returns a RedirectResponse to the given route with the given parameters. + */ + protected function redirectToRoute(string $route, array $parameters = [], int $status = 302): RedirectResponse + { + return $this->redirect($this->generateUrl($route, $parameters), $status); + } + + /** + * Returns a JsonResponse that uses the serializer component if enabled, or json_encode. + */ + protected function json($data, int $status = 200, array $headers = [], array $context = []): JsonResponse + { + if ($this->container->has('serializer')) { + $json = $this->container->get('serializer')->serialize($data, 'json', array_merge([ + 'json_encode_options' => JsonResponse::DEFAULT_ENCODING_OPTIONS, + ], $context)); + + return new JsonResponse($json, $status, $headers, true); + } + + return new JsonResponse($data, $status, $headers); + } + + /** + * Returns a BinaryFileResponse object with original or customized file name and disposition header. + * + * @param \SplFileInfo|string $file File object or path to file to be sent as response + */ + protected function file($file, string $fileName = null, string $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT): BinaryFileResponse + { + $response = new BinaryFileResponse($file); + $response->setContentDisposition($disposition, null === $fileName ? $response->getFile()->getFilename() : $fileName); + + return $response; + } + + /** + * Adds a flash message to the current session for type. + * + * @throws \LogicException + */ + protected function addFlash(string $type, $message): void + { + try { + $this->container->get('request_stack')->getSession()->getFlashBag()->add($type, $message); + } catch (SessionNotFoundException $e) { + throw new \LogicException('You cannot use the addFlash method if sessions are disabled. Enable them in "config/packages/framework.yaml".', 0, $e); + } + } + + /** + * Checks if the attribute is granted against the current authentication token and optionally supplied subject. + * + * @throws \LogicException + */ + protected function isGranted($attribute, $subject = null): bool + { + if (!$this->container->has('security.authorization_checker')) { + throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".'); + } + + return $this->container->get('security.authorization_checker')->isGranted($attribute, $subject); + } + + /** + * Throws an exception unless the attribute is granted against the current authentication token and optionally + * supplied subject. + * + * @throws AccessDeniedException + */ + protected function denyAccessUnlessGranted($attribute, $subject = null, string $message = 'Access Denied.'): void + { + if (!$this->isGranted($attribute, $subject)) { + $exception = $this->createAccessDeniedException($message); + $exception->setAttributes($attribute); + $exception->setSubject($subject); + + throw $exception; + } + } + + /** + * Returns a rendered view. + */ + protected function renderView(string $view, array $parameters = []): string + { + if (!$this->container->has('twig')) { + throw new \LogicException('You cannot use the "renderView" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".'); + } + + return $this->container->get('twig')->render($view, $parameters); + } + + /** + * Renders a view. + */ + protected function render(string $view, array $parameters = [], Response $response = null): Response + { + $content = $this->renderView($view, $parameters); + + if (null === $response) { + $response = new Response(); + } + + $response->setContent($content); + + return $response; + } + + /** + * Renders a view and sets the appropriate status code when a form is listed in parameters. + * + * If an invalid form is found in the list of parameters, a 422 status code is returned. + */ + protected function renderForm(string $view, array $parameters = [], Response $response = null): Response + { + if (null === $response) { + $response = new Response(); + } + + foreach ($parameters as $k => $v) { + if ($v instanceof FormView) { + throw new \LogicException(sprintf('Passing a FormView to "%s::renderForm()" is not supported, pass directly the form instead for parameter "%s".', get_debug_type($this), $k)); + } + + if (!$v instanceof FormInterface) { + continue; + } + + $parameters[$k] = $v->createView(); + + if (200 === $response->getStatusCode() && $v->isSubmitted() && !$v->isValid()) { + $response->setStatusCode(422); + } + } + + return $this->render($view, $parameters, $response); + } + + /** + * Streams a view. + */ + protected function stream(string $view, array $parameters = [], StreamedResponse $response = null): StreamedResponse + { + if (!$this->container->has('twig')) { + throw new \LogicException('You cannot use the "stream" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".'); + } + + $twig = $this->container->get('twig'); + + $callback = function () use ($twig, $view, $parameters) { + $twig->display($view, $parameters); + }; + + if (null === $response) { + return new StreamedResponse($callback); + } + + $response->setCallback($callback); + + return $response; + } + + /** + * Returns a NotFoundHttpException. + * + * This will result in a 404 response code. Usage example: + * + * throw $this->createNotFoundException('Page not found!'); + */ + protected function createNotFoundException(string $message = 'Not Found', \Throwable $previous = null): NotFoundHttpException + { + return new NotFoundHttpException($message, $previous); + } + + /** + * Returns an AccessDeniedException. + * + * This will result in a 403 response code. Usage example: + * + * throw $this->createAccessDeniedException('Unable to access this page!'); + * + * @throws \LogicException If the Security component is not available + */ + protected function createAccessDeniedException(string $message = 'Access Denied.', \Throwable $previous = null): AccessDeniedException + { + if (!class_exists(AccessDeniedException::class)) { + throw new \LogicException('You cannot use the "createAccessDeniedException" method if the Security component is not available. Try running "composer require symfony/security-bundle".'); + } + + return new AccessDeniedException($message, $previous); + } + + /** + * Creates and returns a Form instance from the type of the form. + */ + protected function createForm(string $type, $data = null, array $options = []): FormInterface + { + return $this->container->get('form.factory')->create($type, $data, $options); + } + + /** + * Creates and returns a form builder instance. + */ + protected function createFormBuilder($data = null, array $options = []): FormBuilderInterface + { + return $this->container->get('form.factory')->createBuilder(FormType::class, $data, $options); + } + + /** + * Shortcut to return the Doctrine Registry service. + * + * @throws \LogicException If DoctrineBundle is not available + * + * @deprecated since Symfony 5.4, inject an instance of ManagerRegistry in your controller instead + */ + protected function getDoctrine(): ManagerRegistry + { + trigger_deprecation('symfony/framework-bundle', '5.4', 'Method "%s()" is deprecated, inject an instance of ManagerRegistry in your controller instead.', __METHOD__); + + if (!$this->container->has('doctrine')) { + throw new \LogicException('The DoctrineBundle is not registered in your application. Try running "composer require symfony/orm-pack".'); + } + + return $this->container->get('doctrine'); + } + + /** + * Get a user from the Security Token Storage. + * + * @return UserInterface|null + * + * @throws \LogicException If SecurityBundle is not available + * + * @see TokenInterface::getUser() + */ + protected function getUser() + { + if (!$this->container->has('security.token_storage')) { + throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".'); + } + + if (null === $token = $this->container->get('security.token_storage')->getToken()) { + return null; + } + + // @deprecated since 5.4, $user will always be a UserInterface instance + if (!\is_object($user = $token->getUser())) { + // e.g. anonymous authentication + return null; + } + + return $user; + } + + /** + * Checks the validity of a CSRF token. + * + * @param string $id The id used when generating the token + * @param string|null $token The actual token sent with the request that should be validated + */ + protected function isCsrfTokenValid(string $id, ?string $token): bool + { + if (!$this->container->has('security.csrf.token_manager')) { + throw new \LogicException('CSRF protection is not enabled in your application. Enable it with the "csrf_protection" key in "config/packages/framework.yaml".'); + } + + return $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($id, $token)); + } + + /** + * Dispatches a message to the bus. + * + * @param object|Envelope $message The message or the message pre-wrapped in an envelope + * + * @deprecated since Symfony 5.4, inject an instance of MessageBusInterface in your controller instead + */ + protected function dispatchMessage(object $message, array $stamps = []): Envelope + { + trigger_deprecation('symfony/framework-bundle', '5.4', 'Method "%s()" is deprecated, inject an instance of MessageBusInterface in your controller instead.', __METHOD__); + + if (!$this->container->has('messenger.default_bus')) { + $message = class_exists(Envelope::class) ? 'You need to define the "messenger.default_bus" configuration option.' : 'Try running "composer require symfony/messenger".'; + throw new \LogicException('The message bus is not enabled in your application. '.$message); + } + + return $this->container->get('messenger.default_bus')->dispatch($message, $stamps); + } + + /** + * Adds a Link HTTP header to the current response. + * + * @see https://tools.ietf.org/html/rfc5988 + */ + protected function addLink(Request $request, LinkInterface $link): void + { + if (!class_exists(AddLinkHeaderListener::class)) { + throw new \LogicException('You cannot use the "addLink" method if the WebLink component is not available. Try running "composer require symfony/web-link".'); + } + + if (null === $linkProvider = $request->attributes->get('_links')) { + $request->attributes->set('_links', new GenericLinkProvider([$link])); + + return; + } + + $request->attributes->set('_links', $linkProvider->withLink($link)); + } } diff --git a/lib/symfony/framework-bundle/Controller/Controller.php b/lib/symfony/framework-bundle/Controller/Controller.php deleted file mode 100644 index b9f15d7c25..0000000000 --- a/lib/symfony/framework-bundle/Controller/Controller.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Controller; - -use Symfony\Component\DependencyInjection\ContainerAwareInterface; -use Symfony\Component\DependencyInjection\ContainerAwareTrait; - -/** - * Controller is a simple implementation of a Controller. - * - * It provides methods to common features needed in controllers. - * - * @author Fabien Potencier - */ -abstract class Controller implements ContainerAwareInterface -{ - use ContainerAwareTrait; - use ControllerTrait; - - /** - * Gets a container configuration parameter by its name. - * - * @param string $name The parameter name - * - * @return mixed - * - * @final since version 3.4 - */ - protected function getParameter($name) - { - return $this->container->getParameter($name); - } -} diff --git a/lib/symfony/framework-bundle/Controller/ControllerNameParser.php b/lib/symfony/framework-bundle/Controller/ControllerNameParser.php deleted file mode 100644 index 3a758ee202..0000000000 --- a/lib/symfony/framework-bundle/Controller/ControllerNameParser.php +++ /dev/null @@ -1,152 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Controller; - -use Symfony\Component\HttpKernel\KernelInterface; - -/** - * ControllerNameParser converts controller from the short notation a:b:c - * (BlogBundle:Post:index) to a fully-qualified class::method string - * (Bundle\BlogBundle\Controller\PostController::indexAction). - * - * @author Fabien Potencier - */ -class ControllerNameParser -{ - protected $kernel; - - public function __construct(KernelInterface $kernel) - { - $this->kernel = $kernel; - } - - /** - * Converts a short notation a:b:c to a class::method. - * - * @param string $controller A short notation controller (a:b:c) - * - * @return string A string in the class::method notation - * - * @throws \InvalidArgumentException when the specified bundle is not enabled - * or the controller cannot be found - */ - public function parse($controller) - { - $parts = explode(':', $controller); - if (3 !== \count($parts) || \in_array('', $parts, true)) { - throw new \InvalidArgumentException(sprintf('The "%s" controller is not a valid "a:b:c" controller string.', $controller)); - } - - $originalController = $controller; - list($bundle, $controller, $action) = $parts; - $controller = str_replace('/', '\\', $controller); - $bundles = []; - - try { - // this throws an exception if there is no such bundle - $allBundles = $this->kernel->getBundle($bundle, false, true); - } catch (\InvalidArgumentException $e) { - $message = sprintf( - 'The "%s" (from the _controller value "%s") does not exist or is not enabled in your kernel!', - $bundle, - $originalController - ); - - if ($alternative = $this->findAlternative($bundle)) { - $message .= sprintf(' Did you mean "%s:%s:%s"?', $alternative, $controller, $action); - } - - throw new \InvalidArgumentException($message, 0, $e); - } - - if (!\is_array($allBundles)) { - // happens when HttpKernel is version 4+ - $allBundles = [$allBundles]; - } - - foreach ($allBundles as $b) { - $try = $b->getNamespace().'\\Controller\\'.$controller.'Controller'; - if (class_exists($try)) { - return $try.'::'.$action.'Action'; - } - - $bundles[] = $b->getName(); - $msg = sprintf('The _controller value "%s:%s:%s" maps to a "%s" class, but this class was not found. Create this class or check the spelling of the class and its namespace.', $bundle, $controller, $action, $try); - } - - if (\count($bundles) > 1) { - $msg = sprintf('Unable to find controller "%s:%s" in bundles %s.', $bundle, $controller, implode(', ', $bundles)); - } - - throw new \InvalidArgumentException($msg); - } - - /** - * Converts a class::method notation to a short one (a:b:c). - * - * @param string $controller A string in the class::method notation - * - * @return string A short notation controller (a:b:c) - * - * @throws \InvalidArgumentException when the controller is not valid or cannot be found in any bundle - */ - public function build($controller) - { - if (0 === preg_match('#^(.*?\\\\Controller\\\\(.+)Controller)::(.+)Action$#', $controller, $match)) { - throw new \InvalidArgumentException(sprintf('The "%s" controller is not a valid "class::method" string.', $controller)); - } - - $className = $match[1]; - $controllerName = $match[2]; - $actionName = $match[3]; - foreach ($this->kernel->getBundles() as $name => $bundle) { - if (0 !== strpos($className, $bundle->getNamespace())) { - continue; - } - - return sprintf('%s:%s:%s', $name, $controllerName, $actionName); - } - - throw new \InvalidArgumentException(sprintf('Unable to find a bundle that defines controller "%s".', $controller)); - } - - /** - * Attempts to find a bundle that is *similar* to the given bundle name. - * - * @param string $nonExistentBundleName - * - * @return string - */ - private function findAlternative($nonExistentBundleName) - { - $bundleNames = array_map(function ($b) { - return $b->getName(); - }, $this->kernel->getBundles()); - - $alternative = null; - $shortest = null; - foreach ($bundleNames as $bundleName) { - // if there's a partial match, return it immediately - if (false !== strpos($bundleName, $nonExistentBundleName)) { - return $bundleName; - } - - $lev = levenshtein($nonExistentBundleName, $bundleName); - if ($lev <= \strlen($nonExistentBundleName) / 3 && (null === $alternative || $lev < $shortest)) { - $alternative = $bundleName; - $shortest = $lev; - } - } - - return $alternative; - } -} diff --git a/lib/symfony/framework-bundle/Controller/ControllerResolver.php b/lib/symfony/framework-bundle/Controller/ControllerResolver.php index 742a0dd637..0539c1ee73 100644 --- a/lib/symfony/framework-bundle/Controller/ControllerResolver.php +++ b/lib/symfony/framework-bundle/Controller/ControllerResolver.php @@ -11,66 +11,32 @@ namespace Symfony\Bundle\FrameworkBundle\Controller; -use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpKernel\Controller\ContainerControllerResolver; /** * @author Fabien Potencier + * + * @final */ class ControllerResolver extends ContainerControllerResolver { - protected $parser; - - public function __construct(ContainerInterface $container, ControllerNameParser $parser, LoggerInterface $logger = null) - { - $this->parser = $parser; - - parent::__construct($container, $logger); - } - /** * {@inheritdoc} */ - protected function createController($controller) + protected function instantiateController(string $class): object { - if (false === strpos($controller, '::') && 2 === substr_count($controller, ':')) { - // controller in the a:b:c notation then - $controller = $this->parser->parse($controller); - } + $controller = parent::instantiateController($class); - $resolvedController = parent::createController($controller); - - if (1 === substr_count($controller, ':') && \is_array($resolvedController)) { - $resolvedController[0] = $this->configureController($resolvedController[0]); - } - - return $resolvedController; - } - - /** - * {@inheritdoc} - */ - protected function instantiateController($class) - { - return $this->configureController(parent::instantiateController($class)); - } - - private function configureController($controller) - { if ($controller instanceof ContainerAwareInterface) { - // @deprecated switch, to be removed in 4.0 where these classes - // won't implement ContainerAwareInterface anymore - switch (\get_class($controller)) { - case RedirectController::class: - case TemplateController::class: - return $controller; - } $controller->setContainer($this->container); } - if ($controller instanceof AbstractController && null !== $previousContainer = $controller->setContainer($this->container)) { - $controller->setContainer($previousContainer); + if ($controller instanceof AbstractController) { + if (null === $previousContainer = $controller->setContainer($this->container)) { + throw new \LogicException(sprintf('"%s" has no container set, did you forget to define it as a service subscriber?', $class)); + } else { + $controller->setContainer($previousContainer); + } } return $controller; diff --git a/lib/symfony/framework-bundle/Controller/ControllerTrait.php b/lib/symfony/framework-bundle/Controller/ControllerTrait.php deleted file mode 100644 index 8858ca66c4..0000000000 --- a/lib/symfony/framework-bundle/Controller/ControllerTrait.php +++ /dev/null @@ -1,480 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Controller; - -use Doctrine\Common\Persistence\ManagerRegistry as LegacyManagerRegistry; -use Doctrine\Persistence\ManagerRegistry; -use Psr\Container\ContainerInterface; -use Symfony\Component\Form\Extension\Core\Type\FormType; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\FormInterface; -use Symfony\Component\HttpFoundation\BinaryFileResponse; -use Symfony\Component\HttpFoundation\JsonResponse; -use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\ResponseHeaderBag; -use Symfony\Component\HttpFoundation\StreamedResponse; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\HttpKernel\HttpKernelInterface; -use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Component\Security\Core\Exception\AccessDeniedException; -use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Component\Security\Csrf\CsrfToken; - -/** - * Common features needed in controllers. - * - * @author Fabien Potencier - * - * @internal - * - * @property ContainerInterface $container - */ -trait ControllerTrait -{ - /** - * Returns true if the service id is defined. - * - * @param string $id The service id - * - * @return bool true if the service id is defined, false otherwise - * - * @final since version 3.4 - */ - protected function has($id) - { - return $this->container->has($id); - } - - /** - * Gets a container service by its id. - * - * @param string $id The service id - * - * @return object The service - * - * @final since version 3.4 - */ - protected function get($id) - { - return $this->container->get($id); - } - - /** - * Generates a URL from the given parameters. - * - * @param string $route The name of the route - * @param array $parameters An array of parameters - * @param int $referenceType The type of reference (one of the constants in UrlGeneratorInterface) - * - * @return string The generated URL - * - * @see UrlGeneratorInterface - * - * @final since version 3.4 - */ - protected function generateUrl($route, $parameters = [], $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH) - { - return $this->container->get('router')->generate($route, $parameters, $referenceType); - } - - /** - * Forwards the request to another controller. - * - * @param string $controller The controller name (a string like BlogBundle:Post:index) - * @param array $path An array of path parameters - * @param array $query An array of query parameters - * - * @return Response A Response instance - * - * @final since version 3.4 - */ - protected function forward($controller, array $path = [], array $query = []) - { - $request = $this->container->get('request_stack')->getCurrentRequest(); - $path['_forwarded'] = $request->attributes; - $path['_controller'] = $controller; - $subRequest = $request->duplicate($query, null, $path); - - return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST); - } - - /** - * Returns a RedirectResponse to the given URL. - * - * @param string $url The URL to redirect to - * @param int $status The status code to use for the Response - * - * @return RedirectResponse - * - * @final since version 3.4 - */ - protected function redirect($url, $status = 302) - { - return new RedirectResponse($url, $status); - } - - /** - * Returns a RedirectResponse to the given route with the given parameters. - * - * @param string $route The name of the route - * @param array $parameters An array of parameters - * @param int $status The status code to use for the Response - * - * @return RedirectResponse - * - * @final since version 3.4 - */ - protected function redirectToRoute($route, array $parameters = [], $status = 302) - { - return $this->redirect($this->generateUrl($route, $parameters), $status); - } - - /** - * Returns a JsonResponse that uses the serializer component if enabled, or json_encode. - * - * @param mixed $data The response data - * @param int $status The status code to use for the Response - * @param array $headers Array of extra headers to add - * @param array $context Context to pass to serializer when using serializer component - * - * @return JsonResponse - * - * @final since version 3.4 - */ - protected function json($data, $status = 200, $headers = [], $context = []) - { - if ($this->container->has('serializer')) { - $json = $this->container->get('serializer')->serialize($data, 'json', array_merge([ - 'json_encode_options' => JsonResponse::DEFAULT_ENCODING_OPTIONS, - ], $context)); - - return new JsonResponse($json, $status, $headers, true); - } - - return new JsonResponse($data, $status, $headers); - } - - /** - * Returns a BinaryFileResponse object with original or customized file name and disposition header. - * - * @param \SplFileInfo|string $file File object or path to file to be sent as response - * @param string|null $fileName File name to be sent to response or null (will use original file name) - * @param string $disposition Disposition of response ("attachment" is default, other type is "inline") - * - * @return BinaryFileResponse - * - * @final since version 3.4 - */ - protected function file($file, $fileName = null, $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT) - { - $response = new BinaryFileResponse($file); - $response->setContentDisposition($disposition, null === $fileName ? $response->getFile()->getFilename() : $fileName); - - return $response; - } - - /** - * Adds a flash message to the current session for type. - * - * @param string $type The type - * @param mixed $message The message - * - * @throws \LogicException - * - * @final since version 3.4 - */ - protected function addFlash($type, $message) - { - if (!$this->container->has('session')) { - throw new \LogicException('You can not use the addFlash method if sessions are disabled. Enable them in "config/packages/framework.yaml".'); - } - - $this->container->get('session')->getFlashBag()->add($type, $message); - } - - /** - * Checks if the attributes are granted against the current authentication token and optionally supplied subject. - * - * @param mixed $attributes The attributes - * @param mixed $subject The subject - * - * @return bool - * - * @throws \LogicException - * - * @final since version 3.4 - */ - protected function isGranted($attributes, $subject = null) - { - if (!$this->container->has('security.authorization_checker')) { - throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".'); - } - - return $this->container->get('security.authorization_checker')->isGranted($attributes, $subject); - } - - /** - * Throws an exception unless the attributes are granted against the current authentication token and optionally - * supplied subject. - * - * @param mixed $attributes The attributes - * @param mixed $subject The subject - * @param string $message The message passed to the exception - * - * @throws AccessDeniedException - * - * @final since version 3.4 - */ - protected function denyAccessUnlessGranted($attributes, $subject = null, $message = 'Access Denied.') - { - if (!$this->isGranted($attributes, $subject)) { - $exception = $this->createAccessDeniedException($message); - $exception->setAttributes($attributes); - $exception->setSubject($subject); - - throw $exception; - } - } - - /** - * Returns a rendered view. - * - * @param string $view The view name - * @param array $parameters An array of parameters to pass to the view - * - * @return string The rendered view - * - * @final since version 3.4 - */ - protected function renderView($view, array $parameters = []) - { - if ($this->container->has('templating')) { - return $this->container->get('templating')->render($view, $parameters); - } - - if (!$this->container->has('twig')) { - throw new \LogicException('You can not use the "renderView" method if the Templating Component or the Twig Bundle are not available. Try running "composer require symfony/twig-bundle".'); - } - - return $this->container->get('twig')->render($view, $parameters); - } - - /** - * Renders a view. - * - * @param string $view The view name - * @param array $parameters An array of parameters to pass to the view - * @param Response $response A response instance - * - * @return Response A Response instance - * - * @final since version 3.4 - */ - protected function render($view, array $parameters = [], Response $response = null) - { - if ($this->container->has('templating')) { - $content = $this->container->get('templating')->render($view, $parameters); - } elseif ($this->container->has('twig')) { - $content = $this->container->get('twig')->render($view, $parameters); - } else { - throw new \LogicException('You can not use the "render" method if the Templating Component or the Twig Bundle are not available. Try running "composer require symfony/twig-bundle".'); - } - - if (null === $response) { - $response = new Response(); - } - - $response->setContent($content); - - return $response; - } - - /** - * Streams a view. - * - * @param string $view The view name - * @param array $parameters An array of parameters to pass to the view - * @param StreamedResponse $response A response instance - * - * @return StreamedResponse A StreamedResponse instance - * - * @final since version 3.4 - */ - protected function stream($view, array $parameters = [], StreamedResponse $response = null) - { - if ($this->container->has('templating')) { - $templating = $this->container->get('templating'); - - $callback = function () use ($templating, $view, $parameters) { - $templating->stream($view, $parameters); - }; - } elseif ($this->container->has('twig')) { - $twig = $this->container->get('twig'); - - $callback = function () use ($twig, $view, $parameters) { - $twig->display($view, $parameters); - }; - } else { - throw new \LogicException('You can not use the "stream" method if the Templating Component or the Twig Bundle are not available. Try running "composer require symfony/twig-bundle".'); - } - - if (null === $response) { - return new StreamedResponse($callback); - } - - $response->setCallback($callback); - - return $response; - } - - /** - * Returns a NotFoundHttpException. - * - * This will result in a 404 response code. Usage example: - * - * throw $this->createNotFoundException('Page not found!'); - * - * @param string $message A message - * @param \Exception|null $previous The previous exception - * - * @return NotFoundHttpException - * - * @final since version 3.4 - */ - protected function createNotFoundException($message = 'Not Found', \Exception $previous = null) - { - return new NotFoundHttpException($message, $previous); - } - - /** - * Returns an AccessDeniedException. - * - * This will result in a 403 response code. Usage example: - * - * throw $this->createAccessDeniedException('Unable to access this page!'); - * - * @param string $message A message - * @param \Exception|null $previous The previous exception - * - * @return AccessDeniedException - * - * @throws \LogicException If the Security component is not available - * - * @final since version 3.4 - */ - protected function createAccessDeniedException($message = 'Access Denied.', \Exception $previous = null) - { - if (!class_exists(AccessDeniedException::class)) { - throw new \LogicException('You can not use the "createAccessDeniedException" method if the Security component is not available. Try running "composer require symfony/security-bundle".'); - } - - return new AccessDeniedException($message, $previous); - } - - /** - * Creates and returns a Form instance from the type of the form. - * - * @param string $type The fully qualified class name of the form type - * @param mixed $data The initial data for the form - * @param array $options Options for the form - * - * @return FormInterface - * - * @final since version 3.4 - */ - protected function createForm($type, $data = null, array $options = []) - { - return $this->container->get('form.factory')->create($type, $data, $options); - } - - /** - * Creates and returns a form builder instance. - * - * @param mixed $data The initial data for the form - * @param array $options Options for the form - * - * @return FormBuilderInterface - * - * @final since version 3.4 - */ - protected function createFormBuilder($data = null, array $options = []) - { - return $this->container->get('form.factory')->createBuilder(FormType::class, $data, $options); - } - - /** - * Shortcut to return the Doctrine Registry service. - * - * @return ManagerRegistry|LegacyManagerRegistry - * - * @throws \LogicException If DoctrineBundle is not available - * - * @final since version 3.4 - */ - protected function getDoctrine() - { - if (!$this->container->has('doctrine')) { - throw new \LogicException('The DoctrineBundle is not registered in your application. Try running "composer require symfony/orm-pack".'); - } - - return $this->container->get('doctrine'); - } - - /** - * Get a user from the Security Token Storage. - * - * @return UserInterface|object|null - * - * @throws \LogicException If SecurityBundle is not available - * - * @see TokenInterface::getUser() - * - * @final since version 3.4 - */ - protected function getUser() - { - if (!$this->container->has('security.token_storage')) { - throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".'); - } - - if (null === $token = $this->container->get('security.token_storage')->getToken()) { - return null; - } - - if (!\is_object($user = $token->getUser())) { - // e.g. anonymous authentication - return null; - } - - return $user; - } - - /** - * Checks the validity of a CSRF token. - * - * @param string $id The id used when generating the token - * @param string $token The actual token sent with the request that should be validated - * - * @return bool - * - * @final since version 3.4 - */ - protected function isCsrfTokenValid($id, $token) - { - if (!$this->container->has('security.csrf.token_manager')) { - throw new \LogicException('CSRF protection is not enabled in your application. Enable it with the "csrf_protection" key in "config/packages/framework.yaml".'); - } - - return $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($id, $token)); - } -} diff --git a/lib/symfony/framework-bundle/Controller/RedirectController.php b/lib/symfony/framework-bundle/Controller/RedirectController.php index dbb69cc5ea..6a0fed64f6 100644 --- a/lib/symfony/framework-bundle/Controller/RedirectController.php +++ b/lib/symfony/framework-bundle/Controller/RedirectController.php @@ -11,8 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Controller; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\HeaderUtils; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -24,37 +23,21 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; * * @author Fabien Potencier * - * @final since version 3.4 + * @final */ -class RedirectController implements ContainerAwareInterface +class RedirectController { - /** - * @deprecated since version 3.4, to be removed in 4.0 - */ - protected $container; - private $router; private $httpPort; private $httpsPort; - public function __construct(UrlGeneratorInterface $router = null, $httpPort = null, $httpsPort = null) + public function __construct(UrlGeneratorInterface $router = null, int $httpPort = null, int $httpsPort = null) { $this->router = $router; $this->httpPort = $httpPort; $this->httpsPort = $httpsPort; } - /** - * @deprecated since version 3.4, to be removed in 4.0 alongside with the ContainerAwareInterface type. - */ - public function setContainer(ContainerInterface $container = null) - { - @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.4 and will be removed in 4.0. Inject an UrlGeneratorInterface using the constructor instead.', __METHOD__), \E_USER_DEPRECATED); - - $this->container = $container; - $this->router = $container->get('router'); - } - /** * Redirects to another route with the given name. * @@ -64,16 +47,14 @@ class RedirectController implements ContainerAwareInterface * In case the route name is empty, the status code will be 404 when permanent is false * and 410 otherwise. * - * @param Request $request The request instance - * @param string $route The route name to redirect to - * @param bool $permanent Whether the redirection is permanent - * @param bool|array $ignoreAttributes Whether to ignore attributes or an array of attributes to ignore - * - * @return Response A Response instance + * @param string $route The route name to redirect to + * @param bool $permanent Whether the redirection is permanent + * @param bool|array $ignoreAttributes Whether to ignore attributes or an array of attributes to ignore + * @param bool $keepRequestMethod Whether redirect action should keep HTTP request method * * @throws HttpException In case the route name is empty */ - public function redirectAction(Request $request, $route, $permanent = false, $ignoreAttributes = false) + public function redirectAction(Request $request, string $route, bool $permanent = false, $ignoreAttributes = false, bool $keepRequestMethod = false, bool $keepQueryParams = false): Response { if ('' == $route) { throw new HttpException($permanent ? 410 : 404); @@ -82,13 +63,30 @@ class RedirectController implements ContainerAwareInterface $attributes = []; if (false === $ignoreAttributes || \is_array($ignoreAttributes)) { $attributes = $request->attributes->get('_route_params'); - unset($attributes['route'], $attributes['permanent'], $attributes['ignoreAttributes']); + + if ($keepQueryParams) { + if ($query = $request->server->get('QUERY_STRING')) { + $query = HeaderUtils::parseQuery($query); + } else { + $query = $request->query->all(); + } + + $attributes = array_merge($query, $attributes); + } + + unset($attributes['route'], $attributes['permanent'], $attributes['ignoreAttributes'], $attributes['keepRequestMethod'], $attributes['keepQueryParams']); if ($ignoreAttributes) { $attributes = array_diff_key($attributes, array_flip($ignoreAttributes)); } } - return new RedirectResponse($this->router->generate($route, $attributes, UrlGeneratorInterface::ABSOLUTE_URL), $permanent ? 301 : 302); + if ($keepRequestMethod) { + $statusCode = $permanent ? 308 : 307; + } else { + $statusCode = $permanent ? 301 : 302; + } + + return new RedirectResponse($this->router->generate($route, $attributes, UrlGeneratorInterface::ABSOLUTE_URL), $statusCode); } /** @@ -100,24 +98,26 @@ class RedirectController implements ContainerAwareInterface * In case the path is empty, the status code will be 404 when permanent is false * and 410 otherwise. * - * @param Request $request The request instance - * @param string $path The absolute path or URL to redirect to - * @param bool $permanent Whether the redirect is permanent or not - * @param string|null $scheme The URL scheme (null to keep the current one) - * @param int|null $httpPort The HTTP port (null to keep the current one for the same scheme or the configured port in the container) - * @param int|null $httpsPort The HTTPS port (null to keep the current one for the same scheme or the configured port in the container) - * - * @return Response A Response instance + * @param string $path The absolute path or URL to redirect to + * @param bool $permanent Whether the redirect is permanent or not + * @param string|null $scheme The URL scheme (null to keep the current one) + * @param int|null $httpPort The HTTP port (null to keep the current one for the same scheme or the default configured port) + * @param int|null $httpsPort The HTTPS port (null to keep the current one for the same scheme or the default configured port) + * @param bool $keepRequestMethod Whether redirect action should keep HTTP request method * * @throws HttpException In case the path is empty */ - public function urlRedirectAction(Request $request, $path, $permanent = false, $scheme = null, $httpPort = null, $httpsPort = null) + public function urlRedirectAction(Request $request, string $path, bool $permanent = false, string $scheme = null, int $httpPort = null, int $httpsPort = null, bool $keepRequestMethod = false): Response { if ('' == $path) { throw new HttpException($permanent ? 410 : 404); } - $statusCode = $permanent ? 301 : 302; + if ($keepRequestMethod) { + $statusCode = $permanent ? 308 : 307; + } else { + $statusCode = $permanent ? 301 : 302; + } // redirect if the path is a full URL if (parse_url($path, \PHP_URL_SCHEME)) { @@ -129,7 +129,7 @@ class RedirectController implements ContainerAwareInterface } if ($qs = $request->server->get('QUERY_STRING') ?: $request->getQueryString()) { - if (false === strpos($path, '?')) { + if (!str_contains($path, '?')) { $qs = '?'.$qs; } else { $qs = '&'.$qs; @@ -141,9 +141,6 @@ class RedirectController implements ContainerAwareInterface if (null === $httpPort) { if ('http' === $request->getScheme()) { $httpPort = $request->getPort(); - } elseif ($this->container && $this->container->hasParameter('request_listener.http_port')) { - @trigger_error(sprintf('Passing the http port as a container parameter is deprecated since Symfony 3.4 and won\'t be possible in 4.0. Pass it to the constructor of the "%s" class instead.', __CLASS__), \E_USER_DEPRECATED); - $httpPort = $this->container->getParameter('request_listener.http_port'); } else { $httpPort = $this->httpPort; } @@ -156,9 +153,6 @@ class RedirectController implements ContainerAwareInterface if (null === $httpsPort) { if ('https' === $request->getScheme()) { $httpsPort = $request->getPort(); - } elseif ($this->container && $this->container->hasParameter('request_listener.https_port')) { - @trigger_error(sprintf('Passing the https port as a container parameter is deprecated since Symfony 3.4 and won\'t be possible in 4.0. Pass it to the constructor of the "%s" class instead.', __CLASS__), \E_USER_DEPRECATED); - $httpsPort = $this->container->getParameter('request_listener.https_port'); } else { $httpsPort = $this->httpsPort; } @@ -173,4 +167,23 @@ class RedirectController implements ContainerAwareInterface return new RedirectResponse($url, $statusCode); } + + public function __invoke(Request $request): Response + { + $p = $request->attributes->get('_route_params', []); + + if (\array_key_exists('route', $p)) { + if (\array_key_exists('path', $p)) { + throw new \RuntimeException(sprintf('Ambiguous redirection settings, use the "path" or "route" parameter, not both: "%s" and "%s" found respectively in "%s" routing configuration.', $p['path'], $p['route'], $request->attributes->get('_route'))); + } + + return $this->redirectAction($request, $p['route'], $p['permanent'] ?? false, $p['ignoreAttributes'] ?? false, $p['keepRequestMethod'] ?? false, $p['keepQueryParams'] ?? false); + } + + if (\array_key_exists('path', $p)) { + return $this->urlRedirectAction($request, $p['path'], $p['permanent'] ?? false, $p['scheme'] ?? null, $p['httpPort'] ?? null, $p['httpsPort'] ?? null, $p['keepRequestMethod'] ?? false); + } + + throw new \RuntimeException(sprintf('The parameter "path" or "route" is required to configure the redirect action in "%s" routing configuration.', $request->attributes->get('_route'))); + } } diff --git a/lib/symfony/framework-bundle/Controller/TemplateController.php b/lib/symfony/framework-bundle/Controller/TemplateController.php index f91520e126..2283dbc91f 100644 --- a/lib/symfony/framework-bundle/Controller/TemplateController.php +++ b/lib/symfony/framework-bundle/Controller/TemplateController.php @@ -11,10 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Controller; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Templating\EngineInterface; use Twig\Environment; /** @@ -22,60 +19,36 @@ use Twig\Environment; * * @author Fabien Potencier * - * @final since version 3.4 + * @final */ -class TemplateController implements ContainerAwareInterface +class TemplateController { - /** - * @deprecated since version 3.4, to be removed in 4.0 - */ - protected $container; - private $twig; - private $templating; - public function __construct(Environment $twig = null, EngineInterface $templating = null) + public function __construct(Environment $twig = null) { $this->twig = $twig; - $this->templating = $templating; - } - - /** - * @deprecated since version 3.4, to be removed in 4.0 alongside with the ContainerAwareInterface type. - */ - public function setContainer(ContainerInterface $container = null) - { - @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.4 and will be removed in 4.0. Inject a Twig Environment or an EngineInterface using the constructor instead.', __METHOD__), \E_USER_DEPRECATED); - - if ($container->has('templating')) { - $this->templating = $container->get('templating'); - } elseif ($container->has('twig')) { - $this->twig = $container->get('twig'); - } - $this->container = $container; } /** * Renders a template. * - * @param string $template The template name - * @param int|null $maxAge Max age for client caching - * @param int|null $sharedAge Max age for shared (proxy) caching - * @param bool|null $private Whether or not caching should apply for client caches only - * - * @return Response A Response instance + * @param string $template The template name + * @param int|null $maxAge Max age for client caching + * @param int|null $sharedAge Max age for shared (proxy) caching + * @param bool|null $private Whether or not caching should apply for client caches only + * @param array $context The context (arguments) of the template + * @param int $statusCode The HTTP status code to return with the response. Defaults to 200 */ - public function templateAction($template, $maxAge = null, $sharedAge = null, $private = null) + public function templateAction(string $template, int $maxAge = null, int $sharedAge = null, bool $private = null, array $context = [], int $statusCode = 200): Response { - if ($this->templating) { - $response = new Response($this->templating->render($template)); - } elseif ($this->twig) { - $response = new Response($this->twig->render($template)); - } else { - throw new \LogicException('You can not use the TemplateController if the Templating Component or the Twig Bundle are not available.'); + if (null === $this->twig) { + throw new \LogicException('You cannot use the TemplateController if the Twig Bundle is not available.'); } - if (null !== $maxAge) { + $response = new Response($this->twig->render($template, $context), $statusCode); + + if ($maxAge) { $response->setMaxAge($maxAge); } @@ -91,4 +64,9 @@ class TemplateController implements ContainerAwareInterface return $response; } + + public function __invoke(string $template, int $maxAge = null, int $sharedAge = null, bool $private = null, array $context = [], int $statusCode = 200): Response + { + return $this->templateAction($template, $maxAge, $sharedAge, $private, $context, $statusCode); + } } diff --git a/lib/symfony/framework-bundle/DataCollector/AbstractDataCollector.php b/lib/symfony/framework-bundle/DataCollector/AbstractDataCollector.php new file mode 100644 index 0000000000..7fa1ee2d3e --- /dev/null +++ b/lib/symfony/framework-bundle/DataCollector/AbstractDataCollector.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 Symfony\Bundle\FrameworkBundle\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\DataCollector; + +/** + * @author Laurent VOULLEMIER + */ +abstract class AbstractDataCollector extends DataCollector implements TemplateAwareDataCollectorInterface +{ + public function getName(): string + { + return static::class; + } + + public function reset(): void + { + $this->data = []; + } + + public static function getTemplate(): ?string + { + return null; + } +} diff --git a/lib/symfony/framework-bundle/DataCollector/RequestDataCollector.php b/lib/symfony/framework-bundle/DataCollector/RequestDataCollector.php deleted file mode 100644 index 3beb8b7e9d..0000000000 --- a/lib/symfony/framework-bundle/DataCollector/RequestDataCollector.php +++ /dev/null @@ -1,77 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DataCollector; - -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\ParameterBag; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector as BaseRequestCollector; -use Symfony\Component\HttpKernel\Event\FilterControllerEvent; - -/** - * RequestDataCollector. - * - * @author Jules Pietri - */ -class RequestDataCollector extends BaseRequestCollector implements EventSubscriberInterface -{ - /** - * {@inheritdoc} - */ - public function collect(Request $request, Response $response, \Exception $exception = null) - { - parent::collect($request, $response, $exception); - - if ($parentRequestAttributes = $request->attributes->get('_forwarded')) { - if ($parentRequestAttributes instanceof ParameterBag) { - $parentRequestAttributes->set('_forward_token', $response->headers->get('x-debug-token')); - } - } - if ($request->attributes->has('_forward_controller')) { - $this->data['forward'] = [ - 'token' => $request->attributes->get('_forward_token'), - 'controller' => $this->parseController($request->attributes->get('_forward_controller')), - ]; - } - } - - /** - * Gets the parsed forward controller. - * - * @return array|bool An array with keys 'token' the forward profile token, and - * 'controller' the parsed forward controller, false otherwise - */ - public function getForward() - { - return isset($this->data['forward']) ? $this->data['forward'] : false; - } - - public function onKernelController(FilterControllerEvent $event) - { - $this->controllers[$event->getRequest()] = $event->getController(); - - if ($parentRequestAttributes = $event->getRequest()->attributes->get('_forwarded')) { - if ($parentRequestAttributes instanceof ParameterBag) { - $parentRequestAttributes->set('_forward_controller', $event->getController()); - } - } - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'request'; - } -} diff --git a/lib/symfony/framework-bundle/DataCollector/RouterDataCollector.php b/lib/symfony/framework-bundle/DataCollector/RouterDataCollector.php index 90a88ca10e..c5d0673dea 100644 --- a/lib/symfony/framework-bundle/DataCollector/RouterDataCollector.php +++ b/lib/symfony/framework-bundle/DataCollector/RouterDataCollector.php @@ -16,9 +16,9 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\DataCollector\RouterDataCollector as BaseRouterDataCollector; /** - * RouterDataCollector. - * * @author Fabien Potencier + * + * @final */ class RouterDataCollector extends BaseRouterDataCollector { diff --git a/lib/symfony/framework-bundle/DataCollector/TemplateAwareDataCollectorInterface.php b/lib/symfony/framework-bundle/DataCollector/TemplateAwareDataCollectorInterface.php new file mode 100644 index 0000000000..5ef17abc54 --- /dev/null +++ b/lib/symfony/framework-bundle/DataCollector/TemplateAwareDataCollectorInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; + +/** + * @author Laurent VOULLEMIER + */ +interface TemplateAwareDataCollectorInterface extends DataCollectorInterface +{ + public static function getTemplate(): ?string; +} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php index 4f09e52bdc..d7db6ebf05 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddAnnotationsCachedReaderPass.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -34,8 +35,11 @@ class AddAnnotationsCachedReaderPass implements CompilerPassInterface $provider = $properties['cacheProviderBackup']->getValues()[0]; unset($properties['cacheProviderBackup']); $reader->setProperties($properties); - $container->set($id, null); - $container->setDefinition($id, $reader->replaceArgument(1, $provider)); + $reader->replaceArgument(1, $provider); + } elseif (4 <= \count($arguments = $reader->getArguments()) && $arguments[3] instanceof ServiceClosureArgument) { + $arguments[1] = $arguments[3]->getValues()[0]; + unset($arguments[3]); + $reader->setArguments($arguments); } } } diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddCacheClearerPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddCacheClearerPass.php deleted file mode 100644 index 4802640ebc..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddCacheClearerPass.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 Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use tagged iterator arguments instead.', AddCacheClearerPass::class), \E_USER_DEPRECATED); - -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; - -/** - * Registers the cache clearers. - * - * @deprecated since version 3.4, to be removed in 4.0. Use tagged iterator arguments. - * - * @author Dustin Dobervich - */ -class AddCacheClearerPass implements CompilerPassInterface -{ - /** - * {@inheritdoc} - */ - public function process(ContainerBuilder $container) - { - if (!$container->hasDefinition('cache_clearer')) { - return; - } - - $clearers = []; - foreach ($container->findTaggedServiceIds('kernel.cache_clearer', true) as $id => $attributes) { - $clearers[] = new Reference($id); - } - - $container->getDefinition('cache_clearer')->replaceArgument(0, $clearers); - } -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddCacheWarmerPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddCacheWarmerPass.php deleted file mode 100644 index f09f29aa5c..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddCacheWarmerPass.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use tagged iterator arguments instead.', AddCacheWarmerPass::class), \E_USER_DEPRECATED); - -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; -use Symfony\Component\DependencyInjection\ContainerBuilder; - -/** - * Registers the cache warmers. - * - * @deprecated since version 3.4, to be removed in 4.0. Use tagged iterator arguments instead. - * - * @author Fabien Potencier - */ -class AddCacheWarmerPass implements CompilerPassInterface -{ - use PriorityTaggedServiceTrait; - - /** - * {@inheritdoc} - */ - public function process(ContainerBuilder $container) - { - if (!$container->hasDefinition('cache_warmer')) { - return; - } - - $warmers = $this->findAndSortTaggedServices('kernel.cache_warmer', $container); - - if (empty($warmers)) { - return; - } - - $container->getDefinition('cache_warmer')->replaceArgument(0, $warmers); - } -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddConsoleCommandPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddConsoleCommandPass.php deleted file mode 100644 index 0ef27964b3..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddConsoleCommandPass.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -@trigger_error(sprintf('%s is deprecated since Symfony 3.3 and will be removed in 4.0. Use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass instead.', AddConsoleCommandPass::class), \E_USER_DEPRECATED); - -use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass as BaseAddConsoleCommandPass; - -/** - * Registers console commands. - * - * @author Grégoire Pineau - * - * @deprecated since version 3.3, to be removed in 4.0. Use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass instead. - */ -class AddConsoleCommandPass extends BaseAddConsoleCommandPass -{ -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddConstraintValidatorsPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddConstraintValidatorsPass.php deleted file mode 100644 index b0fc18caa5..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddConstraintValidatorsPass.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass as BaseAddConstraintValidatorsPass; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use %s instead.', AddConstraintValidatorsPass::class, BaseAddConstraintValidatorsPass::class), \E_USER_DEPRECATED); - -/** - * @deprecated since version 3.3, to be removed in 4.0. Use {@link BaseAddConstraintValidatorsPass} instead - */ -class AddConstraintValidatorsPass extends BaseAddConstraintValidatorsPass -{ -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php index b4da145010..51b2975546 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddDebugLogProcessorPass.php @@ -30,6 +30,14 @@ class AddDebugLogProcessorPass implements CompilerPassInterface } $definition = $container->getDefinition('monolog.logger_prototype'); + $definition->setConfigurator([__CLASS__, 'configureLogger']); $definition->addMethodCall('pushProcessor', [new Reference('debug.log_processor')]); } + + public static function configureLogger($logger) + { + if (\is_object($logger) && method_exists($logger, 'removeDebugLogger') && \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { + $logger->removeDebugLogger(); + } + } } diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php index 77c0700599..3e2f2768ed 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddExpressionLanguageProvidersPass.php @@ -28,19 +28,11 @@ class AddExpressionLanguageProvidersPass implements CompilerPassInterface public function process(ContainerBuilder $container) { // routing - if ($container->has('router')) { - $definition = $container->findDefinition('router'); + if ($container->has('router.default')) { + $definition = $container->findDefinition('router.default'); foreach ($container->findTaggedServiceIds('routing.expression_language_provider', true) as $id => $attributes) { $definition->addMethodCall('addExpressionLanguageProvider', [new Reference($id)]); } } - - // security - if ($container->has('security.expression_language')) { - $definition = $container->findDefinition('security.expression_language'); - foreach ($container->findTaggedServiceIds('security.expression_language_provider', true) as $id => $attributes) { - $definition->addMethodCall('registerProvider', [new Reference($id)]); - } - } } } diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddValidatorInitializersPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddValidatorInitializersPass.php deleted file mode 100644 index ded0d6d319..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AddValidatorInitializersPass.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -use Symfony\Component\Validator\DependencyInjection\AddValidatorInitializersPass as BaseAddValidatorsInitializerPass; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use %s instead.', AddValidatorInitializersPass::class, BaseAddValidatorsInitializerPass::class), \E_USER_DEPRECATED); - -/** - * @deprecated since version 3.3, to be removed in 4.0. Use {@link BaseAddValidatorInitializersPass} instead - */ -class AddValidatorInitializersPass extends BaseAddValidatorsInitializerPass -{ -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/AssetsContextPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AssetsContextPass.php new file mode 100644 index 0000000000..3fc79f0ee0 --- /dev/null +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/AssetsContextPass.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +class AssetsContextPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('assets.context')) { + return; + } + + if (!$container->hasDefinition('router.request_context')) { + $container->setParameter('asset.request_context.base_path', $container->getParameter('asset.request_context.base_path') ?? ''); + $container->setParameter('asset.request_context.secure', $container->getParameter('asset.request_context.secure') ?? false); + + return; + } + + $context = $container->getDefinition('assets.context'); + + if (null === $container->getParameter('asset.request_context.base_path')) { + $context->replaceArgument(1, (new Definition('string'))->setFactory([new Reference('router.request_context'), 'getBaseUrl'])); + } + + if (null === $container->getParameter('asset.request_context.secure')) { + $context->replaceArgument(2, (new Definition('bool'))->setFactory([new Reference('router.request_context'), 'isSecure'])); + } + } +} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/CacheCollectorPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/CacheCollectorPass.php deleted file mode 100644 index 6bb614489e..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/CacheCollectorPass.php +++ /dev/null @@ -1,68 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface; -use Symfony\Component\Cache\Adapter\TraceableAdapter; -use Symfony\Component\Cache\Adapter\TraceableTagAwareAdapter; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\Reference; - -/** - * Inject a data collector to all the cache services to be able to get detailed statistics. - * - * @author Tobias Nyholm - */ -class CacheCollectorPass implements CompilerPassInterface -{ - /** - * {@inheritdoc} - */ - public function process(ContainerBuilder $container) - { - if (!$container->hasDefinition('data_collector.cache')) { - return; - } - - $collectorDefinition = $container->getDefinition('data_collector.cache'); - foreach ($container->findTaggedServiceIds('cache.pool') as $id => $attributes) { - $definition = $container->getDefinition($id); - if ($definition->isAbstract()) { - continue; - } - - $recorder = new Definition(is_subclass_of($definition->getClass(), TagAwareAdapterInterface::class) ? TraceableTagAwareAdapter::class : TraceableAdapter::class); - $recorder->setTags($definition->getTags()); - if (!$definition->isPublic() || !$definition->isPrivate()) { - $recorder->setPublic($definition->isPublic()); - } - $recorder->setArguments([new Reference($innerId = $id.'.recorder_inner')]); - - $definition->setTags([]); - $definition->setPublic(false); - - if (method_exists($definition, 'getAutowiringTypes') && $types = $definition->getAutowiringTypes(false)) { - $recorder->setAutowiringTypes($types); - $definition->setAutowiringTypes([]); - } - - $container->setDefinition($innerId, $definition); - $container->setDefinition($id, $recorder); - - // Tell the collector to add the new instance - $collectorDefinition->addMethodCall('addInstance', [$id, new Reference($id)]); - $collectorDefinition->setPublic(false); - } - } -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/CachePoolClearerPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/CachePoolClearerPass.php deleted file mode 100644 index 9f9b907b24..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/CachePoolClearerPass.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; - -/** - * @author Nicolas Grekas - */ -final class CachePoolClearerPass implements CompilerPassInterface -{ - /** - * {@inheritdoc} - */ - public function process(ContainerBuilder $container) - { - $container->getParameterBag()->remove('cache.prefix.seed'); - - foreach ($container->findTaggedServiceIds('cache.pool.clearer') as $id => $attr) { - $clearer = $container->getDefinition($id); - $pools = []; - foreach ($clearer->getArgument(0) as $id => $ref) { - if ($container->hasDefinition($id)) { - $pools[$id] = new Reference($id); - } - } - $clearer->replaceArgument(0, $pools); - } - } -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/CachePoolPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/CachePoolPass.php deleted file mode 100644 index 04ceb75630..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/CachePoolPass.php +++ /dev/null @@ -1,148 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -use Symfony\Component\Cache\Adapter\AbstractAdapter; -use Symfony\Component\Cache\Adapter\ArrayAdapter; -use Symfony\Component\DependencyInjection\ChildDefinition; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; -use Symfony\Component\DependencyInjection\Reference; - -/** - * @author Nicolas Grekas - */ -class CachePoolPass implements CompilerPassInterface -{ - /** - * {@inheritdoc} - */ - public function process(ContainerBuilder $container) - { - if ($container->hasParameter('cache.prefix.seed')) { - $seed = '.'.$container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed')); - } else { - $seed = '_'.$container->getParameter('kernel.root_dir'); - } - $seed .= '.'.$container->getParameter('kernel.name').'.'.$container->getParameter('kernel.environment'); - - $pools = []; - $clearers = []; - $attributes = [ - 'provider', - 'namespace', - 'default_lifetime', - 'reset', - ]; - foreach ($container->findTaggedServiceIds('cache.pool') as $id => $tags) { - $adapter = $pool = $container->getDefinition($id); - if ($pool->isAbstract()) { - continue; - } - while ($adapter instanceof ChildDefinition) { - $adapter = $container->findDefinition($adapter->getParent()); - if ($t = $adapter->getTag('cache.pool')) { - $tags[0] += $t[0]; - } - } - if (!isset($tags[0]['namespace'])) { - $tags[0]['namespace'] = $this->getNamespace($seed, $id); - } - if (isset($tags[0]['clearer'])) { - $clearer = $tags[0]['clearer']; - while ($container->hasAlias($clearer)) { - $clearer = (string) $container->getAlias($clearer); - } - } else { - $clearer = null; - } - unset($tags[0]['clearer']); - - if (isset($tags[0]['provider'])) { - $tags[0]['provider'] = new Reference(static::getServiceProvider($container, $tags[0]['provider'])); - } - $i = 0; - foreach ($attributes as $attr) { - if (!isset($tags[0][$attr])) { - // no-op - } elseif ('reset' === $attr) { - if ($tags[0][$attr]) { - $pool->addTag('kernel.reset', ['method' => $tags[0][$attr]]); - } - } elseif ('namespace' !== $attr || ArrayAdapter::class !== $adapter->getClass()) { - $pool->replaceArgument($i++, $tags[0][$attr]); - } - unset($tags[0][$attr]); - } - if (!empty($tags[0])) { - throw new InvalidArgumentException(sprintf('Invalid "cache.pool" tag for service "%s": accepted attributes are "clearer", "provider", "namespace", "default_lifetime" and "reset", found "%s".', $id, implode('", "', array_keys($tags[0])))); - } - - if (null !== $clearer) { - $clearers[$clearer][$id] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); - } - - $pools[$id] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE); - } - - $clearer = 'cache.global_clearer'; - while ($container->hasAlias($clearer)) { - $clearer = (string) $container->getAlias($clearer); - } - if ($container->hasDefinition($clearer)) { - $clearers['cache.global_clearer'] = $pools; - } - - foreach ($clearers as $id => $pools) { - $clearer = $container->getDefinition($id); - if ($clearer instanceof ChildDefinition) { - $clearer->replaceArgument(0, $pools); - } else { - $clearer->setArgument(0, $pools); - } - $clearer->addTag('cache.pool.clearer'); - - if ('cache.system_clearer' === $id) { - $clearer->addTag('kernel.cache_clearer'); - } - } - } - - private function getNamespace($seed, $id) - { - return substr(str_replace('/', '-', base64_encode(hash('sha256', $id.$seed, true))), 0, 10); - } - - /** - * @internal - */ - public static function getServiceProvider(ContainerBuilder $container, $name) - { - $container->resolveEnvPlaceholders($name, null, $usedEnvs); - - if ($usedEnvs || preg_match('#^[a-z]++://#', $name)) { - $dsn = $name; - - if (!$container->hasDefinition($name = 'cache_connection.'.ContainerBuilder::hash($dsn))) { - $definition = new Definition(AbstractAdapter::class); - $definition->setPublic(false); - $definition->setFactory([AbstractAdapter::class, 'createConnection']); - $definition->setArguments([$dsn, ['lazy' => true]]); - $container->setDefinition($name, $definition); - } - } - - return $name; - } -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/CompilerDebugDumpPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/CompilerDebugDumpPass.php deleted file mode 100644 index 7934e170e0..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/CompilerDebugDumpPass.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0.', CompilerDebugDumpPass::class), \E_USER_DEPRECATED); - -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\Filesystem\Exception\IOException; -use Symfony\Component\Filesystem\Filesystem; - -/** - * @deprecated since version 3.3, to be removed in 4.0. - */ -class CompilerDebugDumpPass implements CompilerPassInterface -{ - public function process(ContainerBuilder $container) - { - $filename = self::getCompilerLogFilename($container); - - $filesystem = new Filesystem(); - $filesystem->dumpFile($filename, implode("\n", $container->getCompiler()->getLog()), null); - try { - $filesystem->chmod($filename, 0666, umask()); - } catch (IOException $e) { - // discard chmod failure (some filesystem may not support it) - } - } - - public static function getCompilerLogFilename(ContainerInterface $container) - { - $class = $container->getParameter('kernel.container_class'); - - return $container->getParameter('kernel.cache_dir').'/'.$class.'Compiler.log'; - } -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/ConfigCachePass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/ConfigCachePass.php deleted file mode 100644 index 80bdac80bf..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/ConfigCachePass.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -use Symfony\Component\Config\DependencyInjection\ConfigCachePass as BaseConfigCachePass; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use tagged iterator arguments instead.', ConfigCachePass::class), \E_USER_DEPRECATED); - -/** - * Adds services tagged config_cache.resource_checker to the config_cache_factory service, ordering them by priority. - * - * @deprecated since version 3.3, to be removed in 4.0. Use tagged iterator arguments instead. - * - * @author Matthias Pigulla - * @author Benjamin Klotz - */ -class ConfigCachePass extends BaseConfigCachePass -{ -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/ControllerArgumentValueResolverPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/ControllerArgumentValueResolverPass.php deleted file mode 100644 index 6d78f8439b..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/ControllerArgumentValueResolverPass.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass as BaseControllerArgumentValueResolverPass; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use %s instead.', ControllerArgumentValueResolverPass::class, BaseControllerArgumentValueResolverPass::class), \E_USER_DEPRECATED); - -/** - * Gathers and configures the argument value resolvers. - * - * @author Iltar van der Berg - * - * @deprecated since version 3.3, to be removed in 4.0. Use {@link BaseControllerArgumentValueResolverPass} - */ -class ControllerArgumentValueResolverPass extends BaseControllerArgumentValueResolverPass -{ -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/FormPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/FormPass.php deleted file mode 100644 index 54f7026792..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/FormPass.php +++ /dev/null @@ -1,88 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use Symfony\Component\Form\DependencyInjection\FormPass instead.', FormPass::class), \E_USER_DEPRECATED); - -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; - -/** - * Adds all services with the tags "form.type" and "form.type_guesser" as - * arguments of the "form.extension" service. - * - * @author Bernhard Schussek - * - * @deprecated since version 3.3, to be removed in 4.0. Use FormPass in the Form component instead. - */ -class FormPass implements CompilerPassInterface -{ - use PriorityTaggedServiceTrait; - - public function process(ContainerBuilder $container) - { - if (!$container->hasDefinition('form.extension')) { - return; - } - - $definition = $container->getDefinition('form.extension'); - - // Builds an array with fully-qualified type class names as keys and service IDs as values - $types = []; - - foreach ($container->findTaggedServiceIds('form.type') as $serviceId => $tag) { - $serviceDefinition = $container->getDefinition($serviceId); - if (!$serviceDefinition->isPublic()) { - $serviceDefinition->setPublic(true); - } - - // Support type access by FQCN - $types[$serviceDefinition->getClass()] = $serviceId; - } - - $definition->replaceArgument(1, $types); - - $typeExtensions = []; - - foreach ($this->findAndSortTaggedServices('form.type_extension', $container) as $reference) { - $serviceId = (string) $reference; - $serviceDefinition = $container->getDefinition($serviceId); - if (!$serviceDefinition->isPublic()) { - $serviceDefinition->setPublic(true); - } - - $tag = $serviceDefinition->getTag('form.type_extension'); - if (isset($tag[0]['extended_type'])) { - $extendedType = $tag[0]['extended_type']; - } else { - throw new InvalidArgumentException(sprintf('Tagged form type extension must have the extended type configured using the extended_type/extended-type attribute, none was configured for the "%s" service.', $serviceId)); - } - - $typeExtensions[$extendedType][] = $serviceId; - } - - $definition->replaceArgument(2, $typeExtensions); - - // Find all services annotated with "form.type_guesser" - $guessers = array_keys($container->findTaggedServiceIds('form.type_guesser')); - foreach ($guessers as $serviceId) { - $serviceDefinition = $container->getDefinition($serviceId); - if (!$serviceDefinition->isPublic()) { - $serviceDefinition->setPublic(true); - } - } - - $definition->replaceArgument(3, $guessers); - } -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/LoggingTranslatorPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/LoggingTranslatorPass.php index 8866035637..80cbe52e6c 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/LoggingTranslatorPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/LoggingTranslatorPass.php @@ -15,7 +15,7 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\Translation\TranslatorBagInterface; -use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorInterface; /** * @author Abdellatif Ait boudad diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/ProfilerPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/ProfilerPass.php index 4318081db3..7200b12b9b 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/ProfilerPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/ProfilerPass.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; +use Symfony\Bundle\FrameworkBundle\DataCollector\TemplateAwareDataCollectorInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; @@ -34,14 +35,17 @@ class ProfilerPass implements CompilerPassInterface $collectors = new \SplPriorityQueue(); $order = \PHP_INT_MAX; foreach ($container->findTaggedServiceIds('data_collector', true) as $id => $attributes) { - $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0; + $priority = $attributes[0]['priority'] ?? 0; $template = null; - if (isset($attributes[0]['template'])) { - if (!isset($attributes[0]['id'])) { + $collectorClass = $container->findDefinition($id)->getClass(); + $isTemplateAware = is_subclass_of($collectorClass, TemplateAwareDataCollectorInterface::class); + if (isset($attributes[0]['template']) || $isTemplateAware) { + $idForTemplate = $attributes[0]['id'] ?? $collectorClass; + if (!$idForTemplate) { throw new InvalidArgumentException(sprintf('Data collector service "%s" must have an id attribute in order to specify a template.', $id)); } - $template = [$attributes[0]['id'], $attributes[0]['template']]; + $template = [$idForTemplate, $attributes[0]['template'] ?? $collectorClass::getTemplate()]; } $collectors->insert([$id, $template], [$priority, --$order]); diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/PropertyInfoPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/PropertyInfoPass.php deleted file mode 100644 index ef47bc0cda..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/PropertyInfoPass.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass instead.', PropertyInfoPass::class), \E_USER_DEPRECATED); - -use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass as BasePropertyInfoPass; - -/** - * Adds extractors to the property_info service. - * - * @author Kévin Dunglas - * - * @deprecated since version 3.3, to be removed in 4.0. Use {@link BasePropertyInfoPass instead}. - */ -class PropertyInfoPass extends BasePropertyInfoPass -{ -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.php new file mode 100644 index 0000000000..8b6479c4f2 --- /dev/null +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/RemoveUnusedSessionMarshallingHandlerPass.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 Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * @author Ahmed TAILOULOUTE + */ +class RemoveUnusedSessionMarshallingHandlerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('session.marshalling_handler')) { + return; + } + + $isMarshallerDecorated = false; + + foreach ($container->getDefinitions() as $definition) { + $decorated = $definition->getDecoratedService(); + if (null !== $decorated && 'session.marshaller' === $decorated[0]) { + $isMarshallerDecorated = true; + + break; + } + } + + if (!$isMarshallerDecorated) { + $container->removeDefinition('session.marshalling_handler'); + } + } +} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/RoutingResolverPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/RoutingResolverPass.php deleted file mode 100644 index f1f50a43d7..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/RoutingResolverPass.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass as BaseRoutingResolverPass; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use %s instead.', RoutingResolverPass::class, BaseRoutingResolverPass::class), \E_USER_DEPRECATED); - -/** - * Adds tagged routing.loader services to routing.resolver service. - * - * @author Fabien Potencier - * - * @deprecated since version 3.3, to be removed in 4.0. Use {@link BaseRoutingResolverPass} - */ -class RoutingResolverPass extends BaseRoutingResolverPass -{ -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/SerializerPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/SerializerPass.php deleted file mode 100644 index 117e9b2f91..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/SerializerPass.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use Symfony\Component\Serializer\DependencyInjection\SerializerPass instead.', SerializerPass::class), \E_USER_DEPRECATED); - -use Symfony\Component\Serializer\DependencyInjection\SerializerPass as BaseSerializerPass; - -/** - * Adds all services with the tags "serializer.encoder" and "serializer.normalizer" as - * encoders and normalizers to the Serializer service. - * - * @deprecated since version 3.3, to be removed in 4.0. Use {@link BaseSerializerPass} instead. - * - * @author Javier Lopez - */ -class SerializerPass extends BaseSerializerPass -{ -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/SessionPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/SessionPass.php index 0f4950615f..7230fc9fb4 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/SessionPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/SessionPass.php @@ -22,21 +22,43 @@ class SessionPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { - if (!$container->hasDefinition('session')) { + if (!$container->has('session.factory')) { return; } + // BC layer: Make "session" an alias of ".session.do-not-use" when not overridden by the user + if (!$container->has('session')) { + $alias = $container->setAlias('session', '.session.do-not-use'); + $alias->setDeprecated('symfony/framework-bundle', '5.3', 'The "%alias_id%" service and "SessionInterface" alias are deprecated, use "$requestStack->getSession()" instead.'); + // restore previous behavior + $alias->setPublic(true); + + return; + } + + if ($container->hasDefinition('session')) { + $definition = $container->getDefinition('session'); + $definition->setDeprecated('symfony/framework-bundle', '5.3', 'The "%service_id%" service and "SessionInterface" alias are deprecated, use "$requestStack->getSession()" instead.'); + } else { + $alias = $container->getAlias('session'); + $alias->setDeprecated('symfony/framework-bundle', '5.3', 'The "%alias_id%" and "SessionInterface" aliases are deprecated, use "$requestStack->getSession()" instead.'); + $definition = $container->findDefinition('session'); + } + + // Convert internal service `.session.do-not-use` into alias of `session`. + $container->setAlias('.session.do-not-use', 'session'); + $bags = [ 'session.flash_bag' => $container->hasDefinition('session.flash_bag') ? $container->getDefinition('session.flash_bag') : null, 'session.attribute_bag' => $container->hasDefinition('session.attribute_bag') ? $container->getDefinition('session.attribute_bag') : null, ]; - foreach ($container->getDefinition('session')->getArguments() as $v) { + foreach ($definition->getArguments() as $v) { if (!$v instanceof Reference || !isset($bags[$bag = (string) $v]) || !\is_array($factory = $bags[$bag]->getFactory())) { continue; } - if ([0, 1] !== array_keys($factory) || !$factory[0] instanceof Reference || 'session' !== (string) $factory[0]) { + if ([0, 1] !== array_keys($factory) || !$factory[0] instanceof Reference || !\in_array((string) $factory[0], ['session', '.session.do-not-use'], true)) { continue; } diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/TemplatingPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/TemplatingPass.php deleted file mode 100644 index 4eff0b6c8e..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/TemplatingPass.php +++ /dev/null @@ -1,54 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface as FrameworkBundleEngineInterface; -use Symfony\Component\DependencyInjection\Alias; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\Templating\EngineInterface as ComponentEngineInterface; - -class TemplatingPass implements CompilerPassInterface -{ - public function process(ContainerBuilder $container) - { - if ($container->hasDefinition('templating')) { - return; - } - - if ($container->hasAlias('templating')) { - $container->setAlias(ComponentEngineInterface::class, new Alias('templating', false)); - $container->setAlias(FrameworkBundleEngineInterface::class, new Alias('templating', false)); - } - - if ($container->hasDefinition('templating.engine.php')) { - $refs = []; - $helpers = []; - foreach ($container->findTaggedServiceIds('templating.helper', true) as $id => $attributes) { - if (isset($attributes[0]['alias'])) { - $helpers[$attributes[0]['alias']] = $id; - $refs[$id] = new Reference($id); - } - } - - if (\count($helpers) > 0) { - $definition = $container->getDefinition('templating.engine.php'); - $definition->addMethodCall('setHelpers', [$helpers]); - - if ($container->hasDefinition('templating.engine.php.helpers_locator')) { - $container->getDefinition('templating.engine.php.helpers_locator')->replaceArgument(0, $refs); - } - } - } - } -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php new file mode 100644 index 0000000000..222b5c7b75 --- /dev/null +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.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 Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Nicolas Grekas + */ +class TestServiceContainerRealRefPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('test.private_services_locator')) { + return; + } + + $privateContainer = $container->getDefinition('test.private_services_locator'); + $definitions = $container->getDefinitions(); + $privateServices = $privateContainer->getArgument(0); + + foreach ($privateServices as $id => $argument) { + if (isset($definitions[$target = (string) $argument->getValues()[0]])) { + $argument->setValues([new Reference($target)]); + } else { + unset($privateServices[$id]); + } + } + + $privateContainer->replaceArgument(0, $privateServices); + } +} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php new file mode 100644 index 0000000000..bc1e5a9365 --- /dev/null +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Nicolas Grekas + */ +class TestServiceContainerWeakRefPass implements CompilerPassInterface +{ + private $privateTagName; + + public function __construct(string $privateTagName = 'container.private') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/framework-bundle', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->privateTagName = $privateTagName; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('test.private_services_locator')) { + return; + } + + $privateServices = []; + $definitions = $container->getDefinitions(); + $hasErrors = method_exists(Definition::class, 'hasErrors') ? 'hasErrors' : 'getErrors'; + + foreach ($definitions as $id => $definition) { + if ($id && '.' !== $id[0] && (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag($this->privateTagName)) && !$definition->$hasErrors() && !$definition->isAbstract()) { + $privateServices[$id] = new Reference($id, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE); + } + } + + $aliases = $container->getAliases(); + + foreach ($aliases as $id => $alias) { + if ($id && '.' !== $id[0] && (!$alias->isPublic() || $alias->isPrivate())) { + while (isset($aliases[$target = (string) $alias])) { + $alias = $aliases[$target]; + } + if (isset($definitions[$target]) && !$definitions[$target]->$hasErrors() && !$definitions[$target]->isAbstract()) { + $privateServices[$id] = new Reference($target, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE); + } + } + } + + if ($privateServices) { + $id = (string) ServiceLocatorTagPass::register($container, $privateServices); + $container->setDefinition('test.private_services_locator', $container->getDefinition($id))->setPublic(true); + $container->removeDefinition($id); + } + } +} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/TranslationDumperPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/TranslationDumperPass.php deleted file mode 100644 index 3a7f6865b5..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/TranslationDumperPass.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use Symfony\Component\Translation\DependencyInjection\TranslationDumperPass instead.', TranslationDumperPass::class), \E_USER_DEPRECATED); - -use Symfony\Component\Translation\DependencyInjection\TranslationDumperPass as BaseTranslationDumperPass; - -/** - * Adds tagged translation.formatter services to translation writer. - * - * @deprecated since version 3.4, to be removed in 4.0. Use {@link BaseTranslationDumperPass instead}. - */ -class TranslationDumperPass extends BaseTranslationDumperPass -{ -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/TranslationExtractorPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/TranslationExtractorPass.php deleted file mode 100644 index 510571c133..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/TranslationExtractorPass.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass instead.', TranslationExtractorPass::class), \E_USER_DEPRECATED); - -use Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass as BaseTranslationExtractorPass; - -/** - * Adds tagged translation.formatter services to translation writer. - * - * @deprecated since version 3.4, to be removed in 4.0. Use {@link BaseTranslationDumperPass instead}. - */ -class TranslationExtractorPass extends BaseTranslationExtractorPass -{ -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/TranslatorPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/TranslatorPass.php deleted file mode 100644 index f4c1bffe87..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/TranslatorPass.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use Symfony\Component\Translation\DependencyInjection\TranslatorPass instead.', TranslatorPass::class), \E_USER_DEPRECATED); - -use Symfony\Component\Translation\DependencyInjection\TranslatorPass as BaseTranslatorPass; - -/** - * Adds tagged translation.formatter services to translation writer. - * - * @deprecated since version 3.4, to be removed in 4.0. Use {@link BaseTranslatorPass instead}. - */ -class TranslatorPass extends BaseTranslatorPass -{ -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/UnusedTagsPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/UnusedTagsPass.php index f49f6a18c0..386ee7d0c4 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -21,60 +21,87 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; */ class UnusedTagsPass implements CompilerPassInterface { - private $knownTags = [ + private const KNOWN_TAGS = [ 'annotations.cached_reader', + 'assets.package', 'auto_alias', 'cache.pool', 'cache.pool.clearer', + 'chatter.transport_factory', 'config_cache.resource_checker', 'console.command', + 'container.do_not_inline', + 'container.env_var_loader', 'container.env_var_processor', 'container.hot_path', + 'container.no_preload', + 'container.preload', + 'container.private', + 'container.reversible', 'container.service_locator', + 'container.service_locator_context', 'container.service_subscriber', + 'container.stack', 'controller.argument_value_resolver', 'controller.service_arguments', 'data_collector', + 'event_dispatcher.dispatcher', 'form.type', 'form.type_extension', 'form.type_guesser', + 'http_client.client', 'kernel.cache_clearer', 'kernel.cache_warmer', 'kernel.event_listener', 'kernel.event_subscriber', 'kernel.fragment_renderer', + 'kernel.locale_aware', 'kernel.reset', + 'ldap', + 'mailer.transport_factory', + 'messenger.bus', + 'messenger.message_handler', + 'messenger.receiver', + 'messenger.transport_factory', + 'mime.mime_type_guesser', 'monolog.logger', + 'notifier.channel', 'property_info.access_extractor', + 'property_info.initializable_extractor', 'property_info.list_extractor', 'property_info.type_extractor', 'proxy', + 'routing.expression_language_function', 'routing.expression_language_provider', 'routing.loader', + 'routing.route_loader', + 'security.authenticator.login_linker', 'security.expression_language_provider', 'security.remember_me_aware', + 'security.remember_me_handler', 'security.voter', 'serializer.encoder', 'serializer.normalizer', - 'templating.helper', + 'texter.transport_factory', 'translation.dumper', 'translation.extractor', 'translation.loader', + 'translation.provider_factory', 'twig.extension', 'twig.loader', 'twig.runtime', + 'validator.auto_mapper', 'validator.constraint_validator', 'validator.initializer', - 'workflow.definition', ]; public function process(ContainerBuilder $container) { - $tags = array_unique(array_merge($container->findTags(), $this->knownTags)); + $tags = array_unique(array_merge($container->findTags(), self::KNOWN_TAGS)); foreach ($container->findUnusedTags() as $tag) { // skip known tags - if (\in_array($tag, $this->knownTags)) { + if (\in_array($tag, self::KNOWN_TAGS)) { continue; } @@ -85,7 +112,7 @@ class UnusedTagsPass implements CompilerPassInterface continue; } - if (false !== strpos($definedTag, $tag) || levenshtein($tag, $definedTag) <= \strlen($tag) / 3) { + if (str_contains($definedTag, $tag) || levenshtein($tag, $definedTag) <= \strlen($tag) / 3) { $candidates[] = $definedTag; } } diff --git a/lib/symfony/framework-bundle/DependencyInjection/Compiler/ValidateWorkflowsPass.php b/lib/symfony/framework-bundle/DependencyInjection/Compiler/ValidateWorkflowsPass.php deleted file mode 100644 index 5cc14ab50b..0000000000 --- a/lib/symfony/framework-bundle/DependencyInjection/Compiler/ValidateWorkflowsPass.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; - -use Symfony\Component\Workflow\DependencyInjection\ValidateWorkflowsPass as BaseValidateWorkflowsPass; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use %s instead.', ValidateWorkflowsPass::class, BaseValidateWorkflowsPass::class), \E_USER_DEPRECATED); - -/** - * @author Tobias Nyholm - * - * @deprecated since version 3.3, to be removed in 4.0. Use {@link BaseValidateWorkflowsPass} instead - */ -class ValidateWorkflowsPass extends BaseValidateWorkflowsPass -{ -} diff --git a/lib/symfony/framework-bundle/DependencyInjection/Configuration.php b/lib/symfony/framework-bundle/DependencyInjection/Configuration.php index f3e94ac04e..a86bac9fb9 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/Configuration.php +++ b/lib/symfony/framework-bundle/DependencyInjection/Configuration.php @@ -12,28 +12,40 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; use Doctrine\Common\Annotations\Annotation; +use Doctrine\Common\Annotations\PsrCachedReader; use Doctrine\Common\Cache\Cache; +use Doctrine\DBAL\Connection; +use Psr\Log\LogLevel; use Symfony\Bundle\FullStack; use Symfony\Component\Asset\Package; +use Symfony\Component\Cache\Adapter\DoctrineAdapter; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; +use Symfony\Component\Config\Definition\Builder\NodeBuilder; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\Form\Form; +use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\Lock\Lock; use Symfony\Component\Lock\Store\SemaphoreStore; +use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\Notifier\Notifier; +use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Translation\Translator; +use Symfony\Component\Uid\Factory\UuidFactory; use Symfony\Component\Validator\Validation; use Symfony\Component\WebLink\HttpHeaderSerializer; +use Symfony\Component\Workflow\WorkflowEvents; /** * FrameworkExtension configuration structure. - * - * @author Jeremy Mikola */ class Configuration implements ConfigurationInterface { @@ -42,20 +54,20 @@ class Configuration implements ConfigurationInterface /** * @param bool $debug Whether debugging is enabled or not */ - public function __construct($debug) + public function __construct(bool $debug) { - $this->debug = (bool) $debug; + $this->debug = $debug; } /** * Generates the configuration tree builder. * - * @return TreeBuilder The tree builder + * @return TreeBuilder */ public function getConfigTreeBuilder() { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('framework'); + $treeBuilder = new TreeBuilder('framework'); + $rootNode = $treeBuilder->getRootNode(); $rootNode ->beforeNormalization() @@ -66,57 +78,65 @@ class Configuration implements ConfigurationInterface return $v; }) ->end() + ->fixXmlConfig('enabled_locale') ->children() ->scalarNode('secret')->end() ->scalarNode('http_method_override') ->info("Set true to enable support for the '_method' request parameter to determine the intended HTTP method on POST requests. Note: When using the HttpCache, you need to call the method in your front controller instead") ->defaultTrue() ->end() - ->arrayNode('trusted_proxies') - ->setDeprecated('The "%path%.%node%" configuration key has been deprecated in Symfony 3.3. Use the Request::setTrustedProxies() method in your front controller instead.') - ->beforeNormalization() - ->ifTrue(function ($v) { - return !\is_array($v) && null !== $v; - }) - ->then(function ($v) { return \is_bool($v) ? [] : preg_split('/\s*,\s*/', $v); }) - ->end() - ->prototype('scalar') - ->validate() - ->ifTrue(function ($v) { - if (empty($v)) { - return false; - } - - if (false !== strpos($v, '/')) { - if ('0.0.0.0/0' === $v) { - return false; - } - - list($v, $mask) = explode('/', $v, 2); - - if (strcmp($mask, (int) $mask) || $mask < 1 || $mask > (false !== strpos($v, ':') ? 128 : 32)) { - return true; - } - } - - return !filter_var($v, \FILTER_VALIDATE_IP); - }) - ->thenInvalid('Invalid proxy IP "%s"') - ->end() - ->end() - ->end() ->scalarNode('ide')->defaultNull()->end() ->booleanNode('test')->end() ->scalarNode('default_locale')->defaultValue('en')->end() + ->booleanNode('set_locale_from_accept_language') + ->info('Whether to use the Accept-Language HTTP header to set the Request locale (only when the "_locale" request attribute is not passed).') + ->defaultFalse() + ->end() + ->booleanNode('set_content_language_from_locale') + ->info('Whether to set the Content-Language HTTP header on the Response using the Request locale.') + ->defaultFalse() + ->end() + ->arrayNode('enabled_locales') + ->info('Defines the possible locales for the application. This list is used for generating translations files, but also to restrict which locales are allowed when it is set from Accept-Language header (using "set_locale_from_accept_language").') + ->prototype('scalar')->end() + ->end() ->arrayNode('trusted_hosts') ->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end() ->prototype('scalar')->end() ->end() + ->scalarNode('trusted_proxies')->end() + ->arrayNode('trusted_headers') + ->fixXmlConfig('trusted_header') + ->performNoDeepMerging() + ->defaultValue(['x-forwarded-for', 'x-forwarded-port', 'x-forwarded-proto']) + ->beforeNormalization()->ifString()->then(function ($v) { return $v ? array_map('trim', explode(',', $v)) : []; })->end() + ->enumPrototype() + ->values([ + 'forwarded', + 'x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port', 'x-forwarded-prefix', + ]) + ->end() + ->end() + ->scalarNode('error_controller') + ->defaultValue('error_controller') + ->end() ->end() ; + $willBeAvailable = static function (string $package, string $class, string $parentPackage = null) { + $parentPackages = (array) $parentPackage; + $parentPackages[] = 'symfony/framework-bundle'; + + return ContainerBuilder::willBeAvailable($package, $class, $parentPackages, true); + }; + + $enableIfStandalone = static function (string $package, string $class) use ($willBeAvailable) { + return !class_exists(FullStack::class) && $willBeAvailable($package, $class) ? 'canBeDisabled' : 'canBeEnabled'; + }; + $this->addCsrfSection($rootNode); - $this->addFormSection($rootNode); + $this->addFormSection($rootNode, $enableIfStandalone); + $this->addHttpCacheSection($rootNode); $this->addEsiSection($rootNode); $this->addSsiSection($rootNode); $this->addFragmentsSection($rootNode); @@ -125,22 +145,46 @@ class Configuration implements ConfigurationInterface $this->addRouterSection($rootNode); $this->addSessionSection($rootNode); $this->addRequestSection($rootNode); - $this->addTemplatingSection($rootNode); - $this->addAssetsSection($rootNode); - $this->addTranslatorSection($rootNode); - $this->addValidationSection($rootNode); - $this->addAnnotationsSection($rootNode); - $this->addSerializerSection($rootNode); - $this->addPropertyAccessSection($rootNode); - $this->addPropertyInfoSection($rootNode); - $this->addCacheSection($rootNode); + $this->addAssetsSection($rootNode, $enableIfStandalone); + $this->addTranslatorSection($rootNode, $enableIfStandalone); + $this->addValidationSection($rootNode, $enableIfStandalone, $willBeAvailable); + $this->addAnnotationsSection($rootNode, $willBeAvailable); + $this->addSerializerSection($rootNode, $enableIfStandalone, $willBeAvailable); + $this->addPropertyAccessSection($rootNode, $willBeAvailable); + $this->addPropertyInfoSection($rootNode, $enableIfStandalone); + $this->addCacheSection($rootNode, $willBeAvailable); $this->addPhpErrorsSection($rootNode); - $this->addWebLinkSection($rootNode); - $this->addLockSection($rootNode); + $this->addExceptionsSection($rootNode); + $this->addWebLinkSection($rootNode, $enableIfStandalone); + $this->addLockSection($rootNode, $enableIfStandalone); + $this->addMessengerSection($rootNode, $enableIfStandalone); + $this->addRobotsIndexSection($rootNode); + $this->addHttpClientSection($rootNode, $enableIfStandalone); + $this->addMailerSection($rootNode, $enableIfStandalone); + $this->addSecretsSection($rootNode); + $this->addNotifierSection($rootNode, $enableIfStandalone); + $this->addRateLimiterSection($rootNode, $enableIfStandalone); + $this->addUidSection($rootNode, $enableIfStandalone); return $treeBuilder; } + private function addSecretsSection(ArrayNodeDefinition $rootNode) + { + $rootNode + ->children() + ->arrayNode('secrets') + ->canBeDisabled() + ->children() + ->scalarNode('vault_directory')->defaultValue('%kernel.project_dir%/config/secrets/%kernel.runtime_environment%')->cannotBeEmpty()->end() + ->scalarNode('local_dotenv_file')->defaultValue('%kernel.project_dir%/.env.%kernel.environment%.local')->end() + ->scalarNode('decryption_env_var')->defaultValue('base64:default::SYMFONY_DECRYPTION_SECRET')->end() + ->end() + ->end() + ->end() + ; + } + private function addCsrfSection(ArrayNodeDefinition $rootNode) { $rootNode @@ -159,13 +203,13 @@ class Configuration implements ConfigurationInterface ; } - private function addFormSection(ArrayNodeDefinition $rootNode) + private function addFormSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('form') ->info('form configuration') - ->{!class_exists(FullStack::class) && class_exists(Form::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/form', Form::class)}() ->children() ->arrayNode('csrf_protection') ->treatFalseLike(['enabled' => false]) @@ -177,6 +221,47 @@ class Configuration implements ConfigurationInterface ->scalarNode('field_name')->defaultValue('_token')->end() ->end() ->end() + // to be set to false in Symfony 6.0 + ->booleanNode('legacy_error_messages') + ->defaultTrue() + ->validate() + ->ifTrue() + ->then(function ($v) { + trigger_deprecation('symfony/framework-bundle', '5.2', 'Setting the "framework.form.legacy_error_messages" option to "true" is deprecated. It will have no effect as of Symfony 6.0.'); + + return $v; + }) + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addHttpCacheSection(ArrayNodeDefinition $rootNode) + { + $rootNode + ->children() + ->arrayNode('http_cache') + ->info('HTTP cache configuration') + ->canBeEnabled() + ->fixXmlConfig('private_header') + ->children() + ->booleanNode('debug')->defaultValue('%kernel.debug%')->end() + ->enumNode('trace_level') + ->values(['none', 'short', 'full']) + ->end() + ->scalarNode('trace_header')->end() + ->integerNode('default_ttl')->end() + ->arrayNode('private_headers') + ->performNoDeepMerging() + ->scalarPrototype()->end() + ->end() + ->booleanNode('allow_reload')->end() + ->booleanNode('allow_revalidate')->end() + ->integerNode('stale_while_revalidate')->end() + ->integerNode('stale_if_error')->end() ->end() ->end() ->end() @@ -214,6 +299,7 @@ class Configuration implements ConfigurationInterface ->info('fragments configuration') ->canBeEnabled() ->children() + ->scalarNode('hinclude_default_template')->defaultNull()->end() ->scalarNode('path')->defaultValue('/_fragment')->end() ->end() ->end() @@ -230,26 +316,11 @@ class Configuration implements ConfigurationInterface ->canBeEnabled() ->children() ->booleanNode('collect')->defaultTrue()->end() + ->scalarNode('collect_parameter')->defaultNull()->info('The name of the parameter to use to enable or disable collection on a per request basis')->end() ->booleanNode('only_exceptions')->defaultFalse()->end() - ->booleanNode('only_master_requests')->defaultFalse()->end() + ->booleanNode('only_main_requests')->defaultFalse()->end() + ->booleanNode('only_master_requests')->setDeprecated('symfony/framework-bundle', '5.3', 'Option "%node%" at "%path%" is deprecated, use "only_main_requests" instead.')->defaultFalse()->end() ->scalarNode('dsn')->defaultValue('file:%kernel.cache_dir%/profiler')->end() - ->arrayNode('matcher') - ->setDeprecated('The "profiler.matcher" configuration key has been deprecated in Symfony 3.4 and it will be removed in 4.0.') - ->canBeEnabled() - ->performNoDeepMerging() - ->fixXmlConfig('ip') - ->children() - ->scalarNode('path') - ->info('use the urldecoded format') - ->example('^/path to resource/') - ->end() - ->scalarNode('service')->end() - ->arrayNode('ips') - ->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end() - ->prototype('scalar')->end() - ->end() - ->end() - ->end() ->end() ->end() ->end() @@ -273,7 +344,7 @@ class Configuration implements ConfigurationInterface $workflows = []; } - if (1 === \count($workflows) && isset($workflows['workflows']) && array_keys($workflows['workflows']) !== range(0, \count($workflows) - 1) && !empty(array_diff(array_keys($workflows['workflows']), ['audit_trail', 'type', 'marking_store', 'supports', 'support_strategy', 'initial_place', 'places', 'transitions']))) { + if (1 === \count($workflows) && isset($workflows['workflows']) && !array_is_list($workflows['workflows']) && !empty(array_diff(array_keys($workflows['workflows']), ['audit_trail', 'type', 'marking_store', 'supports', 'support_strategy', 'initial_marking', 'places', 'transitions']))) { $workflows = $workflows['workflows']; } @@ -301,40 +372,27 @@ class Configuration implements ConfigurationInterface ->fixXmlConfig('support') ->fixXmlConfig('place') ->fixXmlConfig('transition') + ->fixXmlConfig('event_to_dispatch', 'events_to_dispatch') ->children() ->arrayNode('audit_trail') ->canBeEnabled() ->end() ->enumNode('type') ->values(['workflow', 'state_machine']) + ->defaultValue('state_machine') ->end() ->arrayNode('marking_store') - ->fixXmlConfig('argument') ->children() ->enumNode('type') - ->values(['multiple_state', 'single_state']) + ->values(['method']) ->end() - ->arrayNode('arguments') - ->beforeNormalization() - ->ifString() - ->then(function ($v) { return [$v]; }) - ->end() - ->requiresAtLeastOneElement() - ->prototype('scalar') - ->end() + ->scalarNode('property') + ->defaultValue('marking') ->end() ->scalarNode('service') ->cannotBeEmpty() ->end() ->end() - ->validate() - ->ifTrue(function ($v) { return isset($v['type']) && isset($v['service']); }) - ->thenInvalid('"type" and "service" cannot be used together.') - ->end() - ->validate() - ->ifTrue(function ($v) { return !empty($v['arguments']) && isset($v['service']); }) - ->thenInvalid('"arguments" and "service" cannot be used together.') - ->end() ->end() ->arrayNode('supports') ->beforeNormalization() @@ -344,7 +402,7 @@ class Configuration implements ConfigurationInterface ->prototype('scalar') ->cannotBeEmpty() ->validate() - ->ifTrue(function ($v) { return !class_exists($v) && !interface_exists($v); }) + ->ifTrue(function ($v) { return !class_exists($v) && !interface_exists($v, false); }) ->thenInvalid('The supported class or interface "%s" does not exist.') ->end() ->end() @@ -352,27 +410,94 @@ class Configuration implements ConfigurationInterface ->scalarNode('support_strategy') ->cannotBeEmpty() ->end() - ->scalarNode('initial_place') - ->defaultNull() + ->arrayNode('initial_marking') + ->beforeNormalization()->castToArray()->end() + ->defaultValue([]) + ->prototype('scalar')->end() + ->end() + ->variableNode('events_to_dispatch') + ->defaultValue(null) + ->validate() + ->ifTrue(function ($v) { + if (null === $v) { + return false; + } + if (!\is_array($v)) { + return true; + } + + foreach ($v as $value) { + if (!\is_string($value)) { + return true; + } + if (class_exists(WorkflowEvents::class) && !\in_array($value, WorkflowEvents::ALIASES)) { + return true; + } + } + + return false; + }) + ->thenInvalid('The value must be "null" or an array of workflow events (like ["workflow.enter"]).') + ->end() + ->info('Select which Transition events should be dispatched for this Workflow') + ->example(['workflow.enter', 'workflow.transition']) ->end() ->arrayNode('places') + ->beforeNormalization() + ->always() + ->then(function ($places) { + // It's an indexed array of shape ['place1', 'place2'] + if (isset($places[0]) && \is_string($places[0])) { + return array_map(function (string $place) { + return ['name' => $place]; + }, $places); + } + + // It's an indexed array, we let the validation occur + if (isset($places[0]) && \is_array($places[0])) { + return $places; + } + + foreach ($places as $name => $place) { + if (\is_array($place) && \array_key_exists('name', $place)) { + continue; + } + $place['name'] = $name; + $places[$name] = $place; + } + + return array_values($places); + }) + ->end() ->isRequired() ->requiresAtLeastOneElement() - ->prototype('scalar') - ->cannotBeEmpty() + ->prototype('array') + ->children() + ->scalarNode('name') + ->isRequired() + ->cannotBeEmpty() + ->end() + ->arrayNode('metadata') + ->normalizeKeys(false) + ->defaultValue([]) + ->example(['color' => 'blue', 'description' => 'Workflow to manage article.']) + ->prototype('variable') + ->end() + ->end() + ->end() ->end() ->end() ->arrayNode('transitions') ->beforeNormalization() ->always() ->then(function ($transitions) { - // It's an indexed array, we let the validation occurs - if (isset($transitions[0])) { + // It's an indexed array, we let the validation occur + if (isset($transitions[0]) && \is_array($transitions[0])) { return $transitions; } foreach ($transitions as $name => $transition) { - if (\array_key_exists('name', $transition)) { + if (\is_array($transition) && \array_key_exists('name', $transition)) { continue; } $transition['name'] = $name; @@ -393,7 +518,7 @@ class Configuration implements ConfigurationInterface ->scalarNode('guard') ->cannotBeEmpty() ->info('An expression to block the transition') - ->example('is_fully_authenticated() and has_role(\'ROLE_JOURNALIST\') and subject.getTitle() == \'My first article\'') + ->example('is_fully_authenticated() and is_granted(\'ROLE_JOURNALIST\') and subject.getTitle() == \'My first article\'') ->end() ->arrayNode('from') ->beforeNormalization() @@ -415,9 +540,23 @@ class Configuration implements ConfigurationInterface ->cannotBeEmpty() ->end() ->end() + ->arrayNode('metadata') + ->normalizeKeys(false) + ->defaultValue([]) + ->example(['color' => 'blue', 'description' => 'Workflow to manage article.']) + ->prototype('variable') + ->end() + ->end() ->end() ->end() ->end() + ->arrayNode('metadata') + ->normalizeKeys(false) + ->defaultValue([]) + ->example(['color' => 'blue', 'description' => 'Workflow to manage article.']) + ->prototype('variable') + ->end() + ->end() ->end() ->validate() ->ifTrue(function ($v) { @@ -431,6 +570,18 @@ class Configuration implements ConfigurationInterface }) ->thenInvalid('"supports" or "support_strategy" should be configured.') ->end() + ->beforeNormalization() + ->always() + ->then(function ($values) { + // Special case to deal with XML when the user wants an empty array + if (\array_key_exists('event_to_dispatch', $values) && null === $values['event_to_dispatch']) { + $values['events_to_dispatch'] = []; + unset($values['event_to_dispatch']); + } + + return $values; + }) + ->end() ->end() ->end() ->end() @@ -449,6 +600,10 @@ class Configuration implements ConfigurationInterface ->children() ->scalarNode('resource')->isRequired()->end() ->scalarNode('type')->end() + ->scalarNode('default_uri') + ->info('The default URI used to generate URLs in a non-HTTP context') + ->defaultNull() + ->end() ->scalarNode('http_port')->defaultValue(80)->end() ->scalarNode('https_port')->defaultValue(443)->end() ->scalarNode('strict_requirements') @@ -460,6 +615,7 @@ class Configuration implements ConfigurationInterface ) ->defaultTrue() ->end() + ->booleanNode('utf8')->defaultNull()->end() ->end() ->end() ->end() @@ -473,8 +629,15 @@ class Configuration implements ConfigurationInterface ->arrayNode('session') ->info('session configuration') ->canBeEnabled() + ->beforeNormalization() + ->ifTrue(function ($v) { + return \is_array($v) && isset($v['storage_id']) && isset($v['storage_factory_id']); + }) + ->thenInvalid('You cannot use both "storage_id" and "storage_factory_id" at the same time under "framework.session"') + ->end() ->children() ->scalarNode('storage_id')->defaultValue('session.storage.native')->end() + ->scalarNode('storage_factory_id')->defaultNull()->end() ->scalarNode('handler_id')->defaultValue('session.handler.native_file')->end() ->scalarNode('name') ->validate() @@ -489,22 +652,26 @@ class Configuration implements ConfigurationInterface ->scalarNode('cookie_lifetime')->end() ->scalarNode('cookie_path')->end() ->scalarNode('cookie_domain')->end() - ->booleanNode('cookie_secure')->end() + ->enumNode('cookie_secure')->values([true, false, 'auto'])->end() ->booleanNode('cookie_httponly')->defaultTrue()->end() ->enumNode('cookie_samesite')->values([null, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT, Cookie::SAMESITE_NONE])->defaultNull()->end() ->booleanNode('use_cookies')->end() ->scalarNode('gc_divisor')->end() ->scalarNode('gc_probability')->defaultValue(1)->end() ->scalarNode('gc_maxlifetime')->end() - ->booleanNode('use_strict_mode') - ->defaultTrue() - ->setDeprecated('The "%path%.%node%" option is enabled by default and deprecated since Symfony 3.4. It will be always enabled in 4.0.') - ->end() ->scalarNode('save_path')->defaultValue('%kernel.cache_dir%/sessions')->end() ->integerNode('metadata_update_threshold') - ->defaultValue('0') + ->defaultValue(0) ->info('seconds to wait between 2 session metadata updates') ->end() + ->integerNode('sid_length') + ->min(22) + ->max(256) + ->end() + ->integerNode('sid_bits_per_character') + ->min(4) + ->max(6) + ->end() ->end() ->end() ->end() @@ -527,10 +694,7 @@ class Configuration implements ConfigurationInterface ->ifTrue(function ($v) { return \is_array($v) && isset($v['mime_type']); }) ->then(function ($v) { return $v['mime_type']; }) ->end() - ->beforeNormalization() - ->ifTrue(function ($v) { return !\is_array($v); }) - ->then(function ($v) { return [$v]; }) - ->end() + ->beforeNormalization()->castToArray()->end() ->prototype('scalar')->end() ->end() ->end() @@ -540,75 +704,19 @@ class Configuration implements ConfigurationInterface ; } - private function addTemplatingSection(ArrayNodeDefinition $rootNode) - { - $rootNode - ->children() - ->arrayNode('templating') - ->info('templating configuration') - ->canBeEnabled() - ->beforeNormalization() - ->ifTrue(function ($v) { return false === $v || \is_array($v) && false === $v['enabled']; }) - ->then(function () { return ['enabled' => false, 'engines' => false]; }) - ->end() - ->children() - ->scalarNode('hinclude_default_template')->defaultNull()->end() - ->scalarNode('cache')->end() - ->arrayNode('form') - ->addDefaultsIfNotSet() - ->fixXmlConfig('resource') - ->children() - ->arrayNode('resources') - ->addDefaultChildrenIfNoneSet() - ->prototype('scalar')->defaultValue('FrameworkBundle:Form')->end() - ->validate() - ->ifTrue(function ($v) {return !\in_array('FrameworkBundle:Form', $v); }) - ->then(function ($v) { - return array_merge(['FrameworkBundle:Form'], $v); - }) - ->end() - ->end() - ->end() - ->end() - ->end() - ->fixXmlConfig('engine') - ->children() - ->arrayNode('engines') - ->example(['twig']) - ->isRequired() - ->requiresAtLeastOneElement() - ->canBeUnset() - ->beforeNormalization() - ->ifTrue(function ($v) { return !\is_array($v) && false !== $v; }) - ->then(function ($v) { return [$v]; }) - ->end() - ->prototype('scalar')->end() - ->end() - ->end() - ->fixXmlConfig('loader') - ->children() - ->arrayNode('loaders') - ->beforeNormalization() - ->ifTrue(function ($v) { return !\is_array($v); }) - ->then(function ($v) { return [$v]; }) - ->end() - ->prototype('scalar')->end() - ->end() - ->end() - ->end() - ->end() - ; - } - - private function addAssetsSection(ArrayNodeDefinition $rootNode) + private function addAssetsSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('assets') ->info('assets configuration') - ->{!class_exists(FullStack::class) && class_exists(Package::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/asset', Package::class)}() ->fixXmlConfig('base_url') ->children() + ->booleanNode('strict_mode') + ->info('Throw an exception if an entry is missing from the manifest.json') + ->defaultFalse() + ->end() ->scalarNode('version_strategy')->defaultNull()->end() ->scalarNode('version')->defaultNull()->end() ->scalarNode('version_format')->defaultValue('%%s?%%s')->end() @@ -616,10 +724,7 @@ class Configuration implements ConfigurationInterface ->scalarNode('base_path')->defaultValue('')->end() ->arrayNode('base_urls') ->requiresAtLeastOneElement() - ->beforeNormalization() - ->ifTrue(function ($v) { return !\is_array($v); }) - ->then(function ($v) { return [$v]; }) - ->end() + ->beforeNormalization()->castToArray()->end() ->prototype('scalar')->end() ->end() ->end() @@ -649,6 +754,10 @@ class Configuration implements ConfigurationInterface ->prototype('array') ->fixXmlConfig('base_url') ->children() + ->booleanNode('strict_mode') + ->info('Throw an exception if an entry is missing from the manifest.json') + ->defaultFalse() + ->end() ->scalarNode('version_strategy')->defaultNull()->end() ->scalarNode('version') ->beforeNormalization() @@ -661,10 +770,7 @@ class Configuration implements ConfigurationInterface ->scalarNode('base_path')->defaultValue('')->end() ->arrayNode('base_urls') ->requiresAtLeastOneElement() - ->beforeNormalization() - ->ifTrue(function ($v) { return !\is_array($v); }) - ->then(function ($v) { return [$v]; }) - ->end() + ->beforeNormalization()->castToArray()->end() ->prototype('scalar')->end() ->end() ->end() @@ -694,23 +800,27 @@ class Configuration implements ConfigurationInterface ; } - private function addTranslatorSection(ArrayNodeDefinition $rootNode) + private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('translator') ->info('translator configuration') - ->{!class_exists(FullStack::class) && class_exists(Translator::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/translation', Translator::class)}() ->fixXmlConfig('fallback') ->fixXmlConfig('path') + ->fixXmlConfig('enabled_locale') + ->fixXmlConfig('provider') ->children() ->arrayNode('fallbacks') + ->info('Defaults to the value of "default_locale".') ->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end() ->prototype('scalar')->end() - ->defaultValue(['en']) + ->defaultValue([]) ->end() - ->booleanNode('logging')->defaultValue($this->debug)->end() + ->booleanNode('logging')->defaultValue(false)->end() ->scalarNode('formatter')->defaultValue('translator.formatter.default')->end() + ->scalarNode('cache_dir')->defaultValue('%kernel.cache_dir%/translations')->end() ->scalarNode('default_path') ->info('The default path used to load translations') ->defaultValue('%kernel.project_dir%/translations') @@ -718,45 +828,72 @@ class Configuration implements ConfigurationInterface ->arrayNode('paths') ->prototype('scalar')->end() ->end() + ->arrayNode('enabled_locales') + ->setDeprecated('symfony/framework-bundle', '5.3', 'Option "%node%" at "%path%" is deprecated, set the "framework.enabled_locales" option instead.') + ->prototype('scalar')->end() + ->defaultValue([]) + ->end() + ->arrayNode('pseudo_localization') + ->canBeEnabled() + ->fixXmlConfig('localizable_html_attribute') + ->children() + ->booleanNode('accents')->defaultTrue()->end() + ->floatNode('expansion_factor') + ->min(1.0) + ->defaultValue(1.0) + ->end() + ->booleanNode('brackets')->defaultTrue()->end() + ->booleanNode('parse_html')->defaultFalse()->end() + ->arrayNode('localizable_html_attributes') + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->arrayNode('providers') + ->info('Translation providers you can read/write your translations from') + ->useAttributeAsKey('name') + ->prototype('array') + ->fixXmlConfig('domain') + ->fixXmlConfig('locale') + ->children() + ->scalarNode('dsn')->end() + ->arrayNode('domains') + ->prototype('scalar')->end() + ->defaultValue([]) + ->end() + ->arrayNode('locales') + ->prototype('scalar')->end() + ->defaultValue([]) + ->info('If not set, all locales listed under framework.enabled_locales are used.') + ->end() + ->end() + ->end() + ->defaultValue([]) + ->end() ->end() ->end() ->end() ; } - private function addValidationSection(ArrayNodeDefinition $rootNode) + private function addValidationSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone, callable $willBeAvailable) { $rootNode ->children() ->arrayNode('validation') ->info('validation configuration') - ->{!class_exists(FullStack::class) && class_exists(Validation::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/validator', Validation::class)}() ->children() - ->scalarNode('cache') - // Can be removed in 4.0, when validator.mapping.cache.doctrine.apc is removed - ->setDeprecated('The "%path%.%node%" option is deprecated since Symfony 3.2 and will be removed in 4.0. Configure the "cache.validator" service under "framework.cache.pools" instead.') - ->beforeNormalization() - ->ifString()->then(function ($v) { - if ('validator.mapping.cache.doctrine.apc' === $v && !class_exists('Doctrine\Common\Cache\ApcCache')) { - throw new LogicException('Doctrine APC cache for the validator cannot be enabled as the Doctrine Cache package is not installed.'); - } - - return $v; - }) - ->end() - ->end() - ->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && class_exists(Annotation::class) ? 'defaultTrue' : 'defaultFalse'}()->end() + ->scalarNode('cache')->end() + ->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && (\PHP_VERSION_ID >= 80000 || $willBeAvailable('doctrine/annotations', Annotation::class, 'symfony/validator')) ? 'defaultTrue' : 'defaultFalse'}()->end() ->arrayNode('static_method') ->defaultValue(['loadValidatorMetadata']) ->prototype('scalar')->end() ->treatFalseLike([]) - ->validate() - ->ifTrue(function ($v) { return !\is_array($v); }) - ->then(function ($v) { return (array) $v; }) - ->end() + ->validate()->castToArray()->end() ->end() ->scalarNode('translation_domain')->defaultValue('validators')->end() - ->booleanNode('strict_email')->defaultFalse()->end() + ->enumNode('email_validation_mode')->values(['html5', 'loose', 'strict'])->end() ->arrayNode('mapping') ->addDefaultsIfNotSet() ->fixXmlConfig('path') @@ -766,21 +903,81 @@ class Configuration implements ConfigurationInterface ->end() ->end() ->end() + ->arrayNode('not_compromised_password') + ->canBeDisabled() + ->children() + ->booleanNode('enabled') + ->defaultTrue() + ->info('When disabled, compromised passwords will be accepted as valid.') + ->end() + ->scalarNode('endpoint') + ->defaultNull() + ->info('API endpoint for the NotCompromisedPassword Validator.') + ->end() + ->end() + ->end() + ->arrayNode('auto_mapping') + ->info('A collection of namespaces for which auto-mapping will be enabled by default, or null to opt-in with the EnableAutoMapping constraint.') + ->example([ + 'App\\Entity\\' => [], + 'App\\WithSpecificLoaders\\' => ['validator.property_info_loader'], + ]) + ->useAttributeAsKey('namespace') + ->normalizeKeys(false) + ->beforeNormalization() + ->ifArray() + ->then(function (array $values): array { + foreach ($values as $k => $v) { + if (isset($v['service'])) { + continue; + } + + if (isset($v['namespace'])) { + $values[$k]['services'] = []; + continue; + } + + if (!\is_array($v)) { + $values[$v]['services'] = []; + unset($values[$k]); + continue; + } + + $tmp = $v; + unset($values[$k]); + $values[$k]['services'] = $tmp; + } + + return $values; + }) + ->end() + ->arrayPrototype() + ->fixXmlConfig('service') + ->children() + ->arrayNode('services') + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->end() ->end() ->end() ->end() ; } - private function addAnnotationsSection(ArrayNodeDefinition $rootNode) + private function addAnnotationsSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) { + $doctrineCache = $willBeAvailable('doctrine/cache', Cache::class, 'doctrine/annotations'); + $psr6Cache = $willBeAvailable('symfony/cache', PsrCachedReader::class, 'doctrine/annotations'); + $rootNode ->children() ->arrayNode('annotations') ->info('annotation configuration') - ->{class_exists(Annotation::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$willBeAvailable('doctrine/annotations', Annotation::class) ? 'canBeDisabled' : 'canBeEnabled'}() ->children() - ->scalarNode('cache')->defaultValue(interface_exists(Cache::class) ? 'php_array' : 'none')->end() + ->scalarNode('cache')->defaultValue(($doctrineCache || $psr6Cache) ? 'php_array' : 'none')->end() ->scalarNode('file_cache_dir')->defaultValue('%kernel.cache_dir%/annotations')->end() ->booleanNode('debug')->defaultValue($this->debug)->end() ->end() @@ -789,20 +986,18 @@ class Configuration implements ConfigurationInterface ; } - private function addSerializerSection(ArrayNodeDefinition $rootNode) + private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone, callable $willBeAvailable) { $rootNode ->children() ->arrayNode('serializer') ->info('serializer configuration') - ->{!class_exists(FullStack::class) && class_exists(Serializer::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/serializer', Serializer::class)}() ->children() - ->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && class_exists(Annotation::class) ? 'defaultTrue' : 'defaultFalse'}()->end() - ->scalarNode('cache') - ->setDeprecated('The "%path%.%node%" option is deprecated since Symfony 3.1 and will be removed in 4.0. Configure the "cache.serializer" service under "framework.cache.pools" instead.') - ->end() + ->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && (\PHP_VERSION_ID >= 80000 || $willBeAvailable('doctrine/annotations', Annotation::class, 'symfony/serializer')) ? 'defaultTrue' : 'defaultFalse'}()->end() ->scalarNode('name_converter')->end() ->scalarNode('circular_reference_handler')->end() + ->scalarNode('max_depth_handler')->end() ->arrayNode('mapping') ->addDefaultsIfNotSet() ->fixXmlConfig('path') @@ -812,41 +1007,51 @@ class Configuration implements ConfigurationInterface ->end() ->end() ->end() + ->arrayNode('default_context') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->defaultValue([]) + ->prototype('variable')->end() + ->end() ->end() ->end() ->end() ; } - private function addPropertyAccessSection(ArrayNodeDefinition $rootNode) + private function addPropertyAccessSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) { $rootNode ->children() ->arrayNode('property_access') ->addDefaultsIfNotSet() ->info('Property access configuration') + ->{$willBeAvailable('symfony/property-access', PropertyAccessor::class) ? 'canBeDisabled' : 'canBeEnabled'}() ->children() ->booleanNode('magic_call')->defaultFalse()->end() + ->booleanNode('magic_get')->defaultTrue()->end() + ->booleanNode('magic_set')->defaultTrue()->end() ->booleanNode('throw_exception_on_invalid_index')->defaultFalse()->end() + ->booleanNode('throw_exception_on_invalid_property_path')->defaultTrue()->end() ->end() ->end() ->end() ; } - private function addPropertyInfoSection(ArrayNodeDefinition $rootNode) + private function addPropertyInfoSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('property_info') ->info('Property info configuration') - ->{!class_exists(FullStack::class) && interface_exists(PropertyInfoExtractorInterface::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/property-info', PropertyInfoExtractorInterface::class)}() ->end() ->end() ; } - private function addCacheSection(ArrayNodeDefinition $rootNode) + private function addCacheSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) { $rootNode ->children() @@ -857,7 +1062,8 @@ class Configuration implements ConfigurationInterface ->children() ->scalarNode('prefix_seed') ->info('Used to namespace cache keys when using several apps with the same shared backend') - ->example('my-application-name') + ->defaultValue('_%kernel.project_dir%.%kernel.container_class%') + ->example('my-application-name/%kernel.environment%') ->end() ->scalarNode('app') ->info('App related cache pools configuration') @@ -867,20 +1073,61 @@ class Configuration implements ConfigurationInterface ->info('System related cache pools configuration') ->defaultValue('cache.adapter.system') ->end() - ->scalarNode('directory')->defaultValue('%kernel.cache_dir%/pools')->end() + ->scalarNode('directory')->defaultValue('%kernel.cache_dir%/pools/app')->end() ->scalarNode('default_doctrine_provider')->end() ->scalarNode('default_psr6_provider')->end() ->scalarNode('default_redis_provider')->defaultValue('redis://localhost')->end() ->scalarNode('default_memcached_provider')->defaultValue('memcached://localhost')->end() + ->scalarNode('default_doctrine_dbal_provider')->defaultValue('database_connection')->end() + ->scalarNode('default_pdo_provider')->defaultValue($willBeAvailable('doctrine/dbal', Connection::class) && class_exists(DoctrineAdapter::class) ? 'database_connection' : null)->end() ->arrayNode('pools') ->useAttributeAsKey('name') ->prototype('array') + ->fixXmlConfig('adapter') + ->beforeNormalization() + ->ifTrue(function ($v) { return isset($v['provider']) && \is_array($v['adapters'] ?? $v['adapter'] ?? null) && 1 < \count($v['adapters'] ?? $v['adapter']); }) + ->thenInvalid('Pool cannot have a "provider" while more than one adapter is defined') + ->end() ->children() - ->scalarNode('adapter')->defaultValue('cache.app')->end() + ->arrayNode('adapters') + ->performNoDeepMerging() + ->info('One or more adapters to chain for creating the pool, defaults to "cache.app".') + ->beforeNormalization()->castToArray()->end() + ->beforeNormalization() + ->always()->then(function ($values) { + if ([0] === array_keys($values) && \is_array($values[0])) { + return $values[0]; + } + $adapters = []; + + foreach ($values as $k => $v) { + if (\is_int($k) && \is_string($v)) { + $adapters[] = $v; + } elseif (!\is_array($v)) { + $adapters[$k] = $v; + } elseif (isset($v['provider'])) { + $adapters[$v['provider']] = $v['name'] ?? $v; + } else { + $adapters[] = $v['name'] ?? $v; + } + } + + return $adapters; + }) + ->end() + ->prototype('scalar')->end() + ->end() + ->scalarNode('tags')->defaultNull()->end() ->booleanNode('public')->defaultFalse()->end() - ->integerNode('default_lifetime')->end() + ->scalarNode('default_lifetime') + ->info('Default lifetime of the pool') + ->example('"600" for 5 minutes expressed in seconds, "PT5M" for five minutes expressed as ISO 8601 time interval, or "5 minutes" as a date expression') + ->end() ->scalarNode('provider') - ->info('The service name to use as provider when the specified adapter needs one.') + ->info('Overwrite the setting from the default provider for this adapter.') + ->end() + ->scalarNode('early_expiration_message_bus') + ->example('"messenger.default_bus" to send early expiration events to the default Messenger bus.') ->end() ->scalarNode('clearer')->end() ->end() @@ -904,10 +1151,32 @@ class Configuration implements ConfigurationInterface ->info('PHP errors handling configuration') ->addDefaultsIfNotSet() ->children() - ->booleanNode('log') - ->info('Use the app logger instead of the PHP logger for logging PHP errors.') + ->variableNode('log') + ->info('Use the application logger instead of the PHP logger for logging PHP errors.') + ->example('"true" to use the default configuration: log all errors. "false" to disable. An integer bit field of E_* constants, or an array mapping E_* constants to log levels.') ->defaultValue($this->debug) ->treatNullLike($this->debug) + ->beforeNormalization() + ->ifArray() + ->then(function (array $v): array { + if (!($v[0]['type'] ?? false)) { + return $v; + } + + // Fix XML normalization + + $ret = []; + foreach ($v as ['type' => $type, 'logLevel' => $logLevel]) { + $ret[$type] = $logLevel; + } + + return $ret; + }) + ->end() + ->validate() + ->ifTrue(function ($v) { return !(\is_int($v) || \is_bool($v) || \is_array($v)); }) + ->thenInvalid('The "php_errors.log" parameter should be either an integer, a boolean, or an array') + ->end() ->end() ->booleanNode('throw') ->info('Throw PHP errors as \ErrorException instances.') @@ -920,13 +1189,71 @@ class Configuration implements ConfigurationInterface ; } - private function addLockSection(ArrayNodeDefinition $rootNode) + private function addExceptionsSection(ArrayNodeDefinition $rootNode) + { + $logLevels = (new \ReflectionClass(LogLevel::class))->getConstants(); + + $rootNode + ->children() + ->arrayNode('exceptions') + ->info('Exception handling configuration') + ->beforeNormalization() + ->ifArray() + ->then(function (array $v): array { + if (!\array_key_exists('exception', $v)) { + return $v; + } + + // Fix XML normalization + $data = isset($v['exception'][0]) ? $v['exception'] : [$v['exception']]; + $exceptions = []; + foreach ($data as $exception) { + $config = []; + if (\array_key_exists('log-level', $exception)) { + $config['log_level'] = $exception['log-level']; + } + if (\array_key_exists('status-code', $exception)) { + $config['status_code'] = $exception['status-code']; + } + $exceptions[$exception['name']] = $config; + } + + return $exceptions; + }) + ->end() + ->prototype('array') + ->fixXmlConfig('exception') + ->children() + ->scalarNode('log_level') + ->info('The level of log message. Null to let Symfony decide.') + ->validate() + ->ifTrue(function ($v) use ($logLevels) { return !\in_array($v, $logLevels); }) + ->thenInvalid(sprintf('The log level is not valid. Pick one among "%s".', implode('", "', $logLevels))) + ->end() + ->defaultNull() + ->end() + ->scalarNode('status_code') + ->info('The status code of the response. Null to let Symfony decide.') + ->validate() + ->ifTrue(function ($v) { return $v < 100 || $v > 599; }) + ->thenInvalid('The status code is not valid. Pick a value between 100 and 599.') + ->end() + ->defaultNull() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('lock') ->info('Lock configuration') - ->{!class_exists(FullStack::class) && class_exists(Lock::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/lock', Lock::class)}() ->beforeNormalization() ->ifString()->then(function ($v) { return ['enabled' => true, 'resources' => $v]; }) ->end() @@ -947,28 +1274,29 @@ class Configuration implements ConfigurationInterface ->fixXmlConfig('resource') ->children() ->arrayNode('resources') + ->normalizeKeys(false) + ->useAttributeAsKey('name') ->requiresAtLeastOneElement() ->defaultValue(['default' => [class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphore' : 'flock']]) ->beforeNormalization() ->ifString()->then(function ($v) { return ['default' => $v]; }) ->end() ->beforeNormalization() - ->ifTrue(function ($v) { return \is_array($v) && array_keys($v) === range(0, \count($v) - 1); }) + ->ifTrue(function ($v) { return \is_array($v) && array_is_list($v); }) ->then(function ($v) { $resources = []; foreach ($v as $resource) { - $resources = array_merge_recursive( - $resources, - \is_array($resource) && isset($resource['name']) - ? [$resource['name'] => $resource['value']] - : ['default' => $resource] - ); + $resources[] = \is_array($resource) && isset($resource['name']) + ? [$resource['name'] => $resource['value']] + : ['default' => $resource] + ; } - return $resources; + return array_merge_recursive([], ...$resources); }) ->end() ->prototype('array') + ->performNoDeepMerging() ->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end() ->prototype('scalar')->end() ->end() @@ -979,13 +1307,764 @@ class Configuration implements ConfigurationInterface ; } - private function addWebLinkSection(ArrayNodeDefinition $rootNode) + private function addWebLinkSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('web_link') ->info('web links configuration') - ->{!class_exists(FullStack::class) && class_exists(HttpHeaderSerializer::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/weblink', HttpHeaderSerializer::class)}() + ->end() + ->end() + ; + } + + private function addMessengerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + { + $rootNode + ->children() + ->arrayNode('messenger') + ->info('Messenger configuration') + ->{$enableIfStandalone('symfony/messenger', MessageBusInterface::class)}() + ->fixXmlConfig('transport') + ->fixXmlConfig('bus', 'buses') + ->validate() + ->ifTrue(function ($v) { return isset($v['buses']) && \count($v['buses']) > 1 && null === $v['default_bus']; }) + ->thenInvalid('You must specify the "default_bus" if you define more than one bus.') + ->end() + ->validate() + ->ifTrue(static function ($v): bool { return isset($v['buses']) && null !== $v['default_bus'] && !isset($v['buses'][$v['default_bus']]); }) + ->then(static function (array $v): void { throw new InvalidConfigurationException(sprintf('The specified default bus "%s" is not configured. Available buses are "%s".', $v['default_bus'], implode('", "', array_keys($v['buses'])))); }) + ->end() + ->children() + ->arrayNode('routing') + ->normalizeKeys(false) + ->useAttributeAsKey('message_class') + ->beforeNormalization() + ->always() + ->then(function ($config) { + if (!\is_array($config)) { + return []; + } + // If XML config with only one routing attribute + if (2 === \count($config) && isset($config['message-class']) && isset($config['sender'])) { + $config = [0 => $config]; + } + + $newConfig = []; + foreach ($config as $k => $v) { + if (!\is_int($k)) { + $newConfig[$k] = [ + 'senders' => $v['senders'] ?? (\is_array($v) ? array_values($v) : [$v]), + ]; + } else { + $newConfig[$v['message-class']]['senders'] = array_map( + function ($a) { + return \is_string($a) ? $a : $a['service']; + }, + array_values($v['sender']) + ); + } + } + + return $newConfig; + }) + ->end() + ->prototype('array') + ->performNoDeepMerging() + ->children() + ->arrayNode('senders') + ->requiresAtLeastOneElement() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->end() + ->arrayNode('serializer') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('default_serializer') + ->defaultValue('messenger.transport.native_php_serializer') + ->info('Service id to use as the default serializer for the transports.') + ->end() + ->arrayNode('symfony_serializer') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('format')->defaultValue('json')->info('Serialization format for the messenger.transport.symfony_serializer service (which is not the serializer used by default).')->end() + ->arrayNode('context') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->defaultValue([]) + ->info('Context array for the messenger.transport.symfony_serializer service (which is not the serializer used by default).') + ->prototype('variable')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->arrayNode('transports') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->arrayPrototype() + ->beforeNormalization() + ->ifString() + ->then(function (string $dsn) { + return ['dsn' => $dsn]; + }) + ->end() + ->fixXmlConfig('option') + ->children() + ->scalarNode('dsn')->end() + ->scalarNode('serializer')->defaultNull()->info('Service id of a custom serializer to use.')->end() + ->arrayNode('options') + ->normalizeKeys(false) + ->defaultValue([]) + ->prototype('variable') + ->end() + ->end() + ->scalarNode('failure_transport') + ->defaultNull() + ->info('Transport name to send failed messages to (after all retries have failed).') + ->end() + ->arrayNode('retry_strategy') + ->addDefaultsIfNotSet() + ->beforeNormalization() + ->always(function ($v) { + if (isset($v['service']) && (isset($v['max_retries']) || isset($v['delay']) || isset($v['multiplier']) || isset($v['max_delay']))) { + throw new \InvalidArgumentException('The "service" cannot be used along with the other "retry_strategy" options.'); + } + + return $v; + }) + ->end() + ->children() + ->scalarNode('service')->defaultNull()->info('Service id to override the retry strategy entirely')->end() + ->integerNode('max_retries')->defaultValue(3)->min(0)->end() + ->integerNode('delay')->defaultValue(1000)->min(0)->info('Time in ms to delay (or the initial value when multiplier is used)')->end() + ->floatNode('multiplier')->defaultValue(2)->min(1)->info('If greater than 1, delay will grow exponentially for each retry: this delay = (delay * (multiple ^ retries))')->end() + ->integerNode('max_delay')->defaultValue(0)->min(0)->info('Max time in ms that a retry should ever be delayed (0 = infinite)')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->scalarNode('failure_transport') + ->defaultNull() + ->info('Transport name to send failed messages to (after all retries have failed).') + ->end() + ->booleanNode('reset_on_message') + ->defaultNull() + ->info('Reset container services after each message.') + ->end() + ->scalarNode('default_bus')->defaultNull()->end() + ->arrayNode('buses') + ->defaultValue(['messenger.bus.default' => ['default_middleware' => true, 'middleware' => []]]) + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->arrayPrototype() + ->addDefaultsIfNotSet() + ->children() + ->enumNode('default_middleware') + ->values([true, false, 'allow_no_handlers']) + ->defaultTrue() + ->end() + ->arrayNode('middleware') + ->performNoDeepMerging() + ->beforeNormalization() + ->ifTrue(function ($v) { return \is_string($v) || (\is_array($v) && !\is_int(key($v))); }) + ->then(function ($v) { return [$v]; }) + ->end() + ->defaultValue([]) + ->arrayPrototype() + ->beforeNormalization() + ->always() + ->then(function ($middleware): array { + if (!\is_array($middleware)) { + return ['id' => $middleware]; + } + if (isset($middleware['id'])) { + return $middleware; + } + if (1 < \count($middleware)) { + throw new \InvalidArgumentException('Invalid middleware at path "framework.messenger": a map with a single factory id as key and its arguments as value was expected, '.json_encode($middleware).' given.'); + } + + return [ + 'id' => key($middleware), + 'arguments' => current($middleware), + ]; + }) + ->end() + ->fixXmlConfig('argument') + ->children() + ->scalarNode('id')->isRequired()->cannotBeEmpty()->end() + ->arrayNode('arguments') + ->normalizeKeys(false) + ->defaultValue([]) + ->prototype('variable') + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addRobotsIndexSection(ArrayNodeDefinition $rootNode) + { + $rootNode + ->children() + ->booleanNode('disallow_search_engine_index') + ->info('Enabled by default when debug is enabled.') + ->defaultValue($this->debug) + ->treatNullLike($this->debug) + ->end() + ->end() + ; + } + + private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + { + $rootNode + ->children() + ->arrayNode('http_client') + ->info('HTTP Client configuration') + ->{$enableIfStandalone('symfony/http-client', HttpClient::class)}() + ->fixXmlConfig('scoped_client') + ->beforeNormalization() + ->always(function ($config) { + if (empty($config['scoped_clients']) || !\is_array($config['default_options']['retry_failed'] ?? null)) { + return $config; + } + + foreach ($config['scoped_clients'] as &$scopedConfig) { + if (!isset($scopedConfig['retry_failed']) || true === $scopedConfig['retry_failed']) { + $scopedConfig['retry_failed'] = $config['default_options']['retry_failed']; + continue; + } + if (\is_array($scopedConfig['retry_failed'])) { + $scopedConfig['retry_failed'] = $scopedConfig['retry_failed'] + $config['default_options']['retry_failed']; + } + } + + return $config; + }) + ->end() + ->children() + ->integerNode('max_host_connections') + ->info('The maximum number of connections to a single host.') + ->end() + ->arrayNode('default_options') + ->fixXmlConfig('header') + ->children() + ->arrayNode('headers') + ->info('Associative array: header => value(s).') + ->useAttributeAsKey('name') + ->normalizeKeys(false) + ->variablePrototype()->end() + ->end() + ->integerNode('max_redirects') + ->info('The maximum number of redirects to follow.') + ->end() + ->scalarNode('http_version') + ->info('The default HTTP version, typically 1.1 or 2.0, leave to null for the best version.') + ->end() + ->arrayNode('resolve') + ->info('Associative array: domain => IP.') + ->useAttributeAsKey('host') + ->beforeNormalization() + ->always(function ($config) { + if (!\is_array($config)) { + return []; + } + if (!isset($config['host'], $config['value']) || \count($config) > 2) { + return $config; + } + + return [$config['host'] => $config['value']]; + }) + ->end() + ->normalizeKeys(false) + ->scalarPrototype()->end() + ->end() + ->scalarNode('proxy') + ->info('The URL of the proxy to pass requests through or null for automatic detection.') + ->end() + ->scalarNode('no_proxy') + ->info('A comma separated list of hosts that do not require a proxy to be reached.') + ->end() + ->floatNode('timeout') + ->info('The idle timeout, defaults to the "default_socket_timeout" ini parameter.') + ->end() + ->floatNode('max_duration') + ->info('The maximum execution time for the request+response as a whole.') + ->end() + ->scalarNode('bindto') + ->info('A network interface name, IP address, a host name or a UNIX socket to bind to.') + ->end() + ->booleanNode('verify_peer') + ->info('Indicates if the peer should be verified in an SSL/TLS context.') + ->end() + ->booleanNode('verify_host') + ->info('Indicates if the host should exist as a certificate common name.') + ->end() + ->scalarNode('cafile') + ->info('A certificate authority file.') + ->end() + ->scalarNode('capath') + ->info('A directory that contains multiple certificate authority files.') + ->end() + ->scalarNode('local_cert') + ->info('A PEM formatted certificate file.') + ->end() + ->scalarNode('local_pk') + ->info('A private key file.') + ->end() + ->scalarNode('passphrase') + ->info('The passphrase used to encrypt the "local_pk" file.') + ->end() + ->scalarNode('ciphers') + ->info('A list of SSL/TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...)') + ->end() + ->arrayNode('peer_fingerprint') + ->info('Associative array: hashing algorithm => hash(es).') + ->normalizeKeys(false) + ->children() + ->variableNode('sha1')->end() + ->variableNode('pin-sha256')->end() + ->variableNode('md5')->end() + ->end() + ->end() + ->append($this->addHttpClientRetrySection()) + ->end() + ->end() + ->scalarNode('mock_response_factory') + ->info('The id of the service that should generate mock responses. It should be either an invokable or an iterable.') + ->end() + ->arrayNode('scoped_clients') + ->useAttributeAsKey('name') + ->normalizeKeys(false) + ->arrayPrototype() + ->fixXmlConfig('header') + ->beforeNormalization() + ->always() + ->then(function ($config) { + if (!class_exists(HttpClient::class)) { + throw new LogicException('HttpClient support cannot be enabled as the component is not installed. Try running "composer require symfony/http-client".'); + } + + return \is_array($config) ? $config : ['base_uri' => $config]; + }) + ->end() + ->validate() + ->ifTrue(function ($v) { return !isset($v['scope']) && !isset($v['base_uri']); }) + ->thenInvalid('Either "scope" or "base_uri" should be defined.') + ->end() + ->validate() + ->ifTrue(function ($v) { return !empty($v['query']) && !isset($v['base_uri']); }) + ->thenInvalid('"query" applies to "base_uri" but no base URI is defined.') + ->end() + ->children() + ->scalarNode('scope') + ->info('The regular expression that the request URL must match before adding the other options. When none is provided, the base URI is used instead.') + ->cannotBeEmpty() + ->end() + ->scalarNode('base_uri') + ->info('The URI to resolve relative URLs, following rules in RFC 3985, section 2.') + ->cannotBeEmpty() + ->end() + ->scalarNode('auth_basic') + ->info('An HTTP Basic authentication "username:password".') + ->end() + ->scalarNode('auth_bearer') + ->info('A token enabling HTTP Bearer authorization.') + ->end() + ->scalarNode('auth_ntlm') + ->info('A "username:password" pair to use Microsoft NTLM authentication (requires the cURL extension).') + ->end() + ->arrayNode('query') + ->info('Associative array of query string values merged with the base URI.') + ->useAttributeAsKey('key') + ->beforeNormalization() + ->always(function ($config) { + if (!\is_array($config)) { + return []; + } + if (!isset($config['key'], $config['value']) || \count($config) > 2) { + return $config; + } + + return [$config['key'] => $config['value']]; + }) + ->end() + ->normalizeKeys(false) + ->scalarPrototype()->end() + ->end() + ->arrayNode('headers') + ->info('Associative array: header => value(s).') + ->useAttributeAsKey('name') + ->normalizeKeys(false) + ->variablePrototype()->end() + ->end() + ->integerNode('max_redirects') + ->info('The maximum number of redirects to follow.') + ->end() + ->scalarNode('http_version') + ->info('The default HTTP version, typically 1.1 or 2.0, leave to null for the best version.') + ->end() + ->arrayNode('resolve') + ->info('Associative array: domain => IP.') + ->useAttributeAsKey('host') + ->beforeNormalization() + ->always(function ($config) { + if (!\is_array($config)) { + return []; + } + if (!isset($config['host'], $config['value']) || \count($config) > 2) { + return $config; + } + + return [$config['host'] => $config['value']]; + }) + ->end() + ->normalizeKeys(false) + ->scalarPrototype()->end() + ->end() + ->scalarNode('proxy') + ->info('The URL of the proxy to pass requests through or null for automatic detection.') + ->end() + ->scalarNode('no_proxy') + ->info('A comma separated list of hosts that do not require a proxy to be reached.') + ->end() + ->floatNode('timeout') + ->info('The idle timeout, defaults to the "default_socket_timeout" ini parameter.') + ->end() + ->floatNode('max_duration') + ->info('The maximum execution time for the request+response as a whole.') + ->end() + ->scalarNode('bindto') + ->info('A network interface name, IP address, a host name or a UNIX socket to bind to.') + ->end() + ->booleanNode('verify_peer') + ->info('Indicates if the peer should be verified in an SSL/TLS context.') + ->end() + ->booleanNode('verify_host') + ->info('Indicates if the host should exist as a certificate common name.') + ->end() + ->scalarNode('cafile') + ->info('A certificate authority file.') + ->end() + ->scalarNode('capath') + ->info('A directory that contains multiple certificate authority files.') + ->end() + ->scalarNode('local_cert') + ->info('A PEM formatted certificate file.') + ->end() + ->scalarNode('local_pk') + ->info('A private key file.') + ->end() + ->scalarNode('passphrase') + ->info('The passphrase used to encrypt the "local_pk" file.') + ->end() + ->scalarNode('ciphers') + ->info('A list of SSL/TLS ciphers separated by colons, commas or spaces (e.g. "RC3-SHA:TLS13-AES-128-GCM-SHA256"...)') + ->end() + ->arrayNode('peer_fingerprint') + ->info('Associative array: hashing algorithm => hash(es).') + ->normalizeKeys(false) + ->children() + ->variableNode('sha1')->end() + ->variableNode('pin-sha256')->end() + ->variableNode('md5')->end() + ->end() + ->end() + ->append($this->addHttpClientRetrySection()) + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addHttpClientRetrySection() + { + $root = new NodeBuilder(); + + return $root + ->arrayNode('retry_failed') + ->fixXmlConfig('http_code') + ->canBeEnabled() + ->addDefaultsIfNotSet() + ->beforeNormalization() + ->always(function ($v) { + if (isset($v['retry_strategy']) && (isset($v['http_codes']) || isset($v['delay']) || isset($v['multiplier']) || isset($v['max_delay']) || isset($v['jitter']))) { + throw new \InvalidArgumentException('The "retry_strategy" option cannot be used along with the "http_codes", "delay", "multiplier", "max_delay" or "jitter" options.'); + } + + return $v; + }) + ->end() + ->children() + ->scalarNode('retry_strategy')->defaultNull()->info('service id to override the retry strategy')->end() + ->arrayNode('http_codes') + ->performNoDeepMerging() + ->beforeNormalization() + ->ifArray() + ->then(static function ($v) { + $list = []; + foreach ($v as $key => $val) { + if (is_numeric($val)) { + $list[] = ['code' => $val]; + } elseif (\is_array($val)) { + if (isset($val['code']) || isset($val['methods'])) { + $list[] = $val; + } else { + $list[] = ['code' => $key, 'methods' => $val]; + } + } elseif (true === $val || null === $val) { + $list[] = ['code' => $key]; + } + } + + return $list; + }) + ->end() + ->useAttributeAsKey('code') + ->arrayPrototype() + ->fixXmlConfig('method') + ->children() + ->integerNode('code')->end() + ->arrayNode('methods') + ->beforeNormalization() + ->ifArray() + ->then(function ($v) { + return array_map('strtoupper', $v); + }) + ->end() + ->prototype('scalar')->end() + ->info('A list of HTTP methods that triggers a retry for this status code. When empty, all methods are retried') + ->end() + ->end() + ->end() + ->info('A list of HTTP status code that triggers a retry') + ->end() + ->integerNode('max_retries')->defaultValue(3)->min(0)->end() + ->integerNode('delay')->defaultValue(1000)->min(0)->info('Time in ms to delay (or the initial value when multiplier is used)')->end() + ->floatNode('multiplier')->defaultValue(2)->min(1)->info('If greater than 1, delay will grow exponentially for each retry: delay * (multiple ^ retries)')->end() + ->integerNode('max_delay')->defaultValue(0)->min(0)->info('Max time in ms that a retry should ever be delayed (0 = infinite)')->end() + ->floatNode('jitter')->defaultValue(0.1)->min(0)->max(1)->info('Randomness in percent (between 0 and 1) to apply to the delay')->end() + ->end() + ; + } + + private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + { + $rootNode + ->children() + ->arrayNode('mailer') + ->info('Mailer configuration') + ->{$enableIfStandalone('symfony/mailer', Mailer::class)}() + ->validate() + ->ifTrue(function ($v) { return isset($v['dsn']) && \count($v['transports']); }) + ->thenInvalid('"dsn" and "transports" cannot be used together.') + ->end() + ->fixXmlConfig('transport') + ->fixXmlConfig('header') + ->children() + ->scalarNode('message_bus')->defaultNull()->info('The message bus to use. Defaults to the default bus if the Messenger component is installed.')->end() + ->scalarNode('dsn')->defaultNull()->end() + ->arrayNode('transports') + ->useAttributeAsKey('name') + ->prototype('scalar')->end() + ->end() + ->arrayNode('envelope') + ->info('Mailer Envelope configuration') + ->children() + ->scalarNode('sender')->end() + ->arrayNode('recipients') + ->performNoDeepMerging() + ->beforeNormalization() + ->ifArray() + ->then(function ($v) { + return array_filter(array_values($v)); + }) + ->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->arrayNode('headers') + ->normalizeKeys(false) + ->useAttributeAsKey('name') + ->prototype('array') + ->normalizeKeys(false) + ->beforeNormalization() + ->ifTrue(function ($v) { return !\is_array($v) || array_keys($v) !== ['value']; }) + ->then(function ($v) { return ['value' => $v]; }) + ->end() + ->children() + ->variableNode('value')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addNotifierSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + { + $rootNode + ->children() + ->arrayNode('notifier') + ->info('Notifier configuration') + ->{$enableIfStandalone('symfony/notifier', Notifier::class)}() + ->fixXmlConfig('chatter_transport') + ->children() + ->arrayNode('chatter_transports') + ->useAttributeAsKey('name') + ->prototype('scalar')->end() + ->end() + ->end() + ->fixXmlConfig('texter_transport') + ->children() + ->arrayNode('texter_transports') + ->useAttributeAsKey('name') + ->prototype('scalar')->end() + ->end() + ->end() + ->children() + ->booleanNode('notification_on_failed_messages')->defaultFalse()->end() + ->end() + ->children() + ->arrayNode('channel_policy') + ->useAttributeAsKey('name') + ->prototype('array') + ->beforeNormalization()->ifString()->then(function (string $v) { return [$v]; })->end() + ->prototype('scalar')->end() + ->end() + ->end() + ->end() + ->fixXmlConfig('admin_recipient') + ->children() + ->arrayNode('admin_recipients') + ->prototype('array') + ->children() + ->scalarNode('email')->cannotBeEmpty()->end() + ->scalarNode('phone')->defaultValue('')->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + { + $rootNode + ->children() + ->arrayNode('rate_limiter') + ->info('Rate limiter configuration') + ->{$enableIfStandalone('symfony/rate-limiter', TokenBucketLimiter::class)}() + ->fixXmlConfig('limiter') + ->beforeNormalization() + ->ifTrue(function ($v) { return \is_array($v) && !isset($v['limiters']) && !isset($v['limiter']); }) + ->then(function (array $v) { + $newV = [ + 'enabled' => $v['enabled'] ?? true, + ]; + unset($v['enabled']); + + $newV['limiters'] = $v; + + return $newV; + }) + ->end() + ->children() + ->arrayNode('limiters') + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->scalarNode('lock_factory') + ->info('The service ID of the lock factory used by this limiter (or null to disable locking)') + ->defaultValue('lock.factory') + ->end() + ->scalarNode('cache_pool') + ->info('The cache pool to use for storing the current limiter state') + ->defaultValue('cache.rate_limiter') + ->end() + ->scalarNode('storage_service') + ->info('The service ID of a custom storage implementation, this precedes any configured "cache_pool"') + ->defaultNull() + ->end() + ->enumNode('policy') + ->info('The algorithm to be used by this limiter') + ->isRequired() + ->values(['fixed_window', 'token_bucket', 'sliding_window', 'no_limit']) + ->end() + ->integerNode('limit') + ->info('The maximum allowed hits in a fixed interval or burst') + ->isRequired() + ->end() + ->scalarNode('interval') + ->info('Configures the fixed interval if "policy" is set to "fixed_window" or "sliding_window". The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent).') + ->end() + ->arrayNode('rate') + ->info('Configures the fill rate if "policy" is set to "token_bucket"') + ->children() + ->scalarNode('interval') + ->info('Configures the rate interval. The value must be a number followed by "second", "minute", "hour", "day", "week" or "month" (or their plural equivalent).') + ->end() + ->integerNode('amount')->info('Amount of tokens to add each interval')->defaultValue(1)->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } + + private function addUidSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) + { + $rootNode + ->children() + ->arrayNode('uid') + ->info('Uid configuration') + ->{$enableIfStandalone('symfony/uid', UuidFactory::class)}() + ->addDefaultsIfNotSet() + ->children() + ->enumNode('default_uuid_version') + ->defaultValue(6) + ->values([6, 4, 1]) + ->end() + ->enumNode('name_based_uuid_version') + ->defaultValue(5) + ->values([5, 3]) + ->end() + ->scalarNode('name_based_uuid_namespace') + ->cannotBeEmpty() + ->end() + ->enumNode('time_based_uuid_version') + ->defaultValue(6) + ->values([6, 1]) + ->end() + ->scalarNode('time_based_uuid_node') + ->cannotBeEmpty() + ->end() + ->end() ->end() ->end() ; diff --git a/lib/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php b/lib/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php index 2e3c465a35..d72ef5f6da 100644 --- a/lib/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php +++ b/lib/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php @@ -11,17 +11,36 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; +use Composer\InstalledVersions; use Doctrine\Common\Annotations\AnnotationRegistry; use Doctrine\Common\Annotations\Reader; +use Http\Client\HttpClient; +use phpDocumentor\Reflection\DocBlockFactoryInterface; +use phpDocumentor\Reflection\Types\ContextFactory; +use PHPStan\PhpDocParser\Parser\PhpDocParser; +use Psr\Cache\CacheItemPoolInterface; +use Psr\Container\ContainerInterface as PsrContainerInterface; +use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; +use Psr\Http\Client\ClientInterface; +use Psr\Log\LoggerAwareInterface; use Symfony\Bridge\Monolog\Processor\DebugProcessor; +use Symfony\Bridge\Twig\Extension\CsrfExtension; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader; +use Symfony\Bundle\FrameworkBundle\Routing\RouteLoaderInterface; use Symfony\Bundle\FullStack; -use Symfony\Component\BrowserKit\Client; -use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Bundle\MercureBundle\MercureBundle; +use Symfony\Component\Asset\PackageInterface; +use Symfony\Component\BrowserKit\AbstractBrowser; use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\ChainAdapter; +use Symfony\Component\Cache\Adapter\DoctrineAdapter; +use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter; +use Symfony\Component\Cache\Adapter\TagAwareAdapter; +use Symfony\Component\Cache\DependencyInjection\CachePoolPass; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface; use Symfony\Component\Cache\ResettableInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Loader\LoaderInterface; @@ -32,66 +51,175 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\EnvVarLoaderInterface; use Symfony\Component\DependencyInjection\EnvVarProcessorInterface; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\LogicException; -use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; -use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher; -use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\Dotenv\Command\DebugCommand; +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\Finder\Finder; +use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator; +use Symfony\Component\Form\Form; +use Symfony\Component\Form\FormTypeExtensionInterface; use Symfony\Component\Form\FormTypeGuesserInterface; use Symfony\Component\Form\FormTypeInterface; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Retry\GenericRetryStrategy; +use Symfony\Component\HttpClient\RetryableHttpClient; +use Symfony\Component\HttpClient\ScopingHttpClient; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; +use Symfony\Component\HttpKernel\Attribute\AsController; use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; use Symfony\Component\HttpKernel\DependencyInjection\Extension; -use Symfony\Component\Lock\Factory; use Symfony\Component\Lock\Lock; +use Symfony\Component\Lock\LockFactory; use Symfony\Component\Lock\LockInterface; +use Symfony\Component\Lock\PersistingStoreInterface; use Symfony\Component\Lock\Store\StoreFactory; use Symfony\Component\Lock\StoreInterface; +use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory; +use Symfony\Component\Mailer\Bridge\Google\Transport\GmailTransportFactory; +use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillTransportFactory; +use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory; +use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory; +use Symfony\Component\Mailer\Bridge\OhMySmtp\Transport\OhMySmtpTransportFactory; +use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; +use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory; +use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory; +use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Mercure\HubRegistry; +use Symfony\Component\Messenger\Attribute\AsMessageHandler; +use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory; +use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory; +use Symfony\Component\Messenger\Bridge\Beanstalkd\Transport\BeanstalkdTransportFactory; +use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransportFactory; +use Symfony\Component\Messenger\Handler\BatchHandlerInterface; +use Symfony\Component\Messenger\Handler\MessageHandlerInterface; +use Symfony\Component\Messenger\MessageBus; +use Symfony\Component\Messenger\MessageBusInterface; +use Symfony\Component\Messenger\Middleware\RouterContextMiddleware; +use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; +use Symfony\Component\Messenger\Transport\TransportFactoryInterface; +use Symfony\Component\Messenger\Transport\TransportInterface; +use Symfony\Component\Mime\Header\Headers; +use Symfony\Component\Mime\MimeTypeGuesserInterface; +use Symfony\Component\Mime\MimeTypes; +use Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory; +use Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsTransportFactory; +use Symfony\Component\Notifier\Bridge\Clickatell\ClickatellTransportFactory; +use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; +use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory; +use Symfony\Component\Notifier\Bridge\Expo\ExpoTransportFactory; +use Symfony\Component\Notifier\Bridge\FakeChat\FakeChatTransportFactory; +use Symfony\Component\Notifier\Bridge\FakeSms\FakeSmsTransportFactory; +use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory; +use Symfony\Component\Notifier\Bridge\FreeMobile\FreeMobileTransportFactory; +use Symfony\Component\Notifier\Bridge\GatewayApi\GatewayApiTransportFactory; +use Symfony\Component\Notifier\Bridge\Gitter\GitterTransportFactory; +use Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory; +use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; +use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; +use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; +use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; +use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory as MailjetNotifierTransportFactory; +use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; +use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory; +use Symfony\Component\Notifier\Bridge\MessageBird\MessageBirdTransport; +use Symfony\Component\Notifier\Bridge\MessageMedia\MessageMediaTransportFactory; +use Symfony\Component\Notifier\Bridge\MicrosoftTeams\MicrosoftTeamsTransportFactory; +use Symfony\Component\Notifier\Bridge\Mobyt\MobytTransportFactory; +use Symfony\Component\Notifier\Bridge\Nexmo\NexmoTransportFactory; +use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory; +use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; +use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; +use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; +use Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransportFactory as SendinblueNotifierTransportFactory; +use Symfony\Component\Notifier\Bridge\Sinch\SinchTransportFactory; +use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory; +use Symfony\Component\Notifier\Bridge\Sms77\Sms77TransportFactory; +use Symfony\Component\Notifier\Bridge\Smsapi\SmsapiTransportFactory; +use Symfony\Component\Notifier\Bridge\SmsBiuras\SmsBiurasTransportFactory; +use Symfony\Component\Notifier\Bridge\Smsc\SmscTransportFactory; +use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory; +use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; +use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; +use Symfony\Component\Notifier\Bridge\TurboSms\TurboSmsTransport; +use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; +use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; +use Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory; +use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory; +use Symfony\Component\Notifier\Notifier; +use Symfony\Component\Notifier\Recipient\Recipient; +use Symfony\Component\Notifier\Transport\TransportFactoryInterface as NotifierTransportFactoryInterface; use Symfony\Component\PropertyAccess\PropertyAccessor; +use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface; use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; +use Symfony\Component\RateLimiter\LimiterInterface; +use Symfony\Component\RateLimiter\RateLimiterFactory; +use Symfony\Component\RateLimiter\Storage\CacheStorage; use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader; use Symfony\Component\Routing\Loader\AnnotationFileLoader; +use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Serializer\Encoder\DecoderInterface; use Symfony\Component\Serializer\Encoder\EncoderInterface; -use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory; -use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer; use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\String\LazyString; +use Symfony\Component\String\Slugger\SluggerInterface; +use Symfony\Component\Translation\Bridge\Crowdin\CrowdinProviderFactory; +use Symfony\Component\Translation\Bridge\Loco\LocoProviderFactory; +use Symfony\Component\Translation\Bridge\Lokalise\LokaliseProviderFactory; use Symfony\Component\Translation\Command\XliffLintCommand as BaseXliffLintCommand; +use Symfony\Component\Translation\PseudoLocalizationTranslator; use Symfony\Component\Translation\Translator; +use Symfony\Component\Uid\Factory\UuidFactory; +use Symfony\Component\Uid\UuidV4; use Symfony\Component\Validator\ConstraintValidatorInterface; +use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; use Symfony\Component\Validator\ObjectInitializerInterface; +use Symfony\Component\Validator\Validation; use Symfony\Component\WebLink\HttpHeaderSerializer; use Symfony\Component\Workflow; +use Symfony\Component\Workflow\WorkflowInterface; use Symfony\Component\Yaml\Command\LintCommand as BaseYamlLintCommand; use Symfony\Component\Yaml\Yaml; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\CallbackInterface; +use Symfony\Contracts\Cache\TagAwareCacheInterface; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\Service\ResetInterface; +use Symfony\Contracts\Service\ServiceSubscriberInterface; +use Symfony\Contracts\Translation\LocaleAwareInterface; /** - * FrameworkExtension. - * - * @author Fabien Potencier - * @author Jeremy Mikola - * @author Kévin Dunglas - * @author Grégoire Pineau + * Process the configuration and prepare the dependency injection container with + * parameters and services. */ class FrameworkExtension extends Extension { @@ -100,11 +228,12 @@ class FrameworkExtension extends Extension private $sessionConfigEnabled = false; private $annotationsConfigEnabled = false; private $validatorConfigEnabled = false; - - /** - * @var string|null - */ - private $kernelRootHash; + private $messengerConfigEnabled = false; + private $mailerConfigEnabled = false; + private $httpClientConfigEnabled = false; + private $notifierConfigEnabled = false; + private $propertyAccessConfigEnabled = false; + private static $lockConfigEnabled = false; /** * Responds to the app.config configuration parameter. @@ -113,44 +242,25 @@ class FrameworkExtension extends Extension */ public function load(array $configs, ContainerBuilder $container) { - $loader = new XmlFileLoader($container, new FileLocator(\dirname(__DIR__).'/Resources/config')); - - $loader->load('web.xml'); - $loader->load('services.xml'); - - $container->getDefinition('kernel.class_cache.cache_warmer')->setPrivate(true); - $container->getDefinition('uri_signer')->setPrivate(true); - $container->getDefinition('config_cache_factory')->setPrivate(true); - $container->getDefinition('response_listener')->setPrivate(true); - $container->getDefinition('file_locator')->setPrivate(true); - $container->getDefinition('streamed_response_listener')->setPrivate(true); - $container->getDefinition('locale_listener')->setPrivate(true); - $container->getDefinition('validate_request_listener')->setPrivate(true); - - // forward compatibility with Symfony 4.0 where the ContainerAwareEventDispatcher class is removed - if (!class_exists(ContainerAwareEventDispatcher::class)) { - $definition = $container->getDefinition('event_dispatcher'); - $definition->setClass(EventDispatcher::class); - $definition->setArguments([]); + if (!class_exists(InstalledVersions::class)) { + trigger_deprecation('symfony/framework-bundle', '5.4', 'Configuring Symfony without the Composer Runtime API is deprecated. Consider upgrading to Composer 2.1 or later.'); } - if (\PHP_VERSION_ID < 70000) { - $definition = $container->getDefinition('kernel.class_cache.cache_warmer'); - $definition->addTag('kernel.cache_warmer'); - // Ignore deprecation for PHP versions below 7.0 - $definition->setDeprecated(false); + $loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/Resources/config')); + + $loader->load('web.php'); + $loader->load('services.php'); + $loader->load('fragment_renderer.php'); + $loader->load('error_renderer.php'); + + if (ContainerBuilder::willBeAvailable('psr/event-dispatcher', PsrEventDispatcherInterface::class, ['symfony/framework-bundle'], true)) { + $container->setAlias(PsrEventDispatcherInterface::class, 'event_dispatcher'); } - $loader->load('fragment_renderer.xml'); + $container->registerAliasForArgument('parameter_bag', PsrContainerInterface::class); - $container->getDefinition('fragment.handler')->setPrivate(true); - $container->getDefinition('fragment.renderer.inline')->setPrivate(true); - $container->getDefinition('fragment.renderer.hinclude')->setPrivate(true); - $container->getDefinition('fragment.renderer.esi')->setPrivate(true); - $container->getDefinition('fragment.renderer.ssi')->setPrivate(true); - - if (class_exists(Application::class)) { - $loader->load('console.xml'); + if ($this->hasConsole()) { + $loader->load('console.php'); if (!class_exists(BaseXliffLintCommand::class)) { $container->removeDefinition('console.command.xliff_lint'); @@ -158,19 +268,14 @@ class FrameworkExtension extends Extension if (!class_exists(BaseYamlLintCommand::class)) { $container->removeDefinition('console.command.yaml_lint'); } + + if (!class_exists(DebugCommand::class)) { + $container->removeDefinition('console.command.dotenv_debug'); + } } // Load Cache configuration first as it is used by other components - $loader->load('cache.xml'); - - $container->getDefinition('cache.adapter.system')->setPrivate(true); - $container->getDefinition('cache.adapter.apcu')->setPrivate(true); - $container->getDefinition('cache.adapter.doctrine')->setPrivate(true); - $container->getDefinition('cache.adapter.filesystem')->setPrivate(true); - $container->getDefinition('cache.adapter.psr6')->setPrivate(true); - $container->getDefinition('cache.adapter.redis')->setPrivate(true); - $container->getDefinition('cache.adapter.memcached')->setPrivate(true); - $container->getDefinition('cache.default_clearer')->setPrivate(true); + $loader->load('cache.php'); $configuration = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); @@ -182,12 +287,30 @@ class FrameworkExtension extends Extension // default in the Form and Validator component). If disabled, an identity // translator will be used and everything will still work as expected. if ($this->isConfigEnabled($container, $config['translator']) || $this->isConfigEnabled($container, $config['form']) || $this->isConfigEnabled($container, $config['validation'])) { - if (!class_exists('Symfony\Component\Translation\Translator') && $this->isConfigEnabled($container, $config['translator'])) { + if (!class_exists(Translator::class) && $this->isConfigEnabled($container, $config['translator'])) { throw new LogicException('Translation support cannot be enabled as the Translation component is not installed. Try running "composer require symfony/translation".'); } if (class_exists(Translator::class)) { - $loader->load('identity_translator.xml'); + $loader->load('identity_translator.php'); + } + } + + $container->getDefinition('locale_listener')->replaceArgument(3, $config['set_locale_from_accept_language']); + $container->getDefinition('response_listener')->replaceArgument(1, $config['set_content_language_from_locale']); + + // If the slugger is used but the String component is not available, we should throw an error + if (!ContainerBuilder::willBeAvailable('symfony/string', SluggerInterface::class, ['symfony/framework-bundle'], true)) { + $container->register('slugger', 'stdClass') + ->addError('You cannot use the "slugger" service since the String component is not installed. Try running "composer require symfony/string".'); + } else { + if (!ContainerBuilder::willBeAvailable('symfony/translation', LocaleAwareInterface::class, ['symfony/framework-bundle'], true)) { + $container->register('slugger', 'stdClass') + ->addError('You cannot use the "slugger" service since the Translation contracts are not installed. Try running "composer require symfony/translation".'); + } + + if (!\extension_loaded('intl') && !\defined('PHPUNIT_COMPOSER_INSTALL')) { + trigger_deprecation('', '', 'Please install the "intl" PHP extension for best performance.'); } } @@ -197,41 +320,107 @@ class FrameworkExtension extends Extension $container->setParameter('kernel.http_method_override', $config['http_method_override']); $container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']); - if ($config['trusted_proxies']) { - $container->setParameter('kernel.trusted_proxies', $config['trusted_proxies']); - } $container->setParameter('kernel.default_locale', $config['default_locale']); + $container->setParameter('kernel.enabled_locales', $config['enabled_locales']); + $container->setParameter('kernel.error_controller', $config['error_controller']); + + if (($config['trusted_proxies'] ?? false) && ($config['trusted_headers'] ?? false)) { + $container->setParameter('kernel.trusted_proxies', $config['trusted_proxies']); + $container->setParameter('kernel.trusted_headers', $this->resolveTrustedHeaders($config['trusted_headers'])); + } if (!$container->hasParameter('debug.file_link_format')) { - if (!$container->hasParameter('templating.helper.code.file_link_format')) { - $links = [ - 'textmate' => 'txmt://open?url=file://%%f&line=%%l', - 'macvim' => 'mvim://open?url=file://%%f&line=%%l', - 'emacs' => 'emacs://open?url=file://%%f&line=%%l', - 'sublime' => 'subl://open?url=file://%%f&line=%%l', - 'phpstorm' => 'phpstorm://open?file=%%f&line=%%l', - ]; - $ide = $config['ide']; - // mark any env vars found in the ide setting as used - $container->resolveEnvPlaceholders($ide); - - $container->setParameter('templating.helper.code.file_link_format', str_replace('%', '%%', ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format')) ?: (isset($links[$ide]) ? $links[$ide] : $ide)); - } - $container->setParameter('debug.file_link_format', '%templating.helper.code.file_link_format%'); + $container->setParameter('debug.file_link_format', $config['ide']); } if (!empty($config['test'])) { - $loader->load('test.xml'); + $loader->load('test.php'); - $container->getDefinition('test.client.history')->setPrivate(true); - $container->getDefinition('test.client.cookiejar')->setPrivate(true); - $container->getDefinition('test.session.listener')->setPrivate(true); - - if (!class_exists(Client::class)) { + if (!class_exists(AbstractBrowser::class)) { $container->removeDefinition('test.client'); } } + if ($this->isConfigEnabled($container, $config['request'])) { + $this->registerRequestConfiguration($config['request'], $container, $loader); + } + + if ($this->isConfigEnabled($container, $config['assets'])) { + if (!class_exists(\Symfony\Component\Asset\Package::class)) { + throw new LogicException('Asset support cannot be enabled as the Asset component is not installed. Try running "composer require symfony/asset".'); + } + + $this->registerAssetsConfiguration($config['assets'], $container, $loader); + } + + if ($this->httpClientConfigEnabled = $this->isConfigEnabled($container, $config['http_client'])) { + $this->registerHttpClientConfiguration($config['http_client'], $container, $loader, $config['profiler']); + } + + if ($this->mailerConfigEnabled = $this->isConfigEnabled($container, $config['mailer'])) { + $this->registerMailerConfiguration($config['mailer'], $container, $loader); + } + + $propertyInfoEnabled = $this->isConfigEnabled($container, $config['property_info']); + $this->registerHttpCacheConfiguration($config['http_cache'], $container, $config['http_method_override']); + $this->registerEsiConfiguration($config['esi'], $container, $loader); + $this->registerSsiConfiguration($config['ssi'], $container, $loader); + $this->registerFragmentsConfiguration($config['fragments'], $container, $loader); + $this->registerTranslatorConfiguration($config['translator'], $container, $loader, $config['default_locale'], $config['enabled_locales']); + $this->registerWorkflowConfiguration($config['workflows'], $container, $loader); + $this->registerDebugConfiguration($config['php_errors'], $container, $loader); + // @deprecated since Symfony 5.4, in 6.0 change to: + // $this->registerRouterConfiguration($config['router'], $container, $loader, $config['enabled_locales']); + $this->registerRouterConfiguration($config['router'], $container, $loader, $config['translator']['enabled_locales'] ?: $config['enabled_locales']); + $this->registerAnnotationsConfiguration($config['annotations'], $container, $loader); + $this->registerPropertyAccessConfiguration($config['property_access'], $container, $loader); + $this->registerSecretsConfiguration($config['secrets'], $container, $loader); + + $container->getDefinition('exception_listener')->replaceArgument(3, $config['exceptions']); + + if ($this->isConfigEnabled($container, $config['serializer'])) { + if (!class_exists(\Symfony\Component\Serializer\Serializer::class)) { + throw new LogicException('Serializer support cannot be enabled as the Serializer component is not installed. Try running "composer require symfony/serializer-pack".'); + } + + $this->registerSerializerConfiguration($config['serializer'], $container, $loader); + } + + if ($propertyInfoEnabled) { + $this->registerPropertyInfoConfiguration($container, $loader); + } + + if (self::$lockConfigEnabled = $this->isConfigEnabled($container, $config['lock'])) { + $this->registerLockConfiguration($config['lock'], $container, $loader); + } + + if ($this->isConfigEnabled($container, $config['rate_limiter'])) { + if (!interface_exists(LimiterInterface::class)) { + throw new LogicException('Rate limiter support cannot be enabled as the RateLimiter component is not installed. Try running "composer require symfony/rate-limiter".'); + } + + $this->registerRateLimiterConfiguration($config['rate_limiter'], $container, $loader); + } + + if ($this->isConfigEnabled($container, $config['web_link'])) { + if (!class_exists(HttpHeaderSerializer::class)) { + throw new LogicException('WebLink support cannot be enabled as the WebLink component is not installed. Try running "composer require symfony/weblink".'); + } + + $loader->load('web_link.php'); + } + + if ($this->isConfigEnabled($container, $config['uid'])) { + if (!class_exists(UuidFactory::class)) { + throw new LogicException('Uid support cannot be enabled as the Uid component is not installed. Try running "composer require symfony/uid".'); + } + + $this->registerUidConfiguration($config['uid'], $container, $loader); + } + + // register cache before session so both can share the connection services + $this->registerCacheConfiguration($config['cache'], $container); + if ($this->isConfigEnabled($container, $config['session'])) { if (!\extension_loaded('session')) { throw new LogicException('Session support cannot be enabled as the session extension is not installed. See https://php.net/session.installation for instructions.'); @@ -239,26 +428,31 @@ class FrameworkExtension extends Extension $this->sessionConfigEnabled = true; $this->registerSessionConfiguration($config['session'], $container, $loader); + if (!empty($config['test'])) { + // test listener will replace the existing session listener + // as we are aliasing to avoid duplicated registered events + $container->setAlias('session_listener', 'test.session.listener'); + } + } elseif (!empty($config['test'])) { + $container->removeDefinition('test.session.listener'); } - if ($this->isConfigEnabled($container, $config['request'])) { - $this->registerRequestConfiguration($config['request'], $container, $loader); - } - + // csrf depends on session being registered if (null === $config['csrf_protection']['enabled']) { - $config['csrf_protection']['enabled'] = $this->sessionConfigEnabled && !class_exists(FullStack::class) && interface_exists(CsrfTokenManagerInterface::class); + $config['csrf_protection']['enabled'] = $this->sessionConfigEnabled && !class_exists(FullStack::class) && ContainerBuilder::willBeAvailable('symfony/security-csrf', CsrfTokenManagerInterface::class, ['symfony/framework-bundle'], true); } $this->registerSecurityCsrfConfiguration($config['csrf_protection'], $container, $loader); + // form depends on csrf being registered if ($this->isConfigEnabled($container, $config['form'])) { - if (!class_exists('Symfony\Component\Form\Form')) { + if (!class_exists(Form::class)) { throw new LogicException('Form support cannot be enabled as the Form component is not installed. Try running "composer require symfony/form".'); } $this->formConfigEnabled = true; $this->registerFormConfiguration($config, $container, $loader); - if (class_exists('Symfony\Component\Validator\Validation')) { + if (ContainerBuilder::willBeAvailable('symfony/validator', Validation::class, ['symfony/framework-bundle', 'symfony/form'], true)) { $config['validation']['enabled'] = true; } else { $container->setParameter('validator.translation_domain', 'validators'); @@ -270,58 +464,50 @@ class FrameworkExtension extends Extension $container->removeDefinition('console.command.form_debug'); } - if ($this->isConfigEnabled($container, $config['assets'])) { - if (!class_exists('Symfony\Component\Asset\Package')) { - throw new LogicException('Asset support cannot be enabled as the Asset component is not installed. Try running "composer require symfony/asset".'); + // validation depends on form, annotations being registered + $this->registerValidationConfiguration($config['validation'], $container, $loader, $propertyInfoEnabled); + + // messenger depends on validation being registered + if ($this->messengerConfigEnabled = $this->isConfigEnabled($container, $config['messenger'])) { + $this->registerMessengerConfiguration($config['messenger'], $container, $loader, $config['validation']); + } else { + $container->removeDefinition('console.command.messenger_consume_messages'); + $container->removeDefinition('console.command.messenger_debug'); + $container->removeDefinition('console.command.messenger_stop_workers'); + $container->removeDefinition('console.command.messenger_setup_transports'); + $container->removeDefinition('console.command.messenger_failed_messages_retry'); + $container->removeDefinition('console.command.messenger_failed_messages_show'); + $container->removeDefinition('console.command.messenger_failed_messages_remove'); + $container->removeDefinition('cache.messenger.restart_workers_signal'); + + if ($container->hasDefinition('messenger.transport.amqp.factory') && !class_exists(AmqpTransportFactory::class)) { + if (class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class)) { + $container->getDefinition('messenger.transport.amqp.factory') + ->setClass(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class) + ->addTag('messenger.transport_factory'); + } else { + $container->removeDefinition('messenger.transport.amqp.factory'); + } } - $this->registerAssetsConfiguration($config['assets'], $container, $loader); - } - - if ($this->isConfigEnabled($container, $config['templating'])) { - if (!class_exists('Symfony\Component\Templating\PhpEngine')) { - throw new LogicException('Templating support cannot be enabled as the Templating component is not installed. Try running "composer require symfony/templating".'); + if ($container->hasDefinition('messenger.transport.redis.factory') && !class_exists(RedisTransportFactory::class)) { + if (class_exists(\Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class)) { + $container->getDefinition('messenger.transport.redis.factory') + ->setClass(\Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class) + ->addTag('messenger.transport_factory'); + } else { + $container->removeDefinition('messenger.transport.redis.factory'); + } } - - $this->registerTemplatingConfiguration($config['templating'], $container, $loader); } - $this->registerValidationConfiguration($config['validation'], $container, $loader); - $this->registerEsiConfiguration($config['esi'], $container, $loader); - $this->registerSsiConfiguration($config['ssi'], $container, $loader); - $this->registerFragmentsConfiguration($config['fragments'], $container, $loader); - $this->registerTranslatorConfiguration($config['translator'], $container, $loader); + // notifier depends on messenger, mailer being registered + if ($this->notifierConfigEnabled = $this->isConfigEnabled($container, $config['notifier'])) { + $this->registerNotifierConfiguration($config['notifier'], $container, $loader); + } + + // profiler depends on form, validation, translation, messenger, mailer, http-client, notifier being registered $this->registerProfilerConfiguration($config['profiler'], $container, $loader); - $this->registerCacheConfiguration($config['cache'], $container); - $this->registerWorkflowConfiguration($config['workflows'], $container, $loader); - $this->registerDebugConfiguration($config['php_errors'], $container, $loader); - $this->registerRouterConfiguration($config['router'], $container, $loader); - $this->registerAnnotationsConfiguration($config['annotations'], $container, $loader); - $this->registerPropertyAccessConfiguration($config['property_access'], $container, $loader); - - if ($this->isConfigEnabled($container, $config['serializer'])) { - if (!class_exists('Symfony\Component\Serializer\Serializer')) { - throw new LogicException('Serializer support cannot be enabled as the Serializer component is not installed. Try running "composer require symfony/serializer-pack".'); - } - - $this->registerSerializerConfiguration($config['serializer'], $container, $loader); - } - - if ($this->isConfigEnabled($container, $config['property_info'])) { - $this->registerPropertyInfoConfiguration($container, $loader); - } - - if ($this->isConfigEnabled($container, $config['lock'])) { - $this->registerLockConfiguration($config['lock'], $container, $loader); - } - - if ($this->isConfigEnabled($container, $config['web_link'])) { - if (!class_exists(HttpHeaderSerializer::class)) { - throw new LogicException('WebLink support cannot be enabled as the WebLink component is not installed. Try running "composer require symfony/weblink".'); - } - - $loader->load('web_link.xml'); - } $this->addAnnotatedClassesToCompile([ '**\\Controller\\', @@ -329,37 +515,58 @@ class FrameworkExtension extends Extension // Added explicitly so that we don't rely on the class map being dumped to make it work 'Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController', - 'Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller', ]); + if (ContainerBuilder::willBeAvailable('symfony/mime', MimeTypes::class, ['symfony/framework-bundle'], true)) { + $loader->load('mime_type.php'); + } + + $container->registerForAutoconfiguration(PackageInterface::class) + ->addTag('assets.package'); $container->registerForAutoconfiguration(Command::class) ->addTag('console.command'); $container->registerForAutoconfiguration(ResourceCheckerInterface::class) ->addTag('config_cache.resource_checker'); + $container->registerForAutoconfiguration(EnvVarLoaderInterface::class) + ->addTag('container.env_var_loader'); $container->registerForAutoconfiguration(EnvVarProcessorInterface::class) ->addTag('container.env_var_processor'); + $container->registerForAutoconfiguration(CallbackInterface::class) + ->addTag('container.reversible'); + $container->registerForAutoconfiguration(ServiceLocator::class) + ->addTag('container.service_locator'); $container->registerForAutoconfiguration(ServiceSubscriberInterface::class) ->addTag('container.service_subscriber'); $container->registerForAutoconfiguration(ArgumentValueResolverInterface::class) ->addTag('controller.argument_value_resolver'); $container->registerForAutoconfiguration(AbstractController::class) ->addTag('controller.service_arguments'); - $container->registerForAutoconfiguration(Controller::class) - ->addTag('controller.service_arguments'); $container->registerForAutoconfiguration(DataCollectorInterface::class) ->addTag('data_collector'); $container->registerForAutoconfiguration(FormTypeInterface::class) ->addTag('form.type'); $container->registerForAutoconfiguration(FormTypeGuesserInterface::class) ->addTag('form.type_guesser'); + $container->registerForAutoconfiguration(FormTypeExtensionInterface::class) + ->addTag('form.type_extension'); $container->registerForAutoconfiguration(CacheClearerInterface::class) ->addTag('kernel.cache_clearer'); $container->registerForAutoconfiguration(CacheWarmerInterface::class) ->addTag('kernel.cache_warmer'); + $container->registerForAutoconfiguration(EventDispatcherInterface::class) + ->addTag('event_dispatcher.dispatcher'); $container->registerForAutoconfiguration(EventSubscriberInterface::class) ->addTag('kernel.event_subscriber'); - $container->registerForAutoconfiguration(ResettableInterface::class) + $container->registerForAutoconfiguration(LocaleAwareInterface::class) + ->addTag('kernel.locale_aware'); + $container->registerForAutoconfiguration(ResetInterface::class) ->addTag('kernel.reset', ['method' => 'reset']); + + if (!interface_exists(MarshallerInterface::class)) { + $container->registerForAutoconfiguration(ResettableInterface::class) + ->addTag('kernel.reset', ['method' => 'reset']); + } + $container->registerForAutoconfiguration(PropertyListExtractorInterface::class) ->addTag('property_info.list_extractor'); $container->registerForAutoconfiguration(PropertyTypeExtractorInterface::class) @@ -368,6 +575,8 @@ class FrameworkExtension extends Extension ->addTag('property_info.description_extractor'); $container->registerForAutoconfiguration(PropertyAccessExtractorInterface::class) ->addTag('property_info.access_extractor'); + $container->registerForAutoconfiguration(PropertyInitializableExtractorInterface::class) + ->addTag('property_info.initializable_extractor'); $container->registerForAutoconfiguration(EncoderInterface::class) ->addTag('serializer.encoder'); $container->registerForAutoconfiguration(DecoderInterface::class) @@ -380,52 +589,60 @@ class FrameworkExtension extends Extension ->addTag('validator.constraint_validator'); $container->registerForAutoconfiguration(ObjectInitializerInterface::class) ->addTag('validator.initializer'); + $container->registerForAutoconfiguration(MessageHandlerInterface::class) + ->addTag('messenger.message_handler'); + $container->registerForAutoconfiguration(BatchHandlerInterface::class) + ->addTag('messenger.message_handler'); + $container->registerForAutoconfiguration(TransportFactoryInterface::class) + ->addTag('messenger.transport_factory'); + $container->registerForAutoconfiguration(MimeTypeGuesserInterface::class) + ->addTag('mime.mime_type_guesser'); + $container->registerForAutoconfiguration(LoggerAwareInterface::class) + ->addMethodCall('setLogger', [new Reference('logger')]); + + $container->registerAttributeForAutoconfiguration(AsEventListener::class, static function (ChildDefinition $definition, AsEventListener $attribute, \Reflector $reflector) { + $tagAttributes = get_object_vars($attribute); + if ($reflector instanceof \ReflectionMethod) { + if (isset($tagAttributes['method'])) { + throw new LogicException(sprintf('AsEventListener attribute cannot declare a method on "%s::%s()".', $reflector->class, $reflector->name)); + } + $tagAttributes['method'] = $reflector->getName(); + } + $definition->addTag('kernel.event_listener', $tagAttributes); + }); + $container->registerAttributeForAutoconfiguration(AsController::class, static function (ChildDefinition $definition, AsController $attribute): void { + $definition->addTag('controller.service_arguments'); + }); + $container->registerAttributeForAutoconfiguration(AsMessageHandler::class, static function (ChildDefinition $definition, AsMessageHandler $attribute): void { + $tagAttributes = get_object_vars($attribute); + $tagAttributes['from_transport'] = $tagAttributes['fromTransport']; + unset($tagAttributes['fromTransport']); + + $definition->addTag('messenger.message_handler', $tagAttributes); + }); if (!$container->getParameter('kernel.debug')) { // remove tagged iterator argument for resource checkers $container->getDefinition('config_cache_factory')->setArguments([]); } - if (\PHP_VERSION_ID < 70000) { - $this->addClassesToCompile([ - 'Symfony\\Component\\Config\\ConfigCache', - 'Symfony\\Component\\Config\\FileLocator', - - 'Symfony\\Component\\Debug\\ErrorHandler', - - 'Symfony\\Component\\DependencyInjection\\ContainerAwareInterface', - 'Symfony\\Component\\DependencyInjection\\Container', - - 'Symfony\\Component\\EventDispatcher\\Event', - 'Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher', - - 'Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener', - 'Symfony\\Component\\HttpKernel\\EventListener\\RouterListener', - 'Symfony\\Component\\HttpKernel\\Bundle\\Bundle', - 'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver', - 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver', - 'Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadata', - 'Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadataFactory', - 'Symfony\\Component\\HttpKernel\\Event\\KernelEvent', - 'Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent', - 'Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent', - 'Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent', - 'Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent', - 'Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent', - 'Symfony\\Component\\HttpKernel\\HttpKernel', - 'Symfony\\Component\\HttpKernel\\KernelEvents', - 'Symfony\\Component\\HttpKernel\\Config\\FileLocator', - - 'Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerNameParser', - 'Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerResolver', - - // Cannot be included because annotations will parse the big compiled class file - // 'Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller', - - // cannot be included as commands are discovered based on the path to this class via Reflection - // 'Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle', - ]); + if (!$config['disallow_search_engine_index'] ?? false) { + $container->removeDefinition('disallow_search_engine_index_response_listener'); } + + $container->registerForAutoconfiguration(RouteLoaderInterface::class) + ->addTag('routing.route_loader'); + + $container->setParameter('container.behavior_describing_tags', [ + 'annotations.cached_reader', + 'container.do_not_inline', + 'container.service_locator', + 'container.service_subscriber', + 'kernel.event_subscriber', + 'kernel.event_listener', + 'kernel.locale_aware', + 'kernel.reset', + ]); } /** @@ -436,31 +653,27 @@ class FrameworkExtension extends Extension return new Configuration($container->getParameter('kernel.debug')); } - private function registerFormConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + protected function hasConsole(): bool { - $loader->load('form.xml'); + return class_exists(Application::class); + } - $container->getDefinition('form.resolved_type_factory')->setPrivate(true); - $container->getDefinition('form.registry')->setPrivate(true); - $container->getDefinition('form.type_guesser.validator')->setPrivate(true); - $container->getDefinition('form.type.form')->setPrivate(true); - $container->getDefinition('form.type.choice')->setPrivate(true); - $container->getDefinition('form.type_extension.form.http_foundation')->setPrivate(true); - $container->getDefinition('form.type_extension.form.validator')->setPrivate(true); - $container->getDefinition('form.type_extension.repeated.validator')->setPrivate(true); - $container->getDefinition('form.type_extension.submit.validator')->setPrivate(true); - $container->getDefinition('form.type_extension.upload.validator')->setPrivate(true); - $container->getDefinition('deprecated.form.registry')->setPrivate(true); + private function registerFormConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + { + $loader->load('form.php'); + + $container->getDefinition('form.type_extension.form.validator')->setArgument(1, $config['form']['legacy_error_messages']); if (null === $config['form']['csrf_protection']['enabled']) { $config['form']['csrf_protection']['enabled'] = $config['csrf_protection']['enabled']; } if ($this->isConfigEnabled($container, $config['form']['csrf_protection'])) { - $loader->load('form_csrf.xml'); + if (!$container->hasDefinition('security.csrf.token_generator')) { + throw new \LogicException('To use form CSRF protection, "framework.csrf_protection" must be enabled.'); + } - $container->getDefinition('form.type_extension.csrf')->setPrivate(true); - $container->getDefinition('deprecated.form.registry.csrf')->setPrivate(true); + $loader->load('form_csrf.php'); $container->setParameter('form.type_extension.csrf.enabled', true); $container->setParameter('form.type_extension.csrf.field_name', $config['form']['csrf_protection']['field_name']); @@ -468,12 +681,38 @@ class FrameworkExtension extends Extension $container->setParameter('form.type_extension.csrf.enabled', false); } - if (!class_exists(Translator::class)) { + if (!ContainerBuilder::willBeAvailable('symfony/translation', Translator::class, ['symfony/framework-bundle', 'symfony/form'], true)) { $container->removeDefinition('form.type_extension.upload.validator'); } + if (!method_exists(CachingFactoryDecorator::class, 'reset')) { + $container->getDefinition('form.choice_list_factory.cached') + ->clearTag('kernel.reset') + ; + } } - private function registerEsiConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerHttpCacheConfiguration(array $config, ContainerBuilder $container, bool $httpMethodOverride) + { + $options = $config; + unset($options['enabled']); + + if (!$options['private_headers']) { + unset($options['private_headers']); + } + + $container->getDefinition('http_cache') + ->setPublic($config['enabled']) + ->replaceArgument(3, $options); + + if ($httpMethodOverride) { + $container->getDefinition('http_cache') + ->addArgument((new Definition('void')) + ->setFactory([Request::class, 'enableHttpMethodParameterOverride']) + ); + } + } + + private function registerEsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { if (!$this->isConfigEnabled($container, $config)) { $container->removeDefinition('fragment.renderer.esi'); @@ -481,13 +720,10 @@ class FrameworkExtension extends Extension return; } - $loader->load('esi.xml'); - - $container->getDefinition('esi')->setPrivate(true); - $container->getDefinition('esi_listener')->setPrivate(true); + $loader->load('esi.php'); } - private function registerSsiConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerSsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { if (!$this->isConfigEnabled($container, $config)) { $container->removeDefinition('fragment.renderer.ssi'); @@ -495,13 +731,10 @@ class FrameworkExtension extends Extension return; } - $loader->load('ssi.xml'); - - $container->getDefinition('ssi')->setPrivate(true); - $container->getDefinition('ssi_listener')->setPrivate(true); + $loader->load('ssi.php'); } - private function registerFragmentsConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerFragmentsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { if (!$this->isConfigEnabled($container, $config)) { $container->removeDefinition('fragment.renderer.hinclude'); @@ -509,11 +742,13 @@ class FrameworkExtension extends Extension return; } - $loader->load('fragment_listener.xml'); + $container->setParameter('fragment.renderer.hinclude.global_template', $config['hinclude_default_template']); + + $loader->load('fragment_listener.php'); $container->setParameter('fragment.path', $config['path']); } - private function registerProfilerConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerProfilerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { if (!$this->isConfigEnabled($container, $config)) { // this is needed for the WebProfiler to work even if the profiler is disabled @@ -522,72 +757,60 @@ class FrameworkExtension extends Extension return; } - $loader->load('profiling.xml'); - $loader->load('collectors.xml'); - $loader->load('cache_debug.xml'); - - $container->getDefinition('data_collector.request')->setPrivate(true); - $container->getDefinition('data_collector.router')->setPrivate(true); - $container->getDefinition('profiler_listener')->setPrivate(true); + $loader->load('profiling.php'); + $loader->load('collectors.php'); + $loader->load('cache_debug.php'); if ($this->formConfigEnabled) { - $loader->load('form_debug.xml'); - - $container->getDefinition('form.resolved_type_factory')->setPrivate(true); - $container->getDefinition('data_collector.form.extractor')->setPrivate(true); - $container->getDefinition('data_collector.form')->setPrivate(true); + $loader->load('form_debug.php'); } if ($this->validatorConfigEnabled) { - $loader->load('validator_debug.xml'); + $loader->load('validator_debug.php'); } if ($this->translationConfigEnabled) { - $loader->load('translation_debug.xml'); - - $container->getDefinition('data_collector.translation')->setPrivate(true); + $loader->load('translation_debug.php'); $container->getDefinition('translator.data_collector')->setDecoratedService('translator'); } + if ($this->messengerConfigEnabled) { + $loader->load('messenger_debug.php'); + } + + if ($this->mailerConfigEnabled) { + $loader->load('mailer_debug.php'); + } + + if ($this->httpClientConfigEnabled) { + $loader->load('http_client_debug.php'); + } + + if ($this->notifierConfigEnabled) { + $loader->load('notifier_debug.php'); + } + $container->setParameter('profiler_listener.only_exceptions', $config['only_exceptions']); - $container->setParameter('profiler_listener.only_master_requests', $config['only_master_requests']); + $container->setParameter('profiler_listener.only_main_requests', $config['only_main_requests'] || $config['only_master_requests']); // Choose storage class based on the DSN - list($class) = explode(':', $config['dsn'], 2); + [$class] = explode(':', $config['dsn'], 2); if ('file' !== $class) { throw new \LogicException(sprintf('Driver "%s" is not supported for the profiler.', $class)); } $container->setParameter('profiler.storage.dsn', $config['dsn']); - if ($this->isConfigEnabled($container, $config['matcher'])) { - if (isset($config['matcher']['service'])) { - $container->setAlias('profiler.request_matcher', $config['matcher']['service'])->setPrivate(true); - } elseif (isset($config['matcher']['ip']) || isset($config['matcher']['path']) || isset($config['matcher']['ips'])) { - $definition = $container->register('profiler.request_matcher', 'Symfony\\Component\\HttpFoundation\\RequestMatcher'); - $definition->setPublic(false); - - if (isset($config['matcher']['ip'])) { - $definition->addMethodCall('matchIp', [$config['matcher']['ip']]); - } - - if (isset($config['matcher']['ips'])) { - $definition->addMethodCall('matchIps', [$config['matcher']['ips']]); - } - - if (isset($config['matcher']['path'])) { - $definition->addMethodCall('matchPath', [$config['matcher']['path']]); - } - } - } - $container->getDefinition('profiler') ->addArgument($config['collect']) ->addTag('kernel.reset', ['method' => 'reset']); + + $container->getDefinition('profiler_listener') + ->addArgument($config['collect_parameter']); } - private function registerWorkflowConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerWorkflowConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { if (!$config['enabled']) { $container->removeDefinition('console.command.workflow_dump'); @@ -599,32 +822,42 @@ class FrameworkExtension extends Extension throw new LogicException('Workflow support cannot be enabled as the Workflow component is not installed. Try running "composer require symfony/workflow".'); } - $loader->load('workflow.xml'); - - $container->getDefinition('workflow.marking_store.multiple_state')->setPrivate(true); - $container->getDefinition('workflow.marking_store.single_state')->setPrivate(true); - $container->getDefinition('workflow.registry')->setPrivate(true); + $loader->load('workflow.php'); $registryDefinition = $container->getDefinition('workflow.registry'); + $workflows = []; + foreach ($config['workflows'] as $name => $workflow) { - if (!\array_key_exists('type', $workflow)) { - $workflow['type'] = 'workflow'; - @trigger_error(sprintf('The "type" option of the "framework.workflows.%s" configuration entry must be defined since Symfony 3.3. The default value will be "state_machine" in Symfony 4.0.', $name), \E_USER_DEPRECATED); - } $type = $workflow['type']; $workflowId = sprintf('%s.%s', $type, $name); + // Process Metadata (workflow + places (transition is done in the "create transition" block)) + $metadataStoreDefinition = new Definition(Workflow\Metadata\InMemoryMetadataStore::class, [[], [], null]); + if ($workflow['metadata']) { + $metadataStoreDefinition->replaceArgument(0, $workflow['metadata']); + } + $placesMetadata = []; + foreach ($workflow['places'] as $place) { + if ($place['metadata']) { + $placesMetadata[$place['name']] = $place['metadata']; + } + } + if ($placesMetadata) { + $metadataStoreDefinition->replaceArgument(1, $placesMetadata); + } + // Create transitions $transitions = []; $guardsConfiguration = []; + $transitionsMetadataDefinition = new Definition(\SplObjectStorage::class); // Global transition counter per workflow $transitionCounter = 0; foreach ($workflow['transitions'] as $transition) { if ('workflow' === $type) { $transitionDefinition = new Definition(Workflow\Transition::class, [$transition['name'], $transition['from'], $transition['to']]); $transitionDefinition->setPublic(false); - $transitionId = sprintf('%s.transition.%s', $workflowId, $transitionCounter++); + $transitionId = sprintf('.%s.transition.%s', $workflowId, $transitionCounter++); $container->setDefinition($transitionId, $transitionDefinition); $transitions[] = new Reference($transitionId); if (isset($transition['guard'])) { @@ -635,12 +868,18 @@ class FrameworkExtension extends Extension $eventName = sprintf('workflow.%s.guard.%s', $name, $transition['name']); $guardsConfiguration[$eventName][] = $configuration; } + if ($transition['metadata']) { + $transitionsMetadataDefinition->addMethodCall('attach', [ + new Reference($transitionId), + $transition['metadata'], + ]); + } } elseif ('state_machine' === $type) { foreach ($transition['from'] as $from) { foreach ($transition['to'] as $to) { $transitionDefinition = new Definition(Workflow\Transition::class, [$transition['name'], $from, $to]); $transitionDefinition->setPublic(false); - $transitionId = sprintf('%s.transition.%s', $workflowId, $transitionCounter++); + $transitionId = sprintf('.%s.transition.%s', $workflowId, $transitionCounter++); $container->setDefinition($transitionId, $transitionDefinition); $transitions[] = new Reference($transitionId); if (isset($transition['guard'])) { @@ -651,31 +890,40 @@ class FrameworkExtension extends Extension $eventName = sprintf('workflow.%s.guard.%s', $name, $transition['name']); $guardsConfiguration[$eventName][] = $configuration; } + if ($transition['metadata']) { + $transitionsMetadataDefinition->addMethodCall('attach', [ + new Reference($transitionId), + $transition['metadata'], + ]); + } } } } } + $metadataStoreDefinition->replaceArgument(2, $transitionsMetadataDefinition); + $container->setDefinition(sprintf('%s.metadata_store', $workflowId), $metadataStoreDefinition); + + // Create places + $places = array_column($workflow['places'], 'name'); + $initialMarking = $workflow['initial_marking'] ?? []; // Create a Definition $definitionDefinition = new Definition(Workflow\Definition::class); $definitionDefinition->setPublic(false); - $definitionDefinition->addArgument($workflow['places']); + $definitionDefinition->addArgument($places); $definitionDefinition->addArgument($transitions); - $definitionDefinition->addTag('workflow.definition', [ - 'name' => $name, - 'type' => $type, - 'marking_store' => isset($workflow['marking_store']['type']) ? $workflow['marking_store']['type'] : null, - ]); - if (isset($workflow['initial_place'])) { - $definitionDefinition->addArgument($workflow['initial_place']); - } + $definitionDefinition->addArgument($initialMarking); + $definitionDefinition->addArgument(new Reference(sprintf('%s.metadata_store', $workflowId))); + + $workflows[$workflowId] = $definitionDefinition; // Create MarkingStore if (isset($workflow['marking_store']['type'])) { - $markingStoreDefinition = new ChildDefinition('workflow.marking_store.'.$workflow['marking_store']['type']); - foreach ($workflow['marking_store']['arguments'] as $argument) { - $markingStoreDefinition->addArgument($argument); - } + $markingStoreDefinition = new ChildDefinition('workflow.marking_store.method'); + $markingStoreDefinition->setArguments([ + 'state_machine' === $type, // single state + $workflow['marking_store']['property'], + ]); } elseif (isset($workflow['marking_store']['service'])) { $markingStoreDefinition = new Reference($workflow['marking_store']['service']); } @@ -683,36 +931,52 @@ class FrameworkExtension extends Extension // Create Workflow $workflowDefinition = new ChildDefinition(sprintf('%s.abstract', $type)); $workflowDefinition->replaceArgument(0, new Reference(sprintf('%s.definition', $workflowId))); - if (isset($markingStoreDefinition)) { - $workflowDefinition->replaceArgument(1, $markingStoreDefinition); - } + $workflowDefinition->replaceArgument(1, $markingStoreDefinition ?? null); $workflowDefinition->replaceArgument(3, $name); + $workflowDefinition->replaceArgument(4, $workflow['events_to_dispatch']); + $workflowDefinition->addTag('container.private', [ + 'package' => 'symfony/framework-bundle', + 'version' => '5.3', + ]); // Store to container $container->setDefinition($workflowId, $workflowDefinition); $container->setDefinition(sprintf('%s.definition', $workflowId), $definitionDefinition); + $container->registerAliasForArgument($workflowId, WorkflowInterface::class, $name.'.'.$type); + + // Validate Workflow + if ('state_machine' === $workflow['type']) { + $validator = new Workflow\Validator\StateMachineValidator(); + } else { + $validator = new Workflow\Validator\WorkflowValidator(); + } + + $trs = array_map(function (Reference $ref) use ($container): Workflow\Transition { + return $container->get((string) $ref); + }, $transitions); + $realDefinition = new Workflow\Definition($places, $trs, $initialMarking); + $validator->validate($realDefinition, $name); // Add workflow to Registry if ($workflow['supports']) { foreach ($workflow['supports'] as $supportedClassName) { - $strategyDefinition = new Definition(Workflow\SupportStrategy\ClassInstanceSupportStrategy::class, [$supportedClassName]); + $strategyDefinition = new Definition(Workflow\SupportStrategy\InstanceOfSupportStrategy::class, [$supportedClassName]); $strategyDefinition->setPublic(false); - $registryDefinition->addMethodCall('add', [new Reference($workflowId), $strategyDefinition]); + $registryDefinition->addMethodCall('addWorkflow', [new Reference($workflowId), $strategyDefinition]); } } elseif (isset($workflow['support_strategy'])) { - $registryDefinition->addMethodCall('add', [new Reference($workflowId), new Reference($workflow['support_strategy'])]); + $registryDefinition->addMethodCall('addWorkflow', [new Reference($workflowId), new Reference($workflow['support_strategy'])]); } // Enable the AuditTrail if ($workflow['audit_trail']['enabled']) { $listener = new Definition(Workflow\EventListener\AuditTrailListener::class); - $listener->setPrivate(true); $listener->addTag('monolog.logger', ['channel' => 'workflow']); $listener->addTag('kernel.event_listener', ['event' => sprintf('workflow.%s.leave', $name), 'method' => 'onLeave']); $listener->addTag('kernel.event_listener', ['event' => sprintf('workflow.%s.transition', $name), 'method' => 'onTransition']); $listener->addTag('kernel.event_listener', ['event' => sprintf('workflow.%s.enter', $name), 'method' => 'onEnter']); $listener->addArgument(new Reference('logger')); - $container->setDefinition(sprintf('%s.listener.audit_trail', $workflowId), $listener); + $container->setDefinition(sprintf('.%s.listener.audit_trail', $workflowId), $listener); } // Add Guard Listener @@ -722,11 +986,10 @@ class FrameworkExtension extends Extension } if (!class_exists(Security::class)) { - throw new LogicException('Cannot guard workflows as the Security component is not installed. Try running "composer require symfony/security".'); + throw new LogicException('Cannot guard workflows as the Security component is not installed. Try running "composer require symfony/security-core".'); } $guard = new Definition(Workflow\EventListener\GuardListener::class); - $guard->setPrivate(true); $guard->setArguments([ $guardsConfiguration, @@ -741,22 +1004,22 @@ class FrameworkExtension extends Extension $guard->addTag('kernel.event_listener', ['event' => $eventName, 'method' => 'onTransition']); } - $container->setDefinition(sprintf('%s.listener.guard', $workflowId), $guard); + $container->setDefinition(sprintf('.%s.listener.guard', $workflowId), $guard); $container->setParameter('workflow.has_guard_listeners', true); } } + + $commandDumpDefinition = $container->getDefinition('console.command.workflow_dump'); + $commandDumpDefinition->setArgument(0, $workflows); } - private function registerDebugConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerDebugConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { - $loader->load('debug_prod.xml'); - - $container->getDefinition('debug.debug_handlers_listener')->setPrivate(true); + $loader->load('debug_prod.php'); if (class_exists(Stopwatch::class)) { $container->register('debug.stopwatch', Stopwatch::class) ->addArgument(true) - ->setPrivate(true) ->addTag('kernel.reset', ['method' => 'reset']); $container->setAlias(Stopwatch::class, new Alias('debug.stopwatch', false)); } @@ -764,51 +1027,66 @@ class FrameworkExtension extends Extension $debug = $container->getParameter('kernel.debug'); if ($debug) { - $container->setParameter('debug.container.dump', '%kernel.cache_dir%/%kernel.container_class%.xml'); + $container->setParameter('debug.container.dump', '%kernel.build_dir%/%kernel.container_class%.xml'); } if ($debug && class_exists(Stopwatch::class)) { - $loader->load('debug.xml'); - $container->getDefinition('debug.event_dispatcher')->setPrivate(true); - $container->getDefinition('debug.controller_resolver')->setPrivate(true); - $container->getDefinition('debug.argument_resolver')->setPrivate(true); + $loader->load('debug.php'); } $definition = $container->findDefinition('debug.debug_handlers_listener'); - if (!$config['log']) { + if (false === $config['log']) { $definition->replaceArgument(1, null); + } elseif (true !== $config['log']) { + $definition->replaceArgument(2, $config['log']); } if (!$config['throw']) { $container->setParameter('debug.error_handler.throw_at', 0); } - $definition->replaceArgument(4, $debug); - $definition->replaceArgument(6, $debug); - if ($debug && class_exists(DebugProcessor::class)) { $definition = new Definition(DebugProcessor::class); $definition->setPublic(false); + $definition->addArgument(new Reference('request_stack')); $container->setDefinition('debug.log_processor', $definition); } } - private function registerRouterConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerRouterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $enabledLocales = []) { if (!$this->isConfigEnabled($container, $config)) { $container->removeDefinition('console.command.router_debug'); $container->removeDefinition('console.command.router_match'); + $container->removeDefinition('messenger.middleware.router_context'); return; } + if (!class_exists(RouterContextMiddleware::class)) { + $container->removeDefinition('messenger.middleware.router_context'); + } - $loader->load('routing.xml'); + $loader->load('routing.php'); - $container->getDefinition('router_listener')->setPrivate(true); + if (null === $config['utf8']) { + trigger_deprecation('symfony/framework-bundle', '5.1', 'Not setting the "framework.router.utf8" configuration option is deprecated, it will default to "true" in version 6.0.'); + } + + if ($config['utf8']) { + $container->getDefinition('routing.loader')->replaceArgument(1, ['utf8' => true]); + } + + if ($enabledLocales) { + $enabledLocales = implode('|', array_map('preg_quote', $enabledLocales)); + $container->getDefinition('routing.loader')->replaceArgument(2, ['_locale' => $enabledLocales]); + } + + if (!ContainerBuilder::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/framework-bundle', 'symfony/routing'], true)) { + $container->removeDefinition('router.expression_language_provider'); + } $container->setParameter('router.resource', $config['resource']); - $container->setParameter('router.cache_class_prefix', $container->getParameter('kernel.container_class')); $router = $container->findDefinition('router.default'); $argument = $router->getArgument(2); $argument['strict_requirements'] = $config['strict_requirements']; @@ -820,240 +1098,137 @@ class FrameworkExtension extends Extension $container->setParameter('request_listener.http_port', $config['http_port']); $container->setParameter('request_listener.https_port', $config['https_port']); - if (\PHP_VERSION_ID < 70000) { - $this->addClassesToCompile([ - 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', - 'Symfony\\Component\\Routing\\RequestContext', - 'Symfony\\Component\\Routing\\Router', - 'Symfony\\Bundle\\FrameworkBundle\\Routing\\RedirectableUrlMatcher', - $container->findDefinition('router.default')->getClass(), + if (null !== $config['default_uri']) { + $container->getDefinition('router.request_context') + ->replaceArgument(0, $config['default_uri']); + } + + if (\PHP_VERSION_ID < 80000 && !$this->annotationsConfigEnabled) { + return; + } + + $container->register('routing.loader.annotation', AnnotatedRouteControllerLoader::class) + ->setPublic(false) + ->addTag('routing.loader', ['priority' => -10]) + ->setArguments([ + new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE), + '%kernel.environment%', ]); - } - if ($this->annotationsConfigEnabled) { - $container->register('routing.loader.annotation', AnnotatedRouteControllerLoader::class) - ->setPublic(false) - ->addTag('routing.loader', ['priority' => -10]) - ->addArgument(new Reference('annotation_reader')); + $container->register('routing.loader.annotation.directory', AnnotationDirectoryLoader::class) + ->setPublic(false) + ->addTag('routing.loader', ['priority' => -10]) + ->setArguments([ + new Reference('file_locator'), + new Reference('routing.loader.annotation'), + ]); - $container->register('routing.loader.annotation.directory', AnnotationDirectoryLoader::class) - ->setPublic(false) - ->addTag('routing.loader', ['priority' => -10]) - ->setArguments([ - new Reference('file_locator'), - new Reference('routing.loader.annotation'), - ]); - - $container->register('routing.loader.annotation.file', AnnotationFileLoader::class) - ->setPublic(false) - ->addTag('routing.loader', ['priority' => -10]) - ->setArguments([ - new Reference('file_locator'), - new Reference('routing.loader.annotation'), - ]); - } + $container->register('routing.loader.annotation.file', AnnotationFileLoader::class) + ->setPublic(false) + ->addTag('routing.loader', ['priority' => -10]) + ->setArguments([ + new Reference('file_locator'), + new Reference('routing.loader.annotation'), + ]); } - private function registerSessionConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerSessionConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { - $loader->load('session.xml'); - - $container->getDefinition('session.storage.native')->setPrivate(true); - $container->getDefinition('session.storage.php_bridge')->setPrivate(true); - $container->getDefinition('session_listener')->setPrivate(true); - $container->getDefinition('session.save_listener')->setPrivate(true); - $container->getAlias('session.storage.filesystem')->setPrivate(true); + $loader->load('session.php'); // session storage - $container->setAlias('session.storage', $config['storage_id'])->setPrivate(true); + if (null === $config['storage_factory_id']) { + trigger_deprecation('symfony/framework-bundle', '5.3', 'Not setting the "framework.session.storage_factory_id" configuration option is deprecated, it will default to "session.storage.factory.native" and will replace the "framework.session.storage_id" configuration option in version 6.0.'); + $container->setAlias('session.storage', $config['storage_id']); + $container->setAlias('session.storage.factory', 'session.storage.factory.service'); + } else { + $container->setAlias('session.storage.factory', $config['storage_factory_id']); + + $container->removeAlias(SessionStorageInterface::class); + $container->removeDefinition('session.storage.metadata_bag'); + $container->removeDefinition('session.storage.native'); + $container->removeDefinition('session.storage.php_bridge'); + $container->removeDefinition('session.storage.mock_file'); + $container->removeAlias('session.storage.filesystem'); + } + $options = ['cache_limiter' => '0']; - foreach (['name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'cookie_samesite', 'use_cookies', 'gc_maxlifetime', 'gc_probability', 'gc_divisor'] as $key) { + foreach (['name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'cookie_samesite', 'use_cookies', 'gc_maxlifetime', 'gc_probability', 'gc_divisor', 'sid_length', 'sid_bits_per_character'] as $key) { if (isset($config[$key])) { $options[$key] = $config[$key]; } } + if ('auto' === ($options['cookie_secure'] ?? null)) { + if (null === $config['storage_factory_id']) { + $locator = $container->getDefinition('session_listener')->getArgument(0); + $locator->setValues($locator->getValues() + [ + 'session_storage' => new Reference('session.storage', ContainerInterface::IGNORE_ON_INVALID_REFERENCE), + 'request_stack' => new Reference('request_stack'), + ]); + } else { + $container->getDefinition('session.storage.factory.native')->replaceArgument(3, true); + $container->getDefinition('session.storage.factory.php_bridge')->replaceArgument(2, true); + } + } + $container->setParameter('session.storage.options', $options); // session handler (the internal callback registered with PHP session management) if (null === $config['handler_id']) { // Set the handler class to be null - $container->getDefinition('session.storage.native')->replaceArgument(1, null); - $container->getDefinition('session.storage.php_bridge')->replaceArgument(0, null); - $container->setAlias('session.handler', 'session.handler.native_file')->setPrivate(true); + if ($container->hasDefinition('session.storage.native')) { + $container->getDefinition('session.storage.native')->replaceArgument(1, null); + $container->getDefinition('session.storage.php_bridge')->replaceArgument(0, null); + } else { + $container->getDefinition('session.storage.factory.native')->replaceArgument(1, null); + $container->getDefinition('session.storage.factory.php_bridge')->replaceArgument(0, null); + } + + $container->setAlias('session.handler', 'session.handler.native_file'); } else { - $container->setAlias('session.handler', $config['handler_id'])->setPrivate(true); + $container->resolveEnvPlaceholders($config['handler_id'], null, $usedEnvs); + + if ($usedEnvs || preg_match('#^[a-z]++://#', $config['handler_id'])) { + $id = '.cache_connection.'.ContainerBuilder::hash($config['handler_id']); + + $container->getDefinition('session.abstract_handler') + ->replaceArgument(0, $container->hasDefinition($id) ? new Reference($id) : $config['handler_id']); + + $container->setAlias('session.handler', 'session.abstract_handler'); + } else { + $container->setAlias('session.handler', $config['handler_id']); + } } $container->setParameter('session.save_path', $config['save_path']); - if (\PHP_VERSION_ID < 70000) { - $this->addClassesToCompile([ - 'Symfony\\Component\\HttpKernel\\EventListener\\SessionListener', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\PhpBridgeSessionStorage', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeFileSessionHandler', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\AbstractProxy', - 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\SessionHandlerProxy', - $container->getDefinition('session')->getClass(), - ]); - - if ($container->hasDefinition($config['storage_id'])) { - $this->addClassesToCompile([ - $container->findDefinition('session.storage')->getClass(), - ]); - } - } - $container->setParameter('session.metadata.update_threshold', $config['metadata_update_threshold']); } - private function registerRequestConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerRequestConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { if ($config['formats']) { - $loader->load('request.xml'); + $loader->load('request.php'); $listener = $container->getDefinition('request.add_request_formats_listener'); - $listener->setPrivate(true); $listener->replaceArgument(0, $config['formats']); } } - private function registerTemplatingConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerAssetsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { - $loader->load('templating.xml'); - - $container->getDefinition('templating.name_parser')->setPrivate(true); - $container->getDefinition('templating.filename_parser')->setPrivate(true); - - $container->setParameter('fragment.renderer.hinclude.global_template', $config['hinclude_default_template']); - - if ($container->getParameter('kernel.debug')) { - $logger = new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE); - - $container->getDefinition('templating.loader.cache') - ->addTag('monolog.logger', ['channel' => 'templating']) - ->addMethodCall('setLogger', [$logger]); - $container->getDefinition('templating.loader.chain') - ->addTag('monolog.logger', ['channel' => 'templating']) - ->addMethodCall('setLogger', [$logger]); - } - - if (!empty($config['loaders'])) { - $loaders = array_map(function ($loader) { return new Reference($loader); }, $config['loaders']); - - // Use a delegation unless only a single loader was registered - if (1 === \count($loaders)) { - $container->setAlias('templating.loader', (string) reset($loaders))->setPrivate(true); - } else { - $container->getDefinition('templating.loader.chain')->addArgument($loaders); - $container->setAlias('templating.loader', 'templating.loader.chain')->setPrivate(true); - } - } - - $container->setParameter('templating.loader.cache.path', null); - if (isset($config['cache'])) { - // Wrap the existing loader with cache (must happen after loaders are registered) - $container->setDefinition('templating.loader.wrapped', $container->findDefinition('templating.loader')); - $loaderCache = $container->getDefinition('templating.loader.cache'); - $container->setParameter('templating.loader.cache.path', $config['cache']); - - $container->setDefinition('templating.loader', $loaderCache); - } - - if (\PHP_VERSION_ID < 70000) { - $this->addClassesToCompile([ - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\GlobalVariables', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\TemplateReference', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\TemplateNameParser', - $container->findDefinition('templating.locator')->getClass(), - ]); - } - - $container->setParameter('templating.engines', $config['engines']); - $engines = array_map(function ($engine) { return new Reference('templating.engine.'.$engine); }, $config['engines']); - - // Use a delegation unless only a single engine was registered - if (1 === \count($engines)) { - $container->setAlias('templating', (string) reset($engines))->setPublic(true); - } else { - $templateEngineDefinition = $container->getDefinition('templating.engine.delegating'); - foreach ($engines as $engine) { - $templateEngineDefinition->addMethodCall('addEngine', [$engine]); - } - $container->setAlias('templating', 'templating.engine.delegating')->setPublic(true); - } - - $container->getDefinition('fragment.renderer.hinclude') - ->addTag('kernel.fragment_renderer', ['alias' => 'hinclude']) - ->replaceArgument(0, new Reference('templating')) - ; - - // configure the PHP engine if needed - if (\in_array('php', $config['engines'], true)) { - $loader->load('templating_php.xml'); - - $container->getDefinition('templating.helper.slots')->setPrivate(true); - $container->getDefinition('templating.helper.request')->setPrivate(true); - $container->getDefinition('templating.helper.session')->setPrivate(true); - $container->getDefinition('templating.helper.router')->setPrivate(true); - $container->getDefinition('templating.helper.assets')->setPrivate(true); - $container->getDefinition('templating.helper.actions')->setPrivate(true); - $container->getDefinition('templating.helper.code')->setPrivate(true); - $container->getDefinition('templating.helper.translator')->setPrivate(true); - $container->getDefinition('templating.helper.form')->setPrivate(true); - $container->getDefinition('templating.helper.stopwatch')->setPrivate(true); - $container->getDefinition('templating.globals')->setPrivate(true); - - $container->setParameter('templating.helper.form.resources', $config['form']['resources']); - - if ($container->getParameter('kernel.debug') && class_exists(Stopwatch::class)) { - $loader->load('templating_debug.xml'); - - $container->setDefinition('templating.engine.php', $container->findDefinition('debug.templating.engine.php')); - $container->setAlias('debug.templating.engine.php', 'templating.engine.php')->setPrivate(true); - } - - if (\PHP_VERSION_ID < 70000) { - $this->addClassesToCompile([ - 'Symfony\\Component\\Templating\\Storage\\FileStorage', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\PhpEngine', - 'Symfony\\Bundle\\FrameworkBundle\\Templating\\Loader\\FilesystemLoader', - ]); - } - - if ($container->has('assets.packages')) { - $container->getDefinition('templating.helper.assets')->replaceArgument(0, new Reference('assets.packages')); - } else { - $container->removeDefinition('templating.helper.assets'); - } - - if (!$this->translationConfigEnabled) { - $container->removeDefinition('templating.helper.translator'); - } - } - } - - private function registerAssetsConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) - { - $loader->load('assets.xml'); - - $container->getDefinition('assets.packages')->setPrivate(true); - $container->getDefinition('assets.context')->setPrivate(true); - $container->getDefinition('assets.path_package')->setPrivate(true); - $container->getDefinition('assets.url_package')->setPrivate(true); - $container->getDefinition('assets.static_version_strategy')->setPrivate(true); + $loader->load('assets.php'); if ($config['version_strategy']) { $defaultVersion = new Reference($config['version_strategy']); } else { - $defaultVersion = $this->createVersion($container, $config['version'], $config['version_format'], $config['json_manifest_path'], '_default'); + $defaultVersion = $this->createVersion($container, $config['version'], $config['version_format'], $config['json_manifest_path'], '_default', $config['strict_mode']); } $defaultPackage = $this->createPackageDefinition($config['base_path'], $config['base_urls'], $defaultVersion); $container->setDefinition('assets._default_package', $defaultPackage); - $namedPackages = []; foreach ($config['packages'] as $name => $package) { if (null !== $package['version_strategy']) { $version = new Reference($package['version_strategy']); @@ -1063,24 +1238,21 @@ class FrameworkExtension extends Extension } else { // let format fallback to main version_format $format = $package['version_format'] ?: $config['version_format']; - $version = isset($package['version']) ? $package['version'] : null; - $version = $this->createVersion($container, $version, $format, $package['json_manifest_path'], $name); + $version = $package['version'] ?? null; + $version = $this->createVersion($container, $version, $format, $package['json_manifest_path'], $name, $package['strict_mode']); } - $container->setDefinition('assets._package_'.$name, $this->createPackageDefinition($package['base_path'], $package['base_urls'], $version)); - $namedPackages[$name] = new Reference('assets._package_'.$name); + $packageDefinition = $this->createPackageDefinition($package['base_path'], $package['base_urls'], $version) + ->addTag('assets.package', ['package' => $name]); + $container->setDefinition('assets._package_'.$name, $packageDefinition); + $container->registerAliasForArgument('assets._package_'.$name, PackageInterface::class, $name.'.package'); } - - $container->getDefinition('assets.packages') - ->replaceArgument(0, new Reference('assets._default_package')) - ->replaceArgument(1, $namedPackages) - ; } /** * Returns a definition for an asset package. */ - private function createPackageDefinition($basePath, array $baseUrls, Reference $version) + private function createPackageDefinition(?string $basePath, array $baseUrls, Reference $version): Definition { if ($basePath && $baseUrls) { throw new \LogicException('An asset package cannot have base URLs and base paths.'); @@ -1096,7 +1268,7 @@ class FrameworkExtension extends Extension return $package; } - private function createVersion(ContainerBuilder $container, $version, $format, $jsonManifestPath, $name) + private function createVersion(ContainerBuilder $container, ?string $version, ?string $format, ?string $jsonManifestPath, string $name, bool $strictMode): Reference { // Configuration prevents $version and $jsonManifestPath from being set if (null !== $version) { @@ -1113,6 +1285,7 @@ class FrameworkExtension extends Extension if (null !== $jsonManifestPath) { $def = new ChildDefinition('assets.json_manifest_version_strategy'); $def->replaceArgument(0, $jsonManifestPath); + $def->replaceArgument(2, $strictMode); $container->setDefinition('assets._version_'.$name, $def); return new Reference('assets._version_'.$name); @@ -1121,144 +1294,211 @@ class FrameworkExtension extends Extension return new Reference('assets.empty_version_strategy'); } - private function registerTranslatorConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader) + private function registerTranslatorConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader, string $defaultLocale, array $enabledLocales) { if (!$this->isConfigEnabled($container, $config)) { $container->removeDefinition('console.command.translation_debug'); - $container->removeDefinition('console.command.translation_update'); + $container->removeDefinition('console.command.translation_extract'); + $container->removeDefinition('console.command.translation_pull'); + $container->removeDefinition('console.command.translation_push'); return; } - $loader->load('translation.xml'); - - $container->getDefinition('translator.default')->setPrivate(true); - $container->getDefinition('translation.loader.php')->setPrivate(true); - $container->getDefinition('translation.loader.yml')->setPrivate(true); - $container->getDefinition('translation.loader.xliff')->setPrivate(true); - $container->getDefinition('translation.loader.po')->setPrivate(true); - $container->getDefinition('translation.loader.mo')->setPrivate(true); - $container->getDefinition('translation.loader.qt')->setPrivate(true); - $container->getDefinition('translation.loader.csv')->setPrivate(true); - $container->getDefinition('translation.loader.res')->setPrivate(true); - $container->getDefinition('translation.loader.dat')->setPrivate(true); - $container->getDefinition('translation.loader.ini')->setPrivate(true); - $container->getDefinition('translation.loader.json')->setPrivate(true); - $container->getDefinition('translation.dumper.php')->setPrivate(true); - $container->getDefinition('translation.dumper.xliff')->setPrivate(true); - $container->getDefinition('translation.dumper.po')->setPrivate(true); - $container->getDefinition('translation.dumper.mo')->setPrivate(true); - $container->getDefinition('translation.dumper.yml')->setPrivate(true); - $container->getDefinition('translation.dumper.qt')->setPrivate(true); - $container->getDefinition('translation.dumper.csv')->setPrivate(true); - $container->getDefinition('translation.dumper.ini')->setPrivate(true); - $container->getDefinition('translation.dumper.json')->setPrivate(true); - $container->getDefinition('translation.dumper.res')->setPrivate(true); - $container->getDefinition('translation.extractor.php')->setPrivate(true); - $container->getDefinition('translator_listener')->setPrivate(true); - $container->getDefinition('translation.loader')->setPrivate(true); - $container->getDefinition('translation.reader')->setPrivate(true); - $container->getDefinition('translation.extractor')->setPrivate(true); - $container->getDefinition('translation.writer')->setPrivate(true); + $loader->load('translation.php'); + $loader->load('translation_providers.php'); // Use the "real" translator instead of the identity default $container->setAlias('translator', 'translator.default')->setPublic(true); $container->setAlias('translator.formatter', new Alias($config['formatter'], false)); $translator = $container->findDefinition('translator.default'); - $translator->addMethodCall('setFallbackLocales', [$config['fallbacks']]); + $translator->addMethodCall('setFallbackLocales', [$config['fallbacks'] ?: [$defaultLocale]]); + + $defaultOptions = $translator->getArgument(4); + $defaultOptions['cache_dir'] = $config['cache_dir']; + $translator->setArgument(4, $defaultOptions); + + // @deprecated since Symfony 5.4, in 6.0 change to: + // $translator->setArgument(5, $enabledLocales); + $translator->setArgument(5, $config['enabled_locales'] ?: $enabledLocales); $container->setParameter('translator.logging', $config['logging']); $container->setParameter('translator.default_path', $config['default_path']); // Discover translation directories $dirs = []; - if (class_exists('Symfony\Component\Validator\Validation')) { - $r = new \ReflectionClass('Symfony\Component\Validator\Validation'); + $transPaths = []; + $nonExistingDirs = []; + if (ContainerBuilder::willBeAvailable('symfony/validator', Validation::class, ['symfony/framework-bundle', 'symfony/translation'], true)) { + $r = new \ReflectionClass(Validation::class); - $dirs[] = \dirname($r->getFileName()).'/Resources/translations'; + $dirs[] = $transPaths[] = \dirname($r->getFileName()).'/Resources/translations'; } - if (class_exists('Symfony\Component\Form\Form')) { - $r = new \ReflectionClass('Symfony\Component\Form\Form'); + if (ContainerBuilder::willBeAvailable('symfony/form', Form::class, ['symfony/framework-bundle', 'symfony/translation'], true)) { + $r = new \ReflectionClass(Form::class); - $dirs[] = \dirname($r->getFileName()).'/Resources/translations'; + $dirs[] = $transPaths[] = \dirname($r->getFileName()).'/Resources/translations'; } - if (class_exists('Symfony\Component\Security\Core\Exception\AuthenticationException')) { - $r = new \ReflectionClass('Symfony\Component\Security\Core\Exception\AuthenticationException'); + if (ContainerBuilder::willBeAvailable('symfony/security-core', AuthenticationException::class, ['symfony/framework-bundle', 'symfony/translation'], true)) { + $r = new \ReflectionClass(AuthenticationException::class); - $dirs[] = \dirname(\dirname($r->getFileName())).'/Resources/translations'; + $dirs[] = $transPaths[] = \dirname($r->getFileName(), 2).'/Resources/translations'; } $defaultDir = $container->getParameterBag()->resolveValue($config['default_path']); - $rootDir = $container->getParameter('kernel.root_dir'); foreach ($container->getParameter('kernel.bundles_metadata') as $name => $bundle) { - if ($container->fileExists($dir = $bundle['path'].'/Resources/translations')) { - $dirs[] = $dir; - } - if ($container->fileExists($dir = $rootDir.sprintf('/Resources/%s/translations', $name))) { + if ($container->fileExists($dir = $bundle['path'].'/Resources/translations') || $container->fileExists($dir = $bundle['path'].'/translations')) { $dirs[] = $dir; + } else { + $nonExistingDirs[] = $dir; } } foreach ($config['paths'] as $dir) { if ($container->fileExists($dir)) { - $dirs[] = $dir; + $dirs[] = $transPaths[] = $dir; } else { throw new \UnexpectedValueException(sprintf('"%s" defined in translator.paths does not exist or is not a directory.', $dir)); } } - if ($container->fileExists($defaultDir)) { - $dirs[] = $defaultDir; + if ($container->hasDefinition('console.command.translation_debug')) { + $container->getDefinition('console.command.translation_debug')->replaceArgument(5, $transPaths); } - if ($container->fileExists($dir = $rootDir.'/Resources/translations')) { - $dirs[] = $dir; + + if ($container->hasDefinition('console.command.translation_extract')) { + $container->getDefinition('console.command.translation_extract')->replaceArgument(6, $transPaths); + } + + if (null === $defaultDir) { + // allow null + } elseif ($container->fileExists($defaultDir)) { + $dirs[] = $defaultDir; + } else { + $nonExistingDirs[] = $defaultDir; } // Register translation resources if ($dirs) { $files = []; - $finder = Finder::create() - ->followLinks() - ->files() - ->filter(function (\SplFileInfo $file) { - return 2 === substr_count($file->getBasename(), '.') && preg_match('/\.\w+$/', $file->getBasename()); - }) - ->in($dirs) - ->sortByName() - ; - foreach ($finder as $file) { - list(, $locale) = explode('.', $file->getBasename(), 3); - if (!isset($files[$locale])) { - $files[$locale] = []; + foreach ($dirs as $dir) { + $finder = Finder::create() + ->followLinks() + ->files() + ->filter(function (\SplFileInfo $file) { + return 2 <= substr_count($file->getBasename(), '.') && preg_match('/\.\w+$/', $file->getBasename()); + }) + ->in($dir) + ->sortByName() + ; + foreach ($finder as $file) { + $fileNameParts = explode('.', basename($file)); + $locale = $fileNameParts[\count($fileNameParts) - 2]; + if (!isset($files[$locale])) { + $files[$locale] = []; + } + + $files[$locale][] = (string) $file; } - - $files[$locale][] = (string) $file; } + $projectDir = $container->getParameter('kernel.project_dir'); + $options = array_merge( $translator->getArgument(4), - ['resource_files' => $files] + [ + 'resource_files' => $files, + 'scanned_directories' => $scannedDirectories = array_merge($dirs, $nonExistingDirs), + 'cache_vary' => [ + 'scanned_directories' => array_map(static function (string $dir) use ($projectDir): string { + return str_starts_with($dir, $projectDir.'/') ? substr($dir, 1 + \strlen($projectDir)) : $dir; + }, $scannedDirectories), + ], + ] ); $translator->replaceArgument(4, $options); } - } - private function registerValidationConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) - { - if (!$this->validatorConfigEnabled = $this->isConfigEnabled($container, $config)) { + if ($config['pseudo_localization']['enabled']) { + $options = $config['pseudo_localization']; + unset($options['enabled']); + + $container + ->register('translator.pseudo', PseudoLocalizationTranslator::class) + ->setDecoratedService('translator', null, -1) // Lower priority than "translator.data_collector" + ->setArguments([ + new Reference('translator.pseudo.inner'), + $options, + ]); + } + + $classToServices = [ + CrowdinProviderFactory::class => 'translation.provider_factory.crowdin', + LocoProviderFactory::class => 'translation.provider_factory.loco', + LokaliseProviderFactory::class => 'translation.provider_factory.lokalise', + ]; + + $parentPackages = ['symfony/framework-bundle', 'symfony/translation', 'symfony/http-client']; + + foreach ($classToServices as $class => $service) { + $package = substr($service, \strlen('translation.provider_factory.')); + + if (!$container->hasDefinition('http_client') || !ContainerBuilder::willBeAvailable(sprintf('symfony/%s-translation-provider', $package), $class, $parentPackages, true)) { + $container->removeDefinition($service); + } + } + + if (!$config['providers']) { return; } - if (!class_exists('Symfony\Component\Validator\Validation')) { + // @deprecated since Symfony 5.4, in 6.0 change to: + // $locales = $enabledLocales; + $locales = $config['enabled_locales'] ?: $enabledLocales; + + foreach ($config['providers'] as $provider) { + if ($provider['locales']) { + $locales += $provider['locales']; + } + } + + $locales = array_unique($locales); + + $container->getDefinition('console.command.translation_pull') + ->replaceArgument(4, array_merge($transPaths, [$config['default_path']])) + ->replaceArgument(5, $locales) + ; + + $container->getDefinition('console.command.translation_push') + ->replaceArgument(2, array_merge($transPaths, [$config['default_path']])) + ->replaceArgument(3, $locales) + ; + + $container->getDefinition('translation.provider_collection_factory') + ->replaceArgument(1, $locales) + ; + + $container->getDefinition('translation.provider_collection')->setArgument(0, $config['providers']); + } + + private function registerValidationConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $propertyInfoEnabled) + { + if (!$this->validatorConfigEnabled = $this->isConfigEnabled($container, $config)) { + $container->removeDefinition('console.command.validator_debug'); + + return; + } + + if (!class_exists(Validation::class)) { throw new LogicException('Validation support cannot be enabled as the Validator component is not installed. Try running "composer require symfony/validator".'); } - $loader->load('validator.xml'); + if (!isset($config['email_validation_mode'])) { + $config['email_validation_mode'] = 'loose'; + } - $container->getDefinition('validator.builder')->setPrivate(true); - $container->getDefinition('validator.expression')->setPrivate(true); - $container->getDefinition('validator.email')->setPrivate(true); + $loader->load('validator.php'); $validatorBuilder = $container->getDefinition('validator.builder'); @@ -1276,14 +1516,17 @@ class FrameworkExtension extends Extension } $definition = $container->findDefinition('validator.email'); - $definition->replaceArgument(0, $config['strict_email']); + $definition->replaceArgument(0, $config['email_validation_mode']); if (\array_key_exists('enable_annotations', $config) && $config['enable_annotations']) { - if (!$this->annotationsConfigEnabled) { - throw new \LogicException('"enable_annotations" on the validator cannot be set as Annotations support is disabled.'); + if (!$this->annotationsConfigEnabled && \PHP_VERSION_ID < 80000) { + throw new \LogicException('"enable_annotations" on the validator cannot be set as the PHP version is lower than 8 and Doctrine Annotations support is disabled. Consider upgrading PHP.'); } - $validatorBuilder->addMethodCall('enableAnnotationMapping', [new Reference('annotation_reader')]); + $validatorBuilder->addMethodCall('enableAnnotationMapping', [true]); + if ($this->annotationsConfigEnabled) { + $validatorBuilder->addMethodCall('setDoctrineAnnotationReader', [new Reference('annotation_reader')]); + } } if (\array_key_exists('static_method', $config) && $config['static_method']) { @@ -1292,15 +1535,23 @@ class FrameworkExtension extends Extension } } - if (isset($config['cache']) && $config['cache']) { - $container->setParameter( - 'validator.mapping.cache.prefix', - 'validator_'.$this->getKernelRootHash($container) - ); + if (!$container->getParameter('kernel.debug')) { + $validatorBuilder->addMethodCall('setMappingCache', [new Reference('validator.mapping.cache.adapter')]); + } - $validatorBuilder->addMethodCall('setMetadataCache', [new Reference($config['cache'])]); - } elseif (!$container->getParameter('kernel.debug')) { - $validatorBuilder->addMethodCall('setMetadataCache', [new Reference('validator.mapping.cache.symfony')]); + $container->setParameter('validator.auto_mapping', $config['auto_mapping']); + if (!$propertyInfoEnabled || !class_exists(PropertyInfoLoader::class)) { + $container->removeDefinition('validator.property_info_loader'); + } + + $container + ->getDefinition('validator.not_compromised_password') + ->setArgument(2, $config['not_compromised_password']['enabled']) + ->setArgument(3, $config['not_compromised_password']['endpoint']) + ; + + if (!class_exists(ExpressionLanguage::class)) { + $container->removeDefinition('validator.expression_language'); } } @@ -1310,26 +1561,26 @@ class FrameworkExtension extends Extension $files['yaml' === $extension ? 'yml' : $extension][] = $path; }; - if (interface_exists('Symfony\Component\Form\FormInterface')) { - $reflClass = new \ReflectionClass('Symfony\Component\Form\FormInterface'); + if (ContainerBuilder::willBeAvailable('symfony/form', Form::class, ['symfony/framework-bundle', 'symfony/validator'], true)) { + $reflClass = new \ReflectionClass(Form::class); $fileRecorder('xml', \dirname($reflClass->getFileName()).'/Resources/config/validation.xml'); } foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) { - $dirname = $bundle['path']; + $configDir = is_dir($bundle['path'].'/Resources/config') ? $bundle['path'].'/Resources/config' : $bundle['path'].'/config'; if ( - $container->fileExists($file = $dirname.'/Resources/config/validation.yaml', false) || - $container->fileExists($file = $dirname.'/Resources/config/validation.yml', false) + $container->fileExists($file = $configDir.'/validation.yaml', false) || + $container->fileExists($file = $configDir.'/validation.yml', false) ) { $fileRecorder('yml', $file); } - if ($container->fileExists($file = $dirname.'/Resources/config/validation.xml', false)) { + if ($container->fileExists($file = $configDir.'/validation.xml', false)) { $fileRecorder('xml', $file); } - if ($container->fileExists($dir = $dirname.'/Resources/config/validation', '/^$/')) { + if ($container->fileExists($dir = $configDir.'/validation', '/^$/')) { $this->registerMappingFilesFromDir($dir, $fileRecorder); } } @@ -1342,7 +1593,7 @@ class FrameworkExtension extends Extension $this->registerMappingFilesFromConfig($container, $config, $fileRecorder); } - private function registerMappingFilesFromDir($dir, callable $fileRecorder) + private function registerMappingFilesFromDir(string $dir, callable $fileRecorder) { foreach (Finder::create()->followLinks()->files()->in($dir)->name('/\.(xml|ya?ml)$/')->sortByName() as $file) { $fileRecorder($file->getExtension(), $file->getRealPath()); @@ -1366,46 +1617,39 @@ class FrameworkExtension extends Extension } } - private function registerAnnotationsConfiguration(array $config, ContainerBuilder $container, $loader) + private function registerAnnotationsConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader) { if (!$this->annotationsConfigEnabled) { return; } - if (!class_exists('Doctrine\Common\Annotations\Annotation')) { - throw new LogicException('Annotations cannot be enabled as the Doctrine Annotation library is not installed.'); + if (!class_exists(\Doctrine\Common\Annotations\Annotation::class)) { + throw new LogicException('Annotations cannot be enabled as the Doctrine Annotation library is not installed. Try running "composer require doctrine/annotations".'); } - $loader->load('annotations.xml'); - - $container->getAlias('annotation_reader')->setPrivate(true); + $loader->load('annotations.php'); if (!method_exists(AnnotationRegistry::class, 'registerUniqueLoader')) { $container->getDefinition('annotations.dummy_registry') ->setMethodCalls([['registerLoader', ['class_exists']]]); } - if ('none' !== $config['cache']) { - if (!class_exists('Doctrine\Common\Cache\CacheProvider')) { - throw new LogicException('Annotations cannot be enabled as the Doctrine Cache library is not installed.'); - } + if ('none' === $config['cache']) { + $container->removeDefinition('annotations.cached_reader'); - $cacheService = $config['cache']; + return; + } + $cacheService = $config['cache']; + if (\in_array($config['cache'], ['php_array', 'file'])) { if ('php_array' === $config['cache']) { - $cacheService = 'annotations.cache'; + $cacheService = 'annotations.cache_adapter'; // Enable warmer only if PHP array is used for cache $definition = $container->findDefinition('annotations.cache_warmer'); $definition->addTag('kernel.cache_warmer'); - - if (\PHP_VERSION_ID < 70000) { - $this->addClassesToCompile([ - 'Symfony\Component\Cache\Adapter\PhpArrayAdapter', - 'Symfony\Component\Cache\DoctrineProvider', - ]); - } } elseif ('file' === $config['cache']) { + $cacheService = 'annotations.filesystem_cache_adapter'; $cacheDir = $container->getParameterBag()->resolveValue($config['file_cache_dir']); if (!is_dir($cacheDir) && false === @mkdir($cacheDir, 0777, true) && !is_dir($cacheDir)) { @@ -1413,52 +1657,99 @@ class FrameworkExtension extends Extension } $container - ->getDefinition('annotations.filesystem_cache') - ->replaceArgument(0, $cacheDir) + ->getDefinition('annotations.filesystem_cache_adapter') + ->replaceArgument(2, $cacheDir) ; - - $cacheService = 'annotations.filesystem_cache'; } - - $container - ->getDefinition('annotations.cached_reader') - ->replaceArgument(2, $config['debug']) - // temporary property to lazy-reference the cache provider without using it until AddAnnotationsCachedReaderPass runs - ->setProperty('cacheProviderBackup', new ServiceClosureArgument(new Reference($cacheService))) - ->addTag('annotations.cached_reader') - ; - - $container->setAlias('annotation_reader', 'annotations.cached_reader')->setPrivate(true); - $container->setAlias(Reader::class, new Alias('annotations.cached_reader', false)); } else { - $container->removeDefinition('annotations.cached_reader'); + trigger_deprecation('symfony/framework-bundle', '5.3', 'Using a custom service for "framework.annotation.cache" is deprecated, only values "none", "php_array" and "file" are valid in version 6.0.'); } + + $container + ->getDefinition('annotations.cached_reader') + ->replaceArgument(2, $config['debug']) + // reference the cache provider without using it until AddAnnotationsCachedReaderPass runs + ->addArgument(new ServiceClosureArgument(new Reference($cacheService))) + ; + + $container->setAlias('annotation_reader', 'annotations.cached_reader'); + $container->setAlias(Reader::class, new Alias('annotations.cached_reader', false)); + $container->removeDefinition('annotations.psr_cached_reader'); } - private function registerPropertyAccessConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerPropertyAccessConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { - if (!class_exists('Symfony\Component\PropertyAccess\PropertyAccessor')) { + if (!$this->propertyAccessConfigEnabled = $this->isConfigEnabled($container, $config)) { return; } - $loader->load('property_access.xml'); + $loader->load('property_access.php'); - $container->getDefinition('property_accessor')->setPrivate(true); + $magicMethods = PropertyAccessor::DISALLOW_MAGIC_METHODS; + $magicMethods |= $config['magic_call'] ? PropertyAccessor::MAGIC_CALL : 0; + $magicMethods |= $config['magic_get'] ? PropertyAccessor::MAGIC_GET : 0; + $magicMethods |= $config['magic_set'] ? PropertyAccessor::MAGIC_SET : 0; + + $throw = PropertyAccessor::DO_NOT_THROW; + $throw |= $config['throw_exception_on_invalid_index'] ? PropertyAccessor::THROW_ON_INVALID_INDEX : 0; + $throw |= $config['throw_exception_on_invalid_property_path'] ? PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH : 0; $container ->getDefinition('property_accessor') - ->replaceArgument(0, $config['magic_call']) - ->replaceArgument(1, $config['throw_exception_on_invalid_index']) + ->replaceArgument(0, $magicMethods) + ->replaceArgument(1, $throw) + ->replaceArgument(3, new Reference(PropertyReadInfoExtractorInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE)) + ->replaceArgument(4, new Reference(PropertyWriteInfoExtractorInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE)) ; } - private function registerSecurityCsrfConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerSecretsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + { + if (!$this->isConfigEnabled($container, $config)) { + $container->removeDefinition('console.command.secrets_set'); + $container->removeDefinition('console.command.secrets_list'); + $container->removeDefinition('console.command.secrets_remove'); + $container->removeDefinition('console.command.secrets_generate_key'); + $container->removeDefinition('console.command.secrets_decrypt_to_local'); + $container->removeDefinition('console.command.secrets_encrypt_from_local'); + + return; + } + + $loader->load('secrets.php'); + + $container->getDefinition('secrets.vault')->replaceArgument(0, $config['vault_directory']); + + if ($config['local_dotenv_file']) { + $container->getDefinition('secrets.local_vault')->replaceArgument(0, $config['local_dotenv_file']); + } else { + $container->removeDefinition('secrets.local_vault'); + } + + if ($config['decryption_env_var']) { + if (!preg_match('/^(?:[-.\w]*+:)*+\w++$/', $config['decryption_env_var'])) { + throw new InvalidArgumentException(sprintf('Invalid value "%s" set as "decryption_env_var": only "word" characters are allowed.', $config['decryption_env_var'])); + } + + if (ContainerBuilder::willBeAvailable('symfony/string', LazyString::class, ['symfony/framework-bundle'], true)) { + $container->getDefinition('secrets.decryption_key')->replaceArgument(1, $config['decryption_env_var']); + } else { + $container->getDefinition('secrets.vault')->replaceArgument(1, "%env({$config['decryption_env_var']})%"); + $container->removeDefinition('secrets.decryption_key'); + } + } else { + $container->getDefinition('secrets.vault')->replaceArgument(1, null); + $container->removeDefinition('secrets.decryption_key'); + } + } + + private function registerSecurityCsrfConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { if (!$this->isConfigEnabled($container, $config)) { return; } - if (!class_exists('Symfony\Component\Security\Csrf\CsrfToken')) { + if (!class_exists(\Symfony\Component\Security\Csrf\CsrfToken::class)) { throw new LogicException('CSRF support cannot be enabled as the Security CSRF component is not installed. Try running "composer require symfony/security-csrf".'); } @@ -1467,22 +1758,23 @@ class FrameworkExtension extends Extension } // Enable services for CSRF protection (even without forms) - $loader->load('security_csrf.xml'); + $loader->load('security_csrf.php'); + + if (!class_exists(CsrfExtension::class)) { + $container->removeDefinition('twig.extension.security_csrf'); + } } - private function registerSerializerConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerSerializerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { - $loader->load('serializer.xml'); - - if (!class_exists(DateIntervalNormalizer::class)) { - $container->removeDefinition('serializer.normalizer.dateinterval'); + $loader->load('serializer.php'); + if ($container->getParameter('kernel.debug')) { + $container->removeDefinition('serializer.mapping.cache_class_metadata_factory'); } - $container->getDefinition('serializer.mapping.cache.symfony')->setPrivate(true); - $chainLoader = $container->getDefinition('serializer.mapping.chain_loader'); - if (!class_exists('Symfony\Component\PropertyAccess\PropertyAccessor')) { + if (!$this->propertyAccessConfigEnabled) { $container->removeAlias('serializer.property_accessor'); $container->removeDefinition('serializer.normalizer.object'); } @@ -1491,15 +1783,23 @@ class FrameworkExtension extends Extension $container->removeDefinition('serializer.encoder.yaml'); } + if (!class_exists(UnwrappingDenormalizer::class) || !$this->propertyAccessConfigEnabled) { + $container->removeDefinition('serializer.denormalizer.unwrapping'); + } + + if (!class_exists(Headers::class)) { + $container->removeDefinition('serializer.normalizer.mime_message'); + } + $serializerLoaders = []; if (isset($config['enable_annotations']) && $config['enable_annotations']) { - if (!$this->annotationsConfigEnabled) { - throw new \LogicException('"enable_annotations" on the serializer cannot be set as Annotations support is disabled.'); + if (\PHP_VERSION_ID < 80000 && !$this->annotationsConfigEnabled) { + throw new \LogicException('"enable_annotations" on the serializer cannot be set as the PHP version is lower than 8 and Annotations support is disabled. Consider upgrading PHP.'); } $annotationLoader = new Definition( 'Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader', - [new Reference('annotation_reader')] + [new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE)] ); $annotationLoader->setPublic(false); @@ -1513,20 +1813,20 @@ class FrameworkExtension extends Extension }; foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) { - $dirname = $bundle['path']; + $configDir = is_dir($bundle['path'].'/Resources/config') ? $bundle['path'].'/Resources/config' : $bundle['path'].'/config'; - if ($container->fileExists($file = $dirname.'/Resources/config/serialization.xml', false)) { + if ($container->fileExists($file = $configDir.'/serialization.xml', false)) { $fileRecorder('xml', $file); } if ( - $container->fileExists($file = $dirname.'/Resources/config/serialization.yaml', false) || - $container->fileExists($file = $dirname.'/Resources/config/serialization.yml', false) + $container->fileExists($file = $configDir.'/serialization.yaml', false) || + $container->fileExists($file = $configDir.'/serialization.yml', false) ) { $fileRecorder('yml', $file); } - if ($container->fileExists($dir = $dirname.'/Resources/config/serialization', '/^$/')) { + if ($container->fileExists($dir = $configDir.'/serialization', '/^$/')) { $this->registerMappingFilesFromDir($dir, $fileRecorder); } } @@ -1541,59 +1841,58 @@ class FrameworkExtension extends Extension $chainLoader->replaceArgument(0, $serializerLoaders); $container->getDefinition('serializer.mapping.cache_warmer')->replaceArgument(0, $serializerLoaders); - if (isset($config['cache']) && $config['cache']) { - $container->setParameter( - 'serializer.mapping.cache.prefix', - 'serializer_'.$this->getKernelRootHash($container) - ); - - $container->getDefinition('serializer.mapping.class_metadata_factory')->replaceArgument( - 1, new Reference($config['cache']) - ); - } elseif (!$container->getParameter('kernel.debug')) { - $cacheMetadataFactory = new Definition( - CacheClassMetadataFactory::class, - [ - new Reference('serializer.mapping.cache_class_metadata_factory.inner'), - new Reference('serializer.mapping.cache.symfony'), - ] - ); - $cacheMetadataFactory->setPublic(false); - $cacheMetadataFactory->setDecoratedService('serializer.mapping.class_metadata_factory'); - - $container->setDefinition('serializer.mapping.cache_class_metadata_factory', $cacheMetadataFactory); - } - if (isset($config['name_converter']) && $config['name_converter']) { - $container->getDefinition('serializer.normalizer.object')->replaceArgument(1, new Reference($config['name_converter'])); + $container->getDefinition('serializer.name_converter.metadata_aware')->setArgument(1, new Reference($config['name_converter'])); } if (isset($config['circular_reference_handler']) && $config['circular_reference_handler']) { - $container->getDefinition('serializer.normalizer.object')->addMethodCall('setCircularReferenceHandler', [new Reference($config['circular_reference_handler'])]); + $arguments = $container->getDefinition('serializer.normalizer.object')->getArguments(); + $context = ($arguments[6] ?? []) + ['circular_reference_handler' => new Reference($config['circular_reference_handler'])]; + $container->getDefinition('serializer.normalizer.object')->setArgument(5, null); + $container->getDefinition('serializer.normalizer.object')->setArgument(6, $context); + } + + if ($config['max_depth_handler'] ?? false) { + $defaultContext = $container->getDefinition('serializer.normalizer.object')->getArgument(6); + $defaultContext += ['max_depth_handler' => new Reference($config['max_depth_handler'])]; + $container->getDefinition('serializer.normalizer.object')->replaceArgument(6, $defaultContext); + } + + if (isset($config['default_context']) && $config['default_context']) { + $container->setParameter('serializer.default_context', $config['default_context']); } } - private function registerPropertyInfoConfiguration(ContainerBuilder $container, XmlFileLoader $loader) + private function registerPropertyInfoConfiguration(ContainerBuilder $container, PhpFileLoader $loader) { if (!interface_exists(PropertyInfoExtractorInterface::class)) { throw new LogicException('PropertyInfo support cannot be enabled as the PropertyInfo component is not installed. Try running "composer require symfony/property-info".'); } - $loader->load('property_info.xml'); + $loader->load('property_info.php'); - $container->getDefinition('property_info')->setPrivate(true); + if ( + ContainerBuilder::willBeAvailable('phpstan/phpdoc-parser', PhpDocParser::class, ['symfony/framework-bundle', 'symfony/property-info'], true) + && ContainerBuilder::willBeAvailable('phpdocumentor/type-resolver', ContextFactory::class, ['symfony/framework-bundle', 'symfony/property-info'], true) + ) { + $definition = $container->register('property_info.phpstan_extractor', PhpStanExtractor::class); + $definition->addTag('property_info.type_extractor', ['priority' => -1000]); + } - if (interface_exists('phpDocumentor\Reflection\DocBlockFactoryInterface')) { + if (ContainerBuilder::willBeAvailable('phpdocumentor/reflection-docblock', DocBlockFactoryInterface::class, ['symfony/framework-bundle', 'symfony/property-info'], true)) { $definition = $container->register('property_info.php_doc_extractor', 'Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor'); - $definition->setPrivate(true); $definition->addTag('property_info.description_extractor', ['priority' => -1000]); $definition->addTag('property_info.type_extractor', ['priority' => -1001]); } + + if ($container->getParameter('kernel.debug')) { + $container->removeDefinition('property_info.cache'); + } } - private function registerLockConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) + private function registerLockConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { - $loader->load('lock.xml'); + $loader->load('lock.php'); foreach ($config['resources'] as $resourceName => $resourceStores) { if (0 === \count($resourceStores)) { @@ -1602,36 +1901,15 @@ class FrameworkExtension extends Extension // Generate stores $storeDefinitions = []; - foreach ($resourceStores as $storeDsn) { - $storeDsn = $container->resolveEnvPlaceholders($storeDsn, null, $usedEnvs); - switch (true) { - case 'flock' === $storeDsn: - $storeDefinition = new Reference('lock.store.flock'); - break; - case 'semaphore' === $storeDsn: - $storeDefinition = new Reference('lock.store.semaphore'); - break; - case $usedEnvs || preg_match('#^[a-z]++://#', $storeDsn): - if (!$container->hasDefinition($connectionDefinitionId = $container->hash($storeDsn))) { - $connectionDefinition = new Definition(\stdClass::class); - $connectionDefinition->setPublic(false); - $connectionDefinition->setFactory([AbstractAdapter::class, 'createConnection']); - $connectionDefinition->setArguments([$storeDsn, ['lazy' => true]]); - $container->setDefinition($connectionDefinitionId, $connectionDefinition); - } + foreach ($resourceStores as $resourceStore) { + $storeDsn = $container->resolveEnvPlaceholders($resourceStore, null, $usedEnvs); + $storeDefinition = new Definition(interface_exists(StoreInterface::class) ? StoreInterface::class : PersistingStoreInterface::class); + $storeDefinition->setFactory([StoreFactory::class, 'createStore']); + $storeDefinition->setArguments([$resourceStore]); - $storeDefinition = new Definition(StoreInterface::class); - $storeDefinition->setPublic(false); - $storeDefinition->setFactory([StoreFactory::class, 'createStore']); - $storeDefinition->setArguments([new Reference($connectionDefinitionId)]); + $container->setDefinition($storeDefinitionId = '.lock.'.$resourceName.'.store.'.$container->hash($storeDsn), $storeDefinition); - $container->setDefinition($storeDefinitionId = 'lock.'.$resourceName.'.store.'.$container->hash($storeDsn), $storeDefinition); - - $storeDefinition = new Reference($storeDefinitionId); - break; - default: - throw new InvalidArgumentException(sprintf('Lock store DSN "%s" is not valid in resource "%s".', $storeDsn, $resourceName)); - } + $storeDefinition = new Reference($storeDefinitionId); $storeDefinitions[] = $storeDefinition; } @@ -1640,14 +1918,15 @@ class FrameworkExtension extends Extension if (\count($storeDefinitions) > 1) { $combinedDefinition = new ChildDefinition('lock.store.combined.abstract'); $combinedDefinition->replaceArgument(0, $storeDefinitions); - $container->setDefinition('lock.'.$resourceName.'.store', $combinedDefinition); + $container->setDefinition('lock.'.$resourceName.'.store', $combinedDefinition)->setDeprecated('symfony/framework-bundle', '5.2', 'The "%service_id%" service is deprecated, use "lock.'.$resourceName.'.factory" instead.'); + $container->setDefinition($storeDefinitionId = '.lock.'.$resourceName.'.store.'.$container->hash($resourceStores), $combinedDefinition); } else { - $container->setAlias('lock.'.$resourceName.'.store', new Alias((string) $storeDefinitions[0], false)); + $container->setAlias('lock.'.$resourceName.'.store', (new Alias($storeDefinitionId, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "lock.'.$resourceName.'.factory" instead.')); } // Generate factories for each resource $factoryDefinition = new ChildDefinition('lock.factory.abstract'); - $factoryDefinition->replaceArgument(0, new Reference('lock.'.$resourceName.'.store')); + $factoryDefinition->replaceArgument(0, new Reference($storeDefinitionId)); $container->setDefinition('lock.'.$resourceName.'.factory', $factoryDefinition); // Generate services for lock instances @@ -1655,22 +1934,269 @@ class FrameworkExtension extends Extension $lockDefinition->setPublic(false); $lockDefinition->setFactory([new Reference('lock.'.$resourceName.'.factory'), 'createLock']); $lockDefinition->setArguments([$resourceName]); - $container->setDefinition('lock.'.$resourceName, $lockDefinition); + $container->setDefinition('lock.'.$resourceName, $lockDefinition)->setDeprecated('symfony/framework-bundle', '5.2', 'The "%service_id%" service is deprecated, use "lock.'.$resourceName.'.factory" instead.'); // provide alias for default resource if ('default' === $resourceName) { - $container->setAlias('lock.store', new Alias('lock.'.$resourceName.'.store', false)); + $container->setAlias('lock.store', (new Alias($storeDefinitionId, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "lock.factory" instead.')); $container->setAlias('lock.factory', new Alias('lock.'.$resourceName.'.factory', false)); - $container->setAlias('lock', new Alias('lock.'.$resourceName, false)); - $container->setAlias(StoreInterface::class, new Alias('lock.store', false)); - $container->setAlias(Factory::class, new Alias('lock.factory', false)); - $container->setAlias(LockInterface::class, new Alias('lock', false)); + $container->setAlias('lock', (new Alias('lock.'.$resourceName, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "lock.factory" instead.')); + $container->setAlias(PersistingStoreInterface::class, (new Alias($storeDefinitionId, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "'.LockFactory::class.'" instead.')); + $container->setAlias(LockFactory::class, new Alias('lock.factory', false)); + $container->setAlias(LockInterface::class, (new Alias('lock.'.$resourceName, false))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "'.LockFactory::class.'" instead.')); + } else { + $container->registerAliasForArgument($storeDefinitionId, PersistingStoreInterface::class, $resourceName.'.lock.store')->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "'.LockFactory::class.' '.$resourceName.'LockFactory" instead.'); + $container->registerAliasForArgument('lock.'.$resourceName.'.factory', LockFactory::class, $resourceName.'.lock.factory'); + $container->registerAliasForArgument('lock.'.$resourceName, LockInterface::class, $resourceName.'.lock')->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "'.LockFactory::class.' $'.$resourceName.'LockFactory" instead.'); } } } + private function registerMessengerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $validationConfig) + { + if (!interface_exists(MessageBusInterface::class)) { + throw new LogicException('Messenger support cannot be enabled as the Messenger component is not installed. Try running "composer require symfony/messenger".'); + } + + $loader->load('messenger.php'); + + if (!interface_exists(DenormalizerInterface::class)) { + $container->removeDefinition('serializer.normalizer.flatten_exception'); + } + + if (ContainerBuilder::willBeAvailable('symfony/amqp-messenger', AmqpTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'], true)) { + $container->getDefinition('messenger.transport.amqp.factory')->addTag('messenger.transport_factory'); + } + + if (ContainerBuilder::willBeAvailable('symfony/redis-messenger', RedisTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'], true)) { + $container->getDefinition('messenger.transport.redis.factory')->addTag('messenger.transport_factory'); + } + + if (ContainerBuilder::willBeAvailable('symfony/amazon-sqs-messenger', AmazonSqsTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'], true)) { + $container->getDefinition('messenger.transport.sqs.factory')->addTag('messenger.transport_factory'); + } + + if (ContainerBuilder::willBeAvailable('symfony/beanstalkd-messenger', BeanstalkdTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'], true)) { + $container->getDefinition('messenger.transport.beanstalkd.factory')->addTag('messenger.transport_factory'); + } + + if (null === $config['default_bus'] && 1 === \count($config['buses'])) { + $config['default_bus'] = key($config['buses']); + } + + $defaultMiddleware = [ + 'before' => [ + ['id' => 'add_bus_name_stamp_middleware'], + ['id' => 'reject_redelivered_message_middleware'], + ['id' => 'dispatch_after_current_bus'], + ['id' => 'failed_message_processing_middleware'], + ], + 'after' => [ + ['id' => 'send_message'], + ['id' => 'handle_message'], + ], + ]; + foreach ($config['buses'] as $busId => $bus) { + $middleware = $bus['middleware']; + + if ($bus['default_middleware']) { + if ('allow_no_handlers' === $bus['default_middleware']) { + $defaultMiddleware['after'][1]['arguments'] = [true]; + } else { + unset($defaultMiddleware['after'][1]['arguments']); + } + + // argument to add_bus_name_stamp_middleware + $defaultMiddleware['before'][0]['arguments'] = [$busId]; + + $middleware = array_merge($defaultMiddleware['before'], $middleware, $defaultMiddleware['after']); + } + + foreach ($middleware as $middlewareItem) { + if (!$validationConfig['enabled'] && \in_array($middlewareItem['id'], ['validation', 'messenger.middleware.validation'], true)) { + throw new LogicException('The Validation middleware is only available when the Validator component is installed and enabled. Try running "composer require symfony/validator".'); + } + } + + if ($container->getParameter('kernel.debug') && class_exists(Stopwatch::class)) { + array_unshift($middleware, ['id' => 'traceable', 'arguments' => [$busId]]); + } + + $container->setParameter($busId.'.middleware', $middleware); + $container->register($busId, MessageBus::class)->addArgument([])->addTag('messenger.bus'); + + if ($busId === $config['default_bus']) { + $container->setAlias('messenger.default_bus', $busId)->setPublic(true); + $container->setAlias(MessageBusInterface::class, $busId); + } else { + $container->registerAliasForArgument($busId, MessageBusInterface::class); + } + } + + if (empty($config['transports'])) { + $container->removeDefinition('messenger.transport.symfony_serializer'); + $container->removeDefinition('messenger.transport.amqp.factory'); + $container->removeDefinition('messenger.transport.redis.factory'); + $container->removeDefinition('messenger.transport.sqs.factory'); + $container->removeDefinition('messenger.transport.beanstalkd.factory'); + $container->removeAlias(SerializerInterface::class); + } else { + $container->getDefinition('messenger.transport.symfony_serializer') + ->replaceArgument(1, $config['serializer']['symfony_serializer']['format']) + ->replaceArgument(2, $config['serializer']['symfony_serializer']['context']); + $container->setAlias('messenger.default_serializer', $config['serializer']['default_serializer']); + } + + $failureTransports = []; + if ($config['failure_transport']) { + if (!isset($config['transports'][$config['failure_transport']])) { + throw new LogicException(sprintf('Invalid Messenger configuration: the failure transport "%s" is not a valid transport or service id.', $config['failure_transport'])); + } + + $container->setAlias('messenger.failure_transports.default', 'messenger.transport.'.$config['failure_transport']); + $failureTransports[] = $config['failure_transport']; + } + + $failureTransportsByName = []; + foreach ($config['transports'] as $name => $transport) { + if ($transport['failure_transport']) { + $failureTransports[] = $transport['failure_transport']; + $failureTransportsByName[$name] = $transport['failure_transport']; + } elseif ($config['failure_transport']) { + $failureTransportsByName[$name] = $config['failure_transport']; + } + } + + $senderAliases = []; + $transportRetryReferences = []; + foreach ($config['transports'] as $name => $transport) { + $serializerId = $transport['serializer'] ?? 'messenger.default_serializer'; + $transportDefinition = (new Definition(TransportInterface::class)) + ->setFactory([new Reference('messenger.transport_factory'), 'createTransport']) + ->setArguments([$transport['dsn'], $transport['options'] + ['transport_name' => $name], new Reference($serializerId)]) + ->addTag('messenger.receiver', [ + 'alias' => $name, + 'is_failure_transport' => \in_array($name, $failureTransports), + ] + ) + ; + $container->setDefinition($transportId = 'messenger.transport.'.$name, $transportDefinition); + $senderAliases[$name] = $transportId; + + if (null !== $transport['retry_strategy']['service']) { + $transportRetryReferences[$name] = new Reference($transport['retry_strategy']['service']); + } else { + $retryServiceId = sprintf('messenger.retry.multiplier_retry_strategy.%s', $name); + $retryDefinition = new ChildDefinition('messenger.retry.abstract_multiplier_retry_strategy'); + $retryDefinition + ->replaceArgument(0, $transport['retry_strategy']['max_retries']) + ->replaceArgument(1, $transport['retry_strategy']['delay']) + ->replaceArgument(2, $transport['retry_strategy']['multiplier']) + ->replaceArgument(3, $transport['retry_strategy']['max_delay']); + $container->setDefinition($retryServiceId, $retryDefinition); + + $transportRetryReferences[$name] = new Reference($retryServiceId); + } + } + + $senderReferences = []; + // alias => service_id + foreach ($senderAliases as $alias => $serviceId) { + $senderReferences[$alias] = new Reference($serviceId); + } + // service_id => service_id + foreach ($senderAliases as $serviceId) { + $senderReferences[$serviceId] = new Reference($serviceId); + } + + foreach ($config['transports'] as $name => $transport) { + if ($transport['failure_transport']) { + if (!isset($senderReferences[$transport['failure_transport']])) { + throw new LogicException(sprintf('Invalid Messenger configuration: the failure transport "%s" is not a valid transport or service id.', $transport['failure_transport'])); + } + } + } + + $failureTransportReferencesByTransportName = array_map(function ($failureTransportName) use ($senderReferences) { + return $senderReferences[$failureTransportName]; + }, $failureTransportsByName); + + $messageToSendersMapping = []; + foreach ($config['routing'] as $message => $messageConfiguration) { + if ('*' !== $message && !class_exists($message) && !interface_exists($message, false)) { + throw new LogicException(sprintf('Invalid Messenger routing configuration: class or interface "%s" not found.', $message)); + } + + // make sure senderAliases contains all senders + foreach ($messageConfiguration['senders'] as $sender) { + if (!isset($senderReferences[$sender])) { + throw new LogicException(sprintf('Invalid Messenger routing configuration: the "%s" class is being routed to a sender called "%s". This is not a valid transport or service id.', $message, $sender)); + } + } + + $messageToSendersMapping[$message] = $messageConfiguration['senders']; + } + + $sendersServiceLocator = ServiceLocatorTagPass::register($container, $senderReferences); + + $container->getDefinition('messenger.senders_locator') + ->replaceArgument(0, $messageToSendersMapping) + ->replaceArgument(1, $sendersServiceLocator) + ; + + $container->getDefinition('messenger.retry.send_failed_message_for_retry_listener') + ->replaceArgument(0, $sendersServiceLocator) + ; + + $container->getDefinition('messenger.retry_strategy_locator') + ->replaceArgument(0, $transportRetryReferences); + + if (\count($failureTransports) > 0) { + $container->getDefinition('console.command.messenger_failed_messages_retry') + ->replaceArgument(0, $config['failure_transport']); + $container->getDefinition('console.command.messenger_failed_messages_show') + ->replaceArgument(0, $config['failure_transport']); + $container->getDefinition('console.command.messenger_failed_messages_remove') + ->replaceArgument(0, $config['failure_transport']); + + $failureTransportsByTransportNameServiceLocator = ServiceLocatorTagPass::register($container, $failureTransportReferencesByTransportName); + $container->getDefinition('messenger.failure.send_failed_message_to_failure_transport_listener') + ->replaceArgument(0, $failureTransportsByTransportNameServiceLocator); + } else { + $container->removeDefinition('messenger.failure.send_failed_message_to_failure_transport_listener'); + $container->removeDefinition('console.command.messenger_failed_messages_retry'); + $container->removeDefinition('console.command.messenger_failed_messages_show'); + $container->removeDefinition('console.command.messenger_failed_messages_remove'); + } + + if (false === $config['reset_on_message']) { + throw new LogicException('The "framework.messenger.reset_on_message" configuration option can be set to "true" only. To prevent services resetting after each message you can set the "--no-reset" option in "messenger:consume" command.'); + } + + if (!$container->hasDefinition('console.command.messenger_consume_messages')) { + $container->removeDefinition('messenger.listener.reset_services'); + } elseif (null === $config['reset_on_message']) { + trigger_deprecation('symfony/framework-bundle', '5.4', 'Not setting the "framework.messenger.reset_on_message" configuration option is deprecated, it will default to "true" in version 6.0.'); + + $container->getDefinition('console.command.messenger_consume_messages')->replaceArgument(5, null); + $container->removeDefinition('messenger.listener.reset_services'); + } + } + private function registerCacheConfiguration(array $config, ContainerBuilder $container) { + if (!class_exists(DefaultMarshaller::class)) { + $container->removeDefinition('cache.default_marshaller'); + } + + if (!class_exists(DoctrineAdapter::class)) { + $container->removeDefinition('cache.adapter.doctrine'); + } + + if (!class_exists(DoctrineDbalAdapter::class)) { + $container->removeDefinition('cache.adapter.doctrine_dbal'); + } + $version = new Parameter('container.build_id'); $container->getDefinition('cache.adapter.apcu')->replaceArgument(2, $version); $container->getDefinition('cache.adapter.system')->replaceArgument(2, $version); @@ -1683,21 +2209,80 @@ class FrameworkExtension extends Extension // Inline any env vars referenced in the parameter $container->setParameter('cache.prefix.seed', $container->resolveEnvPlaceholders($container->getParameter('cache.prefix.seed'), true)); } - foreach (['doctrine', 'psr6', 'redis', 'memcached'] as $name) { + foreach (['doctrine', 'psr6', 'redis', 'memcached', 'doctrine_dbal', 'pdo'] as $name) { if (isset($config[$name = 'default_'.$name.'_provider'])) { - $container->setAlias('cache.'.$name, new Alias(Compiler\CachePoolPass::getServiceProvider($container, $config[$name]), false)); + $container->setAlias('cache.'.$name, new Alias(CachePoolPass::getServiceProvider($container, $config[$name]), false)); } } foreach (['app', 'system'] as $name) { $config['pools']['cache.'.$name] = [ - 'adapter' => $config[$name], + 'adapters' => [$config[$name]], 'public' => true, + 'tags' => false, ]; } foreach ($config['pools'] as $name => $pool) { - $definition = new ChildDefinition($pool['adapter']); + $pool['adapters'] = $pool['adapters'] ?: ['cache.app']; + + $isRedisTagAware = ['cache.adapter.redis_tag_aware'] === $pool['adapters']; + foreach ($pool['adapters'] as $provider => $adapter) { + if (($config['pools'][$adapter]['adapters'] ?? null) === ['cache.adapter.redis_tag_aware']) { + $isRedisTagAware = true; + } elseif ($config['pools'][$adapter]['tags'] ?? false) { + $pool['adapters'][$provider] = $adapter = '.'.$adapter.'.inner'; + } + } + + if (1 === \count($pool['adapters'])) { + if (!isset($pool['provider']) && !\is_int($provider)) { + $pool['provider'] = $provider; + } + $definition = new ChildDefinition($adapter); + } else { + $definition = new Definition(ChainAdapter::class, [$pool['adapters'], 0]); + $pool['reset'] = 'reset'; + } + + if ($isRedisTagAware && 'cache.app' === $name) { + $container->setAlias('cache.app.taggable', $name); + } elseif ($isRedisTagAware) { + $tagAwareId = $name; + $container->setAlias('.'.$name.'.inner', $name); + } elseif ($pool['tags']) { + if (true !== $pool['tags'] && ($config['pools'][$pool['tags']]['tags'] ?? false)) { + $pool['tags'] = '.'.$pool['tags'].'.inner'; + } + $container->register($name, TagAwareAdapter::class) + ->addArgument(new Reference('.'.$name.'.inner')) + ->addArgument(true !== $pool['tags'] ? new Reference($pool['tags']) : null) + ->setPublic($pool['public']) + ; + + if (method_exists(TagAwareAdapter::class, 'setLogger')) { + $container + ->getDefinition($name) + ->addMethodCall('setLogger', [new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]) + ->addTag('monolog.logger', ['channel' => 'cache']); + } + + $pool['name'] = $tagAwareId = $name; + $pool['public'] = false; + $name = '.'.$name.'.inner'; + } elseif (!\in_array($name, ['cache.app', 'cache.system'], true)) { + $tagAwareId = '.'.$name.'.taggable'; + $container->register($tagAwareId, TagAwareAdapter::class) + ->addArgument(new Reference($name)) + ; + } + + if (!\in_array($name, ['cache.app', 'cache.system'], true)) { + $container->registerAliasForArgument($tagAwareId, TagAwareCacheInterface::class, $pool['name'] ?? $name); + $container->registerAliasForArgument($name, CacheInterface::class, $pool['name'] ?? $name); + $container->registerAliasForArgument($name, CacheItemPoolInterface::class, $pool['name'] ?? $name); + } + $definition->setPublic($pool['public']); - unset($pool['adapter'], $pool['public']); + unset($pool['adapters'], $pool['public'], $pool['tags']); $definition->addTag('cache.pool', $pool); $container->setDefinition($name, $definition); @@ -1709,7 +2294,7 @@ class FrameworkExtension extends Extension if (!$container->getParameter('kernel.debug')) { $propertyAccessDefinition->setFactory([PropertyAccessor::class, 'createCache']); - $propertyAccessDefinition->setArguments([null, 0, $version, new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]); + $propertyAccessDefinition->setArguments(['', 0, $version, new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]); $propertyAccessDefinition->addTag('cache.pool', ['clearer' => 'cache.system_clearer']); $propertyAccessDefinition->addTag('monolog.logger', ['channel' => 'cache']); } else { @@ -1717,28 +2302,393 @@ class FrameworkExtension extends Extension $propertyAccessDefinition->setArguments([0, false]); } } + } - if (\PHP_VERSION_ID < 70000) { - $this->addClassesToCompile([ - 'Symfony\Component\Cache\Adapter\ApcuAdapter', - 'Symfony\Component\Cache\Adapter\FilesystemAdapter', - 'Symfony\Component\Cache\CacheItem', - ]); + private function registerHttpClientConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $profilerConfig) + { + $loader->load('http_client.php'); + + $options = $config['default_options'] ?? []; + $retryOptions = $options['retry_failed'] ?? ['enabled' => false]; + unset($options['retry_failed']); + $container->getDefinition('http_client')->setArguments([$options, $config['max_host_connections'] ?? 6]); + + if (!$hasPsr18 = ContainerBuilder::willBeAvailable('psr/http-client', ClientInterface::class, ['symfony/framework-bundle', 'symfony/http-client'], true)) { + $container->removeDefinition('psr18.http_client'); + $container->removeAlias(ClientInterface::class); + } + + if (!ContainerBuilder::willBeAvailable('php-http/httplug', HttpClient::class, ['symfony/framework-bundle', 'symfony/http-client'], true)) { + $container->removeDefinition(HttpClient::class); + } + + if ($this->isConfigEnabled($container, $retryOptions)) { + $this->registerRetryableHttpClient($retryOptions, 'http_client', $container); + } + + $httpClientId = ($retryOptions['enabled'] ?? false) ? 'http_client.retryable.inner' : ($this->isConfigEnabled($container, $profilerConfig) ? '.debug.http_client.inner' : 'http_client'); + foreach ($config['scoped_clients'] as $name => $scopeConfig) { + if ('http_client' === $name) { + throw new InvalidArgumentException(sprintf('Invalid scope name: "%s" is reserved.', $name)); + } + + $scope = $scopeConfig['scope'] ?? null; + unset($scopeConfig['scope']); + $retryOptions = $scopeConfig['retry_failed'] ?? ['enabled' => false]; + unset($scopeConfig['retry_failed']); + + if (null === $scope) { + $baseUri = $scopeConfig['base_uri']; + unset($scopeConfig['base_uri']); + + $container->register($name, ScopingHttpClient::class) + ->setFactory([ScopingHttpClient::class, 'forBaseUri']) + ->setArguments([new Reference($httpClientId), $baseUri, $scopeConfig]) + ->addTag('http_client.client') + ; + } else { + $container->register($name, ScopingHttpClient::class) + ->setArguments([new Reference($httpClientId), [$scope => $scopeConfig], $scope]) + ->addTag('http_client.client') + ; + } + + if ($this->isConfigEnabled($container, $retryOptions)) { + $this->registerRetryableHttpClient($retryOptions, $name, $container); + } + + $container->registerAliasForArgument($name, HttpClientInterface::class); + + if ($hasPsr18) { + $container->setDefinition('psr18.'.$name, new ChildDefinition('psr18.http_client')) + ->replaceArgument(0, new Reference($name)); + + $container->registerAliasForArgument('psr18.'.$name, ClientInterface::class, $name); + } + } + + if ($responseFactoryId = $config['mock_response_factory'] ?? null) { + $container->register($httpClientId.'.mock_client', MockHttpClient::class) + ->setDecoratedService($httpClientId, null, -10) // lower priority than TraceableHttpClient + ->setArguments([new Reference($responseFactoryId)]); } } - /** - * Gets a hash of the kernel root directory. - * - * @return string - */ - private function getKernelRootHash(ContainerBuilder $container) + private function registerRetryableHttpClient(array $options, string $name, ContainerBuilder $container) { - if (!$this->kernelRootHash) { - $this->kernelRootHash = hash('sha256', $container->getParameter('kernel.root_dir')); + if (!class_exists(RetryableHttpClient::class)) { + throw new LogicException('Support for retrying failed requests requires symfony/http-client 5.2 or higher, try upgrading.'); } - return $this->kernelRootHash; + if (null !== $options['retry_strategy']) { + $retryStrategy = new Reference($options['retry_strategy']); + } else { + $retryStrategy = new ChildDefinition('http_client.abstract_retry_strategy'); + $codes = []; + foreach ($options['http_codes'] as $code => $codeOptions) { + if ($codeOptions['methods']) { + $codes[$code] = $codeOptions['methods']; + } else { + $codes[] = $code; + } + } + + $retryStrategy + ->replaceArgument(0, $codes ?: GenericRetryStrategy::DEFAULT_RETRY_STATUS_CODES) + ->replaceArgument(1, $options['delay']) + ->replaceArgument(2, $options['multiplier']) + ->replaceArgument(3, $options['max_delay']) + ->replaceArgument(4, $options['jitter']); + $container->setDefinition($name.'.retry_strategy', $retryStrategy); + + $retryStrategy = new Reference($name.'.retry_strategy'); + } + + $container + ->register($name.'.retryable', RetryableHttpClient::class) + ->setDecoratedService($name, null, 10) // higher priority than TraceableHttpClient + ->setArguments([new Reference($name.'.retryable.inner'), $retryStrategy, $options['max_retries'], new Reference('logger')]) + ->addTag('monolog.logger', ['channel' => 'http_client']); + } + + private function registerMailerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + { + if (!class_exists(Mailer::class)) { + throw new LogicException('Mailer support cannot be enabled as the component is not installed. Try running "composer require symfony/mailer".'); + } + + $loader->load('mailer.php'); + $loader->load('mailer_transports.php'); + if (!\count($config['transports']) && null === $config['dsn']) { + $config['dsn'] = 'smtp://null'; + } + $transports = $config['dsn'] ? ['main' => $config['dsn']] : $config['transports']; + $container->getDefinition('mailer.transports')->setArgument(0, $transports); + $container->getDefinition('mailer.default_transport')->setArgument(0, current($transports)); + + $container->removeDefinition('mailer.logger_message_listener'); + $container->setAlias('mailer.logger_message_listener', (new Alias('mailer.message_logger_listener'))->setDeprecated('symfony/framework-bundle', '5.2', 'The "%alias_id%" alias is deprecated, use "mailer.message_logger_listener" instead.')); + + $mailer = $container->getDefinition('mailer.mailer'); + if (false === $messageBus = $config['message_bus']) { + $mailer->replaceArgument(1, null); + } else { + $mailer->replaceArgument(1, $messageBus ? new Reference($messageBus) : new Reference('messenger.default_bus', ContainerInterface::NULL_ON_INVALID_REFERENCE)); + } + + $classToServices = [ + GmailTransportFactory::class => 'mailer.transport_factory.gmail', + MailgunTransportFactory::class => 'mailer.transport_factory.mailgun', + MailjetTransportFactory::class => 'mailer.transport_factory.mailjet', + MandrillTransportFactory::class => 'mailer.transport_factory.mailchimp', + OhMySmtpTransportFactory::class => 'mailer.transport_factory.ohmysmtp', + PostmarkTransportFactory::class => 'mailer.transport_factory.postmark', + SendgridTransportFactory::class => 'mailer.transport_factory.sendgrid', + SendinblueTransportFactory::class => 'mailer.transport_factory.sendinblue', + SesTransportFactory::class => 'mailer.transport_factory.amazon', + ]; + + foreach ($classToServices as $class => $service) { + $package = substr($service, \strlen('mailer.transport_factory.')); + + if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-mailer', 'gmail' === $package ? 'google' : $package), $class, ['symfony/framework-bundle', 'symfony/mailer'], true)) { + $container->removeDefinition($service); + } + } + + $envelopeListener = $container->getDefinition('mailer.envelope_listener'); + $envelopeListener->setArgument(0, $config['envelope']['sender'] ?? null); + $envelopeListener->setArgument(1, $config['envelope']['recipients'] ?? null); + + if ($config['headers']) { + $headers = new Definition(Headers::class); + foreach ($config['headers'] as $name => $data) { + $value = $data['value']; + if (\in_array(strtolower($name), ['from', 'to', 'cc', 'bcc', 'reply-to'])) { + $value = (array) $value; + } + $headers->addMethodCall('addHeader', [$name, $value]); + } + $messageListener = $container->getDefinition('mailer.message_listener'); + $messageListener->setArgument(0, $headers); + } else { + $container->removeDefinition('mailer.message_listener'); + } + } + + private function registerNotifierConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + { + if (!class_exists(Notifier::class)) { + throw new LogicException('Notifier support cannot be enabled as the component is not installed. Try running "composer require symfony/notifier".'); + } + + $loader->load('notifier.php'); + $loader->load('notifier_transports.php'); + + if ($config['chatter_transports']) { + $container->getDefinition('chatter.transports')->setArgument(0, $config['chatter_transports']); + } else { + $container->removeDefinition('chatter'); + } + if ($config['texter_transports']) { + $container->getDefinition('texter.transports')->setArgument(0, $config['texter_transports']); + } else { + $container->removeDefinition('texter'); + } + + if ($this->mailerConfigEnabled) { + $sender = $container->getDefinition('mailer.envelope_listener')->getArgument(0); + $container->getDefinition('notifier.channel.email')->setArgument(2, $sender); + } else { + $container->removeDefinition('notifier.channel.email'); + } + + if ($this->messengerConfigEnabled) { + if ($config['notification_on_failed_messages']) { + $container->getDefinition('notifier.failed_message_listener')->addTag('kernel.event_subscriber'); + } + + // as we have a bus, the channels don't need the transports + $container->getDefinition('notifier.channel.chat')->setArgument(0, null); + if ($container->hasDefinition('notifier.channel.email')) { + $container->getDefinition('notifier.channel.email')->setArgument(0, null); + } + $container->getDefinition('notifier.channel.sms')->setArgument(0, null); + $container->getDefinition('notifier.channel.push')->setArgument(0, null); + } + + $container->getDefinition('notifier.channel_policy')->setArgument(0, $config['channel_policy']); + + $container->registerForAutoconfiguration(NotifierTransportFactoryInterface::class) + ->addTag('chatter.transport_factory'); + + $container->registerForAutoconfiguration(NotifierTransportFactoryInterface::class) + ->addTag('texter.transport_factory'); + + $classToServices = [ + AllMySmsTransportFactory::class => 'notifier.transport_factory.all-my-sms', + AmazonSnsTransportFactory::class => 'notifier.transport_factory.amazon-sns', + ClickatellTransportFactory::class => 'notifier.transport_factory.clickatell', + DiscordTransportFactory::class => 'notifier.transport_factory.discord', + EsendexTransportFactory::class => 'notifier.transport_factory.esendex', + ExpoTransportFactory::class => 'notifier.transport_factory.expo', + FakeChatTransportFactory::class => 'notifier.transport_factory.fake-chat', + FakeSmsTransportFactory::class => 'notifier.transport_factory.fake-sms', + FirebaseTransportFactory::class => 'notifier.transport_factory.firebase', + FreeMobileTransportFactory::class => 'notifier.transport_factory.free-mobile', + GatewayApiTransportFactory::class => 'notifier.transport_factory.gateway-api', + GitterTransportFactory::class => 'notifier.transport_factory.gitter', + GoogleChatTransportFactory::class => 'notifier.transport_factory.google-chat', + InfobipTransportFactory::class => 'notifier.transport_factory.infobip', + IqsmsTransportFactory::class => 'notifier.transport_factory.iqsms', + LightSmsTransportFactory::class => 'notifier.transport_factory.light-sms', + LinkedInTransportFactory::class => 'notifier.transport_factory.linked-in', + MailjetNotifierTransportFactory::class => 'notifier.transport_factory.mailjet', + MattermostTransportFactory::class => 'notifier.transport_factory.mattermost', + MercureTransportFactory::class => 'notifier.transport_factory.mercure', + MessageBirdTransport::class => 'notifier.transport_factory.message-bird', + MessageMediaTransportFactory::class => 'notifier.transport_factory.message-media', + MicrosoftTeamsTransportFactory::class => 'notifier.transport_factory.microsoft-teams', + MobytTransportFactory::class => 'notifier.transport_factory.mobyt', + NexmoTransportFactory::class => 'notifier.transport_factory.nexmo', + OctopushTransportFactory::class => 'notifier.transport_factory.octopush', + OneSignalTransportFactory::class => 'notifier.transport_factory.one-signal', + OvhCloudTransportFactory::class => 'notifier.transport_factory.ovh-cloud', + RocketChatTransportFactory::class => 'notifier.transport_factory.rocket-chat', + SendinblueNotifierTransportFactory::class => 'notifier.transport_factory.sendinblue', + SinchTransportFactory::class => 'notifier.transport_factory.sinch', + SlackTransportFactory::class => 'notifier.transport_factory.slack', + Sms77TransportFactory::class => 'notifier.transport_factory.sms77', + SmsapiTransportFactory::class => 'notifier.transport_factory.smsapi', + SmsBiurasTransportFactory::class => 'notifier.transport_factory.sms-biuras', + SmscTransportFactory::class => 'notifier.transport_factory.smsc', + SpotHitTransportFactory::class => 'notifier.transport_factory.spot-hit', + TelegramTransportFactory::class => 'notifier.transport_factory.telegram', + TelnyxTransportFactory::class => 'notifier.transport_factory.telnyx', + TurboSmsTransport::class => 'notifier.transport_factory.turbo-sms', + TwilioTransportFactory::class => 'notifier.transport_factory.twilio', + VonageTransportFactory::class => 'notifier.transport_factory.vonage', + YunpianTransportFactory::class => 'notifier.transport_factory.yunpian', + ZulipTransportFactory::class => 'notifier.transport_factory.zulip', + ]; + + $parentPackages = ['symfony/framework-bundle', 'symfony/notifier']; + + foreach ($classToServices as $class => $service) { + $package = substr($service, \strlen('notifier.transport_factory.')); + + if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-notifier', $package), $class, $parentPackages, true)) { + $container->removeDefinition($service); + $container->removeAlias(str_replace('-', '', $service)); // @deprecated to be removed in 6.0 + } + } + + if (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages, true) && ContainerBuilder::willBeAvailable('symfony/mercure-bundle', MercureBundle::class, $parentPackages, true)) { + $container->getDefinition($classToServices[MercureTransportFactory::class]) + ->replaceArgument('$registry', new Reference(HubRegistry::class)); + } elseif (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages, true)) { + $container->removeDefinition($classToServices[MercureTransportFactory::class]); + } + + if (ContainerBuilder::willBeAvailable('symfony/fake-chat-notifier', FakeChatTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier', 'symfony/mailer'], true)) { + $container->getDefinition($classToServices[FakeChatTransportFactory::class]) + ->replaceArgument('$mailer', new Reference('mailer')) + ->replaceArgument('$logger', new Reference('logger')); + } + + if (ContainerBuilder::willBeAvailable('symfony/fake-sms-notifier', FakeSmsTransportFactory::class, ['symfony/framework-bundle', 'symfony/notifier', 'symfony/mailer'], true)) { + $container->getDefinition($classToServices[FakeSmsTransportFactory::class]) + ->replaceArgument('$mailer', new Reference('mailer')) + ->replaceArgument('$logger', new Reference('logger')); + } + + if (isset($config['admin_recipients'])) { + $notifier = $container->getDefinition('notifier'); + foreach ($config['admin_recipients'] as $i => $recipient) { + $id = 'notifier.admin_recipient.'.$i; + $container->setDefinition($id, new Definition(Recipient::class, [$recipient['email'], $recipient['phone']])); + $notifier->addMethodCall('addAdminRecipient', [new Reference($id)]); + } + } + } + + private function registerRateLimiterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + { + $loader->load('rate_limiter.php'); + + foreach ($config['limiters'] as $name => $limiterConfig) { + self::registerRateLimiter($container, $name, $limiterConfig); + } + } + + public static function registerRateLimiter(ContainerBuilder $container, string $name, array $limiterConfig) + { + // default configuration (when used by other DI extensions) + $limiterConfig += ['lock_factory' => 'lock.factory', 'cache_pool' => 'cache.rate_limiter']; + + $limiter = $container->setDefinition($limiterId = 'limiter.'.$name, new ChildDefinition('limiter')); + + if (null !== $limiterConfig['lock_factory']) { + if (!self::$lockConfigEnabled) { + throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be installed and configured.', $name)); + } + + $limiter->replaceArgument(2, new Reference($limiterConfig['lock_factory'])); + } + unset($limiterConfig['lock_factory']); + + $storageId = $limiterConfig['storage_service'] ?? null; + if (null === $storageId) { + $container->register($storageId = 'limiter.storage.'.$name, CacheStorage::class)->addArgument(new Reference($limiterConfig['cache_pool'])); + } + + $limiter->replaceArgument(1, new Reference($storageId)); + unset($limiterConfig['storage_service']); + unset($limiterConfig['cache_pool']); + + $limiterConfig['id'] = $name; + $limiter->replaceArgument(0, $limiterConfig); + + $container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name.'.limiter'); + } + + private function registerUidConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) + { + $loader->load('uid.php'); + + $container->getDefinition('uuid.factory') + ->setArguments([ + $config['default_uuid_version'], + $config['time_based_uuid_version'], + $config['name_based_uuid_version'], + UuidV4::class, + $config['time_based_uuid_node'] ?? null, + $config['name_based_uuid_namespace'] ?? null, + ]) + ; + + if (isset($config['name_based_uuid_namespace'])) { + $container->getDefinition('name_based_uuid.factory') + ->setArguments([$config['name_based_uuid_namespace']]); + } + } + + private function resolveTrustedHeaders(array $headers): int + { + $trustedHeaders = 0; + + foreach ($headers as $h) { + switch ($h) { + case 'forwarded': $trustedHeaders |= Request::HEADER_FORWARDED; break; + case 'x-forwarded-for': $trustedHeaders |= Request::HEADER_X_FORWARDED_FOR; break; + case 'x-forwarded-host': $trustedHeaders |= Request::HEADER_X_FORWARDED_HOST; break; + case 'x-forwarded-proto': $trustedHeaders |= Request::HEADER_X_FORWARDED_PROTO; break; + case 'x-forwarded-port': $trustedHeaders |= Request::HEADER_X_FORWARDED_PORT; break; + case 'x-forwarded-prefix': $trustedHeaders |= Request::HEADER_X_FORWARDED_PREFIX; break; + } + } + + return $trustedHeaders; } /** diff --git a/lib/symfony/framework-bundle/EventListener/ResolveControllerNameSubscriber.php b/lib/symfony/framework-bundle/EventListener/ResolveControllerNameSubscriber.php deleted file mode 100644 index ffb32b2f2b..0000000000 --- a/lib/symfony/framework-bundle/EventListener/ResolveControllerNameSubscriber.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\EventListener; - -use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; -use Symfony\Component\HttpKernel\KernelEvents; - -/** - * Guarantees that the _controller key is parsed into its final format. - * - * @author Ryan Weaver - */ -class ResolveControllerNameSubscriber implements EventSubscriberInterface -{ - private $parser; - - public function __construct(ControllerNameParser $parser) - { - $this->parser = $parser; - } - - public function onKernelRequest(GetResponseEvent $event) - { - $controller = $event->getRequest()->attributes->get('_controller'); - if (\is_string($controller) && false === strpos($controller, '::') && 2 === substr_count($controller, ':')) { - // controller in the a:b:c notation then - $event->getRequest()->attributes->set('_controller', $this->parser->parse($controller)); - } - } - - public static function getSubscribedEvents() - { - return [ - KernelEvents::REQUEST => ['onKernelRequest', 24], - ]; - } -} diff --git a/lib/symfony/framework-bundle/EventListener/SessionListener.php b/lib/symfony/framework-bundle/EventListener/SessionListener.php deleted file mode 100644 index 68dd2d23fd..0000000000 --- a/lib/symfony/framework-bundle/EventListener/SessionListener.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\EventListener; - -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\HttpKernel\EventListener\AbstractSessionListener; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use Symfony\Component\HttpKernel\EventListener\SessionListener instead.', SessionListener::class), \E_USER_DEPRECATED); - -/** - * Sets the session in the request. - * - * @author Fabien Potencier - * - * @deprecated since version 3.3, to be removed in 4.0. Use Symfony\Component\HttpKernel\EventListener\SessionListener instead - */ -class SessionListener extends AbstractSessionListener -{ - private $container; - - public function __construct(ContainerInterface $container) - { - $this->container = $container; - } - - protected function getSession() - { - return $this->container->get('session', ContainerInterface::NULL_ON_INVALID_REFERENCE); - } -} diff --git a/lib/symfony/framework-bundle/EventListener/SuggestMissingPackageSubscriber.php b/lib/symfony/framework-bundle/EventListener/SuggestMissingPackageSubscriber.php new file mode 100644 index 0000000000..53cae12ebb --- /dev/null +++ b/lib/symfony/framework-bundle/EventListener/SuggestMissingPackageSubscriber.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\EventListener; + +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Exception\CommandNotFoundException; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Suggests a package, that should be installed (via composer), + * if the package is missing, and the input command namespace can be mapped to a Symfony bundle. + * + * @author Przemysław Bogusz + * + * @internal + */ +final class SuggestMissingPackageSubscriber implements EventSubscriberInterface +{ + private const PACKAGES = [ + 'doctrine' => [ + 'fixtures' => ['DoctrineFixturesBundle', 'doctrine/doctrine-fixtures-bundle --dev'], + 'mongodb' => ['DoctrineMongoDBBundle', 'doctrine/mongodb-odm-bundle'], + '_default' => ['Doctrine ORM', 'symfony/orm-pack'], + ], + 'generate' => [ + '_default' => ['SensioGeneratorBundle', 'sensio/generator-bundle'], + ], + 'make' => [ + '_default' => ['MakerBundle', 'symfony/maker-bundle --dev'], + ], + 'server' => [ + '_default' => ['Debug Bundle', 'symfony/debug-bundle --dev'], + ], + ]; + + public function onConsoleError(ConsoleErrorEvent $event): void + { + if (!$event->getError() instanceof CommandNotFoundException) { + return; + } + + [$namespace, $command] = explode(':', $event->getInput()->getFirstArgument()) + [1 => '']; + + if (!isset(self::PACKAGES[$namespace])) { + return; + } + + if (isset(self::PACKAGES[$namespace][$command])) { + $suggestion = self::PACKAGES[$namespace][$command]; + $exact = true; + } else { + $suggestion = self::PACKAGES[$namespace]['_default']; + $exact = false; + } + + $error = $event->getError(); + + if ($error->getAlternatives() && !$exact) { + return; + } + + $message = sprintf("%s\n\nYou may be looking for a command provided by the \"%s\" which is currently not installed. Try running \"composer require %s\".", $error->getMessage(), $suggestion[0], $suggestion[1]); + $event->setError(new CommandNotFoundException($message)); + } + + public static function getSubscribedEvents(): array + { + return [ + ConsoleEvents::ERROR => ['onConsoleError', 0], + ]; + } +} diff --git a/lib/symfony/framework-bundle/EventListener/TestSessionListener.php b/lib/symfony/framework-bundle/EventListener/TestSessionListener.php deleted file mode 100644 index e21f474540..0000000000 --- a/lib/symfony/framework-bundle/EventListener/TestSessionListener.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\EventListener; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use Symfony\Component\HttpKernel\EventListener\TestSessionListener instead.', TestSessionListener::class), \E_USER_DEPRECATED); - -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\HttpKernel\EventListener\AbstractTestSessionListener; - -/** - * TestSessionListener. - * - * @author Fabien Potencier - * - * @deprecated since version 3.3, to be removed in 4.0. - */ -class TestSessionListener extends AbstractTestSessionListener -{ - protected $container; - - public function __construct(ContainerInterface $container) - { - $this->container = $container; - } - - protected function getSession() - { - return $this->container->get('session', ContainerInterface::NULL_ON_INVALID_REFERENCE); - } -} diff --git a/lib/symfony/framework-bundle/FrameworkBundle.php b/lib/symfony/framework-bundle/FrameworkBundle.php index f95157202c..4ec54eccf5 100644 --- a/lib/symfony/framework-bundle/FrameworkBundle.php +++ b/lib/symfony/framework-bundle/FrameworkBundle.php @@ -14,44 +14,72 @@ namespace Symfony\Bundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddAnnotationsCachedReaderPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddDebugLogProcessorPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass; -use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CacheCollectorPass; -use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolClearerPass; -use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolPass; -use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolPrunerPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AssetsContextPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ContainerBuilderDebugDumpPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\DataCollectorTranslatorPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\LoggingTranslatorPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RemoveUnusedSessionMarshallingHandlerPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\SessionPass; -use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerRealRefPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerWeakRefPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\WorkflowGuardListenerPass; +use Symfony\Component\Cache\Adapter\ApcuAdapter; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\ChainAdapter; +use Symfony\Component\Cache\Adapter\PhpArrayAdapter; +use Symfony\Component\Cache\Adapter\PhpFilesAdapter; +use Symfony\Component\Cache\DependencyInjection\CacheCollectorPass; +use Symfony\Component\Cache\DependencyInjection\CachePoolClearerPass; +use Symfony\Component\Cache\DependencyInjection\CachePoolPass; +use Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass; use Symfony\Component\Config\Resource\ClassExistenceResource; -use Symfony\Component\Console\Application; +use Symfony\Component\Console\ConsoleEvents; use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass; -use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\Dotenv\Dotenv; +use Symfony\Component\ErrorHandler\ErrorHandler; use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; use Symfony\Component\Form\DependencyInjection\FormPass; +use Symfony\Component\HttpClient\DependencyInjection\HttpClientPass; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass; use Symfony\Component\HttpKernel\DependencyInjection\FragmentRendererPass; use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass; use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass; +use Symfony\Component\HttpKernel\DependencyInjection\RegisterLocaleAwareServicesPass; use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass; use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Messenger\DependencyInjection\MessengerPass; +use Symfony\Component\Mime\DependencyInjection\AddMimeTypeGuesserPass; use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass; use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass; use Symfony\Component\Serializer\DependencyInjection\SerializerPass; use Symfony\Component\Translation\DependencyInjection\TranslationDumperPass; use Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass; use Symfony\Component\Translation\DependencyInjection\TranslatorPass; +use Symfony\Component\Translation\DependencyInjection\TranslatorPathsPass; +use Symfony\Component\Validator\DependencyInjection\AddAutoMappingConfigurationPass; use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass; use Symfony\Component\Validator\DependencyInjection\AddValidatorInitializersPass; -use Symfony\Component\Workflow\DependencyInjection\ValidateWorkflowsPass; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\Registry; + +// Help opcache.preload discover always-needed symbols +class_exists(ApcuAdapter::class); +class_exists(ArrayAdapter::class); +class_exists(ChainAdapter::class); +class_exists(PhpArrayAdapter::class); +class_exists(PhpFilesAdapter::class); +class_exists(Dotenv::class); +class_exists(ErrorHandler::class); +class_exists(Hydrator::class); +class_exists(Registry::class); /** * Bundle. @@ -64,79 +92,84 @@ class FrameworkBundle extends Bundle { ErrorHandler::register(null, false)->throwAt($this->container->getParameter('debug.error_handler.throw_at'), true); - if ($this->container->hasParameter('kernel.trusted_proxies')) { - @trigger_error('The "kernel.trusted_proxies" parameter is deprecated since Symfony 3.3 and will be removed in 4.0. Use the Request::setTrustedProxies() method in your front controller instead.', \E_USER_DEPRECATED); - - if ($trustedProxies = $this->container->getParameter('kernel.trusted_proxies')) { - Request::setTrustedProxies($trustedProxies, Request::getTrustedHeaderSet()); - } - } - if ($this->container->getParameter('kernel.http_method_override')) { Request::enableHttpMethodParameterOverride(); } - - if ($trustedHosts = $this->container->getParameter('kernel.trusted_hosts')) { - Request::setTrustedHosts($trustedHosts); - } } public function build(ContainerBuilder $container) { parent::build($container); - $hotPathEvents = [ + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->setHotPathEvents([ KernelEvents::REQUEST, KernelEvents::CONTROLLER, KernelEvents::CONTROLLER_ARGUMENTS, KernelEvents::RESPONSE, KernelEvents::FINISH_REQUEST, - ]; + ]); + if (class_exists(ConsoleEvents::class)) { + $registerListenersPass->setNoPreloadEvents([ + ConsoleEvents::COMMAND, + ConsoleEvents::TERMINATE, + ConsoleEvents::ERROR, + ]); + } + $container->addCompilerPass(new AssetsContextPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION); $container->addCompilerPass(new LoggerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); $container->addCompilerPass(new RegisterControllerArgumentLocatorsPass()); $container->addCompilerPass(new RemoveEmptyControllerArgumentLocatorsPass(), PassConfig::TYPE_BEFORE_REMOVING); $container->addCompilerPass(new RoutingResolverPass()); + $container->addCompilerPass(new DataCollectorTranslatorPass()); $container->addCompilerPass(new ProfilerPass()); // must be registered before removing private services as some might be listeners/subscribers // but as late as possible to get resolved parameters - $container->addCompilerPass((new RegisterListenersPass())->setHotPathEvents($hotPathEvents), PassConfig::TYPE_BEFORE_REMOVING); - $container->addCompilerPass(new TemplatingPass()); + $container->addCompilerPass($registerListenersPass, PassConfig::TYPE_BEFORE_REMOVING); $this->addCompilerPassIfExists($container, AddConstraintValidatorsPass::class); $container->addCompilerPass(new AddAnnotationsCachedReaderPass(), PassConfig::TYPE_AFTER_REMOVING, -255); $this->addCompilerPassIfExists($container, AddValidatorInitializersPass::class); $this->addCompilerPassIfExists($container, AddConsoleCommandPass::class, PassConfig::TYPE_BEFORE_REMOVING); - if (class_exists(TranslatorPass::class)) { - // Arguments to be removed in 4.0, relying on the default values - $container->addCompilerPass(new TranslatorPass('translator.default', 'translation.loader')); - } + // must be registered as late as possible to get access to all Twig paths registered in + // twig.template_iterator definition + $this->addCompilerPassIfExists($container, TranslatorPass::class, PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); + $this->addCompilerPassIfExists($container, TranslatorPathsPass::class, PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new LoggingTranslatorPass()); - $container->addCompilerPass(new AddExpressionLanguageProvidersPass()); + $container->addCompilerPass(new AddExpressionLanguageProvidersPass(false)); $this->addCompilerPassIfExists($container, TranslationExtractorPass::class); $this->addCompilerPassIfExists($container, TranslationDumperPass::class); $container->addCompilerPass(new FragmentRendererPass()); $this->addCompilerPassIfExists($container, SerializerPass::class); $this->addCompilerPassIfExists($container, PropertyInfoPass::class); - $container->addCompilerPass(new DataCollectorTranslatorPass()); $container->addCompilerPass(new ControllerArgumentValueResolverPass()); $container->addCompilerPass(new CachePoolPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 32); - $this->addCompilerPassIfExists($container, ValidateWorkflowsPass::class); $container->addCompilerPass(new CachePoolClearerPass(), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new CachePoolPrunerPass(), PassConfig::TYPE_AFTER_REMOVING); $this->addCompilerPassIfExists($container, FormPass::class); $container->addCompilerPass(new WorkflowGuardListenerPass()); - $container->addCompilerPass(new ResettableServicePass()); + $container->addCompilerPass(new ResettableServicePass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); + $container->addCompilerPass(new RegisterLocaleAwareServicesPass()); + $container->addCompilerPass(new TestServiceContainerWeakRefPass(), PassConfig::TYPE_BEFORE_REMOVING, -32); + $container->addCompilerPass(new TestServiceContainerRealRefPass(), PassConfig::TYPE_AFTER_REMOVING); + $this->addCompilerPassIfExists($container, AddMimeTypeGuesserPass::class); + $this->addCompilerPassIfExists($container, MessengerPass::class); + $this->addCompilerPassIfExists($container, HttpClientPass::class); + $this->addCompilerPassIfExists($container, AddAutoMappingConfigurationPass::class); + $container->addCompilerPass(new RegisterReverseContainerPass(true)); + $container->addCompilerPass(new RegisterReverseContainerPass(false), PassConfig::TYPE_AFTER_REMOVING); + $container->addCompilerPass(new RemoveUnusedSessionMarshallingHandlerPass()); $container->addCompilerPass(new SessionPass()); if ($container->getParameter('kernel.debug')) { - $container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); + $container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 2); $container->addCompilerPass(new UnusedTagsPass(), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_BEFORE_REMOVING, -255); $container->addCompilerPass(new CacheCollectorPass(), PassConfig::TYPE_BEFORE_REMOVING); } } - private function addCompilerPassIfExists(ContainerBuilder $container, $class, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, $priority = 0) + private function addCompilerPassIfExists(ContainerBuilder $container, string $class, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0) { $container->addResource(new ClassExistenceResource($class)); @@ -144,9 +177,4 @@ class FrameworkBundle extends Bundle $container->addCompilerPass(new $class(), $type, $priority); } } - - public function registerCommands(Application $application) - { - // noop - } } diff --git a/lib/symfony/framework-bundle/HttpCache/HttpCache.php b/lib/symfony/framework-bundle/HttpCache/HttpCache.php index c5220f350c..fe38c4adca 100644 --- a/lib/symfony/framework-bundle/HttpCache/HttpCache.php +++ b/lib/symfony/framework-bundle/HttpCache/HttpCache.php @@ -16,6 +16,8 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\HttpCache\Esi; use Symfony\Component\HttpKernel\HttpCache\HttpCache as BaseHttpCache; use Symfony\Component\HttpKernel\HttpCache\Store; +use Symfony\Component\HttpKernel\HttpCache\StoreInterface; +use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface; use Symfony\Component\HttpKernel\KernelInterface; /** @@ -23,64 +25,77 @@ use Symfony\Component\HttpKernel\KernelInterface; * * @author Fabien Potencier */ -abstract class HttpCache extends BaseHttpCache +class HttpCache extends BaseHttpCache { protected $cacheDir; protected $kernel; + private $store; + private $surrogate; + private $options; + /** - * @param KernelInterface $kernel A KernelInterface instance - * @param string $cacheDir The cache directory (default used if null) + * @param string|StoreInterface $cache The cache directory (default used if null) or the storage instance */ - public function __construct(KernelInterface $kernel, $cacheDir = null) + public function __construct(KernelInterface $kernel, $cache = null, SurrogateInterface $surrogate = null, array $options = null) { $this->kernel = $kernel; - $this->cacheDir = $cacheDir; + $this->surrogate = $surrogate; + $this->options = $options ?? []; - $isDebug = $kernel->isDebug(); - $options = ['debug' => $isDebug]; - - if ($isDebug) { - $options['stale_if_error'] = 0; + if ($cache instanceof StoreInterface) { + $this->store = $cache; + } elseif (null !== $cache && !\is_string($cache)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be a string or a SurrogateInterface, "%s" given.', __METHOD__, get_debug_type($cache))); + } else { + $this->cacheDir = $cache; } - parent::__construct($kernel, $this->createStore(), $this->createSurrogate(), array_merge($options, $this->getOptions())); + if (null === $options && $kernel->isDebug()) { + $this->options = ['debug' => true]; + } + + if ($this->options['debug'] ?? false) { + $this->options += ['stale_if_error' => 0]; + } + + parent::__construct($kernel, $this->createStore(), $this->createSurrogate(), array_merge($this->options, $this->getOptions())); } /** - * Forwards the Request to the backend and returns the Response. - * - * @param Request $request A Request instance - * @param bool $raw Whether to catch exceptions or not - * @param Response $entry A Response instance (the stale entry if present, null otherwise) - * - * @return Response A Response instance + * {@inheritdoc} */ - protected function forward(Request $request, $raw = false, Response $entry = null) + protected function forward(Request $request, bool $catch = false, Response $entry = null) { $this->getKernel()->boot(); $this->getKernel()->getContainer()->set('cache', $this); - return parent::forward($request, $raw, $entry); + return parent::forward($request, $catch, $entry); } /** * Returns an array of options to customize the Cache configuration. * - * @return array An array of options + * @return array */ protected function getOptions() { return []; } + /** + * @return SurrogateInterface + */ protected function createSurrogate() { - return new Esi(); + return $this->surrogate ?? new Esi(); } + /** + * @return StoreInterface + */ protected function createStore() { - return new Store($this->cacheDir ?: $this->kernel->getCacheDir().'/http_cache'); + return $this->store ?? new Store($this->cacheDir ?: $this->kernel->getCacheDir().'/http_cache'); } } diff --git a/lib/symfony/framework-bundle/Kernel/MicroKernelTrait.php b/lib/symfony/framework-bundle/Kernel/MicroKernelTrait.php index 3779792d6d..c25f90d63c 100644 --- a/lib/symfony/framework-bundle/Kernel/MicroKernelTrait.php +++ b/lib/symfony/framework-bundle/Kernel/MicroKernelTrait.php @@ -13,7 +13,13 @@ namespace Symfony\Bundle\FrameworkBundle\Kernel; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\DependencyInjection\Loader\Configurator\AbstractConfigurator; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader as ContainerPhpFileLoader; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; +use Symfony\Component\Routing\Loader\PhpFileLoader as RoutingPhpFileLoader; +use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\RouteCollectionBuilder; /** @@ -24,32 +30,109 @@ use Symfony\Component\Routing\RouteCollectionBuilder; */ trait MicroKernelTrait { - /** - * Add or import routes into your application. - * - * $routes->import('config/routing.yml'); - * $routes->add('/admin', 'AppBundle:Admin:dashboard', 'admin_dashboard'); - */ - abstract protected function configureRoutes(RouteCollectionBuilder $routes); - /** * Configures the container. * * You can register extensions: * - * $c->loadFromExtension('framework', [ + * $container->extension('framework', [ * 'secret' => '%secret%' * ]); * * Or services: * - * $c->register('halloween', 'FooBundle\HalloweenProvider'); + * $container->services()->set('halloween', 'FooBundle\HalloweenProvider'); * * Or parameters: * - * $c->setParameter('halloween', 'lot of fun'); + * $container->parameters()->set('halloween', 'lot of fun'); */ - abstract protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader); + private function configureContainer(ContainerConfigurator $container, LoaderInterface $loader, ContainerBuilder $builder): void + { + $configDir = $this->getConfigDir(); + + $container->import($configDir.'/{packages}/*.yaml'); + $container->import($configDir.'/{packages}/'.$this->environment.'/*.yaml'); + + if (is_file($configDir.'/services.yaml')) { + $container->import($configDir.'/services.yaml'); + $container->import($configDir.'/{services}_'.$this->environment.'.yaml'); + } else { + $container->import($configDir.'/{services}.php'); + } + } + + /** + * Adds or imports routes into your application. + * + * $routes->import($this->getConfigDir().'/*.{yaml,php}'); + * $routes + * ->add('admin_dashboard', '/admin') + * ->controller('App\Controller\AdminController::dashboard') + * ; + */ + private function configureRoutes(RoutingConfigurator $routes): void + { + $configDir = $this->getConfigDir(); + + $routes->import($configDir.'/{routes}/'.$this->environment.'/*.yaml'); + $routes->import($configDir.'/{routes}/*.yaml'); + + if (is_file($configDir.'/routes.yaml')) { + $routes->import($configDir.'/routes.yaml'); + } else { + $routes->import($configDir.'/{routes}.php'); + } + } + + /** + * Gets the path to the configuration directory. + */ + private function getConfigDir(): string + { + return $this->getProjectDir().'/config'; + } + + /** + * Gets the path to the bundles configuration file. + */ + private function getBundlesPath(): string + { + return $this->getConfigDir().'/bundles.php'; + } + + /** + * {@inheritdoc} + */ + public function getCacheDir(): string + { + if (isset($_SERVER['APP_CACHE_DIR'])) { + return $_SERVER['APP_CACHE_DIR'].'/'.$this->environment; + } + + return parent::getCacheDir(); + } + + /** + * {@inheritdoc} + */ + public function getLogDir(): string + { + return $_SERVER['APP_LOG_DIR'] ?? parent::getLogDir(); + } + + /** + * {@inheritdoc} + */ + public function registerBundles(): iterable + { + $contents = require $this->getBundlesPath(); + foreach ($contents as $class => $envs) { + if ($envs[$this->environment] ?? $envs['all'] ?? false) { + yield new $class(); + } + } + } /** * {@inheritdoc} @@ -59,33 +142,93 @@ trait MicroKernelTrait $loader->load(function (ContainerBuilder $container) use ($loader) { $container->loadFromExtension('framework', [ 'router' => [ - 'resource' => 'kernel:loadRoutes', + 'resource' => 'kernel::loadRoutes', 'type' => 'service', ], ]); - if ($this instanceof EventSubscriberInterface) { - $container->register('kernel', static::class) + $kernelClass = false !== strpos(static::class, "@anonymous\0") ? parent::class : static::class; + + if (!$container->hasDefinition('kernel')) { + $container->register('kernel', $kernelClass) + ->addTag('controller.service_arguments') + ->setAutoconfigured(true) ->setSynthetic(true) ->setPublic(true) - ->addTag('kernel.event_subscriber') ; } - $this->configureContainer($container, $loader); + $kernelDefinition = $container->getDefinition('kernel'); + $kernelDefinition->addTag('routing.route_loader'); $container->addObjectResource($this); + $container->fileExists($this->getBundlesPath()); + + $configureContainer = new \ReflectionMethod($this, 'configureContainer'); + $configuratorClass = $configureContainer->getNumberOfParameters() > 0 && ($type = $configureContainer->getParameters()[0]->getType()) instanceof \ReflectionNamedType && !$type->isBuiltin() ? $type->getName() : null; + + if ($configuratorClass && !is_a(ContainerConfigurator::class, $configuratorClass, true)) { + $configureContainer->getClosure($this)($container, $loader); + + return; + } + + $file = (new \ReflectionObject($this))->getFileName(); + /* @var ContainerPhpFileLoader $kernelLoader */ + $kernelLoader = $loader->getResolver()->resolve($file); + $kernelLoader->setCurrentDir(\dirname($file)); + $instanceof = &\Closure::bind(function &() { return $this->instanceof; }, $kernelLoader, $kernelLoader)(); + + $valuePreProcessor = AbstractConfigurator::$valuePreProcessor; + AbstractConfigurator::$valuePreProcessor = function ($value) { + return $this === $value ? new Reference('kernel') : $value; + }; + + try { + $configureContainer->getClosure($this)(new ContainerConfigurator($container, $kernelLoader, $instanceof, $file, $file, $this->getEnvironment()), $loader, $container); + } finally { + $instanceof = []; + $kernelLoader->registerAliasesForSinglyImplementedInterfaces(); + AbstractConfigurator::$valuePreProcessor = $valuePreProcessor; + } + + $container->setAlias($kernelClass, 'kernel')->setPublic(true); }); } /** * @internal */ - public function loadRoutes(LoaderInterface $loader) + public function loadRoutes(LoaderInterface $loader): RouteCollection { - $routes = new RouteCollectionBuilder($loader); - $this->configureRoutes($routes); + $file = (new \ReflectionObject($this))->getFileName(); + /* @var RoutingPhpFileLoader $kernelLoader */ + $kernelLoader = $loader->getResolver()->resolve($file, 'php'); + $kernelLoader->setCurrentDir(\dirname($file)); + $collection = new RouteCollection(); - return $routes->build(); + $configureRoutes = new \ReflectionMethod($this, 'configureRoutes'); + $configuratorClass = $configureRoutes->getNumberOfParameters() > 0 && ($type = $configureRoutes->getParameters()[0]->getType()) instanceof \ReflectionNamedType && !$type->isBuiltin() ? $type->getName() : null; + + if ($configuratorClass && !is_a(RoutingConfigurator::class, $configuratorClass, true)) { + trigger_deprecation('symfony/framework-bundle', '5.1', 'Using type "%s" for argument 1 of method "%s:configureRoutes()" is deprecated, use "%s" instead.', RouteCollectionBuilder::class, self::class, RoutingConfigurator::class); + + $routes = new RouteCollectionBuilder($loader); + $this->configureRoutes($routes); + + return $routes->build(); + } + + $configureRoutes->getClosure($this)(new RoutingConfigurator($collection, $kernelLoader, $file, $file, $this->getEnvironment())); + + foreach ($collection as $route) { + $controller = $route->getDefault('_controller'); + + if (\is_array($controller) && [0, 1] === array_keys($controller) && $this === $controller[0]) { + $route->setDefault('_controller', ['kernel', $controller[1]]); + } + } + + return $collection; } } diff --git a/lib/symfony/framework-bundle/KernelBrowser.php b/lib/symfony/framework-bundle/KernelBrowser.php new file mode 100644 index 0000000000..a028530125 --- /dev/null +++ b/lib/symfony/framework-bundle/KernelBrowser.php @@ -0,0 +1,262 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle; + +use Symfony\Bundle\FrameworkBundle\Test\TestBrowserToken; +use Symfony\Component\BrowserKit\Cookie; +use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\BrowserKit\History; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelBrowser; +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\HttpKernel\Profiler\Profile as HttpProfile; +use Symfony\Component\Security\Core\User\UserInterface; + +/** + * Simulates a browser and makes requests to a Kernel object. + * + * @author Fabien Potencier + */ +class KernelBrowser extends HttpKernelBrowser +{ + private $hasPerformedRequest = false; + private $profiler = false; + private $reboot = true; + + /** + * {@inheritdoc} + */ + public function __construct(KernelInterface $kernel, array $server = [], History $history = null, CookieJar $cookieJar = null) + { + parent::__construct($kernel, $server, $history, $cookieJar); + } + + /** + * Returns the container. + * + * @return ContainerInterface + */ + public function getContainer() + { + $container = $this->kernel->getContainer(); + + return $container->has('test.service_container') ? $container->get('test.service_container') : $container; + } + + /** + * Returns the kernel. + * + * @return KernelInterface + */ + public function getKernel() + { + return $this->kernel; + } + + /** + * Gets the profile associated with the current Response. + * + * @return HttpProfile|false|null + */ + public function getProfile() + { + if (null === $this->response || !$this->getContainer()->has('profiler')) { + return false; + } + + return $this->getContainer()->get('profiler')->loadProfileFromResponse($this->response); + } + + /** + * Enables the profiler for the very next request. + * + * If the profiler is not enabled, the call to this method does nothing. + */ + public function enableProfiler() + { + if ($this->getContainer()->has('profiler')) { + $this->profiler = true; + } + } + + /** + * Disables kernel reboot between requests. + * + * By default, the Client reboots the Kernel for each request. This method + * allows to keep the same kernel across requests. + */ + public function disableReboot() + { + $this->reboot = false; + } + + /** + * Enables kernel reboot between requests. + */ + public function enableReboot() + { + $this->reboot = true; + } + + /** + * @param UserInterface $user + * + * @return $this + */ + public function loginUser(object $user, string $firewallContext = 'main'): self + { + if (!interface_exists(UserInterface::class)) { + throw new \LogicException(sprintf('"%s" requires symfony/security-core to be installed.', __METHOD__)); + } + + if (!$user instanceof UserInterface) { + throw new \LogicException(sprintf('The first argument of "%s" must be instance of "%s", "%s" provided.', __METHOD__, UserInterface::class, \is_object($user) ? \get_class($user) : \gettype($user))); + } + + $token = new TestBrowserToken($user->getRoles(), $user, $firewallContext); + // @deprecated since Symfony 5.4 + if (method_exists($token, 'setAuthenticated')) { + $token->setAuthenticated(true, false); + } + + $container = $this->getContainer(); + $container->get('security.untracked_token_storage')->setToken($token); + + if ($container->has('session.factory')) { + $session = $container->get('session.factory')->createSession(); + } elseif ($container->has('session')) { + $session = $container->get('session'); + } else { + return $this; + } + + $session->set('_security_'.$firewallContext, serialize($token)); + $session->save(); + + $domains = array_unique(array_map(function (Cookie $cookie) use ($session) { + return $cookie->getName() === $session->getName() ? $cookie->getDomain() : ''; + }, $this->getCookieJar()->all())) ?: ['']; + foreach ($domains as $domain) { + $cookie = new Cookie($session->getName(), $session->getId(), null, null, $domain); + $this->getCookieJar()->set($cookie); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @param Request $request + * + * @return Response + */ + protected function doRequest(object $request) + { + // avoid shutting down the Kernel if no request has been performed yet + // WebTestCase::createClient() boots the Kernel but do not handle a request + if ($this->hasPerformedRequest && $this->reboot) { + $this->kernel->boot(); + $this->kernel->shutdown(); + } else { + $this->hasPerformedRequest = true; + } + + if ($this->profiler) { + $this->profiler = false; + + $this->kernel->boot(); + $this->getContainer()->get('profiler')->enable(); + } + + return parent::doRequest($request); + } + + /** + * {@inheritdoc} + * + * @param Request $request + * + * @return Response + */ + protected function doRequestInProcess(object $request) + { + $response = parent::doRequestInProcess($request); + + $this->profiler = false; + + return $response; + } + + /** + * Returns the script to execute when the request must be insulated. + * + * It assumes that the autoloader is named 'autoload.php' and that it is + * stored in the same directory as the kernel (this is the case for the + * Symfony Standard Edition). If this is not your case, create your own + * client and override this method. + * + * @param Request $request + * + * @return string + */ + protected function getScript(object $request) + { + $kernel = var_export(serialize($this->kernel), true); + $request = var_export(serialize($request), true); + $errorReporting = error_reporting(); + + $requires = ''; + foreach (get_declared_classes() as $class) { + if (str_starts_with($class, 'ComposerAutoloaderInit')) { + $r = new \ReflectionClass($class); + $file = \dirname($r->getFileName(), 2).'/autoload.php'; + if (is_file($file)) { + $requires .= 'require_once '.var_export($file, true).";\n"; + } + } + } + + if (!$requires) { + throw new \RuntimeException('Composer autoloader not found.'); + } + + $requires .= 'require_once '.var_export((new \ReflectionObject($this->kernel))->getFileName(), true).";\n"; + + $profilerCode = ''; + if ($this->profiler) { + $profilerCode = <<<'EOF' +$container = $kernel->getContainer(); +$container = $container->has('test.service_container') ? $container->get('test.service_container') : $container; +$container->get('profiler')->enable(); +EOF; + } + + $code = <<boot(); +$profilerCode + +\$request = unserialize($request); +EOF; + + return $code.$this->getHandleScript(); + } +} diff --git a/lib/symfony/framework-bundle/LICENSE b/lib/symfony/framework-bundle/LICENSE index 9e936ec044..88bf75bb4d 100644 --- a/lib/symfony/framework-bundle/LICENSE +++ b/lib/symfony/framework-bundle/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2020 Fabien Potencier +Copyright (c) 2004-2022 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/lib/symfony/framework-bundle/README.md b/lib/symfony/framework-bundle/README.md index 9280d87439..76c7700fa0 100644 --- a/lib/symfony/framework-bundle/README.md +++ b/lib/symfony/framework-bundle/README.md @@ -1,10 +1,13 @@ FrameworkBundle =============== +FrameworkBundle provides a tight integration between Symfony components and the +Symfony full-stack framework. + Resources --------- - * [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) + * [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/lib/symfony/framework-bundle/Resources/bin/check-unused-known-tags.php b/lib/symfony/framework-bundle/Resources/bin/check-unused-known-tags.php index ec9ae1f97c..4920c43ebe 100644 --- a/lib/symfony/framework-bundle/Resources/bin/check-unused-known-tags.php +++ b/lib/symfony/framework-bundle/Resources/bin/check-unused-known-tags.php @@ -15,5 +15,5 @@ use Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\UnusedTags $target = dirname(__DIR__, 2).'/DependencyInjection/Compiler/UnusedTagsPass.php'; $contents = file_get_contents($target); -$contents = preg_replace('{private \$knownTags = \[(.+?)\];}sm', "private \$knownTags = [\n '".implode("',\n '", UnusedTagsPassUtils::getDefinedTags())."',\n ];", $contents); +$contents = preg_replace('{private const KNOWN_TAGS = \[(.+?)\];}sm', "private const KNOWN_TAGS = [\n '".implode("',\n '", UnusedTagsPassUtils::getDefinedTags())."',\n ];", $contents); file_put_contents($target, $contents); diff --git a/lib/symfony/framework-bundle/Resources/config/annotations.php b/lib/symfony/framework-bundle/Resources/config/annotations.php new file mode 100644 index 0000000000..64e3f4e31d --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/annotations.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 Symfony\Component\DependencyInjection\Loader\Configurator; + +use Doctrine\Common\Annotations\AnnotationReader; +use Doctrine\Common\Annotations\AnnotationRegistry; +use Doctrine\Common\Annotations\PsrCachedReader; +use Doctrine\Common\Annotations\Reader; +use Doctrine\Common\Cache\Psr6\DoctrineProvider; +use Symfony\Bundle\FrameworkBundle\CacheWarmer\AnnotationsCacheWarmer; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\FilesystemAdapter; +use Symfony\Component\Cache\Adapter\PhpArrayAdapter; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('annotations.reader', AnnotationReader::class) + ->call('addGlobalIgnoredName', [ + 'required', + service('annotations.dummy_registry'), // dummy arg to register class_exists as annotation loader only when required + ]) + + ->set('annotations.dummy_registry', AnnotationRegistry::class) + ->call('registerUniqueLoader', ['class_exists']) + + ->set('annotations.cached_reader', PsrCachedReader::class) + ->args([ + service('annotations.reader'), + inline_service(ArrayAdapter::class), + abstract_arg('Debug-Flag'), + ]) + ->tag('annotations.cached_reader') + ->tag('container.do_not_inline') + + ->set('annotations.filesystem_cache_adapter', FilesystemAdapter::class) + ->args([ + '', + 0, + abstract_arg('Cache-Directory'), + ]) + + ->set('annotations.filesystem_cache', DoctrineProvider::class) + ->factory([DoctrineProvider::class, 'wrap']) + ->args([ + service('annotations.filesystem_cache_adapter'), + ]) + ->deprecate('symfony/framework-bundle', '5.4', '"%service_id% is deprecated"') + + ->set('annotations.cache_warmer', AnnotationsCacheWarmer::class) + ->args([ + service('annotations.reader'), + param('kernel.cache_dir').'/annotations.php', + '#^Symfony\\\\(?:Component\\\\HttpKernel\\\\|Bundle\\\\FrameworkBundle\\\\Controller\\\\(?!.*Controller$))#', + param('kernel.debug'), + ]) + + ->set('annotations.cache_adapter', PhpArrayAdapter::class) + ->factory([PhpArrayAdapter::class, 'create']) + ->args([ + param('kernel.cache_dir').'/annotations.php', + service('cache.annotations'), + ]) + ->tag('container.hot_path') + + ->set('annotations.cache', DoctrineProvider::class) + ->factory([DoctrineProvider::class, 'wrap']) + ->args([ + service('annotations.cache_adapter'), + ]) + ->deprecate('symfony/framework-bundle', '5.4', '"%service_id% is deprecated"') + + ->alias('annotation_reader', 'annotations.reader') + ->alias(Reader::class, 'annotation_reader'); +}; diff --git a/lib/symfony/framework-bundle/Resources/config/annotations.xml b/lib/symfony/framework-bundle/Resources/config/annotations.xml deleted file mode 100644 index 9403917ff5..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/annotations.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - required - - - - - - - - class_exists - - - - - - - - - - - - - - - - - - %kernel.cache_dir%/annotations.php - - #^Symfony\\(?:Component\\HttpKernel\\|Bundle\\FrameworkBundle\\Controller\\(?!.*Controller$))# - %kernel.debug% - - - - - - - %kernel.cache_dir%/annotations.php - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/assets.php b/lib/symfony/framework-bundle/Resources/config/assets.php new file mode 100644 index 0000000000..1e250aab4d --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/assets.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 Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Asset\Context\RequestStackContext; +use Symfony\Component\Asset\Package; +use Symfony\Component\Asset\Packages; +use Symfony\Component\Asset\PathPackage; +use Symfony\Component\Asset\UrlPackage; +use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy; +use Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy; +use Symfony\Component\Asset\VersionStrategy\RemoteJsonManifestVersionStrategy; +use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy; + +return static function (ContainerConfigurator $container) { + $container->parameters() + ->set('asset.request_context.base_path', null) + ->set('asset.request_context.secure', null) + ; + + $container->services() + ->set('assets.packages', Packages::class) + ->args([ + service('assets._default_package'), + tagged_iterator('assets.package', 'package'), + ]) + + ->alias(Packages::class, 'assets.packages') + + ->set('assets.empty_package', Package::class) + ->args([ + service('assets.empty_version_strategy'), + ]) + + ->alias('assets._default_package', 'assets.empty_package') + + ->set('assets.context', RequestStackContext::class) + ->args([ + service('request_stack'), + param('asset.request_context.base_path'), + param('asset.request_context.secure'), + ]) + + ->set('assets.path_package', PathPackage::class) + ->abstract() + ->args([ + abstract_arg('base path'), + abstract_arg('version strategy'), + service('assets.context'), + ]) + + ->set('assets.url_package', UrlPackage::class) + ->abstract() + ->args([ + abstract_arg('base URLs'), + abstract_arg('version strategy'), + service('assets.context'), + ]) + + ->set('assets.static_version_strategy', StaticVersionStrategy::class) + ->abstract() + ->args([ + abstract_arg('version'), + abstract_arg('format'), + ]) + + ->set('assets.empty_version_strategy', EmptyVersionStrategy::class) + + ->set('assets.json_manifest_version_strategy', JsonManifestVersionStrategy::class) + ->abstract() + ->args([ + abstract_arg('manifest path'), + service('http_client')->nullOnInvalid(), + false, + ]) + + ->set('assets.remote_json_manifest_version_strategy', RemoteJsonManifestVersionStrategy::class) + ->abstract() + ->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, use "assets.json_manifest_version_strategy" instead.') + ->args([ + abstract_arg('manifest url'), + service('http_client'), + ]) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/assets.xml b/lib/symfony/framework-bundle/Resources/config/assets.xml deleted file mode 100644 index 4aaa702df5..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/assets.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - false - - - - - - - - - - - - - - - - - - %asset.request_context.base_path% - %asset.request_context.secure% - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/cache.php b/lib/symfony/framework-bundle/Resources/config/cache.php new file mode 100644 index 0000000000..e333e5f547 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/cache.php @@ -0,0 +1,267 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Cache\Adapter\ApcuAdapter; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\DoctrineAdapter; +use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter; +use Symfony\Component\Cache\Adapter\FilesystemAdapter; +use Symfony\Component\Cache\Adapter\MemcachedAdapter; +use Symfony\Component\Cache\Adapter\PdoAdapter; +use Symfony\Component\Cache\Adapter\ProxyAdapter; +use Symfony\Component\Cache\Adapter\RedisAdapter; +use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter; +use Symfony\Component\Cache\Adapter\TagAwareAdapter; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller; +use Symfony\Component\Cache\Messenger\EarlyExpirationHandler; +use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; +use Symfony\Contracts\Cache\CacheInterface; +use Symfony\Contracts\Cache\TagAwareCacheInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('cache.app') + ->parent('cache.adapter.filesystem') + ->public() + ->tag('cache.pool', ['clearer' => 'cache.app_clearer']) + + ->set('cache.app.taggable', TagAwareAdapter::class) + ->args([service('cache.app')]) + + ->set('cache.system') + ->parent('cache.adapter.system') + ->public() + ->tag('cache.pool') + + ->set('cache.validator') + ->parent('cache.system') + ->private() + ->tag('cache.pool') + + ->set('cache.serializer') + ->parent('cache.system') + ->private() + ->tag('cache.pool') + + ->set('cache.annotations') + ->parent('cache.system') + ->private() + ->tag('cache.pool') + + ->set('cache.property_info') + ->parent('cache.system') + ->private() + ->tag('cache.pool') + + ->set('cache.messenger.restart_workers_signal') + ->parent('cache.app') + ->private() + ->tag('cache.pool') + + ->set('cache.adapter.system', AdapterInterface::class) + ->abstract() + ->factory([AbstractAdapter::class, 'createSystemCache']) + ->args([ + '', // namespace + 0, // default lifetime + abstract_arg('version'), + sprintf('%s/pools/system', param('kernel.cache_dir')), + service('logger')->ignoreOnInvalid(), + ]) + ->tag('cache.pool', ['clearer' => 'cache.system_clearer', 'reset' => 'reset']) + ->tag('monolog.logger', ['channel' => 'cache']) + + ->set('cache.adapter.apcu', ApcuAdapter::class) + ->abstract() + ->args([ + '', // namespace + 0, // default lifetime + abstract_arg('version'), + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('cache.pool', ['clearer' => 'cache.default_clearer', 'reset' => 'reset']) + ->tag('monolog.logger', ['channel' => 'cache']) + + ->set('cache.adapter.doctrine', DoctrineAdapter::class) + ->abstract() + ->args([ + abstract_arg('Doctrine provider service'), + '', // namespace + 0, // default lifetime + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('cache.pool', [ + 'provider' => 'cache.default_doctrine_provider', + 'clearer' => 'cache.default_clearer', + 'reset' => 'reset', + ]) + ->tag('monolog.logger', ['channel' => 'cache']) + ->deprecate('symfony/framework-bundle', '5.4', 'The "%service_id%" service inherits from "cache.adapter.doctrine" which is deprecated.') + + ->set('cache.adapter.filesystem', FilesystemAdapter::class) + ->abstract() + ->args([ + '', // namespace + 0, // default lifetime + sprintf('%s/pools/app', param('kernel.cache_dir')), + service('cache.default_marshaller')->ignoreOnInvalid(), + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('cache.pool', ['clearer' => 'cache.default_clearer', 'reset' => 'reset']) + ->tag('monolog.logger', ['channel' => 'cache']) + + ->set('cache.adapter.psr6', ProxyAdapter::class) + ->abstract() + ->args([ + abstract_arg('PSR-6 provider service'), + '', // namespace + 0, // default lifetime + ]) + ->tag('cache.pool', [ + 'provider' => 'cache.default_psr6_provider', + 'clearer' => 'cache.default_clearer', + 'reset' => 'reset', + ]) + + ->set('cache.adapter.redis', RedisAdapter::class) + ->abstract() + ->args([ + abstract_arg('Redis connection service'), + '', // namespace + 0, // default lifetime + service('cache.default_marshaller')->ignoreOnInvalid(), + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('cache.pool', [ + 'provider' => 'cache.default_redis_provider', + 'clearer' => 'cache.default_clearer', + 'reset' => 'reset', + ]) + ->tag('monolog.logger', ['channel' => 'cache']) + + ->set('cache.adapter.redis_tag_aware', RedisTagAwareAdapter::class) + ->abstract() + ->args([ + abstract_arg('Redis connection service'), + '', // namespace + 0, // default lifetime + service('cache.default_marshaller')->ignoreOnInvalid(), + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('cache.pool', [ + 'provider' => 'cache.default_redis_provider', + 'clearer' => 'cache.default_clearer', + 'reset' => 'reset', + ]) + ->tag('monolog.logger', ['channel' => 'cache']) + + ->set('cache.adapter.memcached', MemcachedAdapter::class) + ->abstract() + ->args([ + abstract_arg('Memcached connection service'), + '', // namespace + 0, // default lifetime + service('cache.default_marshaller')->ignoreOnInvalid(), + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('cache.pool', [ + 'provider' => 'cache.default_memcached_provider', + 'clearer' => 'cache.default_clearer', + 'reset' => 'reset', + ]) + ->tag('monolog.logger', ['channel' => 'cache']) + + ->set('cache.adapter.doctrine_dbal', DoctrineDbalAdapter::class) + ->abstract() + ->args([ + abstract_arg('DBAL connection service'), + '', // namespace + 0, // default lifetime + [], // table options + service('cache.default_marshaller')->ignoreOnInvalid(), + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('cache.pool', [ + 'provider' => 'cache.default_doctrine_dbal_provider', + 'clearer' => 'cache.default_clearer', + 'reset' => 'reset', + ]) + ->tag('monolog.logger', ['channel' => 'cache']) + + ->set('cache.adapter.pdo', PdoAdapter::class) + ->abstract() + ->args([ + abstract_arg('PDO connection service'), + '', // namespace + 0, // default lifetime + [], // table options + service('cache.default_marshaller')->ignoreOnInvalid(), + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('cache.pool', [ + 'provider' => 'cache.default_pdo_provider', + 'clearer' => 'cache.default_clearer', + 'reset' => 'reset', + ]) + ->tag('monolog.logger', ['channel' => 'cache']) + + ->set('cache.adapter.array', ArrayAdapter::class) + ->abstract() + ->args([ + 0, // default lifetime + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('cache.pool', ['clearer' => 'cache.default_clearer', 'reset' => 'reset']) + ->tag('monolog.logger', ['channel' => 'cache']) + + ->set('cache.default_marshaller', DefaultMarshaller::class) + ->args([ + null, // use igbinary_serialize() when available + '%kernel.debug%', + ]) + + ->set('cache.early_expiration_handler', EarlyExpirationHandler::class) + ->args([ + service('reverse_container'), + ]) + ->tag('messenger.message_handler') + + ->set('cache.default_clearer', Psr6CacheClearer::class) + ->args([ + [], + ]) + + ->set('cache.system_clearer') + ->parent('cache.default_clearer') + ->public() + + ->set('cache.global_clearer') + ->parent('cache.default_clearer') + ->public() + + ->alias('cache.app_clearer', 'cache.default_clearer') + ->public() + + ->alias(CacheItemPoolInterface::class, 'cache.app') + + ->alias(AdapterInterface::class, 'cache.app') + ->deprecate('symfony/framework-bundle', '5.4', sprintf('The "%%alias_id%%" alias is deprecated, use "%s" instead.', CacheItemPoolInterface::class)) + + ->alias(CacheInterface::class, 'cache.app') + + ->alias(TagAwareCacheInterface::class, 'cache.app.taggable') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/cache.xml b/lib/symfony/framework-bundle/Resources/config/cache.xml deleted file mode 100644 index a7aaaec7c0..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/cache.xml +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - - %kernel.cache_dir%/pools - - - - - - - - 0 - - - - - - - - - - - - 0 - - - - - - - - - - 0 - %kernel.cache_dir%/pools - - - - - - - - - - 0 - - - - - - - - 0 - - - - - - - - - - - 0 - - - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/cache_debug.php b/lib/symfony/framework-bundle/Resources/config/cache_debug.php new file mode 100644 index 0000000000..8f29d9f1dc --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/cache_debug.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 Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\CacheWarmer\CachePoolClearerCacheWarmer; +use Symfony\Component\Cache\DataCollector\CacheDataCollector; + +return static function (ContainerConfigurator $container) { + $container->services() + // DataCollector (public to prevent inlining, made private in CacheCollectorPass) + ->set('data_collector.cache', CacheDataCollector::class) + ->public() + ->tag('data_collector', [ + 'template' => '@WebProfiler/Collector/cache.html.twig', + 'id' => 'cache', + 'priority' => 275, + ]) + + // CacheWarmer used in dev to clear cache pool + ->set('cache_pool_clearer.cache_warmer', CachePoolClearerCacheWarmer::class) + ->args([ + service('cache.system_clearer'), + [ + 'cache.validator', + 'cache.serializer', + ], + ]) + ->tag('kernel.cache_warmer', ['priority' => 64]) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/cache_debug.xml b/lib/symfony/framework-bundle/Resources/config/cache_debug.xml deleted file mode 100644 index 20e22761a3..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/cache_debug.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/collectors.php b/lib/symfony/framework-bundle/Resources/config/collectors.php new file mode 100644 index 0000000000..abf9ded5e5 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/collectors.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector; +use Symfony\Component\HttpKernel\DataCollector\AjaxDataCollector; +use Symfony\Component\HttpKernel\DataCollector\ConfigDataCollector; +use Symfony\Component\HttpKernel\DataCollector\EventDataCollector; +use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector; +use Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector; +use Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector; +use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector; +use Symfony\Component\HttpKernel\DataCollector\TimeDataCollector; +use Symfony\Component\HttpKernel\KernelEvents; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('data_collector.config', ConfigDataCollector::class) + ->call('setKernel', [service('kernel')->ignoreOnInvalid()]) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/config.html.twig', 'id' => 'config', 'priority' => -255]) + + ->set('data_collector.request', RequestDataCollector::class) + ->args([ + service('request_stack')->ignoreOnInvalid(), + ]) + ->tag('kernel.event_subscriber') + ->tag('data_collector', ['template' => '@WebProfiler/Collector/request.html.twig', 'id' => 'request', 'priority' => 335]) + + ->set('data_collector.request.session_collector', \Closure::class) + ->factory([\Closure::class, 'fromCallable']) + ->args([[service('data_collector.request'), 'collectSessionUsage']]) + + ->set('data_collector.ajax', AjaxDataCollector::class) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/ajax.html.twig', 'id' => 'ajax', 'priority' => 315]) + + ->set('data_collector.exception', ExceptionDataCollector::class) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/exception.html.twig', 'id' => 'exception', 'priority' => 305]) + + ->set('data_collector.events', EventDataCollector::class) + ->args([ + service('debug.event_dispatcher')->ignoreOnInvalid(), + service('request_stack')->ignoreOnInvalid(), + ]) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/events.html.twig', 'id' => 'events', 'priority' => 290]) + + ->set('data_collector.logger', LoggerDataCollector::class) + ->args([ + service('logger')->ignoreOnInvalid(), + sprintf('%s/%s', param('kernel.build_dir'), param('kernel.container_class')), + service('request_stack')->ignoreOnInvalid(), + ]) + ->tag('monolog.logger', ['channel' => 'profiler']) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/logger.html.twig', 'id' => 'logger', 'priority' => 300]) + + ->set('data_collector.time', TimeDataCollector::class) + ->args([ + service('kernel')->ignoreOnInvalid(), + service('debug.stopwatch')->ignoreOnInvalid(), + ]) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/time.html.twig', 'id' => 'time', 'priority' => 330]) + + ->set('data_collector.memory', MemoryDataCollector::class) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/memory.html.twig', 'id' => 'memory', 'priority' => 325]) + + ->set('data_collector.router', RouterDataCollector::class) + ->tag('kernel.event_listener', ['event' => KernelEvents::CONTROLLER, 'method' => 'onKernelController']) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/router.html.twig', 'id' => 'router', 'priority' => 285]) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/collectors.xml b/lib/symfony/framework-bundle/Resources/config/collectors.xml deleted file mode 100644 index e1d919aa0e..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/collectors.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %kernel.cache_dir%/%kernel.container_class% - - - - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/console.php b/lib/symfony/framework-bundle/Resources/config/console.php new file mode 100644 index 0000000000..610a83adde --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/console.php @@ -0,0 +1,331 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\Command\AboutCommand; +use Symfony\Bundle\FrameworkBundle\Command\AssetsInstallCommand; +use Symfony\Bundle\FrameworkBundle\Command\CacheClearCommand; +use Symfony\Bundle\FrameworkBundle\Command\CachePoolClearCommand; +use Symfony\Bundle\FrameworkBundle\Command\CachePoolDeleteCommand; +use Symfony\Bundle\FrameworkBundle\Command\CachePoolListCommand; +use Symfony\Bundle\FrameworkBundle\Command\CachePoolPruneCommand; +use Symfony\Bundle\FrameworkBundle\Command\CacheWarmupCommand; +use Symfony\Bundle\FrameworkBundle\Command\ConfigDebugCommand; +use Symfony\Bundle\FrameworkBundle\Command\ConfigDumpReferenceCommand; +use Symfony\Bundle\FrameworkBundle\Command\ContainerDebugCommand; +use Symfony\Bundle\FrameworkBundle\Command\ContainerLintCommand; +use Symfony\Bundle\FrameworkBundle\Command\DebugAutowiringCommand; +use Symfony\Bundle\FrameworkBundle\Command\EventDispatcherDebugCommand; +use Symfony\Bundle\FrameworkBundle\Command\RouterDebugCommand; +use Symfony\Bundle\FrameworkBundle\Command\RouterMatchCommand; +use Symfony\Bundle\FrameworkBundle\Command\SecretsDecryptToLocalCommand; +use Symfony\Bundle\FrameworkBundle\Command\SecretsEncryptFromLocalCommand; +use Symfony\Bundle\FrameworkBundle\Command\SecretsGenerateKeysCommand; +use Symfony\Bundle\FrameworkBundle\Command\SecretsListCommand; +use Symfony\Bundle\FrameworkBundle\Command\SecretsRemoveCommand; +use Symfony\Bundle\FrameworkBundle\Command\SecretsSetCommand; +use Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand; +use Symfony\Bundle\FrameworkBundle\Command\TranslationUpdateCommand; +use Symfony\Bundle\FrameworkBundle\Command\WorkflowDumpCommand; +use Symfony\Bundle\FrameworkBundle\Command\YamlLintCommand; +use Symfony\Bundle\FrameworkBundle\EventListener\SuggestMissingPackageSubscriber; +use Symfony\Component\Console\EventListener\ErrorListener; +use Symfony\Component\Dotenv\Command\DebugCommand as DotenvDebugCommand; +use Symfony\Component\Messenger\Command\ConsumeMessagesCommand; +use Symfony\Component\Messenger\Command\DebugCommand; +use Symfony\Component\Messenger\Command\FailedMessagesRemoveCommand; +use Symfony\Component\Messenger\Command\FailedMessagesRetryCommand; +use Symfony\Component\Messenger\Command\FailedMessagesShowCommand; +use Symfony\Component\Messenger\Command\SetupTransportsCommand; +use Symfony\Component\Messenger\Command\StopWorkersCommand; +use Symfony\Component\Translation\Command\TranslationPullCommand; +use Symfony\Component\Translation\Command\TranslationPushCommand; +use Symfony\Component\Translation\Command\XliffLintCommand; +use Symfony\Component\Validator\Command\DebugCommand as ValidatorDebugCommand; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('console.error_listener', ErrorListener::class) + ->args([ + service('logger')->nullOnInvalid(), + ]) + ->tag('kernel.event_subscriber') + ->tag('monolog.logger', ['channel' => 'console']) + + ->set('console.suggest_missing_package_subscriber', SuggestMissingPackageSubscriber::class) + ->tag('kernel.event_subscriber') + + ->set('console.command.about', AboutCommand::class) + ->tag('console.command') + + ->set('console.command.assets_install', AssetsInstallCommand::class) + ->args([ + service('filesystem'), + param('kernel.project_dir'), + ]) + ->tag('console.command') + + ->set('console.command.cache_clear', CacheClearCommand::class) + ->args([ + service('cache_clearer'), + service('filesystem'), + ]) + ->tag('console.command') + + ->set('console.command.cache_pool_clear', CachePoolClearCommand::class) + ->args([ + service('cache.global_clearer'), + ]) + ->tag('console.command') + + ->set('console.command.cache_pool_prune', CachePoolPruneCommand::class) + ->args([ + [], + ]) + ->tag('console.command') + + ->set('console.command.cache_pool_delete', CachePoolDeleteCommand::class) + ->args([ + service('cache.global_clearer'), + ]) + ->tag('console.command') + + ->set('console.command.cache_pool_list', CachePoolListCommand::class) + ->args([ + null, + ]) + ->tag('console.command') + + ->set('console.command.cache_warmup', CacheWarmupCommand::class) + ->args([ + service('cache_warmer'), + ]) + ->tag('console.command') + + ->set('console.command.config_debug', ConfigDebugCommand::class) + ->tag('console.command') + + ->set('console.command.config_dump_reference', ConfigDumpReferenceCommand::class) + ->tag('console.command') + + ->set('console.command.container_debug', ContainerDebugCommand::class) + ->tag('console.command') + + ->set('console.command.container_lint', ContainerLintCommand::class) + ->tag('console.command') + + ->set('console.command.debug_autowiring', DebugAutowiringCommand::class) + ->args([ + null, + service('debug.file_link_formatter')->nullOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.dotenv_debug', DotenvDebugCommand::class) + ->args([ + param('kernel.environment'), + param('kernel.project_dir'), + ]) + ->tag('console.command') + + ->set('console.command.event_dispatcher_debug', EventDispatcherDebugCommand::class) + ->args([ + tagged_locator('event_dispatcher.dispatcher', 'name'), + ]) + ->tag('console.command') + + ->set('console.command.messenger_consume_messages', ConsumeMessagesCommand::class) + ->args([ + abstract_arg('Routable message bus'), + service('messenger.receiver_locator'), + service('event_dispatcher'), + service('logger')->nullOnInvalid(), + [], // Receiver names + service('messenger.listener.reset_services')->nullOnInvalid(), + [], // Bus names + ]) + ->tag('console.command') + ->tag('monolog.logger', ['channel' => 'messenger']) + + ->set('console.command.messenger_setup_transports', SetupTransportsCommand::class) + ->args([ + service('messenger.receiver_locator'), + [], // Receiver names + ]) + ->tag('console.command') + + ->set('console.command.messenger_debug', DebugCommand::class) + ->args([ + [], // Message to handlers mapping + ]) + ->tag('console.command') + + ->set('console.command.messenger_stop_workers', StopWorkersCommand::class) + ->args([ + service('cache.messenger.restart_workers_signal'), + ]) + ->tag('console.command') + + ->set('console.command.messenger_failed_messages_retry', FailedMessagesRetryCommand::class) + ->args([ + abstract_arg('Default failure receiver name'), + abstract_arg('Receivers'), + service('messenger.routable_message_bus'), + service('event_dispatcher'), + service('logger'), + ]) + ->tag('console.command') + + ->set('console.command.messenger_failed_messages_show', FailedMessagesShowCommand::class) + ->args([ + abstract_arg('Default failure receiver name'), + abstract_arg('Receivers'), + ]) + ->tag('console.command') + + ->set('console.command.messenger_failed_messages_remove', FailedMessagesRemoveCommand::class) + ->args([ + abstract_arg('Default failure receiver name'), + abstract_arg('Receivers'), + ]) + ->tag('console.command') + + ->set('console.command.router_debug', RouterDebugCommand::class) + ->args([ + service('router'), + service('debug.file_link_formatter')->nullOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.router_match', RouterMatchCommand::class) + ->args([ + service('router'), + tagged_iterator('routing.expression_language_provider'), + ]) + ->tag('console.command') + + ->set('console.command.translation_debug', TranslationDebugCommand::class) + ->args([ + service('translator'), + service('translation.reader'), + service('translation.extractor'), + param('translator.default_path'), + null, // twig.default_path + [], // Translator paths + [], // Twig paths + param('kernel.enabled_locales'), + ]) + ->tag('console.command') + + ->set('console.command.translation_extract', TranslationUpdateCommand::class) + ->args([ + service('translation.writer'), + service('translation.reader'), + service('translation.extractor'), + param('kernel.default_locale'), + param('translator.default_path'), + null, // twig.default_path + [], // Translator paths + [], // Twig paths + param('kernel.enabled_locales'), + ]) + ->tag('console.command') + + ->set('console.command.validator_debug', ValidatorDebugCommand::class) + ->args([ + service('validator'), + ]) + ->tag('console.command') + + ->set('console.command.translation_pull', TranslationPullCommand::class) + ->args([ + service('translation.provider_collection'), + service('translation.writer'), + service('translation.reader'), + param('kernel.default_locale'), + [], // Translator paths + [], // Enabled locales + ]) + ->tag('console.command', ['command' => 'translation:pull']) + + ->set('console.command.translation_push', TranslationPushCommand::class) + ->args([ + service('translation.provider_collection'), + service('translation.reader'), + [], // Translator paths + [], // Enabled locales + ]) + ->tag('console.command', ['command' => 'translation:push']) + + ->set('console.command.workflow_dump', WorkflowDumpCommand::class) + ->tag('console.command') + + ->set('console.command.xliff_lint', XliffLintCommand::class) + ->tag('console.command') + + ->set('console.command.yaml_lint', YamlLintCommand::class) + ->tag('console.command') + + ->set('console.command.form_debug', \Symfony\Component\Form\Command\DebugCommand::class) + ->args([ + service('form.registry'), + [], // All form types namespaces are stored here by FormPass + [], // All services form types are stored here by FormPass + [], // All type extensions are stored here by FormPass + [], // All type guessers are stored here by FormPass + service('debug.file_link_formatter')->nullOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.secrets_set', SecretsSetCommand::class) + ->args([ + service('secrets.vault'), + service('secrets.local_vault')->nullOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.secrets_remove', SecretsRemoveCommand::class) + ->args([ + service('secrets.vault'), + service('secrets.local_vault')->nullOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.secrets_generate_key', SecretsGenerateKeysCommand::class) + ->args([ + service('secrets.vault'), + service('secrets.local_vault')->ignoreOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.secrets_list', SecretsListCommand::class) + ->args([ + service('secrets.vault'), + service('secrets.local_vault')->ignoreOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.secrets_decrypt_to_local', SecretsDecryptToLocalCommand::class) + ->args([ + service('secrets.vault'), + service('secrets.local_vault')->ignoreOnInvalid(), + ]) + ->tag('console.command') + + ->set('console.command.secrets_encrypt_from_local', SecretsEncryptFromLocalCommand::class) + ->args([ + service('secrets.vault'), + service('secrets.local_vault')->ignoreOnInvalid(), + ]) + ->tag('console.command') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/console.xml b/lib/symfony/framework-bundle/Resources/config/console.xml deleted file mode 100644 index 814eb031c3..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/console.xml +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %translator.default_path% - - - - - - - - - %kernel.default_locale% - %translator.default_path% - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/debug.php b/lib/symfony/framework-bundle/Resources/config/debug.php new file mode 100644 index 0000000000..cfaad8c1de --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/debug.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\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\NotTaggedControllerValueResolver; +use Symfony\Component\HttpKernel\Controller\TraceableArgumentResolver; +use Symfony\Component\HttpKernel\Controller\TraceableControllerResolver; +use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('debug.event_dispatcher', TraceableEventDispatcher::class) + ->decorate('event_dispatcher') + ->args([ + service('debug.event_dispatcher.inner'), + service('debug.stopwatch'), + service('logger')->nullOnInvalid(), + service('request_stack')->nullOnInvalid(), + ]) + ->tag('monolog.logger', ['channel' => 'event']) + ->tag('kernel.reset', ['method' => 'reset']) + + ->set('debug.controller_resolver', TraceableControllerResolver::class) + ->decorate('controller_resolver') + ->args([ + service('debug.controller_resolver.inner'), + service('debug.stopwatch'), + ]) + + ->set('debug.argument_resolver', TraceableArgumentResolver::class) + ->decorate('argument_resolver') + ->args([ + service('debug.argument_resolver.inner'), + service('debug.stopwatch'), + ]) + + ->set('argument_resolver.not_tagged_controller', NotTaggedControllerValueResolver::class) + ->args([abstract_arg('Controller argument, set in FrameworkExtension')]) + ->tag('controller.argument_value_resolver', ['priority' => -200]) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/debug.xml b/lib/symfony/framework-bundle/Resources/config/debug.xml deleted file mode 100644 index 9f1da6a83c..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/debug.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/debug_prod.php b/lib/symfony/framework-bundle/Resources/config/debug_prod.php new file mode 100644 index 0000000000..f381b018f0 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/debug_prod.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 Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\HttpKernel\EventListener\DebugHandlersListener; + +return static function (ContainerConfigurator $container) { + $container->parameters()->set('debug.error_handler.throw_at', -1); + + $container->services() + ->set('debug.debug_handlers_listener', DebugHandlersListener::class) + ->args([ + null, // Exception handler + service('monolog.logger.php')->nullOnInvalid(), + null, // Log levels map for enabled error levels + param('debug.error_handler.throw_at'), + param('kernel.debug'), + param('kernel.debug'), + service('monolog.logger.deprecation')->nullOnInvalid(), + ]) + ->tag('kernel.event_subscriber') + ->tag('monolog.logger', ['channel' => 'php']) + + ->set('debug.file_link_formatter', FileLinkFormatter::class) + ->args([param('debug.file_link_format')]) + + ->alias(FileLinkFormatter::class, 'debug.file_link_formatter') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/debug_prod.xml b/lib/symfony/framework-bundle/Resources/config/debug_prod.xml deleted file mode 100644 index e118d03223..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/debug_prod.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - -1 - - - - - - - - - null - - -1 - %debug.error_handler.throw_at% - true - - true - - - - %debug.file_link_format% - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/error_renderer.php b/lib/symfony/framework-bundle/Resources/config/error_renderer.php new file mode 100644 index 0000000000..67f28ce44d --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/error_renderer.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\DependencyInjection\Loader\Configurator; + +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('error_handler.error_renderer.html', HtmlErrorRenderer::class) + ->args([ + inline_service() + ->factory([HtmlErrorRenderer::class, 'isDebug']) + ->args([ + service('request_stack'), + param('kernel.debug'), + ]), + param('kernel.charset'), + service('debug.file_link_formatter')->nullOnInvalid(), + param('kernel.project_dir'), + inline_service() + ->factory([HtmlErrorRenderer::class, 'getAndCleanOutputBuffer']) + ->args([service('request_stack')]), + service('logger')->nullOnInvalid(), + ]) + + ->alias('error_renderer.html', 'error_handler.error_renderer.html') + ->alias('error_renderer', 'error_renderer.html') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/esi.php b/lib/symfony/framework-bundle/Resources/config/esi.php new file mode 100644 index 0000000000..7ac9f90533 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/esi.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HttpKernel\EventListener\SurrogateListener; +use Symfony\Component\HttpKernel\HttpCache\Esi; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('esi', Esi::class) + + ->set('esi_listener', SurrogateListener::class) + ->args([service('esi')->ignoreOnInvalid()]) + ->tag('kernel.event_subscriber') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/esi.xml b/lib/symfony/framework-bundle/Resources/config/esi.xml deleted file mode 100644 index 65e26d81e2..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/esi.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/form.php b/lib/symfony/framework-bundle/Resources/config/form.php new file mode 100644 index 0000000000..e4c0745c60 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/form.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator; +use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory; +use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\ColorType; +use Symfony\Component\Form\Extension\Core\Type\FileType; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Core\Type\TransformationFailureExtension; +use Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension; +use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler; +use Symfony\Component\Form\Extension\HttpFoundation\Type\FormTypeHttpFoundationExtension; +use Symfony\Component\Form\Extension\Validator\Type\FormTypeValidatorExtension; +use Symfony\Component\Form\Extension\Validator\Type\RepeatedTypeValidatorExtension; +use Symfony\Component\Form\Extension\Validator\Type\SubmitTypeValidatorExtension; +use Symfony\Component\Form\Extension\Validator\Type\UploadValidatorExtension; +use Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser; +use Symfony\Component\Form\FormFactory; +use Symfony\Component\Form\FormFactoryInterface; +use Symfony\Component\Form\FormRegistry; +use Symfony\Component\Form\FormRegistryInterface; +use Symfony\Component\Form\ResolvedFormTypeFactory; +use Symfony\Component\Form\ResolvedFormTypeFactoryInterface; +use Symfony\Component\Form\Util\ServerParams; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('form.resolved_type_factory', ResolvedFormTypeFactory::class) + + ->alias(ResolvedFormTypeFactoryInterface::class, 'form.resolved_type_factory') + + ->set('form.registry', FormRegistry::class) + ->args([ + [ + /* + * We don't need to be able to add more extensions. + * more types can be registered with the form.type tag + * more type extensions can be registered with the form.type_extension tag + * more type_guessers can be registered with the form.type_guesser tag + */ + service('form.extension'), + ], + service('form.resolved_type_factory'), + ]) + + ->alias(FormRegistryInterface::class, 'form.registry') + + ->set('form.factory', FormFactory::class) + ->public() + ->args([service('form.registry')]) + ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2']) + + ->alias(FormFactoryInterface::class, 'form.factory') + + ->set('form.extension', DependencyInjectionExtension::class) + ->args([ + abstract_arg('All services with tag "form.type" are stored in a service locator by FormPass'), + abstract_arg('All services with tag "form.type_extension" are stored here by FormPass'), + abstract_arg('All services with tag "form.type_guesser" are stored here by FormPass'), + ]) + + ->set('form.type_guesser.validator', ValidatorTypeGuesser::class) + ->args([service('validator.mapping.class_metadata_factory')]) + ->tag('form.type_guesser') + + ->alias('form.property_accessor', 'property_accessor') + + ->set('form.choice_list_factory.default', DefaultChoiceListFactory::class) + + ->set('form.choice_list_factory.property_access', PropertyAccessDecorator::class) + ->args([ + service('form.choice_list_factory.default'), + service('form.property_accessor'), + ]) + + ->set('form.choice_list_factory.cached', CachingFactoryDecorator::class) + ->args([service('form.choice_list_factory.property_access')]) + ->tag('kernel.reset', ['method' => 'reset']) + + ->alias('form.choice_list_factory', 'form.choice_list_factory.cached') + + ->set('form.type.form', FormType::class) + ->args([service('form.property_accessor')]) + ->tag('form.type') + + ->set('form.type.choice', ChoiceType::class) + ->args([ + service('form.choice_list_factory'), + service('translator')->ignoreOnInvalid(), + ]) + ->tag('form.type') + + ->set('form.type.file', FileType::class) + ->public() + ->args([service('translator')->ignoreOnInvalid()]) + ->tag('form.type') + ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2']) + + ->set('form.type.color', ColorType::class) + ->args([service('translator')->ignoreOnInvalid()]) + ->tag('form.type') + + ->set('form.type_extension.form.transformation_failure_handling', TransformationFailureExtension::class) + ->args([service('translator')->ignoreOnInvalid()]) + ->tag('form.type_extension', ['extended-type' => FormType::class]) + + ->set('form.type_extension.form.http_foundation', FormTypeHttpFoundationExtension::class) + ->args([service('form.type_extension.form.request_handler')]) + ->tag('form.type_extension') + + ->set('form.type_extension.form.request_handler', HttpFoundationRequestHandler::class) + ->args([service('form.server_params')]) + + ->set('form.server_params', ServerParams::class) + ->args([service('request_stack')]) + + ->set('form.type_extension.form.validator', FormTypeValidatorExtension::class) + ->args([ + service('validator'), + true, + service('twig.form.renderer')->ignoreOnInvalid(), + service('translator')->ignoreOnInvalid(), + ]) + ->tag('form.type_extension', ['extended-type' => FormType::class]) + + ->set('form.type_extension.repeated.validator', RepeatedTypeValidatorExtension::class) + ->tag('form.type_extension') + + ->set('form.type_extension.submit.validator', SubmitTypeValidatorExtension::class) + ->tag('form.type_extension', ['extended-type' => SubmitType::class]) + + ->set('form.type_extension.upload.validator', UploadValidatorExtension::class) + ->args([ + service('translator'), + param('validator.translation_domain'), + ]) + ->tag('form.type_extension') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/form.xml b/lib/symfony/framework-bundle/Resources/config/form.xml deleted file mode 100644 index c5bc8cf5db..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/form.xml +++ /dev/null @@ -1,205 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - null - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - - - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - - - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %validator.translation_domain% - - - - - The service "%service_id%" is internal and deprecated since Symfony 3.3 and will be removed in Symfony 4.0 - - - diff --git a/lib/symfony/framework-bundle/Resources/config/form_csrf.php b/lib/symfony/framework-bundle/Resources/config/form_csrf.php new file mode 100644 index 0000000000..c8e5e973e4 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/form_csrf.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('form.type_extension.csrf', FormTypeCsrfExtension::class) + ->args([ + service('security.csrf.token_manager'), + param('form.type_extension.csrf.enabled'), + param('form.type_extension.csrf.field_name'), + service('translator')->nullOnInvalid(), + param('validator.translation_domain'), + service('form.server_params'), + ]) + ->tag('form.type_extension') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/form_csrf.xml b/lib/symfony/framework-bundle/Resources/config/form_csrf.xml deleted file mode 100644 index 8164425193..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/form_csrf.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - %form.type_extension.csrf.enabled% - %form.type_extension.csrf.field_name% - - %validator.translation_domain% - - - - - - The service "%service_id%" is internal and deprecated since Symfony 3.3 and will be removed in Symfony 4.0 - - - diff --git a/lib/symfony/framework-bundle/Resources/config/form_debug.php b/lib/symfony/framework-bundle/Resources/config/form_debug.php new file mode 100644 index 0000000000..f5e2c3ecdd --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/form_debug.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\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Form\Extension\DataCollector\FormDataCollector; +use Symfony\Component\Form\Extension\DataCollector\FormDataExtractor; +use Symfony\Component\Form\Extension\DataCollector\Proxy\ResolvedTypeFactoryDataCollectorProxy; +use Symfony\Component\Form\Extension\DataCollector\Type\DataCollectorTypeExtension; +use Symfony\Component\Form\ResolvedFormTypeFactory; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('form.resolved_type_factory', ResolvedTypeFactoryDataCollectorProxy::class) + ->args([ + inline_service(ResolvedFormTypeFactory::class), + service('data_collector.form'), + ]) + + ->set('form.type_extension.form.data_collector', DataCollectorTypeExtension::class) + ->args([service('data_collector.form')]) + ->tag('form.type_extension') + + ->set('data_collector.form.extractor', FormDataExtractor::class) + + ->set('data_collector.form', FormDataCollector::class) + ->args([service('data_collector.form.extractor')]) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/form.html.twig', 'id' => 'form', 'priority' => 310]) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/form_debug.xml b/lib/symfony/framework-bundle/Resources/config/form_debug.xml deleted file mode 100644 index 9ab1765b02..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/form_debug.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/fragment_listener.php b/lib/symfony/framework-bundle/Resources/config/fragment_listener.php new file mode 100644 index 0000000000..465c304263 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/fragment_listener.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HttpKernel\EventListener\FragmentListener; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('fragment.listener', FragmentListener::class) + ->args([service('uri_signer'), param('fragment.path')]) + ->tag('kernel.event_subscriber') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/fragment_listener.xml b/lib/symfony/framework-bundle/Resources/config/fragment_listener.xml deleted file mode 100644 index b7c64119f8..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/fragment_listener.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - %fragment.path% - - - diff --git a/lib/symfony/framework-bundle/Resources/config/fragment_renderer.php b/lib/symfony/framework-bundle/Resources/config/fragment_renderer.php new file mode 100644 index 0000000000..76f49c6339 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/fragment_renderer.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HttpKernel\DependencyInjection\LazyLoadingFragmentHandler; +use Symfony\Component\HttpKernel\Fragment\EsiFragmentRenderer; +use Symfony\Component\HttpKernel\Fragment\FragmentUriGenerator; +use Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface; +use Symfony\Component\HttpKernel\Fragment\HIncludeFragmentRenderer; +use Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer; +use Symfony\Component\HttpKernel\Fragment\SsiFragmentRenderer; + +return static function (ContainerConfigurator $container) { + $container->parameters() + ->set('fragment.renderer.hinclude.global_template', null) + ->set('fragment.path', '/_fragment') + ; + + $container->services() + ->set('fragment.handler', LazyLoadingFragmentHandler::class) + ->args([ + abstract_arg('fragment renderer locator'), + service('request_stack'), + param('kernel.debug'), + ]) + + ->set('fragment.uri_generator', FragmentUriGenerator::class) + ->args([param('fragment.path'), service('uri_signer'), service('request_stack')]) + ->alias(FragmentUriGeneratorInterface::class, 'fragment.uri_generator') + + ->set('fragment.renderer.inline', InlineFragmentRenderer::class) + ->args([service('http_kernel'), service('event_dispatcher')]) + ->call('setFragmentPath', [param('fragment.path')]) + ->tag('kernel.fragment_renderer', ['alias' => 'inline']) + + ->set('fragment.renderer.hinclude', HIncludeFragmentRenderer::class) + ->args([ + service('twig')->nullOnInvalid(), + service('uri_signer'), + param('fragment.renderer.hinclude.global_template'), + ]) + ->call('setFragmentPath', [param('fragment.path')]) + + ->set('fragment.renderer.esi', EsiFragmentRenderer::class) + ->args([ + service('esi')->nullOnInvalid(), + service('fragment.renderer.inline'), + service('uri_signer'), + ]) + ->call('setFragmentPath', [param('fragment.path')]) + ->tag('kernel.fragment_renderer', ['alias' => 'esi']) + + ->set('fragment.renderer.ssi', SsiFragmentRenderer::class) + ->args([ + service('ssi')->nullOnInvalid(), + service('fragment.renderer.inline'), + service('uri_signer'), + ]) + ->call('setFragmentPath', [param('fragment.path')]) + ->tag('kernel.fragment_renderer', ['alias' => 'ssi']) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/fragment_renderer.xml b/lib/symfony/framework-bundle/Resources/config/fragment_renderer.xml deleted file mode 100644 index 394033734d..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/fragment_renderer.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - /_fragment - - - - - - - - - %kernel.debug% - - - - - - - %fragment.path% - - - - - - %fragment.renderer.hinclude.global_template% - %fragment.path% - - - - - - - - %fragment.path% - - - - - - - - %fragment.path% - - - diff --git a/lib/symfony/framework-bundle/Resources/config/http_client.php b/lib/symfony/framework-bundle/Resources/config/http_client.php new file mode 100644 index 0000000000..ba70b90ad6 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/http_client.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\StreamFactoryInterface; +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Component\HttpClient\HttplugClient; +use Symfony\Component\HttpClient\Psr18Client; +use Symfony\Component\HttpClient\Retry\GenericRetryStrategy; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('http_client', HttpClientInterface::class) + ->factory([HttpClient::class, 'create']) + ->args([ + [], // default options + abstract_arg('max host connections'), + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('monolog.logger', ['channel' => 'http_client']) + ->tag('kernel.reset', ['method' => 'reset', 'on_invalid' => 'ignore']) + ->tag('http_client.client') + + ->alias(HttpClientInterface::class, 'http_client') + + ->set('psr18.http_client', Psr18Client::class) + ->args([ + service('http_client'), + service(ResponseFactoryInterface::class)->ignoreOnInvalid(), + service(StreamFactoryInterface::class)->ignoreOnInvalid(), + ]) + + ->alias(ClientInterface::class, 'psr18.http_client') + + ->set(\Http\Client\HttpClient::class, HttplugClient::class) + ->args([ + service('http_client'), + service(ResponseFactoryInterface::class)->ignoreOnInvalid(), + service(StreamFactoryInterface::class)->ignoreOnInvalid(), + ]) + + ->set('http_client.abstract_retry_strategy', GenericRetryStrategy::class) + ->abstract() + ->args([ + abstract_arg('http codes'), + abstract_arg('delay ms'), + abstract_arg('multiplier'), + abstract_arg('max delay ms'), + abstract_arg('jitter'), + ]) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/http_client_debug.php b/lib/symfony/framework-bundle/Resources/config/http_client_debug.php new file mode 100644 index 0000000000..44031eb5f8 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/http_client_debug.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HttpClient\DataCollector\HttpClientDataCollector; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('data_collector.http_client', HttpClientDataCollector::class) + ->tag('data_collector', [ + 'template' => '@WebProfiler/Collector/http_client.html.twig', + 'id' => 'http_client', + 'priority' => 250, + ]) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/identity_translator.php b/lib/symfony/framework-bundle/Resources/config/identity_translator.php new file mode 100644 index 0000000000..a9066e1f00 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/identity_translator.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Translation\IdentityTranslator; +use Symfony\Contracts\Translation\TranslatorInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('translator', IdentityTranslator::class) + ->public() + ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2']) + ->alias(TranslatorInterface::class, 'translator') + + ->set('identity_translator', IdentityTranslator::class) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/identity_translator.xml b/lib/symfony/framework-bundle/Resources/config/identity_translator.xml deleted file mode 100644 index b731844965..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/identity_translator.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/lock.php b/lib/symfony/framework-bundle/Resources/config/lock.php new file mode 100644 index 0000000000..4e14636211 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/lock.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Lock\LockFactory; +use Symfony\Component\Lock\Store\CombinedStore; +use Symfony\Component\Lock\Strategy\ConsensusStrategy; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('lock.store.combined.abstract', CombinedStore::class)->abstract() + ->args([abstract_arg('List of stores'), service('lock.strategy.majority')]) + + ->set('lock.strategy.majority', ConsensusStrategy::class) + + ->set('lock.factory.abstract', LockFactory::class)->abstract() + ->args([abstract_arg('Store')]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('monolog.logger', ['channel' => 'lock']) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/lock.xml b/lib/symfony/framework-bundle/Resources/config/lock.xml deleted file mode 100644 index ce5b9c8d30..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/lock.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/mailer.php b/lib/symfony/framework-bundle/Resources/config/mailer.php new file mode 100644 index 0000000000..b15b29270a --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/mailer.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 Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Mailer\EventListener\EnvelopeListener; +use Symfony\Component\Mailer\EventListener\MessageListener; +use Symfony\Component\Mailer\EventListener\MessageLoggerListener; +use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mailer\Messenger\MessageHandler; +use Symfony\Component\Mailer\Transport; +use Symfony\Component\Mailer\Transport\TransportInterface; +use Symfony\Component\Mailer\Transport\Transports; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('mailer.mailer', Mailer::class) + ->args([ + service('mailer.transports'), + abstract_arg('message bus'), + service('event_dispatcher')->ignoreOnInvalid(), + ]) + ->alias('mailer', 'mailer.mailer') + ->alias(MailerInterface::class, 'mailer.mailer') + + ->set('mailer.transports', Transports::class) + ->factory([service('mailer.transport_factory'), 'fromStrings']) + ->args([ + abstract_arg('transports'), + ]) + + ->set('mailer.transport_factory', Transport::class) + ->args([ + tagged_iterator('mailer.transport_factory'), + ]) + + ->set('mailer.default_transport', TransportInterface::class) + ->factory([service('mailer.transport_factory'), 'fromString']) + ->args([ + abstract_arg('env(MAILER_DSN)'), + ]) + ->alias(TransportInterface::class, 'mailer.default_transport') + + ->set('mailer.messenger.message_handler', MessageHandler::class) + ->args([ + service('mailer.transports'), + ]) + ->tag('messenger.message_handler') + + ->set('mailer.envelope_listener', EnvelopeListener::class) + ->args([ + abstract_arg('sender'), + abstract_arg('recipients'), + ]) + ->tag('kernel.event_subscriber') + + ->set('mailer.message_listener', MessageListener::class) + ->args([ + abstract_arg('headers'), + ]) + ->tag('kernel.event_subscriber') + + ->set('mailer.logger_message_listener', MessageLoggerListener::class) + ->tag('kernel.event_subscriber') + ->tag('kernel.reset', ['method' => 'reset']) + ->deprecate('symfony/framework-bundle', '5.2', 'The "%service_id%" service is deprecated, use "mailer.message_logger_listener" instead.') + + ->set('mailer.message_logger_listener', MessageLoggerListener::class) + ->tag('kernel.event_subscriber') + ->tag('kernel.reset', ['method' => 'reset']) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/mailer_debug.php b/lib/symfony/framework-bundle/Resources/config/mailer_debug.php new file mode 100644 index 0000000000..cdb205750f --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/mailer_debug.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Mailer\DataCollector\MessageDataCollector; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('mailer.data_collector', MessageDataCollector::class) + ->args([ + service('mailer.message_logger_listener'), + ]) + ->tag('data_collector', [ + 'template' => '@WebProfiler/Collector/mailer.html.twig', + 'id' => 'mailer', + ]) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/mailer_transports.php b/lib/symfony/framework-bundle/Resources/config/mailer_transports.php new file mode 100644 index 0000000000..7bddfa7567 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/mailer_transports.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory; +use Symfony\Component\Mailer\Bridge\Google\Transport\GmailTransportFactory; +use Symfony\Component\Mailer\Bridge\Mailchimp\Transport\MandrillTransportFactory; +use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory; +use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory; +use Symfony\Component\Mailer\Bridge\OhMySmtp\Transport\OhMySmtpTransportFactory; +use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory; +use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory; +use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory; +use Symfony\Component\Mailer\Transport\AbstractTransportFactory; +use Symfony\Component\Mailer\Transport\NativeTransportFactory; +use Symfony\Component\Mailer\Transport\NullTransportFactory; +use Symfony\Component\Mailer\Transport\SendmailTransportFactory; +use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransportFactory; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('mailer.transport_factory.abstract', AbstractTransportFactory::class) + ->abstract() + ->args([ + service('event_dispatcher'), + service('http_client')->ignoreOnInvalid(), + service('logger')->ignoreOnInvalid(), + ]) + ->tag('monolog.logger', ['channel' => 'mailer']) + + ->set('mailer.transport_factory.amazon', SesTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') + + ->set('mailer.transport_factory.gmail', GmailTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') + + ->set('mailer.transport_factory.mailchimp', MandrillTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') + + ->set('mailer.transport_factory.mailjet', MailjetTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') + + ->set('mailer.transport_factory.mailgun', MailgunTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') + + ->set('mailer.transport_factory.postmark', PostmarkTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') + + ->set('mailer.transport_factory.sendgrid', SendgridTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') + + ->set('mailer.transport_factory.null', NullTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') + + ->set('mailer.transport_factory.sendmail', SendmailTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') + + ->set('mailer.transport_factory.sendinblue', SendinblueTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') + + ->set('mailer.transport_factory.ohmysmtp', OhMySmtpTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory') + + ->set('mailer.transport_factory.smtp', EsmtpTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory', ['priority' => -100]) + + ->set('mailer.transport_factory.native', NativeTransportFactory::class) + ->parent('mailer.transport_factory.abstract') + ->tag('mailer.transport_factory'); +}; diff --git a/lib/symfony/framework-bundle/Resources/config/messenger.php b/lib/symfony/framework-bundle/Resources/config/messenger.php new file mode 100644 index 0000000000..813d503000 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/messenger.php @@ -0,0 +1,215 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory; +use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory; +use Symfony\Component\Messenger\Bridge\Beanstalkd\Transport\BeanstalkdTransportFactory; +use Symfony\Component\Messenger\Bridge\Redis\Transport\RedisTransportFactory; +use Symfony\Component\Messenger\EventListener\AddErrorDetailsStampListener; +use Symfony\Component\Messenger\EventListener\DispatchPcntlSignalListener; +use Symfony\Component\Messenger\EventListener\ResetServicesListener; +use Symfony\Component\Messenger\EventListener\SendFailedMessageForRetryListener; +use Symfony\Component\Messenger\EventListener\SendFailedMessageToFailureTransportListener; +use Symfony\Component\Messenger\EventListener\StopWorkerOnCustomStopExceptionListener; +use Symfony\Component\Messenger\EventListener\StopWorkerOnRestartSignalListener; +use Symfony\Component\Messenger\EventListener\StopWorkerOnSigtermSignalListener; +use Symfony\Component\Messenger\Middleware\AddBusNameStampMiddleware; +use Symfony\Component\Messenger\Middleware\DispatchAfterCurrentBusMiddleware; +use Symfony\Component\Messenger\Middleware\FailedMessageProcessingMiddleware; +use Symfony\Component\Messenger\Middleware\HandleMessageMiddleware; +use Symfony\Component\Messenger\Middleware\RejectRedeliveredMessageMiddleware; +use Symfony\Component\Messenger\Middleware\RouterContextMiddleware; +use Symfony\Component\Messenger\Middleware\SendMessageMiddleware; +use Symfony\Component\Messenger\Middleware\TraceableMiddleware; +use Symfony\Component\Messenger\Middleware\ValidationMiddleware; +use Symfony\Component\Messenger\Retry\MultiplierRetryStrategy; +use Symfony\Component\Messenger\RoutableMessageBus; +use Symfony\Component\Messenger\Transport\InMemoryTransportFactory; +use Symfony\Component\Messenger\Transport\Sender\SendersLocator; +use Symfony\Component\Messenger\Transport\Serialization\Normalizer\FlattenExceptionNormalizer; +use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer; +use Symfony\Component\Messenger\Transport\Serialization\Serializer; +use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface; +use Symfony\Component\Messenger\Transport\Sync\SyncTransportFactory; +use Symfony\Component\Messenger\Transport\TransportFactory; + +return static function (ContainerConfigurator $container) { + $container->services() + ->alias(SerializerInterface::class, 'messenger.default_serializer') + + // Asynchronous + ->set('messenger.senders_locator', SendersLocator::class) + ->args([ + abstract_arg('per message senders map'), + abstract_arg('senders service locator'), + ]) + ->set('messenger.middleware.send_message', SendMessageMiddleware::class) + ->args([ + service('messenger.senders_locator'), + service('event_dispatcher'), + ]) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + ->tag('monolog.logger', ['channel' => 'messenger']) + + // Message encoding/decoding + ->set('messenger.transport.symfony_serializer', Serializer::class) + ->args([ + service('serializer'), + abstract_arg('format'), + abstract_arg('context'), + ]) + + ->set('serializer.normalizer.flatten_exception', FlattenExceptionNormalizer::class) + ->tag('serializer.normalizer', ['priority' => -880]) + + ->set('messenger.transport.native_php_serializer', PhpSerializer::class) + + // Middleware + ->set('messenger.middleware.handle_message', HandleMessageMiddleware::class) + ->abstract() + ->args([ + abstract_arg('bus handler resolver'), + ]) + ->tag('monolog.logger', ['channel' => 'messenger']) + ->call('setLogger', [service('logger')->ignoreOnInvalid()]) + + ->set('messenger.middleware.add_bus_name_stamp_middleware', AddBusNameStampMiddleware::class) + ->abstract() + + ->set('messenger.middleware.dispatch_after_current_bus', DispatchAfterCurrentBusMiddleware::class) + + ->set('messenger.middleware.validation', ValidationMiddleware::class) + ->args([ + service('validator'), + ]) + + ->set('messenger.middleware.reject_redelivered_message_middleware', RejectRedeliveredMessageMiddleware::class) + + ->set('messenger.middleware.failed_message_processing_middleware', FailedMessageProcessingMiddleware::class) + + ->set('messenger.middleware.traceable', TraceableMiddleware::class) + ->abstract() + ->args([ + service('debug.stopwatch'), + ]) + + ->set('messenger.middleware.router_context', RouterContextMiddleware::class) + ->args([ + service('router'), + ]) + + // Discovery + ->set('messenger.receiver_locator', ServiceLocator::class) + ->args([ + [], + ]) + ->tag('container.service_locator') + + // Transports + ->set('messenger.transport_factory', TransportFactory::class) + ->args([ + tagged_iterator('messenger.transport_factory'), + ]) + + ->set('messenger.transport.amqp.factory', AmqpTransportFactory::class) + + ->set('messenger.transport.redis.factory', RedisTransportFactory::class) + + ->set('messenger.transport.sync.factory', SyncTransportFactory::class) + ->args([ + service('messenger.routable_message_bus'), + ]) + ->tag('messenger.transport_factory') + + ->set('messenger.transport.in_memory.factory', InMemoryTransportFactory::class) + ->tag('messenger.transport_factory') + ->tag('kernel.reset', ['method' => 'reset']) + + ->set('messenger.transport.sqs.factory', AmazonSqsTransportFactory::class) + ->args([ + service('logger')->ignoreOnInvalid(), + ]) + + ->set('messenger.transport.beanstalkd.factory', BeanstalkdTransportFactory::class) + + // retry + ->set('messenger.retry_strategy_locator', ServiceLocator::class) + ->args([ + [], + ]) + ->tag('container.service_locator') + + ->set('messenger.retry.abstract_multiplier_retry_strategy', MultiplierRetryStrategy::class) + ->abstract() + ->args([ + abstract_arg('max retries'), + abstract_arg('delay ms'), + abstract_arg('multiplier'), + abstract_arg('max delay ms'), + ]) + + // worker event listener + ->set('messenger.retry.send_failed_message_for_retry_listener', SendFailedMessageForRetryListener::class) + ->args([ + abstract_arg('senders service locator'), + service('messenger.retry_strategy_locator'), + service('logger')->ignoreOnInvalid(), + service('event_dispatcher'), + ]) + ->tag('kernel.event_subscriber') + ->tag('monolog.logger', ['channel' => 'messenger']) + + ->set('messenger.failure.add_error_details_stamp_listener', AddErrorDetailsStampListener::class) + ->tag('kernel.event_subscriber') + + ->set('messenger.failure.send_failed_message_to_failure_transport_listener', SendFailedMessageToFailureTransportListener::class) + ->args([ + abstract_arg('failure transports'), + service('logger')->ignoreOnInvalid(), + ]) + ->tag('kernel.event_subscriber') + ->tag('monolog.logger', ['channel' => 'messenger']) + + ->set('messenger.listener.dispatch_pcntl_signal_listener', DispatchPcntlSignalListener::class) + ->tag('kernel.event_subscriber') + + ->set('messenger.listener.stop_worker_on_restart_signal_listener', StopWorkerOnRestartSignalListener::class) + ->args([ + service('cache.messenger.restart_workers_signal'), + service('logger')->ignoreOnInvalid(), + ]) + ->tag('kernel.event_subscriber') + ->tag('monolog.logger', ['channel' => 'messenger']) + + ->set('messenger.listener.stop_worker_on_sigterm_signal_listener', StopWorkerOnSigtermSignalListener::class) + ->args([ + service('logger')->ignoreOnInvalid(), + ]) + ->tag('kernel.event_subscriber') + + ->set('messenger.listener.stop_worker_on_stop_exception_listener', StopWorkerOnCustomStopExceptionListener::class) + ->tag('kernel.event_subscriber') + + ->set('messenger.listener.reset_services', ResetServicesListener::class) + ->args([ + service('services_resetter'), + ]) + + ->set('messenger.routable_message_bus', RoutableMessageBus::class) + ->args([ + abstract_arg('message bus locator'), + service('messenger.default_bus'), + ]) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/messenger_debug.php b/lib/symfony/framework-bundle/Resources/config/messenger_debug.php new file mode 100644 index 0000000000..58f9be1f9e --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/messenger_debug.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Messenger\DataCollector\MessengerDataCollector; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('data_collector.messenger', MessengerDataCollector::class) + ->tag('data_collector', [ + 'template' => '@WebProfiler/Collector/messenger.html.twig', + 'id' => 'messenger', + 'priority' => 100, + ]) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/mime_type.php b/lib/symfony/framework-bundle/Resources/config/mime_type.php new file mode 100644 index 0000000000..a7e9bbd912 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/mime_type.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Mime\MimeTypeGuesserInterface; +use Symfony\Component\Mime\MimeTypes; +use Symfony\Component\Mime\MimeTypesInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('mime_types', MimeTypes::class) + ->call('setDefault', [service('mime_types')]) + + ->alias(MimeTypesInterface::class, 'mime_types') + ->alias(MimeTypeGuesserInterface::class, 'mime_types') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/notifier.php b/lib/symfony/framework-bundle/Resources/config/notifier.php new file mode 100644 index 0000000000..73beb2c346 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/notifier.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bridge\Monolog\Handler\NotifierHandler; +use Symfony\Component\Notifier\Channel\BrowserChannel; +use Symfony\Component\Notifier\Channel\ChannelPolicy; +use Symfony\Component\Notifier\Channel\ChatChannel; +use Symfony\Component\Notifier\Channel\EmailChannel; +use Symfony\Component\Notifier\Channel\PushChannel; +use Symfony\Component\Notifier\Channel\SmsChannel; +use Symfony\Component\Notifier\Chatter; +use Symfony\Component\Notifier\ChatterInterface; +use Symfony\Component\Notifier\EventListener\NotificationLoggerListener; +use Symfony\Component\Notifier\EventListener\SendFailedMessageToNotifierListener; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\PushMessage; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Messenger\MessageHandler; +use Symfony\Component\Notifier\Notifier; +use Symfony\Component\Notifier\NotifierInterface; +use Symfony\Component\Notifier\Texter; +use Symfony\Component\Notifier\TexterInterface; +use Symfony\Component\Notifier\Transport; +use Symfony\Component\Notifier\Transport\Transports; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('notifier', Notifier::class) + ->args([tagged_locator('notifier.channel', 'channel'), service('notifier.channel_policy')->ignoreOnInvalid()]) + + ->alias(NotifierInterface::class, 'notifier') + + ->set('notifier.channel_policy', ChannelPolicy::class) + ->args([[]]) + + ->set('notifier.channel.browser', BrowserChannel::class) + ->args([service('request_stack')]) + ->tag('notifier.channel', ['channel' => 'browser']) + + ->set('notifier.channel.chat', ChatChannel::class) + ->args([service('chatter.transports'), service('messenger.default_bus')->ignoreOnInvalid()]) + ->tag('notifier.channel', ['channel' => 'chat']) + + ->set('notifier.channel.sms', SmsChannel::class) + ->args([service('texter.transports'), service('messenger.default_bus')->ignoreOnInvalid()]) + ->tag('notifier.channel', ['channel' => 'sms']) + + ->set('notifier.channel.email', EmailChannel::class) + ->args([service('mailer.transports'), service('messenger.default_bus')->ignoreOnInvalid()]) + ->tag('notifier.channel', ['channel' => 'email']) + + ->set('notifier.channel.push', PushChannel::class) + ->args([service('texter.transports'), service('messenger.default_bus')->ignoreOnInvalid()]) + ->tag('notifier.channel', ['channel' => 'push']) + + ->set('notifier.monolog_handler', NotifierHandler::class) + ->args([service('notifier')]) + + ->set('notifier.failed_message_listener', SendFailedMessageToNotifierListener::class) + ->args([service('notifier')]) + + ->set('chatter', Chatter::class) + ->args([ + service('chatter.transports'), + service('messenger.default_bus')->ignoreOnInvalid(), + service('event_dispatcher')->ignoreOnInvalid(), + ]) + + ->alias(ChatterInterface::class, 'chatter') + + ->set('chatter.transports', Transports::class) + ->factory([service('chatter.transport_factory'), 'fromStrings']) + ->args([[]]) + + ->set('chatter.transport_factory', Transport::class) + ->args([tagged_iterator('chatter.transport_factory')]) + + ->set('chatter.messenger.chat_handler', MessageHandler::class) + ->args([service('chatter.transports')]) + ->tag('messenger.message_handler', ['handles' => ChatMessage::class]) + + ->set('texter', Texter::class) + ->args([ + service('texter.transports'), + service('messenger.default_bus')->ignoreOnInvalid(), + service('event_dispatcher')->ignoreOnInvalid(), + ]) + + ->alias(TexterInterface::class, 'texter') + + ->set('texter.transports', Transports::class) + ->factory([service('texter.transport_factory'), 'fromStrings']) + ->args([[]]) + + ->set('texter.transport_factory', Transport::class) + ->args([tagged_iterator('texter.transport_factory')]) + + ->set('texter.messenger.sms_handler', MessageHandler::class) + ->args([service('texter.transports')]) + ->tag('messenger.message_handler', ['handles' => SmsMessage::class]) + + ->set('texter.messenger.push_handler', MessageHandler::class) + ->args([service('texter.transports')]) + ->tag('messenger.message_handler', ['handles' => PushMessage::class]) + + ->set('notifier.logger_notification_listener', NotificationLoggerListener::class) + ->tag('kernel.event_subscriber') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/notifier_debug.php b/lib/symfony/framework-bundle/Resources/config/notifier_debug.php new file mode 100644 index 0000000000..6147d34e4e --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/notifier_debug.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Notifier\DataCollector\NotificationDataCollector; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('notifier.data_collector', NotificationDataCollector::class) + ->args([service('notifier.logger_notification_listener')]) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/notifier.html.twig', 'id' => 'notifier']) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/notifier_transports.php b/lib/symfony/framework-bundle/Resources/config/notifier_transports.php new file mode 100644 index 0000000000..c07028847d --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/notifier_transports.php @@ -0,0 +1,277 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory; +use Symfony\Component\Notifier\Bridge\AmazonSns\AmazonSnsTransportFactory; +use Symfony\Component\Notifier\Bridge\Clickatell\ClickatellTransportFactory; +use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; +use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory; +use Symfony\Component\Notifier\Bridge\Expo\ExpoTransportFactory; +use Symfony\Component\Notifier\Bridge\FakeChat\FakeChatTransportFactory; +use Symfony\Component\Notifier\Bridge\FakeSms\FakeSmsTransportFactory; +use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory; +use Symfony\Component\Notifier\Bridge\FreeMobile\FreeMobileTransportFactory; +use Symfony\Component\Notifier\Bridge\GatewayApi\GatewayApiTransportFactory; +use Symfony\Component\Notifier\Bridge\Gitter\GitterTransportFactory; +use Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory; +use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; +use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; +use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; +use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; +use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory; +use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; +use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory; +use Symfony\Component\Notifier\Bridge\MessageBird\MessageBirdTransportFactory; +use Symfony\Component\Notifier\Bridge\MessageMedia\MessageMediaTransportFactory; +use Symfony\Component\Notifier\Bridge\MicrosoftTeams\MicrosoftTeamsTransportFactory; +use Symfony\Component\Notifier\Bridge\Mobyt\MobytTransportFactory; +use Symfony\Component\Notifier\Bridge\Nexmo\NexmoTransportFactory; +use Symfony\Component\Notifier\Bridge\Octopush\OctopushTransportFactory; +use Symfony\Component\Notifier\Bridge\OneSignal\OneSignalTransportFactory; +use Symfony\Component\Notifier\Bridge\OvhCloud\OvhCloudTransportFactory; +use Symfony\Component\Notifier\Bridge\RocketChat\RocketChatTransportFactory; +use Symfony\Component\Notifier\Bridge\Sendinblue\SendinblueTransportFactory; +use Symfony\Component\Notifier\Bridge\Sinch\SinchTransportFactory; +use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory; +use Symfony\Component\Notifier\Bridge\Sms77\Sms77TransportFactory; +use Symfony\Component\Notifier\Bridge\Smsapi\SmsapiTransportFactory; +use Symfony\Component\Notifier\Bridge\SmsBiuras\SmsBiurasTransportFactory; +use Symfony\Component\Notifier\Bridge\Smsc\SmscTransportFactory; +use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory; +use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; +use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; +use Symfony\Component\Notifier\Bridge\TurboSms\TurboSmsTransportFactory; +use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; +use Symfony\Component\Notifier\Bridge\Vonage\VonageTransportFactory; +use Symfony\Component\Notifier\Bridge\Yunpian\YunpianTransportFactory; +use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory; +use Symfony\Component\Notifier\Transport\AbstractTransportFactory; +use Symfony\Component\Notifier\Transport\NullTransportFactory; + +return static function (ContainerConfigurator $container) { + $container->services() + ->alias('notifier.transport_factory.allmysms', 'notifier.transport_factory.all-my-sms') + ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.all-my-sms" instead.') + ->alias('notifier.transport_factory.fakechat', 'notifier.transport_factory.fake-chat') + ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.fake-chat" instead.') + ->alias('notifier.transport_factory.fakesms', 'notifier.transport_factory.fake-sms') + ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.fake-sms" instead.') + ->alias('notifier.transport_factory.freemobile', 'notifier.transport_factory.free-mobile') + ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.free-mobile" instead.') + ->alias('notifier.transport_factory.gatewayapi', 'notifier.transport_factory.gateway-api') + ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.gateway-api" instead.') + ->alias('notifier.transport_factory.googlechat', 'notifier.transport_factory.google-chat') + ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.google-chat" instead.') + ->alias('notifier.transport_factory.lightsms', 'notifier.transport_factory.light-sms') + ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.light-sms" instead.') + ->alias('notifier.transport_factory.linkedin', 'notifier.transport_factory.linked-in') + ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.linked-in" instead.') + ->alias('notifier.transport_factory.microsoftteams', 'notifier.transport_factory.microsoft-teams') + ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.microsoft-teams" instead.') + ->alias('notifier.transport_factory.onesignal', 'notifier.transport_factory.one-signal') + ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.one-signal" instead.') + ->alias('notifier.transport_factory.ovhcloud', 'notifier.transport_factory.ovh-cloud') + ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.ovh-cloud" instead.') + ->alias('notifier.transport_factory.rocketchat', 'notifier.transport_factory.rocket-chat') + ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.rocket-chat" instead.') + ->alias('notifier.transport_factory.spothit', 'notifier.transport_factory.spot-hit') + ->deprecate('symfony/framework-bundle', '5.4', 'The "%alias_id% service is deprecated, use "notifier.transport_factory.spot-hit" instead.') + + ->set('notifier.transport_factory.abstract', AbstractTransportFactory::class) + ->abstract() + ->args([service('event_dispatcher'), service('http_client')->ignoreOnInvalid()]) + + ->set('notifier.transport_factory.slack', SlackTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.linked-in', LinkedInTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.telegram', TelegramTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.mattermost', MattermostTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.nexmo', NexmoTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + ->deprecate('symfony/framework-bundle', '5.4', 'The "%service_id% service is deprecated, use "notifier.transport_factory.vonage" instead.') + + ->set('notifier.transport_factory.vonage', VonageTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.rocket-chat', RocketChatTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.google-chat', GoogleChatTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.twilio', TwilioTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.all-my-sms', AllMySmsTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.firebase', FirebaseTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.free-mobile', FreeMobileTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.spot-hit', SpotHitTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.fake-chat', FakeChatTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.fake-sms', FakeSmsTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.ovh-cloud', OvhCloudTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.sinch', SinchTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.zulip', ZulipTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.infobip', InfobipTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.mobyt', MobytTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.smsapi', SmsapiTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.esendex', EsendexTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.sendinblue', SendinblueTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.iqsms', IqsmsTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.octopush', OctopushTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.discord', DiscordTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.microsoft-teams', MicrosoftTeamsTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.gateway-api', GatewayApiTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.mercure', MercureTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.gitter', GitterTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.clickatell', ClickatellTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.amazon-sns', AmazonSnsTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + ->tag('chatter.transport_factory') + + ->set('notifier.transport_factory.null', NullTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('chatter.transport_factory') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.light-sms', LightSmsTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.sms-biuras', SmsBiurasTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.smsc', SmscTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.message-bird', MessageBirdTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.message-media', MessageMediaTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.telnyx', TelnyxTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.mailjet', MailjetTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.yunpian', YunpianTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.turbo-sms', TurboSmsTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.sms77', Sms77TransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.one-signal', OneSignalTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.expo', ExpoTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/profiling.php b/lib/symfony/framework-bundle/Resources/config/profiling.php new file mode 100644 index 0000000000..221217896f --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/profiling.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 Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HttpKernel\EventListener\ProfilerListener; +use Symfony\Component\HttpKernel\Profiler\FileProfilerStorage; +use Symfony\Component\HttpKernel\Profiler\Profiler; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('profiler', Profiler::class) + ->public() + ->args([service('profiler.storage'), service('logger')->nullOnInvalid()]) + ->tag('monolog.logger', ['channel' => 'profiler']) + ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.4']) + + ->set('profiler.storage', FileProfilerStorage::class) + ->args([param('profiler.storage.dsn')]) + + ->set('profiler_listener', ProfilerListener::class) + ->args([ + service('profiler'), + service('request_stack'), + null, + param('profiler_listener.only_exceptions'), + param('profiler_listener.only_main_requests'), + ]) + ->tag('kernel.event_subscriber') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/profiling.xml b/lib/symfony/framework-bundle/Resources/config/profiling.xml deleted file mode 100644 index 1c61fa09c5..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/profiling.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - %profiler.storage.dsn% - - - - - - - - %profiler_listener.only_exceptions% - %profiler_listener.only_master_requests% - - - diff --git a/lib/symfony/framework-bundle/Resources/config/property_access.php b/lib/symfony/framework-bundle/Resources/config/property_access.php new file mode 100644 index 0000000000..85ab9f18e6 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/property_access.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\PropertyAccess\PropertyAccessor; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('property_accessor', PropertyAccessor::class) + ->args([ + abstract_arg('magic methods allowed, set by the extension'), + abstract_arg('throw exceptions, set by the extension'), + service('cache.property_access')->ignoreOnInvalid(), + abstract_arg('propertyReadInfoExtractor, set by the extension'), + abstract_arg('propertyWriteInfoExtractor, set by the extension'), + ]) + + ->alias(PropertyAccessorInterface::class, 'property_accessor') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/property_access.xml b/lib/symfony/framework-bundle/Resources/config/property_access.xml deleted file mode 100644 index 4fb4f2ff98..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/property_access.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/property_info.php b/lib/symfony/framework-bundle/Resources/config/property_info.php new file mode 100644 index 0000000000..90587839d5 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/property_info.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; +use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyInfoCacheExtractor; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; +use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; +use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('property_info', PropertyInfoExtractor::class) + ->args([[], [], [], [], []]) + + ->alias(PropertyAccessExtractorInterface::class, 'property_info') + ->alias(PropertyDescriptionExtractorInterface::class, 'property_info') + ->alias(PropertyInfoExtractorInterface::class, 'property_info') + ->alias(PropertyTypeExtractorInterface::class, 'property_info') + ->alias(PropertyListExtractorInterface::class, 'property_info') + ->alias(PropertyInitializableExtractorInterface::class, 'property_info') + + ->set('property_info.cache', PropertyInfoCacheExtractor::class) + ->decorate('property_info') + ->args([service('property_info.cache.inner'), service('cache.property_info')]) + + // Extractor + ->set('property_info.reflection_extractor', ReflectionExtractor::class) + ->tag('property_info.list_extractor', ['priority' => -1000]) + ->tag('property_info.type_extractor', ['priority' => -1002]) + ->tag('property_info.access_extractor', ['priority' => -1000]) + ->tag('property_info.initializable_extractor', ['priority' => -1000]) + + ->alias(PropertyReadInfoExtractorInterface::class, 'property_info.reflection_extractor') + ->alias(PropertyWriteInfoExtractorInterface::class, 'property_info.reflection_extractor') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/property_info.xml b/lib/symfony/framework-bundle/Resources/config/property_info.xml deleted file mode 100644 index 664fbe0e56..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/property_info.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/rate_limiter.php b/lib/symfony/framework-bundle/Resources/config/rate_limiter.php new file mode 100644 index 0000000000..727a1f6364 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/rate_limiter.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\RateLimiter\RateLimiterFactory; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('cache.rate_limiter') + ->parent('cache.app') + ->tag('cache.pool') + + ->set('limiter', RateLimiterFactory::class) + ->abstract() + ->args([ + abstract_arg('config'), + abstract_arg('storage'), + null, + ]) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/request.php b/lib/symfony/framework-bundle/Resources/config/request.php new file mode 100644 index 0000000000..ef8fc9a5e7 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/request.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HttpKernel\EventListener\AddRequestFormatsListener; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('request.add_request_formats_listener', AddRequestFormatsListener::class) + ->args([abstract_arg('formats')]) + ->tag('kernel.event_subscriber') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/request.xml b/lib/symfony/framework-bundle/Resources/config/request.xml deleted file mode 100644 index 048b61ec46..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/request.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/routing.php b/lib/symfony/framework-bundle/Resources/config/routing.php new file mode 100644 index 0000000000..09e340ff8a --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/routing.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\Component\DependencyInjection\Loader\Configurator; + +use Psr\Container\ContainerInterface; +use Symfony\Bundle\FrameworkBundle\CacheWarmer\RouterCacheWarmer; +use Symfony\Bundle\FrameworkBundle\Controller\RedirectController; +use Symfony\Bundle\FrameworkBundle\Controller\TemplateController; +use Symfony\Bundle\FrameworkBundle\Routing\DelegatingLoader; +use Symfony\Bundle\FrameworkBundle\Routing\RedirectableCompiledUrlMatcher; +use Symfony\Bundle\FrameworkBundle\Routing\Router; +use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\HttpKernel\EventListener\RouterListener; +use Symfony\Component\Routing\Generator\CompiledUrlGenerator; +use Symfony\Component\Routing\Generator\Dumper\CompiledUrlGeneratorDumper; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Loader\ContainerLoader; +use Symfony\Component\Routing\Loader\DirectoryLoader; +use Symfony\Component\Routing\Loader\GlobFileLoader; +use Symfony\Component\Routing\Loader\PhpFileLoader; +use Symfony\Component\Routing\Loader\XmlFileLoader; +use Symfony\Component\Routing\Loader\YamlFileLoader; +use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper; +use Symfony\Component\Routing\Matcher\ExpressionLanguageProvider; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RequestContextAwareInterface; +use Symfony\Component\Routing\RouterInterface; + +return static function (ContainerConfigurator $container) { + $container->parameters() + ->set('router.request_context.host', 'localhost') + ->set('router.request_context.scheme', 'http') + ->set('router.request_context.base_url', '') + ; + + $container->services() + ->set('routing.resolver', LoaderResolver::class) + + ->set('routing.loader.xml', XmlFileLoader::class) + ->args([ + service('file_locator'), + '%kernel.environment%', + ]) + ->tag('routing.loader') + + ->set('routing.loader.yml', YamlFileLoader::class) + ->args([ + service('file_locator'), + '%kernel.environment%', + ]) + ->tag('routing.loader') + + ->set('routing.loader.php', PhpFileLoader::class) + ->args([ + service('file_locator'), + '%kernel.environment%', + ]) + ->tag('routing.loader') + + ->set('routing.loader.glob', GlobFileLoader::class) + ->args([ + service('file_locator'), + '%kernel.environment%', + ]) + ->tag('routing.loader') + + ->set('routing.loader.directory', DirectoryLoader::class) + ->args([ + service('file_locator'), + '%kernel.environment%', + ]) + ->tag('routing.loader') + + ->set('routing.loader.container', ContainerLoader::class) + ->args([ + tagged_locator('routing.route_loader'), + '%kernel.environment%', + ]) + ->tag('routing.loader') + + ->set('routing.loader', DelegatingLoader::class) + ->public() + ->args([ + service('routing.resolver'), + [], // Default options + [], // Default requirements + ]) + + ->set('router.default', Router::class) + ->args([ + service(ContainerInterface::class), + param('router.resource'), + [ + 'cache_dir' => param('kernel.cache_dir'), + 'debug' => param('kernel.debug'), + 'generator_class' => CompiledUrlGenerator::class, + 'generator_dumper_class' => CompiledUrlGeneratorDumper::class, + 'matcher_class' => RedirectableCompiledUrlMatcher::class, + 'matcher_dumper_class' => CompiledUrlMatcherDumper::class, + ], + service('router.request_context')->ignoreOnInvalid(), + service('parameter_bag')->ignoreOnInvalid(), + service('logger')->ignoreOnInvalid(), + param('kernel.default_locale'), + ]) + ->call('setConfigCacheFactory', [ + service('config_cache_factory'), + ]) + ->tag('monolog.logger', ['channel' => 'router']) + ->tag('container.service_subscriber', ['id' => 'routing.loader']) + ->alias('router', 'router.default') + ->public() + ->alias(RouterInterface::class, 'router') + ->alias(UrlGeneratorInterface::class, 'router') + ->alias(UrlMatcherInterface::class, 'router') + ->alias(RequestContextAwareInterface::class, 'router') + + ->set('router.request_context', RequestContext::class) + ->factory([RequestContext::class, 'fromUri']) + ->args([ + param('router.request_context.base_url'), + param('router.request_context.host'), + param('router.request_context.scheme'), + param('request_listener.http_port'), + param('request_listener.https_port'), + ]) + ->call('setParameter', [ + '_functions', + service('router.expression_language_provider')->ignoreOnInvalid(), + ]) + ->alias(RequestContext::class, 'router.request_context') + + ->set('router.expression_language_provider', ExpressionLanguageProvider::class) + ->args([ + tagged_locator('routing.expression_language_function', 'function'), + ]) + ->tag('routing.expression_language_provider') + + ->set('router.cache_warmer', RouterCacheWarmer::class) + ->args([service(ContainerInterface::class)]) + ->tag('container.service_subscriber', ['id' => 'router']) + ->tag('kernel.cache_warmer') + + ->set('router_listener', RouterListener::class) + ->args([ + service('router'), + service('request_stack'), + service('router.request_context')->ignoreOnInvalid(), + service('logger')->ignoreOnInvalid(), + param('kernel.project_dir'), + param('kernel.debug'), + ]) + ->tag('kernel.event_subscriber') + ->tag('monolog.logger', ['channel' => 'request']) + + ->set(RedirectController::class) + ->public() + ->args([ + service('router'), + inline_service('int') + ->factory([service('router.request_context'), 'getHttpPort']), + inline_service('int') + ->factory([service('router.request_context'), 'getHttpsPort']), + ]) + + ->set(TemplateController::class) + ->args([ + service('twig')->ignoreOnInvalid(), + ]) + ->public() + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/routing.xml b/lib/symfony/framework-bundle/Resources/config/routing.xml deleted file mode 100644 index e66a49c14c..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/routing.xml +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - Symfony\Component\Routing\Generator\UrlGenerator - Symfony\Component\Routing\Generator\UrlGenerator - Symfony\Component\Routing\Generator\Dumper\PhpGeneratorDumper - Symfony\Bundle\FrameworkBundle\Routing\RedirectableUrlMatcher - Symfony\Bundle\FrameworkBundle\Routing\RedirectableUrlMatcher - Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper - %router.cache_class_prefix%UrlMatcher - %router.cache_class_prefix%UrlGenerator - localhost - http - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %router.resource% - - %kernel.cache_dir% - %kernel.debug% - %router.options.generator_class% - %router.options.generator_base_class% - %router.options.generator_dumper_class% - %router.options.generator.cache_class% - %router.options.matcher_class% - %router.options.matcher_base_class% - %router.options.matcher_dumper_class% - %router.options.matcher.cache_class% - - - - - - - - - - - - - - - %router.request_context.base_url% - GET - %router.request_context.host% - %router.request_context.scheme% - %request_listener.http_port% - %request_listener.https_port% - - - - - - - - - - - - - - - - - %kernel.project_dir% - %kernel.debug% - - - - - %request_listener.http_port% - %request_listener.https_port% - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/routing/errors.xml b/lib/symfony/framework-bundle/Resources/config/routing/errors.xml new file mode 100644 index 0000000000..13a9cc4076 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/routing/errors.xml @@ -0,0 +1,12 @@ + + + + + + error_controller::preview + html + \d+ + + diff --git a/lib/symfony/framework-bundle/Resources/config/schema/symfony-1.0.xsd b/lib/symfony/framework-bundle/Resources/config/schema/symfony-1.0.xsd index 39011822e9..d5f369cb1e 100644 --- a/lib/symfony/framework-bundle/Resources/config/schema/symfony-1.0.xsd +++ b/lib/symfony/framework-bundle/Resources/config/schema/symfony-1.0.xsd @@ -20,7 +20,6 @@ - @@ -30,15 +29,29 @@ + + + + + + + + + - + + + + + + @@ -46,6 +59,7 @@ + @@ -68,6 +82,7 @@ + @@ -75,12 +90,10 @@ - - - - + + @@ -89,29 +102,26 @@ - - - - - - + + + - + @@ -119,9 +129,10 @@ - + + @@ -149,6 +160,7 @@ + @@ -162,41 +174,49 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -205,6 +225,7 @@ + @@ -213,6 +234,21 @@ + + + + + + + + + + + + + + + @@ -222,18 +258,22 @@ + + + + - + @@ -249,52 +289,102 @@ + - + + + + + + + + + + + + + - + + + + + + + + + - + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -302,11 +392,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -316,6 +452,21 @@ + + + + + + + + + + + + + + + @@ -337,4 +488,326 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/symfony/framework-bundle/Resources/config/secrets.php b/lib/symfony/framework-bundle/Resources/config/secrets.php new file mode 100644 index 0000000000..a21d282702 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/secrets.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\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault; +use Symfony\Bundle\FrameworkBundle\Secrets\SodiumVault; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('secrets.vault', SodiumVault::class) + ->args([ + abstract_arg('Secret dir, set in FrameworkExtension'), + service('secrets.decryption_key')->ignoreOnInvalid(), + ]) + ->tag('container.env_var_loader') + + ->set('secrets.decryption_key') + ->parent('container.env') + ->args([abstract_arg('Decryption env var, set in FrameworkExtension')]) + + ->set('secrets.local_vault', DotenvVault::class) + ->args([abstract_arg('.env file path, set in FrameworkExtension')]) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/security_csrf.php b/lib/symfony/framework-bundle/Resources/config/security_csrf.php new file mode 100644 index 0000000000..9644d5b449 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/security_csrf.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bridge\Twig\Extension\CsrfExtension; +use Symfony\Bridge\Twig\Extension\CsrfRuntime; +use Symfony\Component\Security\Csrf\CsrfTokenManager; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface; +use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator; +use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage; +use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('security.csrf.token_generator', UriSafeTokenGenerator::class) + + ->alias(TokenGeneratorInterface::class, 'security.csrf.token_generator') + + ->set('security.csrf.token_storage', SessionTokenStorage::class) + ->args([service('request_stack')]) + + ->alias(TokenStorageInterface::class, 'security.csrf.token_storage') + + ->set('security.csrf.token_manager', CsrfTokenManager::class) + ->public() + ->args([ + service('security.csrf.token_generator'), + service('security.csrf.token_storage'), + service('request_stack')->ignoreOnInvalid(), + ]) + ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2']) + + ->alias(CsrfTokenManagerInterface::class, 'security.csrf.token_manager') + + ->set('twig.runtime.security_csrf', CsrfRuntime::class) + ->args([service('security.csrf.token_manager')]) + ->tag('twig.runtime') + + ->set('twig.extension.security_csrf', CsrfExtension::class) + ->tag('twig.extension') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/security_csrf.xml b/lib/symfony/framework-bundle/Resources/config/security_csrf.xml deleted file mode 100644 index a6c3aa1d31..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/security_csrf.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/serializer.php b/lib/symfony/framework-bundle/Resources/config/serializer.php new file mode 100644 index 0000000000..6a4508d0d7 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/serializer.php @@ -0,0 +1,222 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Bundle\FrameworkBundle\CacheWarmer\SerializerCacheWarmer; +use Symfony\Component\Cache\Adapter\PhpArrayAdapter; +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\ErrorHandler\ErrorRenderer\SerializerErrorRenderer; +use Symfony\Component\PropertyInfo\Extractor\SerializerExtractor; +use Symfony\Component\Serializer\Encoder\CsvEncoder; +use Symfony\Component\Serializer\Encoder\DecoderInterface; +use Symfony\Component\Serializer\Encoder\EncoderInterface; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Encoder\XmlEncoder; +use Symfony\Component\Serializer\Encoder\YamlEncoder; +use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; +use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface; +use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; +use Symfony\Component\Serializer\Mapping\Loader\LoaderChain; +use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; +use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; +use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; +use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer; +use Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer; +use Symfony\Component\Serializer\Normalizer\DataUriNormalizer; +use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer; +use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; +use Symfony\Component\Serializer\Normalizer\DateTimeZoneNormalizer; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\FormErrorNormalizer; +use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer; +use Symfony\Component\Serializer\Normalizer\MimeMessageNormalizer; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Normalizer\ProblemNormalizer; +use Symfony\Component\Serializer\Normalizer\PropertyNormalizer; +use Symfony\Component\Serializer\Normalizer\UidNormalizer; +use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer; +use Symfony\Component\Serializer\Serializer; +use Symfony\Component\Serializer\SerializerInterface; + +return static function (ContainerConfigurator $container) { + $container->parameters() + ->set('serializer.mapping.cache.file', '%kernel.cache_dir%/serialization.php') + ; + + $container->services() + ->set('serializer', Serializer::class) + ->public() + ->args([[], []]) + ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2']) + + ->alias(SerializerInterface::class, 'serializer') + ->alias(NormalizerInterface::class, 'serializer') + ->alias(DenormalizerInterface::class, 'serializer') + ->alias(EncoderInterface::class, 'serializer') + ->alias(DecoderInterface::class, 'serializer') + + ->alias('serializer.property_accessor', 'property_accessor') + + // Discriminator Map + ->set('serializer.mapping.class_discriminator_resolver', ClassDiscriminatorFromClassMetadata::class) + ->args([service('serializer.mapping.class_metadata_factory')]) + + ->alias(ClassDiscriminatorResolverInterface::class, 'serializer.mapping.class_discriminator_resolver') + + // Normalizer + ->set('serializer.normalizer.constraint_violation_list', ConstraintViolationListNormalizer::class) + ->args([[], service('serializer.name_converter.metadata_aware')]) + ->tag('serializer.normalizer', ['priority' => -915]) + + ->set('serializer.normalizer.mime_message', MimeMessageNormalizer::class) + ->args([service('serializer.normalizer.property')]) + ->tag('serializer.normalizer', ['priority' => -915]) + + ->set('serializer.normalizer.datetimezone', DateTimeZoneNormalizer::class) + ->tag('serializer.normalizer', ['priority' => -915]) + + ->set('serializer.normalizer.dateinterval', DateIntervalNormalizer::class) + ->tag('serializer.normalizer', ['priority' => -915]) + + ->set('serializer.normalizer.data_uri', DataUriNormalizer::class) + ->args([service('mime_types')->nullOnInvalid()]) + ->tag('serializer.normalizer', ['priority' => -920]) + + ->set('serializer.normalizer.datetime', DateTimeNormalizer::class) + ->tag('serializer.normalizer', ['priority' => -910]) + + ->set('serializer.normalizer.json_serializable', JsonSerializableNormalizer::class) + ->args([null, null]) + ->tag('serializer.normalizer', ['priority' => -950]) + + ->set('serializer.normalizer.problem', ProblemNormalizer::class) + ->args([param('kernel.debug')]) + ->tag('serializer.normalizer', ['priority' => -890]) + + ->set('serializer.denormalizer.unwrapping', UnwrappingDenormalizer::class) + ->args([service('serializer.property_accessor')]) + ->tag('serializer.normalizer', ['priority' => 1000]) + + ->set('serializer.normalizer.uid', UidNormalizer::class) + ->tag('serializer.normalizer', ['priority' => -890]) + + ->set('serializer.normalizer.form_error', FormErrorNormalizer::class) + ->tag('serializer.normalizer', ['priority' => -915]) + + ->set('serializer.normalizer.object', ObjectNormalizer::class) + ->args([ + service('serializer.mapping.class_metadata_factory'), + service('serializer.name_converter.metadata_aware'), + service('serializer.property_accessor'), + service('property_info')->ignoreOnInvalid(), + service('serializer.mapping.class_discriminator_resolver')->ignoreOnInvalid(), + null, + [], + ]) + ->tag('serializer.normalizer', ['priority' => -1000]) + + ->alias(ObjectNormalizer::class, 'serializer.normalizer.object') + + ->set('serializer.normalizer.property', PropertyNormalizer::class) + ->args([ + service('serializer.mapping.class_metadata_factory'), + service('serializer.name_converter.metadata_aware'), + service('property_info')->ignoreOnInvalid(), + service('serializer.mapping.class_discriminator_resolver')->ignoreOnInvalid(), + null, + [], + ]) + + ->alias(PropertyNormalizer::class, 'serializer.normalizer.property') + + ->set('serializer.denormalizer.array', ArrayDenormalizer::class) + ->tag('serializer.normalizer', ['priority' => -990]) + + // Loader + ->set('serializer.mapping.chain_loader', LoaderChain::class) + ->args([[]]) + + // Class Metadata Factory + ->set('serializer.mapping.class_metadata_factory', ClassMetadataFactory::class) + ->args([service('serializer.mapping.chain_loader')]) + + ->alias(ClassMetadataFactoryInterface::class, 'serializer.mapping.class_metadata_factory') + + // Cache + ->set('serializer.mapping.cache_warmer', SerializerCacheWarmer::class) + ->args([abstract_arg('The serializer metadata loaders'), param('serializer.mapping.cache.file')]) + ->tag('kernel.cache_warmer') + + ->set('serializer.mapping.cache.symfony', CacheItemPoolInterface::class) + ->factory([PhpArrayAdapter::class, 'create']) + ->args([param('serializer.mapping.cache.file'), service('cache.serializer')]) + + ->set('serializer.mapping.cache_class_metadata_factory', CacheClassMetadataFactory::class) + ->decorate('serializer.mapping.class_metadata_factory') + ->args([ + service('serializer.mapping.cache_class_metadata_factory.inner'), + service('serializer.mapping.cache.symfony'), + ]) + + // Encoders + ->set('serializer.encoder.xml', XmlEncoder::class) + ->tag('serializer.encoder') + + ->set('serializer.encoder.json', JsonEncoder::class) + ->tag('serializer.encoder') + + ->set('serializer.encoder.yaml', YamlEncoder::class) + ->args([null, null]) + ->tag('serializer.encoder') + + ->set('serializer.encoder.csv', CsvEncoder::class) + ->tag('serializer.encoder') + + // Name converter + ->set('serializer.name_converter.camel_case_to_snake_case', CamelCaseToSnakeCaseNameConverter::class) + + ->set('serializer.name_converter.metadata_aware', MetadataAwareNameConverter::class) + ->args([service('serializer.mapping.class_metadata_factory')]) + + // PropertyInfo extractor + ->set('property_info.serializer_extractor', SerializerExtractor::class) + ->args([service('serializer.mapping.class_metadata_factory')]) + ->tag('property_info.list_extractor', ['priority' => -999]) + + // ErrorRenderer integration + ->alias('error_renderer', 'error_renderer.serializer') + ->alias('error_renderer.serializer', 'error_handler.error_renderer.serializer') + + ->set('error_handler.error_renderer.serializer', SerializerErrorRenderer::class) + ->args([ + service('serializer'), + inline_service() + ->factory([SerializerErrorRenderer::class, 'getPreferredFormat']) + ->args([service('request_stack')]), + service('error_renderer.html'), + inline_service() + ->factory([HtmlErrorRenderer::class, 'isDebug']) + ->args([service('request_stack'), param('kernel.debug')]), + ]) + ; + + if (interface_exists(\BackedEnum::class)) { + $container->services() + ->set('serializer.normalizer.backed_enum', BackedEnumNormalizer::class) + ->tag('serializer.normalizer', ['priority' => -915]) + ; + } +}; diff --git a/lib/symfony/framework-bundle/Resources/config/serializer.xml b/lib/symfony/framework-bundle/Resources/config/serializer.xml deleted file mode 100644 index fe93664854..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/serializer.xml +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - %kernel.cache_dir%/serialization.php - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - null - - - - - - - - - - - - - - - - - - - - - - null - - - - - - - - %serializer.mapping.cache.file% - - - - - - - %serializer.mapping.cache.file% - - - - - - %serializer.mapping.cache.prefix% - - The "%service_id%" service is deprecated since Symfony 3.2 and will be removed in 4.0. APCu should now be automatically used when available. - - - - The "%service_id%" service is deprecated since Symfony 3.1 and will be removed in 4.0. APCu should now be automatically used when available. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/services.php b/lib/symfony/framework-bundle/Resources/config/services.php new file mode 100644 index 0000000000..a26dfb5adc --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/services.php @@ -0,0 +1,218 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\CacheWarmer\ConfigBuilderCacheWarmer; +use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; +use Symfony\Component\Config\Resource\SelfCheckingResourceChecker; +use Symfony\Component\Config\ResourceCheckerConfigCacheFactory; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\DependencyInjection\Config\ContainerParametersResourceChecker; +use Symfony\Component\DependencyInjection\EnvVarProcessor; +use Symfony\Component\DependencyInjection\ParameterBag\ContainerBag; +use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use Symfony\Component\DependencyInjection\ReverseContainer; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface as EventDispatcherInterfaceComponentAlias; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\UrlHelper; +use Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; +use Symfony\Component\HttpKernel\Config\FileLocator; +use Symfony\Component\HttpKernel\DependencyInjection\ServicesResetter; +use Symfony\Component\HttpKernel\EventListener\LocaleAwareListener; +use Symfony\Component\HttpKernel\HttpCache\Store; +use Symfony\Component\HttpKernel\HttpCache\StoreInterface; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\HttpKernel\UriSigner; +use Symfony\Component\Runtime\Runner\Symfony\HttpKernelRunner; +use Symfony\Component\Runtime\Runner\Symfony\ResponseRunner; +use Symfony\Component\Runtime\SymfonyRuntime; +use Symfony\Component\String\LazyString; +use Symfony\Component\String\Slugger\AsciiSlugger; +use Symfony\Component\String\Slugger\SluggerInterface; +use Symfony\Component\Workflow\WorkflowEvents; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; + +return static function (ContainerConfigurator $container) { + // this parameter is used at compile time in RegisterListenersPass + $container->parameters()->set('event_dispatcher.event_aliases', array_merge( + class_exists(ConsoleEvents::class) ? ConsoleEvents::ALIASES : [], + class_exists(FormEvents::class) ? FormEvents::ALIASES : [], + KernelEvents::ALIASES, + class_exists(WorkflowEvents::class) ? WorkflowEvents::ALIASES : [] + )); + + $container->services() + + ->set('parameter_bag', ContainerBag::class) + ->args([ + service('service_container'), + ]) + ->alias(ContainerBagInterface::class, 'parameter_bag') + ->alias(ParameterBagInterface::class, 'parameter_bag') + + ->set('event_dispatcher', EventDispatcher::class) + ->public() + ->tag('container.hot_path') + ->tag('event_dispatcher.dispatcher', ['name' => 'event_dispatcher']) + ->alias(EventDispatcherInterfaceComponentAlias::class, 'event_dispatcher') + ->alias(EventDispatcherInterface::class, 'event_dispatcher') + + ->set('http_kernel', HttpKernel::class) + ->public() + ->args([ + service('event_dispatcher'), + service('controller_resolver'), + service('request_stack'), + service('argument_resolver'), + ]) + ->tag('container.hot_path') + ->tag('container.preload', ['class' => HttpKernelRunner::class]) + ->tag('container.preload', ['class' => ResponseRunner::class]) + ->tag('container.preload', ['class' => SymfonyRuntime::class]) + ->alias(HttpKernelInterface::class, 'http_kernel') + + ->set('request_stack', RequestStack::class) + ->public() + ->alias(RequestStack::class, 'request_stack') + + ->set('http_cache', HttpCache::class) + ->args([ + service('kernel'), + service('http_cache.store'), + service('esi')->nullOnInvalid(), + abstract_arg('options'), + ]) + ->tag('container.hot_path') + + ->set('http_cache.store', Store::class) + ->args([ + param('kernel.cache_dir').'/http_cache', + ]) + ->alias(StoreInterface::class, 'http_cache.store') + + ->set('url_helper', UrlHelper::class) + ->args([ + service('request_stack'), + service('router.request_context')->ignoreOnInvalid(), + ]) + ->alias(UrlHelper::class, 'url_helper') + + ->set('cache_warmer', CacheWarmerAggregate::class) + ->public() + ->args([ + tagged_iterator('kernel.cache_warmer'), + param('kernel.debug'), + sprintf('%s/%sDeprecations.log', param('kernel.build_dir'), param('kernel.container_class')), + ]) + ->tag('container.no_preload') + + ->set('cache_clearer', ChainCacheClearer::class) + ->public() + ->args([ + tagged_iterator('kernel.cache_clearer'), + ]) + ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2']) + + ->set('kernel') + ->synthetic() + ->public() + ->alias(KernelInterface::class, 'kernel') + + ->set('filesystem', Filesystem::class) + ->public() + ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2']) + ->alias(Filesystem::class, 'filesystem') + + ->set('file_locator', FileLocator::class) + ->args([ + service('kernel'), + ]) + ->alias(FileLocator::class, 'file_locator') + + ->set('uri_signer', UriSigner::class) + ->args([ + param('kernel.secret'), + ]) + ->alias(UriSigner::class, 'uri_signer') + + ->set('config_cache_factory', ResourceCheckerConfigCacheFactory::class) + ->args([ + tagged_iterator('config_cache.resource_checker'), + ]) + + ->set('dependency_injection.config.container_parameters_resource_checker', ContainerParametersResourceChecker::class) + ->args([ + service('service_container'), + ]) + ->tag('config_cache.resource_checker', ['priority' => -980]) + + ->set('config.resource.self_checking_resource_checker', SelfCheckingResourceChecker::class) + ->tag('config_cache.resource_checker', ['priority' => -990]) + + ->set('services_resetter', ServicesResetter::class) + ->public() + + ->set('reverse_container', ReverseContainer::class) + ->args([ + service('service_container'), + service_locator([]), + ]) + ->alias(ReverseContainer::class, 'reverse_container') + + ->set('locale_aware_listener', LocaleAwareListener::class) + ->args([ + [], // locale aware services + service('request_stack'), + ]) + ->tag('kernel.event_subscriber') + + ->set('container.env_var_processor', EnvVarProcessor::class) + ->args([ + service('service_container'), + tagged_iterator('container.env_var_loader'), + ]) + ->tag('container.env_var_processor') + + ->set('slugger', AsciiSlugger::class) + ->args([ + param('kernel.default_locale'), + ]) + ->tag('kernel.locale_aware') + ->alias(SluggerInterface::class, 'slugger') + + ->set('container.getenv', \Closure::class) + ->factory([\Closure::class, 'fromCallable']) + ->args([ + [service('service_container'), 'getEnv'], + ]) + ->tag('routing.expression_language_function', ['function' => 'env']) + + // inherit from this service to lazily access env vars + ->set('container.env', LazyString::class) + ->abstract() + ->factory([LazyString::class, 'fromCallable']) + ->args([ + service('container.getenv'), + ]) + ->set('config_builder.warmer', ConfigBuilderCacheWarmer::class) + ->args([service(KernelInterface::class), service('logger')->nullOnInvalid()]) + ->tag('kernel.cache_warmer') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/services.xml b/lib/symfony/framework-bundle/Resources/config/services.xml deleted file mode 100644 index 0c7b666f1b..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/services.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Symfony\Component\HttpFoundation\ParameterBag - Symfony\Component\HttpFoundation\HeaderBag - Symfony\Component\HttpFoundation\FileBag - Symfony\Component\HttpFoundation\ServerBag - Symfony\Component\HttpFoundation\Request - Symfony\Component\HttpKernel\Kernel - - The "%service_id%" option is deprecated since version 3.3, to be removed in 4.0. - - - - - - - - - - - - - - - %kernel.root_dir%/Resources - - %kernel.root_dir% - - - - - - %kernel.secret% - - - - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/session.php b/lib/symfony/framework-bundle/Resources/config/session.php new file mode 100644 index 0000000000..43c0000dde --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/session.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\Session\DeprecatedSessionFactory; +use Symfony\Bundle\FrameworkBundle\Session\ServiceSessionFactory; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\SessionFactory; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\IdentityMarshaller; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\MarshallingSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\SessionHandlerFactory; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; +use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorageFactory; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorageFactory; +use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorageFactory; +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; +use Symfony\Component\HttpKernel\EventListener\SessionListener; + +return static function (ContainerConfigurator $container) { + $container->parameters()->set('session.metadata.storage_key', '_sf2_meta'); + + $container->services() + ->set('.session.do-not-use', Session::class) // to be removed in 6.0 + ->factory([service('session.factory'), 'createSession']) + ->set('session.factory', SessionFactory::class) + ->args([ + service('request_stack'), + service('session.storage.factory'), + [service('session_listener'), 'onSessionUsage'], + ]) + + ->set('session.storage.factory.native', NativeSessionStorageFactory::class) + ->args([ + param('session.storage.options'), + service('session.handler'), + inline_service(MetadataBag::class) + ->args([ + param('session.metadata.storage_key'), + param('session.metadata.update_threshold'), + ]), + false, + ]) + ->set('session.storage.factory.php_bridge', PhpBridgeSessionStorageFactory::class) + ->args([ + service('session.handler'), + inline_service(MetadataBag::class) + ->args([ + param('session.metadata.storage_key'), + param('session.metadata.update_threshold'), + ]), + false, + ]) + ->set('session.storage.factory.mock_file', MockFileSessionStorageFactory::class) + ->args([ + param('kernel.cache_dir').'/sessions', + 'MOCKSESSID', + inline_service(MetadataBag::class) + ->args([ + param('session.metadata.storage_key'), + param('session.metadata.update_threshold'), + ]), + ]) + ->set('session.storage.factory.service', ServiceSessionFactory::class) + ->args([ + service('session.storage'), + ]) + ->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, use "session.storage.factory.native", "session.storage.factory.php_bridge" or "session.storage.factory.mock_file" instead.') + + ->set('.session.deprecated', SessionInterface::class) // to be removed in 6.0 + ->factory([inline_service(DeprecatedSessionFactory::class)->args([service('request_stack')]), 'getSession']) + ->alias(SessionInterface::class, '.session.do-not-use') + ->deprecate('symfony/framework-bundle', '5.3', 'The "%alias_id%" and "SessionInterface" aliases are deprecated, use "$requestStack->getSession()" instead.') + ->alias(SessionStorageInterface::class, 'session.storage') + ->deprecate('symfony/framework-bundle', '5.3', 'The "%alias_id%" alias is deprecated, use "session.storage.factory" instead.') + ->alias(\SessionHandlerInterface::class, 'session.handler') + + ->set('session.storage.metadata_bag', MetadataBag::class) + ->args([ + param('session.metadata.storage_key'), + param('session.metadata.update_threshold'), + ]) + ->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, create your own "session.storage.factory" instead.') + + ->set('session.storage.native', NativeSessionStorage::class) + ->args([ + param('session.storage.options'), + service('session.handler'), + service('session.storage.metadata_bag'), + ]) + ->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, use "session.storage.factory.native" instead.') + + ->set('session.storage.php_bridge', PhpBridgeSessionStorage::class) + ->args([ + service('session.handler'), + service('session.storage.metadata_bag'), + ]) + ->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, use "session.storage.factory.php_bridge" instead.') + + ->set('session.flash_bag', FlashBag::class) + ->factory([service('.session.do-not-use'), 'getFlashBag']) + ->deprecate('symfony/framework-bundle', '5.1', 'The "%service_id%" service is deprecated, use "$session->getFlashBag()" instead.') + ->alias(FlashBagInterface::class, 'session.flash_bag') + + ->set('session.attribute_bag', AttributeBag::class) + ->factory([service('.session.do-not-use'), 'getBag']) + ->args(['attributes']) + ->deprecate('symfony/framework-bundle', '5.1', 'The "%service_id%" service is deprecated, use "$session->getAttributeBag()" instead.') + + ->set('session.storage.mock_file', MockFileSessionStorage::class) + ->args([ + param('kernel.cache_dir').'/sessions', + 'MOCKSESSID', + service('session.storage.metadata_bag'), + ]) + ->deprecate('symfony/framework-bundle', '5.3', 'The "%service_id%" service is deprecated, use "session.storage.factory.mock_file" instead.') + + ->set('session.handler.native_file', StrictSessionHandler::class) + ->args([ + inline_service(NativeFileSessionHandler::class) + ->args([param('session.save_path')]), + ]) + + ->set('session.abstract_handler', AbstractSessionHandler::class) + ->factory([SessionHandlerFactory::class, 'createHandler']) + ->args([abstract_arg('A string or a connection object')]) + + ->set('session_listener', SessionListener::class) + ->args([ + service_locator([ + 'session_factory' => service('session.factory')->ignoreOnInvalid(), + 'session' => service('.session.do-not-use')->ignoreOnInvalid(), + 'initialized_session' => service('.session.do-not-use')->ignoreOnUninitialized(), + 'logger' => service('logger')->ignoreOnInvalid(), + 'session_collector' => service('data_collector.request.session_collector')->ignoreOnInvalid(), + ]), + param('kernel.debug'), + param('session.storage.options'), + ]) + ->tag('kernel.event_subscriber') + ->tag('kernel.reset', ['method' => 'reset']) + + // for BC + ->alias('session.storage.filesystem', 'session.storage.mock_file') + ->deprecate('symfony/framework-bundle', '5.3', 'The "%alias_id%" alias is deprecated, use "session.storage.factory.mock_file" instead.') + + ->set('session.marshaller', IdentityMarshaller::class) + + ->set('session.marshalling_handler', MarshallingSessionHandler::class) + ->decorate('session.handler') + ->args([ + service('session.marshalling_handler.inner'), + service('session.marshaller'), + ]) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/session.xml b/lib/symfony/framework-bundle/Resources/config/session.xml deleted file mode 100644 index 9080af9379..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/session.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - _sf2_meta - - - - - - - - - - - - - - - %session.metadata.storage_key% - %session.metadata.update_threshold% - - - - %session.storage.options% - - - - - - - - - - - - - - - - - attributes - - - - %kernel.cache_dir%/sessions - MOCKSESSID - - - - - - - %session.save_path% - - - - - - The "%service_id%" service is deprecated since Symfony 3.4 and will be removed in 4.0. Use the `session.lazy_write` ini setting instead. - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/ssi.php b/lib/symfony/framework-bundle/Resources/config/ssi.php new file mode 100644 index 0000000000..d41aa74d1e --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/ssi.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\HttpKernel\EventListener\SurrogateListener; +use Symfony\Component\HttpKernel\HttpCache\Ssi; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('ssi', Ssi::class) + + ->set('ssi_listener', SurrogateListener::class) + ->args([service('ssi')->ignoreOnInvalid()]) + ->tag('kernel.event_subscriber') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/ssi.xml b/lib/symfony/framework-bundle/Resources/config/ssi.xml deleted file mode 100644 index b4e5b3d3df..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/ssi.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/templating.xml b/lib/symfony/framework-bundle/Resources/config/templating.xml deleted file mode 100644 index 83c904f16f..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/templating.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - %kernel.cache_dir% - - - - - - %kernel.root_dir%/Resources - - - - - - - - - - - - - - - %templating.loader.cache.path% - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/templating_debug.xml b/lib/symfony/framework-bundle/Resources/config/templating_debug.xml deleted file mode 100644 index f6cfd4ae68..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/templating_debug.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - %kernel.charset% - - - diff --git a/lib/symfony/framework-bundle/Resources/config/templating_php.xml b/lib/symfony/framework-bundle/Resources/config/templating_php.xml deleted file mode 100644 index 19c0b28234..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/templating_php.xml +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - %kernel.charset% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %kernel.project_dir% - %kernel.charset% - - - - - - - - - - - - - - - - - - - - %templating.helper.form.resources% - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/test.php b/lib/symfony/framework-bundle/Resources/config/test.php new file mode 100644 index 0000000000..76709595bf --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/test.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\KernelBrowser; +use Symfony\Bundle\FrameworkBundle\Test\TestContainer; +use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\BrowserKit\History; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\HttpKernel\EventListener\SessionListener; + +return static function (ContainerConfigurator $container) { + $container->parameters()->set('test.client.parameters', []); + + $container->services() + ->set('test.client', KernelBrowser::class) + ->args([ + service('kernel'), + param('test.client.parameters'), + service('test.client.history'), + service('test.client.cookiejar'), + ]) + ->share(false) + ->public() + + ->set('test.client.history', History::class)->share(false) + ->set('test.client.cookiejar', CookieJar::class)->share(false) + + ->set('test.session.listener', SessionListener::class) + ->args([ + service_locator([ + 'session' => service('.session.do-not-use')->ignoreOnInvalid(), + 'session_factory' => service('session.factory')->ignoreOnInvalid(), + ]), + param('kernel.debug'), + param('session.storage.options'), + ]) + ->tag('kernel.event_subscriber') + + ->set('test.service_container', TestContainer::class) + ->args([ + service('kernel'), + 'test.private_services_locator', + ]) + ->public() + + ->set('test.private_services_locator', ServiceLocator::class) + ->args([abstract_arg('callable collection')]) + ->public() + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/test.xml b/lib/symfony/framework-bundle/Resources/config/test.xml deleted file mode 100644 index 376b30d6e6..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/test.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - %test.client.parameters% - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/translation.php b/lib/symfony/framework-bundle/Resources/config/translation.php new file mode 100644 index 0000000000..706e4928ee --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/translation.php @@ -0,0 +1,162 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Psr\Container\ContainerInterface; +use Symfony\Bundle\FrameworkBundle\CacheWarmer\TranslationsCacheWarmer; +use Symfony\Bundle\FrameworkBundle\Translation\Translator; +use Symfony\Component\Translation\Dumper\CsvFileDumper; +use Symfony\Component\Translation\Dumper\IcuResFileDumper; +use Symfony\Component\Translation\Dumper\IniFileDumper; +use Symfony\Component\Translation\Dumper\JsonFileDumper; +use Symfony\Component\Translation\Dumper\MoFileDumper; +use Symfony\Component\Translation\Dumper\PhpFileDumper; +use Symfony\Component\Translation\Dumper\PoFileDumper; +use Symfony\Component\Translation\Dumper\QtFileDumper; +use Symfony\Component\Translation\Dumper\XliffFileDumper; +use Symfony\Component\Translation\Dumper\YamlFileDumper; +use Symfony\Component\Translation\Extractor\ChainExtractor; +use Symfony\Component\Translation\Extractor\ExtractorInterface; +use Symfony\Component\Translation\Extractor\PhpExtractor; +use Symfony\Component\Translation\Formatter\MessageFormatter; +use Symfony\Component\Translation\Loader\CsvFileLoader; +use Symfony\Component\Translation\Loader\IcuDatFileLoader; +use Symfony\Component\Translation\Loader\IcuResFileLoader; +use Symfony\Component\Translation\Loader\IniFileLoader; +use Symfony\Component\Translation\Loader\JsonFileLoader; +use Symfony\Component\Translation\Loader\MoFileLoader; +use Symfony\Component\Translation\Loader\PhpFileLoader; +use Symfony\Component\Translation\Loader\PoFileLoader; +use Symfony\Component\Translation\Loader\QtFileLoader; +use Symfony\Component\Translation\Loader\XliffFileLoader; +use Symfony\Component\Translation\Loader\YamlFileLoader; +use Symfony\Component\Translation\LoggingTranslator; +use Symfony\Component\Translation\Reader\TranslationReader; +use Symfony\Component\Translation\Reader\TranslationReaderInterface; +use Symfony\Component\Translation\Writer\TranslationWriter; +use Symfony\Component\Translation\Writer\TranslationWriterInterface; +use Symfony\Contracts\Translation\TranslatorInterface; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('translator.default', Translator::class) + ->args([ + abstract_arg('translation loaders locator'), + service('translator.formatter'), + param('kernel.default_locale'), + abstract_arg('translation loaders ids'), + [ + 'cache_dir' => param('kernel.cache_dir').'/translations', + 'debug' => param('kernel.debug'), + ], + abstract_arg('enabled locales'), + ]) + ->call('setConfigCacheFactory', [service('config_cache_factory')]) + ->tag('kernel.locale_aware') + + ->alias(TranslatorInterface::class, 'translator') + + ->set('translator.logging', LoggingTranslator::class) + ->args([ + service('translator.logging.inner'), + service('logger'), + ]) + ->tag('monolog.logger', ['channel' => 'translation']) + + ->set('translator.formatter.default', MessageFormatter::class) + ->args([service('identity_translator')]) + + ->set('translation.loader.php', PhpFileLoader::class) + ->tag('translation.loader', ['alias' => 'php']) + + ->set('translation.loader.yml', YamlFileLoader::class) + ->tag('translation.loader', ['alias' => 'yaml', 'legacy-alias' => 'yml']) + + ->set('translation.loader.xliff', XliffFileLoader::class) + ->tag('translation.loader', ['alias' => 'xlf', 'legacy-alias' => 'xliff']) + + ->set('translation.loader.po', PoFileLoader::class) + ->tag('translation.loader', ['alias' => 'po']) + + ->set('translation.loader.mo', MoFileLoader::class) + ->tag('translation.loader', ['alias' => 'mo']) + + ->set('translation.loader.qt', QtFileLoader::class) + ->tag('translation.loader', ['alias' => 'ts']) + + ->set('translation.loader.csv', CsvFileLoader::class) + ->tag('translation.loader', ['alias' => 'csv']) + + ->set('translation.loader.res', IcuResFileLoader::class) + ->tag('translation.loader', ['alias' => 'res']) + + ->set('translation.loader.dat', IcuDatFileLoader::class) + ->tag('translation.loader', ['alias' => 'dat']) + + ->set('translation.loader.ini', IniFileLoader::class) + ->tag('translation.loader', ['alias' => 'ini']) + + ->set('translation.loader.json', JsonFileLoader::class) + ->tag('translation.loader', ['alias' => 'json']) + + ->set('translation.dumper.php', PhpFileDumper::class) + ->tag('translation.dumper', ['alias' => 'php']) + + ->set('translation.dumper.xliff', XliffFileDumper::class) + ->tag('translation.dumper', ['alias' => 'xlf']) + + ->set('translation.dumper.po', PoFileDumper::class) + ->tag('translation.dumper', ['alias' => 'po']) + + ->set('translation.dumper.mo', MoFileDumper::class) + ->tag('translation.dumper', ['alias' => 'mo']) + + ->set('translation.dumper.yml', YamlFileDumper::class) + ->tag('translation.dumper', ['alias' => 'yml']) + + ->set('translation.dumper.yaml', YamlFileDumper::class) + ->args(['yaml']) + ->tag('translation.dumper', ['alias' => 'yaml']) + + ->set('translation.dumper.qt', QtFileDumper::class) + ->tag('translation.dumper', ['alias' => 'ts']) + + ->set('translation.dumper.csv', CsvFileDumper::class) + ->tag('translation.dumper', ['alias' => 'csv']) + + ->set('translation.dumper.ini', IniFileDumper::class) + ->tag('translation.dumper', ['alias' => 'ini']) + + ->set('translation.dumper.json', JsonFileDumper::class) + ->tag('translation.dumper', ['alias' => 'json']) + + ->set('translation.dumper.res', IcuResFileDumper::class) + ->tag('translation.dumper', ['alias' => 'res']) + + ->set('translation.extractor.php', PhpExtractor::class) + ->tag('translation.extractor', ['alias' => 'php']) + + ->set('translation.reader', TranslationReader::class) + ->alias(TranslationReaderInterface::class, 'translation.reader') + + ->set('translation.extractor', ChainExtractor::class) + ->alias(ExtractorInterface::class, 'translation.extractor') + + ->set('translation.writer', TranslationWriter::class) + ->alias(TranslationWriterInterface::class, 'translation.writer') + + ->set('translation.warmer', TranslationsCacheWarmer::class) + ->args([service(ContainerInterface::class)]) + ->tag('container.service_subscriber', ['id' => 'translator']) + ->tag('kernel.cache_warmer') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/translation.xml b/lib/symfony/framework-bundle/Resources/config/translation.xml deleted file mode 100644 index d1a47553d9..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/translation.xml +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - - - - - - %kernel.default_locale% - - - %kernel.cache_dir%/translations - %kernel.debug% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - yaml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The "%service_id%" service is deprecated since Symfony 3.4 and will be removed in 4.0. Use "translation.reader" instead. - - - - - - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/translation_debug.php b/lib/symfony/framework-bundle/Resources/config/translation_debug.php new file mode 100644 index 0000000000..7a83301811 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/translation_debug.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Translation\DataCollector\TranslationDataCollector; +use Symfony\Component\Translation\DataCollectorTranslator; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('translator.data_collector', DataCollectorTranslator::class) + ->args([service('translator.data_collector.inner')]) + + ->set('data_collector.translation', TranslationDataCollector::class) + ->args([service('translator.data_collector')]) + ->tag('data_collector', [ + 'template' => '@WebProfiler/Collector/translation.html.twig', + 'id' => 'translation', + 'priority' => 275, + ]) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/translation_debug.xml b/lib/symfony/framework-bundle/Resources/config/translation_debug.xml deleted file mode 100644 index c9c5385fbf..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/translation_debug.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/translation_providers.php b/lib/symfony/framework-bundle/Resources/config/translation_providers.php new file mode 100644 index 0000000000..cd140f077c --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/translation_providers.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 Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Translation\Bridge\Crowdin\CrowdinProviderFactory; +use Symfony\Component\Translation\Bridge\Loco\LocoProviderFactory; +use Symfony\Component\Translation\Bridge\Lokalise\LokaliseProviderFactory; +use Symfony\Component\Translation\Provider\NullProviderFactory; +use Symfony\Component\Translation\Provider\TranslationProviderCollection; +use Symfony\Component\Translation\Provider\TranslationProviderCollectionFactory; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('translation.provider_collection', TranslationProviderCollection::class) + ->factory([service('translation.provider_collection_factory'), 'fromConfig']) + ->args([ + [], // Providers + ]) + + ->set('translation.provider_collection_factory', TranslationProviderCollectionFactory::class) + ->args([ + tagged_iterator('translation.provider_factory'), + [], // Enabled locales + ]) + + ->set('translation.provider_factory.null', NullProviderFactory::class) + ->tag('translation.provider_factory') + + ->set('translation.provider_factory.crowdin', CrowdinProviderFactory::class) + ->args([ + service('http_client'), + service('logger'), + param('kernel.default_locale'), + service('translation.loader.xliff'), + service('translation.dumper.xliff'), + ]) + ->tag('translation.provider_factory') + + ->set('translation.provider_factory.loco', LocoProviderFactory::class) + ->args([ + service('http_client'), + service('logger'), + param('kernel.default_locale'), + service('translation.loader.xliff'), + ]) + ->tag('translation.provider_factory') + + ->set('translation.provider_factory.lokalise', LokaliseProviderFactory::class) + ->args([ + service('http_client'), + service('logger'), + param('kernel.default_locale'), + service('translation.loader.xliff'), + ]) + ->tag('translation.provider_factory') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/uid.php b/lib/symfony/framework-bundle/Resources/config/uid.php new file mode 100644 index 0000000000..840fb97b5f --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/uid.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 Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Uid\Factory\NameBasedUuidFactory; +use Symfony\Component\Uid\Factory\RandomBasedUuidFactory; +use Symfony\Component\Uid\Factory\TimeBasedUuidFactory; +use Symfony\Component\Uid\Factory\UlidFactory; +use Symfony\Component\Uid\Factory\UuidFactory; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('ulid.factory', UlidFactory::class) + ->alias(UlidFactory::class, 'ulid.factory') + + ->set('uuid.factory', UuidFactory::class) + ->alias(UuidFactory::class, 'uuid.factory') + + ->set('name_based_uuid.factory', NameBasedUuidFactory::class) + ->factory([service('uuid.factory'), 'nameBased']) + ->args([abstract_arg('Please set the "framework.uid.name_based_uuid_namespace" configuration option to use the "name_based_uuid.factory" service')]) + ->alias(NameBasedUuidFactory::class, 'name_based_uuid.factory') + + ->set('random_based_uuid.factory', RandomBasedUuidFactory::class) + ->factory([service('uuid.factory'), 'randomBased']) + ->alias(RandomBasedUuidFactory::class, 'random_based_uuid.factory') + + ->set('time_based_uuid.factory', TimeBasedUuidFactory::class) + ->factory([service('uuid.factory'), 'timeBased']) + ->alias(TimeBasedUuidFactory::class, 'time_based_uuid.factory') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/validator.php b/lib/symfony/framework-bundle/Resources/config/validator.php new file mode 100644 index 0000000000..700ea69edd --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/validator.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\CacheWarmer\ValidatorCacheWarmer; +use Symfony\Component\Cache\Adapter\PhpArrayAdapter; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\Validator\Constraints\EmailValidator; +use Symfony\Component\Validator\Constraints\ExpressionValidator; +use Symfony\Component\Validator\Constraints\NotCompromisedPasswordValidator; +use Symfony\Component\Validator\ContainerConstraintValidatorFactory; +use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; +use Symfony\Component\Validator\Validation; +use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Component\Validator\ValidatorBuilder; + +return static function (ContainerConfigurator $container) { + $container->parameters() + ->set('validator.mapping.cache.file', param('kernel.cache_dir').'/validation.php'); + + $container->services() + ->set('validator', ValidatorInterface::class) + ->public() + ->factory([service('validator.builder'), 'getValidator']) + ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.2']) + ->alias(ValidatorInterface::class, 'validator') + + ->set('validator.builder', ValidatorBuilder::class) + ->factory([Validation::class, 'createValidatorBuilder']) + ->call('setConstraintValidatorFactory', [ + service('validator.validator_factory'), + ]) + ->call('setTranslator', [ + service('translator')->ignoreOnInvalid(), + ]) + ->call('setTranslationDomain', [ + param('validator.translation_domain'), + ]) + ->alias('validator.mapping.class_metadata_factory', 'validator') + + ->set('validator.mapping.cache_warmer', ValidatorCacheWarmer::class) + ->args([ + service('validator.builder'), + param('validator.mapping.cache.file'), + ]) + ->tag('kernel.cache_warmer') + + ->set('validator.mapping.cache.adapter', PhpArrayAdapter::class) + ->factory([PhpArrayAdapter::class, 'create']) + ->args([ + param('validator.mapping.cache.file'), + service('cache.validator'), + ]) + + ->set('validator.validator_factory', ContainerConstraintValidatorFactory::class) + ->args([ + abstract_arg('Constraint validators locator'), + ]) + + ->set('validator.expression', ExpressionValidator::class) + ->args([service('validator.expression_language')->nullOnInvalid()]) + ->tag('validator.constraint_validator', [ + 'alias' => 'validator.expression', + ]) + + ->set('validator.expression_language', ExpressionLanguage::class) + ->args([service('cache.validator_expression_language')->nullOnInvalid()]) + + ->set('cache.validator_expression_language') + ->parent('cache.system') + ->tag('cache.pool') + + ->set('validator.email', EmailValidator::class) + ->args([ + abstract_arg('Default mode'), + ]) + ->tag('validator.constraint_validator', [ + 'alias' => EmailValidator::class, + ]) + + ->set('validator.not_compromised_password', NotCompromisedPasswordValidator::class) + ->args([ + service('http_client')->nullOnInvalid(), + param('kernel.charset'), + false, + ]) + ->tag('validator.constraint_validator', [ + 'alias' => NotCompromisedPasswordValidator::class, + ]) + + ->set('validator.property_info_loader', PropertyInfoLoader::class) + ->args([ + service('property_info'), + service('property_info'), + service('property_info'), + ]) + ->tag('validator.auto_mapper') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/validator.xml b/lib/symfony/framework-bundle/Resources/config/validator.xml deleted file mode 100644 index 8a4265eea1..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/validator.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - %kernel.cache_dir%/validation.php - - - - - - - - - - - - - - - - - - - - %validator.translation_domain% - - - - - - - - %validator.mapping.cache.file% - - - - - - - - - %validator.mapping.cache.file% - - - - - - - - - - %validator.mapping.cache.prefix% - - - - The "%service_id%" service is deprecated since Symfony 3.4 and will be removed in 4.0. Use a Psr6 cache like "validator.mapping.cache.symfony" instead. - - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/validator_debug.php b/lib/symfony/framework-bundle/Resources/config/validator_debug.php new file mode 100644 index 0000000000..e9fe441140 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/validator_debug.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\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Validator\DataCollector\ValidatorDataCollector; +use Symfony\Component\Validator\Validator\TraceableValidator; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('debug.validator', TraceableValidator::class) + ->decorate('validator', null, 255) + ->args([ + service('debug.validator.inner'), + ]) + ->tag('kernel.reset', [ + 'method' => 'reset', + ]) + + ->set('data_collector.validator', ValidatorDataCollector::class) + ->args([ + service('debug.validator'), + ]) + ->tag('data_collector', [ + 'template' => '@WebProfiler/Collector/validator.html.twig', + 'id' => 'validator', + 'priority' => 320, + ]) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/validator_debug.xml b/lib/symfony/framework-bundle/Resources/config/validator_debug.xml deleted file mode 100644 index 939c55553c..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/validator_debug.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/web.php b/lib/symfony/framework-bundle/Resources/config/web.php new file mode 100644 index 0000000000..53613d3b50 --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/web.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\ServiceValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\SessionValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver; +use Symfony\Component\HttpKernel\Controller\ErrorController; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; +use Symfony\Component\HttpKernel\EventListener\DisallowRobotsIndexingListener; +use Symfony\Component\HttpKernel\EventListener\ErrorListener; +use Symfony\Component\HttpKernel\EventListener\LocaleListener; +use Symfony\Component\HttpKernel\EventListener\ResponseListener; +use Symfony\Component\HttpKernel\EventListener\StreamedResponseListener; +use Symfony\Component\HttpKernel\EventListener\ValidateRequestListener; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('controller_resolver', ControllerResolver::class) + ->args([ + service('service_container'), + service('logger')->ignoreOnInvalid(), + ]) + ->tag('monolog.logger', ['channel' => 'request']) + + ->set('argument_metadata_factory', ArgumentMetadataFactory::class) + + ->set('argument_resolver', ArgumentResolver::class) + ->args([ + service('argument_metadata_factory'), + abstract_arg('argument value resolvers'), + ]) + + ->set('argument_resolver.request_attribute', RequestAttributeValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => 100]) + + ->set('argument_resolver.request', RequestValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => 50]) + + ->set('argument_resolver.session', SessionValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => 50]) + + ->set('argument_resolver.service', ServiceValueResolver::class) + ->args([ + abstract_arg('service locator, set in RegisterControllerArgumentLocatorsPass'), + ]) + ->tag('controller.argument_value_resolver', ['priority' => -50]) + + ->set('argument_resolver.default', DefaultValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => -100]) + + ->set('argument_resolver.variadic', VariadicValueResolver::class) + ->tag('controller.argument_value_resolver', ['priority' => -150]) + + ->set('response_listener', ResponseListener::class) + ->args([ + param('kernel.charset'), + abstract_arg('The "set_content_language_from_locale" config value'), + ]) + ->tag('kernel.event_subscriber') + + ->set('streamed_response_listener', StreamedResponseListener::class) + ->tag('kernel.event_subscriber') + + ->set('locale_listener', LocaleListener::class) + ->args([ + service('request_stack'), + param('kernel.default_locale'), + service('router')->ignoreOnInvalid(), + abstract_arg('The "set_locale_from_accept_language" config value'), + param('kernel.enabled_locales'), + ]) + ->tag('kernel.event_subscriber') + + ->set('validate_request_listener', ValidateRequestListener::class) + ->tag('kernel.event_subscriber') + + ->set('disallow_search_engine_index_response_listener', DisallowRobotsIndexingListener::class) + ->tag('kernel.event_subscriber') + + ->set('error_controller', ErrorController::class) + ->public() + ->args([ + service('http_kernel'), + param('kernel.error_controller'), + service('error_renderer'), + ]) + + ->set('exception_listener', ErrorListener::class) + ->args([ + param('kernel.error_controller'), + service('logger')->nullOnInvalid(), + param('kernel.debug'), + abstract_arg('an exceptions to log & status code mapping'), + ]) + ->tag('kernel.event_subscriber') + ->tag('monolog.logger', ['channel' => 'request']) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/web.xml b/lib/symfony/framework-bundle/Resources/config/web.xml deleted file mode 100644 index fb797ff96b..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/web.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %kernel.charset% - - - - - - - - - - %kernel.default_locale% - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/web_link.php b/lib/symfony/framework-bundle/Resources/config/web_link.php new file mode 100644 index 0000000000..0b0e79db8c --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/web_link.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 Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('web_link.add_link_header_listener', AddLinkHeaderListener::class) + ->tag('kernel.event_subscriber') + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/web_link.xml b/lib/symfony/framework-bundle/Resources/config/web_link.xml deleted file mode 100644 index bf3e8d7211..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/web_link.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/config/workflow.php b/lib/symfony/framework-bundle/Resources/config/workflow.php new file mode 100644 index 0000000000..909bb5acab --- /dev/null +++ b/lib/symfony/framework-bundle/Resources/config/workflow.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\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Component\Workflow\EventListener\ExpressionLanguage; +use Symfony\Component\Workflow\MarkingStore\MethodMarkingStore; +use Symfony\Component\Workflow\Registry; +use Symfony\Component\Workflow\StateMachine; +use Symfony\Component\Workflow\Workflow; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('workflow.abstract', Workflow::class) + ->args([ + abstract_arg('workflow definition'), + abstract_arg('marking store'), + service('event_dispatcher')->ignoreOnInvalid(), + abstract_arg('workflow name'), + abstract_arg('events to dispatch'), + ]) + ->abstract() + ->public() + ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.3']) + ->set('state_machine.abstract', StateMachine::class) + ->args([ + abstract_arg('workflow definition'), + abstract_arg('marking store'), + service('event_dispatcher')->ignoreOnInvalid(), + abstract_arg('workflow name'), + abstract_arg('events to dispatch'), + ]) + ->abstract() + ->public() + ->tag('container.private', ['package' => 'symfony/framework-bundle', 'version' => '5.3']) + ->set('workflow.marking_store.method', MethodMarkingStore::class) + ->abstract() + ->set('workflow.registry', Registry::class) + ->alias(Registry::class, 'workflow.registry') + ->set('workflow.security.expression_language', ExpressionLanguage::class) + ; +}; diff --git a/lib/symfony/framework-bundle/Resources/config/workflow.xml b/lib/symfony/framework-bundle/Resources/config/workflow.xml deleted file mode 100644 index 5f349ea6a0..0000000000 --- a/lib/symfony/framework-bundle/Resources/config/workflow.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - null - - - - - - null - - - - - - - - - - - - - diff --git a/lib/symfony/framework-bundle/Resources/views/Form/attributes.html.php b/lib/symfony/framework-bundle/Resources/views/Form/attributes.html.php deleted file mode 100644 index dbed8eff72..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/attributes.html.php +++ /dev/null @@ -1,9 +0,0 @@ - $v): ?> - -escape($k), $view->escape(false !== $translation_domain ? $view['translator']->trans($v, [], $translation_domain) : $v)) ?> - -escape($k), $view->escape($k)) ?> - -escape($k), $view->escape($v)) ?> - - diff --git a/lib/symfony/framework-bundle/Resources/views/Form/button_attributes.html.php b/lib/symfony/framework-bundle/Resources/views/Form/button_attributes.html.php deleted file mode 100644 index 279233baa3..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/button_attributes.html.php +++ /dev/null @@ -1,2 +0,0 @@ -id="escape($id) ?>" name="escape($full_name) ?>" disabled="disabled" -block($form, 'attributes') : '' ?> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/button_row.html.php b/lib/symfony/framework-bundle/Resources/views/Form/button_row.html.php deleted file mode 100644 index b52e929845..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/button_row.html.php +++ /dev/null @@ -1,3 +0,0 @@ -
                  - widget($form) ?> -
                  diff --git a/lib/symfony/framework-bundle/Resources/views/Form/button_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/button_widget.html.php deleted file mode 100644 index 2a10aa0f41..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/button_widget.html.php +++ /dev/null @@ -1,4 +0,0 @@ - $name, '%id%' => $id]) - : $view['form']->humanize($name); } ?> - diff --git a/lib/symfony/framework-bundle/Resources/views/Form/checkbox_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/checkbox_widget.html.php deleted file mode 100644 index 143557dea8..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/checkbox_widget.html.php +++ /dev/null @@ -1,5 +0,0 @@ -block($form, 'widget_attributes') ?> - 0): ?> value="escape($value) ?>" - checked="checked" -/> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/choice_attributes.html.php b/lib/symfony/framework-bundle/Resources/views/Form/choice_attributes.html.php deleted file mode 100644 index 18f8368dc8..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/choice_attributes.html.php +++ /dev/null @@ -1,8 +0,0 @@ -disabled="disabled" - $v): ?> - -escape($k), $view->escape($k)) ?> - -escape($k), $view->escape($v)) ?> - - diff --git a/lib/symfony/framework-bundle/Resources/views/Form/choice_options.html.php b/lib/symfony/framework-bundle/Resources/views/Form/choice_options.html.php deleted file mode 100644 index 211ae73f1c..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/choice_options.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'choice_widget_options') ?> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/choice_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/choice_widget.html.php deleted file mode 100644 index 13593a96f1..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/choice_widget.html.php +++ /dev/null @@ -1,5 +0,0 @@ - -block($form, 'choice_widget_expanded') ?> - -block($form, 'choice_widget_collapsed') ?> - diff --git a/lib/symfony/framework-bundle/Resources/views/Form/choice_widget_collapsed.html.php b/lib/symfony/framework-bundle/Resources/views/Form/choice_widget_collapsed.html.php deleted file mode 100644 index 6a57d585c7..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/choice_widget_collapsed.html.php +++ /dev/null @@ -1,18 +0,0 @@ - diff --git a/lib/symfony/framework-bundle/Resources/views/Form/choice_widget_expanded.html.php b/lib/symfony/framework-bundle/Resources/views/Form/choice_widget_expanded.html.php deleted file mode 100644 index 9691759a90..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/choice_widget_expanded.html.php +++ /dev/null @@ -1,6 +0,0 @@ -
                  block($form, 'widget_container_attributes') ?>> - - widget($child) ?> - label($child, null, ['translation_domain' => $choice_translation_domain]) ?> - -
                  diff --git a/lib/symfony/framework-bundle/Resources/views/Form/choice_widget_options.html.php b/lib/symfony/framework-bundle/Resources/views/Form/choice_widget_options.html.php deleted file mode 100644 index a19b5a5e6e..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/choice_widget_options.html.php +++ /dev/null @@ -1,13 +0,0 @@ - - - $choice): ?> - - - block($form, 'choice_widget_options', ['choices' => $choice]) ?> - - - - - diff --git a/lib/symfony/framework-bundle/Resources/views/Form/collection_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/collection_widget.html.php deleted file mode 100644 index 40dfdaf1f0..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/collection_widget.html.php +++ /dev/null @@ -1,4 +0,0 @@ - - escape($view['form']->row($prototype)) ?> - -widget($form, ['attr' => $attr]) ?> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/color_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/color_widget.html.php deleted file mode 100644 index 48f5c2c30d..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/color_widget.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_widget_simple', ['type' => isset($type) ? $type : 'color']); diff --git a/lib/symfony/framework-bundle/Resources/views/Form/container_attributes.html.php b/lib/symfony/framework-bundle/Resources/views/Form/container_attributes.html.php deleted file mode 100644 index 302bbfcd47..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/container_attributes.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'widget_container_attributes') ?> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/date_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/date_widget.html.php deleted file mode 100644 index fbd844f286..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/date_widget.html.php +++ /dev/null @@ -1,11 +0,0 @@ - - block($form, 'form_widget_simple'); ?> - -
                  block($form, 'widget_container_attributes') ?>> - widget($form['year']), - $view['form']->widget($form['month']), - $view['form']->widget($form['day']), - ], $date_pattern) ?> -
                  - diff --git a/lib/symfony/framework-bundle/Resources/views/Form/datetime_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/datetime_widget.html.php deleted file mode 100644 index aef2a32512..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/datetime_widget.html.php +++ /dev/null @@ -1,7 +0,0 @@ - - block($form, 'form_widget_simple'); ?> - -
                  block($form, 'widget_container_attributes') ?>> - widget($form['date']).' '.$view['form']->widget($form['time']) ?> -
                  - diff --git a/lib/symfony/framework-bundle/Resources/views/Form/email_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/email_widget.html.php deleted file mode 100644 index fa6b0ee8a1..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/email_widget.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_widget_simple', ['type' => isset($type) ? $type : 'email']) ?> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/form.html.php b/lib/symfony/framework-bundle/Resources/views/Form/form.html.php deleted file mode 100644 index fb789faff7..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/form.html.php +++ /dev/null @@ -1,3 +0,0 @@ -start($form) ?> - widget($form) ?> -end($form) ?> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/form_enctype.html.php b/lib/symfony/framework-bundle/Resources/views/Form/form_enctype.html.php deleted file mode 100644 index 36eba3c9e8..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/form_enctype.html.php +++ /dev/null @@ -1 +0,0 @@ -vars['multipart']): ?>enctype="multipart/form-data" diff --git a/lib/symfony/framework-bundle/Resources/views/Form/form_end.html.php b/lib/symfony/framework-bundle/Resources/views/Form/form_end.html.php deleted file mode 100644 index fe6843905c..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/form_end.html.php +++ /dev/null @@ -1,4 +0,0 @@ - -rest($form) ?> - - diff --git a/lib/symfony/framework-bundle/Resources/views/Form/form_errors.html.php b/lib/symfony/framework-bundle/Resources/views/Form/form_errors.html.php deleted file mode 100644 index d97179e9a6..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/form_errors.html.php +++ /dev/null @@ -1,7 +0,0 @@ - 0): ?> -
                    - -
                  • escape($error->getMessage()) ?>
                  • - -
                  - diff --git a/lib/symfony/framework-bundle/Resources/views/Form/form_label.html.php b/lib/symfony/framework-bundle/Resources/views/Form/form_label.html.php deleted file mode 100644 index 8e8e5b9498..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/form_label.html.php +++ /dev/null @@ -1,8 +0,0 @@ - - - - $name, '%id%' => $id]) - : $view['form']->humanize($name); } ?> -block($form, 'attributes', ['attr' => $label_attr]); } ?>>escape(false !== $translation_domain ? $view['translator']->trans($label, [], $translation_domain) : $label) ?> - diff --git a/lib/symfony/framework-bundle/Resources/views/Form/form_rest.html.php b/lib/symfony/framework-bundle/Resources/views/Form/form_rest.html.php deleted file mode 100644 index 89041c6ec6..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/form_rest.html.php +++ /dev/null @@ -1,5 +0,0 @@ - - isRendered()): ?> - row($child) ?> - - diff --git a/lib/symfony/framework-bundle/Resources/views/Form/form_row.html.php b/lib/symfony/framework-bundle/Resources/views/Form/form_row.html.php deleted file mode 100644 index a4f86d0223..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/form_row.html.php +++ /dev/null @@ -1,5 +0,0 @@ -
                  - label($form) ?> - errors($form) ?> - widget($form) ?> -
                  diff --git a/lib/symfony/framework-bundle/Resources/views/Form/form_rows.html.php b/lib/symfony/framework-bundle/Resources/views/Form/form_rows.html.php deleted file mode 100644 index 8c3ba86f7a..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/form_rows.html.php +++ /dev/null @@ -1,3 +0,0 @@ - - row($child) ?> - diff --git a/lib/symfony/framework-bundle/Resources/views/Form/form_start.html.php b/lib/symfony/framework-bundle/Resources/views/Form/form_start.html.php deleted file mode 100644 index 7e24425805..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/form_start.html.php +++ /dev/null @@ -1,6 +0,0 @@ - - -
                  action="escape($action) ?>" $v) { printf(' %s="%s"', $view->escape($k), $view->escape($v)); } ?> enctype="multipart/form-data"> - - - diff --git a/lib/symfony/framework-bundle/Resources/views/Form/form_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/form_widget.html.php deleted file mode 100644 index c5af39a5b6..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/form_widget.html.php +++ /dev/null @@ -1,5 +0,0 @@ - -block($form, 'form_widget_compound')?> - -block($form, 'form_widget_simple')?> - diff --git a/lib/symfony/framework-bundle/Resources/views/Form/form_widget_compound.html.php b/lib/symfony/framework-bundle/Resources/views/Form/form_widget_compound.html.php deleted file mode 100644 index 7a4f7cd51f..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/form_widget_compound.html.php +++ /dev/null @@ -1,7 +0,0 @@ -
                  block($form, 'widget_container_attributes') ?>> - parent && $errors): ?> - errors($form) ?> - - block($form, 'form_rows') ?> - rest($form) ?> -
                  diff --git a/lib/symfony/framework-bundle/Resources/views/Form/form_widget_simple.html.php b/lib/symfony/framework-bundle/Resources/views/Form/form_widget_simple.html.php deleted file mode 100644 index 5d7654f54c..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/form_widget_simple.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'widget_attributes') ?> value="escape($value) ?>" /> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/hidden_row.html.php b/lib/symfony/framework-bundle/Resources/views/Form/hidden_row.html.php deleted file mode 100644 index 3239d8f415..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/hidden_row.html.php +++ /dev/null @@ -1 +0,0 @@ -widget($form) ?> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/hidden_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/hidden_widget.html.php deleted file mode 100644 index 6bcbb5e046..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/hidden_widget.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_widget_simple', ['type' => isset($type) ? $type : 'hidden']) ?> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/integer_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/integer_widget.html.php deleted file mode 100644 index faaff51e67..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/integer_widget.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_widget_simple', ['type' => isset($type) ? $type : 'number']) ?> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/money_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/money_widget.html.php deleted file mode 100644 index 25fe13f7e0..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/money_widget.html.php +++ /dev/null @@ -1 +0,0 @@ -formEncodeCurrency($money_pattern, $view['form']->block($form, 'form_widget_simple')) ?> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/number_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/number_widget.html.php deleted file mode 100644 index 3d6f79c63b..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/number_widget.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_widget_simple', ['type' => isset($type) ? $type : 'text']) ?> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/password_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/password_widget.html.php deleted file mode 100644 index 5514468f6a..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/password_widget.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_widget_simple', ['type' => isset($type) ? $type : 'password']) ?> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/percent_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/percent_widget.html.php deleted file mode 100644 index f5839ebf70..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/percent_widget.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_widget_simple', ['type' => isset($type) ? $type : 'text']) ?> % diff --git a/lib/symfony/framework-bundle/Resources/views/Form/radio_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/radio_widget.html.php deleted file mode 100644 index ddc8c529df..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/radio_widget.html.php +++ /dev/null @@ -1,5 +0,0 @@ -block($form, 'widget_attributes') ?> - value="escape($value) ?>" - checked="checked" -/> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/range_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/range_widget.html.php deleted file mode 100644 index d6650d8154..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/range_widget.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_widget_simple', ['type' => isset($type) ? $type : 'range']); diff --git a/lib/symfony/framework-bundle/Resources/views/Form/repeated_row.html.php b/lib/symfony/framework-bundle/Resources/views/Form/repeated_row.html.php deleted file mode 100644 index d4af23d712..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/repeated_row.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_rows') ?> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/reset_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/reset_widget.html.php deleted file mode 100644 index af45b15669..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/reset_widget.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'button_widget', ['type' => isset($type) ? $type : 'reset']) ?> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/search_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/search_widget.html.php deleted file mode 100644 index 191279b517..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/search_widget.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_widget_simple', ['type' => isset($type) ? $type : 'search']) ?> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/submit_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/submit_widget.html.php deleted file mode 100644 index baea833326..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/submit_widget.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'button_widget', ['type' => isset($type) ? $type : 'submit']) ?> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/tel_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/tel_widget.html.php deleted file mode 100644 index dd471b9518..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/tel_widget.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_widget_simple', ['type' => isset($type) ? $type : 'tel']); diff --git a/lib/symfony/framework-bundle/Resources/views/Form/textarea_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/textarea_widget.html.php deleted file mode 100644 index c989ce575d..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/textarea_widget.html.php +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/symfony/framework-bundle/Resources/views/Form/time_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/time_widget.html.php deleted file mode 100644 index cd2f559601..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/time_widget.html.php +++ /dev/null @@ -1,22 +0,0 @@ - - block($form, 'form_widget_simple'); ?> - - ['size' => 1]] : [] ?> -
                  block($form, 'widget_container_attributes') ?>> - widget($form['hour'], $vars); - - if ($with_minutes) { - echo ':'; - echo $view['form']->widget($form['minute'], $vars); - } - - if ($with_seconds) { - echo ':'; - echo $view['form']->widget($form['second'], $vars); - } - ?> -
                  - diff --git a/lib/symfony/framework-bundle/Resources/views/Form/url_widget.html.php b/lib/symfony/framework-bundle/Resources/views/Form/url_widget.html.php deleted file mode 100644 index cd653c89c5..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/url_widget.html.php +++ /dev/null @@ -1 +0,0 @@ -block($form, 'form_widget_simple', ['type' => isset($type) ? $type : 'url']) ?> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/widget_attributes.html.php b/lib/symfony/framework-bundle/Resources/views/Form/widget_attributes.html.php deleted file mode 100644 index 41c0cc7bfe..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/widget_attributes.html.php +++ /dev/null @@ -1,3 +0,0 @@ -id="escape($id) ?>" name="escape($full_name) ?>" disabled="disabled" - required="required" -block($form, 'attributes') : '' ?> diff --git a/lib/symfony/framework-bundle/Resources/views/Form/widget_container_attributes.html.php b/lib/symfony/framework-bundle/Resources/views/Form/widget_container_attributes.html.php deleted file mode 100644 index fdd176d12c..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/Form/widget_container_attributes.html.php +++ /dev/null @@ -1,2 +0,0 @@ -id="escape($id) ?>" -block($form, 'attributes') : '' ?> diff --git a/lib/symfony/framework-bundle/Resources/views/FormTable/button_row.html.php b/lib/symfony/framework-bundle/Resources/views/FormTable/button_row.html.php deleted file mode 100644 index 67d0137e20..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/FormTable/button_row.html.php +++ /dev/null @@ -1,6 +0,0 @@ - - - - widget($form); ?> - - diff --git a/lib/symfony/framework-bundle/Resources/views/FormTable/form_row.html.php b/lib/symfony/framework-bundle/Resources/views/FormTable/form_row.html.php deleted file mode 100644 index e2f03ff2b7..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/FormTable/form_row.html.php +++ /dev/null @@ -1,9 +0,0 @@ - - - label($form); ?> - - - errors($form); ?> - widget($form); ?> - - diff --git a/lib/symfony/framework-bundle/Resources/views/FormTable/form_widget_compound.html.php b/lib/symfony/framework-bundle/Resources/views/FormTable/form_widget_compound.html.php deleted file mode 100644 index adc8973388..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/FormTable/form_widget_compound.html.php +++ /dev/null @@ -1,11 +0,0 @@ -block($form, 'widget_container_attributes'); ?>> - parent && $errors): ?> - - - - - block($form, 'form_rows'); ?> - rest($form); ?> -
                  - errors($form); ?> -
                  diff --git a/lib/symfony/framework-bundle/Resources/views/FormTable/hidden_row.html.php b/lib/symfony/framework-bundle/Resources/views/FormTable/hidden_row.html.php deleted file mode 100644 index 116b300bd5..0000000000 --- a/lib/symfony/framework-bundle/Resources/views/FormTable/hidden_row.html.php +++ /dev/null @@ -1,5 +0,0 @@ - - - widget($form); ?> - - diff --git a/lib/symfony/framework-bundle/Routing/AnnotatedRouteControllerLoader.php b/lib/symfony/framework-bundle/Routing/AnnotatedRouteControllerLoader.php index 51419c8914..511e87058b 100644 --- a/lib/symfony/framework-bundle/Routing/AnnotatedRouteControllerLoader.php +++ b/lib/symfony/framework-bundle/Routing/AnnotatedRouteControllerLoader.php @@ -24,10 +24,8 @@ class AnnotatedRouteControllerLoader extends AnnotationClassLoader { /** * Configures the _controller default parameter of a given Route instance. - * - * @param mixed $annot The annotation class instance */ - protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot) + protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot) { if ('__invoke' === $method->getName()) { $route->setDefault('_controller', $class->getName()); @@ -43,14 +41,12 @@ class AnnotatedRouteControllerLoader extends AnnotationClassLoader */ protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method) { - return preg_replace([ - '/(bundle|controller)_/', - '/action(_\d+)?$/', - '/__/', - ], [ - '_', - '\\1', - '_', - ], parent::getDefaultRouteName($class, $method)); + $name = preg_replace('/(bundle|controller)_/', '_', parent::getDefaultRouteName($class, $method)); + + if (str_ends_with($method->name, 'Action') || str_ends_with($method->name, '_action')) { + $name = preg_replace('/action(_\d+)?$/', '\\1', $name); + } + + return str_replace('__', '_', $name); } } diff --git a/lib/symfony/framework-bundle/Routing/DelegatingLoader.php b/lib/symfony/framework-bundle/Routing/DelegatingLoader.php index cf94ffee97..e130bd2fa9 100644 --- a/lib/symfony/framework-bundle/Routing/DelegatingLoader.php +++ b/lib/symfony/framework-bundle/Routing/DelegatingLoader.php @@ -11,10 +11,10 @@ namespace Symfony\Bundle\FrameworkBundle\Routing; -use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser; -use Symfony\Component\Config\Exception\FileLoaderLoadException; +use Symfony\Component\Config\Exception\LoaderLoadException; use Symfony\Component\Config\Loader\DelegatingLoader as BaseDelegatingLoader; use Symfony\Component\Config\Loader\LoaderResolverInterface; +use Symfony\Component\Routing\RouteCollection; /** * DelegatingLoader delegates route loading to other loaders using a loader resolver. @@ -23,19 +23,19 @@ use Symfony\Component\Config\Loader\LoaderResolverInterface; * to the fully-qualified form (from a:b:c to class::method). * * @author Fabien Potencier + * + * @final */ class DelegatingLoader extends BaseDelegatingLoader { - protected $parser; private $loading = false; + private $defaultOptions; + private $defaultRequirements; - /** - * @param ControllerNameParser $parser A ControllerNameParser instance - * @param LoaderResolverInterface $resolver A LoaderResolverInterface instance - */ - public function __construct(ControllerNameParser $parser, LoaderResolverInterface $resolver) + public function __construct(LoaderResolverInterface $resolver, array $defaultOptions = [], array $defaultRequirements = []) { - $this->parser = $parser; + $this->defaultOptions = $defaultOptions; + $this->defaultRequirements = $defaultRequirements; parent::__construct($resolver); } @@ -43,7 +43,7 @@ class DelegatingLoader extends BaseDelegatingLoader /** * {@inheritdoc} */ - public function load($resource, $type = null) + public function load($resource, string $type = null): RouteCollection { if ($this->loading) { // This can happen if a fatal error occurs in parent::load(). @@ -62,7 +62,7 @@ class DelegatingLoader extends BaseDelegatingLoader // - this handles the case and prevents the second fatal error // by triggering an exception beforehand. - throw new FileLoaderLoadException($resource, null, null, null, $type); + throw new LoaderLoadException($resource, null, 0, null, $type); } $this->loading = true; @@ -73,14 +73,18 @@ class DelegatingLoader extends BaseDelegatingLoader } foreach ($collection->all() as $route) { - if (!\is_string($controller = $route->getDefault('_controller')) || !$controller) { + if ($this->defaultOptions) { + $route->setOptions($route->getOptions() + $this->defaultOptions); + } + if ($this->defaultRequirements) { + $route->setRequirements($route->getRequirements() + $this->defaultRequirements); + } + if (!\is_string($controller = $route->getDefault('_controller'))) { continue; } - try { - $controller = $this->parser->parse($controller); - } catch (\InvalidArgumentException $e) { - // unable to optimize unknown notation + if (str_contains($controller, '::')) { + continue; } $route->setDefault('_controller', $controller); diff --git a/lib/symfony/framework-bundle/Routing/RedirectableCompiledUrlMatcher.php b/lib/symfony/framework-bundle/Routing/RedirectableCompiledUrlMatcher.php new file mode 100644 index 0000000000..dba9d6d961 --- /dev/null +++ b/lib/symfony/framework-bundle/Routing/RedirectableCompiledUrlMatcher.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 Symfony\Bundle\FrameworkBundle\Routing; + +use Symfony\Component\Routing\Matcher\CompiledUrlMatcher; +use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface; + +/** + * @author Fabien Potencier + * + * @internal + */ +class RedirectableCompiledUrlMatcher extends CompiledUrlMatcher implements RedirectableUrlMatcherInterface +{ + /** + * {@inheritdoc} + */ + public function redirect(string $path, string $route, string $scheme = null): array + { + return [ + '_controller' => 'Symfony\\Bundle\\FrameworkBundle\\Controller\\RedirectController::urlRedirectAction', + 'path' => $path, + 'permanent' => true, + 'scheme' => $scheme, + 'httpPort' => $this->context->getHttpPort(), + 'httpsPort' => $this->context->getHttpsPort(), + '_route' => $route, + ]; + } +} diff --git a/lib/symfony/framework-bundle/Routing/RedirectableUrlMatcher.php b/lib/symfony/framework-bundle/Routing/RedirectableUrlMatcher.php deleted file mode 100644 index fd1594c883..0000000000 --- a/lib/symfony/framework-bundle/Routing/RedirectableUrlMatcher.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Routing; - -use Symfony\Component\Routing\Matcher\RedirectableUrlMatcher as BaseMatcher; - -/** - * @author Fabien Potencier - */ -class RedirectableUrlMatcher extends BaseMatcher -{ - /** - * Redirects the user to another URL. - * - * @param string $path The path info to redirect to - * @param string $route The route that matched - * @param string $scheme The URL scheme (null to keep the current one) - * - * @return array An array of parameters - */ - public function redirect($path, $route, $scheme = null) - { - return [ - '_controller' => 'Symfony\\Bundle\\FrameworkBundle\\Controller\\RedirectController::urlRedirectAction', - 'path' => $path, - 'permanent' => true, - 'scheme' => $scheme, - 'httpPort' => $this->context->getHttpPort(), - 'httpsPort' => $this->context->getHttpsPort(), - '_route' => $route, - ]; - } -} diff --git a/lib/symfony/framework-bundle/Routing/RouteLoaderInterface.php b/lib/symfony/framework-bundle/Routing/RouteLoaderInterface.php new file mode 100644 index 0000000000..d1cb55a7af --- /dev/null +++ b/lib/symfony/framework-bundle/Routing/RouteLoaderInterface.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 Symfony\Bundle\FrameworkBundle\Routing; + +/** + * Marker interface for service route loaders. + */ +interface RouteLoaderInterface +{ +} diff --git a/lib/symfony/framework-bundle/Routing/Router.php b/lib/symfony/framework-bundle/Routing/Router.php index f4cf18ee33..8e36efe0a6 100644 --- a/lib/symfony/framework-bundle/Routing/Router.php +++ b/lib/symfony/framework-bundle/Routing/Router.php @@ -11,16 +11,20 @@ namespace Symfony\Bundle\FrameworkBundle\Routing; +use Psr\Container\ContainerInterface; +use Psr\Log\LoggerInterface; use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\Resource\FileExistenceResource; +use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\DependencyInjection\Config\ContainerParametersResource; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface; use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; -use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Router as BaseRouter; +use Symfony\Contracts\Service\ServiceSubscriberInterface; /** * This Router creates the Loader only when the cache is empty. @@ -31,20 +35,28 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberI { private $container; private $collectedParameters = []; + private $paramFetcher; /** - * @param ContainerInterface $container A ContainerInterface instance - * @param mixed $resource The main resource to load - * @param array $options An array of options - * @param RequestContext $context The context + * @param mixed $resource The main resource to load */ - public function __construct(ContainerInterface $container, $resource, array $options = [], RequestContext $context = null) + public function __construct(ContainerInterface $container, $resource, array $options = [], RequestContext $context = null, ContainerInterface $parameters = null, LoggerInterface $logger = null, string $defaultLocale = null) { $this->container = $container; - $this->resource = $resource; - $this->context = $context ?: new RequestContext(); + $this->context = $context ?? new RequestContext(); + $this->logger = $logger; $this->setOptions($options); + + if ($parameters) { + $this->paramFetcher = \Closure::fromCallable([$parameters, 'get']); + } elseif ($container instanceof SymfonyContainerInterface) { + $this->paramFetcher = \Closure::fromCallable([$container, 'getParameter']); + } else { + throw new \LogicException(sprintf('You should either pass a "%s" instance or provide the $parameters argument of the "%s" method.', SymfonyContainerInterface::class, __METHOD__)); + } + + $this->defaultLocale = $defaultLocale; } /** @@ -56,6 +68,16 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberI $this->collection = $this->container->get('routing.loader')->load($this->resource, $this->options['resource_type']); $this->resolveParameters($this->collection); $this->collection->addResource(new ContainerParametersResource($this->collectedParameters)); + + try { + $containerFile = ($this->paramFetcher)('kernel.cache_dir').'/'.($this->paramFetcher)('kernel.container_class').'.php'; + if (file_exists($containerFile)) { + $this->collection->addResource(new FileResource($containerFile)); + } else { + $this->collection->addResource(new FileExistenceResource($containerFile)); + } + } catch (ParameterNotFoundException $exception) { + } } return $this->collection; @@ -63,8 +85,10 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberI /** * {@inheritdoc} + * + * @return string[] A list of classes to preload on PHP 7.4+ */ - public function warmUp($cacheDir) + public function warmUp(string $cacheDir) { $currentDir = $this->getOption('cache_dir'); @@ -74,6 +98,11 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberI $this->getGenerator(); $this->setOption('cache_dir', $currentDir); + + return [ + $this->getOption('generator_class'), + $this->getOption('matcher_class'), + ]; } /** @@ -101,15 +130,15 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberI $schemes = []; foreach ($route->getSchemes() as $scheme) { - $schemes = array_merge($schemes, explode('|', $this->resolve($scheme))); + $schemes[] = explode('|', $this->resolve($scheme)); } - $route->setSchemes($schemes); + $route->setSchemes(array_merge([], ...$schemes)); $methods = []; foreach ($route->getMethods() as $method) { - $methods = array_merge($methods, explode('|', $this->resolve($method))); + $methods[] = explode('|', $this->resolve($method)); } - $route->setMethods($methods); + $route->setMethods(array_merge([], ...$methods)); $route->setCondition($this->resolve($route->getCondition())); } } @@ -139,9 +168,7 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberI return $value; } - $container = $this->container; - - $escapedValue = preg_replace_callback('/%%|%([^%\s]++)%/', function ($match) use ($container, $value) { + $escapedValue = preg_replace_callback('/%%|%([^%\s]++)%/', function ($match) use ($value) { // skip %% if (!isset($match[1])) { return '%%'; @@ -151,15 +178,21 @@ class Router extends BaseRouter implements WarmableInterface, ServiceSubscriberI throw new RuntimeException(sprintf('Using "%%%s%%" is not allowed in routing configuration.', $match[1])); } - $resolved = $container->getParameter($match[1]); + $resolved = ($this->paramFetcher)($match[1]); - if (\is_string($resolved) || is_numeric($resolved)) { + if (\is_scalar($resolved)) { $this->collectedParameters[$match[1]] = $resolved; - return (string) $this->resolve($resolved); + if (\is_string($resolved)) { + $resolved = $this->resolve($resolved); + } + + if (\is_scalar($resolved)) { + return false === $resolved ? '0' : (string) $resolved; + } } - throw new RuntimeException(sprintf('The container parameter "%s", used in the route configuration value "%s", must be a string or numeric, but it is of type "%s".', $match[1], $value, \gettype($resolved))); + throw new RuntimeException(sprintf('The container parameter "%s", used in the route configuration value "%s", must be a string or numeric, but it is of type "%s".', $match[1], $value, get_debug_type($resolved))); }, $value); return str_replace('%%', '%', $escapedValue); diff --git a/lib/symfony/framework-bundle/Secrets/AbstractVault.php b/lib/symfony/framework-bundle/Secrets/AbstractVault.php new file mode 100644 index 0000000000..eeecbbb68b --- /dev/null +++ b/lib/symfony/framework-bundle/Secrets/AbstractVault.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Secrets; + +/** + * @author Nicolas Grekas + * + * @internal + */ +abstract class AbstractVault +{ + protected $lastMessage; + + public function getLastMessage(): ?string + { + return $this->lastMessage; + } + + abstract public function generateKeys(bool $override = false): bool; + + abstract public function seal(string $name, string $value): void; + + abstract public function reveal(string $name): ?string; + + abstract public function remove(string $name): bool; + + abstract public function list(bool $reveal = false): array; + + protected function validateName(string $name): void + { + if (!preg_match('/^\w++$/D', $name)) { + throw new \LogicException(sprintf('Invalid secret name "%s": only "word" characters are allowed.', $name)); + } + } + + protected function getPrettyPath(string $path) + { + return str_replace(getcwd().\DIRECTORY_SEPARATOR, '', $path); + } +} diff --git a/lib/symfony/framework-bundle/Secrets/DotenvVault.php b/lib/symfony/framework-bundle/Secrets/DotenvVault.php new file mode 100644 index 0000000000..7c6f6987e2 --- /dev/null +++ b/lib/symfony/framework-bundle/Secrets/DotenvVault.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Secrets; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class DotenvVault extends AbstractVault +{ + private $dotenvFile; + + public function __construct(string $dotenvFile) + { + $this->dotenvFile = strtr($dotenvFile, '/', \DIRECTORY_SEPARATOR); + } + + public function generateKeys(bool $override = false): bool + { + $this->lastMessage = 'The dotenv vault doesn\'t encrypt secrets thus doesn\'t need keys.'; + + return false; + } + + public function seal(string $name, string $value): void + { + $this->lastMessage = null; + $this->validateName($name); + $v = str_replace("'", "'\\''", $value); + + $content = is_file($this->dotenvFile) ? file_get_contents($this->dotenvFile) : ''; + $content = preg_replace("/^$name=((\\\\'|'[^']++')++|.*)/m", "$name='$v'", $content, -1, $count); + + if (!$count) { + $content .= "$name='$v'\n"; + } + + file_put_contents($this->dotenvFile, $content); + + $this->lastMessage = sprintf('Secret "%s" %s in "%s".', $name, $count ? 'added' : 'updated', $this->getPrettyPath($this->dotenvFile)); + } + + public function reveal(string $name): ?string + { + $this->lastMessage = null; + $this->validateName($name); + $v = \is_string($_SERVER[$name] ?? null) && !str_starts_with($name, 'HTTP_') ? $_SERVER[$name] : ($_ENV[$name] ?? null); + + if (null === $v) { + $this->lastMessage = sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath($this->dotenvFile)); + + return null; + } + + return $v; + } + + public function remove(string $name): bool + { + $this->lastMessage = null; + $this->validateName($name); + + $content = is_file($this->dotenvFile) ? file_get_contents($this->dotenvFile) : ''; + $content = preg_replace("/^$name=((\\\\'|'[^']++')++|.*)\n?/m", '', $content, -1, $count); + + if ($count) { + file_put_contents($this->dotenvFile, $content); + $this->lastMessage = sprintf('Secret "%s" removed from file "%s".', $name, $this->getPrettyPath($this->dotenvFile)); + + return true; + } + + $this->lastMessage = sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath($this->dotenvFile)); + + return false; + } + + public function list(bool $reveal = false): array + { + $this->lastMessage = null; + $secrets = []; + + foreach ($_ENV as $k => $v) { + if (preg_match('/^\w+$/D', $k)) { + $secrets[$k] = $reveal ? $v : null; + } + } + + foreach ($_SERVER as $k => $v) { + if (\is_string($v) && preg_match('/^\w+$/D', $k)) { + $secrets[$k] = $reveal ? $v : null; + } + } + + return $secrets; + } +} diff --git a/lib/symfony/framework-bundle/Secrets/SodiumVault.php b/lib/symfony/framework-bundle/Secrets/SodiumVault.php new file mode 100644 index 0000000000..a9c86ab047 --- /dev/null +++ b/lib/symfony/framework-bundle/Secrets/SodiumVault.php @@ -0,0 +1,233 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Secrets; + +use Symfony\Component\DependencyInjection\EnvVarLoaderInterface; +use Symfony\Component\VarExporter\VarExporter; + +/** + * @author Tobias Schultze + * @author Jérémy Derussé + * @author Nicolas Grekas + * + * @internal + */ +class SodiumVault extends AbstractVault implements EnvVarLoaderInterface +{ + private $encryptionKey; + private $decryptionKey; + private $pathPrefix; + private $secretsDir; + + /** + * @param string|\Stringable|null $decryptionKey A string or a stringable object that defines the private key to use to decrypt the vault + * or null to store generated keys in the provided $secretsDir + */ + public function __construct(string $secretsDir, $decryptionKey = null) + { + if (null !== $decryptionKey && !\is_string($decryptionKey) && !(\is_object($decryptionKey) && method_exists($decryptionKey, '__toString'))) { + throw new \TypeError(sprintf('Decryption key should be a string or an object that implements the __toString() method, "%s" given.', get_debug_type($decryptionKey))); + } + + $this->pathPrefix = rtrim(strtr($secretsDir, '/', \DIRECTORY_SEPARATOR), \DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR.basename($secretsDir).'.'; + $this->decryptionKey = $decryptionKey; + $this->secretsDir = $secretsDir; + } + + public function generateKeys(bool $override = false): bool + { + $this->lastMessage = null; + + if (null === $this->encryptionKey && '' !== $this->decryptionKey = (string) $this->decryptionKey) { + $this->lastMessage = 'Cannot generate keys when a decryption key has been provided while instantiating the vault.'; + + return false; + } + + try { + $this->loadKeys(); + } catch (\RuntimeException $e) { + // ignore failures to load keys + } + + if ('' !== $this->decryptionKey && !is_file($this->pathPrefix.'encrypt.public.php')) { + $this->export('encrypt.public', $this->encryptionKey); + } + + if (!$override && null !== $this->encryptionKey) { + $this->lastMessage = sprintf('Sodium keys already exist at "%s*.{public,private}" and won\'t be overridden.', $this->getPrettyPath($this->pathPrefix)); + + return false; + } + + $this->decryptionKey = sodium_crypto_box_keypair(); + $this->encryptionKey = sodium_crypto_box_publickey($this->decryptionKey); + + $this->export('encrypt.public', $this->encryptionKey); + $this->export('decrypt.private', $this->decryptionKey); + + $this->lastMessage = sprintf('Sodium keys have been generated at "%s*.public/private.php".', $this->getPrettyPath($this->pathPrefix)); + + return true; + } + + public function seal(string $name, string $value): void + { + $this->lastMessage = null; + $this->validateName($name); + $this->loadKeys(); + $filename = $this->getFilename($name); + $this->export($filename, sodium_crypto_box_seal($value, $this->encryptionKey ?? sodium_crypto_box_publickey($this->decryptionKey))); + + $list = $this->list(); + $list[$name] = null; + uksort($list, 'strnatcmp'); + file_put_contents($this->pathPrefix.'list.php', sprintf("lastMessage = sprintf('Secret "%s" encrypted in "%s"; you can commit it.', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR)); + } + + public function reveal(string $name): ?string + { + $this->lastMessage = null; + $this->validateName($name); + + $filename = $this->getFilename($name); + if (!is_file($file = $this->pathPrefix.$filename.'.php')) { + $this->lastMessage = sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR)); + + return null; + } + + if (!\function_exists('sodium_crypto_box_seal')) { + $this->lastMessage = sprintf('Secret "%s" cannot be revealed as the "sodium" PHP extension missing. Try running "composer require paragonie/sodium_compat" if you cannot enable the extension."', $name); + + return null; + } + + $this->loadKeys(); + + if ('' === $this->decryptionKey) { + $this->lastMessage = sprintf('Secret "%s" cannot be revealed as no decryption key was found in "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR)); + + return null; + } + + if (false === $value = sodium_crypto_box_seal_open(include $file, $this->decryptionKey)) { + $this->lastMessage = sprintf('Secret "%s" cannot be revealed as the wrong decryption key was provided for "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR)); + + return null; + } + + return $value; + } + + public function remove(string $name): bool + { + $this->lastMessage = null; + $this->validateName($name); + + $filename = $this->getFilename($name); + if (!is_file($file = $this->pathPrefix.$filename.'.php')) { + $this->lastMessage = sprintf('Secret "%s" not found in "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR)); + + return false; + } + + $list = $this->list(); + unset($list[$name]); + file_put_contents($this->pathPrefix.'list.php', sprintf("lastMessage = sprintf('Secret "%s" removed from "%s".', $name, $this->getPrettyPath(\dirname($this->pathPrefix).\DIRECTORY_SEPARATOR)); + + return @unlink($file) || !file_exists($file); + } + + public function list(bool $reveal = false): array + { + $this->lastMessage = null; + + if (!is_file($file = $this->pathPrefix.'list.php')) { + return []; + } + + $secrets = include $file; + + if (!$reveal) { + return $secrets; + } + + foreach ($secrets as $name => $value) { + $secrets[$name] = $this->reveal($name); + } + + return $secrets; + } + + public function loadEnvVars(): array + { + return $this->list(true); + } + + private function loadKeys(): void + { + if (!\function_exists('sodium_crypto_box_seal')) { + throw new \LogicException('The "sodium" PHP extension is required to deal with secrets. Alternatively, try running "composer require paragonie/sodium_compat" if you cannot enable the extension.".'); + } + + if (null !== $this->encryptionKey || '' !== $this->decryptionKey = (string) $this->decryptionKey) { + return; + } + + if (is_file($this->pathPrefix.'decrypt.private.php')) { + $this->decryptionKey = (string) include $this->pathPrefix.'decrypt.private.php'; + } + + if (is_file($this->pathPrefix.'encrypt.public.php')) { + $this->encryptionKey = (string) include $this->pathPrefix.'encrypt.public.php'; + } elseif ('' !== $this->decryptionKey) { + $this->encryptionKey = sodium_crypto_box_publickey($this->decryptionKey); + } else { + throw new \RuntimeException(sprintf('Encryption key not found in "%s".', \dirname($this->pathPrefix))); + } + } + + private function export(string $filename, string $data): void + { + $b64 = 'decrypt.private' === $filename ? '// SYMFONY_DECRYPTION_SECRET='.base64_encode($data)."\n" : ''; + $name = basename($this->pathPrefix.$filename); + $data = str_replace('%', '\x', rawurlencode($data)); + $data = sprintf("createSecretsDir(); + + if (false === file_put_contents($this->pathPrefix.$filename.'.php', $data, \LOCK_EX)) { + $e = error_get_last(); + throw new \ErrorException($e['message'] ?? 'Failed to write secrets data.', 0, $e['type'] ?? \E_USER_WARNING); + } + } + + private function createSecretsDir(): void + { + if ($this->secretsDir && !is_dir($this->secretsDir) && !@mkdir($this->secretsDir, 0777, true) && !is_dir($this->secretsDir)) { + throw new \RuntimeException(sprintf('Unable to create the secrets directory (%s).', $this->secretsDir)); + } + + $this->secretsDir = null; + } + + private function getFilename(string $name): string + { + // The MD5 hash allows making secrets case-sensitive. The filename is not enough on Windows. + return $name.'.'.substr(md5($name), 0, 6); + } +} diff --git a/lib/symfony/framework-bundle/Session/DeprecatedSessionFactory.php b/lib/symfony/framework-bundle/Session/DeprecatedSessionFactory.php new file mode 100644 index 0000000000..faa29a1c7e --- /dev/null +++ b/lib/symfony/framework-bundle/Session/DeprecatedSessionFactory.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Session; + +use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * Provides session and trigger deprecation. + * + * Used by service that should trigger deprecation when accessed by the user. + * + * @author Jérémy Derussé + * + * @internal to be removed in 6.0 + */ +class DeprecatedSessionFactory +{ + private $requestStack; + + public function __construct(RequestStack $requestStack) + { + $this->requestStack = $requestStack; + } + + public function getSession(): ?SessionInterface + { + trigger_deprecation('symfony/framework-bundle', '5.3', 'The "session" service and "SessionInterface" alias are deprecated, use "$requestStack->getSession()" instead.'); + + try { + return $this->requestStack->getSession(); + } catch (SessionNotFoundException $e) { + return null; + } + } +} diff --git a/lib/symfony/framework-bundle/Session/ServiceSessionFactory.php b/lib/symfony/framework-bundle/Session/ServiceSessionFactory.php new file mode 100644 index 0000000000..c057375016 --- /dev/null +++ b/lib/symfony/framework-bundle/Session/ServiceSessionFactory.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 Symfony\Bundle\FrameworkBundle\Session; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageFactoryInterface; +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; + +/** + * @author Jérémy Derussé + * + * @internal to be removed in Symfony 6 + */ +final class ServiceSessionFactory implements SessionStorageFactoryInterface +{ + private $storage; + + public function __construct(SessionStorageInterface $storage) + { + $this->storage = $storage; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + if ($this->storage instanceof NativeSessionStorage && $request && $request->isSecure()) { + $this->storage->setOptions(['cookie_secure' => true]); + } + + return $this->storage; + } +} diff --git a/lib/symfony/framework-bundle/Templating/DelegatingEngine.php b/lib/symfony/framework-bundle/Templating/DelegatingEngine.php deleted file mode 100644 index f40760fc91..0000000000 --- a/lib/symfony/framework-bundle/Templating/DelegatingEngine.php +++ /dev/null @@ -1,74 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Templating; - -use Psr\Container\ContainerInterface; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Templating\DelegatingEngine as BaseDelegatingEngine; - -/** - * DelegatingEngine selects an engine for a given template. - * - * @author Fabien Potencier - */ -class DelegatingEngine extends BaseDelegatingEngine implements EngineInterface -{ - protected $container; - - public function __construct(ContainerInterface $container, array $engineIds) - { - $this->container = $container; - $this->engines = $engineIds; - } - - /** - * {@inheritdoc} - */ - public function getEngine($name) - { - $this->resolveEngines(); - - return parent::getEngine($name); - } - - /** - * {@inheritdoc} - */ - public function renderResponse($view, array $parameters = [], Response $response = null) - { - $engine = $this->getEngine($view); - - if ($engine instanceof EngineInterface) { - return $engine->renderResponse($view, $parameters, $response); - } - - if (null === $response) { - $response = new Response(); - } - - $response->setContent($engine->render($view, $parameters)); - - return $response; - } - - /** - * Resolved engine ids to their real engine instances from the container. - */ - private function resolveEngines() - { - foreach ($this->engines as $i => $engine) { - if (\is_string($engine)) { - $this->engines[$i] = $this->container->get($engine); - } - } - } -} diff --git a/lib/symfony/framework-bundle/Templating/EngineInterface.php b/lib/symfony/framework-bundle/Templating/EngineInterface.php deleted file mode 100644 index 0b3a8b3200..0000000000 --- a/lib/symfony/framework-bundle/Templating/EngineInterface.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Templating; - -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Templating\EngineInterface as BaseEngineInterface; - -/** - * EngineInterface is the interface each engine must implement. - * - * @author Fabien Potencier - */ -interface EngineInterface extends BaseEngineInterface -{ - /** - * Renders a view and returns a Response. - * - * @param string $view The view name - * @param array $parameters An array of parameters to pass to the view - * @param Response $response A Response instance - * - * @return Response A Response instance - * - * @throws \RuntimeException if the template cannot be rendered - */ - public function renderResponse($view, array $parameters = [], Response $response = null); -} diff --git a/lib/symfony/framework-bundle/Templating/GlobalVariables.php b/lib/symfony/framework-bundle/Templating/GlobalVariables.php deleted file mode 100644 index ad5aee77fe..0000000000 --- a/lib/symfony/framework-bundle/Templating/GlobalVariables.php +++ /dev/null @@ -1,87 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Templating; - -use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Session\Session; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; - -/** - * GlobalVariables is the entry point for Symfony global variables in PHP templates. - * - * @author Fabien Potencier - */ -class GlobalVariables -{ - protected $container; - - public function __construct(ContainerInterface $container) - { - $this->container = $container; - } - - /** - * @return TokenInterface|null - */ - public function getToken() - { - if (!$this->container->has('security.token_storage')) { - return null; - } - - return $this->container->get('security.token_storage')->getToken(); - } - - public function getUser() - { - if (!$token = $this->getToken()) { - return null; - } - - $user = $token->getUser(); - - return \is_object($user) ? $user : null; - } - - /** - * @return Request|null The HTTP request object - */ - public function getRequest() - { - return $this->container->has('request_stack') ? $this->container->get('request_stack')->getCurrentRequest() : null; - } - - /** - * @return Session|null The session - */ - public function getSession() - { - return ($request = $this->getRequest()) ? $request->getSession() : null; - } - - /** - * @return string The current environment string (e.g 'dev') - */ - public function getEnvironment() - { - return $this->container->getParameter('kernel.environment'); - } - - /** - * @return bool The current debug mode - */ - public function getDebug() - { - return (bool) $this->container->getParameter('kernel.debug'); - } -} diff --git a/lib/symfony/framework-bundle/Templating/Helper/ActionsHelper.php b/lib/symfony/framework-bundle/Templating/Helper/ActionsHelper.php deleted file mode 100644 index 70e5a314bb..0000000000 --- a/lib/symfony/framework-bundle/Templating/Helper/ActionsHelper.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Templating\Helper; - -use Symfony\Component\HttpKernel\Controller\ControllerReference; -use Symfony\Component\HttpKernel\Fragment\FragmentHandler; -use Symfony\Component\Templating\Helper\Helper; - -/** - * ActionsHelper manages action inclusions. - * - * @author Fabien Potencier - */ -class ActionsHelper extends Helper -{ - private $handler; - - public function __construct(FragmentHandler $handler) - { - $this->handler = $handler; - } - - /** - * Returns the fragment content for a given URI. - * - * @param string $uri A URI - * @param array $options An array of options - * - * @return string The fragment content - * - * @see FragmentHandler::render() - */ - public function render($uri, array $options = []) - { - $strategy = isset($options['strategy']) ? $options['strategy'] : 'inline'; - unset($options['strategy']); - - return $this->handler->render($uri, $strategy, $options); - } - - public function controller($controller, $attributes = [], $query = []) - { - return new ControllerReference($controller, $attributes, $query); - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'actions'; - } -} diff --git a/lib/symfony/framework-bundle/Templating/Helper/AssetsHelper.php b/lib/symfony/framework-bundle/Templating/Helper/AssetsHelper.php deleted file mode 100644 index 072b6e7fc9..0000000000 --- a/lib/symfony/framework-bundle/Templating/Helper/AssetsHelper.php +++ /dev/null @@ -1,67 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Templating\Helper; - -use Symfony\Component\Asset\Packages; -use Symfony\Component\Templating\Helper\Helper; - -/** - * AssetsHelper helps manage asset URLs. - * - * @author Fabien Potencier - */ -class AssetsHelper extends Helper -{ - private $packages; - - public function __construct(Packages $packages) - { - $this->packages = $packages; - } - - /** - * Returns the public url/path of an asset. - * - * If the package used to generate the path is an instance of - * UrlPackage, you will always get a URL and not a path. - * - * @param string $path A public path - * @param string $packageName The name of the asset package to use - * - * @return string The public path of the asset - */ - public function getUrl($path, $packageName = null) - { - return $this->packages->getUrl($path, $packageName); - } - - /** - * Returns the version of an asset. - * - * @param string $path A public path - * @param string $packageName The name of the asset package to use - * - * @return string The asset version - */ - public function getVersion($path, $packageName = null) - { - return $this->packages->getVersion($path, $packageName); - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'assets'; - } -} diff --git a/lib/symfony/framework-bundle/Templating/Helper/CodeHelper.php b/lib/symfony/framework-bundle/Templating/Helper/CodeHelper.php deleted file mode 100644 index 01f5595e9c..0000000000 --- a/lib/symfony/framework-bundle/Templating/Helper/CodeHelper.php +++ /dev/null @@ -1,227 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Templating\Helper; - -use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; -use Symfony\Component\Templating\Helper\Helper; - -/** - * @author Fabien Potencier - */ -class CodeHelper extends Helper -{ - protected $fileLinkFormat; - protected $rootDir; - protected $charset; - - /** - * @param string|FileLinkFormatter $fileLinkFormat The format for links to source files - * @param string $rootDir The project root directory - * @param string $charset The charset - */ - public function __construct($fileLinkFormat, $rootDir, $charset) - { - $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); - $this->rootDir = str_replace('\\', '/', $rootDir).'/'; - $this->charset = $charset; - } - - /** - * Formats an array as a string. - * - * @param array $args The argument array - * - * @return string - */ - public function formatArgsAsText(array $args) - { - return strip_tags($this->formatArgs($args)); - } - - public function abbrClass($class) - { - $parts = explode('\\', $class); - $short = array_pop($parts); - - return sprintf('%s', $class, $short); - } - - public function abbrMethod($method) - { - if (false !== strpos($method, '::')) { - list($class, $method) = explode('::', $method, 2); - $result = sprintf('%s::%s()', $this->abbrClass($class), $method); - } elseif ('Closure' === $method) { - $result = sprintf('%1$s', $method); - } else { - $result = sprintf('%1$s()', $method); - } - - return $result; - } - - /** - * Formats an array as a string. - * - * @param array $args The argument array - * - * @return string - */ - public function formatArgs(array $args) - { - $result = []; - foreach ($args as $key => $item) { - if ('object' === $item[0]) { - $parts = explode('\\', $item[1]); - $short = array_pop($parts); - $formattedValue = sprintf('object(%s)', $item[1], $short); - } elseif ('array' === $item[0]) { - $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); - } elseif ('string' === $item[0]) { - $formattedValue = sprintf("'%s'", htmlspecialchars($item[1], \ENT_QUOTES, $this->getCharset())); - } elseif ('null' === $item[0]) { - $formattedValue = 'null'; - } elseif ('boolean' === $item[0]) { - $formattedValue = ''.strtolower(var_export($item[1], true)).''; - } elseif ('resource' === $item[0]) { - $formattedValue = 'resource'; - } else { - $formattedValue = str_replace("\n", '', var_export(htmlspecialchars((string) $item[1], \ENT_QUOTES, $this->getCharset()), true)); - } - - $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue); - } - - return implode(', ', $result); - } - - /** - * Returns an excerpt of a code file around the given line number. - * - * @param string $file A file path - * @param int $line The selected line number - * - * @return string|null An HTML string - */ - public function fileExcerpt($file, $line) - { - if (is_readable($file)) { - if (\extension_loaded('fileinfo')) { - $finfo = new \finfo(); - - // Check if the file is an application/octet-stream (eg. Phar file) because highlight_file cannot parse these files - if ('application/octet-stream' === $finfo->file($file, \FILEINFO_MIME_TYPE)) { - return ''; - } - } - - // highlight_file could throw warnings - // see https://bugs.php.net/25725 - $code = @highlight_file($file, true); - // remove main code/span tags - $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); - $content = explode('
                  ', $code); - - $lines = []; - for ($i = max($line - 3, 1), $max = min($line + 3, \count($content)); $i <= $max; ++$i) { - $lines[] = ''.self::fixCodeMarkup($content[$i - 1]).'
                • '; - } - - return '
                    '.implode("\n", $lines).'
                  '; - } - - return null; - } - - /** - * Formats a file path. - * - * @param string $file An absolute file path - * @param int $line The line number - * @param string $text Use this text for the link rather than the file path - * - * @return string - */ - public function formatFile($file, $line, $text = null) - { - $flags = \ENT_QUOTES | \ENT_SUBSTITUTE; - - if (null === $text) { - $file = trim($file); - $fileStr = $file; - if (0 === strpos($fileStr, $this->rootDir)) { - $fileStr = str_replace(['\\', $this->rootDir], ['/', ''], $fileStr); - $fileStr = htmlspecialchars($fileStr, $flags, $this->charset); - $fileStr = sprintf('kernel.root_dir/%s', htmlspecialchars($this->rootDir, $flags, $this->charset), $fileStr); - } - - $text = sprintf('%s at line %d', $fileStr, $line); - } - - if (false !== $link = $this->getFileLink($file, $line)) { - return sprintf('%s', htmlspecialchars($link, $flags, $this->charset), $text); - } - - return $text; - } - - /** - * Returns the link for a given file/line pair. - * - * @param string $file An absolute file path - * @param int $line The line number - * - * @return string A link of false - */ - public function getFileLink($file, $line) - { - if ($fmt = $this->fileLinkFormat) { - return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line); - } - - return false; - } - - public function formatFileFromText($text) - { - return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) { - return 'in '.$this->formatFile($match[2], $match[3]); - }, $text); - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'code'; - } - - protected static function fixCodeMarkup($line) - { - // ending tag from previous line - $opening = strpos($line, ''); - if (false !== $closing && (false === $opening || $closing < $opening)) { - $line = substr_replace($line, '', $closing, 7); - } - - // missing tag at the end of line - $opening = strpos($line, ''); - if (false !== $opening && (false === $closing || $closing > $opening)) { - $line .= ''; - } - - return $line; - } -} diff --git a/lib/symfony/framework-bundle/Templating/Helper/FormHelper.php b/lib/symfony/framework-bundle/Templating/Helper/FormHelper.php deleted file mode 100644 index 3343680b8b..0000000000 --- a/lib/symfony/framework-bundle/Templating/Helper/FormHelper.php +++ /dev/null @@ -1,255 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Templating\Helper; - -use Symfony\Component\Form\FormRendererInterface; -use Symfony\Component\Form\FormView; -use Symfony\Component\Templating\Helper\Helper; - -/** - * FormHelper provides helpers to help display forms. - * - * @author Fabien Potencier - * @author Bernhard Schussek - */ -class FormHelper extends Helper -{ - private $renderer; - - public function __construct(FormRendererInterface $renderer) - { - $this->renderer = $renderer; - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'form'; - } - - /** - * Sets a theme for a given view. - * - * The theme format is ":". - * - * @param FormView $view A FormView instance - * @param string|array $themes A theme or an array of theme - * @param bool $useDefaultThemes If true, will use default themes defined in the renderer - */ - public function setTheme(FormView $view, $themes, $useDefaultThemes = true) - { - $this->renderer->setTheme($view, $themes, $useDefaultThemes); - } - - /** - * Renders the HTML for a form. - * - * Example usage: - * - * form($form) ?> - * - * You can pass options during the call: - * - * form($form, ['attr' => ['class' => 'foo']]) ?> - * - * form($form, ['separator' => '+++++']) ?> - * - * This method is mainly intended for prototyping purposes. If you want to - * control the layout of a form in a more fine-grained manner, you are - * advised to use the other helper methods for rendering the parts of the - * form individually. You can also create a custom form theme to adapt - * the look of the form. - * - * @param FormView $view The view for which to render the form - * @param array $variables Additional variables passed to the template - * - * @return string The HTML markup - */ - public function form(FormView $view, array $variables = []) - { - return $this->renderer->renderBlock($view, 'form', $variables); - } - - /** - * Renders the form start tag. - * - * Example usage templates: - * - * start($form) ?>> - * - * @param FormView $view The view for which to render the start tag - * @param array $variables Additional variables passed to the template - * - * @return string The HTML markup - */ - public function start(FormView $view, array $variables = []) - { - return $this->renderer->renderBlock($view, 'form_start', $variables); - } - - /** - * Renders the form end tag. - * - * Example usage templates: - * - * end($form) ?>> - * - * @param FormView $view The view for which to render the end tag - * @param array $variables Additional variables passed to the template - * - * @return string The HTML markup - */ - public function end(FormView $view, array $variables = []) - { - return $this->renderer->renderBlock($view, 'form_end', $variables); - } - - /** - * Renders the HTML for a given view. - * - * Example usage: - * - * widget($form) ?> - * - * You can pass options during the call: - * - * widget($form, ['attr' => ['class' => 'foo']]) ?> - * - * widget($form, ['separator' => '+++++']) ?> - * - * @param FormView $view The view for which to render the widget - * @param array $variables Additional variables passed to the template - * - * @return string The HTML markup - */ - public function widget(FormView $view, array $variables = []) - { - return $this->renderer->searchAndRenderBlock($view, 'widget', $variables); - } - - /** - * Renders the entire form field "row". - * - * @param FormView $view The view for which to render the row - * @param array $variables Additional variables passed to the template - * - * @return string The HTML markup - */ - public function row(FormView $view, array $variables = []) - { - return $this->renderer->searchAndRenderBlock($view, 'row', $variables); - } - - /** - * Renders the label of the given view. - * - * @param FormView $view The view for which to render the label - * @param string $label The label - * @param array $variables Additional variables passed to the template - * - * @return string The HTML markup - */ - public function label(FormView $view, $label = null, array $variables = []) - { - if (null !== $label) { - $variables += ['label' => $label]; - } - - return $this->renderer->searchAndRenderBlock($view, 'label', $variables); - } - - /** - * Renders the errors of the given view. - * - * @return string The HTML markup - */ - public function errors(FormView $view) - { - return $this->renderer->searchAndRenderBlock($view, 'errors'); - } - - /** - * Renders views which have not already been rendered. - * - * @param FormView $view The parent view - * @param array $variables An array of variables - * - * @return string The HTML markup - */ - public function rest(FormView $view, array $variables = []) - { - return $this->renderer->searchAndRenderBlock($view, 'rest', $variables); - } - - /** - * Renders a block of the template. - * - * @param FormView $view The view for determining the used themes - * @param string $blockName The name of the block to render - * @param array $variables The variable to pass to the template - * - * @return string The HTML markup - */ - public function block(FormView $view, $blockName, array $variables = []) - { - return $this->renderer->renderBlock($view, $blockName, $variables); - } - - /** - * Returns a CSRF token. - * - * Use this helper for CSRF protection without the overhead of creating a - * form. - * - * echo $view['form']->csrfToken('rm_user_'.$user->getId()); - * - * Check the token in your action using the same CSRF token id. - * - * // $csrfProvider being an instance of Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface - * if (!$csrfProvider->isCsrfTokenValid('rm_user_'.$user->getId(), $token)) { - * throw new \RuntimeException('CSRF attack detected.'); - * } - * - * @param string $tokenId The CSRF token id of the protected action - * - * @return string A CSRF token - * - * @throws \BadMethodCallException when no CSRF provider was injected in the constructor - */ - public function csrfToken($tokenId) - { - return $this->renderer->renderCsrfToken($tokenId); - } - - public function humanize($text) - { - return $this->renderer->humanize($text); - } - - /** - * @internal - */ - public function formEncodeCurrency($text, $widget = '') - { - if ('UTF-8' === $charset = $this->getCharset()) { - $text = htmlspecialchars($text, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8'); - } else { - $text = htmlentities($text, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8'); - $text = iconv('UTF-8', $charset, $text); - $widget = iconv('UTF-8', $charset, $widget); - } - - return str_replace('{{ widget }}', $widget, $text); - } -} diff --git a/lib/symfony/framework-bundle/Templating/Helper/RequestHelper.php b/lib/symfony/framework-bundle/Templating/Helper/RequestHelper.php deleted file mode 100644 index 3beaaada2a..0000000000 --- a/lib/symfony/framework-bundle/Templating/Helper/RequestHelper.php +++ /dev/null @@ -1,72 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Templating\Helper; - -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Templating\Helper\Helper; - -/** - * RequestHelper provides access to the current request parameters. - * - * @author Fabien Potencier - */ -class RequestHelper extends Helper -{ - protected $requestStack; - - public function __construct(RequestStack $requestStack) - { - $this->requestStack = $requestStack; - } - - /** - * Returns a parameter from the current request object. - * - * @param string $key The name of the parameter - * @param string $default A default value - * - * @return mixed - * - * @see Request::get() - */ - public function getParameter($key, $default = null) - { - return $this->getRequest()->get($key, $default); - } - - /** - * Returns the locale. - * - * @return string - */ - public function getLocale() - { - return $this->getRequest()->getLocale(); - } - - private function getRequest() - { - if (!$this->requestStack->getCurrentRequest()) { - throw new \LogicException('A Request must be available.'); - } - - return $this->requestStack->getCurrentRequest(); - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'request'; - } -} diff --git a/lib/symfony/framework-bundle/Templating/Helper/RouterHelper.php b/lib/symfony/framework-bundle/Templating/Helper/RouterHelper.php deleted file mode 100644 index f242479086..0000000000 --- a/lib/symfony/framework-bundle/Templating/Helper/RouterHelper.php +++ /dev/null @@ -1,70 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Templating\Helper; - -use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Component\Templating\Helper\Helper; - -/** - * RouterHelper manages links between pages in a template context. - * - * @author Fabien Potencier - */ -class RouterHelper extends Helper -{ - protected $generator; - - public function __construct(UrlGeneratorInterface $router) - { - $this->generator = $router; - } - - /** - * Generates a URL reference (as an absolute or relative path) to the route with the given parameters. - * - * @param string $name The name of the route - * @param mixed $parameters An array of parameters - * @param bool $relative Whether to generate a relative or absolute path - * - * @return string The generated URL reference - * - * @see UrlGeneratorInterface - */ - public function path($name, $parameters = [], $relative = false) - { - return $this->generator->generate($name, $parameters, $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH); - } - - /** - * Generates a URL reference (as an absolute URL or network path) to the route with the given parameters. - * - * @param string $name The name of the route - * @param mixed $parameters An array of parameters - * @param bool $schemeRelative Whether to omit the scheme in the generated URL reference - * - * @return string The generated URL reference - * - * @see UrlGeneratorInterface - */ - public function url($name, $parameters = [], $schemeRelative = false) - { - return $this->generator->generate($name, $parameters, $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL); - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'router'; - } -} diff --git a/lib/symfony/framework-bundle/Templating/Helper/SessionHelper.php b/lib/symfony/framework-bundle/Templating/Helper/SessionHelper.php deleted file mode 100644 index 072c1d3d21..0000000000 --- a/lib/symfony/framework-bundle/Templating/Helper/SessionHelper.php +++ /dev/null @@ -1,80 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Templating\Helper; - -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Templating\Helper\Helper; - -/** - * SessionHelper provides read-only access to the session attributes. - * - * @author Fabien Potencier - */ -class SessionHelper extends Helper -{ - protected $session; - protected $requestStack; - - public function __construct(RequestStack $requestStack) - { - $this->requestStack = $requestStack; - } - - /** - * Returns an attribute. - * - * @param string $name The attribute name - * @param mixed $default The default value - * - * @return mixed - */ - public function get($name, $default = null) - { - return $this->getSession()->get($name, $default); - } - - public function getFlash($name, array $default = []) - { - return $this->getSession()->getFlashBag()->get($name, $default); - } - - public function getFlashes() - { - return $this->getSession()->getFlashBag()->all(); - } - - public function hasFlash($name) - { - return $this->getSession()->getFlashBag()->has($name); - } - - private function getSession() - { - if (null === $this->session) { - if (!$this->requestStack->getMasterRequest()) { - throw new \LogicException('A Request must be available.'); - } - - $this->session = $this->requestStack->getMasterRequest()->getSession(); - } - - return $this->session; - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'session'; - } -} diff --git a/lib/symfony/framework-bundle/Templating/Helper/StopwatchHelper.php b/lib/symfony/framework-bundle/Templating/Helper/StopwatchHelper.php deleted file mode 100644 index 8f2a21a681..0000000000 --- a/lib/symfony/framework-bundle/Templating/Helper/StopwatchHelper.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Templating\Helper; - -use Symfony\Component\Stopwatch\Stopwatch; -use Symfony\Component\Templating\Helper\Helper; - -/** - * StopwatchHelper provides methods time your PHP templates. - * - * @author Wouter J - */ -class StopwatchHelper extends Helper -{ - private $stopwatch; - - public function __construct(Stopwatch $stopwatch = null) - { - $this->stopwatch = $stopwatch; - } - - public function getName() - { - return 'stopwatch'; - } - - public function __call($method, $arguments = []) - { - if (null === $this->stopwatch) { - return null; - } - - if (method_exists($this->stopwatch, $method)) { - return \call_user_func_array([$this->stopwatch, $method], $arguments); - } - - throw new \BadMethodCallException(sprintf('Method "%s" of Stopwatch does not exist.', $method)); - } -} diff --git a/lib/symfony/framework-bundle/Templating/Helper/TranslatorHelper.php b/lib/symfony/framework-bundle/Templating/Helper/TranslatorHelper.php deleted file mode 100644 index 4846cb1101..0000000000 --- a/lib/symfony/framework-bundle/Templating/Helper/TranslatorHelper.php +++ /dev/null @@ -1,52 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Templating\Helper; - -use Symfony\Component\Templating\Helper\Helper; -use Symfony\Component\Translation\TranslatorInterface; - -/** - * @author Fabien Potencier - */ -class TranslatorHelper extends Helper -{ - protected $translator; - - public function __construct(TranslatorInterface $translator) - { - $this->translator = $translator; - } - - /** - * @see TranslatorInterface::trans() - */ - public function trans($id, array $parameters = [], $domain = 'messages', $locale = null) - { - return $this->translator->trans($id, $parameters, $domain, $locale); - } - - /** - * @see TranslatorInterface::transChoice() - */ - public function transChoice($id, $number, array $parameters = [], $domain = 'messages', $locale = null) - { - return $this->translator->transChoice($id, $number, $parameters, $domain, $locale); - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'translator'; - } -} diff --git a/lib/symfony/framework-bundle/Templating/Loader/FilesystemLoader.php b/lib/symfony/framework-bundle/Templating/Loader/FilesystemLoader.php deleted file mode 100644 index 4edceed8ab..0000000000 --- a/lib/symfony/framework-bundle/Templating/Loader/FilesystemLoader.php +++ /dev/null @@ -1,62 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Templating\Loader; - -use Symfony\Component\Config\FileLocatorInterface; -use Symfony\Component\Templating\Loader\LoaderInterface; -use Symfony\Component\Templating\Storage\FileStorage; -use Symfony\Component\Templating\TemplateReferenceInterface; - -/** - * FilesystemLoader is a loader that read templates from the filesystem. - * - * @author Fabien Potencier - */ -class FilesystemLoader implements LoaderInterface -{ - protected $locator; - - public function __construct(FileLocatorInterface $locator) - { - $this->locator = $locator; - } - - /** - * {@inheritdoc} - */ - public function load(TemplateReferenceInterface $template) - { - try { - $file = $this->locator->locate($template); - } catch (\InvalidArgumentException $e) { - return false; - } - - return new FileStorage($file); - } - - /** - * {@inheritdoc} - */ - public function isFresh(TemplateReferenceInterface $template, $time) - { - if (false === $storage = $this->load($template)) { - return false; - } - - if (!is_readable((string) $storage)) { - return false; - } - - return filemtime((string) $storage) < $time; - } -} diff --git a/lib/symfony/framework-bundle/Templating/Loader/TemplateLocator.php b/lib/symfony/framework-bundle/Templating/Loader/TemplateLocator.php deleted file mode 100644 index 267b59e4ab..0000000000 --- a/lib/symfony/framework-bundle/Templating/Loader/TemplateLocator.php +++ /dev/null @@ -1,85 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Templating\Loader; - -use Symfony\Component\Config\FileLocatorInterface; -use Symfony\Component\Templating\TemplateReferenceInterface; - -/** - * TemplateLocator locates templates in bundles. - * - * @author Fabien Potencier - */ -class TemplateLocator implements FileLocatorInterface -{ - protected $locator; - protected $cache; - - private $cacheHits = []; - - /** - * @param FileLocatorInterface $locator A FileLocatorInterface instance - * @param string $cacheDir The cache path - */ - public function __construct(FileLocatorInterface $locator, $cacheDir = null) - { - if (null !== $cacheDir && file_exists($cache = $cacheDir.'/templates.php')) { - $this->cache = require $cache; - } - - $this->locator = $locator; - } - - /** - * Returns a full path for a given file. - * - * @return string The full path for the file - */ - protected function getCacheKey($template) - { - return $template->getLogicalName(); - } - - /** - * Returns a full path for a given file. - * - * @param TemplateReferenceInterface $template A template - * @param string $currentPath Unused - * @param bool $first Unused - * - * @return string The full path for the file - * - * @throws \InvalidArgumentException When the template is not an instance of TemplateReferenceInterface - * @throws \InvalidArgumentException When the template file can not be found - */ - public function locate($template, $currentPath = null, $first = true) - { - if (!$template instanceof TemplateReferenceInterface) { - throw new \InvalidArgumentException('The template must be an instance of TemplateReferenceInterface.'); - } - - $key = $this->getCacheKey($template); - - if (isset($this->cacheHits[$key])) { - return $this->cacheHits[$key]; - } - if (isset($this->cache[$key])) { - return $this->cacheHits[$key] = realpath($this->cache[$key]) ?: $this->cache[$key]; - } - - try { - return $this->cacheHits[$key] = $this->locator->locate($template->getPath(), $currentPath); - } catch (\InvalidArgumentException $e) { - throw new \InvalidArgumentException(sprintf('Unable to find template "%s": ', $template).$e->getMessage(), 0, $e); - } - } -} diff --git a/lib/symfony/framework-bundle/Templating/PhpEngine.php b/lib/symfony/framework-bundle/Templating/PhpEngine.php deleted file mode 100644 index ef9c43b8fd..0000000000 --- a/lib/symfony/framework-bundle/Templating/PhpEngine.php +++ /dev/null @@ -1,78 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Templating; - -use Psr\Container\ContainerInterface; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Templating\Loader\LoaderInterface; -use Symfony\Component\Templating\PhpEngine as BasePhpEngine; -use Symfony\Component\Templating\TemplateNameParserInterface; - -/** - * This engine knows how to render Symfony templates. - * - * @author Fabien Potencier - */ -class PhpEngine extends BasePhpEngine implements EngineInterface -{ - protected $container; - - public function __construct(TemplateNameParserInterface $parser, ContainerInterface $container, LoaderInterface $loader, GlobalVariables $globals = null) - { - $this->container = $container; - - parent::__construct($parser, $loader); - - if (null !== $globals) { - $this->addGlobal('app', $globals); - } - } - - /** - * {@inheritdoc} - */ - public function get($name) - { - if (!isset($this->helpers[$name])) { - throw new \InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); - } - - if (\is_string($this->helpers[$name])) { - $this->helpers[$name] = $this->container->get($this->helpers[$name]); - $this->helpers[$name]->setCharset($this->charset); - } - - return $this->helpers[$name]; - } - - /** - * {@inheritdoc} - */ - public function setHelpers(array $helpers) - { - $this->helpers = $helpers; - } - - /** - * {@inheritdoc} - */ - public function renderResponse($view, array $parameters = [], Response $response = null) - { - if (null === $response) { - $response = new Response(); - } - - $response->setContent($this->render($view, $parameters)); - - return $response; - } -} diff --git a/lib/symfony/framework-bundle/Templating/TemplateFilenameParser.php b/lib/symfony/framework-bundle/Templating/TemplateFilenameParser.php deleted file mode 100644 index b6870334c6..0000000000 --- a/lib/symfony/framework-bundle/Templating/TemplateFilenameParser.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 Symfony\Bundle\FrameworkBundle\Templating; - -use Symfony\Component\Templating\TemplateNameParserInterface; -use Symfony\Component\Templating\TemplateReferenceInterface; - -/** - * TemplateFilenameParser converts template filenames to - * TemplateReferenceInterface instances. - * - * @author Fabien Potencier - */ -class TemplateFilenameParser implements TemplateNameParserInterface -{ - /** - * {@inheritdoc} - */ - public function parse($name) - { - if ($name instanceof TemplateReferenceInterface) { - return $name; - } - - $parts = explode('/', str_replace('\\', '/', $name)); - - $elements = explode('.', array_pop($parts)); - if (3 > \count($elements)) { - return false; - } - $engine = array_pop($elements); - $format = array_pop($elements); - - return new TemplateReference('', implode('/', $parts), implode('.', $elements), $format, $engine); - } -} diff --git a/lib/symfony/framework-bundle/Templating/TemplateNameParser.php b/lib/symfony/framework-bundle/Templating/TemplateNameParser.php deleted file mode 100644 index c8822e105b..0000000000 --- a/lib/symfony/framework-bundle/Templating/TemplateNameParser.php +++ /dev/null @@ -1,80 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Templating; - -use Symfony\Component\HttpKernel\KernelInterface; -use Symfony\Component\Templating\TemplateNameParser as BaseTemplateNameParser; -use Symfony\Component\Templating\TemplateReferenceInterface; - -/** - * TemplateNameParser converts template names from the short notation - * "bundle:section:template.format.engine" to TemplateReferenceInterface - * instances. - * - * @author Fabien Potencier - */ -class TemplateNameParser extends BaseTemplateNameParser -{ - protected $kernel; - protected $cache = []; - - public function __construct(KernelInterface $kernel) - { - $this->kernel = $kernel; - } - - /** - * {@inheritdoc} - */ - public function parse($name) - { - if ($name instanceof TemplateReferenceInterface) { - return $name; - } elseif (isset($this->cache[$name])) { - return $this->cache[$name]; - } - - // normalize name - $name = preg_replace('#/{2,}#', '/', str_replace('\\', '/', $name)); - - if (false !== strpos($name, '..')) { - throw new \RuntimeException(sprintf('Template name "%s" contains invalid characters.', $name)); - } - - if ($this->isAbsolutePath($name) || !preg_match('/^(?:([^:]*):([^:]*):)?(.+)\.([^\.]+)\.([^\.]+)$/', $name, $matches) || 0 === strpos($name, '@')) { - return parent::parse($name); - } - - $template = new TemplateReference($matches[1], $matches[2], $matches[3], $matches[4], $matches[5]); - - if ($template->get('bundle')) { - try { - $this->kernel->getBundle($template->get('bundle')); - } catch (\Exception $e) { - throw new \InvalidArgumentException(sprintf('Template name "%s" is not valid.', $name), 0, $e); - } - } - - return $this->cache[$name] = $template; - } - - private function isAbsolutePath($file) - { - $isAbsolute = (bool) preg_match('#^(?:/|[a-zA-Z]:)#', $file); - - if ($isAbsolute) { - @trigger_error('Absolute template path support is deprecated since Symfony 3.1 and will be removed in 4.0.', \E_USER_DEPRECATED); - } - - return $isAbsolute; - } -} diff --git a/lib/symfony/framework-bundle/Templating/TemplateReference.php b/lib/symfony/framework-bundle/Templating/TemplateReference.php deleted file mode 100644 index 8f1daf8599..0000000000 --- a/lib/symfony/framework-bundle/Templating/TemplateReference.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Templating; - -use Symfony\Component\Templating\TemplateReference as BaseTemplateReference; - -/** - * Internal representation of a template. - * - * @author Victor Berchet - */ -class TemplateReference extends BaseTemplateReference -{ - public function __construct($bundle = null, $controller = null, $name = null, $format = null, $engine = null) - { - $this->parameters = [ - 'bundle' => $bundle, - 'controller' => $controller, - 'name' => $name, - 'format' => $format, - 'engine' => $engine, - ]; - } - - /** - * Returns the path to the template - * - as a path when the template is not part of a bundle - * - as a resource when the template is part of a bundle. - * - * @return string A path to the template or a resource - */ - public function getPath() - { - $controller = str_replace('\\', '/', $this->get('controller')); - - $path = (empty($controller) ? '' : $controller.'/').$this->get('name').'.'.$this->get('format').'.'.$this->get('engine'); - - return empty($this->parameters['bundle']) ? 'views/'.$path : '@'.$this->get('bundle').'/Resources/views/'.$path; - } - - /** - * {@inheritdoc} - */ - public function getLogicalName() - { - return sprintf('%s:%s:%s.%s.%s', $this->parameters['bundle'], $this->parameters['controller'], $this->parameters['name'], $this->parameters['format'], $this->parameters['engine']); - } -} diff --git a/lib/symfony/framework-bundle/Templating/TimedPhpEngine.php b/lib/symfony/framework-bundle/Templating/TimedPhpEngine.php deleted file mode 100644 index 783e675f97..0000000000 --- a/lib/symfony/framework-bundle/Templating/TimedPhpEngine.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Templating; - -use Psr\Container\ContainerInterface; -use Symfony\Component\Stopwatch\Stopwatch; -use Symfony\Component\Templating\Loader\LoaderInterface; -use Symfony\Component\Templating\TemplateNameParserInterface; - -/** - * Times the time spent to render a template. - * - * @author Fabien Potencier - */ -class TimedPhpEngine extends PhpEngine -{ - protected $stopwatch; - - public function __construct(TemplateNameParserInterface $parser, ContainerInterface $container, LoaderInterface $loader, Stopwatch $stopwatch, GlobalVariables $globals = null) - { - parent::__construct($parser, $container, $loader, $globals); - - $this->stopwatch = $stopwatch; - } - - /** - * {@inheritdoc} - */ - public function render($name, array $parameters = []) - { - $e = $this->stopwatch->start(sprintf('template.php (%s)', $name), 'template'); - - $ret = parent::render($name, $parameters); - - $e->stop(); - - return $ret; - } -} diff --git a/lib/symfony/framework-bundle/Translation/PhpExtractor.php b/lib/symfony/framework-bundle/Translation/PhpExtractor.php deleted file mode 100644 index 9ef4518e5b..0000000000 --- a/lib/symfony/framework-bundle/Translation/PhpExtractor.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Translation; - -use Symfony\Component\Translation\Extractor\PhpExtractor as NewPhpExtractor; - -@trigger_error(sprintf('The class "%s" is deprecated since Symfony 3.4 and will be removed in 4.0. Use "%s" instead. ', PhpExtractor::class, NewPhpExtractor::class), \E_USER_DEPRECATED); - -/** - * @deprecated since version 3.4 and will be removed in 4.0. Use Symfony\Component\Translation\Extractor\PhpExtractor instead - */ -class PhpExtractor extends NewPhpExtractor -{ -} diff --git a/lib/symfony/framework-bundle/Translation/PhpStringTokenParser.php b/lib/symfony/framework-bundle/Translation/PhpStringTokenParser.php deleted file mode 100644 index 02c800997e..0000000000 --- a/lib/symfony/framework-bundle/Translation/PhpStringTokenParser.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Translation; - -@trigger_error(sprintf('The class "%s" is deprecated since Symfony 3.4 and will be removed in 4.0. Use "%s" instead. ', PhpStringTokenParser::class, \Symfony\Component\Translation\Extractor\PhpStringTokenParser::class), \E_USER_DEPRECATED); - -/** - * @deprecated since version 3.4 and will be removed in 4.0. Use Symfony\Component\Translation\Extractor\PhpStringTokenParser instead - */ -class PhpStringTokenParser extends \Symfony\Component\Translation\Extractor\PhpStringTokenParser -{ -} diff --git a/lib/symfony/framework-bundle/Translation/TranslationLoader.php b/lib/symfony/framework-bundle/Translation/TranslationLoader.php deleted file mode 100644 index 4511be87cc..0000000000 --- a/lib/symfony/framework-bundle/Translation/TranslationLoader.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\Bundle\FrameworkBundle\Translation; - -use Symfony\Component\Translation\MessageCatalogue; -use Symfony\Component\Translation\Reader\TranslationReader; - -/** - * @deprecated since version 3.4 and will be removed in 4.0. Use Symfony\Component\Translation\Reader\TranslationReader instead - */ -class TranslationLoader extends TranslationReader -{ - public function __construct() - { - @trigger_error(sprintf('The class "%s" is deprecated since Symfony 3.4 and will be removed in 4.0. Use "%s" instead. ', self::class, TranslationReader::class), \E_USER_DEPRECATED); - } - - /** - * Loads translation messages from a directory to the catalogue. - * - * @param string $directory The directory to look into - * @param MessageCatalogue $catalogue The catalogue - */ - public function loadMessages($directory, MessageCatalogue $catalogue) - { - $this->read($directory, $catalogue); - } -} diff --git a/lib/symfony/framework-bundle/Translation/Translator.php b/lib/symfony/framework-bundle/Translation/Translator.php index a3e43a836c..5173f8a8ef 100644 --- a/lib/symfony/framework-bundle/Translation/Translator.php +++ b/lib/symfony/framework-bundle/Translation/Translator.php @@ -12,15 +12,14 @@ namespace Symfony\Bundle\FrameworkBundle\Translation; use Psr\Container\ContainerInterface; -use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface; +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Config\Resource\FileExistenceResource; use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; use Symfony\Component\Translation\Exception\InvalidArgumentException; use Symfony\Component\Translation\Formatter\MessageFormatterInterface; use Symfony\Component\Translation\Translator as BaseTranslator; /** - * Translator. - * * @author Fabien Potencier */ class Translator extends BaseTranslator implements WarmableInterface @@ -32,10 +31,12 @@ class Translator extends BaseTranslator implements WarmableInterface 'cache_dir' => null, 'debug' => false, 'resource_files' => [], + 'scanned_directories' => [], + 'cache_vary' => [], ]; /** - * @var array + * @var list */ private $resourceLocales; @@ -43,43 +44,42 @@ class Translator extends BaseTranslator implements WarmableInterface * Holds parameters from addResource() calls so we can defer the actual * parent::addResource() calls until initialize() is executed. * - * @var array + * @var array[] */ private $resources = []; + /** + * @var string[][] + */ + private $resourceFiles; + + /** + * @var string[] + */ + private $scannedDirectories; + + /** + * @var string[] + */ + private $enabledLocales; + /** * Constructor. * * Available options: * - * * cache_dir: The cache directory (or null to disable caching) - * * debug: Whether to enable debugging or not (false by default) + * * cache_dir: The cache directory (or null to disable caching) + * * debug: Whether to enable debugging or not (false by default) * * resource_files: List of translation resources available grouped by locale. - * - * @param ContainerInterface $container A ContainerInterface instance - * @param MessageFormatterInterface $formatter The message formatter - * @param string $defaultLocale - * @param array $loaderIds An array of loader Ids - * @param array $options An array of options + * * cache_vary: An array of data that is serialized to generate the cached catalogue name. * * @throws InvalidArgumentException */ - public function __construct(ContainerInterface $container, $formatter, $defaultLocale = null, array $loaderIds = [], array $options = []) + public function __construct(ContainerInterface $container, MessageFormatterInterface $formatter, string $defaultLocale, array $loaderIds = [], array $options = [], array $enabledLocales = []) { - // BC 3.x, to be removed in 4.0 along with the $defaultLocale default value - if (\is_array($defaultLocale) || 3 > \func_num_args()) { - if (!$container instanceof SymfonyContainerInterface) { - throw new \InvalidArgumentException('Missing third $defaultLocale argument.'); - } - - $options = $loaderIds; - $loaderIds = $defaultLocale; - $defaultLocale = $container->getParameter('kernel.default_locale'); - @trigger_error(sprintf('The "%s()" method takes the default locale as the 3rd argument since Symfony 3.3. Not passing it is deprecated and will trigger an error in 4.0.', __METHOD__), \E_USER_DEPRECATED); - } - $this->container = $container; $this->loaderIds = $loaderIds; + $this->enabledLocales = $enabledLocales; // check option names if ($diff = array_diff(array_keys($options), array_keys($this->options))) { @@ -88,23 +88,27 @@ class Translator extends BaseTranslator implements WarmableInterface $this->options = array_merge($this->options, $options); $this->resourceLocales = array_keys($this->options['resource_files']); - $this->addResourceFiles($this->options['resource_files']); + $this->resourceFiles = $this->options['resource_files']; + $this->scannedDirectories = $this->options['scanned_directories']; - parent::__construct($defaultLocale, $formatter, $this->options['cache_dir'], $this->options['debug']); + parent::__construct($defaultLocale, $formatter, $this->options['cache_dir'], $this->options['debug'], $this->options['cache_vary']); } /** * {@inheritdoc} + * + * @return string[] */ - public function warmUp($cacheDir) + public function warmUp(string $cacheDir) { // skip warmUp when translator doesn't use cache if (null === $this->options['cache_dir']) { - return; + return []; } - $locales = array_merge($this->getFallbackLocales(), [$this->getLocale()], $this->resourceLocales); - foreach (array_unique($locales) as $locale) { + $localesToWarmUp = $this->enabledLocales ?: array_merge($this->getFallbackLocales(), [$this->getLocale()], $this->resourceLocales); + + foreach (array_unique($localesToWarmUp) as $locale) { // reset catalogue in case it's already loaded during the dump of the other locales. if (isset($this->catalogues[$locale])) { unset($this->catalogues[$locale]); @@ -112,26 +116,47 @@ class Translator extends BaseTranslator implements WarmableInterface $this->loadCatalogue($locale); } + + return []; } - public function addResource($format, $resource, $locale, $domain = null) + public function addResource(string $format, $resource, string $locale, string $domain = null) { + if ($this->resourceFiles) { + $this->addResourceFiles(); + } $this->resources[] = [$format, $resource, $locale, $domain]; } /** * {@inheritdoc} */ - protected function initializeCatalogue($locale) + protected function initializeCatalogue(string $locale) { $this->initialize(); parent::initializeCatalogue($locale); } + /** + * @internal + */ + protected function doLoadCatalogue(string $locale): void + { + parent::doLoadCatalogue($locale); + + foreach ($this->scannedDirectories as $directory) { + $resourceClass = file_exists($directory) ? DirectoryResource::class : FileExistenceResource::class; + $this->catalogues[$locale]->addResource(new $resourceClass($directory)); + } + } + protected function initialize() { - foreach ($this->resources as $key => $params) { - list($format, $resource, $locale, $domain) = $params; + if ($this->resourceFiles) { + $this->addResourceFiles(); + } + foreach ($this->resources as $params) { + [$format, $resource, $locale, $domain] = $params; parent::addResource($format, $resource, $locale, $domain); } $this->resources = []; @@ -143,12 +168,18 @@ class Translator extends BaseTranslator implements WarmableInterface } } - private function addResourceFiles($filesByLocale) + private function addResourceFiles(): void { - foreach ($filesByLocale as $locale => $files) { - foreach ($files as $key => $file) { + $filesByLocale = $this->resourceFiles; + $this->resourceFiles = []; + + foreach ($filesByLocale as $files) { + foreach ($files as $file) { // filename is domain.locale.format - list($domain, $locale, $format) = explode('.', basename($file), 3); + $fileNameParts = explode('.', basename($file)); + $format = array_pop($fileNameParts); + $locale = array_pop($fileNameParts); + $domain = implode('.', $fileNameParts); $this->addResource($format, $file, $locale, $domain); } } diff --git a/lib/symfony/framework-bundle/Validator/ConstraintValidatorFactory.php b/lib/symfony/framework-bundle/Validator/ConstraintValidatorFactory.php deleted file mode 100644 index 9b5f5adc8c..0000000000 --- a/lib/symfony/framework-bundle/Validator/ConstraintValidatorFactory.php +++ /dev/null @@ -1,84 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Validator; - -use Psr\Container\ContainerInterface; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\ConstraintValidatorInterface; -use Symfony\Component\Validator\ContainerConstraintValidatorFactory; -use Symfony\Component\Validator\Exception\UnexpectedTypeException; -use Symfony\Component\Validator\Exception\ValidatorException; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use %s instead.', ConstraintValidatorFactory::class, ContainerConstraintValidatorFactory::class), \E_USER_DEPRECATED); - -/** - * Uses a service container to create constraint validators. - * - * A constraint validator should be tagged as "validator.constraint_validator" - * in the service container and include an "alias" attribute: - * - * - * - * - * - * - * A constraint may then return this alias in its validatedBy() method: - * - * public function validatedBy() - * { - * return 'some_alias'; - * } - * - * @author Kris Wallsmith - * - * @deprecated since version 3.3 - */ -class ConstraintValidatorFactory extends ContainerConstraintValidatorFactory -{ - protected $container; - protected $validators; - - public function __construct(ContainerInterface $container, array $validators = []) - { - parent::__construct($container); - - $this->validators = $validators; - $this->container = $container; - } - - /** - * Returns the validator for the supplied constraint. - * - * @return ConstraintValidatorInterface A validator for the supplied constraint - * - * @throws ValidatorException When the validator class does not exist - * @throws UnexpectedTypeException When the validator is not an instance of ConstraintValidatorInterface - */ - public function getInstance(Constraint $constraint) - { - $name = $constraint->validatedBy(); - - if (!isset($this->validators[$name])) { - return parent::getInstance($constraint); - } - - if (\is_string($this->validators[$name])) { - $this->validators[$name] = $this->container->get($this->validators[$name]); - } - - if (!$this->validators[$name] instanceof ConstraintValidatorInterface) { - throw new UnexpectedTypeException($this->validators[$name], 'Symfony\Component\Validator\ConstraintValidatorInterface'); - } - - return $this->validators[$name]; - } -} diff --git a/lib/symfony/framework-bundle/composer.json b/lib/symfony/framework-bundle/composer.json index 824bc8f07a..c9482a7676 100644 --- a/lib/symfony/framework-bundle/composer.json +++ b/lib/symfony/framework-bundle/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/framework-bundle", "type": "symfony-bundle", - "description": "Symfony FrameworkBundle", + "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "keywords": [], "homepage": "https://symfony.com", "license": "MIT", @@ -16,63 +16,87 @@ } ], "require": { - "php": "^5.5.9|>=7.0.8", + "php": ">=7.2.5", "ext-xml": "*", - "symfony/cache": "~3.4.31|^4.3.4", - "symfony/class-loader": "~3.2", - "symfony/dependency-injection": "^3.4.24|^4.2.5", - "symfony/config": "^3.4.31|^4.3.4", - "symfony/debug": "~2.8|~3.0|~4.0", - "symfony/event-dispatcher": "~3.4|~4.0", - "symfony/http-foundation": "^3.4.38|^4.3", - "symfony/http-kernel": "^3.4.44|^4.3.4", + "symfony/cache": "^5.2|^6.0", + "symfony/config": "^5.3|^6.0", + "symfony/dependency-injection": "^5.4.5|^6.0.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/event-dispatcher": "^5.1|^6.0", + "symfony/error-handler": "^4.4.1|^5.0.1|^6.0", + "symfony/http-foundation": "^5.3|^6.0", + "symfony/http-kernel": "^5.4|^6.0", "symfony/polyfill-mbstring": "~1.0", - "symfony/filesystem": "~2.8|~3.0|~4.0", - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/routing": "^3.4.5|^4.0.5" + "symfony/polyfill-php80": "^1.16", + "symfony/polyfill-php81": "^1.22", + "symfony/filesystem": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/routing": "^5.3|^6.0" }, "require-dev": { - "doctrine/cache": "~1.0", - "fig/link-util": "^1.0", - "symfony/asset": "~3.3|~4.0", - "symfony/browser-kit": "~2.8|~3.0|~4.0", - "symfony/console": "~3.4.31|^4.3.4", - "symfony/css-selector": "~2.8|~3.0|~4.0", - "symfony/dom-crawler": "~2.8|~3.0|~4.0", + "doctrine/annotations": "^1.13.1", + "doctrine/cache": "^1.11|^2.0", + "doctrine/persistence": "^1.3|^2|^3", + "symfony/asset": "^5.3|^6.0", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/console": "^5.4.9|^6.0.9", + "symfony/css-selector": "^4.4|^5.0|^6.0", + "symfony/dom-crawler": "^4.4.30|^5.3.7|^6.0", + "symfony/dotenv": "^5.1|^6.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/form": "^3.4.31|^4.3.4", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/process": "~2.8|~3.0|~4.0", - "symfony/security-core": "~3.2|~4.0", - "symfony/security-csrf": "^2.8.31|^3.3.13|~4.0", - "symfony/serializer": "~3.3|~4.0", - "symfony/stopwatch": "~3.4|~4.0", - "symfony/translation": "~3.4|~4.0", - "symfony/templating": "~2.8|~3.0|~4.0", - "symfony/validator": "~3.4|~4.0", - "symfony/var-dumper": "~3.3|~4.0", - "symfony/workflow": "~3.3|~4.0", - "symfony/yaml": "~3.2|~4.0", - "symfony/property-info": "~3.3|~4.0", - "symfony/lock": "~3.4|~4.0", - "symfony/web-link": "~3.3|~4.0", - "doctrine/annotations": "~1.7", - "phpdocumentor/reflection-docblock": "^3.0|^4.0", - "twig/twig": "~1.34|~2.4" + "symfony/form": "^5.2|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/mailer": "^5.2|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/notifier": "^5.4|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/rate-limiter": "^5.2|^6.0", + "symfony/security-bundle": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0", + "symfony/string": "^5.0|^6.0", + "symfony/translation": "^5.3|^6.0", + "symfony/twig-bundle": "^4.4|^5.0|^6.0", + "symfony/validator": "^5.2|^6.0", + "symfony/workflow": "^5.2|^6.0", + "symfony/yaml": "^4.4|^5.0|^6.0", + "symfony/property-info": "^4.4|^5.0|^6.0", + "symfony/web-link": "^4.4|^5.0|^6.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "twig/twig": "^2.10|^3.0" }, "conflict": { - "phpdocumentor/reflection-docblock": "<3.0", - "phpdocumentor/type-resolver": "<0.2.1", - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", - "symfony/asset": "<3.3", - "symfony/console": "<3.4", - "symfony/form": "<3.4", - "symfony/property-info": "<3.3", - "symfony/serializer": "<3.3", - "symfony/stopwatch": "<3.4", - "symfony/translation": "<3.4", - "symfony/validator": "<3.4", - "symfony/workflow": "<3.3" + "doctrine/annotations": "<1.13.1", + "doctrine/cache": "<1.11", + "doctrine/persistence": "<1.3", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "phpunit/phpunit": "<5.4.3", + "symfony/asset": "<5.3", + "symfony/console": "<5.2.5", + "symfony/dotenv": "<5.1", + "symfony/dom-crawler": "<4.4", + "symfony/http-client": "<4.4", + "symfony/form": "<5.2", + "symfony/lock": "<4.4", + "symfony/mailer": "<5.2", + "symfony/messenger": "<5.4", + "symfony/mime": "<4.4", + "symfony/property-info": "<4.4", + "symfony/property-access": "<5.3", + "symfony/serializer": "<5.2", + "symfony/service-contracts": ">=3.0", + "symfony/security-csrf": "<5.3", + "symfony/stopwatch": "<4.4", + "symfony/translation": "<5.3", + "symfony/twig-bridge": "<4.4", + "symfony/twig-bundle": "<4.4", + "symfony/validator": "<5.2", + "symfony/web-profiler-bundle": "<4.4", + "symfony/workflow": "<5.2" }, "suggest": { "ext-apcu": "For best performance of the system caches", diff --git a/lib/symfony/framework-bundle/phpunit.xml.dist b/lib/symfony/framework-bundle/phpunit.xml.dist deleted file mode 100644 index c0d8df4156..0000000000 --- a/lib/symfony/framework-bundle/phpunit.xml.dist +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - ./Tests/ - - - - - - ./ - - ./Resources - ./Tests - ./vendor - - - - diff --git a/lib/symfony/http-foundation/.gitignore b/lib/symfony/http-foundation/.gitignore deleted file mode 100644 index c49a5d8df5..0000000000 --- a/lib/symfony/http-foundation/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/lib/symfony/http-foundation/AcceptHeader.php b/lib/symfony/http-foundation/AcceptHeader.php index daf7f1f4b1..057c6b530c 100644 --- a/lib/symfony/http-foundation/AcceptHeader.php +++ b/lib/symfony/http-foundation/AcceptHeader.php @@ -11,6 +11,9 @@ namespace Symfony\Component\HttpFoundation; +// Help opcache.preload discover always-needed symbols +class_exists(AcceptHeaderItem::class); + /** * Represents an Accept-* header. * @@ -44,20 +47,23 @@ class AcceptHeader /** * Builds an AcceptHeader instance from a string. * - * @param string $headerValue - * * @return self */ - public static function fromString($headerValue) + public static function fromString(?string $headerValue) { $index = 0; - return new self(array_map(function ($itemValue) use (&$index) { - $item = AcceptHeaderItem::fromString($itemValue); + $parts = HeaderUtils::split($headerValue ?? '', ',;='); + + return new self(array_map(function ($subParts) use (&$index) { + $part = array_shift($subParts); + $attributes = HeaderUtils::combine($subParts); + + $item = new AcceptHeaderItem($part[0], $attributes); $item->setIndex($index++); return $item; - }, preg_split('/\s*(?:,*("[^"]+"),*|,*(\'[^\']+\'),*|,+)\s*/', $headerValue, 0, \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE))); + }, $parts)); } /** @@ -73,11 +79,9 @@ class AcceptHeader /** * Tests if header has given value. * - * @param string $value - * * @return bool */ - public function has($value) + public function has(string $value) { return isset($this->items[$value]); } @@ -85,13 +89,11 @@ class AcceptHeader /** * Returns given value's item, if exists. * - * @param string $value - * * @return AcceptHeaderItem|null */ - public function get($value) + public function get(string $value) { - return isset($this->items[$value]) ? $this->items[$value] : null; + return $this->items[$value] ?? $this->items[explode('/', $value)[0].'/*'] ?? $this->items['*/*'] ?? $this->items['*'] ?? null; } /** @@ -122,11 +124,9 @@ class AcceptHeader /** * Filters items on their value using given regex. * - * @param string $pattern - * * @return self */ - public function filter($pattern) + public function filter(string $pattern) { return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) { return preg_match($pattern, $item->getValue()); @@ -148,7 +148,7 @@ class AcceptHeader /** * Sorts items by descending quality. */ - private function sort() + private function sort(): void { if (!$this->sorted) { uasort($this->items, function (AcceptHeaderItem $a, AcceptHeaderItem $b) { diff --git a/lib/symfony/http-foundation/AcceptHeaderItem.php b/lib/symfony/http-foundation/AcceptHeaderItem.php index 9eb74490bc..8b86eee672 100644 --- a/lib/symfony/http-foundation/AcceptHeaderItem.php +++ b/lib/symfony/http-foundation/AcceptHeaderItem.php @@ -23,10 +23,7 @@ class AcceptHeaderItem private $index = 0; private $attributes = []; - /** - * @param string $value - */ - public function __construct($value, array $attributes = []) + public function __construct(string $value, array $attributes = []) { $this->value = $value; foreach ($attributes as $name => $value) { @@ -37,30 +34,16 @@ class AcceptHeaderItem /** * Builds an AcceptHeaderInstance instance from a string. * - * @param string $itemValue - * * @return self */ - public static function fromString($itemValue) + public static function fromString(?string $itemValue) { - $bits = preg_split('/\s*(?:;*("[^"]+");*|;*(\'[^\']+\');*|;+)\s*/', $itemValue, 0, \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE); - $value = array_shift($bits); - $attributes = []; + $parts = HeaderUtils::split($itemValue ?? '', ';='); - $lastNullAttribute = null; - foreach ($bits as $bit) { - if (($start = substr($bit, 0, 1)) === ($end = substr($bit, -1)) && ('"' === $start || '\'' === $start)) { - $attributes[$lastNullAttribute] = substr($bit, 1, -1); - } elseif ('=' === $end) { - $lastNullAttribute = $bit = substr($bit, 0, -1); - $attributes[$bit] = null; - } else { - $parts = explode('=', $bit); - $attributes[$parts[0]] = isset($parts[1]) && \strlen($parts[1]) > 0 ? $parts[1] : ''; - } - } + $part = array_shift($parts); + $attributes = HeaderUtils::combine($parts); - return new self(($start = substr($value, 0, 1)) === ($end = substr($value, -1)) && ('"' === $start || '\'' === $start) ? substr($value, 1, -1) : $value, $attributes); + return new self($part[0], $attributes); } /** @@ -72,9 +55,7 @@ class AcceptHeaderItem { $string = $this->value.($this->quality < 1 ? ';q='.$this->quality : ''); if (\count($this->attributes) > 0) { - $string .= ';'.implode(';', array_map(function ($name, $value) { - return sprintf(preg_match('/[,;=]/', $value) ? '%s="%s"' : '%s=%s', $name, $value); - }, array_keys($this->attributes), $this->attributes)); + $string .= '; '.HeaderUtils::toString($this->attributes, ';'); } return $string; @@ -83,11 +64,9 @@ class AcceptHeaderItem /** * Set the item value. * - * @param string $value - * * @return $this */ - public function setValue($value) + public function setValue(string $value) { $this->value = $value; @@ -107,11 +86,9 @@ class AcceptHeaderItem /** * Set the item quality. * - * @param float $quality - * * @return $this */ - public function setQuality($quality) + public function setQuality(float $quality) { $this->quality = $quality; @@ -131,11 +108,9 @@ class AcceptHeaderItem /** * Set the item index. * - * @param int $index - * * @return $this */ - public function setIndex($index) + public function setIndex(int $index) { $this->index = $index; @@ -155,11 +130,9 @@ class AcceptHeaderItem /** * Tests if an attribute exists. * - * @param string $name - * * @return bool */ - public function hasAttribute($name) + public function hasAttribute(string $name) { return isset($this->attributes[$name]); } @@ -167,14 +140,13 @@ class AcceptHeaderItem /** * Returns an attribute by its name. * - * @param string $name - * @param mixed $default + * @param mixed $default * * @return mixed */ - public function getAttribute($name, $default = null) + public function getAttribute(string $name, $default = null) { - return isset($this->attributes[$name]) ? $this->attributes[$name] : $default; + return $this->attributes[$name] ?? $default; } /** @@ -190,17 +162,14 @@ class AcceptHeaderItem /** * Set an attribute. * - * @param string $name - * @param string $value - * * @return $this */ - public function setAttribute($name, $value) + public function setAttribute(string $name, string $value) { if ('q' === $name) { $this->quality = (float) $value; } else { - $this->attributes[$name] = (string) $value; + $this->attributes[$name] = $value; } return $this; diff --git a/lib/symfony/http-foundation/ApacheRequest.php b/lib/symfony/http-foundation/ApacheRequest.php deleted file mode 100644 index 4e99186dcd..0000000000 --- a/lib/symfony/http-foundation/ApacheRequest.php +++ /dev/null @@ -1,43 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation; - -/** - * Request represents an HTTP request from an Apache server. - * - * @author Fabien Potencier - */ -class ApacheRequest extends Request -{ - /** - * {@inheritdoc} - */ - protected function prepareRequestUri() - { - return $this->server->get('REQUEST_URI'); - } - - /** - * {@inheritdoc} - */ - protected function prepareBaseUrl() - { - $baseUrl = $this->server->get('SCRIPT_NAME'); - - if (false === strpos($this->server->get('REQUEST_URI'), $baseUrl)) { - // assume mod_rewrite - return rtrim(\dirname($baseUrl), '/\\'); - } - - return $baseUrl; - } -} diff --git a/lib/symfony/http-foundation/BinaryFileResponse.php b/lib/symfony/http-foundation/BinaryFileResponse.php index 0a43b8ac71..beb0eda0a5 100644 --- a/lib/symfony/http-foundation/BinaryFileResponse.php +++ b/lib/symfony/http-foundation/BinaryFileResponse.php @@ -34,6 +34,7 @@ class BinaryFileResponse extends Response protected $offset = 0; protected $maxlen = -1; protected $deleteFileAfterSend = false; + protected $chunkSize = 8 * 1024; /** * @param \SplFileInfo|string $file The file to stream @@ -44,7 +45,7 @@ class BinaryFileResponse extends Response * @param bool $autoEtag Whether the ETag header should be automatically set * @param bool $autoLastModified Whether the Last-Modified header should be automatically set */ - public function __construct($file, $status = 200, $headers = [], $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true) + public function __construct($file, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) { parent::__construct(null, $status, $headers); @@ -65,25 +66,26 @@ class BinaryFileResponse extends Response * @param bool $autoLastModified Whether the Last-Modified header should be automatically set * * @return static + * + * @deprecated since Symfony 5.2, use __construct() instead. */ - public static function create($file = null, $status = 200, $headers = [], $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true) + public static function create($file = null, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) { + trigger_deprecation('symfony/http-foundation', '5.2', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); + return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified); } /** * Sets the file to stream. * - * @param \SplFileInfo|string $file The file to stream - * @param string $contentDisposition - * @param bool $autoEtag - * @param bool $autoLastModified + * @param \SplFileInfo|string $file The file to stream * * @return $this * * @throws FileException */ - public function setFile($file, $contentDisposition = null, $autoEtag = false, $autoLastModified = true) + public function setFile($file, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) { if (!$file instanceof File) { if ($file instanceof \SplFileInfo) { @@ -117,15 +119,33 @@ class BinaryFileResponse extends Response /** * Gets the file. * - * @return File The file to stream + * @return File */ public function getFile() { return $this->file; } + /** + * Sets the response stream chunk size. + * + * @return $this + */ + public function setChunkSize(int $chunkSize): self + { + if ($chunkSize < 1 || $chunkSize > \PHP_INT_MAX) { + throw new \LogicException('The chunk size of a BinaryFileResponse cannot be less than 1 or greater than PHP_INT_MAX.'); + } + + $this->chunkSize = $chunkSize; + + return $this; + } + /** * Automatically sets the Last-Modified header according the file modification date. + * + * @return $this */ public function setAutoLastModified() { @@ -136,6 +156,8 @@ class BinaryFileResponse extends Response /** * Automatically sets the ETag header according to the checksum of the file. + * + * @return $this */ public function setAutoEtag() { @@ -153,13 +175,13 @@ class BinaryFileResponse extends Response * * @return $this */ - public function setContentDisposition($disposition, $filename = '', $filenameFallback = '') + public function setContentDisposition(string $disposition, string $filename = '', string $filenameFallback = '') { if ('' === $filename) { $filename = $this->file->getFilename(); } - if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || false !== strpos($filename, '%'))) { + if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || str_contains($filename, '%'))) { $encoding = mb_detect_encoding($filename, null, true) ?: '8bit'; for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) { @@ -204,7 +226,7 @@ class BinaryFileResponse extends Response if (!$this->headers->has('Accept-Ranges')) { // Only accept ranges on safe HTTP methods - $this->headers->set('Accept-Ranges', $request->isMethodSafe(false) ? 'bytes' : 'none'); + $this->headers->set('Accept-Ranges', $request->isMethodSafe() ? 'bytes' : 'none'); } if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) { @@ -218,21 +240,16 @@ class BinaryFileResponse extends Response if ('x-accel-redirect' === strtolower($type)) { // Do X-Accel-Mapping substitutions. // @link https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/#x-accel-redirect - foreach (explode(',', $request->headers->get('X-Accel-Mapping', '')) as $mapping) { - $mapping = explode('=', $mapping, 2); - - if (2 === \count($mapping)) { - $pathPrefix = trim($mapping[0]); - $location = trim($mapping[1]); - - if (substr($path, 0, \strlen($pathPrefix)) === $pathPrefix) { - $path = $location.substr($path, \strlen($pathPrefix)); - // Only set X-Accel-Redirect header if a valid URI can be produced - // as nginx does not serve arbitrary file paths. - $this->headers->set($type, $path); - $this->maxlen = 0; - break; - } + $parts = HeaderUtils::split($request->headers->get('X-Accel-Mapping', ''), ',='); + foreach ($parts as $part) { + [$pathPrefix, $location] = $part; + if (substr($path, 0, \strlen($pathPrefix)) === $pathPrefix) { + $path = $location.substr($path, \strlen($pathPrefix)); + // Only set X-Accel-Redirect header if a valid URI can be produced + // as nginx does not serve arbitrary file paths. + $this->headers->set($type, $path); + $this->maxlen = 0; + break; } } } else { @@ -244,8 +261,8 @@ class BinaryFileResponse extends Response if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) { $range = $request->headers->get('Range'); - if (0 === strpos($range, 'bytes=')) { - list($start, $end) = explode('-', substr($range, 6), 2) + [0]; + if (str_starts_with($range, 'bytes=')) { + [$start, $end] = explode('-', substr($range, 6), 2) + [0]; $end = ('' === $end) ? $fileSize - 1 : (int) $end; @@ -277,7 +294,7 @@ class BinaryFileResponse extends Response return $this; } - private function hasValidIfRangeHeader($header) + private function hasValidIfRangeHeader(?string $header): bool { if ($this->getEtag() === $header) { return true; @@ -291,8 +308,6 @@ class BinaryFileResponse extends Response } /** - * Sends the file. - * * {@inheritdoc} */ public function sendContent() @@ -305,15 +320,31 @@ class BinaryFileResponse extends Response return $this; } - $out = fopen('php://output', 'wb'); - $file = fopen($this->file->getPathname(), 'rb'); + $out = fopen('php://output', 'w'); + $file = fopen($this->file->getPathname(), 'r'); - stream_copy_to_stream($file, $out, $this->maxlen, $this->offset); + ignore_user_abort(true); + + if (0 !== $this->offset) { + fseek($file, $this->offset); + } + + $length = $this->maxlen; + while ($length && !feof($file)) { + $read = ($length > $this->chunkSize) ? $this->chunkSize : $length; + $length -= $read; + + stream_copy_to_stream($file, $out, $read); + + if (connection_aborted()) { + break; + } + } fclose($out); fclose($file); - if ($this->deleteFileAfterSend && file_exists($this->file->getPathname())) { + if ($this->deleteFileAfterSend && is_file($this->file->getPathname())) { unlink($this->file->getPathname()); } @@ -325,7 +356,7 @@ class BinaryFileResponse extends Response * * @throws \LogicException when the content is not null */ - public function setContent($content) + public function setContent(?string $content) { if (null !== $content) { throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.'); @@ -354,11 +385,9 @@ class BinaryFileResponse extends Response * If this is set to true, the file will be unlinked after the request is sent * Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used. * - * @param bool $shouldDelete - * * @return $this */ - public function deleteFileAfterSend($shouldDelete) + public function deleteFileAfterSend(bool $shouldDelete = true) { $this->deleteFileAfterSend = $shouldDelete; diff --git a/lib/symfony/http-foundation/CHANGELOG.md b/lib/symfony/http-foundation/CHANGELOG.md index c0d8901677..ad7607add3 100644 --- a/lib/symfony/http-foundation/CHANGELOG.md +++ b/lib/symfony/http-foundation/CHANGELOG.md @@ -1,12 +1,143 @@ CHANGELOG ========= -3.4.14 ------- +5.4 +--- + + * Deprecate passing `null` as `$requestIp` to `IpUtils::__checkIp()`, `IpUtils::__checkIp4()` or `IpUtils::__checkIp6()`, pass an empty string instead. + * Add the `litespeed_finish_request` method to work with Litespeed + * Deprecate `upload_progress.*` and `url_rewriter.tags` session options + * Allow setting session options via DSN + +5.3 +--- + + * Add the `SessionFactory`, `NativeSessionStorageFactory`, `PhpBridgeSessionStorageFactory` and `MockFileSessionStorageFactory` classes + * Calling `Request::getSession()` when there is no available session throws a `SessionNotFoundException` + * Add the `RequestStack::getSession` method + * Deprecate the `NamespacedAttributeBag` class + * Add `ResponseFormatSame` PHPUnit constraint + * Deprecate the `RequestStack::getMasterRequest()` method and add `getMainRequest()` as replacement + +5.2.0 +----- + + * added support for `X-Forwarded-Prefix` header + * added `HeaderUtils::parseQuery()`: it does the same as `parse_str()` but preserves dots in variable names + * added `File::getContent()` + * added ability to use comma separated ip addresses for `RequestMatcher::matchIps()` + * added `Request::toArray()` to parse a JSON request body to an array + * added `RateLimiter\RequestRateLimiterInterface` and `RateLimiter\AbstractRequestRateLimiter` + * deprecated not passing a `Closure` together with `FILTER_CALLBACK` to `ParameterBag::filter()`; wrap your filter in a closure instead. + * Deprecated the `Request::HEADER_X_FORWARDED_ALL` constant, use either `HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO` or `HEADER_X_FORWARDED_AWS_ELB` or `HEADER_X_FORWARDED_TRAEFIK` constants instead. + * Deprecated `BinaryFileResponse::create()`, use `__construct()` instead + +5.1.0 +----- + + * added `Cookie::withValue`, `Cookie::withDomain`, `Cookie::withExpires`, + `Cookie::withPath`, `Cookie::withSecure`, `Cookie::withHttpOnly`, + `Cookie::withRaw`, `Cookie::withSameSite` + * Deprecate `Response::create()`, `JsonResponse::create()`, + `RedirectResponse::create()`, and `StreamedResponse::create()` methods (use + `__construct()` instead) + * added `Request::preferSafeContent()` and `Response::setContentSafe()` to handle "safe" HTTP preference + according to [RFC 8674](https://tools.ietf.org/html/rfc8674) + * made the Mime component an optional dependency + * added `MarshallingSessionHandler`, `IdentityMarshaller` + * made `Session` accept a callback to report when the session is being used + * Add support for all core cache control directives + * Added `Symfony\Component\HttpFoundation\InputBag` + * Deprecated retrieving non-string values using `InputBag::get()`, use `InputBag::all()` if you need access to the collection of values + +5.0.0 +----- + + * made `Cookie` auto-secure and lax by default + * removed classes in the `MimeType` namespace, use the Symfony Mime component instead + * removed method `UploadedFile::getClientSize()` and the related constructor argument + * made `Request::getSession()` throw if the session has not been set before + * removed `Response::HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL` + * passing a null url when instantiating a `RedirectResponse` is not allowed + +4.4.0 +----- + + * passing arguments to `Request::isMethodSafe()` is deprecated. + * `ApacheRequest` is deprecated, use the `Request` class instead. + * passing a third argument to `HeaderBag::get()` is deprecated, use method `all()` instead + * [BC BREAK] `PdoSessionHandler` with MySQL changed the type of the lifetime column, + make sure to run `ALTER TABLE sessions MODIFY sess_lifetime INTEGER UNSIGNED NOT NULL` to + update your database. + * `PdoSessionHandler` now precalculates the expiry timestamp in the lifetime column, + make sure to run `CREATE INDEX EXPIRY ON sessions (sess_lifetime)` to update your database + to speed up garbage collection of expired sessions. + * added `SessionHandlerFactory` to create session handlers with a DSN + * added `IpUtils::anonymize()` to help with GDPR compliance. + +4.3.0 +----- + + * added PHPUnit constraints: `RequestAttributeValueSame`, `ResponseCookieValueSame`, `ResponseHasCookie`, + `ResponseHasHeader`, `ResponseHeaderSame`, `ResponseIsRedirected`, `ResponseIsSuccessful`, and `ResponseStatusCodeSame` + * deprecated `MimeTypeGuesserInterface` and `ExtensionGuesserInterface` in favor of `Symfony\Component\Mime\MimeTypesInterface`. + * deprecated `MimeType` and `MimeTypeExtensionGuesser` in favor of `Symfony\Component\Mime\MimeTypes`. + * deprecated `FileBinaryMimeTypeGuesser` in favor of `Symfony\Component\Mime\FileBinaryMimeTypeGuesser`. + * deprecated `FileinfoMimeTypeGuesser` in favor of `Symfony\Component\Mime\FileinfoMimeTypeGuesser`. + * added `UrlHelper` that allows to get an absolute URL and a relative path for a given path + +4.2.0 +----- + + * the default value of the "$secure" and "$samesite" arguments of Cookie's constructor + will respectively change from "false" to "null" and from "null" to "lax" in Symfony + 5.0, you should define their values explicitly or use "Cookie::create()" instead. + * added `matchPort()` in RequestMatcher + +4.1.3 +----- * [BC BREAK] Support for the IIS-only `X_ORIGINAL_URL` and `X_REWRITE_URL` HTTP headers has been dropped for security reasons. +4.1.0 +----- + + * Query string normalization uses `parse_str()` instead of custom parsing logic. + * Passing the file size to the constructor of the `UploadedFile` class is deprecated. + * The `getClientSize()` method of the `UploadedFile` class is deprecated. Use `getSize()` instead. + * added `RedisSessionHandler` to use Redis as a session storage + * The `get()` method of the `AcceptHeader` class now takes into account the + `*` and `*/*` default values (if they are present in the Accept HTTP header) + when looking for items. + * deprecated `Request::getSession()` when no session has been set. Use `Request::hasSession()` instead. + * added `CannotWriteFileException`, `ExtensionFileException`, `FormSizeFileException`, + `IniSizeFileException`, `NoFileException`, `NoTmpDirFileException`, `PartialFileException` to + handle failed `UploadedFile`. + * added `MigratingSessionHandler` for migrating between two session handlers without losing sessions + * added `HeaderUtils`. + +4.0.0 +----- + + * the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()` + methods have been removed + * the `Request::HEADER_CLIENT_IP` constant has been removed, use + `Request::HEADER_X_FORWARDED_FOR` instead + * the `Request::HEADER_CLIENT_HOST` constant has been removed, use + `Request::HEADER_X_FORWARDED_HOST` instead + * the `Request::HEADER_CLIENT_PROTO` constant has been removed, use + `Request::HEADER_X_FORWARDED_PROTO` instead + * the `Request::HEADER_CLIENT_PORT` constant has been removed, use + `Request::HEADER_X_FORWARDED_PORT` instead + * checking for cacheable HTTP methods using the `Request::isMethodSafe()` + method (by not passing `false` as its argument) is not supported anymore and + throws a `\BadMethodCallException` + * the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes have been removed + * setting session save handlers that do not implement `\SessionHandlerInterface` in + `NativeSessionStorage::setSaveHandler()` is not supported anymore and throws a + `\TypeError` + 3.4.0 ----- diff --git a/lib/symfony/http-foundation/Cookie.php b/lib/symfony/http-foundation/Cookie.php index 98a5ef00a8..b4b26c0151 100644 --- a/lib/symfony/http-foundation/Cookie.php +++ b/lib/symfony/http-foundation/Cookie.php @@ -18,9 +18,9 @@ namespace Symfony\Component\HttpFoundation; */ class Cookie { - const SAMESITE_NONE = 'none'; - const SAMESITE_LAX = 'lax'; - const SAMESITE_STRICT = 'strict'; + public const SAMESITE_NONE = 'none'; + public const SAMESITE_LAX = 'lax'; + public const SAMESITE_STRICT = 'strict'; protected $name; protected $value; @@ -32,20 +32,18 @@ class Cookie private $raw; private $sameSite; + private $secureDefault = false; - private static $reservedCharsList = "=,; \t\r\n\v\f"; - private static $reservedCharsFrom = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"]; - private static $reservedCharsTo = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C']; + private const RESERVED_CHARS_LIST = "=,; \t\r\n\v\f"; + private const RESERVED_CHARS_FROM = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"]; + private const RESERVED_CHARS_TO = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C']; /** * Creates cookie from raw header string. * - * @param string $cookie - * @param bool $decode - * * @return static */ - public static function fromString($cookie, $decode = false) + public static function fromString(string $cookie, bool $decode = false) { $data = [ 'expires' => 0, @@ -56,34 +54,26 @@ class Cookie 'raw' => !$decode, 'samesite' => null, ]; - foreach (explode(';', $cookie) as $part) { - if (false === strpos($part, '=')) { - $key = trim($part); - $value = true; - } else { - list($key, $value) = explode('=', trim($part), 2); - $key = trim($key); - $value = trim($value); - } - if (!isset($data['name'])) { - $data['name'] = $decode ? urldecode($key) : $key; - $data['value'] = true === $value ? null : ($decode ? urldecode($value) : $value); - continue; - } - switch ($key = strtolower($key)) { - case 'name': - case 'value': - break; - case 'max-age': - $data['expires'] = time() + (int) $value; - break; - default: - $data[$key] = $value; - break; - } + + $parts = HeaderUtils::split($cookie, ';='); + $part = array_shift($parts); + + $name = $decode ? urldecode($part[0]) : $part[0]; + $value = isset($part[1]) ? ($decode ? urldecode($part[1]) : $part[1]) : null; + + $data = HeaderUtils::combine($parts) + $data; + $data['expires'] = self::expiresTimestamp($data['expires']); + + if (isset($data['max-age']) && ($data['max-age'] > 0 || $data['expires'] > time())) { + $data['expires'] = time() + (int) $data['max-age']; } - return new static($data['name'], $data['value'], $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']); + return new static($name, $value, $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']); + } + + public static function create(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX): self + { + return new self($name, $value, $expire, $path, $domain, $secure, $httpOnly, $raw, $sameSite); } /** @@ -92,17 +82,17 @@ class Cookie * @param int|string|\DateTimeInterface $expire The time the cookie expires * @param string $path The path on the server in which the cookie will be available on * @param string|null $domain The domain that the cookie is available to - * @param bool $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client + * @param bool|null $secure Whether the client should send back the cookie only over HTTPS or null to auto-enable this when the request is already using HTTPS * @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol * @param bool $raw Whether the cookie value should be sent with no url encoding * @param string|null $sameSite Whether the cookie will be available for cross-site requests * * @throws \InvalidArgumentException */ - public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true, $raw = false, $sameSite = null) + public function __construct(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = 'lax') { // from PHP source code - if ($raw && false !== strpbrk($name, self::$reservedCharsList)) { + if ($raw && false !== strpbrk($name, self::RESERVED_CHARS_LIST)) { throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name)); } @@ -110,6 +100,65 @@ class Cookie throw new \InvalidArgumentException('The cookie name cannot be empty.'); } + $this->name = $name; + $this->value = $value; + $this->domain = $domain; + $this->expire = self::expiresTimestamp($expire); + $this->path = empty($path) ? '/' : $path; + $this->secure = $secure; + $this->httpOnly = $httpOnly; + $this->raw = $raw; + $this->sameSite = $this->withSameSite($sameSite)->sameSite; + } + + /** + * Creates a cookie copy with a new value. + * + * @return static + */ + public function withValue(?string $value): self + { + $cookie = clone $this; + $cookie->value = $value; + + return $cookie; + } + + /** + * Creates a cookie copy with a new domain that the cookie is available to. + * + * @return static + */ + public function withDomain(?string $domain): self + { + $cookie = clone $this; + $cookie->domain = $domain; + + return $cookie; + } + + /** + * Creates a cookie copy with a new time the cookie expires. + * + * @param int|string|\DateTimeInterface $expire + * + * @return static + */ + public function withExpires($expire = 0): self + { + $cookie = clone $this; + $cookie->expire = self::expiresTimestamp($expire); + + return $cookie; + } + + /** + * Converts expires formats to a unix timestamp. + * + * @param int|string|\DateTimeInterface $expire + */ + private static function expiresTimestamp($expire = 0): int + { // convert expiration time to a Unix timestamp if ($expire instanceof \DateTimeInterface) { $expire = $expire->format('U'); @@ -121,16 +170,75 @@ class Cookie } } - $this->name = $name; - $this->value = $value; - $this->domain = $domain; - $this->expire = 0 < $expire ? (int) $expire : 0; - $this->path = empty($path) ? '/' : $path; - $this->secure = (bool) $secure; - $this->httpOnly = (bool) $httpOnly; - $this->raw = (bool) $raw; + return 0 < $expire ? (int) $expire : 0; + } - if (null !== $sameSite) { + /** + * Creates a cookie copy with a new path on the server in which the cookie will be available on. + * + * @return static + */ + public function withPath(string $path): self + { + $cookie = clone $this; + $cookie->path = '' === $path ? '/' : $path; + + return $cookie; + } + + /** + * Creates a cookie copy that only be transmitted over a secure HTTPS connection from the client. + * + * @return static + */ + public function withSecure(bool $secure = true): self + { + $cookie = clone $this; + $cookie->secure = $secure; + + return $cookie; + } + + /** + * Creates a cookie copy that be accessible only through the HTTP protocol. + * + * @return static + */ + public function withHttpOnly(bool $httpOnly = true): self + { + $cookie = clone $this; + $cookie->httpOnly = $httpOnly; + + return $cookie; + } + + /** + * Creates a cookie copy that uses no url encoding. + * + * @return static + */ + public function withRaw(bool $raw = true): self + { + if ($raw && false !== strpbrk($this->name, self::RESERVED_CHARS_LIST)) { + throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $this->name)); + } + + $cookie = clone $this; + $cookie->raw = $raw; + + return $cookie; + } + + /** + * Creates a cookie copy with SameSite attribute. + * + * @return static + */ + public function withSameSite(?string $sameSite): self + { + if ('' === $sameSite) { + $sameSite = null; + } elseif (null !== $sameSite) { $sameSite = strtolower($sameSite); } @@ -138,20 +246,23 @@ class Cookie throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.'); } - $this->sameSite = $sameSite; + $cookie = clone $this; + $cookie->sameSite = $sameSite; + + return $cookie; } /** * Returns the cookie as a string. * - * @return string The cookie + * @return string */ public function __toString() { if ($this->isRaw()) { $str = $this->getName(); } else { - $str = str_replace(self::$reservedCharsFrom, self::$reservedCharsTo, $this->getName()); + $str = str_replace(self::RESERVED_CHARS_FROM, self::RESERVED_CHARS_TO, $this->getName()); } $str .= '='; @@ -258,7 +369,7 @@ class Cookie */ public function isSecure() { - return $this->secure; + return $this->secure ?? $this->secureDefault; } /** @@ -300,4 +411,12 @@ class Cookie { return $this->sameSite; } + + /** + * @param bool $default The default value of the "secure" flag when it is set to null + */ + public function setSecureDefault(bool $default): void + { + $this->secureDefault = $default; + } } diff --git a/lib/symfony/http-foundation/Exception/BadRequestException.php b/lib/symfony/http-foundation/Exception/BadRequestException.php new file mode 100644 index 0000000000..e4bb309c42 --- /dev/null +++ b/lib/symfony/http-foundation/Exception/BadRequestException.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 Symfony\Component\HttpFoundation\Exception; + +/** + * Raised when a user sends a malformed request. + */ +class BadRequestException extends \UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/lib/symfony/http-foundation/Exception/JsonException.php b/lib/symfony/http-foundation/Exception/JsonException.php new file mode 100644 index 0000000000..5990e760e6 --- /dev/null +++ b/lib/symfony/http-foundation/Exception/JsonException.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 Symfony\Component\HttpFoundation\Exception; + +/** + * Thrown by Request::toArray() when the content cannot be JSON-decoded. + * + * @author Tobias Nyholm + */ +final class JsonException extends \UnexpectedValueException implements RequestExceptionInterface +{ +} diff --git a/lib/symfony/http-foundation/Exception/SessionNotFoundException.php b/lib/symfony/http-foundation/Exception/SessionNotFoundException.php new file mode 100644 index 0000000000..9c719aa041 --- /dev/null +++ b/lib/symfony/http-foundation/Exception/SessionNotFoundException.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * Raised when a session does not exists. This happens in the following cases: + * - the session is not enabled + * - attempt to read a session outside a request context (ie. cli script). + * + * @author Jérémy Derussé + */ +class SessionNotFoundException extends \LogicException implements RequestExceptionInterface +{ + public function __construct(string $message = 'There is currently no session available.', int $code = 0, \Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + } +} diff --git a/lib/symfony/http-foundation/File/Exception/AccessDeniedException.php b/lib/symfony/http-foundation/File/Exception/AccessDeniedException.php index 3b8e41d4a2..136d2a9f51 100644 --- a/lib/symfony/http-foundation/File/Exception/AccessDeniedException.php +++ b/lib/symfony/http-foundation/File/Exception/AccessDeniedException.php @@ -18,10 +18,7 @@ namespace Symfony\Component\HttpFoundation\File\Exception; */ class AccessDeniedException extends FileException { - /** - * @param string $path The path to the accessed file - */ - public function __construct($path) + public function __construct(string $path) { parent::__construct(sprintf('The file %s could not be accessed', $path)); } diff --git a/lib/symfony/http-foundation/File/Exception/CannotWriteFileException.php b/lib/symfony/http-foundation/File/Exception/CannotWriteFileException.php new file mode 100644 index 0000000000..c49f53a6cf --- /dev/null +++ b/lib/symfony/http-foundation/File/Exception/CannotWriteFileException.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 Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_CANT_WRITE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class CannotWriteFileException extends FileException +{ +} diff --git a/lib/symfony/http-foundation/File/Exception/ExtensionFileException.php b/lib/symfony/http-foundation/File/Exception/ExtensionFileException.php new file mode 100644 index 0000000000..ed83499c00 --- /dev/null +++ b/lib/symfony/http-foundation/File/Exception/ExtensionFileException.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 Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_EXTENSION error occurred with UploadedFile. + * + * @author Florent Mata + */ +class ExtensionFileException extends FileException +{ +} diff --git a/lib/symfony/http-foundation/File/Exception/FileNotFoundException.php b/lib/symfony/http-foundation/File/Exception/FileNotFoundException.php index bfcc37ec66..31bdf68fef 100644 --- a/lib/symfony/http-foundation/File/Exception/FileNotFoundException.php +++ b/lib/symfony/http-foundation/File/Exception/FileNotFoundException.php @@ -18,10 +18,7 @@ namespace Symfony\Component\HttpFoundation\File\Exception; */ class FileNotFoundException extends FileException { - /** - * @param string $path The path to the file that was not found - */ - public function __construct($path) + public function __construct(string $path) { parent::__construct(sprintf('The file "%s" does not exist', $path)); } diff --git a/lib/symfony/http-foundation/File/Exception/FormSizeFileException.php b/lib/symfony/http-foundation/File/Exception/FormSizeFileException.php new file mode 100644 index 0000000000..8741be0884 --- /dev/null +++ b/lib/symfony/http-foundation/File/Exception/FormSizeFileException.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 Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_FORM_SIZE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class FormSizeFileException extends FileException +{ +} diff --git a/lib/symfony/http-foundation/File/Exception/IniSizeFileException.php b/lib/symfony/http-foundation/File/Exception/IniSizeFileException.php new file mode 100644 index 0000000000..c8fde6103a --- /dev/null +++ b/lib/symfony/http-foundation/File/Exception/IniSizeFileException.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 Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_INI_SIZE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class IniSizeFileException extends FileException +{ +} diff --git a/lib/symfony/http-foundation/File/Exception/NoFileException.php b/lib/symfony/http-foundation/File/Exception/NoFileException.php new file mode 100644 index 0000000000..4b48cc7799 --- /dev/null +++ b/lib/symfony/http-foundation/File/Exception/NoFileException.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 Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_NO_FILE error occurred with UploadedFile. + * + * @author Florent Mata + */ +class NoFileException extends FileException +{ +} diff --git a/lib/symfony/http-foundation/File/Exception/NoTmpDirFileException.php b/lib/symfony/http-foundation/File/Exception/NoTmpDirFileException.php new file mode 100644 index 0000000000..bdead2d91c --- /dev/null +++ b/lib/symfony/http-foundation/File/Exception/NoTmpDirFileException.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 Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_NO_TMP_DIR error occurred with UploadedFile. + * + * @author Florent Mata + */ +class NoTmpDirFileException extends FileException +{ +} diff --git a/lib/symfony/http-foundation/File/Exception/PartialFileException.php b/lib/symfony/http-foundation/File/Exception/PartialFileException.php new file mode 100644 index 0000000000..4641efb55a --- /dev/null +++ b/lib/symfony/http-foundation/File/Exception/PartialFileException.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 Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an UPLOAD_ERR_PARTIAL error occurred with UploadedFile. + * + * @author Florent Mata + */ +class PartialFileException extends FileException +{ +} diff --git a/lib/symfony/http-foundation/File/Exception/UnexpectedTypeException.php b/lib/symfony/http-foundation/File/Exception/UnexpectedTypeException.php index 62005d3b63..8533f99a8c 100644 --- a/lib/symfony/http-foundation/File/Exception/UnexpectedTypeException.php +++ b/lib/symfony/http-foundation/File/Exception/UnexpectedTypeException.php @@ -13,8 +13,8 @@ namespace Symfony\Component\HttpFoundation\File\Exception; class UnexpectedTypeException extends FileException { - public function __construct($value, $expectedType) + public function __construct($value, string $expectedType) { - parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, \is_object($value) ? \get_class($value) : \gettype($value))); + parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, get_debug_type($value))); } } diff --git a/lib/symfony/http-foundation/File/File.php b/lib/symfony/http-foundation/File/File.php index 309339e5cf..d941577d25 100644 --- a/lib/symfony/http-foundation/File/File.php +++ b/lib/symfony/http-foundation/File/File.php @@ -13,8 +13,7 @@ namespace Symfony\Component\HttpFoundation\File; use Symfony\Component\HttpFoundation\File\Exception\FileException; use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; -use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser; -use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser; +use Symfony\Component\Mime\MimeTypes; /** * A file in the file system. @@ -31,7 +30,7 @@ class File extends \SplFileInfo * * @throws FileNotFoundException If the given path is not a file */ - public function __construct($path, $checkPath = true) + public function __construct(string $path, bool $checkPath = true) { if ($checkPath && !is_file($path)) { throw new FileNotFoundException($path); @@ -48,54 +47,57 @@ class File extends \SplFileInfo * This method uses the mime type as guessed by getMimeType() * to guess the file extension. * - * @return string|null The guessed extension or null if it cannot be guessed + * @return string|null * - * @see ExtensionGuesser + * @see MimeTypes * @see getMimeType() */ public function guessExtension() { - $type = $this->getMimeType(); - $guesser = ExtensionGuesser::getInstance(); + if (!class_exists(MimeTypes::class)) { + throw new \LogicException('You cannot guess the extension as the Mime component is not installed. Try running "composer require symfony/mime".'); + } - return $guesser->guess($type); + return MimeTypes::getDefault()->getExtensions($this->getMimeType())[0] ?? null; } /** * Returns the mime type of the file. * - * The mime type is guessed using a MimeTypeGuesser instance, which uses finfo(), - * mime_content_type() and the system binary "file" (in this order), depending on - * which of those are available. + * The mime type is guessed using a MimeTypeGuesserInterface instance, + * which uses finfo_file() then the "file" system binary, + * depending on which of those are available. * - * @return string|null The guessed mime type (e.g. "application/pdf") + * @return string|null * - * @see MimeTypeGuesser + * @see MimeTypes */ public function getMimeType() { - $guesser = MimeTypeGuesser::getInstance(); + if (!class_exists(MimeTypes::class)) { + throw new \LogicException('You cannot guess the mime type as the Mime component is not installed. Try running "composer require symfony/mime".'); + } - return $guesser->guess($this->getPathname()); + return MimeTypes::getDefault()->guessMimeType($this->getPathname()); } /** * Moves the file to a new location. * - * @param string $directory The destination folder - * @param string $name The new file name - * - * @return self A File object representing the new file + * @return self * * @throws FileException if the target file could not be created */ - public function move($directory, $name = null) + public function move(string $directory, string $name = null) { $target = $this->getTargetFile($directory, $name); set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); - $renamed = rename($this->getPathname(), $target); - restore_error_handler(); + try { + $renamed = rename($this->getPathname(), $target); + } finally { + restore_error_handler(); + } if (!$renamed) { throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error))); } @@ -105,7 +107,21 @@ class File extends \SplFileInfo return $target; } - protected function getTargetFile($directory, $name = null) + public function getContent(): string + { + $content = file_get_contents($this->getPathname()); + + if (false === $content) { + throw new FileException(sprintf('Could not get the content of the file "%s".', $this->getPathname())); + } + + return $content; + } + + /** + * @return self + */ + protected function getTargetFile(string $directory, string $name = null) { if (!is_dir($directory)) { if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) { @@ -123,11 +139,9 @@ class File extends \SplFileInfo /** * Returns locale independent base name of the given path. * - * @param string $name The new file name - * - * @return string containing + * @return string */ - protected function getName($name) + protected function getName(string $name) { $originalName = str_replace('\\', '/', $name); $pos = strrpos($originalName, '/'); diff --git a/lib/symfony/http-foundation/File/MimeType/ExtensionGuesser.php b/lib/symfony/http-foundation/File/MimeType/ExtensionGuesser.php deleted file mode 100644 index f9393df900..0000000000 --- a/lib/symfony/http-foundation/File/MimeType/ExtensionGuesser.php +++ /dev/null @@ -1,96 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File\MimeType; - -/** - * A singleton mime type to file extension guesser. - * - * A default guesser is provided. - * You can register custom guessers by calling the register() - * method on the singleton instance: - * - * $guesser = ExtensionGuesser::getInstance(); - * $guesser->register(new MyCustomExtensionGuesser()); - * - * The last registered guesser is preferred over previously registered ones. - */ -class ExtensionGuesser implements ExtensionGuesserInterface -{ - /** - * The singleton instance. - * - * @var ExtensionGuesser - */ - private static $instance = null; - - /** - * All registered ExtensionGuesserInterface instances. - * - * @var array - */ - protected $guessers = []; - - /** - * Returns the singleton instance. - * - * @return self - */ - public static function getInstance() - { - if (null === self::$instance) { - self::$instance = new self(); - } - - return self::$instance; - } - - /** - * Registers all natively provided extension guessers. - */ - private function __construct() - { - $this->register(new MimeTypeExtensionGuesser()); - } - - /** - * Registers a new extension guesser. - * - * When guessing, this guesser is preferred over previously registered ones. - */ - public function register(ExtensionGuesserInterface $guesser) - { - array_unshift($this->guessers, $guesser); - } - - /** - * Tries to guess the extension. - * - * The mime type is passed to each registered mime type guesser in reverse order - * of their registration (last registered is queried first). Once a guesser - * returns a value that is not NULL, this method terminates and returns the - * value. - * - * @param string $mimeType The mime type - * - * @return string The guessed extension or NULL, if none could be guessed - */ - public function guess($mimeType) - { - foreach ($this->guessers as $guesser) { - if (null !== $extension = $guesser->guess($mimeType)) { - return $extension; - } - } - - return null; - } -} diff --git a/lib/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php b/lib/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php deleted file mode 100644 index d19a0e5371..0000000000 --- a/lib/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File\MimeType; - -/** - * Guesses the file extension corresponding to a given mime type. - */ -interface ExtensionGuesserInterface -{ - /** - * Makes a best guess for a file extension, given a mime type. - * - * @param string $mimeType The mime type - * - * @return string The guessed extension or NULL, if none could be guessed - */ - public function guess($mimeType); -} diff --git a/lib/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php b/lib/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php deleted file mode 100644 index 7045e94df6..0000000000 --- a/lib/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php +++ /dev/null @@ -1,99 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File\MimeType; - -use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; -use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; - -/** - * Guesses the mime type with the binary "file" (only available on *nix). - * - * @author Bernhard Schussek - */ -class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface -{ - private $cmd; - - /** - * The $cmd pattern must contain a "%s" string that will be replaced - * with the file name to guess. - * - * The command output must start with the mime type of the file. - * - * @param string $cmd The command to run to get the mime type of a file - */ - public function __construct($cmd = 'file -b --mime -- %s 2>/dev/null') - { - $this->cmd = $cmd; - } - - /** - * Returns whether this guesser is supported on the current OS. - * - * @return bool - */ - public static function isSupported() - { - static $supported = null; - - if (null !== $supported) { - return $supported; - } - - if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('passthru') || !\function_exists('escapeshellarg')) { - return $supported = false; - } - - ob_start(); - passthru('command -v file', $exitStatus); - $binPath = trim(ob_get_clean()); - - return $supported = 0 === $exitStatus && '' !== $binPath; - } - - /** - * {@inheritdoc} - */ - public function guess($path) - { - if (!is_file($path)) { - throw new FileNotFoundException($path); - } - - if (!is_readable($path)) { - throw new AccessDeniedException($path); - } - - if (!self::isSupported()) { - return null; - } - - ob_start(); - - // need to use --mime instead of -i. see #6641 - passthru(sprintf($this->cmd, escapeshellarg((0 === strpos($path, '-') ? './' : '').$path)), $return); - if ($return > 0) { - ob_end_clean(); - - return null; - } - - $type = trim(ob_get_clean()); - - if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\+\.]+)#i', $type, $match)) { - // it's not a type, but an error message - return null; - } - - return $match[1]; - } -} diff --git a/lib/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php b/lib/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php deleted file mode 100644 index 99aa44f9d3..0000000000 --- a/lib/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php +++ /dev/null @@ -1,75 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File\MimeType; - -use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; -use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; - -/** - * Guesses the mime type using the PECL extension FileInfo. - * - * @author Bernhard Schussek - */ -class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface -{ - private $magicFile; - - /** - * @param string $magicFile A magic file to use with the finfo instance - * - * @see https://php.net/finfo-open - */ - public function __construct($magicFile = null) - { - $this->magicFile = $magicFile; - } - - /** - * Returns whether this guesser is supported on the current OS/PHP setup. - * - * @return bool - */ - public static function isSupported() - { - return \function_exists('finfo_open'); - } - - /** - * {@inheritdoc} - */ - public function guess($path) - { - if (!is_file($path)) { - throw new FileNotFoundException($path); - } - - if (!is_readable($path)) { - throw new AccessDeniedException($path); - } - - if (!self::isSupported()) { - return null; - } - - if (!$finfo = new \finfo(\FILEINFO_MIME_TYPE, $this->magicFile)) { - return null; - } - $mimeType = $finfo->file($path); - - if ($mimeType && 0 === (\strlen($mimeType) % 2)) { - $mimeStart = substr($mimeType, 0, \strlen($mimeType) >> 1); - $mimeType = $mimeStart.$mimeStart === $mimeType ? $mimeStart : $mimeType; - } - - return $mimeType; - } -} diff --git a/lib/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php b/lib/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php deleted file mode 100644 index 5a809a2486..0000000000 --- a/lib/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php +++ /dev/null @@ -1,819 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File\MimeType; - -/** - * Provides a best-guess mapping of mime type to file extension. - */ -class MimeTypeExtensionGuesser implements ExtensionGuesserInterface -{ - /** - * A map of mime types and their default extensions. - * - * This list has been placed under the public domain by the Apache HTTPD project. - * This list has been updated from upstream on 2019-01-14. - * - * @see https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types - */ - protected $defaultExtensions = [ - 'application/andrew-inset' => 'ez', - 'application/applixware' => 'aw', - 'application/atom+xml' => 'atom', - 'application/atomcat+xml' => 'atomcat', - 'application/atomsvc+xml' => 'atomsvc', - 'application/ccxml+xml' => 'ccxml', - 'application/cdmi-capability' => 'cdmia', - 'application/cdmi-container' => 'cdmic', - 'application/cdmi-domain' => 'cdmid', - 'application/cdmi-object' => 'cdmio', - 'application/cdmi-queue' => 'cdmiq', - 'application/cu-seeme' => 'cu', - 'application/davmount+xml' => 'davmount', - 'application/docbook+xml' => 'dbk', - 'application/dssc+der' => 'dssc', - 'application/dssc+xml' => 'xdssc', - 'application/ecmascript' => 'ecma', - 'application/emma+xml' => 'emma', - 'application/epub+zip' => 'epub', - 'application/exi' => 'exi', - 'application/font-tdpfr' => 'pfr', - 'application/gml+xml' => 'gml', - 'application/gpx+xml' => 'gpx', - 'application/gxf' => 'gxf', - 'application/hyperstudio' => 'stk', - 'application/inkml+xml' => 'ink', - 'application/ipfix' => 'ipfix', - 'application/java-archive' => 'jar', - 'application/java-serialized-object' => 'ser', - 'application/java-vm' => 'class', - 'application/javascript' => 'js', - 'application/json' => 'json', - 'application/jsonml+json' => 'jsonml', - 'application/lost+xml' => 'lostxml', - 'application/mac-binhex40' => 'hqx', - 'application/mac-compactpro' => 'cpt', - 'application/mads+xml' => 'mads', - 'application/marc' => 'mrc', - 'application/marcxml+xml' => 'mrcx', - 'application/mathematica' => 'ma', - 'application/mathml+xml' => 'mathml', - 'application/mbox' => 'mbox', - 'application/mediaservercontrol+xml' => 'mscml', - 'application/metalink+xml' => 'metalink', - 'application/metalink4+xml' => 'meta4', - 'application/mets+xml' => 'mets', - 'application/mods+xml' => 'mods', - 'application/mp21' => 'm21', - 'application/mp4' => 'mp4s', - 'application/msword' => 'doc', - 'application/mxf' => 'mxf', - 'application/octet-stream' => 'bin', - 'application/oda' => 'oda', - 'application/oebps-package+xml' => 'opf', - 'application/ogg' => 'ogx', - 'application/omdoc+xml' => 'omdoc', - 'application/onenote' => 'onetoc', - 'application/oxps' => 'oxps', - 'application/patch-ops-error+xml' => 'xer', - 'application/pdf' => 'pdf', - 'application/pgp-encrypted' => 'pgp', - 'application/pgp-signature' => 'asc', - 'application/pics-rules' => 'prf', - 'application/pkcs10' => 'p10', - 'application/pkcs7-mime' => 'p7m', - 'application/pkcs7-signature' => 'p7s', - 'application/pkcs8' => 'p8', - 'application/pkix-attr-cert' => 'ac', - 'application/pkix-cert' => 'cer', - 'application/pkix-crl' => 'crl', - 'application/pkix-pkipath' => 'pkipath', - 'application/pkixcmp' => 'pki', - 'application/pls+xml' => 'pls', - 'application/postscript' => 'ai', - 'application/prs.cww' => 'cww', - 'application/pskc+xml' => 'pskcxml', - 'application/rdf+xml' => 'rdf', - 'application/reginfo+xml' => 'rif', - 'application/relax-ng-compact-syntax' => 'rnc', - 'application/resource-lists+xml' => 'rl', - 'application/resource-lists-diff+xml' => 'rld', - 'application/rls-services+xml' => 'rs', - 'application/rpki-ghostbusters' => 'gbr', - 'application/rpki-manifest' => 'mft', - 'application/rpki-roa' => 'roa', - 'application/rsd+xml' => 'rsd', - 'application/rss+xml' => 'rss', - 'application/rtf' => 'rtf', - 'application/sbml+xml' => 'sbml', - 'application/scvp-cv-request' => 'scq', - 'application/scvp-cv-response' => 'scs', - 'application/scvp-vp-request' => 'spq', - 'application/scvp-vp-response' => 'spp', - 'application/sdp' => 'sdp', - 'application/set-payment-initiation' => 'setpay', - 'application/set-registration-initiation' => 'setreg', - 'application/shf+xml' => 'shf', - 'application/smil+xml' => 'smi', - 'application/sparql-query' => 'rq', - 'application/sparql-results+xml' => 'srx', - 'application/srgs' => 'gram', - 'application/srgs+xml' => 'grxml', - 'application/sru+xml' => 'sru', - 'application/ssdl+xml' => 'ssdl', - 'application/ssml+xml' => 'ssml', - 'application/tei+xml' => 'tei', - 'application/thraud+xml' => 'tfi', - 'application/timestamped-data' => 'tsd', - 'application/vnd.3gpp.pic-bw-large' => 'plb', - 'application/vnd.3gpp.pic-bw-small' => 'psb', - 'application/vnd.3gpp.pic-bw-var' => 'pvb', - 'application/vnd.3gpp2.tcap' => 'tcap', - 'application/vnd.3m.post-it-notes' => 'pwn', - 'application/vnd.accpac.simply.aso' => 'aso', - 'application/vnd.accpac.simply.imp' => 'imp', - 'application/vnd.acucobol' => 'acu', - 'application/vnd.acucorp' => 'atc', - 'application/vnd.adobe.air-application-installer-package+zip' => 'air', - 'application/vnd.adobe.formscentral.fcdt' => 'fcdt', - 'application/vnd.adobe.fxp' => 'fxp', - 'application/vnd.adobe.xdp+xml' => 'xdp', - 'application/vnd.adobe.xfdf' => 'xfdf', - 'application/vnd.ahead.space' => 'ahead', - 'application/vnd.airzip.filesecure.azf' => 'azf', - 'application/vnd.airzip.filesecure.azs' => 'azs', - 'application/vnd.amazon.ebook' => 'azw', - 'application/vnd.americandynamics.acc' => 'acc', - 'application/vnd.amiga.ami' => 'ami', - 'application/vnd.android.package-archive' => 'apk', - 'application/vnd.anser-web-certificate-issue-initiation' => 'cii', - 'application/vnd.anser-web-funds-transfer-initiation' => 'fti', - 'application/vnd.antix.game-component' => 'atx', - 'application/vnd.apple.installer+xml' => 'mpkg', - 'application/vnd.apple.mpegurl' => 'm3u8', - 'application/vnd.aristanetworks.swi' => 'swi', - 'application/vnd.astraea-software.iota' => 'iota', - 'application/vnd.audiograph' => 'aep', - 'application/vnd.blueice.multipass' => 'mpm', - 'application/vnd.bmi' => 'bmi', - 'application/vnd.businessobjects' => 'rep', - 'application/vnd.chemdraw+xml' => 'cdxml', - 'application/vnd.chipnuts.karaoke-mmd' => 'mmd', - 'application/vnd.cinderella' => 'cdy', - 'application/vnd.claymore' => 'cla', - 'application/vnd.cloanto.rp9' => 'rp9', - 'application/vnd.clonk.c4group' => 'c4g', - 'application/vnd.cluetrust.cartomobile-config' => 'c11amc', - 'application/vnd.cluetrust.cartomobile-config-pkg' => 'c11amz', - 'application/vnd.commonspace' => 'csp', - 'application/vnd.contact.cmsg' => 'cdbcmsg', - 'application/vnd.cosmocaller' => 'cmc', - 'application/vnd.crick.clicker' => 'clkx', - 'application/vnd.crick.clicker.keyboard' => 'clkk', - 'application/vnd.crick.clicker.palette' => 'clkp', - 'application/vnd.crick.clicker.template' => 'clkt', - 'application/vnd.crick.clicker.wordbank' => 'clkw', - 'application/vnd.criticaltools.wbs+xml' => 'wbs', - 'application/vnd.ctc-posml' => 'pml', - 'application/vnd.cups-ppd' => 'ppd', - 'application/vnd.curl.car' => 'car', - 'application/vnd.curl.pcurl' => 'pcurl', - 'application/vnd.dart' => 'dart', - 'application/vnd.data-vision.rdz' => 'rdz', - 'application/vnd.dece.data' => 'uvf', - 'application/vnd.dece.ttml+xml' => 'uvt', - 'application/vnd.dece.unspecified' => 'uvx', - 'application/vnd.dece.zip' => 'uvz', - 'application/vnd.denovo.fcselayout-link' => 'fe_launch', - 'application/vnd.dna' => 'dna', - 'application/vnd.dolby.mlp' => 'mlp', - 'application/vnd.dpgraph' => 'dpg', - 'application/vnd.dreamfactory' => 'dfac', - 'application/vnd.ds-keypoint' => 'kpxx', - 'application/vnd.dvb.ait' => 'ait', - 'application/vnd.dvb.service' => 'svc', - 'application/vnd.dynageo' => 'geo', - 'application/vnd.ecowin.chart' => 'mag', - 'application/vnd.enliven' => 'nml', - 'application/vnd.epson.esf' => 'esf', - 'application/vnd.epson.msf' => 'msf', - 'application/vnd.epson.quickanime' => 'qam', - 'application/vnd.epson.salt' => 'slt', - 'application/vnd.epson.ssf' => 'ssf', - 'application/vnd.eszigno3+xml' => 'es3', - 'application/vnd.ezpix-album' => 'ez2', - 'application/vnd.ezpix-package' => 'ez3', - 'application/vnd.fdf' => 'fdf', - 'application/vnd.fdsn.mseed' => 'mseed', - 'application/vnd.fdsn.seed' => 'seed', - 'application/vnd.flographit' => 'gph', - 'application/vnd.fluxtime.clip' => 'ftc', - 'application/vnd.framemaker' => 'fm', - 'application/vnd.frogans.fnc' => 'fnc', - 'application/vnd.frogans.ltf' => 'ltf', - 'application/vnd.fsc.weblaunch' => 'fsc', - 'application/vnd.fujitsu.oasys' => 'oas', - 'application/vnd.fujitsu.oasys2' => 'oa2', - 'application/vnd.fujitsu.oasys3' => 'oa3', - 'application/vnd.fujitsu.oasysgp' => 'fg5', - 'application/vnd.fujitsu.oasysprs' => 'bh2', - 'application/vnd.fujixerox.ddd' => 'ddd', - 'application/vnd.fujixerox.docuworks' => 'xdw', - 'application/vnd.fujixerox.docuworks.binder' => 'xbd', - 'application/vnd.fuzzysheet' => 'fzs', - 'application/vnd.genomatix.tuxedo' => 'txd', - 'application/vnd.geogebra.file' => 'ggb', - 'application/vnd.geogebra.tool' => 'ggt', - 'application/vnd.geometry-explorer' => 'gex', - 'application/vnd.geonext' => 'gxt', - 'application/vnd.geoplan' => 'g2w', - 'application/vnd.geospace' => 'g3w', - 'application/vnd.gmx' => 'gmx', - 'application/vnd.google-earth.kml+xml' => 'kml', - 'application/vnd.google-earth.kmz' => 'kmz', - 'application/vnd.grafeq' => 'gqf', - 'application/vnd.groove-account' => 'gac', - 'application/vnd.groove-help' => 'ghf', - 'application/vnd.groove-identity-message' => 'gim', - 'application/vnd.groove-injector' => 'grv', - 'application/vnd.groove-tool-message' => 'gtm', - 'application/vnd.groove-tool-template' => 'tpl', - 'application/vnd.groove-vcard' => 'vcg', - 'application/vnd.hal+xml' => 'hal', - 'application/vnd.handheld-entertainment+xml' => 'zmm', - 'application/vnd.hbci' => 'hbci', - 'application/vnd.hhe.lesson-player' => 'les', - 'application/vnd.hp-hpgl' => 'hpgl', - 'application/vnd.hp-hpid' => 'hpid', - 'application/vnd.hp-hps' => 'hps', - 'application/vnd.hp-jlyt' => 'jlt', - 'application/vnd.hp-pcl' => 'pcl', - 'application/vnd.hp-pclxl' => 'pclxl', - 'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx', - 'application/vnd.ibm.minipay' => 'mpy', - 'application/vnd.ibm.modcap' => 'afp', - 'application/vnd.ibm.rights-management' => 'irm', - 'application/vnd.ibm.secure-container' => 'sc', - 'application/vnd.iccprofile' => 'icc', - 'application/vnd.igloader' => 'igl', - 'application/vnd.immervision-ivp' => 'ivp', - 'application/vnd.immervision-ivu' => 'ivu', - 'application/vnd.insors.igm' => 'igm', - 'application/vnd.intercon.formnet' => 'xpw', - 'application/vnd.intergeo' => 'i2g', - 'application/vnd.intu.qbo' => 'qbo', - 'application/vnd.intu.qfx' => 'qfx', - 'application/vnd.ipunplugged.rcprofile' => 'rcprofile', - 'application/vnd.irepository.package+xml' => 'irp', - 'application/vnd.is-xpr' => 'xpr', - 'application/vnd.isac.fcs' => 'fcs', - 'application/vnd.jam' => 'jam', - 'application/vnd.jcp.javame.midlet-rms' => 'rms', - 'application/vnd.jisp' => 'jisp', - 'application/vnd.joost.joda-archive' => 'joda', - 'application/vnd.kahootz' => 'ktz', - 'application/vnd.kde.karbon' => 'karbon', - 'application/vnd.kde.kchart' => 'chrt', - 'application/vnd.kde.kformula' => 'kfo', - 'application/vnd.kde.kivio' => 'flw', - 'application/vnd.kde.kontour' => 'kon', - 'application/vnd.kde.kpresenter' => 'kpr', - 'application/vnd.kde.kspread' => 'ksp', - 'application/vnd.kde.kword' => 'kwd', - 'application/vnd.kenameaapp' => 'htke', - 'application/vnd.kidspiration' => 'kia', - 'application/vnd.kinar' => 'kne', - 'application/vnd.koan' => 'skp', - 'application/vnd.kodak-descriptor' => 'sse', - 'application/vnd.las.las+xml' => 'lasxml', - 'application/vnd.llamagraphics.life-balance.desktop' => 'lbd', - 'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe', - 'application/vnd.lotus-1-2-3' => '123', - 'application/vnd.lotus-approach' => 'apr', - 'application/vnd.lotus-freelance' => 'pre', - 'application/vnd.lotus-notes' => 'nsf', - 'application/vnd.lotus-organizer' => 'org', - 'application/vnd.lotus-screencam' => 'scm', - 'application/vnd.lotus-wordpro' => 'lwp', - 'application/vnd.macports.portpkg' => 'portpkg', - 'application/vnd.mcd' => 'mcd', - 'application/vnd.medcalcdata' => 'mc1', - 'application/vnd.mediastation.cdkey' => 'cdkey', - 'application/vnd.mfer' => 'mwf', - 'application/vnd.mfmp' => 'mfm', - 'application/vnd.micrografx.flo' => 'flo', - 'application/vnd.micrografx.igx' => 'igx', - 'application/vnd.mif' => 'mif', - 'application/vnd.mobius.daf' => 'daf', - 'application/vnd.mobius.dis' => 'dis', - 'application/vnd.mobius.mbk' => 'mbk', - 'application/vnd.mobius.mqy' => 'mqy', - 'application/vnd.mobius.msl' => 'msl', - 'application/vnd.mobius.plc' => 'plc', - 'application/vnd.mobius.txf' => 'txf', - 'application/vnd.mophun.application' => 'mpn', - 'application/vnd.mophun.certificate' => 'mpc', - 'application/vnd.mozilla.xul+xml' => 'xul', - 'application/vnd.ms-artgalry' => 'cil', - 'application/vnd.ms-cab-compressed' => 'cab', - 'application/vnd.ms-excel' => 'xls', - 'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam', - 'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb', - 'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm', - 'application/vnd.ms-excel.template.macroenabled.12' => 'xltm', - 'application/vnd.ms-fontobject' => 'eot', - 'application/vnd.ms-htmlhelp' => 'chm', - 'application/vnd.ms-ims' => 'ims', - 'application/vnd.ms-lrm' => 'lrm', - 'application/vnd.ms-officetheme' => 'thmx', - 'application/vnd.ms-pki.seccat' => 'cat', - 'application/vnd.ms-pki.stl' => 'stl', - 'application/vnd.ms-powerpoint' => 'ppt', - 'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam', - 'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm', - 'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm', - 'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm', - 'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm', - 'application/vnd.ms-project' => 'mpp', - 'application/vnd.ms-word.document.macroenabled.12' => 'docm', - 'application/vnd.ms-word.template.macroenabled.12' => 'dotm', - 'application/vnd.ms-works' => 'wps', - 'application/vnd.ms-wpl' => 'wpl', - 'application/vnd.ms-xpsdocument' => 'xps', - 'application/vnd.mseq' => 'mseq', - 'application/vnd.musician' => 'mus', - 'application/vnd.muvee.style' => 'msty', - 'application/vnd.mynfc' => 'taglet', - 'application/vnd.neurolanguage.nlu' => 'nlu', - 'application/vnd.nitf' => 'ntf', - 'application/vnd.noblenet-directory' => 'nnd', - 'application/vnd.noblenet-sealer' => 'nns', - 'application/vnd.noblenet-web' => 'nnw', - 'application/vnd.nokia.n-gage.data' => 'ngdat', - 'application/vnd.nokia.n-gage.symbian.install' => 'n-gage', - 'application/vnd.nokia.radio-preset' => 'rpst', - 'application/vnd.nokia.radio-presets' => 'rpss', - 'application/vnd.novadigm.edm' => 'edm', - 'application/vnd.novadigm.edx' => 'edx', - 'application/vnd.novadigm.ext' => 'ext', - 'application/vnd.oasis.opendocument.chart' => 'odc', - 'application/vnd.oasis.opendocument.chart-template' => 'otc', - 'application/vnd.oasis.opendocument.database' => 'odb', - 'application/vnd.oasis.opendocument.formula' => 'odf', - 'application/vnd.oasis.opendocument.formula-template' => 'odft', - 'application/vnd.oasis.opendocument.graphics' => 'odg', - 'application/vnd.oasis.opendocument.graphics-template' => 'otg', - 'application/vnd.oasis.opendocument.image' => 'odi', - 'application/vnd.oasis.opendocument.image-template' => 'oti', - 'application/vnd.oasis.opendocument.presentation' => 'odp', - 'application/vnd.oasis.opendocument.presentation-template' => 'otp', - 'application/vnd.oasis.opendocument.spreadsheet' => 'ods', - 'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots', - 'application/vnd.oasis.opendocument.text' => 'odt', - 'application/vnd.oasis.opendocument.text-master' => 'odm', - 'application/vnd.oasis.opendocument.text-template' => 'ott', - 'application/vnd.oasis.opendocument.text-web' => 'oth', - 'application/vnd.olpc-sugar' => 'xo', - 'application/vnd.oma.dd2+xml' => 'dd2', - 'application/vnd.openofficeorg.extension' => 'oxt', - 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', - 'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx', - 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx', - 'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx', - 'application/vnd.osgeo.mapguide.package' => 'mgp', - 'application/vnd.osgi.dp' => 'dp', - 'application/vnd.osgi.subsystem' => 'esa', - 'application/vnd.palm' => 'pdb', - 'application/vnd.pawaafile' => 'paw', - 'application/vnd.pg.format' => 'str', - 'application/vnd.pg.osasli' => 'ei6', - 'application/vnd.picsel' => 'efif', - 'application/vnd.pmi.widget' => 'wg', - 'application/vnd.pocketlearn' => 'plf', - 'application/vnd.powerbuilder6' => 'pbd', - 'application/vnd.previewsystems.box' => 'box', - 'application/vnd.proteus.magazine' => 'mgz', - 'application/vnd.publishare-delta-tree' => 'qps', - 'application/vnd.pvi.ptid1' => 'ptid', - 'application/vnd.quark.quarkxpress' => 'qxd', - 'application/vnd.realvnc.bed' => 'bed', - 'application/vnd.recordare.musicxml' => 'mxl', - 'application/vnd.recordare.musicxml+xml' => 'musicxml', - 'application/vnd.rig.cryptonote' => 'cryptonote', - 'application/vnd.rim.cod' => 'cod', - 'application/vnd.rn-realmedia' => 'rm', - 'application/vnd.rn-realmedia-vbr' => 'rmvb', - 'application/vnd.route66.link66+xml' => 'link66', - 'application/vnd.sailingtracker.track' => 'st', - 'application/vnd.seemail' => 'see', - 'application/vnd.sema' => 'sema', - 'application/vnd.semd' => 'semd', - 'application/vnd.semf' => 'semf', - 'application/vnd.shana.informed.formdata' => 'ifm', - 'application/vnd.shana.informed.formtemplate' => 'itp', - 'application/vnd.shana.informed.interchange' => 'iif', - 'application/vnd.shana.informed.package' => 'ipk', - 'application/vnd.simtech-mindmapper' => 'twd', - 'application/vnd.smaf' => 'mmf', - 'application/vnd.smart.teacher' => 'teacher', - 'application/vnd.solent.sdkm+xml' => 'sdkm', - 'application/vnd.spotfire.dxp' => 'dxp', - 'application/vnd.spotfire.sfs' => 'sfs', - 'application/vnd.stardivision.calc' => 'sdc', - 'application/vnd.stardivision.draw' => 'sda', - 'application/vnd.stardivision.impress' => 'sdd', - 'application/vnd.stardivision.math' => 'smf', - 'application/vnd.stardivision.writer' => 'sdw', - 'application/vnd.stardivision.writer-global' => 'sgl', - 'application/vnd.stepmania.package' => 'smzip', - 'application/vnd.stepmania.stepchart' => 'sm', - 'application/vnd.sun.xml.calc' => 'sxc', - 'application/vnd.sun.xml.calc.template' => 'stc', - 'application/vnd.sun.xml.draw' => 'sxd', - 'application/vnd.sun.xml.draw.template' => 'std', - 'application/vnd.sun.xml.impress' => 'sxi', - 'application/vnd.sun.xml.impress.template' => 'sti', - 'application/vnd.sun.xml.math' => 'sxm', - 'application/vnd.sun.xml.writer' => 'sxw', - 'application/vnd.sun.xml.writer.global' => 'sxg', - 'application/vnd.sun.xml.writer.template' => 'stw', - 'application/vnd.sus-calendar' => 'sus', - 'application/vnd.svd' => 'svd', - 'application/vnd.symbian.install' => 'sis', - 'application/vnd.syncml+xml' => 'xsm', - 'application/vnd.syncml.dm+wbxml' => 'bdm', - 'application/vnd.syncml.dm+xml' => 'xdm', - 'application/vnd.tao.intent-module-archive' => 'tao', - 'application/vnd.tcpdump.pcap' => 'pcap', - 'application/vnd.tmobile-livetv' => 'tmo', - 'application/vnd.trid.tpt' => 'tpt', - 'application/vnd.triscape.mxs' => 'mxs', - 'application/vnd.trueapp' => 'tra', - 'application/vnd.ufdl' => 'ufd', - 'application/vnd.uiq.theme' => 'utz', - 'application/vnd.umajin' => 'umj', - 'application/vnd.unity' => 'unityweb', - 'application/vnd.uoml+xml' => 'uoml', - 'application/vnd.vcx' => 'vcx', - 'application/vnd.visio' => 'vsd', - 'application/vnd.visionary' => 'vis', - 'application/vnd.vsf' => 'vsf', - 'application/vnd.wap.wbxml' => 'wbxml', - 'application/vnd.wap.wmlc' => 'wmlc', - 'application/vnd.wap.wmlscriptc' => 'wmlsc', - 'application/vnd.webturbo' => 'wtb', - 'application/vnd.wolfram.player' => 'nbp', - 'application/vnd.wordperfect' => 'wpd', - 'application/vnd.wqd' => 'wqd', - 'application/vnd.wt.stf' => 'stf', - 'application/vnd.xara' => 'xar', - 'application/vnd.xfdl' => 'xfdl', - 'application/vnd.yamaha.hv-dic' => 'hvd', - 'application/vnd.yamaha.hv-script' => 'hvs', - 'application/vnd.yamaha.hv-voice' => 'hvp', - 'application/vnd.yamaha.openscoreformat' => 'osf', - 'application/vnd.yamaha.openscoreformat.osfpvg+xml' => 'osfpvg', - 'application/vnd.yamaha.smaf-audio' => 'saf', - 'application/vnd.yamaha.smaf-phrase' => 'spf', - 'application/vnd.yellowriver-custom-menu' => 'cmp', - 'application/vnd.zul' => 'zir', - 'application/vnd.zzazz.deck+xml' => 'zaz', - 'application/voicexml+xml' => 'vxml', - 'application/widget' => 'wgt', - 'application/winhlp' => 'hlp', - 'application/wsdl+xml' => 'wsdl', - 'application/wspolicy+xml' => 'wspolicy', - 'application/x-7z-compressed' => '7z', - 'application/x-abiword' => 'abw', - 'application/x-ace-compressed' => 'ace', - 'application/x-apple-diskimage' => 'dmg', - 'application/x-authorware-bin' => 'aab', - 'application/x-authorware-map' => 'aam', - 'application/x-authorware-seg' => 'aas', - 'application/x-bcpio' => 'bcpio', - 'application/x-bittorrent' => 'torrent', - 'application/x-blorb' => 'blb', - 'application/x-bzip' => 'bz', - 'application/x-bzip2' => 'bz2', - 'application/x-cbr' => 'cbr', - 'application/x-cdlink' => 'vcd', - 'application/x-cfs-compressed' => 'cfs', - 'application/x-chat' => 'chat', - 'application/x-chess-pgn' => 'pgn', - 'application/x-conference' => 'nsc', - 'application/x-cpio' => 'cpio', - 'application/x-csh' => 'csh', - 'application/x-debian-package' => 'deb', - 'application/x-dgc-compressed' => 'dgc', - 'application/x-director' => 'dir', - 'application/x-doom' => 'wad', - 'application/x-dtbncx+xml' => 'ncx', - 'application/x-dtbook+xml' => 'dtb', - 'application/x-dtbresource+xml' => 'res', - 'application/x-dvi' => 'dvi', - 'application/x-envoy' => 'evy', - 'application/x-eva' => 'eva', - 'application/x-font-bdf' => 'bdf', - 'application/x-font-ghostscript' => 'gsf', - 'application/x-font-linux-psf' => 'psf', - 'application/x-font-otf' => 'otf', - 'application/x-font-pcf' => 'pcf', - 'application/x-font-snf' => 'snf', - 'application/x-font-ttf' => 'ttf', - 'application/x-font-type1' => 'pfa', - 'application/x-font-woff' => 'woff', - 'application/x-freearc' => 'arc', - 'application/x-futuresplash' => 'spl', - 'application/x-gca-compressed' => 'gca', - 'application/x-glulx' => 'ulx', - 'application/x-gnumeric' => 'gnumeric', - 'application/x-gramps-xml' => 'gramps', - 'application/x-gtar' => 'gtar', - 'application/x-hdf' => 'hdf', - 'application/x-install-instructions' => 'install', - 'application/x-iso9660-image' => 'iso', - 'application/x-java-jnlp-file' => 'jnlp', - 'application/x-latex' => 'latex', - 'application/x-lzh-compressed' => 'lzh', - 'application/x-mie' => 'mie', - 'application/x-mobipocket-ebook' => 'prc', - 'application/x-ms-application' => 'application', - 'application/x-ms-shortcut' => 'lnk', - 'application/x-ms-wmd' => 'wmd', - 'application/x-ms-wmz' => 'wmz', - 'application/x-ms-xbap' => 'xbap', - 'application/x-msaccess' => 'mdb', - 'application/x-msbinder' => 'obd', - 'application/x-mscardfile' => 'crd', - 'application/x-msclip' => 'clp', - 'application/x-msdownload' => 'exe', - 'application/x-msmediaview' => 'mvb', - 'application/x-msmetafile' => 'wmf', - 'application/x-msmoney' => 'mny', - 'application/x-mspublisher' => 'pub', - 'application/x-msschedule' => 'scd', - 'application/x-msterminal' => 'trm', - 'application/x-mswrite' => 'wri', - 'application/x-netcdf' => 'nc', - 'application/x-nzb' => 'nzb', - 'application/x-pkcs12' => 'p12', - 'application/x-pkcs7-certificates' => 'p7b', - 'application/x-pkcs7-certreqresp' => 'p7r', - 'application/x-rar-compressed' => 'rar', - 'application/x-rar' => 'rar', - 'application/x-research-info-systems' => 'ris', - 'application/x-sh' => 'sh', - 'application/x-shar' => 'shar', - 'application/x-shockwave-flash' => 'swf', - 'application/x-silverlight-app' => 'xap', - 'application/x-sql' => 'sql', - 'application/x-stuffit' => 'sit', - 'application/x-stuffitx' => 'sitx', - 'application/x-subrip' => 'srt', - 'application/x-sv4cpio' => 'sv4cpio', - 'application/x-sv4crc' => 'sv4crc', - 'application/x-t3vm-image' => 't3', - 'application/x-tads' => 'gam', - 'application/x-tar' => 'tar', - 'application/x-tcl' => 'tcl', - 'application/x-tex' => 'tex', - 'application/x-tex-tfm' => 'tfm', - 'application/x-texinfo' => 'texinfo', - 'application/x-tgif' => 'obj', - 'application/x-ustar' => 'ustar', - 'application/x-wais-source' => 'src', - 'application/x-x509-ca-cert' => 'der', - 'application/x-xfig' => 'fig', - 'application/x-xliff+xml' => 'xlf', - 'application/x-xpinstall' => 'xpi', - 'application/x-xz' => 'xz', - 'application/x-zip-compressed' => 'zip', - 'application/x-zmachine' => 'z1', - 'application/xaml+xml' => 'xaml', - 'application/xcap-diff+xml' => 'xdf', - 'application/xenc+xml' => 'xenc', - 'application/xhtml+xml' => 'xhtml', - 'application/xml' => 'xml', - 'application/xml-dtd' => 'dtd', - 'application/xop+xml' => 'xop', - 'application/xproc+xml' => 'xpl', - 'application/xslt+xml' => 'xslt', - 'application/xspf+xml' => 'xspf', - 'application/xv+xml' => 'mxml', - 'application/yang' => 'yang', - 'application/yin+xml' => 'yin', - 'application/zip' => 'zip', - 'audio/adpcm' => 'adp', - 'audio/basic' => 'au', - 'audio/midi' => 'mid', - 'audio/mp4' => 'm4a', - 'audio/mpeg' => 'mp3', - 'audio/ogg' => 'oga', - 'audio/s3m' => 's3m', - 'audio/silk' => 'sil', - 'audio/vnd.dece.audio' => 'uva', - 'audio/vnd.digital-winds' => 'eol', - 'audio/vnd.dra' => 'dra', - 'audio/vnd.dts' => 'dts', - 'audio/vnd.dts.hd' => 'dtshd', - 'audio/vnd.lucent.voice' => 'lvp', - 'audio/vnd.ms-playready.media.pya' => 'pya', - 'audio/vnd.nuera.ecelp4800' => 'ecelp4800', - 'audio/vnd.nuera.ecelp7470' => 'ecelp7470', - 'audio/vnd.nuera.ecelp9600' => 'ecelp9600', - 'audio/vnd.rip' => 'rip', - 'audio/webm' => 'weba', - 'audio/x-aac' => 'aac', - 'audio/x-aiff' => 'aif', - 'audio/x-caf' => 'caf', - 'audio/x-flac' => 'flac', - 'audio/x-matroska' => 'mka', - 'audio/x-mpegurl' => 'm3u', - 'audio/x-ms-wax' => 'wax', - 'audio/x-ms-wma' => 'wma', - 'audio/x-pn-realaudio' => 'ram', - 'audio/x-pn-realaudio-plugin' => 'rmp', - 'audio/x-wav' => 'wav', - 'audio/xm' => 'xm', - 'chemical/x-cdx' => 'cdx', - 'chemical/x-cif' => 'cif', - 'chemical/x-cmdf' => 'cmdf', - 'chemical/x-cml' => 'cml', - 'chemical/x-csml' => 'csml', - 'chemical/x-xyz' => 'xyz', - 'font/collection' => 'ttc', - 'font/otf' => 'otf', - 'font/ttf' => 'ttf', - 'font/woff' => 'woff', - 'font/woff2' => 'woff2', - 'image/bmp' => 'bmp', - 'image/x-ms-bmp' => 'bmp', - 'image/cgm' => 'cgm', - 'image/g3fax' => 'g3', - 'image/gif' => 'gif', - 'image/ief' => 'ief', - 'image/jpeg' => 'jpeg', - 'image/pjpeg' => 'jpeg', - 'image/ktx' => 'ktx', - 'image/png' => 'png', - 'image/prs.btif' => 'btif', - 'image/sgi' => 'sgi', - 'image/svg+xml' => 'svg', - 'image/tiff' => 'tiff', - 'image/vnd.adobe.photoshop' => 'psd', - 'image/vnd.dece.graphic' => 'uvi', - 'image/vnd.djvu' => 'djvu', - 'image/vnd.dvb.subtitle' => 'sub', - 'image/vnd.dwg' => 'dwg', - 'image/vnd.dxf' => 'dxf', - 'image/vnd.fastbidsheet' => 'fbs', - 'image/vnd.fpx' => 'fpx', - 'image/vnd.fst' => 'fst', - 'image/vnd.fujixerox.edmics-mmr' => 'mmr', - 'image/vnd.fujixerox.edmics-rlc' => 'rlc', - 'image/vnd.ms-modi' => 'mdi', - 'image/vnd.ms-photo' => 'wdp', - 'image/vnd.net-fpx' => 'npx', - 'image/vnd.wap.wbmp' => 'wbmp', - 'image/vnd.xiff' => 'xif', - 'image/webp' => 'webp', - 'image/x-3ds' => '3ds', - 'image/x-cmu-raster' => 'ras', - 'image/x-cmx' => 'cmx', - 'image/x-freehand' => 'fh', - 'image/x-icon' => 'ico', - 'image/x-mrsid-image' => 'sid', - 'image/x-pcx' => 'pcx', - 'image/x-pict' => 'pic', - 'image/x-portable-anymap' => 'pnm', - 'image/x-portable-bitmap' => 'pbm', - 'image/x-portable-graymap' => 'pgm', - 'image/x-portable-pixmap' => 'ppm', - 'image/x-rgb' => 'rgb', - 'image/x-tga' => 'tga', - 'image/x-xbitmap' => 'xbm', - 'image/x-xpixmap' => 'xpm', - 'image/x-xwindowdump' => 'xwd', - 'message/rfc822' => 'eml', - 'model/iges' => 'igs', - 'model/mesh' => 'msh', - 'model/vnd.collada+xml' => 'dae', - 'model/vnd.dwf' => 'dwf', - 'model/vnd.gdl' => 'gdl', - 'model/vnd.gtw' => 'gtw', - 'model/vnd.mts' => 'mts', - 'model/vnd.vtu' => 'vtu', - 'model/vrml' => 'wrl', - 'model/x3d+binary' => 'x3db', - 'model/x3d+vrml' => 'x3dv', - 'model/x3d+xml' => 'x3d', - 'text/cache-manifest' => 'appcache', - 'text/calendar' => 'ics', - 'text/css' => 'css', - 'text/csv' => 'csv', - 'text/html' => 'html', - 'text/n3' => 'n3', - 'text/plain' => 'txt', - 'text/prs.lines.tag' => 'dsc', - 'text/richtext' => 'rtx', - 'text/rtf' => 'rtf', - 'text/sgml' => 'sgml', - 'text/tab-separated-values' => 'tsv', - 'text/troff' => 't', - 'text/turtle' => 'ttl', - 'text/uri-list' => 'uri', - 'text/vcard' => 'vcard', - 'text/vnd.curl' => 'curl', - 'text/vnd.curl.dcurl' => 'dcurl', - 'text/vnd.curl.mcurl' => 'mcurl', - 'text/vnd.curl.scurl' => 'scurl', - 'text/vnd.dvb.subtitle' => 'sub', - 'text/vnd.fly' => 'fly', - 'text/vnd.fmi.flexstor' => 'flx', - 'text/vnd.graphviz' => 'gv', - 'text/vnd.in3d.3dml' => '3dml', - 'text/vnd.in3d.spot' => 'spot', - 'text/vnd.sun.j2me.app-descriptor' => 'jad', - 'text/vnd.wap.wml' => 'wml', - 'text/vnd.wap.wmlscript' => 'wmls', - 'text/vtt' => 'vtt', - 'text/x-asm' => 's', - 'text/x-c' => 'c', - 'text/x-fortran' => 'f', - 'text/x-java-source' => 'java', - 'text/x-nfo' => 'nfo', - 'text/x-opml' => 'opml', - 'text/x-pascal' => 'p', - 'text/x-setext' => 'etx', - 'text/x-sfv' => 'sfv', - 'text/x-uuencode' => 'uu', - 'text/x-vcalendar' => 'vcs', - 'text/x-vcard' => 'vcf', - 'video/3gpp' => '3gp', - 'video/3gpp2' => '3g2', - 'video/h261' => 'h261', - 'video/h263' => 'h263', - 'video/h264' => 'h264', - 'video/jpeg' => 'jpgv', - 'video/jpm' => 'jpm', - 'video/mj2' => 'mj2', - 'video/mp4' => 'mp4', - 'video/mpeg' => 'mpeg', - 'video/ogg' => 'ogv', - 'video/quicktime' => 'qt', - 'video/vnd.dece.hd' => 'uvh', - 'video/vnd.dece.mobile' => 'uvm', - 'video/vnd.dece.pd' => 'uvp', - 'video/vnd.dece.sd' => 'uvs', - 'video/vnd.dece.video' => 'uvv', - 'video/vnd.dvb.file' => 'dvb', - 'video/vnd.fvt' => 'fvt', - 'video/vnd.mpegurl' => 'mxu', - 'video/vnd.ms-playready.media.pyv' => 'pyv', - 'video/vnd.uvvu.mp4' => 'uvu', - 'video/vnd.vivo' => 'viv', - 'video/webm' => 'webm', - 'video/x-f4v' => 'f4v', - 'video/x-fli' => 'fli', - 'video/x-flv' => 'flv', - 'video/x-m4v' => 'm4v', - 'video/x-matroska' => 'mkv', - 'video/x-mng' => 'mng', - 'video/x-ms-asf' => 'asf', - 'video/x-ms-vob' => 'vob', - 'video/x-ms-wm' => 'wm', - 'video/x-ms-wmv' => 'wmv', - 'video/x-ms-wmx' => 'wmx', - 'video/x-ms-wvx' => 'wvx', - 'video/x-msvideo' => 'avi', - 'video/x-sgi-movie' => 'movie', - 'video/x-smv' => 'smv', - 'x-conference/x-cooltalk' => 'ice', - ]; - - /** - * {@inheritdoc} - */ - public function guess($mimeType) - { - if (isset($this->defaultExtensions[$mimeType])) { - return $this->defaultExtensions[$mimeType]; - } - - $lcMimeType = strtolower($mimeType); - - return isset($this->defaultExtensions[$lcMimeType]) ? $this->defaultExtensions[$lcMimeType] : null; - } -} diff --git a/lib/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php b/lib/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php deleted file mode 100644 index 8d971ed677..0000000000 --- a/lib/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php +++ /dev/null @@ -1,135 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File\MimeType; - -use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; -use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; - -/** - * A singleton mime type guesser. - * - * By default, all mime type guessers provided by the framework are installed - * (if available on the current OS/PHP setup). - * - * You can register custom guessers by calling the register() method on the - * singleton instance. Custom guessers are always called before any default ones. - * - * $guesser = MimeTypeGuesser::getInstance(); - * $guesser->register(new MyCustomMimeTypeGuesser()); - * - * If you want to change the order of the default guessers, just re-register your - * preferred one as a custom one. The last registered guesser is preferred over - * previously registered ones. - * - * Re-registering a built-in guesser also allows you to configure it: - * - * $guesser = MimeTypeGuesser::getInstance(); - * $guesser->register(new FileinfoMimeTypeGuesser('/path/to/magic/file')); - * - * @author Bernhard Schussek - */ -class MimeTypeGuesser implements MimeTypeGuesserInterface -{ - /** - * The singleton instance. - * - * @var MimeTypeGuesser - */ - private static $instance = null; - - /** - * All registered MimeTypeGuesserInterface instances. - * - * @var array - */ - protected $guessers = []; - - /** - * Returns the singleton instance. - * - * @return self - */ - public static function getInstance() - { - if (null === self::$instance) { - self::$instance = new self(); - } - - return self::$instance; - } - - /** - * Resets the singleton instance. - */ - public static function reset() - { - self::$instance = null; - } - - /** - * Registers all natively provided mime type guessers. - */ - private function __construct() - { - $this->register(new FileBinaryMimeTypeGuesser()); - $this->register(new FileinfoMimeTypeGuesser()); - } - - /** - * Registers a new mime type guesser. - * - * When guessing, this guesser is preferred over previously registered ones. - */ - public function register(MimeTypeGuesserInterface $guesser) - { - array_unshift($this->guessers, $guesser); - } - - /** - * Tries to guess the mime type of the given file. - * - * The file is passed to each registered mime type guesser in reverse order - * of their registration (last registered is queried first). Once a guesser - * returns a value that is not NULL, this method terminates and returns the - * value. - * - * @param string $path The path to the file - * - * @return string The mime type or NULL, if none could be guessed - * - * @throws \LogicException - * @throws FileNotFoundException - * @throws AccessDeniedException - */ - public function guess($path) - { - if (!is_file($path)) { - throw new FileNotFoundException($path); - } - - if (!is_readable($path)) { - throw new AccessDeniedException($path); - } - - foreach ($this->guessers as $guesser) { - if (null !== $mimeType = $guesser->guess($path)) { - return $mimeType; - } - } - - if (2 === \count($this->guessers) && !FileBinaryMimeTypeGuesser::isSupported() && !FileinfoMimeTypeGuesser::isSupported()) { - throw new \LogicException('Unable to guess the mime type as no guessers are available (Did you enable the php_fileinfo extension?).'); - } - - return null; - } -} diff --git a/lib/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php b/lib/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php deleted file mode 100644 index e46e78eef4..0000000000 --- a/lib/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File\MimeType; - -use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; -use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; - -/** - * Guesses the mime type of a file. - * - * @author Bernhard Schussek - */ -interface MimeTypeGuesserInterface -{ - /** - * Guesses the mime type of the file with the given path. - * - * @param string $path The path to the file - * - * @return string|null The mime type or NULL, if none could be guessed - * - * @throws FileNotFoundException If the file does not exist - * @throws AccessDeniedException If the file could not be read - */ - public function guess($path); -} diff --git a/lib/symfony/http-foundation/File/Stream.php b/lib/symfony/http-foundation/File/Stream.php index 69ae74c110..cef3e03977 100644 --- a/lib/symfony/http-foundation/File/Stream.php +++ b/lib/symfony/http-foundation/File/Stream.php @@ -20,7 +20,10 @@ class Stream extends File { /** * {@inheritdoc} + * + * @return int|false */ + #[\ReturnTypeWillChange] public function getSize() { return false; diff --git a/lib/symfony/http-foundation/File/UploadedFile.php b/lib/symfony/http-foundation/File/UploadedFile.php index 99132d1416..fcc6299138 100644 --- a/lib/symfony/http-foundation/File/UploadedFile.php +++ b/lib/symfony/http-foundation/File/UploadedFile.php @@ -11,9 +11,16 @@ namespace Symfony\Component\HttpFoundation\File; +use Symfony\Component\HttpFoundation\File\Exception\CannotWriteFileException; +use Symfony\Component\HttpFoundation\File\Exception\ExtensionFileException; use Symfony\Component\HttpFoundation\File\Exception\FileException; use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; -use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser; +use Symfony\Component\HttpFoundation\File\Exception\FormSizeFileException; +use Symfony\Component\HttpFoundation\File\Exception\IniSizeFileException; +use Symfony\Component\HttpFoundation\File\Exception\NoFileException; +use Symfony\Component\HttpFoundation\File\Exception\NoTmpDirFileException; +use Symfony\Component\HttpFoundation\File\Exception\PartialFileException; +use Symfony\Component\Mime\MimeTypes; /** * A file uploaded through a form. @@ -27,7 +34,6 @@ class UploadedFile extends File private $test; private $originalName; private $mimeType; - private $size; private $error; /** @@ -47,7 +53,6 @@ class UploadedFile extends File * @param string $path The full temporary path to the file * @param string $originalName The original file name of the uploaded file * @param string|null $mimeType The type of the file as provided by PHP; null defaults to application/octet-stream - * @param int|null $size The file size provided by the uploader * @param int|null $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants); null defaults to UPLOAD_ERR_OK * @param bool $test Whether the test mode is active * Local files are used in test mode hence the code should not enforce HTTP uploads @@ -55,13 +60,12 @@ class UploadedFile extends File * @throws FileException If file_uploads is disabled * @throws FileNotFoundException If the file does not exist */ - public function __construct($path, $originalName, $mimeType = null, $size = null, $error = null, $test = false) + public function __construct(string $path, string $originalName, string $mimeType = null, int $error = null, bool $test = false) { $this->originalName = $this->getName($originalName); $this->mimeType = $mimeType ?: 'application/octet-stream'; - $this->size = $size; $this->error = $error ?: \UPLOAD_ERR_OK; - $this->test = (bool) $test; + $this->test = $test; parent::__construct($path, \UPLOAD_ERR_OK === $this->error); } @@ -72,7 +76,7 @@ class UploadedFile extends File * It is extracted from the request from which the file has been uploaded. * Then it should not be considered as a safe value. * - * @return string The original name + * @return string */ public function getClientOriginalName() { @@ -85,7 +89,7 @@ class UploadedFile extends File * It is extracted from the original file name that was uploaded. * Then it should not be considered as a safe value. * - * @return string The extension + * @return string */ public function getClientOriginalExtension() { @@ -101,7 +105,7 @@ class UploadedFile extends File * For a trusted mime type, use getMimeType() instead (which guesses the mime * type based on the file content). * - * @return string The mime type + * @return string * * @see getMimeType() */ @@ -122,30 +126,18 @@ class UploadedFile extends File * For a trusted extension, use guessExtension() instead (which guesses * the extension based on the guessed mime type for the file). * - * @return string|null The guessed extension or null if it cannot be guessed + * @return string|null * * @see guessExtension() * @see getClientMimeType() */ public function guessClientExtension() { - $type = $this->getClientMimeType(); - $guesser = ExtensionGuesser::getInstance(); + if (!class_exists(MimeTypes::class)) { + throw new \LogicException('You cannot guess the extension as the Mime component is not installed. Try running "composer require symfony/mime".'); + } - return $guesser->guess($type); - } - - /** - * Returns the file size. - * - * It is extracted from the request from which the file has been uploaded. - * Then it should not be considered as a safe value. - * - * @return int|null The file size - */ - public function getClientSize() - { - return $this->size; + return MimeTypes::getDefault()->getExtensions($this->getClientMimeType())[0] ?? null; } /** @@ -154,7 +146,7 @@ class UploadedFile extends File * If the upload was successful, the constant UPLOAD_ERR_OK is returned. * Otherwise one of the other UPLOAD_ERR_XXX constants is returned. * - * @return int The upload error + * @return int */ public function getError() { @@ -162,9 +154,9 @@ class UploadedFile extends File } /** - * Returns whether the file was uploaded successfully. + * Returns whether the file has been uploaded with HTTP and no error occurred. * - * @return bool True if the file has been uploaded with HTTP and no error occurred + * @return bool */ public function isValid() { @@ -176,14 +168,11 @@ class UploadedFile extends File /** * Moves the file to a new location. * - * @param string $directory The destination folder - * @param string $name The new file name - * - * @return File A File object representing the new file + * @return File * * @throws FileException if, for any reason, the file could not have been moved */ - public function move($directory, $name = null) + public function move(string $directory, string $name = null) { if ($this->isValid()) { if ($this->test) { @@ -193,8 +182,11 @@ class UploadedFile extends File $target = $this->getTargetFile($directory, $name); set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); - $moved = move_uploaded_file($this->getPathname(), $target); - restore_error_handler(); + try { + $moved = move_uploaded_file($this->getPathname(), $target); + } finally { + restore_error_handler(); + } if (!$moved) { throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error))); } @@ -204,18 +196,35 @@ class UploadedFile extends File return $target; } + switch ($this->error) { + case \UPLOAD_ERR_INI_SIZE: + throw new IniSizeFileException($this->getErrorMessage()); + case \UPLOAD_ERR_FORM_SIZE: + throw new FormSizeFileException($this->getErrorMessage()); + case \UPLOAD_ERR_PARTIAL: + throw new PartialFileException($this->getErrorMessage()); + case \UPLOAD_ERR_NO_FILE: + throw new NoFileException($this->getErrorMessage()); + case \UPLOAD_ERR_CANT_WRITE: + throw new CannotWriteFileException($this->getErrorMessage()); + case \UPLOAD_ERR_NO_TMP_DIR: + throw new NoTmpDirFileException($this->getErrorMessage()); + case \UPLOAD_ERR_EXTENSION: + throw new ExtensionFileException($this->getErrorMessage()); + } + throw new FileException($this->getErrorMessage()); } /** * Returns the maximum size of an uploaded file as configured in php.ini. * - * @return int The maximum size of an uploaded file in bytes + * @return int|float The maximum size of an uploaded file in bytes (returns float if size > PHP_INT_MAX) */ public static function getMaxFilesize() { - $sizePostMax = self::parseFilesize(ini_get('post_max_size')); - $sizeUploadMax = self::parseFilesize(ini_get('upload_max_filesize')); + $sizePostMax = self::parseFilesize(\ini_get('post_max_size')); + $sizeUploadMax = self::parseFilesize(\ini_get('upload_max_filesize')); return min($sizePostMax ?: \PHP_INT_MAX, $sizeUploadMax ?: \PHP_INT_MAX); } @@ -223,9 +232,9 @@ class UploadedFile extends File /** * Returns the given size from an ini value in bytes. * - * @return int The given size in bytes + * @return int|float Returns float if size > PHP_INT_MAX */ - private static function parseFilesize($size) + private static function parseFilesize(string $size) { if ('' === $size) { return 0; @@ -234,9 +243,9 @@ class UploadedFile extends File $size = strtolower($size); $max = ltrim($size, '+'); - if (0 === strpos($max, '0x')) { + if (str_starts_with($max, '0x')) { $max = \intval($max, 16); - } elseif (0 === strpos($max, '0')) { + } elseif (str_starts_with($max, '0')) { $max = \intval($max, 8); } else { $max = (int) $max; @@ -244,11 +253,11 @@ class UploadedFile extends File switch (substr($size, -1)) { case 't': $max *= 1024; - // no break + // no break case 'g': $max *= 1024; - // no break + // no break case 'm': $max *= 1024; - // no break + // no break case 'k': $max *= 1024; } @@ -258,7 +267,7 @@ class UploadedFile extends File /** * Returns an informative upload error message. * - * @return string The error message regarding the specified error code + * @return string */ public function getErrorMessage() { @@ -274,7 +283,7 @@ class UploadedFile extends File $errorCode = $this->error; $maxFilesize = \UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0; - $message = isset($errors[$errorCode]) ? $errors[$errorCode] : 'The file "%s" was not uploaded due to an unknown error.'; + $message = $errors[$errorCode] ?? 'The file "%s" was not uploaded due to an unknown error.'; return sprintf($message, $this->getClientOriginalName(), $maxFilesize); } diff --git a/lib/symfony/http-foundation/FileBag.php b/lib/symfony/http-foundation/FileBag.php index e2acca4ea3..ff5ab7778f 100644 --- a/lib/symfony/http-foundation/FileBag.php +++ b/lib/symfony/http-foundation/FileBag.php @@ -21,10 +21,10 @@ use Symfony\Component\HttpFoundation\File\UploadedFile; */ class FileBag extends ParameterBag { - private static $fileKeys = ['error', 'name', 'size', 'tmp_name', 'type']; + private const FILE_KEYS = ['error', 'name', 'size', 'tmp_name', 'type']; /** - * @param array $parameters An array of HTTP files + * @param array|UploadedFile[] $parameters An array of HTTP files */ public function __construct(array $parameters = []) { @@ -43,7 +43,7 @@ class FileBag extends ParameterBag /** * {@inheritdoc} */ - public function set($key, $value) + public function set(string $key, $value) { if (!\is_array($value) && !$value instanceof UploadedFile) { throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.'); @@ -67,7 +67,7 @@ class FileBag extends ParameterBag * * @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information * - * @return UploadedFile[]|UploadedFile|null A (multi-dimensional) array of UploadedFile instances + * @return UploadedFile[]|UploadedFile|null */ protected function convertFileInformation($file) { @@ -75,22 +75,20 @@ class FileBag extends ParameterBag return $file; } - if (\is_array($file)) { - $file = $this->fixPhpFilesArray($file); - $keys = array_keys($file); - sort($keys); + $file = $this->fixPhpFilesArray($file); + $keys = array_keys($file); + sort($keys); - if ($keys == self::$fileKeys) { - if (\UPLOAD_ERR_NO_FILE == $file['error']) { - $file = null; - } else { - $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['size'], $file['error']); - } + if (self::FILE_KEYS == $keys) { + if (\UPLOAD_ERR_NO_FILE == $file['error']) { + $file = null; } else { - $file = array_map([$this, 'convertFileInformation'], $file); - if (array_keys($keys) === $keys) { - $file = array_filter($file); - } + $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['error'], false); + } + } else { + $file = array_map(function ($v) { return $v instanceof UploadedFile || \is_array($v) ? $this->convertFileInformation($v) : $v; }, $file); + if (array_keys($keys) === $keys) { + $file = array_filter($file); } } @@ -109,21 +107,21 @@ class FileBag extends ParameterBag * It's safe to pass an already converted array, in which case this method * just returns the original array unmodified. * - * @param array $data - * * @return array */ - protected function fixPhpFilesArray($data) + protected function fixPhpFilesArray(array $data) { + // Remove extra key added by PHP 8.1. + unset($data['full_path']); $keys = array_keys($data); sort($keys); - if (self::$fileKeys != $keys || !isset($data['name']) || !\is_array($data['name'])) { + if (self::FILE_KEYS != $keys || !isset($data['name']) || !\is_array($data['name'])) { return $data; } $files = $data; - foreach (self::$fileKeys as $k) { + foreach (self::FILE_KEYS as $k) { unset($files[$k]); } diff --git a/lib/symfony/http-foundation/HeaderBag.php b/lib/symfony/http-foundation/HeaderBag.php index 301ec9cf53..4683a68409 100644 --- a/lib/symfony/http-foundation/HeaderBag.php +++ b/lib/symfony/http-foundation/HeaderBag.php @@ -15,15 +15,20 @@ namespace Symfony\Component\HttpFoundation; * HeaderBag is a container for HTTP headers. * * @author Fabien Potencier + * + * @implements \IteratorAggregate> */ class HeaderBag implements \IteratorAggregate, \Countable { + protected const UPPER = '_ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + protected const LOWER = '-abcdefghijklmnopqrstuvwxyz'; + + /** + * @var array> + */ protected $headers = []; protected $cacheControl = []; - /** - * @param array $headers An array of HTTP headers - */ public function __construct(array $headers = []) { foreach ($headers as $key => $values) { @@ -34,7 +39,7 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Returns the headers as a string. * - * @return string The headers + * @return string */ public function __toString() { @@ -46,7 +51,7 @@ class HeaderBag implements \IteratorAggregate, \Countable $max = max(array_map('strlen', array_keys($headers))) + 1; $content = ''; foreach ($headers as $name => $values) { - $name = implode('-', array_map('ucfirst', explode('-', $name))); + $name = ucwords($name, '-'); foreach ($values as $value) { $content .= sprintf("%-{$max}s %s\r\n", $name.':', $value); } @@ -58,17 +63,23 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Returns the headers. * - * @return array An array of headers + * @param string|null $key The name of the headers to return or null to get them all + * + * @return array>|array */ - public function all() + public function all(string $key = null) { + if (null !== $key) { + return $this->headers[strtr($key, self::UPPER, self::LOWER)] ?? []; + } + return $this->headers; } /** * Returns the parameter keys. * - * @return array An array of parameter keys + * @return string[] */ public function keys() { @@ -77,8 +88,6 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Replaces the current HTTP headers by a new set. - * - * @param array $headers An array of HTTP headers */ public function replace(array $headers = []) { @@ -88,8 +97,6 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Adds new headers the current HTTP headers set. - * - * @param array $headers An array of HTTP headers */ public function add(array $headers) { @@ -99,52 +106,34 @@ class HeaderBag implements \IteratorAggregate, \Countable } /** - * Returns a header value by name. + * Returns the first header by name or the default one. * - * @param string $key The header name - * @param string|null $default The default value - * @param bool $first Whether to return the first value or all header values - * - * @return string|string[]|null The first header value or default value if $first is true, an array of values otherwise + * @return string|null */ - public function get($key, $default = null, $first = true) + public function get(string $key, string $default = null) { - $key = str_replace('_', '-', strtolower($key)); - $headers = $this->all(); + $headers = $this->all($key); - if (!\array_key_exists($key, $headers)) { - if (null === $default) { - return $first ? null : []; - } - - return $first ? $default : [$default]; + if (!$headers) { + return $default; } - if ($first) { - if (!$headers[$key]) { - return $default; - } - - if (null === $headers[$key][0]) { - return null; - } - - return (string) $headers[$key][0]; + if (null === $headers[0]) { + return null; } - return $headers[$key]; + return (string) $headers[0]; } /** * Sets a header by name. * - * @param string $key The key - * @param string|string[] $values The value or an array of values - * @param bool $replace Whether to replace the actual value or not (true by default) + * @param string|string[]|null $values The value or an array of values + * @param bool $replace Whether to replace the actual value or not (true by default) */ - public function set($key, $values, $replace = true) + public function set(string $key, $values, bool $replace = true) { - $key = str_replace('_', '-', strtolower($key)); + $key = strtr($key, self::UPPER, self::LOWER); if (\is_array($values)) { $values = array_values($values); @@ -170,36 +159,29 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Returns true if the HTTP header is defined. * - * @param string $key The HTTP header - * - * @return bool true if the parameter exists, false otherwise + * @return bool */ - public function has($key) + public function has(string $key) { - return \array_key_exists(str_replace('_', '-', strtolower($key)), $this->all()); + return \array_key_exists(strtr($key, self::UPPER, self::LOWER), $this->all()); } /** * Returns true if the given HTTP header contains the given value. * - * @param string $key The HTTP header name - * @param string $value The HTTP value - * - * @return bool true if the value is contained in the header, false otherwise + * @return bool */ - public function contains($key, $value) + public function contains(string $key, string $value) { - return \in_array($value, $this->get($key, null, false)); + return \in_array($value, $this->all($key)); } /** * Removes a header. - * - * @param string $key The HTTP header name */ - public function remove($key) + public function remove(string $key) { - $key = str_replace('_', '-', strtolower($key)); + $key = strtr($key, self::UPPER, self::LOWER); unset($this->headers[$key]); @@ -211,14 +193,11 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Returns the HTTP header value converted to a date. * - * @param string $key The parameter key - * @param \DateTime $default The default value - * - * @return \DateTime|null The parsed DateTime or the default value if the header does not exist + * @return \DateTimeInterface|null * * @throws \RuntimeException When the HTTP header is not parseable */ - public function getDate($key, \DateTime $default = null) + public function getDate(string $key, \DateTime $default = null) { if (null === $value = $this->get($key)) { return $default; @@ -234,10 +213,9 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Adds a custom Cache-Control directive. * - * @param string $key The Cache-Control directive name - * @param mixed $value The Cache-Control directive value + * @param bool|string $value The Cache-Control directive value */ - public function addCacheControlDirective($key, $value = true) + public function addCacheControlDirective(string $key, $value = true) { $this->cacheControl[$key] = $value; @@ -247,11 +225,9 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Returns true if the Cache-Control directive is defined. * - * @param string $key The Cache-Control directive - * - * @return bool true if the directive exists, false otherwise + * @return bool */ - public function hasCacheControlDirective($key) + public function hasCacheControlDirective(string $key) { return \array_key_exists($key, $this->cacheControl); } @@ -259,21 +235,17 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Returns a Cache-Control directive value by name. * - * @param string $key The directive name - * - * @return mixed|null The directive value if defined, null otherwise + * @return bool|string|null */ - public function getCacheControlDirective($key) + public function getCacheControlDirective(string $key) { - return \array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null; + return $this->cacheControl[$key] ?? null; } /** * Removes a Cache-Control directive. - * - * @param string $key The Cache-Control directive */ - public function removeCacheControlDirective($key) + public function removeCacheControlDirective(string $key) { unset($this->cacheControl[$key]); @@ -283,8 +255,9 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Returns an iterator for headers. * - * @return \ArrayIterator An \ArrayIterator instance + * @return \ArrayIterator> */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->headers); @@ -293,8 +266,9 @@ class HeaderBag implements \IteratorAggregate, \Countable /** * Returns the number of headers. * - * @return int The number of headers + * @return int */ + #[\ReturnTypeWillChange] public function count() { return \count($this->headers); @@ -302,38 +276,20 @@ class HeaderBag implements \IteratorAggregate, \Countable protected function getCacheControlHeader() { - $parts = []; ksort($this->cacheControl); - foreach ($this->cacheControl as $key => $value) { - if (true === $value) { - $parts[] = $key; - } else { - if (preg_match('#[^a-zA-Z0-9._-]#', $value)) { - $value = '"'.$value.'"'; - } - $parts[] = "$key=$value"; - } - } - - return implode(', ', $parts); + return HeaderUtils::toString($this->cacheControl, ','); } /** * Parses a Cache-Control HTTP header. * - * @param string $header The value of the Cache-Control HTTP header - * - * @return array An array representing the attribute values + * @return array */ - protected function parseCacheControl($header) + protected function parseCacheControl(string $header) { - $cacheControl = []; - preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, \PREG_SET_ORDER); - foreach ($matches as $match) { - $cacheControl[strtolower($match[1])] = isset($match[3]) ? $match[3] : (isset($match[2]) ? $match[2] : true); - } + $parts = HeaderUtils::split($header, ',='); - return $cacheControl; + return HeaderUtils::combine($parts); } } diff --git a/lib/symfony/http-foundation/HeaderUtils.php b/lib/symfony/http-foundation/HeaderUtils.php new file mode 100644 index 0000000000..1d56be0805 --- /dev/null +++ b/lib/symfony/http-foundation/HeaderUtils.php @@ -0,0 +1,293 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * HTTP header utility functions. + * + * @author Christian Schmidt + */ +class HeaderUtils +{ + public const DISPOSITION_ATTACHMENT = 'attachment'; + public const DISPOSITION_INLINE = 'inline'; + + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Splits an HTTP header by one or more separators. + * + * Example: + * + * HeaderUtils::split("da, en-gb;q=0.8", ",;") + * // => ['da'], ['en-gb', 'q=0.8']] + * + * @param string $separators List of characters to split on, ordered by + * precedence, e.g. ",", ";=", or ",;=" + * + * @return array Nested array with as many levels as there are characters in + * $separators + */ + public static function split(string $header, string $separators): array + { + $quotedSeparators = preg_quote($separators, '/'); + + preg_match_all(' + / + (?!\s) + (?: + # quoted-string + "(?:[^"\\\\]|\\\\.)*(?:"|\\\\|$) + | + # token + [^"'.$quotedSeparators.']+ + )+ + (?['.$quotedSeparators.']) + \s* + /x', trim($header), $matches, \PREG_SET_ORDER); + + return self::groupParts($matches, $separators); + } + + /** + * Combines an array of arrays into one associative array. + * + * Each of the nested arrays should have one or two elements. The first + * value will be used as the keys in the associative array, and the second + * will be used as the values, or true if the nested array only contains one + * element. Array keys are lowercased. + * + * Example: + * + * HeaderUtils::combine([["foo", "abc"], ["bar"]]) + * // => ["foo" => "abc", "bar" => true] + */ + public static function combine(array $parts): array + { + $assoc = []; + foreach ($parts as $part) { + $name = strtolower($part[0]); + $value = $part[1] ?? true; + $assoc[$name] = $value; + } + + return $assoc; + } + + /** + * Joins an associative array into a string for use in an HTTP header. + * + * The key and value of each entry are joined with "=", and all entries + * are joined with the specified separator and an additional space (for + * readability). Values are quoted if necessary. + * + * Example: + * + * HeaderUtils::toString(["foo" => "abc", "bar" => true, "baz" => "a b c"], ",") + * // => 'foo=abc, bar, baz="a b c"' + */ + public static function toString(array $assoc, string $separator): string + { + $parts = []; + foreach ($assoc as $name => $value) { + if (true === $value) { + $parts[] = $name; + } else { + $parts[] = $name.'='.self::quote($value); + } + } + + return implode($separator.' ', $parts); + } + + /** + * Encodes a string as a quoted string, if necessary. + * + * If a string contains characters not allowed by the "token" construct in + * the HTTP specification, it is backslash-escaped and enclosed in quotes + * to match the "quoted-string" construct. + */ + public static function quote(string $s): string + { + if (preg_match('/^[a-z0-9!#$%&\'*.^_`|~-]+$/i', $s)) { + return $s; + } + + return '"'.addcslashes($s, '"\\"').'"'; + } + + /** + * Decodes a quoted string. + * + * If passed an unquoted string that matches the "token" construct (as + * defined in the HTTP specification), it is passed through verbatimly. + */ + public static function unquote(string $s): string + { + return preg_replace('/\\\\(.)|"/', '$1', $s); + } + + /** + * Generates an HTTP Content-Disposition field-value. + * + * @param string $disposition One of "inline" or "attachment" + * @param string $filename A unicode string + * @param string $filenameFallback A string containing only ASCII characters that + * is semantically equivalent to $filename. If the filename is already ASCII, + * it can be omitted, or just copied from $filename + * + * @throws \InvalidArgumentException + * + * @see RFC 6266 + */ + public static function makeDisposition(string $disposition, string $filename, string $filenameFallback = ''): string + { + if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) { + throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE)); + } + + if ('' === $filenameFallback) { + $filenameFallback = $filename; + } + + // filenameFallback is not ASCII. + if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) { + throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.'); + } + + // percent characters aren't safe in fallback. + if (str_contains($filenameFallback, '%')) { + throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.'); + } + + // path separators aren't allowed in either. + if (str_contains($filename, '/') || str_contains($filename, '\\') || str_contains($filenameFallback, '/') || str_contains($filenameFallback, '\\')) { + throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.'); + } + + $params = ['filename' => $filenameFallback]; + if ($filename !== $filenameFallback) { + $params['filename*'] = "utf-8''".rawurlencode($filename); + } + + return $disposition.'; '.self::toString($params, ';'); + } + + /** + * Like parse_str(), but preserves dots in variable names. + */ + public static function parseQuery(string $query, bool $ignoreBrackets = false, string $separator = '&'): array + { + $q = []; + + foreach (explode($separator, $query) as $v) { + if (false !== $i = strpos($v, "\0")) { + $v = substr($v, 0, $i); + } + + if (false === $i = strpos($v, '=')) { + $k = urldecode($v); + $v = ''; + } else { + $k = urldecode(substr($v, 0, $i)); + $v = substr($v, $i); + } + + if (false !== $i = strpos($k, "\0")) { + $k = substr($k, 0, $i); + } + + $k = ltrim($k, ' '); + + if ($ignoreBrackets) { + $q[$k][] = urldecode(substr($v, 1)); + + continue; + } + + if (false === $i = strpos($k, '[')) { + $q[] = bin2hex($k).$v; + } else { + $q[] = bin2hex(substr($k, 0, $i)).rawurlencode(substr($k, $i)).$v; + } + } + + if ($ignoreBrackets) { + return $q; + } + + parse_str(implode('&', $q), $q); + + $query = []; + + foreach ($q as $k => $v) { + if (false !== $i = strpos($k, '_')) { + $query[substr_replace($k, hex2bin(substr($k, 0, $i)).'[', 0, 1 + $i)] = $v; + } else { + $query[hex2bin($k)] = $v; + } + } + + return $query; + } + + private static function groupParts(array $matches, string $separators, bool $first = true): array + { + $separator = $separators[0]; + $partSeparators = substr($separators, 1); + + $i = 0; + $partMatches = []; + $previousMatchWasSeparator = false; + foreach ($matches as $match) { + if (!$first && $previousMatchWasSeparator && isset($match['separator']) && $match['separator'] === $separator) { + $previousMatchWasSeparator = true; + $partMatches[$i][] = $match; + } elseif (isset($match['separator']) && $match['separator'] === $separator) { + $previousMatchWasSeparator = true; + ++$i; + } else { + $previousMatchWasSeparator = false; + $partMatches[$i][] = $match; + } + } + + $parts = []; + if ($partSeparators) { + foreach ($partMatches as $matches) { + $parts[] = self::groupParts($matches, $partSeparators, false); + } + } else { + foreach ($partMatches as $matches) { + $parts[] = self::unquote($matches[0][0]); + } + + if (!$first && 2 < \count($parts)) { + $parts = [ + $parts[0], + implode($separator, \array_slice($parts, 1)), + ]; + } + } + + return $parts; + } +} diff --git a/lib/symfony/http-foundation/InputBag.php b/lib/symfony/http-foundation/InputBag.php new file mode 100644 index 0000000000..a9d3cd82af --- /dev/null +++ b/lib/symfony/http-foundation/InputBag.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Exception\BadRequestException; + +/** + * InputBag is a container for user input values such as $_GET, $_POST, $_REQUEST, and $_COOKIE. + * + * @author Saif Eddin Gmati + */ +final class InputBag extends ParameterBag +{ + /** + * Returns a scalar input value by name. + * + * @param string|int|float|bool|null $default The default value if the input key does not exist + * + * @return string|int|float|bool|null + */ + public function get(string $key, $default = null) + { + if (null !== $default && !\is_scalar($default) && !(\is_object($default) && method_exists($default, '__toString'))) { + trigger_deprecation('symfony/http-foundation', '5.1', 'Passing a non-scalar value as 2nd argument to "%s()" is deprecated, pass a scalar or null instead.', __METHOD__); + } + + $value = parent::get($key, $this); + + if (null !== $value && $this !== $value && !\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + trigger_deprecation('symfony/http-foundation', '5.1', 'Retrieving a non-scalar value from "%s()" is deprecated, and will throw a "%s" exception in Symfony 6.0, use "%s::all($key)" instead.', __METHOD__, BadRequestException::class, __CLASS__); + } + + return $this === $value ? $default : $value; + } + + /** + * {@inheritdoc} + */ + public function all(string $key = null): array + { + return parent::all($key); + } + + /** + * Replaces the current input values by a new set. + */ + public function replace(array $inputs = []) + { + $this->parameters = []; + $this->add($inputs); + } + + /** + * Adds input values. + */ + public function add(array $inputs = []) + { + foreach ($inputs as $input => $value) { + $this->set($input, $value); + } + } + + /** + * Sets an input by name. + * + * @param string|int|float|bool|array|null $value + */ + public function set(string $key, $value) + { + if (null !== $value && !\is_scalar($value) && !\is_array($value) && !method_exists($value, '__toString')) { + trigger_deprecation('symfony/http-foundation', '5.1', 'Passing "%s" as a 2nd Argument to "%s()" is deprecated, pass a scalar, array, or null instead.', get_debug_type($value), __METHOD__); + } + + $this->parameters[$key] = $value; + } + + /** + * {@inheritdoc} + */ + public function filter(string $key, $default = null, int $filter = \FILTER_DEFAULT, $options = []) + { + $value = $this->has($key) ? $this->all()[$key] : $default; + + // Always turn $options into an array - this allows filter_var option shortcuts. + if (!\is_array($options) && $options) { + $options = ['flags' => $options]; + } + + if (\is_array($value) && !(($options['flags'] ?? 0) & (\FILTER_REQUIRE_ARRAY | \FILTER_FORCE_ARRAY))) { + trigger_deprecation('symfony/http-foundation', '5.1', 'Filtering an array value with "%s()" without passing the FILTER_REQUIRE_ARRAY or FILTER_FORCE_ARRAY flag is deprecated', __METHOD__); + + if (!isset($options['flags'])) { + $options['flags'] = \FILTER_REQUIRE_ARRAY; + } + } + + if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) { + trigger_deprecation('symfony/http-foundation', '5.2', 'Not passing a Closure together with FILTER_CALLBACK to "%s()" is deprecated. Wrap your filter in a closure instead.', __METHOD__); + // throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null))); + } + + return filter_var($value, $filter, $options); + } +} diff --git a/lib/symfony/http-foundation/IpUtils.php b/lib/symfony/http-foundation/IpUtils.php index a83d32493f..24111f1ec4 100644 --- a/lib/symfony/http-foundation/IpUtils.php +++ b/lib/symfony/http-foundation/IpUtils.php @@ -30,13 +30,18 @@ class IpUtils /** * Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets. * - * @param string $requestIp IP to check - * @param string|array $ips List of IPs or subnets (can be a string if only a single one) + * @param string|array $ips List of IPs or subnets (can be a string if only a single one) * - * @return bool Whether the IP is valid + * @return bool */ - public static function checkIp($requestIp, $ips) + public static function checkIp(?string $requestIp, $ips) { + if (null === $requestIp) { + trigger_deprecation('symfony/http-foundation', '5.4', 'Passing null as $requestIp to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + return false; + } + if (!\is_array($ips)) { $ips = [$ips]; } @@ -56,13 +61,18 @@ class IpUtils * Compares two IPv4 addresses. * In case a subnet is given, it checks if it contains the request IP. * - * @param string $requestIp IPv4 address to check - * @param string $ip IPv4 address or subnet in CIDR notation + * @param string $ip IPv4 address or subnet in CIDR notation * * @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet */ - public static function checkIp4($requestIp, $ip) + public static function checkIp4(?string $requestIp, string $ip) { + if (null === $requestIp) { + trigger_deprecation('symfony/http-foundation', '5.4', 'Passing null as $requestIp to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + return false; + } + $cacheKey = $requestIp.'-'.$ip; if (isset(self::$checkedIps[$cacheKey])) { return self::$checkedIps[$cacheKey]; @@ -72,8 +82,8 @@ class IpUtils return self::$checkedIps[$cacheKey] = false; } - if (false !== strpos($ip, '/')) { - list($address, $netmask) = explode('/', $ip, 2); + if (str_contains($ip, '/')) { + [$address, $netmask] = explode('/', $ip, 2); if ('0' === $netmask) { return self::$checkedIps[$cacheKey] = filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4); @@ -102,15 +112,20 @@ class IpUtils * * @see https://github.com/dsp/v6tools * - * @param string $requestIp IPv6 address to check - * @param string $ip IPv6 address or subnet in CIDR notation + * @param string $ip IPv6 address or subnet in CIDR notation * - * @return bool Whether the IP is valid + * @return bool * * @throws \RuntimeException When IPV6 support is not enabled */ - public static function checkIp6($requestIp, $ip) + public static function checkIp6(?string $requestIp, string $ip) { + if (null === $requestIp) { + trigger_deprecation('symfony/http-foundation', '5.4', 'Passing null as $requestIp to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + return false; + } + $cacheKey = $requestIp.'-'.$ip; if (isset(self::$checkedIps[$cacheKey])) { return self::$checkedIps[$cacheKey]; @@ -120,8 +135,8 @@ class IpUtils throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".'); } - if (false !== strpos($ip, '/')) { - list($address, $netmask) = explode('/', $ip, 2); + if (str_contains($ip, '/')) { + [$address, $netmask] = explode('/', $ip, 2); if ('0' === $netmask) { return (bool) unpack('n*', @inet_pton($address)); @@ -145,7 +160,7 @@ class IpUtils for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) { $left = $netmask - 16 * ($i - 1); $left = ($left <= 16) ? $left : 16; - $mask = ~(0xffff >> $left) & 0xffff; + $mask = ~(0xFFFF >> $left) & 0xFFFF; if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) { return self::$checkedIps[$cacheKey] = false; } @@ -153,4 +168,36 @@ class IpUtils return self::$checkedIps[$cacheKey] = true; } + + /** + * Anonymizes an IP/IPv6. + * + * Removes the last byte for v4 and the last 8 bytes for v6 IPs + */ + public static function anonymize(string $ip): string + { + $wrappedIPv6 = false; + if ('[' === substr($ip, 0, 1) && ']' === substr($ip, -1, 1)) { + $wrappedIPv6 = true; + $ip = substr($ip, 1, -1); + } + + $packedAddress = inet_pton($ip); + if (4 === \strlen($packedAddress)) { + $mask = '255.255.255.0'; + } elseif ($ip === inet_ntop($packedAddress & inet_pton('::ffff:ffff:ffff'))) { + $mask = '::ffff:ffff:ff00'; + } elseif ($ip === inet_ntop($packedAddress & inet_pton('::ffff:ffff'))) { + $mask = '::ffff:ff00'; + } else { + $mask = 'ffff:ffff:ffff:ffff:0000:0000:0000:0000'; + } + $ip = inet_ntop($packedAddress & inet_pton($mask)); + + if ($wrappedIPv6) { + $ip = '['.$ip.']'; + } + + return $ip; + } } diff --git a/lib/symfony/http-foundation/JsonResponse.php b/lib/symfony/http-foundation/JsonResponse.php index 1a23a9335c..501a6387d9 100644 --- a/lib/symfony/http-foundation/JsonResponse.php +++ b/lib/symfony/http-foundation/JsonResponse.php @@ -29,7 +29,7 @@ class JsonResponse extends Response // Encode <, >, ', &, and " characters in the JSON, making it also safe to be embedded into HTML. // 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT - const DEFAULT_ENCODING_OPTIONS = 15; + public const DEFAULT_ENCODING_OPTIONS = 15; protected $encodingOptions = self::DEFAULT_ENCODING_OPTIONS; @@ -39,10 +39,14 @@ class JsonResponse extends Response * @param array $headers An array of response headers * @param bool $json If the data is already a JSON string */ - public function __construct($data = null, $status = 200, $headers = [], $json = false) + public function __construct($data = null, int $status = 200, array $headers = [], bool $json = false) { parent::__construct('', $status, $headers); + if ($json && !\is_string($data) && !is_numeric($data) && !\is_callable([$data, '__toString'])) { + throw new \TypeError(sprintf('"%s": If $json is set to true, argument $data must be a string or object implementing __toString(), "%s" given.', __METHOD__, get_debug_type($data))); + } + if (null === $data) { $data = new \ArrayObject(); } @@ -63,9 +67,13 @@ class JsonResponse extends Response * @param array $headers An array of response headers * * @return static + * + * @deprecated since Symfony 5.1, use __construct() instead. */ - public static function create($data = null, $status = 200, $headers = []) + public static function create($data = null, int $status = 200, array $headers = []) { + trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); + return new static($data, $status, $headers); } @@ -77,13 +85,13 @@ class JsonResponse extends Response * return JsonResponse::fromJsonString('{"key": "value"}') * ->setSharedMaxAge(300); * - * @param string|null $data The JSON response string - * @param int $status The response status code - * @param array $headers An array of response headers + * @param string $data The JSON response string + * @param int $status The response status code + * @param array $headers An array of response headers * * @return static */ - public static function fromJsonString($data = null, $status = 200, $headers = []) + public static function fromJsonString(string $data, int $status = 200, array $headers = []) { return new static($data, $status, $headers, true); } @@ -97,7 +105,7 @@ class JsonResponse extends Response * * @throws \InvalidArgumentException When the callback name is not valid */ - public function setCallback($callback = null) + public function setCallback(string $callback = null) { if (null !== $callback) { // partially taken from https://geekality.net/2011/08/03/valid-javascript-identifier/ @@ -126,13 +134,9 @@ class JsonResponse extends Response /** * Sets a raw string containing a JSON document to be sent. * - * @param string $json - * * @return $this - * - * @throws \InvalidArgumentException */ - public function setJson($json) + public function setJson(string $json) { $this->data = $json; @@ -150,33 +154,17 @@ class JsonResponse extends Response */ public function setData($data = []) { - if (\defined('HHVM_VERSION')) { - // HHVM does not trigger any warnings and let exceptions - // thrown from a JsonSerializable object pass through. - // If only PHP did the same... + try { $data = json_encode($data, $this->encodingOptions); - } else { - if (!interface_exists('JsonSerializable', false)) { - set_error_handler(function () { return false; }); - try { - $data = @json_encode($data, $this->encodingOptions); - } finally { - restore_error_handler(); - } - } else { - try { - $data = json_encode($data, $this->encodingOptions); - } catch (\Exception $e) { - if ('Exception' === \get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) { - throw $e->getPrevious() ?: $e; - } - throw $e; - } - - if (\PHP_VERSION_ID >= 70300 && (\JSON_THROW_ON_ERROR & $this->encodingOptions)) { - return $this->setJson($data); - } + } catch (\Exception $e) { + if ('Exception' === \get_class($e) && str_starts_with($e->getMessage(), 'Failed calling ')) { + throw $e->getPrevious() ?: $e; } + throw $e; + } + + if (\PHP_VERSION_ID >= 70300 && (\JSON_THROW_ON_ERROR & $this->encodingOptions)) { + return $this->setJson($data); } if (\JSON_ERROR_NONE !== json_last_error()) { @@ -199,13 +187,11 @@ class JsonResponse extends Response /** * Sets options used while encoding data to JSON. * - * @param int $encodingOptions - * * @return $this */ - public function setEncodingOptions($encodingOptions) + public function setEncodingOptions(int $encodingOptions) { - $this->encodingOptions = (int) $encodingOptions; + $this->encodingOptions = $encodingOptions; return $this->setData(json_decode($this->data)); } diff --git a/lib/symfony/http-foundation/LICENSE b/lib/symfony/http-foundation/LICENSE index 9e936ec044..88bf75bb4d 100644 --- a/lib/symfony/http-foundation/LICENSE +++ b/lib/symfony/http-foundation/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2020 Fabien Potencier +Copyright (c) 2004-2022 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/lib/symfony/http-foundation/ParameterBag.php b/lib/symfony/http-foundation/ParameterBag.php index 5f2d9293dc..e1f89d69ea 100644 --- a/lib/symfony/http-foundation/ParameterBag.php +++ b/lib/symfony/http-foundation/ParameterBag.php @@ -11,10 +11,14 @@ namespace Symfony\Component\HttpFoundation; +use Symfony\Component\HttpFoundation\Exception\BadRequestException; + /** * ParameterBag is a container for key/value pairs. * * @author Fabien Potencier + * + * @implements \IteratorAggregate */ class ParameterBag implements \IteratorAggregate, \Countable { @@ -23,9 +27,6 @@ class ParameterBag implements \IteratorAggregate, \Countable */ protected $parameters; - /** - * @param array $parameters An array of parameters - */ public function __construct(array $parameters = []) { $this->parameters = $parameters; @@ -34,17 +35,29 @@ class ParameterBag implements \IteratorAggregate, \Countable /** * Returns the parameters. * - * @return array An array of parameters + * @param string|null $key The name of the parameter to return or null to get them all + * + * @return array */ - public function all() + public function all(/* string $key = null */) { - return $this->parameters; + $key = \func_num_args() > 0 ? func_get_arg(0) : null; + + if (null === $key) { + return $this->parameters; + } + + if (!\is_array($value = $this->parameters[$key] ?? [])) { + throw new BadRequestException(sprintf('Unexpected value for parameter "%s": expecting "array", got "%s".', $key, get_debug_type($value))); + } + + return $value; } /** * Returns the parameter keys. * - * @return array An array of parameter keys + * @return array */ public function keys() { @@ -53,8 +66,6 @@ class ParameterBag implements \IteratorAggregate, \Countable /** * Replaces the current parameters by a new set. - * - * @param array $parameters An array of parameters */ public function replace(array $parameters = []) { @@ -63,8 +74,6 @@ class ParameterBag implements \IteratorAggregate, \Countable /** * Adds parameters. - * - * @param array $parameters An array of parameters */ public function add(array $parameters = []) { @@ -74,12 +83,11 @@ class ParameterBag implements \IteratorAggregate, \Countable /** * Returns a parameter by name. * - * @param string $key The key - * @param mixed $default The default value if the parameter key does not exist + * @param mixed $default The default value if the parameter key does not exist * * @return mixed */ - public function get($key, $default = null) + public function get(string $key, $default = null) { return \array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default; } @@ -87,10 +95,9 @@ class ParameterBag implements \IteratorAggregate, \Countable /** * Sets a parameter by name. * - * @param string $key The key - * @param mixed $value The value + * @param mixed $value The value */ - public function set($key, $value) + public function set(string $key, $value) { $this->parameters[$key] = $value; } @@ -98,21 +105,17 @@ class ParameterBag implements \IteratorAggregate, \Countable /** * Returns true if the parameter is defined. * - * @param string $key The key - * - * @return bool true if the parameter exists, false otherwise + * @return bool */ - public function has($key) + public function has(string $key) { return \array_key_exists($key, $this->parameters); } /** * Removes a parameter. - * - * @param string $key The key */ - public function remove($key) + public function remove(string $key) { unset($this->parameters[$key]); } @@ -120,12 +123,9 @@ class ParameterBag implements \IteratorAggregate, \Countable /** * Returns the alphabetic characters of the parameter value. * - * @param string $key The parameter key - * @param string $default The default value if the parameter key does not exist - * - * @return string The filtered value + * @return string */ - public function getAlpha($key, $default = '') + public function getAlpha(string $key, string $default = '') { return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default)); } @@ -133,12 +133,9 @@ class ParameterBag implements \IteratorAggregate, \Countable /** * Returns the alphabetic characters and digits of the parameter value. * - * @param string $key The parameter key - * @param string $default The default value if the parameter key does not exist - * - * @return string The filtered value + * @return string */ - public function getAlnum($key, $default = '') + public function getAlnum(string $key, string $default = '') { return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default)); } @@ -146,12 +143,9 @@ class ParameterBag implements \IteratorAggregate, \Countable /** * Returns the digits of the parameter value. * - * @param string $key The parameter key - * @param string $default The default value if the parameter key does not exist - * - * @return string The filtered value + * @return string */ - public function getDigits($key, $default = '') + public function getDigits(string $key, string $default = '') { // we need to remove - and + because they're allowed in the filter return str_replace(['-', '+'], '', $this->filter($key, $default, \FILTER_SANITIZE_NUMBER_INT)); @@ -160,12 +154,9 @@ class ParameterBag implements \IteratorAggregate, \Countable /** * Returns the parameter value converted to integer. * - * @param string $key The parameter key - * @param int $default The default value if the parameter key does not exist - * - * @return int The filtered value + * @return int */ - public function getInt($key, $default = 0) + public function getInt(string $key, int $default = 0) { return (int) $this->get($key, $default); } @@ -173,12 +164,9 @@ class ParameterBag implements \IteratorAggregate, \Countable /** * Returns the parameter value converted to boolean. * - * @param string $key The parameter key - * @param bool $default The default value if the parameter key does not exist - * - * @return bool The filtered value + * @return bool */ - public function getBoolean($key, $default = false) + public function getBoolean(string $key, bool $default = false) { return $this->filter($key, $default, \FILTER_VALIDATE_BOOLEAN); } @@ -186,16 +174,15 @@ class ParameterBag implements \IteratorAggregate, \Countable /** * Filter key. * - * @param string $key Key - * @param mixed $default Default = null - * @param int $filter FILTER_* constant - * @param mixed $options Filter options + * @param mixed $default Default = null + * @param int $filter FILTER_* constant + * @param mixed $options Filter options * * @see https://php.net/filter-var * * @return mixed */ - public function filter($key, $default = null, $filter = \FILTER_DEFAULT, $options = []) + public function filter(string $key, $default = null, int $filter = \FILTER_DEFAULT, $options = []) { $value = $this->get($key, $default); @@ -209,14 +196,20 @@ class ParameterBag implements \IteratorAggregate, \Countable $options['flags'] = \FILTER_REQUIRE_ARRAY; } + if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) { + trigger_deprecation('symfony/http-foundation', '5.2', 'Not passing a Closure together with FILTER_CALLBACK to "%s()" is deprecated. Wrap your filter in a closure instead.', __METHOD__); + // throw new \InvalidArgumentException(sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null))); + } + return filter_var($value, $filter, $options); } /** * Returns an iterator for parameters. * - * @return \ArrayIterator An \ArrayIterator instance + * @return \ArrayIterator */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->parameters); @@ -225,8 +218,9 @@ class ParameterBag implements \IteratorAggregate, \Countable /** * Returns the number of parameters. * - * @return int The number of parameters + * @return int */ + #[\ReturnTypeWillChange] public function count() { return \count($this->parameters); diff --git a/lib/symfony/http-foundation/README.md b/lib/symfony/http-foundation/README.md index ac98f9b80a..424f2c4f0b 100644 --- a/lib/symfony/http-foundation/README.md +++ b/lib/symfony/http-foundation/README.md @@ -4,11 +4,25 @@ HttpFoundation Component The HttpFoundation component defines an object-oriented layer for the HTTP specification. +Sponsor +------- + +The HttpFoundation component for Symfony 5.4/6.0 is [backed][1] by [Laravel][2]. + +Laravel is a PHP web development framework that is passionate about maximum developer +happiness. Laravel is built using a variety of bespoke and Symfony based components. + +Help Symfony by [sponsoring][3] its development! + Resources --------- - * [Documentation](https://symfony.com/doc/current/components/http_foundation.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) + * [Documentation](https://symfony.com/doc/current/components/http_foundation.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) + +[1]: https://symfony.com/backers +[2]: https://laravel.com/ +[3]: https://symfony.com/sponsor diff --git a/lib/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php b/lib/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php new file mode 100644 index 0000000000..c91d614fe3 --- /dev/null +++ b/lib/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RateLimiter; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\RateLimiter\LimiterInterface; +use Symfony\Component\RateLimiter\Policy\NoLimiter; +use Symfony\Component\RateLimiter\RateLimit; + +/** + * An implementation of RequestRateLimiterInterface that + * fits most use-cases. + * + * @author Wouter de Jong + */ +abstract class AbstractRequestRateLimiter implements RequestRateLimiterInterface +{ + public function consume(Request $request): RateLimit + { + $limiters = $this->getLimiters($request); + if (0 === \count($limiters)) { + $limiters = [new NoLimiter()]; + } + + $minimalRateLimit = null; + foreach ($limiters as $limiter) { + $rateLimit = $limiter->consume(1); + + if (null === $minimalRateLimit || $rateLimit->getRemainingTokens() < $minimalRateLimit->getRemainingTokens()) { + $minimalRateLimit = $rateLimit; + } + } + + return $minimalRateLimit; + } + + public function reset(Request $request): void + { + foreach ($this->getLimiters($request) as $limiter) { + $limiter->reset(); + } + } + + /** + * @return LimiterInterface[] a set of limiters using keys extracted from the request + */ + abstract protected function getLimiters(Request $request): array; +} diff --git a/lib/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php b/lib/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php new file mode 100644 index 0000000000..4c87a40a89 --- /dev/null +++ b/lib/symfony/http-foundation/RateLimiter/RequestRateLimiterInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\RateLimiter; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\RateLimiter\RateLimit; + +/** + * A special type of limiter that deals with requests. + * + * This allows to limit on different types of information + * from the requests. + * + * @author Wouter de Jong + */ +interface RequestRateLimiterInterface +{ + public function consume(Request $request): RateLimit; + + public function reset(Request $request): void; +} diff --git a/lib/symfony/http-foundation/RedirectResponse.php b/lib/symfony/http-foundation/RedirectResponse.php index 9ef9059f0c..2103280c60 100644 --- a/lib/symfony/http-foundation/RedirectResponse.php +++ b/lib/symfony/http-foundation/RedirectResponse.php @@ -32,7 +32,7 @@ class RedirectResponse extends Response * * @see https://tools.ietf.org/html/rfc2616#section-10.3 */ - public function __construct($url, $status = 302, $headers = []) + public function __construct(string $url, int $status = 302, array $headers = []) { parent::__construct('', $status, $headers); @@ -50,21 +50,23 @@ class RedirectResponse extends Response /** * Factory method for chainability. * - * @param string $url The url to redirect to - * @param int $status The response status code - * @param array $headers An array of response headers + * @param string $url The URL to redirect to * * @return static + * + * @deprecated since Symfony 5.1, use __construct() instead. */ - public static function create($url = '', $status = 302, $headers = []) + public static function create($url = '', int $status = 302, array $headers = []) { + trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); + return new static($url, $status, $headers); } /** * Returns the target URL. * - * @return string target URL + * @return string */ public function getTargetUrl() { @@ -74,15 +76,13 @@ class RedirectResponse extends Response /** * Sets the redirect target of this response. * - * @param string $url The URL to redirect to - * * @return $this * * @throws \InvalidArgumentException */ - public function setTargetUrl($url) + public function setTargetUrl(string $url) { - if (empty($url)) { + if ('' === $url) { throw new \InvalidArgumentException('Cannot redirect to an empty URL.'); } diff --git a/lib/symfony/http-foundation/Request.php b/lib/symfony/http-foundation/Request.php index c9bf0a261b..65f30a1a7f 100644 --- a/lib/symfony/http-foundation/Request.php +++ b/lib/symfony/http-foundation/Request.php @@ -12,9 +12,20 @@ namespace Symfony\Component\HttpFoundation; use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; +use Symfony\Component\HttpFoundation\Exception\JsonException; +use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; use Symfony\Component\HttpFoundation\Session\SessionInterface; +// Help opcache.preload discover always-needed symbols +class_exists(AcceptHeader::class); +class_exists(FileBag::class); +class_exists(HeaderBag::class); +class_exists(HeaderUtils::class); +class_exists(InputBag::class); +class_exists(ParameterBag::class); +class_exists(ServerBag::class); + /** * Request represents an HTTP request. * @@ -30,33 +41,28 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface; */ class Request { - const HEADER_FORWARDED = 0b00001; // When using RFC 7239 - const HEADER_X_FORWARDED_FOR = 0b00010; - const HEADER_X_FORWARDED_HOST = 0b00100; - const HEADER_X_FORWARDED_PROTO = 0b01000; - const HEADER_X_FORWARDED_PORT = 0b10000; - const HEADER_X_FORWARDED_ALL = 0b11110; // All "X-Forwarded-*" headers - const HEADER_X_FORWARDED_AWS_ELB = 0b11010; // AWS ELB doesn't send X-Forwarded-Host + public const HEADER_FORWARDED = 0b000001; // When using RFC 7239 + public const HEADER_X_FORWARDED_FOR = 0b000010; + public const HEADER_X_FORWARDED_HOST = 0b000100; + public const HEADER_X_FORWARDED_PROTO = 0b001000; + public const HEADER_X_FORWARDED_PORT = 0b010000; + public const HEADER_X_FORWARDED_PREFIX = 0b100000; - /** @deprecated since version 3.3, to be removed in 4.0 */ - const HEADER_CLIENT_IP = self::HEADER_X_FORWARDED_FOR; - /** @deprecated since version 3.3, to be removed in 4.0 */ - const HEADER_CLIENT_HOST = self::HEADER_X_FORWARDED_HOST; - /** @deprecated since version 3.3, to be removed in 4.0 */ - const HEADER_CLIENT_PROTO = self::HEADER_X_FORWARDED_PROTO; - /** @deprecated since version 3.3, to be removed in 4.0 */ - const HEADER_CLIENT_PORT = self::HEADER_X_FORWARDED_PORT; + /** @deprecated since Symfony 5.2, use either "HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO" or "HEADER_X_FORWARDED_AWS_ELB" or "HEADER_X_FORWARDED_TRAEFIK" constants instead. */ + public const HEADER_X_FORWARDED_ALL = 0b1011110; // All "X-Forwarded-*" headers sent by "usual" reverse proxy + public const HEADER_X_FORWARDED_AWS_ELB = 0b0011010; // AWS ELB doesn't send X-Forwarded-Host + public const HEADER_X_FORWARDED_TRAEFIK = 0b0111110; // All "X-Forwarded-*" headers sent by Traefik reverse proxy - const METHOD_HEAD = 'HEAD'; - const METHOD_GET = 'GET'; - const METHOD_POST = 'POST'; - const METHOD_PUT = 'PUT'; - const METHOD_PATCH = 'PATCH'; - const METHOD_DELETE = 'DELETE'; - const METHOD_PURGE = 'PURGE'; - const METHOD_OPTIONS = 'OPTIONS'; - const METHOD_TRACE = 'TRACE'; - const METHOD_CONNECT = 'CONNECT'; + public const METHOD_HEAD = 'HEAD'; + public const METHOD_GET = 'GET'; + public const METHOD_POST = 'POST'; + public const METHOD_PUT = 'PUT'; + public const METHOD_PATCH = 'PATCH'; + public const METHOD_DELETE = 'DELETE'; + public const METHOD_PURGE = 'PURGE'; + public const METHOD_OPTIONS = 'OPTIONS'; + public const METHOD_TRACE = 'TRACE'; + public const METHOD_CONNECT = 'CONNECT'; /** * @var string[] @@ -73,25 +79,6 @@ class Request */ protected static $trustedHosts = []; - /** - * Names for headers that can be trusted when - * using trusted proxies. - * - * The FORWARDED header is the standard as of rfc7239. - * - * The other headers are non-standard, but widely used - * by popular reverse proxies (like Apache mod_proxy or Amazon EC2). - * - * @deprecated since version 3.3, to be removed in 4.0 - */ - protected static $trustedHeaders = [ - self::HEADER_FORWARDED => 'FORWARDED', - self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR', - self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST', - self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO', - self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT', - ]; - protected static $httpMethodParameterOverride = false; /** @@ -104,14 +91,14 @@ class Request /** * Request body parameters ($_POST). * - * @var ParameterBag + * @var InputBag */ public $request; /** * Query string parameters ($_GET). * - * @var ParameterBag + * @var InputBag */ public $query; @@ -132,7 +119,7 @@ class Request /** * Cookies ($_COOKIE). * - * @var ParameterBag + * @var InputBag */ public $cookies; @@ -199,7 +186,7 @@ class Request protected $format; /** - * @var SessionInterface + * @var SessionInterface|callable(): SessionInterface */ protected $session; @@ -220,27 +207,45 @@ class Request protected static $requestFactory; + /** + * @var string|null + */ + private $preferredFormat; private $isHostValid = true; private $isForwardedValid = true; + /** + * @var bool|null + */ + private $isSafeContentPreferred; + private static $trustedHeaderSet = -1; - /** @deprecated since version 3.3, to be removed in 4.0 */ - private static $trustedHeaderNames = [ - self::HEADER_FORWARDED => 'FORWARDED', - self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR', - self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST', - self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO', - self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT', - ]; - - private static $forwardedParams = [ + private const FORWARDED_PARAMS = [ self::HEADER_X_FORWARDED_FOR => 'for', self::HEADER_X_FORWARDED_HOST => 'host', self::HEADER_X_FORWARDED_PROTO => 'proto', self::HEADER_X_FORWARDED_PORT => 'host', ]; + /** + * Names for headers that can be trusted when + * using trusted proxies. + * + * The FORWARDED header is the standard as of rfc7239. + * + * The other headers are non-standard, but widely used + * by popular reverse proxies (like Apache mod_proxy or Amazon EC2). + */ + private const TRUSTED_HEADERS = [ + self::HEADER_FORWARDED => 'FORWARDED', + self::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR', + self::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST', + self::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO', + self::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT', + self::HEADER_X_FORWARDED_PREFIX => 'X_FORWARDED_PREFIX', + ]; + /** * @param array $query The GET parameters * @param array $request The POST parameters @@ -270,10 +275,10 @@ class Request */ public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) { - $this->request = new ParameterBag($request); - $this->query = new ParameterBag($query); + $this->request = new InputBag($request); + $this->query = new InputBag($query); $this->attributes = new ParameterBag($attributes); - $this->cookies = new ParameterBag($cookies); + $this->cookies = new InputBag($cookies); $this->files = new FileBag($files); $this->server = new ServerBag($server); $this->headers = new HeaderBag($this->server->getHeaders()); @@ -298,26 +303,13 @@ class Request */ public static function createFromGlobals() { - // With the php's bug #66606, the php's built-in web server - // stores the Content-Type and Content-Length header values in - // HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields. - $server = $_SERVER; - if ('cli-server' === \PHP_SAPI) { - if (\array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) { - $server['CONTENT_LENGTH'] = $_SERVER['HTTP_CONTENT_LENGTH']; - } - if (\array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) { - $server['CONTENT_TYPE'] = $_SERVER['HTTP_CONTENT_TYPE']; - } - } + $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER); - $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $server); - - if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded') + if (str_starts_with($request->headers->get('CONTENT_TYPE', ''), 'application/x-www-form-urlencoded') && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH']) ) { parse_str($request->getContent(), $data); - $request->request = new ParameterBag($data); + $request->request = new InputBag($data); } return $request; @@ -339,13 +331,13 @@ class Request * * @return static */ - public static function create($uri, $method = 'GET', $parameters = [], $cookies = [], $files = [], $server = [], $content = null) + public static function create(string $uri, string $method = 'GET', array $parameters = [], array $cookies = [], array $files = [], array $server = [], $content = null) { $server = array_replace([ 'SERVER_NAME' => 'localhost', 'SERVER_PORT' => 80, 'HTTP_HOST' => 'localhost', - 'HTTP_USER_AGENT' => 'Symfony/3.X', + 'HTTP_USER_AGENT' => 'Symfony', 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5', 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', @@ -354,6 +346,7 @@ class Request 'SCRIPT_FILENAME' => '', 'SERVER_PROTOCOL' => 'HTTP/1.1', 'REQUEST_TIME' => time(), + 'REQUEST_TIME_FLOAT' => microtime(true), ], $server); $server['PATH_INFO'] = ''; @@ -437,10 +430,8 @@ class Request * This is mainly useful when you need to override the Request class * to keep BC with an existing system. It should not be used for any * other purpose. - * - * @param callable|null $callable A PHP callable */ - public static function setFactory($callable) + public static function setFactory(?callable $callable) { self::$requestFactory = $callable; } @@ -461,16 +452,16 @@ class Request { $dup = clone $this; if (null !== $query) { - $dup->query = new ParameterBag($query); + $dup->query = new InputBag($query); } if (null !== $request) { - $dup->request = new ParameterBag($request); + $dup->request = new InputBag($request); } if (null !== $attributes) { $dup->attributes = new ParameterBag($attributes); } if (null !== $cookies) { - $dup->cookies = new ParameterBag($cookies); + $dup->cookies = new InputBag($cookies); } if (null !== $files) { $dup->files = new FileBag($files); @@ -521,19 +512,11 @@ class Request /** * Returns the request as a string. * - * @return string The request + * @return string */ public function __toString() { - try { - $content = $this->getContent(); - } catch (\LogicException $e) { - if (\PHP_VERSION_ID >= 70400) { - throw $e; - } - - return trigger_error($e, \E_USER_ERROR); - } + $content = $this->getContent(); $cookieHeader = ''; $cookies = []; @@ -570,7 +553,7 @@ class Request foreach ($this->headers->all() as $key => $value) { $key = strtoupper(str_replace('-', '_', $key)); - if (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH'])) { + if (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true)) { $_SERVER[$key] = implode(', ', $value); } else { $_SERVER['HTTP_'.$key] = implode(', ', $value); @@ -579,13 +562,16 @@ class Request $request = ['g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE]; - $requestOrder = ini_get('request_order') ?: ini_get('variables_order'); + $requestOrder = \ini_get('request_order') ?: \ini_get('variables_order'); $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp'; - $_REQUEST = []; + $_REQUEST = [[]]; + foreach (str_split($requestOrder) as $order) { - $_REQUEST = array_merge($_REQUEST, $request[$order]); + $_REQUEST[] = $request[$order]; } + + $_REQUEST = array_merge(...$_REQUEST); } /** @@ -593,32 +579,30 @@ class Request * * You should only list the reverse proxies that you manage directly. * - * @param array $proxies A list of trusted proxies + * @param array $proxies A list of trusted proxies, the string 'REMOTE_ADDR' will be replaced with $_SERVER['REMOTE_ADDR'] * @param int $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies - * - * @throws \InvalidArgumentException When $trustedHeaderSet is invalid */ - public static function setTrustedProxies(array $proxies/*, int $trustedHeaderSet*/) + public static function setTrustedProxies(array $proxies, int $trustedHeaderSet) { - self::$trustedProxies = $proxies; - - if (2 > \func_num_args()) { - @trigger_error(sprintf('The %s() method expects a bit field of Request::HEADER_* as second argument since Symfony 3.3. Defining it will be required in 4.0. ', __METHOD__), \E_USER_DEPRECATED); - - return; + if (self::HEADER_X_FORWARDED_ALL === $trustedHeaderSet) { + trigger_deprecation('symfony/http-foundation', '5.2', 'The "HEADER_X_FORWARDED_ALL" constant is deprecated, use either "HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO" or "HEADER_X_FORWARDED_AWS_ELB" or "HEADER_X_FORWARDED_TRAEFIK" constants instead.'); } - $trustedHeaderSet = (int) func_get_arg(1); + self::$trustedProxies = array_reduce($proxies, function ($proxies, $proxy) { + if ('REMOTE_ADDR' !== $proxy) { + $proxies[] = $proxy; + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $proxies[] = $_SERVER['REMOTE_ADDR']; + } - foreach (self::$trustedHeaderNames as $header => $name) { - self::$trustedHeaders[$header] = $header & $trustedHeaderSet ? $name : null; - } + return $proxies; + }, []); self::$trustedHeaderSet = $trustedHeaderSet; } /** * Gets the list of trusted proxies. * - * @return array An array of trusted proxies + * @return array */ public static function getTrustedProxies() { @@ -654,126 +638,31 @@ class Request /** * Gets the list of trusted host patterns. * - * @return array An array of trusted host patterns + * @return array */ public static function getTrustedHosts() { return self::$trustedHostPatterns; } - /** - * Sets the name for trusted headers. - * - * The following header keys are supported: - * - * * Request::HEADER_CLIENT_IP: defaults to X-Forwarded-For (see getClientIp()) - * * Request::HEADER_CLIENT_HOST: defaults to X-Forwarded-Host (see getHost()) - * * Request::HEADER_CLIENT_PORT: defaults to X-Forwarded-Port (see getPort()) - * * Request::HEADER_CLIENT_PROTO: defaults to X-Forwarded-Proto (see getScheme() and isSecure()) - * * Request::HEADER_FORWARDED: defaults to Forwarded (see RFC 7239) - * - * Setting an empty value allows to disable the trusted header for the given key. - * - * @param string $key The header key - * @param string $value The header name - * - * @throws \InvalidArgumentException - * - * @deprecated since version 3.3, to be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead. - */ - public static function setTrustedHeaderName($key, $value) - { - @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.', __METHOD__), \E_USER_DEPRECATED); - - if ('forwarded' === $key) { - $key = self::HEADER_FORWARDED; - } elseif ('client_ip' === $key) { - $key = self::HEADER_CLIENT_IP; - } elseif ('client_host' === $key) { - $key = self::HEADER_CLIENT_HOST; - } elseif ('client_proto' === $key) { - $key = self::HEADER_CLIENT_PROTO; - } elseif ('client_port' === $key) { - $key = self::HEADER_CLIENT_PORT; - } elseif (!\array_key_exists($key, self::$trustedHeaders)) { - throw new \InvalidArgumentException(sprintf('Unable to set the trusted header name for key "%s".', $key)); - } - - self::$trustedHeaders[$key] = $value; - - if (null !== $value) { - self::$trustedHeaderNames[$key] = $value; - self::$trustedHeaderSet |= $key; - } else { - self::$trustedHeaderSet &= ~$key; - } - } - - /** - * Gets the trusted proxy header name. - * - * @param string $key The header key - * - * @return string The header name - * - * @throws \InvalidArgumentException - * - * @deprecated since version 3.3, to be removed in 4.0. Use the Request::getTrustedHeaderSet() method instead. - */ - public static function getTrustedHeaderName($key) - { - if (2 > \func_num_args() || func_get_arg(1)) { - @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the Request::getTrustedHeaderSet() method instead.', __METHOD__), \E_USER_DEPRECATED); - } - - if (!\array_key_exists($key, self::$trustedHeaders)) { - throw new \InvalidArgumentException(sprintf('Unable to get the trusted header name for key "%s".', $key)); - } - - return self::$trustedHeaders[$key]; - } - /** * Normalizes a query string. * * It builds a normalized query string, where keys/value pairs are alphabetized, * have consistent escaping and unneeded delimiters are removed. * - * @param string $qs Query string - * - * @return string A normalized query string for the Request + * @return string */ - public static function normalizeQueryString($qs) + public static function normalizeQueryString(?string $qs) { - if ('' == $qs) { + if ('' === ($qs ?? '')) { return ''; } - $parts = []; - $order = []; + $qs = HeaderUtils::parseQuery($qs); + ksort($qs); - foreach (explode('&', $qs) as $param) { - if ('' === $param || '=' === $param[0]) { - // Ignore useless delimiters, e.g. "x=y&". - // Also ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway. - // PHP also does not include them when building _GET. - continue; - } - - $keyValuePair = explode('=', $param, 2); - - // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded). - // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str. This is why we use urldecode and then normalize to - // RFC 3986 with rawurlencode. - $parts[] = isset($keyValuePair[1]) ? - rawurlencode(urldecode($keyValuePair[0])).'='.rawurlencode(urldecode($keyValuePair[1])) : - rawurlencode(urldecode($keyValuePair[0])); - $order[] = urldecode($keyValuePair[0]); - } - - array_multisort($order, \SORT_ASC, $parts); - - return implode('&', $parts); + return http_build_query($qs, '', '&', \PHP_QUERY_RFC3986); } /** @@ -795,7 +684,7 @@ class Request /** * Checks whether support for the _method request parameter is enabled. * - * @return bool True when the _method request parameter is enabled, false otherwise + * @return bool */ public static function getHttpMethodParameterOverride() { @@ -809,25 +698,26 @@ class Request * flexibility in controllers, it is better to explicitly get request parameters from the appropriate * public property instead (attributes, query, request). * - * Order of precedence: PATH (routing placeholders or custom attributes), GET, BODY + * Order of precedence: PATH (routing placeholders or custom attributes), GET, POST * - * @param string $key The key - * @param mixed $default The default value if the parameter key does not exist + * @param mixed $default The default value if the parameter key does not exist * * @return mixed + * + * @internal since Symfony 5.4, use explicit input sources instead */ - public function get($key, $default = null) + public function get(string $key, $default = null) { if ($this !== $result = $this->attributes->get($key, $this)) { return $result; } - if ($this !== $result = $this->query->get($key, $this)) { - return $result; + if ($this->query->has($key)) { + return $this->query->all()[$key]; } - if ($this !== $result = $this->request->get($key, $this)) { - return $result; + if ($this->request->has($key)) { + return $this->request->all()[$key]; } return $default; @@ -836,11 +726,20 @@ class Request /** * Gets the Session. * - * @return SessionInterface|null The session + * @return SessionInterface */ public function getSession() { - return $this->session; + $session = $this->session; + if (!$session instanceof SessionInterface && null !== $session) { + $this->setSession($session = $session()); + } + + if (null === $session) { + throw new SessionNotFoundException('Session has not been set.'); + } + + return $session; } /** @@ -852,7 +751,7 @@ class Request public function hasPreviousSession() { // the check for $this->session avoids malicious users trying to fake a session cookie with proper name - return $this->hasSession() && $this->cookies->has($this->session->getName()); + return $this->hasSession() && $this->cookies->has($this->getSession()->getName()); } /** @@ -862,23 +761,32 @@ class Request * like whether the session is started or not. It is just a way to check if this Request * is associated with a Session instance. * - * @return bool true when the Request contains a Session object, false otherwise + * @param bool $skipIfUninitialized When true, ignores factories injected by `setSessionFactory` + * + * @return bool */ - public function hasSession() + public function hasSession(/* bool $skipIfUninitialized = false */) { - return null !== $this->session; + $skipIfUninitialized = \func_num_args() > 0 ? func_get_arg(0) : false; + + return null !== $this->session && (!$skipIfUninitialized || $this->session instanceof SessionInterface); } - /** - * Sets the Session. - * - * @param SessionInterface $session The Session - */ public function setSession(SessionInterface $session) { $this->session = $session; } + /** + * @internal + * + * @param callable(): SessionInterface $factory + */ + public function setSessionFactory(callable $factory) + { + $this->session = $factory; + } + /** * Returns the client IP addresses. * @@ -888,7 +796,7 @@ class Request * * Use this method carefully; you should use getClientIp() instead. * - * @return array The client IP addresses + * @return array * * @see getClientIp() */ @@ -900,7 +808,7 @@ class Request return [$ip]; } - return $this->getTrustedValues(self::HEADER_CLIENT_IP, $ip) ?: [$ip]; + return $this->getTrustedValues(self::HEADER_X_FORWARDED_FOR, $ip) ?: [$ip]; } /** @@ -916,7 +824,7 @@ class Request * ("Client-Ip" for instance), configure it via the $trustedHeaderSet * argument of the Request::setTrustedProxies() method instead. * - * @return string|null The client IP address + * @return string|null * * @see getClientIps() * @see https://wikipedia.org/wiki/X-Forwarded-For @@ -993,6 +901,24 @@ class Request * @return string The raw URL (i.e. not urldecoded) */ public function getBaseUrl() + { + $trustedPrefix = ''; + + // the proxy prefix must be prepended to any prefix being needed at the webserver level + if ($this->isFromTrustedProxy() && $trustedPrefixValues = $this->getTrustedValues(self::HEADER_X_FORWARDED_PREFIX)) { + $trustedPrefix = rtrim($trustedPrefixValues[0], '/'); + } + + return $trustedPrefix.$this->getBaseUrlReal(); + } + + /** + * Returns the real base URL received by the webserver from which this request is executed. + * The URL does not include trusted reverse proxy prefix. + * + * @return string The raw URL (i.e. not urldecoded) + */ + private function getBaseUrlReal(): string { if (null === $this->baseUrl) { $this->baseUrl = $this->prepareBaseUrl(); @@ -1019,17 +945,13 @@ class Request * * The "X-Forwarded-Port" header must contain the client port. * - * If your reverse proxy uses a different header name than "X-Forwarded-Port", - * configure it via via the $trustedHeaderSet argument of the - * Request::setTrustedProxies() method instead. - * - * @return int|string can be a string if fetched from the server bag + * @return int|string|null Can be a string if fetched from the server bag */ public function getPort() { - if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_PORT)) { + if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_PORT)) { $host = $host[0]; - } elseif ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) { + } elseif ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_HOST)) { $host = $host[0]; } elseif (!$host = $this->headers->get('HOST')) { return $this->server->get('SERVER_PORT'); @@ -1071,7 +993,7 @@ class Request /** * Gets the user info. * - * @return string A user name and, optionally, scheme-specific information about how to gain authorization to access the server + * @return string|null A user name if any and, optionally, scheme-specific information about how to gain authorization to access the server */ public function getUserInfo() { @@ -1124,7 +1046,7 @@ class Request * If the URL was called with basic authentication, the user * and the password are not added to the generated string. * - * @return string The scheme and HTTP host + * @return string */ public function getSchemeAndHttpHost() { @@ -1134,7 +1056,7 @@ class Request /** * Generates a normalized URI (URL) for the Request. * - * @return string A normalized URI (URL) for the Request + * @return string * * @see getQueryString() */ @@ -1152,9 +1074,9 @@ class Request * * @param string $path A path to use instead of the current one * - * @return string The normalized URI for the path + * @return string */ - public function getUriForPath($path) + public function getUriForPath(string $path) { return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path; } @@ -1174,11 +1096,9 @@ class Request * - "/a/b/c/other" -> "other" * - "/a/x/y" -> "../../x/y" * - * @param string $path The target path - * - * @return string The relative target path + * @return string */ - public function getRelativeUriForPath($path) + public function getRelativeUriForPath(string $path) { // be sure that we are dealing with an absolute path if (!isset($path[0]) || '/' !== $path[0]) { @@ -1220,7 +1140,7 @@ class Request * It builds a normalized query string, where keys/value pairs are alphabetized * and have consistent escaping. * - * @return string|null A normalized query string for the Request + * @return string|null */ public function getQueryString() { @@ -1237,15 +1157,11 @@ class Request * * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http". * - * If your reverse proxy uses a different header name than "X-Forwarded-Proto" - * ("SSL_HTTPS" for instance), configure it via the $trustedHeaderSet - * argument of the Request::setTrustedProxies() method instead. - * * @return bool */ public function isSecure() { - if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_CLIENT_PROTO)) { + if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_X_FORWARDED_PROTO)) { return \in_array(strtolower($proto[0]), ['https', 'on', 'ssl', '1'], true); } @@ -1262,17 +1178,13 @@ class Request * * The "X-Forwarded-Host" header must contain the client host name. * - * If your reverse proxy uses a different header name than "X-Forwarded-Host", - * configure it via the $trustedHeaderSet argument of the - * Request::setTrustedProxies() method instead. - * * @return string * * @throws SuspiciousOperationException when the host name is invalid or not trusted */ public function getHost() { - if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) { + if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_X_FORWARDED_HOST)) { $host = $host[0]; } elseif (!$host = $this->headers->get('HOST')) { if (!$host = $this->server->get('SERVER_NAME')) { @@ -1324,10 +1236,8 @@ class Request /** * Sets the request method. - * - * @param string $method */ - public function setMethod($method) + public function setMethod(string $method) { $this->method = null; $this->server->set('REQUEST_METHOD', $method); @@ -1344,7 +1254,7 @@ class Request * * The method is always an uppercased string. * - * @return string The request method + * @return string * * @see getRealMethod() */ @@ -1386,7 +1296,7 @@ class Request /** * Gets the "real" request method. * - * @return string The request method + * @return string * * @see getMethod() */ @@ -1398,11 +1308,9 @@ class Request /** * Gets the mime type associated with the format. * - * @param string $format The format - * - * @return string|null The associated mime type (null if not found) + * @return string|null */ - public function getMimeType($format) + public function getMimeType(string $format) { if (null === static::$formats) { static::initializeFormats(); @@ -1414,30 +1322,26 @@ class Request /** * Gets the mime types associated with the format. * - * @param string $format The format - * - * @return array The associated mime types + * @return array */ - public static function getMimeTypes($format) + public static function getMimeTypes(string $format) { if (null === static::$formats) { static::initializeFormats(); } - return isset(static::$formats[$format]) ? static::$formats[$format] : []; + return static::$formats[$format] ?? []; } /** * Gets the format associated with the mime type. * - * @param string $mimeType The associated mime type - * - * @return string|null The format (null if not found) + * @return string|null */ - public function getFormat($mimeType) + public function getFormat(?string $mimeType) { $canonicalMimeType = null; - if (false !== $pos = strpos($mimeType, ';')) { + if ($mimeType && false !== $pos = strpos($mimeType, ';')) { $canonicalMimeType = trim(substr($mimeType, 0, $pos)); } @@ -1460,10 +1364,9 @@ class Request /** * Associates a format with mime types. * - * @param string $format The format * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type) */ - public function setFormat($format, $mimeTypes) + public function setFormat(?string $format, $mimeTypes) { if (null === static::$formats) { static::initializeFormats(); @@ -1481,25 +1384,23 @@ class Request * * _format request attribute * * $default * - * @param string|null $default The default format + * @see getPreferredFormat * - * @return string|null The request format + * @return string|null */ - public function getRequestFormat($default = 'html') + public function getRequestFormat(?string $default = 'html') { if (null === $this->format) { $this->format = $this->attributes->get('_format'); } - return null === $this->format ? $default : $this->format; + return $this->format ?? $default; } /** * Sets the request format. - * - * @param string $format The request format */ - public function setRequestFormat($format) + public function setRequestFormat(?string $format) { $this->format = $format; } @@ -1507,19 +1408,17 @@ class Request /** * Gets the format associated with the request. * - * @return string|null The format (null if no content type is present) + * @return string|null */ public function getContentType() { - return $this->getFormat($this->headers->get('CONTENT_TYPE')); + return $this->getFormat($this->headers->get('CONTENT_TYPE', '')); } /** * Sets the default locale. - * - * @param string $locale */ - public function setDefaultLocale($locale) + public function setDefaultLocale(string $locale) { $this->defaultLocale = $locale; @@ -1540,10 +1439,8 @@ class Request /** * Sets the locale. - * - * @param string $locale */ - public function setLocale($locale) + public function setLocale(string $locale) { $this->setPhpDefaultLocale($this->locale = $locale); } @@ -1565,7 +1462,7 @@ class Request * * @return bool */ - public function isMethod($method) + public function isMethod(string $method) { return $this->getMethod() === strtoupper($method); } @@ -1575,20 +1472,10 @@ class Request * * @see https://tools.ietf.org/html/rfc7231#section-4.2.1 * - * @param bool $andCacheable Adds the additional condition that the method should be cacheable. True by default. - * * @return bool */ - public function isMethodSafe(/* $andCacheable = true */) + public function isMethodSafe() { - if (!\func_num_args() || func_get_arg(0)) { - // This deprecation should be turned into a BadMethodCallException in 4.0 (without adding the argument in the signature) - // then setting $andCacheable to false should be deprecated in 4.1 - @trigger_error('Checking only for cacheable HTTP methods with Symfony\Component\HttpFoundation\Request::isMethodSafe() is deprecated since Symfony 3.2 and will throw an exception in 4.0. Disable checking only for cacheable methods by calling the method with `false` as first argument or use the Request::isMethodCacheable() instead.', \E_USER_DEPRECATED); - - return \in_array($this->getMethod(), ['GET', 'HEAD']); - } - return \in_array($this->getMethod(), ['GET', 'HEAD', 'OPTIONS', 'TRACE']); } @@ -1607,7 +1494,7 @@ class Request * * @see https://tools.ietf.org/html/rfc7231#section-4.2.3 * - * @return bool True for GET and HEAD, false otherwise + * @return bool */ public function isMethodCacheable() { @@ -1623,12 +1510,12 @@ class Request * if the proxy is trusted (see "setTrustedProxies()"), otherwise it returns * the latter (from the "SERVER_PROTOCOL" server parameter). * - * @return string + * @return string|null */ public function getProtocolVersion() { if ($this->isFromTrustedProxy()) { - preg_match('~^(HTTP/)?([1-9]\.[0-9]) ~', $this->headers->get('Via'), $matches); + preg_match('~^(HTTP/)?([1-9]\.[0-9]) ~', $this->headers->get('Via') ?? '', $matches); if ($matches) { return 'HTTP/'.$matches[2]; @@ -1643,16 +1530,11 @@ class Request * * @param bool $asResource If true, a resource will be returned * - * @return string|resource The request body content or a resource to read the body stream - * - * @throws \LogicException + * @return string|resource */ - public function getContent($asResource = false) + public function getContent(bool $asResource = false) { $currentContentIsResource = \is_resource($this->content); - if (\PHP_VERSION_ID < 50600 && false === $this->content) { - throw new \LogicException('getContent() can only be called once when using the resource return type and PHP below 5.6.'); - } if (true === $asResource) { if ($currentContentIsResource) { @@ -1672,7 +1554,7 @@ class Request $this->content = false; - return fopen('php://input', 'rb'); + return fopen('php://input', 'r'); } if ($currentContentIsResource) { @@ -1688,14 +1570,44 @@ class Request return $this->content; } + /** + * Gets the request body decoded as array, typically from a JSON payload. + * + * @throws JsonException When the body cannot be decoded to an array + * + * @return array + */ + public function toArray() + { + if ('' === $content = $this->getContent()) { + throw new JsonException('Request body is empty.'); + } + + try { + $content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | (\PHP_VERSION_ID >= 70300 ? \JSON_THROW_ON_ERROR : 0)); + } catch (\JsonException $e) { + throw new JsonException('Could not decode request body.', $e->getCode(), $e); + } + + if (\PHP_VERSION_ID < 70300 && \JSON_ERROR_NONE !== json_last_error()) { + throw new JsonException('Could not decode request body: '.json_last_error_msg(), json_last_error()); + } + + if (!\is_array($content)) { + throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned.', get_debug_type($content))); + } + + return $content; + } + /** * Gets the Etags. * - * @return array The entity tags + * @return array */ public function getETags() { - return preg_split('/\s*,\s*/', $this->headers->get('if_none_match'), null, \PREG_SPLIT_NO_EMPTY); + return preg_split('/\s*,\s*/', $this->headers->get('If-None-Match', ''), -1, \PREG_SPLIT_NO_EMPTY); } /** @@ -1706,19 +1618,42 @@ class Request return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma'); } + /** + * Gets the preferred format for the response by inspecting, in the following order: + * * the request format set using setRequestFormat; + * * the values of the Accept HTTP header. + * + * Note that if you use this method, you should send the "Vary: Accept" header + * in the response to prevent any issues with intermediary HTTP caches. + */ + public function getPreferredFormat(?string $default = 'html'): ?string + { + if (null !== $this->preferredFormat || null !== $this->preferredFormat = $this->getRequestFormat(null)) { + return $this->preferredFormat; + } + + foreach ($this->getAcceptableContentTypes() as $mimeType) { + if ($this->preferredFormat = $this->getFormat($mimeType)) { + return $this->preferredFormat; + } + } + + return $default; + } + /** * Returns the preferred language. * - * @param array $locales An array of ordered available locales + * @param string[] $locales An array of ordered available locales * - * @return string|null The preferred locale + * @return string|null */ public function getPreferredLanguage(array $locales = null) { $preferredLanguages = $this->getLanguages(); if (empty($locales)) { - return isset($preferredLanguages[0]) ? $preferredLanguages[0] : null; + return $preferredLanguages[0] ?? null; } if (!$preferredLanguages) { @@ -1738,13 +1673,13 @@ class Request $preferredLanguages = array_values(array_intersect($extendedPreferredLanguages, $locales)); - return isset($preferredLanguages[0]) ? $preferredLanguages[0] : $locales[0]; + return $preferredLanguages[0] ?? $locales[0]; } /** - * Gets a list of languages acceptable by the client browser. + * Gets a list of languages acceptable by the client browser ordered in the user browser preferences. * - * @return array Languages ordered in the user browser preferences + * @return array */ public function getLanguages() { @@ -1755,7 +1690,7 @@ class Request $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all(); $this->languages = []; foreach ($languages as $lang => $acceptHeaderItem) { - if (false !== strpos($lang, '-')) { + if (str_contains($lang, '-')) { $codes = explode('-', $lang); if ('i' === $codes[0]) { // Language not listed in ISO 639 that are not variants @@ -1782,9 +1717,9 @@ class Request } /** - * Gets a list of charsets acceptable by the client browser. + * Gets a list of charsets acceptable by the client browser in preferable order. * - * @return array List of charsets in preferable order + * @return array */ public function getCharsets() { @@ -1796,9 +1731,9 @@ class Request } /** - * Gets a list of encodings acceptable by the client browser. + * Gets a list of encodings acceptable by the client browser in preferable order. * - * @return array List of encodings in preferable order + * @return array */ public function getEncodings() { @@ -1810,9 +1745,9 @@ class Request } /** - * Gets a list of content types acceptable by the client browser. + * Gets a list of content types acceptable by the client browser in preferable order. * - * @return array List of content types in preferable order + * @return array */ public function getAcceptableContentTypes() { @@ -1824,20 +1759,39 @@ class Request } /** - * Returns true if the request is a XMLHttpRequest. + * Returns true if the request is an XMLHttpRequest. * * It works if your JavaScript library sets an X-Requested-With HTTP header. * It is known to work with common JavaScript frameworks: * * @see https://wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript * - * @return bool true if the request is an XMLHttpRequest, false otherwise + * @return bool */ public function isXmlHttpRequest() { return 'XMLHttpRequest' == $this->headers->get('X-Requested-With'); } + /** + * Checks whether the client browser prefers safe content or not according to RFC8674. + * + * @see https://tools.ietf.org/html/rfc8674 + */ + public function preferSafeContent(): bool + { + if (null !== $this->isSafeContentPreferred) { + return $this->isSafeContentPreferred; + } + + if (!$this->isSecure()) { + // see https://tools.ietf.org/html/rfc8674#section-3 + return $this->isSafeContentPreferred = false; + } + + return $this->isSafeContentPreferred = AcceptHeader::fromString($this->headers->get('Prefer'))->has('safe'); + } + /* * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24) * @@ -1898,13 +1852,13 @@ class Request */ protected function prepareBaseUrl() { - $filename = basename($this->server->get('SCRIPT_FILENAME')); + $filename = basename($this->server->get('SCRIPT_FILENAME', '')); - if (basename($this->server->get('SCRIPT_NAME')) === $filename) { + if (basename($this->server->get('SCRIPT_NAME', '')) === $filename) { $baseUrl = $this->server->get('SCRIPT_NAME'); - } elseif (basename($this->server->get('PHP_SELF')) === $filename) { + } elseif (basename($this->server->get('PHP_SELF', '')) === $filename) { $baseUrl = $this->server->get('PHP_SELF'); - } elseif (basename($this->server->get('ORIG_SCRIPT_NAME')) === $filename) { + } elseif (basename($this->server->get('ORIG_SCRIPT_NAME', '')) === $filename) { $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility } else { // Backtrack up the script_filename to find the portion matching @@ -1929,12 +1883,12 @@ class Request $requestUri = '/'.$requestUri; } - if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) { + if ($baseUrl && null !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) { // full $baseUrl matches return $prefix; } - if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(\dirname($baseUrl), '/'.\DIRECTORY_SEPARATOR).'/')) { + if ($baseUrl && null !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(\dirname($baseUrl), '/'.\DIRECTORY_SEPARATOR).'/')) { // directory portion of $baseUrl matches return rtrim($prefix, '/'.\DIRECTORY_SEPARATOR); } @@ -1944,7 +1898,7 @@ class Request $truncatedRequestUri = substr($requestUri, 0, $pos); } - $basename = basename($baseUrl); + $basename = basename($baseUrl ?? ''); if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) { // no match whatsoever; set it blank return ''; @@ -1963,7 +1917,7 @@ class Request /** * Prepares the base path. * - * @return string base path + * @return string */ protected function prepareBasePath() { @@ -1989,7 +1943,7 @@ class Request /** * Prepares the path info. * - * @return string path info + * @return string */ protected function preparePathInfo() { @@ -2005,7 +1959,7 @@ class Request $requestUri = '/'.$requestUri; } - if (null === ($baseUrl = $this->getBaseUrl())) { + if (null === ($baseUrl = $this->getBaseUrlReal())) { return $requestUri; } @@ -2015,7 +1969,7 @@ class Request return '/'; } - return (string) $pathInfo; + return $pathInfo; } /** @@ -2034,22 +1988,17 @@ class Request 'rdf' => ['application/rdf+xml'], 'atom' => ['application/atom+xml'], 'rss' => ['application/rss+xml'], - 'form' => ['application/x-www-form-urlencoded'], + 'form' => ['application/x-www-form-urlencoded', 'multipart/form-data'], ]; } - /** - * Sets the default PHP locale. - * - * @param string $locale - */ - private function setPhpDefaultLocale($locale) + private function setPhpDefaultLocale(string $locale): void { // if either the class Locale doesn't exist, or an exception is thrown when // setting the default locale, the intl module is not installed, and // the call can be ignored: try { - if (class_exists('Locale', false)) { + if (class_exists(\Locale::class, false)) { \Locale::setDefault($locale); } } catch (\Exception $e) { @@ -2058,17 +2007,12 @@ class Request /** * Returns the prefix as encoded in the string when the string starts with - * the given prefix, false otherwise. - * - * @param string $string The urlencoded string - * @param string $prefix The prefix not encoded - * - * @return string|false The prefix as it is encoded in $string, or false + * the given prefix, null otherwise. */ - private function getUrlencodedPrefix($string, $prefix) + private function getUrlencodedPrefix(string $string, string $prefix): ?string { - if (0 !== strpos(rawurldecode($string), $prefix)) { - return false; + if (!str_starts_with(rawurldecode($string), $prefix)) { + return null; } $len = \strlen($prefix); @@ -2077,13 +2021,13 @@ class Request return $match[0]; } - return false; + return null; } - private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null) + private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null): self { if (self::$requestFactory) { - $request = \call_user_func(self::$requestFactory, $query, $request, $attributes, $cookies, $files, $server, $content); + $request = (self::$requestFactory)($query, $request, $attributes, $cookies, $files, $server, $content); if (!$request instanceof self) { throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.'); @@ -2101,34 +2045,40 @@ class Request * This can be useful to determine whether or not to trust the * contents of a proxy-specific header. * - * @return bool true if the request came from a trusted proxy, false otherwise + * @return bool */ public function isFromTrustedProxy() { - return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies); + return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR', ''), self::$trustedProxies); } - private function getTrustedValues($type, $ip = null) + private function getTrustedValues(int $type, string $ip = null): array { $clientValues = []; $forwardedValues = []; - if (self::$trustedHeaders[$type] && $this->headers->has(self::$trustedHeaders[$type])) { - foreach (explode(',', $this->headers->get(self::$trustedHeaders[$type])) as $v) { - $clientValues[] = (self::HEADER_CLIENT_PORT === $type ? '0.0.0.0:' : '').trim($v); + if ((self::$trustedHeaderSet & $type) && $this->headers->has(self::TRUSTED_HEADERS[$type])) { + foreach (explode(',', $this->headers->get(self::TRUSTED_HEADERS[$type])) as $v) { + $clientValues[] = (self::HEADER_X_FORWARDED_PORT === $type ? '0.0.0.0:' : '').trim($v); } } - if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) { - $forwardedValues = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]); - $forwardedValues = preg_match_all(sprintf('{(?:%s)="?([a-zA-Z0-9\.:_\-/\[\]]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : []; - if (self::HEADER_CLIENT_PORT === $type) { - foreach ($forwardedValues as $k => $v) { - if (']' === substr($v, -1) || false === $v = strrchr($v, ':')) { + if ((self::$trustedHeaderSet & self::HEADER_FORWARDED) && (isset(self::FORWARDED_PARAMS[$type])) && $this->headers->has(self::TRUSTED_HEADERS[self::HEADER_FORWARDED])) { + $forwarded = $this->headers->get(self::TRUSTED_HEADERS[self::HEADER_FORWARDED]); + $parts = HeaderUtils::split($forwarded, ',;='); + $forwardedValues = []; + $param = self::FORWARDED_PARAMS[$type]; + foreach ($parts as $subParts) { + if (null === $v = HeaderUtils::combine($subParts)[$param] ?? null) { + continue; + } + if (self::HEADER_X_FORWARDED_PORT === $type) { + if (str_ends_with($v, ']') || false === $v = strrchr($v, ':')) { $v = $this->isSecure() ? ':443' : ':80'; } - $forwardedValues[$k] = '0.0.0.0'.$v; + $v = '0.0.0.0'.$v; } + $forwardedValues[] = $v; } } @@ -2150,10 +2100,10 @@ class Request } $this->isForwardedValid = false; - throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::$trustedHeaders[self::HEADER_FORWARDED], self::$trustedHeaders[$type])); + throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::TRUSTED_HEADERS[self::HEADER_FORWARDED], self::TRUSTED_HEADERS[$type])); } - private function normalizeAndFilterClientIps(array $clientIps, $ip) + private function normalizeAndFilterClientIps(array $clientIps, string $ip): array { if (!$clientIps) { return []; @@ -2169,7 +2119,7 @@ class Request if ($i) { $clientIps[$key] = $clientIp = substr($clientIp, 0, $i); } - } elseif (0 === strpos($clientIp, '[')) { + } elseif (str_starts_with($clientIp, '[')) { // Strip brackets and :port from IPv6 addresses. $i = strpos($clientIp, ']', 1); $clientIps[$key] = $clientIp = substr($clientIp, 1, $i - 1); diff --git a/lib/symfony/http-foundation/RequestMatcher.php b/lib/symfony/http-foundation/RequestMatcher.php index 3f51495797..f2645f9ae9 100644 --- a/lib/symfony/http-foundation/RequestMatcher.php +++ b/lib/symfony/http-foundation/RequestMatcher.php @@ -28,6 +28,11 @@ class RequestMatcher implements RequestMatcherInterface */ private $host; + /** + * @var int|null + */ + private $port; + /** * @var string[] */ @@ -49,19 +54,18 @@ class RequestMatcher implements RequestMatcherInterface private $schemes = []; /** - * @param string|null $path - * @param string|null $host * @param string|string[]|null $methods * @param string|string[]|null $ips * @param string|string[]|null $schemes */ - public function __construct($path = null, $host = null, $methods = null, $ips = null, array $attributes = [], $schemes = null) + public function __construct(string $path = null, string $host = null, $methods = null, $ips = null, array $attributes = [], $schemes = null, int $port = null) { $this->matchPath($path); $this->matchHost($host); $this->matchMethod($methods); $this->matchIps($ips); $this->matchScheme($schemes); + $this->matchPort($port); foreach ($attributes as $k => $v) { $this->matchAttribute($k, $v); @@ -80,20 +84,26 @@ class RequestMatcher implements RequestMatcherInterface /** * Adds a check for the URL host name. - * - * @param string|null $regexp A Regexp */ - public function matchHost($regexp) + public function matchHost(?string $regexp) { $this->host = $regexp; } /** - * Adds a check for the URL path info. + * Adds a check for the the URL port. * - * @param string|null $regexp A Regexp + * @param int|null $port The port number to connect to */ - public function matchPath($regexp) + public function matchPort(?int $port) + { + $this->port = $port; + } + + /** + * Adds a check for the URL path info. + */ + public function matchPath(?string $regexp) { $this->path = $regexp; } @@ -103,7 +113,7 @@ class RequestMatcher implements RequestMatcherInterface * * @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 */ - public function matchIp($ip) + public function matchIp(string $ip) { $this->matchIps($ip); } @@ -115,7 +125,11 @@ class RequestMatcher implements RequestMatcherInterface */ public function matchIps($ips) { - $this->ips = null !== $ips ? (array) $ips : []; + $ips = null !== $ips ? (array) $ips : []; + + $this->ips = array_reduce($ips, static function (array $ips, string $ip) { + return array_merge($ips, preg_split('/\s*,\s*/', $ip)); + }, []); } /** @@ -130,11 +144,8 @@ class RequestMatcher implements RequestMatcherInterface /** * Adds a check for request attribute. - * - * @param string $key The request attribute name - * @param string $regexp A Regexp */ - public function matchAttribute($key, $regexp) + public function matchAttribute(string $key, string $regexp) { $this->attributes[$key] = $regexp; } @@ -153,7 +164,11 @@ class RequestMatcher implements RequestMatcherInterface } foreach ($this->attributes as $key => $pattern) { - if (!preg_match('{'.$pattern.'}', $request->attributes->get($key))) { + $requestAttribute = $request->attributes->get($key); + if (!\is_string($requestAttribute)) { + return false; + } + if (!preg_match('{'.$pattern.'}', $requestAttribute)) { return false; } } @@ -166,7 +181,11 @@ class RequestMatcher implements RequestMatcherInterface return false; } - if (IpUtils::checkIp($request->getClientIp(), $this->ips)) { + if (null !== $this->port && 0 < $this->port && $request->getPort() !== $this->port) { + return false; + } + + if (IpUtils::checkIp($request->getClientIp() ?? '', $this->ips)) { return true; } diff --git a/lib/symfony/http-foundation/RequestMatcherInterface.php b/lib/symfony/http-foundation/RequestMatcherInterface.php index c26db3e6f4..c2e1478587 100644 --- a/lib/symfony/http-foundation/RequestMatcherInterface.php +++ b/lib/symfony/http-foundation/RequestMatcherInterface.php @@ -21,7 +21,7 @@ interface RequestMatcherInterface /** * Decides whether the rule(s) implemented by the strategy matches the supplied request. * - * @return bool true if the request matches, false otherwise + * @return bool */ public function matches(Request $request); } diff --git a/lib/symfony/http-foundation/RequestStack.php b/lib/symfony/http-foundation/RequestStack.php index 244a77d631..855b51816a 100644 --- a/lib/symfony/http-foundation/RequestStack.php +++ b/lib/symfony/http-foundation/RequestStack.php @@ -11,6 +11,9 @@ namespace Symfony\Component\HttpFoundation; +use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + /** * Request stack that controls the lifecycle of requests. * @@ -62,15 +65,13 @@ class RequestStack } /** - * Gets the master Request. + * Gets the main request. * - * Be warned that making your code aware of the master request + * Be warned that making your code aware of the main request * might make it un-compatible with other features of your framework * like ESI support. - * - * @return Request|null */ - public function getMasterRequest() + public function getMainRequest(): ?Request { if (!$this->requests) { return null; @@ -79,6 +80,20 @@ class RequestStack return $this->requests[0]; } + /** + * Gets the master request. + * + * @return Request|null + * + * @deprecated since symfony/http-foundation 5.3, use getMainRequest() instead + */ + public function getMasterRequest() + { + trigger_deprecation('symfony/http-foundation', '5.3', '"%s()" is deprecated, use "getMainRequest()" instead.', __METHOD__); + + return $this->getMainRequest(); + } + /** * Returns the parent request of the current. * @@ -86,7 +101,7 @@ class RequestStack * might make it un-compatible with other features of your framework * like ESI support. * - * If current Request is the master request, it returns null. + * If current Request is the main request, it returns null. * * @return Request|null */ @@ -94,10 +109,20 @@ class RequestStack { $pos = \count($this->requests) - 2; - if (!isset($this->requests[$pos])) { - return null; + return $this->requests[$pos] ?? null; + } + + /** + * Gets the current session. + * + * @throws SessionNotFoundException + */ + public function getSession(): SessionInterface + { + if ((null !== $request = end($this->requests) ?: null) && $request->hasSession()) { + return $request->getSession(); } - return $this->requests[$pos]; + throw new SessionNotFoundException(); } } diff --git a/lib/symfony/http-foundation/Response.php b/lib/symfony/http-foundation/Response.php index 9544a671e2..88635bb490 100644 --- a/lib/symfony/http-foundation/Response.php +++ b/lib/symfony/http-foundation/Response.php @@ -11,6 +11,9 @@ namespace Symfony\Component\HttpFoundation; +// Help opcache.preload discover always-needed symbols +class_exists(ResponseHeaderBag::class); + /** * Response represents an HTTP response. * @@ -18,74 +21,87 @@ namespace Symfony\Component\HttpFoundation; */ class Response { - const HTTP_CONTINUE = 100; - const HTTP_SWITCHING_PROTOCOLS = 101; - const HTTP_PROCESSING = 102; // RFC2518 - const HTTP_EARLY_HINTS = 103; // RFC8297 - const HTTP_OK = 200; - const HTTP_CREATED = 201; - const HTTP_ACCEPTED = 202; - const HTTP_NON_AUTHORITATIVE_INFORMATION = 203; - const HTTP_NO_CONTENT = 204; - const HTTP_RESET_CONTENT = 205; - const HTTP_PARTIAL_CONTENT = 206; - const HTTP_MULTI_STATUS = 207; // RFC4918 - const HTTP_ALREADY_REPORTED = 208; // RFC5842 - const HTTP_IM_USED = 226; // RFC3229 - const HTTP_MULTIPLE_CHOICES = 300; - const HTTP_MOVED_PERMANENTLY = 301; - const HTTP_FOUND = 302; - const HTTP_SEE_OTHER = 303; - const HTTP_NOT_MODIFIED = 304; - const HTTP_USE_PROXY = 305; - const HTTP_RESERVED = 306; - const HTTP_TEMPORARY_REDIRECT = 307; - const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238 - const HTTP_BAD_REQUEST = 400; - const HTTP_UNAUTHORIZED = 401; - const HTTP_PAYMENT_REQUIRED = 402; - const HTTP_FORBIDDEN = 403; - const HTTP_NOT_FOUND = 404; - const HTTP_METHOD_NOT_ALLOWED = 405; - const HTTP_NOT_ACCEPTABLE = 406; - const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407; - const HTTP_REQUEST_TIMEOUT = 408; - const HTTP_CONFLICT = 409; - const HTTP_GONE = 410; - const HTTP_LENGTH_REQUIRED = 411; - const HTTP_PRECONDITION_FAILED = 412; - const HTTP_REQUEST_ENTITY_TOO_LARGE = 413; - const HTTP_REQUEST_URI_TOO_LONG = 414; - const HTTP_UNSUPPORTED_MEDIA_TYPE = 415; - const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416; - const HTTP_EXPECTATION_FAILED = 417; - const HTTP_I_AM_A_TEAPOT = 418; // RFC2324 - const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540 - const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918 - const HTTP_LOCKED = 423; // RFC4918 - const HTTP_FAILED_DEPENDENCY = 424; // RFC4918 + public const HTTP_CONTINUE = 100; + public const HTTP_SWITCHING_PROTOCOLS = 101; + public const HTTP_PROCESSING = 102; // RFC2518 + public const HTTP_EARLY_HINTS = 103; // RFC8297 + public const HTTP_OK = 200; + public const HTTP_CREATED = 201; + public const HTTP_ACCEPTED = 202; + public const HTTP_NON_AUTHORITATIVE_INFORMATION = 203; + public const HTTP_NO_CONTENT = 204; + public const HTTP_RESET_CONTENT = 205; + public const HTTP_PARTIAL_CONTENT = 206; + public const HTTP_MULTI_STATUS = 207; // RFC4918 + public const HTTP_ALREADY_REPORTED = 208; // RFC5842 + public const HTTP_IM_USED = 226; // RFC3229 + public const HTTP_MULTIPLE_CHOICES = 300; + public const HTTP_MOVED_PERMANENTLY = 301; + public const HTTP_FOUND = 302; + public const HTTP_SEE_OTHER = 303; + public const HTTP_NOT_MODIFIED = 304; + public const HTTP_USE_PROXY = 305; + public const HTTP_RESERVED = 306; + public const HTTP_TEMPORARY_REDIRECT = 307; + public const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238 + public const HTTP_BAD_REQUEST = 400; + public const HTTP_UNAUTHORIZED = 401; + public const HTTP_PAYMENT_REQUIRED = 402; + public const HTTP_FORBIDDEN = 403; + public const HTTP_NOT_FOUND = 404; + public const HTTP_METHOD_NOT_ALLOWED = 405; + public const HTTP_NOT_ACCEPTABLE = 406; + public const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407; + public const HTTP_REQUEST_TIMEOUT = 408; + public const HTTP_CONFLICT = 409; + public const HTTP_GONE = 410; + public const HTTP_LENGTH_REQUIRED = 411; + public const HTTP_PRECONDITION_FAILED = 412; + public const HTTP_REQUEST_ENTITY_TOO_LARGE = 413; + public const HTTP_REQUEST_URI_TOO_LONG = 414; + public const HTTP_UNSUPPORTED_MEDIA_TYPE = 415; + public const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416; + public const HTTP_EXPECTATION_FAILED = 417; + public const HTTP_I_AM_A_TEAPOT = 418; // RFC2324 + public const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540 + public const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918 + public const HTTP_LOCKED = 423; // RFC4918 + public const HTTP_FAILED_DEPENDENCY = 424; // RFC4918 + public const HTTP_TOO_EARLY = 425; // RFC-ietf-httpbis-replay-04 + public const HTTP_UPGRADE_REQUIRED = 426; // RFC2817 + public const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585 + public const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585 + public const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585 + public const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; // RFC7725 + public const HTTP_INTERNAL_SERVER_ERROR = 500; + public const HTTP_NOT_IMPLEMENTED = 501; + public const HTTP_BAD_GATEWAY = 502; + public const HTTP_SERVICE_UNAVAILABLE = 503; + public const HTTP_GATEWAY_TIMEOUT = 504; + public const HTTP_VERSION_NOT_SUPPORTED = 505; + public const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295 + public const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918 + public const HTTP_LOOP_DETECTED = 508; // RFC5842 + public const HTTP_NOT_EXTENDED = 510; // RFC2774 + public const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585 /** - * @deprecated + * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control */ - const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425; // RFC2817 - const HTTP_TOO_EARLY = 425; // RFC-ietf-httpbis-replay-04 - const HTTP_UPGRADE_REQUIRED = 426; // RFC2817 - const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585 - const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585 - const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585 - const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; - const HTTP_INTERNAL_SERVER_ERROR = 500; - const HTTP_NOT_IMPLEMENTED = 501; - const HTTP_BAD_GATEWAY = 502; - const HTTP_SERVICE_UNAVAILABLE = 503; - const HTTP_GATEWAY_TIMEOUT = 504; - const HTTP_VERSION_NOT_SUPPORTED = 505; - const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295 - const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918 - const HTTP_LOOP_DETECTED = 508; // RFC5842 - const HTTP_NOT_EXTENDED = 510; // RFC2774 - const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585 + private const HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES = [ + 'must_revalidate' => false, + 'no_cache' => false, + 'no_store' => false, + 'no_transform' => false, + 'public' => false, + 'private' => false, + 'proxy_revalidate' => false, + 'max_age' => true, + 's_maxage' => true, + 'immutable' => false, + 'last_modified' => true, + 'etag' => true, + ]; /** * @var ResponseHeaderBag @@ -122,7 +138,7 @@ class Response * * The list of codes is complete according to the * {@link https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml Hypertext Transfer Protocol (HTTP) Status Code Registry} - * (last updated 2016-03-01). + * (last updated 2021-10-01). * * Unless otherwise noted, the status code is defined in RFC2616. * @@ -164,14 +180,14 @@ class Response 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', - 413 => 'Payload Too Large', + 413 => 'Content Too Large', // RFC-ietf-httpbis-semantics 414 => 'URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Range Not Satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', // RFC2324 421 => 'Misdirected Request', // RFC7540 - 422 => 'Unprocessable Entity', // RFC4918 + 422 => 'Unprocessable Content', // RFC-ietf-httpbis-semantics 423 => 'Locked', // RFC4918 424 => 'Failed Dependency', // RFC4918 425 => 'Too Early', // RFC-ietf-httpbis-replay-04 @@ -194,13 +210,9 @@ class Response ]; /** - * @param mixed $content The response content, see setContent() - * @param int $status The response status code - * @param array $headers An array of response headers - * * @throws \InvalidArgumentException When the HTTP status code is not valid */ - public function __construct($content = '', $status = 200, $headers = []) + public function __construct(?string $content = '', int $status = 200, array $headers = []) { $this->headers = new ResponseHeaderBag($headers); $this->setContent($content); @@ -216,14 +228,14 @@ class Response * return Response::create($body, 200) * ->setSharedMaxAge(300); * - * @param mixed $content The response content, see setContent() - * @param int $status The response status code - * @param array $headers An array of response headers - * * @return static + * + * @deprecated since Symfony 5.1, use __construct() instead. */ - public static function create($content = '', $status = 200, $headers = []) + public static function create(?string $content = '', int $status = 200, array $headers = []) { + trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); + return new static($content, $status, $headers); } @@ -234,7 +246,7 @@ class Response * one that will be sent to the client only if the prepare() method * has been called before. * - * @return string The Response as an HTTP string + * @return string * * @see prepare() */ @@ -271,10 +283,12 @@ class Response $this->setContent(null); $headers->remove('Content-Type'); $headers->remove('Content-Length'); + // prevent PHP from sending the Content-Type header based on default_mimetype + ini_set('default_mimetype', ''); } else { // Content-type based on the Request if (!$headers->has('Content-Type')) { - $format = $request->getRequestFormat(); + $format = $request->getRequestFormat(null); if (null !== $format && $mimeType = $request->getMimeType($format)) { $headers->set('Content-Type', $mimeType); } @@ -310,13 +324,19 @@ class Response } // Check if we need to send extra expire info headers - if ('1.0' == $this->getProtocolVersion() && false !== strpos($headers->get('Cache-Control'), 'no-cache')) { + if ('1.0' == $this->getProtocolVersion() && str_contains($headers->get('Cache-Control', ''), 'no-cache')) { $headers->set('pragma', 'no-cache'); $headers->set('expires', -1); } $this->ensureIEOverSSLCompatibility($request); + if ($request->isSecure()) { + foreach ($headers->getCookies() as $cookie) { + $cookie->setSecureDefault(true); + } + } + return $this; } @@ -375,6 +395,8 @@ class Response if (\function_exists('fastcgi_finish_request')) { fastcgi_finish_request(); + } elseif (\function_exists('litespeed_finish_request')) { + litespeed_finish_request(); } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { static::closeOutputBuffers(0, true); } @@ -385,21 +407,11 @@ class Response /** * Sets the response content. * - * Valid types are strings, numbers, null, and objects that implement a __toString() method. - * - * @param mixed $content Content that can be cast to string - * * @return $this - * - * @throws \UnexpectedValueException */ - public function setContent($content) + public function setContent(?string $content) { - if (null !== $content && !\is_string($content) && !is_numeric($content) && !\is_callable([$content, '__toString'])) { - throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', \gettype($content))); - } - - $this->content = (string) $content; + $this->content = $content ?? ''; return $this; } @@ -417,13 +429,11 @@ class Response /** * Sets the HTTP protocol version (1.0 or 1.1). * - * @param string $version The HTTP protocol version - * * @return $this * - * @final since version 3.2 + * @final */ - public function setProtocolVersion($version) + public function setProtocolVersion(string $version): object { $this->version = $version; @@ -433,11 +443,9 @@ class Response /** * Gets the HTTP protocol version. * - * @return string The HTTP protocol version - * - * @final since version 3.2 + * @final */ - public function getProtocolVersion() + public function getProtocolVersion(): string { return $this->version; } @@ -448,24 +456,21 @@ class Response * If the status text is null it will be automatically populated for the known * status codes and left empty otherwise. * - * @param int $code HTTP status code - * @param mixed $text HTTP status text - * * @return $this * * @throws \InvalidArgumentException When the HTTP status code is not valid * - * @final since version 3.2 + * @final */ - public function setStatusCode($code, $text = null) + public function setStatusCode(int $code, string $text = null): object { - $this->statusCode = $code = (int) $code; + $this->statusCode = $code; if ($this->isInvalid()) { throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code)); } if (null === $text) { - $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : 'unknown status'; + $this->statusText = self::$statusTexts[$code] ?? 'unknown status'; return $this; } @@ -484,11 +489,9 @@ class Response /** * Retrieves the status code for the current web response. * - * @return int Status code - * - * @final since version 3.2 + * @final */ - public function getStatusCode() + public function getStatusCode(): int { return $this->statusCode; } @@ -496,13 +499,11 @@ class Response /** * Sets the response charset. * - * @param string $charset Character set - * * @return $this * - * @final since version 3.2 + * @final */ - public function setCharset($charset) + public function setCharset(string $charset): object { $this->charset = $charset; @@ -512,11 +513,9 @@ class Response /** * Retrieves the response charset. * - * @return string Character set - * - * @final since version 3.2 + * @final */ - public function getCharset() + public function getCharset(): ?string { return $this->charset; } @@ -536,11 +535,9 @@ class Response * can be reused by a cache with heuristic expiration unless otherwise indicated" * (https://tools.ietf.org/html/rfc7231#section-6.1) * - * @return bool true if the response is worth caching, false otherwise - * - * @final since version 3.3 + * @final */ - public function isCacheable() + public function isCacheable(): bool { if (!\in_array($this->statusCode, [200, 203, 300, 301, 302, 404, 410])) { return false; @@ -560,11 +557,9 @@ class Response * origin. A response is considered fresh when it includes a Cache-Control/max-age * indicator or Expires header and the calculated age is less than the freshness lifetime. * - * @return bool true if the response is fresh, false otherwise - * - * @final since version 3.3 + * @final */ - public function isFresh() + public function isFresh(): bool { return $this->getTtl() > 0; } @@ -573,11 +568,9 @@ class Response * Returns true if the response includes headers that can be used to validate * the response with the origin server using a conditional GET request. * - * @return bool true if the response is validateable, false otherwise - * - * @final since version 3.3 + * @final */ - public function isValidateable() + public function isValidateable(): bool { return $this->headers->has('Last-Modified') || $this->headers->has('ETag'); } @@ -589,9 +582,9 @@ class Response * * @return $this * - * @final since version 3.2 + * @final */ - public function setPrivate() + public function setPrivate(): object { $this->headers->removeCacheControlDirective('public'); $this->headers->addCacheControlDirective('private'); @@ -606,9 +599,9 @@ class Response * * @return $this * - * @final since version 3.2 + * @final */ - public function setPublic() + public function setPublic(): object { $this->headers->addCacheControlDirective('public'); $this->headers->removeCacheControlDirective('private'); @@ -619,13 +612,11 @@ class Response /** * Marks the response as "immutable". * - * @param bool $immutable enables or disables the immutable directive - * * @return $this * * @final */ - public function setImmutable($immutable = true) + public function setImmutable(bool $immutable = true): object { if ($immutable) { $this->headers->addCacheControlDirective('immutable'); @@ -639,11 +630,9 @@ class Response /** * Returns true if the response is marked as "immutable". * - * @return bool returns true if the response is marked as "immutable"; otherwise false - * * @final */ - public function isImmutable() + public function isImmutable(): bool { return $this->headers->hasCacheControlDirective('immutable'); } @@ -656,11 +645,9 @@ class Response * When present, the TTL of the response should not be overridden to be * greater than the value provided by the origin. * - * @return bool true if the response must be revalidated by a cache, false otherwise - * - * @final since version 3.3 + * @final */ - public function mustRevalidate() + public function mustRevalidate(): bool { return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate'); } @@ -668,13 +655,11 @@ class Response /** * Returns the Date header as a DateTime instance. * - * @return \DateTime A \DateTime instance - * * @throws \RuntimeException When the header is not parseable * - * @final since version 3.2 + * @final */ - public function getDate() + public function getDate(): ?\DateTimeInterface { return $this->headers->getDate('Date'); } @@ -684,24 +669,26 @@ class Response * * @return $this * - * @final since version 3.2 + * @final */ - public function setDate(\DateTime $date) + public function setDate(\DateTimeInterface $date): object { - $date->setTimezone(new \DateTimeZone('UTC')); + if ($date instanceof \DateTime) { + $date = \DateTimeImmutable::createFromMutable($date); + } + + $date = $date->setTimezone(new \DateTimeZone('UTC')); $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT'); return $this; } /** - * Returns the age of the response. + * Returns the age of the response in seconds. * - * @return int The age of the response in seconds - * - * @final since version 3.2 + * @final */ - public function getAge() + public function getAge(): int { if (null !== $age = $this->headers->get('Age')) { return (int) $age; @@ -728,17 +715,15 @@ class Response /** * Returns the value of the Expires header as a DateTime instance. * - * @return \DateTime|null A DateTime instance or null if the header does not exist - * - * @final since version 3.2 + * @final */ - public function getExpires() + public function getExpires(): ?\DateTimeInterface { try { return $this->headers->getDate('Expires'); } catch (\RuntimeException $e) { // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past - return \DateTime::createFromFormat(\DATE_RFC2822, 'Sat, 01 Jan 00 00:00:00 +0000'); + return \DateTime::createFromFormat('U', time() - 172800); } } @@ -747,22 +732,25 @@ class Response * * Passing null as value will remove the header. * - * @param \DateTime|null $date A \DateTime instance or null to remove the header - * * @return $this * - * @final since version 3.2 + * @final */ - public function setExpires(\DateTime $date = null) + public function setExpires(\DateTimeInterface $date = null): object { if (null === $date) { $this->headers->remove('Expires'); - } else { - $date = clone $date; - $date->setTimezone(new \DateTimeZone('UTC')); - $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; } + if ($date instanceof \DateTime) { + $date = \DateTimeImmutable::createFromMutable($date); + } + + $date = $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT'); + return $this; } @@ -773,11 +761,9 @@ class Response * First, it checks for a s-maxage directive, then a max-age directive, and then it falls * back on an expires header. It returns null when no maximum age can be established. * - * @return int|null Number of seconds - * - * @final since version 3.2 + * @final */ - public function getMaxAge() + public function getMaxAge(): ?int { if ($this->headers->hasCacheControlDirective('s-maxage')) { return (int) $this->headers->getCacheControlDirective('s-maxage'); @@ -799,13 +785,11 @@ class Response * * This methods sets the Cache-Control max-age directive. * - * @param int $value Number of seconds - * * @return $this * - * @final since version 3.2 + * @final */ - public function setMaxAge($value) + public function setMaxAge(int $value): object { $this->headers->addCacheControlDirective('max-age', $value); @@ -817,13 +801,11 @@ class Response * * This methods sets the Cache-Control s-maxage directive. * - * @param int $value Number of seconds - * * @return $this * - * @final since version 3.2 + * @final */ - public function setSharedMaxAge($value) + public function setSharedMaxAge(int $value): object { $this->setPublic(); $this->headers->addCacheControlDirective('s-maxage', $value); @@ -839,31 +821,25 @@ class Response * When the responses TTL is <= 0, the response may not be served from cache without first * revalidating with the origin. * - * @return int|null The TTL in seconds - * - * @final since version 3.2 + * @final */ - public function getTtl() + public function getTtl(): ?int { - if (null !== $maxAge = $this->getMaxAge()) { - return $maxAge - $this->getAge(); - } + $maxAge = $this->getMaxAge(); - return null; + return null !== $maxAge ? $maxAge - $this->getAge() : null; } /** - * Sets the response's time-to-live for shared caches. + * Sets the response's time-to-live for shared caches in seconds. * * This method adjusts the Cache-Control/s-maxage directive. * - * @param int $seconds Number of seconds - * * @return $this * - * @final since version 3.2 + * @final */ - public function setTtl($seconds) + public function setTtl(int $seconds): object { $this->setSharedMaxAge($this->getAge() + $seconds); @@ -871,17 +847,15 @@ class Response } /** - * Sets the response's time-to-live for private/client caches. + * Sets the response's time-to-live for private/client caches in seconds. * * This method adjusts the Cache-Control/max-age directive. * - * @param int $seconds Number of seconds - * * @return $this * - * @final since version 3.2 + * @final */ - public function setClientTtl($seconds) + public function setClientTtl(int $seconds): object { $this->setMaxAge($this->getAge() + $seconds); @@ -891,13 +865,11 @@ class Response /** * Returns the Last-Modified HTTP header as a DateTime instance. * - * @return \DateTime|null A DateTime instance or null if the header does not exist - * * @throws \RuntimeException When the HTTP header is not parseable * - * @final since version 3.2 + * @final */ - public function getLastModified() + public function getLastModified(): ?\DateTimeInterface { return $this->headers->getDate('Last-Modified'); } @@ -907,33 +879,34 @@ class Response * * Passing null as value will remove the header. * - * @param \DateTime|null $date A \DateTime instance or null to remove the header - * * @return $this * - * @final since version 3.2 + * @final */ - public function setLastModified(\DateTime $date = null) + public function setLastModified(\DateTimeInterface $date = null): object { if (null === $date) { $this->headers->remove('Last-Modified'); - } else { - $date = clone $date; - $date->setTimezone(new \DateTimeZone('UTC')); - $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; } + if ($date instanceof \DateTime) { + $date = \DateTimeImmutable::createFromMutable($date); + } + + $date = $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT'); + return $this; } /** * Returns the literal value of the ETag HTTP header. * - * @return string|null The ETag HTTP header or null if it does not exist - * - * @final since version 3.2 + * @final */ - public function getEtag() + public function getEtag(): ?string { return $this->headers->get('ETag'); } @@ -946,14 +919,14 @@ class Response * * @return $this * - * @final since version 3.2 + * @final */ - public function setEtag($etag = null, $weak = false) + public function setEtag(string $etag = null, bool $weak = false): object { if (null === $etag) { $this->headers->remove('Etag'); } else { - if (0 !== strpos($etag, '"')) { + if (!str_starts_with($etag, '"')) { $etag = '"'.$etag.'"'; } @@ -966,19 +939,17 @@ class Response /** * Sets the response's cache headers (validation and/or expiration). * - * Available options are: etag, last_modified, max_age, s_maxage, private, public and immutable. - * - * @param array $options An array of cache options + * Available options are: must_revalidate, no_cache, no_store, no_transform, public, private, proxy_revalidate, max_age, s_maxage, immutable, last_modified and etag. * * @return $this * * @throws \InvalidArgumentException * - * @final since version 3.3 + * @final */ - public function setCache(array $options) + public function setCache(array $options): object { - if ($diff = array_diff(array_keys($options), ['etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public', 'immutable'])) { + if ($diff = array_diff(array_keys($options), array_keys(self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES))) { throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', $diff))); } @@ -998,6 +969,16 @@ class Response $this->setSharedMaxAge($options['s_maxage']); } + foreach (self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES as $directive => $hasValue) { + if (!$hasValue && isset($options[$directive])) { + if ($options[$directive]) { + $this->headers->addCacheControlDirective(str_replace('_', '-', $directive)); + } else { + $this->headers->removeCacheControlDirective(str_replace('_', '-', $directive)); + } + } + } + if (isset($options['public'])) { if ($options['public']) { $this->setPublic(); @@ -1014,10 +995,6 @@ class Response } } - if (isset($options['immutable'])) { - $this->setImmutable((bool) $options['immutable']); - } - return $this; } @@ -1031,9 +1008,9 @@ class Response * * @see https://tools.ietf.org/html/rfc2616#section-10.3.5 * - * @final since version 3.3 + * @final */ - public function setNotModified() + public function setNotModified(): object { $this->setStatusCode(304); $this->setContent(null); @@ -1049,11 +1026,9 @@ class Response /** * Returns true if the response includes a Vary header. * - * @return bool true if the response includes a Vary header, false otherwise - * - * @final since version 3.2 + * @final */ - public function hasVary() + public function hasVary(): bool { return null !== $this->headers->get('Vary'); } @@ -1061,22 +1036,20 @@ class Response /** * Returns an array of header names given in the Vary header. * - * @return array An array of Vary names - * - * @final since version 3.2 + * @final */ - public function getVary() + public function getVary(): array { - if (!$vary = $this->headers->get('Vary', null, false)) { + if (!$vary = $this->headers->all('Vary')) { return []; } $ret = []; foreach ($vary as $item) { - $ret = array_merge($ret, preg_split('/[\s,]+/', $item)); + $ret[] = preg_split('/[\s,]+/', $item); } - return $ret; + return array_merge([], ...$ret); } /** @@ -1087,9 +1060,9 @@ class Response * * @return $this * - * @final since version 3.2 + * @final */ - public function setVary($headers, $replace = true) + public function setVary($headers, bool $replace = true): object { $this->headers->set('Vary', $headers, $replace); @@ -1103,11 +1076,9 @@ class Response * If the Response is not modified, it sets the status code to 304 and * removes the actual content by calling the setNotModified() method. * - * @return bool true if the Response validators match the Request, false otherwise - * - * @final since version 3.3 + * @final */ - public function isNotModified(Request $request) + public function isNotModified(Request $request): bool { if (!$request->isMethodCacheable()) { return false; @@ -1117,12 +1088,26 @@ class Response $lastModified = $this->headers->get('Last-Modified'); $modifiedSince = $request->headers->get('If-Modified-Since'); - if ($etags = $request->getETags()) { - $notModified = \in_array($this->getEtag(), $etags) || \in_array('*', $etags); - } + if (($ifNoneMatchEtags = $request->getETags()) && (null !== $etag = $this->getEtag())) { + if (0 == strncmp($etag, 'W/', 2)) { + $etag = substr($etag, 2); + } - if ($modifiedSince && $lastModified) { - $notModified = strtotime($modifiedSince) >= strtotime($lastModified) && (!$etags || $notModified); + // Use weak comparison as per https://tools.ietf.org/html/rfc7232#section-3.2. + foreach ($ifNoneMatchEtags as $ifNoneMatchEtag) { + if (0 == strncmp($ifNoneMatchEtag, 'W/', 2)) { + $ifNoneMatchEtag = substr($ifNoneMatchEtag, 2); + } + + if ($ifNoneMatchEtag === $etag || '*' === $ifNoneMatchEtag) { + $notModified = true; + break; + } + } + } + // Only do If-Modified-Since date comparison when If-None-Match is not present as per https://tools.ietf.org/html/rfc7232#section-3.3. + elseif ($modifiedSince && $lastModified) { + $notModified = strtotime($modifiedSince) >= strtotime($lastModified); } if ($notModified) { @@ -1135,13 +1120,11 @@ class Response /** * Is response invalid? * - * @return bool - * * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html * - * @final since version 3.2 + * @final */ - public function isInvalid() + public function isInvalid(): bool { return $this->statusCode < 100 || $this->statusCode >= 600; } @@ -1149,11 +1132,9 @@ class Response /** * Is response informative? * - * @return bool - * - * @final since version 3.3 + * @final */ - public function isInformational() + public function isInformational(): bool { return $this->statusCode >= 100 && $this->statusCode < 200; } @@ -1161,11 +1142,9 @@ class Response /** * Is response successful? * - * @return bool - * - * @final since version 3.2 + * @final */ - public function isSuccessful() + public function isSuccessful(): bool { return $this->statusCode >= 200 && $this->statusCode < 300; } @@ -1173,11 +1152,9 @@ class Response /** * Is the response a redirect? * - * @return bool - * - * @final since version 3.2 + * @final */ - public function isRedirection() + public function isRedirection(): bool { return $this->statusCode >= 300 && $this->statusCode < 400; } @@ -1185,11 +1162,9 @@ class Response /** * Is there a client error? * - * @return bool - * - * @final since version 3.2 + * @final */ - public function isClientError() + public function isClientError(): bool { return $this->statusCode >= 400 && $this->statusCode < 500; } @@ -1197,11 +1172,9 @@ class Response /** * Was there a server side error? * - * @return bool - * - * @final since version 3.3 + * @final */ - public function isServerError() + public function isServerError(): bool { return $this->statusCode >= 500 && $this->statusCode < 600; } @@ -1209,11 +1182,9 @@ class Response /** * Is the response OK? * - * @return bool - * - * @final since version 3.2 + * @final */ - public function isOk() + public function isOk(): bool { return 200 === $this->statusCode; } @@ -1221,11 +1192,9 @@ class Response /** * Is the response forbidden? * - * @return bool - * - * @final since version 3.2 + * @final */ - public function isForbidden() + public function isForbidden(): bool { return 403 === $this->statusCode; } @@ -1233,11 +1202,9 @@ class Response /** * Is the response a not found error? * - * @return bool - * - * @final since version 3.2 + * @final */ - public function isNotFound() + public function isNotFound(): bool { return 404 === $this->statusCode; } @@ -1245,13 +1212,9 @@ class Response /** * Is the response a redirect of some form? * - * @param string $location - * - * @return bool - * - * @final since version 3.2 + * @final */ - public function isRedirect($location = null) + public function isRedirect(string $location = null): bool { return \in_array($this->statusCode, [201, 301, 302, 303, 307, 308]) && (null === $location ?: $location == $this->headers->get('Location')); } @@ -1259,11 +1222,9 @@ class Response /** * Is the response empty? * - * @return bool - * - * @final since version 3.2 + * @final */ - public function isEmpty() + public function isEmpty(): bool { return \in_array($this->statusCode, [204, 304]); } @@ -1273,37 +1234,50 @@ class Response * * Resulting level can be greater than target level if a non-removable buffer has been encountered. * - * @param int $targetLevel The target output buffering level - * @param bool $flush Whether to flush or clean the buffers - * - * @final since version 3.3 + * @final */ - public static function closeOutputBuffers($targetLevel, $flush) + public static function closeOutputBuffers(int $targetLevel, bool $flush): void { $status = ob_get_status(true); $level = \count($status); - // PHP_OUTPUT_HANDLER_* are not defined on HHVM 3.3 - $flags = \defined('PHP_OUTPUT_HANDLER_REMOVABLE') ? \PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? \PHP_OUTPUT_HANDLER_FLUSHABLE : \PHP_OUTPUT_HANDLER_CLEANABLE) : -1; + $flags = \PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? \PHP_OUTPUT_HANDLER_FLUSHABLE : \PHP_OUTPUT_HANDLER_CLEANABLE); while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) { if ($flush) { ob_end_flush(); + flush(); } else { ob_end_clean(); } } } + /** + * Marks a response as safe according to RFC8674. + * + * @see https://tools.ietf.org/html/rfc8674 + */ + public function setContentSafe(bool $safe = true): void + { + if ($safe) { + $this->headers->set('Preference-Applied', 'safe'); + } elseif ('safe' === $this->headers->get('Preference-Applied')) { + $this->headers->remove('Preference-Applied'); + } + + $this->setVary('Prefer', false); + } + /** * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9. * * @see http://support.microsoft.com/kb/323308 * - * @final since version 3.3 + * @final */ - protected function ensureIEOverSSLCompatibility(Request $request) + protected function ensureIEOverSSLCompatibility(Request $request): void { - if (false !== stripos($this->headers->get('Content-Disposition'), 'attachment') && 1 == preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT'), $match) && true === $request->isSecure()) { + if (false !== stripos($this->headers->get('Content-Disposition') ?? '', 'attachment') && 1 == preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT') ?? '', $match) && true === $request->isSecure()) { if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) { $this->headers->remove('Cache-Control'); } diff --git a/lib/symfony/http-foundation/ResponseHeaderBag.php b/lib/symfony/http-foundation/ResponseHeaderBag.php index 9a6f87648f..1df13fa21b 100644 --- a/lib/symfony/http-foundation/ResponseHeaderBag.php +++ b/lib/symfony/http-foundation/ResponseHeaderBag.php @@ -18,11 +18,11 @@ namespace Symfony\Component\HttpFoundation; */ class ResponseHeaderBag extends HeaderBag { - const COOKIES_FLAT = 'flat'; - const COOKIES_ARRAY = 'array'; + public const COOKIES_FLAT = 'flat'; + public const COOKIES_ARRAY = 'array'; - const DISPOSITION_ATTACHMENT = 'attachment'; - const DISPOSITION_INLINE = 'inline'; + public const DISPOSITION_ATTACHMENT = 'attachment'; + public const DISPOSITION_INLINE = 'inline'; protected $computedCacheControl = []; protected $cookies = []; @@ -45,13 +45,13 @@ class ResponseHeaderBag extends HeaderBag /** * Returns the headers, with original capitalizations. * - * @return array An array of headers + * @return array */ public function allPreserveCase() { $headers = []; foreach ($this->all() as $name => $value) { - $headers[isset($this->headerNames[$name]) ? $this->headerNames[$name] : $name] = $value; + $headers[$this->headerNames[$name] ?? $name] = $value; } return $headers; @@ -88,9 +88,16 @@ class ResponseHeaderBag extends HeaderBag /** * {@inheritdoc} */ - public function all() + public function all(string $key = null) { $headers = parent::all(); + + if (null !== $key) { + $key = strtr($key, self::UPPER, self::LOWER); + + return 'set-cookie' !== $key ? $headers[$key] ?? [] : array_map('strval', $this->getCookies()); + } + foreach ($this->getCookies() as $cookie) { $headers['set-cookie'][] = (string) $cookie; } @@ -101,9 +108,9 @@ class ResponseHeaderBag extends HeaderBag /** * {@inheritdoc} */ - public function set($key, $values, $replace = true) + public function set(string $key, $values, bool $replace = true) { - $uniqueKey = str_replace('_', '-', strtolower($key)); + $uniqueKey = strtr($key, self::UPPER, self::LOWER); if ('set-cookie' === $uniqueKey) { if ($replace) { @@ -122,8 +129,7 @@ class ResponseHeaderBag extends HeaderBag parent::set($key, $values, $replace); // ensure the cache-control header has sensible defaults - if (\in_array($uniqueKey, ['cache-control', 'etag', 'last-modified', 'expires'], true)) { - $computed = $this->computeCacheControlValue(); + if (\in_array($uniqueKey, ['cache-control', 'etag', 'last-modified', 'expires'], true) && '' !== $computed = $this->computeCacheControlValue()) { $this->headers['cache-control'] = [$computed]; $this->headerNames['cache-control'] = 'Cache-Control'; $this->computedCacheControl = $this->parseCacheControl($computed); @@ -133,9 +139,9 @@ class ResponseHeaderBag extends HeaderBag /** * {@inheritdoc} */ - public function remove($key) + public function remove(string $key) { - $uniqueKey = str_replace('_', '-', strtolower($key)); + $uniqueKey = strtr($key, self::UPPER, self::LOWER); unset($this->headerNames[$uniqueKey]); if ('set-cookie' === $uniqueKey) { @@ -158,7 +164,7 @@ class ResponseHeaderBag extends HeaderBag /** * {@inheritdoc} */ - public function hasCacheControlDirective($key) + public function hasCacheControlDirective(string $key) { return \array_key_exists($key, $this->computedCacheControl); } @@ -166,9 +172,9 @@ class ResponseHeaderBag extends HeaderBag /** * {@inheritdoc} */ - public function getCacheControlDirective($key) + public function getCacheControlDirective(string $key) { - return \array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null; + return $this->computedCacheControl[$key] ?? null; } public function setCookie(Cookie $cookie) @@ -179,12 +185,8 @@ class ResponseHeaderBag extends HeaderBag /** * Removes a cookie from the array, but does not unset it in the browser. - * - * @param string $name - * @param string $path - * @param string $domain */ - public function removeCookie($name, $path = '/', $domain = null) + public function removeCookie(string $name, ?string $path = '/', string $domain = null) { if (null === $path) { $path = '/'; @@ -208,13 +210,11 @@ class ResponseHeaderBag extends HeaderBag /** * Returns an array with all cookies. * - * @param string $format - * * @return Cookie[] * * @throws \InvalidArgumentException When the $format is invalid */ - public function getCookies($format = self::COOKIES_FLAT) + public function getCookies(string $format = self::COOKIES_FLAT) { if (!\in_array($format, [self::COOKIES_FLAT, self::COOKIES_ARRAY])) { throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', [self::COOKIES_FLAT, self::COOKIES_ARRAY]))); @@ -238,68 +238,18 @@ class ResponseHeaderBag extends HeaderBag /** * Clears a cookie in the browser. - * - * @param string $name - * @param string $path - * @param string $domain - * @param bool $secure - * @param bool $httpOnly - * @param string $sameSite */ - public function clearCookie($name, $path = '/', $domain = null, $secure = false, $httpOnly = true/*, $sameSite = null*/) + public function clearCookie(string $name, ?string $path = '/', string $domain = null, bool $secure = false, bool $httpOnly = true, string $sameSite = null) { - $sameSite = \func_num_args() > 5 ? func_get_arg(5) : null; - $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly, false, $sameSite)); } /** - * Generates a HTTP Content-Disposition field-value. - * - * @param string $disposition One of "inline" or "attachment" - * @param string $filename A unicode string - * @param string $filenameFallback A string containing only ASCII characters that - * is semantically equivalent to $filename. If the filename is already ASCII, - * it can be omitted, or just copied from $filename - * - * @return string A string suitable for use as a Content-Disposition field-value - * - * @throws \InvalidArgumentException - * - * @see RFC 6266 + * @see HeaderUtils::makeDisposition() */ - public function makeDisposition($disposition, $filename, $filenameFallback = '') + public function makeDisposition(string $disposition, string $filename, string $filenameFallback = '') { - if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) { - throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE)); - } - - if ('' == $filenameFallback) { - $filenameFallback = $filename; - } - - // filenameFallback is not ASCII. - if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) { - throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.'); - } - - // percent characters aren't safe in fallback. - if (false !== strpos($filenameFallback, '%')) { - throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.'); - } - - // path separators aren't allowed in either. - if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) { - throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.'); - } - - $output = sprintf('%s; filename="%s"', $disposition, str_replace('"', '\\"', $filenameFallback)); - - if ($filename !== $filenameFallback) { - $output .= sprintf("; filename*=utf-8''%s", rawurlencode($filename)); - } - - return $output; + return HeaderUtils::makeDisposition($disposition, $filename, $filenameFallback); } /** @@ -334,10 +284,8 @@ class ResponseHeaderBag extends HeaderBag return $header; } - private function initDate() + private function initDate(): void { - $now = \DateTime::createFromFormat('U', time()); - $now->setTimezone(new \DateTimeZone('UTC')); - $this->set('Date', $now->format('D, d M Y H:i:s').' GMT'); + $this->set('Date', gmdate('D, d M Y H:i:s').' GMT'); } } diff --git a/lib/symfony/http-foundation/ServerBag.php b/lib/symfony/http-foundation/ServerBag.php index f3b6402348..25688d5230 100644 --- a/lib/symfony/http-foundation/ServerBag.php +++ b/lib/symfony/http-foundation/ServerBag.php @@ -28,20 +28,17 @@ class ServerBag extends ParameterBag public function getHeaders() { $headers = []; - $contentHeaders = ['CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true]; foreach ($this->parameters as $key => $value) { - if (0 === strpos($key, 'HTTP_')) { + if (str_starts_with($key, 'HTTP_')) { $headers[substr($key, 5)] = $value; - } - // CONTENT_* are not prefixed with HTTP_ - elseif (isset($contentHeaders[$key])) { + } elseif (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true)) { $headers[$key] = $value; } } if (isset($this->parameters['PHP_AUTH_USER'])) { $headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER']; - $headers['PHP_AUTH_PW'] = isset($this->parameters['PHP_AUTH_PW']) ? $this->parameters['PHP_AUTH_PW'] : ''; + $headers['PHP_AUTH_PW'] = $this->parameters['PHP_AUTH_PW'] ?? ''; } else { /* * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default @@ -69,7 +66,7 @@ class ServerBag extends ParameterBag // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic $exploded = explode(':', base64_decode(substr($authorizationHeader, 6)), 2); if (2 == \count($exploded)) { - list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded; + [$headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']] = $exploded; } } elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest '))) { // In some circumstances PHP_AUTH_DIGEST needs to be set @@ -92,7 +89,7 @@ class ServerBag extends ParameterBag // PHP_AUTH_USER/PHP_AUTH_PW if (isset($headers['PHP_AUTH_USER'])) { - $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']); + $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.($headers['PHP_AUTH_PW'] ?? '')); } elseif (isset($headers['PHP_AUTH_DIGEST'])) { $headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST']; } diff --git a/lib/symfony/http-foundation/Session/Attribute/AttributeBag.php b/lib/symfony/http-foundation/Session/Attribute/AttributeBag.php index 07118e891b..f4f051c7a6 100644 --- a/lib/symfony/http-foundation/Session/Attribute/AttributeBag.php +++ b/lib/symfony/http-foundation/Session/Attribute/AttributeBag.php @@ -13,6 +13,8 @@ namespace Symfony\Component\HttpFoundation\Session\Attribute; /** * This class relates to session attribute storage. + * + * @implements \IteratorAggregate */ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable { @@ -24,7 +26,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta /** * @param string $storageKey The key used to store attributes in the session */ - public function __construct($storageKey = '_sf2_attributes') + public function __construct(string $storageKey = '_sf2_attributes') { $this->storageKey = $storageKey; } @@ -37,7 +39,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta return $this->name; } - public function setName($name) + public function setName(string $name) { $this->name = $name; } @@ -61,7 +63,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta /** * {@inheritdoc} */ - public function has($name) + public function has(string $name) { return \array_key_exists($name, $this->attributes); } @@ -69,7 +71,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta /** * {@inheritdoc} */ - public function get($name, $default = null) + public function get(string $name, $default = null) { return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; } @@ -77,7 +79,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta /** * {@inheritdoc} */ - public function set($name, $value) + public function set(string $name, $value) { $this->attributes[$name] = $value; } @@ -104,7 +106,7 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta /** * {@inheritdoc} */ - public function remove($name) + public function remove(string $name) { $retval = null; if (\array_key_exists($name, $this->attributes)) { @@ -129,8 +131,9 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta /** * Returns an iterator for attributes. * - * @return \ArrayIterator An \ArrayIterator instance + * @return \ArrayIterator */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->attributes); @@ -139,8 +142,9 @@ class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Counta /** * Returns the number of attributes. * - * @return int The number of attributes + * @return int */ + #[\ReturnTypeWillChange] public function count() { return \count($this->attributes); diff --git a/lib/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php b/lib/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php index 0d8d17991b..cb50696814 100644 --- a/lib/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php +++ b/lib/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php @@ -23,50 +23,39 @@ interface AttributeBagInterface extends SessionBagInterface /** * Checks if an attribute is defined. * - * @param string $name The attribute name - * - * @return bool true if the attribute is defined, false otherwise + * @return bool */ - public function has($name); + public function has(string $name); /** * Returns an attribute. * - * @param string $name The attribute name - * @param mixed $default The default value if not found + * @param mixed $default The default value if not found * * @return mixed */ - public function get($name, $default = null); + public function get(string $name, $default = null); /** * Sets an attribute. * - * @param string $name - * @param mixed $value + * @param mixed $value */ - public function set($name, $value); + public function set(string $name, $value); /** * Returns attributes. * - * @return array Attributes + * @return array */ public function all(); - /** - * Sets attributes. - * - * @param array $attributes Attributes - */ public function replace(array $attributes); /** * Removes an attribute. * - * @param string $name - * * @return mixed The removed value or null when it does not exist */ - public function remove($name); + public function remove(string $name); } diff --git a/lib/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php b/lib/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php index 07885e7fbf..864b35fb70 100644 --- a/lib/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php +++ b/lib/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php @@ -11,11 +11,15 @@ namespace Symfony\Component\HttpFoundation\Session\Attribute; +trigger_deprecation('symfony/http-foundation', '5.3', 'The "%s" class is deprecated.', NamespacedAttributeBag::class); + /** * This class provides structured storage of session attributes using * a name spacing character in the key. * * @author Drak + * + * @deprecated since Symfony 5.3 */ class NamespacedAttributeBag extends AttributeBag { @@ -25,7 +29,7 @@ class NamespacedAttributeBag extends AttributeBag * @param string $storageKey Session storage key * @param string $namespaceCharacter Namespace character to use in keys */ - public function __construct($storageKey = '_sf2_attributes', $namespaceCharacter = '/') + public function __construct(string $storageKey = '_sf2_attributes', string $namespaceCharacter = '/') { $this->namespaceCharacter = $namespaceCharacter; parent::__construct($storageKey); @@ -34,7 +38,7 @@ class NamespacedAttributeBag extends AttributeBag /** * {@inheritdoc} */ - public function has($name) + public function has(string $name) { // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is $attributes = $this->resolveAttributePath($name); @@ -50,7 +54,7 @@ class NamespacedAttributeBag extends AttributeBag /** * {@inheritdoc} */ - public function get($name, $default = null) + public function get(string $name, $default = null) { // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is $attributes = $this->resolveAttributePath($name); @@ -66,7 +70,7 @@ class NamespacedAttributeBag extends AttributeBag /** * {@inheritdoc} */ - public function set($name, $value) + public function set(string $name, $value) { $attributes = &$this->resolveAttributePath($name, true); $name = $this->resolveKey($name); @@ -76,7 +80,7 @@ class NamespacedAttributeBag extends AttributeBag /** * {@inheritdoc} */ - public function remove($name) + public function remove(string $name) { $retval = null; $attributes = &$this->resolveAttributePath($name); @@ -99,10 +103,10 @@ class NamespacedAttributeBag extends AttributeBag * * @return array|null */ - protected function &resolveAttributePath($name, $writeContext = false) + protected function &resolveAttributePath(string $name, bool $writeContext = false) { $array = &$this->attributes; - $name = (0 === strpos($name, $this->namespaceCharacter)) ? substr($name, 1) : $name; + $name = (str_starts_with($name, $this->namespaceCharacter)) ? substr($name, 1) : $name; // Check if there is anything to do, else return if (!$name) { @@ -144,11 +148,9 @@ class NamespacedAttributeBag extends AttributeBag * * This is the last part in a dot separated string. * - * @param string $name - * * @return string */ - protected function resolveKey($name) + protected function resolveKey(string $name) { if (false !== $pos = strrpos($name, $this->namespaceCharacter)) { $name = substr($name, $pos + 1); diff --git a/lib/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php b/lib/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php index 451c4a5a1d..8aab3a1228 100644 --- a/lib/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php +++ b/lib/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php @@ -25,7 +25,7 @@ class AutoExpireFlashBag implements FlashBagInterface /** * @param string $storageKey The key used to store flashes in the session */ - public function __construct($storageKey = '_symfony_flashes') + public function __construct(string $storageKey = '_symfony_flashes') { $this->storageKey = $storageKey; } @@ -38,7 +38,7 @@ class AutoExpireFlashBag implements FlashBagInterface return $this->name; } - public function setName($name) + public function setName(string $name) { $this->name = $name; } @@ -60,7 +60,7 @@ class AutoExpireFlashBag implements FlashBagInterface /** * {@inheritdoc} */ - public function add($type, $message) + public function add(string $type, $message) { $this->flashes['new'][$type][] = $message; } @@ -68,7 +68,7 @@ class AutoExpireFlashBag implements FlashBagInterface /** * {@inheritdoc} */ - public function peek($type, array $default = []) + public function peek(string $type, array $default = []) { return $this->has($type) ? $this->flashes['display'][$type] : $default; } @@ -78,13 +78,13 @@ class AutoExpireFlashBag implements FlashBagInterface */ public function peekAll() { - return \array_key_exists('display', $this->flashes) ? (array) $this->flashes['display'] : []; + return \array_key_exists('display', $this->flashes) ? $this->flashes['display'] : []; } /** * {@inheritdoc} */ - public function get($type, array $default = []) + public function get(string $type, array $default = []) { $return = $default; @@ -122,7 +122,7 @@ class AutoExpireFlashBag implements FlashBagInterface /** * {@inheritdoc} */ - public function set($type, $messages) + public function set(string $type, $messages) { $this->flashes['new'][$type] = (array) $messages; } @@ -130,7 +130,7 @@ class AutoExpireFlashBag implements FlashBagInterface /** * {@inheritdoc} */ - public function has($type) + public function has(string $type) { return \array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type]; } diff --git a/lib/symfony/http-foundation/Session/Flash/FlashBag.php b/lib/symfony/http-foundation/Session/Flash/FlashBag.php index f5d984af08..88df7508ac 100644 --- a/lib/symfony/http-foundation/Session/Flash/FlashBag.php +++ b/lib/symfony/http-foundation/Session/Flash/FlashBag.php @@ -25,7 +25,7 @@ class FlashBag implements FlashBagInterface /** * @param string $storageKey The key used to store flashes in the session */ - public function __construct($storageKey = '_symfony_flashes') + public function __construct(string $storageKey = '_symfony_flashes') { $this->storageKey = $storageKey; } @@ -38,7 +38,7 @@ class FlashBag implements FlashBagInterface return $this->name; } - public function setName($name) + public function setName(string $name) { $this->name = $name; } @@ -54,7 +54,7 @@ class FlashBag implements FlashBagInterface /** * {@inheritdoc} */ - public function add($type, $message) + public function add(string $type, $message) { $this->flashes[$type][] = $message; } @@ -62,7 +62,7 @@ class FlashBag implements FlashBagInterface /** * {@inheritdoc} */ - public function peek($type, array $default = []) + public function peek(string $type, array $default = []) { return $this->has($type) ? $this->flashes[$type] : $default; } @@ -78,7 +78,7 @@ class FlashBag implements FlashBagInterface /** * {@inheritdoc} */ - public function get($type, array $default = []) + public function get(string $type, array $default = []) { if (!$this->has($type)) { return $default; @@ -105,7 +105,7 @@ class FlashBag implements FlashBagInterface /** * {@inheritdoc} */ - public function set($type, $messages) + public function set(string $type, $messages) { $this->flashes[$type] = (array) $messages; } @@ -121,7 +121,7 @@ class FlashBag implements FlashBagInterface /** * {@inheritdoc} */ - public function has($type) + public function has(string $type) { return \array_key_exists($type, $this->flashes) && $this->flashes[$type]; } diff --git a/lib/symfony/http-foundation/Session/Flash/FlashBagInterface.php b/lib/symfony/http-foundation/Session/Flash/FlashBagInterface.php index 99e8074214..8713e71d0f 100644 --- a/lib/symfony/http-foundation/Session/Flash/FlashBagInterface.php +++ b/lib/symfony/http-foundation/Session/Flash/FlashBagInterface.php @@ -23,18 +23,16 @@ interface FlashBagInterface extends SessionBagInterface /** * Adds a flash message for the given type. * - * @param string $type - * @param mixed $message + * @param mixed $message */ - public function add($type, $message); + public function add(string $type, $message); /** * Registers one or more messages for a given type. * - * @param string $type * @param string|array $messages */ - public function set($type, $messages); + public function set(string $type, $messages); /** * Gets flash messages for a given type. @@ -44,7 +42,7 @@ interface FlashBagInterface extends SessionBagInterface * * @return array */ - public function peek($type, array $default = []); + public function peek(string $type, array $default = []); /** * Gets all flash messages. @@ -56,12 +54,11 @@ interface FlashBagInterface extends SessionBagInterface /** * Gets and clears flash from the stack. * - * @param string $type - * @param array $default Default value if $type does not exist + * @param array $default Default value if $type does not exist * * @return array */ - public function get($type, array $default = []); + public function get(string $type, array $default = []); /** * Gets and clears flashes from the stack. @@ -78,11 +75,9 @@ interface FlashBagInterface extends SessionBagInterface /** * Has flash messages for a given type? * - * @param string $type - * * @return bool */ - public function has($type); + public function has(string $type); /** * Returns a list of all defined types. diff --git a/lib/symfony/http-foundation/Session/Session.php b/lib/symfony/http-foundation/Session/Session.php index db0b9aeb0b..022e3986fe 100644 --- a/lib/symfony/http-foundation/Session/Session.php +++ b/lib/symfony/http-foundation/Session/Session.php @@ -18,9 +18,16 @@ use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; +// Help opcache.preload discover always-needed symbols +class_exists(AttributeBag::class); +class_exists(FlashBag::class); +class_exists(SessionBagProxy::class); + /** * @author Fabien Potencier * @author Drak + * + * @implements \IteratorAggregate */ class Session implements SessionInterface, \IteratorAggregate, \Countable { @@ -30,21 +37,18 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable private $attributeName; private $data = []; private $usageIndex = 0; + private $usageReporter; - /** - * @param SessionStorageInterface $storage A SessionStorageInterface instance - * @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag) - * @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for default FlashBag) - */ - public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) + public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null, callable $usageReporter = null) { - $this->storage = $storage ?: new NativeSessionStorage(); + $this->storage = $storage ?? new NativeSessionStorage(); + $this->usageReporter = $usageReporter; - $attributes = $attributes ?: new AttributeBag(); + $attributes = $attributes ?? new AttributeBag(); $this->attributeName = $attributes->getName(); $this->registerBag($attributes); - $flashes = $flashes ?: new FlashBag(); + $flashes = $flashes ?? new FlashBag(); $this->flashName = $flashes->getName(); $this->registerBag($flashes); } @@ -60,7 +64,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable /** * {@inheritdoc} */ - public function has($name) + public function has(string $name) { return $this->getAttributeBag()->has($name); } @@ -68,7 +72,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable /** * {@inheritdoc} */ - public function get($name, $default = null) + public function get(string $name, $default = null) { return $this->getAttributeBag()->get($name, $default); } @@ -76,7 +80,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable /** * {@inheritdoc} */ - public function set($name, $value) + public function set(string $name, $value) { $this->getAttributeBag()->set($name, $value); } @@ -100,7 +104,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable /** * {@inheritdoc} */ - public function remove($name) + public function remove(string $name) { return $this->getAttributeBag()->remove($name); } @@ -124,8 +128,9 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable /** * Returns an iterator for attributes. * - * @return \ArrayIterator An \ArrayIterator instance + * @return \ArrayIterator */ + #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->getAttributeBag()->all()); @@ -134,32 +139,29 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable /** * Returns the number of attributes. * - * @return int The number of attributes + * @return int */ + #[\ReturnTypeWillChange] public function count() { return \count($this->getAttributeBag()->all()); } - /** - * @return int - * - * @internal - */ - public function getUsageIndex() + public function &getUsageIndex(): int { return $this->usageIndex; } /** - * @return bool - * * @internal */ - public function isEmpty() + public function isEmpty(): bool { if ($this->isStarted()) { ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } } foreach ($this->data as &$data) { if (!empty($data)) { @@ -173,7 +175,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable /** * {@inheritdoc} */ - public function invalidate($lifetime = null) + public function invalidate(int $lifetime = null) { $this->storage->clear(); @@ -183,7 +185,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable /** * {@inheritdoc} */ - public function migrate($destroy = false, $lifetime = null) + public function migrate(bool $destroy = false, int $lifetime = null) { return $this->storage->regenerate($destroy, $lifetime); } @@ -207,7 +209,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable /** * {@inheritdoc} */ - public function setId($id) + public function setId(string $id) { if ($this->storage->getId() !== $id) { $this->storage->setId($id); @@ -225,7 +227,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable /** * {@inheritdoc} */ - public function setName($name) + public function setName(string $name) { $this->storage->setName($name); } @@ -236,6 +238,9 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable public function getMetadataBag() { ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } return $this->storage->getMetadataBag(); } @@ -245,13 +250,13 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable */ public function registerBag(SessionBagInterface $bag) { - $this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex)); + $this->storage->registerBag(new SessionBagProxy($bag, $this->data, $this->usageIndex, $this->usageReporter)); } /** * {@inheritdoc} */ - public function getBag($name) + public function getBag(string $name) { $bag = $this->storage->getBag($name); @@ -272,10 +277,8 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable * Gets the attributebag interface. * * Note that this method was added to help with IDE autocompletion. - * - * @return AttributeBagInterface */ - private function getAttributeBag() + private function getAttributeBag(): AttributeBagInterface { return $this->getBag($this->attributeName); } diff --git a/lib/symfony/http-foundation/Session/SessionBagProxy.php b/lib/symfony/http-foundation/Session/SessionBagProxy.php index 3504bdfe7b..90aa010c90 100644 --- a/lib/symfony/http-foundation/Session/SessionBagProxy.php +++ b/lib/symfony/http-foundation/Session/SessionBagProxy.php @@ -21,33 +21,35 @@ final class SessionBagProxy implements SessionBagInterface private $bag; private $data; private $usageIndex; + private $usageReporter; - public function __construct(SessionBagInterface $bag, array &$data, &$usageIndex) + public function __construct(SessionBagInterface $bag, array &$data, ?int &$usageIndex, ?callable $usageReporter) { $this->bag = $bag; $this->data = &$data; $this->usageIndex = &$usageIndex; + $this->usageReporter = $usageReporter; } - /** - * @return SessionBagInterface - */ - public function getBag() + public function getBag(): SessionBagInterface { ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } return $this->bag; } - /** - * @return bool - */ - public function isEmpty() + public function isEmpty(): bool { if (!isset($this->data[$this->bag->getStorageKey()])) { return true; } ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } return empty($this->data[$this->bag->getStorageKey()]); } @@ -55,7 +57,7 @@ final class SessionBagProxy implements SessionBagInterface /** * {@inheritdoc} */ - public function getName() + public function getName(): string { return $this->bag->getName(); } @@ -63,9 +65,13 @@ final class SessionBagProxy implements SessionBagInterface /** * {@inheritdoc} */ - public function initialize(array &$array) + public function initialize(array &$array): void { ++$this->usageIndex; + if ($this->usageReporter && 0 <= $this->usageIndex) { + ($this->usageReporter)(); + } + $this->data[$this->bag->getStorageKey()] = &$array; $this->bag->initialize($array); @@ -74,7 +80,7 @@ final class SessionBagProxy implements SessionBagInterface /** * {@inheritdoc} */ - public function getStorageKey() + public function getStorageKey(): string { return $this->bag->getStorageKey(); } diff --git a/lib/symfony/http-foundation/Session/SessionFactory.php b/lib/symfony/http-foundation/Session/SessionFactory.php new file mode 100644 index 0000000000..04c4b06a04 --- /dev/null +++ b/lib/symfony/http-foundation/Session/SessionFactory.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageFactoryInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(Session::class); + +/** + * @author Jérémy Derussé + */ +class SessionFactory implements SessionFactoryInterface +{ + private $requestStack; + private $storageFactory; + private $usageReporter; + + public function __construct(RequestStack $requestStack, SessionStorageFactoryInterface $storageFactory, callable $usageReporter = null) + { + $this->requestStack = $requestStack; + $this->storageFactory = $storageFactory; + $this->usageReporter = $usageReporter; + } + + public function createSession(): SessionInterface + { + return new Session($this->storageFactory->createStorage($this->requestStack->getMainRequest()), null, null, $this->usageReporter); + } +} diff --git a/lib/symfony/http-foundation/Session/SessionFactoryInterface.php b/lib/symfony/http-foundation/Session/SessionFactoryInterface.php new file mode 100644 index 0000000000..b24fdc4958 --- /dev/null +++ b/lib/symfony/http-foundation/Session/SessionFactoryInterface.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 Symfony\Component\HttpFoundation\Session; + +/** + * @author Kevin Bond + */ +interface SessionFactoryInterface +{ + public function createSession(): SessionInterface; +} diff --git a/lib/symfony/http-foundation/Session/SessionInterface.php b/lib/symfony/http-foundation/Session/SessionInterface.php index 95fca857e2..b2f09fd0dc 100644 --- a/lib/symfony/http-foundation/Session/SessionInterface.php +++ b/lib/symfony/http-foundation/Session/SessionInterface.php @@ -23,7 +23,7 @@ interface SessionInterface /** * Starts the session storage. * - * @return bool True if session started + * @return bool * * @throws \RuntimeException if session fails to start */ @@ -32,30 +32,26 @@ interface SessionInterface /** * Returns the session ID. * - * @return string The session ID + * @return string */ public function getId(); /** * Sets the session ID. - * - * @param string $id */ - public function setId($id); + public function setId(string $id); /** * Returns the session name. * - * @return mixed The session name + * @return string */ public function getName(); /** * Sets the session name. - * - * @param string $name */ - public function setName($name); + public function setName(string $name); /** * Invalidates the current session. @@ -68,9 +64,9 @@ interface SessionInterface * to expire with browser session. Time is in seconds, and is * not a Unix timestamp. * - * @return bool True if session invalidated, false if error + * @return bool */ - public function invalidate($lifetime = null); + public function invalidate(int $lifetime = null); /** * Migrates the current session to a new session id while maintaining all @@ -82,9 +78,9 @@ interface SessionInterface * to expire with browser session. Time is in seconds, and is * not a Unix timestamp. * - * @return bool True if session migrated, false if error + * @return bool */ - public function migrate($destroy = false, $lifetime = null); + public function migrate(bool $destroy = false, int $lifetime = null); /** * Force the session to be saved and closed. @@ -98,52 +94,44 @@ interface SessionInterface /** * Checks if an attribute is defined. * - * @param string $name The attribute name - * - * @return bool true if the attribute is defined, false otherwise + * @return bool */ - public function has($name); + public function has(string $name); /** * Returns an attribute. * - * @param string $name The attribute name - * @param mixed $default The default value if not found + * @param mixed $default The default value if not found * * @return mixed */ - public function get($name, $default = null); + public function get(string $name, $default = null); /** * Sets an attribute. * - * @param string $name - * @param mixed $value + * @param mixed $value */ - public function set($name, $value); + public function set(string $name, $value); /** * Returns attributes. * - * @return array Attributes + * @return array */ public function all(); /** * Sets attributes. - * - * @param array $attributes Attributes */ public function replace(array $attributes); /** * Removes an attribute. * - * @param string $name - * * @return mixed The removed value or null when it does not exist */ - public function remove($name); + public function remove(string $name); /** * Clears all attributes. @@ -165,11 +153,9 @@ interface SessionInterface /** * Gets a bag instance by name. * - * @param string $name - * * @return SessionBagInterface */ - public function getBag($name); + public function getBag(string $name); /** * Gets session meta. diff --git a/lib/symfony/http-foundation/Session/SessionUtils.php b/lib/symfony/http-foundation/Session/SessionUtils.php index 04a25f7165..b5bce4a884 100644 --- a/lib/symfony/http-foundation/Session/SessionUtils.php +++ b/lib/symfony/http-foundation/Session/SessionUtils.php @@ -22,10 +22,10 @@ namespace Symfony\Component\HttpFoundation\Session; final class SessionUtils { /** - * Find the session header amongst the headers that are to be sent, remove it, and return + * Finds the session header amongst the headers that are to be sent, removes it, and returns * it so the caller can process it further. */ - public static function popSessionCookie($sessionName, $sessionId) + public static function popSessionCookie(string $sessionName, string $sessionId): ?string { $sessionCookie = null; $sessionCookiePrefix = sprintf(' %s=', urlencode($sessionName)); diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php index 455ced8ce5..35d7b4b812 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php @@ -31,41 +31,36 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess /** * @return bool */ + #[\ReturnTypeWillChange] public function open($savePath, $sessionName) { $this->sessionName = $sessionName; - if (!headers_sent() && !ini_get('session.cache_limiter') && '0' !== ini_get('session.cache_limiter')) { - header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) ini_get('session.cache_expire'))); + if (!headers_sent() && !\ini_get('session.cache_limiter') && '0' !== \ini_get('session.cache_limiter')) { + header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) \ini_get('session.cache_expire'))); } return true; } /** - * @param string $sessionId - * * @return string */ - abstract protected function doRead($sessionId); - - /** - * @param string $sessionId - * @param string $data - * - * @return bool - */ - abstract protected function doWrite($sessionId, $data); - - /** - * @param string $sessionId - * - * @return bool - */ - abstract protected function doDestroy($sessionId); + abstract protected function doRead(string $sessionId); /** * @return bool */ + abstract protected function doWrite(string $sessionId, string $data); + + /** + * @return bool + */ + abstract protected function doDestroy(string $sessionId); + + /** + * @return bool + */ + #[\ReturnTypeWillChange] public function validateId($sessionId) { $this->prefetchData = $this->read($sessionId); @@ -86,6 +81,7 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess /** * @return string */ + #[\ReturnTypeWillChange] public function read($sessionId) { if (null !== $this->prefetchId) { @@ -102,9 +98,6 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess $data = $this->doRead($sessionId); $this->newSessionId = '' === $data ? $sessionId : null; - if (\PHP_VERSION_ID < 70000) { - $this->prefetchData = $data; - } return $data; } @@ -112,16 +105,9 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess /** * @return bool */ + #[\ReturnTypeWillChange] public function write($sessionId, $data) { - if (\PHP_VERSION_ID < 70000 && $this->prefetchData) { - $readData = $this->prefetchData; - $this->prefetchData = null; - - if ($readData === $data) { - return $this->updateTimestamp($sessionId, $data); - } - } if (null === $this->igbinaryEmptyData) { // see https://github.com/igbinary/igbinary/issues/146 $this->igbinaryEmptyData = \function_exists('igbinary_serialize') ? igbinary_serialize([]) : ''; @@ -137,12 +123,10 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess /** * @return bool */ + #[\ReturnTypeWillChange] public function destroy($sessionId) { - if (\PHP_VERSION_ID < 70000) { - $this->prefetchData = null; - } - if (!headers_sent() && filter_var(ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN)) { + if (!headers_sent() && filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN)) { if (!$this->sessionName) { throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class)); } @@ -157,7 +141,7 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess */ if (null === $cookie || isset($_COOKIE[$this->sessionName])) { if (\PHP_VERSION_ID < 70300) { - setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), filter_var(ini_get('session.cookie_secure'), \FILTER_VALIDATE_BOOLEAN), filter_var(ini_get('session.cookie_httponly'), \FILTER_VALIDATE_BOOLEAN)); + setcookie($this->sessionName, '', 0, \ini_get('session.cookie_path'), \ini_get('session.cookie_domain'), filter_var(\ini_get('session.cookie_secure'), \FILTER_VALIDATE_BOOLEAN), filter_var(\ini_get('session.cookie_httponly'), \FILTER_VALIDATE_BOOLEAN)); } else { $params = session_get_cookie_params(); unset($params['lifetime']); diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php b/lib/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php new file mode 100644 index 0000000000..bea3a323ed --- /dev/null +++ b/lib/symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.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\HttpFoundation\Session\Storage\Handler; + +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Ahmed TAILOULOUTE + */ +class IdentityMarshaller implements MarshallerInterface +{ + /** + * {@inheritdoc} + */ + public function marshall(array $values, ?array &$failed): array + { + foreach ($values as $key => $value) { + if (!\is_string($value)) { + throw new \LogicException(sprintf('%s accepts only string as data.', __METHOD__)); + } + } + + return $values; + } + + /** + * {@inheritdoc} + */ + public function unmarshall(string $value): string + { + return $value; + } +} diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php new file mode 100644 index 0000000000..c321c8c932 --- /dev/null +++ b/lib/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Symfony\Component\Cache\Marshaller\MarshallerInterface; + +/** + * @author Ahmed TAILOULOUTE + */ +class MarshallingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + private $handler; + private $marshaller; + + public function __construct(AbstractSessionHandler $handler, MarshallerInterface $marshaller) + { + $this->handler = $handler; + $this->marshaller = $marshaller; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function open($savePath, $name) + { + return $this->handler->open($savePath, $name); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + return $this->handler->close(); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function destroy($sessionId) + { + return $this->handler->destroy($sessionId); + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + return $this->handler->gc($maxlifetime); + } + + /** + * @return string + */ + #[\ReturnTypeWillChange] + public function read($sessionId) + { + return $this->marshaller->unmarshall($this->handler->read($sessionId)); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function write($sessionId, $data) + { + $failed = []; + $marshalledData = $this->marshaller->marshall(['data' => $data], $failed); + + if (isset($failed['data'])) { + return false; + } + + return $this->handler->write($sessionId, $marshalledData['data']); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function validateId($sessionId) + { + return $this->handler->validateId($sessionId); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $data) + { + return $this->handler->updateTimestamp($sessionId, $data); + } +} diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php deleted file mode 100644 index ed74ce8048..0000000000 --- a/lib/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php +++ /dev/null @@ -1,118 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; - -@trigger_error(sprintf('The class %s is deprecated since Symfony 3.4 and will be removed in 4.0. Use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler instead.', MemcacheSessionHandler::class), \E_USER_DEPRECATED); - -/** - * @author Drak - * - * @deprecated since version 3.4, to be removed in 4.0. Use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler instead. - */ -class MemcacheSessionHandler implements \SessionHandlerInterface -{ - private $memcache; - - /** - * @var int Time to live in seconds - */ - private $ttl; - - /** - * @var string Key prefix for shared environments - */ - private $prefix; - - /** - * Constructor. - * - * List of available options: - * * prefix: The prefix to use for the memcache keys in order to avoid collision - * * expiretime: The time to live in seconds - * - * @param \Memcache $memcache A \Memcache instance - * @param array $options An associative array of Memcache options - * - * @throws \InvalidArgumentException When unsupported options are passed - */ - public function __construct(\Memcache $memcache, array $options = []) - { - if ($diff = array_diff(array_keys($options), ['prefix', 'expiretime'])) { - throw new \InvalidArgumentException(sprintf('The following options are not supported "%s".', implode(', ', $diff))); - } - - $this->memcache = $memcache; - $this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400; - $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s'; - } - - /** - * {@inheritdoc} - */ - public function open($savePath, $sessionName) - { - return true; - } - - /** - * {@inheritdoc} - */ - public function close() - { - return true; - } - - /** - * {@inheritdoc} - */ - public function read($sessionId) - { - return $this->memcache->get($this->prefix.$sessionId) ?: ''; - } - - /** - * {@inheritdoc} - */ - public function write($sessionId, $data) - { - return $this->memcache->set($this->prefix.$sessionId, $data, 0, time() + $this->ttl); - } - - /** - * {@inheritdoc} - */ - public function destroy($sessionId) - { - $this->memcache->delete($this->prefix.$sessionId); - - return true; - } - - /** - * {@inheritdoc} - */ - public function gc($maxlifetime) - { - // not required here because memcache will auto expire the records anyhow. - return true; - } - - /** - * Return a Memcache instance. - * - * @return \Memcache - */ - protected function getMemcache() - { - return $this->memcache; - } -} diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php index 6711e0a55f..9cb841ae0d 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -38,7 +38,7 @@ class MemcachedSessionHandler extends AbstractSessionHandler * * List of available options: * * prefix: The prefix to use for the memcached keys in order to avoid collision - * * expiretime: The time to live in seconds. + * * ttl: The time to live in seconds. * * @throws \InvalidArgumentException When unsupported options are passed */ @@ -46,17 +46,18 @@ class MemcachedSessionHandler extends AbstractSessionHandler { $this->memcached = $memcached; - if ($diff = array_diff(array_keys($options), ['prefix', 'expiretime'])) { + if ($diff = array_diff(array_keys($options), ['prefix', 'expiretime', 'ttl'])) { throw new \InvalidArgumentException(sprintf('The following options are not supported "%s".', implode(', ', $diff))); } - $this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400; - $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s'; + $this->ttl = $options['expiretime'] ?? $options['ttl'] ?? null; + $this->prefix = $options['prefix'] ?? 'sf2s'; } /** * @return bool */ + #[\ReturnTypeWillChange] public function close() { return $this->memcached->quit(); @@ -65,7 +66,7 @@ class MemcachedSessionHandler extends AbstractSessionHandler /** * {@inheritdoc} */ - protected function doRead($sessionId) + protected function doRead(string $sessionId) { return $this->memcached->get($this->prefix.$sessionId) ?: ''; } @@ -73,9 +74,10 @@ class MemcachedSessionHandler extends AbstractSessionHandler /** * @return bool */ + #[\ReturnTypeWillChange] public function updateTimestamp($sessionId, $data) { - $this->memcached->touch($this->prefix.$sessionId, time() + $this->ttl); + $this->memcached->touch($this->prefix.$sessionId, time() + (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime'))); return true; } @@ -83,15 +85,15 @@ class MemcachedSessionHandler extends AbstractSessionHandler /** * {@inheritdoc} */ - protected function doWrite($sessionId, $data) + protected function doWrite(string $sessionId, string $data) { - return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl); + return $this->memcached->set($this->prefix.$sessionId, $data, time() + (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime'))); } /** * {@inheritdoc} */ - protected function doDestroy($sessionId) + protected function doDestroy(string $sessionId) { $result = $this->memcached->delete($this->prefix.$sessionId); @@ -99,12 +101,13 @@ class MemcachedSessionHandler extends AbstractSessionHandler } /** - * @return bool + * @return int|false */ + #[\ReturnTypeWillChange] public function gc($maxlifetime) { // not required here because memcached will auto expire the records anyhow. - return true; + return 0; } /** diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php new file mode 100644 index 0000000000..bf27ca6cc5 --- /dev/null +++ b/lib/symfony/http-foundation/Session/Storage/Handler/MigratingSessionHandler.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Migrating session handler for migrating from one handler to another. It reads + * from the current handler and writes both the current and new ones. + * + * It ignores errors from the new handler. + * + * @author Ross Motley + * @author Oliver Radwell + */ +class MigratingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + /** + * @var \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface + */ + private $currentHandler; + + /** + * @var \SessionHandlerInterface&\SessionUpdateTimestampHandlerInterface + */ + private $writeOnlyHandler; + + public function __construct(\SessionHandlerInterface $currentHandler, \SessionHandlerInterface $writeOnlyHandler) + { + if (!$currentHandler instanceof \SessionUpdateTimestampHandlerInterface) { + $currentHandler = new StrictSessionHandler($currentHandler); + } + if (!$writeOnlyHandler instanceof \SessionUpdateTimestampHandlerInterface) { + $writeOnlyHandler = new StrictSessionHandler($writeOnlyHandler); + } + + $this->currentHandler = $currentHandler; + $this->writeOnlyHandler = $writeOnlyHandler; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function close() + { + $result = $this->currentHandler->close(); + $this->writeOnlyHandler->close(); + + return $result; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function destroy($sessionId) + { + $result = $this->currentHandler->destroy($sessionId); + $this->writeOnlyHandler->destroy($sessionId); + + return $result; + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + $result = $this->currentHandler->gc($maxlifetime); + $this->writeOnlyHandler->gc($maxlifetime); + + return $result; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function open($savePath, $sessionName) + { + $result = $this->currentHandler->open($savePath, $sessionName); + $this->writeOnlyHandler->open($savePath, $sessionName); + + return $result; + } + + /** + * @return string + */ + #[\ReturnTypeWillChange] + public function read($sessionId) + { + // No reading from new handler until switch-over + return $this->currentHandler->read($sessionId); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function write($sessionId, $sessionData) + { + $result = $this->currentHandler->write($sessionId, $sessionData); + $this->writeOnlyHandler->write($sessionId, $sessionData); + + return $result; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function validateId($sessionId) + { + // No reading from new handler until switch-over + return $this->currentHandler->validateId($sessionId); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $sessionData) + { + $result = $this->currentHandler->updateTimestamp($sessionId, $sessionData); + $this->writeOnlyHandler->updateTimestamp($sessionId, $sessionData); + + return $result; + } +} diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php index 52dc15de83..ef8f71942f 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -11,6 +11,11 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; +use MongoDB\BSON\Binary; +use MongoDB\BSON\UTCDateTime; +use MongoDB\Client; +use MongoDB\Collection; + /** * Session handler using the mongodb/mongodb package and MongoDB driver extension. * @@ -24,7 +29,7 @@ class MongoDbSessionHandler extends AbstractSessionHandler private $mongo; /** - * @var \MongoCollection + * @var Collection */ private $collection; @@ -51,7 +56,7 @@ class MongoDbSessionHandler extends AbstractSessionHandler * A TTL collections can be used on MongoDB 2.2+ to cleanup expired sessions * automatically. Such an index can for example look like this: * - * db..ensureIndex( + * db..createIndex( * { "": 1 }, * { "expireAfterSeconds": 0 } * ) @@ -61,22 +66,10 @@ class MongoDbSessionHandler extends AbstractSessionHandler * If you use such an index, you can drop `gc_probability` to 0 since * no garbage-collection is required. * - * @param \MongoDB\Client $mongo A MongoDB\Client instance - * @param array $options An associative array of field options - * - * @throws \InvalidArgumentException When MongoClient or Mongo instance not provided * @throws \InvalidArgumentException When "database" or "collection" not provided */ - public function __construct($mongo, array $options) + public function __construct(Client $mongo, array $options) { - if ($mongo instanceof \MongoClient || $mongo instanceof \Mongo) { - @trigger_error(sprintf('Using %s with the legacy mongo extension is deprecated as of 3.4 and will be removed in 4.0. Use it with the mongodb/mongodb package and ext-mongodb instead.', __CLASS__), \E_USER_DEPRECATED); - } - - if (!($mongo instanceof \MongoDB\Client || $mongo instanceof \MongoClient || $mongo instanceof \Mongo)) { - throw new \InvalidArgumentException('MongoClient or Mongo instance required.'); - } - if (!isset($options['database']) || !isset($options['collection'])) { throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler.'); } @@ -92,8 +85,9 @@ class MongoDbSessionHandler extends AbstractSessionHandler } /** - * {@inheritdoc} + * @return bool */ + #[\ReturnTypeWillChange] public function close() { return true; @@ -102,11 +96,9 @@ class MongoDbSessionHandler extends AbstractSessionHandler /** * {@inheritdoc} */ - protected function doDestroy($sessionId) + protected function doDestroy(string $sessionId) { - $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove'; - - $this->getCollection()->$methodName([ + $this->getCollection()->deleteOne([ $this->options['id_field'] => $sessionId, ]); @@ -114,73 +106,52 @@ class MongoDbSessionHandler extends AbstractSessionHandler } /** - * {@inheritdoc} + * @return int|false */ + #[\ReturnTypeWillChange] public function gc($maxlifetime) { - $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteMany' : 'remove'; - - $this->getCollection()->$methodName([ - $this->options['expiry_field'] => ['$lt' => $this->createDateTime()], - ]); - - return true; + return $this->getCollection()->deleteMany([ + $this->options['expiry_field'] => ['$lt' => new UTCDateTime()], + ])->getDeletedCount(); } /** * {@inheritdoc} */ - protected function doWrite($sessionId, $data) + protected function doWrite(string $sessionId, string $data) { - $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime')); + $expiry = new UTCDateTime((time() + (int) \ini_get('session.gc_maxlifetime')) * 1000); $fields = [ - $this->options['time_field'] => $this->createDateTime(), + $this->options['time_field'] => new UTCDateTime(), $this->options['expiry_field'] => $expiry, + $this->options['data_field'] => new Binary($data, Binary::TYPE_OLD_BINARY), ]; - $options = ['upsert' => true]; - - if ($this->mongo instanceof \MongoDB\Client) { - $fields[$this->options['data_field']] = new \MongoDB\BSON\Binary($data, \MongoDB\BSON\Binary::TYPE_OLD_BINARY); - } else { - $fields[$this->options['data_field']] = new \MongoBinData($data, \MongoBinData::BYTE_ARRAY); - $options['multiple'] = false; - } - - $methodName = $this->mongo instanceof \MongoDB\Client ? 'updateOne' : 'update'; - - $this->getCollection()->$methodName( + $this->getCollection()->updateOne( [$this->options['id_field'] => $sessionId], ['$set' => $fields], - $options + ['upsert' => true] ); return true; } /** - * {@inheritdoc} + * @return bool */ + #[\ReturnTypeWillChange] public function updateTimestamp($sessionId, $data) { - $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime')); + $expiry = new UTCDateTime((time() + (int) \ini_get('session.gc_maxlifetime')) * 1000); - if ($this->mongo instanceof \MongoDB\Client) { - $methodName = 'updateOne'; - $options = []; - } else { - $methodName = 'update'; - $options = ['multiple' => false]; - } - - $this->getCollection()->$methodName( + $this->getCollection()->updateOne( [$this->options['id_field'] => $sessionId], ['$set' => [ - $this->options['time_field'] => $this->createDateTime(), + $this->options['time_field'] => new UTCDateTime(), $this->options['expiry_field'] => $expiry, - ]], - $options + ]] ); return true; @@ -189,30 +160,21 @@ class MongoDbSessionHandler extends AbstractSessionHandler /** * {@inheritdoc} */ - protected function doRead($sessionId) + protected function doRead(string $sessionId) { $dbData = $this->getCollection()->findOne([ $this->options['id_field'] => $sessionId, - $this->options['expiry_field'] => ['$gte' => $this->createDateTime()], + $this->options['expiry_field'] => ['$gte' => new UTCDateTime()], ]); if (null === $dbData) { return ''; } - if ($dbData[$this->options['data_field']] instanceof \MongoDB\BSON\Binary) { - return $dbData[$this->options['data_field']]->getData(); - } - - return $dbData[$this->options['data_field']]->bin; + return $dbData[$this->options['data_field']]->getData(); } - /** - * Return a "MongoCollection" instance. - * - * @return \MongoCollection - */ - private function getCollection() + private function getCollection(): Collection { if (null === $this->collection) { $this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']); @@ -222,34 +184,10 @@ class MongoDbSessionHandler extends AbstractSessionHandler } /** - * Return a Mongo instance. - * - * @return \Mongo|\MongoClient|\MongoDB\Client + * @return Client */ protected function getMongo() { return $this->mongo; } - - /** - * Create a date object using the class appropriate for the current mongo connection. - * - * Return an instance of a MongoDate or \MongoDB\BSON\UTCDateTime - * - * @param int $seconds An integer representing UTC seconds since Jan 1 1970. Defaults to now. - * - * @return \MongoDate|\MongoDB\BSON\UTCDateTime - */ - private function createDateTime($seconds = null) - { - if (null === $seconds) { - $seconds = time(); - } - - if ($this->mongo instanceof \MongoDB\Client) { - return new \MongoDB\BSON\UTCDateTime($seconds * 1000); - } - - return new \MongoDate($seconds); - } } diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php index f24271fbf5..1ca4bfeb08 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php @@ -16,7 +16,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; * * @author Drak */ -class NativeFileSessionHandler extends NativeSessionHandler +class NativeFileSessionHandler extends \SessionHandler { /** * @param string $savePath Path of directory to save session files @@ -28,10 +28,10 @@ class NativeFileSessionHandler extends NativeSessionHandler * @throws \InvalidArgumentException On invalid $savePath * @throws \RuntimeException When failing to create the save directory */ - public function __construct($savePath = null) + public function __construct(string $savePath = null) { if (null === $savePath) { - $savePath = ini_get('session.save_path'); + $savePath = \ini_get('session.save_path'); } $baseDir = $savePath; diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php deleted file mode 100644 index 280d0d6eea..0000000000 --- a/lib/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; - -/** - * @deprecated since version 3.4, to be removed in 4.0. Use \SessionHandler instead. - * @see https://php.net/sessionhandler - */ -class NativeSessionHandler extends \SessionHandler -{ - public function __construct() - { - @trigger_error('The '.__NAMESPACE__.'\NativeSessionHandler class is deprecated since Symfony 3.4 and will be removed in 4.0. Use the \SessionHandler class instead.', \E_USER_DEPRECATED); - } -} diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php index 3ba9378ca7..4331dbe502 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php @@ -19,16 +19,18 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; class NullSessionHandler extends AbstractSessionHandler { /** - * {@inheritdoc} + * @return bool */ + #[\ReturnTypeWillChange] public function close() { return true; } /** - * {@inheritdoc} + * @return bool */ + #[\ReturnTypeWillChange] public function validateId($sessionId) { return true; @@ -37,14 +39,15 @@ class NullSessionHandler extends AbstractSessionHandler /** * {@inheritdoc} */ - protected function doRead($sessionId) + protected function doRead(string $sessionId) { return ''; } /** - * {@inheritdoc} + * @return bool */ + #[\ReturnTypeWillChange] public function updateTimestamp($sessionId, $data) { return true; @@ -53,7 +56,7 @@ class NullSessionHandler extends AbstractSessionHandler /** * {@inheritdoc} */ - protected function doWrite($sessionId, $data) + protected function doWrite(string $sessionId, string $data) { return true; } @@ -61,16 +64,17 @@ class NullSessionHandler extends AbstractSessionHandler /** * {@inheritdoc} */ - protected function doDestroy($sessionId) + protected function doDestroy(string $sessionId) { return true; } /** - * @return bool + * @return int|false */ + #[\ReturnTypeWillChange] public function gc($maxlifetime) { - return true; + return 0; } } diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php index db32d549b6..24c98940dc 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php @@ -46,7 +46,7 @@ class PdoSessionHandler extends AbstractSessionHandler * write will win in this case. It might be useful when you implement your own * logic to deal with this like an optimistic approach. */ - const LOCK_NONE = 0; + public const LOCK_NONE = 0; /** * Creates an application-level lock on a session. The disadvantage is that the @@ -55,7 +55,7 @@ class PdoSessionHandler extends AbstractSessionHandler * does not require a transaction. * This mode is not available for SQLite and not yet implemented for oci and sqlsrv. */ - const LOCK_ADVISORY = 1; + public const LOCK_ADVISORY = 1; /** * Issues a real row lock. Since it uses a transaction between opening and @@ -63,7 +63,9 @@ class PdoSessionHandler extends AbstractSessionHandler * that you also use for your application logic. This mode is the default because * it's the only reliable solution across DBMSs. */ - const LOCK_TRANSACTIONAL = 2; + public const LOCK_TRANSACTIONAL = 2; + + private const MAX_LIFETIME = 315576000; /** * @var \PDO|null PDO instance or null when not connected yet @@ -71,57 +73,67 @@ class PdoSessionHandler extends AbstractSessionHandler private $pdo; /** - * @var string|false|null DSN string or null for session.save_path or false when lazy connection disabled + * DSN string or null for session.save_path or false when lazy connection disabled. + * + * @var string|false|null */ private $dsn = false; /** - * @var string Database driver + * @var string|null */ private $driver; /** - * @var string Table name + * @var string */ private $table = 'sessions'; /** - * @var string Column for session id + * @var string */ private $idCol = 'sess_id'; /** - * @var string Column for session data + * @var string */ private $dataCol = 'sess_data'; /** - * @var string Column for lifetime + * @var string */ private $lifetimeCol = 'sess_lifetime'; /** - * @var string Column for timestamp + * @var string */ private $timeCol = 'sess_time'; /** - * @var string Username when lazy-connect + * Username when lazy-connect. + * + * @var string */ private $username = ''; /** - * @var string Password when lazy-connect + * Password when lazy-connect. + * + * @var string */ private $password = ''; /** - * @var array Connection options when lazy-connect + * Connection options when lazy-connect. + * + * @var array */ private $connectionOptions = []; /** - * @var int The strategy for locking, see constants + * The strategy for locking, see constants. + * + * @var int */ private $lockMode = self::LOCK_TRANSACTIONAL; @@ -133,17 +145,23 @@ class PdoSessionHandler extends AbstractSessionHandler private $unlockStatements = []; /** - * @var bool True when the current session exists but expired according to session.gc_maxlifetime + * True when the current session exists but expired according to session.gc_maxlifetime. + * + * @var bool */ private $sessionExpired = false; /** - * @var bool Whether a transaction is active + * Whether a transaction is active. + * + * @var bool */ private $inTransaction = false; /** - * @var bool Whether gc() has been called + * Whether gc() has been called. + * + * @var bool */ private $gcCalled = false; @@ -165,7 +183,6 @@ class PdoSessionHandler extends AbstractSessionHandler * * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL] * * @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or URL string or null - * @param array $options An associative array of options * * @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION */ @@ -178,21 +195,21 @@ class PdoSessionHandler extends AbstractSessionHandler $this->pdo = $pdoOrDsn; $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); - } elseif (\is_string($pdoOrDsn) && false !== strpos($pdoOrDsn, '://')) { + } elseif (\is_string($pdoOrDsn) && str_contains($pdoOrDsn, '://')) { $this->dsn = $this->buildDsnFromUrl($pdoOrDsn); } else { $this->dsn = $pdoOrDsn; } - $this->table = isset($options['db_table']) ? $options['db_table'] : $this->table; - $this->idCol = isset($options['db_id_col']) ? $options['db_id_col'] : $this->idCol; - $this->dataCol = isset($options['db_data_col']) ? $options['db_data_col'] : $this->dataCol; - $this->lifetimeCol = isset($options['db_lifetime_col']) ? $options['db_lifetime_col'] : $this->lifetimeCol; - $this->timeCol = isset($options['db_time_col']) ? $options['db_time_col'] : $this->timeCol; - $this->username = isset($options['db_username']) ? $options['db_username'] : $this->username; - $this->password = isset($options['db_password']) ? $options['db_password'] : $this->password; - $this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions; - $this->lockMode = isset($options['lock_mode']) ? $options['lock_mode'] : $this->lockMode; + $this->table = $options['db_table'] ?? $this->table; + $this->idCol = $options['db_id_col'] ?? $this->idCol; + $this->dataCol = $options['db_data_col'] ?? $this->dataCol; + $this->lifetimeCol = $options['db_lifetime_col'] ?? $this->lifetimeCol; + $this->timeCol = $options['db_time_col'] ?? $this->timeCol; + $this->username = $options['db_username'] ?? $this->username; + $this->password = $options['db_password'] ?? $this->password; + $this->connectionOptions = $options['db_connection_options'] ?? $this->connectionOptions; + $this->lockMode = $options['lock_mode'] ?? $this->lockMode; } /** @@ -238,6 +255,7 @@ class PdoSessionHandler extends AbstractSessionHandler try { $this->pdo->exec($sql); + $this->pdo->exec("CREATE INDEX EXPIRY ON $this->table ($this->lifetimeCol)"); } catch (\PDOException $e) { $this->rollback(); @@ -250,7 +268,7 @@ class PdoSessionHandler extends AbstractSessionHandler * * Can be used to distinguish between a new session and one that expired due to inactivity. * - * @return bool Whether current session expired + * @return bool */ public function isSessionExpired() { @@ -258,8 +276,9 @@ class PdoSessionHandler extends AbstractSessionHandler } /** - * {@inheritdoc} + * @return bool */ + #[\ReturnTypeWillChange] public function open($savePath, $sessionName) { $this->sessionExpired = false; @@ -272,8 +291,9 @@ class PdoSessionHandler extends AbstractSessionHandler } /** - * {@inheritdoc} + * @return string */ + #[\ReturnTypeWillChange] public function read($sessionId) { try { @@ -286,21 +306,22 @@ class PdoSessionHandler extends AbstractSessionHandler } /** - * @return bool + * @return int|false */ + #[\ReturnTypeWillChange] public function gc($maxlifetime) { // We delay gc() to close() so that it is executed outside the transactional and blocking read-write process. // This way, pruning expired sessions does not block them from being started while the current session is used. $this->gcCalled = true; - return true; + return 0; } /** * {@inheritdoc} */ - protected function doDestroy($sessionId) + protected function doDestroy(string $sessionId) { // delete the record associated with this id $sql = "DELETE FROM $this->table WHERE $this->idCol = :id"; @@ -321,9 +342,9 @@ class PdoSessionHandler extends AbstractSessionHandler /** * {@inheritdoc} */ - protected function doWrite($sessionId, $data) + protected function doWrite(string $sessionId, string $data) { - $maxlifetime = (int) ini_get('session.gc_maxlifetime'); + $maxlifetime = (int) \ini_get('session.gc_maxlifetime'); try { // We use a single MERGE SQL query when supported by the database. @@ -348,7 +369,7 @@ class PdoSessionHandler extends AbstractSessionHandler $insertStmt->execute(); } catch (\PDOException $e) { // Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys - if (0 === strpos($e->getCode(), '23')) { + if (str_starts_with($e->getCode(), '23')) { $updateStmt->execute(); } else { throw $e; @@ -365,18 +386,19 @@ class PdoSessionHandler extends AbstractSessionHandler } /** - * {@inheritdoc} + * @return bool */ + #[\ReturnTypeWillChange] public function updateTimestamp($sessionId, $data) { - $maxlifetime = (int) ini_get('session.gc_maxlifetime'); + $expiry = time() + (int) \ini_get('session.gc_maxlifetime'); try { $updateStmt = $this->pdo->prepare( - "UPDATE $this->table SET $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id" + "UPDATE $this->table SET $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id" ); $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); - $updateStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $updateStmt->bindParam(':expiry', $expiry, \PDO::PARAM_INT); $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT); $updateStmt->execute(); } catch (\PDOException $e) { @@ -389,8 +411,9 @@ class PdoSessionHandler extends AbstractSessionHandler } /** - * {@inheritdoc} + * @return bool */ + #[\ReturnTypeWillChange] public function close() { $this->commit(); @@ -403,19 +426,27 @@ class PdoSessionHandler extends AbstractSessionHandler $this->gcCalled = false; // delete the session records that have expired - if ('mysql' === $this->driver) { - $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol < :time"; - } else { - $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol < :time - $this->timeCol"; - } - + $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol < :time AND $this->lifetimeCol > :min"; $stmt = $this->pdo->prepare($sql); $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->bindValue(':min', self::MAX_LIFETIME, \PDO::PARAM_INT); + $stmt->execute(); + // to be removed in 6.0 + if ('mysql' === $this->driver) { + $legacySql = "DELETE FROM $this->table WHERE $this->lifetimeCol <= :min AND $this->lifetimeCol + $this->timeCol < :time"; + } else { + $legacySql = "DELETE FROM $this->table WHERE $this->lifetimeCol <= :min AND $this->lifetimeCol < :time - $this->timeCol"; + } + + $stmt = $this->pdo->prepare($legacySql); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->bindValue(':min', self::MAX_LIFETIME, \PDO::PARAM_INT); $stmt->execute(); } if (false !== $this->dsn) { $this->pdo = null; // only close lazy-connection + $this->driver = null; } return true; @@ -423,10 +454,8 @@ class PdoSessionHandler extends AbstractSessionHandler /** * Lazy-connects to the database. - * - * @param string $dsn DSN string */ - private function connect($dsn) + private function connect(string $dsn): void { $this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions); $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); @@ -436,13 +465,9 @@ class PdoSessionHandler extends AbstractSessionHandler /** * Builds a PDO DSN from a URL-like connection string. * - * @param string $dsnOrUrl - * - * @return string - * * @todo implement missing support for oci DSN (which look totally different from other PDO ones) */ - private function buildDsnFromUrl($dsnOrUrl) + private function buildDsnFromUrl(string $dsnOrUrl): string { // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $dsnOrUrl); @@ -476,17 +501,39 @@ class PdoSessionHandler extends AbstractSessionHandler 'sqlite3' => 'sqlite', ]; - $driver = isset($driverAliasMap[$params['scheme']]) ? $driverAliasMap[$params['scheme']] : $params['scheme']; + $driver = $driverAliasMap[$params['scheme']] ?? $params['scheme']; // Doctrine DBAL supports passing its internal pdo_* driver names directly too (allowing both dashes and underscores). This allows supporting the same here. - if (0 === strpos($driver, 'pdo_') || 0 === strpos($driver, 'pdo-')) { + if (str_starts_with($driver, 'pdo_') || str_starts_with($driver, 'pdo-')) { $driver = substr($driver, 4); } + $dsn = null; switch ($driver) { case 'mysql': + $dsn = 'mysql:'; + if ('' !== ($params['query'] ?? '')) { + $queryParams = []; + parse_str($params['query'], $queryParams); + if ('' !== ($queryParams['charset'] ?? '')) { + $dsn .= 'charset='.$queryParams['charset'].';'; + } + + if ('' !== ($queryParams['unix_socket'] ?? '')) { + $dsn .= 'unix_socket='.$queryParams['unix_socket'].';'; + + if (isset($params['path'])) { + $dbName = substr($params['path'], 1); // Remove the leading slash + $dsn .= 'dbname='.$dbName.';'; + } + + return $dsn; + } + } + // If "unix_socket" is not in the query, we continue with the same process as pgsql + // no break case 'pgsql': - $dsn = $driver.':'; + $dsn ?? $dsn = 'pgsql:'; if (isset($params['host']) && '' !== $params['host']) { $dsn .= 'host='.$params['host'].';'; @@ -541,7 +588,7 @@ class PdoSessionHandler extends AbstractSessionHandler * due to https://percona.com/blog/2013/12/12/one-more-innodb-gap-lock-to-avoid/ . * So we change it to READ COMMITTED. */ - private function beginTransaction() + private function beginTransaction(): void { if (!$this->inTransaction) { if ('sqlite' === $this->driver) { @@ -559,7 +606,7 @@ class PdoSessionHandler extends AbstractSessionHandler /** * Helper method to commit a transaction. */ - private function commit() + private function commit(): void { if ($this->inTransaction) { try { @@ -581,7 +628,7 @@ class PdoSessionHandler extends AbstractSessionHandler /** * Helper method to rollback a transaction. */ - private function rollback() + private function rollback(): void { // We only need to rollback if we are in a transaction. Otherwise the resulting // error would hide the real problem why rollback was called. We might not be @@ -603,11 +650,9 @@ class PdoSessionHandler extends AbstractSessionHandler * We need to make sure we do not return session data that is already considered garbage according * to the session.gc_maxlifetime setting because gc() is called after read() and only sometimes. * - * @param string $sessionId Session ID - * - * @return string The session data + * @return string */ - protected function doRead($sessionId) + protected function doRead(string $sessionId) { if (self::LOCK_ADVISORY === $this->lockMode) { $this->unlockStatements[] = $this->doAdvisoryLock($sessionId); @@ -618,12 +663,17 @@ class PdoSessionHandler extends AbstractSessionHandler $selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); $insertStmt = null; - do { + while (true) { $selectStmt->execute(); $sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM); if ($sessionRows) { - if ($sessionRows[0][1] + $sessionRows[0][2] < time()) { + $expiry = (int) $sessionRows[0][1]; + if ($expiry <= self::MAX_LIFETIME) { + $expiry += $sessionRows[0][2]; + } + + if ($expiry < time()) { $this->sessionExpired = true; return ''; @@ -637,7 +687,7 @@ class PdoSessionHandler extends AbstractSessionHandler throw new \RuntimeException('Failed to read session: INSERT reported a duplicate id but next SELECT did not return any data.'); } - if (!filter_var(ini_get('session.use_strict_mode'), \FILTER_VALIDATE_BOOLEAN) && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { + if (!filter_var(\ini_get('session.use_strict_mode'), \FILTER_VALIDATE_BOOLEAN) && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { // In strict mode, session fixation is not possible: new sessions always start with a unique // random id, so that concurrency is not possible and this code path can be skipped. // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block @@ -648,7 +698,7 @@ class PdoSessionHandler extends AbstractSessionHandler } catch (\PDOException $e) { // Catch duplicate key error because other connection created the session already. // It would only not be the case when the other connection destroyed the session. - if (0 === strpos($e->getCode(), '23')) { + if (str_starts_with($e->getCode(), '23')) { // Retrieve finished session data written by concurrent connection by restarting the loop. // We have to start a new transaction as a failed query will mark the current transaction as // aborted in PostgreSQL and disallow further queries within it. @@ -662,14 +712,12 @@ class PdoSessionHandler extends AbstractSessionHandler } return ''; - } while (true); + } } /** * Executes an application-level lock on the database. * - * @param string $sessionId Session ID - * * @return \PDOStatement The statement that needs to be executed later to release the lock * * @throws \DomainException When an unsupported PDO driver is used @@ -678,7 +726,7 @@ class PdoSessionHandler extends AbstractSessionHandler * - for oci using DBMS_LOCK.REQUEST * - for sqlsrv using sp_getapplock with LockOwner = Session */ - private function doAdvisoryLock($sessionId) + private function doAdvisoryLock(string $sessionId): \PDOStatement { switch ($this->driver) { case 'mysql': @@ -733,12 +781,8 @@ class PdoSessionHandler extends AbstractSessionHandler * Encodes the first 4 (when PHP_INT_SIZE == 4) or 8 characters of the string as an integer. * * Keep in mind, PHP integers are signed. - * - * @param string $string - * - * @return int */ - private function convertStringToInt($string) + private function convertStringToInt(string $string): int { if (4 === \PHP_INT_SIZE) { return (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]); @@ -753,15 +797,14 @@ class PdoSessionHandler extends AbstractSessionHandler /** * Return a locking or nonlocking SQL query to read session information. * - * @return string The SQL string - * * @throws \DomainException When an unsupported PDO driver is used */ - private function getSelectSql() + private function getSelectSql(): string { if (self::LOCK_TRANSACTIONAL === $this->lockMode) { $this->beginTransaction(); + // selecting the time column should be removed in 6.0 switch ($this->driver) { case 'mysql': case 'oci': @@ -782,32 +825,26 @@ class PdoSessionHandler extends AbstractSessionHandler /** * Returns an insert statement supported by the database for writing session data. - * - * @param string $sessionId Session ID - * @param string $sessionData Encoded session data - * @param int $maxlifetime session.gc_maxlifetime - * - * @return \PDOStatement The insert statement */ - private function getInsertStatement($sessionId, $sessionData, $maxlifetime) + private function getInsertStatement(string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement { switch ($this->driver) { case 'oci': $data = fopen('php://memory', 'r+'); fwrite($data, $sessionData); rewind($data); - $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, EMPTY_BLOB(), :lifetime, :time) RETURNING $this->dataCol into :data"; + $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, EMPTY_BLOB(), :expiry, :time) RETURNING $this->dataCol into :data"; break; default: $data = $sessionData; - $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"; + $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time)"; break; } $stmt = $this->pdo->prepare($sql); $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); - $stmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $stmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT); $stmt->bindValue(':time', time(), \PDO::PARAM_INT); return $stmt; @@ -815,32 +852,26 @@ class PdoSessionHandler extends AbstractSessionHandler /** * Returns an update statement supported by the database for writing session data. - * - * @param string $sessionId Session ID - * @param string $sessionData Encoded session data - * @param int $maxlifetime session.gc_maxlifetime - * - * @return \PDOStatement The update statement */ - private function getUpdateStatement($sessionId, $sessionData, $maxlifetime) + private function getUpdateStatement(string $sessionId, string $sessionData, int $maxlifetime): \PDOStatement { switch ($this->driver) { case 'oci': $data = fopen('php://memory', 'r+'); fwrite($data, $sessionData); rewind($data); - $sql = "UPDATE $this->table SET $this->dataCol = EMPTY_BLOB(), $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id RETURNING $this->dataCol into :data"; + $sql = "UPDATE $this->table SET $this->dataCol = EMPTY_BLOB(), $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id RETURNING $this->dataCol into :data"; break; default: $data = $sessionData; - $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id"; + $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id"; break; } $stmt = $this->pdo->prepare($sql); $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); - $stmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $stmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT); $stmt->bindValue(':time', time(), \PDO::PARAM_INT); return $stmt; @@ -848,18 +879,12 @@ class PdoSessionHandler extends AbstractSessionHandler /** * Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data. - * - * @param string $sessionId Session ID - * @param string $data Encoded session data - * @param int $maxlifetime session.gc_maxlifetime - * - * @return \PDOStatement|null The merge statement or null when not supported */ - private function getMergeStatement($sessionId, $data, $maxlifetime) + private function getMergeStatement(string $sessionId, string $data, int $maxlifetime): ?\PDOStatement { switch (true) { case 'mysql' === $this->driver: - $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ". + $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time) ". "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)"; break; case 'sqlsrv' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='): @@ -870,10 +895,10 @@ class PdoSessionHandler extends AbstractSessionHandler "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;"; break; case 'sqlite' === $this->driver: - $mergeSql = "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"; + $mergeSql = "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time)"; break; case 'pgsql' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='): - $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ". + $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :expiry, :time) ". "ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)"; break; default: @@ -887,15 +912,15 @@ class PdoSessionHandler extends AbstractSessionHandler $mergeStmt->bindParam(1, $sessionId, \PDO::PARAM_STR); $mergeStmt->bindParam(2, $sessionId, \PDO::PARAM_STR); $mergeStmt->bindParam(3, $data, \PDO::PARAM_LOB); - $mergeStmt->bindParam(4, $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(4, time() + $maxlifetime, \PDO::PARAM_INT); $mergeStmt->bindValue(5, time(), \PDO::PARAM_INT); $mergeStmt->bindParam(6, $data, \PDO::PARAM_LOB); - $mergeStmt->bindParam(7, $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(7, time() + $maxlifetime, \PDO::PARAM_INT); $mergeStmt->bindValue(8, time(), \PDO::PARAM_INT); } else { $mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); $mergeStmt->bindParam(':data', $data, \PDO::PARAM_LOB); - $mergeStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(':expiry', time() + $maxlifetime, \PDO::PARAM_INT); $mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT); } @@ -910,7 +935,7 @@ class PdoSessionHandler extends AbstractSessionHandler protected function getConnection() { if (null === $this->pdo) { - $this->connect($this->dsn ?: ini_get('session.save_path')); + $this->connect($this->dsn ?: \ini_get('session.save_path')); } return $this->pdo; diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php new file mode 100644 index 0000000000..31954e677a --- /dev/null +++ b/lib/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Predis\Response\ErrorInterface; +use Symfony\Component\Cache\Traits\RedisClusterProxy; +use Symfony\Component\Cache\Traits\RedisProxy; + +/** + * Redis based session storage handler based on the Redis class + * provided by the PHP redis extension. + * + * @author Dalibor Karlović + */ +class RedisSessionHandler extends AbstractSessionHandler +{ + private $redis; + + /** + * @var string Key prefix for shared environments + */ + private $prefix; + + /** + * @var int Time to live in seconds + */ + private $ttl; + + /** + * List of available options: + * * prefix: The prefix to use for the keys in order to avoid collision on the Redis server + * * ttl: The time to live in seconds. + * + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis + * + * @throws \InvalidArgumentException When unsupported client or options are passed + */ + public function __construct($redis, array $options = []) + { + if ( + !$redis instanceof \Redis && + !$redis instanceof \RedisArray && + !$redis instanceof \RedisCluster && + !$redis instanceof \Predis\ClientInterface && + !$redis instanceof RedisProxy && + !$redis instanceof RedisClusterProxy + ) { + throw new \InvalidArgumentException(sprintf('"%s()" expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, "%s" given.', __METHOD__, get_debug_type($redis))); + } + + if ($diff = array_diff(array_keys($options), ['prefix', 'ttl'])) { + throw new \InvalidArgumentException(sprintf('The following options are not supported "%s".', implode(', ', $diff))); + } + + $this->redis = $redis; + $this->prefix = $options['prefix'] ?? 'sf_s'; + $this->ttl = $options['ttl'] ?? null; + } + + /** + * {@inheritdoc} + */ + protected function doRead(string $sessionId): string + { + return $this->redis->get($this->prefix.$sessionId) ?: ''; + } + + /** + * {@inheritdoc} + */ + protected function doWrite(string $sessionId, string $data): bool + { + $result = $this->redis->setEx($this->prefix.$sessionId, (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')), $data); + + return $result && !$result instanceof ErrorInterface; + } + + /** + * {@inheritdoc} + */ + protected function doDestroy(string $sessionId): bool + { + static $unlink = true; + + if ($unlink) { + try { + $unlink = false !== $this->redis->unlink($this->prefix.$sessionId); + } catch (\Throwable $e) { + $unlink = false; + } + } + + if (!$unlink) { + $this->redis->del($this->prefix.$sessionId); + } + + return true; + } + + /** + * {@inheritdoc} + */ + #[\ReturnTypeWillChange] + public function close(): bool + { + return true; + } + + /** + * {@inheritdoc} + * + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + return 0; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function updateTimestamp($sessionId, $data) + { + return (bool) $this->redis->expire($this->prefix.$sessionId, (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime'))); + } +} diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php b/lib/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php new file mode 100644 index 0000000000..f3f7b201d9 --- /dev/null +++ b/lib/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +use Doctrine\DBAL\DriverManager; +use Symfony\Component\Cache\Adapter\AbstractAdapter; +use Symfony\Component\Cache\Traits\RedisClusterProxy; +use Symfony\Component\Cache\Traits\RedisProxy; + +/** + * @author Nicolas Grekas + */ +class SessionHandlerFactory +{ + /** + * @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy|\Memcached|\PDO|string $connection Connection or DSN + */ + public static function createHandler($connection): AbstractSessionHandler + { + if (!\is_string($connection) && !\is_object($connection)) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a string or a connection object, "%s" given.', __METHOD__, get_debug_type($connection))); + } + + if ($options = \is_string($connection) ? parse_url($connection) : false) { + parse_str($options['query'] ?? '', $options); + } + + switch (true) { + case $connection instanceof \Redis: + case $connection instanceof \RedisArray: + case $connection instanceof \RedisCluster: + case $connection instanceof \Predis\ClientInterface: + case $connection instanceof RedisProxy: + case $connection instanceof RedisClusterProxy: + return new RedisSessionHandler($connection); + + case $connection instanceof \Memcached: + return new MemcachedSessionHandler($connection); + + case $connection instanceof \PDO: + return new PdoSessionHandler($connection); + + case !\is_string($connection): + throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', get_debug_type($connection))); + case str_starts_with($connection, 'file://'): + $savePath = substr($connection, 7); + + return new StrictSessionHandler(new NativeFileSessionHandler('' === $savePath ? null : $savePath)); + + case str_starts_with($connection, 'redis:'): + case str_starts_with($connection, 'rediss:'): + case str_starts_with($connection, 'memcached:'): + if (!class_exists(AbstractAdapter::class)) { + throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $connection)); + } + $handlerClass = str_starts_with($connection, 'memcached:') ? MemcachedSessionHandler::class : RedisSessionHandler::class; + $connection = AbstractAdapter::createConnection($connection, ['lazy' => true]); + + return new $handlerClass($connection, array_intersect_key($options ?: [], ['prefix' => 1, 'ttl' => 1])); + + case str_starts_with($connection, 'pdo_oci://'): + if (!class_exists(DriverManager::class)) { + throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require doctrine/dbal".', $connection)); + } + $connection = DriverManager::getConnection(['url' => $connection])->getWrappedConnection(); + // no break; + + case str_starts_with($connection, 'mssql://'): + case str_starts_with($connection, 'mysql://'): + case str_starts_with($connection, 'mysql2://'): + case str_starts_with($connection, 'pgsql://'): + case str_starts_with($connection, 'postgres://'): + case str_starts_with($connection, 'postgresql://'): + case str_starts_with($connection, 'sqlsrv://'): + case str_starts_with($connection, 'sqlite://'): + case str_starts_with($connection, 'sqlite3://'): + return new PdoSessionHandler($connection, $options ?: []); + } + + throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', $connection)); + } +} diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php index fab8e9a16d..0461e997e2 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php @@ -24,15 +24,16 @@ class StrictSessionHandler extends AbstractSessionHandler public function __construct(\SessionHandlerInterface $handler) { if ($handler instanceof \SessionUpdateTimestampHandlerInterface) { - throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', \get_class($handler), self::class)); + throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', get_debug_type($handler), self::class)); } $this->handler = $handler; } /** - * {@inheritdoc} + * @return bool */ + #[\ReturnTypeWillChange] public function open($savePath, $sessionName) { parent::open($savePath, $sessionName); @@ -43,14 +44,15 @@ class StrictSessionHandler extends AbstractSessionHandler /** * {@inheritdoc} */ - protected function doRead($sessionId) + protected function doRead(string $sessionId) { return $this->handler->read($sessionId); } /** - * {@inheritdoc} + * @return bool */ + #[\ReturnTypeWillChange] public function updateTimestamp($sessionId, $data) { return $this->write($sessionId, $data); @@ -59,14 +61,15 @@ class StrictSessionHandler extends AbstractSessionHandler /** * {@inheritdoc} */ - protected function doWrite($sessionId, $data) + protected function doWrite(string $sessionId, string $data) { return $this->handler->write($sessionId, $data); } /** - * {@inheritdoc} + * @return bool */ + #[\ReturnTypeWillChange] public function destroy($sessionId) { $this->doDestroy = true; @@ -78,7 +81,7 @@ class StrictSessionHandler extends AbstractSessionHandler /** * {@inheritdoc} */ - protected function doDestroy($sessionId) + protected function doDestroy(string $sessionId) { $this->doDestroy = false; @@ -86,16 +89,18 @@ class StrictSessionHandler extends AbstractSessionHandler } /** - * {@inheritdoc} + * @return bool */ + #[\ReturnTypeWillChange] public function close() { return $this->handler->close(); } /** - * @return bool + * @return int|false */ + #[\ReturnTypeWillChange] public function gc($maxlifetime) { return $this->handler->gc($maxlifetime); diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php deleted file mode 100644 index d1e5c14072..0000000000 --- a/lib/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php +++ /dev/null @@ -1,92 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; - -/** - * Wraps another SessionHandlerInterface to only write the session when it has been modified. - * - * @author Adrien Brault - * - * @deprecated since version 3.4, to be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead. - */ -class WriteCheckSessionHandler implements \SessionHandlerInterface -{ - private $wrappedSessionHandler; - - /** - * @var array sessionId => session - */ - private $readSessions; - - public function __construct(\SessionHandlerInterface $wrappedSessionHandler) - { - @trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead.', self::class), \E_USER_DEPRECATED); - - $this->wrappedSessionHandler = $wrappedSessionHandler; - } - - /** - * {@inheritdoc} - */ - public function close() - { - return $this->wrappedSessionHandler->close(); - } - - /** - * {@inheritdoc} - */ - public function destroy($sessionId) - { - return $this->wrappedSessionHandler->destroy($sessionId); - } - - /** - * {@inheritdoc} - */ - public function gc($maxlifetime) - { - return $this->wrappedSessionHandler->gc($maxlifetime); - } - - /** - * {@inheritdoc} - */ - public function open($savePath, $sessionName) - { - return $this->wrappedSessionHandler->open($savePath, $sessionName); - } - - /** - * {@inheritdoc} - */ - public function read($sessionId) - { - $session = $this->wrappedSessionHandler->read($sessionId); - - $this->readSessions[$sessionId] = $session; - - return $session; - } - - /** - * {@inheritdoc} - */ - public function write($sessionId, $data) - { - if (isset($this->readSessions[$sessionId]) && $data === $this->readSessions[$sessionId]) { - return true; - } - - return $this->wrappedSessionHandler->write($sessionId, $data); - } -} diff --git a/lib/symfony/http-foundation/Session/Storage/MetadataBag.php b/lib/symfony/http-foundation/Session/Storage/MetadataBag.php index a62f108b34..595a9e23c2 100644 --- a/lib/symfony/http-foundation/Session/Storage/MetadataBag.php +++ b/lib/symfony/http-foundation/Session/Storage/MetadataBag.php @@ -22,9 +22,9 @@ use Symfony\Component\HttpFoundation\Session\SessionBagInterface; */ class MetadataBag implements SessionBagInterface { - const CREATED = 'c'; - const UPDATED = 'u'; - const LIFETIME = 'l'; + public const CREATED = 'c'; + public const UPDATED = 'u'; + public const LIFETIME = 'l'; /** * @var string @@ -57,7 +57,7 @@ class MetadataBag implements SessionBagInterface * @param string $storageKey The key used to store bag in the session * @param int $updateThreshold The time to wait between two UPDATED updates */ - public function __construct($storageKey = '_sf2_meta', $updateThreshold = 0) + public function __construct(string $storageKey = '_sf2_meta', int $updateThreshold = 0) { $this->storageKey = $storageKey; $this->updateThreshold = $updateThreshold; @@ -100,7 +100,7 @@ class MetadataBag implements SessionBagInterface * to expire with browser session. Time is in seconds, and is * not a Unix timestamp. */ - public function stampNew($lifetime = null) + public function stampNew(int $lifetime = null) { $this->stampCreated($lifetime); } @@ -139,6 +139,7 @@ class MetadataBag implements SessionBagInterface public function clear() { // nothing to do + return null; } /** @@ -151,18 +152,16 @@ class MetadataBag implements SessionBagInterface /** * Sets name. - * - * @param string $name */ - public function setName($name) + public function setName(string $name) { $this->name = $name; } - private function stampCreated($lifetime = null) + private function stampCreated(int $lifetime = null): void { $timeStamp = time(); $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp; - $this->meta[self::LIFETIME] = (null === $lifetime) ? ini_get('session.cookie_lifetime') : $lifetime; + $this->meta[self::LIFETIME] = $lifetime ?? (int) \ini_get('session.cookie_lifetime'); } } diff --git a/lib/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php b/lib/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php index 5474d92e36..c5c2bb0731 100644 --- a/lib/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php +++ b/lib/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php @@ -62,11 +62,7 @@ class MockArraySessionStorage implements SessionStorageInterface */ protected $bags = []; - /** - * @param string $name Session name - * @param MetadataBag $metaBag MetadataBag instance - */ - public function __construct($name = 'MOCKSESSID', MetadataBag $metaBag = null) + public function __construct(string $name = 'MOCKSESSID', MetadataBag $metaBag = null) { $this->name = $name; $this->setMetadataBag($metaBag); @@ -98,7 +94,7 @@ class MockArraySessionStorage implements SessionStorageInterface /** * {@inheritdoc} */ - public function regenerate($destroy = false, $lifetime = null) + public function regenerate(bool $destroy = false, int $lifetime = null) { if (!$this->started) { $this->start(); @@ -121,7 +117,7 @@ class MockArraySessionStorage implements SessionStorageInterface /** * {@inheritdoc} */ - public function setId($id) + public function setId(string $id) { if ($this->started) { throw new \LogicException('Cannot set session ID after the session has started.'); @@ -141,7 +137,7 @@ class MockArraySessionStorage implements SessionStorageInterface /** * {@inheritdoc} */ - public function setName($name) + public function setName(string $name) { $this->name = $name; } @@ -187,7 +183,7 @@ class MockArraySessionStorage implements SessionStorageInterface /** * {@inheritdoc} */ - public function getBag($name) + public function getBag(string $name) { if (!isset($this->bags[$name])) { throw new \InvalidArgumentException(sprintf('The SessionBagInterface "%s" is not registered.', $name)); @@ -246,7 +242,7 @@ class MockArraySessionStorage implements SessionStorageInterface foreach ($bags as $bag) { $key = $bag->getStorageKey(); - $this->data[$key] = isset($this->data[$key]) ? $this->data[$key] : []; + $this->data[$key] = $this->data[$key] ?? []; $bag->initialize($this->data[$key]); } diff --git a/lib/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php b/lib/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php index c5b1d1a360..8e32a45e38 100644 --- a/lib/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php +++ b/lib/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php @@ -13,7 +13,8 @@ namespace Symfony\Component\HttpFoundation\Session\Storage; /** * MockFileSessionStorage is used to mock sessions for - * functional testing when done in a single PHP process. + * functional testing where you may need to persist session data + * across separate PHP processes. * * No PHP session is actually started since a session can be initialized * and shutdown only once per PHP execution cycle and this class does @@ -27,11 +28,9 @@ class MockFileSessionStorage extends MockArraySessionStorage private $savePath; /** - * @param string $savePath Path of directory to save session files - * @param string $name Session name - * @param MetadataBag $metaBag MetadataBag instance + * @param string|null $savePath Path of directory to save session files */ - public function __construct($savePath = null, $name = 'MOCKSESSID', MetadataBag $metaBag = null) + public function __construct(string $savePath = null, string $name = 'MOCKSESSID', MetadataBag $metaBag = null) { if (null === $savePath) { $savePath = sys_get_temp_dir(); @@ -69,7 +68,7 @@ class MockFileSessionStorage extends MockArraySessionStorage /** * {@inheritdoc} */ - public function regenerate($destroy = false, $lifetime = null) + public function regenerate(bool $destroy = false, int $lifetime = null) { if (!$this->started) { $this->start(); @@ -104,7 +103,10 @@ class MockFileSessionStorage extends MockArraySessionStorage try { if ($data) { - file_put_contents($this->getFilePath(), serialize($data)); + $path = $this->getFilePath(); + $tmp = $path.bin2hex(random_bytes(6)); + file_put_contents($tmp, serialize($data)); + rename($tmp, $path); } else { $this->destroy(); } @@ -112,9 +114,8 @@ class MockFileSessionStorage extends MockArraySessionStorage $this->data = $data; } - // this is needed for Silex, where the session object is re-used across requests - // in functional tests. In Symfony, the container is rebooted, so we don't have - // this issue + // this is needed when the session object is re-used across multiple requests + // in functional tests. $this->started = false; } @@ -122,19 +123,20 @@ class MockFileSessionStorage extends MockArraySessionStorage * Deletes a session from persistent storage. * Deliberately leaves session data in memory intact. */ - private function destroy() + private function destroy(): void { - if (is_file($this->getFilePath())) { + set_error_handler(static function () {}); + try { unlink($this->getFilePath()); + } finally { + restore_error_handler(); } } /** * Calculate path to file. - * - * @return string File path */ - private function getFilePath() + private function getFilePath(): string { return $this->savePath.'/'.$this->id.'.mocksess'; } @@ -142,10 +144,16 @@ class MockFileSessionStorage extends MockArraySessionStorage /** * Reads session from storage and loads session. */ - private function read() + private function read(): void { - $filePath = $this->getFilePath(); - $this->data = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : []; + set_error_handler(static function () {}); + try { + $data = file_get_contents($this->getFilePath()); + } finally { + restore_error_handler(); + } + + $this->data = $data ? unserialize($data) : []; $this->loadSession(); } diff --git a/lib/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php b/lib/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php new file mode 100644 index 0000000000..d0da1e1692 --- /dev/null +++ b/lib/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.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\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +// Help opcache.preload discover always-needed symbols +class_exists(MockFileSessionStorage::class); + +/** + * @author Jérémy Derussé + */ +class MockFileSessionStorageFactory implements SessionStorageFactoryInterface +{ + private $savePath; + private $name; + private $metaBag; + + /** + * @see MockFileSessionStorage constructor. + */ + public function __construct(string $savePath = null, string $name = 'MOCKSESSID', MetadataBag $metaBag = null) + { + $this->savePath = $savePath; + $this->name = $name; + $this->metaBag = $metaBag; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + return new MockFileSessionStorage($this->savePath, $this->name, $this->metaBag); + } +} diff --git a/lib/symfony/http-foundation/Session/Storage/NativeSessionStorage.php b/lib/symfony/http-foundation/Session/Storage/NativeSessionStorage.php index be84c6dcf9..a50c8270fe 100644 --- a/lib/symfony/http-foundation/Session/Storage/NativeSessionStorage.php +++ b/lib/symfony/http-foundation/Session/Storage/NativeSessionStorage.php @@ -17,6 +17,11 @@ use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandle use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; +// Help opcache.preload discover always-needed symbols +class_exists(MetadataBag::class); +class_exists(StrictSessionHandler::class); +class_exists(SessionHandlerProxy::class); + /** * This provides a base class for session attribute storage. * @@ -74,28 +79,17 @@ class NativeSessionStorage implements SessionStorageInterface * cookie_path, "/" * cookie_secure, "" * cookie_samesite, null - * entropy_file, "" - * entropy_length, "0" * gc_divisor, "100" * gc_maxlifetime, "1440" * gc_probability, "1" - * hash_bits_per_character, "4" - * hash_function, "0" * lazy_write, "1" * name, "PHPSESSID" * referer_check, "" * serialize_handler, "php" - * use_strict_mode, "0" + * use_strict_mode, "1" * use_cookies, "1" * use_only_cookies, "1" * use_trans_sid, "0" - * upload_progress.enabled, "1" - * upload_progress.cleanup, "1" - * upload_progress.prefix, "upload_progress_" - * upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS" - * upload_progress.freq, "1%" - * upload_progress.min-freq, "1" - * url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset=" * sid_length, "32" * sid_bits_per_character, "5" * trans_sid_hosts, $_SERVER['HTTP_HOST'] @@ -114,6 +108,7 @@ class NativeSessionStorage implements SessionStorageInterface 'cache_expire' => 0, 'use_cookies' => 1, 'lazy_write' => 1, + 'use_strict_mode' => 1, ]; session_register_shutdown(); @@ -146,10 +141,46 @@ class NativeSessionStorage implements SessionStorageInterface throw new \RuntimeException('Failed to start the session: already started by PHP.'); } - if (filter_var(ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN) && headers_sent($file, $line)) { + if (filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN) && headers_sent($file, $line)) { throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line)); } + $sessionId = $_COOKIE[session_name()] ?? null; + /* + * Explanation of the session ID regular expression: `/^[a-zA-Z0-9,-]{22,250}$/`. + * + * ---------- Part 1 + * + * The part `[a-zA-Z0-9,-]` is related to the PHP ini directive `session.sid_bits_per_character` defined as 6. + * See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-bits-per-character. + * Allowed values are integers such as: + * - 4 for range `a-f0-9` + * - 5 for range `a-v0-9` + * - 6 for range `a-zA-Z0-9,-` + * + * ---------- Part 2 + * + * The part `{22,250}` is related to the PHP ini directive `session.sid_length`. + * See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-length. + * Allowed values are integers between 22 and 256, but we use 250 for the max. + * + * Where does the 250 come from? + * - The length of Windows and Linux filenames is limited to 255 bytes. Then the max must not exceed 255. + * - The session filename prefix is `sess_`, a 5 bytes string. Then the max must not exceed 255 - 5 = 250. + * + * ---------- Conclusion + * + * The parts 1 and 2 prevent the warning below: + * `PHP Warning: SessionHandler::read(): Session ID is too long or contains illegal characters. Only the A-Z, a-z, 0-9, "-", and "," characters are allowed.` + * + * The part 2 prevents the warning below: + * `PHP Warning: SessionHandler::read(): open(filepath, O_RDWR) failed: No such file or directory (2).` + */ + if ($sessionId && $this->saveHandler instanceof AbstractProxy && 'files' === $this->saveHandler->getSaveHandlerName() && !preg_match('/^[a-zA-Z0-9,-]{22,250}$/', $sessionId)) { + // the session ID in the header is invalid, create a new one + session_id(session_create_id()); + } + // ok to try and start the session if (!session_start()) { throw new \RuntimeException('Failed to start the session.'); @@ -178,7 +209,7 @@ class NativeSessionStorage implements SessionStorageInterface /** * {@inheritdoc} */ - public function setId($id) + public function setId(string $id) { $this->saveHandler->setId($id); } @@ -194,7 +225,7 @@ class NativeSessionStorage implements SessionStorageInterface /** * {@inheritdoc} */ - public function setName($name) + public function setName(string $name) { $this->saveHandler->setName($name); } @@ -202,7 +233,7 @@ class NativeSessionStorage implements SessionStorageInterface /** * {@inheritdoc} */ - public function regenerate($destroy = false, $lifetime = null) + public function regenerate(bool $destroy = false, int $lifetime = null) { // Cannot regenerate the session ID for non-active sessions. if (\PHP_SESSION_ACTIVE !== session_status()) { @@ -213,7 +244,7 @@ class NativeSessionStorage implements SessionStorageInterface return false; } - if (null !== $lifetime && $lifetime != ini_get('session.cookie_lifetime')) { + if (null !== $lifetime && $lifetime != \ini_get('session.cookie_lifetime')) { $this->save(); ini_set('session.cookie_lifetime', $lifetime); $this->start(); @@ -248,13 +279,13 @@ class NativeSessionStorage implements SessionStorageInterface unset($_SESSION[$key]); } } - if ([$key = $this->metadataBag->getStorageKey()] === array_keys($_SESSION)) { + if ($_SESSION && [$key = $this->metadataBag->getStorageKey()] === array_keys($_SESSION)) { unset($_SESSION[$key]); } // Register error handler to add information about the current save handler $previousHandler = set_error_handler(function ($type, $msg, $file, $line) use (&$previousHandler) { - if (\E_WARNING === $type && 0 === strpos($msg, 'session_write_close():')) { + if (\E_WARNING === $type && str_starts_with($msg, 'session_write_close():')) { $handler = $this->saveHandler instanceof SessionHandlerProxy ? $this->saveHandler->getHandler() : $this->saveHandler; $msg = sprintf('session_write_close(): Failed to write session data with "%s" handler', \get_class($handler)); } @@ -309,7 +340,7 @@ class NativeSessionStorage implements SessionStorageInterface /** * {@inheritdoc} */ - public function getBag($name) + public function getBag(string $name) { if (!isset($this->bags[$name])) { throw new \InvalidArgumentException(sprintf('The SessionBagInterface "%s" is not registered.', $name)); @@ -370,9 +401,8 @@ class NativeSessionStorage implements SessionStorageInterface $validOptions = array_flip([ 'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly', 'cookie_lifetime', 'cookie_path', 'cookie_secure', 'cookie_samesite', - 'entropy_file', 'entropy_length', 'gc_divisor', - 'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character', - 'hash_function', 'lazy_write', 'name', 'referer_check', + 'gc_divisor', 'gc_maxlifetime', 'gc_probability', + 'lazy_write', 'name', 'referer_check', 'serialize_handler', 'use_strict_mode', 'use_cookies', 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled', 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name', @@ -382,12 +412,22 @@ class NativeSessionStorage implements SessionStorageInterface foreach ($options as $key => $value) { if (isset($validOptions[$key])) { + if (str_starts_with($key, 'upload_progress.')) { + trigger_deprecation('symfony/http-foundation', '5.4', 'Support for the "%s" session option is deprecated. The settings prefixed with "session.upload_progress." can not be changed at runtime.', $key); + continue; + } + if ('url_rewriter.tags' === $key) { + trigger_deprecation('symfony/http-foundation', '5.4', 'Support for the "%s" session option is deprecated. Use "trans_sid_tags" instead.', $key); + } if ('cookie_samesite' === $key && \PHP_VERSION_ID < 70300) { // PHP < 7.3 does not support same_site cookies. We will emulate it in // the start() method instead. $this->emulateSameSite = $value; continue; } + if ('cookie_secure' === $key && 'auto' === $value) { + continue; + } ini_set('url_rewriter.tags' !== $key ? 'session.'.$key : $key, $value); } } diff --git a/lib/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php b/lib/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php new file mode 100644 index 0000000000..a7d7411ff3 --- /dev/null +++ b/lib/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +// Help opcache.preload discover always-needed symbols +class_exists(NativeSessionStorage::class); + +/** + * @author Jérémy Derussé + */ +class NativeSessionStorageFactory implements SessionStorageFactoryInterface +{ + private $options; + private $handler; + private $metaBag; + private $secure; + + /** + * @see NativeSessionStorage constructor. + */ + public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null, bool $secure = false) + { + $this->options = $options; + $this->handler = $handler; + $this->metaBag = $metaBag; + $this->secure = $secure; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + $storage = new NativeSessionStorage($this->options, $this->handler, $this->metaBag); + if ($this->secure && $request && $request->isSecure()) { + $storage->setOptions(['cookie_secure' => true]); + } + + return $storage; + } +} diff --git a/lib/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php b/lib/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php index 8969e609aa..72dbef1346 100644 --- a/lib/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php +++ b/lib/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpFoundation\Session\Storage; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; + /** * Allows session to be started by PHP and managed by Symfony. * @@ -19,8 +21,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage; class PhpBridgeSessionStorage extends NativeSessionStorage { /** - * @param \SessionHandlerInterface|null $handler - * @param MetadataBag $metaBag MetadataBag + * @param AbstractProxy|\SessionHandlerInterface|null $handler */ public function __construct($handler = null, MetadataBag $metaBag = null) { diff --git a/lib/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php b/lib/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php new file mode 100644 index 0000000000..173ef71dea --- /dev/null +++ b/lib/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +// Help opcache.preload discover always-needed symbols +class_exists(PhpBridgeSessionStorage::class); + +/** + * @author Jérémy Derussé + */ +class PhpBridgeSessionStorageFactory implements SessionStorageFactoryInterface +{ + private $handler; + private $metaBag; + private $secure; + + /** + * @see PhpBridgeSessionStorage constructor. + */ + public function __construct($handler = null, MetadataBag $metaBag = null, bool $secure = false) + { + $this->handler = $handler; + $this->metaBag = $metaBag; + $this->secure = $secure; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + $storage = new PhpBridgeSessionStorage($this->handler, $this->metaBag); + if ($this->secure && $request && $request->isSecure()) { + $storage->setOptions(['cookie_secure' => true]); + } + + return $storage; + } +} diff --git a/lib/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php b/lib/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php index 9e1c94ddf6..edd04dff80 100644 --- a/lib/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php +++ b/lib/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php @@ -81,11 +81,9 @@ abstract class AbstractProxy /** * Sets the session ID. * - * @param string $id - * * @throws \LogicException */ - public function setId($id) + public function setId(string $id) { if ($this->isActive()) { throw new \LogicException('Cannot change the ID of an active session.'); @@ -107,11 +105,9 @@ abstract class AbstractProxy /** * Sets the session name. * - * @param string $name - * * @throws \LogicException */ - public function setName($name) + public function setName(string $name) { if ($this->isActive()) { throw new \LogicException('Cannot change the name of an active session.'); diff --git a/lib/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php b/lib/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php deleted file mode 100644 index 9d94ba97a9..0000000000 --- a/lib/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php +++ /dev/null @@ -1,40 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; - -@trigger_error('The '.__NAMESPACE__.'\NativeProxy class is deprecated since Symfony 3.4 and will be removed in 4.0. Use your session handler implementation directly.', \E_USER_DEPRECATED); - -/** - * This proxy is built-in session handlers in PHP 5.3.x. - * - * @deprecated since version 3.4, to be removed in 4.0. Use your session handler implementation directly. - * - * @author Drak - */ -class NativeProxy extends AbstractProxy -{ - public function __construct() - { - // this makes an educated guess as to what the handler is since it should already be set. - $this->saveHandlerName = ini_get('session.save_handler'); - } - - /** - * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. - * - * @return bool False - */ - public function isWrapper() - { - return false; - } -} diff --git a/lib/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php b/lib/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php index e40712d93f..6539acf989 100644 --- a/lib/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php +++ b/lib/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php @@ -21,8 +21,8 @@ class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterf public function __construct(\SessionHandlerInterface $handler) { $this->handler = $handler; - $this->wrapper = ($handler instanceof \SessionHandler); - $this->saveHandlerName = $this->wrapper ? ini_get('session.save_handler') : 'user'; + $this->wrapper = $handler instanceof \SessionHandler; + $this->saveHandlerName = $this->wrapper ? \ini_get('session.save_handler') : 'user'; } /** @@ -36,64 +36,72 @@ class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterf // \SessionHandlerInterface /** - * {@inheritdoc} + * @return bool */ + #[\ReturnTypeWillChange] public function open($savePath, $sessionName) { - return (bool) $this->handler->open($savePath, $sessionName); - } - - /** - * {@inheritdoc} - */ - public function close() - { - return (bool) $this->handler->close(); - } - - /** - * {@inheritdoc} - */ - public function read($sessionId) - { - return (string) $this->handler->read($sessionId); - } - - /** - * {@inheritdoc} - */ - public function write($sessionId, $data) - { - return (bool) $this->handler->write($sessionId, $data); - } - - /** - * {@inheritdoc} - */ - public function destroy($sessionId) - { - return (bool) $this->handler->destroy($sessionId); + return $this->handler->open($savePath, $sessionName); } /** * @return bool */ - public function gc($maxlifetime) + #[\ReturnTypeWillChange] + public function close() { - return (bool) $this->handler->gc($maxlifetime); + return $this->handler->close(); } /** - * {@inheritdoc} + * @return string|false */ + #[\ReturnTypeWillChange] + public function read($sessionId) + { + return $this->handler->read($sessionId); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function write($sessionId, $data) + { + return $this->handler->write($sessionId, $data); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function destroy($sessionId) + { + return $this->handler->destroy($sessionId); + } + + /** + * @return int|false + */ + #[\ReturnTypeWillChange] + public function gc($maxlifetime) + { + return $this->handler->gc($maxlifetime); + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] public function validateId($sessionId) { return !$this->handler instanceof \SessionUpdateTimestampHandlerInterface || $this->handler->validateId($sessionId); } /** - * {@inheritdoc} + * @return bool */ + #[\ReturnTypeWillChange] public function updateTimestamp($sessionId, $data) { return $this->handler instanceof \SessionUpdateTimestampHandlerInterface ? $this->handler->updateTimestamp($sessionId, $data) : $this->write($sessionId, $data); diff --git a/lib/symfony/http-foundation/Session/Storage/ServiceSessionFactory.php b/lib/symfony/http-foundation/Session/Storage/ServiceSessionFactory.php new file mode 100644 index 0000000000..d17c60aeba --- /dev/null +++ b/lib/symfony/http-foundation/Session/Storage/ServiceSessionFactory.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\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +/** + * @author Jérémy Derussé + * + * @internal to be removed in Symfony 6 + */ +final class ServiceSessionFactory implements SessionStorageFactoryInterface +{ + private $storage; + + public function __construct(SessionStorageInterface $storage) + { + $this->storage = $storage; + } + + public function createStorage(?Request $request): SessionStorageInterface + { + if ($this->storage instanceof NativeSessionStorage && $request && $request->isSecure()) { + $this->storage->setOptions(['cookie_secure' => true]); + } + + return $this->storage; + } +} diff --git a/lib/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php b/lib/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php new file mode 100644 index 0000000000..d03f0da4cd --- /dev/null +++ b/lib/symfony/http-foundation/Session/Storage/SessionStorageFactoryInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Request; + +/** + * @author Jérémy Derussé + */ +interface SessionStorageFactoryInterface +{ + /** + * Creates a new instance of SessionStorageInterface. + */ + public function createStorage(?Request $request): SessionStorageInterface; +} diff --git a/lib/symfony/http-foundation/Session/Storage/SessionStorageInterface.php b/lib/symfony/http-foundation/Session/Storage/SessionStorageInterface.php index eeb396a2f1..b7f66e7c73 100644 --- a/lib/symfony/http-foundation/Session/Storage/SessionStorageInterface.php +++ b/lib/symfony/http-foundation/Session/Storage/SessionStorageInterface.php @@ -24,7 +24,7 @@ interface SessionStorageInterface /** * Starts the session. * - * @return bool True if started + * @return bool * * @throws \RuntimeException if something goes wrong starting the session */ @@ -33,37 +33,33 @@ interface SessionStorageInterface /** * Checks if the session is started. * - * @return bool True if started, false otherwise + * @return bool */ public function isStarted(); /** * Returns the session ID. * - * @return string The session ID or empty + * @return string */ public function getId(); /** * Sets the session ID. - * - * @param string $id */ - public function setId($id); + public function setId(string $id); /** * Returns the session name. * - * @return mixed The session name + * @return string */ public function getName(); /** * Sets the session name. - * - * @param string $name */ - public function setName($name); + public function setName(string $name); /** * Regenerates id that represents this storage. @@ -90,11 +86,11 @@ interface SessionStorageInterface * to expire with browser session. Time is in seconds, and is * not a Unix timestamp. * - * @return bool True if session regenerated, false if error + * @return bool * * @throws \RuntimeException If an error occurs while regenerating this storage */ - public function regenerate($destroy = false, $lifetime = null); + public function regenerate(bool $destroy = false, int $lifetime = null); /** * Force the session to be saved and closed. @@ -117,13 +113,11 @@ interface SessionStorageInterface /** * Gets a SessionBagInterface by name. * - * @param string $name - * * @return SessionBagInterface * * @throws \InvalidArgumentException If the bag does not exist */ - public function getBag($name); + public function getBag(string $name); /** * Registers a SessionBagInterface for use. diff --git a/lib/symfony/http-foundation/StreamedResponse.php b/lib/symfony/http-foundation/StreamedResponse.php index b9148ea872..676cd66875 100644 --- a/lib/symfony/http-foundation/StreamedResponse.php +++ b/lib/symfony/http-foundation/StreamedResponse.php @@ -30,12 +30,7 @@ class StreamedResponse extends Response protected $streamed; private $headersSent; - /** - * @param callable|null $callback A valid PHP callback or null to set it later - * @param int $status The response status code - * @param array $headers An array of response headers - */ - public function __construct(callable $callback = null, $status = 200, $headers = []) + public function __construct(callable $callback = null, int $status = 200, array $headers = []) { parent::__construct(null, $status, $headers); @@ -50,21 +45,21 @@ class StreamedResponse extends Response * Factory method for chainability. * * @param callable|null $callback A valid PHP callback or null to set it later - * @param int $status The response status code - * @param array $headers An array of response headers * * @return static + * + * @deprecated since Symfony 5.1, use __construct() instead. */ - public static function create($callback = null, $status = 200, $headers = []) + public static function create($callback = null, int $status = 200, array $headers = []) { + trigger_deprecation('symfony/http-foundation', '5.1', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); + return new static($callback, $status, $headers); } /** * Sets the PHP callback associated with this Response. * - * @param callable $callback A valid PHP callback - * * @return $this */ public function setCallback(callable $callback) @@ -111,7 +106,7 @@ class StreamedResponse extends Response throw new \LogicException('The Response callback must not be null.'); } - \call_user_func($this->callback); + ($this->callback)(); return $this; } @@ -123,7 +118,7 @@ class StreamedResponse extends Response * * @return $this */ - public function setContent($content) + public function setContent(?string $content) { if (null !== $content) { throw new \LogicException('The content cannot be set on a StreamedResponse instance.'); diff --git a/lib/symfony/http-foundation/UrlHelper.php b/lib/symfony/http-foundation/UrlHelper.php new file mode 100644 index 0000000000..c15f101cdf --- /dev/null +++ b/lib/symfony/http-foundation/UrlHelper.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\Routing\RequestContext; + +/** + * A helper service for manipulating URLs within and outside the request scope. + * + * @author Valentin Udaltsov + */ +final class UrlHelper +{ + private $requestStack; + private $requestContext; + + public function __construct(RequestStack $requestStack, RequestContext $requestContext = null) + { + $this->requestStack = $requestStack; + $this->requestContext = $requestContext; + } + + public function getAbsoluteUrl(string $path): string + { + if (str_contains($path, '://') || '//' === substr($path, 0, 2)) { + return $path; + } + + if (null === $request = $this->requestStack->getMainRequest()) { + return $this->getAbsoluteUrlFromContext($path); + } + + if ('#' === $path[0]) { + $path = $request->getRequestUri().$path; + } elseif ('?' === $path[0]) { + $path = $request->getPathInfo().$path; + } + + if (!$path || '/' !== $path[0]) { + $prefix = $request->getPathInfo(); + $last = \strlen($prefix) - 1; + if ($last !== $pos = strrpos($prefix, '/')) { + $prefix = substr($prefix, 0, $pos).'/'; + } + + return $request->getUriForPath($prefix.$path); + } + + return $request->getSchemeAndHttpHost().$path; + } + + public function getRelativePath(string $path): string + { + if (str_contains($path, '://') || '//' === substr($path, 0, 2)) { + return $path; + } + + if (null === $request = $this->requestStack->getMainRequest()) { + return $path; + } + + return $request->getRelativeUriForPath($path); + } + + private function getAbsoluteUrlFromContext(string $path): string + { + if (null === $this->requestContext || '' === $host = $this->requestContext->getHost()) { + return $path; + } + + $scheme = $this->requestContext->getScheme(); + $port = ''; + + if ('http' === $scheme && 80 !== $this->requestContext->getHttpPort()) { + $port = ':'.$this->requestContext->getHttpPort(); + } elseif ('https' === $scheme && 443 !== $this->requestContext->getHttpsPort()) { + $port = ':'.$this->requestContext->getHttpsPort(); + } + + if ('#' === $path[0]) { + $queryString = $this->requestContext->getQueryString(); + $path = $this->requestContext->getPathInfo().($queryString ? '?'.$queryString : '').$path; + } elseif ('?' === $path[0]) { + $path = $this->requestContext->getPathInfo().$path; + } + + if ('/' !== $path[0]) { + $path = rtrim($this->requestContext->getBaseUrl(), '/').'/'.$path; + } + + return $scheme.'://'.$host.$port.$path; + } +} diff --git a/lib/symfony/http-foundation/composer.json b/lib/symfony/http-foundation/composer.json index 2f5ad1a31c..d54bbfd160 100644 --- a/lib/symfony/http-foundation/composer.json +++ b/lib/symfony/http-foundation/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/http-foundation", "type": "library", - "description": "Symfony HttpFoundation Component", + "description": "Defines an object-oriented layer for the HTTP specification", "keywords": [], "homepage": "https://symfony.com", "license": "MIT", @@ -16,12 +16,19 @@ } ], "require": { - "php": "^5.5.9|>=7.0.8", + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php70": "~1.6" + "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "symfony/expression-language": "~2.8|~3.0|~4.0" + "predis/predis": "~1.0", + "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0" + }, + "suggest" : { + "symfony/mime": "To use the file extension guesser" }, "autoload": { "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" }, diff --git a/lib/symfony/http-foundation/phpunit.xml.dist b/lib/symfony/http-foundation/phpunit.xml.dist deleted file mode 100644 index f57bc9e62d..0000000000 --- a/lib/symfony/http-foundation/phpunit.xml.dist +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - ./Tests/ - - - - - - ./ - - ./Resources - ./Tests - ./vendor - - - - diff --git a/lib/symfony/http-kernel/.gitignore b/lib/symfony/http-kernel/.gitignore deleted file mode 100644 index 94a6a25288..0000000000 --- a/lib/symfony/http-kernel/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -vendor/ -composer.lock -phpunit.xml -Tests/Fixtures/cache/ -Tests/Fixtures/logs/ diff --git a/lib/symfony/http-kernel/Attribute/ArgumentInterface.php b/lib/symfony/http-kernel/Attribute/ArgumentInterface.php new file mode 100644 index 0000000000..78769f1ac0 --- /dev/null +++ b/lib/symfony/http-kernel/Attribute/ArgumentInterface.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\Component\HttpKernel\Attribute; + +trigger_deprecation('symfony/http-kernel', '5.3', 'The "%s" interface is deprecated.', ArgumentInterface::class); + +/** + * Marker interface for controller argument attributes. + * + * @deprecated since Symfony 5.3 + */ +interface ArgumentInterface +{ +} diff --git a/lib/symfony/http-kernel/Attribute/AsController.php b/lib/symfony/http-kernel/Attribute/AsController.php new file mode 100644 index 0000000000..ef37104513 --- /dev/null +++ b/lib/symfony/http-kernel/Attribute/AsController.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\Component\HttpKernel\Attribute; + +/** + * Service tag to autoconfigure controllers. + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class AsController +{ + public function __construct() + { + } +} diff --git a/lib/symfony/http-kernel/Bundle/Bundle.php b/lib/symfony/http-kernel/Bundle/Bundle.php index d0f9811e97..54a1d10b90 100644 --- a/lib/symfony/http-kernel/Bundle/Bundle.php +++ b/lib/symfony/http-kernel/Bundle/Bundle.php @@ -16,7 +16,6 @@ use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerAwareTrait; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; -use Symfony\Component\Finder\Finder; /** * An implementation of BundleInterface that adds a few conventions for DependencyInjection extensions. @@ -59,7 +58,7 @@ abstract class Bundle implements BundleInterface /** * Returns the bundle's container extension. * - * @return ExtensionInterface|null The container extension + * @return ExtensionInterface|null * * @throws \LogicException */ @@ -70,7 +69,7 @@ abstract class Bundle implements BundleInterface if (null !== $extension) { if (!$extension instanceof ExtensionInterface) { - throw new \LogicException(sprintf('Extension "%s" must implement Symfony\Component\DependencyInjection\Extension\ExtensionInterface.', \get_class($extension))); + throw new \LogicException(sprintf('Extension "%s" must implement Symfony\Component\DependencyInjection\Extension\ExtensionInterface.', get_debug_type($extension))); } // check naming convention @@ -116,16 +115,9 @@ abstract class Bundle implements BundleInterface } /** - * {@inheritdoc} + * Returns the bundle name (the class short name). */ - public function getParent() - { - } - - /** - * {@inheritdoc} - */ - final public function getName() + final public function getName(): string { if (null === $this->name) { $this->parseClassName(); @@ -134,48 +126,8 @@ abstract class Bundle implements BundleInterface return $this->name; } - /** - * Finds and registers Commands. - * - * Override this method if your bundle commands do not follow the conventions: - * - * * Commands are in the 'Command' sub-directory - * * Commands extend Symfony\Component\Console\Command\Command - */ public function registerCommands(Application $application) { - if (!is_dir($dir = $this->getPath().'/Command')) { - return; - } - - if (!class_exists('Symfony\Component\Finder\Finder')) { - throw new \RuntimeException('You need the symfony/finder component to register bundle commands.'); - } - - $finder = new Finder(); - $finder->files()->name('*Command.php')->in($dir); - - $prefix = $this->getNamespace().'\\Command'; - foreach ($finder as $file) { - $ns = $prefix; - if ($relativePath = $file->getRelativePath()) { - $ns .= '\\'.str_replace('/', '\\', $relativePath); - } - $class = $ns.'\\'.$file->getBasename('.php'); - if ($this->container) { - $commandIds = $this->container->hasParameter('console.command.ids') ? $this->container->getParameter('console.command.ids') : []; - $alias = 'console.command.'.strtolower(str_replace('\\', '_', $class)); - if (isset($commandIds[$alias]) || $this->container->has($alias)) { - continue; - } - } - $r = new \ReflectionClass($class); - if ($r->isSubclassOf('Symfony\\Component\\Console\\Command\\Command') && !$r->isAbstract() && !$r->getConstructor()->getNumberOfRequiredParameters()) { - @trigger_error(sprintf('Auto-registration of the command "%s" is deprecated since Symfony 3.4 and won\'t be supported in 4.0. Use PSR-4 based service discovery instead.', $class), \E_USER_DEPRECATED); - - $application->add($r->newInstance()); - } - } } /** diff --git a/lib/symfony/http-kernel/Bundle/BundleInterface.php b/lib/symfony/http-kernel/Bundle/BundleInterface.php index 14a7f6f4fd..fdc13e0c87 100644 --- a/lib/symfony/http-kernel/Bundle/BundleInterface.php +++ b/lib/symfony/http-kernel/Bundle/BundleInterface.php @@ -42,34 +42,21 @@ interface BundleInterface extends ContainerAwareInterface /** * Returns the container extension that should be implicitly loaded. * - * @return ExtensionInterface|null The default extension or null if there is none + * @return ExtensionInterface|null */ public function getContainerExtension(); - /** - * Returns the bundle name that this bundle overrides. - * - * Despite its name, this method does not imply any parent/child relationship - * between the bundles, just a way to extend and override an existing - * bundle. - * - * @return string The Bundle name it overrides or null if no parent - * - * @deprecated This method is deprecated as of 3.4 and will be removed in 4.0. - */ - public function getParent(); - /** * Returns the bundle name (the class short name). * - * @return string The Bundle name + * @return string */ public function getName(); /** * Gets the Bundle namespace. * - * @return string The Bundle namespace + * @return string */ public function getNamespace(); @@ -78,7 +65,7 @@ interface BundleInterface extends ContainerAwareInterface * * The path should always be returned as a Unix path (with /). * - * @return string The Bundle absolute path + * @return string */ public function getPath(); } diff --git a/lib/symfony/http-kernel/CHANGELOG.md b/lib/symfony/http-kernel/CHANGELOG.md index fb29f76962..d0dc2076c2 100644 --- a/lib/symfony/http-kernel/CHANGELOG.md +++ b/lib/symfony/http-kernel/CHANGELOG.md @@ -1,6 +1,172 @@ CHANGELOG ========= +5.4 +--- + + * Add the ability to enable the profiler using a request query parameter, body parameter or attribute + * Deprecate `AbstractTestSessionListener` and `TestSessionListener`, use `AbstractSessionListener` and `SessionListener` instead + * Deprecate the `fileLinkFormat` parameter of `DebugHandlersListener` + * Add support for configuring log level, and status code by exception class + * Allow ignoring "kernel.reset" methods that don't exist with "on_invalid" attribute + +5.3 +--- + + * Deprecate `ArgumentInterface` + * Add `ArgumentMetadata::getAttributes()` + * Deprecate `ArgumentMetadata::getAttribute()`, use `getAttributes()` instead + * Mark the class `Symfony\Component\HttpKernel\EventListener\DebugHandlersListener` as internal + * Deprecate returning a `ContainerBuilder` from `KernelInterface::registerContainerConfiguration()` + * Deprecate `HttpKernelInterface::MASTER_REQUEST` and add `HttpKernelInterface::MAIN_REQUEST` as replacement + * Deprecate `KernelEvent::isMasterRequest()` and add `isMainRequest()` as replacement + * Add `#[AsController]` attribute for declaring standalone controllers on PHP 8 + * Add `FragmentUriGeneratorInterface` and `FragmentUriGenerator` to generate the URI of a fragment + +5.2.0 +----- + + * added session usage + * made the public `http_cache` service handle requests when available + * allowed enabling trusted hosts and proxies using new `kernel.trusted_hosts`, + `kernel.trusted_proxies` and `kernel.trusted_headers` parameters + * content of request parameter `_password` is now also hidden + in the request profiler raw content section + * Allowed adding attributes on controller arguments that will be passed to argument resolvers. + * kernels implementing the `ExtensionInterface` will now be auto-registered to the container + * added parameter `kernel.runtime_environment`, defined as `%env(default:kernel.environment:APP_RUNTIME_ENV)%` + * do not set a default `Accept` HTTP header when using `HttpKernelBrowser` + +5.1.0 +----- + + * allowed to use a specific logger channel for deprecations + * made `WarmableInterface::warmUp()` return a list of classes or files to preload on PHP 7.4+; + not returning an array is deprecated + * made kernels implementing `WarmableInterface` be part of the cache warmup stage + * deprecated support for `service:action` syntax to reference controllers, use `serviceOrFqcn::method` instead + * allowed using public aliases to reference controllers + * added session usage reporting when the `_stateless` attribute of the request is set to `true` + * added `AbstractSessionListener::onSessionUsage()` to report when the session is used while a request is stateless + +5.0.0 +----- + + * removed support for getting the container from a non-booted kernel + * removed the first and second constructor argument of `ConfigDataCollector` + * removed `ConfigDataCollector::getApplicationName()` + * removed `ConfigDataCollector::getApplicationVersion()` + * removed support for `Symfony\Component\Templating\EngineInterface` in `HIncludeFragmentRenderer`, use a `Twig\Environment` only + * removed `TranslatorListener` in favor of `LocaleAwareListener` + * removed `getRootDir()` and `getName()` from `Kernel` and `KernelInterface` + * removed `FilterControllerArgumentsEvent`, use `ControllerArgumentsEvent` instead + * removed `FilterControllerEvent`, use `ControllerEvent` instead + * removed `FilterResponseEvent`, use `ResponseEvent` instead + * removed `GetResponseEvent`, use `RequestEvent` instead + * removed `GetResponseForControllerResultEvent`, use `ViewEvent` instead + * removed `GetResponseForExceptionEvent`, use `ExceptionEvent` instead + * removed `PostResponseEvent`, use `TerminateEvent` instead + * removed `SaveSessionListener` in favor of `AbstractSessionListener` + * removed `Client`, use `HttpKernelBrowser` instead + * added method `getProjectDir()` to `KernelInterface` + * removed methods `serialize` and `unserialize` from `DataCollector`, store the serialized state in the data property instead + * made `ProfilerStorageInterface` internal + * removed the second and third argument of `KernelInterface::locateResource` + * removed the second and third argument of `FileLocator::__construct` + * removed loading resources from `%kernel.root_dir%/Resources` and `%kernel.root_dir%` as + fallback directories. + * removed class `ExceptionListener`, use `ErrorListener` instead + +4.4.0 +----- + + * The `DebugHandlersListener` class has been marked as `final` + * Added new Bundle directory convention consistent with standard skeletons + * Deprecated the second and third argument of `KernelInterface::locateResource` + * Deprecated the second and third argument of `FileLocator::__construct` + * Deprecated loading resources from `%kernel.root_dir%/Resources` and `%kernel.root_dir%` as + fallback directories. Resources like service definitions are usually loaded relative to the + current directory or with a glob pattern. The fallback directories have never been advocated + so you likely do not use those in any app based on the SF Standard or Flex edition. + * Marked all dispatched event classes as `@final` + * Added `ErrorController` to enable the preview and error rendering mechanism + * Getting the container from a non-booted kernel is deprecated. + * Marked the `AjaxDataCollector`, `ConfigDataCollector`, `EventDataCollector`, + `ExceptionDataCollector`, `LoggerDataCollector`, `MemoryDataCollector`, + `RequestDataCollector` and `TimeDataCollector` classes as `@final`. + * Marked the `RouterDataCollector::collect()` method as `@final`. + * The `DataCollectorInterface::collect()` and `Profiler::collect()` methods third parameter signature + will be `\Throwable $exception = null` instead of `\Exception $exception = null` in Symfony 5.0. + * Deprecated methods `ExceptionEvent::get/setException()`, use `get/setThrowable()` instead + * Deprecated class `ExceptionListener`, use `ErrorListener` instead + +4.3.0 +----- + + * renamed `Client` to `HttpKernelBrowser` + * `KernelInterface` doesn't extend `Serializable` anymore + * deprecated the `Kernel::serialize()` and `unserialize()` methods + * increased the priority of `Symfony\Component\HttpKernel\EventListener\AddRequestFormatsListener` + * made `Symfony\Component\HttpKernel\EventListener\LocaleListener` set the default locale early + * deprecated `TranslatorListener` in favor of `LocaleAwareListener` + * added the registration of all `LocaleAwareInterface` implementations into the `LocaleAwareListener` + * made `FileLinkFormatter` final and not implement `Serializable` anymore + * the base `DataCollector` doesn't implement `Serializable` anymore, you should + store all the serialized state in the data property instead + * `DumpDataCollector` has been marked as `final` + * added an event listener to prevent search engines from indexing applications in debug mode. + * renamed `FilterControllerArgumentsEvent` to `ControllerArgumentsEvent` + * renamed `FilterControllerEvent` to `ControllerEvent` + * renamed `FilterResponseEvent` to `ResponseEvent` + * renamed `GetResponseEvent` to `RequestEvent` + * renamed `GetResponseForControllerResultEvent` to `ViewEvent` + * renamed `GetResponseForExceptionEvent` to `ExceptionEvent` + * renamed `PostResponseEvent` to `TerminateEvent` + * added `HttpClientKernel` for handling requests with an `HttpClientInterface` instance + * added `trace_header` and `trace_level` configuration options to `HttpCache` + +4.2.0 +----- + + * deprecated `KernelInterface::getRootDir()` and the `kernel.root_dir` parameter + * deprecated `KernelInterface::getName()` and the `kernel.name` parameter + * deprecated the first and second constructor argument of `ConfigDataCollector` + * deprecated `ConfigDataCollector::getApplicationName()` + * deprecated `ConfigDataCollector::getApplicationVersion()` + +4.1.0 +----- + + * added orphaned events support to `EventDataCollector` + * `ExceptionListener` now logs exceptions at priority `0` (previously logged at `-128`) + * Added support for using `service::method` to reference controllers, making it consistent with other cases. It is recommended over the `service:action` syntax with a single colon, which will be deprecated in the future. + * Added the ability to profile individual argument value resolvers via the + `Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver` + +4.0.0 +----- + + * removed the `DataCollector::varToString()` method, use `DataCollector::cloneVar()` + instead + * using the `DataCollector::cloneVar()` method requires the VarDumper component + * removed the `ValueExporter` class + * removed `ControllerResolverInterface::getArguments()` + * removed `TraceableControllerResolver::getArguments()` + * removed `ControllerResolver::getArguments()` and the ability to resolve arguments + * removed the `argument_resolver` service dependency from the `debug.controller_resolver` + * removed `LazyLoadingFragmentHandler::addRendererService()` + * removed `Psr6CacheClearer::addPool()` + * removed `Extension::addClassesToCompile()` and `Extension::getClassesToCompile()` + * removed `Kernel::loadClassCache()`, `Kernel::doLoadClassCache()`, `Kernel::setClassCache()`, + and `Kernel::getEnvParameters()` + * support for the `X-Status-Code` when handling exceptions in the `HttpKernel` + has been dropped, use the `HttpKernel::allowCustomResponseCode()` method + instead + * removed convention-based commands registration + * removed the `ChainCacheClearer::add()` method + * removed the `CacheaWarmerAggregate::add()` and `setWarmers()` methods + * made `CacheWarmerAggregate` and `ChainCacheClearer` classes final + 3.4.0 ----- diff --git a/lib/symfony/http-kernel/CacheClearer/CacheClearerInterface.php b/lib/symfony/http-kernel/CacheClearer/CacheClearerInterface.php index 675c584234..270f690e5b 100644 --- a/lib/symfony/http-kernel/CacheClearer/CacheClearerInterface.php +++ b/lib/symfony/http-kernel/CacheClearer/CacheClearerInterface.php @@ -20,8 +20,6 @@ interface CacheClearerInterface { /** * Clears any caches necessary. - * - * @param string $cacheDir The cache directory */ - public function clear($cacheDir); + public function clear(string $cacheDir); } diff --git a/lib/symfony/http-kernel/CacheClearer/ChainCacheClearer.php b/lib/symfony/http-kernel/CacheClearer/ChainCacheClearer.php index a71cc4056a..a875d899d0 100644 --- a/lib/symfony/http-kernel/CacheClearer/ChainCacheClearer.php +++ b/lib/symfony/http-kernel/CacheClearer/ChainCacheClearer.php @@ -16,18 +16,16 @@ namespace Symfony\Component\HttpKernel\CacheClearer; * * @author Dustin Dobervich * - * @final since version 3.4 + * @final */ class ChainCacheClearer implements CacheClearerInterface { - protected $clearers; + private $clearers; /** - * Constructs a new instance of ChainCacheClearer. - * - * @param array $clearers The initial clearers + * @param iterable $clearers */ - public function __construct($clearers = []) + public function __construct(iterable $clearers = []) { $this->clearers = $clearers; } @@ -35,22 +33,10 @@ class ChainCacheClearer implements CacheClearerInterface /** * {@inheritdoc} */ - public function clear($cacheDir) + public function clear(string $cacheDir) { foreach ($this->clearers as $clearer) { $clearer->clear($cacheDir); } } - - /** - * Adds a cache clearer to the aggregate. - * - * @deprecated since version 3.4, to be removed in 4.0, inject the list of clearers as a constructor argument instead. - */ - public function add(CacheClearerInterface $clearer) - { - @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.4 and will be removed in 4.0, inject the list of clearers as a constructor argument instead.', __METHOD__), \E_USER_DEPRECATED); - - $this->clearers[] = $clearer; - } } diff --git a/lib/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php b/lib/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php index 4271e0f88a..a074060e44 100644 --- a/lib/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php +++ b/lib/symfony/http-kernel/CacheClearer/Psr6CacheClearer.php @@ -20,24 +20,42 @@ class Psr6CacheClearer implements CacheClearerInterface { private $pools = []; + /** + * @param array $pools + */ public function __construct(array $pools = []) { $this->pools = $pools; } - public function addPool(CacheItemPoolInterface $pool) - { - @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Pass an array of pools indexed by name to the constructor instead.', __METHOD__), \E_USER_DEPRECATED); - - $this->pools[] = $pool; - } - - public function hasPool($name) + /** + * @return bool + */ + public function hasPool(string $name) { return isset($this->pools[$name]); } - public function clearPool($name) + /** + * @return CacheItemPoolInterface + * + * @throws \InvalidArgumentException If the cache pool with the given name does not exist + */ + public function getPool(string $name) + { + if (!$this->hasPool($name)) { + throw new \InvalidArgumentException(sprintf('Cache pool not found: "%s".', $name)); + } + + return $this->pools[$name]; + } + + /** + * @return bool + * + * @throws \InvalidArgumentException If the cache pool with the given name does not exist + */ + public function clearPool(string $name) { if (!isset($this->pools[$name])) { throw new \InvalidArgumentException(sprintf('Cache pool not found: "%s".', $name)); @@ -49,7 +67,7 @@ class Psr6CacheClearer implements CacheClearerInterface /** * {@inheritdoc} */ - public function clear($cacheDir) + public function clear(string $cacheDir) { foreach ($this->pools as $pool) { $pool->clear(); diff --git a/lib/symfony/http-kernel/CacheWarmer/CacheWarmer.php b/lib/symfony/http-kernel/CacheWarmer/CacheWarmer.php index 52dc2ad2c3..aef42d62f4 100644 --- a/lib/symfony/http-kernel/CacheWarmer/CacheWarmer.php +++ b/lib/symfony/http-kernel/CacheWarmer/CacheWarmer.php @@ -18,7 +18,7 @@ namespace Symfony\Component\HttpKernel\CacheWarmer; */ abstract class CacheWarmer implements CacheWarmerInterface { - protected function writeCacheFile($file, $content) + protected function writeCacheFile(string $file, $content) { $tmpFile = @tempnam(\dirname($file), basename($file)); if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) { diff --git a/lib/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php b/lib/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php index 010a3998c6..67f9ed50b4 100644 --- a/lib/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php +++ b/lib/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php @@ -16,20 +16,24 @@ namespace Symfony\Component\HttpKernel\CacheWarmer; * * @author Fabien Potencier * - * @final since version 3.4 + * @final */ class CacheWarmerAggregate implements CacheWarmerInterface { - protected $warmers = []; - protected $optionalsEnabled = false; - private $triggerDeprecation = false; + private $warmers; + private $debug; + private $deprecationLogsFilepath; + private $optionalsEnabled = false; + private $onlyOptionalsEnabled = false; - public function __construct($warmers = []) + /** + * @param iterable $warmers + */ + public function __construct(iterable $warmers = [], bool $debug = false, string $deprecationLogsFilepath = null) { - foreach ($warmers as $warmer) { - $this->add($warmer); - } - $this->triggerDeprecation = true; + $this->warmers = $warmers; + $this->debug = $debug; + $this->deprecationLogsFilepath = $deprecationLogsFilepath; } public function enableOptionalWarmers() @@ -37,54 +41,86 @@ class CacheWarmerAggregate implements CacheWarmerInterface $this->optionalsEnabled = true; } - /** - * Warms up the cache. - * - * @param string $cacheDir The cache directory - */ - public function warmUp($cacheDir) + public function enableOnlyOptionalWarmers() { - foreach ($this->warmers as $warmer) { - if (!$this->optionalsEnabled && $warmer->isOptional()) { - continue; - } - - $warmer->warmUp($cacheDir); - } + $this->onlyOptionalsEnabled = $this->optionalsEnabled = true; } /** - * Checks whether this warmer is optional or not. - * - * @return bool always false + * {@inheritdoc} */ - public function isOptional() + public function warmUp(string $cacheDir): array + { + if ($collectDeprecations = $this->debug && !\defined('PHPUNIT_COMPOSER_INSTALL')) { + $collectedLogs = []; + $previousHandler = set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) { + if (\E_USER_DEPRECATED !== $type && \E_DEPRECATED !== $type) { + return $previousHandler ? $previousHandler($type, $message, $file, $line) : false; + } + + if (isset($collectedLogs[$message])) { + ++$collectedLogs[$message]['count']; + + return null; + } + + $backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 3); + // Clean the trace by removing first frames added by the error handler itself. + for ($i = 0; isset($backtrace[$i]); ++$i) { + if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { + $backtrace = \array_slice($backtrace, 1 + $i); + break; + } + } + + $collectedLogs[$message] = [ + 'type' => $type, + 'message' => $message, + 'file' => $file, + 'line' => $line, + 'trace' => $backtrace, + 'count' => 1, + ]; + + return null; + }); + } + + $preload = []; + try { + foreach ($this->warmers as $warmer) { + if (!$this->optionalsEnabled && $warmer->isOptional()) { + continue; + } + if ($this->onlyOptionalsEnabled && !$warmer->isOptional()) { + continue; + } + + $preload[] = array_values((array) $warmer->warmUp($cacheDir)); + } + } finally { + if ($collectDeprecations) { + restore_error_handler(); + + if (is_file($this->deprecationLogsFilepath)) { + $previousLogs = unserialize(file_get_contents($this->deprecationLogsFilepath)); + if (\is_array($previousLogs)) { + $collectedLogs = array_merge($previousLogs, $collectedLogs); + } + } + + file_put_contents($this->deprecationLogsFilepath, serialize(array_values($collectedLogs))); + } + } + + return array_values(array_unique(array_merge([], ...$preload))); + } + + /** + * {@inheritdoc} + */ + public function isOptional(): bool { return false; } - - /** - * @deprecated since version 3.4, to be removed in 4.0, inject the list of clearers as a constructor argument instead. - */ - public function setWarmers(array $warmers) - { - @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.4 and will be removed in 4.0, inject the list of clearers as a constructor argument instead.', __METHOD__), \E_USER_DEPRECATED); - - $this->warmers = []; - foreach ($warmers as $warmer) { - $this->add($warmer); - } - } - - /** - * @deprecated since version 3.4, to be removed in 4.0, inject the list of clearers as a constructor argument instead. - */ - public function add(CacheWarmerInterface $warmer) - { - if ($this->triggerDeprecation) { - @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.4 and will be removed in 4.0, inject the list of clearers as a constructor argument instead.', __METHOD__), \E_USER_DEPRECATED); - } - - $this->warmers[] = $warmer; - } } diff --git a/lib/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php b/lib/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php index 8fece5e954..1f1740b7e2 100644 --- a/lib/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php +++ b/lib/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php @@ -26,7 +26,7 @@ interface CacheWarmerInterface extends WarmableInterface * A warmer should return true if the cache can be * generated incrementally and on-demand. * - * @return bool true if the warmer is optional, false otherwise + * @return bool */ public function isOptional(); } diff --git a/lib/symfony/http-kernel/CacheWarmer/WarmableInterface.php b/lib/symfony/http-kernel/CacheWarmer/WarmableInterface.php index 25d8ee8f61..2f442cb536 100644 --- a/lib/symfony/http-kernel/CacheWarmer/WarmableInterface.php +++ b/lib/symfony/http-kernel/CacheWarmer/WarmableInterface.php @@ -21,7 +21,7 @@ interface WarmableInterface /** * Warms up the cache. * - * @param string $cacheDir The cache directory + * @return string[] A list of classes or files to preload on PHP 7.4+ */ - public function warmUp($cacheDir); + public function warmUp(string $cacheDir); } diff --git a/lib/symfony/http-kernel/Client.php b/lib/symfony/http-kernel/Client.php deleted file mode 100644 index 14794727f3..0000000000 --- a/lib/symfony/http-kernel/Client.php +++ /dev/null @@ -1,206 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel; - -use Symfony\Component\BrowserKit\Client as BaseClient; -use Symfony\Component\BrowserKit\CookieJar; -use Symfony\Component\BrowserKit\History; -use Symfony\Component\BrowserKit\Request as DomRequest; -use Symfony\Component\BrowserKit\Response as DomResponse; -use Symfony\Component\HttpFoundation\File\UploadedFile; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; - -/** - * Client simulates a browser and makes requests to a Kernel object. - * - * @author Fabien Potencier - * - * @method Request|null getRequest() A Request instance - * @method Response|null getResponse() A Response instance - */ -class Client extends BaseClient -{ - protected $kernel; - private $catchExceptions = true; - - /** - * @param HttpKernelInterface $kernel An HttpKernel instance - * @param array $server The server parameters (equivalent of $_SERVER) - * @param History $history A History instance to store the browser history - * @param CookieJar $cookieJar A CookieJar instance to store the cookies - */ - public function __construct(HttpKernelInterface $kernel, array $server = [], History $history = null, CookieJar $cookieJar = null) - { - // These class properties must be set before calling the parent constructor, as it may depend on it. - $this->kernel = $kernel; - $this->followRedirects = false; - - parent::__construct($server, $history, $cookieJar); - } - - /** - * Sets whether to catch exceptions when the kernel is handling a request. - * - * @param bool $catchExceptions Whether to catch exceptions - */ - public function catchExceptions($catchExceptions) - { - $this->catchExceptions = $catchExceptions; - } - - /** - * Makes a request. - * - * @return Response A Response instance - */ - protected function doRequest($request) - { - $response = $this->kernel->handle($request, HttpKernelInterface::MASTER_REQUEST, $this->catchExceptions); - - if ($this->kernel instanceof TerminableInterface) { - $this->kernel->terminate($request, $response); - } - - return $response; - } - - /** - * Returns the script to execute when the request must be insulated. - * - * @return string - */ - protected function getScript($request) - { - $kernel = var_export(serialize($this->kernel), true); - $request = var_export(serialize($request), true); - - $errorReporting = error_reporting(); - - $requires = ''; - foreach (get_declared_classes() as $class) { - if (0 === strpos($class, 'ComposerAutoloaderInit')) { - $r = new \ReflectionClass($class); - $file = \dirname(\dirname($r->getFileName())).'/autoload.php'; - if (file_exists($file)) { - $requires .= 'require_once '.var_export($file, true).";\n"; - } - } - } - - if (!$requires) { - throw new \RuntimeException('Composer autoloader not found.'); - } - - $code = <<getHandleScript(); - } - - protected function getHandleScript() - { - return <<<'EOF' -$response = $kernel->handle($request); - -if ($kernel instanceof Symfony\Component\HttpKernel\TerminableInterface) { - $kernel->terminate($request, $response); -} - -echo serialize($response); -EOF; - } - - /** - * Converts the BrowserKit request to a HttpKernel request. - * - * @return Request A Request instance - */ - protected function filterRequest(DomRequest $request) - { - $httpRequest = Request::create($request->getUri(), $request->getMethod(), $request->getParameters(), $request->getCookies(), $request->getFiles(), $request->getServer(), $request->getContent()); - - foreach ($this->filterFiles($httpRequest->files->all()) as $key => $value) { - $httpRequest->files->set($key, $value); - } - - return $httpRequest; - } - - /** - * Filters an array of files. - * - * This method created test instances of UploadedFile so that the move() - * method can be called on those instances. - * - * If the size of a file is greater than the allowed size (from php.ini) then - * an invalid UploadedFile is returned with an error set to UPLOAD_ERR_INI_SIZE. - * - * @see UploadedFile - * - * @return array An array with all uploaded files marked as already moved - */ - protected function filterFiles(array $files) - { - $filtered = []; - foreach ($files as $key => $value) { - if (\is_array($value)) { - $filtered[$key] = $this->filterFiles($value); - } elseif ($value instanceof UploadedFile) { - if ($value->isValid() && $value->getSize() > UploadedFile::getMaxFilesize()) { - $filtered[$key] = new UploadedFile( - '', - $value->getClientOriginalName(), - $value->getClientMimeType(), - 0, - \UPLOAD_ERR_INI_SIZE, - true - ); - } else { - $filtered[$key] = new UploadedFile( - $value->getPathname(), - $value->getClientOriginalName(), - $value->getClientMimeType(), - $value->getClientSize(), - $value->getError(), - true - ); - } - } - } - - return $filtered; - } - - /** - * Converts the HttpKernel response to a BrowserKit response. - * - * @return DomResponse A DomResponse instance - */ - protected function filterResponse($response) - { - // this is needed to support StreamedResponse - ob_start(); - $response->sendContent(); - $content = ob_get_clean(); - - return new DomResponse($content, $response->getStatusCode(), $response->headers->all()); - } -} diff --git a/lib/symfony/http-kernel/Config/EnvParametersResource.php b/lib/symfony/http-kernel/Config/EnvParametersResource.php deleted file mode 100644 index 26869ced2f..0000000000 --- a/lib/symfony/http-kernel/Config/EnvParametersResource.php +++ /dev/null @@ -1,105 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\Config; - -use Symfony\Component\Config\Resource\SelfCheckingResourceInterface; - -/** - * EnvParametersResource represents resources stored in prefixed environment variables. - * - * @author Chris Wilkinson - * - * @deprecated since version 3.4, to be removed in 4.0 - */ -class EnvParametersResource implements SelfCheckingResourceInterface, \Serializable -{ - /** - * @var string - */ - private $prefix; - - /** - * @var string - */ - private $variables; - - /** - * @param string $prefix - */ - public function __construct($prefix) - { - $this->prefix = $prefix; - $this->variables = $this->findVariables(); - } - - /** - * {@inheritdoc} - */ - public function __toString() - { - return serialize($this->getResource()); - } - - /** - * @return array An array with two keys: 'prefix' for the prefix used and 'variables' containing all the variables watched by this resource - */ - public function getResource() - { - return ['prefix' => $this->prefix, 'variables' => $this->variables]; - } - - /** - * {@inheritdoc} - */ - public function isFresh($timestamp) - { - return $this->findVariables() === $this->variables; - } - - /** - * @internal - */ - public function serialize() - { - return serialize(['prefix' => $this->prefix, 'variables' => $this->variables]); - } - - /** - * @internal - */ - public function unserialize($serialized) - { - if (\PHP_VERSION_ID >= 70000) { - $unserialized = unserialize($serialized, ['allowed_classes' => false]); - } else { - $unserialized = unserialize($serialized); - } - - $this->prefix = $unserialized['prefix']; - $this->variables = $unserialized['variables']; - } - - private function findVariables() - { - $variables = []; - - foreach ($_SERVER as $key => $value) { - if (0 === strpos($key, $this->prefix)) { - $variables[$key] = $value; - } - } - - ksort($variables); - - return $variables; - } -} diff --git a/lib/symfony/http-kernel/Config/FileLocator.php b/lib/symfony/http-kernel/Config/FileLocator.php index fd5c8a32c1..6eca98635c 100644 --- a/lib/symfony/http-kernel/Config/FileLocator.php +++ b/lib/symfony/http-kernel/Config/FileLocator.php @@ -22,31 +22,23 @@ use Symfony\Component\HttpKernel\KernelInterface; class FileLocator extends BaseFileLocator { private $kernel; - private $path; - /** - * @param KernelInterface $kernel A KernelInterface instance - * @param string|null $path The path the global resource directory - * @param array $paths An array of paths where to look for resources - */ - public function __construct(KernelInterface $kernel, $path = null, array $paths = []) + public function __construct(KernelInterface $kernel) { $this->kernel = $kernel; - if (null !== $path) { - $this->path = $path; - $paths[] = $path; - } - parent::__construct($paths); + parent::__construct(); } /** * {@inheritdoc} */ - public function locate($file, $currentPath = null, $first = true) + public function locate(string $file, string $currentPath = null, bool $first = true) { if (isset($file[0]) && '@' === $file[0]) { - return $this->kernel->locateResource($file, $this->path, $first); + $resource = $this->kernel->locateResource($file); + + return $first ? $resource : [$resource]; } return parent::locate($file, $currentPath, $first); diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver.php index 9d5f96ed2c..a54140b7e5 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolver.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver.php @@ -28,22 +28,21 @@ use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInter final class ArgumentResolver implements ArgumentResolverInterface { private $argumentMetadataFactory; - - /** - * @var iterable|ArgumentValueResolverInterface[] - */ private $argumentValueResolvers; - public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, $argumentValueResolvers = []) + /** + * @param iterable $argumentValueResolvers + */ + public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, iterable $argumentValueResolvers = []) { - $this->argumentMetadataFactory = $argumentMetadataFactory ?: new ArgumentMetadataFactory(); + $this->argumentMetadataFactory = $argumentMetadataFactory ?? new ArgumentMetadataFactory(); $this->argumentValueResolvers = $argumentValueResolvers ?: self::getDefaultArgumentValueResolvers(); } /** * {@inheritdoc} */ - public function getArguments(Request $request, $controller) + public function getArguments(Request $request, callable $controller): array { $arguments = []; @@ -55,12 +54,14 @@ final class ArgumentResolver implements ArgumentResolverInterface $resolved = $resolver->resolve($request, $metadata); - if (!$resolved instanceof \Generator) { - throw new \InvalidArgumentException(sprintf('"%s::resolve()" must yield at least one value.', \get_class($resolver))); + $atLeastOne = false; + foreach ($resolved as $append) { + $atLeastOne = true; + $arguments[] = $append; } - foreach ($resolved as $append) { - $arguments[] = $append; + if (!$atLeastOne) { + throw new \InvalidArgumentException(sprintf('"%s::resolve()" must yield at least one value.', get_debug_type($resolver))); } // continue to the next controller argument @@ -81,7 +82,10 @@ final class ArgumentResolver implements ArgumentResolverInterface return $arguments; } - public static function getDefaultArgumentValueResolvers() + /** + * @return iterable + */ + public static function getDefaultArgumentValueResolvers(): iterable { return [ new RequestAttributeValueResolver(), diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php index e58fd3ab2b..32a0e071d6 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/DefaultValueResolver.php @@ -25,7 +25,7 @@ final class DefaultValueResolver implements ArgumentValueResolverInterface /** * {@inheritdoc} */ - public function supports(Request $request, ArgumentMetadata $argument) + public function supports(Request $request, ArgumentMetadata $argument): bool { return $argument->hasDefaultValue() || (null !== $argument->getType() && $argument->isNullable() && !$argument->isVariadic()); } @@ -33,7 +33,7 @@ final class DefaultValueResolver implements ArgumentValueResolverInterface /** * {@inheritdoc} */ - public function resolve(Request $request, ArgumentMetadata $argument) + public function resolve(Request $request, ArgumentMetadata $argument): iterable { yield $argument->hasDefaultValue() ? $argument->getDefaultValue() : null; } diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php new file mode 100644 index 0000000000..d4971cc1a5 --- /dev/null +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.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 Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; + +/** + * Provides an intuitive error message when controller fails because it is not registered as a service. + * + * @author Simeon Kolev + */ +final class NotTaggedControllerValueResolver implements ArgumentValueResolverInterface +{ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument): bool + { + $controller = $request->attributes->get('_controller'); + + if (\is_array($controller) && \is_callable($controller, true) && \is_string($controller[0])) { + $controller = $controller[0].'::'.$controller[1]; + } elseif (!\is_string($controller) || '' === $controller) { + return false; + } + + if ('\\' === $controller[0]) { + $controller = ltrim($controller, '\\'); + } + + if (!$this->container->has($controller) && false !== $i = strrpos($controller, ':')) { + $controller = substr($controller, 0, $i).strtolower(substr($controller, $i)); + } + + return false === $this->container->has($controller); + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument): iterable + { + if (\is_array($controller = $request->attributes->get('_controller'))) { + $controller = $controller[0].'::'.$controller[1]; + } + + if ('\\' === $controller[0]) { + $controller = ltrim($controller, '\\'); + } + + if (!$this->container->has($controller)) { + $i = strrpos($controller, ':'); + $controller = substr($controller, 0, $i).strtolower(substr($controller, $i)); + } + + $what = sprintf('argument $%s of "%s()"', $argument->getName(), $controller); + $message = sprintf('Could not resolve %s, maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"?', $what); + + throw new RuntimeException($message); + } +} diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php index 05be372d84..c62d327b65 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestAttributeValueResolver.php @@ -25,7 +25,7 @@ final class RequestAttributeValueResolver implements ArgumentValueResolverInterf /** * {@inheritdoc} */ - public function supports(Request $request, ArgumentMetadata $argument) + public function supports(Request $request, ArgumentMetadata $argument): bool { return !$argument->isVariadic() && $request->attributes->has($argument->getName()); } @@ -33,7 +33,7 @@ final class RequestAttributeValueResolver implements ArgumentValueResolverInterf /** * {@inheritdoc} */ - public function resolve(Request $request, ArgumentMetadata $argument) + public function resolve(Request $request, ArgumentMetadata $argument): iterable { yield $request->attributes->get($argument->getName()); } diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php index 2a5060a612..75cbd97edb 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/RequestValueResolver.php @@ -25,7 +25,7 @@ final class RequestValueResolver implements ArgumentValueResolverInterface /** * {@inheritdoc} */ - public function supports(Request $request, ArgumentMetadata $argument) + public function supports(Request $request, ArgumentMetadata $argument): bool { return Request::class === $argument->getType() || is_subclass_of($argument->getType(), Request::class); } @@ -33,7 +33,7 @@ final class RequestValueResolver implements ArgumentValueResolverInterface /** * {@inheritdoc} */ - public function resolve(Request $request, ArgumentMetadata $argument) + public function resolve(Request $request, ArgumentMetadata $argument): iterable { yield $request; } diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php index 3294ec862e..4ffb8c99eb 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/ServiceValueResolver.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Psr\Container\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; @@ -33,7 +34,7 @@ final class ServiceValueResolver implements ArgumentValueResolverInterface /** * {@inheritdoc} */ - public function supports(Request $request, ArgumentMetadata $argument) + public function supports(Request $request, ArgumentMetadata $argument): bool { $controller = $request->attributes->get('_controller'); @@ -57,7 +58,7 @@ final class ServiceValueResolver implements ArgumentValueResolverInterface /** * {@inheritdoc} */ - public function resolve(Request $request, ArgumentMetadata $argument) + public function resolve(Request $request, ArgumentMetadata $argument): iterable { if (\is_array($controller = $request->attributes->get('_controller'))) { $controller = $controller[0].'::'.$controller[1]; @@ -72,6 +73,21 @@ final class ServiceValueResolver implements ArgumentValueResolverInterface $controller = substr($controller, 0, $i).strtolower(substr($controller, $i)); } - yield $this->container->get($controller)->get($argument->getName()); + try { + yield $this->container->get($controller)->get($argument->getName()); + } catch (RuntimeException $e) { + $what = sprintf('argument $%s of "%s()"', $argument->getName(), $controller); + $message = preg_replace('/service "\.service_locator\.[^"]++"/', $what, $e->getMessage()); + + if ($e->getMessage() === $message) { + $message = sprintf('Cannot resolve %s: %s', $what, $message); + } + + $r = new \ReflectionProperty($e, 'message'); + $r->setAccessible(true); + $r->setValue($e, $message); + + throw $e; + } } } diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php index 9e656d281b..a1e6b43159 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/SessionValueResolver.php @@ -26,8 +26,12 @@ final class SessionValueResolver implements ArgumentValueResolverInterface /** * {@inheritdoc} */ - public function supports(Request $request, ArgumentMetadata $argument) + public function supports(Request $request, ArgumentMetadata $argument): bool { + if (!$request->hasSession()) { + return false; + } + $type = $argument->getType(); if (SessionInterface::class !== $type && !is_subclass_of($type, SessionInterface::class)) { return false; @@ -39,7 +43,7 @@ final class SessionValueResolver implements ArgumentValueResolverInterface /** * {@inheritdoc} */ - public function resolve(Request $request, ArgumentMetadata $argument) + public function resolve(Request $request, ArgumentMetadata $argument): iterable { yield $request->getSession(); } diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php new file mode 100644 index 0000000000..bde3c90c1a --- /dev/null +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/TraceableValueResolver.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * Provides timing information via the stopwatch. + * + * @author Iltar van der Berg + */ +final class TraceableValueResolver implements ArgumentValueResolverInterface +{ + private $inner; + private $stopwatch; + + public function __construct(ArgumentValueResolverInterface $inner, Stopwatch $stopwatch) + { + $this->inner = $inner; + $this->stopwatch = $stopwatch; + } + + /** + * {@inheritdoc} + */ + public function supports(Request $request, ArgumentMetadata $argument): bool + { + $method = \get_class($this->inner).'::'.__FUNCTION__; + $this->stopwatch->start($method, 'controller.argument_value_resolver'); + + $return = $this->inner->supports($request, $argument); + + $this->stopwatch->stop($method); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function resolve(Request $request, ArgumentMetadata $argument): iterable + { + $method = \get_class($this->inner).'::'.__FUNCTION__; + $this->stopwatch->start($method, 'controller.argument_value_resolver'); + + yield from $this->inner->resolve($request, $argument); + + $this->stopwatch->stop($method); + } +} diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php index 7ee2d7af5c..a8f7e0f440 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/VariadicValueResolver.php @@ -25,7 +25,7 @@ final class VariadicValueResolver implements ArgumentValueResolverInterface /** * {@inheritdoc} */ - public function supports(Request $request, ArgumentMetadata $argument) + public function supports(Request $request, ArgumentMetadata $argument): bool { return $argument->isVariadic() && $request->attributes->has($argument->getName()); } @@ -33,16 +33,14 @@ final class VariadicValueResolver implements ArgumentValueResolverInterface /** * {@inheritdoc} */ - public function resolve(Request $request, ArgumentMetadata $argument) + public function resolve(Request $request, ArgumentMetadata $argument): iterable { $values = $request->attributes->get($argument->getName()); if (!\is_array($values)) { - throw new \InvalidArgumentException(sprintf('The action argument "...$%1$s" is required to be an array, the request attribute "%1$s" contains a type of "%2$s" instead.', $argument->getName(), \gettype($values))); + throw new \InvalidArgumentException(sprintf('The action argument "...$%1$s" is required to be an array, the request attribute "%1$s" contains a type of "%2$s" instead.', $argument->getName(), get_debug_type($values))); } - foreach ($values as $value) { - yield $value; - } + yield from $values; } } diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolverInterface.php b/lib/symfony/http-kernel/Controller/ArgumentResolverInterface.php index ba97775a90..30e4783e89 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolverInterface.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolverInterface.php @@ -24,11 +24,9 @@ interface ArgumentResolverInterface /** * Returns the arguments to pass to the controller. * - * @param callable $controller - * - * @return array An array of arguments to pass to the controller + * @return array * * @throws \RuntimeException When no value could be provided for a required argument */ - public function getArguments(Request $request, $controller); + public function getArguments(Request $request, callable $controller); } diff --git a/lib/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php b/lib/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php index 6b14ed5be3..1317707b1d 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php +++ b/lib/symfony/http-kernel/Controller/ArgumentValueResolverInterface.php @@ -31,7 +31,7 @@ interface ArgumentValueResolverInterface /** * Returns the possible value(s). * - * @return \Generator + * @return iterable */ public function resolve(Request $request, ArgumentMetadata $argument); } diff --git a/lib/symfony/http-kernel/Controller/ContainerControllerResolver.php b/lib/symfony/http-kernel/Controller/ContainerControllerResolver.php index b08877da93..3b9468465c 100644 --- a/lib/symfony/http-kernel/Controller/ContainerControllerResolver.php +++ b/lib/symfony/http-kernel/Controller/ContainerControllerResolver.php @@ -14,10 +14,9 @@ namespace Symfony\Component\HttpKernel\Controller; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\Container; -use Symfony\Component\HttpFoundation\Request; /** - * A controller resolver searching for a controller in a psr-11 container when using the "service:method" notation. + * A controller resolver searching for a controller in a psr-11 container when using the "service::method" notation. * * @author Fabien Potencier * @author Maxime Steinhausser @@ -33,89 +32,45 @@ class ContainerControllerResolver extends ControllerResolver parent::__construct($logger); } - /** - * {@inheritdoc} - */ - public function getController(Request $request) + protected function createController(string $controller) { - $controller = parent::getController($request); - - if (\is_array($controller) && isset($controller[0]) && \is_string($controller[0]) && $this->container->has($controller[0])) { - $controller[0] = $this->instantiateController($controller[0]); + if (1 === substr_count($controller, ':')) { + $controller = str_replace(':', '::', $controller); + trigger_deprecation('symfony/http-kernel', '5.1', 'Referencing controllers with a single colon is deprecated. Use "%s" instead.', $controller); } - return $controller; - } - - /** - * Returns a callable for the given controller. - * - * @param string $controller A Controller string - * - * @return mixed A PHP callable - * - * @throws \LogicException When the name could not be parsed - * @throws \InvalidArgumentException When the controller class does not exist - */ - protected function createController($controller) - { - if (false !== strpos($controller, '::')) { - return parent::createController($controller); - } - - $method = null; - if (1 == substr_count($controller, ':')) { - // controller in the "service:method" notation - list($controller, $method) = explode(':', $controller, 2); - } - - if (!$this->container->has($controller)) { - $this->throwExceptionIfControllerWasRemoved($controller); - - throw new \LogicException(sprintf('Controller not found: service "%s" does not exist.', $controller)); - } - - $service = $this->container->get($controller); - if (null !== $method) { - return [$service, $method]; - } - - if (!method_exists($service, '__invoke')) { - throw new \LogicException(sprintf('Controller "%s" cannot be called without a method name. Did you forget an "__invoke" method?', $controller)); - } - - return $service; + return parent::createController($controller); } /** * {@inheritdoc} */ - protected function instantiateController($class) + protected function instantiateController(string $class) { + $class = ltrim($class, '\\'); + if ($this->container->has($class)) { return $this->container->get($class); } try { return parent::instantiateController($class); - } catch (\ArgumentCountError $e) { - } catch (\ErrorException $e) { - } catch (\TypeError $e) { + } catch (\Error $e) { } $this->throwExceptionIfControllerWasRemoved($class, $e); - throw $e; + if ($e instanceof \ArgumentCountError) { + throw new \InvalidArgumentException(sprintf('Controller "%s" has required constructor arguments and does not exist in the container. Did you forget to define the controller as a service?', $class), 0, $e); + } + + throw new \InvalidArgumentException(sprintf('Controller "%s" does neither exist as service nor as class.', $class), 0, $e); } - /** - * @param string $controller - * @param \Exception|\Throwable|null $previous - */ - private function throwExceptionIfControllerWasRemoved($controller, $previous = null) + private function throwExceptionIfControllerWasRemoved(string $controller, \Throwable $previous) { if ($this->container instanceof Container && isset($this->container->getRemovedIds()[$controller])) { - throw new \LogicException(sprintf('Controller "%s" cannot be fetched from the container because it is private. Did you forget to tag the service with "controller.service_arguments"?', $controller), 0, $previous); + throw new \InvalidArgumentException(sprintf('Controller "%s" cannot be fetched from the container because it is private. Did you forget to tag the service with "controller.service_arguments"?', $controller), 0, $previous); } } } diff --git a/lib/symfony/http-kernel/Controller/ControllerReference.php b/lib/symfony/http-kernel/Controller/ControllerReference.php index e1e9c813ea..b4fdadd21e 100644 --- a/lib/symfony/http-kernel/Controller/ControllerReference.php +++ b/lib/symfony/http-kernel/Controller/ControllerReference.php @@ -35,7 +35,7 @@ class ControllerReference * @param array $attributes An array of parameters to add to the Request attributes * @param array $query An array of parameters to add to the Request query string */ - public function __construct($controller, array $attributes = [], array $query = []) + public function __construct(string $controller, array $attributes = [], array $query = []) { $this->controller = $controller; $this->attributes = $attributes; diff --git a/lib/symfony/http-kernel/Controller/ControllerResolver.php b/lib/symfony/http-kernel/Controller/ControllerResolver.php index 6248be1814..8abbadd48b 100644 --- a/lib/symfony/http-kernel/Controller/ControllerResolver.php +++ b/lib/symfony/http-kernel/Controller/ControllerResolver.php @@ -16,44 +16,22 @@ use Symfony\Component\HttpFoundation\Request; /** * This implementation uses the '_controller' request attribute to determine - * the controller to execute and uses the request attributes to determine - * the controller method arguments. + * the controller to execute. * * @author Fabien Potencier + * @author Tobias Schultze */ -class ControllerResolver implements ArgumentResolverInterface, ControllerResolverInterface +class ControllerResolver implements ControllerResolverInterface { private $logger; - /** - * If the ...$arg functionality is available. - * - * Requires at least PHP 5.6.0 or HHVM 3.9.1 - * - * @var bool - */ - private $supportsVariadic; - - /** - * If scalar types exists. - * - * @var bool - */ - private $supportsScalarTypes; - public function __construct(LoggerInterface $logger = null) { $this->logger = $logger; - - $this->supportsVariadic = method_exists('ReflectionParameter', 'isVariadic'); - $this->supportsScalarTypes = method_exists('ReflectionParameter', 'getType'); } /** * {@inheritdoc} - * - * This method looks for a '_controller' request attribute that represents - * the controller name (a string like ClassName::MethodName). */ public function getController(Request $request) { @@ -66,23 +44,42 @@ class ControllerResolver implements ArgumentResolverInterface, ControllerResolve } if (\is_array($controller)) { + if (isset($controller[0]) && \is_string($controller[0]) && isset($controller[1])) { + try { + $controller[0] = $this->instantiateController($controller[0]); + } catch (\Error|\LogicException $e) { + try { + // We cannot just check is_callable but have to use reflection because a non-static method + // can still be called statically in PHP but we don't want that. This is deprecated in PHP 7, so we + // could simplify this with PHP 8. + if ((new \ReflectionMethod($controller[0], $controller[1]))->isStatic()) { + return $controller; + } + } catch (\ReflectionException $reflectionException) { + throw $e; + } + + throw $e; + } + } + + if (!\is_callable($controller)) { + throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$this->getControllerError($controller)); + } + return $controller; } if (\is_object($controller)) { - if (method_exists($controller, '__invoke')) { - return $controller; + if (!\is_callable($controller)) { + throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$this->getControllerError($controller)); } - throw new \InvalidArgumentException(sprintf('Controller "%s" for URI "%s" is not callable.', \get_class($controller), $request->getPathInfo())); + return $controller; } - if (\is_string($controller) && false === strpos($controller, ':')) { - if (method_exists($controller, '__invoke')) { - return $this->instantiateController($controller); - } elseif (\function_exists($controller)) { - return $controller; - } + if (\function_exists($controller)) { + return $controller; } try { @@ -91,96 +88,48 @@ class ControllerResolver implements ArgumentResolverInterface, ControllerResolve throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$e->getMessage(), 0, $e); } + if (!\is_callable($callable)) { + throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable: ', $request->getPathInfo()).$this->getControllerError($callable)); + } + return $callable; } - /** - * {@inheritdoc} - * - * @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. Implement the ArgumentResolverInterface and inject it in the HttpKernel instead. - */ - public function getArguments(Request $request, $controller) - { - @trigger_error(sprintf('The "%s()" method is deprecated as of 3.1 and will be removed in 4.0. Implement the %s and inject it in the HttpKernel instead.', __METHOD__, ArgumentResolverInterface::class), \E_USER_DEPRECATED); - - if (\is_array($controller)) { - $r = new \ReflectionMethod($controller[0], $controller[1]); - } elseif (\is_object($controller) && !$controller instanceof \Closure) { - $r = new \ReflectionObject($controller); - $r = $r->getMethod('__invoke'); - } else { - $r = new \ReflectionFunction($controller); - } - - return $this->doGetArguments($request, $controller, $r->getParameters()); - } - - /** - * @param callable $controller - * @param \ReflectionParameter[] $parameters - * - * @return array The arguments to use when calling the action - * - * @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. Implement the ArgumentResolverInterface and inject it in the HttpKernel instead. - */ - protected function doGetArguments(Request $request, $controller, array $parameters) - { - @trigger_error(sprintf('The "%s()" method is deprecated as of 3.1 and will be removed in 4.0. Implement the %s and inject it in the HttpKernel instead.', __METHOD__, ArgumentResolverInterface::class), \E_USER_DEPRECATED); - - $attributes = $request->attributes->all(); - $arguments = []; - foreach ($parameters as $param) { - if (\array_key_exists($param->name, $attributes)) { - if ($this->supportsVariadic && $param->isVariadic() && \is_array($attributes[$param->name])) { - $arguments = array_merge($arguments, array_values($attributes[$param->name])); - } else { - $arguments[] = $attributes[$param->name]; - } - } elseif ($this->typeMatchesRequestClass($param, $request)) { - $arguments[] = $request; - } elseif ($param->isDefaultValueAvailable()) { - $arguments[] = $param->getDefaultValue(); - } elseif ($this->supportsScalarTypes && $param->hasType() && $param->allowsNull()) { - $arguments[] = null; - } else { - if (\is_array($controller)) { - $repr = sprintf('%s::%s()', \get_class($controller[0]), $controller[1]); - } elseif (\is_object($controller)) { - $repr = \get_class($controller); - } else { - $repr = $controller; - } - - throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument (because there is no default value or because there is a non optional argument after this one).', $repr, $param->name)); - } - } - - return $arguments; - } - /** * Returns a callable for the given controller. * - * @param string $controller A Controller string - * - * @return callable A PHP callable + * @return callable * * @throws \InvalidArgumentException When the controller cannot be created */ - protected function createController($controller) + protected function createController(string $controller) { - if (false === strpos($controller, '::')) { - throw new \InvalidArgumentException(sprintf('Unable to find controller "%s".', $controller)); + if (!str_contains($controller, '::')) { + $controller = $this->instantiateController($controller); + + if (!\is_callable($controller)) { + throw new \InvalidArgumentException($this->getControllerError($controller)); + } + + return $controller; } - list($class, $method) = explode('::', $controller, 2); + [$class, $method] = explode('::', $controller, 2); - if (!class_exists($class)) { - throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); + try { + $controller = [$this->instantiateController($class), $method]; + } catch (\Error|\LogicException $e) { + try { + if ((new \ReflectionMethod($class, $method))->isStatic()) { + return $class.'::'.$method; + } + } catch (\ReflectionException $reflectionException) { + throw $e; + } + + throw $e; } - $controller = [$this->instantiateController($class), $method]; - if (!\is_callable($controller)) { throw new \InvalidArgumentException($this->getControllerError($controller)); } @@ -191,59 +140,58 @@ class ControllerResolver implements ArgumentResolverInterface, ControllerResolve /** * Returns an instantiated controller. * - * @param string $class A class name - * * @return object */ - protected function instantiateController($class) + protected function instantiateController(string $class) { return new $class(); } - private function getControllerError($callable) + private function getControllerError($callable): string { if (\is_string($callable)) { - if (false !== strpos($callable, '::')) { - $callable = explode('::', $callable); - } - - if (class_exists($callable) && !method_exists($callable, '__invoke')) { - return sprintf('Class "%s" does not have a method "__invoke".', $callable); - } - - if (!\function_exists($callable)) { + if (str_contains($callable, '::')) { + $callable = explode('::', $callable, 2); + } else { return sprintf('Function "%s" does not exist.', $callable); } } + if (\is_object($callable)) { + $availableMethods = $this->getClassMethodsWithoutMagicMethods($callable); + $alternativeMsg = $availableMethods ? sprintf(' or use one of the available methods: "%s"', implode('", "', $availableMethods)) : ''; + + return sprintf('Controller class "%s" cannot be called without a method name. You need to implement "__invoke"%s.', get_debug_type($callable), $alternativeMsg); + } + if (!\is_array($callable)) { - return sprintf('Invalid type for controller given, expected string or array, got "%s".', \gettype($callable)); + return sprintf('Invalid type for controller given, expected string, array or object, got "%s".', get_debug_type($callable)); } - if (2 !== \count($callable)) { - return 'Invalid format for controller, expected [controller, method] or controller::method.'; + if (!isset($callable[0]) || !isset($callable[1]) || 2 !== \count($callable)) { + return 'Invalid array callable, expected [controller, method].'; } - list($controller, $method) = $callable; + [$controller, $method] = $callable; if (\is_string($controller) && !class_exists($controller)) { return sprintf('Class "%s" does not exist.', $controller); } - $className = \is_object($controller) ? \get_class($controller) : $controller; + $className = \is_object($controller) ? get_debug_type($controller) : $controller; if (method_exists($controller, $method)) { return sprintf('Method "%s" on class "%s" should be public and non-abstract.', $method, $className); } - $collection = get_class_methods($controller); + $collection = $this->getClassMethodsWithoutMagicMethods($controller); $alternatives = []; foreach ($collection as $item) { $lev = levenshtein($method, $item); - if ($lev <= \strlen($method) / 3 || false !== strpos($item, $method)) { + if ($lev <= \strlen($method) / 3 || str_contains($item, $method)) { $alternatives[] = $item; } } @@ -261,21 +209,12 @@ class ControllerResolver implements ArgumentResolverInterface, ControllerResolve return $message; } - /** - * @return bool - */ - private function typeMatchesRequestClass(\ReflectionParameter $param, Request $request) + private function getClassMethodsWithoutMagicMethods($classOrObject): array { - if (!method_exists($param, 'getType')) { - return $param->getClass() && $param->getClass()->isInstance($request); - } + $methods = get_class_methods($classOrObject); - if (!($type = $param->getType()) || $type->isBuiltin()) { - return false; - } - - $class = new \ReflectionClass($type instanceof \ReflectionNamedType ? $type->getName() : (string) $type); - - return $class && $class->isInstance($request); + return array_filter($methods, function (string $method) { + return 0 !== strncmp($method, '__', 2); + }); } } diff --git a/lib/symfony/http-kernel/Controller/ControllerResolverInterface.php b/lib/symfony/http-kernel/Controller/ControllerResolverInterface.php index 95c59036ff..8b70a88f63 100644 --- a/lib/symfony/http-kernel/Controller/ControllerResolverInterface.php +++ b/lib/symfony/http-kernel/Controller/ControllerResolverInterface.php @@ -17,8 +17,6 @@ use Symfony\Component\HttpFoundation\Request; * A ControllerResolverInterface implementation knows how to determine the * controller to execute based on a Request object. * - * It can also determine the arguments to pass to the Controller. - * * A Controller can be any valid PHP callable. * * @author Fabien Potencier @@ -37,21 +35,7 @@ interface ControllerResolverInterface * @return callable|false A PHP callable representing the Controller, * or false if this resolver is not able to determine the controller * - * @throws \LogicException If the controller can't be found + * @throws \LogicException If a controller was found based on the request but it is not callable */ public function getController(Request $request); - - /** - * Returns the arguments to pass to the controller. - * - * @param Request $request A Request instance - * @param callable $controller A PHP callable - * - * @return array An array of arguments to pass to the controller - * - * @throws \RuntimeException When value for argument given is not provided - * - * @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. Please use the {@see ArgumentResolverInterface} instead. - */ - public function getArguments(Request $request, $controller); } diff --git a/lib/symfony/http-kernel/Controller/ErrorController.php b/lib/symfony/http-kernel/Controller/ErrorController.php new file mode 100644 index 0000000000..b6c440103f --- /dev/null +++ b/lib/symfony/http-kernel/Controller/ErrorController.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Renders error or exception pages from a given FlattenException. + * + * @author Yonel Ceruto + * @author Matthias Pigulla + */ +class ErrorController +{ + private $kernel; + private $controller; + private $errorRenderer; + + public function __construct(HttpKernelInterface $kernel, $controller, ErrorRendererInterface $errorRenderer) + { + $this->kernel = $kernel; + $this->controller = $controller; + $this->errorRenderer = $errorRenderer; + } + + public function __invoke(\Throwable $exception): Response + { + $exception = $this->errorRenderer->render($exception); + + return new Response($exception->getAsString(), $exception->getStatusCode(), $exception->getHeaders()); + } + + public function preview(Request $request, int $code): Response + { + /* + * This Request mimics the parameters set by + * \Symfony\Component\HttpKernel\EventListener\ErrorListener::duplicateRequest, with + * the additional "showException" flag. + */ + $subRequest = $request->duplicate(null, null, [ + '_controller' => $this->controller, + 'exception' => new HttpException($code, 'This is a sample exception.'), + 'logger' => null, + 'showException' => false, + ]); + + return $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST); + } +} diff --git a/lib/symfony/http-kernel/Controller/TraceableArgumentResolver.php b/lib/symfony/http-kernel/Controller/TraceableArgumentResolver.php index 39848127b7..e22cf082c4 100644 --- a/lib/symfony/http-kernel/Controller/TraceableArgumentResolver.php +++ b/lib/symfony/http-kernel/Controller/TraceableArgumentResolver.php @@ -31,7 +31,7 @@ class TraceableArgumentResolver implements ArgumentResolverInterface /** * {@inheritdoc} */ - public function getArguments(Request $request, $controller) + public function getArguments(Request $request, callable $controller) { $e = $this->stopwatch->start('controller.get_arguments'); diff --git a/lib/symfony/http-kernel/Controller/TraceableControllerResolver.php b/lib/symfony/http-kernel/Controller/TraceableControllerResolver.php index 09701cfe04..bf6b6aa1f2 100644 --- a/lib/symfony/http-kernel/Controller/TraceableControllerResolver.php +++ b/lib/symfony/http-kernel/Controller/TraceableControllerResolver.php @@ -17,26 +17,15 @@ use Symfony\Component\Stopwatch\Stopwatch; /** * @author Fabien Potencier */ -class TraceableControllerResolver implements ControllerResolverInterface, ArgumentResolverInterface +class TraceableControllerResolver implements ControllerResolverInterface { private $resolver; private $stopwatch; - private $argumentResolver; - public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch, ArgumentResolverInterface $argumentResolver = null) + public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch) { $this->resolver = $resolver; $this->stopwatch = $stopwatch; - $this->argumentResolver = $argumentResolver; - - // BC - if (null === $this->argumentResolver) { - $this->argumentResolver = $resolver; - } - - if (!$this->argumentResolver instanceof TraceableArgumentResolver) { - $this->argumentResolver = new TraceableArgumentResolver($this->argumentResolver, $this->stopwatch); - } } /** @@ -52,18 +41,4 @@ class TraceableControllerResolver implements ControllerResolverInterface, Argume return $ret; } - - /** - * {@inheritdoc} - * - * @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. - */ - public function getArguments(Request $request, $controller) - { - @trigger_error(sprintf('The "%s()" method is deprecated as of 3.1 and will be removed in 4.0. Please use the %s instead.', __METHOD__, TraceableArgumentResolver::class), \E_USER_DEPRECATED); - - $ret = $this->argumentResolver->getArguments($request, $controller); - - return $ret; - } } diff --git a/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php b/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php index 6df388d83a..1a9ebc0c3a 100644 --- a/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php +++ b/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadata.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpKernel\ControllerMetadata; +use Symfony\Component\HttpKernel\Attribute\ArgumentInterface; + /** * Responsible for storing metadata of an argument. * @@ -18,22 +20,20 @@ namespace Symfony\Component\HttpKernel\ControllerMetadata; */ class ArgumentMetadata { + public const IS_INSTANCEOF = 2; + private $name; private $type; private $isVariadic; private $hasDefaultValue; private $defaultValue; private $isNullable; + private $attributes; /** - * @param string $name - * @param string $type - * @param bool $isVariadic - * @param bool $hasDefaultValue - * @param mixed $defaultValue - * @param bool $isNullable + * @param object[] $attributes */ - public function __construct($name, $type, $isVariadic, $hasDefaultValue, $defaultValue, $isNullable = false) + public function __construct(string $name, ?string $type, bool $isVariadic, bool $hasDefaultValue, $defaultValue, bool $isNullable = false, $attributes = []) { $this->name = $name; $this->type = $type; @@ -41,6 +41,13 @@ class ArgumentMetadata $this->hasDefaultValue = $hasDefaultValue; $this->defaultValue = $defaultValue; $this->isNullable = $isNullable || null === $type || ($hasDefaultValue && null === $defaultValue); + + if (null === $attributes || $attributes instanceof ArgumentInterface) { + trigger_deprecation('symfony/http-kernel', '5.3', 'The "%s" constructor expects an array of PHP attributes as last argument, %s given.', __CLASS__, get_debug_type($attributes)); + $attributes = $attributes ? [$attributes] : []; + } + + $this->attributes = $attributes; } /** @@ -112,4 +119,45 @@ class ArgumentMetadata return $this->defaultValue; } + + /** + * Returns the attribute (if any) that was set on the argument. + */ + public function getAttribute(): ?ArgumentInterface + { + trigger_deprecation('symfony/http-kernel', '5.3', 'Method "%s()" is deprecated, use "getAttributes()" instead.', __METHOD__); + + if (!$this->attributes) { + return null; + } + + return $this->attributes[0] instanceof ArgumentInterface ? $this->attributes[0] : null; + } + + /** + * @return object[] + */ + public function getAttributes(string $name = null, int $flags = 0): array + { + if (!$name) { + return $this->attributes; + } + + $attributes = []; + if ($flags & self::IS_INSTANCEOF) { + foreach ($this->attributes as $attribute) { + if ($attribute instanceof $name) { + $attributes[] = $attribute; + } + } + } else { + foreach ($this->attributes as $attribute) { + if (\get_class($attribute) === $name) { + $attributes[] = $attribute; + } + } + } + + return $attributes; + } } diff --git a/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php b/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php index 14e7ca3d11..e3835c057a 100644 --- a/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php +++ b/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php @@ -18,118 +18,61 @@ namespace Symfony\Component\HttpKernel\ControllerMetadata; */ final class ArgumentMetadataFactory implements ArgumentMetadataFactoryInterface { - /** - * If the ...$arg functionality is available. - * - * Requires at least PHP 5.6.0 or HHVM 3.9.1 - * - * @var bool - */ - private $supportsVariadic; - - /** - * If the reflection supports the getType() method to resolve types. - * - * Requires at least PHP 7.0.0 or HHVM 3.11.0 - * - * @var bool - */ - private $supportsParameterType; - - public function __construct() - { - $this->supportsVariadic = method_exists('ReflectionParameter', 'isVariadic'); - $this->supportsParameterType = method_exists('ReflectionParameter', 'getType'); - } - /** * {@inheritdoc} */ - public function createArgumentMetadata($controller) + public function createArgumentMetadata($controller): array { $arguments = []; if (\is_array($controller)) { $reflection = new \ReflectionMethod($controller[0], $controller[1]); + $class = $reflection->class; } elseif (\is_object($controller) && !$controller instanceof \Closure) { - $reflection = (new \ReflectionObject($controller))->getMethod('__invoke'); + $reflection = new \ReflectionMethod($controller, '__invoke'); + $class = $reflection->class; } else { $reflection = new \ReflectionFunction($controller); + if ($class = str_contains($reflection->name, '{closure}') ? null : $reflection->getClosureScopeClass()) { + $class = $class->name; + } } foreach ($reflection->getParameters() as $param) { - $arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param, $reflection), $this->isVariadic($param), $this->hasDefaultValue($param), $this->getDefaultValue($param), $param->allowsNull()); + $attributes = []; + if (\PHP_VERSION_ID >= 80000) { + foreach ($param->getAttributes() as $reflectionAttribute) { + if (class_exists($reflectionAttribute->getName())) { + $attributes[] = $reflectionAttribute->newInstance(); + } + } + } + + $arguments[] = new ArgumentMetadata($param->getName(), $this->getType($param, $class), $param->isVariadic(), $param->isDefaultValueAvailable(), $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, $param->allowsNull(), $attributes); } return $arguments; } - /** - * Returns whether an argument is variadic. - * - * @return bool - */ - private function isVariadic(\ReflectionParameter $parameter) - { - return $this->supportsVariadic && $parameter->isVariadic(); - } - - /** - * Determines whether an argument has a default value. - * - * @return bool - */ - private function hasDefaultValue(\ReflectionParameter $parameter) - { - return $parameter->isDefaultValueAvailable(); - } - - /** - * Returns a default value if available. - * - * @return mixed|null - */ - private function getDefaultValue(\ReflectionParameter $parameter) - { - return $this->hasDefaultValue($parameter) ? $parameter->getDefaultValue() : null; - } - /** * Returns an associated type to the given parameter if available. - * - * @return string|null */ - private function getType(\ReflectionParameter $parameter, \ReflectionFunctionAbstract $function) + private function getType(\ReflectionParameter $parameter, ?string $class): ?string { - if ($this->supportsParameterType) { - if (!$type = $parameter->getType()) { - return null; - } - $name = $type instanceof \ReflectionNamedType ? $type->getName() : (string) $type; - if ('array' === $name && !$type->isBuiltin()) { - // Special case for HHVM with variadics - return null; - } - } elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $parameter, $name)) { - $name = $name[1]; - } else { + if (!$type = $parameter->getType()) { return null; } - $lcName = strtolower($name); + $name = $type instanceof \ReflectionNamedType ? $type->getName() : (string) $type; - if ('self' !== $lcName && 'parent' !== $lcName) { - return $name; - } - if (!$function instanceof \ReflectionMethod) { - return null; - } - if ('self' === $lcName) { - return $function->getDeclaringClass()->name; - } - if ($parent = $function->getDeclaringClass()->getParentClass()) { - return $parent->name; + if (null !== $class) { + switch (strtolower($name)) { + case 'self': + return $class; + case 'parent': + return get_parent_class($class) ?: null; + } } - return null; + return $name; } } diff --git a/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php b/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php index 6ea179d783..a34befc22d 100644 --- a/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php +++ b/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactoryInterface.php @@ -19,7 +19,7 @@ namespace Symfony\Component\HttpKernel\ControllerMetadata; interface ArgumentMetadataFactoryInterface { /** - * @param mixed $controller The controller to resolve the arguments for + * @param string|object|array $controller The controller to resolve the arguments for * * @return ArgumentMetadata[] */ diff --git a/lib/symfony/http-kernel/DataCollector/AjaxDataCollector.php b/lib/symfony/http-kernel/DataCollector/AjaxDataCollector.php index 370a874fe5..fda6a4eaaa 100644 --- a/lib/symfony/http-kernel/DataCollector/AjaxDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/AjaxDataCollector.php @@ -15,13 +15,13 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; /** - * AjaxDataCollector. - * * @author Bart van den Burg + * + * @final */ class AjaxDataCollector extends DataCollector { - public function collect(Request $request, Response $response, \Exception $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null) { // all collecting is done client side } @@ -31,7 +31,7 @@ class AjaxDataCollector extends DataCollector // all collecting is done client side } - public function getName() + public function getName(): string { return 'ajax'; } diff --git a/lib/symfony/http-kernel/DataCollector/ConfigDataCollector.php b/lib/symfony/http-kernel/DataCollector/ConfigDataCollector.php index 8ad6279e5f..9819507aab 100644 --- a/lib/symfony/http-kernel/DataCollector/ConfigDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/ConfigDataCollector.php @@ -15,10 +15,12 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\HttpKernel\KernelInterface; -use Symfony\Component\VarDumper\Caster\LinkStub; +use Symfony\Component\VarDumper\Caster\ClassStub; /** * @author Fabien Potencier + * + * @final */ class ConfigDataCollector extends DataCollector implements LateDataCollectorInterface { @@ -26,20 +28,6 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte * @var KernelInterface */ private $kernel; - private $name; - private $version; - private $hasVarDumper; - - /** - * @param string $name The name of the application using the web profiler - * @param string $version The version of the application using the web profiler - */ - public function __construct($name = null, $version = null) - { - $this->name = $name; - $this->version = $version; - $this->hasVarDumper = class_exists(LinkStub::class); - } /** * Sets the Kernel associated with this Request. @@ -52,39 +40,36 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte /** * {@inheritdoc} */ - public function collect(Request $request, Response $response, \Exception $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null) { + $eom = \DateTime::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_MAINTENANCE); + $eol = \DateTime::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_LIFE); + $this->data = [ - 'app_name' => $this->name, - 'app_version' => $this->version, 'token' => $response->headers->get('X-Debug-Token'), 'symfony_version' => Kernel::VERSION, - 'symfony_state' => 'unknown', - 'name' => isset($this->kernel) ? $this->kernel->getName() : 'n/a', + 'symfony_minor_version' => sprintf('%s.%s', Kernel::MAJOR_VERSION, Kernel::MINOR_VERSION), + 'symfony_lts' => 4 === Kernel::MINOR_VERSION, + 'symfony_state' => $this->determineSymfonyState(), + 'symfony_eom' => $eom->format('F Y'), + 'symfony_eol' => $eol->format('F Y'), 'env' => isset($this->kernel) ? $this->kernel->getEnvironment() : 'n/a', 'debug' => isset($this->kernel) ? $this->kernel->isDebug() : 'n/a', 'php_version' => \PHP_VERSION, 'php_architecture' => \PHP_INT_SIZE * 8, - 'php_intl_locale' => class_exists('Locale', false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a', + 'php_intl_locale' => class_exists(\Locale::class, false) && \Locale::getDefault() ? \Locale::getDefault() : 'n/a', 'php_timezone' => date_default_timezone_get(), 'xdebug_enabled' => \extension_loaded('xdebug'), - 'apcu_enabled' => \extension_loaded('apcu') && filter_var(ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN), - 'zend_opcache_enabled' => \extension_loaded('Zend OPcache') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN), + 'apcu_enabled' => \extension_loaded('apcu') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN), + 'zend_opcache_enabled' => \extension_loaded('Zend OPcache') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN), 'bundles' => [], 'sapi_name' => \PHP_SAPI, ]; if (isset($this->kernel)) { foreach ($this->kernel->getBundles() as $name => $bundle) { - $this->data['bundles'][$name] = $this->hasVarDumper ? new LinkStub($bundle->getPath()) : $bundle->getPath(); + $this->data['bundles'][$name] = new ClassStub(\get_class($bundle)); } - - $this->data['symfony_state'] = $this->determineSymfonyState(); - $this->data['symfony_minor_version'] = sprintf('%s.%s', Kernel::MAJOR_VERSION, Kernel::MINOR_VERSION); - $eom = \DateTime::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_MAINTENANCE); - $eol = \DateTime::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_LIFE); - $this->data['symfony_eom'] = $eom->format('F Y'); - $this->data['symfony_eol'] = $eol->format('F Y'); } if (preg_match('~^(\d+(?:\.\d+)*)(.+)?$~', $this->data['php_version'], $matches) && isset($matches[2])) { @@ -106,32 +91,18 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte $this->data = $this->cloneVar($this->data); } - public function getApplicationName() - { - return $this->data['app_name']; - } - - public function getApplicationVersion() - { - return $this->data['app_version']; - } - /** * Gets the token. - * - * @return string|null The token */ - public function getToken() + public function getToken(): ?string { return $this->data['token']; } /** * Gets the Symfony version. - * - * @return string The Symfony version */ - public function getSymfonyVersion() + public function getSymfonyVersion(): string { return $this->data['symfony_version']; } @@ -141,7 +112,7 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte * * @return string One of: unknown, dev, stable, eom, eol */ - public function getSymfonyState() + public function getSymfonyState(): string { return $this->data['symfony_state']; } @@ -149,96 +120,76 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte /** * Returns the minor Symfony version used (without patch numbers of extra * suffix like "RC", "beta", etc.). - * - * @return string */ - public function getSymfonyMinorVersion() + public function getSymfonyMinorVersion(): string { return $this->data['symfony_minor_version']; } /** - * Returns the human redable date when this Symfony version ends its - * maintenance period. - * - * @return string + * Returns if the current Symfony version is a Long-Term Support one. */ - public function getSymfonyEom() + public function isSymfonyLts(): bool + { + return $this->data['symfony_lts']; + } + + /** + * Returns the human readable date when this Symfony version ends its + * maintenance period. + */ + public function getSymfonyEom(): string { return $this->data['symfony_eom']; } /** - * Returns the human redable date when this Symfony version reaches its + * Returns the human readable date when this Symfony version reaches its * "end of life" and won't receive bugs or security fixes. - * - * @return string */ - public function getSymfonyEol() + public function getSymfonyEol(): string { return $this->data['symfony_eol']; } /** * Gets the PHP version. - * - * @return string The PHP version */ - public function getPhpVersion() + public function getPhpVersion(): string { return $this->data['php_version']; } /** * Gets the PHP version extra part. - * - * @return string|null The extra part */ - public function getPhpVersionExtra() + public function getPhpVersionExtra(): ?string { - return isset($this->data['php_version_extra']) ? $this->data['php_version_extra'] : null; + return $this->data['php_version_extra'] ?? null; } /** * @return int The PHP architecture as number of bits (e.g. 32 or 64) */ - public function getPhpArchitecture() + public function getPhpArchitecture(): int { return $this->data['php_architecture']; } - /** - * @return string - */ - public function getPhpIntlLocale() + public function getPhpIntlLocale(): string { return $this->data['php_intl_locale']; } - /** - * @return string - */ - public function getPhpTimezone() + public function getPhpTimezone(): string { return $this->data['php_timezone']; } - /** - * Gets the application name. - * - * @return string The application name - */ - public function getAppName() - { - return $this->data['name']; - } - /** * Gets the environment. - * - * @return string The environment */ - public function getEnv() + public function getEnv(): string { return $this->data['env']; } @@ -246,7 +197,7 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte /** * Returns true if the debug is enabled. * - * @return bool true if debug is enabled, false otherwise + * @return bool|string true if debug is enabled, false otherwise or a string if no kernel was set */ public function isDebug() { @@ -255,30 +206,24 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte /** * Returns true if the XDebug is enabled. - * - * @return bool true if XDebug is enabled, false otherwise */ - public function hasXDebug() + public function hasXDebug(): bool { return $this->data['xdebug_enabled']; } /** * Returns true if APCu is enabled. - * - * @return bool true if APCu is enabled, false otherwise */ - public function hasApcu() + public function hasApcu(): bool { return $this->data['apcu_enabled']; } /** * Returns true if Zend OPcache is enabled. - * - * @return bool true if Zend OPcache is enabled, false otherwise */ - public function hasZendOpcache() + public function hasZendOpcache(): bool { return $this->data['zend_opcache_enabled']; } @@ -290,10 +235,8 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte /** * Gets the PHP SAPI name. - * - * @return string The environment */ - public function getSapiName() + public function getSapiName(): string { return $this->data['sapi_name']; } @@ -301,7 +244,7 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte /** * {@inheritdoc} */ - public function getName() + public function getName(): string { return 'config'; } @@ -311,7 +254,7 @@ class ConfigDataCollector extends DataCollector implements LateDataCollectorInte * * @return string One of: dev, stable, eom, eol */ - private function determineSymfonyState() + private function determineSymfonyState(): string { $now = new \DateTime(); $eom = \DateTime::createFromFormat('d/m/Y', '01/'.Kernel::END_OF_MAINTENANCE)->modify('last day of this month'); diff --git a/lib/symfony/http-kernel/DataCollector/DataCollector.php b/lib/symfony/http-kernel/DataCollector/DataCollector.php index 7119545b68..ccaf66da04 100644 --- a/lib/symfony/http-kernel/DataCollector/DataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/DataCollector.php @@ -11,8 +11,8 @@ namespace Symfony\Component\HttpKernel\DataCollector; -use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; use Symfony\Component\VarDumper\Caster\CutStub; +use Symfony\Component\VarDumper\Caster\ReflectionCaster; use Symfony\Component\VarDumper\Cloner\ClonerInterface; use Symfony\Component\VarDumper\Cloner\Data; use Symfony\Component\VarDumper\Cloner\Stub; @@ -26,36 +26,18 @@ use Symfony\Component\VarDumper\Cloner\VarCloner; * @author Fabien Potencier * @author Bernhard Schussek */ -abstract class DataCollector implements DataCollectorInterface, \Serializable +abstract class DataCollector implements DataCollectorInterface { /** * @var array|Data */ protected $data = []; - /** - * @var ValueExporter - */ - private $valueExporter; - /** * @var ClonerInterface */ private $cloner; - public function serialize() - { - $trace = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2); - $isCalledFromOverridingMethod = isset($trace[1]['function'], $trace[1]['object']) && 'serialize' === $trace[1]['function'] && $this === $trace[1]['object']; - - return $isCalledFromOverridingMethod ? $this->data : serialize($this->data); - } - - public function unserialize($data) - { - $this->data = \is_array($data) ? $data : unserialize($data); - } - /** * Converts the variable into a serializable Data instance. * @@ -72,52 +54,20 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable return $var; } if (null === $this->cloner) { - if (class_exists(CutStub::class)) { - $this->cloner = new VarCloner(); - $this->cloner->setMaxItems(-1); - $this->cloner->addCasters($this->getCasters()); - } else { - @trigger_error(sprintf('Using the %s() method without the VarDumper component is deprecated since Symfony 3.2 and won\'t be supported in 4.0. Install symfony/var-dumper version 3.2 or above.', __METHOD__), \E_USER_DEPRECATED); - $this->cloner = false; - } - } - if (false === $this->cloner) { - if (null === $this->valueExporter) { - $this->valueExporter = new ValueExporter(); - } - - return $this->valueExporter->exportValue($var); + $this->cloner = new VarCloner(); + $this->cloner->setMaxItems(-1); + $this->cloner->addCasters($this->getCasters()); } return $this->cloner->cloneVar($var); } - /** - * Converts a PHP variable to a string. - * - * @param mixed $var A PHP variable - * - * @return string The string representation of the variable - * - * @deprecated since version 3.2, to be removed in 4.0. Use cloneVar() instead. - */ - protected function varToString($var) - { - @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.2 and will be removed in 4.0. Use cloneVar() instead.', __METHOD__), \E_USER_DEPRECATED); - - if (null === $this->valueExporter) { - $this->valueExporter = new ValueExporter(); - } - - return $this->valueExporter->exportValue($var); - } - /** * @return callable[] The casters to add to the cloner */ protected function getCasters() { - return [ + $casters = [ '*' => function ($v, array $a, Stub $s, $isNested) { if (!$v instanceof Stub) { foreach ($a as $k => $v) { @@ -129,6 +79,34 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable return $a; }, - ]; + ] + ReflectionCaster::UNSET_CLOSURE_FILE_INFO; + + return $casters; + } + + /** + * @return array + */ + public function __sleep() + { + return ['data']; + } + + public function __wakeup() + { + } + + /** + * @internal to prevent implementing \Serializable + */ + final protected function serialize() + { + } + + /** + * @internal to prevent implementing \Serializable + */ + final protected function unserialize($data) + { } } diff --git a/lib/symfony/http-kernel/DataCollector/DataCollectorInterface.php b/lib/symfony/http-kernel/DataCollector/DataCollectorInterface.php index 2470089b81..1cb865fd66 100644 --- a/lib/symfony/http-kernel/DataCollector/DataCollectorInterface.php +++ b/lib/symfony/http-kernel/DataCollector/DataCollectorInterface.php @@ -13,25 +13,24 @@ namespace Symfony\Component\HttpKernel\DataCollector; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Contracts\Service\ResetInterface; /** * DataCollectorInterface. * * @author Fabien Potencier - * - * @method reset() Resets this data collector to its initial state. */ -interface DataCollectorInterface +interface DataCollectorInterface extends ResetInterface { /** * Collects data for the given Request and Response. */ - public function collect(Request $request, Response $response, \Exception $exception = null); + public function collect(Request $request, Response $response, \Throwable $exception = null); /** * Returns the name of the collector. * - * @return string The collector name + * @return string */ public function getName(); } diff --git a/lib/symfony/http-kernel/DataCollector/DumpDataCollector.php b/lib/symfony/http-kernel/DataCollector/DumpDataCollector.php index e5c276de6c..08026e5622 100644 --- a/lib/symfony/http-kernel/DataCollector/DumpDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/DumpDataCollector.php @@ -14,16 +14,20 @@ namespace Symfony\Component\HttpKernel\DataCollector; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Component\VarDumper\Cloner\Data; use Symfony\Component\VarDumper\Cloner\VarCloner; use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; use Symfony\Component\VarDumper\Dumper\DataDumperInterface; use Symfony\Component\VarDumper\Dumper\HtmlDumper; -use Twig\Template; +use Symfony\Component\VarDumper\Server\Connection; /** * @author Nicolas Grekas + * + * @final */ class DumpDataCollector extends DataCollector implements DataDumperInterface { @@ -37,16 +41,20 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface private $charset; private $requestStack; private $dumper; - private $dumperIsInjected; + private $sourceContextProvider; - public function __construct(Stopwatch $stopwatch = null, $fileLinkFormat = null, $charset = null, RequestStack $requestStack = null, DataDumperInterface $dumper = null) + /** + * @param string|FileLinkFormatter|null $fileLinkFormat + * @param DataDumperInterface|Connection|null $dumper + */ + public function __construct(Stopwatch $stopwatch = null, $fileLinkFormat = null, string $charset = null, RequestStack $requestStack = null, $dumper = null) { + $fileLinkFormat = $fileLinkFormat ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); $this->stopwatch = $stopwatch; - $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); - $this->charset = $charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8'; + $this->fileLinkFormat = $fileLinkFormat instanceof FileLinkFormatter && false === $fileLinkFormat->format('', 0) ? false : $fileLinkFormat; + $this->charset = $charset ?: \ini_get('php.output_encoding') ?: \ini_get('default_charset') ?: 'UTF-8'; $this->requestStack = $requestStack; $this->dumper = $dumper; - $this->dumperIsInjected = null !== $dumper; // All clones share these properties by reference: $this->rootRefs = [ @@ -55,6 +63,8 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface &$this->isCollected, &$this->clonesCount, ]; + + $this->sourceContextProvider = $dumper instanceof Connection && isset($dumper->getContextProviders()['source']) ? $dumper->getContextProviders()['source'] : new SourceContextProvider($this->charset); } public function __clone() @@ -67,67 +77,22 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface if ($this->stopwatch) { $this->stopwatch->start('dump'); } - if ($this->isCollected && !$this->dumper) { + + ['name' => $name, 'file' => $file, 'line' => $line, 'file_excerpt' => $fileExcerpt] = $this->sourceContextProvider->getContext(); + + if ($this->dumper instanceof Connection) { + if (!$this->dumper->write($data)) { + $this->isCollected = false; + } + } elseif ($this->dumper) { + $this->doDump($this->dumper, $data, $name, $file, $line); + } else { $this->isCollected = false; } - $trace = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 7); - - $file = $trace[0]['file']; - $line = $trace[0]['line']; - $name = false; - $fileExcerpt = false; - - for ($i = 1; $i < 7; ++$i) { - if (isset($trace[$i]['class'], $trace[$i]['function']) - && 'dump' === $trace[$i]['function'] - && 'Symfony\Component\VarDumper\VarDumper' === $trace[$i]['class'] - ) { - $file = $trace[$i]['file']; - $line = $trace[$i]['line']; - - while (++$i < 7) { - if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && 0 !== strpos($trace[$i]['function'], 'call_user_func')) { - $file = $trace[$i]['file']; - $line = $trace[$i]['line']; - - break; - } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) { - $template = $trace[$i]['object']; - $name = $template->getTemplateName(); - $src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false); - $info = $template->getDebugInfo(); - if (isset($info[$trace[$i - 1]['line']])) { - $line = $info[$trace[$i - 1]['line']]; - $file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null; - - if ($src) { - $src = explode("\n", $src); - $fileExcerpt = []; - - for ($i = max($line - 3, 1), $max = min($line + 3, \count($src)); $i <= $max; ++$i) { - $fileExcerpt[] = ''.$this->htmlEncode($src[$i - 1]).''; - } - - $fileExcerpt = '
                    '.implode("\n", $fileExcerpt).'
                  '; - } - } - break; - } - } - break; - } + if (!$this->dataCount) { + $this->data = []; } - - if (false === $name) { - $name = str_replace('\\', '/', $file); - $name = substr($name, strrpos($name, '/') + 1); - } - - if ($this->dumper) { - $this->doDump($data, $name, $file, $line); - } - $this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt'); ++$this->dataCount; @@ -136,10 +101,14 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface } } - public function collect(Request $request, Response $response, \Exception $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null) { + if (!$this->dataCount) { + $this->data = []; + } + // Sub-requests and programmatic calls stay in the collected profile. - if ($this->dumper || ($this->requestStack && $this->requestStack->getMasterRequest() !== $request) || $request->isXmlHttpRequest() || $request->headers->has('Origin')) { + if ($this->dumper || ($this->requestStack && $this->requestStack->getMainRequest() !== $request) || $request->isXmlHttpRequest() || $request->headers->has('Origin')) { return; } @@ -147,19 +116,22 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface if (!$this->requestStack || !$response->headers->has('X-Debug-Token') || $response->isRedirection() - || ($response->headers->has('Content-Type') && false === strpos($response->headers->get('Content-Type'), 'html')) + || ($response->headers->has('Content-Type') && !str_contains($response->headers->get('Content-Type'), 'html')) || 'html' !== $request->getRequestFormat() || false === strripos($response->getContent(), '') ) { - if ($response->headers->has('Content-Type') && false !== strpos($response->headers->get('Content-Type'), 'html')) { - $this->dumper = new HtmlDumper('php://output', $this->charset); - $this->dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); + if ($response->headers->has('Content-Type') && str_contains($response->headers->get('Content-Type'), 'html')) { + $dumper = new HtmlDumper('php://output', $this->charset); + $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); } else { - $this->dumper = new CliDumper('php://output', $this->charset); + $dumper = new CliDumper('php://output', $this->charset); + if (method_exists($dumper, 'setDisplayOptions')) { + $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); + } } foreach ($this->data as $dump) { - $this->doDump($dump['data'], $dump['name'], $dump['file'], $dump['line']); + $this->doDump($dumper, $dump['data'], $dump['name'], $dump['file'], $dump['line']); } } } @@ -176,42 +148,54 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface $this->clonesIndex = 0; } - public function serialize() + /** + * @internal + */ + public function __sleep(): array { + if (!$this->dataCount) { + $this->data = []; + } + if ($this->clonesCount !== $this->clonesIndex) { - return 'a:0:{}'; + return []; } $this->data[] = $this->fileLinkFormat; $this->data[] = $this->charset; - $ser = serialize($this->data); - $this->data = []; $this->dataCount = 0; $this->isCollected = true; - if (!$this->dumperIsInjected) { - $this->dumper = null; - } - return $ser; + return parent::__sleep(); } - public function unserialize($data) + /** + * @internal + */ + public function __wakeup() { - $this->data = unserialize($data); + parent::__wakeup(); + $charset = array_pop($this->data); $fileLinkFormat = array_pop($this->data); $this->dataCount = \count($this->data); - self::__construct($this->stopwatch, $fileLinkFormat, $charset); + foreach ($this->data as $dump) { + if (!\is_string($dump['name']) || !\is_string($dump['file']) || !\is_int($dump['line'])) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + } + + self::__construct($this->stopwatch, \is_string($fileLinkFormat) || $fileLinkFormat instanceof FileLinkFormatter ? $fileLinkFormat : null, \is_string($charset) ? $charset : null); } - public function getDumpsCount() + public function getDumpsCount(): int { return $this->dataCount; } - public function getDumps($format, $maxDepthLimit = -1, $maxItemsPerDepth = -1) + public function getDumps(string $format, int $maxDepthLimit = -1, int $maxItemsPerDepth = -1): array { - $data = fopen('php://memory', 'r+b'); + $data = fopen('php://memory', 'r+'); if ('html' === $format) { $dumper = new HtmlDumper($data, $this->charset); @@ -221,6 +205,10 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface } $dumps = []; + if (!$this->dataCount) { + return $this->data = []; + } + foreach ($this->data as $dump) { $dumper->dump($dump['data']->withMaxDepth($maxDepthLimit)->withMaxItemsPerDepth($maxItemsPerDepth)); $dump['data'] = stream_get_contents($data, -1, 0); @@ -232,34 +220,37 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface return $dumps; } - public function getName() + public function getName(): string { return 'dump'; } public function __destruct() { - if (0 === $this->clonesCount-- && !$this->isCollected && $this->data) { + if (0 === $this->clonesCount-- && !$this->isCollected && $this->dataCount) { $this->clonesCount = 0; $this->isCollected = true; $h = headers_list(); $i = \count($h); - array_unshift($h, 'Content-Type: '.ini_get('default_mimetype')); + array_unshift($h, 'Content-Type: '.\ini_get('default_mimetype')); while (0 !== stripos($h[$i], 'Content-Type:')) { --$i; } if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && stripos($h[$i], 'html')) { - $this->dumper = new HtmlDumper('php://output', $this->charset); - $this->dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); + $dumper = new HtmlDumper('php://output', $this->charset); + $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); } else { - $this->dumper = new CliDumper('php://output', $this->charset); + $dumper = new CliDumper('php://output', $this->charset); + if (method_exists($dumper, 'setDisplayOptions')) { + $dumper->setDisplayOptions(['fileLinkFormat' => $this->fileLinkFormat]); + } } foreach ($this->data as $i => $dump) { $this->data[$i] = null; - $this->doDump($dump['data'], $dump['name'], $dump['file'], $dump['line']); + $this->doDump($dumper, $dump['data'], $dump['name'], $dump['file'], $dump['line']); } $this->data = []; @@ -267,9 +258,9 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface } } - private function doDump($data, $name, $file, $line) + private function doDump(DataDumperInterface $dumper, Data $data, string $name, string $file, int $line) { - if ($this->dumper instanceof CliDumper) { + if ($dumper instanceof CliDumper) { $contextDumper = function ($name, $file, $line, $fmt) { if ($this instanceof HtmlDumper) { if ($file) { @@ -290,26 +281,12 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface } $this->dumpLine(0); }; - $contextDumper = $contextDumper->bindTo($this->dumper, $this->dumper); + $contextDumper = $contextDumper->bindTo($dumper, $dumper); $contextDumper($name, $file, $line, $this->fileLinkFormat); } else { $cloner = new VarCloner(); - $this->dumper->dump($cloner->cloneVar($name.' on line '.$line.':')); + $dumper->dump($cloner->cloneVar($name.' on line '.$line.':')); } - $this->dumper->dump($data); - } - - private function htmlEncode($s) - { - $html = ''; - - $dumper = new HtmlDumper(function ($line) use (&$html) { $html .= $line; }, $this->charset); - $dumper->setDumpHeader(''); - $dumper->setDumpBoundaries('', ''); - - $cloner = new VarCloner(); - $dumper->dump($cloner->cloneVar($s)); - - return substr(strip_tags($html), 1, -1); + $dumper->dump($data); } } diff --git a/lib/symfony/http-kernel/DataCollector/EventDataCollector.php b/lib/symfony/http-kernel/DataCollector/EventDataCollector.php index e3f8576cdc..a813553364 100644 --- a/lib/symfony/http-kernel/DataCollector/EventDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/EventDataCollector.php @@ -11,36 +11,41 @@ namespace Symfony\Component\HttpKernel\DataCollector; -use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\Service\ResetInterface; /** - * EventDataCollector. - * * @author Fabien Potencier + * + * @final */ class EventDataCollector extends DataCollector implements LateDataCollectorInterface { protected $dispatcher; + private $requestStack; + private $currentRequest; - public function __construct(EventDispatcherInterface $dispatcher = null) + public function __construct(EventDispatcherInterface $dispatcher = null, RequestStack $requestStack = null) { - if ($dispatcher instanceof TraceableEventDispatcherInterface && !method_exists($dispatcher, 'reset')) { - @trigger_error(sprintf('Implementing "%s" without the "reset()" method is deprecated since Symfony 3.4 and will be unsupported in 4.0 for class "%s".', TraceableEventDispatcherInterface::class, \get_class($dispatcher)), \E_USER_DEPRECATED); - } $this->dispatcher = $dispatcher; + $this->requestStack = $requestStack; } /** * {@inheritdoc} */ - public function collect(Request $request, Response $response, \Exception $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null) { + $this->currentRequest = $this->requestStack && $this->requestStack->getMainRequest() !== $request ? $request : null; $this->data = [ 'called_listeners' => [], 'not_called_listeners' => [], + 'orphaned_events' => [], ]; } @@ -48,30 +53,26 @@ class EventDataCollector extends DataCollector implements LateDataCollectorInter { $this->data = []; - if ($this->dispatcher instanceof TraceableEventDispatcherInterface) { - if (!method_exists($this->dispatcher, 'reset')) { - return; // @deprecated - } - + if ($this->dispatcher instanceof ResetInterface) { $this->dispatcher->reset(); } } public function lateCollect() { - if ($this->dispatcher instanceof TraceableEventDispatcherInterface) { - $this->setCalledListeners($this->dispatcher->getCalledListeners()); - $this->setNotCalledListeners($this->dispatcher->getNotCalledListeners()); + if ($this->dispatcher instanceof TraceableEventDispatcher) { + $this->setCalledListeners($this->dispatcher->getCalledListeners($this->currentRequest)); + $this->setNotCalledListeners($this->dispatcher->getNotCalledListeners($this->currentRequest)); + $this->setOrphanedEvents($this->dispatcher->getOrphanedEvents($this->currentRequest)); } + $this->data = $this->cloneVar($this->data); } /** - * Sets the called listeners. - * * @param array $listeners An array of called listeners * - * @see TraceableEventDispatcherInterface + * @see TraceableEventDispatcher */ public function setCalledListeners(array $listeners) { @@ -79,11 +80,9 @@ class EventDataCollector extends DataCollector implements LateDataCollectorInter } /** - * Gets the called listeners. + * @see TraceableEventDispatcher * - * @return array An array of called listeners - * - * @see TraceableEventDispatcherInterface + * @return array|Data */ public function getCalledListeners() { @@ -91,11 +90,7 @@ class EventDataCollector extends DataCollector implements LateDataCollectorInter } /** - * Sets the not called listeners. - * - * @param array $listeners An array of not called listeners - * - * @see TraceableEventDispatcherInterface + * @see TraceableEventDispatcher */ public function setNotCalledListeners(array $listeners) { @@ -103,21 +98,39 @@ class EventDataCollector extends DataCollector implements LateDataCollectorInter } /** - * Gets the not called listeners. + * @see TraceableEventDispatcher * - * @return array An array of not called listeners - * - * @see TraceableEventDispatcherInterface + * @return array|Data */ public function getNotCalledListeners() { return $this->data['not_called_listeners']; } + /** + * @param array $events An array of orphaned events + * + * @see TraceableEventDispatcher + */ + public function setOrphanedEvents(array $events) + { + $this->data['orphaned_events'] = $events; + } + + /** + * @see TraceableEventDispatcher + * + * @return array|Data + */ + public function getOrphanedEvents() + { + return $this->data['orphaned_events']; + } + /** * {@inheritdoc} */ - public function getName() + public function getName(): string { return 'events'; } diff --git a/lib/symfony/http-kernel/DataCollector/ExceptionDataCollector.php b/lib/symfony/http-kernel/DataCollector/ExceptionDataCollector.php index f9be5bddff..14bbbb364b 100644 --- a/lib/symfony/http-kernel/DataCollector/ExceptionDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/ExceptionDataCollector.php @@ -11,25 +11,25 @@ namespace Symfony\Component\HttpKernel\DataCollector; -use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; /** - * ExceptionDataCollector. - * * @author Fabien Potencier + * + * @final */ class ExceptionDataCollector extends DataCollector { /** * {@inheritdoc} */ - public function collect(Request $request, Response $response, \Exception $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null) { if (null !== $exception) { $this->data = [ - 'exception' => FlattenException::create($exception), + 'exception' => FlattenException::createFromThrowable($exception), ]; } } @@ -42,19 +42,12 @@ class ExceptionDataCollector extends DataCollector $this->data = []; } - /** - * Checks if the exception is not null. - * - * @return bool true if the exception is not null, false otherwise - */ - public function hasException() + public function hasException(): bool { return isset($this->data['exception']); } /** - * Gets the exception. - * * @return \Exception|FlattenException */ public function getException() @@ -62,42 +55,22 @@ class ExceptionDataCollector extends DataCollector return $this->data['exception']; } - /** - * Gets the exception message. - * - * @return string The exception message - */ - public function getMessage() + public function getMessage(): string { return $this->data['exception']->getMessage(); } - /** - * Gets the exception code. - * - * @return int The exception code - */ - public function getCode() + public function getCode(): int { return $this->data['exception']->getCode(); } - /** - * Gets the status code. - * - * @return int The status code - */ - public function getStatusCode() + public function getStatusCode(): int { return $this->data['exception']->getStatusCode(); } - /** - * Gets the exception trace. - * - * @return array The exception trace - */ - public function getTrace() + public function getTrace(): array { return $this->data['exception']->getTrace(); } @@ -105,7 +78,7 @@ class ExceptionDataCollector extends DataCollector /** * {@inheritdoc} */ - public function getName() + public function getName(): string { return 'exception'; } diff --git a/lib/symfony/http-kernel/DataCollector/LoggerDataCollector.php b/lib/symfony/http-kernel/DataCollector/LoggerDataCollector.php index c2c337a017..15094fdbed 100644 --- a/lib/symfony/http-kernel/DataCollector/LoggerDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/LoggerDataCollector.php @@ -11,40 +11,41 @@ namespace Symfony\Component\HttpKernel\DataCollector; -use Symfony\Component\Debug\Exception\SilencedErrorContext; +use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; /** - * LogDataCollector. - * * @author Fabien Potencier + * + * @final */ class LoggerDataCollector extends DataCollector implements LateDataCollectorInterface { private $logger; private $containerPathPrefix; + private $currentRequest; + private $requestStack; + private $processedLogs; - public function __construct($logger = null, $containerPathPrefix = null) + public function __construct(object $logger = null, string $containerPathPrefix = null, RequestStack $requestStack = null) { if (null !== $logger && $logger instanceof DebugLoggerInterface) { - if (!method_exists($logger, 'clear')) { - @trigger_error(sprintf('Implementing "%s" without the "clear()" method is deprecated since Symfony 3.4 and will be unsupported in 4.0 for class "%s".', DebugLoggerInterface::class, \get_class($logger)), \E_USER_DEPRECATED); - } - $this->logger = $logger; } $this->containerPathPrefix = $containerPathPrefix; + $this->requestStack = $requestStack; } /** * {@inheritdoc} */ - public function collect(Request $request, Response $response, \Exception $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null) { - // everything is done as late as possible + $this->currentRequest = $this->requestStack && $this->requestStack->getMainRequest() !== $request ? $request : null; } /** @@ -52,7 +53,7 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte */ public function reset() { - if ($this->logger && method_exists($this->logger, 'clear')) { + if ($this->logger instanceof DebugLoggerInterface) { $this->logger->clear(); } $this->data = []; @@ -66,58 +67,137 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte if (null !== $this->logger) { $containerDeprecationLogs = $this->getContainerDeprecationLogs(); $this->data = $this->computeErrorsCount($containerDeprecationLogs); - $this->data['compiler_logs'] = $this->getContainerCompilerLogs(); - $this->data['logs'] = $this->sanitizeLogs(array_merge($this->logger->getLogs(), $containerDeprecationLogs)); + // get compiler logs later (only when they are needed) to improve performance + $this->data['compiler_logs'] = []; + $this->data['compiler_logs_filepath'] = $this->containerPathPrefix.'Compiler.log'; + $this->data['logs'] = $this->sanitizeLogs(array_merge($this->logger->getLogs($this->currentRequest), $containerDeprecationLogs)); $this->data = $this->cloneVar($this->data); } + $this->currentRequest = null; } public function getLogs() { - return isset($this->data['logs']) ? $this->data['logs'] : []; + return $this->data['logs'] ?? []; + } + + public function getProcessedLogs() + { + if (null !== $this->processedLogs) { + return $this->processedLogs; + } + + $rawLogs = $this->getLogs(); + if ([] === $rawLogs) { + return $this->processedLogs = $rawLogs; + } + + $logs = []; + foreach ($this->getLogs()->getValue() as $rawLog) { + $rawLogData = $rawLog->getValue(); + + if ($rawLogData['priority']->getValue() > 300) { + $logType = 'error'; + } elseif (isset($rawLogData['scream']) && false === $rawLogData['scream']->getValue()) { + $logType = 'deprecation'; + } elseif (isset($rawLogData['scream']) && true === $rawLogData['scream']->getValue()) { + $logType = 'silenced'; + } else { + $logType = 'regular'; + } + + $logs[] = [ + 'type' => $logType, + 'errorCount' => $rawLog['errorCount'] ?? 1, + 'timestamp' => $rawLogData['timestamp_rfc3339']->getValue(), + 'priority' => $rawLogData['priority']->getValue(), + 'priorityName' => $rawLogData['priorityName']->getValue(), + 'channel' => $rawLogData['channel']->getValue(), + 'message' => $rawLogData['message'], + 'context' => $rawLogData['context'], + ]; + } + + // sort logs from oldest to newest + usort($logs, static function ($logA, $logB) { + return $logA['timestamp'] <=> $logB['timestamp']; + }); + + return $this->processedLogs = $logs; + } + + public function getFilters() + { + $filters = [ + 'channel' => [], + 'priority' => [ + 'Debug' => 100, + 'Info' => 200, + 'Notice' => 250, + 'Warning' => 300, + 'Error' => 400, + 'Critical' => 500, + 'Alert' => 550, + 'Emergency' => 600, + ], + ]; + + $allChannels = []; + foreach ($this->getProcessedLogs() as $log) { + if ('' === trim($log['channel'])) { + continue; + } + + $allChannels[] = $log['channel']; + } + $channels = array_unique($allChannels); + sort($channels); + $filters['channel'] = $channels; + + return $filters; } public function getPriorities() { - return isset($this->data['priorities']) ? $this->data['priorities'] : []; + return $this->data['priorities'] ?? []; } public function countErrors() { - return isset($this->data['error_count']) ? $this->data['error_count'] : 0; + return $this->data['error_count'] ?? 0; } public function countDeprecations() { - return isset($this->data['deprecation_count']) ? $this->data['deprecation_count'] : 0; + return $this->data['deprecation_count'] ?? 0; } public function countWarnings() { - return isset($this->data['warning_count']) ? $this->data['warning_count'] : 0; + return $this->data['warning_count'] ?? 0; } public function countScreams() { - return isset($this->data['scream_count']) ? $this->data['scream_count'] : 0; + return $this->data['scream_count'] ?? 0; } public function getCompilerLogs() { - return isset($this->data['compiler_logs']) ? $this->data['compiler_logs'] : []; + return $this->cloneVar($this->getContainerCompilerLogs($this->data['compiler_logs_filepath'] ?? null)); } /** * {@inheritdoc} */ - public function getName() + public function getName(): string { return 'logger'; } - private function getContainerDeprecationLogs() + private function getContainerDeprecationLogs(): array { - if (null === $this->containerPathPrefix || !file_exists($file = $this->containerPathPrefix.'Deprecations.log')) { + if (null === $this->containerPathPrefix || !is_file($file = $this->containerPathPrefix.'Deprecations.log')) { return []; } @@ -130,9 +210,10 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte foreach (unserialize($logContent) as $log) { $log['context'] = ['exception' => new SilencedErrorContext($log['type'], $log['file'], $log['line'], $log['trace'], $log['count'])]; $log['timestamp'] = $bootTime; + $log['timestamp_rfc3339'] = (new \DateTimeImmutable())->setTimestamp($bootTime)->format(\DateTimeInterface::RFC3339_EXTENDED); $log['priority'] = 100; $log['priorityName'] = 'DEBUG'; - $log['channel'] = '-'; + $log['channel'] = null; $log['scream'] = false; unset($log['type'], $log['file'], $log['line'], $log['trace'], $log['trace'], $log['count']); $logs[] = $log; @@ -141,14 +222,14 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte return $logs; } - private function getContainerCompilerLogs() + private function getContainerCompilerLogs(string $compilerLogsFilepath = null): array { - if (null === $this->containerPathPrefix || !file_exists($file = $this->containerPathPrefix.'Compiler.log')) { + if (!is_file($compilerLogsFilepath)) { return []; } $logs = []; - foreach (file($file, \FILE_IGNORE_NEW_LINES) as $log) { + foreach (file($compilerLogsFilepath, \FILE_IGNORE_NEW_LINES) as $log) { $log = explode(': ', $log, 2); if (!isset($log[1]) || !preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)++$/', $log[0])) { $log = ['Unknown Compiler Pass', implode(': ', $log)]; @@ -160,7 +241,7 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte return $logs; } - private function sanitizeLogs($logs) + private function sanitizeLogs(array $logs) { $sanitizedLogs = []; $silencedLogs = []; @@ -209,7 +290,7 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte return array_values($sanitizedLogs); } - private function isSilencedOrDeprecationErrorLog(array $log) + private function isSilencedOrDeprecationErrorLog(array $log): bool { if (!isset($log['context']['exception'])) { return false; @@ -228,18 +309,18 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte return false; } - private function computeErrorsCount(array $containerDeprecationLogs) + private function computeErrorsCount(array $containerDeprecationLogs): array { $silencedLogs = []; $count = [ - 'error_count' => $this->logger->countErrors(), + 'error_count' => $this->logger->countErrors($this->currentRequest), 'deprecation_count' => 0, 'warning_count' => 0, 'scream_count' => 0, 'priorities' => [], ]; - foreach ($this->logger->getLogs() as $log) { + foreach ($this->logger->getLogs($this->currentRequest) as $log) { if (isset($count['priorities'][$log['priority']])) { ++$count['priorities'][$log['priority']]['count']; } else { diff --git a/lib/symfony/http-kernel/DataCollector/MemoryDataCollector.php b/lib/symfony/http-kernel/DataCollector/MemoryDataCollector.php index 7a6e1c0646..53a1f9e448 100644 --- a/lib/symfony/http-kernel/DataCollector/MemoryDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/MemoryDataCollector.php @@ -15,9 +15,9 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; /** - * MemoryDataCollector. - * * @author Fabien Potencier + * + * @final */ class MemoryDataCollector extends DataCollector implements LateDataCollectorInterface { @@ -29,7 +29,7 @@ class MemoryDataCollector extends DataCollector implements LateDataCollectorInte /** * {@inheritdoc} */ - public function collect(Request $request, Response $response, \Exception $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null) { $this->updateMemoryUsage(); } @@ -41,7 +41,7 @@ class MemoryDataCollector extends DataCollector implements LateDataCollectorInte { $this->data = [ 'memory' => 0, - 'memory_limit' => $this->convertToBytes(ini_get('memory_limit')), + 'memory_limit' => $this->convertToBytes(\ini_get('memory_limit')), ]; } @@ -53,29 +53,19 @@ class MemoryDataCollector extends DataCollector implements LateDataCollectorInte $this->updateMemoryUsage(); } - /** - * Gets the memory. - * - * @return int The memory - */ - public function getMemory() + public function getMemory(): int { return $this->data['memory']; } /** - * Gets the PHP memory limit. - * - * @return int The memory limit + * @return int|float */ public function getMemoryLimit() { return $this->data['memory_limit']; } - /** - * Updates the memory usage data. - */ public function updateMemoryUsage() { $this->data['memory'] = memory_get_peak_usage(true); @@ -84,12 +74,15 @@ class MemoryDataCollector extends DataCollector implements LateDataCollectorInte /** * {@inheritdoc} */ - public function getName() + public function getName(): string { return 'memory'; } - private function convertToBytes($memoryLimit) + /** + * @return int|float + */ + private function convertToBytes(string $memoryLimit) { if ('-1' === $memoryLimit) { return -1; @@ -97,9 +90,9 @@ class MemoryDataCollector extends DataCollector implements LateDataCollectorInte $memoryLimit = strtolower($memoryLimit); $max = strtolower(ltrim($memoryLimit, '+')); - if (0 === strpos($max, '0x')) { + if (str_starts_with($max, '0x')) { $max = \intval($max, 16); - } elseif (0 === strpos($max, '0')) { + } elseif (str_starts_with($max, '0')) { $max = \intval($max, 8); } else { $max = (int) $max; diff --git a/lib/symfony/http-kernel/DataCollector/RequestDataCollector.php b/lib/symfony/http-kernel/DataCollector/RequestDataCollector.php index 41372a5654..523f5c957f 100644 --- a/lib/symfony/http-kernel/DataCollector/RequestDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/RequestDataCollector.php @@ -15,27 +15,39 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Event\FilterControllerEvent; -use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpKernel\Event\ControllerEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\VarDumper\Cloner\Data; /** * @author Fabien Potencier + * + * @final */ class RequestDataCollector extends DataCollector implements EventSubscriberInterface, LateDataCollectorInterface { - protected $controllers; + /** + * @var \SplObjectStorage + */ + private $controllers; + private $sessionUsages = []; + private $requestStack; - public function __construct() + public function __construct(RequestStack $requestStack = null) { $this->controllers = new \SplObjectStorage(); + $this->requestStack = $requestStack; } /** * {@inheritdoc} */ - public function collect(Request $request, Response $response, \Exception $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null) { // attributes are serialized and as they can be anything, they need to be converted to strings. $attributes = []; @@ -49,12 +61,7 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter } } - try { - $content = $request->getContent(); - } catch (\LogicException $e) { - // the user already got the request content as a resource - $content = false; - } + $content = $request->getContent(); $sessionMetadata = []; $sessionAttributes = []; @@ -77,15 +84,22 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter $responseCookies[$cookie->getName()] = $cookie; } + $dotenvVars = []; + foreach (explode(',', $_SERVER['SYMFONY_DOTENV_VARS'] ?? $_ENV['SYMFONY_DOTENV_VARS'] ?? '') as $name) { + if ('' !== $name && isset($_ENV[$name])) { + $dotenvVars[$name] = $_ENV[$name]; + } + } + $this->data = [ 'method' => $request->getMethod(), 'format' => $request->getRequestFormat(), - 'content' => $content, 'content_type' => $response->headers->get('Content-Type', 'text/html'), - 'status_text' => isset(Response::$statusTexts[$statusCode]) ? Response::$statusTexts[$statusCode] : '', + 'status_text' => Response::$statusTexts[$statusCode] ?? '', 'status_code' => $statusCode, 'request_query' => $request->query->all(), 'request_request' => $request->request->all(), + 'request_files' => $request->files->all(), 'request_headers' => $request->headers->all(), 'request_server' => $request->server->all(), 'request_cookies' => $request->cookies->all(), @@ -95,10 +109,13 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter 'response_cookies' => $responseCookies, 'session_metadata' => $sessionMetadata, 'session_attributes' => $sessionAttributes, + 'session_usages' => array_values($this->sessionUsages), + 'stateless_check' => $this->requestStack && $this->requestStack->getMainRequest()->attributes->get('_stateless', false), 'flashes' => $flashes, 'path_info' => $request->getPathInfo(), 'controller' => 'n/a', 'locale' => $request->getLocale(), + 'dotenv_vars' => $dotenvVars, ]; if (isset($this->data['request_headers']['php-auth-pw'])) { @@ -110,9 +127,13 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter } if (isset($this->data['request_request']['_password'])) { + $encodedPassword = rawurlencode($this->data['request_request']['_password']); + $content = str_replace('_password='.$encodedPassword, '_password=******', $content); $this->data['request_request']['_password'] = '******'; } + $this->data['content'] = $content; + foreach ($this->data as $key => $value) { if (!\is_array($value)) { continue; @@ -142,12 +163,17 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter 'method' => $request->getMethod(), 'controller' => $this->parseController($request->attributes->get('_controller')), 'status_code' => $statusCode, - 'status_text' => Response::$statusTexts[(int) $statusCode], - ]) + 'status_text' => Response::$statusTexts[$statusCode], + ]), + 0, '/', null, $request->isSecure(), true, false, 'lax' )); } $this->data['identifier'] = $this->data['route'] ?: (\is_array($this->data['controller']) ? $this->data['controller']['class'].'::'.$this->data['controller']['method'].'()' : $this->data['controller']); + + if ($response->headers->has('x-previous-debug-token')) { + $this->data['forward_token'] = $response->headers->get('x-previous-debug-token'); + } } public function lateCollect() @@ -159,6 +185,7 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter { $this->data = []; $this->controllers = new \SplObjectStorage(); + $this->sessionUsages = []; } public function getMethod() @@ -181,17 +208,22 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter return new ParameterBag($this->data['request_query']->getValue()); } + public function getRequestFiles() + { + return new ParameterBag($this->data['request_files']->getValue()); + } + public function getRequestHeaders() { return new ParameterBag($this->data['request_headers']->getValue()); } - public function getRequestServer($raw = false) + public function getRequestServer(bool $raw = false) { return new ParameterBag($this->data['request_server']->getValue($raw)); } - public function getRequestCookies($raw = false) + public function getRequestCookies(bool $raw = false) { return new ParameterBag($this->data['request_cookies']->getValue($raw)); } @@ -221,6 +253,16 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter return $this->data['session_attributes']->getValue(); } + public function getStatelessCheck() + { + return $this->data['stateless_check']; + } + + public function getSessionUsages() + { + return $this->data['session_usages']; + } + public function getFlashes() { return $this->data['flashes']->getValue(); @@ -231,6 +273,18 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter return $this->data['content']; } + public function isJsonRequest() + { + return 1 === preg_match('{^application/(?:\w+\++)*json$}i', $this->data['request_headers']['content-type']); + } + + public function getPrettyJson() + { + $decoded = json_decode($this->getContent()); + + return \JSON_ERROR_NONE === json_last_error() ? json_encode($decoded, \JSON_PRETTY_PRINT) : null; + } + public function getContentType() { return $this->data['content_type']; @@ -256,14 +310,17 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter return $this->data['locale']; } + public function getDotenvVars() + { + return new ParameterBag($this->data['dotenv_vars']->getValue()); + } + /** * Gets the route name. * * The _route request attributes is automatically set by the Router Matcher. - * - * @return string The route */ - public function getRoute() + public function getRoute(): string { return $this->data['route']; } @@ -277,10 +334,8 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter * Gets the route parameters. * * The _route_params request attributes is automatically set by the RouterListener. - * - * @return array The parameters */ - public function getRouteParams() + public function getRouteParams(): array { return isset($this->data['request_attributes']['_route_params']) ? $this->data['request_attributes']['_route_params']->getValue() : []; } @@ -288,8 +343,8 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter /** * Gets the parsed controller. * - * @return array|string The controller as a string or array of data - * with keys 'class', 'method', 'file' and 'line' + * @return array|string|Data The controller as a string or array of data + * with keys 'class', 'method', 'file' and 'line' */ public function getController() { @@ -299,22 +354,27 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter /** * Gets the previous request attributes. * - * @return array|bool A legacy array of data from the previous redirection response - * or false otherwise + * @return array|Data|false A legacy array of data from the previous redirection response + * or false otherwise */ public function getRedirect() { - return isset($this->data['redirect']) ? $this->data['redirect'] : false; + return $this->data['redirect'] ?? false; } - public function onKernelController(FilterControllerEvent $event) + public function getForwardToken() + { + return $this->data['forward_token'] ?? null; + } + + public function onKernelController(ControllerEvent $event) { $this->controllers[$event->getRequest()] = $event->getController(); } - public function onKernelResponse(FilterResponseEvent $event) + public function onKernelResponse(ResponseEvent $event) { - if (!$event->isMasterRequest()) { + if (!$event->isMainRequest()) { return; } @@ -323,7 +383,7 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter } } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ KernelEvents::CONTROLLER => 'onKernelController', @@ -334,21 +394,50 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter /** * {@inheritdoc} */ - public function getName() + public function getName(): string { return 'request'; } + public function collectSessionUsage(): void + { + $trace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS); + + $traceEndIndex = \count($trace) - 1; + for ($i = $traceEndIndex; $i > 0; --$i) { + if (null !== ($class = $trace[$i]['class'] ?? null) && (is_subclass_of($class, SessionInterface::class) || is_subclass_of($class, SessionBagInterface::class))) { + $traceEndIndex = $i; + break; + } + } + + if ((\count($trace) - 1) === $traceEndIndex) { + return; + } + + // Remove part of the backtrace that belongs to session only + array_splice($trace, 0, $traceEndIndex); + + // Merge identical backtraces generated by internal call reports + $name = sprintf('%s:%s', $trace[1]['class'] ?? $trace[0]['file'], $trace[0]['line']); + if (!\array_key_exists($name, $this->sessionUsages)) { + $this->sessionUsages[$name] = [ + 'name' => $name, + 'file' => $trace[0]['file'], + 'line' => $trace[0]['line'], + 'trace' => $trace, + ]; + } + } + /** - * Parse a controller. - * - * @param mixed $controller The controller to parse + * @param string|object|array|null $controller The controller to parse * * @return array|string An array of controller data or a simple string */ - protected function parseController($controller) + private function parseController($controller) { - if (\is_string($controller) && false !== strpos($controller, '::')) { + if (\is_string($controller) && str_contains($controller, '::')) { $controller = explode('::', $controller); } @@ -357,7 +446,7 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter $r = new \ReflectionMethod($controller[0], $controller[1]); return [ - 'class' => \is_object($controller[0]) ? \get_class($controller[0]) : $controller[0], + 'class' => \is_object($controller[0]) ? get_debug_type($controller[0]) : $controller[0], 'method' => $controller[1], 'file' => $r->getFileName(), 'line' => $r->getStartLine(), @@ -366,7 +455,7 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter if (\is_callable($controller)) { // using __call or __callStatic return [ - 'class' => \is_object($controller[0]) ? \get_class($controller[0]) : $controller[0], + 'class' => \is_object($controller[0]) ? get_debug_type($controller[0]) : $controller[0], 'method' => $controller[1], 'file' => 'n/a', 'line' => 'n/a', @@ -385,7 +474,7 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter 'line' => $r->getStartLine(), ]; - if (false !== strpos($r->name, '{closure}')) { + if (str_contains($r->name, '{closure}')) { return $controller; } $controller['method'] = $r->name; diff --git a/lib/symfony/http-kernel/DataCollector/RouterDataCollector.php b/lib/symfony/http-kernel/DataCollector/RouterDataCollector.php index 432dc3618f..372ede0378 100644 --- a/lib/symfony/http-kernel/DataCollector/RouterDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/RouterDataCollector.php @@ -14,17 +14,15 @@ namespace Symfony\Component\HttpKernel\DataCollector; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Event\FilterControllerEvent; +use Symfony\Component\HttpKernel\Event\ControllerEvent; /** - * RouterDataCollector. - * * @author Fabien Potencier */ class RouterDataCollector extends DataCollector { /** - * @var \SplObjectStorage + * @var \SplObjectStorage */ protected $controllers; @@ -35,8 +33,10 @@ class RouterDataCollector extends DataCollector /** * {@inheritdoc} + * + * @final */ - public function collect(Request $request, Response $response, \Exception $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null) { if ($response instanceof RedirectResponse) { $this->data['redirect'] = true; @@ -69,7 +69,7 @@ class RouterDataCollector extends DataCollector /** * Remembers the controller associated to each request. */ - public function onKernelController(FilterControllerEvent $event) + public function onKernelController(ControllerEvent $event) { $this->controllers[$event->getRequest()] = $event->getController(); } @@ -83,7 +83,7 @@ class RouterDataCollector extends DataCollector } /** - * @return string|null The target URL + * @return string|null */ public function getTargetUrl() { @@ -91,7 +91,7 @@ class RouterDataCollector extends DataCollector } /** - * @return string|null The target route + * @return string|null */ public function getTargetRoute() { diff --git a/lib/symfony/http-kernel/DataCollector/TimeDataCollector.php b/lib/symfony/http-kernel/DataCollector/TimeDataCollector.php index cb490c2bb3..43799060f6 100644 --- a/lib/symfony/http-kernel/DataCollector/TimeDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/TimeDataCollector.php @@ -19,11 +19,13 @@ use Symfony\Component\Stopwatch\StopwatchEvent; /** * @author Fabien Potencier + * + * @final */ class TimeDataCollector extends DataCollector implements LateDataCollectorInterface { - protected $kernel; - protected $stopwatch; + private $kernel; + private $stopwatch; public function __construct(KernelInterface $kernel = null, Stopwatch $stopwatch = null) { @@ -34,7 +36,7 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf /** * {@inheritdoc} */ - public function collect(Request $request, Response $response, \Exception $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null) { if (null !== $this->kernel) { $startTime = $this->kernel->getStartTime(); @@ -43,7 +45,7 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf } $this->data = [ - 'token' => $response->headers->get('X-Debug-Token'), + 'token' => $request->attributes->get('_stopwatch_token'), 'start_time' => $startTime * 1000, 'events' => [], 'stopwatch_installed' => class_exists(Stopwatch::class, false), @@ -74,8 +76,6 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf } /** - * Sets the request events. - * * @param StopwatchEvent[] $events The request events */ public function setEvents(array $events) @@ -88,21 +88,17 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf } /** - * Gets the request events. - * - * @return StopwatchEvent[] The request events + * @return StopwatchEvent[] */ - public function getEvents() + public function getEvents(): array { return $this->data['events']; } /** * Gets the request elapsed time. - * - * @return float The elapsed time */ - public function getDuration() + public function getDuration(): float { if (!isset($this->data['events']['__section__'])) { return 0; @@ -117,10 +113,8 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf * Gets the initialization time. * * This is the time spent until the beginning of the request handling. - * - * @return float The elapsed time */ - public function getInitTime() + public function getInitTime(): float { if (!isset($this->data['events']['__section__'])) { return 0; @@ -129,20 +123,12 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf return $this->data['events']['__section__']->getOrigin() - $this->getStartTime(); } - /** - * Gets the request time. - * - * @return float - */ - public function getStartTime() + public function getStartTime(): float { return $this->data['start_time']; } - /** - * @return bool whether or not the stopwatch component is installed - */ - public function isStopwatchInstalled() + public function isStopwatchInstalled(): bool { return $this->data['stopwatch_installed']; } @@ -150,7 +136,7 @@ class TimeDataCollector extends DataCollector implements LateDataCollectorInterf /** * {@inheritdoc} */ - public function getName() + public function getName(): string { return 'time'; } diff --git a/lib/symfony/http-kernel/DataCollector/Util/ValueExporter.php b/lib/symfony/http-kernel/DataCollector/Util/ValueExporter.php deleted file mode 100644 index f84aeeae59..0000000000 --- a/lib/symfony/http-kernel/DataCollector/Util/ValueExporter.php +++ /dev/null @@ -1,99 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\DataCollector\Util; - -@trigger_error('The '.__NAMESPACE__.'\ValueExporter class is deprecated since Symfony 3.2 and will be removed in 4.0. Use the VarDumper component instead.', \E_USER_DEPRECATED); - -/** - * @author Bernhard Schussek - * - * @deprecated since version 3.2, to be removed in 4.0. Use the VarDumper component instead. - */ -class ValueExporter -{ - /** - * Converts a PHP value to a string. - * - * @param mixed $value The PHP value - * @param int $depth Only for internal usage - * @param bool $deep Only for internal usage - * - * @return string The string representation of the given value - */ - public function exportValue($value, $depth = 1, $deep = false) - { - if ($value instanceof \__PHP_Incomplete_Class) { - return sprintf('__PHP_Incomplete_Class(%s)', $this->getClassNameFromIncomplete($value)); - } - - if (\is_object($value)) { - if ($value instanceof \DateTimeInterface) { - return sprintf('Object(%s) - %s', \get_class($value), $value->format(\DateTime::ATOM)); - } - - return sprintf('Object(%s)', \get_class($value)); - } - - if (\is_array($value)) { - if (empty($value)) { - return '[]'; - } - - $indent = str_repeat(' ', $depth); - - $a = []; - foreach ($value as $k => $v) { - if (\is_array($v)) { - $deep = true; - } - $a[] = sprintf('%s => %s', $k, $this->exportValue($v, $depth + 1, $deep)); - } - - if ($deep) { - return sprintf("[\n%s%s\n%s]", $indent, implode(sprintf(", \n%s", $indent), $a), str_repeat(' ', $depth - 1)); - } - - $s = sprintf('[%s]', implode(', ', $a)); - - if (80 > \strlen($s)) { - return $s; - } - - return sprintf("[\n%s%s\n]", $indent, implode(sprintf(",\n%s", $indent), $a)); - } - - if (\is_resource($value)) { - return sprintf('Resource(%s#%d)', get_resource_type($value), $value); - } - - if (null === $value) { - return 'null'; - } - - if (false === $value) { - return 'false'; - } - - if (true === $value) { - return 'true'; - } - - return (string) $value; - } - - private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value) - { - $array = new \ArrayObject($value); - - return $array['__PHP_Incomplete_Class_Name']; - } -} diff --git a/lib/symfony/http-kernel/Debug/FileLinkFormatter.php b/lib/symfony/http-kernel/Debug/FileLinkFormatter.php index 63ae6e6aab..9ac688cc56 100644 --- a/lib/symfony/http-kernel/Debug/FileLinkFormatter.php +++ b/lib/symfony/http-kernel/Debug/FileLinkFormatter.php @@ -13,28 +13,39 @@ namespace Symfony\Component\HttpKernel\Debug; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Routing\Exception\ExceptionInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; /** * Formats debug file links. * * @author Jérémy Romey + * + * @final */ -class FileLinkFormatter implements \Serializable +class FileLinkFormatter { + private const FORMATS = [ + 'textmate' => 'txmt://open?url=file://%f&line=%l', + 'macvim' => 'mvim://open?url=file://%f&line=%l', + 'emacs' => 'emacs://open?url=file://%f&line=%l', + 'sublime' => 'subl://open?url=file://%f&line=%l', + 'phpstorm' => 'phpstorm://open?file=%f&line=%l', + 'atom' => 'atom://core/open/file?filename=%f&line=%l', + 'vscode' => 'vscode://file/%f:%l', + ]; + private $fileLinkFormat; private $requestStack; private $baseDir; private $urlFormat; /** - * @param string|\Closure $urlFormat the URL format, or a closure that returns it on-demand + * @param string|array|null $fileLinkFormat + * @param string|\Closure $urlFormat the URL format, or a closure that returns it on-demand */ - public function __construct($fileLinkFormat = null, RequestStack $requestStack = null, $baseDir = null, $urlFormat = null) + public function __construct($fileLinkFormat = null, RequestStack $requestStack = null, string $baseDir = null, $urlFormat = null) { - $fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); - if ($fileLinkFormat && !\is_array($fileLinkFormat)) { + if (!\is_array($fileLinkFormat) && $fileLinkFormat = (self::FORMATS[$fileLinkFormat] ?? $fileLinkFormat) ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format')) { $i = strpos($f = $fileLinkFormat, '&', max(strrpos($f, '%f'), strrpos($f, '%l'))) ?: \strlen($f); $fileLinkFormat = [substr($f, 0, $i)] + preg_split('/&([^>]++)>/', substr($f, $i), -1, \PREG_SPLIT_DELIM_CAPTURE); } @@ -45,11 +56,11 @@ class FileLinkFormatter implements \Serializable $this->urlFormat = $urlFormat; } - public function format($file, $line) + public function format(string $file, int $line) { if ($fmt = $this->getFileLinkFormat()) { for ($i = 1; isset($fmt[$i]); ++$i) { - if (0 === strpos($file, $k = $fmt[$i++])) { + if (str_starts_with($file, $k = $fmt[$i++])) { $file = substr_replace($file, $fmt[$i], 0, \strlen($k)); break; } @@ -64,31 +75,21 @@ class FileLinkFormatter implements \Serializable /** * @internal */ - public function serialize() + public function __sleep(): array { - return serialize($this->getFileLinkFormat()); + $this->fileLinkFormat = $this->getFileLinkFormat(); + + return ['fileLinkFormat']; } /** * @internal */ - public function unserialize($serialized) - { - if (\PHP_VERSION_ID >= 70000) { - $this->fileLinkFormat = unserialize($serialized, ['allowed_classes' => false]); - } else { - $this->fileLinkFormat = unserialize($serialized); - } - } - - /** - * @internal - */ - public static function generateUrlFormat(UrlGeneratorInterface $router, $routeName, $queryString) + public static function generateUrlFormat(UrlGeneratorInterface $router, string $routeName, string $queryString): ?string { try { return $router->generate($routeName).$queryString; - } catch (ExceptionInterface $e) { + } catch (\Throwable $e) { return null; } } @@ -98,13 +99,11 @@ class FileLinkFormatter implements \Serializable if ($this->fileLinkFormat) { return $this->fileLinkFormat; } - if ($this->requestStack && $this->baseDir && $this->urlFormat) { - $request = $this->requestStack->getMasterRequest(); - if ($request instanceof Request) { - if ($this->urlFormat instanceof \Closure && !$this->urlFormat = \call_user_func($this->urlFormat)) { - return null; - } + if ($this->requestStack && $this->baseDir && $this->urlFormat) { + $request = $this->requestStack->getMainRequest(); + + if ($request instanceof Request && (!$this->urlFormat instanceof \Closure || $this->urlFormat = ($this->urlFormat)())) { return [ $request->getSchemeAndHttpHost().$this->urlFormat, $this->baseDir.\DIRECTORY_SEPARATOR, '', diff --git a/lib/symfony/http-kernel/Debug/TraceableEventDispatcher.php b/lib/symfony/http-kernel/Debug/TraceableEventDispatcher.php index c265b6010d..fd1cf9e584 100644 --- a/lib/symfony/http-kernel/Debug/TraceableEventDispatcher.php +++ b/lib/symfony/http-kernel/Debug/TraceableEventDispatcher.php @@ -12,7 +12,6 @@ namespace Symfony\Component\HttpKernel\Debug; use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher as BaseTraceableEventDispatcher; -use Symfony\Component\EventDispatcher\Event; use Symfony\Component\HttpKernel\KernelEvents; /** @@ -27,10 +26,11 @@ class TraceableEventDispatcher extends BaseTraceableEventDispatcher /** * {@inheritdoc} */ - protected function preDispatch($eventName, Event $event) + protected function beforeDispatch(string $eventName, object $event) { switch ($eventName) { case KernelEvents::REQUEST: + $event->getRequest()->attributes->set('_stopwatch_token', substr(hash('sha256', uniqid(mt_rand(), true)), 0, 6)); $this->stopwatch->openSection(); break; case KernelEvents::VIEW: @@ -41,8 +41,8 @@ class TraceableEventDispatcher extends BaseTraceableEventDispatcher } break; case KernelEvents::TERMINATE: - $token = $event->getResponse()->headers->get('X-Debug-Token'); - if (null === $token) { + $sectionId = $event->getRequest()->attributes->get('_stopwatch_token'); + if (null === $sectionId) { break; } // There is a very special case when using built-in AppCache class as kernel wrapper, in the case @@ -51,7 +51,7 @@ class TraceableEventDispatcher extends BaseTraceableEventDispatcher // is equal to the [A] debug token. Trying to reopen section with the [B] token throws an exception // which must be caught. try { - $this->stopwatch->openSection($token); + $this->stopwatch->openSection($sectionId); } catch (\LogicException $e) { } break; @@ -61,28 +61,28 @@ class TraceableEventDispatcher extends BaseTraceableEventDispatcher /** * {@inheritdoc} */ - protected function postDispatch($eventName, Event $event) + protected function afterDispatch(string $eventName, object $event) { switch ($eventName) { case KernelEvents::CONTROLLER_ARGUMENTS: $this->stopwatch->start('controller', 'section'); break; case KernelEvents::RESPONSE: - $token = $event->getResponse()->headers->get('X-Debug-Token'); - if (null === $token) { + $sectionId = $event->getRequest()->attributes->get('_stopwatch_token'); + if (null === $sectionId) { break; } - $this->stopwatch->stopSection($token); + $this->stopwatch->stopSection($sectionId); break; case KernelEvents::TERMINATE: // In the special case described in the `preDispatch` method above, the `$token` section // does not exist, then closing it throws an exception which must be caught. - $token = $event->getResponse()->headers->get('X-Debug-Token'); - if (null === $token) { + $sectionId = $event->getRequest()->attributes->get('_stopwatch_token'); + if (null === $sectionId) { break; } try { - $this->stopwatch->stopSection($token); + $this->stopwatch->stopSection($sectionId); } catch (\LogicException $e) { } break; diff --git a/lib/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php b/lib/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php index b59949379d..4bb60b41f7 100644 --- a/lib/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php +++ b/lib/symfony/http-kernel/DependencyInjection/AddAnnotatedClassesToCachePass.php @@ -12,9 +12,10 @@ namespace Symfony\Component\HttpKernel\DependencyInjection; use Composer\Autoload\ClassLoader; -use Symfony\Component\Debug\DebugClassLoader; +use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\ErrorHandler\DebugClassLoader; use Symfony\Component\HttpKernel\Kernel; /** @@ -36,23 +37,17 @@ class AddAnnotatedClassesToCachePass implements CompilerPassInterface */ public function process(ContainerBuilder $container) { - $classes = []; $annotatedClasses = []; foreach ($container->getExtensions() as $extension) { if ($extension instanceof Extension) { - if (\PHP_VERSION_ID < 70000) { - $classes = array_merge($classes, $extension->getClassesToCompile()); - } - $annotatedClasses = array_merge($annotatedClasses, $extension->getAnnotatedClassesToCompile()); + $annotatedClasses[] = $extension->getAnnotatedClassesToCompile(); } } + $annotatedClasses = array_merge($this->kernel->getAnnotatedClassesToCompile(), ...$annotatedClasses); + $existingClasses = $this->getClassesInComposerClassMaps(); - if (\PHP_VERSION_ID < 70000) { - $classes = $container->getParameterBag()->resolveValue($classes); - $this->kernel->setClassCache($this->expandClasses($classes, $existingClasses)); - } $annotatedClasses = $container->getParameterBag()->resolveValue($annotatedClasses); $this->kernel->setAnnotatedClassCache($this->expandClasses($annotatedClasses, $existingClasses)); } @@ -62,16 +57,14 @@ class AddAnnotatedClassesToCachePass implements CompilerPassInterface * * @param array $patterns The class patterns to expand * @param array $classes The existing classes to match against the patterns - * - * @return array A list of classes derived from the patterns */ - private function expandClasses(array $patterns, array $classes) + private function expandClasses(array $patterns, array $classes): array { $expanded = []; // Explicit classes declared in the patterns are returned directly foreach ($patterns as $key => $pattern) { - if ('\\' !== substr($pattern, -1) && false === strpos($pattern, '*')) { + if (!str_ends_with($pattern, '\\') && !str_contains($pattern, '*')) { unset($patterns[$key]); $expanded[] = ltrim($pattern, '\\'); } @@ -91,7 +84,7 @@ class AddAnnotatedClassesToCachePass implements CompilerPassInterface return array_unique($expanded); } - private function getClassesInComposerClassMaps() + private function getClassesInComposerClassMaps(): array { $classes = []; @@ -100,7 +93,7 @@ class AddAnnotatedClassesToCachePass implements CompilerPassInterface continue; } - if ($function[0] instanceof DebugClassLoader) { + if ($function[0] instanceof DebugClassLoader || $function[0] instanceof LegacyDebugClassLoader) { $function = $function[0]->getClassLoader(); } @@ -112,7 +105,7 @@ class AddAnnotatedClassesToCachePass implements CompilerPassInterface return array_keys($classes); } - private function patternsToRegexps($patterns) + private function patternsToRegexps(array $patterns): array { $regexps = []; @@ -134,12 +127,12 @@ class AddAnnotatedClassesToCachePass implements CompilerPassInterface return $regexps; } - private function matchAnyRegexps($class, $regexps) + private function matchAnyRegexps(string $class, array $regexps): bool { - $isTest = false !== strpos($class, 'Test'); + $isTest = str_contains($class, 'Test'); foreach ($regexps as $regex) { - if ($isTest && false === strpos($regex, 'Test')) { + if ($isTest && !str_contains($regex, 'Test')) { continue; } diff --git a/lib/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php b/lib/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php deleted file mode 100644 index 8b3dcb7f61..0000000000 --- a/lib/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\DependencyInjection; - -@trigger_error('The '.__NAMESPACE__.'\AddClassesToCachePass class is deprecated since Symfony 3.3 and will be removed in 4.0.', \E_USER_DEPRECATED); - -/** - * Sets the classes to compile in the cache for the container. - * - * @author Fabien Potencier - * - * @deprecated since version 3.3, to be removed in 4.0. - */ -class AddClassesToCachePass extends AddAnnotatedClassesToCachePass -{ -} diff --git a/lib/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php b/lib/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php index 343e217b96..d925ed6b0e 100644 --- a/lib/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php +++ b/lib/symfony/http-kernel/DependencyInjection/ControllerArgumentValueResolverPass.php @@ -15,6 +15,9 @@ use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver; +use Symfony\Component\Stopwatch\Stopwatch; /** * Gathers and configures the argument value resolvers. @@ -27,11 +30,17 @@ class ControllerArgumentValueResolverPass implements CompilerPassInterface private $argumentResolverService; private $argumentValueResolverTag; + private $traceableResolverStopwatch; - public function __construct($argumentResolverService = 'argument_resolver', $argumentValueResolverTag = 'controller.argument_value_resolver') + public function __construct(string $argumentResolverService = 'argument_resolver', string $argumentValueResolverTag = 'controller.argument_value_resolver', string $traceableResolverStopwatch = 'debug.stopwatch') { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + $this->argumentResolverService = $argumentResolverService; $this->argumentValueResolverTag = $argumentValueResolverTag; + $this->traceableResolverStopwatch = $traceableResolverStopwatch; } public function process(ContainerBuilder $container) @@ -40,9 +49,20 @@ class ControllerArgumentValueResolverPass implements CompilerPassInterface return; } + $resolvers = $this->findAndSortTaggedServices($this->argumentValueResolverTag, $container); + + if ($container->getParameter('kernel.debug') && class_exists(Stopwatch::class) && $container->has($this->traceableResolverStopwatch)) { + foreach ($resolvers as $resolverReference) { + $id = (string) $resolverReference; + $container->register("debug.$id", TraceableValueResolver::class) + ->setDecoratedService($id) + ->setArguments([new Reference("debug.$id.inner"), new Reference($this->traceableResolverStopwatch)]); + } + } + $container ->getDefinition($this->argumentResolverService) - ->replaceArgument(1, new IteratorArgument($this->findAndSortTaggedServices($this->argumentValueResolverTag, $container))) + ->replaceArgument(1, new IteratorArgument($resolvers)) ; } } diff --git a/lib/symfony/http-kernel/DependencyInjection/Extension.php b/lib/symfony/http-kernel/DependencyInjection/Extension.php index 55a4759dbf..4090fd822f 100644 --- a/lib/symfony/http-kernel/DependencyInjection/Extension.php +++ b/lib/symfony/http-kernel/DependencyInjection/Extension.php @@ -20,51 +20,18 @@ use Symfony\Component\DependencyInjection\Extension\Extension as BaseExtension; */ abstract class Extension extends BaseExtension { - private $classes = []; private $annotatedClasses = []; - /** - * Gets the classes to cache. - * - * @return array An array of classes - * - * @deprecated since version 3.3, to be removed in 4.0. - */ - public function getClassesToCompile() - { - if (\PHP_VERSION_ID >= 70000) { - @trigger_error(__METHOD__.'() is deprecated since Symfony 3.3, to be removed in 4.0.', \E_USER_DEPRECATED); - } - - return $this->classes; - } - /** * Gets the annotated classes to cache. * - * @return array An array of classes + * @return array */ public function getAnnotatedClassesToCompile() { return $this->annotatedClasses; } - /** - * Adds classes to the class cache. - * - * @param array $classes An array of class patterns - * - * @deprecated since version 3.3, to be removed in 4.0. - */ - public function addClassesToCompile(array $classes) - { - if (\PHP_VERSION_ID >= 70000) { - @trigger_error(__METHOD__.'() is deprecated since Symfony 3.3, to be removed in 4.0.', \E_USER_DEPRECATED); - } - - $this->classes = array_merge($this->classes, $classes); - } - /** * Adds annotated classes to the class cache. * diff --git a/lib/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php b/lib/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php index a15fbaa172..f26baeca9d 100644 --- a/lib/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php +++ b/lib/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php @@ -28,12 +28,12 @@ class FragmentRendererPass implements CompilerPassInterface private $handlerService; private $rendererTag; - /** - * @param string $handlerService Service name of the fragment handler in the container - * @param string $rendererTag Tag name used for fragments - */ - public function __construct($handlerService = 'fragment.handler', $rendererTag = 'kernel.fragment_renderer') + public function __construct(string $handlerService = 'fragment.handler', string $rendererTag = 'kernel.fragment_renderer') { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + $this->handlerService = $handlerService; $this->rendererTag = $rendererTag; } diff --git a/lib/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php b/lib/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php index 4deaaf0d1a..f253287045 100644 --- a/lib/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php +++ b/lib/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php @@ -23,52 +23,24 @@ use Symfony\Component\HttpKernel\Fragment\FragmentHandler; class LazyLoadingFragmentHandler extends FragmentHandler { private $container; - /** - * @deprecated since version 3.3, to be removed in 4.0 - */ - private $rendererIds = []; - private $initialized = []; /** - * @param ContainerInterface $container A container - * @param RequestStack $requestStack The Request stack that controls the lifecycle of requests - * @param bool $debug Whether the debug mode is enabled or not + * @var array */ - public function __construct(ContainerInterface $container, RequestStack $requestStack, $debug = false) + private $initialized = []; + + public function __construct(ContainerInterface $container, RequestStack $requestStack, bool $debug = false) { $this->container = $container; parent::__construct($requestStack, [], $debug); } - /** - * Adds a service as a fragment renderer. - * - * @param string $name The service name - * @param string $renderer The render service id - * - * @deprecated since version 3.3, to be removed in 4.0 - */ - public function addRendererService($name, $renderer) - { - @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); - - $this->rendererIds[$name] = $renderer; - } - /** * {@inheritdoc} */ - public function render($uri, $renderer = 'inline', array $options = []) + public function render($uri, string $renderer = 'inline', array $options = []) { - // BC 3.x, to be removed in 4.0 - if (isset($this->rendererIds[$renderer])) { - $this->addRenderer($this->container->get($this->rendererIds[$renderer])); - unset($this->rendererIds[$renderer]); - - return parent::render($uri, $renderer, $options); - } - if (!isset($this->initialized[$renderer]) && $this->container->has($renderer)) { $this->addRenderer($this->container->get($renderer)); $this->initialized[$renderer] = true; diff --git a/lib/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php b/lib/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php index 83e1b758de..5f0f0d8dee 100644 --- a/lib/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php +++ b/lib/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php @@ -23,6 +23,9 @@ class MergeExtensionConfigurationPass extends BaseMergeExtensionConfigurationPas { private $extensions; + /** + * @param string[] $extensions + */ public function __construct(array $extensions) { $this->extensions = $extensions; diff --git a/lib/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/lib/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php index c7d3d279a4..8fd1f553e0 100644 --- a/lib/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php +++ b/lib/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpKernel\DependencyInjection; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\DependencyInjection\Attribute\Target; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; @@ -23,6 +24,7 @@ use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\TypedReference; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\SessionInterface; /** * Creates the service-locators required by ServiceValueResolver. @@ -33,22 +35,37 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface { private $resolverServiceId; private $controllerTag; + private $controllerLocator; + private $notTaggedControllerResolverServiceId; - public function __construct($resolverServiceId = 'argument_resolver.service', $controllerTag = 'controller.service_arguments') + public function __construct(string $resolverServiceId = 'argument_resolver.service', string $controllerTag = 'controller.service_arguments', string $controllerLocator = 'argument_resolver.controller_locator', string $notTaggedControllerResolverServiceId = 'argument_resolver.not_tagged_controller') { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + $this->resolverServiceId = $resolverServiceId; $this->controllerTag = $controllerTag; + $this->controllerLocator = $controllerLocator; + $this->notTaggedControllerResolverServiceId = $notTaggedControllerResolverServiceId; } public function process(ContainerBuilder $container) { - if (false === $container->hasDefinition($this->resolverServiceId)) { + if (false === $container->hasDefinition($this->resolverServiceId) && false === $container->hasDefinition($this->notTaggedControllerResolverServiceId)) { return; } $parameterBag = $container->getParameterBag(); $controllers = []; + $publicAliases = []; + foreach ($container->getAliases() as $id => $alias) { + if ($alias->isPublic() && !$alias->isPrivate()) { + $publicAliases[(string) $alias][] = $id; + } + } + foreach ($container->findTaggedServiceIds($this->controllerTag, true) as $id => $tags) { $def = $container->getDefinition($id); $def->setPublic(true); @@ -95,7 +112,7 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface if (!isset($methods[$action = strtolower($attributes['action'])])) { throw new InvalidArgumentException(sprintf('Invalid "action" attribute on tag "%s" for service "%s": no public "%s()" method found on class "%s".', $this->controllerTag, $id, $attributes['action'], $class)); } - list($r, $parameters) = $methods[$action]; + [$r, $parameters] = $methods[$action]; $found = false; foreach ($parameters as $p) { @@ -113,43 +130,51 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface } } - foreach ($methods as list($r, $parameters)) { + foreach ($methods as [$r, $parameters]) { /** @var \ReflectionMethod $r */ // create a per-method map of argument-names to service/type-references $args = []; foreach ($parameters as $p) { /** @var \ReflectionParameter $p */ - $type = $target = ProxyHelper::getTypeHint($r, $p, true); + $type = ltrim($target = (string) ProxyHelper::getTypeHint($r, $p), '\\'); $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; if (isset($arguments[$r->name][$p->name])) { $target = $arguments[$r->name][$p->name]; if ('?' !== $target[0]) { - $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + $invalidBehavior = ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE; } elseif ('' === $target = (string) substr($target, 1)) { throw new InvalidArgumentException(sprintf('A "%s" tag must have non-empty "id" attributes for service "%s".', $this->controllerTag, $id)); } elseif ($p->allowsNull() && !$p->isOptional()) { $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; } - } elseif (isset($bindings[$bindingName = '$'.$p->name]) || isset($bindings[$bindingName = $type])) { + } elseif (isset($bindings[$bindingName = $type.' $'.$name = Target::parseName($p)]) || isset($bindings[$bindingName = '$'.$name]) || isset($bindings[$bindingName = $type])) { $binding = $bindings[$bindingName]; - list($bindingValue, $bindingId) = $binding->getValues(); + [$bindingValue, $bindingId, , $bindingType, $bindingFile] = $binding->getValues(); + $binding->setValues([$bindingValue, $bindingId, true, $bindingType, $bindingFile]); if (!$bindingValue instanceof Reference) { - continue; + $args[$p->name] = new Reference('.value.'.$container->hash($bindingValue)); + $container->register((string) $args[$p->name], 'mixed') + ->setFactory('current') + ->addArgument([$bindingValue]); + } else { + $args[$p->name] = $bindingValue; } - $binding->setValues([$bindingValue, $bindingId, true]); - $args[$p->name] = $bindingValue; - continue; - } elseif (!$type || !$autowire) { + } elseif (!$type || !$autowire || '\\' !== $target[0]) { continue; + } elseif (is_subclass_of($type, \UnitEnum::class)) { + // do not attempt to register enum typed arguments if not already present in bindings + continue; + } elseif (!$p->allowsNull()) { + $invalidBehavior = ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE; } - if (Request::class === $type) { + if (Request::class === $type || SessionInterface::class === $type) { continue; } @@ -161,19 +186,38 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface $message .= ' Did you forget to add a use statement?'; } - throw new InvalidArgumentException($message); - } + $container->register($erroredId = '.errored.'.$container->hash($message), $type) + ->addError($message); - $args[$p->name] = $type ? new TypedReference($target, $type, $r->class, $invalidBehavior) : new Reference($target, $invalidBehavior); + $args[$p->name] = new Reference($erroredId, ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE); + } else { + $target = ltrim($target, '\\'); + $args[$p->name] = $type ? new TypedReference($target, $type, $invalidBehavior, Target::parseName($p)) : new Reference($target, $invalidBehavior); + } } // register the maps as a per-method service-locators if ($args) { - $controllers[$id.':'.$r->name] = ServiceLocatorTagPass::register($container, $args); + $controllers[$id.'::'.$r->name] = ServiceLocatorTagPass::register($container, $args); + + foreach ($publicAliases[$id] ?? [] as $alias) { + $controllers[$alias.'::'.$r->name] = clone $controllers[$id.'::'.$r->name]; + } } } } - $container->getDefinition($this->resolverServiceId) - ->replaceArgument(0, ServiceLocatorTagPass::register($container, $controllers)); + $controllerLocatorRef = ServiceLocatorTagPass::register($container, $controllers); + + if ($container->hasDefinition($this->resolverServiceId)) { + $container->getDefinition($this->resolverServiceId) + ->replaceArgument(0, $controllerLocatorRef); + } + + if ($container->hasDefinition($this->notTaggedControllerResolverServiceId)) { + $container->getDefinition($this->notTaggedControllerResolverServiceId) + ->replaceArgument(0, $controllerLocatorRef); + } + + $container->setAlias($this->controllerLocator, (string) $controllerLocatorRef); } } diff --git a/lib/symfony/http-kernel/DependencyInjection/RegisterLocaleAwareServicesPass.php b/lib/symfony/http-kernel/DependencyInjection/RegisterLocaleAwareServicesPass.php new file mode 100644 index 0000000000..f0b801b8d6 --- /dev/null +++ b/lib/symfony/http-kernel/DependencyInjection/RegisterLocaleAwareServicesPass.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Register all services that have the "kernel.locale_aware" tag into the listener. + * + * @author Pierre Bobiet + */ +class RegisterLocaleAwareServicesPass implements CompilerPassInterface +{ + private $listenerServiceId; + private $localeAwareTag; + + public function __construct(string $listenerServiceId = 'locale_aware_listener', string $localeAwareTag = 'kernel.locale_aware') + { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->listenerServiceId = $listenerServiceId; + $this->localeAwareTag = $localeAwareTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->listenerServiceId)) { + return; + } + + $services = []; + + foreach ($container->findTaggedServiceIds($this->localeAwareTag) as $id => $tags) { + $services[] = new Reference($id); + } + + if (!$services) { + $container->removeDefinition($this->listenerServiceId); + + return; + } + + $container + ->getDefinition($this->listenerServiceId) + ->setArgument(0, new IteratorArgument($services)) + ; + } +} diff --git a/lib/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php b/lib/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php index e230a67ea6..2d077a0cb5 100644 --- a/lib/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php +++ b/lib/symfony/http-kernel/DependencyInjection/RemoveEmptyControllerArgumentLocatorsPass.php @@ -21,21 +21,20 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; */ class RemoveEmptyControllerArgumentLocatorsPass implements CompilerPassInterface { - private $resolverServiceId; + private $controllerLocator; - public function __construct($resolverServiceId = 'argument_resolver.service') + public function __construct(string $controllerLocator = 'argument_resolver.controller_locator') { - $this->resolverServiceId = $resolverServiceId; + if (0 < \func_num_args()) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + + $this->controllerLocator = $controllerLocator; } public function process(ContainerBuilder $container) { - if (false === $container->hasDefinition($this->resolverServiceId)) { - return; - } - - $serviceResolver = $container->getDefinition($this->resolverServiceId); - $controllerLocator = $container->getDefinition((string) $serviceResolver->getArgument(0)); + $controllerLocator = $container->findDefinition($this->controllerLocator); $controllers = $controllerLocator->getArgument(0); foreach ($controllers as $controller => $argumentRef) { @@ -47,19 +46,23 @@ class RemoveEmptyControllerArgumentLocatorsPass implements CompilerPassInterface } else { // any methods listed for call-at-instantiation cannot be actions $reason = false; - $action = substr(strrchr($controller, ':'), 1); - $id = substr($controller, 0, -1 - \strlen($action)); + [$id, $action] = explode('::', $controller); + + if ($container->hasAlias($id)) { + continue; + } + $controllerDef = $container->getDefinition($id); - foreach ($controllerDef->getMethodCalls() as list($method)) { + foreach ($controllerDef->getMethodCalls() as [$method]) { if (0 === strcasecmp($action, $method)) { $reason = sprintf('Removing method "%s" of service "%s" from controller candidates: the method is called at instantiation, thus cannot be an action.', $action, $id); break; } } if (!$reason) { - if ($controllerDef->getClass() === $id) { - $controllers[$id.'::'.$action] = $argumentRef; - } + // see Symfony\Component\HttpKernel\Controller\ContainerControllerResolver + $controllers[$id.':'.$action] = $argumentRef; + if ('__invoke' === $action) { $controllers[$id] = $argumentRef; } diff --git a/lib/symfony/http-kernel/DependencyInjection/ResettableServicePass.php b/lib/symfony/http-kernel/DependencyInjection/ResettableServicePass.php index 51a518dc28..2e4cd69270 100644 --- a/lib/symfony/http-kernel/DependencyInjection/ResettableServicePass.php +++ b/lib/symfony/http-kernel/DependencyInjection/ResettableServicePass.php @@ -25,8 +25,12 @@ class ResettableServicePass implements CompilerPassInterface { private $tagName; - public function __construct($tagName = 'kernel.reset') + public function __construct(string $tagName = 'kernel.reset') { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + $this->tagName = $tagName; } @@ -43,16 +47,25 @@ class ResettableServicePass implements CompilerPassInterface foreach ($container->findTaggedServiceIds($this->tagName, true) as $id => $tags) { $services[$id] = new Reference($id, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE); - $attributes = $tags[0]; - if (!isset($attributes['method'])) { - throw new RuntimeException(sprintf('Tag "%s" requires the "method" attribute to be set.', $this->tagName)); + foreach ($tags as $attributes) { + if (!isset($attributes['method'])) { + throw new RuntimeException(sprintf('Tag "%s" requires the "method" attribute to be set.', $this->tagName)); + } + + if (!isset($methods[$id])) { + $methods[$id] = []; + } + + if ('ignore' === ($attributes['on_invalid'] ?? null)) { + $attributes['method'] = '?'.$attributes['method']; + } + + $methods[$id][] = $attributes['method']; } - - $methods[$id] = $attributes['method']; } - if (empty($services)) { + if (!$services) { $container->removeAlias('services_resetter'); $container->removeDefinition('services_resetter'); diff --git a/lib/symfony/http-kernel/DependencyInjection/ServicesResetter.php b/lib/symfony/http-kernel/DependencyInjection/ServicesResetter.php index b82d2fef3c..0063deca36 100644 --- a/lib/symfony/http-kernel/DependencyInjection/ServicesResetter.php +++ b/lib/symfony/http-kernel/DependencyInjection/ServicesResetter.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpKernel\DependencyInjection; +use Symfony\Contracts\Service\ResetInterface; + /** * Resets provided services. * @@ -19,11 +21,15 @@ namespace Symfony\Component\HttpKernel\DependencyInjection; * * @internal */ -class ServicesResetter +class ServicesResetter implements ResetInterface { private $resettableServices; private $resetMethods; + /** + * @param \Traversable $resettableServices + * @param array $resetMethods + */ public function __construct(\Traversable $resettableServices, array $resetMethods) { $this->resettableServices = $resettableServices; @@ -33,7 +39,13 @@ class ServicesResetter public function reset() { foreach ($this->resettableServices as $id => $service) { - $service->{$this->resetMethods[$id]}(); + foreach ((array) $this->resetMethods[$id] as $resetMethod) { + if ('?' === $resetMethod[0] && !method_exists($service, $resetMethod = substr($resetMethod, 1))) { + continue; + } + + $service->$resetMethod(); + } } } } diff --git a/lib/symfony/http-kernel/Event/ControllerArgumentsEvent.php b/lib/symfony/http-kernel/Event/ControllerArgumentsEvent.php new file mode 100644 index 0000000000..d075ee90bd --- /dev/null +++ b/lib/symfony/http-kernel/Event/ControllerArgumentsEvent.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Allows filtering of controller arguments. + * + * You can call getController() to retrieve the controller and getArguments + * to retrieve the current arguments. With setArguments() you can replace + * arguments that are used to call the controller. + * + * Arguments set in the event must be compatible with the signature of the + * controller. + * + * @author Christophe Coevoet + */ +final class ControllerArgumentsEvent extends KernelEvent +{ + private $controller; + private $arguments; + + public function __construct(HttpKernelInterface $kernel, callable $controller, array $arguments, Request $request, ?int $requestType) + { + parent::__construct($kernel, $request, $requestType); + + $this->controller = $controller; + $this->arguments = $arguments; + } + + public function getController(): callable + { + return $this->controller; + } + + public function setController(callable $controller) + { + $this->controller = $controller; + } + + public function getArguments(): array + { + return $this->arguments; + } + + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + } +} diff --git a/lib/symfony/http-kernel/Event/ControllerEvent.php b/lib/symfony/http-kernel/Event/ControllerEvent.php new file mode 100644 index 0000000000..da88800e20 --- /dev/null +++ b/lib/symfony/http-kernel/Event/ControllerEvent.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Allows filtering of a controller callable. + * + * You can call getController() to retrieve the current controller. With + * setController() you can set a new controller that is used in the processing + * of the request. + * + * Controllers should be callables. + * + * @author Bernhard Schussek + */ +final class ControllerEvent extends KernelEvent +{ + private $controller; + + public function __construct(HttpKernelInterface $kernel, callable $controller, Request $request, ?int $requestType) + { + parent::__construct($kernel, $request, $requestType); + + $this->setController($controller); + } + + public function getController(): callable + { + return $this->controller; + } + + public function setController(callable $controller): void + { + $this->controller = $controller; + } +} diff --git a/lib/symfony/http-kernel/Event/ExceptionEvent.php b/lib/symfony/http-kernel/Event/ExceptionEvent.php new file mode 100644 index 0000000000..a18fbd31f4 --- /dev/null +++ b/lib/symfony/http-kernel/Event/ExceptionEvent.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Allows to create a response for a thrown exception. + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * You can also call setThrowable() to replace the thrown exception. This + * exception will be thrown if no response is set during processing of this + * event. + * + * @author Bernhard Schussek + */ +final class ExceptionEvent extends RequestEvent +{ + private $throwable; + + /** + * @var bool + */ + private $allowCustomResponseCode = false; + + public function __construct(HttpKernelInterface $kernel, Request $request, int $requestType, \Throwable $e) + { + parent::__construct($kernel, $request, $requestType); + + $this->setThrowable($e); + } + + public function getThrowable(): \Throwable + { + return $this->throwable; + } + + /** + * Replaces the thrown exception. + * + * This exception will be thrown if no response is set in the event. + */ + public function setThrowable(\Throwable $exception): void + { + $this->throwable = $exception; + } + + /** + * Mark the event as allowing a custom response code. + */ + public function allowCustomResponseCode(): void + { + $this->allowCustomResponseCode = true; + } + + /** + * Returns true if the event allows a custom response code. + */ + public function isAllowingCustomResponseCode(): bool + { + return $this->allowCustomResponseCode; + } +} diff --git a/lib/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php b/lib/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php deleted file mode 100644 index 043e0584a8..0000000000 --- a/lib/symfony/http-kernel/Event/FilterControllerArgumentsEvent.php +++ /dev/null @@ -1,52 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\Event; - -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\HttpKernelInterface; - -/** - * Allows filtering of controller arguments. - * - * You can call getController() to retrieve the controller and getArguments - * to retrieve the current arguments. With setArguments() you can replace - * arguments that are used to call the controller. - * - * Arguments set in the event must be compatible with the signature of the - * controller. - * - * @author Christophe Coevoet - */ -class FilterControllerArgumentsEvent extends FilterControllerEvent -{ - private $arguments; - - public function __construct(HttpKernelInterface $kernel, callable $controller, array $arguments, Request $request, $requestType) - { - parent::__construct($kernel, $controller, $request, $requestType); - - $this->arguments = $arguments; - } - - /** - * @return array - */ - public function getArguments() - { - return $this->arguments; - } - - public function setArguments(array $arguments) - { - $this->arguments = $arguments; - } -} diff --git a/lib/symfony/http-kernel/Event/FilterControllerEvent.php b/lib/symfony/http-kernel/Event/FilterControllerEvent.php deleted file mode 100644 index a4cfd62587..0000000000 --- a/lib/symfony/http-kernel/Event/FilterControllerEvent.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\Event; - -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\HttpKernelInterface; - -/** - * Allows filtering of a controller callable. - * - * You can call getController() to retrieve the current controller. With - * setController() you can set a new controller that is used in the processing - * of the request. - * - * Controllers should be callables. - * - * @author Bernhard Schussek - */ -class FilterControllerEvent extends KernelEvent -{ - private $controller; - - public function __construct(HttpKernelInterface $kernel, callable $controller, Request $request, $requestType) - { - parent::__construct($kernel, $request, $requestType); - - $this->setController($controller); - } - - /** - * Returns the current controller. - * - * @return callable - */ - public function getController() - { - return $this->controller; - } - - public function setController(callable $controller) - { - $this->controller = $controller; - } -} diff --git a/lib/symfony/http-kernel/Event/FilterResponseEvent.php b/lib/symfony/http-kernel/Event/FilterResponseEvent.php deleted file mode 100644 index 1b80e34bab..0000000000 --- a/lib/symfony/http-kernel/Event/FilterResponseEvent.php +++ /dev/null @@ -1,55 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\Event; - -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\HttpKernelInterface; - -/** - * Allows to filter a Response object. - * - * You can call getResponse() to retrieve the current response. With - * setResponse() you can set a new response that will be returned to the - * browser. - * - * @author Bernhard Schussek - */ -class FilterResponseEvent extends KernelEvent -{ - private $response; - - public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, Response $response) - { - parent::__construct($kernel, $request, $requestType); - - $this->setResponse($response); - } - - /** - * Returns the current response object. - * - * @return Response - */ - public function getResponse() - { - return $this->response; - } - - /** - * Sets a new response object. - */ - public function setResponse(Response $response) - { - $this->response = $response; - } -} diff --git a/lib/symfony/http-kernel/Event/FinishRequestEvent.php b/lib/symfony/http-kernel/Event/FinishRequestEvent.php index ee724843cd..306a7ad1cd 100644 --- a/lib/symfony/http-kernel/Event/FinishRequestEvent.php +++ b/lib/symfony/http-kernel/Event/FinishRequestEvent.php @@ -16,6 +16,6 @@ namespace Symfony\Component\HttpKernel\Event; * * @author Benjamin Eberlei */ -class FinishRequestEvent extends KernelEvent +final class FinishRequestEvent extends KernelEvent { } diff --git a/lib/symfony/http-kernel/Event/GetResponseEvent.php b/lib/symfony/http-kernel/Event/GetResponseEvent.php deleted file mode 100644 index c25a0f1cf1..0000000000 --- a/lib/symfony/http-kernel/Event/GetResponseEvent.php +++ /dev/null @@ -1,58 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\Event; - -use Symfony\Component\HttpFoundation\Response; - -/** - * Allows to create a response for a request. - * - * Call setResponse() to set the response that will be returned for the - * current request. The propagation of this event is stopped as soon as a - * response is set. - * - * @author Bernhard Schussek - */ -class GetResponseEvent extends KernelEvent -{ - private $response; - - /** - * Returns the response object. - * - * @return Response|null - */ - public function getResponse() - { - return $this->response; - } - - /** - * Sets a response and stops event propagation. - */ - public function setResponse(Response $response) - { - $this->response = $response; - - $this->stopPropagation(); - } - - /** - * Returns whether a response was set. - * - * @return bool Whether a response was set - */ - public function hasResponse() - { - return null !== $this->response; - } -} diff --git a/lib/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php b/lib/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php deleted file mode 100644 index d68eaa18c2..0000000000 --- a/lib/symfony/http-kernel/Event/GetResponseForControllerResultEvent.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 Symfony\Component\HttpKernel\Event; - -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\HttpKernelInterface; - -/** - * Allows to create a response for the return value of a controller. - * - * Call setResponse() to set the response that will be returned for the - * current request. The propagation of this event is stopped as soon as a - * response is set. - * - * @author Bernhard Schussek - */ -class GetResponseForControllerResultEvent extends GetResponseEvent -{ - /** - * The return value of the controller. - * - * @var mixed - */ - private $controllerResult; - - public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, $controllerResult) - { - parent::__construct($kernel, $request, $requestType); - - $this->controllerResult = $controllerResult; - } - - /** - * Returns the return value of the controller. - * - * @return mixed The controller return value - */ - public function getControllerResult() - { - return $this->controllerResult; - } - - /** - * Assigns the return value of the controller. - * - * @param mixed $controllerResult The controller return value - */ - public function setControllerResult($controllerResult) - { - $this->controllerResult = $controllerResult; - } -} diff --git a/lib/symfony/http-kernel/Event/GetResponseForExceptionEvent.php b/lib/symfony/http-kernel/Event/GetResponseForExceptionEvent.php deleted file mode 100644 index 6199838fcc..0000000000 --- a/lib/symfony/http-kernel/Event/GetResponseForExceptionEvent.php +++ /dev/null @@ -1,90 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\Event; - -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\HttpKernelInterface; - -/** - * Allows to create a response for a thrown exception. - * - * Call setResponse() to set the response that will be returned for the - * current request. The propagation of this event is stopped as soon as a - * response is set. - * - * You can also call setException() to replace the thrown exception. This - * exception will be thrown if no response is set during processing of this - * event. - * - * @author Bernhard Schussek - */ -class GetResponseForExceptionEvent extends GetResponseEvent -{ - /** - * The exception object. - * - * @var \Exception - */ - private $exception; - - /** - * @var bool - */ - private $allowCustomResponseCode = false; - - public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, \Exception $e) - { - parent::__construct($kernel, $request, $requestType); - - $this->setException($e); - } - - /** - * Returns the thrown exception. - * - * @return \Exception The thrown exception - */ - public function getException() - { - return $this->exception; - } - - /** - * Replaces the thrown exception. - * - * This exception will be thrown if no response is set in the event. - * - * @param \Exception $exception The thrown exception - */ - public function setException(\Exception $exception) - { - $this->exception = $exception; - } - - /** - * Mark the event as allowing a custom response code. - */ - public function allowCustomResponseCode() - { - $this->allowCustomResponseCode = true; - } - - /** - * Returns true if the event allows a custom response code. - * - * @return bool - */ - public function isAllowingCustomResponseCode() - { - return $this->allowCustomResponseCode; - } -} diff --git a/lib/symfony/http-kernel/Event/KernelEvent.php b/lib/symfony/http-kernel/Event/KernelEvent.php index cccf01f3da..d9d425e114 100644 --- a/lib/symfony/http-kernel/Event/KernelEvent.php +++ b/lib/symfony/http-kernel/Event/KernelEvent.php @@ -11,9 +11,9 @@ namespace Symfony\Component\HttpKernel\Event; -use Symfony\Component\EventDispatcher\Event; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Contracts\EventDispatcher\Event; /** * Base class for events thrown in the HttpKernel component. @@ -27,12 +27,10 @@ class KernelEvent extends Event private $requestType; /** - * @param HttpKernelInterface $kernel The kernel in which this event was thrown - * @param Request $request The request the kernel is currently processing - * @param int $requestType The request type the kernel is currently processing; one of - * HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST + * @param int $requestType The request type the kernel is currently processing; one of + * HttpKernelInterface::MAIN_REQUEST or HttpKernelInterface::SUB_REQUEST */ - public function __construct(HttpKernelInterface $kernel, Request $request, $requestType) + public function __construct(HttpKernelInterface $kernel, Request $request, ?int $requestType) { $this->kernel = $kernel; $this->request = $request; @@ -62,7 +60,7 @@ class KernelEvent extends Event /** * Returns the request type the kernel is currently processing. * - * @return int One of HttpKernelInterface::MASTER_REQUEST and + * @return int One of HttpKernelInterface::MAIN_REQUEST and * HttpKernelInterface::SUB_REQUEST */ public function getRequestType() @@ -70,13 +68,25 @@ class KernelEvent extends Event return $this->requestType; } + /** + * Checks if this is the main request. + */ + public function isMainRequest(): bool + { + return HttpKernelInterface::MAIN_REQUEST === $this->requestType; + } + /** * Checks if this is a master request. * - * @return bool True if the request is a master request + * @return bool + * + * @deprecated since symfony/http-kernel 5.3, use isMainRequest() instead */ public function isMasterRequest() { - return HttpKernelInterface::MASTER_REQUEST === $this->requestType; + trigger_deprecation('symfony/http-kernel', '5.3', '"%s()" is deprecated, use "isMainRequest()" instead.', __METHOD__); + + return $this->isMainRequest(); } } diff --git a/lib/symfony/http-kernel/Event/PostResponseEvent.php b/lib/symfony/http-kernel/Event/PostResponseEvent.php deleted file mode 100644 index 0981e64c92..0000000000 --- a/lib/symfony/http-kernel/Event/PostResponseEvent.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\Event; - -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\HttpKernelInterface; - -/** - * Allows to execute logic after a response was sent. - * - * Since it's only triggered on master requests, the `getRequestType()` method - * will always return the value of `HttpKernelInterface::MASTER_REQUEST`. - * - * @author Jordi Boggiano - */ -class PostResponseEvent extends KernelEvent -{ - private $response; - - public function __construct(HttpKernelInterface $kernel, Request $request, Response $response) - { - parent::__construct($kernel, $request, HttpKernelInterface::MASTER_REQUEST); - - $this->response = $response; - } - - /** - * Returns the response for which this event was thrown. - * - * @return Response - */ - public function getResponse() - { - return $this->response; - } -} diff --git a/lib/symfony/http-kernel/Event/RequestEvent.php b/lib/symfony/http-kernel/Event/RequestEvent.php new file mode 100644 index 0000000000..30ffcdcbde --- /dev/null +++ b/lib/symfony/http-kernel/Event/RequestEvent.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 Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Response; + +/** + * Allows to create a response for a request. + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * @author Bernhard Schussek + */ +class RequestEvent extends KernelEvent +{ + private $response; + + /** + * Returns the response object. + * + * @return Response|null + */ + public function getResponse() + { + return $this->response; + } + + /** + * Sets a response and stops event propagation. + */ + public function setResponse(Response $response) + { + $this->response = $response; + + $this->stopPropagation(); + } + + /** + * Returns whether a response was set. + * + * @return bool + */ + public function hasResponse() + { + return null !== $this->response; + } +} diff --git a/lib/symfony/http-kernel/Event/ResponseEvent.php b/lib/symfony/http-kernel/Event/ResponseEvent.php new file mode 100644 index 0000000000..1e56ebea2c --- /dev/null +++ b/lib/symfony/http-kernel/Event/ResponseEvent.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Allows to filter a Response object. + * + * You can call getResponse() to retrieve the current response. With + * setResponse() you can set a new response that will be returned to the + * browser. + * + * @author Bernhard Schussek + */ +final class ResponseEvent extends KernelEvent +{ + private $response; + + public function __construct(HttpKernelInterface $kernel, Request $request, int $requestType, Response $response) + { + parent::__construct($kernel, $request, $requestType); + + $this->setResponse($response); + } + + public function getResponse(): Response + { + return $this->response; + } + + public function setResponse(Response $response): void + { + $this->response = $response; + } +} diff --git a/lib/symfony/http-kernel/Event/TerminateEvent.php b/lib/symfony/http-kernel/Event/TerminateEvent.php new file mode 100644 index 0000000000..014ca535fe --- /dev/null +++ b/lib/symfony/http-kernel/Event/TerminateEvent.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 Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Allows to execute logic after a response was sent. + * + * Since it's only triggered on main requests, the `getRequestType()` method + * will always return the value of `HttpKernelInterface::MAIN_REQUEST`. + * + * @author Jordi Boggiano + */ +final class TerminateEvent extends KernelEvent +{ + private $response; + + public function __construct(HttpKernelInterface $kernel, Request $request, Response $response) + { + parent::__construct($kernel, $request, HttpKernelInterface::MAIN_REQUEST); + + $this->response = $response; + } + + public function getResponse(): Response + { + return $this->response; + } +} diff --git a/lib/symfony/http-kernel/Event/ViewEvent.php b/lib/symfony/http-kernel/Event/ViewEvent.php new file mode 100644 index 0000000000..88211da417 --- /dev/null +++ b/lib/symfony/http-kernel/Event/ViewEvent.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Allows to create a response for the return value of a controller. + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * @author Bernhard Schussek + */ +final class ViewEvent extends RequestEvent +{ + /** + * The return value of the controller. + * + * @var mixed + */ + private $controllerResult; + + public function __construct(HttpKernelInterface $kernel, Request $request, int $requestType, $controllerResult) + { + parent::__construct($kernel, $request, $requestType); + + $this->controllerResult = $controllerResult; + } + + /** + * Returns the return value of the controller. + * + * @return mixed + */ + public function getControllerResult() + { + return $this->controllerResult; + } + + /** + * Assigns the return value of the controller. + * + * @param mixed $controllerResult The controller return value + */ + public function setControllerResult($controllerResult): void + { + $this->controllerResult = $controllerResult; + } +} diff --git a/lib/symfony/http-kernel/EventListener/AbstractSessionListener.php b/lib/symfony/http-kernel/EventListener/AbstractSessionListener.php index 0a6bb4f79a..d0e8f45d03 100644 --- a/lib/symfony/http-kernel/EventListener/AbstractSessionListener.php +++ b/lib/symfony/http-kernel/EventListener/AbstractSessionListener.php @@ -11,82 +11,299 @@ namespace Symfony\Component\HttpKernel\EventListener; +use Psr\Container\ContainerInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\SessionInterface; -use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpFoundation\Session\SessionUtils; use Symfony\Component\HttpKernel\Event\FinishRequestEvent; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\Exception\UnexpectedSessionUsageException; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Contracts\Service\ResetInterface; /** - * Sets the session in the request. + * Sets the session onto the request on the "kernel.request" event and saves + * it on the "kernel.response" event. + * + * In addition, if the session has been started it overrides the Cache-Control + * header in such a way that all caching is disabled in that case. + * If you have a scenario where caching responses with session information in + * them makes sense, you can disable this behaviour by setting the header + * AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER on the response. * * @author Johannes M. Schmitt + * @author Tobias Schultze + * + * @internal */ -abstract class AbstractSessionListener implements EventSubscriberInterface +abstract class AbstractSessionListener implements EventSubscriberInterface, ResetInterface { - private $sessionUsageStack = []; + public const NO_AUTO_CACHE_CONTROL_HEADER = 'Symfony-Session-NoAutoCacheControl'; - public function onKernelRequest(GetResponseEvent $event) + protected $container; + private $sessionUsageStack = []; + private $debug; + + /** + * @var array + */ + private $sessionOptions; + + public function __construct(ContainerInterface $container = null, bool $debug = false, array $sessionOptions = []) { - if (!$event->isMasterRequest()) { + $this->container = $container; + $this->debug = $debug; + $this->sessionOptions = $sessionOptions; + } + + public function onKernelRequest(RequestEvent $event) + { + if (!$event->isMainRequest()) { return; } $request = $event->getRequest(); - $session = $this->getSession(); - $this->sessionUsageStack[] = $session instanceof Session ? $session->getUsageIndex() : null; - if (null === $session || $request->hasSession()) { - return; + if (!$request->hasSession()) { + // This variable prevents calling `$this->getSession()` twice in case the Request (and the below factory) is cloned + $sess = null; + $request->setSessionFactory(function () use (&$sess, $request) { + if (!$sess) { + $sess = $this->getSession(); + + /* + * For supporting sessions in php runtime with runners like roadrunner or swoole, the session + * cookie needs to be read from the cookie bag and set on the session storage. + * + * Do not set it when a native php session is active. + */ + if ($sess && !$sess->isStarted() && \PHP_SESSION_ACTIVE !== session_status()) { + $sessionId = $sess->getId() ?: $request->cookies->get($sess->getName(), ''); + $sess->setId($sessionId); + } + } + + return $sess; + }); } - $request->setSession($session); + $session = $this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : null; + $this->sessionUsageStack[] = $session instanceof Session ? $session->getUsageIndex() : 0; } - public function onKernelResponse(FilterResponseEvent $event) + public function onKernelResponse(ResponseEvent $event) { - if (!$event->isMasterRequest()) { + if (!$event->isMainRequest() || (!$this->container->has('initialized_session') && !$event->getRequest()->hasSession())) { return; } - if (!$session = $event->getRequest()->getSession()) { + $response = $event->getResponse(); + $autoCacheControl = !$response->headers->has(self::NO_AUTO_CACHE_CONTROL_HEADER); + // Always remove the internal header if present + $response->headers->remove(self::NO_AUTO_CACHE_CONTROL_HEADER); + + if (!$session = $this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : ($event->getRequest()->hasSession() ? $event->getRequest()->getSession() : null)) { return; } - if ($session instanceof Session ? $session->getUsageIndex() !== end($this->sessionUsageStack) : $session->isStarted()) { - $event->getResponse() + if ($session->isStarted()) { + /* + * Saves the session, in case it is still open, before sending the response/headers. + * + * This ensures several things in case the developer did not save the session explicitly: + * + * * If a session save handler without locking is used, it ensures the data is available + * on the next request, e.g. after a redirect. PHPs auto-save at script end via + * session_register_shutdown is executed after fastcgi_finish_request. So in this case + * the data could be missing the next request because it might not be saved the moment + * the new request is processed. + * * A locking save handler (e.g. the native 'files') circumvents concurrency problems like + * the one above. But by saving the session before long-running things in the terminate event, + * we ensure the session is not blocked longer than needed. + * * When regenerating the session ID no locking is involved in PHPs session design. See + * https://bugs.php.net/61470 for a discussion. So in this case, the session must + * be saved anyway before sending the headers with the new session ID. Otherwise session + * data could get lost again for concurrent requests with the new ID. One result could be + * that you get logged out after just logging in. + * + * This listener should be executed as one of the last listeners, so that previous listeners + * can still operate on the open session. This prevents the overhead of restarting it. + * Listeners after closing the session can still work with the session as usual because + * Symfonys session implementation starts the session on demand. So writing to it after + * it is saved will just restart it. + */ + $session->save(); + + /* + * For supporting sessions in php runtime with runners like roadrunner or swoole the session + * cookie need to be written on the response object and should not be written by PHP itself. + */ + $sessionName = $session->getName(); + $sessionId = $session->getId(); + $sessionOptions = $this->getSessionOptions($this->sessionOptions); + $sessionCookiePath = $sessionOptions['cookie_path'] ?? '/'; + $sessionCookieDomain = $sessionOptions['cookie_domain'] ?? null; + $sessionCookieSecure = $sessionOptions['cookie_secure'] ?? false; + $sessionCookieHttpOnly = $sessionOptions['cookie_httponly'] ?? true; + $sessionCookieSameSite = $sessionOptions['cookie_samesite'] ?? Cookie::SAMESITE_LAX; + $sessionUseCookies = $sessionOptions['use_cookies'] ?? true; + + SessionUtils::popSessionCookie($sessionName, $sessionId); + + if ($sessionUseCookies) { + $request = $event->getRequest(); + $requestSessionCookieId = $request->cookies->get($sessionName); + + $isSessionEmpty = $session->isEmpty() && empty($_SESSION); // checking $_SESSION to keep compatibility with native sessions + if ($requestSessionCookieId && $isSessionEmpty) { + $response->headers->clearCookie( + $sessionName, + $sessionCookiePath, + $sessionCookieDomain, + $sessionCookieSecure, + $sessionCookieHttpOnly, + $sessionCookieSameSite + ); + } elseif ($sessionId !== $requestSessionCookieId && !$isSessionEmpty) { + $expire = 0; + $lifetime = $sessionOptions['cookie_lifetime'] ?? null; + if ($lifetime) { + $expire = time() + $lifetime; + } + + $response->headers->setCookie( + Cookie::create( + $sessionName, + $sessionId, + $expire, + $sessionCookiePath, + $sessionCookieDomain, + $sessionCookieSecure, + $sessionCookieHttpOnly, + false, + $sessionCookieSameSite + ) + ); + } + } + } + + if ($session instanceof Session ? $session->getUsageIndex() === end($this->sessionUsageStack) : !$session->isStarted()) { + return; + } + + if ($autoCacheControl) { + $response ->setExpires(new \DateTime()) ->setPrivate() ->setMaxAge(0) ->headers->addCacheControlDirective('must-revalidate'); } + + if (!$event->getRequest()->attributes->get('_stateless', false)) { + return; + } + + if ($this->debug) { + throw new UnexpectedSessionUsageException('Session was used while the request was declared stateless.'); + } + + if ($this->container->has('logger')) { + $this->container->get('logger')->warning('Session was used while the request was declared stateless.'); + } } - /** - * @internal - */ public function onFinishRequest(FinishRequestEvent $event) { - if ($event->isMasterRequest()) { + if ($event->isMainRequest()) { array_pop($this->sessionUsageStack); } } - public static function getSubscribedEvents() + public function onSessionUsage(): void + { + if (!$this->debug) { + return; + } + + if ($this->container && $this->container->has('session_collector')) { + $this->container->get('session_collector')(); + } + + if (!$requestStack = $this->container && $this->container->has('request_stack') ? $this->container->get('request_stack') : null) { + return; + } + + $stateless = false; + $clonedRequestStack = clone $requestStack; + while (null !== ($request = $clonedRequestStack->pop()) && !$stateless) { + $stateless = $request->attributes->get('_stateless'); + } + + if (!$stateless) { + return; + } + + if (!$session = $this->container && $this->container->has('initialized_session') ? $this->container->get('initialized_session') : $requestStack->getCurrentRequest()->getSession()) { + return; + } + + if ($session->isStarted()) { + $session->save(); + } + + throw new UnexpectedSessionUsageException('Session was used while the request was declared stateless.'); + } + + public static function getSubscribedEvents(): array { return [ KernelEvents::REQUEST => ['onKernelRequest', 128], - // low priority to come after regular response listeners, same as SaveSessionListener + // low priority to come after regular response listeners, but higher than StreamedResponseListener KernelEvents::RESPONSE => ['onKernelResponse', -1000], KernelEvents::FINISH_REQUEST => ['onFinishRequest'], ]; } + public function reset(): void + { + if (\PHP_SESSION_ACTIVE === session_status()) { + session_abort(); + } + + session_unset(); + $_SESSION = []; + + if (!headers_sent()) { // session id can only be reset when no headers were so we check for headers_sent first + session_id(''); + } + } + /** * Gets the session object. * - * @return SessionInterface|null A SessionInterface instance or null if no session is available + * @return SessionInterface|null */ abstract protected function getSession(); + + private function getSessionOptions(array $sessionOptions): array + { + $mergedSessionOptions = []; + + foreach (session_get_cookie_params() as $key => $value) { + $mergedSessionOptions['cookie_'.$key] = $value; + } + + foreach ($sessionOptions as $key => $value) { + // do the same logic as in the NativeSessionStorage + if ('cookie_secure' === $key && 'auto' === $value) { + continue; + } + $mergedSessionOptions[$key] = $value; + } + + return $mergedSessionOptions; + } } diff --git a/lib/symfony/http-kernel/EventListener/AbstractTestSessionListener.php b/lib/symfony/http-kernel/EventListener/AbstractTestSessionListener.php index 714b315a32..838c2944b4 100644 --- a/lib/symfony/http-kernel/EventListener/AbstractTestSessionListener.php +++ b/lib/symfony/http-kernel/EventListener/AbstractTestSessionListener.php @@ -15,10 +15,12 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\SessionInterface; -use Symfony\Component\HttpKernel\Event\FilterResponseEvent; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; +trigger_deprecation('symfony/http-kernel', '5.4', '"%s" is deprecated use "%s" instead.', AbstractTestSessionListener::class, AbstractSessionListener::class); + /** * TestSessionListener. * @@ -26,20 +28,31 @@ use Symfony\Component\HttpKernel\KernelEvents; * * @author Bulat Shakirzyanov * @author Fabien Potencier + * + * @internal + * + * @deprecated since Symfony 5.4, use AbstractSessionListener instead */ abstract class AbstractTestSessionListener implements EventSubscriberInterface { private $sessionId; + private $sessionOptions; - public function onKernelRequest(GetResponseEvent $event) + public function __construct(array $sessionOptions = []) { - if (!$event->isMasterRequest()) { + $this->sessionOptions = $sessionOptions; + } + + public function onKernelRequest(RequestEvent $event) + { + if (!$event->isMainRequest()) { return; } // bootstrap the session - $session = $this->getSession(); - if (!$session) { + if ($event->getRequest()->hasSession()) { + $session = $event->getRequest()->getSession(); + } elseif (!$session = $this->getSession()) { return; } @@ -52,25 +65,32 @@ abstract class AbstractTestSessionListener implements EventSubscriberInterface } /** - * Checks if session was initialized and saves if current request is master + * Checks if session was initialized and saves if current request is the main request * Runs on 'kernel.response' in test environment. */ - public function onKernelResponse(FilterResponseEvent $event) + public function onKernelResponse(ResponseEvent $event) { - if (!$event->isMasterRequest()) { + if (!$event->isMainRequest()) { return; } - if (!$session = $event->getRequest()->getSession()) { + $request = $event->getRequest(); + if (!$request->hasSession()) { return; } + $session = $request->getSession(); if ($wasStarted = $session->isStarted()) { $session->save(); } if ($session instanceof Session ? !$session->isEmpty() || (null !== $this->sessionId && $session->getId() !== $this->sessionId) : $wasStarted) { - $params = session_get_cookie_params(); + $params = session_get_cookie_params() + ['samesite' => null]; + foreach ($this->sessionOptions as $k => $v) { + if (str_starts_with($k, 'cookie_')) { + $params[substr($k, 7)] = $v; + } + } foreach ($event->getResponse()->headers->getCookies() as $cookie) { if ($session->getName() === $cookie->getName() && $params['path'] === $cookie->getPath() && $params['domain'] == $cookie->getDomain()) { @@ -78,15 +98,15 @@ abstract class AbstractTestSessionListener implements EventSubscriberInterface } } - $event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly'])); + $event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly'], false, $params['samesite'] ?: null)); $this->sessionId = $session->getId(); } } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ - KernelEvents::REQUEST => ['onKernelRequest', 192], + KernelEvents::REQUEST => ['onKernelRequest', 127], // AFTER SessionListener KernelEvents::RESPONSE => ['onKernelResponse', -128], ]; } @@ -94,7 +114,7 @@ abstract class AbstractTestSessionListener implements EventSubscriberInterface /** * Gets the session object. * - * @return SessionInterface|null A SessionInterface instance or null if no session is available + * @return SessionInterface|null */ abstract protected function getSession(); } diff --git a/lib/symfony/http-kernel/EventListener/AddRequestFormatsListener.php b/lib/symfony/http-kernel/EventListener/AddRequestFormatsListener.php index 68d806af01..9e896adb31 100644 --- a/lib/symfony/http-kernel/EventListener/AddRequestFormatsListener.php +++ b/lib/symfony/http-kernel/EventListener/AddRequestFormatsListener.php @@ -12,13 +12,15 @@ namespace Symfony\Component\HttpKernel\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; /** * Adds configured formats to each request. * * @author Gildas Quemener + * + * @final */ class AddRequestFormatsListener implements EventSubscriberInterface { @@ -32,7 +34,7 @@ class AddRequestFormatsListener implements EventSubscriberInterface /** * Adds request formats. */ - public function onKernelRequest(GetResponseEvent $event) + public function onKernelRequest(RequestEvent $event) { $request = $event->getRequest(); foreach ($this->formats as $format => $mimeTypes) { @@ -43,8 +45,8 @@ class AddRequestFormatsListener implements EventSubscriberInterface /** * {@inheritdoc} */ - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { - return [KernelEvents::REQUEST => ['onKernelRequest', 1]]; + return [KernelEvents::REQUEST => ['onKernelRequest', 100]]; } } diff --git a/lib/symfony/http-kernel/EventListener/DebugHandlersListener.php b/lib/symfony/http-kernel/EventListener/DebugHandlersListener.php index 5f530631e6..bd124f94d0 100644 --- a/lib/symfony/http-kernel/EventListener/DebugHandlersListener.php +++ b/lib/symfony/http-kernel/EventListener/DebugHandlersListener.php @@ -15,11 +15,8 @@ use Psr\Log\LoggerInterface; use Symfony\Component\Console\ConsoleEvents; use Symfony\Component\Console\Event\ConsoleEvent; use Symfony\Component\Console\Output\ConsoleOutputInterface; -use Symfony\Component\Debug\ErrorHandler; -use Symfony\Component\Debug\ExceptionHandler; -use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\ErrorHandler\ErrorHandler; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use Symfony\Component\HttpKernel\Event\KernelEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -27,81 +24,97 @@ use Symfony\Component\HttpKernel\KernelEvents; * Configures errors and exceptions handlers. * * @author Nicolas Grekas + * + * @final + * + * @internal since Symfony 5.3 */ class DebugHandlersListener implements EventSubscriberInterface { + private $earlyHandler; private $exceptionHandler; private $logger; + private $deprecationLogger; private $levels; private $throwAt; private $scream; - private $fileLinkFormat; private $scope; private $firstCall = true; private $hasTerminatedWithException; /** - * @param callable|null $exceptionHandler A handler that will be called on Exception - * @param LoggerInterface|null $logger A PSR-3 logger - * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants - * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value - * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged - * @param string|FileLinkFormatter|null $fileLinkFormat The format for links to source files - * @param bool $scope Enables/disables scoping mode + * @param callable|null $exceptionHandler A handler that must support \Throwable instances that will be called on Exception + * @param array|int|null $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants + * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value + * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged + * @param bool $scope Enables/disables scoping mode */ - public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = \E_ALL, $throwAt = \E_ALL, $scream = true, $fileLinkFormat = null, $scope = true) + public function __construct(callable $exceptionHandler = null, LoggerInterface $logger = null, $levels = \E_ALL, ?int $throwAt = \E_ALL, bool $scream = true, $scope = true, $deprecationLogger = null, $fileLinkFormat = null) { + if (!\is_bool($scope)) { + trigger_deprecation('symfony/http-kernel', '5.4', 'Passing a $fileLinkFormat is deprecated.'); + $scope = $deprecationLogger; + $deprecationLogger = $fileLinkFormat; + } + + $handler = set_exception_handler('is_int'); + $this->earlyHandler = \is_array($handler) ? $handler[0] : null; + restore_exception_handler(); + $this->exceptionHandler = $exceptionHandler; $this->logger = $logger; - $this->levels = null === $levels ? \E_ALL : $levels; - $this->throwAt = is_numeric($throwAt) ? (int) $throwAt : (null === $throwAt ? null : ($throwAt ? \E_ALL : null)); - $this->scream = (bool) $scream; - $this->fileLinkFormat = $fileLinkFormat; - $this->scope = (bool) $scope; + $this->levels = $levels ?? \E_ALL; + $this->throwAt = \is_int($throwAt) ? $throwAt : (null === $throwAt ? null : ($throwAt ? \E_ALL : null)); + $this->scream = $scream; + $this->scope = $scope; + $this->deprecationLogger = $deprecationLogger; } /** * Configures the error handler. */ - public function configure(Event $event = null) + public function configure(object $event = null) { if ($event instanceof ConsoleEvent && !\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { return; } - if (!$event instanceof KernelEvent ? !$this->firstCall : !$event->isMasterRequest()) { + if (!$event instanceof KernelEvent ? !$this->firstCall : !$event->isMainRequest()) { return; } $this->firstCall = $this->hasTerminatedWithException = false; - $handler = set_exception_handler('var_dump'); + $handler = set_exception_handler('is_int'); $handler = \is_array($handler) ? $handler[0] : null; restore_exception_handler(); - if ($this->logger || null !== $this->throwAt) { - if ($handler instanceof ErrorHandler) { - if ($this->logger) { - $handler->setDefaultLogger($this->logger, $this->levels); - if (\is_array($this->levels)) { - $levels = 0; - foreach ($this->levels as $type => $log) { - $levels |= $type; - } - } else { - $levels = $this->levels; + if (!$handler instanceof ErrorHandler) { + $handler = $this->earlyHandler; + } + + if ($handler instanceof ErrorHandler) { + if ($this->logger || $this->deprecationLogger) { + $this->setDefaultLoggers($handler); + if (\is_array($this->levels)) { + $levels = 0; + foreach ($this->levels as $type => $log) { + $levels |= $type; } - if ($this->scream) { - $handler->screamAt($levels); - } - if ($this->scope) { - $handler->scopeAt($levels & ~\E_USER_DEPRECATED & ~\E_DEPRECATED); - } else { - $handler->scopeAt(0, true); - } - $this->logger = $this->levels = null; + } else { + $levels = $this->levels; } - if (null !== $this->throwAt) { - $handler->throwAt($this->throwAt, true); + + if ($this->scream) { + $handler->screamAt($levels); } + if ($this->scope) { + $handler->scopeAt($levels & ~\E_USER_DEPRECATED & ~\E_DEPRECATED); + } else { + $handler->scopeAt(0, true); + } + $this->logger = $this->deprecationLogger = $this->levels = null; + } + if (null !== $this->throwAt) { + $handler->throwAt($this->throwAt, true); } } if (!$this->exceptionHandler) { @@ -109,10 +122,11 @@ class DebugHandlersListener implements EventSubscriberInterface if (method_exists($kernel = $event->getKernel(), 'terminateWithException')) { $request = $event->getRequest(); $hasRun = &$this->hasTerminatedWithException; - $this->exceptionHandler = static function (\Exception $e) use ($kernel, $request, &$hasRun) { + $this->exceptionHandler = static function (\Throwable $e) use ($kernel, $request, &$hasRun) { if ($hasRun) { throw $e; } + $hasRun = true; $kernel->terminateWithException($e, $request); }; @@ -122,32 +136,48 @@ class DebugHandlersListener implements EventSubscriberInterface if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } - $this->exceptionHandler = function ($e) use ($app, $output) { - $app->renderException($e, $output); + $this->exceptionHandler = static function (\Throwable $e) use ($app, $output) { + $app->renderThrowable($e, $output); }; } } if ($this->exceptionHandler) { if ($handler instanceof ErrorHandler) { - $h = $handler->setExceptionHandler('var_dump'); - if (\is_array($h) && $h[0] instanceof ExceptionHandler) { - $handler->setExceptionHandler($h); - $handler = $h[0]; - } else { - $handler->setExceptionHandler($this->exceptionHandler); - } - } - if ($handler instanceof ExceptionHandler) { - $handler->setHandler($this->exceptionHandler); - if (null !== $this->fileLinkFormat) { - $handler->setFileLinkFormat($this->fileLinkFormat); - } + $handler->setExceptionHandler($this->exceptionHandler); } $this->exceptionHandler = null; } } - public static function getSubscribedEvents() + private function setDefaultLoggers(ErrorHandler $handler): void + { + if (\is_array($this->levels)) { + $levelsDeprecatedOnly = []; + $levelsWithoutDeprecated = []; + foreach ($this->levels as $type => $log) { + if (\E_DEPRECATED == $type || \E_USER_DEPRECATED == $type) { + $levelsDeprecatedOnly[$type] = $log; + } else { + $levelsWithoutDeprecated[$type] = $log; + } + } + } else { + $levelsDeprecatedOnly = $this->levels & (\E_DEPRECATED | \E_USER_DEPRECATED); + $levelsWithoutDeprecated = $this->levels & ~\E_DEPRECATED & ~\E_USER_DEPRECATED; + } + + $defaultLoggerLevels = $this->levels; + if ($this->deprecationLogger && $levelsDeprecatedOnly) { + $handler->setDefaultLogger($this->deprecationLogger, $levelsDeprecatedOnly); + $defaultLoggerLevels = $levelsWithoutDeprecated; + } + + if ($this->logger && $defaultLoggerLevels) { + $handler->setDefaultLogger($this->logger, $defaultLoggerLevels); + } + } + + public static function getSubscribedEvents(): array { $events = [KernelEvents::REQUEST => ['configure', 2048]]; diff --git a/lib/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.php b/lib/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.php new file mode 100644 index 0000000000..6607e49e99 --- /dev/null +++ b/lib/symfony/http-kernel/EventListener/DisallowRobotsIndexingListener.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 Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Ensures that the application is not indexed by search engines. + * + * @author Gary PEGEOT + */ +class DisallowRobotsIndexingListener implements EventSubscriberInterface +{ + private const HEADER_NAME = 'X-Robots-Tag'; + + public function onResponse(ResponseEvent $event): void + { + if (!$event->getResponse()->headers->has(static::HEADER_NAME)) { + $event->getResponse()->headers->set(static::HEADER_NAME, 'noindex'); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return [ + KernelEvents::RESPONSE => ['onResponse', -255], + ]; + } +} diff --git a/lib/symfony/http-kernel/EventListener/DumpListener.php b/lib/symfony/http-kernel/EventListener/DumpListener.php index 2f47d1068c..30908a4f45 100644 --- a/lib/symfony/http-kernel/EventListener/DumpListener.php +++ b/lib/symfony/http-kernel/EventListener/DumpListener.php @@ -15,6 +15,7 @@ use Symfony\Component\Console\ConsoleEvents; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\VarDumper\Cloner\ClonerInterface; use Symfony\Component\VarDumper\Dumper\DataDumperInterface; +use Symfony\Component\VarDumper\Server\Connection; use Symfony\Component\VarDumper\VarDumper; /** @@ -26,20 +27,27 @@ class DumpListener implements EventSubscriberInterface { private $cloner; private $dumper; + private $connection; - public function __construct(ClonerInterface $cloner, DataDumperInterface $dumper) + public function __construct(ClonerInterface $cloner, DataDumperInterface $dumper, Connection $connection = null) { $this->cloner = $cloner; $this->dumper = $dumper; + $this->connection = $connection; } public function configure() { $cloner = $this->cloner; $dumper = $this->dumper; + $connection = $this->connection; - VarDumper::setHandler(function ($var) use ($cloner, $dumper) { - $dumper->dump($cloner->cloneVar($var)); + VarDumper::setHandler(static function ($var) use ($cloner, $dumper, $connection) { + $data = $cloner->cloneVar($var); + + if (!$connection || !$connection->write($data)) { + $dumper->dump($data); + } }); } diff --git a/lib/symfony/http-kernel/EventListener/ErrorListener.php b/lib/symfony/http-kernel/EventListener/ErrorListener.php new file mode 100644 index 0000000000..9dc3871c25 --- /dev/null +++ b/lib/symfony/http-kernel/EventListener/ErrorListener.php @@ -0,0 +1,180 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Debug\Exception\FlattenException as LegacyFlattenException; +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Event\ExceptionEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; + +/** + * @author Fabien Potencier + */ +class ErrorListener implements EventSubscriberInterface +{ + protected $controller; + protected $logger; + protected $debug; + protected $exceptionsMapping; + + public function __construct($controller, LoggerInterface $logger = null, bool $debug = false, array $exceptionsMapping = []) + { + $this->controller = $controller; + $this->logger = $logger; + $this->debug = $debug; + $this->exceptionsMapping = $exceptionsMapping; + } + + public function logKernelException(ExceptionEvent $event) + { + $throwable = $event->getThrowable(); + $logLevel = null; + + foreach ($this->exceptionsMapping as $class => $config) { + if ($throwable instanceof $class && $config['log_level']) { + $logLevel = $config['log_level']; + break; + } + } + + foreach ($this->exceptionsMapping as $class => $config) { + if (!$throwable instanceof $class || !$config['status_code']) { + continue; + } + if (!$throwable instanceof HttpExceptionInterface || $throwable->getStatusCode() !== $config['status_code']) { + $headers = $throwable instanceof HttpExceptionInterface ? $throwable->getHeaders() : []; + $throwable = new HttpException($config['status_code'], $throwable->getMessage(), $throwable, $headers); + $event->setThrowable($throwable); + } + break; + } + + $e = FlattenException::createFromThrowable($throwable); + + $this->logException($throwable, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', $e->getClass(), $e->getMessage(), $e->getFile(), $e->getLine()), $logLevel); + } + + public function onKernelException(ExceptionEvent $event) + { + if (null === $this->controller) { + return; + } + + $throwable = $event->getThrowable(); + $request = $this->duplicateRequest($throwable, $event->getRequest()); + + try { + $response = $event->getKernel()->handle($request, HttpKernelInterface::SUB_REQUEST, false); + } catch (\Exception $e) { + $f = FlattenException::createFromThrowable($e); + + $this->logException($e, sprintf('Exception thrown when handling an exception (%s: %s at %s line %s)', $f->getClass(), $f->getMessage(), $e->getFile(), $e->getLine())); + + $prev = $e; + do { + if ($throwable === $wrapper = $prev) { + throw $e; + } + } while ($prev = $wrapper->getPrevious()); + + $prev = new \ReflectionProperty($wrapper instanceof \Exception ? \Exception::class : \Error::class, 'previous'); + $prev->setAccessible(true); + $prev->setValue($wrapper, $throwable); + + throw $e; + } + + $event->setResponse($response); + + if ($this->debug) { + $event->getRequest()->attributes->set('_remove_csp_headers', true); + } + } + + public function removeCspHeader(ResponseEvent $event): void + { + if ($this->debug && $event->getRequest()->attributes->get('_remove_csp_headers', false)) { + $event->getResponse()->headers->remove('Content-Security-Policy'); + } + } + + public function onControllerArguments(ControllerArgumentsEvent $event) + { + $e = $event->getRequest()->attributes->get('exception'); + + if (!$e instanceof \Throwable || false === $k = array_search($e, $event->getArguments(), true)) { + return; + } + + $r = new \ReflectionFunction(\Closure::fromCallable($event->getController())); + $r = $r->getParameters()[$k] ?? null; + + if ($r && (!($r = $r->getType()) instanceof \ReflectionNamedType || \in_array($r->getName(), [FlattenException::class, LegacyFlattenException::class], true))) { + $arguments = $event->getArguments(); + $arguments[$k] = FlattenException::createFromThrowable($e); + $event->setArguments($arguments); + } + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::CONTROLLER_ARGUMENTS => 'onControllerArguments', + KernelEvents::EXCEPTION => [ + ['logKernelException', 0], + ['onKernelException', -128], + ], + KernelEvents::RESPONSE => ['removeCspHeader', -128], + ]; + } + + /** + * Logs an exception. + */ + protected function logException(\Throwable $exception, string $message, string $logLevel = null): void + { + if (null !== $this->logger) { + if (null !== $logLevel) { + $this->logger->log($logLevel, $message, ['exception' => $exception]); + } elseif (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) { + $this->logger->critical($message, ['exception' => $exception]); + } else { + $this->logger->error($message, ['exception' => $exception]); + } + } + } + + /** + * Clones the request for the exception. + */ + protected function duplicateRequest(\Throwable $exception, Request $request): Request + { + $attributes = [ + '_controller' => $this->controller, + 'exception' => $exception, + 'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null, + ]; + $request = $request->duplicate(null, null, $attributes); + $request->setMethod('GET'); + + return $request; + } +} diff --git a/lib/symfony/http-kernel/EventListener/ExceptionListener.php b/lib/symfony/http-kernel/EventListener/ExceptionListener.php deleted file mode 100644 index e7e36d188b..0000000000 --- a/lib/symfony/http-kernel/EventListener/ExceptionListener.php +++ /dev/null @@ -1,128 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\EventListener; - -use Psr\Log\LoggerInterface; -use Symfony\Component\Debug\Exception\FlattenException; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Event\FilterResponseEvent; -use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; -use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; -use Symfony\Component\HttpKernel\HttpKernelInterface; -use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; - -/** - * ExceptionListener. - * - * @author Fabien Potencier - */ -class ExceptionListener implements EventSubscriberInterface -{ - protected $controller; - protected $logger; - protected $debug; - - public function __construct($controller, LoggerInterface $logger = null, $debug = false) - { - $this->controller = $controller; - $this->logger = $logger; - $this->debug = $debug; - } - - public function onKernelException(GetResponseForExceptionEvent $event) - { - $exception = $event->getException(); - $request = $event->getRequest(); - $eventDispatcher = \func_num_args() > 2 ? func_get_arg(2) : null; - - $this->logException($exception, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', \get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine())); - - $request = $this->duplicateRequest($exception, $request); - - try { - $response = $event->getKernel()->handle($request, HttpKernelInterface::SUB_REQUEST, false); - } catch (\Exception $e) { - $this->logException($e, sprintf('Exception thrown when handling an exception (%s: %s at %s line %s)', \get_class($e), $e->getMessage(), $e->getFile(), $e->getLine())); - - $prev = $e; - do { - if ($exception === $wrapper = $prev) { - throw $e; - } - } while ($prev = $wrapper->getPrevious()); - - $prev = new \ReflectionProperty($wrapper instanceof \Exception ? \Exception::class : \Error::class, 'previous'); - $prev->setAccessible(true); - $prev->setValue($wrapper, $exception); - - throw $e; - } - - $event->setResponse($response); - - if ($this->debug && $eventDispatcher instanceof EventDispatcherInterface) { - $cspRemovalListener = function (FilterResponseEvent $event) use (&$cspRemovalListener, $eventDispatcher) { - $event->getResponse()->headers->remove('Content-Security-Policy'); - $eventDispatcher->removeListener(KernelEvents::RESPONSE, $cspRemovalListener); - }; - $eventDispatcher->addListener(KernelEvents::RESPONSE, $cspRemovalListener, -128); - } - } - - public static function getSubscribedEvents() - { - return [ - KernelEvents::EXCEPTION => ['onKernelException', -128], - ]; - } - - /** - * Logs an exception. - * - * @param \Exception $exception The \Exception instance - * @param string $message The error message to log - */ - protected function logException(\Exception $exception, $message) - { - if (null !== $this->logger) { - if (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) { - $this->logger->critical($message, ['exception' => $exception]); - } else { - $this->logger->error($message, ['exception' => $exception]); - } - } - } - - /** - * Clones the request for the exception. - * - * @param \Exception $exception The thrown exception - * @param Request $request The original request - * - * @return Request The cloned request - */ - protected function duplicateRequest(\Exception $exception, Request $request) - { - $attributes = [ - '_controller' => $this->controller, - 'exception' => FlattenException::create($exception), - 'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null, - ]; - $request = $request->duplicate(null, null, $attributes); - $request->setMethod('GET'); - - return $request; - } -} diff --git a/lib/symfony/http-kernel/EventListener/FragmentListener.php b/lib/symfony/http-kernel/EventListener/FragmentListener.php index 997f260d33..c01d9ad491 100644 --- a/lib/symfony/http-kernel/EventListener/FragmentListener.php +++ b/lib/symfony/http-kernel/EventListener/FragmentListener.php @@ -13,7 +13,7 @@ namespace Symfony\Component\HttpKernel\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\UriSigner; @@ -28,6 +28,8 @@ use Symfony\Component\HttpKernel\UriSigner; * is not signed or if it is not an internal sub-request. * * @author Fabien Potencier + * + * @final */ class FragmentListener implements EventSubscriberInterface { @@ -35,10 +37,9 @@ class FragmentListener implements EventSubscriberInterface private $fragmentPath; /** - * @param UriSigner $signer A UriSigner instance - * @param string $fragmentPath The path that triggers this listener + * @param string $fragmentPath The path that triggers this listener */ - public function __construct(UriSigner $signer, $fragmentPath = '/_fragment') + public function __construct(UriSigner $signer, string $fragmentPath = '/_fragment') { $this->signer = $signer; $this->fragmentPath = $fragmentPath; @@ -49,7 +50,7 @@ class FragmentListener implements EventSubscriberInterface * * @throws AccessDeniedHttpException if the request does not come from a trusted IP */ - public function onKernelRequest(GetResponseEvent $event) + public function onKernelRequest(RequestEvent $event) { $request = $event->getRequest(); @@ -64,7 +65,7 @@ class FragmentListener implements EventSubscriberInterface return; } - if ($event->isMasterRequest()) { + if ($event->isMainRequest()) { $this->validateRequest($request); } @@ -77,20 +78,19 @@ class FragmentListener implements EventSubscriberInterface protected function validateRequest(Request $request) { // is the Request safe? - if (!$request->isMethodSafe(false)) { + if (!$request->isMethodSafe()) { throw new AccessDeniedHttpException(); } // is the Request signed? - // we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering) - if ($this->signer->check($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().(null !== ($qs = $request->server->get('QUERY_STRING')) ? '?'.$qs : ''))) { + if ($this->signer->checkRequest($request)) { return; } throw new AccessDeniedHttpException(); } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ KernelEvents::REQUEST => [['onKernelRequest', 48]], diff --git a/lib/symfony/http-kernel/EventListener/LocaleAwareListener.php b/lib/symfony/http-kernel/EventListener/LocaleAwareListener.php new file mode 100644 index 0000000000..a126f06ecb --- /dev/null +++ b/lib/symfony/http-kernel/EventListener/LocaleAwareListener.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\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Contracts\Translation\LocaleAwareInterface; + +/** + * Pass the current locale to the provided services. + * + * @author Pierre Bobiet + */ +class LocaleAwareListener implements EventSubscriberInterface +{ + private $localeAwareServices; + private $requestStack; + + /** + * @param iterable $localeAwareServices + */ + public function __construct(iterable $localeAwareServices, RequestStack $requestStack) + { + $this->localeAwareServices = $localeAwareServices; + $this->requestStack = $requestStack; + } + + public function onKernelRequest(RequestEvent $event): void + { + $this->setLocale($event->getRequest()->getLocale(), $event->getRequest()->getDefaultLocale()); + } + + public function onKernelFinishRequest(FinishRequestEvent $event): void + { + if (null === $parentRequest = $this->requestStack->getParentRequest()) { + foreach ($this->localeAwareServices as $service) { + $service->setLocale($event->getRequest()->getDefaultLocale()); + } + + return; + } + + $this->setLocale($parentRequest->getLocale(), $parentRequest->getDefaultLocale()); + } + + public static function getSubscribedEvents() + { + return [ + // must be registered after the Locale listener + KernelEvents::REQUEST => [['onKernelRequest', 15]], + KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', -15]], + ]; + } + + private function setLocale(string $locale, string $defaultLocale): void + { + foreach ($this->localeAwareServices as $service) { + try { + $service->setLocale($locale); + } catch (\InvalidArgumentException $e) { + $service->setLocale($defaultLocale); + } + } + } +} diff --git a/lib/symfony/http-kernel/EventListener/LocaleListener.php b/lib/symfony/http-kernel/EventListener/LocaleListener.php index c7e32dbb23..f19e13649e 100644 --- a/lib/symfony/http-kernel/EventListener/LocaleListener.php +++ b/lib/symfony/http-kernel/EventListener/LocaleListener.php @@ -15,7 +15,8 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\Event\FinishRequestEvent; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\KernelEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Routing\RequestContextAwareInterface; @@ -23,29 +24,34 @@ use Symfony\Component\Routing\RequestContextAwareInterface; * Initializes the locale based on the current request. * * @author Fabien Potencier + * + * @final */ class LocaleListener implements EventSubscriberInterface { private $router; private $defaultLocale; private $requestStack; + private $useAcceptLanguageHeader; + private $enabledLocales; - /** - * @param RequestStack $requestStack A RequestStack instance - * @param string $defaultLocale The default locale - * @param RequestContextAwareInterface|null $router The router - */ - public function __construct(RequestStack $requestStack, $defaultLocale = 'en', RequestContextAwareInterface $router = null) + public function __construct(RequestStack $requestStack, string $defaultLocale = 'en', RequestContextAwareInterface $router = null, bool $useAcceptLanguageHeader = false, array $enabledLocales = []) { $this->defaultLocale = $defaultLocale; $this->requestStack = $requestStack; $this->router = $router; + $this->useAcceptLanguageHeader = $useAcceptLanguageHeader; + $this->enabledLocales = $enabledLocales; } - public function onKernelRequest(GetResponseEvent $event) + public function setDefaultLocale(KernelEvent $event) + { + $event->getRequest()->setDefaultLocale($this->defaultLocale); + } + + public function onKernelRequest(RequestEvent $event) { $request = $event->getRequest(); - $request->setDefaultLocale($this->defaultLocale); $this->setLocale($request); $this->setRouterContext($request); @@ -62,6 +68,9 @@ class LocaleListener implements EventSubscriberInterface { if ($locale = $request->attributes->get('_locale')) { $request->setLocale($locale); + } elseif ($this->useAcceptLanguageHeader && $this->enabledLocales && ($preferredLanguage = $request->getPreferredLanguage($this->enabledLocales))) { + $request->setLocale($preferredLanguage); + $request->attributes->set('_vary_by_language', true); } } @@ -72,11 +81,14 @@ class LocaleListener implements EventSubscriberInterface } } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ - // must be registered after the Router to have access to the _locale - KernelEvents::REQUEST => [['onKernelRequest', 16]], + KernelEvents::REQUEST => [ + ['setDefaultLocale', 100], + // must be registered after the Router to have access to the _locale + ['onKernelRequest', 16], + ], KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', 0]], ]; } diff --git a/lib/symfony/http-kernel/EventListener/ProfilerListener.php b/lib/symfony/http-kernel/EventListener/ProfilerListener.php index 5304b8097b..adbafe62e9 100644 --- a/lib/symfony/http-kernel/EventListener/ProfilerListener.php +++ b/lib/symfony/http-kernel/EventListener/ProfilerListener.php @@ -12,67 +12,72 @@ namespace Symfony\Component\HttpKernel\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestMatcherInterface; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\HttpKernel\Event\FilterResponseEvent; -use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; -use Symfony\Component\HttpKernel\Event\PostResponseEvent; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpKernel\Event\ExceptionEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\Event\TerminateEvent; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Profiler\Profile; use Symfony\Component\HttpKernel\Profiler\Profiler; /** * ProfilerListener collects data for the current request by listening to the kernel events. * * @author Fabien Potencier + * + * @final */ class ProfilerListener implements EventSubscriberInterface { protected $profiler; protected $matcher; protected $onlyException; - protected $onlyMasterRequests; + protected $onlyMainRequests; protected $exception; + /** @var \SplObjectStorage */ protected $profiles; protected $requestStack; + protected $collectParameter; + /** @var \SplObjectStorage */ protected $parents; /** - * @param Profiler $profiler A Profiler instance - * @param RequestStack $requestStack A RequestStack instance - * @param RequestMatcherInterface|null $matcher A RequestMatcher instance - * @param bool $onlyException True if the profiler only collects data when an exception occurs, false otherwise - * @param bool $onlyMasterRequests True if the profiler only collects data when the request is a master request, false otherwise + * @param bool $onlyException True if the profiler only collects data when an exception occurs, false otherwise + * @param bool $onlyMainRequests True if the profiler only collects data when the request is the main request, false otherwise */ - public function __construct(Profiler $profiler, RequestStack $requestStack, RequestMatcherInterface $matcher = null, $onlyException = false, $onlyMasterRequests = false) + public function __construct(Profiler $profiler, RequestStack $requestStack, RequestMatcherInterface $matcher = null, bool $onlyException = false, bool $onlyMainRequests = false, string $collectParameter = null) { $this->profiler = $profiler; $this->matcher = $matcher; - $this->onlyException = (bool) $onlyException; - $this->onlyMasterRequests = (bool) $onlyMasterRequests; + $this->onlyException = $onlyException; + $this->onlyMainRequests = $onlyMainRequests; $this->profiles = new \SplObjectStorage(); $this->parents = new \SplObjectStorage(); $this->requestStack = $requestStack; + $this->collectParameter = $collectParameter; } /** * Handles the onKernelException event. */ - public function onKernelException(GetResponseForExceptionEvent $event) + public function onKernelException(ExceptionEvent $event) { - if ($this->onlyMasterRequests && !$event->isMasterRequest()) { + if ($this->onlyMainRequests && !$event->isMainRequest()) { return; } - $this->exception = $event->getException(); + $this->exception = $event->getThrowable(); } /** * Handles the onKernelResponse event. */ - public function onKernelResponse(FilterResponseEvent $event) + public function onKernelResponse(ResponseEvent $event) { - $master = $event->isMasterRequest(); - if ($this->onlyMasterRequests && !$master) { + if ($this->onlyMainRequests && !$event->isMainRequest()) { return; } @@ -81,6 +86,10 @@ class ProfilerListener implements EventSubscriberInterface } $request = $event->getRequest(); + if (null !== $this->collectParameter && null !== $collectParameterValue = $request->get($this->collectParameter)) { + true === $collectParameterValue || filter_var($collectParameterValue, \FILTER_VALIDATE_BOOLEAN) ? $this->profiler->enable() : $this->profiler->disable(); + } + $exception = $this->exception; $this->exception = null; @@ -88,8 +97,21 @@ class ProfilerListener implements EventSubscriberInterface return; } - if (!$profile = $this->profiler->collect($request, $event->getResponse(), $exception)) { - return; + $session = $request->hasPreviousSession() && $request->hasSession() ? $request->getSession() : null; + + if ($session instanceof Session) { + $usageIndexValue = $usageIndexReference = &$session->getUsageIndex(); + $usageIndexReference = \PHP_INT_MIN; + } + + try { + if (!$profile = $this->profiler->collect($request, $event->getResponse(), $exception)) { + return; + } + } finally { + if ($session instanceof Session) { + $usageIndexReference = $usageIndexValue; + } } $this->profiles[$request] = $profile; @@ -97,7 +119,7 @@ class ProfilerListener implements EventSubscriberInterface $this->parents[$request] = $this->requestStack->getParentRequest(); } - public function onKernelTerminate(PostResponseEvent $event) + public function onKernelTerminate(TerminateEvent $event) { // attach children to parents foreach ($this->profiles as $request) { @@ -117,11 +139,11 @@ class ProfilerListener implements EventSubscriberInterface $this->parents = new \SplObjectStorage(); } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ KernelEvents::RESPONSE => ['onKernelResponse', -100], - KernelEvents::EXCEPTION => 'onKernelException', + KernelEvents::EXCEPTION => ['onKernelException', 0], KernelEvents::TERMINATE => ['onKernelTerminate', -1024], ]; } diff --git a/lib/symfony/http-kernel/EventListener/ResponseListener.php b/lib/symfony/http-kernel/EventListener/ResponseListener.php index 43527b7ffc..a4090159bb 100644 --- a/lib/symfony/http-kernel/EventListener/ResponseListener.php +++ b/lib/symfony/http-kernel/EventListener/ResponseListener.php @@ -12,29 +12,33 @@ namespace Symfony\Component\HttpKernel\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; /** * ResponseListener fixes the Response headers based on the Request. * * @author Fabien Potencier + * + * @final */ class ResponseListener implements EventSubscriberInterface { private $charset; + private $addContentLanguageHeader; - public function __construct($charset) + public function __construct(string $charset, bool $addContentLanguageHeader = false) { $this->charset = $charset; + $this->addContentLanguageHeader = $addContentLanguageHeader; } /** * Filters the Response. */ - public function onKernelResponse(FilterResponseEvent $event) + public function onKernelResponse(ResponseEvent $event) { - if (!$event->isMasterRequest()) { + if (!$event->isMainRequest()) { return; } @@ -44,10 +48,18 @@ class ResponseListener implements EventSubscriberInterface $response->setCharset($this->charset); } + if ($this->addContentLanguageHeader && !$response->isInformational() && !$response->isEmpty() && !$response->headers->has('Content-Language')) { + $response->headers->set('Content-Language', $event->getRequest()->getLocale()); + } + + if ($event->getRequest()->attributes->get('_vary_by_language')) { + $response->setVary('Accept-Language', false); + } + $response->prepare($event->getRequest()); } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ KernelEvents::RESPONSE => 'onKernelResponse', diff --git a/lib/symfony/http-kernel/EventListener/RouterListener.php b/lib/symfony/http-kernel/EventListener/RouterListener.php index 3803105e85..7c4da98928 100644 --- a/lib/symfony/http-kernel/EventListener/RouterListener.php +++ b/lib/symfony/http-kernel/EventListener/RouterListener.php @@ -16,9 +16,9 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\Event\FinishRequestEvent; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; -use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -37,6 +37,8 @@ use Symfony\Component\Routing\RequestContextAwareInterface; * * @author Fabien Potencier * @author Yonel Ceruto + * + * @final */ class RouterListener implements EventSubscriberInterface { @@ -48,16 +50,12 @@ class RouterListener implements EventSubscriberInterface private $debug; /** - * @param UrlMatcherInterface|RequestMatcherInterface $matcher The Url or Request matcher - * @param RequestStack $requestStack A RequestStack instance - * @param RequestContext|null $context The RequestContext (can be null when $matcher implements RequestContextAwareInterface) - * @param LoggerInterface|null $logger The logger - * @param string $projectDir - * @param bool $debug + * @param UrlMatcherInterface|RequestMatcherInterface $matcher The Url or Request matcher + * @param RequestContext|null $context The RequestContext (can be null when $matcher implements RequestContextAwareInterface) * * @throws \InvalidArgumentException */ - public function __construct($matcher, RequestStack $requestStack, RequestContext $context = null, LoggerInterface $logger = null, $projectDir = null, $debug = true) + public function __construct($matcher, RequestStack $requestStack, RequestContext $context = null, LoggerInterface $logger = null, string $projectDir = null, bool $debug = true) { if (!$matcher instanceof UrlMatcherInterface && !$matcher instanceof RequestMatcherInterface) { throw new \InvalidArgumentException('Matcher must either implement UrlMatcherInterface or RequestMatcherInterface.'); @@ -68,7 +66,7 @@ class RouterListener implements EventSubscriberInterface } $this->matcher = $matcher; - $this->context = $context ?: $matcher->getContext(); + $this->context = $context ?? $matcher->getContext(); $this->requestStack = $requestStack; $this->logger = $logger; $this->projectDir = $projectDir; @@ -95,7 +93,7 @@ class RouterListener implements EventSubscriberInterface $this->setCurrentRequest($this->requestStack->getParentRequest()); } - public function onKernelRequest(GetResponseEvent $event) + public function onKernelRequest(RequestEvent $event) { $request = $event->getRequest(); @@ -117,7 +115,7 @@ class RouterListener implements EventSubscriberInterface if (null !== $this->logger) { $this->logger->info('Matched route "{route}".', [ - 'route' => isset($parameters['_route']) ? $parameters['_route'] : 'n/a', + 'route' => $parameters['_route'] ?? 'n/a', 'route_parameters' => $parameters, 'request_uri' => $request->getUri(), 'method' => $request->getMethod(), @@ -128,7 +126,7 @@ class RouterListener implements EventSubscriberInterface unset($parameters['_route'], $parameters['_controller']); $request->attributes->set('_route_params', $parameters); } catch (ResourceNotFoundException $e) { - $message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getPathInfo()); + $message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getUriForPath($request->getPathInfo())); if ($referer = $request->headers->get('referer')) { $message .= sprintf(' (from "%s")', $referer); @@ -136,15 +134,15 @@ class RouterListener implements EventSubscriberInterface throw new NotFoundHttpException($message, $e); } catch (MethodNotAllowedException $e) { - $message = sprintf('No route found for "%s %s": Method Not Allowed (Allow: %s)', $request->getMethod(), $request->getPathInfo(), implode(', ', $e->getAllowedMethods())); + $message = sprintf('No route found for "%s %s": Method Not Allowed (Allow: %s)', $request->getMethod(), $request->getUriForPath($request->getPathInfo()), implode(', ', $e->getAllowedMethods())); throw new MethodNotAllowedHttpException($e->getAllowedMethods(), $message, $e); } } - public function onKernelException(GetResponseForExceptionEvent $event) + public function onKernelException(ExceptionEvent $event) { - if (!$this->debug || !($e = $event->getException()) instanceof NotFoundHttpException) { + if (!$this->debug || !($e = $event->getThrowable()) instanceof NotFoundHttpException) { return; } @@ -153,7 +151,7 @@ class RouterListener implements EventSubscriberInterface } } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ KernelEvents::REQUEST => [['onKernelRequest', 32]], @@ -162,14 +160,14 @@ class RouterListener implements EventSubscriberInterface ]; } - private function createWelcomeResponse() + private function createWelcomeResponse(): Response { $version = Kernel::VERSION; - $baseDir = realpath($this->projectDir).\DIRECTORY_SEPARATOR; + $projectDir = realpath((string) $this->projectDir).\DIRECTORY_SEPARATOR; $docVersion = substr(Kernel::VERSION, 0, 3); ob_start(); - include __DIR__.'/../Resources/welcome.html.php'; + include \dirname(__DIR__).'/Resources/welcome.html.php'; return new Response(ob_get_clean(), Response::HTTP_NOT_FOUND); } diff --git a/lib/symfony/http-kernel/EventListener/SaveSessionListener.php b/lib/symfony/http-kernel/EventListener/SaveSessionListener.php deleted file mode 100644 index 5f5cd24801..0000000000 --- a/lib/symfony/http-kernel/EventListener/SaveSessionListener.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\EventListener; - -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpKernel\Event\FilterResponseEvent; -use Symfony\Component\HttpKernel\KernelEvents; - -/** - * Saves the session, in case it is still open, before sending the response/headers. - * - * This ensures several things in case the developer did not save the session explicitly: - * - * * If a session save handler without locking is used, it ensures the data is available - * on the next request, e.g. after a redirect. PHPs auto-save at script end via - * session_register_shutdown is executed after fastcgi_finish_request. So in this case - * the data could be missing the next request because it might not be saved the moment - * the new request is processed. - * * A locking save handler (e.g. the native 'files') circumvents concurrency problems like - * the one above. But by saving the session before long-running things in the terminate event, - * we ensure the session is not blocked longer than needed. - * * When regenerating the session ID no locking is involved in PHPs session design. See - * https://bugs.php.net/61470 for a discussion. So in this case, the session must - * be saved anyway before sending the headers with the new session ID. Otherwise session - * data could get lost again for concurrent requests with the new ID. One result could be - * that you get logged out after just logging in. - * - * This listener should be executed as one of the last listeners, so that previous listeners - * can still operate on the open session. This prevents the overhead of restarting it. - * Listeners after closing the session can still work with the session as usual because - * Symfonys session implementation starts the session on demand. So writing to it after - * it is saved will just restart it. - * - * @author Tobias Schultze - */ -class SaveSessionListener implements EventSubscriberInterface -{ - public function onKernelResponse(FilterResponseEvent $event) - { - if (!$event->isMasterRequest()) { - return; - } - - $session = $event->getRequest()->getSession(); - if ($session && $session->isStarted()) { - $session->save(); - } - } - - public static function getSubscribedEvents() - { - return [ - // low priority but higher than StreamedResponseListener - KernelEvents::RESPONSE => [['onKernelResponse', -1000]], - ]; - } -} diff --git a/lib/symfony/http-kernel/EventListener/SessionListener.php b/lib/symfony/http-kernel/EventListener/SessionListener.php index 9e36b7626b..61887fde68 100644 --- a/lib/symfony/http-kernel/EventListener/SessionListener.php +++ b/lib/symfony/http-kernel/EventListener/SessionListener.php @@ -11,30 +11,50 @@ namespace Symfony\Component\HttpKernel\EventListener; -use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpKernel\Event\RequestEvent; /** * Sets the session in the request. * + * When the passed container contains a "session_storage" entry which + * holds a NativeSessionStorage instance, the "cookie_secure" option + * will be set to true whenever the current main request is secure. + * * @author Fabien Potencier * - * @final since version 3.3 + * @final */ class SessionListener extends AbstractSessionListener { - private $container; - - public function __construct(ContainerInterface $container) + public function onKernelRequest(RequestEvent $event) { - $this->container = $container; - } + parent::onKernelRequest($event); - protected function getSession() - { - if (!$this->container->has('session')) { - return null; + if (!$event->isMainRequest() || (!$this->container->has('session') && !$this->container->has('session_factory'))) { + return; } - return $this->container->get('session'); + if ($this->container->has('session_storage') + && ($storage = $this->container->get('session_storage')) instanceof NativeSessionStorage + && ($mainRequest = $this->container->get('request_stack')->getMainRequest()) + && $mainRequest->isSecure() + ) { + $storage->setOptions(['cookie_secure' => true]); + } + } + + protected function getSession(): ?SessionInterface + { + if ($this->container->has('session')) { + return $this->container->get('session'); + } + + if ($this->container->has('session_factory')) { + return $this->container->get('session_factory')->createSession(); + } + + return null; } } diff --git a/lib/symfony/http-kernel/EventListener/StreamedResponseListener.php b/lib/symfony/http-kernel/EventListener/StreamedResponseListener.php index 895176a931..b3f7ca40fa 100644 --- a/lib/symfony/http-kernel/EventListener/StreamedResponseListener.php +++ b/lib/symfony/http-kernel/EventListener/StreamedResponseListener.php @@ -13,7 +13,7 @@ namespace Symfony\Component\HttpKernel\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\StreamedResponse; -use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; /** @@ -21,15 +21,17 @@ use Symfony\Component\HttpKernel\KernelEvents; * to the client. * * @author Fabien Potencier + * + * @final */ class StreamedResponseListener implements EventSubscriberInterface { /** * Filters the Response. */ - public function onKernelResponse(FilterResponseEvent $event) + public function onKernelResponse(ResponseEvent $event) { - if (!$event->isMasterRequest()) { + if (!$event->isMainRequest()) { return; } @@ -40,7 +42,7 @@ class StreamedResponseListener implements EventSubscriberInterface } } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ KernelEvents::RESPONSE => ['onKernelResponse', -1024], diff --git a/lib/symfony/http-kernel/EventListener/SurrogateListener.php b/lib/symfony/http-kernel/EventListener/SurrogateListener.php index 0fddddde42..9081bff652 100644 --- a/lib/symfony/http-kernel/EventListener/SurrogateListener.php +++ b/lib/symfony/http-kernel/EventListener/SurrogateListener.php @@ -12,7 +12,7 @@ namespace Symfony\Component\HttpKernel\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\HttpCache\HttpCache; use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface; use Symfony\Component\HttpKernel\KernelEvents; @@ -21,6 +21,8 @@ use Symfony\Component\HttpKernel\KernelEvents; * SurrogateListener adds a Surrogate-Control HTTP header when the Response needs to be parsed for Surrogates. * * @author Fabien Potencier + * + * @final */ class SurrogateListener implements EventSubscriberInterface { @@ -34,9 +36,9 @@ class SurrogateListener implements EventSubscriberInterface /** * Filters the Response. */ - public function onKernelResponse(FilterResponseEvent $event) + public function onKernelResponse(ResponseEvent $event) { - if (!$event->isMasterRequest()) { + if (!$event->isMainRequest()) { return; } @@ -56,7 +58,7 @@ class SurrogateListener implements EventSubscriberInterface $surrogate->addSurrogateControl($event->getResponse()); } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ KernelEvents::RESPONSE => 'onKernelResponse', diff --git a/lib/symfony/http-kernel/EventListener/TestSessionListener.php b/lib/symfony/http-kernel/EventListener/TestSessionListener.php index e2c6f5c9e8..45fa312be7 100644 --- a/lib/symfony/http-kernel/EventListener/TestSessionListener.php +++ b/lib/symfony/http-kernel/EventListener/TestSessionListener.php @@ -12,29 +12,35 @@ namespace Symfony\Component\HttpKernel\EventListener; use Psr\Container\ContainerInterface; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +trigger_deprecation('symfony/http-kernel', '5.4', '"%s" is deprecated, use "%s" instead.', TestSessionListener::class, SessionListener::class); /** * Sets the session in the request. * * @author Fabien Potencier * - * @final since version 3.3 + * @final + * + * @deprecated since Symfony 5.4, use SessionListener instead */ class TestSessionListener extends AbstractTestSessionListener { private $container; - public function __construct(ContainerInterface $container) + public function __construct(ContainerInterface $container, array $sessionOptions = []) { $this->container = $container; + parent::__construct($sessionOptions); } - protected function getSession() + protected function getSession(): ?SessionInterface { - if (!$this->container->has('session')) { - return null; + if ($this->container->has('session')) { + return $this->container->get('session'); } - return $this->container->get('session'); + return null; } } diff --git a/lib/symfony/http-kernel/EventListener/TranslatorListener.php b/lib/symfony/http-kernel/EventListener/TranslatorListener.php deleted file mode 100644 index 0f506061a6..0000000000 --- a/lib/symfony/http-kernel/EventListener/TranslatorListener.php +++ /dev/null @@ -1,69 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpKernel\EventListener; - -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\HttpKernel\Event\FinishRequestEvent; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; -use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\Translation\TranslatorInterface; - -/** - * Synchronizes the locale between the request and the translator. - * - * @author Fabien Potencier - */ -class TranslatorListener implements EventSubscriberInterface -{ - private $translator; - private $requestStack; - - public function __construct(TranslatorInterface $translator, RequestStack $requestStack) - { - $this->translator = $translator; - $this->requestStack = $requestStack; - } - - public function onKernelRequest(GetResponseEvent $event) - { - $this->setLocale($event->getRequest()); - } - - public function onKernelFinishRequest(FinishRequestEvent $event) - { - if (null === $parentRequest = $this->requestStack->getParentRequest()) { - return; - } - - $this->setLocale($parentRequest); - } - - public static function getSubscribedEvents() - { - return [ - // must be registered after the Locale listener - KernelEvents::REQUEST => [['onKernelRequest', 10]], - KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', 0]], - ]; - } - - private function setLocale(Request $request) - { - try { - $this->translator->setLocale($request->getLocale()); - } catch (\InvalidArgumentException $e) { - $this->translator->setLocale($request->getDefaultLocale()); - } - } -} diff --git a/lib/symfony/http-kernel/EventListener/ValidateRequestListener.php b/lib/symfony/http-kernel/EventListener/ValidateRequestListener.php index 2e921869b9..caa0f32aab 100644 --- a/lib/symfony/http-kernel/EventListener/ValidateRequestListener.php +++ b/lib/symfony/http-kernel/EventListener/ValidateRequestListener.php @@ -12,22 +12,24 @@ namespace Symfony\Component\HttpKernel\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; /** * Validates Requests. * * @author Magnus Nordlander + * + * @final */ class ValidateRequestListener implements EventSubscriberInterface { /** * Performs the validation. */ - public function onKernelRequest(GetResponseEvent $event) + public function onKernelRequest(RequestEvent $event) { - if (!$event->isMasterRequest()) { + if (!$event->isMainRequest()) { return; } $request = $event->getRequest(); @@ -42,7 +44,7 @@ class ValidateRequestListener implements EventSubscriberInterface /** * {@inheritdoc} */ - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ KernelEvents::REQUEST => [ diff --git a/lib/symfony/http-kernel/Exception/AccessDeniedHttpException.php b/lib/symfony/http-kernel/Exception/AccessDeniedHttpException.php index 05fe7c4a40..58680a3278 100644 --- a/lib/symfony/http-kernel/Exception/AccessDeniedHttpException.php +++ b/lib/symfony/http-kernel/Exception/AccessDeniedHttpException.php @@ -18,12 +18,18 @@ namespace Symfony\Component\HttpKernel\Exception; class AccessDeniedHttpException extends HttpException { /** - * @param string $message The internal exception message - * @param \Exception $previous The previous exception - * @param int $code The internal exception code + * @param string|null $message The internal exception message + * @param \Throwable|null $previous The previous exception + * @param int $code The internal exception code */ - public function __construct($message = null, \Exception $previous = null, $code = 0) + public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - parent::__construct(403, $message, $previous, [], $code); + if (null === $message) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + + parent::__construct(403, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/BadRequestHttpException.php b/lib/symfony/http-kernel/Exception/BadRequestHttpException.php index 683ef1a99e..f530f7db49 100644 --- a/lib/symfony/http-kernel/Exception/BadRequestHttpException.php +++ b/lib/symfony/http-kernel/Exception/BadRequestHttpException.php @@ -17,12 +17,18 @@ namespace Symfony\Component\HttpKernel\Exception; class BadRequestHttpException extends HttpException { /** - * @param string $message The internal exception message - * @param \Exception $previous The previous exception - * @param int $code The internal exception code + * @param string|null $message The internal exception message + * @param \Throwable|null $previous The previous exception + * @param int $code The internal exception code */ - public function __construct($message = null, \Exception $previous = null, $code = 0) + public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - parent::__construct(400, $message, $previous, [], $code); + if (null === $message) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + + parent::__construct(400, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/ConflictHttpException.php b/lib/symfony/http-kernel/Exception/ConflictHttpException.php index aaf258a07d..79c36041c3 100644 --- a/lib/symfony/http-kernel/Exception/ConflictHttpException.php +++ b/lib/symfony/http-kernel/Exception/ConflictHttpException.php @@ -17,12 +17,18 @@ namespace Symfony\Component\HttpKernel\Exception; class ConflictHttpException extends HttpException { /** - * @param string $message The internal exception message - * @param \Exception $previous The previous exception - * @param int $code The internal exception code + * @param string|null $message The internal exception message + * @param \Throwable|null $previous The previous exception + * @param int $code The internal exception code */ - public function __construct($message = null, \Exception $previous = null, $code = 0) + public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - parent::__construct(409, $message, $previous, [], $code); + if (null === $message) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + + parent::__construct(409, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/ControllerDoesNotReturnResponseException.php b/lib/symfony/http-kernel/Exception/ControllerDoesNotReturnResponseException.php new file mode 100644 index 0000000000..54c80be90f --- /dev/null +++ b/lib/symfony/http-kernel/Exception/ControllerDoesNotReturnResponseException.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Grégoire Pineau + */ +class ControllerDoesNotReturnResponseException extends \LogicException +{ + public function __construct(string $message, callable $controller, string $file, int $line) + { + parent::__construct($message); + + if (!$controllerDefinition = $this->parseControllerDefinition($controller)) { + return; + } + + $this->file = $controllerDefinition['file']; + $this->line = $controllerDefinition['line']; + $r = new \ReflectionProperty(\Exception::class, 'trace'); + $r->setAccessible(true); + $r->setValue($this, array_merge([ + [ + 'line' => $line, + 'file' => $file, + ], + ], $this->getTrace())); + } + + private function parseControllerDefinition(callable $controller): ?array + { + if (\is_string($controller) && str_contains($controller, '::')) { + $controller = explode('::', $controller); + } + + if (\is_array($controller)) { + try { + $r = new \ReflectionMethod($controller[0], $controller[1]); + + return [ + 'file' => $r->getFileName(), + 'line' => $r->getEndLine(), + ]; + } catch (\ReflectionException $e) { + return null; + } + } + + if ($controller instanceof \Closure) { + $r = new \ReflectionFunction($controller); + + return [ + 'file' => $r->getFileName(), + 'line' => $r->getEndLine(), + ]; + } + + if (\is_object($controller)) { + $r = new \ReflectionClass($controller); + + try { + $line = $r->getMethod('__invoke')->getEndLine(); + } catch (\ReflectionException $e) { + $line = $r->getEndLine(); + } + + return [ + 'file' => $r->getFileName(), + 'line' => $line, + ]; + } + + return null; + } +} diff --git a/lib/symfony/http-kernel/Exception/GoneHttpException.php b/lib/symfony/http-kernel/Exception/GoneHttpException.php index f855ba02c8..9ea65057b3 100644 --- a/lib/symfony/http-kernel/Exception/GoneHttpException.php +++ b/lib/symfony/http-kernel/Exception/GoneHttpException.php @@ -17,12 +17,18 @@ namespace Symfony\Component\HttpKernel\Exception; class GoneHttpException extends HttpException { /** - * @param string $message The internal exception message - * @param \Exception $previous The previous exception - * @param int $code The internal exception code + * @param string|null $message The internal exception message + * @param \Throwable|null $previous The previous exception + * @param int $code The internal exception code */ - public function __construct($message = null, \Exception $previous = null, $code = 0) + public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - parent::__construct(410, $message, $previous, [], $code); + if (null === $message) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + + parent::__construct(410, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/HttpException.php b/lib/symfony/http-kernel/Exception/HttpException.php index 809d29e6e3..249fe366d5 100644 --- a/lib/symfony/http-kernel/Exception/HttpException.php +++ b/lib/symfony/http-kernel/Exception/HttpException.php @@ -21,8 +21,19 @@ class HttpException extends \RuntimeException implements HttpExceptionInterface private $statusCode; private $headers; - public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = [], $code = 0) + public function __construct(int $statusCode, ?string $message = '', \Throwable $previous = null, array $headers = [], ?int $code = 0) { + if (null === $message) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + if (null === $code) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); + + $code = 0; + } + $this->statusCode = $statusCode; $this->headers = $headers; diff --git a/lib/symfony/http-kernel/Exception/HttpExceptionInterface.php b/lib/symfony/http-kernel/Exception/HttpExceptionInterface.php index 8aa50a9f41..4ae050945c 100644 --- a/lib/symfony/http-kernel/Exception/HttpExceptionInterface.php +++ b/lib/symfony/http-kernel/Exception/HttpExceptionInterface.php @@ -16,19 +16,19 @@ namespace Symfony\Component\HttpKernel\Exception; * * @author Kris Wallsmith */ -interface HttpExceptionInterface +interface HttpExceptionInterface extends \Throwable { /** * Returns the status code. * - * @return int An HTTP response status code + * @return int */ public function getStatusCode(); /** * Returns response headers. * - * @return array Response headers + * @return array */ public function getHeaders(); } diff --git a/lib/symfony/http-kernel/Exception/InvalidMetadataException.php b/lib/symfony/http-kernel/Exception/InvalidMetadataException.php new file mode 100644 index 0000000000..129267ab05 --- /dev/null +++ b/lib/symfony/http-kernel/Exception/InvalidMetadataException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +class InvalidMetadataException extends \LogicException +{ +} diff --git a/lib/symfony/http-kernel/Exception/LengthRequiredHttpException.php b/lib/symfony/http-kernel/Exception/LengthRequiredHttpException.php index 8ad08f4208..fcac137852 100644 --- a/lib/symfony/http-kernel/Exception/LengthRequiredHttpException.php +++ b/lib/symfony/http-kernel/Exception/LengthRequiredHttpException.php @@ -17,12 +17,18 @@ namespace Symfony\Component\HttpKernel\Exception; class LengthRequiredHttpException extends HttpException { /** - * @param string $message The internal exception message - * @param \Exception $previous The previous exception - * @param int $code The internal exception code + * @param string|null $message The internal exception message + * @param \Throwable|null $previous The previous exception + * @param int $code The internal exception code */ - public function __construct($message = null, \Exception $previous = null, $code = 0) + public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - parent::__construct(411, $message, $previous, [], $code); + if (null === $message) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + + parent::__construct(411, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php b/lib/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php index f256ef8615..37576bcacb 100644 --- a/lib/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php +++ b/lib/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php @@ -17,14 +17,25 @@ namespace Symfony\Component\HttpKernel\Exception; class MethodNotAllowedHttpException extends HttpException { /** - * @param array $allow An array of allowed methods - * @param string $message The internal exception message - * @param \Exception $previous The previous exception - * @param int $code The internal exception code + * @param string[] $allow An array of allowed methods + * @param string|null $message The internal exception message + * @param \Throwable|null $previous The previous exception + * @param int|null $code The internal exception code */ - public function __construct(array $allow, $message = null, \Exception $previous = null, $code = 0) + public function __construct(array $allow, ?string $message = '', \Throwable $previous = null, ?int $code = 0, array $headers = []) { - $headers = ['Allow' => strtoupper(implode(', ', $allow))]; + if (null === $message) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + if (null === $code) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); + + $code = 0; + } + + $headers['Allow'] = strtoupper(implode(', ', $allow)); parent::__construct(405, $message, $previous, $headers, $code); } diff --git a/lib/symfony/http-kernel/Exception/NotAcceptableHttpException.php b/lib/symfony/http-kernel/Exception/NotAcceptableHttpException.php index a48bbe70eb..5a422406ba 100644 --- a/lib/symfony/http-kernel/Exception/NotAcceptableHttpException.php +++ b/lib/symfony/http-kernel/Exception/NotAcceptableHttpException.php @@ -17,12 +17,18 @@ namespace Symfony\Component\HttpKernel\Exception; class NotAcceptableHttpException extends HttpException { /** - * @param string $message The internal exception message - * @param \Exception $previous The previous exception - * @param int $code The internal exception code + * @param string|null $message The internal exception message + * @param \Throwable|null $previous The previous exception + * @param int $code The internal exception code */ - public function __construct($message = null, \Exception $previous = null, $code = 0) + public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - parent::__construct(406, $message, $previous, [], $code); + if (null === $message) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + + parent::__construct(406, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/NotFoundHttpException.php b/lib/symfony/http-kernel/Exception/NotFoundHttpException.php index 01a5bd080c..a475113c5f 100644 --- a/lib/symfony/http-kernel/Exception/NotFoundHttpException.php +++ b/lib/symfony/http-kernel/Exception/NotFoundHttpException.php @@ -17,12 +17,18 @@ namespace Symfony\Component\HttpKernel\Exception; class NotFoundHttpException extends HttpException { /** - * @param string $message The internal exception message - * @param \Exception $previous The previous exception - * @param int $code The internal exception code + * @param string|null $message The internal exception message + * @param \Throwable|null $previous The previous exception + * @param int $code The internal exception code */ - public function __construct($message = null, \Exception $previous = null, $code = 0) + public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - parent::__construct(404, $message, $previous, [], $code); + if (null === $message) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + + parent::__construct(404, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/PreconditionFailedHttpException.php b/lib/symfony/http-kernel/Exception/PreconditionFailedHttpException.php index 3cf33f988a..e23740a28d 100644 --- a/lib/symfony/http-kernel/Exception/PreconditionFailedHttpException.php +++ b/lib/symfony/http-kernel/Exception/PreconditionFailedHttpException.php @@ -17,12 +17,18 @@ namespace Symfony\Component\HttpKernel\Exception; class PreconditionFailedHttpException extends HttpException { /** - * @param string $message The internal exception message - * @param \Exception $previous The previous exception - * @param int $code The internal exception code + * @param string|null $message The internal exception message + * @param \Throwable|null $previous The previous exception + * @param int $code The internal exception code */ - public function __construct($message = null, \Exception $previous = null, $code = 0) + public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - parent::__construct(412, $message, $previous, [], $code); + if (null === $message) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + + parent::__construct(412, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php b/lib/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php index a2512c5c1b..5c31fae822 100644 --- a/lib/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php +++ b/lib/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php @@ -19,12 +19,18 @@ namespace Symfony\Component\HttpKernel\Exception; class PreconditionRequiredHttpException extends HttpException { /** - * @param string $message The internal exception message - * @param \Exception $previous The previous exception - * @param int $code The internal exception code + * @param string|null $message The internal exception message + * @param \Throwable|null $previous The previous exception + * @param int $code The internal exception code */ - public function __construct($message = null, \Exception $previous = null, $code = 0) + public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - parent::__construct(428, $message, $previous, [], $code); + if (null === $message) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + + parent::__construct(428, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php b/lib/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php index 52ce75132f..d5681bbeb3 100644 --- a/lib/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php +++ b/lib/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php @@ -17,16 +17,26 @@ namespace Symfony\Component\HttpKernel\Exception; class ServiceUnavailableHttpException extends HttpException { /** - * @param int|string $retryAfter The number of seconds or HTTP-date after which the request may be retried - * @param string $message The internal exception message - * @param \Exception $previous The previous exception - * @param int $code The internal exception code + * @param int|string|null $retryAfter The number of seconds or HTTP-date after which the request may be retried + * @param string|null $message The internal exception message + * @param \Throwable|null $previous The previous exception + * @param int|null $code The internal exception code */ - public function __construct($retryAfter = null, $message = null, \Exception $previous = null, $code = 0) + public function __construct($retryAfter = null, ?string $message = '', \Throwable $previous = null, ?int $code = 0, array $headers = []) { - $headers = []; + if (null === $message) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + if (null === $code) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); + + $code = 0; + } + if ($retryAfter) { - $headers = ['Retry-After' => $retryAfter]; + $headers['Retry-After'] = $retryAfter; } parent::__construct(503, $message, $previous, $headers, $code); diff --git a/lib/symfony/http-kernel/Exception/TooManyRequestsHttpException.php b/lib/symfony/http-kernel/Exception/TooManyRequestsHttpException.php index d313f7ead8..fd74402b5d 100644 --- a/lib/symfony/http-kernel/Exception/TooManyRequestsHttpException.php +++ b/lib/symfony/http-kernel/Exception/TooManyRequestsHttpException.php @@ -19,16 +19,26 @@ namespace Symfony\Component\HttpKernel\Exception; class TooManyRequestsHttpException extends HttpException { /** - * @param int|string $retryAfter The number of seconds or HTTP-date after which the request may be retried - * @param string $message The internal exception message - * @param \Exception $previous The previous exception - * @param int $code The internal exception code + * @param int|string|null $retryAfter The number of seconds or HTTP-date after which the request may be retried + * @param string|null $message The internal exception message + * @param \Throwable|null $previous The previous exception + * @param int|null $code The internal exception code */ - public function __construct($retryAfter = null, $message = null, \Exception $previous = null, $code = 0) + public function __construct($retryAfter = null, ?string $message = '', \Throwable $previous = null, ?int $code = 0, array $headers = []) { - $headers = []; + if (null === $message) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + if (null === $code) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); + + $code = 0; + } + if ($retryAfter) { - $headers = ['Retry-After' => $retryAfter]; + $headers['Retry-After'] = $retryAfter; } parent::__construct(429, $message, $previous, $headers, $code); diff --git a/lib/symfony/http-kernel/Exception/UnauthorizedHttpException.php b/lib/symfony/http-kernel/Exception/UnauthorizedHttpException.php index d945df2bc8..aeb9713a3d 100644 --- a/lib/symfony/http-kernel/Exception/UnauthorizedHttpException.php +++ b/lib/symfony/http-kernel/Exception/UnauthorizedHttpException.php @@ -17,14 +17,25 @@ namespace Symfony\Component\HttpKernel\Exception; class UnauthorizedHttpException extends HttpException { /** - * @param string $challenge WWW-Authenticate challenge string - * @param string $message The internal exception message - * @param \Exception $previous The previous exception - * @param int $code The internal exception code + * @param string $challenge WWW-Authenticate challenge string + * @param string|null $message The internal exception message + * @param \Throwable|null $previous The previous exception + * @param int|null $code The internal exception code */ - public function __construct($challenge, $message = null, \Exception $previous = null, $code = 0) + public function __construct(string $challenge, ?string $message = '', \Throwable $previous = null, ?int $code = 0, array $headers = []) { - $headers = ['WWW-Authenticate' => $challenge]; + if (null === $message) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + if (null === $code) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $code to "%s()" is deprecated, pass 0 instead.', __METHOD__); + + $code = 0; + } + + $headers['WWW-Authenticate'] = $challenge; parent::__construct(401, $message, $previous, $headers, $code); } diff --git a/lib/symfony/http-kernel/Exception/UnexpectedSessionUsageException.php b/lib/symfony/http-kernel/Exception/UnexpectedSessionUsageException.php new file mode 100644 index 0000000000..0145b1691c --- /dev/null +++ b/lib/symfony/http-kernel/Exception/UnexpectedSessionUsageException.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 Symfony\Component\HttpKernel\Exception; + +/** + * @author Mathias Arlaud + */ +class UnexpectedSessionUsageException extends \LogicException +{ +} diff --git a/lib/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php b/lib/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php index 2263a31253..7b828b1d92 100644 --- a/lib/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php +++ b/lib/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php @@ -17,12 +17,18 @@ namespace Symfony\Component\HttpKernel\Exception; class UnprocessableEntityHttpException extends HttpException { /** - * @param string $message The internal exception message - * @param \Exception $previous The previous exception - * @param int $code The internal exception code + * @param string|null $message The internal exception message + * @param \Throwable|null $previous The previous exception + * @param int $code The internal exception code */ - public function __construct($message = null, \Exception $previous = null, $code = 0) + public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - parent::__construct(422, $message, $previous, [], $code); + if (null === $message) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + + parent::__construct(422, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php b/lib/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php index 75f844ab57..7908423f42 100644 --- a/lib/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php +++ b/lib/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php @@ -17,12 +17,18 @@ namespace Symfony\Component\HttpKernel\Exception; class UnsupportedMediaTypeHttpException extends HttpException { /** - * @param string $message The internal exception message - * @param \Exception $previous The previous exception - * @param int $code The internal exception code + * @param string|null $message The internal exception message + * @param \Throwable|null $previous The previous exception + * @param int $code The internal exception code */ - public function __construct($message = null, \Exception $previous = null, $code = 0) + public function __construct(?string $message = '', \Throwable $previous = null, int $code = 0, array $headers = []) { - parent::__construct(415, $message, $previous, [], $code); + if (null === $message) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + + parent::__construct(415, $message, $previous, $headers, $code); } } diff --git a/lib/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php b/lib/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php index 3d050c8852..4e4d028b48 100644 --- a/lib/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php +++ b/lib/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php @@ -32,9 +32,7 @@ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRendere * The "fallback" strategy when surrogate is not available should always be an * instance of InlineFragmentRenderer. * - * @param SurrogateInterface $surrogate An Surrogate instance * @param FragmentRendererInterface $inlineStrategy The inline strategy to use when the surrogate is not supported - * @param UriSigner $signer */ public function __construct(SurrogateInterface $surrogate = null, FragmentRendererInterface $inlineStrategy, UriSigner $signer = null) { @@ -63,7 +61,7 @@ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRendere { if (!$this->surrogate || !$this->surrogate->hasSurrogateCapability($request)) { if ($uri instanceof ControllerReference && $this->containsNonScalars($uri->attributes)) { - @trigger_error('Passing non-scalar values as part of URI attributes to the ESI and SSI rendering strategies is deprecated since Symfony 3.1, and will be removed in 4.0. Use a different rendering strategy or pass scalar values.', \E_USER_DEPRECATED); + throw new \InvalidArgumentException('Passing non-scalar values as part of URI attributes to the ESI and SSI rendering strategies is not supported. Use a different rendering strategy or pass scalar values.'); } return $this->inlineStrategy->render($uri, $request, $options); @@ -73,34 +71,29 @@ abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRendere $uri = $this->generateSignedFragmentUri($uri, $request); } - $alt = isset($options['alt']) ? $options['alt'] : null; + $alt = $options['alt'] ?? null; if ($alt instanceof ControllerReference) { $alt = $this->generateSignedFragmentUri($alt, $request); } - $tag = $this->surrogate->renderIncludeTag($uri, $alt, isset($options['ignore_errors']) ? $options['ignore_errors'] : false, isset($options['comment']) ? $options['comment'] : ''); + $tag = $this->surrogate->renderIncludeTag($uri, $alt, $options['ignore_errors'] ?? false, $options['comment'] ?? ''); return new Response($tag); } - private function generateSignedFragmentUri($uri, Request $request) + private function generateSignedFragmentUri(ControllerReference $uri, Request $request): string { - if (null === $this->signer) { - throw new \LogicException('You must use a URI when using the ESI rendering strategy or set a URL signer.'); - } - - // we need to sign the absolute URI, but want to return the path only. - $fragmentUri = $this->signer->sign($this->generateFragmentUri($uri, $request, true)); - - return substr($fragmentUri, \strlen($request->getSchemeAndHttpHost())); + return (new FragmentUriGenerator($this->fragmentPath, $this->signer))->generate($uri, $request); } - private function containsNonScalars(array $values) + private function containsNonScalars(array $values): bool { foreach ($values as $value) { - if (\is_array($value)) { - return $this->containsNonScalars($value); - } elseif (!is_scalar($value) && null !== $value) { + if (\is_scalar($value) || null === $value) { + continue; + } + + if (!\is_array($value) || $this->containsNonScalars($value)) { return true; } } diff --git a/lib/symfony/http-kernel/Fragment/FragmentHandler.php b/lib/symfony/http-kernel/Fragment/FragmentHandler.php index b98b1779ad..1ecaaef1aa 100644 --- a/lib/symfony/http-kernel/Fragment/FragmentHandler.php +++ b/lib/symfony/http-kernel/Fragment/FragmentHandler.php @@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Exception\HttpException; /** * Renders a URI that represents a resource fragment. @@ -33,11 +34,10 @@ class FragmentHandler private $requestStack; /** - * @param RequestStack $requestStack The Request stack that controls the lifecycle of requests - * @param FragmentRendererInterface[] $renderers An array of FragmentRendererInterface instances - * @param bool $debug Whether the debug mode is enabled or not + * @param FragmentRendererInterface[] $renderers An array of FragmentRendererInterface instances + * @param bool $debug Whether the debug mode is enabled or not */ - public function __construct(RequestStack $requestStack, array $renderers = [], $debug = false) + public function __construct(RequestStack $requestStack, array $renderers = [], bool $debug = false) { $this->requestStack = $requestStack; foreach ($renderers as $renderer) { @@ -61,16 +61,14 @@ class FragmentHandler * * * ignore_errors: true to return an empty string in case of an error * - * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance - * @param string $renderer The renderer name - * @param array $options An array of options + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance * - * @return string|null The Response content or null when the Response is streamed + * @return string|null * * @throws \InvalidArgumentException when the renderer does not exist - * @throws \LogicException when no master request is being handled + * @throws \LogicException when no main request is being handled */ - public function render($uri, $renderer = 'inline', array $options = []) + public function render($uri, string $renderer = 'inline', array $options = []) { if (!isset($options['ignore_errors'])) { $options['ignore_errors'] = !$this->debug; @@ -100,7 +98,8 @@ class FragmentHandler protected function deliver(Response $response) { if (!$response->isSuccessful()) { - throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %d).', $this->requestStack->getCurrentRequest()->getUri(), $response->getStatusCode())); + $responseStatusCode = $response->getStatusCode(); + throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %d).', $this->requestStack->getCurrentRequest()->getUri(), $responseStatusCode), 0, new HttpException($responseStatusCode)); } if (!$response instanceof StreamedResponse) { diff --git a/lib/symfony/http-kernel/Fragment/FragmentRendererInterface.php b/lib/symfony/http-kernel/Fragment/FragmentRendererInterface.php index 8e454a01a6..568b1781a9 100644 --- a/lib/symfony/http-kernel/Fragment/FragmentRendererInterface.php +++ b/lib/symfony/http-kernel/Fragment/FragmentRendererInterface.php @@ -25,18 +25,16 @@ interface FragmentRendererInterface /** * Renders a URI and returns the Response content. * - * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance - * @param Request $request A Request instance - * @param array $options An array of options + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance * - * @return Response A Response instance + * @return Response */ public function render($uri, Request $request, array $options = []); /** * Gets the name of the strategy. * - * @return string The strategy name + * @return string */ public function getName(); } diff --git a/lib/symfony/http-kernel/Fragment/FragmentUriGenerator.php b/lib/symfony/http-kernel/Fragment/FragmentUriGenerator.php new file mode 100644 index 0000000000..4c0fac997a --- /dev/null +++ b/lib/symfony/http-kernel/Fragment/FragmentUriGenerator.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\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\UriSigner; + +/** + * Generates a fragment URI. + * + * @author Kévin Dunglas + * @author Fabien Potencier + */ +final class FragmentUriGenerator implements FragmentUriGeneratorInterface +{ + private $fragmentPath; + private $signer; + private $requestStack; + + public function __construct(string $fragmentPath, UriSigner $signer = null, RequestStack $requestStack = null) + { + $this->fragmentPath = $fragmentPath; + $this->signer = $signer; + $this->requestStack = $requestStack; + } + + /** + * {@inheritDoc} + */ + public function generate(ControllerReference $controller, Request $request = null, bool $absolute = false, bool $strict = true, bool $sign = true): string + { + if (null === $request && (null === $this->requestStack || null === $request = $this->requestStack->getCurrentRequest())) { + throw new \LogicException('Generating a fragment URL can only be done when handling a Request.'); + } + + if ($sign && null === $this->signer) { + throw new \LogicException('You must use a URI when using the ESI rendering strategy or set a URL signer.'); + } + + if ($strict) { + $this->checkNonScalar($controller->attributes); + } + + // We need to forward the current _format and _locale values as we don't have + // a proper routing pattern to do the job for us. + // This makes things inconsistent if you switch from rendering a controller + // to rendering a route if the route pattern does not contain the special + // _format and _locale placeholders. + if (!isset($controller->attributes['_format'])) { + $controller->attributes['_format'] = $request->getRequestFormat(); + } + if (!isset($controller->attributes['_locale'])) { + $controller->attributes['_locale'] = $request->getLocale(); + } + + $controller->attributes['_controller'] = $controller->controller; + $controller->query['_path'] = http_build_query($controller->attributes, '', '&'); + $path = $this->fragmentPath.'?'.http_build_query($controller->query, '', '&'); + + // we need to sign the absolute URI, but want to return the path only. + $fragmentUri = $sign || $absolute ? $request->getUriForPath($path) : $request->getBaseUrl().$path; + + if (!$sign) { + return $fragmentUri; + } + + $fragmentUri = $this->signer->sign($fragmentUri); + + return $absolute ? $fragmentUri : substr($fragmentUri, \strlen($request->getSchemeAndHttpHost())); + } + + private function checkNonScalar(array $values): void + { + foreach ($values as $key => $value) { + if (\is_array($value)) { + $this->checkNonScalar($value); + } elseif (!\is_scalar($value) && null !== $value) { + throw new \LogicException(sprintf('Controller attributes cannot contain non-scalar/non-null values (value for key "%s" is not a scalar or null).', $key)); + } + } + } +} diff --git a/lib/symfony/http-kernel/Fragment/FragmentUriGeneratorInterface.php b/lib/symfony/http-kernel/Fragment/FragmentUriGeneratorInterface.php new file mode 100644 index 0000000000..b211f5e373 --- /dev/null +++ b/lib/symfony/http-kernel/Fragment/FragmentUriGeneratorInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +/** + * Interface implemented by rendering strategies able to generate an URL for a fragment. + * + * @author Kévin Dunglas + */ +interface FragmentUriGeneratorInterface +{ + /** + * Generates a fragment URI for a given controller. + * + * @param bool $absolute Whether to generate an absolute URL or not + * @param bool $strict Whether to allow non-scalar attributes or not + * @param bool $sign Whether to sign the URL or not + */ + public function generate(ControllerReference $controller, Request $request = null, bool $absolute = false, bool $strict = true, bool $sign = true): string; +} diff --git a/lib/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php b/lib/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php index ed0188c5b2..446ce2d9df 100644 --- a/lib/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php +++ b/lib/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php @@ -15,11 +15,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Controller\ControllerReference; use Symfony\Component\HttpKernel\UriSigner; -use Symfony\Component\Templating\EngineInterface; use Twig\Environment; -use Twig\Error\LoaderError; -use Twig\Loader\ExistsLoaderInterface; -use Twig\Loader\SourceContextLoaderInterface; /** * Implements the Hinclude rendering strategy. @@ -30,47 +26,28 @@ class HIncludeFragmentRenderer extends RoutableFragmentRenderer { private $globalDefaultTemplate; private $signer; - private $templating; + private $twig; private $charset; /** - * @param EngineInterface|Environment $templating An EngineInterface or a Twig instance - * @param UriSigner $signer A UriSigner instance - * @param string $globalDefaultTemplate The global default content (it can be a template name or the content) - * @param string $charset + * @param string $globalDefaultTemplate The global default content (it can be a template name or the content) */ - public function __construct($templating = null, UriSigner $signer = null, $globalDefaultTemplate = null, $charset = 'utf-8') + public function __construct(Environment $twig = null, UriSigner $signer = null, string $globalDefaultTemplate = null, string $charset = 'utf-8') { - $this->setTemplating($templating); + $this->twig = $twig; $this->globalDefaultTemplate = $globalDefaultTemplate; $this->signer = $signer; $this->charset = $charset; } - /** - * Sets the templating engine to use to render the default content. - * - * @param EngineInterface|Environment|null $templating An EngineInterface or an Environment instance - * - * @throws \InvalidArgumentException - */ - public function setTemplating($templating) - { - if (null !== $templating && !$templating instanceof EngineInterface && !$templating instanceof Environment) { - throw new \InvalidArgumentException('The hinclude rendering strategy needs an instance of Twig\Environment or Symfony\Component\Templating\EngineInterface.'); - } - - $this->templating = $templating; - } - /** * Checks if a templating engine has been set. * - * @return bool true if the templating engine has been set, false otherwise + * @return bool */ public function hasTemplating() { - return null !== $this->templating; + return null !== $this->twig; } /** @@ -85,20 +62,15 @@ class HIncludeFragmentRenderer extends RoutableFragmentRenderer public function render($uri, Request $request, array $options = []) { if ($uri instanceof ControllerReference) { - if (null === $this->signer) { - throw new \LogicException('You must use a proper URI when using the Hinclude rendering strategy or set a URL signer.'); - } - - // we need to sign the absolute URI, but want to return the path only. - $uri = substr($this->signer->sign($this->generateFragmentUri($uri, $request, true)), \strlen($request->getSchemeAndHttpHost())); + $uri = (new FragmentUriGenerator($this->fragmentPath, $this->signer))->generate($uri, $request); } // We need to replace ampersands in the URI with the encoded form in order to return valid html/xml content. $uri = str_replace('&', '&', $uri); - $template = isset($options['default']) ? $options['default'] : $this->globalDefaultTemplate; - if (null !== $this->templating && $template && $this->templateExists($template)) { - $content = $this->templating->render($template); + $template = $options['default'] ?? $this->globalDefaultTemplate; + if (null !== $this->twig && $template && $this->twig->getLoader()->exists($template)) { + $content = $this->twig->render($template); } else { $content = $template; } @@ -122,41 +94,6 @@ class HIncludeFragmentRenderer extends RoutableFragmentRenderer return new Response(sprintf('%s', $uri, $renderedAttributes, $content)); } - /** - * @param string $template - * - * @return bool - */ - private function templateExists($template) - { - if ($this->templating instanceof EngineInterface) { - try { - return $this->templating->exists($template); - } catch (\Exception $e) { - return false; - } - } - - $loader = $this->templating->getLoader(); - - if (1 === Environment::MAJOR_VERSION && !$loader instanceof ExistsLoaderInterface) { - try { - if ($loader instanceof SourceContextLoaderInterface) { - $loader->getSourceContext($template); - } else { - $loader->getSource($template); - } - - return true; - } catch (LoaderError $e) { - } - - return false; - } - - return $loader->exists($template); - } - /** * {@inheritdoc} */ diff --git a/lib/symfony/http-kernel/Fragment/InlineFragmentRenderer.php b/lib/symfony/http-kernel/Fragment/InlineFragmentRenderer.php index ce88bd45de..ea45fdcb3f 100644 --- a/lib/symfony/http-kernel/Fragment/InlineFragmentRenderer.php +++ b/lib/symfony/http-kernel/Fragment/InlineFragmentRenderer.php @@ -11,14 +11,14 @@ namespace Symfony\Component\HttpKernel\Fragment; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Controller\ControllerReference; -use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\HttpCache\SubRequestHandler; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * Implements the inline rendering strategy where the Request is rendered by the current HTTP kernel. @@ -82,9 +82,9 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer // we dispatch the exception event to trigger the logging // the response that comes back is ignored if (isset($options['ignore_errors']) && $options['ignore_errors'] && $this->dispatcher) { - $event = new GetResponseForExceptionEvent($this->kernel, $request, HttpKernelInterface::SUB_REQUEST, $e); + $event = new ExceptionEvent($this->kernel, $request, HttpKernelInterface::SUB_REQUEST, $e); - $this->dispatcher->dispatch(KernelEvents::EXCEPTION, $event); + $this->dispatcher->dispatch($event, KernelEvents::EXCEPTION); } // let's clean up the output buffers that were created by the sub-request @@ -105,7 +105,7 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer } } - protected function createSubRequest($uri, Request $request) + protected function createSubRequest(string $uri, Request $request) { $cookies = $request->cookies->all(); $server = $request->server->all(); @@ -118,9 +118,12 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer $subRequest->headers->set('Surrogate-Capability', $request->headers->get('Surrogate-Capability')); } - if ($session = $request->getSession()) { - $subRequest->setSession($session); + static $setSession; + + if (null === $setSession) { + $setSession = \Closure::bind(static function ($subRequest, $request) { $subRequest->session = $request->session; }, null, Request::class); } + $setSession($subRequest, $request); if ($request->get('_format')) { $subRequest->attributes->set('_format', $request->get('_format')); diff --git a/lib/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php b/lib/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php index 0c1b95d4e9..e922ffb64d 100644 --- a/lib/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php +++ b/lib/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php @@ -22,16 +22,17 @@ use Symfony\Component\HttpKernel\EventListener\FragmentListener; */ abstract class RoutableFragmentRenderer implements FragmentRendererInterface { - private $fragmentPath = '/_fragment'; + /** + * @internal + */ + protected $fragmentPath = '/_fragment'; /** * Sets the fragment path that triggers the fragment listener. * - * @param string $path The path - * * @see FragmentListener */ - public function setFragmentPath($path) + public function setFragmentPath(string $path) { $this->fragmentPath = $path; } @@ -39,52 +40,13 @@ abstract class RoutableFragmentRenderer implements FragmentRendererInterface /** * Generates a fragment URI for a given controller. * - * @param ControllerReference $reference A ControllerReference instance - * @param Request $request A Request instance - * @param bool $absolute Whether to generate an absolute URL or not - * @param bool $strict Whether to allow non-scalar attributes or not + * @param bool $absolute Whether to generate an absolute URL or not + * @param bool $strict Whether to allow non-scalar attributes or not * - * @return string A fragment URI + * @return string */ - protected function generateFragmentUri(ControllerReference $reference, Request $request, $absolute = false, $strict = true) + protected function generateFragmentUri(ControllerReference $reference, Request $request, bool $absolute = false, bool $strict = true) { - if ($strict) { - $this->checkNonScalar($reference->attributes); - } - - // We need to forward the current _format and _locale values as we don't have - // a proper routing pattern to do the job for us. - // This makes things inconsistent if you switch from rendering a controller - // to rendering a route if the route pattern does not contain the special - // _format and _locale placeholders. - if (!isset($reference->attributes['_format'])) { - $reference->attributes['_format'] = $request->getRequestFormat(); - } - if (!isset($reference->attributes['_locale'])) { - $reference->attributes['_locale'] = $request->getLocale(); - } - - $reference->attributes['_controller'] = $reference->controller; - - $reference->query['_path'] = http_build_query($reference->attributes, '', '&'); - - $path = $this->fragmentPath.'?'.http_build_query($reference->query, '', '&'); - - if ($absolute) { - return $request->getUriForPath($path); - } - - return $request->getBaseUrl().$path; - } - - private function checkNonScalar($values) - { - foreach ($values as $key => $value) { - if (\is_array($value)) { - $this->checkNonScalar($value); - } elseif (!is_scalar($value) && null !== $value) { - throw new \LogicException(sprintf('Controller attributes cannot contain non-scalar/non-null values (value for key "%s" is not a scalar or null).', $key)); - } - } + return (new FragmentUriGenerator($this->fragmentPath))->generate($reference, $request, $absolute, $strict, false); } } diff --git a/lib/symfony/http-kernel/HttpCache/AbstractSurrogate.php b/lib/symfony/http-kernel/HttpCache/AbstractSurrogate.php index 472d87e483..f2d809e8de 100644 --- a/lib/symfony/http-kernel/HttpCache/AbstractSurrogate.php +++ b/lib/symfony/http-kernel/HttpCache/AbstractSurrogate.php @@ -41,7 +41,7 @@ abstract class AbstractSurrogate implements SurrogateInterface /** * Returns a new cache strategy instance. * - * @return ResponseCacheStrategyInterface A ResponseCacheStrategyInterface instance + * @return ResponseCacheStrategyInterface */ public function createCacheStrategy() { @@ -57,7 +57,7 @@ abstract class AbstractSurrogate implements SurrogateInterface return false; } - return false !== strpos($value, sprintf('%s/1.0', strtoupper($this->getName()))); + return str_contains($value, sprintf('%s/1.0', strtoupper($this->getName()))); } /** @@ -88,14 +88,14 @@ abstract class AbstractSurrogate implements SurrogateInterface /** * {@inheritdoc} */ - public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors) + public function handle(HttpCache $cache, string $uri, string $alt, bool $ignoreErrors) { $subRequest = Request::create($uri, Request::METHOD_GET, [], $cache->getRequest()->cookies->all(), [], $cache->getRequest()->server->all()); try { $response = $cache->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true); - if (!$response->isSuccessful()) { + if (!$response->isSuccessful() && Response::HTTP_NOT_MODIFIED !== $response->getStatusCode()) { throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %d).', $subRequest->getUri(), $response->getStatusCode())); } diff --git a/lib/symfony/http-kernel/HttpCache/Esi.php b/lib/symfony/http-kernel/HttpCache/Esi.php index 3d461a7fe3..cd6a00a10d 100644 --- a/lib/symfony/http-kernel/HttpCache/Esi.php +++ b/lib/symfony/http-kernel/HttpCache/Esi.php @@ -37,7 +37,7 @@ class Esi extends AbstractSurrogate */ public function addSurrogateControl(Response $response) { - if (false !== strpos($response->getContent(), 'getContent(), 'headers->set('Surrogate-Control', 'content="ESI/1.0"'); } } @@ -45,7 +45,7 @@ class Esi extends AbstractSurrogate /** * {@inheritdoc} */ - public function renderIncludeTag($uri, $alt = null, $ignoreErrors = true, $comment = '') + public function renderIncludeTag(string $uri, string $alt = null, bool $ignoreErrors = true, string $comment = '') { $html = sprintf('', $uri, @@ -97,7 +97,7 @@ class Esi extends AbstractSurrogate $chunks[$i] = sprintf('surrogate->handle($this, %s, %s, %s) ?>'."\n", var_export($options['src'], true), - var_export(isset($options['alt']) ? $options['alt'] : '', true), + var_export($options['alt'] ?? '', true), isset($options['onerror']) && 'continue' === $options['onerror'] ? 'true' : 'false' ); ++$i; diff --git a/lib/symfony/http-kernel/HttpCache/HttpCache.php b/lib/symfony/http-kernel/HttpCache/HttpCache.php index 3471758525..28be364c17 100644 --- a/lib/symfony/http-kernel/HttpCache/HttpCache.php +++ b/lib/symfony/http-kernel/HttpCache/HttpCache.php @@ -5,12 +5,14 @@ * * (c) Fabien Potencier * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* * This code is partially based on the Rack-Cache library by Ryan Tomayko, * which is released under the MIT license. * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. */ namespace Symfony\Component\HttpKernel\HttpCache; @@ -40,7 +42,14 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * * The available options are: * - * * debug: If true, the traces are added as a HTTP header to ease debugging + * * debug If true, exceptions are thrown when things go wrong. Otherwise, the cache + * will try to carry on and deliver a meaningful response. + * + * * trace_level May be one of 'none', 'short' and 'full'. For 'short', a concise trace of the + * main request will be added as an HTTP header. 'full' will add traces for all + * requests (including ESI subrequests). (default: 'full' if in debug; 'none' otherwise) + * + * * trace_header Header name to use for traces. (default: X-Symfony-Cache) * * * default_ttl The number of seconds that a cache entry should be considered * fresh when no explicit freshness information is provided in @@ -87,13 +96,19 @@ class HttpCache implements HttpKernelInterface, TerminableInterface 'allow_revalidate' => false, 'stale_while_revalidate' => 2, 'stale_if_error' => 60, + 'trace_level' => 'none', + 'trace_header' => 'X-Symfony-Cache', ], $options); + + if (!isset($options['trace_level'])) { + $this->options['trace_level'] = $this->options['debug'] ? 'full' : 'none'; + } } /** * Gets the current store. * - * @return StoreInterface A StoreInterface instance + * @return StoreInterface */ public function getStore() { @@ -103,17 +118,34 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Returns an array of events that took place during processing of the last request. * - * @return array An array of events + * @return array */ public function getTraces() { return $this->traces; } + private function addTraces(Response $response) + { + $traceString = null; + + if ('full' === $this->options['trace_level']) { + $traceString = $this->getLog(); + } + + if ('short' === $this->options['trace_level'] && $masterId = array_key_first($this->traces)) { + $traceString = implode('/', $this->traces[$masterId]); + } + + if (null !== $traceString) { + $response->headers->add([$this->options['trace_header'] => $traceString]); + } + } + /** * Returns a log message for the events of the last request processing. * - * @return string A log message + * @return string */ public function getLog() { @@ -126,9 +158,9 @@ class HttpCache implements HttpKernelInterface, TerminableInterface } /** - * Gets the Request instance associated with the master request. + * Gets the Request instance associated with the main request. * - * @return Request A Request instance + * @return Request */ public function getRequest() { @@ -138,7 +170,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Gets the Kernel instance. * - * @return HttpKernelInterface An HttpKernelInterface instance + * @return HttpKernelInterface */ public function getKernel() { @@ -148,7 +180,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Gets the Surrogate instance. * - * @return SurrogateInterface A Surrogate instance + * @return SurrogateInterface * * @throws \LogicException */ @@ -160,10 +192,10 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * {@inheritdoc} */ - public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true) { // FIXME: catch exceptions and implement a 500 error page here? -> in Varnish, there is a built-in error page mechanism - if (HttpKernelInterface::MASTER_REQUEST === $type) { + if (HttpKernelInterface::MAIN_REQUEST === $type) { $this->traces = []; // Keep a clone of the original request for surrogates so they can access it. // We must clone here to get a separate instance because the application will modify the request during @@ -177,7 +209,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface $this->traces[$this->getTraceKey($request)] = []; - if (!$request->isMethodSafe(false)) { + if (!$request->isMethodSafe()) { $response = $this->invalidate($request, $catch); } elseif ($request->headers->has('expect') || !$request->isMethodCacheable()) { $response = $this->pass($request, $catch); @@ -194,12 +226,12 @@ class HttpCache implements HttpKernelInterface, TerminableInterface $this->restoreResponseBody($request, $response); - if (HttpKernelInterface::MASTER_REQUEST === $type && $this->options['debug']) { - $response->headers->set('X-Symfony-Cache', $this->getLog()); + if (HttpKernelInterface::MAIN_REQUEST === $type) { + $this->addTraces($response); } if (null !== $this->surrogate) { - if (HttpKernelInterface::MASTER_REQUEST === $type) { + if (HttpKernelInterface::MAIN_REQUEST === $type) { $this->surrogateCacheStrategy->update($response); } else { $this->surrogateCacheStrategy->add($response); @@ -226,12 +258,11 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Forwards the Request to the backend without storing the Response in the cache. * - * @param Request $request A Request instance - * @param bool $catch Whether to process exceptions + * @param bool $catch Whether to process exceptions * - * @return Response A Response instance + * @return Response */ - protected function pass(Request $request, $catch = false) + protected function pass(Request $request, bool $catch = false) { $this->record($request, 'pass'); @@ -241,16 +272,15 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Invalidates non-safe methods (like POST, PUT, and DELETE). * - * @param Request $request A Request instance - * @param bool $catch Whether to process exceptions + * @param bool $catch Whether to process exceptions * - * @return Response A Response instance + * @return Response * * @throws \Exception * * @see RFC2616 13.10 */ - protected function invalidate(Request $request, $catch = false) + protected function invalidate(Request $request, bool $catch = false) { $response = $this->pass($request, $catch); @@ -290,14 +320,13 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * the backend using conditional GET. When no matching cache entry is found, * it triggers "miss" processing. * - * @param Request $request A Request instance - * @param bool $catch Whether to process exceptions + * @param bool $catch Whether to process exceptions * - * @return Response A Response instance + * @return Response * * @throws \Exception */ - protected function lookup(Request $request, $catch = false) + protected function lookup(Request $request, bool $catch = false) { try { $entry = $this->store->lookup($request); @@ -340,13 +369,11 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * The original request is used as a template for a conditional * GET request with the backend. * - * @param Request $request A Request instance - * @param Response $entry A Response instance to validate - * @param bool $catch Whether to process exceptions + * @param bool $catch Whether to process exceptions * - * @return Response A Response instance + * @return Response */ - protected function validate(Request $request, Response $entry, $catch = false) + protected function validate(Request $request, Response $entry, bool $catch = false) { $subRequest = clone $request; @@ -357,7 +384,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface // add our cached last-modified validator if ($entry->headers->has('Last-Modified')) { - $subRequest->headers->set('if_modified_since', $entry->headers->get('Last-Modified')); + $subRequest->headers->set('If-Modified-Since', $entry->headers->get('Last-Modified')); } // Add our cached etag validator to the environment. @@ -366,7 +393,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface $cachedEtags = $entry->getEtag() ? [$entry->getEtag()] : []; $requestEtags = $request->getETags(); if ($etags = array_unique(array_merge($cachedEtags, $requestEtags))) { - $subRequest->headers->set('if_none_match', implode(', ', $etags)); + $subRequest->headers->set('If-None-Match', implode(', ', $etags)); } $response = $this->forward($subRequest, $catch, $entry); @@ -405,12 +432,11 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * Unconditionally fetches a fresh response from the backend and * stores it in the cache if is cacheable. * - * @param Request $request A Request instance - * @param bool $catch Whether to process exceptions + * @param bool $catch Whether to process exceptions * - * @return Response A Response instance + * @return Response */ - protected function fetch(Request $request, $catch = false) + protected function fetch(Request $request, bool $catch = false) { $subRequest = clone $request; @@ -420,8 +446,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface } // avoid that the backend sends no content - $subRequest->headers->remove('if_modified_since'); - $subRequest->headers->remove('if_none_match'); + $subRequest->headers->remove('If-Modified-Since'); + $subRequest->headers->remove('If-None-Match'); $response = $this->forward($subRequest, $catch); @@ -441,16 +467,16 @@ class HttpCache implements HttpKernelInterface, TerminableInterface * @param bool $catch Whether to catch exceptions or not * @param Response|null $entry A Response instance (the stale entry if present, null otherwise) * - * @return Response A Response instance + * @return Response */ - protected function forward(Request $request, $catch = false, Response $entry = null) + protected function forward(Request $request, bool $catch = false, Response $entry = null) { if ($this->surrogate) { $this->surrogate->addSurrogateCapability($request); } // always a "master" request (as the real master request can be in cache) - $response = SubRequestHandler::handle($this->kernel, $request, HttpKernelInterface::MASTER_REQUEST, $catch); + $response = SubRequestHandler::handle($this->kernel, $request, HttpKernelInterface::MAIN_REQUEST, $catch); /* * Support stale-if-error given on Responses or as a config option. @@ -514,7 +540,7 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Checks whether the cache entry is "fresh enough" to satisfy the Request. * - * @return bool true if the cache entry if fresh enough, false otherwise + * @return bool */ protected function isFreshEnough(Request $request, Response $entry) { @@ -641,10 +667,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Checks if the Request includes authorization or other sensitive information * that should cause the Response to be considered private by default. - * - * @return bool true if the Request is private, false otherwise */ - private function isPrivateRequest(Request $request) + private function isPrivateRequest(Request $request): bool { foreach ($this->options['private_headers'] as $key) { $key = strtolower(str_replace('HTTP_', '', $key)); @@ -663,21 +687,16 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Records that an event took place. - * - * @param Request $request A Request instance - * @param string $event The event name */ - private function record(Request $request, $event) + private function record(Request $request, string $event) { $this->traces[$this->getTraceKey($request)][] = $event; } /** * Calculates the key we use in the "trace" array for a given request. - * - * @return string */ - private function getTraceKey(Request $request) + private function getTraceKey(Request $request): string { $path = $request->getPathInfo(); if ($qs = $request->getQueryString()) { @@ -690,10 +709,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface /** * Checks whether the given (cached) response may be served as "stale" when a revalidation * is currently in progress. - * - * @return bool true when the stale response may be served, false otherwise */ - private function mayServeStaleWhileRevalidate(Response $entry) + private function mayServeStaleWhileRevalidate(Response $entry): bool { $timeout = $entry->headers->getCacheControlDirective('stale-while-revalidate'); @@ -701,17 +718,13 @@ class HttpCache implements HttpKernelInterface, TerminableInterface $timeout = $this->options['stale_while_revalidate']; } - return abs($entry->getTtl()) < $timeout; + return abs($entry->getTtl() ?? 0) < $timeout; } /** * Waits for the store to release a locked entry. - * - * @param Request $request The request to wait for - * - * @return bool true if the lock was released before the internal timeout was hit; false if the wait timeout was exceeded */ - private function waitForLock(Request $request) + private function waitForLock(Request $request): bool { $wait = 0; while ($this->store->isLocked($request) && $wait < 100) { diff --git a/lib/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php b/lib/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php index aee689e1ce..cf8682257e 100644 --- a/lib/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php +++ b/lib/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php @@ -17,7 +17,7 @@ use Symfony\Component\HttpFoundation\Response; * ResponseCacheStrategy knows how to compute the Response cache HTTP header * based on the different response cache headers. * - * This implementation changes the master response TTL to the smallest TTL received + * This implementation changes the main response TTL to the smallest TTL received * or force validation if one of the surrogates has validation cache strategy. * * @author Fabien Potencier @@ -27,12 +27,12 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface /** * Cache-Control headers that are sent to the final response if they appear in ANY of the responses. */ - private static $overrideDirectives = ['private', 'no-cache', 'no-store', 'no-transform', 'must-revalidate', 'proxy-revalidate']; + private const OVERRIDE_DIRECTIVES = ['private', 'no-cache', 'no-store', 'no-transform', 'must-revalidate', 'proxy-revalidate']; /** * Cache-Control headers that are sent to the final response if they appear in ALL of the responses. */ - private static $inheritDirectives = ['public', 'immutable']; + private const INHERIT_DIRECTIVES = ['public', 'immutable']; private $embeddedResponses = 0; private $isNotCacheableResponseEmbedded = false; @@ -60,13 +60,13 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface { ++$this->embeddedResponses; - foreach (self::$overrideDirectives as $directive) { + foreach (self::OVERRIDE_DIRECTIVES as $directive) { if ($response->headers->hasCacheControlDirective($directive)) { $this->flagDirectives[$directive] = true; } } - foreach (self::$inheritDirectives as $directive) { + foreach (self::INHERIT_DIRECTIVES as $directive) { if (false !== $this->flagDirectives[$directive]) { $this->flagDirectives[$directive] = $response->headers->hasCacheControlDirective($directive); } @@ -81,12 +81,15 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface return; } - $this->storeRelativeAgeDirective('max-age', $response->headers->getCacheControlDirective('max-age'), $age); - $this->storeRelativeAgeDirective('s-maxage', $response->headers->getCacheControlDirective('s-maxage') ?: $response->headers->getCacheControlDirective('max-age'), $age); + $isHeuristicallyCacheable = $response->headers->hasCacheControlDirective('public'); + $maxAge = $response->headers->hasCacheControlDirective('max-age') ? (int) $response->headers->getCacheControlDirective('max-age') : null; + $this->storeRelativeAgeDirective('max-age', $maxAge, $age, $isHeuristicallyCacheable); + $sharedMaxAge = $response->headers->hasCacheControlDirective('s-maxage') ? (int) $response->headers->getCacheControlDirective('s-maxage') : $maxAge; + $this->storeRelativeAgeDirective('s-maxage', $sharedMaxAge, $age, $isHeuristicallyCacheable); $expires = $response->getExpires(); $expires = null !== $expires ? (int) $expires->format('U') - (int) $response->getDate()->format('U') : null; - $this->storeRelativeAgeDirective('expires', $expires >= 0 ? $expires : null, 0); + $this->storeRelativeAgeDirective('expires', $expires >= 0 ? $expires : null, 0, $isHeuristicallyCacheable); } /** @@ -153,10 +156,8 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface * RFC2616, Section 13.4. * * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4 - * - * @return bool */ - private function willMakeFinalResponseUncacheable(Response $response) + private function willMakeFinalResponseUncacheable(Response $response): bool { // RFC2616: A response received with a status code of 200, 203, 300, 301 or 410 // MAY be stored by a cache […] unless a cache-control directive prohibits caching. @@ -199,15 +200,29 @@ class ResponseCacheStrategy implements ResponseCacheStrategyInterface * we have to subtract the age so that the value is normalized for an age of 0. * * If the value is lower than the currently stored value, we update the value, to keep a rolling - * minimal value of each instruction. If the value is NULL, the directive will not be set on the final response. + * minimal value of each instruction. * - * @param string $directive - * @param int|null $value - * @param int $age + * If the value is NULL and the isHeuristicallyCacheable parameter is false, the directive will + * not be set on the final response. In this case, not all responses had the directive set and no + * value can be found that satisfies the requirements of all responses. The directive will be dropped + * from the final response. + * + * If the isHeuristicallyCacheable parameter is true, however, the current response has been marked + * as cacheable in a public (shared) cache, but did not provide an explicit lifetime that would serve + * as an upper bound. In this case, we can proceed and possibly keep the directive on the final response. */ - private function storeRelativeAgeDirective($directive, $value, $age) + private function storeRelativeAgeDirective(string $directive, ?int $value, int $age, bool $isHeuristicallyCacheable) { if (null === $value) { + if ($isHeuristicallyCacheable) { + /* + * See https://datatracker.ietf.org/doc/html/rfc7234#section-4.2.2 + * This particular response does not require maximum lifetime; heuristics might be applied. + * Other responses, however, might have more stringent requirements on maximum lifetime. + * So, return early here so that the final response can have the more limiting value set. + */ + return; + } $this->ageDirectives[$directive] = false; } diff --git a/lib/symfony/http-kernel/HttpCache/Ssi.php b/lib/symfony/http-kernel/HttpCache/Ssi.php index 6dba4e11df..f114e05cfb 100644 --- a/lib/symfony/http-kernel/HttpCache/Ssi.php +++ b/lib/symfony/http-kernel/HttpCache/Ssi.php @@ -34,7 +34,7 @@ class Ssi extends AbstractSurrogate */ public function addSurrogateControl(Response $response) { - if (false !== strpos($response->getContent(), '', $uri); } diff --git a/lib/symfony/http-kernel/HttpCache/Store.php b/lib/symfony/http-kernel/HttpCache/Store.php index 0a93eb0eee..c777391385 100644 --- a/lib/symfony/http-kernel/HttpCache/Store.php +++ b/lib/symfony/http-kernel/HttpCache/Store.php @@ -25,22 +25,21 @@ use Symfony\Component\HttpFoundation\Response; class Store implements StoreInterface { protected $root; + /** @var \SplObjectStorage */ private $keyCache; - private $locks; + /** @var array */ + private $locks = []; /** - * @param string $root The path to the cache directory - * * @throws \RuntimeException */ - public function __construct($root) + public function __construct(string $root) { $this->root = $root; - if (!file_exists($this->root) && !@mkdir($this->root, 0777, true) && !is_dir($this->root)) { + if (!is_dir($this->root) && !@mkdir($this->root, 0777, true) && !is_dir($this->root)) { throw new \RuntimeException(sprintf('Unable to create the store directory (%s).', $this->root)); } $this->keyCache = new \SplObjectStorage(); - $this->locks = []; } /** @@ -68,10 +67,10 @@ class Store implements StoreInterface if (!isset($this->locks[$key])) { $path = $this->getPath($key); - if (!file_exists(\dirname($path)) && false === @mkdir(\dirname($path), 0777, true) && !is_dir(\dirname($path))) { + if (!is_dir(\dirname($path)) && false === @mkdir(\dirname($path), 0777, true) && !is_dir(\dirname($path))) { return $path; } - $h = fopen($path, 'cb'); + $h = fopen($path, 'c'); if (!flock($h, \LOCK_EX | \LOCK_NB)) { fclose($h); @@ -112,11 +111,11 @@ class Store implements StoreInterface return true; // shortcut if lock held by this process } - if (!file_exists($path = $this->getPath($key))) { + if (!is_file($path = $this->getPath($key))) { return false; } - $h = fopen($path, 'rb'); + $h = fopen($path, 'r'); flock($h, \LOCK_EX | \LOCK_NB, $wouldBlock); flock($h, \LOCK_UN); // release the lock we just acquired fclose($h); @@ -127,7 +126,7 @@ class Store implements StoreInterface /** * Locates a cached Response for the Request provided. * - * @return Response|null A Response instance, or null if no cache entry was found + * @return Response|null */ public function lookup(Request $request) { @@ -168,7 +167,7 @@ class Store implements StoreInterface * Existing entries are read and any that match the response are removed. This * method calls write with the new list of cache entries. * - * @return string The key under which the response is stored + * @return string * * @throws \RuntimeException */ @@ -209,7 +208,7 @@ class Store implements StoreInterface $entry[1]['vary'] = ['']; } - if ($entry[1]['vary'][0] != $vary || !$this->requestsMatch($vary, $entry[0], $storedEnv)) { + if ($entry[1]['vary'][0] != $vary || !$this->requestsMatch($vary ?? '', $entry[0], $storedEnv)) { $entries[] = $entry; } } @@ -267,13 +266,11 @@ class Store implements StoreInterface * Determines whether two Request HTTP header sets are non-varying based on * the vary response header value provided. * - * @param string $vary A Response vary header - * @param array $env1 A Request HTTP header array - * @param array $env2 A Request HTTP header array - * - * @return bool true if the two environments match, false otherwise + * @param string|null $vary A Response vary header + * @param array $env1 A Request HTTP header array + * @param array $env2 A Request HTTP header array */ - private function requestsMatch($vary, $env1, $env2) + private function requestsMatch(?string $vary, array $env1, array $env2): bool { if (empty($vary)) { return true; @@ -281,8 +278,8 @@ class Store implements StoreInterface foreach (preg_split('/[\s,]+/', $vary) as $header) { $key = str_replace('_', '-', strtolower($header)); - $v1 = isset($env1[$key]) ? $env1[$key] : null; - $v2 = isset($env2[$key]) ? $env2[$key] : null; + $v1 = $env1[$key] ?? null; + $v2 = $env2[$key] ?? null; if ($v1 !== $v2) { return false; } @@ -295,18 +292,14 @@ class Store implements StoreInterface * Gets all data associated with the given key. * * Use this method only if you know what you are doing. - * - * @param string $key The store key - * - * @return array An array of data associated with the key */ - private function getMetadata($key) + private function getMetadata(string $key): array { if (!$entries = $this->load($key)) { return []; } - return unserialize($entries); + return unserialize($entries) ?: []; } /** @@ -314,11 +307,9 @@ class Store implements StoreInterface * * This method purges both the HTTP and the HTTPS version of the cache entry. * - * @param string $url A URL - * * @return bool true if the URL exists with either HTTP or HTTPS scheme and has been purged, false otherwise */ - public function purge($url) + public function purge(string $url) { $http = preg_replace('#^https:#', 'http:', $url); $https = preg_replace('#^http:#', 'https:', $url); @@ -331,12 +322,8 @@ class Store implements StoreInterface /** * Purges data for the given URL. - * - * @param string $url A URL - * - * @return bool true if the URL exists and has been purged, false otherwise */ - private function doPurge($url) + private function doPurge(string $url): bool { $key = $this->getCacheKey(Request::create($url)); if (isset($this->locks[$key])) { @@ -345,7 +332,7 @@ class Store implements StoreInterface unset($this->locks[$key]); } - if (file_exists($path = $this->getPath($key))) { + if (is_file($path = $this->getPath($key))) { unlink($path); return true; @@ -356,28 +343,18 @@ class Store implements StoreInterface /** * Loads data for the given key. - * - * @param string $key The store key - * - * @return string|null The data associated with the key */ - private function load($key) + private function load(string $key): ?string { $path = $this->getPath($key); - return file_exists($path) && false !== ($contents = file_get_contents($path)) ? $contents : null; + return is_file($path) && false !== ($contents = @file_get_contents($path)) ? $contents : null; } /** * Save data for the given key. - * - * @param string $key The store key - * @param string $data The data to store - * @param bool $overwrite Whether existing data should be overwritten - * - * @return bool */ - private function save($key, $data, $overwrite = true) + private function save(string $key, string $data, bool $overwrite = true): bool { $path = $this->getPath($key); @@ -396,12 +373,12 @@ class Store implements StoreInterface return false; } } else { - if (!file_exists(\dirname($path)) && false === @mkdir(\dirname($path), 0777, true) && !is_dir(\dirname($path))) { + if (!is_dir(\dirname($path)) && false === @mkdir(\dirname($path), 0777, true) && !is_dir(\dirname($path))) { return false; } $tmpFile = tempnam(\dirname($path), basename($path)); - if (false === $fp = @fopen($tmpFile, 'wb')) { + if (false === $fp = @fopen($tmpFile, 'w')) { @unlink($tmpFile); return false; @@ -427,7 +404,7 @@ class Store implements StoreInterface return true; } - public function getPath($key) + public function getPath(string $key) { return $this->root.\DIRECTORY_SEPARATOR.substr($key, 0, 2).\DIRECTORY_SEPARATOR.substr($key, 2, 2).\DIRECTORY_SEPARATOR.substr($key, 4, 2).\DIRECTORY_SEPARATOR.substr($key, 6); } @@ -442,7 +419,7 @@ class Store implements StoreInterface * headers, use a Vary header to indicate them, and each representation will * be stored independently under the same cache key. * - * @return string A key for the given Request + * @return string */ protected function generateCacheKey(Request $request) { @@ -451,10 +428,8 @@ class Store implements StoreInterface /** * Returns a cache key for the given Request. - * - * @return string A key for the given Request */ - private function getCacheKey(Request $request) + private function getCacheKey(Request $request): string { if (isset($this->keyCache[$request])) { return $this->keyCache[$request]; @@ -465,20 +440,16 @@ class Store implements StoreInterface /** * Persists the Request HTTP headers. - * - * @return array An array of HTTP headers */ - private function persistRequest(Request $request) + private function persistRequest(Request $request): array { return $request->headers->all(); } /** * Persists the Response HTTP headers. - * - * @return array An array of HTTP headers */ - private function persistResponse(Response $response) + private function persistResponse(Response $response): array { $headers = $response->headers->all(); $headers['X-Status'] = [$response->getStatusCode()]; @@ -488,13 +459,8 @@ class Store implements StoreInterface /** * Restores a Response from the HTTP headers and body. - * - * @param array $headers An array of HTTP headers for the Response - * @param string $path Path to the Response body - * - * @return Response */ - private function restoreResponse($headers, $path = null) + private function restoreResponse(array $headers, string $path = null): Response { $status = $headers['X-Status'][0]; unset($headers['X-Status']); diff --git a/lib/symfony/http-kernel/HttpCache/StoreInterface.php b/lib/symfony/http-kernel/HttpCache/StoreInterface.php index 8f1cf4409e..3d07ef3fc3 100644 --- a/lib/symfony/http-kernel/HttpCache/StoreInterface.php +++ b/lib/symfony/http-kernel/HttpCache/StoreInterface.php @@ -27,7 +27,7 @@ interface StoreInterface /** * Locates a cached Response for the Request provided. * - * @return Response|null A Response instance, or null if no cache entry was found + * @return Response|null */ public function lookup(Request $request); @@ -70,11 +70,9 @@ interface StoreInterface /** * Purges data for the given URL. * - * @param string $url A URL - * * @return bool true if the URL exists and has been purged, false otherwise */ - public function purge($url); + public function purge(string $url); /** * Cleanups storage. diff --git a/lib/symfony/http-kernel/HttpCache/SubRequestHandler.php b/lib/symfony/http-kernel/HttpCache/SubRequestHandler.php index 895207152b..253071f07d 100644 --- a/lib/symfony/http-kernel/HttpCache/SubRequestHandler.php +++ b/lib/symfony/http-kernel/HttpCache/SubRequestHandler.php @@ -23,42 +23,26 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; */ class SubRequestHandler { - /** - * @return Response - */ - public static function handle(HttpKernelInterface $kernel, Request $request, $type, $catch) + public static function handle(HttpKernelInterface $kernel, Request $request, int $type, bool $catch): Response { // save global state related to trusted headers and proxies $trustedProxies = Request::getTrustedProxies(); $trustedHeaderSet = Request::getTrustedHeaderSet(); - if (method_exists(Request::class, 'getTrustedHeaderName')) { - Request::setTrustedProxies($trustedProxies, -1); - $trustedHeaders = [ - Request::HEADER_FORWARDED => Request::getTrustedHeaderName(Request::HEADER_FORWARDED, false), - Request::HEADER_X_FORWARDED_FOR => Request::getTrustedHeaderName(Request::HEADER_X_FORWARDED_FOR, false), - Request::HEADER_X_FORWARDED_HOST => Request::getTrustedHeaderName(Request::HEADER_X_FORWARDED_HOST, false), - Request::HEADER_X_FORWARDED_PROTO => Request::getTrustedHeaderName(Request::HEADER_X_FORWARDED_PROTO, false), - Request::HEADER_X_FORWARDED_PORT => Request::getTrustedHeaderName(Request::HEADER_X_FORWARDED_PORT, false), - ]; - Request::setTrustedProxies($trustedProxies, $trustedHeaderSet); - } else { - $trustedHeaders = [ - Request::HEADER_FORWARDED => 'FORWARDED', - Request::HEADER_X_FORWARDED_FOR => 'X_FORWARDED_FOR', - Request::HEADER_X_FORWARDED_HOST => 'X_FORWARDED_HOST', - Request::HEADER_X_FORWARDED_PROTO => 'X_FORWARDED_PROTO', - Request::HEADER_X_FORWARDED_PORT => 'X_FORWARDED_PORT', - ]; - } // remove untrusted values $remoteAddr = $request->server->get('REMOTE_ADDR'); - if (!IpUtils::checkIp($remoteAddr, $trustedProxies)) { - foreach ($trustedHeaders as $key => $name) { - if ($trustedHeaderSet & $key) { - $request->headers->remove($name); - $request->server->remove('HTTP_'.strtoupper(str_replace('-', '_', $name))); - } + if (!$remoteAddr || !IpUtils::checkIp($remoteAddr, $trustedProxies)) { + $trustedHeaders = [ + 'FORWARDED' => $trustedHeaderSet & Request::HEADER_FORWARDED, + 'X_FORWARDED_FOR' => $trustedHeaderSet & Request::HEADER_X_FORWARDED_FOR, + 'X_FORWARDED_HOST' => $trustedHeaderSet & Request::HEADER_X_FORWARDED_HOST, + 'X_FORWARDED_PROTO' => $trustedHeaderSet & Request::HEADER_X_FORWARDED_PROTO, + 'X_FORWARDED_PORT' => $trustedHeaderSet & Request::HEADER_X_FORWARDED_PORT, + 'X_FORWARDED_PREFIX' => $trustedHeaderSet & Request::HEADER_X_FORWARDED_PREFIX, + ]; + foreach (array_filter($trustedHeaders) as $name => $key) { + $request->headers->remove($name); + $request->server->remove('HTTP_'.$name); } } @@ -77,16 +61,16 @@ class SubRequestHandler // set trusted values, reusing as much as possible the global trusted settings if (Request::HEADER_FORWARDED & $trustedHeaderSet) { $trustedValues[0] .= sprintf(';host="%s";proto=%s', $request->getHttpHost(), $request->getScheme()); - $request->headers->set($name = $trustedHeaders[Request::HEADER_FORWARDED], $v = implode(', ', $trustedValues)); - $request->server->set('HTTP_'.strtoupper(str_replace('-', '_', $name)), $v); + $request->headers->set('Forwarded', $v = implode(', ', $trustedValues)); + $request->server->set('HTTP_FORWARDED', $v); } if (Request::HEADER_X_FORWARDED_FOR & $trustedHeaderSet) { - $request->headers->set($name = $trustedHeaders[Request::HEADER_X_FORWARDED_FOR], $v = implode(', ', $trustedIps)); - $request->server->set('HTTP_'.strtoupper(str_replace('-', '_', $name)), $v); + $request->headers->set('X-Forwarded-For', $v = implode(', ', $trustedIps)); + $request->server->set('HTTP_X_FORWARDED_FOR', $v); } elseif (!(Request::HEADER_FORWARDED & $trustedHeaderSet)) { Request::setTrustedProxies($trustedProxies, $trustedHeaderSet | Request::HEADER_X_FORWARDED_FOR); - $request->headers->set($name = $trustedHeaders[Request::HEADER_X_FORWARDED_FOR], $v = implode(', ', $trustedIps)); - $request->server->set('HTTP_'.strtoupper(str_replace('-', '_', $name)), $v); + $request->headers->set('X-Forwarded-For', $v = implode(', ', $trustedIps)); + $request->server->set('HTTP_X_FORWARDED_FOR', $v); } // fix the client IP address by setting it to 127.0.0.1, diff --git a/lib/symfony/http-kernel/HttpCache/SurrogateInterface.php b/lib/symfony/http-kernel/HttpCache/SurrogateInterface.php index 85391f8f36..3f3c74a97a 100644 --- a/lib/symfony/http-kernel/HttpCache/SurrogateInterface.php +++ b/lib/symfony/http-kernel/HttpCache/SurrogateInterface.php @@ -26,14 +26,14 @@ interface SurrogateInterface /** * Returns a new cache strategy instance. * - * @return ResponseCacheStrategyInterface A ResponseCacheStrategyInterface instance + * @return ResponseCacheStrategyInterface */ public function createCacheStrategy(); /** * Checks that at least one surrogate has Surrogate capability. * - * @return bool true if one surrogate has Surrogate capability, false otherwise + * @return bool */ public function hasSurrogateCapability(Request $request); @@ -52,21 +52,19 @@ interface SurrogateInterface /** * Checks that the Response needs to be parsed for Surrogate tags. * - * @return bool true if the Response needs to be parsed, false otherwise + * @return bool */ public function needsParsing(Response $response); /** * Renders a Surrogate tag. * - * @param string $uri A URI - * @param string $alt An alternate URI - * @param bool $ignoreErrors Whether to ignore errors or not - * @param string $comment A comment to add as an esi:include tag + * @param string $alt An alternate URI + * @param string $comment A comment to add as an esi:include tag * * @return string */ - public function renderIncludeTag($uri, $alt = null, $ignoreErrors = true, $comment = ''); + public function renderIncludeTag(string $uri, string $alt = null, bool $ignoreErrors = true, string $comment = ''); /** * Replaces a Response Surrogate tags with the included resource content. @@ -78,15 +76,12 @@ interface SurrogateInterface /** * Handles a Surrogate from the cache. * - * @param HttpCache $cache An HttpCache instance - * @param string $uri The main URI - * @param string $alt An alternative URI - * @param bool $ignoreErrors Whether to ignore errors or not + * @param string $alt An alternative URI * * @return string * * @throws \RuntimeException * @throws \Exception */ - public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors); + public function handle(HttpCache $cache, string $uri, string $alt, bool $ignoreErrors); } diff --git a/lib/symfony/http-kernel/HttpClientKernel.php b/lib/symfony/http-kernel/HttpClientKernel.php new file mode 100644 index 0000000000..58ca82e5a8 --- /dev/null +++ b/lib/symfony/http-kernel/HttpClientKernel.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpClient\HttpClient; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; +use Symfony\Component\Mime\Part\AbstractPart; +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Component\Mime\Part\Multipart\FormDataPart; +use Symfony\Component\Mime\Part\TextPart; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(ResponseHeaderBag::class); + +/** + * An implementation of a Symfony HTTP kernel using a "real" HTTP client. + * + * @author Fabien Potencier + */ +final class HttpClientKernel implements HttpKernelInterface +{ + private $client; + + public function __construct(HttpClientInterface $client = null) + { + if (null === $client && !class_exists(HttpClient::class)) { + throw new \LogicException(sprintf('You cannot use "%s" as the HttpClient component is not installed. Try running "composer require symfony/http-client".', __CLASS__)); + } + + $this->client = $client ?? HttpClient::create(); + } + + public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response + { + $headers = $this->getHeaders($request); + $body = ''; + if (null !== $part = $this->getBody($request)) { + $headers = array_merge($headers, $part->getPreparedHeaders()->toArray()); + $body = $part->bodyToIterable(); + } + $response = $this->client->request($request->getMethod(), $request->getUri(), [ + 'headers' => $headers, + 'body' => $body, + ] + $request->attributes->get('http_client_options', [])); + + $response = new Response($response->getContent(!$catch), $response->getStatusCode(), $response->getHeaders(!$catch)); + + $response->headers->remove('X-Body-File'); + $response->headers->remove('X-Body-Eval'); + $response->headers->remove('X-Content-Digest'); + + $response->headers = new class($response->headers->all()) extends ResponseHeaderBag { + protected function computeCacheControlValue(): string + { + return $this->getCacheControlHeader(); // preserve the original value + } + }; + + return $response; + } + + private function getBody(Request $request): ?AbstractPart + { + if (\in_array($request->getMethod(), ['GET', 'HEAD'])) { + return null; + } + + if (!class_exists(AbstractPart::class)) { + throw new \LogicException('You cannot pass non-empty bodies as the Mime component is not installed. Try running "composer require symfony/mime".'); + } + + if ($content = $request->getContent()) { + return new TextPart($content, 'utf-8', 'plain', '8bit'); + } + + $fields = $request->request->all(); + foreach ($request->files->all() as $name => $file) { + $fields[$name] = DataPart::fromPath($file->getPathname(), $file->getClientOriginalName(), $file->getClientMimeType()); + } + + return new FormDataPart($fields); + } + + private function getHeaders(Request $request): array + { + $headers = []; + foreach ($request->headers as $key => $value) { + $headers[$key] = $value; + } + $cookies = []; + foreach ($request->cookies->all() as $name => $value) { + $cookies[] = $name.'='.$value; + } + if ($cookies) { + $headers['cookie'] = implode('; ', $cookies); + } + + return $headers; + } +} diff --git a/lib/symfony/http-kernel/HttpKernel.php b/lib/symfony/http-kernel/HttpKernel.php index 8c20695d8c..e10e683383 100644 --- a/lib/symfony/http-kernel/HttpKernel.php +++ b/lib/symfony/http-kernel/HttpKernel.php @@ -11,7 +11,6 @@ namespace Symfony\Component\HttpKernel; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; @@ -19,17 +18,30 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; -use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent; -use Symfony\Component\HttpKernel\Event\FilterControllerEvent; -use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Event\ControllerEvent; +use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\Event\FinishRequestEvent; -use Symfony\Component\HttpKernel\Event\GetResponseEvent; -use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; -use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; -use Symfony\Component\HttpKernel\Event\PostResponseEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\Event\TerminateEvent; +use Symfony\Component\HttpKernel\Event\ViewEvent; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\ControllerDoesNotReturnResponseException; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(ControllerArgumentsEvent::class); +class_exists(ControllerEvent::class); +class_exists(ExceptionEvent::class); +class_exists(FinishRequestEvent::class); +class_exists(RequestEvent::class); +class_exists(ResponseEvent::class); +class_exists(TerminateEvent::class); +class_exists(ViewEvent::class); +class_exists(KernelEvents::class); /** * HttpKernel notifies events to convert a Request object to a Response one. @@ -47,20 +59,14 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface { $this->dispatcher = $dispatcher; $this->resolver = $resolver; - $this->requestStack = $requestStack ?: new RequestStack(); - $this->argumentResolver = $argumentResolver; - - if (null === $this->argumentResolver) { - @trigger_error(sprintf('As of 3.1 an %s is used to resolve arguments. In 4.0 the $argumentResolver becomes the %s if no other is provided instead of using the $resolver argument.', ArgumentResolverInterface::class, ArgumentResolver::class), \E_USER_DEPRECATED); - // fallback in case of deprecations - $this->argumentResolver = $resolver; - } + $this->requestStack = $requestStack ?? new RequestStack(); + $this->argumentResolver = $argumentResolver ?? new ArgumentResolver(); } /** * {@inheritdoc} */ - public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true) { $request->headers->set('X-Php-Ob-Level', (string) ob_get_level()); @@ -76,7 +82,7 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface throw $e; } - return $this->handleException($e, $request, $type); + return $this->handleThrowable($e, $request, $type); } } @@ -85,19 +91,19 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface */ public function terminate(Request $request, Response $response) { - $this->dispatcher->dispatch(KernelEvents::TERMINATE, new PostResponseEvent($this, $request, $response)); + $this->dispatcher->dispatch(new TerminateEvent($this, $request, $response), KernelEvents::TERMINATE); } /** * @internal */ - public function terminateWithException(\Exception $exception, Request $request = null) + public function terminateWithException(\Throwable $exception, Request $request = null) { - if (!$request = $request ?: $this->requestStack->getMasterRequest()) { + if (!$request = $request ?: $this->requestStack->getMainRequest()) { throw $exception; } - $response = $this->handleException($exception, $request, self::MASTER_REQUEST); + $response = $this->handleThrowable($exception, $request, self::MAIN_REQUEST); $response->sendHeaders(); $response->sendContent(); @@ -110,21 +116,16 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface * * Exceptions are not caught. * - * @param Request $request A Request instance - * @param int $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST) - * - * @return Response A Response instance - * * @throws \LogicException If one of the listener does not behave as expected * @throws NotFoundHttpException When controller cannot be found */ - private function handleRaw(Request $request, $type = self::MASTER_REQUEST) + private function handleRaw(Request $request, int $type = self::MAIN_REQUEST): Response { $this->requestStack->push($request); // request - $event = new GetResponseEvent($this, $request, $type); - $this->dispatcher->dispatch(KernelEvents::REQUEST, $event); + $event = new RequestEvent($this, $request, $type); + $this->dispatcher->dispatch($event, KernelEvents::REQUEST); if ($event->hasResponse()) { return $this->filterResponse($event->getResponse(), $request, $type); @@ -135,38 +136,37 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo())); } - $event = new FilterControllerEvent($this, $controller, $request, $type); - $this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event); + $event = new ControllerEvent($this, $controller, $request, $type); + $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER); $controller = $event->getController(); // controller arguments $arguments = $this->argumentResolver->getArguments($request, $controller); - $event = new FilterControllerArgumentsEvent($this, $controller, $arguments, $request, $type); - $this->dispatcher->dispatch(KernelEvents::CONTROLLER_ARGUMENTS, $event); + $event = new ControllerArgumentsEvent($this, $controller, $arguments, $request, $type); + $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER_ARGUMENTS); $controller = $event->getController(); $arguments = $event->getArguments(); // call controller - $response = \call_user_func_array($controller, $arguments); + $response = $controller(...$arguments); // view if (!$response instanceof Response) { - $event = new GetResponseForControllerResultEvent($this, $request, $type, $response); - $this->dispatcher->dispatch(KernelEvents::VIEW, $event); + $event = new ViewEvent($this, $request, $type, $response); + $this->dispatcher->dispatch($event, KernelEvents::VIEW); if ($event->hasResponse()) { $response = $event->getResponse(); - } - - if (!$response instanceof Response) { - $msg = sprintf('The controller must return a response (%s given).', $this->varToString($response)); + } else { + $msg = sprintf('The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned %s.', $this->varToString($response)); // the user may have forgotten to return something if (null === $response) { $msg .= ' Did you forget to add a return statement somewhere in your controller?'; } - throw new \LogicException($msg); + + throw new ControllerDoesNotReturnResponseException($msg, $controller, __FILE__, __LINE__ - 17); } } @@ -176,19 +176,13 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface /** * Filters a response object. * - * @param Response $response A Response instance - * @param Request $request An error message in case the response is not a Response object - * @param int $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST) - * - * @return Response The filtered Response instance - * * @throws \RuntimeException if the passed object is not a Response instance */ - private function filterResponse(Response $response, Request $request, $type) + private function filterResponse(Response $response, Request $request, int $type): Response { - $event = new FilterResponseEvent($this, $request, $type, $response); + $event = new ResponseEvent($this, $request, $type, $response); - $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + $this->dispatcher->dispatch($event, KernelEvents::RESPONSE); $this->finishRequest($request, $type); @@ -201,33 +195,25 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface * Note that the order of the operations is important here, otherwise * operations such as {@link RequestStack::getParentRequest()} can lead to * weird results. - * - * @param int $type */ - private function finishRequest(Request $request, $type) + private function finishRequest(Request $request, int $type) { - $this->dispatcher->dispatch(KernelEvents::FINISH_REQUEST, new FinishRequestEvent($this, $request, $type)); + $this->dispatcher->dispatch(new FinishRequestEvent($this, $request, $type), KernelEvents::FINISH_REQUEST); $this->requestStack->pop(); } /** - * Handles an exception by trying to convert it to a Response. - * - * @param \Exception $e An \Exception instance - * @param Request $request A Request instance - * @param int $type The type of the request - * - * @return Response A Response instance + * Handles a throwable by trying to convert it to a Response. * * @throws \Exception */ - private function handleException(\Exception $e, $request, $type) + private function handleThrowable(\Throwable $e, Request $request, int $type): Response { - $event = new GetResponseForExceptionEvent($this, $request, $type, $e); - $this->dispatcher->dispatch(KernelEvents::EXCEPTION, $event); + $event = new ExceptionEvent($this, $request, $type, $e); + $this->dispatcher->dispatch($event, KernelEvents::EXCEPTION); // a listener might have replaced the exception - $e = $event->getException(); + $e = $event->getThrowable(); if (!$event->hasResponse()) { $this->finishRequest($request, $type); @@ -238,13 +224,7 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface $response = $event->getResponse(); // the developer asked for a specific status code - if ($response->headers->has('X-Status-Code')) { - @trigger_error(sprintf('Using the X-Status-Code header is deprecated since Symfony 3.3 and will be removed in 4.0. Use %s::allowCustomResponseCode() instead.', GetResponseForExceptionEvent::class), \E_USER_DEPRECATED); - - $response->setStatusCode($response->headers->get('X-Status-Code')); - - $response->headers->remove('X-Status-Code'); - } elseif (!$event->isAllowingCustomResponseCode() && !$response->isClientError() && !$response->isServerError() && !$response->isRedirect()) { + if (!$event->isAllowingCustomResponseCode() && !$response->isClientError() && !$response->isServerError() && !$response->isRedirect()) { // ensure that we actually have an error response if ($e instanceof HttpExceptionInterface) { // keep the HTTP status code and headers @@ -265,23 +245,23 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface /** * Returns a human-readable string for the specified variable. */ - private function varToString($var) + private function varToString($var): string { if (\is_object($var)) { - return sprintf('Object(%s)', \get_class($var)); + return sprintf('an object of type %s', \get_class($var)); } if (\is_array($var)) { $a = []; foreach ($var as $k => $v) { - $a[] = sprintf('%s => %s', $k, $this->varToString($v)); + $a[] = sprintf('%s => ...', $k); } - return sprintf('Array(%s)', implode(', ', $a)); + return sprintf('an array ([%s])', mb_substr(implode(', ', $a), 0, 255)); } if (\is_resource($var)) { - return sprintf('Resource(%s)', get_resource_type($var)); + return sprintf('a resource (%s)', get_resource_type($var)); } if (null === $var) { @@ -289,11 +269,19 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface } if (false === $var) { - return 'false'; + return 'a boolean value (false)'; } if (true === $var) { - return 'true'; + return 'a boolean value (true)'; + } + + if (\is_string($var)) { + return sprintf('a string ("%s%s")', mb_substr($var, 0, 255), mb_strlen($var) > 255 ? '...' : ''); + } + + if (is_numeric($var)) { + return sprintf('a number (%s)', (string) $var); } return (string) $var; diff --git a/lib/symfony/http-kernel/HttpKernelBrowser.php b/lib/symfony/http-kernel/HttpKernelBrowser.php new file mode 100644 index 0000000000..643134f1bb --- /dev/null +++ b/lib/symfony/http-kernel/HttpKernelBrowser.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\BrowserKit\AbstractBrowser; +use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\BrowserKit\History; +use Symfony\Component\BrowserKit\Request as DomRequest; +use Symfony\Component\BrowserKit\Response as DomResponse; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Simulates a browser and makes requests to an HttpKernel instance. + * + * @author Fabien Potencier + * + * @method Request getRequest() + * @method Response getResponse() + */ +class HttpKernelBrowser extends AbstractBrowser +{ + protected $kernel; + private $catchExceptions = true; + + /** + * @param array $server The server parameters (equivalent of $_SERVER) + */ + public function __construct(HttpKernelInterface $kernel, array $server = [], History $history = null, CookieJar $cookieJar = null) + { + // These class properties must be set before calling the parent constructor, as it may depend on it. + $this->kernel = $kernel; + $this->followRedirects = false; + + parent::__construct($server, $history, $cookieJar); + } + + /** + * Sets whether to catch exceptions when the kernel is handling a request. + */ + public function catchExceptions(bool $catchExceptions) + { + $this->catchExceptions = $catchExceptions; + } + + /** + * {@inheritdoc} + * + * @param Request $request + * + * @return Response + */ + protected function doRequest(object $request) + { + $response = $this->kernel->handle($request, HttpKernelInterface::MAIN_REQUEST, $this->catchExceptions); + + if ($this->kernel instanceof TerminableInterface) { + $this->kernel->terminate($request, $response); + } + + return $response; + } + + /** + * {@inheritdoc} + * + * @param Request $request + * + * @return string + */ + protected function getScript(object $request) + { + $kernel = var_export(serialize($this->kernel), true); + $request = var_export(serialize($request), true); + + $errorReporting = error_reporting(); + + $requires = ''; + foreach (get_declared_classes() as $class) { + if (0 === strpos($class, 'ComposerAutoloaderInit')) { + $r = new \ReflectionClass($class); + $file = \dirname($r->getFileName(), 2).'/autoload.php'; + if (file_exists($file)) { + $requires .= 'require_once '.var_export($file, true).";\n"; + } + } + } + + if (!$requires) { + throw new \RuntimeException('Composer autoloader not found.'); + } + + $code = <<getHandleScript(); + } + + protected function getHandleScript() + { + return <<<'EOF' +$response = $kernel->handle($request); + +if ($kernel instanceof Symfony\Component\HttpKernel\TerminableInterface) { + $kernel->terminate($request, $response); +} + +echo serialize($response); +EOF; + } + + /** + * {@inheritdoc} + * + * @return Request + */ + protected function filterRequest(DomRequest $request) + { + $httpRequest = Request::create($request->getUri(), $request->getMethod(), $request->getParameters(), $request->getCookies(), $request->getFiles(), $server = $request->getServer(), $request->getContent()); + if (!isset($server['HTTP_ACCEPT'])) { + $httpRequest->headers->remove('Accept'); + } + + foreach ($this->filterFiles($httpRequest->files->all()) as $key => $value) { + $httpRequest->files->set($key, $value); + } + + return $httpRequest; + } + + /** + * Filters an array of files. + * + * This method created test instances of UploadedFile so that the move() + * method can be called on those instances. + * + * If the size of a file is greater than the allowed size (from php.ini) then + * an invalid UploadedFile is returned with an error set to UPLOAD_ERR_INI_SIZE. + * + * @see UploadedFile + * + * @return array + */ + protected function filterFiles(array $files) + { + $filtered = []; + foreach ($files as $key => $value) { + if (\is_array($value)) { + $filtered[$key] = $this->filterFiles($value); + } elseif ($value instanceof UploadedFile) { + if ($value->isValid() && $value->getSize() > UploadedFile::getMaxFilesize()) { + $filtered[$key] = new UploadedFile( + '', + $value->getClientOriginalName(), + $value->getClientMimeType(), + \UPLOAD_ERR_INI_SIZE, + true + ); + } else { + $filtered[$key] = new UploadedFile( + $value->getPathname(), + $value->getClientOriginalName(), + $value->getClientMimeType(), + $value->getError(), + true + ); + } + } + } + + return $filtered; + } + + /** + * {@inheritdoc} + * + * @param Response $response + * + * @return DomResponse + */ + protected function filterResponse(object $response) + { + // this is needed to support StreamedResponse + ob_start(); + $response->sendContent(); + $content = ob_get_clean(); + + return new DomResponse($content, $response->getStatusCode(), $response->headers->all()); + } +} diff --git a/lib/symfony/http-kernel/HttpKernelInterface.php b/lib/symfony/http-kernel/HttpKernelInterface.php index 5050bfcfba..0449240e7e 100644 --- a/lib/symfony/http-kernel/HttpKernelInterface.php +++ b/lib/symfony/http-kernel/HttpKernelInterface.php @@ -21,8 +21,14 @@ use Symfony\Component\HttpFoundation\Response; */ interface HttpKernelInterface { - const MASTER_REQUEST = 1; - const SUB_REQUEST = 2; + public const MAIN_REQUEST = 1; + public const SUB_REQUEST = 2; + + /** + * @deprecated since symfony/http-kernel 5.3, use MAIN_REQUEST instead. + * To ease the migration, this constant won't be removed until Symfony 7.0. + */ + public const MASTER_REQUEST = self::MAIN_REQUEST; /** * Handles a Request to convert it to a Response. @@ -30,14 +36,13 @@ interface HttpKernelInterface * When $catch is true, the implementation must catch all exceptions * and do its best to convert them to a Response instance. * - * @param Request $request A Request instance - * @param int $type The type of the request - * (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST) - * @param bool $catch Whether to catch exceptions or not + * @param int $type The type of the request + * (one of HttpKernelInterface::MAIN_REQUEST or HttpKernelInterface::SUB_REQUEST) + * @param bool $catch Whether to catch exceptions or not * - * @return Response A Response instance + * @return Response * * @throws \Exception When an Exception occurs during processing */ - public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true); + public function handle(Request $request, int $type = self::MAIN_REQUEST, bool $catch = true); } diff --git a/lib/symfony/http-kernel/Kernel.php b/lib/symfony/http-kernel/Kernel.php index de3b408442..358b926460 100644 --- a/lib/symfony/http-kernel/Kernel.php +++ b/lib/symfony/http-kernel/Kernel.php @@ -13,15 +13,18 @@ namespace Symfony\Component\HttpKernel; use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator; use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; -use Symfony\Component\ClassLoader\ClassCollectionLoader; +use Symfony\Component\Config\Builder\ConfigBuilderGenerator; use Symfony\Component\Config\ConfigCache; use Symfony\Component\Config\Loader\DelegatingLoader; use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; +use Symfony\Component\DependencyInjection\Dumper\Preloader; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\DependencyInjection\Loader\ClosureLoader; use Symfony\Component\DependencyInjection\Loader\DirectoryLoader; use Symfony\Component\DependencyInjection\Loader\GlobFileLoader; @@ -29,64 +32,69 @@ use Symfony\Component\DependencyInjection\Loader\IniFileLoader; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +use Symfony\Component\ErrorHandler\DebugClassLoader; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Bundle\BundleInterface; -use Symfony\Component\HttpKernel\Config\EnvParametersResource; +use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface; use Symfony\Component\HttpKernel\Config\FileLocator; use Symfony\Component\HttpKernel\DependencyInjection\AddAnnotatedClassesToCachePass; use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; +// Help opcache.preload discover always-needed symbols +class_exists(ConfigCache::class); + /** * The Kernel is the heart of the Symfony system. * * It manages an environment made of bundles. * + * Environment names must always start with a letter and + * they must only contain letters and numbers. + * * @author Fabien Potencier */ abstract class Kernel implements KernelInterface, RebootableInterface, TerminableInterface { /** - * @var BundleInterface[] + * @var array */ protected $bundles = []; - protected $bundleMap; protected $container; - protected $rootDir; protected $environment; protected $debug; protected $booted = false; - protected $name; protected $startTime; - protected $loadClassCache; private $projectDir; private $warmupDir; private $requestStackSize = 0; private $resetServices = false; - const VERSION = '3.4.49'; - const VERSION_ID = 30449; - const MAJOR_VERSION = 3; - const MINOR_VERSION = 4; - const RELEASE_VERSION = 49; - const EXTRA_VERSION = ''; - - const END_OF_MAINTENANCE = '11/2020'; - const END_OF_LIFE = '11/2021'; - /** - * @param string $environment The environment - * @param bool $debug Whether to enable debugging or not + * @var array */ - public function __construct($environment, $debug) + private static $freshCache = []; + + public const VERSION = '5.4.11'; + public const VERSION_ID = 50411; + public const MAJOR_VERSION = 5; + public const MINOR_VERSION = 4; + public const RELEASE_VERSION = 11; + public const EXTRA_VERSION = ''; + + public const END_OF_MAINTENANCE = '11/2024'; + public const END_OF_LIFE = '11/2025'; + + public function __construct(string $environment, bool $debug) { - $this->environment = $environment; - $this->debug = (bool) $debug; - $this->rootDir = $this->getRootDir(); - $this->name = $this->getName(); + if (!$this->environment = $environment) { + throw new \InvalidArgumentException(sprintf('Invalid environment provided to "%s": the environment cannot be empty.', get_debug_type($this))); + } + + $this->debug = $debug; } public function __clone() @@ -115,25 +123,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl return; } - if ($this->debug) { - $this->startTime = microtime(true); - } - if ($this->debug && !isset($_ENV['SHELL_VERBOSITY']) && !isset($_SERVER['SHELL_VERBOSITY'])) { - putenv('SHELL_VERBOSITY=3'); - $_ENV['SHELL_VERBOSITY'] = 3; - $_SERVER['SHELL_VERBOSITY'] = 3; - } - if ($this->loadClassCache) { - $this->doLoadClassCache($this->loadClassCache[0], $this->loadClassCache[1]); + if (null === $this->container) { + $this->preBoot(); } - // init bundles - $this->initializeBundles(); - - // init container - $this->initializeContainer(); - foreach ($this->getBundles() as $bundle) { $bundle->setContainer($this->container); $bundle->boot(); @@ -145,7 +139,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl /** * {@inheritdoc} */ - public function reboot($warmupDir) + public function reboot(?string $warmupDir) { $this->shutdown(); $this->warmupDir = $warmupDir; @@ -190,8 +184,16 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl /** * {@inheritdoc} */ - public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true) { + if (!$this->booted) { + $container = $this->container ?? $this->preBoot(); + + if ($container->has('http_cache')) { + return $container->get('http_cache')->handle($request, $type, $catch); + } + } + $this->boot(); ++$this->requestStackSize; $this->resetServices = true; @@ -204,7 +206,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl } /** - * Gets a HTTP kernel from the container. + * Gets an HTTP kernel from the container. * * @return HttpKernelInterface */ @@ -224,98 +226,42 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl /** * {@inheritdoc} */ - public function getBundle($name, $first = true/*, $noDeprecation = false */) + public function getBundle(string $name) { - $noDeprecation = false; - if (\func_num_args() >= 3) { - $noDeprecation = func_get_arg(2); + if (!isset($this->bundles[$name])) { + throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the "registerBundles()" method of your "%s.php" file?', $name, get_debug_type($this))); } - if (!$first && !$noDeprecation) { - @trigger_error(sprintf('Passing "false" as the second argument to "%s()" is deprecated as of 3.4 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); - } - - if (!isset($this->bundleMap[$name])) { - throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the registerBundles() method of your "%s.php" file?', $name, static::class)); - } - - if (true === $first) { - return $this->bundleMap[$name][0]; - } - - return $this->bundleMap[$name]; + return $this->bundles[$name]; } /** * {@inheritdoc} - * - * @throws \RuntimeException if a custom resource is hidden by a resource in a derived bundle */ - public function locateResource($name, $dir = null, $first = true) + public function locateResource(string $name) { if ('@' !== $name[0]) { throw new \InvalidArgumentException(sprintf('A resource name must start with @ ("%s" given).', $name)); } - if (false !== strpos($name, '..')) { + if (str_contains($name, '..')) { throw new \RuntimeException(sprintf('File name "%s" contains invalid characters (..).', $name)); } $bundleName = substr($name, 1); $path = ''; - if (false !== strpos($bundleName, '/')) { - list($bundleName, $path) = explode('/', $bundleName, 2); + if (str_contains($bundleName, '/')) { + [$bundleName, $path] = explode('/', $bundleName, 2); } - $isResource = 0 === strpos($path, 'Resources') && null !== $dir; - $overridePath = substr($path, 9); - $resourceBundle = null; - $bundles = $this->getBundle($bundleName, false, true); - $files = []; - - foreach ($bundles as $bundle) { - if ($isResource && file_exists($file = $dir.'/'.$bundle->getName().$overridePath)) { - if (null !== $resourceBundle) { - throw new \RuntimeException(sprintf('"%s" resource is hidden by a resource from the "%s" derived bundle. Create a "%s" file to override the bundle resource.', $file, $resourceBundle, $dir.'/'.$bundles[0]->getName().$overridePath)); - } - - if ($first) { - return $file; - } - $files[] = $file; - } - - if (file_exists($file = $bundle->getPath().'/'.$path)) { - if ($first && !$isResource) { - return $file; - } - $files[] = $file; - $resourceBundle = $bundle->getName(); - } - } - - if (\count($files) > 0) { - return $first && $isResource ? $files[0] : $files; + $bundle = $this->getBundle($bundleName); + if (file_exists($file = $bundle->getPath().'/'.$path)) { + return $file; } throw new \InvalidArgumentException(sprintf('Unable to find file "%s".', $name)); } - /** - * {@inheritdoc} - */ - public function getName() - { - if (null === $this->name) { - $this->name = preg_replace('/[^a-zA-Z0-9_]+/', '', basename($this->rootDir)); - if (ctype_digit($this->name[0])) { - $this->name = '_'.$this->name; - } - } - - return $this->name; - } - /** * {@inheritdoc} */ @@ -332,35 +278,22 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl return $this->debug; } - /** - * {@inheritdoc} - */ - public function getRootDir() - { - if (null === $this->rootDir) { - $r = new \ReflectionObject($this); - $this->rootDir = \dirname($r->getFileName()); - } - - return $this->rootDir; - } - /** * Gets the application root dir (path of the project's composer file). * - * @return string The project root dir + * @return string */ public function getProjectDir() { if (null === $this->projectDir) { $r = new \ReflectionObject($this); - if (!file_exists($dir = $r->getFileName())) { + if (!is_file($dir = $r->getFileName())) { throw new \LogicException(sprintf('Cannot auto-detect project dir for kernel of class "%s".', $r->name)); } $dir = $rootDir = \dirname($dir); - while (!file_exists($dir.'/composer.json')) { + while (!is_file($dir.'/composer.json')) { if ($dir === \dirname($dir)) { return $this->projectDir = $rootDir; } @@ -377,52 +310,19 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ public function getContainer() { + if (!$this->container) { + throw new \LogicException('Cannot retrieve the container from a non-booted kernel.'); + } + return $this->container; } - /** - * Loads the PHP class cache. - * - * This methods only registers the fact that you want to load the cache classes. - * The cache will actually only be loaded when the Kernel is booted. - * - * That optimization is mainly useful when using the HttpCache class in which - * case the class cache is not loaded if the Response is in the cache. - * - * @param string $name The cache name prefix - * @param string $extension File extension of the resulting file - * - * @deprecated since version 3.3, to be removed in 4.0. The class cache is not needed anymore when using PHP 7.0. - */ - public function loadClassCache($name = 'classes', $extension = '.php') - { - if (\PHP_VERSION_ID >= 70000) { - @trigger_error(__METHOD__.'() is deprecated since Symfony 3.3, to be removed in 4.0.', \E_USER_DEPRECATED); - } - - $this->loadClassCache = [$name, $extension]; - } - - /** - * @internal - * - * @deprecated since version 3.3, to be removed in 4.0. - */ - public function setClassCache(array $classes) - { - if (\PHP_VERSION_ID >= 70000) { - @trigger_error(__METHOD__.'() is deprecated since Symfony 3.3, to be removed in 4.0.', \E_USER_DEPRECATED); - } - - file_put_contents(($this->warmupDir ?: $this->getCacheDir()).'/classes.map', sprintf('warmupDir ?: $this->getCacheDir()).'/annotations.map', sprintf('warmupDir ?: $this->getBuildDir()).'/annotations.map', sprintf('rootDir.'/cache/'.$this->environment; + return $this->getProjectDir().'/var/cache/'.$this->environment; + } + + /** + * {@inheritdoc} + */ + public function getBuildDir(): string + { + // Returns $this->getCacheDir() for backward compatibility + return $this->getCacheDir(); } /** @@ -446,7 +355,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ public function getLogDir() { - return $this->rootDir.'/logs'; + return $this->getProjectDir().'/var/log'; } /** @@ -458,83 +367,28 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl } /** - * @deprecated since version 3.3, to be removed in 4.0. + * Gets the patterns defining the classes to parse and cache for annotations. */ - protected function doLoadClassCache($name, $extension) + public function getAnnotatedClassesToCompile(): array { - if (\PHP_VERSION_ID >= 70000) { - @trigger_error(__METHOD__.'() is deprecated since Symfony 3.3, to be removed in 4.0.', \E_USER_DEPRECATED); - } - $cacheDir = $this->warmupDir ?: $this->getCacheDir(); - - if (!$this->booted && is_file($cacheDir.'/classes.map')) { - ClassCollectionLoader::load(include($cacheDir.'/classes.map'), $cacheDir, $name, $this->debug, false, $extension); - } + return []; } /** - * Initializes the data structures related to the bundle management. - * - * - the bundles property maps a bundle name to the bundle instance, - * - the bundleMap property maps a bundle name to the bundle inheritance hierarchy (most derived bundle first). + * Initializes bundles. * * @throws \LogicException if two bundles share a common name - * @throws \LogicException if a bundle tries to extend a non-registered bundle - * @throws \LogicException if a bundle tries to extend itself - * @throws \LogicException if two bundles extend the same ancestor */ protected function initializeBundles() { // init bundles $this->bundles = []; - $topMostBundles = []; - $directChildren = []; - foreach ($this->registerBundles() as $bundle) { $name = $bundle->getName(); if (isset($this->bundles[$name])) { throw new \LogicException(sprintf('Trying to register two bundles with the same name "%s".', $name)); } $this->bundles[$name] = $bundle; - - if ($parentName = $bundle->getParent()) { - @trigger_error('Bundle inheritance is deprecated as of 3.4 and will be removed in 4.0.', \E_USER_DEPRECATED); - - if (isset($directChildren[$parentName])) { - throw new \LogicException(sprintf('Bundle "%s" is directly extended by two bundles "%s" and "%s".', $parentName, $name, $directChildren[$parentName])); - } - if ($parentName == $name) { - throw new \LogicException(sprintf('Bundle "%s" can not extend itself.', $name)); - } - $directChildren[$parentName] = $name; - } else { - $topMostBundles[$name] = $bundle; - } - } - - // look for orphans - if (!empty($directChildren) && \count($diff = array_diff_key($directChildren, $this->bundles))) { - $diff = array_keys($diff); - - throw new \LogicException(sprintf('Bundle "%s" extends bundle "%s", which is not registered.', $directChildren[$diff[0]], $diff[0])); - } - - // inheritance - $this->bundleMap = []; - foreach ($topMostBundles as $name => $bundle) { - $bundleMap = [$bundle]; - $hierarchy = [$name]; - - while (isset($directChildren[$name])) { - $name = $directChildren[$name]; - array_unshift($bundleMap, $this->bundles[$name]); - $hierarchy[] = $name; - } - - foreach ($hierarchy as $hierarchyBundle) { - $this->bundleMap[$hierarchyBundle] = $bundleMap; - array_pop($bundleMap); - } } } @@ -550,11 +404,21 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl /** * Gets the container class. * - * @return string The container class + * @throws \InvalidArgumentException If the generated classname is invalid + * + * @return string */ protected function getContainerClass() { - return $this->name.ucfirst($this->environment).($this->debug ? 'Debug' : '').'ProjectContainer'; + $class = static::class; + $class = str_contains($class, "@anonymous\0") ? get_parent_class($class).str_replace('.', '_', ContainerBuilder::hash($class)) : $class; + $class = str_replace('\\', '_', $class).ucfirst($this->environment).($this->debug ? 'Debug' : '').'Container'; + + if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $class)) { + throw new \InvalidArgumentException(sprintf('The environment "%s" contains invalid characters, it can only contain characters allowed in PHP class names.', $this->environment)); + } + + return $class; } /** @@ -572,34 +436,54 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl /** * Initializes the service container. * - * The cached version of the service container is used when fresh, otherwise the + * The built version of the service container is used when fresh, otherwise the * container is built. */ protected function initializeContainer() { $class = $this->getContainerClass(); - $cacheDir = $this->warmupDir ?: $this->getCacheDir(); - $cache = new ConfigCache($cacheDir.'/'.$class.'.php', $this->debug); - $oldContainer = null; - if ($fresh = $cache->isFresh()) { - // Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors - $errorLevel = error_reporting(\E_ALL ^ \E_WARNING); - $fresh = $oldContainer = false; - try { - if (file_exists($cache->getPath()) && \is_object($this->container = include $cache->getPath())) { - $this->container->set('kernel', $this); - $oldContainer = $this->container; - $fresh = true; - } - } catch (\Throwable $e) { - } catch (\Exception $e) { - } finally { + $buildDir = $this->warmupDir ?: $this->getBuildDir(); + $cache = new ConfigCache($buildDir.'/'.$class.'.php', $this->debug); + $cachePath = $cache->getPath(); + + // Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors + $errorLevel = error_reporting(\E_ALL ^ \E_WARNING); + + try { + if (is_file($cachePath) && \is_object($this->container = include $cachePath) + && (!$this->debug || (self::$freshCache[$cachePath] ?? $cache->isFresh())) + ) { + self::$freshCache[$cachePath] = true; + $this->container->set('kernel', $this); error_reporting($errorLevel); + + return; } + } catch (\Throwable $e) { } - if ($fresh) { - return; + $oldContainer = \is_object($this->container) ? new \ReflectionClass($this->container) : $this->container = null; + + try { + is_dir($buildDir) ?: mkdir($buildDir, 0777, true); + + if ($lock = fopen($cachePath.'.lock', 'w')) { + if (!flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock) && !flock($lock, $wouldBlock ? \LOCK_SH : \LOCK_EX)) { + fclose($lock); + $lock = null; + } elseif (!is_file($cachePath) || !\is_object($this->container = include $cachePath)) { + $this->container = null; + } elseif (!$oldContainer || \get_class($this->container) !== $oldContainer->name) { + flock($lock, \LOCK_UN); + fclose($lock); + $this->container->set('kernel', $this); + + return; + } + } + } catch (\Throwable $e) { + } finally { + error_reporting($errorLevel); } if ($collectDeprecations = $this->debug && !\defined('PHPUNIT_COMPOSER_INSTALL')) { @@ -615,7 +499,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl return null; } - $backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 3); + $backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 5); // Clean the trace by removing first frames added by the error handler itself. for ($i = 0; isset($backtrace[$i]); ++$i) { if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { @@ -623,13 +507,32 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl break; } } + for ($i = 0; isset($backtrace[$i]); ++$i) { + if (!isset($backtrace[$i]['file'], $backtrace[$i]['line'], $backtrace[$i]['function'])) { + continue; + } + if (!isset($backtrace[$i]['class']) && 'trigger_deprecation' === $backtrace[$i]['function']) { + $file = $backtrace[$i]['file']; + $line = $backtrace[$i]['line']; + $backtrace = \array_slice($backtrace, 1 + $i); + break; + } + } + + // Remove frames added by DebugClassLoader. + for ($i = \count($backtrace) - 2; 0 < $i; --$i) { + if (\in_array($backtrace[$i]['class'] ?? null, [DebugClassLoader::class, LegacyDebugClassLoader::class], true)) { + $backtrace = [$backtrace[$i + 1]]; + break; + } + } $collectedLogs[$message] = [ 'type' => $type, 'message' => $message, 'file' => $file, 'line' => $line, - 'trace' => $backtrace, + 'trace' => [$backtrace[0]], 'count' => 1, ]; @@ -645,25 +548,19 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl if ($collectDeprecations) { restore_error_handler(); - file_put_contents($cacheDir.'/'.$class.'Deprecations.log', serialize(array_values($collectedLogs))); - file_put_contents($cacheDir.'/'.$class.'Compiler.log', null !== $container ? implode("\n", $container->getCompiler()->getLog()) : ''); + @file_put_contents($buildDir.'/'.$class.'Deprecations.log', serialize(array_values($collectedLogs))); + @file_put_contents($buildDir.'/'.$class.'Compiler.log', null !== $container ? implode("\n", $container->getCompiler()->getLog()) : ''); } } - if (null === $oldContainer && file_exists($cache->getPath())) { - $errorLevel = error_reporting(\E_ALL ^ \E_WARNING); - try { - $oldContainer = include $cache->getPath(); - } catch (\Throwable $e) { - } catch (\Exception $e) { - } finally { - error_reporting($errorLevel); - } - } - $oldContainer = \is_object($oldContainer) ? new \ReflectionClass($oldContainer) : false; - $this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass()); - $this->container = require $cache->getPath(); + + if ($lock) { + flock($lock, \LOCK_UN); + fclose($lock); + } + + $this->container = require $cachePath; $this->container->set('kernel', $this); if ($oldContainer && \get_class($this->container) !== $oldContainer->name) { @@ -682,15 +579,21 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl touch($oldContainerDir.'.legacy'); } + $preload = $this instanceof WarmableInterface ? (array) $this->warmUp($this->container->getParameter('kernel.cache_dir')) : []; + if ($this->container->has('cache_warmer')) { - $this->container->get('cache_warmer')->warmUp($this->container->getParameter('kernel.cache_dir')); + $preload = array_merge($preload, (array) $this->container->get('cache_warmer')->warmUp($this->container->getParameter('kernel.cache_dir'))); + } + + if ($preload && method_exists(Preloader::class, 'append') && file_exists($preloadFile = $buildDir.'/'.$class.'.preload.php')) { + Preloader::append($preloadFile, $preload); } } /** * Returns the kernel parameters. * - * @return array An array of kernel parameters + * @return array */ protected function getKernelParameters() { @@ -700,66 +603,36 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl foreach ($this->bundles as $name => $bundle) { $bundles[$name] = \get_class($bundle); $bundlesMetadata[$name] = [ - 'parent' => $bundle->getParent(), 'path' => $bundle->getPath(), 'namespace' => $bundle->getNamespace(), ]; } - return array_merge( - [ - 'kernel.root_dir' => realpath($this->rootDir) ?: $this->rootDir, - 'kernel.project_dir' => realpath($this->getProjectDir()) ?: $this->getProjectDir(), - 'kernel.environment' => $this->environment, - 'kernel.debug' => $this->debug, - 'kernel.name' => $this->name, - 'kernel.cache_dir' => realpath($cacheDir = $this->warmupDir ?: $this->getCacheDir()) ?: $cacheDir, - 'kernel.logs_dir' => realpath($this->getLogDir()) ?: $this->getLogDir(), - 'kernel.bundles' => $bundles, - 'kernel.bundles_metadata' => $bundlesMetadata, - 'kernel.charset' => $this->getCharset(), - 'kernel.container_class' => $this->getContainerClass(), - ], - $this->getEnvParameters(false) - ); - } - - /** - * Gets the environment parameters. - * - * Only the parameters starting with "SYMFONY__" are considered. - * - * @return array An array of parameters - * - * @deprecated since version 3.3, to be removed in 4.0 - */ - protected function getEnvParameters() - { - if (0 === \func_num_args() || func_get_arg(0)) { - @trigger_error(sprintf('The "%s()" method is deprecated as of 3.3 and will be removed in 4.0. Use the %%env()%% syntax to get the value of any environment variable from configuration files instead.', __METHOD__), \E_USER_DEPRECATED); - } - - $parameters = []; - foreach ($_SERVER as $key => $value) { - if (0 === strpos($key, 'SYMFONY__')) { - @trigger_error(sprintf('The support of special environment variables that start with SYMFONY__ (such as "%s") is deprecated as of 3.3 and will be removed in 4.0. Use the %%env()%% syntax instead to get the value of environment variables in configuration files.', $key), \E_USER_DEPRECATED); - $parameters[strtolower(str_replace('__', '.', substr($key, 9)))] = $value; - } - } - - return $parameters; + return [ + 'kernel.project_dir' => realpath($this->getProjectDir()) ?: $this->getProjectDir(), + 'kernel.environment' => $this->environment, + 'kernel.runtime_environment' => '%env(default:kernel.environment:APP_RUNTIME_ENV)%', + 'kernel.debug' => $this->debug, + 'kernel.build_dir' => realpath($buildDir = $this->warmupDir ?: $this->getBuildDir()) ?: $buildDir, + 'kernel.cache_dir' => realpath($cacheDir = ($this->getCacheDir() === $this->getBuildDir() ? ($this->warmupDir ?: $this->getCacheDir()) : $this->getCacheDir())) ?: $cacheDir, + 'kernel.logs_dir' => realpath($this->getLogDir()) ?: $this->getLogDir(), + 'kernel.bundles' => $bundles, + 'kernel.bundles_metadata' => $bundlesMetadata, + 'kernel.charset' => $this->getCharset(), + 'kernel.container_class' => $this->getContainerClass(), + ]; } /** * Builds the service container. * - * @return ContainerBuilder The compiled service container + * @return ContainerBuilder * * @throws \RuntimeException */ protected function buildContainer() { - foreach (['cache' => $this->warmupDir ?: $this->getCacheDir(), 'logs' => $this->getLogDir()] as $name => $dir) { + foreach (['cache' => $this->getCacheDir(), 'build' => $this->warmupDir ?: $this->getBuildDir(), 'logs' => $this->getLogDir()] as $name => $dir) { if (!is_dir($dir)) { if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) { throw new \RuntimeException(sprintf('Unable to create the "%s" directory (%s).', $name, $dir)); @@ -774,11 +647,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl $this->prepareContainer($container); if (null !== $cont = $this->registerContainerConfiguration($this->getContainerLoader($container))) { + trigger_deprecation('symfony/http-kernel', '5.3', 'Returning a ContainerBuilder from "%s::registerContainerConfiguration()" is deprecated.', get_debug_type($this)); $container->merge($cont); } $container->addCompilerPass(new AddAnnotatedClassesToCachePass($this)); - $container->addResource(new EnvParametersResource('SYMFONY__')); return $container; } @@ -823,10 +696,13 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl $container = new ContainerBuilder(); $container->getParameterBag()->add($this->getKernelParameters()); + if ($this instanceof ExtensionInterface) { + $container->registerExtension($this); + } if ($this instanceof CompilerPassInterface) { $container->addCompilerPass($this, PassConfig::TYPE_BEFORE_OPTIMIZATION, -10000); } - if (class_exists('ProxyManager\Configuration') && class_exists('Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator')) { + if (class_exists(\ProxyManager\Configuration::class) && class_exists(RuntimeInstantiator::class)) { $container->setProxyInstantiator(new RuntimeInstantiator()); } @@ -836,17 +712,15 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl /** * Dumps the service container to PHP code in the cache. * - * @param ConfigCache $cache The config cache - * @param ContainerBuilder $container The service container - * @param string $class The name of the class to generate - * @param string $baseClass The name of the container's base class + * @param string $class The name of the class to generate + * @param string $baseClass The name of the container's base class */ - protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container, $class, $baseClass) + protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container, string $class, string $baseClass) { // cache the container $dumper = new PhpDumper($container); - if (class_exists('ProxyManager\Configuration') && class_exists('Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper')) { + if (class_exists(\ProxyManager\Configuration::class) && class_exists(ProxyDumper::class)) { $dumper->setProxyDumper(new ProxyDumper()); } @@ -856,8 +730,8 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl 'file' => $cache->getPath(), 'as_files' => true, 'debug' => $this->debug, - 'inline_class_loader_parameter' => \PHP_VERSION_ID >= 70000 && !$this->loadClassCache && !class_exists(ClassCollectionLoader::class, false) ? 'container.dumper.inline_class_loader' : null, 'build_time' => $container->hasParameter('kernel.container_build_time') ? $container->getParameter('kernel.container_build_time') : time(), + 'preload_classes' => array_map('get_class', $this->bundles), ]); $rootCode = array_pop($content); @@ -869,7 +743,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl @chmod($dir.$file, 0666 & ~umask()); } $legacyFile = \dirname($dir.key($content)).'.legacy'; - if (file_exists($legacyFile)) { + if (is_file($legacyFile)) { @unlink($legacyFile); } @@ -879,35 +753,61 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl /** * Returns a loader for the container. * - * @return DelegatingLoader The loader + * @return DelegatingLoader */ protected function getContainerLoader(ContainerInterface $container) { + $env = $this->getEnvironment(); $locator = new FileLocator($this); $resolver = new LoaderResolver([ - new XmlFileLoader($container, $locator), - new YamlFileLoader($container, $locator), - new IniFileLoader($container, $locator), - new PhpFileLoader($container, $locator), - new GlobFileLoader($container, $locator), - new DirectoryLoader($container, $locator), - new ClosureLoader($container), + new XmlFileLoader($container, $locator, $env), + new YamlFileLoader($container, $locator, $env), + new IniFileLoader($container, $locator, $env), + new PhpFileLoader($container, $locator, $env, class_exists(ConfigBuilderGenerator::class) ? new ConfigBuilderGenerator($this->getBuildDir()) : null), + new GlobFileLoader($container, $locator, $env), + new DirectoryLoader($container, $locator, $env), + new ClosureLoader($container, $env), ]); return new DelegatingLoader($resolver); } + private function preBoot(): ContainerInterface + { + if ($this->debug) { + $this->startTime = microtime(true); + } + if ($this->debug && !isset($_ENV['SHELL_VERBOSITY']) && !isset($_SERVER['SHELL_VERBOSITY'])) { + putenv('SHELL_VERBOSITY=3'); + $_ENV['SHELL_VERBOSITY'] = 3; + $_SERVER['SHELL_VERBOSITY'] = 3; + } + + $this->initializeBundles(); + $this->initializeContainer(); + + $container = $this->container; + + if ($container->hasParameter('kernel.trusted_hosts') && $trustedHosts = $container->getParameter('kernel.trusted_hosts')) { + Request::setTrustedHosts($trustedHosts); + } + + if ($container->hasParameter('kernel.trusted_proxies') && $container->hasParameter('kernel.trusted_headers') && $trustedProxies = $container->getParameter('kernel.trusted_proxies')) { + Request::setTrustedProxies(\is_array($trustedProxies) ? $trustedProxies : array_map('trim', explode(',', $trustedProxies)), $container->getParameter('kernel.trusted_headers')); + } + + return $container; + } + /** * Removes comments from a PHP source string. * * We don't use the PHP php_strip_whitespace() function * as we want the content to be readable and well-formatted. * - * @param string $source A PHP string - * - * @return string The PHP string with the comments removed + * @return string */ - public static function stripComments($source) + public static function stripComments(string $source) { if (!\function_exists('token_get_all')) { return $source; @@ -938,6 +838,9 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl // replace multiple new lines with a single newline $rawChunk .= preg_replace(['/\n{2,}/S'], "\n", $token[1]); } elseif (\in_array($token[0], [\T_COMMENT, \T_DOC_COMMENT])) { + if (!\in_array($rawChunk[\strlen($rawChunk) - 1], [' ', "\n", "\r", "\t"], true)) { + $rawChunk .= ' '; + } $ignoreSpace = true; } else { $rawChunk .= $token[1]; @@ -945,34 +848,34 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl // The PHP-open tag already has a new-line if (\T_OPEN_TAG === $token[0]) { $ignoreSpace = true; + } else { + $ignoreSpace = false; } } } $output .= $rawChunk; - if (\PHP_VERSION_ID >= 70000) { - // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098 - unset($tokens, $rawChunk); - gc_mem_caches(); - } + unset($tokens, $rawChunk); + gc_mem_caches(); return $output; } - public function serialize() + /** + * @return array + */ + public function __sleep() { - return serialize([$this->environment, $this->debug]); + return ['environment', 'debug']; } - public function unserialize($data) + public function __wakeup() { - if (\PHP_VERSION_ID >= 70000) { - list($environment, $debug) = unserialize($data, ['allowed_classes' => false]); - } else { - list($environment, $debug) = unserialize($data); + if (\is_object($this->environment) || \is_object($this->debug)) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); } - $this->__construct($environment, $debug); + $this->__construct($this->environment, $this->debug); } } diff --git a/lib/symfony/http-kernel/KernelEvents.php b/lib/symfony/http-kernel/KernelEvents.php index 6743763258..3d47f60801 100644 --- a/lib/symfony/http-kernel/KernelEvents.php +++ b/lib/symfony/http-kernel/KernelEvents.php @@ -11,6 +11,15 @@ namespace Symfony\Component\HttpKernel; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; +use Symfony\Component\HttpKernel\Event\ControllerEvent; +use Symfony\Component\HttpKernel\Event\ExceptionEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\Event\TerminateEvent; +use Symfony\Component\HttpKernel\Event\ViewEvent; + /** * Contains all events thrown in the HttpKernel component. * @@ -25,9 +34,9 @@ final class KernelEvents * This event allows you to create a response for a request before any * other code in the framework is executed. * - * @Event("Symfony\Component\HttpKernel\Event\GetResponseEvent") + * @Event("Symfony\Component\HttpKernel\Event\RequestEvent") */ - const REQUEST = 'kernel.request'; + public const REQUEST = 'kernel.request'; /** * The EXCEPTION event occurs when an uncaught exception appears. @@ -35,20 +44,9 @@ final class KernelEvents * This event allows you to create a response for a thrown exception or * to modify the thrown exception. * - * @Event("Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent") + * @Event("Symfony\Component\HttpKernel\Event\ExceptionEvent") */ - const EXCEPTION = 'kernel.exception'; - - /** - * The VIEW event occurs when the return value of a controller - * is not a Response instance. - * - * This event allows you to create a response for the return value of the - * controller. - * - * @Event("Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent") - */ - const VIEW = 'kernel.view'; + public const EXCEPTION = 'kernel.exception'; /** * The CONTROLLER event occurs once a controller was found for @@ -57,9 +55,9 @@ final class KernelEvents * This event allows you to change the controller that will handle the * request. * - * @Event("Symfony\Component\HttpKernel\Event\FilterControllerEvent") + * @Event("Symfony\Component\HttpKernel\Event\ControllerEvent") */ - const CONTROLLER = 'kernel.controller'; + public const CONTROLLER = 'kernel.controller'; /** * The CONTROLLER_ARGUMENTS event occurs once controller arguments have been resolved. @@ -67,9 +65,20 @@ final class KernelEvents * This event allows you to change the arguments that will be passed to * the controller. * - * @Event("Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent") + * @Event("Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent") */ - const CONTROLLER_ARGUMENTS = 'kernel.controller_arguments'; + public const CONTROLLER_ARGUMENTS = 'kernel.controller_arguments'; + + /** + * The VIEW event occurs when the return value of a controller + * is not a Response instance. + * + * This event allows you to create a response for the return value of the + * controller. + * + * @Event("Symfony\Component\HttpKernel\Event\ViewEvent") + */ + public const VIEW = 'kernel.view'; /** * The RESPONSE event occurs once a response was created for @@ -78,18 +87,9 @@ final class KernelEvents * This event allows you to modify or replace the response that will be * replied. * - * @Event("Symfony\Component\HttpKernel\Event\FilterResponseEvent") + * @Event("Symfony\Component\HttpKernel\Event\ResponseEvent") */ - const RESPONSE = 'kernel.response'; - - /** - * The TERMINATE event occurs once a response was sent. - * - * This event allows you to run expensive post-response jobs. - * - * @Event("Symfony\Component\HttpKernel\Event\PostResponseEvent") - */ - const TERMINATE = 'kernel.terminate'; + public const RESPONSE = 'kernel.response'; /** * The FINISH_REQUEST event occurs when a response was generated for a request. @@ -99,5 +99,30 @@ final class KernelEvents * * @Event("Symfony\Component\HttpKernel\Event\FinishRequestEvent") */ - const FINISH_REQUEST = 'kernel.finish_request'; + public const FINISH_REQUEST = 'kernel.finish_request'; + + /** + * The TERMINATE event occurs once a response was sent. + * + * This event allows you to run expensive post-response jobs. + * + * @Event("Symfony\Component\HttpKernel\Event\TerminateEvent") + */ + public const TERMINATE = 'kernel.terminate'; + + /** + * Event aliases. + * + * These aliases can be consumed by RegisterListenersPass. + */ + public const ALIASES = [ + ControllerArgumentsEvent::class => self::CONTROLLER_ARGUMENTS, + ControllerEvent::class => self::CONTROLLER, + ResponseEvent::class => self::RESPONSE, + FinishRequestEvent::class => self::FINISH_REQUEST, + RequestEvent::class => self::REQUEST, + ViewEvent::class => self::VIEW, + ExceptionEvent::class => self::EXCEPTION, + TerminateEvent::class => self::TERMINATE, + ]; } diff --git a/lib/symfony/http-kernel/KernelInterface.php b/lib/symfony/http-kernel/KernelInterface.php index 2445bbb43a..41135a6d80 100644 --- a/lib/symfony/http-kernel/KernelInterface.php +++ b/lib/symfony/http-kernel/KernelInterface.php @@ -18,16 +18,20 @@ use Symfony\Component\HttpKernel\Bundle\BundleInterface; /** * The Kernel is the heart of the Symfony system. * - * It manages an environment made of bundles. + * It manages an environment made of application kernel and bundles. + * + * @method string getBuildDir() Returns the build directory - not implementing it is deprecated since Symfony 5.2. + * This directory should be used to store build artifacts, and can be read-only at runtime. + * Caches written at runtime should be stored in the "cache directory" ({@see KernelInterface::getCacheDir()}). * * @author Fabien Potencier */ -interface KernelInterface extends HttpKernelInterface, \Serializable +interface KernelInterface extends HttpKernelInterface { /** * Returns an array of bundles to register. * - * @return iterable|BundleInterface[] An iterable of bundle instances + * @return iterable */ public function registerBundles(); @@ -51,27 +55,21 @@ interface KernelInterface extends HttpKernelInterface, \Serializable /** * Gets the registered bundle instances. * - * @return BundleInterface[] An array of registered bundle instances + * @return array */ public function getBundles(); /** - * Returns a bundle and optionally its descendants by its name. + * Returns a bundle. * - * The second argument is deprecated as of 3.4 and will be removed in 4.0. This method - * will always return an instance of BundleInterface in 4.0. - * - * @param string $name Bundle name - * @param bool $first Whether to return the first bundle only or together with its descendants - * - * @return BundleInterface|BundleInterface[] A BundleInterface instance or an array of BundleInterface instances if $first is false + * @return BundleInterface * * @throws \InvalidArgumentException when the bundle is not enabled */ - public function getBundle($name, $first = true); + public function getBundle(string $name); /** - * Returns the file path for a given resource. + * Returns the file path for a given bundle resource. * * A Resource can be a file or a directory. * @@ -82,84 +80,70 @@ interface KernelInterface extends HttpKernelInterface, \Serializable * where BundleName is the name of the bundle * and the remaining part is the relative path in the bundle. * - * If $dir is passed, and the first segment of the path is "Resources", - * this method will look for a file named: - * - * $dir//path/without/Resources - * - * before looking in the bundle resource folder. - * - * @param string $name A resource name to locate - * @param string $dir A directory where to look for the resource first - * @param bool $first Whether to return the first path or paths for all matching bundles - * - * @return string|array The absolute path of the resource or an array if $first is false + * @return string * * @throws \InvalidArgumentException if the file cannot be found or the name is not valid * @throws \RuntimeException if the name contains invalid/unsafe characters */ - public function locateResource($name, $dir = null, $first = true); - - /** - * Gets the name of the kernel. - * - * @return string The kernel name - */ - public function getName(); + public function locateResource(string $name); /** * Gets the environment. * - * @return string The current environment + * @return string */ public function getEnvironment(); /** * Checks if debug mode is enabled. * - * @return bool true if debug mode is enabled, false otherwise + * @return bool */ public function isDebug(); /** - * Gets the application root dir (path of the project's Kernel class). + * Gets the project dir (path of the project's composer file). * - * @return string The Kernel root dir + * @return string */ - public function getRootDir(); + public function getProjectDir(); /** * Gets the current container. * - * @return ContainerInterface|null A ContainerInterface instance or null when the Kernel is shutdown + * @return ContainerInterface */ public function getContainer(); /** * Gets the request start time (not available if debug is disabled). * - * @return float The request start timestamp + * @return float */ public function getStartTime(); /** * Gets the cache directory. * - * @return string The cache directory + * Since Symfony 5.2, the cache directory should be used for caches that are written at runtime. + * For caches and artifacts that can be warmed at compile-time and deployed as read-only, + * use the new "build directory" returned by the {@see getBuildDir()} method. + * + * @return string */ public function getCacheDir(); /** * Gets the log directory. * - * @return string The log directory + * @return string */ public function getLogDir(); /** * Gets the charset of the application. * - * @return string The charset + * @return string */ public function getCharset(); } diff --git a/lib/symfony/http-kernel/LICENSE b/lib/symfony/http-kernel/LICENSE index 9e936ec044..88bf75bb4d 100644 --- a/lib/symfony/http-kernel/LICENSE +++ b/lib/symfony/http-kernel/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2020 Fabien Potencier +Copyright (c) 2004-2022 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/lib/symfony/http-kernel/Log/DebugLoggerInterface.php b/lib/symfony/http-kernel/Log/DebugLoggerInterface.php index f0606d3b0e..19ff0db181 100644 --- a/lib/symfony/http-kernel/Log/DebugLoggerInterface.php +++ b/lib/symfony/http-kernel/Log/DebugLoggerInterface.php @@ -11,12 +11,12 @@ namespace Symfony\Component\HttpKernel\Log; +use Symfony\Component\HttpFoundation\Request; + /** * DebugLoggerInterface. * * @author Fabien Potencier - * - * @method clear() Removes all log records. */ interface DebugLoggerInterface { @@ -27,14 +27,19 @@ interface DebugLoggerInterface * timestamp, message, priority, and priorityName. * It can also have an optional context key containing an array. * - * @return array An array of logs + * @return array */ - public function getLogs(); + public function getLogs(Request $request = null); /** * Returns the number of errors. * - * @return int The number of errors + * @return int */ - public function countErrors(); + public function countErrors(Request $request = null); + + /** + * Removes all log records. + */ + public function clear(); } diff --git a/lib/symfony/http-kernel/Log/Logger.php b/lib/symfony/http-kernel/Log/Logger.php index 509d1e293b..06ec0f96dc 100644 --- a/lib/symfony/http-kernel/Log/Logger.php +++ b/lib/symfony/http-kernel/Log/Logger.php @@ -22,7 +22,7 @@ use Psr\Log\LogLevel; */ class Logger extends AbstractLogger { - private static $levels = [ + private const LEVELS = [ LogLevel::DEBUG => 0, LogLevel::INFO => 1, LogLevel::NOTICE => 2, @@ -35,15 +35,20 @@ class Logger extends AbstractLogger private $minLevelIndex; private $formatter; + + /** @var resource|null */ private $handle; - public function __construct($minLevel = null, $output = null, callable $formatter = null) + /** + * @param string|resource|null $output + */ + public function __construct(string $minLevel = null, $output = null, callable $formatter = null) { if (null === $minLevel) { $minLevel = null === $output || 'php://stdout' === $output || 'php://stderr' === $output ? LogLevel::ERROR : LogLevel::WARNING; if (isset($_ENV['SHELL_VERBOSITY']) || isset($_SERVER['SHELL_VERBOSITY'])) { - switch ((int) (isset($_ENV['SHELL_VERBOSITY']) ? $_ENV['SHELL_VERBOSITY'] : $_SERVER['SHELL_VERBOSITY'])) { + switch ((int) ($_ENV['SHELL_VERBOSITY'] ?? $_SERVER['SHELL_VERBOSITY'])) { case -1: $minLevel = LogLevel::ERROR; break; case 1: $minLevel = LogLevel::NOTICE; break; case 2: $minLevel = LogLevel::INFO; break; @@ -52,11 +57,11 @@ class Logger extends AbstractLogger } } - if (!isset(self::$levels[$minLevel])) { + if (!isset(self::LEVELS[$minLevel])) { throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $minLevel)); } - $this->minLevelIndex = self::$levels[$minLevel]; + $this->minLevelIndex = self::LEVELS[$minLevel]; $this->formatter = $formatter ?: [$this, 'format']; if ($output && false === $this->handle = \is_resource($output) ? $output : @fopen($output, 'a')) { throw new InvalidArgumentException(sprintf('Unable to open "%s".', $output)); @@ -65,14 +70,16 @@ class Logger extends AbstractLogger /** * {@inheritdoc} + * + * @return void */ public function log($level, $message, array $context = []) { - if (!isset(self::$levels[$level])) { + if (!isset(self::LEVELS[$level])) { throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); } - if (self::$levels[$level] < $this->minLevelIndex) { + if (self::LEVELS[$level] < $this->minLevelIndex) { return; } @@ -84,18 +91,12 @@ class Logger extends AbstractLogger } } - /** - * @param string $level - * @param string $message - * - * @return string - */ - private function format($level, $message, array $context, $prefixDate = true) + private function format(string $level, string $message, array $context, bool $prefixDate = true): string { - if (false !== strpos($message, '{')) { + if (str_contains($message, '{')) { $replacements = []; foreach ($context as $key => $val) { - if (null === $val || is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) { + if (null === $val || \is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) { $replacements["{{$key}}"] = $val; } elseif ($val instanceof \DateTimeInterface) { $replacements["{{$key}}"] = $val->format(\DateTime::RFC3339); diff --git a/lib/symfony/http-kernel/Profiler/FileProfilerStorage.php b/lib/symfony/http-kernel/Profiler/FileProfilerStorage.php index c70830ae2f..3e7b1bd884 100644 --- a/lib/symfony/http-kernel/Profiler/FileProfilerStorage.php +++ b/lib/symfony/http-kernel/Profiler/FileProfilerStorage.php @@ -30,13 +30,11 @@ class FileProfilerStorage implements ProfilerStorageInterface * * Example : "file:/path/to/the/storage/folder" * - * @param string $dsn The DSN - * * @throws \RuntimeException */ - public function __construct($dsn) + public function __construct(string $dsn) { - if (0 !== strpos($dsn, 'file:')) { + if (!str_starts_with($dsn, 'file:')) { throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use FileStorage with an invalid dsn "%s". The expected format is "file:/path/to/the/storage/folder".', $dsn)); } $this->folder = substr($dsn, 5); @@ -49,7 +47,7 @@ class FileProfilerStorage implements ProfilerStorageInterface /** * {@inheritdoc} */ - public function find($ip, $url, $limit, $method, $start = null, $end = null, $statusCode = null) + public function find(?string $ip, ?string $url, ?int $limit, ?string $method, int $start = null, int $end = null, string $statusCode = null): array { $file = $this->getIndexFilename(); @@ -63,10 +61,10 @@ class FileProfilerStorage implements ProfilerStorageInterface $result = []; while (\count($result) < $limit && $line = $this->readLineFromFile($file)) { $values = str_getcsv($line); - list($csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode) = $values; + [$csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode] = $values; $csvTime = (int) $csvTime; - if ($ip && false === strpos($csvIp, $ip) || $url && false === strpos($csvUrl, $url) || $method && false === strpos($csvMethod, $method) || $statusCode && false === strpos($csvStatusCode, $statusCode)) { + if ($ip && !str_contains($csvIp, $ip) || $url && !str_contains($csvUrl, $url) || $method && !str_contains($csvMethod, $method) || $statusCode && !str_contains($csvStatusCode, $statusCode)) { continue; } @@ -115,13 +113,21 @@ class FileProfilerStorage implements ProfilerStorageInterface /** * {@inheritdoc} */ - public function read($token) + public function read(string $token): ?Profile { if (!$token || !file_exists($file = $this->getFilename($token))) { return null; } - return $this->createProfileFromData($token, unserialize(file_get_contents($file))); + if (\function_exists('gzcompress')) { + $file = 'compress.zlib://'.$file; + } + + if (!$data = unserialize(file_get_contents($file))) { + return null; + } + + return $this->createProfileFromData($token, $data); } /** @@ -129,7 +135,7 @@ class FileProfilerStorage implements ProfilerStorageInterface * * @throws \RuntimeException */ - public function write(Profile $profile) + public function write(Profile $profile): bool { $file = $this->getFilename($profile->getToken()); @@ -146,7 +152,7 @@ class FileProfilerStorage implements ProfilerStorageInterface // when there are errors in sub-requests, the parent and/or children tokens // may equal the profile token, resulting in infinite loops $parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null; - $childrenToken = array_filter(array_map(function ($p) use ($profileToken) { + $childrenToken = array_filter(array_map(function (Profile $p) use ($profileToken) { return $profileToken !== $p->getToken() ? $p->getToken() : null; }, $profile->getChildren())); @@ -163,7 +169,14 @@ class FileProfilerStorage implements ProfilerStorageInterface 'status_code' => $profile->getStatusCode(), ]; - if (false === file_put_contents($file, serialize($data))) { + $context = stream_context_create(); + + if (\function_exists('gzcompress')) { + $file = 'compress.zlib://'.$file; + stream_context_set_option($context, 'zlib', 'level', 3); + } + + if (false === file_put_contents($file, serialize($data), 0, $context)) { return false; } @@ -191,11 +204,9 @@ class FileProfilerStorage implements ProfilerStorageInterface /** * Gets filename to store data, associated to the token. * - * @param string $token - * - * @return string The profile filename + * @return string */ - protected function getFilename($token) + protected function getFilename(string $token) { // Uses 4 last characters, because first are mostly the same. $folderA = substr($token, -2, 2); @@ -207,7 +218,7 @@ class FileProfilerStorage implements ProfilerStorageInterface /** * Gets the index filename. * - * @return string The index filename + * @return string */ protected function getIndexFilename() { @@ -221,7 +232,7 @@ class FileProfilerStorage implements ProfilerStorageInterface * * @param resource $file The file resource, with the pointer placed at the end of the line to read * - * @return mixed A string representing the line or null if beginning of file is reached + * @return mixed */ protected function readLineFromFile($file) { @@ -261,7 +272,7 @@ class FileProfilerStorage implements ProfilerStorageInterface return '' === $line ? null : $line; } - protected function createProfileFromData($token, $data, $parent = null) + protected function createProfileFromData(string $token, array $data, Profile $parent = null) { $profile = new Profile($token); $profile->setIp($data['ip']); @@ -284,7 +295,15 @@ class FileProfilerStorage implements ProfilerStorageInterface continue; } - $profile->addChild($this->createProfileFromData($token, unserialize(file_get_contents($file)), $profile)); + if (\function_exists('gzcompress')) { + $file = 'compress.zlib://'.$file; + } + + if (!$childData = unserialize(file_get_contents($file))) { + continue; + } + + $profile->addChild($this->createProfileFromData($token, $childData, $profile)); } return $profile; diff --git a/lib/symfony/http-kernel/Profiler/Profile.php b/lib/symfony/http-kernel/Profiler/Profile.php index 3665545d23..a622403e1b 100644 --- a/lib/symfony/http-kernel/Profiler/Profile.php +++ b/lib/symfony/http-kernel/Profiler/Profile.php @@ -43,20 +43,12 @@ class Profile */ private $children = []; - /** - * @param string $token The token - */ - public function __construct($token) + public function __construct(string $token) { $this->token = $token; } - /** - * Sets the token. - * - * @param string $token The token - */ - public function setToken($token) + public function setToken(string $token) { $this->token = $token; } @@ -64,7 +56,7 @@ class Profile /** * Gets the token. * - * @return string The token + * @return string */ public function getToken() { @@ -82,7 +74,7 @@ class Profile /** * Returns the parent profile. * - * @return self + * @return self|null */ public function getParent() { @@ -92,7 +84,7 @@ class Profile /** * Returns the parent token. * - * @return string|null The parent token + * @return string|null */ public function getParentToken() { @@ -102,19 +94,14 @@ class Profile /** * Returns the IP. * - * @return string|null The IP + * @return string|null */ public function getIp() { return $this->ip; } - /** - * Sets the IP. - * - * @param string $ip - */ - public function setIp($ip) + public function setIp(?string $ip) { $this->ip = $ip; } @@ -122,14 +109,14 @@ class Profile /** * Returns the request method. * - * @return string|null The request method + * @return string|null */ public function getMethod() { return $this->method; } - public function setMethod($method) + public function setMethod(string $method) { $this->method = $method; } @@ -137,47 +124,32 @@ class Profile /** * Returns the URL. * - * @return string|null The URL + * @return string|null */ public function getUrl() { return $this->url; } - /** - * @param string $url - */ - public function setUrl($url) + public function setUrl(?string $url) { $this->url = $url; } /** - * Returns the time. - * - * @return int The time + * @return int */ public function getTime() { - if (null === $this->time) { - return 0; - } - - return $this->time; + return $this->time ?? 0; } - /** - * @param int $time The time - */ - public function setTime($time) + public function setTime(int $time) { $this->time = $time; } - /** - * @param int $statusCode - */ - public function setStatusCode($statusCode) + public function setStatusCode(int $statusCode) { $this->statusCode = $statusCode; } @@ -222,16 +194,25 @@ class Profile $child->setParent($this); } + public function getChildByToken(string $token): ?self + { + foreach ($this->children as $child) { + if ($token === $child->getToken()) { + return $child; + } + } + + return null; + } + /** * Gets a Collector by name. * - * @param string $name A collector name - * - * @return DataCollectorInterface A DataCollectorInterface instance + * @return DataCollectorInterface * * @throws \InvalidArgumentException if the collector does not exist */ - public function getCollector($name) + public function getCollector(string $name) { if (!isset($this->collectors[$name])) { throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name)); @@ -272,17 +253,16 @@ class Profile } /** - * Returns true if a Collector for the given name exists. - * - * @param string $name A collector name - * * @return bool */ - public function hasCollector($name) + public function hasCollector(string $name) { return isset($this->collectors[$name]); } + /** + * @return array + */ public function __sleep() { return ['token', 'parent', 'children', 'collectors', 'ip', 'method', 'url', 'time', 'statusCode']; diff --git a/lib/symfony/http-kernel/Profiler/Profiler.php b/lib/symfony/http-kernel/Profiler/Profiler.php index 5cca92d765..25e126f731 100644 --- a/lib/symfony/http-kernel/Profiler/Profiler.php +++ b/lib/symfony/http-kernel/Profiler/Profiler.php @@ -17,13 +17,14 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use Symfony\Contracts\Service\ResetInterface; /** * Profiler. * * @author Fabien Potencier */ -class Profiler +class Profiler implements ResetInterface { private $storage; @@ -36,14 +37,11 @@ class Profiler private $initiallyEnabled = true; private $enabled = true; - /** - * @param bool $enable The initial enabled state - */ - public function __construct(ProfilerStorageInterface $storage, LoggerInterface $logger = null, $enable = true) + public function __construct(ProfilerStorageInterface $storage, LoggerInterface $logger = null, bool $enable = true) { $this->storage = $storage; $this->logger = $logger; - $this->initiallyEnabled = $this->enabled = (bool) $enable; + $this->initiallyEnabled = $this->enabled = $enable; } /** @@ -65,7 +63,7 @@ class Profiler /** * Loads the Profile for the given Response. * - * @return Profile|null A Profile instance + * @return Profile|null */ public function loadProfileFromResponse(Response $response) { @@ -79,11 +77,9 @@ class Profiler /** * Loads the Profile for the given token. * - * @param string $token A token - * - * @return Profile|null A Profile instance + * @return Profile|null */ - public function loadProfile($token) + public function loadProfile(string $token) { return $this->storage->read($token); } @@ -120,19 +116,15 @@ class Profiler /** * Finds profiler tokens for the given criteria. * - * @param string $ip The IP - * @param string $url The URL - * @param string $limit The maximum number of tokens to return - * @param string $method The request method - * @param string $start The start date to search from - * @param string $end The end date to search to - * @param string $statusCode The request status code + * @param string|null $limit The maximum number of tokens to return + * @param string|null $start The start date to search from + * @param string|null $end The end date to search to * - * @return array An array of tokens + * @return array * * @see https://php.net/datetime.formats for the supported date/time formats */ - public function find($ip, $url, $limit, $method, $start, $end, $statusCode = null) + public function find(?string $ip, ?string $url, ?string $limit, ?string $method, ?string $start, ?string $end, string $statusCode = null) { return $this->storage->find($ip, $url, $limit, $method, $this->getTimestamp($start), $this->getTimestamp($end), $statusCode); } @@ -140,9 +132,9 @@ class Profiler /** * Collects data for the given Response. * - * @return Profile|null A Profile instance or null if the profiler is disabled + * @return Profile|null */ - public function collect(Request $request, Response $response, \Exception $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null) { if (false === $this->enabled) { return null; @@ -159,6 +151,10 @@ class Profiler $profile->setIp('Unknown'); } + if ($prevToken = $response->headers->get('X-Debug-Token')) { + $response->headers->set('X-Previous-Debug-Token', $prevToken); + } + $response->headers->set('X-Debug-Token', $profile->getToken()); foreach ($this->collectors as $collector) { @@ -174,10 +170,6 @@ class Profiler public function reset() { foreach ($this->collectors as $collector) { - if (!method_exists($collector, 'reset')) { - continue; - } - $collector->reset(); } $this->enabled = $this->initiallyEnabled; @@ -186,7 +178,7 @@ class Profiler /** * Gets the Collectors associated with this profiler. * - * @return array An array of collectors + * @return array */ public function all() { @@ -211,10 +203,6 @@ class Profiler */ public function add(DataCollectorInterface $collector) { - if (!method_exists($collector, 'reset')) { - @trigger_error(sprintf('Implementing "%s" without the "reset()" method is deprecated since Symfony 3.4 and will be unsupported in 4.0 for class "%s".', DataCollectorInterface::class, \get_class($collector)), \E_USER_DEPRECATED); - } - $this->collectors[$collector->getName()] = $collector; } @@ -225,7 +213,7 @@ class Profiler * * @return bool */ - public function has($name) + public function has(string $name) { return isset($this->collectors[$name]); } @@ -235,11 +223,11 @@ class Profiler * * @param string $name A collector name * - * @return DataCollectorInterface A DataCollectorInterface instance + * @return DataCollectorInterface * * @throws \InvalidArgumentException if the collector does not exist */ - public function get($name) + public function get(string $name) { if (!isset($this->collectors[$name])) { throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name)); @@ -248,12 +236,9 @@ class Profiler return $this->collectors[$name]; } - /** - * @return int|null - */ - private function getTimestamp($value) + private function getTimestamp(?string $value): ?int { - if (null === $value || '' == $value) { + if (null === $value || '' === $value) { return null; } diff --git a/lib/symfony/http-kernel/Profiler/ProfilerStorageInterface.php b/lib/symfony/http-kernel/Profiler/ProfilerStorageInterface.php index 04cba51e2d..95d72f46b3 100644 --- a/lib/symfony/http-kernel/Profiler/ProfilerStorageInterface.php +++ b/lib/symfony/http-kernel/Profiler/ProfilerStorageInterface.php @@ -14,6 +14,14 @@ namespace Symfony\Component\HttpKernel\Profiler; /** * ProfilerStorageInterface. * + * This interface exists for historical reasons. The only supported + * implementation is FileProfilerStorage. + * + * As the profiler must only be used on non-production servers, the file storage + * is more than enough and no other implementations will ever be supported. + * + * @internal + * * @author Fabien Potencier */ interface ProfilerStorageInterface @@ -21,34 +29,23 @@ interface ProfilerStorageInterface /** * Finds profiler tokens for the given criteria. * - * @param string $ip The IP - * @param string $url The URL - * @param string $limit The maximum number of tokens to return - * @param string $method The request method - * @param int|null $start The start date to search from - * @param int|null $end The end date to search to - * - * @return array An array of tokens + * @param int|null $limit The maximum number of tokens to return + * @param int|null $start The start date to search from + * @param int|null $end The end date to search to */ - public function find($ip, $url, $limit, $method, $start = null, $end = null); + public function find(?string $ip, ?string $url, ?int $limit, ?string $method, int $start = null, int $end = null): array; /** * Reads data associated with the given token. * * The method returns false if the token does not exist in the storage. - * - * @param string $token A token - * - * @return Profile|null The profile associated with token */ - public function read($token); + public function read(string $token): ?Profile; /** * Saves a Profile. - * - * @return bool Write operation successful */ - public function write(Profile $profile); + public function write(Profile $profile): bool; /** * Purges all data from the database. diff --git a/lib/symfony/http-kernel/README.md b/lib/symfony/http-kernel/README.md index abdaf513f9..ca50417827 100644 --- a/lib/symfony/http-kernel/README.md +++ b/lib/symfony/http-kernel/README.md @@ -3,14 +3,29 @@ HttpKernel Component The HttpKernel component provides a structured process for converting a Request into a Response by making use of the EventDispatcher component. It's flexible -enough to create a full-stack framework (Symfony), a micro-framework (Silex) or -an advanced CMS system (Drupal). +enough to create full-stack frameworks, micro-frameworks or advanced CMS systems like Drupal. + +Sponsor +------- + +The HttpKernel component for Symfony 5.4/6.0 is [backed][1] by [Les-Tilleuls.coop][2]. + +Les-Tilleuls.coop is a team of 50+ Symfony experts who can help you design, develop and +fix your projects. We provide a wide range of professional services including development, +consulting, coaching, training and audits. We also are highly skilled in JS, Go and DevOps. +We are a worker cooperative! + +Help Symfony by [sponsoring][3] its development! Resources --------- - * [Documentation](https://symfony.com/doc/current/components/http_kernel.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) + * [Documentation](https://symfony.com/doc/current/components/http_kernel.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) + +[1]: https://symfony.com/backers +[2]: https://les-tilleuls.coop +[3]: https://symfony.com/sponsor diff --git a/lib/symfony/http-kernel/RebootableInterface.php b/lib/symfony/http-kernel/RebootableInterface.php index 58d9ef59e4..e257237da9 100644 --- a/lib/symfony/http-kernel/RebootableInterface.php +++ b/lib/symfony/http-kernel/RebootableInterface.php @@ -21,10 +21,10 @@ interface RebootableInterface /** * Reboots a kernel. * - * The getCacheDir() method of a rebootable kernel should not be called - * while building the container. Use the %kernel.cache_dir% parameter instead. + * The getBuildDir() method of a rebootable kernel should not be called + * while building the container. Use the %kernel.build_dir% parameter instead. * - * @param string|null $warmupDir pass null to reboot in the regular cache directory + * @param string|null $warmupDir pass null to reboot in the regular build directory */ - public function reboot($warmupDir); + public function reboot(?string $warmupDir); } diff --git a/lib/symfony/http-kernel/Resources/welcome.html.php b/lib/symfony/http-kernel/Resources/welcome.html.php index 8fdc00506c..b25f99b3d0 100644 --- a/lib/symfony/http-kernel/Resources/welcome.html.php +++ b/lib/symfony/http-kernel/Resources/welcome.html.php @@ -1,70 +1,123 @@ - + - Welcome! + + Welcome to Symfony! -
                  -
                  -
                  -

                  Welcome to Symfony

                  -
                  - -
                  -

                  - - - Your application is now ready. You can start working on it at:
                  - -

                  -
                  - - -
                  -
                  +
                  +
                  +

                  - You're seeing this page because debug mode is enabled and you haven't configured any homepage URL. + You're seeing this page because you haven't configured any homepage URL and debug mode is enabled.

                  + +
                  +
                  + +

                  Welcome to Symfony

                  +
                  + +
                  + + + + + + +

                  Your application is now ready and you can start working on it.

                  +
                  + + +
                  + +
                  + +
                  diff --git a/lib/symfony/http-kernel/UriSigner.php b/lib/symfony/http-kernel/UriSigner.php index 3927f10114..38931ce170 100644 --- a/lib/symfony/http-kernel/UriSigner.php +++ b/lib/symfony/http-kernel/UriSigner.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpKernel; +use Symfony\Component\HttpFoundation\Request; + /** * Signs URIs. * @@ -25,7 +27,7 @@ class UriSigner * @param string $secret A secret * @param string $parameter Query string parameter to use */ - public function __construct($secret, $parameter = '_hash') + public function __construct(string $secret, string $parameter = '_hash') { $this->secret = $secret; $this->parameter = $parameter; @@ -37,11 +39,9 @@ class UriSigner * The given URI is signed by adding the query string parameter * which value depends on the URI and the secret. * - * @param string $uri A URI to sign - * - * @return string The signed URI + * @return string */ - public function sign($uri) + public function sign(string $uri) { $url = parse_url($uri); if (isset($url['query'])) { @@ -59,11 +59,9 @@ class UriSigner /** * Checks that a URI contains the correct hash. * - * @param string $uri A signed URI - * - * @return bool True if the URI is signed correctly, false otherwise + * @return bool */ - public function check($uri) + public function check(string $uri) { $url = parse_url($uri); if (isset($url['query'])) { @@ -82,23 +80,31 @@ class UriSigner return hash_equals($this->computeHash($this->buildUrl($url, $params)), $hash); } - private function computeHash($uri) + public function checkRequest(Request $request): bool + { + $qs = ($qs = $request->server->get('QUERY_STRING')) ? '?'.$qs : ''; + + // we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering) + return $this->check($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().$qs); + } + + private function computeHash(string $uri): string { return base64_encode(hash_hmac('sha256', $uri, $this->secret, true)); } - private function buildUrl(array $url, array $params = []) + private function buildUrl(array $url, array $params = []): string { ksort($params, \SORT_STRING); $url['query'] = http_build_query($params, '', '&'); $scheme = isset($url['scheme']) ? $url['scheme'].'://' : ''; - $host = isset($url['host']) ? $url['host'] : ''; + $host = $url['host'] ?? ''; $port = isset($url['port']) ? ':'.$url['port'] : ''; - $user = isset($url['user']) ? $url['user'] : ''; + $user = $url['user'] ?? ''; $pass = isset($url['pass']) ? ':'.$url['pass'] : ''; $pass = ($user || $pass) ? "$pass@" : ''; - $path = isset($url['path']) ? $url['path'] : ''; + $path = $url['path'] ?? ''; $query = isset($url['query']) && $url['query'] ? '?'.$url['query'] : ''; $fragment = isset($url['fragment']) ? '#'.$url['fragment'] : ''; diff --git a/lib/symfony/http-kernel/composer.json b/lib/symfony/http-kernel/composer.json index 06a67607ec..09682db49d 100644 --- a/lib/symfony/http-kernel/composer.json +++ b/lib/symfony/http-kernel/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/http-kernel", "type": "library", - "description": "Symfony HttpKernel Component", + "description": "Provides a structured process for converting a Request into a Response", "keywords": [], "homepage": "https://symfony.com", "license": "MIT", @@ -16,48 +16,58 @@ } ], "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/event-dispatcher": "~2.8|~3.0|~4.0", - "symfony/http-foundation": "~3.4.12|~4.0.12|^4.1.1", - "symfony/debug": "^3.3.3|~4.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php56": "~1.8", - "psr/log": "~1.0" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^5.0|^6.0", + "symfony/http-foundation": "^5.3.7|^6.0", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "psr/log": "^1|^2" }, "require-dev": { - "symfony/browser-kit": "~2.8|~3.0|~4.0", - "symfony/class-loader": "~2.8|~3.0", - "symfony/config": "~2.8|~3.0|~4.0", - "symfony/console": "~2.8|~3.0|~4.0", - "symfony/css-selector": "~2.8|~3.0|~4.0", - "symfony/dependency-injection": "^3.4.10|^4.0.10", - "symfony/dom-crawler": "~2.8|~3.0|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/process": "~2.8|~3.0|~4.0", - "symfony/routing": "~3.4|~4.0", - "symfony/stopwatch": "~2.8|~3.0|~4.0", - "symfony/templating": "~2.8|~3.0|~4.0", - "symfony/translation": "~2.8|~3.0|~4.0", - "symfony/var-dumper": "~3.3|~4.0", - "psr/cache": "~1.0" + "symfony/browser-kit": "^5.4|^6.0", + "symfony/config": "^5.0|^6.0", + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/css-selector": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.3|^6.0", + "symfony/dom-crawler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/http-client-contracts": "^1.1|^2|^3", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/routing": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0", + "symfony/translation": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2|^3", + "psr/cache": "^1.0|^2.0|^3.0", + "twig/twig": "^2.13|^3.0.4" }, "provide": { - "psr/log-implementation": "1.0" + "psr/log-implementation": "1.0|2.0" }, "conflict": { - "symfony/config": "<2.8", - "symfony/dependency-injection": "<3.4.10|<4.0.10,>=4", - "symfony/var-dumper": "<3.3", - "twig/twig": "<1.34|<2.4,>=2" + "symfony/browser-kit": "<5.4", + "symfony/cache": "<5.0", + "symfony/config": "<5.0", + "symfony/console": "<4.4", + "symfony/form": "<5.0", + "symfony/dependency-injection": "<5.3", + "symfony/doctrine-bridge": "<5.0", + "symfony/http-client": "<5.0", + "symfony/mailer": "<5.0", + "symfony/messenger": "<5.0", + "symfony/translation": "<5.0", + "symfony/twig-bridge": "<5.0", + "symfony/validator": "<5.0", + "twig/twig": "<2.13" }, "suggest": { "symfony/browser-kit": "", "symfony/config": "", "symfony/console": "", - "symfony/dependency-injection": "", - "symfony/finder": "", - "symfony/var-dumper": "" + "symfony/dependency-injection": "" }, "autoload": { "psr-4": { "Symfony\\Component\\HttpKernel\\": "" }, diff --git a/lib/symfony/http-kernel/phpunit.xml.dist b/lib/symfony/http-kernel/phpunit.xml.dist deleted file mode 100644 index 3fc07707f2..0000000000 --- a/lib/symfony/http-kernel/phpunit.xml.dist +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - ./Tests/ - - - - - - ./ - - ./Tests - ./vendor - - - - - - - - - Symfony\Component\HttpFoundation - - - - - diff --git a/lib/symfony/polyfill-apcu/Apcu.php b/lib/symfony/polyfill-apcu/Apcu.php deleted file mode 100644 index 95a65459a5..0000000000 --- a/lib/symfony/polyfill-apcu/Apcu.php +++ /dev/null @@ -1,106 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Polyfill\Apcu; - -/** - * Apcu for Zend Server Data Cache. - * - * @author Kate Gray - * @author Nicolas Grekas - * - * @internal - */ -final class Apcu -{ - public static function apcu_add($key, $var = null, $ttl = 0) - { - if (!\is_array($key)) { - return apc_add($key, $var, $ttl); - } - - $errors = array(); - foreach ($key as $k => $v) { - if (!apc_add($k, $v, $ttl)) { - $errors[$k] = -1; - } - } - - return $errors; - } - - public static function apcu_store($key, $var = null, $ttl = 0) - { - if (!\is_array($key)) { - return apc_store($key, $var, $ttl); - } - - $errors = array(); - foreach ($key as $k => $v) { - if (!apc_store($k, $v, $ttl)) { - $errors[$k] = -1; - } - } - - return $errors; - } - - public static function apcu_exists($keys) - { - if (!\is_array($keys)) { - return apc_exists($keys); - } - - $existing = array(); - foreach ($keys as $k) { - if (apc_exists($k)) { - $existing[$k] = true; - } - } - - return $existing; - } - - public static function apcu_fetch($key, &$success = null) - { - if (!\is_array($key)) { - return apc_fetch($key, $success); - } - - $succeeded = true; - $values = array(); - foreach ($key as $k) { - $v = apc_fetch($k, $success); - if ($success) { - $values[$k] = $v; - } else { - $succeeded = false; - } - } - $success = $succeeded; - - return $values; - } - - public static function apcu_delete($key) - { - if (!\is_array($key)) { - return apc_delete($key); - } - - $success = true; - foreach ($key as $k) { - $success = apc_delete($k) && $success; - } - - return $success; - } -} diff --git a/lib/symfony/polyfill-apcu/README.md b/lib/symfony/polyfill-apcu/README.md deleted file mode 100644 index b8a8997581..0000000000 --- a/lib/symfony/polyfill-apcu/README.md +++ /dev/null @@ -1,12 +0,0 @@ -Symfony Polyfill / APCu -======================== - -This component provides `apcu_*` functions and the `APCuIterator` class to users of the legacy APC extension. - -More information can be found in the -[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). - -License -======= - -This library is released under the [MIT license](LICENSE). diff --git a/lib/symfony/polyfill-apcu/bootstrap.php b/lib/symfony/polyfill-apcu/bootstrap.php deleted file mode 100644 index 90141a641b..0000000000 --- a/lib/symfony/polyfill-apcu/bootstrap.php +++ /dev/null @@ -1,79 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -use Symfony\Polyfill\Apcu as p; - -if (!extension_loaded('apc') && !extension_loaded('apcu')) { - return; -} - -if (extension_loaded('Zend Data Cache')) { - if (!function_exists('apcu_add')) { - function apcu_add($key, $var = null, $ttl = 0) { return p\Apcu::apcu_add($key, $var, $ttl); } - } - if (!function_exists('apcu_delete')) { - function apcu_delete($key) { return p\Apcu::apcu_delete($key); } - } - if (!function_exists('apcu_exists')) { - function apcu_exists($keys) { return p\Apcu::apcu_exists($keys); } - } - if (!function_exists('apcu_fetch')) { - function apcu_fetch($key, &$success = null) { return p\Apcu::apcu_fetch($key, $success); } - } - if (!function_exists('apcu_store')) { - function apcu_store($key, $var = null, $ttl = 0) { return p\Apcu::apcu_store($key, $var, $ttl); } - } -} else { - if (!function_exists('apcu_add')) { - function apcu_add($key, $var = null, $ttl = 0) { return apc_add($key, $var, $ttl); } - } - if (!function_exists('apcu_delete')) { - function apcu_delete($key) { return apc_delete($key); } - } - if (!function_exists('apcu_exists')) { - function apcu_exists($keys) { return apc_exists($keys); } - } - if (!function_exists('apcu_fetch')) { - function apcu_fetch($key, &$success = null) { return apc_fetch($key, $success); } - } - if (!function_exists('apcu_store')) { - function apcu_store($key, $var = null, $ttl = 0) { return apc_store($key, $var, $ttl); } - } -} - -if (!function_exists('apcu_cache_info')) { - function apcu_cache_info($limited = false) { return apc_cache_info('user', $limited); } -} -if (!function_exists('apcu_cas')) { - function apcu_cas($key, $old, $new) { return apc_cas($key, $old, $new); } -} -if (!function_exists('apcu_clear_cache')) { - function apcu_clear_cache() { return apc_clear_cache('user'); } -} -if (!function_exists('apcu_dec')) { - function apcu_dec($key, $step = 1, &$success = false) { return apc_dec($key, $step, $success); } -} -if (!function_exists('apcu_inc')) { - function apcu_inc($key, $step = 1, &$success = false) { return apc_inc($key, $step, $success); } -} -if (!function_exists('apcu_sma_info')) { - function apcu_sma_info($limited = false) { return apc_sma_info($limited); } -} - -if (!class_exists('APCuIterator', false) && class_exists('APCIterator', false)) { - class APCuIterator extends APCIterator - { - public function __construct($search = null, $format = APC_ITER_ALL, $chunk_size = 100, $list = APC_LIST_ACTIVE) - { - parent::__construct('user', $search, $format, $chunk_size, $list); - } - } -} diff --git a/lib/symfony/polyfill-apcu/composer.json b/lib/symfony/polyfill-apcu/composer.json deleted file mode 100644 index 816b438d75..0000000000 --- a/lib/symfony/polyfill-apcu/composer.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "symfony/polyfill-apcu", - "type": "library", - "description": "Symfony polyfill backporting apcu_* functions to lower PHP versions", - "keywords": ["polyfill", "shim", "compatibility", "portable", "apcu"], - "homepage": "https://symfony.com", - "license": "MIT", - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "require": { - "php": ">=5.3.3" - }, - "autoload": { - "psr-4": { "Symfony\\Polyfill\\Apcu\\": "" }, - "files": [ "bootstrap.php" ] - }, - "minimum-stability": "dev", - "extra": { - "branch-alias": { - "dev-main": "1.19-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - } -} diff --git a/lib/symfony/polyfill-ctype/Ctype.php b/lib/symfony/polyfill-ctype/Ctype.php index 58414dc73b..ba75a2c95f 100644 --- a/lib/symfony/polyfill-ctype/Ctype.php +++ b/lib/symfony/polyfill-ctype/Ctype.php @@ -25,13 +25,13 @@ final class Ctype * * @see https://php.net/ctype-alnum * - * @param string|int $text + * @param mixed $text * * @return bool */ public static function ctype_alnum($text) { - $text = self::convert_int_to_char_for_ctype($text); + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text); } @@ -41,13 +41,13 @@ final class Ctype * * @see https://php.net/ctype-alpha * - * @param string|int $text + * @param mixed $text * * @return bool */ public static function ctype_alpha($text) { - $text = self::convert_int_to_char_for_ctype($text); + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text); } @@ -57,13 +57,13 @@ final class Ctype * * @see https://php.net/ctype-cntrl * - * @param string|int $text + * @param mixed $text * * @return bool */ public static function ctype_cntrl($text) { - $text = self::convert_int_to_char_for_ctype($text); + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text); } @@ -73,13 +73,13 @@ final class Ctype * * @see https://php.net/ctype-digit * - * @param string|int $text + * @param mixed $text * * @return bool */ public static function ctype_digit($text) { - $text = self::convert_int_to_char_for_ctype($text); + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text); } @@ -89,13 +89,13 @@ final class Ctype * * @see https://php.net/ctype-graph * - * @param string|int $text + * @param mixed $text * * @return bool */ public static function ctype_graph($text) { - $text = self::convert_int_to_char_for_ctype($text); + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text); } @@ -105,13 +105,13 @@ final class Ctype * * @see https://php.net/ctype-lower * - * @param string|int $text + * @param mixed $text * * @return bool */ public static function ctype_lower($text) { - $text = self::convert_int_to_char_for_ctype($text); + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text); } @@ -121,13 +121,13 @@ final class Ctype * * @see https://php.net/ctype-print * - * @param string|int $text + * @param mixed $text * * @return bool */ public static function ctype_print($text) { - $text = self::convert_int_to_char_for_ctype($text); + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text); } @@ -137,13 +137,13 @@ final class Ctype * * @see https://php.net/ctype-punct * - * @param string|int $text + * @param mixed $text * * @return bool */ public static function ctype_punct($text) { - $text = self::convert_int_to_char_for_ctype($text); + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text); } @@ -153,13 +153,13 @@ final class Ctype * * @see https://php.net/ctype-space * - * @param string|int $text + * @param mixed $text * * @return bool */ public static function ctype_space($text) { - $text = self::convert_int_to_char_for_ctype($text); + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text); } @@ -169,13 +169,13 @@ final class Ctype * * @see https://php.net/ctype-upper * - * @param string|int $text + * @param mixed $text * * @return bool */ public static function ctype_upper($text) { - $text = self::convert_int_to_char_for_ctype($text); + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text); } @@ -185,13 +185,13 @@ final class Ctype * * @see https://php.net/ctype-xdigit * - * @param string|int $text + * @param mixed $text * * @return bool */ public static function ctype_xdigit($text) { - $text = self::convert_int_to_char_for_ctype($text); + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text); } @@ -204,11 +204,12 @@ final class Ctype * (negative values have 256 added in order to allow characters in the Extended ASCII range). * Any other integer is interpreted as a string containing the decimal digits of the integer. * - * @param string|int $int + * @param mixed $int + * @param string $function * * @return mixed */ - private static function convert_int_to_char_for_ctype($int) + private static function convert_int_to_char_for_ctype($int, $function) { if (!\is_int($int)) { return $int; @@ -218,6 +219,10 @@ 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/lib/symfony/polyfill-ctype/README.md b/lib/symfony/polyfill-ctype/README.md index 8add1ab009..b144d03c3c 100644 --- a/lib/symfony/polyfill-ctype/README.md +++ b/lib/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/lib/symfony/polyfill-ctype/bootstrap.php b/lib/symfony/polyfill-ctype/bootstrap.php index 0bc45cfdf4..d54524b31b 100644 --- a/lib/symfony/polyfill-ctype/bootstrap.php +++ b/lib/symfony/polyfill-ctype/bootstrap.php @@ -11,36 +11,40 @@ use Symfony\Polyfill\Ctype as p; +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + if (!function_exists('ctype_alnum')) { - function ctype_alnum($input) { return p\Ctype::ctype_alnum($input); } + function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); } } if (!function_exists('ctype_alpha')) { - function ctype_alpha($input) { return p\Ctype::ctype_alpha($input); } + function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); } } if (!function_exists('ctype_cntrl')) { - function ctype_cntrl($input) { return p\Ctype::ctype_cntrl($input); } + function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); } } if (!function_exists('ctype_digit')) { - function ctype_digit($input) { return p\Ctype::ctype_digit($input); } + function ctype_digit($text) { return p\Ctype::ctype_digit($text); } } if (!function_exists('ctype_graph')) { - function ctype_graph($input) { return p\Ctype::ctype_graph($input); } + function ctype_graph($text) { return p\Ctype::ctype_graph($text); } } if (!function_exists('ctype_lower')) { - function ctype_lower($input) { return p\Ctype::ctype_lower($input); } + function ctype_lower($text) { return p\Ctype::ctype_lower($text); } } if (!function_exists('ctype_print')) { - function ctype_print($input) { return p\Ctype::ctype_print($input); } + function ctype_print($text) { return p\Ctype::ctype_print($text); } } if (!function_exists('ctype_punct')) { - function ctype_punct($input) { return p\Ctype::ctype_punct($input); } + function ctype_punct($text) { return p\Ctype::ctype_punct($text); } } if (!function_exists('ctype_space')) { - function ctype_space($input) { return p\Ctype::ctype_space($input); } + function ctype_space($text) { return p\Ctype::ctype_space($text); } } if (!function_exists('ctype_upper')) { - function ctype_upper($input) { return p\Ctype::ctype_upper($input); } + function ctype_upper($text) { return p\Ctype::ctype_upper($text); } } if (!function_exists('ctype_xdigit')) { - function ctype_xdigit($input) { return p\Ctype::ctype_xdigit($input); } + function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); } } diff --git a/lib/symfony/polyfill-ctype/bootstrap80.php b/lib/symfony/polyfill-ctype/bootstrap80.php new file mode 100644 index 0000000000..ab2f8611da --- /dev/null +++ b/lib/symfony/polyfill-ctype/bootstrap80.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (!function_exists('ctype_alnum')) { + function ctype_alnum(mixed $text): bool { return p\Ctype::ctype_alnum($text); } +} +if (!function_exists('ctype_alpha')) { + function ctype_alpha(mixed $text): bool { return p\Ctype::ctype_alpha($text); } +} +if (!function_exists('ctype_cntrl')) { + function ctype_cntrl(mixed $text): bool { return p\Ctype::ctype_cntrl($text); } +} +if (!function_exists('ctype_digit')) { + function ctype_digit(mixed $text): bool { return p\Ctype::ctype_digit($text); } +} +if (!function_exists('ctype_graph')) { + function ctype_graph(mixed $text): bool { return p\Ctype::ctype_graph($text); } +} +if (!function_exists('ctype_lower')) { + function ctype_lower(mixed $text): bool { return p\Ctype::ctype_lower($text); } +} +if (!function_exists('ctype_print')) { + function ctype_print(mixed $text): bool { return p\Ctype::ctype_print($text); } +} +if (!function_exists('ctype_punct')) { + function ctype_punct(mixed $text): bool { return p\Ctype::ctype_punct($text); } +} +if (!function_exists('ctype_space')) { + function ctype_space(mixed $text): bool { return p\Ctype::ctype_space($text); } +} +if (!function_exists('ctype_upper')) { + function ctype_upper(mixed $text): bool { return p\Ctype::ctype_upper($text); } +} +if (!function_exists('ctype_xdigit')) { + function ctype_xdigit(mixed $text): bool { return p\Ctype::ctype_xdigit($text); } +} diff --git a/lib/symfony/polyfill-ctype/composer.json b/lib/symfony/polyfill-ctype/composer.json index d7c9abb654..ee5c931cd1 100644 --- a/lib/symfony/polyfill-ctype/composer.json +++ b/lib/symfony/polyfill-ctype/composer.json @@ -16,7 +16,10 @@ } ], "require": { - "php": ">=5.3.3" + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" }, "autoload": { "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" }, @@ -28,7 +31,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "1.19-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", diff --git a/lib/symfony/polyfill-intl-grapheme/Grapheme.php b/lib/symfony/polyfill-intl-grapheme/Grapheme.php new file mode 100644 index 0000000000..6f7c0c78d8 --- /dev/null +++ b/lib/symfony/polyfill-intl-grapheme/Grapheme.php @@ -0,0 +1,247 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Grapheme; + +\define('SYMFONY_GRAPHEME_CLUSTER_RX', ((float) \PCRE_VERSION < 10 ? (float) \PCRE_VERSION >= 8.32 : (float) \PCRE_VERSION >= 10.39) ? '\X' : Grapheme::GRAPHEME_CLUSTER_RX); + +/** + * Partial intl implementation in pure PHP. + * + * Implemented: + * - grapheme_extract - Extract a sequence of grapheme clusters from a text buffer, which must be encoded in UTF-8 + * - grapheme_stripos - Find position (in grapheme units) of first occurrence of a case-insensitive string + * - grapheme_stristr - Returns part of haystack string from the first occurrence of case-insensitive needle to the end of haystack + * - grapheme_strlen - Get string length in grapheme units + * - grapheme_strpos - Find position (in grapheme units) of first occurrence of a string + * - grapheme_strripos - Find position (in grapheme units) of last occurrence of a case-insensitive string + * - grapheme_strrpos - Find position (in grapheme units) of last occurrence of a string + * - grapheme_strstr - Returns part of haystack string from the first occurrence of needle to the end of haystack + * - grapheme_substr - Return part of a string + * + * @author Nicolas Grekas + * + * @internal + */ +final class Grapheme +{ + // (CRLF|([ZWNJ-ZWJ]|T+|L*(LV?V+|LV|LVT)T*|L+|[^Control])[Extend]*|[Control]) + // This regular expression is a work around for http://bugs.exim.org/1279 + public const GRAPHEME_CLUSTER_RX = '(?:\r\n|(?:[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[ᄀ-ᅟ]*(?:[가개갸걔거게겨계고과괘괴교구궈궤귀규그긔기까깨꺄꺠꺼께껴꼐꼬꽈꽤꾀꾜꾸꿔꿰뀌뀨끄끠끼나내냐냬너네녀녜노놔놰뇌뇨누눠눼뉘뉴느늬니다대댜댸더데뎌뎨도돠돼되됴두둬뒈뒤듀드듸디따때땨떄떠떼뗘뗴또똬뙈뙤뚀뚜뚸뛔뛰뜌뜨띄띠라래랴럐러레려례로롸뢔뢰료루뤄뤠뤼류르릐리마매먀먜머메며몌모뫄뫠뫼묘무뭐뭬뮈뮤므믜미바배뱌뱨버베벼볘보봐봬뵈뵤부붜붸뷔뷰브븨비빠빼뺘뺴뻐뻬뼈뼤뽀뽜뽸뾔뾰뿌뿨쀄쀠쀼쁘쁴삐사새샤섀서세셔셰소솨쇄쇠쇼수숴쉐쉬슈스싀시싸쌔쌰썌써쎄쎠쎼쏘쏴쐐쐬쑈쑤쒀쒜쒸쓔쓰씌씨아애야얘어에여예오와왜외요우워웨위유으의이자재쟈쟤저제져졔조좌좨죄죠주줘줴쥐쥬즈즤지짜째쨔쨰쩌쩨쪄쪠쪼쫘쫴쬐쬬쭈쭤쮀쮜쮸쯔쯰찌차채챠챼처체쳐쳬초촤쵀최쵸추춰췌취츄츠츼치카캐캬컈커케켜켸코콰쾌쾨쿄쿠쿼퀘퀴큐크킈키타태탸턔터테텨톄토톼퇘퇴툐투퉈퉤튀튜트틔티파패퍄퍠퍼페펴폐포퐈퐤푀표푸풔풰퓌퓨프픠피하해햐햬허헤혀혜호화홰회효후훠훼휘휴흐희히]?[ᅠ-ᆢ]+|[가-힣])[ᆨ-ᇹ]*|[ᄀ-ᅟ]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\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{200D}\x{1D165}\x{1D16E}-\x{1D172}]*|[\p{Cc}\p{Cf}\p{Zl}\p{Zp}])'; + + private const CASE_FOLD = [ + ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + public static function grapheme_extract($s, $size, $type = \GRAPHEME_EXTR_COUNT, $start = 0, &$next = 0) + { + if (0 > $start) { + $start = \strlen($s) + $start; + } + + if (!is_scalar($s)) { + $hasError = false; + set_error_handler(function () use (&$hasError) { $hasError = true; }); + $next = substr($s, $start); + restore_error_handler(); + if ($hasError) { + substr($s, $start); + $s = ''; + } else { + $s = $next; + } + } else { + $s = substr($s, $start); + } + $size = (int) $size; + $type = (int) $type; + $start = (int) $start; + + if (\GRAPHEME_EXTR_COUNT !== $type && \GRAPHEME_EXTR_MAXBYTES !== $type && \GRAPHEME_EXTR_MAXCHARS !== $type) { + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('grapheme_extract(): Argument #3 ($type) must be one of GRAPHEME_EXTR_COUNT, GRAPHEME_EXTR_MAXBYTES, or GRAPHEME_EXTR_MAXCHARS'); + } + + if (!isset($s[0]) || 0 > $size || 0 > $start) { + return false; + } + if (0 === $size) { + return ''; + } + + $next = $start; + + $s = preg_split('/('.SYMFONY_GRAPHEME_CLUSTER_RX.')/u', "\r\n".$s, $size + 1, \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE); + + if (!isset($s[1])) { + return false; + } + + $i = 1; + $ret = ''; + + do { + if (\GRAPHEME_EXTR_COUNT === $type) { + --$size; + } elseif (\GRAPHEME_EXTR_MAXBYTES === $type) { + $size -= \strlen($s[$i]); + } else { + $size -= iconv_strlen($s[$i], 'UTF-8//IGNORE'); + } + + if ($size >= 0) { + $ret .= $s[$i]; + } + } while (isset($s[++$i]) && $size > 0); + + $next += \strlen($ret); + + return $ret; + } + + public static function grapheme_strlen($s) + { + preg_replace('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', '', $s, -1, $len); + + return 0 === $len && '' !== $s ? null : $len; + } + + public static function grapheme_substr($s, $start, $len = null) + { + if (null === $len) { + $len = 2147483647; + } + + preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s, $s); + + $slen = \count($s[0]); + $start = (int) $start; + + if (0 > $start) { + $start += $slen; + } + if (0 > $start) { + if (\PHP_VERSION_ID < 80000) { + return false; + } + + $start = 0; + } + if ($start >= $slen) { + return \PHP_VERSION_ID >= 80000 ? '' : false; + } + + $rem = $slen - $start; + + if (0 > $len) { + $len += $rem; + } + if (0 === $len) { + return ''; + } + if (0 > $len) { + return \PHP_VERSION_ID >= 80000 ? '' : false; + } + if ($len > $rem) { + $len = $rem; + } + + return implode('', \array_slice($s[0], $start, $len)); + } + + public static function grapheme_strpos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 0); + } + + public static function grapheme_stripos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 1); + } + + public static function grapheme_strrpos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 2); + } + + public static function grapheme_strripos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 3); + } + + public static function grapheme_stristr($s, $needle, $beforeNeedle = false) + { + return mb_stristr($s, $needle, $beforeNeedle, 'UTF-8'); + } + + public static function grapheme_strstr($s, $needle, $beforeNeedle = false) + { + return mb_strstr($s, $needle, $beforeNeedle, 'UTF-8'); + } + + private static function grapheme_position($s, $needle, $offset, $mode) + { + $needle = (string) $needle; + if (80000 > \PHP_VERSION_ID && !preg_match('/./us', $needle)) { + return false; + } + $s = (string) $s; + if (!preg_match('/./us', $s)) { + return false; + } + if ($offset > 0) { + $s = self::grapheme_substr($s, $offset); + } elseif ($offset < 0) { + if (2 > $mode) { + $offset += self::grapheme_strlen($s); + $s = self::grapheme_substr($s, $offset); + if (0 > $offset) { + $offset = 0; + } + } elseif (0 > $offset += self::grapheme_strlen($needle)) { + $s = self::grapheme_substr($s, 0, $offset); + $offset = 0; + } else { + $offset = 0; + } + } + + // As UTF-8 is self-synchronizing, and we have ensured the strings are valid UTF-8, + // we can use normal binary string functions here. For case-insensitive searches, + // case fold the strings first. + $caseInsensitive = $mode & 1; + $reverse = $mode & 2; + if ($caseInsensitive) { + // Use the same case folding mode as mbstring does for mb_stripos(). + // Stick to SIMPLE case folding to avoid changing the length of the string, which + // might result in offsets being shifted. + $mode = \defined('MB_CASE_FOLD_SIMPLE') ? \MB_CASE_FOLD_SIMPLE : \MB_CASE_LOWER; + $s = mb_convert_case($s, $mode, 'UTF-8'); + $needle = mb_convert_case($needle, $mode, 'UTF-8'); + + if (!\defined('MB_CASE_FOLD_SIMPLE')) { + $s = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $s); + $needle = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $needle); + } + } + if ($reverse) { + $needlePos = strrpos($s, $needle); + } else { + $needlePos = strpos($s, $needle); + } + + return false !== $needlePos ? self::grapheme_strlen(substr($s, 0, $needlePos)) + $offset : false; + } +} diff --git a/lib/symfony/polyfill-apcu/LICENSE b/lib/symfony/polyfill-intl-grapheme/LICENSE similarity index 100% rename from lib/symfony/polyfill-apcu/LICENSE rename to lib/symfony/polyfill-intl-grapheme/LICENSE diff --git a/lib/symfony/polyfill-intl-grapheme/README.md b/lib/symfony/polyfill-intl-grapheme/README.md new file mode 100644 index 0000000000..f55d92c5c3 --- /dev/null +++ b/lib/symfony/polyfill-intl-grapheme/README.md @@ -0,0 +1,31 @@ +Symfony Polyfill / Intl: Grapheme +================================= + +This component provides a partial, native PHP implementation of the +[Grapheme functions](https://php.net/intl.grapheme) from the +[Intl](https://php.net/intl) extension. + +- [`grapheme_extract`](https://php.net/grapheme_extract): Extract a sequence of grapheme + clusters from a text buffer, which must be encoded in UTF-8 +- [`grapheme_stripos`](https://php.net/grapheme_stripos): Find position (in grapheme units) + of first occurrence of a case-insensitive string +- [`grapheme_stristr`](https://php.net/grapheme_stristr): Returns part of haystack string + from the first occurrence of case-insensitive needle to the end of haystack +- [`grapheme_strlen`](https://php.net/grapheme_strlen): Get string length in grapheme units +- [`grapheme_strpos`](https://php.net/grapheme_strpos): Find position (in grapheme units) + of first occurrence of a string +- [`grapheme_strripos`](https://php.net/grapheme_strripos): Find position (in grapheme units) + of last occurrence of a case-insensitive string +- [`grapheme_strrpos`](https://php.net/grapheme_strrpos): Find position (in grapheme units) + of last occurrence of a string +- [`grapheme_strstr`](https://php.net/grapheme_strstr): Returns part of haystack string from + the first occurrence of needle to the end of haystack +- [`grapheme_substr`](https://php.net/grapheme_substr): Return part of a string + +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/lib/symfony/polyfill-intl-grapheme/bootstrap.php b/lib/symfony/polyfill-intl-grapheme/bootstrap.php new file mode 100644 index 0000000000..a9ea03c7e5 --- /dev/null +++ b/lib/symfony/polyfill-intl-grapheme/bootstrap.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Grapheme as p; + +if (extension_loaded('intl')) { + return; +} + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!defined('GRAPHEME_EXTR_COUNT')) { + define('GRAPHEME_EXTR_COUNT', 0); +} +if (!defined('GRAPHEME_EXTR_MAXBYTES')) { + define('GRAPHEME_EXTR_MAXBYTES', 1); +} +if (!defined('GRAPHEME_EXTR_MAXCHARS')) { + define('GRAPHEME_EXTR_MAXCHARS', 2); +} + +if (!function_exists('grapheme_extract')) { + function grapheme_extract($haystack, $size, $type = 0, $start = 0, &$next = 0) { return p\Grapheme::grapheme_extract($haystack, $size, $type, $start, $next); } +} +if (!function_exists('grapheme_stripos')) { + function grapheme_stripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_stripos($haystack, $needle, $offset); } +} +if (!function_exists('grapheme_stristr')) { + function grapheme_stristr($haystack, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_stristr($haystack, $needle, $beforeNeedle); } +} +if (!function_exists('grapheme_strlen')) { + function grapheme_strlen($input) { return p\Grapheme::grapheme_strlen($input); } +} +if (!function_exists('grapheme_strpos')) { + function grapheme_strpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strpos($haystack, $needle, $offset); } +} +if (!function_exists('grapheme_strripos')) { + function grapheme_strripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strripos($haystack, $needle, $offset); } +} +if (!function_exists('grapheme_strrpos')) { + function grapheme_strrpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strrpos($haystack, $needle, $offset); } +} +if (!function_exists('grapheme_strstr')) { + function grapheme_strstr($haystack, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_strstr($haystack, $needle, $beforeNeedle); } +} +if (!function_exists('grapheme_substr')) { + function grapheme_substr($string, $offset, $length = null) { return p\Grapheme::grapheme_substr($string, $offset, $length); } +} diff --git a/lib/symfony/polyfill-intl-grapheme/bootstrap80.php b/lib/symfony/polyfill-intl-grapheme/bootstrap80.php new file mode 100644 index 0000000000..b8c0786778 --- /dev/null +++ b/lib/symfony/polyfill-intl-grapheme/bootstrap80.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Grapheme as p; + +if (!defined('GRAPHEME_EXTR_COUNT')) { + define('GRAPHEME_EXTR_COUNT', 0); +} +if (!defined('GRAPHEME_EXTR_MAXBYTES')) { + define('GRAPHEME_EXTR_MAXBYTES', 1); +} +if (!defined('GRAPHEME_EXTR_MAXCHARS')) { + define('GRAPHEME_EXTR_MAXCHARS', 2); +} + +if (!function_exists('grapheme_extract')) { + function grapheme_extract(?string $haystack, ?int $size, ?int $type = GRAPHEME_EXTR_COUNT, ?int $offset = 0, &$next = null): string|false { return p\Grapheme::grapheme_extract((string) $haystack, (int) $size, (int) $type, (int) $offset, $next); } +} +if (!function_exists('grapheme_stripos')) { + function grapheme_stripos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_stripos((string) $haystack, (string) $needle, (int) $offset); } +} +if (!function_exists('grapheme_stristr')) { + function grapheme_stristr(?string $haystack, ?string $needle, ?bool $beforeNeedle = false): string|false { return p\Grapheme::grapheme_stristr((string) $haystack, (string) $needle, (bool) $beforeNeedle); } +} +if (!function_exists('grapheme_strlen')) { + function grapheme_strlen(?string $string): int|false|null { return p\Grapheme::grapheme_strlen((string) $string); } +} +if (!function_exists('grapheme_strpos')) { + function grapheme_strpos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strpos((string) $haystack, (string) $needle, (int) $offset); } +} +if (!function_exists('grapheme_strripos')) { + function grapheme_strripos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strripos((string) $haystack, (string) $needle, (int) $offset); } +} +if (!function_exists('grapheme_strrpos')) { + function grapheme_strrpos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strrpos((string) $haystack, (string) $needle, (int) $offset); } +} +if (!function_exists('grapheme_strstr')) { + function grapheme_strstr(?string $haystack, ?string $needle, ?bool $beforeNeedle = false): string|false { return p\Grapheme::grapheme_strstr((string) $haystack, (string) $needle, (bool) $beforeNeedle); } +} +if (!function_exists('grapheme_substr')) { + function grapheme_substr(?string $string, ?int $offset, ?int $length = null): string|false { return p\Grapheme::grapheme_substr((string) $string, (int) $offset, $length); } +} diff --git a/lib/symfony/polyfill-intl-grapheme/composer.json b/lib/symfony/polyfill-intl-grapheme/composer.json new file mode 100644 index 0000000000..824c96f935 --- /dev/null +++ b/lib/symfony/polyfill-intl-grapheme/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/polyfill-intl-grapheme", + "type": "library", + "description": "Symfony polyfill for intl's grapheme_* functions", + "keywords": ["polyfill", "shim", "compatibility", "portable", "intl", "grapheme"], + "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.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Intl\\Grapheme\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-intl": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/lib/symfony/polyfill-intl-idn/Idn.php b/lib/symfony/polyfill-intl-idn/Idn.php new file mode 100644 index 0000000000..fee3026df2 --- /dev/null +++ b/lib/symfony/polyfill-intl-idn/Idn.php @@ -0,0 +1,925 @@ + and Trevor Rowbotham + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Idn; + +use Exception; +use Normalizer; +use Symfony\Polyfill\Intl\Idn\Resources\unidata\DisallowedRanges; +use Symfony\Polyfill\Intl\Idn\Resources\unidata\Regex; + +/** + * @see https://www.unicode.org/reports/tr46/ + * + * @internal + */ +final class Idn +{ + public const ERROR_EMPTY_LABEL = 1; + public const ERROR_LABEL_TOO_LONG = 2; + public const ERROR_DOMAIN_NAME_TOO_LONG = 4; + public const ERROR_LEADING_HYPHEN = 8; + public const ERROR_TRAILING_HYPHEN = 0x10; + public const ERROR_HYPHEN_3_4 = 0x20; + public const ERROR_LEADING_COMBINING_MARK = 0x40; + public const ERROR_DISALLOWED = 0x80; + public const ERROR_PUNYCODE = 0x100; + public const ERROR_LABEL_HAS_DOT = 0x200; + public const ERROR_INVALID_ACE_LABEL = 0x400; + public const ERROR_BIDI = 0x800; + public const ERROR_CONTEXTJ = 0x1000; + public const ERROR_CONTEXTO_PUNCTUATION = 0x2000; + public const ERROR_CONTEXTO_DIGITS = 0x4000; + + public const INTL_IDNA_VARIANT_2003 = 0; + public const INTL_IDNA_VARIANT_UTS46 = 1; + + public const IDNA_DEFAULT = 0; + public const IDNA_ALLOW_UNASSIGNED = 1; + public const IDNA_USE_STD3_RULES = 2; + public const IDNA_CHECK_BIDI = 4; + public const IDNA_CHECK_CONTEXTJ = 8; + public const IDNA_NONTRANSITIONAL_TO_ASCII = 16; + public const IDNA_NONTRANSITIONAL_TO_UNICODE = 32; + + public const MAX_DOMAIN_SIZE = 253; + public const MAX_LABEL_SIZE = 63; + + public const BASE = 36; + public const TMIN = 1; + public const TMAX = 26; + public const SKEW = 38; + public const DAMP = 700; + public const INITIAL_BIAS = 72; + public const INITIAL_N = 128; + public const DELIMITER = '-'; + public const MAX_INT = 2147483647; + + /** + * Contains the numeric value of a basic code point (for use in representing integers) in the + * range 0 to BASE-1, or -1 if b is does not represent a value. + * + * @var array + */ + private static $basicToDigit = [ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, + + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + ]; + + /** + * @var array + */ + private static $virama; + + /** + * @var array + */ + private static $mapped; + + /** + * @var array + */ + private static $ignored; + + /** + * @var array + */ + private static $deviation; + + /** + * @var array + */ + private static $disallowed; + + /** + * @var array + */ + private static $disallowed_STD3_mapped; + + /** + * @var array + */ + private static $disallowed_STD3_valid; + + /** + * @var bool + */ + private static $mappingTableLoaded = false; + + /** + * @see https://www.unicode.org/reports/tr46/#ToASCII + * + * @param string $domainName + * @param int $options + * @param int $variant + * @param array $idna_info + * + * @return string|false + */ + public static function idn_to_ascii($domainName, $options = self::IDNA_DEFAULT, $variant = self::INTL_IDNA_VARIANT_UTS46, &$idna_info = []) + { + if (\PHP_VERSION_ID >= 70200 && self::INTL_IDNA_VARIANT_2003 === $variant) { + @trigger_error('idn_to_ascii(): INTL_IDNA_VARIANT_2003 is deprecated', \E_USER_DEPRECATED); + } + + $options = [ + 'CheckHyphens' => true, + 'CheckBidi' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 !== ($options & self::IDNA_CHECK_BIDI), + 'CheckJoiners' => self::INTL_IDNA_VARIANT_UTS46 === $variant && 0 !== ($options & self::IDNA_CHECK_CONTEXTJ), + 'UseSTD3ASCIIRules' => 0 !== ($options & self::IDNA_USE_STD3_RULES), + 'Transitional_Processing' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 === ($options & self::IDNA_NONTRANSITIONAL_TO_ASCII), + 'VerifyDnsLength' => true, + ]; + $info = new Info(); + $labels = self::process((string) $domainName, $options, $info); + + foreach ($labels as $i => $label) { + // Only convert labels to punycode that contain non-ASCII code points + if (1 === preg_match('/[^\x00-\x7F]/', $label)) { + try { + $label = 'xn--'.self::punycodeEncode($label); + } catch (Exception $e) { + $info->errors |= self::ERROR_PUNYCODE; + } + + $labels[$i] = $label; + } + } + + if ($options['VerifyDnsLength']) { + self::validateDomainAndLabelLength($labels, $info); + } + + $idna_info = [ + 'result' => implode('.', $labels), + 'isTransitionalDifferent' => $info->transitionalDifferent, + 'errors' => $info->errors, + ]; + + return 0 === $info->errors ? $idna_info['result'] : false; + } + + /** + * @see https://www.unicode.org/reports/tr46/#ToUnicode + * + * @param string $domainName + * @param int $options + * @param int $variant + * @param array $idna_info + * + * @return string|false + */ + public static function idn_to_utf8($domainName, $options = self::IDNA_DEFAULT, $variant = self::INTL_IDNA_VARIANT_UTS46, &$idna_info = []) + { + if (\PHP_VERSION_ID >= 70200 && self::INTL_IDNA_VARIANT_2003 === $variant) { + @trigger_error('idn_to_utf8(): INTL_IDNA_VARIANT_2003 is deprecated', \E_USER_DEPRECATED); + } + + $info = new Info(); + $labels = self::process((string) $domainName, [ + 'CheckHyphens' => true, + 'CheckBidi' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 !== ($options & self::IDNA_CHECK_BIDI), + 'CheckJoiners' => self::INTL_IDNA_VARIANT_UTS46 === $variant && 0 !== ($options & self::IDNA_CHECK_CONTEXTJ), + 'UseSTD3ASCIIRules' => 0 !== ($options & self::IDNA_USE_STD3_RULES), + 'Transitional_Processing' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 === ($options & self::IDNA_NONTRANSITIONAL_TO_UNICODE), + ], $info); + $idna_info = [ + 'result' => implode('.', $labels), + 'isTransitionalDifferent' => $info->transitionalDifferent, + 'errors' => $info->errors, + ]; + + return 0 === $info->errors ? $idna_info['result'] : false; + } + + /** + * @param string $label + * + * @return bool + */ + private static function isValidContextJ(array $codePoints, $label) + { + if (!isset(self::$virama)) { + self::$virama = require __DIR__.\DIRECTORY_SEPARATOR.'Resources'.\DIRECTORY_SEPARATOR.'unidata'.\DIRECTORY_SEPARATOR.'virama.php'; + } + + $offset = 0; + + foreach ($codePoints as $i => $codePoint) { + if (0x200C !== $codePoint && 0x200D !== $codePoint) { + continue; + } + + if (!isset($codePoints[$i - 1])) { + return false; + } + + // If Canonical_Combining_Class(Before(cp)) .eq. Virama Then True; + if (isset(self::$virama[$codePoints[$i - 1]])) { + continue; + } + + // If RegExpMatch((Joining_Type:{L,D})(Joining_Type:T)*\u200C(Joining_Type:T)*(Joining_Type:{R,D})) Then + // True; + // Generated RegExp = ([Joining_Type:{L,D}][Joining_Type:T]*\u200C[Joining_Type:T]*)[Joining_Type:{R,D}] + if (0x200C === $codePoint && 1 === preg_match(Regex::ZWNJ, $label, $matches, \PREG_OFFSET_CAPTURE, $offset)) { + $offset += \strlen($matches[1][0]); + + continue; + } + + return false; + } + + return true; + } + + /** + * @see https://www.unicode.org/reports/tr46/#ProcessingStepMap + * + * @param string $input + * @param array $options + * + * @return string + */ + private static function mapCodePoints($input, array $options, Info $info) + { + $str = ''; + $useSTD3ASCIIRules = $options['UseSTD3ASCIIRules']; + $transitional = $options['Transitional_Processing']; + + foreach (self::utf8Decode($input) as $codePoint) { + $data = self::lookupCodePointStatus($codePoint, $useSTD3ASCIIRules); + + switch ($data['status']) { + case 'disallowed': + $info->errors |= self::ERROR_DISALLOWED; + + // no break. + + case 'valid': + $str .= mb_chr($codePoint, 'utf-8'); + + break; + + case 'ignored': + // Do nothing. + break; + + case 'mapped': + $str .= $data['mapping']; + + break; + + case 'deviation': + $info->transitionalDifferent = true; + $str .= ($transitional ? $data['mapping'] : mb_chr($codePoint, 'utf-8')); + + break; + } + } + + return $str; + } + + /** + * @see https://www.unicode.org/reports/tr46/#Processing + * + * @param string $domain + * @param array $options + * + * @return array + */ + private static function process($domain, array $options, Info $info) + { + // If VerifyDnsLength is not set, we are doing ToUnicode otherwise we are doing ToASCII and + // we need to respect the VerifyDnsLength option. + $checkForEmptyLabels = !isset($options['VerifyDnsLength']) || $options['VerifyDnsLength']; + + if ($checkForEmptyLabels && '' === $domain) { + $info->errors |= self::ERROR_EMPTY_LABEL; + + return [$domain]; + } + + // Step 1. Map each code point in the domain name string + $domain = self::mapCodePoints($domain, $options, $info); + + // Step 2. Normalize the domain name string to Unicode Normalization Form C. + if (!Normalizer::isNormalized($domain, Normalizer::FORM_C)) { + $domain = Normalizer::normalize($domain, Normalizer::FORM_C); + } + + // Step 3. Break the string into labels at U+002E (.) FULL STOP. + $labels = explode('.', $domain); + $lastLabelIndex = \count($labels) - 1; + + // Step 4. Convert and validate each label in the domain name string. + foreach ($labels as $i => $label) { + $validationOptions = $options; + + if ('xn--' === substr($label, 0, 4)) { + try { + $label = self::punycodeDecode(substr($label, 4)); + } catch (Exception $e) { + $info->errors |= self::ERROR_PUNYCODE; + + continue; + } + + $validationOptions['Transitional_Processing'] = false; + $labels[$i] = $label; + } + + self::validateLabel($label, $info, $validationOptions, $i > 0 && $i === $lastLabelIndex); + } + + if ($info->bidiDomain && !$info->validBidiDomain) { + $info->errors |= self::ERROR_BIDI; + } + + // Any input domain name string that does not record an error has been successfully + // processed according to this specification. Conversely, if an input domain_name string + // causes an error, then the processing of the input domain_name string fails. Determining + // what to do with error input is up to the caller, and not in the scope of this document. + return $labels; + } + + /** + * @see https://tools.ietf.org/html/rfc5893#section-2 + * + * @param string $label + */ + private static function validateBidiLabel($label, Info $info) + { + if (1 === preg_match(Regex::RTL_LABEL, $label)) { + $info->bidiDomain = true; + + // Step 1. The first character must be a character with Bidi property L, R, or AL. + // If it has the R or AL property, it is an RTL label + if (1 !== preg_match(Regex::BIDI_STEP_1_RTL, $label)) { + $info->validBidiDomain = false; + + return; + } + + // Step 2. In an RTL label, only characters with the Bidi properties R, AL, AN, EN, ES, + // CS, ET, ON, BN, or NSM are allowed. + if (1 === preg_match(Regex::BIDI_STEP_2, $label)) { + $info->validBidiDomain = false; + + return; + } + + // Step 3. In an RTL label, the end of the label must be a character with Bidi property + // R, AL, EN, or AN, followed by zero or more characters with Bidi property NSM. + if (1 !== preg_match(Regex::BIDI_STEP_3, $label)) { + $info->validBidiDomain = false; + + return; + } + + // Step 4. In an RTL label, if an EN is present, no AN may be present, and vice versa. + if (1 === preg_match(Regex::BIDI_STEP_4_AN, $label) && 1 === preg_match(Regex::BIDI_STEP_4_EN, $label)) { + $info->validBidiDomain = false; + + return; + } + + return; + } + + // We are a LTR label + // Step 1. The first character must be a character with Bidi property L, R, or AL. + // If it has the L property, it is an LTR label. + if (1 !== preg_match(Regex::BIDI_STEP_1_LTR, $label)) { + $info->validBidiDomain = false; + + return; + } + + // Step 5. In an LTR label, only characters with the Bidi properties L, EN, + // ES, CS, ET, ON, BN, or NSM are allowed. + if (1 === preg_match(Regex::BIDI_STEP_5, $label)) { + $info->validBidiDomain = false; + + return; + } + + // Step 6.In an LTR label, the end of the label must be a character with Bidi property L or + // EN, followed by zero or more characters with Bidi property NSM. + if (1 !== preg_match(Regex::BIDI_STEP_6, $label)) { + $info->validBidiDomain = false; + + return; + } + } + + /** + * @param array $labels + */ + private static function validateDomainAndLabelLength(array $labels, Info $info) + { + $maxDomainSize = self::MAX_DOMAIN_SIZE; + $length = \count($labels); + + // Number of "." delimiters. + $domainLength = $length - 1; + + // If the last label is empty and it is not the first label, then it is the root label. + // Increase the max size by 1, making it 254, to account for the root label's "." + // delimiter. This also means we don't need to check the last label's length for being too + // long. + if ($length > 1 && '' === $labels[$length - 1]) { + ++$maxDomainSize; + --$length; + } + + for ($i = 0; $i < $length; ++$i) { + $bytes = \strlen($labels[$i]); + $domainLength += $bytes; + + if ($bytes > self::MAX_LABEL_SIZE) { + $info->errors |= self::ERROR_LABEL_TOO_LONG; + } + } + + if ($domainLength > $maxDomainSize) { + $info->errors |= self::ERROR_DOMAIN_NAME_TOO_LONG; + } + } + + /** + * @see https://www.unicode.org/reports/tr46/#Validity_Criteria + * + * @param string $label + * @param array $options + * @param bool $canBeEmpty + */ + private static function validateLabel($label, Info $info, array $options, $canBeEmpty) + { + if ('' === $label) { + if (!$canBeEmpty && (!isset($options['VerifyDnsLength']) || $options['VerifyDnsLength'])) { + $info->errors |= self::ERROR_EMPTY_LABEL; + } + + return; + } + + // Step 1. The label must be in Unicode Normalization Form C. + if (!Normalizer::isNormalized($label, Normalizer::FORM_C)) { + $info->errors |= self::ERROR_INVALID_ACE_LABEL; + } + + $codePoints = self::utf8Decode($label); + + if ($options['CheckHyphens']) { + // Step 2. If CheckHyphens, the label must not contain a U+002D HYPHEN-MINUS character + // in both the thrid and fourth positions. + if (isset($codePoints[2], $codePoints[3]) && 0x002D === $codePoints[2] && 0x002D === $codePoints[3]) { + $info->errors |= self::ERROR_HYPHEN_3_4; + } + + // Step 3. If CheckHyphens, the label must neither begin nor end with a U+002D + // HYPHEN-MINUS character. + if ('-' === substr($label, 0, 1)) { + $info->errors |= self::ERROR_LEADING_HYPHEN; + } + + if ('-' === substr($label, -1, 1)) { + $info->errors |= self::ERROR_TRAILING_HYPHEN; + } + } + + // Step 4. The label must not contain a U+002E (.) FULL STOP. + if (false !== strpos($label, '.')) { + $info->errors |= self::ERROR_LABEL_HAS_DOT; + } + + // Step 5. The label must not begin with a combining mark, that is: General_Category=Mark. + if (1 === preg_match(Regex::COMBINING_MARK, $label)) { + $info->errors |= self::ERROR_LEADING_COMBINING_MARK; + } + + // Step 6. Each code point in the label must only have certain status values according to + // Section 5, IDNA Mapping Table: + $transitional = $options['Transitional_Processing']; + $useSTD3ASCIIRules = $options['UseSTD3ASCIIRules']; + + foreach ($codePoints as $codePoint) { + $data = self::lookupCodePointStatus($codePoint, $useSTD3ASCIIRules); + $status = $data['status']; + + if ('valid' === $status || (!$transitional && 'deviation' === $status)) { + continue; + } + + $info->errors |= self::ERROR_DISALLOWED; + + break; + } + + // Step 7. If CheckJoiners, the label must satisify the ContextJ rules from Appendix A, in + // The Unicode Code Points and Internationalized Domain Names for Applications (IDNA) + // [IDNA2008]. + if ($options['CheckJoiners'] && !self::isValidContextJ($codePoints, $label)) { + $info->errors |= self::ERROR_CONTEXTJ; + } + + // Step 8. If CheckBidi, and if the domain name is a Bidi domain name, then the label must + // satisfy all six of the numbered conditions in [IDNA2008] RFC 5893, Section 2. + if ($options['CheckBidi'] && (!$info->bidiDomain || $info->validBidiDomain)) { + self::validateBidiLabel($label, $info); + } + } + + /** + * @see https://tools.ietf.org/html/rfc3492#section-6.2 + * + * @param string $input + * + * @return string + */ + private static function punycodeDecode($input) + { + $n = self::INITIAL_N; + $out = 0; + $i = 0; + $bias = self::INITIAL_BIAS; + $lastDelimIndex = strrpos($input, self::DELIMITER); + $b = false === $lastDelimIndex ? 0 : $lastDelimIndex; + $inputLength = \strlen($input); + $output = []; + $bytes = array_map('ord', str_split($input)); + + for ($j = 0; $j < $b; ++$j) { + if ($bytes[$j] > 0x7F) { + throw new Exception('Invalid input'); + } + + $output[$out++] = $input[$j]; + } + + if ($b > 0) { + ++$b; + } + + for ($in = $b; $in < $inputLength; ++$out) { + $oldi = $i; + $w = 1; + + for ($k = self::BASE; /* no condition */; $k += self::BASE) { + if ($in >= $inputLength) { + throw new Exception('Invalid input'); + } + + $digit = self::$basicToDigit[$bytes[$in++] & 0xFF]; + + if ($digit < 0) { + throw new Exception('Invalid input'); + } + + if ($digit > intdiv(self::MAX_INT - $i, $w)) { + throw new Exception('Integer overflow'); + } + + $i += $digit * $w; + + if ($k <= $bias) { + $t = self::TMIN; + } elseif ($k >= $bias + self::TMAX) { + $t = self::TMAX; + } else { + $t = $k - $bias; + } + + if ($digit < $t) { + break; + } + + $baseMinusT = self::BASE - $t; + + if ($w > intdiv(self::MAX_INT, $baseMinusT)) { + throw new Exception('Integer overflow'); + } + + $w *= $baseMinusT; + } + + $outPlusOne = $out + 1; + $bias = self::adaptBias($i - $oldi, $outPlusOne, 0 === $oldi); + + if (intdiv($i, $outPlusOne) > self::MAX_INT - $n) { + throw new Exception('Integer overflow'); + } + + $n += intdiv($i, $outPlusOne); + $i %= $outPlusOne; + array_splice($output, $i++, 0, [mb_chr($n, 'utf-8')]); + } + + return implode('', $output); + } + + /** + * @see https://tools.ietf.org/html/rfc3492#section-6.3 + * + * @param string $input + * + * @return string + */ + private static function punycodeEncode($input) + { + $n = self::INITIAL_N; + $delta = 0; + $out = 0; + $bias = self::INITIAL_BIAS; + $inputLength = 0; + $output = ''; + $iter = self::utf8Decode($input); + + foreach ($iter as $codePoint) { + ++$inputLength; + + if ($codePoint < 0x80) { + $output .= \chr($codePoint); + ++$out; + } + } + + $h = $out; + $b = $out; + + if ($b > 0) { + $output .= self::DELIMITER; + ++$out; + } + + while ($h < $inputLength) { + $m = self::MAX_INT; + + foreach ($iter as $codePoint) { + if ($codePoint >= $n && $codePoint < $m) { + $m = $codePoint; + } + } + + if ($m - $n > intdiv(self::MAX_INT - $delta, $h + 1)) { + throw new Exception('Integer overflow'); + } + + $delta += ($m - $n) * ($h + 1); + $n = $m; + + foreach ($iter as $codePoint) { + if ($codePoint < $n && 0 === ++$delta) { + throw new Exception('Integer overflow'); + } + + if ($codePoint === $n) { + $q = $delta; + + for ($k = self::BASE; /* no condition */; $k += self::BASE) { + if ($k <= $bias) { + $t = self::TMIN; + } elseif ($k >= $bias + self::TMAX) { + $t = self::TMAX; + } else { + $t = $k - $bias; + } + + if ($q < $t) { + break; + } + + $qMinusT = $q - $t; + $baseMinusT = self::BASE - $t; + $output .= self::encodeDigit($t + ($qMinusT) % ($baseMinusT), false); + ++$out; + $q = intdiv($qMinusT, $baseMinusT); + } + + $output .= self::encodeDigit($q, false); + ++$out; + $bias = self::adaptBias($delta, $h + 1, $h === $b); + $delta = 0; + ++$h; + } + } + + ++$delta; + ++$n; + } + + return $output; + } + + /** + * @see https://tools.ietf.org/html/rfc3492#section-6.1 + * + * @param int $delta + * @param int $numPoints + * @param bool $firstTime + * + * @return int + */ + private static function adaptBias($delta, $numPoints, $firstTime) + { + // xxx >> 1 is a faster way of doing intdiv(xxx, 2) + $delta = $firstTime ? intdiv($delta, self::DAMP) : $delta >> 1; + $delta += intdiv($delta, $numPoints); + $k = 0; + + while ($delta > ((self::BASE - self::TMIN) * self::TMAX) >> 1) { + $delta = intdiv($delta, self::BASE - self::TMIN); + $k += self::BASE; + } + + return $k + intdiv((self::BASE - self::TMIN + 1) * $delta, $delta + self::SKEW); + } + + /** + * @param int $d + * @param bool $flag + * + * @return string + */ + private static function encodeDigit($d, $flag) + { + return \chr($d + 22 + 75 * ($d < 26 ? 1 : 0) - (($flag ? 1 : 0) << 5)); + } + + /** + * Takes a UTF-8 encoded string and converts it into a series of integer code points. Any + * invalid byte sequences will be replaced by a U+FFFD replacement code point. + * + * @see https://encoding.spec.whatwg.org/#utf-8-decoder + * + * @param string $input + * + * @return array + */ + private static function utf8Decode($input) + { + $bytesSeen = 0; + $bytesNeeded = 0; + $lowerBoundary = 0x80; + $upperBoundary = 0xBF; + $codePoint = 0; + $codePoints = []; + $length = \strlen($input); + + for ($i = 0; $i < $length; ++$i) { + $byte = \ord($input[$i]); + + if (0 === $bytesNeeded) { + if ($byte >= 0x00 && $byte <= 0x7F) { + $codePoints[] = $byte; + + continue; + } + + if ($byte >= 0xC2 && $byte <= 0xDF) { + $bytesNeeded = 1; + $codePoint = $byte & 0x1F; + } elseif ($byte >= 0xE0 && $byte <= 0xEF) { + if (0xE0 === $byte) { + $lowerBoundary = 0xA0; + } elseif (0xED === $byte) { + $upperBoundary = 0x9F; + } + + $bytesNeeded = 2; + $codePoint = $byte & 0xF; + } elseif ($byte >= 0xF0 && $byte <= 0xF4) { + if (0xF0 === $byte) { + $lowerBoundary = 0x90; + } elseif (0xF4 === $byte) { + $upperBoundary = 0x8F; + } + + $bytesNeeded = 3; + $codePoint = $byte & 0x7; + } else { + $codePoints[] = 0xFFFD; + } + + continue; + } + + if ($byte < $lowerBoundary || $byte > $upperBoundary) { + $codePoint = 0; + $bytesNeeded = 0; + $bytesSeen = 0; + $lowerBoundary = 0x80; + $upperBoundary = 0xBF; + --$i; + $codePoints[] = 0xFFFD; + + continue; + } + + $lowerBoundary = 0x80; + $upperBoundary = 0xBF; + $codePoint = ($codePoint << 6) | ($byte & 0x3F); + + if (++$bytesSeen !== $bytesNeeded) { + continue; + } + + $codePoints[] = $codePoint; + $codePoint = 0; + $bytesNeeded = 0; + $bytesSeen = 0; + } + + // String unexpectedly ended, so append a U+FFFD code point. + if (0 !== $bytesNeeded) { + $codePoints[] = 0xFFFD; + } + + return $codePoints; + } + + /** + * @param int $codePoint + * @param bool $useSTD3ASCIIRules + * + * @return array{status: string, mapping?: string} + */ + private static function lookupCodePointStatus($codePoint, $useSTD3ASCIIRules) + { + if (!self::$mappingTableLoaded) { + self::$mappingTableLoaded = true; + self::$mapped = require __DIR__.'/Resources/unidata/mapped.php'; + self::$ignored = require __DIR__.'/Resources/unidata/ignored.php'; + self::$deviation = require __DIR__.'/Resources/unidata/deviation.php'; + self::$disallowed = require __DIR__.'/Resources/unidata/disallowed.php'; + self::$disallowed_STD3_mapped = require __DIR__.'/Resources/unidata/disallowed_STD3_mapped.php'; + self::$disallowed_STD3_valid = require __DIR__.'/Resources/unidata/disallowed_STD3_valid.php'; + } + + if (isset(self::$mapped[$codePoint])) { + return ['status' => 'mapped', 'mapping' => self::$mapped[$codePoint]]; + } + + if (isset(self::$ignored[$codePoint])) { + return ['status' => 'ignored']; + } + + if (isset(self::$deviation[$codePoint])) { + return ['status' => 'deviation', 'mapping' => self::$deviation[$codePoint]]; + } + + if (isset(self::$disallowed[$codePoint]) || DisallowedRanges::inRange($codePoint)) { + return ['status' => 'disallowed']; + } + + $isDisallowedMapped = isset(self::$disallowed_STD3_mapped[$codePoint]); + + if ($isDisallowedMapped || isset(self::$disallowed_STD3_valid[$codePoint])) { + $status = 'disallowed'; + + if (!$useSTD3ASCIIRules) { + $status = $isDisallowedMapped ? 'mapped' : 'valid'; + } + + if ($isDisallowedMapped) { + return ['status' => $status, 'mapping' => self::$disallowed_STD3_mapped[$codePoint]]; + } + + return ['status' => $status]; + } + + return ['status' => 'valid']; + } +} diff --git a/lib/symfony/polyfill-intl-idn/Info.php b/lib/symfony/polyfill-intl-idn/Info.php new file mode 100644 index 0000000000..25c3582b2a --- /dev/null +++ b/lib/symfony/polyfill-intl-idn/Info.php @@ -0,0 +1,23 @@ + and Trevor Rowbotham + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Idn; + +/** + * @internal + */ +class Info +{ + public $bidiDomain = false; + public $errors = 0; + public $validBidiDomain = true; + public $transitionalDifferent = false; +} diff --git a/lib/symfony/polyfill-intl-idn/LICENSE b/lib/symfony/polyfill-intl-idn/LICENSE new file mode 100644 index 0000000000..03c5e25774 --- /dev/null +++ b/lib/symfony/polyfill-intl-idn/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 Fabien Potencier and Trevor Rowbotham + +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/lib/symfony/polyfill-intl-idn/README.md b/lib/symfony/polyfill-intl-idn/README.md new file mode 100644 index 0000000000..cae551705a --- /dev/null +++ b/lib/symfony/polyfill-intl-idn/README.md @@ -0,0 +1,12 @@ +Symfony Polyfill / Intl: Idn +============================ + +This component provides [`idn_to_ascii`](https://php.net/idn-to-ascii) and [`idn_to_utf8`](https://php.net/idn-to-utf8) functions to users who run php versions without the [Intl](https://php.net/intl) extension. + +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/lib/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php b/lib/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php new file mode 100644 index 0000000000..5bb70e48ac --- /dev/null +++ b/lib/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php @@ -0,0 +1,375 @@ += 128 && $codePoint <= 159) { + return true; + } + + if ($codePoint >= 2155 && $codePoint <= 2207) { + return true; + } + + if ($codePoint >= 3676 && $codePoint <= 3712) { + return true; + } + + if ($codePoint >= 3808 && $codePoint <= 3839) { + return true; + } + + if ($codePoint >= 4059 && $codePoint <= 4095) { + return true; + } + + if ($codePoint >= 4256 && $codePoint <= 4293) { + return true; + } + + if ($codePoint >= 6849 && $codePoint <= 6911) { + return true; + } + + if ($codePoint >= 11859 && $codePoint <= 11903) { + return true; + } + + if ($codePoint >= 42955 && $codePoint <= 42996) { + return true; + } + + if ($codePoint >= 55296 && $codePoint <= 57343) { + return true; + } + + if ($codePoint >= 57344 && $codePoint <= 63743) { + return true; + } + + if ($codePoint >= 64218 && $codePoint <= 64255) { + return true; + } + + if ($codePoint >= 64976 && $codePoint <= 65007) { + return true; + } + + if ($codePoint >= 65630 && $codePoint <= 65663) { + return true; + } + + if ($codePoint >= 65953 && $codePoint <= 65999) { + return true; + } + + if ($codePoint >= 66046 && $codePoint <= 66175) { + return true; + } + + if ($codePoint >= 66518 && $codePoint <= 66559) { + return true; + } + + if ($codePoint >= 66928 && $codePoint <= 67071) { + return true; + } + + if ($codePoint >= 67432 && $codePoint <= 67583) { + return true; + } + + if ($codePoint >= 67760 && $codePoint <= 67807) { + return true; + } + + if ($codePoint >= 67904 && $codePoint <= 67967) { + return true; + } + + if ($codePoint >= 68256 && $codePoint <= 68287) { + return true; + } + + if ($codePoint >= 68528 && $codePoint <= 68607) { + return true; + } + + if ($codePoint >= 68681 && $codePoint <= 68735) { + return true; + } + + if ($codePoint >= 68922 && $codePoint <= 69215) { + return true; + } + + if ($codePoint >= 69298 && $codePoint <= 69375) { + return true; + } + + if ($codePoint >= 69466 && $codePoint <= 69551) { + return true; + } + + if ($codePoint >= 70207 && $codePoint <= 70271) { + return true; + } + + if ($codePoint >= 70517 && $codePoint <= 70655) { + return true; + } + + if ($codePoint >= 70874 && $codePoint <= 71039) { + return true; + } + + if ($codePoint >= 71134 && $codePoint <= 71167) { + return true; + } + + if ($codePoint >= 71370 && $codePoint <= 71423) { + return true; + } + + if ($codePoint >= 71488 && $codePoint <= 71679) { + return true; + } + + if ($codePoint >= 71740 && $codePoint <= 71839) { + return true; + } + + if ($codePoint >= 72026 && $codePoint <= 72095) { + return true; + } + + if ($codePoint >= 72441 && $codePoint <= 72703) { + return true; + } + + if ($codePoint >= 72887 && $codePoint <= 72959) { + return true; + } + + if ($codePoint >= 73130 && $codePoint <= 73439) { + return true; + } + + if ($codePoint >= 73465 && $codePoint <= 73647) { + return true; + } + + if ($codePoint >= 74650 && $codePoint <= 74751) { + return true; + } + + if ($codePoint >= 75076 && $codePoint <= 77823) { + return true; + } + + if ($codePoint >= 78905 && $codePoint <= 82943) { + return true; + } + + if ($codePoint >= 83527 && $codePoint <= 92159) { + return true; + } + + if ($codePoint >= 92784 && $codePoint <= 92879) { + return true; + } + + if ($codePoint >= 93072 && $codePoint <= 93759) { + return true; + } + + if ($codePoint >= 93851 && $codePoint <= 93951) { + return true; + } + + if ($codePoint >= 94112 && $codePoint <= 94175) { + return true; + } + + if ($codePoint >= 101590 && $codePoint <= 101631) { + return true; + } + + if ($codePoint >= 101641 && $codePoint <= 110591) { + return true; + } + + if ($codePoint >= 110879 && $codePoint <= 110927) { + return true; + } + + if ($codePoint >= 111356 && $codePoint <= 113663) { + return true; + } + + if ($codePoint >= 113828 && $codePoint <= 118783) { + return true; + } + + if ($codePoint >= 119366 && $codePoint <= 119519) { + return true; + } + + if ($codePoint >= 119673 && $codePoint <= 119807) { + return true; + } + + if ($codePoint >= 121520 && $codePoint <= 122879) { + return true; + } + + if ($codePoint >= 122923 && $codePoint <= 123135) { + return true; + } + + if ($codePoint >= 123216 && $codePoint <= 123583) { + return true; + } + + if ($codePoint >= 123648 && $codePoint <= 124927) { + return true; + } + + if ($codePoint >= 125143 && $codePoint <= 125183) { + return true; + } + + if ($codePoint >= 125280 && $codePoint <= 126064) { + return true; + } + + if ($codePoint >= 126133 && $codePoint <= 126208) { + return true; + } + + if ($codePoint >= 126270 && $codePoint <= 126463) { + return true; + } + + if ($codePoint >= 126652 && $codePoint <= 126703) { + return true; + } + + if ($codePoint >= 126706 && $codePoint <= 126975) { + return true; + } + + if ($codePoint >= 127406 && $codePoint <= 127461) { + return true; + } + + if ($codePoint >= 127590 && $codePoint <= 127743) { + return true; + } + + if ($codePoint >= 129202 && $codePoint <= 129279) { + return true; + } + + if ($codePoint >= 129751 && $codePoint <= 129791) { + return true; + } + + if ($codePoint >= 129995 && $codePoint <= 130031) { + return true; + } + + if ($codePoint >= 130042 && $codePoint <= 131069) { + return true; + } + + if ($codePoint >= 173790 && $codePoint <= 173823) { + return true; + } + + if ($codePoint >= 191457 && $codePoint <= 194559) { + return true; + } + + if ($codePoint >= 195102 && $codePoint <= 196605) { + return true; + } + + if ($codePoint >= 201547 && $codePoint <= 262141) { + return true; + } + + if ($codePoint >= 262144 && $codePoint <= 327677) { + return true; + } + + if ($codePoint >= 327680 && $codePoint <= 393213) { + return true; + } + + if ($codePoint >= 393216 && $codePoint <= 458749) { + return true; + } + + if ($codePoint >= 458752 && $codePoint <= 524285) { + return true; + } + + if ($codePoint >= 524288 && $codePoint <= 589821) { + return true; + } + + if ($codePoint >= 589824 && $codePoint <= 655357) { + return true; + } + + if ($codePoint >= 655360 && $codePoint <= 720893) { + return true; + } + + if ($codePoint >= 720896 && $codePoint <= 786429) { + return true; + } + + if ($codePoint >= 786432 && $codePoint <= 851965) { + return true; + } + + if ($codePoint >= 851968 && $codePoint <= 917501) { + return true; + } + + if ($codePoint >= 917536 && $codePoint <= 917631) { + return true; + } + + if ($codePoint >= 917632 && $codePoint <= 917759) { + return true; + } + + if ($codePoint >= 918000 && $codePoint <= 983037) { + return true; + } + + if ($codePoint >= 983040 && $codePoint <= 1048573) { + return true; + } + + if ($codePoint >= 1048576 && $codePoint <= 1114109) { + return true; + } + + return false; + } +} diff --git a/lib/symfony/polyfill-intl-idn/Resources/unidata/Regex.php b/lib/symfony/polyfill-intl-idn/Resources/unidata/Regex.php new file mode 100644 index 0000000000..5c1c51ddee --- /dev/null +++ b/lib/symfony/polyfill-intl-idn/Resources/unidata/Regex.php @@ -0,0 +1,24 @@ + 'ss', + 962 => 'σ', + 8204 => '', + 8205 => '', +); diff --git a/lib/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php b/lib/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php new file mode 100644 index 0000000000..25a5f564d5 --- /dev/null +++ b/lib/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php @@ -0,0 +1,2638 @@ + true, + 889 => true, + 896 => true, + 897 => true, + 898 => true, + 899 => true, + 907 => true, + 909 => true, + 930 => true, + 1216 => true, + 1328 => true, + 1367 => true, + 1368 => true, + 1419 => true, + 1420 => true, + 1424 => true, + 1480 => true, + 1481 => true, + 1482 => true, + 1483 => true, + 1484 => true, + 1485 => true, + 1486 => true, + 1487 => true, + 1515 => true, + 1516 => true, + 1517 => true, + 1518 => true, + 1525 => true, + 1526 => true, + 1527 => true, + 1528 => true, + 1529 => true, + 1530 => true, + 1531 => true, + 1532 => true, + 1533 => true, + 1534 => true, + 1535 => true, + 1536 => true, + 1537 => true, + 1538 => true, + 1539 => true, + 1540 => true, + 1541 => true, + 1564 => true, + 1565 => true, + 1757 => true, + 1806 => true, + 1807 => true, + 1867 => true, + 1868 => true, + 1970 => true, + 1971 => true, + 1972 => true, + 1973 => true, + 1974 => true, + 1975 => true, + 1976 => true, + 1977 => true, + 1978 => true, + 1979 => true, + 1980 => true, + 1981 => true, + 1982 => true, + 1983 => true, + 2043 => true, + 2044 => true, + 2094 => true, + 2095 => true, + 2111 => true, + 2140 => true, + 2141 => true, + 2143 => true, + 2229 => true, + 2248 => true, + 2249 => true, + 2250 => true, + 2251 => true, + 2252 => true, + 2253 => true, + 2254 => true, + 2255 => true, + 2256 => true, + 2257 => true, + 2258 => true, + 2274 => true, + 2436 => true, + 2445 => true, + 2446 => true, + 2449 => true, + 2450 => true, + 2473 => true, + 2481 => true, + 2483 => true, + 2484 => true, + 2485 => true, + 2490 => true, + 2491 => true, + 2501 => true, + 2502 => true, + 2505 => true, + 2506 => true, + 2511 => true, + 2512 => true, + 2513 => true, + 2514 => true, + 2515 => true, + 2516 => true, + 2517 => true, + 2518 => true, + 2520 => true, + 2521 => true, + 2522 => true, + 2523 => true, + 2526 => true, + 2532 => true, + 2533 => true, + 2559 => true, + 2560 => true, + 2564 => true, + 2571 => true, + 2572 => true, + 2573 => true, + 2574 => true, + 2577 => true, + 2578 => true, + 2601 => true, + 2609 => true, + 2612 => true, + 2615 => true, + 2618 => true, + 2619 => true, + 2621 => true, + 2627 => true, + 2628 => true, + 2629 => true, + 2630 => true, + 2633 => true, + 2634 => true, + 2638 => true, + 2639 => true, + 2640 => true, + 2642 => true, + 2643 => true, + 2644 => true, + 2645 => true, + 2646 => true, + 2647 => true, + 2648 => true, + 2653 => true, + 2655 => true, + 2656 => true, + 2657 => true, + 2658 => true, + 2659 => true, + 2660 => true, + 2661 => true, + 2679 => true, + 2680 => true, + 2681 => true, + 2682 => true, + 2683 => true, + 2684 => true, + 2685 => true, + 2686 => true, + 2687 => true, + 2688 => true, + 2692 => true, + 2702 => true, + 2706 => true, + 2729 => true, + 2737 => true, + 2740 => true, + 2746 => true, + 2747 => true, + 2758 => true, + 2762 => true, + 2766 => true, + 2767 => true, + 2769 => true, + 2770 => true, + 2771 => true, + 2772 => true, + 2773 => true, + 2774 => true, + 2775 => true, + 2776 => true, + 2777 => true, + 2778 => true, + 2779 => true, + 2780 => true, + 2781 => true, + 2782 => true, + 2783 => true, + 2788 => true, + 2789 => true, + 2802 => true, + 2803 => true, + 2804 => true, + 2805 => true, + 2806 => true, + 2807 => true, + 2808 => true, + 2816 => true, + 2820 => true, + 2829 => true, + 2830 => true, + 2833 => true, + 2834 => true, + 2857 => true, + 2865 => true, + 2868 => true, + 2874 => true, + 2875 => true, + 2885 => true, + 2886 => true, + 2889 => true, + 2890 => true, + 2894 => true, + 2895 => true, + 2896 => true, + 2897 => true, + 2898 => true, + 2899 => true, + 2900 => true, + 2904 => true, + 2905 => true, + 2906 => true, + 2907 => true, + 2910 => true, + 2916 => true, + 2917 => true, + 2936 => true, + 2937 => true, + 2938 => true, + 2939 => true, + 2940 => true, + 2941 => true, + 2942 => true, + 2943 => true, + 2944 => true, + 2945 => true, + 2948 => true, + 2955 => true, + 2956 => true, + 2957 => true, + 2961 => true, + 2966 => true, + 2967 => true, + 2968 => true, + 2971 => true, + 2973 => true, + 2976 => true, + 2977 => true, + 2978 => true, + 2981 => true, + 2982 => true, + 2983 => true, + 2987 => true, + 2988 => true, + 2989 => true, + 3002 => true, + 3003 => true, + 3004 => true, + 3005 => true, + 3011 => true, + 3012 => true, + 3013 => true, + 3017 => true, + 3022 => true, + 3023 => true, + 3025 => true, + 3026 => true, + 3027 => true, + 3028 => true, + 3029 => true, + 3030 => true, + 3032 => true, + 3033 => true, + 3034 => true, + 3035 => true, + 3036 => true, + 3037 => true, + 3038 => true, + 3039 => true, + 3040 => true, + 3041 => true, + 3042 => true, + 3043 => true, + 3044 => true, + 3045 => true, + 3067 => true, + 3068 => true, + 3069 => true, + 3070 => true, + 3071 => true, + 3085 => true, + 3089 => true, + 3113 => true, + 3130 => true, + 3131 => true, + 3132 => true, + 3141 => true, + 3145 => true, + 3150 => true, + 3151 => true, + 3152 => true, + 3153 => true, + 3154 => true, + 3155 => true, + 3156 => true, + 3159 => true, + 3163 => true, + 3164 => true, + 3165 => true, + 3166 => true, + 3167 => true, + 3172 => true, + 3173 => true, + 3184 => true, + 3185 => true, + 3186 => true, + 3187 => true, + 3188 => true, + 3189 => true, + 3190 => true, + 3213 => true, + 3217 => true, + 3241 => true, + 3252 => true, + 3258 => true, + 3259 => true, + 3269 => true, + 3273 => true, + 3278 => true, + 3279 => true, + 3280 => true, + 3281 => true, + 3282 => true, + 3283 => true, + 3284 => true, + 3287 => true, + 3288 => true, + 3289 => true, + 3290 => true, + 3291 => true, + 3292 => true, + 3293 => true, + 3295 => true, + 3300 => true, + 3301 => true, + 3312 => true, + 3315 => true, + 3316 => true, + 3317 => true, + 3318 => true, + 3319 => true, + 3320 => true, + 3321 => true, + 3322 => true, + 3323 => true, + 3324 => true, + 3325 => true, + 3326 => true, + 3327 => true, + 3341 => true, + 3345 => true, + 3397 => true, + 3401 => true, + 3408 => true, + 3409 => true, + 3410 => true, + 3411 => true, + 3428 => true, + 3429 => true, + 3456 => true, + 3460 => true, + 3479 => true, + 3480 => true, + 3481 => true, + 3506 => true, + 3516 => true, + 3518 => true, + 3519 => true, + 3527 => true, + 3528 => true, + 3529 => true, + 3531 => true, + 3532 => true, + 3533 => true, + 3534 => true, + 3541 => true, + 3543 => true, + 3552 => true, + 3553 => true, + 3554 => true, + 3555 => true, + 3556 => true, + 3557 => true, + 3568 => true, + 3569 => true, + 3573 => true, + 3574 => true, + 3575 => true, + 3576 => true, + 3577 => true, + 3578 => true, + 3579 => true, + 3580 => true, + 3581 => true, + 3582 => true, + 3583 => true, + 3584 => true, + 3643 => true, + 3644 => true, + 3645 => true, + 3646 => true, + 3715 => true, + 3717 => true, + 3723 => true, + 3748 => true, + 3750 => true, + 3774 => true, + 3775 => true, + 3781 => true, + 3783 => true, + 3790 => true, + 3791 => true, + 3802 => true, + 3803 => true, + 3912 => true, + 3949 => true, + 3950 => true, + 3951 => true, + 3952 => true, + 3992 => true, + 4029 => true, + 4045 => true, + 4294 => true, + 4296 => true, + 4297 => true, + 4298 => true, + 4299 => true, + 4300 => true, + 4302 => true, + 4303 => true, + 4447 => true, + 4448 => true, + 4681 => true, + 4686 => true, + 4687 => true, + 4695 => true, + 4697 => true, + 4702 => true, + 4703 => true, + 4745 => true, + 4750 => true, + 4751 => true, + 4785 => true, + 4790 => true, + 4791 => true, + 4799 => true, + 4801 => true, + 4806 => true, + 4807 => true, + 4823 => true, + 4881 => true, + 4886 => true, + 4887 => true, + 4955 => true, + 4956 => true, + 4989 => true, + 4990 => true, + 4991 => true, + 5018 => true, + 5019 => true, + 5020 => true, + 5021 => true, + 5022 => true, + 5023 => true, + 5110 => true, + 5111 => true, + 5118 => true, + 5119 => true, + 5760 => true, + 5789 => true, + 5790 => true, + 5791 => true, + 5881 => true, + 5882 => true, + 5883 => true, + 5884 => true, + 5885 => true, + 5886 => true, + 5887 => true, + 5901 => true, + 5909 => true, + 5910 => true, + 5911 => true, + 5912 => true, + 5913 => true, + 5914 => true, + 5915 => true, + 5916 => true, + 5917 => true, + 5918 => true, + 5919 => true, + 5943 => true, + 5944 => true, + 5945 => true, + 5946 => true, + 5947 => true, + 5948 => true, + 5949 => true, + 5950 => true, + 5951 => true, + 5972 => true, + 5973 => true, + 5974 => true, + 5975 => true, + 5976 => true, + 5977 => true, + 5978 => true, + 5979 => true, + 5980 => true, + 5981 => true, + 5982 => true, + 5983 => true, + 5997 => true, + 6001 => true, + 6004 => true, + 6005 => true, + 6006 => true, + 6007 => true, + 6008 => true, + 6009 => true, + 6010 => true, + 6011 => true, + 6012 => true, + 6013 => true, + 6014 => true, + 6015 => true, + 6068 => true, + 6069 => true, + 6110 => true, + 6111 => true, + 6122 => true, + 6123 => true, + 6124 => true, + 6125 => true, + 6126 => true, + 6127 => true, + 6138 => true, + 6139 => true, + 6140 => true, + 6141 => true, + 6142 => true, + 6143 => true, + 6150 => true, + 6158 => true, + 6159 => true, + 6170 => true, + 6171 => true, + 6172 => true, + 6173 => true, + 6174 => true, + 6175 => true, + 6265 => true, + 6266 => true, + 6267 => true, + 6268 => true, + 6269 => true, + 6270 => true, + 6271 => true, + 6315 => true, + 6316 => true, + 6317 => true, + 6318 => true, + 6319 => true, + 6390 => true, + 6391 => true, + 6392 => true, + 6393 => true, + 6394 => true, + 6395 => true, + 6396 => true, + 6397 => true, + 6398 => true, + 6399 => true, + 6431 => true, + 6444 => true, + 6445 => true, + 6446 => true, + 6447 => true, + 6460 => true, + 6461 => true, + 6462 => true, + 6463 => true, + 6465 => true, + 6466 => true, + 6467 => true, + 6510 => true, + 6511 => true, + 6517 => true, + 6518 => true, + 6519 => true, + 6520 => true, + 6521 => true, + 6522 => true, + 6523 => true, + 6524 => true, + 6525 => true, + 6526 => true, + 6527 => true, + 6572 => true, + 6573 => true, + 6574 => true, + 6575 => true, + 6602 => true, + 6603 => true, + 6604 => true, + 6605 => true, + 6606 => true, + 6607 => true, + 6619 => true, + 6620 => true, + 6621 => true, + 6684 => true, + 6685 => true, + 6751 => true, + 6781 => true, + 6782 => true, + 6794 => true, + 6795 => true, + 6796 => true, + 6797 => true, + 6798 => true, + 6799 => true, + 6810 => true, + 6811 => true, + 6812 => true, + 6813 => true, + 6814 => true, + 6815 => true, + 6830 => true, + 6831 => true, + 6988 => true, + 6989 => true, + 6990 => true, + 6991 => true, + 7037 => true, + 7038 => true, + 7039 => true, + 7156 => true, + 7157 => true, + 7158 => true, + 7159 => true, + 7160 => true, + 7161 => true, + 7162 => true, + 7163 => true, + 7224 => true, + 7225 => true, + 7226 => true, + 7242 => true, + 7243 => true, + 7244 => true, + 7305 => true, + 7306 => true, + 7307 => true, + 7308 => true, + 7309 => true, + 7310 => true, + 7311 => true, + 7355 => true, + 7356 => true, + 7368 => true, + 7369 => true, + 7370 => true, + 7371 => true, + 7372 => true, + 7373 => true, + 7374 => true, + 7375 => true, + 7419 => true, + 7420 => true, + 7421 => true, + 7422 => true, + 7423 => true, + 7674 => true, + 7958 => true, + 7959 => true, + 7966 => true, + 7967 => true, + 8006 => true, + 8007 => true, + 8014 => true, + 8015 => true, + 8024 => true, + 8026 => true, + 8028 => true, + 8030 => true, + 8062 => true, + 8063 => true, + 8117 => true, + 8133 => true, + 8148 => true, + 8149 => true, + 8156 => true, + 8176 => true, + 8177 => true, + 8181 => true, + 8191 => true, + 8206 => true, + 8207 => true, + 8228 => true, + 8229 => true, + 8230 => true, + 8232 => true, + 8233 => true, + 8234 => true, + 8235 => true, + 8236 => true, + 8237 => true, + 8238 => true, + 8289 => true, + 8290 => true, + 8291 => true, + 8293 => true, + 8294 => true, + 8295 => true, + 8296 => true, + 8297 => true, + 8298 => true, + 8299 => true, + 8300 => true, + 8301 => true, + 8302 => true, + 8303 => true, + 8306 => true, + 8307 => true, + 8335 => true, + 8349 => true, + 8350 => true, + 8351 => true, + 8384 => true, + 8385 => true, + 8386 => true, + 8387 => true, + 8388 => true, + 8389 => true, + 8390 => true, + 8391 => true, + 8392 => true, + 8393 => true, + 8394 => true, + 8395 => true, + 8396 => true, + 8397 => true, + 8398 => true, + 8399 => true, + 8433 => true, + 8434 => true, + 8435 => true, + 8436 => true, + 8437 => true, + 8438 => true, + 8439 => true, + 8440 => true, + 8441 => true, + 8442 => true, + 8443 => true, + 8444 => true, + 8445 => true, + 8446 => true, + 8447 => true, + 8498 => true, + 8579 => true, + 8588 => true, + 8589 => true, + 8590 => true, + 8591 => true, + 9255 => true, + 9256 => true, + 9257 => true, + 9258 => true, + 9259 => true, + 9260 => true, + 9261 => true, + 9262 => true, + 9263 => true, + 9264 => true, + 9265 => true, + 9266 => true, + 9267 => true, + 9268 => true, + 9269 => true, + 9270 => true, + 9271 => true, + 9272 => true, + 9273 => true, + 9274 => true, + 9275 => true, + 9276 => true, + 9277 => true, + 9278 => true, + 9279 => true, + 9291 => true, + 9292 => true, + 9293 => true, + 9294 => true, + 9295 => true, + 9296 => true, + 9297 => true, + 9298 => true, + 9299 => true, + 9300 => true, + 9301 => true, + 9302 => true, + 9303 => true, + 9304 => true, + 9305 => true, + 9306 => true, + 9307 => true, + 9308 => true, + 9309 => true, + 9310 => true, + 9311 => true, + 9352 => true, + 9353 => true, + 9354 => true, + 9355 => true, + 9356 => true, + 9357 => true, + 9358 => true, + 9359 => true, + 9360 => true, + 9361 => true, + 9362 => true, + 9363 => true, + 9364 => true, + 9365 => true, + 9366 => true, + 9367 => true, + 9368 => true, + 9369 => true, + 9370 => true, + 9371 => true, + 11124 => true, + 11125 => true, + 11158 => true, + 11311 => true, + 11359 => true, + 11508 => true, + 11509 => true, + 11510 => true, + 11511 => true, + 11512 => true, + 11558 => true, + 11560 => true, + 11561 => true, + 11562 => true, + 11563 => true, + 11564 => true, + 11566 => true, + 11567 => true, + 11624 => true, + 11625 => true, + 11626 => true, + 11627 => true, + 11628 => true, + 11629 => true, + 11630 => true, + 11633 => true, + 11634 => true, + 11635 => true, + 11636 => true, + 11637 => true, + 11638 => true, + 11639 => true, + 11640 => true, + 11641 => true, + 11642 => true, + 11643 => true, + 11644 => true, + 11645 => true, + 11646 => true, + 11671 => true, + 11672 => true, + 11673 => true, + 11674 => true, + 11675 => true, + 11676 => true, + 11677 => true, + 11678 => true, + 11679 => true, + 11687 => true, + 11695 => true, + 11703 => true, + 11711 => true, + 11719 => true, + 11727 => true, + 11735 => true, + 11743 => true, + 11930 => true, + 12020 => true, + 12021 => true, + 12022 => true, + 12023 => true, + 12024 => true, + 12025 => true, + 12026 => true, + 12027 => true, + 12028 => true, + 12029 => true, + 12030 => true, + 12031 => true, + 12246 => true, + 12247 => true, + 12248 => true, + 12249 => true, + 12250 => true, + 12251 => true, + 12252 => true, + 12253 => true, + 12254 => true, + 12255 => true, + 12256 => true, + 12257 => true, + 12258 => true, + 12259 => true, + 12260 => true, + 12261 => true, + 12262 => true, + 12263 => true, + 12264 => true, + 12265 => true, + 12266 => true, + 12267 => true, + 12268 => true, + 12269 => true, + 12270 => true, + 12271 => true, + 12272 => true, + 12273 => true, + 12274 => true, + 12275 => true, + 12276 => true, + 12277 => true, + 12278 => true, + 12279 => true, + 12280 => true, + 12281 => true, + 12282 => true, + 12283 => true, + 12284 => true, + 12285 => true, + 12286 => true, + 12287 => true, + 12352 => true, + 12439 => true, + 12440 => true, + 12544 => true, + 12545 => true, + 12546 => true, + 12547 => true, + 12548 => true, + 12592 => true, + 12644 => true, + 12687 => true, + 12772 => true, + 12773 => true, + 12774 => true, + 12775 => true, + 12776 => true, + 12777 => true, + 12778 => true, + 12779 => true, + 12780 => true, + 12781 => true, + 12782 => true, + 12783 => true, + 12831 => true, + 13250 => true, + 13255 => true, + 13272 => true, + 40957 => true, + 40958 => true, + 40959 => true, + 42125 => true, + 42126 => true, + 42127 => true, + 42183 => true, + 42184 => true, + 42185 => true, + 42186 => true, + 42187 => true, + 42188 => true, + 42189 => true, + 42190 => true, + 42191 => true, + 42540 => true, + 42541 => true, + 42542 => true, + 42543 => true, + 42544 => true, + 42545 => true, + 42546 => true, + 42547 => true, + 42548 => true, + 42549 => true, + 42550 => true, + 42551 => true, + 42552 => true, + 42553 => true, + 42554 => true, + 42555 => true, + 42556 => true, + 42557 => true, + 42558 => true, + 42559 => true, + 42744 => true, + 42745 => true, + 42746 => true, + 42747 => true, + 42748 => true, + 42749 => true, + 42750 => true, + 42751 => true, + 42944 => true, + 42945 => true, + 43053 => true, + 43054 => true, + 43055 => true, + 43066 => true, + 43067 => true, + 43068 => true, + 43069 => true, + 43070 => true, + 43071 => true, + 43128 => true, + 43129 => true, + 43130 => true, + 43131 => true, + 43132 => true, + 43133 => true, + 43134 => true, + 43135 => true, + 43206 => true, + 43207 => true, + 43208 => true, + 43209 => true, + 43210 => true, + 43211 => true, + 43212 => true, + 43213 => true, + 43226 => true, + 43227 => true, + 43228 => true, + 43229 => true, + 43230 => true, + 43231 => true, + 43348 => true, + 43349 => true, + 43350 => true, + 43351 => true, + 43352 => true, + 43353 => true, + 43354 => true, + 43355 => true, + 43356 => true, + 43357 => true, + 43358 => true, + 43389 => true, + 43390 => true, + 43391 => true, + 43470 => true, + 43482 => true, + 43483 => true, + 43484 => true, + 43485 => true, + 43519 => true, + 43575 => true, + 43576 => true, + 43577 => true, + 43578 => true, + 43579 => true, + 43580 => true, + 43581 => true, + 43582 => true, + 43583 => true, + 43598 => true, + 43599 => true, + 43610 => true, + 43611 => true, + 43715 => true, + 43716 => true, + 43717 => true, + 43718 => true, + 43719 => true, + 43720 => true, + 43721 => true, + 43722 => true, + 43723 => true, + 43724 => true, + 43725 => true, + 43726 => true, + 43727 => true, + 43728 => true, + 43729 => true, + 43730 => true, + 43731 => true, + 43732 => true, + 43733 => true, + 43734 => true, + 43735 => true, + 43736 => true, + 43737 => true, + 43738 => true, + 43767 => true, + 43768 => true, + 43769 => true, + 43770 => true, + 43771 => true, + 43772 => true, + 43773 => true, + 43774 => true, + 43775 => true, + 43776 => true, + 43783 => true, + 43784 => true, + 43791 => true, + 43792 => true, + 43799 => true, + 43800 => true, + 43801 => true, + 43802 => true, + 43803 => true, + 43804 => true, + 43805 => true, + 43806 => true, + 43807 => true, + 43815 => true, + 43823 => true, + 43884 => true, + 43885 => true, + 43886 => true, + 43887 => true, + 44014 => true, + 44015 => true, + 44026 => true, + 44027 => true, + 44028 => true, + 44029 => true, + 44030 => true, + 44031 => true, + 55204 => true, + 55205 => true, + 55206 => true, + 55207 => true, + 55208 => true, + 55209 => true, + 55210 => true, + 55211 => true, + 55212 => true, + 55213 => true, + 55214 => true, + 55215 => true, + 55239 => true, + 55240 => true, + 55241 => true, + 55242 => true, + 55292 => true, + 55293 => true, + 55294 => true, + 55295 => true, + 64110 => true, + 64111 => true, + 64263 => true, + 64264 => true, + 64265 => true, + 64266 => true, + 64267 => true, + 64268 => true, + 64269 => true, + 64270 => true, + 64271 => true, + 64272 => true, + 64273 => true, + 64274 => true, + 64280 => true, + 64281 => true, + 64282 => true, + 64283 => true, + 64284 => true, + 64311 => true, + 64317 => true, + 64319 => true, + 64322 => true, + 64325 => true, + 64450 => true, + 64451 => true, + 64452 => true, + 64453 => true, + 64454 => true, + 64455 => true, + 64456 => true, + 64457 => true, + 64458 => true, + 64459 => true, + 64460 => true, + 64461 => true, + 64462 => true, + 64463 => true, + 64464 => true, + 64465 => true, + 64466 => true, + 64832 => true, + 64833 => true, + 64834 => true, + 64835 => true, + 64836 => true, + 64837 => true, + 64838 => true, + 64839 => true, + 64840 => true, + 64841 => true, + 64842 => true, + 64843 => true, + 64844 => true, + 64845 => true, + 64846 => true, + 64847 => true, + 64912 => true, + 64913 => true, + 64968 => true, + 64969 => true, + 64970 => true, + 64971 => true, + 64972 => true, + 64973 => true, + 64974 => true, + 64975 => true, + 65022 => true, + 65023 => true, + 65042 => true, + 65049 => true, + 65050 => true, + 65051 => true, + 65052 => true, + 65053 => true, + 65054 => true, + 65055 => true, + 65072 => true, + 65106 => true, + 65107 => true, + 65127 => true, + 65132 => true, + 65133 => true, + 65134 => true, + 65135 => true, + 65141 => true, + 65277 => true, + 65278 => true, + 65280 => true, + 65440 => true, + 65471 => true, + 65472 => true, + 65473 => true, + 65480 => true, + 65481 => true, + 65488 => true, + 65489 => true, + 65496 => true, + 65497 => true, + 65501 => true, + 65502 => true, + 65503 => true, + 65511 => true, + 65519 => true, + 65520 => true, + 65521 => true, + 65522 => true, + 65523 => true, + 65524 => true, + 65525 => true, + 65526 => true, + 65527 => true, + 65528 => true, + 65529 => true, + 65530 => true, + 65531 => true, + 65532 => true, + 65533 => true, + 65534 => true, + 65535 => true, + 65548 => true, + 65575 => true, + 65595 => true, + 65598 => true, + 65614 => true, + 65615 => true, + 65787 => true, + 65788 => true, + 65789 => true, + 65790 => true, + 65791 => true, + 65795 => true, + 65796 => true, + 65797 => true, + 65798 => true, + 65844 => true, + 65845 => true, + 65846 => true, + 65935 => true, + 65949 => true, + 65950 => true, + 65951 => true, + 66205 => true, + 66206 => true, + 66207 => true, + 66257 => true, + 66258 => true, + 66259 => true, + 66260 => true, + 66261 => true, + 66262 => true, + 66263 => true, + 66264 => true, + 66265 => true, + 66266 => true, + 66267 => true, + 66268 => true, + 66269 => true, + 66270 => true, + 66271 => true, + 66300 => true, + 66301 => true, + 66302 => true, + 66303 => true, + 66340 => true, + 66341 => true, + 66342 => true, + 66343 => true, + 66344 => true, + 66345 => true, + 66346 => true, + 66347 => true, + 66348 => true, + 66379 => true, + 66380 => true, + 66381 => true, + 66382 => true, + 66383 => true, + 66427 => true, + 66428 => true, + 66429 => true, + 66430 => true, + 66431 => true, + 66462 => true, + 66500 => true, + 66501 => true, + 66502 => true, + 66503 => true, + 66718 => true, + 66719 => true, + 66730 => true, + 66731 => true, + 66732 => true, + 66733 => true, + 66734 => true, + 66735 => true, + 66772 => true, + 66773 => true, + 66774 => true, + 66775 => true, + 66812 => true, + 66813 => true, + 66814 => true, + 66815 => true, + 66856 => true, + 66857 => true, + 66858 => true, + 66859 => true, + 66860 => true, + 66861 => true, + 66862 => true, + 66863 => true, + 66916 => true, + 66917 => true, + 66918 => true, + 66919 => true, + 66920 => true, + 66921 => true, + 66922 => true, + 66923 => true, + 66924 => true, + 66925 => true, + 66926 => true, + 67383 => true, + 67384 => true, + 67385 => true, + 67386 => true, + 67387 => true, + 67388 => true, + 67389 => true, + 67390 => true, + 67391 => true, + 67414 => true, + 67415 => true, + 67416 => true, + 67417 => true, + 67418 => true, + 67419 => true, + 67420 => true, + 67421 => true, + 67422 => true, + 67423 => true, + 67590 => true, + 67591 => true, + 67593 => true, + 67638 => true, + 67641 => true, + 67642 => true, + 67643 => true, + 67645 => true, + 67646 => true, + 67670 => true, + 67743 => true, + 67744 => true, + 67745 => true, + 67746 => true, + 67747 => true, + 67748 => true, + 67749 => true, + 67750 => true, + 67827 => true, + 67830 => true, + 67831 => true, + 67832 => true, + 67833 => true, + 67834 => true, + 67868 => true, + 67869 => true, + 67870 => true, + 67898 => true, + 67899 => true, + 67900 => true, + 67901 => true, + 67902 => true, + 68024 => true, + 68025 => true, + 68026 => true, + 68027 => true, + 68048 => true, + 68049 => true, + 68100 => true, + 68103 => true, + 68104 => true, + 68105 => true, + 68106 => true, + 68107 => true, + 68116 => true, + 68120 => true, + 68150 => true, + 68151 => true, + 68155 => true, + 68156 => true, + 68157 => true, + 68158 => true, + 68169 => true, + 68170 => true, + 68171 => true, + 68172 => true, + 68173 => true, + 68174 => true, + 68175 => true, + 68185 => true, + 68186 => true, + 68187 => true, + 68188 => true, + 68189 => true, + 68190 => true, + 68191 => true, + 68327 => true, + 68328 => true, + 68329 => true, + 68330 => true, + 68343 => true, + 68344 => true, + 68345 => true, + 68346 => true, + 68347 => true, + 68348 => true, + 68349 => true, + 68350 => true, + 68351 => true, + 68406 => true, + 68407 => true, + 68408 => true, + 68438 => true, + 68439 => true, + 68467 => true, + 68468 => true, + 68469 => true, + 68470 => true, + 68471 => true, + 68498 => true, + 68499 => true, + 68500 => true, + 68501 => true, + 68502 => true, + 68503 => true, + 68504 => true, + 68509 => true, + 68510 => true, + 68511 => true, + 68512 => true, + 68513 => true, + 68514 => true, + 68515 => true, + 68516 => true, + 68517 => true, + 68518 => true, + 68519 => true, + 68520 => true, + 68787 => true, + 68788 => true, + 68789 => true, + 68790 => true, + 68791 => true, + 68792 => true, + 68793 => true, + 68794 => true, + 68795 => true, + 68796 => true, + 68797 => true, + 68798 => true, + 68799 => true, + 68851 => true, + 68852 => true, + 68853 => true, + 68854 => true, + 68855 => true, + 68856 => true, + 68857 => true, + 68904 => true, + 68905 => true, + 68906 => true, + 68907 => true, + 68908 => true, + 68909 => true, + 68910 => true, + 68911 => true, + 69247 => true, + 69290 => true, + 69294 => true, + 69295 => true, + 69416 => true, + 69417 => true, + 69418 => true, + 69419 => true, + 69420 => true, + 69421 => true, + 69422 => true, + 69423 => true, + 69580 => true, + 69581 => true, + 69582 => true, + 69583 => true, + 69584 => true, + 69585 => true, + 69586 => true, + 69587 => true, + 69588 => true, + 69589 => true, + 69590 => true, + 69591 => true, + 69592 => true, + 69593 => true, + 69594 => true, + 69595 => true, + 69596 => true, + 69597 => true, + 69598 => true, + 69599 => true, + 69623 => true, + 69624 => true, + 69625 => true, + 69626 => true, + 69627 => true, + 69628 => true, + 69629 => true, + 69630 => true, + 69631 => true, + 69710 => true, + 69711 => true, + 69712 => true, + 69713 => true, + 69744 => true, + 69745 => true, + 69746 => true, + 69747 => true, + 69748 => true, + 69749 => true, + 69750 => true, + 69751 => true, + 69752 => true, + 69753 => true, + 69754 => true, + 69755 => true, + 69756 => true, + 69757 => true, + 69758 => true, + 69821 => true, + 69826 => true, + 69827 => true, + 69828 => true, + 69829 => true, + 69830 => true, + 69831 => true, + 69832 => true, + 69833 => true, + 69834 => true, + 69835 => true, + 69836 => true, + 69837 => true, + 69838 => true, + 69839 => true, + 69865 => true, + 69866 => true, + 69867 => true, + 69868 => true, + 69869 => true, + 69870 => true, + 69871 => true, + 69882 => true, + 69883 => true, + 69884 => true, + 69885 => true, + 69886 => true, + 69887 => true, + 69941 => true, + 69960 => true, + 69961 => true, + 69962 => true, + 69963 => true, + 69964 => true, + 69965 => true, + 69966 => true, + 69967 => true, + 70007 => true, + 70008 => true, + 70009 => true, + 70010 => true, + 70011 => true, + 70012 => true, + 70013 => true, + 70014 => true, + 70015 => true, + 70112 => true, + 70133 => true, + 70134 => true, + 70135 => true, + 70136 => true, + 70137 => true, + 70138 => true, + 70139 => true, + 70140 => true, + 70141 => true, + 70142 => true, + 70143 => true, + 70162 => true, + 70279 => true, + 70281 => true, + 70286 => true, + 70302 => true, + 70314 => true, + 70315 => true, + 70316 => true, + 70317 => true, + 70318 => true, + 70319 => true, + 70379 => true, + 70380 => true, + 70381 => true, + 70382 => true, + 70383 => true, + 70394 => true, + 70395 => true, + 70396 => true, + 70397 => true, + 70398 => true, + 70399 => true, + 70404 => true, + 70413 => true, + 70414 => true, + 70417 => true, + 70418 => true, + 70441 => true, + 70449 => true, + 70452 => true, + 70458 => true, + 70469 => true, + 70470 => true, + 70473 => true, + 70474 => true, + 70478 => true, + 70479 => true, + 70481 => true, + 70482 => true, + 70483 => true, + 70484 => true, + 70485 => true, + 70486 => true, + 70488 => true, + 70489 => true, + 70490 => true, + 70491 => true, + 70492 => true, + 70500 => true, + 70501 => true, + 70509 => true, + 70510 => true, + 70511 => true, + 70748 => true, + 70754 => true, + 70755 => true, + 70756 => true, + 70757 => true, + 70758 => true, + 70759 => true, + 70760 => true, + 70761 => true, + 70762 => true, + 70763 => true, + 70764 => true, + 70765 => true, + 70766 => true, + 70767 => true, + 70768 => true, + 70769 => true, + 70770 => true, + 70771 => true, + 70772 => true, + 70773 => true, + 70774 => true, + 70775 => true, + 70776 => true, + 70777 => true, + 70778 => true, + 70779 => true, + 70780 => true, + 70781 => true, + 70782 => true, + 70783 => true, + 70856 => true, + 70857 => true, + 70858 => true, + 70859 => true, + 70860 => true, + 70861 => true, + 70862 => true, + 70863 => true, + 71094 => true, + 71095 => true, + 71237 => true, + 71238 => true, + 71239 => true, + 71240 => true, + 71241 => true, + 71242 => true, + 71243 => true, + 71244 => true, + 71245 => true, + 71246 => true, + 71247 => true, + 71258 => true, + 71259 => true, + 71260 => true, + 71261 => true, + 71262 => true, + 71263 => true, + 71277 => true, + 71278 => true, + 71279 => true, + 71280 => true, + 71281 => true, + 71282 => true, + 71283 => true, + 71284 => true, + 71285 => true, + 71286 => true, + 71287 => true, + 71288 => true, + 71289 => true, + 71290 => true, + 71291 => true, + 71292 => true, + 71293 => true, + 71294 => true, + 71295 => true, + 71353 => true, + 71354 => true, + 71355 => true, + 71356 => true, + 71357 => true, + 71358 => true, + 71359 => true, + 71451 => true, + 71452 => true, + 71468 => true, + 71469 => true, + 71470 => true, + 71471 => true, + 71923 => true, + 71924 => true, + 71925 => true, + 71926 => true, + 71927 => true, + 71928 => true, + 71929 => true, + 71930 => true, + 71931 => true, + 71932 => true, + 71933 => true, + 71934 => true, + 71943 => true, + 71944 => true, + 71946 => true, + 71947 => true, + 71956 => true, + 71959 => true, + 71990 => true, + 71993 => true, + 71994 => true, + 72007 => true, + 72008 => true, + 72009 => true, + 72010 => true, + 72011 => true, + 72012 => true, + 72013 => true, + 72014 => true, + 72015 => true, + 72104 => true, + 72105 => true, + 72152 => true, + 72153 => true, + 72165 => true, + 72166 => true, + 72167 => true, + 72168 => true, + 72169 => true, + 72170 => true, + 72171 => true, + 72172 => true, + 72173 => true, + 72174 => true, + 72175 => true, + 72176 => true, + 72177 => true, + 72178 => true, + 72179 => true, + 72180 => true, + 72181 => true, + 72182 => true, + 72183 => true, + 72184 => true, + 72185 => true, + 72186 => true, + 72187 => true, + 72188 => true, + 72189 => true, + 72190 => true, + 72191 => true, + 72264 => true, + 72265 => true, + 72266 => true, + 72267 => true, + 72268 => true, + 72269 => true, + 72270 => true, + 72271 => true, + 72355 => true, + 72356 => true, + 72357 => true, + 72358 => true, + 72359 => true, + 72360 => true, + 72361 => true, + 72362 => true, + 72363 => true, + 72364 => true, + 72365 => true, + 72366 => true, + 72367 => true, + 72368 => true, + 72369 => true, + 72370 => true, + 72371 => true, + 72372 => true, + 72373 => true, + 72374 => true, + 72375 => true, + 72376 => true, + 72377 => true, + 72378 => true, + 72379 => true, + 72380 => true, + 72381 => true, + 72382 => true, + 72383 => true, + 72713 => true, + 72759 => true, + 72774 => true, + 72775 => true, + 72776 => true, + 72777 => true, + 72778 => true, + 72779 => true, + 72780 => true, + 72781 => true, + 72782 => true, + 72783 => true, + 72813 => true, + 72814 => true, + 72815 => true, + 72848 => true, + 72849 => true, + 72872 => true, + 72967 => true, + 72970 => true, + 73015 => true, + 73016 => true, + 73017 => true, + 73019 => true, + 73022 => true, + 73032 => true, + 73033 => true, + 73034 => true, + 73035 => true, + 73036 => true, + 73037 => true, + 73038 => true, + 73039 => true, + 73050 => true, + 73051 => true, + 73052 => true, + 73053 => true, + 73054 => true, + 73055 => true, + 73062 => true, + 73065 => true, + 73103 => true, + 73106 => true, + 73113 => true, + 73114 => true, + 73115 => true, + 73116 => true, + 73117 => true, + 73118 => true, + 73119 => true, + 73649 => true, + 73650 => true, + 73651 => true, + 73652 => true, + 73653 => true, + 73654 => true, + 73655 => true, + 73656 => true, + 73657 => true, + 73658 => true, + 73659 => true, + 73660 => true, + 73661 => true, + 73662 => true, + 73663 => true, + 73714 => true, + 73715 => true, + 73716 => true, + 73717 => true, + 73718 => true, + 73719 => true, + 73720 => true, + 73721 => true, + 73722 => true, + 73723 => true, + 73724 => true, + 73725 => true, + 73726 => true, + 74863 => true, + 74869 => true, + 74870 => true, + 74871 => true, + 74872 => true, + 74873 => true, + 74874 => true, + 74875 => true, + 74876 => true, + 74877 => true, + 74878 => true, + 74879 => true, + 78895 => true, + 78896 => true, + 78897 => true, + 78898 => true, + 78899 => true, + 78900 => true, + 78901 => true, + 78902 => true, + 78903 => true, + 78904 => true, + 92729 => true, + 92730 => true, + 92731 => true, + 92732 => true, + 92733 => true, + 92734 => true, + 92735 => true, + 92767 => true, + 92778 => true, + 92779 => true, + 92780 => true, + 92781 => true, + 92910 => true, + 92911 => true, + 92918 => true, + 92919 => true, + 92920 => true, + 92921 => true, + 92922 => true, + 92923 => true, + 92924 => true, + 92925 => true, + 92926 => true, + 92927 => true, + 92998 => true, + 92999 => true, + 93000 => true, + 93001 => true, + 93002 => true, + 93003 => true, + 93004 => true, + 93005 => true, + 93006 => true, + 93007 => true, + 93018 => true, + 93026 => true, + 93048 => true, + 93049 => true, + 93050 => true, + 93051 => true, + 93052 => true, + 94027 => true, + 94028 => true, + 94029 => true, + 94030 => true, + 94088 => true, + 94089 => true, + 94090 => true, + 94091 => true, + 94092 => true, + 94093 => true, + 94094 => true, + 94181 => true, + 94182 => true, + 94183 => true, + 94184 => true, + 94185 => true, + 94186 => true, + 94187 => true, + 94188 => true, + 94189 => true, + 94190 => true, + 94191 => true, + 94194 => true, + 94195 => true, + 94196 => true, + 94197 => true, + 94198 => true, + 94199 => true, + 94200 => true, + 94201 => true, + 94202 => true, + 94203 => true, + 94204 => true, + 94205 => true, + 94206 => true, + 94207 => true, + 100344 => true, + 100345 => true, + 100346 => true, + 100347 => true, + 100348 => true, + 100349 => true, + 100350 => true, + 100351 => true, + 110931 => true, + 110932 => true, + 110933 => true, + 110934 => true, + 110935 => true, + 110936 => true, + 110937 => true, + 110938 => true, + 110939 => true, + 110940 => true, + 110941 => true, + 110942 => true, + 110943 => true, + 110944 => true, + 110945 => true, + 110946 => true, + 110947 => true, + 110952 => true, + 110953 => true, + 110954 => true, + 110955 => true, + 110956 => true, + 110957 => true, + 110958 => true, + 110959 => true, + 113771 => true, + 113772 => true, + 113773 => true, + 113774 => true, + 113775 => true, + 113789 => true, + 113790 => true, + 113791 => true, + 113801 => true, + 113802 => true, + 113803 => true, + 113804 => true, + 113805 => true, + 113806 => true, + 113807 => true, + 113818 => true, + 113819 => true, + 119030 => true, + 119031 => true, + 119032 => true, + 119033 => true, + 119034 => true, + 119035 => true, + 119036 => true, + 119037 => true, + 119038 => true, + 119039 => true, + 119079 => true, + 119080 => true, + 119155 => true, + 119156 => true, + 119157 => true, + 119158 => true, + 119159 => true, + 119160 => true, + 119161 => true, + 119162 => true, + 119273 => true, + 119274 => true, + 119275 => true, + 119276 => true, + 119277 => true, + 119278 => true, + 119279 => true, + 119280 => true, + 119281 => true, + 119282 => true, + 119283 => true, + 119284 => true, + 119285 => true, + 119286 => true, + 119287 => true, + 119288 => true, + 119289 => true, + 119290 => true, + 119291 => true, + 119292 => true, + 119293 => true, + 119294 => true, + 119295 => true, + 119540 => true, + 119541 => true, + 119542 => true, + 119543 => true, + 119544 => true, + 119545 => true, + 119546 => true, + 119547 => true, + 119548 => true, + 119549 => true, + 119550 => true, + 119551 => true, + 119639 => true, + 119640 => true, + 119641 => true, + 119642 => true, + 119643 => true, + 119644 => true, + 119645 => true, + 119646 => true, + 119647 => true, + 119893 => true, + 119965 => true, + 119968 => true, + 119969 => true, + 119971 => true, + 119972 => true, + 119975 => true, + 119976 => true, + 119981 => true, + 119994 => true, + 119996 => true, + 120004 => true, + 120070 => true, + 120075 => true, + 120076 => true, + 120085 => true, + 120093 => true, + 120122 => true, + 120127 => true, + 120133 => true, + 120135 => true, + 120136 => true, + 120137 => true, + 120145 => true, + 120486 => true, + 120487 => true, + 120780 => true, + 120781 => true, + 121484 => true, + 121485 => true, + 121486 => true, + 121487 => true, + 121488 => true, + 121489 => true, + 121490 => true, + 121491 => true, + 121492 => true, + 121493 => true, + 121494 => true, + 121495 => true, + 121496 => true, + 121497 => true, + 121498 => true, + 121504 => true, + 122887 => true, + 122905 => true, + 122906 => true, + 122914 => true, + 122917 => true, + 123181 => true, + 123182 => true, + 123183 => true, + 123198 => true, + 123199 => true, + 123210 => true, + 123211 => true, + 123212 => true, + 123213 => true, + 123642 => true, + 123643 => true, + 123644 => true, + 123645 => true, + 123646 => true, + 125125 => true, + 125126 => true, + 125260 => true, + 125261 => true, + 125262 => true, + 125263 => true, + 125274 => true, + 125275 => true, + 125276 => true, + 125277 => true, + 126468 => true, + 126496 => true, + 126499 => true, + 126501 => true, + 126502 => true, + 126504 => true, + 126515 => true, + 126520 => true, + 126522 => true, + 126524 => true, + 126525 => true, + 126526 => true, + 126527 => true, + 126528 => true, + 126529 => true, + 126531 => true, + 126532 => true, + 126533 => true, + 126534 => true, + 126536 => true, + 126538 => true, + 126540 => true, + 126544 => true, + 126547 => true, + 126549 => true, + 126550 => true, + 126552 => true, + 126554 => true, + 126556 => true, + 126558 => true, + 126560 => true, + 126563 => true, + 126565 => true, + 126566 => true, + 126571 => true, + 126579 => true, + 126584 => true, + 126589 => true, + 126591 => true, + 126602 => true, + 126620 => true, + 126621 => true, + 126622 => true, + 126623 => true, + 126624 => true, + 126628 => true, + 126634 => true, + 127020 => true, + 127021 => true, + 127022 => true, + 127023 => true, + 127124 => true, + 127125 => true, + 127126 => true, + 127127 => true, + 127128 => true, + 127129 => true, + 127130 => true, + 127131 => true, + 127132 => true, + 127133 => true, + 127134 => true, + 127135 => true, + 127151 => true, + 127152 => true, + 127168 => true, + 127184 => true, + 127222 => true, + 127223 => true, + 127224 => true, + 127225 => true, + 127226 => true, + 127227 => true, + 127228 => true, + 127229 => true, + 127230 => true, + 127231 => true, + 127232 => true, + 127491 => true, + 127492 => true, + 127493 => true, + 127494 => true, + 127495 => true, + 127496 => true, + 127497 => true, + 127498 => true, + 127499 => true, + 127500 => true, + 127501 => true, + 127502 => true, + 127503 => true, + 127548 => true, + 127549 => true, + 127550 => true, + 127551 => true, + 127561 => true, + 127562 => true, + 127563 => true, + 127564 => true, + 127565 => true, + 127566 => true, + 127567 => true, + 127570 => true, + 127571 => true, + 127572 => true, + 127573 => true, + 127574 => true, + 127575 => true, + 127576 => true, + 127577 => true, + 127578 => true, + 127579 => true, + 127580 => true, + 127581 => true, + 127582 => true, + 127583 => true, + 128728 => true, + 128729 => true, + 128730 => true, + 128731 => true, + 128732 => true, + 128733 => true, + 128734 => true, + 128735 => true, + 128749 => true, + 128750 => true, + 128751 => true, + 128765 => true, + 128766 => true, + 128767 => true, + 128884 => true, + 128885 => true, + 128886 => true, + 128887 => true, + 128888 => true, + 128889 => true, + 128890 => true, + 128891 => true, + 128892 => true, + 128893 => true, + 128894 => true, + 128895 => true, + 128985 => true, + 128986 => true, + 128987 => true, + 128988 => true, + 128989 => true, + 128990 => true, + 128991 => true, + 129004 => true, + 129005 => true, + 129006 => true, + 129007 => true, + 129008 => true, + 129009 => true, + 129010 => true, + 129011 => true, + 129012 => true, + 129013 => true, + 129014 => true, + 129015 => true, + 129016 => true, + 129017 => true, + 129018 => true, + 129019 => true, + 129020 => true, + 129021 => true, + 129022 => true, + 129023 => true, + 129036 => true, + 129037 => true, + 129038 => true, + 129039 => true, + 129096 => true, + 129097 => true, + 129098 => true, + 129099 => true, + 129100 => true, + 129101 => true, + 129102 => true, + 129103 => true, + 129114 => true, + 129115 => true, + 129116 => true, + 129117 => true, + 129118 => true, + 129119 => true, + 129160 => true, + 129161 => true, + 129162 => true, + 129163 => true, + 129164 => true, + 129165 => true, + 129166 => true, + 129167 => true, + 129198 => true, + 129199 => true, + 129401 => true, + 129484 => true, + 129620 => true, + 129621 => true, + 129622 => true, + 129623 => true, + 129624 => true, + 129625 => true, + 129626 => true, + 129627 => true, + 129628 => true, + 129629 => true, + 129630 => true, + 129631 => true, + 129646 => true, + 129647 => true, + 129653 => true, + 129654 => true, + 129655 => true, + 129659 => true, + 129660 => true, + 129661 => true, + 129662 => true, + 129663 => true, + 129671 => true, + 129672 => true, + 129673 => true, + 129674 => true, + 129675 => true, + 129676 => true, + 129677 => true, + 129678 => true, + 129679 => true, + 129705 => true, + 129706 => true, + 129707 => true, + 129708 => true, + 129709 => true, + 129710 => true, + 129711 => true, + 129719 => true, + 129720 => true, + 129721 => true, + 129722 => true, + 129723 => true, + 129724 => true, + 129725 => true, + 129726 => true, + 129727 => true, + 129731 => true, + 129732 => true, + 129733 => true, + 129734 => true, + 129735 => true, + 129736 => true, + 129737 => true, + 129738 => true, + 129739 => true, + 129740 => true, + 129741 => true, + 129742 => true, + 129743 => true, + 129939 => true, + 131070 => true, + 131071 => true, + 177973 => true, + 177974 => true, + 177975 => true, + 177976 => true, + 177977 => true, + 177978 => true, + 177979 => true, + 177980 => true, + 177981 => true, + 177982 => true, + 177983 => true, + 178206 => true, + 178207 => true, + 183970 => true, + 183971 => true, + 183972 => true, + 183973 => true, + 183974 => true, + 183975 => true, + 183976 => true, + 183977 => true, + 183978 => true, + 183979 => true, + 183980 => true, + 183981 => true, + 183982 => true, + 183983 => true, + 194664 => true, + 194676 => true, + 194847 => true, + 194911 => true, + 195007 => true, + 196606 => true, + 196607 => true, + 262142 => true, + 262143 => true, + 327678 => true, + 327679 => true, + 393214 => true, + 393215 => true, + 458750 => true, + 458751 => true, + 524286 => true, + 524287 => true, + 589822 => true, + 589823 => true, + 655358 => true, + 655359 => true, + 720894 => true, + 720895 => true, + 786430 => true, + 786431 => true, + 851966 => true, + 851967 => true, + 917502 => true, + 917503 => true, + 917504 => true, + 917505 => true, + 917506 => true, + 917507 => true, + 917508 => true, + 917509 => true, + 917510 => true, + 917511 => true, + 917512 => true, + 917513 => true, + 917514 => true, + 917515 => true, + 917516 => true, + 917517 => true, + 917518 => true, + 917519 => true, + 917520 => true, + 917521 => true, + 917522 => true, + 917523 => true, + 917524 => true, + 917525 => true, + 917526 => true, + 917527 => true, + 917528 => true, + 917529 => true, + 917530 => true, + 917531 => true, + 917532 => true, + 917533 => true, + 917534 => true, + 917535 => true, + 983038 => true, + 983039 => true, + 1048574 => true, + 1048575 => true, + 1114110 => true, + 1114111 => true, +); diff --git a/lib/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php b/lib/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php new file mode 100644 index 0000000000..54f21cc0cd --- /dev/null +++ b/lib/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php @@ -0,0 +1,308 @@ + ' ', + 168 => ' ̈', + 175 => ' ̄', + 180 => ' ́', + 184 => ' ̧', + 728 => ' ̆', + 729 => ' ̇', + 730 => ' ̊', + 731 => ' ̨', + 732 => ' ̃', + 733 => ' ̋', + 890 => ' ι', + 894 => ';', + 900 => ' ́', + 901 => ' ̈́', + 8125 => ' ̓', + 8127 => ' ̓', + 8128 => ' ͂', + 8129 => ' ̈͂', + 8141 => ' ̓̀', + 8142 => ' ̓́', + 8143 => ' ̓͂', + 8157 => ' ̔̀', + 8158 => ' ̔́', + 8159 => ' ̔͂', + 8173 => ' ̈̀', + 8174 => ' ̈́', + 8175 => '`', + 8189 => ' ́', + 8190 => ' ̔', + 8192 => ' ', + 8193 => ' ', + 8194 => ' ', + 8195 => ' ', + 8196 => ' ', + 8197 => ' ', + 8198 => ' ', + 8199 => ' ', + 8200 => ' ', + 8201 => ' ', + 8202 => ' ', + 8215 => ' ̳', + 8239 => ' ', + 8252 => '!!', + 8254 => ' ̅', + 8263 => '??', + 8264 => '?!', + 8265 => '!?', + 8287 => ' ', + 8314 => '+', + 8316 => '=', + 8317 => '(', + 8318 => ')', + 8330 => '+', + 8332 => '=', + 8333 => '(', + 8334 => ')', + 8448 => 'a/c', + 8449 => 'a/s', + 8453 => 'c/o', + 8454 => 'c/u', + 9332 => '(1)', + 9333 => '(2)', + 9334 => '(3)', + 9335 => '(4)', + 9336 => '(5)', + 9337 => '(6)', + 9338 => '(7)', + 9339 => '(8)', + 9340 => '(9)', + 9341 => '(10)', + 9342 => '(11)', + 9343 => '(12)', + 9344 => '(13)', + 9345 => '(14)', + 9346 => '(15)', + 9347 => '(16)', + 9348 => '(17)', + 9349 => '(18)', + 9350 => '(19)', + 9351 => '(20)', + 9372 => '(a)', + 9373 => '(b)', + 9374 => '(c)', + 9375 => '(d)', + 9376 => '(e)', + 9377 => '(f)', + 9378 => '(g)', + 9379 => '(h)', + 9380 => '(i)', + 9381 => '(j)', + 9382 => '(k)', + 9383 => '(l)', + 9384 => '(m)', + 9385 => '(n)', + 9386 => '(o)', + 9387 => '(p)', + 9388 => '(q)', + 9389 => '(r)', + 9390 => '(s)', + 9391 => '(t)', + 9392 => '(u)', + 9393 => '(v)', + 9394 => '(w)', + 9395 => '(x)', + 9396 => '(y)', + 9397 => '(z)', + 10868 => '::=', + 10869 => '==', + 10870 => '===', + 12288 => ' ', + 12443 => ' ゙', + 12444 => ' ゚', + 12800 => '(ᄀ)', + 12801 => '(ᄂ)', + 12802 => '(ᄃ)', + 12803 => '(ᄅ)', + 12804 => '(ᄆ)', + 12805 => '(ᄇ)', + 12806 => '(ᄉ)', + 12807 => '(ᄋ)', + 12808 => '(ᄌ)', + 12809 => '(ᄎ)', + 12810 => '(ᄏ)', + 12811 => '(ᄐ)', + 12812 => '(ᄑ)', + 12813 => '(ᄒ)', + 12814 => '(가)', + 12815 => '(나)', + 12816 => '(다)', + 12817 => '(라)', + 12818 => '(마)', + 12819 => '(바)', + 12820 => '(사)', + 12821 => '(아)', + 12822 => '(자)', + 12823 => '(차)', + 12824 => '(카)', + 12825 => '(타)', + 12826 => '(파)', + 12827 => '(하)', + 12828 => '(주)', + 12829 => '(오전)', + 12830 => '(오후)', + 12832 => '(一)', + 12833 => '(二)', + 12834 => '(三)', + 12835 => '(四)', + 12836 => '(五)', + 12837 => '(六)', + 12838 => '(七)', + 12839 => '(八)', + 12840 => '(九)', + 12841 => '(十)', + 12842 => '(月)', + 12843 => '(火)', + 12844 => '(水)', + 12845 => '(木)', + 12846 => '(金)', + 12847 => '(土)', + 12848 => '(日)', + 12849 => '(株)', + 12850 => '(有)', + 12851 => '(社)', + 12852 => '(名)', + 12853 => '(特)', + 12854 => '(財)', + 12855 => '(祝)', + 12856 => '(労)', + 12857 => '(代)', + 12858 => '(呼)', + 12859 => '(学)', + 12860 => '(監)', + 12861 => '(企)', + 12862 => '(資)', + 12863 => '(協)', + 12864 => '(祭)', + 12865 => '(休)', + 12866 => '(自)', + 12867 => '(至)', + 64297 => '+', + 64606 => ' ٌّ', + 64607 => ' ٍّ', + 64608 => ' َّ', + 64609 => ' ُّ', + 64610 => ' ِّ', + 64611 => ' ّٰ', + 65018 => 'صلى الله عليه وسلم', + 65019 => 'جل جلاله', + 65040 => ',', + 65043 => ':', + 65044 => ';', + 65045 => '!', + 65046 => '?', + 65075 => '_', + 65076 => '_', + 65077 => '(', + 65078 => ')', + 65079 => '{', + 65080 => '}', + 65095 => '[', + 65096 => ']', + 65097 => ' ̅', + 65098 => ' ̅', + 65099 => ' ̅', + 65100 => ' ̅', + 65101 => '_', + 65102 => '_', + 65103 => '_', + 65104 => ',', + 65108 => ';', + 65109 => ':', + 65110 => '?', + 65111 => '!', + 65113 => '(', + 65114 => ')', + 65115 => '{', + 65116 => '}', + 65119 => '#', + 65120 => '&', + 65121 => '*', + 65122 => '+', + 65124 => '<', + 65125 => '>', + 65126 => '=', + 65128 => '\\', + 65129 => '$', + 65130 => '%', + 65131 => '@', + 65136 => ' ً', + 65138 => ' ٌ', + 65140 => ' ٍ', + 65142 => ' َ', + 65144 => ' ُ', + 65146 => ' ِ', + 65148 => ' ّ', + 65150 => ' ْ', + 65281 => '!', + 65282 => '"', + 65283 => '#', + 65284 => '$', + 65285 => '%', + 65286 => '&', + 65287 => '\'', + 65288 => '(', + 65289 => ')', + 65290 => '*', + 65291 => '+', + 65292 => ',', + 65295 => '/', + 65306 => ':', + 65307 => ';', + 65308 => '<', + 65309 => '=', + 65310 => '>', + 65311 => '?', + 65312 => '@', + 65339 => '[', + 65340 => '\\', + 65341 => ']', + 65342 => '^', + 65343 => '_', + 65344 => '`', + 65371 => '{', + 65372 => '|', + 65373 => '}', + 65374 => '~', + 65507 => ' ̄', + 127233 => '0,', + 127234 => '1,', + 127235 => '2,', + 127236 => '3,', + 127237 => '4,', + 127238 => '5,', + 127239 => '6,', + 127240 => '7,', + 127241 => '8,', + 127242 => '9,', + 127248 => '(a)', + 127249 => '(b)', + 127250 => '(c)', + 127251 => '(d)', + 127252 => '(e)', + 127253 => '(f)', + 127254 => '(g)', + 127255 => '(h)', + 127256 => '(i)', + 127257 => '(j)', + 127258 => '(k)', + 127259 => '(l)', + 127260 => '(m)', + 127261 => '(n)', + 127262 => '(o)', + 127263 => '(p)', + 127264 => '(q)', + 127265 => '(r)', + 127266 => '(s)', + 127267 => '(t)', + 127268 => '(u)', + 127269 => '(v)', + 127270 => '(w)', + 127271 => '(x)', + 127272 => '(y)', + 127273 => '(z)', +); diff --git a/lib/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php b/lib/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php new file mode 100644 index 0000000000..223396ec4c --- /dev/null +++ b/lib/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php @@ -0,0 +1,71 @@ + true, + 1 => true, + 2 => true, + 3 => true, + 4 => true, + 5 => true, + 6 => true, + 7 => true, + 8 => true, + 9 => true, + 10 => true, + 11 => true, + 12 => true, + 13 => true, + 14 => true, + 15 => true, + 16 => true, + 17 => true, + 18 => true, + 19 => true, + 20 => true, + 21 => true, + 22 => true, + 23 => true, + 24 => true, + 25 => true, + 26 => true, + 27 => true, + 28 => true, + 29 => true, + 30 => true, + 31 => true, + 32 => true, + 33 => true, + 34 => true, + 35 => true, + 36 => true, + 37 => true, + 38 => true, + 39 => true, + 40 => true, + 41 => true, + 42 => true, + 43 => true, + 44 => true, + 47 => true, + 58 => true, + 59 => true, + 60 => true, + 61 => true, + 62 => true, + 63 => true, + 64 => true, + 91 => true, + 92 => true, + 93 => true, + 94 => true, + 95 => true, + 96 => true, + 123 => true, + 124 => true, + 125 => true, + 126 => true, + 127 => true, + 8800 => true, + 8814 => true, + 8815 => true, +); diff --git a/lib/symfony/polyfill-intl-idn/Resources/unidata/ignored.php b/lib/symfony/polyfill-intl-idn/Resources/unidata/ignored.php new file mode 100644 index 0000000000..b377844130 --- /dev/null +++ b/lib/symfony/polyfill-intl-idn/Resources/unidata/ignored.php @@ -0,0 +1,273 @@ + true, + 847 => true, + 6155 => true, + 6156 => true, + 6157 => true, + 8203 => true, + 8288 => true, + 8292 => true, + 65024 => true, + 65025 => true, + 65026 => true, + 65027 => true, + 65028 => true, + 65029 => true, + 65030 => true, + 65031 => true, + 65032 => true, + 65033 => true, + 65034 => true, + 65035 => true, + 65036 => true, + 65037 => true, + 65038 => true, + 65039 => true, + 65279 => true, + 113824 => true, + 113825 => true, + 113826 => true, + 113827 => true, + 917760 => true, + 917761 => true, + 917762 => true, + 917763 => true, + 917764 => true, + 917765 => true, + 917766 => true, + 917767 => true, + 917768 => true, + 917769 => true, + 917770 => true, + 917771 => true, + 917772 => true, + 917773 => true, + 917774 => true, + 917775 => true, + 917776 => true, + 917777 => true, + 917778 => true, + 917779 => true, + 917780 => true, + 917781 => true, + 917782 => true, + 917783 => true, + 917784 => true, + 917785 => true, + 917786 => true, + 917787 => true, + 917788 => true, + 917789 => true, + 917790 => true, + 917791 => true, + 917792 => true, + 917793 => true, + 917794 => true, + 917795 => true, + 917796 => true, + 917797 => true, + 917798 => true, + 917799 => true, + 917800 => true, + 917801 => true, + 917802 => true, + 917803 => true, + 917804 => true, + 917805 => true, + 917806 => true, + 917807 => true, + 917808 => true, + 917809 => true, + 917810 => true, + 917811 => true, + 917812 => true, + 917813 => true, + 917814 => true, + 917815 => true, + 917816 => true, + 917817 => true, + 917818 => true, + 917819 => true, + 917820 => true, + 917821 => true, + 917822 => true, + 917823 => true, + 917824 => true, + 917825 => true, + 917826 => true, + 917827 => true, + 917828 => true, + 917829 => true, + 917830 => true, + 917831 => true, + 917832 => true, + 917833 => true, + 917834 => true, + 917835 => true, + 917836 => true, + 917837 => true, + 917838 => true, + 917839 => true, + 917840 => true, + 917841 => true, + 917842 => true, + 917843 => true, + 917844 => true, + 917845 => true, + 917846 => true, + 917847 => true, + 917848 => true, + 917849 => true, + 917850 => true, + 917851 => true, + 917852 => true, + 917853 => true, + 917854 => true, + 917855 => true, + 917856 => true, + 917857 => true, + 917858 => true, + 917859 => true, + 917860 => true, + 917861 => true, + 917862 => true, + 917863 => true, + 917864 => true, + 917865 => true, + 917866 => true, + 917867 => true, + 917868 => true, + 917869 => true, + 917870 => true, + 917871 => true, + 917872 => true, + 917873 => true, + 917874 => true, + 917875 => true, + 917876 => true, + 917877 => true, + 917878 => true, + 917879 => true, + 917880 => true, + 917881 => true, + 917882 => true, + 917883 => true, + 917884 => true, + 917885 => true, + 917886 => true, + 917887 => true, + 917888 => true, + 917889 => true, + 917890 => true, + 917891 => true, + 917892 => true, + 917893 => true, + 917894 => true, + 917895 => true, + 917896 => true, + 917897 => true, + 917898 => true, + 917899 => true, + 917900 => true, + 917901 => true, + 917902 => true, + 917903 => true, + 917904 => true, + 917905 => true, + 917906 => true, + 917907 => true, + 917908 => true, + 917909 => true, + 917910 => true, + 917911 => true, + 917912 => true, + 917913 => true, + 917914 => true, + 917915 => true, + 917916 => true, + 917917 => true, + 917918 => true, + 917919 => true, + 917920 => true, + 917921 => true, + 917922 => true, + 917923 => true, + 917924 => true, + 917925 => true, + 917926 => true, + 917927 => true, + 917928 => true, + 917929 => true, + 917930 => true, + 917931 => true, + 917932 => true, + 917933 => true, + 917934 => true, + 917935 => true, + 917936 => true, + 917937 => true, + 917938 => true, + 917939 => true, + 917940 => true, + 917941 => true, + 917942 => true, + 917943 => true, + 917944 => true, + 917945 => true, + 917946 => true, + 917947 => true, + 917948 => true, + 917949 => true, + 917950 => true, + 917951 => true, + 917952 => true, + 917953 => true, + 917954 => true, + 917955 => true, + 917956 => true, + 917957 => true, + 917958 => true, + 917959 => true, + 917960 => true, + 917961 => true, + 917962 => true, + 917963 => true, + 917964 => true, + 917965 => true, + 917966 => true, + 917967 => true, + 917968 => true, + 917969 => true, + 917970 => true, + 917971 => true, + 917972 => true, + 917973 => true, + 917974 => true, + 917975 => true, + 917976 => true, + 917977 => true, + 917978 => true, + 917979 => true, + 917980 => true, + 917981 => true, + 917982 => true, + 917983 => true, + 917984 => true, + 917985 => true, + 917986 => true, + 917987 => true, + 917988 => true, + 917989 => true, + 917990 => true, + 917991 => true, + 917992 => true, + 917993 => true, + 917994 => true, + 917995 => true, + 917996 => true, + 917997 => true, + 917998 => true, + 917999 => true, +); diff --git a/lib/symfony/polyfill-intl-idn/Resources/unidata/mapped.php b/lib/symfony/polyfill-intl-idn/Resources/unidata/mapped.php new file mode 100644 index 0000000000..9b85fe9d3f --- /dev/null +++ b/lib/symfony/polyfill-intl-idn/Resources/unidata/mapped.php @@ -0,0 +1,5778 @@ + 'a', + 66 => 'b', + 67 => 'c', + 68 => 'd', + 69 => 'e', + 70 => 'f', + 71 => 'g', + 72 => 'h', + 73 => 'i', + 74 => 'j', + 75 => 'k', + 76 => 'l', + 77 => 'm', + 78 => 'n', + 79 => 'o', + 80 => 'p', + 81 => 'q', + 82 => 'r', + 83 => 's', + 84 => 't', + 85 => 'u', + 86 => 'v', + 87 => 'w', + 88 => 'x', + 89 => 'y', + 90 => 'z', + 170 => 'a', + 178 => '2', + 179 => '3', + 181 => 'μ', + 185 => '1', + 186 => 'o', + 188 => '1⁄4', + 189 => '1⁄2', + 190 => '3⁄4', + 192 => 'à', + 193 => 'á', + 194 => 'â', + 195 => 'ã', + 196 => 'ä', + 197 => 'å', + 198 => 'æ', + 199 => 'ç', + 200 => 'è', + 201 => 'é', + 202 => 'ê', + 203 => 'ë', + 204 => 'ì', + 205 => 'í', + 206 => 'î', + 207 => 'ï', + 208 => 'ð', + 209 => 'ñ', + 210 => 'ò', + 211 => 'ó', + 212 => 'ô', + 213 => 'õ', + 214 => 'ö', + 216 => 'ø', + 217 => 'ù', + 218 => 'ú', + 219 => 'û', + 220 => 'ü', + 221 => 'ý', + 222 => 'þ', + 256 => 'ā', + 258 => 'ă', + 260 => 'ą', + 262 => 'ć', + 264 => 'ĉ', + 266 => 'ċ', + 268 => 'č', + 270 => 'ď', + 272 => 'đ', + 274 => 'ē', + 276 => 'ĕ', + 278 => 'ė', + 280 => 'ę', + 282 => 'ě', + 284 => 'ĝ', + 286 => 'ğ', + 288 => 'ġ', + 290 => 'ģ', + 292 => 'ĥ', + 294 => 'ħ', + 296 => 'ĩ', + 298 => 'ī', + 300 => 'ĭ', + 302 => 'į', + 304 => 'i̇', + 306 => 'ij', + 307 => 'ij', + 308 => 'ĵ', + 310 => 'ķ', + 313 => 'ĺ', + 315 => 'ļ', + 317 => 'ľ', + 319 => 'l·', + 320 => 'l·', + 321 => 'ł', + 323 => 'ń', + 325 => 'ņ', + 327 => 'ň', + 329 => 'ʼn', + 330 => 'ŋ', + 332 => 'ō', + 334 => 'ŏ', + 336 => 'ő', + 338 => 'œ', + 340 => 'ŕ', + 342 => 'ŗ', + 344 => 'ř', + 346 => 'ś', + 348 => 'ŝ', + 350 => 'ş', + 352 => 'š', + 354 => 'ţ', + 356 => 'ť', + 358 => 'ŧ', + 360 => 'ũ', + 362 => 'ū', + 364 => 'ŭ', + 366 => 'ů', + 368 => 'ű', + 370 => 'ų', + 372 => 'ŵ', + 374 => 'ŷ', + 376 => 'ÿ', + 377 => 'ź', + 379 => 'ż', + 381 => 'ž', + 383 => 's', + 385 => 'ɓ', + 386 => 'ƃ', + 388 => 'ƅ', + 390 => 'ɔ', + 391 => 'ƈ', + 393 => 'ɖ', + 394 => 'ɗ', + 395 => 'ƌ', + 398 => 'ǝ', + 399 => 'ə', + 400 => 'ɛ', + 401 => 'ƒ', + 403 => 'ɠ', + 404 => 'ɣ', + 406 => 'ɩ', + 407 => 'ɨ', + 408 => 'ƙ', + 412 => 'ɯ', + 413 => 'ɲ', + 415 => 'ɵ', + 416 => 'ơ', + 418 => 'ƣ', + 420 => 'ƥ', + 422 => 'ʀ', + 423 => 'ƨ', + 425 => 'ʃ', + 428 => 'ƭ', + 430 => 'ʈ', + 431 => 'ư', + 433 => 'ʊ', + 434 => 'ʋ', + 435 => 'ƴ', + 437 => 'ƶ', + 439 => 'ʒ', + 440 => 'ƹ', + 444 => 'ƽ', + 452 => 'dž', + 453 => 'dž', + 454 => 'dž', + 455 => 'lj', + 456 => 'lj', + 457 => 'lj', + 458 => 'nj', + 459 => 'nj', + 460 => 'nj', + 461 => 'ǎ', + 463 => 'ǐ', + 465 => 'ǒ', + 467 => 'ǔ', + 469 => 'ǖ', + 471 => 'ǘ', + 473 => 'ǚ', + 475 => 'ǜ', + 478 => 'ǟ', + 480 => 'ǡ', + 482 => 'ǣ', + 484 => 'ǥ', + 486 => 'ǧ', + 488 => 'ǩ', + 490 => 'ǫ', + 492 => 'ǭ', + 494 => 'ǯ', + 497 => 'dz', + 498 => 'dz', + 499 => 'dz', + 500 => 'ǵ', + 502 => 'ƕ', + 503 => 'ƿ', + 504 => 'ǹ', + 506 => 'ǻ', + 508 => 'ǽ', + 510 => 'ǿ', + 512 => 'ȁ', + 514 => 'ȃ', + 516 => 'ȅ', + 518 => 'ȇ', + 520 => 'ȉ', + 522 => 'ȋ', + 524 => 'ȍ', + 526 => 'ȏ', + 528 => 'ȑ', + 530 => 'ȓ', + 532 => 'ȕ', + 534 => 'ȗ', + 536 => 'ș', + 538 => 'ț', + 540 => 'ȝ', + 542 => 'ȟ', + 544 => 'ƞ', + 546 => 'ȣ', + 548 => 'ȥ', + 550 => 'ȧ', + 552 => 'ȩ', + 554 => 'ȫ', + 556 => 'ȭ', + 558 => 'ȯ', + 560 => 'ȱ', + 562 => 'ȳ', + 570 => 'ⱥ', + 571 => 'ȼ', + 573 => 'ƚ', + 574 => 'ⱦ', + 577 => 'ɂ', + 579 => 'ƀ', + 580 => 'ʉ', + 581 => 'ʌ', + 582 => 'ɇ', + 584 => 'ɉ', + 586 => 'ɋ', + 588 => 'ɍ', + 590 => 'ɏ', + 688 => 'h', + 689 => 'ɦ', + 690 => 'j', + 691 => 'r', + 692 => 'ɹ', + 693 => 'ɻ', + 694 => 'ʁ', + 695 => 'w', + 696 => 'y', + 736 => 'ɣ', + 737 => 'l', + 738 => 's', + 739 => 'x', + 740 => 'ʕ', + 832 => '̀', + 833 => '́', + 835 => '̓', + 836 => '̈́', + 837 => 'ι', + 880 => 'ͱ', + 882 => 'ͳ', + 884 => 'ʹ', + 886 => 'ͷ', + 895 => 'ϳ', + 902 => 'ά', + 903 => '·', + 904 => 'έ', + 905 => 'ή', + 906 => 'ί', + 908 => 'ό', + 910 => 'ύ', + 911 => 'ώ', + 913 => 'α', + 914 => 'β', + 915 => 'γ', + 916 => 'δ', + 917 => 'ε', + 918 => 'ζ', + 919 => 'η', + 920 => 'θ', + 921 => 'ι', + 922 => 'κ', + 923 => 'λ', + 924 => 'μ', + 925 => 'ν', + 926 => 'ξ', + 927 => 'ο', + 928 => 'π', + 929 => 'ρ', + 931 => 'σ', + 932 => 'τ', + 933 => 'υ', + 934 => 'φ', + 935 => 'χ', + 936 => 'ψ', + 937 => 'ω', + 938 => 'ϊ', + 939 => 'ϋ', + 975 => 'ϗ', + 976 => 'β', + 977 => 'θ', + 978 => 'υ', + 979 => 'ύ', + 980 => 'ϋ', + 981 => 'φ', + 982 => 'π', + 984 => 'ϙ', + 986 => 'ϛ', + 988 => 'ϝ', + 990 => 'ϟ', + 992 => 'ϡ', + 994 => 'ϣ', + 996 => 'ϥ', + 998 => 'ϧ', + 1000 => 'ϩ', + 1002 => 'ϫ', + 1004 => 'ϭ', + 1006 => 'ϯ', + 1008 => 'κ', + 1009 => 'ρ', + 1010 => 'σ', + 1012 => 'θ', + 1013 => 'ε', + 1015 => 'ϸ', + 1017 => 'σ', + 1018 => 'ϻ', + 1021 => 'ͻ', + 1022 => 'ͼ', + 1023 => 'ͽ', + 1024 => 'ѐ', + 1025 => 'ё', + 1026 => 'ђ', + 1027 => 'ѓ', + 1028 => 'є', + 1029 => 'ѕ', + 1030 => 'і', + 1031 => 'ї', + 1032 => 'ј', + 1033 => 'љ', + 1034 => 'њ', + 1035 => 'ћ', + 1036 => 'ќ', + 1037 => 'ѝ', + 1038 => 'ў', + 1039 => 'џ', + 1040 => 'а', + 1041 => 'б', + 1042 => 'в', + 1043 => 'г', + 1044 => 'д', + 1045 => 'е', + 1046 => 'ж', + 1047 => 'з', + 1048 => 'и', + 1049 => 'й', + 1050 => 'к', + 1051 => 'л', + 1052 => 'м', + 1053 => 'н', + 1054 => 'о', + 1055 => 'п', + 1056 => 'р', + 1057 => 'с', + 1058 => 'т', + 1059 => 'у', + 1060 => 'ф', + 1061 => 'х', + 1062 => 'ц', + 1063 => 'ч', + 1064 => 'ш', + 1065 => 'щ', + 1066 => 'ъ', + 1067 => 'ы', + 1068 => 'ь', + 1069 => 'э', + 1070 => 'ю', + 1071 => 'я', + 1120 => 'ѡ', + 1122 => 'ѣ', + 1124 => 'ѥ', + 1126 => 'ѧ', + 1128 => 'ѩ', + 1130 => 'ѫ', + 1132 => 'ѭ', + 1134 => 'ѯ', + 1136 => 'ѱ', + 1138 => 'ѳ', + 1140 => 'ѵ', + 1142 => 'ѷ', + 1144 => 'ѹ', + 1146 => 'ѻ', + 1148 => 'ѽ', + 1150 => 'ѿ', + 1152 => 'ҁ', + 1162 => 'ҋ', + 1164 => 'ҍ', + 1166 => 'ҏ', + 1168 => 'ґ', + 1170 => 'ғ', + 1172 => 'ҕ', + 1174 => 'җ', + 1176 => 'ҙ', + 1178 => 'қ', + 1180 => 'ҝ', + 1182 => 'ҟ', + 1184 => 'ҡ', + 1186 => 'ң', + 1188 => 'ҥ', + 1190 => 'ҧ', + 1192 => 'ҩ', + 1194 => 'ҫ', + 1196 => 'ҭ', + 1198 => 'ү', + 1200 => 'ұ', + 1202 => 'ҳ', + 1204 => 'ҵ', + 1206 => 'ҷ', + 1208 => 'ҹ', + 1210 => 'һ', + 1212 => 'ҽ', + 1214 => 'ҿ', + 1217 => 'ӂ', + 1219 => 'ӄ', + 1221 => 'ӆ', + 1223 => 'ӈ', + 1225 => 'ӊ', + 1227 => 'ӌ', + 1229 => 'ӎ', + 1232 => 'ӑ', + 1234 => 'ӓ', + 1236 => 'ӕ', + 1238 => 'ӗ', + 1240 => 'ә', + 1242 => 'ӛ', + 1244 => 'ӝ', + 1246 => 'ӟ', + 1248 => 'ӡ', + 1250 => 'ӣ', + 1252 => 'ӥ', + 1254 => 'ӧ', + 1256 => 'ө', + 1258 => 'ӫ', + 1260 => 'ӭ', + 1262 => 'ӯ', + 1264 => 'ӱ', + 1266 => 'ӳ', + 1268 => 'ӵ', + 1270 => 'ӷ', + 1272 => 'ӹ', + 1274 => 'ӻ', + 1276 => 'ӽ', + 1278 => 'ӿ', + 1280 => 'ԁ', + 1282 => 'ԃ', + 1284 => 'ԅ', + 1286 => 'ԇ', + 1288 => 'ԉ', + 1290 => 'ԋ', + 1292 => 'ԍ', + 1294 => 'ԏ', + 1296 => 'ԑ', + 1298 => 'ԓ', + 1300 => 'ԕ', + 1302 => 'ԗ', + 1304 => 'ԙ', + 1306 => 'ԛ', + 1308 => 'ԝ', + 1310 => 'ԟ', + 1312 => 'ԡ', + 1314 => 'ԣ', + 1316 => 'ԥ', + 1318 => 'ԧ', + 1320 => 'ԩ', + 1322 => 'ԫ', + 1324 => 'ԭ', + 1326 => 'ԯ', + 1329 => 'ա', + 1330 => 'բ', + 1331 => 'գ', + 1332 => 'դ', + 1333 => 'ե', + 1334 => 'զ', + 1335 => 'է', + 1336 => 'ը', + 1337 => 'թ', + 1338 => 'ժ', + 1339 => 'ի', + 1340 => 'լ', + 1341 => 'խ', + 1342 => 'ծ', + 1343 => 'կ', + 1344 => 'հ', + 1345 => 'ձ', + 1346 => 'ղ', + 1347 => 'ճ', + 1348 => 'մ', + 1349 => 'յ', + 1350 => 'ն', + 1351 => 'շ', + 1352 => 'ո', + 1353 => 'չ', + 1354 => 'պ', + 1355 => 'ջ', + 1356 => 'ռ', + 1357 => 'ս', + 1358 => 'վ', + 1359 => 'տ', + 1360 => 'ր', + 1361 => 'ց', + 1362 => 'ւ', + 1363 => 'փ', + 1364 => 'ք', + 1365 => 'օ', + 1366 => 'ֆ', + 1415 => 'եւ', + 1653 => 'اٴ', + 1654 => 'وٴ', + 1655 => 'ۇٴ', + 1656 => 'يٴ', + 2392 => 'क़', + 2393 => 'ख़', + 2394 => 'ग़', + 2395 => 'ज़', + 2396 => 'ड़', + 2397 => 'ढ़', + 2398 => 'फ़', + 2399 => 'य़', + 2524 => 'ড়', + 2525 => 'ঢ়', + 2527 => 'য়', + 2611 => 'ਲ਼', + 2614 => 'ਸ਼', + 2649 => 'ਖ਼', + 2650 => 'ਗ਼', + 2651 => 'ਜ਼', + 2654 => 'ਫ਼', + 2908 => 'ଡ଼', + 2909 => 'ଢ଼', + 3635 => 'ํา', + 3763 => 'ໍາ', + 3804 => 'ຫນ', + 3805 => 'ຫມ', + 3852 => '་', + 3907 => 'གྷ', + 3917 => 'ཌྷ', + 3922 => 'དྷ', + 3927 => 'བྷ', + 3932 => 'ཛྷ', + 3945 => 'ཀྵ', + 3955 => 'ཱི', + 3957 => 'ཱུ', + 3958 => 'ྲྀ', + 3959 => 'ྲཱྀ', + 3960 => 'ླྀ', + 3961 => 'ླཱྀ', + 3969 => 'ཱྀ', + 3987 => 'ྒྷ', + 3997 => 'ྜྷ', + 4002 => 'ྡྷ', + 4007 => 'ྦྷ', + 4012 => 'ྫྷ', + 4025 => 'ྐྵ', + 4295 => 'ⴧ', + 4301 => 'ⴭ', + 4348 => 'ნ', + 5112 => 'Ᏸ', + 5113 => 'Ᏹ', + 5114 => 'Ᏺ', + 5115 => 'Ᏻ', + 5116 => 'Ᏼ', + 5117 => 'Ᏽ', + 7296 => 'в', + 7297 => 'д', + 7298 => 'о', + 7299 => 'с', + 7300 => 'т', + 7301 => 'т', + 7302 => 'ъ', + 7303 => 'ѣ', + 7304 => 'ꙋ', + 7312 => 'ა', + 7313 => 'ბ', + 7314 => 'გ', + 7315 => 'დ', + 7316 => 'ე', + 7317 => 'ვ', + 7318 => 'ზ', + 7319 => 'თ', + 7320 => 'ი', + 7321 => 'კ', + 7322 => 'ლ', + 7323 => 'მ', + 7324 => 'ნ', + 7325 => 'ო', + 7326 => 'პ', + 7327 => 'ჟ', + 7328 => 'რ', + 7329 => 'ს', + 7330 => 'ტ', + 7331 => 'უ', + 7332 => 'ფ', + 7333 => 'ქ', + 7334 => 'ღ', + 7335 => 'ყ', + 7336 => 'შ', + 7337 => 'ჩ', + 7338 => 'ც', + 7339 => 'ძ', + 7340 => 'წ', + 7341 => 'ჭ', + 7342 => 'ხ', + 7343 => 'ჯ', + 7344 => 'ჰ', + 7345 => 'ჱ', + 7346 => 'ჲ', + 7347 => 'ჳ', + 7348 => 'ჴ', + 7349 => 'ჵ', + 7350 => 'ჶ', + 7351 => 'ჷ', + 7352 => 'ჸ', + 7353 => 'ჹ', + 7354 => 'ჺ', + 7357 => 'ჽ', + 7358 => 'ჾ', + 7359 => 'ჿ', + 7468 => 'a', + 7469 => 'æ', + 7470 => 'b', + 7472 => 'd', + 7473 => 'e', + 7474 => 'ǝ', + 7475 => 'g', + 7476 => 'h', + 7477 => 'i', + 7478 => 'j', + 7479 => 'k', + 7480 => 'l', + 7481 => 'm', + 7482 => 'n', + 7484 => 'o', + 7485 => 'ȣ', + 7486 => 'p', + 7487 => 'r', + 7488 => 't', + 7489 => 'u', + 7490 => 'w', + 7491 => 'a', + 7492 => 'ɐ', + 7493 => 'ɑ', + 7494 => 'ᴂ', + 7495 => 'b', + 7496 => 'd', + 7497 => 'e', + 7498 => 'ə', + 7499 => 'ɛ', + 7500 => 'ɜ', + 7501 => 'g', + 7503 => 'k', + 7504 => 'm', + 7505 => 'ŋ', + 7506 => 'o', + 7507 => 'ɔ', + 7508 => 'ᴖ', + 7509 => 'ᴗ', + 7510 => 'p', + 7511 => 't', + 7512 => 'u', + 7513 => 'ᴝ', + 7514 => 'ɯ', + 7515 => 'v', + 7516 => 'ᴥ', + 7517 => 'β', + 7518 => 'γ', + 7519 => 'δ', + 7520 => 'φ', + 7521 => 'χ', + 7522 => 'i', + 7523 => 'r', + 7524 => 'u', + 7525 => 'v', + 7526 => 'β', + 7527 => 'γ', + 7528 => 'ρ', + 7529 => 'φ', + 7530 => 'χ', + 7544 => 'н', + 7579 => 'ɒ', + 7580 => 'c', + 7581 => 'ɕ', + 7582 => 'ð', + 7583 => 'ɜ', + 7584 => 'f', + 7585 => 'ɟ', + 7586 => 'ɡ', + 7587 => 'ɥ', + 7588 => 'ɨ', + 7589 => 'ɩ', + 7590 => 'ɪ', + 7591 => 'ᵻ', + 7592 => 'ʝ', + 7593 => 'ɭ', + 7594 => 'ᶅ', + 7595 => 'ʟ', + 7596 => 'ɱ', + 7597 => 'ɰ', + 7598 => 'ɲ', + 7599 => 'ɳ', + 7600 => 'ɴ', + 7601 => 'ɵ', + 7602 => 'ɸ', + 7603 => 'ʂ', + 7604 => 'ʃ', + 7605 => 'ƫ', + 7606 => 'ʉ', + 7607 => 'ʊ', + 7608 => 'ᴜ', + 7609 => 'ʋ', + 7610 => 'ʌ', + 7611 => 'z', + 7612 => 'ʐ', + 7613 => 'ʑ', + 7614 => 'ʒ', + 7615 => 'θ', + 7680 => 'ḁ', + 7682 => 'ḃ', + 7684 => 'ḅ', + 7686 => 'ḇ', + 7688 => 'ḉ', + 7690 => 'ḋ', + 7692 => 'ḍ', + 7694 => 'ḏ', + 7696 => 'ḑ', + 7698 => 'ḓ', + 7700 => 'ḕ', + 7702 => 'ḗ', + 7704 => 'ḙ', + 7706 => 'ḛ', + 7708 => 'ḝ', + 7710 => 'ḟ', + 7712 => 'ḡ', + 7714 => 'ḣ', + 7716 => 'ḥ', + 7718 => 'ḧ', + 7720 => 'ḩ', + 7722 => 'ḫ', + 7724 => 'ḭ', + 7726 => 'ḯ', + 7728 => 'ḱ', + 7730 => 'ḳ', + 7732 => 'ḵ', + 7734 => 'ḷ', + 7736 => 'ḹ', + 7738 => 'ḻ', + 7740 => 'ḽ', + 7742 => 'ḿ', + 7744 => 'ṁ', + 7746 => 'ṃ', + 7748 => 'ṅ', + 7750 => 'ṇ', + 7752 => 'ṉ', + 7754 => 'ṋ', + 7756 => 'ṍ', + 7758 => 'ṏ', + 7760 => 'ṑ', + 7762 => 'ṓ', + 7764 => 'ṕ', + 7766 => 'ṗ', + 7768 => 'ṙ', + 7770 => 'ṛ', + 7772 => 'ṝ', + 7774 => 'ṟ', + 7776 => 'ṡ', + 7778 => 'ṣ', + 7780 => 'ṥ', + 7782 => 'ṧ', + 7784 => 'ṩ', + 7786 => 'ṫ', + 7788 => 'ṭ', + 7790 => 'ṯ', + 7792 => 'ṱ', + 7794 => 'ṳ', + 7796 => 'ṵ', + 7798 => 'ṷ', + 7800 => 'ṹ', + 7802 => 'ṻ', + 7804 => 'ṽ', + 7806 => 'ṿ', + 7808 => 'ẁ', + 7810 => 'ẃ', + 7812 => 'ẅ', + 7814 => 'ẇ', + 7816 => 'ẉ', + 7818 => 'ẋ', + 7820 => 'ẍ', + 7822 => 'ẏ', + 7824 => 'ẑ', + 7826 => 'ẓ', + 7828 => 'ẕ', + 7834 => 'aʾ', + 7835 => 'ṡ', + 7838 => 'ss', + 7840 => 'ạ', + 7842 => 'ả', + 7844 => 'ấ', + 7846 => 'ầ', + 7848 => 'ẩ', + 7850 => 'ẫ', + 7852 => 'ậ', + 7854 => 'ắ', + 7856 => 'ằ', + 7858 => 'ẳ', + 7860 => 'ẵ', + 7862 => 'ặ', + 7864 => 'ẹ', + 7866 => 'ẻ', + 7868 => 'ẽ', + 7870 => 'ế', + 7872 => 'ề', + 7874 => 'ể', + 7876 => 'ễ', + 7878 => 'ệ', + 7880 => 'ỉ', + 7882 => 'ị', + 7884 => 'ọ', + 7886 => 'ỏ', + 7888 => 'ố', + 7890 => 'ồ', + 7892 => 'ổ', + 7894 => 'ỗ', + 7896 => 'ộ', + 7898 => 'ớ', + 7900 => 'ờ', + 7902 => 'ở', + 7904 => 'ỡ', + 7906 => 'ợ', + 7908 => 'ụ', + 7910 => 'ủ', + 7912 => 'ứ', + 7914 => 'ừ', + 7916 => 'ử', + 7918 => 'ữ', + 7920 => 'ự', + 7922 => 'ỳ', + 7924 => 'ỵ', + 7926 => 'ỷ', + 7928 => 'ỹ', + 7930 => 'ỻ', + 7932 => 'ỽ', + 7934 => 'ỿ', + 7944 => 'ἀ', + 7945 => 'ἁ', + 7946 => 'ἂ', + 7947 => 'ἃ', + 7948 => 'ἄ', + 7949 => 'ἅ', + 7950 => 'ἆ', + 7951 => 'ἇ', + 7960 => 'ἐ', + 7961 => 'ἑ', + 7962 => 'ἒ', + 7963 => 'ἓ', + 7964 => 'ἔ', + 7965 => 'ἕ', + 7976 => 'ἠ', + 7977 => 'ἡ', + 7978 => 'ἢ', + 7979 => 'ἣ', + 7980 => 'ἤ', + 7981 => 'ἥ', + 7982 => 'ἦ', + 7983 => 'ἧ', + 7992 => 'ἰ', + 7993 => 'ἱ', + 7994 => 'ἲ', + 7995 => 'ἳ', + 7996 => 'ἴ', + 7997 => 'ἵ', + 7998 => 'ἶ', + 7999 => 'ἷ', + 8008 => 'ὀ', + 8009 => 'ὁ', + 8010 => 'ὂ', + 8011 => 'ὃ', + 8012 => 'ὄ', + 8013 => 'ὅ', + 8025 => 'ὑ', + 8027 => 'ὓ', + 8029 => 'ὕ', + 8031 => 'ὗ', + 8040 => 'ὠ', + 8041 => 'ὡ', + 8042 => 'ὢ', + 8043 => 'ὣ', + 8044 => 'ὤ', + 8045 => 'ὥ', + 8046 => 'ὦ', + 8047 => 'ὧ', + 8049 => 'ά', + 8051 => 'έ', + 8053 => 'ή', + 8055 => 'ί', + 8057 => 'ό', + 8059 => 'ύ', + 8061 => 'ώ', + 8064 => 'ἀι', + 8065 => 'ἁι', + 8066 => 'ἂι', + 8067 => 'ἃι', + 8068 => 'ἄι', + 8069 => 'ἅι', + 8070 => 'ἆι', + 8071 => 'ἇι', + 8072 => 'ἀι', + 8073 => 'ἁι', + 8074 => 'ἂι', + 8075 => 'ἃι', + 8076 => 'ἄι', + 8077 => 'ἅι', + 8078 => 'ἆι', + 8079 => 'ἇι', + 8080 => 'ἠι', + 8081 => 'ἡι', + 8082 => 'ἢι', + 8083 => 'ἣι', + 8084 => 'ἤι', + 8085 => 'ἥι', + 8086 => 'ἦι', + 8087 => 'ἧι', + 8088 => 'ἠι', + 8089 => 'ἡι', + 8090 => 'ἢι', + 8091 => 'ἣι', + 8092 => 'ἤι', + 8093 => 'ἥι', + 8094 => 'ἦι', + 8095 => 'ἧι', + 8096 => 'ὠι', + 8097 => 'ὡι', + 8098 => 'ὢι', + 8099 => 'ὣι', + 8100 => 'ὤι', + 8101 => 'ὥι', + 8102 => 'ὦι', + 8103 => 'ὧι', + 8104 => 'ὠι', + 8105 => 'ὡι', + 8106 => 'ὢι', + 8107 => 'ὣι', + 8108 => 'ὤι', + 8109 => 'ὥι', + 8110 => 'ὦι', + 8111 => 'ὧι', + 8114 => 'ὰι', + 8115 => 'αι', + 8116 => 'άι', + 8119 => 'ᾶι', + 8120 => 'ᾰ', + 8121 => 'ᾱ', + 8122 => 'ὰ', + 8123 => 'ά', + 8124 => 'αι', + 8126 => 'ι', + 8130 => 'ὴι', + 8131 => 'ηι', + 8132 => 'ήι', + 8135 => 'ῆι', + 8136 => 'ὲ', + 8137 => 'έ', + 8138 => 'ὴ', + 8139 => 'ή', + 8140 => 'ηι', + 8147 => 'ΐ', + 8152 => 'ῐ', + 8153 => 'ῑ', + 8154 => 'ὶ', + 8155 => 'ί', + 8163 => 'ΰ', + 8168 => 'ῠ', + 8169 => 'ῡ', + 8170 => 'ὺ', + 8171 => 'ύ', + 8172 => 'ῥ', + 8178 => 'ὼι', + 8179 => 'ωι', + 8180 => 'ώι', + 8183 => 'ῶι', + 8184 => 'ὸ', + 8185 => 'ό', + 8186 => 'ὼ', + 8187 => 'ώ', + 8188 => 'ωι', + 8209 => '‐', + 8243 => '′′', + 8244 => '′′′', + 8246 => '‵‵', + 8247 => '‵‵‵', + 8279 => '′′′′', + 8304 => '0', + 8305 => 'i', + 8308 => '4', + 8309 => '5', + 8310 => '6', + 8311 => '7', + 8312 => '8', + 8313 => '9', + 8315 => '−', + 8319 => 'n', + 8320 => '0', + 8321 => '1', + 8322 => '2', + 8323 => '3', + 8324 => '4', + 8325 => '5', + 8326 => '6', + 8327 => '7', + 8328 => '8', + 8329 => '9', + 8331 => '−', + 8336 => 'a', + 8337 => 'e', + 8338 => 'o', + 8339 => 'x', + 8340 => 'ə', + 8341 => 'h', + 8342 => 'k', + 8343 => 'l', + 8344 => 'm', + 8345 => 'n', + 8346 => 'p', + 8347 => 's', + 8348 => 't', + 8360 => 'rs', + 8450 => 'c', + 8451 => '°c', + 8455 => 'ɛ', + 8457 => '°f', + 8458 => 'g', + 8459 => 'h', + 8460 => 'h', + 8461 => 'h', + 8462 => 'h', + 8463 => 'ħ', + 8464 => 'i', + 8465 => 'i', + 8466 => 'l', + 8467 => 'l', + 8469 => 'n', + 8470 => 'no', + 8473 => 'p', + 8474 => 'q', + 8475 => 'r', + 8476 => 'r', + 8477 => 'r', + 8480 => 'sm', + 8481 => 'tel', + 8482 => 'tm', + 8484 => 'z', + 8486 => 'ω', + 8488 => 'z', + 8490 => 'k', + 8491 => 'å', + 8492 => 'b', + 8493 => 'c', + 8495 => 'e', + 8496 => 'e', + 8497 => 'f', + 8499 => 'm', + 8500 => 'o', + 8501 => 'א', + 8502 => 'ב', + 8503 => 'ג', + 8504 => 'ד', + 8505 => 'i', + 8507 => 'fax', + 8508 => 'π', + 8509 => 'γ', + 8510 => 'γ', + 8511 => 'π', + 8512 => '∑', + 8517 => 'd', + 8518 => 'd', + 8519 => 'e', + 8520 => 'i', + 8521 => 'j', + 8528 => '1⁄7', + 8529 => '1⁄9', + 8530 => '1⁄10', + 8531 => '1⁄3', + 8532 => '2⁄3', + 8533 => '1⁄5', + 8534 => '2⁄5', + 8535 => '3⁄5', + 8536 => '4⁄5', + 8537 => '1⁄6', + 8538 => '5⁄6', + 8539 => '1⁄8', + 8540 => '3⁄8', + 8541 => '5⁄8', + 8542 => '7⁄8', + 8543 => '1⁄', + 8544 => 'i', + 8545 => 'ii', + 8546 => 'iii', + 8547 => 'iv', + 8548 => 'v', + 8549 => 'vi', + 8550 => 'vii', + 8551 => 'viii', + 8552 => 'ix', + 8553 => 'x', + 8554 => 'xi', + 8555 => 'xii', + 8556 => 'l', + 8557 => 'c', + 8558 => 'd', + 8559 => 'm', + 8560 => 'i', + 8561 => 'ii', + 8562 => 'iii', + 8563 => 'iv', + 8564 => 'v', + 8565 => 'vi', + 8566 => 'vii', + 8567 => 'viii', + 8568 => 'ix', + 8569 => 'x', + 8570 => 'xi', + 8571 => 'xii', + 8572 => 'l', + 8573 => 'c', + 8574 => 'd', + 8575 => 'm', + 8585 => '0⁄3', + 8748 => '∫∫', + 8749 => '∫∫∫', + 8751 => '∮∮', + 8752 => '∮∮∮', + 9001 => '〈', + 9002 => '〉', + 9312 => '1', + 9313 => '2', + 9314 => '3', + 9315 => '4', + 9316 => '5', + 9317 => '6', + 9318 => '7', + 9319 => '8', + 9320 => '9', + 9321 => '10', + 9322 => '11', + 9323 => '12', + 9324 => '13', + 9325 => '14', + 9326 => '15', + 9327 => '16', + 9328 => '17', + 9329 => '18', + 9330 => '19', + 9331 => '20', + 9398 => 'a', + 9399 => 'b', + 9400 => 'c', + 9401 => 'd', + 9402 => 'e', + 9403 => 'f', + 9404 => 'g', + 9405 => 'h', + 9406 => 'i', + 9407 => 'j', + 9408 => 'k', + 9409 => 'l', + 9410 => 'm', + 9411 => 'n', + 9412 => 'o', + 9413 => 'p', + 9414 => 'q', + 9415 => 'r', + 9416 => 's', + 9417 => 't', + 9418 => 'u', + 9419 => 'v', + 9420 => 'w', + 9421 => 'x', + 9422 => 'y', + 9423 => 'z', + 9424 => 'a', + 9425 => 'b', + 9426 => 'c', + 9427 => 'd', + 9428 => 'e', + 9429 => 'f', + 9430 => 'g', + 9431 => 'h', + 9432 => 'i', + 9433 => 'j', + 9434 => 'k', + 9435 => 'l', + 9436 => 'm', + 9437 => 'n', + 9438 => 'o', + 9439 => 'p', + 9440 => 'q', + 9441 => 'r', + 9442 => 's', + 9443 => 't', + 9444 => 'u', + 9445 => 'v', + 9446 => 'w', + 9447 => 'x', + 9448 => 'y', + 9449 => 'z', + 9450 => '0', + 10764 => '∫∫∫∫', + 10972 => '⫝̸', + 11264 => 'ⰰ', + 11265 => 'ⰱ', + 11266 => 'ⰲ', + 11267 => 'ⰳ', + 11268 => 'ⰴ', + 11269 => 'ⰵ', + 11270 => 'ⰶ', + 11271 => 'ⰷ', + 11272 => 'ⰸ', + 11273 => 'ⰹ', + 11274 => 'ⰺ', + 11275 => 'ⰻ', + 11276 => 'ⰼ', + 11277 => 'ⰽ', + 11278 => 'ⰾ', + 11279 => 'ⰿ', + 11280 => 'ⱀ', + 11281 => 'ⱁ', + 11282 => 'ⱂ', + 11283 => 'ⱃ', + 11284 => 'ⱄ', + 11285 => 'ⱅ', + 11286 => 'ⱆ', + 11287 => 'ⱇ', + 11288 => 'ⱈ', + 11289 => 'ⱉ', + 11290 => 'ⱊ', + 11291 => 'ⱋ', + 11292 => 'ⱌ', + 11293 => 'ⱍ', + 11294 => 'ⱎ', + 11295 => 'ⱏ', + 11296 => 'ⱐ', + 11297 => 'ⱑ', + 11298 => 'ⱒ', + 11299 => 'ⱓ', + 11300 => 'ⱔ', + 11301 => 'ⱕ', + 11302 => 'ⱖ', + 11303 => 'ⱗ', + 11304 => 'ⱘ', + 11305 => 'ⱙ', + 11306 => 'ⱚ', + 11307 => 'ⱛ', + 11308 => 'ⱜ', + 11309 => 'ⱝ', + 11310 => 'ⱞ', + 11360 => 'ⱡ', + 11362 => 'ɫ', + 11363 => 'ᵽ', + 11364 => 'ɽ', + 11367 => 'ⱨ', + 11369 => 'ⱪ', + 11371 => 'ⱬ', + 11373 => 'ɑ', + 11374 => 'ɱ', + 11375 => 'ɐ', + 11376 => 'ɒ', + 11378 => 'ⱳ', + 11381 => 'ⱶ', + 11388 => 'j', + 11389 => 'v', + 11390 => 'ȿ', + 11391 => 'ɀ', + 11392 => 'ⲁ', + 11394 => 'ⲃ', + 11396 => 'ⲅ', + 11398 => 'ⲇ', + 11400 => 'ⲉ', + 11402 => 'ⲋ', + 11404 => 'ⲍ', + 11406 => 'ⲏ', + 11408 => 'ⲑ', + 11410 => 'ⲓ', + 11412 => 'ⲕ', + 11414 => 'ⲗ', + 11416 => 'ⲙ', + 11418 => 'ⲛ', + 11420 => 'ⲝ', + 11422 => 'ⲟ', + 11424 => 'ⲡ', + 11426 => 'ⲣ', + 11428 => 'ⲥ', + 11430 => 'ⲧ', + 11432 => 'ⲩ', + 11434 => 'ⲫ', + 11436 => 'ⲭ', + 11438 => 'ⲯ', + 11440 => 'ⲱ', + 11442 => 'ⲳ', + 11444 => 'ⲵ', + 11446 => 'ⲷ', + 11448 => 'ⲹ', + 11450 => 'ⲻ', + 11452 => 'ⲽ', + 11454 => 'ⲿ', + 11456 => 'ⳁ', + 11458 => 'ⳃ', + 11460 => 'ⳅ', + 11462 => 'ⳇ', + 11464 => 'ⳉ', + 11466 => 'ⳋ', + 11468 => 'ⳍ', + 11470 => 'ⳏ', + 11472 => 'ⳑ', + 11474 => 'ⳓ', + 11476 => 'ⳕ', + 11478 => 'ⳗ', + 11480 => 'ⳙ', + 11482 => 'ⳛ', + 11484 => 'ⳝ', + 11486 => 'ⳟ', + 11488 => 'ⳡ', + 11490 => 'ⳣ', + 11499 => 'ⳬ', + 11501 => 'ⳮ', + 11506 => 'ⳳ', + 11631 => 'ⵡ', + 11935 => '母', + 12019 => '龟', + 12032 => '一', + 12033 => '丨', + 12034 => '丶', + 12035 => '丿', + 12036 => '乙', + 12037 => '亅', + 12038 => '二', + 12039 => '亠', + 12040 => '人', + 12041 => '儿', + 12042 => '入', + 12043 => '八', + 12044 => '冂', + 12045 => '冖', + 12046 => '冫', + 12047 => '几', + 12048 => '凵', + 12049 => '刀', + 12050 => '力', + 12051 => '勹', + 12052 => '匕', + 12053 => '匚', + 12054 => '匸', + 12055 => '十', + 12056 => '卜', + 12057 => '卩', + 12058 => '厂', + 12059 => '厶', + 12060 => '又', + 12061 => '口', + 12062 => '囗', + 12063 => '土', + 12064 => '士', + 12065 => '夂', + 12066 => '夊', + 12067 => '夕', + 12068 => '大', + 12069 => '女', + 12070 => '子', + 12071 => '宀', + 12072 => '寸', + 12073 => '小', + 12074 => '尢', + 12075 => '尸', + 12076 => '屮', + 12077 => '山', + 12078 => '巛', + 12079 => '工', + 12080 => '己', + 12081 => '巾', + 12082 => '干', + 12083 => '幺', + 12084 => '广', + 12085 => '廴', + 12086 => '廾', + 12087 => '弋', + 12088 => '弓', + 12089 => '彐', + 12090 => '彡', + 12091 => '彳', + 12092 => '心', + 12093 => '戈', + 12094 => '戶', + 12095 => '手', + 12096 => '支', + 12097 => '攴', + 12098 => '文', + 12099 => '斗', + 12100 => '斤', + 12101 => '方', + 12102 => '无', + 12103 => '日', + 12104 => '曰', + 12105 => '月', + 12106 => '木', + 12107 => '欠', + 12108 => '止', + 12109 => '歹', + 12110 => '殳', + 12111 => '毋', + 12112 => '比', + 12113 => '毛', + 12114 => '氏', + 12115 => '气', + 12116 => '水', + 12117 => '火', + 12118 => '爪', + 12119 => '父', + 12120 => '爻', + 12121 => '爿', + 12122 => '片', + 12123 => '牙', + 12124 => '牛', + 12125 => '犬', + 12126 => '玄', + 12127 => '玉', + 12128 => '瓜', + 12129 => '瓦', + 12130 => '甘', + 12131 => '生', + 12132 => '用', + 12133 => '田', + 12134 => '疋', + 12135 => '疒', + 12136 => '癶', + 12137 => '白', + 12138 => '皮', + 12139 => '皿', + 12140 => '目', + 12141 => '矛', + 12142 => '矢', + 12143 => '石', + 12144 => '示', + 12145 => '禸', + 12146 => '禾', + 12147 => '穴', + 12148 => '立', + 12149 => '竹', + 12150 => '米', + 12151 => '糸', + 12152 => '缶', + 12153 => '网', + 12154 => '羊', + 12155 => '羽', + 12156 => '老', + 12157 => '而', + 12158 => '耒', + 12159 => '耳', + 12160 => '聿', + 12161 => '肉', + 12162 => '臣', + 12163 => '自', + 12164 => '至', + 12165 => '臼', + 12166 => '舌', + 12167 => '舛', + 12168 => '舟', + 12169 => '艮', + 12170 => '色', + 12171 => '艸', + 12172 => '虍', + 12173 => '虫', + 12174 => '血', + 12175 => '行', + 12176 => '衣', + 12177 => '襾', + 12178 => '見', + 12179 => '角', + 12180 => '言', + 12181 => '谷', + 12182 => '豆', + 12183 => '豕', + 12184 => '豸', + 12185 => '貝', + 12186 => '赤', + 12187 => '走', + 12188 => '足', + 12189 => '身', + 12190 => '車', + 12191 => '辛', + 12192 => '辰', + 12193 => '辵', + 12194 => '邑', + 12195 => '酉', + 12196 => '釆', + 12197 => '里', + 12198 => '金', + 12199 => '長', + 12200 => '門', + 12201 => '阜', + 12202 => '隶', + 12203 => '隹', + 12204 => '雨', + 12205 => '靑', + 12206 => '非', + 12207 => '面', + 12208 => '革', + 12209 => '韋', + 12210 => '韭', + 12211 => '音', + 12212 => '頁', + 12213 => '風', + 12214 => '飛', + 12215 => '食', + 12216 => '首', + 12217 => '香', + 12218 => '馬', + 12219 => '骨', + 12220 => '高', + 12221 => '髟', + 12222 => '鬥', + 12223 => '鬯', + 12224 => '鬲', + 12225 => '鬼', + 12226 => '魚', + 12227 => '鳥', + 12228 => '鹵', + 12229 => '鹿', + 12230 => '麥', + 12231 => '麻', + 12232 => '黃', + 12233 => '黍', + 12234 => '黑', + 12235 => '黹', + 12236 => '黽', + 12237 => '鼎', + 12238 => '鼓', + 12239 => '鼠', + 12240 => '鼻', + 12241 => '齊', + 12242 => '齒', + 12243 => '龍', + 12244 => '龜', + 12245 => '龠', + 12290 => '.', + 12342 => '〒', + 12344 => '十', + 12345 => '卄', + 12346 => '卅', + 12447 => 'より', + 12543 => 'コト', + 12593 => 'ᄀ', + 12594 => 'ᄁ', + 12595 => 'ᆪ', + 12596 => 'ᄂ', + 12597 => 'ᆬ', + 12598 => 'ᆭ', + 12599 => 'ᄃ', + 12600 => 'ᄄ', + 12601 => 'ᄅ', + 12602 => 'ᆰ', + 12603 => 'ᆱ', + 12604 => 'ᆲ', + 12605 => 'ᆳ', + 12606 => 'ᆴ', + 12607 => 'ᆵ', + 12608 => 'ᄚ', + 12609 => 'ᄆ', + 12610 => 'ᄇ', + 12611 => 'ᄈ', + 12612 => 'ᄡ', + 12613 => 'ᄉ', + 12614 => 'ᄊ', + 12615 => 'ᄋ', + 12616 => 'ᄌ', + 12617 => 'ᄍ', + 12618 => 'ᄎ', + 12619 => 'ᄏ', + 12620 => 'ᄐ', + 12621 => 'ᄑ', + 12622 => 'ᄒ', + 12623 => 'ᅡ', + 12624 => 'ᅢ', + 12625 => 'ᅣ', + 12626 => 'ᅤ', + 12627 => 'ᅥ', + 12628 => 'ᅦ', + 12629 => 'ᅧ', + 12630 => 'ᅨ', + 12631 => 'ᅩ', + 12632 => 'ᅪ', + 12633 => 'ᅫ', + 12634 => 'ᅬ', + 12635 => 'ᅭ', + 12636 => 'ᅮ', + 12637 => 'ᅯ', + 12638 => 'ᅰ', + 12639 => 'ᅱ', + 12640 => 'ᅲ', + 12641 => 'ᅳ', + 12642 => 'ᅴ', + 12643 => 'ᅵ', + 12645 => 'ᄔ', + 12646 => 'ᄕ', + 12647 => 'ᇇ', + 12648 => 'ᇈ', + 12649 => 'ᇌ', + 12650 => 'ᇎ', + 12651 => 'ᇓ', + 12652 => 'ᇗ', + 12653 => 'ᇙ', + 12654 => 'ᄜ', + 12655 => 'ᇝ', + 12656 => 'ᇟ', + 12657 => 'ᄝ', + 12658 => 'ᄞ', + 12659 => 'ᄠ', + 12660 => 'ᄢ', + 12661 => 'ᄣ', + 12662 => 'ᄧ', + 12663 => 'ᄩ', + 12664 => 'ᄫ', + 12665 => 'ᄬ', + 12666 => 'ᄭ', + 12667 => 'ᄮ', + 12668 => 'ᄯ', + 12669 => 'ᄲ', + 12670 => 'ᄶ', + 12671 => 'ᅀ', + 12672 => 'ᅇ', + 12673 => 'ᅌ', + 12674 => 'ᇱ', + 12675 => 'ᇲ', + 12676 => 'ᅗ', + 12677 => 'ᅘ', + 12678 => 'ᅙ', + 12679 => 'ᆄ', + 12680 => 'ᆅ', + 12681 => 'ᆈ', + 12682 => 'ᆑ', + 12683 => 'ᆒ', + 12684 => 'ᆔ', + 12685 => 'ᆞ', + 12686 => 'ᆡ', + 12690 => '一', + 12691 => '二', + 12692 => '三', + 12693 => '四', + 12694 => '上', + 12695 => '中', + 12696 => '下', + 12697 => '甲', + 12698 => '乙', + 12699 => '丙', + 12700 => '丁', + 12701 => '天', + 12702 => '地', + 12703 => '人', + 12868 => '問', + 12869 => '幼', + 12870 => '文', + 12871 => '箏', + 12880 => 'pte', + 12881 => '21', + 12882 => '22', + 12883 => '23', + 12884 => '24', + 12885 => '25', + 12886 => '26', + 12887 => '27', + 12888 => '28', + 12889 => '29', + 12890 => '30', + 12891 => '31', + 12892 => '32', + 12893 => '33', + 12894 => '34', + 12895 => '35', + 12896 => 'ᄀ', + 12897 => 'ᄂ', + 12898 => 'ᄃ', + 12899 => 'ᄅ', + 12900 => 'ᄆ', + 12901 => 'ᄇ', + 12902 => 'ᄉ', + 12903 => 'ᄋ', + 12904 => 'ᄌ', + 12905 => 'ᄎ', + 12906 => 'ᄏ', + 12907 => 'ᄐ', + 12908 => 'ᄑ', + 12909 => 'ᄒ', + 12910 => '가', + 12911 => '나', + 12912 => '다', + 12913 => '라', + 12914 => '마', + 12915 => '바', + 12916 => '사', + 12917 => '아', + 12918 => '자', + 12919 => '차', + 12920 => '카', + 12921 => '타', + 12922 => '파', + 12923 => '하', + 12924 => '참고', + 12925 => '주의', + 12926 => '우', + 12928 => '一', + 12929 => '二', + 12930 => '三', + 12931 => '四', + 12932 => '五', + 12933 => '六', + 12934 => '七', + 12935 => '八', + 12936 => '九', + 12937 => '十', + 12938 => '月', + 12939 => '火', + 12940 => '水', + 12941 => '木', + 12942 => '金', + 12943 => '土', + 12944 => '日', + 12945 => '株', + 12946 => '有', + 12947 => '社', + 12948 => '名', + 12949 => '特', + 12950 => '財', + 12951 => '祝', + 12952 => '労', + 12953 => '秘', + 12954 => '男', + 12955 => '女', + 12956 => '適', + 12957 => '優', + 12958 => '印', + 12959 => '注', + 12960 => '項', + 12961 => '休', + 12962 => '写', + 12963 => '正', + 12964 => '上', + 12965 => '中', + 12966 => '下', + 12967 => '左', + 12968 => '右', + 12969 => '医', + 12970 => '宗', + 12971 => '学', + 12972 => '監', + 12973 => '企', + 12974 => '資', + 12975 => '協', + 12976 => '夜', + 12977 => '36', + 12978 => '37', + 12979 => '38', + 12980 => '39', + 12981 => '40', + 12982 => '41', + 12983 => '42', + 12984 => '43', + 12985 => '44', + 12986 => '45', + 12987 => '46', + 12988 => '47', + 12989 => '48', + 12990 => '49', + 12991 => '50', + 12992 => '1月', + 12993 => '2月', + 12994 => '3月', + 12995 => '4月', + 12996 => '5月', + 12997 => '6月', + 12998 => '7月', + 12999 => '8月', + 13000 => '9月', + 13001 => '10月', + 13002 => '11月', + 13003 => '12月', + 13004 => 'hg', + 13005 => 'erg', + 13006 => 'ev', + 13007 => 'ltd', + 13008 => 'ア', + 13009 => 'イ', + 13010 => 'ウ', + 13011 => 'エ', + 13012 => 'オ', + 13013 => 'カ', + 13014 => 'キ', + 13015 => 'ク', + 13016 => 'ケ', + 13017 => 'コ', + 13018 => 'サ', + 13019 => 'シ', + 13020 => 'ス', + 13021 => 'セ', + 13022 => 'ソ', + 13023 => 'タ', + 13024 => 'チ', + 13025 => 'ツ', + 13026 => 'テ', + 13027 => 'ト', + 13028 => 'ナ', + 13029 => 'ニ', + 13030 => 'ヌ', + 13031 => 'ネ', + 13032 => 'ノ', + 13033 => 'ハ', + 13034 => 'ヒ', + 13035 => 'フ', + 13036 => 'ヘ', + 13037 => 'ホ', + 13038 => 'マ', + 13039 => 'ミ', + 13040 => 'ム', + 13041 => 'メ', + 13042 => 'モ', + 13043 => 'ヤ', + 13044 => 'ユ', + 13045 => 'ヨ', + 13046 => 'ラ', + 13047 => 'リ', + 13048 => 'ル', + 13049 => 'レ', + 13050 => 'ロ', + 13051 => 'ワ', + 13052 => 'ヰ', + 13053 => 'ヱ', + 13054 => 'ヲ', + 13055 => '令和', + 13056 => 'アパート', + 13057 => 'アルファ', + 13058 => 'アンペア', + 13059 => 'アール', + 13060 => 'イニング', + 13061 => 'インチ', + 13062 => 'ウォン', + 13063 => 'エスクード', + 13064 => 'エーカー', + 13065 => 'オンス', + 13066 => 'オーム', + 13067 => 'カイリ', + 13068 => 'カラット', + 13069 => 'カロリー', + 13070 => 'ガロン', + 13071 => 'ガンマ', + 13072 => 'ギガ', + 13073 => 'ギニー', + 13074 => 'キュリー', + 13075 => 'ギルダー', + 13076 => 'キロ', + 13077 => 'キログラム', + 13078 => 'キロメートル', + 13079 => 'キロワット', + 13080 => 'グラム', + 13081 => 'グラムトン', + 13082 => 'クルゼイロ', + 13083 => 'クローネ', + 13084 => 'ケース', + 13085 => 'コルナ', + 13086 => 'コーポ', + 13087 => 'サイクル', + 13088 => 'サンチーム', + 13089 => 'シリング', + 13090 => 'センチ', + 13091 => 'セント', + 13092 => 'ダース', + 13093 => 'デシ', + 13094 => 'ドル', + 13095 => 'トン', + 13096 => 'ナノ', + 13097 => 'ノット', + 13098 => 'ハイツ', + 13099 => 'パーセント', + 13100 => 'パーツ', + 13101 => 'バーレル', + 13102 => 'ピアストル', + 13103 => 'ピクル', + 13104 => 'ピコ', + 13105 => 'ビル', + 13106 => 'ファラッド', + 13107 => 'フィート', + 13108 => 'ブッシェル', + 13109 => 'フラン', + 13110 => 'ヘクタール', + 13111 => 'ペソ', + 13112 => 'ペニヒ', + 13113 => 'ヘルツ', + 13114 => 'ペンス', + 13115 => 'ページ', + 13116 => 'ベータ', + 13117 => 'ポイント', + 13118 => 'ボルト', + 13119 => 'ホン', + 13120 => 'ポンド', + 13121 => 'ホール', + 13122 => 'ホーン', + 13123 => 'マイクロ', + 13124 => 'マイル', + 13125 => 'マッハ', + 13126 => 'マルク', + 13127 => 'マンション', + 13128 => 'ミクロン', + 13129 => 'ミリ', + 13130 => 'ミリバール', + 13131 => 'メガ', + 13132 => 'メガトン', + 13133 => 'メートル', + 13134 => 'ヤード', + 13135 => 'ヤール', + 13136 => 'ユアン', + 13137 => 'リットル', + 13138 => 'リラ', + 13139 => 'ルピー', + 13140 => 'ルーブル', + 13141 => 'レム', + 13142 => 'レントゲン', + 13143 => 'ワット', + 13144 => '0点', + 13145 => '1点', + 13146 => '2点', + 13147 => '3点', + 13148 => '4点', + 13149 => '5点', + 13150 => '6点', + 13151 => '7点', + 13152 => '8点', + 13153 => '9点', + 13154 => '10点', + 13155 => '11点', + 13156 => '12点', + 13157 => '13点', + 13158 => '14点', + 13159 => '15点', + 13160 => '16点', + 13161 => '17点', + 13162 => '18点', + 13163 => '19点', + 13164 => '20点', + 13165 => '21点', + 13166 => '22点', + 13167 => '23点', + 13168 => '24点', + 13169 => 'hpa', + 13170 => 'da', + 13171 => 'au', + 13172 => 'bar', + 13173 => 'ov', + 13174 => 'pc', + 13175 => 'dm', + 13176 => 'dm2', + 13177 => 'dm3', + 13178 => 'iu', + 13179 => '平成', + 13180 => '昭和', + 13181 => '大正', + 13182 => '明治', + 13183 => '株式会社', + 13184 => 'pa', + 13185 => 'na', + 13186 => 'μa', + 13187 => 'ma', + 13188 => 'ka', + 13189 => 'kb', + 13190 => 'mb', + 13191 => 'gb', + 13192 => 'cal', + 13193 => 'kcal', + 13194 => 'pf', + 13195 => 'nf', + 13196 => 'μf', + 13197 => 'μg', + 13198 => 'mg', + 13199 => 'kg', + 13200 => 'hz', + 13201 => 'khz', + 13202 => 'mhz', + 13203 => 'ghz', + 13204 => 'thz', + 13205 => 'μl', + 13206 => 'ml', + 13207 => 'dl', + 13208 => 'kl', + 13209 => 'fm', + 13210 => 'nm', + 13211 => 'μm', + 13212 => 'mm', + 13213 => 'cm', + 13214 => 'km', + 13215 => 'mm2', + 13216 => 'cm2', + 13217 => 'm2', + 13218 => 'km2', + 13219 => 'mm3', + 13220 => 'cm3', + 13221 => 'm3', + 13222 => 'km3', + 13223 => 'm∕s', + 13224 => 'm∕s2', + 13225 => 'pa', + 13226 => 'kpa', + 13227 => 'mpa', + 13228 => 'gpa', + 13229 => 'rad', + 13230 => 'rad∕s', + 13231 => 'rad∕s2', + 13232 => 'ps', + 13233 => 'ns', + 13234 => 'μs', + 13235 => 'ms', + 13236 => 'pv', + 13237 => 'nv', + 13238 => 'μv', + 13239 => 'mv', + 13240 => 'kv', + 13241 => 'mv', + 13242 => 'pw', + 13243 => 'nw', + 13244 => 'μw', + 13245 => 'mw', + 13246 => 'kw', + 13247 => 'mw', + 13248 => 'kω', + 13249 => 'mω', + 13251 => 'bq', + 13252 => 'cc', + 13253 => 'cd', + 13254 => 'c∕kg', + 13256 => 'db', + 13257 => 'gy', + 13258 => 'ha', + 13259 => 'hp', + 13260 => 'in', + 13261 => 'kk', + 13262 => 'km', + 13263 => 'kt', + 13264 => 'lm', + 13265 => 'ln', + 13266 => 'log', + 13267 => 'lx', + 13268 => 'mb', + 13269 => 'mil', + 13270 => 'mol', + 13271 => 'ph', + 13273 => 'ppm', + 13274 => 'pr', + 13275 => 'sr', + 13276 => 'sv', + 13277 => 'wb', + 13278 => 'v∕m', + 13279 => 'a∕m', + 13280 => '1日', + 13281 => '2日', + 13282 => '3日', + 13283 => '4日', + 13284 => '5日', + 13285 => '6日', + 13286 => '7日', + 13287 => '8日', + 13288 => '9日', + 13289 => '10日', + 13290 => '11日', + 13291 => '12日', + 13292 => '13日', + 13293 => '14日', + 13294 => '15日', + 13295 => '16日', + 13296 => '17日', + 13297 => '18日', + 13298 => '19日', + 13299 => '20日', + 13300 => '21日', + 13301 => '22日', + 13302 => '23日', + 13303 => '24日', + 13304 => '25日', + 13305 => '26日', + 13306 => '27日', + 13307 => '28日', + 13308 => '29日', + 13309 => '30日', + 13310 => '31日', + 13311 => 'gal', + 42560 => 'ꙁ', + 42562 => 'ꙃ', + 42564 => 'ꙅ', + 42566 => 'ꙇ', + 42568 => 'ꙉ', + 42570 => 'ꙋ', + 42572 => 'ꙍ', + 42574 => 'ꙏ', + 42576 => 'ꙑ', + 42578 => 'ꙓ', + 42580 => 'ꙕ', + 42582 => 'ꙗ', + 42584 => 'ꙙ', + 42586 => 'ꙛ', + 42588 => 'ꙝ', + 42590 => 'ꙟ', + 42592 => 'ꙡ', + 42594 => 'ꙣ', + 42596 => 'ꙥ', + 42598 => 'ꙧ', + 42600 => 'ꙩ', + 42602 => 'ꙫ', + 42604 => 'ꙭ', + 42624 => 'ꚁ', + 42626 => 'ꚃ', + 42628 => 'ꚅ', + 42630 => 'ꚇ', + 42632 => 'ꚉ', + 42634 => 'ꚋ', + 42636 => 'ꚍ', + 42638 => 'ꚏ', + 42640 => 'ꚑ', + 42642 => 'ꚓ', + 42644 => 'ꚕ', + 42646 => 'ꚗ', + 42648 => 'ꚙ', + 42650 => 'ꚛ', + 42652 => 'ъ', + 42653 => 'ь', + 42786 => 'ꜣ', + 42788 => 'ꜥ', + 42790 => 'ꜧ', + 42792 => 'ꜩ', + 42794 => 'ꜫ', + 42796 => 'ꜭ', + 42798 => 'ꜯ', + 42802 => 'ꜳ', + 42804 => 'ꜵ', + 42806 => 'ꜷ', + 42808 => 'ꜹ', + 42810 => 'ꜻ', + 42812 => 'ꜽ', + 42814 => 'ꜿ', + 42816 => 'ꝁ', + 42818 => 'ꝃ', + 42820 => 'ꝅ', + 42822 => 'ꝇ', + 42824 => 'ꝉ', + 42826 => 'ꝋ', + 42828 => 'ꝍ', + 42830 => 'ꝏ', + 42832 => 'ꝑ', + 42834 => 'ꝓ', + 42836 => 'ꝕ', + 42838 => 'ꝗ', + 42840 => 'ꝙ', + 42842 => 'ꝛ', + 42844 => 'ꝝ', + 42846 => 'ꝟ', + 42848 => 'ꝡ', + 42850 => 'ꝣ', + 42852 => 'ꝥ', + 42854 => 'ꝧ', + 42856 => 'ꝩ', + 42858 => 'ꝫ', + 42860 => 'ꝭ', + 42862 => 'ꝯ', + 42864 => 'ꝯ', + 42873 => 'ꝺ', + 42875 => 'ꝼ', + 42877 => 'ᵹ', + 42878 => 'ꝿ', + 42880 => 'ꞁ', + 42882 => 'ꞃ', + 42884 => 'ꞅ', + 42886 => 'ꞇ', + 42891 => 'ꞌ', + 42893 => 'ɥ', + 42896 => 'ꞑ', + 42898 => 'ꞓ', + 42902 => 'ꞗ', + 42904 => 'ꞙ', + 42906 => 'ꞛ', + 42908 => 'ꞝ', + 42910 => 'ꞟ', + 42912 => 'ꞡ', + 42914 => 'ꞣ', + 42916 => 'ꞥ', + 42918 => 'ꞧ', + 42920 => 'ꞩ', + 42922 => 'ɦ', + 42923 => 'ɜ', + 42924 => 'ɡ', + 42925 => 'ɬ', + 42926 => 'ɪ', + 42928 => 'ʞ', + 42929 => 'ʇ', + 42930 => 'ʝ', + 42931 => 'ꭓ', + 42932 => 'ꞵ', + 42934 => 'ꞷ', + 42936 => 'ꞹ', + 42938 => 'ꞻ', + 42940 => 'ꞽ', + 42942 => 'ꞿ', + 42946 => 'ꟃ', + 42948 => 'ꞔ', + 42949 => 'ʂ', + 42950 => 'ᶎ', + 42951 => 'ꟈ', + 42953 => 'ꟊ', + 42997 => 'ꟶ', + 43000 => 'ħ', + 43001 => 'œ', + 43868 => 'ꜧ', + 43869 => 'ꬷ', + 43870 => 'ɫ', + 43871 => 'ꭒ', + 43881 => 'ʍ', + 43888 => 'Ꭰ', + 43889 => 'Ꭱ', + 43890 => 'Ꭲ', + 43891 => 'Ꭳ', + 43892 => 'Ꭴ', + 43893 => 'Ꭵ', + 43894 => 'Ꭶ', + 43895 => 'Ꭷ', + 43896 => 'Ꭸ', + 43897 => 'Ꭹ', + 43898 => 'Ꭺ', + 43899 => 'Ꭻ', + 43900 => 'Ꭼ', + 43901 => 'Ꭽ', + 43902 => 'Ꭾ', + 43903 => 'Ꭿ', + 43904 => 'Ꮀ', + 43905 => 'Ꮁ', + 43906 => 'Ꮂ', + 43907 => 'Ꮃ', + 43908 => 'Ꮄ', + 43909 => 'Ꮅ', + 43910 => 'Ꮆ', + 43911 => 'Ꮇ', + 43912 => 'Ꮈ', + 43913 => 'Ꮉ', + 43914 => 'Ꮊ', + 43915 => 'Ꮋ', + 43916 => 'Ꮌ', + 43917 => 'Ꮍ', + 43918 => 'Ꮎ', + 43919 => 'Ꮏ', + 43920 => 'Ꮐ', + 43921 => 'Ꮑ', + 43922 => 'Ꮒ', + 43923 => 'Ꮓ', + 43924 => 'Ꮔ', + 43925 => 'Ꮕ', + 43926 => 'Ꮖ', + 43927 => 'Ꮗ', + 43928 => 'Ꮘ', + 43929 => 'Ꮙ', + 43930 => 'Ꮚ', + 43931 => 'Ꮛ', + 43932 => 'Ꮜ', + 43933 => 'Ꮝ', + 43934 => 'Ꮞ', + 43935 => 'Ꮟ', + 43936 => 'Ꮠ', + 43937 => 'Ꮡ', + 43938 => 'Ꮢ', + 43939 => 'Ꮣ', + 43940 => 'Ꮤ', + 43941 => 'Ꮥ', + 43942 => 'Ꮦ', + 43943 => 'Ꮧ', + 43944 => 'Ꮨ', + 43945 => 'Ꮩ', + 43946 => 'Ꮪ', + 43947 => 'Ꮫ', + 43948 => 'Ꮬ', + 43949 => 'Ꮭ', + 43950 => 'Ꮮ', + 43951 => 'Ꮯ', + 43952 => 'Ꮰ', + 43953 => 'Ꮱ', + 43954 => 'Ꮲ', + 43955 => 'Ꮳ', + 43956 => 'Ꮴ', + 43957 => 'Ꮵ', + 43958 => 'Ꮶ', + 43959 => 'Ꮷ', + 43960 => 'Ꮸ', + 43961 => 'Ꮹ', + 43962 => 'Ꮺ', + 43963 => 'Ꮻ', + 43964 => 'Ꮼ', + 43965 => 'Ꮽ', + 43966 => 'Ꮾ', + 43967 => 'Ꮿ', + 63744 => '豈', + 63745 => '更', + 63746 => '車', + 63747 => '賈', + 63748 => '滑', + 63749 => '串', + 63750 => '句', + 63751 => '龜', + 63752 => '龜', + 63753 => '契', + 63754 => '金', + 63755 => '喇', + 63756 => '奈', + 63757 => '懶', + 63758 => '癩', + 63759 => '羅', + 63760 => '蘿', + 63761 => '螺', + 63762 => '裸', + 63763 => '邏', + 63764 => '樂', + 63765 => '洛', + 63766 => '烙', + 63767 => '珞', + 63768 => '落', + 63769 => '酪', + 63770 => '駱', + 63771 => '亂', + 63772 => '卵', + 63773 => '欄', + 63774 => '爛', + 63775 => '蘭', + 63776 => '鸞', + 63777 => '嵐', + 63778 => '濫', + 63779 => '藍', + 63780 => '襤', + 63781 => '拉', + 63782 => '臘', + 63783 => '蠟', + 63784 => '廊', + 63785 => '朗', + 63786 => '浪', + 63787 => '狼', + 63788 => '郎', + 63789 => '來', + 63790 => '冷', + 63791 => '勞', + 63792 => '擄', + 63793 => '櫓', + 63794 => '爐', + 63795 => '盧', + 63796 => '老', + 63797 => '蘆', + 63798 => '虜', + 63799 => '路', + 63800 => '露', + 63801 => '魯', + 63802 => '鷺', + 63803 => '碌', + 63804 => '祿', + 63805 => '綠', + 63806 => '菉', + 63807 => '錄', + 63808 => '鹿', + 63809 => '論', + 63810 => '壟', + 63811 => '弄', + 63812 => '籠', + 63813 => '聾', + 63814 => '牢', + 63815 => '磊', + 63816 => '賂', + 63817 => '雷', + 63818 => '壘', + 63819 => '屢', + 63820 => '樓', + 63821 => '淚', + 63822 => '漏', + 63823 => '累', + 63824 => '縷', + 63825 => '陋', + 63826 => '勒', + 63827 => '肋', + 63828 => '凜', + 63829 => '凌', + 63830 => '稜', + 63831 => '綾', + 63832 => '菱', + 63833 => '陵', + 63834 => '讀', + 63835 => '拏', + 63836 => '樂', + 63837 => '諾', + 63838 => '丹', + 63839 => '寧', + 63840 => '怒', + 63841 => '率', + 63842 => '異', + 63843 => '北', + 63844 => '磻', + 63845 => '便', + 63846 => '復', + 63847 => '不', + 63848 => '泌', + 63849 => '數', + 63850 => '索', + 63851 => '參', + 63852 => '塞', + 63853 => '省', + 63854 => '葉', + 63855 => '說', + 63856 => '殺', + 63857 => '辰', + 63858 => '沈', + 63859 => '拾', + 63860 => '若', + 63861 => '掠', + 63862 => '略', + 63863 => '亮', + 63864 => '兩', + 63865 => '凉', + 63866 => '梁', + 63867 => '糧', + 63868 => '良', + 63869 => '諒', + 63870 => '量', + 63871 => '勵', + 63872 => '呂', + 63873 => '女', + 63874 => '廬', + 63875 => '旅', + 63876 => '濾', + 63877 => '礪', + 63878 => '閭', + 63879 => '驪', + 63880 => '麗', + 63881 => '黎', + 63882 => '力', + 63883 => '曆', + 63884 => '歷', + 63885 => '轢', + 63886 => '年', + 63887 => '憐', + 63888 => '戀', + 63889 => '撚', + 63890 => '漣', + 63891 => '煉', + 63892 => '璉', + 63893 => '秊', + 63894 => '練', + 63895 => '聯', + 63896 => '輦', + 63897 => '蓮', + 63898 => '連', + 63899 => '鍊', + 63900 => '列', + 63901 => '劣', + 63902 => '咽', + 63903 => '烈', + 63904 => '裂', + 63905 => '說', + 63906 => '廉', + 63907 => '念', + 63908 => '捻', + 63909 => '殮', + 63910 => '簾', + 63911 => '獵', + 63912 => '令', + 63913 => '囹', + 63914 => '寧', + 63915 => '嶺', + 63916 => '怜', + 63917 => '玲', + 63918 => '瑩', + 63919 => '羚', + 63920 => '聆', + 63921 => '鈴', + 63922 => '零', + 63923 => '靈', + 63924 => '領', + 63925 => '例', + 63926 => '禮', + 63927 => '醴', + 63928 => '隸', + 63929 => '惡', + 63930 => '了', + 63931 => '僚', + 63932 => '寮', + 63933 => '尿', + 63934 => '料', + 63935 => '樂', + 63936 => '燎', + 63937 => '療', + 63938 => '蓼', + 63939 => '遼', + 63940 => '龍', + 63941 => '暈', + 63942 => '阮', + 63943 => '劉', + 63944 => '杻', + 63945 => '柳', + 63946 => '流', + 63947 => '溜', + 63948 => '琉', + 63949 => '留', + 63950 => '硫', + 63951 => '紐', + 63952 => '類', + 63953 => '六', + 63954 => '戮', + 63955 => '陸', + 63956 => '倫', + 63957 => '崙', + 63958 => '淪', + 63959 => '輪', + 63960 => '律', + 63961 => '慄', + 63962 => '栗', + 63963 => '率', + 63964 => '隆', + 63965 => '利', + 63966 => '吏', + 63967 => '履', + 63968 => '易', + 63969 => '李', + 63970 => '梨', + 63971 => '泥', + 63972 => '理', + 63973 => '痢', + 63974 => '罹', + 63975 => '裏', + 63976 => '裡', + 63977 => '里', + 63978 => '離', + 63979 => '匿', + 63980 => '溺', + 63981 => '吝', + 63982 => '燐', + 63983 => '璘', + 63984 => '藺', + 63985 => '隣', + 63986 => '鱗', + 63987 => '麟', + 63988 => '林', + 63989 => '淋', + 63990 => '臨', + 63991 => '立', + 63992 => '笠', + 63993 => '粒', + 63994 => '狀', + 63995 => '炙', + 63996 => '識', + 63997 => '什', + 63998 => '茶', + 63999 => '刺', + 64000 => '切', + 64001 => '度', + 64002 => '拓', + 64003 => '糖', + 64004 => '宅', + 64005 => '洞', + 64006 => '暴', + 64007 => '輻', + 64008 => '行', + 64009 => '降', + 64010 => '見', + 64011 => '廓', + 64012 => '兀', + 64013 => '嗀', + 64016 => '塚', + 64018 => '晴', + 64021 => '凞', + 64022 => '猪', + 64023 => '益', + 64024 => '礼', + 64025 => '神', + 64026 => '祥', + 64027 => '福', + 64028 => '靖', + 64029 => '精', + 64030 => '羽', + 64032 => '蘒', + 64034 => '諸', + 64037 => '逸', + 64038 => '都', + 64042 => '飯', + 64043 => '飼', + 64044 => '館', + 64045 => '鶴', + 64046 => '郞', + 64047 => '隷', + 64048 => '侮', + 64049 => '僧', + 64050 => '免', + 64051 => '勉', + 64052 => '勤', + 64053 => '卑', + 64054 => '喝', + 64055 => '嘆', + 64056 => '器', + 64057 => '塀', + 64058 => '墨', + 64059 => '層', + 64060 => '屮', + 64061 => '悔', + 64062 => '慨', + 64063 => '憎', + 64064 => '懲', + 64065 => '敏', + 64066 => '既', + 64067 => '暑', + 64068 => '梅', + 64069 => '海', + 64070 => '渚', + 64071 => '漢', + 64072 => '煮', + 64073 => '爫', + 64074 => '琢', + 64075 => '碑', + 64076 => '社', + 64077 => '祉', + 64078 => '祈', + 64079 => '祐', + 64080 => '祖', + 64081 => '祝', + 64082 => '禍', + 64083 => '禎', + 64084 => '穀', + 64085 => '突', + 64086 => '節', + 64087 => '練', + 64088 => '縉', + 64089 => '繁', + 64090 => '署', + 64091 => '者', + 64092 => '臭', + 64093 => '艹', + 64094 => '艹', + 64095 => '著', + 64096 => '褐', + 64097 => '視', + 64098 => '謁', + 64099 => '謹', + 64100 => '賓', + 64101 => '贈', + 64102 => '辶', + 64103 => '逸', + 64104 => '難', + 64105 => '響', + 64106 => '頻', + 64107 => '恵', + 64108 => '𤋮', + 64109 => '舘', + 64112 => '並', + 64113 => '况', + 64114 => '全', + 64115 => '侀', + 64116 => '充', + 64117 => '冀', + 64118 => '勇', + 64119 => '勺', + 64120 => '喝', + 64121 => '啕', + 64122 => '喙', + 64123 => '嗢', + 64124 => '塚', + 64125 => '墳', + 64126 => '奄', + 64127 => '奔', + 64128 => '婢', + 64129 => '嬨', + 64130 => '廒', + 64131 => '廙', + 64132 => '彩', + 64133 => '徭', + 64134 => '惘', + 64135 => '慎', + 64136 => '愈', + 64137 => '憎', + 64138 => '慠', + 64139 => '懲', + 64140 => '戴', + 64141 => '揄', + 64142 => '搜', + 64143 => '摒', + 64144 => '敖', + 64145 => '晴', + 64146 => '朗', + 64147 => '望', + 64148 => '杖', + 64149 => '歹', + 64150 => '殺', + 64151 => '流', + 64152 => '滛', + 64153 => '滋', + 64154 => '漢', + 64155 => '瀞', + 64156 => '煮', + 64157 => '瞧', + 64158 => '爵', + 64159 => '犯', + 64160 => '猪', + 64161 => '瑱', + 64162 => '甆', + 64163 => '画', + 64164 => '瘝', + 64165 => '瘟', + 64166 => '益', + 64167 => '盛', + 64168 => '直', + 64169 => '睊', + 64170 => '着', + 64171 => '磌', + 64172 => '窱', + 64173 => '節', + 64174 => '类', + 64175 => '絛', + 64176 => '練', + 64177 => '缾', + 64178 => '者', + 64179 => '荒', + 64180 => '華', + 64181 => '蝹', + 64182 => '襁', + 64183 => '覆', + 64184 => '視', + 64185 => '調', + 64186 => '諸', + 64187 => '請', + 64188 => '謁', + 64189 => '諾', + 64190 => '諭', + 64191 => '謹', + 64192 => '變', + 64193 => '贈', + 64194 => '輸', + 64195 => '遲', + 64196 => '醙', + 64197 => '鉶', + 64198 => '陼', + 64199 => '難', + 64200 => '靖', + 64201 => '韛', + 64202 => '響', + 64203 => '頋', + 64204 => '頻', + 64205 => '鬒', + 64206 => '龜', + 64207 => '𢡊', + 64208 => '𢡄', + 64209 => '𣏕', + 64210 => '㮝', + 64211 => '䀘', + 64212 => '䀹', + 64213 => '𥉉', + 64214 => '𥳐', + 64215 => '𧻓', + 64216 => '齃', + 64217 => '龎', + 64256 => 'ff', + 64257 => 'fi', + 64258 => 'fl', + 64259 => 'ffi', + 64260 => 'ffl', + 64261 => 'st', + 64262 => 'st', + 64275 => 'մն', + 64276 => 'մե', + 64277 => 'մի', + 64278 => 'վն', + 64279 => 'մխ', + 64285 => 'יִ', + 64287 => 'ײַ', + 64288 => 'ע', + 64289 => 'א', + 64290 => 'ד', + 64291 => 'ה', + 64292 => 'כ', + 64293 => 'ל', + 64294 => 'ם', + 64295 => 'ר', + 64296 => 'ת', + 64298 => 'שׁ', + 64299 => 'שׂ', + 64300 => 'שּׁ', + 64301 => 'שּׂ', + 64302 => 'אַ', + 64303 => 'אָ', + 64304 => 'אּ', + 64305 => 'בּ', + 64306 => 'גּ', + 64307 => 'דּ', + 64308 => 'הּ', + 64309 => 'וּ', + 64310 => 'זּ', + 64312 => 'טּ', + 64313 => 'יּ', + 64314 => 'ךּ', + 64315 => 'כּ', + 64316 => 'לּ', + 64318 => 'מּ', + 64320 => 'נּ', + 64321 => 'סּ', + 64323 => 'ףּ', + 64324 => 'פּ', + 64326 => 'צּ', + 64327 => 'קּ', + 64328 => 'רּ', + 64329 => 'שּ', + 64330 => 'תּ', + 64331 => 'וֹ', + 64332 => 'בֿ', + 64333 => 'כֿ', + 64334 => 'פֿ', + 64335 => 'אל', + 64336 => 'ٱ', + 64337 => 'ٱ', + 64338 => 'ٻ', + 64339 => 'ٻ', + 64340 => 'ٻ', + 64341 => 'ٻ', + 64342 => 'پ', + 64343 => 'پ', + 64344 => 'پ', + 64345 => 'پ', + 64346 => 'ڀ', + 64347 => 'ڀ', + 64348 => 'ڀ', + 64349 => 'ڀ', + 64350 => 'ٺ', + 64351 => 'ٺ', + 64352 => 'ٺ', + 64353 => 'ٺ', + 64354 => 'ٿ', + 64355 => 'ٿ', + 64356 => 'ٿ', + 64357 => 'ٿ', + 64358 => 'ٹ', + 64359 => 'ٹ', + 64360 => 'ٹ', + 64361 => 'ٹ', + 64362 => 'ڤ', + 64363 => 'ڤ', + 64364 => 'ڤ', + 64365 => 'ڤ', + 64366 => 'ڦ', + 64367 => 'ڦ', + 64368 => 'ڦ', + 64369 => 'ڦ', + 64370 => 'ڄ', + 64371 => 'ڄ', + 64372 => 'ڄ', + 64373 => 'ڄ', + 64374 => 'ڃ', + 64375 => 'ڃ', + 64376 => 'ڃ', + 64377 => 'ڃ', + 64378 => 'چ', + 64379 => 'چ', + 64380 => 'چ', + 64381 => 'چ', + 64382 => 'ڇ', + 64383 => 'ڇ', + 64384 => 'ڇ', + 64385 => 'ڇ', + 64386 => 'ڍ', + 64387 => 'ڍ', + 64388 => 'ڌ', + 64389 => 'ڌ', + 64390 => 'ڎ', + 64391 => 'ڎ', + 64392 => 'ڈ', + 64393 => 'ڈ', + 64394 => 'ژ', + 64395 => 'ژ', + 64396 => 'ڑ', + 64397 => 'ڑ', + 64398 => 'ک', + 64399 => 'ک', + 64400 => 'ک', + 64401 => 'ک', + 64402 => 'گ', + 64403 => 'گ', + 64404 => 'گ', + 64405 => 'گ', + 64406 => 'ڳ', + 64407 => 'ڳ', + 64408 => 'ڳ', + 64409 => 'ڳ', + 64410 => 'ڱ', + 64411 => 'ڱ', + 64412 => 'ڱ', + 64413 => 'ڱ', + 64414 => 'ں', + 64415 => 'ں', + 64416 => 'ڻ', + 64417 => 'ڻ', + 64418 => 'ڻ', + 64419 => 'ڻ', + 64420 => 'ۀ', + 64421 => 'ۀ', + 64422 => 'ہ', + 64423 => 'ہ', + 64424 => 'ہ', + 64425 => 'ہ', + 64426 => 'ھ', + 64427 => 'ھ', + 64428 => 'ھ', + 64429 => 'ھ', + 64430 => 'ے', + 64431 => 'ے', + 64432 => 'ۓ', + 64433 => 'ۓ', + 64467 => 'ڭ', + 64468 => 'ڭ', + 64469 => 'ڭ', + 64470 => 'ڭ', + 64471 => 'ۇ', + 64472 => 'ۇ', + 64473 => 'ۆ', + 64474 => 'ۆ', + 64475 => 'ۈ', + 64476 => 'ۈ', + 64477 => 'ۇٴ', + 64478 => 'ۋ', + 64479 => 'ۋ', + 64480 => 'ۅ', + 64481 => 'ۅ', + 64482 => 'ۉ', + 64483 => 'ۉ', + 64484 => 'ې', + 64485 => 'ې', + 64486 => 'ې', + 64487 => 'ې', + 64488 => 'ى', + 64489 => 'ى', + 64490 => 'ئا', + 64491 => 'ئا', + 64492 => 'ئە', + 64493 => 'ئە', + 64494 => 'ئو', + 64495 => 'ئو', + 64496 => 'ئۇ', + 64497 => 'ئۇ', + 64498 => 'ئۆ', + 64499 => 'ئۆ', + 64500 => 'ئۈ', + 64501 => 'ئۈ', + 64502 => 'ئې', + 64503 => 'ئې', + 64504 => 'ئې', + 64505 => 'ئى', + 64506 => 'ئى', + 64507 => 'ئى', + 64508 => 'ی', + 64509 => 'ی', + 64510 => 'ی', + 64511 => 'ی', + 64512 => 'ئج', + 64513 => 'ئح', + 64514 => 'ئم', + 64515 => 'ئى', + 64516 => 'ئي', + 64517 => 'بج', + 64518 => 'بح', + 64519 => 'بخ', + 64520 => 'بم', + 64521 => 'بى', + 64522 => 'بي', + 64523 => 'تج', + 64524 => 'تح', + 64525 => 'تخ', + 64526 => 'تم', + 64527 => 'تى', + 64528 => 'تي', + 64529 => 'ثج', + 64530 => 'ثم', + 64531 => 'ثى', + 64532 => 'ثي', + 64533 => 'جح', + 64534 => 'جم', + 64535 => 'حج', + 64536 => 'حم', + 64537 => 'خج', + 64538 => 'خح', + 64539 => 'خم', + 64540 => 'سج', + 64541 => 'سح', + 64542 => 'سخ', + 64543 => 'سم', + 64544 => 'صح', + 64545 => 'صم', + 64546 => 'ضج', + 64547 => 'ضح', + 64548 => 'ضخ', + 64549 => 'ضم', + 64550 => 'طح', + 64551 => 'طم', + 64552 => 'ظم', + 64553 => 'عج', + 64554 => 'عم', + 64555 => 'غج', + 64556 => 'غم', + 64557 => 'فج', + 64558 => 'فح', + 64559 => 'فخ', + 64560 => 'فم', + 64561 => 'فى', + 64562 => 'في', + 64563 => 'قح', + 64564 => 'قم', + 64565 => 'قى', + 64566 => 'قي', + 64567 => 'كا', + 64568 => 'كج', + 64569 => 'كح', + 64570 => 'كخ', + 64571 => 'كل', + 64572 => 'كم', + 64573 => 'كى', + 64574 => 'كي', + 64575 => 'لج', + 64576 => 'لح', + 64577 => 'لخ', + 64578 => 'لم', + 64579 => 'لى', + 64580 => 'لي', + 64581 => 'مج', + 64582 => 'مح', + 64583 => 'مخ', + 64584 => 'مم', + 64585 => 'مى', + 64586 => 'مي', + 64587 => 'نج', + 64588 => 'نح', + 64589 => 'نخ', + 64590 => 'نم', + 64591 => 'نى', + 64592 => 'ني', + 64593 => 'هج', + 64594 => 'هم', + 64595 => 'هى', + 64596 => 'هي', + 64597 => 'يج', + 64598 => 'يح', + 64599 => 'يخ', + 64600 => 'يم', + 64601 => 'يى', + 64602 => 'يي', + 64603 => 'ذٰ', + 64604 => 'رٰ', + 64605 => 'ىٰ', + 64612 => 'ئر', + 64613 => 'ئز', + 64614 => 'ئم', + 64615 => 'ئن', + 64616 => 'ئى', + 64617 => 'ئي', + 64618 => 'بر', + 64619 => 'بز', + 64620 => 'بم', + 64621 => 'بن', + 64622 => 'بى', + 64623 => 'بي', + 64624 => 'تر', + 64625 => 'تز', + 64626 => 'تم', + 64627 => 'تن', + 64628 => 'تى', + 64629 => 'تي', + 64630 => 'ثر', + 64631 => 'ثز', + 64632 => 'ثم', + 64633 => 'ثن', + 64634 => 'ثى', + 64635 => 'ثي', + 64636 => 'فى', + 64637 => 'في', + 64638 => 'قى', + 64639 => 'قي', + 64640 => 'كا', + 64641 => 'كل', + 64642 => 'كم', + 64643 => 'كى', + 64644 => 'كي', + 64645 => 'لم', + 64646 => 'لى', + 64647 => 'لي', + 64648 => 'ما', + 64649 => 'مم', + 64650 => 'نر', + 64651 => 'نز', + 64652 => 'نم', + 64653 => 'نن', + 64654 => 'نى', + 64655 => 'ني', + 64656 => 'ىٰ', + 64657 => 'ير', + 64658 => 'يز', + 64659 => 'يم', + 64660 => 'ين', + 64661 => 'يى', + 64662 => 'يي', + 64663 => 'ئج', + 64664 => 'ئح', + 64665 => 'ئخ', + 64666 => 'ئم', + 64667 => 'ئه', + 64668 => 'بج', + 64669 => 'بح', + 64670 => 'بخ', + 64671 => 'بم', + 64672 => 'به', + 64673 => 'تج', + 64674 => 'تح', + 64675 => 'تخ', + 64676 => 'تم', + 64677 => 'ته', + 64678 => 'ثم', + 64679 => 'جح', + 64680 => 'جم', + 64681 => 'حج', + 64682 => 'حم', + 64683 => 'خج', + 64684 => 'خم', + 64685 => 'سج', + 64686 => 'سح', + 64687 => 'سخ', + 64688 => 'سم', + 64689 => 'صح', + 64690 => 'صخ', + 64691 => 'صم', + 64692 => 'ضج', + 64693 => 'ضح', + 64694 => 'ضخ', + 64695 => 'ضم', + 64696 => 'طح', + 64697 => 'ظم', + 64698 => 'عج', + 64699 => 'عم', + 64700 => 'غج', + 64701 => 'غم', + 64702 => 'فج', + 64703 => 'فح', + 64704 => 'فخ', + 64705 => 'فم', + 64706 => 'قح', + 64707 => 'قم', + 64708 => 'كج', + 64709 => 'كح', + 64710 => 'كخ', + 64711 => 'كل', + 64712 => 'كم', + 64713 => 'لج', + 64714 => 'لح', + 64715 => 'لخ', + 64716 => 'لم', + 64717 => 'له', + 64718 => 'مج', + 64719 => 'مح', + 64720 => 'مخ', + 64721 => 'مم', + 64722 => 'نج', + 64723 => 'نح', + 64724 => 'نخ', + 64725 => 'نم', + 64726 => 'نه', + 64727 => 'هج', + 64728 => 'هم', + 64729 => 'هٰ', + 64730 => 'يج', + 64731 => 'يح', + 64732 => 'يخ', + 64733 => 'يم', + 64734 => 'يه', + 64735 => 'ئم', + 64736 => 'ئه', + 64737 => 'بم', + 64738 => 'به', + 64739 => 'تم', + 64740 => 'ته', + 64741 => 'ثم', + 64742 => 'ثه', + 64743 => 'سم', + 64744 => 'سه', + 64745 => 'شم', + 64746 => 'شه', + 64747 => 'كل', + 64748 => 'كم', + 64749 => 'لم', + 64750 => 'نم', + 64751 => 'نه', + 64752 => 'يم', + 64753 => 'يه', + 64754 => 'ـَّ', + 64755 => 'ـُّ', + 64756 => 'ـِّ', + 64757 => 'طى', + 64758 => 'طي', + 64759 => 'عى', + 64760 => 'عي', + 64761 => 'غى', + 64762 => 'غي', + 64763 => 'سى', + 64764 => 'سي', + 64765 => 'شى', + 64766 => 'شي', + 64767 => 'حى', + 64768 => 'حي', + 64769 => 'جى', + 64770 => 'جي', + 64771 => 'خى', + 64772 => 'خي', + 64773 => 'صى', + 64774 => 'صي', + 64775 => 'ضى', + 64776 => 'ضي', + 64777 => 'شج', + 64778 => 'شح', + 64779 => 'شخ', + 64780 => 'شم', + 64781 => 'شر', + 64782 => 'سر', + 64783 => 'صر', + 64784 => 'ضر', + 64785 => 'طى', + 64786 => 'طي', + 64787 => 'عى', + 64788 => 'عي', + 64789 => 'غى', + 64790 => 'غي', + 64791 => 'سى', + 64792 => 'سي', + 64793 => 'شى', + 64794 => 'شي', + 64795 => 'حى', + 64796 => 'حي', + 64797 => 'جى', + 64798 => 'جي', + 64799 => 'خى', + 64800 => 'خي', + 64801 => 'صى', + 64802 => 'صي', + 64803 => 'ضى', + 64804 => 'ضي', + 64805 => 'شج', + 64806 => 'شح', + 64807 => 'شخ', + 64808 => 'شم', + 64809 => 'شر', + 64810 => 'سر', + 64811 => 'صر', + 64812 => 'ضر', + 64813 => 'شج', + 64814 => 'شح', + 64815 => 'شخ', + 64816 => 'شم', + 64817 => 'سه', + 64818 => 'شه', + 64819 => 'طم', + 64820 => 'سج', + 64821 => 'سح', + 64822 => 'سخ', + 64823 => 'شج', + 64824 => 'شح', + 64825 => 'شخ', + 64826 => 'طم', + 64827 => 'ظم', + 64828 => 'اً', + 64829 => 'اً', + 64848 => 'تجم', + 64849 => 'تحج', + 64850 => 'تحج', + 64851 => 'تحم', + 64852 => 'تخم', + 64853 => 'تمج', + 64854 => 'تمح', + 64855 => 'تمخ', + 64856 => 'جمح', + 64857 => 'جمح', + 64858 => 'حمي', + 64859 => 'حمى', + 64860 => 'سحج', + 64861 => 'سجح', + 64862 => 'سجى', + 64863 => 'سمح', + 64864 => 'سمح', + 64865 => 'سمج', + 64866 => 'سمم', + 64867 => 'سمم', + 64868 => 'صحح', + 64869 => 'صحح', + 64870 => 'صمم', + 64871 => 'شحم', + 64872 => 'شحم', + 64873 => 'شجي', + 64874 => 'شمخ', + 64875 => 'شمخ', + 64876 => 'شمم', + 64877 => 'شمم', + 64878 => 'ضحى', + 64879 => 'ضخم', + 64880 => 'ضخم', + 64881 => 'طمح', + 64882 => 'طمح', + 64883 => 'طمم', + 64884 => 'طمي', + 64885 => 'عجم', + 64886 => 'عمم', + 64887 => 'عمم', + 64888 => 'عمى', + 64889 => 'غمم', + 64890 => 'غمي', + 64891 => 'غمى', + 64892 => 'فخم', + 64893 => 'فخم', + 64894 => 'قمح', + 64895 => 'قمم', + 64896 => 'لحم', + 64897 => 'لحي', + 64898 => 'لحى', + 64899 => 'لجج', + 64900 => 'لجج', + 64901 => 'لخم', + 64902 => 'لخم', + 64903 => 'لمح', + 64904 => 'لمح', + 64905 => 'محج', + 64906 => 'محم', + 64907 => 'محي', + 64908 => 'مجح', + 64909 => 'مجم', + 64910 => 'مخج', + 64911 => 'مخم', + 64914 => 'مجخ', + 64915 => 'همج', + 64916 => 'همم', + 64917 => 'نحم', + 64918 => 'نحى', + 64919 => 'نجم', + 64920 => 'نجم', + 64921 => 'نجى', + 64922 => 'نمي', + 64923 => 'نمى', + 64924 => 'يمم', + 64925 => 'يمم', + 64926 => 'بخي', + 64927 => 'تجي', + 64928 => 'تجى', + 64929 => 'تخي', + 64930 => 'تخى', + 64931 => 'تمي', + 64932 => 'تمى', + 64933 => 'جمي', + 64934 => 'جحى', + 64935 => 'جمى', + 64936 => 'سخى', + 64937 => 'صحي', + 64938 => 'شحي', + 64939 => 'ضحي', + 64940 => 'لجي', + 64941 => 'لمي', + 64942 => 'يحي', + 64943 => 'يجي', + 64944 => 'يمي', + 64945 => 'ممي', + 64946 => 'قمي', + 64947 => 'نحي', + 64948 => 'قمح', + 64949 => 'لحم', + 64950 => 'عمي', + 64951 => 'كمي', + 64952 => 'نجح', + 64953 => 'مخي', + 64954 => 'لجم', + 64955 => 'كمم', + 64956 => 'لجم', + 64957 => 'نجح', + 64958 => 'جحي', + 64959 => 'حجي', + 64960 => 'مجي', + 64961 => 'فمي', + 64962 => 'بحي', + 64963 => 'كمم', + 64964 => 'عجم', + 64965 => 'صمم', + 64966 => 'سخي', + 64967 => 'نجي', + 65008 => 'صلے', + 65009 => 'قلے', + 65010 => 'الله', + 65011 => 'اكبر', + 65012 => 'محمد', + 65013 => 'صلعم', + 65014 => 'رسول', + 65015 => 'عليه', + 65016 => 'وسلم', + 65017 => 'صلى', + 65020 => 'ریال', + 65041 => '、', + 65047 => '〖', + 65048 => '〗', + 65073 => '—', + 65074 => '–', + 65081 => '〔', + 65082 => '〕', + 65083 => '【', + 65084 => '】', + 65085 => '《', + 65086 => '》', + 65087 => '〈', + 65088 => '〉', + 65089 => '「', + 65090 => '」', + 65091 => '『', + 65092 => '』', + 65105 => '、', + 65112 => '—', + 65117 => '〔', + 65118 => '〕', + 65123 => '-', + 65137 => 'ـً', + 65143 => 'ـَ', + 65145 => 'ـُ', + 65147 => 'ـِ', + 65149 => 'ـّ', + 65151 => 'ـْ', + 65152 => 'ء', + 65153 => 'آ', + 65154 => 'آ', + 65155 => 'أ', + 65156 => 'أ', + 65157 => 'ؤ', + 65158 => 'ؤ', + 65159 => 'إ', + 65160 => 'إ', + 65161 => 'ئ', + 65162 => 'ئ', + 65163 => 'ئ', + 65164 => 'ئ', + 65165 => 'ا', + 65166 => 'ا', + 65167 => 'ب', + 65168 => 'ب', + 65169 => 'ب', + 65170 => 'ب', + 65171 => 'ة', + 65172 => 'ة', + 65173 => 'ت', + 65174 => 'ت', + 65175 => 'ت', + 65176 => 'ت', + 65177 => 'ث', + 65178 => 'ث', + 65179 => 'ث', + 65180 => 'ث', + 65181 => 'ج', + 65182 => 'ج', + 65183 => 'ج', + 65184 => 'ج', + 65185 => 'ح', + 65186 => 'ح', + 65187 => 'ح', + 65188 => 'ح', + 65189 => 'خ', + 65190 => 'خ', + 65191 => 'خ', + 65192 => 'خ', + 65193 => 'د', + 65194 => 'د', + 65195 => 'ذ', + 65196 => 'ذ', + 65197 => 'ر', + 65198 => 'ر', + 65199 => 'ز', + 65200 => 'ز', + 65201 => 'س', + 65202 => 'س', + 65203 => 'س', + 65204 => 'س', + 65205 => 'ش', + 65206 => 'ش', + 65207 => 'ش', + 65208 => 'ش', + 65209 => 'ص', + 65210 => 'ص', + 65211 => 'ص', + 65212 => 'ص', + 65213 => 'ض', + 65214 => 'ض', + 65215 => 'ض', + 65216 => 'ض', + 65217 => 'ط', + 65218 => 'ط', + 65219 => 'ط', + 65220 => 'ط', + 65221 => 'ظ', + 65222 => 'ظ', + 65223 => 'ظ', + 65224 => 'ظ', + 65225 => 'ع', + 65226 => 'ع', + 65227 => 'ع', + 65228 => 'ع', + 65229 => 'غ', + 65230 => 'غ', + 65231 => 'غ', + 65232 => 'غ', + 65233 => 'ف', + 65234 => 'ف', + 65235 => 'ف', + 65236 => 'ف', + 65237 => 'ق', + 65238 => 'ق', + 65239 => 'ق', + 65240 => 'ق', + 65241 => 'ك', + 65242 => 'ك', + 65243 => 'ك', + 65244 => 'ك', + 65245 => 'ل', + 65246 => 'ل', + 65247 => 'ل', + 65248 => 'ل', + 65249 => 'م', + 65250 => 'م', + 65251 => 'م', + 65252 => 'م', + 65253 => 'ن', + 65254 => 'ن', + 65255 => 'ن', + 65256 => 'ن', + 65257 => 'ه', + 65258 => 'ه', + 65259 => 'ه', + 65260 => 'ه', + 65261 => 'و', + 65262 => 'و', + 65263 => 'ى', + 65264 => 'ى', + 65265 => 'ي', + 65266 => 'ي', + 65267 => 'ي', + 65268 => 'ي', + 65269 => 'لآ', + 65270 => 'لآ', + 65271 => 'لأ', + 65272 => 'لأ', + 65273 => 'لإ', + 65274 => 'لإ', + 65275 => 'لا', + 65276 => 'لا', + 65293 => '-', + 65294 => '.', + 65296 => '0', + 65297 => '1', + 65298 => '2', + 65299 => '3', + 65300 => '4', + 65301 => '5', + 65302 => '6', + 65303 => '7', + 65304 => '8', + 65305 => '9', + 65313 => 'a', + 65314 => 'b', + 65315 => 'c', + 65316 => 'd', + 65317 => 'e', + 65318 => 'f', + 65319 => 'g', + 65320 => 'h', + 65321 => 'i', + 65322 => 'j', + 65323 => 'k', + 65324 => 'l', + 65325 => 'm', + 65326 => 'n', + 65327 => 'o', + 65328 => 'p', + 65329 => 'q', + 65330 => 'r', + 65331 => 's', + 65332 => 't', + 65333 => 'u', + 65334 => 'v', + 65335 => 'w', + 65336 => 'x', + 65337 => 'y', + 65338 => 'z', + 65345 => 'a', + 65346 => 'b', + 65347 => 'c', + 65348 => 'd', + 65349 => 'e', + 65350 => 'f', + 65351 => 'g', + 65352 => 'h', + 65353 => 'i', + 65354 => 'j', + 65355 => 'k', + 65356 => 'l', + 65357 => 'm', + 65358 => 'n', + 65359 => 'o', + 65360 => 'p', + 65361 => 'q', + 65362 => 'r', + 65363 => 's', + 65364 => 't', + 65365 => 'u', + 65366 => 'v', + 65367 => 'w', + 65368 => 'x', + 65369 => 'y', + 65370 => 'z', + 65375 => '⦅', + 65376 => '⦆', + 65377 => '.', + 65378 => '「', + 65379 => '」', + 65380 => '、', + 65381 => '・', + 65382 => 'ヲ', + 65383 => 'ァ', + 65384 => 'ィ', + 65385 => 'ゥ', + 65386 => 'ェ', + 65387 => 'ォ', + 65388 => 'ャ', + 65389 => 'ュ', + 65390 => 'ョ', + 65391 => 'ッ', + 65392 => 'ー', + 65393 => 'ア', + 65394 => 'イ', + 65395 => 'ウ', + 65396 => 'エ', + 65397 => 'オ', + 65398 => 'カ', + 65399 => 'キ', + 65400 => 'ク', + 65401 => 'ケ', + 65402 => 'コ', + 65403 => 'サ', + 65404 => 'シ', + 65405 => 'ス', + 65406 => 'セ', + 65407 => 'ソ', + 65408 => 'タ', + 65409 => 'チ', + 65410 => 'ツ', + 65411 => 'テ', + 65412 => 'ト', + 65413 => 'ナ', + 65414 => 'ニ', + 65415 => 'ヌ', + 65416 => 'ネ', + 65417 => 'ノ', + 65418 => 'ハ', + 65419 => 'ヒ', + 65420 => 'フ', + 65421 => 'ヘ', + 65422 => 'ホ', + 65423 => 'マ', + 65424 => 'ミ', + 65425 => 'ム', + 65426 => 'メ', + 65427 => 'モ', + 65428 => 'ヤ', + 65429 => 'ユ', + 65430 => 'ヨ', + 65431 => 'ラ', + 65432 => 'リ', + 65433 => 'ル', + 65434 => 'レ', + 65435 => 'ロ', + 65436 => 'ワ', + 65437 => 'ン', + 65438 => '゙', + 65439 => '゚', + 65441 => 'ᄀ', + 65442 => 'ᄁ', + 65443 => 'ᆪ', + 65444 => 'ᄂ', + 65445 => 'ᆬ', + 65446 => 'ᆭ', + 65447 => 'ᄃ', + 65448 => 'ᄄ', + 65449 => 'ᄅ', + 65450 => 'ᆰ', + 65451 => 'ᆱ', + 65452 => 'ᆲ', + 65453 => 'ᆳ', + 65454 => 'ᆴ', + 65455 => 'ᆵ', + 65456 => 'ᄚ', + 65457 => 'ᄆ', + 65458 => 'ᄇ', + 65459 => 'ᄈ', + 65460 => 'ᄡ', + 65461 => 'ᄉ', + 65462 => 'ᄊ', + 65463 => 'ᄋ', + 65464 => 'ᄌ', + 65465 => 'ᄍ', + 65466 => 'ᄎ', + 65467 => 'ᄏ', + 65468 => 'ᄐ', + 65469 => 'ᄑ', + 65470 => 'ᄒ', + 65474 => 'ᅡ', + 65475 => 'ᅢ', + 65476 => 'ᅣ', + 65477 => 'ᅤ', + 65478 => 'ᅥ', + 65479 => 'ᅦ', + 65482 => 'ᅧ', + 65483 => 'ᅨ', + 65484 => 'ᅩ', + 65485 => 'ᅪ', + 65486 => 'ᅫ', + 65487 => 'ᅬ', + 65490 => 'ᅭ', + 65491 => 'ᅮ', + 65492 => 'ᅯ', + 65493 => 'ᅰ', + 65494 => 'ᅱ', + 65495 => 'ᅲ', + 65498 => 'ᅳ', + 65499 => 'ᅴ', + 65500 => 'ᅵ', + 65504 => '¢', + 65505 => '£', + 65506 => '¬', + 65508 => '¦', + 65509 => '¥', + 65510 => '₩', + 65512 => '│', + 65513 => '←', + 65514 => '↑', + 65515 => '→', + 65516 => '↓', + 65517 => '■', + 65518 => '○', + 66560 => '𐐨', + 66561 => '𐐩', + 66562 => '𐐪', + 66563 => '𐐫', + 66564 => '𐐬', + 66565 => '𐐭', + 66566 => '𐐮', + 66567 => '𐐯', + 66568 => '𐐰', + 66569 => '𐐱', + 66570 => '𐐲', + 66571 => '𐐳', + 66572 => '𐐴', + 66573 => '𐐵', + 66574 => '𐐶', + 66575 => '𐐷', + 66576 => '𐐸', + 66577 => '𐐹', + 66578 => '𐐺', + 66579 => '𐐻', + 66580 => '𐐼', + 66581 => '𐐽', + 66582 => '𐐾', + 66583 => '𐐿', + 66584 => '𐑀', + 66585 => '𐑁', + 66586 => '𐑂', + 66587 => '𐑃', + 66588 => '𐑄', + 66589 => '𐑅', + 66590 => '𐑆', + 66591 => '𐑇', + 66592 => '𐑈', + 66593 => '𐑉', + 66594 => '𐑊', + 66595 => '𐑋', + 66596 => '𐑌', + 66597 => '𐑍', + 66598 => '𐑎', + 66599 => '𐑏', + 66736 => '𐓘', + 66737 => '𐓙', + 66738 => '𐓚', + 66739 => '𐓛', + 66740 => '𐓜', + 66741 => '𐓝', + 66742 => '𐓞', + 66743 => '𐓟', + 66744 => '𐓠', + 66745 => '𐓡', + 66746 => '𐓢', + 66747 => '𐓣', + 66748 => '𐓤', + 66749 => '𐓥', + 66750 => '𐓦', + 66751 => '𐓧', + 66752 => '𐓨', + 66753 => '𐓩', + 66754 => '𐓪', + 66755 => '𐓫', + 66756 => '𐓬', + 66757 => '𐓭', + 66758 => '𐓮', + 66759 => '𐓯', + 66760 => '𐓰', + 66761 => '𐓱', + 66762 => '𐓲', + 66763 => '𐓳', + 66764 => '𐓴', + 66765 => '𐓵', + 66766 => '𐓶', + 66767 => '𐓷', + 66768 => '𐓸', + 66769 => '𐓹', + 66770 => '𐓺', + 66771 => '𐓻', + 68736 => '𐳀', + 68737 => '𐳁', + 68738 => '𐳂', + 68739 => '𐳃', + 68740 => '𐳄', + 68741 => '𐳅', + 68742 => '𐳆', + 68743 => '𐳇', + 68744 => '𐳈', + 68745 => '𐳉', + 68746 => '𐳊', + 68747 => '𐳋', + 68748 => '𐳌', + 68749 => '𐳍', + 68750 => '𐳎', + 68751 => '𐳏', + 68752 => '𐳐', + 68753 => '𐳑', + 68754 => '𐳒', + 68755 => '𐳓', + 68756 => '𐳔', + 68757 => '𐳕', + 68758 => '𐳖', + 68759 => '𐳗', + 68760 => '𐳘', + 68761 => '𐳙', + 68762 => '𐳚', + 68763 => '𐳛', + 68764 => '𐳜', + 68765 => '𐳝', + 68766 => '𐳞', + 68767 => '𐳟', + 68768 => '𐳠', + 68769 => '𐳡', + 68770 => '𐳢', + 68771 => '𐳣', + 68772 => '𐳤', + 68773 => '𐳥', + 68774 => '𐳦', + 68775 => '𐳧', + 68776 => '𐳨', + 68777 => '𐳩', + 68778 => '𐳪', + 68779 => '𐳫', + 68780 => '𐳬', + 68781 => '𐳭', + 68782 => '𐳮', + 68783 => '𐳯', + 68784 => '𐳰', + 68785 => '𐳱', + 68786 => '𐳲', + 71840 => '𑣀', + 71841 => '𑣁', + 71842 => '𑣂', + 71843 => '𑣃', + 71844 => '𑣄', + 71845 => '𑣅', + 71846 => '𑣆', + 71847 => '𑣇', + 71848 => '𑣈', + 71849 => '𑣉', + 71850 => '𑣊', + 71851 => '𑣋', + 71852 => '𑣌', + 71853 => '𑣍', + 71854 => '𑣎', + 71855 => '𑣏', + 71856 => '𑣐', + 71857 => '𑣑', + 71858 => '𑣒', + 71859 => '𑣓', + 71860 => '𑣔', + 71861 => '𑣕', + 71862 => '𑣖', + 71863 => '𑣗', + 71864 => '𑣘', + 71865 => '𑣙', + 71866 => '𑣚', + 71867 => '𑣛', + 71868 => '𑣜', + 71869 => '𑣝', + 71870 => '𑣞', + 71871 => '𑣟', + 93760 => '𖹠', + 93761 => '𖹡', + 93762 => '𖹢', + 93763 => '𖹣', + 93764 => '𖹤', + 93765 => '𖹥', + 93766 => '𖹦', + 93767 => '𖹧', + 93768 => '𖹨', + 93769 => '𖹩', + 93770 => '𖹪', + 93771 => '𖹫', + 93772 => '𖹬', + 93773 => '𖹭', + 93774 => '𖹮', + 93775 => '𖹯', + 93776 => '𖹰', + 93777 => '𖹱', + 93778 => '𖹲', + 93779 => '𖹳', + 93780 => '𖹴', + 93781 => '𖹵', + 93782 => '𖹶', + 93783 => '𖹷', + 93784 => '𖹸', + 93785 => '𖹹', + 93786 => '𖹺', + 93787 => '𖹻', + 93788 => '𖹼', + 93789 => '𖹽', + 93790 => '𖹾', + 93791 => '𖹿', + 119134 => '𝅗𝅥', + 119135 => '𝅘𝅥', + 119136 => '𝅘𝅥𝅮', + 119137 => '𝅘𝅥𝅯', + 119138 => '𝅘𝅥𝅰', + 119139 => '𝅘𝅥𝅱', + 119140 => '𝅘𝅥𝅲', + 119227 => '𝆹𝅥', + 119228 => '𝆺𝅥', + 119229 => '𝆹𝅥𝅮', + 119230 => '𝆺𝅥𝅮', + 119231 => '𝆹𝅥𝅯', + 119232 => '𝆺𝅥𝅯', + 119808 => 'a', + 119809 => 'b', + 119810 => 'c', + 119811 => 'd', + 119812 => 'e', + 119813 => 'f', + 119814 => 'g', + 119815 => 'h', + 119816 => 'i', + 119817 => 'j', + 119818 => 'k', + 119819 => 'l', + 119820 => 'm', + 119821 => 'n', + 119822 => 'o', + 119823 => 'p', + 119824 => 'q', + 119825 => 'r', + 119826 => 's', + 119827 => 't', + 119828 => 'u', + 119829 => 'v', + 119830 => 'w', + 119831 => 'x', + 119832 => 'y', + 119833 => 'z', + 119834 => 'a', + 119835 => 'b', + 119836 => 'c', + 119837 => 'd', + 119838 => 'e', + 119839 => 'f', + 119840 => 'g', + 119841 => 'h', + 119842 => 'i', + 119843 => 'j', + 119844 => 'k', + 119845 => 'l', + 119846 => 'm', + 119847 => 'n', + 119848 => 'o', + 119849 => 'p', + 119850 => 'q', + 119851 => 'r', + 119852 => 's', + 119853 => 't', + 119854 => 'u', + 119855 => 'v', + 119856 => 'w', + 119857 => 'x', + 119858 => 'y', + 119859 => 'z', + 119860 => 'a', + 119861 => 'b', + 119862 => 'c', + 119863 => 'd', + 119864 => 'e', + 119865 => 'f', + 119866 => 'g', + 119867 => 'h', + 119868 => 'i', + 119869 => 'j', + 119870 => 'k', + 119871 => 'l', + 119872 => 'm', + 119873 => 'n', + 119874 => 'o', + 119875 => 'p', + 119876 => 'q', + 119877 => 'r', + 119878 => 's', + 119879 => 't', + 119880 => 'u', + 119881 => 'v', + 119882 => 'w', + 119883 => 'x', + 119884 => 'y', + 119885 => 'z', + 119886 => 'a', + 119887 => 'b', + 119888 => 'c', + 119889 => 'd', + 119890 => 'e', + 119891 => 'f', + 119892 => 'g', + 119894 => 'i', + 119895 => 'j', + 119896 => 'k', + 119897 => 'l', + 119898 => 'm', + 119899 => 'n', + 119900 => 'o', + 119901 => 'p', + 119902 => 'q', + 119903 => 'r', + 119904 => 's', + 119905 => 't', + 119906 => 'u', + 119907 => 'v', + 119908 => 'w', + 119909 => 'x', + 119910 => 'y', + 119911 => 'z', + 119912 => 'a', + 119913 => 'b', + 119914 => 'c', + 119915 => 'd', + 119916 => 'e', + 119917 => 'f', + 119918 => 'g', + 119919 => 'h', + 119920 => 'i', + 119921 => 'j', + 119922 => 'k', + 119923 => 'l', + 119924 => 'm', + 119925 => 'n', + 119926 => 'o', + 119927 => 'p', + 119928 => 'q', + 119929 => 'r', + 119930 => 's', + 119931 => 't', + 119932 => 'u', + 119933 => 'v', + 119934 => 'w', + 119935 => 'x', + 119936 => 'y', + 119937 => 'z', + 119938 => 'a', + 119939 => 'b', + 119940 => 'c', + 119941 => 'd', + 119942 => 'e', + 119943 => 'f', + 119944 => 'g', + 119945 => 'h', + 119946 => 'i', + 119947 => 'j', + 119948 => 'k', + 119949 => 'l', + 119950 => 'm', + 119951 => 'n', + 119952 => 'o', + 119953 => 'p', + 119954 => 'q', + 119955 => 'r', + 119956 => 's', + 119957 => 't', + 119958 => 'u', + 119959 => 'v', + 119960 => 'w', + 119961 => 'x', + 119962 => 'y', + 119963 => 'z', + 119964 => 'a', + 119966 => 'c', + 119967 => 'd', + 119970 => 'g', + 119973 => 'j', + 119974 => 'k', + 119977 => 'n', + 119978 => 'o', + 119979 => 'p', + 119980 => 'q', + 119982 => 's', + 119983 => 't', + 119984 => 'u', + 119985 => 'v', + 119986 => 'w', + 119987 => 'x', + 119988 => 'y', + 119989 => 'z', + 119990 => 'a', + 119991 => 'b', + 119992 => 'c', + 119993 => 'd', + 119995 => 'f', + 119997 => 'h', + 119998 => 'i', + 119999 => 'j', + 120000 => 'k', + 120001 => 'l', + 120002 => 'm', + 120003 => 'n', + 120005 => 'p', + 120006 => 'q', + 120007 => 'r', + 120008 => 's', + 120009 => 't', + 120010 => 'u', + 120011 => 'v', + 120012 => 'w', + 120013 => 'x', + 120014 => 'y', + 120015 => 'z', + 120016 => 'a', + 120017 => 'b', + 120018 => 'c', + 120019 => 'd', + 120020 => 'e', + 120021 => 'f', + 120022 => 'g', + 120023 => 'h', + 120024 => 'i', + 120025 => 'j', + 120026 => 'k', + 120027 => 'l', + 120028 => 'm', + 120029 => 'n', + 120030 => 'o', + 120031 => 'p', + 120032 => 'q', + 120033 => 'r', + 120034 => 's', + 120035 => 't', + 120036 => 'u', + 120037 => 'v', + 120038 => 'w', + 120039 => 'x', + 120040 => 'y', + 120041 => 'z', + 120042 => 'a', + 120043 => 'b', + 120044 => 'c', + 120045 => 'd', + 120046 => 'e', + 120047 => 'f', + 120048 => 'g', + 120049 => 'h', + 120050 => 'i', + 120051 => 'j', + 120052 => 'k', + 120053 => 'l', + 120054 => 'm', + 120055 => 'n', + 120056 => 'o', + 120057 => 'p', + 120058 => 'q', + 120059 => 'r', + 120060 => 's', + 120061 => 't', + 120062 => 'u', + 120063 => 'v', + 120064 => 'w', + 120065 => 'x', + 120066 => 'y', + 120067 => 'z', + 120068 => 'a', + 120069 => 'b', + 120071 => 'd', + 120072 => 'e', + 120073 => 'f', + 120074 => 'g', + 120077 => 'j', + 120078 => 'k', + 120079 => 'l', + 120080 => 'm', + 120081 => 'n', + 120082 => 'o', + 120083 => 'p', + 120084 => 'q', + 120086 => 's', + 120087 => 't', + 120088 => 'u', + 120089 => 'v', + 120090 => 'w', + 120091 => 'x', + 120092 => 'y', + 120094 => 'a', + 120095 => 'b', + 120096 => 'c', + 120097 => 'd', + 120098 => 'e', + 120099 => 'f', + 120100 => 'g', + 120101 => 'h', + 120102 => 'i', + 120103 => 'j', + 120104 => 'k', + 120105 => 'l', + 120106 => 'm', + 120107 => 'n', + 120108 => 'o', + 120109 => 'p', + 120110 => 'q', + 120111 => 'r', + 120112 => 's', + 120113 => 't', + 120114 => 'u', + 120115 => 'v', + 120116 => 'w', + 120117 => 'x', + 120118 => 'y', + 120119 => 'z', + 120120 => 'a', + 120121 => 'b', + 120123 => 'd', + 120124 => 'e', + 120125 => 'f', + 120126 => 'g', + 120128 => 'i', + 120129 => 'j', + 120130 => 'k', + 120131 => 'l', + 120132 => 'm', + 120134 => 'o', + 120138 => 's', + 120139 => 't', + 120140 => 'u', + 120141 => 'v', + 120142 => 'w', + 120143 => 'x', + 120144 => 'y', + 120146 => 'a', + 120147 => 'b', + 120148 => 'c', + 120149 => 'd', + 120150 => 'e', + 120151 => 'f', + 120152 => 'g', + 120153 => 'h', + 120154 => 'i', + 120155 => 'j', + 120156 => 'k', + 120157 => 'l', + 120158 => 'm', + 120159 => 'n', + 120160 => 'o', + 120161 => 'p', + 120162 => 'q', + 120163 => 'r', + 120164 => 's', + 120165 => 't', + 120166 => 'u', + 120167 => 'v', + 120168 => 'w', + 120169 => 'x', + 120170 => 'y', + 120171 => 'z', + 120172 => 'a', + 120173 => 'b', + 120174 => 'c', + 120175 => 'd', + 120176 => 'e', + 120177 => 'f', + 120178 => 'g', + 120179 => 'h', + 120180 => 'i', + 120181 => 'j', + 120182 => 'k', + 120183 => 'l', + 120184 => 'm', + 120185 => 'n', + 120186 => 'o', + 120187 => 'p', + 120188 => 'q', + 120189 => 'r', + 120190 => 's', + 120191 => 't', + 120192 => 'u', + 120193 => 'v', + 120194 => 'w', + 120195 => 'x', + 120196 => 'y', + 120197 => 'z', + 120198 => 'a', + 120199 => 'b', + 120200 => 'c', + 120201 => 'd', + 120202 => 'e', + 120203 => 'f', + 120204 => 'g', + 120205 => 'h', + 120206 => 'i', + 120207 => 'j', + 120208 => 'k', + 120209 => 'l', + 120210 => 'm', + 120211 => 'n', + 120212 => 'o', + 120213 => 'p', + 120214 => 'q', + 120215 => 'r', + 120216 => 's', + 120217 => 't', + 120218 => 'u', + 120219 => 'v', + 120220 => 'w', + 120221 => 'x', + 120222 => 'y', + 120223 => 'z', + 120224 => 'a', + 120225 => 'b', + 120226 => 'c', + 120227 => 'd', + 120228 => 'e', + 120229 => 'f', + 120230 => 'g', + 120231 => 'h', + 120232 => 'i', + 120233 => 'j', + 120234 => 'k', + 120235 => 'l', + 120236 => 'm', + 120237 => 'n', + 120238 => 'o', + 120239 => 'p', + 120240 => 'q', + 120241 => 'r', + 120242 => 's', + 120243 => 't', + 120244 => 'u', + 120245 => 'v', + 120246 => 'w', + 120247 => 'x', + 120248 => 'y', + 120249 => 'z', + 120250 => 'a', + 120251 => 'b', + 120252 => 'c', + 120253 => 'd', + 120254 => 'e', + 120255 => 'f', + 120256 => 'g', + 120257 => 'h', + 120258 => 'i', + 120259 => 'j', + 120260 => 'k', + 120261 => 'l', + 120262 => 'm', + 120263 => 'n', + 120264 => 'o', + 120265 => 'p', + 120266 => 'q', + 120267 => 'r', + 120268 => 's', + 120269 => 't', + 120270 => 'u', + 120271 => 'v', + 120272 => 'w', + 120273 => 'x', + 120274 => 'y', + 120275 => 'z', + 120276 => 'a', + 120277 => 'b', + 120278 => 'c', + 120279 => 'd', + 120280 => 'e', + 120281 => 'f', + 120282 => 'g', + 120283 => 'h', + 120284 => 'i', + 120285 => 'j', + 120286 => 'k', + 120287 => 'l', + 120288 => 'm', + 120289 => 'n', + 120290 => 'o', + 120291 => 'p', + 120292 => 'q', + 120293 => 'r', + 120294 => 's', + 120295 => 't', + 120296 => 'u', + 120297 => 'v', + 120298 => 'w', + 120299 => 'x', + 120300 => 'y', + 120301 => 'z', + 120302 => 'a', + 120303 => 'b', + 120304 => 'c', + 120305 => 'd', + 120306 => 'e', + 120307 => 'f', + 120308 => 'g', + 120309 => 'h', + 120310 => 'i', + 120311 => 'j', + 120312 => 'k', + 120313 => 'l', + 120314 => 'm', + 120315 => 'n', + 120316 => 'o', + 120317 => 'p', + 120318 => 'q', + 120319 => 'r', + 120320 => 's', + 120321 => 't', + 120322 => 'u', + 120323 => 'v', + 120324 => 'w', + 120325 => 'x', + 120326 => 'y', + 120327 => 'z', + 120328 => 'a', + 120329 => 'b', + 120330 => 'c', + 120331 => 'd', + 120332 => 'e', + 120333 => 'f', + 120334 => 'g', + 120335 => 'h', + 120336 => 'i', + 120337 => 'j', + 120338 => 'k', + 120339 => 'l', + 120340 => 'm', + 120341 => 'n', + 120342 => 'o', + 120343 => 'p', + 120344 => 'q', + 120345 => 'r', + 120346 => 's', + 120347 => 't', + 120348 => 'u', + 120349 => 'v', + 120350 => 'w', + 120351 => 'x', + 120352 => 'y', + 120353 => 'z', + 120354 => 'a', + 120355 => 'b', + 120356 => 'c', + 120357 => 'd', + 120358 => 'e', + 120359 => 'f', + 120360 => 'g', + 120361 => 'h', + 120362 => 'i', + 120363 => 'j', + 120364 => 'k', + 120365 => 'l', + 120366 => 'm', + 120367 => 'n', + 120368 => 'o', + 120369 => 'p', + 120370 => 'q', + 120371 => 'r', + 120372 => 's', + 120373 => 't', + 120374 => 'u', + 120375 => 'v', + 120376 => 'w', + 120377 => 'x', + 120378 => 'y', + 120379 => 'z', + 120380 => 'a', + 120381 => 'b', + 120382 => 'c', + 120383 => 'd', + 120384 => 'e', + 120385 => 'f', + 120386 => 'g', + 120387 => 'h', + 120388 => 'i', + 120389 => 'j', + 120390 => 'k', + 120391 => 'l', + 120392 => 'm', + 120393 => 'n', + 120394 => 'o', + 120395 => 'p', + 120396 => 'q', + 120397 => 'r', + 120398 => 's', + 120399 => 't', + 120400 => 'u', + 120401 => 'v', + 120402 => 'w', + 120403 => 'x', + 120404 => 'y', + 120405 => 'z', + 120406 => 'a', + 120407 => 'b', + 120408 => 'c', + 120409 => 'd', + 120410 => 'e', + 120411 => 'f', + 120412 => 'g', + 120413 => 'h', + 120414 => 'i', + 120415 => 'j', + 120416 => 'k', + 120417 => 'l', + 120418 => 'm', + 120419 => 'n', + 120420 => 'o', + 120421 => 'p', + 120422 => 'q', + 120423 => 'r', + 120424 => 's', + 120425 => 't', + 120426 => 'u', + 120427 => 'v', + 120428 => 'w', + 120429 => 'x', + 120430 => 'y', + 120431 => 'z', + 120432 => 'a', + 120433 => 'b', + 120434 => 'c', + 120435 => 'd', + 120436 => 'e', + 120437 => 'f', + 120438 => 'g', + 120439 => 'h', + 120440 => 'i', + 120441 => 'j', + 120442 => 'k', + 120443 => 'l', + 120444 => 'm', + 120445 => 'n', + 120446 => 'o', + 120447 => 'p', + 120448 => 'q', + 120449 => 'r', + 120450 => 's', + 120451 => 't', + 120452 => 'u', + 120453 => 'v', + 120454 => 'w', + 120455 => 'x', + 120456 => 'y', + 120457 => 'z', + 120458 => 'a', + 120459 => 'b', + 120460 => 'c', + 120461 => 'd', + 120462 => 'e', + 120463 => 'f', + 120464 => 'g', + 120465 => 'h', + 120466 => 'i', + 120467 => 'j', + 120468 => 'k', + 120469 => 'l', + 120470 => 'm', + 120471 => 'n', + 120472 => 'o', + 120473 => 'p', + 120474 => 'q', + 120475 => 'r', + 120476 => 's', + 120477 => 't', + 120478 => 'u', + 120479 => 'v', + 120480 => 'w', + 120481 => 'x', + 120482 => 'y', + 120483 => 'z', + 120484 => 'ı', + 120485 => 'ȷ', + 120488 => 'α', + 120489 => 'β', + 120490 => 'γ', + 120491 => 'δ', + 120492 => 'ε', + 120493 => 'ζ', + 120494 => 'η', + 120495 => 'θ', + 120496 => 'ι', + 120497 => 'κ', + 120498 => 'λ', + 120499 => 'μ', + 120500 => 'ν', + 120501 => 'ξ', + 120502 => 'ο', + 120503 => 'π', + 120504 => 'ρ', + 120505 => 'θ', + 120506 => 'σ', + 120507 => 'τ', + 120508 => 'υ', + 120509 => 'φ', + 120510 => 'χ', + 120511 => 'ψ', + 120512 => 'ω', + 120513 => '∇', + 120514 => 'α', + 120515 => 'β', + 120516 => 'γ', + 120517 => 'δ', + 120518 => 'ε', + 120519 => 'ζ', + 120520 => 'η', + 120521 => 'θ', + 120522 => 'ι', + 120523 => 'κ', + 120524 => 'λ', + 120525 => 'μ', + 120526 => 'ν', + 120527 => 'ξ', + 120528 => 'ο', + 120529 => 'π', + 120530 => 'ρ', + 120531 => 'σ', + 120532 => 'σ', + 120533 => 'τ', + 120534 => 'υ', + 120535 => 'φ', + 120536 => 'χ', + 120537 => 'ψ', + 120538 => 'ω', + 120539 => '∂', + 120540 => 'ε', + 120541 => 'θ', + 120542 => 'κ', + 120543 => 'φ', + 120544 => 'ρ', + 120545 => 'π', + 120546 => 'α', + 120547 => 'β', + 120548 => 'γ', + 120549 => 'δ', + 120550 => 'ε', + 120551 => 'ζ', + 120552 => 'η', + 120553 => 'θ', + 120554 => 'ι', + 120555 => 'κ', + 120556 => 'λ', + 120557 => 'μ', + 120558 => 'ν', + 120559 => 'ξ', + 120560 => 'ο', + 120561 => 'π', + 120562 => 'ρ', + 120563 => 'θ', + 120564 => 'σ', + 120565 => 'τ', + 120566 => 'υ', + 120567 => 'φ', + 120568 => 'χ', + 120569 => 'ψ', + 120570 => 'ω', + 120571 => '∇', + 120572 => 'α', + 120573 => 'β', + 120574 => 'γ', + 120575 => 'δ', + 120576 => 'ε', + 120577 => 'ζ', + 120578 => 'η', + 120579 => 'θ', + 120580 => 'ι', + 120581 => 'κ', + 120582 => 'λ', + 120583 => 'μ', + 120584 => 'ν', + 120585 => 'ξ', + 120586 => 'ο', + 120587 => 'π', + 120588 => 'ρ', + 120589 => 'σ', + 120590 => 'σ', + 120591 => 'τ', + 120592 => 'υ', + 120593 => 'φ', + 120594 => 'χ', + 120595 => 'ψ', + 120596 => 'ω', + 120597 => '∂', + 120598 => 'ε', + 120599 => 'θ', + 120600 => 'κ', + 120601 => 'φ', + 120602 => 'ρ', + 120603 => 'π', + 120604 => 'α', + 120605 => 'β', + 120606 => 'γ', + 120607 => 'δ', + 120608 => 'ε', + 120609 => 'ζ', + 120610 => 'η', + 120611 => 'θ', + 120612 => 'ι', + 120613 => 'κ', + 120614 => 'λ', + 120615 => 'μ', + 120616 => 'ν', + 120617 => 'ξ', + 120618 => 'ο', + 120619 => 'π', + 120620 => 'ρ', + 120621 => 'θ', + 120622 => 'σ', + 120623 => 'τ', + 120624 => 'υ', + 120625 => 'φ', + 120626 => 'χ', + 120627 => 'ψ', + 120628 => 'ω', + 120629 => '∇', + 120630 => 'α', + 120631 => 'β', + 120632 => 'γ', + 120633 => 'δ', + 120634 => 'ε', + 120635 => 'ζ', + 120636 => 'η', + 120637 => 'θ', + 120638 => 'ι', + 120639 => 'κ', + 120640 => 'λ', + 120641 => 'μ', + 120642 => 'ν', + 120643 => 'ξ', + 120644 => 'ο', + 120645 => 'π', + 120646 => 'ρ', + 120647 => 'σ', + 120648 => 'σ', + 120649 => 'τ', + 120650 => 'υ', + 120651 => 'φ', + 120652 => 'χ', + 120653 => 'ψ', + 120654 => 'ω', + 120655 => '∂', + 120656 => 'ε', + 120657 => 'θ', + 120658 => 'κ', + 120659 => 'φ', + 120660 => 'ρ', + 120661 => 'π', + 120662 => 'α', + 120663 => 'β', + 120664 => 'γ', + 120665 => 'δ', + 120666 => 'ε', + 120667 => 'ζ', + 120668 => 'η', + 120669 => 'θ', + 120670 => 'ι', + 120671 => 'κ', + 120672 => 'λ', + 120673 => 'μ', + 120674 => 'ν', + 120675 => 'ξ', + 120676 => 'ο', + 120677 => 'π', + 120678 => 'ρ', + 120679 => 'θ', + 120680 => 'σ', + 120681 => 'τ', + 120682 => 'υ', + 120683 => 'φ', + 120684 => 'χ', + 120685 => 'ψ', + 120686 => 'ω', + 120687 => '∇', + 120688 => 'α', + 120689 => 'β', + 120690 => 'γ', + 120691 => 'δ', + 120692 => 'ε', + 120693 => 'ζ', + 120694 => 'η', + 120695 => 'θ', + 120696 => 'ι', + 120697 => 'κ', + 120698 => 'λ', + 120699 => 'μ', + 120700 => 'ν', + 120701 => 'ξ', + 120702 => 'ο', + 120703 => 'π', + 120704 => 'ρ', + 120705 => 'σ', + 120706 => 'σ', + 120707 => 'τ', + 120708 => 'υ', + 120709 => 'φ', + 120710 => 'χ', + 120711 => 'ψ', + 120712 => 'ω', + 120713 => '∂', + 120714 => 'ε', + 120715 => 'θ', + 120716 => 'κ', + 120717 => 'φ', + 120718 => 'ρ', + 120719 => 'π', + 120720 => 'α', + 120721 => 'β', + 120722 => 'γ', + 120723 => 'δ', + 120724 => 'ε', + 120725 => 'ζ', + 120726 => 'η', + 120727 => 'θ', + 120728 => 'ι', + 120729 => 'κ', + 120730 => 'λ', + 120731 => 'μ', + 120732 => 'ν', + 120733 => 'ξ', + 120734 => 'ο', + 120735 => 'π', + 120736 => 'ρ', + 120737 => 'θ', + 120738 => 'σ', + 120739 => 'τ', + 120740 => 'υ', + 120741 => 'φ', + 120742 => 'χ', + 120743 => 'ψ', + 120744 => 'ω', + 120745 => '∇', + 120746 => 'α', + 120747 => 'β', + 120748 => 'γ', + 120749 => 'δ', + 120750 => 'ε', + 120751 => 'ζ', + 120752 => 'η', + 120753 => 'θ', + 120754 => 'ι', + 120755 => 'κ', + 120756 => 'λ', + 120757 => 'μ', + 120758 => 'ν', + 120759 => 'ξ', + 120760 => 'ο', + 120761 => 'π', + 120762 => 'ρ', + 120763 => 'σ', + 120764 => 'σ', + 120765 => 'τ', + 120766 => 'υ', + 120767 => 'φ', + 120768 => 'χ', + 120769 => 'ψ', + 120770 => 'ω', + 120771 => '∂', + 120772 => 'ε', + 120773 => 'θ', + 120774 => 'κ', + 120775 => 'φ', + 120776 => 'ρ', + 120777 => 'π', + 120778 => 'ϝ', + 120779 => 'ϝ', + 120782 => '0', + 120783 => '1', + 120784 => '2', + 120785 => '3', + 120786 => '4', + 120787 => '5', + 120788 => '6', + 120789 => '7', + 120790 => '8', + 120791 => '9', + 120792 => '0', + 120793 => '1', + 120794 => '2', + 120795 => '3', + 120796 => '4', + 120797 => '5', + 120798 => '6', + 120799 => '7', + 120800 => '8', + 120801 => '9', + 120802 => '0', + 120803 => '1', + 120804 => '2', + 120805 => '3', + 120806 => '4', + 120807 => '5', + 120808 => '6', + 120809 => '7', + 120810 => '8', + 120811 => '9', + 120812 => '0', + 120813 => '1', + 120814 => '2', + 120815 => '3', + 120816 => '4', + 120817 => '5', + 120818 => '6', + 120819 => '7', + 120820 => '8', + 120821 => '9', + 120822 => '0', + 120823 => '1', + 120824 => '2', + 120825 => '3', + 120826 => '4', + 120827 => '5', + 120828 => '6', + 120829 => '7', + 120830 => '8', + 120831 => '9', + 125184 => '𞤢', + 125185 => '𞤣', + 125186 => '𞤤', + 125187 => '𞤥', + 125188 => '𞤦', + 125189 => '𞤧', + 125190 => '𞤨', + 125191 => '𞤩', + 125192 => '𞤪', + 125193 => '𞤫', + 125194 => '𞤬', + 125195 => '𞤭', + 125196 => '𞤮', + 125197 => '𞤯', + 125198 => '𞤰', + 125199 => '𞤱', + 125200 => '𞤲', + 125201 => '𞤳', + 125202 => '𞤴', + 125203 => '𞤵', + 125204 => '𞤶', + 125205 => '𞤷', + 125206 => '𞤸', + 125207 => '𞤹', + 125208 => '𞤺', + 125209 => '𞤻', + 125210 => '𞤼', + 125211 => '𞤽', + 125212 => '𞤾', + 125213 => '𞤿', + 125214 => '𞥀', + 125215 => '𞥁', + 125216 => '𞥂', + 125217 => '𞥃', + 126464 => 'ا', + 126465 => 'ب', + 126466 => 'ج', + 126467 => 'د', + 126469 => 'و', + 126470 => 'ز', + 126471 => 'ح', + 126472 => 'ط', + 126473 => 'ي', + 126474 => 'ك', + 126475 => 'ل', + 126476 => 'م', + 126477 => 'ن', + 126478 => 'س', + 126479 => 'ع', + 126480 => 'ف', + 126481 => 'ص', + 126482 => 'ق', + 126483 => 'ر', + 126484 => 'ش', + 126485 => 'ت', + 126486 => 'ث', + 126487 => 'خ', + 126488 => 'ذ', + 126489 => 'ض', + 126490 => 'ظ', + 126491 => 'غ', + 126492 => 'ٮ', + 126493 => 'ں', + 126494 => 'ڡ', + 126495 => 'ٯ', + 126497 => 'ب', + 126498 => 'ج', + 126500 => 'ه', + 126503 => 'ح', + 126505 => 'ي', + 126506 => 'ك', + 126507 => 'ل', + 126508 => 'م', + 126509 => 'ن', + 126510 => 'س', + 126511 => 'ع', + 126512 => 'ف', + 126513 => 'ص', + 126514 => 'ق', + 126516 => 'ش', + 126517 => 'ت', + 126518 => 'ث', + 126519 => 'خ', + 126521 => 'ض', + 126523 => 'غ', + 126530 => 'ج', + 126535 => 'ح', + 126537 => 'ي', + 126539 => 'ل', + 126541 => 'ن', + 126542 => 'س', + 126543 => 'ع', + 126545 => 'ص', + 126546 => 'ق', + 126548 => 'ش', + 126551 => 'خ', + 126553 => 'ض', + 126555 => 'غ', + 126557 => 'ں', + 126559 => 'ٯ', + 126561 => 'ب', + 126562 => 'ج', + 126564 => 'ه', + 126567 => 'ح', + 126568 => 'ط', + 126569 => 'ي', + 126570 => 'ك', + 126572 => 'م', + 126573 => 'ن', + 126574 => 'س', + 126575 => 'ع', + 126576 => 'ف', + 126577 => 'ص', + 126578 => 'ق', + 126580 => 'ش', + 126581 => 'ت', + 126582 => 'ث', + 126583 => 'خ', + 126585 => 'ض', + 126586 => 'ظ', + 126587 => 'غ', + 126588 => 'ٮ', + 126590 => 'ڡ', + 126592 => 'ا', + 126593 => 'ب', + 126594 => 'ج', + 126595 => 'د', + 126596 => 'ه', + 126597 => 'و', + 126598 => 'ز', + 126599 => 'ح', + 126600 => 'ط', + 126601 => 'ي', + 126603 => 'ل', + 126604 => 'م', + 126605 => 'ن', + 126606 => 'س', + 126607 => 'ع', + 126608 => 'ف', + 126609 => 'ص', + 126610 => 'ق', + 126611 => 'ر', + 126612 => 'ش', + 126613 => 'ت', + 126614 => 'ث', + 126615 => 'خ', + 126616 => 'ذ', + 126617 => 'ض', + 126618 => 'ظ', + 126619 => 'غ', + 126625 => 'ب', + 126626 => 'ج', + 126627 => 'د', + 126629 => 'و', + 126630 => 'ز', + 126631 => 'ح', + 126632 => 'ط', + 126633 => 'ي', + 126635 => 'ل', + 126636 => 'م', + 126637 => 'ن', + 126638 => 'س', + 126639 => 'ع', + 126640 => 'ف', + 126641 => 'ص', + 126642 => 'ق', + 126643 => 'ر', + 126644 => 'ش', + 126645 => 'ت', + 126646 => 'ث', + 126647 => 'خ', + 126648 => 'ذ', + 126649 => 'ض', + 126650 => 'ظ', + 126651 => 'غ', + 127274 => '〔s〕', + 127275 => 'c', + 127276 => 'r', + 127277 => 'cd', + 127278 => 'wz', + 127280 => 'a', + 127281 => 'b', + 127282 => 'c', + 127283 => 'd', + 127284 => 'e', + 127285 => 'f', + 127286 => 'g', + 127287 => 'h', + 127288 => 'i', + 127289 => 'j', + 127290 => 'k', + 127291 => 'l', + 127292 => 'm', + 127293 => 'n', + 127294 => 'o', + 127295 => 'p', + 127296 => 'q', + 127297 => 'r', + 127298 => 's', + 127299 => 't', + 127300 => 'u', + 127301 => 'v', + 127302 => 'w', + 127303 => 'x', + 127304 => 'y', + 127305 => 'z', + 127306 => 'hv', + 127307 => 'mv', + 127308 => 'sd', + 127309 => 'ss', + 127310 => 'ppv', + 127311 => 'wc', + 127338 => 'mc', + 127339 => 'md', + 127340 => 'mr', + 127376 => 'dj', + 127488 => 'ほか', + 127489 => 'ココ', + 127490 => 'サ', + 127504 => '手', + 127505 => '字', + 127506 => '双', + 127507 => 'デ', + 127508 => '二', + 127509 => '多', + 127510 => '解', + 127511 => '天', + 127512 => '交', + 127513 => '映', + 127514 => '無', + 127515 => '料', + 127516 => '前', + 127517 => '後', + 127518 => '再', + 127519 => '新', + 127520 => '初', + 127521 => '終', + 127522 => '生', + 127523 => '販', + 127524 => '声', + 127525 => '吹', + 127526 => '演', + 127527 => '投', + 127528 => '捕', + 127529 => '一', + 127530 => '三', + 127531 => '遊', + 127532 => '左', + 127533 => '中', + 127534 => '右', + 127535 => '指', + 127536 => '走', + 127537 => '打', + 127538 => '禁', + 127539 => '空', + 127540 => '合', + 127541 => '満', + 127542 => '有', + 127543 => '月', + 127544 => '申', + 127545 => '割', + 127546 => '営', + 127547 => '配', + 127552 => '〔本〕', + 127553 => '〔三〕', + 127554 => '〔二〕', + 127555 => '〔安〕', + 127556 => '〔点〕', + 127557 => '〔打〕', + 127558 => '〔盗〕', + 127559 => '〔勝〕', + 127560 => '〔敗〕', + 127568 => '得', + 127569 => '可', + 130032 => '0', + 130033 => '1', + 130034 => '2', + 130035 => '3', + 130036 => '4', + 130037 => '5', + 130038 => '6', + 130039 => '7', + 130040 => '8', + 130041 => '9', + 194560 => '丽', + 194561 => '丸', + 194562 => '乁', + 194563 => '𠄢', + 194564 => '你', + 194565 => '侮', + 194566 => '侻', + 194567 => '倂', + 194568 => '偺', + 194569 => '備', + 194570 => '僧', + 194571 => '像', + 194572 => '㒞', + 194573 => '𠘺', + 194574 => '免', + 194575 => '兔', + 194576 => '兤', + 194577 => '具', + 194578 => '𠔜', + 194579 => '㒹', + 194580 => '內', + 194581 => '再', + 194582 => '𠕋', + 194583 => '冗', + 194584 => '冤', + 194585 => '仌', + 194586 => '冬', + 194587 => '况', + 194588 => '𩇟', + 194589 => '凵', + 194590 => '刃', + 194591 => '㓟', + 194592 => '刻', + 194593 => '剆', + 194594 => '割', + 194595 => '剷', + 194596 => '㔕', + 194597 => '勇', + 194598 => '勉', + 194599 => '勤', + 194600 => '勺', + 194601 => '包', + 194602 => '匆', + 194603 => '北', + 194604 => '卉', + 194605 => '卑', + 194606 => '博', + 194607 => '即', + 194608 => '卽', + 194609 => '卿', + 194610 => '卿', + 194611 => '卿', + 194612 => '𠨬', + 194613 => '灰', + 194614 => '及', + 194615 => '叟', + 194616 => '𠭣', + 194617 => '叫', + 194618 => '叱', + 194619 => '吆', + 194620 => '咞', + 194621 => '吸', + 194622 => '呈', + 194623 => '周', + 194624 => '咢', + 194625 => '哶', + 194626 => '唐', + 194627 => '啓', + 194628 => '啣', + 194629 => '善', + 194630 => '善', + 194631 => '喙', + 194632 => '喫', + 194633 => '喳', + 194634 => '嗂', + 194635 => '圖', + 194636 => '嘆', + 194637 => '圗', + 194638 => '噑', + 194639 => '噴', + 194640 => '切', + 194641 => '壮', + 194642 => '城', + 194643 => '埴', + 194644 => '堍', + 194645 => '型', + 194646 => '堲', + 194647 => '報', + 194648 => '墬', + 194649 => '𡓤', + 194650 => '売', + 194651 => '壷', + 194652 => '夆', + 194653 => '多', + 194654 => '夢', + 194655 => '奢', + 194656 => '𡚨', + 194657 => '𡛪', + 194658 => '姬', + 194659 => '娛', + 194660 => '娧', + 194661 => '姘', + 194662 => '婦', + 194663 => '㛮', + 194665 => '嬈', + 194666 => '嬾', + 194667 => '嬾', + 194668 => '𡧈', + 194669 => '寃', + 194670 => '寘', + 194671 => '寧', + 194672 => '寳', + 194673 => '𡬘', + 194674 => '寿', + 194675 => '将', + 194677 => '尢', + 194678 => '㞁', + 194679 => '屠', + 194680 => '屮', + 194681 => '峀', + 194682 => '岍', + 194683 => '𡷤', + 194684 => '嵃', + 194685 => '𡷦', + 194686 => '嵮', + 194687 => '嵫', + 194688 => '嵼', + 194689 => '巡', + 194690 => '巢', + 194691 => '㠯', + 194692 => '巽', + 194693 => '帨', + 194694 => '帽', + 194695 => '幩', + 194696 => '㡢', + 194697 => '𢆃', + 194698 => '㡼', + 194699 => '庰', + 194700 => '庳', + 194701 => '庶', + 194702 => '廊', + 194703 => '𪎒', + 194704 => '廾', + 194705 => '𢌱', + 194706 => '𢌱', + 194707 => '舁', + 194708 => '弢', + 194709 => '弢', + 194710 => '㣇', + 194711 => '𣊸', + 194712 => '𦇚', + 194713 => '形', + 194714 => '彫', + 194715 => '㣣', + 194716 => '徚', + 194717 => '忍', + 194718 => '志', + 194719 => '忹', + 194720 => '悁', + 194721 => '㤺', + 194722 => '㤜', + 194723 => '悔', + 194724 => '𢛔', + 194725 => '惇', + 194726 => '慈', + 194727 => '慌', + 194728 => '慎', + 194729 => '慌', + 194730 => '慺', + 194731 => '憎', + 194732 => '憲', + 194733 => '憤', + 194734 => '憯', + 194735 => '懞', + 194736 => '懲', + 194737 => '懶', + 194738 => '成', + 194739 => '戛', + 194740 => '扝', + 194741 => '抱', + 194742 => '拔', + 194743 => '捐', + 194744 => '𢬌', + 194745 => '挽', + 194746 => '拼', + 194747 => '捨', + 194748 => '掃', + 194749 => '揤', + 194750 => '𢯱', + 194751 => '搢', + 194752 => '揅', + 194753 => '掩', + 194754 => '㨮', + 194755 => '摩', + 194756 => '摾', + 194757 => '撝', + 194758 => '摷', + 194759 => '㩬', + 194760 => '敏', + 194761 => '敬', + 194762 => '𣀊', + 194763 => '旣', + 194764 => '書', + 194765 => '晉', + 194766 => '㬙', + 194767 => '暑', + 194768 => '㬈', + 194769 => '㫤', + 194770 => '冒', + 194771 => '冕', + 194772 => '最', + 194773 => '暜', + 194774 => '肭', + 194775 => '䏙', + 194776 => '朗', + 194777 => '望', + 194778 => '朡', + 194779 => '杞', + 194780 => '杓', + 194781 => '𣏃', + 194782 => '㭉', + 194783 => '柺', + 194784 => '枅', + 194785 => '桒', + 194786 => '梅', + 194787 => '𣑭', + 194788 => '梎', + 194789 => '栟', + 194790 => '椔', + 194791 => '㮝', + 194792 => '楂', + 194793 => '榣', + 194794 => '槪', + 194795 => '檨', + 194796 => '𣚣', + 194797 => '櫛', + 194798 => '㰘', + 194799 => '次', + 194800 => '𣢧', + 194801 => '歔', + 194802 => '㱎', + 194803 => '歲', + 194804 => '殟', + 194805 => '殺', + 194806 => '殻', + 194807 => '𣪍', + 194808 => '𡴋', + 194809 => '𣫺', + 194810 => '汎', + 194811 => '𣲼', + 194812 => '沿', + 194813 => '泍', + 194814 => '汧', + 194815 => '洖', + 194816 => '派', + 194817 => '海', + 194818 => '流', + 194819 => '浩', + 194820 => '浸', + 194821 => '涅', + 194822 => '𣴞', + 194823 => '洴', + 194824 => '港', + 194825 => '湮', + 194826 => '㴳', + 194827 => '滋', + 194828 => '滇', + 194829 => '𣻑', + 194830 => '淹', + 194831 => '潮', + 194832 => '𣽞', + 194833 => '𣾎', + 194834 => '濆', + 194835 => '瀹', + 194836 => '瀞', + 194837 => '瀛', + 194838 => '㶖', + 194839 => '灊', + 194840 => '災', + 194841 => '灷', + 194842 => '炭', + 194843 => '𠔥', + 194844 => '煅', + 194845 => '𤉣', + 194846 => '熜', + 194848 => '爨', + 194849 => '爵', + 194850 => '牐', + 194851 => '𤘈', + 194852 => '犀', + 194853 => '犕', + 194854 => '𤜵', + 194855 => '𤠔', + 194856 => '獺', + 194857 => '王', + 194858 => '㺬', + 194859 => '玥', + 194860 => '㺸', + 194861 => '㺸', + 194862 => '瑇', + 194863 => '瑜', + 194864 => '瑱', + 194865 => '璅', + 194866 => '瓊', + 194867 => '㼛', + 194868 => '甤', + 194869 => '𤰶', + 194870 => '甾', + 194871 => '𤲒', + 194872 => '異', + 194873 => '𢆟', + 194874 => '瘐', + 194875 => '𤾡', + 194876 => '𤾸', + 194877 => '𥁄', + 194878 => '㿼', + 194879 => '䀈', + 194880 => '直', + 194881 => '𥃳', + 194882 => '𥃲', + 194883 => '𥄙', + 194884 => '𥄳', + 194885 => '眞', + 194886 => '真', + 194887 => '真', + 194888 => '睊', + 194889 => '䀹', + 194890 => '瞋', + 194891 => '䁆', + 194892 => '䂖', + 194893 => '𥐝', + 194894 => '硎', + 194895 => '碌', + 194896 => '磌', + 194897 => '䃣', + 194898 => '𥘦', + 194899 => '祖', + 194900 => '𥚚', + 194901 => '𥛅', + 194902 => '福', + 194903 => '秫', + 194904 => '䄯', + 194905 => '穀', + 194906 => '穊', + 194907 => '穏', + 194908 => '𥥼', + 194909 => '𥪧', + 194910 => '𥪧', + 194912 => '䈂', + 194913 => '𥮫', + 194914 => '篆', + 194915 => '築', + 194916 => '䈧', + 194917 => '𥲀', + 194918 => '糒', + 194919 => '䊠', + 194920 => '糨', + 194921 => '糣', + 194922 => '紀', + 194923 => '𥾆', + 194924 => '絣', + 194925 => '䌁', + 194926 => '緇', + 194927 => '縂', + 194928 => '繅', + 194929 => '䌴', + 194930 => '𦈨', + 194931 => '𦉇', + 194932 => '䍙', + 194933 => '𦋙', + 194934 => '罺', + 194935 => '𦌾', + 194936 => '羕', + 194937 => '翺', + 194938 => '者', + 194939 => '𦓚', + 194940 => '𦔣', + 194941 => '聠', + 194942 => '𦖨', + 194943 => '聰', + 194944 => '𣍟', + 194945 => '䏕', + 194946 => '育', + 194947 => '脃', + 194948 => '䐋', + 194949 => '脾', + 194950 => '媵', + 194951 => '𦞧', + 194952 => '𦞵', + 194953 => '𣎓', + 194954 => '𣎜', + 194955 => '舁', + 194956 => '舄', + 194957 => '辞', + 194958 => '䑫', + 194959 => '芑', + 194960 => '芋', + 194961 => '芝', + 194962 => '劳', + 194963 => '花', + 194964 => '芳', + 194965 => '芽', + 194966 => '苦', + 194967 => '𦬼', + 194968 => '若', + 194969 => '茝', + 194970 => '荣', + 194971 => '莭', + 194972 => '茣', + 194973 => '莽', + 194974 => '菧', + 194975 => '著', + 194976 => '荓', + 194977 => '菊', + 194978 => '菌', + 194979 => '菜', + 194980 => '𦰶', + 194981 => '𦵫', + 194982 => '𦳕', + 194983 => '䔫', + 194984 => '蓱', + 194985 => '蓳', + 194986 => '蔖', + 194987 => '𧏊', + 194988 => '蕤', + 194989 => '𦼬', + 194990 => '䕝', + 194991 => '䕡', + 194992 => '𦾱', + 194993 => '𧃒', + 194994 => '䕫', + 194995 => '虐', + 194996 => '虜', + 194997 => '虧', + 194998 => '虩', + 194999 => '蚩', + 195000 => '蚈', + 195001 => '蜎', + 195002 => '蛢', + 195003 => '蝹', + 195004 => '蜨', + 195005 => '蝫', + 195006 => '螆', + 195008 => '蟡', + 195009 => '蠁', + 195010 => '䗹', + 195011 => '衠', + 195012 => '衣', + 195013 => '𧙧', + 195014 => '裗', + 195015 => '裞', + 195016 => '䘵', + 195017 => '裺', + 195018 => '㒻', + 195019 => '𧢮', + 195020 => '𧥦', + 195021 => '䚾', + 195022 => '䛇', + 195023 => '誠', + 195024 => '諭', + 195025 => '變', + 195026 => '豕', + 195027 => '𧲨', + 195028 => '貫', + 195029 => '賁', + 195030 => '贛', + 195031 => '起', + 195032 => '𧼯', + 195033 => '𠠄', + 195034 => '跋', + 195035 => '趼', + 195036 => '跰', + 195037 => '𠣞', + 195038 => '軔', + 195039 => '輸', + 195040 => '𨗒', + 195041 => '𨗭', + 195042 => '邔', + 195043 => '郱', + 195044 => '鄑', + 195045 => '𨜮', + 195046 => '鄛', + 195047 => '鈸', + 195048 => '鋗', + 195049 => '鋘', + 195050 => '鉼', + 195051 => '鏹', + 195052 => '鐕', + 195053 => '𨯺', + 195054 => '開', + 195055 => '䦕', + 195056 => '閷', + 195057 => '𨵷', + 195058 => '䧦', + 195059 => '雃', + 195060 => '嶲', + 195061 => '霣', + 195062 => '𩅅', + 195063 => '𩈚', + 195064 => '䩮', + 195065 => '䩶', + 195066 => '韠', + 195067 => '𩐊', + 195068 => '䪲', + 195069 => '𩒖', + 195070 => '頋', + 195071 => '頋', + 195072 => '頩', + 195073 => '𩖶', + 195074 => '飢', + 195075 => '䬳', + 195076 => '餩', + 195077 => '馧', + 195078 => '駂', + 195079 => '駾', + 195080 => '䯎', + 195081 => '𩬰', + 195082 => '鬒', + 195083 => '鱀', + 195084 => '鳽', + 195085 => '䳎', + 195086 => '䳭', + 195087 => '鵧', + 195088 => '𪃎', + 195089 => '䳸', + 195090 => '𪄅', + 195091 => '𪈎', + 195092 => '𪊑', + 195093 => '麻', + 195094 => '䵖', + 195095 => '黹', + 195096 => '黾', + 195097 => '鼅', + 195098 => '鼏', + 195099 => '鼖', + 195100 => '鼻', + 195101 => '𪘀', +); diff --git a/lib/symfony/polyfill-intl-idn/Resources/unidata/virama.php b/lib/symfony/polyfill-intl-idn/Resources/unidata/virama.php new file mode 100644 index 0000000000..1958e37ed2 --- /dev/null +++ b/lib/symfony/polyfill-intl-idn/Resources/unidata/virama.php @@ -0,0 +1,65 @@ + 9, + 2509 => 9, + 2637 => 9, + 2765 => 9, + 2893 => 9, + 3021 => 9, + 3149 => 9, + 3277 => 9, + 3387 => 9, + 3388 => 9, + 3405 => 9, + 3530 => 9, + 3642 => 9, + 3770 => 9, + 3972 => 9, + 4153 => 9, + 4154 => 9, + 5908 => 9, + 5940 => 9, + 6098 => 9, + 6752 => 9, + 6980 => 9, + 7082 => 9, + 7083 => 9, + 7154 => 9, + 7155 => 9, + 11647 => 9, + 43014 => 9, + 43052 => 9, + 43204 => 9, + 43347 => 9, + 43456 => 9, + 43766 => 9, + 44013 => 9, + 68159 => 9, + 69702 => 9, + 69759 => 9, + 69817 => 9, + 69939 => 9, + 69940 => 9, + 70080 => 9, + 70197 => 9, + 70378 => 9, + 70477 => 9, + 70722 => 9, + 70850 => 9, + 71103 => 9, + 71231 => 9, + 71350 => 9, + 71467 => 9, + 71737 => 9, + 71997 => 9, + 71998 => 9, + 72160 => 9, + 72244 => 9, + 72263 => 9, + 72345 => 9, + 72767 => 9, + 73028 => 9, + 73029 => 9, + 73111 => 9, +); diff --git a/lib/symfony/polyfill-intl-idn/bootstrap.php b/lib/symfony/polyfill-intl-idn/bootstrap.php new file mode 100644 index 0000000000..57c78356c9 --- /dev/null +++ b/lib/symfony/polyfill-intl-idn/bootstrap.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Idn as p; + +if (extension_loaded('intl')) { + return; +} + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!defined('U_IDNA_PROHIBITED_ERROR')) { + define('U_IDNA_PROHIBITED_ERROR', 66560); +} +if (!defined('U_IDNA_ERROR_START')) { + define('U_IDNA_ERROR_START', 66560); +} +if (!defined('U_IDNA_UNASSIGNED_ERROR')) { + define('U_IDNA_UNASSIGNED_ERROR', 66561); +} +if (!defined('U_IDNA_CHECK_BIDI_ERROR')) { + define('U_IDNA_CHECK_BIDI_ERROR', 66562); +} +if (!defined('U_IDNA_STD3_ASCII_RULES_ERROR')) { + define('U_IDNA_STD3_ASCII_RULES_ERROR', 66563); +} +if (!defined('U_IDNA_ACE_PREFIX_ERROR')) { + define('U_IDNA_ACE_PREFIX_ERROR', 66564); +} +if (!defined('U_IDNA_VERIFICATION_ERROR')) { + define('U_IDNA_VERIFICATION_ERROR', 66565); +} +if (!defined('U_IDNA_LABEL_TOO_LONG_ERROR')) { + define('U_IDNA_LABEL_TOO_LONG_ERROR', 66566); +} +if (!defined('U_IDNA_ZERO_LENGTH_LABEL_ERROR')) { + define('U_IDNA_ZERO_LENGTH_LABEL_ERROR', 66567); +} +if (!defined('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR')) { + define('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR', 66568); +} +if (!defined('U_IDNA_ERROR_LIMIT')) { + define('U_IDNA_ERROR_LIMIT', 66569); +} +if (!defined('U_STRINGPREP_PROHIBITED_ERROR')) { + define('U_STRINGPREP_PROHIBITED_ERROR', 66560); +} +if (!defined('U_STRINGPREP_UNASSIGNED_ERROR')) { + define('U_STRINGPREP_UNASSIGNED_ERROR', 66561); +} +if (!defined('U_STRINGPREP_CHECK_BIDI_ERROR')) { + define('U_STRINGPREP_CHECK_BIDI_ERROR', 66562); +} +if (!defined('IDNA_DEFAULT')) { + define('IDNA_DEFAULT', 0); +} +if (!defined('IDNA_ALLOW_UNASSIGNED')) { + define('IDNA_ALLOW_UNASSIGNED', 1); +} +if (!defined('IDNA_USE_STD3_RULES')) { + define('IDNA_USE_STD3_RULES', 2); +} +if (!defined('IDNA_CHECK_BIDI')) { + define('IDNA_CHECK_BIDI', 4); +} +if (!defined('IDNA_CHECK_CONTEXTJ')) { + define('IDNA_CHECK_CONTEXTJ', 8); +} +if (!defined('IDNA_NONTRANSITIONAL_TO_ASCII')) { + define('IDNA_NONTRANSITIONAL_TO_ASCII', 16); +} +if (!defined('IDNA_NONTRANSITIONAL_TO_UNICODE')) { + define('IDNA_NONTRANSITIONAL_TO_UNICODE', 32); +} +if (!defined('INTL_IDNA_VARIANT_2003')) { + define('INTL_IDNA_VARIANT_2003', 0); +} +if (!defined('INTL_IDNA_VARIANT_UTS46')) { + define('INTL_IDNA_VARIANT_UTS46', 1); +} +if (!defined('IDNA_ERROR_EMPTY_LABEL')) { + define('IDNA_ERROR_EMPTY_LABEL', 1); +} +if (!defined('IDNA_ERROR_LABEL_TOO_LONG')) { + define('IDNA_ERROR_LABEL_TOO_LONG', 2); +} +if (!defined('IDNA_ERROR_DOMAIN_NAME_TOO_LONG')) { + define('IDNA_ERROR_DOMAIN_NAME_TOO_LONG', 4); +} +if (!defined('IDNA_ERROR_LEADING_HYPHEN')) { + define('IDNA_ERROR_LEADING_HYPHEN', 8); +} +if (!defined('IDNA_ERROR_TRAILING_HYPHEN')) { + define('IDNA_ERROR_TRAILING_HYPHEN', 16); +} +if (!defined('IDNA_ERROR_HYPHEN_3_4')) { + define('IDNA_ERROR_HYPHEN_3_4', 32); +} +if (!defined('IDNA_ERROR_LEADING_COMBINING_MARK')) { + define('IDNA_ERROR_LEADING_COMBINING_MARK', 64); +} +if (!defined('IDNA_ERROR_DISALLOWED')) { + define('IDNA_ERROR_DISALLOWED', 128); +} +if (!defined('IDNA_ERROR_PUNYCODE')) { + define('IDNA_ERROR_PUNYCODE', 256); +} +if (!defined('IDNA_ERROR_LABEL_HAS_DOT')) { + define('IDNA_ERROR_LABEL_HAS_DOT', 512); +} +if (!defined('IDNA_ERROR_INVALID_ACE_LABEL')) { + define('IDNA_ERROR_INVALID_ACE_LABEL', 1024); +} +if (!defined('IDNA_ERROR_BIDI')) { + define('IDNA_ERROR_BIDI', 2048); +} +if (!defined('IDNA_ERROR_CONTEXTJ')) { + define('IDNA_ERROR_CONTEXTJ', 4096); +} + +if (\PHP_VERSION_ID < 70400) { + if (!function_exists('idn_to_ascii')) { + function idn_to_ascii($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_2003, &$idna_info = null) { return p\Idn::idn_to_ascii($domain, $flags, $variant, $idna_info); } + } + if (!function_exists('idn_to_utf8')) { + function idn_to_utf8($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_2003, &$idna_info = null) { return p\Idn::idn_to_utf8($domain, $flags, $variant, $idna_info); } + } +} else { + if (!function_exists('idn_to_ascii')) { + function idn_to_ascii($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_UTS46, &$idna_info = null) { return p\Idn::idn_to_ascii($domain, $flags, $variant, $idna_info); } + } + if (!function_exists('idn_to_utf8')) { + function idn_to_utf8($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_UTS46, &$idna_info = null) { return p\Idn::idn_to_utf8($domain, $flags, $variant, $idna_info); } + } +} diff --git a/lib/symfony/polyfill-intl-idn/bootstrap80.php b/lib/symfony/polyfill-intl-idn/bootstrap80.php new file mode 100644 index 0000000000..a62c2d69bf --- /dev/null +++ b/lib/symfony/polyfill-intl-idn/bootstrap80.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Idn as p; + +if (!defined('U_IDNA_PROHIBITED_ERROR')) { + define('U_IDNA_PROHIBITED_ERROR', 66560); +} +if (!defined('U_IDNA_ERROR_START')) { + define('U_IDNA_ERROR_START', 66560); +} +if (!defined('U_IDNA_UNASSIGNED_ERROR')) { + define('U_IDNA_UNASSIGNED_ERROR', 66561); +} +if (!defined('U_IDNA_CHECK_BIDI_ERROR')) { + define('U_IDNA_CHECK_BIDI_ERROR', 66562); +} +if (!defined('U_IDNA_STD3_ASCII_RULES_ERROR')) { + define('U_IDNA_STD3_ASCII_RULES_ERROR', 66563); +} +if (!defined('U_IDNA_ACE_PREFIX_ERROR')) { + define('U_IDNA_ACE_PREFIX_ERROR', 66564); +} +if (!defined('U_IDNA_VERIFICATION_ERROR')) { + define('U_IDNA_VERIFICATION_ERROR', 66565); +} +if (!defined('U_IDNA_LABEL_TOO_LONG_ERROR')) { + define('U_IDNA_LABEL_TOO_LONG_ERROR', 66566); +} +if (!defined('U_IDNA_ZERO_LENGTH_LABEL_ERROR')) { + define('U_IDNA_ZERO_LENGTH_LABEL_ERROR', 66567); +} +if (!defined('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR')) { + define('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR', 66568); +} +if (!defined('U_IDNA_ERROR_LIMIT')) { + define('U_IDNA_ERROR_LIMIT', 66569); +} +if (!defined('U_STRINGPREP_PROHIBITED_ERROR')) { + define('U_STRINGPREP_PROHIBITED_ERROR', 66560); +} +if (!defined('U_STRINGPREP_UNASSIGNED_ERROR')) { + define('U_STRINGPREP_UNASSIGNED_ERROR', 66561); +} +if (!defined('U_STRINGPREP_CHECK_BIDI_ERROR')) { + define('U_STRINGPREP_CHECK_BIDI_ERROR', 66562); +} +if (!defined('IDNA_DEFAULT')) { + define('IDNA_DEFAULT', 0); +} +if (!defined('IDNA_ALLOW_UNASSIGNED')) { + define('IDNA_ALLOW_UNASSIGNED', 1); +} +if (!defined('IDNA_USE_STD3_RULES')) { + define('IDNA_USE_STD3_RULES', 2); +} +if (!defined('IDNA_CHECK_BIDI')) { + define('IDNA_CHECK_BIDI', 4); +} +if (!defined('IDNA_CHECK_CONTEXTJ')) { + define('IDNA_CHECK_CONTEXTJ', 8); +} +if (!defined('IDNA_NONTRANSITIONAL_TO_ASCII')) { + define('IDNA_NONTRANSITIONAL_TO_ASCII', 16); +} +if (!defined('IDNA_NONTRANSITIONAL_TO_UNICODE')) { + define('IDNA_NONTRANSITIONAL_TO_UNICODE', 32); +} +if (!defined('INTL_IDNA_VARIANT_UTS46')) { + define('INTL_IDNA_VARIANT_UTS46', 1); +} +if (!defined('IDNA_ERROR_EMPTY_LABEL')) { + define('IDNA_ERROR_EMPTY_LABEL', 1); +} +if (!defined('IDNA_ERROR_LABEL_TOO_LONG')) { + define('IDNA_ERROR_LABEL_TOO_LONG', 2); +} +if (!defined('IDNA_ERROR_DOMAIN_NAME_TOO_LONG')) { + define('IDNA_ERROR_DOMAIN_NAME_TOO_LONG', 4); +} +if (!defined('IDNA_ERROR_LEADING_HYPHEN')) { + define('IDNA_ERROR_LEADING_HYPHEN', 8); +} +if (!defined('IDNA_ERROR_TRAILING_HYPHEN')) { + define('IDNA_ERROR_TRAILING_HYPHEN', 16); +} +if (!defined('IDNA_ERROR_HYPHEN_3_4')) { + define('IDNA_ERROR_HYPHEN_3_4', 32); +} +if (!defined('IDNA_ERROR_LEADING_COMBINING_MARK')) { + define('IDNA_ERROR_LEADING_COMBINING_MARK', 64); +} +if (!defined('IDNA_ERROR_DISALLOWED')) { + define('IDNA_ERROR_DISALLOWED', 128); +} +if (!defined('IDNA_ERROR_PUNYCODE')) { + define('IDNA_ERROR_PUNYCODE', 256); +} +if (!defined('IDNA_ERROR_LABEL_HAS_DOT')) { + define('IDNA_ERROR_LABEL_HAS_DOT', 512); +} +if (!defined('IDNA_ERROR_INVALID_ACE_LABEL')) { + define('IDNA_ERROR_INVALID_ACE_LABEL', 1024); +} +if (!defined('IDNA_ERROR_BIDI')) { + define('IDNA_ERROR_BIDI', 2048); +} +if (!defined('IDNA_ERROR_CONTEXTJ')) { + define('IDNA_ERROR_CONTEXTJ', 4096); +} + +if (!function_exists('idn_to_ascii')) { + function idn_to_ascii(?string $domain, ?int $flags = IDNA_DEFAULT, ?int $variant = INTL_IDNA_VARIANT_UTS46, &$idna_info = null): string|false { return p\Idn::idn_to_ascii((string) $domain, (int) $flags, (int) $variant, $idna_info); } +} +if (!function_exists('idn_to_utf8')) { + function idn_to_utf8(?string $domain, ?int $flags = IDNA_DEFAULT, ?int $variant = INTL_IDNA_VARIANT_UTS46, &$idna_info = null): string|false { return p\Idn::idn_to_utf8((string) $domain, (int) $flags, (int) $variant, $idna_info); } +} diff --git a/lib/symfony/polyfill-intl-idn/composer.json b/lib/symfony/polyfill-intl-idn/composer.json new file mode 100644 index 0000000000..71030a2ed7 --- /dev/null +++ b/lib/symfony/polyfill-intl-idn/composer.json @@ -0,0 +1,44 @@ +{ + "name": "symfony/polyfill-intl-idn", + "type": "library", + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "keywords": ["polyfill", "shim", "compatibility", "portable", "intl", "idn"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Intl\\Idn\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-intl": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/lib/symfony/polyfill-php56/LICENSE b/lib/symfony/polyfill-intl-normalizer/LICENSE similarity index 100% rename from lib/symfony/polyfill-php56/LICENSE rename to lib/symfony/polyfill-intl-normalizer/LICENSE diff --git a/lib/symfony/polyfill-intl-normalizer/Normalizer.php b/lib/symfony/polyfill-intl-normalizer/Normalizer.php new file mode 100644 index 0000000000..4443c2322a --- /dev/null +++ b/lib/symfony/polyfill-intl-normalizer/Normalizer.php @@ -0,0 +1,310 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Intl\Normalizer; + +/** + * Normalizer is a PHP fallback implementation of the Normalizer class provided by the intl extension. + * + * It has been validated with Unicode 6.3 Normalization Conformance Test. + * See http://www.unicode.org/reports/tr15/ for detailed info about Unicode normalizations. + * + * @author Nicolas Grekas + * + * @internal + */ +class Normalizer +{ + public const FORM_D = \Normalizer::FORM_D; + public const FORM_KD = \Normalizer::FORM_KD; + public const FORM_C = \Normalizer::FORM_C; + public const FORM_KC = \Normalizer::FORM_KC; + public const NFD = \Normalizer::NFD; + public const NFKD = \Normalizer::NFKD; + public const NFC = \Normalizer::NFC; + public const NFKC = \Normalizer::NFKC; + + private static $C; + private static $D; + private static $KD; + private static $cC; + private static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + private static $ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; + + public static function isNormalized(string $s, int $form = self::FORM_C) + { + if (!\in_array($form, [self::NFD, self::NFKD, self::NFC, self::NFKC])) { + return false; + } + if (!isset($s[strspn($s, self::$ASCII)])) { + return true; + } + if (self::NFC == $form && preg_match('//u', $s) && !preg_match('/[^\x00-\x{2FF}]/u', $s)) { + return true; + } + + return self::normalize($s, $form) === $s; + } + + public static function normalize(string $s, int $form = self::FORM_C) + { + if (!preg_match('//u', $s)) { + return false; + } + + switch ($form) { + case self::NFC: $C = true; $K = false; break; + case self::NFD: $C = false; $K = false; break; + case self::NFKC: $C = true; $K = true; break; + case self::NFKD: $C = false; $K = true; break; + default: + if (\defined('Normalizer::NONE') && \Normalizer::NONE == $form) { + return $s; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError('normalizer_normalize(): Argument #2 ($form) must be a a valid normalization form'); + } + + if ('' === $s) { + return ''; + } + + if ($K && null === self::$KD) { + self::$KD = self::getData('compatibilityDecomposition'); + } + + if (null === self::$D) { + self::$D = self::getData('canonicalDecomposition'); + self::$cC = self::getData('combiningClass'); + } + + if (null !== $mbEncoding = (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) ? mb_internal_encoding() : null) { + mb_internal_encoding('8bit'); + } + + $r = self::decompose($s, $K); + + if ($C) { + if (null === self::$C) { + self::$C = self::getData('canonicalComposition'); + } + + $r = self::recompose($r); + } + if (null !== $mbEncoding) { + mb_internal_encoding($mbEncoding); + } + + return $r; + } + + private static function recompose($s) + { + $ASCII = self::$ASCII; + $compMap = self::$C; + $combClass = self::$cC; + $ulenMask = self::$ulenMask; + + $result = $tail = ''; + + $i = $s[0] < "\x80" ? 1 : $ulenMask[$s[0] & "\xF0"]; + $len = \strlen($s); + + $lastUchr = substr($s, 0, $i); + $lastUcls = isset($combClass[$lastUchr]) ? 256 : 0; + + while ($i < $len) { + if ($s[$i] < "\x80") { + // ASCII chars + + if ($tail) { + $lastUchr .= $tail; + $tail = ''; + } + + if ($j = strspn($s, $ASCII, $i + 1)) { + $lastUchr .= substr($s, $i, $j); + $i += $j; + } + + $result .= $lastUchr; + $lastUchr = $s[$i]; + $lastUcls = 0; + ++$i; + continue; + } + + $ulen = $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + + if ($lastUchr < "\xE1\x84\x80" || "\xE1\x84\x92" < $lastUchr + || $uchr < "\xE1\x85\xA1" || "\xE1\x85\xB5" < $uchr + || $lastUcls) { + // Table lookup and combining chars composition + + $ucls = $combClass[$uchr] ?? 0; + + if (isset($compMap[$lastUchr.$uchr]) && (!$lastUcls || $lastUcls < $ucls)) { + $lastUchr = $compMap[$lastUchr.$uchr]; + } elseif ($lastUcls = $ucls) { + $tail .= $uchr; + } else { + if ($tail) { + $lastUchr .= $tail; + $tail = ''; + } + + $result .= $lastUchr; + $lastUchr = $uchr; + } + } else { + // Hangul chars + + $L = \ord($lastUchr[2]) - 0x80; + $V = \ord($uchr[2]) - 0xA1; + $T = 0; + + $uchr = substr($s, $i + $ulen, 3); + + if ("\xE1\x86\xA7" <= $uchr && $uchr <= "\xE1\x87\x82") { + $T = \ord($uchr[2]) - 0xA7; + 0 > $T && $T += 0x40; + $ulen += 3; + } + + $L = 0xAC00 + ($L * 21 + $V) * 28 + $T; + $lastUchr = \chr(0xE0 | $L >> 12).\chr(0x80 | $L >> 6 & 0x3F).\chr(0x80 | $L & 0x3F); + } + + $i += $ulen; + } + + return $result.$lastUchr.$tail; + } + + private static function decompose($s, $c) + { + $result = ''; + + $ASCII = self::$ASCII; + $decompMap = self::$D; + $combClass = self::$cC; + $ulenMask = self::$ulenMask; + if ($c) { + $compatMap = self::$KD; + } + + $c = []; + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + if ($s[$i] < "\x80") { + // ASCII chars + + if ($c) { + ksort($c); + $result .= implode('', $c); + $c = []; + } + + $j = 1 + strspn($s, $ASCII, $i + 1); + $result .= substr($s, $i, $j); + $i += $j; + continue; + } + + $ulen = $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if ($uchr < "\xEA\xB0\x80" || "\xED\x9E\xA3" < $uchr) { + // Table lookup + + if ($uchr !== $j = $compatMap[$uchr] ?? ($decompMap[$uchr] ?? $uchr)) { + $uchr = $j; + + $j = \strlen($uchr); + $ulen = $uchr[0] < "\x80" ? 1 : $ulenMask[$uchr[0] & "\xF0"]; + + if ($ulen != $j) { + // Put trailing chars in $s + + $j -= $ulen; + $i -= $j; + + if (0 > $i) { + $s = str_repeat(' ', -$i).$s; + $len -= $i; + $i = 0; + } + + while ($j--) { + $s[$i + $j] = $uchr[$ulen + $j]; + } + + $uchr = substr($uchr, 0, $ulen); + } + } + if (isset($combClass[$uchr])) { + // Combining chars, for sorting + + if (!isset($c[$combClass[$uchr]])) { + $c[$combClass[$uchr]] = ''; + } + $c[$combClass[$uchr]] .= $uchr; + continue; + } + } else { + // Hangul chars + + $uchr = unpack('C*', $uchr); + $j = (($uchr[1] - 224) << 12) + (($uchr[2] - 128) << 6) + $uchr[3] - 0xAC80; + + $uchr = "\xE1\x84".\chr(0x80 + (int) ($j / 588)) + ."\xE1\x85".\chr(0xA1 + (int) (($j % 588) / 28)); + + if ($j %= 28) { + $uchr .= $j < 25 + ? ("\xE1\x86".\chr(0xA7 + $j)) + : ("\xE1\x87".\chr(0x67 + $j)); + } + } + if ($c) { + ksort($c); + $result .= implode('', $c); + $c = []; + } + + $result .= $uchr; + } + + if ($c) { + ksort($c); + $result .= implode('', $c); + } + + return $result; + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } +} diff --git a/lib/symfony/polyfill-intl-normalizer/README.md b/lib/symfony/polyfill-intl-normalizer/README.md new file mode 100644 index 0000000000..b9b762e850 --- /dev/null +++ b/lib/symfony/polyfill-intl-normalizer/README.md @@ -0,0 +1,14 @@ +Symfony Polyfill / Intl: Normalizer +=================================== + +This component provides a fallback implementation for the +[`Normalizer`](https://php.net/Normalizer) class provided +by the [Intl](https://php.net/intl) extension. + +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/lib/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php b/lib/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php new file mode 100644 index 0000000000..0fdfc890a2 --- /dev/null +++ b/lib/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php @@ -0,0 +1,17 @@ + 'À', + 'Á' => 'Á', + 'Â' => 'Â', + 'Ã' => 'Ã', + 'Ä' => 'Ä', + 'Å' => 'Å', + 'Ç' => 'Ç', + 'È' => 'È', + 'É' => 'É', + 'Ê' => 'Ê', + 'Ë' => 'Ë', + 'Ì' => 'Ì', + 'Í' => 'Í', + 'Î' => 'Î', + 'Ï' => 'Ï', + 'Ñ' => 'Ñ', + 'Ò' => 'Ò', + 'Ó' => 'Ó', + 'Ô' => 'Ô', + 'Õ' => 'Õ', + 'Ö' => 'Ö', + 'Ù' => 'Ù', + 'Ú' => 'Ú', + 'Û' => 'Û', + 'Ü' => 'Ü', + 'Ý' => 'Ý', + 'à' => 'à', + 'á' => 'á', + 'â' => 'â', + 'ã' => 'ã', + 'ä' => 'ä', + 'å' => 'å', + 'ç' => 'ç', + 'è' => 'è', + 'é' => 'é', + 'ê' => 'ê', + 'ë' => 'ë', + 'ì' => 'ì', + 'í' => 'í', + 'î' => 'î', + 'ï' => 'ï', + 'ñ' => 'ñ', + 'ò' => 'ò', + 'ó' => 'ó', + 'ô' => 'ô', + 'õ' => 'õ', + 'ö' => 'ö', + 'ù' => 'ù', + 'ú' => 'ú', + 'û' => 'û', + 'ü' => 'ü', + 'ý' => 'ý', + 'ÿ' => 'ÿ', + 'Ā' => 'Ā', + 'ā' => 'ā', + 'Ă' => 'Ă', + 'ă' => 'ă', + 'Ą' => 'Ą', + 'ą' => 'ą', + 'Ć' => 'Ć', + 'ć' => 'ć', + 'Ĉ' => 'Ĉ', + 'ĉ' => 'ĉ', + 'Ċ' => 'Ċ', + 'ċ' => 'ċ', + 'Č' => 'Č', + 'č' => 'č', + 'Ď' => 'Ď', + 'ď' => 'ď', + 'Ē' => 'Ē', + 'ē' => 'ē', + 'Ĕ' => 'Ĕ', + 'ĕ' => 'ĕ', + 'Ė' => 'Ė', + 'ė' => 'ė', + 'Ę' => 'Ę', + 'ę' => 'ę', + 'Ě' => 'Ě', + 'ě' => 'ě', + 'Ĝ' => 'Ĝ', + 'ĝ' => 'ĝ', + 'Ğ' => 'Ğ', + 'ğ' => 'ğ', + 'Ġ' => 'Ġ', + 'ġ' => 'ġ', + 'Ģ' => 'Ģ', + 'ģ' => 'ģ', + 'Ĥ' => 'Ĥ', + 'ĥ' => 'ĥ', + 'Ĩ' => 'Ĩ', + 'ĩ' => 'ĩ', + 'Ī' => 'Ī', + 'ī' => 'ī', + 'Ĭ' => 'Ĭ', + 'ĭ' => 'ĭ', + 'Į' => 'Į', + 'į' => 'į', + 'İ' => 'İ', + 'Ĵ' => 'Ĵ', + 'ĵ' => 'ĵ', + 'Ķ' => 'Ķ', + 'ķ' => 'ķ', + 'Ĺ' => 'Ĺ', + 'ĺ' => 'ĺ', + 'Ļ' => 'Ļ', + 'ļ' => 'ļ', + 'Ľ' => 'Ľ', + 'ľ' => 'ľ', + 'Ń' => 'Ń', + 'ń' => 'ń', + 'Ņ' => 'Ņ', + 'ņ' => 'ņ', + 'Ň' => 'Ň', + 'ň' => 'ň', + 'Ō' => 'Ō', + 'ō' => 'ō', + 'Ŏ' => 'Ŏ', + 'ŏ' => 'ŏ', + 'Ő' => 'Ő', + 'ő' => 'ő', + 'Ŕ' => 'Ŕ', + 'ŕ' => 'ŕ', + 'Ŗ' => 'Ŗ', + 'ŗ' => 'ŗ', + 'Ř' => 'Ř', + 'ř' => 'ř', + 'Ś' => 'Ś', + 'ś' => 'ś', + 'Ŝ' => 'Ŝ', + 'ŝ' => 'ŝ', + 'Ş' => 'Ş', + 'ş' => 'ş', + 'Š' => 'Š', + 'š' => 'š', + 'Ţ' => 'Ţ', + 'ţ' => 'ţ', + 'Ť' => 'Ť', + 'ť' => 'ť', + 'Ũ' => 'Ũ', + 'ũ' => 'ũ', + 'Ū' => 'Ū', + 'ū' => 'ū', + 'Ŭ' => 'Ŭ', + 'ŭ' => 'ŭ', + 'Ů' => 'Ů', + 'ů' => 'ů', + 'Ű' => 'Ű', + 'ű' => 'ű', + 'Ų' => 'Ų', + 'ų' => 'ų', + 'Ŵ' => 'Ŵ', + 'ŵ' => 'ŵ', + 'Ŷ' => 'Ŷ', + 'ŷ' => 'ŷ', + 'Ÿ' => 'Ÿ', + 'Ź' => 'Ź', + 'ź' => 'ź', + 'Ż' => 'Ż', + 'ż' => 'ż', + 'Ž' => 'Ž', + 'ž' => 'ž', + 'Ơ' => 'Ơ', + 'ơ' => 'ơ', + 'Ư' => 'Ư', + 'ư' => 'ư', + 'Ǎ' => 'Ǎ', + 'ǎ' => 'ǎ', + 'Ǐ' => 'Ǐ', + 'ǐ' => 'ǐ', + 'Ǒ' => 'Ǒ', + 'ǒ' => 'ǒ', + 'Ǔ' => 'Ǔ', + 'ǔ' => 'ǔ', + 'Ǖ' => 'Ǖ', + 'ǖ' => 'ǖ', + 'Ǘ' => 'Ǘ', + 'ǘ' => 'ǘ', + 'Ǚ' => 'Ǚ', + 'ǚ' => 'ǚ', + 'Ǜ' => 'Ǜ', + 'ǜ' => 'ǜ', + 'Ǟ' => 'Ǟ', + 'ǟ' => 'ǟ', + 'Ǡ' => 'Ǡ', + 'ǡ' => 'ǡ', + 'Ǣ' => 'Ǣ', + 'ǣ' => 'ǣ', + 'Ǧ' => 'Ǧ', + 'ǧ' => 'ǧ', + 'Ǩ' => 'Ǩ', + 'ǩ' => 'ǩ', + 'Ǫ' => 'Ǫ', + 'ǫ' => 'ǫ', + 'Ǭ' => 'Ǭ', + 'ǭ' => 'ǭ', + 'Ǯ' => 'Ǯ', + 'ǯ' => 'ǯ', + 'ǰ' => 'ǰ', + 'Ǵ' => 'Ǵ', + 'ǵ' => 'ǵ', + 'Ǹ' => 'Ǹ', + 'ǹ' => 'ǹ', + 'Ǻ' => 'Ǻ', + 'ǻ' => 'ǻ', + 'Ǽ' => 'Ǽ', + 'ǽ' => 'ǽ', + 'Ǿ' => 'Ǿ', + 'ǿ' => 'ǿ', + 'Ȁ' => 'Ȁ', + 'ȁ' => 'ȁ', + 'Ȃ' => 'Ȃ', + 'ȃ' => 'ȃ', + 'Ȅ' => 'Ȅ', + 'ȅ' => 'ȅ', + 'Ȇ' => 'Ȇ', + 'ȇ' => 'ȇ', + 'Ȉ' => 'Ȉ', + 'ȉ' => 'ȉ', + 'Ȋ' => 'Ȋ', + 'ȋ' => 'ȋ', + 'Ȍ' => 'Ȍ', + 'ȍ' => 'ȍ', + 'Ȏ' => 'Ȏ', + 'ȏ' => 'ȏ', + 'Ȑ' => 'Ȑ', + 'ȑ' => 'ȑ', + 'Ȓ' => 'Ȓ', + 'ȓ' => 'ȓ', + 'Ȕ' => 'Ȕ', + 'ȕ' => 'ȕ', + 'Ȗ' => 'Ȗ', + 'ȗ' => 'ȗ', + 'Ș' => 'Ș', + 'ș' => 'ș', + 'Ț' => 'Ț', + 'ț' => 'ț', + 'Ȟ' => 'Ȟ', + 'ȟ' => 'ȟ', + 'Ȧ' => 'Ȧ', + 'ȧ' => 'ȧ', + 'Ȩ' => 'Ȩ', + 'ȩ' => 'ȩ', + 'Ȫ' => 'Ȫ', + 'ȫ' => 'ȫ', + 'Ȭ' => 'Ȭ', + 'ȭ' => 'ȭ', + 'Ȯ' => 'Ȯ', + 'ȯ' => 'ȯ', + 'Ȱ' => 'Ȱ', + 'ȱ' => 'ȱ', + 'Ȳ' => 'Ȳ', + 'ȳ' => 'ȳ', + '΅' => '΅', + 'Ά' => 'Ά', + 'Έ' => 'Έ', + 'Ή' => 'Ή', + 'Ί' => 'Ί', + 'Ό' => 'Ό', + 'Ύ' => 'Ύ', + 'Ώ' => 'Ώ', + 'ΐ' => 'ΐ', + 'Ϊ' => 'Ϊ', + 'Ϋ' => 'Ϋ', + 'ά' => 'ά', + 'έ' => 'έ', + 'ή' => 'ή', + 'ί' => 'ί', + 'ΰ' => 'ΰ', + 'ϊ' => 'ϊ', + 'ϋ' => 'ϋ', + 'ό' => 'ό', + 'ύ' => 'ύ', + 'ώ' => 'ώ', + 'ϓ' => 'ϓ', + 'ϔ' => 'ϔ', + 'Ѐ' => 'Ѐ', + 'Ё' => 'Ё', + 'Ѓ' => 'Ѓ', + 'Ї' => 'Ї', + 'Ќ' => 'Ќ', + 'Ѝ' => 'Ѝ', + 'Ў' => 'Ў', + 'Й' => 'Й', + 'й' => 'й', + 'ѐ' => 'ѐ', + 'ё' => 'ё', + 'ѓ' => 'ѓ', + 'ї' => 'ї', + 'ќ' => 'ќ', + 'ѝ' => 'ѝ', + 'ў' => 'ў', + 'Ѷ' => 'Ѷ', + 'ѷ' => 'ѷ', + 'Ӂ' => 'Ӂ', + 'ӂ' => 'ӂ', + 'Ӑ' => 'Ӑ', + 'ӑ' => 'ӑ', + 'Ӓ' => 'Ӓ', + 'ӓ' => 'ӓ', + 'Ӗ' => 'Ӗ', + 'ӗ' => 'ӗ', + 'Ӛ' => 'Ӛ', + 'ӛ' => 'ӛ', + 'Ӝ' => 'Ӝ', + 'ӝ' => 'ӝ', + 'Ӟ' => 'Ӟ', + 'ӟ' => 'ӟ', + 'Ӣ' => 'Ӣ', + 'ӣ' => 'ӣ', + 'Ӥ' => 'Ӥ', + 'ӥ' => 'ӥ', + 'Ӧ' => 'Ӧ', + 'ӧ' => 'ӧ', + 'Ӫ' => 'Ӫ', + 'ӫ' => 'ӫ', + 'Ӭ' => 'Ӭ', + 'ӭ' => 'ӭ', + 'Ӯ' => 'Ӯ', + 'ӯ' => 'ӯ', + 'Ӱ' => 'Ӱ', + 'ӱ' => 'ӱ', + 'Ӳ' => 'Ӳ', + 'ӳ' => 'ӳ', + 'Ӵ' => 'Ӵ', + 'ӵ' => 'ӵ', + 'Ӹ' => 'Ӹ', + 'ӹ' => 'ӹ', + 'آ' => 'آ', + 'أ' => 'أ', + 'ؤ' => 'ؤ', + 'إ' => 'إ', + 'ئ' => 'ئ', + 'ۀ' => 'ۀ', + 'ۂ' => 'ۂ', + 'ۓ' => 'ۓ', + 'ऩ' => 'ऩ', + 'ऱ' => 'ऱ', + 'ऴ' => 'ऴ', + 'ো' => 'ো', + 'ৌ' => 'ৌ', + 'ୈ' => 'ୈ', + 'ୋ' => 'ୋ', + 'ୌ' => 'ୌ', + 'ஔ' => 'ஔ', + 'ொ' => 'ொ', + 'ோ' => 'ோ', + 'ௌ' => 'ௌ', + 'ై' => 'ై', + 'ೀ' => 'ೀ', + 'ೇ' => 'ೇ', + 'ೈ' => 'ೈ', + 'ೊ' => 'ೊ', + 'ೋ' => 'ೋ', + 'ൊ' => 'ൊ', + 'ോ' => 'ോ', + 'ൌ' => 'ൌ', + 'ේ' => 'ේ', + 'ො' => 'ො', + 'ෝ' => 'ෝ', + 'ෞ' => 'ෞ', + 'ဦ' => 'ဦ', + 'ᬆ' => 'ᬆ', + 'ᬈ' => 'ᬈ', + 'ᬊ' => 'ᬊ', + 'ᬌ' => 'ᬌ', + 'ᬎ' => 'ᬎ', + 'ᬒ' => 'ᬒ', + 'ᬻ' => 'ᬻ', + 'ᬽ' => 'ᬽ', + 'ᭀ' => 'ᭀ', + 'ᭁ' => 'ᭁ', + 'ᭃ' => 'ᭃ', + 'Ḁ' => 'Ḁ', + 'ḁ' => 'ḁ', + 'Ḃ' => 'Ḃ', + 'ḃ' => 'ḃ', + 'Ḅ' => 'Ḅ', + 'ḅ' => 'ḅ', + 'Ḇ' => 'Ḇ', + 'ḇ' => 'ḇ', + 'Ḉ' => 'Ḉ', + 'ḉ' => 'ḉ', + 'Ḋ' => 'Ḋ', + 'ḋ' => 'ḋ', + 'Ḍ' => 'Ḍ', + 'ḍ' => 'ḍ', + 'Ḏ' => 'Ḏ', + 'ḏ' => 'ḏ', + 'Ḑ' => 'Ḑ', + 'ḑ' => 'ḑ', + 'Ḓ' => 'Ḓ', + 'ḓ' => 'ḓ', + 'Ḕ' => 'Ḕ', + 'ḕ' => 'ḕ', + 'Ḗ' => 'Ḗ', + 'ḗ' => 'ḗ', + 'Ḙ' => 'Ḙ', + 'ḙ' => 'ḙ', + 'Ḛ' => 'Ḛ', + 'ḛ' => 'ḛ', + 'Ḝ' => 'Ḝ', + 'ḝ' => 'ḝ', + 'Ḟ' => 'Ḟ', + 'ḟ' => 'ḟ', + 'Ḡ' => 'Ḡ', + 'ḡ' => 'ḡ', + 'Ḣ' => 'Ḣ', + 'ḣ' => 'ḣ', + 'Ḥ' => 'Ḥ', + 'ḥ' => 'ḥ', + 'Ḧ' => 'Ḧ', + 'ḧ' => 'ḧ', + 'Ḩ' => 'Ḩ', + 'ḩ' => 'ḩ', + 'Ḫ' => 'Ḫ', + 'ḫ' => 'ḫ', + 'Ḭ' => 'Ḭ', + 'ḭ' => 'ḭ', + 'Ḯ' => 'Ḯ', + 'ḯ' => 'ḯ', + 'Ḱ' => 'Ḱ', + 'ḱ' => 'ḱ', + 'Ḳ' => 'Ḳ', + 'ḳ' => 'ḳ', + 'Ḵ' => 'Ḵ', + 'ḵ' => 'ḵ', + 'Ḷ' => 'Ḷ', + 'ḷ' => 'ḷ', + 'Ḹ' => 'Ḹ', + 'ḹ' => 'ḹ', + 'Ḻ' => 'Ḻ', + 'ḻ' => 'ḻ', + 'Ḽ' => 'Ḽ', + 'ḽ' => 'ḽ', + 'Ḿ' => 'Ḿ', + 'ḿ' => 'ḿ', + 'Ṁ' => 'Ṁ', + 'ṁ' => 'ṁ', + 'Ṃ' => 'Ṃ', + 'ṃ' => 'ṃ', + 'Ṅ' => 'Ṅ', + 'ṅ' => 'ṅ', + 'Ṇ' => 'Ṇ', + 'ṇ' => 'ṇ', + 'Ṉ' => 'Ṉ', + 'ṉ' => 'ṉ', + 'Ṋ' => 'Ṋ', + 'ṋ' => 'ṋ', + 'Ṍ' => 'Ṍ', + 'ṍ' => 'ṍ', + 'Ṏ' => 'Ṏ', + 'ṏ' => 'ṏ', + 'Ṑ' => 'Ṑ', + 'ṑ' => 'ṑ', + 'Ṓ' => 'Ṓ', + 'ṓ' => 'ṓ', + 'Ṕ' => 'Ṕ', + 'ṕ' => 'ṕ', + 'Ṗ' => 'Ṗ', + 'ṗ' => 'ṗ', + 'Ṙ' => 'Ṙ', + 'ṙ' => 'ṙ', + 'Ṛ' => 'Ṛ', + 'ṛ' => 'ṛ', + 'Ṝ' => 'Ṝ', + 'ṝ' => 'ṝ', + 'Ṟ' => 'Ṟ', + 'ṟ' => 'ṟ', + 'Ṡ' => 'Ṡ', + 'ṡ' => 'ṡ', + 'Ṣ' => 'Ṣ', + 'ṣ' => 'ṣ', + 'Ṥ' => 'Ṥ', + 'ṥ' => 'ṥ', + 'Ṧ' => 'Ṧ', + 'ṧ' => 'ṧ', + 'Ṩ' => 'Ṩ', + 'ṩ' => 'ṩ', + 'Ṫ' => 'Ṫ', + 'ṫ' => 'ṫ', + 'Ṭ' => 'Ṭ', + 'ṭ' => 'ṭ', + 'Ṯ' => 'Ṯ', + 'ṯ' => 'ṯ', + 'Ṱ' => 'Ṱ', + 'ṱ' => 'ṱ', + 'Ṳ' => 'Ṳ', + 'ṳ' => 'ṳ', + 'Ṵ' => 'Ṵ', + 'ṵ' => 'ṵ', + 'Ṷ' => 'Ṷ', + 'ṷ' => 'ṷ', + 'Ṹ' => 'Ṹ', + 'ṹ' => 'ṹ', + 'Ṻ' => 'Ṻ', + 'ṻ' => 'ṻ', + 'Ṽ' => 'Ṽ', + 'ṽ' => 'ṽ', + 'Ṿ' => 'Ṿ', + 'ṿ' => 'ṿ', + 'Ẁ' => 'Ẁ', + 'ẁ' => 'ẁ', + 'Ẃ' => 'Ẃ', + 'ẃ' => 'ẃ', + 'Ẅ' => 'Ẅ', + 'ẅ' => 'ẅ', + 'Ẇ' => 'Ẇ', + 'ẇ' => 'ẇ', + 'Ẉ' => 'Ẉ', + 'ẉ' => 'ẉ', + 'Ẋ' => 'Ẋ', + 'ẋ' => 'ẋ', + 'Ẍ' => 'Ẍ', + 'ẍ' => 'ẍ', + 'Ẏ' => 'Ẏ', + 'ẏ' => 'ẏ', + 'Ẑ' => 'Ẑ', + 'ẑ' => 'ẑ', + 'Ẓ' => 'Ẓ', + 'ẓ' => 'ẓ', + 'Ẕ' => 'Ẕ', + 'ẕ' => 'ẕ', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'ẘ' => 'ẘ', + 'ẙ' => 'ẙ', + 'ẛ' => 'ẛ', + 'Ạ' => 'Ạ', + 'ạ' => 'ạ', + 'Ả' => 'Ả', + 'ả' => 'ả', + 'Ấ' => 'Ấ', + 'ấ' => 'ấ', + 'Ầ' => 'Ầ', + 'ầ' => 'ầ', + 'Ẩ' => 'Ẩ', + 'ẩ' => 'ẩ', + 'Ẫ' => 'Ẫ', + 'ẫ' => 'ẫ', + 'Ậ' => 'Ậ', + 'ậ' => 'ậ', + 'Ắ' => 'Ắ', + 'ắ' => 'ắ', + 'Ằ' => 'Ằ', + 'ằ' => 'ằ', + 'Ẳ' => 'Ẳ', + 'ẳ' => 'ẳ', + 'Ẵ' => 'Ẵ', + 'ẵ' => 'ẵ', + 'Ặ' => 'Ặ', + 'ặ' => 'ặ', + 'Ẹ' => 'Ẹ', + 'ẹ' => 'ẹ', + 'Ẻ' => 'Ẻ', + 'ẻ' => 'ẻ', + 'Ẽ' => 'Ẽ', + 'ẽ' => 'ẽ', + 'Ế' => 'Ế', + 'ế' => 'ế', + 'Ề' => 'Ề', + 'ề' => 'ề', + 'Ể' => 'Ể', + 'ể' => 'ể', + 'Ễ' => 'Ễ', + 'ễ' => 'ễ', + 'Ệ' => 'Ệ', + 'ệ' => 'ệ', + 'Ỉ' => 'Ỉ', + 'ỉ' => 'ỉ', + 'Ị' => 'Ị', + 'ị' => 'ị', + 'Ọ' => 'Ọ', + 'ọ' => 'ọ', + 'Ỏ' => 'Ỏ', + 'ỏ' => 'ỏ', + 'Ố' => 'Ố', + 'ố' => 'ố', + 'Ồ' => 'Ồ', + 'ồ' => 'ồ', + 'Ổ' => 'Ổ', + 'ổ' => 'ổ', + 'Ỗ' => 'Ỗ', + 'ỗ' => 'ỗ', + 'Ộ' => 'Ộ', + 'ộ' => 'ộ', + 'Ớ' => 'Ớ', + 'ớ' => 'ớ', + 'Ờ' => 'Ờ', + 'ờ' => 'ờ', + 'Ở' => 'Ở', + 'ở' => 'ở', + 'Ỡ' => 'Ỡ', + 'ỡ' => 'ỡ', + 'Ợ' => 'Ợ', + 'ợ' => 'ợ', + 'Ụ' => 'Ụ', + 'ụ' => 'ụ', + 'Ủ' => 'Ủ', + 'ủ' => 'ủ', + 'Ứ' => 'Ứ', + 'ứ' => 'ứ', + 'Ừ' => 'Ừ', + 'ừ' => 'ừ', + 'Ử' => 'Ử', + 'ử' => 'ử', + 'Ữ' => 'Ữ', + 'ữ' => 'ữ', + 'Ự' => 'Ự', + 'ự' => 'ự', + 'Ỳ' => 'Ỳ', + 'ỳ' => 'ỳ', + 'Ỵ' => 'Ỵ', + 'ỵ' => 'ỵ', + 'Ỷ' => 'Ỷ', + 'ỷ' => 'ỷ', + 'Ỹ' => 'Ỹ', + 'ỹ' => 'ỹ', + 'ἀ' => 'ἀ', + 'ἁ' => 'ἁ', + 'ἂ' => 'ἂ', + 'ἃ' => 'ἃ', + 'ἄ' => 'ἄ', + 'ἅ' => 'ἅ', + 'ἆ' => 'ἆ', + 'ἇ' => 'ἇ', + 'Ἀ' => 'Ἀ', + 'Ἁ' => 'Ἁ', + 'Ἂ' => 'Ἂ', + 'Ἃ' => 'Ἃ', + 'Ἄ' => 'Ἄ', + 'Ἅ' => 'Ἅ', + 'Ἆ' => 'Ἆ', + 'Ἇ' => 'Ἇ', + 'ἐ' => 'ἐ', + 'ἑ' => 'ἑ', + 'ἒ' => 'ἒ', + 'ἓ' => 'ἓ', + 'ἔ' => 'ἔ', + 'ἕ' => 'ἕ', + 'Ἐ' => 'Ἐ', + 'Ἑ' => 'Ἑ', + 'Ἒ' => 'Ἒ', + 'Ἓ' => 'Ἓ', + 'Ἔ' => 'Ἔ', + 'Ἕ' => 'Ἕ', + 'ἠ' => 'ἠ', + 'ἡ' => 'ἡ', + 'ἢ' => 'ἢ', + 'ἣ' => 'ἣ', + 'ἤ' => 'ἤ', + 'ἥ' => 'ἥ', + 'ἦ' => 'ἦ', + 'ἧ' => 'ἧ', + 'Ἠ' => 'Ἠ', + 'Ἡ' => 'Ἡ', + 'Ἢ' => 'Ἢ', + 'Ἣ' => 'Ἣ', + 'Ἤ' => 'Ἤ', + 'Ἥ' => 'Ἥ', + 'Ἦ' => 'Ἦ', + 'Ἧ' => 'Ἧ', + 'ἰ' => 'ἰ', + 'ἱ' => 'ἱ', + 'ἲ' => 'ἲ', + 'ἳ' => 'ἳ', + 'ἴ' => 'ἴ', + 'ἵ' => 'ἵ', + 'ἶ' => 'ἶ', + 'ἷ' => 'ἷ', + 'Ἰ' => 'Ἰ', + 'Ἱ' => 'Ἱ', + 'Ἲ' => 'Ἲ', + 'Ἳ' => 'Ἳ', + 'Ἴ' => 'Ἴ', + 'Ἵ' => 'Ἵ', + 'Ἶ' => 'Ἶ', + 'Ἷ' => 'Ἷ', + 'ὀ' => 'ὀ', + 'ὁ' => 'ὁ', + 'ὂ' => 'ὂ', + 'ὃ' => 'ὃ', + 'ὄ' => 'ὄ', + 'ὅ' => 'ὅ', + 'Ὀ' => 'Ὀ', + 'Ὁ' => 'Ὁ', + 'Ὂ' => 'Ὂ', + 'Ὃ' => 'Ὃ', + 'Ὄ' => 'Ὄ', + 'Ὅ' => 'Ὅ', + 'ὐ' => 'ὐ', + 'ὑ' => 'ὑ', + 'ὒ' => 'ὒ', + 'ὓ' => 'ὓ', + 'ὔ' => 'ὔ', + 'ὕ' => 'ὕ', + 'ὖ' => 'ὖ', + 'ὗ' => 'ὗ', + 'Ὑ' => 'Ὑ', + 'Ὓ' => 'Ὓ', + 'Ὕ' => 'Ὕ', + 'Ὗ' => 'Ὗ', + 'ὠ' => 'ὠ', + 'ὡ' => 'ὡ', + 'ὢ' => 'ὢ', + 'ὣ' => 'ὣ', + 'ὤ' => 'ὤ', + 'ὥ' => 'ὥ', + 'ὦ' => 'ὦ', + 'ὧ' => 'ὧ', + 'Ὠ' => 'Ὠ', + 'Ὡ' => 'Ὡ', + 'Ὢ' => 'Ὢ', + 'Ὣ' => 'Ὣ', + 'Ὤ' => 'Ὤ', + 'Ὥ' => 'Ὥ', + 'Ὦ' => 'Ὦ', + 'Ὧ' => 'Ὧ', + 'ὰ' => 'ὰ', + 'ὲ' => 'ὲ', + 'ὴ' => 'ὴ', + 'ὶ' => 'ὶ', + 'ὸ' => 'ὸ', + 'ὺ' => 'ὺ', + 'ὼ' => 'ὼ', + 'ᾀ' => 'ᾀ', + 'ᾁ' => 'ᾁ', + 'ᾂ' => 'ᾂ', + 'ᾃ' => 'ᾃ', + 'ᾄ' => 'ᾄ', + 'ᾅ' => 'ᾅ', + 'ᾆ' => 'ᾆ', + 'ᾇ' => 'ᾇ', + 'ᾈ' => 'ᾈ', + 'ᾉ' => 'ᾉ', + 'ᾊ' => 'ᾊ', + 'ᾋ' => 'ᾋ', + 'ᾌ' => 'ᾌ', + 'ᾍ' => 'ᾍ', + 'ᾎ' => 'ᾎ', + 'ᾏ' => 'ᾏ', + 'ᾐ' => 'ᾐ', + 'ᾑ' => 'ᾑ', + 'ᾒ' => 'ᾒ', + 'ᾓ' => 'ᾓ', + 'ᾔ' => 'ᾔ', + 'ᾕ' => 'ᾕ', + 'ᾖ' => 'ᾖ', + 'ᾗ' => 'ᾗ', + 'ᾘ' => 'ᾘ', + 'ᾙ' => 'ᾙ', + 'ᾚ' => 'ᾚ', + 'ᾛ' => 'ᾛ', + 'ᾜ' => 'ᾜ', + 'ᾝ' => 'ᾝ', + 'ᾞ' => 'ᾞ', + 'ᾟ' => 'ᾟ', + 'ᾠ' => 'ᾠ', + 'ᾡ' => 'ᾡ', + 'ᾢ' => 'ᾢ', + 'ᾣ' => 'ᾣ', + 'ᾤ' => 'ᾤ', + 'ᾥ' => 'ᾥ', + 'ᾦ' => 'ᾦ', + 'ᾧ' => 'ᾧ', + 'ᾨ' => 'ᾨ', + 'ᾩ' => 'ᾩ', + 'ᾪ' => 'ᾪ', + 'ᾫ' => 'ᾫ', + 'ᾬ' => 'ᾬ', + 'ᾭ' => 'ᾭ', + 'ᾮ' => 'ᾮ', + 'ᾯ' => 'ᾯ', + 'ᾰ' => 'ᾰ', + 'ᾱ' => 'ᾱ', + 'ᾲ' => 'ᾲ', + 'ᾳ' => 'ᾳ', + 'ᾴ' => 'ᾴ', + 'ᾶ' => 'ᾶ', + 'ᾷ' => 'ᾷ', + 'Ᾰ' => 'Ᾰ', + 'Ᾱ' => 'Ᾱ', + 'Ὰ' => 'Ὰ', + 'ᾼ' => 'ᾼ', + '῁' => '῁', + 'ῂ' => 'ῂ', + 'ῃ' => 'ῃ', + 'ῄ' => 'ῄ', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῇ', + 'Ὲ' => 'Ὲ', + 'Ὴ' => 'Ὴ', + 'ῌ' => 'ῌ', + '῍' => '῍', + '῎' => '῎', + '῏' => '῏', + 'ῐ' => 'ῐ', + 'ῑ' => 'ῑ', + 'ῒ' => 'ῒ', + 'ῖ' => 'ῖ', + 'ῗ' => 'ῗ', + 'Ῐ' => 'Ῐ', + 'Ῑ' => 'Ῑ', + 'Ὶ' => 'Ὶ', + '῝' => '῝', + '῞' => '῞', + '῟' => '῟', + 'ῠ' => 'ῠ', + 'ῡ' => 'ῡ', + 'ῢ' => 'ῢ', + 'ῤ' => 'ῤ', + 'ῥ' => 'ῥ', + 'ῦ' => 'ῦ', + 'ῧ' => 'ῧ', + 'Ῠ' => 'Ῠ', + 'Ῡ' => 'Ῡ', + 'Ὺ' => 'Ὺ', + 'Ῥ' => 'Ῥ', + '῭' => '῭', + 'ῲ' => 'ῲ', + 'ῳ' => 'ῳ', + 'ῴ' => 'ῴ', + 'ῶ' => 'ῶ', + 'ῷ' => 'ῷ', + 'Ὸ' => 'Ὸ', + 'Ὼ' => 'Ὼ', + 'ῼ' => 'ῼ', + '↚' => '↚', + '↛' => '↛', + '↮' => '↮', + '⇍' => '⇍', + '⇎' => '⇎', + '⇏' => '⇏', + '∄' => '∄', + '∉' => '∉', + '∌' => '∌', + '∤' => '∤', + '∦' => '∦', + '≁' => '≁', + '≄' => '≄', + '≇' => '≇', + '≉' => '≉', + '≠' => '≠', + '≢' => '≢', + '≭' => '≭', + '≮' => '≮', + '≯' => '≯', + '≰' => '≰', + '≱' => '≱', + '≴' => '≴', + '≵' => '≵', + '≸' => '≸', + '≹' => '≹', + '⊀' => '⊀', + '⊁' => '⊁', + '⊄' => '⊄', + '⊅' => '⊅', + '⊈' => '⊈', + '⊉' => '⊉', + '⊬' => '⊬', + '⊭' => '⊭', + '⊮' => '⊮', + '⊯' => '⊯', + '⋠' => '⋠', + '⋡' => '⋡', + '⋢' => '⋢', + '⋣' => '⋣', + '⋪' => '⋪', + '⋫' => '⋫', + '⋬' => '⋬', + '⋭' => '⋭', + 'が' => 'が', + 'ぎ' => 'ぎ', + 'ぐ' => 'ぐ', + 'げ' => 'げ', + 'ご' => 'ご', + 'ざ' => 'ざ', + 'じ' => 'じ', + 'ず' => 'ず', + 'ぜ' => 'ぜ', + 'ぞ' => 'ぞ', + 'だ' => 'だ', + 'ぢ' => 'ぢ', + 'づ' => 'づ', + 'で' => 'で', + 'ど' => 'ど', + 'ば' => 'ば', + 'ぱ' => 'ぱ', + 'び' => 'び', + 'ぴ' => 'ぴ', + 'ぶ' => 'ぶ', + 'ぷ' => 'ぷ', + 'べ' => 'べ', + 'ぺ' => 'ぺ', + 'ぼ' => 'ぼ', + 'ぽ' => 'ぽ', + 'ゔ' => 'ゔ', + 'ゞ' => 'ゞ', + 'ガ' => 'ガ', + 'ギ' => 'ギ', + 'グ' => 'グ', + 'ゲ' => 'ゲ', + 'ゴ' => 'ゴ', + 'ザ' => 'ザ', + 'ジ' => 'ジ', + 'ズ' => 'ズ', + 'ゼ' => 'ゼ', + 'ゾ' => 'ゾ', + 'ダ' => 'ダ', + 'ヂ' => 'ヂ', + 'ヅ' => 'ヅ', + 'デ' => 'デ', + 'ド' => 'ド', + 'バ' => 'バ', + 'パ' => 'パ', + 'ビ' => 'ビ', + 'ピ' => 'ピ', + 'ブ' => 'ブ', + 'プ' => 'プ', + 'ベ' => 'ベ', + 'ペ' => 'ペ', + 'ボ' => 'ボ', + 'ポ' => 'ポ', + 'ヴ' => 'ヴ', + 'ヷ' => 'ヷ', + 'ヸ' => 'ヸ', + 'ヹ' => 'ヹ', + 'ヺ' => 'ヺ', + 'ヾ' => 'ヾ', + '𑂚' => '𑂚', + '𑂜' => '𑂜', + '𑂫' => '𑂫', + '𑄮' => '𑄮', + '𑄯' => '𑄯', + '𑍋' => '𑍋', + '𑍌' => '𑍌', + '𑒻' => '𑒻', + '𑒼' => '𑒼', + '𑒾' => '𑒾', + '𑖺' => '𑖺', + '𑖻' => '𑖻', + '𑤸' => '𑤸', +); diff --git a/lib/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php b/lib/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php new file mode 100644 index 0000000000..5a3e8e0969 --- /dev/null +++ b/lib/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php @@ -0,0 +1,2065 @@ + 'À', + 'Á' => 'Á', + 'Â' => 'Â', + 'Ã' => 'Ã', + 'Ä' => 'Ä', + 'Å' => 'Å', + 'Ç' => 'Ç', + 'È' => 'È', + 'É' => 'É', + 'Ê' => 'Ê', + 'Ë' => 'Ë', + 'Ì' => 'Ì', + 'Í' => 'Í', + 'Î' => 'Î', + 'Ï' => 'Ï', + 'Ñ' => 'Ñ', + 'Ò' => 'Ò', + 'Ó' => 'Ó', + 'Ô' => 'Ô', + 'Õ' => 'Õ', + 'Ö' => 'Ö', + 'Ù' => 'Ù', + 'Ú' => 'Ú', + 'Û' => 'Û', + 'Ü' => 'Ü', + 'Ý' => 'Ý', + 'à' => 'à', + 'á' => 'á', + 'â' => 'â', + 'ã' => 'ã', + 'ä' => 'ä', + 'å' => 'å', + 'ç' => 'ç', + 'è' => 'è', + 'é' => 'é', + 'ê' => 'ê', + 'ë' => 'ë', + 'ì' => 'ì', + 'í' => 'í', + 'î' => 'î', + 'ï' => 'ï', + 'ñ' => 'ñ', + 'ò' => 'ò', + 'ó' => 'ó', + 'ô' => 'ô', + 'õ' => 'õ', + 'ö' => 'ö', + 'ù' => 'ù', + 'ú' => 'ú', + 'û' => 'û', + 'ü' => 'ü', + 'ý' => 'ý', + 'ÿ' => 'ÿ', + 'Ā' => 'Ā', + 'ā' => 'ā', + 'Ă' => 'Ă', + 'ă' => 'ă', + 'Ą' => 'Ą', + 'ą' => 'ą', + 'Ć' => 'Ć', + 'ć' => 'ć', + 'Ĉ' => 'Ĉ', + 'ĉ' => 'ĉ', + 'Ċ' => 'Ċ', + 'ċ' => 'ċ', + 'Č' => 'Č', + 'č' => 'č', + 'Ď' => 'Ď', + 'ď' => 'ď', + 'Ē' => 'Ē', + 'ē' => 'ē', + 'Ĕ' => 'Ĕ', + 'ĕ' => 'ĕ', + 'Ė' => 'Ė', + 'ė' => 'ė', + 'Ę' => 'Ę', + 'ę' => 'ę', + 'Ě' => 'Ě', + 'ě' => 'ě', + 'Ĝ' => 'Ĝ', + 'ĝ' => 'ĝ', + 'Ğ' => 'Ğ', + 'ğ' => 'ğ', + 'Ġ' => 'Ġ', + 'ġ' => 'ġ', + 'Ģ' => 'Ģ', + 'ģ' => 'ģ', + 'Ĥ' => 'Ĥ', + 'ĥ' => 'ĥ', + 'Ĩ' => 'Ĩ', + 'ĩ' => 'ĩ', + 'Ī' => 'Ī', + 'ī' => 'ī', + 'Ĭ' => 'Ĭ', + 'ĭ' => 'ĭ', + 'Į' => 'Į', + 'į' => 'į', + 'İ' => 'İ', + 'Ĵ' => 'Ĵ', + 'ĵ' => 'ĵ', + 'Ķ' => 'Ķ', + 'ķ' => 'ķ', + 'Ĺ' => 'Ĺ', + 'ĺ' => 'ĺ', + 'Ļ' => 'Ļ', + 'ļ' => 'ļ', + 'Ľ' => 'Ľ', + 'ľ' => 'ľ', + 'Ń' => 'Ń', + 'ń' => 'ń', + 'Ņ' => 'Ņ', + 'ņ' => 'ņ', + 'Ň' => 'Ň', + 'ň' => 'ň', + 'Ō' => 'Ō', + 'ō' => 'ō', + 'Ŏ' => 'Ŏ', + 'ŏ' => 'ŏ', + 'Ő' => 'Ő', + 'ő' => 'ő', + 'Ŕ' => 'Ŕ', + 'ŕ' => 'ŕ', + 'Ŗ' => 'Ŗ', + 'ŗ' => 'ŗ', + 'Ř' => 'Ř', + 'ř' => 'ř', + 'Ś' => 'Ś', + 'ś' => 'ś', + 'Ŝ' => 'Ŝ', + 'ŝ' => 'ŝ', + 'Ş' => 'Ş', + 'ş' => 'ş', + 'Š' => 'Š', + 'š' => 'š', + 'Ţ' => 'Ţ', + 'ţ' => 'ţ', + 'Ť' => 'Ť', + 'ť' => 'ť', + 'Ũ' => 'Ũ', + 'ũ' => 'ũ', + 'Ū' => 'Ū', + 'ū' => 'ū', + 'Ŭ' => 'Ŭ', + 'ŭ' => 'ŭ', + 'Ů' => 'Ů', + 'ů' => 'ů', + 'Ű' => 'Ű', + 'ű' => 'ű', + 'Ų' => 'Ų', + 'ų' => 'ų', + 'Ŵ' => 'Ŵ', + 'ŵ' => 'ŵ', + 'Ŷ' => 'Ŷ', + 'ŷ' => 'ŷ', + 'Ÿ' => 'Ÿ', + 'Ź' => 'Ź', + 'ź' => 'ź', + 'Ż' => 'Ż', + 'ż' => 'ż', + 'Ž' => 'Ž', + 'ž' => 'ž', + 'Ơ' => 'Ơ', + 'ơ' => 'ơ', + 'Ư' => 'Ư', + 'ư' => 'ư', + 'Ǎ' => 'Ǎ', + 'ǎ' => 'ǎ', + 'Ǐ' => 'Ǐ', + 'ǐ' => 'ǐ', + 'Ǒ' => 'Ǒ', + 'ǒ' => 'ǒ', + 'Ǔ' => 'Ǔ', + 'ǔ' => 'ǔ', + 'Ǖ' => 'Ǖ', + 'ǖ' => 'ǖ', + 'Ǘ' => 'Ǘ', + 'ǘ' => 'ǘ', + 'Ǚ' => 'Ǚ', + 'ǚ' => 'ǚ', + 'Ǜ' => 'Ǜ', + 'ǜ' => 'ǜ', + 'Ǟ' => 'Ǟ', + 'ǟ' => 'ǟ', + 'Ǡ' => 'Ǡ', + 'ǡ' => 'ǡ', + 'Ǣ' => 'Ǣ', + 'ǣ' => 'ǣ', + 'Ǧ' => 'Ǧ', + 'ǧ' => 'ǧ', + 'Ǩ' => 'Ǩ', + 'ǩ' => 'ǩ', + 'Ǫ' => 'Ǫ', + 'ǫ' => 'ǫ', + 'Ǭ' => 'Ǭ', + 'ǭ' => 'ǭ', + 'Ǯ' => 'Ǯ', + 'ǯ' => 'ǯ', + 'ǰ' => 'ǰ', + 'Ǵ' => 'Ǵ', + 'ǵ' => 'ǵ', + 'Ǹ' => 'Ǹ', + 'ǹ' => 'ǹ', + 'Ǻ' => 'Ǻ', + 'ǻ' => 'ǻ', + 'Ǽ' => 'Ǽ', + 'ǽ' => 'ǽ', + 'Ǿ' => 'Ǿ', + 'ǿ' => 'ǿ', + 'Ȁ' => 'Ȁ', + 'ȁ' => 'ȁ', + 'Ȃ' => 'Ȃ', + 'ȃ' => 'ȃ', + 'Ȅ' => 'Ȅ', + 'ȅ' => 'ȅ', + 'Ȇ' => 'Ȇ', + 'ȇ' => 'ȇ', + 'Ȉ' => 'Ȉ', + 'ȉ' => 'ȉ', + 'Ȋ' => 'Ȋ', + 'ȋ' => 'ȋ', + 'Ȍ' => 'Ȍ', + 'ȍ' => 'ȍ', + 'Ȏ' => 'Ȏ', + 'ȏ' => 'ȏ', + 'Ȑ' => 'Ȑ', + 'ȑ' => 'ȑ', + 'Ȓ' => 'Ȓ', + 'ȓ' => 'ȓ', + 'Ȕ' => 'Ȕ', + 'ȕ' => 'ȕ', + 'Ȗ' => 'Ȗ', + 'ȗ' => 'ȗ', + 'Ș' => 'Ș', + 'ș' => 'ș', + 'Ț' => 'Ț', + 'ț' => 'ț', + 'Ȟ' => 'Ȟ', + 'ȟ' => 'ȟ', + 'Ȧ' => 'Ȧ', + 'ȧ' => 'ȧ', + 'Ȩ' => 'Ȩ', + 'ȩ' => 'ȩ', + 'Ȫ' => 'Ȫ', + 'ȫ' => 'ȫ', + 'Ȭ' => 'Ȭ', + 'ȭ' => 'ȭ', + 'Ȯ' => 'Ȯ', + 'ȯ' => 'ȯ', + 'Ȱ' => 'Ȱ', + 'ȱ' => 'ȱ', + 'Ȳ' => 'Ȳ', + 'ȳ' => 'ȳ', + '̀' => '̀', + '́' => '́', + '̓' => '̓', + '̈́' => '̈́', + 'ʹ' => 'ʹ', + ';' => ';', + '΅' => '΅', + 'Ά' => 'Ά', + '·' => '·', + 'Έ' => 'Έ', + 'Ή' => 'Ή', + 'Ί' => 'Ί', + 'Ό' => 'Ό', + 'Ύ' => 'Ύ', + 'Ώ' => 'Ώ', + 'ΐ' => 'ΐ', + 'Ϊ' => 'Ϊ', + 'Ϋ' => 'Ϋ', + 'ά' => 'ά', + 'έ' => 'έ', + 'ή' => 'ή', + 'ί' => 'ί', + 'ΰ' => 'ΰ', + 'ϊ' => 'ϊ', + 'ϋ' => 'ϋ', + 'ό' => 'ό', + 'ύ' => 'ύ', + 'ώ' => 'ώ', + 'ϓ' => 'ϓ', + 'ϔ' => 'ϔ', + 'Ѐ' => 'Ѐ', + 'Ё' => 'Ё', + 'Ѓ' => 'Ѓ', + 'Ї' => 'Ї', + 'Ќ' => 'Ќ', + 'Ѝ' => 'Ѝ', + 'Ў' => 'Ў', + 'Й' => 'Й', + 'й' => 'й', + 'ѐ' => 'ѐ', + 'ё' => 'ё', + 'ѓ' => 'ѓ', + 'ї' => 'ї', + 'ќ' => 'ќ', + 'ѝ' => 'ѝ', + 'ў' => 'ў', + 'Ѷ' => 'Ѷ', + 'ѷ' => 'ѷ', + 'Ӂ' => 'Ӂ', + 'ӂ' => 'ӂ', + 'Ӑ' => 'Ӑ', + 'ӑ' => 'ӑ', + 'Ӓ' => 'Ӓ', + 'ӓ' => 'ӓ', + 'Ӗ' => 'Ӗ', + 'ӗ' => 'ӗ', + 'Ӛ' => 'Ӛ', + 'ӛ' => 'ӛ', + 'Ӝ' => 'Ӝ', + 'ӝ' => 'ӝ', + 'Ӟ' => 'Ӟ', + 'ӟ' => 'ӟ', + 'Ӣ' => 'Ӣ', + 'ӣ' => 'ӣ', + 'Ӥ' => 'Ӥ', + 'ӥ' => 'ӥ', + 'Ӧ' => 'Ӧ', + 'ӧ' => 'ӧ', + 'Ӫ' => 'Ӫ', + 'ӫ' => 'ӫ', + 'Ӭ' => 'Ӭ', + 'ӭ' => 'ӭ', + 'Ӯ' => 'Ӯ', + 'ӯ' => 'ӯ', + 'Ӱ' => 'Ӱ', + 'ӱ' => 'ӱ', + 'Ӳ' => 'Ӳ', + 'ӳ' => 'ӳ', + 'Ӵ' => 'Ӵ', + 'ӵ' => 'ӵ', + 'Ӹ' => 'Ӹ', + 'ӹ' => 'ӹ', + 'آ' => 'آ', + 'أ' => 'أ', + 'ؤ' => 'ؤ', + 'إ' => 'إ', + 'ئ' => 'ئ', + 'ۀ' => 'ۀ', + 'ۂ' => 'ۂ', + 'ۓ' => 'ۓ', + 'ऩ' => 'ऩ', + 'ऱ' => 'ऱ', + 'ऴ' => 'ऴ', + 'क़' => 'क़', + 'ख़' => 'ख़', + 'ग़' => 'ग़', + 'ज़' => 'ज़', + 'ड़' => 'ड़', + 'ढ़' => 'ढ़', + 'फ़' => 'फ़', + 'य़' => 'य़', + 'ো' => 'ো', + 'ৌ' => 'ৌ', + 'ড়' => 'ড়', + 'ঢ়' => 'ঢ়', + 'য়' => 'য়', + 'ਲ਼' => 'ਲ਼', + 'ਸ਼' => 'ਸ਼', + 'ਖ਼' => 'ਖ਼', + 'ਗ਼' => 'ਗ਼', + 'ਜ਼' => 'ਜ਼', + 'ਫ਼' => 'ਫ਼', + 'ୈ' => 'ୈ', + 'ୋ' => 'ୋ', + 'ୌ' => 'ୌ', + 'ଡ଼' => 'ଡ଼', + 'ଢ଼' => 'ଢ଼', + 'ஔ' => 'ஔ', + 'ொ' => 'ொ', + 'ோ' => 'ோ', + 'ௌ' => 'ௌ', + 'ై' => 'ై', + 'ೀ' => 'ೀ', + 'ೇ' => 'ೇ', + 'ೈ' => 'ೈ', + 'ೊ' => 'ೊ', + 'ೋ' => 'ೋ', + 'ൊ' => 'ൊ', + 'ോ' => 'ോ', + 'ൌ' => 'ൌ', + 'ේ' => 'ේ', + 'ො' => 'ො', + 'ෝ' => 'ෝ', + 'ෞ' => 'ෞ', + 'གྷ' => 'གྷ', + 'ཌྷ' => 'ཌྷ', + 'དྷ' => 'དྷ', + 'བྷ' => 'བྷ', + 'ཛྷ' => 'ཛྷ', + 'ཀྵ' => 'ཀྵ', + 'ཱི' => 'ཱི', + 'ཱུ' => 'ཱུ', + 'ྲྀ' => 'ྲྀ', + 'ླྀ' => 'ླྀ', + 'ཱྀ' => 'ཱྀ', + 'ྒྷ' => 'ྒྷ', + 'ྜྷ' => 'ྜྷ', + 'ྡྷ' => 'ྡྷ', + 'ྦྷ' => 'ྦྷ', + 'ྫྷ' => 'ྫྷ', + 'ྐྵ' => 'ྐྵ', + 'ဦ' => 'ဦ', + 'ᬆ' => 'ᬆ', + 'ᬈ' => 'ᬈ', + 'ᬊ' => 'ᬊ', + 'ᬌ' => 'ᬌ', + 'ᬎ' => 'ᬎ', + 'ᬒ' => 'ᬒ', + 'ᬻ' => 'ᬻ', + 'ᬽ' => 'ᬽ', + 'ᭀ' => 'ᭀ', + 'ᭁ' => 'ᭁ', + 'ᭃ' => 'ᭃ', + 'Ḁ' => 'Ḁ', + 'ḁ' => 'ḁ', + 'Ḃ' => 'Ḃ', + 'ḃ' => 'ḃ', + 'Ḅ' => 'Ḅ', + 'ḅ' => 'ḅ', + 'Ḇ' => 'Ḇ', + 'ḇ' => 'ḇ', + 'Ḉ' => 'Ḉ', + 'ḉ' => 'ḉ', + 'Ḋ' => 'Ḋ', + 'ḋ' => 'ḋ', + 'Ḍ' => 'Ḍ', + 'ḍ' => 'ḍ', + 'Ḏ' => 'Ḏ', + 'ḏ' => 'ḏ', + 'Ḑ' => 'Ḑ', + 'ḑ' => 'ḑ', + 'Ḓ' => 'Ḓ', + 'ḓ' => 'ḓ', + 'Ḕ' => 'Ḕ', + 'ḕ' => 'ḕ', + 'Ḗ' => 'Ḗ', + 'ḗ' => 'ḗ', + 'Ḙ' => 'Ḙ', + 'ḙ' => 'ḙ', + 'Ḛ' => 'Ḛ', + 'ḛ' => 'ḛ', + 'Ḝ' => 'Ḝ', + 'ḝ' => 'ḝ', + 'Ḟ' => 'Ḟ', + 'ḟ' => 'ḟ', + 'Ḡ' => 'Ḡ', + 'ḡ' => 'ḡ', + 'Ḣ' => 'Ḣ', + 'ḣ' => 'ḣ', + 'Ḥ' => 'Ḥ', + 'ḥ' => 'ḥ', + 'Ḧ' => 'Ḧ', + 'ḧ' => 'ḧ', + 'Ḩ' => 'Ḩ', + 'ḩ' => 'ḩ', + 'Ḫ' => 'Ḫ', + 'ḫ' => 'ḫ', + 'Ḭ' => 'Ḭ', + 'ḭ' => 'ḭ', + 'Ḯ' => 'Ḯ', + 'ḯ' => 'ḯ', + 'Ḱ' => 'Ḱ', + 'ḱ' => 'ḱ', + 'Ḳ' => 'Ḳ', + 'ḳ' => 'ḳ', + 'Ḵ' => 'Ḵ', + 'ḵ' => 'ḵ', + 'Ḷ' => 'Ḷ', + 'ḷ' => 'ḷ', + 'Ḹ' => 'Ḹ', + 'ḹ' => 'ḹ', + 'Ḻ' => 'Ḻ', + 'ḻ' => 'ḻ', + 'Ḽ' => 'Ḽ', + 'ḽ' => 'ḽ', + 'Ḿ' => 'Ḿ', + 'ḿ' => 'ḿ', + 'Ṁ' => 'Ṁ', + 'ṁ' => 'ṁ', + 'Ṃ' => 'Ṃ', + 'ṃ' => 'ṃ', + 'Ṅ' => 'Ṅ', + 'ṅ' => 'ṅ', + 'Ṇ' => 'Ṇ', + 'ṇ' => 'ṇ', + 'Ṉ' => 'Ṉ', + 'ṉ' => 'ṉ', + 'Ṋ' => 'Ṋ', + 'ṋ' => 'ṋ', + 'Ṍ' => 'Ṍ', + 'ṍ' => 'ṍ', + 'Ṏ' => 'Ṏ', + 'ṏ' => 'ṏ', + 'Ṑ' => 'Ṑ', + 'ṑ' => 'ṑ', + 'Ṓ' => 'Ṓ', + 'ṓ' => 'ṓ', + 'Ṕ' => 'Ṕ', + 'ṕ' => 'ṕ', + 'Ṗ' => 'Ṗ', + 'ṗ' => 'ṗ', + 'Ṙ' => 'Ṙ', + 'ṙ' => 'ṙ', + 'Ṛ' => 'Ṛ', + 'ṛ' => 'ṛ', + 'Ṝ' => 'Ṝ', + 'ṝ' => 'ṝ', + 'Ṟ' => 'Ṟ', + 'ṟ' => 'ṟ', + 'Ṡ' => 'Ṡ', + 'ṡ' => 'ṡ', + 'Ṣ' => 'Ṣ', + 'ṣ' => 'ṣ', + 'Ṥ' => 'Ṥ', + 'ṥ' => 'ṥ', + 'Ṧ' => 'Ṧ', + 'ṧ' => 'ṧ', + 'Ṩ' => 'Ṩ', + 'ṩ' => 'ṩ', + 'Ṫ' => 'Ṫ', + 'ṫ' => 'ṫ', + 'Ṭ' => 'Ṭ', + 'ṭ' => 'ṭ', + 'Ṯ' => 'Ṯ', + 'ṯ' => 'ṯ', + 'Ṱ' => 'Ṱ', + 'ṱ' => 'ṱ', + 'Ṳ' => 'Ṳ', + 'ṳ' => 'ṳ', + 'Ṵ' => 'Ṵ', + 'ṵ' => 'ṵ', + 'Ṷ' => 'Ṷ', + 'ṷ' => 'ṷ', + 'Ṹ' => 'Ṹ', + 'ṹ' => 'ṹ', + 'Ṻ' => 'Ṻ', + 'ṻ' => 'ṻ', + 'Ṽ' => 'Ṽ', + 'ṽ' => 'ṽ', + 'Ṿ' => 'Ṿ', + 'ṿ' => 'ṿ', + 'Ẁ' => 'Ẁ', + 'ẁ' => 'ẁ', + 'Ẃ' => 'Ẃ', + 'ẃ' => 'ẃ', + 'Ẅ' => 'Ẅ', + 'ẅ' => 'ẅ', + 'Ẇ' => 'Ẇ', + 'ẇ' => 'ẇ', + 'Ẉ' => 'Ẉ', + 'ẉ' => 'ẉ', + 'Ẋ' => 'Ẋ', + 'ẋ' => 'ẋ', + 'Ẍ' => 'Ẍ', + 'ẍ' => 'ẍ', + 'Ẏ' => 'Ẏ', + 'ẏ' => 'ẏ', + 'Ẑ' => 'Ẑ', + 'ẑ' => 'ẑ', + 'Ẓ' => 'Ẓ', + 'ẓ' => 'ẓ', + 'Ẕ' => 'Ẕ', + 'ẕ' => 'ẕ', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'ẘ' => 'ẘ', + 'ẙ' => 'ẙ', + 'ẛ' => 'ẛ', + 'Ạ' => 'Ạ', + 'ạ' => 'ạ', + 'Ả' => 'Ả', + 'ả' => 'ả', + 'Ấ' => 'Ấ', + 'ấ' => 'ấ', + 'Ầ' => 'Ầ', + 'ầ' => 'ầ', + 'Ẩ' => 'Ẩ', + 'ẩ' => 'ẩ', + 'Ẫ' => 'Ẫ', + 'ẫ' => 'ẫ', + 'Ậ' => 'Ậ', + 'ậ' => 'ậ', + 'Ắ' => 'Ắ', + 'ắ' => 'ắ', + 'Ằ' => 'Ằ', + 'ằ' => 'ằ', + 'Ẳ' => 'Ẳ', + 'ẳ' => 'ẳ', + 'Ẵ' => 'Ẵ', + 'ẵ' => 'ẵ', + 'Ặ' => 'Ặ', + 'ặ' => 'ặ', + 'Ẹ' => 'Ẹ', + 'ẹ' => 'ẹ', + 'Ẻ' => 'Ẻ', + 'ẻ' => 'ẻ', + 'Ẽ' => 'Ẽ', + 'ẽ' => 'ẽ', + 'Ế' => 'Ế', + 'ế' => 'ế', + 'Ề' => 'Ề', + 'ề' => 'ề', + 'Ể' => 'Ể', + 'ể' => 'ể', + 'Ễ' => 'Ễ', + 'ễ' => 'ễ', + 'Ệ' => 'Ệ', + 'ệ' => 'ệ', + 'Ỉ' => 'Ỉ', + 'ỉ' => 'ỉ', + 'Ị' => 'Ị', + 'ị' => 'ị', + 'Ọ' => 'Ọ', + 'ọ' => 'ọ', + 'Ỏ' => 'Ỏ', + 'ỏ' => 'ỏ', + 'Ố' => 'Ố', + 'ố' => 'ố', + 'Ồ' => 'Ồ', + 'ồ' => 'ồ', + 'Ổ' => 'Ổ', + 'ổ' => 'ổ', + 'Ỗ' => 'Ỗ', + 'ỗ' => 'ỗ', + 'Ộ' => 'Ộ', + 'ộ' => 'ộ', + 'Ớ' => 'Ớ', + 'ớ' => 'ớ', + 'Ờ' => 'Ờ', + 'ờ' => 'ờ', + 'Ở' => 'Ở', + 'ở' => 'ở', + 'Ỡ' => 'Ỡ', + 'ỡ' => 'ỡ', + 'Ợ' => 'Ợ', + 'ợ' => 'ợ', + 'Ụ' => 'Ụ', + 'ụ' => 'ụ', + 'Ủ' => 'Ủ', + 'ủ' => 'ủ', + 'Ứ' => 'Ứ', + 'ứ' => 'ứ', + 'Ừ' => 'Ừ', + 'ừ' => 'ừ', + 'Ử' => 'Ử', + 'ử' => 'ử', + 'Ữ' => 'Ữ', + 'ữ' => 'ữ', + 'Ự' => 'Ự', + 'ự' => 'ự', + 'Ỳ' => 'Ỳ', + 'ỳ' => 'ỳ', + 'Ỵ' => 'Ỵ', + 'ỵ' => 'ỵ', + 'Ỷ' => 'Ỷ', + 'ỷ' => 'ỷ', + 'Ỹ' => 'Ỹ', + 'ỹ' => 'ỹ', + 'ἀ' => 'ἀ', + 'ἁ' => 'ἁ', + 'ἂ' => 'ἂ', + 'ἃ' => 'ἃ', + 'ἄ' => 'ἄ', + 'ἅ' => 'ἅ', + 'ἆ' => 'ἆ', + 'ἇ' => 'ἇ', + 'Ἀ' => 'Ἀ', + 'Ἁ' => 'Ἁ', + 'Ἂ' => 'Ἂ', + 'Ἃ' => 'Ἃ', + 'Ἄ' => 'Ἄ', + 'Ἅ' => 'Ἅ', + 'Ἆ' => 'Ἆ', + 'Ἇ' => 'Ἇ', + 'ἐ' => 'ἐ', + 'ἑ' => 'ἑ', + 'ἒ' => 'ἒ', + 'ἓ' => 'ἓ', + 'ἔ' => 'ἔ', + 'ἕ' => 'ἕ', + 'Ἐ' => 'Ἐ', + 'Ἑ' => 'Ἑ', + 'Ἒ' => 'Ἒ', + 'Ἓ' => 'Ἓ', + 'Ἔ' => 'Ἔ', + 'Ἕ' => 'Ἕ', + 'ἠ' => 'ἠ', + 'ἡ' => 'ἡ', + 'ἢ' => 'ἢ', + 'ἣ' => 'ἣ', + 'ἤ' => 'ἤ', + 'ἥ' => 'ἥ', + 'ἦ' => 'ἦ', + 'ἧ' => 'ἧ', + 'Ἠ' => 'Ἠ', + 'Ἡ' => 'Ἡ', + 'Ἢ' => 'Ἢ', + 'Ἣ' => 'Ἣ', + 'Ἤ' => 'Ἤ', + 'Ἥ' => 'Ἥ', + 'Ἦ' => 'Ἦ', + 'Ἧ' => 'Ἧ', + 'ἰ' => 'ἰ', + 'ἱ' => 'ἱ', + 'ἲ' => 'ἲ', + 'ἳ' => 'ἳ', + 'ἴ' => 'ἴ', + 'ἵ' => 'ἵ', + 'ἶ' => 'ἶ', + 'ἷ' => 'ἷ', + 'Ἰ' => 'Ἰ', + 'Ἱ' => 'Ἱ', + 'Ἲ' => 'Ἲ', + 'Ἳ' => 'Ἳ', + 'Ἴ' => 'Ἴ', + 'Ἵ' => 'Ἵ', + 'Ἶ' => 'Ἶ', + 'Ἷ' => 'Ἷ', + 'ὀ' => 'ὀ', + 'ὁ' => 'ὁ', + 'ὂ' => 'ὂ', + 'ὃ' => 'ὃ', + 'ὄ' => 'ὄ', + 'ὅ' => 'ὅ', + 'Ὀ' => 'Ὀ', + 'Ὁ' => 'Ὁ', + 'Ὂ' => 'Ὂ', + 'Ὃ' => 'Ὃ', + 'Ὄ' => 'Ὄ', + 'Ὅ' => 'Ὅ', + 'ὐ' => 'ὐ', + 'ὑ' => 'ὑ', + 'ὒ' => 'ὒ', + 'ὓ' => 'ὓ', + 'ὔ' => 'ὔ', + 'ὕ' => 'ὕ', + 'ὖ' => 'ὖ', + 'ὗ' => 'ὗ', + 'Ὑ' => 'Ὑ', + 'Ὓ' => 'Ὓ', + 'Ὕ' => 'Ὕ', + 'Ὗ' => 'Ὗ', + 'ὠ' => 'ὠ', + 'ὡ' => 'ὡ', + 'ὢ' => 'ὢ', + 'ὣ' => 'ὣ', + 'ὤ' => 'ὤ', + 'ὥ' => 'ὥ', + 'ὦ' => 'ὦ', + 'ὧ' => 'ὧ', + 'Ὠ' => 'Ὠ', + 'Ὡ' => 'Ὡ', + 'Ὢ' => 'Ὢ', + 'Ὣ' => 'Ὣ', + 'Ὤ' => 'Ὤ', + 'Ὥ' => 'Ὥ', + 'Ὦ' => 'Ὦ', + 'Ὧ' => 'Ὧ', + 'ὰ' => 'ὰ', + 'ά' => 'ά', + 'ὲ' => 'ὲ', + 'έ' => 'έ', + 'ὴ' => 'ὴ', + 'ή' => 'ή', + 'ὶ' => 'ὶ', + 'ί' => 'ί', + 'ὸ' => 'ὸ', + 'ό' => 'ό', + 'ὺ' => 'ὺ', + 'ύ' => 'ύ', + 'ὼ' => 'ὼ', + 'ώ' => 'ώ', + 'ᾀ' => 'ᾀ', + 'ᾁ' => 'ᾁ', + 'ᾂ' => 'ᾂ', + 'ᾃ' => 'ᾃ', + 'ᾄ' => 'ᾄ', + 'ᾅ' => 'ᾅ', + 'ᾆ' => 'ᾆ', + 'ᾇ' => 'ᾇ', + 'ᾈ' => 'ᾈ', + 'ᾉ' => 'ᾉ', + 'ᾊ' => 'ᾊ', + 'ᾋ' => 'ᾋ', + 'ᾌ' => 'ᾌ', + 'ᾍ' => 'ᾍ', + 'ᾎ' => 'ᾎ', + 'ᾏ' => 'ᾏ', + 'ᾐ' => 'ᾐ', + 'ᾑ' => 'ᾑ', + 'ᾒ' => 'ᾒ', + 'ᾓ' => 'ᾓ', + 'ᾔ' => 'ᾔ', + 'ᾕ' => 'ᾕ', + 'ᾖ' => 'ᾖ', + 'ᾗ' => 'ᾗ', + 'ᾘ' => 'ᾘ', + 'ᾙ' => 'ᾙ', + 'ᾚ' => 'ᾚ', + 'ᾛ' => 'ᾛ', + 'ᾜ' => 'ᾜ', + 'ᾝ' => 'ᾝ', + 'ᾞ' => 'ᾞ', + 'ᾟ' => 'ᾟ', + 'ᾠ' => 'ᾠ', + 'ᾡ' => 'ᾡ', + 'ᾢ' => 'ᾢ', + 'ᾣ' => 'ᾣ', + 'ᾤ' => 'ᾤ', + 'ᾥ' => 'ᾥ', + 'ᾦ' => 'ᾦ', + 'ᾧ' => 'ᾧ', + 'ᾨ' => 'ᾨ', + 'ᾩ' => 'ᾩ', + 'ᾪ' => 'ᾪ', + 'ᾫ' => 'ᾫ', + 'ᾬ' => 'ᾬ', + 'ᾭ' => 'ᾭ', + 'ᾮ' => 'ᾮ', + 'ᾯ' => 'ᾯ', + 'ᾰ' => 'ᾰ', + 'ᾱ' => 'ᾱ', + 'ᾲ' => 'ᾲ', + 'ᾳ' => 'ᾳ', + 'ᾴ' => 'ᾴ', + 'ᾶ' => 'ᾶ', + 'ᾷ' => 'ᾷ', + 'Ᾰ' => 'Ᾰ', + 'Ᾱ' => 'Ᾱ', + 'Ὰ' => 'Ὰ', + 'Ά' => 'Ά', + 'ᾼ' => 'ᾼ', + 'ι' => 'ι', + '῁' => '῁', + 'ῂ' => 'ῂ', + 'ῃ' => 'ῃ', + 'ῄ' => 'ῄ', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῇ', + 'Ὲ' => 'Ὲ', + 'Έ' => 'Έ', + 'Ὴ' => 'Ὴ', + 'Ή' => 'Ή', + 'ῌ' => 'ῌ', + '῍' => '῍', + '῎' => '῎', + '῏' => '῏', + 'ῐ' => 'ῐ', + 'ῑ' => 'ῑ', + 'ῒ' => 'ῒ', + 'ΐ' => 'ΐ', + 'ῖ' => 'ῖ', + 'ῗ' => 'ῗ', + 'Ῐ' => 'Ῐ', + 'Ῑ' => 'Ῑ', + 'Ὶ' => 'Ὶ', + 'Ί' => 'Ί', + '῝' => '῝', + '῞' => '῞', + '῟' => '῟', + 'ῠ' => 'ῠ', + 'ῡ' => 'ῡ', + 'ῢ' => 'ῢ', + 'ΰ' => 'ΰ', + 'ῤ' => 'ῤ', + 'ῥ' => 'ῥ', + 'ῦ' => 'ῦ', + 'ῧ' => 'ῧ', + 'Ῠ' => 'Ῠ', + 'Ῡ' => 'Ῡ', + 'Ὺ' => 'Ὺ', + 'Ύ' => 'Ύ', + 'Ῥ' => 'Ῥ', + '῭' => '῭', + '΅' => '΅', + '`' => '`', + 'ῲ' => 'ῲ', + 'ῳ' => 'ῳ', + 'ῴ' => 'ῴ', + 'ῶ' => 'ῶ', + 'ῷ' => 'ῷ', + 'Ὸ' => 'Ὸ', + 'Ό' => 'Ό', + 'Ὼ' => 'Ὼ', + 'Ώ' => 'Ώ', + 'ῼ' => 'ῼ', + '´' => '´', + ' ' => ' ', + ' ' => ' ', + 'Ω' => 'Ω', + 'K' => 'K', + 'Å' => 'Å', + '↚' => '↚', + '↛' => '↛', + '↮' => '↮', + '⇍' => '⇍', + '⇎' => '⇎', + '⇏' => '⇏', + '∄' => '∄', + '∉' => '∉', + '∌' => '∌', + '∤' => '∤', + '∦' => '∦', + '≁' => '≁', + '≄' => '≄', + '≇' => '≇', + '≉' => '≉', + '≠' => '≠', + '≢' => '≢', + '≭' => '≭', + '≮' => '≮', + '≯' => '≯', + '≰' => '≰', + '≱' => '≱', + '≴' => '≴', + '≵' => '≵', + '≸' => '≸', + '≹' => '≹', + '⊀' => '⊀', + '⊁' => '⊁', + '⊄' => '⊄', + '⊅' => '⊅', + '⊈' => '⊈', + '⊉' => '⊉', + '⊬' => '⊬', + '⊭' => '⊭', + '⊮' => '⊮', + '⊯' => '⊯', + '⋠' => '⋠', + '⋡' => '⋡', + '⋢' => '⋢', + '⋣' => '⋣', + '⋪' => '⋪', + '⋫' => '⋫', + '⋬' => '⋬', + '⋭' => '⋭', + '〈' => '〈', + '〉' => '〉', + '⫝̸' => '⫝̸', + 'が' => 'が', + 'ぎ' => 'ぎ', + 'ぐ' => 'ぐ', + 'げ' => 'げ', + 'ご' => 'ご', + 'ざ' => 'ざ', + 'じ' => 'じ', + 'ず' => 'ず', + 'ぜ' => 'ぜ', + 'ぞ' => 'ぞ', + 'だ' => 'だ', + 'ぢ' => 'ぢ', + 'づ' => 'づ', + 'で' => 'で', + 'ど' => 'ど', + 'ば' => 'ば', + 'ぱ' => 'ぱ', + 'び' => 'び', + 'ぴ' => 'ぴ', + 'ぶ' => 'ぶ', + 'ぷ' => 'ぷ', + 'べ' => 'べ', + 'ぺ' => 'ぺ', + 'ぼ' => 'ぼ', + 'ぽ' => 'ぽ', + 'ゔ' => 'ゔ', + 'ゞ' => 'ゞ', + 'ガ' => 'ガ', + 'ギ' => 'ギ', + 'グ' => 'グ', + 'ゲ' => 'ゲ', + 'ゴ' => 'ゴ', + 'ザ' => 'ザ', + 'ジ' => 'ジ', + 'ズ' => 'ズ', + 'ゼ' => 'ゼ', + 'ゾ' => 'ゾ', + 'ダ' => 'ダ', + 'ヂ' => 'ヂ', + 'ヅ' => 'ヅ', + 'デ' => 'デ', + 'ド' => 'ド', + 'バ' => 'バ', + 'パ' => 'パ', + 'ビ' => 'ビ', + 'ピ' => 'ピ', + 'ブ' => 'ブ', + 'プ' => 'プ', + 'ベ' => 'ベ', + 'ペ' => 'ペ', + 'ボ' => 'ボ', + 'ポ' => 'ポ', + 'ヴ' => 'ヴ', + 'ヷ' => 'ヷ', + 'ヸ' => 'ヸ', + 'ヹ' => 'ヹ', + 'ヺ' => 'ヺ', + 'ヾ' => 'ヾ', + '豈' => '豈', + '更' => '更', + '車' => '車', + '賈' => '賈', + '滑' => '滑', + '串' => '串', + '句' => '句', + '龜' => '龜', + '龜' => '龜', + '契' => '契', + '金' => '金', + '喇' => '喇', + '奈' => '奈', + '懶' => '懶', + '癩' => '癩', + '羅' => '羅', + '蘿' => '蘿', + '螺' => '螺', + '裸' => '裸', + '邏' => '邏', + '樂' => '樂', + '洛' => '洛', + '烙' => '烙', + '珞' => '珞', + '落' => '落', + '酪' => '酪', + '駱' => '駱', + '亂' => '亂', + '卵' => '卵', + '欄' => '欄', + '爛' => '爛', + '蘭' => '蘭', + '鸞' => '鸞', + '嵐' => '嵐', + '濫' => '濫', + '藍' => '藍', + '襤' => '襤', + '拉' => '拉', + '臘' => '臘', + '蠟' => '蠟', + '廊' => '廊', + '朗' => '朗', + '浪' => '浪', + '狼' => '狼', + '郎' => '郎', + '來' => '來', + '冷' => '冷', + '勞' => '勞', + '擄' => '擄', + '櫓' => '櫓', + '爐' => '爐', + '盧' => '盧', + '老' => '老', + '蘆' => '蘆', + '虜' => '虜', + '路' => '路', + '露' => '露', + '魯' => '魯', + '鷺' => '鷺', + '碌' => '碌', + '祿' => '祿', + '綠' => '綠', + '菉' => '菉', + '錄' => '錄', + '鹿' => '鹿', + '論' => '論', + '壟' => '壟', + '弄' => '弄', + '籠' => '籠', + '聾' => '聾', + '牢' => '牢', + '磊' => '磊', + '賂' => '賂', + '雷' => '雷', + '壘' => '壘', + '屢' => '屢', + '樓' => '樓', + '淚' => '淚', + '漏' => '漏', + '累' => '累', + '縷' => '縷', + '陋' => '陋', + '勒' => '勒', + '肋' => '肋', + '凜' => '凜', + '凌' => '凌', + '稜' => '稜', + '綾' => '綾', + '菱' => '菱', + '陵' => '陵', + '讀' => '讀', + '拏' => '拏', + '樂' => '樂', + '諾' => '諾', + '丹' => '丹', + '寧' => '寧', + '怒' => '怒', + '率' => '率', + '異' => '異', + '北' => '北', + '磻' => '磻', + '便' => '便', + '復' => '復', + '不' => '不', + '泌' => '泌', + '數' => '數', + '索' => '索', + '參' => '參', + '塞' => '塞', + '省' => '省', + '葉' => '葉', + '說' => '說', + '殺' => '殺', + '辰' => '辰', + '沈' => '沈', + '拾' => '拾', + '若' => '若', + '掠' => '掠', + '略' => '略', + '亮' => '亮', + '兩' => '兩', + '凉' => '凉', + '梁' => '梁', + '糧' => '糧', + '良' => '良', + '諒' => '諒', + '量' => '量', + '勵' => '勵', + '呂' => '呂', + '女' => '女', + '廬' => '廬', + '旅' => '旅', + '濾' => '濾', + '礪' => '礪', + '閭' => '閭', + '驪' => '驪', + '麗' => '麗', + '黎' => '黎', + '力' => '力', + '曆' => '曆', + '歷' => '歷', + '轢' => '轢', + '年' => '年', + '憐' => '憐', + '戀' => '戀', + '撚' => '撚', + '漣' => '漣', + '煉' => '煉', + '璉' => '璉', + '秊' => '秊', + '練' => '練', + '聯' => '聯', + '輦' => '輦', + '蓮' => '蓮', + '連' => '連', + '鍊' => '鍊', + '列' => '列', + '劣' => '劣', + '咽' => '咽', + '烈' => '烈', + '裂' => '裂', + '說' => '說', + '廉' => '廉', + '念' => '念', + '捻' => '捻', + '殮' => '殮', + '簾' => '簾', + '獵' => '獵', + '令' => '令', + '囹' => '囹', + '寧' => '寧', + '嶺' => '嶺', + '怜' => '怜', + '玲' => '玲', + '瑩' => '瑩', + '羚' => '羚', + '聆' => '聆', + '鈴' => '鈴', + '零' => '零', + '靈' => '靈', + '領' => '領', + '例' => '例', + '禮' => '禮', + '醴' => '醴', + '隸' => '隸', + '惡' => '惡', + '了' => '了', + '僚' => '僚', + '寮' => '寮', + '尿' => '尿', + '料' => '料', + '樂' => '樂', + '燎' => '燎', + '療' => '療', + '蓼' => '蓼', + '遼' => '遼', + '龍' => '龍', + '暈' => '暈', + '阮' => '阮', + '劉' => '劉', + '杻' => '杻', + '柳' => '柳', + '流' => '流', + '溜' => '溜', + '琉' => '琉', + '留' => '留', + '硫' => '硫', + '紐' => '紐', + '類' => '類', + '六' => '六', + '戮' => '戮', + '陸' => '陸', + '倫' => '倫', + '崙' => '崙', + '淪' => '淪', + '輪' => '輪', + '律' => '律', + '慄' => '慄', + '栗' => '栗', + '率' => '率', + '隆' => '隆', + '利' => '利', + '吏' => '吏', + '履' => '履', + '易' => '易', + '李' => '李', + '梨' => '梨', + '泥' => '泥', + '理' => '理', + '痢' => '痢', + '罹' => '罹', + '裏' => '裏', + '裡' => '裡', + '里' => '里', + '離' => '離', + '匿' => '匿', + '溺' => '溺', + '吝' => '吝', + '燐' => '燐', + '璘' => '璘', + '藺' => '藺', + '隣' => '隣', + '鱗' => '鱗', + '麟' => '麟', + '林' => '林', + '淋' => '淋', + '臨' => '臨', + '立' => '立', + '笠' => '笠', + '粒' => '粒', + '狀' => '狀', + '炙' => '炙', + '識' => '識', + '什' => '什', + '茶' => '茶', + '刺' => '刺', + '切' => '切', + '度' => '度', + '拓' => '拓', + '糖' => '糖', + '宅' => '宅', + '洞' => '洞', + '暴' => '暴', + '輻' => '輻', + '行' => '行', + '降' => '降', + '見' => '見', + '廓' => '廓', + '兀' => '兀', + '嗀' => '嗀', + '塚' => '塚', + '晴' => '晴', + '凞' => '凞', + '猪' => '猪', + '益' => '益', + '礼' => '礼', + '神' => '神', + '祥' => '祥', + '福' => '福', + '靖' => '靖', + '精' => '精', + '羽' => '羽', + '蘒' => '蘒', + '諸' => '諸', + '逸' => '逸', + '都' => '都', + '飯' => '飯', + '飼' => '飼', + '館' => '館', + '鶴' => '鶴', + '郞' => '郞', + '隷' => '隷', + '侮' => '侮', + '僧' => '僧', + '免' => '免', + '勉' => '勉', + '勤' => '勤', + '卑' => '卑', + '喝' => '喝', + '嘆' => '嘆', + '器' => '器', + '塀' => '塀', + '墨' => '墨', + '層' => '層', + '屮' => '屮', + '悔' => '悔', + '慨' => '慨', + '憎' => '憎', + '懲' => '懲', + '敏' => '敏', + '既' => '既', + '暑' => '暑', + '梅' => '梅', + '海' => '海', + '渚' => '渚', + '漢' => '漢', + '煮' => '煮', + '爫' => '爫', + '琢' => '琢', + '碑' => '碑', + '社' => '社', + '祉' => '祉', + '祈' => '祈', + '祐' => '祐', + '祖' => '祖', + '祝' => '祝', + '禍' => '禍', + '禎' => '禎', + '穀' => '穀', + '突' => '突', + '節' => '節', + '練' => '練', + '縉' => '縉', + '繁' => '繁', + '署' => '署', + '者' => '者', + '臭' => '臭', + '艹' => '艹', + '艹' => '艹', + '著' => '著', + '褐' => '褐', + '視' => '視', + '謁' => '謁', + '謹' => '謹', + '賓' => '賓', + '贈' => '贈', + '辶' => '辶', + '逸' => '逸', + '難' => '難', + '響' => '響', + '頻' => '頻', + '恵' => '恵', + '𤋮' => '𤋮', + '舘' => '舘', + '並' => '並', + '况' => '况', + '全' => '全', + '侀' => '侀', + '充' => '充', + '冀' => '冀', + '勇' => '勇', + '勺' => '勺', + '喝' => '喝', + '啕' => '啕', + '喙' => '喙', + '嗢' => '嗢', + '塚' => '塚', + '墳' => '墳', + '奄' => '奄', + '奔' => '奔', + '婢' => '婢', + '嬨' => '嬨', + '廒' => '廒', + '廙' => '廙', + '彩' => '彩', + '徭' => '徭', + '惘' => '惘', + '慎' => '慎', + '愈' => '愈', + '憎' => '憎', + '慠' => '慠', + '懲' => '懲', + '戴' => '戴', + '揄' => '揄', + '搜' => '搜', + '摒' => '摒', + '敖' => '敖', + '晴' => '晴', + '朗' => '朗', + '望' => '望', + '杖' => '杖', + '歹' => '歹', + '殺' => '殺', + '流' => '流', + '滛' => '滛', + '滋' => '滋', + '漢' => '漢', + '瀞' => '瀞', + '煮' => '煮', + '瞧' => '瞧', + '爵' => '爵', + '犯' => '犯', + '猪' => '猪', + '瑱' => '瑱', + '甆' => '甆', + '画' => '画', + '瘝' => '瘝', + '瘟' => '瘟', + '益' => '益', + '盛' => '盛', + '直' => '直', + '睊' => '睊', + '着' => '着', + '磌' => '磌', + '窱' => '窱', + '節' => '節', + '类' => '类', + '絛' => '絛', + '練' => '練', + '缾' => '缾', + '者' => '者', + '荒' => '荒', + '華' => '華', + '蝹' => '蝹', + '襁' => '襁', + '覆' => '覆', + '視' => '視', + '調' => '調', + '諸' => '諸', + '請' => '請', + '謁' => '謁', + '諾' => '諾', + '諭' => '諭', + '謹' => '謹', + '變' => '變', + '贈' => '贈', + '輸' => '輸', + '遲' => '遲', + '醙' => '醙', + '鉶' => '鉶', + '陼' => '陼', + '難' => '難', + '靖' => '靖', + '韛' => '韛', + '響' => '響', + '頋' => '頋', + '頻' => '頻', + '鬒' => '鬒', + '龜' => '龜', + '𢡊' => '𢡊', + '𢡄' => '𢡄', + '𣏕' => '𣏕', + '㮝' => '㮝', + '䀘' => '䀘', + '䀹' => '䀹', + '𥉉' => '𥉉', + '𥳐' => '𥳐', + '𧻓' => '𧻓', + '齃' => '齃', + '龎' => '龎', + 'יִ' => 'יִ', + 'ײַ' => 'ײַ', + 'שׁ' => 'שׁ', + 'שׂ' => 'שׂ', + 'שּׁ' => 'שּׁ', + 'שּׂ' => 'שּׂ', + 'אַ' => 'אַ', + 'אָ' => 'אָ', + 'אּ' => 'אּ', + 'בּ' => 'בּ', + 'גּ' => 'גּ', + 'דּ' => 'דּ', + 'הּ' => 'הּ', + 'וּ' => 'וּ', + 'זּ' => 'זּ', + 'טּ' => 'טּ', + 'יּ' => 'יּ', + 'ךּ' => 'ךּ', + 'כּ' => 'כּ', + 'לּ' => 'לּ', + 'מּ' => 'מּ', + 'נּ' => 'נּ', + 'סּ' => 'סּ', + 'ףּ' => 'ףּ', + 'פּ' => 'פּ', + 'צּ' => 'צּ', + 'קּ' => 'קּ', + 'רּ' => 'רּ', + 'שּ' => 'שּ', + 'תּ' => 'תּ', + 'וֹ' => 'וֹ', + 'בֿ' => 'בֿ', + 'כֿ' => 'כֿ', + 'פֿ' => 'פֿ', + '𑂚' => '𑂚', + '𑂜' => '𑂜', + '𑂫' => '𑂫', + '𑄮' => '𑄮', + '𑄯' => '𑄯', + '𑍋' => '𑍋', + '𑍌' => '𑍌', + '𑒻' => '𑒻', + '𑒼' => '𑒼', + '𑒾' => '𑒾', + '𑖺' => '𑖺', + '𑖻' => '𑖻', + '𑤸' => '𑤸', + '𝅗𝅥' => '𝅗𝅥', + '𝅘𝅥' => '𝅘𝅥', + '𝅘𝅥𝅮' => '𝅘𝅥𝅮', + '𝅘𝅥𝅯' => '𝅘𝅥𝅯', + '𝅘𝅥𝅰' => '𝅘𝅥𝅰', + '𝅘𝅥𝅱' => '𝅘𝅥𝅱', + '𝅘𝅥𝅲' => '𝅘𝅥𝅲', + '𝆹𝅥' => '𝆹𝅥', + '𝆺𝅥' => '𝆺𝅥', + '𝆹𝅥𝅮' => '𝆹𝅥𝅮', + '𝆺𝅥𝅮' => '𝆺𝅥𝅮', + '𝆹𝅥𝅯' => '𝆹𝅥𝅯', + '𝆺𝅥𝅯' => '𝆺𝅥𝅯', + '丽' => '丽', + '丸' => '丸', + '乁' => '乁', + '𠄢' => '𠄢', + '你' => '你', + '侮' => '侮', + '侻' => '侻', + '倂' => '倂', + '偺' => '偺', + '備' => '備', + '僧' => '僧', + '像' => '像', + '㒞' => '㒞', + '𠘺' => '𠘺', + '免' => '免', + '兔' => '兔', + '兤' => '兤', + '具' => '具', + '𠔜' => '𠔜', + '㒹' => '㒹', + '內' => '內', + '再' => '再', + '𠕋' => '𠕋', + '冗' => '冗', + '冤' => '冤', + '仌' => '仌', + '冬' => '冬', + '况' => '况', + '𩇟' => '𩇟', + '凵' => '凵', + '刃' => '刃', + '㓟' => '㓟', + '刻' => '刻', + '剆' => '剆', + '割' => '割', + '剷' => '剷', + '㔕' => '㔕', + '勇' => '勇', + '勉' => '勉', + '勤' => '勤', + '勺' => '勺', + '包' => '包', + '匆' => '匆', + '北' => '北', + '卉' => '卉', + '卑' => '卑', + '博' => '博', + '即' => '即', + '卽' => '卽', + '卿' => '卿', + '卿' => '卿', + '卿' => '卿', + '𠨬' => '𠨬', + '灰' => '灰', + '及' => '及', + '叟' => '叟', + '𠭣' => '𠭣', + '叫' => '叫', + '叱' => '叱', + '吆' => '吆', + '咞' => '咞', + '吸' => '吸', + '呈' => '呈', + '周' => '周', + '咢' => '咢', + '哶' => '哶', + '唐' => '唐', + '啓' => '啓', + '啣' => '啣', + '善' => '善', + '善' => '善', + '喙' => '喙', + '喫' => '喫', + '喳' => '喳', + '嗂' => '嗂', + '圖' => '圖', + '嘆' => '嘆', + '圗' => '圗', + '噑' => '噑', + '噴' => '噴', + '切' => '切', + '壮' => '壮', + '城' => '城', + '埴' => '埴', + '堍' => '堍', + '型' => '型', + '堲' => '堲', + '報' => '報', + '墬' => '墬', + '𡓤' => '𡓤', + '売' => '売', + '壷' => '壷', + '夆' => '夆', + '多' => '多', + '夢' => '夢', + '奢' => '奢', + '𡚨' => '𡚨', + '𡛪' => '𡛪', + '姬' => '姬', + '娛' => '娛', + '娧' => '娧', + '姘' => '姘', + '婦' => '婦', + '㛮' => '㛮', + '㛼' => '㛼', + '嬈' => '嬈', + '嬾' => '嬾', + '嬾' => '嬾', + '𡧈' => '𡧈', + '寃' => '寃', + '寘' => '寘', + '寧' => '寧', + '寳' => '寳', + '𡬘' => '𡬘', + '寿' => '寿', + '将' => '将', + '当' => '当', + '尢' => '尢', + '㞁' => '㞁', + '屠' => '屠', + '屮' => '屮', + '峀' => '峀', + '岍' => '岍', + '𡷤' => '𡷤', + '嵃' => '嵃', + '𡷦' => '𡷦', + '嵮' => '嵮', + '嵫' => '嵫', + '嵼' => '嵼', + '巡' => '巡', + '巢' => '巢', + '㠯' => '㠯', + '巽' => '巽', + '帨' => '帨', + '帽' => '帽', + '幩' => '幩', + '㡢' => '㡢', + '𢆃' => '𢆃', + '㡼' => '㡼', + '庰' => '庰', + '庳' => '庳', + '庶' => '庶', + '廊' => '廊', + '𪎒' => '𪎒', + '廾' => '廾', + '𢌱' => '𢌱', + '𢌱' => '𢌱', + '舁' => '舁', + '弢' => '弢', + '弢' => '弢', + '㣇' => '㣇', + '𣊸' => '𣊸', + '𦇚' => '𦇚', + '形' => '形', + '彫' => '彫', + '㣣' => '㣣', + '徚' => '徚', + '忍' => '忍', + '志' => '志', + '忹' => '忹', + '悁' => '悁', + '㤺' => '㤺', + '㤜' => '㤜', + '悔' => '悔', + '𢛔' => '𢛔', + '惇' => '惇', + '慈' => '慈', + '慌' => '慌', + '慎' => '慎', + '慌' => '慌', + '慺' => '慺', + '憎' => '憎', + '憲' => '憲', + '憤' => '憤', + '憯' => '憯', + '懞' => '懞', + '懲' => '懲', + '懶' => '懶', + '成' => '成', + '戛' => '戛', + '扝' => '扝', + '抱' => '抱', + '拔' => '拔', + '捐' => '捐', + '𢬌' => '𢬌', + '挽' => '挽', + '拼' => '拼', + '捨' => '捨', + '掃' => '掃', + '揤' => '揤', + '𢯱' => '𢯱', + '搢' => '搢', + '揅' => '揅', + '掩' => '掩', + '㨮' => '㨮', + '摩' => '摩', + '摾' => '摾', + '撝' => '撝', + '摷' => '摷', + '㩬' => '㩬', + '敏' => '敏', + '敬' => '敬', + '𣀊' => '𣀊', + '旣' => '旣', + '書' => '書', + '晉' => '晉', + '㬙' => '㬙', + '暑' => '暑', + '㬈' => '㬈', + '㫤' => '㫤', + '冒' => '冒', + '冕' => '冕', + '最' => '最', + '暜' => '暜', + '肭' => '肭', + '䏙' => '䏙', + '朗' => '朗', + '望' => '望', + '朡' => '朡', + '杞' => '杞', + '杓' => '杓', + '𣏃' => '𣏃', + '㭉' => '㭉', + '柺' => '柺', + '枅' => '枅', + '桒' => '桒', + '梅' => '梅', + '𣑭' => '𣑭', + '梎' => '梎', + '栟' => '栟', + '椔' => '椔', + '㮝' => '㮝', + '楂' => '楂', + '榣' => '榣', + '槪' => '槪', + '檨' => '檨', + '𣚣' => '𣚣', + '櫛' => '櫛', + '㰘' => '㰘', + '次' => '次', + '𣢧' => '𣢧', + '歔' => '歔', + '㱎' => '㱎', + '歲' => '歲', + '殟' => '殟', + '殺' => '殺', + '殻' => '殻', + '𣪍' => '𣪍', + '𡴋' => '𡴋', + '𣫺' => '𣫺', + '汎' => '汎', + '𣲼' => '𣲼', + '沿' => '沿', + '泍' => '泍', + '汧' => '汧', + '洖' => '洖', + '派' => '派', + '海' => '海', + '流' => '流', + '浩' => '浩', + '浸' => '浸', + '涅' => '涅', + '𣴞' => '𣴞', + '洴' => '洴', + '港' => '港', + '湮' => '湮', + '㴳' => '㴳', + '滋' => '滋', + '滇' => '滇', + '𣻑' => '𣻑', + '淹' => '淹', + '潮' => '潮', + '𣽞' => '𣽞', + '𣾎' => '𣾎', + '濆' => '濆', + '瀹' => '瀹', + '瀞' => '瀞', + '瀛' => '瀛', + '㶖' => '㶖', + '灊' => '灊', + '災' => '災', + '灷' => '灷', + '炭' => '炭', + '𠔥' => '𠔥', + '煅' => '煅', + '𤉣' => '𤉣', + '熜' => '熜', + '𤎫' => '𤎫', + '爨' => '爨', + '爵' => '爵', + '牐' => '牐', + '𤘈' => '𤘈', + '犀' => '犀', + '犕' => '犕', + '𤜵' => '𤜵', + '𤠔' => '𤠔', + '獺' => '獺', + '王' => '王', + '㺬' => '㺬', + '玥' => '玥', + '㺸' => '㺸', + '㺸' => '㺸', + '瑇' => '瑇', + '瑜' => '瑜', + '瑱' => '瑱', + '璅' => '璅', + '瓊' => '瓊', + '㼛' => '㼛', + '甤' => '甤', + '𤰶' => '𤰶', + '甾' => '甾', + '𤲒' => '𤲒', + '異' => '異', + '𢆟' => '𢆟', + '瘐' => '瘐', + '𤾡' => '𤾡', + '𤾸' => '𤾸', + '𥁄' => '𥁄', + '㿼' => '㿼', + '䀈' => '䀈', + '直' => '直', + '𥃳' => '𥃳', + '𥃲' => '𥃲', + '𥄙' => '𥄙', + '𥄳' => '𥄳', + '眞' => '眞', + '真' => '真', + '真' => '真', + '睊' => '睊', + '䀹' => '䀹', + '瞋' => '瞋', + '䁆' => '䁆', + '䂖' => '䂖', + '𥐝' => '𥐝', + '硎' => '硎', + '碌' => '碌', + '磌' => '磌', + '䃣' => '䃣', + '𥘦' => '𥘦', + '祖' => '祖', + '𥚚' => '𥚚', + '𥛅' => '𥛅', + '福' => '福', + '秫' => '秫', + '䄯' => '䄯', + '穀' => '穀', + '穊' => '穊', + '穏' => '穏', + '𥥼' => '𥥼', + '𥪧' => '𥪧', + '𥪧' => '𥪧', + '竮' => '竮', + '䈂' => '䈂', + '𥮫' => '𥮫', + '篆' => '篆', + '築' => '築', + '䈧' => '䈧', + '𥲀' => '𥲀', + '糒' => '糒', + '䊠' => '䊠', + '糨' => '糨', + '糣' => '糣', + '紀' => '紀', + '𥾆' => '𥾆', + '絣' => '絣', + '䌁' => '䌁', + '緇' => '緇', + '縂' => '縂', + '繅' => '繅', + '䌴' => '䌴', + '𦈨' => '𦈨', + '𦉇' => '𦉇', + '䍙' => '䍙', + '𦋙' => '𦋙', + '罺' => '罺', + '𦌾' => '𦌾', + '羕' => '羕', + '翺' => '翺', + '者' => '者', + '𦓚' => '𦓚', + '𦔣' => '𦔣', + '聠' => '聠', + '𦖨' => '𦖨', + '聰' => '聰', + '𣍟' => '𣍟', + '䏕' => '䏕', + '育' => '育', + '脃' => '脃', + '䐋' => '䐋', + '脾' => '脾', + '媵' => '媵', + '𦞧' => '𦞧', + '𦞵' => '𦞵', + '𣎓' => '𣎓', + '𣎜' => '𣎜', + '舁' => '舁', + '舄' => '舄', + '辞' => '辞', + '䑫' => '䑫', + '芑' => '芑', + '芋' => '芋', + '芝' => '芝', + '劳' => '劳', + '花' => '花', + '芳' => '芳', + '芽' => '芽', + '苦' => '苦', + '𦬼' => '𦬼', + '若' => '若', + '茝' => '茝', + '荣' => '荣', + '莭' => '莭', + '茣' => '茣', + '莽' => '莽', + '菧' => '菧', + '著' => '著', + '荓' => '荓', + '菊' => '菊', + '菌' => '菌', + '菜' => '菜', + '𦰶' => '𦰶', + '𦵫' => '𦵫', + '𦳕' => '𦳕', + '䔫' => '䔫', + '蓱' => '蓱', + '蓳' => '蓳', + '蔖' => '蔖', + '𧏊' => '𧏊', + '蕤' => '蕤', + '𦼬' => '𦼬', + '䕝' => '䕝', + '䕡' => '䕡', + '𦾱' => '𦾱', + '𧃒' => '𧃒', + '䕫' => '䕫', + '虐' => '虐', + '虜' => '虜', + '虧' => '虧', + '虩' => '虩', + '蚩' => '蚩', + '蚈' => '蚈', + '蜎' => '蜎', + '蛢' => '蛢', + '蝹' => '蝹', + '蜨' => '蜨', + '蝫' => '蝫', + '螆' => '螆', + '䗗' => '䗗', + '蟡' => '蟡', + '蠁' => '蠁', + '䗹' => '䗹', + '衠' => '衠', + '衣' => '衣', + '𧙧' => '𧙧', + '裗' => '裗', + '裞' => '裞', + '䘵' => '䘵', + '裺' => '裺', + '㒻' => '㒻', + '𧢮' => '𧢮', + '𧥦' => '𧥦', + '䚾' => '䚾', + '䛇' => '䛇', + '誠' => '誠', + '諭' => '諭', + '變' => '變', + '豕' => '豕', + '𧲨' => '𧲨', + '貫' => '貫', + '賁' => '賁', + '贛' => '贛', + '起' => '起', + '𧼯' => '𧼯', + '𠠄' => '𠠄', + '跋' => '跋', + '趼' => '趼', + '跰' => '跰', + '𠣞' => '𠣞', + '軔' => '軔', + '輸' => '輸', + '𨗒' => '𨗒', + '𨗭' => '𨗭', + '邔' => '邔', + '郱' => '郱', + '鄑' => '鄑', + '𨜮' => '𨜮', + '鄛' => '鄛', + '鈸' => '鈸', + '鋗' => '鋗', + '鋘' => '鋘', + '鉼' => '鉼', + '鏹' => '鏹', + '鐕' => '鐕', + '𨯺' => '𨯺', + '開' => '開', + '䦕' => '䦕', + '閷' => '閷', + '𨵷' => '𨵷', + '䧦' => '䧦', + '雃' => '雃', + '嶲' => '嶲', + '霣' => '霣', + '𩅅' => '𩅅', + '𩈚' => '𩈚', + '䩮' => '䩮', + '䩶' => '䩶', + '韠' => '韠', + '𩐊' => '𩐊', + '䪲' => '䪲', + '𩒖' => '𩒖', + '頋' => '頋', + '頋' => '頋', + '頩' => '頩', + '𩖶' => '𩖶', + '飢' => '飢', + '䬳' => '䬳', + '餩' => '餩', + '馧' => '馧', + '駂' => '駂', + '駾' => '駾', + '䯎' => '䯎', + '𩬰' => '𩬰', + '鬒' => '鬒', + '鱀' => '鱀', + '鳽' => '鳽', + '䳎' => '䳎', + '䳭' => '䳭', + '鵧' => '鵧', + '𪃎' => '𪃎', + '䳸' => '䳸', + '𪄅' => '𪄅', + '𪈎' => '𪈎', + '𪊑' => '𪊑', + '麻' => '麻', + '䵖' => '䵖', + '黹' => '黹', + '黾' => '黾', + '鼅' => '鼅', + '鼏' => '鼏', + '鼖' => '鼖', + '鼻' => '鼻', + '𪘀' => '𪘀', +); diff --git a/lib/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php b/lib/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php new file mode 100644 index 0000000000..ec90f36eb6 --- /dev/null +++ b/lib/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.php @@ -0,0 +1,876 @@ + 230, + '́' => 230, + '̂' => 230, + '̃' => 230, + '̄' => 230, + '̅' => 230, + '̆' => 230, + '̇' => 230, + '̈' => 230, + '̉' => 230, + '̊' => 230, + '̋' => 230, + '̌' => 230, + '̍' => 230, + '̎' => 230, + '̏' => 230, + '̐' => 230, + '̑' => 230, + '̒' => 230, + '̓' => 230, + '̔' => 230, + '̕' => 232, + '̖' => 220, + '̗' => 220, + '̘' => 220, + '̙' => 220, + '̚' => 232, + '̛' => 216, + '̜' => 220, + '̝' => 220, + '̞' => 220, + '̟' => 220, + '̠' => 220, + '̡' => 202, + '̢' => 202, + '̣' => 220, + '̤' => 220, + '̥' => 220, + '̦' => 220, + '̧' => 202, + '̨' => 202, + '̩' => 220, + '̪' => 220, + '̫' => 220, + '̬' => 220, + '̭' => 220, + '̮' => 220, + '̯' => 220, + '̰' => 220, + '̱' => 220, + '̲' => 220, + '̳' => 220, + '̴' => 1, + '̵' => 1, + '̶' => 1, + '̷' => 1, + '̸' => 1, + '̹' => 220, + '̺' => 220, + '̻' => 220, + '̼' => 220, + '̽' => 230, + '̾' => 230, + '̿' => 230, + '̀' => 230, + '́' => 230, + '͂' => 230, + '̓' => 230, + '̈́' => 230, + 'ͅ' => 240, + '͆' => 230, + '͇' => 220, + '͈' => 220, + '͉' => 220, + '͊' => 230, + '͋' => 230, + '͌' => 230, + '͍' => 220, + '͎' => 220, + '͐' => 230, + '͑' => 230, + '͒' => 230, + '͓' => 220, + '͔' => 220, + '͕' => 220, + '͖' => 220, + '͗' => 230, + '͘' => 232, + '͙' => 220, + '͚' => 220, + '͛' => 230, + '͜' => 233, + '͝' => 234, + '͞' => 234, + '͟' => 233, + '͠' => 234, + '͡' => 234, + '͢' => 233, + 'ͣ' => 230, + 'ͤ' => 230, + 'ͥ' => 230, + 'ͦ' => 230, + 'ͧ' => 230, + 'ͨ' => 230, + 'ͩ' => 230, + 'ͪ' => 230, + 'ͫ' => 230, + 'ͬ' => 230, + 'ͭ' => 230, + 'ͮ' => 230, + 'ͯ' => 230, + '҃' => 230, + '҄' => 230, + '҅' => 230, + '҆' => 230, + '҇' => 230, + '֑' => 220, + '֒' => 230, + '֓' => 230, + '֔' => 230, + '֕' => 230, + '֖' => 220, + '֗' => 230, + '֘' => 230, + '֙' => 230, + '֚' => 222, + '֛' => 220, + '֜' => 230, + '֝' => 230, + '֞' => 230, + '֟' => 230, + '֠' => 230, + '֡' => 230, + '֢' => 220, + '֣' => 220, + '֤' => 220, + '֥' => 220, + '֦' => 220, + '֧' => 220, + '֨' => 230, + '֩' => 230, + '֪' => 220, + '֫' => 230, + '֬' => 230, + '֭' => 222, + '֮' => 228, + '֯' => 230, + 'ְ' => 10, + 'ֱ' => 11, + 'ֲ' => 12, + 'ֳ' => 13, + 'ִ' => 14, + 'ֵ' => 15, + 'ֶ' => 16, + 'ַ' => 17, + 'ָ' => 18, + 'ֹ' => 19, + 'ֺ' => 19, + 'ֻ' => 20, + 'ּ' => 21, + 'ֽ' => 22, + 'ֿ' => 23, + 'ׁ' => 24, + 'ׂ' => 25, + 'ׄ' => 230, + 'ׅ' => 220, + 'ׇ' => 18, + 'ؐ' => 230, + 'ؑ' => 230, + 'ؒ' => 230, + 'ؓ' => 230, + 'ؔ' => 230, + 'ؕ' => 230, + 'ؖ' => 230, + 'ؗ' => 230, + 'ؘ' => 30, + 'ؙ' => 31, + 'ؚ' => 32, + 'ً' => 27, + 'ٌ' => 28, + 'ٍ' => 29, + 'َ' => 30, + 'ُ' => 31, + 'ِ' => 32, + 'ّ' => 33, + 'ْ' => 34, + 'ٓ' => 230, + 'ٔ' => 230, + 'ٕ' => 220, + 'ٖ' => 220, + 'ٗ' => 230, + '٘' => 230, + 'ٙ' => 230, + 'ٚ' => 230, + 'ٛ' => 230, + 'ٜ' => 220, + 'ٝ' => 230, + 'ٞ' => 230, + 'ٟ' => 220, + 'ٰ' => 35, + 'ۖ' => 230, + 'ۗ' => 230, + 'ۘ' => 230, + 'ۙ' => 230, + 'ۚ' => 230, + 'ۛ' => 230, + 'ۜ' => 230, + '۟' => 230, + '۠' => 230, + 'ۡ' => 230, + 'ۢ' => 230, + 'ۣ' => 220, + 'ۤ' => 230, + 'ۧ' => 230, + 'ۨ' => 230, + '۪' => 220, + '۫' => 230, + '۬' => 230, + 'ۭ' => 220, + 'ܑ' => 36, + 'ܰ' => 230, + 'ܱ' => 220, + 'ܲ' => 230, + 'ܳ' => 230, + 'ܴ' => 220, + 'ܵ' => 230, + 'ܶ' => 230, + 'ܷ' => 220, + 'ܸ' => 220, + 'ܹ' => 220, + 'ܺ' => 230, + 'ܻ' => 220, + 'ܼ' => 220, + 'ܽ' => 230, + 'ܾ' => 220, + 'ܿ' => 230, + '݀' => 230, + '݁' => 230, + '݂' => 220, + '݃' => 230, + '݄' => 220, + '݅' => 230, + '݆' => 220, + '݇' => 230, + '݈' => 220, + '݉' => 230, + '݊' => 230, + '߫' => 230, + '߬' => 230, + '߭' => 230, + '߮' => 230, + '߯' => 230, + '߰' => 230, + '߱' => 230, + '߲' => 220, + '߳' => 230, + '߽' => 220, + 'ࠖ' => 230, + 'ࠗ' => 230, + '࠘' => 230, + '࠙' => 230, + 'ࠛ' => 230, + 'ࠜ' => 230, + 'ࠝ' => 230, + 'ࠞ' => 230, + 'ࠟ' => 230, + 'ࠠ' => 230, + 'ࠡ' => 230, + 'ࠢ' => 230, + 'ࠣ' => 230, + 'ࠥ' => 230, + 'ࠦ' => 230, + 'ࠧ' => 230, + 'ࠩ' => 230, + 'ࠪ' => 230, + 'ࠫ' => 230, + 'ࠬ' => 230, + '࠭' => 230, + '࡙' => 220, + '࡚' => 220, + '࡛' => 220, + '࣓' => 220, + 'ࣔ' => 230, + 'ࣕ' => 230, + 'ࣖ' => 230, + 'ࣗ' => 230, + 'ࣘ' => 230, + 'ࣙ' => 230, + 'ࣚ' => 230, + 'ࣛ' => 230, + 'ࣜ' => 230, + 'ࣝ' => 230, + 'ࣞ' => 230, + 'ࣟ' => 230, + '࣠' => 230, + '࣡' => 230, + 'ࣣ' => 220, + 'ࣤ' => 230, + 'ࣥ' => 230, + 'ࣦ' => 220, + 'ࣧ' => 230, + 'ࣨ' => 230, + 'ࣩ' => 220, + '࣪' => 230, + '࣫' => 230, + '࣬' => 230, + '࣭' => 220, + '࣮' => 220, + '࣯' => 220, + 'ࣰ' => 27, + 'ࣱ' => 28, + 'ࣲ' => 29, + 'ࣳ' => 230, + 'ࣴ' => 230, + 'ࣵ' => 230, + 'ࣶ' => 220, + 'ࣷ' => 230, + 'ࣸ' => 230, + 'ࣹ' => 220, + 'ࣺ' => 220, + 'ࣻ' => 230, + 'ࣼ' => 230, + 'ࣽ' => 230, + 'ࣾ' => 230, + 'ࣿ' => 230, + '़' => 7, + '्' => 9, + '॑' => 230, + '॒' => 220, + '॓' => 230, + '॔' => 230, + '়' => 7, + '্' => 9, + '৾' => 230, + '਼' => 7, + '੍' => 9, + '઼' => 7, + '્' => 9, + '଼' => 7, + '୍' => 9, + '்' => 9, + '్' => 9, + 'ౕ' => 84, + 'ౖ' => 91, + '಼' => 7, + '್' => 9, + '഻' => 9, + '഼' => 9, + '്' => 9, + '්' => 9, + 'ุ' => 103, + 'ู' => 103, + 'ฺ' => 9, + '่' => 107, + '้' => 107, + '๊' => 107, + '๋' => 107, + 'ຸ' => 118, + 'ູ' => 118, + '຺' => 9, + '່' => 122, + '້' => 122, + '໊' => 122, + '໋' => 122, + '༘' => 220, + '༙' => 220, + '༵' => 220, + '༷' => 220, + '༹' => 216, + 'ཱ' => 129, + 'ི' => 130, + 'ུ' => 132, + 'ེ' => 130, + 'ཻ' => 130, + 'ོ' => 130, + 'ཽ' => 130, + 'ྀ' => 130, + 'ྂ' => 230, + 'ྃ' => 230, + '྄' => 9, + '྆' => 230, + '྇' => 230, + '࿆' => 220, + '့' => 7, + '္' => 9, + '်' => 9, + 'ႍ' => 220, + '፝' => 230, + '፞' => 230, + '፟' => 230, + '᜔' => 9, + '᜴' => 9, + '្' => 9, + '៝' => 230, + 'ᢩ' => 228, + '᤹' => 222, + '᤺' => 230, + '᤻' => 220, + 'ᨗ' => 230, + 'ᨘ' => 220, + '᩠' => 9, + '᩵' => 230, + '᩶' => 230, + '᩷' => 230, + '᩸' => 230, + '᩹' => 230, + '᩺' => 230, + '᩻' => 230, + '᩼' => 230, + '᩿' => 220, + '᪰' => 230, + '᪱' => 230, + '᪲' => 230, + '᪳' => 230, + '᪴' => 230, + '᪵' => 220, + '᪶' => 220, + '᪷' => 220, + '᪸' => 220, + '᪹' => 220, + '᪺' => 220, + '᪻' => 230, + '᪼' => 230, + '᪽' => 220, + 'ᪿ' => 220, + 'ᫀ' => 220, + '᬴' => 7, + '᭄' => 9, + '᭫' => 230, + '᭬' => 220, + '᭭' => 230, + '᭮' => 230, + '᭯' => 230, + '᭰' => 230, + '᭱' => 230, + '᭲' => 230, + '᭳' => 230, + '᮪' => 9, + '᮫' => 9, + '᯦' => 7, + '᯲' => 9, + '᯳' => 9, + '᰷' => 7, + '᳐' => 230, + '᳑' => 230, + '᳒' => 230, + '᳔' => 1, + '᳕' => 220, + '᳖' => 220, + '᳗' => 220, + '᳘' => 220, + '᳙' => 220, + '᳚' => 230, + '᳛' => 230, + '᳜' => 220, + '᳝' => 220, + '᳞' => 220, + '᳟' => 220, + '᳠' => 230, + '᳢' => 1, + '᳣' => 1, + '᳤' => 1, + '᳥' => 1, + '᳦' => 1, + '᳧' => 1, + '᳨' => 1, + '᳭' => 220, + '᳴' => 230, + '᳸' => 230, + '᳹' => 230, + '᷀' => 230, + '᷁' => 230, + '᷂' => 220, + '᷃' => 230, + '᷄' => 230, + '᷅' => 230, + '᷆' => 230, + '᷇' => 230, + '᷈' => 230, + '᷉' => 230, + '᷊' => 220, + '᷋' => 230, + '᷌' => 230, + '᷍' => 234, + '᷎' => 214, + '᷏' => 220, + '᷐' => 202, + '᷑' => 230, + '᷒' => 230, + 'ᷓ' => 230, + 'ᷔ' => 230, + 'ᷕ' => 230, + 'ᷖ' => 230, + 'ᷗ' => 230, + 'ᷘ' => 230, + 'ᷙ' => 230, + 'ᷚ' => 230, + 'ᷛ' => 230, + 'ᷜ' => 230, + 'ᷝ' => 230, + 'ᷞ' => 230, + 'ᷟ' => 230, + 'ᷠ' => 230, + 'ᷡ' => 230, + 'ᷢ' => 230, + 'ᷣ' => 230, + 'ᷤ' => 230, + 'ᷥ' => 230, + 'ᷦ' => 230, + 'ᷧ' => 230, + 'ᷨ' => 230, + 'ᷩ' => 230, + 'ᷪ' => 230, + 'ᷫ' => 230, + 'ᷬ' => 230, + 'ᷭ' => 230, + 'ᷮ' => 230, + 'ᷯ' => 230, + 'ᷰ' => 230, + 'ᷱ' => 230, + 'ᷲ' => 230, + 'ᷳ' => 230, + 'ᷴ' => 230, + '᷵' => 230, + '᷶' => 232, + '᷷' => 228, + '᷸' => 228, + '᷹' => 220, + '᷻' => 230, + '᷼' => 233, + '᷽' => 220, + '᷾' => 230, + '᷿' => 220, + '⃐' => 230, + '⃑' => 230, + '⃒' => 1, + '⃓' => 1, + '⃔' => 230, + '⃕' => 230, + '⃖' => 230, + '⃗' => 230, + '⃘' => 1, + '⃙' => 1, + '⃚' => 1, + '⃛' => 230, + '⃜' => 230, + '⃡' => 230, + '⃥' => 1, + '⃦' => 1, + '⃧' => 230, + '⃨' => 220, + '⃩' => 230, + '⃪' => 1, + '⃫' => 1, + '⃬' => 220, + '⃭' => 220, + '⃮' => 220, + '⃯' => 220, + '⃰' => 230, + '⳯' => 230, + '⳰' => 230, + '⳱' => 230, + '⵿' => 9, + 'ⷠ' => 230, + 'ⷡ' => 230, + 'ⷢ' => 230, + 'ⷣ' => 230, + 'ⷤ' => 230, + 'ⷥ' => 230, + 'ⷦ' => 230, + 'ⷧ' => 230, + 'ⷨ' => 230, + 'ⷩ' => 230, + 'ⷪ' => 230, + 'ⷫ' => 230, + 'ⷬ' => 230, + 'ⷭ' => 230, + 'ⷮ' => 230, + 'ⷯ' => 230, + 'ⷰ' => 230, + 'ⷱ' => 230, + 'ⷲ' => 230, + 'ⷳ' => 230, + 'ⷴ' => 230, + 'ⷵ' => 230, + 'ⷶ' => 230, + 'ⷷ' => 230, + 'ⷸ' => 230, + 'ⷹ' => 230, + 'ⷺ' => 230, + 'ⷻ' => 230, + 'ⷼ' => 230, + 'ⷽ' => 230, + 'ⷾ' => 230, + 'ⷿ' => 230, + '〪' => 218, + '〫' => 228, + '〬' => 232, + '〭' => 222, + '〮' => 224, + '〯' => 224, + '゙' => 8, + '゚' => 8, + '꙯' => 230, + 'ꙴ' => 230, + 'ꙵ' => 230, + 'ꙶ' => 230, + 'ꙷ' => 230, + 'ꙸ' => 230, + 'ꙹ' => 230, + 'ꙺ' => 230, + 'ꙻ' => 230, + '꙼' => 230, + '꙽' => 230, + 'ꚞ' => 230, + 'ꚟ' => 230, + '꛰' => 230, + '꛱' => 230, + '꠆' => 9, + '꠬' => 9, + '꣄' => 9, + '꣠' => 230, + '꣡' => 230, + '꣢' => 230, + '꣣' => 230, + '꣤' => 230, + '꣥' => 230, + '꣦' => 230, + '꣧' => 230, + '꣨' => 230, + '꣩' => 230, + '꣪' => 230, + '꣫' => 230, + '꣬' => 230, + '꣭' => 230, + '꣮' => 230, + '꣯' => 230, + '꣰' => 230, + '꣱' => 230, + '꤫' => 220, + '꤬' => 220, + '꤭' => 220, + '꥓' => 9, + '꦳' => 7, + '꧀' => 9, + 'ꪰ' => 230, + 'ꪲ' => 230, + 'ꪳ' => 230, + 'ꪴ' => 220, + 'ꪷ' => 230, + 'ꪸ' => 230, + 'ꪾ' => 230, + '꪿' => 230, + '꫁' => 230, + '꫶' => 9, + '꯭' => 9, + 'ﬞ' => 26, + '︠' => 230, + '︡' => 230, + '︢' => 230, + '︣' => 230, + '︤' => 230, + '︥' => 230, + '︦' => 230, + '︧' => 220, + '︨' => 220, + '︩' => 220, + '︪' => 220, + '︫' => 220, + '︬' => 220, + '︭' => 220, + '︮' => 230, + '︯' => 230, + '𐇽' => 220, + '𐋠' => 220, + '𐍶' => 230, + '𐍷' => 230, + '𐍸' => 230, + '𐍹' => 230, + '𐍺' => 230, + '𐨍' => 220, + '𐨏' => 230, + '𐨸' => 230, + '𐨹' => 1, + '𐨺' => 220, + '𐨿' => 9, + '𐫥' => 230, + '𐫦' => 220, + '𐴤' => 230, + '𐴥' => 230, + '𐴦' => 230, + '𐴧' => 230, + '𐺫' => 230, + '𐺬' => 230, + '𐽆' => 220, + '𐽇' => 220, + '𐽈' => 230, + '𐽉' => 230, + '𐽊' => 230, + '𐽋' => 220, + '𐽌' => 230, + '𐽍' => 220, + '𐽎' => 220, + '𐽏' => 220, + '𐽐' => 220, + '𑁆' => 9, + '𑁿' => 9, + '𑂹' => 9, + '𑂺' => 7, + '𑄀' => 230, + '𑄁' => 230, + '𑄂' => 230, + '𑄳' => 9, + '𑄴' => 9, + '𑅳' => 7, + '𑇀' => 9, + '𑇊' => 7, + '𑈵' => 9, + '𑈶' => 7, + '𑋩' => 7, + '𑋪' => 9, + '𑌻' => 7, + '𑌼' => 7, + '𑍍' => 9, + '𑍦' => 230, + '𑍧' => 230, + '𑍨' => 230, + '𑍩' => 230, + '𑍪' => 230, + '𑍫' => 230, + '𑍬' => 230, + '𑍰' => 230, + '𑍱' => 230, + '𑍲' => 230, + '𑍳' => 230, + '𑍴' => 230, + '𑑂' => 9, + '𑑆' => 7, + '𑑞' => 230, + '𑓂' => 9, + '𑓃' => 7, + '𑖿' => 9, + '𑗀' => 7, + '𑘿' => 9, + '𑚶' => 9, + '𑚷' => 7, + '𑜫' => 9, + '𑠹' => 9, + '𑠺' => 7, + '𑤽' => 9, + '𑤾' => 9, + '𑥃' => 7, + '𑧠' => 9, + '𑨴' => 9, + '𑩇' => 9, + '𑪙' => 9, + '𑰿' => 9, + '𑵂' => 7, + '𑵄' => 9, + '𑵅' => 9, + '𑶗' => 9, + '𖫰' => 1, + '𖫱' => 1, + '𖫲' => 1, + '𖫳' => 1, + '𖫴' => 1, + '𖬰' => 230, + '𖬱' => 230, + '𖬲' => 230, + '𖬳' => 230, + '𖬴' => 230, + '𖬵' => 230, + '𖬶' => 230, + '𖿰' => 6, + '𖿱' => 6, + '𛲞' => 1, + '𝅥' => 216, + '𝅦' => 216, + '𝅧' => 1, + '𝅨' => 1, + '𝅩' => 1, + '𝅭' => 226, + '𝅮' => 216, + '𝅯' => 216, + '𝅰' => 216, + '𝅱' => 216, + '𝅲' => 216, + '𝅻' => 220, + '𝅼' => 220, + '𝅽' => 220, + '𝅾' => 220, + '𝅿' => 220, + '𝆀' => 220, + '𝆁' => 220, + '𝆂' => 220, + '𝆅' => 230, + '𝆆' => 230, + '𝆇' => 230, + '𝆈' => 230, + '𝆉' => 230, + '𝆊' => 220, + '𝆋' => 220, + '𝆪' => 230, + '𝆫' => 230, + '𝆬' => 230, + '𝆭' => 230, + '𝉂' => 230, + '𝉃' => 230, + '𝉄' => 230, + '𞀀' => 230, + '𞀁' => 230, + '𞀂' => 230, + '𞀃' => 230, + '𞀄' => 230, + '𞀅' => 230, + '𞀆' => 230, + '𞀈' => 230, + '𞀉' => 230, + '𞀊' => 230, + '𞀋' => 230, + '𞀌' => 230, + '𞀍' => 230, + '𞀎' => 230, + '𞀏' => 230, + '𞀐' => 230, + '𞀑' => 230, + '𞀒' => 230, + '𞀓' => 230, + '𞀔' => 230, + '𞀕' => 230, + '𞀖' => 230, + '𞀗' => 230, + '𞀘' => 230, + '𞀛' => 230, + '𞀜' => 230, + '𞀝' => 230, + '𞀞' => 230, + '𞀟' => 230, + '𞀠' => 230, + '𞀡' => 230, + '𞀣' => 230, + '𞀤' => 230, + '𞀦' => 230, + '𞀧' => 230, + '𞀨' => 230, + '𞀩' => 230, + '𞀪' => 230, + '𞄰' => 230, + '𞄱' => 230, + '𞄲' => 230, + '𞄳' => 230, + '𞄴' => 230, + '𞄵' => 230, + '𞄶' => 230, + '𞋬' => 230, + '𞋭' => 230, + '𞋮' => 230, + '𞋯' => 230, + '𞣐' => 220, + '𞣑' => 220, + '𞣒' => 220, + '𞣓' => 220, + '𞣔' => 220, + '𞣕' => 220, + '𞣖' => 220, + '𞥄' => 230, + '𞥅' => 230, + '𞥆' => 230, + '𞥇' => 230, + '𞥈' => 230, + '𞥉' => 230, + '𞥊' => 7, +); diff --git a/lib/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php b/lib/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php new file mode 100644 index 0000000000..1574902893 --- /dev/null +++ b/lib/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.php @@ -0,0 +1,3695 @@ + ' ', + '¨' => ' ̈', + 'ª' => 'a', + '¯' => ' ̄', + '²' => '2', + '³' => '3', + '´' => ' ́', + 'µ' => 'μ', + '¸' => ' ̧', + '¹' => '1', + 'º' => 'o', + '¼' => '1⁄4', + '½' => '1⁄2', + '¾' => '3⁄4', + 'IJ' => 'IJ', + 'ij' => 'ij', + 'Ŀ' => 'L·', + 'ŀ' => 'l·', + 'ʼn' => 'ʼn', + 'ſ' => 's', + 'DŽ' => 'DŽ', + 'Dž' => 'Dž', + 'dž' => 'dž', + 'LJ' => 'LJ', + 'Lj' => 'Lj', + 'lj' => 'lj', + 'NJ' => 'NJ', + 'Nj' => 'Nj', + 'nj' => 'nj', + 'DZ' => 'DZ', + 'Dz' => 'Dz', + 'dz' => 'dz', + 'ʰ' => 'h', + 'ʱ' => 'ɦ', + 'ʲ' => 'j', + 'ʳ' => 'r', + 'ʴ' => 'ɹ', + 'ʵ' => 'ɻ', + 'ʶ' => 'ʁ', + 'ʷ' => 'w', + 'ʸ' => 'y', + '˘' => ' ̆', + '˙' => ' ̇', + '˚' => ' ̊', + '˛' => ' ̨', + '˜' => ' ̃', + '˝' => ' ̋', + 'ˠ' => 'ɣ', + 'ˡ' => 'l', + 'ˢ' => 's', + 'ˣ' => 'x', + 'ˤ' => 'ʕ', + 'ͺ' => ' ͅ', + '΄' => ' ́', + '΅' => ' ̈́', + 'ϐ' => 'β', + 'ϑ' => 'θ', + 'ϒ' => 'Υ', + 'ϓ' => 'Ύ', + 'ϔ' => 'Ϋ', + 'ϕ' => 'φ', + 'ϖ' => 'π', + 'ϰ' => 'κ', + 'ϱ' => 'ρ', + 'ϲ' => 'ς', + 'ϴ' => 'Θ', + 'ϵ' => 'ε', + 'Ϲ' => 'Σ', + 'և' => 'եւ', + 'ٵ' => 'اٴ', + 'ٶ' => 'وٴ', + 'ٷ' => 'ۇٴ', + 'ٸ' => 'يٴ', + 'ำ' => 'ํา', + 'ຳ' => 'ໍາ', + 'ໜ' => 'ຫນ', + 'ໝ' => 'ຫມ', + '༌' => '་', + 'ཷ' => 'ྲཱྀ', + 'ཹ' => 'ླཱྀ', + 'ჼ' => 'ნ', + 'ᴬ' => 'A', + 'ᴭ' => 'Æ', + 'ᴮ' => 'B', + 'ᴰ' => 'D', + 'ᴱ' => 'E', + 'ᴲ' => 'Ǝ', + 'ᴳ' => 'G', + 'ᴴ' => 'H', + 'ᴵ' => 'I', + 'ᴶ' => 'J', + 'ᴷ' => 'K', + 'ᴸ' => 'L', + 'ᴹ' => 'M', + 'ᴺ' => 'N', + 'ᴼ' => 'O', + 'ᴽ' => 'Ȣ', + 'ᴾ' => 'P', + 'ᴿ' => 'R', + 'ᵀ' => 'T', + 'ᵁ' => 'U', + 'ᵂ' => 'W', + 'ᵃ' => 'a', + 'ᵄ' => 'ɐ', + 'ᵅ' => 'ɑ', + 'ᵆ' => 'ᴂ', + 'ᵇ' => 'b', + 'ᵈ' => 'd', + 'ᵉ' => 'e', + 'ᵊ' => 'ə', + 'ᵋ' => 'ɛ', + 'ᵌ' => 'ɜ', + 'ᵍ' => 'g', + 'ᵏ' => 'k', + 'ᵐ' => 'm', + 'ᵑ' => 'ŋ', + 'ᵒ' => 'o', + 'ᵓ' => 'ɔ', + 'ᵔ' => 'ᴖ', + 'ᵕ' => 'ᴗ', + 'ᵖ' => 'p', + 'ᵗ' => 't', + 'ᵘ' => 'u', + 'ᵙ' => 'ᴝ', + 'ᵚ' => 'ɯ', + 'ᵛ' => 'v', + 'ᵜ' => 'ᴥ', + 'ᵝ' => 'β', + 'ᵞ' => 'γ', + 'ᵟ' => 'δ', + 'ᵠ' => 'φ', + 'ᵡ' => 'χ', + 'ᵢ' => 'i', + 'ᵣ' => 'r', + 'ᵤ' => 'u', + 'ᵥ' => 'v', + 'ᵦ' => 'β', + 'ᵧ' => 'γ', + 'ᵨ' => 'ρ', + 'ᵩ' => 'φ', + 'ᵪ' => 'χ', + 'ᵸ' => 'н', + 'ᶛ' => 'ɒ', + 'ᶜ' => 'c', + 'ᶝ' => 'ɕ', + 'ᶞ' => 'ð', + 'ᶟ' => 'ɜ', + 'ᶠ' => 'f', + 'ᶡ' => 'ɟ', + 'ᶢ' => 'ɡ', + 'ᶣ' => 'ɥ', + 'ᶤ' => 'ɨ', + 'ᶥ' => 'ɩ', + 'ᶦ' => 'ɪ', + 'ᶧ' => 'ᵻ', + 'ᶨ' => 'ʝ', + 'ᶩ' => 'ɭ', + 'ᶪ' => 'ᶅ', + 'ᶫ' => 'ʟ', + 'ᶬ' => 'ɱ', + 'ᶭ' => 'ɰ', + 'ᶮ' => 'ɲ', + 'ᶯ' => 'ɳ', + 'ᶰ' => 'ɴ', + 'ᶱ' => 'ɵ', + 'ᶲ' => 'ɸ', + 'ᶳ' => 'ʂ', + 'ᶴ' => 'ʃ', + 'ᶵ' => 'ƫ', + 'ᶶ' => 'ʉ', + 'ᶷ' => 'ʊ', + 'ᶸ' => 'ᴜ', + 'ᶹ' => 'ʋ', + 'ᶺ' => 'ʌ', + 'ᶻ' => 'z', + 'ᶼ' => 'ʐ', + 'ᶽ' => 'ʑ', + 'ᶾ' => 'ʒ', + 'ᶿ' => 'θ', + 'ẚ' => 'aʾ', + 'ẛ' => 'ṡ', + '᾽' => ' ̓', + '᾿' => ' ̓', + '῀' => ' ͂', + '῁' => ' ̈͂', + '῍' => ' ̓̀', + '῎' => ' ̓́', + '῏' => ' ̓͂', + '῝' => ' ̔̀', + '῞' => ' ̔́', + '῟' => ' ̔͂', + '῭' => ' ̈̀', + '΅' => ' ̈́', + '´' => ' ́', + '῾' => ' ̔', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + ' ' => ' ', + '‑' => '‐', + '‗' => ' ̳', + '․' => '.', + '‥' => '..', + '…' => '...', + ' ' => ' ', + '″' => '′′', + '‴' => '′′′', + '‶' => '‵‵', + '‷' => '‵‵‵', + '‼' => '!!', + '‾' => ' ̅', + '⁇' => '??', + '⁈' => '?!', + '⁉' => '!?', + '⁗' => '′′′′', + ' ' => ' ', + '⁰' => '0', + 'ⁱ' => 'i', + '⁴' => '4', + '⁵' => '5', + '⁶' => '6', + '⁷' => '7', + '⁸' => '8', + '⁹' => '9', + '⁺' => '+', + '⁻' => '−', + '⁼' => '=', + '⁽' => '(', + '⁾' => ')', + 'ⁿ' => 'n', + '₀' => '0', + '₁' => '1', + '₂' => '2', + '₃' => '3', + '₄' => '4', + '₅' => '5', + '₆' => '6', + '₇' => '7', + '₈' => '8', + '₉' => '9', + '₊' => '+', + '₋' => '−', + '₌' => '=', + '₍' => '(', + '₎' => ')', + 'ₐ' => 'a', + 'ₑ' => 'e', + 'ₒ' => 'o', + 'ₓ' => 'x', + 'ₔ' => 'ə', + 'ₕ' => 'h', + 'ₖ' => 'k', + 'ₗ' => 'l', + 'ₘ' => 'm', + 'ₙ' => 'n', + 'ₚ' => 'p', + 'ₛ' => 's', + 'ₜ' => 't', + '₨' => 'Rs', + '℀' => 'a/c', + '℁' => 'a/s', + 'ℂ' => 'C', + '℃' => '°C', + '℅' => 'c/o', + '℆' => 'c/u', + 'ℇ' => 'Ɛ', + '℉' => '°F', + 'ℊ' => 'g', + 'ℋ' => 'H', + 'ℌ' => 'H', + 'ℍ' => 'H', + 'ℎ' => 'h', + 'ℏ' => 'ħ', + 'ℐ' => 'I', + 'ℑ' => 'I', + 'ℒ' => 'L', + 'ℓ' => 'l', + 'ℕ' => 'N', + '№' => 'No', + 'ℙ' => 'P', + 'ℚ' => 'Q', + 'ℛ' => 'R', + 'ℜ' => 'R', + 'ℝ' => 'R', + '℠' => 'SM', + '℡' => 'TEL', + '™' => 'TM', + 'ℤ' => 'Z', + 'ℨ' => 'Z', + 'ℬ' => 'B', + 'ℭ' => 'C', + 'ℯ' => 'e', + 'ℰ' => 'E', + 'ℱ' => 'F', + 'ℳ' => 'M', + 'ℴ' => 'o', + 'ℵ' => 'א', + 'ℶ' => 'ב', + 'ℷ' => 'ג', + 'ℸ' => 'ד', + 'ℹ' => 'i', + '℻' => 'FAX', + 'ℼ' => 'π', + 'ℽ' => 'γ', + 'ℾ' => 'Γ', + 'ℿ' => 'Π', + '⅀' => '∑', + 'ⅅ' => 'D', + 'ⅆ' => 'd', + 'ⅇ' => 'e', + 'ⅈ' => 'i', + 'ⅉ' => 'j', + '⅐' => '1⁄7', + '⅑' => '1⁄9', + '⅒' => '1⁄10', + '⅓' => '1⁄3', + '⅔' => '2⁄3', + '⅕' => '1⁄5', + '⅖' => '2⁄5', + '⅗' => '3⁄5', + '⅘' => '4⁄5', + '⅙' => '1⁄6', + '⅚' => '5⁄6', + '⅛' => '1⁄8', + '⅜' => '3⁄8', + '⅝' => '5⁄8', + '⅞' => '7⁄8', + '⅟' => '1⁄', + 'Ⅰ' => 'I', + 'Ⅱ' => 'II', + 'Ⅲ' => 'III', + 'Ⅳ' => 'IV', + 'Ⅴ' => 'V', + 'Ⅵ' => 'VI', + 'Ⅶ' => 'VII', + 'Ⅷ' => 'VIII', + 'Ⅸ' => 'IX', + 'Ⅹ' => 'X', + 'Ⅺ' => 'XI', + 'Ⅻ' => 'XII', + 'Ⅼ' => 'L', + 'Ⅽ' => 'C', + 'Ⅾ' => 'D', + 'Ⅿ' => 'M', + 'ⅰ' => 'i', + 'ⅱ' => 'ii', + 'ⅲ' => 'iii', + 'ⅳ' => 'iv', + 'ⅴ' => 'v', + 'ⅵ' => 'vi', + 'ⅶ' => 'vii', + 'ⅷ' => 'viii', + 'ⅸ' => 'ix', + 'ⅹ' => 'x', + 'ⅺ' => 'xi', + 'ⅻ' => 'xii', + 'ⅼ' => 'l', + 'ⅽ' => 'c', + 'ⅾ' => 'd', + 'ⅿ' => 'm', + '↉' => '0⁄3', + '∬' => '∫∫', + '∭' => '∫∫∫', + '∯' => '∮∮', + '∰' => '∮∮∮', + '①' => '1', + '②' => '2', + '③' => '3', + '④' => '4', + '⑤' => '5', + '⑥' => '6', + '⑦' => '7', + '⑧' => '8', + '⑨' => '9', + '⑩' => '10', + '⑪' => '11', + '⑫' => '12', + '⑬' => '13', + '⑭' => '14', + '⑮' => '15', + '⑯' => '16', + '⑰' => '17', + '⑱' => '18', + '⑲' => '19', + '⑳' => '20', + '⑴' => '(1)', + '⑵' => '(2)', + '⑶' => '(3)', + '⑷' => '(4)', + '⑸' => '(5)', + '⑹' => '(6)', + '⑺' => '(7)', + '⑻' => '(8)', + '⑼' => '(9)', + '⑽' => '(10)', + '⑾' => '(11)', + '⑿' => '(12)', + '⒀' => '(13)', + '⒁' => '(14)', + '⒂' => '(15)', + '⒃' => '(16)', + '⒄' => '(17)', + '⒅' => '(18)', + '⒆' => '(19)', + '⒇' => '(20)', + '⒈' => '1.', + '⒉' => '2.', + '⒊' => '3.', + '⒋' => '4.', + '⒌' => '5.', + '⒍' => '6.', + '⒎' => '7.', + '⒏' => '8.', + '⒐' => '9.', + '⒑' => '10.', + '⒒' => '11.', + '⒓' => '12.', + '⒔' => '13.', + '⒕' => '14.', + '⒖' => '15.', + '⒗' => '16.', + '⒘' => '17.', + '⒙' => '18.', + '⒚' => '19.', + '⒛' => '20.', + '⒜' => '(a)', + '⒝' => '(b)', + '⒞' => '(c)', + '⒟' => '(d)', + '⒠' => '(e)', + '⒡' => '(f)', + '⒢' => '(g)', + '⒣' => '(h)', + '⒤' => '(i)', + '⒥' => '(j)', + '⒦' => '(k)', + '⒧' => '(l)', + '⒨' => '(m)', + '⒩' => '(n)', + '⒪' => '(o)', + '⒫' => '(p)', + '⒬' => '(q)', + '⒭' => '(r)', + '⒮' => '(s)', + '⒯' => '(t)', + '⒰' => '(u)', + '⒱' => '(v)', + '⒲' => '(w)', + '⒳' => '(x)', + '⒴' => '(y)', + '⒵' => '(z)', + 'Ⓐ' => 'A', + 'Ⓑ' => 'B', + 'Ⓒ' => 'C', + 'Ⓓ' => 'D', + 'Ⓔ' => 'E', + 'Ⓕ' => 'F', + 'Ⓖ' => 'G', + 'Ⓗ' => 'H', + 'Ⓘ' => 'I', + 'Ⓙ' => 'J', + 'Ⓚ' => 'K', + 'Ⓛ' => 'L', + 'Ⓜ' => 'M', + 'Ⓝ' => 'N', + 'Ⓞ' => 'O', + 'Ⓟ' => 'P', + 'Ⓠ' => 'Q', + 'Ⓡ' => 'R', + 'Ⓢ' => 'S', + 'Ⓣ' => 'T', + 'Ⓤ' => 'U', + 'Ⓥ' => 'V', + 'Ⓦ' => 'W', + 'Ⓧ' => 'X', + 'Ⓨ' => 'Y', + 'Ⓩ' => 'Z', + 'ⓐ' => 'a', + 'ⓑ' => 'b', + 'ⓒ' => 'c', + 'ⓓ' => 'd', + 'ⓔ' => 'e', + 'ⓕ' => 'f', + 'ⓖ' => 'g', + 'ⓗ' => 'h', + 'ⓘ' => 'i', + 'ⓙ' => 'j', + 'ⓚ' => 'k', + 'ⓛ' => 'l', + 'ⓜ' => 'm', + 'ⓝ' => 'n', + 'ⓞ' => 'o', + 'ⓟ' => 'p', + 'ⓠ' => 'q', + 'ⓡ' => 'r', + 'ⓢ' => 's', + 'ⓣ' => 't', + 'ⓤ' => 'u', + 'ⓥ' => 'v', + 'ⓦ' => 'w', + 'ⓧ' => 'x', + 'ⓨ' => 'y', + 'ⓩ' => 'z', + '⓪' => '0', + '⨌' => '∫∫∫∫', + '⩴' => '::=', + '⩵' => '==', + '⩶' => '===', + 'ⱼ' => 'j', + 'ⱽ' => 'V', + 'ⵯ' => 'ⵡ', + '⺟' => '母', + '⻳' => '龟', + '⼀' => '一', + '⼁' => '丨', + '⼂' => '丶', + '⼃' => '丿', + '⼄' => '乙', + '⼅' => '亅', + '⼆' => '二', + '⼇' => '亠', + '⼈' => '人', + '⼉' => '儿', + '⼊' => '入', + '⼋' => '八', + '⼌' => '冂', + '⼍' => '冖', + '⼎' => '冫', + '⼏' => '几', + '⼐' => '凵', + '⼑' => '刀', + '⼒' => '力', + '⼓' => '勹', + '⼔' => '匕', + '⼕' => '匚', + '⼖' => '匸', + '⼗' => '十', + '⼘' => '卜', + '⼙' => '卩', + '⼚' => '厂', + '⼛' => '厶', + '⼜' => '又', + '⼝' => '口', + '⼞' => '囗', + '⼟' => '土', + '⼠' => '士', + '⼡' => '夂', + '⼢' => '夊', + '⼣' => '夕', + '⼤' => '大', + '⼥' => '女', + '⼦' => '子', + '⼧' => '宀', + '⼨' => '寸', + '⼩' => '小', + '⼪' => '尢', + '⼫' => '尸', + '⼬' => '屮', + '⼭' => '山', + '⼮' => '巛', + '⼯' => '工', + '⼰' => '己', + '⼱' => '巾', + '⼲' => '干', + '⼳' => '幺', + '⼴' => '广', + '⼵' => '廴', + '⼶' => '廾', + '⼷' => '弋', + '⼸' => '弓', + '⼹' => '彐', + '⼺' => '彡', + '⼻' => '彳', + '⼼' => '心', + '⼽' => '戈', + '⼾' => '戶', + '⼿' => '手', + '⽀' => '支', + '⽁' => '攴', + '⽂' => '文', + '⽃' => '斗', + '⽄' => '斤', + '⽅' => '方', + '⽆' => '无', + '⽇' => '日', + '⽈' => '曰', + '⽉' => '月', + '⽊' => '木', + '⽋' => '欠', + '⽌' => '止', + '⽍' => '歹', + '⽎' => '殳', + '⽏' => '毋', + '⽐' => '比', + '⽑' => '毛', + '⽒' => '氏', + '⽓' => '气', + '⽔' => '水', + '⽕' => '火', + '⽖' => '爪', + '⽗' => '父', + '⽘' => '爻', + '⽙' => '爿', + '⽚' => '片', + '⽛' => '牙', + '⽜' => '牛', + '⽝' => '犬', + '⽞' => '玄', + '⽟' => '玉', + '⽠' => '瓜', + '⽡' => '瓦', + '⽢' => '甘', + '⽣' => '生', + '⽤' => '用', + '⽥' => '田', + '⽦' => '疋', + '⽧' => '疒', + '⽨' => '癶', + '⽩' => '白', + '⽪' => '皮', + '⽫' => '皿', + '⽬' => '目', + '⽭' => '矛', + '⽮' => '矢', + '⽯' => '石', + '⽰' => '示', + '⽱' => '禸', + '⽲' => '禾', + '⽳' => '穴', + '⽴' => '立', + '⽵' => '竹', + '⽶' => '米', + '⽷' => '糸', + '⽸' => '缶', + '⽹' => '网', + '⽺' => '羊', + '⽻' => '羽', + '⽼' => '老', + '⽽' => '而', + '⽾' => '耒', + '⽿' => '耳', + '⾀' => '聿', + '⾁' => '肉', + '⾂' => '臣', + '⾃' => '自', + '⾄' => '至', + '⾅' => '臼', + '⾆' => '舌', + '⾇' => '舛', + '⾈' => '舟', + '⾉' => '艮', + '⾊' => '色', + '⾋' => '艸', + '⾌' => '虍', + '⾍' => '虫', + '⾎' => '血', + '⾏' => '行', + '⾐' => '衣', + '⾑' => '襾', + '⾒' => '見', + '⾓' => '角', + '⾔' => '言', + '⾕' => '谷', + '⾖' => '豆', + '⾗' => '豕', + '⾘' => '豸', + '⾙' => '貝', + '⾚' => '赤', + '⾛' => '走', + '⾜' => '足', + '⾝' => '身', + '⾞' => '車', + '⾟' => '辛', + '⾠' => '辰', + '⾡' => '辵', + '⾢' => '邑', + '⾣' => '酉', + '⾤' => '釆', + '⾥' => '里', + '⾦' => '金', + '⾧' => '長', + '⾨' => '門', + '⾩' => '阜', + '⾪' => '隶', + '⾫' => '隹', + '⾬' => '雨', + '⾭' => '靑', + '⾮' => '非', + '⾯' => '面', + '⾰' => '革', + '⾱' => '韋', + '⾲' => '韭', + '⾳' => '音', + '⾴' => '頁', + '⾵' => '風', + '⾶' => '飛', + '⾷' => '食', + '⾸' => '首', + '⾹' => '香', + '⾺' => '馬', + '⾻' => '骨', + '⾼' => '高', + '⾽' => '髟', + '⾾' => '鬥', + '⾿' => '鬯', + '⿀' => '鬲', + '⿁' => '鬼', + '⿂' => '魚', + '⿃' => '鳥', + '⿄' => '鹵', + '⿅' => '鹿', + '⿆' => '麥', + '⿇' => '麻', + '⿈' => '黃', + '⿉' => '黍', + '⿊' => '黑', + '⿋' => '黹', + '⿌' => '黽', + '⿍' => '鼎', + '⿎' => '鼓', + '⿏' => '鼠', + '⿐' => '鼻', + '⿑' => '齊', + '⿒' => '齒', + '⿓' => '龍', + '⿔' => '龜', + '⿕' => '龠', + ' ' => ' ', + '〶' => '〒', + '〸' => '十', + '〹' => '卄', + '〺' => '卅', + '゛' => ' ゙', + '゜' => ' ゚', + 'ゟ' => 'より', + 'ヿ' => 'コト', + 'ㄱ' => 'ᄀ', + 'ㄲ' => 'ᄁ', + 'ㄳ' => 'ᆪ', + 'ㄴ' => 'ᄂ', + 'ㄵ' => 'ᆬ', + 'ㄶ' => 'ᆭ', + 'ㄷ' => 'ᄃ', + 'ㄸ' => 'ᄄ', + 'ㄹ' => 'ᄅ', + 'ㄺ' => 'ᆰ', + 'ㄻ' => 'ᆱ', + 'ㄼ' => 'ᆲ', + 'ㄽ' => 'ᆳ', + 'ㄾ' => 'ᆴ', + 'ㄿ' => 'ᆵ', + 'ㅀ' => 'ᄚ', + 'ㅁ' => 'ᄆ', + 'ㅂ' => 'ᄇ', + 'ㅃ' => 'ᄈ', + 'ㅄ' => 'ᄡ', + 'ㅅ' => 'ᄉ', + 'ㅆ' => 'ᄊ', + 'ㅇ' => 'ᄋ', + 'ㅈ' => 'ᄌ', + 'ㅉ' => 'ᄍ', + 'ㅊ' => 'ᄎ', + 'ㅋ' => 'ᄏ', + 'ㅌ' => 'ᄐ', + 'ㅍ' => 'ᄑ', + 'ㅎ' => 'ᄒ', + 'ㅏ' => 'ᅡ', + 'ㅐ' => 'ᅢ', + 'ㅑ' => 'ᅣ', + 'ㅒ' => 'ᅤ', + 'ㅓ' => 'ᅥ', + 'ㅔ' => 'ᅦ', + 'ㅕ' => 'ᅧ', + 'ㅖ' => 'ᅨ', + 'ㅗ' => 'ᅩ', + 'ㅘ' => 'ᅪ', + 'ㅙ' => 'ᅫ', + 'ㅚ' => 'ᅬ', + 'ㅛ' => 'ᅭ', + 'ㅜ' => 'ᅮ', + 'ㅝ' => 'ᅯ', + 'ㅞ' => 'ᅰ', + 'ㅟ' => 'ᅱ', + 'ㅠ' => 'ᅲ', + 'ㅡ' => 'ᅳ', + 'ㅢ' => 'ᅴ', + 'ㅣ' => 'ᅵ', + 'ㅤ' => 'ᅠ', + 'ㅥ' => 'ᄔ', + 'ㅦ' => 'ᄕ', + 'ㅧ' => 'ᇇ', + 'ㅨ' => 'ᇈ', + 'ㅩ' => 'ᇌ', + 'ㅪ' => 'ᇎ', + 'ㅫ' => 'ᇓ', + 'ㅬ' => 'ᇗ', + 'ㅭ' => 'ᇙ', + 'ㅮ' => 'ᄜ', + 'ㅯ' => 'ᇝ', + 'ㅰ' => 'ᇟ', + 'ㅱ' => 'ᄝ', + 'ㅲ' => 'ᄞ', + 'ㅳ' => 'ᄠ', + 'ㅴ' => 'ᄢ', + 'ㅵ' => 'ᄣ', + 'ㅶ' => 'ᄧ', + 'ㅷ' => 'ᄩ', + 'ㅸ' => 'ᄫ', + 'ㅹ' => 'ᄬ', + 'ㅺ' => 'ᄭ', + 'ㅻ' => 'ᄮ', + 'ㅼ' => 'ᄯ', + 'ㅽ' => 'ᄲ', + 'ㅾ' => 'ᄶ', + 'ㅿ' => 'ᅀ', + 'ㆀ' => 'ᅇ', + 'ㆁ' => 'ᅌ', + 'ㆂ' => 'ᇱ', + 'ㆃ' => 'ᇲ', + 'ㆄ' => 'ᅗ', + 'ㆅ' => 'ᅘ', + 'ㆆ' => 'ᅙ', + 'ㆇ' => 'ᆄ', + 'ㆈ' => 'ᆅ', + 'ㆉ' => 'ᆈ', + 'ㆊ' => 'ᆑ', + 'ㆋ' => 'ᆒ', + 'ㆌ' => 'ᆔ', + 'ㆍ' => 'ᆞ', + 'ㆎ' => 'ᆡ', + '㆒' => '一', + '㆓' => '二', + '㆔' => '三', + '㆕' => '四', + '㆖' => '上', + '㆗' => '中', + '㆘' => '下', + '㆙' => '甲', + '㆚' => '乙', + '㆛' => '丙', + '㆜' => '丁', + '㆝' => '天', + '㆞' => '地', + '㆟' => '人', + '㈀' => '(ᄀ)', + '㈁' => '(ᄂ)', + '㈂' => '(ᄃ)', + '㈃' => '(ᄅ)', + '㈄' => '(ᄆ)', + '㈅' => '(ᄇ)', + '㈆' => '(ᄉ)', + '㈇' => '(ᄋ)', + '㈈' => '(ᄌ)', + '㈉' => '(ᄎ)', + '㈊' => '(ᄏ)', + '㈋' => '(ᄐ)', + '㈌' => '(ᄑ)', + '㈍' => '(ᄒ)', + '㈎' => '(가)', + '㈏' => '(나)', + '㈐' => '(다)', + '㈑' => '(라)', + '㈒' => '(마)', + '㈓' => '(바)', + '㈔' => '(사)', + '㈕' => '(아)', + '㈖' => '(자)', + '㈗' => '(차)', + '㈘' => '(카)', + '㈙' => '(타)', + '㈚' => '(파)', + '㈛' => '(하)', + '㈜' => '(주)', + '㈝' => '(오전)', + '㈞' => '(오후)', + '㈠' => '(一)', + '㈡' => '(二)', + '㈢' => '(三)', + '㈣' => '(四)', + '㈤' => '(五)', + '㈥' => '(六)', + '㈦' => '(七)', + '㈧' => '(八)', + '㈨' => '(九)', + '㈩' => '(十)', + '㈪' => '(月)', + '㈫' => '(火)', + '㈬' => '(水)', + '㈭' => '(木)', + '㈮' => '(金)', + '㈯' => '(土)', + '㈰' => '(日)', + '㈱' => '(株)', + '㈲' => '(有)', + '㈳' => '(社)', + '㈴' => '(名)', + '㈵' => '(特)', + '㈶' => '(財)', + '㈷' => '(祝)', + '㈸' => '(労)', + '㈹' => '(代)', + '㈺' => '(呼)', + '㈻' => '(学)', + '㈼' => '(監)', + '㈽' => '(企)', + '㈾' => '(資)', + '㈿' => '(協)', + '㉀' => '(祭)', + '㉁' => '(休)', + '㉂' => '(自)', + '㉃' => '(至)', + '㉄' => '問', + '㉅' => '幼', + '㉆' => '文', + '㉇' => '箏', + '㉐' => 'PTE', + '㉑' => '21', + '㉒' => '22', + '㉓' => '23', + '㉔' => '24', + '㉕' => '25', + '㉖' => '26', + '㉗' => '27', + '㉘' => '28', + '㉙' => '29', + '㉚' => '30', + '㉛' => '31', + '㉜' => '32', + '㉝' => '33', + '㉞' => '34', + '㉟' => '35', + '㉠' => 'ᄀ', + '㉡' => 'ᄂ', + '㉢' => 'ᄃ', + '㉣' => 'ᄅ', + '㉤' => 'ᄆ', + '㉥' => 'ᄇ', + '㉦' => 'ᄉ', + '㉧' => 'ᄋ', + '㉨' => 'ᄌ', + '㉩' => 'ᄎ', + '㉪' => 'ᄏ', + '㉫' => 'ᄐ', + '㉬' => 'ᄑ', + '㉭' => 'ᄒ', + '㉮' => '가', + '㉯' => '나', + '㉰' => '다', + '㉱' => '라', + '㉲' => '마', + '㉳' => '바', + '㉴' => '사', + '㉵' => '아', + '㉶' => '자', + '㉷' => '차', + '㉸' => '카', + '㉹' => '타', + '㉺' => '파', + '㉻' => '하', + '㉼' => '참고', + '㉽' => '주의', + '㉾' => '우', + '㊀' => '一', + '㊁' => '二', + '㊂' => '三', + '㊃' => '四', + '㊄' => '五', + '㊅' => '六', + '㊆' => '七', + '㊇' => '八', + '㊈' => '九', + '㊉' => '十', + '㊊' => '月', + '㊋' => '火', + '㊌' => '水', + '㊍' => '木', + '㊎' => '金', + '㊏' => '土', + '㊐' => '日', + '㊑' => '株', + '㊒' => '有', + '㊓' => '社', + '㊔' => '名', + '㊕' => '特', + '㊖' => '財', + '㊗' => '祝', + '㊘' => '労', + '㊙' => '秘', + '㊚' => '男', + '㊛' => '女', + '㊜' => '適', + '㊝' => '優', + '㊞' => '印', + '㊟' => '注', + '㊠' => '項', + '㊡' => '休', + '㊢' => '写', + '㊣' => '正', + '㊤' => '上', + '㊥' => '中', + '㊦' => '下', + '㊧' => '左', + '㊨' => '右', + '㊩' => '医', + '㊪' => '宗', + '㊫' => '学', + '㊬' => '監', + '㊭' => '企', + '㊮' => '資', + '㊯' => '協', + '㊰' => '夜', + '㊱' => '36', + '㊲' => '37', + '㊳' => '38', + '㊴' => '39', + '㊵' => '40', + '㊶' => '41', + '㊷' => '42', + '㊸' => '43', + '㊹' => '44', + '㊺' => '45', + '㊻' => '46', + '㊼' => '47', + '㊽' => '48', + '㊾' => '49', + '㊿' => '50', + '㋀' => '1月', + '㋁' => '2月', + '㋂' => '3月', + '㋃' => '4月', + '㋄' => '5月', + '㋅' => '6月', + '㋆' => '7月', + '㋇' => '8月', + '㋈' => '9月', + '㋉' => '10月', + '㋊' => '11月', + '㋋' => '12月', + '㋌' => 'Hg', + '㋍' => 'erg', + '㋎' => 'eV', + '㋏' => 'LTD', + '㋐' => 'ア', + '㋑' => 'イ', + '㋒' => 'ウ', + '㋓' => 'エ', + '㋔' => 'オ', + '㋕' => 'カ', + '㋖' => 'キ', + '㋗' => 'ク', + '㋘' => 'ケ', + '㋙' => 'コ', + '㋚' => 'サ', + '㋛' => 'シ', + '㋜' => 'ス', + '㋝' => 'セ', + '㋞' => 'ソ', + '㋟' => 'タ', + '㋠' => 'チ', + '㋡' => 'ツ', + '㋢' => 'テ', + '㋣' => 'ト', + '㋤' => 'ナ', + '㋥' => 'ニ', + '㋦' => 'ヌ', + '㋧' => 'ネ', + '㋨' => 'ノ', + '㋩' => 'ハ', + '㋪' => 'ヒ', + '㋫' => 'フ', + '㋬' => 'ヘ', + '㋭' => 'ホ', + '㋮' => 'マ', + '㋯' => 'ミ', + '㋰' => 'ム', + '㋱' => 'メ', + '㋲' => 'モ', + '㋳' => 'ヤ', + '㋴' => 'ユ', + '㋵' => 'ヨ', + '㋶' => 'ラ', + '㋷' => 'リ', + '㋸' => 'ル', + '㋹' => 'レ', + '㋺' => 'ロ', + '㋻' => 'ワ', + '㋼' => 'ヰ', + '㋽' => 'ヱ', + '㋾' => 'ヲ', + '㋿' => '令和', + '㌀' => 'アパート', + '㌁' => 'アルファ', + '㌂' => 'アンペア', + '㌃' => 'アール', + '㌄' => 'イニング', + '㌅' => 'インチ', + '㌆' => 'ウォン', + '㌇' => 'エスクード', + '㌈' => 'エーカー', + '㌉' => 'オンス', + '㌊' => 'オーム', + '㌋' => 'カイリ', + '㌌' => 'カラット', + '㌍' => 'カロリー', + '㌎' => 'ガロン', + '㌏' => 'ガンマ', + '㌐' => 'ギガ', + '㌑' => 'ギニー', + '㌒' => 'キュリー', + '㌓' => 'ギルダー', + '㌔' => 'キロ', + '㌕' => 'キログラム', + '㌖' => 'キロメートル', + '㌗' => 'キロワット', + '㌘' => 'グラム', + '㌙' => 'グラムトン', + '㌚' => 'クルゼイロ', + '㌛' => 'クローネ', + '㌜' => 'ケース', + '㌝' => 'コルナ', + '㌞' => 'コーポ', + '㌟' => 'サイクル', + '㌠' => 'サンチーム', + '㌡' => 'シリング', + '㌢' => 'センチ', + '㌣' => 'セント', + '㌤' => 'ダース', + '㌥' => 'デシ', + '㌦' => 'ドル', + '㌧' => 'トン', + '㌨' => 'ナノ', + '㌩' => 'ノット', + '㌪' => 'ハイツ', + '㌫' => 'パーセント', + '㌬' => 'パーツ', + '㌭' => 'バーレル', + '㌮' => 'ピアストル', + '㌯' => 'ピクル', + '㌰' => 'ピコ', + '㌱' => 'ビル', + '㌲' => 'ファラッド', + '㌳' => 'フィート', + '㌴' => 'ブッシェル', + '㌵' => 'フラン', + '㌶' => 'ヘクタール', + '㌷' => 'ペソ', + '㌸' => 'ペニヒ', + '㌹' => 'ヘルツ', + '㌺' => 'ペンス', + '㌻' => 'ページ', + '㌼' => 'ベータ', + '㌽' => 'ポイント', + '㌾' => 'ボルト', + '㌿' => 'ホン', + '㍀' => 'ポンド', + '㍁' => 'ホール', + '㍂' => 'ホーン', + '㍃' => 'マイクロ', + '㍄' => 'マイル', + '㍅' => 'マッハ', + '㍆' => 'マルク', + '㍇' => 'マンション', + '㍈' => 'ミクロン', + '㍉' => 'ミリ', + '㍊' => 'ミリバール', + '㍋' => 'メガ', + '㍌' => 'メガトン', + '㍍' => 'メートル', + '㍎' => 'ヤード', + '㍏' => 'ヤール', + '㍐' => 'ユアン', + '㍑' => 'リットル', + '㍒' => 'リラ', + '㍓' => 'ルピー', + '㍔' => 'ルーブル', + '㍕' => 'レム', + '㍖' => 'レントゲン', + '㍗' => 'ワット', + '㍘' => '0点', + '㍙' => '1点', + '㍚' => '2点', + '㍛' => '3点', + '㍜' => '4点', + '㍝' => '5点', + '㍞' => '6点', + '㍟' => '7点', + '㍠' => '8点', + '㍡' => '9点', + '㍢' => '10点', + '㍣' => '11点', + '㍤' => '12点', + '㍥' => '13点', + '㍦' => '14点', + '㍧' => '15点', + '㍨' => '16点', + '㍩' => '17点', + '㍪' => '18点', + '㍫' => '19点', + '㍬' => '20点', + '㍭' => '21点', + '㍮' => '22点', + '㍯' => '23点', + '㍰' => '24点', + '㍱' => 'hPa', + '㍲' => 'da', + '㍳' => 'AU', + '㍴' => 'bar', + '㍵' => 'oV', + '㍶' => 'pc', + '㍷' => 'dm', + '㍸' => 'dm2', + '㍹' => 'dm3', + '㍺' => 'IU', + '㍻' => '平成', + '㍼' => '昭和', + '㍽' => '大正', + '㍾' => '明治', + '㍿' => '株式会社', + '㎀' => 'pA', + '㎁' => 'nA', + '㎂' => 'μA', + '㎃' => 'mA', + '㎄' => 'kA', + '㎅' => 'KB', + '㎆' => 'MB', + '㎇' => 'GB', + '㎈' => 'cal', + '㎉' => 'kcal', + '㎊' => 'pF', + '㎋' => 'nF', + '㎌' => 'μF', + '㎍' => 'μg', + '㎎' => 'mg', + '㎏' => 'kg', + '㎐' => 'Hz', + '㎑' => 'kHz', + '㎒' => 'MHz', + '㎓' => 'GHz', + '㎔' => 'THz', + '㎕' => 'μl', + '㎖' => 'ml', + '㎗' => 'dl', + '㎘' => 'kl', + '㎙' => 'fm', + '㎚' => 'nm', + '㎛' => 'μm', + '㎜' => 'mm', + '㎝' => 'cm', + '㎞' => 'km', + '㎟' => 'mm2', + '㎠' => 'cm2', + '㎡' => 'm2', + '㎢' => 'km2', + '㎣' => 'mm3', + '㎤' => 'cm3', + '㎥' => 'm3', + '㎦' => 'km3', + '㎧' => 'm∕s', + '㎨' => 'm∕s2', + '㎩' => 'Pa', + '㎪' => 'kPa', + '㎫' => 'MPa', + '㎬' => 'GPa', + '㎭' => 'rad', + '㎮' => 'rad∕s', + '㎯' => 'rad∕s2', + '㎰' => 'ps', + '㎱' => 'ns', + '㎲' => 'μs', + '㎳' => 'ms', + '㎴' => 'pV', + '㎵' => 'nV', + '㎶' => 'μV', + '㎷' => 'mV', + '㎸' => 'kV', + '㎹' => 'MV', + '㎺' => 'pW', + '㎻' => 'nW', + '㎼' => 'μW', + '㎽' => 'mW', + '㎾' => 'kW', + '㎿' => 'MW', + '㏀' => 'kΩ', + '㏁' => 'MΩ', + '㏂' => 'a.m.', + '㏃' => 'Bq', + '㏄' => 'cc', + '㏅' => 'cd', + '㏆' => 'C∕kg', + '㏇' => 'Co.', + '㏈' => 'dB', + '㏉' => 'Gy', + '㏊' => 'ha', + '㏋' => 'HP', + '㏌' => 'in', + '㏍' => 'KK', + '㏎' => 'KM', + '㏏' => 'kt', + '㏐' => 'lm', + '㏑' => 'ln', + '㏒' => 'log', + '㏓' => 'lx', + '㏔' => 'mb', + '㏕' => 'mil', + '㏖' => 'mol', + '㏗' => 'PH', + '㏘' => 'p.m.', + '㏙' => 'PPM', + '㏚' => 'PR', + '㏛' => 'sr', + '㏜' => 'Sv', + '㏝' => 'Wb', + '㏞' => 'V∕m', + '㏟' => 'A∕m', + '㏠' => '1日', + '㏡' => '2日', + '㏢' => '3日', + '㏣' => '4日', + '㏤' => '5日', + '㏥' => '6日', + '㏦' => '7日', + '㏧' => '8日', + '㏨' => '9日', + '㏩' => '10日', + '㏪' => '11日', + '㏫' => '12日', + '㏬' => '13日', + '㏭' => '14日', + '㏮' => '15日', + '㏯' => '16日', + '㏰' => '17日', + '㏱' => '18日', + '㏲' => '19日', + '㏳' => '20日', + '㏴' => '21日', + '㏵' => '22日', + '㏶' => '23日', + '㏷' => '24日', + '㏸' => '25日', + '㏹' => '26日', + '㏺' => '27日', + '㏻' => '28日', + '㏼' => '29日', + '㏽' => '30日', + '㏾' => '31日', + '㏿' => 'gal', + 'ꚜ' => 'ъ', + 'ꚝ' => 'ь', + 'ꝰ' => 'ꝯ', + 'ꟸ' => 'Ħ', + 'ꟹ' => 'œ', + 'ꭜ' => 'ꜧ', + 'ꭝ' => 'ꬷ', + 'ꭞ' => 'ɫ', + 'ꭟ' => 'ꭒ', + 'ꭩ' => 'ʍ', + 'ff' => 'ff', + 'fi' => 'fi', + 'fl' => 'fl', + 'ffi' => 'ffi', + 'ffl' => 'ffl', + 'ſt' => 'st', + 'st' => 'st', + 'ﬓ' => 'մն', + 'ﬔ' => 'մե', + 'ﬕ' => 'մի', + 'ﬖ' => 'վն', + 'ﬗ' => 'մխ', + 'ﬠ' => 'ע', + 'ﬡ' => 'א', + 'ﬢ' => 'ד', + 'ﬣ' => 'ה', + 'ﬤ' => 'כ', + 'ﬥ' => 'ל', + 'ﬦ' => 'ם', + 'ﬧ' => 'ר', + 'ﬨ' => 'ת', + '﬩' => '+', + 'ﭏ' => 'אל', + 'ﭐ' => 'ٱ', + 'ﭑ' => 'ٱ', + 'ﭒ' => 'ٻ', + 'ﭓ' => 'ٻ', + 'ﭔ' => 'ٻ', + 'ﭕ' => 'ٻ', + 'ﭖ' => 'پ', + 'ﭗ' => 'پ', + 'ﭘ' => 'پ', + 'ﭙ' => 'پ', + 'ﭚ' => 'ڀ', + 'ﭛ' => 'ڀ', + 'ﭜ' => 'ڀ', + 'ﭝ' => 'ڀ', + 'ﭞ' => 'ٺ', + 'ﭟ' => 'ٺ', + 'ﭠ' => 'ٺ', + 'ﭡ' => 'ٺ', + 'ﭢ' => 'ٿ', + 'ﭣ' => 'ٿ', + 'ﭤ' => 'ٿ', + 'ﭥ' => 'ٿ', + 'ﭦ' => 'ٹ', + 'ﭧ' => 'ٹ', + 'ﭨ' => 'ٹ', + 'ﭩ' => 'ٹ', + 'ﭪ' => 'ڤ', + 'ﭫ' => 'ڤ', + 'ﭬ' => 'ڤ', + 'ﭭ' => 'ڤ', + 'ﭮ' => 'ڦ', + 'ﭯ' => 'ڦ', + 'ﭰ' => 'ڦ', + 'ﭱ' => 'ڦ', + 'ﭲ' => 'ڄ', + 'ﭳ' => 'ڄ', + 'ﭴ' => 'ڄ', + 'ﭵ' => 'ڄ', + 'ﭶ' => 'ڃ', + 'ﭷ' => 'ڃ', + 'ﭸ' => 'ڃ', + 'ﭹ' => 'ڃ', + 'ﭺ' => 'چ', + 'ﭻ' => 'چ', + 'ﭼ' => 'چ', + 'ﭽ' => 'چ', + 'ﭾ' => 'ڇ', + 'ﭿ' => 'ڇ', + 'ﮀ' => 'ڇ', + 'ﮁ' => 'ڇ', + 'ﮂ' => 'ڍ', + 'ﮃ' => 'ڍ', + 'ﮄ' => 'ڌ', + 'ﮅ' => 'ڌ', + 'ﮆ' => 'ڎ', + 'ﮇ' => 'ڎ', + 'ﮈ' => 'ڈ', + 'ﮉ' => 'ڈ', + 'ﮊ' => 'ژ', + 'ﮋ' => 'ژ', + 'ﮌ' => 'ڑ', + 'ﮍ' => 'ڑ', + 'ﮎ' => 'ک', + 'ﮏ' => 'ک', + 'ﮐ' => 'ک', + 'ﮑ' => 'ک', + 'ﮒ' => 'گ', + 'ﮓ' => 'گ', + 'ﮔ' => 'گ', + 'ﮕ' => 'گ', + 'ﮖ' => 'ڳ', + 'ﮗ' => 'ڳ', + 'ﮘ' => 'ڳ', + 'ﮙ' => 'ڳ', + 'ﮚ' => 'ڱ', + 'ﮛ' => 'ڱ', + 'ﮜ' => 'ڱ', + 'ﮝ' => 'ڱ', + 'ﮞ' => 'ں', + 'ﮟ' => 'ں', + 'ﮠ' => 'ڻ', + 'ﮡ' => 'ڻ', + 'ﮢ' => 'ڻ', + 'ﮣ' => 'ڻ', + 'ﮤ' => 'ۀ', + 'ﮥ' => 'ۀ', + 'ﮦ' => 'ہ', + 'ﮧ' => 'ہ', + 'ﮨ' => 'ہ', + 'ﮩ' => 'ہ', + 'ﮪ' => 'ھ', + 'ﮫ' => 'ھ', + 'ﮬ' => 'ھ', + 'ﮭ' => 'ھ', + 'ﮮ' => 'ے', + 'ﮯ' => 'ے', + 'ﮰ' => 'ۓ', + 'ﮱ' => 'ۓ', + 'ﯓ' => 'ڭ', + 'ﯔ' => 'ڭ', + 'ﯕ' => 'ڭ', + 'ﯖ' => 'ڭ', + 'ﯗ' => 'ۇ', + 'ﯘ' => 'ۇ', + 'ﯙ' => 'ۆ', + 'ﯚ' => 'ۆ', + 'ﯛ' => 'ۈ', + 'ﯜ' => 'ۈ', + 'ﯝ' => 'ۇٴ', + 'ﯞ' => 'ۋ', + 'ﯟ' => 'ۋ', + 'ﯠ' => 'ۅ', + 'ﯡ' => 'ۅ', + 'ﯢ' => 'ۉ', + 'ﯣ' => 'ۉ', + 'ﯤ' => 'ې', + 'ﯥ' => 'ې', + 'ﯦ' => 'ې', + 'ﯧ' => 'ې', + 'ﯨ' => 'ى', + 'ﯩ' => 'ى', + 'ﯪ' => 'ئا', + 'ﯫ' => 'ئا', + 'ﯬ' => 'ئە', + 'ﯭ' => 'ئە', + 'ﯮ' => 'ئو', + 'ﯯ' => 'ئو', + 'ﯰ' => 'ئۇ', + 'ﯱ' => 'ئۇ', + 'ﯲ' => 'ئۆ', + 'ﯳ' => 'ئۆ', + 'ﯴ' => 'ئۈ', + 'ﯵ' => 'ئۈ', + 'ﯶ' => 'ئې', + 'ﯷ' => 'ئې', + 'ﯸ' => 'ئې', + 'ﯹ' => 'ئى', + 'ﯺ' => 'ئى', + 'ﯻ' => 'ئى', + 'ﯼ' => 'ی', + 'ﯽ' => 'ی', + 'ﯾ' => 'ی', + 'ﯿ' => 'ی', + 'ﰀ' => 'ئج', + 'ﰁ' => 'ئح', + 'ﰂ' => 'ئم', + 'ﰃ' => 'ئى', + 'ﰄ' => 'ئي', + 'ﰅ' => 'بج', + 'ﰆ' => 'بح', + 'ﰇ' => 'بخ', + 'ﰈ' => 'بم', + 'ﰉ' => 'بى', + 'ﰊ' => 'بي', + 'ﰋ' => 'تج', + 'ﰌ' => 'تح', + 'ﰍ' => 'تخ', + 'ﰎ' => 'تم', + 'ﰏ' => 'تى', + 'ﰐ' => 'تي', + 'ﰑ' => 'ثج', + 'ﰒ' => 'ثم', + 'ﰓ' => 'ثى', + 'ﰔ' => 'ثي', + 'ﰕ' => 'جح', + 'ﰖ' => 'جم', + 'ﰗ' => 'حج', + 'ﰘ' => 'حم', + 'ﰙ' => 'خج', + 'ﰚ' => 'خح', + 'ﰛ' => 'خم', + 'ﰜ' => 'سج', + 'ﰝ' => 'سح', + 'ﰞ' => 'سخ', + 'ﰟ' => 'سم', + 'ﰠ' => 'صح', + 'ﰡ' => 'صم', + 'ﰢ' => 'ضج', + 'ﰣ' => 'ضح', + 'ﰤ' => 'ضخ', + 'ﰥ' => 'ضم', + 'ﰦ' => 'طح', + 'ﰧ' => 'طم', + 'ﰨ' => 'ظم', + 'ﰩ' => 'عج', + 'ﰪ' => 'عم', + 'ﰫ' => 'غج', + 'ﰬ' => 'غم', + 'ﰭ' => 'فج', + 'ﰮ' => 'فح', + 'ﰯ' => 'فخ', + 'ﰰ' => 'فم', + 'ﰱ' => 'فى', + 'ﰲ' => 'في', + 'ﰳ' => 'قح', + 'ﰴ' => 'قم', + 'ﰵ' => 'قى', + 'ﰶ' => 'قي', + 'ﰷ' => 'كا', + 'ﰸ' => 'كج', + 'ﰹ' => 'كح', + 'ﰺ' => 'كخ', + 'ﰻ' => 'كل', + 'ﰼ' => 'كم', + 'ﰽ' => 'كى', + 'ﰾ' => 'كي', + 'ﰿ' => 'لج', + 'ﱀ' => 'لح', + 'ﱁ' => 'لخ', + 'ﱂ' => 'لم', + 'ﱃ' => 'لى', + 'ﱄ' => 'لي', + 'ﱅ' => 'مج', + 'ﱆ' => 'مح', + 'ﱇ' => 'مخ', + 'ﱈ' => 'مم', + 'ﱉ' => 'مى', + 'ﱊ' => 'مي', + 'ﱋ' => 'نج', + 'ﱌ' => 'نح', + 'ﱍ' => 'نخ', + 'ﱎ' => 'نم', + 'ﱏ' => 'نى', + 'ﱐ' => 'ني', + 'ﱑ' => 'هج', + 'ﱒ' => 'هم', + 'ﱓ' => 'هى', + 'ﱔ' => 'هي', + 'ﱕ' => 'يج', + 'ﱖ' => 'يح', + 'ﱗ' => 'يخ', + 'ﱘ' => 'يم', + 'ﱙ' => 'يى', + 'ﱚ' => 'يي', + 'ﱛ' => 'ذٰ', + 'ﱜ' => 'رٰ', + 'ﱝ' => 'ىٰ', + 'ﱞ' => ' ٌّ', + 'ﱟ' => ' ٍّ', + 'ﱠ' => ' َّ', + 'ﱡ' => ' ُّ', + 'ﱢ' => ' ِّ', + 'ﱣ' => ' ّٰ', + 'ﱤ' => 'ئر', + 'ﱥ' => 'ئز', + 'ﱦ' => 'ئم', + 'ﱧ' => 'ئن', + 'ﱨ' => 'ئى', + 'ﱩ' => 'ئي', + 'ﱪ' => 'بر', + 'ﱫ' => 'بز', + 'ﱬ' => 'بم', + 'ﱭ' => 'بن', + 'ﱮ' => 'بى', + 'ﱯ' => 'بي', + 'ﱰ' => 'تر', + 'ﱱ' => 'تز', + 'ﱲ' => 'تم', + 'ﱳ' => 'تن', + 'ﱴ' => 'تى', + 'ﱵ' => 'تي', + 'ﱶ' => 'ثر', + 'ﱷ' => 'ثز', + 'ﱸ' => 'ثم', + 'ﱹ' => 'ثن', + 'ﱺ' => 'ثى', + 'ﱻ' => 'ثي', + 'ﱼ' => 'فى', + 'ﱽ' => 'في', + 'ﱾ' => 'قى', + 'ﱿ' => 'قي', + 'ﲀ' => 'كا', + 'ﲁ' => 'كل', + 'ﲂ' => 'كم', + 'ﲃ' => 'كى', + 'ﲄ' => 'كي', + 'ﲅ' => 'لم', + 'ﲆ' => 'لى', + 'ﲇ' => 'لي', + 'ﲈ' => 'ما', + 'ﲉ' => 'مم', + 'ﲊ' => 'نر', + 'ﲋ' => 'نز', + 'ﲌ' => 'نم', + 'ﲍ' => 'نن', + 'ﲎ' => 'نى', + 'ﲏ' => 'ني', + 'ﲐ' => 'ىٰ', + 'ﲑ' => 'ير', + 'ﲒ' => 'يز', + 'ﲓ' => 'يم', + 'ﲔ' => 'ين', + 'ﲕ' => 'يى', + 'ﲖ' => 'يي', + 'ﲗ' => 'ئج', + 'ﲘ' => 'ئح', + 'ﲙ' => 'ئخ', + 'ﲚ' => 'ئم', + 'ﲛ' => 'ئه', + 'ﲜ' => 'بج', + 'ﲝ' => 'بح', + 'ﲞ' => 'بخ', + 'ﲟ' => 'بم', + 'ﲠ' => 'به', + 'ﲡ' => 'تج', + 'ﲢ' => 'تح', + 'ﲣ' => 'تخ', + 'ﲤ' => 'تم', + 'ﲥ' => 'ته', + 'ﲦ' => 'ثم', + 'ﲧ' => 'جح', + 'ﲨ' => 'جم', + 'ﲩ' => 'حج', + 'ﲪ' => 'حم', + 'ﲫ' => 'خج', + 'ﲬ' => 'خم', + 'ﲭ' => 'سج', + 'ﲮ' => 'سح', + 'ﲯ' => 'سخ', + 'ﲰ' => 'سم', + 'ﲱ' => 'صح', + 'ﲲ' => 'صخ', + 'ﲳ' => 'صم', + 'ﲴ' => 'ضج', + 'ﲵ' => 'ضح', + 'ﲶ' => 'ضخ', + 'ﲷ' => 'ضم', + 'ﲸ' => 'طح', + 'ﲹ' => 'ظم', + 'ﲺ' => 'عج', + 'ﲻ' => 'عم', + 'ﲼ' => 'غج', + 'ﲽ' => 'غم', + 'ﲾ' => 'فج', + 'ﲿ' => 'فح', + 'ﳀ' => 'فخ', + 'ﳁ' => 'فم', + 'ﳂ' => 'قح', + 'ﳃ' => 'قم', + 'ﳄ' => 'كج', + 'ﳅ' => 'كح', + 'ﳆ' => 'كخ', + 'ﳇ' => 'كل', + 'ﳈ' => 'كم', + 'ﳉ' => 'لج', + 'ﳊ' => 'لح', + 'ﳋ' => 'لخ', + 'ﳌ' => 'لم', + 'ﳍ' => 'له', + 'ﳎ' => 'مج', + 'ﳏ' => 'مح', + 'ﳐ' => 'مخ', + 'ﳑ' => 'مم', + 'ﳒ' => 'نج', + 'ﳓ' => 'نح', + 'ﳔ' => 'نخ', + 'ﳕ' => 'نم', + 'ﳖ' => 'نه', + 'ﳗ' => 'هج', + 'ﳘ' => 'هم', + 'ﳙ' => 'هٰ', + 'ﳚ' => 'يج', + 'ﳛ' => 'يح', + 'ﳜ' => 'يخ', + 'ﳝ' => 'يم', + 'ﳞ' => 'يه', + 'ﳟ' => 'ئم', + 'ﳠ' => 'ئه', + 'ﳡ' => 'بم', + 'ﳢ' => 'به', + 'ﳣ' => 'تم', + 'ﳤ' => 'ته', + 'ﳥ' => 'ثم', + 'ﳦ' => 'ثه', + 'ﳧ' => 'سم', + 'ﳨ' => 'سه', + 'ﳩ' => 'شم', + 'ﳪ' => 'شه', + 'ﳫ' => 'كل', + 'ﳬ' => 'كم', + 'ﳭ' => 'لم', + 'ﳮ' => 'نم', + 'ﳯ' => 'نه', + 'ﳰ' => 'يم', + 'ﳱ' => 'يه', + 'ﳲ' => 'ـَّ', + 'ﳳ' => 'ـُّ', + 'ﳴ' => 'ـِّ', + 'ﳵ' => 'طى', + 'ﳶ' => 'طي', + 'ﳷ' => 'عى', + 'ﳸ' => 'عي', + 'ﳹ' => 'غى', + 'ﳺ' => 'غي', + 'ﳻ' => 'سى', + 'ﳼ' => 'سي', + 'ﳽ' => 'شى', + 'ﳾ' => 'شي', + 'ﳿ' => 'حى', + 'ﴀ' => 'حي', + 'ﴁ' => 'جى', + 'ﴂ' => 'جي', + 'ﴃ' => 'خى', + 'ﴄ' => 'خي', + 'ﴅ' => 'صى', + 'ﴆ' => 'صي', + 'ﴇ' => 'ضى', + 'ﴈ' => 'ضي', + 'ﴉ' => 'شج', + 'ﴊ' => 'شح', + 'ﴋ' => 'شخ', + 'ﴌ' => 'شم', + 'ﴍ' => 'شر', + 'ﴎ' => 'سر', + 'ﴏ' => 'صر', + 'ﴐ' => 'ضر', + 'ﴑ' => 'طى', + 'ﴒ' => 'طي', + 'ﴓ' => 'عى', + 'ﴔ' => 'عي', + 'ﴕ' => 'غى', + 'ﴖ' => 'غي', + 'ﴗ' => 'سى', + 'ﴘ' => 'سي', + 'ﴙ' => 'شى', + 'ﴚ' => 'شي', + 'ﴛ' => 'حى', + 'ﴜ' => 'حي', + 'ﴝ' => 'جى', + 'ﴞ' => 'جي', + 'ﴟ' => 'خى', + 'ﴠ' => 'خي', + 'ﴡ' => 'صى', + 'ﴢ' => 'صي', + 'ﴣ' => 'ضى', + 'ﴤ' => 'ضي', + 'ﴥ' => 'شج', + 'ﴦ' => 'شح', + 'ﴧ' => 'شخ', + 'ﴨ' => 'شم', + 'ﴩ' => 'شر', + 'ﴪ' => 'سر', + 'ﴫ' => 'صر', + 'ﴬ' => 'ضر', + 'ﴭ' => 'شج', + 'ﴮ' => 'شح', + 'ﴯ' => 'شخ', + 'ﴰ' => 'شم', + 'ﴱ' => 'سه', + 'ﴲ' => 'شه', + 'ﴳ' => 'طم', + 'ﴴ' => 'سج', + 'ﴵ' => 'سح', + 'ﴶ' => 'سخ', + 'ﴷ' => 'شج', + 'ﴸ' => 'شح', + 'ﴹ' => 'شخ', + 'ﴺ' => 'طم', + 'ﴻ' => 'ظم', + 'ﴼ' => 'اً', + 'ﴽ' => 'اً', + 'ﵐ' => 'تجم', + 'ﵑ' => 'تحج', + 'ﵒ' => 'تحج', + 'ﵓ' => 'تحم', + 'ﵔ' => 'تخم', + 'ﵕ' => 'تمج', + 'ﵖ' => 'تمح', + 'ﵗ' => 'تمخ', + 'ﵘ' => 'جمح', + 'ﵙ' => 'جمح', + 'ﵚ' => 'حمي', + 'ﵛ' => 'حمى', + 'ﵜ' => 'سحج', + 'ﵝ' => 'سجح', + 'ﵞ' => 'سجى', + 'ﵟ' => 'سمح', + 'ﵠ' => 'سمح', + 'ﵡ' => 'سمج', + 'ﵢ' => 'سمم', + 'ﵣ' => 'سمم', + 'ﵤ' => 'صحح', + 'ﵥ' => 'صحح', + 'ﵦ' => 'صمم', + 'ﵧ' => 'شحم', + 'ﵨ' => 'شحم', + 'ﵩ' => 'شجي', + 'ﵪ' => 'شمخ', + 'ﵫ' => 'شمخ', + 'ﵬ' => 'شمم', + 'ﵭ' => 'شمم', + 'ﵮ' => 'ضحى', + 'ﵯ' => 'ضخم', + 'ﵰ' => 'ضخم', + 'ﵱ' => 'طمح', + 'ﵲ' => 'طمح', + 'ﵳ' => 'طمم', + 'ﵴ' => 'طمي', + 'ﵵ' => 'عجم', + 'ﵶ' => 'عمم', + 'ﵷ' => 'عمم', + 'ﵸ' => 'عمى', + 'ﵹ' => 'غمم', + 'ﵺ' => 'غمي', + 'ﵻ' => 'غمى', + 'ﵼ' => 'فخم', + 'ﵽ' => 'فخم', + 'ﵾ' => 'قمح', + 'ﵿ' => 'قمم', + 'ﶀ' => 'لحم', + 'ﶁ' => 'لحي', + 'ﶂ' => 'لحى', + 'ﶃ' => 'لجج', + 'ﶄ' => 'لجج', + 'ﶅ' => 'لخم', + 'ﶆ' => 'لخم', + 'ﶇ' => 'لمح', + 'ﶈ' => 'لمح', + 'ﶉ' => 'محج', + 'ﶊ' => 'محم', + 'ﶋ' => 'محي', + 'ﶌ' => 'مجح', + 'ﶍ' => 'مجم', + 'ﶎ' => 'مخج', + 'ﶏ' => 'مخم', + 'ﶒ' => 'مجخ', + 'ﶓ' => 'همج', + 'ﶔ' => 'همم', + 'ﶕ' => 'نحم', + 'ﶖ' => 'نحى', + 'ﶗ' => 'نجم', + 'ﶘ' => 'نجم', + 'ﶙ' => 'نجى', + 'ﶚ' => 'نمي', + 'ﶛ' => 'نمى', + 'ﶜ' => 'يمم', + 'ﶝ' => 'يمم', + 'ﶞ' => 'بخي', + 'ﶟ' => 'تجي', + 'ﶠ' => 'تجى', + 'ﶡ' => 'تخي', + 'ﶢ' => 'تخى', + 'ﶣ' => 'تمي', + 'ﶤ' => 'تمى', + 'ﶥ' => 'جمي', + 'ﶦ' => 'جحى', + 'ﶧ' => 'جمى', + 'ﶨ' => 'سخى', + 'ﶩ' => 'صحي', + 'ﶪ' => 'شحي', + 'ﶫ' => 'ضحي', + 'ﶬ' => 'لجي', + 'ﶭ' => 'لمي', + 'ﶮ' => 'يحي', + 'ﶯ' => 'يجي', + 'ﶰ' => 'يمي', + 'ﶱ' => 'ممي', + 'ﶲ' => 'قمي', + 'ﶳ' => 'نحي', + 'ﶴ' => 'قمح', + 'ﶵ' => 'لحم', + 'ﶶ' => 'عمي', + 'ﶷ' => 'كمي', + 'ﶸ' => 'نجح', + 'ﶹ' => 'مخي', + 'ﶺ' => 'لجم', + 'ﶻ' => 'كمم', + 'ﶼ' => 'لجم', + 'ﶽ' => 'نجح', + 'ﶾ' => 'جحي', + 'ﶿ' => 'حجي', + 'ﷀ' => 'مجي', + 'ﷁ' => 'فمي', + 'ﷂ' => 'بحي', + 'ﷃ' => 'كمم', + 'ﷄ' => 'عجم', + 'ﷅ' => 'صمم', + 'ﷆ' => 'سخي', + 'ﷇ' => 'نجي', + 'ﷰ' => 'صلے', + 'ﷱ' => 'قلے', + 'ﷲ' => 'الله', + 'ﷳ' => 'اكبر', + 'ﷴ' => 'محمد', + 'ﷵ' => 'صلعم', + 'ﷶ' => 'رسول', + 'ﷷ' => 'عليه', + 'ﷸ' => 'وسلم', + 'ﷹ' => 'صلى', + 'ﷺ' => 'صلى الله عليه وسلم', + 'ﷻ' => 'جل جلاله', + '﷼' => 'ریال', + '︐' => ',', + '︑' => '、', + '︒' => '。', + '︓' => ':', + '︔' => ';', + '︕' => '!', + '︖' => '?', + '︗' => '〖', + '︘' => '〗', + '︙' => '...', + '︰' => '..', + '︱' => '—', + '︲' => '–', + '︳' => '_', + '︴' => '_', + '︵' => '(', + '︶' => ')', + '︷' => '{', + '︸' => '}', + '︹' => '〔', + '︺' => '〕', + '︻' => '【', + '︼' => '】', + '︽' => '《', + '︾' => '》', + '︿' => '〈', + '﹀' => '〉', + '﹁' => '「', + '﹂' => '」', + '﹃' => '『', + '﹄' => '』', + '﹇' => '[', + '﹈' => ']', + '﹉' => ' ̅', + '﹊' => ' ̅', + '﹋' => ' ̅', + '﹌' => ' ̅', + '﹍' => '_', + '﹎' => '_', + '﹏' => '_', + '﹐' => ',', + '﹑' => '、', + '﹒' => '.', + '﹔' => ';', + '﹕' => ':', + '﹖' => '?', + '﹗' => '!', + '﹘' => '—', + '﹙' => '(', + '﹚' => ')', + '﹛' => '{', + '﹜' => '}', + '﹝' => '〔', + '﹞' => '〕', + '﹟' => '#', + '﹠' => '&', + '﹡' => '*', + '﹢' => '+', + '﹣' => '-', + '﹤' => '<', + '﹥' => '>', + '﹦' => '=', + '﹨' => '\\', + '﹩' => '$', + '﹪' => '%', + '﹫' => '@', + 'ﹰ' => ' ً', + 'ﹱ' => 'ـً', + 'ﹲ' => ' ٌ', + 'ﹴ' => ' ٍ', + 'ﹶ' => ' َ', + 'ﹷ' => 'ـَ', + 'ﹸ' => ' ُ', + 'ﹹ' => 'ـُ', + 'ﹺ' => ' ِ', + 'ﹻ' => 'ـِ', + 'ﹼ' => ' ّ', + 'ﹽ' => 'ـّ', + 'ﹾ' => ' ْ', + 'ﹿ' => 'ـْ', + 'ﺀ' => 'ء', + 'ﺁ' => 'آ', + 'ﺂ' => 'آ', + 'ﺃ' => 'أ', + 'ﺄ' => 'أ', + 'ﺅ' => 'ؤ', + 'ﺆ' => 'ؤ', + 'ﺇ' => 'إ', + 'ﺈ' => 'إ', + 'ﺉ' => 'ئ', + 'ﺊ' => 'ئ', + 'ﺋ' => 'ئ', + 'ﺌ' => 'ئ', + 'ﺍ' => 'ا', + 'ﺎ' => 'ا', + 'ﺏ' => 'ب', + 'ﺐ' => 'ب', + 'ﺑ' => 'ب', + 'ﺒ' => 'ب', + 'ﺓ' => 'ة', + 'ﺔ' => 'ة', + 'ﺕ' => 'ت', + 'ﺖ' => 'ت', + 'ﺗ' => 'ت', + 'ﺘ' => 'ت', + 'ﺙ' => 'ث', + 'ﺚ' => 'ث', + 'ﺛ' => 'ث', + 'ﺜ' => 'ث', + 'ﺝ' => 'ج', + 'ﺞ' => 'ج', + 'ﺟ' => 'ج', + 'ﺠ' => 'ج', + 'ﺡ' => 'ح', + 'ﺢ' => 'ح', + 'ﺣ' => 'ح', + 'ﺤ' => 'ح', + 'ﺥ' => 'خ', + 'ﺦ' => 'خ', + 'ﺧ' => 'خ', + 'ﺨ' => 'خ', + 'ﺩ' => 'د', + 'ﺪ' => 'د', + 'ﺫ' => 'ذ', + 'ﺬ' => 'ذ', + 'ﺭ' => 'ر', + 'ﺮ' => 'ر', + 'ﺯ' => 'ز', + 'ﺰ' => 'ز', + 'ﺱ' => 'س', + 'ﺲ' => 'س', + 'ﺳ' => 'س', + 'ﺴ' => 'س', + 'ﺵ' => 'ش', + 'ﺶ' => 'ش', + 'ﺷ' => 'ش', + 'ﺸ' => 'ش', + 'ﺹ' => 'ص', + 'ﺺ' => 'ص', + 'ﺻ' => 'ص', + 'ﺼ' => 'ص', + 'ﺽ' => 'ض', + 'ﺾ' => 'ض', + 'ﺿ' => 'ض', + 'ﻀ' => 'ض', + 'ﻁ' => 'ط', + 'ﻂ' => 'ط', + 'ﻃ' => 'ط', + 'ﻄ' => 'ط', + 'ﻅ' => 'ظ', + 'ﻆ' => 'ظ', + 'ﻇ' => 'ظ', + 'ﻈ' => 'ظ', + 'ﻉ' => 'ع', + 'ﻊ' => 'ع', + 'ﻋ' => 'ع', + 'ﻌ' => 'ع', + 'ﻍ' => 'غ', + 'ﻎ' => 'غ', + 'ﻏ' => 'غ', + 'ﻐ' => 'غ', + 'ﻑ' => 'ف', + 'ﻒ' => 'ف', + 'ﻓ' => 'ف', + 'ﻔ' => 'ف', + 'ﻕ' => 'ق', + 'ﻖ' => 'ق', + 'ﻗ' => 'ق', + 'ﻘ' => 'ق', + 'ﻙ' => 'ك', + 'ﻚ' => 'ك', + 'ﻛ' => 'ك', + 'ﻜ' => 'ك', + 'ﻝ' => 'ل', + 'ﻞ' => 'ل', + 'ﻟ' => 'ل', + 'ﻠ' => 'ل', + 'ﻡ' => 'م', + 'ﻢ' => 'م', + 'ﻣ' => 'م', + 'ﻤ' => 'م', + 'ﻥ' => 'ن', + 'ﻦ' => 'ن', + 'ﻧ' => 'ن', + 'ﻨ' => 'ن', + 'ﻩ' => 'ه', + 'ﻪ' => 'ه', + 'ﻫ' => 'ه', + 'ﻬ' => 'ه', + 'ﻭ' => 'و', + 'ﻮ' => 'و', + 'ﻯ' => 'ى', + 'ﻰ' => 'ى', + 'ﻱ' => 'ي', + 'ﻲ' => 'ي', + 'ﻳ' => 'ي', + 'ﻴ' => 'ي', + 'ﻵ' => 'لآ', + 'ﻶ' => 'لآ', + 'ﻷ' => 'لأ', + 'ﻸ' => 'لأ', + 'ﻹ' => 'لإ', + 'ﻺ' => 'لإ', + 'ﻻ' => 'لا', + 'ﻼ' => 'لا', + '!' => '!', + '"' => '"', + '#' => '#', + '$' => '$', + '%' => '%', + '&' => '&', + ''' => '\'', + '(' => '(', + ')' => ')', + '*' => '*', + '+' => '+', + ',' => ',', + '-' => '-', + '.' => '.', + '/' => '/', + '0' => '0', + '1' => '1', + '2' => '2', + '3' => '3', + '4' => '4', + '5' => '5', + '6' => '6', + '7' => '7', + '8' => '8', + '9' => '9', + ':' => ':', + ';' => ';', + '<' => '<', + '=' => '=', + '>' => '>', + '?' => '?', + '@' => '@', + 'A' => 'A', + 'B' => 'B', + 'C' => 'C', + 'D' => 'D', + 'E' => 'E', + 'F' => 'F', + 'G' => 'G', + 'H' => 'H', + 'I' => 'I', + 'J' => 'J', + 'K' => 'K', + 'L' => 'L', + 'M' => 'M', + 'N' => 'N', + 'O' => 'O', + 'P' => 'P', + 'Q' => 'Q', + 'R' => 'R', + 'S' => 'S', + 'T' => 'T', + 'U' => 'U', + 'V' => 'V', + 'W' => 'W', + 'X' => 'X', + 'Y' => 'Y', + 'Z' => 'Z', + '[' => '[', + '\' => '\\', + ']' => ']', + '^' => '^', + '_' => '_', + '`' => '`', + 'a' => 'a', + 'b' => 'b', + 'c' => 'c', + 'd' => 'd', + 'e' => 'e', + 'f' => 'f', + 'g' => 'g', + 'h' => 'h', + 'i' => 'i', + 'j' => 'j', + 'k' => 'k', + 'l' => 'l', + 'm' => 'm', + 'n' => 'n', + 'o' => 'o', + 'p' => 'p', + 'q' => 'q', + 'r' => 'r', + 's' => 's', + 't' => 't', + 'u' => 'u', + 'v' => 'v', + 'w' => 'w', + 'x' => 'x', + 'y' => 'y', + 'z' => 'z', + '{' => '{', + '|' => '|', + '}' => '}', + '~' => '~', + '⦅' => '⦅', + '⦆' => '⦆', + '。' => '。', + '「' => '「', + '」' => '」', + '、' => '、', + '・' => '・', + 'ヲ' => 'ヲ', + 'ァ' => 'ァ', + 'ィ' => 'ィ', + 'ゥ' => 'ゥ', + 'ェ' => 'ェ', + 'ォ' => 'ォ', + 'ャ' => 'ャ', + 'ュ' => 'ュ', + 'ョ' => 'ョ', + 'ッ' => 'ッ', + 'ー' => 'ー', + 'ア' => 'ア', + 'イ' => 'イ', + 'ウ' => 'ウ', + 'エ' => 'エ', + 'オ' => 'オ', + 'カ' => 'カ', + 'キ' => 'キ', + 'ク' => 'ク', + 'ケ' => 'ケ', + 'コ' => 'コ', + 'サ' => 'サ', + 'シ' => 'シ', + 'ス' => 'ス', + 'セ' => 'セ', + 'ソ' => 'ソ', + 'タ' => 'タ', + 'チ' => 'チ', + 'ツ' => 'ツ', + 'テ' => 'テ', + 'ト' => 'ト', + 'ナ' => 'ナ', + 'ニ' => 'ニ', + 'ヌ' => 'ヌ', + 'ネ' => 'ネ', + 'ノ' => 'ノ', + 'ハ' => 'ハ', + 'ヒ' => 'ヒ', + 'フ' => 'フ', + 'ヘ' => 'ヘ', + 'ホ' => 'ホ', + 'マ' => 'マ', + 'ミ' => 'ミ', + 'ム' => 'ム', + 'メ' => 'メ', + 'モ' => 'モ', + 'ヤ' => 'ヤ', + 'ユ' => 'ユ', + 'ヨ' => 'ヨ', + 'ラ' => 'ラ', + 'リ' => 'リ', + 'ル' => 'ル', + 'レ' => 'レ', + 'ロ' => 'ロ', + 'ワ' => 'ワ', + 'ン' => 'ン', + '゙' => '゙', + '゚' => '゚', + 'ᅠ' => 'ᅠ', + 'ᄀ' => 'ᄀ', + 'ᄁ' => 'ᄁ', + 'ᆪ' => 'ᆪ', + 'ᄂ' => 'ᄂ', + 'ᆬ' => 'ᆬ', + 'ᆭ' => 'ᆭ', + 'ᄃ' => 'ᄃ', + 'ᄄ' => 'ᄄ', + 'ᄅ' => 'ᄅ', + 'ᆰ' => 'ᆰ', + 'ᆱ' => 'ᆱ', + 'ᆲ' => 'ᆲ', + 'ᆳ' => 'ᆳ', + 'ᆴ' => 'ᆴ', + 'ᆵ' => 'ᆵ', + 'ᄚ' => 'ᄚ', + 'ᄆ' => 'ᄆ', + 'ᄇ' => 'ᄇ', + 'ᄈ' => 'ᄈ', + 'ᄡ' => 'ᄡ', + 'ᄉ' => 'ᄉ', + 'ᄊ' => 'ᄊ', + 'ᄋ' => 'ᄋ', + 'ᄌ' => 'ᄌ', + 'ᄍ' => 'ᄍ', + 'ᄎ' => 'ᄎ', + 'ᄏ' => 'ᄏ', + 'ᄐ' => 'ᄐ', + 'ᄑ' => 'ᄑ', + 'ᄒ' => 'ᄒ', + 'ᅡ' => 'ᅡ', + 'ᅢ' => 'ᅢ', + 'ᅣ' => 'ᅣ', + 'ᅤ' => 'ᅤ', + 'ᅥ' => 'ᅥ', + 'ᅦ' => 'ᅦ', + 'ᅧ' => 'ᅧ', + 'ᅨ' => 'ᅨ', + 'ᅩ' => 'ᅩ', + 'ᅪ' => 'ᅪ', + 'ᅫ' => 'ᅫ', + 'ᅬ' => 'ᅬ', + 'ᅭ' => 'ᅭ', + 'ᅮ' => 'ᅮ', + 'ᅯ' => 'ᅯ', + 'ᅰ' => 'ᅰ', + 'ᅱ' => 'ᅱ', + 'ᅲ' => 'ᅲ', + 'ᅳ' => 'ᅳ', + 'ᅴ' => 'ᅴ', + 'ᅵ' => 'ᅵ', + '¢' => '¢', + '£' => '£', + '¬' => '¬', + ' ̄' => ' ̄', + '¦' => '¦', + '¥' => '¥', + '₩' => '₩', + '│' => '│', + '←' => '←', + '↑' => '↑', + '→' => '→', + '↓' => '↓', + '■' => '■', + '○' => '○', + '𝐀' => 'A', + '𝐁' => 'B', + '𝐂' => 'C', + '𝐃' => 'D', + '𝐄' => 'E', + '𝐅' => 'F', + '𝐆' => 'G', + '𝐇' => 'H', + '𝐈' => 'I', + '𝐉' => 'J', + '𝐊' => 'K', + '𝐋' => 'L', + '𝐌' => 'M', + '𝐍' => 'N', + '𝐎' => 'O', + '𝐏' => 'P', + '𝐐' => 'Q', + '𝐑' => 'R', + '𝐒' => 'S', + '𝐓' => 'T', + '𝐔' => 'U', + '𝐕' => 'V', + '𝐖' => 'W', + '𝐗' => 'X', + '𝐘' => 'Y', + '𝐙' => 'Z', + '𝐚' => 'a', + '𝐛' => 'b', + '𝐜' => 'c', + '𝐝' => 'd', + '𝐞' => 'e', + '𝐟' => 'f', + '𝐠' => 'g', + '𝐡' => 'h', + '𝐢' => 'i', + '𝐣' => 'j', + '𝐤' => 'k', + '𝐥' => 'l', + '𝐦' => 'm', + '𝐧' => 'n', + '𝐨' => 'o', + '𝐩' => 'p', + '𝐪' => 'q', + '𝐫' => 'r', + '𝐬' => 's', + '𝐭' => 't', + '𝐮' => 'u', + '𝐯' => 'v', + '𝐰' => 'w', + '𝐱' => 'x', + '𝐲' => 'y', + '𝐳' => 'z', + '𝐴' => 'A', + '𝐵' => 'B', + '𝐶' => 'C', + '𝐷' => 'D', + '𝐸' => 'E', + '𝐹' => 'F', + '𝐺' => 'G', + '𝐻' => 'H', + '𝐼' => 'I', + '𝐽' => 'J', + '𝐾' => 'K', + '𝐿' => 'L', + '𝑀' => 'M', + '𝑁' => 'N', + '𝑂' => 'O', + '𝑃' => 'P', + '𝑄' => 'Q', + '𝑅' => 'R', + '𝑆' => 'S', + '𝑇' => 'T', + '𝑈' => 'U', + '𝑉' => 'V', + '𝑊' => 'W', + '𝑋' => 'X', + '𝑌' => 'Y', + '𝑍' => 'Z', + '𝑎' => 'a', + '𝑏' => 'b', + '𝑐' => 'c', + '𝑑' => 'd', + '𝑒' => 'e', + '𝑓' => 'f', + '𝑔' => 'g', + '𝑖' => 'i', + '𝑗' => 'j', + '𝑘' => 'k', + '𝑙' => 'l', + '𝑚' => 'm', + '𝑛' => 'n', + '𝑜' => 'o', + '𝑝' => 'p', + '𝑞' => 'q', + '𝑟' => 'r', + '𝑠' => 's', + '𝑡' => 't', + '𝑢' => 'u', + '𝑣' => 'v', + '𝑤' => 'w', + '𝑥' => 'x', + '𝑦' => 'y', + '𝑧' => 'z', + '𝑨' => 'A', + '𝑩' => 'B', + '𝑪' => 'C', + '𝑫' => 'D', + '𝑬' => 'E', + '𝑭' => 'F', + '𝑮' => 'G', + '𝑯' => 'H', + '𝑰' => 'I', + '𝑱' => 'J', + '𝑲' => 'K', + '𝑳' => 'L', + '𝑴' => 'M', + '𝑵' => 'N', + '𝑶' => 'O', + '𝑷' => 'P', + '𝑸' => 'Q', + '𝑹' => 'R', + '𝑺' => 'S', + '𝑻' => 'T', + '𝑼' => 'U', + '𝑽' => 'V', + '𝑾' => 'W', + '𝑿' => 'X', + '𝒀' => 'Y', + '𝒁' => 'Z', + '𝒂' => 'a', + '𝒃' => 'b', + '𝒄' => 'c', + '𝒅' => 'd', + '𝒆' => 'e', + '𝒇' => 'f', + '𝒈' => 'g', + '𝒉' => 'h', + '𝒊' => 'i', + '𝒋' => 'j', + '𝒌' => 'k', + '𝒍' => 'l', + '𝒎' => 'm', + '𝒏' => 'n', + '𝒐' => 'o', + '𝒑' => 'p', + '𝒒' => 'q', + '𝒓' => 'r', + '𝒔' => 's', + '𝒕' => 't', + '𝒖' => 'u', + '𝒗' => 'v', + '𝒘' => 'w', + '𝒙' => 'x', + '𝒚' => 'y', + '𝒛' => 'z', + '𝒜' => 'A', + '𝒞' => 'C', + '𝒟' => 'D', + '𝒢' => 'G', + '𝒥' => 'J', + '𝒦' => 'K', + '𝒩' => 'N', + '𝒪' => 'O', + '𝒫' => 'P', + '𝒬' => 'Q', + '𝒮' => 'S', + '𝒯' => 'T', + '𝒰' => 'U', + '𝒱' => 'V', + '𝒲' => 'W', + '𝒳' => 'X', + '𝒴' => 'Y', + '𝒵' => 'Z', + '𝒶' => 'a', + '𝒷' => 'b', + '𝒸' => 'c', + '𝒹' => 'd', + '𝒻' => 'f', + '𝒽' => 'h', + '𝒾' => 'i', + '𝒿' => 'j', + '𝓀' => 'k', + '𝓁' => 'l', + '𝓂' => 'm', + '𝓃' => 'n', + '𝓅' => 'p', + '𝓆' => 'q', + '𝓇' => 'r', + '𝓈' => 's', + '𝓉' => 't', + '𝓊' => 'u', + '𝓋' => 'v', + '𝓌' => 'w', + '𝓍' => 'x', + '𝓎' => 'y', + '𝓏' => 'z', + '𝓐' => 'A', + '𝓑' => 'B', + '𝓒' => 'C', + '𝓓' => 'D', + '𝓔' => 'E', + '𝓕' => 'F', + '𝓖' => 'G', + '𝓗' => 'H', + '𝓘' => 'I', + '𝓙' => 'J', + '𝓚' => 'K', + '𝓛' => 'L', + '𝓜' => 'M', + '𝓝' => 'N', + '𝓞' => 'O', + '𝓟' => 'P', + '𝓠' => 'Q', + '𝓡' => 'R', + '𝓢' => 'S', + '𝓣' => 'T', + '𝓤' => 'U', + '𝓥' => 'V', + '𝓦' => 'W', + '𝓧' => 'X', + '𝓨' => 'Y', + '𝓩' => 'Z', + '𝓪' => 'a', + '𝓫' => 'b', + '𝓬' => 'c', + '𝓭' => 'd', + '𝓮' => 'e', + '𝓯' => 'f', + '𝓰' => 'g', + '𝓱' => 'h', + '𝓲' => 'i', + '𝓳' => 'j', + '𝓴' => 'k', + '𝓵' => 'l', + '𝓶' => 'm', + '𝓷' => 'n', + '𝓸' => 'o', + '𝓹' => 'p', + '𝓺' => 'q', + '𝓻' => 'r', + '𝓼' => 's', + '𝓽' => 't', + '𝓾' => 'u', + '𝓿' => 'v', + '𝔀' => 'w', + '𝔁' => 'x', + '𝔂' => 'y', + '𝔃' => 'z', + '𝔄' => 'A', + '𝔅' => 'B', + '𝔇' => 'D', + '𝔈' => 'E', + '𝔉' => 'F', + '𝔊' => 'G', + '𝔍' => 'J', + '𝔎' => 'K', + '𝔏' => 'L', + '𝔐' => 'M', + '𝔑' => 'N', + '𝔒' => 'O', + '𝔓' => 'P', + '𝔔' => 'Q', + '𝔖' => 'S', + '𝔗' => 'T', + '𝔘' => 'U', + '𝔙' => 'V', + '𝔚' => 'W', + '𝔛' => 'X', + '𝔜' => 'Y', + '𝔞' => 'a', + '𝔟' => 'b', + '𝔠' => 'c', + '𝔡' => 'd', + '𝔢' => 'e', + '𝔣' => 'f', + '𝔤' => 'g', + '𝔥' => 'h', + '𝔦' => 'i', + '𝔧' => 'j', + '𝔨' => 'k', + '𝔩' => 'l', + '𝔪' => 'm', + '𝔫' => 'n', + '𝔬' => 'o', + '𝔭' => 'p', + '𝔮' => 'q', + '𝔯' => 'r', + '𝔰' => 's', + '𝔱' => 't', + '𝔲' => 'u', + '𝔳' => 'v', + '𝔴' => 'w', + '𝔵' => 'x', + '𝔶' => 'y', + '𝔷' => 'z', + '𝔸' => 'A', + '𝔹' => 'B', + '𝔻' => 'D', + '𝔼' => 'E', + '𝔽' => 'F', + '𝔾' => 'G', + '𝕀' => 'I', + '𝕁' => 'J', + '𝕂' => 'K', + '𝕃' => 'L', + '𝕄' => 'M', + '𝕆' => 'O', + '𝕊' => 'S', + '𝕋' => 'T', + '𝕌' => 'U', + '𝕍' => 'V', + '𝕎' => 'W', + '𝕏' => 'X', + '𝕐' => 'Y', + '𝕒' => 'a', + '𝕓' => 'b', + '𝕔' => 'c', + '𝕕' => 'd', + '𝕖' => 'e', + '𝕗' => 'f', + '𝕘' => 'g', + '𝕙' => 'h', + '𝕚' => 'i', + '𝕛' => 'j', + '𝕜' => 'k', + '𝕝' => 'l', + '𝕞' => 'm', + '𝕟' => 'n', + '𝕠' => 'o', + '𝕡' => 'p', + '𝕢' => 'q', + '𝕣' => 'r', + '𝕤' => 's', + '𝕥' => 't', + '𝕦' => 'u', + '𝕧' => 'v', + '𝕨' => 'w', + '𝕩' => 'x', + '𝕪' => 'y', + '𝕫' => 'z', + '𝕬' => 'A', + '𝕭' => 'B', + '𝕮' => 'C', + '𝕯' => 'D', + '𝕰' => 'E', + '𝕱' => 'F', + '𝕲' => 'G', + '𝕳' => 'H', + '𝕴' => 'I', + '𝕵' => 'J', + '𝕶' => 'K', + '𝕷' => 'L', + '𝕸' => 'M', + '𝕹' => 'N', + '𝕺' => 'O', + '𝕻' => 'P', + '𝕼' => 'Q', + '𝕽' => 'R', + '𝕾' => 'S', + '𝕿' => 'T', + '𝖀' => 'U', + '𝖁' => 'V', + '𝖂' => 'W', + '𝖃' => 'X', + '𝖄' => 'Y', + '𝖅' => 'Z', + '𝖆' => 'a', + '𝖇' => 'b', + '𝖈' => 'c', + '𝖉' => 'd', + '𝖊' => 'e', + '𝖋' => 'f', + '𝖌' => 'g', + '𝖍' => 'h', + '𝖎' => 'i', + '𝖏' => 'j', + '𝖐' => 'k', + '𝖑' => 'l', + '𝖒' => 'm', + '𝖓' => 'n', + '𝖔' => 'o', + '𝖕' => 'p', + '𝖖' => 'q', + '𝖗' => 'r', + '𝖘' => 's', + '𝖙' => 't', + '𝖚' => 'u', + '𝖛' => 'v', + '𝖜' => 'w', + '𝖝' => 'x', + '𝖞' => 'y', + '𝖟' => 'z', + '𝖠' => 'A', + '𝖡' => 'B', + '𝖢' => 'C', + '𝖣' => 'D', + '𝖤' => 'E', + '𝖥' => 'F', + '𝖦' => 'G', + '𝖧' => 'H', + '𝖨' => 'I', + '𝖩' => 'J', + '𝖪' => 'K', + '𝖫' => 'L', + '𝖬' => 'M', + '𝖭' => 'N', + '𝖮' => 'O', + '𝖯' => 'P', + '𝖰' => 'Q', + '𝖱' => 'R', + '𝖲' => 'S', + '𝖳' => 'T', + '𝖴' => 'U', + '𝖵' => 'V', + '𝖶' => 'W', + '𝖷' => 'X', + '𝖸' => 'Y', + '𝖹' => 'Z', + '𝖺' => 'a', + '𝖻' => 'b', + '𝖼' => 'c', + '𝖽' => 'd', + '𝖾' => 'e', + '𝖿' => 'f', + '𝗀' => 'g', + '𝗁' => 'h', + '𝗂' => 'i', + '𝗃' => 'j', + '𝗄' => 'k', + '𝗅' => 'l', + '𝗆' => 'm', + '𝗇' => 'n', + '𝗈' => 'o', + '𝗉' => 'p', + '𝗊' => 'q', + '𝗋' => 'r', + '𝗌' => 's', + '𝗍' => 't', + '𝗎' => 'u', + '𝗏' => 'v', + '𝗐' => 'w', + '𝗑' => 'x', + '𝗒' => 'y', + '𝗓' => 'z', + '𝗔' => 'A', + '𝗕' => 'B', + '𝗖' => 'C', + '𝗗' => 'D', + '𝗘' => 'E', + '𝗙' => 'F', + '𝗚' => 'G', + '𝗛' => 'H', + '𝗜' => 'I', + '𝗝' => 'J', + '𝗞' => 'K', + '𝗟' => 'L', + '𝗠' => 'M', + '𝗡' => 'N', + '𝗢' => 'O', + '𝗣' => 'P', + '𝗤' => 'Q', + '𝗥' => 'R', + '𝗦' => 'S', + '𝗧' => 'T', + '𝗨' => 'U', + '𝗩' => 'V', + '𝗪' => 'W', + '𝗫' => 'X', + '𝗬' => 'Y', + '𝗭' => 'Z', + '𝗮' => 'a', + '𝗯' => 'b', + '𝗰' => 'c', + '𝗱' => 'd', + '𝗲' => 'e', + '𝗳' => 'f', + '𝗴' => 'g', + '𝗵' => 'h', + '𝗶' => 'i', + '𝗷' => 'j', + '𝗸' => 'k', + '𝗹' => 'l', + '𝗺' => 'm', + '𝗻' => 'n', + '𝗼' => 'o', + '𝗽' => 'p', + '𝗾' => 'q', + '𝗿' => 'r', + '𝘀' => 's', + '𝘁' => 't', + '𝘂' => 'u', + '𝘃' => 'v', + '𝘄' => 'w', + '𝘅' => 'x', + '𝘆' => 'y', + '𝘇' => 'z', + '𝘈' => 'A', + '𝘉' => 'B', + '𝘊' => 'C', + '𝘋' => 'D', + '𝘌' => 'E', + '𝘍' => 'F', + '𝘎' => 'G', + '𝘏' => 'H', + '𝘐' => 'I', + '𝘑' => 'J', + '𝘒' => 'K', + '𝘓' => 'L', + '𝘔' => 'M', + '𝘕' => 'N', + '𝘖' => 'O', + '𝘗' => 'P', + '𝘘' => 'Q', + '𝘙' => 'R', + '𝘚' => 'S', + '𝘛' => 'T', + '𝘜' => 'U', + '𝘝' => 'V', + '𝘞' => 'W', + '𝘟' => 'X', + '𝘠' => 'Y', + '𝘡' => 'Z', + '𝘢' => 'a', + '𝘣' => 'b', + '𝘤' => 'c', + '𝘥' => 'd', + '𝘦' => 'e', + '𝘧' => 'f', + '𝘨' => 'g', + '𝘩' => 'h', + '𝘪' => 'i', + '𝘫' => 'j', + '𝘬' => 'k', + '𝘭' => 'l', + '𝘮' => 'm', + '𝘯' => 'n', + '𝘰' => 'o', + '𝘱' => 'p', + '𝘲' => 'q', + '𝘳' => 'r', + '𝘴' => 's', + '𝘵' => 't', + '𝘶' => 'u', + '𝘷' => 'v', + '𝘸' => 'w', + '𝘹' => 'x', + '𝘺' => 'y', + '𝘻' => 'z', + '𝘼' => 'A', + '𝘽' => 'B', + '𝘾' => 'C', + '𝘿' => 'D', + '𝙀' => 'E', + '𝙁' => 'F', + '𝙂' => 'G', + '𝙃' => 'H', + '𝙄' => 'I', + '𝙅' => 'J', + '𝙆' => 'K', + '𝙇' => 'L', + '𝙈' => 'M', + '𝙉' => 'N', + '𝙊' => 'O', + '𝙋' => 'P', + '𝙌' => 'Q', + '𝙍' => 'R', + '𝙎' => 'S', + '𝙏' => 'T', + '𝙐' => 'U', + '𝙑' => 'V', + '𝙒' => 'W', + '𝙓' => 'X', + '𝙔' => 'Y', + '𝙕' => 'Z', + '𝙖' => 'a', + '𝙗' => 'b', + '𝙘' => 'c', + '𝙙' => 'd', + '𝙚' => 'e', + '𝙛' => 'f', + '𝙜' => 'g', + '𝙝' => 'h', + '𝙞' => 'i', + '𝙟' => 'j', + '𝙠' => 'k', + '𝙡' => 'l', + '𝙢' => 'm', + '𝙣' => 'n', + '𝙤' => 'o', + '𝙥' => 'p', + '𝙦' => 'q', + '𝙧' => 'r', + '𝙨' => 's', + '𝙩' => 't', + '𝙪' => 'u', + '𝙫' => 'v', + '𝙬' => 'w', + '𝙭' => 'x', + '𝙮' => 'y', + '𝙯' => 'z', + '𝙰' => 'A', + '𝙱' => 'B', + '𝙲' => 'C', + '𝙳' => 'D', + '𝙴' => 'E', + '𝙵' => 'F', + '𝙶' => 'G', + '𝙷' => 'H', + '𝙸' => 'I', + '𝙹' => 'J', + '𝙺' => 'K', + '𝙻' => 'L', + '𝙼' => 'M', + '𝙽' => 'N', + '𝙾' => 'O', + '𝙿' => 'P', + '𝚀' => 'Q', + '𝚁' => 'R', + '𝚂' => 'S', + '𝚃' => 'T', + '𝚄' => 'U', + '𝚅' => 'V', + '𝚆' => 'W', + '𝚇' => 'X', + '𝚈' => 'Y', + '𝚉' => 'Z', + '𝚊' => 'a', + '𝚋' => 'b', + '𝚌' => 'c', + '𝚍' => 'd', + '𝚎' => 'e', + '𝚏' => 'f', + '𝚐' => 'g', + '𝚑' => 'h', + '𝚒' => 'i', + '𝚓' => 'j', + '𝚔' => 'k', + '𝚕' => 'l', + '𝚖' => 'm', + '𝚗' => 'n', + '𝚘' => 'o', + '𝚙' => 'p', + '𝚚' => 'q', + '𝚛' => 'r', + '𝚜' => 's', + '𝚝' => 't', + '𝚞' => 'u', + '𝚟' => 'v', + '𝚠' => 'w', + '𝚡' => 'x', + '𝚢' => 'y', + '𝚣' => 'z', + '𝚤' => 'ı', + '𝚥' => 'ȷ', + '𝚨' => 'Α', + '𝚩' => 'Β', + '𝚪' => 'Γ', + '𝚫' => 'Δ', + '𝚬' => 'Ε', + '𝚭' => 'Ζ', + '𝚮' => 'Η', + '𝚯' => 'Θ', + '𝚰' => 'Ι', + '𝚱' => 'Κ', + '𝚲' => 'Λ', + '𝚳' => 'Μ', + '𝚴' => 'Ν', + '𝚵' => 'Ξ', + '𝚶' => 'Ο', + '𝚷' => 'Π', + '𝚸' => 'Ρ', + '𝚹' => 'Θ', + '𝚺' => 'Σ', + '𝚻' => 'Τ', + '𝚼' => 'Υ', + '𝚽' => 'Φ', + '𝚾' => 'Χ', + '𝚿' => 'Ψ', + '𝛀' => 'Ω', + '𝛁' => '∇', + '𝛂' => 'α', + '𝛃' => 'β', + '𝛄' => 'γ', + '𝛅' => 'δ', + '𝛆' => 'ε', + '𝛇' => 'ζ', + '𝛈' => 'η', + '𝛉' => 'θ', + '𝛊' => 'ι', + '𝛋' => 'κ', + '𝛌' => 'λ', + '𝛍' => 'μ', + '𝛎' => 'ν', + '𝛏' => 'ξ', + '𝛐' => 'ο', + '𝛑' => 'π', + '𝛒' => 'ρ', + '𝛓' => 'ς', + '𝛔' => 'σ', + '𝛕' => 'τ', + '𝛖' => 'υ', + '𝛗' => 'φ', + '𝛘' => 'χ', + '𝛙' => 'ψ', + '𝛚' => 'ω', + '𝛛' => '∂', + '𝛜' => 'ε', + '𝛝' => 'θ', + '𝛞' => 'κ', + '𝛟' => 'φ', + '𝛠' => 'ρ', + '𝛡' => 'π', + '𝛢' => 'Α', + '𝛣' => 'Β', + '𝛤' => 'Γ', + '𝛥' => 'Δ', + '𝛦' => 'Ε', + '𝛧' => 'Ζ', + '𝛨' => 'Η', + '𝛩' => 'Θ', + '𝛪' => 'Ι', + '𝛫' => 'Κ', + '𝛬' => 'Λ', + '𝛭' => 'Μ', + '𝛮' => 'Ν', + '𝛯' => 'Ξ', + '𝛰' => 'Ο', + '𝛱' => 'Π', + '𝛲' => 'Ρ', + '𝛳' => 'Θ', + '𝛴' => 'Σ', + '𝛵' => 'Τ', + '𝛶' => 'Υ', + '𝛷' => 'Φ', + '𝛸' => 'Χ', + '𝛹' => 'Ψ', + '𝛺' => 'Ω', + '𝛻' => '∇', + '𝛼' => 'α', + '𝛽' => 'β', + '𝛾' => 'γ', + '𝛿' => 'δ', + '𝜀' => 'ε', + '𝜁' => 'ζ', + '𝜂' => 'η', + '𝜃' => 'θ', + '𝜄' => 'ι', + '𝜅' => 'κ', + '𝜆' => 'λ', + '𝜇' => 'μ', + '𝜈' => 'ν', + '𝜉' => 'ξ', + '𝜊' => 'ο', + '𝜋' => 'π', + '𝜌' => 'ρ', + '𝜍' => 'ς', + '𝜎' => 'σ', + '𝜏' => 'τ', + '𝜐' => 'υ', + '𝜑' => 'φ', + '𝜒' => 'χ', + '𝜓' => 'ψ', + '𝜔' => 'ω', + '𝜕' => '∂', + '𝜖' => 'ε', + '𝜗' => 'θ', + '𝜘' => 'κ', + '𝜙' => 'φ', + '𝜚' => 'ρ', + '𝜛' => 'π', + '𝜜' => 'Α', + '𝜝' => 'Β', + '𝜞' => 'Γ', + '𝜟' => 'Δ', + '𝜠' => 'Ε', + '𝜡' => 'Ζ', + '𝜢' => 'Η', + '𝜣' => 'Θ', + '𝜤' => 'Ι', + '𝜥' => 'Κ', + '𝜦' => 'Λ', + '𝜧' => 'Μ', + '𝜨' => 'Ν', + '𝜩' => 'Ξ', + '𝜪' => 'Ο', + '𝜫' => 'Π', + '𝜬' => 'Ρ', + '𝜭' => 'Θ', + '𝜮' => 'Σ', + '𝜯' => 'Τ', + '𝜰' => 'Υ', + '𝜱' => 'Φ', + '𝜲' => 'Χ', + '𝜳' => 'Ψ', + '𝜴' => 'Ω', + '𝜵' => '∇', + '𝜶' => 'α', + '𝜷' => 'β', + '𝜸' => 'γ', + '𝜹' => 'δ', + '𝜺' => 'ε', + '𝜻' => 'ζ', + '𝜼' => 'η', + '𝜽' => 'θ', + '𝜾' => 'ι', + '𝜿' => 'κ', + '𝝀' => 'λ', + '𝝁' => 'μ', + '𝝂' => 'ν', + '𝝃' => 'ξ', + '𝝄' => 'ο', + '𝝅' => 'π', + '𝝆' => 'ρ', + '𝝇' => 'ς', + '𝝈' => 'σ', + '𝝉' => 'τ', + '𝝊' => 'υ', + '𝝋' => 'φ', + '𝝌' => 'χ', + '𝝍' => 'ψ', + '𝝎' => 'ω', + '𝝏' => '∂', + '𝝐' => 'ε', + '𝝑' => 'θ', + '𝝒' => 'κ', + '𝝓' => 'φ', + '𝝔' => 'ρ', + '𝝕' => 'π', + '𝝖' => 'Α', + '𝝗' => 'Β', + '𝝘' => 'Γ', + '𝝙' => 'Δ', + '𝝚' => 'Ε', + '𝝛' => 'Ζ', + '𝝜' => 'Η', + '𝝝' => 'Θ', + '𝝞' => 'Ι', + '𝝟' => 'Κ', + '𝝠' => 'Λ', + '𝝡' => 'Μ', + '𝝢' => 'Ν', + '𝝣' => 'Ξ', + '𝝤' => 'Ο', + '𝝥' => 'Π', + '𝝦' => 'Ρ', + '𝝧' => 'Θ', + '𝝨' => 'Σ', + '𝝩' => 'Τ', + '𝝪' => 'Υ', + '𝝫' => 'Φ', + '𝝬' => 'Χ', + '𝝭' => 'Ψ', + '𝝮' => 'Ω', + '𝝯' => '∇', + '𝝰' => 'α', + '𝝱' => 'β', + '𝝲' => 'γ', + '𝝳' => 'δ', + '𝝴' => 'ε', + '𝝵' => 'ζ', + '𝝶' => 'η', + '𝝷' => 'θ', + '𝝸' => 'ι', + '𝝹' => 'κ', + '𝝺' => 'λ', + '𝝻' => 'μ', + '𝝼' => 'ν', + '𝝽' => 'ξ', + '𝝾' => 'ο', + '𝝿' => 'π', + '𝞀' => 'ρ', + '𝞁' => 'ς', + '𝞂' => 'σ', + '𝞃' => 'τ', + '𝞄' => 'υ', + '𝞅' => 'φ', + '𝞆' => 'χ', + '𝞇' => 'ψ', + '𝞈' => 'ω', + '𝞉' => '∂', + '𝞊' => 'ε', + '𝞋' => 'θ', + '𝞌' => 'κ', + '𝞍' => 'φ', + '𝞎' => 'ρ', + '𝞏' => 'π', + '𝞐' => 'Α', + '𝞑' => 'Β', + '𝞒' => 'Γ', + '𝞓' => 'Δ', + '𝞔' => 'Ε', + '𝞕' => 'Ζ', + '𝞖' => 'Η', + '𝞗' => 'Θ', + '𝞘' => 'Ι', + '𝞙' => 'Κ', + '𝞚' => 'Λ', + '𝞛' => 'Μ', + '𝞜' => 'Ν', + '𝞝' => 'Ξ', + '𝞞' => 'Ο', + '𝞟' => 'Π', + '𝞠' => 'Ρ', + '𝞡' => 'Θ', + '𝞢' => 'Σ', + '𝞣' => 'Τ', + '𝞤' => 'Υ', + '𝞥' => 'Φ', + '𝞦' => 'Χ', + '𝞧' => 'Ψ', + '𝞨' => 'Ω', + '𝞩' => '∇', + '𝞪' => 'α', + '𝞫' => 'β', + '𝞬' => 'γ', + '𝞭' => 'δ', + '𝞮' => 'ε', + '𝞯' => 'ζ', + '𝞰' => 'η', + '𝞱' => 'θ', + '𝞲' => 'ι', + '𝞳' => 'κ', + '𝞴' => 'λ', + '𝞵' => 'μ', + '𝞶' => 'ν', + '𝞷' => 'ξ', + '𝞸' => 'ο', + '𝞹' => 'π', + '𝞺' => 'ρ', + '𝞻' => 'ς', + '𝞼' => 'σ', + '𝞽' => 'τ', + '𝞾' => 'υ', + '𝞿' => 'φ', + '𝟀' => 'χ', + '𝟁' => 'ψ', + '𝟂' => 'ω', + '𝟃' => '∂', + '𝟄' => 'ε', + '𝟅' => 'θ', + '𝟆' => 'κ', + '𝟇' => 'φ', + '𝟈' => 'ρ', + '𝟉' => 'π', + '𝟊' => 'Ϝ', + '𝟋' => 'ϝ', + '𝟎' => '0', + '𝟏' => '1', + '𝟐' => '2', + '𝟑' => '3', + '𝟒' => '4', + '𝟓' => '5', + '𝟔' => '6', + '𝟕' => '7', + '𝟖' => '8', + '𝟗' => '9', + '𝟘' => '0', + '𝟙' => '1', + '𝟚' => '2', + '𝟛' => '3', + '𝟜' => '4', + '𝟝' => '5', + '𝟞' => '6', + '𝟟' => '7', + '𝟠' => '8', + '𝟡' => '9', + '𝟢' => '0', + '𝟣' => '1', + '𝟤' => '2', + '𝟥' => '3', + '𝟦' => '4', + '𝟧' => '5', + '𝟨' => '6', + '𝟩' => '7', + '𝟪' => '8', + '𝟫' => '9', + '𝟬' => '0', + '𝟭' => '1', + '𝟮' => '2', + '𝟯' => '3', + '𝟰' => '4', + '𝟱' => '5', + '𝟲' => '6', + '𝟳' => '7', + '𝟴' => '8', + '𝟵' => '9', + '𝟶' => '0', + '𝟷' => '1', + '𝟸' => '2', + '𝟹' => '3', + '𝟺' => '4', + '𝟻' => '5', + '𝟼' => '6', + '𝟽' => '7', + '𝟾' => '8', + '𝟿' => '9', + '𞸀' => 'ا', + '𞸁' => 'ب', + '𞸂' => 'ج', + '𞸃' => 'د', + '𞸅' => 'و', + '𞸆' => 'ز', + '𞸇' => 'ح', + '𞸈' => 'ط', + '𞸉' => 'ي', + '𞸊' => 'ك', + '𞸋' => 'ل', + '𞸌' => 'م', + '𞸍' => 'ن', + '𞸎' => 'س', + '𞸏' => 'ع', + '𞸐' => 'ف', + '𞸑' => 'ص', + '𞸒' => 'ق', + '𞸓' => 'ر', + '𞸔' => 'ش', + '𞸕' => 'ت', + '𞸖' => 'ث', + '𞸗' => 'خ', + '𞸘' => 'ذ', + '𞸙' => 'ض', + '𞸚' => 'ظ', + '𞸛' => 'غ', + '𞸜' => 'ٮ', + '𞸝' => 'ں', + '𞸞' => 'ڡ', + '𞸟' => 'ٯ', + '𞸡' => 'ب', + '𞸢' => 'ج', + '𞸤' => 'ه', + '𞸧' => 'ح', + '𞸩' => 'ي', + '𞸪' => 'ك', + '𞸫' => 'ل', + '𞸬' => 'م', + '𞸭' => 'ن', + '𞸮' => 'س', + '𞸯' => 'ع', + '𞸰' => 'ف', + '𞸱' => 'ص', + '𞸲' => 'ق', + '𞸴' => 'ش', + '𞸵' => 'ت', + '𞸶' => 'ث', + '𞸷' => 'خ', + '𞸹' => 'ض', + '𞸻' => 'غ', + '𞹂' => 'ج', + '𞹇' => 'ح', + '𞹉' => 'ي', + '𞹋' => 'ل', + '𞹍' => 'ن', + '𞹎' => 'س', + '𞹏' => 'ع', + '𞹑' => 'ص', + '𞹒' => 'ق', + '𞹔' => 'ش', + '𞹗' => 'خ', + '𞹙' => 'ض', + '𞹛' => 'غ', + '𞹝' => 'ں', + '𞹟' => 'ٯ', + '𞹡' => 'ب', + '𞹢' => 'ج', + '𞹤' => 'ه', + '𞹧' => 'ح', + '𞹨' => 'ط', + '𞹩' => 'ي', + '𞹪' => 'ك', + '𞹬' => 'م', + '𞹭' => 'ن', + '𞹮' => 'س', + '𞹯' => 'ع', + '𞹰' => 'ف', + '𞹱' => 'ص', + '𞹲' => 'ق', + '𞹴' => 'ش', + '𞹵' => 'ت', + '𞹶' => 'ث', + '𞹷' => 'خ', + '𞹹' => 'ض', + '𞹺' => 'ظ', + '𞹻' => 'غ', + '𞹼' => 'ٮ', + '𞹾' => 'ڡ', + '𞺀' => 'ا', + '𞺁' => 'ب', + '𞺂' => 'ج', + '𞺃' => 'د', + '𞺄' => 'ه', + '𞺅' => 'و', + '𞺆' => 'ز', + '𞺇' => 'ح', + '𞺈' => 'ط', + '𞺉' => 'ي', + '𞺋' => 'ل', + '𞺌' => 'م', + '𞺍' => 'ن', + '𞺎' => 'س', + '𞺏' => 'ع', + '𞺐' => 'ف', + '𞺑' => 'ص', + '𞺒' => 'ق', + '𞺓' => 'ر', + '𞺔' => 'ش', + '𞺕' => 'ت', + '𞺖' => 'ث', + '𞺗' => 'خ', + '𞺘' => 'ذ', + '𞺙' => 'ض', + '𞺚' => 'ظ', + '𞺛' => 'غ', + '𞺡' => 'ب', + '𞺢' => 'ج', + '𞺣' => 'د', + '𞺥' => 'و', + '𞺦' => 'ز', + '𞺧' => 'ح', + '𞺨' => 'ط', + '𞺩' => 'ي', + '𞺫' => 'ل', + '𞺬' => 'م', + '𞺭' => 'ن', + '𞺮' => 'س', + '𞺯' => 'ع', + '𞺰' => 'ف', + '𞺱' => 'ص', + '𞺲' => 'ق', + '𞺳' => 'ر', + '𞺴' => 'ش', + '𞺵' => 'ت', + '𞺶' => 'ث', + '𞺷' => 'خ', + '𞺸' => 'ذ', + '𞺹' => 'ض', + '𞺺' => 'ظ', + '𞺻' => 'غ', + '🄀' => '0.', + '🄁' => '0,', + '🄂' => '1,', + '🄃' => '2,', + '🄄' => '3,', + '🄅' => '4,', + '🄆' => '5,', + '🄇' => '6,', + '🄈' => '7,', + '🄉' => '8,', + '🄊' => '9,', + '🄐' => '(A)', + '🄑' => '(B)', + '🄒' => '(C)', + '🄓' => '(D)', + '🄔' => '(E)', + '🄕' => '(F)', + '🄖' => '(G)', + '🄗' => '(H)', + '🄘' => '(I)', + '🄙' => '(J)', + '🄚' => '(K)', + '🄛' => '(L)', + '🄜' => '(M)', + '🄝' => '(N)', + '🄞' => '(O)', + '🄟' => '(P)', + '🄠' => '(Q)', + '🄡' => '(R)', + '🄢' => '(S)', + '🄣' => '(T)', + '🄤' => '(U)', + '🄥' => '(V)', + '🄦' => '(W)', + '🄧' => '(X)', + '🄨' => '(Y)', + '🄩' => '(Z)', + '🄪' => '〔S〕', + '🄫' => 'C', + '🄬' => 'R', + '🄭' => 'CD', + '🄮' => 'WZ', + '🄰' => 'A', + '🄱' => 'B', + '🄲' => 'C', + '🄳' => 'D', + '🄴' => 'E', + '🄵' => 'F', + '🄶' => 'G', + '🄷' => 'H', + '🄸' => 'I', + '🄹' => 'J', + '🄺' => 'K', + '🄻' => 'L', + '🄼' => 'M', + '🄽' => 'N', + '🄾' => 'O', + '🄿' => 'P', + '🅀' => 'Q', + '🅁' => 'R', + '🅂' => 'S', + '🅃' => 'T', + '🅄' => 'U', + '🅅' => 'V', + '🅆' => 'W', + '🅇' => 'X', + '🅈' => 'Y', + '🅉' => 'Z', + '🅊' => 'HV', + '🅋' => 'MV', + '🅌' => 'SD', + '🅍' => 'SS', + '🅎' => 'PPV', + '🅏' => 'WC', + '🅪' => 'MC', + '🅫' => 'MD', + '🅬' => 'MR', + '🆐' => 'DJ', + '🈀' => 'ほか', + '🈁' => 'ココ', + '🈂' => 'サ', + '🈐' => '手', + '🈑' => '字', + '🈒' => '双', + '🈓' => 'デ', + '🈔' => '二', + '🈕' => '多', + '🈖' => '解', + '🈗' => '天', + '🈘' => '交', + '🈙' => '映', + '🈚' => '無', + '🈛' => '料', + '🈜' => '前', + '🈝' => '後', + '🈞' => '再', + '🈟' => '新', + '🈠' => '初', + '🈡' => '終', + '🈢' => '生', + '🈣' => '販', + '🈤' => '声', + '🈥' => '吹', + '🈦' => '演', + '🈧' => '投', + '🈨' => '捕', + '🈩' => '一', + '🈪' => '三', + '🈫' => '遊', + '🈬' => '左', + '🈭' => '中', + '🈮' => '右', + '🈯' => '指', + '🈰' => '走', + '🈱' => '打', + '🈲' => '禁', + '🈳' => '空', + '🈴' => '合', + '🈵' => '満', + '🈶' => '有', + '🈷' => '月', + '🈸' => '申', + '🈹' => '割', + '🈺' => '営', + '🈻' => '配', + '🉀' => '〔本〕', + '🉁' => '〔三〕', + '🉂' => '〔二〕', + '🉃' => '〔安〕', + '🉄' => '〔点〕', + '🉅' => '〔打〕', + '🉆' => '〔盗〕', + '🉇' => '〔勝〕', + '🉈' => '〔敗〕', + '🉐' => '得', + '🉑' => '可', + '🯰' => '0', + '🯱' => '1', + '🯲' => '2', + '🯳' => '3', + '🯴' => '4', + '🯵' => '5', + '🯶' => '6', + '🯷' => '7', + '🯸' => '8', + '🯹' => '9', +); diff --git a/lib/symfony/polyfill-intl-normalizer/bootstrap.php b/lib/symfony/polyfill-intl-normalizer/bootstrap.php new file mode 100644 index 0000000000..3608e5c05d --- /dev/null +++ b/lib/symfony/polyfill-intl-normalizer/bootstrap.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Normalizer as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('normalizer_is_normalized')) { + function normalizer_is_normalized($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::isNormalized($string, $form); } +} +if (!function_exists('normalizer_normalize')) { + function normalizer_normalize($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::normalize($string, $form); } +} diff --git a/lib/symfony/polyfill-intl-normalizer/bootstrap80.php b/lib/symfony/polyfill-intl-normalizer/bootstrap80.php new file mode 100644 index 0000000000..e36d1a9477 --- /dev/null +++ b/lib/symfony/polyfill-intl-normalizer/bootstrap80.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Intl\Normalizer as p; + +if (!function_exists('normalizer_is_normalized')) { + function normalizer_is_normalized(?string $string, ?int $form = p\Normalizer::FORM_C): bool { return p\Normalizer::isNormalized((string) $string, (int) $form); } +} +if (!function_exists('normalizer_normalize')) { + function normalizer_normalize(?string $string, ?int $form = p\Normalizer::FORM_C): string|false { return p\Normalizer::normalize((string) $string, (int) $form); } +} diff --git a/lib/symfony/polyfill-intl-normalizer/composer.json b/lib/symfony/polyfill-intl-normalizer/composer.json new file mode 100644 index 0000000000..eb452bfd41 --- /dev/null +++ b/lib/symfony/polyfill-intl-normalizer/composer.json @@ -0,0 +1,39 @@ +{ + "name": "symfony/polyfill-intl-normalizer", + "type": "library", + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "keywords": ["polyfill", "shim", "compatibility", "portable", "intl", "normalizer"], + "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.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "suggest": { + "ext-intl": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/lib/symfony/polyfill-mbstring/Mbstring.php b/lib/symfony/polyfill-mbstring/Mbstring.php index dc7801f4c0..693749f22b 100644 --- a/lib/symfony/polyfill-mbstring/Mbstring.php +++ b/lib/symfony/polyfill-mbstring/Mbstring.php @@ -67,19 +67,20 @@ namespace Symfony\Polyfill\Mbstring; */ final class Mbstring { - const MB_CASE_FOLD = PHP_INT_MAX; + public const MB_CASE_FOLD = \PHP_INT_MAX; - private static $encodingList = array('ASCII', 'UTF-8'); + private const CASE_FOLD = [ + ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + private static $encodingList = ['ASCII', 'UTF-8']; private static $language = 'neutral'; private static $internalEncoding = 'UTF-8'; - private static $caseFold = array( - array('µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"), - array('μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'), - ); public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) { - if (\is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) { + if (\is_array($fromEncoding) || ($fromEncoding !== null && false !== strpos($fromEncoding, ','))) { $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); } else { $fromEncoding = self::getEncoding($fromEncoding); @@ -101,27 +102,25 @@ final class Mbstring $fromEncoding = 'Windows-1252'; } if ('UTF-8' !== $fromEncoding) { - $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); + $s = \iconv($fromEncoding, 'UTF-8//IGNORE', $s); } - return preg_replace_callback('/[\x80-\xFF]+/', array(__CLASS__, 'html_encoding_callback'), $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'); + $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8'); $fromEncoding = 'UTF-8'; } - return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + return \iconv($fromEncoding, $toEncoding.'//IGNORE', $s); } - public static function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) + public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) { - $vars = array(&$a, &$b, &$c, &$d, &$e, &$f); - $ok = true; array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { - if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { $ok = false; } }); @@ -131,28 +130,28 @@ final class Mbstring public static function mb_decode_mimeheader($s) { - return iconv_mime_decode($s, 2, self::$internalEncoding); + return \iconv_mime_decode($s, 2, self::$internalEncoding); } public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) { - trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING); + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); } public static function mb_decode_numericentity($s, $convmap, $encoding = null) { - if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) { - trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING); + if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); return null; } - if (!\is_array($convmap) || !$convmap) { + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { return false; } - if (null !== $encoding && !\is_scalar($encoding)) { - trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING); + if (null !== $encoding && !is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); return ''; // Instead of null (cf. mb_encode_numericentity). } @@ -167,10 +166,10 @@ final class Mbstring if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { - $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); } } else { - $s = iconv($encoding, 'UTF-8//IGNORE', $s); + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); } $cnt = floor(\count($convmap) / 4) * 4; @@ -185,7 +184,7 @@ final class Mbstring $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]) { - return Mbstring::mb_chr($c - $convmap[$i + 2]); + return self::mb_chr($c - $convmap[$i + 2]); } } @@ -196,29 +195,29 @@ final class Mbstring return $s; } - return iconv('UTF-8', $encoding.'//IGNORE', $s); + return \iconv('UTF-8', $encoding.'//IGNORE', $s); } public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) { - if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) { - trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING); + if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); return null; } - if (!\is_array($convmap) || !$convmap) { + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { return false; } - if (null !== $encoding && !\is_scalar($encoding)) { - trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING); + if (null !== $encoding && !is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); return null; // Instead of '' (cf. mb_decode_numericentity). } - if (null !== $is_hex && !\is_scalar($is_hex)) { - trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', E_USER_WARNING); + if (null !== $is_hex && !is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING); return null; } @@ -233,13 +232,13 @@ final class Mbstring if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { - $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); } } else { - $s = iconv($encoding, 'UTF-8//IGNORE', $s); + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); } - static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; $cnt = floor(\count($convmap) / 4) * 4; $i = 0; @@ -266,7 +265,7 @@ final class Mbstring return $result; } - return iconv('UTF-8', $encoding.'//IGNORE', $result); + return \iconv('UTF-8', $encoding.'//IGNORE', $result); } public static function mb_convert_case($s, $mode, $encoding = null) @@ -281,20 +280,20 @@ final class Mbstring if ('UTF-8' === $encoding) { $encoding = null; if (!preg_match('//u', $s)) { - $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + $s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s); } } else { - $s = iconv($encoding, 'UTF-8//IGNORE', $s); + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); } - if (MB_CASE_TITLE == $mode) { + if (\MB_CASE_TITLE == $mode) { static $titleRegexp = null; if (null === $titleRegexp) { $titleRegexp = self::getData('titleCaseRegexp'); } - $s = preg_replace_callback($titleRegexp, array(__CLASS__, 'title_case'), $s); + $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); } else { - if (MB_CASE_UPPER == $mode) { + if (\MB_CASE_UPPER == $mode) { static $upper = null; if (null === $upper) { $upper = self::getData('upperCase'); @@ -302,7 +301,7 @@ final class Mbstring $map = $upper; } else { if (self::MB_CASE_FOLD === $mode) { - $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s); + $s = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $s); } static $lower = null; @@ -312,7 +311,7 @@ final class Mbstring $map = $lower; } - static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; $i = 0; $len = \strlen($s); @@ -344,7 +343,7 @@ final class Mbstring return $s; } - return iconv('UTF-8', $encoding.'//IGNORE', $s); + return \iconv('UTF-8', $encoding.'//IGNORE', $s); } public static function mb_internal_encoding($encoding = null) @@ -353,15 +352,19 @@ final class Mbstring return self::$internalEncoding; } - $encoding = self::getEncoding($encoding); + $normalizedEncoding = self::getEncoding($encoding); - if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) { - self::$internalEncoding = $encoding; + if ('UTF-8' === $normalizedEncoding || false !== @\iconv($normalizedEncoding, $normalizedEncoding, ' ')) { + self::$internalEncoding = $normalizedEncoding; return true; } - return false; + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); } public static function mb_language($lang = null) @@ -370,20 +373,24 @@ final class Mbstring return self::$language; } - switch ($lang = strtolower($lang)) { + switch ($normalizedLang = strtolower($lang)) { case 'uni': case 'neutral': - self::$language = $lang; + self::$language = $normalizedLang; return true; } - return false; + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); } public static function mb_list_encodings() { - return array('UTF-8'); + return ['UTF-8']; } public static function mb_encoding_aliases($encoding) @@ -391,7 +398,7 @@ final class Mbstring switch (strtoupper($encoding)) { case 'UTF8': case 'UTF-8': - return array('utf8'); + return ['utf8']; } return false; @@ -406,7 +413,7 @@ final class Mbstring $encoding = self::$internalEncoding; } - return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var); + return self::mb_detect_encoding($var, [$encoding]) || false !== @\iconv($encoding, $encoding, $var); } public static function mb_detect_encoding($str, $encodingList = null, $strict = false) @@ -481,7 +488,7 @@ final class Mbstring return \strlen($s); } - return @iconv_strlen($s, $encoding); + return @\iconv_strlen($s, $encoding); } public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) @@ -493,12 +500,16 @@ final class Mbstring $needle = (string) $needle; if ('' === $needle) { - trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING); + if (80000 > \PHP_VERSION_ID) { + trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); - return false; + return false; + } + + return 0; } - return iconv_strpos($haystack, $needle, $offset, $encoding); + return \iconv_strpos($haystack, $needle, $offset, $encoding); } public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) @@ -521,23 +532,28 @@ final class Mbstring } } - $pos = iconv_strrpos($haystack, $needle, $encoding); + $pos = '' !== $needle || 80000 > \PHP_VERSION_ID + ? \iconv_strrpos($haystack, $needle, $encoding) + : self::mb_strlen($haystack, $encoding); return false !== $pos ? $offset + $pos : false; } public static function mb_str_split($string, $split_length = 1, $encoding = null) { - if (null !== $string && !\is_scalar($string) && !(\is_object($string) && \method_exists($string, '__toString'))) { - trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', E_USER_WARNING); + if (null !== $string && !is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { + trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); return null; } if (1 > $split_length = (int) $split_length) { - trigger_error('The length of each segment must be greater than zero', E_USER_WARNING); + if (80000 > \PHP_VERSION_ID) { + trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); + return false; + } - return false; + throw new \ValueError('Argument #2 ($length) must be greater than 0'); } if (null === $encoding) { @@ -552,10 +568,10 @@ final class Mbstring } $rx .= '.{'.$split_length.'})/us'; - return preg_split($rx, $string, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); } - $result = array(); + $result = []; $length = mb_strlen($string, $encoding); for ($i = 0; $i < $length; $i += $split_length) { @@ -567,21 +583,30 @@ final class Mbstring public static function mb_strtolower($s, $encoding = null) { - return self::mb_convert_case($s, MB_CASE_LOWER, $encoding); + return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); } public static function mb_strtoupper($s, $encoding = null) { - return self::mb_convert_case($s, MB_CASE_UPPER, $encoding); + return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); } public static function mb_substitute_character($c = null) { + if (null === $c) { + return 'none'; + } if (0 === strcasecmp($c, 'none')) { return true; } + if (80000 > \PHP_VERSION_ID) { + return false; + } + if (\is_int($c) || 'long' === $c || 'entity' === $c) { + return false; + } - return null !== $c ? false : 'none'; + throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); } public static function mb_substr($s, $start, $length = null, $encoding = null) @@ -592,7 +617,7 @@ final class Mbstring } if ($start < 0) { - $start = iconv_strlen($s, $encoding) + $start; + $start = \iconv_strlen($s, $encoding) + $start; if ($start < 0) { $start = 0; } @@ -601,13 +626,13 @@ final class Mbstring if (null === $length) { $length = 2147483647; } elseif ($length < 0) { - $length = iconv_strlen($s, $encoding) + $length - $start; + $length = \iconv_strlen($s, $encoding) + $length - $start; if ($length < 0) { return ''; } } - return (string) iconv_substr($s, $start, $length, $encoding); + return (string) \iconv_substr($s, $start, $length, $encoding); } public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) @@ -632,7 +657,7 @@ final class Mbstring $pos = strrpos($haystack, $needle); } else { $needle = self::mb_substr($needle, 0, 1, $encoding); - $pos = iconv_strrpos($haystack, $needle, $encoding); + $pos = \iconv_strrpos($haystack, $needle, $encoding); } return self::getSubpart($pos, $part, $haystack, $encoding); @@ -669,7 +694,7 @@ final class Mbstring public static function mb_get_info($type = 'all') { - $info = array( + $info = [ 'internal_encoding' => self::$internalEncoding, 'http_output' => 'pass', 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', @@ -684,7 +709,7 @@ final class Mbstring 'detect_order' => self::$encodingList, 'substitute_character' => 'none', 'strict_detection' => 'Off', - ); + ]; if ('all' === $type) { return $info; @@ -711,12 +736,12 @@ final class Mbstring $encoding = self::getEncoding($encoding); if ('UTF-8' !== $encoding) { - $s = iconv($encoding, 'UTF-8//IGNORE', $s); + $s = \iconv($encoding, 'UTF-8//IGNORE', $s); } $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); - return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + return ($wide << 1) + \iconv_strlen($s, 'UTF-8'); } public static function mb_substr_count($haystack, $needle, $encoding = null) @@ -788,7 +813,7 @@ final class Mbstring { $i = 1; $entities = ''; - $m = unpack('C*', htmlentities($m[0], ENT_COMPAT, 'UTF-8')); + $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); while (isset($m[$i])) { if (0x80 > $m[$i]) { @@ -811,7 +836,7 @@ 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'); + return 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) diff --git a/lib/symfony/polyfill-mbstring/README.md b/lib/symfony/polyfill-mbstring/README.md index 4efb599d81..478b40da25 100644 --- a/lib/symfony/polyfill-mbstring/README.md +++ b/lib/symfony/polyfill-mbstring/README.md @@ -5,7 +5,7 @@ This component provides a partial, native PHP implementation for the [Mbstring](https://php.net/mbstring) 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/lib/symfony/polyfill-mbstring/Resources/mb_convert_variables.php8 b/lib/symfony/polyfill-mbstring/Resources/mb_convert_variables.php8 deleted file mode 100644 index d22d058e31..0000000000 --- a/lib/symfony/polyfill-mbstring/Resources/mb_convert_variables.php8 +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -use Symfony\Polyfill\Mbstring as p; - -if (!function_exists('mb_convert_variables')) { - /** - * Convert character code in variable(s) - */ - function mb_convert_variables($to_encoding, $from_encoding, &$var, &...$vars) - { - $vars = [&$var, ...$vars]; - - $ok = true; - array_walk_recursive($vars, function (&$v) use (&$ok, $to_encoding, $from_encoding) { - if (false === $v = p\Mbstring::mb_convert_encoding($v, $to_encoding, $from_encoding)) { - $ok = false; - } - }); - - return $ok ? $from_encoding : false; - } -} diff --git a/lib/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/lib/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php index a22eca57bd..fac60b081a 100644 --- a/lib/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php +++ b/lib/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -81,7 +81,7 @@ return array ( 'Ī' => 'ī', 'Ĭ' => 'ĭ', 'Į' => 'į', - 'İ' => 'i', + 'İ' => 'i̇', 'IJ' => 'ij', 'Ĵ' => 'ĵ', 'Ķ' => 'ķ', diff --git a/lib/symfony/polyfill-mbstring/Resources/unidata/upperCase.php b/lib/symfony/polyfill-mbstring/Resources/unidata/upperCase.php index ecbc15895e..56b9cb8520 100644 --- a/lib/symfony/polyfill-mbstring/Resources/unidata/upperCase.php +++ b/lib/symfony/polyfill-mbstring/Resources/unidata/upperCase.php @@ -746,41 +746,41 @@ return array ( 'ύ' => 'Ύ', 'ὼ' => 'Ὼ', 'ώ' => 'Ώ', - 'ᾀ' => 'ᾈ', - 'ᾁ' => 'ᾉ', - 'ᾂ' => 'ᾊ', - 'ᾃ' => 'ᾋ', - 'ᾄ' => 'ᾌ', - 'ᾅ' => 'ᾍ', - 'ᾆ' => 'ᾎ', - 'ᾇ' => 'ᾏ', - 'ᾐ' => 'ᾘ', - 'ᾑ' => 'ᾙ', - 'ᾒ' => 'ᾚ', - 'ᾓ' => 'ᾛ', - 'ᾔ' => 'ᾜ', - 'ᾕ' => 'ᾝ', - 'ᾖ' => 'ᾞ', - 'ᾗ' => 'ᾟ', - 'ᾠ' => 'ᾨ', - 'ᾡ' => 'ᾩ', - 'ᾢ' => 'ᾪ', - 'ᾣ' => 'ᾫ', - 'ᾤ' => 'ᾬ', - 'ᾥ' => 'ᾭ', - 'ᾦ' => 'ᾮ', - 'ᾧ' => 'ᾯ', + 'ᾀ' => 'ἈΙ', + 'ᾁ' => 'ἉΙ', + 'ᾂ' => 'ἊΙ', + 'ᾃ' => 'ἋΙ', + 'ᾄ' => 'ἌΙ', + 'ᾅ' => 'ἍΙ', + 'ᾆ' => 'ἎΙ', + 'ᾇ' => 'ἏΙ', + 'ᾐ' => 'ἨΙ', + 'ᾑ' => 'ἩΙ', + 'ᾒ' => 'ἪΙ', + 'ᾓ' => 'ἫΙ', + 'ᾔ' => 'ἬΙ', + 'ᾕ' => 'ἭΙ', + 'ᾖ' => 'ἮΙ', + 'ᾗ' => 'ἯΙ', + 'ᾠ' => 'ὨΙ', + 'ᾡ' => 'ὩΙ', + 'ᾢ' => 'ὪΙ', + 'ᾣ' => 'ὫΙ', + 'ᾤ' => 'ὬΙ', + 'ᾥ' => 'ὭΙ', + 'ᾦ' => 'ὮΙ', + 'ᾧ' => 'ὯΙ', 'ᾰ' => 'Ᾰ', 'ᾱ' => 'Ᾱ', - 'ᾳ' => 'ᾼ', + 'ᾳ' => 'ΑΙ', 'ι' => 'Ι', - 'ῃ' => 'ῌ', + 'ῃ' => 'ΗΙ', 'ῐ' => 'Ῐ', 'ῑ' => 'Ῑ', 'ῠ' => 'Ῠ', 'ῡ' => 'Ῡ', 'ῥ' => 'Ῥ', - 'ῳ' => 'ῼ', + 'ῳ' => 'ΩΙ', 'ⅎ' => 'Ⅎ', 'ⅰ' => 'Ⅰ', 'ⅱ' => 'Ⅱ', @@ -1411,4 +1411,79 @@ return array ( '𞥁' => '𞤟', '𞥂' => '𞤠', '𞥃' => '𞤡', + 'ß' => 'SS', + 'ff' => 'FF', + 'fi' => 'FI', + 'fl' => 'FL', + 'ffi' => 'FFI', + 'ffl' => 'FFL', + 'ſt' => 'ST', + 'st' => 'ST', + 'և' => 'ԵՒ', + 'ﬓ' => 'ՄՆ', + 'ﬔ' => 'ՄԵ', + 'ﬕ' => 'ՄԻ', + 'ﬖ' => 'ՎՆ', + 'ﬗ' => 'ՄԽ', + 'ʼn' => 'ʼN', + 'ΐ' => 'Ϊ́', + 'ΰ' => 'Ϋ́', + 'ǰ' => 'J̌', + 'ẖ' => 'H̱', + 'ẗ' => 'T̈', + 'ẘ' => 'W̊', + 'ẙ' => 'Y̊', + 'ẚ' => 'Aʾ', + 'ὐ' => 'Υ̓', + 'ὒ' => 'Υ̓̀', + 'ὔ' => 'Υ̓́', + 'ὖ' => 'Υ̓͂', + 'ᾶ' => 'Α͂', + 'ῆ' => 'Η͂', + 'ῒ' => 'Ϊ̀', + 'ΐ' => 'Ϊ́', + 'ῖ' => 'Ι͂', + 'ῗ' => 'Ϊ͂', + 'ῢ' => 'Ϋ̀', + 'ΰ' => 'Ϋ́', + 'ῤ' => 'Ρ̓', + 'ῦ' => 'Υ͂', + 'ῧ' => 'Ϋ͂', + 'ῶ' => 'Ω͂', + 'ᾈ' => 'ἈΙ', + 'ᾉ' => 'ἉΙ', + 'ᾊ' => 'ἊΙ', + 'ᾋ' => 'ἋΙ', + 'ᾌ' => 'ἌΙ', + 'ᾍ' => 'ἍΙ', + 'ᾎ' => 'ἎΙ', + 'ᾏ' => 'ἏΙ', + 'ᾘ' => 'ἨΙ', + 'ᾙ' => 'ἩΙ', + 'ᾚ' => 'ἪΙ', + 'ᾛ' => 'ἫΙ', + 'ᾜ' => 'ἬΙ', + 'ᾝ' => 'ἭΙ', + 'ᾞ' => 'ἮΙ', + 'ᾟ' => 'ἯΙ', + 'ᾨ' => 'ὨΙ', + 'ᾩ' => 'ὩΙ', + 'ᾪ' => 'ὪΙ', + 'ᾫ' => 'ὫΙ', + 'ᾬ' => 'ὬΙ', + 'ᾭ' => 'ὭΙ', + 'ᾮ' => 'ὮΙ', + 'ᾯ' => 'ὯΙ', + 'ᾼ' => 'ΑΙ', + 'ῌ' => 'ΗΙ', + 'ῼ' => 'ΩΙ', + 'ᾲ' => 'ᾺΙ', + 'ᾴ' => 'ΆΙ', + 'ῂ' => 'ῊΙ', + 'ῄ' => 'ΉΙ', + 'ῲ' => 'ῺΙ', + 'ῴ' => 'ΏΙ', + 'ᾷ' => 'Α͂Ι', + 'ῇ' => 'Η͂Ι', + 'ῷ' => 'Ω͂Ι', ); diff --git a/lib/symfony/polyfill-mbstring/bootstrap.php b/lib/symfony/polyfill-mbstring/bootstrap.php index a48f7e62ef..1fedd1f7c8 100644 --- a/lib/symfony/polyfill-mbstring/bootstrap.php +++ b/lib/symfony/polyfill-mbstring/bootstrap.php @@ -11,6 +11,10 @@ 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); } } @@ -18,7 +22,7 @@ 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 = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } + 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); } @@ -51,7 +55,7 @@ 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 = array()) { parse_str($string, $result); } + 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); } @@ -108,13 +112,11 @@ 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 = '') { return p\Mbstring::mb_http_input($type); } + function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } } -if (PHP_VERSION_ID >= 80000) { - require_once __DIR__.'/Resources/mb_convert_variables.php8'; -} elseif (!function_exists('mb_convert_variables')) { - function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $a, $b, $c, $d, $e, $f); } +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')) { diff --git a/lib/symfony/polyfill-mbstring/bootstrap80.php b/lib/symfony/polyfill-mbstring/bootstrap80.php new file mode 100644 index 0000000000..82f5ac4d0f --- /dev/null +++ b/lib/symfony/polyfill-mbstring/bootstrap80.php @@ -0,0 +1,143 @@ + + * + * 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(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +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); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); } +} +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); } +} +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 (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/lib/symfony/polyfill-mbstring/composer.json b/lib/symfony/polyfill-mbstring/composer.json index abdfd3e564..9cd2e924e9 100644 --- a/lib/symfony/polyfill-mbstring/composer.json +++ b/lib/symfony/polyfill-mbstring/composer.json @@ -16,7 +16,10 @@ } ], "require": { - "php": ">=5.3.3" + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" }, "autoload": { "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, @@ -28,7 +31,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "1.19-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", diff --git a/lib/symfony/polyfill-php56/Php56.php b/lib/symfony/polyfill-php56/Php56.php deleted file mode 100644 index dbbc0e13ad..0000000000 --- a/lib/symfony/polyfill-php56/Php56.php +++ /dev/null @@ -1,138 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Polyfill\Php56; - -use Symfony\Polyfill\Util\Binary; - -/** - * @internal - */ -final class Php56 -{ - const LDAP_ESCAPE_FILTER = 1; - const LDAP_ESCAPE_DN = 2; - - public static function hash_equals($knownString, $userInput) - { - if (!\is_string($knownString)) { - trigger_error('Expected known_string to be a string, '.\gettype($knownString).' given', E_USER_WARNING); - - return false; - } - - if (!\is_string($userInput)) { - trigger_error('Expected user_input to be a string, '.\gettype($userInput).' given', E_USER_WARNING); - - return false; - } - - $knownLen = Binary::strlen($knownString); - $userLen = Binary::strlen($userInput); - - if ($knownLen !== $userLen) { - return false; - } - - $result = 0; - - for ($i = 0; $i < $knownLen; ++$i) { - $result |= \ord($knownString[$i]) ^ \ord($userInput[$i]); - } - - return 0 === $result; - } - - /** - * Stub implementation of the {@link ldap_escape()} function of the ldap - * extension. - * - * Escape strings for safe use in LDAP filters and DNs. - * - * @author Chris Wright - * - * @param string $subject - * @param string $ignore - * @param int $flags - * - * @return string - * - * @see http://stackoverflow.com/a/8561604 - */ - public static function ldap_escape($subject, $ignore = '', $flags = 0) - { - static $charMaps = null; - - if (null === $charMaps) { - $charMaps = array( - self::LDAP_ESCAPE_FILTER => array('\\', '*', '(', ')', "\x00"), - self::LDAP_ESCAPE_DN => array('\\', ',', '=', '+', '<', '>', ';', '"', '#', "\r"), - ); - - $charMaps[0] = array(); - - for ($i = 0; $i < 256; ++$i) { - $charMaps[0][\chr($i)] = sprintf('\\%02x', $i); - } - - for ($i = 0, $l = \count($charMaps[self::LDAP_ESCAPE_FILTER]); $i < $l; ++$i) { - $chr = $charMaps[self::LDAP_ESCAPE_FILTER][$i]; - unset($charMaps[self::LDAP_ESCAPE_FILTER][$i]); - $charMaps[self::LDAP_ESCAPE_FILTER][$chr] = $charMaps[0][$chr]; - } - - for ($i = 0, $l = \count($charMaps[self::LDAP_ESCAPE_DN]); $i < $l; ++$i) { - $chr = $charMaps[self::LDAP_ESCAPE_DN][$i]; - unset($charMaps[self::LDAP_ESCAPE_DN][$i]); - $charMaps[self::LDAP_ESCAPE_DN][$chr] = $charMaps[0][$chr]; - } - } - - // Create the base char map to escape - $flags = (int) $flags; - $charMap = array(); - - if ($flags & self::LDAP_ESCAPE_FILTER) { - $charMap += $charMaps[self::LDAP_ESCAPE_FILTER]; - } - - if ($flags & self::LDAP_ESCAPE_DN) { - $charMap += $charMaps[self::LDAP_ESCAPE_DN]; - } - - if (!$charMap) { - $charMap = $charMaps[0]; - } - - // Remove any chars to ignore from the list - $ignore = (string) $ignore; - - for ($i = 0, $l = \strlen($ignore); $i < $l; ++$i) { - unset($charMap[$ignore[$i]]); - } - - // Do the main replacement - $result = strtr($subject, $charMap); - - // Encode leading/trailing spaces if self::LDAP_ESCAPE_DN is passed - if ($flags & self::LDAP_ESCAPE_DN) { - if (' ' === $result[0]) { - $result = '\\20'.substr($result, 1); - } - - if (' ' === $result[\strlen($result) - 1]) { - $result = substr($result, 0, -1).'\\20'; - } - } - - return $result; - } -} diff --git a/lib/symfony/polyfill-php56/README.md b/lib/symfony/polyfill-php56/README.md deleted file mode 100644 index 5ad570be32..0000000000 --- a/lib/symfony/polyfill-php56/README.md +++ /dev/null @@ -1,15 +0,0 @@ -Symfony Polyfill / Php56 -======================== - -This component provides functions unavailable in releases prior to PHP 5.6: - -- [`hash_equals`](https://php.net/hash_equals) (part of [hash](https://php.net/hash) extension) -- [`ldap_escape`](https://php.net/ldap_escape) (part of [ldap](https://php.net/ldap) extension) - -More information can be found in the -[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). - -License -======= - -This library is released under the [MIT license](LICENSE). diff --git a/lib/symfony/polyfill-php56/bootstrap.php b/lib/symfony/polyfill-php56/bootstrap.php deleted file mode 100644 index b8e4cee97b..0000000000 --- a/lib/symfony/polyfill-php56/bootstrap.php +++ /dev/null @@ -1,44 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -use Symfony\Polyfill\Php56 as p; - -if (PHP_VERSION_ID >= 50600) { - return; -} - -if (!function_exists('hash_equals')) { - function hash_equals($known_string, $user_string) { return p\Php56::hash_equals($known_string, $user_string); } -} -if (extension_loaded('ldap') && !defined('LDAP_ESCAPE_FILTER')) { - define('LDAP_ESCAPE_FILTER', 1); -} -if (extension_loaded('ldap') && !defined('LDAP_ESCAPE_DN')) { - define('LDAP_ESCAPE_DN', 2); -} - -if (extension_loaded('ldap') && !function_exists('ldap_escape')) { - function ldap_escape($value, $ignore = '', $flags = 0) { return p\Php56::ldap_escape($value, $ignore, $flags); } -} - -if (50509 === PHP_VERSION_ID && 4 === PHP_INT_SIZE) { - // Missing functions in PHP 5.5.9 - affects 32 bit builds of Ubuntu 14.04LTS - // See https://bugs.launchpad.net/ubuntu/+source/php5/+bug/1315888 - if (!function_exists('gzopen') && function_exists('gzopen64')) { - function gzopen($filename, $mode, $use_include_path = 0) { return gzopen64($filename, $mode, $use_include_path); } - } - if (!function_exists('gzseek') && function_exists('gzseek64')) { - function gzseek($fp, $offset, $whence = SEEK_SET) { return gzseek64($fp, $offset, $whence); } - } - if (!function_exists('gztell') && function_exists('gztell64')) { - function gztell($fp) { return gztell64($fp); } - } -} diff --git a/lib/symfony/polyfill-php56/composer.json b/lib/symfony/polyfill-php56/composer.json deleted file mode 100644 index 8cc5543167..0000000000 --- a/lib/symfony/polyfill-php56/composer.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "symfony/polyfill-php56", - "type": "library", - "description": "Symfony polyfill backporting some PHP 5.6+ 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": ">=5.3.3", - "symfony/polyfill-util": "~1.0" - }, - "autoload": { - "psr-4": { "Symfony\\Polyfill\\Php56\\": "" }, - "files": [ "bootstrap.php" ] - }, - "minimum-stability": "dev", - "extra": { - "branch-alias": { - "dev-main": "1.19-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - } -} diff --git a/lib/symfony/polyfill-php70/Php70.php b/lib/symfony/polyfill-php70/Php70.php deleted file mode 100644 index 7f1ad08a46..0000000000 --- a/lib/symfony/polyfill-php70/Php70.php +++ /dev/null @@ -1,74 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Polyfill\Php70; - -/** - * @author Nicolas Grekas - * - * @internal - */ -final class Php70 -{ - public static function intdiv($dividend, $divisor) - { - $dividend = self::intArg($dividend, __FUNCTION__, 1); - $divisor = self::intArg($divisor, __FUNCTION__, 2); - - if (0 === $divisor) { - throw new \DivisionByZeroError('Division by zero'); - } - if (-1 === $divisor && ~PHP_INT_MAX === $dividend) { - throw new \ArithmeticError('Division of PHP_INT_MIN by -1 is not an integer'); - } - - return ($dividend - ($dividend % $divisor)) / $divisor; - } - - public static function preg_replace_callback_array(array $patterns, $subject, $limit = -1, &$count = 0) - { - $count = 0; - $result = (string) $subject; - if (0 === $limit = self::intArg($limit, __FUNCTION__, 3)) { - return $result; - } - - foreach ($patterns as $pattern => $callback) { - $result = preg_replace_callback($pattern, $callback, $result, $limit, $c); - $count += $c; - } - - return $result; - } - - public static function error_clear_last() - { - static $handler; - if (!$handler) { - $handler = function () { return false; }; - } - set_error_handler($handler); - @trigger_error(''); - restore_error_handler(); - } - - private static function intArg($value, $caller, $pos) - { - if (\is_int($value)) { - return $value; - } - if (!\is_numeric($value) || PHP_INT_MAX <= ($value += 0) || ~PHP_INT_MAX >= $value) { - throw new \TypeError(sprintf('%s() expects parameter %d to be integer, %s given', $caller, $pos, \gettype($value))); - } - - return (int) $value; - } -} diff --git a/lib/symfony/polyfill-php70/README.md b/lib/symfony/polyfill-php70/README.md deleted file mode 100644 index abd5488237..0000000000 --- a/lib/symfony/polyfill-php70/README.md +++ /dev/null @@ -1,28 +0,0 @@ -Symfony Polyfill / Php70 -======================== - -This component provides features unavailable in releases prior to PHP 7.0: - -- [`intdiv`](https://php.net/intdiv) -- [`preg_replace_callback_array`](https://php.net/preg_replace_callback_array) -- [`error_clear_last`](https://php.net/error_clear_last) -- `random_bytes` and `random_int` (from [paragonie/random_compat](https://github.com/paragonie/random_compat)) -- [`*Error` throwable classes](https://php.net/Error) -- [`PHP_INT_MIN`](https://php.net/reserved.constants#constant.php-int-min) -- `SessionUpdateTimestampHandlerInterface` - -More information can be found in the -[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). - -Compatibility notes -=================== - -To write portable code between PHP5 and PHP7, some care must be taken: -- `\*Error` exceptions must be caught before `\Exception`; -- after calling `error_clear_last()`, the result of `$e = error_get_last()` must be - verified using `isset($e['message'][0])` instead of `null !== $e`. - -License -======= - -This library is released under the [MIT license](LICENSE). diff --git a/lib/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php b/lib/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php deleted file mode 100644 index 6819124462..0000000000 --- a/lib/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php +++ /dev/null @@ -1,5 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -use Symfony\Polyfill\Php70 as p; - -if (PHP_VERSION_ID >= 70000) { - return; -} - -if (!defined('PHP_INT_MIN')) { - define('PHP_INT_MIN', ~PHP_INT_MAX); -} - -if (!function_exists('intdiv')) { - function intdiv($num1, $num2) { return p\Php70::intdiv($num1, $num2); } -} -if (!function_exists('preg_replace_callback_array')) { - function preg_replace_callback_array(array $pattern, $subject, $limit = -1, &$count = 0, $flags = null) { return p\Php70::preg_replace_callback_array($pattern, $subject, $limit, $count); } -} -if (!function_exists('error_clear_last')) { - function error_clear_last() { return p\Php70::error_clear_last(); } -} diff --git a/lib/symfony/polyfill-php70/composer.json b/lib/symfony/polyfill-php70/composer.json deleted file mode 100644 index a2b30c07c5..0000000000 --- a/lib/symfony/polyfill-php70/composer.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "symfony/polyfill-php70", - "type": "library", - "description": "Symfony polyfill backporting some PHP 7.0+ 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": ">=5.3.3", - "paragonie/random_compat": "~1.0|~2.0|~9.99" - }, - "autoload": { - "psr-4": { "Symfony\\Polyfill\\Php70\\": "" }, - "files": [ "bootstrap.php" ], - "classmap": [ "Resources/stubs" ] - }, - "minimum-stability": "dev", - "extra": { - "branch-alias": { - "dev-main": "1.19-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - } -} diff --git a/lib/symfony/polyfill-php70/LICENSE b/lib/symfony/polyfill-php72/LICENSE similarity index 100% rename from lib/symfony/polyfill-php70/LICENSE rename to lib/symfony/polyfill-php72/LICENSE diff --git a/lib/symfony/polyfill-php72/Php72.php b/lib/symfony/polyfill-php72/Php72.php new file mode 100644 index 0000000000..5e20d5bf8f --- /dev/null +++ b/lib/symfony/polyfill-php72/Php72.php @@ -0,0 +1,217 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php72; + +/** + * @author Nicolas Grekas + * @author Dariusz Rumiński + * + * @internal + */ +final class Php72 +{ + private static $hashMask; + + public static function utf8_encode($s) + { + $s .= $s; + $len = \strlen($s); + + for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) { + switch (true) { + case $s[$i] < "\x80": $s[$j] = $s[$i]; break; + case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break; + default: $s[$j] = "\xC3"; $s[++$j] = \chr(\ord($s[$i]) - 64); break; + } + } + + return substr($s, 0, $j); + } + + public static function utf8_decode($s) + { + $s = (string) $s; + $len = \strlen($s); + + for ($i = 0, $j = 0; $i < $len; ++$i, ++$j) { + switch ($s[$i] & "\xF0") { + case "\xC0": + case "\xD0": + $c = (\ord($s[$i] & "\x1F") << 6) | \ord($s[++$i] & "\x3F"); + $s[$j] = $c < 256 ? \chr($c) : '?'; + break; + + case "\xF0": + ++$i; + // no break + + case "\xE0": + $s[$j] = '?'; + $i += 2; + break; + + default: + $s[$j] = $s[$i]; + } + } + + return substr($s, 0, $j); + } + + public static function php_os_family() + { + if ('\\' === \DIRECTORY_SEPARATOR) { + return 'Windows'; + } + + $map = [ + 'Darwin' => 'Darwin', + 'DragonFly' => 'BSD', + 'FreeBSD' => 'BSD', + 'NetBSD' => 'BSD', + 'OpenBSD' => 'BSD', + 'Linux' => 'Linux', + 'SunOS' => 'Solaris', + ]; + + return isset($map[\PHP_OS]) ? $map[\PHP_OS] : 'Unknown'; + } + + public static function spl_object_id($object) + { + if (null === self::$hashMask) { + self::initHashMask(); + } + if (null === $hash = spl_object_hash($object)) { + return; + } + + // On 32-bit systems, PHP_INT_SIZE is 4, + return self::$hashMask ^ hexdec(substr($hash, 16 - (\PHP_INT_SIZE * 2 - 1), (\PHP_INT_SIZE * 2 - 1))); + } + + public static function sapi_windows_vt100_support($stream, $enable = null) + { + if (!\is_resource($stream)) { + trigger_error('sapi_windows_vt100_support() expects parameter 1 to be resource, '.\gettype($stream).' given', \E_USER_WARNING); + + return false; + } + + $meta = stream_get_meta_data($stream); + + if ('STDIO' !== $meta['stream_type']) { + trigger_error('sapi_windows_vt100_support() was not able to analyze the specified stream', \E_USER_WARNING); + + return false; + } + + // We cannot actually disable vt100 support if it is set + if (false === $enable || !self::stream_isatty($stream)) { + return false; + } + + // The native function does not apply to stdin + $meta = array_map('strtolower', $meta); + $stdin = 'php://stdin' === $meta['uri'] || 'php://fd/0' === $meta['uri']; + + return !$stdin + && (false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM') + || 'Hyper' === getenv('TERM_PROGRAM')); + } + + public static function stream_isatty($stream) + { + if (!\is_resource($stream)) { + trigger_error('stream_isatty() expects parameter 1 to be resource, '.\gettype($stream).' given', \E_USER_WARNING); + + return false; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $stat = @fstat($stream); + // Check if formatted mode is S_IFCHR + return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; + } + + return \function_exists('posix_isatty') && @posix_isatty($stream); + } + + private static function initHashMask() + { + $obj = (object) []; + self::$hashMask = -1; + + // check if we are nested in an output buffering handler to prevent a fatal error with ob_start() below + $obFuncs = ['ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush']; + foreach (debug_backtrace(\PHP_VERSION_ID >= 50400 ? \DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) { + if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && \in_array($frame['function'], $obFuncs)) { + $frame['line'] = 0; + break; + } + } + if (!empty($frame['line'])) { + ob_start(); + debug_zval_dump($obj); + self::$hashMask = (int) substr(ob_get_clean(), 17); + } + + self::$hashMask ^= hexdec(substr(spl_object_hash($obj), 16 - (\PHP_INT_SIZE * 2 - 1), (\PHP_INT_SIZE * 2 - 1))); + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = $encoding ?? mb_internal_encoding()) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if (null === $encoding) { + $s = mb_convert_encoding($s, 'UTF-8'); + } elseif ('UTF-8' !== $encoding) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $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; + } + + return $code; + } +} diff --git a/lib/symfony/polyfill-php72/README.md b/lib/symfony/polyfill-php72/README.md new file mode 100644 index 0000000000..ed1905055d --- /dev/null +++ b/lib/symfony/polyfill-php72/README.md @@ -0,0 +1,35 @@ +Symfony Polyfill / Php72 +======================== + +This component provides functions added to PHP 7.2 core: + +- [`spl_object_id`](https://php.net/spl_object_id) +- [`stream_isatty`](https://php.net/stream_isatty) + +And also functions added to PHP 7.2 mbstring: + +- [`mb_ord`](https://php.net/mb_ord) +- [`mb_chr`](https://php.net/mb_chr) +- [`mb_scrub`](https://php.net/mb_scrub) + +On Windows only: + +- [`sapi_windows_vt100_support`](https://php.net/sapi_windows_vt100_support) + +Moved to core since 7.2 (was in the optional XML extension earlier): + +- [`utf8_encode`](https://php.net/utf8_encode) +- [`utf8_decode`](https://php.net/utf8_decode) + +Also, it provides constants added to PHP 7.2: + +- [`PHP_FLOAT_*`](https://php.net/reserved.constants#constant.php-float-dig) +- [`PHP_OS_FAMILY`](https://php.net/reserved.constants#constant.php-os-family) + +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/lib/symfony/polyfill-php72/bootstrap.php b/lib/symfony/polyfill-php72/bootstrap.php new file mode 100644 index 0000000000..b5c92d4c7c --- /dev/null +++ b/lib/symfony/polyfill-php72/bootstrap.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php72 as p; + +if (\PHP_VERSION_ID >= 70200) { + return; +} + +if (!defined('PHP_FLOAT_DIG')) { + define('PHP_FLOAT_DIG', 15); +} +if (!defined('PHP_FLOAT_EPSILON')) { + define('PHP_FLOAT_EPSILON', 2.2204460492503E-16); +} +if (!defined('PHP_FLOAT_MIN')) { + define('PHP_FLOAT_MIN', 2.2250738585072E-308); +} +if (!defined('PHP_FLOAT_MAX')) { + define('PHP_FLOAT_MAX', 1.7976931348623157E+308); +} +if (!defined('PHP_OS_FAMILY')) { + define('PHP_OS_FAMILY', p\Php72::php_os_family()); +} + +if ('\\' === \DIRECTORY_SEPARATOR && !function_exists('sapi_windows_vt100_support')) { + function sapi_windows_vt100_support($stream, $enable = null) { return p\Php72::sapi_windows_vt100_support($stream, $enable); } +} +if (!function_exists('stream_isatty')) { + function stream_isatty($stream) { return p\Php72::stream_isatty($stream); } +} +if (!function_exists('utf8_encode')) { + function utf8_encode($string) { return p\Php72::utf8_encode($string); } +} +if (!function_exists('utf8_decode')) { + function utf8_decode($string) { return p\Php72::utf8_decode($string); } +} +if (!function_exists('spl_object_id')) { + function spl_object_id($object) { return p\Php72::spl_object_id($object); } +} +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Php72::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Php72::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); } +} diff --git a/lib/symfony/polyfill-php72/composer.json b/lib/symfony/polyfill-php72/composer.json new file mode 100644 index 0000000000..4eac690e08 --- /dev/null +++ b/lib/symfony/polyfill-php72/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/polyfill-php72", + "type": "library", + "description": "Symfony polyfill backporting some PHP 7.2+ 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.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php72\\": "" }, + "files": [ "bootstrap.php" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/lib/symfony/polyfill-php73/LICENSE b/lib/symfony/polyfill-php73/LICENSE new file mode 100644 index 0000000000..3f853aaf35 --- /dev/null +++ b/lib/symfony/polyfill-php73/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 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/lib/symfony/polyfill-php73/Php73.php b/lib/symfony/polyfill-php73/Php73.php new file mode 100644 index 0000000000..65c35a6a11 --- /dev/null +++ b/lib/symfony/polyfill-php73/Php73.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 Symfony\Polyfill\Php73; + +/** + * @author Gabriel Caruso + * @author Ion Bazan + * + * @internal + */ +final class Php73 +{ + public static $startAt = 1533462603; + + /** + * @param bool $asNum + * + * @return array|float|int + */ + public static function hrtime($asNum = false) + { + $ns = microtime(false); + $s = substr($ns, 11) - self::$startAt; + $ns = 1E9 * (float) $ns; + + if ($asNum) { + $ns += $s * 1E9; + + return \PHP_INT_SIZE === 4 ? $ns : (int) $ns; + } + + return [$s, (int) $ns]; + } +} diff --git a/lib/symfony/polyfill-php73/README.md b/lib/symfony/polyfill-php73/README.md new file mode 100644 index 0000000000..032fafbda0 --- /dev/null +++ b/lib/symfony/polyfill-php73/README.md @@ -0,0 +1,18 @@ +Symfony Polyfill / Php73 +======================== + +This component provides functions added to PHP 7.3 core: + +- [`array_key_first`](https://php.net/array_key_first) +- [`array_key_last`](https://php.net/array_key_last) +- [`hrtime`](https://php.net/function.hrtime) +- [`is_countable`](https://php.net/is_countable) +- [`JsonException`](https://php.net/JsonException) + +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/lib/symfony/polyfill-php73/Resources/stubs/JsonException.php b/lib/symfony/polyfill-php73/Resources/stubs/JsonException.php new file mode 100644 index 0000000000..f06d6c2694 --- /dev/null +++ b/lib/symfony/polyfill-php73/Resources/stubs/JsonException.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 < 70300) { + class JsonException extends Exception + { + } +} diff --git a/lib/symfony/polyfill-php73/bootstrap.php b/lib/symfony/polyfill-php73/bootstrap.php new file mode 100644 index 0000000000..d6b2153823 --- /dev/null +++ b/lib/symfony/polyfill-php73/bootstrap.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php73 as p; + +if (\PHP_VERSION_ID >= 70300) { + return; +} + +if (!function_exists('is_countable')) { + function is_countable($value) { return is_array($value) || $value instanceof Countable || $value instanceof ResourceBundle || $value instanceof SimpleXmlElement; } +} +if (!function_exists('hrtime')) { + require_once __DIR__.'/Php73.php'; + p\Php73::$startAt = (int) microtime(true); + function hrtime($as_number = false) { return p\Php73::hrtime($as_number); } +} +if (!function_exists('array_key_first')) { + function array_key_first(array $array) { foreach ($array as $key => $value) { return $key; } } +} +if (!function_exists('array_key_last')) { + function array_key_last(array $array) { return key(array_slice($array, -1, 1, true)); } +} diff --git a/lib/symfony/polyfill-php73/composer.json b/lib/symfony/polyfill-php73/composer.json new file mode 100644 index 0000000000..af0cf42d23 --- /dev/null +++ b/lib/symfony/polyfill-php73/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/polyfill-php73", + "type": "library", + "description": "Symfony polyfill backporting some PHP 7.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.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php73\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/lib/symfony/polyfill-php80/LICENSE b/lib/symfony/polyfill-php80/LICENSE new file mode 100644 index 0000000000..5593b1d84f --- /dev/null +++ b/lib/symfony/polyfill-php80/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 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/lib/symfony/polyfill-php80/Php80.php b/lib/symfony/polyfill-php80/Php80.php new file mode 100644 index 0000000000..362dd1a959 --- /dev/null +++ b/lib/symfony/polyfill-php80/Php80.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Ion Bazan + * @author Nico Oelgart + * @author Nicolas Grekas + * + * @internal + */ +final class Php80 +{ + public static function fdiv(float $dividend, float $divisor): float + { + return @($dividend / $divisor); + } + + public static function get_debug_type($value): string + { + switch (true) { + case null === $value: return 'null'; + case \is_bool($value): return 'bool'; + case \is_string($value): return 'string'; + case \is_array($value): return 'array'; + case \is_int($value): return 'int'; + case \is_float($value): return 'float'; + case \is_object($value): break; + case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; + default: + if (null === $type = @get_resource_type($value)) { + return 'unknown'; + } + + if ('Unknown' === $type) { + $type = 'closed'; + } + + return "resource ($type)"; + } + + $class = \get_class($value); + + if (false === strpos($class, '@')) { + return $class; + } + + return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous'; + } + + 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))); + } + + return (int) $res; + } + + public static function preg_last_error_msg(): string + { + switch (preg_last_error()) { + case \PREG_INTERNAL_ERROR: + return 'Internal error'; + case \PREG_BAD_UTF8_ERROR: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + case \PREG_BAD_UTF8_OFFSET_ERROR: + return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; + case \PREG_BACKTRACK_LIMIT_ERROR: + return 'Backtrack limit exhausted'; + case \PREG_RECURSION_LIMIT_ERROR: + return 'Recursion limit exhausted'; + case \PREG_JIT_STACKLIMIT_ERROR: + return 'JIT stack limit exhausted'; + case \PREG_NO_ERROR: + return 'No error'; + default: + return 'Unknown error'; + } + } + + public static function str_contains(string $haystack, string $needle): bool + { + return '' === $needle || false !== strpos($haystack, $needle); + } + + public static function str_starts_with(string $haystack, string $needle): bool + { + return 0 === strncmp($haystack, $needle, \strlen($needle)); + } + + public static function str_ends_with(string $haystack, string $needle): bool + { + if ('' === $needle || $needle === $haystack) { + return true; + } + + if ('' === $haystack) { + return false; + } + + $needleLength = \strlen($needle); + + return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength); + } +} diff --git a/lib/symfony/polyfill-php80/PhpToken.php b/lib/symfony/polyfill-php80/PhpToken.php new file mode 100644 index 0000000000..fe6e691056 --- /dev/null +++ b/lib/symfony/polyfill-php80/PhpToken.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Fedonyuk Anton + * + * @internal + */ +class PhpToken implements \Stringable +{ + /** + * @var int + */ + public $id; + + /** + * @var string + */ + public $text; + + /** + * @var int + */ + public $line; + + /** + * @var int + */ + public $pos; + + public function __construct(int $id, string $text, int $line = -1, int $position = -1) + { + $this->id = $id; + $this->text = $text; + $this->line = $line; + $this->pos = $position; + } + + public function getTokenName(): ?string + { + if ('UNKNOWN' === $name = token_name($this->id)) { + $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; + } + + return $name; + } + + /** + * @param int|string|array $kind + */ + public function is($kind): bool + { + foreach ((array) $kind as $value) { + if (\in_array($value, [$this->id, $this->text], true)) { + return true; + } + } + + return false; + } + + public function isIgnorable(): bool + { + return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true); + } + + public function __toString(): string + { + return (string) $this->text; + } + + /** + * @return static[] + */ + public static function tokenize(string $code, int $flags = 0): array + { + $line = 1; + $position = 0; + $tokens = token_get_all($code, $flags); + foreach ($tokens as $index => $token) { + if (\is_string($token)) { + $id = \ord($token); + $text = $token; + } else { + [$id, $text, $line] = $token; + } + $tokens[$index] = new static($id, $text, $line, $position); + $position += \strlen($text); + } + + return $tokens; + } +} diff --git a/lib/symfony/polyfill-php80/README.md b/lib/symfony/polyfill-php80/README.md new file mode 100644 index 0000000000..3816c559d5 --- /dev/null +++ b/lib/symfony/polyfill-php80/README.md @@ -0,0 +1,25 @@ +Symfony Polyfill / Php80 +======================== + +This component provides features added to PHP 8.0 core: + +- [`Stringable`](https://php.net/stringable) interface +- [`fdiv`](https://php.net/fdiv) +- [`ValueError`](https://php.net/valueerror) class +- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class +- `FILTER_VALIDATE_BOOL` constant +- [`get_debug_type`](https://php.net/get_debug_type) +- [`PhpToken`](https://php.net/phptoken) class +- [`preg_last_error_msg`](https://php.net/preg_last_error_msg) +- [`str_contains`](https://php.net/str_contains) +- [`str_starts_with`](https://php.net/str_starts_with) +- [`str_ends_with`](https://php.net/str_ends_with) +- [`get_resource_id`](https://php.net/get_resource_id) + +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/lib/symfony/polyfill-php80/Resources/stubs/Attribute.php b/lib/symfony/polyfill-php80/Resources/stubs/Attribute.php new file mode 100644 index 0000000000..7ea6d2772d --- /dev/null +++ b/lib/symfony/polyfill-php80/Resources/stubs/Attribute.php @@ -0,0 +1,22 @@ +flags = $flags; + } +} diff --git a/lib/symfony/polyfill-php80/Resources/stubs/PhpToken.php b/lib/symfony/polyfill-php80/Resources/stubs/PhpToken.php new file mode 100644 index 0000000000..72f10812b3 --- /dev/null +++ b/lib/symfony/polyfill-php80/Resources/stubs/PhpToken.php @@ -0,0 +1,7 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php80 as p; + +if (\PHP_VERSION_ID >= 80000) { + return; +} + +if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { + define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN); +} + +if (!function_exists('fdiv')) { + function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); } +} +if (!function_exists('preg_last_error_msg')) { + function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } +} +if (!function_exists('str_contains')) { + function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_starts_with')) { + function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_ends_with')) { + function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('get_debug_type')) { + function get_debug_type($value): string { return p\Php80::get_debug_type($value); } +} +if (!function_exists('get_resource_id')) { + function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); } +} diff --git a/lib/symfony/polyfill-php80/composer.json b/lib/symfony/polyfill-php80/composer.json new file mode 100644 index 0000000000..cd3e9b65f4 --- /dev/null +++ b/lib/symfony/polyfill-php80/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/polyfill-php80", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/lib/symfony/polyfill-php81/LICENSE b/lib/symfony/polyfill-php81/LICENSE new file mode 100644 index 0000000000..efb17f98e7 --- /dev/null +++ b/lib/symfony/polyfill-php81/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2021 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/lib/symfony/polyfill-php81/Php81.php b/lib/symfony/polyfill-php81/Php81.php new file mode 100644 index 0000000000..f0507b765c --- /dev/null +++ b/lib/symfony/polyfill-php81/Php81.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\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/lib/symfony/polyfill-php81/README.md b/lib/symfony/polyfill-php81/README.md new file mode 100644 index 0000000000..7d8dd19078 --- /dev/null +++ b/lib/symfony/polyfill-php81/README.md @@ -0,0 +1,17 @@ +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) + +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/lib/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php b/lib/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php new file mode 100644 index 0000000000..f4cad34f64 --- /dev/null +++ b/lib/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php @@ -0,0 +1,11 @@ + + * + * 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/lib/symfony/polyfill-php81/composer.json b/lib/symfony/polyfill-php81/composer.json new file mode 100644 index 0000000000..014da788e3 --- /dev/null +++ b/lib/symfony/polyfill-php81/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/polyfill-php81", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.1+ 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.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php81\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/lib/symfony/polyfill-util/Binary.php b/lib/symfony/polyfill-util/Binary.php deleted file mode 100644 index 23ff974734..0000000000 --- a/lib/symfony/polyfill-util/Binary.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Polyfill\Util; - -if (\extension_loaded('mbstring')) { - class Binary extends BinaryOnFuncOverload - { - } -} else { - class Binary extends BinaryNoFuncOverload - { - } -} diff --git a/lib/symfony/polyfill-util/BinaryNoFuncOverload.php b/lib/symfony/polyfill-util/BinaryNoFuncOverload.php deleted file mode 100644 index 800ad75d05..0000000000 --- a/lib/symfony/polyfill-util/BinaryNoFuncOverload.php +++ /dev/null @@ -1,65 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Polyfill\Util; - -/** - * @author Nicolas Grekas - * - * @internal - */ -class BinaryNoFuncOverload -{ - public static function strlen($s) - { - return \strlen($s); - } - - public static function strpos($haystack, $needle, $offset = 0) - { - return strpos($haystack, $needle, $offset); - } - - public static function strrpos($haystack, $needle, $offset = 0) - { - return strrpos($haystack, $needle, $offset); - } - - public static function substr($string, $start, $length = PHP_INT_MAX) - { - return substr($string, $start, $length); - } - - public static function stripos($s, $needle, $offset = 0) - { - return stripos($s, $needle, $offset); - } - - public static function stristr($s, $needle, $part = false) - { - return stristr($s, $needle, $part); - } - - public static function strrchr($s, $needle, $part = false) - { - return strrchr($s, $needle, $part); - } - - public static function strripos($s, $needle, $offset = 0) - { - return strripos($s, $needle, $offset); - } - - public static function strstr($s, $needle, $part = false) - { - return strstr($s, $needle, $part); - } -} diff --git a/lib/symfony/polyfill-util/BinaryOnFuncOverload.php b/lib/symfony/polyfill-util/BinaryOnFuncOverload.php deleted file mode 100644 index e1b886eaca..0000000000 --- a/lib/symfony/polyfill-util/BinaryOnFuncOverload.php +++ /dev/null @@ -1,67 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Polyfill\Util; - -/** - * Binary safe version of string functions overloaded when MB_OVERLOAD_STRING is enabled. - * - * @author Nicolas Grekas - * - * @internal - */ -class BinaryOnFuncOverload -{ - public static function strlen($s) - { - return mb_strlen($s, '8bit'); - } - - public static function strpos($haystack, $needle, $offset = 0) - { - return mb_strpos($haystack, $needle, $offset, '8bit'); - } - - public static function strrpos($haystack, $needle, $offset = 0) - { - return mb_strrpos($haystack, $needle, $offset, '8bit'); - } - - public static function substr($string, $start, $length = 2147483647) - { - return mb_substr($string, $start, $length, '8bit'); - } - - public static function stripos($s, $needle, $offset = 0) - { - return mb_stripos($s, $needle, $offset, '8bit'); - } - - public static function stristr($s, $needle, $part = false) - { - return mb_stristr($s, $needle, $part, '8bit'); - } - - public static function strrchr($s, $needle, $part = false) - { - return mb_strrchr($s, $needle, $part, '8bit'); - } - - public static function strripos($s, $needle, $offset = 0) - { - return mb_strripos($s, $needle, $offset, '8bit'); - } - - public static function strstr($s, $needle, $part = false) - { - return mb_strstr($s, $needle, $part, '8bit'); - } -} diff --git a/lib/symfony/polyfill-util/LICENSE b/lib/symfony/polyfill-util/LICENSE deleted file mode 100644 index 4cd8bdd300..0000000000 --- a/lib/symfony/polyfill-util/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2015-2019 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/lib/symfony/polyfill-util/README.md b/lib/symfony/polyfill-util/README.md deleted file mode 100644 index 1c655fce37..0000000000 --- a/lib/symfony/polyfill-util/README.md +++ /dev/null @@ -1,13 +0,0 @@ -Symfony Polyfill / Util -======================= - -This component provides binary-safe string functions, using the -[mbstring](https://php.net/mbstring) extension when available. - -More information can be found in the -[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). - -License -======= - -This library is released under the [MIT license](LICENSE). diff --git a/lib/symfony/polyfill-util/TestListener.php b/lib/symfony/polyfill-util/TestListener.php deleted file mode 100644 index 6b17d45cca..0000000000 --- a/lib/symfony/polyfill-util/TestListener.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Polyfill\Util; - -if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) { - class_alias('Symfony\Polyfill\Util\TestListenerForV5', 'Symfony\Polyfill\Util\TestListener'); -// Using an early return instead of a else does not work when using the PHPUnit phar due to some weird PHP behavior (the class -// gets defined without executing the code before it and so the definition is not properly conditional) -} elseif (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '<')) { - class_alias('Symfony\Polyfill\Util\TestListenerForV6', 'Symfony\Polyfill\Util\TestListener'); -} else { - class_alias('Symfony\Polyfill\Util\TestListenerForV7', 'Symfony\Polyfill\Util\TestListener'); -} - -if (false) { - class TestListener - { - } -} diff --git a/lib/symfony/polyfill-util/TestListenerForV5.php b/lib/symfony/polyfill-util/TestListenerForV5.php deleted file mode 100644 index 501053dddf..0000000000 --- a/lib/symfony/polyfill-util/TestListenerForV5.php +++ /dev/null @@ -1,89 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Polyfill\Util; - -/** - * @author Nicolas Grekas - */ -class TestListenerForV5 extends \PHPUnit_Framework_TestSuite implements \PHPUnit_Framework_TestListener -{ - private $suite; - private $trait; - - public function __construct(\PHPUnit_Framework_TestSuite $suite = null) - { - if ($suite) { - $this->suite = $suite; - $this->setName($suite->getName().' with polyfills enabled'); - $this->addTest($suite); - } - $this->trait = new TestListenerTrait(); - } - - public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) - { - $this->trait->startTestSuite($suite); - } - - public function addError(\PHPUnit_Framework_Test $test, \Exception $e, $time) - { - $this->trait->addError($test, $e, $time); - } - - public function addWarning(\PHPUnit_Framework_Test $test, \PHPUnit_Framework_Warning $e, $time) - { - } - - public function addFailure(\PHPUnit_Framework_Test $test, \PHPUnit_Framework_AssertionFailedError $e, $time) - { - $this->trait->addError($test, $e, $time); - } - - public function addIncompleteTest(\PHPUnit_Framework_Test $test, \Exception $e, $time) - { - } - - public function addRiskyTest(\PHPUnit_Framework_Test $test, \Exception $e, $time) - { - } - - public function addSkippedTest(\PHPUnit_Framework_Test $test, \Exception $e, $time) - { - } - - public function endTestSuite(\PHPUnit_Framework_TestSuite $suite) - { - } - - public function startTest(\PHPUnit_Framework_Test $test) - { - } - - public function endTest(\PHPUnit_Framework_Test $test, $time) - { - } - - public static function warning($message) - { - return parent::warning($message); - } - - protected function setUp() - { - TestListenerTrait::$enabledPolyfills = $this->suite->getName(); - } - - protected function tearDown() - { - TestListenerTrait::$enabledPolyfills = false; - } -} diff --git a/lib/symfony/polyfill-util/TestListenerForV6.php b/lib/symfony/polyfill-util/TestListenerForV6.php deleted file mode 100644 index e9e87cbef7..0000000000 --- a/lib/symfony/polyfill-util/TestListenerForV6.php +++ /dev/null @@ -1,95 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Polyfill\Util; - -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestListener as TestListenerInterface; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; - -/** - * @author Nicolas Grekas - */ -class TestListenerForV6 extends TestSuite implements TestListenerInterface -{ - private $suite; - private $trait; - - public function __construct(TestSuite $suite = null) - { - if ($suite) { - $this->suite = $suite; - $this->setName($suite->getName().' with polyfills enabled'); - $this->addTest($suite); - } - $this->trait = new TestListenerTrait(); - } - - public function startTestSuite(TestSuite $suite) - { - $this->trait->startTestSuite($suite); - } - - public function addError(Test $test, \Exception $e, $time) - { - $this->trait->addError($test, $e, $time); - } - - public function addWarning(Test $test, Warning $e, $time) - { - } - - public function addFailure(Test $test, AssertionFailedError $e, $time) - { - $this->trait->addError($test, $e, $time); - } - - public function addIncompleteTest(Test $test, \Exception $e, $time) - { - } - - public function addRiskyTest(Test $test, \Exception $e, $time) - { - } - - public function addSkippedTest(Test $test, \Exception $e, $time) - { - } - - public function endTestSuite(TestSuite $suite) - { - } - - public function startTest(Test $test) - { - } - - public function endTest(Test $test, $time) - { - } - - public static function warning($message) - { - return parent::warning($message); - } - - protected function setUp() - { - TestListenerTrait::$enabledPolyfills = $this->suite->getName(); - } - - protected function tearDown() - { - TestListenerTrait::$enabledPolyfills = false; - } -} diff --git a/lib/symfony/polyfill-util/TestListenerForV7.php b/lib/symfony/polyfill-util/TestListenerForV7.php deleted file mode 100644 index a5c3759b22..0000000000 --- a/lib/symfony/polyfill-util/TestListenerForV7.php +++ /dev/null @@ -1,96 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Polyfill\Util; - -use PHPUnit\Framework\AssertionFailedError; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestListener as TestListenerInterface; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; -use PHPUnit\Framework\WarningTestCase; - -/** - * @author Ion Bazan - */ -class TestListenerForV7 extends TestSuite implements TestListenerInterface -{ - private $suite; - private $trait; - - public function __construct(TestSuite $suite = null) - { - if ($suite) { - $this->suite = $suite; - $this->setName($suite->getName().' with polyfills enabled'); - $this->addTest($suite); - } - $this->trait = new TestListenerTrait(); - } - - public function startTestSuite(TestSuite $suite): void - { - $this->trait->startTestSuite($suite); - } - - public function addError(Test $test, \Throwable $t, float $time): void - { - $this->trait->addError($test, $t, $time); - } - - public function addWarning(Test $test, Warning $e, float $time): void - { - } - - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - $this->trait->addError($test, $e, $time); - } - - public function addIncompleteTest(Test $test, \Throwable $t, float $time): void - { - } - - public function addRiskyTest(Test $test, \Throwable $t, float $time): void - { - } - - public function addSkippedTest(Test $test, \Throwable $t, float $time): void - { - } - - public function endTestSuite(TestSuite $suite): void - { - } - - public function startTest(Test $test): void - { - } - - public function endTest(Test $test, float $time): void - { - } - - public static function warning($message): WarningTestCase - { - return new WarningTestCase($message); - } - - protected function setUp(): void - { - TestListenerTrait::$enabledPolyfills = $this->suite->getName(); - } - - protected function tearDown(): void - { - TestListenerTrait::$enabledPolyfills = false; - } -} diff --git a/lib/symfony/polyfill-util/TestListenerTrait.php b/lib/symfony/polyfill-util/TestListenerTrait.php deleted file mode 100644 index 777fb48439..0000000000 --- a/lib/symfony/polyfill-util/TestListenerTrait.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. - */ - -namespace Symfony\Polyfill\Util; - -/** - * @author Nicolas Grekas - */ -class TestListenerTrait -{ - public static $enabledPolyfills; - - public function startTestSuite($mainSuite) - { - if (null !== self::$enabledPolyfills) { - return; - } - self::$enabledPolyfills = false; - $SkippedTestError = class_exists('PHPUnit\Framework\SkippedTestError') ? 'PHPUnit\Framework\SkippedTestError' : 'PHPUnit_Framework_SkippedTestError'; - - foreach ($mainSuite->tests() as $suite) { - $testClass = $suite->getName(); - if (!$tests = $suite->tests()) { - continue; - } - $testedClass = new \ReflectionClass($testClass); - if (preg_match('{^ \* @requires PHP (.*)}mi', $testedClass->getDocComment(), $m) && version_compare($m[1], \PHP_VERSION, '>')) { - continue; - } - if (!preg_match('/^(.+)\\\\Tests(\\\\.*)Test$/', $testClass, $m)) { - $mainSuite->addTest(TestListener::warning('Unknown naming convention for '.$testClass)); - continue; - } - if (!class_exists($m[1].$m[2])) { - continue; - } - $testedClass = new \ReflectionClass($m[1].$m[2]); - $bootstrap = new \SplFileObject(\dirname($testedClass->getFileName()).'/bootstrap.php'); - $warnings = array(); - $defLine = null; - - foreach (new \RegexIterator($bootstrap, '/define\(\'/') as $defLine) { - preg_match('/define\(\'(?P.+)\'/', $defLine, $matches); - if (\defined($matches['name'])) { - continue; - } - - try { - eval($defLine); - } catch (\PHPUnit_Framework_Exception $ex){ - $warnings[] = TestListener::warning($ex->getMessage()); - } catch (\PHPUnit\Framework\Exception $ex) { - $warnings[] = TestListener::warning($ex->getMessage()); - } - } - - $bootstrap->rewind(); - - foreach (new \RegexIterator($bootstrap, '/return p\\\\'.$testedClass->getShortName().'::/') as $defLine) { - if (!preg_match('/^\s*function (?P[^\(]++)(?P\(.*\)(?: ?: [^ ]++)?) \{ (?return p\\\\'.$testedClass->getShortName().'::[^\(]++)(?P\([^\)]*+\)); \}$/', $defLine, $f)) { - $warnings[] = TestListener::warning('Invalid line in bootstrap.php: '.trim($defLine)); - continue; - } - $testNamespace = substr($testClass, 0, strrpos($testClass, '\\')); - if (\function_exists($testNamespace.'\\'.$f['name'])) { - continue; - } - - try { - $r = new \ReflectionFunction($f['name']); - if ($r->isUserDefined()) { - throw new \ReflectionException(); - } - if ('idn_to_ascii' === $f['name'] || 'idn_to_utf8' === $f['name']) { - $defLine = sprintf('return INTL_IDNA_VARIANT_2003 === $variant ? \\%s($domain, $options, $variant) : \\%1$s%s', $f['name'], $f['args']); - } elseif (false !== strpos($f['signature'], '&') && 'idn_to_ascii' !== $f['name'] && 'idn_to_utf8' !== $f['name']) { - $defLine = sprintf('return \\%s%s', $f['name'], $f['args']); - } else { - $defLine = sprintf("return \\call_user_func_array('%s', \\func_get_args())", $f['name']); - } - } catch (\ReflectionException $e) { - $defLine = sprintf("throw new \\{$SkippedTestError}('Internal function not found: %s')", $f['name']); - } - - eval(<<getNamespaceName()} as p; - -function {$f['name']}{$f['signature']} -{ - if ('{$testClass}' === TestListenerTrait::\$enabledPolyfills) { - {$f['return']}{$f['args']}; - } - - {$defLine}; -} -EOPHP - ); - } - if (!$warnings && null === $defLine) { - $warnings[] = new $SkippedTestError('No Polyfills found in bootstrap.php for '.$testClass); - } else { - $mainSuite->addTest(new TestListener($suite)); - } - } - foreach ($warnings as $w) { - $mainSuite->addTest($w); - } - } - - public function addError($test, \Exception $e, $time) - { - if (false !== self::$enabledPolyfills) { - $r = new \ReflectionProperty('Exception', 'message'); - $r->setAccessible(true); - $r->setValue($e, 'Polyfills enabled, '.$r->getValue($e)); - } - } -} diff --git a/lib/symfony/polyfill-util/composer.json b/lib/symfony/polyfill-util/composer.json deleted file mode 100644 index 318bf5d868..0000000000 --- a/lib/symfony/polyfill-util/composer.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "symfony/polyfill-util", - "type": "library", - "description": "Symfony utilities for portability of PHP codes", - "keywords": ["polyfill", "shim", "compat", "compatibility"], - "homepage": "https://symfony.com", - "license": "MIT", - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "require": { - "php": ">=5.3.3" - }, - "autoload": { - "psr-4": { "Symfony\\Polyfill\\Util\\": "" } - }, - "minimum-stability": "dev", - "extra": { - "branch-alias": { - "dev-main": "1.19-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - } -} diff --git a/lib/symfony/routing/.gitignore b/lib/symfony/routing/.gitignore deleted file mode 100644 index c49a5d8df5..0000000000 --- a/lib/symfony/routing/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/lib/symfony/routing/Alias.php b/lib/symfony/routing/Alias.php new file mode 100644 index 0000000000..f3e1d5a853 --- /dev/null +++ b/lib/symfony/routing/Alias.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Routing\Exception\InvalidArgumentException; + +class Alias +{ + private $id; + private $deprecation = []; + + public function __construct(string $id) + { + $this->id = $id; + } + + /** + * @return static + */ + public function withId(string $id): self + { + $new = clone $this; + + $new->id = $id; + + return $new; + } + + /** + * Returns the target name of this alias. + * + * @return string The target name + */ + public function getId(): string + { + return $this->id; + } + + /** + * Whether this alias is deprecated, that means it should not be referenced anymore. + * + * @param string $package The name of the composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The deprecation message to use + * + * @return $this + * + * @throws InvalidArgumentException when the message template is invalid + */ + public function setDeprecated(string $package, string $version, string $message): self + { + if ('' !== $message) { + if (preg_match('#[\r\n]|\*/#', $message)) { + throw new InvalidArgumentException('Invalid characters found in deprecation template.'); + } + + if (!str_contains($message, '%alias_id%')) { + throw new InvalidArgumentException('The deprecation template must contain the "%alias_id%" placeholder.'); + } + } + + $this->deprecation = [ + 'package' => $package, + 'version' => $version, + 'message' => $message ?: 'The "%alias_id%" route alias is deprecated. You should stop using it, as it will be removed in the future.', + ]; + + return $this; + } + + public function isDeprecated(): bool + { + return (bool) $this->deprecation; + } + + /** + * @param string $name Route name relying on this alias + */ + public function getDeprecation(string $name): array + { + return [ + 'package' => $this->deprecation['package'], + 'version' => $this->deprecation['version'], + 'message' => str_replace('%alias_id%', $name, $this->deprecation['message']), + ]; + } +} diff --git a/lib/symfony/routing/Annotation/Route.php b/lib/symfony/routing/Annotation/Route.php index 42edbbcb22..81563df202 100644 --- a/lib/symfony/routing/Annotation/Route.php +++ b/lib/symfony/routing/Annotation/Route.php @@ -15,13 +15,17 @@ namespace Symfony\Component\Routing\Annotation; * Annotation class for @Route(). * * @Annotation + * @NamedArgumentConstructor * @Target({"CLASS", "METHOD"}) * * @author Fabien Potencier + * @author Alexander M. Turek */ +#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)] class Route { private $path; + private $localizedPaths = []; private $name; private $requirements = []; private $options = []; @@ -30,19 +34,113 @@ class Route private $methods = []; private $schemes = []; private $condition; + private $priority; + private $env; /** - * @param array $data An array of key/value parameters + * @param array|string $data data array managed by the Doctrine Annotations library or the path + * @param array|string|null $path + * @param string[] $requirements + * @param string[]|string $methods + * @param string[]|string $schemes * * @throws \BadMethodCallException */ - public function __construct(array $data) - { + public function __construct( + $data = [], + $path = null, + string $name = null, + array $requirements = [], + array $options = [], + array $defaults = [], + string $host = null, + $methods = [], + $schemes = [], + string $condition = null, + int $priority = null, + string $locale = null, + string $format = null, + bool $utf8 = null, + bool $stateless = null, + string $env = null + ) { + if (\is_string($data)) { + $data = ['path' => $data]; + } elseif (!\is_array($data)) { + throw new \TypeError(sprintf('"%s": Argument $data is expected to be a string or array, got "%s".', __METHOD__, get_debug_type($data))); + } elseif ([] !== $data) { + $deprecation = false; + foreach ($data as $key => $val) { + if (\in_array($key, ['path', 'name', 'requirements', 'options', 'defaults', 'host', 'methods', 'schemes', 'condition', 'priority', 'locale', 'format', 'utf8', 'stateless', 'env', 'value'])) { + $deprecation = true; + } + } + + if ($deprecation) { + trigger_deprecation('symfony/routing', '5.3', 'Passing an array as first argument to "%s" is deprecated. Use named arguments instead.', __METHOD__); + } else { + $localizedPaths = $data; + $data = ['path' => $localizedPaths]; + } + } + if (null !== $path && !\is_string($path) && !\is_array($path)) { + throw new \TypeError(sprintf('"%s": Argument $path is expected to be a string, array or null, got "%s".', __METHOD__, get_debug_type($path))); + } + + $data['path'] = $data['path'] ?? $path; + $data['name'] = $data['name'] ?? $name; + $data['requirements'] = $data['requirements'] ?? $requirements; + $data['options'] = $data['options'] ?? $options; + $data['defaults'] = $data['defaults'] ?? $defaults; + $data['host'] = $data['host'] ?? $host; + $data['methods'] = $data['methods'] ?? $methods; + $data['schemes'] = $data['schemes'] ?? $schemes; + $data['condition'] = $data['condition'] ?? $condition; + $data['priority'] = $data['priority'] ?? $priority; + $data['locale'] = $data['locale'] ?? $locale; + $data['format'] = $data['format'] ?? $format; + $data['utf8'] = $data['utf8'] ?? $utf8; + $data['stateless'] = $data['stateless'] ?? $stateless; + $data['env'] = $data['env'] ?? $env; + + $data = array_filter($data, static function ($value): bool { + return null !== $value; + }); + + if (isset($data['localized_paths'])) { + throw new \BadMethodCallException(sprintf('Unknown property "localized_paths" on annotation "%s".', static::class)); + } + if (isset($data['value'])) { - $data['path'] = $data['value']; + $data[\is_array($data['value']) ? 'localized_paths' : 'path'] = $data['value']; unset($data['value']); } + if (isset($data['path']) && \is_array($data['path'])) { + $data['localized_paths'] = $data['path']; + unset($data['path']); + } + + if (isset($data['locale'])) { + $data['defaults']['_locale'] = $data['locale']; + unset($data['locale']); + } + + if (isset($data['format'])) { + $data['defaults']['_format'] = $data['format']; + unset($data['format']); + } + + if (isset($data['utf8'])) { + $data['options']['utf8'] = filter_var($data['utf8'], \FILTER_VALIDATE_BOOLEAN) ?: false; + unset($data['utf8']); + } + + if (isset($data['stateless'])) { + $data['defaults']['_stateless'] = filter_var($data['stateless'], \FILTER_VALIDATE_BOOLEAN) ?: false; + unset($data['stateless']); + } + foreach ($data as $key => $value) { $method = 'set'.str_replace('_', '', $key); if (!method_exists($this, $method)) { @@ -52,7 +150,7 @@ class Route } } - public function setPath($path) + public function setPath(string $path) { $this->path = $path; } @@ -62,7 +160,17 @@ class Route return $this->path; } - public function setHost($pattern) + public function setLocalizedPaths(array $localizedPaths) + { + $this->localizedPaths = $localizedPaths; + } + + public function getLocalizedPaths(): array + { + return $this->localizedPaths; + } + + public function setHost(string $pattern) { $this->host = $pattern; } @@ -72,7 +180,7 @@ class Route return $this->host; } - public function setName($name) + public function setName(string $name) { $this->name = $name; } @@ -82,7 +190,7 @@ class Route return $this->name; } - public function setRequirements($requirements) + public function setRequirements(array $requirements) { $this->requirements = $requirements; } @@ -92,7 +200,7 @@ class Route return $this->requirements; } - public function setOptions($options) + public function setOptions(array $options) { $this->options = $options; } @@ -102,7 +210,7 @@ class Route return $this->options; } - public function setDefaults($defaults) + public function setDefaults(array $defaults) { $this->defaults = $defaults; } @@ -132,7 +240,7 @@ class Route return $this->methods; } - public function setCondition($condition) + public function setCondition(?string $condition) { $this->condition = $condition; } @@ -141,4 +249,24 @@ class Route { return $this->condition; } + + public function setPriority(int $priority): void + { + $this->priority = $priority; + } + + public function getPriority(): ?int + { + return $this->priority; + } + + public function setEnv(?string $env): void + { + $this->env = $env; + } + + public function getEnv(): ?string + { + return $this->env; + } } diff --git a/lib/symfony/routing/CHANGELOG.md b/lib/symfony/routing/CHANGELOG.md index 19fd7da725..b96638987b 100644 --- a/lib/symfony/routing/CHANGELOG.md +++ b/lib/symfony/routing/CHANGELOG.md @@ -1,6 +1,74 @@ CHANGELOG ========= +5.3 +--- + + * Already encoded slashes are not decoded nor double-encoded anymore when generating URLs + * Add support for per-env configuration in XML and Yaml loaders + * Deprecate creating instances of the `Route` annotation class by passing an array of parameters + * Add `RoutingConfigurator::env()` to get the current environment + +5.2.0 +----- + + * Added support for inline definition of requirements and defaults for host + * Added support for `\A` and `\z` as regex start and end for route requirement + * Added support for `#[Route]` attributes + +5.1.0 +----- + + * added the protected method `PhpFileLoader::callConfigurator()` as extension point to ease custom routing configuration + * deprecated `RouteCollectionBuilder` in favor of `RoutingConfigurator`. + * added "priority" option to annotated routes + * added argument `$priority` to `RouteCollection::add()` + * deprecated the `RouteCompiler::REGEX_DELIMITER` constant + * added `ExpressionLanguageProvider` to expose extra functions to route conditions + * added support for a `stateless` keyword for configuring route stateless in PHP, YAML and XML configurations. + * added the "hosts" option to be able to configure the host per locale. + * added `RequestContext::fromUri()` to ease building the default context + +5.0.0 +----- + + * removed `PhpGeneratorDumper` and `PhpMatcherDumper` + * removed `generator_base_class`, `generator_cache_class`, `matcher_base_class` and `matcher_cache_class` router options + * `Serializable` implementing methods for `Route` and `CompiledRoute` are final + * removed referencing service route loaders with a single colon + * Removed `ServiceRouterLoader` and `ObjectRouteLoader`. + +4.4.0 +----- + + * Deprecated `ServiceRouterLoader` in favor of `ContainerLoader`. + * Deprecated `ObjectRouteLoader` in favor of `ObjectLoader`. + * Added a way to exclude patterns of resources from being imported by the `import()` method + +4.3.0 +----- + + * added `CompiledUrlMatcher` and `CompiledUrlMatcherDumper` + * added `CompiledUrlGenerator` and `CompiledUrlGeneratorDumper` + * deprecated `PhpGeneratorDumper` and `PhpMatcherDumper` + * deprecated `generator_base_class`, `generator_cache_class`, `matcher_base_class` and `matcher_cache_class` router options + * `Serializable` implementing methods for `Route` and `CompiledRoute` are marked as `@internal` and `@final`. + Instead of overwriting them, use `__serialize` and `__unserialize` as extension points which are forward compatible + with the new serialization methods in PHP 7.4. + * exposed `utf8` Route option, defaults "locale" and "format" in configuration loaders and configurators + * added support for invokable service route loaders + +4.2.0 +----- + + * added fallback to cultureless locale for internationalized routes + +4.0.0 +----- + + * dropped support for using UTF-8 route patterns without using the `utf8` option + * dropped support for using UTF-8 route requirements without using the `utf8` option + 3.4.0 ----- @@ -13,15 +81,15 @@ CHANGELOG 3.3.0 ----- - * [DEPRECATION] Class parameters have been deprecated and will be removed in 4.0. - * router.options.generator_class - * router.options.generator_base_class - * router.options.generator_dumper_class - * router.options.matcher_class - * router.options.matcher_base_class - * router.options.matcher_dumper_class - * router.options.matcher.cache_class - * router.options.generator.cache_class + * [DEPRECATION] Class parameters have been deprecated and will be removed in 4.0. + * router.options.generator_class + * router.options.generator_base_class + * router.options.generator_dumper_class + * router.options.matcher_class + * router.options.matcher_base_class + * router.options.matcher_dumper_class + * router.options.matcher.cache_class + * router.options.generator.cache_class 3.2.0 ----- diff --git a/lib/symfony/routing/CompiledRoute.php b/lib/symfony/routing/CompiledRoute.php index 23acc5276e..1449cdb92e 100644 --- a/lib/symfony/routing/CompiledRoute.php +++ b/lib/symfony/routing/CompiledRoute.php @@ -37,9 +37,9 @@ class CompiledRoute implements \Serializable * @param array $hostVariables An array of host variables * @param array $variables An array of variables (variables defined in the path and in the host patterns) */ - public function __construct($staticPrefix, $regex, array $tokens, array $pathVariables, $hostRegex = null, array $hostTokens = [], array $hostVariables = [], array $variables = []) + public function __construct(string $staticPrefix, string $regex, array $tokens, array $pathVariables, string $hostRegex = null, array $hostTokens = [], array $hostVariables = [], array $variables = []) { - $this->staticPrefix = (string) $staticPrefix; + $this->staticPrefix = $staticPrefix; $this->regex = $regex; $this->tokens = $tokens; $this->pathVariables = $pathVariables; @@ -49,12 +49,9 @@ class CompiledRoute implements \Serializable $this->variables = $variables; } - /** - * {@inheritdoc} - */ - public function serialize() + public function __serialize(): array { - return serialize([ + return [ 'vars' => $this->variables, 'path_prefix' => $this->staticPrefix, 'path_regex' => $this->regex, @@ -63,20 +60,19 @@ class CompiledRoute implements \Serializable 'host_regex' => $this->hostRegex, 'host_tokens' => $this->hostTokens, 'host_vars' => $this->hostVariables, - ]); + ]; } /** - * {@inheritdoc} + * @internal */ - public function unserialize($serialized) + final public function serialize(): string { - if (\PHP_VERSION_ID >= 70000) { - $data = unserialize($serialized, ['allowed_classes' => false]); - } else { - $data = unserialize($serialized); - } + return serialize($this->__serialize()); + } + public function __unserialize(array $data): void + { $this->variables = $data['vars']; $this->staticPrefix = $data['path_prefix']; $this->regex = $data['path_regex']; @@ -87,10 +83,18 @@ class CompiledRoute implements \Serializable $this->hostVariables = $data['host_vars']; } + /** + * @internal + */ + final public function unserialize($serialized) + { + $this->__unserialize(unserialize($serialized, ['allowed_classes' => false])); + } + /** * Returns the static prefix. * - * @return string The static prefix + * @return string */ public function getStaticPrefix() { @@ -100,7 +104,7 @@ class CompiledRoute implements \Serializable /** * Returns the regex. * - * @return string The regex + * @return string */ public function getRegex() { @@ -110,7 +114,7 @@ class CompiledRoute implements \Serializable /** * Returns the host regex. * - * @return string|null The host regex or null + * @return string|null */ public function getHostRegex() { @@ -120,7 +124,7 @@ class CompiledRoute implements \Serializable /** * Returns the tokens. * - * @return array The tokens + * @return array */ public function getTokens() { @@ -130,7 +134,7 @@ class CompiledRoute implements \Serializable /** * Returns the host tokens. * - * @return array The tokens + * @return array */ public function getHostTokens() { @@ -140,7 +144,7 @@ class CompiledRoute implements \Serializable /** * Returns the variables. * - * @return array The variables + * @return array */ public function getVariables() { @@ -150,7 +154,7 @@ class CompiledRoute implements \Serializable /** * Returns the path variables. * - * @return array The variables + * @return array */ public function getPathVariables() { @@ -160,7 +164,7 @@ class CompiledRoute implements \Serializable /** * Returns the host variables. * - * @return array The variables + * @return array */ public function getHostVariables() { diff --git a/lib/symfony/routing/DependencyInjection/RoutingResolverPass.php b/lib/symfony/routing/DependencyInjection/RoutingResolverPass.php index 5bdbfb8214..0e9b9c8931 100644 --- a/lib/symfony/routing/DependencyInjection/RoutingResolverPass.php +++ b/lib/symfony/routing/DependencyInjection/RoutingResolverPass.php @@ -28,8 +28,12 @@ class RoutingResolverPass implements CompilerPassInterface private $resolverServiceId; private $loaderTag; - public function __construct($resolverServiceId = 'routing.resolver', $loaderTag = 'routing.loader') + public function __construct(string $resolverServiceId = 'routing.resolver', string $loaderTag = 'routing.loader') { + if (0 < \func_num_args()) { + trigger_deprecation('symfony/routing', '5.3', 'Configuring "%s" is deprecated.', __CLASS__); + } + $this->resolverServiceId = $resolverServiceId; $this->loaderTag = $loaderTag; } diff --git a/lib/symfony/routing/Exception/ExceptionInterface.php b/lib/symfony/routing/Exception/ExceptionInterface.php index db7636211f..22e72b16bd 100644 --- a/lib/symfony/routing/Exception/ExceptionInterface.php +++ b/lib/symfony/routing/Exception/ExceptionInterface.php @@ -16,6 +16,6 @@ namespace Symfony\Component\Routing\Exception; * * @author Alexandre Salomé */ -interface ExceptionInterface +interface ExceptionInterface extends \Throwable { } diff --git a/lib/symfony/routing/Exception/InvalidArgumentException.php b/lib/symfony/routing/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000..950b9b15c0 --- /dev/null +++ b/lib/symfony/routing/Exception/InvalidArgumentException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/lib/symfony/routing/Exception/MethodNotAllowedException.php b/lib/symfony/routing/Exception/MethodNotAllowedException.php index cf108354a6..27cf2125e2 100644 --- a/lib/symfony/routing/Exception/MethodNotAllowedException.php +++ b/lib/symfony/routing/Exception/MethodNotAllowedException.php @@ -22,8 +22,17 @@ class MethodNotAllowedException extends \RuntimeException implements ExceptionIn { protected $allowedMethods = []; - public function __construct(array $allowedMethods, $message = null, $code = 0, \Exception $previous = null) + /** + * @param string[] $allowedMethods + */ + public function __construct(array $allowedMethods, ?string $message = '', int $code = 0, \Throwable $previous = null) { + if (null === $message) { + trigger_deprecation('symfony/routing', '5.3', 'Passing null as $message to "%s()" is deprecated, pass an empty string instead.', __METHOD__); + + $message = ''; + } + $this->allowedMethods = array_map('strtoupper', $allowedMethods); parent::__construct($message, $code, $previous); @@ -32,7 +41,7 @@ class MethodNotAllowedException extends \RuntimeException implements ExceptionIn /** * Gets the allowed HTTP methods. * - * @return array + * @return string[] */ public function getAllowedMethods() { diff --git a/lib/symfony/routing/Exception/RouteCircularReferenceException.php b/lib/symfony/routing/Exception/RouteCircularReferenceException.php new file mode 100644 index 0000000000..841e35989c --- /dev/null +++ b/lib/symfony/routing/Exception/RouteCircularReferenceException.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 Symfony\Component\Routing\Exception; + +class RouteCircularReferenceException extends RuntimeException +{ + public function __construct(string $routeId, array $path) + { + parent::__construct(sprintf('Circular reference detected for route "%s", path: "%s".', $routeId, implode(' -> ', $path))); + } +} diff --git a/lib/symfony/routing/Exception/RuntimeException.php b/lib/symfony/routing/Exception/RuntimeException.php new file mode 100644 index 0000000000..48da62ec8b --- /dev/null +++ b/lib/symfony/routing/Exception/RuntimeException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/lib/symfony/routing/Generator/CompiledUrlGenerator.php b/lib/symfony/routing/Generator/CompiledUrlGenerator.php new file mode 100644 index 0000000000..8cbbf8f702 --- /dev/null +++ b/lib/symfony/routing/Generator/CompiledUrlGenerator.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\RequestContext; + +/** + * Generates URLs based on rules dumped by CompiledUrlGeneratorDumper. + */ +class CompiledUrlGenerator extends UrlGenerator +{ + private $compiledRoutes = []; + private $defaultLocale; + + public function __construct(array $compiledRoutes, RequestContext $context, LoggerInterface $logger = null, string $defaultLocale = null) + { + $this->compiledRoutes = $compiledRoutes; + $this->context = $context; + $this->logger = $logger; + $this->defaultLocale = $defaultLocale; + } + + public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH) + { + $locale = $parameters['_locale'] + ?? $this->context->getParameter('_locale') + ?: $this->defaultLocale; + + if (null !== $locale) { + do { + if (($this->compiledRoutes[$name.'.'.$locale][1]['_canonical_route'] ?? null) === $name) { + $name .= '.'.$locale; + break; + } + } while (false !== $locale = strstr($locale, '_', true)); + } + + if (!isset($this->compiledRoutes[$name])) { + throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); + } + + [$variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes, $deprecations] = $this->compiledRoutes[$name] + [6 => []]; + + foreach ($deprecations as $deprecation) { + trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']); + } + + if (isset($defaults['_canonical_route']) && isset($defaults['_locale'])) { + if (!\in_array('_locale', $variables, true)) { + unset($parameters['_locale']); + } elseif (!isset($parameters['_locale'])) { + $parameters['_locale'] = $defaults['_locale']; + } + } + + return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes); + } +} diff --git a/lib/symfony/routing/Generator/ConfigurableRequirementsInterface.php b/lib/symfony/routing/Generator/ConfigurableRequirementsInterface.php index dc97b7e724..568f7f7753 100644 --- a/lib/symfony/routing/Generator/ConfigurableRequirementsInterface.php +++ b/lib/symfony/routing/Generator/ConfigurableRequirementsInterface.php @@ -20,7 +20,7 @@ namespace Symfony\Component\Routing\Generator; * The possible configurations and use-cases: * - setStrictRequirements(true): Throw an exception for mismatching requirements. This * is mostly useful in development environment. - * - setStrictRequirements(false): Don't throw an exception but return null as URL for + * - setStrictRequirements(false): Don't throw an exception but return an empty string as URL for * mismatching requirements and log the problem. Useful when you cannot control all * params because they come from third party libs but don't want to have a 404 in * production environment. It should log the mismatch so one can review it. @@ -40,10 +40,8 @@ interface ConfigurableRequirementsInterface /** * Enables or disables the exception on incorrect parameters. * Passing null will deactivate the requirements check completely. - * - * @param bool|null $enabled */ - public function setStrictRequirements($enabled); + public function setStrictRequirements(?bool $enabled); /** * Returns whether to throw an exception on incorrect parameters. diff --git a/lib/symfony/routing/Generator/Dumper/CompiledUrlGeneratorDumper.php b/lib/symfony/routing/Generator/Dumper/CompiledUrlGeneratorDumper.php new file mode 100644 index 0000000000..9c6740b61e --- /dev/null +++ b/lib/symfony/routing/Generator/Dumper/CompiledUrlGeneratorDumper.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 Symfony\Component\Routing\Generator\Dumper; + +use Symfony\Component\Routing\Exception\RouteCircularReferenceException; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper; + +/** + * CompiledUrlGeneratorDumper creates a PHP array to be used with CompiledUrlGenerator. + * + * @author Fabien Potencier + * @author Tobias Schultze + * @author Nicolas Grekas + */ +class CompiledUrlGeneratorDumper extends GeneratorDumper +{ + public function getCompiledRoutes(): array + { + $compiledRoutes = []; + foreach ($this->getRoutes()->all() as $name => $route) { + $compiledRoute = $route->compile(); + + $compiledRoutes[$name] = [ + $compiledRoute->getVariables(), + $route->getDefaults(), + $route->getRequirements(), + $compiledRoute->getTokens(), + $compiledRoute->getHostTokens(), + $route->getSchemes(), + [], + ]; + } + + return $compiledRoutes; + } + + public function getCompiledAliases(): array + { + $routes = $this->getRoutes(); + $compiledAliases = []; + foreach ($routes->getAliases() as $name => $alias) { + $deprecations = $alias->isDeprecated() ? [$alias->getDeprecation($name)] : []; + $currentId = $alias->getId(); + $visited = []; + while (null !== $alias = $routes->getAlias($currentId) ?? null) { + if (false !== $searchKey = array_search($currentId, $visited)) { + $visited[] = $currentId; + + throw new RouteCircularReferenceException($currentId, \array_slice($visited, $searchKey)); + } + + if ($alias->isDeprecated()) { + $deprecations[] = $deprecation = $alias->getDeprecation($currentId); + trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']); + } + + $visited[] = $currentId; + $currentId = $alias->getId(); + } + + if (null === $target = $routes->get($currentId)) { + throw new RouteNotFoundException(sprintf('Target route "%s" for alias "%s" does not exist.', $currentId, $name)); + } + + $compiledTarget = $target->compile(); + + $compiledAliases[$name] = [ + $compiledTarget->getVariables(), + $target->getDefaults(), + $target->getRequirements(), + $compiledTarget->getTokens(), + $compiledTarget->getHostTokens(), + $target->getSchemes(), + $deprecations, + ]; + } + + return $compiledAliases; + } + + /** + * {@inheritdoc} + */ + public function dump(array $options = []) + { + return <<generateDeclaredRoutes()} +]; + +EOF; + } + + /** + * Generates PHP code representing an array of defined routes + * together with the routes properties (e.g. requirements). + */ + private function generateDeclaredRoutes(): string + { + $routes = ''; + foreach ($this->getCompiledRoutes() as $name => $properties) { + $routes .= sprintf("\n '%s' => %s,", $name, CompiledUrlMatcherDumper::export($properties)); + } + + foreach ($this->getCompiledAliases() as $alias => $properties) { + $routes .= sprintf("\n '%s' => %s,", $alias, CompiledUrlMatcherDumper::export($properties)); + } + + return $routes; + } +} diff --git a/lib/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php b/lib/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php index 096519aa1a..d4a248a5be 100644 --- a/lib/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php +++ b/lib/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php @@ -24,16 +24,14 @@ interface GeneratorDumperInterface * Dumps a set of routes to a string representation of executable code * that can then be used to generate a URL of such a route. * - * @param array $options An array of options - * - * @return string Executable code + * @return string */ public function dump(array $options = []); /** * Gets the routes to dump. * - * @return RouteCollection A RouteCollection instance + * @return RouteCollection */ public function getRoutes(); } diff --git a/lib/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php b/lib/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php deleted file mode 100644 index 2e99c81d9c..0000000000 --- a/lib/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php +++ /dev/null @@ -1,118 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Routing\Generator\Dumper; - -/** - * PhpGeneratorDumper creates a PHP class able to generate URLs for a given set of routes. - * - * @author Fabien Potencier - * @author Tobias Schultze - */ -class PhpGeneratorDumper extends GeneratorDumper -{ - /** - * Dumps a set of routes to a PHP class. - * - * Available options: - * - * * class: The class name - * * base_class: The base class name - * - * @param array $options An array of options - * - * @return string A PHP class representing the generator class - */ - public function dump(array $options = []) - { - $options = array_merge([ - 'class' => 'ProjectUrlGenerator', - 'base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', - ], $options); - - return <<context = \$context; - \$this->logger = \$logger; - if (null === self::\$declaredRoutes) { - self::\$declaredRoutes = {$this->generateDeclaredRoutes()}; - } - } - -{$this->generateGenerateMethod()} -} - -EOF; - } - - /** - * Generates PHP code representing an array of defined routes - * together with the routes properties (e.g. requirements). - * - * @return string PHP code - */ - private function generateDeclaredRoutes() - { - $routes = "[\n"; - foreach ($this->getRoutes()->all() as $name => $route) { - $compiledRoute = $route->compile(); - - $properties = []; - $properties[] = $compiledRoute->getVariables(); - $properties[] = $route->getDefaults(); - $properties[] = $route->getRequirements(); - $properties[] = $compiledRoute->getTokens(); - $properties[] = $compiledRoute->getHostTokens(); - $properties[] = $route->getSchemes(); - - $routes .= sprintf(" '%s' => %s,\n", $name, str_replace("\n", '', var_export($properties, true))); - } - $routes .= ' ]'; - - return $routes; - } - - /** - * Generates PHP code representing the `generate` method that implements the UrlGeneratorInterface. - * - * @return string PHP code - */ - private function generateGenerateMethod() - { - return <<<'EOF' - public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH) - { - if (!isset(self::$declaredRoutes[$name])) { - throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); - } - - list($variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes) = self::$declaredRoutes[$name]; - - return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes); - } -EOF; - } -} diff --git a/lib/symfony/routing/Generator/UrlGenerator.php b/lib/symfony/routing/Generator/UrlGenerator.php index 89893140af..acf3ead4fc 100644 --- a/lib/symfony/routing/Generator/UrlGenerator.php +++ b/lib/symfony/routing/Generator/UrlGenerator.php @@ -27,6 +27,20 @@ use Symfony\Component\Routing\RouteCollection; */ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInterface { + private const QUERY_FRAGMENT_DECODED = [ + // RFC 3986 explicitly allows those in the query/fragment to reference other URIs unencoded + '%2F' => '/', + '%3F' => '?', + // reserved chars that have no special meaning for HTTP URIs in a query or fragment + // this excludes esp. "&", "=" and also "+" because PHP would treat it as a space (form-encoded) + '%40' => '@', + '%3A' => ':', + '%21' => '!', + '%3B' => ';', + '%2C' => ',', + '%2A' => '*', + ]; + protected $routes; protected $context; @@ -37,6 +51,8 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt protected $logger; + private $defaultLocale; + /** * This array defines the characters (besides alphanumeric ones) that will not be percent-encoded in the path segment of the generated URL. * @@ -50,6 +66,7 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt // some webservers don't allow the slash in encoded form in the path for security reasons anyway // see http://stackoverflow.com/questions/4069002/http-400-if-2f-part-of-get-url-in-jboss '%2F' => '/', + '%252F' => '%2F', // the following chars are general delimiters in the URI specification but have only special meaning in the authority component // so they can safely be used in the path in unencoded form '%40' => '@', @@ -65,11 +82,12 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt '%7C' => '|', ]; - public function __construct(RouteCollection $routes, RequestContext $context, LoggerInterface $logger = null) + public function __construct(RouteCollection $routes, RequestContext $context, LoggerInterface $logger = null, string $defaultLocale = null) { $this->routes = $routes; $this->context = $context; $this->logger = $logger; + $this->defaultLocale = $defaultLocale; } /** @@ -91,9 +109,9 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt /** * {@inheritdoc} */ - public function setStrictRequirements($enabled) + public function setStrictRequirements(?bool $enabled) { - $this->strictRequirements = null === $enabled ? null : (bool) $enabled; + $this->strictRequirements = $enabled; } /** @@ -107,16 +125,40 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt /** * {@inheritdoc} */ - public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH) + public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH) { - if (null === $route = $this->routes->get($name)) { + $route = null; + $locale = $parameters['_locale'] + ?? $this->context->getParameter('_locale') + ?: $this->defaultLocale; + + if (null !== $locale) { + do { + if (null !== ($route = $this->routes->get($name.'.'.$locale)) && $route->getDefault('_canonical_route') === $name) { + break; + } + } while (false !== $locale = strstr($locale, '_', true)); + } + + if (null === $route = $route ?? $this->routes->get($name)) { throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); } // the Route has a cache of its own and is not recompiled as long as it does not get modified $compiledRoute = $route->compile(); - return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostTokens(), $route->getSchemes()); + $defaults = $route->getDefaults(); + $variables = $compiledRoute->getVariables(); + + if (isset($defaults['_canonical_route']) && isset($defaults['_locale'])) { + if (!\in_array('_locale', $variables, true)) { + unset($parameters['_locale']); + } elseif (!isset($parameters['_locale'])) { + $parameters['_locale'] = $defaults['_locale']; + } + } + + return $this->doGenerate($variables, $defaults, $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostTokens(), $route->getSchemes()); } /** @@ -124,9 +166,9 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt * @throws InvalidParameterException When a parameter value for a placeholder is not correct because * it does not match the requirement * - * @return string|null + * @return string */ - protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, array $requiredSchemes = []) + protected function doGenerate(array $variables, array $defaults, array $requirements, array $tokens, array $parameters, string $name, int $referenceType, array $hostTokens, array $requiredSchemes = []) { $variables = array_flip($variables); $mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters); @@ -141,21 +183,25 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt $message = 'Parameter "{parameter}" for route "{route}" must match "{expected}" ("{given}" given) to generate a corresponding URL.'; foreach ($tokens as $token) { if ('variable' === $token[0]) { - if (!$optional || !\array_key_exists($token[3], $defaults) || null !== $mergedParams[$token[3]] && (string) $mergedParams[$token[3]] !== (string) $defaults[$token[3]]) { + $varName = $token[3]; + // variable is not important by default + $important = $token[5] ?? false; + + if (!$optional || $important || !\array_key_exists($varName, $defaults) || (null !== $mergedParams[$varName] && (string) $mergedParams[$varName] !== (string) $defaults[$varName])) { // check requirement (while ignoring look-around patterns) - if (null !== $this->strictRequirements && !preg_match('#^'.preg_replace('/\(\?(?:=|<=|!|strictRequirements && !preg_match('#^'.preg_replace('/\(\?(?:=|<=|!|strictRequirements) { - throw new InvalidParameterException(strtr($message, ['{parameter}' => $token[3], '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$token[3]]])); + throw new InvalidParameterException(strtr($message, ['{parameter}' => $varName, '{route}' => $name, '{expected}' => $token[2], '{given}' => $mergedParams[$varName]])); } if ($this->logger) { - $this->logger->error($message, ['parameter' => $token[3], 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$token[3]]]); + $this->logger->error($message, ['parameter' => $varName, 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$varName]]); } - return null; + return ''; } - $url = $token[1].$mergedParams[$token[3]].$url; + $url = $token[1].$mergedParams[$varName].$url; $optional = false; } } else { @@ -176,9 +222,9 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt // so we need to encode them as they are not used for this purpose here // otherwise we would generate a URI that, when followed by a user agent (e.g. browser), does not match this route $url = strtr($url, ['/../' => '/%2E%2E/', '/./' => '/%2E/']); - if ('/..' === substr($url, -3)) { + if (str_ends_with($url, '/..')) { $url = substr($url, 0, -2).'%2E%2E'; - } elseif ('/.' === substr($url, -2)) { + } elseif (str_ends_with($url, '/.')) { $url = substr($url, 0, -1).'%2E'; } @@ -207,7 +253,7 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt $this->logger->error($message, ['parameter' => $token[3], 'route' => $name, 'expected' => $token[2], 'given' => $mergedParams[$token[3]]]); } - return null; + return ''; } $routeHost = $token[1].$mergedParams[$token[3]].$routeHost; @@ -249,11 +295,19 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt return $a == $b ? 0 : 1; }); + array_walk_recursive($extra, $caster = static function (&$v) use (&$caster) { + if (\is_object($v)) { + if ($vars = get_object_vars($v)) { + array_walk_recursive($vars, $caster); + $v = $vars; + } elseif (method_exists($v, '__toString')) { + $v = (string) $v; + } + } + }); + // extract fragment - $fragment = ''; - if (isset($defaults['_fragment'])) { - $fragment = $defaults['_fragment']; - } + $fragment = $defaults['_fragment'] ?? ''; if (isset($extra['_fragment'])) { $fragment = $extra['_fragment']; @@ -261,13 +315,11 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt } if ($extra && $query = http_build_query($extra, '', '&', \PHP_QUERY_RFC3986)) { - // "/" and "?" can be left decoded for better user experience, see - // http://tools.ietf.org/html/rfc3986#section-3.4 - $url .= '?'.strtr($query, ['%2F' => '/']); + $url .= '?'.strtr($query, self::QUERY_FRAGMENT_DECODED); } if ('' !== $fragment) { - $url .= '#'.strtr(rawurlencode($fragment), ['%2F' => '/', '%3F' => '?']); + $url .= '#'.strtr(rawurlencode($fragment), self::QUERY_FRAGMENT_DECODED); } return $url; @@ -291,9 +343,9 @@ class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInt * @param string $basePath The base path * @param string $targetPath The target path * - * @return string The relative target path + * @return string */ - public static function getRelativePath($basePath, $targetPath) + public static function getRelativePath(string $basePath, string $targetPath) { if ($basePath === $targetPath) { return ''; diff --git a/lib/symfony/routing/Generator/UrlGeneratorInterface.php b/lib/symfony/routing/Generator/UrlGeneratorInterface.php index 64714d354d..c6d5005f9a 100644 --- a/lib/symfony/routing/Generator/UrlGeneratorInterface.php +++ b/lib/symfony/routing/Generator/UrlGeneratorInterface.php @@ -34,25 +34,25 @@ interface UrlGeneratorInterface extends RequestContextAwareInterface /** * Generates an absolute URL, e.g. "http://example.com/dir/file". */ - const ABSOLUTE_URL = 0; + public const ABSOLUTE_URL = 0; /** * Generates an absolute path, e.g. "/dir/file". */ - const ABSOLUTE_PATH = 1; + public const ABSOLUTE_PATH = 1; /** * Generates a relative path based on the current request path, e.g. "../parent-file". * * @see UrlGenerator::getRelativePath() */ - const RELATIVE_PATH = 2; + public const RELATIVE_PATH = 2; /** * Generates a network path, e.g. "//example.com/dir/file". * Such reference reuses the current scheme but specifies the host. */ - const NETWORK_PATH = 3; + public const NETWORK_PATH = 3; /** * Generates a URL or path for a specific route based on the given parameters. @@ -71,16 +71,12 @@ interface UrlGeneratorInterface extends RequestContextAwareInterface * * The special parameter _fragment will be used as the document fragment suffixed to the final URL. * - * @param string $name The name of the route - * @param mixed[] $parameters An array of parameters - * @param int $referenceType The type of reference to be generated (one of the constants) - * - * @return string The generated URL + * @return string * * @throws RouteNotFoundException If the named route doesn't exist * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route * @throws InvalidParameterException When a parameter value for a placeholder is not correct because * it does not match the requirement */ - public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH); + public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH); } diff --git a/lib/symfony/routing/LICENSE b/lib/symfony/routing/LICENSE index 9e936ec044..88bf75bb4d 100644 --- a/lib/symfony/routing/LICENSE +++ b/lib/symfony/routing/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2020 Fabien Potencier +Copyright (c) 2004-2022 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/lib/symfony/routing/Loader/AnnotationClassLoader.php b/lib/symfony/routing/Loader/AnnotationClassLoader.php index 10bddfbc94..ad5af5c942 100644 --- a/lib/symfony/routing/Loader/AnnotationClassLoader.php +++ b/lib/symfony/routing/Loader/AnnotationClassLoader.php @@ -51,34 +51,50 @@ use Symfony\Component\Routing\RouteCollection; * { * } * } + * + * On PHP 8, the annotation class can be used as an attribute as well: + * #[Route('/Blog')] + * class Blog + * { + * #[Route('/', name: 'blog_index')] + * public function index() + * { + * } + * #[Route('/{id}', name: 'blog_post', requirements: ["id" => '\d+'])] + * public function show() + * { + * } + * } + * * @author Fabien Potencier + * @author Alexander M. Turek */ abstract class AnnotationClassLoader implements LoaderInterface { protected $reader; + protected $env; /** * @var string */ - protected $routeAnnotationClass = 'Symfony\\Component\\Routing\\Annotation\\Route'; + protected $routeAnnotationClass = RouteAnnotation::class; /** * @var int */ protected $defaultRouteIndex = 0; - public function __construct(Reader $reader) + public function __construct(Reader $reader = null, string $env = null) { $this->reader = $reader; + $this->env = $env; } /** * Sets the annotation class to read route properties from. - * - * @param string $class A fully-qualified class name */ - public function setRouteAnnotationClass($class) + public function setRouteAnnotationClass(string $class) { $this->routeAnnotationClass = $class; } @@ -86,14 +102,13 @@ abstract class AnnotationClassLoader implements LoaderInterface /** * Loads from annotations from a class. * - * @param string $class A class name - * @param string|null $type The resource type + * @param string $class A class name * - * @return RouteCollection A RouteCollection instance + * @return RouteCollection * * @throws \InvalidArgumentException When route can't be parsed */ - public function load($class, $type = null) + public function load($class, string $type = null) { if (!class_exists($class)) { throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); @@ -109,21 +124,21 @@ abstract class AnnotationClassLoader implements LoaderInterface $collection = new RouteCollection(); $collection->addResource(new FileResource($class->getFileName())); + if ($globals['env'] && $this->env !== $globals['env']) { + return $collection; + } + foreach ($class->getMethods() as $method) { $this->defaultRouteIndex = 0; - foreach ($this->reader->getMethodAnnotations($method) as $annot) { - if ($annot instanceof $this->routeAnnotationClass) { - $this->addRoute($collection, $annot, $globals, $class, $method); - } + foreach ($this->getAnnotations($method) as $annot) { + $this->addRoute($collection, $annot, $globals, $class, $method); } } if (0 === $collection->count() && $class->hasMethod('__invoke')) { $globals = $this->resetGlobals(); - foreach ($this->reader->getClassAnnotations($class) as $annot) { - if ($annot instanceof $this->routeAnnotationClass) { - $this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke')); - } + foreach ($this->getAnnotations($class) as $annot) { + $this->addRoute($collection, $annot, $globals, $class, $class->getMethod('__invoke')); } } @@ -131,24 +146,30 @@ abstract class AnnotationClassLoader implements LoaderInterface } /** - * @param RouteAnnotation $annot or an object that exposes a similar interface - * @param array $globals + * @param RouteAnnotation $annot or an object that exposes a similar interface */ - protected function addRoute(RouteCollection $collection, $annot, $globals, \ReflectionClass $class, \ReflectionMethod $method) + protected function addRoute(RouteCollection $collection, object $annot, array $globals, \ReflectionClass $class, \ReflectionMethod $method) { + if ($annot->getEnv() && $annot->getEnv() !== $this->env) { + return; + } + $name = $annot->getName(); if (null === $name) { $name = $this->getDefaultRouteName($class, $method); } $name = $globals['name'].$name; - $defaults = array_replace($globals['defaults'], $annot->getDefaults()); - foreach ($method->getParameters() as $param) { - if (false !== strpos($globals['path'].$annot->getPath(), sprintf('{%s}', $param->getName())) && !isset($defaults[$param->getName()]) && $param->isDefaultValueAvailable()) { - $defaults[$param->getName()] = $param->getDefaultValue(); + $requirements = $annot->getRequirements(); + + foreach ($requirements as $placeholder => $requirement) { + if (\is_int($placeholder)) { + throw new \InvalidArgumentException(sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" of route "%s" in "%s::%s()"?', $placeholder, $requirement, $name, $class->getName(), $method->getName())); } } - $requirements = array_replace($globals['requirements'], $annot->getRequirements()); + + $defaults = array_replace($globals['defaults'], $annot->getDefaults()); + $requirements = array_replace($globals['requirements'], $requirements); $options = array_replace($globals['options'], $annot->getOptions()); $schemes = array_merge($globals['schemes'], $annot->getSchemes()); $methods = array_merge($globals['methods'], $annot->getMethods()); @@ -158,22 +179,67 @@ abstract class AnnotationClassLoader implements LoaderInterface $host = $globals['host']; } - $condition = $annot->getCondition(); - if (null === $condition) { - $condition = $globals['condition']; + $condition = $annot->getCondition() ?? $globals['condition']; + $priority = $annot->getPriority() ?? $globals['priority']; + + $path = $annot->getLocalizedPaths() ?: $annot->getPath(); + $prefix = $globals['localized_paths'] ?: $globals['path']; + $paths = []; + + if (\is_array($path)) { + if (!\is_array($prefix)) { + foreach ($path as $locale => $localePath) { + $paths[$locale] = $prefix.$localePath; + } + } elseif ($missing = array_diff_key($prefix, $path)) { + throw new \LogicException(sprintf('Route to "%s" is missing paths for locale(s) "%s".', $class->name.'::'.$method->name, implode('", "', array_keys($missing)))); + } else { + foreach ($path as $locale => $localePath) { + if (!isset($prefix[$locale])) { + throw new \LogicException(sprintf('Route to "%s" with locale "%s" is missing a corresponding prefix in class "%s".', $method->name, $locale, $class->name)); + } + + $paths[$locale] = $prefix[$locale].$localePath; + } + } + } elseif (\is_array($prefix)) { + foreach ($prefix as $locale => $localePrefix) { + $paths[$locale] = $localePrefix.$path; + } + } else { + $paths[] = $prefix.$path; } - $route = $this->createRoute($globals['path'].$annot->getPath(), $defaults, $requirements, $options, $host, $schemes, $methods, $condition); + foreach ($method->getParameters() as $param) { + if (isset($defaults[$param->name]) || !$param->isDefaultValueAvailable()) { + continue; + } + foreach ($paths as $locale => $path) { + if (preg_match(sprintf('/\{%s(?:<.*?>)?\}/', preg_quote($param->name)), $path)) { + $defaults[$param->name] = $param->getDefaultValue(); + break; + } + } + } - $this->configureRoute($route, $class, $method, $annot); - - $collection->add($name, $route); + foreach ($paths as $locale => $path) { + $route = $this->createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition); + $this->configureRoute($route, $class, $method, $annot); + if (0 !== $locale) { + $route->setDefault('_locale', $locale); + $route->setRequirement('_locale', preg_quote($locale)); + $route->setDefault('_canonical_route', $name); + $collection->add($name.'.'.$locale, $route, $priority); + } else { + $collection->add($name, $route, $priority); + } + } } /** * {@inheritdoc} */ - public function supports($resource, $type = null) + public function supports($resource, string $type = null) { return \is_string($resource) && preg_match('/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/', $resource) && (!$type || 'annotation' === $type); } @@ -213,7 +279,15 @@ abstract class AnnotationClassLoader implements LoaderInterface { $globals = $this->resetGlobals(); - if ($annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) { + $annot = null; + if (\PHP_VERSION_ID >= 80000 && ($attribute = $class->getAttributes($this->routeAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null)) { + $annot = $attribute->newInstance(); + } + if (!$annot && $this->reader) { + $annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass); + } + + if ($annot) { if (null !== $annot->getName()) { $globals['name'] = $annot->getName(); } @@ -222,6 +296,8 @@ abstract class AnnotationClassLoader implements LoaderInterface $globals['path'] = $annot->getPath(); } + $globals['localized_paths'] = $annot->getLocalizedPaths(); + if (null !== $annot->getRequirements()) { $globals['requirements'] = $annot->getRequirements(); } @@ -249,15 +325,25 @@ abstract class AnnotationClassLoader implements LoaderInterface if (null !== $annot->getCondition()) { $globals['condition'] = $annot->getCondition(); } + + $globals['priority'] = $annot->getPriority() ?? 0; + $globals['env'] = $annot->getEnv(); + + foreach ($globals['requirements'] as $placeholder => $requirement) { + if (\is_int($placeholder)) { + throw new \InvalidArgumentException(sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" in "%s"?', $placeholder, $requirement, $class->getName())); + } + } } return $globals; } - private function resetGlobals() + private function resetGlobals(): array { return [ - 'path' => '', + 'path' => null, + 'localized_paths' => [], 'requirements' => [], 'options' => [], 'defaults' => [], @@ -266,13 +352,43 @@ abstract class AnnotationClassLoader implements LoaderInterface 'host' => '', 'condition' => '', 'name' => '', + 'priority' => 0, + 'env' => null, ]; } - protected function createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition) + protected function createRoute(string $path, array $defaults, array $requirements, array $options, ?string $host, array $schemes, array $methods, ?string $condition) { return new Route($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition); } - abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot); + abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot); + + /** + * @param \ReflectionClass|\ReflectionMethod $reflection + * + * @return iterable + */ + private function getAnnotations(object $reflection): iterable + { + if (\PHP_VERSION_ID >= 80000) { + foreach ($reflection->getAttributes($this->routeAnnotationClass, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { + yield $attribute->newInstance(); + } + } + + if (!$this->reader) { + return; + } + + $anntotations = $reflection instanceof \ReflectionClass + ? $this->reader->getClassAnnotations($reflection) + : $this->reader->getMethodAnnotations($reflection); + + foreach ($anntotations as $annotation) { + if ($annotation instanceof $this->routeAnnotationClass) { + yield $annotation; + } + } + } } diff --git a/lib/symfony/routing/Loader/AnnotationDirectoryLoader.php b/lib/symfony/routing/Loader/AnnotationDirectoryLoader.php index 3fb70ea20b..ae825a39f7 100644 --- a/lib/symfony/routing/Loader/AnnotationDirectoryLoader.php +++ b/lib/symfony/routing/Loader/AnnotationDirectoryLoader.php @@ -28,11 +28,11 @@ class AnnotationDirectoryLoader extends AnnotationFileLoader * @param string $path A directory path * @param string|null $type The resource type * - * @return RouteCollection A RouteCollection instance + * @return RouteCollection * * @throws \InvalidArgumentException When the directory does not exist or its routes cannot be parsed */ - public function load($path, $type = null) + public function load($path, string $type = null) { if (!is_dir($dir = $this->locator->locate($path))) { return parent::supports($path, $type) ? parent::load($path, $type) : new RouteCollection(); @@ -54,7 +54,7 @@ class AnnotationDirectoryLoader extends AnnotationFileLoader }); foreach ($files as $file) { - if (!$file->isFile() || '.php' !== substr($file->getFilename(), -4)) { + if (!$file->isFile() || !str_ends_with($file->getFilename(), '.php')) { continue; } @@ -74,7 +74,7 @@ class AnnotationDirectoryLoader extends AnnotationFileLoader /** * {@inheritdoc} */ - public function supports($resource, $type = null) + public function supports($resource, string $type = null) { if ('annotation' === $type) { return true; diff --git a/lib/symfony/routing/Loader/AnnotationFileLoader.php b/lib/symfony/routing/Loader/AnnotationFileLoader.php index 33d310503d..27af66ee69 100644 --- a/lib/symfony/routing/Loader/AnnotationFileLoader.php +++ b/lib/symfony/routing/Loader/AnnotationFileLoader.php @@ -26,13 +26,10 @@ class AnnotationFileLoader extends FileLoader { protected $loader; - /** - * @throws \RuntimeException - */ public function __construct(FileLocatorInterface $locator, AnnotationClassLoader $loader) { if (!\function_exists('token_get_all')) { - throw new \RuntimeException('The Tokenizer extension is required for the routing annotation loaders.'); + throw new \LogicException('The Tokenizer extension is required for the routing annotation loaders.'); } parent::__construct($locator); @@ -46,11 +43,11 @@ class AnnotationFileLoader extends FileLoader * @param string $file A PHP file path * @param string|null $type The resource type * - * @return RouteCollection|null A RouteCollection instance + * @return RouteCollection|null * * @throws \InvalidArgumentException When the file does not exist or its routes cannot be parsed */ - public function load($file, $type = null) + public function load($file, string $type = null) { $path = $this->locator->locate($file); @@ -64,10 +61,8 @@ class AnnotationFileLoader extends FileLoader $collection->addResource(new FileResource($path)); $collection->addCollection($this->loader->load($class, $type)); } - if (\PHP_VERSION_ID >= 70000) { - // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098 - gc_mem_caches(); - } + + gc_mem_caches(); return $collection; } @@ -75,7 +70,7 @@ class AnnotationFileLoader extends FileLoader /** * {@inheritdoc} */ - public function supports($resource, $type = null) + public function supports($resource, string $type = null) { return \is_string($resource) && 'php' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'annotation' === $type); } @@ -83,11 +78,9 @@ class AnnotationFileLoader extends FileLoader /** * Returns the full class name for the first class in the file. * - * @param string $file A PHP file path - * - * @return string|false Full class name if found, false otherwise + * @return string|false */ - protected function findClass($file) + protected function findClass(string $file) { $class = false; $namespace = false; @@ -99,12 +92,10 @@ class AnnotationFileLoader extends FileLoader $nsTokens = [\T_NS_SEPARATOR => true, \T_STRING => true]; if (\defined('T_NAME_QUALIFIED')) { - $nsTokens[T_NAME_QUALIFIED] = true; + $nsTokens[\T_NAME_QUALIFIED] = true; } - for ($i = 0; isset($tokens[$i]); ++$i) { $token = $tokens[$i]; - if (!isset($token[1])) { continue; } @@ -126,6 +117,9 @@ class AnnotationFileLoader extends FileLoader $skipClassToken = false; for ($j = $i - 1; $j > 0; --$j) { if (!isset($tokens[$j][1])) { + if ('(' === $tokens[$j] || ',' === $tokens[$j]) { + $skipClassToken = true; + } break; } diff --git a/lib/symfony/routing/Loader/ClosureLoader.php b/lib/symfony/routing/Loader/ClosureLoader.php index 5df9f6ae8f..42f950f50f 100644 --- a/lib/symfony/routing/Loader/ClosureLoader.php +++ b/lib/symfony/routing/Loader/ClosureLoader.php @@ -29,17 +29,17 @@ class ClosureLoader extends Loader * @param \Closure $closure A Closure * @param string|null $type The resource type * - * @return RouteCollection A RouteCollection instance + * @return RouteCollection */ - public function load($closure, $type = null) + public function load($closure, string $type = null) { - return $closure(); + return $closure($this->env); } /** * {@inheritdoc} */ - public function supports($resource, $type = null) + public function supports($resource, string $type = null) { return $resource instanceof \Closure && (!$type || 'closure' === $type); } diff --git a/lib/symfony/routing/Loader/Configurator/AliasConfigurator.php b/lib/symfony/routing/Loader/Configurator/AliasConfigurator.php new file mode 100644 index 0000000000..4b2206e68e --- /dev/null +++ b/lib/symfony/routing/Loader/Configurator/AliasConfigurator.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 Symfony\Component\Routing\Loader\Configurator; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\Routing\Alias; + +class AliasConfigurator +{ + private $alias; + + public function __construct(Alias $alias) + { + $this->alias = $alias; + } + + /** + * Whether this alias is deprecated, that means it should not be called anymore. + * + * @param string $package The name of the composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The deprecation message to use + * + * @return $this + * + * @throws InvalidArgumentException when the message template is invalid + */ + public function deprecate(string $package, string $version, string $message): self + { + $this->alias->setDeprecated($package, $version, $message); + + return $this; + } +} diff --git a/lib/symfony/routing/Loader/Configurator/CollectionConfigurator.php b/lib/symfony/routing/Loader/Configurator/CollectionConfigurator.php index 8baefdd592..09274ccdc7 100644 --- a/lib/symfony/routing/Loader/Configurator/CollectionConfigurator.php +++ b/lib/symfony/routing/Loader/Configurator/CollectionConfigurator.php @@ -20,62 +20,106 @@ use Symfony\Component\Routing\RouteCollection; class CollectionConfigurator { use Traits\AddTrait; + use Traits\HostTrait; use Traits\RouteTrait; private $parent; private $parentConfigurator; + private $parentPrefixes; + private $host; - public function __construct(RouteCollection $parent, $name, self $parentConfigurator = null) + public function __construct(RouteCollection $parent, string $name, self $parentConfigurator = null, array $parentPrefixes = null) { $this->parent = $parent; $this->name = $name; $this->collection = new RouteCollection(); $this->route = new Route(''); $this->parentConfigurator = $parentConfigurator; // for GC control + $this->parentPrefixes = $parentPrefixes; + } + + /** + * @return array + */ + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); } public function __destruct() { - $this->collection->addPrefix(rtrim($this->route->getPath(), '/')); + if (null === $this->prefixes) { + $this->collection->addPrefix($this->route->getPath()); + } + if (null !== $this->host) { + $this->addHost($this->collection, $this->host); + } + $this->parent->addCollection($this->collection); } - /** - * Adds a route. - * - * @param string $name - * @param string $path - * - * @return RouteConfigurator - */ - final public function add($name, $path) - { - $this->collection->add($this->name.$name, $route = clone $this->route); - - return new RouteConfigurator($this->collection, $route->setPath($path), $this->name, $this); - } - /** * Creates a sub-collection. - * - * @return self */ - final public function collection($name = '') + final public function collection(string $name = ''): self { - return new self($this->collection, $this->name.$name, $this); + return new self($this->collection, $this->name.$name, $this, $this->prefixes); } /** * Sets the prefix to add to the path of all child routes. * - * @param string $prefix + * @param string|array $prefix the prefix, or the localized prefixes * * @return $this */ - final public function prefix($prefix) + final public function prefix($prefix): self { - $this->route->setPath($prefix); + if (\is_array($prefix)) { + if (null === $this->parentPrefixes) { + // no-op + } elseif ($missing = array_diff_key($this->parentPrefixes, $prefix)) { + throw new \LogicException(sprintf('Collection "%s" is missing prefixes for locale(s) "%s".', $this->name, implode('", "', array_keys($missing)))); + } else { + foreach ($prefix as $locale => $localePrefix) { + if (!isset($this->parentPrefixes[$locale])) { + throw new \LogicException(sprintf('Collection "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $this->name, $locale)); + } + + $prefix[$locale] = $this->parentPrefixes[$locale].$localePrefix; + } + } + $this->prefixes = $prefix; + $this->route->setPath('/'); + } else { + $this->prefixes = null; + $this->route->setPath($prefix); + } return $this; } + + /** + * Sets the host to use for all child routes. + * + * @param string|array $host the host, or the localized hosts + * + * @return $this + */ + final public function host($host): self + { + $this->host = $host; + + return $this; + } + + private function createRoute(string $path): Route + { + return (clone $this->route)->setPath($path); + } } diff --git a/lib/symfony/routing/Loader/Configurator/ImportConfigurator.php b/lib/symfony/routing/Loader/Configurator/ImportConfigurator.php index d0a3c373ff..32f3efe3a9 100644 --- a/lib/symfony/routing/Loader/Configurator/ImportConfigurator.php +++ b/lib/symfony/routing/Loader/Configurator/ImportConfigurator.php @@ -18,6 +18,8 @@ use Symfony\Component\Routing\RouteCollection; */ class ImportConfigurator { + use Traits\HostTrait; + use Traits\PrefixTrait; use Traits\RouteTrait; private $parent; @@ -28,6 +30,19 @@ class ImportConfigurator $this->route = $route; } + /** + * @return array + */ + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + public function __destruct() { $this->parent->addCollection($this->route); @@ -36,13 +51,39 @@ class ImportConfigurator /** * Sets the prefix to add to the path of all child routes. * - * @param string $prefix + * @param string|array $prefix the prefix, or the localized prefixes * * @return $this */ - final public function prefix($prefix) + final public function prefix($prefix, bool $trailingSlashOnRoot = true): self { - $this->route->addPrefix($prefix); + $this->addPrefix($this->route, $prefix, $trailingSlashOnRoot); + + return $this; + } + + /** + * Sets the prefix to add to the name of all child routes. + * + * @return $this + */ + final public function namePrefix(string $namePrefix): self + { + $this->route->addNamePrefix($namePrefix); + + return $this; + } + + /** + * Sets the host to use for all child routes. + * + * @param string|array $host the host, or the localized hosts + * + * @return $this + */ + final public function host($host): self + { + $this->addHost($this->route, $host); return $this; } diff --git a/lib/symfony/routing/Loader/Configurator/RouteConfigurator.php b/lib/symfony/routing/Loader/Configurator/RouteConfigurator.php index 6422bbf676..bb6ce267a7 100644 --- a/lib/symfony/routing/Loader/Configurator/RouteConfigurator.php +++ b/lib/symfony/routing/Loader/Configurator/RouteConfigurator.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Routing\Loader\Configurator; -use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; /** @@ -20,15 +19,31 @@ use Symfony\Component\Routing\RouteCollection; class RouteConfigurator { use Traits\AddTrait; + use Traits\HostTrait; use Traits\RouteTrait; - private $parentConfigurator; + protected $parentConfigurator; - public function __construct(RouteCollection $collection, Route $route, $name = '', CollectionConfigurator $parentConfigurator = null) + public function __construct(RouteCollection $collection, RouteCollection $route, string $name = '', CollectionConfigurator $parentConfigurator = null, array $prefixes = null) { $this->collection = $collection; $this->route = $route; $this->name = $name; $this->parentConfigurator = $parentConfigurator; // for GC control + $this->prefixes = $prefixes; + } + + /** + * Sets the host to use for all child routes. + * + * @param string|array $host the host, or the localized hosts + * + * @return $this + */ + final public function host($host): self + { + $this->addHost($this->route, $host); + + return $this; } } diff --git a/lib/symfony/routing/Loader/Configurator/RoutingConfigurator.php b/lib/symfony/routing/Loader/Configurator/RoutingConfigurator.php index d0cc02d1c3..4687bf6817 100644 --- a/lib/symfony/routing/Loader/Configurator/RoutingConfigurator.php +++ b/lib/symfony/routing/Loader/Configurator/RoutingConfigurator.php @@ -24,23 +24,25 @@ class RoutingConfigurator private $loader; private $path; private $file; + private $env; - public function __construct(RouteCollection $collection, PhpFileLoader $loader, $path, $file) + public function __construct(RouteCollection $collection, PhpFileLoader $loader, string $path, string $file, string $env = null) { $this->collection = $collection; $this->loader = $loader; $this->path = $path; $this->file = $file; + $this->env = $env; } /** - * @return ImportConfigurator + * @param string|string[]|null $exclude Glob patterns to exclude from the import */ - final public function import($resource, $type = null, $ignoreErrors = false) + final public function import($resource, string $type = null, bool $ignoreErrors = false, $exclude = null): ImportConfigurator { $this->loader->setCurrentDir(\dirname($this->path)); - $imported = $this->loader->import($resource, $type, $ignoreErrors, $this->file) ?: []; + $imported = $this->loader->import($resource, $type, $ignoreErrors, $this->file, $exclude) ?: []; if (!\is_array($imported)) { return new ImportConfigurator($this->collection, $imported); } @@ -53,11 +55,27 @@ class RoutingConfigurator return new ImportConfigurator($this->collection, $mergedCollection); } - /** - * @return CollectionConfigurator - */ - final public function collection($name = '') + final public function collection(string $name = ''): CollectionConfigurator { return new CollectionConfigurator($this->collection, $name); } + + /** + * Get the current environment to be able to write conditional configuration. + */ + final public function env(): ?string + { + return $this->env; + } + + /** + * @return static + */ + final public function withPath(string $path): self + { + $clone = clone $this; + $clone->path = $clone->file = $path; + + return $clone; + } } diff --git a/lib/symfony/routing/Loader/Configurator/Traits/AddTrait.php b/lib/symfony/routing/Loader/Configurator/Traits/AddTrait.php index e8b8fa2680..92b1bd0ea5 100644 --- a/lib/symfony/routing/Loader/Configurator/Traits/AddTrait.php +++ b/lib/symfony/routing/Loader/Configurator/Traits/AddTrait.php @@ -11,44 +11,49 @@ namespace Symfony\Component\Routing\Loader\Configurator\Traits; +use Symfony\Component\Routing\Loader\Configurator\AliasConfigurator; +use Symfony\Component\Routing\Loader\Configurator\CollectionConfigurator; use Symfony\Component\Routing\Loader\Configurator\RouteConfigurator; -use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; +/** + * @author Nicolas Grekas + */ trait AddTrait { + use LocalizedRouteTrait; + /** * @var RouteCollection */ - private $collection; - - private $name = ''; + protected $collection; + protected $name = ''; + protected $prefixes; /** * Adds a route. * - * @param string $name - * @param string $path - * - * @return RouteConfigurator + * @param string|array $path the path, or the localized paths of the route */ - final public function add($name, $path) + public function add(string $name, $path): RouteConfigurator { - $parentConfigurator = $this instanceof RouteConfigurator ? $this->parentConfigurator : null; - $this->collection->add($this->name.$name, $route = new Route($path)); + $parentConfigurator = $this instanceof CollectionConfigurator ? $this : ($this instanceof RouteConfigurator ? $this->parentConfigurator : null); + $route = $this->createLocalizedRoute($this->collection, $name, $path, $this->name, $this->prefixes); - return new RouteConfigurator($this->collection, $route, '', $parentConfigurator); + return new RouteConfigurator($this->collection, $route, $this->name, $parentConfigurator, $this->prefixes); + } + + public function alias(string $name, string $alias): AliasConfigurator + { + return new AliasConfigurator($this->collection->addAlias($name, $alias)); } /** * Adds a route. * - * @param string $name - * @param string $path - * - * @return RouteConfigurator + * @param string|array $path the path, or the localized paths of the route */ - final public function __invoke($name, $path) + public function __invoke(string $name, $path): RouteConfigurator { return $this->add($name, $path); } diff --git a/lib/symfony/routing/Loader/Configurator/Traits/HostTrait.php b/lib/symfony/routing/Loader/Configurator/Traits/HostTrait.php new file mode 100644 index 0000000000..54ae6566a9 --- /dev/null +++ b/lib/symfony/routing/Loader/Configurator/Traits/HostTrait.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator\Traits; + +use Symfony\Component\Routing\RouteCollection; + +/** + * @internal + */ +trait HostTrait +{ + final protected function addHost(RouteCollection $routes, $hosts) + { + if (!$hosts || !\is_array($hosts)) { + $routes->setHost($hosts ?: ''); + + return; + } + + foreach ($routes->all() as $name => $route) { + if (null === $locale = $route->getDefault('_locale')) { + $routes->remove($name); + foreach ($hosts as $locale => $host) { + $localizedRoute = clone $route; + $localizedRoute->setDefault('_locale', $locale); + $localizedRoute->setRequirement('_locale', preg_quote($locale)); + $localizedRoute->setDefault('_canonical_route', $name); + $localizedRoute->setHost($host); + $routes->add($name.'.'.$locale, $localizedRoute); + } + } elseif (!isset($hosts[$locale])) { + throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding host in its parent collection.', $name, $locale)); + } else { + $route->setHost($hosts[$locale]); + $route->setRequirement('_locale', preg_quote($locale)); + $routes->add($name, $route); + } + } + } +} diff --git a/lib/symfony/routing/Loader/Configurator/Traits/LocalizedRouteTrait.php b/lib/symfony/routing/Loader/Configurator/Traits/LocalizedRouteTrait.php new file mode 100644 index 0000000000..4734a4eac0 --- /dev/null +++ b/lib/symfony/routing/Loader/Configurator/Traits/LocalizedRouteTrait.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator\Traits; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @internal + * + * @author Nicolas Grekas + * @author Jules Pietri + */ +trait LocalizedRouteTrait +{ + /** + * Creates one or many routes. + * + * @param string|array $path the path, or the localized paths of the route + */ + final protected function createLocalizedRoute(RouteCollection $collection, string $name, $path, string $namePrefix = '', array $prefixes = null): RouteCollection + { + $paths = []; + + $routes = new RouteCollection(); + + if (\is_array($path)) { + if (null === $prefixes) { + $paths = $path; + } elseif ($missing = array_diff_key($prefixes, $path)) { + throw new \LogicException(sprintf('Route "%s" is missing routes for locale(s) "%s".', $name, implode('", "', array_keys($missing)))); + } else { + foreach ($path as $locale => $localePath) { + if (!isset($prefixes[$locale])) { + throw new \LogicException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale)); + } + + $paths[$locale] = $prefixes[$locale].$localePath; + } + } + } elseif (null !== $prefixes) { + foreach ($prefixes as $locale => $prefix) { + $paths[$locale] = $prefix.$path; + } + } else { + $routes->add($namePrefix.$name, $route = $this->createRoute($path)); + $collection->add($namePrefix.$name, $route); + + return $routes; + } + + foreach ($paths as $locale => $path) { + $routes->add($name.'.'.$locale, $route = $this->createRoute($path)); + $collection->add($namePrefix.$name.'.'.$locale, $route); + $route->setDefault('_locale', $locale); + $route->setRequirement('_locale', preg_quote($locale)); + $route->setDefault('_canonical_route', $namePrefix.$name); + } + + return $routes; + } + + private function createRoute(string $path): Route + { + return new Route($path); + } +} diff --git a/lib/symfony/routing/Loader/Configurator/Traits/PrefixTrait.php b/lib/symfony/routing/Loader/Configurator/Traits/PrefixTrait.php new file mode 100644 index 0000000000..27053bcaf5 --- /dev/null +++ b/lib/symfony/routing/Loader/Configurator/Traits/PrefixTrait.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\Configurator\Traits; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * @internal + * + * @author Nicolas Grekas + */ +trait PrefixTrait +{ + final protected function addPrefix(RouteCollection $routes, $prefix, bool $trailingSlashOnRoot) + { + if (\is_array($prefix)) { + foreach ($prefix as $locale => $localePrefix) { + $prefix[$locale] = trim(trim($localePrefix), '/'); + } + foreach ($routes->all() as $name => $route) { + if (null === $locale = $route->getDefault('_locale')) { + $routes->remove($name); + foreach ($prefix as $locale => $localePrefix) { + $localizedRoute = clone $route; + $localizedRoute->setDefault('_locale', $locale); + $localizedRoute->setRequirement('_locale', preg_quote($locale)); + $localizedRoute->setDefault('_canonical_route', $name); + $localizedRoute->setPath($localePrefix.(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); + $routes->add($name.'.'.$locale, $localizedRoute); + } + } elseif (!isset($prefix[$locale])) { + throw new \InvalidArgumentException(sprintf('Route "%s" with locale "%s" is missing a corresponding prefix in its parent collection.', $name, $locale)); + } else { + $route->setPath($prefix[$locale].(!$trailingSlashOnRoot && '/' === $route->getPath() ? '' : $route->getPath())); + $routes->add($name, $route); + } + } + + return; + } + + $routes->addPrefix($prefix); + if (!$trailingSlashOnRoot) { + $rootPath = (new Route(trim(trim($prefix), '/').'/'))->getPath(); + foreach ($routes->all() as $route) { + if ($route->getPath() === $rootPath) { + $route->setPath(rtrim($rootPath, '/')); + } + } + } + } +} diff --git a/lib/symfony/routing/Loader/Configurator/Traits/RouteTrait.php b/lib/symfony/routing/Loader/Configurator/Traits/RouteTrait.php index 1d019fe48f..ac05d10e5f 100644 --- a/lib/symfony/routing/Loader/Configurator/Traits/RouteTrait.php +++ b/lib/symfony/routing/Loader/Configurator/Traits/RouteTrait.php @@ -19,14 +19,14 @@ trait RouteTrait /** * @var RouteCollection|Route */ - private $route; + protected $route; /** * Adds defaults. * * @return $this */ - final public function defaults(array $defaults) + final public function defaults(array $defaults): self { $this->route->addDefaults($defaults); @@ -38,7 +38,7 @@ trait RouteTrait * * @return $this */ - final public function requirements(array $requirements) + final public function requirements(array $requirements): self { $this->route->addRequirements($requirements); @@ -50,7 +50,7 @@ trait RouteTrait * * @return $this */ - final public function options(array $options) + final public function options(array $options): self { $this->route->addOptions($options); @@ -58,13 +58,23 @@ trait RouteTrait } /** - * Sets the condition. - * - * @param string $condition + * Whether paths should accept utf8 encoding. * * @return $this */ - final public function condition($condition) + final public function utf8(bool $utf8 = true): self + { + $this->route->addOptions(['utf8' => $utf8]); + + return $this; + } + + /** + * Sets the condition. + * + * @return $this + */ + final public function condition(string $condition): self { $this->route->setCondition($condition); @@ -74,11 +84,9 @@ trait RouteTrait /** * Sets the pattern for the host. * - * @param string $pattern - * * @return $this */ - final public function host($pattern) + final public function host(string $pattern): self { $this->route->setHost($pattern); @@ -93,7 +101,7 @@ trait RouteTrait * * @return $this */ - final public function schemes(array $schemes) + final public function schemes(array $schemes): self { $this->route->setSchemes($schemes); @@ -108,7 +116,7 @@ trait RouteTrait * * @return $this */ - final public function methods(array $methods) + final public function methods(array $methods): self { $this->route->setMethods($methods); @@ -118,14 +126,50 @@ trait RouteTrait /** * Adds the "_controller" entry to defaults. * - * @param callable|string $controller a callable or parseable pseudo-callable + * @param callable|string|array $controller a callable or parseable pseudo-callable * * @return $this */ - final public function controller($controller) + final public function controller($controller): self { $this->route->addDefaults(['_controller' => $controller]); return $this; } + + /** + * Adds the "_locale" entry to defaults. + * + * @return $this + */ + final public function locale(string $locale): self + { + $this->route->addDefaults(['_locale' => $locale]); + + return $this; + } + + /** + * Adds the "_format" entry to defaults. + * + * @return $this + */ + final public function format(string $format): self + { + $this->route->addDefaults(['_format' => $format]); + + return $this; + } + + /** + * Adds the "_stateless" entry to defaults. + * + * @return $this + */ + final public function stateless(bool $stateless = true): self + { + $this->route->addDefaults(['_stateless' => $stateless]); + + return $this; + } } diff --git a/lib/symfony/routing/Loader/ContainerLoader.php b/lib/symfony/routing/Loader/ContainerLoader.php new file mode 100644 index 0000000000..d8730aec61 --- /dev/null +++ b/lib/symfony/routing/Loader/ContainerLoader.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Psr\Container\ContainerInterface; + +/** + * A route loader that executes a service from a PSR-11 container to load the routes. + * + * @author Ryan Weaver + */ +class ContainerLoader extends ObjectLoader +{ + private $container; + + public function __construct(ContainerInterface $container, string $env = null) + { + $this->container = $container; + parent::__construct($env); + } + + /** + * {@inheritdoc} + */ + public function supports($resource, string $type = null) + { + return 'service' === $type && \is_string($resource); + } + + /** + * {@inheritdoc} + */ + protected function getObject(string $id) + { + return $this->container->get($id); + } +} diff --git a/lib/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php b/lib/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php deleted file mode 100644 index 0276719c10..0000000000 --- a/lib/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Routing\Loader\DependencyInjection; - -use Psr\Container\ContainerInterface; -use Symfony\Component\Routing\Loader\ObjectRouteLoader; - -/** - * A route loader that executes a service to load the routes. - * - * @author Ryan Weaver - */ -class ServiceRouterLoader extends ObjectRouteLoader -{ - /** - * @var ContainerInterface - */ - private $container; - - public function __construct(ContainerInterface $container) - { - $this->container = $container; - } - - protected function getServiceObject($id) - { - return $this->container->get($id); - } -} diff --git a/lib/symfony/routing/Loader/DirectoryLoader.php b/lib/symfony/routing/Loader/DirectoryLoader.php index 08e833e0a1..c0f3491774 100644 --- a/lib/symfony/routing/Loader/DirectoryLoader.php +++ b/lib/symfony/routing/Loader/DirectoryLoader.php @@ -20,7 +20,7 @@ class DirectoryLoader extends FileLoader /** * {@inheritdoc} */ - public function load($file, $type = null) + public function load($file, string $type = null) { $path = $this->locator->locate($file); @@ -49,7 +49,7 @@ class DirectoryLoader extends FileLoader /** * {@inheritdoc} */ - public function supports($resource, $type = null) + public function supports($resource, string $type = null) { // only when type is forced to directory, not to conflict with AnnotationLoader diff --git a/lib/symfony/routing/Loader/GlobFileLoader.php b/lib/symfony/routing/Loader/GlobFileLoader.php index 03ee341b98..780fb15dc7 100644 --- a/lib/symfony/routing/Loader/GlobFileLoader.php +++ b/lib/symfony/routing/Loader/GlobFileLoader.php @@ -24,7 +24,7 @@ class GlobFileLoader extends FileLoader /** * {@inheritdoc} */ - public function load($resource, $type = null) + public function load($resource, string $type = null) { $collection = new RouteCollection(); @@ -40,7 +40,7 @@ class GlobFileLoader extends FileLoader /** * {@inheritdoc} */ - public function supports($resource, $type = null) + public function supports($resource, string $type = null) { return 'glob' === $type; } diff --git a/lib/symfony/routing/Loader/ObjectLoader.php b/lib/symfony/routing/Loader/ObjectLoader.php new file mode 100644 index 0000000000..062453908c --- /dev/null +++ b/lib/symfony/routing/Loader/ObjectLoader.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\RouteCollection; + +/** + * A route loader that calls a method on an object to load the routes. + * + * @author Ryan Weaver + */ +abstract class ObjectLoader extends Loader +{ + /** + * Returns the object that the method will be called on to load routes. + * + * For example, if your application uses a service container, + * the $id may be a service id. + * + * @return object + */ + abstract protected function getObject(string $id); + + /** + * Calls the object method that will load the routes. + * + * @param string $resource object_id::method + * @param string|null $type The resource type + * + * @return RouteCollection + */ + public function load($resource, string $type = null) + { + if (!preg_match('/^[^\:]+(?:::(?:[^\:]+))?$/', $resource)) { + throw new \InvalidArgumentException(sprintf('Invalid resource "%s" passed to the %s route loader: use the format "object_id::method" or "object_id" if your object class has an "__invoke" method.', $resource, \is_string($type) ? '"'.$type.'"' : 'object')); + } + + $parts = explode('::', $resource); + $method = $parts[1] ?? '__invoke'; + + $loaderObject = $this->getObject($parts[0]); + + if (!\is_object($loaderObject)) { + throw new \TypeError(sprintf('"%s:getObject()" must return an object: "%s" returned.', static::class, get_debug_type($loaderObject))); + } + + if (!\is_callable([$loaderObject, $method])) { + throw new \BadMethodCallException(sprintf('Method "%s" not found on "%s" when importing routing resource "%s".', $method, get_debug_type($loaderObject), $resource)); + } + + $routeCollection = $loaderObject->$method($this, $this->env); + + if (!$routeCollection instanceof RouteCollection) { + $type = get_debug_type($routeCollection); + + throw new \LogicException(sprintf('The "%s::%s()" method must return a RouteCollection: "%s" returned.', get_debug_type($loaderObject), $method, $type)); + } + + // make the object file tracked so that if it changes, the cache rebuilds + $this->addClassResource(new \ReflectionClass($loaderObject), $routeCollection); + + return $routeCollection; + } + + private function addClassResource(\ReflectionClass $class, RouteCollection $collection) + { + do { + if (is_file($class->getFileName())) { + $collection->addResource(new FileResource($class->getFileName())); + } + } while ($class = $class->getParentClass()); + } +} diff --git a/lib/symfony/routing/Loader/ObjectRouteLoader.php b/lib/symfony/routing/Loader/ObjectRouteLoader.php deleted file mode 100644 index 65633ca075..0000000000 --- a/lib/symfony/routing/Loader/ObjectRouteLoader.php +++ /dev/null @@ -1,95 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Routing\Loader; - -use Symfony\Component\Config\Loader\Loader; -use Symfony\Component\Config\Resource\FileResource; -use Symfony\Component\Routing\RouteCollection; - -/** - * A route loader that calls a method on an object to load the routes. - * - * @author Ryan Weaver - */ -abstract class ObjectRouteLoader extends Loader -{ - /** - * Returns the object that the method will be called on to load routes. - * - * For example, if your application uses a service container, - * the $id may be a service id. - * - * @param string $id - * - * @return object - */ - abstract protected function getServiceObject($id); - - /** - * Calls the service that will load the routes. - * - * @param mixed $resource Some value that will resolve to a callable - * @param string|null $type The resource type - * - * @return RouteCollection - */ - public function load($resource, $type = null) - { - $parts = explode(':', $resource); - if (2 != \count($parts)) { - throw new \InvalidArgumentException(sprintf('Invalid resource "%s" passed to the "service" route loader: use the format "service_name:methodName".', $resource)); - } - - $serviceString = $parts[0]; - $method = $parts[1]; - - $loaderObject = $this->getServiceObject($serviceString); - - if (!\is_object($loaderObject)) { - throw new \LogicException(sprintf('"%s:getServiceObject()" must return an object: "%s" returned.', static::class, \gettype($loaderObject))); - } - - if (!method_exists($loaderObject, $method)) { - throw new \BadMethodCallException(sprintf('Method "%s" not found on "%s" when importing routing resource "%s".', $method, \get_class($loaderObject), $resource)); - } - - $routeCollection = \call_user_func([$loaderObject, $method], $this); - - if (!$routeCollection instanceof RouteCollection) { - $type = \is_object($routeCollection) ? \get_class($routeCollection) : \gettype($routeCollection); - - throw new \LogicException(sprintf('The "%s::%s()" method must return a RouteCollection: "%s" returned.', \get_class($loaderObject), $method, $type)); - } - - // make the service file tracked so that if it changes, the cache rebuilds - $this->addClassResource(new \ReflectionClass($loaderObject), $routeCollection); - - return $routeCollection; - } - - /** - * {@inheritdoc} - */ - public function supports($resource, $type = null) - { - return 'service' === $type; - } - - private function addClassResource(\ReflectionClass $class, RouteCollection $collection) - { - do { - if (is_file($class->getFileName())) { - $collection->addResource(new FileResource($class->getFileName())); - } - } while ($class = $class->getParentClass()); - } -} diff --git a/lib/symfony/routing/Loader/PhpFileLoader.php b/lib/symfony/routing/Loader/PhpFileLoader.php index 8acb9258b3..39ac812734 100644 --- a/lib/symfony/routing/Loader/PhpFileLoader.php +++ b/lib/symfony/routing/Loader/PhpFileLoader.php @@ -22,6 +22,8 @@ use Symfony\Component\Routing\RouteCollection; * The file must return a RouteCollection instance. * * @author Fabien Potencier + * @author Nicolas grekas + * @author Jules Pietri */ class PhpFileLoader extends FileLoader { @@ -31,9 +33,9 @@ class PhpFileLoader extends FileLoader * @param string $file A PHP file path * @param string|null $type The resource type * - * @return RouteCollection A RouteCollection instance + * @return RouteCollection */ - public function load($file, $type = null) + public function load($file, string $type = null) { $path = $this->locator->locate($file); $this->setCurrentDir(\dirname($path)); @@ -46,9 +48,8 @@ class PhpFileLoader extends FileLoader $result = $load($path); - if ($result instanceof \Closure) { - $collection = new RouteCollection(); - $result(new RoutingConfigurator($collection, $this, $path, $file)); + if (\is_object($result) && \is_callable($result)) { + $collection = $this->callConfigurator($result, $path, $file); } else { $collection = $result; } @@ -61,10 +62,19 @@ class PhpFileLoader extends FileLoader /** * {@inheritdoc} */ - public function supports($resource, $type = null) + public function supports($resource, string $type = null) { return \is_string($resource) && 'php' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'php' === $type); } + + protected function callConfigurator(callable $result, string $path, string $file): RouteCollection + { + $collection = new RouteCollection(); + + $result(new RoutingConfigurator($collection, $this, $path, $file, $this->env)); + + return $collection; + } } /** diff --git a/lib/symfony/routing/Loader/XmlFileLoader.php b/lib/symfony/routing/Loader/XmlFileLoader.php index 43112dff98..220153364f 100644 --- a/lib/symfony/routing/Loader/XmlFileLoader.php +++ b/lib/symfony/routing/Loader/XmlFileLoader.php @@ -14,7 +14,9 @@ namespace Symfony\Component\Routing\Loader; use Symfony\Component\Config\Loader\FileLoader; use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\Util\XmlUtils; -use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\Loader\Configurator\Traits\HostTrait; +use Symfony\Component\Routing\Loader\Configurator\Traits\LocalizedRouteTrait; +use Symfony\Component\Routing\Loader\Configurator\Traits\PrefixTrait; use Symfony\Component\Routing\RouteCollection; /** @@ -25,8 +27,12 @@ use Symfony\Component\Routing\RouteCollection; */ class XmlFileLoader extends FileLoader { - const NAMESPACE_URI = 'http://symfony.com/schema/routing'; - const SCHEME_PATH = '/schema/routing/routing-1.0.xsd'; + use HostTrait; + use LocalizedRouteTrait; + use PrefixTrait; + + public const NAMESPACE_URI = 'http://symfony.com/schema/routing'; + public const SCHEME_PATH = '/schema/routing/routing-1.0.xsd'; /** * Loads an XML file. @@ -34,12 +40,12 @@ class XmlFileLoader extends FileLoader * @param string $file An XML file path * @param string|null $type The resource type * - * @return RouteCollection A RouteCollection instance + * @return RouteCollection * * @throws \InvalidArgumentException when the file cannot be loaded or when the XML cannot be * parsed because it does not validate against the scheme */ - public function load($file, $type = null) + public function load($file, string $type = null) { $path = $this->locator->locate($file); @@ -63,14 +69,9 @@ class XmlFileLoader extends FileLoader /** * Parses a node from a loaded XML file. * - * @param RouteCollection $collection Collection to associate with the node - * @param \DOMElement $node Element to parse - * @param string $path Full path of the XML file being processed - * @param string $file Loaded file name - * * @throws \InvalidArgumentException When the XML is invalid */ - protected function parseNode(RouteCollection $collection, \DOMElement $node, $path, $file) + protected function parseNode(RouteCollection $collection, \DOMElement $node, string $path, string $file) { if (self::NAMESPACE_URI !== $node->namespaceURI) { return; @@ -83,6 +84,16 @@ class XmlFileLoader extends FileLoader case 'import': $this->parseImport($collection, $node, $path, $file); break; + case 'when': + if (!$this->env || $node->getAttribute('env') !== $this->env) { + break; + } + foreach ($node->childNodes as $node) { + if ($node instanceof \DOMElement) { + $this->parseNode($collection, $node, $path, $file); + } + } + break; default: throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "route" or "import".', $node->localName, $path)); } @@ -91,7 +102,7 @@ class XmlFileLoader extends FileLoader /** * {@inheritdoc} */ - public function supports($resource, $type = null) + public function supports($resource, string $type = null) { return \is_string($resource) && 'xml' === pathinfo($resource, \PATHINFO_EXTENSION) && (!$type || 'xml' === $type); } @@ -99,38 +110,56 @@ class XmlFileLoader extends FileLoader /** * Parses a route and adds it to the RouteCollection. * - * @param RouteCollection $collection RouteCollection instance - * @param \DOMElement $node Element to parse that represents a Route - * @param string $path Full path of the XML file being processed - * * @throws \InvalidArgumentException When the XML is invalid */ - protected function parseRoute(RouteCollection $collection, \DOMElement $node, $path) + protected function parseRoute(RouteCollection $collection, \DOMElement $node, string $path) { - if ('' === ($id = $node->getAttribute('id')) || !$node->hasAttribute('path')) { - throw new \InvalidArgumentException(sprintf('The element in file "%s" must have an "id" and a "path" attribute.', $path)); + if ('' === $id = $node->getAttribute('id')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have an "id" attribute.', $path)); + } + + if ('' !== $alias = $node->getAttribute('alias')) { + $alias = $collection->addAlias($id, $alias); + + if ($deprecationInfo = $this->parseDeprecation($node, $path)) { + $alias->setDeprecated($deprecationInfo['package'], $deprecationInfo['version'], $deprecationInfo['message']); + } + + return; } $schemes = preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, \PREG_SPLIT_NO_EMPTY); $methods = preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, \PREG_SPLIT_NO_EMPTY); - list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path); + [$defaults, $requirements, $options, $condition, $paths, /* $prefixes */, $hosts] = $this->parseConfigs($node, $path); - $route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods, $condition); - $collection->add($id, $route); + if (!$paths && '' === $node->getAttribute('path')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "path" attribute or child nodes.', $path)); + } + + if ($paths && '' !== $node->getAttribute('path')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must not have both a "path" attribute and child nodes.', $path)); + } + + $routes = $this->createLocalizedRoute($collection, $id, $paths ?: $node->getAttribute('path')); + $routes->addDefaults($defaults); + $routes->addRequirements($requirements); + $routes->addOptions($options); + $routes->setSchemes($schemes); + $routes->setMethods($methods); + $routes->setCondition($condition); + + if (null !== $hosts) { + $this->addHost($routes, $hosts); + } } /** * Parses an import and adds the routes in the resource to the RouteCollection. * - * @param RouteCollection $collection RouteCollection instance - * @param \DOMElement $node Element to parse that represents a Route - * @param string $path Full path of the XML file being processed - * @param string $file Loaded file name - * * @throws \InvalidArgumentException When the XML is invalid */ - protected function parseImport(RouteCollection $collection, \DOMElement $node, $path, $file) + protected function parseImport(RouteCollection $collection, \DOMElement $node, string $path, string $file) { if ('' === $resource = $node->getAttribute('resource')) { throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "resource" attribute.', $path)); @@ -138,27 +167,47 @@ class XmlFileLoader extends FileLoader $type = $node->getAttribute('type'); $prefix = $node->getAttribute('prefix'); - $host = $node->hasAttribute('host') ? $node->getAttribute('host') : null; $schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, \PREG_SPLIT_NO_EMPTY) : null; $methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, \PREG_SPLIT_NO_EMPTY) : null; + $trailingSlashOnRoot = $node->hasAttribute('trailing-slash-on-root') ? XmlUtils::phpize($node->getAttribute('trailing-slash-on-root')) : true; + $namePrefix = $node->getAttribute('name-prefix') ?: null; - list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path); + [$defaults, $requirements, $options, $condition, /* $paths */, $prefixes, $hosts] = $this->parseConfigs($node, $path); + + if ('' !== $prefix && $prefixes) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must not have both a "prefix" attribute and child nodes.', $path)); + } + + $exclude = []; + foreach ($node->childNodes as $child) { + if ($child instanceof \DOMElement && $child->localName === $exclude && self::NAMESPACE_URI === $child->namespaceURI) { + $exclude[] = $child->nodeValue; + } + } + + if ($node->hasAttribute('exclude')) { + if ($exclude) { + throw new \InvalidArgumentException('You cannot use both the attribute "exclude" and tags at the same time.'); + } + $exclude = [$node->getAttribute('exclude')]; + } $this->setCurrentDir(\dirname($path)); /** @var RouteCollection[] $imported */ - $imported = $this->import($resource, ('' !== $type ? $type : null), false, $file) ?: []; + $imported = $this->import($resource, '' !== $type ? $type : null, false, $file, $exclude) ?: []; if (!\is_array($imported)) { $imported = [$imported]; } foreach ($imported as $subCollection) { - /* @var $subCollection RouteCollection */ - $subCollection->addPrefix($prefix); - if (null !== $host) { - $subCollection->setHost($host); + $this->addPrefix($subCollection, $prefixes ?: $prefix, $trailingSlashOnRoot); + + if (null !== $hosts) { + $this->addHost($subCollection, $hosts); } + if (null !== $condition) { $subCollection->setCondition($condition); } @@ -168,6 +217,9 @@ class XmlFileLoader extends FileLoader if (null !== $methods) { $subCollection->setMethods($methods); } + if (null !== $namePrefix) { + $subCollection->addNamePrefix($namePrefix); + } $subCollection->addDefaults($defaults); $subCollection->addRequirements($requirements); $subCollection->addOptions($options); @@ -177,17 +229,13 @@ class XmlFileLoader extends FileLoader } /** - * Loads an XML file. - * - * @param string $file An XML file path - * * @return \DOMDocument * * @throws \InvalidArgumentException When loading of XML file fails because of syntax errors * or when the XML structure is not as expected by the scheme - * see validate() */ - protected function loadFile($file) + protected function loadFile(string $file) { return XmlUtils::loadFile($file, __DIR__.static::SCHEME_PATH); } @@ -195,19 +243,17 @@ class XmlFileLoader extends FileLoader /** * Parses the config elements (default, requirement, option). * - * @param \DOMElement $node Element to parse that contains the configs - * @param string $path Full path of the XML file being processed - * - * @return array An array with the defaults as first item, requirements as second and options as third - * * @throws \InvalidArgumentException When the XML is invalid */ - private function parseConfigs(\DOMElement $node, $path) + private function parseConfigs(\DOMElement $node, string $path): array { $defaults = []; $requirements = []; $options = []; $condition = null; + $prefixes = []; + $paths = []; + $hosts = []; /** @var \DOMElement $n */ foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) { @@ -216,6 +262,15 @@ class XmlFileLoader extends FileLoader } switch ($n->localName) { + case 'path': + $paths[$n->getAttribute('locale')] = trim($n->textContent); + break; + case 'host': + $hosts[$n->getAttribute('locale')] = trim($n->textContent); + break; + case 'prefix': + $prefixes[$n->getAttribute('locale')] = trim($n->textContent); + break; case 'default': if ($this->isElementValueNull($n)) { $defaults[$n->getAttribute('key')] = null; @@ -247,19 +302,38 @@ class XmlFileLoader extends FileLoader $defaults['_controller'] = $controller; } + if ($node->hasAttribute('locale')) { + $defaults['_locale'] = $node->getAttribute('locale'); + } + if ($node->hasAttribute('format')) { + $defaults['_format'] = $node->getAttribute('format'); + } + if ($node->hasAttribute('utf8')) { + $options['utf8'] = XmlUtils::phpize($node->getAttribute('utf8')); + } + if ($stateless = $node->getAttribute('stateless')) { + if (isset($defaults['_stateless'])) { + $name = $node->hasAttribute('id') ? sprintf('"%s".', $node->getAttribute('id')) : sprintf('the "%s" tag.', $node->tagName); - return [$defaults, $requirements, $options, $condition]; + throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "stateless" attribute and the defaults key "_stateless" for ', $path).$name); + } + + $defaults['_stateless'] = XmlUtils::phpize($stateless); + } + + if (!$hosts) { + $hosts = $node->hasAttribute('host') ? $node->getAttribute('host') : null; + } + + return [$defaults, $requirements, $options, $condition, $paths, $prefixes, $hosts]; } /** * Parses the "default" elements. * - * @param \DOMElement $element The "default" element to parse - * @param string $path Full path of the XML file being processed - * - * @return array|bool|float|int|string|null The parsed value of the "default" element + * @return array|bool|float|int|string|null */ - private function parseDefaultsConfig(\DOMElement $element, $path) + private function parseDefaultsConfig(\DOMElement $element, string $path) { if ($this->isElementValueNull($element)) { return null; @@ -289,14 +363,11 @@ class XmlFileLoader extends FileLoader /** * Recursively parses the value of a "default" element. * - * @param \DOMElement $node The node value - * @param string $path Full path of the XML file being processed - * - * @return array|bool|float|int|string The parsed value + * @return array|bool|float|int|string|null * * @throws \InvalidArgumentException when the XML is invalid */ - private function parseDefaultNode(\DOMElement $node, $path) + private function parseDefaultNode(\DOMElement $node, string $path) { if ($this->isElementValueNull($node)) { return null; @@ -348,7 +419,7 @@ class XmlFileLoader extends FileLoader } } - private function isElementValueNull(\DOMElement $element) + private function isElementValueNull(\DOMElement $element): bool { $namespaceUri = 'http://www.w3.org/2001/XMLSchema-instance'; @@ -358,4 +429,41 @@ class XmlFileLoader extends FileLoader return 'true' === $element->getAttributeNS($namespaceUri, 'nil') || '1' === $element->getAttributeNS($namespaceUri, 'nil'); } + + /** + * Parses the deprecation elements. + * + * @throws \InvalidArgumentException When the XML is invalid + */ + private function parseDeprecation(\DOMElement $node, string $path): array + { + $deprecatedNode = null; + foreach ($node->childNodes as $child) { + if (!$child instanceof \DOMElement || self::NAMESPACE_URI !== $child->namespaceURI) { + continue; + } + if ('deprecated' !== $child->localName) { + throw new \InvalidArgumentException(sprintf('Invalid child element "%s" defined for alias "%s" in "%s".', $child->localName, $node->getAttribute('id'), $path)); + } + + $deprecatedNode = $child; + } + + if (null === $deprecatedNode) { + return []; + } + + if (!$deprecatedNode->hasAttribute('package')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "package" attribute.', $path)); + } + if (!$deprecatedNode->hasAttribute('version')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "version" attribute.', $path)); + } + + return [ + 'package' => $deprecatedNode->getAttribute('package'), + 'version' => $deprecatedNode->getAttribute('version'), + 'message' => trim($deprecatedNode->nodeValue), + ]; + } } diff --git a/lib/symfony/routing/Loader/YamlFileLoader.php b/lib/symfony/routing/Loader/YamlFileLoader.php index caed47bdc1..ae98a314e8 100644 --- a/lib/symfony/routing/Loader/YamlFileLoader.php +++ b/lib/symfony/routing/Loader/YamlFileLoader.php @@ -13,10 +13,13 @@ namespace Symfony\Component\Routing\Loader; use Symfony\Component\Config\Loader\FileLoader; use Symfony\Component\Config\Resource\FileResource; -use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\Loader\Configurator\Traits\HostTrait; +use Symfony\Component\Routing\Loader\Configurator\Traits\LocalizedRouteTrait; +use Symfony\Component\Routing\Loader\Configurator\Traits\PrefixTrait; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Parser as YamlParser; +use Symfony\Component\Yaml\Yaml; /** * YamlFileLoader loads Yaml routing files. @@ -26,8 +29,12 @@ use Symfony\Component\Yaml\Parser as YamlParser; */ class YamlFileLoader extends FileLoader { - private static $availableKeys = [ - 'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', + use HostTrait; + use LocalizedRouteTrait; + use PrefixTrait; + + private const AVAILABLE_KEYS = [ + 'resource', 'type', 'prefix', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', 'controller', 'name_prefix', 'trailing_slash_on_root', 'locale', 'format', 'utf8', 'exclude', 'stateless', ]; private $yamlParser; @@ -37,11 +44,11 @@ class YamlFileLoader extends FileLoader * @param string $file A Yaml file path * @param string|null $type The resource type * - * @return RouteCollection A RouteCollection instance + * @return RouteCollection * * @throws \InvalidArgumentException When a route can't be parsed because YAML is invalid */ - public function load($file, $type = null) + public function load($file, string $type = null) { $path = $this->locator->locate($file); @@ -57,18 +64,10 @@ class YamlFileLoader extends FileLoader $this->yamlParser = new YamlParser(); } - $prevErrorHandler = set_error_handler(function ($level, $message, $script, $line) use ($file, &$prevErrorHandler) { - $message = \E_USER_DEPRECATED === $level ? preg_replace('/ on line \d+/', ' in "'.$file.'"$0', $message) : $message; - - return $prevErrorHandler ? $prevErrorHandler($level, $message, $script, $line) : false; - }); - try { - $parsedConfig = $this->yamlParser->parseFile($path); + $parsedConfig = $this->yamlParser->parseFile($path, Yaml::PARSE_CONSTANT); } catch (ParseException $e) { throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML: ', $path).$e->getMessage(), 0, $e); - } finally { - restore_error_handler(); } $collection = new RouteCollection(); @@ -85,6 +84,24 @@ class YamlFileLoader extends FileLoader } foreach ($parsedConfig as $name => $config) { + if (0 === strpos($name, 'when@')) { + if (!$this->env || 'when@'.$this->env !== $name) { + continue; + } + + foreach ($config as $name => $config) { + $this->validate($config, $name.'" when "@'.$this->env, $path); + + if (isset($config['resource'])) { + $this->parseImport($collection, $config, $path, $file); + } else { + $this->parseRoute($collection, $name, $config, $path); + } + } + + continue; + } + $this->validate($config, $name, $path); if (isset($config['resource'])) { @@ -100,75 +117,117 @@ class YamlFileLoader extends FileLoader /** * {@inheritdoc} */ - public function supports($resource, $type = null) + public function supports($resource, string $type = null) { return \is_string($resource) && \in_array(pathinfo($resource, \PATHINFO_EXTENSION), ['yml', 'yaml'], true) && (!$type || 'yaml' === $type); } /** * Parses a route and adds it to the RouteCollection. - * - * @param RouteCollection $collection A RouteCollection instance - * @param string $name Route name - * @param array $config Route definition - * @param string $path Full path of the YAML file being processed */ - protected function parseRoute(RouteCollection $collection, $name, array $config, $path) + protected function parseRoute(RouteCollection $collection, string $name, array $config, string $path) { - $defaults = isset($config['defaults']) ? $config['defaults'] : []; - $requirements = isset($config['requirements']) ? $config['requirements'] : []; - $options = isset($config['options']) ? $config['options'] : []; - $host = isset($config['host']) ? $config['host'] : ''; - $schemes = isset($config['schemes']) ? $config['schemes'] : []; - $methods = isset($config['methods']) ? $config['methods'] : []; - $condition = isset($config['condition']) ? $config['condition'] : null; + if (isset($config['alias'])) { + $alias = $collection->addAlias($name, $config['alias']); + $deprecation = $config['deprecated'] ?? null; + if (null !== $deprecation) { + $alias->setDeprecated( + $deprecation['package'], + $deprecation['version'], + $deprecation['message'] ?? '' + ); + } + + return; + } + + $defaults = $config['defaults'] ?? []; + $requirements = $config['requirements'] ?? []; + $options = $config['options'] ?? []; + + foreach ($requirements as $placeholder => $requirement) { + if (\is_int($placeholder)) { + throw new \InvalidArgumentException(sprintf('A placeholder name must be a string (%d given). Did you forget to specify the placeholder key for the requirement "%s" of route "%s" in "%s"?', $placeholder, $requirement, $name, $path)); + } + } if (isset($config['controller'])) { $defaults['_controller'] = $config['controller']; } + if (isset($config['locale'])) { + $defaults['_locale'] = $config['locale']; + } + if (isset($config['format'])) { + $defaults['_format'] = $config['format']; + } + if (isset($config['utf8'])) { + $options['utf8'] = $config['utf8']; + } + if (isset($config['stateless'])) { + $defaults['_stateless'] = $config['stateless']; + } - $route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition); + $routes = $this->createLocalizedRoute($collection, $name, $config['path']); + $routes->addDefaults($defaults); + $routes->addRequirements($requirements); + $routes->addOptions($options); + $routes->setSchemes($config['schemes'] ?? []); + $routes->setMethods($config['methods'] ?? []); + $routes->setCondition($config['condition'] ?? null); - $collection->add($name, $route); + if (isset($config['host'])) { + $this->addHost($routes, $config['host']); + } } /** * Parses an import and adds the routes in the resource to the RouteCollection. - * - * @param RouteCollection $collection A RouteCollection instance - * @param array $config Route definition - * @param string $path Full path of the YAML file being processed - * @param string $file Loaded file name */ - protected function parseImport(RouteCollection $collection, array $config, $path, $file) + protected function parseImport(RouteCollection $collection, array $config, string $path, string $file) { - $type = isset($config['type']) ? $config['type'] : null; - $prefix = isset($config['prefix']) ? $config['prefix'] : ''; - $defaults = isset($config['defaults']) ? $config['defaults'] : []; - $requirements = isset($config['requirements']) ? $config['requirements'] : []; - $options = isset($config['options']) ? $config['options'] : []; - $host = isset($config['host']) ? $config['host'] : null; - $condition = isset($config['condition']) ? $config['condition'] : null; - $schemes = isset($config['schemes']) ? $config['schemes'] : null; - $methods = isset($config['methods']) ? $config['methods'] : null; + $type = $config['type'] ?? null; + $prefix = $config['prefix'] ?? ''; + $defaults = $config['defaults'] ?? []; + $requirements = $config['requirements'] ?? []; + $options = $config['options'] ?? []; + $host = $config['host'] ?? null; + $condition = $config['condition'] ?? null; + $schemes = $config['schemes'] ?? null; + $methods = $config['methods'] ?? null; + $trailingSlashOnRoot = $config['trailing_slash_on_root'] ?? true; + $namePrefix = $config['name_prefix'] ?? null; + $exclude = $config['exclude'] ?? null; if (isset($config['controller'])) { $defaults['_controller'] = $config['controller']; } + if (isset($config['locale'])) { + $defaults['_locale'] = $config['locale']; + } + if (isset($config['format'])) { + $defaults['_format'] = $config['format']; + } + if (isset($config['utf8'])) { + $options['utf8'] = $config['utf8']; + } + if (isset($config['stateless'])) { + $defaults['_stateless'] = $config['stateless']; + } $this->setCurrentDir(\dirname($path)); - $imported = $this->import($config['resource'], $type, false, $file) ?: []; + /** @var RouteCollection[] $imported */ + $imported = $this->import($config['resource'], $type, false, $file, $exclude) ?: []; if (!\is_array($imported)) { $imported = [$imported]; } foreach ($imported as $subCollection) { - /* @var $subCollection RouteCollection */ - $subCollection->addPrefix($prefix); + $this->addPrefix($subCollection, $prefix, $trailingSlashOnRoot); + if (null !== $host) { - $subCollection->setHost($host); + $this->addHost($subCollection, $host); } if (null !== $condition) { $subCollection->setCondition($condition); @@ -179,6 +238,9 @@ class YamlFileLoader extends FileLoader if (null !== $methods) { $subCollection->setMethods($methods); } + if (null !== $namePrefix) { + $subCollection->addNamePrefix($namePrefix); + } $subCollection->addDefaults($defaults); $subCollection->addRequirements($requirements); $subCollection->addOptions($options); @@ -197,13 +259,18 @@ class YamlFileLoader extends FileLoader * @throws \InvalidArgumentException If one of the provided config keys is not supported, * something is missing or the combination is nonsense */ - protected function validate($config, $name, $path) + protected function validate($config, string $name, string $path) { if (!\is_array($config)) { throw new \InvalidArgumentException(sprintf('The definition of "%s" in "%s" must be a YAML array.', $name, $path)); } - if ($extraKeys = array_diff(array_keys($config), self::$availableKeys)) { - throw new \InvalidArgumentException(sprintf('The routing file "%s" contains unsupported keys for "%s": "%s". Expected one of: "%s".', $path, $name, implode('", "', $extraKeys), implode('", "', self::$availableKeys))); + if (isset($config['alias'])) { + $this->validateAlias($config, $name, $path); + + return; + } + if ($extraKeys = array_diff(array_keys($config), self::AVAILABLE_KEYS)) { + throw new \InvalidArgumentException(sprintf('The routing file "%s" contains unsupported keys for "%s": "%s". Expected one of: "%s".', $path, $name, implode('", "', $extraKeys), implode('", "', self::AVAILABLE_KEYS))); } if (isset($config['resource']) && isset($config['path'])) { throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "resource" key and the "path" key for "%s". Choose between an import and a route definition.', $path, $name)); @@ -217,5 +284,31 @@ class YamlFileLoader extends FileLoader if (isset($config['controller']) && isset($config['defaults']['_controller'])) { throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "controller" key and the defaults key "_controller" for "%s".', $path, $name)); } + if (isset($config['stateless']) && isset($config['defaults']['_stateless'])) { + throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "stateless" key and the defaults key "_stateless" for "%s".', $path, $name)); + } + } + + /** + * @throws \InvalidArgumentException If one of the provided config keys is not supported, + * something is missing or the combination is nonsense + */ + private function validateAlias(array $config, string $name, string $path): void + { + foreach ($config as $key => $value) { + if (!\in_array($key, ['alias', 'deprecated'], true)) { + throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify other keys than "alias" and "deprecated" for "%s".', $path, $name)); + } + + if ('deprecated' === $key) { + if (!isset($value['package'])) { + throw new \InvalidArgumentException(sprintf('The routing file "%s" must specify the attribute "package" of the "deprecated" option for "%s".', $path, $name)); + } + + if (!isset($value['version'])) { + throw new \InvalidArgumentException(sprintf('The routing file "%s" must specify the attribute "version" of the "deprecated" option for "%s".', $path, $name)); + } + } + } } } diff --git a/lib/symfony/routing/Loader/schema/routing/routing-1.0.xsd b/lib/symfony/routing/Loader/schema/routing/routing-1.0.xsd index a97111aaa5..66c40a0d86 100644 --- a/lib/symfony/routing/Loader/schema/routing/routing-1.0.xsd +++ b/lib/symfony/routing/Loader/schema/routing/routing-1.0.xsd @@ -21,9 +21,26 @@ + + + + + + + + + + + + + + + + + @@ -34,26 +51,46 @@ - - + + + + + + - + + + + + + - - + + + + + + + + + + + + + @@ -145,4 +182,13 @@ + + + + + + + + + diff --git a/lib/symfony/routing/Matcher/CompiledUrlMatcher.php b/lib/symfony/routing/Matcher/CompiledUrlMatcher.php new file mode 100644 index 0000000000..ae13fd7011 --- /dev/null +++ b/lib/symfony/routing/Matcher/CompiledUrlMatcher.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherTrait; +use Symfony\Component\Routing\RequestContext; + +/** + * Matches URLs based on rules dumped by CompiledUrlMatcherDumper. + * + * @author Nicolas Grekas + */ +class CompiledUrlMatcher extends UrlMatcher +{ + use CompiledUrlMatcherTrait; + + public function __construct(array $compiledRoutes, RequestContext $context) + { + $this->context = $context; + [$this->matchHost, $this->staticRoutes, $this->regexpList, $this->dynamicRoutes, $this->checkCondition] = $compiledRoutes; + } +} diff --git a/lib/symfony/routing/Matcher/Dumper/CompiledUrlMatcherDumper.php b/lib/symfony/routing/Matcher/Dumper/CompiledUrlMatcherDumper.php new file mode 100644 index 0000000000..123130ed40 --- /dev/null +++ b/lib/symfony/routing/Matcher/Dumper/CompiledUrlMatcherDumper.php @@ -0,0 +1,501 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * CompiledUrlMatcherDumper creates PHP arrays to be used with CompiledUrlMatcher. + * + * @author Fabien Potencier + * @author Tobias Schultze + * @author Arnaud Le Blanc + * @author Nicolas Grekas + */ +class CompiledUrlMatcherDumper extends MatcherDumper +{ + private $expressionLanguage; + private $signalingException; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + private $expressionLanguageProviders = []; + + /** + * {@inheritdoc} + */ + public function dump(array $options = []) + { + return <<generateCompiledRoutes()}]; + +EOF; + } + + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) + { + $this->expressionLanguageProviders[] = $provider; + } + + /** + * Generates the arrays for CompiledUrlMatcher's constructor. + */ + public function getCompiledRoutes(bool $forDump = false): array + { + // Group hosts by same-suffix, re-order when possible + $matchHost = false; + $routes = new StaticPrefixCollection(); + foreach ($this->getRoutes()->all() as $name => $route) { + if ($host = $route->getHost()) { + $matchHost = true; + $host = '/'.strtr(strrev($host), '}.{', '(/)'); + } + + $routes->addRoute($host ?: '/(.*)', [$name, $route]); + } + + if ($matchHost) { + $compiledRoutes = [true]; + $routes = $routes->populateCollection(new RouteCollection()); + } else { + $compiledRoutes = [false]; + $routes = $this->getRoutes(); + } + + [$staticRoutes, $dynamicRoutes] = $this->groupStaticRoutes($routes); + + $conditions = [null]; + $compiledRoutes[] = $this->compileStaticRoutes($staticRoutes, $conditions); + $chunkLimit = \count($dynamicRoutes); + + while (true) { + try { + $this->signalingException = new \RuntimeException('Compilation failed: regular expression is too large'); + $compiledRoutes = array_merge($compiledRoutes, $this->compileDynamicRoutes($dynamicRoutes, $matchHost, $chunkLimit, $conditions)); + + break; + } catch (\Exception $e) { + if (1 < $chunkLimit && $this->signalingException === $e) { + $chunkLimit = 1 + ($chunkLimit >> 1); + continue; + } + throw $e; + } + } + + if ($forDump) { + $compiledRoutes[2] = $compiledRoutes[4]; + } + unset($conditions[0]); + + if ($conditions) { + foreach ($conditions as $expression => $condition) { + $conditions[$expression] = "case {$condition}: return {$expression};"; + } + + $checkConditionCode = <<indent(implode("\n", $conditions), 3)} + } + } +EOF; + $compiledRoutes[4] = $forDump ? $checkConditionCode.",\n" : eval('return '.$checkConditionCode.';'); + } else { + $compiledRoutes[4] = $forDump ? " null, // \$checkCondition\n" : null; + } + + return $compiledRoutes; + } + + private function generateCompiledRoutes(): string + { + [$matchHost, $staticRoutes, $regexpCode, $dynamicRoutes, $checkConditionCode] = $this->getCompiledRoutes(true); + + $code = self::export($matchHost).', // $matchHost'."\n"; + + $code .= '[ // $staticRoutes'."\n"; + foreach ($staticRoutes as $path => $routes) { + $code .= sprintf(" %s => [\n", self::export($path)); + foreach ($routes as $route) { + $code .= sprintf(" [%s, %s, %s, %s, %s, %s, %s],\n", ...array_map([__CLASS__, 'export'], $route)); + } + $code .= " ],\n"; + } + $code .= "],\n"; + + $code .= sprintf("[ // \$regexpList%s\n],\n", $regexpCode); + + $code .= '[ // $dynamicRoutes'."\n"; + foreach ($dynamicRoutes as $path => $routes) { + $code .= sprintf(" %s => [\n", self::export($path)); + foreach ($routes as $route) { + $code .= sprintf(" [%s, %s, %s, %s, %s, %s, %s],\n", ...array_map([__CLASS__, 'export'], $route)); + } + $code .= " ],\n"; + } + $code .= "],\n"; + $code = preg_replace('/ => \[\n (\[.+?),\n \],/', ' => [$1],', $code); + + return $this->indent($code, 1).$checkConditionCode; + } + + /** + * Splits static routes from dynamic routes, so that they can be matched first, using a simple switch. + */ + private function groupStaticRoutes(RouteCollection $collection): array + { + $staticRoutes = $dynamicRegex = []; + $dynamicRoutes = new RouteCollection(); + + foreach ($collection->all() as $name => $route) { + $compiledRoute = $route->compile(); + $staticPrefix = rtrim($compiledRoute->getStaticPrefix(), '/'); + $hostRegex = $compiledRoute->getHostRegex(); + $regex = $compiledRoute->getRegex(); + if ($hasTrailingSlash = '/' !== $route->getPath()) { + $pos = strrpos($regex, '$'); + $hasTrailingSlash = '/' === $regex[$pos - 1]; + $regex = substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash); + } + + if (!$compiledRoute->getPathVariables()) { + $host = !$compiledRoute->getHostVariables() ? $route->getHost() : ''; + $url = $route->getPath(); + if ($hasTrailingSlash) { + $url = substr($url, 0, -1); + } + foreach ($dynamicRegex as [$hostRx, $rx, $prefix]) { + if (('' === $prefix || str_starts_with($url, $prefix)) && (preg_match($rx, $url) || preg_match($rx, $url.'/')) && (!$host || !$hostRx || preg_match($hostRx, $host))) { + $dynamicRegex[] = [$hostRegex, $regex, $staticPrefix]; + $dynamicRoutes->add($name, $route); + continue 2; + } + } + + $staticRoutes[$url][$name] = [$route, $hasTrailingSlash]; + } else { + $dynamicRegex[] = [$hostRegex, $regex, $staticPrefix]; + $dynamicRoutes->add($name, $route); + } + } + + return [$staticRoutes, $dynamicRoutes]; + } + + /** + * Compiles static routes in a switch statement. + * + * Condition-less paths are put in a static array in the switch's default, with generic matching logic. + * Paths that can match two or more routes, or have user-specified conditions are put in separate switch's cases. + * + * @throws \LogicException + */ + private function compileStaticRoutes(array $staticRoutes, array &$conditions): array + { + if (!$staticRoutes) { + return []; + } + $compiledRoutes = []; + + foreach ($staticRoutes as $url => $routes) { + $compiledRoutes[$url] = []; + foreach ($routes as $name => [$route, $hasTrailingSlash]) { + $compiledRoutes[$url][] = $this->compileRoute($route, $name, (!$route->compile()->getHostVariables() ? $route->getHost() : $route->compile()->getHostRegex()) ?: null, $hasTrailingSlash, false, $conditions); + } + } + + return $compiledRoutes; + } + + /** + * Compiles a regular expression followed by a switch statement to match dynamic routes. + * + * The regular expression matches both the host and the pathinfo at the same time. For stellar performance, + * it is built as a tree of patterns, with re-ordering logic to group same-prefix routes together when possible. + * + * Patterns are named so that we know which one matched (https://pcre.org/current/doc/html/pcre2syntax.html#SEC23). + * This name is used to "switch" to the additional logic required to match the final route. + * + * Condition-less paths are put in a static array in the switch's default, with generic matching logic. + * Paths that can match two or more routes, or have user-specified conditions are put in separate switch's cases. + * + * Last but not least: + * - Because it is not possible to mix unicode/non-unicode patterns in a single regexp, several of them can be generated. + * - The same regexp can be used several times when the logic in the switch rejects the match. When this happens, the + * matching-but-failing subpattern is excluded by replacing its name by "(*F)", which forces a failure-to-match. + * To ease this backlisting operation, the name of subpatterns is also the string offset where the replacement should occur. + */ + private function compileDynamicRoutes(RouteCollection $collection, bool $matchHost, int $chunkLimit, array &$conditions): array + { + if (!$collection->all()) { + return [[], [], '']; + } + $regexpList = []; + $code = ''; + $state = (object) [ + 'regexMark' => 0, + 'regex' => [], + 'routes' => [], + 'mark' => 0, + 'markTail' => 0, + 'hostVars' => [], + 'vars' => [], + ]; + $state->getVars = static function ($m) use ($state) { + if ('_route' === $m[1]) { + return '?:'; + } + + $state->vars[] = $m[1]; + + return ''; + }; + + $chunkSize = 0; + $prev = null; + $perModifiers = []; + foreach ($collection->all() as $name => $route) { + preg_match('#[a-zA-Z]*$#', $route->compile()->getRegex(), $rx); + if ($chunkLimit < ++$chunkSize || $prev !== $rx[0] && $route->compile()->getPathVariables()) { + $chunkSize = 1; + $routes = new RouteCollection(); + $perModifiers[] = [$rx[0], $routes]; + $prev = $rx[0]; + } + $routes->add($name, $route); + } + + foreach ($perModifiers as [$modifiers, $routes]) { + $prev = false; + $perHost = []; + foreach ($routes->all() as $name => $route) { + $regex = $route->compile()->getHostRegex(); + if ($prev !== $regex) { + $routes = new RouteCollection(); + $perHost[] = [$regex, $routes]; + $prev = $regex; + } + $routes->add($name, $route); + } + $prev = false; + $rx = '{^(?'; + $code .= "\n {$state->mark} => ".self::export($rx); + $startingMark = $state->mark; + $state->mark += \strlen($rx); + $state->regex = $rx; + + foreach ($perHost as [$hostRegex, $routes]) { + if ($matchHost) { + if ($hostRegex) { + preg_match('#^.\^(.*)\$.[a-zA-Z]*$#', $hostRegex, $rx); + $state->vars = []; + $hostRegex = '(?i:'.preg_replace_callback('#\?P<([^>]++)>#', $state->getVars, $rx[1]).')\.'; + $state->hostVars = $state->vars; + } else { + $hostRegex = '(?:(?:[^./]*+\.)++)'; + $state->hostVars = []; + } + $state->mark += \strlen($rx = ($prev ? ')' : '')."|{$hostRegex}(?"); + $code .= "\n .".self::export($rx); + $state->regex .= $rx; + $prev = true; + } + + $tree = new StaticPrefixCollection(); + foreach ($routes->all() as $name => $route) { + preg_match('#^.\^(.*)\$.[a-zA-Z]*$#', $route->compile()->getRegex(), $rx); + + $state->vars = []; + $regex = preg_replace_callback('#\?P<([^>]++)>#', $state->getVars, $rx[1]); + if ($hasTrailingSlash = '/' !== $regex && '/' === $regex[-1]) { + $regex = substr($regex, 0, -1); + } + $hasTrailingVar = (bool) preg_match('#\{\w+\}/?$#', $route->getPath()); + + $tree->addRoute($regex, [$name, $regex, $state->vars, $route, $hasTrailingSlash, $hasTrailingVar]); + } + + $code .= $this->compileStaticPrefixCollection($tree, $state, 0, $conditions); + } + if ($matchHost) { + $code .= "\n .')'"; + $state->regex .= ')'; + } + $rx = ")/?$}{$modifiers}"; + $code .= "\n .'{$rx}',"; + $state->regex .= $rx; + $state->markTail = 0; + + // if the regex is too large, throw a signaling exception to recompute with smaller chunk size + set_error_handler(function ($type, $message) { throw str_contains($message, $this->signalingException->getMessage()) ? $this->signalingException : new \ErrorException($message); }); + try { + preg_match($state->regex, ''); + } finally { + restore_error_handler(); + } + + $regexpList[$startingMark] = $state->regex; + } + + $state->routes[$state->mark][] = [null, null, null, null, false, false, 0]; + unset($state->getVars); + + return [$regexpList, $state->routes, $code]; + } + + /** + * Compiles a regexp tree of subpatterns that matches nested same-prefix routes. + * + * @param \stdClass $state A simple state object that keeps track of the progress of the compilation, + * and gathers the generated switch's "case" and "default" statements + */ + private function compileStaticPrefixCollection(StaticPrefixCollection $tree, \stdClass $state, int $prefixLen, array &$conditions): string + { + $code = ''; + $prevRegex = null; + $routes = $tree->getRoutes(); + + foreach ($routes as $i => $route) { + if ($route instanceof StaticPrefixCollection) { + $prevRegex = null; + $prefix = substr($route->getPrefix(), $prefixLen); + $state->mark += \strlen($rx = "|{$prefix}(?"); + $code .= "\n .".self::export($rx); + $state->regex .= $rx; + $code .= $this->indent($this->compileStaticPrefixCollection($route, $state, $prefixLen + \strlen($prefix), $conditions)); + $code .= "\n .')'"; + $state->regex .= ')'; + ++$state->markTail; + continue; + } + + [$name, $regex, $vars, $route, $hasTrailingSlash, $hasTrailingVar] = $route; + $compiledRoute = $route->compile(); + $vars = array_merge($state->hostVars, $vars); + + if ($compiledRoute->getRegex() === $prevRegex) { + $state->routes[$state->mark][] = $this->compileRoute($route, $name, $vars, $hasTrailingSlash, $hasTrailingVar, $conditions); + continue; + } + + $state->mark += 3 + $state->markTail + \strlen($regex) - $prefixLen; + $state->markTail = 2 + \strlen($state->mark); + $rx = sprintf('|%s(*:%s)', substr($regex, $prefixLen), $state->mark); + $code .= "\n .".self::export($rx); + $state->regex .= $rx; + + $prevRegex = $compiledRoute->getRegex(); + $state->routes[$state->mark] = [$this->compileRoute($route, $name, $vars, $hasTrailingSlash, $hasTrailingVar, $conditions)]; + } + + return $code; + } + + /** + * Compiles a single Route to PHP code used to match it against the path info. + */ + private function compileRoute(Route $route, string $name, $vars, bool $hasTrailingSlash, bool $hasTrailingVar, array &$conditions): array + { + $defaults = $route->getDefaults(); + + if (isset($defaults['_canonical_route'])) { + $name = $defaults['_canonical_route']; + unset($defaults['_canonical_route']); + } + + if ($condition = $route->getCondition()) { + $condition = $this->getExpressionLanguage()->compile($condition, ['context', 'request']); + $condition = $conditions[$condition] ?? $conditions[$condition] = (str_contains($condition, '$request') ? 1 : -1) * \count($conditions); + } else { + $condition = null; + } + + return [ + ['_route' => $name] + $defaults, + $vars, + array_flip($route->getMethods()) ?: null, + array_flip($route->getSchemes()) ?: null, + $hasTrailingSlash, + $hasTrailingVar, + $condition, + ]; + } + + private function getExpressionLanguage(): ExpressionLanguage + { + if (null === $this->expressionLanguage) { + if (!class_exists(ExpressionLanguage::class)) { + throw new \LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + } + $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); + } + + return $this->expressionLanguage; + } + + private function indent(string $code, int $level = 1): string + { + return preg_replace('/^./m', str_repeat(' ', $level).'$0', $code); + } + + /** + * @internal + */ + public static function export($value): string + { + if (null === $value) { + return 'null'; + } + if (!\is_array($value)) { + if (\is_object($value)) { + throw new \InvalidArgumentException('Symfony\Component\Routing\Route cannot contain objects.'); + } + + return str_replace("\n", '\'."\n".\'', var_export($value, true)); + } + if (!$value) { + return '[]'; + } + + $i = 0; + $export = '['; + + foreach ($value as $k => $v) { + if ($i === $k) { + ++$i; + } else { + $export .= self::export($k).' => '; + + if (\is_int($k) && $i < $k) { + $i = 1 + $k; + } + } + + $export .= self::export($v).', '; + } + + return substr_replace($export, ']', -2); + } +} diff --git a/lib/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php b/lib/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php new file mode 100644 index 0000000000..bdb7ba3d04 --- /dev/null +++ b/lib/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\NoConfigurationException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface; +use Symfony\Component\Routing\RequestContext; + +/** + * @author Nicolas Grekas + * + * @internal + * + * @property RequestContext $context + */ +trait CompiledUrlMatcherTrait +{ + private $matchHost = false; + private $staticRoutes = []; + private $regexpList = []; + private $dynamicRoutes = []; + + /** + * @var callable|null + */ + private $checkCondition; + + public function match(string $pathinfo): array + { + $allow = $allowSchemes = []; + if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) { + return $ret; + } + if ($allow) { + throw new MethodNotAllowedException(array_keys($allow)); + } + if (!$this instanceof RedirectableUrlMatcherInterface) { + throw new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo)); + } + if (!\in_array($this->context->getMethod(), ['HEAD', 'GET'], true)) { + // no-op + } elseif ($allowSchemes) { + redirect_scheme: + $scheme = $this->context->getScheme(); + $this->context->setScheme(key($allowSchemes)); + try { + if ($ret = $this->doMatch($pathinfo)) { + return $this->redirect($pathinfo, $ret['_route'], $this->context->getScheme()) + $ret; + } + } finally { + $this->context->setScheme($scheme); + } + } elseif ('/' !== $trimmedPathinfo = rtrim($pathinfo, '/') ?: '/') { + $pathinfo = $trimmedPathinfo === $pathinfo ? $pathinfo.'/' : $trimmedPathinfo; + if ($ret = $this->doMatch($pathinfo, $allow, $allowSchemes)) { + return $this->redirect($pathinfo, $ret['_route']) + $ret; + } + if ($allowSchemes) { + goto redirect_scheme; + } + } + + throw new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo)); + } + + private function doMatch(string $pathinfo, array &$allow = [], array &$allowSchemes = []): array + { + $allow = $allowSchemes = []; + $pathinfo = rawurldecode($pathinfo) ?: '/'; + $trimmedPathinfo = rtrim($pathinfo, '/') ?: '/'; + $context = $this->context; + $requestMethod = $canonicalMethod = $context->getMethod(); + + if ($this->matchHost) { + $host = strtolower($context->getHost()); + } + + if ('HEAD' === $requestMethod) { + $canonicalMethod = 'GET'; + } + $supportsRedirections = 'GET' === $canonicalMethod && $this instanceof RedirectableUrlMatcherInterface; + + foreach ($this->staticRoutes[$trimmedPathinfo] ?? [] as [$ret, $requiredHost, $requiredMethods, $requiredSchemes, $hasTrailingSlash, , $condition]) { + if ($condition && !($this->checkCondition)($condition, $context, 0 < $condition ? $request ?? $request = $this->request ?: $this->createRequest($pathinfo) : null)) { + continue; + } + + if ($requiredHost) { + if ('{' !== $requiredHost[0] ? $requiredHost !== $host : !preg_match($requiredHost, $host, $hostMatches)) { + continue; + } + if ('{' === $requiredHost[0] && $hostMatches) { + $hostMatches['_route'] = $ret['_route']; + $ret = $this->mergeDefaults($hostMatches, $ret); + } + } + + if ('/' !== $pathinfo && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) { + if ($supportsRedirections && (!$requiredMethods || isset($requiredMethods['GET']))) { + return $allow = $allowSchemes = []; + } + continue; + } + + $hasRequiredScheme = !$requiredSchemes || isset($requiredSchemes[$context->getScheme()]); + if ($hasRequiredScheme && $requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) { + $allow += $requiredMethods; + continue; + } + + if (!$hasRequiredScheme) { + $allowSchemes += $requiredSchemes; + continue; + } + + return $ret; + } + + $matchedPathinfo = $this->matchHost ? $host.'.'.$pathinfo : $pathinfo; + + foreach ($this->regexpList as $offset => $regex) { + while (preg_match($regex, $matchedPathinfo, $matches)) { + foreach ($this->dynamicRoutes[$m = (int) $matches['MARK']] as [$ret, $vars, $requiredMethods, $requiredSchemes, $hasTrailingSlash, $hasTrailingVar, $condition]) { + if (null !== $condition) { + if (0 === $condition) { // marks the last route in the regexp + continue 3; + } + if (!($this->checkCondition)($condition, $context, 0 < $condition ? $request ?? $request = $this->request ?: $this->createRequest($pathinfo) : null)) { + continue; + } + } + + $hasTrailingVar = $trimmedPathinfo !== $pathinfo && $hasTrailingVar; + + if ($hasTrailingVar && ($hasTrailingSlash || (null === $n = $matches[\count($vars)] ?? null) || '/' !== ($n[-1] ?? '/')) && preg_match($regex, $this->matchHost ? $host.'.'.$trimmedPathinfo : $trimmedPathinfo, $n) && $m === (int) $n['MARK']) { + if ($hasTrailingSlash) { + $matches = $n; + } else { + $hasTrailingVar = false; + } + } + + if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) { + if ($supportsRedirections && (!$requiredMethods || isset($requiredMethods['GET']))) { + return $allow = $allowSchemes = []; + } + continue; + } + + foreach ($vars as $i => $v) { + if (isset($matches[1 + $i])) { + $ret[$v] = $matches[1 + $i]; + } + } + + if ($requiredSchemes && !isset($requiredSchemes[$context->getScheme()])) { + $allowSchemes += $requiredSchemes; + continue; + } + + if ($requiredMethods && !isset($requiredMethods[$canonicalMethod]) && !isset($requiredMethods[$requestMethod])) { + $allow += $requiredMethods; + continue; + } + + return $ret; + } + + $regex = substr_replace($regex, 'F', $m - $offset, 1 + \strlen($m)); + $offset += \strlen($m); + } + } + + if ('/' === $pathinfo && !$allow && !$allowSchemes) { + throw new NoConfigurationException(); + } + + return []; + } +} diff --git a/lib/symfony/routing/Matcher/Dumper/DumperCollection.php b/lib/symfony/routing/Matcher/Dumper/DumperCollection.php deleted file mode 100644 index 84ee1892d3..0000000000 --- a/lib/symfony/routing/Matcher/Dumper/DumperCollection.php +++ /dev/null @@ -1,159 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Routing\Matcher\Dumper; - -/** - * Collection of routes. - * - * @author Arnaud Le Blanc - * - * @internal - */ -class DumperCollection implements \IteratorAggregate -{ - /** - * @var DumperCollection|null - */ - private $parent; - - /** - * @var DumperCollection[]|DumperRoute[] - */ - private $children = []; - - /** - * @var array - */ - private $attributes = []; - - /** - * Returns the children routes and collections. - * - * @return self[]|DumperRoute[] - */ - public function all() - { - return $this->children; - } - - /** - * Adds a route or collection. - * - * @param DumperRoute|DumperCollection The route or collection - */ - public function add($child) - { - if ($child instanceof self) { - $child->setParent($this); - } - $this->children[] = $child; - } - - /** - * Sets children. - * - * @param array $children The children - */ - public function setAll(array $children) - { - foreach ($children as $child) { - if ($child instanceof self) { - $child->setParent($this); - } - } - $this->children = $children; - } - - /** - * Returns an iterator over the children. - * - * @return \Iterator|DumperCollection[]|DumperRoute[] The iterator - */ - public function getIterator() - { - return new \ArrayIterator($this->children); - } - - /** - * Returns the root of the collection. - * - * @return self The root collection - */ - public function getRoot() - { - return (null !== $this->parent) ? $this->parent->getRoot() : $this; - } - - /** - * Returns the parent collection. - * - * @return self|null The parent collection or null if the collection has no parent - */ - protected function getParent() - { - return $this->parent; - } - - /** - * Sets the parent collection. - */ - protected function setParent(self $parent) - { - $this->parent = $parent; - } - - /** - * Returns true if the attribute is defined. - * - * @param string $name The attribute name - * - * @return bool true if the attribute is defined, false otherwise - */ - public function hasAttribute($name) - { - return \array_key_exists($name, $this->attributes); - } - - /** - * Returns an attribute by name. - * - * @param string $name The attribute name - * @param mixed $default Default value is the attribute doesn't exist - * - * @return mixed The attribute value - */ - public function getAttribute($name, $default = null) - { - return $this->hasAttribute($name) ? $this->attributes[$name] : $default; - } - - /** - * Sets an attribute by name. - * - * @param string $name The attribute name - * @param mixed $value The attribute value - */ - public function setAttribute($name, $value) - { - $this->attributes[$name] = $value; - } - - /** - * Sets multiple attributes. - * - * @param array $attributes The attributes - */ - public function setAttributes($attributes) - { - $this->attributes = $attributes; - } -} diff --git a/lib/symfony/routing/Matcher/Dumper/DumperRoute.php b/lib/symfony/routing/Matcher/Dumper/DumperRoute.php deleted file mode 100644 index c71989a3a1..0000000000 --- a/lib/symfony/routing/Matcher/Dumper/DumperRoute.php +++ /dev/null @@ -1,57 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Routing\Matcher\Dumper; - -use Symfony\Component\Routing\Route; - -/** - * Container for a Route. - * - * @author Arnaud Le Blanc - * - * @internal - */ -class DumperRoute -{ - private $name; - private $route; - - /** - * @param string $name The route name - * @param Route $route The route - */ - public function __construct($name, Route $route) - { - $this->name = $name; - $this->route = $route; - } - - /** - * Returns the route name. - * - * @return string The route name - */ - public function getName() - { - return $this->name; - } - - /** - * Returns the route. - * - * @return Route The route - */ - public function getRoute() - { - return $this->route; - } -} diff --git a/lib/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php b/lib/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php index 2a25293aa3..8e33802d36 100644 --- a/lib/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php +++ b/lib/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php @@ -24,16 +24,14 @@ interface MatcherDumperInterface * Dumps a set of routes to a string representation of executable code * that can then be used to match a request against these routes. * - * @param array $options An array of options - * - * @return string Executable code + * @return string */ public function dump(array $options = []); /** * Gets the routes to dump. * - * @return RouteCollection A RouteCollection instance + * @return RouteCollection */ public function getRoutes(); } diff --git a/lib/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php b/lib/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php deleted file mode 100644 index 335d6507ed..0000000000 --- a/lib/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php +++ /dev/null @@ -1,429 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Routing\Matcher\Dumper; - -use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; -use Symfony\Component\ExpressionLanguage\ExpressionLanguage; -use Symfony\Component\Routing\Route; -use Symfony\Component\Routing\RouteCollection; - -/** - * PhpMatcherDumper creates a PHP class able to match URLs for a given set of routes. - * - * @author Fabien Potencier - * @author Tobias Schultze - * @author Arnaud Le Blanc - */ -class PhpMatcherDumper extends MatcherDumper -{ - private $expressionLanguage; - - /** - * @var ExpressionFunctionProviderInterface[] - */ - private $expressionLanguageProviders = []; - - /** - * Dumps a set of routes to a PHP class. - * - * Available options: - * - * * class: The class name - * * base_class: The base class name - * - * @param array $options An array of options - * - * @return string A PHP class representing the matcher class - */ - public function dump(array $options = []) - { - $options = array_replace([ - 'class' => 'ProjectUrlMatcher', - 'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', - ], $options); - - // trailing slash support is only enabled if we know how to redirect the user - $interfaces = class_implements($options['base_class']); - $supportsRedirections = isset($interfaces['Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface']); - - return <<context = \$context; - } - -{$this->generateMatchMethod($supportsRedirections)} -} - -EOF; - } - - public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) - { - $this->expressionLanguageProviders[] = $provider; - } - - /** - * Generates the code for the match method implementing UrlMatcherInterface. - * - * @param bool $supportsRedirections Whether redirections are supported by the base class - * - * @return string Match method as PHP code - */ - private function generateMatchMethod($supportsRedirections) - { - $code = rtrim($this->compileRoutes($this->getRoutes(), $supportsRedirections), "\n"); - - return <<context; - \$request = \$this->request ?: \$this->createRequest(\$pathinfo); - \$requestMethod = \$canonicalMethod = \$context->getMethod(); - - if ('HEAD' === \$requestMethod) { - \$canonicalMethod = 'GET'; - } - -$code - - throw 0 < count(\$allow) ? new MethodNotAllowedException(array_unique(\$allow)) : new ResourceNotFoundException(); - } -EOF; - } - - /** - * Generates PHP code to match a RouteCollection with all its routes. - * - * @param RouteCollection $routes A RouteCollection instance - * @param bool $supportsRedirections Whether redirections are supported by the base class - * - * @return string PHP code - */ - private function compileRoutes(RouteCollection $routes, $supportsRedirections) - { - $fetchedHost = false; - $groups = $this->groupRoutesByHostRegex($routes); - $code = ''; - - foreach ($groups as $collection) { - if (null !== $regex = $collection->getAttribute('host_regex')) { - if (!$fetchedHost) { - $code .= " \$host = \$context->getHost();\n\n"; - $fetchedHost = true; - } - - $code .= sprintf(" if (preg_match(%s, \$host, \$hostMatches)) {\n", var_export($regex, true)); - } - - $tree = $this->buildStaticPrefixCollection($collection); - $groupCode = $this->compileStaticPrefixRoutes($tree, $supportsRedirections); - - if (null !== $regex) { - // apply extra indention at each line (except empty ones) - $groupCode = preg_replace('/^.{2,}$/m', ' $0', $groupCode); - $code .= $groupCode; - $code .= " }\n\n"; - } else { - $code .= $groupCode; - } - } - - // used to display the Welcome Page in apps that don't define a homepage - $code .= " if ('/' === \$pathinfo && !\$allow) {\n"; - $code .= " throw new Symfony\Component\Routing\Exception\NoConfigurationException();\n"; - $code .= " }\n"; - - return $code; - } - - private function buildStaticPrefixCollection(DumperCollection $collection) - { - $prefixCollection = new StaticPrefixCollection(); - - foreach ($collection as $dumperRoute) { - $prefix = $dumperRoute->getRoute()->compile()->getStaticPrefix(); - $prefixCollection->addRoute($prefix, $dumperRoute); - } - - $prefixCollection->optimizeGroups(); - - return $prefixCollection; - } - - /** - * Generates PHP code to match a tree of routes. - * - * @param StaticPrefixCollection $collection A StaticPrefixCollection instance - * @param bool $supportsRedirections Whether redirections are supported by the base class - * @param string $ifOrElseIf either "if" or "elseif" to influence chaining - * - * @return string PHP code - */ - private function compileStaticPrefixRoutes(StaticPrefixCollection $collection, $supportsRedirections, $ifOrElseIf = 'if') - { - $code = ''; - $prefix = $collection->getPrefix(); - - if (!empty($prefix) && '/' !== $prefix) { - $code .= sprintf(" %s (0 === strpos(\$pathinfo, %s)) {\n", $ifOrElseIf, var_export($prefix, true)); - } - - $ifOrElseIf = 'if'; - - foreach ($collection->getItems() as $route) { - if ($route instanceof StaticPrefixCollection) { - $code .= $this->compileStaticPrefixRoutes($route, $supportsRedirections, $ifOrElseIf); - $ifOrElseIf = 'elseif'; - } else { - $code .= $this->compileRoute($route[1]->getRoute(), $route[1]->getName(), $supportsRedirections, $prefix)."\n"; - $ifOrElseIf = 'if'; - } - } - - if (!empty($prefix) && '/' !== $prefix) { - $code .= " }\n\n"; - // apply extra indention at each line (except empty ones) - $code = preg_replace('/^.{2,}$/m', ' $0', $code); - } - - return $code; - } - - /** - * Compiles a single Route to PHP code used to match it against the path info. - * - * @param Route $route A Route instance - * @param string $name The name of the Route - * @param bool $supportsRedirections Whether redirections are supported by the base class - * @param string|null $parentPrefix The prefix of the parent collection used to optimize the code - * - * @return string PHP code - * - * @throws \LogicException - */ - private function compileRoute(Route $route, $name, $supportsRedirections, $parentPrefix = null) - { - $code = ''; - $compiledRoute = $route->compile(); - $conditions = []; - $hasTrailingSlash = false; - $matches = false; - $hostMatches = false; - $methods = $route->getMethods(); - - $supportsTrailingSlash = $supportsRedirections && (!$methods || \in_array('GET', $methods)); - $regex = $compiledRoute->getRegex(); - - if (!\count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P.*?)\$\1#'.('u' === substr($regex, -1) ? 'u' : ''), $regex, $m)) { - if ($supportsTrailingSlash && '/' === substr($m['url'], -1)) { - $conditions[] = sprintf('%s === $trimmedPathinfo', var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true)); - $hasTrailingSlash = true; - } else { - $conditions[] = sprintf('%s === $pathinfo', var_export(str_replace('\\', '', $m['url']), true)); - } - } else { - if ($compiledRoute->getStaticPrefix() && $compiledRoute->getStaticPrefix() !== $parentPrefix) { - $conditions[] = sprintf('0 === strpos($pathinfo, %s)', var_export($compiledRoute->getStaticPrefix(), true)); - } - - if ($supportsTrailingSlash && $pos = strpos($regex, '/$')) { - $regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2); - $hasTrailingSlash = true; - } - $conditions[] = sprintf('preg_match(%s, $pathinfo, $matches)', var_export($regex, true)); - - $matches = true; - } - - if ($compiledRoute->getHostVariables()) { - $hostMatches = true; - } - - if ($route->getCondition()) { - $conditions[] = $this->getExpressionLanguage()->compile($route->getCondition(), ['context', 'request']); - } - - $conditions = implode(' && ', $conditions); - - $code .= <<mergeDefaults(array_replace(%s), %s);\n", - implode(', ', $vars), - str_replace("\n", '', var_export($route->getDefaults(), true)) - ); - } elseif ($route->getDefaults()) { - $code .= sprintf(" \$ret = %s;\n", str_replace("\n", '', var_export(array_replace($route->getDefaults(), ['_route' => $name]), true))); - } else { - $code .= sprintf(" \$ret = ['_route' => '%s'];\n", $name); - } - - if ($hasTrailingSlash) { - $code .= <<redirect(\$rawPathinfo.'/', '$name')); - } - - -EOF; - } - - if ($methods) { - $methodVariable = \in_array('GET', $methods) ? '$canonicalMethod' : '$requestMethod'; - $methods = implode("', '", $methods); - } - - if ($schemes = $route->getSchemes()) { - if (!$supportsRedirections) { - throw new \LogicException('The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.'); - } - $schemes = str_replace("\n", '', var_export(array_flip($schemes), true)); - if ($methods) { - $code .= <<getScheme()]); - if (!in_array($methodVariable, ['$methods'])) { - if (\$hasRequiredScheme) { - \$allow = array_merge(\$allow, ['$methods']); - } - goto $gotoname; - } - if (!\$hasRequiredScheme) { - if ('GET' !== \$canonicalMethod) { - goto $gotoname; - } - - return array_replace(\$ret, \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes))); - } - - -EOF; - } else { - $code .= <<getScheme()])) { - if ('GET' !== \$canonicalMethod) { - goto $gotoname; - } - - return array_replace(\$ret, \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes))); - } - - -EOF; - } - } elseif ($methods) { - $code .= <<setAttribute('host_regex', null); - $groups->add($currentGroup); - - foreach ($routes as $name => $route) { - $hostRegex = $route->compile()->getHostRegex(); - if ($currentGroup->getAttribute('host_regex') !== $hostRegex) { - $currentGroup = new DumperCollection(); - $currentGroup->setAttribute('host_regex', $hostRegex); - $groups->add($currentGroup); - } - $currentGroup->add(new DumperRoute($name, $route)); - } - - return $groups; - } - - private function getExpressionLanguage() - { - if (null === $this->expressionLanguage) { - if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { - throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); - } - $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); - } - - return $this->expressionLanguage; - } -} diff --git a/lib/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php b/lib/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php index c8497c36fa..97bd692a5e 100644 --- a/lib/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php +++ b/lib/symfony/routing/Matcher/Dumper/StaticPrefixCollection.php @@ -11,44 +11,49 @@ namespace Symfony\Component\Routing\Matcher\Dumper; +use Symfony\Component\Routing\RouteCollection; + /** * Prefix tree of routes preserving routes order. * * @author Frank de Jonge + * @author Nicolas Grekas * * @internal */ class StaticPrefixCollection { - /** - * @var string - */ private $prefix; /** - * @var array[]|StaticPrefixCollection[] + * @var string[] + */ + private $staticPrefixes = []; + + /** + * @var string[] + */ + private $prefixes = []; + + /** + * @var array[]|self[] */ private $items = []; - /** - * @var int - */ - private $matchStart = 0; - - public function __construct($prefix = '') + public function __construct(string $prefix = '/') { $this->prefix = $prefix; } - public function getPrefix() + public function getPrefix(): string { return $this->prefix; } /** - * @return mixed[]|StaticPrefixCollection[] + * @return array[]|self[] */ - public function getItems() + public function getRoutes(): array { return $this->items; } @@ -56,183 +61,145 @@ class StaticPrefixCollection /** * Adds a route to a group. * - * @param string $prefix - * @param mixed $route + * @param array|self $route */ - public function addRoute($prefix, $route) + public function addRoute(string $prefix, $route) { - $prefix = '/' === $prefix ? $prefix : rtrim($prefix, '/'); - $this->guardAgainstAddingNotAcceptedRoutes($prefix); + [$prefix, $staticPrefix] = $this->getCommonPrefix($prefix, $prefix); - if ($this->prefix === $prefix) { - // When a prefix is exactly the same as the base we move up the match start position. - // This is needed because otherwise routes that come afterwards have higher precedence - // than a possible regular expression, which goes against the input order sorting. - $this->items[] = [$prefix, $route]; - $this->matchStart = \count($this->items); + for ($i = \count($this->items) - 1; 0 <= $i; --$i) { + $item = $this->items[$i]; + + [$commonPrefix, $commonStaticPrefix] = $this->getCommonPrefix($prefix, $this->prefixes[$i]); + + if ($this->prefix === $commonPrefix) { + // the new route and a previous one have no common prefix, let's see if they are exclusive to each others + + if ($this->prefix !== $staticPrefix && $this->prefix !== $this->staticPrefixes[$i]) { + // the new route and the previous one have exclusive static prefixes + continue; + } + + if ($this->prefix === $staticPrefix && $this->prefix === $this->staticPrefixes[$i]) { + // the new route and the previous one have no static prefix + break; + } + + if ($this->prefixes[$i] !== $this->staticPrefixes[$i] && $this->prefix === $this->staticPrefixes[$i]) { + // the previous route is non-static and has no static prefix + break; + } + + if ($prefix !== $staticPrefix && $this->prefix === $staticPrefix) { + // the new route is non-static and has no static prefix + break; + } + + continue; + } + + if ($item instanceof self && $this->prefixes[$i] === $commonPrefix) { + // the new route is a child of a previous one, let's nest it + $item->addRoute($prefix, $route); + } else { + // the new route and a previous one have a common prefix, let's merge them + $child = new self($commonPrefix); + [$child->prefixes[0], $child->staticPrefixes[0]] = $child->getCommonPrefix($this->prefixes[$i], $this->prefixes[$i]); + [$child->prefixes[1], $child->staticPrefixes[1]] = $child->getCommonPrefix($prefix, $prefix); + $child->items = [$this->items[$i], $route]; + + $this->staticPrefixes[$i] = $commonStaticPrefix; + $this->prefixes[$i] = $commonPrefix; + $this->items[$i] = $child; + } return; } - foreach ($this->items as $i => $item) { - if ($i < $this->matchStart) { - continue; - } - - if ($item instanceof self && $item->accepts($prefix)) { - $item->addRoute($prefix, $route); - - return; - } - - $group = $this->groupWithItem($item, $prefix, $route); - - if ($group instanceof self) { - $this->items[$i] = $group; - - return; - } - } - // No optimised case was found, in this case we simple add the route for possible // grouping when new routes are added. - $this->items[] = [$prefix, $route]; + $this->staticPrefixes[] = $staticPrefix; + $this->prefixes[] = $prefix; + $this->items[] = $route; } /** - * Tries to combine a route with another route or group. - * - * @param StaticPrefixCollection|array $item - * @param string $prefix - * @param mixed $route - * - * @return StaticPrefixCollection|null + * Linearizes back a set of nested routes into a collection. */ - private function groupWithItem($item, $prefix, $route) + public function populateCollection(RouteCollection $routes): RouteCollection { - $itemPrefix = $item instanceof self ? $item->prefix : $item[0]; - $commonPrefix = $this->detectCommonPrefix($prefix, $itemPrefix); - - if (!$commonPrefix) { - return null; + foreach ($this->items as $route) { + if ($route instanceof self) { + $route->populateCollection($routes); + } else { + $routes->add(...$route); + } } - $child = new self($commonPrefix); - - if ($item instanceof self) { - $child->items = [$item]; - } else { - $child->addRoute($item[0], $item[1]); - } - - $child->addRoute($prefix, $route); - - return $child; + return $routes; } /** - * Checks whether a prefix can be contained within the group. + * Gets the full and static common prefixes between two route patterns. * - * @param string $prefix - * - * @return bool Whether a prefix could belong in a given group + * The static prefix stops at last at the first opening bracket. */ - private function accepts($prefix) - { - return '' === $this->prefix || 0 === strpos($prefix, $this->prefix); - } - - /** - * Detects whether there's a common prefix relative to the group prefix and returns it. - * - * @param string $prefix - * @param string $anotherPrefix - * - * @return false|string A common prefix, longer than the base/group prefix, or false when none available - */ - private function detectCommonPrefix($prefix, $anotherPrefix) + private function getCommonPrefix(string $prefix, string $anotherPrefix): array { $baseLength = \strlen($this->prefix); - $commonLength = $baseLength; $end = min(\strlen($prefix), \strlen($anotherPrefix)); + $staticLength = null; + set_error_handler([__CLASS__, 'handleError']); - for ($i = $baseLength; $i <= $end; ++$i) { - if (substr($prefix, 0, $i) !== substr($anotherPrefix, 0, $i)) { - break; - } - - $commonLength = $i; - } - - $commonPrefix = rtrim(substr($prefix, 0, $commonLength), '/'); - - if (\strlen($commonPrefix) > $baseLength) { - return $commonPrefix; - } - - return false; - } - - /** - * Optimizes the tree by inlining items from groups with less than 3 items. - */ - public function optimizeGroups() - { - $index = -1; - - while (isset($this->items[++$index])) { - $item = $this->items[$index]; - - if ($item instanceof self) { - $item->optimizeGroups(); - - // When a group contains only two items there's no reason to optimize because at minimum - // the amount of prefix check is 2. In this case inline the group. - if ($item->shouldBeInlined()) { - array_splice($this->items, $index, 1, $item->items); - - // Lower index to pass through the same index again after optimizing. - // The first item of the replacements might be a group needing optimization. - --$index; + try { + for ($i = $baseLength; $i < $end && $prefix[$i] === $anotherPrefix[$i]; ++$i) { + if ('(' === $prefix[$i]) { + $staticLength = $staticLength ?? $i; + for ($j = 1 + $i, $n = 1; $j < $end && 0 < $n; ++$j) { + if ($prefix[$j] !== $anotherPrefix[$j]) { + break 2; + } + if ('(' === $prefix[$j]) { + ++$n; + } elseif (')' === $prefix[$j]) { + --$n; + } elseif ('\\' === $prefix[$j] && (++$j === $end || $prefix[$j] !== $anotherPrefix[$j])) { + --$j; + break; + } + } + if (0 < $n) { + break; + } + if (('?' === ($prefix[$j] ?? '') || '?' === ($anotherPrefix[$j] ?? '')) && ($prefix[$j] ?? '') !== ($anotherPrefix[$j] ?? '')) { + break; + } + $subPattern = substr($prefix, $i, $j - $i); + if ($prefix !== $anotherPrefix && !preg_match('/^\(\[[^\]]++\]\+\+\)$/', $subPattern) && !preg_match('{(?> 6) && preg_match('//u', $prefix.' '.$anotherPrefix)) { + do { + // Prevent cutting in the middle of an UTF-8 characters + --$i; + } while (0b10 === (\ord($prefix[$i]) >> 6)); + } + + return [substr($prefix, 0, $i), substr($prefix, 0, $staticLength ?? $i)]; } - private function shouldBeInlined() + public static function handleError(int $type, string $msg) { - if (\count($this->items) >= 3) { - return false; - } - - foreach ($this->items as $item) { - if ($item instanceof self) { - return true; - } - } - - foreach ($this->items as $item) { - if (\is_array($item) && $item[0] === $this->prefix) { - return false; - } - } - - return true; - } - - /** - * Guards against adding incompatible prefixes in a group. - * - * @param string $prefix - * - * @throws \LogicException when a prefix does not belong in a group - */ - private function guardAgainstAddingNotAcceptedRoutes($prefix) - { - if (!$this->accepts($prefix)) { - $message = sprintf('Could not add route with prefix %s to collection with prefix %s', $prefix, $this->prefix); - - throw new \LogicException($message); - } + return str_contains($msg, 'Compilation failed: lookbehind assertion is not fixed length'); } } diff --git a/lib/symfony/routing/Matcher/ExpressionLanguageProvider.php b/lib/symfony/routing/Matcher/ExpressionLanguageProvider.php new file mode 100644 index 0000000000..96bb7babf2 --- /dev/null +++ b/lib/symfony/routing/Matcher/ExpressionLanguageProvider.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 Symfony\Component\Routing\Matcher; + +use Symfony\Component\ExpressionLanguage\ExpressionFunction; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; +use Symfony\Contracts\Service\ServiceProviderInterface; + +/** + * Exposes functions defined in the request context to route conditions. + * + * @author Ahmed TAILOULOUTE + */ +class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface +{ + private $functions; + + public function __construct(ServiceProviderInterface $functions) + { + $this->functions = $functions; + } + + /** + * {@inheritdoc} + */ + public function getFunctions() + { + $functions = []; + + foreach ($this->functions->getProvidedServices() as $function => $type) { + $functions[] = new ExpressionFunction( + $function, + static function (...$args) use ($function) { + return sprintf('($context->getParameter(\'_functions\')->get(%s)(%s))', var_export($function, true), implode(', ', $args)); + }, + function ($values, ...$args) use ($function) { + return $values['context']->getParameter('_functions')->get($function)(...$args); + } + ); + } + + return $functions; + } + + public function get(string $function): callable + { + return $this->functions->get($function); + } +} diff --git a/lib/symfony/routing/Matcher/RedirectableUrlMatcher.php b/lib/symfony/routing/Matcher/RedirectableUrlMatcher.php index bccc78fdee..3cd7c81a6c 100644 --- a/lib/symfony/routing/Matcher/RedirectableUrlMatcher.php +++ b/lib/symfony/routing/Matcher/RedirectableUrlMatcher.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Routing\Matcher; +use Symfony\Component\Routing\Exception\ExceptionInterface; use Symfony\Component\Routing\Exception\ResourceNotFoundException; -use Symfony\Component\Routing\Route; /** * @author Fabien Potencier @@ -22,44 +22,43 @@ abstract class RedirectableUrlMatcher extends UrlMatcher implements Redirectable /** * {@inheritdoc} */ - public function match($pathinfo) + public function match(string $pathinfo) { try { - $parameters = parent::match($pathinfo); + return parent::match($pathinfo); } catch (ResourceNotFoundException $e) { - if ('/' === substr($pathinfo, -1) || !\in_array($this->context->getMethod(), ['HEAD', 'GET'])) { + if (!\in_array($this->context->getMethod(), ['HEAD', 'GET'], true)) { throw $e; } - try { - $parameters = parent::match($pathinfo.'/'); + if ($this->allowSchemes) { + redirect_scheme: + $scheme = $this->context->getScheme(); + $this->context->setScheme(current($this->allowSchemes)); + try { + $ret = parent::match($pathinfo); - return array_replace($parameters, $this->redirect($pathinfo.'/', isset($parameters['_route']) ? $parameters['_route'] : null)); - } catch (ResourceNotFoundException $e2) { + return $this->redirect($pathinfo, $ret['_route'] ?? null, $this->context->getScheme()) + $ret; + } catch (ExceptionInterface $e2) { + throw $e; + } finally { + $this->context->setScheme($scheme); + } + } elseif ('/' === $trimmedPathinfo = rtrim($pathinfo, '/') ?: '/') { throw $e; + } else { + try { + $pathinfo = $trimmedPathinfo === $pathinfo ? $pathinfo.'/' : $trimmedPathinfo; + $ret = parent::match($pathinfo); + + return $this->redirect($pathinfo, $ret['_route'] ?? null) + $ret; + } catch (ExceptionInterface $e2) { + if ($this->allowSchemes) { + goto redirect_scheme; + } + throw $e; + } } } - - return $parameters; - } - - /** - * {@inheritdoc} - */ - protected function handleRouteRequirements($pathinfo, $name, Route $route) - { - // expression condition - if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), ['context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)])) { - return [self::REQUIREMENT_MISMATCH, null]; - } - - // check HTTP scheme requirement - $scheme = $this->context->getScheme(); - $schemes = $route->getSchemes(); - if ($schemes && !$route->hasScheme($scheme)) { - return [self::ROUTE_MATCH, $this->redirect($pathinfo, $name, current($schemes))]; - } - - return [self::REQUIREMENT_MATCH, null]; } } diff --git a/lib/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php b/lib/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php index 7c27bc8796..d07f420933 100644 --- a/lib/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php +++ b/lib/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php @@ -19,13 +19,13 @@ namespace Symfony\Component\Routing\Matcher; interface RedirectableUrlMatcherInterface { /** - * Redirects the user to another URL. + * Redirects the user to another URL and returns the parameters for the redirection. * * @param string $path The path info to redirect to * @param string $route The route name that matched * @param string|null $scheme The URL scheme (null to keep the current one) * - * @return array An array of parameters + * @return array */ - public function redirect($path, $route, $scheme = null); + public function redirect(string $path, string $route, string $scheme = null); } diff --git a/lib/symfony/routing/Matcher/RequestMatcherInterface.php b/lib/symfony/routing/Matcher/RequestMatcherInterface.php index 0c193ff2d1..c05016e82b 100644 --- a/lib/symfony/routing/Matcher/RequestMatcherInterface.php +++ b/lib/symfony/routing/Matcher/RequestMatcherInterface.php @@ -26,10 +26,10 @@ interface RequestMatcherInterface /** * Tries to match a request with a set of routes. * - * If the matcher can not find information, it must throw one of the exceptions documented + * If the matcher cannot find information, it must throw one of the exceptions documented * below. * - * @return array An array of parameters + * @return array * * @throws NoConfigurationException If no routing configuration could be found * @throws ResourceNotFoundException If no matching resource could be found diff --git a/lib/symfony/routing/Matcher/TraceableUrlMatcher.php b/lib/symfony/routing/Matcher/TraceableUrlMatcher.php index 0d7087465a..9e8c4c42df 100644 --- a/lib/symfony/routing/Matcher/TraceableUrlMatcher.php +++ b/lib/symfony/routing/Matcher/TraceableUrlMatcher.php @@ -23,13 +23,13 @@ use Symfony\Component\Routing\RouteCollection; */ class TraceableUrlMatcher extends UrlMatcher { - const ROUTE_DOES_NOT_MATCH = 0; - const ROUTE_ALMOST_MATCHES = 1; - const ROUTE_MATCHES = 2; + public const ROUTE_DOES_NOT_MATCH = 0; + public const ROUTE_ALMOST_MATCHES = 1; + public const ROUTE_MATCHES = 2; protected $traces; - public function getTraces($pathinfo) + public function getTraces(string $pathinfo) { $this->traces = []; @@ -50,41 +50,30 @@ class TraceableUrlMatcher extends UrlMatcher return $traces; } - protected function matchCollection($pathinfo, RouteCollection $routes) + protected function matchCollection(string $pathinfo, RouteCollection $routes) { // HEAD and GET are equivalent as per RFC if ('HEAD' === $method = $this->context->getMethod()) { $method = 'GET'; } - $supportsTrailingSlash = '/' !== $pathinfo && '' !== $pathinfo && $this instanceof RedirectableUrlMatcherInterface; + $supportsTrailingSlash = 'GET' === $method && $this instanceof RedirectableUrlMatcherInterface; + $trimmedPathinfo = rtrim($pathinfo, '/') ?: '/'; foreach ($routes as $name => $route) { $compiledRoute = $route->compile(); - $staticPrefix = $compiledRoute->getStaticPrefix(); + $staticPrefix = rtrim($compiledRoute->getStaticPrefix(), '/'); $requiredMethods = $route->getMethods(); // check the static prefix of the URL first. Only use the more expensive preg_match when it matches - if ('' === $staticPrefix || 0 === strpos($pathinfo, $staticPrefix)) { - // no-op - } elseif (!$supportsTrailingSlash || ($requiredMethods && !\in_array('GET', $requiredMethods)) || 'GET' !== $method) { - $this->addTrace(sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route); - continue; - } elseif ('/' === substr($staticPrefix, -1) && substr($staticPrefix, 0, -1) === $pathinfo) { - $this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route); - - return $this->allow = []; - } else { + if ('' !== $staticPrefix && !str_starts_with($trimmedPathinfo, $staticPrefix)) { $this->addTrace(sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route); continue; } $regex = $compiledRoute->getRegex(); - if ($supportsTrailingSlash && $pos = strpos($regex, '/$')) { - $regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2); - $hasTrailingSlash = true; - } else { - $hasTrailingSlash = false; - } + $pos = strrpos($regex, '$'); + $hasTrailingSlash = '/' === $regex[$pos - 1]; + $regex = substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash); if (!preg_match($regex, $pathinfo, $matches)) { // does it match without any requirements? @@ -110,14 +99,14 @@ class TraceableUrlMatcher extends UrlMatcher continue; } - if ($hasTrailingSlash && '/' !== substr($pathinfo, -1)) { - if ((!$requiredMethods || \in_array('GET', $requiredMethods)) && 'GET' === $method) { - $this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route); + $hasTrailingVar = $trimmedPathinfo !== $pathinfo && preg_match('#\{\w+\}/?$#', $route->getPath()); - return $this->allow = []; + if ($hasTrailingVar && ($hasTrailingSlash || (null === $m = $matches[\count($compiledRoute->getPathVariables())] ?? null) || '/' !== ($m[-1] ?? '/')) && preg_match($regex, $trimmedPathinfo, $m)) { + if ($hasTrailingSlash) { + $matches = $m; + } else { + $hasTrailingVar = false; } - $this->addTrace(sprintf('Method "%s" does not match any of the required methods (%s)', $this->context->getMethod(), implode(', ', $requiredMethods)), self::ROUTE_ALMOST_MATCHES, $name, $route); - continue; } $hostMatches = []; @@ -129,36 +118,41 @@ class TraceableUrlMatcher extends UrlMatcher $status = $this->handleRouteRequirements($pathinfo, $name, $route); if (self::REQUIREMENT_MISMATCH === $status[0]) { - if ($route->getCondition()) { - $this->addTrace(sprintf('Condition "%s" does not evaluate to "true"', $route->getCondition()), self::ROUTE_ALMOST_MATCHES, $name, $route); - } else { - $this->addTrace(sprintf('Scheme "%s" does not match any of the required schemes (%s); the user will be redirected to first required scheme', $this->getContext()->getScheme(), implode(', ', $route->getSchemes())), self::ROUTE_ALMOST_MATCHES, $name, $route); - } - + $this->addTrace(sprintf('Condition "%s" does not evaluate to "true"', $route->getCondition()), self::ROUTE_ALMOST_MATCHES, $name, $route); continue; } - // check HTTP method requirement - if ($requiredMethods) { - if (!\in_array($method, $requiredMethods)) { - if (self::REQUIREMENT_MATCH === $status[0]) { - $this->allow = array_merge($this->allow, $requiredMethods); - } - $this->addTrace(sprintf('Method "%s" does not match any of the required methods (%s)', $this->context->getMethod(), implode(', ', $requiredMethods)), self::ROUTE_ALMOST_MATCHES, $name, $route); + if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) { + if ($supportsTrailingSlash && (!$requiredMethods || \in_array('GET', $requiredMethods))) { + $this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route); - continue; + return $this->allow = $this->allowSchemes = []; } + $this->addTrace(sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route); + continue; + } + + if ($route->getSchemes() && !$route->hasScheme($this->context->getScheme())) { + $this->allowSchemes = array_merge($this->allowSchemes, $route->getSchemes()); + $this->addTrace(sprintf('Scheme "%s" does not match any of the required schemes (%s)', $this->context->getScheme(), implode(', ', $route->getSchemes())), self::ROUTE_ALMOST_MATCHES, $name, $route); + continue; + } + + if ($requiredMethods && !\in_array($method, $requiredMethods)) { + $this->allow = array_merge($this->allow, $requiredMethods); + $this->addTrace(sprintf('Method "%s" does not match any of the required methods (%s)', $this->context->getMethod(), implode(', ', $requiredMethods)), self::ROUTE_ALMOST_MATCHES, $name, $route); + continue; } $this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route); - return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, isset($status[1]) ? $status[1] : [])); + return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, $status[1] ?? [])); } return []; } - private function addTrace($log, $level = self::ROUTE_DOES_NOT_MATCH, $name = null, $route = null) + private function addTrace(string $log, int $level = self::ROUTE_DOES_NOT_MATCH, string $name = null, Route $route = null) { $this->traces[] = [ 'log' => $log, diff --git a/lib/symfony/routing/Matcher/UrlMatcher.php b/lib/symfony/routing/Matcher/UrlMatcher.php index b8599b23ad..f076a2f5e7 100644 --- a/lib/symfony/routing/Matcher/UrlMatcher.php +++ b/lib/symfony/routing/Matcher/UrlMatcher.php @@ -28,12 +28,25 @@ use Symfony\Component\Routing\RouteCollection; */ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface { - const REQUIREMENT_MATCH = 0; - const REQUIREMENT_MISMATCH = 1; - const ROUTE_MATCH = 2; + public const REQUIREMENT_MATCH = 0; + public const REQUIREMENT_MISMATCH = 1; + public const ROUTE_MATCH = 2; + /** @var RequestContext */ protected $context; + + /** + * Collects HTTP methods that would be allowed for the request. + */ protected $allow = []; + + /** + * Collects URI schemes that would be allowed for the request. + * + * @internal + */ + protected $allowSchemes = []; + protected $routes; protected $request; protected $expressionLanguage; @@ -68,15 +81,15 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface /** * {@inheritdoc} */ - public function match($pathinfo) + public function match(string $pathinfo) { - $this->allow = []; + $this->allow = $this->allowSchemes = []; - if ($ret = $this->matchCollection(rawurldecode($pathinfo), $this->routes)) { + if ($ret = $this->matchCollection(rawurldecode($pathinfo) ?: '/', $this->routes)) { return $ret; } - if ('/' === $pathinfo && !$this->allow) { + if ('/' === $pathinfo && !$this->allow && !$this->allowSchemes) { throw new NoConfigurationException(); } @@ -105,56 +118,50 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface /** * Tries to match a URL with a set of routes. * - * @param string $pathinfo The path info to be parsed - * @param RouteCollection $routes The set of routes + * @param string $pathinfo The path info to be parsed * - * @return array An array of parameters + * @return array * * @throws NoConfigurationException If no routing configuration could be found * @throws ResourceNotFoundException If the resource could not be found * @throws MethodNotAllowedException If the resource was found but the request method is not allowed */ - protected function matchCollection($pathinfo, RouteCollection $routes) + protected function matchCollection(string $pathinfo, RouteCollection $routes) { // HEAD and GET are equivalent as per RFC if ('HEAD' === $method = $this->context->getMethod()) { $method = 'GET'; } - $supportsTrailingSlash = '/' !== $pathinfo && '' !== $pathinfo && $this instanceof RedirectableUrlMatcherInterface; + $supportsTrailingSlash = 'GET' === $method && $this instanceof RedirectableUrlMatcherInterface; + $trimmedPathinfo = rtrim($pathinfo, '/') ?: '/'; foreach ($routes as $name => $route) { $compiledRoute = $route->compile(); - $staticPrefix = $compiledRoute->getStaticPrefix(); + $staticPrefix = rtrim($compiledRoute->getStaticPrefix(), '/'); $requiredMethods = $route->getMethods(); // check the static prefix of the URL first. Only use the more expensive preg_match when it matches - if ('' === $staticPrefix || 0 === strpos($pathinfo, $staticPrefix)) { - // no-op - } elseif (!$supportsTrailingSlash || ($requiredMethods && !\in_array('GET', $requiredMethods)) || 'GET' !== $method) { - continue; - } elseif ('/' === substr($staticPrefix, -1) && substr($staticPrefix, 0, -1) === $pathinfo) { - return $this->allow = []; - } else { + if ('' !== $staticPrefix && !str_starts_with($trimmedPathinfo, $staticPrefix)) { continue; } $regex = $compiledRoute->getRegex(); - if ($supportsTrailingSlash && $pos = strpos($regex, '/$')) { - $regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2); - $hasTrailingSlash = true; - } else { - $hasTrailingSlash = false; - } + $pos = strrpos($regex, '$'); + $hasTrailingSlash = '/' === $regex[$pos - 1]; + $regex = substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash); if (!preg_match($regex, $pathinfo, $matches)) { continue; } - if ($hasTrailingSlash && '/' !== substr($pathinfo, -1)) { - if ((!$requiredMethods || \in_array('GET', $requiredMethods)) && 'GET' === $method) { - return $this->allow = []; + $hasTrailingVar = $trimmedPathinfo !== $pathinfo && preg_match('#\{\w+\}/?$#', $route->getPath()); + + if ($hasTrailingVar && ($hasTrailingSlash || (null === $m = $matches[\count($compiledRoute->getPathVariables())] ?? null) || '/' !== ($m[-1] ?? '/')) && preg_match($regex, $trimmedPathinfo, $m)) { + if ($hasTrailingSlash) { + $matches = $m; + } else { + $hasTrailingVar = false; } - continue; } $hostMatches = []; @@ -168,18 +175,24 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface continue; } - // check HTTP method requirement - if ($requiredMethods) { - if (!\in_array($method, $requiredMethods)) { - if (self::REQUIREMENT_MATCH === $status[0]) { - $this->allow = array_merge($this->allow, $requiredMethods); - } - - continue; + if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) { + if ($supportsTrailingSlash && (!$requiredMethods || \in_array('GET', $requiredMethods))) { + return $this->allow = $this->allowSchemes = []; } + continue; } - return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, isset($status[1]) ? $status[1] : [])); + if ($route->getSchemes() && !$route->hasScheme($this->context->getScheme())) { + $this->allowSchemes = array_merge($this->allowSchemes, $route->getSchemes()); + continue; + } + + if ($requiredMethods && !\in_array($method, $requiredMethods)) { + $this->allow = array_merge($this->allow, $requiredMethods); + continue; + } + + return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, $status[1] ?? [])); } return []; @@ -192,54 +205,44 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface * in matchers that do not have access to the matched Route instance * (like the PHP and Apache matcher dumpers). * - * @param Route $route The route we are matching against - * @param string $name The name of the route - * @param array $attributes An array of attributes from the matcher - * - * @return array An array of parameters + * @return array */ - protected function getAttributes(Route $route, $name, array $attributes) + protected function getAttributes(Route $route, string $name, array $attributes) { + $defaults = $route->getDefaults(); + if (isset($defaults['_canonical_route'])) { + $name = $defaults['_canonical_route']; + unset($defaults['_canonical_route']); + } $attributes['_route'] = $name; - return $this->mergeDefaults($attributes, $route->getDefaults()); + return $this->mergeDefaults($attributes, $defaults); } /** * Handles specific route requirements. * - * @param string $pathinfo The path - * @param string $name The route name - * @param Route $route The route - * * @return array The first element represents the status, the second contains additional information */ - protected function handleRouteRequirements($pathinfo, $name, Route $route) + protected function handleRouteRequirements(string $pathinfo, string $name, Route $route) { // expression condition if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), ['context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)])) { return [self::REQUIREMENT_MISMATCH, null]; } - // check HTTP scheme requirement - $scheme = $this->context->getScheme(); - $status = $route->getSchemes() && !$route->hasScheme($scheme) ? self::REQUIREMENT_MISMATCH : self::REQUIREMENT_MATCH; - - return [$status, null]; + return [self::REQUIREMENT_MATCH, null]; } /** * Get merged default parameters. * - * @param array $params The parameters - * @param array $defaults The defaults - * - * @return array Merged default parameters + * @return array */ - protected function mergeDefaults($params, $defaults) + protected function mergeDefaults(array $params, array $defaults) { foreach ($params as $key => $value) { - if (!\is_int($key)) { + if (!\is_int($key) && null !== $value) { $defaults[$key] = $value; } } @@ -250,8 +253,8 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface protected function getExpressionLanguage() { if (null === $this->expressionLanguage) { - if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { - throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + if (!class_exists(ExpressionLanguage::class)) { + throw new \LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); } $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); } @@ -262,9 +265,9 @@ class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface /** * @internal */ - protected function createRequest($pathinfo) + protected function createRequest(string $pathinfo): ?Request { - if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + if (!class_exists(Request::class)) { return null; } diff --git a/lib/symfony/routing/Matcher/UrlMatcherInterface.php b/lib/symfony/routing/Matcher/UrlMatcherInterface.php index 17f1f97b3b..0a5be97448 100644 --- a/lib/symfony/routing/Matcher/UrlMatcherInterface.php +++ b/lib/symfony/routing/Matcher/UrlMatcherInterface.php @@ -26,16 +26,16 @@ interface UrlMatcherInterface extends RequestContextAwareInterface /** * Tries to match a URL path with a set of routes. * - * If the matcher can not find information, it must throw one of the exceptions documented + * If the matcher cannot find information, it must throw one of the exceptions documented * below. * * @param string $pathinfo The path info to be parsed (raw format, i.e. not urldecoded) * - * @return array An array of parameters + * @return array * * @throws NoConfigurationException If no routing configuration could be found * @throws ResourceNotFoundException If the resource could not be found * @throws MethodNotAllowedException If the resource was found but the request method is not allowed */ - public function match($pathinfo); + public function match(string $pathinfo); } diff --git a/lib/symfony/routing/README.md b/lib/symfony/routing/README.md index a16d9d7fcb..ae8284f541 100644 --- a/lib/symfony/routing/README.md +++ b/lib/symfony/routing/README.md @@ -3,11 +3,49 @@ Routing Component The Routing component maps an HTTP request to a set of configuration variables. +Getting Started +--------------- + +``` +$ composer require symfony/routing +``` + +```php +use App\Controller\BlogController; +use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +$route = new Route('/blog/{slug}', ['_controller' => BlogController::class]); +$routes = new RouteCollection(); +$routes->add('blog_show', $route); + +$context = new RequestContext(); + +// Routing can match routes with incoming requests +$matcher = new UrlMatcher($routes, $context); +$parameters = $matcher->match('/blog/lorem-ipsum'); +// $parameters = [ +// '_controller' => 'App\Controller\BlogController', +// 'slug' => 'lorem-ipsum', +// '_route' => 'blog_show' +// ] + +// Routing can also generate URLs for a given route +$generator = new UrlGenerator($routes, $context); +$url = $generator->generate('blog_show', [ + 'slug' => 'my-blog-post', +]); +// $url = '/blog/my-blog-post' +``` + Resources --------- - * [Documentation](https://symfony.com/doc/current/components/routing.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) + * [Documentation](https://symfony.com/doc/current/routing.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/lib/symfony/routing/RequestContext.php b/lib/symfony/routing/RequestContext.php index ed50cd70d8..f54c430eeb 100644 --- a/lib/symfony/routing/RequestContext.php +++ b/lib/symfony/routing/RequestContext.php @@ -33,17 +33,7 @@ class RequestContext private $queryString; private $parameters = []; - /** - * @param string $baseUrl The base URL - * @param string $method The HTTP method - * @param string $host The HTTP host name - * @param string $scheme The HTTP scheme - * @param int $httpPort The HTTP port - * @param int $httpsPort The HTTPS port - * @param string $path The path - * @param string $queryString The query string - */ - public function __construct($baseUrl = '', $method = 'GET', $host = 'localhost', $scheme = 'http', $httpPort = 80, $httpsPort = 443, $path = '/', $queryString = '') + public function __construct(string $baseUrl = '', string $method = 'GET', string $host = 'localhost', string $scheme = 'http', int $httpPort = 80, int $httpsPort = 443, string $path = '/', string $queryString = '') { $this->setBaseUrl($baseUrl); $this->setMethod($method); @@ -55,6 +45,23 @@ class RequestContext $this->setQueryString($queryString); } + public static function fromUri(string $uri, string $host = 'localhost', string $scheme = 'http', int $httpPort = 80, int $httpsPort = 443): self + { + $uri = parse_url($uri); + $scheme = $uri['scheme'] ?? $scheme; + $host = $uri['host'] ?? $host; + + if (isset($uri['port'])) { + if ('http' === $scheme) { + $httpPort = $uri['port']; + } elseif ('https' === $scheme) { + $httpsPort = $uri['port']; + } + } + + return new self($uri['path'] ?? '', 'GET', $host, $scheme, $httpPort, $httpsPort); + } + /** * Updates the RequestContext information based on a HttpFoundation Request. * @@ -77,7 +84,7 @@ class RequestContext /** * Gets the base URL. * - * @return string The base URL + * @return string */ public function getBaseUrl() { @@ -87,13 +94,11 @@ class RequestContext /** * Sets the base URL. * - * @param string $baseUrl The base URL - * * @return $this */ - public function setBaseUrl($baseUrl) + public function setBaseUrl(string $baseUrl) { - $this->baseUrl = $baseUrl; + $this->baseUrl = rtrim($baseUrl, '/'); return $this; } @@ -101,7 +106,7 @@ class RequestContext /** * Gets the path info. * - * @return string The path info + * @return string */ public function getPathInfo() { @@ -111,11 +116,9 @@ class RequestContext /** * Sets the path info. * - * @param string $pathInfo The path info - * * @return $this */ - public function setPathInfo($pathInfo) + public function setPathInfo(string $pathInfo) { $this->pathInfo = $pathInfo; @@ -127,7 +130,7 @@ class RequestContext * * The method is always an uppercased string. * - * @return string The HTTP method + * @return string */ public function getMethod() { @@ -137,11 +140,9 @@ class RequestContext /** * Sets the HTTP method. * - * @param string $method The HTTP method - * * @return $this */ - public function setMethod($method) + public function setMethod(string $method) { $this->method = strtoupper($method); @@ -153,7 +154,7 @@ class RequestContext * * The host is always lowercased because it must be treated case-insensitive. * - * @return string The HTTP host + * @return string */ public function getHost() { @@ -163,11 +164,9 @@ class RequestContext /** * Sets the HTTP host. * - * @param string $host The HTTP host - * * @return $this */ - public function setHost($host) + public function setHost(string $host) { $this->host = strtolower($host); @@ -177,7 +176,7 @@ class RequestContext /** * Gets the HTTP scheme. * - * @return string The HTTP scheme + * @return string */ public function getScheme() { @@ -187,11 +186,9 @@ class RequestContext /** * Sets the HTTP scheme. * - * @param string $scheme The HTTP scheme - * * @return $this */ - public function setScheme($scheme) + public function setScheme(string $scheme) { $this->scheme = strtolower($scheme); @@ -201,7 +198,7 @@ class RequestContext /** * Gets the HTTP port. * - * @return int The HTTP port + * @return int */ public function getHttpPort() { @@ -211,13 +208,11 @@ class RequestContext /** * Sets the HTTP port. * - * @param int $httpPort The HTTP port - * * @return $this */ - public function setHttpPort($httpPort) + public function setHttpPort(int $httpPort) { - $this->httpPort = (int) $httpPort; + $this->httpPort = $httpPort; return $this; } @@ -225,7 +220,7 @@ class RequestContext /** * Gets the HTTPS port. * - * @return int The HTTPS port + * @return int */ public function getHttpsPort() { @@ -235,21 +230,19 @@ class RequestContext /** * Sets the HTTPS port. * - * @param int $httpsPort The HTTPS port - * * @return $this */ - public function setHttpsPort($httpsPort) + public function setHttpsPort(int $httpsPort) { - $this->httpsPort = (int) $httpsPort; + $this->httpsPort = $httpsPort; return $this; } /** - * Gets the query string. + * Gets the query string without the "?". * - * @return string The query string without the "?" + * @return string */ public function getQueryString() { @@ -259,11 +252,9 @@ class RequestContext /** * Sets the query string. * - * @param string $queryString The query string (after "?") - * * @return $this */ - public function setQueryString($queryString) + public function setQueryString(?string $queryString) { // string cast to be fault-tolerant, accepting null $this->queryString = (string) $queryString; @@ -274,7 +265,7 @@ class RequestContext /** * Returns the parameters. * - * @return array The parameters + * @return array */ public function getParameters() { @@ -298,23 +289,19 @@ class RequestContext /** * Gets a parameter value. * - * @param string $name A parameter name - * - * @return mixed The parameter value or null if nonexistent + * @return mixed */ - public function getParameter($name) + public function getParameter(string $name) { - return isset($this->parameters[$name]) ? $this->parameters[$name] : null; + return $this->parameters[$name] ?? null; } /** * Checks if a parameter value is set for the given parameter. * - * @param string $name A parameter name - * - * @return bool True if the parameter value is set, false otherwise + * @return bool */ - public function hasParameter($name) + public function hasParameter(string $name) { return \array_key_exists($name, $this->parameters); } @@ -322,15 +309,19 @@ class RequestContext /** * Sets a parameter value. * - * @param string $name A parameter name - * @param mixed $parameter The parameter value + * @param mixed $parameter The parameter value * * @return $this */ - public function setParameter($name, $parameter) + public function setParameter(string $name, $parameter) { $this->parameters[$name] = $parameter; return $this; } + + public function isSecure(): bool + { + return 'https' === $this->scheme; + } } diff --git a/lib/symfony/routing/RequestContextAwareInterface.php b/lib/symfony/routing/RequestContextAwareInterface.php index df5b9fcd47..270a2b0849 100644 --- a/lib/symfony/routing/RequestContextAwareInterface.php +++ b/lib/symfony/routing/RequestContextAwareInterface.php @@ -21,7 +21,7 @@ interface RequestContextAwareInterface /** * Gets the request context. * - * @return RequestContext The context + * @return RequestContext */ public function getContext(); } diff --git a/lib/symfony/routing/Route.php b/lib/symfony/routing/Route.php index 759b6f3b6e..c67bd2de53 100644 --- a/lib/symfony/routing/Route.php +++ b/lib/symfony/routing/Route.php @@ -45,16 +45,16 @@ class Route implements \Serializable * @param array $defaults An array of default parameter values * @param array $requirements An array of requirements for parameters (regexes) * @param array $options An array of options - * @param string $host The host pattern to match + * @param string|null $host The host pattern to match * @param string|string[] $schemes A required URI scheme or an array of restricted schemes * @param string|string[] $methods A required HTTP method or an array of restricted methods - * @param string $condition A condition that should evaluate to true for the route to match + * @param string|null $condition A condition that should evaluate to true for the route to match */ - public function __construct($path, array $defaults = [], array $requirements = [], array $options = [], $host = '', $schemes = [], $methods = [], $condition = '') + public function __construct(string $path, array $defaults = [], array $requirements = [], array $options = [], ?string $host = '', $schemes = [], $methods = [], ?string $condition = '') { $this->setPath($path); - $this->setDefaults($defaults); - $this->setRequirements($requirements); + $this->addDefaults($defaults); + $this->addRequirements($requirements); $this->setOptions($options); $this->setHost($host); $this->setSchemes($schemes); @@ -62,12 +62,9 @@ class Route implements \Serializable $this->setCondition($condition); } - /** - * {@inheritdoc} - */ - public function serialize() + public function __serialize(): array { - return serialize([ + return [ 'path' => $this->path, 'host' => $this->host, 'defaults' => $this->defaults, @@ -77,15 +74,19 @@ class Route implements \Serializable 'methods' => $this->methods, 'condition' => $this->condition, 'compiled' => $this->compiled, - ]); + ]; } /** - * {@inheritdoc} + * @internal */ - public function unserialize($serialized) + final public function serialize(): string + { + return serialize($this->__serialize()); + } + + public function __unserialize(array $data): void { - $data = unserialize($serialized); $this->path = $data['path']; $this->host = $data['host']; $this->defaults = $data['defaults']; @@ -103,9 +104,15 @@ class Route implements \Serializable } /** - * Returns the pattern for the path. - * - * @return string The path pattern + * @internal + */ + final public function unserialize($serialized) + { + $this->__unserialize(unserialize($serialized)); + } + + /** + * @return string */ public function getPath() { @@ -113,16 +120,12 @@ class Route implements \Serializable } /** - * Sets the pattern for the path. - * - * This method implements a fluent interface. - * - * @param string $pattern The path pattern - * * @return $this */ - public function setPath($pattern) + public function setPath(string $pattern) { + $pattern = $this->extractInlineDefaultsAndRequirements($pattern); + // A pattern must start with a slash and must not have multiple slashes at the beginning because the // generated path for this route would be confused with a network path, e.g. '//domain.com/path'. $this->path = '/'.ltrim(trim($pattern), '/'); @@ -132,9 +135,7 @@ class Route implements \Serializable } /** - * Returns the pattern for the host. - * - * @return string The host pattern + * @return string */ public function getHost() { @@ -142,17 +143,11 @@ class Route implements \Serializable } /** - * Sets the pattern for the host. - * - * This method implements a fluent interface. - * - * @param string $pattern The host pattern - * * @return $this */ - public function setHost($pattern) + public function setHost(?string $pattern) { - $this->host = (string) $pattern; + $this->host = $this->extractInlineDefaultsAndRequirements((string) $pattern); $this->compiled = null; return $this; @@ -162,7 +157,7 @@ class Route implements \Serializable * Returns the lowercased schemes this route is restricted to. * So an empty array means that any scheme is allowed. * - * @return string[] The schemes + * @return string[] */ public function getSchemes() { @@ -173,8 +168,6 @@ class Route implements \Serializable * Sets the schemes (e.g. 'https') this route is restricted to. * So an empty array means that any scheme is allowed. * - * This method implements a fluent interface. - * * @param string|string[] $schemes The scheme or an array of schemes * * @return $this @@ -190,11 +183,9 @@ class Route implements \Serializable /** * Checks if a scheme requirement has been set. * - * @param string $scheme - * - * @return bool true if the scheme requirement exists, otherwise false + * @return bool */ - public function hasScheme($scheme) + public function hasScheme(string $scheme) { return \in_array(strtolower($scheme), $this->schemes, true); } @@ -203,7 +194,7 @@ class Route implements \Serializable * Returns the uppercased HTTP methods this route is restricted to. * So an empty array means that any method is allowed. * - * @return string[] The methods + * @return string[] */ public function getMethods() { @@ -214,8 +205,6 @@ class Route implements \Serializable * Sets the HTTP methods (e.g. 'POST') this route is restricted to. * So an empty array means that any method is allowed. * - * This method implements a fluent interface. - * * @param string|string[] $methods The method or an array of methods * * @return $this @@ -229,9 +218,7 @@ class Route implements \Serializable } /** - * Returns the options. - * - * @return array The options + * @return array */ public function getOptions() { @@ -239,12 +226,6 @@ class Route implements \Serializable } /** - * Sets the options. - * - * This method implements a fluent interface. - * - * @param array $options The options - * * @return $this */ public function setOptions(array $options) @@ -257,12 +238,6 @@ class Route implements \Serializable } /** - * Adds options. - * - * This method implements a fluent interface. - * - * @param array $options The options - * * @return $this */ public function addOptions(array $options) @@ -278,14 +253,11 @@ class Route implements \Serializable /** * Sets an option value. * - * This method implements a fluent interface. - * - * @param string $name An option name - * @param mixed $value The option value + * @param mixed $value The option value * * @return $this */ - public function setOption($name, $value) + public function setOption(string $name, $value) { $this->options[$name] = $value; $this->compiled = null; @@ -294,33 +266,25 @@ class Route implements \Serializable } /** - * Get an option value. + * Returns the option value or null when not found. * - * @param string $name An option name - * - * @return mixed The option value or null when not given + * @return mixed */ - public function getOption($name) + public function getOption(string $name) { - return isset($this->options[$name]) ? $this->options[$name] : null; + return $this->options[$name] ?? null; } /** - * Checks if an option has been set. - * - * @param string $name An option name - * - * @return bool true if the option is set, false otherwise + * @return bool */ - public function hasOption($name) + public function hasOption(string $name) { return \array_key_exists($name, $this->options); } /** - * Returns the defaults. - * - * @return array The defaults + * @return array */ public function getDefaults() { @@ -328,12 +292,6 @@ class Route implements \Serializable } /** - * Sets the defaults. - * - * This method implements a fluent interface. - * - * @param array $defaults The defaults - * * @return $this */ public function setDefaults(array $defaults) @@ -344,16 +302,14 @@ class Route implements \Serializable } /** - * Adds defaults. - * - * This method implements a fluent interface. - * - * @param array $defaults The defaults - * * @return $this */ public function addDefaults(array $defaults) { + if (isset($defaults['_locale']) && $this->isLocalized()) { + unset($defaults['_locale']); + } + foreach ($defaults as $name => $default) { $this->defaults[$name] = $default; } @@ -363,25 +319,17 @@ class Route implements \Serializable } /** - * Gets a default value. - * - * @param string $name A variable name - * - * @return mixed The default value or null when not given + * @return mixed */ - public function getDefault($name) + public function getDefault(string $name) { - return isset($this->defaults[$name]) ? $this->defaults[$name] : null; + return $this->defaults[$name] ?? null; } /** - * Checks if a default value is set for the given variable. - * - * @param string $name A variable name - * - * @return bool true if the default value is set, false otherwise + * @return bool */ - public function hasDefault($name) + public function hasDefault(string $name) { return \array_key_exists($name, $this->defaults); } @@ -389,13 +337,16 @@ class Route implements \Serializable /** * Sets a default value. * - * @param string $name A variable name - * @param mixed $default The default value + * @param mixed $default The default value * * @return $this */ - public function setDefault($name, $default) + public function setDefault(string $name, $default) { + if ('_locale' === $name && $this->isLocalized()) { + return $this; + } + $this->defaults[$name] = $default; $this->compiled = null; @@ -403,9 +354,7 @@ class Route implements \Serializable } /** - * Returns the requirements. - * - * @return array The requirements + * @return array */ public function getRequirements() { @@ -413,12 +362,6 @@ class Route implements \Serializable } /** - * Sets the requirements. - * - * This method implements a fluent interface. - * - * @param array $requirements The requirements - * * @return $this */ public function setRequirements(array $requirements) @@ -429,16 +372,14 @@ class Route implements \Serializable } /** - * Adds requirements. - * - * This method implements a fluent interface. - * - * @param array $requirements The requirements - * * @return $this */ public function addRequirements(array $requirements) { + if (isset($requirements['_locale']) && $this->isLocalized()) { + unset($requirements['_locale']); + } + foreach ($requirements as $key => $regex) { $this->requirements[$key] = $this->sanitizeRequirement($key, $regex); } @@ -448,39 +389,30 @@ class Route implements \Serializable } /** - * Returns the requirement for the given key. - * - * @param string $key The key - * - * @return string|null The regex or null when not given + * @return string|null */ - public function getRequirement($key) + public function getRequirement(string $key) { - return isset($this->requirements[$key]) ? $this->requirements[$key] : null; + return $this->requirements[$key] ?? null; } /** - * Checks if a requirement is set for the given key. - * - * @param string $key A variable name - * - * @return bool true if a requirement is specified, false otherwise + * @return bool */ - public function hasRequirement($key) + public function hasRequirement(string $key) { return \array_key_exists($key, $this->requirements); } /** - * Sets a requirement for the given key. - * - * @param string $key The key - * @param string $regex The regex - * * @return $this */ - public function setRequirement($key, $regex) + public function setRequirement(string $key, string $regex) { + if ('_locale' === $key && $this->isLocalized()) { + return $this; + } + $this->requirements[$key] = $this->sanitizeRequirement($key, $regex); $this->compiled = null; @@ -488,9 +420,7 @@ class Route implements \Serializable } /** - * Returns the condition. - * - * @return string The condition + * @return string */ public function getCondition() { @@ -498,15 +428,9 @@ class Route implements \Serializable } /** - * Sets the condition. - * - * This method implements a fluent interface. - * - * @param string $condition The condition - * * @return $this */ - public function setCondition($condition) + public function setCondition(?string $condition) { $this->condition = (string) $condition; $this->compiled = null; @@ -517,7 +441,7 @@ class Route implements \Serializable /** * Compiles the route. * - * @return CompiledRoute A CompiledRoute instance + * @return CompiledRoute * * @throws \LogicException If the Route cannot be compiled because the * path or host pattern is invalid @@ -535,18 +459,38 @@ class Route implements \Serializable return $this->compiled = $class::compile($this); } - private function sanitizeRequirement($key, $regex) + private function extractInlineDefaultsAndRequirements(string $pattern): string { - if (!\is_string($regex)) { - throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" must be a string.', $key)); + if (false === strpbrk($pattern, '?<')) { + return $pattern; } - if ('' !== $regex && '^' === $regex[0]) { - $regex = (string) substr($regex, 1); // returns false for a single character + return preg_replace_callback('#\{(!?)(\w++)(<.*?>)?(\?[^\}]*+)?\}#', function ($m) { + if (isset($m[4][0])) { + $this->setDefault($m[2], '?' !== $m[4] ? substr($m[4], 1) : null); + } + if (isset($m[3][0])) { + $this->setRequirement($m[2], substr($m[3], 1, -1)); + } + + return '{'.$m[1].$m[2].'}'; + }, $pattern); + } + + private function sanitizeRequirement(string $key, string $regex) + { + if ('' !== $regex) { + if ('^' === $regex[0]) { + $regex = substr($regex, 1); + } elseif (0 === strpos($regex, '\\A')) { + $regex = substr($regex, 2); + } } - if ('$' === substr($regex, -1)) { + if (str_ends_with($regex, '$')) { $regex = substr($regex, 0, -1); + } elseif (\strlen($regex) - 2 === strpos($regex, '\\z')) { + $regex = substr($regex, 0, -2); } if ('' === $regex) { @@ -555,4 +499,9 @@ class Route implements \Serializable return $regex; } + + private function isLocalized(): bool + { + return isset($this->defaults['_locale']) && isset($this->defaults['_canonical_route']) && ($this->requirements['_locale'] ?? null) === preg_quote($this->defaults['_locale']); + } } diff --git a/lib/symfony/routing/RouteCollection.php b/lib/symfony/routing/RouteCollection.php index feac415513..a0700bba3d 100644 --- a/lib/symfony/routing/RouteCollection.php +++ b/lib/symfony/routing/RouteCollection.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Routing; use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Routing\Exception\InvalidArgumentException; +use Symfony\Component\Routing\Exception\RouteCircularReferenceException; /** * A RouteCollection represents a set of Route instances. @@ -22,24 +24,40 @@ use Symfony\Component\Config\Resource\ResourceInterface; * * @author Fabien Potencier * @author Tobias Schultze + * + * @implements \IteratorAggregate */ class RouteCollection implements \IteratorAggregate, \Countable { /** - * @var Route[] + * @var array */ private $routes = []; /** - * @var array + * @var array + */ + private $aliases = []; + + /** + * @var array */ private $resources = []; + /** + * @var array + */ + private $priorities = []; + public function __clone() { foreach ($this->routes as $name => $route) { $this->routes[$name] = clone $route; } + + foreach ($this->aliases as $name => $alias) { + $this->aliases[$name] = clone $alias; + } } /** @@ -49,56 +67,87 @@ class RouteCollection implements \IteratorAggregate, \Countable * * @see all() * - * @return \ArrayIterator|Route[] An \ArrayIterator object for iterating over routes + * @return \ArrayIterator */ + #[\ReturnTypeWillChange] public function getIterator() { - return new \ArrayIterator($this->routes); + return new \ArrayIterator($this->all()); } /** * Gets the number of Routes in this collection. * - * @return int The number of routes + * @return int */ + #[\ReturnTypeWillChange] public function count() { return \count($this->routes); } /** - * Adds a route. - * - * @param string $name The route name - * @param Route $route A Route instance + * @param int $priority */ - public function add($name, Route $route) + public function add(string $name, Route $route/* , int $priority = 0 */) { - unset($this->routes[$name]); + if (\func_num_args() < 3 && __CLASS__ !== static::class && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface && !$this instanceof \Mockery\MockInterface) { + trigger_deprecation('symfony/routing', '5.1', 'The "%s()" method will have a new "int $priority = 0" argument in version 6.0, not defining it is deprecated.', __METHOD__); + } + + unset($this->routes[$name], $this->priorities[$name], $this->aliases[$name]); $this->routes[$name] = $route; + + if ($priority = 3 <= \func_num_args() ? func_get_arg(2) : 0) { + $this->priorities[$name] = $priority; + } } /** * Returns all routes in this collection. * - * @return Route[] An array of routes + * @return array */ public function all() { + if ($this->priorities) { + $priorities = $this->priorities; + $keysOrder = array_flip(array_keys($this->routes)); + uksort($this->routes, static function ($n1, $n2) use ($priorities, $keysOrder) { + return (($priorities[$n2] ?? 0) <=> ($priorities[$n1] ?? 0)) ?: ($keysOrder[$n1] <=> $keysOrder[$n2]); + }); + } + return $this->routes; } /** * Gets a route by name. * - * @param string $name The route name - * - * @return Route|null A Route instance or null when not found + * @return Route|null */ - public function get($name) + public function get(string $name) { - return isset($this->routes[$name]) ? $this->routes[$name] : null; + $visited = []; + while (null !== $alias = $this->aliases[$name] ?? null) { + if (false !== $searchKey = array_search($name, $visited)) { + $visited[] = $name; + + throw new RouteCircularReferenceException($name, \array_slice($visited, $searchKey)); + } + + if ($alias->isDeprecated()) { + $deprecation = $alias->getDeprecation($name); + + trigger_deprecation($deprecation['package'], $deprecation['version'], $deprecation['message']); + } + + $visited[] = $name; + $name = $alias->getId(); + } + + return $this->routes[$name] ?? null; } /** @@ -109,7 +158,7 @@ class RouteCollection implements \IteratorAggregate, \Countable public function remove($name) { foreach ((array) $name as $n) { - unset($this->routes[$n]); + unset($this->routes[$n], $this->priorities[$n], $this->aliases[$n]); } } @@ -122,8 +171,18 @@ class RouteCollection implements \IteratorAggregate, \Countable // we need to remove all routes with the same names first because just replacing them // would not place the new route at the end of the merged array foreach ($collection->all() as $name => $route) { - unset($this->routes[$name]); + unset($this->routes[$name], $this->priorities[$name], $this->aliases[$name]); $this->routes[$name] = $route; + + if (isset($collection->priorities[$name])) { + $this->priorities[$name] = $collection->priorities[$name]; + } + } + + foreach ($collection->getAliases() as $name => $alias) { + unset($this->routes[$name], $this->priorities[$name], $this->aliases[$name]); + + $this->aliases[$name] = $alias; } foreach ($collection->getResources() as $resource) { @@ -133,12 +192,8 @@ class RouteCollection implements \IteratorAggregate, \Countable /** * Adds a prefix to the path of all child routes. - * - * @param string $prefix An optional prefix to add before each pattern of the route collection - * @param array $defaults An array of default values - * @param array $requirements An array of requirements */ - public function addPrefix($prefix, array $defaults = [], array $requirements = []) + public function addPrefix(string $prefix, array $defaults = [], array $requirements = []) { $prefix = trim(trim($prefix), '/'); @@ -154,13 +209,37 @@ class RouteCollection implements \IteratorAggregate, \Countable } /** - * Sets the host pattern on all routes. - * - * @param string $pattern The pattern - * @param array $defaults An array of default values - * @param array $requirements An array of requirements + * Adds a prefix to the name of all the routes within in the collection. */ - public function setHost($pattern, array $defaults = [], array $requirements = []) + public function addNamePrefix(string $prefix) + { + $prefixedRoutes = []; + $prefixedPriorities = []; + $prefixedAliases = []; + + foreach ($this->routes as $name => $route) { + $prefixedRoutes[$prefix.$name] = $route; + if (null !== $canonicalName = $route->getDefault('_canonical_route')) { + $route->setDefault('_canonical_route', $prefix.$canonicalName); + } + if (isset($this->priorities[$name])) { + $prefixedPriorities[$prefix.$name] = $this->priorities[$name]; + } + } + + foreach ($this->aliases as $name => $alias) { + $prefixedAliases[$prefix.$name] = $alias->withId($prefix.$alias->getId()); + } + + $this->routes = $prefixedRoutes; + $this->priorities = $prefixedPriorities; + $this->aliases = $prefixedAliases; + } + + /** + * Sets the host pattern on all routes. + */ + public function setHost(?string $pattern, array $defaults = [], array $requirements = []) { foreach ($this->routes as $route) { $route->setHost($pattern); @@ -173,10 +252,8 @@ class RouteCollection implements \IteratorAggregate, \Countable * Sets a condition on all routes. * * Existing conditions will be overridden. - * - * @param string $condition The condition */ - public function setCondition($condition) + public function setCondition(?string $condition) { foreach ($this->routes as $route) { $route->setCondition($condition); @@ -187,8 +264,6 @@ class RouteCollection implements \IteratorAggregate, \Countable * Adds defaults to all routes. * * An existing default value under the same name in a route will be overridden. - * - * @param array $defaults An array of default values */ public function addDefaults(array $defaults) { @@ -203,8 +278,6 @@ class RouteCollection implements \IteratorAggregate, \Countable * Adds requirements to all routes. * * An existing requirement under the same name in a route will be overridden. - * - * @param array $requirements An array of requirements */ public function addRequirements(array $requirements) { @@ -219,8 +292,6 @@ class RouteCollection implements \IteratorAggregate, \Countable * Adds options to all routes. * * An existing option value under the same name in a route will be overridden. - * - * @param array $options An array of options */ public function addOptions(array $options) { @@ -258,7 +329,7 @@ class RouteCollection implements \IteratorAggregate, \Countable /** * Returns an array of resources loaded to build this collection. * - * @return ResourceInterface[] An array of resources + * @return ResourceInterface[] */ public function getResources() { @@ -277,4 +348,36 @@ class RouteCollection implements \IteratorAggregate, \Countable $this->resources[$key] = $resource; } } + + /** + * Sets an alias for an existing route. + * + * @param string $name The alias to create + * @param string $alias The route to alias + * + * @throws InvalidArgumentException if the alias is for itself + */ + public function addAlias(string $name, string $alias): Alias + { + if ($name === $alias) { + throw new InvalidArgumentException(sprintf('Route alias "%s" can not reference itself.', $name)); + } + + unset($this->routes[$name], $this->priorities[$name]); + + return $this->aliases[$name] = new Alias($alias); + } + + /** + * @return array + */ + public function getAliases(): array + { + return $this->aliases; + } + + public function getAlias(string $name): ?Alias + { + return $this->aliases[$name] ?? null; + } } diff --git a/lib/symfony/routing/RouteCollectionBuilder.php b/lib/symfony/routing/RouteCollectionBuilder.php index 45f9e3d391..d7eed31eb8 100644 --- a/lib/symfony/routing/RouteCollectionBuilder.php +++ b/lib/symfony/routing/RouteCollectionBuilder.php @@ -11,14 +11,19 @@ namespace Symfony\Component\Routing; -use Symfony\Component\Config\Exception\FileLoaderLoadException; +use Symfony\Component\Config\Exception\LoaderLoadException; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; + +trigger_deprecation('symfony/routing', '5.1', 'The "%s" class is deprecated, use "%s" instead.', RouteCollectionBuilder::class, RoutingConfigurator::class); /** * Helps add and import routes into a RouteCollection. * * @author Ryan Weaver + * + * @deprecated since Symfony 5.1, use RoutingConfigurator instead */ class RouteCollectionBuilder { @@ -48,17 +53,15 @@ class RouteCollectionBuilder * * $routes->import('blog.yml', '/blog'); * - * @param mixed $resource - * @param string|null $prefix - * @param string $type + * @param mixed $resource * * @return self * - * @throws FileLoaderLoadException + * @throws LoaderLoadException */ - public function import($resource, $prefix = '/', $type = null) + public function import($resource, string $prefix = '/', string $type = null) { - /** @var RouteCollection[] $collection */ + /** @var RouteCollection[] $collections */ $collections = $this->load($resource, $type); // create a builder from the RouteCollection @@ -87,13 +90,9 @@ class RouteCollectionBuilder /** * Adds a route and returns it for future modification. * - * @param string $path The route path - * @param string $controller The route's controller - * @param string|null $name The name to give this route - * * @return Route */ - public function add($path, $controller, $name = null) + public function add(string $path, string $controller, string $name = null) { $route = new Route($path); $route->setDefault('_controller', $controller); @@ -114,11 +113,8 @@ class RouteCollectionBuilder /** * Add a RouteCollectionBuilder. - * - * @param string $prefix - * @param RouteCollectionBuilder $builder */ - public function mount($prefix, self $builder) + public function mount(string $prefix, self $builder) { $builder->prefix = trim(trim($prefix), '/'); $this->routes[] = $builder; @@ -127,11 +123,9 @@ class RouteCollectionBuilder /** * Adds a Route object to the builder. * - * @param string|null $name - * * @return $this */ - public function addRoute(Route $route, $name = null) + public function addRoute(Route $route, string $name = null) { if (null === $name) { // used as a flag to know which routes will need a name later @@ -146,11 +140,9 @@ class RouteCollectionBuilder /** * Sets the host on all embedded routes (unless already set). * - * @param string $pattern - * * @return $this */ - public function setHost($pattern) + public function setHost(?string $pattern) { $this->host = $pattern; @@ -160,11 +152,9 @@ class RouteCollectionBuilder /** * Sets a condition on all embedded routes (unless already set). * - * @param string $condition - * * @return $this */ - public function setCondition($condition) + public function setCondition(?string $condition) { $this->condition = $condition; @@ -175,12 +165,11 @@ class RouteCollectionBuilder * Sets a default value that will be added to all embedded routes (unless that * default value is already set). * - * @param string $key - * @param mixed $value + * @param mixed $value * * @return $this */ - public function setDefault($key, $value) + public function setDefault(string $key, $value) { $this->defaults[$key] = $value; @@ -191,12 +180,11 @@ class RouteCollectionBuilder * Sets a requirement that will be added to all embedded routes (unless that * requirement is already set). * - * @param string $key - * @param mixed $regex + * @param mixed $regex * * @return $this */ - public function setRequirement($key, $regex) + public function setRequirement(string $key, $regex) { $this->requirements[$key] = $regex; @@ -207,12 +195,11 @@ class RouteCollectionBuilder * Sets an option that will be added to all embedded routes (unless that * option is already set). * - * @param string $key - * @param mixed $value + * @param mixed $value * * @return $this */ - public function setOption($key, $value) + public function setOption(string $key, $value) { $this->options[$key] = $value; @@ -252,7 +239,7 @@ class RouteCollectionBuilder * * @return $this */ - private function addResource(ResourceInterface $resource) + private function addResource(ResourceInterface $resource): self { $this->resources[] = $resource; @@ -308,7 +295,9 @@ class RouteCollectionBuilder } else { /* @var self $route */ $subCollection = $route->build(); - $subCollection->addPrefix($this->prefix); + if (null !== $this->prefix) { + $subCollection->addPrefix($this->prefix); + } $routeCollection->addCollection($subCollection); } @@ -323,10 +312,8 @@ class RouteCollectionBuilder /** * Generates a route name based on details of this route. - * - * @return string */ - private function generateRouteName(Route $route) + private function generateRouteName(Route $route): string { $methods = implode('_', $route->getMethods()).'_'; @@ -348,9 +335,9 @@ class RouteCollectionBuilder * * @return RouteCollection[] * - * @throws FileLoaderLoadException If no loader is found + * @throws LoaderLoadException If no loader is found */ - private function load($resource, $type = null) + private function load($resource, string $type = null): array { if (null === $this->loader) { throw new \BadMethodCallException('Cannot import other routing resources: you must pass a LoaderInterface when constructing RouteCollectionBuilder.'); @@ -363,11 +350,11 @@ class RouteCollectionBuilder } if (null === $resolver = $this->loader->getResolver()) { - throw new FileLoaderLoadException($resource, null, null, null, $type); + throw new LoaderLoadException($resource, null, 0, null, $type); } if (false === $loader = $resolver->resolve($resource, $type)) { - throw new FileLoaderLoadException($resource, null, null, null, $type); + throw new LoaderLoadException($resource, null, 0, null, $type); } $collections = $loader->load($resource, $type); diff --git a/lib/symfony/routing/RouteCompiler.php b/lib/symfony/routing/RouteCompiler.php index abff010340..7e78c2931e 100644 --- a/lib/symfony/routing/RouteCompiler.php +++ b/lib/symfony/routing/RouteCompiler.php @@ -19,14 +19,17 @@ namespace Symfony\Component\Routing; */ class RouteCompiler implements RouteCompilerInterface { - const REGEX_DELIMITER = '#'; + /** + * @deprecated since Symfony 5.1, to be removed in 6.0 + */ + public const REGEX_DELIMITER = '#'; /** * This string defines the characters that are automatically considered separators in front of * optional placeholders (with default and no static text following). Such a single separator * can be left out together with the optional placeholder from matching and generating URLs. */ - const SEPARATORS = '/,;.:-_~+*=@|'; + public const SEPARATORS = '/,;.:-_~+*=@|'; /** * The maximum supported length of a PCRE subpattern name @@ -34,7 +37,7 @@ class RouteCompiler implements RouteCompilerInterface * * @internal */ - const VARIABLE_MAXIMUM_LENGTH = 32; + public const VARIABLE_MAXIMUM_LENGTH = 32; /** * {@inheritdoc} @@ -61,6 +64,14 @@ class RouteCompiler implements RouteCompilerInterface $hostRegex = $result['regex']; } + $locale = $route->getDefault('_locale'); + if (null !== $locale && null !== $route->getDefault('_canonical_route') && preg_quote($locale) === $route->getRequirement('_locale')) { + $requirements = $route->getRequirements(); + unset($requirements['_locale']); + $route->setRequirements($requirements); + $route->setPath(str_replace('{_locale}', $locale, $route->getPath())); + } + $path = $route->getPath(); $result = self::compilePattern($route, $path, false); @@ -92,7 +103,7 @@ class RouteCompiler implements RouteCompilerInterface ); } - private static function compilePattern(Route $route, $pattern, $isHost) + private static function compilePattern(Route $route, string $pattern, bool $isHost): array { $tokens = []; $variables = []; @@ -103,8 +114,7 @@ class RouteCompiler implements RouteCompilerInterface $needsUtf8 = $route->getOption('utf8'); if (!$needsUtf8 && $useUtf8 && preg_match('/[\x80-\xFF]/', $pattern)) { - $needsUtf8 = true; - @trigger_error(sprintf('Using UTF-8 route patterns without setting the "utf8" option is deprecated since Symfony 3.2 and will throw a LogicException in 4.0. Turn on the "utf8" route option for pattern "%s".', $pattern), \E_USER_DEPRECATED); + throw new \LogicException(sprintf('Cannot use UTF-8 route patterns without setting the "utf8" option for route "%s".', $route->getPath())); } if (!$useUtf8 && $needsUtf8) { throw new \LogicException(sprintf('Cannot mix UTF-8 requirements with non-UTF-8 pattern "%s".', $pattern)); @@ -112,9 +122,10 @@ class RouteCompiler implements RouteCompilerInterface // Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable // in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself. - preg_match_all('#\{\w+\}#', $pattern, $matches, \PREG_OFFSET_CAPTURE | \PREG_SET_ORDER); + preg_match_all('#\{(!)?(\w+)\}#', $pattern, $matches, \PREG_OFFSET_CAPTURE | \PREG_SET_ORDER); foreach ($matches as $match) { - $varName = substr($match[0][0], 1, -1); + $important = $match[1][1] >= 0; + $varName = $match[2][0]; // get all static text preceding the current variable $precedingText = substr($pattern, $pos, $match[0][1] - $pos); $pos = $match[0][1] + \strlen($match[0][0]); @@ -127,7 +138,7 @@ class RouteCompiler implements RouteCompilerInterface } else { $precedingChar = substr($precedingText, -1); } - $isSeparator = '' !== $precedingChar && false !== strpos(static::SEPARATORS, $precedingChar); + $isSeparator = '' !== $precedingChar && str_contains(static::SEPARATORS, $precedingChar); // A PCRE subpattern name must start with a non-digit. Also a PHP variable cannot start with a digit so the // variable would not be usable as a Controller action argument. @@ -144,7 +155,7 @@ class RouteCompiler implements RouteCompilerInterface if ($isSeparator && $precedingText !== $precedingChar) { $tokens[] = ['text', substr($precedingText, 0, -\strlen($precedingChar))]; - } elseif (!$isSeparator && \strlen($precedingText) > 0) { + } elseif (!$isSeparator && '' !== $precedingText) { $tokens[] = ['text', $precedingText]; } @@ -161,8 +172,8 @@ class RouteCompiler implements RouteCompilerInterface $nextSeparator = self::findNextSeparator($followingPattern, $useUtf8); $regexp = sprintf( '[^%s%s]+', - preg_quote($defaultSeparator, self::REGEX_DELIMITER), - $defaultSeparator !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator, self::REGEX_DELIMITER) : '' + preg_quote($defaultSeparator), + $defaultSeparator !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator) : '' ); if (('' !== $nextSeparator && !preg_match('#^\{\w+\}#', $followingPattern)) || '' === $followingPattern) { // When we have a separator, which is disallowed for the variable, we can optimize the regex with a possessive @@ -176,15 +187,21 @@ class RouteCompiler implements RouteCompilerInterface if (!preg_match('//u', $regexp)) { $useUtf8 = false; } elseif (!$needsUtf8 && preg_match('/[\x80-\xFF]|(?= 0; --$i) { $token = $tokens[$i]; - if ('variable' === $token[0] && $route->hasDefault($token[3])) { + // variable is optional when it is not important and has a default value + if ('variable' === $token[0] && !($token[5] ?? false) && $route->hasDefault($token[3])) { $firstOptional = $i; } else { break; @@ -210,14 +228,14 @@ class RouteCompiler implements RouteCompilerInterface for ($i = 0, $nbToken = \count($tokens); $i < $nbToken; ++$i) { $regexp .= self::computeRegexp($tokens, $i, $firstOptional); } - $regexp = self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'sD'.($isHost ? 'i' : ''); + $regexp = '{^'.$regexp.'$}sD'.($isHost ? 'i' : ''); // enable Utf8 matching if really required if ($needsUtf8) { $regexp .= 'u'; for ($i = 0, $nbToken = \count($tokens); $i < $nbToken; ++$i) { if ('variable' === $tokens[$i][0]) { - $tokens[$i][] = true; + $tokens[$i][4] = true; } } } @@ -232,10 +250,8 @@ class RouteCompiler implements RouteCompilerInterface /** * Determines the longest static prefix possible for a route. - * - * @return string The leading static part of a route's path */ - private static function determineStaticPrefix(Route $route, array $tokens) + private static function determineStaticPrefix(Route $route, array $tokens): string { if ('text' !== $tokens[0][0]) { return ($route->hasDefault($tokens[0][3]) || '/' === $tokens[0][1]) ? '' : $tokens[0][1]; @@ -251,14 +267,9 @@ class RouteCompiler implements RouteCompilerInterface } /** - * Returns the next static character in the Route pattern that will serve as a separator. - * - * @param string $pattern The route pattern - * @param bool $useUtf8 Whether the character is encoded in UTF-8 or not - * - * @return string The next static character that functions as separator (or empty string when none available) + * Returns the next static character in the Route pattern that will serve as a separator (or the empty string when none available). */ - private static function findNextSeparator($pattern, $useUtf8) + private static function findNextSeparator(string $pattern, bool $useUtf8): string { if ('' == $pattern) { // return empty string if pattern is empty or false (false which can be returned by substr) @@ -272,7 +283,7 @@ class RouteCompiler implements RouteCompilerInterface preg_match('/^./u', $pattern, $pattern); } - return false !== strpos(static::SEPARATORS, $pattern[0]) ? $pattern[0] : ''; + return str_contains(static::SEPARATORS, $pattern[0]) ? $pattern[0] : ''; } /** @@ -281,22 +292,20 @@ class RouteCompiler implements RouteCompilerInterface * @param array $tokens The route tokens * @param int $index The index of the current token * @param int $firstOptional The index of the first optional token - * - * @return string The regexp pattern for a single token */ - private static function computeRegexp(array $tokens, $index, $firstOptional) + private static function computeRegexp(array $tokens, int $index, int $firstOptional): string { $token = $tokens[$index]; if ('text' === $token[0]) { // Text tokens - return preg_quote($token[1], self::REGEX_DELIMITER); + return preg_quote($token[1]); } else { // Variable tokens if (0 === $index && 0 === $firstOptional) { // When the only token is an optional variable token, the separator is required - return sprintf('%s(?P<%s>%s)?', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]); + return sprintf('%s(?P<%s>%s)?', preg_quote($token[1]), $token[3], $token[2]); } else { - $regexp = sprintf('%s(?P<%s>%s)', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]); + $regexp = sprintf('%s(?P<%s>%s)', preg_quote($token[1]), $token[3], $token[2]); if ($index >= $firstOptional) { // Enclose each optional token in a subpattern to make it optional. // "?:" means it is non-capturing, i.e. the portion of the subject string that @@ -313,4 +322,25 @@ class RouteCompiler implements RouteCompilerInterface } } } + + private static function transformCapturingGroupsToNonCapturings(string $regexp): string + { + for ($i = 0; $i < \strlen($regexp); ++$i) { + if ('\\' === $regexp[$i]) { + ++$i; + continue; + } + if ('(' !== $regexp[$i] || !isset($regexp[$i + 2])) { + continue; + } + if ('*' === $regexp[++$i] || '?' === $regexp[$i]) { + ++$i; + continue; + } + $regexp = substr_replace($regexp, '?:', $i, 0); + ++$i; + } + + return $regexp; + } } diff --git a/lib/symfony/routing/RouteCompilerInterface.php b/lib/symfony/routing/RouteCompilerInterface.php index ddfa7ca492..9bae33a914 100644 --- a/lib/symfony/routing/RouteCompilerInterface.php +++ b/lib/symfony/routing/RouteCompilerInterface.php @@ -21,7 +21,7 @@ interface RouteCompilerInterface /** * Compiles the current route instance. * - * @return CompiledRoute A CompiledRoute instance + * @return CompiledRoute * * @throws \LogicException If the Route cannot be compiled because the * path or host pattern is invalid diff --git a/lib/symfony/routing/Router.php b/lib/symfony/routing/Router.php index a85fa6d765..89b14925e7 100644 --- a/lib/symfony/routing/Router.php +++ b/lib/symfony/routing/Router.php @@ -18,9 +18,13 @@ use Symfony\Component\Config\ConfigCacheInterface; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Generator\CompiledUrlGenerator; use Symfony\Component\Routing\Generator\ConfigurableRequirementsInterface; +use Symfony\Component\Routing\Generator\Dumper\CompiledUrlGeneratorDumper; use Symfony\Component\Routing\Generator\Dumper\GeneratorDumperInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Matcher\CompiledUrlMatcher; +use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper; use Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface; use Symfony\Component\Routing\Matcher\RequestMatcherInterface; use Symfony\Component\Routing\Matcher\UrlMatcherInterface; @@ -73,6 +77,11 @@ class Router implements RouterInterface, RequestMatcherInterface */ protected $logger; + /** + * @var string|null + */ + protected $defaultLocale; + /** * @var ConfigCacheFactoryInterface|null */ @@ -83,20 +92,19 @@ class Router implements RouterInterface, RequestMatcherInterface */ private $expressionLanguageProviders = []; + private static $cache = []; + /** - * @param LoaderInterface $loader A LoaderInterface instance - * @param mixed $resource The main resource to load - * @param array $options An array of options - * @param RequestContext $context The context - * @param LoggerInterface $logger A logger instance + * @param mixed $resource The main resource to load */ - public function __construct(LoaderInterface $loader, $resource, array $options = [], RequestContext $context = null, LoggerInterface $logger = null) + public function __construct(LoaderInterface $loader, $resource, array $options = [], RequestContext $context = null, LoggerInterface $logger = null, string $defaultLocale = null) { $this->loader = $loader; $this->resource = $resource; $this->logger = $logger; - $this->context = $context ?: new RequestContext(); + $this->context = $context ?? new RequestContext(); $this->setOptions($options); + $this->defaultLocale = $defaultLocale; } /** @@ -107,19 +115,13 @@ class Router implements RouterInterface, RequestMatcherInterface * * cache_dir: The cache directory (or null to disable caching) * * debug: Whether to enable debugging or not (false by default) * * generator_class: The name of a UrlGeneratorInterface implementation - * * generator_base_class: The base class for the dumped generator class - * * generator_cache_class: The class name for the dumped generator class * * generator_dumper_class: The name of a GeneratorDumperInterface implementation * * matcher_class: The name of a UrlMatcherInterface implementation - * * matcher_base_class: The base class for the dumped matcher class - * * matcher_dumper_class: The class name for the dumped matcher class - * * matcher_cache_class: The name of a MatcherDumperInterface implementation + * * matcher_dumper_class: The name of a MatcherDumperInterface implementation * * resource_type: Type hint for the main resource (optional) * * strict_requirements: Configure strict requirement checking for generators * implementing ConfigurableRequirementsInterface (default is true) * - * @param array $options An array of options - * * @throws \InvalidArgumentException When unsupported option is provided */ public function setOptions(array $options) @@ -127,14 +129,10 @@ class Router implements RouterInterface, RequestMatcherInterface $this->options = [ 'cache_dir' => null, 'debug' => false, - 'generator_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', - 'generator_base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', - 'generator_dumper_class' => 'Symfony\\Component\\Routing\\Generator\\Dumper\\PhpGeneratorDumper', - 'generator_cache_class' => 'ProjectUrlGenerator', - 'matcher_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', - 'matcher_base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', - 'matcher_dumper_class' => 'Symfony\\Component\\Routing\\Matcher\\Dumper\\PhpMatcherDumper', - 'matcher_cache_class' => 'ProjectUrlMatcher', + 'generator_class' => CompiledUrlGenerator::class, + 'generator_dumper_class' => CompiledUrlGeneratorDumper::class, + 'matcher_class' => CompiledUrlMatcher::class, + 'matcher_dumper_class' => CompiledUrlMatcherDumper::class, 'resource_type' => null, 'strict_requirements' => true, ]; @@ -157,12 +155,11 @@ class Router implements RouterInterface, RequestMatcherInterface /** * Sets an option. * - * @param string $key The key - * @param mixed $value The value + * @param mixed $value The value * * @throws \InvalidArgumentException */ - public function setOption($key, $value) + public function setOption(string $key, $value) { if (!\array_key_exists($key, $this->options)) { throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key)); @@ -174,13 +171,11 @@ class Router implements RouterInterface, RequestMatcherInterface /** * Gets an option value. * - * @param string $key The key - * - * @return mixed The value + * @return mixed * * @throws \InvalidArgumentException */ - public function getOption($key) + public function getOption(string $key) { if (!\array_key_exists($key, $this->options)) { throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key)); @@ -235,7 +230,7 @@ class Router implements RouterInterface, RequestMatcherInterface /** * {@inheritdoc} */ - public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH) + public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH) { return $this->getGenerator()->generate($name, $parameters, $referenceType); } @@ -243,7 +238,7 @@ class Router implements RouterInterface, RequestMatcherInterface /** * {@inheritdoc} */ - public function match($pathinfo) + public function match(string $pathinfo) { return $this->getMatcher()->match($pathinfo); } @@ -273,8 +268,13 @@ class Router implements RouterInterface, RequestMatcherInterface return $this->matcher; } - if (null === $this->options['cache_dir'] || null === $this->options['matcher_cache_class']) { - $this->matcher = new $this->options['matcher_class']($this->getRouteCollection(), $this->context); + if (null === $this->options['cache_dir']) { + $routes = $this->getRouteCollection(); + $compiled = is_a($this->options['matcher_class'], CompiledUrlMatcher::class, true); + if ($compiled) { + $routes = (new CompiledUrlMatcherDumper($routes))->getCompiledRoutes(); + } + $this->matcher = new $this->options['matcher_class']($routes, $this->context); if (method_exists($this->matcher, 'addExpressionLanguageProvider')) { foreach ($this->expressionLanguageProviders as $provider) { $this->matcher->addExpressionLanguageProvider($provider); @@ -284,7 +284,7 @@ class Router implements RouterInterface, RequestMatcherInterface return $this->matcher; } - $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$this->options['matcher_cache_class'].'.php', + $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/url_matching_routes.php', function (ConfigCacheInterface $cache) { $dumper = $this->getMatcherDumperInstance(); if (method_exists($dumper, 'addExpressionLanguageProvider')) { @@ -293,26 +293,17 @@ class Router implements RouterInterface, RequestMatcherInterface } } - $options = [ - 'class' => $this->options['matcher_cache_class'], - 'base_class' => $this->options['matcher_base_class'], - ]; - - $cache->write($dumper->dump($options), $this->getRouteCollection()->getResources()); + $cache->write($dumper->dump(), $this->getRouteCollection()->getResources()); } ); - if (!class_exists($this->options['matcher_cache_class'], false)) { - require_once $cache->getPath(); - } - - return $this->matcher = new $this->options['matcher_cache_class']($this->context); + return $this->matcher = new $this->options['matcher_class'](self::getCompiledRoutes($cache->getPath()), $this->context); } /** * Gets the UrlGenerator instance associated with this Router. * - * @return UrlGeneratorInterface A UrlGeneratorInterface instance + * @return UrlGeneratorInterface */ public function getGenerator() { @@ -320,27 +311,26 @@ class Router implements RouterInterface, RequestMatcherInterface return $this->generator; } - if (null === $this->options['cache_dir'] || null === $this->options['generator_cache_class']) { - $this->generator = new $this->options['generator_class']($this->getRouteCollection(), $this->context, $this->logger); + if (null === $this->options['cache_dir']) { + $routes = $this->getRouteCollection(); + $aliases = []; + $compiled = is_a($this->options['generator_class'], CompiledUrlGenerator::class, true); + if ($compiled) { + $generatorDumper = new CompiledUrlGeneratorDumper($routes); + $routes = $generatorDumper->getCompiledRoutes(); + $aliases = $generatorDumper->getCompiledAliases(); + } + $this->generator = new $this->options['generator_class'](array_merge($routes, $aliases), $this->context, $this->logger, $this->defaultLocale); } else { - $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$this->options['generator_cache_class'].'.php', + $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/url_generating_routes.php', function (ConfigCacheInterface $cache) { $dumper = $this->getGeneratorDumperInstance(); - $options = [ - 'class' => $this->options['generator_cache_class'], - 'base_class' => $this->options['generator_base_class'], - ]; - - $cache->write($dumper->dump($options), $this->getRouteCollection()->getResources()); + $cache->write($dumper->dump(), $this->getRouteCollection()->getResources()); } ); - if (!class_exists($this->options['generator_cache_class'], false)) { - require_once $cache->getPath(); - } - - $this->generator = new $this->options['generator_cache_class']($this->context, $this->logger); + $this->generator = new $this->options['generator_class'](self::getCompiledRoutes($cache->getPath()), $this->context, $this->logger, $this->defaultLocale); } if ($this->generator instanceof ConfigurableRequirementsInterface) { @@ -374,10 +364,8 @@ class Router implements RouterInterface, RequestMatcherInterface /** * Provides the ConfigCache factory implementation, falling back to a * default implementation if necessary. - * - * @return ConfigCacheFactoryInterface */ - private function getConfigCacheFactory() + private function getConfigCacheFactory(): ConfigCacheFactoryInterface { if (null === $this->configCacheFactory) { $this->configCacheFactory = new ConfigCacheFactory($this->options['debug']); @@ -385,4 +373,21 @@ class Router implements RouterInterface, RequestMatcherInterface return $this->configCacheFactory; } + + private static function getCompiledRoutes(string $path): array + { + if ([] === self::$cache && \function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOLEAN))) { + self::$cache = null; + } + + if (null === self::$cache) { + return require $path; + } + + if (isset(self::$cache[$path])) { + return self::$cache[$path]; + } + + return self::$cache[$path] = require $path; + } } diff --git a/lib/symfony/routing/RouterInterface.php b/lib/symfony/routing/RouterInterface.php index 8a3e33dc22..6912f8a15b 100644 --- a/lib/symfony/routing/RouterInterface.php +++ b/lib/symfony/routing/RouterInterface.php @@ -29,7 +29,7 @@ interface RouterInterface extends UrlMatcherInterface, UrlGeneratorInterface * WARNING: This method should never be used at runtime as it is SLOW. * You might use it in a cache warmer though. * - * @return RouteCollection A RouteCollection instance + * @return RouteCollection */ public function getRouteCollection(); } diff --git a/lib/symfony/routing/composer.json b/lib/symfony/routing/composer.json index 2c686ba739..b978c06263 100644 --- a/lib/symfony/routing/composer.json +++ b/lib/symfony/routing/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/routing", "type": "library", - "description": "Symfony Routing Component", + "description": "Maps an HTTP request to a set of configuration variables", "keywords": ["routing", "router", "URL", "URI"], "homepage": "https://symfony.com", "license": "MIT", @@ -16,28 +16,30 @@ } ], "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" }, "require-dev": { - "symfony/config": "^3.3.1|~4.0", - "symfony/http-foundation": "~2.8|~3.0|~4.0", - "symfony/yaml": "~3.4|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/dependency-injection": "~3.3|~4.0", - "doctrine/annotations": "~1.0", - "psr/log": "~1.0" + "symfony/config": "^5.3|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/yaml": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "doctrine/annotations": "^1.12", + "psr/log": "^1|^2|^3" }, "conflict": { - "symfony/config": "<3.3.1", - "symfony/dependency-injection": "<3.3", - "symfony/yaml": "<3.4" + "doctrine/annotations": "<1.12", + "symfony/config": "<5.3", + "symfony/dependency-injection": "<4.4", + "symfony/yaml": "<4.4" }, "suggest": { "symfony/http-foundation": "For using a Symfony Request object", "symfony/config": "For using the all-in-one router or any loader", "symfony/yaml": "For using the YAML loader", - "symfony/expression-language": "For using expression matching", - "doctrine/annotations": "For using the annotation loader" + "symfony/expression-language": "For using expression matching" }, "autoload": { "psr-4": { "Symfony\\Component\\Routing\\": "" }, diff --git a/lib/symfony/routing/phpunit.xml.dist b/lib/symfony/routing/phpunit.xml.dist deleted file mode 100644 index df742eab00..0000000000 --- a/lib/symfony/routing/phpunit.xml.dist +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - ./Tests/ - - - - - - ./ - - ./Tests - ./vendor - - - - diff --git a/lib/symfony/css-selector/.gitignore b/lib/symfony/service-contracts/.gitignore similarity index 100% rename from lib/symfony/css-selector/.gitignore rename to lib/symfony/service-contracts/.gitignore diff --git a/lib/symfony/service-contracts/Attribute/Required.php b/lib/symfony/service-contracts/Attribute/Required.php new file mode 100644 index 0000000000..9df851189a --- /dev/null +++ b/lib/symfony/service-contracts/Attribute/Required.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service\Attribute; + +/** + * A required dependency. + * + * This attribute indicates that a property holds a required dependency. The annotated property or method should be + * considered during the instantiation process of the containing class. + * + * @author Alexander M. Turek + */ +#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] +final class Required +{ +} diff --git a/lib/symfony/service-contracts/Attribute/SubscribedService.php b/lib/symfony/service-contracts/Attribute/SubscribedService.php new file mode 100644 index 0000000000..10d1bc38e8 --- /dev/null +++ b/lib/symfony/service-contracts/Attribute/SubscribedService.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\Contracts\Service\Attribute; + +use Symfony\Contracts\Service\ServiceSubscriberTrait; + +/** + * Use with {@see ServiceSubscriberTrait} to mark a method's return type + * as a subscribed service. + * + * @author Kevin Bond + */ +#[\Attribute(\Attribute::TARGET_METHOD)] +final class SubscribedService +{ + /** + * @param string|null $key The key to use for the service + * If null, use "ClassName::methodName" + */ + public function __construct( + public ?string $key = null + ) { + } +} diff --git a/lib/symfony/service-contracts/CHANGELOG.md b/lib/symfony/service-contracts/CHANGELOG.md new file mode 100644 index 0000000000..7932e26132 --- /dev/null +++ b/lib/symfony/service-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/lib/symfony/service-contracts/LICENSE b/lib/symfony/service-contracts/LICENSE new file mode 100644 index 0000000000..74cdc2dbf6 --- /dev/null +++ b/lib/symfony/service-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2022 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/lib/symfony/service-contracts/README.md b/lib/symfony/service-contracts/README.md new file mode 100644 index 0000000000..41e054a101 --- /dev/null +++ b/lib/symfony/service-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Service Contracts +========================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful - and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/lib/symfony/service-contracts/ResetInterface.php b/lib/symfony/service-contracts/ResetInterface.php new file mode 100644 index 0000000000..1af1075eee --- /dev/null +++ b/lib/symfony/service-contracts/ResetInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +/** + * Provides a way to reset an object to its initial state. + * + * When calling the "reset()" method on an object, it should be put back to its + * initial state. This usually means clearing any internal buffers and forwarding + * the call to internal dependencies. All properties of the object should be put + * back to the same state it had when it was first ready to use. + * + * This method could be called, for example, to recycle objects that are used as + * services, so that they can be used to handle several requests in the same + * process loop (note that we advise making your services stateless instead of + * implementing this interface when possible.) + */ +interface ResetInterface +{ + public function reset(); +} diff --git a/lib/symfony/service-contracts/ServiceLocatorTrait.php b/lib/symfony/service-contracts/ServiceLocatorTrait.php new file mode 100644 index 0000000000..74dfa4362e --- /dev/null +++ b/lib/symfony/service-contracts/ServiceLocatorTrait.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(ContainerExceptionInterface::class); +class_exists(NotFoundExceptionInterface::class); + +/** + * A trait to help implement ServiceProviderInterface. + * + * @author Robin Chalas + * @author Nicolas Grekas + */ +trait ServiceLocatorTrait +{ + private $factories; + private $loading = []; + private $providedTypes; + + /** + * @param callable[] $factories + */ + public function __construct(array $factories) + { + $this->factories = $factories; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function has(string $id) + { + return isset($this->factories[$id]); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function get(string $id) + { + if (!isset($this->factories[$id])) { + throw $this->createNotFoundException($id); + } + + if (isset($this->loading[$id])) { + $ids = array_values($this->loading); + $ids = \array_slice($this->loading, array_search($id, $ids)); + $ids[] = $id; + + throw $this->createCircularReferenceException($id, $ids); + } + + $this->loading[$id] = $id; + try { + return $this->factories[$id]($this); + } finally { + unset($this->loading[$id]); + } + } + + /** + * {@inheritdoc} + */ + public function getProvidedServices(): array + { + if (null === $this->providedTypes) { + $this->providedTypes = []; + + foreach ($this->factories as $name => $factory) { + if (!\is_callable($factory)) { + $this->providedTypes[$name] = '?'; + } else { + $type = (new \ReflectionFunction($factory))->getReturnType(); + + $this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').($type instanceof \ReflectionNamedType ? $type->getName() : $type) : '?'; + } + } + } + + return $this->providedTypes; + } + + private function createNotFoundException(string $id): NotFoundExceptionInterface + { + if (!$alternatives = array_keys($this->factories)) { + $message = 'is empty...'; + } else { + $last = array_pop($alternatives); + if ($alternatives) { + $message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last); + } else { + $message = sprintf('only knows about the "%s" service.', $last); + } + } + + if ($this->loading) { + $message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message); + } else { + $message = sprintf('Service "%s" not found: the current service locator %s', $id, $message); + } + + return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface { + }; + } + + private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface + { + return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface { + }; + } +} diff --git a/lib/symfony/service-contracts/ServiceProviderInterface.php b/lib/symfony/service-contracts/ServiceProviderInterface.php new file mode 100644 index 0000000000..c60ad0bd4b --- /dev/null +++ b/lib/symfony/service-contracts/ServiceProviderInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; + +/** + * A ServiceProviderInterface exposes the identifiers and the types of services provided by a container. + * + * @author Nicolas Grekas + * @author Mateusz Sip + */ +interface ServiceProviderInterface extends ContainerInterface +{ + /** + * Returns an associative array of service types keyed by the identifiers provided by the current container. + * + * Examples: + * + * * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface + * * ['foo' => '?'] means the container provides service name "foo" of unspecified type + * * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null + * + * @return string[] The provided service types, keyed by service names + */ + public function getProvidedServices(): array; +} diff --git a/lib/symfony/dependency-injection/ServiceSubscriberInterface.php b/lib/symfony/service-contracts/ServiceSubscriberInterface.php similarity index 82% rename from lib/symfony/dependency-injection/ServiceSubscriberInterface.php rename to lib/symfony/service-contracts/ServiceSubscriberInterface.php index 10c2387544..098ab908cd 100644 --- a/lib/symfony/dependency-injection/ServiceSubscriberInterface.php +++ b/lib/symfony/service-contracts/ServiceSubscriberInterface.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\DependencyInjection; +namespace Symfony\Contracts\Service; /** * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method. @@ -35,16 +35,19 @@ interface ServiceSubscriberInterface * * * ['logger' => 'Psr\Log\LoggerInterface'] means the objects use the "logger" name * internally to fetch a service which must implement Psr\Log\LoggerInterface. + * * ['loggers' => 'Psr\Log\LoggerInterface[]'] means the objects use the "loggers" name + * internally to fetch an iterable of Psr\Log\LoggerInterface instances. * * ['Psr\Log\LoggerInterface'] is a shortcut for * * ['Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface'] * * otherwise: * * * ['logger' => '?Psr\Log\LoggerInterface'] denotes an optional dependency + * * ['loggers' => '?Psr\Log\LoggerInterface[]'] denotes an optional iterable dependency * * ['?Psr\Log\LoggerInterface'] is a shortcut for * * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface'] * - * @return array The required service types, optionally keyed by service names + * @return string[] The required service types, optionally keyed by service names */ public static function getSubscribedServices(); } diff --git a/lib/symfony/service-contracts/ServiceSubscriberTrait.php b/lib/symfony/service-contracts/ServiceSubscriberTrait.php new file mode 100644 index 0000000000..16e3eb2c19 --- /dev/null +++ b/lib/symfony/service-contracts/ServiceSubscriberTrait.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Service; + +use Psr\Container\ContainerInterface; +use Symfony\Contracts\Service\Attribute\SubscribedService; + +/** + * Implementation of ServiceSubscriberInterface that determines subscribed services from + * method return types. Service ids are available as "ClassName::methodName". + * + * @author Kevin Bond + */ +trait ServiceSubscriberTrait +{ + /** @var ContainerInterface */ + protected $container; + + /** + * {@inheritdoc} + */ + public static function getSubscribedServices(): array + { + $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : []; + $attributeOptIn = false; + + if (\PHP_VERSION_ID >= 80000) { + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if (self::class !== $method->getDeclaringClass()->name) { + continue; + } + + if (!$attribute = $method->getAttributes(SubscribedService::class)[0] ?? null) { + continue; + } + + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + throw new \LogicException(sprintf('Cannot use "%s" on method "%s::%s()" (can only be used on non-static, non-abstract methods with no parameters).', SubscribedService::class, self::class, $method->name)); + } + + if (!$returnType = $method->getReturnType()) { + throw new \LogicException(sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $method->name, self::class)); + } + + $serviceId = $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType; + + if ($returnType->allowsNull()) { + $serviceId = '?'.$serviceId; + } + + $services[$attribute->newInstance()->key ?? self::class.'::'.$method->name] = $serviceId; + $attributeOptIn = true; + } + } + + if (!$attributeOptIn) { + foreach ((new \ReflectionClass(self::class))->getMethods() as $method) { + if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) { + continue; + } + + if (self::class !== $method->getDeclaringClass()->name) { + continue; + } + + if (!($returnType = $method->getReturnType()) instanceof \ReflectionNamedType) { + continue; + } + + if ($returnType->isBuiltin()) { + continue; + } + + if (\PHP_VERSION_ID >= 80000) { + trigger_deprecation('symfony/service-contracts', '2.5', 'Using "%s" in "%s" without using the "%s" attribute on any method is deprecated.', ServiceSubscriberTrait::class, self::class, SubscribedService::class); + } + + $services[self::class.'::'.$method->name] = '?'.($returnType instanceof \ReflectionNamedType ? $returnType->getName() : $returnType); + } + } + + return $services; + } + + /** + * @required + * + * @return ContainerInterface|null + */ + public function setContainer(ContainerInterface $container) + { + $this->container = $container; + + if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) { + return parent::setContainer($container); + } + + return null; + } +} diff --git a/lib/symfony/service-contracts/composer.json b/lib/symfony/service-contracts/composer.json new file mode 100644 index 0000000000..f058637010 --- /dev/null +++ b/lib/symfony/service-contracts/composer.json @@ -0,0 +1,42 @@ +{ + "name": "symfony/service-contracts", + "type": "library", + "description": "Generic abstractions related to writing services", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "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.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Service\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/lib/symfony/stopwatch/.gitignore b/lib/symfony/stopwatch/.gitignore deleted file mode 100644 index c49a5d8df5..0000000000 --- a/lib/symfony/stopwatch/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/lib/symfony/stopwatch/CHANGELOG.md b/lib/symfony/stopwatch/CHANGELOG.md index 36d0c25f1a..f2fd7d0f03 100644 --- a/lib/symfony/stopwatch/CHANGELOG.md +++ b/lib/symfony/stopwatch/CHANGELOG.md @@ -1,6 +1,21 @@ CHANGELOG ========= +5.2 +--- + + * Add `name` argument to the `StopWatchEvent` constructor, accessible via a new `StopwatchEvent::getName()` + +5.0.0 +----- + + * Removed support for passing `null` as 1st (`$id`) argument of `Section::get()` method, pass a valid child section identifier instead. + +4.4.0 +----- + + * Deprecated passing `null` as 1st (`$id`) argument of `Section::get()` method, pass a valid child section identifier instead. + 3.4.0 ----- diff --git a/lib/symfony/stopwatch/LICENSE b/lib/symfony/stopwatch/LICENSE index 9e936ec044..88bf75bb4d 100644 --- a/lib/symfony/stopwatch/LICENSE +++ b/lib/symfony/stopwatch/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2020 Fabien Potencier +Copyright (c) 2004-2022 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/lib/symfony/stopwatch/README.md b/lib/symfony/stopwatch/README.md index eb0ebb3fa8..13a9dfa5f4 100644 --- a/lib/symfony/stopwatch/README.md +++ b/lib/symfony/stopwatch/README.md @@ -3,11 +3,40 @@ Stopwatch Component The Stopwatch component provides a way to profile code. +Getting Started +--------------- + +``` +$ composer require symfony/stopwatch +``` + +```php +use Symfony\Component\Stopwatch\Stopwatch; + +$stopwatch = new Stopwatch(); + +// optionally group events into sections (e.g. phases of the execution) +$stopwatch->openSection(); + +// starts event named 'eventName' +$stopwatch->start('eventName'); + +// ... run your code here + +// optionally, start a new "lap" time +$stopwatch->lap('foo'); + +// ... run your code here + +$event = $stopwatch->stop('eventName'); + +$stopwatch->stopSection('phase_1'); +``` + Resources --------- - * [Documentation](https://symfony.com/doc/current/components/stopwatch.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) + * [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/lib/symfony/stopwatch/Section.php b/lib/symfony/stopwatch/Section.php index f0c5e8b44e..56cdc6f125 100644 --- a/lib/symfony/stopwatch/Section.php +++ b/lib/symfony/stopwatch/Section.php @@ -47,20 +47,18 @@ class Section * @param float|null $origin Set the origin of the events in this section, use null to set their origin to their start time * @param bool $morePrecision If true, time is stored as float to keep the original microsecond precision */ - public function __construct($origin = null, $morePrecision = false) + public function __construct(float $origin = null, bool $morePrecision = false) { - $this->origin = is_numeric($origin) ? $origin : null; + $this->origin = $origin; $this->morePrecision = $morePrecision; } /** * Returns the child section. * - * @param string $id The child section identifier - * - * @return self|null The child section or null when none found + * @return self|null */ - public function get($id) + public function get(string $id) { foreach ($this->children as $child) { if ($id === $child->getId()) { @@ -78,9 +76,9 @@ class Section * * @return self */ - public function open($id) + public function open(?string $id) { - if (null === $session = $this->get($id)) { + if (null === $id || null === $session = $this->get($id)) { $session = $this->children[] = new self(microtime(true) * 1000, $this->morePrecision); } @@ -88,7 +86,7 @@ class Section } /** - * @return string The identifier of the section + * @return string */ public function getId() { @@ -98,11 +96,9 @@ class Section /** * Sets the session identifier. * - * @param string $id The session identifier - * * @return $this */ - public function setId($id) + public function setId(string $id) { $this->id = $id; @@ -112,15 +108,12 @@ class Section /** * Starts an event. * - * @param string $name The event name - * @param string|null $category The event category - * - * @return StopwatchEvent The event + * @return StopwatchEvent */ - public function startEvent($name, $category) + public function startEvent(string $name, ?string $category) { if (!isset($this->events[$name])) { - $this->events[$name] = new StopwatchEvent($this->origin ?: microtime(true) * 1000, $category, $this->morePrecision); + $this->events[$name] = new StopwatchEvent($this->origin ?: microtime(true) * 1000, $category, $this->morePrecision, $name); } return $this->events[$name]->start(); @@ -129,11 +122,9 @@ class Section /** * Checks if the event was started. * - * @param string $name The event name - * * @return bool */ - public function isEventStarted($name) + public function isEventStarted(string $name) { return isset($this->events[$name]) && $this->events[$name]->isStarted(); } @@ -141,13 +132,11 @@ class Section /** * Stops an event. * - * @param string $name The event name - * - * @return StopwatchEvent The event + * @return StopwatchEvent * * @throws \LogicException When the event has not been started */ - public function stopEvent($name) + public function stopEvent(string $name) { if (!isset($this->events[$name])) { throw new \LogicException(sprintf('Event "%s" is not started.', $name)); @@ -159,13 +148,11 @@ class Section /** * Stops then restarts an event. * - * @param string $name The event name - * - * @return StopwatchEvent The event + * @return StopwatchEvent * * @throws \LogicException When the event has not been started */ - public function lap($name) + public function lap(string $name) { return $this->stopEvent($name)->start(); } @@ -173,13 +160,11 @@ class Section /** * Returns a specific event by name. * - * @param string $name The event name - * - * @return StopwatchEvent The event + * @return StopwatchEvent * * @throws \LogicException When the event is not known */ - public function getEvent($name) + public function getEvent(string $name) { if (!isset($this->events[$name])) { throw new \LogicException(sprintf('Event "%s" is not known.', $name)); @@ -191,7 +176,7 @@ class Section /** * Returns the events from this section. * - * @return StopwatchEvent[] An array of StopwatchEvent instances + * @return StopwatchEvent[] */ public function getEvents() { diff --git a/lib/symfony/stopwatch/Stopwatch.php b/lib/symfony/stopwatch/Stopwatch.php index 128e9488ba..2f46c59981 100644 --- a/lib/symfony/stopwatch/Stopwatch.php +++ b/lib/symfony/stopwatch/Stopwatch.php @@ -11,12 +11,17 @@ namespace Symfony\Component\Stopwatch; +use Symfony\Contracts\Service\ResetInterface; + +// Help opcache.preload discover always-needed symbols +class_exists(Section::class); + /** * Stopwatch provides a way to profile code. * * @author Fabien Potencier */ -class Stopwatch +class Stopwatch implements ResetInterface { /** * @var bool @@ -36,7 +41,7 @@ class Stopwatch /** * @param bool $morePrecision If true, time is stored as float to keep the original microsecond precision */ - public function __construct($morePrecision = false) + public function __construct(bool $morePrecision = false) { $this->morePrecision = $morePrecision; $this->reset(); @@ -57,12 +62,12 @@ class Stopwatch * * @throws \LogicException When the section to re-open is not reachable */ - public function openSection($id = null) + public function openSection(string $id = null) { $current = end($this->activeSections); if (null !== $id && null === $current->get($id)) { - throw new \LogicException(sprintf('The section "%s" has been started at an other level and can not be opened.', $id)); + throw new \LogicException(sprintf('The section "%s" has been started at an other level and cannot be opened.', $id)); } $this->start('__section__.child', 'section'); @@ -77,11 +82,9 @@ class Stopwatch * * @see getSectionEvents() * - * @param string $id The identifier of the section - * * @throws \LogicException When there's no started section to be stopped */ - public function stopSection($id) + public function stopSection(string $id) { $this->stop('__section__'); @@ -96,12 +99,9 @@ class Stopwatch /** * Starts an event. * - * @param string $name The event name - * @param string|null $category The event category - * * @return StopwatchEvent */ - public function start($name, $category = null) + public function start(string $name, string $category = null) { return end($this->activeSections)->startEvent($name, $category); } @@ -109,11 +109,9 @@ class Stopwatch /** * Checks if the event was started. * - * @param string $name The event name - * * @return bool */ - public function isStarted($name) + public function isStarted(string $name) { return end($this->activeSections)->isEventStarted($name); } @@ -121,11 +119,9 @@ class Stopwatch /** * Stops an event. * - * @param string $name The event name - * * @return StopwatchEvent */ - public function stop($name) + public function stop(string $name) { return end($this->activeSections)->stopEvent($name); } @@ -133,11 +129,9 @@ class Stopwatch /** * Stops then restarts an event. * - * @param string $name The event name - * * @return StopwatchEvent */ - public function lap($name) + public function lap(string $name) { return end($this->activeSections)->stopEvent($name)->start(); } @@ -145,11 +139,9 @@ class Stopwatch /** * Returns a specific event by name. * - * @param string $name The event name - * * @return StopwatchEvent */ - public function getEvent($name) + public function getEvent(string $name) { return end($this->activeSections)->getEvent($name); } @@ -157,11 +149,9 @@ class Stopwatch /** * Gets all events for a given section. * - * @param string $id A section identifier - * * @return StopwatchEvent[] */ - public function getSectionEvents($id) + public function getSectionEvents(string $id) { return isset($this->sections[$id]) ? $this->sections[$id]->getEvents() : []; } diff --git a/lib/symfony/stopwatch/StopwatchEvent.php b/lib/symfony/stopwatch/StopwatchEvent.php index 2ef1defc59..945bc7029f 100644 --- a/lib/symfony/stopwatch/StopwatchEvent.php +++ b/lib/symfony/stopwatch/StopwatchEvent.php @@ -43,24 +43,31 @@ class StopwatchEvent */ private $started = []; + /** + * @var string + */ + private $name; + /** * @param float $origin The origin time in milliseconds * @param string|null $category The event category or null to use the default * @param bool $morePrecision If true, time is stored as float to keep the original microsecond precision + * @param string|null $name The event name or null to define the name as default * * @throws \InvalidArgumentException When the raw time is not valid */ - public function __construct($origin, $category = null, $morePrecision = false) + public function __construct(float $origin, string $category = null, bool $morePrecision = false, string $name = null) { $this->origin = $this->formatTime($origin); $this->category = \is_string($category) ? $category : 'default'; $this->morePrecision = $morePrecision; + $this->name = $name ?? 'default'; } /** * Gets the category. * - * @return string The category + * @return string */ public function getCategory() { @@ -68,9 +75,9 @@ class StopwatchEvent } /** - * Gets the origin. + * Gets the origin in milliseconds. * - * @return float The origin in milliseconds + * @return float */ public function getOrigin() { @@ -140,7 +147,7 @@ class StopwatchEvent /** * Gets all event periods. * - * @return StopwatchPeriod[] An array of StopwatchPeriod instances + * @return StopwatchPeriod[] */ public function getPeriods() { @@ -148,9 +155,9 @@ class StopwatchEvent } /** - * Gets the relative time of the start of the first period. + * Gets the relative time of the start of the first period in milliseconds. * - * @return int|float The time (in milliseconds) + * @return int|float */ public function getStartTime() { @@ -166,9 +173,9 @@ class StopwatchEvent } /** - * Gets the relative time of the end of the last period. + * Gets the relative time of the end of the last period in milliseconds. * - * @return int|float The time (in milliseconds) + * @return int|float */ public function getEndTime() { @@ -178,9 +185,9 @@ class StopwatchEvent } /** - * Gets the duration of the events (including all periods). + * Gets the duration of the events in milliseconds (including all periods). * - * @return int|float The duration (in milliseconds) + * @return int|float */ public function getDuration() { @@ -200,9 +207,9 @@ class StopwatchEvent } /** - * Gets the max memory usage of all periods. + * Gets the max memory usage of all periods in bytes. * - * @return int The memory usage (in bytes) + * @return int */ public function getMemory() { @@ -217,9 +224,9 @@ class StopwatchEvent } /** - * Return the current time relative to origin. + * Return the current time relative to origin in milliseconds. * - * @return float Time in ms + * @return float */ protected function getNow() { @@ -229,26 +236,23 @@ class StopwatchEvent /** * Formats a time. * - * @param int|float $time A raw time - * - * @return float The formatted time - * * @throws \InvalidArgumentException When the raw time is not valid */ - private function formatTime($time) + private function formatTime(float $time): float { - if (!is_numeric($time)) { - throw new \InvalidArgumentException('The time must be a numerical value.'); - } - return round($time, 1); } /** - * @return string + * Gets the event name. */ - public function __toString() + public function getName(): string { - return sprintf('%s: %.2F MiB - %d ms', $this->getCategory(), $this->getMemory() / 1024 / 1024, $this->getDuration()); + return $this->name; + } + + public function __toString(): string + { + return sprintf('%s/%s: %.2F MiB - %d ms', $this->getCategory(), $this->getName(), $this->getMemory() / 1024 / 1024, $this->getDuration()); } } diff --git a/lib/symfony/stopwatch/StopwatchPeriod.php b/lib/symfony/stopwatch/StopwatchPeriod.php index 5626aa5333..7a7ae1a776 100644 --- a/lib/symfony/stopwatch/StopwatchPeriod.php +++ b/lib/symfony/stopwatch/StopwatchPeriod.php @@ -27,7 +27,7 @@ class StopwatchPeriod * @param int|float $end The relative time of the end of the period (in milliseconds) * @param bool $morePrecision If true, time is stored as float to keep the original microsecond precision */ - public function __construct($start, $end, $morePrecision = false) + public function __construct($start, $end, bool $morePrecision = false) { $this->start = $morePrecision ? (float) $start : (int) $start; $this->end = $morePrecision ? (float) $end : (int) $end; @@ -35,9 +35,9 @@ class StopwatchPeriod } /** - * Gets the relative time of the start of the period. + * Gets the relative time of the start of the period in milliseconds. * - * @return int|float The time (in milliseconds) + * @return int|float */ public function getStartTime() { @@ -45,9 +45,9 @@ class StopwatchPeriod } /** - * Gets the relative time of the end of the period. + * Gets the relative time of the end of the period in milliseconds. * - * @return int|float The time (in milliseconds) + * @return int|float */ public function getEndTime() { @@ -55,9 +55,9 @@ class StopwatchPeriod } /** - * Gets the time spent in this period. + * Gets the time spent in this period in milliseconds. * - * @return int|float The period duration (in milliseconds) + * @return int|float */ public function getDuration() { @@ -65,12 +65,17 @@ class StopwatchPeriod } /** - * Gets the memory usage. + * Gets the memory usage in bytes. * - * @return int The memory usage (in bytes) + * @return int */ public function getMemory() { return $this->memory; } + + public function __toString(): string + { + return sprintf('%.2F MiB - %d ms', $this->getMemory() / 1024 / 1024, $this->getDuration()); + } } diff --git a/lib/symfony/stopwatch/composer.json b/lib/symfony/stopwatch/composer.json index 9fdd55e917..ed918d36f1 100644 --- a/lib/symfony/stopwatch/composer.json +++ b/lib/symfony/stopwatch/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/stopwatch", "type": "library", - "description": "Symfony Stopwatch Component", + "description": "Provides a way to profile code", "keywords": [], "homepage": "https://symfony.com", "license": "MIT", @@ -16,7 +16,8 @@ } ], "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.2.5", + "symfony/service-contracts": "^1|^2|^3" }, "autoload": { "psr-4": { "Symfony\\Component\\Stopwatch\\": "" }, diff --git a/lib/symfony/stopwatch/phpunit.xml.dist b/lib/symfony/stopwatch/phpunit.xml.dist deleted file mode 100644 index ded03c7380..0000000000 --- a/lib/symfony/stopwatch/phpunit.xml.dist +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - ./Tests/ - - - - - - ./ - - ./Tests - ./vendor - - - - diff --git a/lib/symfony/string/AbstractString.php b/lib/symfony/string/AbstractString.php new file mode 100644 index 0000000000..cf21fef1f4 --- /dev/null +++ b/lib/symfony/string/AbstractString.php @@ -0,0 +1,795 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; +use Symfony\Component\String\Exception\RuntimeException; + +/** + * Represents a string of abstract characters. + * + * Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters). + * This class is the abstract type to use as a type-hint when the logic you want to + * implement doesn't care about the exact variant it deals with. + * + * @author Nicolas Grekas + * @author Hugo Hamon + * + * @throws ExceptionInterface + */ +abstract class AbstractString implements \Stringable, \JsonSerializable +{ + public const PREG_PATTERN_ORDER = \PREG_PATTERN_ORDER; + public const PREG_SET_ORDER = \PREG_SET_ORDER; + public const PREG_OFFSET_CAPTURE = \PREG_OFFSET_CAPTURE; + public const PREG_UNMATCHED_AS_NULL = \PREG_UNMATCHED_AS_NULL; + + public const PREG_SPLIT = 0; + public const PREG_SPLIT_NO_EMPTY = \PREG_SPLIT_NO_EMPTY; + public const PREG_SPLIT_DELIM_CAPTURE = \PREG_SPLIT_DELIM_CAPTURE; + public const PREG_SPLIT_OFFSET_CAPTURE = \PREG_SPLIT_OFFSET_CAPTURE; + + protected $string = ''; + protected $ignoreCase = false; + + abstract public function __construct(string $string = ''); + + /** + * Unwraps instances of AbstractString back to strings. + * + * @return string[]|array + */ + public static function unwrap(array $values): array + { + foreach ($values as $k => $v) { + if ($v instanceof self) { + $values[$k] = $v->__toString(); + } elseif (\is_array($v) && $values[$k] !== $v = static::unwrap($v)) { + $values[$k] = $v; + } + } + + return $values; + } + + /** + * Wraps (and normalizes) strings in instances of AbstractString. + * + * @return static[]|array + */ + public static function wrap(array $values): array + { + $i = 0; + $keys = null; + + foreach ($values as $k => $v) { + if (\is_string($k) && '' !== $k && $k !== $j = (string) new static($k)) { + $keys = $keys ?? array_keys($values); + $keys[$i] = $j; + } + + if (\is_string($v)) { + $values[$k] = new static($v); + } elseif (\is_array($v) && $values[$k] !== $v = static::wrap($v)) { + $values[$k] = $v; + } + + ++$i; + } + + return null !== $keys ? array_combine($keys, $values) : $values; + } + + /** + * @param string|string[] $needle + * + * @return static + */ + public function after($needle, bool $includeNeedle = false, int $offset = 0): self + { + $str = clone $this; + $i = \PHP_INT_MAX; + + foreach ((array) $needle as $n) { + $n = (string) $n; + $j = $this->indexOf($n, $offset); + + if (null !== $j && $j < $i) { + $i = $j; + $str->string = $n; + } + } + + if (\PHP_INT_MAX === $i) { + return $str; + } + + if (!$includeNeedle) { + $i += $str->length(); + } + + return $this->slice($i); + } + + /** + * @param string|string[] $needle + * + * @return static + */ + public function afterLast($needle, bool $includeNeedle = false, int $offset = 0): self + { + $str = clone $this; + $i = null; + + foreach ((array) $needle as $n) { + $n = (string) $n; + $j = $this->indexOfLast($n, $offset); + + if (null !== $j && $j >= $i) { + $i = $offset = $j; + $str->string = $n; + } + } + + if (null === $i) { + return $str; + } + + if (!$includeNeedle) { + $i += $str->length(); + } + + return $this->slice($i); + } + + /** + * @return static + */ + abstract public function append(string ...$suffix): self; + + /** + * @param string|string[] $needle + * + * @return static + */ + public function before($needle, bool $includeNeedle = false, int $offset = 0): self + { + $str = clone $this; + $i = \PHP_INT_MAX; + + foreach ((array) $needle as $n) { + $n = (string) $n; + $j = $this->indexOf($n, $offset); + + if (null !== $j && $j < $i) { + $i = $j; + $str->string = $n; + } + } + + if (\PHP_INT_MAX === $i) { + return $str; + } + + if ($includeNeedle) { + $i += $str->length(); + } + + return $this->slice(0, $i); + } + + /** + * @param string|string[] $needle + * + * @return static + */ + public function beforeLast($needle, bool $includeNeedle = false, int $offset = 0): self + { + $str = clone $this; + $i = null; + + foreach ((array) $needle as $n) { + $n = (string) $n; + $j = $this->indexOfLast($n, $offset); + + if (null !== $j && $j >= $i) { + $i = $offset = $j; + $str->string = $n; + } + } + + if (null === $i) { + return $str; + } + + if ($includeNeedle) { + $i += $str->length(); + } + + return $this->slice(0, $i); + } + + /** + * @return int[] + */ + public function bytesAt(int $offset): array + { + $str = $this->slice($offset, 1); + + return '' === $str->string ? [] : array_values(unpack('C*', $str->string)); + } + + /** + * @return static + */ + abstract public function camel(): self; + + /** + * @return static[] + */ + abstract public function chunk(int $length = 1): array; + + /** + * @return static + */ + public function collapseWhitespace(): self + { + $str = clone $this; + $str->string = trim(preg_replace('/(?:\s{2,}+|[^\S ])/', ' ', $str->string)); + + return $str; + } + + /** + * @param string|string[] $needle + */ + public function containsAny($needle): bool + { + return null !== $this->indexOf($needle); + } + + /** + * @param string|string[] $suffix + */ + public function endsWith($suffix): bool + { + if (!\is_array($suffix) && !$suffix instanceof \Traversable) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + foreach ($suffix as $s) { + if ($this->endsWith((string) $s)) { + return true; + } + } + + return false; + } + + /** + * @return static + */ + public function ensureEnd(string $suffix): self + { + if (!$this->endsWith($suffix)) { + return $this->append($suffix); + } + + $suffix = preg_quote($suffix); + $regex = '{('.$suffix.')(?:'.$suffix.')++$}D'; + + return $this->replaceMatches($regex.($this->ignoreCase ? 'i' : ''), '$1'); + } + + /** + * @return static + */ + public function ensureStart(string $prefix): self + { + $prefix = new static($prefix); + + if (!$this->startsWith($prefix)) { + return $this->prepend($prefix); + } + + $str = clone $this; + $i = $prefixLen = $prefix->length(); + + while ($this->indexOf($prefix, $i) === $i) { + $str = $str->slice($prefixLen); + $i += $prefixLen; + } + + return $str; + } + + /** + * @param string|string[] $string + */ + public function equalsTo($string): bool + { + if (!\is_array($string) && !$string instanceof \Traversable) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + foreach ($string as $s) { + if ($this->equalsTo((string) $s)) { + return true; + } + } + + return false; + } + + /** + * @return static + */ + abstract public function folded(): self; + + /** + * @return static + */ + public function ignoreCase(): self + { + $str = clone $this; + $str->ignoreCase = true; + + return $str; + } + + /** + * @param string|string[] $needle + */ + public function indexOf($needle, int $offset = 0): ?int + { + if (!\is_array($needle) && !$needle instanceof \Traversable) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + $i = \PHP_INT_MAX; + + foreach ($needle as $n) { + $j = $this->indexOf((string) $n, $offset); + + if (null !== $j && $j < $i) { + $i = $j; + } + } + + return \PHP_INT_MAX === $i ? null : $i; + } + + /** + * @param string|string[] $needle + */ + public function indexOfLast($needle, int $offset = 0): ?int + { + if (!\is_array($needle) && !$needle instanceof \Traversable) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + $i = null; + + foreach ($needle as $n) { + $j = $this->indexOfLast((string) $n, $offset); + + if (null !== $j && $j >= $i) { + $i = $offset = $j; + } + } + + return $i; + } + + public function isEmpty(): bool + { + return '' === $this->string; + } + + /** + * @return static + */ + abstract public function join(array $strings, string $lastGlue = null): self; + + public function jsonSerialize(): string + { + return $this->string; + } + + abstract public function length(): int; + + /** + * @return static + */ + abstract public function lower(): self; + + /** + * Matches the string using a regular expression. + * + * Pass PREG_PATTERN_ORDER or PREG_SET_ORDER as $flags to get all occurrences matching the regular expression. + * + * @return array All matches in a multi-dimensional array ordered according to flags + */ + abstract public function match(string $regexp, int $flags = 0, int $offset = 0): array; + + /** + * @return static + */ + abstract public function padBoth(int $length, string $padStr = ' '): self; + + /** + * @return static + */ + abstract public function padEnd(int $length, string $padStr = ' '): self; + + /** + * @return static + */ + abstract public function padStart(int $length, string $padStr = ' '): self; + + /** + * @return static + */ + abstract public function prepend(string ...$prefix): self; + + /** + * @return static + */ + public function repeat(int $multiplier): self + { + if (0 > $multiplier) { + throw new InvalidArgumentException(sprintf('Multiplier must be positive, %d given.', $multiplier)); + } + + $str = clone $this; + $str->string = str_repeat($str->string, $multiplier); + + return $str; + } + + /** + * @return static + */ + abstract public function replace(string $from, string $to): self; + + /** + * @param string|callable $to + * + * @return static + */ + abstract public function replaceMatches(string $fromRegexp, $to): self; + + /** + * @return static + */ + abstract public function reverse(): self; + + /** + * @return static + */ + abstract public function slice(int $start = 0, int $length = null): self; + + /** + * @return static + */ + abstract public function snake(): self; + + /** + * @return static + */ + abstract public function splice(string $replacement, int $start = 0, int $length = null): self; + + /** + * @return static[] + */ + public function split(string $delimiter, int $limit = null, int $flags = null): array + { + if (null === $flags) { + throw new \TypeError('Split behavior when $flags is null must be implemented by child classes.'); + } + + if ($this->ignoreCase) { + $delimiter .= 'i'; + } + + set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + + try { + if (false === $chunks = preg_split($delimiter, $this->string, $limit, $flags)) { + $lastError = preg_last_error(); + + foreach (get_defined_constants(true)['pcre'] as $k => $v) { + if ($lastError === $v && '_ERROR' === substr($k, -6)) { + throw new RuntimeException('Splitting failed with '.$k.'.'); + } + } + + throw new RuntimeException('Splitting failed with unknown error code.'); + } + } finally { + restore_error_handler(); + } + + $str = clone $this; + + if (self::PREG_SPLIT_OFFSET_CAPTURE & $flags) { + foreach ($chunks as &$chunk) { + $str->string = $chunk[0]; + $chunk[0] = clone $str; + } + } else { + foreach ($chunks as &$chunk) { + $str->string = $chunk; + $chunk = clone $str; + } + } + + return $chunks; + } + + /** + * @param string|string[] $prefix + */ + public function startsWith($prefix): bool + { + if (!\is_array($prefix) && !$prefix instanceof \Traversable) { + throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class)); + } + + foreach ($prefix as $prefix) { + if ($this->startsWith((string) $prefix)) { + return true; + } + } + + return false; + } + + /** + * @return static + */ + abstract public function title(bool $allWords = false): self; + + public function toByteString(string $toEncoding = null): ByteString + { + $b = new ByteString(); + + $toEncoding = \in_array($toEncoding, ['utf8', 'utf-8', 'UTF8'], true) ? 'UTF-8' : $toEncoding; + + if (null === $toEncoding || $toEncoding === $fromEncoding = $this instanceof AbstractUnicodeString || preg_match('//u', $b->string) ? 'UTF-8' : 'Windows-1252') { + $b->string = $this->string; + + return $b; + } + + set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + + try { + try { + $b->string = mb_convert_encoding($this->string, $toEncoding, 'UTF-8'); + } catch (InvalidArgumentException $e) { + if (!\function_exists('iconv')) { + throw $e; + } + + $b->string = iconv('UTF-8', $toEncoding, $this->string); + } + } finally { + restore_error_handler(); + } + + return $b; + } + + public function toCodePointString(): CodePointString + { + return new CodePointString($this->string); + } + + public function toString(): string + { + return $this->string; + } + + public function toUnicodeString(): UnicodeString + { + return new UnicodeString($this->string); + } + + /** + * @return static + */ + abstract public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self; + + /** + * @return static + */ + abstract public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self; + + /** + * @param string|string[] $prefix + * + * @return static + */ + public function trimPrefix($prefix): self + { + if (\is_array($prefix) || $prefix instanceof \Traversable) { + foreach ($prefix as $s) { + $t = $this->trimPrefix($s); + + if ($t->string !== $this->string) { + return $t; + } + } + + return clone $this; + } + + $str = clone $this; + + if ($prefix instanceof self) { + $prefix = $prefix->string; + } else { + $prefix = (string) $prefix; + } + + if ('' !== $prefix && \strlen($this->string) >= \strlen($prefix) && 0 === substr_compare($this->string, $prefix, 0, \strlen($prefix), $this->ignoreCase)) { + $str->string = substr($this->string, \strlen($prefix)); + } + + return $str; + } + + /** + * @return static + */ + abstract public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self; + + /** + * @param string|string[] $suffix + * + * @return static + */ + public function trimSuffix($suffix): self + { + if (\is_array($suffix) || $suffix instanceof \Traversable) { + foreach ($suffix as $s) { + $t = $this->trimSuffix($s); + + if ($t->string !== $this->string) { + return $t; + } + } + + return clone $this; + } + + $str = clone $this; + + if ($suffix instanceof self) { + $suffix = $suffix->string; + } else { + $suffix = (string) $suffix; + } + + if ('' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase)) { + $str->string = substr($this->string, 0, -\strlen($suffix)); + } + + return $str; + } + + /** + * @return static + */ + public function truncate(int $length, string $ellipsis = '', bool $cut = true): self + { + $stringLength = $this->length(); + + if ($stringLength <= $length) { + return clone $this; + } + + $ellipsisLength = '' !== $ellipsis ? (new static($ellipsis))->length() : 0; + + if ($length < $ellipsisLength) { + $ellipsisLength = 0; + } + + if (!$cut) { + if (null === $length = $this->indexOf([' ', "\r", "\n", "\t"], ($length ?: 1) - 1)) { + return clone $this; + } + + $length += $ellipsisLength; + } + + $str = $this->slice(0, $length - $ellipsisLength); + + return $ellipsisLength ? $str->trimEnd()->append($ellipsis) : $str; + } + + /** + * @return static + */ + abstract public function upper(): self; + + /** + * Returns the printable length on a terminal. + */ + abstract public function width(bool $ignoreAnsiDecoration = true): int; + + /** + * @return static + */ + public function wordwrap(int $width = 75, string $break = "\n", bool $cut = false): self + { + $lines = '' !== $break ? $this->split($break) : [clone $this]; + $chars = []; + $mask = ''; + + if (1 === \count($lines) && '' === $lines[0]->string) { + return $lines[0]; + } + + foreach ($lines as $i => $line) { + if ($i) { + $chars[] = $break; + $mask .= '#'; + } + + foreach ($line->chunk() as $char) { + $chars[] = $char->string; + $mask .= ' ' === $char->string ? ' ' : '?'; + } + } + + $string = ''; + $j = 0; + $b = $i = -1; + $mask = wordwrap($mask, $width, '#', $cut); + + while (false !== $b = strpos($mask, '#', $b + 1)) { + for (++$i; $i < $b; ++$i) { + $string .= $chars[$j]; + unset($chars[$j++]); + } + + if ($break === $chars[$j] || ' ' === $chars[$j]) { + unset($chars[$j++]); + } + + $string .= $break; + } + + $str = clone $this; + $str->string = $string.implode('', $chars); + + return $str; + } + + public function __sleep(): array + { + return ['string']; + } + + public function __clone() + { + $this->ignoreCase = false; + } + + public function __toString(): string + { + return $this->string; + } +} diff --git a/lib/symfony/string/AbstractUnicodeString.php b/lib/symfony/string/AbstractUnicodeString.php new file mode 100644 index 0000000000..a482300d28 --- /dev/null +++ b/lib/symfony/string/AbstractUnicodeString.php @@ -0,0 +1,623 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; +use Symfony\Component\String\Exception\RuntimeException; + +/** + * Represents a string of abstract Unicode characters. + * + * Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters). + * This class is the abstract type to use as a type-hint when the logic you want to + * implement is Unicode-aware but doesn't care about code points vs grapheme clusters. + * + * @author Nicolas Grekas + * + * @throws ExceptionInterface + */ +abstract class AbstractUnicodeString extends AbstractString +{ + public const NFC = \Normalizer::NFC; + public const NFD = \Normalizer::NFD; + public const NFKC = \Normalizer::NFKC; + public const NFKD = \Normalizer::NFKD; + + // all ASCII letters sorted by typical frequency of occurrence + private const ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; + + // the subset of folded case mappings that is not in lower case mappings + private const FOLD_FROM = ['İ', 'µ', 'ſ', "\xCD\x85", 'ς', 'ϐ', 'ϑ', 'ϕ', 'ϖ', 'ϰ', 'ϱ', 'ϵ', 'ẛ', "\xE1\xBE\xBE", 'ß', 'İ', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'և', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ẞ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'ᾼ', 'ῂ', 'ῃ', 'ῄ', 'ῆ', 'ῇ', 'ῌ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ῲ', 'ῳ', 'ῴ', 'ῶ', 'ῷ', 'ῼ', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ']; + private const FOLD_TO = ['i̇', 'μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', 'ṡ', 'ι', 'ss', 'i̇', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'եւ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'aʾ', 'ss', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὰι', 'αι', 'άι', 'ᾶ', 'ᾶι', 'αι', 'ὴι', 'ηι', 'ήι', 'ῆ', 'ῆι', 'ηι', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ὼι', 'ωι', 'ώι', 'ῶ', 'ῶι', 'ωι', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'st', 'st', 'մն', 'մե', 'մի', 'վն', 'մխ']; + + // the subset of upper case mappings that map one code point to many code points + private const UPPER_FROM = ['ß', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'և', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ', 'ʼn', 'ΐ', 'ΰ', 'ǰ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾶ', 'ῆ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ῶ']; + private const UPPER_TO = ['SS', 'FF', 'FI', 'FL', 'FFI', 'FFL', 'ST', 'ST', 'ԵՒ', 'ՄՆ', 'ՄԵ', 'ՄԻ', 'ՎՆ', 'ՄԽ', 'ʼN', 'Ϊ́', 'Ϋ́', 'J̌', 'H̱', 'T̈', 'W̊', 'Y̊', 'Aʾ', 'Υ̓', 'Υ̓̀', 'Υ̓́', 'Υ̓͂', 'Α͂', 'Η͂', 'Ϊ̀', 'Ϊ́', 'Ι͂', 'Ϊ͂', 'Ϋ̀', 'Ϋ́', 'Ρ̓', 'Υ͂', 'Ϋ͂', 'Ω͂']; + + // the subset of https://github.com/unicode-org/cldr/blob/master/common/transforms/Latin-ASCII.xml that is not in NFKD + private const TRANSLIT_FROM = ['Æ', 'Ð', 'Ø', 'Þ', 'ß', 'æ', 'ð', 'ø', 'þ', 'Đ', 'đ', 'Ħ', 'ħ', 'ı', 'ĸ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'ʼn', 'Ŋ', 'ŋ', 'Œ', 'œ', 'Ŧ', 'ŧ', 'ƀ', 'Ɓ', 'Ƃ', 'ƃ', 'Ƈ', 'ƈ', 'Ɖ', 'Ɗ', 'Ƌ', 'ƌ', 'Ɛ', 'Ƒ', 'ƒ', 'Ɠ', 'ƕ', 'Ɩ', 'Ɨ', 'Ƙ', 'ƙ', 'ƚ', 'Ɲ', 'ƞ', 'Ƣ', 'ƣ', 'Ƥ', 'ƥ', 'ƫ', 'Ƭ', 'ƭ', 'Ʈ', 'Ʋ', 'Ƴ', 'ƴ', 'Ƶ', 'ƶ', 'DŽ', 'Dž', 'dž', 'Ǥ', 'ǥ', 'ȡ', 'Ȥ', 'ȥ', 'ȴ', 'ȵ', 'ȶ', 'ȷ', 'ȸ', 'ȹ', 'Ⱥ', 'Ȼ', 'ȼ', 'Ƚ', 'Ⱦ', 'ȿ', 'ɀ', 'Ƀ', 'Ʉ', 'Ɇ', 'ɇ', 'Ɉ', 'ɉ', 'Ɍ', 'ɍ', 'Ɏ', 'ɏ', 'ɓ', 'ɕ', 'ɖ', 'ɗ', 'ɛ', 'ɟ', 'ɠ', 'ɡ', 'ɢ', 'ɦ', 'ɧ', 'ɨ', 'ɪ', 'ɫ', 'ɬ', 'ɭ', 'ɱ', 'ɲ', 'ɳ', 'ɴ', 'ɶ', 'ɼ', 'ɽ', 'ɾ', 'ʀ', 'ʂ', 'ʈ', 'ʉ', 'ʋ', 'ʏ', 'ʐ', 'ʑ', 'ʙ', 'ʛ', 'ʜ', 'ʝ', 'ʟ', 'ʠ', 'ʣ', 'ʥ', 'ʦ', 'ʪ', 'ʫ', 'ᴀ', 'ᴁ', 'ᴃ', 'ᴄ', 'ᴅ', 'ᴆ', 'ᴇ', 'ᴊ', 'ᴋ', 'ᴌ', 'ᴍ', 'ᴏ', 'ᴘ', 'ᴛ', 'ᴜ', 'ᴠ', 'ᴡ', 'ᴢ', 'ᵫ', 'ᵬ', 'ᵭ', 'ᵮ', 'ᵯ', 'ᵰ', 'ᵱ', 'ᵲ', 'ᵳ', 'ᵴ', 'ᵵ', 'ᵶ', 'ᵺ', 'ᵻ', 'ᵽ', 'ᵾ', 'ᶀ', 'ᶁ', 'ᶂ', 'ᶃ', 'ᶄ', 'ᶅ', 'ᶆ', 'ᶇ', 'ᶈ', 'ᶉ', 'ᶊ', 'ᶌ', 'ᶍ', 'ᶎ', 'ᶏ', 'ᶑ', 'ᶒ', 'ᶓ', 'ᶖ', 'ᶙ', 'ẚ', 'ẜ', 'ẝ', 'ẞ', 'Ỻ', 'ỻ', 'Ỽ', 'ỽ', 'Ỿ', 'ỿ', '©', '®', '₠', '₢', '₣', '₤', '₧', '₺', '₹', 'ℌ', '℞', '㎧', '㎮', '㏆', '㏗', '㏞', '㏟', '¼', '½', '¾', '⅓', '⅔', '⅕', '⅖', '⅗', '⅘', '⅙', '⅚', '⅛', '⅜', '⅝', '⅞', '⅟', '〇', '‘', '’', '‚', '‛', '“', '”', '„', '‟', '′', '″', '〝', '〞', '«', '»', '‹', '›', '‐', '‑', '‒', '–', '—', '―', '︱', '︲', '﹘', '‖', '⁄', '⁅', '⁆', '⁎', '、', '。', '〈', '〉', '《', '》', '〔', '〕', '〘', '〙', '〚', '〛', '︑', '︒', '︹', '︺', '︽', '︾', '︿', '﹀', '﹑', '﹝', '﹞', '⦅', '⦆', '。', '、', '×', '÷', '−', '∕', '∖', '∣', '∥', '≪', '≫', '⦅', '⦆']; + private const TRANSLIT_TO = ['AE', 'D', 'O', 'TH', 'ss', 'ae', 'd', 'o', 'th', 'D', 'd', 'H', 'h', 'i', 'q', 'L', 'l', 'L', 'l', '\'n', 'N', 'n', 'OE', 'oe', 'T', 't', 'b', 'B', 'B', 'b', 'C', 'c', 'D', 'D', 'D', 'd', 'E', 'F', 'f', 'G', 'hv', 'I', 'I', 'K', 'k', 'l', 'N', 'n', 'OI', 'oi', 'P', 'p', 't', 'T', 't', 'T', 'V', 'Y', 'y', 'Z', 'z', 'DZ', 'Dz', 'dz', 'G', 'g', 'd', 'Z', 'z', 'l', 'n', 't', 'j', 'db', 'qp', 'A', 'C', 'c', 'L', 'T', 's', 'z', 'B', 'U', 'E', 'e', 'J', 'j', 'R', 'r', 'Y', 'y', 'b', 'c', 'd', 'd', 'e', 'j', 'g', 'g', 'G', 'h', 'h', 'i', 'I', 'l', 'l', 'l', 'm', 'n', 'n', 'N', 'OE', 'r', 'r', 'r', 'R', 's', 't', 'u', 'v', 'Y', 'z', 'z', 'B', 'G', 'H', 'j', 'L', 'q', 'dz', 'dz', 'ts', 'ls', 'lz', 'A', 'AE', 'B', 'C', 'D', 'D', 'E', 'J', 'K', 'L', 'M', 'O', 'P', 'T', 'U', 'V', 'W', 'Z', 'ue', 'b', 'd', 'f', 'm', 'n', 'p', 'r', 'r', 's', 't', 'z', 'th', 'I', 'p', 'U', 'b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 'v', 'x', 'z', 'a', 'd', 'e', 'e', 'i', 'u', 'a', 's', 's', 'SS', 'LL', 'll', 'V', 'v', 'Y', 'y', '(C)', '(R)', 'CE', 'Cr', 'Fr.', 'L.', 'Pts', 'TL', 'Rs', 'x', 'Rx', 'm/s', 'rad/s', 'C/kg', 'pH', 'V/m', 'A/m', ' 1/4', ' 1/2', ' 3/4', ' 1/3', ' 2/3', ' 1/5', ' 2/5', ' 3/5', ' 4/5', ' 1/6', ' 5/6', ' 1/8', ' 3/8', ' 5/8', ' 7/8', ' 1/', '0', '\'', '\'', ',', '\'', '"', '"', ',,', '"', '\'', '"', '"', '"', '<<', '>>', '<', '>', '-', '-', '-', '-', '-', '-', '-', '-', '-', '||', '/', '[', ']', '*', ',', '.', '<', '>', '<<', '>>', '[', ']', '[', ']', '[', ']', ',', '.', '[', ']', '<<', '>>', '<', '>', ',', '[', ']', '((', '))', '.', ',', '*', '/', '-', '/', '\\', '|', '||', '<<', '>>', '((', '))']; + + private static $transliterators = []; + private static $tableZero; + private static $tableWide; + + /** + * @return static + */ + public static function fromCodePoints(int ...$codes): self + { + $string = ''; + + foreach ($codes as $code) { + if (0x80 > $code %= 0x200000) { + $string .= \chr($code); + } elseif (0x800 > $code) { + $string .= \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $string .= \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $string .= \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + } + + return new static($string); + } + + /** + * Generic UTF-8 to ASCII transliteration. + * + * Install the intl extension for best results. + * + * @param string[]|\Transliterator[]|\Closure[] $rules See "*-Latin" rules from Transliterator::listIDs() + */ + public function ascii(array $rules = []): self + { + $str = clone $this; + $s = $str->string; + $str->string = ''; + + array_unshift($rules, 'nfd'); + $rules[] = 'latin-ascii'; + + if (\function_exists('transliterator_transliterate')) { + $rules[] = 'any-latin/bgn'; + } + + $rules[] = 'nfkd'; + $rules[] = '[:nonspacing mark:] remove'; + + while (\strlen($s) - 1 > $i = strspn($s, self::ASCII)) { + if (0 < --$i) { + $str->string .= substr($s, 0, $i); + $s = substr($s, $i); + } + + if (!$rule = array_shift($rules)) { + $rules = []; // An empty rule interrupts the next ones + } + + if ($rule instanceof \Transliterator) { + $s = $rule->transliterate($s); + } elseif ($rule instanceof \Closure) { + $s = $rule($s); + } elseif ($rule) { + if ('nfd' === $rule = strtolower($rule)) { + normalizer_is_normalized($s, self::NFD) ?: $s = normalizer_normalize($s, self::NFD); + } elseif ('nfkd' === $rule) { + normalizer_is_normalized($s, self::NFKD) ?: $s = normalizer_normalize($s, self::NFKD); + } elseif ('[:nonspacing mark:] remove' === $rule) { + $s = preg_replace('/\p{Mn}++/u', '', $s); + } elseif ('latin-ascii' === $rule) { + $s = str_replace(self::TRANSLIT_FROM, self::TRANSLIT_TO, $s); + } elseif ('de-ascii' === $rule) { + $s = preg_replace("/([AUO])\u{0308}(?=\p{Ll})/u", '$1e', $s); + $s = str_replace(["a\u{0308}", "o\u{0308}", "u\u{0308}", "A\u{0308}", "O\u{0308}", "U\u{0308}"], ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], $s); + } elseif (\function_exists('transliterator_transliterate')) { + if (null === $transliterator = self::$transliterators[$rule] ?? self::$transliterators[$rule] = \Transliterator::create($rule)) { + if ('any-latin/bgn' === $rule) { + $rule = 'any-latin'; + $transliterator = self::$transliterators[$rule] ?? self::$transliterators[$rule] = \Transliterator::create($rule); + } + + if (null === $transliterator) { + throw new InvalidArgumentException(sprintf('Unknown transliteration rule "%s".', $rule)); + } + + self::$transliterators['any-latin/bgn'] = $transliterator; + } + + $s = $transliterator->transliterate($s); + } + } elseif (!\function_exists('iconv')) { + $s = preg_replace('/[^\x00-\x7F]/u', '?', $s); + } else { + $s = @preg_replace_callback('/[^\x00-\x7F]/u', static function ($c) { + $c = (string) iconv('UTF-8', 'ASCII//TRANSLIT', $c[0]); + + if ('' === $c && '' === iconv('UTF-8', 'ASCII//TRANSLIT', '²')) { + throw new \LogicException(sprintf('"%s" requires a translit-able iconv implementation, try installing "gnu-libiconv" if you\'re using Alpine Linux.', static::class)); + } + + return 1 < \strlen($c) ? ltrim($c, '\'`"^~') : ('' !== $c ? $c : '?'); + }, $s); + } + } + + $str->string .= $s; + + return $str; + } + + public function camel(): parent + { + $str = clone $this; + $str->string = str_replace(' ', '', preg_replace_callback('/\b./u', static function ($m) use (&$i) { + return 1 === ++$i ? ('İ' === $m[0] ? 'i̇' : mb_strtolower($m[0], 'UTF-8')) : mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8'); + }, preg_replace('/[^\pL0-9]++/u', ' ', $this->string))); + + return $str; + } + + /** + * @return int[] + */ + public function codePointsAt(int $offset): array + { + $str = $this->slice($offset, 1); + + if ('' === $str->string) { + return []; + } + + $codePoints = []; + + foreach (preg_split('//u', $str->string, -1, \PREG_SPLIT_NO_EMPTY) as $c) { + $codePoints[] = mb_ord($c, 'UTF-8'); + } + + return $codePoints; + } + + public function folded(bool $compat = true): parent + { + $str = clone $this; + + if (!$compat || \PHP_VERSION_ID < 70300 || !\defined('Normalizer::NFKC_CF')) { + $str->string = normalizer_normalize($str->string, $compat ? \Normalizer::NFKC : \Normalizer::NFC); + $str->string = mb_strtolower(str_replace(self::FOLD_FROM, self::FOLD_TO, $this->string), 'UTF-8'); + } else { + $str->string = normalizer_normalize($str->string, \Normalizer::NFKC_CF); + } + + return $str; + } + + public function join(array $strings, string $lastGlue = null): parent + { + $str = clone $this; + + $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : ''; + $str->string = implode($this->string, $strings).$tail; + + if (!preg_match('//u', $str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function lower(): parent + { + $str = clone $this; + $str->string = mb_strtolower(str_replace('İ', 'i̇', $str->string), 'UTF-8'); + + return $str; + } + + public function match(string $regexp, int $flags = 0, int $offset = 0): array + { + $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match'; + + if ($this->ignoreCase) { + $regexp .= 'i'; + } + + set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + + try { + if (false === $match($regexp.'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) { + $lastError = preg_last_error(); + + foreach (get_defined_constants(true)['pcre'] as $k => $v) { + if ($lastError === $v && '_ERROR' === substr($k, -6)) { + throw new RuntimeException('Matching failed with '.$k.'.'); + } + } + + throw new RuntimeException('Matching failed with unknown error code.'); + } + } finally { + restore_error_handler(); + } + + return $matches; + } + + /** + * @return static + */ + public function normalize(int $form = self::NFC): self + { + if (!\in_array($form, [self::NFC, self::NFD, self::NFKC, self::NFKD])) { + throw new InvalidArgumentException('Unsupported normalization form.'); + } + + $str = clone $this; + normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form); + + return $str; + } + + public function padBoth(int $length, string $padStr = ' '): parent + { + if ('' === $padStr || !preg_match('//u', $padStr)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $pad = clone $this; + $pad->string = $padStr; + + return $this->pad($length, $pad, \STR_PAD_BOTH); + } + + public function padEnd(int $length, string $padStr = ' '): parent + { + if ('' === $padStr || !preg_match('//u', $padStr)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $pad = clone $this; + $pad->string = $padStr; + + return $this->pad($length, $pad, \STR_PAD_RIGHT); + } + + public function padStart(int $length, string $padStr = ' '): parent + { + if ('' === $padStr || !preg_match('//u', $padStr)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $pad = clone $this; + $pad->string = $padStr; + + return $this->pad($length, $pad, \STR_PAD_LEFT); + } + + public function replaceMatches(string $fromRegexp, $to): parent + { + if ($this->ignoreCase) { + $fromRegexp .= 'i'; + } + + if (\is_array($to) || $to instanceof \Closure) { + if (!\is_callable($to)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class)); + } + + $replace = 'preg_replace_callback'; + $to = static function (array $m) use ($to): string { + $to = $to($m); + + if ('' !== $to && (!\is_string($to) || !preg_match('//u', $to))) { + throw new InvalidArgumentException('Replace callback must return a valid UTF-8 string.'); + } + + return $to; + }; + } elseif ('' !== $to && !preg_match('//u', $to)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } else { + $replace = 'preg_replace'; + } + + set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + + try { + if (null === $string = $replace($fromRegexp.'u', $to, $this->string)) { + $lastError = preg_last_error(); + + foreach (get_defined_constants(true)['pcre'] as $k => $v) { + if ($lastError === $v && '_ERROR' === substr($k, -6)) { + throw new RuntimeException('Matching failed with '.$k.'.'); + } + } + + throw new RuntimeException('Matching failed with unknown error code.'); + } + } finally { + restore_error_handler(); + } + + $str = clone $this; + $str->string = $string; + + return $str; + } + + public function reverse(): parent + { + $str = clone $this; + $str->string = implode('', array_reverse(preg_split('/(\X)/u', $str->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY))); + + return $str; + } + + public function snake(): parent + { + $str = $this->camel()->title(); + $str->string = mb_strtolower(preg_replace(['/(\p{Lu}+)(\p{Lu}\p{Ll})/u', '/([\p{Ll}0-9])(\p{Lu})/u'], '\1_\2', $str->string), 'UTF-8'); + + return $str; + } + + public function title(bool $allWords = false): parent + { + $str = clone $this; + + $limit = $allWords ? -1 : 1; + + $str->string = preg_replace_callback('/\b./u', static function (array $m): string { + return mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8'); + }, $str->string, $limit); + + return $str; + } + + public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent + { + if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { + throw new InvalidArgumentException('Invalid UTF-8 chars.'); + } + $chars = preg_quote($chars); + + $str = clone $this; + $str->string = preg_replace("{^[$chars]++|[$chars]++$}uD", '', $str->string); + + return $str; + } + + public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent + { + if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { + throw new InvalidArgumentException('Invalid UTF-8 chars.'); + } + $chars = preg_quote($chars); + + $str = clone $this; + $str->string = preg_replace("{[$chars]++$}uD", '', $str->string); + + return $str; + } + + public function trimPrefix($prefix): parent + { + if (!$this->ignoreCase) { + return parent::trimPrefix($prefix); + } + + $str = clone $this; + + if ($prefix instanceof \Traversable) { + $prefix = iterator_to_array($prefix, false); + } elseif ($prefix instanceof parent) { + $prefix = $prefix->string; + } + + $prefix = implode('|', array_map('preg_quote', (array) $prefix)); + $str->string = preg_replace("{^(?:$prefix)}iuD", '', $this->string); + + return $str; + } + + public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent + { + if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) { + throw new InvalidArgumentException('Invalid UTF-8 chars.'); + } + $chars = preg_quote($chars); + + $str = clone $this; + $str->string = preg_replace("{^[$chars]++}uD", '', $str->string); + + return $str; + } + + public function trimSuffix($suffix): parent + { + if (!$this->ignoreCase) { + return parent::trimSuffix($suffix); + } + + $str = clone $this; + + if ($suffix instanceof \Traversable) { + $suffix = iterator_to_array($suffix, false); + } elseif ($suffix instanceof parent) { + $suffix = $suffix->string; + } + + $suffix = implode('|', array_map('preg_quote', (array) $suffix)); + $str->string = preg_replace("{(?:$suffix)$}iuD", '', $this->string); + + return $str; + } + + public function upper(): parent + { + $str = clone $this; + $str->string = mb_strtoupper($str->string, 'UTF-8'); + + if (\PHP_VERSION_ID < 70300) { + $str->string = str_replace(self::UPPER_FROM, self::UPPER_TO, $str->string); + } + + return $str; + } + + public function width(bool $ignoreAnsiDecoration = true): int + { + $width = 0; + $s = str_replace(["\x00", "\x05", "\x07"], '', $this->string); + + if (false !== strpos($s, "\r")) { + $s = str_replace(["\r\n", "\r"], "\n", $s); + } + + if (!$ignoreAnsiDecoration) { + $s = preg_replace('/[\p{Cc}\x7F]++/u', '', $s); + } + + foreach (explode("\n", $s) as $s) { + if ($ignoreAnsiDecoration) { + $s = preg_replace('/(?:\x1B(?: + \[ [\x30-\x3F]*+ [\x20-\x2F]*+ [\x40-\x7E] + | [P\]X^_] .*? \x1B\\\\ + | [\x41-\x7E] + )|[\p{Cc}\x7F]++)/xu', '', $s); + } + + $lineWidth = $this->wcswidth($s); + + if ($lineWidth > $width) { + $width = $lineWidth; + } + } + + return $width; + } + + /** + * @return static + */ + private function pad(int $len, self $pad, int $type): parent + { + $sLen = $this->length(); + + if ($len <= $sLen) { + return clone $this; + } + + $padLen = $pad->length(); + $freeLen = $len - $sLen; + $len = $freeLen % $padLen; + + switch ($type) { + case \STR_PAD_RIGHT: + return $this->append(str_repeat($pad->string, intdiv($freeLen, $padLen)).($len ? $pad->slice(0, $len) : '')); + + case \STR_PAD_LEFT: + return $this->prepend(str_repeat($pad->string, intdiv($freeLen, $padLen)).($len ? $pad->slice(0, $len) : '')); + + case \STR_PAD_BOTH: + $freeLen /= 2; + + $rightLen = ceil($freeLen); + $len = $rightLen % $padLen; + $str = $this->append(str_repeat($pad->string, intdiv($rightLen, $padLen)).($len ? $pad->slice(0, $len) : '')); + + $leftLen = floor($freeLen); + $len = $leftLen % $padLen; + + return $str->prepend(str_repeat($pad->string, intdiv($leftLen, $padLen)).($len ? $pad->slice(0, $len) : '')); + + default: + throw new InvalidArgumentException('Invalid padding type.'); + } + } + + /** + * Based on https://github.com/jquast/wcwidth, a Python implementation of https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c. + */ + private function wcswidth(string $string): int + { + $width = 0; + + foreach (preg_split('//u', $string, -1, \PREG_SPLIT_NO_EMPTY) as $c) { + $codePoint = mb_ord($c, 'UTF-8'); + + if (0 === $codePoint // NULL + || 0x034F === $codePoint // COMBINING GRAPHEME JOINER + || (0x200B <= $codePoint && 0x200F >= $codePoint) // ZERO WIDTH SPACE to RIGHT-TO-LEFT MARK + || 0x2028 === $codePoint // LINE SEPARATOR + || 0x2029 === $codePoint // PARAGRAPH SEPARATOR + || (0x202A <= $codePoint && 0x202E >= $codePoint) // LEFT-TO-RIGHT EMBEDDING to RIGHT-TO-LEFT OVERRIDE + || (0x2060 <= $codePoint && 0x2063 >= $codePoint) // WORD JOINER to INVISIBLE SEPARATOR + ) { + continue; + } + + // Non printable characters + if (32 > $codePoint // C0 control characters + || (0x07F <= $codePoint && 0x0A0 > $codePoint) // C1 control characters and DEL + ) { + return -1; + } + + if (null === self::$tableZero) { + self::$tableZero = require __DIR__.'/Resources/data/wcswidth_table_zero.php'; + } + + if ($codePoint >= self::$tableZero[0][0] && $codePoint <= self::$tableZero[$ubound = \count(self::$tableZero) - 1][1]) { + $lbound = 0; + while ($ubound >= $lbound) { + $mid = floor(($lbound + $ubound) / 2); + + if ($codePoint > self::$tableZero[$mid][1]) { + $lbound = $mid + 1; + } elseif ($codePoint < self::$tableZero[$mid][0]) { + $ubound = $mid - 1; + } else { + continue 2; + } + } + } + + if (null === self::$tableWide) { + self::$tableWide = require __DIR__.'/Resources/data/wcswidth_table_wide.php'; + } + + if ($codePoint >= self::$tableWide[0][0] && $codePoint <= self::$tableWide[$ubound = \count(self::$tableWide) - 1][1]) { + $lbound = 0; + while ($ubound >= $lbound) { + $mid = floor(($lbound + $ubound) / 2); + + if ($codePoint > self::$tableWide[$mid][1]) { + $lbound = $mid + 1; + } elseif ($codePoint < self::$tableWide[$mid][0]) { + $ubound = $mid - 1; + } else { + $width += 2; + + continue 2; + } + } + } + + ++$width; + } + + return $width; + } +} diff --git a/lib/symfony/string/ByteString.php b/lib/symfony/string/ByteString.php new file mode 100644 index 0000000000..bbf8614cf7 --- /dev/null +++ b/lib/symfony/string/ByteString.php @@ -0,0 +1,506 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; +use Symfony\Component\String\Exception\RuntimeException; + +/** + * Represents a binary-safe string of bytes. + * + * @author Nicolas Grekas + * @author Hugo Hamon + * + * @throws ExceptionInterface + */ +class ByteString extends AbstractString +{ + private const ALPHABET_ALPHANUMERIC = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; + + public function __construct(string $string = '') + { + $this->string = $string; + } + + /* + * The following method was derived from code of the Hack Standard Library (v4.40 - 2020-05-03) + * + * https://github.com/hhvm/hsl/blob/80a42c02f036f72a42f0415e80d6b847f4bf62d5/src/random/private.php#L16 + * + * Code subject to the MIT license (https://github.com/hhvm/hsl/blob/master/LICENSE). + * + * Copyright (c) 2004-2020, Facebook, Inc. (https://www.facebook.com/) + */ + + public static function fromRandom(int $length = 16, string $alphabet = null): self + { + if ($length <= 0) { + throw new InvalidArgumentException(sprintf('A strictly positive length is expected, "%d" given.', $length)); + } + + $alphabet = $alphabet ?? self::ALPHABET_ALPHANUMERIC; + $alphabetSize = \strlen($alphabet); + $bits = (int) ceil(log($alphabetSize, 2.0)); + if ($bits <= 0 || $bits > 56) { + throw new InvalidArgumentException('The length of the alphabet must in the [2^1, 2^56] range.'); + } + + $ret = ''; + while ($length > 0) { + $urandomLength = (int) ceil(2 * $length * $bits / 8.0); + $data = random_bytes($urandomLength); + $unpackedData = 0; + $unpackedBits = 0; + for ($i = 0; $i < $urandomLength && $length > 0; ++$i) { + // Unpack 8 bits + $unpackedData = ($unpackedData << 8) | \ord($data[$i]); + $unpackedBits += 8; + + // While we have enough bits to select a character from the alphabet, keep + // consuming the random data + for (; $unpackedBits >= $bits && $length > 0; $unpackedBits -= $bits) { + $index = ($unpackedData & ((1 << $bits) - 1)); + $unpackedData >>= $bits; + // Unfortunately, the alphabet size is not necessarily a power of two. + // Worst case, it is 2^k + 1, which means we need (k+1) bits and we + // have around a 50% chance of missing as k gets larger + if ($index < $alphabetSize) { + $ret .= $alphabet[$index]; + --$length; + } + } + } + } + + return new static($ret); + } + + public function bytesAt(int $offset): array + { + $str = $this->string[$offset] ?? ''; + + return '' === $str ? [] : [\ord($str)]; + } + + public function append(string ...$suffix): parent + { + $str = clone $this; + $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix); + + return $str; + } + + public function camel(): parent + { + $str = clone $this; + $str->string = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->string)))); + + return $str; + } + + public function chunk(int $length = 1): array + { + if (1 > $length) { + throw new InvalidArgumentException('The chunk length must be greater than zero.'); + } + + if ('' === $this->string) { + return []; + } + + $str = clone $this; + $chunks = []; + + foreach (str_split($this->string, $length) as $chunk) { + $str->string = $chunk; + $chunks[] = clone $str; + } + + return $chunks; + } + + public function endsWith($suffix): bool + { + if ($suffix instanceof parent) { + $suffix = $suffix->string; + } elseif (\is_array($suffix) || $suffix instanceof \Traversable) { + return parent::endsWith($suffix); + } else { + $suffix = (string) $suffix; + } + + return '' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase); + } + + public function equalsTo($string): bool + { + if ($string instanceof parent) { + $string = $string->string; + } elseif (\is_array($string) || $string instanceof \Traversable) { + return parent::equalsTo($string); + } else { + $string = (string) $string; + } + + if ('' !== $string && $this->ignoreCase) { + return 0 === strcasecmp($string, $this->string); + } + + return $string === $this->string; + } + + public function folded(): parent + { + $str = clone $this; + $str->string = strtolower($str->string); + + return $str; + } + + public function indexOf($needle, int $offset = 0): ?int + { + if ($needle instanceof parent) { + $needle = $needle->string; + } elseif (\is_array($needle) || $needle instanceof \Traversable) { + return parent::indexOf($needle, $offset); + } else { + $needle = (string) $needle; + } + + if ('' === $needle) { + return null; + } + + $i = $this->ignoreCase ? stripos($this->string, $needle, $offset) : strpos($this->string, $needle, $offset); + + return false === $i ? null : $i; + } + + public function indexOfLast($needle, int $offset = 0): ?int + { + if ($needle instanceof parent) { + $needle = $needle->string; + } elseif (\is_array($needle) || $needle instanceof \Traversable) { + return parent::indexOfLast($needle, $offset); + } else { + $needle = (string) $needle; + } + + if ('' === $needle) { + return null; + } + + $i = $this->ignoreCase ? strripos($this->string, $needle, $offset) : strrpos($this->string, $needle, $offset); + + return false === $i ? null : $i; + } + + public function isUtf8(): bool + { + return '' === $this->string || preg_match('//u', $this->string); + } + + public function join(array $strings, string $lastGlue = null): parent + { + $str = clone $this; + + $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : ''; + $str->string = implode($this->string, $strings).$tail; + + return $str; + } + + public function length(): int + { + return \strlen($this->string); + } + + public function lower(): parent + { + $str = clone $this; + $str->string = strtolower($str->string); + + return $str; + } + + public function match(string $regexp, int $flags = 0, int $offset = 0): array + { + $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match'; + + if ($this->ignoreCase) { + $regexp .= 'i'; + } + + set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + + try { + if (false === $match($regexp, $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) { + $lastError = preg_last_error(); + + foreach (get_defined_constants(true)['pcre'] as $k => $v) { + if ($lastError === $v && '_ERROR' === substr($k, -6)) { + throw new RuntimeException('Matching failed with '.$k.'.'); + } + } + + throw new RuntimeException('Matching failed with unknown error code.'); + } + } finally { + restore_error_handler(); + } + + return $matches; + } + + public function padBoth(int $length, string $padStr = ' '): parent + { + $str = clone $this; + $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_BOTH); + + return $str; + } + + public function padEnd(int $length, string $padStr = ' '): parent + { + $str = clone $this; + $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_RIGHT); + + return $str; + } + + public function padStart(int $length, string $padStr = ' '): parent + { + $str = clone $this; + $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_LEFT); + + return $str; + } + + public function prepend(string ...$prefix): parent + { + $str = clone $this; + $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$str->string; + + return $str; + } + + public function replace(string $from, string $to): parent + { + $str = clone $this; + + if ('' !== $from) { + $str->string = $this->ignoreCase ? str_ireplace($from, $to, $this->string) : str_replace($from, $to, $this->string); + } + + return $str; + } + + public function replaceMatches(string $fromRegexp, $to): parent + { + if ($this->ignoreCase) { + $fromRegexp .= 'i'; + } + + if (\is_array($to)) { + if (!\is_callable($to)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class)); + } + + $replace = 'preg_replace_callback'; + } else { + $replace = $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace'; + } + + set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + + try { + if (null === $string = $replace($fromRegexp, $to, $this->string)) { + $lastError = preg_last_error(); + + foreach (get_defined_constants(true)['pcre'] as $k => $v) { + if ($lastError === $v && '_ERROR' === substr($k, -6)) { + throw new RuntimeException('Matching failed with '.$k.'.'); + } + } + + throw new RuntimeException('Matching failed with unknown error code.'); + } + } finally { + restore_error_handler(); + } + + $str = clone $this; + $str->string = $string; + + return $str; + } + + public function reverse(): parent + { + $str = clone $this; + $str->string = strrev($str->string); + + return $str; + } + + public function slice(int $start = 0, int $length = null): parent + { + $str = clone $this; + $str->string = (string) substr($this->string, $start, $length ?? \PHP_INT_MAX); + + return $str; + } + + public function snake(): parent + { + $str = $this->camel()->title(); + $str->string = strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], '\1_\2', $str->string)); + + return $str; + } + + public function splice(string $replacement, int $start = 0, int $length = null): parent + { + $str = clone $this; + $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX); + + return $str; + } + + public function split(string $delimiter, int $limit = null, int $flags = null): array + { + if (1 > $limit = $limit ?? \PHP_INT_MAX) { + throw new InvalidArgumentException('Split limit must be a positive integer.'); + } + + if ('' === $delimiter) { + throw new InvalidArgumentException('Split delimiter is empty.'); + } + + if (null !== $flags) { + return parent::split($delimiter, $limit, $flags); + } + + $str = clone $this; + $chunks = $this->ignoreCase + ? preg_split('{'.preg_quote($delimiter).'}iD', $this->string, $limit) + : explode($delimiter, $this->string, $limit); + + foreach ($chunks as &$chunk) { + $str->string = $chunk; + $chunk = clone $str; + } + + return $chunks; + } + + public function startsWith($prefix): bool + { + if ($prefix instanceof parent) { + $prefix = $prefix->string; + } elseif (!\is_string($prefix)) { + return parent::startsWith($prefix); + } + + return '' !== $prefix && 0 === ($this->ignoreCase ? strncasecmp($this->string, $prefix, \strlen($prefix)) : strncmp($this->string, $prefix, \strlen($prefix))); + } + + public function title(bool $allWords = false): parent + { + $str = clone $this; + $str->string = $allWords ? ucwords($str->string) : ucfirst($str->string); + + return $str; + } + + public function toUnicodeString(string $fromEncoding = null): UnicodeString + { + return new UnicodeString($this->toCodePointString($fromEncoding)->string); + } + + public function toCodePointString(string $fromEncoding = null): CodePointString + { + $u = new CodePointString(); + + if (\in_array($fromEncoding, [null, 'utf8', 'utf-8', 'UTF8', 'UTF-8'], true) && preg_match('//u', $this->string)) { + $u->string = $this->string; + + return $u; + } + + set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); }); + + try { + try { + $validEncoding = false !== mb_detect_encoding($this->string, $fromEncoding ?? 'Windows-1252', true); + } catch (InvalidArgumentException $e) { + if (!\function_exists('iconv')) { + throw $e; + } + + $u->string = iconv($fromEncoding ?? 'Windows-1252', 'UTF-8', $this->string); + + return $u; + } + } finally { + restore_error_handler(); + } + + if (!$validEncoding) { + throw new InvalidArgumentException(sprintf('Invalid "%s" string.', $fromEncoding ?? 'Windows-1252')); + } + + $u->string = mb_convert_encoding($this->string, 'UTF-8', $fromEncoding ?? 'Windows-1252'); + + return $u; + } + + public function trim(string $chars = " \t\n\r\0\x0B\x0C"): parent + { + $str = clone $this; + $str->string = trim($str->string, $chars); + + return $str; + } + + public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C"): parent + { + $str = clone $this; + $str->string = rtrim($str->string, $chars); + + return $str; + } + + public function trimStart(string $chars = " \t\n\r\0\x0B\x0C"): parent + { + $str = clone $this; + $str->string = ltrim($str->string, $chars); + + return $str; + } + + public function upper(): parent + { + $str = clone $this; + $str->string = strtoupper($str->string); + + return $str; + } + + public function width(bool $ignoreAnsiDecoration = true): int + { + $string = preg_match('//u', $this->string) ? $this->string : preg_replace('/[\x80-\xFF]/', '?', $this->string); + + return (new CodePointString($string))->width($ignoreAnsiDecoration); + } +} diff --git a/lib/symfony/string/CHANGELOG.md b/lib/symfony/string/CHANGELOG.md new file mode 100644 index 0000000000..53af364005 --- /dev/null +++ b/lib/symfony/string/CHANGELOG.md @@ -0,0 +1,35 @@ +CHANGELOG +========= + +5.4 +--- + + * Add `trimSuffix()` and `trimPrefix()` methods + +5.3 +--- + + * Made `AsciiSlugger` fallback to parent locale's symbolsMap + +5.2.0 +----- + + * added a `FrenchInflector` class + +5.1.0 +----- + + * added the `AbstractString::reverse()` method + * made `AbstractString::width()` follow POSIX.1-2001 + * added `LazyString` which provides memoizing stringable objects + * The component is not marked as `@experimental` anymore + * added the `s()` helper method to get either an `UnicodeString` or `ByteString` instance, + depending of the input string UTF-8 compliancy + * added `$cut` parameter to `Symfony\Component\String\AbstractString::truncate()` + * added `AbstractString::containsAny()` + * allow passing a string of custom characters to `ByteString::fromRandom()` + +5.0.0 +----- + + * added the component as experimental diff --git a/lib/symfony/string/CodePointString.php b/lib/symfony/string/CodePointString.php new file mode 100644 index 0000000000..8ab9209413 --- /dev/null +++ b/lib/symfony/string/CodePointString.php @@ -0,0 +1,270 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; + +/** + * Represents a string of Unicode code points encoded as UTF-8. + * + * @author Nicolas Grekas + * @author Hugo Hamon + * + * @throws ExceptionInterface + */ +class CodePointString extends AbstractUnicodeString +{ + public function __construct(string $string = '') + { + if ('' !== $string && !preg_match('//u', $string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $this->string = $string; + } + + public function append(string ...$suffix): AbstractString + { + $str = clone $this; + $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix); + + if (!preg_match('//u', $str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function chunk(int $length = 1): array + { + if (1 > $length) { + throw new InvalidArgumentException('The chunk length must be greater than zero.'); + } + + if ('' === $this->string) { + return []; + } + + $rx = '/('; + while (65535 < $length) { + $rx .= '.{65535}'; + $length -= 65535; + } + $rx .= '.{'.$length.'})/us'; + + $str = clone $this; + $chunks = []; + + foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) { + $str->string = $chunk; + $chunks[] = clone $str; + } + + return $chunks; + } + + public function codePointsAt(int $offset): array + { + $str = $offset ? $this->slice($offset, 1) : $this; + + return '' === $str->string ? [] : [mb_ord($str->string, 'UTF-8')]; + } + + public function endsWith($suffix): bool + { + if ($suffix instanceof AbstractString) { + $suffix = $suffix->string; + } elseif (\is_array($suffix) || $suffix instanceof \Traversable) { + return parent::endsWith($suffix); + } else { + $suffix = (string) $suffix; + } + + if ('' === $suffix || !preg_match('//u', $suffix)) { + return false; + } + + if ($this->ignoreCase) { + return preg_match('{'.preg_quote($suffix).'$}iuD', $this->string); + } + + return \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix)); + } + + public function equalsTo($string): bool + { + if ($string instanceof AbstractString) { + $string = $string->string; + } elseif (\is_array($string) || $string instanceof \Traversable) { + return parent::equalsTo($string); + } else { + $string = (string) $string; + } + + if ('' !== $string && $this->ignoreCase) { + return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8'); + } + + return $string === $this->string; + } + + public function indexOf($needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (\is_array($needle) || $needle instanceof \Traversable) { + return parent::indexOf($needle, $offset); + } else { + $needle = (string) $needle; + } + + if ('' === $needle) { + return null; + } + + $i = $this->ignoreCase ? mb_stripos($this->string, $needle, $offset, 'UTF-8') : mb_strpos($this->string, $needle, $offset, 'UTF-8'); + + return false === $i ? null : $i; + } + + public function indexOfLast($needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (\is_array($needle) || $needle instanceof \Traversable) { + return parent::indexOfLast($needle, $offset); + } else { + $needle = (string) $needle; + } + + if ('' === $needle) { + return null; + } + + $i = $this->ignoreCase ? mb_strripos($this->string, $needle, $offset, 'UTF-8') : mb_strrpos($this->string, $needle, $offset, 'UTF-8'); + + return false === $i ? null : $i; + } + + public function length(): int + { + return mb_strlen($this->string, 'UTF-8'); + } + + public function prepend(string ...$prefix): AbstractString + { + $str = clone $this; + $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string; + + if (!preg_match('//u', $str->string)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function replace(string $from, string $to): AbstractString + { + $str = clone $this; + + if ('' === $from || !preg_match('//u', $from)) { + return $str; + } + + if ('' !== $to && !preg_match('//u', $to)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + if ($this->ignoreCase) { + $str->string = implode($to, preg_split('{'.preg_quote($from).'}iuD', $this->string)); + } else { + $str->string = str_replace($from, $to, $this->string); + } + + return $str; + } + + public function slice(int $start = 0, int $length = null): AbstractString + { + $str = clone $this; + $str->string = mb_substr($this->string, $start, $length, 'UTF-8'); + + return $str; + } + + public function splice(string $replacement, int $start = 0, int $length = null): AbstractString + { + if (!preg_match('//u', $replacement)) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + $str = clone $this; + $start = $start ? \strlen(mb_substr($this->string, 0, $start, 'UTF-8')) : 0; + $length = $length ? \strlen(mb_substr($this->string, $start, $length, 'UTF-8')) : $length; + $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX); + + return $str; + } + + public function split(string $delimiter, int $limit = null, int $flags = null): array + { + if (1 > $limit = $limit ?? \PHP_INT_MAX) { + throw new InvalidArgumentException('Split limit must be a positive integer.'); + } + + if ('' === $delimiter) { + throw new InvalidArgumentException('Split delimiter is empty.'); + } + + if (null !== $flags) { + return parent::split($delimiter.'u', $limit, $flags); + } + + if (!preg_match('//u', $delimiter)) { + throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.'); + } + + $str = clone $this; + $chunks = $this->ignoreCase + ? preg_split('{'.preg_quote($delimiter).'}iuD', $this->string, $limit) + : explode($delimiter, $this->string, $limit); + + foreach ($chunks as &$chunk) { + $str->string = $chunk; + $chunk = clone $str; + } + + return $chunks; + } + + public function startsWith($prefix): bool + { + if ($prefix instanceof AbstractString) { + $prefix = $prefix->string; + } elseif (\is_array($prefix) || $prefix instanceof \Traversable) { + return parent::startsWith($prefix); + } else { + $prefix = (string) $prefix; + } + + if ('' === $prefix || !preg_match('//u', $prefix)) { + return false; + } + + if ($this->ignoreCase) { + return 0 === mb_stripos($this->string, $prefix, 0, 'UTF-8'); + } + + return 0 === strncmp($this->string, $prefix, \strlen($prefix)); + } +} diff --git a/lib/symfony/string/Exception/ExceptionInterface.php b/lib/symfony/string/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..361978656b --- /dev/null +++ b/lib/symfony/string/Exception/ExceptionInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Exception; + +interface ExceptionInterface extends \Throwable +{ +} diff --git a/lib/symfony/string/Exception/InvalidArgumentException.php b/lib/symfony/string/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000..6aa586bcf9 --- /dev/null +++ b/lib/symfony/string/Exception/InvalidArgumentException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Exception; + +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/lib/symfony/string/Exception/RuntimeException.php b/lib/symfony/string/Exception/RuntimeException.php new file mode 100644 index 0000000000..77cb091f9c --- /dev/null +++ b/lib/symfony/string/Exception/RuntimeException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Exception; + +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/lib/symfony/string/Inflector/EnglishInflector.php b/lib/symfony/string/Inflector/EnglishInflector.php new file mode 100644 index 0000000000..9f2fac675c --- /dev/null +++ b/lib/symfony/string/Inflector/EnglishInflector.php @@ -0,0 +1,511 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Inflector; + +final class EnglishInflector implements InflectorInterface +{ + /** + * Map English plural to singular suffixes. + * + * @see http://english-zone.com/spelling/plurals.html + */ + private const PLURAL_MAP = [ + // First entry: plural suffix, reversed + // Second entry: length of plural suffix + // Third entry: Whether the suffix may succeed a vocal + // Fourth entry: Whether the suffix may succeed a consonant + // Fifth entry: singular suffix, normal + + // bacteria (bacterium), criteria (criterion), phenomena (phenomenon) + ['a', 1, true, true, ['on', 'um']], + + // nebulae (nebula) + ['ea', 2, true, true, 'a'], + + // services (service) + ['secivres', 8, true, true, 'service'], + + // mice (mouse), lice (louse) + ['eci', 3, false, true, 'ouse'], + + // geese (goose) + ['esee', 4, false, true, 'oose'], + + // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius) + ['i', 1, true, true, 'us'], + + // men (man), women (woman) + ['nem', 3, true, true, 'man'], + + // children (child) + ['nerdlihc', 8, true, true, 'child'], + + // oxen (ox) + ['nexo', 4, false, false, 'ox'], + + // indices (index), appendices (appendix), prices (price) + ['seci', 4, false, true, ['ex', 'ix', 'ice']], + + // selfies (selfie) + ['seifles', 7, true, true, 'selfie'], + + // zombies (zombie) + ['seibmoz', 7, true, true, 'zombie'], + + // movies (movie) + ['seivom', 6, true, true, 'movie'], + + // conspectuses (conspectus), prospectuses (prospectus) + ['sesutcep', 8, true, true, 'pectus'], + + // feet (foot) + ['teef', 4, true, true, 'foot'], + + // geese (goose) + ['eseeg', 5, true, true, 'goose'], + + // teeth (tooth) + ['hteet', 5, true, true, 'tooth'], + + // news (news) + ['swen', 4, true, true, 'news'], + + // series (series) + ['seires', 6, true, true, 'series'], + + // babies (baby) + ['sei', 3, false, true, 'y'], + + // accesses (access), addresses (address), kisses (kiss) + ['sess', 4, true, false, 'ss'], + + // analyses (analysis), ellipses (ellipsis), fungi (fungus), + // neuroses (neurosis), theses (thesis), emphases (emphasis), + // oases (oasis), crises (crisis), houses (house), bases (base), + // atlases (atlas) + ['ses', 3, true, true, ['s', 'se', 'sis']], + + // objectives (objective), alternative (alternatives) + ['sevit', 5, true, true, 'tive'], + + // drives (drive) + ['sevird', 6, false, true, 'drive'], + + // lives (life), wives (wife) + ['sevi', 4, false, true, 'ife'], + + // moves (move) + ['sevom', 5, true, true, 'move'], + + // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf), caves (cave), staves (staff) + ['sev', 3, true, true, ['f', 've', 'ff']], + + // axes (axis), axes (ax), axes (axe) + ['sexa', 4, false, false, ['ax', 'axe', 'axis']], + + // indexes (index), matrixes (matrix) + ['sex', 3, true, false, 'x'], + + // quizzes (quiz) + ['sezz', 4, true, false, 'z'], + + // bureaus (bureau) + ['suae', 4, false, true, 'eau'], + + // fees (fee), trees (tree), employees (employee) + ['see', 3, true, true, 'ee'], + + // edges (edge) + ['segd', 4, true, true, 'dge'], + + // roses (rose), garages (garage), cassettes (cassette), + // waltzes (waltz), heroes (hero), bushes (bush), arches (arch), + // shoes (shoe) + ['se', 2, true, true, ['', 'e']], + + // tags (tag) + ['s', 1, true, true, ''], + + // chateaux (chateau) + ['xuae', 4, false, true, 'eau'], + + // people (person) + ['elpoep', 6, true, true, 'person'], + ]; + + /** + * Map English singular to plural suffixes. + * + * @see http://english-zone.com/spelling/plurals.html + */ + private const SINGULAR_MAP = [ + // First entry: singular suffix, reversed + // Second entry: length of singular suffix + // Third entry: Whether the suffix may succeed a vocal + // Fourth entry: Whether the suffix may succeed a consonant + // Fifth entry: plural suffix, normal + + // criterion (criteria) + ['airetirc', 8, false, false, 'criterion'], + + // nebulae (nebula) + ['aluben', 6, false, false, 'nebulae'], + + // children (child) + ['dlihc', 5, true, true, 'children'], + + // prices (price) + ['eci', 3, false, true, 'ices'], + + // services (service) + ['ecivres', 7, true, true, 'services'], + + // lives (life), wives (wife) + ['efi', 3, false, true, 'ives'], + + // selfies (selfie) + ['eifles', 6, true, true, 'selfies'], + + // movies (movie) + ['eivom', 5, true, true, 'movies'], + + // lice (louse) + ['esuol', 5, false, true, 'lice'], + + // mice (mouse) + ['esuom', 5, false, true, 'mice'], + + // geese (goose) + ['esoo', 4, false, true, 'eese'], + + // houses (house), bases (base) + ['es', 2, true, true, 'ses'], + + // geese (goose) + ['esoog', 5, true, true, 'geese'], + + // caves (cave) + ['ev', 2, true, true, 'ves'], + + // drives (drive) + ['evird', 5, false, true, 'drives'], + + // objectives (objective), alternative (alternatives) + ['evit', 4, true, true, 'tives'], + + // moves (move) + ['evom', 4, true, true, 'moves'], + + // staves (staff) + ['ffats', 5, true, true, 'staves'], + + // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf) + ['ff', 2, true, true, 'ffs'], + + // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf) + ['f', 1, true, true, ['fs', 'ves']], + + // arches (arch) + ['hc', 2, true, true, 'ches'], + + // bushes (bush) + ['hs', 2, true, true, 'shes'], + + // teeth (tooth) + ['htoot', 5, true, true, 'teeth'], + + // bacteria (bacterium), criteria (criterion), phenomena (phenomenon) + ['mu', 2, true, true, 'a'], + + // men (man), women (woman) + ['nam', 3, true, true, 'men'], + + // people (person) + ['nosrep', 6, true, true, ['persons', 'people']], + + // bacteria (bacterium), criteria (criterion), phenomena (phenomenon) + ['noi', 3, true, true, 'ions'], + + // coupon (coupons) + ['nop', 3, true, true, 'pons'], + + // seasons (season), treasons (treason), poisons (poison), lessons (lesson) + ['nos', 3, true, true, 'sons'], + + // bacteria (bacterium), criteria (criterion), phenomena (phenomenon) + ['no', 2, true, true, 'a'], + + // echoes (echo) + ['ohce', 4, true, true, 'echoes'], + + // heroes (hero) + ['oreh', 4, true, true, 'heroes'], + + // atlases (atlas) + ['salta', 5, true, true, 'atlases'], + + // irises (iris) + ['siri', 4, true, true, 'irises'], + + // analyses (analysis), ellipses (ellipsis), neuroses (neurosis) + // theses (thesis), emphases (emphasis), oases (oasis), + // crises (crisis) + ['sis', 3, true, true, 'ses'], + + // accesses (access), addresses (address), kisses (kiss) + ['ss', 2, true, false, 'sses'], + + // syllabi (syllabus) + ['suballys', 8, true, true, 'syllabi'], + + // buses (bus) + ['sub', 3, true, true, 'buses'], + + // circuses (circus) + ['suc', 3, true, true, 'cuses'], + + // conspectuses (conspectus), prospectuses (prospectus) + ['sutcep', 6, true, true, 'pectuses'], + + // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius) + ['su', 2, true, true, 'i'], + + // news (news) + ['swen', 4, true, true, 'news'], + + // feet (foot) + ['toof', 4, true, true, 'feet'], + + // chateaux (chateau), bureaus (bureau) + ['uae', 3, false, true, ['eaus', 'eaux']], + + // oxen (ox) + ['xo', 2, false, false, 'oxen'], + + // hoaxes (hoax) + ['xaoh', 4, true, false, 'hoaxes'], + + // indices (index) + ['xedni', 5, false, true, ['indicies', 'indexes']], + + // boxes (box) + ['xo', 2, false, true, 'oxes'], + + // indexes (index), matrixes (matrix) + ['x', 1, true, false, ['cies', 'xes']], + + // appendices (appendix) + ['xi', 2, false, true, 'ices'], + + // babies (baby) + ['y', 1, false, true, 'ies'], + + // quizzes (quiz) + ['ziuq', 4, true, false, 'quizzes'], + + // waltzes (waltz) + ['z', 1, true, true, 'zes'], + ]; + + /** + * A list of words which should not be inflected, reversed. + */ + private const UNINFLECTED = [ + '', + + // data + 'atad', + + // deer + 'reed', + + // feedback + 'kcabdeef', + + // fish + 'hsif', + + // info + 'ofni', + + // moose + 'esoom', + + // series + 'seires', + + // sheep + 'peehs', + + // species + 'seiceps', + ]; + + /** + * {@inheritdoc} + */ + public function singularize(string $plural): array + { + $pluralRev = strrev($plural); + $lowerPluralRev = strtolower($pluralRev); + $pluralLength = \strlen($lowerPluralRev); + + // Check if the word is one which is not inflected, return early if so + if (\in_array($lowerPluralRev, self::UNINFLECTED, true)) { + return [$plural]; + } + + // The outer loop iterates over the entries of the plural table + // The inner loop $j iterates over the characters of the plural suffix + // in the plural table to compare them with the characters of the actual + // given plural suffix + foreach (self::PLURAL_MAP as $map) { + $suffix = $map[0]; + $suffixLength = $map[1]; + $j = 0; + + // Compare characters in the plural table and of the suffix of the + // given plural one by one + while ($suffix[$j] === $lowerPluralRev[$j]) { + // Let $j point to the next character + ++$j; + + // Successfully compared the last character + // Add an entry with the singular suffix to the singular array + if ($j === $suffixLength) { + // Is there any character preceding the suffix in the plural string? + if ($j < $pluralLength) { + $nextIsVocal = false !== strpos('aeiou', $lowerPluralRev[$j]); + + if (!$map[2] && $nextIsVocal) { + // suffix may not succeed a vocal but next char is one + break; + } + + if (!$map[3] && !$nextIsVocal) { + // suffix may not succeed a consonant but next char is one + break; + } + } + + $newBase = substr($plural, 0, $pluralLength - $suffixLength); + $newSuffix = $map[4]; + + // Check whether the first character in the plural suffix + // is uppercased. If yes, uppercase the first character in + // the singular suffix too + $firstUpper = ctype_upper($pluralRev[$j - 1]); + + if (\is_array($newSuffix)) { + $singulars = []; + + foreach ($newSuffix as $newSuffixEntry) { + $singulars[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry); + } + + return $singulars; + } + + return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)]; + } + + // Suffix is longer than word + if ($j === $pluralLength) { + break; + } + } + } + + // Assume that plural and singular is identical + return [$plural]; + } + + /** + * {@inheritdoc} + */ + public function pluralize(string $singular): array + { + $singularRev = strrev($singular); + $lowerSingularRev = strtolower($singularRev); + $singularLength = \strlen($lowerSingularRev); + + // Check if the word is one which is not inflected, return early if so + if (\in_array($lowerSingularRev, self::UNINFLECTED, true)) { + return [$singular]; + } + + // The outer loop iterates over the entries of the singular table + // The inner loop $j iterates over the characters of the singular suffix + // in the singular table to compare them with the characters of the actual + // given singular suffix + foreach (self::SINGULAR_MAP as $map) { + $suffix = $map[0]; + $suffixLength = $map[1]; + $j = 0; + + // Compare characters in the singular table and of the suffix of the + // given plural one by one + + while ($suffix[$j] === $lowerSingularRev[$j]) { + // Let $j point to the next character + ++$j; + + // Successfully compared the last character + // Add an entry with the plural suffix to the plural array + if ($j === $suffixLength) { + // Is there any character preceding the suffix in the plural string? + if ($j < $singularLength) { + $nextIsVocal = false !== strpos('aeiou', $lowerSingularRev[$j]); + + if (!$map[2] && $nextIsVocal) { + // suffix may not succeed a vocal but next char is one + break; + } + + if (!$map[3] && !$nextIsVocal) { + // suffix may not succeed a consonant but next char is one + break; + } + } + + $newBase = substr($singular, 0, $singularLength - $suffixLength); + $newSuffix = $map[4]; + + // Check whether the first character in the singular suffix + // is uppercased. If yes, uppercase the first character in + // the singular suffix too + $firstUpper = ctype_upper($singularRev[$j - 1]); + + if (\is_array($newSuffix)) { + $plurals = []; + + foreach ($newSuffix as $newSuffixEntry) { + $plurals[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry); + } + + return $plurals; + } + + return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)]; + } + + // Suffix is longer than word + if ($j === $singularLength) { + break; + } + } + } + + // Assume that plural is singular with a trailing `s` + return [$singular.'s']; + } +} diff --git a/lib/symfony/string/Inflector/FrenchInflector.php b/lib/symfony/string/Inflector/FrenchInflector.php new file mode 100644 index 0000000000..612c8f2e0d --- /dev/null +++ b/lib/symfony/string/Inflector/FrenchInflector.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Inflector; + +/** + * French inflector. + * + * This class does only inflect nouns; not adjectives nor composed words like "soixante-dix". + */ +final class FrenchInflector implements InflectorInterface +{ + /** + * A list of all rules for pluralise. + * + * @see https://la-conjugaison.nouvelobs.com/regles/grammaire/le-pluriel-des-noms-121.php + */ + private const PLURALIZE_REGEXP = [ + // First entry: regexp + // Second entry: replacement + + // Words finishing with "s", "x" or "z" are invariables + // Les mots finissant par "s", "x" ou "z" sont invariables + ['/(s|x|z)$/i', '\1'], + + // Words finishing with "eau" are pluralized with a "x" + // Les mots finissant par "eau" prennent tous un "x" au pluriel + ['/(eau)$/i', '\1x'], + + // Words finishing with "au" are pluralized with a "x" excepted "landau" + // Les mots finissant par "au" prennent un "x" au pluriel sauf "landau" + ['/^(landau)$/i', '\1s'], + ['/(au)$/i', '\1x'], + + // Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu" + // Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu" + ['/^(pneu|bleu|émeu)$/i', '\1s'], + ['/(eu)$/i', '\1x'], + + // Words finishing with "al" are pluralized with a "aux" excepted + // Les mots finissant en "al" se terminent en "aux" sauf + ['/^(bal|carnaval|caracal|chacal|choral|corral|étal|festival|récital|val)$/i', '\1s'], + ['/al$/i', '\1aux'], + + // Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux + ['/^(aspir|b|cor|ém|ferm|soupir|trav|vant|vitr)ail$/i', '\1aux'], + + // Bijou, caillou, chou, genou, hibou, joujou et pou qui prennent un x au pluriel + ['/^(bij|caill|ch|gen|hib|jouj|p)ou$/i', '\1oux'], + + // Invariable words + ['/^(cinquante|soixante|mille)$/i', '\1'], + + // French titles + ['/^(mon|ma)(sieur|dame|demoiselle|seigneur)$/', 'mes\2s'], + ['/^(Mon|Ma)(sieur|dame|demoiselle|seigneur)$/', 'Mes\2s'], + ]; + + /** + * A list of all rules for singularize. + */ + private const SINGULARIZE_REGEXP = [ + // First entry: regexp + // Second entry: replacement + + // Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux + ['/((aspir|b|cor|ém|ferm|soupir|trav|vant|vitr))aux$/i', '\1ail'], + + // Words finishing with "eau" are pluralized with a "x" + // Les mots finissant par "eau" prennent tous un "x" au pluriel + ['/(eau)x$/i', '\1'], + + // Words finishing with "al" are pluralized with a "aux" expected + // Les mots finissant en "al" se terminent en "aux" sauf + ['/(amir|anim|arsen|boc|can|capit|capor|chev|crist|génér|hopit|hôpit|idé|journ|littor|loc|m|mét|minér|princip|radic|termin)aux$/i', '\1al'], + + // Words finishing with "au" are pluralized with a "x" excepted "landau" + // Les mots finissant par "au" prennent un "x" au pluriel sauf "landau" + ['/(au)x$/i', '\1'], + + // Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu" + // Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu" + ['/(eu)x$/i', '\1'], + + // Words finishing with "ou" are pluralized with a "s" excepted bijou, caillou, chou, genou, hibou, joujou, pou + // Les mots finissant par "ou" prennent un "s" sauf bijou, caillou, chou, genou, hibou, joujou, pou + ['/(bij|caill|ch|gen|hib|jouj|p)oux$/i', '\1ou'], + + // French titles + ['/^mes(dame|demoiselle)s$/', 'ma\1'], + ['/^Mes(dame|demoiselle)s$/', 'Ma\1'], + ['/^mes(sieur|seigneur)s$/', 'mon\1'], + ['/^Mes(sieur|seigneur)s$/', 'Mon\1'], + + // Default rule + ['/s$/i', ''], + ]; + + /** + * A list of words which should not be inflected. + * This list is only used by singularize. + */ + private const UNINFLECTED = '/^(abcès|accès|abus|albatros|anchois|anglais|autobus|bois|brebis|carquois|cas|chas|colis|concours|corps|cours|cyprès|décès|devis|discours|dos|embarras|engrais|entrelacs|excès|fils|fois|gâchis|gars|glas|héros|intrus|jars|jus|kermès|lacis|legs|lilas|marais|mars|matelas|mépris|mets|mois|mors|obus|os|palais|paradis|parcours|pardessus|pays|plusieurs|poids|pois|pouls|printemps|processus|progrès|puits|pus|rabais|radis|recors|recours|refus|relais|remords|remous|rictus|rhinocéros|repas|rubis|sans|sas|secours|sens|souris|succès|talus|tapis|tas|taudis|temps|tiers|univers|velours|verglas|vernis|virus)$/i'; + + /** + * {@inheritdoc} + */ + public function singularize(string $plural): array + { + if ($this->isInflectedWord($plural)) { + return [$plural]; + } + + foreach (self::SINGULARIZE_REGEXP as $rule) { + [$regexp, $replace] = $rule; + + if (1 === preg_match($regexp, $plural)) { + return [preg_replace($regexp, $replace, $plural)]; + } + } + + return [$plural]; + } + + /** + * {@inheritdoc} + */ + public function pluralize(string $singular): array + { + if ($this->isInflectedWord($singular)) { + return [$singular]; + } + + foreach (self::PLURALIZE_REGEXP as $rule) { + [$regexp, $replace] = $rule; + + if (1 === preg_match($regexp, $singular)) { + return [preg_replace($regexp, $replace, $singular)]; + } + } + + return [$singular.'s']; + } + + private function isInflectedWord(string $word): bool + { + return 1 === preg_match(self::UNINFLECTED, $word); + } +} diff --git a/lib/symfony/string/Inflector/InflectorInterface.php b/lib/symfony/string/Inflector/InflectorInterface.php new file mode 100644 index 0000000000..67f283404d --- /dev/null +++ b/lib/symfony/string/Inflector/InflectorInterface.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\String\Inflector; + +interface InflectorInterface +{ + /** + * Returns the singular forms of a string. + * + * If the method can't determine the form with certainty, several possible singulars are returned. + * + * @return string[] + */ + public function singularize(string $plural): array; + + /** + * Returns the plural forms of a string. + * + * If the method can't determine the form with certainty, several possible plurals are returned. + * + * @return string[] + */ + public function pluralize(string $singular): array; +} diff --git a/lib/symfony/string/LICENSE b/lib/symfony/string/LICENSE new file mode 100644 index 0000000000..9c907a46a6 --- /dev/null +++ b/lib/symfony/string/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2019-2022 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/lib/symfony/string/LazyString.php b/lib/symfony/string/LazyString.php new file mode 100644 index 0000000000..3b10595f3e --- /dev/null +++ b/lib/symfony/string/LazyString.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 Symfony\Component\String; + +/** + * A string whose value is computed lazily by a callback. + * + * @author Nicolas Grekas + */ +class LazyString implements \Stringable, \JsonSerializable +{ + private $value; + + /** + * @param callable|array $callback A callable or a [Closure, method] lazy-callable + * + * @return static + */ + public static function fromCallable($callback, ...$arguments): self + { + if (!\is_callable($callback) && !(\is_array($callback) && isset($callback[0]) && $callback[0] instanceof \Closure && 2 >= \count($callback))) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a callable or a [Closure, method] lazy-callable, "%s" given.', __METHOD__, get_debug_type($callback))); + } + + $lazyString = new static(); + $lazyString->value = static function () use (&$callback, &$arguments, &$value): string { + if (null !== $arguments) { + if (!\is_callable($callback)) { + $callback[0] = $callback[0](); + $callback[1] = $callback[1] ?? '__invoke'; + } + $value = $callback(...$arguments); + $callback = self::getPrettyName($callback); + $arguments = null; + } + + return $value ?? ''; + }; + + return $lazyString; + } + + /** + * @param string|int|float|bool|\Stringable $value + * + * @return static + */ + public static function fromStringable($value): self + { + if (!self::isStringable($value)) { + throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a scalar or a stringable object, "%s" given.', __METHOD__, get_debug_type($value))); + } + + if (\is_object($value)) { + return static::fromCallable([$value, '__toString']); + } + + $lazyString = new static(); + $lazyString->value = (string) $value; + + return $lazyString; + } + + /** + * Tells whether the provided value can be cast to string. + */ + final public static function isStringable($value): bool + { + return \is_string($value) || $value instanceof self || (\is_object($value) ? method_exists($value, '__toString') : \is_scalar($value)); + } + + /** + * Casts scalars and stringable objects to strings. + * + * @param object|string|int|float|bool $value + * + * @throws \TypeError When the provided value is not stringable + */ + final public static function resolve($value): string + { + return $value; + } + + /** + * @return string + */ + public function __toString() + { + if (\is_string($this->value)) { + return $this->value; + } + + try { + return $this->value = ($this->value)(); + } catch (\Throwable $e) { + if (\TypeError::class === \get_class($e) && __FILE__ === $e->getFile()) { + $type = explode(', ', $e->getMessage()); + $type = substr(array_pop($type), 0, -\strlen(' returned')); + $r = new \ReflectionFunction($this->value); + $callback = $r->getStaticVariables()['callback']; + + $e = new \TypeError(sprintf('Return value of %s() passed to %s::fromCallable() must be of the type string, %s returned.', $callback, static::class, $type)); + } + + if (\PHP_VERSION_ID < 70400) { + // leverage the ErrorHandler component with graceful fallback when it's not available + return trigger_error($e, \E_USER_ERROR); + } + + throw $e; + } + } + + public function __sleep(): array + { + $this->__toString(); + + return ['value']; + } + + public function jsonSerialize(): string + { + return $this->__toString(); + } + + private function __construct() + { + } + + private static function getPrettyName(callable $callback): string + { + if (\is_string($callback)) { + return $callback; + } + + if (\is_array($callback)) { + $class = \is_object($callback[0]) ? get_debug_type($callback[0]) : $callback[0]; + $method = $callback[1]; + } elseif ($callback instanceof \Closure) { + $r = new \ReflectionFunction($callback); + + if (false !== strpos($r->name, '{closure}') || !$class = $r->getClosureScopeClass()) { + return $r->name; + } + + $class = $class->name; + $method = $r->name; + } else { + $class = get_debug_type($callback); + $method = '__invoke'; + } + + return $class.'::'.$method; + } +} diff --git a/lib/symfony/string/README.md b/lib/symfony/string/README.md new file mode 100644 index 0000000000..9c7e1e190d --- /dev/null +++ b/lib/symfony/string/README.md @@ -0,0 +1,14 @@ +String Component +================ + +The String component provides an object-oriented API to strings and deals +with bytes, UTF-8 code points and grapheme clusters in a unified way. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/string.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/lib/symfony/string/Resources/data/wcswidth_table_wide.php b/lib/symfony/string/Resources/data/wcswidth_table_wide.php new file mode 100644 index 0000000000..43c802d05c --- /dev/null +++ b/lib/symfony/string/Resources/data/wcswidth_table_wide.php @@ -0,0 +1,1135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +if (!\function_exists(u::class)) { + function u(?string $string = ''): UnicodeString + { + return new UnicodeString($string ?? ''); + } +} + +if (!\function_exists(b::class)) { + function b(?string $string = ''): ByteString + { + return new ByteString($string ?? ''); + } +} + +if (!\function_exists(s::class)) { + /** + * @return UnicodeString|ByteString + */ + function s(?string $string = ''): AbstractString + { + $string = $string ?? ''; + + return preg_match('//u', $string) ? new UnicodeString($string) : new ByteString($string); + } +} diff --git a/lib/symfony/string/Slugger/AsciiSlugger.php b/lib/symfony/string/Slugger/AsciiSlugger.php new file mode 100644 index 0000000000..5aecfeb5fc --- /dev/null +++ b/lib/symfony/string/Slugger/AsciiSlugger.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Slugger; + +use Symfony\Component\String\AbstractUnicodeString; +use Symfony\Component\String\UnicodeString; +use Symfony\Contracts\Translation\LocaleAwareInterface; + +if (!interface_exists(LocaleAwareInterface::class)) { + throw new \LogicException('You cannot use the "Symfony\Component\String\Slugger\AsciiSlugger" as the "symfony/translation-contracts" package is not installed. Try running "composer require symfony/translation-contracts".'); +} + +/** + * @author Titouan Galopin + */ +class AsciiSlugger implements SluggerInterface, LocaleAwareInterface +{ + private const LOCALE_TO_TRANSLITERATOR_ID = [ + 'am' => 'Amharic-Latin', + 'ar' => 'Arabic-Latin', + 'az' => 'Azerbaijani-Latin', + 'be' => 'Belarusian-Latin', + 'bg' => 'Bulgarian-Latin', + 'bn' => 'Bengali-Latin', + 'de' => 'de-ASCII', + 'el' => 'Greek-Latin', + 'fa' => 'Persian-Latin', + 'he' => 'Hebrew-Latin', + 'hy' => 'Armenian-Latin', + 'ka' => 'Georgian-Latin', + 'kk' => 'Kazakh-Latin', + 'ky' => 'Kirghiz-Latin', + 'ko' => 'Korean-Latin', + 'mk' => 'Macedonian-Latin', + 'mn' => 'Mongolian-Latin', + 'or' => 'Oriya-Latin', + 'ps' => 'Pashto-Latin', + 'ru' => 'Russian-Latin', + 'sr' => 'Serbian-Latin', + 'sr_Cyrl' => 'Serbian-Latin', + 'th' => 'Thai-Latin', + 'tk' => 'Turkmen-Latin', + 'uk' => 'Ukrainian-Latin', + 'uz' => 'Uzbek-Latin', + 'zh' => 'Han-Latin', + ]; + + private $defaultLocale; + private $symbolsMap = [ + 'en' => ['@' => 'at', '&' => 'and'], + ]; + + /** + * Cache of transliterators per locale. + * + * @var \Transliterator[] + */ + private $transliterators = []; + + /** + * @param array|\Closure|null $symbolsMap + */ + public function __construct(string $defaultLocale = null, $symbolsMap = null) + { + if (null !== $symbolsMap && !\is_array($symbolsMap) && !$symbolsMap instanceof \Closure) { + throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be array, Closure or null, "%s" given.', __METHOD__, \gettype($symbolsMap))); + } + + $this->defaultLocale = $defaultLocale; + $this->symbolsMap = $symbolsMap ?? $this->symbolsMap; + } + + /** + * {@inheritdoc} + */ + public function setLocale($locale) + { + $this->defaultLocale = $locale; + } + + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->defaultLocale; + } + + /** + * {@inheritdoc} + */ + public function slug(string $string, string $separator = '-', string $locale = null): AbstractUnicodeString + { + $locale = $locale ?? $this->defaultLocale; + + $transliterator = []; + if ($locale && ('de' === $locale || 0 === strpos($locale, 'de_'))) { + // Use the shortcut for German in UnicodeString::ascii() if possible (faster and no requirement on intl) + $transliterator = ['de-ASCII']; + } elseif (\function_exists('transliterator_transliterate') && $locale) { + $transliterator = (array) $this->createTransliterator($locale); + } + + if ($this->symbolsMap instanceof \Closure) { + // If the symbols map is passed as a closure, there is no need to fallback to the parent locale + // as the closure can just provide substitutions for all locales of interest. + $symbolsMap = $this->symbolsMap; + array_unshift($transliterator, static function ($s) use ($symbolsMap, $locale) { + return $symbolsMap($s, $locale); + }); + } + + $unicodeString = (new UnicodeString($string))->ascii($transliterator); + + if (\is_array($this->symbolsMap)) { + $map = null; + if (isset($this->symbolsMap[$locale])) { + $map = $this->symbolsMap[$locale]; + } else { + $parent = self::getParentLocale($locale); + if ($parent && isset($this->symbolsMap[$parent])) { + $map = $this->symbolsMap[$parent]; + } + } + if ($map) { + foreach ($map as $char => $replace) { + $unicodeString = $unicodeString->replace($char, ' '.$replace.' '); + } + } + } + + return $unicodeString + ->replaceMatches('/[^A-Za-z0-9]++/', $separator) + ->trim($separator) + ; + } + + private function createTransliterator(string $locale): ?\Transliterator + { + if (\array_key_exists($locale, $this->transliterators)) { + return $this->transliterators[$locale]; + } + + // Exact locale supported, cache and return + if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$locale] ?? null) { + return $this->transliterators[$locale] = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id); + } + + // Locale not supported and no parent, fallback to any-latin + if (!$parent = self::getParentLocale($locale)) { + return $this->transliterators[$locale] = null; + } + + // Try to use the parent locale (ie. try "de" for "de_AT") and cache both locales + if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$parent] ?? null) { + $transliterator = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id); + } + + return $this->transliterators[$locale] = $this->transliterators[$parent] = $transliterator ?? null; + } + + private static function getParentLocale(?string $locale): ?string + { + if (!$locale) { + return null; + } + if (false === $str = strrchr($locale, '_')) { + // no parent locale + return null; + } + + return substr($locale, 0, -\strlen($str)); + } +} diff --git a/lib/symfony/string/Slugger/SluggerInterface.php b/lib/symfony/string/Slugger/SluggerInterface.php new file mode 100644 index 0000000000..c679ed9331 --- /dev/null +++ b/lib/symfony/string/Slugger/SluggerInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String\Slugger; + +use Symfony\Component\String\AbstractUnicodeString; + +/** + * Creates a URL-friendly slug from a given string. + * + * @author Titouan Galopin + */ +interface SluggerInterface +{ + /** + * Creates a slug for the given string and locale, using appropriate transliteration when needed. + */ + public function slug(string $string, string $separator = '-', string $locale = null): AbstractUnicodeString; +} diff --git a/lib/symfony/string/UnicodeString.php b/lib/symfony/string/UnicodeString.php new file mode 100644 index 0000000000..9b906c6fc2 --- /dev/null +++ b/lib/symfony/string/UnicodeString.php @@ -0,0 +1,377 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\String; + +use Symfony\Component\String\Exception\ExceptionInterface; +use Symfony\Component\String\Exception\InvalidArgumentException; + +/** + * Represents a string of Unicode grapheme clusters encoded as UTF-8. + * + * A letter followed by combining characters (accents typically) form what Unicode defines + * as a grapheme cluster: a character as humans mean it in written texts. This class knows + * about the concept and won't split a letter apart from its combining accents. It also + * ensures all string comparisons happen on their canonically-composed representation, + * ignoring e.g. the order in which accents are listed when a letter has many of them. + * + * @see https://unicode.org/reports/tr15/ + * + * @author Nicolas Grekas + * @author Hugo Hamon + * + * @throws ExceptionInterface + */ +class UnicodeString extends AbstractUnicodeString +{ + public function __construct(string $string = '') + { + $this->string = normalizer_is_normalized($string) ? $string : normalizer_normalize($string); + + if (false === $this->string) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + } + + public function append(string ...$suffix): AbstractString + { + $str = clone $this; + $str->string = $this->string.(1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix)); + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + if (false === $str->string) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function chunk(int $length = 1): array + { + if (1 > $length) { + throw new InvalidArgumentException('The chunk length must be greater than zero.'); + } + + if ('' === $this->string) { + return []; + } + + $rx = '/('; + while (65535 < $length) { + $rx .= '\X{65535}'; + $length -= 65535; + } + $rx .= '\X{'.$length.'})/u'; + + $str = clone $this; + $chunks = []; + + foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) { + $str->string = $chunk; + $chunks[] = clone $str; + } + + return $chunks; + } + + public function endsWith($suffix): bool + { + if ($suffix instanceof AbstractString) { + $suffix = $suffix->string; + } elseif (\is_array($suffix) || $suffix instanceof \Traversable) { + return parent::endsWith($suffix); + } else { + $suffix = (string) $suffix; + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($suffix, $form) ?: $suffix = normalizer_normalize($suffix, $form); + + if ('' === $suffix || false === $suffix) { + return false; + } + + if ($this->ignoreCase) { + return 0 === mb_stripos(grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix)), $suffix, 0, 'UTF-8'); + } + + return $suffix === grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix)); + } + + public function equalsTo($string): bool + { + if ($string instanceof AbstractString) { + $string = $string->string; + } elseif (\is_array($string) || $string instanceof \Traversable) { + return parent::equalsTo($string); + } else { + $string = (string) $string; + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($string, $form) ?: $string = normalizer_normalize($string, $form); + + if ('' !== $string && false !== $string && $this->ignoreCase) { + return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8'); + } + + return $string === $this->string; + } + + public function indexOf($needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (\is_array($needle) || $needle instanceof \Traversable) { + return parent::indexOf($needle, $offset); + } else { + $needle = (string) $needle; + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($needle, $form) ?: $needle = normalizer_normalize($needle, $form); + + if ('' === $needle || false === $needle) { + return null; + } + + try { + $i = $this->ignoreCase ? grapheme_stripos($this->string, $needle, $offset) : grapheme_strpos($this->string, $needle, $offset); + } catch (\ValueError $e) { + return null; + } + + return false === $i ? null : $i; + } + + public function indexOfLast($needle, int $offset = 0): ?int + { + if ($needle instanceof AbstractString) { + $needle = $needle->string; + } elseif (\is_array($needle) || $needle instanceof \Traversable) { + return parent::indexOfLast($needle, $offset); + } else { + $needle = (string) $needle; + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($needle, $form) ?: $needle = normalizer_normalize($needle, $form); + + if ('' === $needle || false === $needle) { + return null; + } + + $string = $this->string; + + if (0 > $offset) { + // workaround https://bugs.php.net/74264 + if (0 > $offset += grapheme_strlen($needle)) { + $string = grapheme_substr($string, 0, $offset); + } + $offset = 0; + } + + $i = $this->ignoreCase ? grapheme_strripos($string, $needle, $offset) : grapheme_strrpos($string, $needle, $offset); + + return false === $i ? null : $i; + } + + public function join(array $strings, string $lastGlue = null): AbstractString + { + $str = parent::join($strings, $lastGlue); + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + return $str; + } + + public function length(): int + { + return grapheme_strlen($this->string); + } + + /** + * @return static + */ + public function normalize(int $form = self::NFC): parent + { + $str = clone $this; + + if (\in_array($form, [self::NFC, self::NFKC], true)) { + normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form); + } elseif (!\in_array($form, [self::NFD, self::NFKD], true)) { + throw new InvalidArgumentException('Unsupported normalization form.'); + } elseif (!normalizer_is_normalized($str->string, $form)) { + $str->string = normalizer_normalize($str->string, $form); + $str->ignoreCase = null; + } + + return $str; + } + + public function prepend(string ...$prefix): AbstractString + { + $str = clone $this; + $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string; + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + if (false === $str->string) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function replace(string $from, string $to): AbstractString + { + $str = clone $this; + normalizer_is_normalized($from) ?: $from = normalizer_normalize($from); + + if ('' !== $from && false !== $from) { + $tail = $str->string; + $result = ''; + $indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos'; + + while ('' !== $tail && false !== $i = $indexOf($tail, $from)) { + $slice = grapheme_substr($tail, 0, $i); + $result .= $slice.$to; + $tail = substr($tail, \strlen($slice) + \strlen($from)); + } + + $str->string = $result.$tail; + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + if (false === $str->string) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + } + + return $str; + } + + public function replaceMatches(string $fromRegexp, $to): AbstractString + { + $str = parent::replaceMatches($fromRegexp, $to); + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + return $str; + } + + public function slice(int $start = 0, int $length = null): AbstractString + { + $str = clone $this; + + if (\PHP_VERSION_ID < 80000 && 0 > $start && grapheme_strlen($this->string) < -$start) { + $start = 0; + } + $str->string = (string) grapheme_substr($this->string, $start, $length ?? 2147483647); + + return $str; + } + + public function splice(string $replacement, int $start = 0, int $length = null): AbstractString + { + $str = clone $this; + + if (\PHP_VERSION_ID < 80000 && 0 > $start && grapheme_strlen($this->string) < -$start) { + $start = 0; + } + $start = $start ? \strlen(grapheme_substr($this->string, 0, $start)) : 0; + $length = $length ? \strlen(grapheme_substr($this->string, $start, $length ?? 2147483647)) : $length; + $str->string = substr_replace($this->string, $replacement, $start, $length ?? 2147483647); + normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string); + + if (false === $str->string) { + throw new InvalidArgumentException('Invalid UTF-8 string.'); + } + + return $str; + } + + public function split(string $delimiter, int $limit = null, int $flags = null): array + { + if (1 > $limit = $limit ?? 2147483647) { + throw new InvalidArgumentException('Split limit must be a positive integer.'); + } + + if ('' === $delimiter) { + throw new InvalidArgumentException('Split delimiter is empty.'); + } + + if (null !== $flags) { + return parent::split($delimiter.'u', $limit, $flags); + } + + normalizer_is_normalized($delimiter) ?: $delimiter = normalizer_normalize($delimiter); + + if (false === $delimiter) { + throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.'); + } + + $str = clone $this; + $tail = $this->string; + $chunks = []; + $indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos'; + + while (1 < $limit && false !== $i = $indexOf($tail, $delimiter)) { + $str->string = grapheme_substr($tail, 0, $i); + $chunks[] = clone $str; + $tail = substr($tail, \strlen($str->string) + \strlen($delimiter)); + --$limit; + } + + $str->string = $tail; + $chunks[] = clone $str; + + return $chunks; + } + + public function startsWith($prefix): bool + { + if ($prefix instanceof AbstractString) { + $prefix = $prefix->string; + } elseif (\is_array($prefix) || $prefix instanceof \Traversable) { + return parent::startsWith($prefix); + } else { + $prefix = (string) $prefix; + } + + $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC; + normalizer_is_normalized($prefix, $form) ?: $prefix = normalizer_normalize($prefix, $form); + + if ('' === $prefix || false === $prefix) { + return false; + } + + if ($this->ignoreCase) { + return 0 === mb_stripos(grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES), $prefix, 0, 'UTF-8'); + } + + return $prefix === grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES); + } + + public function __wakeup() + { + if (!\is_string($this->string)) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string); + } + + public function __clone() + { + if (null === $this->ignoreCase) { + normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string); + } + + $this->ignoreCase = false; + } +} diff --git a/lib/symfony/string/composer.json b/lib/symfony/string/composer.json new file mode 100644 index 0000000000..2b88fd529e --- /dev/null +++ b/lib/symfony/string/composer.json @@ -0,0 +1,43 @@ +{ + "name": "symfony/string", + "type": "library", + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "keywords": ["string", "utf8", "utf-8", "grapheme", "i18n", "unicode"], + "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.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\String\\": "" }, + "files": [ "Resources/functions.php" ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/lib/symfony/debug/.gitignore b/lib/symfony/translation-contracts/.gitignore similarity index 100% rename from lib/symfony/debug/.gitignore rename to lib/symfony/translation-contracts/.gitignore diff --git a/lib/symfony/translation-contracts/CHANGELOG.md b/lib/symfony/translation-contracts/CHANGELOG.md new file mode 100644 index 0000000000..7932e26132 --- /dev/null +++ b/lib/symfony/translation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/lib/symfony/translation-contracts/LICENSE b/lib/symfony/translation-contracts/LICENSE new file mode 100644 index 0000000000..74cdc2dbf6 --- /dev/null +++ b/lib/symfony/translation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2022 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/lib/symfony/translation-contracts/LocaleAwareInterface.php b/lib/symfony/translation-contracts/LocaleAwareInterface.php new file mode 100644 index 0000000000..693f92ba9b --- /dev/null +++ b/lib/symfony/translation-contracts/LocaleAwareInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +interface LocaleAwareInterface +{ + /** + * Sets the current locale. + * + * @param string $locale The locale + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public function setLocale(string $locale); + + /** + * Returns the current locale. + * + * @return string + */ + public function getLocale(); +} diff --git a/lib/symfony/translation-contracts/README.md b/lib/symfony/translation-contracts/README.md new file mode 100644 index 0000000000..42e5c51754 --- /dev/null +++ b/lib/symfony/translation-contracts/README.md @@ -0,0 +1,9 @@ +Symfony Translation Contracts +============================= + +A set of abstractions extracted out of the Symfony components. + +Can be used to build on semantics that the Symfony components proved useful - and +that already have battle tested implementations. + +See https://github.com/symfony/contracts/blob/main/README.md for more information. diff --git a/lib/symfony/translation-contracts/TranslatableInterface.php b/lib/symfony/translation-contracts/TranslatableInterface.php new file mode 100644 index 0000000000..47fd6fa029 --- /dev/null +++ b/lib/symfony/translation-contracts/TranslatableInterface.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 Symfony\Contracts\Translation; + +/** + * @author Nicolas Grekas + */ +interface TranslatableInterface +{ + public function trans(TranslatorInterface $translator, string $locale = null): string; +} diff --git a/lib/symfony/translation-contracts/TranslatorInterface.php b/lib/symfony/translation-contracts/TranslatorInterface.php new file mode 100644 index 0000000000..77b7a9c586 --- /dev/null +++ b/lib/symfony/translation-contracts/TranslatorInterface.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\Contracts\Translation; + +/** + * @author Fabien Potencier + * + * @method string getLocale() Returns the default locale + */ +interface TranslatorInterface +{ + /** + * Translates the given message. + * + * When a number is provided as a parameter named "%count%", the message is parsed for plural + * forms and a translation is chosen according to this number using the following rules: + * + * Given a message with different plural translations separated by a + * pipe (|), this method returns the correct portion of the message based + * on the given number, locale and the pluralization rules in the message + * itself. + * + * The message supports two different types of pluralization rules: + * + * interval: {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples + * indexed: There is one apple|There are %count% apples + * + * The indexed solution can also contain labels (e.g. one: There is one apple). + * This is purely for making the translations more clear - it does not + * affect the functionality. + * + * The two methods can also be mixed: + * {0} There are no apples|one: There is one apple|more: There are %count% apples + * + * An interval can represent a finite set of numbers: + * {1,2,3,4} + * + * An interval can represent numbers between two numbers: + * [1, +Inf] + * ]-1,2[ + * + * The left delimiter can be [ (inclusive) or ] (exclusive). + * The right delimiter can be [ (exclusive) or ] (inclusive). + * Beside numbers, you can use -Inf and +Inf for the infinite. + * + * @see https://en.wikipedia.org/wiki/ISO_31-11 + * + * @param string $id The message id (may also be an object that can be cast to string) + * @param array $parameters An array of parameters for the message + * @param string|null $domain The domain for the message or null to use the default + * @param string|null $locale The locale or null to use the default + * + * @return string + * + * @throws \InvalidArgumentException If the locale contains invalid characters + */ + public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null); +} diff --git a/lib/symfony/translation-contracts/TranslatorTrait.php b/lib/symfony/translation-contracts/TranslatorTrait.php new file mode 100644 index 0000000000..405ce8d70d --- /dev/null +++ b/lib/symfony/translation-contracts/TranslatorTrait.php @@ -0,0 +1,262 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Contracts\Translation; + +use Symfony\Component\Translation\Exception\InvalidArgumentException; + +/** + * A trait to help implement TranslatorInterface and LocaleAwareInterface. + * + * @author Fabien Potencier + */ +trait TranslatorTrait +{ + private $locale; + + /** + * {@inheritdoc} + */ + public function setLocale(string $locale) + { + $this->locale = $locale; + } + + /** + * {@inheritdoc} + * + * @return string + */ + public function getLocale() + { + return $this->locale ?: (class_exists(\Locale::class) ? \Locale::getDefault() : 'en'); + } + + /** + * {@inheritdoc} + */ + public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string + { + if (null === $id || '' === $id) { + return ''; + } + + if (!isset($parameters['%count%']) || !is_numeric($parameters['%count%'])) { + return strtr($id, $parameters); + } + + $number = (float) $parameters['%count%']; + $locale = $locale ?: $this->getLocale(); + + $parts = []; + if (preg_match('/^\|++$/', $id)) { + $parts = explode('|', $id); + } elseif (preg_match_all('/(?:\|\||[^\|])++/', $id, $matches)) { + $parts = $matches[0]; + } + + $intervalRegexp = <<<'EOF' +/^(?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; + + $standardRules = []; + foreach ($parts as $part) { + $part = trim(str_replace('||', '|', $part)); + + // try to match an explicit rule, then fallback to the standard ones + if (preg_match($intervalRegexp, $part, $matches)) { + if ($matches[2]) { + foreach (explode(',', $matches[3]) as $n) { + if ($number == $n) { + return strtr($matches['message'], $parameters); + } + } + } else { + $leftNumber = '-Inf' === $matches['left'] ? -\INF : (float) $matches['left']; + $rightNumber = is_numeric($matches['right']) ? (float) $matches['right'] : \INF; + + if (('[' === $matches['left_delimiter'] ? $number >= $leftNumber : $number > $leftNumber) + && (']' === $matches['right_delimiter'] ? $number <= $rightNumber : $number < $rightNumber) + ) { + return strtr($matches['message'], $parameters); + } + } + } elseif (preg_match('/^\w+\:\s*(.*?)$/', $part, $matches)) { + $standardRules[] = $matches[1]; + } else { + $standardRules[] = $part; + } + } + + $position = $this->getPluralizationRule($number, $locale); + + if (!isset($standardRules[$position])) { + // when there's exactly one rule given, and that rule is a standard + // rule, use this rule + if (1 === \count($parts) && isset($standardRules[0])) { + 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); + + if (class_exists(InvalidArgumentException::class)) { + throw new InvalidArgumentException($message); + } + + throw new \InvalidArgumentException($message); + } + + return strtr($standardRules[$position], $parameters); + } + + /** + * Returns the plural position to use for the given locale and number. + * + * The plural rules are derived from code of the Zend Framework (2010-09-25), + * which is subject to the new BSD license (http://framework.zend.com/license/new-bsd). + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + */ + private function getPluralizationRule(float $number, string $locale): int + { + $number = abs($number); + + switch ('pt_BR' !== $locale && 'en_US_POSIX' !== $locale && \strlen($locale) > 3 ? substr($locale, 0, strrpos($locale, '_')) : $locale) { + case 'af': + case 'bn': + case 'bg': + case 'ca': + case 'da': + case 'de': + case 'el': + case 'en': + case 'en_US_POSIX': + case 'eo': + case 'es': + case 'et': + case 'eu': + case 'fa': + case 'fi': + case 'fo': + case 'fur': + case 'fy': + case 'gl': + case 'gu': + case 'ha': + case 'he': + case 'hu': + case 'is': + case 'it': + case 'ku': + case 'lb': + case 'ml': + case 'mn': + case 'mr': + case 'nah': + case 'nb': + case 'ne': + case 'nl': + case 'nn': + case 'no': + case 'oc': + case 'om': + case 'or': + case 'pa': + case 'pap': + case 'ps': + case 'pt': + case 'so': + case 'sq': + case 'sv': + case 'sw': + case 'ta': + case 'te': + case 'tk': + case 'ur': + case 'zu': + return (1 == $number) ? 0 : 1; + + case 'am': + case 'bh': + case 'fil': + case 'fr': + case 'gun': + case 'hi': + case 'hy': + case 'ln': + case 'mg': + case 'nso': + case 'pt_BR': + case 'ti': + case 'wa': + return ($number < 2) ? 0 : 1; + + case 'be': + case 'bs': + case 'hr': + case 'ru': + case 'sh': + case 'sr': + case 'uk': + return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + + case 'cs': + case 'sk': + return (1 == $number) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2); + + case 'ga': + return (1 == $number) ? 0 : ((2 == $number) ? 1 : 2); + + case 'lt': + return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + + case 'sl': + return (1 == $number % 100) ? 0 : ((2 == $number % 100) ? 1 : (((3 == $number % 100) || (4 == $number % 100)) ? 2 : 3)); + + case 'mk': + return (1 == $number % 10) ? 0 : 1; + + case 'mt': + return (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3)); + + case 'lv': + return (0 == $number) ? 0 : (((1 == $number % 10) && (11 != $number % 100)) ? 1 : 2); + + case 'pl': + return (1 == $number) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2); + + case 'cy': + return (1 == $number) ? 0 : ((2 == $number) ? 1 : (((8 == $number) || (11 == $number)) ? 2 : 3)); + + case 'ro': + return (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2); + + case 'ar': + return (0 == $number) ? 0 : ((1 == $number) ? 1 : ((2 == $number) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : 5)))); + + default: + return 0; + } + } +} diff --git a/lib/symfony/translation-contracts/composer.json b/lib/symfony/translation-contracts/composer.json new file mode 100644 index 0000000000..65fe243a4f --- /dev/null +++ b/lib/symfony/translation-contracts/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/translation-contracts", + "type": "library", + "description": "Generic abstractions related to translation", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "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.5" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "autoload": { + "psr-4": { "Symfony\\Contracts\\Translation\\": "" } + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/lib/symfony/twig-bridge/.gitignore b/lib/symfony/twig-bridge/.gitignore deleted file mode 100644 index c49a5d8df5..0000000000 --- a/lib/symfony/twig-bridge/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/lib/symfony/twig-bridge/AppVariable.php b/lib/symfony/twig-bridge/AppVariable.php index eb9cec6dd9..23683eb35e 100644 --- a/lib/symfony/twig-bridge/AppVariable.php +++ b/lib/symfony/twig-bridge/AppVariable.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\User\UserInterface; /** * Exposes some Symfony parameters and services as an "app" global variable. @@ -39,14 +40,14 @@ class AppVariable $this->requestStack = $requestStack; } - public function setEnvironment($environment) + public function setEnvironment(string $environment) { $this->environment = $environment; } - public function setDebug($debug) + public function setDebug(bool $debug) { - $this->debug = (bool) $debug; + $this->debug = $debug; } /** @@ -68,7 +69,7 @@ class AppVariable /** * Returns the current user. * - * @return object|null + * @return UserInterface|null * * @see TokenInterface::getUser() */ @@ -84,13 +85,14 @@ class AppVariable $user = $token->getUser(); + // @deprecated since Symfony 5.4, $user will always be a UserInterface instance return \is_object($user) ? $user : null; } /** * Returns the current request. * - * @return Request|null The HTTP request object + * @return Request|null */ public function getRequest() { @@ -104,21 +106,22 @@ class AppVariable /** * Returns the current session. * - * @return Session|null The session + * @return Session|null */ public function getSession() { if (null === $this->requestStack) { throw new \RuntimeException('The "app.session" variable is not available.'); } + $request = $this->getRequest(); - return ($request = $this->getRequest()) ? $request->getSession() : null; + return $request && $request->hasSession() ? $request->getSession() : null; } /** * Returns the current app environment. * - * @return string The current environment string (e.g 'dev') + * @return string */ public function getEnvironment() { @@ -132,7 +135,7 @@ class AppVariable /** * Returns the current app debug mode. * - * @return bool The current debug mode + * @return bool */ public function getDebug() { @@ -154,8 +157,7 @@ class AppVariable public function getFlashes($types = null) { try { - $session = $this->getSession(); - if (null === $session) { + if (null === $session = $this->getSession()) { return []; } } catch (\RuntimeException $e) { diff --git a/lib/symfony/twig-bridge/CHANGELOG.md b/lib/symfony/twig-bridge/CHANGELOG.md index 4ccde38947..535df0c089 100644 --- a/lib/symfony/twig-bridge/CHANGELOG.md +++ b/lib/symfony/twig-bridge/CHANGELOG.md @@ -1,6 +1,75 @@ CHANGELOG ========= +5.4 +--- + +* Add `github` format & autodetection to render errors as annotations when + running the Twig linter command in a Github Actions environment. + +5.3 +--- + + * Add a new `markAsPublic` method on `NotificationEmail` to change the `importance` context option to null after creation + * Add a new `fragment_uri()` helper to generate the URI of a fragment + * Add support of Bootstrap 5 for form theming + * Add a new `serialize` filter to serialize objects using the Serializer component + +5.2.0 +----- + + * added the `impersonation_exit_url()` and `impersonation_exit_path()` functions. They return a URL that allows to switch back to the original user. + * added the `workflow_transition()` function to easily retrieve a specific transition object + * added support for translating `TranslatableInterface` objects + * added the `t()` function to easily create `TranslatableMessage` objects + * Added support for extracting messages from the `t()` function + * Added `field_*` Twig functions to access string values from Form fields + * changed the `importance` context option of `NotificationEmail` to allow `null` + +5.0.0 +----- + + * removed `TwigEngine` class, use `\Twig\Environment` instead. + * removed `transChoice` filter and token + * `HttpFoundationExtension` requires a `UrlHelper` on instantiation + * removed support for implicit STDIN usage in the `lint:twig` command, use `lint:twig -` (append a dash) instead to make it explicit. + * added form theme for Foundation 6 + * added support for Foundation 6 switches: add the `switch-input` class to the attributes of a `CheckboxType` + +4.4.0 +----- + + * added a new `TwigErrorRenderer` for `html` format, integrated with the `ErrorHandler` component + * marked all classes extending twig as `@final` + * deprecated to pass `$rootDir` and `$fileLinkFormatter` as 5th and 6th argument respectively to the + `DebugCommand::__construct()` method, swap the variables position. + * the `LintCommand` lints all the templates stored in all configured Twig paths if none argument is provided + * deprecated accepting STDIN implicitly when using the `lint:twig` command, use `lint:twig -` (append a dash) instead to make it explicit. + * added `--show-deprecations` option to the `lint:twig` command + * added support for Bootstrap4 switches: add the `switch-custom` class to the label attributes of a `CheckboxType` + * Marked the `TwigDataCollector` class as `@final`. + +4.3.0 +----- + + * added the `form_parent()` function that allows to reliably retrieve the parent form in Twig templates + * added the `workflow_transition_blockers()` function + * deprecated the `$requestStack` and `$requestContext` arguments of the + `HttpFoundationExtension`, pass a `Symfony\Component\HttpFoundation\UrlHelper` + instance as the only argument instead + +4.2.0 +----- + + * add bundle name suggestion on wrongly overridden templates paths + * added `name` argument in `debug:twig` command and changed `filter` argument as `--filter` option + * deprecated the `transchoice` tag and filter, use the `trans` ones instead with a `%count%` parameter + +4.1.0 +----- + + * add a `workflow_metadata` function + 3.4.0 ----- diff --git a/lib/symfony/twig-bridge/Command/DebugCommand.php b/lib/symfony/twig-bridge/Command/DebugCommand.php index e196f1b82d..d4c7821011 100644 --- a/lib/symfony/twig-bridge/Command/DebugCommand.php +++ b/lib/symfony/twig-bridge/Command/DebugCommand.php @@ -12,13 +12,19 @@ namespace Symfony\Bridge\Twig\Command; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Input\InputArgument; 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\Finder\Finder; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use Twig\Environment; +use Twig\Loader\ChainLoader; use Twig\Loader\FilesystemLoader; /** @@ -29,64 +35,48 @@ use Twig\Loader\FilesystemLoader; class DebugCommand extends Command { protected static $defaultName = 'debug:twig'; + protected static $defaultDescription = 'Show a list of twig functions, filters, globals and tests'; private $twig; private $projectDir; + private $bundlesMetadata; + private $twigDefaultPath; + private $filesystemLoaders; + private $fileLinkFormatter; - /** - * @param Environment $twig - * @param string|null $projectDir - */ - public function __construct($twig = null, $projectDir = null) + public function __construct(Environment $twig, string $projectDir = null, array $bundlesMetadata = [], string $twigDefaultPath = null, FileLinkFormatter $fileLinkFormatter = null) { - if (!$twig instanceof Environment) { - @trigger_error(sprintf('Passing a command name as the first argument of "%s()" is deprecated since Symfony 3.4 and support for it will be removed in 4.0. If the command was registered by convention, make it a service instead.', __METHOD__), \E_USER_DEPRECATED); - - parent::__construct($twig); - - return; - } - parent::__construct(); $this->twig = $twig; $this->projectDir = $projectDir; - } - - public function setTwigEnvironment(Environment $twig) - { - @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.4 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); - - $this->twig = $twig; - } - - /** - * @return Environment $twig - */ - protected function getTwigEnvironment() - { - @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.4 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); - - return $this->twig; + $this->bundlesMetadata = $bundlesMetadata; + $this->twigDefaultPath = $twigDefaultPath; + $this->fileLinkFormatter = $fileLinkFormatter; } protected function configure() { $this ->setDefinition([ - new InputArgument('filter', InputArgument::OPTIONAL, 'Show details for all entries matching this filter'), + new InputArgument('name', InputArgument::OPTIONAL, 'The template name'), + new InputOption('filter', null, InputOption::VALUE_REQUIRED, 'Show details for all entries matching this filter'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (text or json)', 'text'), ]) - ->setDescription('Shows a list of twig functions, filters, globals and tests') + ->setDescription(self::$defaultDescription) ->setHelp(<<<'EOF' The %command.name% command outputs a list of twig functions, -filters, globals and tests. Output can be filtered with an optional argument. +filters, globals and tests. php %command.full_name% The command lists all functions, filters, etc. - php %command.full_name% date + php %command.full_name% @Twig/Exception/error.html.twig + +The command lists all paths that match the given template name. + + php %command.full_name% --filter=date The command lists everything that contains the word date. @@ -101,49 +91,141 @@ EOF protected function execute(InputInterface $input, OutputInterface $output) { $io = new SymfonyStyle($input, $output); - $decorated = $io->isDecorated(); + $name = $input->getArgument('name'); + $filter = $input->getOption('filter'); - // BC to be removed in 4.0 - if (__CLASS__ !== static::class) { - $r = new \ReflectionMethod($this, 'getTwigEnvironment'); - if (__CLASS__ !== $r->getDeclaringClass()->getName()) { - @trigger_error(sprintf('Usage of method "%s" is deprecated since Symfony 3.4 and will no longer be supported in 4.0. Construct the command with its required arguments instead.', static::class.'::getTwigEnvironment'), \E_USER_DEPRECATED); + if (null !== $name && [] === $this->getFilesystemLoaders()) { + throw new InvalidArgumentException(sprintf('Argument "name" not supported, it requires the Twig loader "%s".', FilesystemLoader::class)); + } - $this->twig = $this->getTwigEnvironment(); + switch ($input->getOption('format')) { + case 'text': + $name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter); + break; + case 'json': + $name ? $this->displayPathsJson($io, $name) : $this->displayGeneralJson($io, $filter); + break; + default: + throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))); + } + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestArgumentValuesFor('name')) { + $suggestions->suggestValues(array_keys($this->getLoaderPaths())); + } + + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues(['text', 'json']); + } + } + + private function displayPathsText(SymfonyStyle $io, string $name) + { + $file = new \ArrayIterator($this->findTemplateFiles($name)); + $paths = $this->getLoaderPaths($name); + + $io->section('Matched File'); + if ($file->valid()) { + if ($fileLink = $this->getFileLink($file->key())) { + $io->block($file->current(), 'OK', sprintf('fg=black;bg=green;href=%s', $fileLink), ' ', true); + } else { + $io->success($file->current()); } - } - if (null === $this->twig) { - throw new \RuntimeException('The Twig environment needs to be set.'); + $file->next(); + + if ($file->valid()) { + $io->section('Overridden Files'); + do { + if ($fileLink = $this->getFileLink($file->key())) { + $io->text(sprintf('* %s', $fileLink, $file->current())); + } else { + $io->text(sprintf('* %s', $file->current())); + } + $file->next(); + } while ($file->valid()); + } + } else { + $alternatives = []; + + if ($paths) { + $shortnames = []; + $dirs = []; + foreach (current($paths) as $path) { + $dirs[] = $this->isAbsolutePath($path) ? $path : $this->projectDir.'/'.$path; + } + foreach (Finder::create()->files()->followLinks()->in($dirs) as $file) { + $shortnames[] = str_replace('\\', '/', $file->getRelativePathname()); + } + + [$namespace, $shortname] = $this->parseTemplateName($name); + $alternatives = $this->findAlternatives($shortname, $shortnames); + if (FilesystemLoader::MAIN_NAMESPACE !== $namespace) { + $alternatives = array_map(function ($shortname) use ($namespace) { + return '@'.$namespace.'/'.$shortname; + }, $alternatives); + } + } + + $this->error($io, sprintf('Template name "%s" not found', $name), $alternatives); } - $filter = $input->getArgument('filter'); - $types = ['functions', 'filters', 'tests', 'globals']; + $io->section('Configured Paths'); + if ($paths) { + $io->table(['Namespace', 'Paths'], $this->buildTableRows($paths)); + } else { + $alternatives = []; + $namespace = $this->parseTemplateName($name)[0]; - if ('json' === $input->getOption('format')) { - $data = []; - foreach ($types as $type) { - foreach ($this->twig->{'get'.ucfirst($type)}() as $name => $entity) { - if (!$filter || false !== strpos($name, $filter)) { - $data[$type][$name] = $this->getMetadata($type, $entity); + if (FilesystemLoader::MAIN_NAMESPACE === $namespace) { + $message = 'No template paths configured for your application'; + } else { + $message = sprintf('No template paths configured for "@%s" namespace', $namespace); + foreach ($this->getFilesystemLoaders() as $loader) { + $namespaces = $loader->getNamespaces(); + foreach ($this->findAlternatives($namespace, $namespaces) as $namespace) { + $alternatives[] = '@'.$namespace; } } } - if (isset($data['tests'])) { - $data['tests'] = array_keys($data['tests']); + $this->error($io, $message, $alternatives); + + if (!$alternatives && $paths = $this->getLoaderPaths()) { + $io->table(['Namespace', 'Paths'], $this->buildTableRows($paths)); } - - $data['loader_paths'] = $this->getLoaderPaths(); - $data = json_encode($data, \JSON_PRETTY_PRINT); - $io->writeln($decorated ? OutputFormatter::escape($data) : $data); - - return 0; } + } + private function displayPathsJson(SymfonyStyle $io, string $name) + { + $files = $this->findTemplateFiles($name); + $paths = $this->getLoaderPaths($name); + + if ($files) { + $data['matched_file'] = array_shift($files); + if ($files) { + $data['overridden_files'] = $files; + } + } else { + $data['matched_file'] = sprintf('Template name "%s" not found', $name); + } + $data['loader_paths'] = $paths; + + $io->writeln(json_encode($data)); + } + + private function displayGeneralText(SymfonyStyle $io, string $filter = null) + { + $decorated = $io->isDecorated(); + $types = ['functions', 'filters', 'tests', 'globals']; foreach ($types as $index => $type) { $items = []; foreach ($this->twig->{'get'.ucfirst($type)}() as $name => $entity) { - if (!$filter || false !== strpos($name, $filter)) { + if (!$filter || str_contains($name, $filter)) { $items[$name] = $name.$this->getPrettyMetadata($type, $entity, $decorated); } } @@ -158,63 +240,73 @@ EOF $io->listing($items); } - $rows = []; - $firstNamespace = true; - $prevHasSeparator = false; - foreach ($this->getLoaderPaths() as $namespace => $paths) { - if (!$firstNamespace && !$prevHasSeparator && \count($paths) > 1) { - $rows[] = ['', '']; - } - $firstNamespace = false; - foreach ($paths as $path) { - $rows[] = [$namespace, $path.\DIRECTORY_SEPARATOR]; - $namespace = ''; - } - if (\count($paths) > 1) { - $rows[] = ['', '']; - $prevHasSeparator = true; - } else { - $prevHasSeparator = false; - } + if (!$filter && $paths = $this->getLoaderPaths()) { + $io->section('Loader Paths'); + $io->table(['Namespace', 'Paths'], $this->buildTableRows($paths)); } - if ($prevHasSeparator) { - array_pop($rows); - } - $io->section('Loader Paths'); - $io->table(['Namespace', 'Paths'], $rows); - return 0; + if ($wrongBundles = $this->findWrongBundleOverrides()) { + foreach ($this->buildWarningMessages($wrongBundles) as $message) { + $io->warning($message); + } + } } - private function getLoaderPaths() + private function displayGeneralJson(SymfonyStyle $io, ?string $filter) { - if (!($loader = $this->twig->getLoader()) instanceof FilesystemLoader) { - return []; + $decorated = $io->isDecorated(); + $types = ['functions', 'filters', 'tests', 'globals']; + $data = []; + foreach ($types as $type) { + foreach ($this->twig->{'get'.ucfirst($type)}() as $name => $entity) { + if (!$filter || str_contains($name, $filter)) { + $data[$type][$name] = $this->getMetadata($type, $entity); + } + } + } + if (isset($data['tests'])) { + $data['tests'] = array_keys($data['tests']); } + if (!$filter && $paths = $this->getLoaderPaths($filter)) { + $data['loader_paths'] = $paths; + } + + if ($wrongBundles = $this->findWrongBundleOverrides()) { + $data['warnings'] = $this->buildWarningMessages($wrongBundles); + } + + $data = json_encode($data, \JSON_PRETTY_PRINT); + $io->writeln($decorated ? OutputFormatter::escape($data) : $data); + } + + private function getLoaderPaths(string $name = null): array + { $loaderPaths = []; - foreach ($loader->getNamespaces() as $namespace) { - $paths = array_map(function ($path) { - if (null !== $this->projectDir && 0 === strpos($path, $this->projectDir)) { - $path = ltrim(substr($path, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); - } - - return $path; - }, $loader->getPaths($namespace)); - - if (FilesystemLoader::MAIN_NAMESPACE === $namespace) { - $namespace = '(None)'; - } else { - $namespace = '@'.$namespace; + foreach ($this->getFilesystemLoaders() as $loader) { + $namespaces = $loader->getNamespaces(); + if (null !== $name) { + $namespace = $this->parseTemplateName($name)[0]; + $namespaces = array_intersect([$namespace], $namespaces); } - $loaderPaths[$namespace] = $paths; + foreach ($namespaces as $namespace) { + $paths = array_map([$this, 'getRelativePath'], $loader->getPaths($namespace)); + + if (FilesystemLoader::MAIN_NAMESPACE === $namespace) { + $namespace = '(None)'; + } else { + $namespace = '@'.$namespace; + } + + $loaderPaths[$namespace] = array_merge($loaderPaths[$namespace] ?? [], $paths); + } } return $loaderPaths; } - private function getMetadata($type, $entity) + private function getMetadata(string $type, $entity) { if ('globals' === $type) { return $entity; @@ -258,7 +350,7 @@ EOF } // format args - $args = array_map(function ($param) { + $args = array_map(function (\ReflectionParameter $param) { if ($param->isDefaultValueAvailable()) { return $param->getName().' = '.json_encode($param->getDefaultValue()); } @@ -272,7 +364,7 @@ EOF return null; } - private function getPrettyMetadata($type, $entity, $decorated) + private function getPrettyMetadata(string $type, $entity, bool $decorated): ?string { if ('tests' === $type) { return ''; @@ -307,4 +399,202 @@ EOF return null; } + + private function findWrongBundleOverrides(): array + { + $alternatives = []; + $bundleNames = []; + + if ($this->twigDefaultPath && $this->projectDir) { + $folders = glob($this->twigDefaultPath.'/bundles/*', \GLOB_ONLYDIR); + $relativePath = ltrim(substr($this->twigDefaultPath.'/bundles/', \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); + $bundleNames = array_reduce($folders, function ($carry, $absolutePath) use ($relativePath) { + if (str_starts_with($absolutePath, $this->projectDir)) { + $name = basename($absolutePath); + $path = ltrim($relativePath.$name, \DIRECTORY_SEPARATOR); + $carry[$name] = $path; + } + + return $carry; + }, $bundleNames); + } + + if ($notFoundBundles = array_diff_key($bundleNames, $this->bundlesMetadata)) { + $alternatives = []; + foreach ($notFoundBundles as $notFoundBundle => $path) { + $alternatives[$path] = $this->findAlternatives($notFoundBundle, array_keys($this->bundlesMetadata)); + } + } + + return $alternatives; + } + + private function buildWarningMessages(array $wrongBundles): array + { + $messages = []; + foreach ($wrongBundles as $path => $alternatives) { + $message = sprintf('Path "%s" not matching any bundle found', $path); + if ($alternatives) { + if (1 === \count($alternatives)) { + $message .= sprintf(", did you mean \"%s\"?\n", $alternatives[0]); + } else { + $message .= ", did you mean one of these:\n"; + foreach ($alternatives as $bundle) { + $message .= sprintf(" - %s\n", $bundle); + } + } + } + $messages[] = trim($message); + } + + return $messages; + } + + private function error(SymfonyStyle $io, string $message, array $alternatives = []): void + { + if ($alternatives) { + if (1 === \count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + $message .= implode("\n ", $alternatives); + } + + $io->block($message, null, 'fg=white;bg=red', ' ', true); + } + + private function findTemplateFiles(string $name): array + { + [$namespace, $shortname] = $this->parseTemplateName($name); + + $files = []; + foreach ($this->getFilesystemLoaders() as $loader) { + foreach ($loader->getPaths($namespace) as $path) { + if (!$this->isAbsolutePath($path)) { + $path = $this->projectDir.'/'.$path; + } + $filename = $path.'/'.$shortname; + + if (is_file($filename)) { + if (false !== $realpath = realpath($filename)) { + $files[$realpath] = $this->getRelativePath($realpath); + } else { + $files[$filename] = $this->getRelativePath($filename); + } + } + } + } + + return $files; + } + + private function parseTemplateName(string $name, string $default = FilesystemLoader::MAIN_NAMESPACE): array + { + if (isset($name[0]) && '@' === $name[0]) { + if (false === ($pos = strpos($name, '/')) || $pos === \strlen($name) - 1) { + throw new InvalidArgumentException(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name)); + } + + $namespace = substr($name, 1, $pos - 1); + $shortname = substr($name, $pos + 1); + + return [$namespace, $shortname]; + } + + return [$default, $name]; + } + + private function buildTableRows(array $loaderPaths): array + { + $rows = []; + $firstNamespace = true; + $prevHasSeparator = false; + + foreach ($loaderPaths as $namespace => $paths) { + if (!$firstNamespace && !$prevHasSeparator && \count($paths) > 1) { + $rows[] = ['', '']; + } + $firstNamespace = false; + foreach ($paths as $path) { + $rows[] = [$namespace, $path.\DIRECTORY_SEPARATOR]; + $namespace = ''; + } + if (\count($paths) > 1) { + $rows[] = ['', '']; + $prevHasSeparator = true; + } else { + $prevHasSeparator = false; + } + } + if ($prevHasSeparator) { + array_pop($rows); + } + + return $rows; + } + + private function findAlternatives(string $name, array $collection): array + { + $alternatives = []; + foreach ($collection as $item) { + $lev = levenshtein($name, $item); + if ($lev <= \strlen($name) / 3 || str_contains($item, $name)) { + $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; + } + } + + $threshold = 1e3; + $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); + ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE); + + return array_keys($alternatives); + } + + private function getRelativePath(string $path): string + { + if (null !== $this->projectDir && str_starts_with($path, $this->projectDir)) { + return ltrim(substr($path, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); + } + + return $path; + } + + private function isAbsolutePath(string $file): bool + { + return strspn($file, '/\\', 0, 1) || (\strlen($file) > 3 && ctype_alpha($file[0]) && ':' === $file[1] && strspn($file, '/\\', 2, 1)) || null !== parse_url($file, \PHP_URL_SCHEME); + } + + /** + * @return FilesystemLoader[] + */ + private function getFilesystemLoaders(): array + { + if (null !== $this->filesystemLoaders) { + return $this->filesystemLoaders; + } + $this->filesystemLoaders = []; + + $loader = $this->twig->getLoader(); + if ($loader instanceof FilesystemLoader) { + $this->filesystemLoaders[] = $loader; + } elseif ($loader instanceof ChainLoader) { + foreach ($loader->getLoaders() as $l) { + if ($l instanceof FilesystemLoader) { + $this->filesystemLoaders[] = $l; + } + } + } + + return $this->filesystemLoaders; + } + + private function getFileLink(string $absolutePath): string + { + if (null === $this->fileLinkFormatter) { + return ''; + } + + return (string) $this->fileLinkFormatter->format($absolutePath, 1); + } } diff --git a/lib/symfony/twig-bridge/Command/LintCommand.php b/lib/symfony/twig-bridge/Command/LintCommand.php index c23c27b37c..b91110b34a 100644 --- a/lib/symfony/twig-bridge/Command/LintCommand.php +++ b/lib/symfony/twig-bridge/Command/LintCommand.php @@ -11,7 +11,10 @@ namespace Symfony\Bridge\Twig\Command; +use Symfony\Component\Console\CI\GithubActionReporter; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputArgument; @@ -23,6 +26,7 @@ use Symfony\Component\Finder\Finder; use Twig\Environment; use Twig\Error\Error; use Twig\Loader\ArrayLoader; +use Twig\Loader\FilesystemLoader; use Twig\Source; /** @@ -34,57 +38,36 @@ use Twig\Source; class LintCommand extends Command { protected static $defaultName = 'lint:twig'; + protected static $defaultDescription = 'Lint a Twig template and outputs encountered errors'; private $twig; /** - * @param Environment $twig + * @var string|null */ - public function __construct($twig = null) + private $format; + + public function __construct(Environment $twig) { - if (!$twig instanceof Environment) { - @trigger_error(sprintf('Passing a command name as the first argument of "%s()" is deprecated since Symfony 3.4 and support for it will be removed in 4.0. If the command was registered by convention, make it a service instead.', __METHOD__), \E_USER_DEPRECATED); - - parent::__construct($twig); - - return; - } - parent::__construct(); $this->twig = $twig; } - public function setTwigEnvironment(Environment $twig) - { - @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.4 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); - - $this->twig = $twig; - } - - /** - * @return Environment $twig - */ - protected function getTwigEnvironment() - { - @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.4 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED); - - return $this->twig; - } - protected function configure() { $this - ->setDescription('Lints a template and outputs encountered errors') - ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt') - ->addArgument('filename', InputArgument::IS_ARRAY) + ->setDescription(self::$defaultDescription) + ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format') + ->addOption('show-deprecations', null, InputOption::VALUE_NONE, 'Show deprecations as errors') + ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') ->setHelp(<<<'EOF' The %command.name% command lints a template and outputs to STDOUT the first encountered syntax error. You can validate the syntax of contents passed from STDIN: - cat filename | php %command.full_name% + cat filename | php %command.full_name% - Or the syntax of a file: @@ -103,41 +86,60 @@ EOF protected function execute(InputInterface $input, OutputInterface $output) { $io = new SymfonyStyle($input, $output); - - // BC to be removed in 4.0 - if (__CLASS__ !== static::class) { - $r = new \ReflectionMethod($this, 'getTwigEnvironment'); - if (__CLASS__ !== $r->getDeclaringClass()->getName()) { - @trigger_error(sprintf('Usage of method "%s" is deprecated since Symfony 3.4 and will no longer be supported in 4.0. Construct the command with its required arguments instead.', static::class.'::getTwigEnvironment'), \E_USER_DEPRECATED); - - $this->twig = $this->getTwigEnvironment(); - } - } - if (null === $this->twig) { - throw new \RuntimeException('The Twig environment needs to be set.'); - } - $filenames = $input->getArgument('filename'); + $showDeprecations = $input->getOption('show-deprecations'); + $this->format = $input->getOption('format'); - if (0 === \count($filenames)) { - if (0 !== ftell(\STDIN)) { + if (null === $this->format) { + $this->format = GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'; + } + + if (['-'] === $filenames) { + return $this->display($input, $output, $io, [$this->validate(file_get_contents('php://stdin'), uniqid('sf_', true))]); + } + + if (!$filenames) { + $loader = $this->twig->getLoader(); + if ($loader instanceof FilesystemLoader) { + $paths = []; + foreach ($loader->getNamespaces() as $namespace) { + $paths[] = $loader->getPaths($namespace); + } + $filenames = array_merge(...$paths); + } + + if (!$filenames) { throw new RuntimeException('Please provide a filename or pipe template content to STDIN.'); } - - $template = ''; - while (!feof(\STDIN)) { - $template .= fread(\STDIN, 1024); - } - - return $this->display($input, $output, $io, [$this->validate($template, uniqid('sf_', true))]); } - $filesInfo = $this->getFilesInfo($filenames); + if ($showDeprecations) { + $prevErrorHandler = set_error_handler(static function ($level, $message, $file, $line) use (&$prevErrorHandler) { + if (\E_USER_DEPRECATED === $level) { + $templateLine = 0; + if (preg_match('/ at line (\d+)[ .]/', $message, $matches)) { + $templateLine = $matches[1]; + } + + throw new Error($message, $templateLine); + } + + return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false; + }); + } + + try { + $filesInfo = $this->getFilesInfo($filenames); + } finally { + if ($showDeprecations) { + restore_error_handler(); + } + } return $this->display($input, $output, $io, $filesInfo); } - private function getFilesInfo(array $filenames) + private function getFilesInfo(array $filenames): array { $filesInfo = []; foreach ($filenames as $filename) { @@ -149,7 +151,7 @@ EOF return $filesInfo; } - protected function findFiles($filename) + protected function findFiles(string $filename) { if (is_file($filename)) { return [$filename]; @@ -160,13 +162,13 @@ EOF throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); } - private function validate($template, $file) + private function validate(string $template, string $file): array { $realLoader = $this->twig->getLoader(); try { - $temporaryLoader = new ArrayLoader([(string) $file => $template]); + $temporaryLoader = new ArrayLoader([$file => $template]); $this->twig->setLoader($temporaryLoader); - $nodeTree = $this->twig->parse($this->twig->tokenize(new Source($template, (string) $file))); + $nodeTree = $this->twig->parse($this->twig->tokenize(new Source($template, $file))); $this->twig->compile($nodeTree); $this->twig->setLoader($realLoader); } catch (Error $e) { @@ -178,28 +180,31 @@ EOF return ['template' => $template, 'file' => $file, 'valid' => true]; } - private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, $files) + private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, array $files) { - switch ($input->getOption('format')) { + switch ($this->format) { case 'txt': return $this->displayTxt($output, $io, $files); case 'json': return $this->displayJson($output, $files); + case 'github': + return $this->displayTxt($output, $io, $files, true); default: throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))); } } - private function displayTxt(OutputInterface $output, SymfonyStyle $io, $filesInfo) + private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false) { $errors = 0; + $githubReporter = $errorAsGithubAnnotations ? new GithubActionReporter($output) : null; foreach ($filesInfo as $info) { if ($info['valid'] && $output->isVerbose()) { $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); } elseif (!$info['valid']) { ++$errors; - $this->renderException($io, $info['template'], $info['exception'], $info['file']); + $this->renderException($io, $info['template'], $info['exception'], $info['file'], $githubReporter); } } @@ -212,7 +217,7 @@ EOF return min($errors, 1); } - private function displayJson(OutputInterface $output, $filesInfo) + private function displayJson(OutputInterface $output, array $filesInfo) { $errors = 0; @@ -231,16 +236,28 @@ EOF return min($errors, 1); } - private function renderException(OutputInterface $output, $template, Error $exception, $file = null) + private function renderException(SymfonyStyle $output, string $template, Error $exception, string $file = null, GithubActionReporter $githubReporter = null) { $line = $exception->getTemplateLine(); + if ($githubReporter) { + $githubReporter->error($exception->getRawMessage(), $file, $line <= 0 ? null : $line); + } + if ($file) { $output->text(sprintf(' ERROR in %s (line %s)', $file, $line)); } else { $output->text(sprintf(' ERROR (line %s)', $line)); } + // If the line is not known (this might happen for deprecations if we fail at detecting the line for instance), + // we render the message without context, to ensure the message is displayed. + if ($line <= 0) { + $output->text(sprintf(' >> %s ', $exception->getRawMessage())); + + return; + } + foreach ($this->getContext($template, $line) as $lineNumber => $code) { $output->text(sprintf( '%s %-6s %s', @@ -254,7 +271,7 @@ EOF } } - private function getContext($template, $line, $context = 3) + private function getContext(string $template, int $line, int $context = 3) { $lines = explode("\n", $template); @@ -269,4 +286,11 @@ EOF return $result; } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues(['txt', 'json', 'github']); + } + } } diff --git a/lib/symfony/twig-bridge/DataCollector/TwigDataCollector.php b/lib/symfony/twig-bridge/DataCollector/TwigDataCollector.php index 80e36e0491..4a46978108 100644 --- a/lib/symfony/twig-bridge/DataCollector/TwigDataCollector.php +++ b/lib/symfony/twig-bridge/DataCollector/TwigDataCollector.php @@ -22,9 +22,9 @@ use Twig\Profiler\Dumper\HtmlDumper; use Twig\Profiler\Profile; /** - * TwigDataCollector. - * * @author Fabien Potencier + * + * @final */ class TwigDataCollector extends DataCollector implements LateDataCollectorInterface { @@ -41,7 +41,7 @@ class TwigDataCollector extends DataCollector implements LateDataCollectorInterf /** * {@inheritdoc} */ - public function collect(Request $request, Response $response, \Exception $exception = null) + public function collect(Request $request, Response $response, \Throwable $exception = null) { } @@ -127,10 +127,12 @@ class TwigDataCollector extends DataCollector implements LateDataCollectorInterf '', '', '', + '', ], [ '', '', '', + '', ], $dump); return new Markup($dump, 'UTF-8'); @@ -139,17 +141,13 @@ class TwigDataCollector extends DataCollector implements LateDataCollectorInterf public function getProfile() { if (null === $this->profile) { - if (\PHP_VERSION_ID >= 70000) { - $this->profile = unserialize($this->data['profile'], ['allowed_classes' => ['Twig_Profiler_Profile', 'Twig\Profiler\Profile']]); - } else { - $this->profile = unserialize($this->data['profile']); - } + $this->profile = unserialize($this->data['profile'], ['allowed_classes' => ['Twig_Profiler_Profile', 'Twig\Profiler\Profile']]); } return $this->profile; } - private function getComputedData($index) + private function getComputedData(string $index) { if (null === $this->computed) { $this->computed = $this->computeData($this->getProfile()); @@ -198,7 +196,7 @@ class TwigDataCollector extends DataCollector implements LateDataCollectorInterf /** * {@inheritdoc} */ - public function getName() + public function getName(): string { return 'twig'; } diff --git a/lib/symfony/twig-bridge/ErrorRenderer/TwigErrorRenderer.php b/lib/symfony/twig-bridge/ErrorRenderer/TwigErrorRenderer.php new file mode 100644 index 0000000000..b0ccd684e8 --- /dev/null +++ b/lib/symfony/twig-bridge/ErrorRenderer/TwigErrorRenderer.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\ErrorRenderer; + +use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface; +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\HttpFoundation\RequestStack; +use Twig\Environment; + +/** + * Provides the ability to render custom Twig-based HTML error pages + * in non-debug mode, otherwise falls back to HtmlErrorRenderer. + * + * @author Yonel Ceruto + */ +class TwigErrorRenderer implements ErrorRendererInterface +{ + private $twig; + private $fallbackErrorRenderer; + private $debug; + + /** + * @param bool|callable $debug The debugging mode as a boolean or a callable that should return it + */ + public function __construct(Environment $twig, HtmlErrorRenderer $fallbackErrorRenderer = null, $debug = false) + { + if (!\is_bool($debug) && !\is_callable($debug)) { + throw new \TypeError(sprintf('Argument 3 passed to "%s()" must be a boolean or a callable, "%s" given.', __METHOD__, get_debug_type($debug))); + } + + $this->twig = $twig; + $this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer(); + $this->debug = $debug; + } + + /** + * {@inheritdoc} + */ + public function render(\Throwable $exception): FlattenException + { + $exception = $this->fallbackErrorRenderer->render($exception); + $debug = \is_bool($this->debug) ? $this->debug : ($this->debug)($exception); + + if ($debug || !$template = $this->findTemplate($exception->getStatusCode())) { + return $exception; + } + + return $exception->setAsString($this->twig->render($template, [ + 'exception' => $exception, + 'status_code' => $exception->getStatusCode(), + 'status_text' => $exception->getStatusText(), + ])); + } + + public static function isDebug(RequestStack $requestStack, bool $debug): \Closure + { + return static function () use ($requestStack, $debug): bool { + if (!$request = $requestStack->getCurrentRequest()) { + return $debug; + } + + return $debug && $request->attributes->getBoolean('showException', true); + }; + } + + private function findTemplate(int $statusCode): ?string + { + $template = sprintf('@Twig/Exception/error%s.html.twig', $statusCode); + if ($this->twig->getLoader()->exists($template)) { + return $template; + } + + $template = '@Twig/Exception/error.html.twig'; + if ($this->twig->getLoader()->exists($template)) { + return $template; + } + + return null; + } +} diff --git a/lib/symfony/twig-bridge/Extension/AssetExtension.php b/lib/symfony/twig-bridge/Extension/AssetExtension.php index cc2cdb268e..694821f7bf 100644 --- a/lib/symfony/twig-bridge/Extension/AssetExtension.php +++ b/lib/symfony/twig-bridge/Extension/AssetExtension.php @@ -20,7 +20,7 @@ use Twig\TwigFunction; * * @author Fabien Potencier */ -class AssetExtension extends AbstractExtension +final class AssetExtension extends AbstractExtension { private $packages; @@ -32,7 +32,7 @@ class AssetExtension extends AbstractExtension /** * {@inheritdoc} */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('asset', [$this, 'getAssetUrl']), @@ -45,37 +45,17 @@ class AssetExtension extends AbstractExtension * * If the package used to generate the path is an instance of * UrlPackage, you will always get a URL and not a path. - * - * @param string $path A public path - * @param string $packageName The name of the asset package to use - * - * @return string The public path of the asset */ - public function getAssetUrl($path, $packageName = null) + public function getAssetUrl(string $path, string $packageName = null): string { return $this->packages->getUrl($path, $packageName); } /** * Returns the version of an asset. - * - * @param string $path A public path - * @param string $packageName The name of the asset package to use - * - * @return string The asset version */ - public function getAssetVersion($path, $packageName = null) + public function getAssetVersion(string $path, string $packageName = null): string { return $this->packages->getVersion($path, $packageName); } - - /** - * Returns the name of the extension. - * - * @return string The extension name - */ - public function getName() - { - return 'asset'; - } } diff --git a/lib/symfony/twig-bridge/Extension/CodeExtension.php b/lib/symfony/twig-bridge/Extension/CodeExtension.php index dec4b61212..3bf8ccd294 100644 --- a/lib/symfony/twig-bridge/Extension/CodeExtension.php +++ b/lib/symfony/twig-bridge/Extension/CodeExtension.php @@ -20,28 +20,26 @@ use Twig\TwigFilter; * * @author Fabien Potencier */ -class CodeExtension extends AbstractExtension +final class CodeExtension extends AbstractExtension { private $fileLinkFormat; - private $rootDir; private $charset; + private $projectDir; /** * @param string|FileLinkFormatter $fileLinkFormat The format for links to source files - * @param string $rootDir The project root directory - * @param string $charset The charset */ - public function __construct($fileLinkFormat, $rootDir, $charset) + public function __construct($fileLinkFormat, string $projectDir, string $charset) { - $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); - $this->rootDir = str_replace('/', \DIRECTORY_SEPARATOR, \dirname($rootDir)).\DIRECTORY_SEPARATOR; + $this->fileLinkFormat = $fileLinkFormat ?: \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + $this->projectDir = str_replace('\\', '/', $projectDir).'/'; $this->charset = $charset; } /** * {@inheritdoc} */ - public function getFilters() + public function getFilters(): array { return [ new TwigFilter('abbr_class', [$this, 'abbrClass'], ['is_safe' => ['html']]), @@ -53,10 +51,11 @@ class CodeExtension extends AbstractExtension new TwigFilter('format_file_from_text', [$this, 'formatFileFromText'], ['is_safe' => ['html']]), new TwigFilter('format_log_message', [$this, 'formatLogMessage'], ['is_safe' => ['html']]), new TwigFilter('file_link', [$this, 'getFileLink']), + new TwigFilter('file_relative', [$this, 'getFileRelative']), ]; } - public function abbrClass($class) + public function abbrClass(string $class): string { $parts = explode('\\', $class); $short = array_pop($parts); @@ -64,10 +63,10 @@ class CodeExtension extends AbstractExtension return sprintf('%s', $class, $short); } - public function abbrMethod($method) + public function abbrMethod(string $method): string { - if (false !== strpos($method, '::')) { - list($class, $method) = explode('::', $method, 2); + if (str_contains($method, '::')) { + [$class, $method] = explode('::', $method, 2); $result = sprintf('%s::%s()', $this->abbrClass($class), $method); } elseif ('Closure' === $method) { $result = sprintf('%1$s', $method); @@ -80,12 +79,8 @@ class CodeExtension extends AbstractExtension /** * Formats an array as a string. - * - * @param array $args The argument array - * - * @return string */ - public function formatArgs($args) + public function formatArgs(array $args): string { $result = []; foreach ($args as $key => $item) { @@ -113,26 +108,16 @@ class CodeExtension extends AbstractExtension /** * Formats an array as a string. - * - * @param array $args The argument array - * - * @return string */ - public function formatArgsAsText($args) + public function formatArgsAsText(array $args): string { return strip_tags($this->formatArgs($args)); } /** * Returns an excerpt of a code file around the given line number. - * - * @param string $file A file path - * @param int $line The selected line number - * @param int $srcContext The number of displayed lines around or -1 for the whole file - * - * @return string An HTML string */ - public function fileExcerpt($file, $line, $srcContext = 3) + public function fileExcerpt(string $file, int $line, int $srcContext = 3): ?string { if (is_file($file) && is_readable($file)) { // highlight_file could throw warnings @@ -152,7 +137,7 @@ class CodeExtension extends AbstractExtension } for ($i = max($line - $srcContext, 1), $max = min($line + $srcContext, \count($content)); $i <= $max; ++$i) { - $lines[] = ''.self::fixCodeMarkup($content[$i - 1]).''; + $lines[] = ''.self::fixCodeMarkup($content[$i - 1]).''; } return '
                    '.implode("\n", $lines).'
                  '; @@ -163,23 +148,16 @@ class CodeExtension extends AbstractExtension /** * Formats a file path. - * - * @param string $file An absolute file path - * @param int $line The line number - * @param string $text Use this text for the link rather than the file path - * - * @return string */ - public function formatFile($file, $line, $text = null) + public function formatFile(string $file, int $line, string $text = null): string { $file = trim($file); if (null === $text) { - $text = str_replace('/', \DIRECTORY_SEPARATOR, $file); - if (0 === strpos($text, $this->rootDir)) { - $text = substr($text, \strlen($this->rootDir)); - $text = explode(\DIRECTORY_SEPARATOR, $text, 2); - $text = sprintf('%s%s', $this->rootDir, $text[0], isset($text[1]) ? \DIRECTORY_SEPARATOR.$text[1] : ''); + $text = $file; + if (null !== $rel = $this->getFileRelative($text)) { + $rel = explode('/', $rel, 2); + $text = sprintf('%s%s', $this->projectDir, $rel[0], '/'.($rel[1] ?? '')); } } @@ -197,12 +175,9 @@ class CodeExtension extends AbstractExtension /** * Returns the link for a given file/line pair. * - * @param string $file An absolute file path - * @param int $line The line number - * - * @return string|false A link or false + * @return string|false */ - public function getFileLink($file, $line) + public function getFileLink(string $file, int $line) { if ($fmt = $this->fileLinkFormat) { return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line); @@ -211,7 +186,18 @@ class CodeExtension extends AbstractExtension return false; } - public function formatFileFromText($text) + public function getFileRelative(string $file): ?string + { + $file = str_replace('\\', '/', $file); + + if (null !== $this->projectDir && str_starts_with($file, $this->projectDir)) { + return ltrim(substr($file, \strlen($this->projectDir)), '/'); + } + + return null; + } + + public function formatFileFromText(string $text): string { return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) { return 'in '.$this->formatFile($match[2], $match[3]); @@ -221,12 +207,12 @@ class CodeExtension extends AbstractExtension /** * @internal */ - public function formatLogMessage($message, array $context) + public function formatLogMessage(string $message, array $context): string { - if ($context && false !== strpos($message, '{')) { + if ($context && str_contains($message, '{')) { $replacements = []; foreach ($context as $key => $val) { - if (is_scalar($val)) { + if (\is_scalar($val)) { $replacements['{'.$key.'}'] = $val; } } @@ -239,15 +225,7 @@ class CodeExtension extends AbstractExtension return htmlspecialchars($message, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset); } - /** - * {@inheritdoc} - */ - public function getName() - { - return 'code'; - } - - protected static function fixCodeMarkup($line) + protected static function fixCodeMarkup(string $line): string { //
                  ending tag from previous line $opening = strpos($line, ' + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * @author Christian Flothmann + * @author Titouan Galopin + */ +final class CsrfExtension extends AbstractExtension +{ + /** + * {@inheritdoc} + */ + public function getFunctions(): array + { + return [ + new TwigFunction('csrf_token', [CsrfRuntime::class, 'getCsrfToken']), + ]; + } +} diff --git a/lib/symfony/twig-bridge/Extension/CsrfRuntime.php b/lib/symfony/twig-bridge/Extension/CsrfRuntime.php new file mode 100644 index 0000000000..c3d5da6470 --- /dev/null +++ b/lib/symfony/twig-bridge/Extension/CsrfRuntime.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\Bridge\Twig\Extension; + +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; + +/** + * @author Christian Flothmann + * @author Titouan Galopin + */ +final class CsrfRuntime +{ + private $csrfTokenManager; + + public function __construct(CsrfTokenManagerInterface $csrfTokenManager) + { + $this->csrfTokenManager = $csrfTokenManager; + } + + public function getCsrfToken(string $tokenId): string + { + return $this->csrfTokenManager->getToken($tokenId)->getValue(); + } +} diff --git a/lib/symfony/twig-bridge/Extension/DumpExtension.php b/lib/symfony/twig-bridge/Extension/DumpExtension.php index 2be1056234..46ad8eaf67 100644 --- a/lib/symfony/twig-bridge/Extension/DumpExtension.php +++ b/lib/symfony/twig-bridge/Extension/DumpExtension.php @@ -24,7 +24,7 @@ use Twig\TwigFunction; * * @author Nicolas Grekas */ -class DumpExtension extends AbstractExtension +final class DumpExtension extends AbstractExtension { private $cloner; private $dumper; @@ -35,24 +35,25 @@ class DumpExtension extends AbstractExtension $this->dumper = $dumper; } - public function getFunctions() + /** + * {@inheritdoc} + */ + public function getFunctions(): array { return [ new TwigFunction('dump', [$this, 'dump'], ['is_safe' => ['html'], 'needs_context' => true, 'needs_environment' => true]), ]; } - public function getTokenParsers() + /** + * {@inheritdoc} + */ + public function getTokenParsers(): array { return [new DumpTokenParser()]; } - public function getName() - { - return 'dump'; - } - - public function dump(Environment $env, $context) + public function dump(Environment $env, array $context): ?string { if (!$env->isDebug()) { return null; @@ -72,8 +73,8 @@ class DumpExtension extends AbstractExtension unset($vars[0], $vars[1]); } - $dump = fopen('php://memory', 'r+b'); - $this->dumper = $this->dumper ?: new HtmlDumper(); + $dump = fopen('php://memory', 'r+'); + $this->dumper = $this->dumper ?? new HtmlDumper(); $this->dumper->setCharset($env->getCharset()); foreach ($vars as $value) { diff --git a/lib/symfony/twig-bridge/Extension/ExpressionExtension.php b/lib/symfony/twig-bridge/Extension/ExpressionExtension.php index 21f6be4d6e..8d2a35c99f 100644 --- a/lib/symfony/twig-bridge/Extension/ExpressionExtension.php +++ b/lib/symfony/twig-bridge/Extension/ExpressionExtension.php @@ -20,30 +20,20 @@ use Twig\TwigFunction; * * @author Fabien Potencier */ -class ExpressionExtension extends AbstractExtension +final class ExpressionExtension extends AbstractExtension { /** * {@inheritdoc} */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('expression', [$this, 'createExpression']), ]; } - public function createExpression($expression) + public function createExpression(string $expression): Expression { return new Expression($expression); } - - /** - * Returns the name of the extension. - * - * @return string The extension name - */ - public function getName() - { - return 'expression'; - } } diff --git a/lib/symfony/twig-bridge/Extension/FormExtension.php b/lib/symfony/twig-bridge/Extension/FormExtension.php index 84626c22c5..7f0b1ed597 100644 --- a/lib/symfony/twig-bridge/Extension/FormExtension.php +++ b/lib/symfony/twig-bridge/Extension/FormExtension.php @@ -11,12 +11,12 @@ namespace Symfony\Bridge\Twig\Extension; -use Symfony\Bridge\Twig\Form\TwigRendererInterface; use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView; use Symfony\Component\Form\ChoiceList\View\ChoiceView; +use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormView; -use Twig\Environment; +use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; use Twig\TwigFunction; @@ -28,41 +28,19 @@ use Twig\TwigTest; * @author Fabien Potencier * @author Bernhard Schussek */ -class FormExtension extends AbstractExtension implements InitRuntimeInterface +final class FormExtension extends AbstractExtension { - /** - * @deprecated since version 3.2, to be removed in 4.0 alongside with magic methods below - */ - private $renderer; + private $translator; - public function __construct($renderer = null) + public function __construct(TranslatorInterface $translator = null) { - if ($renderer instanceof TwigRendererInterface) { - @trigger_error(sprintf('Passing a Twig Form Renderer to the "%s" constructor is deprecated since Symfony 3.2 and won\'t be possible in 4.0. Pass the Twig\Environment to the TwigRendererEngine constructor instead.', static::class), \E_USER_DEPRECATED); - } elseif (null !== $renderer && !(\is_array($renderer) && isset($renderer[0], $renderer[1]) && $renderer[0] instanceof ContainerInterface)) { - throw new \InvalidArgumentException(sprintf('Passing any arguments to the constructor of "%s" is reserved for internal use.', __CLASS__)); - } - $this->renderer = $renderer; - } - - /** - * {@inheritdoc} - * - * To be removed in 4.0 - */ - public function initRuntime(Environment $environment) - { - if ($this->renderer instanceof TwigRendererInterface) { - $this->renderer->setEnvironment($environment); - } elseif (\is_array($this->renderer)) { - $this->renderer[2] = $environment; - } + $this->translator = $translator; } /** * {@inheritdoc} */ - public function getTokenParsers() + public function getTokenParsers(): array { return [ // {% form_theme form "SomeBundle::widgets.twig" %} @@ -73,25 +51,33 @@ class FormExtension extends AbstractExtension implements InitRuntimeInterface /** * {@inheritdoc} */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('form_widget', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), new TwigFunction('form_errors', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), new TwigFunction('form_label', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), + new TwigFunction('form_help', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), new TwigFunction('form_row', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), new TwigFunction('form_rest', null, ['node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => ['html']]), new TwigFunction('form', null, ['node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => ['html']]), new TwigFunction('form_start', null, ['node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => ['html']]), new TwigFunction('form_end', null, ['node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => ['html']]), new TwigFunction('csrf_token', ['Symfony\Component\Form\FormRenderer', 'renderCsrfToken']), + new TwigFunction('form_parent', 'Symfony\Bridge\Twig\Extension\twig_get_form_parent'), + new TwigFunction('field_name', [$this, 'getFieldName']), + new TwigFunction('field_value', [$this, 'getFieldValue']), + new TwigFunction('field_label', [$this, 'getFieldLabel']), + new TwigFunction('field_help', [$this, 'getFieldHelp']), + new TwigFunction('field_errors', [$this, 'getFieldErrors']), + new TwigFunction('field_choices', [$this, 'getFieldChoices']), ]; } /** * {@inheritdoc} */ - public function getFilters() + public function getFilters(): array { return [ new TwigFilter('humanize', ['Symfony\Component\Form\FormRenderer', 'humanize']), @@ -102,7 +88,7 @@ class FormExtension extends AbstractExtension implements InitRuntimeInterface /** * {@inheritdoc} */ - public function getTests() + public function getTests(): array { return [ new TwigTest('selectedchoice', 'Symfony\Bridge\Twig\Extension\twig_is_selected_choice'), @@ -110,68 +96,91 @@ class FormExtension extends AbstractExtension implements InitRuntimeInterface ]; } - /** - * @internal - */ - public function __get($name) + public function getFieldName(FormView $view): string { - if ('renderer' === $name) { - @trigger_error(sprintf('Using the "%s::$renderer" property is deprecated since Symfony 3.2 as it will be removed in 4.0.', __CLASS__), \E_USER_DEPRECATED); + $view->setRendered(); - if (\is_array($this->renderer)) { - $renderer = $this->renderer[0]->get($this->renderer[1]); - if (isset($this->renderer[2]) && $renderer instanceof TwigRendererInterface) { - $renderer->setEnvironment($this->renderer[2]); - } - $this->renderer = $renderer; + return $view->vars['full_name']; + } + + /** + * @return string|array + */ + public function getFieldValue(FormView $view) + { + return $view->vars['value']; + } + + public function getFieldLabel(FormView $view): ?string + { + if (false === $label = $view->vars['label']) { + return null; + } + + if (!$label && $labelFormat = $view->vars['label_format']) { + $label = str_replace(['%id%', '%name%'], [$view->vars['id'], $view->vars['name']], $labelFormat); + } elseif (!$label) { + $label = ucfirst(strtolower(trim(preg_replace(['/([A-Z])/', '/[_\s]+/'], ['_$1', ' '], $view->vars['name'])))); + } + + return $this->createFieldTranslation( + $label, + $view->vars['label_translation_parameters'] ?: [], + $view->vars['translation_domain'] + ); + } + + public function getFieldHelp(FormView $view): ?string + { + return $this->createFieldTranslation( + $view->vars['help'], + $view->vars['help_translation_parameters'] ?: [], + $view->vars['translation_domain'] + ); + } + + /** + * @return string[] + */ + public function getFieldErrors(FormView $view): iterable + { + /** @var FormError $error */ + foreach ($view->vars['errors'] as $error) { + yield $error->getMessage(); + } + } + + /** + * @return string[]|string[][] + */ + public function getFieldChoices(FormView $view): iterable + { + yield from $this->createFieldChoicesList($view->vars['choices'], $view->vars['choice_translation_domain']); + } + + private function createFieldChoicesList(iterable $choices, $translationDomain): iterable + { + foreach ($choices as $choice) { + $translatableLabel = $this->createFieldTranslation($choice->label, [], $translationDomain); + + if ($choice instanceof ChoiceGroupView) { + yield $translatableLabel => $this->createFieldChoicesList($choice, $translationDomain); + + continue; } - } - return $this->$name; + /* @var ChoiceView $choice */ + yield $translatableLabel => $choice->value; + } } - /** - * @internal - */ - public function __set($name, $value) + private function createFieldTranslation(?string $value, array $parameters, $domain): ?string { - if ('renderer' === $name) { - @trigger_error(sprintf('Using the "%s::$renderer" property is deprecated since Symfony 3.2 as it will be removed in 4.0.', __CLASS__), \E_USER_DEPRECATED); + if (!$this->translator || !$value || false === $domain) { + return $value; } - $this->$name = $value; - } - - /** - * @internal - */ - public function __isset($name) - { - if ('renderer' === $name) { - @trigger_error(sprintf('Using the "%s::$renderer" property is deprecated since Symfony 3.2 as it will be removed in 4.0.', __CLASS__), \E_USER_DEPRECATED); - } - - return isset($this->$name); - } - - /** - * @internal - */ - public function __unset($name) - { - if ('renderer' === $name) { - @trigger_error(sprintf('Using the "%s::$renderer" property is deprecated since Symfony 3.2 as it will be removed in 4.0.', __CLASS__), \E_USER_DEPRECATED); - } - - unset($this->$name); - } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'form'; + return $this->translator->trans($value, $parameters, $domain); } } @@ -182,11 +191,9 @@ class FormExtension extends AbstractExtension implements InitRuntimeInterface * * @param string|array $selectedValue The selected value to compare * - * @return bool Whether the choice is selected - * * @see ChoiceView::isSelected() */ -function twig_is_selected_choice(ChoiceView $choice, $selectedValue) +function twig_is_selected_choice(ChoiceView $choice, $selectedValue): bool { if (\is_array($selectedValue)) { return \in_array($choice->value, $selectedValue, true); @@ -198,7 +205,15 @@ function twig_is_selected_choice(ChoiceView $choice, $selectedValue) /** * @internal */ -function twig_is_root_form(FormView $formView) +function twig_is_root_form(FormView $formView): bool { return null === $formView->parent; } + +/** + * @internal + */ +function twig_get_form_parent(FormView $formView): ?FormView +{ + return $formView->parent; +} diff --git a/lib/symfony/twig-bridge/Extension/HttpFoundationExtension.php b/lib/symfony/twig-bridge/Extension/HttpFoundationExtension.php index 82b9a92f75..a9ee05c4d0 100644 --- a/lib/symfony/twig-bridge/Extension/HttpFoundationExtension.php +++ b/lib/symfony/twig-bridge/Extension/HttpFoundationExtension.php @@ -12,8 +12,7 @@ namespace Symfony\Bridge\Twig\Extension; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Routing\RequestContext; +use Symfony\Component\HttpFoundation\UrlHelper; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; @@ -22,21 +21,19 @@ use Twig\TwigFunction; * * @author Fabien Potencier */ -class HttpFoundationExtension extends AbstractExtension +final class HttpFoundationExtension extends AbstractExtension { - private $requestStack; - private $requestContext; + private $urlHelper; - public function __construct(RequestStack $requestStack, RequestContext $requestContext = null) + public function __construct(UrlHelper $urlHelper) { - $this->requestStack = $requestStack; - $this->requestContext = $requestContext; + $this->urlHelper = $urlHelper; } /** * {@inheritdoc} */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('absolute_url', [$this, 'generateAbsoluteUrl']), @@ -49,63 +46,11 @@ class HttpFoundationExtension extends AbstractExtension * * This method returns the path unchanged if no request is available. * - * @param string $path The path - * - * @return string The absolute URL - * * @see Request::getUriForPath() */ - public function generateAbsoluteUrl($path) + public function generateAbsoluteUrl(string $path): string { - if (false !== strpos($path, '://') || '//' === substr($path, 0, 2)) { - return $path; - } - - if (!$request = $this->requestStack->getMasterRequest()) { - if (null !== $this->requestContext && '' !== $host = $this->requestContext->getHost()) { - $scheme = $this->requestContext->getScheme(); - $port = ''; - - if ('http' === $scheme && 80 != $this->requestContext->getHttpPort()) { - $port = ':'.$this->requestContext->getHttpPort(); - } elseif ('https' === $scheme && 443 != $this->requestContext->getHttpsPort()) { - $port = ':'.$this->requestContext->getHttpsPort(); - } - - if ('#' === $path[0]) { - $queryString = $this->requestContext->getQueryString(); - $path = $this->requestContext->getPathInfo().($queryString ? '?'.$queryString : '').$path; - } elseif ('?' === $path[0]) { - $path = $this->requestContext->getPathInfo().$path; - } - - if ('/' !== $path[0]) { - $path = rtrim($this->requestContext->getBaseUrl(), '/').'/'.$path; - } - - return $scheme.'://'.$host.$port.$path; - } - - return $path; - } - - if ('#' === $path[0]) { - $path = $request->getRequestUri().$path; - } elseif ('?' === $path[0]) { - $path = $request->getPathInfo().$path; - } - - if (!$path || '/' !== $path[0]) { - $prefix = $request->getPathInfo(); - $last = \strlen($prefix) - 1; - if ($last !== $pos = strrpos($prefix, '/')) { - $prefix = substr($prefix, 0, $pos).'/'; - } - - return $request->getUriForPath($prefix.$path); - } - - return $request->getSchemeAndHttpHost().$path; + return $this->urlHelper->getAbsoluteUrl($path); } /** @@ -113,32 +58,10 @@ class HttpFoundationExtension extends AbstractExtension * * This method returns the path unchanged if no request is available. * - * @param string $path The path - * - * @return string The relative path - * * @see Request::getRelativeUriForPath() */ - public function generateRelativePath($path) + public function generateRelativePath(string $path): string { - if (false !== strpos($path, '://') || '//' === substr($path, 0, 2)) { - return $path; - } - - if (!$request = $this->requestStack->getMasterRequest()) { - return $path; - } - - return $request->getRelativeUriForPath($path); - } - - /** - * Returns the name of the extension. - * - * @return string The extension name - */ - public function getName() - { - return 'request'; + return $this->urlHelper->getRelativePath($path); } } diff --git a/lib/symfony/twig-bridge/Extension/HttpKernelExtension.php b/lib/symfony/twig-bridge/Extension/HttpKernelExtension.php index f8b93ada15..1af9ddb23c 100644 --- a/lib/symfony/twig-bridge/Extension/HttpKernelExtension.php +++ b/lib/symfony/twig-bridge/Extension/HttpKernelExtension.php @@ -20,27 +20,23 @@ use Twig\TwigFunction; * * @author Fabien Potencier */ -class HttpKernelExtension extends AbstractExtension +final class HttpKernelExtension extends AbstractExtension { - public function getFunctions() + /** + * {@inheritdoc} + */ + public function getFunctions(): array { return [ new TwigFunction('render', [HttpKernelRuntime::class, 'renderFragment'], ['is_safe' => ['html']]), new TwigFunction('render_*', [HttpKernelRuntime::class, 'renderFragmentStrategy'], ['is_safe' => ['html']]), + new TwigFunction('fragment_uri', [HttpKernelRuntime::class, 'generateFragmentUri']), new TwigFunction('controller', static::class.'::controller'), ]; } - public static function controller($controller, $attributes = [], $query = []) + public static function controller(string $controller, array $attributes = [], array $query = []): ControllerReference { return new ControllerReference($controller, $attributes, $query); } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'http_kernel'; - } } diff --git a/lib/symfony/twig-bridge/Extension/HttpKernelRuntime.php b/lib/symfony/twig-bridge/Extension/HttpKernelRuntime.php index fcd7c24416..ab83054a9f 100644 --- a/lib/symfony/twig-bridge/Extension/HttpKernelRuntime.php +++ b/lib/symfony/twig-bridge/Extension/HttpKernelRuntime.php @@ -13,34 +13,34 @@ namespace Symfony\Bridge\Twig\Extension; use Symfony\Component\HttpKernel\Controller\ControllerReference; use Symfony\Component\HttpKernel\Fragment\FragmentHandler; +use Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface; /** * Provides integration with the HttpKernel component. * * @author Fabien Potencier */ -class HttpKernelRuntime +final class HttpKernelRuntime { private $handler; + private $fragmentUriGenerator; - public function __construct(FragmentHandler $handler) + public function __construct(FragmentHandler $handler, FragmentUriGeneratorInterface $fragmentUriGenerator = null) { $this->handler = $handler; + $this->fragmentUriGenerator = $fragmentUriGenerator; } /** * Renders a fragment. * - * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance - * @param array $options An array of options - * - * @return string The fragment content + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance * * @see FragmentHandler::render() */ - public function renderFragment($uri, $options = []) + public function renderFragment($uri, array $options = []): string { - $strategy = isset($options['strategy']) ? $options['strategy'] : 'inline'; + $strategy = $options['strategy'] ?? 'inline'; unset($options['strategy']); return $this->handler->render($uri, $strategy, $options); @@ -49,16 +49,21 @@ class HttpKernelRuntime /** * Renders a fragment. * - * @param string $strategy A strategy name - * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance - * @param array $options An array of options - * - * @return string The fragment content + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance * * @see FragmentHandler::render() */ - public function renderFragmentStrategy($strategy, $uri, $options = []) + public function renderFragmentStrategy(string $strategy, $uri, array $options = []): string { return $this->handler->render($uri, $strategy, $options); } + + public function generateFragmentUri(ControllerReference $controller, bool $absolute = false, bool $strict = true, bool $sign = true): string + { + if (null === $this->fragmentUriGenerator) { + throw new \LogicException(sprintf('An instance of "%s" must be provided to use "%s()".', FragmentUriGeneratorInterface::class, __METHOD__)); + } + + return $this->fragmentUriGenerator->generate($controller, null, $absolute, $strict, $sign); + } } diff --git a/lib/symfony/twig-bridge/Extension/InitRuntimeInterface.php b/lib/symfony/twig-bridge/Extension/InitRuntimeInterface.php deleted file mode 100644 index 5ba5e55702..0000000000 --- a/lib/symfony/twig-bridge/Extension/InitRuntimeInterface.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\Twig\Extension; - -use Twig\Extension\InitRuntimeInterface as TwigInitRuntimeInterface; - -/** - * @deprecated to be removed in 4.x - * - * @internal to be removed in 4.x - */ -interface InitRuntimeInterface extends TwigInitRuntimeInterface -{ -} diff --git a/lib/symfony/twig-bridge/Extension/LogoutUrlExtension.php b/lib/symfony/twig-bridge/Extension/LogoutUrlExtension.php index e8bc6190cd..071b9ff247 100644 --- a/lib/symfony/twig-bridge/Extension/LogoutUrlExtension.php +++ b/lib/symfony/twig-bridge/Extension/LogoutUrlExtension.php @@ -20,7 +20,7 @@ use Twig\TwigFunction; * * @author Jeremy Mikola */ -class LogoutUrlExtension extends AbstractExtension +final class LogoutUrlExtension extends AbstractExtension { private $generator; @@ -32,7 +32,7 @@ class LogoutUrlExtension extends AbstractExtension /** * {@inheritdoc} */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('logout_url', [$this, 'getLogoutUrl']), @@ -44,10 +44,8 @@ class LogoutUrlExtension extends AbstractExtension * Generates the relative logout URL for the firewall. * * @param string|null $key The firewall key or null to use the current firewall key - * - * @return string The relative logout URL */ - public function getLogoutPath($key = null) + public function getLogoutPath(string $key = null): string { return $this->generator->getLogoutPath($key); } @@ -56,19 +54,9 @@ class LogoutUrlExtension extends AbstractExtension * Generates the absolute logout URL for the firewall. * * @param string|null $key The firewall key or null to use the current firewall key - * - * @return string The absolute logout URL */ - public function getLogoutUrl($key = null) + public function getLogoutUrl(string $key = null): string { return $this->generator->getLogoutUrl($key); } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'logout_url'; - } } diff --git a/lib/symfony/twig-bridge/Extension/ProfilerExtension.php b/lib/symfony/twig-bridge/Extension/ProfilerExtension.php index 21214f8119..51d6eba2da 100644 --- a/lib/symfony/twig-bridge/Extension/ProfilerExtension.php +++ b/lib/symfony/twig-bridge/Extension/ProfilerExtension.php @@ -12,15 +12,20 @@ namespace Symfony\Bridge\Twig\Extension; use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\Stopwatch\StopwatchEvent; use Twig\Extension\ProfilerExtension as BaseProfilerExtension; use Twig\Profiler\Profile; /** * @author Fabien Potencier */ -class ProfilerExtension extends BaseProfilerExtension +final class ProfilerExtension extends BaseProfilerExtension { private $stopwatch; + + /** + * @var \SplObjectStorage + */ private $events; public function __construct(Profile $profile, Stopwatch $stopwatch = null) @@ -31,7 +36,7 @@ class ProfilerExtension extends BaseProfilerExtension $this->events = new \SplObjectStorage(); } - public function enter(Profile $profile) + public function enter(Profile $profile): void { if ($this->stopwatch && $profile->isTemplate()) { $this->events[$profile] = $this->stopwatch->start($profile->getName(), 'template'); @@ -40,7 +45,7 @@ class ProfilerExtension extends BaseProfilerExtension parent::enter($profile); } - public function leave(Profile $profile) + public function leave(Profile $profile): void { parent::leave($profile); @@ -49,12 +54,4 @@ class ProfilerExtension extends BaseProfilerExtension unset($this->events[$profile]); } } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'native_profiler'; - } } diff --git a/lib/symfony/twig-bridge/Extension/RoutingExtension.php b/lib/symfony/twig-bridge/Extension/RoutingExtension.php index 936c2d9985..800c22f6d4 100644 --- a/lib/symfony/twig-bridge/Extension/RoutingExtension.php +++ b/lib/symfony/twig-bridge/Extension/RoutingExtension.php @@ -23,7 +23,7 @@ use Twig\TwigFunction; * * @author Fabien Potencier */ -class RoutingExtension extends AbstractExtension +final class RoutingExtension extends AbstractExtension { private $generator; @@ -35,7 +35,7 @@ class RoutingExtension extends AbstractExtension /** * {@inheritdoc} */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('url', [$this, 'getUrl'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), @@ -43,26 +43,12 @@ class RoutingExtension extends AbstractExtension ]; } - /** - * @param string $name - * @param array $parameters - * @param bool $relative - * - * @return string - */ - public function getPath($name, $parameters = [], $relative = false) + public function getPath(string $name, array $parameters = [], bool $relative = false): string { return $this->generator->generate($name, $parameters, $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH); } - /** - * @param string $name - * @param array $parameters - * @param bool $schemeRelative - * - * @return string - */ - public function getUrl($name, $parameters = [], $schemeRelative = false) + public function getUrl(string $name, array $parameters = [], bool $schemeRelative = false): string { return $this->generator->generate($name, $parameters, $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL); } @@ -88,10 +74,8 @@ class RoutingExtension extends AbstractExtension * @param Node $argsNode The arguments of the path/url function * * @return array An array with the contexts the URL is safe - * - * @final since version 3.4, type-hint to be changed to "\Twig\Node\Node" in 4.0 */ - public function isUrlGenerationSafe(\Twig_Node $argsNode) + public function isUrlGenerationSafe(Node $argsNode): array { // support named arguments $paramsNode = $argsNode->hasNode('parameters') ? $argsNode->getNode('parameters') : ( @@ -106,12 +90,4 @@ class RoutingExtension extends AbstractExtension return []; } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'routing'; - } } diff --git a/lib/symfony/twig-bridge/Extension/SecurityExtension.php b/lib/symfony/twig-bridge/Extension/SecurityExtension.php index 439c31aad3..0e58fc0ec6 100644 --- a/lib/symfony/twig-bridge/Extension/SecurityExtension.php +++ b/lib/symfony/twig-bridge/Extension/SecurityExtension.php @@ -14,6 +14,7 @@ namespace Symfony\Bridge\Twig\Extension; use Symfony\Component\Security\Acl\Voter\FieldVote; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; +use Symfony\Component\Security\Http\Impersonate\ImpersonateUrlGenerator; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; @@ -22,16 +23,22 @@ use Twig\TwigFunction; * * @author Fabien Potencier */ -class SecurityExtension extends AbstractExtension +final class SecurityExtension extends AbstractExtension { private $securityChecker; - public function __construct(AuthorizationCheckerInterface $securityChecker = null) + private $impersonateUrlGenerator; + + public function __construct(AuthorizationCheckerInterface $securityChecker = null, ImpersonateUrlGenerator $impersonateUrlGenerator = null) { $this->securityChecker = $securityChecker; + $this->impersonateUrlGenerator = $impersonateUrlGenerator; } - public function isGranted($role, $object = null, $field = null) + /** + * @param mixed $object + */ + public function isGranted($role, $object = null, string $field = null): bool { if (null === $this->securityChecker) { return false; @@ -48,21 +55,33 @@ class SecurityExtension extends AbstractExtension } } - /** - * {@inheritdoc} - */ - public function getFunctions() + public function getImpersonateExitUrl(string $exitTo = null): string { - return [ - new TwigFunction('is_granted', [$this, 'isGranted']), - ]; + if (null === $this->impersonateUrlGenerator) { + return ''; + } + + return $this->impersonateUrlGenerator->generateExitUrl($exitTo); + } + + public function getImpersonateExitPath(string $exitTo = null): string + { + if (null === $this->impersonateUrlGenerator) { + return ''; + } + + return $this->impersonateUrlGenerator->generateExitPath($exitTo); } /** * {@inheritdoc} */ - public function getName() + public function getFunctions(): array { - return 'security'; + return [ + new TwigFunction('is_granted', [$this, 'isGranted']), + new TwigFunction('impersonation_exit_url', [$this, 'getImpersonateExitUrl']), + new TwigFunction('impersonation_exit_path', [$this, 'getImpersonateExitPath']), + ]; } } diff --git a/lib/symfony/twig-bridge/Extension/SerializerExtension.php b/lib/symfony/twig-bridge/Extension/SerializerExtension.php new file mode 100644 index 0000000000..f38571efaa --- /dev/null +++ b/lib/symfony/twig-bridge/Extension/SerializerExtension.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\Bridge\Twig\Extension; + +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; + +/** + * @author Jesse Rushlow + */ +final class SerializerExtension extends AbstractExtension +{ + public function getFilters(): array + { + return [ + new TwigFilter('serialize', [SerializerRuntime::class, 'serialize']), + ]; + } +} diff --git a/lib/symfony/twig-bridge/Extension/SerializerRuntime.php b/lib/symfony/twig-bridge/Extension/SerializerRuntime.php new file mode 100644 index 0000000000..3a4087aa79 --- /dev/null +++ b/lib/symfony/twig-bridge/Extension/SerializerRuntime.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\Bridge\Twig\Extension; + +use Symfony\Component\Serializer\SerializerInterface; +use Twig\Extension\RuntimeExtensionInterface; + +/** + * @author Jesse Rushlow + */ +final class SerializerRuntime implements RuntimeExtensionInterface +{ + private $serializer; + + public function __construct(SerializerInterface $serializer) + { + $this->serializer = $serializer; + } + + public function serialize($data, string $format = 'json', array $context = []): string + { + return $this->serializer->serialize($data, $format, $context); + } +} diff --git a/lib/symfony/twig-bridge/Extension/StopwatchExtension.php b/lib/symfony/twig-bridge/Extension/StopwatchExtension.php index 45b65d4e64..80a25a949b 100644 --- a/lib/symfony/twig-bridge/Extension/StopwatchExtension.php +++ b/lib/symfony/twig-bridge/Extension/StopwatchExtension.php @@ -14,29 +14,33 @@ namespace Symfony\Bridge\Twig\Extension; use Symfony\Bridge\Twig\TokenParser\StopwatchTokenParser; use Symfony\Component\Stopwatch\Stopwatch; use Twig\Extension\AbstractExtension; +use Twig\TokenParser\TokenParserInterface; /** * Twig extension for the stopwatch helper. * * @author Wouter J */ -class StopwatchExtension extends AbstractExtension +final class StopwatchExtension extends AbstractExtension { private $stopwatch; private $enabled; - public function __construct(Stopwatch $stopwatch = null, $enabled = true) + public function __construct(Stopwatch $stopwatch = null, bool $enabled = true) { $this->stopwatch = $stopwatch; $this->enabled = $enabled; } - public function getStopwatch() + public function getStopwatch(): Stopwatch { return $this->stopwatch; } - public function getTokenParsers() + /** + * @return TokenParserInterface[] + */ + public function getTokenParsers(): array { return [ /* @@ -47,9 +51,4 @@ class StopwatchExtension extends AbstractExtension new StopwatchTokenParser(null !== $this->stopwatch && $this->enabled), ]; } - - public function getName() - { - return 'stopwatch'; - } } diff --git a/lib/symfony/twig-bridge/Extension/TranslationExtension.php b/lib/symfony/twig-bridge/Extension/TranslationExtension.php index 4538f77198..c2797d837a 100644 --- a/lib/symfony/twig-bridge/Extension/TranslationExtension.php +++ b/lib/symfony/twig-bridge/Extension/TranslationExtension.php @@ -13,63 +13,80 @@ namespace Symfony\Bridge\Twig\Extension; use Symfony\Bridge\Twig\NodeVisitor\TranslationDefaultDomainNodeVisitor; use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor; -use Symfony\Bridge\Twig\TokenParser\TransChoiceTokenParser; use Symfony\Bridge\Twig\TokenParser\TransDefaultDomainTokenParser; use Symfony\Bridge\Twig\TokenParser\TransTokenParser; -use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Translation\TranslatableMessage; +use Symfony\Contracts\Translation\TranslatableInterface; +use Symfony\Contracts\Translation\TranslatorInterface; +use Symfony\Contracts\Translation\TranslatorTrait; use Twig\Extension\AbstractExtension; -use Twig\NodeVisitor\NodeVisitorInterface; -use Twig\TokenParser\AbstractTokenParser; use Twig\TwigFilter; +use Twig\TwigFunction; + +// Help opcache.preload discover always-needed symbols +class_exists(TranslatorInterface::class); +class_exists(TranslatorTrait::class); /** * Provides integration of the Translation component with Twig. * * @author Fabien Potencier */ -class TranslationExtension extends AbstractExtension +final class TranslationExtension extends AbstractExtension { private $translator; private $translationNodeVisitor; - public function __construct(TranslatorInterface $translator = null, NodeVisitorInterface $translationNodeVisitor = null) + public function __construct(TranslatorInterface $translator = null, TranslationNodeVisitor $translationNodeVisitor = null) { $this->translator = $translator; $this->translationNodeVisitor = $translationNodeVisitor; } - public function getTranslator() + public function getTranslator(): TranslatorInterface { + if (null === $this->translator) { + if (!interface_exists(TranslatorInterface::class)) { + throw new \LogicException(sprintf('You cannot use the "%s" if the Translation Contracts are not available. Try running "composer require symfony/translation".', __CLASS__)); + } + + $this->translator = new class() implements TranslatorInterface { + use TranslatorTrait; + }; + } + return $this->translator; } /** * {@inheritdoc} */ - public function getFilters() + public function getFunctions(): array { return [ - new TwigFilter('trans', [$this, 'trans']), - new TwigFilter('transchoice', [$this, 'transchoice']), + new TwigFunction('t', [$this, 'createTranslatable']), ]; } /** - * Returns the token parser instance to add to the existing list. - * - * @return AbstractTokenParser[] + * {@inheritdoc} */ - public function getTokenParsers() + public function getFilters(): array + { + return [ + new TwigFilter('trans', [$this, 'trans']), + ]; + } + + /** + * {@inheritdoc} + */ + public function getTokenParsers(): array { return [ // {% trans %}Symfony is great!{% endtrans %} new TransTokenParser(), - // {% transchoice count %} - // {0} There is no apples|{1} There is one apple|]1,Inf] There is {{ count }} apples - // {% endtranschoice %} - new TransChoiceTokenParser(), - // {% trans_default_domain "foobar" %} new TransDefaultDomainTokenParser(), ]; @@ -78,39 +95,51 @@ class TranslationExtension extends AbstractExtension /** * {@inheritdoc} */ - public function getNodeVisitors() + public function getNodeVisitors(): array { return [$this->getTranslationNodeVisitor(), new TranslationDefaultDomainNodeVisitor()]; } - public function getTranslationNodeVisitor() + public function getTranslationNodeVisitor(): TranslationNodeVisitor { return $this->translationNodeVisitor ?: $this->translationNodeVisitor = new TranslationNodeVisitor(); } - public function trans($message, array $arguments = [], $domain = null, $locale = null) - { - if (null === $this->translator) { - return strtr($message, $arguments); - } - - return $this->translator->trans($message, $arguments, $domain, $locale); - } - - public function transchoice($message, $count, array $arguments = [], $domain = null, $locale = null) - { - if (null === $this->translator) { - return strtr($message, $arguments); - } - - return $this->translator->transChoice($message, $count, array_merge(['%count%' => $count], $arguments), $domain, $locale); - } - /** - * {@inheritdoc} + * @param string|\Stringable|TranslatableInterface|null $message + * @param array|string $arguments Can be the locale as a string when $message is a TranslatableInterface */ - public function getName() + public function trans($message, $arguments = [], string $domain = null, string $locale = null, int $count = null): string { - return 'translator'; + if ($message instanceof TranslatableInterface) { + if ([] !== $arguments && !\is_string($arguments)) { + throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be a locale passed as a string when the message is a "%s", "%s" given.', __METHOD__, TranslatableInterface::class, get_debug_type($arguments))); + } + + return $message->trans($this->getTranslator(), $locale ?? (\is_string($arguments) ? $arguments : null)); + } + + if (!\is_array($arguments)) { + throw new \TypeError(sprintf('Unless the message is a "%s", argument 2 passed to "%s()" must be an array of parameters, "%s" given.', TranslatableInterface::class, __METHOD__, get_debug_type($arguments))); + } + + if ('' === $message = (string) $message) { + return ''; + } + + if (null !== $count) { + $arguments['%count%'] = $count; + } + + return $this->getTranslator()->trans($message, $arguments, $domain, $locale); + } + + public function createTranslatable(string $message, array $parameters = [], string $domain = null): TranslatableMessage + { + if (!class_exists(TranslatableMessage::class)) { + throw new \LogicException(sprintf('You cannot use the "%s" as the Translation Component is not installed. Try running "composer require symfony/translation".', __CLASS__)); + } + + return new TranslatableMessage($message, $parameters, $domain); } } diff --git a/lib/symfony/twig-bridge/Extension/WebLinkExtension.php b/lib/symfony/twig-bridge/Extension/WebLinkExtension.php index 0ca519ee72..652a75762c 100644 --- a/lib/symfony/twig-bridge/Extension/WebLinkExtension.php +++ b/lib/symfony/twig-bridge/Extension/WebLinkExtension.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Twig\Extension; -use Fig\Link\GenericLinkProvider; -use Fig\Link\Link; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\WebLink\GenericLinkProvider; +use Symfony\Component\WebLink\Link; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; @@ -22,7 +22,7 @@ use Twig\TwigFunction; * * @author Kévin Dunglas */ -class WebLinkExtension extends AbstractExtension +final class WebLinkExtension extends AbstractExtension { private $requestStack; @@ -34,7 +34,7 @@ class WebLinkExtension extends AbstractExtension /** * {@inheritdoc} */ - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('link', [$this, 'link']), @@ -49,15 +49,14 @@ class WebLinkExtension extends AbstractExtension /** * Adds a "Link" HTTP header. * - * @param string $uri The relation URI * @param string $rel The relation type (e.g. "preload", "prefetch", "prerender" or "dns-prefetch") * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") * * @return string The relation URI */ - public function link($uri, $rel, array $attributes = []) + public function link(string $uri, string $rel, array $attributes = []): string { - if (!$request = $this->requestStack->getMasterRequest()) { + if (!$request = $this->requestStack->getMainRequest()) { return $uri; } @@ -75,12 +74,11 @@ class WebLinkExtension extends AbstractExtension /** * Preloads a resource. * - * @param string $uri A public path - * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['crossorigin' => 'use-credentials']") + * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['crossorigin' => 'use-credentials']") * * @return string The path of the asset */ - public function preload($uri, array $attributes = []) + public function preload(string $uri, array $attributes = []): string { return $this->link($uri, 'preload', $attributes); } @@ -88,12 +86,11 @@ class WebLinkExtension extends AbstractExtension /** * Resolves a resource origin as early as possible. * - * @param string $uri A public path - * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") + * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") * * @return string The path of the asset */ - public function dnsPrefetch($uri, array $attributes = []) + public function dnsPrefetch(string $uri, array $attributes = []): string { return $this->link($uri, 'dns-prefetch', $attributes); } @@ -101,12 +98,11 @@ class WebLinkExtension extends AbstractExtension /** * Initiates a early connection to a resource (DNS resolution, TCP handshake, TLS negotiation). * - * @param string $uri A public path - * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") + * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") * * @return string The path of the asset */ - public function preconnect($uri, array $attributes = []) + public function preconnect(string $uri, array $attributes = []): string { return $this->link($uri, 'preconnect', $attributes); } @@ -114,12 +110,11 @@ class WebLinkExtension extends AbstractExtension /** * Indicates to the client that it should prefetch this resource. * - * @param string $uri A public path - * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") + * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") * * @return string The path of the asset */ - public function prefetch($uri, array $attributes = []) + public function prefetch(string $uri, array $attributes = []): string { return $this->link($uri, 'prefetch', $attributes); } @@ -127,12 +122,11 @@ class WebLinkExtension extends AbstractExtension /** * Indicates to the client that it should prerender this resource . * - * @param string $uri A public path - * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") + * @param array $attributes The attributes of this link (e.g. "['as' => true]", "['pr' => 0.5]") * * @return string The path of the asset */ - public function prerender($uri, array $attributes = []) + public function prerender(string $uri, array $attributes = []): string { return $this->link($uri, 'prerender', $attributes); } diff --git a/lib/symfony/twig-bridge/Extension/WorkflowExtension.php b/lib/symfony/twig-bridge/Extension/WorkflowExtension.php index 6ff5fad9c0..9b5911ec28 100644 --- a/lib/symfony/twig-bridge/Extension/WorkflowExtension.php +++ b/lib/symfony/twig-bridge/Extension/WorkflowExtension.php @@ -13,6 +13,7 @@ namespace Symfony\Bridge\Twig\Extension; use Symfony\Component\Workflow\Registry; use Symfony\Component\Workflow\Transition; +use Symfony\Component\Workflow\TransitionBlockerList; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; @@ -20,8 +21,9 @@ use Twig\TwigFunction; * WorkflowExtension. * * @author Grégoire Pineau + * @author Carlos Pereira De Amorim */ -class WorkflowExtension extends AbstractExtension +final class WorkflowExtension extends AbstractExtension { private $workflowRegistry; @@ -30,26 +32,26 @@ class WorkflowExtension extends AbstractExtension $this->workflowRegistry = $workflowRegistry; } - public function getFunctions() + /** + * {@inheritdoc} + */ + public function getFunctions(): array { return [ new TwigFunction('workflow_can', [$this, 'canTransition']), new TwigFunction('workflow_transitions', [$this, 'getEnabledTransitions']), + new TwigFunction('workflow_transition', [$this, 'getEnabledTransition']), new TwigFunction('workflow_has_marked_place', [$this, 'hasMarkedPlace']), new TwigFunction('workflow_marked_places', [$this, 'getMarkedPlaces']), + new TwigFunction('workflow_metadata', [$this, 'getMetadata']), + new TwigFunction('workflow_transition_blockers', [$this, 'buildTransitionBlockerList']), ]; } /** * Returns true if the transition is enabled. - * - * @param object $subject A subject - * @param string $transitionName A transition - * @param string $name A workflow name - * - * @return bool true if the transition is enabled */ - public function canTransition($subject, $transitionName, $name = null) + public function canTransition(object $subject, string $transitionName, string $name = null): bool { return $this->workflowRegistry->get($subject, $name)->can($subject, $transitionName); } @@ -57,26 +59,22 @@ class WorkflowExtension extends AbstractExtension /** * Returns all enabled transitions. * - * @param object $subject A subject - * @param string $name A workflow name - * - * @return Transition[] All enabled transitions + * @return Transition[] */ - public function getEnabledTransitions($subject, $name = null) + public function getEnabledTransitions(object $subject, string $name = null): array { return $this->workflowRegistry->get($subject, $name)->getEnabledTransitions($subject); } + public function getEnabledTransition(object $subject, string $transition, string $name = null): ?Transition + { + return $this->workflowRegistry->get($subject, $name)->getEnabledTransition($subject, $transition); + } + /** * Returns true if the place is marked. - * - * @param object $subject A subject - * @param string $placeName A place name - * @param string $name A workflow name - * - * @return bool true if the transition is enabled */ - public function hasMarkedPlace($subject, $placeName, $name = null) + public function hasMarkedPlace(object $subject, string $placeName, string $name = null): bool { return $this->workflowRegistry->get($subject, $name)->getMarking($subject)->has($placeName); } @@ -84,13 +82,9 @@ class WorkflowExtension extends AbstractExtension /** * Returns marked places. * - * @param object $subject A subject - * @param bool $placesNameOnly If true, returns only places name. If false returns the raw representation - * @param string $name A workflow name - * * @return string[]|int[] */ - public function getMarkedPlaces($subject, $placesNameOnly = true, $name = null) + public function getMarkedPlaces(object $subject, bool $placesNameOnly = true, string $name = null): array { $places = $this->workflowRegistry->get($subject, $name)->getMarking($subject)->getPlaces(); @@ -101,8 +95,27 @@ class WorkflowExtension extends AbstractExtension return $places; } - public function getName() + /** + * Returns the metadata for a specific subject. + * + * @param string|Transition|null $metadataSubject Use null to get workflow metadata + * Use a string (the place name) to get place metadata + * Use a Transition instance to get transition metadata + */ + public function getMetadata(object $subject, string $key, $metadataSubject = null, string $name = null) { - return 'workflow'; + return $this + ->workflowRegistry + ->get($subject, $name) + ->getMetadataStore() + ->getMetadata($key, $metadataSubject) + ; + } + + public function buildTransitionBlockerList(object $subject, string $transitionName, string $name = null): TransitionBlockerList + { + $workflow = $this->workflowRegistry->get($subject, $name); + + return $workflow->buildTransitionBlockerList($subject, $transitionName); } } diff --git a/lib/symfony/twig-bridge/Extension/YamlExtension.php b/lib/symfony/twig-bridge/Extension/YamlExtension.php index fb364346df..63df133603 100644 --- a/lib/symfony/twig-bridge/Extension/YamlExtension.php +++ b/lib/symfony/twig-bridge/Extension/YamlExtension.php @@ -12,7 +12,6 @@ namespace Symfony\Bridge\Twig\Extension; use Symfony\Component\Yaml\Dumper as YamlDumper; -use Symfony\Component\Yaml\Yaml; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; @@ -21,12 +20,12 @@ use Twig\TwigFilter; * * @author Fabien Potencier */ -class YamlExtension extends AbstractExtension +final class YamlExtension extends AbstractExtension { /** * {@inheritdoc} */ - public function getFilters() + public function getFilters(): array { return [ new TwigFilter('yaml_encode', [$this, 'encode']), @@ -34,7 +33,7 @@ class YamlExtension extends AbstractExtension ]; } - public function encode($input, $inline = 0, $dumpObjects = 0) + public function encode($input, int $inline = 0, int $dumpObjects = 0): string { static $dumper; @@ -43,21 +42,13 @@ class YamlExtension extends AbstractExtension } if (\defined('Symfony\Component\Yaml\Yaml::DUMP_OBJECT')) { - if (\is_bool($dumpObjects)) { - @trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::DUMP_OBJECT flag instead.', \E_USER_DEPRECATED); - - $flags = $dumpObjects ? Yaml::DUMP_OBJECT : 0; - } else { - $flags = $dumpObjects; - } - - return $dumper->dump($input, $inline, 0, $flags); + return $dumper->dump($input, $inline, 0, $dumpObjects); } return $dumper->dump($input, $inline, 0, false, $dumpObjects); } - public function dump($value, $inline = 0, $dumpObjects = false) + public function dump($value, int $inline = 0, int $dumpObjects = 0): string { if (\is_resource($value)) { return '%Resource%'; @@ -69,12 +60,4 @@ class YamlExtension extends AbstractExtension return $this->encode($value, $inline, $dumpObjects); } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'yaml'; - } } diff --git a/lib/symfony/twig-bridge/Form/TwigRenderer.php b/lib/symfony/twig-bridge/Form/TwigRenderer.php deleted file mode 100644 index 34407d088c..0000000000 --- a/lib/symfony/twig-bridge/Form/TwigRenderer.php +++ /dev/null @@ -1,49 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\Twig\Form; - -use Symfony\Component\Form\FormRenderer; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; -use Twig\Environment; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use %s instead.', TwigRenderer::class, FormRenderer::class), \E_USER_DEPRECATED); - -/** - * @author Bernhard Schussek - * - * @deprecated since version 3.4, to be removed in 4.0. Use Symfony\Component\Form\FormRenderer instead. - */ -class TwigRenderer extends FormRenderer implements TwigRendererInterface -{ - public function __construct(TwigRendererEngineInterface $engine, CsrfTokenManagerInterface $csrfTokenManager = null) - { - parent::__construct($engine, $csrfTokenManager); - } - - /** - * Returns the engine used by this renderer. - * - * @return TwigRendererEngineInterface The renderer engine - */ - public function getEngine() - { - return parent::getEngine(); - } - - /** - * {@inheritdoc} - */ - public function setEnvironment(Environment $environment) - { - $this->getEngine()->setEnvironment($environment); - } -} diff --git a/lib/symfony/twig-bridge/Form/TwigRendererEngine.php b/lib/symfony/twig-bridge/Form/TwigRendererEngine.php index f5097be454..b17da34098 100644 --- a/lib/symfony/twig-bridge/Form/TwigRendererEngine.php +++ b/lib/symfony/twig-bridge/Form/TwigRendererEngine.php @@ -19,7 +19,7 @@ use Twig\Template; /** * @author Bernhard Schussek */ -class TwigRendererEngine extends AbstractRendererEngine implements TwigRendererEngineInterface +class TwigRendererEngine extends AbstractRendererEngine { /** * @var Environment @@ -31,34 +31,16 @@ class TwigRendererEngine extends AbstractRendererEngine implements TwigRendererE */ private $template; - public function __construct(array $defaultThemes = [], Environment $environment = null) + public function __construct(array $defaultThemes, Environment $environment) { - if (null === $environment) { - @trigger_error(sprintf('Not passing a Twig Environment as the second argument for "%s" constructor is deprecated since Symfony 3.2 and won\'t be possible in 4.0.', static::class), \E_USER_DEPRECATED); - } - parent::__construct($defaultThemes); $this->environment = $environment; } /** * {@inheritdoc} - * - * @deprecated since version 3.3, to be removed in 4.0 */ - public function setEnvironment(Environment $environment) - { - if ($this->environment) { - @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Pass the Twig Environment as second argument of the constructor instead.', __METHOD__), \E_USER_DEPRECATED); - } - - $this->environment = $environment; - } - - /** - * {@inheritdoc} - */ - public function renderBlock(FormView $view, $resource, $blockName, array $variables = []) + public function renderBlock(FormView $view, $resource, string $blockName, array $variables = []) { $cacheKey = $view->vars[self::CACHE_KEY_VAR]; @@ -88,13 +70,9 @@ class TwigRendererEngine extends AbstractRendererEngine implements TwigRendererE * * @see getResourceForBlock() * - * @param string $cacheKey The cache key of the form view - * @param FormView $view The form view for finding the applying themes - * @param string $blockName The name of the block to load - * - * @return bool True if the resource could be loaded, false otherwise + * @return bool */ - protected function loadResourceForBlockName($cacheKey, FormView $view, $blockName) + protected function loadResourceForBlockName(string $cacheKey, FormView $view, string $blockName) { // The caller guarantees that $this->resources[$cacheKey][$block] is // not set, but it doesn't have to check whether $this->resources[$cacheKey] @@ -161,18 +139,17 @@ class TwigRendererEngine extends AbstractRendererEngine implements TwigRendererE /** * Loads the resources for all blocks in a theme. * - * @param string $cacheKey The cache key for storing the resource - * @param mixed $theme The theme to load the block from. This parameter - * is passed by reference, because it might be necessary - * to initialize the theme first. Any changes made to - * this variable will be kept and be available upon - * further calls to this method using the same theme. + * @param mixed $theme The theme to load the block from. This parameter + * is passed by reference, because it might be necessary + * to initialize the theme first. Any changes made to + * this variable will be kept and be available upon + * further calls to this method using the same theme. */ - protected function loadResourcesFromTheme($cacheKey, &$theme) + protected function loadResourcesFromTheme(string $cacheKey, &$theme) { if (!$theme instanceof Template) { /* @var Template $theme */ - $theme = $this->environment->loadTemplate($theme); + $theme = $this->environment->load($theme)->unwrap(); } if (null === $this->template) { diff --git a/lib/symfony/twig-bridge/Form/TwigRendererEngineInterface.php b/lib/symfony/twig-bridge/Form/TwigRendererEngineInterface.php deleted file mode 100644 index a58f491f1c..0000000000 --- a/lib/symfony/twig-bridge/Form/TwigRendererEngineInterface.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\Twig\Form; - -use Symfony\Component\Form\FormRendererEngineInterface; -use Twig\Environment; - -// BC/FC with namespaced Twig -class_exists('Twig\Environment'); - -/** - * @author Bernhard Schussek - * - * @deprecated since version 3.2, to be removed in 4.0. - */ -interface TwigRendererEngineInterface extends FormRendererEngineInterface -{ - public function setEnvironment(Environment $environment); -} diff --git a/lib/symfony/twig-bridge/Form/TwigRendererInterface.php b/lib/symfony/twig-bridge/Form/TwigRendererInterface.php deleted file mode 100644 index 3bcbf5992d..0000000000 --- a/lib/symfony/twig-bridge/Form/TwigRendererInterface.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\Twig\Form; - -use Symfony\Component\Form\FormRendererInterface; -use Twig\Environment; - -// BC/FC with namespaced Twig -class_exists('Twig\Environment'); - -/** - * @author Bernhard Schussek - * - * @deprecated since version 3.2, to be removed in 4.0. - */ -interface TwigRendererInterface extends FormRendererInterface -{ - public function setEnvironment(Environment $environment); -} diff --git a/lib/symfony/twig-bridge/LICENSE b/lib/symfony/twig-bridge/LICENSE index 9e936ec044..88bf75bb4d 100644 --- a/lib/symfony/twig-bridge/LICENSE +++ b/lib/symfony/twig-bridge/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2020 Fabien Potencier +Copyright (c) 2004-2022 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/lib/symfony/twig-bridge/Mime/BodyRenderer.php b/lib/symfony/twig-bridge/Mime/BodyRenderer.php new file mode 100644 index 0000000000..47901d3108 --- /dev/null +++ b/lib/symfony/twig-bridge/Mime/BodyRenderer.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Mime; + +use League\HTMLToMarkdown\HtmlConverter; +use Symfony\Component\Mime\BodyRendererInterface; +use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\Message; +use Twig\Environment; + +/** + * @author Fabien Potencier + */ +final class BodyRenderer implements BodyRendererInterface +{ + private $twig; + private $context; + private $converter; + + public function __construct(Environment $twig, array $context = []) + { + $this->twig = $twig; + $this->context = $context; + if (class_exists(HtmlConverter::class)) { + $this->converter = new HtmlConverter([ + 'hard_break' => true, + 'strip_tags' => true, + 'remove_nodes' => 'head style', + ]); + } + } + + public function render(Message $message): void + { + if (!$message instanceof TemplatedEmail) { + return; + } + + $messageContext = $message->getContext(); + + $previousRenderingKey = $messageContext[__CLASS__] ?? null; + unset($messageContext[__CLASS__]); + $currentRenderingKey = $this->getFingerPrint($message); + if ($previousRenderingKey === $currentRenderingKey) { + return; + } + + if (isset($messageContext['email'])) { + throw new InvalidArgumentException(sprintf('A "%s" context cannot have an "email" entry as this is a reserved variable.', get_debug_type($message))); + } + + $vars = array_merge($this->context, $messageContext, [ + 'email' => new WrappedTemplatedEmail($this->twig, $message), + ]); + + if ($template = $message->getTextTemplate()) { + $message->text($this->twig->render($template, $vars)); + } + + if ($template = $message->getHtmlTemplate()) { + $message->html($this->twig->render($template, $vars)); + } + + // if text body is empty, compute one from the HTML body + if (!$message->getTextBody() && null !== $html = $message->getHtmlBody()) { + $message->text($this->convertHtmlToText(\is_resource($html) ? stream_get_contents($html) : $html)); + } + $message->context($message->getContext() + [__CLASS__ => $currentRenderingKey]); + } + + private function getFingerPrint(TemplatedEmail $message): string + { + $messageContext = $message->getContext(); + unset($messageContext[__CLASS__]); + + $payload = [$messageContext, $message->getTextTemplate(), $message->getHtmlTemplate()]; + try { + $serialized = serialize($payload); + } catch (\Exception $e) { + // Serialization of 'Closure' is not allowed + // Happens when context contain a closure, in that case, we assume that context always change. + $serialized = random_bytes(8); + } + + return md5($serialized); + } + + private function convertHtmlToText(string $html): string + { + if (null !== $this->converter) { + return $this->converter->convert($html); + } + + return strip_tags(preg_replace('{<(head|style)\b.*?}is', '', $html)); + } +} diff --git a/lib/symfony/twig-bridge/Mime/NotificationEmail.php b/lib/symfony/twig-bridge/Mime/NotificationEmail.php new file mode 100644 index 0000000000..3bdcd71dd6 --- /dev/null +++ b/lib/symfony/twig-bridge/Mime/NotificationEmail.php @@ -0,0 +1,250 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Mime; + +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\Mime\Header\Headers; +use Symfony\Component\Mime\Part\AbstractPart; +use Twig\Extra\CssInliner\CssInlinerExtension; +use Twig\Extra\Inky\InkyExtension; +use Twig\Extra\Markdown\MarkdownExtension; + +/** + * @author Fabien Potencier + */ +class NotificationEmail extends TemplatedEmail +{ + public const IMPORTANCE_URGENT = 'urgent'; + public const IMPORTANCE_HIGH = 'high'; + public const IMPORTANCE_MEDIUM = 'medium'; + public const IMPORTANCE_LOW = 'low'; + + private $theme = 'default'; + private $context = [ + 'importance' => self::IMPORTANCE_LOW, + 'content' => '', + 'exception' => false, + 'action_text' => null, + 'action_url' => null, + 'markdown' => false, + 'raw' => false, + 'footer_text' => 'Notification e-mail sent by Symfony', + ]; + + public function __construct(Headers $headers = null, AbstractPart $body = null) + { + $missingPackages = []; + if (!class_exists(CssInlinerExtension::class)) { + $missingPackages['twig/cssinliner-extra'] = 'CSS Inliner'; + } + + if (!class_exists(InkyExtension::class)) { + $missingPackages['twig/inky-extra'] = 'Inky'; + } + + if ($missingPackages) { + throw new \LogicException(sprintf('You cannot use "%s" if the "%s" Twig extension%s not available; try running "%s".', static::class, implode('" and "', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', 'composer require '.implode(' ', array_keys($missingPackages)))); + } + + parent::__construct($headers, $body); + } + + /** + * Creates a NotificationEmail instance that is appropriate to send to normal (non-admin) users. + */ + public static function asPublicEmail(Headers $headers = null, AbstractPart $body = null): self + { + $email = new static($headers, $body); + $email->markAsPublic(); + + return $email; + } + + /** + * @return $this + */ + public function markAsPublic(): self + { + $this->context['importance'] = null; + $this->context['footer_text'] = null; + + return $this; + } + + /** + * @return $this + */ + public function markdown(string $content) + { + if (!class_exists(MarkdownExtension::class)) { + throw new \LogicException(sprintf('You cannot use "%s" if the Markdown Twig extension is not available; try running "composer require twig/markdown-extra".', __METHOD__)); + } + + $this->context['markdown'] = true; + + return $this->content($content); + } + + /** + * @return $this + */ + public function content(string $content, bool $raw = false) + { + $this->context['content'] = $content; + $this->context['raw'] = $raw; + + return $this; + } + + /** + * @return $this + */ + public function action(string $text, string $url) + { + $this->context['action_text'] = $text; + $this->context['action_url'] = $url; + + return $this; + } + + /** + * @return $this + */ + public function importance(string $importance) + { + $this->context['importance'] = $importance; + + return $this; + } + + /** + * @param \Throwable|FlattenException $exception + * + * @return $this + */ + public function exception($exception) + { + if (!$exception instanceof \Throwable && !$exception instanceof FlattenException) { + throw new \LogicException(sprintf('"%s" accepts "%s" or "%s" instances.', __METHOD__, \Throwable::class, FlattenException::class)); + } + + $exceptionAsString = $this->getExceptionAsString($exception); + + $this->context['exception'] = true; + $this->attach($exceptionAsString, 'exception.txt', 'text/plain'); + $this->importance(self::IMPORTANCE_URGENT); + + if (!$this->getSubject()) { + $this->subject($exception->getMessage()); + } + + return $this; + } + + /** + * @return $this + */ + public function theme(string $theme) + { + $this->theme = $theme; + + return $this; + } + + public function getTextTemplate(): ?string + { + if ($template = parent::getTextTemplate()) { + return $template; + } + + return '@email/'.$this->theme.'/notification/body.txt.twig'; + } + + public function getHtmlTemplate(): ?string + { + if ($template = parent::getHtmlTemplate()) { + return $template; + } + + return '@email/'.$this->theme.'/notification/body.html.twig'; + } + + public function getContext(): array + { + return array_merge($this->context, parent::getContext()); + } + + public function getPreparedHeaders(): Headers + { + $headers = parent::getPreparedHeaders(); + + $importance = $this->context['importance'] ?? self::IMPORTANCE_LOW; + $this->priority($this->determinePriority($importance)); + if ($this->context['importance']) { + $headers->setHeaderBody('Text', 'Subject', sprintf('[%s] %s', strtoupper($importance), $this->getSubject())); + } + + return $headers; + } + + private function determinePriority(string $importance): int + { + switch ($importance) { + case self::IMPORTANCE_URGENT: + return self::PRIORITY_HIGHEST; + case self::IMPORTANCE_HIGH: + return self::PRIORITY_HIGH; + case self::IMPORTANCE_MEDIUM: + return self::PRIORITY_NORMAL; + case self::IMPORTANCE_LOW: + default: + return self::PRIORITY_LOW; + } + } + + private function getExceptionAsString($exception): string + { + if (class_exists(FlattenException::class)) { + $exception = $exception instanceof FlattenException ? $exception : FlattenException::createFromThrowable($exception); + + return $exception->getAsString(); + } + + $message = \get_class($exception); + if ('' !== $exception->getMessage()) { + $message .= ': '.$exception->getMessage(); + } + + $message .= ' in '.$exception->getFile().':'.$exception->getLine()."\n"; + $message .= "Stack trace:\n".$exception->getTraceAsString()."\n\n"; + + return rtrim($message); + } + + /** + * @internal + */ + public function __serialize(): array + { + return [$this->context, parent::__serialize()]; + } + + /** + * @internal + */ + public function __unserialize(array $data): void + { + [$this->context, $parentData] = $data; + + parent::__unserialize($parentData); + } +} diff --git a/lib/symfony/twig-bridge/Mime/TemplatedEmail.php b/lib/symfony/twig-bridge/Mime/TemplatedEmail.php new file mode 100644 index 0000000000..6dd9202de8 --- /dev/null +++ b/lib/symfony/twig-bridge/Mime/TemplatedEmail.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 Symfony\Bridge\Twig\Mime; + +use Symfony\Component\Mime\Email; + +/** + * @author Fabien Potencier + */ +class TemplatedEmail extends Email +{ + private $htmlTemplate; + private $textTemplate; + private $context = []; + + /** + * @return $this + */ + public function textTemplate(?string $template) + { + $this->textTemplate = $template; + + return $this; + } + + /** + * @return $this + */ + public function htmlTemplate(?string $template) + { + $this->htmlTemplate = $template; + + return $this; + } + + public function getTextTemplate(): ?string + { + return $this->textTemplate; + } + + public function getHtmlTemplate(): ?string + { + return $this->htmlTemplate; + } + + /** + * @return $this + */ + public function context(array $context) + { + $this->context = $context; + + return $this; + } + + public function getContext(): array + { + return $this->context; + } + + /** + * @internal + */ + public function __serialize(): array + { + return [$this->htmlTemplate, $this->textTemplate, $this->context, parent::__serialize()]; + } + + /** + * @internal + */ + public function __unserialize(array $data): void + { + [$this->htmlTemplate, $this->textTemplate, $this->context, $parentData] = $data; + + parent::__unserialize($parentData); + } +} diff --git a/lib/symfony/twig-bridge/Mime/WrappedTemplatedEmail.php b/lib/symfony/twig-bridge/Mime/WrappedTemplatedEmail.php new file mode 100644 index 0000000000..f1726914b4 --- /dev/null +++ b/lib/symfony/twig-bridge/Mime/WrappedTemplatedEmail.php @@ -0,0 +1,194 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Mime; + +use Symfony\Component\Mime\Address; +use Twig\Environment; + +/** + * @internal + * + * @author Fabien Potencier + */ +final class WrappedTemplatedEmail +{ + private $twig; + private $message; + + public function __construct(Environment $twig, TemplatedEmail $message) + { + $this->twig = $twig; + $this->message = $message; + } + + public function toName(): string + { + return $this->message->getTo()[0]->getName(); + } + + public function image(string $image, string $contentType = null): string + { + $file = $this->twig->getLoader()->getSourceContext($image); + if ($path = $file->getPath()) { + $this->message->embedFromPath($path, $image, $contentType); + } else { + $this->message->embed($file->getCode(), $image, $contentType); + } + + return 'cid:'.$image; + } + + public function attach(string $file, string $name = null, string $contentType = null): void + { + $file = $this->twig->getLoader()->getSourceContext($file); + if ($path = $file->getPath()) { + $this->message->attachFromPath($path, $name, $contentType); + } else { + $this->message->attach($file->getCode(), $name, $contentType); + } + } + + /** + * @return $this + */ + public function setSubject(string $subject): self + { + $this->message->subject($subject); + + return $this; + } + + public function getSubject(): ?string + { + return $this->message->getSubject(); + } + + /** + * @return $this + */ + public function setReturnPath(string $address): self + { + $this->message->returnPath($address); + + return $this; + } + + public function getReturnPath(): string + { + return $this->message->getReturnPath(); + } + + /** + * @return $this + */ + public function addFrom(string $address, string $name = ''): self + { + $this->message->addFrom(new Address($address, $name)); + + return $this; + } + + /** + * @return Address[] + */ + public function getFrom(): array + { + return $this->message->getFrom(); + } + + /** + * @return $this + */ + public function addReplyTo(string $address): self + { + $this->message->addReplyTo($address); + + return $this; + } + + /** + * @return Address[] + */ + public function getReplyTo(): array + { + return $this->message->getReplyTo(); + } + + /** + * @return $this + */ + public function addTo(string $address, string $name = ''): self + { + $this->message->addTo(new Address($address, $name)); + + return $this; + } + + /** + * @return Address[] + */ + public function getTo(): array + { + return $this->message->getTo(); + } + + /** + * @return $this + */ + public function addCc(string $address, string $name = ''): self + { + $this->message->addCc(new Address($address, $name)); + + return $this; + } + + /** + * @return Address[] + */ + public function getCc(): array + { + return $this->message->getCc(); + } + + /** + * @return $this + */ + public function addBcc(string $address, string $name = ''): self + { + $this->message->addBcc(new Address($address, $name)); + + return $this; + } + + /** + * @return Address[] + */ + public function getBcc(): array + { + return $this->message->getBcc(); + } + + /** + * @return $this + */ + public function setPriority(int $priority): self + { + $this->message->priority($priority); + + return $this; + } + + public function getPriority(): int + { + return $this->message->getPriority(); + } +} diff --git a/lib/symfony/twig-bridge/Node/DumpNode.php b/lib/symfony/twig-bridge/Node/DumpNode.php index 387f826434..68c00556f8 100644 --- a/lib/symfony/twig-bridge/Node/DumpNode.php +++ b/lib/symfony/twig-bridge/Node/DumpNode.php @@ -17,11 +17,11 @@ use Twig\Node\Node; /** * @author Julien Galenski */ -class DumpNode extends Node +final class DumpNode extends Node { private $varPrefix; - public function __construct($varPrefix, Node $values = null, $lineno, $tag = null) + public function __construct(string $varPrefix, ?Node $values, int $lineno, string $tag = null) { $nodes = []; if (null !== $values) { @@ -32,10 +32,7 @@ class DumpNode extends Node $this->varPrefix = $varPrefix; } - /** - * {@inheritdoc} - */ - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->write("if (\$this->env->isDebug()) {\n") diff --git a/lib/symfony/twig-bridge/Node/FormThemeNode.php b/lib/symfony/twig-bridge/Node/FormThemeNode.php index 2ab4c35a3f..e37311267b 100644 --- a/lib/symfony/twig-bridge/Node/FormThemeNode.php +++ b/lib/symfony/twig-bridge/Node/FormThemeNode.php @@ -11,35 +11,26 @@ namespace Symfony\Bridge\Twig\Node; -use Symfony\Bridge\Twig\Form\TwigRenderer; use Symfony\Component\Form\FormRenderer; use Twig\Compiler; -use Twig\Error\RuntimeError; use Twig\Node\Node; /** * @author Fabien Potencier */ -class FormThemeNode extends Node +final class FormThemeNode extends Node { - public function __construct(Node $form, Node $resources, $lineno, $tag = null, $only = false) + public function __construct(Node $form, Node $resources, int $lineno, string $tag = null, bool $only = false) { - parent::__construct(['form' => $form, 'resources' => $resources], ['only' => (bool) $only], $lineno, $tag); + parent::__construct(['form' => $form, 'resources' => $resources], ['only' => $only], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { - try { - $compiler->getEnvironment()->getRuntime(FormRenderer::class); - $renderer = FormRenderer::class; - } catch (RuntimeError $e) { - $renderer = TwigRenderer::class; - } - $compiler ->addDebugInfo($this) ->write('$this->env->getRuntime(') - ->string($renderer) + ->string(FormRenderer::class) ->raw(')->setTheme(') ->subcompile($this->getNode('form')) ->raw(', ') diff --git a/lib/symfony/twig-bridge/Node/RenderBlockNode.php b/lib/symfony/twig-bridge/Node/RenderBlockNode.php index dc7d860793..4d4cf61365 100644 --- a/lib/symfony/twig-bridge/Node/RenderBlockNode.php +++ b/lib/symfony/twig-bridge/Node/RenderBlockNode.php @@ -22,9 +22,9 @@ use Twig\Node\Expression\FunctionExpression; * * @author Bernhard Schussek */ -class RenderBlockNode extends FunctionExpression +final class RenderBlockNode extends FunctionExpression { - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->addDebugInfo($this); $arguments = iterator_to_array($this->getNode('arguments')); diff --git a/lib/symfony/twig-bridge/Node/SearchAndRenderBlockNode.php b/lib/symfony/twig-bridge/Node/SearchAndRenderBlockNode.php index 8925d58877..9967639d16 100644 --- a/lib/symfony/twig-bridge/Node/SearchAndRenderBlockNode.php +++ b/lib/symfony/twig-bridge/Node/SearchAndRenderBlockNode.php @@ -19,9 +19,9 @@ use Twig\Node\Expression\FunctionExpression; /** * @author Bernhard Schussek */ -class SearchAndRenderBlockNode extends FunctionExpression +final class SearchAndRenderBlockNode extends FunctionExpression { - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->addDebugInfo($this); $compiler->raw('$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock('); @@ -40,7 +40,7 @@ class SearchAndRenderBlockNode extends FunctionExpression // The "label" function expects the label in the second and // the variables in the third argument $label = $arguments[1]; - $variables = isset($arguments[2]) ? $arguments[2] : null; + $variables = $arguments[2] ?? null; $lineno = $label->getTemplateLine(); if ($label instanceof ConstantExpression) { diff --git a/lib/symfony/twig-bridge/Node/StopwatchNode.php b/lib/symfony/twig-bridge/Node/StopwatchNode.php index 538c22bb79..cfa4d8a197 100644 --- a/lib/symfony/twig-bridge/Node/StopwatchNode.php +++ b/lib/symfony/twig-bridge/Node/StopwatchNode.php @@ -20,14 +20,14 @@ use Twig\Node\Node; * * @author Wouter J */ -class StopwatchNode extends Node +final class StopwatchNode extends Node { - public function __construct(Node $name, Node $body, AssignNameExpression $var, $lineno = 0, $tag = null) + public function __construct(Node $name, Node $body, AssignNameExpression $var, int $lineno = 0, string $tag = null) { parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) diff --git a/lib/symfony/twig-bridge/Node/TransDefaultDomainNode.php b/lib/symfony/twig-bridge/Node/TransDefaultDomainNode.php index 294718ba1f..df29f0a199 100644 --- a/lib/symfony/twig-bridge/Node/TransDefaultDomainNode.php +++ b/lib/symfony/twig-bridge/Node/TransDefaultDomainNode.php @@ -18,14 +18,14 @@ use Twig\Node\Node; /** * @author Fabien Potencier */ -class TransDefaultDomainNode extends Node +final class TransDefaultDomainNode extends Node { - public function __construct(AbstractExpression $expr, $lineno = 0, $tag = null) + public function __construct(AbstractExpression $expr, int $lineno = 0, string $tag = null) { parent::__construct(['expr' => $expr], [], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { // noop as this node is just a marker for TranslationDefaultDomainNodeVisitor } diff --git a/lib/symfony/twig-bridge/Node/TransNode.php b/lib/symfony/twig-bridge/Node/TransNode.php index 1b02b9c3d7..8a126ba569 100644 --- a/lib/symfony/twig-bridge/Node/TransNode.php +++ b/lib/symfony/twig-bridge/Node/TransNode.php @@ -19,15 +19,12 @@ use Twig\Node\Expression\NameExpression; use Twig\Node\Node; use Twig\Node\TextNode; -// BC/FC with namespaced Twig -class_exists('Twig\Node\Expression\ArrayExpression'); - /** * @author Fabien Potencier */ -class TransNode extends Node +final class TransNode extends Node { - public function __construct(Node $body, Node $domain = null, AbstractExpression $count = null, AbstractExpression $vars = null, AbstractExpression $locale = null, $lineno = 0, $tag = null) + public function __construct(Node $body, Node $domain = null, AbstractExpression $count = null, AbstractExpression $vars = null, AbstractExpression $locale = null, int $lineno = 0, string $tag = null) { $nodes = ['body' => $body]; if (null !== $domain) { @@ -46,7 +43,7 @@ class TransNode extends Node parent::__construct($nodes, [], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->addDebugInfo($this); @@ -55,24 +52,15 @@ class TransNode extends Node $defaults = $this->getNode('vars'); $vars = null; } - list($msg, $defaults) = $this->compileString($this->getNode('body'), $defaults, (bool) $vars); - - $method = !$this->hasNode('count') ? 'trans' : 'transChoice'; + [$msg, $defaults] = $this->compileString($this->getNode('body'), $defaults, (bool) $vars); $compiler - ->write('echo $this->env->getExtension(\'Symfony\Bridge\Twig\Extension\TranslationExtension\')->getTranslator()->'.$method.'(') + ->write('echo $this->env->getExtension(\'Symfony\Bridge\Twig\Extension\TranslationExtension\')->trans(') ->subcompile($msg) ; $compiler->raw(', '); - if ($this->hasNode('count')) { - $compiler - ->subcompile($this->getNode('count')) - ->raw(', ') - ; - } - if (null !== $vars) { $compiler ->raw('array_merge(') @@ -98,11 +86,21 @@ class TransNode extends Node ->raw(', ') ->subcompile($this->getNode('locale')) ; + } elseif ($this->hasNode('count')) { + $compiler->raw(', null'); } + + if ($this->hasNode('count')) { + $compiler + ->raw(', ') + ->subcompile($this->getNode('count')) + ; + } + $compiler->raw(");\n"); } - protected function compileString(Node $body, ArrayExpression $vars, $ignoreStrictCheck = false) + private function compileString(Node $body, ArrayExpression $vars, bool $ignoreStrictCheck = false): array { if ($body instanceof ConstantExpression) { $msg = $body->getAttribute('value'); diff --git a/lib/symfony/twig-bridge/NodeVisitor/Scope.php b/lib/symfony/twig-bridge/NodeVisitor/Scope.php index 642623f2a6..765b4b69bd 100644 --- a/lib/symfony/twig-bridge/NodeVisitor/Scope.php +++ b/lib/symfony/twig-bridge/NodeVisitor/Scope.php @@ -50,14 +50,11 @@ class Scope /** * Stores data into current scope. * - * @param string $key - * @param mixed $value - * * @return $this * * @throws \LogicException */ - public function set($key, $value) + public function set(string $key, $value) { if ($this->left) { throw new \LogicException('Left scope is not mutable.'); @@ -71,11 +68,9 @@ class Scope /** * Tests if a data is visible from current scope. * - * @param string $key - * * @return bool */ - public function has($key) + public function has(string $key) { if (\array_key_exists($key, $this->data)) { return true; @@ -91,12 +86,9 @@ class Scope /** * Returns data visible from current scope. * - * @param string $key - * @param mixed $default - * * @return mixed */ - public function get($key, $default = null) + public function get(string $key, $default = null) { if (\array_key_exists($key, $this->data)) { return $this->data[$key]; diff --git a/lib/symfony/twig-bridge/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/lib/symfony/twig-bridge/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index be08d0d1d1..213365ed9f 100644 --- a/lib/symfony/twig-bridge/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/lib/symfony/twig-bridge/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -28,7 +28,7 @@ use Twig\NodeVisitor\AbstractNodeVisitor; /** * @author Fabien Potencier */ -class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor +final class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor { private $scope; @@ -40,7 +40,7 @@ class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor /** * {@inheritdoc} */ - protected function doEnterNode(Node $node, Environment $env) + protected function doEnterNode(Node $node, Environment $env): Node { if ($node instanceof BlockNode || $node instanceof ModuleNode) { $this->scope = $this->scope->enter(); @@ -64,21 +64,18 @@ class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor return $node; } - if ($node instanceof FilterExpression && \in_array($node->getNode('filter')->getAttribute('value'), ['trans', 'transchoice'])) { + if ($node instanceof FilterExpression && 'trans' === $node->getNode('filter')->getAttribute('value')) { $arguments = $node->getNode('arguments'); - $ind = 'trans' === $node->getNode('filter')->getAttribute('value') ? 1 : 2; if ($this->isNamedArguments($arguments)) { - if (!$arguments->hasNode('domain') && !$arguments->hasNode($ind)) { + if (!$arguments->hasNode('domain') && !$arguments->hasNode(1)) { $arguments->setNode('domain', $this->scope->get('domain')); } - } else { - if (!$arguments->hasNode($ind)) { - if (!$arguments->hasNode($ind - 1)) { - $arguments->setNode($ind - 1, new ArrayExpression([], $node->getTemplateLine())); - } - - $arguments->setNode($ind, $this->scope->get('domain')); + } elseif (!$arguments->hasNode(1)) { + if (!$arguments->hasNode(0)) { + $arguments->setNode(0, new ArrayExpression([], $node->getTemplateLine())); } + + $arguments->setNode(1, $this->scope->get('domain')); } } elseif ($node instanceof TransNode) { if (!$node->hasNode('domain')) { @@ -92,7 +89,7 @@ class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor /** * {@inheritdoc} */ - protected function doLeaveNode(Node $node, Environment $env) + protected function doLeaveNode(Node $node, Environment $env): ?Node { if ($node instanceof TransDefaultDomainNode) { return null; @@ -107,18 +104,13 @@ class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor /** * {@inheritdoc} - * - * @return int */ - public function getPriority() + public function getPriority(): int { return -10; } - /** - * @return bool - */ - private function isNamedArguments($arguments) + private function isNamedArguments(Node $arguments): bool { foreach ($arguments as $name => $node) { if (!\is_int($name)) { @@ -129,7 +121,7 @@ class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor return false; } - private function getVarName() + private function getVarName(): string { return sprintf('__internal_%s', hash('sha256', uniqid(mt_rand(), true), false)); } diff --git a/lib/symfony/twig-bridge/NodeVisitor/TranslationNodeVisitor.php b/lib/symfony/twig-bridge/NodeVisitor/TranslationNodeVisitor.php index 1a399ce8ba..d42245e2b8 100644 --- a/lib/symfony/twig-bridge/NodeVisitor/TranslationNodeVisitor.php +++ b/lib/symfony/twig-bridge/NodeVisitor/TranslationNodeVisitor.php @@ -13,8 +13,10 @@ namespace Symfony\Bridge\Twig\NodeVisitor; use Symfony\Bridge\Twig\Node\TransNode; use Twig\Environment; +use Twig\Node\Expression\Binary\ConcatBinary; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FilterExpression; +use Twig\Node\Expression\FunctionExpression; use Twig\Node\Node; use Twig\NodeVisitor\AbstractNodeVisitor; @@ -23,26 +25,26 @@ use Twig\NodeVisitor\AbstractNodeVisitor; * * @author Fabien Potencier */ -class TranslationNodeVisitor extends AbstractNodeVisitor +final class TranslationNodeVisitor extends AbstractNodeVisitor { - const UNDEFINED_DOMAIN = '_undefined'; + public const UNDEFINED_DOMAIN = '_undefined'; private $enabled = false; private $messages = []; - public function enable() + public function enable(): void { $this->enabled = true; $this->messages = []; } - public function disable() + public function disable(): void { $this->enabled = false; $this->messages = []; } - public function getMessages() + public function getMessages(): array { return $this->messages; } @@ -50,7 +52,7 @@ class TranslationNodeVisitor extends AbstractNodeVisitor /** * {@inheritdoc} */ - protected function doEnterNode(Node $node, Environment $env) + protected function doEnterNode(Node $node, Environment $env): Node { if (!$this->enabled) { return $node; @@ -67,21 +69,33 @@ class TranslationNodeVisitor extends AbstractNodeVisitor $this->getReadDomainFromArguments($node->getNode('arguments'), 1), ]; } elseif ( - $node instanceof FilterExpression && - 'transchoice' === $node->getNode('filter')->getAttribute('value') && - $node->getNode('node') instanceof ConstantExpression + $node instanceof FunctionExpression && + 't' === $node->getAttribute('name') ) { - // extract constant nodes with a trans filter - $this->messages[] = [ - $node->getNode('node')->getAttribute('value'), - $this->getReadDomainFromArguments($node->getNode('arguments'), 2), - ]; + $nodeArguments = $node->getNode('arguments'); + + if ($nodeArguments->getIterator()->current() instanceof ConstantExpression) { + $this->messages[] = [ + $this->getReadMessageFromArguments($nodeArguments, 0), + $this->getReadDomainFromArguments($nodeArguments, 2), + ]; + } } elseif ($node instanceof TransNode) { // extract trans nodes $this->messages[] = [ $node->getNode('body')->getAttribute('data'), $node->hasNode('domain') ? $this->getReadDomainFromNode($node->getNode('domain')) : null, ]; + } elseif ( + $node instanceof FilterExpression && + 'trans' === $node->getNode('filter')->getAttribute('value') && + $node->getNode('node') instanceof ConcatBinary && + $message = $this->getConcatValueFromNode($node->getNode('node'), null) + ) { + $this->messages[] = [ + $message, + $this->getReadDomainFromArguments($node->getNode('arguments'), 1), + ]; } return $node; @@ -90,27 +104,42 @@ class TranslationNodeVisitor extends AbstractNodeVisitor /** * {@inheritdoc} */ - protected function doLeaveNode(Node $node, Environment $env) + protected function doLeaveNode(Node $node, Environment $env): ?Node { return $node; } /** * {@inheritdoc} - * - * @return int */ - public function getPriority() + public function getPriority(): int { return 0; } - /** - * @param int $index - * - * @return string|null - */ - private function getReadDomainFromArguments(Node $arguments, $index) + private function getReadMessageFromArguments(Node $arguments, int $index): ?string + { + if ($arguments->hasNode('message')) { + $argument = $arguments->getNode('message'); + } elseif ($arguments->hasNode($index)) { + $argument = $arguments->getNode($index); + } else { + return null; + } + + return $this->getReadMessageFromNode($argument); + } + + private function getReadMessageFromNode(Node $node): ?string + { + if ($node instanceof ConstantExpression) { + return $node->getAttribute('value'); + } + + return null; + } + + private function getReadDomainFromArguments(Node $arguments, int $index): ?string { if ($arguments->hasNode('domain')) { $argument = $arguments->getNode('domain'); @@ -123,10 +152,7 @@ class TranslationNodeVisitor extends AbstractNodeVisitor return $this->getReadDomainFromNode($argument); } - /** - * @return string|null - */ - private function getReadDomainFromNode(Node $node) + private function getReadDomainFromNode(Node $node): ?string { if ($node instanceof ConstantExpression) { return $node->getAttribute('value'); @@ -134,4 +160,28 @@ class TranslationNodeVisitor extends AbstractNodeVisitor return self::UNDEFINED_DOMAIN; } + + private function getConcatValueFromNode(Node $node, ?string $value): ?string + { + if ($node instanceof ConcatBinary) { + foreach ($node as $nextNode) { + if ($nextNode instanceof ConcatBinary) { + $nextValue = $this->getConcatValueFromNode($nextNode, $value); + if (null === $nextValue) { + return null; + } + $value .= $nextValue; + } elseif ($nextNode instanceof ConstantExpression) { + $value .= $nextNode->getAttribute('value'); + } else { + // this is a node we cannot process (variable, or translation in translation) + return null; + } + } + } elseif ($node instanceof ConstantExpression) { + $value .= $node->getAttribute('value'); + } + + return $value; + } } diff --git a/lib/symfony/twig-bridge/README.md b/lib/symfony/twig-bridge/README.md index 602f5a54c3..533d573dbc 100644 --- a/lib/symfony/twig-bridge/README.md +++ b/lib/symfony/twig-bridge/README.md @@ -1,13 +1,13 @@ Twig Bridge =========== -Provides integration for [Twig](https://twig.symfony.com/) with various -Symfony components. +The Twig bridge provides integration for [Twig](https://twig.symfony.com/) with +various Symfony components. Resources --------- - * [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) + * [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/lib/symfony/twig-bridge/Resources/views/Email/default/notification/body.html.twig b/lib/symfony/twig-bridge/Resources/views/Email/default/notification/body.html.twig new file mode 100644 index 0000000000..9027546861 --- /dev/null +++ b/lib/symfony/twig-bridge/Resources/views/Email/default/notification/body.html.twig @@ -0,0 +1 @@ +{% extends "@email/zurb_2/notification/body.html.twig" %} diff --git a/lib/symfony/twig-bridge/Resources/views/Email/default/notification/body.txt.twig b/lib/symfony/twig-bridge/Resources/views/Email/default/notification/body.txt.twig new file mode 100644 index 0000000000..37671b1f28 --- /dev/null +++ b/lib/symfony/twig-bridge/Resources/views/Email/default/notification/body.txt.twig @@ -0,0 +1 @@ +{% extends "@email/zurb_2/notification/body.txt.twig" %} diff --git a/lib/symfony/twig-bridge/Resources/views/Email/zurb_2/main.css b/lib/symfony/twig-bridge/Resources/views/Email/zurb_2/main.css new file mode 100644 index 0000000000..b826813ec5 --- /dev/null +++ b/lib/symfony/twig-bridge/Resources/views/Email/zurb_2/main.css @@ -0,0 +1,1667 @@ +/* + * Copyright (c) 2017 ZURB, inc. -- MIT License + * + * https://raw.githubusercontent.com/foundation/foundation-emails/v2.2.1/dist/foundation-emails.css + */ + +.wrapper { + width: 100%; +} + +#outlook a { + padding: 0; +} + +body { + width: 100% !important; + min-width: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + margin: 0; + Margin: 0; + padding: 0; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.ExternalClass { + width: 100%; +} + +.ExternalClass, +.ExternalClass p, +.ExternalClass span, +.ExternalClass font, +.ExternalClass td, +.ExternalClass div { + line-height: 100%; +} + +#backgroundTable { + margin: 0; + Margin: 0; + padding: 0; + width: 100% !important; + line-height: 100% !important; +} + +img { + outline: none; + text-decoration: none; + -ms-interpolation-mode: bicubic; + width: auto; + max-width: 100%; + clear: both; + display: block; +} + +center { + width: 100%; + min-width: 580px; +} + +a img { + border: none; +} + +p { + margin: 0 0 0 10px; + Margin: 0 0 0 10px; +} + +table { + border-spacing: 0; + border-collapse: collapse; +} + +td { + word-wrap: break-word; + -webkit-hyphens: auto; + -moz-hyphens: auto; + hyphens: auto; + border-collapse: collapse !important; +} + +table, +tr, +td { + padding: 0; + vertical-align: top; + text-align: left; +} + +@media only screen { + html { + min-height: 100%; + background: #f3f3f3; + } +} + +table.body { + background: #f3f3f3; + height: 100%; + width: 100%; +} + +table.container { + background: #fefefe; + width: 580px; + margin: 0 auto; + Margin: 0 auto; + text-align: inherit; +} + +table.row { + padding: 0; + width: 100%; + position: relative; +} + +table.spacer { + width: 100%; +} + +table.spacer td { + mso-line-height-rule: exactly; +} + +table.container table.row { + display: table; +} + +td.columns, +td.column, +th.columns, +th.column { + margin: 0 auto; + Margin: 0 auto; + padding-left: 16px; + padding-bottom: 16px; +} + +td.columns .column, +td.columns .columns, +td.column .column, +td.column .columns, +th.columns .column, +th.columns .columns, +th.column .column, +th.column .columns { + padding-left: 0 !important; + padding-right: 0 !important; +} + +td.columns .column center, +td.columns .columns center, +td.column .column center, +td.column .columns center, +th.columns .column center, +th.columns .columns center, +th.column .column center, +th.column .columns center { + min-width: none !important; +} + +td.columns.last, +td.column.last, +th.columns.last, +th.column.last { + padding-right: 16px; +} + +td.columns table:not(.button), +td.column table:not(.button), +th.columns table:not(.button), +th.column table:not(.button) { + width: 100%; +} + +td.large-1, +th.large-1 { + width: 32.33333px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-1.first, +th.large-1.first { + padding-left: 16px; +} + +td.large-1.last, +th.large-1.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-1, +.collapse>tbody>tr>th.large-1 { + padding-right: 0; + padding-left: 0; + width: 48.33333px; +} + +.collapse td.large-1.first, +.collapse th.large-1.first, +.collapse td.large-1.last, +.collapse th.large-1.last { + width: 56.33333px; +} + +td.large-1 center, +th.large-1 center { + min-width: 0.33333px; +} + +.body .columns td.large-1, +.body .column td.large-1, +.body .columns th.large-1, +.body .column th.large-1 { + width: 8.33333%; +} + +td.large-2, +th.large-2 { + width: 80.66667px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-2.first, +th.large-2.first { + padding-left: 16px; +} + +td.large-2.last, +th.large-2.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-2, +.collapse>tbody>tr>th.large-2 { + padding-right: 0; + padding-left: 0; + width: 96.66667px; +} + +.collapse td.large-2.first, +.collapse th.large-2.first, +.collapse td.large-2.last, +.collapse th.large-2.last { + width: 104.66667px; +} + +td.large-2 center, +th.large-2 center { + min-width: 48.66667px; +} + +.body .columns td.large-2, +.body .column td.large-2, +.body .columns th.large-2, +.body .column th.large-2 { + width: 16.66667%; +} + +td.large-3, +th.large-3 { + width: 129px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-3.first, +th.large-3.first { + padding-left: 16px; +} + +td.large-3.last, +th.large-3.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-3, +.collapse>tbody>tr>th.large-3 { + padding-right: 0; + padding-left: 0; + width: 145px; +} + +.collapse td.large-3.first, +.collapse th.large-3.first, +.collapse td.large-3.last, +.collapse th.large-3.last { + width: 153px; +} + +td.large-3 center, +th.large-3 center { + min-width: 97px; +} + +.body .columns td.large-3, +.body .column td.large-3, +.body .columns th.large-3, +.body .column th.large-3 { + width: 25%; +} + +td.large-4, +th.large-4 { + width: 177.33333px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-4.first, +th.large-4.first { + padding-left: 16px; +} + +td.large-4.last, +th.large-4.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-4, +.collapse>tbody>tr>th.large-4 { + padding-right: 0; + padding-left: 0; + width: 193.33333px; +} + +.collapse td.large-4.first, +.collapse th.large-4.first, +.collapse td.large-4.last, +.collapse th.large-4.last { + width: 201.33333px; +} + +td.large-4 center, +th.large-4 center { + min-width: 145.33333px; +} + +.body .columns td.large-4, +.body .column td.large-4, +.body .columns th.large-4, +.body .column th.large-4 { + width: 33.33333%; +} + +td.large-5, +th.large-5 { + width: 225.66667px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-5.first, +th.large-5.first { + padding-left: 16px; +} + +td.large-5.last, +th.large-5.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-5, +.collapse>tbody>tr>th.large-5 { + padding-right: 0; + padding-left: 0; + width: 241.66667px; +} + +.collapse td.large-5.first, +.collapse th.large-5.first, +.collapse td.large-5.last, +.collapse th.large-5.last { + width: 249.66667px; +} + +td.large-5 center, +th.large-5 center { + min-width: 193.66667px; +} + +.body .columns td.large-5, +.body .column td.large-5, +.body .columns th.large-5, +.body .column th.large-5 { + width: 41.66667%; +} + +td.large-6, +th.large-6 { + width: 274px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-6.first, +th.large-6.first { + padding-left: 16px; +} + +td.large-6.last, +th.large-6.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-6, +.collapse>tbody>tr>th.large-6 { + padding-right: 0; + padding-left: 0; + width: 290px; +} + +.collapse td.large-6.first, +.collapse th.large-6.first, +.collapse td.large-6.last, +.collapse th.large-6.last { + width: 298px; +} + +td.large-6 center, +th.large-6 center { + min-width: 242px; +} + +.body .columns td.large-6, +.body .column td.large-6, +.body .columns th.large-6, +.body .column th.large-6 { + width: 50%; +} + +td.large-7, +th.large-7 { + width: 322.33333px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-7.first, +th.large-7.first { + padding-left: 16px; +} + +td.large-7.last, +th.large-7.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-7, +.collapse>tbody>tr>th.large-7 { + padding-right: 0; + padding-left: 0; + width: 338.33333px; +} + +.collapse td.large-7.first, +.collapse th.large-7.first, +.collapse td.large-7.last, +.collapse th.large-7.last { + width: 346.33333px; +} + +td.large-7 center, +th.large-7 center { + min-width: 290.33333px; +} + +.body .columns td.large-7, +.body .column td.large-7, +.body .columns th.large-7, +.body .column th.large-7 { + width: 58.33333%; +} + +td.large-8, +th.large-8 { + width: 370.66667px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-8.first, +th.large-8.first { + padding-left: 16px; +} + +td.large-8.last, +th.large-8.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-8, +.collapse>tbody>tr>th.large-8 { + padding-right: 0; + padding-left: 0; + width: 386.66667px; +} + +.collapse td.large-8.first, +.collapse th.large-8.first, +.collapse td.large-8.last, +.collapse th.large-8.last { + width: 394.66667px; +} + +td.large-8 center, +th.large-8 center { + min-width: 338.66667px; +} + +.body .columns td.large-8, +.body .column td.large-8, +.body .columns th.large-8, +.body .column th.large-8 { + width: 66.66667%; +} + +td.large-9, +th.large-9 { + width: 419px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-9.first, +th.large-9.first { + padding-left: 16px; +} + +td.large-9.last, +th.large-9.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-9, +.collapse>tbody>tr>th.large-9 { + padding-right: 0; + padding-left: 0; + width: 435px; +} + +.collapse td.large-9.first, +.collapse th.large-9.first, +.collapse td.large-9.last, +.collapse th.large-9.last { + width: 443px; +} + +td.large-9 center, +th.large-9 center { + min-width: 387px; +} + +.body .columns td.large-9, +.body .column td.large-9, +.body .columns th.large-9, +.body .column th.large-9 { + width: 75%; +} + +td.large-10, +th.large-10 { + width: 467.33333px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-10.first, +th.large-10.first { + padding-left: 16px; +} + +td.large-10.last, +th.large-10.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-10, +.collapse>tbody>tr>th.large-10 { + padding-right: 0; + padding-left: 0; + width: 483.33333px; +} + +.collapse td.large-10.first, +.collapse th.large-10.first, +.collapse td.large-10.last, +.collapse th.large-10.last { + width: 491.33333px; +} + +td.large-10 center, +th.large-10 center { + min-width: 435.33333px; +} + +.body .columns td.large-10, +.body .column td.large-10, +.body .columns th.large-10, +.body .column th.large-10 { + width: 83.33333%; +} + +td.large-11, +th.large-11 { + width: 515.66667px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-11.first, +th.large-11.first { + padding-left: 16px; +} + +td.large-11.last, +th.large-11.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-11, +.collapse>tbody>tr>th.large-11 { + padding-right: 0; + padding-left: 0; + width: 531.66667px; +} + +.collapse td.large-11.first, +.collapse th.large-11.first, +.collapse td.large-11.last, +.collapse th.large-11.last { + width: 539.66667px; +} + +td.large-11 center, +th.large-11 center { + min-width: 483.66667px; +} + +.body .columns td.large-11, +.body .column td.large-11, +.body .columns th.large-11, +.body .column th.large-11 { + width: 91.66667%; +} + +td.large-12, +th.large-12 { + width: 564px; + padding-left: 8px; + padding-right: 8px; +} + +td.large-12.first, +th.large-12.first { + padding-left: 16px; +} + +td.large-12.last, +th.large-12.last { + padding-right: 16px; +} + +.collapse>tbody>tr>td.large-12, +.collapse>tbody>tr>th.large-12 { + padding-right: 0; + padding-left: 0; + width: 580px; +} + +.collapse td.large-12.first, +.collapse th.large-12.first, +.collapse td.large-12.last, +.collapse th.large-12.last { + width: 588px; +} + +td.large-12 center, +th.large-12 center { + min-width: 532px; +} + +.body .columns td.large-12, +.body .column td.large-12, +.body .columns th.large-12, +.body .column th.large-12 { + width: 100%; +} + +td.large-offset-1, +td.large-offset-1.first, +td.large-offset-1.last, +th.large-offset-1, +th.large-offset-1.first, +th.large-offset-1.last { + padding-left: 64.33333px; +} + +td.large-offset-2, +td.large-offset-2.first, +td.large-offset-2.last, +th.large-offset-2, +th.large-offset-2.first, +th.large-offset-2.last { + padding-left: 112.66667px; +} + +td.large-offset-3, +td.large-offset-3.first, +td.large-offset-3.last, +th.large-offset-3, +th.large-offset-3.first, +th.large-offset-3.last { + padding-left: 161px; +} + +td.large-offset-4, +td.large-offset-4.first, +td.large-offset-4.last, +th.large-offset-4, +th.large-offset-4.first, +th.large-offset-4.last { + padding-left: 209.33333px; +} + +td.large-offset-5, +td.large-offset-5.first, +td.large-offset-5.last, +th.large-offset-5, +th.large-offset-5.first, +th.large-offset-5.last { + padding-left: 257.66667px; +} + +td.large-offset-6, +td.large-offset-6.first, +td.large-offset-6.last, +th.large-offset-6, +th.large-offset-6.first, +th.large-offset-6.last { + padding-left: 306px; +} + +td.large-offset-7, +td.large-offset-7.first, +td.large-offset-7.last, +th.large-offset-7, +th.large-offset-7.first, +th.large-offset-7.last { + padding-left: 354.33333px; +} + +td.large-offset-8, +td.large-offset-8.first, +td.large-offset-8.last, +th.large-offset-8, +th.large-offset-8.first, +th.large-offset-8.last { + padding-left: 402.66667px; +} + +td.large-offset-9, +td.large-offset-9.first, +td.large-offset-9.last, +th.large-offset-9, +th.large-offset-9.first, +th.large-offset-9.last { + padding-left: 451px; +} + +td.large-offset-10, +td.large-offset-10.first, +td.large-offset-10.last, +th.large-offset-10, +th.large-offset-10.first, +th.large-offset-10.last { + padding-left: 499.33333px; +} + +td.large-offset-11, +td.large-offset-11.first, +td.large-offset-11.last, +th.large-offset-11, +th.large-offset-11.first, +th.large-offset-11.last { + padding-left: 547.66667px; +} + +td.expander, +th.expander { + visibility: hidden; + width: 0; + padding: 0 !important; +} + +table.container.radius { + border-radius: 0; + border-collapse: separate; +} + +.block-grid { + width: 100%; + max-width: 580px; +} + +.block-grid td { + display: inline-block; + padding: 8px; +} + +.up-2 td { + width: 274px !important; +} + +.up-3 td { + width: 177px !important; +} + +.up-4 td { + width: 129px !important; +} + +.up-5 td { + width: 100px !important; +} + +.up-6 td { + width: 80px !important; +} + +.up-7 td { + width: 66px !important; +} + +.up-8 td { + width: 56px !important; +} + +table.text-center, +th.text-center, +td.text-center, +h1.text-center, +h2.text-center, +h3.text-center, +h4.text-center, +h5.text-center, +h6.text-center, +p.text-center, +span.text-center { + text-align: center; +} + +table.text-left, +th.text-left, +td.text-left, +h1.text-left, +h2.text-left, +h3.text-left, +h4.text-left, +h5.text-left, +h6.text-left, +p.text-left, +span.text-left { + text-align: left; +} + +table.text-right, +th.text-right, +td.text-right, +h1.text-right, +h2.text-right, +h3.text-right, +h4.text-right, +h5.text-right, +h6.text-right, +p.text-right, +span.text-right { + text-align: right; +} + +span.text-center { + display: block; + width: 100%; + text-align: center; +} + +@media only screen and (max-width: 596px) { + .small-float-center { + margin: 0 auto !important; + float: none !important; + text-align: center !important; + } + .small-text-center { + text-align: center !important; + } + .small-text-left { + text-align: left !important; + } + .small-text-right { + text-align: right !important; + } +} + +img.float-left { + float: left; + text-align: left; +} + +img.float-right { + float: right; + text-align: right; +} + +img.float-center, +img.text-center { + margin: 0 auto; + Margin: 0 auto; + float: none; + text-align: center; +} + +table.float-center, +td.float-center, +th.float-center { + margin: 0 auto; + Margin: 0 auto; + float: none; + text-align: center; +} + +.hide-for-large { + display: none !important; + mso-hide: all; + overflow: hidden; + max-height: 0; + font-size: 0; + width: 0; + line-height: 0; +} + +@media only screen and (max-width: 596px) { + .hide-for-large { + display: block !important; + width: auto !important; + overflow: visible !important; + max-height: none !important; + font-size: inherit !important; + line-height: inherit !important; + } +} + +table.body table.container .hide-for-large * { + mso-hide: all; +} + +@media only screen and (max-width: 596px) { + table.body table.container .hide-for-large, + table.body table.container .row.hide-for-large { + display: table !important; + width: 100% !important; + } +} + +@media only screen and (max-width: 596px) { + table.body table.container .callout-inner.hide-for-large { + display: table-cell !important; + width: 100% !important; + } +} + +@media only screen and (max-width: 596px) { + table.body table.container .show-for-large { + display: none !important; + width: 0; + mso-hide: all; + overflow: hidden; + } +} + +body, +table.body, +h1, +h2, +h3, +h4, +h5, +h6, +p, +td, +th, +a { + color: #0a0a0a; + font-family: Helvetica, Arial, sans-serif; + font-weight: normal; + padding: 0; + margin: 0; + Margin: 0; + text-align: left; + line-height: 1.3; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + color: inherit; + word-wrap: normal; + font-family: Helvetica, Arial, sans-serif; + font-weight: normal; + margin-bottom: 10px; + Margin-bottom: 10px; +} + +h1 { + font-size: 34px; +} + +h2 { + font-size: 30px; +} + +h3 { + font-size: 28px; +} + +h4 { + font-size: 24px; +} + +h5 { + font-size: 20px; +} + +h6 { + font-size: 18px; +} + +body, +table.body, +p, +td, +th { + font-size: 16px; + line-height: 1.3; +} + +p { + margin-bottom: 10px; + Margin-bottom: 10px; +} + +p.lead { + font-size: 20px; + line-height: 1.6; +} + +p.subheader { + margin-top: 4px; + margin-bottom: 8px; + Margin-top: 4px; + Margin-bottom: 8px; + font-weight: normal; + line-height: 1.4; + color: #8a8a8a; +} + +small { + font-size: 80%; + color: #cacaca; +} + +a { + color: #2199e8; + text-decoration: none; +} + +a:hover { + color: #147dc2; +} + +a:active { + color: #147dc2; +} + +a:visited { + color: #2199e8; +} + +h1 a, +h1 a:visited, +h2 a, +h2 a:visited, +h3 a, +h3 a:visited, +h4 a, +h4 a:visited, +h5 a, +h5 a:visited, +h6 a, +h6 a:visited { + color: #2199e8; +} + +pre { + background: #f3f3f3; + margin: 30px 0; + Margin: 30px 0; +} + +pre code { + color: #cacaca; +} + +pre code span.callout { + color: #8a8a8a; + font-weight: bold; +} + +pre code span.callout-strong { + color: #ff6908; + font-weight: bold; +} + +table.hr { + width: 100%; +} + +table.hr th { + height: 0; + max-width: 580px; + border-top: 0; + border-right: 0; + border-bottom: 1px solid #0a0a0a; + border-left: 0; + margin: 20px auto; + Margin: 20px auto; + clear: both; +} + +.stat { + font-size: 40px; + line-height: 1; +} + +p+.stat { + margin-top: -16px; + Margin-top: -16px; +} + +span.preheader { + display: none !important; + visibility: hidden; + mso-hide: all !important; + font-size: 1px; + color: #f3f3f3; + line-height: 1px; + max-height: 0px; + max-width: 0px; + opacity: 0; + overflow: hidden; +} + +table.button { + width: auto; + margin: 0 0 16px 0; + Margin: 0 0 16px 0; +} + +table.button table td { + text-align: left; + color: #fefefe; + background: #2199e8; + border: 2px solid #2199e8; +} + +table.button table td a { + font-family: Helvetica, Arial, sans-serif; + font-size: 16px; + font-weight: bold; + color: #fefefe; + text-decoration: none; + display: inline-block; + padding: 8px 16px 8px 16px; + border: 0 solid #2199e8; + border-radius: 3px; +} + +table.button.radius table td { + border-radius: 3px; + border: none; +} + +table.button.rounded table td { + border-radius: 500px; + border: none; +} + +table.button:hover table tr td a, +table.button:active table tr td a, +table.button table tr td a:visited, +table.button.tiny:hover table tr td a, +table.button.tiny:active table tr td a, +table.button.tiny table tr td a:visited, +table.button.small:hover table tr td a, +table.button.small:active table tr td a, +table.button.small table tr td a:visited, +table.button.large:hover table tr td a, +table.button.large:active table tr td a, +table.button.large table tr td a:visited { + color: #fefefe; +} + +table.button.tiny table td, +table.button.tiny table a { + padding: 4px 8px 4px 8px; +} + +table.button.tiny table a { + font-size: 10px; + font-weight: normal; +} + +table.button.small table td, +table.button.small table a { + padding: 5px 10px 5px 10px; + font-size: 12px; +} + +table.button.large table a { + padding: 10px 20px 10px 20px; + font-size: 20px; +} + +table.button.expand, +table.button.expanded { + width: 100% !important; +} + +table.button.expand table, +table.button.expanded table { + width: 100%; +} + +table.button.expand table a, +table.button.expanded table a { + text-align: center; + width: 100%; + padding-left: 0; + padding-right: 0; +} + +table.button.expand center, +table.button.expanded center { + min-width: 0; +} + +table.button:hover table td, +table.button:visited table td, +table.button:active table td { + background: #147dc2; + color: #fefefe; +} + +table.button:hover table a, +table.button:visited table a, +table.button:active table a { + border: 0 solid #147dc2; +} + +table.button.secondary table td { + background: #777777; + color: #fefefe; + border: 0px solid #777777; +} + +table.button.secondary table a { + color: #fefefe; + border: 0 solid #777777; +} + +table.button.secondary:hover table td { + background: #919191; + color: #fefefe; +} + +table.button.secondary:hover table a { + border: 0 solid #919191; +} + +table.button.secondary:hover table td a { + color: #fefefe; +} + +table.button.secondary:active table td a { + color: #fefefe; +} + +table.button.secondary table td a:visited { + color: #fefefe; +} + +table.button.success table td { + background: #3adb76; + border: 0px solid #3adb76; +} + +table.button.success table a { + border: 0 solid #3adb76; +} + +table.button.success:hover table td { + background: #23bf5d; +} + +table.button.success:hover table a { + border: 0 solid #23bf5d; +} + +table.button.alert table td { + background: #ec5840; + border: 0px solid #ec5840; +} + +table.button.alert table a { + border: 0 solid #ec5840; +} + +table.button.alert:hover table td { + background: #e23317; +} + +table.button.alert:hover table a { + border: 0 solid #e23317; +} + +table.button.warning table td { + background: #ffae00; + border: 0px solid #ffae00; +} + +table.button.warning table a { + border: 0px solid #ffae00; +} + +table.button.warning:hover table td { + background: #cc8b00; +} + +table.button.warning:hover table a { + border: 0px solid #cc8b00; +} + +table.callout { + margin-bottom: 16px; + Margin-bottom: 16px; +} + +th.callout-inner { + width: 100%; + border: 1px solid #cbcbcb; + padding: 10px; + background: #fefefe; +} + +th.callout-inner.primary { + background: #def0fc; + border: 1px solid #444444; + color: #0a0a0a; +} + +th.callout-inner.secondary { + background: #ebebeb; + border: 1px solid #444444; + color: #0a0a0a; +} + +th.callout-inner.success { + background: #e1faea; + border: 1px solid #1b9448; + color: #fefefe; +} + +th.callout-inner.warning { + background: #fff3d9; + border: 1px solid #996800; + color: #fefefe; +} + +th.callout-inner.alert { + background: #fce6e2; + border: 1px solid #b42912; + color: #fefefe; +} + +.thumbnail { + border: solid 4px #fefefe; + box-shadow: 0 0 0 1px rgba(10, 10, 10, 0.2); + display: inline-block; + line-height: 0; + max-width: 100%; + transition: box-shadow 200ms ease-out; + border-radius: 3px; + margin-bottom: 16px; +} + +.thumbnail:hover, +.thumbnail:focus { + box-shadow: 0 0 6px 1px rgba(33, 153, 232, 0.5); +} + +table.menu { + width: 580px; +} + +table.menu td.menu-item, +table.menu th.menu-item { + padding: 10px; + padding-right: 10px; +} + +table.menu td.menu-item a, +table.menu th.menu-item a { + color: #2199e8; +} + +table.menu.vertical td.menu-item, +table.menu.vertical th.menu-item { + padding: 10px; + padding-right: 0; + display: block; +} + +table.menu.vertical td.menu-item a, +table.menu.vertical th.menu-item a { + width: 100%; +} + +table.menu.vertical td.menu-item table.menu.vertical td.menu-item, +table.menu.vertical td.menu-item table.menu.vertical th.menu-item, +table.menu.vertical th.menu-item table.menu.vertical td.menu-item, +table.menu.vertical th.menu-item table.menu.vertical th.menu-item { + padding-left: 10px; +} + +table.menu.text-center a { + text-align: center; +} + +.menu[align="center"] { + width: auto !important; +} + +body.outlook p { + display: inline !important; +} + +@media only screen and (max-width: 596px) { + table.body img { + width: auto; + height: auto; + } + table.body center { + min-width: 0 !important; + } + table.body .container { + width: 95% !important; + } + table.body .columns, + table.body .column { + height: auto !important; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + padding-left: 16px !important; + padding-right: 16px !important; + } + table.body .columns .column, + table.body .columns .columns, + table.body .column .column, + table.body .column .columns { + padding-left: 0 !important; + padding-right: 0 !important; + } + table.body .collapse .columns, + table.body .collapse .column { + padding-left: 0 !important; + padding-right: 0 !important; + } + td.small-1, + th.small-1 { + display: inline-block !important; + width: 8.33333% !important; + } + td.small-2, + th.small-2 { + display: inline-block !important; + width: 16.66667% !important; + } + td.small-3, + th.small-3 { + display: inline-block !important; + width: 25% !important; + } + td.small-4, + th.small-4 { + display: inline-block !important; + width: 33.33333% !important; + } + td.small-5, + th.small-5 { + display: inline-block !important; + width: 41.66667% !important; + } + td.small-6, + th.small-6 { + display: inline-block !important; + width: 50% !important; + } + td.small-7, + th.small-7 { + display: inline-block !important; + width: 58.33333% !important; + } + td.small-8, + th.small-8 { + display: inline-block !important; + width: 66.66667% !important; + } + td.small-9, + th.small-9 { + display: inline-block !important; + width: 75% !important; + } + td.small-10, + th.small-10 { + display: inline-block !important; + width: 83.33333% !important; + } + td.small-11, + th.small-11 { + display: inline-block !important; + width: 91.66667% !important; + } + td.small-12, + th.small-12 { + display: inline-block !important; + width: 100% !important; + } + .columns td.small-12, + .column td.small-12, + .columns th.small-12, + .column th.small-12 { + display: block !important; + width: 100% !important; + } + table.body td.small-offset-1, + table.body th.small-offset-1 { + margin-left: 8.33333% !important; + Margin-left: 8.33333% !important; + } + table.body td.small-offset-2, + table.body th.small-offset-2 { + margin-left: 16.66667% !important; + Margin-left: 16.66667% !important; + } + table.body td.small-offset-3, + table.body th.small-offset-3 { + margin-left: 25% !important; + Margin-left: 25% !important; + } + table.body td.small-offset-4, + table.body th.small-offset-4 { + margin-left: 33.33333% !important; + Margin-left: 33.33333% !important; + } + table.body td.small-offset-5, + table.body th.small-offset-5 { + margin-left: 41.66667% !important; + Margin-left: 41.66667% !important; + } + table.body td.small-offset-6, + table.body th.small-offset-6 { + margin-left: 50% !important; + Margin-left: 50% !important; + } + table.body td.small-offset-7, + table.body th.small-offset-7 { + margin-left: 58.33333% !important; + Margin-left: 58.33333% !important; + } + table.body td.small-offset-8, + table.body th.small-offset-8 { + margin-left: 66.66667% !important; + Margin-left: 66.66667% !important; + } + table.body td.small-offset-9, + table.body th.small-offset-9 { + margin-left: 75% !important; + Margin-left: 75% !important; + } + table.body td.small-offset-10, + table.body th.small-offset-10 { + margin-left: 83.33333% !important; + Margin-left: 83.33333% !important; + } + table.body td.small-offset-11, + table.body th.small-offset-11 { + margin-left: 91.66667% !important; + Margin-left: 91.66667% !important; + } + table.body table.columns td.expander, + table.body table.columns th.expander { + display: none !important; + } + table.body .right-text-pad, + table.body .text-pad-right { + padding-left: 10px !important; + } + table.body .left-text-pad, + table.body .text-pad-left { + padding-right: 10px !important; + } + table.menu { + width: 100% !important; + } + table.menu td, + table.menu th { + width: auto !important; + display: inline-block !important; + } + table.menu.vertical td, + table.menu.vertical th, + table.menu.small-vertical td, + table.menu.small-vertical th { + display: block !important; + } + table.menu[align="center"] { + width: auto !important; + } + table.button.small-expand, + table.button.small-expanded { + width: 100% !important; + } + table.button.small-expand table, + table.button.small-expanded table { + width: 100%; + } + table.button.small-expand table a, + table.button.small-expanded table a { + text-align: center !important; + width: 100% !important; + padding-left: 0 !important; + padding-right: 0 !important; + } + table.button.small-expand center, + table.button.small-expanded center { + min-width: 0; + } +} diff --git a/lib/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/body.html.twig b/lib/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/body.html.twig new file mode 100644 index 0000000000..0a52d36b37 --- /dev/null +++ b/lib/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/body.html.twig @@ -0,0 +1,67 @@ +{% apply inky_to_html|inline_css %} + + + + + + + + + + + + {% block lead %} + {% if importance is not null %}{{ importance|upper }}{% endif %} +

                  + {{ email.subject }} +

                  + {% endblock %} + + {% block content %} + {% if markdown %} + {{ include('@email/zurb_2/notification/content_markdown.html.twig') }} + {% else %} + {{ (raw ? content|raw : content)|nl2br }} + {% endif %} + {% endblock %} + + {% block action %} + {% if action_url %} + + + {% endif %} + {% endblock %} + + {% block exception %} + {% if exception %} + +

                  Exception stack trace attached.

                  + {% endif %} + {% endblock %} +
                  +
                  + + + + {% block footer %} + {% if footer_text is defined and footer_text is not null %} + + + {% block footer_content %} +

                  {{ footer_text }}

                  + {% endblock %} +
                  +
                  + {% endif %} + {% endblock %} +
                  +
                  +
                  + + +{% endapply %} diff --git a/lib/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/body.txt.twig b/lib/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/body.txt.twig new file mode 100644 index 0000000000..c98bb08a74 --- /dev/null +++ b/lib/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/body.txt.twig @@ -0,0 +1,20 @@ +{% block lead %} +{{ email.subject }} +{% endblock %} + +{% block content %} +{{ content }} +{% endblock %} + +{% block action %} +{% if action_url %} +{{ action_text }}: {{ action_url }} +{% endif %} +{% endblock %} + +{% block exception %} +{% if exception %} +Exception stack trace attached. +{{ exception }} +{% endif %} +{% endblock %} diff --git a/lib/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/content_markdown.html.twig b/lib/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/content_markdown.html.twig new file mode 100644 index 0000000000..120b2caad9 --- /dev/null +++ b/lib/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/content_markdown.html.twig @@ -0,0 +1 @@ +{{ content|markdown_to_html }} diff --git a/lib/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/local.css b/lib/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/local.css new file mode 100644 index 0000000000..2e68dcd3ef --- /dev/null +++ b/lib/symfony/twig-bridge/Resources/views/Email/zurb_2/notification/local.css @@ -0,0 +1,19 @@ +body { + background: #f3f3f3; +} + +.wrapper.secondary { + background: #f3f3f3; +} + +.container.body_alert { + border-top: 8px solid #ec5840; +} + +.container.body_warning { + border-top: 8px solid #ffae00; +} + +.container.body_default { + border-top: 8px solid #aaaaaa; +} diff --git a/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig b/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig index d6b08f7637..49cd804398 100644 --- a/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig +++ b/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig @@ -23,17 +23,22 @@ col-sm-2 {# Rows #} {% block form_row -%} -
                  + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + {{- form_label(form) -}}
                  - {{- form_widget(form) -}} + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} {{- form_errors(form) -}}
                  {##}
                  {%- endblock form_row %} {% block submit_row -%} -
                  {#--#} + {#--#}
                  {#--#}
                  {{- form_widget(form) -}} @@ -42,7 +47,7 @@ col-sm-2 {%- endblock submit_row %} {% block reset_row -%} -
                  {#--#} + {#--#}
                  {#--#}
                  {{- form_widget(form) -}} @@ -55,10 +60,11 @@ col-sm-10 {%- endblock form_group_class %} {% block checkbox_row -%} -
                  {#--#} + {#--#}
                  {#--#}
                  {{- form_widget(form) -}} + {{- form_help(form) -}} {{- form_errors(form) -}}
                  {#--#}
                  diff --git a/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_3_layout.html.twig b/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_3_layout.html.twig index 708e149bce..865f9078a9 100644 --- a/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_3_layout.html.twig +++ b/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_3_layout.html.twig @@ -54,6 +54,11 @@ {%- endif -%} {%- endblock radio_widget %} +{% block choice_widget_collapsed -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) -%} + {{- parent() -}} +{%- endblock choice_widget_collapsed %} + {# Labels #} {% block form_label -%} @@ -85,9 +90,6 @@ {%- if required -%} {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) -%} {%- endif -%} - {%- if parent_label_class is defined -%} - {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ parent_label_class)|trim}) -%} - {%- endif -%} {%- if label is not same as(false) and label is empty -%} {%- if label_format is not empty -%} {%- set label = label_format|replace({ @@ -99,7 +101,22 @@ {%- endif -%} {%- endif -%} - {{- widget|raw }} {{ label is not same as(false) ? (translation_domain is same as(false) ? label : label|trans({}, translation_domain)) -}} + {#- if statement must be kept on the same line, to force the space between widget and label -#} + {{- widget|raw }} {% if label is not same as(false) -%} + {%- if translation_domain is same as(false) -%} + {%- if label_html is same as(false) -%} + {{ label -}} + {%- else -%} + {{ label|raw -}} + {%- endif -%} + {%- else -%} + {%- if label_html is same as(false) -%} + {{ label|trans(label_translation_parameters, translation_domain) -}} + {%- else -%} + {{ label|trans(label_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} + {%- endif -%} {%- endif -%} {%- endblock checkbox_radio_label %} @@ -107,15 +124,20 @@ {# Rows #} {% block form_row -%} -
                  + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + {{- form_label(form) }} {# -#} - {{ form_widget(form) }} {# -#} + {{ form_widget(form, widget_attr) }} {# -#} + {{- form_help(form) -}} {{ form_errors(form) }} {# -#}
                  {# -#} {%- endblock form_row %} {% block button_row -%} -
                  + {{- form_widget(form) -}}
                  {%- endblock button_row %} @@ -141,15 +163,17 @@ {%- endblock datetime_row %} {% block checkbox_row -%} -
                  + {{- form_widget(form) -}} + {{- form_help(form) -}} {{- form_errors(form) -}}
                  {%- endblock checkbox_row %} {% block radio_row -%} -
                  + {{- form_widget(form) -}} + {{- form_help(form) -}} {{- form_errors(form) -}}
                  {%- endblock radio_row %} @@ -167,3 +191,26 @@ {% if form is not rootform %}{% else %}
                  {% endif %} {%- endif %} {%- endblock form_errors %} + +{# Help #} + +{% block form_help -%} + {%- if help is not empty -%} + {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' help-block')|trim}) -%} + + {%- if translation_domain is same as(false) -%} + {%- if help_html is same as(false) -%} + {{- help -}} + {%- else -%} + {{- help|raw -}} + {%- endif -%} + {%- else -%} + {%- if help_html is same as(false) -%} + {{- help|trans(help_translation_parameters, translation_domain) -}} + {%- else -%} + {{- help|trans(help_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} + + {%- endif -%} +{%- endblock form_help %} diff --git a/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_4_horizontal_layout.html.twig b/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_4_horizontal_layout.html.twig index 5673cf2127..990b324cb0 100644 --- a/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_4_horizontal_layout.html.twig +++ b/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_4_horizontal_layout.html.twig @@ -24,28 +24,39 @@ col-sm-2 {%- if expanded is defined and expanded -%} {{ block('fieldset_form_row') }} {%- else -%} -
                  + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + {{- form_label(form) -}}
                  - {{- form_widget(form) -}} + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}}
                  {##}
                  {%- endif -%} {%- endblock form_row %} {% block fieldset_form_row -%} -
                  + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} +
                  {{- form_label(form) -}}
                  - {{- form_widget(form) -}} + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}}
                  {##}
                  {%- endblock fieldset_form_row %} {% block submit_row -%} -
                  {#--#} + {#--#}
                  {#--#}
                  {{- form_widget(form) -}} @@ -54,7 +65,7 @@ col-sm-2 {%- endblock submit_row %} {% block reset_row -%} -
                  {#--#} + {#--#}
                  {#--#}
                  {{- form_widget(form) -}} @@ -67,10 +78,11 @@ col-sm-10 {%- endblock form_group_class %} {% block checkbox_row -%} -
                  {#--#} + {#--#}
                  {#--#}
                  {{- form_widget(form) -}} + {{- form_help(form) -}}
                  {#--#}
                  {%- endblock checkbox_row %} diff --git a/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_4_layout.html.twig b/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_4_layout.html.twig index b13d7ed9ca..0e80840541 100644 --- a/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_4_layout.html.twig +++ b/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_4_layout.html.twig @@ -106,18 +106,44 @@ {%- endblock dateinterval_widget %} {% block percent_widget -%} -
                  - {{- block('form_widget_simple') -}} -
                  - % + {%- if symbol -%} +
                  + {{- block('form_widget_simple') -}} +
                  + {{ symbol|default('%') }} +
                  -
                  + {%- else -%} + {{- block('form_widget_simple') -}} + {%- endif -%} {%- endblock percent_widget %} +{% block file_widget -%} + <{{ element|default('div') }} class="custom-file"> + {%- set type = type|default('file') -%} + {%- set input_lang = 'en' -%} + {% if app is defined and app.request is defined %}{%- set input_lang = app.request.locale -%}{%- endif -%} + {%- set attr = {lang: input_lang} | merge(attr) -%} + {{- block('form_widget_simple') -}} + {%- set label_attr = label_attr|merge({ class: (label_attr.class|default('') ~ ' custom-file-label')|trim })|filter((value, key) => key != 'id') -%} + + +{% endblock %} + {% block form_widget_simple -%} - {% if type is not defined or type != 'hidden' %} - {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control' ~ (type|default('') == 'file' ? '-file' : ''))|trim}) -%} - {% endif %} + {%- if type is not defined or type != 'hidden' -%} + {%- set className = ' form-control' -%} + {%- if type|default('') == 'file' -%} + {%- set className = ' custom-file-input' -%} + {%- elseif type|default('') == 'range' -%} + {%- set className = ' form-control-range' -%} + {%- endif -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ className)|trim}) -%} + {%- endif -%} {%- if type is defined and (type == 'range' or type == 'color') %} {# Attribute "required" is not supported #} {%- set required = false -%} @@ -125,12 +151,12 @@ {{- parent() -}} {%- endblock form_widget_simple %} -{%- block widget_attributes -%} - {%- if not valid %} +{% block widget_attributes -%} + {%- if not valid -%} {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) %} - {% endif -%} + {%- endif -%} {{ parent() }} -{%- endblock widget_attributes -%} +{%- endblock widget_attributes %} {% block button_widget -%} {%- set attr = attr|merge({class: (attr.class|default('btn-secondary') ~ ' btn')|trim}) -%} @@ -149,6 +175,11 @@
                  {{- form_label(form, null, { widget: parent() }) -}}
                  + {%- elseif 'switch-custom' in parent_label_class -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' custom-control-input')|trim}) -%} +
                  + {{- form_label(form, null, { widget: parent() }) -}} +
                  {%- else -%} {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%}
                  @@ -172,6 +203,11 @@ {%- endif -%} {%- endblock radio_widget %} +{% block choice_widget_collapsed -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) -%} + {{- parent() -}} +{%- endblock choice_widget_collapsed %} + {% block choice_widget_expanded -%}
                  {%- for child in form %} @@ -207,7 +243,21 @@ {% set label = name|humanize %} {%- endif -%} {%- endif -%} - <{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}>{{ translation_domain is same as(false) ? label : label|trans({}, translation_domain) }}{% block form_label_errors %}{{- form_errors(form) -}}{% endblock form_label_errors %} + <{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}> + {%- if translation_domain is same as(false) -%} + {%- if label_html is same as(false) -%} + {{- label -}} + {%- else -%} + {{- label|raw -}} + {%- endif -%} + {%- else -%} + {%- if label_html is same as(false) -%} + {{- label|trans(label_translation_parameters, translation_domain) -}} + {%- else -%} + {{- label|trans(label_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} + {% block form_label_errors %}{{- form_errors(form) -}}{% endblock form_label_errors %} {%- else -%} {%- if errors|length > 0 -%}
                  @@ -220,8 +270,8 @@ {% block checkbox_radio_label -%} {#- Do not display the label if widget is not defined in order to prevent double label rendering -#} {%- if widget is defined -%} - {% set is_parent_custom = parent_label_class is defined and ('checkbox-custom' in parent_label_class or 'radio-custom' in parent_label_class) %} - {% set is_custom = label_attr.class is defined and ('checkbox-custom' in label_attr.class or 'radio-custom' in label_attr.class) %} + {% set is_parent_custom = parent_label_class is defined and ('checkbox-custom' in parent_label_class or 'radio-custom' in parent_label_class or 'switch-custom' in parent_label_class) %} + {% set is_custom = label_attr.class is defined and ('checkbox-custom' in label_attr.class or 'radio-custom' in label_attr.class or 'switch-custom' in label_attr.class) %} {%- if is_parent_custom or is_custom -%} {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' custom-control-label')|trim}) -%} {%- else %} @@ -233,9 +283,6 @@ {%- if required -%} {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) -%} {%- endif -%} - {%- if parent_label_class is defined -%} - {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ parent_label_class)|replace({'checkbox-inline': '', 'radio-inline': '', 'checkbox-custom': '', 'radio-custom': ''})|trim}) -%} - {%- endif -%} {%- if label is not same as(false) and label is empty -%} {%- if label_format is not empty -%} {%- set label = label_format|replace({ @@ -249,7 +296,21 @@ {{ widget|raw }} - {{- label is not same as(false) ? (translation_domain is same as(false) ? label : label|trans({}, translation_domain)) -}} + {%- if label is not same as(false) -%} + {%- if translation_domain is same as(false) -%} + {%- if label_html is same as(false) -%} + {{- label -}} + {%- else -%} + {{- label|raw -}} + {%- endif -%} + {%- else -%} + {%- if label_html is same as(false) -%} + {{- label|trans(label_translation_parameters, translation_domain) -}} + {%- else -%} + {{- label|trans(label_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} + {%- endif -%} {{- form_errors(form) -}} {%- endif -%} @@ -261,9 +322,14 @@ {%- if compound is defined and compound -%} {%- set element = 'fieldset' -%} {%- endif -%} - <{{ element|default('div') }} class="form-group"> + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + <{{ element|default('div') }}{% with {attr: row_attr|merge({class: (row_attr.class|default('') ~ ' form-group')|trim})} %}{{ block('attributes') }}{% endwith %}> {{- form_label(form) -}} - {{- form_widget(form) -}} + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} {%- endblock form_row %} @@ -280,3 +346,26 @@ {%- endif %} {%- endblock form_errors %} + +{# Help #} + +{% block form_help -%} + {%- if help is not empty -%} + {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' form-text text-muted')|trim}) -%} + + {%- if translation_domain is same as(false) -%} + {%- if help_html is same as(false) -%} + {{- help -}} + {%- else -%} + {{- help|raw -}} + {%- endif -%} + {%- else -%} + {%- if help_html is same as(false) -%} + {{- help|trans(help_translation_parameters, translation_domain) -}} + {%- else -%} + {{- help|trans(help_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} + + {%- endif -%} +{%- endblock form_help %} diff --git a/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_5_horizontal_layout.html.twig b/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_5_horizontal_layout.html.twig new file mode 100644 index 0000000000..3c24166d48 --- /dev/null +++ b/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_5_horizontal_layout.html.twig @@ -0,0 +1,130 @@ +{% use "bootstrap_5_layout.html.twig" %} + +{# Labels #} + +{% block form_label -%} + {%- if label is same as(false) -%} +
                  + {%- else -%} + {%- set row_class = row_class|default(row_attr.class|default('')) -%} + {%- if 'form-floating' not in row_class and 'input-group' not in row_class -%} + {%- if expanded is not defined or not expanded -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' col-form-label')|trim}) -%} + {%- endif -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ block('form_label_class'))|trim}) -%} + {%- endif -%} + {{- parent() -}} + {%- endif -%} +{%- endblock form_label %} + +{% block form_label_class -%} + col-sm-2 +{%- endblock form_label_class %} + +{# Rows #} + +{% block form_row -%} + {%- if expanded is defined and expanded -%} + {{ block('fieldset_form_row') }} + {%- else -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + {%- set row_class = row_class|default(row_attr.class|default('mb-3')) -%} + {%- set is_form_floating = is_form_floating|default('form-floating' in row_class) -%} + {%- set is_input_group = is_input_group|default('input-group' in row_class) -%} + {#- Remove behavior class from the main container -#} + {%- set row_class = row_class|replace({'form-floating': '', 'input-group': ''}) -%} + + {%- if is_form_floating or is_input_group -%} +
                  +
                  + {%- if is_form_floating -%} +
                  + {{- form_widget(form, widget_attr) -}} + {{- form_label(form) -}} +
                  + {%- elseif is_input_group -%} +
                  + {{- form_label(form) -}} + {{- form_widget(form, widget_attr) -}} + {#- Hack to properly display help with input group -#} + {{- form_help(form) -}} +
                  + {%- endif -%} + {%- if not is_input_group -%} + {{- form_help(form) -}} + {%- endif -%} + {{- form_errors(form) -}} +
                  + {%- else -%} + {{- form_label(form) -}} +
                  + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} +
                  + {%- endif -%} + {##}
                  + {%- endif -%} +{%- endblock form_row %} + +{% block fieldset_form_row -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + +
                  + {{- form_label(form) -}} +
                  + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} +
                  +
                  + +{%- endblock fieldset_form_row %} + +{% block submit_row -%} + {#--#} +
                  {#--#} +
                  + {{- form_widget(form) -}} +
                  {#--#} +
                  +{%- endblock submit_row %} + +{% block reset_row -%} + {#--#} +
                  {#--#} +
                  + {{- form_widget(form) -}} +
                  {#--#} +
                  +{%- endblock reset_row %} + +{% block button_row -%} + {#--#} +
                  {#--#} +
                  + {{- form_widget(form) -}} +
                  {#--#} +
                  +{%- endblock button_row %} + +{% block checkbox_row -%} + {#--#} +
                  {#--#} +
                  + {{- form_widget(form) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}} +
                  {#--#} +
                  +{%- endblock checkbox_row %} + +{% block form_group_class -%} + col-sm-10 +{%- endblock form_group_class %} diff --git a/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_5_layout.html.twig b/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_5_layout.html.twig new file mode 100644 index 0000000000..eef6f606ed --- /dev/null +++ b/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_5_layout.html.twig @@ -0,0 +1,374 @@ +{% use "bootstrap_base_layout.html.twig" %} + +{# Widgets #} + +{% block money_widget -%} + {%- set prepend = not (money_pattern starts with '{{') -%} + {%- set append = not (money_pattern ends with '}}') -%} + {%- if prepend or append -%} +
                  + {%- if prepend -%} + {{ money_pattern|form_encode_currency }} + {%- endif -%} + {{- block('form_widget_simple') -}} + {%- if append -%} + {{ money_pattern|form_encode_currency }} + {%- endif -%} +
                  + {%- else -%} + {{- block('form_widget_simple') -}} + {%- endif -%} +{%- endblock money_widget %} + +{% block date_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {% if not valid %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) -%} + {% set valid = true %} + {% endif %} + {%- if datetime is not defined or not datetime -%} +
                  + {%- endif %} + {%- if label is not same as(false) -%} +
                  + {{- form_label(form.year) -}} + {{- form_label(form.month) -}} + {{- form_label(form.day) -}} +
                  + {%- endif -%} +
                  + {{- date_pattern|replace({ + '{{ year }}': form_widget(form.year), + '{{ month }}': form_widget(form.month), + '{{ day }}': form_widget(form.day), + })|raw -}} +
                  + {%- if datetime is not defined or not datetime -%} +
                  + {%- endif -%} + {%- endif -%} +{%- endblock date_widget %} + +{% block time_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {% if not valid %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) -%} + {% set valid = true %} + {% endif %} + {%- if datetime is not defined or false == datetime -%} +
                  + {%- endif -%} + {%- if label is not same as(false) -%} +
                  + {{- form_label(form.hour) -}} + {%- if with_minutes -%}{{ form_label(form.minute) }}{%- endif -%} + {%- if with_seconds -%}{{ form_label(form.second) }}{%- endif -%} +
                  + {%- endif -%} + {% if with_minutes or with_seconds %} +
                  + {% endif %} + {{- form_widget(form.hour) -}} + {%- if with_minutes -%} + : + {{- form_widget(form.minute) -}} + {%- endif -%} + {%- if with_seconds -%} + : + {{- form_widget(form.second) -}} + {%- endif -%} + {% if with_minutes or with_seconds %} +
                  + {% endif %} + {%- if datetime is not defined or false == datetime -%} +
                  + {%- endif -%} + {%- endif -%} +{%- endblock time_widget %} + +{% block datetime_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {% if not valid %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) -%} + {% set valid = true %} + {% endif %} +
                  + {{- form_widget(form.date, { datetime: true } ) -}} + {{- form_errors(form.date) -}} + {{- form_widget(form.time, { datetime: true } ) -}} + {{- form_errors(form.time) -}} +
                  + {%- endif -%} +{%- endblock datetime_widget %} + +{% block dateinterval_widget -%} + {%- if widget == 'single_text' -%} + {{- block('form_widget_simple') -}} + {%- else -%} + {% if not valid %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) -%} + {% set valid = true %} + {% endif %} +
                  + {%- if with_years -%} +
                  + {{ form_label(form.years) }} + {{ form_widget(form.years) }} +
                  + {%- endif -%} + {%- if with_months -%} +
                  + {{ form_label(form.months) }} + {{ form_widget(form.months) }} +
                  + {%- endif -%} + {%- if with_weeks -%} +
                  + {{ form_label(form.weeks) }} + {{ form_widget(form.weeks) }} +
                  + {%- endif -%} + {%- if with_days -%} +
                  + {{ form_label(form.days) }} + {{ form_widget(form.days) }} +
                  + {%- endif -%} + {%- if with_hours -%} +
                  + {{ form_label(form.hours) }} + {{ form_widget(form.hours) }} +
                  + {%- endif -%} + {%- if with_minutes -%} +
                  + {{ form_label(form.minutes) }} + {{ form_widget(form.minutes) }} +
                  + {%- endif -%} + {%- if with_seconds -%} +
                  + {{ form_label(form.seconds) }} + {{ form_widget(form.seconds) }} +
                  + {%- endif -%} + {%- if with_invert %}{{ form_widget(form.invert) }}{% endif -%} +
                  + {%- endif -%} +{%- endblock dateinterval_widget %} + +{% block percent_widget -%} + {%- if symbol -%} +
                  + {{- block('form_widget_simple') -}} + {{ symbol|default('%') }} +
                  + {%- else -%} + {{- block('form_widget_simple') -}} + {%- endif -%} +{%- endblock percent_widget %} + +{% block form_widget_simple -%} + {%- if type is not defined or type != 'hidden' %} + {%- set widget_class = ' form-control' %} + {%- if type|default('') == 'color' -%} + {%- set widget_class = widget_class ~ ' form-control-color' -%} + {%- elseif type|default('') == 'range' -%} + {%- set widget_class = ' form-range' -%} + {%- endif -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ widget_class)|trim}) -%} + {% endif -%} + {%- if type is defined and type in ['range', 'color'] %} + {# Attribute "required" is not supported #} + {% set required = false %} + {% endif -%} + {{- parent() -}} +{%- endblock form_widget_simple %} + +{%- block widget_attributes -%} + {%- if not valid %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' is-invalid')|trim}) %} + {% endif -%} + {{ parent() }} +{%- endblock widget_attributes -%} + +{%- block button_widget -%} + {%- set attr = attr|merge({class: (attr.class|default('btn-secondary') ~ ' btn')|trim}) -%} + {{- parent() -}} +{%- endblock button_widget %} + +{%- block submit_widget -%} + {%- set attr = attr|merge({class: (attr.class|default('btn-primary'))|trim}) -%} + {{- parent() -}} +{%- endblock submit_widget %} + +{%- block checkbox_widget -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {%- set row_class = 'form-check' -%} + {%- if 'checkbox-inline' in parent_label_class %} + {% set row_class = row_class ~ ' form-check-inline' %} + {% endif -%} + {%- if 'checkbox-switch' in parent_label_class %} + {% set row_class = row_class ~ ' form-switch' %} + {% endif -%} +
                  + {{- form_label(form, null, { widget: parent() }) -}} +
                  +{%- endblock checkbox_widget %} + +{%- block radio_widget -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-check-input')|trim}) -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {%- set row_class = 'form-check' -%} + {%- if 'radio-inline' in parent_label_class -%} + {%- set row_class = row_class ~ ' form-check-inline' -%} + {%- endif -%} +
                  + {{- form_label(form, null, { widget: parent() }) -}} +
                  +{%- endblock radio_widget %} + +{%- block choice_widget_collapsed -%} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-select')|trim}) -%} + {{- parent() -}} +{%- endblock choice_widget_collapsed -%} + +{%- block choice_widget_expanded -%} +
                  + {%- for child in form %} + {{- form_widget(child, { + parent_label_class: label_attr.class|default(''), + translation_domain: choice_translation_domain, + valid: valid, + }) -}} + {% endfor -%} +
                  +{%- endblock choice_widget_expanded %} + +{# Labels #} + +{%- block form_label -%} + {% if label is not same as(false) -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {%- if compound is defined and compound -%} + {%- set element = 'legend' -%} + {%- if 'col-form-label' not in parent_label_class -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' col-form-label' )|trim}) -%} + {%- endif -%} + {%- else -%} + {%- set row_class = row_class|default(row_attr.class|default('')) -%} + {%- set label_attr = label_attr|merge({for: id}) -%} + {%- if 'col-form-label' not in parent_label_class -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ('input-group' in row_class ? ' input-group-text' : ' form-label') )|trim}) -%} + {%- endif -%} + {%- endif -%} + {%- endif -%} + {{- parent() -}} +{%- endblock form_label %} + +{%- block checkbox_radio_label -%} + {#- Do not display the label if widget is not defined in order to prevent double label rendering -#} + {%- if widget is defined -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' form-check-label')|trim}) -%} + {%- if not compound -%} + {% set label_attr = label_attr|merge({'for': id}) %} + {%- endif -%} + {%- if required -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) -%} + {%- endif -%} + {%- if parent_label_class is defined -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ parent_label_class)|replace({'checkbox-inline': '', 'radio-inline': ''})|trim}) -%} + {%- endif -%} + {%- if label is not same as(false) and label is empty -%} + {%- if label_format is not empty -%} + {%- set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) -%} + {%- else -%} + {%- set label = name|humanize -%} + {%- endif -%} + {%- endif -%} + + {{ widget|raw }} + + {%- if label is not same as(false) -%} + {%- if translation_domain is same as(false) -%} + {%- if label_html is same as(false) -%} + {{- label -}} + {%- else -%} + {{- label|raw -}} + {%- endif -%} + {%- else -%} + {%- if label_html is same as(false) -%} + {{- label|trans(label_translation_parameters, translation_domain) -}} + {%- else -%} + {{- label|trans(label_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} + {%- endif -%} + + {%- endif -%} +{%- endblock checkbox_radio_label %} + +{# Rows #} + +{%- block form_row -%} + {%- if compound is defined and compound -%} + {%- set element = 'fieldset' -%} + {%- endif -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + {%- set row_class = row_class|default(row_attr.class|default('mb-3')|trim) -%} + <{{ element|default('div') }}{% with {attr: row_attr|merge({class: row_class})} %}{{ block('attributes') }}{% endwith %}> + {%- if 'form-floating' in row_class -%} + {{- form_widget(form, widget_attr) -}} + {{- form_label(form) -}} + {%- else -%} + {{- form_label(form) -}} + {{- form_widget(form, widget_attr) -}} + {%- endif -%} + {{- form_help(form) -}} + {{- form_errors(form) -}} + +{%- endblock form_row %} + +{%- block button_row -%} + + {{- form_widget(form) -}} +
                  +{%- endblock button_row %} + +{# Errors #} + +{%- block form_errors -%} + {%- if errors|length > 0 -%} + {%- for error in errors -%} +
                  {{ error.message }}
                  + {%- endfor -%} + {%- endif %} +{%- endblock form_errors %} + +{# Help #} + +{%- block form_help -%} + {% set row_class = row_attr.class|default('') %} + {% set help_class = ' form-text' %} + {% if 'input-group' in row_class %} + {#- Hack to properly display help with input group -#} + {% set help_class = ' input-group-text' %} + {% endif %} + {%- if help is not empty -%} + {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ help_class ~ ' mb-0')|trim}) -%} + {%- endif -%} + {{- parent() -}} +{%- endblock form_help %} diff --git a/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_base_layout.html.twig b/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_base_layout.html.twig index 2630803573..b8cb8c44aa 100644 --- a/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_base_layout.html.twig +++ b/lib/symfony/twig-bridge/Resources/views/Form/bootstrap_base_layout.html.twig @@ -26,10 +26,14 @@ {%- endblock money_widget %} {% block percent_widget -%} -
                  + {%- if symbol -%} +
                  + {{- block('form_widget_simple') -}} + {{ symbol|default('%') }} +
                  + {%- else -%} {{- block('form_widget_simple') -}} - % -
                  + {%- endif -%} {%- endblock percent_widget %} {% block datetime_widget -%} @@ -64,11 +68,13 @@ {%- if datetime is not defined or not datetime -%}
                  {%- endif %} + {%- if label is not same as(false) -%}
                  {{ form_label(form.year) }} {{ form_label(form.month) }} {{ form_label(form.day) }}
                  + {%- endif -%} {{- date_pattern|replace({ '{{ year }}': form_widget(form.year), @@ -89,10 +95,10 @@ {%- if datetime is not defined or false == datetime -%}
                  {%- endif -%} -
                  {{ form_label(form.hour) }}
                  + {%- if label is not same as(false) -%}
                  {{ form_label(form.hour) }}
                  {%- endif -%} {{- form_widget(form.hour) -}} - {%- if with_minutes -%}:
                  {{ form_label(form.minute) }}
                  {{ form_widget(form.minute) }}{%- endif -%} - {%- if with_seconds -%}:
                  {{ form_label(form.second) }}
                  {{ form_widget(form.second) }}{%- endif -%} + {%- if with_minutes -%}:{%- if label is not same as(false) -%}
                  {{ form_label(form.minute) }}
                  {%- endif -%}{{ form_widget(form.minute) }}{%- endif -%} + {%- if with_seconds -%}:{%- if label is not same as(false) -%}
                  {{ form_label(form.second) }}
                  {%- endif -%}{{ form_widget(form.second) }}{%- endif -%} {%- if datetime is not defined or false == datetime -%}
                  {%- endif -%} @@ -137,11 +143,6 @@ {%- endif -%} {%- endblock dateinterval_widget -%} -{% block choice_widget_collapsed -%} - {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) -%} - {{- parent() -}} -{%- endblock choice_widget_collapsed %} - {% block choice_widget_expanded -%} {%- if '-inline' in label_attr.class|default('') -%} {%- for child in form %} @@ -166,7 +167,7 @@ {% block choice_label -%} {# remove the checkbox-inline and radio-inline class, it's only useful for embed labels #} - {%- set label_attr = label_attr|merge({class: label_attr.class|default('')|replace({'checkbox-inline': '', 'radio-inline': '', 'checkbox-custom': '', 'radio-custom': ''})|trim}) -%} + {%- set label_attr = label_attr|merge({class: label_attr.class|default('')|replace({'checkbox-inline': '', 'radio-inline': '', 'checkbox-custom': '', 'radio-custom': '', 'switch-custom': ''})|trim}) -%} {{- block('form_label') -}} {% endblock choice_label %} @@ -181,7 +182,7 @@ {# Rows #} {% block button_row -%} -
                  + {{- form_widget(form) -}}
                  {%- endblock button_row %} diff --git a/lib/symfony/twig-bridge/Resources/views/Form/form_div_layout.html.twig b/lib/symfony/twig-bridge/Resources/views/Form/form_div_layout.html.twig index 362e27ce95..94f87dc165 100644 --- a/lib/symfony/twig-bridge/Resources/views/Form/form_div_layout.html.twig +++ b/lib/symfony/twig-bridge/Resources/views/Form/form_div_layout.html.twig @@ -10,6 +10,10 @@ {%- block form_widget_simple -%} {%- set type = type|default('text') -%} + {%- if type == 'range' or type == 'color' -%} + {# Attribute "required" is not supported #} + {%- set required = false -%} + {%- endif -%} {%- endblock form_widget_simple -%} @@ -61,12 +65,14 @@ {%- endif -%} {%- if preferred_choices|length > 0 -%} {% set options = preferred_choices %} + {% set render_preferred_choices = true %} {{- block('choice_widget_options') -}} {%- if choices|length > 0 and separator is not none -%} {%- endif -%} {%- endif -%} {%- set options = choices -%} + {%- set render_preferred_choices = false -%} {{- block('choice_widget_options') -}} {%- endblock choice_widget_collapsed -%} @@ -79,7 +85,7 @@ {{- block('choice_widget_options') -}} {%- else -%} - + {%- endif -%} {% endfor %} {%- endblock choice_widget_options -%} @@ -166,7 +172,7 @@ {%- endblock dateinterval_widget -%} {%- block number_widget -%} - {# type="number" doesn't work with floats #} + {# type="number" doesn't work with floats in localized formats #} {%- set type = type|default('text') -%} {{ block('form_widget_simple') }} {%- endblock number_widget -%} @@ -192,7 +198,7 @@ {%- block percent_widget -%} {%- set type = type|default('text') -%} - {{ block('form_widget_simple') }} % + {{ block('form_widget_simple') }}{% if symbol %} {{ symbol|default('%') }}{% endif %} {%- endblock percent_widget -%} {%- block password_widget -%} @@ -226,7 +232,21 @@ {% set label = name|humanize %} {%- endif -%} {%- endif -%} - + {%- endblock button_widget -%} {%- block submit_widget -%} @@ -249,6 +269,17 @@ {{ block('form_widget_simple') }} {%- endblock color_widget -%} +{%- block week_widget -%} + {%- if widget == 'single_text' -%} + {{ block('form_widget_simple') }} + {%- else -%} + {%- set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} -%} +
                  + {{ form_widget(form.year, vars) }}-{{ form_widget(form.week, vars) }} +
                  + {%- endif -%} +{%- endblock week_widget -%} + {# Labels #} {%- block form_label -%} @@ -271,9 +302,17 @@ {%- endif -%} <{{ element|default('label') }}{% if label_attr %}{% with { attr: label_attr } %}{{ block('attributes') }}{% endwith %}{% endif %}> {%- if translation_domain is same as(false) -%} - {{- label -}} + {%- if label_html is same as(false) -%} + {{- label -}} + {%- else -%} + {{- label|raw -}} + {%- endif -%} {%- else -%} - {{- label|trans({}, translation_domain) -}} + {%- if label_html is same as(false) -%} + {{- label|trans(label_translation_parameters, translation_domain) -}} + {%- else -%} + {{- label|trans(label_translation_parameters, translation_domain)|raw -}} + {%- endif -%} {%- endif -%} {%- endif -%} @@ -281,6 +320,29 @@ {%- block button_label -%}{%- endblock -%} +{# Help #} + +{% block form_help -%} + {%- if help is not empty -%} + {%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' help-text')|trim}) -%} +

                  + {%- if translation_domain is same as(false) -%} + {%- if help_html is same as(false) -%} + {{- help -}} + {%- else -%} + {{- help|raw -}} + {%- endif -%} + {%- else -%} + {%- if help_html is same as(false) -%} + {{- help|trans(help_translation_parameters, translation_domain) -}} + {%- else -%} + {{- help|trans(help_translation_parameters, translation_domain)|raw -}} + {%- endif -%} + {%- endif -%} +

                  + {%- endif -%} +{%- endblock form_help %} + {# Rows #} {%- block repeated_row -%} @@ -292,15 +354,20 @@ {%- endblock repeated_row -%} {%- block form_row -%} -
                  + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + {{- form_label(form) -}} {{- form_errors(form) -}} - {{- form_widget(form) -}} + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}}
                  {%- endblock form_row -%} {%- block button_row -%} -
                  + {{- form_widget(form) -}}
                  {%- endblock button_row -%} @@ -373,7 +440,7 @@ {# Support #} {%- block form_rows -%} - {% for child in form %} + {% for child in form|filter(child => not child.rendered) %} {{- form_row(child) -}} {% endfor %} {%- endblock form_rows -%} @@ -399,7 +466,7 @@ {%- for attrname, attrvalue in attr -%} {{- " " -}} {%- if attrname in ['placeholder', 'title'] -%} - {{- attrname }}="{{ translation_domain is same as(false) or attrvalue is null ? attrvalue : attrvalue|trans({}, translation_domain) }}" + {{- attrname }}="{{ translation_domain is same as(false) or attrvalue is null ? attrvalue : attrvalue|trans(attr_translation_parameters, translation_domain) }}" {%- elseif attrvalue is same as(true) -%} {{- attrname }}="{{ attrname }}" {%- elseif attrvalue is not same as(false) -%} diff --git a/lib/symfony/twig-bridge/Resources/views/Form/form_table_layout.html.twig b/lib/symfony/twig-bridge/Resources/views/Form/form_table_layout.html.twig index 39274c6c8d..00a51ab04b 100644 --- a/lib/symfony/twig-bridge/Resources/views/Form/form_table_layout.html.twig +++ b/lib/symfony/twig-bridge/Resources/views/Form/form_table_layout.html.twig @@ -1,19 +1,24 @@ {% use "form_div_layout.html.twig" %} {%- block form_row -%} - + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + {{- form_label(form) -}} {{- form_errors(form) -}} - {{- form_widget(form) -}} + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} {%- endblock form_row -%} {%- block button_row -%} - + {{- form_widget(form) -}} @@ -22,7 +27,8 @@ {%- endblock button_row -%} {%- block hidden_row -%} - + {%- set style = row_attr.style is defined ? (row_attr.style ~ (row_attr.style|trim|last != ';' ? '; ')) -%} + {{- form_widget(form) -}} diff --git a/lib/symfony/twig-bridge/Resources/views/Form/foundation_5_layout.html.twig b/lib/symfony/twig-bridge/Resources/views/Form/foundation_5_layout.html.twig index 7876be3e43..f8c51b83dd 100644 --- a/lib/symfony/twig-bridge/Resources/views/Form/foundation_5_layout.html.twig +++ b/lib/symfony/twig-bridge/Resources/views/Form/foundation_5_layout.html.twig @@ -43,12 +43,18 @@ {% block percent_widget -%}
                  -
                  - {{- block('form_widget_simple') -}} -
                  -
                  - % -
                  + {%- if symbol -%} +
                  + {{- block('form_widget_simple') -}} +
                  +
                  + {{ symbol|default('%') }} +
                  + {%- else -%} +
                  + {{- block('form_widget_simple') -}} +
                  + {%- endif -%}
                  {%- endblock percent_widget %} @@ -154,12 +160,14 @@ {%- endif %} {%- if preferred_choices|length > 0 -%} {% set options = preferred_choices %} + {% set render_preferred_choices = true %} {{- block('choice_widget_options') -}} {% if choices|length > 0 and separator is not none -%} {%- endif %} {%- endif -%} {% set options = choices -%} + {%- set render_preferred_choices = false -%} {{- block('choice_widget_options') -}} {%- endblock choice_widget_collapsed %} @@ -245,9 +253,6 @@ {% if errors|length > 0 -%} {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' error')|trim}) %} {% endif %} - {% if parent_label_class is defined %} - {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ parent_label_class)|trim}) %} - {% endif %} {% if label is empty %} {%- if label_format is not empty -%} {% set label = label_format|replace({ @@ -260,18 +265,23 @@ {% endif %} {{ widget|raw }} - {{ translation_domain is same as(false) ? label : label|trans({}, translation_domain) }} + {{ translation_domain is same as(false) ? label : label|trans(label_translation_parameters, translation_domain) }} {%- endblock checkbox_radio_label %} {# Rows #} {% block form_row -%} -
                  + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} +
                  - {{ form_label(form) }} - {{ form_widget(form) }} - {{ form_errors(form) }} + {{- form_label(form) -}} + {{- form_widget(form, widget_attr) -}} + {{- form_help(form) -}} + {{- form_errors(form) -}}
                  {%- endblock form_row %} @@ -297,18 +307,20 @@ {%- endblock datetime_row %} {% block checkbox_row -%} -
                  +
                  {{ form_widget(form) }} + {{- form_help(form) -}} {{ form_errors(form) }}
                  {%- endblock checkbox_row %} {% block radio_row -%} -
                  +
                  {{ form_widget(form) }} + {{- form_help(form) -}} {{ form_errors(form) }}
                  diff --git a/lib/symfony/twig-bridge/Resources/views/Form/foundation_6_layout.html.twig b/lib/symfony/twig-bridge/Resources/views/Form/foundation_6_layout.html.twig new file mode 100644 index 0000000000..04ed730f5c --- /dev/null +++ b/lib/symfony/twig-bridge/Resources/views/Form/foundation_6_layout.html.twig @@ -0,0 +1,50 @@ +{% extends "form_div_layout.html.twig" %} + +{%- block checkbox_row -%} + {%- set parent_class = parent_class|default(attr.class|default('')) -%} + {%- if 'switch-input' in parent_class -%} + {{- form_label(form) -}} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' switch-input')|trim}) -%} + {{- form_widget(form) -}} + + {{- form_errors(form) -}} + {%- else -%} + {{- block('form_row') -}} + {%- endif -%} +{%- endblock checkbox_row -%} + +{% block money_widget -%} + {% set prepend = not (money_pattern starts with '{{') %} + {% set append = not (money_pattern ends with '}}') %} + {% if prepend or append %} +
                  + {% if prepend %} + {{ money_pattern|form_encode_currency }} + {% endif %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' input-group-field')|trim}) %} + {{- block('form_widget_simple') -}} + {% if append %} + {{ money_pattern|form_encode_currency }} + {% endif %} +
                  + {% else %} + {{- block('form_widget_simple') -}} + {% endif %} +{%- endblock money_widget %} + +{% block percent_widget -%} + {%- if symbol -%} +
                  + {% set attr = attr|merge({class: (attr.class|default('') ~ ' input-group-field')|trim}) %} + {{- block('form_widget_simple') -}} + {{ symbol|default('%') }} +
                  + {%- else -%} + {{- block('form_widget_simple') -}} + {%- endif -%} +{%- endblock percent_widget %} + +{% block button_widget -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' button')|trim}) %} + {{- parent() -}} +{%- endblock button_widget %} diff --git a/lib/symfony/twig-bridge/Resources/views/Form/tailwind_2_layout.html.twig b/lib/symfony/twig-bridge/Resources/views/Form/tailwind_2_layout.html.twig new file mode 100644 index 0000000000..7f31e70b79 --- /dev/null +++ b/lib/symfony/twig-bridge/Resources/views/Form/tailwind_2_layout.html.twig @@ -0,0 +1,69 @@ +{% use 'form_div_layout.html.twig' %} + +{%- block form_row -%} + {%- set row_attr = row_attr|merge({ class: row_attr.class|default(row_class|default('mb-6')) }) -%} + {{- parent() -}} +{%- endblock form_row -%} + +{%- block widget_attributes -%} + {%- set attr = attr|merge({ class: attr.class|default(widget_class|default('mt-1 w-full')) ~ (disabled ? ' ' ~ widget_disabled_class|default('border-gray-300 text-gray-500')) ~ (errors|length ? ' ' ~ widget_errors_class|default('border-red-700')) }) -%} + {{- parent() -}} +{%- endblock widget_attributes -%} + +{%- block form_label -%} + {%- set label_attr = label_attr|merge({ class: label_attr.class|default(label_class|default('block text-gray-800')) }) -%} + {{- parent() -}} +{%- endblock form_label -%} + +{%- block form_help -%} + {%- set help_attr = help_attr|merge({ class: help_attr.class|default(help_class|default('mt-1 text-gray-600')) }) -%} + {{- parent() -}} +{%- endblock form_help -%} + +{%- block form_errors -%} + {%- if errors|length > 0 -%} +
                    + {%- for error in errors -%} +
                  • {{ error.message }}
                  • + {%- endfor -%} +
                  + {%- endif -%} +{%- endblock form_errors -%} + +{%- block choice_widget_expanded -%} + {%- set attr = attr|merge({ class: attr.class|default('mt-2') }) -%} +
                  + {%- for child in form %} +
                  + {{- form_widget(child) -}} + {{- form_label(child, null, { translation_domain: choice_translation_domain }) -}} +
                  + {% endfor -%} +
                  +{%- endblock choice_widget_expanded -%} + +{%- block checkbox_row -%} + {%- set row_attr = row_attr|merge({ class: row_attr.class|default(row_class|default('mb-6')) }) -%} + {%- set widget_attr = {} -%} + {%- if help is not empty -%} + {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%} + {%- endif -%} + + {{- form_errors(form) -}} +
                  + {{- form_widget(form, widget_attr) -}} + {{- form_label(form) -}} +
                  + {{- form_help(form) -}} +
                  +{%- endblock checkbox_row -%} + +{%- block checkbox_widget -%} + {%- set widget_class = widget_class|default('mr-2') -%} + {{- parent() -}} +{%- endblock checkbox_widget -%} + +{%- block radio_widget -%} + {%- set widget_class = widget_class|default('mr-2') -%} + {{- parent() -}} +{%- endblock radio_widget -%} diff --git a/lib/symfony/twig-bridge/TokenParser/DumpTokenParser.php b/lib/symfony/twig-bridge/TokenParser/DumpTokenParser.php index a4d7d6f690..341dc41855 100644 --- a/lib/symfony/twig-bridge/TokenParser/DumpTokenParser.php +++ b/lib/symfony/twig-bridge/TokenParser/DumpTokenParser.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Twig\TokenParser; use Symfony\Bridge\Twig\Node\DumpNode; +use Twig\Node\Node; use Twig\Token; use Twig\TokenParser\AbstractTokenParser; @@ -26,12 +27,12 @@ use Twig\TokenParser\AbstractTokenParser; * * @author Julien Galenski */ -class DumpTokenParser extends AbstractTokenParser +final class DumpTokenParser extends AbstractTokenParser { /** * {@inheritdoc} */ - public function parse(Token $token) + public function parse(Token $token): Node { $values = null; if (!$this->parser->getStream()->test(Token::BLOCK_END_TYPE)) { @@ -45,7 +46,7 @@ class DumpTokenParser extends AbstractTokenParser /** * {@inheritdoc} */ - public function getTag() + public function getTag(): string { return 'dump'; } diff --git a/lib/symfony/twig-bridge/TokenParser/FormThemeTokenParser.php b/lib/symfony/twig-bridge/TokenParser/FormThemeTokenParser.php index ffef9e9859..ef5dacb59d 100644 --- a/lib/symfony/twig-bridge/TokenParser/FormThemeTokenParser.php +++ b/lib/symfony/twig-bridge/TokenParser/FormThemeTokenParser.php @@ -22,14 +22,12 @@ use Twig\TokenParser\AbstractTokenParser; * * @author Fabien Potencier */ -class FormThemeTokenParser extends AbstractTokenParser +final class FormThemeTokenParser extends AbstractTokenParser { /** - * Parses a token and returns a node. - * - * @return Node + * {@inheritdoc} */ - public function parse(Token $token) + public function parse(Token $token): Node { $lineno = $token->getLine(); $stream = $this->parser->getStream(); @@ -57,11 +55,9 @@ class FormThemeTokenParser extends AbstractTokenParser } /** - * Gets the tag name associated with this token parser. - * - * @return string The tag name + * {@inheritdoc} */ - public function getTag() + public function getTag(): string { return 'form_theme'; } diff --git a/lib/symfony/twig-bridge/TokenParser/StopwatchTokenParser.php b/lib/symfony/twig-bridge/TokenParser/StopwatchTokenParser.php index 4cf4ffcd7e..a70e94b801 100644 --- a/lib/symfony/twig-bridge/TokenParser/StopwatchTokenParser.php +++ b/lib/symfony/twig-bridge/TokenParser/StopwatchTokenParser.php @@ -13,6 +13,7 @@ namespace Symfony\Bridge\Twig\TokenParser; use Symfony\Bridge\Twig\Node\StopwatchNode; use Twig\Node\Expression\AssignNameExpression; +use Twig\Node\Node; use Twig\Token; use Twig\TokenParser\AbstractTokenParser; @@ -21,16 +22,16 @@ use Twig\TokenParser\AbstractTokenParser; * * @author Wouter J */ -class StopwatchTokenParser extends AbstractTokenParser +final class StopwatchTokenParser extends AbstractTokenParser { protected $stopwatchIsAvailable; - public function __construct($stopwatchIsAvailable) + public function __construct(bool $stopwatchIsAvailable) { $this->stopwatchIsAvailable = $stopwatchIsAvailable; } - public function parse(Token $token) + public function parse(Token $token): Node { $lineno = $token->getLine(); $stream = $this->parser->getStream(); @@ -51,12 +52,12 @@ class StopwatchTokenParser extends AbstractTokenParser return $body; } - public function decideStopwatchEnd(Token $token) + public function decideStopwatchEnd(Token $token): bool { return $token->test('endstopwatch'); } - public function getTag() + public function getTag(): string { return 'stopwatch'; } diff --git a/lib/symfony/twig-bridge/TokenParser/TransChoiceTokenParser.php b/lib/symfony/twig-bridge/TokenParser/TransChoiceTokenParser.php deleted file mode 100644 index b1c8a39b3a..0000000000 --- a/lib/symfony/twig-bridge/TokenParser/TransChoiceTokenParser.php +++ /dev/null @@ -1,86 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\Twig\TokenParser; - -use Symfony\Bridge\Twig\Node\TransNode; -use Twig\Error\SyntaxError; -use Twig\Node\Expression\AbstractExpression; -use Twig\Node\Expression\ArrayExpression; -use Twig\Node\TextNode; -use Twig\Token; - -/** - * Token Parser for the 'transchoice' tag. - * - * @author Fabien Potencier - */ -class TransChoiceTokenParser extends TransTokenParser -{ - /** - * {@inheritdoc} - */ - public function parse(Token $token) - { - $lineno = $token->getLine(); - $stream = $this->parser->getStream(); - - $vars = new ArrayExpression([], $lineno); - - $count = $this->parser->getExpressionParser()->parseExpression(); - - $domain = null; - $locale = null; - - if ($stream->test('with')) { - // {% transchoice count with vars %} - $stream->next(); - $vars = $this->parser->getExpressionParser()->parseExpression(); - } - - if ($stream->test('from')) { - // {% transchoice count from "messages" %} - $stream->next(); - $domain = $this->parser->getExpressionParser()->parseExpression(); - } - - if ($stream->test('into')) { - // {% transchoice count into "fr" %} - $stream->next(); - $locale = $this->parser->getExpressionParser()->parseExpression(); - } - - $stream->expect(Token::BLOCK_END_TYPE); - - $body = $this->parser->subparse([$this, 'decideTransChoiceFork'], true); - - if (!$body instanceof TextNode && !$body instanceof AbstractExpression) { - throw new SyntaxError('A message inside a transchoice tag must be a simple text.', $body->getTemplateLine(), $stream->getSourceContext()); - } - - $stream->expect(Token::BLOCK_END_TYPE); - - return new TransNode($body, $domain, $count, $vars, $locale, $lineno, $this->getTag()); - } - - public function decideTransChoiceFork($token) - { - return $token->test(['endtranschoice']); - } - - /** - * {@inheritdoc} - */ - public function getTag() - { - return 'transchoice'; - } -} diff --git a/lib/symfony/twig-bridge/TokenParser/TransDefaultDomainTokenParser.php b/lib/symfony/twig-bridge/TokenParser/TransDefaultDomainTokenParser.php index 72fbda77b8..19b820497d 100644 --- a/lib/symfony/twig-bridge/TokenParser/TransDefaultDomainTokenParser.php +++ b/lib/symfony/twig-bridge/TokenParser/TransDefaultDomainTokenParser.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Twig\TokenParser; use Symfony\Bridge\Twig\Node\TransDefaultDomainNode; +use Twig\Node\Node; use Twig\Token; use Twig\TokenParser\AbstractTokenParser; @@ -20,12 +21,12 @@ use Twig\TokenParser\AbstractTokenParser; * * @author Fabien Potencier */ -class TransDefaultDomainTokenParser extends AbstractTokenParser +final class TransDefaultDomainTokenParser extends AbstractTokenParser { /** * {@inheritdoc} */ - public function parse(Token $token) + public function parse(Token $token): Node { $expr = $this->parser->getExpressionParser()->parseExpression(); @@ -37,7 +38,7 @@ class TransDefaultDomainTokenParser extends AbstractTokenParser /** * {@inheritdoc} */ - public function getTag() + public function getTag(): string { return 'trans_default_domain'; } diff --git a/lib/symfony/twig-bridge/TokenParser/TransTokenParser.php b/lib/symfony/twig-bridge/TokenParser/TransTokenParser.php index 5f1e19acb9..ffe8828590 100644 --- a/lib/symfony/twig-bridge/TokenParser/TransTokenParser.php +++ b/lib/symfony/twig-bridge/TokenParser/TransTokenParser.php @@ -15,6 +15,7 @@ use Symfony\Bridge\Twig\Node\TransNode; use Twig\Error\SyntaxError; use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Node; use Twig\Node\TextNode; use Twig\Token; use Twig\TokenParser\AbstractTokenParser; @@ -24,20 +25,27 @@ use Twig\TokenParser\AbstractTokenParser; * * @author Fabien Potencier */ -class TransTokenParser extends AbstractTokenParser +final class TransTokenParser extends AbstractTokenParser { /** * {@inheritdoc} */ - public function parse(Token $token) + public function parse(Token $token): Node { $lineno = $token->getLine(); $stream = $this->parser->getStream(); + $count = null; $vars = new ArrayExpression([], $lineno); $domain = null; $locale = null; if (!$stream->test(Token::BLOCK_END_TYPE)) { + if ($stream->test('count')) { + // {% trans count 5 %} + $stream->next(); + $count = $this->parser->getExpressionParser()->parseExpression(); + } + if ($stream->test('with')) { // {% trans with vars %} $stream->next(); @@ -69,10 +77,10 @@ class TransTokenParser extends AbstractTokenParser $stream->expect(Token::BLOCK_END_TYPE); - return new TransNode($body, $domain, null, $vars, $locale, $lineno, $this->getTag()); + return new TransNode($body, $domain, $count, $vars, $locale, $lineno, $this->getTag()); } - public function decideTransFork($token) + public function decideTransFork(Token $token): bool { return $token->test(['endtrans']); } @@ -80,7 +88,7 @@ class TransTokenParser extends AbstractTokenParser /** * {@inheritdoc} */ - public function getTag() + public function getTag(): string { return 'trans'; } diff --git a/lib/symfony/twig-bridge/Translation/TwigExtractor.php b/lib/symfony/twig-bridge/Translation/TwigExtractor.php index 3d01ca57c7..e79ec697e0 100644 --- a/lib/symfony/twig-bridge/Translation/TwigExtractor.php +++ b/lib/symfony/twig-bridge/Translation/TwigExtractor.php @@ -65,12 +65,12 @@ class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface /** * {@inheritdoc} */ - public function setPrefix($prefix) + public function setPrefix(string $prefix) { $this->prefix = $prefix; } - protected function extractTemplate($template, MessageCatalogue $catalogue) + protected function extractTemplate(string $template, MessageCatalogue $catalogue) { $visitor = $this->twig->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->getTranslationNodeVisitor(); $visitor->enable(); @@ -85,11 +85,9 @@ class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface } /** - * @param string $file - * * @return bool */ - protected function canBeExtracted($file) + protected function canBeExtracted(string $file) { return $this->isFile($file) && 'twig' === pathinfo($file, \PATHINFO_EXTENSION); } diff --git a/lib/symfony/twig-bridge/TwigEngine.php b/lib/symfony/twig-bridge/TwigEngine.php deleted file mode 100644 index cfbb5d8f4e..0000000000 --- a/lib/symfony/twig-bridge/TwigEngine.php +++ /dev/null @@ -1,136 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bridge\Twig; - -use Symfony\Component\Templating\EngineInterface; -use Symfony\Component\Templating\StreamingEngineInterface; -use Symfony\Component\Templating\TemplateNameParserInterface; -use Symfony\Component\Templating\TemplateReferenceInterface; -use Twig\Environment; -use Twig\Error\Error; -use Twig\Error\LoaderError; -use Twig\Loader\ExistsLoaderInterface; -use Twig\Loader\SourceContextLoaderInterface; -use Twig\Template; - -/** - * This engine knows how to render Twig templates. - * - * @author Fabien Potencier - */ -class TwigEngine implements EngineInterface, StreamingEngineInterface -{ - protected $environment; - protected $parser; - - public function __construct(Environment $environment, TemplateNameParserInterface $parser) - { - $this->environment = $environment; - $this->parser = $parser; - } - - /** - * {@inheritdoc} - * - * It also supports Template as name parameter. - * - * @throws Error if something went wrong like a thrown exception while rendering the template - */ - public function render($name, array $parameters = []) - { - return $this->load($name)->render($parameters); - } - - /** - * {@inheritdoc} - * - * It also supports Template as name parameter. - * - * @throws Error if something went wrong like a thrown exception while rendering the template - */ - public function stream($name, array $parameters = []) - { - $this->load($name)->display($parameters); - } - - /** - * {@inheritdoc} - * - * It also supports Template as name parameter. - */ - public function exists($name) - { - if ($name instanceof Template) { - return true; - } - - $loader = $this->environment->getLoader(); - - if (1 === Environment::MAJOR_VERSION && !$loader instanceof ExistsLoaderInterface) { - try { - // cast possible TemplateReferenceInterface to string because the - // EngineInterface supports them but LoaderInterface does not - if ($loader instanceof SourceContextLoaderInterface) { - $loader->getSourceContext((string) $name); - } else { - $loader->getSource((string) $name); - } - - return true; - } catch (LoaderError $e) { - } - - return false; - } - - return $loader->exists((string) $name); - } - - /** - * {@inheritdoc} - * - * It also supports Template as name parameter. - */ - public function supports($name) - { - if ($name instanceof Template) { - return true; - } - - $template = $this->parser->parse($name); - - return 'twig' === $template->get('engine'); - } - - /** - * Loads the given template. - * - * @param string|TemplateReferenceInterface|Template $name A template name or an instance of - * TemplateReferenceInterface or Template - * - * @return Template - * - * @throws \InvalidArgumentException if the template does not exist - */ - protected function load($name) - { - if ($name instanceof Template) { - return $name; - } - - try { - return $this->environment->loadTemplate((string) $name); - } catch (LoaderError $e) { - throw new \InvalidArgumentException($e->getMessage(), $e->getCode(), $e); - } - } -} diff --git a/lib/symfony/twig-bridge/UndefinedCallableHandler.php b/lib/symfony/twig-bridge/UndefinedCallableHandler.php index 43cac82bcb..608bbaa8e3 100644 --- a/lib/symfony/twig-bridge/UndefinedCallableHandler.php +++ b/lib/symfony/twig-bridge/UndefinedCallableHandler.php @@ -13,28 +13,32 @@ namespace Symfony\Bridge\Twig; use Symfony\Bundle\FullStack; use Twig\Error\SyntaxError; +use Twig\TwigFilter; +use Twig\TwigFunction; /** * @internal */ class UndefinedCallableHandler { - private static $filterComponents = [ + private const FILTER_COMPONENTS = [ 'humanize' => 'form', 'trans' => 'translation', - 'transchoice' => 'translation', 'yaml_encode' => 'yaml', 'yaml_dump' => 'yaml', ]; - private static $functionComponents = [ + private const FUNCTION_COMPONENTS = [ 'asset' => 'asset', 'asset_version' => 'asset', 'dump' => 'debug-bundle', + 'encore_entry_link_tags' => 'webpack-encore-bundle', + 'encore_entry_script_tags' => 'webpack-encore-bundle', 'expression' => 'expression-language', 'form_widget' => 'form', 'form_errors' => 'form', 'form_label' => 'form', + 'form_help' => 'form', 'form_row' => 'form', 'form_rest' => 'form', 'form' => 'form', @@ -56,7 +60,7 @@ class UndefinedCallableHandler 'workflow_marked_places' => 'workflow', ]; - private static $fullStackEnable = [ + private const FULL_STACK_ENABLE = [ 'form' => 'enable "framework.form"', 'security-core' => 'add the "SecurityBundle"', 'security-http' => 'add the "SecurityBundle"', @@ -64,34 +68,40 @@ class UndefinedCallableHandler 'workflow' => 'enable "framework.workflows"', ]; - public static function onUndefinedFilter($name) + /** + * @return TwigFilter|false + */ + public static function onUndefinedFilter(string $name) { - if (!isset(self::$filterComponents[$name])) { + if (!isset(self::FILTER_COMPONENTS[$name])) { return false; } - self::onUndefined($name, 'filter', self::$filterComponents[$name]); - - return true; + throw new SyntaxError(self::onUndefined($name, 'filter', self::FILTER_COMPONENTS[$name])); } - public static function onUndefinedFunction($name) + /** + * @return TwigFunction|false + */ + public static function onUndefinedFunction(string $name) { - if (!isset(self::$functionComponents[$name])) { + if (!isset(self::FUNCTION_COMPONENTS[$name])) { return false; } - self::onUndefined($name, 'function', self::$functionComponents[$name]); - - return true; - } - - private static function onUndefined($name, $type, $component) - { - if (class_exists(FullStack::class) && isset(self::$fullStackEnable[$component])) { - throw new SyntaxError(sprintf('Did you forget to %s? Unknown %s "%s".', self::$fullStackEnable[$component], $type, $name)); + if ('webpack-encore-bundle' === self::FUNCTION_COMPONENTS[$name]) { + return new TwigFunction($name, static function () { return ''; }); } - throw new SyntaxError(sprintf('Did you forget to run "composer require symfony/%s"? Unknown %s "%s".', $component, $type, $name)); + throw new SyntaxError(self::onUndefined($name, 'function', self::FUNCTION_COMPONENTS[$name])); + } + + private static function onUndefined(string $name, string $type, string $component): string + { + if (class_exists(FullStack::class) && isset(self::FULL_STACK_ENABLE[$component])) { + return sprintf('Did you forget to %s? Unknown %s "%s".', self::FULL_STACK_ENABLE[$component], $type, $name); + } + + return sprintf('Did you forget to run "composer require symfony/%s"? Unknown %s "%s".', $component, $type, $name); } } diff --git a/lib/symfony/twig-bridge/composer.json b/lib/symfony/twig-bridge/composer.json index f61e4018eb..63b0726055 100644 --- a/lib/symfony/twig-bridge/composer.json +++ b/lib/symfony/twig-bridge/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/twig-bridge", "type": "symfony-bridge", - "description": "Symfony Twig Bridge", + "description": "Provides integration for Twig with various Symfony components", "keywords": [], "homepage": "https://symfony.com", "license": "MIT", @@ -16,34 +16,51 @@ } ], "require": { - "php": "^5.5.9|>=7.0.8", - "twig/twig": "^1.41|^2.10" + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16", + "symfony/translation-contracts": "^1.1|^2|^3", + "twig/twig": "^2.13|^3.0.4" }, "require-dev": { - "fig/link-util": "^1.0", - "symfony/asset": "~2.8|~3.0|~4.0", - "symfony/dependency-injection": "~2.8|~3.0|~4.0", - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/form": "^3.4.31|^4.3.4", - "symfony/http-foundation": "^3.3.11|~4.0", - "symfony/http-kernel": "~3.2|~4.0", + "doctrine/annotations": "^1.12", + "egulias/email-validator": "^2.1.10|^3", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/form": "^5.3|^6.0", + "symfony/http-foundation": "^5.3|^6.0", + "symfony/http-kernel": "^4.4|^5.0|^6.0", + "symfony/intl": "^4.4|^5.0|^6.0", + "symfony/mime": "^5.2|^6.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/routing": "~2.8|~3.0|~4.0", - "symfony/templating": "~2.8|~3.0|~4.0", - "symfony/translation": "~2.8|~3.0|~4.0", - "symfony/yaml": "~2.8|~3.0|~4.0", - "symfony/security": "^2.8.31|^3.3.13|~4.0", - "symfony/security-acl": "~2.8|~3.0", - "symfony/stopwatch": "~2.8|~3.0|~4.0", - "symfony/console": "~3.4|~4.0", - "symfony/var-dumper": "~2.8.10|~3.1.4|~3.2|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/web-link": "~3.3|~4.0", - "symfony/workflow": "~3.3|~4.0" + "symfony/property-info": "^4.4|^5.1|^6.0", + "symfony/routing": "^4.4|^5.0|^6.0", + "symfony/translation": "^5.2|^6.0", + "symfony/yaml": "^4.4|^5.0|^6.0", + "symfony/security-acl": "^2.8|^3.0", + "symfony/security-core": "^4.4|^5.0|^6.0", + "symfony/security-csrf": "^4.4|^5.0|^6.0", + "symfony/security-http": "^4.4|^5.0|^6.0", + "symfony/serializer": "^5.2|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0", + "symfony/console": "^5.3|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/web-link": "^4.4|^5.0|^6.0", + "symfony/workflow": "^5.2|^6.0", + "twig/cssinliner-extra": "^2.12|^3", + "twig/inky-extra": "^2.12|^3", + "twig/markdown-extra": "^2.12|^3" }, "conflict": { - "symfony/form": "<3.4.31|>=4.0,<4.3.4", - "symfony/console": "<3.4" + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/console": "<5.3", + "symfony/form": "<5.3", + "symfony/http-foundation": "<5.3", + "symfony/http-kernel": "<4.4", + "symfony/translation": "<5.2", + "symfony/workflow": "<5.2" }, "suggest": { "symfony/finder": "", @@ -51,10 +68,11 @@ "symfony/form": "For using the FormExtension", "symfony/http-kernel": "For using the HttpKernelExtension", "symfony/routing": "For using the RoutingExtension", - "symfony/templating": "For using the TwigEngine", "symfony/translation": "For using the TranslationExtension", "symfony/yaml": "For using the YamlExtension", - "symfony/security": "For using the SecurityExtension", + "symfony/security-core": "For using the SecurityExtension", + "symfony/security-csrf": "For using the CsrfExtension", + "symfony/security-http": "For using the LogoutUrlExtension", "symfony/stopwatch": "For using the StopwatchExtension", "symfony/var-dumper": "For using the DumpExtension", "symfony/expression-language": "For using the ExpressionExtension", diff --git a/lib/symfony/twig-bridge/phpunit.xml.dist b/lib/symfony/twig-bridge/phpunit.xml.dist deleted file mode 100644 index 6e1ada1b39..0000000000 --- a/lib/symfony/twig-bridge/phpunit.xml.dist +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - ./Tests/ - - - - - - ./ - - ./Resources - ./Tests - ./vendor - - - - diff --git a/lib/symfony/twig-bundle/.gitignore b/lib/symfony/twig-bundle/.gitignore deleted file mode 100644 index c49a5d8df5..0000000000 --- a/lib/symfony/twig-bundle/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/lib/symfony/twig-bundle/CHANGELOG.md b/lib/symfony/twig-bundle/CHANGELOG.md index bdf357181f..83a6cccf96 100644 --- a/lib/symfony/twig-bundle/CHANGELOG.md +++ b/lib/symfony/twig-bundle/CHANGELOG.md @@ -1,6 +1,51 @@ CHANGELOG ========= +5.3 +--- + + * Add support for the new `serialize` filter (from Twig Bridge) + +5.2.0 +----- + + * deprecated the public `twig` service to private + +5.0.0 +----- + + * updated default value for the `strict_variables` option to `%kernel.debug%` parameter + * removed support to load templates from the legacy directories `src/Resources/views/` and `src/Resources//views/` + * removed `TwigEngine` class, use `Twig\Environment` instead + * removed `FilesystemLoader` and `NativeFilesystemLoader`, use Twig notation for templates instead + * removed `twig.exception_controller` configuration option, use `framework.error_controller` option instead + * removed `ExceptionController`, `PreviewErrorController` and all built-in error templates in favor of the new error renderer mechanism + +4.4.0 +----- + + * marked the `TemplateIterator` as `internal` + * added HTML comment to beginning and end of `exception_full.html.twig` + * deprecated `ExceptionController` and `PreviewErrorController` controllers, use `ErrorController` from the `HttpKernel` component instead + * deprecated all built-in error templates in favor of the new error renderer mechanism + * deprecated `twig.exception_controller` configuration option, set it to "null" and use `framework.error_controller` configuration instead + +4.2.0 +----- + + * deprecated support for legacy templates directories `src/Resources/views/` and `src/Resources//views/`, use `templates/` and `templates/bundles//` instead. + +4.1.0 +----- + + * added priority to Twig extensions + * deprecated relying on the default value (`false`) of the `twig.strict_variables` configuration option. The `%kernel.debug%` parameter will be the new default in 5.0 + +4.0.0 +----- + + * removed `ContainerAwareRuntimeLoader` + 3.4.0 ----- diff --git a/lib/symfony/twig-bundle/CacheWarmer/TemplateCacheCacheWarmer.php b/lib/symfony/twig-bundle/CacheWarmer/TemplateCacheCacheWarmer.php deleted file mode 100644 index 03233c5985..0000000000 --- a/lib/symfony/twig-bundle/CacheWarmer/TemplateCacheCacheWarmer.php +++ /dev/null @@ -1,131 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\TwigBundle\CacheWarmer; - -use Psr\Container\ContainerInterface; -use Symfony\Bundle\FrameworkBundle\CacheWarmer\TemplateFinderInterface; -use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; -use Symfony\Component\Finder\Finder; -use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; -use Symfony\Component\Templating\TemplateReference; -use Twig\Environment; -use Twig\Error\Error; - -/** - * Generates the Twig cache for all templates. - * - * This warmer must be registered after TemplatePathsCacheWarmer, - * as the Twig loader will need the cache generated by it. - * - * @author Fabien Potencier - */ -class TemplateCacheCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInterface -{ - protected $container; - protected $finder; - private $paths; - - /** - * @param array $paths Additional twig paths to warm - */ - public function __construct(ContainerInterface $container, TemplateFinderInterface $finder = null, array $paths = []) - { - // We don't inject the Twig environment directly as it depends on the - // template locator (via the loader) which might be a cached one. - // The cached template locator is available once the TemplatePathsCacheWarmer - // has been warmed up. - // But it can also be null if templating has been disabled. - $this->container = $container; - $this->finder = $finder; - $this->paths = $paths; - } - - /** - * Warms up the cache. - * - * @param string $cacheDir The cache directory - */ - public function warmUp($cacheDir) - { - if (null === $this->finder) { - return; - } - - $twig = $this->container->get('twig'); - - $templates = $this->finder->findAllTemplates(); - - foreach ($this->paths as $path => $namespace) { - $templates = array_merge($templates, $this->findTemplatesInFolder($namespace, $path)); - } - - foreach ($templates as $template) { - if ('twig' !== $template->get('engine')) { - continue; - } - - try { - $twig->loadTemplate($template); - } catch (Error $e) { - // problem during compilation, give up - } - } - } - - /** - * Checks whether this warmer is optional or not. - * - * @return bool always true - */ - public function isOptional() - { - return true; - } - - /** - * {@inheritdoc} - */ - public static function getSubscribedServices() - { - return [ - 'twig' => Environment::class, - ]; - } - - /** - * Find templates in the given directory. - * - * @param string $namespace The namespace for these templates - * @param string $dir The folder where to look for templates - * - * @return array An array of templates of type TemplateReferenceInterface - */ - private function findTemplatesInFolder($namespace, $dir) - { - if (!is_dir($dir)) { - return []; - } - - $templates = []; - $finder = new Finder(); - - foreach ($finder->files()->followLinks()->in($dir) as $file) { - $name = $file->getRelativePathname(); - $templates[] = new TemplateReference( - $namespace ? sprintf('@%s/%s', $namespace, $name) : $name, - 'twig' - ); - } - - return $templates; - } -} diff --git a/lib/symfony/twig-bundle/CacheWarmer/TemplateCacheWarmer.php b/lib/symfony/twig-bundle/CacheWarmer/TemplateCacheWarmer.php index 329e018c2b..4a15dcf2fa 100644 --- a/lib/symfony/twig-bundle/CacheWarmer/TemplateCacheWarmer.php +++ b/lib/symfony/twig-bundle/CacheWarmer/TemplateCacheWarmer.php @@ -12,8 +12,8 @@ namespace Symfony\Bundle\TwigBundle\CacheWarmer; use Psr\Container\ContainerInterface; -use Symfony\Component\DependencyInjection\ServiceSubscriberInterface; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use Symfony\Contracts\Service\ServiceSubscriberInterface; use Twig\Environment; use Twig\Error\Error; @@ -28,43 +28,46 @@ class TemplateCacheWarmer implements CacheWarmerInterface, ServiceSubscriberInte private $twig; private $iterator; - /** - * TemplateCacheWarmer constructor. - * - * @param ContainerInterface $container - */ - public function __construct($container, \Traversable $iterator) + public function __construct(ContainerInterface $container, iterable $iterator) { // As this cache warmer is optional, dependencies should be lazy-loaded, that's why a container should be injected. - if ($container instanceof ContainerInterface) { - $this->container = $container; - } elseif ($container instanceof Environment) { - $this->twig = $container; - @trigger_error(sprintf('Using a "%s" as first argument of %s is deprecated since Symfony 3.4 and will be unsupported in version 4.0. Use a %s instead.', Environment::class, __CLASS__, ContainerInterface::class), \E_USER_DEPRECATED); - } else { - throw new \InvalidArgumentException(sprintf('"%s" only accepts instance of Psr\Container\ContainerInterface as first argument.', __CLASS__)); - } - + $this->container = $container; $this->iterator = $iterator; } /** * {@inheritdoc} + * + * @return string[] A list of template files to preload on PHP 7.4+ */ - public function warmUp($cacheDir) + public function warmUp(string $cacheDir) { if (null === $this->twig) { $this->twig = $this->container->get('twig'); } + $files = []; + foreach ($this->iterator as $template) { try { - $this->twig->loadTemplate($template); + $template = $this->twig->load($template); + + if (\is_callable([$template, 'unwrap'])) { + $files[] = (new \ReflectionClass($template->unwrap()))->getFileName(); + } } catch (Error $e) { - // problem during compilation, give up - // might be a syntax error or a non-Twig template + /* + * Problem during compilation, give up for this template (e.g. syntax errors). + * Failing silently here allows to ignore templates that rely on functions that aren't available in + * the current environment. For example, the WebProfilerBundle shouldn't be available in the prod + * environment, but some templates that are never used in prod might rely on functions the bundle provides. + * As we can't detect which templates are "really" important, we try to load all of them and ignore + * errors. Error checks may be performed by calling the lint:twig command. + */ } } + + return $files; } /** diff --git a/lib/symfony/twig-bundle/Command/DebugCommand.php b/lib/symfony/twig-bundle/Command/DebugCommand.php deleted file mode 100644 index c04a28a9f3..0000000000 --- a/lib/symfony/twig-bundle/Command/DebugCommand.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\TwigBundle\Command; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use Symfony\Bridge\Twig\Command\DebugCommand instead.', DebugCommand::class), \E_USER_DEPRECATED); - -use Symfony\Bridge\Twig\Command\DebugCommand as BaseDebugCommand; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; -use Symfony\Component\DependencyInjection\ContainerAwareTrait; - -/** - * Lists twig functions, filters, globals and tests present in the current project. - * - * @author Jordi Boggiano - * - * @deprecated since version 3.4, to be removed in 4.0. - */ -final class DebugCommand extends BaseDebugCommand implements ContainerAwareInterface -{ - use ContainerAwareTrait; - - /** - * {@inheritdoc} - */ - protected function getTwigEnvironment() - { - return $this->container->get('twig'); - } -} diff --git a/lib/symfony/twig-bundle/Command/LintCommand.php b/lib/symfony/twig-bundle/Command/LintCommand.php index 1474a4de0a..a0a52e28a0 100644 --- a/lib/symfony/twig-bundle/Command/LintCommand.php +++ b/lib/symfony/twig-bundle/Command/LintCommand.php @@ -12,8 +12,6 @@ namespace Symfony\Bundle\TwigBundle\Command; use Symfony\Bridge\Twig\Command\LintCommand as BaseLintCommand; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; -use Symfony\Component\DependencyInjection\ContainerAwareTrait; use Symfony\Component\Finder\Finder; /** @@ -22,10 +20,10 @@ use Symfony\Component\Finder\Finder; * @author Marc Weistroff * @author Jérôme Tamarelle */ -final class LintCommand extends BaseLintCommand implements ContainerAwareInterface +final class LintCommand extends BaseLintCommand { - // BC to be removed in 4.0 - use ContainerAwareTrait; + protected static $defaultName = 'lint:twig'; + protected static $defaultDescription = 'Lint a Twig template and outputs encountered errors'; /** * {@inheritdoc} @@ -47,9 +45,9 @@ EOF ; } - protected function findFiles($filename) + protected function findFiles(string $filename): iterable { - if (0 === strpos($filename, '@')) { + if (str_starts_with($filename, '@')) { $dir = $this->getApplication()->getKernel()->locateResource($filename); return Finder::create()->files()->in($dir)->name('*.twig'); diff --git a/lib/symfony/twig-bundle/ContainerAwareRuntimeLoader.php b/lib/symfony/twig-bundle/ContainerAwareRuntimeLoader.php deleted file mode 100644 index 47ec9a961f..0000000000 --- a/lib/symfony/twig-bundle/ContainerAwareRuntimeLoader.php +++ /dev/null @@ -1,55 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\TwigBundle; - -@trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use the Twig\RuntimeLoader\ContainerRuntimeLoader class instead.', ContainerAwareRuntimeLoader::class), \E_USER_DEPRECATED); - -use Psr\Log\LoggerInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Twig\RuntimeLoader\RuntimeLoaderInterface; - -/** - * Loads Twig extension runtimes via the service container. - * - * @author Fabien Potencier - * - * @deprecated since version 3.3, will be removed in 4.0. Use \Twig\Loader\ContainerRuntimeLoader instead. - */ -class ContainerAwareRuntimeLoader implements RuntimeLoaderInterface -{ - private $container; - private $mapping; - private $logger; - - public function __construct(ContainerInterface $container, array $mapping, LoggerInterface $logger = null) - { - $this->container = $container; - $this->mapping = $mapping; - $this->logger = $logger; - } - - /** - * {@inheritdoc} - */ - public function load($class) - { - if (isset($this->mapping[$class])) { - return $this->container->get($this->mapping[$class]); - } - - if (null !== $this->logger) { - $this->logger->warning(sprintf('Class "%s" is not configured as a Twig runtime. Add the "twig.runtime" tag to the related service in the container.', $class)); - } - - return null; - } -} diff --git a/lib/symfony/twig-bundle/Controller/ExceptionController.php b/lib/symfony/twig-bundle/Controller/ExceptionController.php deleted file mode 100644 index 22753af964..0000000000 --- a/lib/symfony/twig-bundle/Controller/ExceptionController.php +++ /dev/null @@ -1,148 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\TwigBundle\Controller; - -use Symfony\Component\Debug\Exception\FlattenException; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; -use Twig\Environment; -use Twig\Error\LoaderError; -use Twig\Loader\ExistsLoaderInterface; -use Twig\Loader\SourceContextLoaderInterface; - -/** - * ExceptionController renders error or exception pages for a given - * FlattenException. - * - * @author Fabien Potencier - * @author Matthias Pigulla - */ -class ExceptionController -{ - protected $twig; - protected $debug; - - /** - * @param bool $debug Show error (false) or exception (true) pages by default - */ - public function __construct(Environment $twig, $debug) - { - $this->twig = $twig; - $this->debug = $debug; - } - - /** - * Converts an Exception to a Response. - * - * A "showException" request parameter can be used to force display of an error page (when set to false) or - * the exception page (when true). If it is not present, the "debug" value passed into the constructor will - * be used. - * - * @return Response - * - * @throws \InvalidArgumentException When the exception template does not exist - */ - public function showAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null) - { - $currentContent = $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level', -1)); - $showException = $request->attributes->get('showException', $this->debug); // As opposed to an additional parameter, this maintains BC - - $code = $exception->getStatusCode(); - - return new Response($this->twig->render( - (string) $this->findTemplate($request, $request->getRequestFormat(), $code, $showException), - [ - 'status_code' => $code, - 'status_text' => isset(Response::$statusTexts[$code]) ? Response::$statusTexts[$code] : '', - 'exception' => $exception, - 'logger' => $logger, - 'currentContent' => $currentContent, - ] - ), 200, ['Content-Type' => $request->getMimeType($request->getRequestFormat()) ?: 'text/html']); - } - - /** - * @param int $startObLevel - * - * @return string - */ - protected function getAndCleanOutputBuffering($startObLevel) - { - if (ob_get_level() <= $startObLevel) { - return ''; - } - - Response::closeOutputBuffers($startObLevel + 1, true); - - return ob_get_clean(); - } - - /** - * @param string $format - * @param int $code An HTTP response status code - * @param bool $showException - * - * @return string - */ - protected function findTemplate(Request $request, $format, $code, $showException) - { - $name = $showException ? 'exception' : 'error'; - if ($showException && 'html' == $format) { - $name = 'exception_full'; - } - - // For error pages, try to find a template for the specific HTTP status code and format - if (!$showException) { - $template = sprintf('@Twig/Exception/%s%s.%s.twig', $name, $code, $format); - if ($this->templateExists($template)) { - return $template; - } - } - - // try to find a template for the given format - $template = sprintf('@Twig/Exception/%s.%s.twig', $name, $format); - if ($this->templateExists($template)) { - return $template; - } - - // default to a generic HTML exception - $request->setRequestFormat('html'); - - return sprintf('@Twig/Exception/%s.html.twig', $showException ? 'exception_full' : $name); - } - - // to be removed when the minimum required version of Twig is >= 2.0 - protected function templateExists($template) - { - $template = (string) $template; - - $loader = $this->twig->getLoader(); - - if (1 === Environment::MAJOR_VERSION && !$loader instanceof ExistsLoaderInterface) { - try { - if ($loader instanceof SourceContextLoaderInterface) { - $loader->getSourceContext($template); - } else { - $loader->getSource($template); - } - - return true; - } catch (LoaderError $e) { - } - - return false; - } - - return $loader->exists($template); - } -} diff --git a/lib/symfony/twig-bundle/Controller/PreviewErrorController.php b/lib/symfony/twig-bundle/Controller/PreviewErrorController.php deleted file mode 100644 index c529cfbb46..0000000000 --- a/lib/symfony/twig-bundle/Controller/PreviewErrorController.php +++ /dev/null @@ -1,56 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\TwigBundle\Controller; - -use Symfony\Component\Debug\Exception\FlattenException; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\HttpKernelInterface; - -/** - * PreviewErrorController can be used to test error pages. - * - * It will create a test exception and forward it to another controller. - * - * @author Matthias Pigulla - */ -class PreviewErrorController -{ - protected $kernel; - protected $controller; - - public function __construct(HttpKernelInterface $kernel, $controller) - { - $this->kernel = $kernel; - $this->controller = $controller; - } - - public function previewErrorPageAction(Request $request, $code) - { - $exception = FlattenException::create(new \Exception('Something has intentionally gone wrong.'), $code); - - /* - * This Request mimics the parameters set by - * \Symfony\Component\HttpKernel\EventListener\ExceptionListener::duplicateRequest, with - * the additional "showException" flag. - */ - - $subRequest = $request->duplicate(null, null, [ - '_controller' => $this->controller, - 'exception' => $exception, - 'logger' => null, - 'format' => $request->getRequestFormat(), - 'showException' => false, - ]); - - return $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST); - } -} diff --git a/lib/symfony/twig-bundle/DependencyInjection/Compiler/ExceptionListenerPass.php b/lib/symfony/twig-bundle/DependencyInjection/Compiler/ExceptionListenerPass.php deleted file mode 100644 index 6b6149ad57..0000000000 --- a/lib/symfony/twig-bundle/DependencyInjection/Compiler/ExceptionListenerPass.php +++ /dev/null @@ -1,40 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler; - -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\ContainerBuilder; - -/** - * Registers the Twig exception listener if Twig is registered as a templating engine. - * - * @author Fabien Potencier - */ -class ExceptionListenerPass implements CompilerPassInterface -{ - public function process(ContainerBuilder $container) - { - if (false === $container->hasDefinition('twig')) { - return; - } - - // register the exception controller only if Twig is enabled and required dependencies do exist - if (!class_exists('Symfony\Component\Debug\Exception\FlattenException') || !interface_exists('Symfony\Component\EventDispatcher\EventSubscriberInterface')) { - $container->removeDefinition('twig.exception_listener'); - } elseif ($container->hasParameter('templating.engines')) { - $engines = $container->getParameter('templating.engines'); - if (!\in_array('twig', $engines)) { - $container->removeDefinition('twig.exception_listener'); - } - } - } -} diff --git a/lib/symfony/twig-bundle/DependencyInjection/Compiler/ExtensionPass.php b/lib/symfony/twig-bundle/DependencyInjection/Compiler/ExtensionPass.php index 79a6ad9ae8..12724e0f1c 100644 --- a/lib/symfony/twig-bundle/DependencyInjection/Compiler/ExtensionPass.php +++ b/lib/symfony/twig-bundle/DependencyInjection/Compiler/ExtensionPass.php @@ -11,11 +11,14 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler; +use Symfony\Component\Asset\Packages; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Workflow\Workflow; +use Symfony\Component\Yaml\Yaml; /** * @author Jean-François Simon @@ -24,40 +27,44 @@ class ExtensionPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { - if (!class_exists('Symfony\Component\Asset\Packages')) { + if (!class_exists(Packages::class)) { $container->removeDefinition('twig.extension.assets'); } - if (!class_exists('Symfony\Component\ExpressionLanguage\Expression')) { + if (!class_exists(Expression::class)) { $container->removeDefinition('twig.extension.expression'); } - if (!interface_exists('Symfony\Component\Routing\Generator\UrlGeneratorInterface')) { + if (!interface_exists(UrlGeneratorInterface::class)) { $container->removeDefinition('twig.extension.routing'); } - if (!class_exists('Symfony\Component\Yaml\Yaml')) { + if (!class_exists(Yaml::class)) { $container->removeDefinition('twig.extension.yaml'); } + $viewDir = \dirname((new \ReflectionClass(\Symfony\Bridge\Twig\Extension\FormExtension::class))->getFileName(), 2).'/Resources/views'; + $templateIterator = $container->getDefinition('twig.template_iterator'); + $templatePaths = $templateIterator->getArgument(1); + $loader = $container->getDefinition('twig.loader.native_filesystem'); + + if ($container->has('mailer')) { + $emailPath = $viewDir.'/Email'; + $loader->addMethodCall('addPath', [$emailPath, 'email']); + $loader->addMethodCall('addPath', [$emailPath, '!email']); + $templatePaths[$emailPath] = 'email'; + } + if ($container->has('form.extension')) { $container->getDefinition('twig.extension.form')->addTag('twig.extension'); - $reflClass = new \ReflectionClass('Symfony\Bridge\Twig\Extension\FormExtension'); - $coreThemePath = \dirname(\dirname($reflClass->getFileName())).'/Resources/views/Form'; - $container->getDefinition('twig.loader.native_filesystem')->addMethodCall('addPath', [$coreThemePath]); - - $paths = $container->getDefinition('twig.template_iterator')->getArgument(2); - $paths[$coreThemePath] = null; - $container->getDefinition('twig.template_iterator')->replaceArgument(2, $paths); - - if ($container->hasDefinition('twig.cache_warmer')) { - $paths = $container->getDefinition('twig.cache_warmer')->getArgument(2); - $paths[$coreThemePath] = null; - $container->getDefinition('twig.cache_warmer')->replaceArgument(2, $paths); - } + $coreThemePath = $viewDir.'/Form'; + $loader->addMethodCall('addPath', [$coreThemePath]); + $templatePaths[$coreThemePath] = null; } + $templateIterator->replaceArgument(1, $templatePaths); + if ($container->has('router')) { $container->getDefinition('twig.extension.routing')->addTag('twig.extension'); } @@ -66,19 +73,13 @@ class ExtensionPass implements CompilerPassInterface $container->getDefinition('twig.extension.httpkernel')->addTag('twig.extension'); $container->getDefinition('twig.runtime.httpkernel')->addTag('twig.runtime'); - // inject Twig in the hinclude service if Twig is the only registered templating engine - if ((!$container->hasParameter('templating.engines') || ['twig'] == $container->getParameter('templating.engines')) && $container->hasDefinition('fragment.renderer.hinclude')) { + if ($container->hasDefinition('fragment.renderer.hinclude')) { $container->getDefinition('fragment.renderer.hinclude') ->addTag('kernel.fragment_renderer', ['alias' => 'hinclude']) - ->replaceArgument(0, new Reference('twig')) ; } } - if (!$container->has('http_kernel')) { - $container->removeDefinition('twig.controller.preview_error'); - } - if ($container->has('request_stack')) { $container->getDefinition('twig.extension.httpfoundation')->addTag('twig.extension'); } @@ -96,16 +97,7 @@ class ExtensionPass implements CompilerPassInterface $container->getDefinition('twig.extension.weblink')->addTag('twig.extension'); } - $twigLoader = $container->getDefinition('twig.loader.native_filesystem'); - if ($container->has('templating')) { - $loader = $container->getDefinition('twig.loader.filesystem'); - $loader->setMethodCalls(array_merge($twigLoader->getMethodCalls(), $loader->getMethodCalls())); - - $twigLoader->clearTag('twig.loader'); - } else { - $container->setAlias('twig.loader.filesystem', new Alias('twig.loader.native_filesystem', false)); - $container->removeDefinition('templating.engine.twig'); - } + $container->setAlias('twig.loader.filesystem', new Alias('twig.loader.native_filesystem', false)); if ($container->has('assets.packages')) { $container->getDefinition('twig.extension.assets')->addTag('twig.extension'); @@ -115,7 +107,7 @@ class ExtensionPass implements CompilerPassInterface $container->getDefinition('twig.extension.yaml')->addTag('twig.extension'); } - if (class_exists('Symfony\Component\Stopwatch\Stopwatch')) { + if (class_exists(\Symfony\Component\Stopwatch\Stopwatch::class)) { $container->getDefinition('twig.extension.debug.stopwatch')->addTag('twig.extension'); } @@ -128,5 +120,10 @@ class ExtensionPass implements CompilerPassInterface } else { $container->getDefinition('workflow.twig_extension')->addTag('twig.extension'); } + + if ($container->has('serializer')) { + $container->getDefinition('twig.runtime.serializer')->addTag('twig.runtime'); + $container->getDefinition('twig.extension.serializer')->addTag('twig.extension'); + } } } diff --git a/lib/symfony/twig-bundle/DependencyInjection/Compiler/TwigEnvironmentPass.php b/lib/symfony/twig-bundle/DependencyInjection/Compiler/TwigEnvironmentPass.php index be2c45a787..45413dc932 100644 --- a/lib/symfony/twig-bundle/DependencyInjection/Compiler/TwigEnvironmentPass.php +++ b/lib/symfony/twig-bundle/DependencyInjection/Compiler/TwigEnvironmentPass.php @@ -12,8 +12,8 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; /** * Adds tagged twig.extension services to twig service. @@ -22,6 +22,8 @@ use Symfony\Component\DependencyInjection\Reference; */ class TwigEnvironmentPass implements CompilerPassInterface { + use PriorityTaggedServiceTrait; + public function process(ContainerBuilder $container) { if (false === $container->hasDefinition('twig')) { @@ -37,11 +39,11 @@ class TwigEnvironmentPass implements CompilerPassInterface $currentMethodCalls = $definition->getMethodCalls(); $twigBridgeExtensionsMethodCalls = []; $othersExtensionsMethodCalls = []; - foreach ($container->findTaggedServiceIds('twig.extension', true) as $id => $attributes) { - $methodCall = ['addExtension', [new Reference($id)]]; - $extensionClass = $container->getDefinition($id)->getClass(); + foreach ($this->findAndSortTaggedServices('twig.extension', $container) as $extension) { + $methodCall = ['addExtension', [$extension]]; + $extensionClass = $container->getDefinition((string) $extension)->getClass(); - if (\is_string($extensionClass) && 0 === strpos($extensionClass, 'Symfony\Bridge\Twig\Extension')) { + if (\is_string($extensionClass) && str_starts_with($extensionClass, 'Symfony\Bridge\Twig\Extension')) { $twigBridgeExtensionsMethodCalls[] = $methodCall; } else { $othersExtensionsMethodCalls[] = $methodCall; diff --git a/lib/symfony/twig-bundle/DependencyInjection/Compiler/TwigLoaderPass.php b/lib/symfony/twig-bundle/DependencyInjection/Compiler/TwigLoaderPass.php index bd0c606a86..a422f66825 100644 --- a/lib/symfony/twig-bundle/DependencyInjection/Compiler/TwigLoaderPass.php +++ b/lib/symfony/twig-bundle/DependencyInjection/Compiler/TwigLoaderPass.php @@ -33,7 +33,7 @@ class TwigLoaderPass implements CompilerPassInterface $found = 0; foreach ($container->findTaggedServiceIds('twig.loader', true) as $id => $attributes) { - $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0; + $priority = $attributes[0]['priority'] ?? 0; $prioritizedLoaders[$priority][] = $id; ++$found; } @@ -43,7 +43,7 @@ class TwigLoaderPass implements CompilerPassInterface } if (1 === $found) { - $container->setAlias('twig.loader', $id)->setPrivate(true); + $container->setAlias('twig.loader', $id); } else { $chainLoader = $container->getDefinition('twig.loader.chain'); krsort($prioritizedLoaders); @@ -54,7 +54,7 @@ class TwigLoaderPass implements CompilerPassInterface } } - $container->setAlias('twig.loader', 'twig.loader.chain')->setPrivate(true); + $container->setAlias('twig.loader', 'twig.loader.chain'); } } } diff --git a/lib/symfony/twig-bundle/DependencyInjection/Configuration.php b/lib/symfony/twig-bundle/DependencyInjection/Configuration.php index 4981905cde..76faa0107e 100644 --- a/lib/symfony/twig-bundle/DependencyInjection/Configuration.php +++ b/lib/symfony/twig-bundle/DependencyInjection/Configuration.php @@ -14,6 +14,7 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; /** * TwigExtension configuration structure. @@ -25,18 +26,25 @@ class Configuration implements ConfigurationInterface /** * Generates the configuration tree builder. * - * @return TreeBuilder The tree builder + * @return TreeBuilder */ public function getConfigTreeBuilder() { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('twig'); + $treeBuilder = new TreeBuilder('twig'); + $rootNode = $treeBuilder->getRootNode(); - $rootNode - ->children() - ->scalarNode('exception_controller')->defaultValue('twig.controller.exception:showAction')->end() - ->end() - ; + $rootNode->beforeNormalization() + ->ifTrue(function ($v) { return \is_array($v) && \array_key_exists('exception_controller', $v); }) + ->then(function ($v) { + if (isset($v['exception_controller'])) { + throw new InvalidConfigurationException('Option "exception_controller" under "twig" must be null or unset, use "error_controller" under "framework" instead.'); + } + + unset($v['exception_controller']); + + return $v; + }) + ->end(); $this->addFormThemesSection($rootNode); $this->addGlobalsSection($rootNode); @@ -54,7 +62,7 @@ class Configuration implements ConfigurationInterface ->arrayNode('form_themes') ->addDefaultChildrenIfNoneSet() ->prototype('scalar')->defaultValue('form_div_layout.html.twig')->end() - ->example(['MyBundle::form.html.twig']) + ->example(['@My/form.html.twig']) ->validate() ->ifTrue(function ($v) { return !\in_array('form_div_layout.html.twig', $v); }) ->then(function ($v) { @@ -74,12 +82,13 @@ class Configuration implements ConfigurationInterface ->arrayNode('globals') ->normalizeKeys(false) ->useAttributeAsKey('key') - ->example(['foo' => '"@bar"', 'pi' => 3.14]) + ->example(['foo' => '@bar', 'pi' => 3.14]) ->prototype('array') + ->normalizeKeys(false) ->beforeNormalization() - ->ifTrue(function ($v) { return \is_string($v) && 0 === strpos($v, '@'); }) + ->ifTrue(function ($v) { return \is_string($v) && str_starts_with($v, '@'); }) ->then(function ($v) { - if (0 === strpos($v, '@@')) { + if (str_starts_with($v, '@@')) { return substr($v, 1); } @@ -127,7 +136,7 @@ class Configuration implements ConfigurationInterface ->scalarNode('cache')->defaultValue('%kernel.cache_dir%/twig')->end() ->scalarNode('charset')->defaultValue('%kernel.charset%')->end() ->booleanNode('debug')->defaultValue('%kernel.debug%')->end() - ->booleanNode('strict_variables')->end() + ->booleanNode('strict_variables')->defaultValue('%kernel.debug%')->end() ->scalarNode('auto_reload')->end() ->integerNode('optimizations')->min(-1)->end() ->scalarNode('default_path') diff --git a/lib/symfony/twig-bundle/DependencyInjection/Configurator/EnvironmentConfigurator.php b/lib/symfony/twig-bundle/DependencyInjection/Configurator/EnvironmentConfigurator.php index 13ad88ef6b..07ec691769 100644 --- a/lib/symfony/twig-bundle/DependencyInjection/Configurator/EnvironmentConfigurator.php +++ b/lib/symfony/twig-bundle/DependencyInjection/Configurator/EnvironmentConfigurator.php @@ -15,7 +15,7 @@ use Symfony\Bridge\Twig\UndefinedCallableHandler; use Twig\Environment; // BC/FC with namespaced Twig -class_exists('Twig\Environment'); +class_exists(Environment::class); /** * Twig environment configurator. @@ -31,7 +31,7 @@ class EnvironmentConfigurator private $decimalPoint; private $thousandsSeparator; - public function __construct($dateFormat, $intervalFormat, $timezone, $decimals, $decimalPoint, $thousandsSeparator) + public function __construct(string $dateFormat, string $intervalFormat, ?string $timezone, int $decimals, string $decimalPoint, string $thousandsSeparator) { $this->dateFormat = $dateFormat; $this->intervalFormat = $intervalFormat; diff --git a/lib/symfony/twig-bundle/DependencyInjection/TwigExtension.php b/lib/symfony/twig-bundle/DependencyInjection/TwigExtension.php index 52ba0503e4..4cec78064f 100644 --- a/lib/symfony/twig-bundle/DependencyInjection/TwigExtension.php +++ b/lib/symfony/twig-bundle/DependencyInjection/TwigExtension.php @@ -11,13 +11,19 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection; +use Composer\InstalledVersions; use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\Resource\FileExistenceResource; use Symfony\Component\Console\Application; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Form\AbstractRendererEngine; +use Symfony\Component\Form\Form; use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Translation\Translator; +use Symfony\Contracts\Service\ResetInterface; use Twig\Extension\ExtensionInterface; use Twig\Extension\RuntimeExtensionInterface; use Twig\Loader\LoaderInterface; @@ -32,32 +38,32 @@ class TwigExtension extends Extension { public function load(array $configs, ContainerBuilder $container) { - $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); - $loader->load('twig.xml'); - - $container->getDefinition('twig.profile')->setPrivate(true); - $container->getDefinition('twig.translation.extractor')->setPrivate(true); - $container->getDefinition('workflow.twig_extension')->setPrivate(true); - $container->getDefinition('twig.exception_listener')->setPrivate(true); - - if ($container->has('fragment.handler')) { - $container->getDefinition('twig.runtime.httpkernel')->setPrivate(true); + if (!class_exists(InstalledVersions::class)) { + trigger_deprecation('symfony/twig-bundle', '5.4', 'Configuring Symfony without the Composer Runtime API is deprecated. Consider upgrading to Composer 2.1 or later.'); } - if (class_exists('Symfony\Component\Form\Form')) { - $loader->load('form.xml'); - $container->getDefinition('twig.form.renderer')->setPrivate(true); + $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('twig.php'); + + if ($container::willBeAvailable('symfony/form', Form::class, ['symfony/twig-bundle'], true)) { + $loader->load('form.php'); + + if (is_subclass_of(AbstractRendererEngine::class, ResetInterface::class)) { + $container->getDefinition('twig.form.engine')->addTag('kernel.reset', [ + 'method' => 'reset', + ]); + } } - if (interface_exists('Symfony\Component\Templating\EngineInterface')) { - $loader->load('templating.xml'); + if ($container::willBeAvailable('symfony/console', Application::class, ['symfony/twig-bundle'], true)) { + $loader->load('console.php'); } - if (class_exists(Application::class)) { - $loader->load('console.xml'); + if ($container::willBeAvailable('symfony/mailer', Mailer::class, ['symfony/twig-bundle'], true)) { + $loader->load('mailer.php'); } - if (!interface_exists('Symfony\Component\Translation\TranslatorInterface')) { + if (!$container::willBeAvailable('symfony/translation', Translator::class, ['symfony/twig-bundle'], true)) { $container->removeDefinition('twig.translation.extractor'); } @@ -78,10 +84,9 @@ class TwigExtension extends Extension $config = $this->processConfiguration($configuration, $configs); - $container->setParameter('twig.exception_listener.controller', $config['exception_controller']); - $container->setParameter('twig.form.resources', $config['form_themes']); $container->setParameter('twig.default_path', $config['default_path']); + $defaultTwigPath = $container->getParameterBag()->resolveValue($config['default_path']); $envConfiguratorDefinition = $container->getDefinition('twig.configurator.environment'); $envConfiguratorDefinition->replaceArgument(0, $config['date']['format']); @@ -103,41 +108,24 @@ class TwigExtension extends Extension } // paths are modified in ExtensionPass if forms are enabled - $container->getDefinition('twig.cache_warmer')->replaceArgument(2, $config['paths']); - $container->getDefinition('twig.template_iterator')->replaceArgument(2, $config['paths']); + $container->getDefinition('twig.template_iterator')->replaceArgument(1, $config['paths']); - $bundleHierarchy = $this->getBundleHierarchy($container, $config); - - foreach ($bundleHierarchy as $name => $bundle) { + foreach ($this->getBundleTemplatePaths($container, $config) as $name => $paths) { $namespace = $this->normalizeBundleName($name); - - foreach ($bundle['children'] as $child) { - foreach ($bundleHierarchy[$child]['paths'] as $path) { - $twigFilesystemLoaderDefinition->addMethodCall('addPath', [$path, $namespace]); - } - } - - foreach ($bundle['paths'] as $path) { + foreach ($paths as $path) { $twigFilesystemLoaderDefinition->addMethodCall('addPath', [$path, $namespace]); } - // add exclusive namespace for root bundles only - // to override a bundle template that also extends itself - if (\count($bundle['paths']) > 0 && 0 === \count($bundle['parents'])) { + if ($paths) { // the last path must be the bundle views directory - $twigFilesystemLoaderDefinition->addMethodCall('addPath', [end($bundle['paths']), '!'.$namespace]); + $twigFilesystemLoaderDefinition->addMethodCall('addPath', [$path, '!'.$namespace]); } } - if (file_exists($dir = $container->getParameter('kernel.root_dir').'/Resources/views')) { - $twigFilesystemLoaderDefinition->addMethodCall('addPath', [$dir]); + if (file_exists($defaultTwigPath)) { + $twigFilesystemLoaderDefinition->addMethodCall('addPath', [$defaultTwigPath]); } - $container->addResource(new FileExistenceResource($dir)); - - if (file_exists($dir = $container->getParameterBag()->resolveValue($config['default_path']))) { - $twigFilesystemLoaderDefinition->addMethodCall('addPath', [$dir]); - } - $container->addResource(new FileExistenceResource($dir)); + $container->addResource(new FileExistenceResource($defaultTwigPath)); if (!empty($config['globals'])) { $def = $container->getDefinition('twig'); @@ -172,84 +160,33 @@ class TwigExtension extends Extension $container->registerForAutoconfiguration(RuntimeExtensionInterface::class)->addTag('twig.runtime'); if (false === $config['cache']) { - $container->removeDefinition('twig.cache_warmer'); $container->removeDefinition('twig.template_cache_warmer'); } - - if (\PHP_VERSION_ID < 70000) { - $this->addClassesToCompile([ - 'Twig_Environment', - 'Twig_Extension', - 'Twig_Extension_Core', - 'Twig_Extension_Escaper', - 'Twig_Extension_Optimizer', - 'Twig_LoaderInterface', - 'Twig_Markup', - 'Twig_Template', - ]); - } } - private function getBundleHierarchy(ContainerBuilder $container, array $config) + private function getBundleTemplatePaths(ContainerBuilder $container, array $config): array { $bundleHierarchy = []; - foreach ($container->getParameter('kernel.bundles_metadata') as $name => $bundle) { - if (!\array_key_exists($name, $bundleHierarchy)) { - $bundleHierarchy[$name] = [ - 'paths' => [], - 'parents' => [], - 'children' => [], - ]; - } + $defaultOverrideBundlePath = $container->getParameterBag()->resolveValue($config['default_path']).'/bundles/'.$name; - if (file_exists($dir = $container->getParameter('kernel.root_dir').'/Resources/'.$name.'/views')) { - $bundleHierarchy[$name]['paths'][] = $dir; + if (file_exists($defaultOverrideBundlePath)) { + $bundleHierarchy[$name][] = $defaultOverrideBundlePath; + } + $container->addResource(new FileExistenceResource($defaultOverrideBundlePath)); + + if (file_exists($dir = $bundle['path'].'/Resources/views') || file_exists($dir = $bundle['path'].'/templates')) { + $bundleHierarchy[$name][] = $dir; } $container->addResource(new FileExistenceResource($dir)); - - if (file_exists($dir = $container->getParameterBag()->resolveValue($config['default_path']).'/bundles/'.$name)) { - $bundleHierarchy[$name]['paths'][] = $dir; - } - $container->addResource(new FileExistenceResource($dir)); - - if (file_exists($dir = $bundle['path'].'/Resources/views')) { - $bundleHierarchy[$name]['paths'][] = $dir; - } - $container->addResource(new FileExistenceResource($dir)); - - if (!isset($bundle['parent']) || null === $bundle['parent']) { - continue; - } - - $bundleHierarchy[$name]['parents'][] = $bundle['parent']; - - if (!\array_key_exists($bundle['parent'], $bundleHierarchy)) { - $bundleHierarchy[$bundle['parent']] = [ - 'paths' => [], - 'parents' => [], - 'children' => [], - ]; - } - - $bundleHierarchy[$bundle['parent']]['children'] = array_merge($bundleHierarchy[$name]['children'], [$name], $bundleHierarchy[$bundle['parent']]['children']); - - foreach ($bundleHierarchy[$bundle['parent']]['parents'] as $parent) { - $bundleHierarchy[$name]['parents'][] = $parent; - $bundleHierarchy[$parent]['children'] = array_merge($bundleHierarchy[$name]['children'], [$name], $bundleHierarchy[$parent]['children']); - } - - foreach ($bundleHierarchy[$name]['children'] as $child) { - $bundleHierarchy[$child]['parents'] = array_merge($bundleHierarchy[$child]['parents'], $bundleHierarchy[$name]['parents']); - } } return $bundleHierarchy; } - private function normalizeBundleName($name) + private function normalizeBundleName(string $name): string { - if ('Bundle' === substr($name, -6)) { + if (str_ends_with($name, 'Bundle')) { $name = substr($name, 0, -6); } diff --git a/lib/symfony/twig-bundle/LICENSE b/lib/symfony/twig-bundle/LICENSE index 9e936ec044..88bf75bb4d 100644 --- a/lib/symfony/twig-bundle/LICENSE +++ b/lib/symfony/twig-bundle/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2020 Fabien Potencier +Copyright (c) 2004-2022 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/lib/symfony/twig-bundle/Loader/FilesystemLoader.php b/lib/symfony/twig-bundle/Loader/FilesystemLoader.php deleted file mode 100644 index 61dadacc67..0000000000 --- a/lib/symfony/twig-bundle/Loader/FilesystemLoader.php +++ /dev/null @@ -1,98 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\TwigBundle\Loader; - -use Symfony\Component\Config\FileLocatorInterface; -use Symfony\Component\Templating\TemplateNameParserInterface; -use Symfony\Component\Templating\TemplateReferenceInterface; -use Twig\Error\LoaderError; -use Twig\Loader\FilesystemLoader as BaseFilesystemLoader; - -/** - * FilesystemLoader extends the default Twig filesystem loader - * to work with the Symfony paths and template references. - * - * @author Fabien Potencier - */ -class FilesystemLoader extends BaseFilesystemLoader -{ - protected $locator; - protected $parser; - - /** - * @param string|null $rootPath The root path common to all relative paths (null for getcwd()) - */ - public function __construct(FileLocatorInterface $locator, TemplateNameParserInterface $parser, $rootPath = null) - { - parent::__construct([], $rootPath); - - $this->locator = $locator; - $this->parser = $parser; - } - - /** - * {@inheritdoc} - * - * The name parameter might also be a TemplateReferenceInterface. - */ - public function exists($name) - { - return parent::exists((string) $name); - } - - /** - * Returns the path to the template file. - * - * The file locator is used to locate the template when the naming convention - * is the symfony one (i.e. the name can be parsed). - * Otherwise the template is located using the locator from the twig library. - * - * @param string|TemplateReferenceInterface $template The template - * @param bool $throw When true, a LoaderError exception will be thrown if a template could not be found - * - * @return string The path to the template file - * - * @throws LoaderError if the template could not be found - */ - protected function findTemplate($template, $throw = true) - { - $logicalName = (string) $template; - - if (isset($this->cache[$logicalName])) { - return $this->cache[$logicalName]; - } - - $file = null; - try { - $file = parent::findTemplate($logicalName); - } catch (LoaderError $e) { - $twigLoaderException = $e; - - // for BC - try { - $template = $this->parser->parse($template); - $file = $this->locator->locate($template); - } catch (\Exception $e) { - } - } - - if (false === $file || null === $file) { - if ($throw) { - throw $twigLoaderException; - } - - return false; - } - - return $this->cache[$logicalName] = $file; - } -} diff --git a/lib/symfony/twig-bundle/README.md b/lib/symfony/twig-bundle/README.md index a979325666..3ae2985bae 100644 --- a/lib/symfony/twig-bundle/README.md +++ b/lib/symfony/twig-bundle/README.md @@ -1,10 +1,13 @@ TwigBundle ========== +TwigBundle provides a tight integration of Twig into the Symfony full-stack +framework. + Resources --------- - * [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) + * [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/lib/symfony/twig-bundle/Resources/config/console.php b/lib/symfony/twig-bundle/Resources/config/console.php new file mode 100644 index 0000000000..0dc7ebdb7a --- /dev/null +++ b/lib/symfony/twig-bundle/Resources/config/console.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\DependencyInjection\Loader\Configurator; + +use Symfony\Bridge\Twig\Command\DebugCommand; +use Symfony\Bundle\TwigBundle\Command\LintCommand; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('twig.command.debug', DebugCommand::class) + ->args([ + service('twig'), + param('kernel.project_dir'), + param('kernel.bundles_metadata'), + param('twig.default_path'), + service('debug.file_link_formatter')->nullOnInvalid(), + ]) + ->tag('console.command') + + ->set('twig.command.lint', LintCommand::class) + ->args([service('twig')]) + ->tag('console.command') + ; +}; diff --git a/lib/symfony/twig-bundle/Resources/config/console.xml b/lib/symfony/twig-bundle/Resources/config/console.xml deleted file mode 100644 index 9d5515a7b5..0000000000 --- a/lib/symfony/twig-bundle/Resources/config/console.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - %kernel.project_dir% - - - - - - - - - diff --git a/lib/symfony/twig-bundle/Resources/config/form.php b/lib/symfony/twig-bundle/Resources/config/form.php new file mode 100644 index 0000000000..9f2efdf941 --- /dev/null +++ b/lib/symfony/twig-bundle/Resources/config/form.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bridge\Twig\Extension\FormExtension; +use Symfony\Bridge\Twig\Form\TwigRendererEngine; +use Symfony\Component\Form\FormRenderer; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('twig.extension.form', FormExtension::class) + ->args([service('translator')->nullOnInvalid()]) + + ->set('twig.form.engine', TwigRendererEngine::class) + ->args([param('twig.form.resources'), service('twig')]) + + ->set('twig.form.renderer', FormRenderer::class) + ->args([service('twig.form.engine'), service('security.csrf.token_manager')->nullOnInvalid()]) + ->tag('twig.runtime') + ; +}; diff --git a/lib/symfony/twig-bundle/Resources/config/form.xml b/lib/symfony/twig-bundle/Resources/config/form.xml deleted file mode 100644 index 4177da62de..0000000000 --- a/lib/symfony/twig-bundle/Resources/config/form.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - twig.form.renderer - - - - - %twig.form.resources% - - - - - - - - - - diff --git a/lib/symfony/twig-bundle/Resources/config/mailer.php b/lib/symfony/twig-bundle/Resources/config/mailer.php new file mode 100644 index 0000000000..1444481f2c --- /dev/null +++ b/lib/symfony/twig-bundle/Resources/config/mailer.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bridge\Twig\Mime\BodyRenderer; +use Symfony\Component\Mailer\EventListener\MessageListener; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('twig.mailer.message_listener', MessageListener::class) + ->args([null, service('twig.mime_body_renderer')]) + ->tag('kernel.event_subscriber') + + ->set('twig.mime_body_renderer', BodyRenderer::class) + ->args([service('twig')]) + ; +}; diff --git a/lib/symfony/twig-bundle/Resources/config/routing/errors.xml b/lib/symfony/twig-bundle/Resources/config/routing/errors.xml deleted file mode 100644 index a4b76f18cc..0000000000 --- a/lib/symfony/twig-bundle/Resources/config/routing/errors.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - twig.controller.preview_error:previewErrorPageAction - html - \d+ - - diff --git a/lib/symfony/twig-bundle/Resources/config/schema/twig-1.0.xsd b/lib/symfony/twig-bundle/Resources/config/schema/twig-1.0.xsd index 3e491f7029..429c91db67 100644 --- a/lib/symfony/twig-bundle/Resources/config/schema/twig-1.0.xsd +++ b/lib/symfony/twig-bundle/Resources/config/schema/twig-1.0.xsd @@ -25,7 +25,6 @@ - diff --git a/lib/symfony/twig-bundle/Resources/config/templating.xml b/lib/symfony/twig-bundle/Resources/config/templating.xml deleted file mode 100644 index 1eba213f0e..0000000000 --- a/lib/symfony/twig-bundle/Resources/config/templating.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - %kernel.project_dir% - - - - - - - - - - diff --git a/lib/symfony/twig-bundle/Resources/config/twig.php b/lib/symfony/twig-bundle/Resources/config/twig.php new file mode 100644 index 0000000000..3bc7f66fab --- /dev/null +++ b/lib/symfony/twig-bundle/Resources/config/twig.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Psr\Container\ContainerInterface; +use Symfony\Bridge\Twig\AppVariable; +use Symfony\Bridge\Twig\DataCollector\TwigDataCollector; +use Symfony\Bridge\Twig\ErrorRenderer\TwigErrorRenderer; +use Symfony\Bridge\Twig\Extension\AssetExtension; +use Symfony\Bridge\Twig\Extension\CodeExtension; +use Symfony\Bridge\Twig\Extension\ExpressionExtension; +use Symfony\Bridge\Twig\Extension\HttpFoundationExtension; +use Symfony\Bridge\Twig\Extension\HttpKernelExtension; +use Symfony\Bridge\Twig\Extension\HttpKernelRuntime; +use Symfony\Bridge\Twig\Extension\ProfilerExtension; +use Symfony\Bridge\Twig\Extension\RoutingExtension; +use Symfony\Bridge\Twig\Extension\SerializerExtension; +use Symfony\Bridge\Twig\Extension\SerializerRuntime; +use Symfony\Bridge\Twig\Extension\StopwatchExtension; +use Symfony\Bridge\Twig\Extension\TranslationExtension; +use Symfony\Bridge\Twig\Extension\WebLinkExtension; +use Symfony\Bridge\Twig\Extension\WorkflowExtension; +use Symfony\Bridge\Twig\Extension\YamlExtension; +use Symfony\Bridge\Twig\Translation\TwigExtractor; +use Symfony\Bundle\TwigBundle\CacheWarmer\TemplateCacheWarmer; +use Symfony\Bundle\TwigBundle\DependencyInjection\Configurator\EnvironmentConfigurator; +use Symfony\Bundle\TwigBundle\TemplateIterator; +use Twig\Cache\FilesystemCache; +use Twig\Environment; +use Twig\Extension\CoreExtension; +use Twig\Extension\DebugExtension; +use Twig\Extension\EscaperExtension; +use Twig\Extension\OptimizerExtension; +use Twig\Extension\StagingExtension; +use Twig\ExtensionSet; +use Twig\Loader\ChainLoader; +use Twig\Loader\FilesystemLoader; +use Twig\Profiler\Profile; +use Twig\RuntimeLoader\ContainerRuntimeLoader; +use Twig\Template; +use Twig\TemplateWrapper; + +return static function (ContainerConfigurator $container) { + $container->services() + ->set('twig', Environment::class) + ->public() + ->args([service('twig.loader'), abstract_arg('Twig options')]) + ->call('addGlobal', ['app', service('twig.app_variable')]) + ->call('addRuntimeLoader', [service('twig.runtime_loader')]) + ->configurator([service('twig.configurator.environment'), 'configure']) + ->tag('container.preload', ['class' => FilesystemCache::class]) + ->tag('container.preload', ['class' => CoreExtension::class]) + ->tag('container.preload', ['class' => EscaperExtension::class]) + ->tag('container.preload', ['class' => OptimizerExtension::class]) + ->tag('container.preload', ['class' => StagingExtension::class]) + ->tag('container.preload', ['class' => ExtensionSet::class]) + ->tag('container.preload', ['class' => Template::class]) + ->tag('container.preload', ['class' => TemplateWrapper::class]) + ->tag('container.private', ['package' => 'symfony/twig-bundle', 'version' => '5.2']) + + ->alias('Twig_Environment', 'twig') + ->alias(Environment::class, 'twig') + + ->set('twig.app_variable', AppVariable::class) + ->call('setEnvironment', [param('kernel.environment')]) + ->call('setDebug', [param('kernel.debug')]) + ->call('setTokenStorage', [service('security.token_storage')->ignoreOnInvalid()]) + ->call('setRequestStack', [service('request_stack')->ignoreOnInvalid()]) + + ->set('twig.template_iterator', TemplateIterator::class) + ->args([service('kernel'), abstract_arg('Twig paths'), param('twig.default_path')]) + + ->set('twig.template_cache_warmer', TemplateCacheWarmer::class) + ->args([service(ContainerInterface::class), service('twig.template_iterator')]) + ->tag('kernel.cache_warmer') + ->tag('container.service_subscriber', ['id' => 'twig']) + + ->set('twig.loader.native_filesystem', FilesystemLoader::class) + ->args([[], param('kernel.project_dir')]) + ->tag('twig.loader') + + ->set('twig.loader.chain', ChainLoader::class) + + ->set('twig.extension.profiler', ProfilerExtension::class) + ->args([service('twig.profile'), service('debug.stopwatch')->ignoreOnInvalid()]) + + ->set('twig.profile', Profile::class) + + ->set('data_collector.twig', TwigDataCollector::class) + ->args([service('twig.profile'), service('twig')]) + ->tag('data_collector', ['template' => '@WebProfiler/Collector/twig.html.twig', 'id' => 'twig', 'priority' => 257]) + + ->set('twig.extension.trans', TranslationExtension::class) + ->args([service('translator')->nullOnInvalid()]) + ->tag('twig.extension') + + ->set('twig.extension.assets', AssetExtension::class) + ->args([service('assets.packages')]) + + ->set('twig.extension.code', CodeExtension::class) + ->args([service('debug.file_link_formatter')->ignoreOnInvalid(), param('kernel.project_dir'), param('kernel.charset')]) + ->tag('twig.extension') + + ->set('twig.extension.routing', RoutingExtension::class) + ->args([service('router')]) + + ->set('twig.extension.yaml', YamlExtension::class) + + ->set('twig.extension.debug.stopwatch', StopwatchExtension::class) + ->args([service('debug.stopwatch')->ignoreOnInvalid(), param('kernel.debug')]) + + ->set('twig.extension.expression', ExpressionExtension::class) + + ->set('twig.extension.httpkernel', HttpKernelExtension::class) + + ->set('twig.runtime.httpkernel', HttpKernelRuntime::class) + ->args([service('fragment.handler'), service('fragment.uri_generator')->ignoreOnInvalid()]) + + ->set('twig.extension.httpfoundation', HttpFoundationExtension::class) + ->args([service('url_helper')]) + + ->set('twig.extension.debug', DebugExtension::class) + + ->set('twig.extension.weblink', WebLinkExtension::class) + ->args([service('request_stack')]) + + ->set('twig.translation.extractor', TwigExtractor::class) + ->args([service('twig')]) + ->tag('translation.extractor', ['alias' => 'twig']) + + ->set('workflow.twig_extension', WorkflowExtension::class) + ->args([service('workflow.registry')]) + + ->set('twig.configurator.environment', EnvironmentConfigurator::class) + ->args([ + abstract_arg('date format, set in TwigExtension'), + abstract_arg('interval format, set in TwigExtension'), + abstract_arg('timezone, set in TwigExtension'), + abstract_arg('decimals, set in TwigExtension'), + abstract_arg('decimal point, set in TwigExtension'), + abstract_arg('thousands separator, set in TwigExtension'), + ]) + + ->set('twig.runtime_loader', ContainerRuntimeLoader::class) + ->args([abstract_arg('runtime locator')]) + + ->set('twig.error_renderer.html', TwigErrorRenderer::class) + ->decorate('error_renderer.html') + ->args([ + service('twig'), + service('twig.error_renderer.html.inner'), + inline_service('bool') + ->factory([TwigErrorRenderer::class, 'isDebug']) + ->args([service('request_stack'), param('kernel.debug')]), + ]) + + ->set('twig.runtime.serializer', SerializerRuntime::class) + ->args([service('serializer')]) + + ->set('twig.extension.serializer', SerializerExtension::class) + ; +}; diff --git a/lib/symfony/twig-bundle/Resources/config/twig.xml b/lib/symfony/twig-bundle/Resources/config/twig.xml deleted file mode 100644 index 1d894cf20c..0000000000 --- a/lib/symfony/twig-bundle/Resources/config/twig.xml +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - - - - - - - app - - - - - - - - - - - - %kernel.environment% - %kernel.debug% - - - - - - - - - - - - - - - %kernel.root_dir% - - %twig.default_path% - - - - - - - - - - - - %kernel.project_dir% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %kernel.root_dir% - %kernel.charset% - - - - - - - - - - - %kernel.debug% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %twig.exception_listener.controller% - - %kernel.debug% - - - - - %kernel.debug% - - - - - %twig.exception_listener.controller% - - - - - - - - - - - - - - - - diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/error.atom.twig b/lib/symfony/twig-bundle/Resources/views/Exception/error.atom.twig deleted file mode 100644 index 25c84a6c9b..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/error.atom.twig +++ /dev/null @@ -1 +0,0 @@ -{{ include('@Twig/Exception/error.xml.twig') }} diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/error.css.twig b/lib/symfony/twig-bundle/Resources/views/Exception/error.css.twig deleted file mode 100644 index d8a9369487..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/error.css.twig +++ /dev/null @@ -1,4 +0,0 @@ -/* -{{ status_code }} {{ status_text }} - -*/ diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/error.html.twig b/lib/symfony/twig-bundle/Resources/views/Exception/error.html.twig deleted file mode 100644 index 138a60ad96..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/error.html.twig +++ /dev/null @@ -1,16 +0,0 @@ - - - - - An Error Occurred: {{ status_text }} - - -

                  Oops! An Error Occurred

                  -

                  The server returned a "{{ status_code }} {{ status_text }}".

                  - -
                  - Something is broken. Please let us know what you were doing when this error occurred. - We will fix it as soon as possible. Sorry for any inconvenience caused. -
                  - - diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/error.js.twig b/lib/symfony/twig-bundle/Resources/views/Exception/error.js.twig deleted file mode 100644 index d8a9369487..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/error.js.twig +++ /dev/null @@ -1,4 +0,0 @@ -/* -{{ status_code }} {{ status_text }} - -*/ diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/error.json.twig b/lib/symfony/twig-bundle/Resources/views/Exception/error.json.twig deleted file mode 100644 index fc19fd83bb..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/error.json.twig +++ /dev/null @@ -1 +0,0 @@ -{{ { 'error': { 'code': status_code, 'message': status_text } }|json_encode|raw }} diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/error.rdf.twig b/lib/symfony/twig-bundle/Resources/views/Exception/error.rdf.twig deleted file mode 100644 index 25c84a6c9b..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/error.rdf.twig +++ /dev/null @@ -1 +0,0 @@ -{{ include('@Twig/Exception/error.xml.twig') }} diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/error.txt.twig b/lib/symfony/twig-bundle/Resources/views/Exception/error.txt.twig deleted file mode 100644 index bec5b1e302..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/error.txt.twig +++ /dev/null @@ -1,7 +0,0 @@ -Oops! An Error Occurred -======================= - -The server returned a "{{ status_code }} {{ status_text }}". - -Something is broken. Please let us know what you were doing when this error occurred. -We will fix it as soon as possible. Sorry for any inconvenience caused. diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/error.xml.twig b/lib/symfony/twig-bundle/Resources/views/Exception/error.xml.twig deleted file mode 100644 index 5ea8f565ab..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/error.xml.twig +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/exception.atom.twig b/lib/symfony/twig-bundle/Resources/views/Exception/exception.atom.twig deleted file mode 100644 index 2cdf03f2bc..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/exception.atom.twig +++ /dev/null @@ -1 +0,0 @@ -{{ include('@Twig/Exception/exception.xml.twig', { exception: exception }) }} diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/exception.css.twig b/lib/symfony/twig-bundle/Resources/views/Exception/exception.css.twig deleted file mode 100644 index 593d490257..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/exception.css.twig +++ /dev/null @@ -1,3 +0,0 @@ -/* -{{ include('@Twig/Exception/exception.txt.twig', { exception: exception }) }} -*/ diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/exception.html.twig b/lib/symfony/twig-bundle/Resources/views/Exception/exception.html.twig deleted file mode 100644 index 2f29571ae3..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/exception.html.twig +++ /dev/null @@ -1,102 +0,0 @@ -
                  - - -
                  -
                  -

                  - {{- exception.message|nl2br|format_file_from_text -}} -

                  - -
                  - {{ include('@Twig/images/symfony-ghost.svg') }} -
                  -
                  -
                  -
                  - -
                  -
                  -
                  - {% set exception_as_array = exception.toarray %} - {% set _exceptions_with_user_code = [] %} - {% for i, e in exception_as_array %} - {% for trace in e.trace %} - {% if (trace.file is not empty) and ('/vendor/' not in trace.file) and ('/var/cache/' not in trace.file) and not loop.last %} - {% set _exceptions_with_user_code = _exceptions_with_user_code|merge([i]) %} - {% endif %} - {% endfor %} - {% endfor %} -

                  - {% if exception_as_array|length > 1 %} - Exceptions {{ exception_as_array|length }} - {% else %} - Exception - {% endif %} -

                  - -
                  - {% for i, e in exception_as_array %} - {{ include('@Twig/Exception/traces.html.twig', { exception: e, index: loop.index, expand: i in _exceptions_with_user_code or (_exceptions_with_user_code is empty and loop.first) }, with_context = false) }} - {% endfor %} -
                  -
                  - - {% if logger %} -
                  -

                  - Logs - {% if logger.counterrors ?? false %}{{ logger.counterrors }}{% endif %} -

                  - -
                  - {% if logger.logs %} - {{ include('@Twig/Exception/logs.html.twig', { logs: logger.logs }, with_context = false) }} - {% else %} -
                  -

                  No log messages

                  -
                  - {% endif %} -
                  -
                  - {% endif %} - -
                  -

                  - {% if exception_as_array|length > 1 %} - Stack Traces {{ exception_as_array|length }} - {% else %} - Stack Trace - {% endif %} -

                  - -
                  - {% for e in exception_as_array %} - {{ include('@Twig/Exception/traces_text.html.twig', { exception: e, index: loop.index, num_exceptions: loop.length }, with_context = false) }} - {% endfor %} -
                  -
                  - - {% if currentContent is not empty %} -
                  -

                  Output content

                  - -
                  - {{ currentContent }} -
                  -
                  - {% endif %} -
                  -
                  diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/exception.js.twig b/lib/symfony/twig-bundle/Resources/views/Exception/exception.js.twig deleted file mode 100644 index 593d490257..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/exception.js.twig +++ /dev/null @@ -1,3 +0,0 @@ -/* -{{ include('@Twig/Exception/exception.txt.twig', { exception: exception }) }} -*/ diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/exception.json.twig b/lib/symfony/twig-bundle/Resources/views/Exception/exception.json.twig deleted file mode 100644 index 13a41476f2..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/exception.json.twig +++ /dev/null @@ -1 +0,0 @@ -{{ { 'error': { 'code': status_code, 'message': status_text, 'exception': exception.toarray } }|json_encode|raw }} diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/exception.rdf.twig b/lib/symfony/twig-bundle/Resources/views/Exception/exception.rdf.twig deleted file mode 100644 index 2cdf03f2bc..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/exception.rdf.twig +++ /dev/null @@ -1 +0,0 @@ -{{ include('@Twig/Exception/exception.xml.twig', { exception: exception }) }} diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/exception.txt.twig b/lib/symfony/twig-bundle/Resources/views/Exception/exception.txt.twig deleted file mode 100644 index cb17fb149f..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/exception.txt.twig +++ /dev/null @@ -1,7 +0,0 @@ -[exception] {{ status_code ~ ' | ' ~ status_text ~ ' | ' ~ exception.class }} -[message] {{ exception.message }} -{% for i, e in exception.toarray %} -[{{ i + 1 }}] {{ e.class }}: {{ e.message }} -{{ include('@Twig/Exception/traces.txt.twig', { exception: e }, with_context = false) }} - -{% endfor %} diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/exception.xml.twig b/lib/symfony/twig-bundle/Resources/views/Exception/exception.xml.twig deleted file mode 100644 index 36c9449b6c..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/exception.xml.twig +++ /dev/null @@ -1,9 +0,0 @@ - - - -{% for e in exception.toarray %} - -{{ include('@Twig/Exception/traces.xml.twig', { exception: e }, with_context = false) }} - -{% endfor %} - diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/exception_full.html.twig b/lib/symfony/twig-bundle/Resources/views/Exception/exception_full.html.twig deleted file mode 100644 index e4f220896e..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/exception_full.html.twig +++ /dev/null @@ -1,142 +0,0 @@ -{% extends '@Twig/layout.html.twig' %} - -{% block head %} - -{% endblock %} - -{% block title %} - {{ exception.message }} ({{ status_code }} {{ status_text }}) -{% endblock %} - -{% block body %} - {% include '@Twig/Exception/exception.html.twig' %} -{% endblock %} diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/logs.html.twig b/lib/symfony/twig-bundle/Resources/views/Exception/logs.html.twig deleted file mode 100644 index f2a190ad23..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/logs.html.twig +++ /dev/null @@ -1,36 +0,0 @@ -{% set channel_is_defined = (logs|first).channel is defined %} - - - - - - {% if channel_is_defined %}{% endif %} - - - - - - {% for log in logs %} - {% if log.priority >= 400 %} - {% set status = 'error' %} - {% elseif log.priority >= 300 %} - {% set status = 'warning' %} - {% else %} - {% set severity = log.context.exception.severity|default(false) %} - {% set status = severity is constant('E_DEPRECATED') or severity is constant('E_USER_DEPRECATED') ? 'warning' : 'normal' %} - {% endif %} - - - {% if channel_is_defined %} - - {% endif %} - - - {% endfor %} - -
                  LevelChannelMessage
                  - {{ log.priorityName }} - {{ log.timestamp|date('H:i:s') }} - - {{ log.channel }} - {{ log.message|format_log_message(log.context) }}
                  diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/trace.html.twig b/lib/symfony/twig-bundle/Resources/views/Exception/trace.html.twig deleted file mode 100644 index 4e6c85a420..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/trace.html.twig +++ /dev/null @@ -1,33 +0,0 @@ -
                  - {% if trace.file|default(false) %} - {{ include('@Twig/images/icon-minus-square.svg') }} - {{ include('@Twig/images/icon-plus-square.svg') }} - {% endif %} - - {% if trace.function %} - {{ trace.class|abbr_class }}{% if trace.type is not empty %}{{ trace.type }}{% endif %}{{ trace.function }}({{ trace.args|format_args }}) - {% endif %} - - {% if trace.file|default(false) %} - {% set line_number = trace.line|default(1) %} - {% set file_link = trace.file|file_link(line_number) %} - {% set file_path = trace.file|format_file(line_number)|striptags|replace({ (' at line ' ~ line_number): '' }) %} - {% set file_path_parts = file_path|split(constant('DIRECTORY_SEPARATOR')) %} - - - in - {{ file_path_parts[:-1]|join(constant('DIRECTORY_SEPARATOR')) }}{{ constant('DIRECTORY_SEPARATOR') }}{{ file_path_parts|last }} - (line {{ line_number }}) - - {% endif %} -
                  -{% if trace.file|default(false) %} -
                  - {{ trace.file|file_excerpt(trace.line, 5)|replace({ - '#DD0000': '#183691', - '#007700': '#a71d5d', - '#0000BB': '#222222', - '#FF8000': '#969896' - })|raw }} -
                  -{% endif %} diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/trace.txt.twig b/lib/symfony/twig-bundle/Resources/views/Exception/trace.txt.twig deleted file mode 100644 index 538355cbe1..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/trace.txt.twig +++ /dev/null @@ -1,6 +0,0 @@ -{% if trace.function %} -at {{ trace.class ~ trace.type ~ trace.function }}({{ trace.args|format_args_as_text }}) -{%- endif -%} -{% if trace.file|default('') is not empty and trace.line|default('') is not empty %} - {{- trace.function ? '\n (' : 'at '}}{{ trace.file|format_file(trace.line)|striptags|replace({ (' at line ' ~ trace.line): '' }) }}:{{ trace.line }}{{ trace.function ? ')' }} -{%- endif %} diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/traces.html.twig b/lib/symfony/twig-bundle/Resources/views/Exception/traces.html.twig deleted file mode 100644 index 2bf3e7613a..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/traces.html.twig +++ /dev/null @@ -1,33 +0,0 @@ -
                  -
                  -
                  - -

                  - - {{ exception.class|split('\\')|slice(0, -1)|join('\\') }} - {{- exception.class|split('\\')|length > 1 ? '\\' }} - - {{ exception.class|split('\\')|last }} - - {{ include('@Twig/images/icon-minus-square-o.svg') }} - {{ include('@Twig/images/icon-plus-square-o.svg') }} -

                  - - {% if exception.message is not empty and index > 1 %} -

                  {{ exception.message }}

                  - {% endif %} -
                  -
                  - -
                  - {% set _is_first_user_code = true %} - {% for i, trace in exception.trace %} - {% set _display_code_snippet = _is_first_user_code and ('/vendor/' not in trace.file) and ('/var/cache/' not in trace.file) and (trace.file is not empty) %} - {% if _display_code_snippet %}{% set _is_first_user_code = false %}{% endif %} -
                  - {{ include('@Twig/Exception/trace.html.twig', { prefix: index, i: i, trace: trace, _display_code_snippet: _display_code_snippet }, with_context = false) }} -
                  - {% endfor %} -
                  -
                  -
                  diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/traces.txt.twig b/lib/symfony/twig-bundle/Resources/views/Exception/traces.txt.twig deleted file mode 100644 index 88a2d6922d..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/traces.txt.twig +++ /dev/null @@ -1,10 +0,0 @@ -{% if exception.trace|length %} -{{ exception.class }}: -{% if exception.message is not empty %} - {{- exception.message }} -{% endif %} - -{% for trace in exception.trace %} - {{ include('@Twig/Exception/trace.txt.twig', { trace: trace }, with_context = false) }} -{% endfor %} -{% endif %} diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/traces.xml.twig b/lib/symfony/twig-bundle/Resources/views/Exception/traces.xml.twig deleted file mode 100644 index ae46775925..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/traces.xml.twig +++ /dev/null @@ -1,8 +0,0 @@ - -{% for trace in exception.trace %} - -{{ include('@Twig/Exception/trace.txt.twig', { trace: trace }, with_context = false) }} - - -{% endfor %} - diff --git a/lib/symfony/twig-bundle/Resources/views/Exception/traces_text.html.twig b/lib/symfony/twig-bundle/Resources/views/Exception/traces_text.html.twig deleted file mode 100644 index 2f28255c03..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/Exception/traces_text.html.twig +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - -
                  -

                  - {% if num_exceptions > 1 %} - [{{ num_exceptions - index + 1 }}/{{ num_exceptions }}] - {% endif %} - {{ exception.class|split('\\')|last }} - {{ include('@Twig/images/icon-minus-square-o.svg') }} - {{ include('@Twig/images/icon-plus-square-o.svg') }} -

                  -
                  - {% if exception.trace|length %} -
                  -                {%- apply escape('html') -%}
                  -                    {{- include('@Twig/Exception/traces.txt.twig', { exception: exception, format: 'html' }, with_context = false) }}
                  -                {% endapply %}
                  -                
                  - {% endif %} -
                  diff --git a/lib/symfony/twig-bundle/Resources/views/base_js.html.twig b/lib/symfony/twig-bundle/Resources/views/base_js.html.twig deleted file mode 100644 index 1bc9ccf14c..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/base_js.html.twig +++ /dev/null @@ -1,181 +0,0 @@ -{# This file is based on WebProfilerBundle/Resources/views/Profiler/base_js.html.twig. - If you make any change in this file, verify the same change is needed in the other file. #} -/*' element */ - while (activeTab.tagName.toLowerCase() !== 'li') { - activeTab = activeTab.parentNode; - } - - /* get the full list of tabs through the parent of the active tab element */ - var tabNavigation = activeTab.parentNode.children; - for (var k = 0; k < tabNavigation.length; k++) { - var tabId = tabNavigation[k].getAttribute('data-tab-id'); - document.getElementById(tabId).className = 'hidden'; - removeClass(tabNavigation[k], 'active'); - } - - addClass(activeTab, 'active'); - var activeTabId = activeTab.getAttribute('data-tab-id'); - document.getElementById(activeTabId).className = 'block'; - }); - } - - tabGroups[i].setAttribute('data-processed', 'true'); - } - }, - - createToggles: function() { - var toggles = document.querySelectorAll('.sf-toggle:not([data-processed=true])'); - - for (var i = 0; i < toggles.length; i++) { - var elementSelector = toggles[i].getAttribute('data-toggle-selector'); - var element = document.querySelector(elementSelector); - - addClass(element, 'sf-toggle-content'); - - if (toggles[i].hasAttribute('data-toggle-initial') && toggles[i].getAttribute('data-toggle-initial') == 'display') { - addClass(toggles[i], 'sf-toggle-on'); - addClass(element, 'sf-toggle-visible'); - } else { - addClass(toggles[i], 'sf-toggle-off'); - addClass(element, 'sf-toggle-hidden'); - } - - addEventListener(toggles[i], 'click', function(e) { - e.preventDefault(); - - if ('' !== window.getSelection().toString()) { - /* Don't do anything on text selection */ - return; - } - - var toggle = e.target || e.srcElement; - - /* needed because when the toggle contains HTML contents, user can click */ - /* on any of those elements instead of their parent '.sf-toggle' element */ - while (!hasClass(toggle, 'sf-toggle')) { - toggle = toggle.parentNode; - } - - var element = document.querySelector(toggle.getAttribute('data-toggle-selector')); - - toggleClass(toggle, 'sf-toggle-on'); - toggleClass(toggle, 'sf-toggle-off'); - toggleClass(element, 'sf-toggle-hidden'); - toggleClass(element, 'sf-toggle-visible'); - - /* the toggle doesn't change its contents when clicking on it */ - if (!toggle.hasAttribute('data-toggle-alt-content')) { - return; - } - - if (!toggle.hasAttribute('data-toggle-original-content')) { - toggle.setAttribute('data-toggle-original-content', toggle.innerHTML); - } - - var currentContent = toggle.innerHTML; - var originalContent = toggle.getAttribute('data-toggle-original-content'); - var altContent = toggle.getAttribute('data-toggle-alt-content'); - toggle.innerHTML = currentContent !== altContent ? altContent : originalContent; - }); - - /* Prevents from disallowing clicks on links inside toggles */ - var toggleLinks = toggles[i].querySelectorAll('a'); - for (var j = 0; j < toggleLinks.length; j++) { - addEventListener(toggleLinks[j], 'click', function(e) { - e.stopPropagation(); - }); - } - - toggles[i].setAttribute('data-processed', 'true'); - } - } - }; - })(); - - Sfjs.addEventListener(document, 'DOMContentLoaded', function() { - Sfjs.createTabs(); - Sfjs.createToggles(); - }); - -/*]]>*/ diff --git a/lib/symfony/twig-bundle/Resources/views/exception.css.twig b/lib/symfony/twig-bundle/Resources/views/exception.css.twig deleted file mode 100644 index f0ca2f5b04..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/exception.css.twig +++ /dev/null @@ -1,128 +0,0 @@ -html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0} - -html { - /* always display the vertical scrollbar to avoid jumps when toggling contents */ - overflow-y: scroll; -} -body { background-color: #F9F9F9; color: #222; font: 14px/1.4 Helvetica, Arial, sans-serif; padding-bottom: 45px; } - -a { cursor: pointer; text-decoration: none; } -a:hover { text-decoration: underline; } -abbr[title] { border-bottom: none; cursor: help; text-decoration: none; } - -code, pre { font: 13px/1.5 Consolas, Monaco, Menlo, "Ubuntu Mono", "Liberation Mono", monospace; } - -table, tr, th, td { background: #FFF; border-collapse: collapse; vertical-align: top; } -table { background: #FFF; border: 1px solid #E0E0E0; box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; } -table th, table td { border: solid #E0E0E0; border-width: 1px 0; padding: 8px 10px; } -table th { background-color: #E0E0E0; font-weight: bold; text-align: left; } - -.hidden-xs-down { display: none; } -.block { display: block; } -.hidden { display: none; } -.nowrap { white-space: nowrap; } -.newline { display: block; } -.break-long-words { word-wrap: break-word; overflow-wrap: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; min-width: 0; } -.text-small { font-size: 12px !important; } -.text-muted { color: #999; } -.text-bold { font-weight: bold; } -.empty { border: 4px dashed #E0E0E0; color: #999; margin: 1em 0; padding: .5em 2em; } - -.status-success { background: rgba(94, 151, 110, 0.3); } -.status-warning { background: rgba(240, 181, 24, 0.3); } -.status-error { background: rgba(176, 65, 62, 0.2); } -.status-success td, .status-warning td, .status-error td { background: transparent; } -tr.status-error td, tr.status-warning td { border-bottom: 1px solid #FAFAFA; border-top: 1px solid #FAFAFA; } -.status-warning .colored { color: #A46A1F; } -.status-error .colored { color: #B0413E; } - -.sf-toggle { cursor: pointer; } -.sf-toggle-content { -moz-transition: display .25s ease; -webkit-transition: display .25s ease; transition: display .25s ease; } -.sf-toggle-content.sf-toggle-hidden { display: none; } -.sf-toggle-content.sf-toggle-visible { display: block; } -thead.sf-toggle-content.sf-toggle-visible, tbody.sf-toggle-content.sf-toggle-visible { display: table-row-group; } -.sf-toggle-off .icon-close, .sf-toggle-on .icon-open { display: none; } -.sf-toggle-off .icon-open, .sf-toggle-on .icon-close { display: block; } - -.tab-navigation { margin: 0 0 1em 0; padding: 0; } -.tab-navigation li { background: #FFF; border: 1px solid #DDD; color: #444; cursor: pointer; display: inline-block; font-size: 16px; margin: 0 0 0 -1px; padding: .5em .75em; z-index: 1; } -.tab-navigation li:hover { background: #EEE; } -.tab-navigation li.disabled { background: #F5F5F5; color: #999; } -.tab-navigation li.active { background: #666; border-color: #666; color: #FAFAFA; z-index: 1100; } -.tab-navigation li .badge { background-color: #F5F5F5; color: #777; display: inline-block; font-size: 14px; font-weight: bold; margin-left: 8px; min-width: 10px; padding: 1px 6px; text-align: center; } -.tab-navigation li:hover .badge { background: #FAFAFA; color: #777; } -.tab-navigation li.active .badge { background-color: #444; color: #FFF; } -.tab-navigation li .badge.status-warning { background: #A46A1F; color: #FFF; } -.tab-navigation li .badge.status-error { background: #B0413E; color: #FFF; } -.tab-content > *:first-child { margin-top: 0; } - -.container { max-width: 1024px; margin: 0 auto; padding: 0 15px; } -.container::after { content: ""; display: table; clear: both; } - -header { background-color: #222; color: rgba(255, 255, 255, 0.75); font-size: 13px; height: 33px; line-height: 33px; padding: 0; } -header .container { display: flex; justify-content: space-between; } -.logo { flex: 1; font-size: 13px; font-weight: normal; margin: 0; padding: 0; } -.logo svg { height: 18px; width: 18px; opacity: .8; vertical-align: -5px; } - -.help-link { margin-left: 15px; } -.help-link a { color: inherit; } -.help-link .icon svg { height: 15px; width: 15px; opacity: .7; vertical-align: -2px; } -.help-link a:hover { color: #EEE; text-decoration: none; } -.help-link a:hover svg { opacity: .9; } - -.exception-summary { background: #B0413E; border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 15px; } -.exception-metadata { background: rgba(0, 0, 0, 0.1); padding: 7px 0; } -.exception-metadata .container { display: flex; flex-direction: row; justify-content: space-between; } -.exception-metadata h2 { color: rgba(255, 255, 255, 0.8); font-size: 13px; font-weight: 400; margin: 0; } -.exception-http small { font-size: 13px; opacity: .7; } -.exception-hierarchy { flex: 1; } -.exception-hierarchy .icon { margin: 0 3px; opacity: .7; } -.exception-hierarchy .icon svg { height: 13px; width: 13px; vertical-align: -2px; } - -.exception-without-message .exception-message-wrapper { display: none; } -.exception-message-wrapper .container { display: flex; align-items: flex-start; min-height: 70px; padding: 10px 15px 8px; } -.exception-message { flex-grow: 1; } -.exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; } -.exception-message.long { font-size: 18px; } -.exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; } -.exception-message a:hover { border-bottom-color: #ffffff; } - -.exception-illustration { flex-basis: 111px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; } - -.trace + .trace { margin-top: 30px; } -.trace-head { background-color: #e0e0e0; padding: 10px; } -.trace-head .trace-class { color: #222; font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; } -.trace-head .trace-namespace { color: #999; display: block; font-size: 13px; } -.trace-head .icon { position: absolute; right: 0; top: 0; } -.trace-head .icon svg { height: 24px; width: 24px; } - -.trace-details { background: #FFF; border: 1px solid #E0E0E0; box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; } - -.trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; } -.trace-details { table-layout: fixed; } -.trace-line { position: relative; padding-top: 8px; padding-bottom: 8px; } -.trace-line:hover { background: #F5F5F5; } -.trace-line a { color: #222; } -.trace-line .icon { opacity: .4; position: absolute; left: 10px; top: 11px; } -.trace-line .icon svg { height: 16px; width: 16px; } -.trace-line-header { padding-left: 36px; padding-right: 10px; } - -.trace-file-path, .trace-file-path a { color: #222; font-size: 13px; } -.trace-class { color: #B0413E; } -.trace-type { padding: 0 2px; } -.trace-method { color: #B0413E; font-weight: bold; } -.trace-arguments { color: #777; font-weight: normal; padding-left: 2px; } - -.trace-code { background: #FFF; font-size: 12px; margin: 10px 10px 2px 10px; padding: 10px; overflow-x: auto; white-space: nowrap; } -.trace-code ol { margin: 0; float: left; } -.trace-code li { color: #969896; margin: 0; padding-left: 10px; float: left; width: 100%; } -.trace-code li + li { margin-top: 5px; } -.trace-code li.selected { background: #F7E5A1; margin-top: 2px; } -.trace-code li code { color: #222; white-space: nowrap; } - -.trace-as-text .stacktrace { line-height: 1.8; margin: 0 0 15px; white-space: pre-wrap; } - -@media (min-width: 575px) { - .hidden-xs-down { display: initial; } - .help-link { margin-left: 30px; } -} diff --git a/lib/symfony/twig-bundle/Resources/views/images/symfony-ghost.svg b/lib/symfony/twig-bundle/Resources/views/images/symfony-ghost.svg deleted file mode 100644 index 58266bcbfa..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/images/symfony-ghost.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/symfony/twig-bundle/Resources/views/layout.html.twig b/lib/symfony/twig-bundle/Resources/views/layout.html.twig deleted file mode 100644 index d3a6786c59..0000000000 --- a/lib/symfony/twig-bundle/Resources/views/layout.html.twig +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - {% block title %}{% endblock %} - - - {% block head %}{% endblock %} - - -
                  - -
                  - - {% block body %}{% endblock %} - {{ include('@Twig/base_js.html.twig') }} - - diff --git a/lib/symfony/twig-bundle/TemplateIterator.php b/lib/symfony/twig-bundle/TemplateIterator.php index 323602943f..8cc0ffc4df 100644 --- a/lib/symfony/twig-bundle/TemplateIterator.php +++ b/lib/symfony/twig-bundle/TemplateIterator.php @@ -18,72 +18,64 @@ use Symfony\Component\HttpKernel\KernelInterface; * Iterator for all templates in bundles and in the application Resources directory. * * @author Fabien Potencier + * + * @internal + * + * @implements \IteratorAggregate */ class TemplateIterator implements \IteratorAggregate { private $kernel; - private $rootDir; private $templates; private $paths; private $defaultPath; /** - * @param KernelInterface $kernel A KernelInterface instance - * @param string $rootDir The directory where global templates can be stored - * @param array $paths Additional Twig paths to warm - * @param string $defaultPath The directory where global templates can be stored + * @param array $paths Additional Twig paths to warm + * @param string|null $defaultPath The directory where global templates can be stored */ - public function __construct(KernelInterface $kernel, $rootDir, array $paths = [], $defaultPath = null) + public function __construct(KernelInterface $kernel, array $paths = [], string $defaultPath = null) { $this->kernel = $kernel; - $this->rootDir = $rootDir; $this->paths = $paths; $this->defaultPath = $defaultPath; } - /** - * {@inheritdoc} - */ - public function getIterator() + public function getIterator(): \Traversable { if (null !== $this->templates) { return $this->templates; } - $this->templates = array_merge( - $this->findTemplatesInDirectory($this->rootDir.'/Resources/views'), - $this->findTemplatesInDirectory($this->defaultPath, null, ['bundles']) - ); + $templates = null !== $this->defaultPath ? [$this->findTemplatesInDirectory($this->defaultPath, null, ['bundles'])] : []; + foreach ($this->kernel->getBundles() as $bundle) { $name = $bundle->getName(); - if ('Bundle' === substr($name, -6)) { + if (str_ends_with($name, 'Bundle')) { $name = substr($name, 0, -6); } - $this->templates = array_merge( - $this->templates, - $this->findTemplatesInDirectory($bundle->getPath().'/Resources/views', $name), - $this->findTemplatesInDirectory($this->rootDir.'/Resources/'.$bundle->getName().'/views', $name), - $this->findTemplatesInDirectory($this->defaultPath.'/bundles/'.$bundle->getName(), $name) - ); + $bundleTemplatesDir = is_dir($bundle->getPath().'/Resources/views') ? $bundle->getPath().'/Resources/views' : $bundle->getPath().'/templates'; + + $templates[] = $this->findTemplatesInDirectory($bundleTemplatesDir, $name); + if (null !== $this->defaultPath) { + $templates[] = $this->findTemplatesInDirectory($this->defaultPath.'/bundles/'.$bundle->getName(), $name); + } } foreach ($this->paths as $dir => $namespace) { - $this->templates = array_merge($this->templates, $this->findTemplatesInDirectory($dir, $namespace)); + $templates[] = $this->findTemplatesInDirectory($dir, $namespace); } - return $this->templates = new \ArrayIterator(array_unique($this->templates)); + return $this->templates = new \ArrayIterator(array_unique(array_merge([], ...$templates))); } /** * Find templates in the given directory. * - * @param string $dir The directory where to look for templates - * @param string|null $namespace The template namespace - * - * @return array + * @return string[] */ - private function findTemplatesInDirectory($dir, $namespace = null, array $excludeDirs = []) + private function findTemplatesInDirectory(string $dir, string $namespace = null, array $excludeDirs = []): array { if (!is_dir($dir)) { return []; diff --git a/lib/symfony/twig-bundle/TwigBundle.php b/lib/symfony/twig-bundle/TwigBundle.php index 5a353833eb..3910dd5e2e 100644 --- a/lib/symfony/twig-bundle/TwigBundle.php +++ b/lib/symfony/twig-bundle/TwigBundle.php @@ -11,7 +11,6 @@ namespace Symfony\Bundle\TwigBundle; -use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\ExceptionListenerPass; use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\ExtensionPass; use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\RuntimeLoaderPass; use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\TwigEnvironmentPass; @@ -36,7 +35,6 @@ class TwigBundle extends Bundle $container->addCompilerPass(new ExtensionPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 10); $container->addCompilerPass(new TwigEnvironmentPass()); $container->addCompilerPass(new TwigLoaderPass()); - $container->addCompilerPass(new ExceptionListenerPass()); $container->addCompilerPass(new RuntimeLoaderPass(), PassConfig::TYPE_BEFORE_REMOVING); } diff --git a/lib/symfony/twig-bundle/TwigEngine.php b/lib/symfony/twig-bundle/TwigEngine.php deleted file mode 100644 index f2dc6b5569..0000000000 --- a/lib/symfony/twig-bundle/TwigEngine.php +++ /dev/null @@ -1,53 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\TwigBundle; - -use Symfony\Bridge\Twig\TwigEngine as BaseEngine; -use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface; -use Symfony\Component\Config\FileLocatorInterface; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Templating\TemplateNameParserInterface; -use Twig\Environment; -use Twig\Error\Error; - -/** - * This engine renders Twig templates. - * - * @author Fabien Potencier - */ -class TwigEngine extends BaseEngine implements EngineInterface -{ - protected $locator; - - public function __construct(Environment $environment, TemplateNameParserInterface $parser, FileLocatorInterface $locator) - { - parent::__construct($environment, $parser); - - $this->locator = $locator; - } - - /** - * {@inheritdoc} - * - * @throws Error if something went wrong like a thrown exception while rendering the template - */ - public function renderResponse($view, array $parameters = [], Response $response = null) - { - if (null === $response) { - $response = new Response(); - } - - $response->setContent($this->render($view, $parameters)); - - return $response; - } -} diff --git a/lib/symfony/twig-bundle/composer.json b/lib/symfony/twig-bundle/composer.json index 16c0888ef0..5635bb430d 100644 --- a/lib/symfony/twig-bundle/composer.json +++ b/lib/symfony/twig-bundle/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/twig-bundle", "type": "symfony-bundle", - "description": "Symfony TwigBundle", + "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "keywords": [], "homepage": "https://symfony.com", "license": "MIT", @@ -16,33 +16,35 @@ } ], "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/config": "~3.2|~4.0", - "symfony/debug": "~2.8|~3.0|~4.0", - "symfony/twig-bridge": "^3.4.3|^4.0.3", - "symfony/http-foundation": "~2.8|~3.0|~4.0", - "symfony/http-kernel": "^3.3|~4.0", + "php": ">=7.2.5", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/twig-bridge": "^5.3|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/http-kernel": "^5.0|^6.0", "symfony/polyfill-ctype": "~1.8", - "twig/twig": "~1.41|~2.10" + "symfony/polyfill-php80": "^1.16", + "twig/twig": "^2.13|^3.0.4" }, "require-dev": { - "symfony/asset": "~2.8|~3.0|~4.0", - "symfony/stopwatch": "~2.8|~3.0|~4.0", - "symfony/dependency-injection": "~3.4.24|^4.2.5", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/form": "~2.8|~3.0|~4.0", - "symfony/routing": "~2.8|~3.0|~4.0", - "symfony/templating": "~2.8|~3.0|~4.0", - "symfony/yaml": "~2.8|~3.0|~4.0", - "symfony/framework-bundle": "^3.3.11|~4.0", - "symfony/web-link": "~3.3|~4.0", - "doctrine/annotations": "~1.7", - "doctrine/cache": "~1.0" + "symfony/asset": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.3|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/form": "^4.4|^5.0|^6.0", + "symfony/routing": "^4.4|^5.0|^6.0", + "symfony/translation": "^5.0|^6.0", + "symfony/yaml": "^4.4|^5.0|^6.0", + "symfony/framework-bundle": "^5.0|^6.0", + "symfony/web-link": "^4.4|^5.0|^6.0", + "doctrine/annotations": "^1.10.4", + "doctrine/cache": "^1.0|^2.0" }, "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/event-dispatcher": "<3.3.1" + "symfony/dependency-injection": "<5.3", + "symfony/framework-bundle": "<5.0", + "symfony/service-contracts": ">=3.0", + "symfony/translation": "<5.0" }, "autoload": { "psr-4": { "Symfony\\Bundle\\TwigBundle\\": "" }, diff --git a/lib/symfony/twig-bundle/phpunit.xml.dist b/lib/symfony/twig-bundle/phpunit.xml.dist deleted file mode 100644 index 9237724385..0000000000 --- a/lib/symfony/twig-bundle/phpunit.xml.dist +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - ./Tests/ - - - - - - ./ - - ./Resources - ./Tests - ./vendor - - - - diff --git a/lib/symfony/var-dumper/.gitignore b/lib/symfony/var-dumper/.gitignore deleted file mode 100644 index 5414c2c655..0000000000 --- a/lib/symfony/var-dumper/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -composer.lock -phpunit.xml -vendor/ diff --git a/lib/symfony/var-dumper/CHANGELOG.md b/lib/symfony/var-dumper/CHANGELOG.md index 2d44cad225..f58ed31706 100644 --- a/lib/symfony/var-dumper/CHANGELOG.md +++ b/lib/symfony/var-dumper/CHANGELOG.md @@ -1,6 +1,65 @@ CHANGELOG ========= +5.4 +--- + + * Add ability to style integer and double values independently + * Add casters for Symfony's UUIDs and ULIDs + * Add support for `Fiber` + +5.2.0 +----- + + * added support for PHPUnit `--colors` option + * added `VAR_DUMPER_FORMAT=server` env var value support + * prevent replacing the handler when the `VAR_DUMPER_FORMAT` env var is set + +5.1.0 +----- + + * added `RdKafka` support + +4.4.0 +----- + + * added `VarDumperTestTrait::setUpVarDumper()` and `VarDumperTestTrait::tearDownVarDumper()` + to configure casters & flags to use in tests + * added `ImagineCaster` and infrastructure to dump images + * added the stamps of a message after it is dispatched in `TraceableMessageBus` and `MessengerDataCollector` collected data + * added `UuidCaster` + * made all casters final + * added support for the `NO_COLOR` env var (https://no-color.org/) + +4.3.0 +----- + + * added `DsCaster` to support dumping the contents of data structures from the Ds extension + +4.2.0 +----- + + * support selecting the format to use by setting the environment variable `VAR_DUMPER_FORMAT` to `html` or `cli` + +4.1.0 +----- + + * added a `ServerDumper` to send serialized Data clones to a server + * added a `ServerDumpCommand` and `DumpServer` to run a server collecting + and displaying dumps on a single place with multiple formats support + * added `CliDescriptor` and `HtmlDescriptor` descriptors for `server:dump` CLI and HTML formats support + +4.0.0 +----- + + * support for passing `\ReflectionClass` instances to the `Caster::castObject()` + method has been dropped, pass class names as strings instead + * the `Data::getRawData()` method has been removed + * the `VarDumperTestTrait::assertDumpEquals()` method expects a 3rd `$filter = 0` + argument and moves `$message = ''` argument at 4th position. + * the `VarDumperTestTrait::assertDumpMatchesFormat()` method expects a 3rd `$filter = 0` + argument and moves `$message = ''` argument at 4th position. + 3.4.0 ----- @@ -10,4 +69,4 @@ CHANGELOG 2.7.0 ----- - * deprecated Cloner\Data::getLimitedClone(). Use withMaxDepth, withMaxItemsPerDepth or withRefHandles instead. + * deprecated `Cloner\Data::getLimitedClone()`. Use `withMaxDepth`, `withMaxItemsPerDepth` or `withRefHandles` instead. diff --git a/lib/symfony/var-dumper/Caster/AmqpCaster.php b/lib/symfony/var-dumper/Caster/AmqpCaster.php index dc7a6414fc..dc3b62198a 100644 --- a/lib/symfony/var-dumper/Caster/AmqpCaster.php +++ b/lib/symfony/var-dumper/Caster/AmqpCaster.php @@ -17,10 +17,12 @@ use Symfony\Component\VarDumper\Cloner\Stub; * Casts Amqp related classes to array representation. * * @author Grégoire Pineau + * + * @final */ class AmqpCaster { - private static $flags = [ + private const FLAGS = [ \AMQP_DURABLE => 'AMQP_DURABLE', \AMQP_PASSIVE => 'AMQP_PASSIVE', \AMQP_EXCLUSIVE => 'AMQP_EXCLUSIVE', @@ -37,14 +39,14 @@ class AmqpCaster \AMQP_REQUEUE => 'AMQP_REQUEUE', ]; - private static $exchangeTypes = [ + private const EXCHANGE_TYPES = [ \AMQP_EX_TYPE_DIRECT => 'AMQP_EX_TYPE_DIRECT', \AMQP_EX_TYPE_FANOUT => 'AMQP_EX_TYPE_FANOUT', \AMQP_EX_TYPE_TOPIC => 'AMQP_EX_TYPE_TOPIC', \AMQP_EX_TYPE_HEADERS => 'AMQP_EX_TYPE_HEADERS', ]; - public static function castConnection(\AMQPConnection $c, array $a, Stub $stub, $isNested) + public static function castConnection(\AMQPConnection $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; @@ -77,7 +79,7 @@ class AmqpCaster return $a; } - public static function castChannel(\AMQPChannel $c, array $a, Stub $stub, $isNested) + public static function castChannel(\AMQPChannel $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; @@ -100,7 +102,7 @@ class AmqpCaster return $a; } - public static function castQueue(\AMQPQueue $c, array $a, Stub $stub, $isNested) + public static function castQueue(\AMQPQueue $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; @@ -123,7 +125,7 @@ class AmqpCaster return $a; } - public static function castExchange(\AMQPExchange $c, array $a, Stub $stub, $isNested) + public static function castExchange(\AMQPExchange $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; @@ -131,7 +133,7 @@ class AmqpCaster $prefix.'flags' => self::extractFlags($c->getFlags()), ]; - $type = isset(self::$exchangeTypes[$c->getType()]) ? new ConstStub(self::$exchangeTypes[$c->getType()], $c->getType()) : $c->getType(); + $type = isset(self::EXCHANGE_TYPES[$c->getType()]) ? new ConstStub(self::EXCHANGE_TYPES[$c->getType()], $c->getType()) : $c->getType(); // Recent version of the extension already expose private properties if (isset($a["\x00AMQPExchange\x00name"])) { @@ -151,7 +153,7 @@ class AmqpCaster return $a; } - public static function castEnvelope(\AMQPEnvelope $c, array $a, Stub $stub, $isNested, $filter = 0) + public static function castEnvelope(\AMQPEnvelope $c, array $a, Stub $stub, bool $isNested, int $filter = 0) { $prefix = Caster::PREFIX_VIRTUAL; @@ -191,11 +193,11 @@ class AmqpCaster return $a; } - private static function extractFlags($flags) + private static function extractFlags(int $flags): ConstStub { $flagsArray = []; - foreach (self::$flags as $value => $name) { + foreach (self::FLAGS as $value => $name) { if ($flags & $value) { $flagsArray[] = $name; } diff --git a/lib/symfony/var-dumper/Caster/ArgsStub.php b/lib/symfony/var-dumper/Caster/ArgsStub.php index 081fb47e99..b3f7bbee3a 100644 --- a/lib/symfony/var-dumper/Caster/ArgsStub.php +++ b/lib/symfony/var-dumper/Caster/ArgsStub.php @@ -22,13 +22,13 @@ class ArgsStub extends EnumStub { private static $parameters = []; - public function __construct(array $args, $function, $class) + public function __construct(array $args, string $function, ?string $class) { - list($variadic, $params) = self::getParameters($function, $class); + [$variadic, $params] = self::getParameters($function, $class); $values = []; foreach ($args as $k => $v) { - $values[$k] = !is_scalar($v) && !$v instanceof Stub ? new CutStub($v) : $v; + $values[$k] = !\is_scalar($v) && !$v instanceof Stub ? new CutStub($v) : $v; } if (null === $params) { parent::__construct($values, false); @@ -49,7 +49,7 @@ class ArgsStub extends EnumStub } } - private static function getParameters($function, $class) + private static function getParameters(string $function, ?string $class): array { if (isset(self::$parameters[$k = $class.'::'.$function])) { return self::$parameters[$k]; @@ -68,7 +68,7 @@ class ArgsStub extends EnumStub if ($v->isPassedByReference()) { $k = '&'.$k; } - if (method_exists($v, 'isVariadic') && $v->isVariadic()) { + if ($v->isVariadic()) { $variadic .= $k; } else { $params[] = $k; diff --git a/lib/symfony/var-dumper/Caster/Caster.php b/lib/symfony/var-dumper/Caster/Caster.php index a6ebc25bdd..53f4461d0d 100644 --- a/lib/symfony/var-dumper/Caster/Caster.php +++ b/lib/symfony/var-dumper/Caster/Caster.php @@ -22,38 +22,28 @@ use Symfony\Component\VarDumper\Cloner\Stub; */ class Caster { - const EXCLUDE_VERBOSE = 1; - const EXCLUDE_VIRTUAL = 2; - const EXCLUDE_DYNAMIC = 4; - const EXCLUDE_PUBLIC = 8; - const EXCLUDE_PROTECTED = 16; - const EXCLUDE_PRIVATE = 32; - const EXCLUDE_NULL = 64; - const EXCLUDE_EMPTY = 128; - const EXCLUDE_NOT_IMPORTANT = 256; - const EXCLUDE_STRICT = 512; + public const EXCLUDE_VERBOSE = 1; + public const EXCLUDE_VIRTUAL = 2; + public const EXCLUDE_DYNAMIC = 4; + public const EXCLUDE_PUBLIC = 8; + public const EXCLUDE_PROTECTED = 16; + public const EXCLUDE_PRIVATE = 32; + public const EXCLUDE_NULL = 64; + public const EXCLUDE_EMPTY = 128; + public const EXCLUDE_NOT_IMPORTANT = 256; + public const EXCLUDE_STRICT = 512; - const PREFIX_VIRTUAL = "\0~\0"; - const PREFIX_DYNAMIC = "\0+\0"; - const PREFIX_PROTECTED = "\0*\0"; + public const PREFIX_VIRTUAL = "\0~\0"; + public const PREFIX_DYNAMIC = "\0+\0"; + public const PREFIX_PROTECTED = "\0*\0"; /** * Casts objects to arrays and adds the dynamic property prefix. * - * @param object $obj The object to cast - * @param string $class The class of the object - * @param bool $hasDebugInfo Whether the __debugInfo method exists on $obj or not - * - * @return array The array-cast of the object, with prefixed dynamic properties + * @param bool $hasDebugInfo Whether the __debugInfo method exists on $obj or not */ - public static function castObject($obj, $class, $hasDebugInfo = false, $debugClass = null) + public static function castObject(object $obj, string $class, bool $hasDebugInfo = false, string $debugClass = null): array { - if ($class instanceof \ReflectionClass) { - @trigger_error(sprintf('Passing a ReflectionClass to "%s()" is deprecated since Symfony 3.3 and will be unsupported in 4.0. Pass the class name as string instead.', __METHOD__), \E_USER_DEPRECATED); - $hasDebugInfo = $class->hasMethod('__debugInfo'); - $class = $class->name; - } - if ($hasDebugInfo) { try { $debugInfo = $obj->__debugInfo(); @@ -71,22 +61,12 @@ class Caster if ($a) { static $publicProperties = []; - if (null === $debugClass) { - if (\PHP_VERSION_ID >= 80000) { - $debugClass = get_debug_type($obj); - } else { - $debugClass = $class; - - if (isset($debugClass[15]) && "\0" === $debugClass[15]) { - $debugClass = (get_parent_class($debugClass) ?: key(class_implements($debugClass)) ?: 'class').'@anonymous'; - } - } - } + $debugClass = $debugClass ?? get_debug_type($obj); $i = 0; $prefixedKeys = []; foreach ($a as $k => $v) { - if (isset($k[0]) ? "\0" !== $k[0] : \PHP_VERSION_ID >= 70200) { + if ("\0" !== ($k[0] ?? '')) { if (!isset($publicProperties[$class])) { foreach ((new \ReflectionClass($class))->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) { $publicProperties[$class][$prop->name] = true; @@ -136,10 +116,8 @@ class Caster * @param int $filter A bit field of Caster::EXCLUDE_* constants specifying which properties to filter out * @param string[] $listedProperties List of properties to exclude when Caster::EXCLUDE_VERBOSE is set, and to preserve when Caster::EXCLUDE_NOT_IMPORTANT is set * @param int &$count Set to the number of removed properties - * - * @return array The filtered array */ - public static function filter(array $a, $filter, array $listedProperties = [], &$count = 0) + public static function filter(array $a, int $filter, array $listedProperties = [], ?int &$count = 0): array { $count = 0; @@ -180,7 +158,7 @@ class Caster return $a; } - public static function castPhpIncompleteClass(\__PHP_Incomplete_Class $c, array $a, Stub $stub, $isNested) + public static function castPhpIncompleteClass(\__PHP_Incomplete_Class $c, array $a, Stub $stub, bool $isNested): array { if (isset($a['__PHP_Incomplete_Class_Name'])) { $stub->class .= '('.$a['__PHP_Incomplete_Class_Name'].')'; diff --git a/lib/symfony/var-dumper/Caster/ClassStub.php b/lib/symfony/var-dumper/Caster/ClassStub.php index 1a85098e15..48f848354b 100644 --- a/lib/symfony/var-dumper/Caster/ClassStub.php +++ b/lib/symfony/var-dumper/Caster/ClassStub.php @@ -11,6 +11,8 @@ namespace Symfony\Component\VarDumper\Caster; +use Symfony\Component\VarDumper\Cloner\Stub; + /** * Represents a PHP class identifier. * @@ -22,16 +24,10 @@ class ClassStub extends ConstStub * @param string $identifier A PHP identifier, e.g. a class, method, interface, etc. name * @param callable $callable The callable targeted by the identifier when it is ambiguous or not a real PHP identifier */ - public function __construct($identifier, $callable = null) + public function __construct(string $identifier, $callable = null) { $this->value = $identifier; - if (0 < $i = strrpos($identifier, '\\')) { - $this->attr['ellipsis'] = \strlen($identifier) - $i; - $this->attr['ellipsis-type'] = 'class'; - $this->attr['ellipsis-tail'] = 1; - } - try { if (null !== $callable) { if ($callable instanceof \Closure) { @@ -58,8 +54,31 @@ class ClassStub extends ConstStub $r = new \ReflectionClass($r[0]); } } + + if (str_contains($identifier, "@anonymous\0")) { + $this->value = $identifier = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) { + return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; + }, $identifier); + } + + if (null !== $callable && $r instanceof \ReflectionFunctionAbstract) { + $s = ReflectionCaster::castFunctionAbstract($r, [], new Stub(), true, Caster::EXCLUDE_VERBOSE); + $s = ReflectionCaster::getSignature($s); + + if (str_ends_with($identifier, '()')) { + $this->value = substr_replace($identifier, $s, -2); + } else { + $this->value .= $s; + } + } } catch (\ReflectionException $e) { return; + } finally { + if (0 < $i = strrpos($this->value, '\\')) { + $this->attr['ellipsis'] = \strlen($this->value) - $i; + $this->attr['ellipsis-type'] = 'class'; + $this->attr['ellipsis-tail'] = 1; + } } if ($f = $r->getFileName()) { @@ -75,9 +94,9 @@ class ClassStub extends ConstStub } if (!\is_array($callable)) { - $callable = new static($callable); + $callable = new static($callable, $callable); } elseif (\is_string($callable[0])) { - $callable[0] = new static($callable[0]); + $callable[0] = new static($callable[0], $callable); } else { $callable[1] = new static($callable[1], $callable); } diff --git a/lib/symfony/var-dumper/Caster/ConstStub.php b/lib/symfony/var-dumper/Caster/ConstStub.php index 26c0010b66..8b0179745f 100644 --- a/lib/symfony/var-dumper/Caster/ConstStub.php +++ b/lib/symfony/var-dumper/Caster/ConstStub.php @@ -20,12 +20,15 @@ use Symfony\Component\VarDumper\Cloner\Stub; */ class ConstStub extends Stub { - public function __construct($name, $value) + public function __construct(string $name, $value = null) { $this->class = $name; - $this->value = $value; + $this->value = 1 < \func_num_args() ? $value : $name; } + /** + * @return string + */ public function __toString() { return (string) $this->value; diff --git a/lib/symfony/var-dumper/Caster/CutStub.php b/lib/symfony/var-dumper/Caster/CutStub.php index 690338f542..464c6dbd19 100644 --- a/lib/symfony/var-dumper/Caster/CutStub.php +++ b/lib/symfony/var-dumper/Caster/CutStub.php @@ -28,6 +28,11 @@ class CutStub extends Stub case 'object': $this->type = self::TYPE_OBJECT; $this->class = \get_class($value); + + if ($value instanceof \Closure) { + ReflectionCaster::castClosure($value, [], $this, true, Caster::EXCLUDE_VERBOSE); + } + $this->cut = -1; break; diff --git a/lib/symfony/var-dumper/Caster/DOMCaster.php b/lib/symfony/var-dumper/Caster/DOMCaster.php index fef3d432a7..4dd16e0ee7 100644 --- a/lib/symfony/var-dumper/Caster/DOMCaster.php +++ b/lib/symfony/var-dumper/Caster/DOMCaster.php @@ -17,10 +17,12 @@ use Symfony\Component\VarDumper\Cloner\Stub; * Casts DOM related classes to array representation. * * @author Nicolas Grekas + * + * @final */ class DOMCaster { - private static $errorCodes = [ + private const ERROR_CODES = [ \DOM_PHP_ERR => 'DOM_PHP_ERR', \DOM_INDEX_SIZE_ERR => 'DOM_INDEX_SIZE_ERR', \DOMSTRING_SIZE_ERR => 'DOMSTRING_SIZE_ERR', @@ -40,7 +42,7 @@ class DOMCaster \DOM_VALIDATION_ERR => 'DOM_VALIDATION_ERR', ]; - private static $nodeTypes = [ + private const NODE_TYPES = [ \XML_ELEMENT_NODE => 'XML_ELEMENT_NODE', \XML_ATTRIBUTE_NODE => 'XML_ATTRIBUTE_NODE', \XML_TEXT_NODE => 'XML_TEXT_NODE', @@ -61,17 +63,17 @@ class DOMCaster \XML_NAMESPACE_DECL_NODE => 'XML_NAMESPACE_DECL_NODE', ]; - public static function castException(\DOMException $e, array $a, Stub $stub, $isNested) + public static function castException(\DOMException $e, array $a, Stub $stub, bool $isNested) { $k = Caster::PREFIX_PROTECTED.'code'; - if (isset($a[$k], self::$errorCodes[$a[$k]])) { - $a[$k] = new ConstStub(self::$errorCodes[$a[$k]], $a[$k]); + if (isset($a[$k], self::ERROR_CODES[$a[$k]])) { + $a[$k] = new ConstStub(self::ERROR_CODES[$a[$k]], $a[$k]); } return $a; } - public static function castLength($dom, array $a, Stub $stub, $isNested) + public static function castLength($dom, array $a, Stub $stub, bool $isNested) { $a += [ 'length' => $dom->length, @@ -80,7 +82,7 @@ class DOMCaster return $a; } - public static function castImplementation($dom, array $a, Stub $stub, $isNested) + public static function castImplementation(\DOMImplementation $dom, array $a, Stub $stub, bool $isNested) { $a += [ Caster::PREFIX_VIRTUAL.'Core' => '1.0', @@ -90,12 +92,12 @@ class DOMCaster return $a; } - public static function castNode(\DOMNode $dom, array $a, Stub $stub, $isNested) + public static function castNode(\DOMNode $dom, array $a, Stub $stub, bool $isNested) { $a += [ 'nodeName' => $dom->nodeName, 'nodeValue' => new CutStub($dom->nodeValue), - 'nodeType' => new ConstStub(self::$nodeTypes[$dom->nodeType], $dom->nodeType), + 'nodeType' => new ConstStub(self::NODE_TYPES[$dom->nodeType], $dom->nodeType), 'parentNode' => new CutStub($dom->parentNode), 'childNodes' => $dom->childNodes, 'firstChild' => new CutStub($dom->firstChild), @@ -114,12 +116,12 @@ class DOMCaster return $a; } - public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, Stub $stub, $isNested) + public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, Stub $stub, bool $isNested) { $a += [ 'nodeName' => $dom->nodeName, 'nodeValue' => new CutStub($dom->nodeValue), - 'nodeType' => new ConstStub(self::$nodeTypes[$dom->nodeType], $dom->nodeType), + 'nodeType' => new ConstStub(self::NODE_TYPES[$dom->nodeType], $dom->nodeType), 'prefix' => $dom->prefix, 'localName' => $dom->localName, 'namespaceURI' => $dom->namespaceURI, @@ -130,7 +132,7 @@ class DOMCaster return $a; } - public static function castDocument(\DOMDocument $dom, array $a, Stub $stub, $isNested, $filter = 0) + public static function castDocument(\DOMDocument $dom, array $a, Stub $stub, bool $isNested, int $filter = 0) { $a += [ 'doctype' => $dom->doctype, @@ -164,7 +166,7 @@ class DOMCaster return $a; } - public static function castCharacterData(\DOMCharacterData $dom, array $a, Stub $stub, $isNested) + public static function castCharacterData(\DOMCharacterData $dom, array $a, Stub $stub, bool $isNested) { $a += [ 'data' => $dom->data, @@ -174,7 +176,7 @@ class DOMCaster return $a; } - public static function castAttr(\DOMAttr $dom, array $a, Stub $stub, $isNested) + public static function castAttr(\DOMAttr $dom, array $a, Stub $stub, bool $isNested) { $a += [ 'name' => $dom->name, @@ -187,7 +189,7 @@ class DOMCaster return $a; } - public static function castElement(\DOMElement $dom, array $a, Stub $stub, $isNested) + public static function castElement(\DOMElement $dom, array $a, Stub $stub, bool $isNested) { $a += [ 'tagName' => $dom->tagName, @@ -197,7 +199,7 @@ class DOMCaster return $a; } - public static function castText(\DOMText $dom, array $a, Stub $stub, $isNested) + public static function castText(\DOMText $dom, array $a, Stub $stub, bool $isNested) { $a += [ 'wholeText' => $dom->wholeText, @@ -206,7 +208,7 @@ class DOMCaster return $a; } - public static function castTypeinfo(\DOMTypeinfo $dom, array $a, Stub $stub, $isNested) + public static function castTypeinfo(\DOMTypeinfo $dom, array $a, Stub $stub, bool $isNested) { $a += [ 'typeName' => $dom->typeName, @@ -216,7 +218,7 @@ class DOMCaster return $a; } - public static function castDomError(\DOMDomError $dom, array $a, Stub $stub, $isNested) + public static function castDomError(\DOMDomError $dom, array $a, Stub $stub, bool $isNested) { $a += [ 'severity' => $dom->severity, @@ -230,7 +232,7 @@ class DOMCaster return $a; } - public static function castLocator(\DOMLocator $dom, array $a, Stub $stub, $isNested) + public static function castLocator(\DOMLocator $dom, array $a, Stub $stub, bool $isNested) { $a += [ 'lineNumber' => $dom->lineNumber, @@ -243,7 +245,7 @@ class DOMCaster return $a; } - public static function castDocumentType(\DOMDocumentType $dom, array $a, Stub $stub, $isNested) + public static function castDocumentType(\DOMDocumentType $dom, array $a, Stub $stub, bool $isNested) { $a += [ 'name' => $dom->name, @@ -257,7 +259,7 @@ class DOMCaster return $a; } - public static function castNotation(\DOMNotation $dom, array $a, Stub $stub, $isNested) + public static function castNotation(\DOMNotation $dom, array $a, Stub $stub, bool $isNested) { $a += [ 'publicId' => $dom->publicId, @@ -267,7 +269,7 @@ class DOMCaster return $a; } - public static function castEntity(\DOMEntity $dom, array $a, Stub $stub, $isNested) + public static function castEntity(\DOMEntity $dom, array $a, Stub $stub, bool $isNested) { $a += [ 'publicId' => $dom->publicId, @@ -281,7 +283,7 @@ class DOMCaster return $a; } - public static function castProcessingInstruction(\DOMProcessingInstruction $dom, array $a, Stub $stub, $isNested) + public static function castProcessingInstruction(\DOMProcessingInstruction $dom, array $a, Stub $stub, bool $isNested) { $a += [ 'target' => $dom->target, @@ -291,7 +293,7 @@ class DOMCaster return $a; } - public static function castXPath(\DOMXPath $dom, array $a, Stub $stub, $isNested) + public static function castXPath(\DOMXPath $dom, array $a, Stub $stub, bool $isNested) { $a += [ 'document' => $dom->document, diff --git a/lib/symfony/var-dumper/Caster/DateCaster.php b/lib/symfony/var-dumper/Caster/DateCaster.php index 70f229a0d8..18641fbc1d 100644 --- a/lib/symfony/var-dumper/Caster/DateCaster.php +++ b/lib/symfony/var-dumper/Caster/DateCaster.php @@ -17,10 +17,14 @@ use Symfony\Component\VarDumper\Cloner\Stub; * Casts DateTimeInterface related classes to array representation. * * @author Dany Maillard + * + * @final */ class DateCaster { - public static function castDateTime(\DateTimeInterface $d, array $a, Stub $stub, $isNested, $filter) + private const PERIOD_LIMIT = 3; + + public static function castDateTime(\DateTimeInterface $d, array $a, Stub $stub, bool $isNested, int $filter) { $prefix = Caster::PREFIX_VIRTUAL; $location = $d->getTimezone()->getLocation(); @@ -43,9 +47,9 @@ class DateCaster return $a; } - public static function castInterval(\DateInterval $interval, array $a, Stub $stub, $isNested, $filter) + public static function castInterval(\DateInterval $interval, array $a, Stub $stub, bool $isNested, int $filter) { - $now = new \DateTimeImmutable(); + $now = new \DateTimeImmutable('@0', new \DateTimeZone('UTC')); $numberOfSeconds = $now->add($interval)->getTimestamp() - $now->getTimestamp(); $title = number_format($numberOfSeconds, 0, '.', ' ').'s'; @@ -54,66 +58,56 @@ class DateCaster return $filter & Caster::EXCLUDE_VERBOSE ? $i : $i + $a; } - private static function formatInterval(\DateInterval $i) + private static function formatInterval(\DateInterval $i): string { $format = '%R '; if (0 === $i->y && 0 === $i->m && ($i->h >= 24 || $i->i >= 60 || $i->s >= 60)) { - $i = date_diff($d = new \DateTime(), date_add(clone $d, $i)); // recalculate carry over points + $d = new \DateTimeImmutable('@0', new \DateTimeZone('UTC')); + $i = $d->diff($d->add($i)); // recalculate carry over points $format .= 0 < $i->days ? '%ad ' : ''; } else { $format .= ($i->y ? '%yy ' : '').($i->m ? '%mm ' : '').($i->d ? '%dd ' : ''); } - if (\PHP_VERSION_ID >= 70100 && isset($i->f)) { - $format .= $i->h || $i->i || $i->s || $i->f ? '%H:%I:'.self::formatSeconds($i->s, substr($i->f, 2)) : ''; - } else { - $format .= $i->h || $i->i || $i->s ? '%H:%I:%S' : ''; - } - + $format .= $i->h || $i->i || $i->s || $i->f ? '%H:%I:'.self::formatSeconds($i->s, substr($i->f, 2)) : ''; $format = '%R ' === $format ? '0s' : $format; return $i->format(rtrim($format)); } - public static function castTimeZone(\DateTimeZone $timeZone, array $a, Stub $stub, $isNested, $filter) + public static function castTimeZone(\DateTimeZone $timeZone, array $a, Stub $stub, bool $isNested, int $filter) { $location = $timeZone->getLocation(); $formatted = (new \DateTime('now', $timeZone))->format($location ? 'e (P)' : 'P'); - $title = $location && \extension_loaded('intl') ? \Locale::getDisplayRegion('-'.$location['country_code'], \Locale::getDefault()) : ''; + $title = $location && \extension_loaded('intl') ? \Locale::getDisplayRegion('-'.$location['country_code']) : ''; $z = [Caster::PREFIX_VIRTUAL.'timezone' => new ConstStub($formatted, $title)]; return $filter & Caster::EXCLUDE_VERBOSE ? $z : $z + $a; } - public static function castPeriod(\DatePeriod $p, array $a, Stub $stub, $isNested, $filter) + public static function castPeriod(\DatePeriod $p, array $a, Stub $stub, bool $isNested, int $filter) { - if (\defined('HHVM_VERSION_ID') || \PHP_VERSION_ID < 50620 || (\PHP_VERSION_ID >= 70000 && \PHP_VERSION_ID < 70005)) { // see https://bugs.php.net/71635 - return $a; - } - $dates = []; - if (\PHP_VERSION_ID >= 70107) { // see https://bugs.php.net/74639 - foreach (clone $p as $i => $d) { - if (3 === $i) { - $now = new \DateTimeImmutable(); - $dates[] = sprintf('%s more', ($end = $p->getEndDate()) - ? ceil(($end->format('U.u') - $d->format('U.u')) / ((int) $now->add($p->getDateInterval())->format('U.u') - (int) $now->format('U.u'))) - : $p->recurrences - $i - ); - break; - } - $dates[] = sprintf('%s) %s', $i + 1, self::formatDateTime($d)); + foreach (clone $p as $i => $d) { + if (self::PERIOD_LIMIT === $i) { + $now = new \DateTimeImmutable('now', new \DateTimeZone('UTC')); + $dates[] = sprintf('%s more', ($end = $p->getEndDate()) + ? ceil(($end->format('U.u') - $d->format('U.u')) / ((int) $now->add($p->getDateInterval())->format('U.u') - (int) $now->format('U.u'))) + : $p->recurrences - $i + ); + break; } + $dates[] = sprintf('%s) %s', $i + 1, self::formatDateTime($d)); } $period = sprintf( - 'every %s, from %s (%s) %s', + 'every %s, from %s%s %s', self::formatInterval($p->getDateInterval()), + $p->include_start_date ? '[' : ']', self::formatDateTime($p->getStartDate()), - $p->include_start_date ? 'included' : 'excluded', - ($end = $p->getEndDate()) ? 'to '.self::formatDateTime($end) : 'recurring '.$p->recurrences.' time/s' + ($end = $p->getEndDate()) ? 'to '.self::formatDateTime($end).(\PHP_VERSION_ID >= 80200 && $p->include_end_date ? ']' : '[') : 'recurring '.$p->recurrences.' time/s' ); $p = [Caster::PREFIX_VIRTUAL.'period' => new ConstStub($period, implode("\n", $dates))]; @@ -121,12 +115,12 @@ class DateCaster return $filter & Caster::EXCLUDE_VERBOSE ? $p : $p + $a; } - private static function formatDateTime(\DateTimeInterface $d, $extra = '') + private static function formatDateTime(\DateTimeInterface $d, string $extra = ''): string { return $d->format('Y-m-d H:i:'.self::formatSeconds($d->format('s'), $d->format('u')).$extra); } - private static function formatSeconds($s, $us) + private static function formatSeconds(string $s, string $us): string { return sprintf('%02d.%s', $s, 0 === ($len = \strlen($t = rtrim($us, '0'))) ? '0' : ($len <= 3 ? str_pad($t, 3, '0') : $us)); } diff --git a/lib/symfony/var-dumper/Caster/DoctrineCaster.php b/lib/symfony/var-dumper/Caster/DoctrineCaster.php index 696b87816e..129b2cb47b 100644 --- a/lib/symfony/var-dumper/Caster/DoctrineCaster.php +++ b/lib/symfony/var-dumper/Caster/DoctrineCaster.php @@ -20,10 +20,12 @@ use Symfony\Component\VarDumper\Cloner\Stub; * Casts Doctrine related classes to array representation. * * @author Nicolas Grekas + * + * @final */ class DoctrineCaster { - public static function castCommonProxy(CommonProxy $proxy, array $a, Stub $stub, $isNested) + public static function castCommonProxy(CommonProxy $proxy, array $a, Stub $stub, bool $isNested) { foreach (['__cloner__', '__initializer__'] as $k) { if (\array_key_exists($k, $a)) { @@ -35,7 +37,7 @@ class DoctrineCaster return $a; } - public static function castOrmProxy(OrmProxy $proxy, array $a, Stub $stub, $isNested) + public static function castOrmProxy(OrmProxy $proxy, array $a, Stub $stub, bool $isNested) { foreach (['_entityPersister', '_identifier'] as $k) { if (\array_key_exists($k = "\0Doctrine\\ORM\\Proxy\\Proxy\0".$k, $a)) { @@ -47,7 +49,7 @@ class DoctrineCaster return $a; } - public static function castPersistentCollection(PersistentCollection $coll, array $a, Stub $stub, $isNested) + public static function castPersistentCollection(PersistentCollection $coll, array $a, Stub $stub, bool $isNested) { foreach (['snapshot', 'association', 'typeClass'] as $k) { if (\array_key_exists($k = "\0Doctrine\\ORM\\PersistentCollection\0".$k, $a)) { diff --git a/lib/symfony/var-dumper/Caster/DsCaster.php b/lib/symfony/var-dumper/Caster/DsCaster.php new file mode 100644 index 0000000000..b34b67004b --- /dev/null +++ b/lib/symfony/var-dumper/Caster/DsCaster.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Ds\Collection; +use Ds\Map; +use Ds\Pair; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Ds extension classes to array representation. + * + * @author Jáchym Toušek + * + * @final + */ +class DsCaster +{ + public static function castCollection(Collection $c, array $a, Stub $stub, bool $isNested): array + { + $a[Caster::PREFIX_VIRTUAL.'count'] = $c->count(); + $a[Caster::PREFIX_VIRTUAL.'capacity'] = $c->capacity(); + + if (!$c instanceof Map) { + $a += $c->toArray(); + } + + return $a; + } + + public static function castMap(Map $c, array $a, Stub $stub, bool $isNested): array + { + foreach ($c as $k => $v) { + $a[] = new DsPairStub($k, $v); + } + + return $a; + } + + public static function castPair(Pair $c, array $a, Stub $stub, bool $isNested): array + { + foreach ($c->toArray() as $k => $v) { + $a[Caster::PREFIX_VIRTUAL.$k] = $v; + } + + return $a; + } + + public static function castPairStub(DsPairStub $c, array $a, Stub $stub, bool $isNested): array + { + if ($isNested) { + $stub->class = Pair::class; + $stub->value = null; + $stub->handle = 0; + + $a = $c->value; + } + + return $a; + } +} diff --git a/lib/symfony/var-dumper/Caster/DsPairStub.php b/lib/symfony/var-dumper/Caster/DsPairStub.php new file mode 100644 index 0000000000..a1dcc15618 --- /dev/null +++ b/lib/symfony/var-dumper/Caster/DsPairStub.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\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + */ +class DsPairStub extends Stub +{ + public function __construct($key, $value) + { + $this->value = [ + Caster::PREFIX_VIRTUAL.'key' => $key, + Caster::PREFIX_VIRTUAL.'value' => $value, + ]; + } +} diff --git a/lib/symfony/var-dumper/Caster/EnumStub.php b/lib/symfony/var-dumper/Caster/EnumStub.php index 3cee23eac2..7a4e98a21b 100644 --- a/lib/symfony/var-dumper/Caster/EnumStub.php +++ b/lib/symfony/var-dumper/Caster/EnumStub.php @@ -22,7 +22,7 @@ class EnumStub extends Stub { public $dumpKeys = true; - public function __construct(array $values, $dumpKeys = true) + public function __construct(array $values, bool $dumpKeys = true) { $this->value = $values; $this->dumpKeys = $dumpKeys; diff --git a/lib/symfony/var-dumper/Caster/ExceptionCaster.php b/lib/symfony/var-dumper/Caster/ExceptionCaster.php index 62b57402f8..7f5cb65eb2 100644 --- a/lib/symfony/var-dumper/Caster/ExceptionCaster.php +++ b/lib/symfony/var-dumper/Caster/ExceptionCaster.php @@ -11,7 +11,7 @@ namespace Symfony\Component\VarDumper\Caster; -use Symfony\Component\Debug\Exception\SilencedErrorContext; +use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; use Symfony\Component\VarDumper\Cloner\Stub; use Symfony\Component\VarDumper\Exception\ThrowingCasterException; @@ -19,6 +19,8 @@ use Symfony\Component\VarDumper\Exception\ThrowingCasterException; * Casts common Exception classes to array representation. * * @author Nicolas Grekas + * + * @final */ class ExceptionCaster { @@ -44,17 +46,17 @@ class ExceptionCaster private static $framesCache = []; - public static function castError(\Error $e, array $a, Stub $stub, $isNested, $filter = 0) + public static function castError(\Error $e, array $a, Stub $stub, bool $isNested, int $filter = 0) { return self::filterExceptionArray($stub->class, $a, "\0Error\0", $filter); } - public static function castException(\Exception $e, array $a, Stub $stub, $isNested, $filter = 0) + public static function castException(\Exception $e, array $a, Stub $stub, bool $isNested, int $filter = 0) { return self::filterExceptionArray($stub->class, $a, "\0Exception\0", $filter); } - public static function castErrorException(\ErrorException $e, array $a, Stub $stub, $isNested) + public static function castErrorException(\ErrorException $e, array $a, Stub $stub, bool $isNested) { if (isset($a[$s = Caster::PREFIX_PROTECTED.'severity'], self::$errorTypes[$a[$s]])) { $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]); @@ -63,7 +65,7 @@ class ExceptionCaster return $a; } - public static function castThrowingCasterException(ThrowingCasterException $e, array $a, Stub $stub, $isNested) + public static function castThrowingCasterException(ThrowingCasterException $e, array $a, Stub $stub, bool $isNested) { $trace = Caster::PREFIX_VIRTUAL.'trace'; $prefix = Caster::PREFIX_PROTECTED; @@ -71,7 +73,8 @@ class ExceptionCaster if (isset($a[$xPrefix.'previous'], $a[$trace]) && $a[$xPrefix.'previous'] instanceof \Exception) { $b = (array) $a[$xPrefix.'previous']; - self::traceUnshift($b[$xPrefix.'trace'], \get_class($a[$xPrefix.'previous']), $b[$prefix.'file'], $b[$prefix.'line']); + $class = get_debug_type($a[$xPrefix.'previous']); + self::traceUnshift($b[$xPrefix.'trace'], $class, $b[$prefix.'file'], $b[$prefix.'line']); $a[$trace] = new TraceStub($b[$xPrefix.'trace'], false, 0, -\count($a[$trace]->value)); } @@ -80,7 +83,7 @@ class ExceptionCaster return $a; } - public static function castSilencedErrorContext(SilencedErrorContext $e, array $a, Stub $stub, $isNested) + public static function castSilencedErrorContext(SilencedErrorContext $e, array $a, Stub $stub, bool $isNested) { $sPrefix = "\0".SilencedErrorContext::class."\0"; @@ -107,7 +110,7 @@ class ExceptionCaster return $a; } - public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, $isNested) + public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, bool $isNested) { if (!$isNested) { return $a; @@ -135,10 +138,10 @@ class ExceptionCaster $frame = new FrameStub( [ - 'object' => isset($f['object']) ? $f['object'] : null, - 'class' => isset($f['class']) ? $f['class'] : null, - 'type' => isset($f['type']) ? $f['type'] : null, - 'function' => isset($f['function']) ? $f['function'] : null, + 'object' => $f['object'] ?? null, + 'class' => $f['class'] ?? null, + 'type' => $f['type'] ?? null, + 'function' => $f['function'] ?? null, ] + $frames[$i - 1], false, true @@ -146,7 +149,7 @@ class ExceptionCaster $f = self::castFrameStub($frame, [], $frame, true); if (isset($f[$prefix.'src'])) { foreach ($f[$prefix.'src']->value as $label => $frame) { - if (0 === strpos($label, "\0~collapse=0")) { + if (str_starts_with($label, "\0~collapse=0")) { if ($collapse) { $label = substr_replace($label, '1', 11, 1); } else { @@ -157,7 +160,7 @@ class ExceptionCaster } $f = $frames[$i - 1]; if ($trace->keepArgs && !empty($f['args']) && $frame instanceof EnumStub) { - $frame->value['arguments'] = new ArgsStub($f['args'], isset($f['function']) ? $f['function'] : null, isset($f['class']) ? $f['class'] : null); + $frame->value['arguments'] = new ArgsStub($f['args'], $f['function'] ?? null, $f['class'] ?? null); } } elseif ('???' !== $lastCall) { $label = new ClassStub($lastCall); @@ -181,7 +184,7 @@ class ExceptionCaster return $a; } - public static function castFrameStub(FrameStub $frame, array $a, Stub $stub, $isNested) + public static function castFrameStub(FrameStub $frame, array $a, Stub $stub, bool $isNested) { if (!$isNested) { return $a; @@ -202,39 +205,44 @@ class ExceptionCaster $f['file'] = substr($f['file'], 0, -\strlen($match[0])); $f['line'] = (int) $match[1]; } - $caller = isset($f['function']) ? sprintf('in %s() on line %d', (isset($f['class']) ? $f['class'].$f['type'] : '').$f['function'], $f['line']) : null; $src = $f['line']; $srcKey = $f['file']; $ellipsis = new LinkStub($srcKey, 0); $srcAttr = 'collapse='.(int) $ellipsis->inVendor; - $ellipsisTail = isset($ellipsis->attr['ellipsis-tail']) ? $ellipsis->attr['ellipsis-tail'] : 0; - $ellipsis = isset($ellipsis->attr['ellipsis']) ? $ellipsis->attr['ellipsis'] : 0; + $ellipsisTail = $ellipsis->attr['ellipsis-tail'] ?? 0; + $ellipsis = $ellipsis->attr['ellipsis'] ?? 0; - if (file_exists($f['file']) && 0 <= self::$srcContext) { + if (is_file($f['file']) && 0 <= self::$srcContext) { if (!empty($f['class']) && (is_subclass_of($f['class'], 'Twig\Template') || is_subclass_of($f['class'], 'Twig_Template')) && method_exists($f['class'], 'getDebugInfo')) { - $template = isset($f['object']) ? $f['object'] : unserialize(sprintf('O:%d:"%s":0:{}', \strlen($f['class']), $f['class'])); - - $ellipsis = 0; - $templateSrc = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : ''); - $templateInfo = $template->getDebugInfo(); - if (isset($templateInfo[$f['line']])) { - if (!method_exists($template, 'getSourceContext') || !file_exists($templatePath = $template->getSourceContext()->getPath())) { - $templatePath = null; - } - if ($templateSrc) { - $src = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext, $caller, 'twig', $templatePath); - $srcKey = ($templatePath ?: $template->getTemplateName()).':'.$templateInfo[$f['line']]; + $template = null; + if (isset($f['object'])) { + $template = $f['object']; + } elseif ((new \ReflectionClass($f['class']))->isInstantiable()) { + $template = unserialize(sprintf('O:%d:"%s":0:{}', \strlen($f['class']), $f['class'])); + } + if (null !== $template) { + $ellipsis = 0; + $templateSrc = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : ''); + $templateInfo = $template->getDebugInfo(); + if (isset($templateInfo[$f['line']])) { + if (!method_exists($template, 'getSourceContext') || !is_file($templatePath = $template->getSourceContext()->getPath())) { + $templatePath = null; + } + if ($templateSrc) { + $src = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext, 'twig', $templatePath, $f); + $srcKey = ($templatePath ?: $template->getTemplateName()).':'.$templateInfo[$f['line']]; + } } } } if ($srcKey == $f['file']) { - $src = self::extractSource(file_get_contents($f['file']), $f['line'], self::$srcContext, $caller, 'php', $f['file']); + $src = self::extractSource(file_get_contents($f['file']), $f['line'], self::$srcContext, 'php', $f['file'], $f); $srcKey .= ':'.$f['line']; if ($ellipsis) { $ellipsis += 1 + \strlen($f['line']); } } - $srcAttr .= '&separator= '; + $srcAttr .= sprintf('&separator= &file=%s&line=%d', rawurlencode($f['file']), $f['line']); } else { $srcAttr .= '&separator=:'; } @@ -259,7 +267,7 @@ class ExceptionCaster return $a; } - private static function filterExceptionArray($xClass, array $a, $xPrefix, $filter) + private static function filterExceptionArray(string $xClass, array $a, string $xPrefix, int $filter): array { if (isset($a[$xPrefix.'trace'])) { $trace = $a[$xPrefix.'trace']; @@ -279,6 +287,12 @@ class ExceptionCaster } unset($a[$xPrefix.'string'], $a[Caster::PREFIX_DYNAMIC.'xdebug_message'], $a[Caster::PREFIX_DYNAMIC.'__destructorException']); + if (isset($a[Caster::PREFIX_PROTECTED.'message']) && str_contains($a[Caster::PREFIX_PROTECTED.'message'], "@anonymous\0")) { + $a[Caster::PREFIX_PROTECTED.'message'] = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) { + return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; + }, $a[Caster::PREFIX_PROTECTED.'message']); + } + if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) { $a[Caster::PREFIX_PROTECTED.'file'] = new LinkStub($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']); } @@ -286,7 +300,7 @@ class ExceptionCaster return $a; } - private static function traceUnshift(&$trace, $class, $file, $line) + private static function traceUnshift(array &$trace, ?string $class, string $file, int $line): void { if (isset($trace[0]['file'], $trace[0]['line']) && $trace[0]['file'] === $file && $trace[0]['line'] === $line) { return; @@ -298,16 +312,41 @@ class ExceptionCaster ]); } - private static function extractSource($srcLines, $line, $srcContext, $title, $lang, $file = null) + private static function extractSource(string $srcLines, int $line, int $srcContext, string $lang, ?string $file, array $frame): EnumStub { $srcLines = explode("\n", $srcLines); $src = []; for ($i = $line - 1 - $srcContext; $i <= $line - 1 + $srcContext; ++$i) { - $src[] = (isset($srcLines[$i]) ? $srcLines[$i] : '')."\n"; + $src[] = ($srcLines[$i] ?? '')."\n"; + } + + if ($frame['function'] ?? false) { + $stub = new CutStub(new \stdClass()); + $stub->class = (isset($frame['class']) ? $frame['class'].$frame['type'] : '').$frame['function']; + $stub->type = Stub::TYPE_OBJECT; + $stub->attr['cut_hash'] = true; + $stub->attr['file'] = $frame['file']; + $stub->attr['line'] = $frame['line']; + + try { + $caller = isset($frame['class']) ? new \ReflectionMethod($frame['class'], $frame['function']) : new \ReflectionFunction($frame['function']); + $stub->class .= ReflectionCaster::getSignature(ReflectionCaster::castFunctionAbstract($caller, [], $stub, true, Caster::EXCLUDE_VERBOSE)); + + if ($f = $caller->getFileName()) { + $stub->attr['file'] = $f; + $stub->attr['line'] = $caller->getStartLine(); + } + } catch (\ReflectionException $e) { + // ignore fake class/function + } + + $srcLines = ["\0~separator=\0" => $stub]; + } else { + $stub = null; + $srcLines = []; } - $srcLines = []; $ltrim = 0; do { $pad = null; @@ -334,7 +373,7 @@ class ExceptionCaster if ($i !== $srcContext) { $c = new ConstStub('default', $c); } else { - $c = new ConstStub($c, $title); + $c = new ConstStub($c, $stub ? 'in '.$stub->class : ''); if (null !== $file) { $c->attr['file'] = $file; $c->attr['line'] = $line; diff --git a/lib/symfony/var-dumper/Caster/FiberCaster.php b/lib/symfony/var-dumper/Caster/FiberCaster.php new file mode 100644 index 0000000000..c74a9e59c4 --- /dev/null +++ b/lib/symfony/var-dumper/Caster/FiberCaster.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 Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Fiber related classes to array representation. + * + * @author Grégoire Pineau + */ +final class FiberCaster +{ + public static function castFiber(\Fiber $fiber, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + + if ($fiber->isTerminated()) { + $status = 'terminated'; + } elseif ($fiber->isRunning()) { + $status = 'running'; + } elseif ($fiber->isSuspended()) { + $status = 'suspended'; + } elseif ($fiber->isStarted()) { + $status = 'started'; + } else { + $status = 'not started'; + } + + $a[$prefix.'status'] = $status; + + return $a; + } +} diff --git a/lib/symfony/var-dumper/Caster/FrameStub.php b/lib/symfony/var-dumper/Caster/FrameStub.php index 1e1194dc85..878675528f 100644 --- a/lib/symfony/var-dumper/Caster/FrameStub.php +++ b/lib/symfony/var-dumper/Caster/FrameStub.php @@ -21,7 +21,7 @@ class FrameStub extends EnumStub public $keepArgs; public $inTraceStub; - public function __construct(array $frame, $keepArgs = true, $inTraceStub = false) + public function __construct(array $frame, bool $keepArgs = true, bool $inTraceStub = false) { $this->value = $frame; $this->keepArgs = $keepArgs; diff --git a/lib/symfony/var-dumper/Caster/GmpCaster.php b/lib/symfony/var-dumper/Caster/GmpCaster.php new file mode 100644 index 0000000000..b018cc7f87 --- /dev/null +++ b/lib/symfony/var-dumper/Caster/GmpCaster.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts GMP objects to array representation. + * + * @author Hamza Amrouche + * @author Nicolas Grekas + * + * @final + */ +class GmpCaster +{ + public static function castGmp(\GMP $gmp, array $a, Stub $stub, bool $isNested, int $filter): array + { + $a[Caster::PREFIX_VIRTUAL.'value'] = new ConstStub(gmp_strval($gmp), gmp_strval($gmp)); + + return $a; + } +} diff --git a/lib/symfony/var-dumper/Caster/ImagineCaster.php b/lib/symfony/var-dumper/Caster/ImagineCaster.php new file mode 100644 index 0000000000..d1289da337 --- /dev/null +++ b/lib/symfony/var-dumper/Caster/ImagineCaster.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Imagine\Image\ImageInterface; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Grégoire Pineau + */ +final class ImagineCaster +{ + public static function castImage(ImageInterface $c, array $a, Stub $stub, bool $isNested): array + { + $imgData = $c->get('png'); + if (\strlen($imgData) > 1 * 1000 * 1000) { + $a += [ + Caster::PREFIX_VIRTUAL.'image' => new ConstStub($c->getSize()), + ]; + } else { + $a += [ + Caster::PREFIX_VIRTUAL.'image' => new ImgStub($imgData, 'image/png', $c->getSize()), + ]; + } + + return $a; + } +} diff --git a/lib/symfony/var-dumper/Caster/ImgStub.php b/lib/symfony/var-dumper/Caster/ImgStub.php new file mode 100644 index 0000000000..a16681f736 --- /dev/null +++ b/lib/symfony/var-dumper/Caster/ImgStub.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * @author Grégoire Pineau + */ +class ImgStub extends ConstStub +{ + public function __construct(string $data, string $contentType, string $size = '') + { + $this->value = ''; + $this->attr['img-data'] = $data; + $this->attr['img-size'] = $size; + $this->attr['content-type'] = $contentType; + } +} diff --git a/lib/symfony/var-dumper/Caster/IntlCaster.php b/lib/symfony/var-dumper/Caster/IntlCaster.php new file mode 100644 index 0000000000..1ed91d4d6a --- /dev/null +++ b/lib/symfony/var-dumper/Caster/IntlCaster.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + * @author Jan Schädlich + * + * @final + */ +class IntlCaster +{ + public static function castMessageFormatter(\MessageFormatter $c, array $a, Stub $stub, bool $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), + Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), + ]; + + return self::castError($c, $a); + } + + public static function castNumberFormatter(\NumberFormatter $c, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $a += [ + Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), + Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), + ]; + + if ($filter & Caster::EXCLUDE_VERBOSE) { + $stub->cut += 3; + + return self::castError($c, $a); + } + + $a += [ + Caster::PREFIX_VIRTUAL.'attributes' => new EnumStub( + [ + 'PARSE_INT_ONLY' => $c->getAttribute(\NumberFormatter::PARSE_INT_ONLY), + 'GROUPING_USED' => $c->getAttribute(\NumberFormatter::GROUPING_USED), + 'DECIMAL_ALWAYS_SHOWN' => $c->getAttribute(\NumberFormatter::DECIMAL_ALWAYS_SHOWN), + 'MAX_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_INTEGER_DIGITS), + 'MIN_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_INTEGER_DIGITS), + 'INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::INTEGER_DIGITS), + 'MAX_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_FRACTION_DIGITS), + 'MIN_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_FRACTION_DIGITS), + 'FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::FRACTION_DIGITS), + 'MULTIPLIER' => $c->getAttribute(\NumberFormatter::MULTIPLIER), + 'GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::GROUPING_SIZE), + 'ROUNDING_MODE' => $c->getAttribute(\NumberFormatter::ROUNDING_MODE), + 'ROUNDING_INCREMENT' => $c->getAttribute(\NumberFormatter::ROUNDING_INCREMENT), + 'FORMAT_WIDTH' => $c->getAttribute(\NumberFormatter::FORMAT_WIDTH), + 'PADDING_POSITION' => $c->getAttribute(\NumberFormatter::PADDING_POSITION), + 'SECONDARY_GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::SECONDARY_GROUPING_SIZE), + 'SIGNIFICANT_DIGITS_USED' => $c->getAttribute(\NumberFormatter::SIGNIFICANT_DIGITS_USED), + 'MIN_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_SIGNIFICANT_DIGITS), + 'MAX_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_SIGNIFICANT_DIGITS), + 'LENIENT_PARSE' => $c->getAttribute(\NumberFormatter::LENIENT_PARSE), + ] + ), + Caster::PREFIX_VIRTUAL.'text_attributes' => new EnumStub( + [ + 'POSITIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_PREFIX), + 'POSITIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_SUFFIX), + 'NEGATIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_PREFIX), + 'NEGATIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_SUFFIX), + 'PADDING_CHARACTER' => $c->getTextAttribute(\NumberFormatter::PADDING_CHARACTER), + 'CURRENCY_CODE' => $c->getTextAttribute(\NumberFormatter::CURRENCY_CODE), + 'DEFAULT_RULESET' => $c->getTextAttribute(\NumberFormatter::DEFAULT_RULESET), + 'PUBLIC_RULESETS' => $c->getTextAttribute(\NumberFormatter::PUBLIC_RULESETS), + ] + ), + Caster::PREFIX_VIRTUAL.'symbols' => new EnumStub( + [ + 'DECIMAL_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL), + 'GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL), + 'PATTERN_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::PATTERN_SEPARATOR_SYMBOL), + 'PERCENT_SYMBOL' => $c->getSymbol(\NumberFormatter::PERCENT_SYMBOL), + 'ZERO_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::ZERO_DIGIT_SYMBOL), + 'DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::DIGIT_SYMBOL), + 'MINUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::MINUS_SIGN_SYMBOL), + 'PLUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::PLUS_SIGN_SYMBOL), + 'CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::CURRENCY_SYMBOL), + 'INTL_CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::INTL_CURRENCY_SYMBOL), + 'MONETARY_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL), + 'EXPONENTIAL_SYMBOL' => $c->getSymbol(\NumberFormatter::EXPONENTIAL_SYMBOL), + 'PERMILL_SYMBOL' => $c->getSymbol(\NumberFormatter::PERMILL_SYMBOL), + 'PAD_ESCAPE_SYMBOL' => $c->getSymbol(\NumberFormatter::PAD_ESCAPE_SYMBOL), + 'INFINITY_SYMBOL' => $c->getSymbol(\NumberFormatter::INFINITY_SYMBOL), + 'NAN_SYMBOL' => $c->getSymbol(\NumberFormatter::NAN_SYMBOL), + 'SIGNIFICANT_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL), + 'MONETARY_GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL), + ] + ), + ]; + + return self::castError($c, $a); + } + + public static function castIntlTimeZone(\IntlTimeZone $c, array $a, Stub $stub, bool $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'display_name' => $c->getDisplayName(), + Caster::PREFIX_VIRTUAL.'id' => $c->getID(), + Caster::PREFIX_VIRTUAL.'raw_offset' => $c->getRawOffset(), + ]; + + if ($c->useDaylightTime()) { + $a += [ + Caster::PREFIX_VIRTUAL.'dst_savings' => $c->getDSTSavings(), + ]; + } + + return self::castError($c, $a); + } + + public static function castIntlCalendar(\IntlCalendar $c, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $a += [ + Caster::PREFIX_VIRTUAL.'type' => $c->getType(), + Caster::PREFIX_VIRTUAL.'first_day_of_week' => $c->getFirstDayOfWeek(), + Caster::PREFIX_VIRTUAL.'minimal_days_in_first_week' => $c->getMinimalDaysInFirstWeek(), + Caster::PREFIX_VIRTUAL.'repeated_wall_time_option' => $c->getRepeatedWallTimeOption(), + Caster::PREFIX_VIRTUAL.'skipped_wall_time_option' => $c->getSkippedWallTimeOption(), + Caster::PREFIX_VIRTUAL.'time' => $c->getTime(), + Caster::PREFIX_VIRTUAL.'in_daylight_time' => $c->inDaylightTime(), + Caster::PREFIX_VIRTUAL.'is_lenient' => $c->isLenient(), + Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(), + ]; + + return self::castError($c, $a); + } + + public static function castIntlDateFormatter(\IntlDateFormatter $c, array $a, Stub $stub, bool $isNested, int $filter = 0) + { + $a += [ + Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), + Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), + Caster::PREFIX_VIRTUAL.'calendar' => $c->getCalendar(), + Caster::PREFIX_VIRTUAL.'time_zone_id' => $c->getTimeZoneId(), + Caster::PREFIX_VIRTUAL.'time_type' => $c->getTimeType(), + Caster::PREFIX_VIRTUAL.'date_type' => $c->getDateType(), + Caster::PREFIX_VIRTUAL.'calendar_object' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getCalendarObject()) : $c->getCalendarObject(), + Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(), + ]; + + return self::castError($c, $a); + } + + private static function castError(object $c, array $a): array + { + if ($errorCode = $c->getErrorCode()) { + $a += [ + Caster::PREFIX_VIRTUAL.'error_code' => $errorCode, + Caster::PREFIX_VIRTUAL.'error_message' => $c->getErrorMessage(), + ]; + } + + return $a; + } +} diff --git a/lib/symfony/var-dumper/Caster/LinkStub.php b/lib/symfony/var-dumper/Caster/LinkStub.php index b589b502d4..7e0780339a 100644 --- a/lib/symfony/var-dumper/Caster/LinkStub.php +++ b/lib/symfony/var-dumper/Caster/LinkStub.php @@ -23,7 +23,7 @@ class LinkStub extends ConstStub private static $vendorRoots; private static $composerRoots; - public function __construct($label, $line = 0, $href = null) + public function __construct(string $label, int $line = 0, string $href = null) { $this->value = $label; @@ -33,17 +33,17 @@ class LinkStub extends ConstStub if (!\is_string($href)) { return; } - if (0 === strpos($href, 'file://')) { + if (str_starts_with($href, 'file://')) { if ($href === $label) { $label = substr($label, 7); } $href = substr($href, 7); - } elseif (false !== strpos($href, '://')) { + } elseif (str_contains($href, '://')) { $this->attr['href'] = $href; return; } - if (!file_exists($href)) { + if (!is_file($href)) { return; } if ($line) { @@ -63,16 +63,16 @@ class LinkStub extends ConstStub } } - private function getComposerRoot($file, &$inVendor) + private function getComposerRoot(string $file, bool &$inVendor) { if (null === self::$vendorRoots) { self::$vendorRoots = []; foreach (get_declared_classes() as $class) { - if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) { + if ('C' === $class[0] && str_starts_with($class, 'ComposerAutoloaderInit')) { $r = new \ReflectionClass($class); - $v = \dirname(\dirname($r->getFileName())); - if (file_exists($v.'/composer/installed.json')) { + $v = \dirname($r->getFileName(), 2); + if (is_file($v.'/composer/installed.json')) { self::$vendorRoots[] = $v.\DIRECTORY_SEPARATOR; } } @@ -85,13 +85,13 @@ class LinkStub extends ConstStub } foreach (self::$vendorRoots as $root) { - if ($inVendor = 0 === strpos($file, $root)) { + if ($inVendor = str_starts_with($file, $root)) { return $root; } } $parent = $dir; - while (!@file_exists($parent.'/composer.json')) { + while (!@is_file($parent.'/composer.json')) { if (!@file_exists($parent)) { // open_basedir restriction in effect break; diff --git a/lib/symfony/var-dumper/Caster/MemcachedCaster.php b/lib/symfony/var-dumper/Caster/MemcachedCaster.php new file mode 100644 index 0000000000..cfef19acc3 --- /dev/null +++ b/lib/symfony/var-dumper/Caster/MemcachedCaster.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 Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Jan Schädlich + * + * @final + */ +class MemcachedCaster +{ + private static $optionConstants; + private static $defaultOptions; + + public static function castMemcached(\Memcached $c, array $a, Stub $stub, bool $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'servers' => $c->getServerList(), + Caster::PREFIX_VIRTUAL.'options' => new EnumStub( + self::getNonDefaultOptions($c) + ), + ]; + + return $a; + } + + private static function getNonDefaultOptions(\Memcached $c): array + { + self::$defaultOptions = self::$defaultOptions ?? self::discoverDefaultOptions(); + self::$optionConstants = self::$optionConstants ?? self::getOptionConstants(); + + $nonDefaultOptions = []; + foreach (self::$optionConstants as $constantKey => $value) { + if (self::$defaultOptions[$constantKey] !== $option = $c->getOption($value)) { + $nonDefaultOptions[$constantKey] = $option; + } + } + + return $nonDefaultOptions; + } + + private static function discoverDefaultOptions(): array + { + $defaultMemcached = new \Memcached(); + $defaultMemcached->addServer('127.0.0.1', 11211); + + $defaultOptions = []; + self::$optionConstants = self::$optionConstants ?? self::getOptionConstants(); + + foreach (self::$optionConstants as $constantKey => $value) { + $defaultOptions[$constantKey] = $defaultMemcached->getOption($value); + } + + return $defaultOptions; + } + + private static function getOptionConstants(): array + { + $reflectedMemcached = new \ReflectionClass(\Memcached::class); + + $optionConstants = []; + foreach ($reflectedMemcached->getConstants() as $constantKey => $value) { + if (str_starts_with($constantKey, 'OPT_')) { + $optionConstants[$constantKey] = $value; + } + } + + return $optionConstants; + } +} diff --git a/lib/symfony/var-dumper/Caster/MongoCaster.php b/lib/symfony/var-dumper/Caster/MongoCaster.php deleted file mode 100644 index 98f1b8e25d..0000000000 --- a/lib/symfony/var-dumper/Caster/MongoCaster.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\VarDumper\Caster; - -use Symfony\Component\VarDumper\Cloner\Stub; - -@trigger_error('The '.__NAMESPACE__.'\MongoCaster class is deprecated since Symfony 3.4 and will be removed in 4.0.', \E_USER_DEPRECATED); - -/** - * Casts classes from the MongoDb extension to array representation. - * - * @author Nicolas Grekas - * - * @deprecated since version 3.4, to be removed in 4.0. - */ -class MongoCaster -{ - public static function castCursor(\MongoCursorInterface $cursor, array $a, Stub $stub, $isNested) - { - if ($info = $cursor->info()) { - foreach ($info as $k => $v) { - $a[Caster::PREFIX_VIRTUAL.$k] = $v; - } - } - $a[Caster::PREFIX_VIRTUAL.'dead'] = $cursor->dead(); - - return $a; - } -} diff --git a/lib/symfony/var-dumper/Caster/MysqliCaster.php b/lib/symfony/var-dumper/Caster/MysqliCaster.php new file mode 100644 index 0000000000..bfe6f0822d --- /dev/null +++ b/lib/symfony/var-dumper/Caster/MysqliCaster.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\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class MysqliCaster +{ + public static function castMysqliDriver(\mysqli_driver $c, array $a, Stub $stub, bool $isNested): array + { + foreach ($a as $k => $v) { + if (isset($c->$k)) { + $a[$k] = $c->$k; + } + } + + return $a; + } +} diff --git a/lib/symfony/var-dumper/Caster/PdoCaster.php b/lib/symfony/var-dumper/Caster/PdoCaster.php index 8af51829a9..140473b536 100644 --- a/lib/symfony/var-dumper/Caster/PdoCaster.php +++ b/lib/symfony/var-dumper/Caster/PdoCaster.php @@ -17,10 +17,12 @@ use Symfony\Component\VarDumper\Cloner\Stub; * Casts PDO related classes to array representation. * * @author Nicolas Grekas + * + * @final */ class PdoCaster { - private static $pdoAttributes = [ + private const PDO_ATTRIBUTES = [ 'CASE' => [ \PDO::CASE_LOWER => 'LOWER', \PDO::CASE_NATURAL => 'NATURAL', @@ -57,13 +59,13 @@ class PdoCaster ], ]; - public static function castPdo(\PDO $c, array $a, Stub $stub, $isNested) + public static function castPdo(\PDO $c, array $a, Stub $stub, bool $isNested) { $attr = []; $errmode = $c->getAttribute(\PDO::ATTR_ERRMODE); $c->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); - foreach (self::$pdoAttributes as $k => $v) { + foreach (self::PDO_ATTRIBUTES as $k => $v) { if (!isset($k[0])) { $k = $v; $v = []; @@ -106,7 +108,7 @@ class PdoCaster return $a; } - public static function castPdoStatement(\PDOStatement $c, array $a, Stub $stub, $isNested) + public static function castPdoStatement(\PDOStatement $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; $a[$prefix.'errorInfo'] = $c->errorInfo(); diff --git a/lib/symfony/var-dumper/Caster/PgSqlCaster.php b/lib/symfony/var-dumper/Caster/PgSqlCaster.php index fe1f0cc8d9..d8e5b52534 100644 --- a/lib/symfony/var-dumper/Caster/PgSqlCaster.php +++ b/lib/symfony/var-dumper/Caster/PgSqlCaster.php @@ -17,10 +17,12 @@ use Symfony\Component\VarDumper\Cloner\Stub; * Casts pqsql resources to array representation. * * @author Nicolas Grekas + * + * @final */ class PgSqlCaster { - private static $paramCodes = [ + private const PARAM_CODES = [ 'server_encoding', 'client_encoding', 'is_superuser', @@ -33,7 +35,7 @@ class PgSqlCaster 'standard_conforming_strings', ]; - private static $transactionStatus = [ + private const TRANSACTION_STATUS = [ \PGSQL_TRANSACTION_IDLE => 'PGSQL_TRANSACTION_IDLE', \PGSQL_TRANSACTION_ACTIVE => 'PGSQL_TRANSACTION_ACTIVE', \PGSQL_TRANSACTION_INTRANS => 'PGSQL_TRANSACTION_INTRANS', @@ -41,7 +43,7 @@ class PgSqlCaster \PGSQL_TRANSACTION_UNKNOWN => 'PGSQL_TRANSACTION_UNKNOWN', ]; - private static $resultStatus = [ + private const RESULT_STATUS = [ \PGSQL_EMPTY_QUERY => 'PGSQL_EMPTY_QUERY', \PGSQL_COMMAND_OK => 'PGSQL_COMMAND_OK', \PGSQL_TUPLES_OK => 'PGSQL_TUPLES_OK', @@ -52,7 +54,7 @@ class PgSqlCaster \PGSQL_FATAL_ERROR => 'PGSQL_FATAL_ERROR', ]; - private static $diagCodes = [ + private const DIAG_CODES = [ 'severity' => \PGSQL_DIAG_SEVERITY, 'sqlstate' => \PGSQL_DIAG_SQLSTATE, 'message' => \PGSQL_DIAG_MESSAGE_PRIMARY, @@ -67,22 +69,22 @@ class PgSqlCaster 'function' => \PGSQL_DIAG_SOURCE_FUNCTION, ]; - public static function castLargeObject($lo, array $a, Stub $stub, $isNested) + public static function castLargeObject($lo, array $a, Stub $stub, bool $isNested) { $a['seek position'] = pg_lo_tell($lo); return $a; } - public static function castLink($link, array $a, Stub $stub, $isNested) + public static function castLink($link, array $a, Stub $stub, bool $isNested) { $a['status'] = pg_connection_status($link); $a['status'] = new ConstStub(\PGSQL_CONNECTION_OK === $a['status'] ? 'PGSQL_CONNECTION_OK' : 'PGSQL_CONNECTION_BAD', $a['status']); $a['busy'] = pg_connection_busy($link); $a['transaction'] = pg_transaction_status($link); - if (isset(self::$transactionStatus[$a['transaction']])) { - $a['transaction'] = new ConstStub(self::$transactionStatus[$a['transaction']], $a['transaction']); + if (isset(self::TRANSACTION_STATUS[$a['transaction']])) { + $a['transaction'] = new ConstStub(self::TRANSACTION_STATUS[$a['transaction']], $a['transaction']); } $a['pid'] = pg_get_pid($link); @@ -94,7 +96,7 @@ class PgSqlCaster $a['options'] = pg_options($link); $a['version'] = pg_version($link); - foreach (self::$paramCodes as $v) { + foreach (self::PARAM_CODES as $v) { if (false !== $s = pg_parameter_status($link, $v)) { $a['param'][$v] = $s; } @@ -106,17 +108,17 @@ class PgSqlCaster return $a; } - public static function castResult($result, array $a, Stub $stub, $isNested) + public static function castResult($result, array $a, Stub $stub, bool $isNested) { $a['num rows'] = pg_num_rows($result); $a['status'] = pg_result_status($result); - if (isset(self::$resultStatus[$a['status']])) { - $a['status'] = new ConstStub(self::$resultStatus[$a['status']], $a['status']); + if (isset(self::RESULT_STATUS[$a['status']])) { + $a['status'] = new ConstStub(self::RESULT_STATUS[$a['status']], $a['status']); } $a['command-completion tag'] = pg_result_status($result, \PGSQL_STATUS_STRING); if (-1 === $a['num rows']) { - foreach (self::$diagCodes as $k => $v) { + foreach (self::DIAG_CODES as $k => $v) { $a['error'][$k] = pg_result_error_field($result, $v); } } diff --git a/lib/symfony/var-dumper/Caster/ProxyManagerCaster.php b/lib/symfony/var-dumper/Caster/ProxyManagerCaster.php new file mode 100644 index 0000000000..e7120191fe --- /dev/null +++ b/lib/symfony/var-dumper/Caster/ProxyManagerCaster.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\VarDumper\Caster; + +use ProxyManager\Proxy\ProxyInterface; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + * + * @final + */ +class ProxyManagerCaster +{ + public static function castProxy(ProxyInterface $c, array $a, Stub $stub, bool $isNested) + { + if ($parent = get_parent_class($c)) { + $stub->class .= ' - '.$parent; + } + $stub->class .= '@proxy'; + + return $a; + } +} diff --git a/lib/symfony/var-dumper/Caster/RdKafkaCaster.php b/lib/symfony/var-dumper/Caster/RdKafkaCaster.php new file mode 100644 index 0000000000..db4bba8d38 --- /dev/null +++ b/lib/symfony/var-dumper/Caster/RdKafkaCaster.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use RdKafka\Conf; +use RdKafka\Exception as RdKafkaException; +use RdKafka\KafkaConsumer; +use RdKafka\Message; +use RdKafka\Metadata\Broker as BrokerMetadata; +use RdKafka\Metadata\Collection as CollectionMetadata; +use RdKafka\Metadata\Partition as PartitionMetadata; +use RdKafka\Metadata\Topic as TopicMetadata; +use RdKafka\Topic; +use RdKafka\TopicConf; +use RdKafka\TopicPartition; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts RdKafka related classes to array representation. + * + * @author Romain Neutron + */ +class RdKafkaCaster +{ + public static function castKafkaConsumer(KafkaConsumer $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + try { + $assignment = $c->getAssignment(); + } catch (RdKafkaException $e) { + $assignment = []; + } + + $a += [ + $prefix.'subscription' => $c->getSubscription(), + $prefix.'assignment' => $assignment, + ]; + + $a += self::extractMetadata($c); + + return $a; + } + + public static function castTopic(Topic $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'name' => $c->getName(), + ]; + + return $a; + } + + public static function castTopicPartition(TopicPartition $c, array $a) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'offset' => $c->getOffset(), + $prefix.'partition' => $c->getPartition(), + $prefix.'topic' => $c->getTopic(), + ]; + + return $a; + } + + public static function castMessage(Message $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'errstr' => $c->errstr(), + ]; + + return $a; + } + + public static function castConf(Conf $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + foreach ($c->dump() as $key => $value) { + $a[$prefix.$key] = $value; + } + + return $a; + } + + public static function castTopicConf(TopicConf $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + foreach ($c->dump() as $key => $value) { + $a[$prefix.$key] = $value; + } + + return $a; + } + + public static function castRdKafka(\RdKafka $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'out_q_len' => $c->getOutQLen(), + ]; + + $a += self::extractMetadata($c); + + return $a; + } + + public static function castCollectionMetadata(CollectionMetadata $c, array $a, Stub $stub, bool $isNested) + { + $a += iterator_to_array($c); + + return $a; + } + + public static function castTopicMetadata(TopicMetadata $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'name' => $c->getTopic(), + $prefix.'partitions' => $c->getPartitions(), + ]; + + return $a; + } + + public static function castPartitionMetadata(PartitionMetadata $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'id' => $c->getId(), + $prefix.'err' => $c->getErr(), + $prefix.'leader' => $c->getLeader(), + ]; + + return $a; + } + + public static function castBrokerMetadata(BrokerMetadata $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'id' => $c->getId(), + $prefix.'host' => $c->getHost(), + $prefix.'port' => $c->getPort(), + ]; + + return $a; + } + + private static function extractMetadata($c) + { + $prefix = Caster::PREFIX_VIRTUAL; + + try { + $m = $c->getMetadata(true, null, 500); + } catch (RdKafkaException $e) { + return []; + } + + return [ + $prefix.'orig_broker_id' => $m->getOrigBrokerId(), + $prefix.'orig_broker_name' => $m->getOrigBrokerName(), + $prefix.'brokers' => $m->getBrokers(), + $prefix.'topics' => $m->getTopics(), + ]; + } +} diff --git a/lib/symfony/var-dumper/Caster/RedisCaster.php b/lib/symfony/var-dumper/Caster/RedisCaster.php index 1e2fb39916..8f97eaad3b 100644 --- a/lib/symfony/var-dumper/Caster/RedisCaster.php +++ b/lib/symfony/var-dumper/Caster/RedisCaster.php @@ -17,61 +17,136 @@ use Symfony\Component\VarDumper\Cloner\Stub; * Casts Redis class from ext-redis to array representation. * * @author Nicolas Grekas + * + * @final */ class RedisCaster { - private static $serializer = [ + private const SERIALIZERS = [ \Redis::SERIALIZER_NONE => 'NONE', \Redis::SERIALIZER_PHP => 'PHP', 2 => 'IGBINARY', // Optional Redis::SERIALIZER_IGBINARY ]; - public static function castRedis(\Redis $c, array $a, Stub $stub, $isNested) + private const MODES = [ + \Redis::ATOMIC => 'ATOMIC', + \Redis::MULTI => 'MULTI', + \Redis::PIPELINE => 'PIPELINE', + ]; + + private const COMPRESSION_MODES = [ + 0 => 'NONE', // Redis::COMPRESSION_NONE + 1 => 'LZF', // Redis::COMPRESSION_LZF + ]; + + private const FAILOVER_OPTIONS = [ + \RedisCluster::FAILOVER_NONE => 'NONE', + \RedisCluster::FAILOVER_ERROR => 'ERROR', + \RedisCluster::FAILOVER_DISTRIBUTE => 'DISTRIBUTE', + \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES => 'DISTRIBUTE_SLAVES', + ]; + + public static function castRedis(\Redis $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; - if (\defined('HHVM_VERSION_ID')) { - if (isset($a[Caster::PREFIX_PROTECTED.'serializer'])) { - $ser = $a[Caster::PREFIX_PROTECTED.'serializer']; - $a[Caster::PREFIX_PROTECTED.'serializer'] = isset(self::$serializer[$ser]) ? new ConstStub(self::$serializer[$ser], $ser) : $ser; - } - - return $a; - } - if (!$connected = $c->isConnected()) { return $a + [ $prefix.'isConnected' => $connected, ]; } - $ser = $c->getOption(\Redis::OPT_SERIALIZER); - $retry = \defined('Redis::OPT_SCAN') ? $c->getOption(\Redis::OPT_SCAN) : 0; + $mode = $c->getMode(); return $a + [ $prefix.'isConnected' => $connected, $prefix.'host' => $c->getHost(), $prefix.'port' => $c->getPort(), $prefix.'auth' => $c->getAuth(), + $prefix.'mode' => isset(self::MODES[$mode]) ? new ConstStub(self::MODES[$mode], $mode) : $mode, $prefix.'dbNum' => $c->getDbNum(), $prefix.'timeout' => $c->getTimeout(), + $prefix.'lastError' => $c->getLastError(), $prefix.'persistentId' => $c->getPersistentID(), - $prefix.'options' => new EnumStub([ - 'READ_TIMEOUT' => $c->getOption(\Redis::OPT_READ_TIMEOUT), - 'SERIALIZER' => isset(self::$serializer[$ser]) ? new ConstStub(self::$serializer[$ser], $ser) : $ser, - 'PREFIX' => $c->getOption(\Redis::OPT_PREFIX), - 'SCAN' => new ConstStub($retry ? 'RETRY' : 'NORETRY', $retry), - ]), + $prefix.'options' => self::getRedisOptions($c), ]; } - public static function castRedisArray(\RedisArray $c, array $a, Stub $stub, $isNested) + public static function castRedisArray(\RedisArray $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; return $a + [ $prefix.'hosts' => $c->_hosts(), $prefix.'function' => ClassStub::wrapCallable($c->_function()), + $prefix.'lastError' => $c->getLastError(), + $prefix.'options' => self::getRedisOptions($c), ]; } + + public static function castRedisCluster(\RedisCluster $c, array $a, Stub $stub, bool $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + $failover = $c->getOption(\RedisCluster::OPT_SLAVE_FAILOVER); + + $a += [ + $prefix.'_masters' => $c->_masters(), + $prefix.'_redir' => $c->_redir(), + $prefix.'mode' => new ConstStub($c->getMode() ? 'MULTI' : 'ATOMIC', $c->getMode()), + $prefix.'lastError' => $c->getLastError(), + $prefix.'options' => self::getRedisOptions($c, [ + 'SLAVE_FAILOVER' => isset(self::FAILOVER_OPTIONS[$failover]) ? new ConstStub(self::FAILOVER_OPTIONS[$failover], $failover) : $failover, + ]), + ]; + + return $a; + } + + /** + * @param \Redis|\RedisArray|\RedisCluster $redis + */ + private static function getRedisOptions($redis, array $options = []): EnumStub + { + $serializer = $redis->getOption(\Redis::OPT_SERIALIZER); + if (\is_array($serializer)) { + foreach ($serializer as &$v) { + if (isset(self::SERIALIZERS[$v])) { + $v = new ConstStub(self::SERIALIZERS[$v], $v); + } + } + } elseif (isset(self::SERIALIZERS[$serializer])) { + $serializer = new ConstStub(self::SERIALIZERS[$serializer], $serializer); + } + + $compression = \defined('Redis::OPT_COMPRESSION') ? $redis->getOption(\Redis::OPT_COMPRESSION) : 0; + if (\is_array($compression)) { + foreach ($compression as &$v) { + if (isset(self::COMPRESSION_MODES[$v])) { + $v = new ConstStub(self::COMPRESSION_MODES[$v], $v); + } + } + } elseif (isset(self::COMPRESSION_MODES[$compression])) { + $compression = new ConstStub(self::COMPRESSION_MODES[$compression], $compression); + } + + $retry = \defined('Redis::OPT_SCAN') ? $redis->getOption(\Redis::OPT_SCAN) : 0; + if (\is_array($retry)) { + foreach ($retry as &$v) { + $v = new ConstStub($v ? 'RETRY' : 'NORETRY', $v); + } + } else { + $retry = new ConstStub($retry ? 'RETRY' : 'NORETRY', $retry); + } + + $options += [ + 'TCP_KEEPALIVE' => \defined('Redis::OPT_TCP_KEEPALIVE') ? $redis->getOption(\Redis::OPT_TCP_KEEPALIVE) : 0, + 'READ_TIMEOUT' => $redis->getOption(\Redis::OPT_READ_TIMEOUT), + 'COMPRESSION' => $compression, + 'SERIALIZER' => $serializer, + 'PREFIX' => $redis->getOption(\Redis::OPT_PREFIX), + 'SCAN' => $retry, + ]; + + return new EnumStub($options); + } } diff --git a/lib/symfony/var-dumper/Caster/ReflectionCaster.php b/lib/symfony/var-dumper/Caster/ReflectionCaster.php index f19886172a..274ee0d98f 100644 --- a/lib/symfony/var-dumper/Caster/ReflectionCaster.php +++ b/lib/symfony/var-dumper/Caster/ReflectionCaster.php @@ -17,10 +17,14 @@ use Symfony\Component\VarDumper\Cloner\Stub; * Casts Reflector related classes to array representation. * * @author Nicolas Grekas + * + * @final */ class ReflectionCaster { - private static $extraMap = [ + public const UNSET_CLOSURE_FILE_INFO = ['Closure' => __CLASS__.'::unsetClosureFileInfo']; + + private const EXTRA_MAP = [ 'docComment' => 'getDocComment', 'extension' => 'getExtensionName', 'isDisabled' => 'isDisabled', @@ -31,49 +35,51 @@ class ReflectionCaster 'isVariadic' => 'isVariadic', ]; - public static function castClosure(\Closure $c, array $a, Stub $stub, $isNested, $filter = 0) + public static function castClosure(\Closure $c, array $a, Stub $stub, bool $isNested, int $filter = 0) { $prefix = Caster::PREFIX_VIRTUAL; $c = new \ReflectionFunction($c); - $stub->class = 'Closure'; // HHVM generates unique class names for closures $a = static::castFunctionAbstract($c, $a, $stub, $isNested, $filter); - if (false === strpos($c->name, '{closure}')) { + if (!str_contains($c->name, '{closure}')) { $stub->class = isset($a[$prefix.'class']) ? $a[$prefix.'class']->value.'::'.$c->name : $c->name; unset($a[$prefix.'class']); } + unset($a[$prefix.'extra']); - if (isset($a[$prefix.'parameters'])) { - foreach ($a[$prefix.'parameters']->value as &$v) { - $param = $v; - $v = new EnumStub([]); - foreach (static::castParameter($param, [], $stub, true) as $k => $param) { - if ("\0" === $k[0]) { - $v->value[substr($k, 3)] = $param; - } - } - unset($v->value['position'], $v->value['isVariadic'], $v->value['byReference'], $v); - } + $stub->class .= self::getSignature($a); + + if ($f = $c->getFileName()) { + $stub->attr['file'] = $f; + $stub->attr['line'] = $c->getStartLine(); } - if (!($filter & Caster::EXCLUDE_VERBOSE) && $f = $c->getFileName()) { + unset($a[$prefix.'parameters']); + + if ($filter & Caster::EXCLUDE_VERBOSE) { + $stub->cut += ($c->getFileName() ? 2 : 0) + \count($a); + + return []; + } + + if ($f) { $a[$prefix.'file'] = new LinkStub($f, $c->getStartLine()); $a[$prefix.'line'] = $c->getStartLine().' to '.$c->getEndLine(); } - $prefix = Caster::PREFIX_DYNAMIC; - unset($a['name'], $a[$prefix.'this'], $a[$prefix.'parameter'], $a[Caster::PREFIX_VIRTUAL.'extra']); + return $a; + } + + public static function unsetClosureFileInfo(\Closure $c, array $a) + { + unset($a[Caster::PREFIX_VIRTUAL.'file'], $a[Caster::PREFIX_VIRTUAL.'line']); return $a; } - public static function castGenerator(\Generator $c, array $a, Stub $stub, $isNested) + public static function castGenerator(\Generator $c, array $a, Stub $stub, bool $isNested) { - if (!class_exists('ReflectionGenerator', false)) { - return $a; - } - // Cannot create ReflectionGenerator based on a terminated Generator try { $reflectionGenerator = new \ReflectionGenerator($c); @@ -86,20 +92,39 @@ class ReflectionCaster return self::castReflectionGenerator($reflectionGenerator, $a, $stub, $isNested); } - public static function castType(\ReflectionType $c, array $a, Stub $stub, $isNested) + public static function castType(\ReflectionType $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; - $a += [ - $prefix.'name' => $c instanceof \ReflectionNamedType ? $c->getName() : (string) $c, - $prefix.'allowsNull' => $c->allowsNull(), - $prefix.'isBuiltin' => $c->isBuiltin(), - ]; + if ($c instanceof \ReflectionNamedType || \PHP_VERSION_ID < 80000) { + $a += [ + $prefix.'name' => $c instanceof \ReflectionNamedType ? $c->getName() : (string) $c, + $prefix.'allowsNull' => $c->allowsNull(), + $prefix.'isBuiltin' => $c->isBuiltin(), + ]; + } elseif ($c instanceof \ReflectionUnionType || $c instanceof \ReflectionIntersectionType) { + $a[$prefix.'allowsNull'] = $c->allowsNull(); + self::addMap($a, $c, [ + 'types' => 'getTypes', + ]); + } else { + $a[$prefix.'allowsNull'] = $c->allowsNull(); + } return $a; } - public static function castReflectionGenerator(\ReflectionGenerator $c, array $a, Stub $stub, $isNested) + public static function castAttribute(\ReflectionAttribute $c, array $a, Stub $stub, bool $isNested) + { + self::addMap($a, $c, [ + 'name' => 'getName', + 'arguments' => 'getArguments', + ]); + + return $a; + } + + public static function castReflectionGenerator(\ReflectionGenerator $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; @@ -108,7 +133,7 @@ class ReflectionCaster } $function = $c->getFunction(); $frame = [ - 'class' => isset($function->class) ? $function->class : null, + 'class' => $function->class ?? null, 'type' => isset($function->class) ? ($function->isStatic() ? '::' : '->') : null, 'function' => $function->name, 'file' => $c->getExecutingFile(), @@ -119,16 +144,14 @@ class ReflectionCaster array_unshift($trace, [ 'function' => 'yield', 'file' => $function->getExecutingFile(), - 'line' => $function->getExecutingLine() - 1, + 'line' => $function->getExecutingLine() - (int) (\PHP_VERSION_ID < 80100), ]); $trace[] = $frame; $a[$prefix.'trace'] = new TraceStub($trace, false, 0, -1, -1); } else { $function = new FrameStub($frame, false, true); $function = ExceptionCaster::castFrameStub($function, [], $function, true); - $a[$prefix.'executing'] = new EnumStub([ - "\0~separator= \0".$frame['class'].$frame['type'].$frame['function'].'()' => $function[$prefix.'src'], - ]); + $a[$prefix.'executing'] = $function[$prefix.'src']; } $a[Caster::PREFIX_VIRTUAL.'closed'] = false; @@ -136,7 +159,7 @@ class ReflectionCaster return $a; } - public static function castClass(\ReflectionClass $c, array $a, Stub $stub, $isNested, $filter = 0) + public static function castClass(\ReflectionClass $c, array $a, Stub $stub, bool $isNested, int $filter = 0) { $prefix = Caster::PREFIX_VIRTUAL; @@ -147,7 +170,7 @@ class ReflectionCaster self::addMap($a, $c, [ 'extends' => 'getParentClass', 'implements' => 'getInterfaceNames', - 'constants' => 'getConstants', + 'constants' => 'getReflectionConstants', ]); foreach ($c->getProperties() as $n) { @@ -158,6 +181,8 @@ class ReflectionCaster $a[$prefix.'methods'][$n->name] = $n; } + self::addAttributes($a, $c, $prefix); + if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) { self::addExtra($a, $c); } @@ -165,7 +190,7 @@ class ReflectionCaster return $a; } - public static function castFunctionAbstract(\ReflectionFunctionAbstract $c, array $a, Stub $stub, $isNested, $filter = 0) + public static function castFunctionAbstract(\ReflectionFunctionAbstract $c, array $a, Stub $stub, bool $isNested, int $filter = 0) { $prefix = Caster::PREFIX_VIRTUAL; @@ -179,7 +204,7 @@ class ReflectionCaster if (isset($a[$prefix.'returnType'])) { $v = $a[$prefix.'returnType']; $v = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; - $a[$prefix.'returnType'] = new ClassStub($a[$prefix.'returnType']->allowsNull() ? '?'.$v : $v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']); + $a[$prefix.'returnType'] = new ClassStub($a[$prefix.'returnType'] instanceof \ReflectionNamedType && $a[$prefix.'returnType']->allowsNull() && 'mixed' !== $v ? '?'.$v : $v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']); } if (isset($a[$prefix.'class'])) { $a[$prefix.'class'] = new ClassStub($a[$prefix.'class']); @@ -190,7 +215,7 @@ class ReflectionCaster foreach ($c->getParameters() as $v) { $k = '$'.$v->name; - if (method_exists($v, 'isVariadic') && $v->isVariadic()) { + if ($v->isVariadic()) { $k = '...'.$k; } if ($v->isPassedByReference()) { @@ -202,7 +227,9 @@ class ReflectionCaster $a[$prefix.'parameters'] = new EnumStub($a[$prefix.'parameters']); } - if ($v = $c->getStaticVariables()) { + self::addAttributes($a, $c, $prefix); + + if (!($filter & Caster::EXCLUDE_VERBOSE) && $v = $c->getStaticVariables()) { foreach ($v as $k => &$v) { if (\is_object($v)) { $a[$prefix.'use']['$'.$k] = new CutStub($v); @@ -218,26 +245,30 @@ class ReflectionCaster self::addExtra($a, $c); } - // Added by HHVM - unset($a[Caster::PREFIX_DYNAMIC.'static']); + return $a; + } + + public static function castClassConstant(\ReflectionClassConstant $c, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); + $a[Caster::PREFIX_VIRTUAL.'value'] = $c->getValue(); + + self::addAttributes($a, $c); return $a; } - public static function castMethod(\ReflectionMethod $c, array $a, Stub $stub, $isNested) + public static function castMethod(\ReflectionMethod $c, array $a, Stub $stub, bool $isNested) { $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); return $a; } - public static function castParameter(\ReflectionParameter $c, array $a, Stub $stub, $isNested) + public static function castParameter(\ReflectionParameter $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; - // Added by HHVM - unset($a['info']); - self::addMap($a, $c, [ 'position' => 'getPosition', 'isVariadic' => 'isVariadic', @@ -245,12 +276,10 @@ class ReflectionCaster 'allowsNull' => 'allowsNull', ]); - if (method_exists($c, 'getType')) { - if ($v = $c->getType()) { - $a[$prefix.'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; - } - } elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $c, $v)) { - $a[$prefix.'typeHint'] = $v[1]; + self::addAttributes($a, $c, $prefix); + + if ($v = $c->getType()) { + $a[$prefix.'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; } if (isset($a[$prefix.'typeHint'])) { @@ -260,33 +289,40 @@ class ReflectionCaster unset($a[$prefix.'allowsNull']); } - try { - $a[$prefix.'default'] = $v = $c->getDefaultValue(); - if (method_exists($c, 'isDefaultValueConstant') && $c->isDefaultValueConstant()) { - $a[$prefix.'default'] = new ConstStub($c->getDefaultValueConstantName(), $v); - } - if (null === $v) { - unset($a[$prefix.'allowsNull']); - } - } catch (\ReflectionException $e) { - if (isset($a[$prefix.'typeHint']) && $c->allowsNull() && !class_exists('ReflectionNamedType', false)) { - $a[$prefix.'default'] = null; - unset($a[$prefix.'allowsNull']); + if ($c->isOptional()) { + try { + $a[$prefix.'default'] = $v = $c->getDefaultValue(); + if ($c->isDefaultValueConstant()) { + $a[$prefix.'default'] = new ConstStub($c->getDefaultValueConstantName(), $v); + } + if (null === $v) { + unset($a[$prefix.'allowsNull']); + } + } catch (\ReflectionException $e) { } } return $a; } - public static function castProperty(\ReflectionProperty $c, array $a, Stub $stub, $isNested) + public static function castProperty(\ReflectionProperty $c, array $a, Stub $stub, bool $isNested) { $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); + + self::addAttributes($a, $c); self::addExtra($a, $c); return $a; } - public static function castExtension(\ReflectionExtension $c, array $a, Stub $stub, $isNested) + public static function castReference(\ReflectionReference $c, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'id'] = $c->getId(); + + return $a; + } + + public static function castExtension(\ReflectionExtension $c, array $a, Stub $stub, bool $isNested) { self::addMap($a, $c, [ 'version' => 'getVersion', @@ -302,7 +338,7 @@ class ReflectionCaster return $a; } - public static function castZendExtension(\ReflectionZendExtension $c, array $a, Stub $stub, $isNested) + public static function castZendExtension(\ReflectionZendExtension $c, array $a, Stub $stub, bool $isNested) { self::addMap($a, $c, [ 'version' => 'getVersion', @@ -314,7 +350,59 @@ class ReflectionCaster return $a; } - private static function addExtra(&$a, \Reflector $c) + public static function getSignature(array $a) + { + $prefix = Caster::PREFIX_VIRTUAL; + $signature = ''; + + if (isset($a[$prefix.'parameters'])) { + foreach ($a[$prefix.'parameters']->value as $k => $param) { + $signature .= ', '; + if ($type = $param->getType()) { + if (!$type instanceof \ReflectionNamedType) { + $signature .= $type.' '; + } else { + if (!$param->isOptional() && $param->allowsNull() && 'mixed' !== $type->getName()) { + $signature .= '?'; + } + $signature .= substr(strrchr('\\'.$type->getName(), '\\'), 1).' '; + } + } + $signature .= $k; + + if (!$param->isDefaultValueAvailable()) { + continue; + } + $v = $param->getDefaultValue(); + $signature .= ' = '; + + if ($param->isDefaultValueConstant()) { + $signature .= substr(strrchr('\\'.$param->getDefaultValueConstantName(), '\\'), 1); + } elseif (null === $v) { + $signature .= 'null'; + } elseif (\is_array($v)) { + $signature .= $v ? '[…'.\count($v).']' : '[]'; + } elseif (\is_string($v)) { + $signature .= 10 > \strlen($v) && !str_contains($v, '\\') ? "'{$v}'" : "'…".\strlen($v)."'"; + } elseif (\is_bool($v)) { + $signature .= $v ? 'true' : 'false'; + } elseif (\is_object($v)) { + $signature .= 'new '.substr(strrchr('\\'.get_debug_type($v), '\\'), 1); + } else { + $signature .= $v; + } + } + } + $signature = (empty($a[$prefix.'returnsReference']) ? '' : '&').'('.substr($signature, 2).')'; + + if (isset($a[$prefix.'returnType'])) { + $signature .= ': '.substr(strrchr('\\'.$a[$prefix.'returnType'], '\\'), 1); + } + + return $signature; + } + + private static function addExtra(array &$a, \Reflector $c) { $x = isset($a[Caster::PREFIX_VIRTUAL.'extra']) ? $a[Caster::PREFIX_VIRTUAL.'extra']->value : []; @@ -323,14 +411,14 @@ class ReflectionCaster $x['line'] = $c->getStartLine().' to '.$c->getEndLine(); } - self::addMap($x, $c, self::$extraMap, ''); + self::addMap($x, $c, self::EXTRA_MAP, ''); if ($x) { $a[Caster::PREFIX_VIRTUAL.'extra'] = new EnumStub($x); } } - private static function addMap(&$a, \Reflector $c, $map, $prefix = Caster::PREFIX_VIRTUAL) + private static function addMap(array &$a, object $c, array $map, string $prefix = Caster::PREFIX_VIRTUAL) { foreach ($map as $k => $m) { if (\PHP_VERSION_ID >= 80000 && 'isDisabled' === $k) { @@ -342,4 +430,13 @@ class ReflectionCaster } } } + + private static function addAttributes(array &$a, \Reflector $c, string $prefix = Caster::PREFIX_VIRTUAL): void + { + if (\PHP_VERSION_ID >= 80000) { + foreach ($c->getAttributes() as $n) { + $a[$prefix.'attributes'][] = $n; + } + } + } } diff --git a/lib/symfony/var-dumper/Caster/ResourceCaster.php b/lib/symfony/var-dumper/Caster/ResourceCaster.php index eb11aee1f0..2c34ca9171 100644 --- a/lib/symfony/var-dumper/Caster/ResourceCaster.php +++ b/lib/symfony/var-dumper/Caster/ResourceCaster.php @@ -17,20 +17,20 @@ use Symfony\Component\VarDumper\Cloner\Stub; * Casts common resource types to array representation. * * @author Nicolas Grekas + * + * @final */ class ResourceCaster { /** * @param \CurlHandle|resource $h - * - * @return array */ - public static function castCurl($h, array $a, Stub $stub, $isNested) + public static function castCurl($h, array $a, Stub $stub, bool $isNested): array { return curl_getinfo($h); } - public static function castDba($dba, array $a, Stub $stub, $isNested) + public static function castDba($dba, array $a, Stub $stub, bool $isNested) { $list = dba_list(); $a['file'] = $list[(int) $dba]; @@ -38,27 +38,27 @@ class ResourceCaster return $a; } - public static function castProcess($process, array $a, Stub $stub, $isNested) + public static function castProcess($process, array $a, Stub $stub, bool $isNested) { return proc_get_status($process); } - public static function castStream($stream, array $a, Stub $stub, $isNested) + public static function castStream($stream, array $a, Stub $stub, bool $isNested) { $a = stream_get_meta_data($stream) + static::castStreamContext($stream, $a, $stub, $isNested); - if (isset($a['uri'])) { + if ($a['uri'] ?? false) { $a['uri'] = new LinkStub($a['uri']); } return $a; } - public static function castStreamContext($stream, array $a, Stub $stub, $isNested) + public static function castStreamContext($stream, array $a, Stub $stub, bool $isNested) { return @stream_context_get_params($stream) ?: $a; } - public static function castGd($gd, array $a, Stub $stub, $isNested) + public static function castGd($gd, array $a, Stub $stub, bool $isNested) { $a['size'] = imagesx($gd).'x'.imagesy($gd); $a['trueColor'] = imageistruecolor($gd); @@ -66,7 +66,7 @@ class ResourceCaster return $a; } - public static function castMysqlLink($h, array $a, Stub $stub, $isNested) + public static function castMysqlLink($h, array $a, Stub $stub, bool $isNested) { $a['host'] = mysql_get_host_info($h); $a['protocol'] = mysql_get_proto_info($h); @@ -74,4 +74,30 @@ class ResourceCaster return $a; } + + public static function castOpensslX509($h, array $a, Stub $stub, bool $isNested) + { + $stub->cut = -1; + $info = openssl_x509_parse($h, false); + + $pin = openssl_pkey_get_public($h); + $pin = openssl_pkey_get_details($pin)['key']; + $pin = \array_slice(explode("\n", $pin), 1, -2); + $pin = base64_decode(implode('', $pin)); + $pin = base64_encode(hash('sha256', $pin, true)); + + $a += [ + 'subject' => new EnumStub(array_intersect_key($info['subject'], ['organizationName' => true, 'commonName' => true])), + 'issuer' => new EnumStub(array_intersect_key($info['issuer'], ['organizationName' => true, 'commonName' => true])), + 'expiry' => new ConstStub(date(\DateTime::ISO8601, $info['validTo_time_t']), $info['validTo_time_t']), + 'fingerprint' => new EnumStub([ + 'md5' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'md5')), 2, ':', true)), + 'sha1' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha1')), 2, ':', true)), + 'sha256' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha256')), 2, ':', true)), + 'pin-sha256' => new ConstStub($pin), + ]), + ]; + + return $a; + } } diff --git a/lib/symfony/var-dumper/Caster/SplCaster.php b/lib/symfony/var-dumper/Caster/SplCaster.php index 360a1a416e..07f445116f 100644 --- a/lib/symfony/var-dumper/Caster/SplCaster.php +++ b/lib/symfony/var-dumper/Caster/SplCaster.php @@ -17,27 +17,29 @@ use Symfony\Component\VarDumper\Cloner\Stub; * Casts SPL related classes to array representation. * * @author Nicolas Grekas + * + * @final */ class SplCaster { - private static $splFileObjectFlags = [ + private const SPL_FILE_OBJECT_FLAGS = [ \SplFileObject::DROP_NEW_LINE => 'DROP_NEW_LINE', \SplFileObject::READ_AHEAD => 'READ_AHEAD', \SplFileObject::SKIP_EMPTY => 'SKIP_EMPTY', \SplFileObject::READ_CSV => 'READ_CSV', ]; - public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, $isNested) + public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, bool $isNested) { return self::castSplArray($c, $a, $stub, $isNested); } - public static function castArrayIterator(\ArrayIterator $c, array $a, Stub $stub, $isNested) + public static function castArrayIterator(\ArrayIterator $c, array $a, Stub $stub, bool $isNested) { return self::castSplArray($c, $a, $stub, $isNested); } - public static function castHeap(\Iterator $c, array $a, Stub $stub, $isNested) + public static function castHeap(\Iterator $c, array $a, Stub $stub, bool $isNested) { $a += [ Caster::PREFIX_VIRTUAL.'heap' => iterator_to_array(clone $c), @@ -46,7 +48,7 @@ class SplCaster return $a; } - public static function castDoublyLinkedList(\SplDoublyLinkedList $c, array $a, Stub $stub, $isNested) + public static function castDoublyLinkedList(\SplDoublyLinkedList $c, array $a, Stub $stub, bool $isNested) { $prefix = Caster::PREFIX_VIRTUAL; $mode = $c->getIteratorMode(); @@ -61,7 +63,7 @@ class SplCaster return $a; } - public static function castFileInfo(\SplFileInfo $c, array $a, Stub $stub, $isNested) + public static function castFileInfo(\SplFileInfo $c, array $a, Stub $stub, bool $isNested) { static $map = [ 'path' => 'getPath', @@ -127,7 +129,7 @@ class SplCaster } } - if (isset($a[$prefix.'realPath'])) { + if ($a[$prefix.'realPath'] ?? false) { $a[$prefix.'realPath'] = new LinkStub($a[$prefix.'realPath']); } @@ -145,7 +147,7 @@ class SplCaster return $a; } - public static function castFileObject(\SplFileObject $c, array $a, Stub $stub, $isNested) + public static function castFileObject(\SplFileObject $c, array $a, Stub $stub, bool $isNested) { static $map = [ 'csvControl' => 'getCsvControl', @@ -167,7 +169,7 @@ class SplCaster if (isset($a[$prefix.'flags'])) { $flagsArray = []; - foreach (self::$splFileObjectFlags as $value => $name) { + foreach (self::SPL_FILE_OBJECT_FLAGS as $value => $name) { if ($a[$prefix.'flags'] & $value) { $flagsArray[] = $name; } @@ -182,7 +184,7 @@ class SplCaster return $a; } - public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $stub, $isNested) + public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $stub, bool $isNested) { $storage = []; unset($a[Caster::PREFIX_DYNAMIC."\0gcdata"]); // Don't hit https://bugs.php.net/65967 @@ -203,14 +205,21 @@ class SplCaster return $a; } - public static function castOuterIterator(\OuterIterator $c, array $a, Stub $stub, $isNested) + public static function castOuterIterator(\OuterIterator $c, array $a, Stub $stub, bool $isNested) { $a[Caster::PREFIX_VIRTUAL.'innerIterator'] = $c->getInnerIterator(); return $a; } - private static function castSplArray($c, array $a, Stub $stub, $isNested) + public static function castWeakReference(\WeakReference $c, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'object'] = $c->get(); + + return $a; + } + + private static function castSplArray($c, array $a, Stub $stub, bool $isNested): array { $prefix = Caster::PREFIX_VIRTUAL; $flags = $c->getFlags(); diff --git a/lib/symfony/var-dumper/Caster/StubCaster.php b/lib/symfony/var-dumper/Caster/StubCaster.php index 9927d42610..32ead7c277 100644 --- a/lib/symfony/var-dumper/Caster/StubCaster.php +++ b/lib/symfony/var-dumper/Caster/StubCaster.php @@ -17,10 +17,12 @@ use Symfony\Component\VarDumper\Cloner\Stub; * Casts a caster's Stub. * * @author Nicolas Grekas + * + * @final */ class StubCaster { - public static function castStub(Stub $c, array $a, Stub $stub, $isNested) + public static function castStub(Stub $c, array $a, Stub $stub, bool $isNested) { if ($isNested) { $stub->type = $c->type; @@ -41,12 +43,12 @@ class StubCaster return $a; } - public static function castCutArray(CutArrayStub $c, array $a, Stub $stub, $isNested) + public static function castCutArray(CutArrayStub $c, array $a, Stub $stub, bool $isNested) { return $isNested ? $c->preservedSubset : $a; } - public static function cutInternals($obj, array $a, Stub $stub, $isNested) + public static function cutInternals($obj, array $a, Stub $stub, bool $isNested) { if ($isNested) { $stub->cut += \count($a); @@ -57,7 +59,7 @@ class StubCaster return $a; } - public static function castEnum(EnumStub $c, array $a, Stub $stub, $isNested) + public static function castEnum(EnumStub $c, array $a, Stub $stub, bool $isNested) { if ($isNested) { $stub->class = $c->dumpKeys ? '' : null; diff --git a/lib/symfony/var-dumper/Caster/SymfonyCaster.php b/lib/symfony/var-dumper/Caster/SymfonyCaster.php index ae7134f55b..08428b9274 100644 --- a/lib/symfony/var-dumper/Caster/SymfonyCaster.php +++ b/lib/symfony/var-dumper/Caster/SymfonyCaster.php @@ -12,11 +12,16 @@ namespace Symfony\Component\VarDumper\Caster; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Uid\Ulid; +use Symfony\Component\Uid\Uuid; use Symfony\Component\VarDumper\Cloner\Stub; +/** + * @final + */ class SymfonyCaster { - private static $requestGetters = [ + private const REQUEST_GETTERS = [ 'pathInfo' => 'getPathInfo', 'requestUri' => 'getRequestUri', 'baseUrl' => 'getBaseUrl', @@ -25,12 +30,13 @@ class SymfonyCaster 'format' => 'getRequestFormat', ]; - public static function castRequest(Request $request, array $a, Stub $stub, $isNested) + public static function castRequest(Request $request, array $a, Stub $stub, bool $isNested) { $clone = null; - foreach (self::$requestGetters as $prop => $getter) { - if (null === $a[Caster::PREFIX_PROTECTED.$prop]) { + foreach (self::REQUEST_GETTERS as $prop => $getter) { + $key = Caster::PREFIX_PROTECTED.$prop; + if (\array_key_exists($key, $a) && null === $a[$key]) { if (null === $clone) { $clone = clone $request; } @@ -40,4 +46,52 @@ class SymfonyCaster return $a; } + + public static function castHttpClient($client, array $a, Stub $stub, bool $isNested) + { + $multiKey = sprintf("\0%s\0multi", \get_class($client)); + if (isset($a[$multiKey])) { + $a[$multiKey] = new CutStub($a[$multiKey]); + } + + return $a; + } + + public static function castHttpClientResponse($response, array $a, Stub $stub, bool $isNested) + { + $stub->cut += \count($a); + $a = []; + + foreach ($response->getInfo() as $k => $v) { + $a[Caster::PREFIX_VIRTUAL.$k] = $v; + } + + return $a; + } + + public static function castUuid(Uuid $uuid, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'toBase58'] = $uuid->toBase58(); + $a[Caster::PREFIX_VIRTUAL.'toBase32'] = $uuid->toBase32(); + + // symfony/uid >= 5.3 + if (method_exists($uuid, 'getDateTime')) { + $a[Caster::PREFIX_VIRTUAL.'time'] = $uuid->getDateTime()->format('Y-m-d H:i:s.u \U\T\C'); + } + + return $a; + } + + public static function castUlid(Ulid $ulid, array $a, Stub $stub, bool $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'toBase58'] = $ulid->toBase58(); + $a[Caster::PREFIX_VIRTUAL.'toRfc4122'] = $ulid->toRfc4122(); + + // symfony/uid >= 5.3 + if (method_exists($ulid, 'getDateTime')) { + $a[Caster::PREFIX_VIRTUAL.'time'] = $ulid->getDateTime()->format('Y-m-d H:i:s.v \U\T\C'); + } + + return $a; + } } diff --git a/lib/symfony/var-dumper/Caster/TraceStub.php b/lib/symfony/var-dumper/Caster/TraceStub.php index 59548acaee..5eea1c8766 100644 --- a/lib/symfony/var-dumper/Caster/TraceStub.php +++ b/lib/symfony/var-dumper/Caster/TraceStub.php @@ -25,7 +25,7 @@ class TraceStub extends Stub public $sliceLength; public $numberingOffset; - public function __construct(array $trace, $keepArgs = true, $sliceOffset = 0, $sliceLength = null, $numberingOffset = 0) + public function __construct(array $trace, bool $keepArgs = true, int $sliceOffset = 0, int $sliceLength = null, int $numberingOffset = 0) { $this->value = $trace; $this->keepArgs = $keepArgs; diff --git a/lib/symfony/var-dumper/Caster/UuidCaster.php b/lib/symfony/var-dumper/Caster/UuidCaster.php new file mode 100644 index 0000000000..b102774571 --- /dev/null +++ b/lib/symfony/var-dumper/Caster/UuidCaster.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Ramsey\Uuid\UuidInterface; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Grégoire Pineau + */ +final class UuidCaster +{ + public static function castRamseyUuid(UuidInterface $c, array $a, Stub $stub, bool $isNested): array + { + $a += [ + Caster::PREFIX_VIRTUAL.'uuid' => (string) $c, + ]; + + return $a; + } +} diff --git a/lib/symfony/var-dumper/Caster/XmlReaderCaster.php b/lib/symfony/var-dumper/Caster/XmlReaderCaster.php index 3ae9ec0ba1..5b455651bd 100644 --- a/lib/symfony/var-dumper/Caster/XmlReaderCaster.php +++ b/lib/symfony/var-dumper/Caster/XmlReaderCaster.php @@ -1,4 +1,5 @@ + * + * @final */ class XmlReaderCaster { - private static $nodeTypes = [ + private const NODE_TYPES = [ \XMLReader::NONE => 'NONE', \XMLReader::ELEMENT => 'ELEMENT', \XMLReader::ATTRIBUTE => 'ATTRIBUTE', @@ -40,13 +43,29 @@ class XmlReaderCaster \XMLReader::XML_DECLARATION => 'XML_DECLARATION', ]; - public static function castXmlReader(\XMLReader $reader, array $a, Stub $stub, $isNested) + public static function castXmlReader(\XMLReader $reader, array $a, Stub $stub, bool $isNested) { + try { + $properties = [ + 'LOADDTD' => @$reader->getParserProperty(\XMLReader::LOADDTD), + 'DEFAULTATTRS' => @$reader->getParserProperty(\XMLReader::DEFAULTATTRS), + 'VALIDATE' => @$reader->getParserProperty(\XMLReader::VALIDATE), + 'SUBST_ENTITIES' => @$reader->getParserProperty(\XMLReader::SUBST_ENTITIES), + ]; + } catch (\Error $e) { + $properties = [ + 'LOADDTD' => false, + 'DEFAULTATTRS' => false, + 'VALIDATE' => false, + 'SUBST_ENTITIES' => false, + ]; + } + $props = Caster::PREFIX_VIRTUAL.'parserProperties'; $info = [ 'localName' => $reader->localName, 'prefix' => $reader->prefix, - 'nodeType' => new ConstStub(self::$nodeTypes[$reader->nodeType], $reader->nodeType), + 'nodeType' => new ConstStub(self::NODE_TYPES[$reader->nodeType], $reader->nodeType), 'depth' => $reader->depth, 'isDefault' => $reader->isDefault, 'isEmptyElement' => \XMLReader::NONE === $reader->nodeType ? null : $reader->isEmptyElement, @@ -55,12 +74,7 @@ class XmlReaderCaster 'value' => $reader->value, 'namespaceURI' => $reader->namespaceURI, 'baseURI' => $reader->baseURI ? new LinkStub($reader->baseURI) : $reader->baseURI, - $props => [ - 'LOADDTD' => $reader->getParserProperty(\XMLReader::LOADDTD), - 'DEFAULTATTRS' => $reader->getParserProperty(\XMLReader::DEFAULTATTRS), - 'VALIDATE' => $reader->getParserProperty(\XMLReader::VALIDATE), - 'SUBST_ENTITIES' => $reader->getParserProperty(\XMLReader::SUBST_ENTITIES), - ], + $props => $properties, ]; if ($info[$props] = Caster::filter($info[$props], Caster::EXCLUDE_EMPTY, [], $count)) { diff --git a/lib/symfony/var-dumper/Caster/XmlResourceCaster.php b/lib/symfony/var-dumper/Caster/XmlResourceCaster.php index 99c1486483..ba55fcedd9 100644 --- a/lib/symfony/var-dumper/Caster/XmlResourceCaster.php +++ b/lib/symfony/var-dumper/Caster/XmlResourceCaster.php @@ -17,10 +17,12 @@ use Symfony\Component\VarDumper\Cloner\Stub; * Casts XML resources to array representation. * * @author Nicolas Grekas + * + * @final */ class XmlResourceCaster { - private static $xmlErrors = [ + private const XML_ERRORS = [ \XML_ERROR_NONE => 'XML_ERROR_NONE', \XML_ERROR_NO_MEMORY => 'XML_ERROR_NO_MEMORY', \XML_ERROR_SYNTAX => 'XML_ERROR_SYNTAX', @@ -45,15 +47,15 @@ class XmlResourceCaster \XML_ERROR_EXTERNAL_ENTITY_HANDLING => 'XML_ERROR_EXTERNAL_ENTITY_HANDLING', ]; - public static function castXml($h, array $a, Stub $stub, $isNested) + public static function castXml($h, array $a, Stub $stub, bool $isNested) { $a['current_byte_index'] = xml_get_current_byte_index($h); $a['current_column_number'] = xml_get_current_column_number($h); $a['current_line_number'] = xml_get_current_line_number($h); $a['error_code'] = xml_get_error_code($h); - if (isset(self::$xmlErrors[$a['error_code']])) { - $a['error_code'] = new ConstStub(self::$xmlErrors[$a['error_code']], $a['error_code']); + if (isset(self::XML_ERRORS[$a['error_code']])) { + $a['error_code'] = new ConstStub(self::XML_ERRORS[$a['error_code']], $a['error_code']); } return $a; diff --git a/lib/symfony/var-dumper/Cloner/AbstractCloner.php b/lib/symfony/var-dumper/Cloner/AbstractCloner.php index 76b55b478b..f74a61d7a6 100644 --- a/lib/symfony/var-dumper/Cloner/AbstractCloner.php +++ b/lib/symfony/var-dumper/Cloner/AbstractCloner.php @@ -29,15 +29,20 @@ abstract class AbstractCloner implements ClonerInterface 'Symfony\Component\VarDumper\Caster\ConstStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], 'Symfony\Component\VarDumper\Caster\EnumStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castEnum'], + 'Fiber' => ['Symfony\Component\VarDumper\Caster\FiberCaster', 'castFiber'], + 'Closure' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClosure'], 'Generator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castGenerator'], 'ReflectionType' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castType'], + 'ReflectionAttribute' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castAttribute'], 'ReflectionGenerator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReflectionGenerator'], 'ReflectionClass' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClass'], + 'ReflectionClassConstant' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClassConstant'], 'ReflectionFunctionAbstract' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castFunctionAbstract'], 'ReflectionMethod' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castMethod'], 'ReflectionParameter' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castParameter'], 'ReflectionProperty' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castProperty'], + 'ReflectionReference' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReference'], 'ReflectionExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castExtension'], 'ReflectionZendExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castZendExtension'], @@ -70,18 +75,34 @@ abstract class AbstractCloner implements ClonerInterface 'DOMProcessingInstruction' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castProcessingInstruction'], 'DOMXPath' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castXPath'], - 'XmlReader' => ['Symfony\Component\VarDumper\Caster\XmlReaderCaster', 'castXmlReader'], + 'XMLReader' => ['Symfony\Component\VarDumper\Caster\XmlReaderCaster', 'castXmlReader'], 'ErrorException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castErrorException'], 'Exception' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castException'], 'Error' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castError'], + 'Symfony\Bridge\Monolog\Logger' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], 'Symfony\Component\DependencyInjection\ContainerInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\EventDispatcher\EventDispatcherInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\HttpClient\AmpHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], + 'Symfony\Component\HttpClient\CurlHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], + 'Symfony\Component\HttpClient\NativeHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], + 'Symfony\Component\HttpClient\Response\AmpResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], + 'Symfony\Component\HttpClient\Response\CurlResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], + 'Symfony\Component\HttpClient\Response\NativeResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], 'Symfony\Component\HttpFoundation\Request' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castRequest'], + 'Symfony\Component\Uid\Ulid' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castUlid'], + 'Symfony\Component\Uid\Uuid' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castUuid'], 'Symfony\Component\VarDumper\Exception\ThrowingCasterException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castThrowingCasterException'], 'Symfony\Component\VarDumper\Caster\TraceStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castTraceStub'], 'Symfony\Component\VarDumper\Caster\FrameStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castFrameStub'], - 'Symfony\Component\Debug\Exception\SilencedErrorContext' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castSilencedErrorContext'], + 'Symfony\Component\VarDumper\Cloner\AbstractCloner' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\ErrorHandler\Exception\SilencedErrorContext' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castSilencedErrorContext'], + 'Imagine\Image\ImageInterface' => ['Symfony\Component\VarDumper\Caster\ImagineCaster', 'castImage'], + + 'Ramsey\Uuid\UuidInterface' => ['Symfony\Component\VarDumper\Caster\UuidCaster', 'castRamseyUuid'], + + 'ProxyManager\Proxy\ProxyInterface' => ['Symfony\Component\VarDumper\Caster\ProxyManagerCaster', 'castProxy'], 'PHPUnit_Framework_MockObject_MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], 'PHPUnit\Framework\MockObject\MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], 'PHPUnit\Framework\MockObject\Stub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], @@ -106,23 +127,43 @@ abstract class AbstractCloner implements ClonerInterface 'SplObjectStorage' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castObjectStorage'], 'SplPriorityQueue' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'], 'OuterIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castOuterIterator'], - - 'MongoCursorInterface' => ['Symfony\Component\VarDumper\Caster\MongoCaster', 'castCursor'], + 'WeakReference' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castWeakReference'], 'Redis' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'], 'RedisArray' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisArray'], + 'RedisCluster' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisCluster'], 'DateTimeInterface' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castDateTime'], 'DateInterval' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castInterval'], 'DateTimeZone' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castTimeZone'], 'DatePeriod' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castPeriod'], + 'GMP' => ['Symfony\Component\VarDumper\Caster\GmpCaster', 'castGmp'], + + 'MessageFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castMessageFormatter'], + 'NumberFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castNumberFormatter'], + 'IntlTimeZone' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlTimeZone'], + 'IntlCalendar' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlCalendar'], + 'IntlDateFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlDateFormatter'], + + 'Memcached' => ['Symfony\Component\VarDumper\Caster\MemcachedCaster', 'castMemcached'], + + 'Ds\Collection' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castCollection'], + 'Ds\Map' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castMap'], + 'Ds\Pair' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPair'], + 'Symfony\Component\VarDumper\Caster\DsPairStub' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPairStub'], + + 'mysqli_driver' => ['Symfony\Component\VarDumper\Caster\MysqliCaster', 'castMysqliDriver'], + 'CurlHandle' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'], ':curl' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'], ':dba' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], ':dba persistent' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], + + 'GdImage' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], ':gd' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], + ':mysql link' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castMysqlLink'], ':pgsql large object' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLargeObject'], ':pgsql link' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], @@ -130,18 +171,43 @@ abstract class AbstractCloner implements ClonerInterface ':pgsql result' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castResult'], ':process' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castProcess'], ':stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], + + 'OpenSSLCertificate' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], + ':OpenSSL X.509' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], + ':persistent stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], ':stream-context' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStreamContext'], + + 'XmlParser' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], ':xml' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], + + 'RdKafka' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castRdKafka'], + 'RdKafka\Conf' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castConf'], + 'RdKafka\KafkaConsumer' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castKafkaConsumer'], + 'RdKafka\Metadata\Broker' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castBrokerMetadata'], + 'RdKafka\Metadata\Collection' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castCollectionMetadata'], + 'RdKafka\Metadata\Partition' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castPartitionMetadata'], + 'RdKafka\Metadata\Topic' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicMetadata'], + 'RdKafka\Message' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castMessage'], + 'RdKafka\Topic' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopic'], + 'RdKafka\TopicPartition' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicPartition'], + 'RdKafka\TopicConf' => ['Symfony\Component\VarDumper\Caster\RdKafkaCaster', 'castTopicConf'], ]; protected $maxItems = 2500; protected $maxString = -1; protected $minDepth = 1; - protected $useExt; + /** + * @var array> + */ private $casters = []; + + /** + * @var callable|null + */ private $prevErrorHandler; + private $classInfo = []; private $filter = 0; @@ -156,7 +222,6 @@ abstract class AbstractCloner implements ClonerInterface $casters = static::$defaultCasters; } $this->addCasters($casters); - $this->useExt = \extension_loaded('symfony_debug'); } /** @@ -172,39 +237,33 @@ abstract class AbstractCloner implements ClonerInterface public function addCasters(array $casters) { foreach ($casters as $type => $callback) { - $this->casters[strtolower($type)][] = \is_string($callback) && false !== strpos($callback, '::') ? explode('::', $callback, 2) : $callback; + $this->casters[$type][] = $callback; } } /** * Sets the maximum number of items to clone past the minimum depth in nested structures. - * - * @param int $maxItems */ - public function setMaxItems($maxItems) + public function setMaxItems(int $maxItems) { - $this->maxItems = (int) $maxItems; + $this->maxItems = $maxItems; } /** * Sets the maximum cloned length for strings. - * - * @param int $maxString */ - public function setMaxString($maxString) + public function setMaxString(int $maxString) { - $this->maxString = (int) $maxString; + $this->maxString = $maxString; } /** * Sets the minimum tree depth where we are guaranteed to clone all the items. After this * depth is reached, only setMaxItems items will be cloned. - * - * @param int $minDepth */ - public function setMinDepth($minDepth) + public function setMinDepth(int $minDepth) { - $this->minDepth = (int) $minDepth; + $this->minDepth = $minDepth; } /** @@ -213,9 +272,9 @@ abstract class AbstractCloner implements ClonerInterface * @param mixed $var Any PHP variable * @param int $filter A bit field of Caster::EXCLUDE_* constants * - * @return Data The cloned variable represented by a Data object + * @return Data */ - public function cloneVar($var, $filter = 0) + public function cloneVar($var, int $filter = 0) { $this->prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) { if (\E_RECOVERABLE_ERROR === $type || \E_USER_ERROR === $type) { @@ -224,7 +283,7 @@ abstract class AbstractCloner implements ClonerInterface } if ($this->prevErrorHandler) { - return \call_user_func($this->prevErrorHandler, $type, $msg, $file, $line, $context); + return ($this->prevErrorHandler)($type, $msg, $file, $line, $context); } return false; @@ -250,46 +309,52 @@ abstract class AbstractCloner implements ClonerInterface * * @param mixed $var Any PHP variable * - * @return array The cloned variable represented in an array + * @return array */ abstract protected function doClone($var); /** * Casts an object to an array representation. * - * @param Stub $stub The Stub for the casted object * @param bool $isNested True if the object is nested in the dumped structure * - * @return array The object casted as array + * @return array */ - protected function castObject(Stub $stub, $isNested) + protected function castObject(Stub $stub, bool $isNested) { $obj = $stub->value; $class = $stub->class; - if ((\PHP_VERSION_ID >= 80000 || (isset($class[15]) && "\0" === $class[15])) && false !== strpos($class, "@anonymous\0")) { - $stub->class = \PHP_VERSION_ID < 80000 ? (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous' : get_debug_type($obj); + if (\PHP_VERSION_ID < 80000 ? "\0" === ($class[15] ?? null) : str_contains($class, "@anonymous\0")) { + $stub->class = get_debug_type($obj); } if (isset($this->classInfo[$class])) { - list($i, $parents, $hasDebugInfo) = $this->classInfo[$class]; + [$i, $parents, $hasDebugInfo, $fileInfo] = $this->classInfo[$class]; } else { $i = 2; - $parents = [strtolower($class)]; + $parents = [$class]; $hasDebugInfo = method_exists($class, '__debugInfo'); foreach (class_parents($class) as $p) { - $parents[] = strtolower($p); + $parents[] = $p; ++$i; } foreach (class_implements($class) as $p) { - $parents[] = strtolower($p); + $parents[] = $p; ++$i; } $parents[] = '*'; - $this->classInfo[$class] = [$i, $parents, $hasDebugInfo]; + $r = new \ReflectionClass($class); + $fileInfo = $r->isInternal() || $r->isSubclassOf(Stub::class) ? [] : [ + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ]; + + $this->classInfo[$class] = [$i, $parents, $hasDebugInfo, $fileInfo]; } + $stub->attr += $fileInfo; $a = Caster::castObject($obj, $class, $hasDebugInfo, $stub->class); try { @@ -310,12 +375,11 @@ abstract class AbstractCloner implements ClonerInterface /** * Casts a resource to an array representation. * - * @param Stub $stub The Stub for the casted resource * @param bool $isNested True if the object is nested in the dumped structure * - * @return array The resource casted as array + * @return array */ - protected function castResource(Stub $stub, $isNested) + protected function castResource(Stub $stub, bool $isNested) { $a = []; $res = $stub->value; diff --git a/lib/symfony/var-dumper/Cloner/ClonerInterface.php b/lib/symfony/var-dumper/Cloner/ClonerInterface.php index 7ed287a2dd..90b1495324 100644 --- a/lib/symfony/var-dumper/Cloner/ClonerInterface.php +++ b/lib/symfony/var-dumper/Cloner/ClonerInterface.php @@ -21,7 +21,7 @@ interface ClonerInterface * * @param mixed $var Any PHP variable * - * @return Data The cloned variable represented by a Data object + * @return Data */ public function cloneVar($var); } diff --git a/lib/symfony/var-dumper/Cloner/Cursor.php b/lib/symfony/var-dumper/Cloner/Cursor.php index 5b0542f6c2..1fd796d675 100644 --- a/lib/symfony/var-dumper/Cloner/Cursor.php +++ b/lib/symfony/var-dumper/Cloner/Cursor.php @@ -18,10 +18,10 @@ namespace Symfony\Component\VarDumper\Cloner; */ class Cursor { - const HASH_INDEXED = Stub::ARRAY_INDEXED; - const HASH_ASSOC = Stub::ARRAY_ASSOC; - const HASH_OBJECT = Stub::TYPE_OBJECT; - const HASH_RESOURCE = Stub::TYPE_RESOURCE; + public const HASH_INDEXED = Stub::ARRAY_INDEXED; + public const HASH_ASSOC = Stub::ARRAY_ASSOC; + public const HASH_OBJECT = Stub::TYPE_OBJECT; + public const HASH_RESOURCE = Stub::TYPE_RESOURCE; public $depth = 0; public $refIndex = 0; diff --git a/lib/symfony/var-dumper/Cloner/Data.php b/lib/symfony/var-dumper/Cloner/Data.php index 3973720794..ea8f0f33ab 100644 --- a/lib/symfony/var-dumper/Cloner/Data.php +++ b/lib/symfony/var-dumper/Cloner/Data.php @@ -12,6 +12,7 @@ namespace Symfony\Component\VarDumper\Cloner; use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; /** * @author Nicolas Grekas @@ -24,6 +25,7 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate private $maxDepth = 20; private $maxItemsPerDepth = -1; private $useRefHandles = -1; + private $context = []; /** * @param array $data An array as returned by ClonerInterface::cloneVar() @@ -34,7 +36,7 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate } /** - * @return string|null The type of the value + * @return string|null */ public function getType() { @@ -63,9 +65,11 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate } /** + * Returns a native representation of the original value. + * * @param array|bool $recursive Whether values should be resolved recursively or not * - * @return string|int|float|bool|array|Data[]|null A native representation of the original value + * @return string|int|float|bool|array|Data[]|null */ public function getValue($recursive = false) { @@ -106,23 +110,29 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate return $children; } + /** + * @return int + */ + #[\ReturnTypeWillChange] public function count() { return \count($this->getValue()); } + /** + * @return \Traversable + */ + #[\ReturnTypeWillChange] public function getIterator() { if (!\is_array($value = $this->getValue())) { - throw new \LogicException(sprintf('"%s" object holds non-iterable type "%s".', self::class, \gettype($value))); + throw new \LogicException(sprintf('"%s" object holds non-iterable type "%s".', self::class, get_debug_type($value))); } - foreach ($value as $k => $v) { - yield $k => $v; - } + yield from $value; } - public function __get($key) + public function __get(string $key) { if (null !== $data = $this->seek($key)) { $item = $this->getStub($data->data[$data->position][$data->key]); @@ -133,31 +143,53 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate return null; } - public function __isset($key) + /** + * @return bool + */ + public function __isset(string $key) { return null !== $this->seek($key); } + /** + * @return bool + */ + #[\ReturnTypeWillChange] public function offsetExists($key) { return $this->__isset($key); } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function offsetGet($key) { return $this->__get($key); } + /** + * @return void + */ + #[\ReturnTypeWillChange] public function offsetSet($key, $value) { throw new \BadMethodCallException(self::class.' objects are immutable.'); } + /** + * @return void + */ + #[\ReturnTypeWillChange] public function offsetUnset($key) { throw new \BadMethodCallException(self::class.' objects are immutable.'); } + /** + * @return string + */ public function __toString() { $value = $this->getValue(); @@ -169,29 +201,15 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate return sprintf('%s (count=%d)', $this->getType(), \count($value)); } - /** - * @return array The raw data structure - * - * @deprecated since version 3.3. Use array or object access instead. - */ - public function getRawData() - { - @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the array or object access instead.', __METHOD__)); - - return $this->data; - } - /** * Returns a depth limited clone of $this. * - * @param int $maxDepth The max dumped depth level - * * @return static */ - public function withMaxDepth($maxDepth) + public function withMaxDepth(int $maxDepth) { $data = clone $this; - $data->maxDepth = (int) $maxDepth; + $data->maxDepth = $maxDepth; return $data; } @@ -199,14 +217,12 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate /** * Limits the number of elements per depth level. * - * @param int $maxItemsPerDepth The max number of items dumped per depth level - * * @return static */ - public function withMaxItemsPerDepth($maxItemsPerDepth) + public function withMaxItemsPerDepth(int $maxItemsPerDepth) { $data = clone $this; - $data->maxItemsPerDepth = (int) $maxItemsPerDepth; + $data->maxItemsPerDepth = $maxItemsPerDepth; return $data; } @@ -218,7 +234,7 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate * * @return static */ - public function withRefHandles($useRefHandles) + public function withRefHandles(bool $useRefHandles) { $data = clone $this; $data->useRefHandles = $useRefHandles ? -1 : 0; @@ -226,12 +242,23 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate return $data; } + /** + * @return static + */ + public function withContext(array $context) + { + $data = clone $this; + $data->context = $context; + + return $data; + } + /** * Seeks to a specific key in nested data structures. * * @param string|int $key The key to seek to * - * @return static|null Null if the key is not set + * @return static|null */ public function seek($key) { @@ -280,18 +307,26 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate public function dump(DumperInterface $dumper) { $refs = [0]; - $this->dumpItem($dumper, new Cursor(), $refs, $this->data[$this->position][$this->key]); + $cursor = new Cursor(); + + if ($cursor->attr = $this->context[SourceContextProvider::class] ?? []) { + $cursor->attr['if_links'] = true; + $cursor->hashType = -1; + $dumper->dumpScalar($cursor, 'default', '^'); + $cursor->attr = ['if_links' => true]; + $dumper->dumpScalar($cursor, 'default', ' '); + $cursor->hashType = 0; + } + + $this->dumpItem($dumper, $cursor, $refs, $this->data[$this->position][$this->key]); } /** * Depth-first dumping of items. * - * @param DumperInterface $dumper The dumper being used for dumping - * @param Cursor $cursor A cursor used for tracking dumper state position - * @param array &$refs A map of all references discovered while dumping - * @param mixed $item A Stub object or the original value being dumped + * @param mixed $item A Stub object or the original value being dumped */ - private function dumpItem($dumper, $cursor, &$refs, $item) + private function dumpItem(DumperInterface $dumper, Cursor $cursor, array &$refs, $item) { $cursor->refIndex = 0; $cursor->softRefTo = $cursor->softRefHandle = $cursor->softRefCount = 0; @@ -313,7 +348,7 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate } $cursor->hardRefTo = $refs[$r]; $cursor->hardRefHandle = $this->useRefHandles & $item->handle; - $cursor->hardRefCount = $item->refCount; + $cursor->hardRefCount = 0 < $item->handle ? $item->refCount : 0; } $cursor->attr = $item->attr; $type = $item->class ?: \gettype($item->value); @@ -389,17 +424,9 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate /** * Dumps children of hash structures. * - * @param DumperInterface $dumper - * @param Cursor $parentCursor The cursor of the parent hash - * @param array &$refs A map of all references discovered while dumping - * @param array $children The children to dump - * @param int $hashCut The number of items removed from the original hash - * @param string $hashType A Cursor::HASH_* const - * @param bool $dumpKeys Whether keys should be dumped or not - * * @return int The final number of removed items */ - private function dumpChildren($dumper, $parentCursor, &$refs, $children, $hashCut, $hashType, $dumpKeys) + private function dumpChildren(DumperInterface $dumper, Cursor $parentCursor, array &$refs, array $children, int $hashCut, int $hashType, bool $dumpKeys): int { $cursor = clone $parentCursor; ++$cursor->depth; diff --git a/lib/symfony/var-dumper/Cloner/DumperInterface.php b/lib/symfony/var-dumper/Cloner/DumperInterface.php index 912bb52139..6d60b723c7 100644 --- a/lib/symfony/var-dumper/Cloner/DumperInterface.php +++ b/lib/symfony/var-dumper/Cloner/DumperInterface.php @@ -21,40 +21,36 @@ interface DumperInterface /** * Dumps a scalar value. * - * @param Cursor $cursor The Cursor position in the dump - * @param string $type The PHP type of the value being dumped - * @param string|int|float|bool $value The scalar value being dumped + * @param string $type The PHP type of the value being dumped + * @param string|int|float|bool $value The scalar value being dumped */ - public function dumpScalar(Cursor $cursor, $type, $value); + public function dumpScalar(Cursor $cursor, string $type, $value); /** * Dumps a string. * - * @param Cursor $cursor The Cursor position in the dump - * @param string $str The string being dumped - * @param bool $bin Whether $str is UTF-8 or binary encoded - * @param int $cut The number of characters $str has been cut by + * @param string $str The string being dumped + * @param bool $bin Whether $str is UTF-8 or binary encoded + * @param int $cut The number of characters $str has been cut by */ - public function dumpString(Cursor $cursor, $str, $bin, $cut); + public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut); /** * Dumps while entering an hash. * - * @param Cursor $cursor The Cursor position in the dump * @param int $type A Cursor::HASH_* const for the type of hash * @param string|int $class The object class, resource type or array count * @param bool $hasChild When the dump of the hash has child item */ - public function enterHash(Cursor $cursor, $type, $class, $hasChild); + public function enterHash(Cursor $cursor, int $type, $class, bool $hasChild); /** * Dumps while leaving an hash. * - * @param Cursor $cursor The Cursor position in the dump * @param int $type A Cursor::HASH_* const for the type of hash * @param string|int $class The object class, resource type or array count * @param bool $hasChild When the dump of the hash has child item * @param int $cut The number of items the hash has been cut by */ - public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut); + public function leaveHash(Cursor $cursor, int $type, $class, bool $hasChild, int $cut); } diff --git a/lib/symfony/var-dumper/Cloner/Stub.php b/lib/symfony/var-dumper/Cloner/Stub.php index a56120ce36..073c56efbd 100644 --- a/lib/symfony/var-dumper/Cloner/Stub.php +++ b/lib/symfony/var-dumper/Cloner/Stub.php @@ -18,17 +18,17 @@ namespace Symfony\Component\VarDumper\Cloner; */ class Stub { - const TYPE_REF = 1; - const TYPE_STRING = 2; - const TYPE_ARRAY = 3; - const TYPE_OBJECT = 4; - const TYPE_RESOURCE = 5; + public const TYPE_REF = 1; + public const TYPE_STRING = 2; + public const TYPE_ARRAY = 3; + public const TYPE_OBJECT = 4; + public const TYPE_RESOURCE = 5; - const STRING_BINARY = 1; - const STRING_UTF8 = 2; + public const STRING_BINARY = 1; + public const STRING_UTF8 = 2; - const ARRAY_ASSOC = 1; - const ARRAY_INDEXED = 2; + public const ARRAY_ASSOC = 1; + public const ARRAY_INDEXED = 2; public $type = self::TYPE_REF; public $class = ''; @@ -44,7 +44,7 @@ class Stub /** * @internal */ - public function __sleep() + public function __sleep(): array { $properties = []; diff --git a/lib/symfony/var-dumper/Cloner/VarCloner.php b/lib/symfony/var-dumper/Cloner/VarCloner.php index 8c4221220e..80c4a2f839 100644 --- a/lib/symfony/var-dumper/Cloner/VarCloner.php +++ b/lib/symfony/var-dumper/Cloner/VarCloner.php @@ -17,8 +17,6 @@ namespace Symfony\Component\VarDumper\Cloner; class VarCloner extends AbstractCloner { private static $gid; - private static $hashMask = 0; - private static $hashOffset = 0; private static $arrayCache = []; /** @@ -29,13 +27,12 @@ class VarCloner extends AbstractCloner $len = 1; // Length of $queue $pos = 0; // Number of cloned items past the minimum depth $refsCounter = 0; // Hard references counter - $queue = [[$var]]; // This breadth-first queue is the return value - $indexedArrays = []; // Map of queue indexes that hold numerically indexed arrays - $hardRefs = []; // Map of original zval hashes to stub objects - $objRefs = []; // Map of original object handles to their stub object counterpart - $objects = []; // Keep a ref to objects to ensure their handle cannot be reused while cloning - $resRefs = []; // Map of original resource handles to their stub object counterpart - $values = []; // Map of stub objects' hashes to original values + $queue = [[$var]]; // This breadth-first queue is the return value + $hardRefs = []; // Map of original zval ids to stub objects + $objRefs = []; // Map of original object handles to their stub object counterpart + $objects = []; // Keep a ref to objects to ensure their handle cannot be reused while cloning + $resRefs = []; // Map of original resource handles to their stub object counterpart + $values = []; // Map of stub objects' ids to original values $maxItems = $this->maxItems; $maxString = $this->maxString; $minDepth = $this->minDepth; @@ -47,13 +44,9 @@ class VarCloner extends AbstractCloner $stub = null; // Stub capturing the main properties of an original item value // or null if the original value is used directly - if (!self::$hashMask) { - self::initHashMask(); - self::$gid = md5(dechex(self::$hashMask)); // Unique string used to detect the special $GLOBALS variable + if (!$gid = self::$gid) { + $gid = self::$gid = md5(random_bytes(6)); // Unique string used to detect the special $GLOBALS variable } - $gid = self::$gid; - $hashMask = self::$hashMask; - $hashOffset = self::$hashOffset; $arrayStub = new Stub(); $arrayStub->type = Stub::TYPE_ARRAY; $fromObjCast = false; @@ -69,50 +62,45 @@ class VarCloner extends AbstractCloner } $refs = $vals = $queue[$i]; - if (\PHP_VERSION_ID < 70200 && empty($indexedArrays[$i])) { - // see https://wiki.php.net/rfc/convert_numeric_keys_in_object_array_casts - foreach ($vals as $k => $v) { - if (\is_int($k)) { - continue; - } - foreach ([$k => true] as $gk => $gv) { - } - if ($gk !== $k) { - $fromObjCast = true; - $refs = $vals = array_values($queue[$i]); - break; - } - } - } foreach ($vals as $k => $v) { // $v is the original value or a stub object in case of hard references if (\PHP_VERSION_ID >= 70400) { - $zvalIsRef = null !== \ReflectionReference::fromArrayElement($vals, $k); + $zvalRef = ($r = \ReflectionReference::fromArrayElement($vals, $k)) ? $r->getId() : null; } else { $refs[$k] = $cookie; - $zvalIsRef = $vals[$k] === $cookie; + $zvalRef = $vals[$k] === $cookie; } - if ($zvalIsRef) { + if ($zvalRef) { $vals[$k] = &$stub; // Break hard references to make $queue completely unset($stub); // independent from the original structure - if ($v instanceof Stub && isset($hardRefs[spl_object_hash($v)])) { - $vals[$k] = $refs[$k] = $v; + if (\PHP_VERSION_ID >= 70400 ? null !== $vals[$k] = $hardRefs[$zvalRef] ?? null : $v instanceof Stub && isset($hardRefs[spl_object_id($v)])) { + if (\PHP_VERSION_ID >= 70400) { + $v = $vals[$k]; + } else { + $refs[$k] = $vals[$k] = $v; + } if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) { ++$v->value->refCount; } ++$v->refCount; continue; } - $refs[$k] = $vals[$k] = new Stub(); - $refs[$k]->value = $v; - $h = spl_object_hash($refs[$k]); - $hardRefs[$h] = &$refs[$k]; - $values[$h] = $v; + $vals[$k] = new Stub(); + $vals[$k]->value = $v; $vals[$k]->handle = ++$refsCounter; + + if (\PHP_VERSION_ID >= 70400) { + $hardRefs[$zvalRef] = $vals[$k]; + } else { + $refs[$k] = $vals[$k]; + $h = spl_object_id($refs[$k]); + $hardRefs[$h] = &$refs[$k]; + $values[$h] = $v; + } } - // Create $stub when the original value $v can not be used directly + // Create $stub when the original value $v cannot be used directly // If $v is a nested structure, put that structure in array $a switch (true) { case null === $v: @@ -151,41 +139,55 @@ class VarCloner extends AbstractCloner continue 2; } $stub = $arrayStub; + + if (\PHP_VERSION_ID >= 80100) { + $stub->class = array_is_list($v) ? Stub::ARRAY_INDEXED : Stub::ARRAY_ASSOC; + $a = $v; + break; + } + $stub->class = Stub::ARRAY_INDEXED; $j = -1; foreach ($v as $gk => $gv) { if ($gk !== ++$j) { $stub->class = Stub::ARRAY_ASSOC; + $a = $v; + $a[$gid] = true; break; } } - $a = $v; - if (Stub::ARRAY_ASSOC === $stub->class) { - // Copies of $GLOBALS have very strange behavior, - // let's detect them with some black magic - $a[$gid] = true; - - // Happens with copies of $GLOBALS - if (isset($v[$gid])) { - unset($v[$gid]); - $a = []; - foreach ($v as $gk => &$gv) { - $a[$gk] = &$gv; + // Copies of $GLOBALS have very strange behavior, + // let's detect them with some black magic + if (isset($v[$gid])) { + unset($v[$gid]); + $a = []; + foreach ($v as $gk => &$gv) { + if ($v === $gv && (\PHP_VERSION_ID < 70400 || !isset($hardRefs[\ReflectionReference::fromArrayElement($v, $gk)->getId()]))) { + unset($v); + $v = new Stub(); + $v->value = [$v->cut = \count($gv), Stub::TYPE_ARRAY => 0]; + $v->handle = -1; + if (\PHP_VERSION_ID >= 70400) { + $gv = &$a[$gk]; + $hardRefs[\ReflectionReference::fromArrayElement($a, $gk)->getId()] = &$gv; + } else { + $gv = &$hardRefs[spl_object_id($v)]; + } + $gv = $v; } - unset($gv); - } else { - $a = $v; + + $a[$gk] = &$gv; } - } elseif (\PHP_VERSION_ID < 70200) { - $indexedArrays[$len] = true; + unset($gv); + } else { + $a = $v; } break; case \is_object($v): - case $v instanceof \__PHP_Incomplete_Class: - if (empty($objRefs[$h = $hashMask ^ hexdec(substr(spl_object_hash($v), $hashOffset, \PHP_INT_SIZE))])) { + if (empty($objRefs[$h = spl_object_id($v)])) { $stub = new Stub(); $stub->type = Stub::TYPE_OBJECT; $stub->class = \get_class($v); @@ -196,8 +198,7 @@ class VarCloner extends AbstractCloner if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) { break; } - $h = $hashMask ^ hexdec(substr(spl_object_hash($stub->value), $hashOffset, \PHP_INT_SIZE)); - $stub->handle = $h; + $stub->handle = $h = spl_object_id($stub->value); } $stub->value = null; if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { @@ -271,10 +272,12 @@ class VarCloner extends AbstractCloner } } - if ($zvalIsRef) { - $refs[$k]->value = $stub; - } else { + if (!$zvalRef) { $vals[$k] = $stub; + } elseif (\PHP_VERSION_ID >= 70400) { + $hardRefs[$zvalRef]->value = $stub; + } else { + $refs[$k]->value = $stub; } } @@ -305,31 +308,4 @@ class VarCloner extends AbstractCloner return $queue; } - - private static function initHashMask() - { - $obj = (object) []; - self::$hashOffset = 16 - \PHP_INT_SIZE; - self::$hashMask = -1; - - if (\defined('HHVM_VERSION')) { - self::$hashOffset += 16; - } else { - // check if we are nested in an output buffering handler to prevent a fatal error with ob_start() below - $obFuncs = ['ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush']; - foreach (debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS) as $frame) { - if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && \in_array($frame['function'], $obFuncs)) { - $frame['line'] = 0; - break; - } - } - if (!empty($frame['line'])) { - ob_start(); - debug_zval_dump($obj); - self::$hashMask = (int) substr(ob_get_clean(), 17); - } - } - - self::$hashMask ^= hexdec(substr(spl_object_hash($obj), self::$hashOffset, \PHP_INT_SIZE)); - } } diff --git a/lib/symfony/var-dumper/Command/Descriptor/CliDescriptor.php b/lib/symfony/var-dumper/Command/Descriptor/CliDescriptor.php new file mode 100644 index 0000000000..2afaa7bf39 --- /dev/null +++ b/lib/symfony/var-dumper/Command/Descriptor/CliDescriptor.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\VarDumper\Command\Descriptor; + +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * Describe collected data clones for cli output. + * + * @author Maxime Steinhausser + * + * @final + */ +class CliDescriptor implements DumpDescriptorInterface +{ + private $dumper; + private $lastIdentifier; + + public function __construct(CliDumper $dumper) + { + $this->dumper = $dumper; + } + + public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void + { + $io = $output instanceof SymfonyStyle ? $output : new SymfonyStyle(new ArrayInput([]), $output); + $this->dumper->setColors($output->isDecorated()); + + $rows = [['date', date('r', (int) $context['timestamp'])]]; + $lastIdentifier = $this->lastIdentifier; + $this->lastIdentifier = $clientId; + + $section = "Received from client #$clientId"; + if (isset($context['request'])) { + $request = $context['request']; + $this->lastIdentifier = $request['identifier']; + $section = sprintf('%s %s', $request['method'], $request['uri']); + if ($controller = $request['controller']) { + $rows[] = ['controller', rtrim($this->dumper->dump($controller, true), "\n")]; + } + } elseif (isset($context['cli'])) { + $this->lastIdentifier = $context['cli']['identifier']; + $section = '$ '.$context['cli']['command_line']; + } + + if ($this->lastIdentifier !== $lastIdentifier) { + $io->section($section); + } + + if (isset($context['source'])) { + $source = $context['source']; + $sourceInfo = sprintf('%s on line %d', $source['name'], $source['line']); + if ($fileLink = $source['file_link'] ?? null) { + $sourceInfo = sprintf('%s', $fileLink, $sourceInfo); + } + $rows[] = ['source', $sourceInfo]; + $file = $source['file_relative'] ?? $source['file']; + $rows[] = ['file', $file]; + } + + $io->table([], $rows); + + $this->dumper->dump($data); + $io->newLine(); + } +} diff --git a/lib/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php b/lib/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php new file mode 100644 index 0000000000..267d27bfac --- /dev/null +++ b/lib/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.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\Component\VarDumper\Command\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @author Maxime Steinhausser + */ +interface DumpDescriptorInterface +{ + public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void; +} diff --git a/lib/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php b/lib/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php new file mode 100644 index 0000000000..636b61828d --- /dev/null +++ b/lib/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; + +/** + * Describe collected data clones for html output. + * + * @author Maxime Steinhausser + * + * @final + */ +class HtmlDescriptor implements DumpDescriptorInterface +{ + private $dumper; + private $initialized = false; + + public function __construct(HtmlDumper $dumper) + { + $this->dumper = $dumper; + } + + public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void + { + if (!$this->initialized) { + $styles = file_get_contents(__DIR__.'/../../Resources/css/htmlDescriptor.css'); + $scripts = file_get_contents(__DIR__.'/../../Resources/js/htmlDescriptor.js'); + $output->writeln(""); + $this->initialized = true; + } + + $title = '-'; + if (isset($context['request'])) { + $request = $context['request']; + $controller = "{$this->dumper->dump($request['controller'], true, ['maxDepth' => 0])}"; + $title = sprintf('%s %s', $request['method'], $uri = $request['uri'], $uri); + $dedupIdentifier = $request['identifier']; + } elseif (isset($context['cli'])) { + $title = '$ '.$context['cli']['command_line']; + $dedupIdentifier = $context['cli']['identifier']; + } else { + $dedupIdentifier = uniqid('', true); + } + + $sourceDescription = ''; + if (isset($context['source'])) { + $source = $context['source']; + $projectDir = $source['project_dir'] ?? null; + $sourceDescription = sprintf('%s on line %d', $source['name'], $source['line']); + if (isset($source['file_link'])) { + $sourceDescription = sprintf('%s', $source['file_link'], $sourceDescription); + } + } + + $isoDate = $this->extractDate($context, 'c'); + $tags = array_filter([ + 'controller' => $controller ?? null, + 'project dir' => $projectDir ?? null, + ]); + + $output->writeln(<< +
                  +
                  +

                  $title

                  + +
                  + {$this->renderTags($tags)} +
                  +
                  +

                  + $sourceDescription +

                  + {$this->dumper->dump($data, true)} +
                  + +HTML + ); + } + + private function extractDate(array $context, string $format = 'r'): string + { + return date($format, (int) $context['timestamp']); + } + + private function renderTags(array $tags): string + { + if (!$tags) { + return ''; + } + + $renderedTags = ''; + foreach ($tags as $key => $value) { + $renderedTags .= sprintf('
                • %s%s
                • ', $key, $value); + } + + return << +
                    + $renderedTags +
                  +
                  +HTML; + } +} diff --git a/lib/symfony/var-dumper/Command/ServerDumpCommand.php b/lib/symfony/var-dumper/Command/ServerDumpCommand.php new file mode 100644 index 0000000000..3a6959522e --- /dev/null +++ b/lib/symfony/var-dumper/Command/ServerDumpCommand.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +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\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Command\Descriptor\CliDescriptor; +use Symfony\Component\VarDumper\Command\Descriptor\DumpDescriptorInterface; +use Symfony\Component\VarDumper\Command\Descriptor\HtmlDescriptor; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\Server\DumpServer; + +/** + * Starts a dump server to collect and output dumps on a single place with multiple formats support. + * + * @author Maxime Steinhausser + * + * @final + */ +class ServerDumpCommand extends Command +{ + protected static $defaultName = 'server:dump'; + protected static $defaultDescription = 'Start a dump server that collects and displays dumps in a single place'; + + private $server; + + /** @var DumpDescriptorInterface[] */ + private $descriptors; + + public function __construct(DumpServer $server, array $descriptors = []) + { + $this->server = $server; + $this->descriptors = $descriptors + [ + 'cli' => new CliDescriptor(new CliDumper()), + 'html' => new HtmlDescriptor(new HtmlDumper()), + ]; + + parent::__construct(); + } + + protected function configure() + { + $this + ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format (%s)', implode(', ', $this->getAvailableFormats())), 'cli') + ->setDescription(self::$defaultDescription) + ->setHelp(<<<'EOF' +%command.name% starts a dump server that collects and displays +dumps in a single place for debugging you application: + + php %command.full_name% + +You can consult dumped data in HTML format in your browser by providing the --format=html option +and redirecting the output to a file: + + php %command.full_name% --format="html" > dump.html + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $format = $input->getOption('format'); + + if (!$descriptor = $this->descriptors[$format] ?? null) { + throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $format)); + } + + $errorIo = $io->getErrorStyle(); + $errorIo->title('Symfony Var Dumper Server'); + + $this->server->start(); + + $errorIo->success(sprintf('Server listening on %s', $this->server->getHost())); + $errorIo->comment('Quit the server with CONTROL-C.'); + + $this->server->listen(function (Data $data, array $context, int $clientId) use ($descriptor, $io) { + $descriptor->describe($io, $data, $context, $clientId); + }); + + return 0; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues($this->getAvailableFormats()); + } + } + + private function getAvailableFormats(): array + { + return array_keys($this->descriptors); + } +} diff --git a/lib/symfony/var-dumper/Dumper/AbstractDumper.php b/lib/symfony/var-dumper/Dumper/AbstractDumper.php index 5ea6294f3d..ae19faf613 100644 --- a/lib/symfony/var-dumper/Dumper/AbstractDumper.php +++ b/lib/symfony/var-dumper/Dumper/AbstractDumper.php @@ -21,10 +21,10 @@ use Symfony\Component\VarDumper\Cloner\DumperInterface; */ abstract class AbstractDumper implements DataDumperInterface, DumperInterface { - const DUMP_LIGHT_ARRAY = 1; - const DUMP_STRING_LENGTH = 2; - const DUMP_COMMA_SEPARATOR = 4; - const DUMP_TRAILING_COMMA = 8; + public const DUMP_LIGHT_ARRAY = 1; + public const DUMP_STRING_LENGTH = 2; + public const DUMP_COMMA_SEPARATOR = 4; + public const DUMP_TRAILING_COMMA = 8; public static $defaultOutput = 'php://output'; @@ -42,12 +42,11 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface * @param string|null $charset The default character encoding to use for non-UTF8 strings * @param int $flags A bit field of static::DUMP_* constants to fine tune dumps representation */ - public function __construct($output = null, $charset = null, $flags = 0) + public function __construct($output = null, string $charset = null, int $flags = 0) { - $this->flags = (int) $flags; - $this->setCharset($charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8'); - $this->decimalPoint = localeconv(); - $this->decimalPoint = $this->decimalPoint['decimal_point']; + $this->flags = $flags; + $this->setCharset($charset ?: \ini_get('php.output_encoding') ?: \ini_get('default_charset') ?: 'UTF-8'); + $this->decimalPoint = \PHP_VERSION_ID >= 80000 ? '.' : localeconv()['decimal_point']; $this->setOutput($output ?: static::$defaultOutput); if (!$output && \is_string(static::$defaultOutput)) { static::$defaultOutput = $this->outputStream; @@ -63,14 +62,14 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface */ public function setOutput($output) { - $prev = null !== $this->outputStream ? $this->outputStream : $this->lineDumper; + $prev = $this->outputStream ?? $this->lineDumper; if (\is_callable($output)) { $this->outputStream = null; $this->lineDumper = $output; } else { if (\is_string($output)) { - $output = fopen($output, 'wb'); + $output = fopen($output, 'w'); } $this->outputStream = $output; $this->lineDumper = [$this, 'echoLine']; @@ -82,11 +81,9 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface /** * Sets the default character encoding to use for non-UTF8 strings. * - * @param string $charset The default character encoding to use for non-UTF8 strings - * * @return string The previous charset */ - public function setCharset($charset) + public function setCharset(string $charset) { $prev = $this->charset; @@ -105,7 +102,7 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface * * @return string The previous indent pad */ - public function setIndentPad($pad) + public function setIndentPad(string $pad) { $prev = $this->indentPad; $this->indentPad = $pad; @@ -116,22 +113,20 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface /** * Dumps a Data object. * - * @param Data $data A Data object * @param callable|resource|string|true|null $output A line dumper callable, an opened stream, an output path or true to return the dump * * @return string|null The dump as string when $output is true */ public function dump(Data $data, $output = null) { - $this->decimalPoint = localeconv(); - $this->decimalPoint = $this->decimalPoint['decimal_point']; + $this->decimalPoint = \PHP_VERSION_ID >= 80000 ? '.' : localeconv()['decimal_point']; if ($locale = $this->flags & (self::DUMP_COMMA_SEPARATOR | self::DUMP_TRAILING_COMMA) ? setlocale(\LC_NUMERIC, 0) : null) { setlocale(\LC_NUMERIC, 'C'); } if ($returnDump = true === $output) { - $output = fopen('php://memory', 'r+b'); + $output = fopen('php://memory', 'r+'); } if ($output) { $prevOutput = $this->setOutput($output); @@ -164,20 +159,16 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface * @param int $depth The recursive depth in the dumped structure for the line being dumped, * or -1 to signal the end-of-dump to the line dumper callable */ - protected function dumpLine($depth) + protected function dumpLine(int $depth) { - \call_user_func($this->lineDumper, $this->line, $depth, $this->indentPad); + ($this->lineDumper)($this->line, $depth, $this->indentPad); $this->line = ''; } /** * Generic line dumper callback. - * - * @param string $line The line to write - * @param int $depth The recursive depth in the dumped structure - * @param string $indentPad The line indent pad */ - protected function echoLine($line, $depth, $indentPad) + protected function echoLine(string $line, int $depth, string $indentPad) { if (-1 !== $depth) { fwrite($this->outputStream, str_repeat($indentPad, $depth).$line."\n"); @@ -187,11 +178,9 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface /** * Converts a non-UTF-8 string to UTF-8. * - * @param string|null $s The non-UTF-8 string to convert - * - * @return string|null The string converted to UTF-8 + * @return string|null */ - protected function utf8Encode($s) + protected function utf8Encode(?string $s) { if (null === $s || preg_match('//u', $s)) { return $s; diff --git a/lib/symfony/var-dumper/Dumper/CliDumper.php b/lib/symfony/var-dumper/Dumper/CliDumper.php index 4c48ab7cc8..94dc8ee973 100644 --- a/lib/symfony/var-dumper/Dumper/CliDumper.php +++ b/lib/symfony/var-dumper/Dumper/CliDumper.php @@ -55,10 +55,16 @@ class CliDumper extends AbstractDumper protected $collapseNextHash = false; protected $expandNextHash = false; + private $displayOptions = [ + 'fileLinkFormat' => null, + ]; + + private $handlesHrefGracefully; + /** * {@inheritdoc} */ - public function __construct($output = null, $charset = null, $flags = 0) + public function __construct($output = null, string $charset = null, int $flags = 0) { parent::__construct($output, $charset, $flags); @@ -76,26 +82,24 @@ class CliDumper extends AbstractDumper 'index' => '34', ]); } + + $this->displayOptions['fileLinkFormat'] = \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f#L%l'; } /** * Enables/disables colored output. - * - * @param bool $colors */ - public function setColors($colors) + public function setColors(bool $colors) { - $this->colors = (bool) $colors; + $this->colors = $colors; } /** * Sets the maximum number of characters per line for dumped strings. - * - * @param int $maxStringWidth */ - public function setMaxStringWidth($maxStringWidth) + public function setMaxStringWidth(int $maxStringWidth) { - $this->maxStringWidth = (int) $maxStringWidth; + $this->maxStringWidth = $maxStringWidth; } /** @@ -108,10 +112,20 @@ class CliDumper extends AbstractDumper $this->styles = $styles + $this->styles; } + /** + * Configures display options. + * + * @param array $displayOptions A map of display options to customize the behavior + */ + public function setDisplayOptions(array $displayOptions) + { + $this->displayOptions = $displayOptions + $this->displayOptions; + } + /** * {@inheritdoc} */ - public function dumpScalar(Cursor $cursor, $type, $value) + public function dumpScalar(Cursor $cursor, string $type, $value) { $this->dumpKey($cursor); @@ -125,18 +139,27 @@ class CliDumper extends AbstractDumper case 'integer': $style = 'num'; + + if (isset($this->styles['integer'])) { + $style = 'integer'; + } + break; case 'double': $style = 'num'; + if (isset($this->styles['float'])) { + $style = 'float'; + } + switch (true) { case \INF === $value: $value = 'INF'; break; case -\INF === $value: $value = '-INF'; break; case is_nan($value): $value = 'NAN'; break; default: $value = (string) $value; - if (false === strpos($value, $this->decimalPoint)) { + if (!str_contains($value, $this->decimalPoint)) { $value .= $this->decimalPoint.'0'; } break; @@ -165,7 +188,7 @@ class CliDumper extends AbstractDumper /** * {@inheritdoc} */ - public function dumpString(Cursor $cursor, $str, $bin, $cut) + public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut) { $this->dumpKey($cursor); $attr = $cursor->attr; @@ -181,7 +204,7 @@ class CliDumper extends AbstractDumper 'length' => 0 <= $cut ? mb_strlen($str, 'UTF-8') + $cut : 0, 'binary' => $bin, ]; - $str = explode("\n", $str); + $str = $bin && false !== strpos($str, "\0") ? [$str] : explode("\n", $str); if (isset($str[1]) && !isset($str[2]) && !isset($str[1][0])) { unset($str[1]); $str[0] .= "\n"; @@ -253,13 +276,14 @@ class CliDumper extends AbstractDumper /** * {@inheritdoc} */ - public function enterHash(Cursor $cursor, $type, $class, $hasChild) + public function enterHash(Cursor $cursor, int $type, $class, bool $hasChild) { if (null === $this->colors) { $this->colors = $this->supportsColors(); } $this->dumpKey($cursor); + $attr = $cursor->attr; if ($this->collapseNextHash) { $cursor->skipChildren = true; @@ -268,14 +292,14 @@ class CliDumper extends AbstractDumper $class = $this->utf8Encode($class); if (Cursor::HASH_OBJECT === $type) { - $prefix = $class && 'stdClass' !== $class ? $this->style('note', $class).' {' : '{'; + $prefix = $class && 'stdClass' !== $class ? $this->style('note', $class, $attr).(empty($attr['cut_hash']) ? ' {' : '') : '{'; } elseif (Cursor::HASH_RESOURCE === $type) { - $prefix = $this->style('note', $class.' resource').($hasChild ? ' {' : ' '); + $prefix = $this->style('note', $class.' resource', $attr).($hasChild ? ' {' : ' '); } else { $prefix = $class && !(self::DUMP_LIGHT_ARRAY & $this->flags) ? $this->style('note', 'array:'.$class).' [' : '['; } - if ($cursor->softRefCount || 0 < $cursor->softRefHandle) { + if (($cursor->softRefCount || 0 < $cursor->softRefHandle) && empty($attr['cut_hash'])) { $prefix .= $this->style('ref', (Cursor::HASH_RESOURCE === $type ? '@' : '#').(0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->softRefTo), ['count' => $cursor->softRefCount]); } elseif ($cursor->hardRefTo && !$cursor->refIndex && $class) { $prefix .= $this->style('ref', '&'.$cursor->hardRefTo, ['count' => $cursor->hardRefCount]); @@ -293,21 +317,23 @@ class CliDumper extends AbstractDumper /** * {@inheritdoc} */ - public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut) + public function leaveHash(Cursor $cursor, int $type, $class, bool $hasChild, int $cut) { - $this->dumpEllipsis($cursor, $hasChild, $cut); - $this->line .= Cursor::HASH_OBJECT === $type ? '}' : (Cursor::HASH_RESOURCE !== $type ? ']' : ($hasChild ? '}' : '')); + if (empty($cursor->attr['cut_hash'])) { + $this->dumpEllipsis($cursor, $hasChild, $cut); + $this->line .= Cursor::HASH_OBJECT === $type ? '}' : (Cursor::HASH_RESOURCE !== $type ? ']' : ($hasChild ? '}' : '')); + } + $this->endValue($cursor); } /** * Dumps an ellipsis for cut children. * - * @param Cursor $cursor The Cursor position in the dump - * @param bool $hasChild When the dump of the hash has child item - * @param int $cut The number of items the hash has been cut by + * @param bool $hasChild When the dump of the hash has child item + * @param int $cut The number of items the hash has been cut by */ - protected function dumpEllipsis(Cursor $cursor, $hasChild, $cut) + protected function dumpEllipsis(Cursor $cursor, bool $hasChild, int $cut) { if ($cut) { $this->line .= ' …'; @@ -322,8 +348,6 @@ class CliDumper extends AbstractDumper /** * Dumps a key in a hash structure. - * - * @param Cursor $cursor The Cursor position in the dump */ protected function dumpKey(Cursor $cursor) { @@ -390,7 +414,7 @@ class CliDumper extends AbstractDumper } } - $this->line .= $bin.$this->style($style, $key[1], $attr).(isset($attr['separator']) ? $attr['separator'] : ': '); + $this->line .= $bin.$this->style($style, $key[1], $attr).($attr['separator'] ?? ': '); } else { // This case should not happen $this->line .= '-'.$bin.'"'.$this->style('private', $key, ['class' => '']).'": '; @@ -411,17 +435,22 @@ class CliDumper extends AbstractDumper * @param string $value The value being styled * @param array $attr Optional context information * - * @return string The value with style decoration + * @return string */ - protected function style($style, $value, $attr = []) + protected function style(string $style, string $value, array $attr = []) { if (null === $this->colors) { $this->colors = $this->supportsColors(); } + if (null === $this->handlesHrefGracefully) { + $this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') + && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100); + } + if (isset($attr['ellipsis'], $attr['ellipsis-type'])) { $prefix = substr($value, 0, -$attr['ellipsis']); - if ('cli' === \PHP_SAPI && 'path' === $attr['ellipsis-type'] && isset($_SERVER[$pwd = '\\' === \DIRECTORY_SEPARATOR ? 'CD' : 'PWD']) && 0 === strpos($prefix, $_SERVER[$pwd])) { + if ('cli' === \PHP_SAPI && 'path' === $attr['ellipsis-type'] && isset($_SERVER[$pwd = '\\' === \DIRECTORY_SEPARATOR ? 'CD' : 'PWD']) && str_starts_with($prefix, $_SERVER[$pwd])) { $prefix = '.'.substr($prefix, \strlen($_SERVER[$pwd])); } if (!empty($attr['ellipsis-tail'])) { @@ -431,19 +460,19 @@ class CliDumper extends AbstractDumper $value = substr($value, -$attr['ellipsis']); } - return $this->style('default', $prefix).$this->style($style, $value); - } + $value = $this->style('default', $prefix).$this->style($style, $value); - $style = $this->styles[$style]; + goto href; + } $map = static::$controlCharsMap; $startCchr = $this->colors ? "\033[m\033[{$this->styles['default']}m" : ''; - $endCchr = $this->colors ? "\033[m\033[{$style}m" : ''; + $endCchr = $this->colors ? "\033[m\033[{$this->styles[$style]}m" : ''; $value = preg_replace_callback(static::$controlCharsRx, function ($c) use ($map, $startCchr, $endCchr) { $s = $startCchr; $c = $c[$i = 0]; do { - $s .= isset($map[$c[$i]]) ? $map[$c[$i]] : sprintf('\x%02X', \ord($c[$i])); + $s .= $map[$c[$i]] ?? sprintf('\x%02X', \ord($c[$i])); } while (isset($c[++$i])); return $s.$endCchr; @@ -453,20 +482,36 @@ class CliDumper extends AbstractDumper if ($cchrCount && "\033" === $value[0]) { $value = substr($value, \strlen($startCchr)); } else { - $value = "\033[{$style}m".$value; + $value = "\033[{$this->styles[$style]}m".$value; } - if ($cchrCount && $endCchr === substr($value, -\strlen($endCchr))) { + if ($cchrCount && str_ends_with($value, $endCchr)) { $value = substr($value, 0, -\strlen($endCchr)); } else { $value .= "\033[{$this->styles['default']}m"; } } + href: + if ($this->colors && $this->handlesHrefGracefully) { + if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], $attr['line'] ?? 0)) { + if ('note' === $style) { + $value .= "\033]8;;{$href}\033\\^\033]8;;\033\\"; + } else { + $attr['href'] = $href; + } + } + if (isset($attr['href'])) { + $value = "\033]8;;{$attr['href']}\033\\{$value}\033]8;;\033\\"; + } + } elseif ($attr['if_links'] ?? false) { + return ''; + } + return $value; } /** - * @return bool Tells if the current output stream supports ANSI colors or not + * @return bool */ protected function supportsColors() { @@ -487,12 +532,14 @@ class CliDumper extends AbstractDumper case '--color=yes': case '--color=force': case '--color=always': + case '--colors=always': return static::$defaultColors = true; case '--no-ansi': case '--color=no': case '--color=none': case '--color=never': + case '--colors=never': return static::$defaultColors = false; } } @@ -500,7 +547,7 @@ class CliDumper extends AbstractDumper } $h = stream_get_meta_data($this->outputStream) + ['wrapper_type' => null]; - $h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'wb') : $this->outputStream; + $h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'w') : $this->outputStream; return static::$defaultColors = $this->hasColorSupport($h); } @@ -508,7 +555,7 @@ class CliDumper extends AbstractDumper /** * {@inheritdoc} */ - protected function dumpLine($depth, $endOfValue = false) + protected function dumpLine(int $depth, bool $endOfValue = false) { if ($this->colors) { $this->line = sprintf("\033[%sm%s\033[m", $this->styles['default'], $this->line); @@ -518,6 +565,10 @@ class CliDumper extends AbstractDumper protected function endValue(Cursor $cursor) { + if (-1 === $cursor->hashType) { + return; + } + if (Stub::ARRAY_INDEXED === $cursor->hashType || Stub::ARRAY_ASSOC === $cursor->hashType) { if (self::DUMP_TRAILING_COMMA & $this->flags && 0 < $cursor->depth) { $this->line .= ','; @@ -536,15 +587,18 @@ class CliDumper extends AbstractDumper * https://github.com/composer/xdebug-handler * * @param mixed $stream A CLI output stream - * - * @return bool */ - private function hasColorSupport($stream) + private function hasColorSupport($stream): bool { if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) { return false; } + // Follow https://no-color.org/ + if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) { + return false; + } + if ('Hyper' === getenv('TERM_PROGRAM')) { return true; } @@ -557,17 +611,7 @@ class CliDumper extends AbstractDumper || 'xterm' === getenv('TERM'); } - if (\function_exists('stream_isatty')) { - return @stream_isatty($stream); - } - - if (\function_exists('posix_isatty')) { - return @posix_isatty($stream); - } - - $stat = @fstat($stream); - // Check if formatted mode is S_IFCHR - return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; + return stream_isatty($stream); } /** @@ -576,17 +620,15 @@ class CliDumper extends AbstractDumper * Note that this does not check an output stream, but relies on environment * variables from known implementations, or a PHP and Windows version that * supports true color. - * - * @return bool */ - private function isWindowsTrueColor() + private function isWindowsTrueColor(): bool { $result = 183 <= getenv('ANSICON_VER') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM') || 'Hyper' === getenv('TERM_PROGRAM'); - if (!$result && \PHP_VERSION_ID >= 70200) { + if (!$result) { $version = sprintf( '%s.%s.%s', PHP_WINDOWS_VERSION_MAJOR, @@ -598,4 +640,13 @@ class CliDumper extends AbstractDumper return $result; } + + private function getSourceLink(string $file, int $line) + { + if ($fmt = $this->displayOptions['fileLinkFormat']) { + return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : ($fmt->format($file, $line) ?: 'file://'.$file.'#L'.$line); + } + + return false; + } } diff --git a/lib/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php b/lib/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php new file mode 100644 index 0000000000..38f878971c --- /dev/null +++ b/lib/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +/** + * Tries to provide context on CLI. + * + * @author Maxime Steinhausser + */ +final class CliContextProvider implements ContextProviderInterface +{ + public function getContext(): ?array + { + if ('cli' !== \PHP_SAPI) { + return null; + } + + return [ + 'command_line' => $commandLine = implode(' ', $_SERVER['argv'] ?? []), + 'identifier' => hash('crc32b', $commandLine.$_SERVER['REQUEST_TIME_FLOAT']), + ]; + } +} diff --git a/lib/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php b/lib/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php new file mode 100644 index 0000000000..532aa0f96f --- /dev/null +++ b/lib/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +/** + * Interface to provide contextual data about dump data clones sent to a server. + * + * @author Maxime Steinhausser + */ +interface ContextProviderInterface +{ + public function getContext(): ?array; +} diff --git a/lib/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php b/lib/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php new file mode 100644 index 0000000000..3684a47535 --- /dev/null +++ b/lib/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\VarDumper\Caster\ReflectionCaster; +use Symfony\Component\VarDumper\Cloner\VarCloner; + +/** + * Tries to provide context from a request. + * + * @author Maxime Steinhausser + */ +final class RequestContextProvider implements ContextProviderInterface +{ + private $requestStack; + private $cloner; + + public function __construct(RequestStack $requestStack) + { + $this->requestStack = $requestStack; + $this->cloner = new VarCloner(); + $this->cloner->setMaxItems(0); + $this->cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); + } + + public function getContext(): ?array + { + if (null === $request = $this->requestStack->getCurrentRequest()) { + return null; + } + + $controller = $request->attributes->get('_controller'); + + return [ + 'uri' => $request->getUri(), + 'method' => $request->getMethod(), + 'controller' => $controller ? $this->cloner->cloneVar($controller) : $controller, + 'identifier' => spl_object_hash($request), + ]; + } +} diff --git a/lib/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php b/lib/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php new file mode 100644 index 0000000000..2e2c818161 --- /dev/null +++ b/lib/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\VarDumper; +use Twig\Template; + +/** + * Tries to provide context from sources (class name, file, line, code excerpt, ...). + * + * @author Nicolas Grekas + * @author Maxime Steinhausser + */ +final class SourceContextProvider implements ContextProviderInterface +{ + private $limit; + private $charset; + private $projectDir; + private $fileLinkFormatter; + + public function __construct(string $charset = null, string $projectDir = null, FileLinkFormatter $fileLinkFormatter = null, int $limit = 9) + { + $this->charset = $charset; + $this->projectDir = $projectDir; + $this->fileLinkFormatter = $fileLinkFormatter; + $this->limit = $limit; + } + + public function getContext(): ?array + { + $trace = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, $this->limit); + + $file = $trace[1]['file']; + $line = $trace[1]['line']; + $name = false; + $fileExcerpt = false; + + for ($i = 2; $i < $this->limit; ++$i) { + if (isset($trace[$i]['class'], $trace[$i]['function']) + && 'dump' === $trace[$i]['function'] + && VarDumper::class === $trace[$i]['class'] + ) { + $file = $trace[$i]['file'] ?? $file; + $line = $trace[$i]['line'] ?? $line; + + while (++$i < $this->limit) { + if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && !str_starts_with($trace[$i]['function'], 'call_user_func')) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + break; + } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) { + $template = $trace[$i]['object']; + $name = $template->getTemplateName(); + $src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false); + $info = $template->getDebugInfo(); + if (isset($info[$trace[$i - 1]['line']])) { + $line = $info[$trace[$i - 1]['line']]; + $file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null; + + if ($src) { + $src = explode("\n", $src); + $fileExcerpt = []; + + for ($i = max($line - 3, 1), $max = min($line + 3, \count($src)); $i <= $max; ++$i) { + $fileExcerpt[] = ''.$this->htmlEncode($src[$i - 1]).''; + } + + $fileExcerpt = '
                    '.implode("\n", $fileExcerpt).'
                  '; + } + } + break; + } + } + break; + } + } + + if (false === $name) { + $name = str_replace('\\', '/', $file); + $name = substr($name, strrpos($name, '/') + 1); + } + + $context = ['name' => $name, 'file' => $file, 'line' => $line]; + $context['file_excerpt'] = $fileExcerpt; + + if (null !== $this->projectDir) { + $context['project_dir'] = $this->projectDir; + if (str_starts_with($file, $this->projectDir)) { + $context['file_relative'] = ltrim(substr($file, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); + } + } + + if ($this->fileLinkFormatter && $fileLink = $this->fileLinkFormatter->format($context['file'], $context['line'])) { + $context['file_link'] = $fileLink; + } + + return $context; + } + + private function htmlEncode(string $s): string + { + $html = ''; + + $dumper = new HtmlDumper(function ($line) use (&$html) { $html .= $line; }, $this->charset); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); + + $cloner = new VarCloner(); + $dumper->dump($cloner->cloneVar($s)); + + return substr(strip_tags($html), 1, -1); + } +} diff --git a/lib/symfony/var-dumper/Dumper/ContextualizedDumper.php b/lib/symfony/var-dumper/Dumper/ContextualizedDumper.php new file mode 100644 index 0000000000..76384176ef --- /dev/null +++ b/lib/symfony/var-dumper/Dumper/ContextualizedDumper.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 Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; + +/** + * @author Kévin Thérage + */ +class ContextualizedDumper implements DataDumperInterface +{ + private $wrappedDumper; + private $contextProviders; + + /** + * @param ContextProviderInterface[] $contextProviders + */ + public function __construct(DataDumperInterface $wrappedDumper, array $contextProviders) + { + $this->wrappedDumper = $wrappedDumper; + $this->contextProviders = $contextProviders; + } + + public function dump(Data $data) + { + $context = []; + foreach ($this->contextProviders as $contextProvider) { + $context[\get_class($contextProvider)] = $contextProvider->getContext(); + } + + $this->wrappedDumper->dump($data->withContext($context)); + } +} diff --git a/lib/symfony/var-dumper/Dumper/HtmlDumper.php b/lib/symfony/var-dumper/Dumper/HtmlDumper.php index ccbdc96f23..af4de96136 100644 --- a/lib/symfony/var-dumper/Dumper/HtmlDumper.php +++ b/lib/symfony/var-dumper/Dumper/HtmlDumper.php @@ -23,6 +23,41 @@ class HtmlDumper extends CliDumper { public static $defaultOutput = 'php://output'; + protected static $themes = [ + 'dark' => [ + 'default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', + 'num' => 'font-weight:bold; color:#1299DA', + 'const' => 'font-weight:bold', + 'str' => 'font-weight:bold; color:#56DB3A', + 'note' => 'color:#1299DA', + 'ref' => 'color:#A0A0A0', + 'public' => 'color:#FFFFFF', + 'protected' => 'color:#FFFFFF', + 'private' => 'color:#FFFFFF', + 'meta' => 'color:#B729D9', + 'key' => 'color:#56DB3A', + 'index' => 'color:#1299DA', + 'ellipsis' => 'color:#FF8400', + 'ns' => 'user-select:none;', + ], + 'light' => [ + 'default' => 'background:none; color:#CC7832; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', + 'num' => 'font-weight:bold; color:#1299DA', + 'const' => 'font-weight:bold', + 'str' => 'font-weight:bold; color:#629755;', + 'note' => 'color:#6897BB', + 'ref' => 'color:#6E6E6E', + 'public' => 'color:#262626', + 'protected' => 'color:#262626', + 'private' => 'color:#262626', + 'meta' => 'color:#B729D9', + 'key' => 'color:#789339', + 'index' => 'color:#1299DA', + 'ellipsis' => 'color:#CC7832', + 'ns' => 'user-select:none;', + ], + ]; + protected $dumpHeader; protected $dumpPrefix = '
                  ';
                       protected $dumpSuffix = '
                  '; @@ -30,21 +65,7 @@ class HtmlDumper extends CliDumper protected $colors = true; protected $headerIsDumped = false; protected $lastDepth = -1; - protected $styles = [ - 'default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', - 'num' => 'font-weight:bold; color:#1299DA', - 'const' => 'font-weight:bold', - 'str' => 'font-weight:bold; color:#56DB3A', - 'note' => 'color:#1299DA', - 'ref' => 'color:#A0A0A0', - 'public' => 'color:#FFFFFF', - 'protected' => 'color:#FFFFFF', - 'private' => 'color:#FFFFFF', - 'meta' => 'color:#B729D9', - 'key' => 'color:#56DB3A', - 'index' => 'color:#1299DA', - 'ellipsis' => 'color:#FF8400', - ]; + protected $styles; private $displayOptions = [ 'maxDepth' => 1, @@ -56,11 +77,12 @@ class HtmlDumper extends CliDumper /** * {@inheritdoc} */ - public function __construct($output = null, $charset = null, $flags = 0) + public function __construct($output = null, string $charset = null, int $flags = 0) { AbstractDumper::__construct($output, $charset, $flags); $this->dumpId = 'sf-dump-'.mt_rand(); - $this->displayOptions['fileLinkFormat'] = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + $this->displayOptions['fileLinkFormat'] = \ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + $this->styles = static::$themes['dark'] ?? self::$themes['dark']; } /** @@ -72,6 +94,15 @@ class HtmlDumper extends CliDumper $this->styles = $styles + $this->styles; } + public function setTheme(string $themeName) + { + if (!isset(static::$themes[$themeName])) { + throw new \InvalidArgumentException(sprintf('Theme "%s" does not exist in class "%s".', $themeName, static::class)); + } + + $this->setStyles(static::$themes[$themeName]); + } + /** * Configures display options. * @@ -85,21 +116,16 @@ class HtmlDumper extends CliDumper /** * Sets an HTML header that will be dumped once in the output stream. - * - * @param string $header An HTML string */ - public function setDumpHeader($header) + public function setDumpHeader(?string $header) { $this->dumpHeader = $header; } /** * Sets an HTML prefix and suffix that will encapse every single dump. - * - * @param string $prefix The prepended HTML string - * @param string $suffix The appended HTML string */ - public function setDumpBoundaries($prefix, $suffix) + public function setDumpBoundaries(string $prefix, string $suffix) { $this->dumpPrefix = $prefix; $this->dumpSuffix = $suffix; @@ -122,7 +148,7 @@ class HtmlDumper extends CliDumper */ protected function getDumpHeader() { - $this->headerIsDumped = null !== $this->outputStream ? $this->outputStream : $this->lineDumper; + $this->headerIsDumped = $this->outputStream ?? $this->lineDumper; if (null !== $this->dumpHeader) { return $this->dumpHeader; @@ -140,6 +166,9 @@ var refStyle = doc.createElement('style'), e.addEventListener(n, cb, false); }; +refStyle.innerHTML = 'pre.sf-dump .sf-dump-compact, .sf-dump-str-collapse .sf-dump-str-collapse, .sf-dump-str-expand .sf-dump-str-expand { display: none; }'; +(doc.documentElement.firstElementChild || doc.documentElement.children[0]).appendChild(refStyle); +refStyle = doc.createElement('style'); (doc.documentElement.firstElementChild || doc.documentElement.children[0]).appendChild(refStyle); if (!doc.addEventListener) { @@ -283,13 +312,21 @@ return function (root, x) { } function a(e, f) { - addEventListener(root, e, function (e) { + addEventListener(root, e, function (e, n) { if ('A' == e.target.tagName) { f(e.target, e); } else if ('A' == e.target.parentNode.tagName) { f(e.target.parentNode, e); - } else if (e.target.nextElementSibling && 'A' == e.target.nextElementSibling.tagName) { - f(e.target.nextElementSibling, e, true); + } else { + n = /\bsf-dump-ellipsis\b/.test(e.target.className) ? e.target.parentNode : e.target; + + if ((n = n.nextElementSibling) && 'A' == n.tagName) { + if (!/\bsf-dump-toggle\b/.test(n.className)) { + n = n.nextElementSibling || n; + } + + f(n, e, true); + } } }); }; @@ -332,7 +369,7 @@ return function (root, x) { if (/\bsf-dump-toggle\b/.test(a.className)) { e.preventDefault(); if (!toggle(a, isCtrlKey(e))) { - var r = doc.getElementById(a.getAttribute('href').substr(1)), + var r = doc.getElementById(a.getAttribute('href').slice(1)), s = r.previousSibling, f = r.parentNode, t = a.parentNode; @@ -385,21 +422,15 @@ return function (root, x) { a.innerHTML += ' '; } a.title = (a.title ? a.title+'\n[' : '[')+keyHint+'+click] Expand all children'; - a.innerHTML += ''; + a.innerHTML += elt.className == 'sf-dump-compact' ? '' : ''; a.className += ' sf-dump-toggle'; x = 1; if ('sf-dump' != elt.parentNode.className) { x += elt.parentNode.getAttribute('data-depth')/1; } - elt.setAttribute('data-depth', x); - var className = elt.className; - elt.className = 'sf-dump-expanded'; - if (className ? 'sf-dump-expanded' !== className : (x > options.maxDepth)) { - toggle(a); - } } else if (/\bsf-dump-ref\b/.test(elt.className) && (a = elt.getAttribute('href'))) { - a = a.substr(1); + a = a.slice(1); elt.className += ' '+a; if (/[\[{]$/.test(elt.previousSibling.nodeValue)) { @@ -469,10 +500,18 @@ return function (root, x) { function showCurrent(state) { - var currentNode = state.current(); + var currentNode = state.current(), currentRect, searchRect; if (currentNode) { reveal(currentNode); highlight(root, currentNode, state.nodes); + if ('scrollIntoView' in currentNode) { + currentNode.scrollIntoView(true); + currentRect = currentNode.getBoundingClientRect(); + searchRect = search.getBoundingClientRect(); + if (currentRect.top < (searchRect.top + searchRect.height)) { + window.scrollBy(0, -(searchRect.top + searchRect.height + 5)); + } + } } counter.textContent = (state.isEmpty() ? 0 : state.idx + 1) + ' of ' + state.count(); } @@ -545,6 +584,15 @@ return function (root, x) { var isSearchActive = !/\bsf-dump-search-hidden\b/.test(search.className); if ((114 === e.keyCode && !isSearchActive) || (isCtrlKey(e) && 70 === e.keyCode)) { /* F3 or CMD/CTRL + F */ + if (70 === e.keyCode && document.activeElement === searchInput) { + /* + * If CMD/CTRL + F is hit while having focus on search input, + * the user probably meant to trigger browser search instead. + * Let the browser execute its behavior: + */ + return; + } + e.preventDefault(); search.className = search.className.replace(/\bsf-dump-search-hidden\b/, ''); searchInput.focus(); @@ -603,6 +651,7 @@ pre.sf-dump { display: block; white-space: pre; padding: 5px; + overflow: initial !important; } pre.sf-dump:after { content: ""; @@ -614,14 +663,6 @@ pre.sf-dump:after { pre.sf-dump span { display: inline; } -pre.sf-dump .sf-dump-compact { - display: none; -} -pre.sf-dump abbr { - text-decoration: none; - border: none; - cursor: help; -} pre.sf-dump a { text-decoration: none; cursor: pointer; @@ -629,6 +670,13 @@ pre.sf-dump a { outline: none; color: inherit; } +pre.sf-dump img { + max-width: 50em; + max-height: 50em; + margin: .5em 0 0 0; + padding: 0; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAAHUlEQVQY02O8zAABilCaiQEN0EeA8QuUcX9g3QEAAjcC5piyhyEAAAAASUVORK5CYII=) #D3D3D3; +} pre.sf-dump .sf-dump-ellipsis { display: inline-block; overflow: visible; @@ -646,12 +694,6 @@ pre.sf-dump code { padding:0; background:none; } -.sf-dump-str-collapse .sf-dump-str-collapse { - display: none; -} -.sf-dump-str-expand .sf-dump-str-expand { - display: none; -} .sf-dump-public.sf-dump-highlight, .sf-dump-protected.sf-dump-highlight, .sf-dump-private.sf-dump-highlight, @@ -671,14 +713,16 @@ pre.sf-dump code { border-radius: 3px; } pre.sf-dump .sf-dump-search-hidden { - display: none; + display: none !important; } pre.sf-dump .sf-dump-search-wrapper { - float: right; font-size: 0; white-space: nowrap; - max-width: 100%; - text-align: right; + margin-bottom: 5px; + display: flex; + position: -webkit-sticky; + position: sticky; + top: 5px; } pre.sf-dump .sf-dump-search-wrapper > * { vertical-align: top; @@ -695,10 +739,11 @@ pre.sf-dump .sf-dump-search-wrapper > input.sf-dump-search-input { height: 21px; font-size: 12px; border-right: none; - width: 140px; border-top-left-radius: 3px; border-bottom-left-radius: 3px; color: #000; + min-width: 15px; + width: 100%; } pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-next, pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-previous { @@ -732,6 +777,7 @@ EOHTML foreach ($this->styles as $class => $style) { $line .= 'pre.sf-dump'.('default' === $class ? ', pre.sf-dump' : '').' .sf-dump-'.$class.'{'.$style.'}'; } + $line .= 'pre.sf-dump .sf-dump-ellipsis-note{'.$this->styles['note'].'}'; return $this->dumpHeader = preg_replace('/\s+/', ' ', $line).''.$this->dumpHeader; } @@ -739,22 +785,41 @@ EOHTML /** * {@inheritdoc} */ - public function enterHash(Cursor $cursor, $type, $class, $hasChild) + public function dumpString(Cursor $cursor, string $str, bool $bin, int $cut) { + if ('' === $str && isset($cursor->attr['img-data'], $cursor->attr['content-type'])) { + $this->dumpKey($cursor); + $this->line .= $this->style('default', $cursor->attr['img-size'] ?? '', []); + $this->line .= $cursor->depth >= $this->displayOptions['maxDepth'] ? ' ' : ' '; + $this->endValue($cursor); + $this->line .= $this->indentPad; + $this->line .= sprintf('', $cursor->attr['content-type'], base64_encode($cursor->attr['img-data'])); + $this->endValue($cursor); + } else { + parent::dumpString($cursor, $str, $bin, $cut); + } + } + + /** + * {@inheritdoc} + */ + public function enterHash(Cursor $cursor, int $type, $class, bool $hasChild) + { + if (Cursor::HASH_OBJECT === $type) { + $cursor->attr['depth'] = $cursor->depth; + } parent::enterHash($cursor, $type, $class, false); - if ($cursor->skipChildren) { + if ($cursor->skipChildren || $cursor->depth >= $this->displayOptions['maxDepth']) { $cursor->skipChildren = false; $eol = ' class=sf-dump-compact>'; - } elseif ($this->expandNextHash) { + } else { $this->expandNextHash = false; $eol = ' class=sf-dump-expanded>'; - } else { - $eol = '>'; } if ($hasChild) { - $this->line .= 'line .= '%s', $v, $style, substr($v, $c + 1)); + } elseif ('note' === $style && 0 < ($attr['depth'] ?? 0) && false !== $c = strrpos($value, '\\')) { + $style .= ' title=""'; + $attr += [ + 'ellipsis' => \strlen($value) - $c, + 'ellipsis-type' => 'note', + 'ellipsis-tail' => 1, + ]; } elseif ('protected' === $style) { $style .= ' title="Protected property"'; } elseif ('meta' === $style && isset($attr['title'])) { @@ -826,23 +896,35 @@ EOHTML if (!empty($attr['ellipsis-tail'])) { $tail = \strlen(esc(substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']))); - $v .= sprintf('%s%s', substr($label, 0, $tail), substr($label, $tail)); + $v .= sprintf('%s%s', $class, substr($label, 0, $tail), substr($label, $tail)); } else { $v .= $label; } } $v = "".preg_replace_callback(static::$controlCharsRx, function ($c) use ($map) { - $s = ''; + $s = $b = ''; }, $v).''; - if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], isset($attr['line']) ? $attr['line'] : 0)) { + if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], $attr['line'] ?? 0)) { $attr['href'] = $href; } if (isset($attr['href'])) { @@ -859,12 +941,12 @@ EOHTML /** * {@inheritdoc} */ - protected function dumpLine($depth, $endOfValue = false) + protected function dumpLine(int $depth, bool $endOfValue = false) { if (-1 === $this->lastDepth) { $this->line = sprintf($this->dumpPrefix, $this->dumpId, $this->indentPad).$this->line; } - if ($this->headerIsDumped !== (null !== $this->outputStream ? $this->outputStream : $this->lineDumper)) { + if ($this->headerIsDumped !== ($this->outputStream ?? $this->lineDumper)) { $this->line = $this->getDumpHeader().$this->line; } @@ -878,7 +960,7 @@ EOHTML } $this->lastDepth = $depth; - $this->line = mb_convert_encoding($this->line, 'HTML-ENTITIES', 'UTF-8'); + $this->line = mb_encode_numericentity($this->line, [0x80, 0x10FFFF, 0, 0x1FFFFF], 'UTF-8'); if (-1 === $depth) { AbstractDumper::dumpLine(0); @@ -886,7 +968,7 @@ EOHTML AbstractDumper::dumpLine($depth); } - private function getSourceLink($file, $line) + private function getSourceLink(string $file, int $line) { $options = $this->extraDisplayOptions + $this->displayOptions; @@ -898,7 +980,7 @@ EOHTML } } -function esc($str) +function esc(string $str) { return htmlspecialchars($str, \ENT_QUOTES, 'UTF-8'); } diff --git a/lib/symfony/var-dumper/Dumper/ServerDumper.php b/lib/symfony/var-dumper/Dumper/ServerDumper.php new file mode 100644 index 0000000000..94795bf6d6 --- /dev/null +++ b/lib/symfony/var-dumper/Dumper/ServerDumper.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; +use Symfony\Component\VarDumper\Server\Connection; + +/** + * ServerDumper forwards serialized Data clones to a server. + * + * @author Maxime Steinhausser + */ +class ServerDumper implements DataDumperInterface +{ + private $connection; + private $wrappedDumper; + + /** + * @param string $host The server host + * @param DataDumperInterface|null $wrappedDumper A wrapped instance used whenever we failed contacting the server + * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name + */ + public function __construct(string $host, DataDumperInterface $wrappedDumper = null, array $contextProviders = []) + { + $this->connection = new Connection($host, $contextProviders); + $this->wrappedDumper = $wrappedDumper; + } + + public function getContextProviders(): array + { + return $this->connection->getContextProviders(); + } + + /** + * {@inheritdoc} + */ + public function dump(Data $data) + { + if (!$this->connection->write($data) && $this->wrappedDumper) { + $this->wrappedDumper->dump($data); + } + } +} diff --git a/lib/symfony/var-dumper/Exception/ThrowingCasterException.php b/lib/symfony/var-dumper/Exception/ThrowingCasterException.php index af47753ad5..122f0d358a 100644 --- a/lib/symfony/var-dumper/Exception/ThrowingCasterException.php +++ b/lib/symfony/var-dumper/Exception/ThrowingCasterException.php @@ -17,9 +17,9 @@ namespace Symfony\Component\VarDumper\Exception; class ThrowingCasterException extends \Exception { /** - * @param \Exception $prev The exception thrown from the caster + * @param \Throwable $prev The exception thrown from the caster */ - public function __construct(\Exception $prev) + public function __construct(\Throwable $prev) { parent::__construct('Unexpected '.\get_class($prev).' thrown from a caster: '.$prev->getMessage(), 0, $prev); } diff --git a/lib/symfony/var-dumper/LICENSE b/lib/symfony/var-dumper/LICENSE index 684fbf94df..a843ec124e 100644 --- a/lib/symfony/var-dumper/LICENSE +++ b/lib/symfony/var-dumper/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014-2020 Fabien Potencier +Copyright (c) 2014-2022 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/lib/symfony/var-dumper/README.md b/lib/symfony/var-dumper/README.md index 339f73eba3..a0da8c9ab3 100644 --- a/lib/symfony/var-dumper/README.md +++ b/lib/symfony/var-dumper/README.md @@ -3,13 +3,13 @@ VarDumper Component The VarDumper component provides mechanisms for walking through any arbitrary PHP variable. It provides a better `dump()` function that you can use instead -of `var_dump`. +of `var_dump()`. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/var_dumper/introduction.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) + * [Documentation](https://symfony.com/doc/current/components/var_dumper/introduction.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/lib/symfony/var-dumper/Resources/bin/var-dump-server b/lib/symfony/var-dumper/Resources/bin/var-dump-server new file mode 100644 index 0000000000..98c813a063 --- /dev/null +++ b/lib/symfony/var-dumper/Resources/bin/var-dump-server @@ -0,0 +1,63 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Starts a dump server to collect and output dumps on a single place with multiple formats support. + * + * @author Maxime Steinhausser + */ + +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Logger\ConsoleLogger; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\VarDumper\Command\ServerDumpCommand; +use Symfony\Component\VarDumper\Server\DumpServer; + +function includeIfExists(string $file): bool +{ + return file_exists($file) && include $file; +} + +if ( + !includeIfExists(__DIR__ . '/../../../../autoload.php') && + !includeIfExists(__DIR__ . '/../../vendor/autoload.php') && + !includeIfExists(__DIR__ . '/../../../../../../vendor/autoload.php') +) { + fwrite(STDERR, 'Install dependencies using Composer.'.PHP_EOL); + exit(1); +} + +if (!class_exists(Application::class)) { + fwrite(STDERR, 'You need the "symfony/console" component in order to run the VarDumper server.'.PHP_EOL); + exit(1); +} + +$input = new ArgvInput(); +$output = new ConsoleOutput(); +$defaultHost = '127.0.0.1:9912'; +$host = $input->getParameterOption(['--host'], $_SERVER['VAR_DUMPER_SERVER'] ?? $defaultHost, true); +$logger = interface_exists(LoggerInterface::class) ? new ConsoleLogger($output->getErrorOutput()) : null; + +$app = new Application(); + +$app->getDefinition()->addOption( + new InputOption('--host', null, InputOption::VALUE_REQUIRED, 'The address the server should listen to', $defaultHost) +); + +$app->add($command = new ServerDumpCommand(new DumpServer($host, $logger))) + ->getApplication() + ->setDefaultCommand($command->getName(), true) + ->run($input, $output) +; diff --git a/lib/symfony/var-dumper/Resources/css/htmlDescriptor.css b/lib/symfony/var-dumper/Resources/css/htmlDescriptor.css new file mode 100644 index 0000000000..8f706d640f --- /dev/null +++ b/lib/symfony/var-dumper/Resources/css/htmlDescriptor.css @@ -0,0 +1,130 @@ +body { + display: flex; + flex-direction: column-reverse; + justify-content: flex-end; + max-width: 1140px; + margin: auto; + padding: 15px; + word-wrap: break-word; + background-color: #F9F9F9; + color: #222; + font-family: Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.4; +} +p { + margin: 0; +} +a { + color: #218BC3; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +.text-small { + font-size: 12px !important; +} +article { + margin: 5px; + margin-bottom: 10px; +} +article > header > .row { + display: flex; + flex-direction: row; + align-items: baseline; + margin-bottom: 10px; +} +article > header > .row > .col { + flex: 1; + display: flex; + align-items: baseline; +} +article > header > .row > h2 { + font-size: 14px; + color: #222; + font-weight: normal; + font-family: "Lucida Console", monospace, sans-serif; + word-break: break-all; + margin: 20px 5px 0 0; + user-select: all; +} +article > header > .row > h2 > code { + white-space: nowrap; + user-select: none; + color: #cc2255; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; + border-radius: 3px; + margin-right: 5px; + padding: 0 3px; +} +article > header > .row > time.col { + flex: 0; + text-align: right; + white-space: nowrap; + color: #999; + font-style: italic; +} +article > header ul.tags { + list-style: none; + padding: 0; + margin: 0; + font-size: 12px; +} +article > header ul.tags > li { + user-select: all; + margin-bottom: 2px; +} +article > header ul.tags > li > span.badge { + display: inline-block; + padding: .25em .4em; + margin-right: 5px; + border-radius: 4px; + background-color: #6c757d3b; + color: #524d4d; + font-size: 12px; + text-align: center; + font-weight: 700; + line-height: 1; + white-space: nowrap; + vertical-align: baseline; + user-select: none; +} +article > section.body { + border: 1px solid #d8d8d8; + background: #FFF; + padding: 10px; + border-radius: 3px; +} +pre.sf-dump { + border-radius: 3px; + margin-bottom: 0; +} +.hidden { + display: none !important; +} +.dumped-tag > .sf-dump { + display: inline-block; + margin: 0; + padding: 1px 5px; + line-height: 1.4; + vertical-align: top; + background-color: transparent; + user-select: auto; +} +.dumped-tag > pre.sf-dump, +.dumped-tag > .sf-dump-default { + color: #CC7832; + background: none; +} +.dumped-tag > .sf-dump .sf-dump-str { color: #629755; } +.dumped-tag > .sf-dump .sf-dump-private, +.dumped-tag > .sf-dump .sf-dump-protected, +.dumped-tag > .sf-dump .sf-dump-public { color: #262626; } +.dumped-tag > .sf-dump .sf-dump-note { color: #6897BB; } +.dumped-tag > .sf-dump .sf-dump-key { color: #789339; } +.dumped-tag > .sf-dump .sf-dump-ref { color: #6E6E6E; } +.dumped-tag > .sf-dump .sf-dump-ellipsis { color: #CC7832; max-width: 100em; } +.dumped-tag > .sf-dump .sf-dump-ellipsis-path { max-width: 5em; } +.dumped-tag > .sf-dump .sf-dump-ns { user-select: none; } diff --git a/lib/symfony/var-dumper/Resources/functions/dump.php b/lib/symfony/var-dumper/Resources/functions/dump.php index 0e0e4d0435..f26aad5bdf 100644 --- a/lib/symfony/var-dumper/Resources/functions/dump.php +++ b/lib/symfony/var-dumper/Resources/functions/dump.php @@ -15,9 +15,11 @@ if (!function_exists('dump')) { /** * @author Nicolas Grekas */ - function dump($var) + function dump($var, ...$moreVars) { - foreach (func_get_args() as $v) { + VarDumper::dump($var); + + foreach ($moreVars as $v) { VarDumper::dump($v); } @@ -28,3 +30,21 @@ if (!function_exists('dump')) { return $var; } } + +if (!function_exists('dd')) { + /** + * @return never + */ + function dd(...$vars) + { + if (!in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && !headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + + foreach ($vars as $v) { + VarDumper::dump($v); + } + + exit(1); + } +} diff --git a/lib/symfony/var-dumper/Resources/js/htmlDescriptor.js b/lib/symfony/var-dumper/Resources/js/htmlDescriptor.js new file mode 100644 index 0000000000..63101e57c3 --- /dev/null +++ b/lib/symfony/var-dumper/Resources/js/htmlDescriptor.js @@ -0,0 +1,10 @@ +document.addEventListener('DOMContentLoaded', function() { + let prev = null; + Array.from(document.getElementsByTagName('article')).reverse().forEach(function (article) { + const dedupId = article.dataset.dedupId; + if (dedupId === prev) { + article.getElementsByTagName('header')[0].classList.add('hidden'); + } + prev = dedupId; + }); +}); diff --git a/lib/symfony/var-dumper/Server/Connection.php b/lib/symfony/var-dumper/Server/Connection.php new file mode 100644 index 0000000000..d0611a1f6c --- /dev/null +++ b/lib/symfony/var-dumper/Server/Connection.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Server; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; + +/** + * Forwards serialized Data clones to a server. + * + * @author Maxime Steinhausser + */ +class Connection +{ + private $host; + private $contextProviders; + + /** + * @var resource|null + */ + private $socket; + + /** + * @param string $host The server host + * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name + */ + public function __construct(string $host, array $contextProviders = []) + { + if (!str_contains($host, '://')) { + $host = 'tcp://'.$host; + } + + $this->host = $host; + $this->contextProviders = $contextProviders; + } + + public function getContextProviders(): array + { + return $this->contextProviders; + } + + public function write(Data $data): bool + { + $socketIsFresh = !$this->socket; + if (!$this->socket = $this->socket ?: $this->createSocket()) { + return false; + } + + $context = ['timestamp' => microtime(true)]; + foreach ($this->contextProviders as $name => $provider) { + $context[$name] = $provider->getContext(); + } + $context = array_filter($context); + $encodedPayload = base64_encode(serialize([$data, $context]))."\n"; + + set_error_handler([self::class, 'nullErrorHandler']); + try { + if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) { + return true; + } + if (!$socketIsFresh) { + stream_socket_shutdown($this->socket, \STREAM_SHUT_RDWR); + fclose($this->socket); + $this->socket = $this->createSocket(); + } + if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) { + return true; + } + } finally { + restore_error_handler(); + } + + return false; + } + + private static function nullErrorHandler(int $t, string $m) + { + // no-op + } + + private function createSocket() + { + set_error_handler([self::class, 'nullErrorHandler']); + try { + return stream_socket_client($this->host, $errno, $errstr, 3, \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT); + } finally { + restore_error_handler(); + } + } +} diff --git a/lib/symfony/var-dumper/Server/DumpServer.php b/lib/symfony/var-dumper/Server/DumpServer.php new file mode 100644 index 0000000000..f9735db785 --- /dev/null +++ b/lib/symfony/var-dumper/Server/DumpServer.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Server; + +use Psr\Log\LoggerInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * A server collecting Data clones sent by a ServerDumper. + * + * @author Maxime Steinhausser + * + * @final + */ +class DumpServer +{ + private $host; + private $logger; + + /** + * @var resource|null + */ + private $socket; + + public function __construct(string $host, LoggerInterface $logger = null) + { + if (!str_contains($host, '://')) { + $host = 'tcp://'.$host; + } + + $this->host = $host; + $this->logger = $logger; + } + + public function start(): void + { + if (!$this->socket = stream_socket_server($this->host, $errno, $errstr)) { + throw new \RuntimeException(sprintf('Server start failed on "%s": ', $this->host).$errstr.' '.$errno); + } + } + + public function listen(callable $callback): void + { + if (null === $this->socket) { + $this->start(); + } + + foreach ($this->getMessages() as $clientId => $message) { + if ($this->logger) { + $this->logger->info('Received a payload from client {clientId}', ['clientId' => $clientId]); + } + + $payload = @unserialize(base64_decode($message), ['allowed_classes' => [Data::class, Stub::class]]); + + // Impossible to decode the message, give up. + if (false === $payload) { + if ($this->logger) { + $this->logger->warning('Unable to decode a message from {clientId} client.', ['clientId' => $clientId]); + } + + continue; + } + + if (!\is_array($payload) || \count($payload) < 2 || !$payload[0] instanceof Data || !\is_array($payload[1])) { + if ($this->logger) { + $this->logger->warning('Invalid payload from {clientId} client. Expected an array of two elements (Data $data, array $context)', ['clientId' => $clientId]); + } + + continue; + } + + [$data, $context] = $payload; + + $callback($data, $context, $clientId); + } + } + + public function getHost(): string + { + return $this->host; + } + + private function getMessages(): iterable + { + $sockets = [(int) $this->socket => $this->socket]; + $write = []; + + while (true) { + $read = $sockets; + stream_select($read, $write, $write, null); + + foreach ($read as $stream) { + if ($this->socket === $stream) { + $stream = stream_socket_accept($this->socket); + $sockets[(int) $stream] = $stream; + } elseif (feof($stream)) { + unset($sockets[(int) $stream]); + fclose($stream); + } else { + yield (int) $stream => fgets($stream); + } + } + } + } +} diff --git a/lib/symfony/var-dumper/VarDumper.php b/lib/symfony/var-dumper/VarDumper.php index 2ef20c064b..20429ac788 100644 --- a/lib/symfony/var-dumper/VarDumper.php +++ b/lib/symfony/var-dumper/VarDumper.php @@ -11,9 +11,18 @@ namespace Symfony\Component\VarDumper; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\VarDumper\Caster\ReflectionCaster; use Symfony\Component\VarDumper\Cloner\VarCloner; use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\ContextProvider\CliContextProvider; +use Symfony\Component\VarDumper\Dumper\ContextProvider\RequestContextProvider; +use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; +use Symfony\Component\VarDumper\Dumper\ContextualizedDumper; use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\Dumper\ServerDumper; // Load the global dump() function require_once __DIR__.'/Resources/functions/dump.php'; @@ -23,26 +32,84 @@ require_once __DIR__.'/Resources/functions/dump.php'; */ class VarDumper { + /** + * @var callable|null + */ private static $handler; public static function dump($var) { if (null === self::$handler) { - $cloner = new VarCloner(); - $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? new CliDumper() : new HtmlDumper(); - self::$handler = function ($var) use ($cloner, $dumper) { - $dumper->dump($cloner->cloneVar($var)); - }; + self::register(); } - return \call_user_func(self::$handler, $var); + return (self::$handler)($var); } + /** + * @return callable|null + */ public static function setHandler(callable $callable = null) { $prevHandler = self::$handler; + + // Prevent replacing the handler with expected format as soon as the env var was set: + if (isset($_SERVER['VAR_DUMPER_FORMAT'])) { + return $prevHandler; + } + self::$handler = $callable; return $prevHandler; } + + private static function register(): void + { + $cloner = new VarCloner(); + $cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); + + $format = $_SERVER['VAR_DUMPER_FORMAT'] ?? null; + switch (true) { + case 'html' === $format: + $dumper = new HtmlDumper(); + break; + case 'cli' === $format: + $dumper = new CliDumper(); + break; + case 'server' === $format: + case $format && 'tcp' === parse_url($format, \PHP_URL_SCHEME): + $host = 'server' === $format ? $_SERVER['VAR_DUMPER_SERVER'] ?? '127.0.0.1:9912' : $format; + $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? new CliDumper() : new HtmlDumper(); + $dumper = new ServerDumper($host, $dumper, self::getDefaultContextProviders()); + break; + default: + $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? new CliDumper() : new HtmlDumper(); + } + + if (!$dumper instanceof ServerDumper) { + $dumper = new ContextualizedDumper($dumper, [new SourceContextProvider()]); + } + + self::$handler = function ($var) use ($cloner, $dumper) { + $dumper->dump($cloner->cloneVar($var)); + }; + } + + private static function getDefaultContextProviders(): array + { + $contextProviders = []; + + if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && class_exists(Request::class)) { + $requestStack = new RequestStack(); + $requestStack->push(Request::createFromGlobals()); + $contextProviders['request'] = new RequestContextProvider($requestStack); + } + + $fileLinkFormatter = class_exists(FileLinkFormatter::class) ? new FileLinkFormatter(null, $requestStack ?? null) : null; + + return $contextProviders + [ + 'cli' => new CliContextProvider(), + 'source' => new SourceContextProvider(null, null, $fileLinkFormatter), + ]; + } } diff --git a/lib/symfony/var-dumper/composer.json b/lib/symfony/var-dumper/composer.json index 9f2352ee25..dc46f58d99 100644 --- a/lib/symfony/var-dumper/composer.json +++ b/lib/symfony/var-dumper/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/var-dumper", "type": "library", - "description": "Symfony mechanism for exploring and dumping PHP variables", + "description": "Provides mechanisms for walking through any arbitrary PHP variable", "keywords": ["dump", "debug"], "homepage": "https://symfony.com", "license": "MIT", @@ -16,20 +16,25 @@ } ], "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16" }, "require-dev": { "ext-iconv": "*", - "twig/twig": "~1.34|~2.4" + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/uid": "^5.1|^6.0", + "twig/twig": "^2.13|^3.0.4" }, "conflict": { - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<4.4" }, "suggest": { "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", "ext-intl": "To show region name in time zone dump", - "ext-symfony_debug": "" + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" }, "autoload": { "files": [ "Resources/functions/dump.php" ], @@ -38,5 +43,8 @@ "/Tests/" ] }, + "bin": [ + "Resources/bin/var-dump-server" + ], "minimum-stability": "dev" } diff --git a/lib/symfony/var-dumper/phpunit.xml.dist b/lib/symfony/var-dumper/phpunit.xml.dist deleted file mode 100644 index 3243fcd027..0000000000 --- a/lib/symfony/var-dumper/phpunit.xml.dist +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - ./Tests/ - - - - - - ./ - - ./Resources - ./Tests - ./vendor - - - - diff --git a/lib/symfony/var-exporter/CHANGELOG.md b/lib/symfony/var-exporter/CHANGELOG.md new file mode 100644 index 0000000000..3406c30efb --- /dev/null +++ b/lib/symfony/var-exporter/CHANGELOG.md @@ -0,0 +1,12 @@ +CHANGELOG +========= + +5.1.0 +----- + + * added argument `array &$foundClasses` to `VarExporter::export()` to ease with preloading exported values + +4.2.0 +----- + + * added the component diff --git a/lib/symfony/var-exporter/Exception/ClassNotFoundException.php b/lib/symfony/var-exporter/Exception/ClassNotFoundException.php new file mode 100644 index 0000000000..4cebe44b0f --- /dev/null +++ b/lib/symfony/var-exporter/Exception/ClassNotFoundException.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 Symfony\Component\VarExporter\Exception; + +class ClassNotFoundException extends \Exception implements ExceptionInterface +{ + public function __construct(string $class, \Throwable $previous = null) + { + parent::__construct(sprintf('Class "%s" not found.', $class), 0, $previous); + } +} diff --git a/lib/symfony/var-exporter/Exception/ExceptionInterface.php b/lib/symfony/var-exporter/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..adfaed47c0 --- /dev/null +++ b/lib/symfony/var-exporter/Exception/ExceptionInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Exception; + +interface ExceptionInterface extends \Throwable +{ +} diff --git a/lib/symfony/var-exporter/Exception/NotInstantiableTypeException.php b/lib/symfony/var-exporter/Exception/NotInstantiableTypeException.php new file mode 100644 index 0000000000..771ee612db --- /dev/null +++ b/lib/symfony/var-exporter/Exception/NotInstantiableTypeException.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 Symfony\Component\VarExporter\Exception; + +class NotInstantiableTypeException extends \Exception implements ExceptionInterface +{ + public function __construct(string $type, \Throwable $previous = null) + { + parent::__construct(sprintf('Type "%s" is not instantiable.', $type), 0, $previous); + } +} diff --git a/lib/symfony/var-exporter/Instantiator.php b/lib/symfony/var-exporter/Instantiator.php new file mode 100644 index 0000000000..368c769ac4 --- /dev/null +++ b/lib/symfony/var-exporter/Instantiator.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Exception\ExceptionInterface; +use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\Registry; + +/** + * A utility class to create objects without calling their constructor. + * + * @author Nicolas Grekas + */ +final class Instantiator +{ + /** + * Creates an object and sets its properties without calling its constructor nor any other methods. + * + * For example: + * + * // creates an empty instance of Foo + * Instantiator::instantiate(Foo::class); + * + * // creates a Foo instance and sets one of its properties + * Instantiator::instantiate(Foo::class, ['propertyName' => $propertyValue]); + * + * // creates a Foo instance and sets a private property defined on its parent Bar class + * Instantiator::instantiate(Foo::class, [], [ + * Bar::class => ['privateBarProperty' => $propertyValue], + * ]); + * + * Instances of ArrayObject, ArrayIterator and SplObjectStorage can be created + * by using the special "\0" property name to define their internal value: + * + * // creates an SplObjectStorage where $info1 is attached to $obj1, etc. + * Instantiator::instantiate(SplObjectStorage::class, ["\0" => [$obj1, $info1, $obj2, $info2...]]); + * + * // creates an ArrayObject populated with $inputArray + * Instantiator::instantiate(ArrayObject::class, ["\0" => [$inputArray]]); + * + * @param string $class The class of the instance to create + * @param array $properties The properties to set on the instance + * @param array $privateProperties The private properties to set on the instance, + * keyed by their declaring class + * + * @throws ExceptionInterface When the instance cannot be created + */ + public static function instantiate(string $class, array $properties = [], array $privateProperties = []): object + { + $reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class); + + if (Registry::$cloneable[$class]) { + $wrappedInstance = [clone Registry::$prototypes[$class]]; + } elseif (Registry::$instantiableWithoutConstructor[$class]) { + $wrappedInstance = [$reflector->newInstanceWithoutConstructor()]; + } elseif (null === Registry::$prototypes[$class]) { + throw new NotInstantiableTypeException($class); + } elseif ($reflector->implementsInterface('Serializable') && (\PHP_VERSION_ID < 70400 || !method_exists($class, '__unserialize'))) { + $wrappedInstance = [unserialize('C:'.\strlen($class).':"'.$class.'":0:{}')]; + } else { + $wrappedInstance = [unserialize('O:'.\strlen($class).':"'.$class.'":0:{}')]; + } + + if ($properties) { + $privateProperties[$class] = isset($privateProperties[$class]) ? $properties + $privateProperties[$class] : $properties; + } + + foreach ($privateProperties as $class => $properties) { + if (!$properties) { + continue; + } + foreach ($properties as $name => $value) { + // because they're also used for "unserialization", hydrators + // deal with array of instances, so we need to wrap values + $properties[$name] = [$value]; + } + (Hydrator::$hydrators[$class] ?? Hydrator::getHydrator($class))($properties, $wrappedInstance); + } + + return $wrappedInstance[0]; + } +} diff --git a/lib/symfony/var-exporter/Internal/Exporter.php b/lib/symfony/var-exporter/Internal/Exporter.php new file mode 100644 index 0000000000..a034dddb98 --- /dev/null +++ b/lib/symfony/var-exporter/Internal/Exporter.php @@ -0,0 +1,406 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Exporter +{ + /** + * Prepares an array of values for VarExporter. + * + * For performance this method is public and has no type-hints. + * + * @param array &$values + * @param \SplObjectStorage $objectsPool + * @param array &$refsPool + * @param int &$objectsCount + * @param bool &$valuesAreStatic + * + * @throws NotInstantiableTypeException When a value cannot be serialized + */ + public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic): array + { + $refs = $values; + foreach ($values as $k => $value) { + if (\is_resource($value)) { + throw new NotInstantiableTypeException(get_resource_type($value).' resource'); + } + $refs[$k] = $objectsPool; + + if ($isRef = !$valueIsStatic = $values[$k] !== $objectsPool) { + $values[$k] = &$value; // Break hard references to make $values completely + unset($value); // independent from the original structure + $refs[$k] = $value = $values[$k]; + if ($value instanceof Reference && 0 > $value->id) { + $valuesAreStatic = false; + ++$value->count; + continue; + } + $refsPool[] = [&$refs[$k], $value, &$value]; + $refs[$k] = $values[$k] = new Reference(-\count($refsPool), $value); + } + + if (\is_array($value)) { + if ($value) { + $value = self::prepare($value, $objectsPool, $refsPool, $objectsCount, $valueIsStatic); + } + goto handle_value; + } elseif (!\is_object($value) || $value instanceof \UnitEnum) { + goto handle_value; + } + + $valueIsStatic = false; + if (isset($objectsPool[$value])) { + ++$objectsCount; + $value = new Reference($objectsPool[$value][0]); + goto handle_value; + } + + $class = \get_class($value); + $reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class); + + if ($reflector->hasMethod('__serialize')) { + if (!$reflector->getMethod('__serialize')->isPublic()) { + throw new \Error(sprintf('Call to %s method "%s::__serialize()".', $reflector->getMethod('__serialize')->isProtected() ? 'protected' : 'private', $class)); + } + + if (!\is_array($properties = $value->__serialize())) { + throw new \TypeError($class.'::__serialize() must return an array'); + } + + goto prepare_value; + } + + $properties = []; + $sleep = null; + $proto = Registry::$prototypes[$class]; + + if (($value instanceof \ArrayIterator || $value instanceof \ArrayObject) && null !== $proto) { + // ArrayIterator and ArrayObject need special care because their "flags" + // option changes the behavior of the (array) casting operator. + [$arrayValue, $properties] = self::getArrayObjectProperties($value, $proto); + + // populates Registry::$prototypes[$class] with a new instance + Registry::getClassReflector($class, Registry::$instantiableWithoutConstructor[$class], Registry::$cloneable[$class]); + } elseif ($value instanceof \SplObjectStorage && Registry::$cloneable[$class] && null !== $proto) { + // By implementing Serializable, SplObjectStorage breaks + // internal references; let's deal with it on our own. + foreach (clone $value as $v) { + $properties[] = $v; + $properties[] = $value[$v]; + } + $properties = ['SplObjectStorage' => ["\0" => $properties]]; + $arrayValue = (array) $value; + } elseif ($value instanceof \Serializable + || $value instanceof \__PHP_Incomplete_Class + || \PHP_VERSION_ID < 80200 && $value instanceof \DatePeriod + ) { + ++$objectsCount; + $objectsPool[$value] = [$id = \count($objectsPool), serialize($value), [], 0]; + $value = new Reference($id); + goto handle_value; + } else { + if (method_exists($class, '__sleep')) { + if (!\is_array($sleep = $value->__sleep())) { + trigger_error('serialize(): __sleep should return an array only containing the names of instance-variables to serialize', \E_USER_NOTICE); + $value = null; + goto handle_value; + } + $sleep = array_flip($sleep); + } + + $arrayValue = (array) $value; + } + + $proto = (array) $proto; + + foreach ($arrayValue as $name => $v) { + $i = 0; + $n = (string) $name; + if ('' === $n || "\0" !== $n[0]) { + $c = \PHP_VERSION_ID >= 80100 && $reflector->hasProperty($n) && ($p = $reflector->getProperty($n))->isReadOnly() ? $p->class : 'stdClass'; + } elseif ('*' === $n[1]) { + $n = substr($n, 3); + $c = $reflector->getProperty($n)->class; + if ('Error' === $c) { + $c = 'TypeError'; + } elseif ('Exception' === $c) { + $c = 'ErrorException'; + } + } else { + $i = strpos($n, "\0", 2); + $c = substr($n, 1, $i - 1); + $n = substr($n, 1 + $i); + } + if (null !== $sleep) { + if (!isset($sleep[$n]) || ($i && $c !== $class)) { + continue; + } + $sleep[$n] = false; + } + if (!\array_key_exists($name, $proto) || $proto[$name] !== $v || "\x00Error\x00trace" === $name || "\x00Exception\x00trace" === $name) { + $properties[$c][$n] = $v; + } + } + if ($sleep) { + foreach ($sleep as $n => $v) { + if (false !== $v) { + trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $n), \E_USER_NOTICE); + } + } + } + + prepare_value: + $objectsPool[$value] = [$id = \count($objectsPool)]; + $properties = self::prepare($properties, $objectsPool, $refsPool, $objectsCount, $valueIsStatic); + ++$objectsCount; + $objectsPool[$value] = [$id, $class, $properties, method_exists($class, '__unserialize') ? -$objectsCount : (method_exists($class, '__wakeup') ? $objectsCount : 0)]; + + $value = new Reference($id); + + handle_value: + if ($isRef) { + unset($value); // Break the hard reference created above + } elseif (!$valueIsStatic) { + $values[$k] = $value; + } + $valuesAreStatic = $valueIsStatic && $valuesAreStatic; + } + + return $values; + } + + public static function export($value, string $indent = '') + { + switch (true) { + case \is_int($value) || \is_float($value): return var_export($value, true); + case [] === $value: return '[]'; + case false === $value: return 'false'; + case true === $value: return 'true'; + case null === $value: return 'null'; + case '' === $value: return "''"; + case $value instanceof \UnitEnum: return ltrim(var_export($value, true), '\\'); + } + + if ($value instanceof Reference) { + if (0 <= $value->id) { + return '$o['.$value->id.']'; + } + if (!$value->count) { + return self::export($value->value, $indent); + } + $value = -$value->id; + + return '&$r['.$value.']'; + } + $subIndent = $indent.' '; + + if (\is_string($value)) { + $code = sprintf("'%s'", addcslashes($value, "'\\")); + + $code = preg_replace_callback("/((?:[\\0\\r\\n]|\u{202A}|\u{202B}|\u{202D}|\u{202E}|\u{2066}|\u{2067}|\u{2068}|\u{202C}|\u{2069})++)(.)/", function ($m) use ($subIndent) { + $m[1] = sprintf('\'."%s".\'', str_replace( + ["\0", "\r", "\n", "\u{202A}", "\u{202B}", "\u{202D}", "\u{202E}", "\u{2066}", "\u{2067}", "\u{2068}", "\u{202C}", "\u{2069}", '\n\\'], + ['\0', '\r', '\n', '\u{202A}', '\u{202B}', '\u{202D}', '\u{202E}', '\u{2066}', '\u{2067}', '\u{2068}', '\u{202C}', '\u{2069}', '\n"'."\n".$subIndent.'."\\'], + $m[1] + )); + + if ("'" === $m[2]) { + return substr($m[1], 0, -2); + } + + if ('n".\'' === substr($m[1], -4)) { + return substr_replace($m[1], "\n".$subIndent.".'".$m[2], -2); + } + + return $m[1].$m[2]; + }, $code, -1, $count); + + if ($count && str_starts_with($code, "''.")) { + $code = substr($code, 3); + } + + return $code; + } + + if (\is_array($value)) { + $j = -1; + $code = ''; + foreach ($value as $k => $v) { + $code .= $subIndent; + if (!\is_int($k) || 1 !== $k - $j) { + $code .= self::export($k, $subIndent).' => '; + } + if (\is_int($k) && $k > $j) { + $j = $k; + } + $code .= self::export($v, $subIndent).",\n"; + } + + return "[\n".$code.$indent.']'; + } + + if ($value instanceof Values) { + $code = $subIndent."\$r = [],\n"; + foreach ($value->values as $k => $v) { + $code .= $subIndent.'$r['.$k.'] = '.self::export($v, $subIndent).",\n"; + } + + return "[\n".$code.$indent.']'; + } + + if ($value instanceof Registry) { + return self::exportRegistry($value, $indent, $subIndent); + } + + if ($value instanceof Hydrator) { + return self::exportHydrator($value, $indent, $subIndent); + } + + throw new \UnexpectedValueException(sprintf('Cannot export value of type "%s".', get_debug_type($value))); + } + + private static function exportRegistry(Registry $value, string $indent, string $subIndent): string + { + $code = ''; + $serializables = []; + $seen = []; + $prototypesAccess = 0; + $factoriesAccess = 0; + $r = '\\'.Registry::class; + $j = -1; + + foreach ($value->classes as $k => $class) { + if (':' === ($class[1] ?? null)) { + $serializables[$k] = $class; + continue; + } + if (!Registry::$instantiableWithoutConstructor[$class]) { + if (is_subclass_of($class, 'Serializable') && !method_exists($class, '__unserialize')) { + $serializables[$k] = 'C:'.\strlen($class).':"'.$class.'":0:{}'; + } else { + $serializables[$k] = 'O:'.\strlen($class).':"'.$class.'":0:{}'; + } + if (is_subclass_of($class, 'Throwable')) { + $eol = is_subclass_of($class, 'Error') ? "\0Error\0" : "\0Exception\0"; + $serializables[$k] = substr_replace($serializables[$k], '1:{s:'.(5 + \strlen($eol)).':"'.$eol.'trace";a:0:{}}', -4); + } + continue; + } + $code .= $subIndent.(1 !== $k - $j ? $k.' => ' : ''); + $j = $k; + $eol = ",\n"; + $c = '['.self::export($class).']'; + + if ($seen[$class] ?? false) { + if (Registry::$cloneable[$class]) { + ++$prototypesAccess; + $code .= 'clone $p'.$c; + } else { + ++$factoriesAccess; + $code .= '$f'.$c.'()'; + } + } else { + $seen[$class] = true; + if (Registry::$cloneable[$class]) { + $code .= 'clone ('.($prototypesAccess++ ? '$p' : '($p = &'.$r.'::$prototypes)').$c.' ?? '.$r.'::p'; + } else { + $code .= '('.($factoriesAccess++ ? '$f' : '($f = &'.$r.'::$factories)').$c.' ?? '.$r.'::f'; + $eol = '()'.$eol; + } + $code .= '('.substr($c, 1, -1).'))'; + } + $code .= $eol; + } + + if (1 === $prototypesAccess) { + $code = str_replace('($p = &'.$r.'::$prototypes)', $r.'::$prototypes', $code); + } + if (1 === $factoriesAccess) { + $code = str_replace('($f = &'.$r.'::$factories)', $r.'::$factories', $code); + } + if ('' !== $code) { + $code = "\n".$code.$indent; + } + + if ($serializables) { + $code = $r.'::unserialize(['.$code.'], '.self::export($serializables, $indent).')'; + } else { + $code = '['.$code.']'; + } + + return '$o = '.$code; + } + + private static function exportHydrator(Hydrator $value, string $indent, string $subIndent): string + { + $code = ''; + foreach ($value->properties as $class => $properties) { + $code .= $subIndent.' '.self::export($class).' => '.self::export($properties, $subIndent.' ').",\n"; + } + + $code = [ + self::export($value->registry, $subIndent), + self::export($value->values, $subIndent), + '' !== $code ? "[\n".$code.$subIndent.']' : '[]', + self::export($value->value, $subIndent), + self::export($value->wakeups, $subIndent), + ]; + + return '\\'.\get_class($value)."::hydrate(\n".$subIndent.implode(",\n".$subIndent, $code)."\n".$indent.')'; + } + + /** + * @param \ArrayIterator|\ArrayObject $value + * @param \ArrayIterator|\ArrayObject $proto + */ + private static function getArrayObjectProperties($value, $proto): array + { + $reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject'; + $reflector = Registry::$reflectors[$reflector] ?? Registry::getClassReflector($reflector); + + $properties = [ + $arrayValue = (array) $value, + $reflector->getMethod('getFlags')->invoke($value), + $value instanceof \ArrayObject ? $reflector->getMethod('getIteratorClass')->invoke($value) : 'ArrayIterator', + ]; + + $reflector = $reflector->getMethod('setFlags'); + $reflector->invoke($proto, \ArrayObject::STD_PROP_LIST); + + if ($properties[1] & \ArrayObject::STD_PROP_LIST) { + $reflector->invoke($value, 0); + $properties[0] = (array) $value; + } else { + $reflector->invoke($value, \ArrayObject::STD_PROP_LIST); + $arrayValue = (array) $value; + } + $reflector->invoke($value, $properties[1]); + + if ([[], 0, 'ArrayIterator'] === $properties) { + $properties = []; + } else { + if ('ArrayIterator' === $properties[2]) { + unset($properties[2]); + } + $properties = [$reflector->class => ["\0" => $properties]]; + } + + return [$arrayValue, $properties]; + } +} diff --git a/lib/symfony/var-exporter/Internal/Hydrator.php b/lib/symfony/var-exporter/Internal/Hydrator.php new file mode 100644 index 0000000000..5ed6bdc948 --- /dev/null +++ b/lib/symfony/var-exporter/Internal/Hydrator.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Exception\ClassNotFoundException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Hydrator +{ + public static $hydrators = []; + + public $registry; + public $values; + public $properties; + public $value; + public $wakeups; + + public function __construct(?Registry $registry, ?Values $values, array $properties, $value, array $wakeups) + { + $this->registry = $registry; + $this->values = $values; + $this->properties = $properties; + $this->value = $value; + $this->wakeups = $wakeups; + } + + public static function hydrate($objects, $values, $properties, $value, $wakeups) + { + foreach ($properties as $class => $vars) { + (self::$hydrators[$class] ?? self::getHydrator($class))($vars, $objects); + } + foreach ($wakeups as $k => $v) { + if (\is_array($v)) { + $objects[-$k]->__unserialize($v); + } else { + $objects[$v]->__wakeup(); + } + } + + return $value; + } + + public static function getHydrator($class) + { + switch ($class) { + case 'stdClass': + return self::$hydrators[$class] = static function ($properties, $objects) { + foreach ($properties as $name => $values) { + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + + case 'ErrorException': + return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \ErrorException { + }); + + case 'TypeError': + return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \Error { + }); + + case 'SplObjectStorage': + return self::$hydrators[$class] = static function ($properties, $objects) { + foreach ($properties as $name => $values) { + if ("\0" === $name) { + foreach ($values as $i => $v) { + for ($j = 0; $j < \count($v); ++$j) { + $objects[$i]->attach($v[$j], $v[++$j]); + } + } + continue; + } + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + } + + if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { + throw new ClassNotFoundException($class); + } + $classReflector = new \ReflectionClass($class); + + switch ($class) { + case 'ArrayIterator': + case 'ArrayObject': + $constructor = \Closure::fromCallable([$classReflector->getConstructor(), 'invokeArgs']); + + return self::$hydrators[$class] = static function ($properties, $objects) use ($constructor) { + foreach ($properties as $name => $values) { + if ("\0" !== $name) { + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + } + foreach ($properties["\0"] ?? [] as $i => $v) { + $constructor($objects[$i], $v); + } + }; + } + + if (!$classReflector->isInternal()) { + return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, $class); + } + + if ($classReflector->name !== $class) { + return self::$hydrators[$classReflector->name] ?? self::getHydrator($classReflector->name); + } + + $propertySetters = []; + foreach ($classReflector->getProperties() as $propertyReflector) { + if (!$propertyReflector->isStatic()) { + $propertyReflector->setAccessible(true); + $propertySetters[$propertyReflector->name] = \Closure::fromCallable([$propertyReflector, 'setValue']); + } + } + + if (!$propertySetters) { + return self::$hydrators[$class] = self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'); + } + + return self::$hydrators[$class] = static function ($properties, $objects) use ($propertySetters) { + foreach ($properties as $name => $values) { + if ($setValue = $propertySetters[$name] ?? null) { + foreach ($values as $i => $v) { + $setValue($objects[$i], $v); + } + continue; + } + foreach ($values as $i => $v) { + $objects[$i]->$name = $v; + } + } + }; + } +} diff --git a/lib/symfony/var-exporter/Internal/Reference.php b/lib/symfony/var-exporter/Internal/Reference.php new file mode 100644 index 0000000000..e371c07b88 --- /dev/null +++ b/lib/symfony/var-exporter/Internal/Reference.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Reference +{ + public $id; + public $value; + public $count = 0; + + public function __construct(int $id, $value = null) + { + $this->id = $id; + $this->value = $value; + } +} diff --git a/lib/symfony/var-exporter/Internal/Registry.php b/lib/symfony/var-exporter/Internal/Registry.php new file mode 100644 index 0000000000..24b77b9ef6 --- /dev/null +++ b/lib/symfony/var-exporter/Internal/Registry.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +use Symfony\Component\VarExporter\Exception\ClassNotFoundException; +use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Registry +{ + public static $reflectors = []; + public static $prototypes = []; + public static $factories = []; + public static $cloneable = []; + public static $instantiableWithoutConstructor = []; + + public $classes = []; + + public function __construct(array $classes) + { + $this->classes = $classes; + } + + public static function unserialize($objects, $serializables) + { + $unserializeCallback = ini_set('unserialize_callback_func', __CLASS__.'::getClassReflector'); + + try { + foreach ($serializables as $k => $v) { + $objects[$k] = unserialize($v); + } + } finally { + ini_set('unserialize_callback_func', $unserializeCallback); + } + + return $objects; + } + + public static function p($class) + { + self::getClassReflector($class, true, true); + + return self::$prototypes[$class]; + } + + public static function f($class) + { + $reflector = self::$reflectors[$class] ?? self::getClassReflector($class, true, false); + + return self::$factories[$class] = \Closure::fromCallable([$reflector, 'newInstanceWithoutConstructor']); + } + + public static function getClassReflector($class, $instantiableWithoutConstructor = false, $cloneable = null) + { + if (!($isClass = class_exists($class)) && !interface_exists($class, false) && !trait_exists($class, false)) { + throw new ClassNotFoundException($class); + } + $reflector = new \ReflectionClass($class); + + if ($instantiableWithoutConstructor) { + $proto = $reflector->newInstanceWithoutConstructor(); + } elseif (!$isClass || $reflector->isAbstract()) { + throw new NotInstantiableTypeException($class); + } elseif ($reflector->name !== $class) { + $reflector = self::$reflectors[$name = $reflector->name] ?? self::getClassReflector($name, false, $cloneable); + self::$cloneable[$class] = self::$cloneable[$name]; + self::$instantiableWithoutConstructor[$class] = self::$instantiableWithoutConstructor[$name]; + self::$prototypes[$class] = self::$prototypes[$name]; + + return self::$reflectors[$class] = $reflector; + } else { + try { + $proto = $reflector->newInstanceWithoutConstructor(); + $instantiableWithoutConstructor = true; + } catch (\ReflectionException $e) { + $proto = $reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize') ? 'C:' : 'O:'; + if ('C:' === $proto && !$reflector->getMethod('unserialize')->isInternal()) { + $proto = null; + } else { + try { + $proto = @unserialize($proto.\strlen($class).':"'.$class.'":0:{}'); + } catch (\Exception $e) { + if (__FILE__ !== $e->getFile()) { + throw $e; + } + throw new NotInstantiableTypeException($class, $e); + } + if (false === $proto) { + throw new NotInstantiableTypeException($class); + } + } + } + if (null !== $proto && !$proto instanceof \Throwable && !$proto instanceof \Serializable && !method_exists($class, '__sleep') && (\PHP_VERSION_ID < 70400 || !method_exists($class, '__serialize'))) { + try { + serialize($proto); + } catch (\Exception $e) { + throw new NotInstantiableTypeException($class, $e); + } + } + } + + if (null === $cloneable) { + if (($proto instanceof \Reflector || $proto instanceof \ReflectionGenerator || $proto instanceof \ReflectionType || $proto instanceof \IteratorIterator || $proto instanceof \RecursiveIteratorIterator) && (!$proto instanceof \Serializable && !method_exists($proto, '__wakeup') && (\PHP_VERSION_ID < 70400 || !method_exists($class, '__unserialize')))) { + throw new NotInstantiableTypeException($class); + } + + $cloneable = $reflector->isCloneable() && !$reflector->hasMethod('__clone'); + } + + self::$cloneable[$class] = $cloneable; + self::$instantiableWithoutConstructor[$class] = $instantiableWithoutConstructor; + self::$prototypes[$class] = $proto; + + if ($proto instanceof \Throwable) { + static $setTrace; + + if (null === $setTrace) { + $setTrace = [ + new \ReflectionProperty(\Error::class, 'trace'), + new \ReflectionProperty(\Exception::class, 'trace'), + ]; + $setTrace[0]->setAccessible(true); + $setTrace[1]->setAccessible(true); + $setTrace[0] = \Closure::fromCallable([$setTrace[0], 'setValue']); + $setTrace[1] = \Closure::fromCallable([$setTrace[1], 'setValue']); + } + + $setTrace[$proto instanceof \Exception]($proto, []); + } + + return self::$reflectors[$class] = $reflector; + } +} diff --git a/lib/symfony/var-exporter/Internal/Values.php b/lib/symfony/var-exporter/Internal/Values.php new file mode 100644 index 0000000000..21ae04e68b --- /dev/null +++ b/lib/symfony/var-exporter/Internal/Values.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter\Internal; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class Values +{ + public $values; + + public function __construct(array $values) + { + $this->values = $values; + } +} diff --git a/lib/symfony/var-exporter/LICENSE b/lib/symfony/var-exporter/LICENSE new file mode 100644 index 0000000000..74cdc2dbf6 --- /dev/null +++ b/lib/symfony/var-exporter/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2022 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/lib/symfony/var-exporter/README.md b/lib/symfony/var-exporter/README.md new file mode 100644 index 0000000000..a34e4c23d7 --- /dev/null +++ b/lib/symfony/var-exporter/README.md @@ -0,0 +1,38 @@ +VarExporter Component +===================== + +The VarExporter component allows exporting any serializable PHP data structure to +plain PHP code. While doing so, it preserves all the semantics associated with +the serialization mechanism of PHP (`__wakeup`, `__sleep`, `Serializable`, +`__serialize`, `__unserialize`). + +It also provides an instantiator that allows creating and populating objects +without calling their constructor nor any other methods. + +The reason to use this component *vs* `serialize()` or +[igbinary](https://github.com/igbinary/igbinary) is performance: thanks to +OPcache, the resulting code is significantly faster and more memory efficient +than using `unserialize()` or `igbinary_unserialize()`. + +Unlike `var_export()`, this works on any serializable PHP value. + +It also provides a few improvements over `var_export()`/`serialize()`: + + * the output is PSR-2 compatible; + * the output can be re-indented without messing up with `\r` or `\n` in the data + * missing classes throw a `ClassNotFoundException` instead of being unserialized to + `PHP_Incomplete_Class` objects; + * references involving `SplObjectStorage`, `ArrayObject` or `ArrayIterator` + instances are preserved; + * `Reflection*`, `IteratorIterator` and `RecursiveIteratorIterator` classes + throw an exception when being serialized (their unserialized version is broken + anyway, see https://bugs.php.net/76737). + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/var_exporter.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/lib/symfony/var-exporter/VarExporter.php b/lib/symfony/var-exporter/VarExporter.php new file mode 100644 index 0000000000..003388e798 --- /dev/null +++ b/lib/symfony/var-exporter/VarExporter.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarExporter; + +use Symfony\Component\VarExporter\Exception\ExceptionInterface; +use Symfony\Component\VarExporter\Internal\Exporter; +use Symfony\Component\VarExporter\Internal\Hydrator; +use Symfony\Component\VarExporter\Internal\Registry; +use Symfony\Component\VarExporter\Internal\Values; + +/** + * Exports serializable PHP values to PHP code. + * + * VarExporter allows serializing PHP data structures to plain PHP code (like var_export()) + * while preserving all the semantics associated with serialize() (unlike var_export()). + * + * By leveraging OPcache, the generated PHP code is faster than doing the same with unserialize(). + * + * @author Nicolas Grekas + */ +final class VarExporter +{ + /** + * Exports a serializable PHP value to PHP code. + * + * @param mixed $value The value to export + * @param bool &$isStaticValue Set to true after execution if the provided value is static, false otherwise + * @param bool &$classes Classes found in the value are added to this list as both keys and values + * + * @throws ExceptionInterface When the provided value cannot be serialized + */ + public static function export($value, bool &$isStaticValue = null, array &$foundClasses = []): string + { + $isStaticValue = true; + + if (!\is_object($value) && !(\is_array($value) && $value) && !\is_resource($value) || $value instanceof \UnitEnum) { + return Exporter::export($value); + } + + $objectsPool = new \SplObjectStorage(); + $refsPool = []; + $objectsCount = 0; + + try { + $value = Exporter::prepare([$value], $objectsPool, $refsPool, $objectsCount, $isStaticValue)[0]; + } finally { + $references = []; + foreach ($refsPool as $i => $v) { + if ($v[0]->count) { + $references[1 + $i] = $v[2]; + } + $v[0] = $v[1]; + } + } + + if ($isStaticValue) { + return Exporter::export($value); + } + + $classes = []; + $values = []; + $states = []; + foreach ($objectsPool as $i => $v) { + [, $class, $values[], $wakeup] = $objectsPool[$v]; + $foundClasses[$class] = $classes[] = $class; + + if (0 < $wakeup) { + $states[$wakeup] = $i; + } elseif (0 > $wakeup) { + $states[-$wakeup] = [$i, array_pop($values)]; + $values[] = []; + } + } + ksort($states); + + $wakeups = [null]; + foreach ($states as $k => $v) { + if (\is_array($v)) { + $wakeups[-$v[0]] = $v[1]; + } else { + $wakeups[] = $v; + } + } + + if (null === $wakeups[0]) { + unset($wakeups[0]); + } + + $properties = []; + foreach ($values as $i => $vars) { + foreach ($vars as $class => $values) { + foreach ($values as $name => $v) { + $properties[$class][$name][$i] = $v; + } + } + } + + if ($classes || $references) { + $value = new Hydrator(new Registry($classes), $references ? new Values($references) : null, $properties, $value, $wakeups); + } else { + $isStaticValue = true; + } + + return Exporter::export($value); + } +} diff --git a/lib/symfony/var-exporter/composer.json b/lib/symfony/var-exporter/composer.json new file mode 100644 index 0000000000..29d4901d37 --- /dev/null +++ b/lib/symfony/var-exporter/composer.json @@ -0,0 +1,32 @@ +{ + "name": "symfony/var-exporter", + "type": "library", + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "keywords": ["export", "serialize", "instantiate", "hydrate", "construct", "clone"], + "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.5", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/var-dumper": "^4.4.9|^5.0.9|^6.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\VarExporter\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/lib/symfony/web-profiler-bundle/.gitignore b/lib/symfony/web-profiler-bundle/.gitignore deleted file mode 100644 index c49a5d8df5..0000000000 --- a/lib/symfony/web-profiler-bundle/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/lib/symfony/web-profiler-bundle/CHANGELOG.md b/lib/symfony/web-profiler-bundle/CHANGELOG.md index c1259b0a20..f0974a6ed9 100644 --- a/lib/symfony/web-profiler-bundle/CHANGELOG.md +++ b/lib/symfony/web-profiler-bundle/CHANGELOG.md @@ -1,6 +1,54 @@ CHANGELOG ========= +5.4 +--- + + * Add a "preview" tab in mailer profiler for HTML email + +5.2.0 +----- + + * added session usage + +5.0.0 +----- + + * removed the `ExceptionController`, use `ExceptionPanelController` instead + * removed the `TemplateManager::templateExists()` method + +4.4.0 +----- + + * added support for the Mailer component + * added support for the HttpClient component + * added button to clear the ajax request tab + * deprecated the `ExceptionController::templateExists()` method + * deprecated the `TemplateManager::templateExists()` method + * deprecated the `ExceptionController` in favor of `ExceptionPanelController` + * marked all classes of the WebProfilerBundle as internal + * added a section with the stamps of a message after it is dispatched in the Messenger panel + +4.3.0 +----- + + * Replaced the canvas performance graph renderer with an SVG renderer + +4.1.0 +----- + + * added information about orphaned events + * made the toolbar auto-update with info from ajax reponses when they set the + `Symfony-Debug-Toolbar-Replace header` to `1` + +4.0.0 +----- + + * removed the `WebProfilerExtension::dumpValue()` method + * removed the `getTemplates()` method of the `TemplateManager` class in favor of the ``getNames()`` method + * removed the `web_profiler.position` config option and the + `web_profiler.debug_toolbar.position` container parameter + 3.4.0 ----- diff --git a/lib/symfony/web-profiler-bundle/Controller/ExceptionController.php b/lib/symfony/web-profiler-bundle/Controller/ExceptionController.php deleted file mode 100644 index e82144b874..0000000000 --- a/lib/symfony/web-profiler-bundle/Controller/ExceptionController.php +++ /dev/null @@ -1,140 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\WebProfilerBundle\Controller; - -use Symfony\Component\Debug\ExceptionHandler; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\HttpKernel\Profiler\Profiler; -use Twig\Environment; -use Twig\Error\LoaderError; -use Twig\Loader\ExistsLoaderInterface; -use Twig\Loader\SourceContextLoaderInterface; - -/** - * ExceptionController. - * - * @author Fabien Potencier - */ -class ExceptionController -{ - protected $twig; - protected $debug; - protected $profiler; - private $fileLinkFormat; - - public function __construct(Profiler $profiler = null, Environment $twig, $debug, FileLinkFormatter $fileLinkFormat = null) - { - $this->profiler = $profiler; - $this->twig = $twig; - $this->debug = $debug; - $this->fileLinkFormat = $fileLinkFormat; - } - - /** - * Renders the exception panel for the given token. - * - * @param string $token The profiler token - * - * @return Response A Response instance - * - * @throws NotFoundHttpException - */ - public function showAction($token) - { - if (null === $this->profiler) { - throw new NotFoundHttpException('The profiler must be enabled.'); - } - - $this->profiler->disable(); - - $exception = $this->profiler->loadProfile($token)->getCollector('exception')->getException(); - $template = $this->getTemplate(); - - if (!$this->templateExists($template)) { - $handler = new ExceptionHandler($this->debug, $this->twig->getCharset(), $this->fileLinkFormat); - - return new Response($handler->getContent($exception), 200, ['Content-Type' => 'text/html']); - } - - $code = $exception->getStatusCode(); - - return new Response($this->twig->render( - $template, - [ - 'status_code' => $code, - 'status_text' => Response::$statusTexts[$code], - 'exception' => $exception, - 'logger' => null, - 'currentContent' => '', - ] - ), 200, ['Content-Type' => 'text/html']); - } - - /** - * Renders the exception panel stylesheet for the given token. - * - * @param string $token The profiler token - * - * @return Response A Response instance - * - * @throws NotFoundHttpException - */ - public function cssAction($token) - { - if (null === $this->profiler) { - throw new NotFoundHttpException('The profiler must be enabled.'); - } - - $this->profiler->disable(); - - $exception = $this->profiler->loadProfile($token)->getCollector('exception')->getException(); - $template = $this->getTemplate(); - - if (!$this->templateExists($template)) { - $handler = new ExceptionHandler($this->debug, $this->twig->getCharset(), $this->fileLinkFormat); - - return new Response($handler->getStylesheet($exception), 200, ['Content-Type' => 'text/css']); - } - - return new Response($this->twig->render('@WebProfiler/Collector/exception.css.twig'), 200, ['Content-Type' => 'text/css']); - } - - protected function getTemplate() - { - return '@Twig/Exception/'.($this->debug ? 'exception' : 'error').'.html.twig'; - } - - // to be removed when the minimum required version of Twig is >= 2.0 - protected function templateExists($template) - { - $loader = $this->twig->getLoader(); - - if (1 === Environment::MAJOR_VERSION && !$loader instanceof ExistsLoaderInterface) { - try { - if ($loader instanceof SourceContextLoaderInterface) { - $loader->getSourceContext($template); - } else { - $loader->getSource($template); - } - - return true; - } catch (LoaderError $e) { - } - - return false; - } - - return $loader->exists($template); - } -} diff --git a/lib/symfony/web-profiler-bundle/Controller/ExceptionPanelController.php b/lib/symfony/web-profiler-bundle/Controller/ExceptionPanelController.php new file mode 100644 index 0000000000..4941208c88 --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Controller/ExceptionPanelController.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\WebProfilerBundle\Controller; + +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Profiler\Profiler; + +/** + * Renders the exception panel. + * + * @author Yonel Ceruto + * + * @internal + */ +class ExceptionPanelController +{ + private $errorRenderer; + private $profiler; + + public function __construct(HtmlErrorRenderer $errorRenderer, Profiler $profiler = null) + { + $this->errorRenderer = $errorRenderer; + $this->profiler = $profiler; + } + + /** + * Renders the exception panel stacktrace for the given token. + */ + public function body(string $token): Response + { + if (null === $this->profiler) { + throw new NotFoundHttpException('The profiler must be enabled.'); + } + + $exception = $this->profiler->loadProfile($token) + ->getCollector('exception') + ->getException() + ; + + return new Response($this->errorRenderer->getBody($exception), 200, ['Content-Type' => 'text/html']); + } + + /** + * Renders the exception panel stylesheet. + */ + public function stylesheet(): Response + { + return new Response($this->errorRenderer->getStylesheet(), 200, ['Content-Type' => 'text/css']); + } +} diff --git a/lib/symfony/web-profiler-bundle/Controller/ProfilerController.php b/lib/symfony/web-profiler-bundle/Controller/ProfilerController.php index 8021451601..2ad7df3292 100644 --- a/lib/symfony/web-profiler-bundle/Controller/ProfilerController.php +++ b/lib/symfony/web-profiler-bundle/Controller/ProfilerController.php @@ -11,12 +11,15 @@ namespace Symfony\Bundle\WebProfilerBundle\Controller; +use Symfony\Bundle\FullStack; use Symfony\Bundle\WebProfilerBundle\Csp\ContentSecurityPolicyHandler; use Symfony\Bundle\WebProfilerBundle\Profiler\TemplateManager; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag; +use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector; +use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Profiler\Profiler; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -24,6 +27,8 @@ use Twig\Environment; /** * @author Fabien Potencier + * + * @internal */ class ProfilerController { @@ -32,26 +37,15 @@ class ProfilerController private $profiler; private $twig; private $templates; - private $toolbarPosition; private $cspHandler; private $baseDir; - /** - * @param UrlGeneratorInterface $generator The URL Generator - * @param Profiler $profiler The profiler - * @param Environment $twig The twig environment - * @param array $templates The templates - * @param string $toolbarPosition The toolbar position (top, bottom, normal, or null -- use the configuration) - * @param ContentSecurityPolicyHandler $cspHandler The Content-Security-Policy handler - * @param string $baseDir The project root directory - */ - public function __construct(UrlGeneratorInterface $generator, Profiler $profiler = null, Environment $twig, array $templates, $toolbarPosition = 'bottom', ContentSecurityPolicyHandler $cspHandler = null, $baseDir = null) + public function __construct(UrlGeneratorInterface $generator, Profiler $profiler = null, Environment $twig, array $templates, ContentSecurityPolicyHandler $cspHandler = null, string $baseDir = null) { $this->generator = $generator; $this->profiler = $profiler; $this->twig = $twig; $this->templates = $templates; - $this->toolbarPosition = $toolbarPosition; $this->cspHandler = $cspHandler; $this->baseDir = $baseDir; } @@ -59,17 +53,11 @@ class ProfilerController /** * Redirects to the last profiles. * - * @return RedirectResponse A RedirectResponse instance - * * @throws NotFoundHttpException */ - public function homeAction() + public function homeAction(): RedirectResponse { - if (null === $this->profiler) { - throw new NotFoundHttpException('The profiler must be enabled.'); - } - - $this->profiler->disable(); + $this->denyAccessIfProfilerDisabled(); return new RedirectResponse($this->generator->generate('_profiler_search_results', ['token' => 'empty', 'limit' => 10]), 302, ['Content-Type' => 'text/html']); } @@ -77,26 +65,17 @@ class ProfilerController /** * Renders a profiler panel for the given token. * - * @param Request $request The current HTTP request - * @param string $token The profiler token - * - * @return Response A Response instance - * * @throws NotFoundHttpException */ - public function panelAction(Request $request, $token) + public function panelAction(Request $request, string $token): Response { - if (null === $this->profiler) { - throw new NotFoundHttpException('The profiler must be enabled.'); - } - - $this->profiler->disable(); + $this->denyAccessIfProfilerDisabled(); if (null !== $this->cspHandler) { $this->cspHandler->disableCsp(); } - $panel = $request->query->get('panel', 'request'); + $panel = $request->query->get('panel'); $page = $request->query->get('page', 'home'); if ('latest' === $token && $latest = current($this->profiler->find(null, null, 1, null, null, null))) { @@ -104,14 +83,30 @@ class ProfilerController } if (!$profile = $this->profiler->loadProfile($token)) { - return new Response($this->twig->render('@WebProfiler/Profiler/info.html.twig', ['about' => 'no_token', 'token' => $token, 'request' => $request]), 200, ['Content-Type' => 'text/html']); + return $this->renderWithCspNonces($request, '@WebProfiler/Profiler/info.html.twig', ['about' => 'no_token', 'token' => $token, 'request' => $request]); + } + + if (null === $panel) { + $panel = 'request'; + + foreach ($profile->getCollectors() as $collector) { + if ($collector instanceof ExceptionDataCollector && $collector->hasException()) { + $panel = $collector->getName(); + + break; + } + + if ($collector instanceof DumpDataCollector && $collector->getDumpsCount() > 0) { + $panel = $collector->getName(); + } + } } if (!$profile->hasCollector($panel)) { throw new NotFoundHttpException(sprintf('Panel "%s" is not available for token "%s".', $panel, $token)); } - return new Response($this->twig->render($this->getTemplateManager()->getName($profile, $panel), [ + return $this->renderWithCspNonces($request, $this->getTemplateManager()->getName($profile, $panel), [ 'token' => $token, 'profile' => $profile, 'collector' => $profile->getCollector($panel), @@ -121,26 +116,21 @@ class ProfilerController 'templates' => $this->getTemplateManager()->getNames($profile), 'is_ajax' => $request->isXmlHttpRequest(), 'profiler_markup_version' => 2, // 1 = original profiler, 2 = Symfony 2.8+ profiler - ]), 200, ['Content-Type' => 'text/html']); + ]); } /** * Renders the Web Debug Toolbar. * - * @param Request $request The current HTTP Request - * @param string $token The profiler token - * - * @return Response A Response instance - * * @throws NotFoundHttpException */ - public function toolbarAction(Request $request, $token) + public function toolbarAction(Request $request, string $token = null): Response { if (null === $this->profiler) { throw new NotFoundHttpException('The profiler must be enabled.'); } - if ($request->hasSession() && ($session = $request->getSession()) && $session->isStarted() && $session->getFlashBag() instanceof AutoExpireFlashBag) { + if ($request->hasSession() && ($session = $request->getSession())->isStarted() && $session->getFlashBag() instanceof AutoExpireFlashBag) { // keep current flashes for one more request if using AutoExpireFlashBag $session->getFlashBag()->setAll($session->getFlashBag()->peekAll()); } @@ -155,11 +145,6 @@ class ProfilerController return new Response('', 404, ['Content-Type' => 'text/html']); } - // the toolbar position (top, bottom, normal, or null -- use the configuration) - if (null === $position = $request->query->get('position')) { - $position = $this->toolbarPosition; - } - $url = null; try { $url = $this->generator->generate('_profiler', ['token' => $token], UrlGeneratorInterface::ABSOLUTE_URL); @@ -168,8 +153,8 @@ class ProfilerController } return $this->renderWithCspNonces($request, '@WebProfiler/Profiler/toolbar.html.twig', [ + 'full_stack' => class_exists(FullStack::class), 'request' => $request, - 'position' => $position, 'profile' => $profile, 'templates' => $this->getTemplateManager()->getNames($profile), 'profiler_url' => $url, @@ -181,17 +166,11 @@ class ProfilerController /** * Renders the profiler search bar. * - * @return Response A Response instance - * * @throws NotFoundHttpException */ - public function searchBarAction(Request $request) + public function searchBarAction(Request $request): Response { - if (null === $this->profiler) { - throw new NotFoundHttpException('The profiler must be enabled.'); - } - - $this->profiler->disable(); + $this->denyAccessIfProfilerDisabled(); if (null !== $this->cspHandler) { $this->cspHandler->disableCsp(); @@ -239,20 +218,11 @@ class ProfilerController /** * Renders the search results. * - * @param Request $request The current HTTP Request - * @param string $token The token - * - * @return Response A Response instance - * * @throws NotFoundHttpException */ - public function searchResultsAction(Request $request, $token) + public function searchResultsAction(Request $request, string $token): Response { - if (null === $this->profiler) { - throw new NotFoundHttpException('The profiler must be enabled.'); - } - - $this->profiler->disable(); + $this->denyAccessIfProfilerDisabled(); if (null !== $this->cspHandler) { $this->cspHandler->disableCsp(); @@ -268,7 +238,7 @@ class ProfilerController $end = $request->query->get('end', null); $limit = $request->query->get('limit'); - return new Response($this->twig->render('@WebProfiler/Profiler/results.html.twig', [ + return $this->renderWithCspNonces($request, '@WebProfiler/Profiler/results.html.twig', [ 'request' => $request, 'token' => $token, 'profile' => $profile, @@ -281,23 +251,17 @@ class ProfilerController 'end' => $end, 'limit' => $limit, 'panel' => null, - ]), 200, ['Content-Type' => 'text/html']); + ]); } /** * Narrows the search bar. * - * @return Response A Response instance - * * @throws NotFoundHttpException */ - public function searchAction(Request $request) + public function searchAction(Request $request): Response { - if (null === $this->profiler) { - throw new NotFoundHttpException('The profiler must be enabled.'); - } - - $this->profiler->disable(); + $this->denyAccessIfProfilerDisabled(); $ip = $request->query->get('ip'); $method = $request->query->get('method'); @@ -342,17 +306,11 @@ class ProfilerController /** * Displays the PHP info. * - * @return Response A Response instance - * * @throws NotFoundHttpException */ - public function phpinfoAction() + public function phpinfoAction(): Response { - if (null === $this->profiler) { - throw new NotFoundHttpException('The profiler must be enabled.'); - } - - $this->profiler->disable(); + $this->denyAccessIfProfilerDisabled(); if (null !== $this->cspHandler) { $this->cspHandler->disableCsp(); @@ -368,11 +326,9 @@ class ProfilerController /** * Displays the source of a file. * - * @return Response A Response instance - * * @throws NotFoundHttpException */ - public function openAction(Request $request) + public function openAction(Request $request): Response { if (null === $this->baseDir) { throw new NotFoundHttpException('The base dir should be set.'); @@ -391,19 +347,17 @@ class ProfilerController throw new NotFoundHttpException(sprintf('The file "%s" cannot be opened.', $file)); } - return new Response($this->twig->render('@WebProfiler/Profiler/open.html.twig', [ + return $this->renderWithCspNonces($request, '@WebProfiler/Profiler/open.html.twig', [ 'filename' => $filename, 'file' => $file, 'line' => $line, - ]), 200, ['Content-Type' => 'text/html']); + ]); } /** * Gets the Template Manager. - * - * @return TemplateManager The Template Manager */ - protected function getTemplateManager() + protected function getTemplateManager(): TemplateManager { if (null === $this->templateManager) { $this->templateManager = new TemplateManager($this->profiler, $this->twig, $this->templates); @@ -412,14 +366,23 @@ class ProfilerController return $this->templateManager; } - private function renderWithCspNonces(Request $request, $template, $variables, $code = 200, $headers = ['Content-Type' => 'text/html']) + private function denyAccessIfProfilerDisabled() + { + if (null === $this->profiler) { + throw new NotFoundHttpException('The profiler must be enabled.'); + } + + $this->profiler->disable(); + } + + private function renderWithCspNonces(Request $request, string $template, array $variables, int $code = 200, array $headers = ['Content-Type' => 'text/html']): Response { $response = new Response('', $code, $headers); $nonces = $this->cspHandler ? $this->cspHandler->getNonces($request, $response) : []; - $variables['csp_script_nonce'] = isset($nonces['csp_script_nonce']) ? $nonces['csp_script_nonce'] : null; - $variables['csp_style_nonce'] = isset($nonces['csp_style_nonce']) ? $nonces['csp_style_nonce'] : null; + $variables['csp_script_nonce'] = $nonces['csp_script_nonce'] ?? null; + $variables['csp_style_nonce'] = $nonces['csp_style_nonce'] ?? null; $response->setContent($this->twig->render($template, $variables)); diff --git a/lib/symfony/web-profiler-bundle/Controller/RouterController.php b/lib/symfony/web-profiler-bundle/Controller/RouterController.php index 7f2a2406a9..50560e0b3f 100644 --- a/lib/symfony/web-profiler-bundle/Controller/RouterController.php +++ b/lib/symfony/web-profiler-bundle/Controller/RouterController.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\WebProfilerBundle\Controller; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector; @@ -23,9 +24,9 @@ use Symfony\Component\Routing\RouterInterface; use Twig\Environment; /** - * RouterController. - * * @author Fabien Potencier + * + * @internal */ class RouterController { @@ -34,24 +35,26 @@ class RouterController private $matcher; private $routes; - public function __construct(Profiler $profiler = null, Environment $twig, UrlMatcherInterface $matcher = null, RouteCollection $routes = null) + /** + * @var ExpressionFunctionProviderInterface[] + */ + private $expressionLanguageProviders = []; + + public function __construct(Profiler $profiler = null, Environment $twig, UrlMatcherInterface $matcher = null, RouteCollection $routes = null, iterable $expressionLanguageProviders = []) { $this->profiler = $profiler; $this->twig = $twig; $this->matcher = $matcher; $this->routes = (null === $routes && $matcher instanceof RouterInterface) ? $matcher->getRouteCollection() : $routes; + $this->expressionLanguageProviders = $expressionLanguageProviders; } /** * Renders the profiler panel for the given token. * - * @param string $token The profiler token - * - * @return Response A Response instance - * * @throws NotFoundHttpException */ - public function panelAction($token) + public function panelAction(string $token): Response { if (null === $this->profiler) { throw new NotFoundHttpException('The profiler must be enabled.'); @@ -77,17 +80,13 @@ class RouterController /** * Returns the routing traces associated to the given request. - * - * @param string $method - * - * @return array */ - private function getTraces(RequestDataCollector $request, $method) + private function getTraces(RequestDataCollector $request, string $method): array { $traceRequest = Request::create( $request->getPathInfo(), $request->getRequestServer(true)->get('REQUEST_METHOD'), - [], + \in_array($request->getMethod(), ['DELETE', 'PATCH', 'POST', 'PUT'], true) ? $request->getRequestRequest()->all() : $request->getRequestQuery()->all(), $request->getRequestCookies(true)->all(), [], $request->getRequestServer(true)->all() @@ -96,6 +95,9 @@ class RouterController $context = $this->matcher->getContext(); $context->setMethod($method); $matcher = new TraceableUrlMatcher($this->routes, $context); + foreach ($this->expressionLanguageProviders as $provider) { + $matcher->addExpressionLanguageProvider($provider); + } return $matcher->getTracesForRequest($traceRequest); } diff --git a/lib/symfony/web-profiler-bundle/Csp/ContentSecurityPolicyHandler.php b/lib/symfony/web-profiler-bundle/Csp/ContentSecurityPolicyHandler.php index 1742b32d12..ce24136926 100644 --- a/lib/symfony/web-profiler-bundle/Csp/ContentSecurityPolicyHandler.php +++ b/lib/symfony/web-profiler-bundle/Csp/ContentSecurityPolicyHandler.php @@ -38,10 +38,8 @@ class ContentSecurityPolicyHandler * - The request - In case HTML content is fetched via AJAX and inserted in DOM, it must use the same nonce as origin * - The response - A call to getNonces() has already been done previously. Same nonce are returned * - They are otherwise randomly generated - * - * @return array */ - public function getNonces(Request $request, Response $response) + public function getNonces(Request $request, Response $response): array { if ($request->headers->has('X-SymfonyProfiler-Script-Nonce') && $request->headers->has('X-SymfonyProfiler-Style-Nonce')) { return [ @@ -83,7 +81,7 @@ class ContentSecurityPolicyHandler * * @return array Nonces used by the bundle in Content-Security-Policy header */ - public function updateResponseHeaders(Request $request, Response $response) + public function updateResponseHeaders(Request $request, Response $response): array { if ($this->cspDisabled) { $this->removeCspHeaders($response); @@ -113,10 +111,8 @@ class ContentSecurityPolicyHandler /** * Updates Content-Security-Policy headers in a response. - * - * @return array */ - private function updateCspHeaders(Response $response, array $nonces = []) + private function updateCspHeaders(Response $response, array $nonces = []): array { $nonces = array_replace([ 'csp_script_nonce' => $this->generateNonce(), @@ -144,6 +140,12 @@ class ContentSecurityPolicyHandler continue; } + if (['\'none\''] === $fallback) { + // Fallback came from "default-src: 'none'" + // 'none' is invalid if it's not the only expression in the source list, so we leave it out + $fallback = []; + } + $headers[$header][$type] = $fallback; } $ruleIsSet = true; @@ -167,22 +169,16 @@ class ContentSecurityPolicyHandler /** * Generates a valid Content-Security-Policy nonce. - * - * @return string */ - private function generateNonce() + private function generateNonce(): string { return $this->nonceGenerator->generate(); } /** * Converts a directive set array into Content-Security-Policy header. - * - * @param array $directives The directive set - * - * @return string The Content-Security-Policy header */ - private function generateCspHeader(array $directives) + private function generateCspHeader(array $directives): string { return array_reduce(array_keys($directives), function ($res, $name) use ($directives) { return ('' !== $res ? $res.'; ' : '').sprintf('%s %s', $name, implode(' ', $directives[$name])); @@ -191,12 +187,8 @@ class ContentSecurityPolicyHandler /** * Converts a Content-Security-Policy header value into a directive set array. - * - * @param string $header The header value - * - * @return array The directive set */ - private function parseDirectives($header) + private function parseDirectives(string $header): array { $directives = []; @@ -214,13 +206,8 @@ class ContentSecurityPolicyHandler /** * Detects if the 'unsafe-inline' is prevented for a directive within the directive set. - * - * @param array $directivesSet The directive set - * @param string $type The name of the directive to check - * - * @return bool */ - private function authorizesInline(array $directivesSet, $type) + private function authorizesInline(array $directivesSet, string $type): bool { if (isset($directivesSet[$type])) { $directives = $directivesSet[$type]; @@ -231,10 +218,10 @@ class ContentSecurityPolicyHandler return \in_array('\'unsafe-inline\'', $directives, true) && !$this->hasHashOrNonce($directives); } - private function hasHashOrNonce(array $directives) + private function hasHashOrNonce(array $directives): bool { foreach ($directives as $directive) { - if ('\'' !== substr($directive, -1)) { + if (!str_ends_with($directive, '\'')) { continue; } if ('\'nonce-' === substr($directive, 0, 7)) { @@ -248,7 +235,7 @@ class ContentSecurityPolicyHandler return false; } - private function getDirectiveFallback(array $directiveSet, $type) + private function getDirectiveFallback(array $directiveSet, string $type) { if (\in_array($type, ['script-src-elem', 'style-src-elem'], true) || !isset($directiveSet['default-src'])) { // Let the browser fallback on it's own @@ -261,10 +248,8 @@ class ContentSecurityPolicyHandler /** * Retrieves the Content-Security-Policy headers (either X-Content-Security-Policy or Content-Security-Policy) from * a response. - * - * @return array An associative array of headers */ - private function getCspHeaders(Response $response) + private function getCspHeaders(Response $response): array { $headers = []; diff --git a/lib/symfony/web-profiler-bundle/Csp/NonceGenerator.php b/lib/symfony/web-profiler-bundle/Csp/NonceGenerator.php index 728043551f..19af849690 100644 --- a/lib/symfony/web-profiler-bundle/Csp/NonceGenerator.php +++ b/lib/symfony/web-profiler-bundle/Csp/NonceGenerator.php @@ -20,7 +20,7 @@ namespace Symfony\Bundle\WebProfilerBundle\Csp; */ class NonceGenerator { - public function generate() + public function generate(): string { return bin2hex(random_bytes(16)); } diff --git a/lib/symfony/web-profiler-bundle/DependencyInjection/Configuration.php b/lib/symfony/web-profiler-bundle/DependencyInjection/Configuration.php index 6c3af8641f..041c3350a6 100644 --- a/lib/symfony/web-profiler-bundle/DependencyInjection/Configuration.php +++ b/lib/symfony/web-profiler-bundle/DependencyInjection/Configuration.php @@ -27,24 +27,15 @@ class Configuration implements ConfigurationInterface /** * Generates the configuration tree builder. * - * @return TreeBuilder The tree builder + * @return TreeBuilder */ public function getConfigTreeBuilder() { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('web_profiler'); + $treeBuilder = new TreeBuilder('web_profiler'); - $rootNode + $treeBuilder->getRootNode() ->children() ->booleanNode('toolbar')->defaultFalse()->end() - ->scalarNode('position') - ->defaultValue('bottom') - ->setDeprecated('The "web_profiler.position" configuration key has been deprecated in Symfony 3.4 and it will be removed in 4.0.') - ->validate() - ->ifNotInArray(['bottom', 'top']) - ->thenInvalid('The CSS position %s is not supported') - ->end() - ->end() ->booleanNode('intercept_redirects')->defaultFalse()->end() ->scalarNode('excluded_ajax_paths')->defaultValue('^/((index|app(_[\w]+)?)\.php/)?_wdt')->end() ->end() diff --git a/lib/symfony/web-profiler-bundle/DependencyInjection/WebProfilerExtension.php b/lib/symfony/web-profiler-bundle/DependencyInjection/WebProfilerExtension.php index 19b75c59ea..0bb949c095 100644 --- a/lib/symfony/web-profiler-bundle/DependencyInjection/WebProfilerExtension.php +++ b/lib/symfony/web-profiler-bundle/DependencyInjection/WebProfilerExtension.php @@ -16,9 +16,8 @@ use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\Extension; -use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\HttpKernel\Kernel; /** * WebProfilerExtension. @@ -37,30 +36,25 @@ class WebProfilerExtension extends Extension /** * Loads the web profiler configuration. * - * @param array $configs An array of configuration settings - * @param ContainerBuilder $container A ContainerBuilder instance + * @param array $configs An array of configuration settings */ public function load(array $configs, ContainerBuilder $container) { $configuration = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); - $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); - $loader->load('profiler.xml'); - $container->setParameter('web_profiler.debug_toolbar.position', $config['position']); + $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('profiler.php'); if ($config['toolbar'] || $config['intercept_redirects']) { - $loader->load('toolbar.xml'); - $container->getDefinition('web_profiler.debug_toolbar')->setPrivate(true); - $container->getDefinition('web_profiler.debug_toolbar')->replaceArgument(5, $config['excluded_ajax_paths']); + $loader->load('toolbar.php'); + $container->getDefinition('web_profiler.debug_toolbar')->replaceArgument(4, $config['excluded_ajax_paths']); $container->setParameter('web_profiler.debug_toolbar.intercept_redirects', $config['intercept_redirects']); $container->setParameter('web_profiler.debug_toolbar.mode', $config['toolbar'] ? WebDebugToolbarListener::ENABLED : WebDebugToolbarListener::DISABLED); } - if (Kernel::VERSION_ID >= 40008 || (Kernel::VERSION_ID >= 30408 && Kernel::VERSION_ID < 40000)) { - $container->getDefinition('debug.file_link_formatter') - ->replaceArgument(3, new ServiceClosureArgument(new Reference('debug.file_link_formatter.url_format'))); - } + $container->getDefinition('debug.file_link_formatter') + ->replaceArgument(3, new ServiceClosureArgument(new Reference('debug.file_link_formatter.url_format'))); } /** diff --git a/lib/symfony/web-profiler-bundle/EventListener/WebDebugToolbarListener.php b/lib/symfony/web-profiler-bundle/EventListener/WebDebugToolbarListener.php index 3b32f40112..b2e7db2696 100644 --- a/lib/symfony/web-profiler-bundle/EventListener/WebDebugToolbarListener.php +++ b/lib/symfony/web-profiler-bundle/EventListener/WebDebugToolbarListener.php @@ -11,12 +11,14 @@ namespace Symfony\Bundle\WebProfilerBundle\EventListener; +use Symfony\Bundle\FullStack; use Symfony\Bundle\WebProfilerBundle\Csp\ContentSecurityPolicyHandler; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag; -use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector; +use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Twig\Environment; @@ -30,37 +32,48 @@ use Twig\Environment; * This means that the WDT is never included in sub-requests or ESI requests. * * @author Fabien Potencier + * + * @final */ class WebDebugToolbarListener implements EventSubscriberInterface { - const DISABLED = 1; - const ENABLED = 2; + public const DISABLED = 1; + public const ENABLED = 2; protected $twig; protected $urlGenerator; protected $interceptRedirects; protected $mode; - protected $position; protected $excludedAjaxPaths; private $cspHandler; + private $dumpDataCollector; - public function __construct(Environment $twig, $interceptRedirects = false, $mode = self::ENABLED, $position = 'bottom', UrlGeneratorInterface $urlGenerator = null, $excludedAjaxPaths = '^/bundles|^/_wdt', ContentSecurityPolicyHandler $cspHandler = null) + public function __construct(Environment $twig, bool $interceptRedirects = false, int $mode = self::ENABLED, UrlGeneratorInterface $urlGenerator = null, string $excludedAjaxPaths = '^/bundles|^/_wdt', ContentSecurityPolicyHandler $cspHandler = null, DumpDataCollector $dumpDataCollector = null) { $this->twig = $twig; $this->urlGenerator = $urlGenerator; - $this->interceptRedirects = (bool) $interceptRedirects; - $this->mode = (int) $mode; - $this->position = $position; + $this->interceptRedirects = $interceptRedirects; + $this->mode = $mode; $this->excludedAjaxPaths = $excludedAjaxPaths; $this->cspHandler = $cspHandler; + $this->dumpDataCollector = $dumpDataCollector; } - public function isEnabled() + public function isEnabled(): bool { return self::DISABLED !== $this->mode; } - public function onKernelResponse(FilterResponseEvent $event) + public function setMode(int $mode): void + { + if (self::DISABLED !== $mode && self::ENABLED !== $mode) { + throw new \InvalidArgumentException(sprintf('Invalid value provided for mode, use one of "%s::DISABLED" or "%s::ENABLED".', self::class, self::class)); + } + + $this->mode = $mode; + } + + public function onKernelResponse(ResponseEvent $event) { $response = $event->getResponse(); $request = $event->getRequest(); @@ -76,11 +89,18 @@ class WebDebugToolbarListener implements EventSubscriberInterface } } - if (!$event->isMasterRequest()) { + if (!$event->isMainRequest()) { return; } - $nonces = $this->cspHandler ? $this->cspHandler->updateResponseHeaders($request, $response) : []; + $nonces = []; + if ($this->cspHandler) { + if ($this->dumpDataCollector && $this->dumpDataCollector->getDumpsCount() > 0) { + $this->cspHandler->disableCsp(); + } + + $nonces = $this->cspHandler->updateResponseHeaders($request, $response); + } // do not capture redirects or modify XML HTTP Requests if ($request->isXmlHttpRequest()) { @@ -88,8 +108,7 @@ class WebDebugToolbarListener implements EventSubscriberInterface } if ($response->headers->has('X-Debug-Token') && $response->isRedirect() && $this->interceptRedirects && 'html' === $request->getRequestFormat()) { - $session = $request->getSession(); - if (null !== $session && $session->isStarted() && $session->getFlashBag() instanceof AutoExpireFlashBag) { + if ($request->hasSession() && ($session = $request->getSession())->isStarted() && $session->getFlashBag() instanceof AutoExpireFlashBag) { // keep current flashes for one more request if using AutoExpireFlashBag $session->getFlashBag()->setAll($session->getFlashBag()->peekAll()); } @@ -102,9 +121,9 @@ class WebDebugToolbarListener implements EventSubscriberInterface if (self::DISABLED === $this->mode || !$response->headers->has('X-Debug-Token') || $response->isRedirection() - || ($response->headers->has('Content-Type') && false === strpos($response->headers->get('Content-Type'), 'html')) + || ($response->headers->has('Content-Type') && !str_contains($response->headers->get('Content-Type'), 'html')) || 'html' !== $request->getRequestFormat() - || false !== stripos($response->headers->get('Content-Disposition'), 'attachment;') + || false !== stripos($response->headers->get('Content-Disposition', ''), 'attachment;') ) { return; } @@ -124,12 +143,12 @@ class WebDebugToolbarListener implements EventSubscriberInterface $toolbar = "\n".str_replace("\n", '', $this->twig->render( '@WebProfiler/Profiler/toolbar_js.html.twig', [ - 'position' => $this->position, + 'full_stack' => class_exists(FullStack::class), 'excluded_ajax_paths' => $this->excludedAjaxPaths, 'token' => $response->headers->get('X-Debug-Token'), 'request' => $request, - 'csp_script_nonce' => isset($nonces['csp_script_nonce']) ? $nonces['csp_script_nonce'] : null, - 'csp_style_nonce' => isset($nonces['csp_style_nonce']) ? $nonces['csp_style_nonce'] : null, + 'csp_script_nonce' => $nonces['csp_script_nonce'] ?? null, + 'csp_style_nonce' => $nonces['csp_style_nonce'] ?? null, ] ))."\n"; $content = substr($content, 0, $pos).$toolbar.substr($content, $pos); @@ -137,7 +156,7 @@ class WebDebugToolbarListener implements EventSubscriberInterface } } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ KernelEvents::RESPONSE => ['onKernelResponse', -128], diff --git a/lib/symfony/web-profiler-bundle/LICENSE b/lib/symfony/web-profiler-bundle/LICENSE index 9e936ec044..88bf75bb4d 100644 --- a/lib/symfony/web-profiler-bundle/LICENSE +++ b/lib/symfony/web-profiler-bundle/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2020 Fabien Potencier +Copyright (c) 2004-2022 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/lib/symfony/web-profiler-bundle/Profiler/TemplateManager.php b/lib/symfony/web-profiler-bundle/Profiler/TemplateManager.php index 4f8da03cb5..f962e69f1a 100644 --- a/lib/symfony/web-profiler-bundle/Profiler/TemplateManager.php +++ b/lib/symfony/web-profiler-bundle/Profiler/TemplateManager.php @@ -15,16 +15,12 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Profiler\Profile; use Symfony\Component\HttpKernel\Profiler\Profiler; use Twig\Environment; -use Twig\Error\LoaderError; -use Twig\Loader\ExistsLoaderInterface; -use Twig\Loader\SourceContextLoaderInterface; -use Twig\Template; /** - * Profiler Templates Manager. - * * @author Fabien Potencier * @author Artur Wielogórski + * + * @internal */ class TemplateManager { @@ -42,13 +38,11 @@ class TemplateManager /** * Gets the template name for a given panel. * - * @param string $panel - * * @return mixed * * @throws NotFoundHttpException */ - public function getName(Profile $profile, $panel) + public function getName(Profile $profile, string $panel) { $templates = $this->getNames($profile); @@ -59,33 +53,14 @@ class TemplateManager return $templates[$panel]; } - /** - * Gets the templates for a given profile. - * - * @return Template[] - * - * @deprecated not used anymore internally - */ - public function getTemplates(Profile $profile) - { - $templates = $this->getNames($profile); - - foreach ($templates as $name => $template) { - $templates[$name] = $this->twig->loadTemplate($template); - } - - return $templates; - } - /** * Gets template names of templates that are present in the viewed profile. * - * @return array - * * @throws \UnexpectedValueException */ - public function getNames(Profile $profile) + public function getNames(Profile $profile): array { + $loader = $this->twig->getLoader(); $templates = []; foreach ($this->templates as $arguments) { @@ -93,17 +68,17 @@ class TemplateManager continue; } - list($name, $template) = $arguments; + [$name, $template] = $arguments; if (!$this->profiler->has($name) || !$profile->hasCollector($name)) { continue; } - if ('.html.twig' === substr($template, -10)) { + if (str_ends_with($template, '.html.twig')) { $template = substr($template, 0, -10); } - if (!$this->templateExists($template.'.html.twig')) { + if (!$loader->exists($template.'.html.twig')) { throw new \UnexpectedValueException(sprintf('The profiler template "%s.html.twig" for data collector "%s" does not exist.', $template, $name)); } @@ -112,27 +87,4 @@ class TemplateManager return $templates; } - - // to be removed when the minimum required version of Twig is >= 2.0 - protected function templateExists($template) - { - $loader = $this->twig->getLoader(); - - if (1 === Environment::MAJOR_VERSION && !$loader instanceof ExistsLoaderInterface) { - try { - if ($loader instanceof SourceContextLoaderInterface) { - $loader->getSourceContext($template); - } else { - $loader->getSource($template); - } - - return true; - } catch (LoaderError $e) { - } - - return false; - } - - return $loader->exists($template); - } } diff --git a/lib/symfony/web-profiler-bundle/README.md b/lib/symfony/web-profiler-bundle/README.md index 48e6075636..e3c1400b1c 100644 --- a/lib/symfony/web-profiler-bundle/README.md +++ b/lib/symfony/web-profiler-bundle/README.md @@ -1,7 +1,7 @@ WebProfilerBundle ================= -The Web profiler bundle is a **development tool** that gives detailed +WebProfilerBundle provides a **development tool** that gives detailed information about the execution of any request. **Never** enable it on production servers as it will lead to major security @@ -10,7 +10,7 @@ vulnerabilities in your project. Resources --------- - * [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) + * [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/lib/symfony/web-profiler-bundle/Resources/config/profiler.php b/lib/symfony/web-profiler-bundle/Resources/config/profiler.php new file mode 100644 index 0000000000..85c64f268b --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Resources/config/profiler.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 Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\WebProfilerBundle\Controller\ExceptionPanelController; +use Symfony\Bundle\WebProfilerBundle\Controller\ProfilerController; +use Symfony\Bundle\WebProfilerBundle\Controller\RouterController; +use Symfony\Bundle\WebProfilerBundle\Csp\ContentSecurityPolicyHandler; +use Symfony\Bundle\WebProfilerBundle\Csp\NonceGenerator; +use Symfony\Bundle\WebProfilerBundle\Twig\WebProfilerExtension; +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; + +return static function (ContainerConfigurator $container) { + $container->services() + + ->set('web_profiler.controller.profiler', ProfilerController::class) + ->public() + ->args([ + service('router')->nullOnInvalid(), + service('profiler')->nullOnInvalid(), + service('twig'), + param('data_collector.templates'), + service('web_profiler.csp.handler'), + param('kernel.project_dir'), + ]) + + ->set('web_profiler.controller.router', RouterController::class) + ->public() + ->args([ + service('profiler')->nullOnInvalid(), + service('twig'), + service('router')->nullOnInvalid(), + null, + tagged_iterator('routing.expression_language_provider'), + ]) + + ->set('web_profiler.controller.exception_panel', ExceptionPanelController::class) + ->public() + ->args([ + service('error_handler.error_renderer.html'), + service('profiler')->nullOnInvalid(), + ]) + + ->set('web_profiler.csp.handler', ContentSecurityPolicyHandler::class) + ->args([ + inline_service(NonceGenerator::class), + ]) + + ->set('twig.extension.webprofiler', WebProfilerExtension::class) + ->args([ + inline_service(HtmlDumper::class) + ->args([null, param('kernel.charset'), HtmlDumper::DUMP_LIGHT_ARRAY]) + ->call('setDisplayOptions', [['maxStringLength' => 4096, 'fileLinkFormat' => service('debug.file_link_formatter')]]), + ]) + ->tag('twig.extension') + + ->set('debug.file_link_formatter', FileLinkFormatter::class) + ->args([ + param('debug.file_link_format'), + service('request_stack')->ignoreOnInvalid(), + param('kernel.project_dir'), + '/_profiler/open?file=%%f&line=%%l#line%%l', + ]) + + ->set('debug.file_link_formatter.url_format', 'string') + ->factory([FileLinkFormatter::class, 'generateUrlFormat']) + ->args([ + service('router'), + '_profiler_open_file', + '?file=%%f&line=%%l#line%%l', + ]) + ; +}; diff --git a/lib/symfony/web-profiler-bundle/Resources/config/profiler.xml b/lib/symfony/web-profiler-bundle/Resources/config/profiler.xml deleted file mode 100644 index a8526d1275..0000000000 --- a/lib/symfony/web-profiler-bundle/Resources/config/profiler.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - %data_collector.templates% - %web_profiler.debug_toolbar.position% - - %kernel.project_dir% - - - - - - - - - - - - %kernel.debug% - - - - - - - - - - - - - - null - %kernel.charset% - Symfony\Component\VarDumper\Dumper\HtmlDumper::DUMP_LIGHT_ARRAY - - - 4096 - - - - - - - - - %debug.file_link_format% - - %kernel.project_dir% - /_profiler/open?file=%%f&line=%%l#line%%l - - - - - - _profiler_open_file - ?file=%%f&line=%%l#line%%l - - - diff --git a/lib/symfony/web-profiler-bundle/Resources/config/routing/profiler.xml b/lib/symfony/web-profiler-bundle/Resources/config/routing/profiler.xml index 3899b6cea2..f20cba0e67 100644 --- a/lib/symfony/web-profiler-bundle/Resources/config/routing/profiler.xml +++ b/lib/symfony/web-profiler-bundle/Resources/config/routing/profiler.xml @@ -5,43 +5,43 @@ xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd"> - web_profiler.controller.profiler:homeAction + web_profiler.controller.profiler::homeAction - web_profiler.controller.profiler:searchAction + web_profiler.controller.profiler::searchAction - web_profiler.controller.profiler:searchBarAction + web_profiler.controller.profiler::searchBarAction - web_profiler.controller.profiler:phpinfoAction + web_profiler.controller.profiler::phpinfoAction - web_profiler.controller.profiler:searchResultsAction + web_profiler.controller.profiler::searchResultsAction - web_profiler.controller.profiler:openAction + web_profiler.controller.profiler::openAction - web_profiler.controller.profiler:panelAction + web_profiler.controller.profiler::panelAction - web_profiler.controller.router:panelAction + web_profiler.controller.router::panelAction - web_profiler.controller.exception:showAction + web_profiler.controller.exception_panel::body - web_profiler.controller.exception:cssAction + web_profiler.controller.exception_panel::stylesheet diff --git a/lib/symfony/web-profiler-bundle/Resources/config/routing/wdt.xml b/lib/symfony/web-profiler-bundle/Resources/config/routing/wdt.xml index 29ed0ffa5a..0f7e960cc8 100644 --- a/lib/symfony/web-profiler-bundle/Resources/config/routing/wdt.xml +++ b/lib/symfony/web-profiler-bundle/Resources/config/routing/wdt.xml @@ -5,6 +5,6 @@ xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd"> - web_profiler.controller.profiler:toolbarAction + web_profiler.controller.profiler::toolbarAction diff --git a/lib/symfony/web-profiler-bundle/Resources/config/schema/webprofiler-1.0.xsd b/lib/symfony/web-profiler-bundle/Resources/config/schema/webprofiler-1.0.xsd index 84cc8ae9a9..e22105a178 100644 --- a/lib/symfony/web-profiler-bundle/Resources/config/schema/webprofiler-1.0.xsd +++ b/lib/symfony/web-profiler-bundle/Resources/config/schema/webprofiler-1.0.xsd @@ -10,13 +10,5 @@ - - - - - - - - diff --git a/lib/symfony/web-profiler-bundle/Resources/config/toolbar.php b/lib/symfony/web-profiler-bundle/Resources/config/toolbar.php new file mode 100644 index 0000000000..473b3630f7 --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Resources/config/toolbar.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\WebProfilerBundle\EventListener\WebDebugToolbarListener; + +return static function (ContainerConfigurator $container) { + $container->services() + + ->set('web_profiler.debug_toolbar', WebDebugToolbarListener::class) + ->args([ + service('twig'), + param('web_profiler.debug_toolbar.intercept_redirects'), + param('web_profiler.debug_toolbar.mode'), + service('router')->ignoreOnInvalid(), + abstract_arg('paths that should be excluded from the AJAX requests shown in the toolbar'), + service('web_profiler.csp.handler'), + service('data_collector.dump')->ignoreOnInvalid(), + ]) + ->tag('kernel.event_subscriber') + ; +}; diff --git a/lib/symfony/web-profiler-bundle/Resources/config/toolbar.xml b/lib/symfony/web-profiler-bundle/Resources/config/toolbar.xml deleted file mode 100644 index 7687ab7c96..0000000000 --- a/lib/symfony/web-profiler-bundle/Resources/config/toolbar.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - %web_profiler.debug_toolbar.intercept_redirects% - %web_profiler.debug_toolbar.mode% - %web_profiler.debug_toolbar.position% - - - - - - diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/ajax.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/ajax.html.twig index 5df0d9ea9b..e4e7d6418e 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/ajax.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/ajax.html.twig @@ -8,18 +8,22 @@ {% set text %}
                  - + + + (Clear) +
                  + + - diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/cache.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/cache.html.twig index cbc705f51c..0c406e9442 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/cache.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/cache.html.twig @@ -108,9 +108,9 @@
                  {% if key == 'time' %} - {{ '%0.2f'|format(1000 * value.value) }} ms + {{ '%0.2f'|format(1000 * value) }} ms {% elseif key == 'hit_read_ratio' %} - {{ value.value ?? 0 }} % + {{ value ?? 0 }} % {% else %} {{ value }} {% endif %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/config.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/config.html.twig index b9d130b13e..6dfd27bcbc 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/config.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/config.html.twig @@ -19,26 +19,14 @@ {% endif %} {% set icon %} - {% if collector.applicationname %} - {{ collector.applicationname }} - {{ collector.applicationversion }} - {% elseif collector.symfonyState is defined %} - - {{ include('@WebProfiler/Icon/symfony.svg') }} - - {{ collector.symfonyversion }} - {% endif %} + + {{ include('@WebProfiler/Icon/symfony.svg') }} + + {{ collector.symfonyState is defined ? collector.symfonyversion : 'n/a' }} {% endset %} {% set text %}
                  - {% if collector.applicationname %} -
                  - {{ collector.applicationname }} - {{ collector.applicationversion }} -
                  - {% endif %} -
                  Profiler token @@ -50,13 +38,6 @@
                  - {% if 'n/a' is not same as(collector.appname) %} -
                  - Kernel name - {{ collector.appname }} -
                  - {% endif %} - {% if 'n/a' is not same as(collector.env) %}
                  Environment @@ -83,9 +64,9 @@
                  PHP Extensions - xdebug - APCu - OPcache + xdebug {{ collector.hasxdebug ? '✓' : '✗' }} + APCu {{ collector.hasapcu ? '✓' : '✗' }} + OPcache {{ collector.haszendopcache ? '✓' : '✗' }}
                  @@ -99,15 +80,9 @@
                  Resources - {% if 'Silex' == collector.applicationname %} - - Read Silex Docs - - {% else %} - - Read Symfony {{ collector.symfonyversion }} Docs - - {% endif %} + + Read Symfony {{ collector.symfonyversion }} Docs +
                  @@ -126,88 +101,63 @@ {% endblock %} {% block menu %} - + {{ include('@WebProfiler/Icon/config.svg') }} Configuration {% endblock %} {% block panel %} - {% if collector.applicationname %} - {# this application is not the Symfony framework #} -

                  Project Configuration

                  +

                  Symfony Configuration

                  -
                  -
                  - {{ collector.applicationname }} - Application name -
                  - -
                  - {{ collector.applicationversion }} - Application version -
                  +
                  +
                  + {{ collector.symfonyversion }} + Symfony version
                  -

                  - Based on Symfony {{ collector.symfonyversion }} -

                  - {% else %} -

                  Symfony Configuration

                  - -
                  + {% if 'n/a' is not same as(collector.env) %}
                  - {{ collector.symfonyversion }} - Symfony version + {{ collector.env }} + Environment
                  + {% endif %} - {% if 'n/a' != collector.appname %} -
                  - {{ collector.appname }} - Application name -
                  - {% endif %} + {% if 'n/a' is not same as(collector.debug) %} +
                  + {{ collector.debug ? 'enabled' : 'disabled' }} + Debug +
                  + {% endif %} +
                  - {% if 'n/a' != collector.env %} -
                  - {{ collector.env }} - Environment -
                  - {% endif %} - - {% if 'n/a' != collector.debug %} -
                  - {{ collector.debug ? 'enabled' : 'disabled' }} - Debug -
                  - {% endif %} -
                  - - {% set symfony_status = { dev: 'Unstable Version', stable: 'Stable Version', eom: 'Maintenance Ended', eol: 'Version Expired' } %} - {% set symfony_status_class = { dev: 'warning', stable: 'success', eom: 'warning', eol: 'error' } %} -
                  #Profile Method Type Status URL TimeProfile
                  - - - - - - - - - - - - - - - - -
                  Symfony StatusBugs {{ collector.symfonystate in ['eom', 'eol'] ? 'were' : 'are' }} fixed untilSecurity issues {{ collector.symfonystate == 'eol' ? 'were' : 'are' }} fixed until
                  - {{ symfony_status[collector.symfonystate]|upper }} - {{ collector.symfonyeom }}{{ collector.symfonyeol }} - View roadmap -
                  - {% endif %} + {% set symfony_status = { dev: 'Unstable Version', stable: 'Stable Version', eom: 'Maintenance Ended', eol: 'Version Expired' } %} + {% set symfony_status_class = { dev: 'warning', stable: 'success', eom: 'warning', eol: 'error' } %} + + + + + + + + + + + + + + + + + +
                  Symfony StatusBugs {{ collector.symfonystate in ['eom', 'eol'] ? 'were' : 'are' }} fixed untilSecurity issues {{ collector.symfonystate == 'eol' ? 'were' : 'are' }} fixed until
                  + {{ symfony_status[collector.symfonystate]|upper }} + {% if collector.symfonylts %} +   Long-Term Support + {% endif %} + {{ collector.symfonyeom }}{{ collector.symfonyeol }} + View roadmap +

                  PHP Configuration

                  @@ -240,12 +190,12 @@
                  - {{ include('@WebProfiler/Icon/' ~ (collector.hasapcu ? 'yes' : 'no') ~ '.svg') }} + {{ include('@WebProfiler/Icon/' ~ (collector.hasapcu ? 'yes' : 'no-gray') ~ '.svg') }} APCu
                  - {{ include('@WebProfiler/Icon/' ~ (collector.hasxdebug ? 'yes' : 'no') ~ '.svg') }} + {{ include('@WebProfiler/Icon/' ~ (collector.hasxdebug ? 'yes' : 'no-gray') ~ '.svg') }} Xdebug
                  @@ -260,7 +210,7 @@ Name - Path + Class diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/events.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/events.html.twig index 53040a0d9b..c0be48a377 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/events.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/events.html.twig @@ -45,6 +45,39 @@ {% endif %}
                  + +
                  +

                  Orphaned Events {{ collector.orphanedEvents|length }}

                  +
                  + {% if collector.orphanedEvents is empty %} +
                  +

                  + There are no orphaned events. +

                  +

                  + All dispatched events were handled or an error occurred + when trying to collect orphaned events (in which case check the + logs to get more information). +

                  +
                  + {% else %} + + + + + + + + {% for event in collector.orphanedEvents %} + + + + {% endfor %} + +
                  Event
                  {{ event }}
                  + {% endif %} +
                  +
                  {% endif %} {% endblock %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/exception.css.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/exception.css.twig index c849cb2966..aad7625a22 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/exception.css.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/exception.css.twig @@ -1,7 +1,5 @@ -{{ include('@Twig/exception.css.twig') }} - .container { - max-width: auto; + max-width: none; margin: 0; padding: 0; } @@ -10,8 +8,8 @@ } .exception-summary { - background: #FFF; - border: 1px solid #E0E0E0; + background: var(--base-0); + border: var(--border); box-shadow: 0 0 1px rgba(128, 128, 128, .2); margin: 1em 0; padding: 10px; @@ -21,7 +19,7 @@ } .exception-message { - color: #B0413E; + color: var(--color-error); } .exception-metadata, @@ -30,5 +28,5 @@ } .exception-message-wrapper .container { - min-height: auto; + min-height: unset; } diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/exception.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/exception.html.twig index 94dfbb6aca..1fe0f5d470 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/exception.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/exception.html.twig @@ -3,7 +3,8 @@ {% block head %} {% if collector.hasexception %} {% endif %} {{ parent() }} @@ -30,7 +31,7 @@
                  {% else %}
                  - {{ render(path('_profiler_exception', { token: token })) }} + {{ render(controller('web_profiler.controller.exception_panel::body', { token: token })) }}
                  {% endif %} {% endblock %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/form.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/form.html.twig index c7b99d2e42..db97100e49 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/form.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/form.html.twig @@ -4,7 +4,7 @@ {% block toolbar %} {% if collector.data.nb_errors > 0 or collector.data.forms|length %} - {% set status_color = collector.data.nb_errors ? 'red' : '' %} + {% set status_color = collector.data.nb_errors ? 'red' %} {% set icon %} {{ include('@WebProfiler/Icon/form.svg') }} @@ -131,8 +131,11 @@ .tree .tree-inner:hover { background: #dfdfdf; } + .tree .tree-inner:hover span:not(.has-error) { + color: var(--base-0); + } .tree .tree-inner.active, .tree .tree-inner.active:hover { - background: #E0E0E0; + background: var(--tree-active-background); font-weight: bold; } .tree .tree-inner.active .toggle-icon, .tree .tree-inner:hover .toggle-icon, .tree .tree-inner.active:hover .toggle-icon { @@ -153,7 +156,7 @@ } .badge-error { float: right; - background: #B0413E; + background: var(--background-error); color: #FFF; padding: 1px 4px; font-size: 10px; @@ -161,17 +164,17 @@ vertical-align: middle; } .has-error { - color: #B0413E; + color: var(--color-error); } .errors h3 { - color: #B0413E; + color: var(--color-error); } .errors th { - background: #B0413E; + background: var(--background-error); color: #FFF; } .errors .toggle-icon { - background-color: #B0413E; + background-color: var(--background-error); } h3 a, h3 a:hover, h3 a:focus { color: inherit; @@ -183,6 +186,20 @@ h3.form-data-type + h3 { margin-top: 1em; } + .theme-dark .toggle-icon { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAgBAMAAADpp+X/AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAVUExURUdwTH+Ag0lNUZiYmGRmbP///zU5P2n9VV4AAAAFdFJOUwCv+yror0g1sQAAAE1JREFUGNNjSFM0YGBgEEpjSGEAAzcGBQiDiUEAwmBkMIAwmBmwgVAgQGWgA7h2uIFwK+CWwp1BpHtYA6DuATEYkBlY3IOmBq6dCPcAAKMtEEs3tfChAAAAAElFTkSuQmCC'); + } + .theme-dark .toggle-icon.empty { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAASUExURUdwTDI3OzQ5PS4uLjU3PzU5P4keoyIAAAAFdFJOUwBApgtzrnKGEwAAADJJREFUCNdjCFU0YGBgEAplCGEAA1cGBQiDiUEAwmBkMIAwmBnIA3DtcAPhVsAthTkDACsZBBmrTTSxAAAAAElFTkSuQmCC'); + } + .theme-dark .tree .tree-inner.active .toggle-icon, .theme-dark .tree .tree-inner:hover .toggle-icon, .theme-dark .tree .tree-inner.active:hover .toggle-icon { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAgBAMAAADpp+X/AAAAD1BMVEVHcEx/gIOYmJiZmZn///+IJ2wIAAAAA3RSTlMAryoIUq0uAAAAUElEQVQY02NgYFQ2NjYWYGBgMAYDBgZmCMOAQRjCMGRQhjCMoEqAipAYLkCAykBXA9cONxBuBdxShDOIc4+JM9Q9IIYxMgOLe9DUwLUT4R4AznguG0qfEa0AAAAASUVORK5CYII='); + background-color: transparent; + } + .theme-dark .tree .tree-inner.active .toggle-icon.empty, .theme-dark .tree .tree-inner:hover .toggle-icon.empty, .theme-dark .tree .tree-inner.active:hover .toggle-icon.empty { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAACVBMVEVHcEwyNzuqqqrd9nIgAAAAAnRSTlMAQABPjKgAAAArSURBVAjXY2BctcqBgWvVqgUMWqtWrWDIWrVqJcMqICCGACsGawMbADIKANflJYEoGMqtAAAAAElFTkSuQmCC'); + background-color: transparent; + } {% endblock %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/http_client.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/http_client.html.twig new file mode 100644 index 0000000000..8496ef186d --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/http_client.html.twig @@ -0,0 +1,131 @@ +{% extends '@WebProfiler/Profiler/layout.html.twig' %} + +{% block toolbar %} + {% if collector.requestCount %} + {% set icon %} + {{ include('@WebProfiler/Icon/http-client.svg') }} + {% set status_color = '' %} + {{ collector.requestCount }} + {% endset %} + + {% set text %} +
                  + Total requests + {{ collector.requestCount }} +
                  +
                  + HTTP errors + {{ collector.errorCount }} +
                  + {% endset %} + + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: status_color }) }} + {% endif %} +{% endblock %} + +{% block menu %} + + {{ include('@WebProfiler/Icon/http-client.svg') }} + HTTP Client + {% if collector.requestCount %} + + {{ collector.requestCount }} + + {% endif %} + +{% endblock %} + +{% block panel %} +

                  HTTP Client

                  + {% if collector.requestCount == 0 %} +
                  +

                  No HTTP requests were made.

                  +
                  + {% else %} +
                  +
                  + {{ collector.requestCount }} + Total requests +
                  +
                  + {{ collector.errorCount }} + HTTP errors +
                  +
                  +

                  Clients

                  +
                  + {% for name, client in collector.clients %} +
                  +

                  {{ name }} {{ client.traces|length }}

                  +
                  + {% if client.traces|length == 0 %} +
                  +

                  No requests were made with the "{{ name }}" service.

                  +
                  + {% else %} +

                  Requests

                  + {% for trace in client.traces %} + {% set profiler_token = '' %} + {% set profiler_link = '' %} + {% if trace.info.response_headers is defined %} + {% for header in trace.info.response_headers %} + {% if header matches '/^x-debug-token: .*$/i' %} + {% set profiler_token = (header.getValue | slice('x-debug-token: ' | length)) %} + {% endif %} + {% if header matches '/^x-debug-token-link: .*$/i' %} + {% set profiler_link = (header.getValue | slice('x-debug-token-link: ' | length)) %} + {% endif %} + {% endfor %} + {% endif %} + + + + + + {% if profiler_token and profiler_link %} + + {% endif %} + + + + + + + {% if profiler_token and profiler_link %} + + {% endif %} + + +
                  + {{ trace.method }} + + {{ trace.url }} + {% if trace.options is not empty %} + {{ profiler_dump(trace.options, maxDepth=1) }} + {% endif %} + + Profile +
                  + {% if trace.http_code >= 500 %} + {% set responseStatus = 'error' %} + {% elseif trace.http_code >= 400 %} + {% set responseStatus = 'warning' %} + {% else %} + {% set responseStatus = 'success' %} + {% endif %} + + {{ trace.http_code }} + + + {{ profiler_dump(trace.info, maxDepth=1) }} + + {{ profiler_token }} +
                  + {% endfor %} + {% endif %} +
                  +
                  + {% endfor %} + {% endif %} +
                  +{% endblock %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/logger.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/logger.html.twig index 6e92022a44..df2679d79c 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/logger.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/logger.html.twig @@ -5,7 +5,7 @@ {% block toolbar %} {% if collector.counterrors or collector.countdeprecations or collector.countwarnings %} {% set icon %} - {% set status_color = collector.counterrors ? 'red' : 'yellow' %} + {% set status_color = collector.counterrors ? 'red' : collector.countwarnings ? 'yellow' : 'none' %} {{ include('@WebProfiler/Icon/logger.svg') }} {{ collector.counterrors ?: (collector.countdeprecations + collector.countwarnings) }} {% endset %} @@ -23,7 +23,7 @@
                  Deprecations - {{ collector.countdeprecations|default(0) }} + {{ collector.countdeprecations|default(0) }}
                  {% endset %} @@ -32,7 +32,7 @@ {% endblock %} {% block menu %} - + {{ include('@WebProfiler/Icon/logger.svg') }} Logs {% if collector.counterrors or collector.countdeprecations or collector.countwarnings %} @@ -46,182 +46,185 @@ {% block panel %}

                  Log Messages

                  - {% if collector.logs is empty %} + {% if collector.processedLogs is empty %}

                  No log messages available.

                  {% else %} - {# sort collected logs in groups #} - {% set deprecation_logs, debug_logs, info_and_error_logs, silenced_logs = [], [], [], [] %} - {% for log in collector.logs %} - {% if log.scream is defined and not log.scream %} - {% set deprecation_logs = deprecation_logs|merge([log]) %} - {% elseif log.scream is defined and log.scream %} - {% set silenced_logs = silenced_logs|merge([log]) %} - {% elseif log.priorityName == 'DEBUG' %} - {% set debug_logs = debug_logs|merge([log]) %} - {% else %} - {% set info_and_error_logs = info_and_error_logs|merge([log]) %} - {% endif %} - {% endfor %} + {% set has_error_logs = collector.processedLogs|column('type')|filter(type => 'error' == type)|length > 0 %} + {% set has_deprecation_logs = collector.processedLogs|column('type')|filter(type => 'deprecation' == type)|length > 0 %} -
                  -
                  -

                  Info. & Errors {{ collector.counterrors ?: info_and_error_logs|length }}

                  -

                  Informational and error log messages generated during the execution of the application.

                  + {% set filters = collector.filters %} +
                  +
                  +
                    +
                  • + + +
                  • -
                    - {% if info_and_error_logs is empty %} -
                    -

                    There are no log messages of this level.

                    -
                    - {% else %} - {{ helper.render_table(info_and_error_logs, 'info', true) }} - {% endif %} -
                    +
                  • + + +
                  • + +
                  • + + +
                  • +
                  -
                  - {# 'deprecation_logs|length' is not used because deprecations are - now grouped and the group count doesn't match the message count #} -

                  Deprecations {{ collector.countdeprecations|default(0) }}

                  -

                  Log messages generated by using features marked as deprecated.

                  +
                  + + {{ include('@WebProfiler/Icon/filter.svg') }} + Level ({{ filters.priority|length - 1 }}) + -
                  - {% if deprecation_logs is empty %} -
                  -

                  There are no log messages about deprecated features.

                  +
                  +
                  + + +
                  + + {% for label, value in filters.priority %} +
                  + +
                  - {% else %} - {{ helper.render_table(deprecation_logs, 'deprecation', false, true) }} - {% endif %} + {% endfor %}
                  -
                  +
                  -
                  -

                  Debug {{ debug_logs|length }}

                  -

                  Unimportant log messages generated during the execution of the application.

                  +
                  + + {{ include('@WebProfiler/Icon/filter.svg') }} + Channel ({{ filters.channel|length - 1 }}) + -
                  - {% if debug_logs is empty %} -
                  -

                  There are no log messages of this level.

                  +
                  +
                  + + +
                  + + {% for value in filters.channel %} +
                  + +
                  - {% else %} - {{ helper.render_table(debug_logs, 'debug') }} - {% endif %} + {% endfor %}
                  -
                  - -
                  -

                  PHP Notices {{ collector.countscreams|default(0) }}

                  -

                  Log messages generated by PHP notices silenced with the @ operator.

                  - -
                  - {% if silenced_logs is empty %} -
                  -

                  There are no log messages of this level.

                  -
                  - {% else %} - {{ helper.render_table(silenced_logs, 'silenced') }} - {% endif %} -
                  -
                  - - {% set compilerLogTotal = 0 %} - {% for logs in collector.compilerLogs %} - {% set compilerLogTotal = compilerLogTotal + logs|length %} - {% endfor %} -
                  -

                  Container {{ compilerLogTotal }}

                  -

                  Log messages generated during the compilation of the service container.

                  - -
                  - {% if collector.compilerLogs is empty %} -
                  -

                  There are no compiler log messages.

                  -
                  - {% else %} - - - - - - - - - - {% for class, logs in collector.compilerLogs %} - - - - - {% endfor %} - -
                  ClassMessages
                  - {% set context_id = 'context-compiler-' ~ loop.index %} - - {{ class }} - -
                  -
                    - {% for log in logs %} -
                  • {{ profiler_dump_log(log.message) }}
                  • - {% endfor %} -
                  -
                  -
                  {{ logs|length }}
                  - {% endif %} -
                  -
                  - +
                  - {% endif %} -{% endblock %} -{% macro render_table(logs, category = '', show_level = false, is_deprecation = false) %} - {% import _self as helper %} - {% set channel_is_defined = (logs|first).channel is defined %} + + + + + -
                  - - - - {% if channel_is_defined %}{% endif %} - - - + + + + - - {% for log in logs %} - {% set css_class = is_deprecation ? '' - : log.priorityName in ['CRITICAL', 'ERROR', 'ALERT', 'EMERGENCY'] ? 'status-error' - : log.priorityName == 'WARNING' ? 'status-warning' - %} - - + + {% for log in collector.processedLogs %} + {% set css_class = 'error' == log.type ? 'error' + : (log.priorityName == 'WARNING' or 'deprecation' == log.type) ? 'warning' + : 'silenced' == log.type ? 'silenced' + %} + + - {% endif %} + + + {% endfor %} + +
                  {{ show_level ? 'Level' : 'Time' }}ChannelMessage
                  TimeMessage
                  - {% if show_level %} - {{ log.priorityName }} - {% endif %} - {{ log.timestamp|date('H:i:s') }} -
                  + - {% if channel_is_defined %} - - {{ log.channel }} - {% if log.errorCount is defined and log.errorCount > 1 %} - ({{ log.errorCount }} times) + {% if log.type in ['error', 'deprecation', 'silenced'] or 'WARNING' == log.priorityName %} + + {% if 'error' == log.type or 'WARNING' == log.priorityName %} + {{ log.priorityName|lower }} + {% else %} + {{ log.type|lower }} + {% endif %} + + {% else %} + + {{ log.priorityName|lower }} + {% endif %} + {{ helper.render_log_message('debug', loop.index, log) }} +
                  - {{ helper.render_log_message(category, loop.index, log) }} +
                  +

                  There are no log messages.

                  +
                  + + + {% endif %} + + {% set compilerLogTotal = 0 %} + {% for logs in collector.compilerLogs %} + {% set compilerLogTotal = compilerLogTotal + logs|length %} + {% endfor %} + +
                  + +

                  Container Compilation Logs ({{ compilerLogTotal }})

                  +

                  Log messages generated during the compilation of the service container.

                  +
                  + + {% if collector.compilerLogs is empty %} +
                  +

                  There are no compiler log messages.

                  +
                  + {% else %} + + + + + - {% endfor %} - -
                  MessagesClass
                  -{% endmacro %} + + + + {% for class, logs in collector.compilerLogs %} + + {{ logs|length }} + + {% set context_id = 'context-compiler-' ~ loop.index %} + + + +
                  +
                    + {% for log in logs %} +
                  • {{ profiler_dump_log(log.message) }}
                  • + {% endfor %} +
                  +
                  + + + {% endfor %} + + + {% endif %} +
                  +{% endblock %} {% macro render_log_message(category, log_index, log) %} {% set has_context = log.context is defined and log.context is not empty %} @@ -231,26 +234,41 @@ {{ profiler_dump_log(log.message) }} {% else %} {{ profiler_dump_log(log.message, log.context) }} + {% endif %} -
                  + + {% if has_trace %} + {% set trace_id = 'trace-' ~ category ~ '-' ~ log_index %} + -
                  - {{ profiler_dump(log.context, maxDepth=1) }} -
                  +
                  + {{ profiler_dump(log.context.exception.trace, maxDepth=1) }} +
                  + {% endif %} + + {% if has_context %} +
                  + {{ profiler_dump(log.context, maxDepth=1) }} +
                  + {% endif %} {% if has_trace %}
                  {{ profiler_dump(log.context.exception.trace, maxDepth=1) }}
                  {% endif %} - {% endif %} +
                  {% endmacro %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/mailer.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/mailer.html.twig new file mode 100644 index 0000000000..dab2e9c6c0 --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/mailer.html.twig @@ -0,0 +1,217 @@ +{% extends '@WebProfiler/Profiler/layout.html.twig' %} + +{% block toolbar %} + {% set events = collector.events %} + + {% if events.messages|length %} + {% set icon %} + {% include('@WebProfiler/Icon/mailer.svg') %} + {{ events.messages|length }} + {% endset %} + + {% set text %} +
                  + Queued messages + {{ events.events|filter(e => e.isQueued())|length }} +
                  +
                  + Sent messages + {{ events.events|filter(e => not e.isQueued())|length }} +
                  + {% endset %} + + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { 'link': profiler_url }) }} + {% endif %} +{% endblock %} + +{% block head %} + {{ parent() }} + +{% endblock %} + +{% block menu %} + {% set events = collector.events %} + + + {{ include('@WebProfiler/Icon/mailer.svg') }} + + E-mails + {% if events.messages|length > 0 %} + + {{ events.messages|length }} + + {% endif %} + +{% endblock %} + +{% block panel %} + {% set events = collector.events %} + +

                  Emails

                  + + {% if not events.messages|length %} +
                  +

                  No emails were sent.

                  +
                  + {% endif %} + +
                  +
                  + {{ events.events|filter(e => e.isQueued())|length }} + Queued +
                  + +
                  + {{ events.events|filter(e => not e.isQueued())|length }} + Sent +
                  +
                  + + {% for transport in events.transports %} +
                  +
                  + {% for event in events.events(transport) %} + {% set message = event.message %} +
                  +

                  Email {{ event.isQueued() ? 'queued' : 'sent via ' ~ transport }}

                  +
                  +
                  + {% if message.headers is not defined %} + {# RawMessage instance #} +
                  +
                  {{ message.toString() }}
                  +
                  + {% else %} + {# Message instance #} +
                  +
                  +
                  +

                  Headers

                  +
                  + Subject +

                  {{ message.headers.get('subject').bodyAsString() ?? '(empty)' }}

                  +
                  +
                  + From +
                  {{ (message.headers.get('from').bodyAsString() ?? '(empty)')|replace({'From:': ''}) }}
                  + + To +
                  {{ (message.headers.get('to').bodyAsString() ?? '(empty)')|replace({'To:': ''}) }}
                  +
                  +
                  + Headers +
                  {% for header in message.headers.all|filter(header => (header.name ?? '') not in ['Subject', 'From', 'To']) %}
                  +                                                                {{- header.toString }}
                  +                                                            {%~ endfor %}
                  +
                  +
                  +
                  +
                  + {% if message.htmlBody is defined %} + {# Email instance #} + {% set htmlBody = message.htmlBody() %} + {% if htmlBody is not null %} +
                  +

                  HTML Preview

                  +
                  +
                  +                                                                
                  +                                                            
                  +
                  +
                  +
                  +

                  HTML Content

                  +
                  +
                  +                                                                {%- if message.htmlCharset() %}
                  +                                                                    {{- htmlBody|convert_encoding('UTF-8', message.htmlCharset()) }}
                  +                                                                {%- else %}
                  +                                                                    {{- htmlBody }}
                  +                                                                {%- endif -%}
                  +                                                            
                  +
                  +
                  + {% endif %} + {% set textBody = message.textBody() %} + {% if textBody is not null %} +
                  +

                  Text Content

                  +
                  +
                  +                                                                {%- if message.textCharset() %}
                  +                                                                    {{- textBody|convert_encoding('UTF-8', message.textCharset()) }}
                  +                                                                {%- else %}
                  +                                                                    {{- textBody }}
                  +                                                                {%- endif -%}
                  +                                                            
                  +
                  +
                  + {% endif %} + {% for attachment in message.attachments %} +
                  +

                  Attachment #{{ loop.index }}

                  +
                  +
                  {{ attachment.toString() }}
                  +
                  +
                  + {% endfor %} + {% endif %} +
                  +

                  Parts Hierarchy

                  +
                  +
                  {{ message.body().asDebugString() }}
                  +
                  +
                  +
                  +

                  Raw

                  +
                  +
                  {{ message.toString() }}
                  +
                  +
                  +
                  +
                  + {% endif %} +
                  +
                  +
                  + {% endfor %} +
                  +
                  + {% endfor %} +{% endblock %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/memory.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/memory.html.twig index 49878a72d5..1336a57a23 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/memory.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/memory.html.twig @@ -2,7 +2,7 @@ {% block toolbar %} {% set icon %} - {% set status_color = (collector.memory / 1024 / 1024) > 50 ? 'yellow' : '' %} + {% set status_color = (collector.memory / 1024 / 1024) > 50 ? 'yellow' %} {{ include('@WebProfiler/Icon/memory.svg') }} {{ '%.1f'|format(collector.memory / 1024 / 1024) }} MiB diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/messenger.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/messenger.html.twig new file mode 100644 index 0000000000..b48aaa82e5 --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/messenger.html.twig @@ -0,0 +1,201 @@ +{% extends '@WebProfiler/Profiler/layout.html.twig' %} + +{% import _self as helper %} + +{% block toolbar %} + {% if collector.messages|length > 0 %} + {% set status_color = collector.exceptionsCount ? 'red' %} + {% set icon %} + {{ include('@WebProfiler/Icon/messenger.svg') }} + {{ collector.messages|length }} + {% endset %} + + {% set text %} + {% for bus in collector.buses %} + {% set exceptionsCount = collector.exceptionsCount(bus) %} +
                  + {{ bus }} + + {{ collector.messages(bus)|length }} + +
                  + {% endfor %} + {% endset %} + + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: 'messenger', status: status_color }) }} + {% endif %} +{% endblock %} + +{% block menu %} + + {{ include('@WebProfiler/Icon/messenger.svg') }} + Messages + {% if collector.exceptionsCount > 0 %} + + {{ collector.exceptionsCount }} + + {% endif %} + +{% endblock %} + +{% block head %} + {{ parent() }} + +{% endblock %} + +{% block panel %} + {% import _self as helper %} + +

                  Messages

                  + + {% if collector.messages is empty %} +
                  +

                  No messages have been collected.

                  +
                  + {% else %} +
                  +
                  + {% set messages = collector.messages %} + {% set exceptionsCount = collector.exceptionsCount %} +

                  All{{ messages|length }}

                  + +
                  +

                  Ordered list of dispatched messages across all your buses

                  + {{ helper.render_bus_messages(messages, true) }} +
                  +
                  + + {% for bus in collector.buses %} +
                  + {% set messages = collector.messages(bus) %} + {% set exceptionsCount = collector.exceptionsCount(bus) %} +

                  {{ bus }}{{ messages|length }}

                  + +
                  +

                  Ordered list of messages dispatched on the {{ bus }} bus

                  + {{ helper.render_bus_messages(messages) }} +
                  +
                  + {% endfor %} +
                  + {% endif %} + +{% endblock %} + +{% macro render_bus_messages(messages, showBus = false) %} + {% set discr = random() %} + {% for dispatchCall in messages %} + + + + + + + + + + + {% if showBus %} + + + + + {% endif %} + + + + + + + + + {% if dispatchCall.stamps_after_dispatch is defined %} + + + + + {% endif %} + {% if dispatchCall.exception is defined %} + + + + + {% endif %} + +
                  + {{ profiler_dump(dispatchCall.message.type) }} + {% if showBus %} + {{ dispatchCall.bus }} + {% endif %} + {% if dispatchCall.exception is defined %} + exception + {% endif %} + + {{ include('@WebProfiler/images/icon-minus-square.svg') }} + {{ include('@WebProfiler/images/icon-plus-square.svg') }} + +
                  + + + +
                  Bus{{ dispatchCall.bus }}
                  Message{{ profiler_dump(dispatchCall.message.value, maxDepth=2) }}
                  Envelope stamps when dispatching + {% for item in dispatchCall.stamps %} + {{ profiler_dump(item) }} + {% else %} + No items + {% endfor %} +
                  Envelope stamps after dispatch + {% for item in dispatchCall.stamps_after_dispatch %} + {{ profiler_dump(item) }} + {% else %} + No items + {% endfor %} +
                  Exception + {{ profiler_dump(dispatchCall.exception.value, maxDepth=1) }} +
                  + {% endfor %} +{% endmacro %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/notifier.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/notifier.html.twig new file mode 100644 index 0000000000..dd17fab989 --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/notifier.html.twig @@ -0,0 +1,168 @@ +{% extends '@WebProfiler/Profiler/layout.html.twig' %} + +{% block toolbar %} + {% set events = collector.events %} + + {% if events.messages|length %} + {% set icon %} + {% include('@WebProfiler/Icon/notifier.svg') %} + {{ events.messages|length }} + {% endset %} + + {% set text %} +
                  + Sent notifications + {{ events.messages|length }} +
                  + + {% for transport in events.transports %} +
                  + {{ transport }} + {{ events.messages(transport)|length }} +
                  + {% endfor %} + {% endset %} + + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { 'link': profiler_url }) }} + {% endif %} +{% endblock %} + +{% block head %} + {{ parent() }} + +{% endblock %} + +{% block menu %} + {% set events = collector.events %} + + + {{ include('@WebProfiler/Icon/notifier.svg') }} + + Notifications + {% if events.messages|length > 0 %} + + {{ events.messages|length }} + + {% endif %} + +{% endblock %} + +{% block panel %} + {% set events = collector.events %} + +

                  Notifications

                  + + {% if not events.messages|length %} +
                  +

                  No notifications were sent.

                  +
                  + {% endif %} + +
                  + {% for transport in events.transports %} +
                  + {{ events.messages(transport)|length }} + {{ transport }} +
                  + {% endfor %} +
                  + + {% for transport in events.transports %} +

                  {{ transport }}

                  + +
                  +
                  + {% for event in events.events(transport) %} + {% set message = event.message %} +
                  +

                  Message #{{ loop.index }} ({{ event.isQueued() ? 'queued' : 'sent' }})

                  +
                  +
                  +
                  + Subject +

                  {{ message.getSubject() ?? '(empty)' }}

                  +
                  + {% if message.getNotification is defined %} +
                  +
                  +
                  + Content +
                  {{ message.getNotification().getContent() ?? '(empty)' }}
                  + Importance +
                  {{ message.getNotification().getImportance() }}
                  +
                  +
                  +
                  + {% endif %} +
                  +
                  + {% if message.getNotification is defined %} +
                  +

                  Notification

                  + {% set notification = event.message.getNotification() %} +
                  +
                  +                                                            {{- 'Subject: ' ~ notification.getSubject() }}
                  + {{- 'Content: ' ~ notification.getContent() }}
                  + {{- 'Importance: ' ~ notification.getImportance() }}
                  + {{- 'Emoji: ' ~ (notification.getEmoji() is empty ? '(empty)' : notification.getEmoji()) }}
                  + {{- 'Exception: ' ~ notification.getException() ?? '(empty)' }}
                  + {{- 'ExceptionAsString: ' ~ (notification.getExceptionAsString() is empty ? '(empty)' : notification.getExceptionAsString()) }} +
                  +
                  +
                  + {% endif %} +
                  +

                  Message Options

                  +
                  +
                  +                                                            {%- if message.getOptions() is null %}
                  +                                                                {{- '(empty)' }}
                  +                                                            {%- else %}
                  +                                                                {{- message.getOptions()|json_encode(constant('JSON_PRETTY_PRINT')) }}
                  +                                                            {%- endif %}
                  +                                                        
                  +
                  +
                  +
                  +
                  +
                  +
                  +
                  + {% endfor %} +
                  +
                  + {% endfor %} +{% endblock %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/request.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/request.html.twig index a1ff4afd01..18311c169f 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/request.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/request.html.twig @@ -12,9 +12,10 @@ {% endset %} {% endif %} - {% if collector.forward|default(false) %} + {% if collector.forwardtoken %} + {% set forward_profile = profile.childByToken(collector.forwardtoken) %} {% set forward_handler %} - {{ helper.set_handler(collector.forward.controller) }} + {{ helper.set_handler(forward_profile ? forward_profile.collector('request').controller : 'n/a') }} {% endset %} {% endif %} @@ -24,7 +25,7 @@ {{ collector.statuscode }} {% if collector.route %} {% if collector.redirect %}{{ include('@WebProfiler/Icon/redirect.svg') }}{% endif %} - {% if collector.forward|default(false) %}{{ include('@WebProfiler/Icon/forward.svg') }}{% endif %} + {% if collector.forwardtoken %}{{ include('@WebProfiler/Icon/forward.svg') }}{% endif %} {{ 'GET' != collector.method ? collector.method }} @ {{ collector.route }} {% endif %} @@ -49,13 +50,6 @@ {{ request_handler }}
                  - {% if collector.controller.class is defined -%} -
                  - Controller class - {{ collector.controller.class }} -
                  - {%- endif %} -
                  Route name {{ collector.route|default('n/a') }} @@ -65,6 +59,11 @@ Has session {% if collector.sessionmetadata|length %}yes{% else %}no{% endif %}
                  + +
                  + Stateless Check + {% if collector.statelesscheck %}yes{% else %}no{% endif %} +
                  {% if redirect_handler is defined -%} @@ -88,7 +87,7 @@ Forwarded to {{ forward_handler }} - ({{ collector.forward.token }}) + ({{ collector.forwardtoken }})
                  @@ -137,6 +136,16 @@ {{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestrequest, maxDepth: 1 }, with_context = false) }} {% endif %} +

                  Uploaded Files

                  + + {% if collector.requestfiles is empty %} +
                  +

                  No files were uploaded

                  +
                  + {% else %} + {{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestfiles, maxDepth: 1 }, with_context = false) }} + {% endif %} +

                  Request Attributes

                  {% if collector.requestattributes.all is empty %} @@ -157,17 +166,33 @@

                  Request content not available (it was retrieved as a resource).

                  {% elseif collector.content %} -
                  -
                  {{ collector.content }}
                  +
                  + {% set prettyJson = collector.isJsonRequest ? collector.prettyJson : null %} + {% if prettyJson is not null %} +
                  +

                  Pretty

                  +
                  +
                  +
                  {{ prettyJson }}
                  +
                  +
                  +
                  + {% endif %} + +
                  +

                  Raw

                  +
                  +
                  +
                  {{ collector.content }}
                  +
                  +
                  +
                  {% else %}

                  No content

                  {% endif %} - -

                  Server Parameters

                  - {{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestserver }, with_context = false) }}
    @@ -208,7 +233,7 @@
    @@ -249,6 +322,22 @@
    +
    +

    Server Parameters

    +
    +

    Server Parameters

    +

    Defined in .env

    + {{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.dotenvvars }, with_context = false) }} + +

    Defined as regular env variables

    + {% set requestserver = [] %} + {% for key, value in collector.requestserver|filter((_, key) => key not in collector.dotenvvars.keys) %} + {% set requestserver = requestserver|merge({(key): value}) %} + {% endfor %} + {{ include('@WebProfiler/Profiler/table.html.twig', { data: requestserver }, with_context = false) }} +
    +
    + {% if profile.parent %}

    Parent Request

    @@ -287,7 +376,7 @@ {% if controller.class is defined -%} {%- if method|default(false) %}{{ method }}{% endif -%} {%- set link = controller.file|file_link(controller.line) %} - {%- if link %}{% else %}{% endif %} + {%- if link %}{% else %}{% endif %} {%- if route|default(false) -%} @{{ route }} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/router.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/router.html.twig index 94faa719cd..a1449c2b27 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/router.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/router.html.twig @@ -10,5 +10,5 @@ {% endblock %} {% block panel %} - {{ render(path('_profiler_router', { token: token })) }} + {{ render(controller('web_profiler.controller.router::panelAction', { token: token })) }} {% endblock %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/time.css.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/time.css.twig new file mode 100644 index 0000000000..b64b6ff869 --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/time.css.twig @@ -0,0 +1,64 @@ +/* Legend */ + +.sf-profiler-timeline .legends .timeline-category { + border: none; + background: none; + border-left: 1em solid transparent; + line-height: 1em; + margin: 0 1em 0 0; + padding: 0 0.5em; + display: none; + opacity: 0.5; +} + +.sf-profiler-timeline .legends .timeline-category.active { + opacity: 1; +} + +.sf-profiler-timeline .legends .timeline-category.present { + display: inline-block; +} + +.timeline-graph { + margin: 1em 0; + width: 100%; + background-color: var(--table-background); + border: 1px solid var(--table-border); +} + +/* Typography */ + +.timeline-graph .timeline-label { + font-family: var(--font-sans-serif); + font-size: 12px; + line-height: 12px; + font-weight: normal; + fill: var(--color-text); +} + +.timeline-graph .timeline-label .timeline-sublabel { + margin-left: 1em; + fill: var(--color-muted); +} + +.timeline-graph .timeline-subrequest, +.timeline-graph .timeline-border { + fill: none; + stroke: var(--table-border); + stroke-width: 1px; +} + +.timeline-graph .timeline-subrequest { + fill: url(#subrequest); + fill-opacity: 0.5; +} + +.timeline-subrequest-pattern { + fill: var(--table-border); +} + +/* Timeline periods */ + +.timeline-graph .timeline-period { + stroke-width: 0; +} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/time.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/time.html.twig index a9fd19225d..0ed3ddc09b 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/time.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/time.html.twig @@ -2,23 +2,11 @@ {% import _self as helper %} -{% if colors is not defined %} - {% set colors = { - 'default': '#999', - 'section': '#444', - 'event_listener': '#00B8F5', - 'template': '#66CC00', - 'doctrine': '#FF6633', - } %} -{% endif %} - - {% block toolbar %} {% set has_time_events = collector.events|length > 0 %} - {% set total_time = has_time_events ? '%.0f'|format(collector.duration) : 'n/a' %} {% set initialization_time = collector.events|length ? '%.0f'|format(collector.inittime) : 'n/a' %} - {% set status_color = has_time_events and collector.duration > 1000 ? 'yellow' : '' %} + {% set status_color = has_time_events and collector.duration > 1000 ? 'yellow' %} {% set icon %} {{ include('@WebProfiler/Icon/time.svg') }} @@ -101,7 +89,7 @@
    {% elseif collector.events is empty %}
    -

    No timing events have been recorded. Are you sure that debugging is enabled in the kernel?

    +

    No timing events have been recorded. Check that symfony/stopwatch is installed and debugging enabled in the kernel.

    {% else %} {{ block('panelContent') }} @@ -112,7 +100,7 @@ - ms + ms (timeline only displays events with a duration longer than this threshold) @@ -130,7 +118,7 @@ {% endif %} - {{ helper.display_timeline('timeline_' ~ token, collector.events, colors) }} + {{ helper.display_timeline(token, collector.events, collector.events.__section__.origin) }} {% if profile.children|length %}

    Note: sections with a striped background correspond to sub-requests.

    @@ -144,380 +132,34 @@ {{ events.__section__.duration }} ms - {{ helper.display_timeline('timeline_' ~ child.token, events, colors) }} + {{ helper.display_timeline(child.token, events, collector.events.__section__.origin) }} {% endfor %} {% endif %} - + + + + + + + + + {% endblock %} -{% macro dump_request_data(token, profile, events, origin) %} +{% macro dump_request_data(token, events, origin) %} {% autoescape 'js' %} {% from _self import dump_events %} - { - "id": "{{ token }}", - "left": {{ "%F"|format(events.__section__.origin - origin) }}, - "events": [ -{{ dump_events(events) }} - ] - } +{ + id: "{{ token }}", + left: {{ "%F"|format(events.__section__.origin - origin) }}, + end: "{{ '%F'|format(events.__section__.endtime) }}", + events: [ {{ dump_events(events) }} ], +} {% endautoescape %} {% endmacro %} @@ -525,32 +167,48 @@ {% autoescape 'js' %} {% for name, event in events %} {% if '__section__' != name %} - { - "name": "{{ name }}", - "category": "{{ event.category }}", - "origin": {{ "%F"|format(event.origin) }}, - "starttime": {{ "%F"|format(event.starttime) }}, - "endtime": {{ "%F"|format(event.endtime) }}, - "duration": {{ "%F"|format(event.duration) }}, - "memory": {{ "%.1F"|format(event.memory / 1024 / 1024) }}, - "periods": [ - {%- for period in event.periods -%} - {"start": {{ "%F"|format(period.starttime) }}, "end": {{ "%F"|format(period.endtime) }}}{{ loop.last ? '' : ', ' }} - {%- endfor -%} - ] - }{{ loop.last ? '' : ',' }} +{ + name: "{{ name }}", + category: "{{ event.category }}", + origin: {{ "%F"|format(event.origin) }}, + starttime: {{ "%F"|format(event.starttime) }}, + endtime: {{ "%F"|format(event.endtime) }}, + duration: {{ "%F"|format(event.duration) }}, + memory: {{ "%.1F"|format(event.memory / 1024 / 1024) }}, + elements: {}, + periods: [ + {%- for period in event.periods -%} + { + start: {{ "%F"|format(period.starttime) }}, + end: {{ "%F"|format(period.endtime) }}, + duration: {{ "%F"|format(period.duration) }}, + elements: {} + }, + {%- endfor -%} + ], +}, {% endif %} {% endfor %} {% endautoescape %} {% endmacro %} -{% macro display_timeline(id, events, colors) %} +{% macro display_timeline(token, events, origin) %} +{% import _self as helper %}
    -
    - {% for category, color in colors %} - {{ category }} - {% endfor %} -
    - +
    + +
    {% endmacro %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/time.js b/lib/symfony/web-profiler-bundle/Resources/views/Collector/time.js new file mode 100644 index 0000000000..156c9343ff --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/time.js @@ -0,0 +1,457 @@ +'use strict'; + +class TimelineEngine { + /** + * @param {Theme} theme + * @param {Renderer} renderer + * @param {Legend} legend + * @param {Element} threshold + * @param {Object} request + * @param {Number} eventHeight + * @param {Number} horizontalMargin + */ + constructor(theme, renderer, legend, threshold, request, eventHeight = 36, horizontalMargin = 10) { + this.theme = theme; + this.renderer = renderer; + this.legend = legend; + this.threshold = threshold; + this.request = request; + this.scale = renderer.width / request.end; + this.eventHeight = eventHeight; + this.horizontalMargin = horizontalMargin; + this.labelY = Math.round(this.eventHeight * 0.48); + this.periodY = Math.round(this.eventHeight * 0.66); + this.FqcnMatcher = /\\([^\\]+)$/i; + this.origin = null; + + this.createEventElements = this.createEventElements.bind(this); + this.createBackground = this.createBackground.bind(this); + this.createPeriod = this.createPeriod.bind(this); + this.render = this.render.bind(this); + this.renderEvent = this.renderEvent.bind(this); + this.renderPeriod = this.renderPeriod.bind(this); + this.onResize = this.onResize.bind(this); + this.isActive = this.isActive.bind(this); + + this.threshold.addEventListener('change', this.render); + this.legend.addEventListener('change', this.render); + + window.addEventListener('resize', this.onResize); + + this.createElements(); + this.render(); + } + + onResize() { + this.renderer.measure(); + this.setScale(this.renderer.width / this.request.end); + } + + setScale(scale) { + if (scale !== this.scale) { + this.scale = scale; + this.render(); + } + } + + createElements() { + this.origin = this.renderer.setFullVerticalLine(this.createBorder(), 0); + this.renderer.add(this.origin); + + this.request.events + .filter(event => event.category === 'section') + .map(this.createBackground) + .forEach(this.renderer.add); + + this.request.events + .map(this.createEventElements) + .forEach(this.renderer.add); + } + + createBackground(event) { + const subrequest = event.name === '__section__.child'; + const background = this.renderer.create('rect', subrequest ? 'timeline-subrequest' : 'timeline-border'); + + event.elements = Object.assign(event.elements || {}, { background }); + + return background; + } + + createEventElements(event) { + const { name, category, duration, memory, periods } = event; + const border = this.renderer.setFullHorizontalLine(this.createBorder(), 0); + const lines = periods.map(period => this.createPeriod(period, category)); + const label = this.createLabel(this.getShortName(name), duration, memory, periods[0]); + const title = this.renderer.createTitle(name); + const group = this.renderer.group([title, border, label].concat(lines), this.theme.getCategoryColor(event.category)); + + event.elements = Object.assign(event.elements || {}, { group, label, border }); + + this.legend.add(event.category) + + return group; + } + + createLabel(name, duration, memory, period) { + const label = this.renderer.createText(name, period.start * this.scale, this.labelY, 'timeline-label'); + const sublabel = this.renderer.createTspan(` ${duration} ms / ${memory} MiB`, 'timeline-sublabel'); + + label.appendChild(sublabel); + + return label; + } + + createPeriod(period, category) { + const timeline = this.renderer.createPath(null, 'timeline-period', this.theme.getCategoryColor(category)); + + period.draw = category === 'section' ? this.renderer.setSectionLine : this.renderer.setPeriodLine; + period.elements = Object.assign(period.elements || {}, { timeline }); + + return timeline; + } + + createBorder() { + return this.renderer.createPath(null, 'timeline-border'); + } + + isActive(event) { + const { duration, category } = event; + + return duration >= this.threshold.value && this.legend.isActive(category); + } + + render() { + const events = this.request.events.filter(this.isActive); + const width = this.renderer.width + this.horizontalMargin * 2; + const height = this.eventHeight * events.length; + + // Set view box + this.renderer.setViewBox(-this.horizontalMargin, 0, width, height); + + // Show 0ms origin + this.renderer.setFullVerticalLine(this.origin, 0); + + // Render all events + this.request.events.forEach(event => this.renderEvent(event, events.indexOf(event))); + } + + renderEvent(event, index) { + const { name, category, duration, memory, periods, elements } = event; + const { group, label, border, background } = elements; + const visible = index >= 0; + + group.setAttribute('visibility', visible ? 'visible' : 'hidden'); + + if (background) { + background.setAttribute('visibility', visible ? 'visible' : 'hidden'); + + if (visible) { + const [min, max] = this.getEventLimits(event); + + this.renderer.setFullRectangle(background, min * this.scale, max * this.scale); + } + } + + if (visible) { + // Position the group + group.setAttribute('transform', `translate(0, ${index * this.eventHeight})`); + + // Update top border + this.renderer.setFullHorizontalLine(border, 0); + + // render label and ensure it doesn't escape the viewport + this.renderLabel(label, event); + + // Update periods + periods.forEach(this.renderPeriod); + } + } + + renderLabel(label, event) { + const width = this.getLabelWidth(label); + const [min, max] = this.getEventLimits(event); + const alignLeft = (min * this.scale) + width <= this.renderer.width; + + label.setAttribute('x', (alignLeft ? min : max) * this.scale); + label.setAttribute('text-anchor', alignLeft ? 'start' : 'end'); + } + + renderPeriod(period) { + const { elements, start, duration } = period; + + period.draw(elements.timeline, start * this.scale, this.periodY, Math.max(duration * this.scale, 1)); + } + + getLabelWidth(label) { + if (typeof label.width === 'undefined') { + label.width = label.getBBox().width; + } + + return label.width; + } + + getEventLimits(event) { + if (typeof event.limits === 'undefined') { + const { periods } = event; + + event.limits = [ + periods[0].start, + periods[periods.length - 1].end + ]; + } + + return event.limits; + } + + getShortName(name) { + const matches = this.FqcnMatcher.exec(name); + + if (matches) { + return matches[1]; + } + + return name; + } +} + +class Legend { + constructor(element, theme) { + this.element = element; + this.theme = theme; + + this.toggle = this.toggle.bind(this); + this.createCategory = this.createCategory.bind(this); + + this.categories = []; + this.theme.getDefaultCategories().forEach(this.createCategory); + } + + add(category) { + this.get(category).classList.add('present'); + } + + createCategory(category) { + const element = document.createElement('button'); + element.className = `timeline-category active`; + element.style.borderColor = this.theme.getCategoryColor(category); + element.innerText = category; + element.value = category; + element.type = 'button'; + element.addEventListener('click', this.toggle); + + this.element.appendChild(element); + + this.categories.push(element); + + return element; + } + + toggle(event) { + event.target.classList.toggle('active'); + + this.emit('change'); + } + + isActive(category) { + return this.get(category).classList.contains('active'); + } + + get(category) { + return this.categories.find(element => element.value === category) || this.createCategory(category); + } + + emit(name) { + this.element.dispatchEvent(new Event(name)); + } + + addEventListener(name, callback) { + this.element.addEventListener(name, callback); + } + + removeEventListener(name, callback) { + this.element.removeEventListener(name, callback); + } +} + +class SvgRenderer { + /** + * @param {SVGElement} element + */ + constructor(element) { + this.ns = 'http://www.w3.org/2000/svg'; + this.width = null; + this.viewBox = {}; + this.element = element; + + this.add = this.add.bind(this); + + this.setViewBox(0, 0, 0, 0); + this.measure(); + } + + setViewBox(x, y, width, height) { + this.viewBox = { x, y, width, height }; + this.element.setAttribute('viewBox', `${x} ${y} ${width} ${height}`); + } + + measure() { + this.width = this.element.getBoundingClientRect().width; + } + + add(element) { + this.element.appendChild(element); + } + + group(elements, className) { + const group = this.create('g', className); + + elements.forEach(element => group.appendChild(element)); + + return group; + } + + setHorizontalLine(element, x, y, width) { + element.setAttribute('d', `M${x},${y} h${width}`); + + return element; + } + + setVerticalLine(element, x, y, height) { + element.setAttribute('d', `M${x},${y} v${height}`); + + return element; + } + + setFullHorizontalLine(element, y) { + return this.setHorizontalLine(element, this.viewBox.x, y, this.viewBox.width); + } + + setFullVerticalLine(element, x) { + return this.setVerticalLine(element, x, this.viewBox.y, this.viewBox.height); + } + + setFullRectangle(element, min, max) { + element.setAttribute('x', min); + element.setAttribute('y', this.viewBox.y); + element.setAttribute('width', max - min); + element.setAttribute('height', this.viewBox.height); + } + + setSectionLine(element, x, y, width, height = 4, markerSize = 6) { + const totalHeight = height + markerSize; + const maxMarkerWidth = Math.min(markerSize, width / 2); + const widthWithoutMarker = Math.max(0, width - (maxMarkerWidth * 2)); + + element.setAttribute('d', `M${x},${y + totalHeight} v${-totalHeight} h${width} v${totalHeight} l${-maxMarkerWidth} ${-markerSize} h${-widthWithoutMarker} Z`); + } + + setPeriodLine(element, x, y, width, height = 4, markerWidth = 2, markerHeight = 4) { + const totalHeight = height + markerHeight; + const maxMarkerWidth = Math.min(markerWidth, width); + + element.setAttribute('d', `M${x + maxMarkerWidth},${y + totalHeight} h${-maxMarkerWidth} v${-totalHeight} h${width} v${height} h${maxMarkerWidth-width}Z`); + } + + createText(content, x, y, className) { + const element = this.create('text', className); + + element.setAttribute('x', x); + element.setAttribute('y', y); + element.textContent = content; + + return element; + } + + createTspan(content, className) { + const element = this.create('tspan', className); + + element.textContent = content; + + return element; + } + + createTitle(content) { + const element = this.create('title'); + + element.textContent = content; + + return element; + } + + createPath(path = null, className = null, color = null) { + const element = this.create('path', className); + + if (path) { + element.setAttribute('d', path); + } + + if (color) { + element.setAttribute('fill', color); + } + + return element; + } + + create(name, className = null) { + const element = document.createElementNS(this.ns, name); + + if (className) { + element.setAttribute('class', className); + } + + return element; + } +} + +class Theme { + constructor(element) { + this.reservedCategoryColors = { + 'default': '#777', + 'section': '#999', + 'event_listener': '#00b8f5', + 'template': '#66cc00', + 'doctrine': '#ff6633', + 'messenger_middleware': '#bdb81e', + 'controller.argument_value_resolver': '#8c5de6', + 'http_client': '#ffa333', + }; + + this.customCategoryColors = [ + '#dbab09', // dark yellow + '#ea4aaa', // pink + '#964b00', // brown + '#22863a', // dark green + '#0366d6', // dark blue + '#17a2b8', // teal + ]; + + this.getCategoryColor = this.getCategoryColor.bind(this); + this.getDefaultCategories = this.getDefaultCategories.bind(this); + } + + getDefaultCategories() { + return Object.keys(this.reservedCategoryColors); + } + + getCategoryColor(category) { + return this.reservedCategoryColors[category] || this.getRandomColor(category); + } + + getRandomColor(category) { + // instead of pure randomness, colors are assigned deterministically based on the + // category name, to ensure that each custom category always displays the same color + return this.customCategoryColors[this.hash(category) % this.customCategoryColors.length]; + } + + // copied from https://github.com/darkskyapp/string-hash + hash(string) { + var hash = 5381; + var i = string.length; + + while(i) { + hash = (hash * 33) ^ string.charCodeAt(--i); + } + + return hash >>> 0; + } +} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/translation.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/translation.html.twig index 79c6e60ffb..a8a5c27365 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/translation.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/translation.html.twig @@ -56,19 +56,7 @@ {% endblock %} {% block panel %} - {% if collector.messages is empty %} -

    Translations

    -
    -

    No translations have been called.

    -
    - {% else %} - {{ block('panelContent') }} - {% endif %} -{% endblock %} - -{% block panelContent %} - -

    Translation Locales

    +

    Translation

    @@ -77,123 +65,112 @@
    {{ collector.fallbackLocales|join(', ')|default('-') }} - Fallback locales + Fallback locale{{ collector.fallbackLocales|length != 1 ? 's' }}
    -

    Translation Metrics

    +

    Messages

    -
    -
    - {{ collector.countDefines }} - Defined messages + {% if collector.messages is empty %} +
    +

    No translations have been called.

    + {% else %} + {% block messages %} -
    - {{ collector.countFallbacks }} - Fallback messages -
    + {# sort translation messages in groups #} + {% set messages_defined, messages_missing, messages_fallback = [], [], [] %} + {% for message in collector.messages %} + {% if message.state == constant('Symfony\\Component\\Translation\\DataCollectorTranslator::MESSAGE_DEFINED') %} + {% set messages_defined = messages_defined|merge([message]) %} + {% elseif message.state == constant('Symfony\\Component\\Translation\\DataCollectorTranslator::MESSAGE_MISSING') %} + {% set messages_missing = messages_missing|merge([message]) %} + {% elseif message.state == constant('Symfony\\Component\\Translation\\DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK') %} + {% set messages_fallback = messages_fallback|merge([message]) %} + {% endif %} + {% endfor %} -
    - {{ collector.countMissings }} - Missing messages -
    -
    +
    +
    +

    Defined {{ collector.countDefines }}

    -

    Translation Messages

    +
    +

    + These messages are correctly translated into the given locale. +

    - {% block messages %} + {% if messages_defined is empty %} +
    +

    None of the used translation messages are defined for the given locale.

    +
    + {% else %} + {% block defined_messages %} + {{ helper.render_table(messages_defined) }} + {% endblock %} + {% endif %} +
    +
    - {# sort translation messages in groups #} - {% set messages_defined, messages_missing, messages_fallback = [], [], [] %} - {% for message in collector.messages %} - {% if message.state == constant('Symfony\\Component\\Translation\\DataCollectorTranslator::MESSAGE_DEFINED') %} - {% set messages_defined = messages_defined|merge([message]) %} - {% elseif message.state == constant('Symfony\\Component\\Translation\\DataCollectorTranslator::MESSAGE_MISSING') %} - {% set messages_missing = messages_missing|merge([message]) %} - {% elseif message.state == constant('Symfony\\Component\\Translation\\DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK') %} - {% set messages_fallback = messages_fallback|merge([message]) %} - {% endif %} - {% endfor %} +
    +

    Fallback {{ collector.countFallbacks }}

    -
    -
    -

    Defined {{ collector.countDefines }}

    +
    +

    + These messages are not available for the given locale + but Symfony found them in the fallback locale catalog. +

    -
    -

    - These messages are correctly translated into the given locale. -

    + {% if messages_fallback is empty %} +
    +

    No fallback translation messages were used.

    +
    + {% else %} + {% block fallback_messages %} + {{ helper.render_table(messages_fallback, true) }} + {% endblock %} + {% endif %} +
    +
    - {% if messages_defined is empty %} -
    -

    None of the used translation messages are defined for the given locale.

    -
    - {% else %} - {% block defined_messages %} - {{ helper.render_table(messages_defined) }} - {% endblock %} - {% endif %} +
    +

    Missing {{ collector.countMissings }}

    + +
    +

    + These messages are not available for the given locale and cannot + be found in the fallback locales. Add them to the translation + catalogue to avoid Symfony outputting untranslated contents. +

    + + {% if messages_missing is empty %} +
    +

    There are no messages of this category.

    +
    + {% else %} + {% block missing_messages %} + {{ helper.render_table(messages_missing) }} + {% endblock %} + {% endif %} +
    -
    -

    Fallback {{ collector.countFallbacks }}

    + -
    -

    - These messages are not available for the given locale - but Symfony found them in the fallback locale catalog. -

    - - {% if messages_fallback is empty %} -
    -

    No fallback translation messages were used.

    -
    - {% else %} - {% block fallback_messages %} - {{ helper.render_table(messages_fallback, true) }} - {% endblock %} - {% endif %} -
    -
    - -
    -

    Missing {{ collector.countMissings }}

    - -
    -

    - These messages are not available for the given locale and cannot - be found in the fallback locales. Add them to the translation - catalogue to avoid Symfony outputting untranslated contents. -

    - - {% if messages_missing is empty %} -
    -

    There are no messages of this category.

    -
    - {% else %} - {% block missing_messages %} - {{ helper.render_table(messages_missing) }} - {% endblock %} - {% endif %} -
    -
    -
    - - {% endblock messages %} + {% endblock messages %} + {% endif %} {% endblock %} {% macro render_table(messages, is_fallback) %} - +
    - + {% if is_fallback %} {% endif %} - + @@ -201,7 +178,7 @@ {% for message in messages %} - + {% if is_fallback %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/twig.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/twig.html.twig index b473b62099..be84c19b17 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/twig.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/twig.html.twig @@ -75,20 +75,32 @@

    Rendered Templates

    -
    LocaleLocaleFallback localeDomainDomain Times used Message ID Message Preview
    {{ message.locale }}{{ message.fallbackLocale|default('-') }}
    +
    - - - - + + + + {% for template, count in collector.templates %} {%- set file = collector.templatePaths[template]|default(false) -%} {%- set link = file ? file|file_link(1) : false -%} - - + + {% endfor %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Collector/validator.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Collector/validator.html.twig index 6153637026..f3b7b7656e 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Collector/validator.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Collector/validator.html.twig @@ -2,7 +2,7 @@ {% block toolbar %} {% if collector.violationsCount > 0 or collector.calls|length %} - {% set status_color = collector.violationsCount ? 'red' : '' %} + {% set status_color = collector.violationsCount ? 'red' %} {% set icon %} {{ include('@WebProfiler/Icon/validator.svg') }} @@ -59,7 +59,12 @@ diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Icon/filter.svg b/lib/symfony/web-profiler-bundle/Resources/views/Icon/filter.svg new file mode 100644 index 0000000000..8800f1c05d --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Resources/views/Icon/filter.svg @@ -0,0 +1 @@ + diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Icon/http-client.svg b/lib/symfony/web-profiler-bundle/Resources/views/Icon/http-client.svg new file mode 100644 index 0000000000..e6b1fb2fe9 --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Resources/views/Icon/http-client.svg @@ -0,0 +1 @@ + diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Icon/mailer.svg b/lib/symfony/web-profiler-bundle/Resources/views/Icon/mailer.svg new file mode 100644 index 0000000000..ed649d0681 --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Resources/views/Icon/mailer.svg @@ -0,0 +1 @@ + diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Icon/messenger.svg b/lib/symfony/web-profiler-bundle/Resources/views/Icon/messenger.svg new file mode 100644 index 0000000000..3af5178135 --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Resources/views/Icon/messenger.svg @@ -0,0 +1 @@ + diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Icon/no-gray.svg b/lib/symfony/web-profiler-bundle/Resources/views/Icon/no-gray.svg new file mode 100644 index 0000000000..ea00891593 --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Resources/views/Icon/no-gray.svg @@ -0,0 +1 @@ + diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Icon/notifier.svg b/lib/symfony/web-profiler-bundle/Resources/views/Icon/notifier.svg new file mode 100644 index 0000000000..0648f126e9 --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Resources/views/Icon/notifier.svg @@ -0,0 +1 @@ + diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Icon/twig.svg b/lib/symfony/web-profiler-bundle/Resources/views/Icon/twig.svg index 8c6ad014e4..4a6ef7ab32 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Icon/twig.svg +++ b/lib/symfony/web-profiler-bundle/Resources/views/Icon/twig.svg @@ -1 +1 @@ - + diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/base.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/base.html.twig index 5386b01464..1a74431d89 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/base.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/base.html.twig @@ -4,16 +4,31 @@ - Symfony Profiler + {% block title %}Symfony Profiler{% endblock %} {% block head %} - {% endblock %} + + if (null === localStorage.getItem('symfony/profiler/theme') || 'theme-auto' === localStorage.getItem('symfony/profiler/theme')) { + document.body.classList.add((matchMedia('(prefers-color-scheme: dark)').matches ? 'theme-dark' : 'theme-light')); + // needed to respond dynamically to OS changes without having to refresh the page + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { + document.body.classList.remove('theme-light', 'theme-dark'); + document.body.classList.add(e.matches ? 'theme-dark' : 'theme-light'); + }); + } else { + document.body.classList.add(localStorage.getItem('symfony/profiler/theme')); + } + + document.body.classList.add(localStorage.getItem('symfony/profiler/width') || 'width-normal'); + + {% block body '' %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/base_js.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/base_js.html.twig index 18900811ae..af36bc0331 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/base_js.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/base_js.html.twig @@ -1,10 +1,11 @@ -{# This file is partially duplicated in TwigBundle/Resources/views/base_js.html.twig. If you - make any change in this file, verify the same change is needed in the other file. #} +{# This file is partially duplicated in src/Symfony/Component/ErrorHandler/Resources/assets/js/exception.js. + If you make any change in this file, verify the same change is needed in the other file. #} /* 1) { - setTimeout(function(){ - options.maxTries--; - request(url, onSuccess, onError, payload, options); - }, 1000); + if (xhr.status == 404 && options.retry && !options.stop) { + setTimeout(function() { + if (options.stop) { + return; + } + request(url, onSuccess, onError, payload, options, tries + 1); + }, delay); return null; } @@ -50,6 +79,11 @@ (onError || noop)(xhr); } }; + + if (options.onSend) { + options.onSend(tries); + } + xhr.send(payload || ''); }; @@ -83,6 +117,10 @@ if (ret = allHeaders.match(/^x-debug-token-link:\s+(.*)$/im)) { stackElement.profilerUrl = ret[1]; } + if (ret = allHeaders.match(/^Symfony-Debug-Toolbar-Replace:\s+(.*)$/im)) { + stackElement.toolbarReplaceFinished = false; + stackElement.toolbarReplace = '1' === ret[1]; + } }; var successStreak = 4; @@ -124,7 +162,7 @@ var nbOfAjaxRequest = tbody.rows.length; if (nbOfAjaxRequest >= 100) { - tbody.deleteRow(nbOfAjaxRequest - 1); + tbody.deleteRow(0); } var request = requestStack[index]; @@ -132,6 +170,14 @@ var row = document.createElement('tr'); request.DOMNode = row; + var requestNumberCell = document.createElement('td'); + requestNumberCell.textContent = index + 1; + row.appendChild(requestNumberCell); + + var profilerCell = document.createElement('td'); + profilerCell.textContent = 'n/a'; + row.appendChild(profilerCell); + var methodCell = document.createElement('td'); methodCell.textContent = request.method; row.appendChild(methodCell); @@ -164,29 +210,47 @@ durationCell.textContent = 'n/a'; row.appendChild(durationCell); - var profilerCell = document.createElement('td'); - profilerCell.textContent = 'n/a'; - row.appendChild(profilerCell); + request.liveDurationHandle = setInterval(function() { + durationCell.textContent = (new Date() - request.start) + ' ms'; + }, 100); row.className = 'sf-ajax-request sf-ajax-request-loading'; - tbody.insertBefore(row, tbody.firstChild); + tbody.insertBefore(row, null); + + var toolbarInfo = document.querySelector('.sf-toolbar-block-ajax .sf-toolbar-info'); + toolbarInfo.scrollTop = toolbarInfo.scrollHeight; renderAjaxRequests(); }; var finishAjaxRequest = function(index) { var request = requestStack[index]; + clearInterval(request.liveDurationHandle); + if (!request.DOMNode) { return; } + + if (request.toolbarReplace && !request.toolbarReplaceFinished && request.profile) { + /* Flag as complete because finishAjaxRequest can be called multiple times. */ + request.toolbarReplaceFinished = true; + /* Search up through the DOM to find the toolbar's container ID. */ + for (var elem = request.DOMNode; elem && elem !== document; elem = elem.parentNode) { + if (elem.id.match(/^sfwdt/)) { + Sfjs.loadToolbar(elem.id.replace(/^sfwdt/, ''), request.profile); + break; + } + } + } + pendingRequests--; var row = request.DOMNode; /* Unpack the children from the row */ - var methodCell = row.children[0]; - var statusCodeCell = row.children[2]; + var profilerCell = row.children[1]; + var methodCell = row.children[2]; + var statusCodeCell = row.children[4]; var statusCodeElem = statusCodeCell.children[0]; - var durationCell = row.children[4]; - var profilerCell = row.children[5]; + var durationCell = row.children[6]; if (request.error) { row.className = 'sf-ajax-request sf-ajax-request-error'; @@ -211,7 +275,7 @@ } if (request.duration) { - durationCell.textContent = request.duration + 'ms'; + durationCell.textContent = request.duration + ' ms'; } if (request.profilerUrl) { @@ -225,19 +289,6 @@ renderAjaxRequests(); }; - var addEventListener; - - var el = document.createElement('div'); - if (!('addEventListener' in el)) { - addEventListener = function (element, eventName, callback) { - element.attachEvent('on' + eventName, callback); - }; - } else { - addEventListener = function (element, eventName, callback) { - element.addEventListener(eventName, callback, false); - }; - } - {% if excluded_ajax_paths is defined %} if (window.fetch && window.fetch.polyfill === undefined) { var oldFetch = window.fetch; @@ -279,6 +330,8 @@ stackElement.statusCode = r.status; stackElement.profile = r.headers.get('x-debug-token'); stackElement.profilerUrl = r.headers.get('x-debug-token-link'); + stackElement.toolbarReplaceFinished = false; + stackElement.toolbarReplace = '1' === r.headers.get('Symfony-Debug-Toolbar-Replace'); finishAjaxRequest(idx); }, function (e){ stackElement.error = true; @@ -298,13 +351,13 @@ /* prevent logging AJAX calls to static and inline files, like templates */ var path = url; - if (url.substr(0, 1) === '/') { + if (url.slice(0, 1) === '/') { if (0 === url.indexOf('{{ request.basePath|e('js') }}')) { - path = url.substr({{ request.basePath|length }}); + path = url.slice({{ request.basePath|length }}); } } else if (0 === url.indexOf('{{ (request.schemeAndHttpHost ~ request.basePath)|e('js') }}')) { - path = url.substr({{ (request.schemeAndHttpHost ~ request.basePath)|length }}); + path = url.slice({{ (request.schemeAndHttpHost ~ request.basePath)|length }}); } if (!path.match(new RegExp({{ excluded_ajax_paths|json_encode|raw }}))) { @@ -356,6 +409,14 @@ renderAjaxRequests: renderAjaxRequests, + getSfwdt: function(token) { + if (!this.sfwdt) { + this.sfwdt = document.getElementById('sfwdt' + token); + } + + return this.sfwdt; + }, + load: function(selector, url, onSuccess, onError, options) { var el = document.getElementById(selector); @@ -366,12 +427,15 @@ el.innerHTML = xhr.responseText; el.setAttribute('data-sfurl', url); removeClass(el, 'loading'); + var pending = pendingRequests; for (var i = 0; i < requestStack.length; i++) { startAjaxRequest(i); if (requestStack[i].duration) { finishAjaxRequest(i); } } + /* Revert the pending state in case there was a start called without a finish above. */ + pendingRequests = pending; (onSuccess || noop)(xhr, el); }, function(xhr) { (onError || noop)(xhr, el); }, @@ -383,6 +447,189 @@ return this; }, + showToolbar: function(token) { + var sfwdt = this.getSfwdt(token); + removeClass(sfwdt, 'sf-display-none'); + + if (getPreference('toolbar/displayState') == 'none') { + document.getElementById('sfToolbarMainContent-' + token).style.display = 'none'; + document.getElementById('sfToolbarClearer-' + token).style.display = 'none'; + document.getElementById('sfMiniToolbar-' + token).style.display = 'block'; + } else { + document.getElementById('sfToolbarMainContent-' + token).style.display = 'block'; + document.getElementById('sfToolbarClearer-' + token).style.display = 'block'; + document.getElementById('sfMiniToolbar-' + token).style.display = 'none'; + } + }, + + hideToolbar: function(token) { + var sfwdt = this.getSfwdt(token); + addClass(sfwdt, 'sf-display-none'); + }, + + initToolbar: function(token) { + this.showToolbar(token); + + var hideButton = document.getElementById('sfToolbarHideButton-' + token); + var hideButtonSvg = hideButton.querySelector('svg'); + hideButtonSvg.setAttribute('aria-hidden', 'true'); + hideButtonSvg.setAttribute('focusable', 'false'); + addEventListener(hideButton, 'click', function (event) { + event.preventDefault(); + + var p = this.parentNode; + p.style.display = 'none'; + (p.previousElementSibling || p.previousSibling).style.display = 'none'; + document.getElementById('sfMiniToolbar-' + token).style.display = 'block'; + setPreference('toolbar/displayState', 'none'); + }); + + var showButton = document.getElementById('sfToolbarMiniToggler-' + token); + var showButtonSvg = showButton.querySelector('svg'); + showButtonSvg.setAttribute('aria-hidden', 'true'); + showButtonSvg.setAttribute('focusable', 'false'); + addEventListener(showButton, 'click', function (event) { + event.preventDefault(); + + var elem = this.parentNode; + if (elem.style.display == 'none') { + document.getElementById('sfToolbarMainContent-' + token).style.display = 'none'; + document.getElementById('sfToolbarClearer-' + token).style.display = 'none'; + elem.style.display = 'block'; + } else { + document.getElementById('sfToolbarMainContent-' + token).style.display = 'block'; + document.getElementById('sfToolbarClearer-' + token).style.display = 'block'; + elem.style.display = 'none' + } + + setPreference('toolbar/displayState', 'block'); + }); + }, + + loadToolbar: function(token, newToken) { + var that = this; + var triesCounter = document.getElementById('sfLoadCounter-' + token); + + var options = { + retry: true, + onSend: function (count) { + if (count === 3) { + that.initToolbar(token); + } + + if (triesCounter) { + triesCounter.textContent = count; + } + }, + }; + + var cancelButton = document.getElementById('sfLoadCancel-' + token); + if (cancelButton) { + addEventListener(cancelButton, 'click', function (event) { + event.preventDefault(); + + options.stop = true; + that.hideToolbar(token); + }); + } + + newToken = (newToken || token); + + this.load( + 'sfwdt' + token, + '{{ url("_wdt", { "token": "xxxxxx" })|escape('js') }}'.replace(/xxxxxx/, newToken), + function(xhr, el) { + /* Do nothing in the edge case where the toolbar has already been replaced with a new one */ + if (!document.getElementById('sfToolbarMainContent-' + newToken)) { + return; + } + + /* Evaluate in global scope scripts embedded inside the toolbar */ + var i, scripts = [].slice.call(el.querySelectorAll('script')); + for (i = 0; i < scripts.length; ++i) { + eval.call({}, scripts[i].firstChild.nodeValue); + } + + el.style.display = -1 !== xhr.responseText.indexOf('sf-toolbarreset') ? 'block' : 'none'; + + if (el.style.display == 'none') { + return; + } + + that.initToolbar(newToken); + + /* Handle toolbar-info position */ + var toolbarBlocks = [].slice.call(el.querySelectorAll('.sf-toolbar-block')); + for (i = 0; i < toolbarBlocks.length; ++i) { + toolbarBlocks[i].onmouseover = function () { + var toolbarInfo = this.querySelectorAll('.sf-toolbar-info')[0]; + var pageWidth = document.body.clientWidth; + var elementWidth = toolbarInfo.offsetWidth; + var leftValue = (elementWidth + this.offsetLeft) - pageWidth; + var rightValue = (elementWidth + (pageWidth - this.offsetLeft)) - pageWidth; + + /* Reset right and left value, useful on window resize */ + toolbarInfo.style.right = ''; + toolbarInfo.style.left = ''; + + if (elementWidth > pageWidth) { + toolbarInfo.style.left = 0; + } + else if (leftValue > 0 && rightValue > 0) { + toolbarInfo.style.right = (rightValue * -1) + 'px'; + } else if (leftValue < 0) { + toolbarInfo.style.left = 0; + } else { + toolbarInfo.style.right = '0px'; + } + }; + } + + renderAjaxRequests(); + addEventListener(document.querySelector('.sf-toolbar-ajax-clear'), 'click', function() { + requestStack = []; + renderAjaxRequests(); + successStreak = 4; + document.querySelector('.sf-toolbar-ajax-request-list').innerHTML = ''; + }); + addEventListener(document.querySelector('.sf-toolbar-block-ajax'), 'mouseenter', function (event) { + var elem = document.querySelector('.sf-toolbar-block-ajax .sf-toolbar-info'); + elem.scrollTop = elem.scrollHeight; + }); + addEventListener(document.querySelector('.sf-toolbar-block-ajax > .sf-toolbar-icon'), 'click', function (event) { + event.preventDefault(); + + toggleClass(this.parentNode, 'hover'); + }); + + var dumpInfo = document.querySelector('.sf-toolbar-block-dump .sf-toolbar-info'); + if (null !== dumpInfo) { + addEventListener(dumpInfo, 'sfbeforedumpcollapse', function () { + dumpInfo.style.minHeight = dumpInfo.getBoundingClientRect().height+'px'; + }); + addEventListener(dumpInfo, 'mouseleave', function () { + dumpInfo.style.minHeight = ''; + }); + } + }, + function(xhr) { + if (xhr.status !== 0 && !options.stop) { + var sfwdt = that.getSfwdt(token); + sfwdt.innerHTML = '\ +
    \ +
    \ + An error occurred while loading the web debug toolbar. Open the web profiler.\ +
    \ + '; + sfwdt.setAttribute('class', 'sf-toolbar sf-error-toolbar'); + } + }, + options + ); + + return this; + }, + toggle: function(selector, elOn, elOff) { var tmp = elOn.style.display, el = document.getElementById(selector); @@ -402,17 +649,18 @@ /* create the tab navigation for each group of tabs */ for (var i = 0; i < tabGroups.length; i++) { - var tabs = tabGroups[i].querySelectorAll('.tab'); + var tabs = tabGroups[i].querySelectorAll(':scope > .tab'); var tabNavigation = document.createElement('ul'); tabNavigation.className = 'tab-navigation'; + var selectedTabId = 'tab-' + i + '-0'; /* select the first tab by default */ for (var j = 0; j < tabs.length; j++) { var tabId = 'tab-' + i + '-' + j; var tabTitle = tabs[j].querySelector('.tab-title').innerHTML; var tabNavigationItem = document.createElement('li'); tabNavigationItem.setAttribute('data-tab-id', tabId); - if (j == 0) { addClass(tabNavigationItem, 'active'); } + if (hasClass(tabs[j], 'active')) { selectedTabId = tabId; } if (hasClass(tabs[j], 'disabled')) { addClass(tabNavigationItem, 'disabled'); } tabNavigationItem.innerHTML = tabTitle; tabNavigation.appendChild(tabNavigationItem); @@ -422,11 +670,12 @@ } tabGroups[i].insertBefore(tabNavigation, tabGroups[i].firstChild); + addClass(document.querySelector('[data-tab-id="' + selectedTabId + '"]'), 'active'); } /* display the active tab and add the 'click' event listeners */ for (i = 0; i < tabGroups.length; i++) { - tabNavigation = tabGroups[i].querySelectorAll('.tab-navigation li'); + tabNavigation = tabGroups[i].querySelectorAll(':scope > .tab-navigation li'); for (j = 0; j < tabNavigation.length; j++) { tabId = tabNavigation[j].getAttribute('data-tab-id'); @@ -528,9 +777,92 @@ }); } + /* Prevents from disallowing clicks on "copy to clipboard" elements inside toggles */ + var copyToClipboardElements = toggles[i].querySelectorAll('span[data-clipboard-text]'); + for (var k = 0; k < copyToClipboardElements.length; k++) { + addEventListener(copyToClipboardElements[k], 'click', function(e) { + e.stopPropagation(); + }); + } + toggles[i].setAttribute('data-processed', 'true'); } - } + }, + + initializeLogsTable: function() { + Sfjs.updateLogsTable(); + + document.querySelectorAll('.log-filter input').forEach((input) => { + input.addEventListener('change', () => { Sfjs.updateLogsTable(); }); + }); + + document.querySelectorAll('.filter-select-all-or-none button').forEach((link) => { + link.addEventListener('click', () => { + const selectAll = link.classList.contains('select-all'); + link.closest('.log-filter-content').querySelectorAll('input').forEach((input) => { + input.checked = selectAll; + }); + + Sfjs.updateLogsTable(); + }); + }); + + document.body.addEventListener('click', (event) => { + document.querySelectorAll('details.log-filter').forEach((filterElement) => { + if (!filterElement.contains(event.target) && filterElement.open) { + filterElement.open = false; + } + }); + }); + }, + + updateLogsTable: function() { + const selectedType = document.querySelector('#log-filter-type input:checked').value; + const priorities = document.querySelectorAll('#log-filter-priority input'); + const allPriorities = Array.from(priorities).map((input) => input.value); + const selectedPriorities = Array.from(priorities).filter((input) => input.checked).map((input) => input.value); + const channels = document.querySelectorAll('#log-filter-channel input'); + const selectedChannels = Array.from(channels).filter((input) => input.checked).map((input) => input.value); + + const logs = document.querySelector('table.logs'); + if (null === logs) { + return; + } + + /* hide rows that don't match the current filters */ + let numVisibleRows = 0; + logs.querySelectorAll('tbody tr').forEach((row) => { + if ('all' !== selectedType && selectedType !== row.getAttribute('data-type')) { + row.style.display = 'none'; + return; + } + + const priority = row.getAttribute('data-priority'); + if (false === selectedPriorities.includes(priority) && true === allPriorities.includes(priority)) { + row.style.display = 'none'; + return; + } + + if ('' !== row.getAttribute('data-channel') && false === selectedChannels.includes(row.getAttribute('data-channel'))) { + row.style.display = 'none'; + return; + } + + row.style.display = 'table-row'; + numVisibleRows++; + }); + + document.querySelector('table.logs').style.display = 0 === numVisibleRows ? 'none' : 'table'; + document.querySelector('.no-logs-message').style.display = 0 === numVisibleRows ? 'block' : 'none'; + + /* update the selected totals of all filters */ + document.querySelector('#log-filter-priority .filter-active-num').innerText = (priorities.length === selectedPriorities.length) ? 'All' : selectedPriorities.length; + document.querySelector('#log-filter-channel .filter-active-num').innerText = (channels.length === selectedChannels.length) ? 'All' : selectedChannels.length; + + /* update the currently selected "log type" tab */ + document.querySelectorAll('#log-filter-type li').forEach((tab) => tab.classList.remove('active')); + document.querySelector(`#log-filter-type input[value="${selectedType}"]`).parentElement.classList.add('active'); + }, }; })(); @@ -538,5 +870,5 @@ Sfjs.createTabs(); Sfjs.createToggles(); }); - +} /*]]>*/ diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/cancel.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/cancel.html.twig new file mode 100644 index 0000000000..6f1763d3a2 --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/cancel.html.twig @@ -0,0 +1,25 @@ +{% block toolbar %} + {% set icon %} + {{ include('@WebProfiler/Icon/symfony.svg') }} + + + Loading… + + {% endset %} + + {% set text %} +
    + Loading the web debug toolbar… +
    +
    + Attempt # +
    +
    + + + +
    + {% endset %} + + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url }) }} +{% endblock %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/layout.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/layout.html.twig index 822323315e..379653cf93 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/layout.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/layout.html.twig @@ -6,7 +6,8 @@
    {% block summary %} {% if profile is defined %} - {% set status_code = ('request' in profile.collectors|keys) ? profile.getcollector('request').statuscode|default(0) : 0 %} + {% set request_collector = profile.collectors.request|default(false) %} + {% set status_code = request_collector ? request_collector.statuscode|default(0) : 0 %} {% set css_class = status_code > 399 ? 'status-error' : status_code > 299 ? 'status-warning' : 'status-success' %}
    @@ -16,10 +17,13 @@ {{ profile.url }} {% else %} {{ profile.url }} + {% set referer = request_collector ? request_collector.requestheaders.get('referer') : null %} + {% if referer %} + Return to referer URL + {% endif %} {% endif %} - {% set request_collector = profile.collectors.request|default(false) %} {% if request_collector and request_collector.redirect -%} {%- set redirect = request_collector.redirect -%} {%- set controller = redirect.controller -%} @@ -44,18 +48,22 @@ {%- endif %} - {% if request_collector and request_collector.forward|default(false) and request_collector.forward.controller.class is defined -%} - {%- set forward = request_collector.forward -%} - {%- set controller = forward.controller -%} + {% if request_collector and request_collector.forwardtoken -%} + {% set forward_profile = profile.childByToken(request_collector.forwardtoken) %} + {% set controller = forward_profile ? forward_profile.collector('request').controller : 'n/a' %} {%- endif %} @@ -73,7 +81,7 @@
    Profiled on
    -
    {{ profile.time|date('r') }}
    +
    Token
    {{ profile.token }}
    @@ -100,7 +108,7 @@ {{ include('@WebProfiler/Icon/search.svg') }} Search - {{ render(path('_profiler_search_bar', request.query.all)) }} + {{ render(controller('web_profiler.controller.profiler::searchBarAction', request.query.all)) }}
    @@ -115,13 +123,15 @@ {%- endif -%} {%- endset %} {% if menu is not empty %} -
  • +
  • {{ menu|raw }}
  • {% endif %} {% endfor %} {% endif %} + + {{ include('@WebProfiler/Profiler/settings.html.twig') }}
    @@ -138,6 +148,6 @@ event.preventDefault(); Sfjs.toggleClass(document.getElementById('sidebar'), 'expanded'); }) - }()) + }()); {% endblock %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/profiler.css.twig b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/profiler.css.twig index 40138d07ff..24ec0863d6 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/profiler.css.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/profiler.css.twig @@ -1,19 +1,121 @@ -{# Mixins - ========================================================================= #} -{% set mixins = { - 'break_long_words': '-ms-word-break: break-all; word-break: break-all; word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto;', - 'monospace_font': 'font-family: monospace; font-size: 13px; font-size-adjust: 0.5;', - 'sans_serif_font': 'font-family: Helvetica, Arial, sans-serif;', - 'subtle_border_and_shadow': 'background: #FFF; border: 1px solid #E0E0E0; box-shadow: 0px 0px 1px rgba(128, 128, 128, .2);' -} %} - -{# when updating any of these colors, do the same in toolbar.css.twig #} -{% set colors = { 'success': '#4F805D', 'warning': '#A46A1F', 'error': '#B0413E' } %} - +{# This file is partially duplicated in TwigBundle/Resources/views/exceotion.css.twig. + If you make any change in this file, verify the same change is needed in the other file. #} {# Normalization (normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css) ========================================================================= #} -html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0} +*{-webkit-box-sizing: border-box;-moz-box-sizing: border-box;box-sizing: border-box}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0} + +:root { + --font-sans-serif: Helvetica, Arial, sans-serif; + --page-background: #f9f9f9; + --color-text: #222; + --color-muted: #999; + --color-link: #218BC3; + /* when updating any of these colors, do the same in toolbar.css.twig */ + --color-success: #4f805d; + --color-warning: #a46a1f; + --color-error: #b0413e; + --badge-background: #f5f5f5; + --badge-color: #666; + --badge-warning-background: #FEF3C7; + --badge-warning-color: #B45309; + --badge-danger-background: #FEE2E2; + --badge-danger-color: #B91C1C; + --tab-background: #fff; + --tab-color: #444; + --tab-active-background: #666; + --tab-active-color: #fafafa; + --tab-disabled-background: #f5f5f5; + --tab-disabled-color: #999; + --log-filter-button-background: #fff; + --log-filter-button-border: #999; + --log-filter-button-color: #555; + --log-filter-active-num-color: #2563EB; + --log-timestamp-color: #555; + --metric-value-background: #fff; + --metric-value-color: inherit; + --metric-unit-color: #999; + --metric-label-background: #e0e0e0; + --metric-label-color: inherit; + --trace-selected-background: #F7E5A1; + --table-border: #e0e0e0; + --table-background: #fff; + --table-header: #e0e0e0; + --info-background: #ddf; + --tree-active-background: #F7E5A1; + --exception-title-color: var(--base-2); + --shadow: 0px 0px 1px rgba(128, 128, 128, .2); + --border: 1px solid #e0e0e0; + --background-error: var(--color-error); + --highlight-comment: #969896; + --highlight-default: #222222; + --highlight-keyword: #a71d5d; + --highlight-string: #183691; + --highlight-selected-line: rgba(255, 255, 153, 0.5); + --base-0: #fff; + --base-1: #f5f5f5; + --base-2: #e0e0e0; + --base-3: #ccc; + --base-4: #666; + --base-5: #444; + --base-6: #222; + --card-label-background: #eee; + --card-label-color: var(--base-6); +} + +.theme-dark { + --page-background: #36393e; + --color-text: #e0e0e0; + --color-muted: #777; + --color-link: #93C5FD; + --color-error: #d43934; + --badge-background: #555; + --badge-color: #ddd; + --badge-warning-background: #B45309; + --badge-warning-color: #FEF3C7; + --badge-danger-background: #B91C1C; + --badge-danger-color: #FEE2E2; + --tab-background: #555; + --tab-color: #ccc; + --tab-active-background: #888; + --tab-active-color: #fafafa; + --tab-disabled-background: var(--page-background); + --tab-disabled-color: #777; + --log-filter-button-background: #555; + --log-filter-button-border: #999; + --log-filter-button-color: #ccc; + --log-filter-active-num-color: #93C5FD; + --log-timestamp-color: #ccc; + --metric-value-background: #555; + --metric-value-color: inherit; + --metric-unit-color: #999; + --metric-label-background: #777; + --metric-label-color: #e0e0e0; + --trace-selected-background: #71663acc; + --table-border: #444; + --table-background: #333; + --table-header: #555; + --info-background: rgba(79, 148, 195, 0.5); + --tree-active-background: var(--metric-label-background); + --exception-title-color: var(--base-2); + --shadow: 0px 0px 1px rgba(32, 32, 32, .2); + --border: 1px solid #666; + --background-error: #b0413e; + --highlight-comment: #dedede; + --highlight-default: var(--base-6); + --highlight-keyword: #ff413c; + --highlight-string: #70a6fd; + --highlight-selected-line: rgba(14, 14, 14, 0.5); + --base-0: #2e3136; + --base-1: #444; + --base-2: #666; + --base-3: #666; + --base-4: #666; + --base-5: #e0e0e0; + --base-6: #f5f5f5; + --card-label-background: var(--tab-active-background); + --card-label-color: var(--tab-active-color); +} {# Basic styles ========================================================================= #} @@ -22,11 +124,11 @@ html, body { width: 100%; } body { - background-color: #F9F9F9; - color: #222; + background-color: var(--page-background); + color: var(--base-6); display: flex; flex-direction: column; - {{ mixins.sans_serif_font|raw }} + font-family: var(--font-sans-serif); font-size: 14px; line-height: 1.4; } @@ -50,7 +152,7 @@ h4 { } h2 span, h3 span, h4 span, h2 small, h3 small, h4 small { - color: #999; + color: var(--color-muted); } li { @@ -63,7 +165,7 @@ p { } a { - color: #218BC3; + color: var(--color-link); text-decoration: none; } a:hover { @@ -91,24 +193,25 @@ h4 a:hover { } abbr { - border-bottom: 1px dotted #444; + border-bottom: 1px dotted var(--base-5); cursor: help; } code, pre { - {{ mixins.monospace_font|raw }} + font-family: monospace; + font-size: 13px; } -{# Buttons +{# Buttons (the colors of this element don't change based on the selected theme) ------------------------------------------------------------------------- #} button { - {{ mixins.sans_serif_font|raw }} + font-family: var(--font-sans-serif); } .btn { background: #777; border-radius: 2px; border: 0; - color: #F5F5F5; + color: #f5f5f5; display: inline-block; padding: .5em .75em; } @@ -127,7 +230,7 @@ button { } .btn-link { border-color: transparent; - color: #218BC3; + color: var(--color-link); text-decoration: none; background-color: transparent; outline: none; @@ -141,13 +244,15 @@ button { {# Tables ------------------------------------------------------------------------- #} table, tr, th, td { - background: #FFF; + background: var(--table-background); border-collapse: collapse; line-height: 1.5; vertical-align: top; } table { - {{ mixins.subtle_border_and_shadow|raw }}; + background: var(--base-0); + border: var(--border); + box-shadow: var(--shadow); margin: 1em 0; width: 100%; } @@ -161,7 +266,7 @@ table th { text-align: left; } table thead th { - background-color: #E0E0E0; + background-color: var(--table-header); } table thead th.key { width: 19%; @@ -173,9 +278,10 @@ table thead.small th { table tbody th, table tbody td { - {{ mixins.monospace_font|raw }} - border: 1px solid #E0E0E0; + border: 1px solid var(--base-2); border-width: 1px 0; + font-family: monospace; + font-size: 13px; } table tbody div { @@ -186,6 +292,11 @@ table tbody ul { padding: 0 0 0 1em; } +table thead th.num-col, +table tbody td.num-col { + text-align: center; +} + {# Utility classes ========================================================================= #} .block { @@ -207,13 +318,21 @@ table tbody ul { display: block; } .break-long-words { - {{ mixins.break_long_words|raw }} + -ms-word-break: break-all; + word-break: break-all; + word-break: break-word; + -webkit-hyphens: auto; + -moz-hyphens: auto; + hyphens: auto; } .text-small { font-size: 12px !important; } .text-muted { - color: #999; + color: var(--color-muted); +} +.text-danger { + color: var(--color-error); } .text-bold { font-weight: bold; @@ -225,23 +344,23 @@ table tbody ul { text-align: center; } .font-normal { - {{ mixins.sans_serif_font|raw }} + font-family: var(--font-sans-serif); font-size: 14px; } .help { - color: #999; + color: var(--color-muted); font-size: 14px; margin-bottom: .5em; } .empty { - border: 4px dashed #E0E0E0; - color: #999; + border: 4px dashed var(--base-2); + color: var(--color-muted); margin: 1em 0; padding: .5em 2em; } .label { - background-color: #666; + background-color: var(--base-4); color: #FAFAFA; display: inline-block; font-size: 12px; @@ -253,9 +372,9 @@ table tbody ul { min-width: 70px; text-align: center; } -.label.status-success { background: {{ colors.success|raw }}; color: #FFF; } -.label.status-warning { background: {{ colors.warning|raw }}; color: #FFF; } -.label.status-error { background: {{ colors.error|raw }}; color: #FFF; } +.label.status-success { background: var(--color-success); color: #FFF; } +.label.status-warning { background: var(--color-warning); color: #FFF; } +.label.status-error { background: var(--background-error); color: #FFF; } {# Metrics ------------------------------------------------------------------------- #} @@ -269,7 +388,10 @@ table tbody ul { } .metric { - {{ mixins.subtle_border_and_shadow|raw }}; + background: var(--metric-value-background); + border: 1px solid var(--table-border); + box-shadow: var(--shadow); + color: var(--metric-value-color); min-width: 100px; min-height: 70px; } @@ -283,13 +405,13 @@ table tbody ul { margin: 5px 0 -5px; } .metric .unit { - color: #999; + color: var(--metric-unit-color); font-size: 18px; margin-left: -4px; } .metric .label { - background: #E0E0E0; - color: #222; + background: var(--metric-label-background); + color: var(--metric-label-color); display: block; font-size: 12px; padding: 5px; @@ -329,12 +451,14 @@ table tbody ul { {# Cards ------------------------------------------------------------------------- #} .card { - {{ mixins.subtle_border_and_shadow|raw }}; + background: var(--base-0); + border: var(--border); + box-shadow: var(--shadow); margin: 1em 0; padding: 10px; } .card-block + .card-block { - border-top: 1px solid #E0E0E0; + border-top: 1px solid var(--base-2); padding-top: 10px; } .card *:first-child, @@ -342,8 +466,8 @@ table tbody ul { margin-top: 0; } .card .label { - background-color: #EEE; - color: #222; + background-color: var(--card-label-background); + color: var(--card-label-color); } {# Status @@ -364,15 +488,15 @@ table tbody ul { } tr.status-error td, tr.status-warning td { - border-bottom: 1px solid #FAFAFA; - border-top: 1px solid #FAFAFA; + border-bottom: 1px solid var(--base-2); + border-top: 1px solid var(--base-2); } .status-warning .colored { - color: {{ colors.warning|raw }}; + color: var(--color-warning); } .status-error .colored { - color: {{ colors.error|raw }}; + color: var(--color-error); } {# Syntax highlighting @@ -383,9 +507,9 @@ tr.status-warning td { } .highlight .keyword { color: #8959A8; font-weight: bold; } -.highlight .word { color: #222222; } +.highlight .word { color: var(--color-text); } .highlight .variable { color: #916319; } -.highlight .symbol { color: #222222; } +.highlight .symbol { color: var(--color-text); } .highlight .comment { color: #999999; } .highlight .backtick { color: #718C00; } .highlight .string { color: #718C00; } @@ -459,14 +583,14 @@ tr.status-warning td { margin-top: 0; } -{# Header +{# Header (the colors of this element don't change based on the selected theme) ========================================================================= #} #header { background-color: #222; overflow: hidden; } #header h1 { - color: #FFF; + color: #fff; flex: 1; font-weight: normal; font-size: 21px; @@ -474,7 +598,7 @@ tr.status-warning td { padding: 10px 10px 8px; } #header h1 span { - color: #CCC; + color: #ccc; } #header h1 svg { height: 40px; @@ -484,13 +608,13 @@ tr.status-warning td { } #header h1 svg path, #header h1 svg .sf-svg-path { - fill: #FFF; + fill: #fff; } #header .search { padding-top: 11px; } #header .search input { - border: 1px solid #DDD; + border: 1px solid #ddd; margin-right: 4px; padding: 7px 8px; width: 200px; @@ -499,25 +623,34 @@ tr.status-warning td { {# Summary ========================================================================= #} #summary .status { - background: #E0E0E0; + background: var(--base-2); border: solid rgba(0, 0, 0, 0.1); border-width: 2px 0; padding: 10px; } #summary h2, #summary h2 a { - color: #222; + color: var(--base-6); font-size: 21px; margin: 0; text-decoration: none; + vertical-align: middle; } #summary h2 a:hover { text-decoration: underline; } +#summary h2 a.referer { + margin-left: .5em; + font-size: 75%; + color: rgba(255, 255, 255, 0.5); +} +#summary h2 a.referer:before { + content: '\1F503\00a0'; +} -#summary .status-success { background: {{ colors.success|raw }}; } -#summary .status-warning { background: {{ colors.warning|raw }}; } -#summary .status-error { background: {{ colors.error|raw }}; } +#summary .status-success { background: var(--color-success); } +#summary .status-warning { background: var(--color-warning); } +#summary .status-error { background: var(--background-error); } #summary .status-success h2, #summary .status-success a, @@ -556,7 +689,7 @@ tr.status-warning td { ========================================================================= #} #sidebar { background: #444; - color: #CCC; + color: #ccc; padding-bottom: 30px; position: relative; width: 220px; @@ -585,7 +718,7 @@ tr.status-warning td { text-align: center; } #sidebar #sidebar-shortcuts .btn { - color: #F5F5F5; + color: #f5f5f5; } #sidebar #sidebar-shortcuts .btn + .btn { margin-left: 5px; @@ -594,7 +727,7 @@ tr.status-warning td { padding: .5em; } -{# Sidebar Search +{# Sidebar Search (the colors of this element don't change based on the selected theme) ------------------------------------------------------------------------- #} #sidebar-search .form-group:first-of-type { padding-top: 20px; @@ -617,8 +750,8 @@ tr.status-warning td { padding: 3px 6px; } #sidebar-search .form-group input { - background: #CCC; - border: 1px solid #999; + background: #ccc; + border: 1px solid var(--color-muted); color: #222; width: 120px; } @@ -630,7 +763,7 @@ tr.status-warning td { margin-right: 10px; } -{# Sidebar Menu +{# Sidebar Menu (the colors of this element don't change based on the selected theme) ------------------------------------------------------------------------- #} #menu-profiler { margin: 0; @@ -644,7 +777,7 @@ tr.status-warning td { #menu-profiler li a { border: solid transparent; border-width: 2px 0; - color: #CCC; + color: var(--base-3); display: block; } #menu-profiler li a:hover { @@ -702,12 +835,12 @@ tr.status-warning td { #menu-profiler li.selected a .icon svg .sf-svg-path, #menu-profiler li a:hover .icon svg path, #menu-profiler li a:hover .icon svg .sf-svg-path { - fill: #FFF; + fill: #fff; } #menu-profiler li a .count { background-color: #666; - color: #FFF; + color: #fff; display: inline-block; font-weight: bold; min-width: 10px; @@ -728,16 +861,17 @@ tr.status-warning td { } #menu-profiler .label-status-warning .count { - background: {{ colors.warning|raw }}; + background: var(--color-warning); } #menu-profiler .label-status-error .count { - background: {{ colors.error|raw }}; + background: var(--background-error); } {# Timeline panel ========================================================================= #} #timeline-control { - background: #FFF; + background: var(--table-background); + box-shadow: var(--shadow); margin: 1em 0; padding: 10px; } @@ -746,10 +880,12 @@ tr.status-warning td { margin-right: 1em; } #timeline-control input { + background: var(--metric-value-background); + border: 1px solid var(--table-border); font-size: 16px; padding: 4px; text-align: right; - width: 40px; + width: 5em; } #timeline-control .help { margin-left: 1em; @@ -759,15 +895,6 @@ tr.status-warning td { font-size: 12px; line-height: 1.5em; } -.sf-profiler-timeline .legends span { - border-left: solid 14px; - padding: 0 10px 0 5px; -} -.sf-profiler-timeline canvas { - border: 1px solid #DDD; - background: #FFF; - margin: .5em 0; -} .sf-profiler-timeline + p.help { margin-top: 0; } @@ -779,9 +906,9 @@ tr.status-warning td { padding: 0; } .tab-navigation li { - background: #FFF; - border: 1px solid #DDD; - color: #444; + background: var(--tab-background); + border: 1px solid var(--table-border); + color: var(--tab-color); cursor: pointer; display: inline-block; font-size: 16px; @@ -789,12 +916,9 @@ tr.status-warning td { padding: .5em .75em; z-index: 1; } -.tab-navigation li:hover { - background: #EEE; -} .tab-navigation li .badge { - background-color: #F5F5F5; - color: #777; + background-color: var(--base-1); + color: var(--base-4); display: inline-block; font-size: 14px; font-weight: bold; @@ -804,29 +928,24 @@ tr.status-warning td { text-align: center; white-space: nowrap; } -.tab-navigation li:hover .badge { - background: #FAFAFA; - color: #777; -} .tab-navigation li.disabled { - background: #F5F5F5; - color: #999; + background: var(--tab-disabled-background); + color: var(--tab-disabled-color); } .tab-navigation li.active { - background: #666; - border-color: #666; - color: #FAFAFA; + background: var(--tab-active-background); + color: var(--tab-active-color); z-index: 1100; } .tab-navigation li.active .badge { - background-color: #444; - color: #FFF; + background-color: var(--base-5); + color: var(--base-2); } .tab-content > *:first-child { margin-top: 0; } -.tab-navigation li .badge.status-warning { background: {{ colors.warning|raw }}; color: #FFF; } -.tab-navigation li .badge.status-error { background: {{ colors.error|raw }}; color: #FFF; } +.tab-navigation li .badge.status-warning { background: var(--color-warning); color: #FFF; } +.tab-navigation li .badge.status-error { background: var(--background-error); color: #FFF; } .sf-tabs .tab:not(:first-child) { display: none; } @@ -844,31 +963,272 @@ tr.status-warning td { display: block; } +{# Filters + ========================================================================= #} +[data-filters] { position: relative; } +[data-filtered] { cursor: pointer; } +[data-filtered]:after { content: '\00a0\25BE'; } +[data-filtered]:hover .filter-list li { display: inline-flex; } +[class*="filter-hidden-"] { display: none; } +.filter-list { position: absolute; border: var(--border); box-shadow: var(--shadow); margin: 0; padding: 0; display: flex; flex-direction: column; } +.filter-list :after { content: ''; } +.filter-list li { + background: var(--tab-disabled-background); + border-bottom: var(--border); + color: var(--tab-disabled-color); + display: none; + list-style: none; + margin: 0; + padding: 5px 10px; + text-align: left; + font-weight: normal; +} +.filter-list li.active { + background: var(--tab-background); + color: var(--tab-color); +} +.filter-list li.last-active { + background: var(--tab-active-background); + color: var(--tab-active-color); +} + +.filter-list-level li { cursor: s-resize; } +.filter-list-level li.active { cursor: n-resize; } +.filter-list-level li.last-active { cursor: default; } +.filter-list-level li.last-active:before { content: '\2714\00a0'; } +.filter-list-choice li:before { content: '\2714\00a0'; color: transparent; } +.filter-list-choice li.active:before { color: unset; } + {# Twig panel ========================================================================= #} #twig-dump pre { font-size: 12px; line-height: 1.7; + background-color: var(--base-0); + border: var(--border); + padding: 15px; + box-shadow: 0 0 1px rgba(128, 128, 128, .2); } #twig-dump span { border-radius: 2px; padding: 1px 2px; } -#twig-dump .status-error { background: transparent; color: #B0413E; } +#twig-dump .status-error { background: transparent; color: var(--color-error); } #twig-dump .status-warning { background: rgba(240, 181, 24, 0.3); } #twig-dump .status-success { background: rgba(100, 189, 99, 0.2); } +#twig-dump .status-info { background: var(--info-background); } + +#twig-table tbody td { + vertical-align: middle; +} +#twig-table tbody td > a { + margin-left: -5px; +} +#twig-table tbody td div { + margin: 0; +} + +.icon-twig { + vertical-align: text-bottom; +} +.icon-twig svg path { + fill: #7eea12; +} {# Logger panel ========================================================================= #} +.badge { + background: var(--badge-background); + border-radius: 4px; + color: var(--badge-color); + font-size: 12px; + font-weight: bold; + padding: 1px 4px; +} +.badge-warning { + background: var(--badge-warning-background); + color: var(--badge-warning-color); +} + +.log-filters { + display: flex; +} +.log-filters .log-filter { + position: relative; +} +.log-filters .log-filter + .log-filter { + margin-left: 15px; +} +.log-filters .log-filter summary { + align-items: center; + background: var(--log-filter-button-background); + border-radius: 2px; + border: 1px solid var(--log-filter-button-border); + color: var(--log-filter-button-color); + cursor: pointer; + display: flex; + padding: 5px 8px; +} +.log-filters .log-filter summary .icon { + height: 18px; + width: 18px; + margin: 0 7px 0 0; +} +.log-filters .log-filter summary svg { + height: 18px; + width: 18px; + opacity: 0.7; +} +.log-filters .log-filter summary .filter-active-num { + color: var(--log-filter-active-num-color); + font-weight: bold; + padding: 0 1px; +} +.log-filter .tab-navigation { + margin-bottom: 0; +} +.log-filter .tab-navigation li:first-child { + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; +} +.log-filter .tab-navigation li:last-child { + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; +} +.log-filter .tab-navigation li { + border-color: var(--log-filter-button-border); + padding: 0; +} +.log-filter .tab-navigation li + li { + margin-left: -5px; +} +.log-filter .tab-navigation li .badge { + font-size: 13px; + padding: 0 6px; +} +.log-filter .tab-navigation li input { + display: none; +} +.log-filter .tab-navigation li label { + align-items: center; + cursor: pointer; + padding: 5px 10px; + display: inline-flex; + font-size: 14px; +} + +.log-filters .log-filter .log-filter-content { + background: var(--base-0); + border: 1px solid var(--table-border); + border-radius: 2px; + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + padding: 15px; + position: absolute; + left: 0; + top: 36px; + max-width: 400px; + min-width: 200px; + z-index: 9999; +} +.log-filters .log-filter .log-filter-content .log-filter-option { + align-items: center; + display: flex; +} +.log-filter .filter-select-all-or-none { + margin-bottom: 10px; +} +.log-filter .filter-select-all-or-none button + button { + margin-left: 15px; +} +.log-filters .log-filter .log-filter-content .log-filter-option + .log-filter-option { + margin: 7px 0 0; +} +.log-filters .log-filter .log-filter-content .log-filter-option label { + cursor: pointer; + flex: 1; + padding-left: 10px; +} + table.logs .metadata { display: block; font-size: 12px; } +.theme-dark tr.status-error td, +.theme-dark tr.status-warning td { border-bottom: unset; border-top: unset; } + +table.logs .log-timestamp { + color: var(--log-timestamp-color); +} +table.logs .log-metadata { + margin: 8px 0 0; +} +table.logs .log-metadata > span { + display: inline-block; +} +table.logs .log-metadata > span + span { + margin-left: 10px; +} +table.logs .log-metadata .log-channel { + color: var(--base-1); + font-size: 13px; + font-weight: bold; +} +table.logs .log-metadata .log-num-occurrences { + color: var(--color-muted); + font-size: 13px; +} +.log-type-badge { + display: inline-block; + font-family: var(--font-sans-serif); + margin-top: 5px; +} +.log-type-badge.badge-deprecation { + background: var(--badge-warning-background); + color: var(--badge-warning-color); +} +.log-type-badge.badge-error { + background: var(--badge-danger-background); + color: var(--badge-danger-color); +} +.log-type-badge.badge-silenced { + background: #EDE9FE; + color: #6D28D9; +} +.theme-dark .log-type-badge.badge-silenced { + background: #5B21B6; + color: #EDE9FE; +} + +tr.log-status-warning { + border-left: 4px solid #F59E0B; +} +tr.log-status-error { + border-left: 4px solid #EF4444; +} +tr.log-status-silenced { + border-left: 4px solid #A78BFA; +} + +.container-compilation-logs { + background: var(--table-background); + border: 1px solid var(--base-2); + margin-top: 30px; + padding: 15px; +} +.container-compilation-logs summary { + cursor: pointer; +} +.container-compilation-logs summary h4 { + margin: 0 0 5px; +} +.container-compilation-logs summary p { + margin: 0; +} {# Doctrine panel ========================================================================= #} .sql-runnable { - background: #F5F5F5; + background: var(--base-1); margin: .5em 0; padding: 1em; } @@ -880,9 +1240,39 @@ table.logs .metadata { word-break: normal; } .queries-table pre { - {{ mixins.break_long_words|raw }} margin: 0; white-space: pre-wrap; + -ms-word-break: break-all; + word-break: break-all; + word-break: break-word; + -webkit-hyphens: auto; + -moz-hyphens: auto; + hyphens: auto; +} + +{# Security panel + ========================================================================= #} +#collector-content .decision-log .voter_result td { + border-top-width: 1px; + border-bottom-width: 0; + padding-bottom: 0; +} + +#collector-content .decision-log .voter_details td { + border-top-width: 0; + border-bottom-width: 1px; + padding-bottom: 0; +} + +#collector-content .decision-log .voter_details table { + border: 0; + margin: 0; + box-shadow: unset; +} + +#collector-content .decision-log .voter_details table td { + border: 0; + padding: 0 0 8px 0; } {# Validator panel @@ -894,8 +1284,8 @@ table.logs .metadata { #collector-content .sf-validator .sf-validator-context, #collector-content .sf-validator .trace { - border: 1px solid #DDD; - background: #FFF; + border: var(--border); + background: var(--base-0); padding: 10px; margin: 0.5em 0; overflow: auto; @@ -908,26 +1298,51 @@ table.logs .metadata { padding: 0; } #collector-content .sf-validator .trace li.selected { - background: rgba(255, 255, 153, 0.5); + background: var(--highlight-selected-line); +} + +{# Messenger panel + ========================================================================= #} + +#collector-content .message-bus .trace { + border: var(--border); + background: var(--base-0); + padding: 10px; + margin: 0.5em 0; + overflow: auto; +} +#collector-content .message-bus .trace { + font-size: 12px; +} +#collector-content .message-bus .trace li { + margin-bottom: 0; + padding: 0; +} +#collector-content .message-bus .trace li.selected { + background: var(--highlight-selected-line); } {# Dump panel ========================================================================= #} +pre.sf-dump, pre.sf-dump .sf-dump-default { + z-index: 1000 !important; +} + #collector-content .sf-dump { margin-bottom: 2em; } #collector-content pre.sf-dump, #collector-content .sf-dump code, #collector-content .sf-dump samp { - {{ mixins.monospace_font|raw }} + font-family: monospace; + font-size: 13px; } #collector-content .sf-dump a { cursor: pointer; } #collector-content .sf-dump pre.sf-dump, #collector-content .sf-dump .trace { - border: 1px solid #DDD; - background: #FFF; + border: var(--border); padding: 10px; margin: 0.5em 0; overflow: auto; @@ -935,18 +1350,9 @@ table.logs .metadata { #collector-content pre.sf-dump, #collector-content .sf-dump-default { - color: #CC7832; background: none; } -#collector-content .sf-dump-str { color: #629755; } -#collector-content .sf-dump-private, -#collector-content .sf-dump-protected, -#collector-content .sf-dump-public { color: #262626; } -#collector-content .sf-dump-note { color: #6897BB; } -#collector-content .sf-dump-key { color: #789339; } -#collector-content .sf-dump-ref { color: #6E6E6E; } -#collector-content .sf-dump-ellipsis { color: #CC7832; max-width: 100em; } -#collector-content .sf-dump-ellipsis-path { max-width: 5em; } +#collector-content .sf-dump-ellipsis { max-width: 100em; } #collector-content .sf-dump { margin: 0; @@ -971,14 +1377,11 @@ table.logs .metadata { margin-bottom: 0; padding: 0; } -#collector-content .sf-dump .trace li.selected { - background: rgba(255, 255, 153, 0.5); -} {# Search Results page ========================================================================= #} #search-results td { - {{ mixins.sans_serif_font|raw }} + font-family: var(--font-sans-serif); vertical-align: middle; } @@ -1047,3 +1450,30 @@ table.logs .metadata { margin-left: 2px; } } + +{# Config Options + ========================================================================= #} +body.width-full .container { + max-width: 100%; +} + +body.theme-light #collector-content .sf-dump pre.sf-dump, +body.theme-light #collector-content .sf-dump .trace { + background: #FFF; +} +body.theme-light #collector-content pre.sf-dump, +body.theme-light #collector-content .sf-dump-default { + color: #CC7832; +} +body.theme-light #collector-content .sf-dump-str { color: #629755; } +body.theme-light #collector-content .sf-dump-private, +body.theme-light #collector-content .sf-dump-protected, +body.theme-light #collector-content .sf-dump-public { color: #262626; } +body.theme-light #collector-content .sf-dump-note { color: #6897BB; } +body.theme-light #collector-content .sf-dump-key { color: #789339; } +body.theme-light #collector-content .sf-dump-ref { color: #6E6E6E; } +body.theme-light #collector-content .sf-dump-ellipsis { color: #CC7832; max-width: 100em; } +body.theme-light #collector-content .sf-dump-ellipsis-path { max-width: 5em; } +body.theme-light #collector-content .sf-dump .trace li.selected { + background: rgba(255, 255, 153, 0.5); +} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/results.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/results.html.twig index cd9fd06cc6..7ddbf9c4f0 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/results.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/results.html.twig @@ -1,5 +1,13 @@ {% extends '@WebProfiler/Profiler/layout.html.twig' %} +{% macro profile_search_filter(request, result, property) %} + {%- if request.hasSession -%} + {{ include('@WebProfiler/Icon/search.svg') }} + {%- endif -%} +{% endmacro %} + +{% import _self as helper %} + {% block summary %}
    @@ -32,28 +40,14 @@ {{ result.status_code|default('n/a') }}
    \n"; echo " \n"; echo " \n"; echo " \n"; @@ -1814,17 +1830,17 @@ EOF; echo " \n"; echo " \n"; echo " \n"; echo " \n"; echo " \n"; echo " \n"; echo " \n"; echo " \n"; @@ -2312,7 +2328,7 @@ EOF; * * @param MFElement $oNewNode The replacement * - * @since 2.7.7 3.0.1 3.1.0 N°3129 rename method (from `ReplaceWith` to `MFReplaceWith`) to avoid collision with parent `\DOMElement::replaceWith` method (different method modifier and parameters : + * @since 2.7.7 3.0.1 3.1.0 N°3129 rename method (from `ReplaceWith` to `ReplaceWithSingleNode`) to avoid collision with parent `\DOMElement::replaceWith` method (different method modifier and parameters : * throws fatal error in PHP 8.0) */ protected function ReplaceWithSingleNode($oNewNode) @@ -2522,6 +2538,8 @@ class MFDocument extends \Combodo\iTop\DesignDocument * @return string * @throws \Exception */ + // Return type union is not supported by PHP 7.4, we can remove the following PHP attribute and add the return type once iTop min PHP version is PHP 8.0+ + #[\ReturnTypeWillChange] public function saveXML(DOMNode $node = null, $options = 0) { $oRootNode = $this->firstChild; @@ -2545,12 +2563,14 @@ class MFDocument extends \Combodo\iTop\DesignDocument * * @param string $sName * @param null $value - * @param null $namespaceURI + * @param string $namespaceURI * * @return \MFElement * @throws \Exception + * + * @since 3.1.0 N°4517 $namespaceURI parameter must be empty string by default so */ - function createElement($sName, $value = null, $namespaceURI = null) + function createElement($sName, $value = null, $namespaceURI = '') { /** @var \MFElement $oElement */ $oElement = $this->importNode(new MFElement($sName, null, $namespaceURI)); diff --git a/setup/modulediscovery.class.inc.php b/setup/modulediscovery.class.inc.php index 1330f4a2cb..cfbfb36536 100644 --- a/setup/modulediscovery.class.inc.php +++ b/setup/modulediscovery.class.inc.php @@ -19,9 +19,47 @@ * */ -class MissingDependencyException extends Exception +class MissingDependencyException extends CoreException { + /** + * @see \ModuleDiscovery::OrderModulesByDependencies property init + * @var array> + * module id as key + * another array as value, containing : 'module' with module info, 'dependencies' with missing dependencies + */ public $aModulesInfo; + + /** + * @return string HTML to print to the user the modules impacted + * @since 2.7.7 3.0.2 3.1.0 PR #280 + */ + public function getHtmlDesc($sHighlightHtmlBegin = null, $sHighlightHtmlEnd = null) + { + $sErrorMessage = <<The following modules have unmet dependencies:

    +
      +HTML; + foreach ($this->aModulesInfo as $sModuleId => $aModuleErrors) { + $sModuleLabel = $aModuleErrors['module']['label']; + $aModuleMissingDependencies = $aModuleErrors['dependencies']; + $sErrorMessage .= <<{$sModuleLabel} ({$sModuleId}): +
        +HTML; + + foreach ($aModuleMissingDependencies as $sMissingModule) { + $sErrorMessage .= "
      • {$sMissingModule}
      • "; + } + $sErrorMessage .= << + +HTML; + + } + $sErrorMessage .= '
      '; + + return $sErrorMessage; + } } class ModuleDiscovery diff --git a/setup/permissions-test-folder/.htaccess b/setup/permissions-test-folder/.htaccess new file mode 100644 index 0000000000..782472c78a --- /dev/null +++ b/setup/permissions-test-folder/.htaccess @@ -0,0 +1,13 @@ +# Apache 2.4 + +Require all denied + + +# Apache 2.2 + +deny from all +Satisfy All + + +# Apache 2.2 and 2.4 +IndexIgnore * diff --git a/lib/symfony/framework-bundle/Resources/views/Form/button_label.html.php b/setup/permissions-test-folder/permissions-test-subfolder/permissions-test-file similarity index 100% rename from lib/symfony/framework-bundle/Resources/views/Form/button_label.html.php rename to setup/permissions-test-folder/permissions-test-subfolder/permissions-test-file diff --git a/setup/permissions-test-folder/web.config b/setup/permissions-test-folder/web.config new file mode 100644 index 0000000000..58c9c3ac30 --- /dev/null +++ b/setup/permissions-test-folder/web.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/setup/runtimeenv.class.inc.php b/setup/runtimeenv.class.inc.php index 988a9bf1dc..cdd1b677a1 100644 --- a/setup/runtimeenv.class.inc.php +++ b/setup/runtimeenv.class.inc.php @@ -812,10 +812,14 @@ class RunTimeEnvironment // Database is created, installation has been tracked into it return true; } - + + /** + * @param \Config $oConfig + * + * @return array|false + */ public function GetApplicationVersion(Config $oConfig) { - $aResult = false; try { CMDBSource::InitFromConfig($oConfig); @@ -829,7 +833,8 @@ class RunTimeEnvironment $this->log_error('Exception '.$e->getMessage()); return false; } - + + $aResult = []; // Scan the list of installed modules to get the version of the 'ROOT' module which holds the main application version foreach ($aSelectInstall as $aInstall) { @@ -867,7 +872,7 @@ class RunTimeEnvironment $aResult['datamodel_version'] = $aResult['product_version']; } $this->log_info("GetApplicationVersion returns: product_name: ".$aResult['product_name'].', product_version: '.$aResult['product_version']); - return $aResult; + return empty($aResult) ? false : $aResult; } public static function MakeDirSafe($sDir) @@ -1109,29 +1114,24 @@ class RunTimeEnvironment */ public function LoadData($aAvailableModules, $aSelectedModules, $bSampleData) { - $oDataLoader = new XMLDataLoader(); - - CMDBObject::SetTrackInfo("Initialization"); - $oMyChange = CMDBObject::GetCurrentChange(); + $oDataLoader = new XMLDataLoader(); + + CMDBObject::SetCurrentChangeFromParams("Initialization from XML files for the selected modules "); + $oMyChange = CMDBObject::GetCurrentChange(); SetupLog::Info("starting data load session"); - $oDataLoader->StartSession($oMyChange); - - $aFiles = array(); - $aPreviouslyLoadedFiles = array(); - foreach($aAvailableModules as $sModuleId => $aModule) - { - if (($sModuleId != ROOT_MODULE)) - { - $sRelativePath = 'env-'.$this->sTargetEnv.'/'.basename($aModule['root_dir']); - // Load data only for selected AND newly installed modules - if (in_array($sModuleId, $aSelectedModules)) - { - if ($aModule['version_db'] != '') - { - // Simulate the load of the previously loaded XML files to get the mapping of the keys - if ($bSampleData) - { + $oDataLoader->StartSession($oMyChange); + + $aFiles = array(); + $aPreviouslyLoadedFiles = array(); + foreach ($aAvailableModules as $sModuleId => $aModule) { + if (($sModuleId != ROOT_MODULE)) { + $sRelativePath = 'env-'.$this->sTargetEnv.'/'.basename($aModule['root_dir']); + // Load data only for selected AND newly installed modules + if (in_array($sModuleId, $aSelectedModules)) { + if ($aModule['version_db'] != '') { + // Simulate the load of the previously loaded XML files to get the mapping of the keys + if ($bSampleData) { $aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.struct']); $aPreviouslyLoadedFiles = static::MergeWithRelativeDir($aPreviouslyLoadedFiles, $sRelativePath, $aAvailableModules[$sModuleId]['data.sample']); } diff --git a/setup/setup.js b/setup/setup.js index bdec79eeaa..0556d9c71c 100644 --- a/setup/setup.js +++ b/setup/setup.js @@ -53,4 +53,17 @@ function ExecuteStep(sStep) } ); } +function CheckDirectoryConfFilesPermissions(sWikiVersion){ + $.ajax('permissions-test-folder/permissions-test-subfolder/permissions-test-file', + { + statusCode: { + 200: function() { + $('#details').prepend('
      Security issue: iTop is bundled with directory-level configuration files. You must check that those files will be read by your web server (eg. ' + + 'AllowOverride directive should be set to All for Apache HTTP Server) see documentation.
      '); + $(' and 1 Security issue').insertBefore('h2.message button:first'); + } + } + }); +} + CombodoTooltip.InitAllNonInstantiatedTooltips(); \ No newline at end of file diff --git a/setup/setuppage.class.inc.php b/setup/setuppage.class.inc.php index a1603323f0..de92d44501 100644 --- a/setup/setuppage.class.inc.php +++ b/setup/setuppage.class.inc.php @@ -157,7 +157,7 @@ class SetupPage extends NiceWebPage public function output() { - $sLogo = utils::GetAbsoluteUrlAppRoot().'/images/itop-logo.png?t='.utils::GetCacheBusterTimestamp(); + $sLogo = utils::GetAbsoluteUrlAppRoot().'/images/logos/logo-itop-simple-dark.svg?t='.utils::GetCacheBusterTimestamp(); $oSetupPage = UIContentBlockUIBlockFactory::MakeStandard(); $oHeader = UIContentBlockUIBlockFactory::MakeStandard('header', ['ibo-setup--header']); $oSetupPage->AddSubBlock($oHeader); diff --git a/setup/setuputils.class.inc.php b/setup/setuputils.class.inc.php index b6ccc2d400..80a41a3cfb 100644 --- a/setup/setuputils.class.inc.php +++ b/setup/setuputils.class.inc.php @@ -94,17 +94,17 @@ class CheckResult { class SetupUtils { // -- Minimum versions (requirements : forbids installation if not met) - const PHP_MIN_VERSION = '7.1.3'; // 7 will be supported until the end of 2019 (see http://php.net/supported-versions.php) - const MYSQL_MIN_VERSION = '5.7.0'; // 5.6 is no longer supported + const PHP_MIN_VERSION = '7.4.0'; + const MYSQL_MIN_VERSION = '5.7.0'; // 5.6 is no longer supported const MYSQL_NOT_VALIDATED_VERSION = ''; // MySQL 8 is now OK (N°2010 in 2.7.0) but has no query cache so mind the perf on large volumes ! // -- versions that will be the minimum in next iTop major release (warning if not met) - const PHP_NEXT_MIN_VERSION = ''; // - const MYSQL_NEXT_MIN_VERSION = ''; // no new MySQL requirement for next iTop version + const PHP_NEXT_MIN_VERSION = ''; // No new PHP requirement for next iTop version yet + const MYSQL_NEXT_MIN_VERSION = ''; // No new MySQL requirement for next iTop version yet // -- First recent version that is not yet validated by Combodo (warning) - const PHP_NOT_VALIDATED_VERSION = '8.0.0'; + const PHP_NOT_VALIDATED_VERSION = '8.2.0'; - const MIN_MEMORY_LIMIT = '32M'; + const MIN_MEMORY_LIMIT = '32M'; const SUHOSIN_GET_MAX_VALUE_LENGTH = 2048; /** @@ -151,6 +151,14 @@ class SetupUtils } $aWritableDirsErrors = self::CheckWritableDirs($aWritableDirs); $aResult = array_merge($aResult, $aWritableDirsErrors); + // Check temp dir (N°5235) : as this path isn't under APPROOT we are doing a custom check and not using \SetupUtils::CheckWritableDirs + $sTmpDir = static::GetTmpDir(); + clearstatcache(true, $sTmpDir); + if (is_writable($sTmpDir)) { + $aResult[] = new CheckResult(CheckResult::INFO, "The temp directory is writable by the application."); + } else { + $aResult[] = new CheckResult(CheckResult::WARNING, "The temp directory '".$sTmpDir."' is not writable by the application. Change its permission or use another dir (sys_temp_dir option in php.ini)."); + } $aMandatoryExtensions = self::GetPHPMandatoryExtensions(); $aOptionalExtensions = self::GetPHPOptionalExtensions(); @@ -454,12 +462,12 @@ class SetupUtils if (!empty($sPhpNextMinVersion)) { if (version_compare($sPhpVersion, self::PHP_NEXT_MIN_VERSION, '>=')) { $aResult[] = new CheckResult(CheckResult::INFO, - "The current PHP Version (".$sPhpVersion.") is greater than the minimum version required to run next ".ITOP_APPLICATION." release, which is (".self::PHP_NEXT_MIN_VERSION.")"); + "The current PHP Version (".$sPhpVersion.") is greater than the minimum version required to run next ".ITOP_APPLICATION." major release, which is (".self::PHP_NEXT_MIN_VERSION.")"); } else { $aResult[] = new CheckResult(CheckResult::WARNING, - "The current PHP Version (".$sPhpVersion.") is lower than the minimum version required to run next ".ITOP_APPLICATION." release, which is (".self::PHP_NEXT_MIN_VERSION.")"); + "The current PHP Version (".$sPhpVersion.") is lower than the minimum version required to run next ".ITOP_APPLICATION." major release, which is (".self::PHP_NEXT_MIN_VERSION.")"); } } @@ -684,6 +692,7 @@ class SetupUtils * Emulates sys_get_temp_dir if needed (PHP < 5.2.1) * * @return string Path to the system's temp directory + * @uses \sys_get_temp_dir() */ public static function GetTmpDir() { return realpath(sys_get_temp_dir()); @@ -1020,23 +1029,21 @@ class SetupUtils //-- DB connection params $oPage->add('
    '); - $oPage->add(''); - $oPage->add(''); + $oPage->add(''); - $oPage->add(''); + $oPage->add(''); $oPage->add('
    Template NameRender Count
    Template Name & PathRender Count
    {% if link %}{{ template }}{% else %}{{ template }}{% endif %}{{ count }} + {{ include('@WebProfiler/Icon/twig.svg') }} + {% if link %} + {{ template }} + + {% else %} + {{ template }} + {% endif %} + {{ count }}
    - {{ result.ip }} - {% if request.session is not null %} - - {{ include('@WebProfiler/Icon/search.svg') }} - - {% endif %} + {{ result.ip }} {{ helper.profile_search_filter(request, result, 'ip') }} - {{ result.method }} - {% if request.session is not null %} - - {{ include('@WebProfiler/Icon/search.svg') }} - - {% endif %} + {{ result.method }} {{ helper.profile_search_filter(request, result, 'method') }} {{ result.url }} - {% if request.session is not null %} - - {{ include('@WebProfiler/Icon/search.svg') }} - - {% endif %} + {{ helper.profile_search_filter(request, result, 'url') }} {{ result.time|date('d-M-Y') }} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/search.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/search.html.twig index d98414e727..7494b4ec7f 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/search.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/search.html.twig @@ -17,7 +17,7 @@
    - +
    diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/settings.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/settings.html.twig new file mode 100644 index 0000000000..4b2394687f --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/settings.html.twig @@ -0,0 +1,193 @@ + + +Settings + + + + diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/toolbar.css.twig b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/toolbar.css.twig index cf406339fe..5e675b9605 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/toolbar.css.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/toolbar.css.twig @@ -14,8 +14,10 @@ z-index: 99999; } -.sf-minitoolbar a { - display: block; +.sf-minitoolbar button { + background-color: transparent; + padding: 0; + border: none; } .sf-minitoolbar svg, .sf-minitoolbar img { @@ -71,6 +73,10 @@ display: inline-block; } +.sf-toolbarreset .sf-cancel-button { + color: #444; +} + .sf-toolbarreset .hide-button { background: #444; display: block; @@ -81,10 +87,13 @@ height: 36px; cursor: pointer; text-align: center; + border: none; + margin: 0; + padding: 0; } .sf-toolbarreset .hide-button svg { max-height: 18px; - margin-top: 10px; + margin-top: 1px; } .sf-toolbar-block { @@ -94,11 +103,14 @@ height: 36px; margin-right: 0; white-space: nowrap; + max-width: 15%; } .sf-toolbar-block > a, .sf-toolbar-block > a:hover { display: block; text-decoration: none; + background-color: transparent; + color: inherit; } .sf-toolbar-block span { @@ -236,6 +248,7 @@ div.sf-toolbar .sf-toolbar-block a:hover { padding: 0 10px; } .sf-toolbar-block-request .sf-toolbar-info-piece a { + background-color: transparent; text-decoration: none; } .sf-toolbar-block-request .sf-toolbar-info-piece a:hover { @@ -279,6 +292,8 @@ div.sf-toolbar .sf-toolbar-block a:hover { display: block; height: 36px; padding: 0 7px; + overflow: hidden; + text-overflow: ellipsis; } .sf-toolbar-block-request .sf-toolbar-icon { padding-left: 0; @@ -318,7 +333,7 @@ div.sf-toolbar .sf-toolbar-block a:hover { .sf-toolbar-block.hover .sf-toolbar-info { display: block; padding: 10px; - max-width: 480px; + max-width: 525px; max-height: 480px; word-wrap: break-word; overflow: hidden; @@ -407,34 +422,6 @@ div.sf-toolbar .sf-toolbar-block a:hover { display: none; } -/* Override the setting when the toolbar is on the top */ -{% if position == 'top' %} - .sf-minitoolbar { - border-bottom-left-radius: 4px; - border-top-left-radius: 0; - bottom: auto; - right: 0; - top: 0; - } - - .sf-toolbarreset { - bottom: auto; - box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); - top: 0; - } - - .sf-toolbar-block .sf-toolbar-info { - bottom: auto; - top: 36px; - } -{% endif %} - -{% if not floatable %} - .sf-toolbarreset { - position: static; - } -{% endif %} - /* Responsive Design */ .sf-toolbar-icon .sf-toolbar-label, .sf-toolbar-icon .sf-toolbar-value { @@ -523,7 +510,7 @@ div.sf-toolbar .sf-toolbar-block a:hover { @media (min-width: 1024px) { .sf-toolbar-block .sf-toolbar-info-piece-additional, .sf-toolbar-block .sf-toolbar-info-piece-additional-detail { - display: inline-block; + display: inline; } .sf-toolbar-block .sf-toolbar-info-piece-additional:empty, @@ -562,6 +549,11 @@ div.sf-toolbar .sf-toolbar-block a:hover { margin-right: 10px; } +.sf-full-stack { + left: 0px; + font-size: 12px; +} + /***** Media query print: Do not print the Toolbar. *****/ @media print { .sf-toolbar { diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/toolbar.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/toolbar.html.twig index a211617d72..236fc70da9 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/toolbar.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/toolbar.html.twig @@ -1,8 +1,8 @@
    @@ -10,9 +10,9 @@ {% for name, template in templates %} {% if block('toolbar', template) is defined %} {% with { - collector: profile.getcollector(name), + collector: profile ? profile.getcollector(name) : null, profiler_url: profiler_url, - token: profile.token, + token: token ?? (profile ? profile.token : null), name: name, profiler_markup_version: profiler_markup_version, csp_script_nonce: csp_script_nonce, @@ -22,9 +22,25 @@ {% endwith %} {% endif %} {% endfor %} + {% if full_stack %} +
    +
    + Using symfony/symfony is NOT supported +
    +
    +

    This project is using Symfony via the "symfony/symfony" package.

    +

    This is NOT supported anymore since Symfony 4.0.

    +

    Even if it seems to work well, it has some important limitations with no workarounds.

    +

    Using this package also makes your project slower.

    - + Please, stop using this package and replace it with individual packages instead. +
    +
    +
    + {% endif %} + +
    diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/toolbar_js.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/toolbar_js.html.twig index 7366808d11..352fbb0ea4 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/toolbar_js.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/toolbar_js.html.twig @@ -1,127 +1,21 @@ -
    +
    + {% include('@WebProfiler/Profiler/toolbar.html.twig') with { + templates: { + 'request': '@WebProfiler/Profiler/cancel.html.twig' + }, + profile: null, + profiler_url: url('_profiler', {token: token}), + profiler_markup_version: 2, + } %} +
    + {{ include('@WebProfiler/Profiler/base_js.html.twig') }} + - {{ include('@WebProfiler/Profiler/toolbar.css.twig', { 'position': position, 'floatable': true }) }} + {{ include('@WebProfiler/Profiler/toolbar.css.twig') }} /* pageWidth) { - toolbarInfo.style.left = 0; - } - else if (leftValue > 0 && rightValue > 0) { - toolbarInfo.style.right = (rightValue * -1) + 'px'; - } else if (leftValue < 0) { - toolbarInfo.style.left = 0; - } else { - toolbarInfo.style.right = '0px'; - } - }; - } - Sfjs.addEventListener(document.getElementById('sfToolbarHideButton-{{ token }}'), 'click', function (event) { - event.preventDefault(); - - var p = this.parentNode; - p.style.display = 'none'; - (p.previousElementSibling || p.previousSibling).style.display = 'none'; - document.getElementById('sfMiniToolbar-{{ token }}').style.display = 'block'; - Sfjs.setPreference('toolbar/displayState', 'none'); - }); - Sfjs.addEventListener(document.getElementById('sfToolbarMiniToggler-{{ token }}'), 'click', function (event) { - event.preventDefault(); - - var elem = this.parentNode; - if (elem.style.display == 'none') { - document.getElementById('sfToolbarMainContent-{{ token }}').style.display = 'none'; - document.getElementById('sfToolbarClearer-{{ token }}').style.display = 'none'; - elem.style.display = 'block'; - } else { - document.getElementById('sfToolbarMainContent-{{ token }}').style.display = 'block'; - document.getElementById('sfToolbarClearer-{{ token }}').style.display = 'block'; - elem.style.display = 'none' - } - - Sfjs.setPreference('toolbar/displayState', 'block'); - }); - Sfjs.renderAjaxRequests(); - Sfjs.addEventListener(document.querySelector('.sf-toolbar-block-ajax > .sf-toolbar-icon'), 'click', function (event) { - event.preventDefault(); - - Sfjs.toggleClass(this.parentNode, 'hover'); - }); - - var dumpInfo = document.querySelector('.sf-toolbar-block-dump .sf-toolbar-info'); - if (null !== dumpInfo) { - Sfjs.addEventListener(dumpInfo, 'sfbeforedumpcollapse', function () { - dumpInfo.style.minHeight = dumpInfo.getBoundingClientRect().height+'px'; - }); - Sfjs.addEventListener(dumpInfo, 'mouseleave', function () { - dumpInfo.style.minHeight = ''; - }); - } - }, - function(xhr) { - if (xhr.status !== 0) { - var sfwdt = document.getElementById('sfwdt{{ token }}'); - sfwdt.innerHTML = '\ -
    \ -
    \ - An error occurred while loading the web debug toolbar. Open the web profiler.\ -
    \ - '; - sfwdt.setAttribute('class', 'sf-toolbar sf-error-toolbar'); - } - }, - { maxTries: 5 } - ); + Sfjs.loadToolbar('{{ token }}'); })(); /*]]>*/ diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/toolbar_redirect.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/toolbar_redirect.html.twig index 35b6e90eb5..18d43b2253 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Profiler/toolbar_redirect.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Profiler/toolbar_redirect.html.twig @@ -1,4 +1,4 @@ -{% extends '@Twig/layout.html.twig' %} +{% extends '@WebProfiler/Profiler/base.html.twig' %} {% block title 'Redirection Intercepted' %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/Router/panel.html.twig b/lib/symfony/web-profiler-bundle/Resources/views/Router/panel.html.twig index ea8600a2d0..41636d1440 100644 --- a/lib/symfony/web-profiler-bundle/Resources/views/Router/panel.html.twig +++ b/lib/symfony/web-profiler-bundle/Resources/views/Router/panel.html.twig @@ -5,13 +5,6 @@ {{ request.route ?: '(none)' }} Matched route - - {% if request.route %} -
    - {{ traces|length }} - Tested routes before match -
    - {% endif %} {% if request.route %} diff --git a/lib/symfony/web-profiler-bundle/Resources/views/images/icon-minus-square.svg b/lib/symfony/web-profiler-bundle/Resources/views/images/icon-minus-square.svg new file mode 100644 index 0000000000..471c2741c7 --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Resources/views/images/icon-minus-square.svg @@ -0,0 +1 @@ + diff --git a/lib/symfony/web-profiler-bundle/Resources/views/images/icon-plus-square.svg b/lib/symfony/web-profiler-bundle/Resources/views/images/icon-plus-square.svg new file mode 100644 index 0000000000..2f5c3b3583 --- /dev/null +++ b/lib/symfony/web-profiler-bundle/Resources/views/images/icon-plus-square.svg @@ -0,0 +1 @@ + diff --git a/lib/symfony/web-profiler-bundle/Twig/WebProfilerExtension.php b/lib/symfony/web-profiler-bundle/Twig/WebProfilerExtension.php index ee998ce095..8a8721a3a1 100644 --- a/lib/symfony/web-profiler-bundle/Twig/WebProfilerExtension.php +++ b/lib/symfony/web-profiler-bundle/Twig/WebProfilerExtension.php @@ -11,7 +11,6 @@ namespace Symfony\Bundle\WebProfilerBundle\Twig; -use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; use Symfony\Component\VarDumper\Cloner\Data; use Symfony\Component\VarDumper\Dumper\HtmlDumper; use Twig\Environment; @@ -23,14 +22,11 @@ use Twig\TwigFunction; * Twig extension for the profiler. * * @author Fabien Potencier + * + * @internal */ class WebProfilerExtension extends ProfilerExtension { - /** - * @var ValueExporter - */ - private $valueExporter; - /** * @var HtmlDumper */ @@ -48,38 +44,34 @@ class WebProfilerExtension extends ProfilerExtension public function __construct(HtmlDumper $dumper = null) { - $this->dumper = $dumper ?: new HtmlDumper(); - $this->dumper->setOutput($this->output = fopen('php://memory', 'r+b')); + $this->dumper = $dumper ?? new HtmlDumper(); + $this->dumper->setOutput($this->output = fopen('php://memory', 'r+')); } - public function enter(Profile $profile) + public function enter(Profile $profile): void { ++$this->stackLevel; } - public function leave(Profile $profile) + public function leave(Profile $profile): void { if (0 === --$this->stackLevel) { - $this->dumper->setOutput($this->output = fopen('php://memory', 'r+b')); + $this->dumper->setOutput($this->output = fopen('php://memory', 'r+')); } } /** * {@inheritdoc} */ - public function getFunctions() + public function getFunctions(): array { - $profilerDump = function (Environment $env, $value, $maxDepth = 0) { - return $value instanceof Data ? $this->dumpData($env, $value, $maxDepth) : twig_escape_filter($env, $this->dumpValue($value)); - }; - return [ - new TwigFunction('profiler_dump', $profilerDump, ['is_safe' => ['html'], 'needs_environment' => true]), + new TwigFunction('profiler_dump', [$this, 'dumpData'], ['is_safe' => ['html'], 'needs_environment' => true]), new TwigFunction('profiler_dump_log', [$this, 'dumpLog'], ['is_safe' => ['html'], 'needs_environment' => true]), ]; } - public function dumpData(Environment $env, Data $data, $maxDepth = 0) + public function dumpData(Environment $env, Data $data, int $maxDepth = 0) { $this->dumper->setCharset($env->getCharset()); $this->dumper->dump($data, null, [ @@ -93,12 +85,12 @@ class WebProfilerExtension extends ProfilerExtension return str_replace("\n$1"', $message); - if (null === $context || false === strpos($message, '{')) { + if (null === $context || !str_contains($message, '{')) { return ''.$message.''; } @@ -111,20 +103,6 @@ class WebProfilerExtension extends ProfilerExtension return ''.strtr($message, $replacements).''; } - /** - * @deprecated since 3.2, to be removed in 4.0. Use the dumpData() method instead. - */ - public function dumpValue($value) - { - @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.2 and will be removed in 4.0. Use the dumpData() method instead.', __METHOD__), \E_USER_DEPRECATED); - - if (null === $this->valueExporter) { - $this->valueExporter = new ValueExporter(); - } - - return $this->valueExporter->exportValue($value); - } - /** * {@inheritdoc} */ diff --git a/lib/symfony/web-profiler-bundle/WebProfilerBundle.php b/lib/symfony/web-profiler-bundle/WebProfilerBundle.php index fecc0f365f..7fcc4ec47f 100644 --- a/lib/symfony/web-profiler-bundle/WebProfilerBundle.php +++ b/lib/symfony/web-profiler-bundle/WebProfilerBundle.php @@ -14,10 +14,14 @@ namespace Symfony\Bundle\WebProfilerBundle; use Symfony\Component\HttpKernel\Bundle\Bundle; /** - * Bundle. - * * @author Fabien Potencier */ class WebProfilerBundle extends Bundle { + public function boot() + { + if ('prod' === $this->container->getParameter('kernel.environment')) { + @trigger_error('Using WebProfilerBundle in production is not supported and puts your project at risk, disable it.', \E_USER_WARNING); + } + } } diff --git a/lib/symfony/web-profiler-bundle/composer.json b/lib/symfony/web-profiler-bundle/composer.json index e956f6f42e..3f67bb6ff5 100644 --- a/lib/symfony/web-profiler-bundle/composer.json +++ b/lib/symfony/web-profiler-bundle/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/web-profiler-bundle", "type": "symfony-bundle", - "description": "Symfony WebProfilerBundle", + "description": "Provides a development tool that gives detailed information about the execution of any request", "keywords": [], "homepage": "https://symfony.com", "license": "MIT", @@ -16,27 +16,26 @@ } ], "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/config": "~3.4|~4.0", - "symfony/http-kernel": "~3.4.25|^4.2.6", - "symfony/polyfill-php70": "~1.0", - "symfony/routing": "~3.4.7|~4.0", - "symfony/twig-bundle": "~3.4|~4.0", - "symfony/var-dumper": "~3.3|~4.0", - "twig/twig": "~1.34|~2.4" + "php": ">=7.2.5", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/framework-bundle": "^5.3|^6.0", + "symfony/http-kernel": "^5.3|^6.0", + "symfony/polyfill-php80": "^1.16", + "symfony/routing": "^4.4|^5.0|^6.0", + "symfony/twig-bundle": "^4.4|^5.0|^6.0", + "twig/twig": "^2.13|^3.0.4" }, "require-dev": { - "symfony/browser-kit": "~3.4|~4.0", - "symfony/console": "~3.4|~4.0", - "symfony/css-selector": "~3.4|~4.0", - "symfony/framework-bundle": "~3.4|~4.0", - "symfony/stopwatch": "~3.4|~4.0" + "symfony/browser-kit": "^4.4|^5.0|^6.0", + "symfony/console": "^4.4|^5.0|^6.0", + "symfony/css-selector": "^4.4|^5.0|^6.0", + "symfony/stopwatch": "^4.4|^5.0|^6.0" }, "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/event-dispatcher": "<3.3.1", - "symfony/framework-bundle": ">4.3.99", - "symfony/var-dumper": "<3.3" + "symfony/form": "<4.4", + "symfony/mailer": "<5.4", + "symfony/messenger": "<4.4", + "symfony/dependency-injection": "<5.2" }, "autoload": { "psr-4": { "Symfony\\Bundle\\WebProfilerBundle\\": "" }, diff --git a/lib/symfony/web-profiler-bundle/phpunit.xml.dist b/lib/symfony/web-profiler-bundle/phpunit.xml.dist deleted file mode 100644 index 37fd9f9895..0000000000 --- a/lib/symfony/web-profiler-bundle/phpunit.xml.dist +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - ./Tests/ - - - - - - ./ - - ./Resources - ./Tests - ./vendor - - - - diff --git a/lib/symfony/yaml/.gitignore b/lib/symfony/yaml/.gitignore deleted file mode 100644 index c49a5d8df5..0000000000 --- a/lib/symfony/yaml/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/lib/symfony/yaml/CHANGELOG.md b/lib/symfony/yaml/CHANGELOG.md index fd41c9dd22..b9561b2af2 100644 --- a/lib/symfony/yaml/CHANGELOG.md +++ b/lib/symfony/yaml/CHANGELOG.md @@ -1,6 +1,94 @@ CHANGELOG ========= +5.4 +--- + + * Add new `lint:yaml dirname --exclude=/dirname/foo.yaml --exclude=/dirname/bar.yaml` + option to exclude one or more specific files from multiple file list + * Allow negatable for the parse tags option with `--no-parse-tags` + +5.3 +--- + + * Added `github` format support & autodetection to render errors as annotations + when running the YAML linter command in a Github Action environment. + +5.1.0 +----- + + * Added support for parsing numbers prefixed with `0o` as octal numbers. + * Deprecated support for parsing numbers starting with `0` as octal numbers. They will be parsed as strings as of Symfony 6.0. Prefix numbers with `0o` + so that they are parsed as octal numbers. + + Before: + + ```yaml + Yaml::parse('072'); + ``` + + After: + + ```yaml + Yaml::parse('0o72'); + ``` + + * Added `yaml-lint` binary. + * Deprecated using the `!php/object` and `!php/const` tags without a value. + +5.0.0 +----- + + * Removed support for mappings inside multi-line strings. + * removed support for implicit STDIN usage in the `lint:yaml` command, use `lint:yaml -` (append a dash) instead to make it explicit. + +4.4.0 +----- + + * Added support for parsing the inline notation spanning multiple lines. + * Added support to dump `null` as `~` by using the `Yaml::DUMP_NULL_AS_TILDE` flag. + * deprecated accepting STDIN implicitly when using the `lint:yaml` command, use `lint:yaml -` (append a dash) instead to make it explicit. + +4.3.0 +----- + + * Using a mapping inside a multi-line string is deprecated and will throw a `ParseException` in 5.0. + +4.2.0 +----- + + * added support for multiple files or directories in `LintCommand` + +4.0.0 +----- + + * The behavior of the non-specific tag `!` is changed and now forces + non-evaluating your values. + * complex mappings will throw a `ParseException` + * support for the comma as a group separator for floats has been dropped, use + the underscore instead + * support for the `!!php/object` tag has been dropped, use the `!php/object` + tag instead + * duplicate mapping keys throw a `ParseException` + * non-string mapping keys throw a `ParseException`, use the `Yaml::PARSE_KEYS_AS_STRINGS` + flag to cast them to strings + * `%` at the beginning of an unquoted string throw a `ParseException` + * mappings with a colon (`:`) that is not followed by a whitespace throw a + `ParseException` + * the `Dumper::setIndentation()` method has been removed + * being able to pass boolean options to the `Yaml::parse()`, `Yaml::dump()`, + `Parser::parse()`, and `Dumper::dump()` methods to configure the behavior of + the parser and dumper is no longer supported, pass bitmask flags instead + * the constructor arguments of the `Parser` class have been removed + * the `Inline` class is internal and no longer part of the BC promise + * removed support for the `!str` tag, use the `!!str` tag instead + * added support for tagged scalars. + + ```yml + Yaml::parse('!foo bar', Yaml::PARSE_CUSTOM_TAGS); + // returns TaggedValue('foo', 'bar'); + ``` + 3.4.0 ----- diff --git a/lib/symfony/yaml/Command/LintCommand.php b/lib/symfony/yaml/Command/LintCommand.php index fb8e3e6598..3ebd570e76 100644 --- a/lib/symfony/yaml/Command/LintCommand.php +++ b/lib/symfony/yaml/Command/LintCommand.php @@ -11,9 +11,13 @@ namespace Symfony\Component\Yaml\Command; +use Symfony\Component\Console\CI\GithubActionReporter; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -31,6 +35,7 @@ use Symfony\Component\Yaml\Yaml; class LintCommand extends Command { protected static $defaultName = 'lint:yaml'; + protected static $defaultDescription = 'Lint a YAML file and outputs encountered errors'; private $parser; private $format; @@ -38,7 +43,7 @@ class LintCommand extends Command private $directoryIteratorProvider; private $isReadableProvider; - public function __construct($name = null, $directoryIteratorProvider = null, $isReadableProvider = null) + public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null) { parent::__construct($name); @@ -52,17 +57,18 @@ class LintCommand extends Command protected function configure() { $this - ->setDescription('Lints a file and outputs encountered errors') - ->addArgument('filename', null, 'A file or a directory or STDIN') - ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt') - ->addOption('parse-tags', null, InputOption::VALUE_NONE, 'Parse custom tags') + ->setDescription(self::$defaultDescription) + ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') + ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format') + ->addOption('exclude', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Path(s) to exclude') + ->addOption('parse-tags', null, InputOption::VALUE_NEGATABLE, 'Parse custom tags', null) ->setHelp(<<%command.name% command lints a YAML file and outputs to STDOUT the first encountered syntax error. You can validates YAML 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: @@ -73,6 +79,10 @@ Or of a whole directory: php %command.full_name% dirname php %command.full_name% dirname --format=json +You can also exclude one or more specific files: + + php %command.full_name% dirname --exclude="dirname/foo.yaml" --exclude="dirname/bar.yaml" + EOF ) ; @@ -81,32 +91,49 @@ EOF protected function execute(InputInterface $input, OutputInterface $output) { $io = new SymfonyStyle($input, $output); - $filename = $input->getArgument('filename'); + $filenames = (array) $input->getArgument('filename'); + $excludes = $input->getOption('exclude'); $this->format = $input->getOption('format'); - $this->displayCorrectFiles = $output->isVerbose(); - $flags = $input->getOption('parse-tags') ? Yaml::PARSE_CUSTOM_TAGS : 0; + $flags = $input->getOption('parse-tags'); - if (!$filename) { - if (!$stdin = $this->getStdin()) { - throw new RuntimeException('Please provide a filename or pipe file content to STDIN.'); - } - - return $this->display($io, [$this->validate($stdin, $flags)]); + if ('github' === $this->format && !class_exists(GithubActionReporter::class)) { + throw new \InvalidArgumentException('The "github" format is only available since "symfony/console" >= 5.3.'); } - if (!$this->isReadable($filename)) { - throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + if (null === $this->format) { + // Autodetect format according to CI environment + $this->format = class_exists(GithubActionReporter::class) && GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'; + } + + $flags = $flags ? Yaml::PARSE_CUSTOM_TAGS : 0; + + $this->displayCorrectFiles = $output->isVerbose(); + + if (['-'] === $filenames) { + return $this->display($io, [$this->validate(file_get_contents('php://stdin'), $flags)]); + } + + if (!$filenames) { + throw new RuntimeException('Please provide a filename or pipe file content to STDIN.'); } $filesInfo = []; - foreach ($this->getFiles($filename) as $file) { - $filesInfo[] = $this->validate(file_get_contents($file), $flags, $file); + foreach ($filenames as $filename) { + if (!$this->isReadable($filename)) { + throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + } + + foreach ($this->getFiles($filename) as $file) { + if (!\in_array($file->getPathname(), $excludes, true)) { + $filesInfo[] = $this->validate(file_get_contents($file), $flags, $file); + } + } } return $this->display($io, $filesInfo); } - private function validate($content, $flags, $file = null) + private function validate(string $content, int $flags, string $file = null) { $prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) { if (\E_USER_DEPRECATED === $level) { @@ -127,22 +154,29 @@ EOF return ['file' => $file, 'valid' => true]; } - private function display(SymfonyStyle $io, array $files) + private function display(SymfonyStyle $io, array $files): int { switch ($this->format) { case 'txt': return $this->displayTxt($io, $files); case 'json': return $this->displayJson($io, $files); + case 'github': + return $this->displayTxt($io, $files, true); default: throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)); } } - private function displayTxt(SymfonyStyle $io, array $filesInfo) + private function displayTxt(SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false): int { $countFiles = \count($filesInfo); $erroredFiles = 0; + $suggestTagOption = false; + + if ($errorAsGithubAnnotations) { + $githubReporter = new GithubActionReporter($io); + } foreach ($filesInfo as $info) { if ($info['valid'] && $this->displayCorrectFiles) { @@ -151,19 +185,27 @@ EOF ++$erroredFiles; $io->text(' ERROR '.($info['file'] ? sprintf(' in %s', $info['file']) : '')); $io->text(sprintf(' >> %s', $info['message'])); + + if (false !== strpos($info['message'], 'PARSE_CUSTOM_TAGS')) { + $suggestTagOption = true; + } + + if ($errorAsGithubAnnotations) { + $githubReporter->error($info['message'], $info['file'] ?? 'php://stdin', $info['line']); + } } } if (0 === $erroredFiles) { $io->success(sprintf('All %d YAML files contain valid syntax.', $countFiles)); } else { - $io->warning(sprintf('%d YAML files have valid syntax and %d contain errors.', $countFiles - $erroredFiles, $erroredFiles)); + $io->warning(sprintf('%d YAML files have valid syntax and %d contain errors.%s', $countFiles - $erroredFiles, $erroredFiles, $suggestTagOption ? ' Use the --parse-tags option if you want parse custom tags.' : '')); } return min($erroredFiles, 1); } - private function displayJson(SymfonyStyle $io, array $filesInfo) + private function displayJson(SymfonyStyle $io, array $filesInfo): int { $errors = 0; @@ -172,6 +214,10 @@ EOF if (!$v['valid']) { ++$errors; } + + if (isset($v['message']) && false !== strpos($v['message'], 'PARSE_CUSTOM_TAGS')) { + $v['message'] .= ' Use the --parse-tags option if you want parse custom tags.'; + } }); $io->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); @@ -179,7 +225,7 @@ EOF return min($errors, 1); } - private function getFiles($fileOrDirectory) + private function getFiles(string $fileOrDirectory): iterable { if (is_file($fileOrDirectory)) { yield new \SplFileInfo($fileOrDirectory); @@ -196,24 +242,7 @@ EOF } } - /** - * @return string|null - */ - private function getStdin() - { - if (0 !== ftell(\STDIN)) { - return null; - } - - $inputs = ''; - while (!feof(\STDIN)) { - $inputs .= fread(\STDIN, 1024); - } - - return $inputs; - } - - private function getParser() + private function getParser(): Parser { if (!$this->parser) { $this->parser = new Parser(); @@ -222,7 +251,7 @@ EOF return $this->parser; } - private function getDirectoryIterator($directory) + private function getDirectoryIterator(string $directory): iterable { $default = function ($directory) { return new \RecursiveIteratorIterator( @@ -232,22 +261,29 @@ EOF }; if (null !== $this->directoryIteratorProvider) { - return \call_user_func($this->directoryIteratorProvider, $directory, $default); + return ($this->directoryIteratorProvider)($directory, $default); } return $default($directory); } - private function isReadable($fileOrDirectory) + private function isReadable(string $fileOrDirectory): bool { $default = function ($fileOrDirectory) { return is_readable($fileOrDirectory); }; if (null !== $this->isReadableProvider) { - return \call_user_func($this->isReadableProvider, $fileOrDirectory, $default); + return ($this->isReadableProvider)($fileOrDirectory, $default); } return $default($fileOrDirectory); } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues(['txt', 'json', 'github']); + } + } } diff --git a/lib/symfony/yaml/Dumper.php b/lib/symfony/yaml/Dumper.php index 336e39c90c..711840c109 100644 --- a/lib/symfony/yaml/Dumper.php +++ b/lib/symfony/yaml/Dumper.php @@ -18,7 +18,7 @@ use Symfony\Component\Yaml\Tag\TaggedValue; * * @author Fabien Potencier * - * @final since version 3.4 + * @final */ class Dumper { @@ -29,10 +29,7 @@ class Dumper */ protected $indentation; - /** - * @param int $indentation - */ - public function __construct($indentation = 4) + public function __construct(int $indentation = 4) { if ($indentation < 1) { throw new \InvalidArgumentException('The indentation must be greater than zero.'); @@ -41,20 +38,6 @@ class Dumper $this->indentation = $indentation; } - /** - * Sets the indentation. - * - * @param int $num The amount of spaces to use for indentation of nested nodes - * - * @deprecated since version 3.1, to be removed in 4.0. Pass the indentation to the constructor instead. - */ - public function setIndentation($num) - { - @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 3.1 and will be removed in 4.0. Pass the indentation to the constructor instead.', \E_USER_DEPRECATED); - - $this->indentation = (int) $num; - } - /** * Dumps a PHP value to YAML. * @@ -62,29 +45,9 @@ class Dumper * @param int $inline The level where you switch to inline YAML * @param int $indent The level of indentation (used internally) * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string - * - * @return string The YAML representation of the PHP value */ - public function dump($input, $inline = 0, $indent = 0, $flags = 0) + public function dump($input, int $inline = 0, int $indent = 0, int $flags = 0): string { - if (\is_bool($flags)) { - @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE flag instead.', \E_USER_DEPRECATED); - - if ($flags) { - $flags = Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE; - } else { - $flags = 0; - } - } - - if (\func_num_args() >= 5) { - @trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::DUMP_OBJECT flag instead.', \E_USER_DEPRECATED); - - if (func_get_arg(4)) { - $flags |= Yaml::DUMP_OBJECT; - } - } - $output = ''; $prefix = $indent ? str_repeat(' ', $indent) : ''; $dumpObjectAsInlineMap = true; @@ -99,14 +62,31 @@ class Dumper $dumpAsMap = Inline::isHash($input); foreach ($input as $key => $value) { - if ($inline >= 1 && Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && false !== strpos($value, "\n") && false === strpos($value, "\r")) { + if ('' !== $output && "\n" !== $output[-1]) { + $output .= "\n"; + } + + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && false !== strpos($value, "\n") && false === strpos($value, "\r")) { // If the first line starts with a space character, the spec requires a blockIndicationIndicator // http://www.yaml.org/spec/1.2/spec.html#id2793979 $blockIndentationIndicator = (' ' === substr($value, 0, 1)) ? (string) $this->indentation : ''; - $output .= sprintf("%s%s%s |%s\n", $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator); + + if (isset($value[-2]) && "\n" === $value[-2] && "\n" === $value[-1]) { + $blockChompingIndicator = '+'; + } elseif ("\n" === $value[-1]) { + $blockChompingIndicator = ''; + } else { + $blockChompingIndicator = '-'; + } + + $output .= sprintf('%s%s%s |%s%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator, $blockChompingIndicator); foreach (explode("\n", $value) as $row) { - $output .= sprintf("%s%s%s\n", $prefix, str_repeat(' ', $this->indentation), $row); + if ('' === $row) { + $output .= "\n"; + } else { + $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); + } } continue; @@ -115,20 +95,20 @@ class Dumper if ($value instanceof TaggedValue) { $output .= sprintf('%s%s !%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', $value->getTag()); - if ($inline >= 1 && Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && false !== strpos($value->getValue(), "\n") && false === strpos($value->getValue(), "\r\n")) { + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && false !== strpos($value->getValue(), "\n") && false === strpos($value->getValue(), "\r\n")) { // If the first line starts with a space character, the spec requires a blockIndicationIndicator // http://www.yaml.org/spec/1.2/spec.html#id2793979 $blockIndentationIndicator = (' ' === substr($value->getValue(), 0, 1)) ? (string) $this->indentation : ''; - $output .= sprintf(" |%s\n", $blockIndentationIndicator); + $output .= sprintf(' |%s', $blockIndentationIndicator); foreach (explode("\n", $value->getValue()) as $row) { - $output .= sprintf("%s%s%s\n", $prefix, str_repeat(' ', $this->indentation), $row); + $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); } continue; } - if ($inline - 1 <= 0 || null === $value->getValue() || is_scalar($value->getValue())) { + if ($inline - 1 <= 0 || null === $value->getValue() || \is_scalar($value->getValue())) { $output .= ' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n"; } else { $output .= "\n"; diff --git a/lib/symfony/yaml/Escaper.php b/lib/symfony/yaml/Escaper.php index 9413d7a2c4..e8090d8c63 100644 --- a/lib/symfony/yaml/Escaper.php +++ b/lib/symfony/yaml/Escaper.php @@ -22,13 +22,13 @@ namespace Symfony\Component\Yaml; class Escaper { // Characters that would cause a dumped string to require double quoting. - const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\x7f|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9"; + public const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\x7f|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9"; // Mapping arrays for escaping a double quoted string. The backslash is // first to ensure proper escaping because str_replace operates iteratively // on the input arrays. This ordering of the characters avoids the use of strtr, // which performs more slowly. - private static $escapees = ['\\', '\\\\', '\\"', '"', + private const ESCAPEES = ['\\', '\\\\', '\\"', '"', "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", @@ -36,7 +36,7 @@ class Escaper "\x7f", "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9", ]; - private static $escaped = ['\\\\', '\\"', '\\\\', '\\"', + private const ESCAPED = ['\\\\', '\\"', '\\\\', '\\"', '\\0', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\a', '\\b', '\\t', '\\n', '\\v', '\\f', '\\r', '\\x0e', '\\x0f', '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', @@ -49,10 +49,8 @@ class Escaper * Determines if a PHP value would require double quoting in YAML. * * @param string $value A PHP value - * - * @return bool True if the value would require double quotes */ - public static function requiresDoubleQuoting($value) + public static function requiresDoubleQuoting(string $value): bool { return 0 < preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value); } @@ -61,22 +59,18 @@ class Escaper * Escapes and surrounds a PHP value with double quotes. * * @param string $value A PHP value - * - * @return string The quoted, escaped string */ - public static function escapeWithDoubleQuotes($value) + public static function escapeWithDoubleQuotes(string $value): string { - return sprintf('"%s"', str_replace(self::$escapees, self::$escaped, $value)); + return sprintf('"%s"', str_replace(self::ESCAPEES, self::ESCAPED, $value)); } /** * Determines if a PHP value would require single quoting in YAML. * * @param string $value A PHP value - * - * @return bool True if the value would require single quotes */ - public static function requiresSingleQuoting($value) + public static function requiresSingleQuoting(string $value): bool { // Determines if a PHP value is entirely composed of a value that would // require single quoting in YAML. @@ -86,17 +80,15 @@ class Escaper // Determines if the PHP value contains any single characters that would // cause it to require single quoting in YAML. - return 0 < preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? | < > = ! % @ ` ]/x', $value); + return 0 < preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? | < > = ! % @ ` \p{Zs}]/xu', $value); } /** * Escapes and surrounds a PHP value with single quotes. * * @param string $value A PHP value - * - * @return string The quoted, escaped string */ - public static function escapeWithSingleQuotes($value) + public static function escapeWithSingleQuotes(string $value): string { return sprintf("'%s'", str_replace('\'', '\'\'', $value)); } diff --git a/lib/symfony/yaml/Exception/ExceptionInterface.php b/lib/symfony/yaml/Exception/ExceptionInterface.php index ad850eea1d..909131684c 100644 --- a/lib/symfony/yaml/Exception/ExceptionInterface.php +++ b/lib/symfony/yaml/Exception/ExceptionInterface.php @@ -16,6 +16,6 @@ namespace Symfony\Component\Yaml\Exception; * * @author Fabien Potencier */ -interface ExceptionInterface +interface ExceptionInterface extends \Throwable { } diff --git a/lib/symfony/yaml/Exception/ParseException.php b/lib/symfony/yaml/Exception/ParseException.php index 4207d9abc4..8748d2b228 100644 --- a/lib/symfony/yaml/Exception/ParseException.php +++ b/lib/symfony/yaml/Exception/ParseException.php @@ -24,13 +24,12 @@ class ParseException extends RuntimeException private $rawMessage; /** - * @param string $message The error message - * @param int $parsedLine The line where the error occurred - * @param string|null $snippet The snippet of code near the problem - * @param string|null $parsedFile The file name where the error occurred - * @param \Exception|null $previous The previous exception + * @param string $message The error message + * @param int $parsedLine The line where the error occurred + * @param string|null $snippet The snippet of code near the problem + * @param string|null $parsedFile The file name where the error occurred */ - public function __construct($message, $parsedLine = -1, $snippet = null, $parsedFile = null, \Exception $previous = null) + public function __construct(string $message, int $parsedLine = -1, string $snippet = null, string $parsedFile = null, \Throwable $previous = null) { $this->parsedFile = $parsedFile; $this->parsedLine = $parsedLine; @@ -45,7 +44,7 @@ class ParseException extends RuntimeException /** * Gets the snippet of code near the error. * - * @return string The snippet of code + * @return string */ public function getSnippet() { @@ -54,10 +53,8 @@ class ParseException extends RuntimeException /** * Sets the snippet of code near the error. - * - * @param string $snippet The code snippet */ - public function setSnippet($snippet) + public function setSnippet(string $snippet) { $this->snippet = $snippet; @@ -69,7 +66,7 @@ class ParseException extends RuntimeException * * This method returns null if a string is parsed. * - * @return string The filename + * @return string */ public function getParsedFile() { @@ -78,10 +75,8 @@ class ParseException extends RuntimeException /** * Sets the filename where the error occurred. - * - * @param string $parsedFile The filename */ - public function setParsedFile($parsedFile) + public function setParsedFile(string $parsedFile) { $this->parsedFile = $parsedFile; @@ -91,7 +86,7 @@ class ParseException extends RuntimeException /** * Gets the line where the error occurred. * - * @return int The file line + * @return int */ public function getParsedLine() { @@ -100,10 +95,8 @@ class ParseException extends RuntimeException /** * Sets the line where the error occurred. - * - * @param int $parsedLine The file line */ - public function setParsedLine($parsedLine) + public function setParsedLine(int $parsedLine) { $this->parsedLine = $parsedLine; diff --git a/lib/symfony/yaml/Inline.php b/lib/symfony/yaml/Inline.php index 64ac48a9c6..d8994cb34c 100644 --- a/lib/symfony/yaml/Inline.php +++ b/lib/symfony/yaml/Inline.php @@ -24,7 +24,7 @@ use Symfony\Component\Yaml\Tag\TaggedValue; */ class Inline { - const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')'; + public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')'; public static $parsedLineNumber = -1; public static $parsedFilename; @@ -34,12 +34,7 @@ class Inline private static $objectForMap = false; private static $constantSupport = false; - /** - * @param int $flags - * @param int|null $parsedLineNumber - * @param string|null $parsedFilename - */ - public static function initialize($flags, $parsedLineNumber = null, $parsedFilename = null) + public static function initialize(int $flags, int $parsedLineNumber = null, string $parsedFilename = null) { self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags); self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags); @@ -59,44 +54,12 @@ class Inline * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior * @param array $references Mapping of variable names to values * - * @return mixed A PHP value + * @return mixed * * @throws ParseException */ - public static function parse($value, $flags = 0, $references = []) + public static function parse(string $value = null, int $flags = 0, array &$references = []) { - if (\is_bool($flags)) { - @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE flag instead.', \E_USER_DEPRECATED); - - if ($flags) { - $flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE; - } else { - $flags = 0; - } - } - - if (\func_num_args() >= 3 && !\is_array($references)) { - @trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT flag instead.', \E_USER_DEPRECATED); - - if ($references) { - $flags |= Yaml::PARSE_OBJECT; - } - - if (\func_num_args() >= 4) { - @trigger_error('Passing a boolean flag to toggle object for map support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.', \E_USER_DEPRECATED); - - if (func_get_arg(3)) { - $flags |= Yaml::PARSE_OBJECT_FOR_MAP; - } - } - - if (\func_num_args() >= 5) { - $references = func_get_arg(4); - } else { - $references = []; - } - } - self::initialize($flags); $value = trim($value); @@ -105,7 +68,7 @@ class Inline return ''; } - if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) { + if (2 /* MB_OVERLOAD_STRING */ & (int) \ini_get('mbstring.func_overload')) { $mbEncoding = mb_internal_encoding(); mb_internal_encoding('ASCII'); } @@ -131,7 +94,7 @@ class Inline throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename); } - if (null !== $tag) { + if (null !== $tag && '' !== $tag) { return new TaggedValue($tag, $result); } @@ -149,39 +112,21 @@ class Inline * @param mixed $value The PHP variable to convert * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string * - * @return string The YAML string representing the PHP value - * * @throws DumpException When trying to dump PHP resource */ - public static function dump($value, $flags = 0) + public static function dump($value, int $flags = 0): string { - if (\is_bool($flags)) { - @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE flag instead.', \E_USER_DEPRECATED); - - if ($flags) { - $flags = Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE; - } else { - $flags = 0; - } - } - - if (\func_num_args() >= 3) { - @trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::DUMP_OBJECT flag instead.', \E_USER_DEPRECATED); - - if (func_get_arg(2)) { - $flags |= Yaml::DUMP_OBJECT; - } - } - switch (true) { case \is_resource($value): if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); } - return 'null'; + return self::dumpNull($flags); case $value instanceof \DateTimeInterface: return $value->format('c'); + case $value instanceof \UnitEnum: + return sprintf('!php/const %s::%s', \get_class($value), $value->name); case \is_object($value): if ($value instanceof TaggedValue) { return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags); @@ -192,25 +137,31 @@ class Inline } if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) { - return self::dumpArray($value, $flags & ~Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE); + $output = []; + + foreach ($value as $key => $val) { + $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); + } + + return sprintf('{ %s }', implode(', ', $output)); } if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { throw new DumpException('Object support when dumping a YAML file has been disabled.'); } - return 'null'; + return self::dumpNull($flags); case \is_array($value): return self::dumpArray($value, $flags); case null === $value: - return 'null'; + return self::dumpNull($flags); case true === $value: return 'true'; case false === $value: return 'false'; - case ctype_digit($value): - return \is_string($value) ? "'$value'" : (int) $value; - case is_numeric($value) && false === strpos($value, "\f") && false === strpos($value, "\n") && false === strpos($value, "\r") && false === strpos($value, "\t") && false === strpos($value, "\v"): + case \is_int($value): + return $value; + case is_numeric($value) && false === strpbrk($value, "\f\n\r\t\v"): $locale = setlocale(\LC_NUMERIC, 0); if (false !== $locale) { setlocale(\LC_NUMERIC, 'C'); @@ -221,7 +172,9 @@ class Inline $repr = str_ireplace('INF', '.Inf', $repr); } elseif (floor($value) == $value && $repr == $value) { // Preserve float data type since storing a whole number will result in integer value. - $repr = '!!float '.$repr; + if (false === strpos($repr, 'E')) { + $repr = $repr.'.0'; + } } } else { $repr = \is_string($value) ? "'$value'" : (string) $value; @@ -250,13 +203,9 @@ class Inline /** * Check if given array is hash or just normal indexed array. * - * @internal - * * @param array|\ArrayObject|\stdClass $value The PHP array or array-like object to check - * - * @return bool true if value is hash array, false otherwise */ - public static function isHash($value) + public static function isHash($value): bool { if ($value instanceof \stdClass || $value instanceof \ArrayObject) { return true; @@ -278,10 +227,8 @@ class Inline * * @param array $value The PHP array to dump * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string - * - * @return string The YAML string representing the PHP array */ - private static function dumpArray($value, $flags) + private static function dumpArray(array $value, int $flags): string { // array if (($value || Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE & $flags) && !self::isHash($value)) { @@ -302,30 +249,31 @@ class Inline return sprintf('{ %s }', implode(', ', $output)); } + private static function dumpNull(int $flags): string + { + if (Yaml::DUMP_NULL_AS_TILDE & $flags) { + return '~'; + } + + return 'null'; + } + /** * Parses a YAML scalar. * - * @param string $scalar - * @param int $flags - * @param string[] $delimiters - * @param int &$i - * @param bool $evaluate - * @param array $references - * - * @return string + * @return mixed * * @throws ParseException When malformed inline YAML string is parsed - * - * @internal */ - public static function parseScalar($scalar, $flags = 0, $delimiters = null, &$i = 0, $evaluate = true, $references = [], $legacyOmittedKeySupport = false) + public static function parseScalar(string $scalar, int $flags = 0, array $delimiters = null, int &$i = 0, bool $evaluate = true, array &$references = [], bool &$isQuoted = null) { - if (\in_array($scalar[$i], ['"', "'"])) { + if (\in_array($scalar[$i], ['"', "'"], true)) { // quoted scalar + $isQuoted = true; $output = self::parseQuotedScalar($scalar, $i); if (null !== $delimiters) { - $tmp = ltrim(substr($scalar, $i), ' '); + $tmp = ltrim(substr($scalar, $i), " \n"); if ('' === $tmp) { throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } @@ -335,6 +283,8 @@ class Inline } } else { // "normal" string + $isQuoted = false; + if (!$delimiters) { $output = substr($scalar, $i); $i += \strlen($output); @@ -343,24 +293,21 @@ class Inline if (Parser::preg_match('/[ \t]+#/', $output, $match, \PREG_OFFSET_CAPTURE)) { $output = substr($output, 0, $match[0][1]); } - } elseif (Parser::preg_match('/^(.'.($legacyOmittedKeySupport ? '+' : '*').'?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { + } elseif (Parser::preg_match('/^(.*?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { $output = $match[1]; $i += \strlen($output); + $output = trim($output); } else { throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $scalar), self::$parsedLineNumber + 1, null, self::$parsedFilename); } // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >) - if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0])) { + if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0] || '%' === $output[0])) { throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]), self::$parsedLineNumber + 1, $output, self::$parsedFilename); } - if ($output && '%' === $output[0]) { - @trigger_error(self::getDeprecationMessage(sprintf('Not quoting the scalar "%s" starting with the "%%" indicator character is deprecated since Symfony 3.1 and will throw a ParseException in 4.0.', $output)), \E_USER_DEPRECATED); - } - if ($evaluate) { - $output = self::evaluateScalar($output, $flags, $references); + $output = self::evaluateScalar($output, $flags, $references, $isQuoted); } } @@ -370,20 +317,15 @@ class Inline /** * Parses a YAML quoted scalar. * - * @param string $scalar - * @param int &$i - * - * @return string - * * @throws ParseException When malformed inline YAML string is parsed */ - private static function parseQuotedScalar($scalar, &$i) + private static function parseQuotedScalar(string $scalar, int &$i = 0): string { if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) { throw new ParseException(sprintf('Malformed inline YAML string: "%s".', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } - $output = substr($match[0], 1, \strlen($match[0]) - 2); + $output = substr($match[0], 1, -1); $unescaper = new Unescaper(); if ('"' == $scalar[$i]) { @@ -400,16 +342,9 @@ class Inline /** * Parses a YAML sequence. * - * @param string $sequence - * @param int $flags - * @param int &$i - * @param array $references - * - * @return array - * * @throws ParseException When malformed inline YAML string is parsed */ - private static function parseSequence($sequence, $flags, &$i = 0, $references = []) + private static function parseSequence(string $sequence, int $flags, int &$i = 0, array &$references = []): array { $output = []; $len = \strlen($sequence); @@ -437,8 +372,7 @@ class Inline $value = self::parseMapping($sequence, $flags, $i, $references); break; default: - $isQuoted = \in_array($sequence[$i], ['"', "'"]); - $value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references); + $value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references, $isQuoted); // the value can be an array if a reference has been resolved to an array var if (\is_string($value) && !$isQuoted && false !== strpos($value, ': ')) { @@ -451,10 +385,15 @@ class Inline } } + if (!$isQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) { + $references[$matches['ref']] = $matches['value']; + $value = $matches['value']; + } + --$i; } - if (null !== $tag) { + if (null !== $tag && '' !== $tag) { $value = new TaggedValue($tag, $value); } @@ -469,16 +408,11 @@ class Inline /** * Parses a YAML mapping. * - * @param string $mapping - * @param int $flags - * @param int &$i - * @param array $references - * * @return array|\stdClass * * @throws ParseException When malformed inline YAML string is parsed */ - private static function parseMapping($mapping, $flags, &$i = 0, $references = []) + private static function parseMapping(string $mapping, int $flags, int &$i = 0, array &$references = []) { $output = []; $len = \strlen($mapping); @@ -490,6 +424,7 @@ class Inline switch ($mapping[$i]) { case ' ': case ',': + case "\n": ++$i; continue 2; case '}': @@ -501,37 +436,33 @@ class Inline } // key + $offsetBeforeKeyParsing = $i; $isKeyQuoted = \in_array($mapping[$i], ['"', "'"], true); - $key = self::parseScalar($mapping, $flags, [':', ' '], $i, false, [], true); + $key = self::parseScalar($mapping, $flags, [':', ' '], $i, false); + + if ($offsetBeforeKeyParsing === $i) { + throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping); + } if ('!php/const' === $key) { - $key .= self::parseScalar($mapping, $flags, [':', ' '], $i, false, [], true); - if ('!php/const:' === $key && ':' !== $mapping[$i]) { - $key = ''; - --$i; - } else { - $key = self::evaluateScalar($key, $flags); - } + $key .= ' '.self::parseScalar($mapping, $flags, [':'], $i, false); + $key = self::evaluateScalar($key, $flags); } - if (':' !== $key && false === $i = strpos($mapping, ':', $i)) { + if (false === $i = strpos($mapping, ':', $i)) { break; } - if (':' === $key) { - @trigger_error(self::getDeprecationMessage('Omitting the key of a mapping is deprecated and will throw a ParseException in 4.0.'), \E_USER_DEPRECATED); - } - if (!$isKeyQuoted) { $evaluatedKey = self::evaluateScalar($key, $flags, $references); if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) { - @trigger_error(self::getDeprecationMessage('Implicit casting of incompatible mapping keys to strings is deprecated since Symfony 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0. Quote your evaluable mapping keys instead.'), \E_USER_DEPRECATED); + throw new ParseException('Implicit casting of incompatible mapping keys to strings is not supported. Quote your evaluable mapping keys instead.', self::$parsedLineNumber + 1, $mapping); } } - if (':' !== $key && !$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}'], true))) { - @trigger_error(self::getDeprecationMessage('Using a colon after an unquoted mapping key that is not followed by an indication character (i.e. " ", ",", "[", "]", "{", "}") is deprecated since Symfony 3.2 and will throw a ParseException in 4.0.'), \E_USER_DEPRECATED); + if (!$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}', "\n"], true))) { + throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").', self::$parsedLineNumber + 1, $mapping); } if ('<<' === $key) { @@ -539,7 +470,7 @@ class Inline } while ($i < $len) { - if (':' === $mapping[$i] || ' ' === $mapping[$i]) { + if (':' === $mapping[$i] || ' ' === $mapping[$i] || "\n" === $mapping[$i]) { ++$i; continue; @@ -565,7 +496,7 @@ class Inline $output[$key] = $value; } } elseif (isset($output[$key])) { - @trigger_error(self::getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), \E_USER_DEPRECATED); + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); } break; case '{': @@ -584,11 +515,11 @@ class Inline $output[$key] = $value; } } elseif (isset($output[$key])) { - @trigger_error(self::getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), \E_USER_DEPRECATED); + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); } break; default: - $value = self::parseScalar($mapping, $flags, [',', '}'], $i, null === $tag, $references); + $value = self::parseScalar($mapping, $flags, [',', '}', "\n"], $i, null === $tag, $references, $isValueQuoted); // Spec: Keys MUST be unique; first one wins. // Parser cannot abort this mapping earlier, since lines // are processed sequentially. @@ -596,13 +527,18 @@ class Inline if ('<<' === $key) { $output += $value; } elseif ($allowOverwrite || !isset($output[$key])) { + if (!$isValueQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) { + $references[$matches['ref']] = $matches['value']; + $value = $matches['value']; + } + if (null !== $tag) { $output[$key] = new TaggedValue($tag, $value); } else { $output[$key] = $value; } } elseif (isset($output[$key])) { - @trigger_error(self::getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), \E_USER_DEPRECATED); + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); } --$i; } @@ -618,18 +554,14 @@ class Inline /** * Evaluates scalars and replaces magic values. * - * @param string $scalar - * @param int $flags - * @param array $references - * - * @return mixed The evaluated YAML string + * @return mixed * * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved */ - private static function evaluateScalar($scalar, $flags, $references = []) + private static function evaluateScalar(string $scalar, int $flags, array &$references = [], bool &$isQuotedString = null) { + $isQuotedString = false; $scalar = trim($scalar); - $scalarLower = strtolower($scalar); if (0 === strpos($scalar, '*')) { if (false !== $pos = strpos($scalar, '#')) { @@ -650,6 +582,8 @@ class Inline return $references[$value]; } + $scalarLower = strtolower($scalar); + switch (true) { case 'null' === $scalarLower: case '' === $scalar: @@ -661,43 +595,22 @@ class Inline return false; case '!' === $scalar[0]: switch (true) { - case 0 === strpos($scalar, '!str'): - @trigger_error(self::getDeprecationMessage('Support for the !str tag is deprecated since Symfony 3.4. Use the !!str tag instead.'), \E_USER_DEPRECATED); - - return (string) substr($scalar, 5); case 0 === strpos($scalar, '!!str '): - return (string) substr($scalar, 6); + $s = (string) substr($scalar, 6); + + if (\in_array($s[0] ?? '', ['"', "'"], true)) { + $isQuotedString = true; + $s = self::parseQuotedScalar($s); + } + + return $s; case 0 === strpos($scalar, '! '): - @trigger_error(self::getDeprecationMessage('Using the non-specific tag "!" is deprecated since Symfony 3.4 as its behavior will change in 4.0. It will force non-evaluating your values in 4.0. Use plain integers or !!float instead.'), \E_USER_DEPRECATED); - - return (int) self::parseScalar(substr($scalar, 2), $flags); - case 0 === strpos($scalar, '!php/object:'): - if (self::$objectSupport) { - @trigger_error(self::getDeprecationMessage('The !php/object: tag to indicate dumped PHP objects is deprecated since Symfony 3.4 and will be removed in 4.0. Use the !php/object (without the colon) tag instead.'), \E_USER_DEPRECATED); - - return unserialize(substr($scalar, 12)); - } - - if (self::$exceptionOnInvalidType) { - throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); - } - - return null; - case 0 === strpos($scalar, '!!php/object:'): - if (self::$objectSupport) { - @trigger_error(self::getDeprecationMessage('The !!php/object: tag to indicate dumped PHP objects is deprecated since Symfony 3.1 and will be removed in 4.0. Use the !php/object (without the colon) tag instead.'), \E_USER_DEPRECATED); - - return unserialize(substr($scalar, 13)); - } - - if (self::$exceptionOnInvalidType) { - throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); - } - - return null; + return substr($scalar, 2); case 0 === strpos($scalar, '!php/object'): if (self::$objectSupport) { if (!isset($scalar[12])) { + trigger_deprecation('symfony/yaml', '5.1', 'Using the !php/object tag without a value is deprecated.'); + return false; } @@ -708,25 +621,12 @@ class Inline throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); } - return null; - case 0 === strpos($scalar, '!php/const:'): - if (self::$constantSupport) { - @trigger_error(self::getDeprecationMessage('The !php/const: tag to indicate dumped PHP constants is deprecated since Symfony 3.4 and will be removed in 4.0. Use the !php/const (without the colon) tag instead.'), \E_USER_DEPRECATED); - - if (\defined($const = substr($scalar, 11))) { - return \constant($const); - } - - throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); - } - if (self::$exceptionOnInvalidType) { - throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); - } - return null; case 0 === strpos($scalar, '!php/const'): if (self::$constantSupport) { if (!isset($scalar[11])) { + trigger_deprecation('symfony/yaml', '5.1', 'Using the !php/const tag without a value is deprecated.'); + return ''; } @@ -746,20 +646,28 @@ class Inline return (float) substr($scalar, 8); case 0 === strpos($scalar, '!!binary '): return self::evaluateBinaryScalar(substr($scalar, 9)); - default: - @trigger_error(self::getDeprecationMessage(sprintf('Using the unquoted scalar value "%s" is deprecated since Symfony 3.3 and will be considered as a tagged value in 4.0. You must quote it.', $scalar)), \E_USER_DEPRECATED); } + throw new ParseException(sprintf('The string "%s" could not be parsed as it uses an unsupported built-in tag.', $scalar), self::$parsedLineNumber, $scalar, self::$parsedFilename); + case preg_match('/^(?:\+|-)?0o(?P[0-7_]++)$/', $scalar, $matches): + $value = str_replace('_', '', $matches['value']); + + if ('-' === $scalar[0]) { + return -octdec($value); + } + + return octdec($value); // Optimize for returning strings. - // no break - case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || is_numeric($scalar[0]): + case \in_array($scalar[0], ['+', '-', '.'], true) || is_numeric($scalar[0]): if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) { - $scalar = str_replace('_', '', (string) $scalar); + $scalar = str_replace('_', '', $scalar); } switch (true) { case ctype_digit($scalar): if (preg_match('/^0[0-7]+$/', $scalar)) { + trigger_deprecation('symfony/yaml', '5.1', 'Support for parsing numbers prefixed with 0 as octal numbers. They will be parsed as strings as of 6.0. Use "%s" to represent the octal number.', '0o'.substr($scalar, 1)); + return octdec($scalar); } @@ -768,6 +676,8 @@ class Inline return ($scalar === (string) $cast) ? $cast : $scalar; case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): if (preg_match('/^-0[0-7]+$/', $scalar)) { + trigger_deprecation('symfony/yaml', '5.1', 'Support for parsing numbers prefixed with 0 as octal numbers. They will be parsed as strings as of 6.0. Use "%s" to represent the octal number.', '-0o'.substr($scalar, 2)); + return -octdec(substr($scalar, 1)); } @@ -784,78 +694,72 @@ class Inline return -log(0); case '-.inf' === $scalarLower: return log(0); - case Parser::preg_match('/^(-|\+)?[0-9][0-9,]*(\.[0-9_]+)?$/', $scalar): case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/', $scalar): - if (false !== strpos($scalar, ',')) { - @trigger_error(self::getDeprecationMessage('Using the comma as a group separator for floats is deprecated since Symfony 3.2 and will be removed in 4.0.'), \E_USER_DEPRECATED); - } - - return (float) str_replace([',', '_'], '', $scalar); + return (float) str_replace('_', '', $scalar); case Parser::preg_match(self::getTimestampRegex(), $scalar): + // When no timezone is provided in the parsed date, YAML spec says we must assume UTC. + $time = new \DateTime($scalar, new \DateTimeZone('UTC')); + if (Yaml::PARSE_DATETIME & $flags) { - // When no timezone is provided in the parsed date, YAML spec says we must assume UTC. - return new \DateTime($scalar, new \DateTimeZone('UTC')); + return $time; } - $timeZone = date_default_timezone_get(); - date_default_timezone_set('UTC'); - $time = strtotime($scalar); - date_default_timezone_set($timeZone); + try { + if (false !== $scalar = $time->getTimestamp()) { + return $scalar; + } + } catch (\ValueError $e) { + // no-op + } - return $time; + return $time->format('U'); } } return (string) $scalar; } - /** - * @param string $value - * @param int &$i - * @param int $flags - * - * @return string|null - */ - private static function parseTag($value, &$i, $flags) + private static function parseTag(string $value, int &$i, int $flags): ?string { if ('!' !== $value[$i]) { return null; } - $tagLength = strcspn($value, " \t\n", $i + 1); + $tagLength = strcspn($value, " \t\n[]{},", $i + 1); $tag = substr($value, $i + 1, $tagLength); $nextOffset = $i + $tagLength + 1; $nextOffset += strspn($value, ' ', $nextOffset); - // Is followed by a scalar - if ((!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && 'tagged' !== $tag) { - // Manage non-whitelisted scalars in {@link self::evaluateScalar()} + if ('' === $tag && (!isset($value[$nextOffset]) || \in_array($value[$nextOffset], [']', '}', ','], true))) { + throw new ParseException('Using the unquoted scalar value "!" is not supported. You must quote it.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + // Is followed by a scalar and is a built-in tag + if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && ('!' === $tag[0] || 'str' === $tag || 'php/const' === $tag || 'php/object' === $tag)) { + // Manage in {@link self::evaluateScalar()} return null; } + $i = $nextOffset; + // Built-in tags - if ($tag && '!' === $tag[0]) { + if ('' !== $tag && '!' === $tag[0]) { throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); } - if (Yaml::PARSE_CUSTOM_TAGS & $flags) { - $i = $nextOffset; + if ('' !== $tag && !isset($value[$i])) { + throw new ParseException(sprintf('Missing value for tag "%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + if ('' === $tag || Yaml::PARSE_CUSTOM_TAGS & $flags) { return $tag; } - throw new ParseException(sprintf('Tags support is not enabled. Enable the `Yaml::PARSE_CUSTOM_TAGS` flag to use "!%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + throw new ParseException(sprintf('Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); } - /** - * @param string $scalar - * - * @return string - * - * @internal - */ - public static function evaluateBinaryScalar($scalar) + public static function evaluateBinaryScalar(string $scalar): string { $parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar)); @@ -870,7 +774,7 @@ class Inline return base64_decode($parsedBinaryData, true); } - private static function isBinaryString($value) + private static function isBinaryString(string $value): bool { return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value); } @@ -878,11 +782,9 @@ class Inline /** * Gets a regex that matches a YAML date. * - * @return string The regular expression - * * @see http://www.yaml.org/spec/1.2/spec.html#id2761573 */ - private static function getTimestampRegex() + private static function getTimestampRegex(): string { return << * - * @final since version 3.4 + * @final */ class Parser { - const TAG_PATTERN = '(?P![\w!.\/:-]+)'; - const BLOCK_SCALAR_HEADER_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?'; + public const TAG_PATTERN = '(?P![\w!.\/:-]+)'; + public const BLOCK_SCALAR_HEADER_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?'; + public const REFERENCE_PATTERN = '#^&(?P[^ ]++) *+(?P.*)#u'; private $filename; private $offset = 0; + private $numberOfParsedLines = 0; private $totalNumberOfLines; private $lines = []; private $currentLineNb = -1; @@ -37,32 +39,17 @@ class Parser private $locallySkippedLineNumbers = []; private $refsBeingParsed = []; - public function __construct() - { - if (\func_num_args() > 0) { - @trigger_error(sprintf('The constructor arguments $offset, $totalNumberOfLines, $skippedLineNumbers of %s are deprecated and will be removed in 4.0', self::class), \E_USER_DEPRECATED); - - $this->offset = func_get_arg(0); - if (\func_num_args() > 1) { - $this->totalNumberOfLines = func_get_arg(1); - } - if (\func_num_args() > 2) { - $this->skippedLineNumbers = func_get_arg(2); - } - } - } - /** * Parses a YAML file into a PHP value. * * @param string $filename The path to the YAML file to be parsed * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior * - * @return mixed The YAML converted to a PHP value + * @return mixed * * @throws ParseException If the file could not be read or the YAML is not valid */ - public function parseFile($filename, $flags = 0) + public function parseFile(string $filename, int $flags = 0) { if (!is_file($filename)) { throw new ParseException(sprintf('File "%s" does not exist.', $filename)); @@ -87,42 +74,12 @@ class Parser * @param string $value A YAML string * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior * - * @return mixed A PHP value + * @return mixed * * @throws ParseException If the YAML is not valid */ - public function parse($value, $flags = 0) + public function parse(string $value, int $flags = 0) { - if (\is_bool($flags)) { - @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE flag instead.', \E_USER_DEPRECATED); - - if ($flags) { - $flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE; - } else { - $flags = 0; - } - } - - if (\func_num_args() >= 3) { - @trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT flag instead.', \E_USER_DEPRECATED); - - if (func_get_arg(2)) { - $flags |= Yaml::PARSE_OBJECT; - } - } - - if (\func_num_args() >= 4) { - @trigger_error('Passing a boolean flag to toggle object for map support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.', \E_USER_DEPRECATED); - - if (func_get_arg(3)) { - $flags |= Yaml::PARSE_OBJECT_FOR_MAP; - } - } - - if (Yaml::PARSE_KEYS_AS_STRINGS & $flags) { - @trigger_error('Using the Yaml::PARSE_KEYS_AS_STRINGS flag is deprecated since Symfony 3.4 as it will be removed in 4.0. Quote your keys when they are evaluable instead.', \E_USER_DEPRECATED); - } - if (false === preg_match('//u', $value)) { throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename); } @@ -130,48 +87,43 @@ class Parser $this->refs = []; $mbEncoding = null; - $e = null; - $data = null; - if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) { + if (2 /* MB_OVERLOAD_STRING */ & (int) \ini_get('mbstring.func_overload')) { $mbEncoding = mb_internal_encoding(); mb_internal_encoding('UTF-8'); } try { $data = $this->doParse($value, $flags); - } catch (\Exception $e) { - } catch (\Throwable $e) { - } - - if (null !== $mbEncoding) { - mb_internal_encoding($mbEncoding); - } - - $this->lines = []; - $this->currentLine = ''; - $this->refs = []; - $this->skippedLineNumbers = []; - $this->locallySkippedLineNumbers = []; - $this->totalNumberOfLines = null; - - if (null !== $e) { - throw $e; + } finally { + if (null !== $mbEncoding) { + mb_internal_encoding($mbEncoding); + } + $this->refsBeingParsed = []; + $this->offset = 0; + $this->lines = []; + $this->currentLine = ''; + $this->numberOfParsedLines = 0; + $this->refs = []; + $this->skippedLineNumbers = []; + $this->locallySkippedLineNumbers = []; + $this->totalNumberOfLines = null; } return $data; } - private function doParse($value, $flags) + private function doParse(string $value, int $flags) { $this->currentLineNb = -1; $this->currentLine = ''; $value = $this->cleanup($value); $this->lines = explode("\n", $value); + $this->numberOfParsedLines = \count($this->lines); $this->locallySkippedLineNumbers = []; if (null === $this->totalNumberOfLines) { - $this->totalNumberOfLines = \count($this->lines); + $this->totalNumberOfLines = $this->numberOfParsedLines; } if (!$this->moveToNextLine()) { @@ -206,25 +158,34 @@ class Parser Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename); $isRef = $mergeNode = false; - if (self::preg_match('#^\-((?P\s+)(?P.+))?$#u', rtrim($this->currentLine), $values)) { + if ('-' === $this->currentLine[0] && self::preg_match('#^\-((?P\s+)(?P.+))?$#u', rtrim($this->currentLine), $values)) { if ($context && 'mapping' == $context) { throw new ParseException('You cannot define a sequence item when in a mapping.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } $context = 'sequence'; - if (isset($values['value']) && self::preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) { + if (isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { $isRef = $matches['ref']; $this->refsBeingParsed[] = $isRef; $values['value'] = $matches['value']; } if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) { - @trigger_error($this->getDeprecationMessage('Starting an unquoted string with a question mark followed by a space is deprecated since Symfony 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.'), \E_USER_DEPRECATED); + throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } // array - if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { - $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags); + if (isset($values['value']) && 0 === strpos(ltrim($values['value'], ' '), '-')) { + // Inline first child + $currentLineNumber = $this->getRealCurrentLineNb(); + + $sequenceIndentation = \strlen($values['leadspaces']) + 1; + $sequenceYaml = substr($this->currentLine, $sequenceIndentation); + $sequenceYaml .= "\n".$this->getNextEmbedBlock($sequenceIndentation, true); + + $data[] = $this->parseBlock($currentLineNumber, rtrim($sequenceYaml), $flags); + } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { + $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true) ?? '', $flags); } elseif (null !== $subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags)) { $data[] = new TaggedValue( $subTag, @@ -254,7 +215,7 @@ class Parser array_pop($this->refsBeingParsed); } } elseif ( - self::preg_match('#^(?P(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(\s++(?P.+))?$#u', rtrim($this->currentLine), $values) + self::preg_match('#^(?P(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(( |\t)++(?P.+))?$#u', rtrim($this->currentLine), $values) && (false === strpos($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"])) ) { if ($context && 'sequence' == $context) { @@ -263,15 +224,7 @@ class Parser $context = 'mapping'; try { - $i = 0; - $evaluateKey = !(Yaml::PARSE_KEYS_AS_STRINGS & $flags); - - // constants in key will be evaluated anyway - if (isset($values['key'][0]) && '!' === $values['key'][0] && Yaml::PARSE_CONSTANT & $flags) { - $evaluateKey = true; - } - - $key = Inline::parseScalar($values['key'], 0, null, $i, $evaluateKey); + $key = Inline::parseScalar($values['key']); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); @@ -280,8 +233,7 @@ class Parser } if (!\is_string($key) && !\is_int($key)) { - $keyType = is_numeric($key) ? 'numeric key' : 'non-string key'; - @trigger_error($this->getDeprecationMessage(sprintf('Implicit casting of %s to string is deprecated since Symfony 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0. Quote your evaluable mapping keys instead.', $keyType)), \E_USER_DEPRECATED); + throw new ParseException((is_numeric($key) ? 'Numeric' : 'Non-string').' keys are not supported. Quote your evaluable mapping keys instead.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } // Convert float keys to strings, to avoid being converted to integers by PHP @@ -289,14 +241,14 @@ class Parser $key = (string) $key; } - if ('<<' === $key && (!isset($values['value']) || !self::preg_match('#^&(?P[^ ]+)#u', $values['value'], $refMatches))) { + if ('<<' === $key && (!isset($values['value']) || '&' !== $values['value'][0] || !self::preg_match('#^&(?P[^ ]+)#u', $values['value'], $refMatches))) { $mergeNode = true; $allowOverwrite = true; if (isset($values['value'][0]) && '*' === $values['value'][0]) { $refName = substr(rtrim($values['value']), 1); if (!\array_key_exists($refName, $this->refs)) { if (false !== $pos = array_search($refName, $this->refsBeingParsed, true)) { - throw new ParseException(sprintf('Circular reference [%s, %s] detected for reference "%s".', implode(', ', \array_slice($this->refsBeingParsed, $pos)), $refName, $refName), $this->currentLineNb + 1, $this->currentLine, $this->filename); + throw new ParseException(sprintf('Circular reference [%s] detected for reference "%s".', implode(', ', array_merge(\array_slice($this->refsBeingParsed, $pos), [$refName])), $refName), $this->currentLineNb + 1, $this->currentLine, $this->filename); } throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); @@ -350,7 +302,7 @@ class Parser $data += $parsed; // array union } } - } elseif ('<<' !== $key && isset($values['value']) && self::preg_match('#^&(?P[^ ]++) *+(?P.*)#u', $values['value'], $matches)) { + } elseif ('<<' !== $key && isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { $isRef = $matches['ref']; $this->refsBeingParsed[] = $isRef; $values['value'] = $matches['value']; @@ -372,9 +324,11 @@ class Parser $data[$key] = null; } } else { - @trigger_error($this->getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), \E_USER_DEPRECATED); + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); } } else { + // remember the parsed line number here in case we need it to provide some contexts in error messages below + $realCurrentLineNbKey = $this->getRealCurrentLineNb(); $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags); if ('<<' === $key) { $this->refs[$refMatches['ref']] = $value; @@ -393,7 +347,7 @@ class Parser $data[$key] = $value; } } else { - @trigger_error($this->getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), \E_USER_DEPRECATED); + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $realCurrentLineNbKey + 1, $this->currentLine); } } } else { @@ -403,13 +357,68 @@ class Parser if ($allowOverwrite || !isset($data[$key])) { $data[$key] = $value; } else { - @trigger_error($this->getDeprecationMessage(sprintf('Duplicate key "%s" detected whilst parsing YAML. Silent handling of duplicate mapping keys in YAML is deprecated since Symfony 3.2 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.', $key)), \E_USER_DEPRECATED); + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); } } if ($isRef) { $this->refs[$isRef] = $data[$key]; array_pop($this->refsBeingParsed); } + } elseif ('"' === $this->currentLine[0] || "'" === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + return Inline::parse($this->lexInlineQuotedString(), $flags, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } elseif ('{' === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + $parsedMapping = Inline::parse($this->lexInlineMapping(), $flags, $this->refs); + + while ($this->moveToNextLine()) { + if (!$this->isCurrentLineEmpty()) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return $parsedMapping; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } elseif ('[' === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + $parsedSequence = Inline::parse($this->lexInlineSequence(), $flags, $this->refs); + + while ($this->moveToNextLine()) { + if (!$this->isCurrentLineEmpty()) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return $parsedSequence; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } } else { // multiple documents are not supported if ('---' === $this->currentLine) { @@ -417,7 +426,7 @@ class Parser } if ($deprecatedUsage = (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1])) { - @trigger_error($this->getDeprecationMessage('Starting an unquoted string with a question mark followed by a space is deprecated since Symfony 3.3 and will throw \Symfony\Component\Yaml\Exception\ParseException in 4.0.'), \E_USER_DEPRECATED); + throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } // 1-liner optionally followed by newline(s) @@ -441,26 +450,32 @@ class Parser $value = ''; foreach ($this->lines as $line) { - if ('' !== ltrim($line) && '#' === ltrim($line)[0]) { + $trimmedLine = trim($line); + if ('#' === ($trimmedLine[0] ?? '')) { continue; } // If the indentation is not consistent at offset 0, it is to be considered as a ParseError if (0 === $this->offset && !$deprecatedUsage && isset($line[0]) && ' ' === $line[0]) { throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); } - if ('' === trim($line)) { + + if (false !== strpos($line, ': ')) { + throw new ParseException('Mapping values are not allowed in multi-line blocks.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if ('' === $trimmedLine) { $value .= "\n"; } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { $value .= ' '; } - if ('' !== trim($line) && '\\' === substr($line, -1)) { + if ('' !== $trimmedLine && '\\' === substr($line, -1)) { $value .= ltrim(substr($line, 0, -1)); - } elseif ('' !== trim($line)) { - $value .= trim($line); + } elseif ('' !== $trimmedLine) { + $value .= $trimmedLine; } - if ('' === trim($line)) { + if ('' === $trimmedLine) { $previousLineWasNewline = true; $previousLineWasTerminatedWithBackslash = false; } elseif ('\\' === substr($line, -1)) { @@ -487,7 +502,7 @@ class Parser $data = new TaggedValue($tag, $data); } - if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && !\is_object($data) && 'mapping' === $context) { + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && 'mapping' === $context && !\is_object($data)) { $object = new \stdClass(); foreach ($data as $key => $value) { @@ -500,7 +515,7 @@ class Parser return empty($data) ? null : $data; } - private function parseBlock($offset, $yaml, $flags) + private function parseBlock(int $offset, string $yaml, int $flags) { $skippedLineNumbers = $this->skippedLineNumbers; @@ -526,10 +541,8 @@ class Parser * Returns the current line number (takes the offset into account). * * @internal - * - * @return int The current line number */ - public function getRealCurrentLineNb() + public function getRealCurrentLineNb(): int { $realCurrentLineNumber = $this->currentLineNb + $this->offset; @@ -546,25 +559,25 @@ class Parser /** * Returns the current line indentation. - * - * @return int The current line indentation */ - private function getCurrentLineIndentation() + private function getCurrentLineIndentation(): int { + if (' ' !== ($this->currentLine[0] ?? '')) { + return 0; + } + return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' ')); } /** * Returns the next embed block of YAML. * - * @param int $indentation The indent level at which the block is to be read, or null for default - * @param bool $inSequence True if the enclosing data structure is a sequence - * - * @return string A YAML string + * @param int|null $indentation The indent level at which the block is to be read, or null for default + * @param bool $inSequence True if the enclosing data structure is a sequence * * @throws ParseException When indentation problem are detected */ - private function getNextEmbedBlock($indentation = null, $inSequence = false) + private function getNextEmbedBlock(int $indentation = null, bool $inSequence = false): string { $oldLineIndentation = $this->getCurrentLineIndentation(); @@ -605,8 +618,9 @@ class Parser } $data = []; + if ($this->getCurrentLineIndentation() >= $newIndent) { - $data[] = substr($this->currentLine, $newIndent); + $data[] = substr($this->currentLine, $newIndent ?? 0); } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { $data[] = $this->currentLine; } else { @@ -660,14 +674,17 @@ class Parser return implode("\n", $data); } + private function hasMoreLines(): bool + { + return (\count($this->lines) - 1) > $this->currentLineNb; + } + /** * Moves the parser to the next line. - * - * @return bool */ - private function moveToNextLine() + private function moveToNextLine(): bool { - if ($this->currentLineNb >= \count($this->lines) - 1) { + if ($this->currentLineNb >= $this->numberOfParsedLines - 1) { return false; } @@ -678,10 +695,8 @@ class Parser /** * Moves the parser to the previous line. - * - * @return bool */ - private function moveToPreviousLine() + private function moveToPreviousLine(): bool { if ($this->currentLineNb < 1) { return false; @@ -699,11 +714,11 @@ class Parser * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior * @param string $context The parser context (either sequence or mapping) * - * @return mixed A PHP value + * @return mixed * * @throws ParseException When reference does not exist */ - private function parseValue($value, $flags, $context) + private function parseValue(string $value, int $flags, string $context) { if (0 === strpos($value, '*')) { if (false !== $pos = strpos($value, '#')) { @@ -714,7 +729,7 @@ class Parser if (!\array_key_exists($value, $this->refs)) { if (false !== $pos = array_search($value, $this->refsBeingParsed, true)) { - throw new ParseException(sprintf('Circular reference [%s, %s] detected for reference "%s".', implode(', ', \array_slice($this->refsBeingParsed, $pos)), $value, $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); + throw new ParseException(sprintf('Circular reference [%s] detected for reference "%s".', implode(', ', array_merge(\array_slice($this->refsBeingParsed, $pos), [$value])), $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); } throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); @@ -723,73 +738,81 @@ class Parser return $this->refs[$value]; } - if (self::preg_match('/^(?:'.self::TAG_PATTERN.' +)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) { - $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : ''; + if (\in_array($value[0], ['!', '|', '>'], true) && self::preg_match('/^(?:'.self::TAG_PATTERN.' +)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) { + $modifiers = $matches['modifiers'] ?? ''; $data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), abs((int) $modifiers)); - if ('' !== $matches['tag']) { + if ('' !== $matches['tag'] && '!' !== $matches['tag']) { if ('!!binary' === $matches['tag']) { return Inline::evaluateBinaryScalar($data); - } elseif ('tagged' === $matches['tag']) { - return new TaggedValue(substr($matches['tag'], 1), $data); - } elseif ('!' !== $matches['tag']) { - @trigger_error($this->getDeprecationMessage(sprintf('Using the custom tag "%s" for the value "%s" is deprecated since Symfony 3.3. It will be replaced by an instance of %s in 4.0.', $matches['tag'], $data, TaggedValue::class)), \E_USER_DEPRECATED); } + + return new TaggedValue(substr($matches['tag'], 1), $data); } return $data; } try { - $quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null; + if ('' !== $value && '{' === $value[0]) { + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); - // do not take following lines into account when the current line is a quoted single line value - if (null !== $quotation && self::preg_match('/^'.$quotation.'.*'.$quotation.'(\s*#.*)?$/', $value)) { - return Inline::parse($value, $flags, $this->refs); + return Inline::parse($this->lexInlineMapping($cursor), $flags, $this->refs); + } elseif ('' !== $value && '[' === $value[0]) { + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + + return Inline::parse($this->lexInlineSequence($cursor), $flags, $this->refs); } - $lines = []; + switch ($value[0] ?? '') { + case '"': + case "'": + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + $parsedValue = Inline::parse($this->lexInlineQuotedString($cursor), $flags, $this->refs); - while ($this->moveToNextLine()) { - // unquoted strings end before the first unindented line - if (null === $quotation && 0 === $this->getCurrentLineIndentation()) { - $this->moveToPreviousLine(); + if (isset($this->currentLine[$cursor]) && preg_replace('/\s*(#.*)?$/A', '', substr($this->currentLine, $cursor))) { + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($this->currentLine, $cursor))); + } - break; - } + return $parsedValue; + default: + $lines = []; - $lines[] = trim($this->currentLine); + while ($this->moveToNextLine()) { + // unquoted strings end before the first unindented line + if (0 === $this->getCurrentLineIndentation()) { + $this->moveToPreviousLine(); - // quoted string values end with a line that is terminated with the quotation character - $escapedLine = str_replace(['\\\\', '\\"'], '', $this->currentLine); - if ('' !== $escapedLine && substr($escapedLine, -1) === $quotation) { - break; - } + break; + } + + $lines[] = trim($this->currentLine); + } + + for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) { + if ('' === $lines[$i]) { + $value .= "\n"; + $previousLineBlank = true; + } elseif ($previousLineBlank) { + $value .= $lines[$i]; + $previousLineBlank = false; + } else { + $value .= ' '.$lines[$i]; + $previousLineBlank = false; + } + } + + Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); + + $parsedValue = Inline::parse($value, $flags, $this->refs); + + if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) { + throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + return $parsedValue; } - - for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) { - if ('' === $lines[$i]) { - $value .= "\n"; - $previousLineBlank = true; - } elseif ($previousLineBlank) { - $value .= $lines[$i]; - $previousLineBlank = false; - } else { - $value .= ' '.$lines[$i]; - $previousLineBlank = false; - } - } - - Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); - - $parsedValue = Inline::parse($value, $flags, $this->refs); - - if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) { - throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename); - } - - return $parsedValue; } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); @@ -804,10 +827,8 @@ class Parser * @param string $style The style indicator that was used to begin this block scalar (| or >) * @param string $chomping The chomping indicator that was used to begin this block scalar (+ or -) * @param int $indentation The indentation indicator that was used to begin this block scalar - * - * @return string The text value */ - private function parseBlockScalar($style, $chomping = '', $indentation = 0) + private function parseBlockScalar(string $style, string $chomping = '', int $indentation = 0): string { $notEOF = $this->moveToNextLine(); if (!$notEOF) { @@ -828,8 +849,10 @@ class Parser // determine indentation if not specified if (0 === $indentation) { - if (self::preg_match('/^ +/', $this->currentLine, $matches)) { - $indentation = \strlen($matches[0]); + $currentLineLength = \strlen($this->currentLine); + + for ($i = 0; $i < $currentLineLength && ' ' === $this->currentLine[$i]; ++$i) { + ++$indentation; } } @@ -911,10 +934,8 @@ class Parser /** * Returns true if the next line is indented. - * - * @return bool Returns true if the next line is indented, false otherwise */ - private function isNextLineIndented() + private function isNextLineIndented(): bool { $currentIndentation = $this->getCurrentLineIndentation(); $movements = 0; @@ -942,38 +963,32 @@ class Parser /** * Returns true if the current line is blank or if it is a comment line. - * - * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise */ - private function isCurrentLineEmpty() + private function isCurrentLineEmpty(): bool { return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); } /** * Returns true if the current line is blank. - * - * @return bool Returns true if the current line is blank, false otherwise */ - private function isCurrentLineBlank() + private function isCurrentLineBlank(): bool { - return '' == trim($this->currentLine, ' '); + return '' === $this->currentLine || '' === trim($this->currentLine, ' '); } /** * Returns true if the current line is a comment line. - * - * @return bool Returns true if the current line is a comment line, false otherwise */ - private function isCurrentLineComment() + private function isCurrentLineComment(): bool { - //checking explicitly the first char of the trim is faster than loops or strpos - $ltrimmedLine = ltrim($this->currentLine, ' '); + // checking explicitly the first char of the trim is faster than loops or strpos + $ltrimmedLine = '' !== $this->currentLine && ' ' === $this->currentLine[0] ? ltrim($this->currentLine, ' ') : $this->currentLine; return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0]; } - private function isCurrentLineLastLineInDocument() + private function isCurrentLineLastLineInDocument(): bool { return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1); } @@ -982,10 +997,8 @@ class Parser * Cleanups a YAML string to be parsed. * * @param string $value The input YAML string - * - * @return string A cleaned up YAML string */ - private function cleanup($value) + private function cleanup(string $value): string { $value = str_replace(["\r\n", "\r"], "\n", $value); @@ -1018,10 +1031,8 @@ class Parser /** * Returns true if the next line starts unindented collection. - * - * @return bool Returns true if the next line starts unindented collection, false otherwise */ - private function isNextLineUnIndentedCollection() + private function isNextLineUnIndentedCollection(): bool { $currentIndentation = $this->getCurrentLineIndentation(); $movements = 0; @@ -1049,16 +1060,14 @@ class Parser /** * Returns true if the string is un-indented collection item. - * - * @return bool Returns true if the string is un-indented collection item, false otherwise */ - private function isStringUnIndentedCollectionItem() + private function isStringUnIndentedCollectionItem(): bool { return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- '); } /** - * A local wrapper for `preg_match` which will throw a ParseException if there + * A local wrapper for "preg_match" which will throw a ParseException if there * is an internal error in the PCRE engine. * * This avoids us needing to check for "false" every time PCRE is used @@ -1070,7 +1079,7 @@ class Parser * * @internal */ - public static function preg_match($pattern, $subject, &$matches = null, $flags = 0, $offset = 0) + public static function preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int { if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) { switch (preg_last_error()) { @@ -1102,10 +1111,10 @@ class Parser /** * Trim the tag on top of the value. * - * Prevent values such as `!foo {quz: bar}` to be considered as + * Prevent values such as "!foo {quz: bar}" to be considered as * a mapping block. */ - private function trimTag($value) + private function trimTag(string $value): string { if ('!' === $value[0]) { return ltrim(substr($value, 1, strcspn($value, " \r\n", 1)), ' '); @@ -1114,10 +1123,7 @@ class Parser return $value; } - /** - * @return string|null - */ - private function getLineTag($value, $flags, $nextLineCheck = true) + private function getLineTag(string $value, int $flags, bool $nextLineCheck = true): ?string { if ('' === $value || '!' !== $value[0] || 1 !== self::preg_match('/^'.self::TAG_PATTERN.' *( +#.*)?$/', $value, $matches)) { return null; @@ -1138,19 +1144,162 @@ class Parser return $tag; } - throw new ParseException(sprintf('Tags support is not enabled. You must use the flag `Yaml::PARSE_CUSTOM_TAGS` to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename); + throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename); } - private function getDeprecationMessage($message) + private function lexInlineQuotedString(int &$cursor = 0): string { - $message = rtrim($message, '.'); + $quotation = $this->currentLine[$cursor]; + $value = $quotation; + ++$cursor; - if (null !== $this->filename) { - $message .= ' in '.$this->filename; + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + $lineNumber = 0; + + do { + if (++$lineNumber > 1) { + $cursor += strspn($this->currentLine, ' ', $cursor); + } + + if ($this->isCurrentLineBlank()) { + $value .= "\n"; + } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { + $value .= ' '; + } + + for (; \strlen($this->currentLine) > $cursor; ++$cursor) { + switch ($this->currentLine[$cursor]) { + case '\\': + if ("'" === $quotation) { + $value .= '\\'; + } elseif (isset($this->currentLine[++$cursor])) { + $value .= '\\'.$this->currentLine[$cursor]; + } + + break; + case $quotation: + ++$cursor; + + if ("'" === $quotation && isset($this->currentLine[$cursor]) && "'" === $this->currentLine[$cursor]) { + $value .= "''"; + break; + } + + return $value.$quotation; + default: + $value .= $this->currentLine[$cursor]; + } + } + + if ($this->isCurrentLineBlank()) { + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + } elseif ('\\' === $this->currentLine[-1]) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = true; + } else { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + } + + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + throw new ParseException('Malformed inline YAML string.'); + } + + private function lexUnquotedString(int &$cursor): string + { + $offset = $cursor; + $cursor += strcspn($this->currentLine, '[]{},: ', $cursor); + + if ($cursor === $offset) { + throw new ParseException('Malformed unquoted YAML string.'); } - $message .= ' on line '.($this->getRealCurrentLineNb() + 1); + return substr($this->currentLine, $offset, $cursor - $offset); + } - return $message.'.'; + private function lexInlineMapping(int &$cursor = 0): string + { + return $this->lexInlineStructure($cursor, '}'); + } + + private function lexInlineSequence(int &$cursor = 0): string + { + return $this->lexInlineStructure($cursor, ']'); + } + + private function lexInlineStructure(int &$cursor, string $closingTag): string + { + $value = $this->currentLine[$cursor]; + ++$cursor; + + do { + $this->consumeWhitespaces($cursor); + + while (isset($this->currentLine[$cursor])) { + switch ($this->currentLine[$cursor]) { + case '"': + case "'": + $value .= $this->lexInlineQuotedString($cursor); + break; + case ':': + case ',': + $value .= $this->currentLine[$cursor]; + ++$cursor; + break; + case '{': + $value .= $this->lexInlineMapping($cursor); + break; + case '[': + $value .= $this->lexInlineSequence($cursor); + break; + case $closingTag: + $value .= $this->currentLine[$cursor]; + ++$cursor; + + return $value; + case '#': + break 2; + default: + $value .= $this->lexUnquotedString($cursor); + } + + if ($this->consumeWhitespaces($cursor)) { + $value .= ' '; + } + } + + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + throw new ParseException('Malformed inline YAML string.'); + } + + private function consumeWhitespaces(int &$cursor): bool + { + $whitespacesConsumed = 0; + + do { + $whitespaceOnlyTokenLength = strspn($this->currentLine, ' ', $cursor); + $whitespacesConsumed += $whitespaceOnlyTokenLength; + $cursor += $whitespaceOnlyTokenLength; + + if (isset($this->currentLine[$cursor])) { + return 0 < $whitespacesConsumed; + } + + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + return 0 < $whitespacesConsumed; } } diff --git a/lib/symfony/yaml/README.md b/lib/symfony/yaml/README.md index b914e7836c..ac25024b63 100644 --- a/lib/symfony/yaml/README.md +++ b/lib/symfony/yaml/README.md @@ -6,8 +6,8 @@ The Yaml component loads and dumps YAML files. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/yaml.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) + * [Documentation](https://symfony.com/doc/current/components/yaml.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/lib/symfony/yaml/Resources/bin/yaml-lint b/lib/symfony/yaml/Resources/bin/yaml-lint new file mode 100644 index 0000000000..0ad73d7147 --- /dev/null +++ b/lib/symfony/yaml/Resources/bin/yaml-lint @@ -0,0 +1,45 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Runs the Yaml lint command. + * + * @author Jan Schädlich + */ + +use Symfony\Component\Console\Application; +use Symfony\Component\Yaml\Command\LintCommand; + +function includeIfExists(string $file): bool +{ + return file_exists($file) && include $file; +} + +if ( + !includeIfExists(__DIR__ . '/../../../../autoload.php') && + !includeIfExists(__DIR__ . '/../../vendor/autoload.php') && + !includeIfExists(__DIR__ . '/../../../../../../vendor/autoload.php') +) { + fwrite(STDERR, 'Install dependencies using Composer.'.PHP_EOL); + exit(1); +} + +if (!class_exists(Application::class)) { + fwrite(STDERR, 'You need the "symfony/console" component in order to run the Yaml linter.'.PHP_EOL); + exit(1); +} + +(new Application())->add($command = new LintCommand()) + ->getApplication() + ->setDefaultCommand($command->getName(), true) + ->run() +; diff --git a/lib/symfony/yaml/Tag/TaggedValue.php b/lib/symfony/yaml/Tag/TaggedValue.php index 000c1d992c..4ea3406135 100644 --- a/lib/symfony/yaml/Tag/TaggedValue.php +++ b/lib/symfony/yaml/Tag/TaggedValue.php @@ -20,27 +20,17 @@ final class TaggedValue private $tag; private $value; - /** - * @param string $tag - * @param mixed $value - */ - public function __construct($tag, $value) + public function __construct(string $tag, $value) { $this->tag = $tag; $this->value = $value; } - /** - * @return string - */ - public function getTag() + public function getTag(): string { return $this->tag; } - /** - * @return mixed - */ public function getValue() { return $this->value; diff --git a/lib/symfony/yaml/Unescaper.php b/lib/symfony/yaml/Unescaper.php index 6a12999deb..d1ef041233 100644 --- a/lib/symfony/yaml/Unescaper.php +++ b/lib/symfony/yaml/Unescaper.php @@ -26,16 +26,14 @@ class Unescaper /** * Regex fragment that matches an escaped character in a double quoted string. */ - const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)'; + public const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)'; /** * Unescapes a single quoted string. * * @param string $value A single quoted string - * - * @return string The unescaped string */ - public function unescapeSingleQuotedString($value) + public function unescapeSingleQuotedString(string $value): string { return str_replace('\'\'', '\'', $value); } @@ -44,10 +42,8 @@ class Unescaper * Unescapes a double quoted string. * * @param string $value A double quoted string - * - * @return string The unescaped string */ - public function unescapeDoubleQuotedString($value) + public function unescapeDoubleQuotedString(string $value): string { $callback = function ($match) { return $this->unescapeCharacter($match[0]); @@ -61,10 +57,8 @@ class Unescaper * Unescapes a character that was found in a double-quoted string. * * @param string $value An escaped character - * - * @return string The unescaped character */ - private function unescapeCharacter($value) + private function unescapeCharacter(string $value): string { switch ($value[1]) { case '0': @@ -120,12 +114,8 @@ class Unescaper /** * Get the UTF-8 character for the given code point. - * - * @param int $c The unicode code point - * - * @return string The corresponding UTF-8 character */ - private static function utf8chr($c) + private static function utf8chr(int $c): string { if (0x80 > $c %= 0x200000) { return \chr($c); diff --git a/lib/symfony/yaml/Yaml.php b/lib/symfony/yaml/Yaml.php index 8719083318..ea13045288 100644 --- a/lib/symfony/yaml/Yaml.php +++ b/lib/symfony/yaml/Yaml.php @@ -18,26 +18,22 @@ use Symfony\Component\Yaml\Exception\ParseException; * * @author Fabien Potencier * - * @final since version 3.4 + * @final */ class Yaml { - const DUMP_OBJECT = 1; - const PARSE_EXCEPTION_ON_INVALID_TYPE = 2; - const PARSE_OBJECT = 4; - const PARSE_OBJECT_FOR_MAP = 8; - const DUMP_EXCEPTION_ON_INVALID_TYPE = 16; - const PARSE_DATETIME = 32; - const DUMP_OBJECT_AS_MAP = 64; - const DUMP_MULTI_LINE_LITERAL_BLOCK = 128; - const PARSE_CONSTANT = 256; - const PARSE_CUSTOM_TAGS = 512; - const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024; - - /** - * @deprecated since version 3.4, to be removed in 4.0. Quote your evaluable keys instead. - */ - const PARSE_KEYS_AS_STRINGS = 2048; + public const DUMP_OBJECT = 1; + public const PARSE_EXCEPTION_ON_INVALID_TYPE = 2; + public const PARSE_OBJECT = 4; + public const PARSE_OBJECT_FOR_MAP = 8; + public const DUMP_EXCEPTION_ON_INVALID_TYPE = 16; + public const PARSE_DATETIME = 32; + public const DUMP_OBJECT_AS_MAP = 64; + public const DUMP_MULTI_LINE_LITERAL_BLOCK = 128; + public const PARSE_CONSTANT = 256; + public const PARSE_CUSTOM_TAGS = 512; + public const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024; + public const DUMP_NULL_AS_TILDE = 2048; /** * Parses a YAML file into a PHP value. @@ -50,11 +46,11 @@ class Yaml * @param string $filename The path to the YAML file to be parsed * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior * - * @return mixed The YAML converted to a PHP value + * @return mixed * * @throws ParseException If the file could not be read or the YAML is not valid */ - public static function parseFile($filename, $flags = 0) + public static function parseFile(string $filename, int $flags = 0) { $yaml = new Parser(); @@ -73,38 +69,12 @@ class Yaml * @param string $input A string containing YAML * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior * - * @return mixed The YAML converted to a PHP value + * @return mixed * * @throws ParseException If the YAML is not valid */ - public static function parse($input, $flags = 0) + public static function parse(string $input, int $flags = 0) { - if (\is_bool($flags)) { - @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since Symfony 3.1 and will be removed in 4.0. Use the PARSE_EXCEPTION_ON_INVALID_TYPE flag instead.', \E_USER_DEPRECATED); - - if ($flags) { - $flags = self::PARSE_EXCEPTION_ON_INVALID_TYPE; - } else { - $flags = 0; - } - } - - if (\func_num_args() >= 3) { - @trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the PARSE_OBJECT flag instead.', \E_USER_DEPRECATED); - - if (func_get_arg(2)) { - $flags |= self::PARSE_OBJECT; - } - } - - if (\func_num_args() >= 4) { - @trigger_error('Passing a boolean flag to toggle object for map support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the Yaml::PARSE_OBJECT_FOR_MAP flag instead.', \E_USER_DEPRECATED); - - if (func_get_arg(3)) { - $flags |= self::PARSE_OBJECT_FOR_MAP; - } - } - $yaml = new Parser(); return $yaml->parse($input, $flags); @@ -120,29 +90,9 @@ class Yaml * @param int $inline The level where you switch to inline YAML * @param int $indent The amount of spaces to use for indentation of nested nodes * @param int $flags A bit field of DUMP_* constants to customize the dumped YAML string - * - * @return string A YAML string representing the original PHP value */ - public static function dump($input, $inline = 2, $indent = 4, $flags = 0) + public static function dump($input, int $inline = 2, int $indent = 4, int $flags = 0): string { - if (\is_bool($flags)) { - @trigger_error('Passing a boolean flag to toggle exception handling is deprecated since Symfony 3.1 and will be removed in 4.0. Use the DUMP_EXCEPTION_ON_INVALID_TYPE flag instead.', \E_USER_DEPRECATED); - - if ($flags) { - $flags = self::DUMP_EXCEPTION_ON_INVALID_TYPE; - } else { - $flags = 0; - } - } - - if (\func_num_args() >= 5) { - @trigger_error('Passing a boolean flag to toggle object support is deprecated since Symfony 3.1 and will be removed in 4.0. Use the DUMP_OBJECT flag instead.', \E_USER_DEPRECATED); - - if (func_get_arg(4)) { - $flags |= self::DUMP_OBJECT; - } - } - $yaml = new Dumper($indent); return $yaml->dump($input, $inline, 0, $flags); diff --git a/lib/symfony/yaml/composer.json b/lib/symfony/yaml/composer.json index b2f0286e0e..7fa6e2cc56 100644 --- a/lib/symfony/yaml/composer.json +++ b/lib/symfony/yaml/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/yaml", "type": "library", - "description": "Symfony Yaml Component", + "description": "Loads and dumps YAML files", "keywords": [], "homepage": "https://symfony.com", "license": "MIT", @@ -16,14 +16,15 @@ } ], "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-ctype": "^1.8" }, "require-dev": { - "symfony/console": "~3.4|~4.0" + "symfony/console": "^5.3|^6.0" }, "conflict": { - "symfony/console": "<3.4" + "symfony/console": "<5.3" }, "suggest": { "symfony/console": "For validating YAML files using the lint command" @@ -34,5 +35,8 @@ "/Tests/" ] }, + "bin": [ + "Resources/bin/yaml-lint" + ], "minimum-stability": "dev" } diff --git a/lib/symfony/yaml/phpunit.xml.dist b/lib/symfony/yaml/phpunit.xml.dist deleted file mode 100644 index b5d4d914f8..0000000000 --- a/lib/symfony/yaml/phpunit.xml.dist +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - ./Tests/ - - - - - - ./ - - ./Tests - ./vendor - - - - diff --git a/lib/thenetworg/oauth2-azure/.devcontainer/Dockerfile b/lib/thenetworg/oauth2-azure/.devcontainer/Dockerfile new file mode 100644 index 0000000000..35ab8f9c43 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/.devcontainer/Dockerfile @@ -0,0 +1,15 @@ +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.134.0/containers/php/.devcontainer/base.Dockerfile +ARG VARIANT="7" +FROM mcr.microsoft.com/vscode/devcontainers/php:0-${VARIANT} + +# [Optional] Install a version of Node.js using nvm for front end dev +ARG INSTALL_NODE="true" +ARG NODE_VERSION="lts/*" +RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 \ No newline at end of file diff --git a/lib/thenetworg/oauth2-azure/.devcontainer/devcontainer.json b/lib/thenetworg/oauth2-azure/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..676de3dcb8 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/.devcontainer/devcontainer.json @@ -0,0 +1,29 @@ +// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.134.0/containers/php +{ + "name": "PHP", + "build": { + "dockerfile": "Dockerfile", + "args": { + // Update VARIANT to pick a PHP version: 7, 7.4, 7.3 + "VARIANT": "7", + "INSTALL_NODE": "false", + "NODE_VERSION": "lts/*" + } + }, + // Set *default* container specific settings.json values on container create. + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "felixfbecker.php-debug", + "felixfbecker.php-intellisense" + ], + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "php -v", + // Comment out to connect as root instead. + "remoteUser": "vscode" +} \ No newline at end of file diff --git a/lib/thenetworg/oauth2-azure/.gitignore b/lib/thenetworg/oauth2-azure/.gitignore new file mode 100644 index 0000000000..57a23bd064 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/.gitignore @@ -0,0 +1,9 @@ +/build +/vendor +composer.phar +composer.lock +.DS_Store + +# IDE +/.idea +/.vscode diff --git a/lib/thenetworg/oauth2-azure/.php_cs b/lib/thenetworg/oauth2-azure/.php_cs new file mode 100644 index 0000000000..47be3dfc00 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/.php_cs @@ -0,0 +1,70 @@ +in(__DIR__ . '/src'); + +return PhpCsFixer\Config::create() + ->setRiskyAllowed(true) + ->setUsingCache(false) + ->setRules([ + '@PSR2' => true, + 'align_multiline_comment' => true, + 'array_indentation' => true, + 'array_syntax' => ['syntax' => 'short'], + 'binary_operator_spaces' => ['default' => 'align_single_space_minimal'], + 'blank_line_after_opening_tag' => true, + 'class_attributes_separation' => true, + 'combine_consecutive_issets' => true, + 'general_phpdoc_annotation_remove' => ['annotations' => ['author', 'package', 'subpackage']], + 'declare_equal_normalize' => ['space' => 'single'], + 'dir_constant' => true, + 'fully_qualified_strict_types' => true, + 'function_typehint_space' => true, + 'heredoc_to_nowdoc' => true, + 'include' => true, + 'is_null' => ['use_yoda_style' => true], + 'linebreak_after_opening_tag' => true, + 'lowercase_cast' => true, + 'modernize_types_casting' => true, + 'new_with_braces' => true, + 'no_alias_functions' => true, + 'no_alternative_syntax' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => ['use' => 'echo'], + 'no_multiline_whitespace_before_semicolons' => true, + 'no_null_property_initialization' => true, + 'no_php4_constructor' => true, + 'no_short_echo_tag' => false, + 'no_unreachable_default_argument_value' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'ordered_class_elements' => true, + 'ordered_imports' => true, + 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false], + 'phpdoc_order' => true, + 'phpdoc_return_self_reference' => true, + 'phpdoc_scalar' => true, + 'phpdoc_single_line_var_spacing' => true, + 'phpdoc_to_comment' => true, + 'phpdoc_trim' => true, + 'phpdoc_types' => true, + 'phpdoc_types_order' => ['null_adjustment' => 'always_last'], + 'phpdoc_var_without_name' => true, + 'short_scalar_cast' => true, + 'simplified_null_return' => true, + 'single_blank_line_before_namespace' => true, + 'single_line_comment_style' => true, + 'single_quote' => ['strings_containing_single_quote_chars' => true], + 'standardize_increment' => true, + 'standardize_not_equals' => true, + 'trailing_comma_in_multiline_array' => true, + 'trim_array_spaces' => true, + 'whitespace_after_comma_in_array' => true, + 'yoda_style' => true, + ]) + ->setFinder($finder); diff --git a/lib/thenetworg/oauth2-azure/.vscode/launch.json b/lib/thenetworg/oauth2-azure/.vscode/launch.json new file mode 100644 index 0000000000..c69965a9e8 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch application", + "type": "php", + "request": "launch", + "program": "${workspaceFolder}/index.php", + "cwd": "${workspaceFolder}", + "port": 9000 + }, + { + "name": "Listen for XDebug", + "type": "php", + "request": "launch", + "port": 9000 + }, + { + "name": "Launch currently open script", + "type": "php", + "request": "launch", + "program": "${file}", + "cwd": "${fileDirname}", + "port": 9000 + } + ] +} \ No newline at end of file diff --git a/lib/thenetworg/oauth2-azure/CHANGELOG.md b/lib/thenetworg/oauth2-azure/CHANGELOG.md new file mode 100644 index 0000000000..689c666852 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog +All Notable changes to `oauth2-azure` will be documented in this file + +## v1.0.0 - 16NOV2015 +- Initial release \ No newline at end of file diff --git a/lib/thenetworg/oauth2-azure/LICENSE.md b/lib/thenetworg/oauth2-azure/LICENSE.md new file mode 100644 index 0000000000..196dd8655b --- /dev/null +++ b/lib/thenetworg/oauth2-azure/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 TheNetw.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/thenetworg/oauth2-azure/README.md b/lib/thenetworg/oauth2-azure/README.md new file mode 100644 index 0000000000..ee6f06d3fa --- /dev/null +++ b/lib/thenetworg/oauth2-azure/README.md @@ -0,0 +1,283 @@ +# Azure Active Directory Provider for OAuth 2.0 Client +[![Latest Version](https://img.shields.io/github/release/thenetworg/oauth2-azure.svg?style=flat-square)](https://github.com/thenetworg/oauth2-azure/releases) +[![Total Downloads](https://img.shields.io/packagist/dt/thenetworg/oauth2-azure.svg?style=flat-square)](https://packagist.org/packages/thenetworg/oauth2-azure) +[![Software License](https://img.shields.io/packagist/l/thenetworg/oauth2-azure.svg?style=flat-square)](LICENSE.md) + +This package provides [Azure Active Directory](https://azure.microsoft.com/en-us/services/active-directory/) OAuth 2.0 support for the PHP League's [OAuth 2.0 Client](https://github.com/thephpleague/oauth2-client). + +## Table of Contents +- [Installation](#installation) +- [Usage](#usage) + - [Authorization Code Flow](#authorization-code-flow) + - [Advanced flow](#advanced-flow) + - [Using custom parameters](#using-custom-parameters) + - [**NEW** - Call on behalf of a token provided by another app](#call-on-behalf-of-a-token-provided-by-another-app) + - [**NEW** - Logging out](#logging-out) +- [Making API Requests](#making-api-requests) + - [Variables](#variables) +- [Resource Owner](#resource-owner) +- [**UPDATED** - Microsoft Graph](#microsoft-graph) +- [**NEW** - Protecting your API - *experimental*](#protecting-your-api---experimental) +- [Azure Active Directory B2C - *experimental*](#azure-active-directory-b2c---experimental) +- [Multipurpose refresh tokens - *experimental*](#multipurpose-refresh-tokens---experimental) +- [Known users](#known-users) +- [Contributing](#contributing) +- [Credits](#credits) +- [Support](#support) +- [License](#license) + +## Installation + +To install, use composer: + +``` +composer require thenetworg/oauth2-azure +``` + +## Usage + +Usage is the same as The League's OAuth client, using `\TheNetworg\OAuth2\Client\Provider\Azure` as the provider. + +### Authorization Code Flow + +```php +$provider = new TheNetworg\OAuth2\Client\Provider\Azure([ + 'clientId' => '{azure-client-id}', + 'clientSecret' => '{azure-client-secret}', + 'redirectUri' => 'https://example.com/callback-url', + //Optional + 'scopes' => ['openid'], + //Optional + 'defaultEndPointVersion' => '2.0' +]); + +// Set to use v2 API, skip the line or set the value to Azure::ENDPOINT_VERSION_1_0 if willing to use v1 API +$provider->defaultEndPointVersion = TheNetworg\OAuth2\Client\Provider\Azure::ENDPOINT_VERSION_2_0; + +$baseGraphUri = $provider->getRootMicrosoftGraphUri(null); +$provider->scope = 'openid profile email offline_access ' . $baseGraphUri . '/User.Read'; + +if (isset($_GET['code']) && isset($_SESSION['OAuth2.state']) && isset($_GET['state'])) { + if ($_GET['state'] == $_SESSION['OAuth2.state']) { + unset($_SESSION['OAuth2.state']); + + // Try to get an access token (using the authorization code grant) + /** @var AccessToken $token */ + $token = $provider->getAccessToken('authorization_code', [ + 'scope' => $provider->scope, + 'code' => $_GET['code'], + ]); + + // Verify token + // Save it to local server session data + + return $token->getToken(); + } else { + echo 'Invalid state'; + + return null; + } +} else { + // // Check local server's session data for a token + // // and verify if still valid + // /** @var ?AccessToken $token */ + // $token = // token cached in session data, null if not found; + // + // if (isset($token)) { + // $me = $provider->get($provider->getRootMicrosoftGraphUri($token) . '/v1.0/me', $token); + // $userEmail = $me['mail']; + // + // if ($token->hasExpired()) { + // if (!is_null($token->getRefreshToken())) { + // $token = $provider->getAccessToken('refresh_token', [ + // 'scope' => $provider->scope, + // 'refresh_token' => $token->getRefreshToken() + // ]); + // } else { + // $token = null; + // } + // } + //} + // + // If the token is not found in + // if (!isset($token)) { + $authorizationUrl = $provider->getAuthorizationUrl(['scope' => $provider->scope]); + + $_SESSION['OAuth2.state'] = $provider->getState(); + + header('Location: ' . $authorizationUrl); + + exit; + // } + + return $token->getToken(); +} +``` + +#### Advanced flow + +The [Authorization Code Grant Flow](https://msdn.microsoft.com/en-us/library/azure/dn645542.aspx) is a little bit different for Azure Active Directory. Instead of scopes, you specify the resource which you would like to access - there is a param `$provider->authWithResource` which will automatically populate the `resource` param of request with the value of either `$provider->resource` or `$provider->urlAPI`. This feature is mostly intended for v2.0 endpoint of Azure AD (see more [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/azure-ad-endpoint-comparison#scopes-not-resources)). + +#### Using custom parameters + +With [oauth2-client](https://github.com/thephpleague/oauth2-client) of version 1.3.0 and higher, it is now possible to specify custom parameters for the authorization URL, so you can now make use of options like `prompt`, `login_hint` and similar. See the following example of obtaining an authorization URL which will force the user to reauthenticate: +```php +$authUrl = $provider->getAuthorizationUrl([ + 'prompt' => 'login' +]); +``` +You can find additional parameters [here](https://msdn.microsoft.com/en-us/library/azure/dn645542.aspx). + +### Logging out +If you need to quickly generate a logout URL for the user, you can do following: +```php +// Assuming you have provider properly initialized. +$post_logout_redirect_uri = 'https://www.msn.com'; // The logout destination after the user is logged out from their account. +$logoutUrl = $provider->getLogoutUrl($post_logout_redirect_uri); +header('Location: '.$logoutUrl); // Redirect the user to the generated URL +``` + +#### Call on behalf of a token provided by another app + +```php +// Use token provided by the other app +// Make sure the other app mentioned this app in the scope when requesting the token +$suppliedToken = ''; + +$provider = xxxxx;// Initialize provider + +// Call this to get claims +// $claims = $provider->validateAccessToken($suppliedToken); + +/** @var AccessToken $token */ +$token = $provider->getAccessToken('jwt_bearer', [ + 'scope' => $provider->scope, + 'assertion' => $suppliedToken, + 'requested_token_use' => 'on_behalf_of', +]); +``` + +## Making API Requests + +This library also provides easy interface to make it easier to interact with [Azure Graph API](https://msdn.microsoft.com/en-us/library/azure/hh974476.aspx) and [Microsoft Graph](http://graph.microsoft.io), the following methods are available on `provider` object (it also handles automatic token refresh flow should it be needed during making the request): + +- `get($ref, $accessToken, $headers = [])` +- `post($ref, $body, $accessToken, $headers = [])` +- `put($ref, $body, $accessToken, $headers = [])` +- `delete($ref, $body, $accessToken, $headers = [])` +- `patch($ref, $body, $accessToken, $headers = [])` +- `getObjects($tenant, $ref, $accessToken, $headers = [])` This is used for example for listing large amount of data - where you need to list all users for example - it automatically follows `odata.nextLink` until the end. + - `$tenant` tenant has to be provided since the `odata.nextLink` doesn't contain it. +- `request($method, $ref, $accessToken, $options = [])` See [#36](https://github.com/TheNetworg/oauth2-azure/issues/36) for use case. + +*Please note that if you need to create a custom request, the method getAuthenticatedRequest and getResponse can still be used.* + +### Variables +- `$ref` The URL reference without the leading `/`, for example `myOrganization/groups` +- `$body` The contents of the request, make has to be either string (so make sure to use `json_encode` to encode the request)s or stream (see [Guzzle HTTP](http://docs.guzzlephp.org/en/latest/request-options.html#body)) +- `$accessToken` The access token object obtained by using `getAccessToken` method +- `$headers` Ability to set custom headers for the request (see [Guzzle HTTP](http://docs.guzzlephp.org/en/latest/request-options.html#headers)) + +## Resource Owner +With version 1.1.0 and onward, the Resource Owner information is parsed from the JWT passed in `access_token` by Azure Active Directory. It exposes few attributes and one function. + +**Example:** +```php +$resourceOwner = $provider->getResourceOwner($token); +echo 'Hello, '.$resourceOwner->getFirstName().'!'; +``` +The exposed attributes and function are: +- `getId()` - Gets user's object id - unique for each user +- `getFirstName()` - Gets user's first name +- `getLastName()` - Gets user's family name/surname +- `getTenantId()` - Gets id of tenant which the user is member of +- `getUpn()` - Gets user's User Principal Name, which can be also used as user's e-mail address +- `claim($name)` - Gets any other claim (specified as `$name`) from the JWT, full list can be found [here](https://azure.microsoft.com/en-us/documentation/articles/active-directory-token-and-claims/) + +## Microsoft Graph +Calling [Microsoft Graph](http://graph.microsoft.io/) is very simple with this library. After provider initialization simply change the API URL followingly (replace `v1.0` with your desired version): +```php +// Mention Microsoft Graph scope when initializing the provider +$baseGraphUri = $provider->getRootMicrosoftGraphUri(null); +$provider->scope = 'your scope ' . $baseGraphUri . '/User.Read'; + +// Call a query +$provider->get($provider->getRootMicrosoftGraphUri($token) . '/v1.0/me', $token); +``` +After that, when requesting access token, refresh token or so, provide the `resource` with value `https://graph.microsoft.com/` in order to be able to make calls to the Graph (see more about `resource` [here](#advanced-flow)). + +## Protecting your API - *experimental* +With version 1.2.0 you can now use this library to protect your API with Azure Active Directory authentication very easily. The Provider now also exposes `validateAccessToken(string $token)` which lets you pass an access token inside which you for example received in the `Authorization` header of the request on your API. You can use the function followingly (in vanilla PHP): +```php +// Assuming you have already initialized the $provider + +// Obtain the accessToken - in this case, we are getting it from Authorization header +$headers = getallheaders(); +// Assuming you got the value of Authorization header as "Bearer [the_access_token]" we parse it +$authorization = explode(' ', $headers['Authorization']); +$accessToken = $authorization[1]; + +try { + $claims = $provider->validateAccessToken($accessToken); +} catch (Exception $e) { + // Something happened, handle the error +} + +// The access token is valid, you can now proceed with your code. You can also access the $claims as defined in JWT - for example roles, group memberships etc. +``` + +You may also need to access some other resource from the API like the Microsoft Graph to get some additional information. In order to do that, there is `urn:ietf:params:oauth:grant-type:jwt-bearer` grant available ([RFC](https://tools.ietf.org/html/draft-jones-oauth-jwt-bearer-03)). An example (assuming you have the code above working and you have the required permissions configured correctly in the Azure AD application): +```php +$graphAccessToken = $provider->getAccessToken('jwt_bearer', [ + 'resource' => 'https://graph.microsoft.com/v1.0/', + 'assertion' => $accessToken, + 'requested_token_use' => 'on_behalf_of' +]); + +$me = $provider->get('https://graph.microsoft.com/v1.0/me', $graphAccessToken); +print_r($me); +``` +Just to make it easier so you don't have to remember entire name for `grant_type` (`urn:ietf:params:oauth:grant-type:jwt-bearer`), you just use short `jwt_bearer` instead. + +## Azure Active Directory B2C - *experimental* +You can also now very simply make use of [Azure Active Directory B2C](https://azure.microsoft.com/en-us/documentation/articles/active-directory-b2c-reference-oauth-code/). Before authentication, change the endpoints using `pathAuthorize`, `pathToken` and `scope` and additionally specify your [login policy](https://azure.microsoft.com/en-gb/documentation/articles/active-directory-b2c-reference-policies/). **Please note that the B2C support is still experimental and wasn't fully tested.** +```php +$provider->pathAuthorize = "/oauth2/v2.0/authorize"; +$provider->pathToken = "/oauth2/v2.0/token"; +$provider->scope = ["idtoken"]; + +// Specify custom policy in our authorization URL +$authUrl = $provider->getAuthorizationUrl([ + 'p' => 'b2c_1_siup' +]); +``` + +## Multipurpose refresh tokens - *experimental* +In cause that you need to access multiple resources (like your API and Microsoft Graph), you can use multipurpose [refresh tokens](https://msdn.microsoft.com/en-us/library/azure/dn645538.aspx). Once obtaining a token for first resource, you can simply request another token for different resource like so: +```php +$accessToken2 = $provider->getAccessToken('refresh_token', [ + 'refresh_token' => $accessToken1->getRefreshToken(), + 'resource' => 'http://urlOfYourSecondResource' +]); +``` +At the moment, there is one issue: When you make a call to your API and the token has expired, it will have the value of `$provider->urlAPI` which is obviously wrong for `$accessToken2`. The solution is very simple - set the `$provider->urlAPI` to the resource which you want to call. This issue will be addressed in future release. **Please note that this is experimental and wasn't fully tested.** + +## Known users +If you are using this library and would like to be listed here, please let us know! +- [TheNetworg/DreamSpark-SSO](https://github.com/thenetworg/dreamspark-sso) + +## Contributing +We accept contributions via [Pull Requests on Github](https://github.com/thenetworg/oauth2-azure). + +## Credits +- [Jan Hajek](https://github.com/hajekj) ([TheNetw.org](https://thenetw.org)) +- [Vittorio Bertocci](https://github.com/vibronet) (Microsoft) + - Thanks for the splendid support while implementing #16 +- [Martin Cetkovský](https://github.com/mcetkovsky) ([cetkovsky.eu](https://www.cetkovsky.eu)] +- [All Contributors](https://github.com/thenetworg/oauth2-azure/contributors) + +## Support +If you find a bug or encounter any issue or have a problem/question with this library please create a [new issue](https://github.com/TheNetworg/oauth2-azure/issues). + +## License +The MIT License (MIT). Please see [License File](https://github.com/thenetworg/oauth2-azure/blob/master/LICENSE) for more information. diff --git a/lib/thenetworg/oauth2-azure/composer.json b/lib/thenetworg/oauth2-azure/composer.json new file mode 100644 index 0000000000..4fb3484639 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/composer.json @@ -0,0 +1,36 @@ +{ + "name": "thenetworg/oauth2-azure", + "description": "Azure Active Directory OAuth 2.0 Client Provider for The PHP League OAuth2-Client", + "license": "MIT", + "authors": [ + { + "name": "Jan Hajek", + "email": "jan.hajek@thenetw.org", + "homepage": "https://thenetw.org" + } + ], + "keywords": [ + "oauth", + "oauth2", + "client", + "authorization", + "microsoft", + "windows azure", + "azure", + "azure active directory", + "aad", + "sso" + ], + "require": { + "ext-json": "*", + "ext-openssl": "*", + "php": "^7.1|^8.0", + "league/oauth2-client": "~2.0", + "firebase/php-jwt": "~3.0||~4.0||~5.0||~6.0" + }, + "autoload": { + "psr-4": { + "TheNetworg\\OAuth2\\Client\\": "src/" + } + } +} diff --git a/lib/thenetworg/oauth2-azure/src/Grant/JwtBearer.php b/lib/thenetworg/oauth2-azure/src/Grant/JwtBearer.php new file mode 100644 index 0000000000..c237728555 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/src/Grant/JwtBearer.php @@ -0,0 +1,19 @@ +scope = array_merge($options['scopes'], $this->scope); + } + if (isset($options['defaultEndPointVersion']) && + in_array($options['defaultEndPointVersion'], self::ENDPOINT_VERSIONS, true)) { + $this->defaultEndPointVersion = $options['defaultEndPointVersion']; + } + $this->grantFactory->setGrant('jwt_bearer', new JwtBearer()); + } + + /** + * @param string $tenant + * @param string $version + */ + protected function getOpenIdConfiguration($tenant, $version) { + if (!is_array($this->openIdConfiguration)) { + $this->openIdConfiguration = []; + } + if (!array_key_exists($tenant, $this->openIdConfiguration)) { + $this->openIdConfiguration[$tenant] = []; + } + if (!array_key_exists($version, $this->openIdConfiguration[$tenant])) { + $versionInfix = $this->getVersionUriInfix($version); + $openIdConfigurationUri = $this->urlLogin . $tenant . $versionInfix . '/.well-known/openid-configuration?appid=' . $this->clientId; + + $factory = $this->getRequestFactory(); + $request = $factory->getRequestWithOptions( + 'get', + $openIdConfigurationUri, + [] + ); + $response = $this->getParsedResponse($request); + $this->openIdConfiguration[$tenant][$version] = $response; + } + + return $this->openIdConfiguration[$tenant][$version]; + } + + /** + * @inheritdoc + */ + public function getBaseAuthorizationUrl(): string + { + $openIdConfiguration = $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); + return $openIdConfiguration['authorization_endpoint']; + } + + /** + * @inheritdoc + */ + public function getBaseAccessTokenUrl(array $params): string + { + $openIdConfiguration = $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); + return $openIdConfiguration['token_endpoint']; + } + + /** + * @inheritdoc + */ + public function getAccessToken($grant, array $options = []): AccessTokenInterface + { + if ($this->defaultEndPointVersion != self::ENDPOINT_VERSION_2_0) { + // Version 2.0 does not support the resources parameter + // https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow + // while version 1.0 does recommend it + // https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code + if ($this->authWithResource) { + $options['resource'] = $this->resource ? $this->resource : $this->urlAPI; + } + } + return parent::getAccessToken($grant, $options); + } + + /** + * @inheritdoc + */ + public function getResourceOwner(\League\OAuth2\Client\Token\AccessToken $token): ResourceOwnerInterface + { + $data = $token->getIdTokenClaims(); + return $this->createResourceOwner($data, $token); + } + + /** + * @inheritdoc + */ + public function getResourceOwnerDetailsUrl(\League\OAuth2\Client\Token\AccessToken $token): string + { + return ''; // shouldn't that return such a URL? + } + + public function getObjects($tenant, $ref, &$accessToken, $headers = []) + { + $objects = []; + + $response = null; + do { + if (false === filter_var($ref, FILTER_VALIDATE_URL)) { + $ref = $tenant . '/' . $ref; + } + + $response = $this->request('get', $ref, $accessToken, ['headers' => $headers]); + $values = $response; + if (isset($response['value'])) { + $values = $response['value']; + } + foreach ($values as $value) { + $objects[] = $value; + } + if (isset($response['odata.nextLink'])) { + $ref = $response['odata.nextLink']; + } elseif (isset($response['@odata.nextLink'])) { + $ref = $response['@odata.nextLink']; + } else { + $ref = null; + } + } while (null != $ref); + + return $objects; + } + + /** + * @param $accessToken AccessToken|null + * @return string + */ + public function getRootMicrosoftGraphUri($accessToken) + { + if (is_null($accessToken)) { + $tenant = $this->tenant; + $version = $this->defaultEndPointVersion; + } else { + $idTokenClaims = $accessToken->getIdTokenClaims(); + $tenant = array_key_exists('tid', $idTokenClaims) ? $idTokenClaims['tid'] : $this->tenant; + $version = array_key_exists('ver', $idTokenClaims) ? $idTokenClaims['ver'] : $this->defaultEndPointVersion; + } + $openIdConfiguration = $this->getOpenIdConfiguration($tenant, $version); + return 'https://' . $openIdConfiguration['msgraph_host']; + } + + public function get($ref, &$accessToken, $headers = [], $doNotWrap = false) + { + $response = $this->request('get', $ref, $accessToken, ['headers' => $headers]); + + return $doNotWrap ? $response : $this->wrapResponse($response); + } + + public function post($ref, $body, &$accessToken, $headers = []) + { + $response = $this->request('post', $ref, $accessToken, ['body' => $body, 'headers' => $headers]); + + return $this->wrapResponse($response); + } + + public function put($ref, $body, &$accessToken, $headers = []) + { + $response = $this->request('put', $ref, $accessToken, ['body' => $body, 'headers' => $headers]); + + return $this->wrapResponse($response); + } + + public function delete($ref, &$accessToken, $headers = []) + { + $response = $this->request('delete', $ref, $accessToken, ['headers' => $headers]); + + return $this->wrapResponse($response); + } + + public function patch($ref, $body, &$accessToken, $headers = []) + { + $response = $this->request('patch', $ref, $accessToken, ['body' => $body, 'headers' => $headers]); + + return $this->wrapResponse($response); + } + + public function request($method, $ref, &$accessToken, $options = []) + { + if ($accessToken->hasExpired()) { + $accessToken = $this->getAccessToken('refresh_token', [ + 'refresh_token' => $accessToken->getRefreshToken(), + ]); + } + + $url = null; + if (false !== filter_var($ref, FILTER_VALIDATE_URL)) { + $url = $ref; + } else { + if (false !== strpos($this->urlAPI, 'graph.windows.net')) { + $tenant = 'common'; + if (property_exists($this, 'tenant')) { + $tenant = $this->tenant; + } + $ref = "$tenant/$ref"; + + $url = $this->urlAPI . $ref; + + $url .= (false === strrpos($url, '?')) ? '?' : '&'; + $url .= 'api-version=' . $this->API_VERSION; + } else { + $url = $this->urlAPI . $ref; + } + } + + if (isset($options['body']) && ('array' == gettype($options['body']) || 'object' == gettype($options['body']))) { + $options['body'] = json_encode($options['body']); + } + if (!isset($options['headers']['Content-Type']) && isset($options['body'])) { + $options['headers']['Content-Type'] = 'application/json'; + } + + $request = $this->getAuthenticatedRequest($method, $url, $accessToken, $options); + $response = $this->getParsedResponse($request); + + return $response; + } + + public function getClientId() + { + return $this->clientId; + } + + /** + * Obtain URL for logging out the user. + * + * @param $post_logout_redirect_uri string The URL which the user should be redirected to after logout + * + * @return string + */ + public function getLogoutUrl($post_logout_redirect_uri = "") + { + $openIdConfiguration = $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); + $logoutUri = $openIdConfiguration['end_session_endpoint']; + + if (!empty($post_logout_redirect_uri)) { + $logoutUri .= '?post_logout_redirect_uri=' . rawurlencode($post_logout_redirect_uri); + } + + return $logoutUri; + } + + /** + * Validate the access token you received in your application. + * + * @param $accessToken string The access token you received in the authorization header. + * + * @return array + */ + public function validateAccessToken($accessToken) + { + $keys = $this->getJwtVerificationKeys(); + $tokenClaims = (array)JWT::decode($accessToken, $keys, ['RS256']); + + $this->validateTokenClaims($tokenClaims); + + return $tokenClaims; + } + + /** + * Validate the access token claims from an access token you received in your application. + * + * @param $tokenClaims array The token claims from an access token you received in the authorization header. + * + * @return void + */ + public function validateTokenClaims($tokenClaims) { + if ($this->getClientId() != $tokenClaims['aud']) { + throw new \RuntimeException('The client_id / audience is invalid!'); + } + if ($tokenClaims['nbf'] > time() || $tokenClaims['exp'] < time()) { + // Additional validation is being performed in firebase/JWT itself + throw new \RuntimeException('The id_token is invalid!'); + } + + if ('common' == $this->tenant) { + $this->tenant = $tokenClaims['tid']; + } + + $version = array_key_exists('ver', $tokenClaims) ? $tokenClaims['ver'] : $this->defaultEndPointVersion; + $tenant = $this->getTenantDetails($this->tenant, $version); + if ($tokenClaims['iss'] != $tenant['issuer']) { + throw new \RuntimeException('Invalid token issuer (tokenClaims[iss]' . $tokenClaims['iss'] . ', tenant[issuer] ' . $tenant['issuer'] . ')!'); + } + } + + /** + * Get JWT verification keys from Azure Active Directory. + * + * @return array + */ + public function getJwtVerificationKeys() + { + $openIdConfiguration = $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); + $keysUri = $openIdConfiguration['jwks_uri']; + + $factory = $this->getRequestFactory(); + $request = $factory->getRequestWithOptions('get', $keysUri, []); + + $response = $this->getParsedResponse($request); + + $keys = []; + foreach ($response['keys'] as $i => $keyinfo) { + if (isset($keyinfo['x5c']) && is_array($keyinfo['x5c'])) { + foreach ($keyinfo['x5c'] as $encodedkey) { + $cert = + '-----BEGIN CERTIFICATE-----' . PHP_EOL + . chunk_split($encodedkey, 64, PHP_EOL) + . '-----END CERTIFICATE-----' . PHP_EOL; + + $cert_object = openssl_x509_read($cert); + + if ($cert_object === false) { + throw new \RuntimeException('An attempt to read ' . $encodedkey . ' as a certificate failed.'); + } + + $pkey_object = openssl_pkey_get_public($cert_object); + + if ($pkey_object === false) { + throw new \RuntimeException('An attempt to read a public key from a ' . $encodedkey . ' certificate failed.'); + } + + $pkey_array = openssl_pkey_get_details($pkey_object); + + if ($pkey_array === false) { + throw new \RuntimeException('An attempt to get a public key as an array from a ' . $encodedkey . ' certificate failed.'); + } + + $publicKey = $pkey_array ['key']; + + $keys[$keyinfo['kid']] = new Key($publicKey, 'RS256'); + } + } else if (isset($keyinfo['n']) && isset($keyinfo['e'])) { + $pkey_object = JWK::parseKey($keyinfo); + + if ($pkey_object === false) { + throw new \RuntimeException('An attempt to read a public key from a ' . $keyinfo['n'] . ' certificate failed.'); + } + + $pkey_array = openssl_pkey_get_details($pkey_object); + + if ($pkey_array === false) { + throw new \RuntimeException('An attempt to get a public key as an array from a ' . $keyinfo['n'] . ' certificate failed.'); + } + + $publicKey = $pkey_array ['key']; + + $keys[$keyinfo['kid']] = new Key($publicKey, 'RS256');; + } + } + + return $keys; + } + + protected function getVersionUriInfix($version) + { + return + ($version == self::ENDPOINT_VERSION_2_0) + ? '/v' . self::ENDPOINT_VERSION_2_0 + : ''; + } + + /** + * Get the specified tenant's details. + * + * @param string $tenant + * @param string|null $version + * + * @return array + * @throws IdentityProviderException + */ + public function getTenantDetails($tenant, $version) + { + return $this->getOpenIdConfiguration($this->tenant, $this->defaultEndPointVersion); + } + + /** + * @inheritdoc + */ + protected function checkResponse(ResponseInterface $response, $data): void + { + if (isset($data['odata.error']) || isset($data['error'])) { + if (isset($data['odata.error']['message']['value'])) { + $message = $data['odata.error']['message']['value']; + } elseif (isset($data['error']['message'])) { + $message = $data['error']['message']; + } elseif (isset($data['error']) && !is_array($data['error'])) { + $message = $data['error']; + } else { + $message = $response->getReasonPhrase(); + } + + if (isset($data['error_description']) && !is_array($data['error_description'])) { + $message .= PHP_EOL . $data['error_description']; + } + + throw new IdentityProviderException( + $message, + $response->getStatusCode(), + $response->getBody() + ); + } + } + + /** + * @inheritdoc + */ + protected function getDefaultScopes(): array + { + return $this->scope; + } + + /** + * @inheritdoc + */ + protected function getScopeSeparator(): string + { + return $this->scopeSeparator; + } + + /** + * @inheritdoc + */ + protected function createAccessToken(array $response, AbstractGrant $grant): AccessToken + { + return new AccessToken($response, $this); + } + + /** + * @inheritdoc + */ + protected function createResourceOwner(array $response, \League\OAuth2\Client\Token\AccessToken $token): AzureResourceOwner + { + return new AzureResourceOwner($response); + } + + private function wrapResponse($response) + { + if (empty($response)) { + return; + } elseif (isset($response['value'])) { + return $response['value']; + } + + return $response; + } +} diff --git a/lib/thenetworg/oauth2-azure/src/Provider/AzureResourceOwner.php b/lib/thenetworg/oauth2-azure/src/Provider/AzureResourceOwner.php new file mode 100644 index 0000000000..337ae51250 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/src/Provider/AzureResourceOwner.php @@ -0,0 +1,97 @@ +data = $data; + } + + /** + * Retrieves id of resource owner. + * + * @return string|null + */ + public function getId() + { + return $this->claim('oid'); + } + + /** + * Retrieves first name of resource owner. + * + * @return string|null + */ + public function getFirstName() + { + return $this->claim('given_name'); + } + + /** + * Retrieves last name of resource owner. + * + * @return string|null + */ + public function getLastName() + { + return $this->claim('family_name'); + } + + /** + * Retrieves user principal name of resource owner. + * + * @return string|null + */ + public function getUpn() + { + return $this->claim('upn'); + } + + /** + * Retrieves tenant id of resource owner. + * + * @return string|null + */ + public function getTenantId() + { + return $this->claim('tid'); + } + + /** + * Returns a field from the parsed JWT data. + * + * @param string $name + * + * @return mixed|null + */ + public function claim($name) + { + return isset($this->data[$name]) ? $this->data[$name] : null; + } + + /** + * Returns all the data obtained about the user. + * + * @return array + */ + public function toArray() + { + return $this->data; + } +} diff --git a/lib/thenetworg/oauth2-azure/src/Token/AccessToken.php b/lib/thenetworg/oauth2-azure/src/Token/AccessToken.php new file mode 100644 index 0000000000..7de80f9e52 --- /dev/null +++ b/lib/thenetworg/oauth2-azure/src/Token/AccessToken.php @@ -0,0 +1,73 @@ +idToken = $options['id_token']; + + unset($this->values['id_token']); + + $keys = $provider->getJwtVerificationKeys(); + $idTokenClaims = null; + try { + $tks = explode('.', $this->idToken); + // Check if the id_token contains signature + if (3 == count($tks) && !empty($tks[2])) { + $idTokenClaims = (array)JWT::decode($this->idToken, $keys, ['RS256']); + } else { + // The id_token is unsigned (coming from v1.0 endpoint) - https://msdn.microsoft.com/en-us/library/azure/dn645542.aspx + + // Since idToken is not signed, we just do OAuth2 flow without validating the id_token + // // Validate the access_token signature first by parsing it as JWT into claims + // $accessTokenClaims = (array)JWT::decode($options['access_token'], $keys, ['RS256']); + // Then parse the idToken claims only without validating the signature + $idTokenClaims = (array)JWT::jsonDecode(JWT::urlsafeB64Decode($tks[1])); + } + } catch (JWT_Exception $e) { + throw new RuntimeException('Unable to parse the id_token!'); + } + + $provider->validateTokenClaims($idTokenClaims); + + $this->idTokenClaims = $idTokenClaims; + } + } + + public function getIdToken() + { + return $this->idToken; + } + + public function getIdTokenClaims() + { + return $this->idTokenClaims; + } + + /** + * @inheritdoc + */ + public function jsonSerialize() + { + $parameters = parent::jsonSerialize(); + + if ($this->idToken) { + $parameters['id_token'] = $this->idToken; + } + + return $parameters; + } +} diff --git a/lib/twig/twig/.gitattributes b/lib/twig/twig/.gitattributes index 3a3ce6e7ce..1ce832b520 100644 --- a/lib/twig/twig/.gitattributes +++ b/lib/twig/twig/.gitattributes @@ -1,2 +1,4 @@ +/doc/** export-ignore +/extra/** export-ignore /tests export-ignore /phpunit.xml.dist export-ignore diff --git a/lib/twig/twig/.github/workflows/ci.yml b/lib/twig/twig/.github/workflows/ci.yml new file mode 100644 index 0000000000..50f23f9a47 --- /dev/null +++ b/lib/twig/twig/.github/workflows/ci.yml @@ -0,0 +1,149 @@ +name: "CI" + +on: + pull_request: + push: + branches: + - '3.x' + +env: + SYMFONY_PHPUNIT_DISABLE_RESULT_CACHE: 1 + +permissions: + contents: read + +jobs: + tests: + name: "PHP ${{ matrix.php-version }}" + + runs-on: 'ubuntu-latest' + + continue-on-error: ${{ matrix.experimental }} + + strategy: + matrix: + php-version: + - '7.2.5' + - '7.3' + - '7.4' + - '8.0' + - '8.1' + experimental: [false] + + steps: + - name: "Checkout code" + uses: actions/checkout@v2 + + - name: "Install PHP with extensions" + uses: shivammathur/setup-php@v2 + with: + coverage: "none" + php-version: ${{ matrix.php-version }} + ini-values: memory_limit=-1 + + - name: "Add PHPUnit matcher" + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - run: composer install + + - name: "Install PHPUnit" + run: vendor/bin/simple-phpunit install + + - name: "PHPUnit version" + run: vendor/bin/simple-phpunit --version + + - name: "Run tests" + run: vendor/bin/simple-phpunit + + extension-tests: + needs: + - 'tests' + + name: "${{ matrix.extension }} with PHP ${{ matrix.php-version }}" + + runs-on: 'ubuntu-latest' + + continue-on-error: true + + strategy: + matrix: + php-version: + - '7.2.5' + - '7.3' + - '7.4' + - '8.0' + - '8.1' + extension: + - 'extra/cache-extra' + - 'extra/cssinliner-extra' + - 'extra/html-extra' + - 'extra/inky-extra' + - 'extra/intl-extra' + - 'extra/markdown-extra' + - 'extra/string-extra' + - 'extra/twig-extra-bundle' + experimental: [false] + + steps: + - name: "Checkout code" + uses: actions/checkout@v2 + + - name: "Install PHP with extensions" + uses: shivammathur/setup-php@v2 + with: + coverage: "none" + php-version: ${{ matrix.php-version }} + ini-values: memory_limit=-1 + + - name: "Add PHPUnit matcher" + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - run: composer install + + - name: "Install PHPUnit" + run: vendor/bin/simple-phpunit install + + - name: "PHPUnit version" + run: vendor/bin/simple-phpunit --version + + - name: "Composer install" + working-directory: ${{ matrix.extension}} + run: composer install + + - name: "Run tests" + working-directory: ${{ matrix.extension}} + run: ../../vendor/bin/simple-phpunit + +# +# Drupal does not support Twig 3 now! +# +# integration-tests: +# needs: +# - 'tests' +# +# name: "Integration tests with PHP ${{ matrix.php-version }}" +# +# runs-on: 'ubuntu-20.04' +# +# continue-on-error: true +# +# strategy: +# matrix: +# php-version: +# - '7.3' +# +# steps: +# - name: "Checkout code" +# uses: actions/checkout@v2 +# +# - name: "Install PHP with extensions" +# uses: shivammathur/setup-php@2 +# with: +# coverage: "none" +# extensions: "gd, pdo_sqlite" +# php-version: ${{ matrix.php-version }} +# ini-values: memory_limit=-1 +# tools: composer:v2 +# +# - run: bash ./tests/drupal_test.sh +# shell: "bash" diff --git a/lib/twig/twig/.github/workflows/documentation.yml b/lib/twig/twig/.github/workflows/documentation.yml new file mode 100644 index 0000000000..e6a2270f5d --- /dev/null +++ b/lib/twig/twig/.github/workflows/documentation.yml @@ -0,0 +1,63 @@ +name: "Documentation" + +on: + pull_request: + push: + branches: + - '3.x' + +permissions: + contents: read + +jobs: + build: + name: "Build" + + runs-on: ubuntu-latest + + steps: + - name: "Checkout code" + uses: actions/checkout@v2 + + - name: "Set up Python 3.7" + uses: actions/setup-python@v1 + with: + python-version: '3.7' # Semantic version range syntax or exact version of a Python version + + - name: "Display Python version" + run: python -c "import sys; print(sys.version)" + + - name: "Install Sphinx dependencies" + run: sudo apt-get install python-dev build-essential + + - name: "Cache pip" + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('_build/.requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: "Install Sphinx + requirements via pip" + working-directory: "doc" + run: pip install -r _build/.requirements.txt + + - name: "Build documentation" + working-directory: "doc" + run: make -C _build SPHINXOPTS="-nqW -j auto" html + + doctor-rst: + name: "DOCtor-RST" + + runs-on: ubuntu-latest + + steps: + - name: "Checkout code" + uses: actions/checkout@v2 + + - name: "Run DOCtor-RST" + uses: docker://oskarstark/doctor-rst + with: + args: --short + env: + DOCS_DIR: 'doc/' diff --git a/lib/twig/twig/.gitignore b/lib/twig/twig/.gitignore index 8a29959e2a..cd52aeace6 100644 --- a/lib/twig/twig/.gitignore +++ b/lib/twig/twig/.gitignore @@ -1,6 +1,4 @@ -/build /composer.lock -/ext/twig/autom4te.cache/ /phpunit.xml /vendor .phpunit.result.cache diff --git a/lib/twig/twig/.php-cs-fixer.dist.php b/lib/twig/twig/.php-cs-fixer.dist.php new file mode 100644 index 0000000000..b07ac7fcab --- /dev/null +++ b/lib/twig/twig/.php-cs-fixer.dist.php @@ -0,0 +1,20 @@ +setRules([ + '@Symfony' => true, + '@Symfony:risky' => true, + '@PHPUnit75Migration:risky' => true, + 'php_unit_dedicate_assert' => ['target' => '5.6'], + 'array_syntax' => ['syntax' => 'short'], + 'php_unit_fqcn_annotation' => true, + 'no_unreachable_default_argument_value' => false, + 'braces' => ['allow_single_line_closure' => true], + 'heredoc_to_nowdoc' => false, + 'ordered_imports' => true, + 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], + 'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'all'], + ]) + ->setRiskyAllowed(true) + ->setFinder((new PhpCsFixer\Finder())->in(__DIR__)) +; diff --git a/lib/twig/twig/.php_cs.dist b/lib/twig/twig/.php_cs.dist deleted file mode 100644 index b81882fbf2..0000000000 --- a/lib/twig/twig/.php_cs.dist +++ /dev/null @@ -1,20 +0,0 @@ -setRules([ - '@Symfony' => true, - '@Symfony:risky' => true, - '@PHPUnit75Migration:risky' => true, - 'php_unit_dedicate_assert' => ['target' => '5.6'], - 'array_syntax' => ['syntax' => 'short'], - 'php_unit_fqcn_annotation' => true, - 'no_unreachable_default_argument_value' => false, - 'braces' => ['allow_single_line_closure' => true], - 'heredoc_to_nowdoc' => false, - 'ordered_imports' => true, - 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], - 'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'all'], - ]) - ->setRiskyAllowed(true) - ->setFinder(PhpCsFixer\Finder::create()->in(__DIR__)) -; diff --git a/lib/twig/twig/.travis.yml b/lib/twig/twig/.travis.yml deleted file mode 100644 index ff6132b358..0000000000 --- a/lib/twig/twig/.travis.yml +++ /dev/null @@ -1,46 +0,0 @@ -language: php - -dist: trusty - -sudo: false - -cache: - directories: - - vendor - - $HOME/.composer/cache/files - - -env: - global: - - TWIG_EXT=no - - SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1 - -before_install: - - phpenv config-rm xdebug.ini || return 0 - -install: - - travis_retry composer install - -before_script: - - if [ "$TWIG_EXT" == "yes" ]; then sh -c "cd ext/twig && phpize && ./configure --enable-twig && make && make install"; fi - - if [ "$TWIG_EXT" == "yes" ]; then echo "extension=twig.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`; fi - -script: ./vendor/bin/simple-phpunit - -jobs: - fast_finish: true - include: - - php: 5.5 - - php: 5.5 - env: TWIG_EXT=yes - - php: 5.6 - - php: 5.6 - env: TWIG_EXT=yes - - php: 7.0 - - php: 7.1 - - php: 7.2 - - php: 7.3 - - php: 7.4snapshot - - stage: integration tests - php: 7.3 - script: ./drupal_test.sh diff --git a/lib/twig/twig/CHANGELOG b/lib/twig/twig/CHANGELOG index b9833e51e9..2ee8723d39 100644 --- a/lib/twig/twig/CHANGELOG +++ b/lib/twig/twig/CHANGELOG @@ -1,1134 +1,146 @@ -* 1.42.4 (2019-11-11) +# 3.4.2 (2022-08-12) - * optimized "block('foo') ?? 'bar" - * added supported for exponential numbers + * Allow inherited magic method to still run with calling class + * Fix CallExpression::reflectCallable() throwing TypeError + * Fix typo in naming (currency_code) -* 1.42.3 (2019-08-24) +# 3.4.1 (2022-05-17) - * fixed the "split" filter when the delimiter is "0" - * fixed the "empty" test on Traversable instances - * fixed cache when opcache is installed but disabled - * fixed PHP 7.4 compatibility - * bumped the minimal PHP version to 5.5 +* Fix optimizing non-public named closures -* 1.42.2 (2019-06-18) +# 3.4.0 (2022-05-22) - * Display partial output (PHP buffer) when an error occurs in debug mode + * Add support for named closures -* 1.42.1 (2019-06-04) +# 3.3.10 (2022-04-06) - * added support for "Twig\Markup" instances in the "in" test (again) - * allowed string operators as variables names in assignments + * Enable bytecode invalidation when auto_reload is enabled -* 1.42.0 (2019-05-31) +# 3.3.9 (2022-03-25) - * fixed the "filter" filter when the argument is \Traversable but does not implement \Iterator (\SimpleXmlElement for instance) - * fixed a PHP fatal error when calling a macro imported in a block in a nested block - * fixed a PHP fatal error when calling a macro imported in the template in another macro - * fixed wrong error message on "import" and "from" + * Fix custom escapers when using multiple Twig environments + * Add support for "constant('class', object)" + * Do not reuse internally generated variable names during parsing -* 1.41.0 (2019-05-14) +# 3.3.8 (2022-02-04) - * fixed support for PHP 7.4 - * added "filter", "map", and "reduce" filters (and support for arrow functions) - * fixed partial output leak when a PHP fatal error occurs - * optimized context access on PHP 7.4 + * Fix a security issue when in a sandbox: the `sort` filter must require a Closure for the `arrow` parameter + * Fix deprecation notice on `round` + * Fix call to deprecated `convertToHtml` method -* 1.40.1 (2019-04-29) +# 3.3.7 (2022-01-03) -* fixed regression in NodeTraverser +* Allow more null support when Twig expects a string (for better 8.1 support) +* Only use Commonmark extensions if markdown enabled -* 1.40.0 (2019-04-28) +# 3.3.6 (2022-01-03) - * allowed Twig\NodeVisitor\NodeVisitorInterface::leaveNode() to return "null" instead of "false" (same meaning) - * added the "apply" tag as a replacement for the "filter" tag - * allowed Twig\Loader\FilesystemLoader::findTemplate() to return "null" instead of "false" (same meaning) - * added support for "Twig\Markup" instances in the "in" test - * fixed Lexer when using custom options containing the # char - * fixed "import" when macros are stored in a template string +* Only use Commonmark extensions if markdown enabled -* 1.39.1 (2019-04-16) +# 3.3.5 (2022-01-03) - * fixed EscaperNodeVisitor +* Allow CommonMark extensions to easily be added +* Allow null when Twig expects a string (for better 8.1 support) +* Make some performance optimizations +* Allow Symfony translation contract v3+ -* 1.39.0 (2019-04-16) +# 3.3.4 (2021-11-25) - * added Traversable support for the length filter - * fixed some wrong location in error messages - * made exception creation faster - * made escaping on ternary expressions (?: and ??) more fine-grained - * added the possibility to give a nice name to string templates (template_from_string function) - * fixed the "with" behavior to always include the globals (for consistency with the "include" and "embed" tags) - * fixed "include" with "ignore missing" when an error loading occurs in the included template - * added support for a new whitespace trimming option ({%~ ~%}, {{~ ~}}, {#~ ~#}) + * Bump minimum supported Symfony component versions + * Fix a deprecated message -* 1.38.4 (2019-03-23) +# 3.3.3 (2021-09-17) - * fixed CheckToStringNode implementation (broken when a function/filter is variadic) + * Allow Symfony 6 + * Improve compatibility with PHP 8.1 + * Explicitly specify the encoding for mb_ord in JS escaper -* 1.38.3 (2019-03-21) +# 3.3.2 (2021-05-16) - * fixed the spaceless filter so that it behaves like the spaceless tag - * fixed BC break on Environment::resolveTemplate() - * fixed the bundled Autoloader to also load namespaced classes - * allowed Traversable objects to be used in the "with" tag - * allowed Traversable objects to be used in the "with" argument of the "include" and "embed" tags + * Revert "Throw a proper exception when a template name is an absolute path (as it has never been supported)" -* 1.38.2 (2019-03-12) +# 3.3.1 (2021-05-12) - * added TemplateWrapper::getTemplateName() + * Fix PHP 8.1 compatibility + * Throw a proper exception when a template name is an absolute path (as it has never been supported) -* 1.38.1 (2019-03-12) +# 3.3.0 (2021-02-08) - * fixed class aliases + * Fix macro calls in a "cache" tag + * Add the slug filter + * Allow extra bundle to be compatible with Twig 2 -* 1.38.0 (2019-03-12) +# 3.2.1 (2021-01-05) - * fixed sandbox security issue (under some circumstances, calling the - __toString() method on an object was possible even if not allowed by the - security policy) - * fixed batch filter clobbers array keys when fill parameter is used - * added preserveKeys support for the batch filter - * fixed "embed" support when used from "template_from_string" - * added the possibility to pass a TemplateWrapper to Twig\Environment::load() - * improved the performance of the sandbox - * added a spaceless filter - * added max value to the "random" function - * made namespace classes the default classes (PSR-0 ones are aliases now) - * removed duplicated directory separator in FilesystemLoader - * added Twig\Loader\ChainLoader::getLoaders() - * changed internal code to use the namespaced classes as much as possible + * Fix extra bundle compat with older versions of Symfony -* 1.37.1 (2019-01-14) +# 3.2.0 (2021-01-05) - * fixed regression (key exists check for non ArrayObject objects) - * fixed logic in TemplateWrapper + * Add the Cache extension in the "extra" repositories: "cache" tag + * Add "registerUndefinedTokenParserCallback" + * Mark built-in node visitors as @internal + * Fix "odd" not working for negative numbers -* 1.37.0 (2019-01-14) +# 3.1.1 (2020-10-27) - * fixed ArrayObject access with a null value - * fixed embedded templates starting with a BOM - * fixed using a Twig_TemplateWrapper instance as an argument to extends - * switched generated code to use the PHP short array notation - * dropped PHP 5.3 support - * fixed float representation in compiled templates - * added a second argument to the join filter (last separator configuration) + * Fix "include(template_from_string())" -* 1.36.0 (2018-12-16) +# 3.1.0 (2020-10-21) - * made sure twig_include returns a string - * fixed multi-byte UFT-8 in escape('html_attr') - * added the "deprecated" tag - * added support for dynamically named tests - * fixed GlobalsInterface extended class - * fixed filesystem loader throwing an exception instead of returning false + * Fix sandbox support when using "include(template_from_string())" + * Make round brackets optional for one argument tests like "same as" or "divisible by" + * Add support for ES2015 style object initialisation shortcut { a } is the same as { 'a': a } -* 1.35.4 (2018-07-13) +# 3.0.5 (2020-08-05) - * ensured that syntax errors are triggered with the right line - * added the Symfony ctype polyfill as a dependency - * "js" filter now produces valid JSON + * Fix twig_compare w.r.t. whitespace trimming + * Fix sandbox not disabled if syntax error occurs within {% sandbox %} tag + * Fix a regression when not using a space before an operator + * Restrict callables to closures in filters + * Allow trailing commas in argument lists (in calls as well as definitions) -* 1.35.3 (2018-03-20) +# 3.0.4 (2020-07-05) - * fixed block names unicity - * fixed counting children of SimpleXMLElement objects - * added missing else clause to avoid infinite loops - * fixed .. (range operator) in sandbox policy + * Fix comparison operators + * Fix options not taken into account when using "Michelf\MarkdownExtra" + * Fix "Twig\Extra\Intl\IntlExtension::getCountryName()" to accept "null" as a first argument + * Throw exception in case non-Traversable data is passed to "filter" + * Fix context optimization on PHP 7.4 + * Fix PHP 8 compatibility + * Fix ambiguous syntax parsing -* 1.35.2 (2018-03-03) +# 3.0.3 (2020-02-11) - * fixed a regression in the way the profiler is registered in templates + * Add a check to ensure that iconv() is defined -* 1.35.1 (2018-03-02) +# 3.0.2 (2020-02-11) - * added an exception when using "===" instead of "same as" - * fixed possible array to string conversion concealing actual error - * made variable names deterministic in compiled templates - * fixed length filter when passing an instance of IteratorAggregate - * fixed Environment::resolveTemplate to accept instances of TemplateWrapper + * Avoid exceptions when an intl resource is not found + * Fix implementation of case-insensitivity for method names -* 1.35.0 (2017-09-27) +# 3.0.1 (2019-12-28) - * added Twig_Profiler_Profile::reset() - * fixed use TokenParser to return an empty Node - * added RuntimeExtensionInterface - * added circular reference detection when loading templates + * fixed Symfony 5.0 support for the HTML extra extension -* 1.34.4 (2017-07-04) +# 3.0.0 (2019-11-15) - * added support for runtime loaders in IntegrationTestCase - * fixed deprecation when using Twig_Profiler_Dumper_Html + * fixed number formatter in Intl extra extension when using a formatter prototype -* 1.34.3 (2017-06-07) +# 3.0.0-BETA1 (2019-11-11) - * fixed namespaces introduction - -* 1.34.2 (2017-06-05) - - * fixed namespaces introduction - -* 1.34.1 (2017-06-05) - - * fixed namespaces introduction - -* 1.34.0 (2017-06-05) - - * added support for PHPUnit 6 when testing extensions - * fixed PHP 7.2 compatibility - * fixed template name generation in Twig_Environment::createTemplate() - * removed final tag on Twig_TokenParser_Include - * added namespaced aliases for all (non-deprecated) classes and interfaces - * dropped HHVM support - * dropped PHP 5.2 support - -* 1.33.2 (2017-04-20) - - * fixed edge case in the method cache for Twig attributes - -* 1.33.1 (2017-04-18) - - * fixed the empty() test - -* 1.33.0 (2017-03-22) - - * fixed a race condition handling when writing cache files - * "length" filter now returns string length when applied to an object that does - not implement \Countable but provides __toString() - * "empty" test will now consider the return value of the __toString() method for - objects implement __toString() but not \Countable - * fixed JS escaping for unicode characters with higher code points - -* 1.32.0 (2017-02-26) - - * fixed deprecation notice in Twig_Util_DeprecationCollector - * added a PSR-11 compatible runtime loader - * added `side` argument to `trim` to allow left or right trimming only. - -* 1.31.0 (2017-01-11) - - * added Twig_NodeCaptureInterface for nodes that capture all output - * fixed marking the environment as initialized too early - * fixed C89 compat for the C extension - * turned fatal error into exception when a previously generated cache is corrupted - * fixed offline cache warm-ups for embedded templates - -* 1.30.0 (2016-12-23) - - * added Twig_FactoryRuntimeLoader - * deprecated function/test/filter/tag overriding - * deprecated the "disable_c_ext" attribute on Twig_Node_Expression_GetAttr - -* 1.29.0 (2016-12-13) - - * fixed sandbox being left enabled if an exception is thrown while rendering - * marked some classes as being final (via @final) - * made Twig_Error report real source path when possible - * added support for {{ _self }} to provide an upgrade path from 1.x to 2.0 (replaces {{ _self.templateName }}) - * deprecated silent display of undefined blocks - * deprecated support for mbstring.func_overload != 0 - -* 1.28.2 (2016-11-23) - - * fixed precedence between getFoo() and isFoo() in Twig_Template::getAttribute() - * improved a deprecation message - -* 1.28.1 (2016-11-18) - - * fixed block() function when used with a template argument - -* 1.28.0 (2016-11-17) - - * added support for the PHP 7 null coalescing operator for the ?? Twig implementation - * exposed a way to access template data and methods in a portable way - * changed context access to use the PHP 7 null coalescing operator when available - * added the "with" tag - * added support for a custom template on the block() function - * added "is defined" support for block() and constant() - * optimized the way attributes are fetched - -* 1.27.0 (2016-10-25) - - * deprecated Twig_Parser::getEnvironment() - * deprecated Twig_Parser::addHandler() and Twig_Parser::addNodeVisitor() - * deprecated Twig_Compiler::addIndentation() - * fixed regression when registering two extensions having the same class name - * deprecated Twig_LoaderInterface::getSource() (implement Twig_SourceContextLoaderInterface instead) - * fixed the filesystem loader with relative paths - * deprecated Twig_Node::getLine() in favor of Twig_Node::getTemplateLine() - * deprecated Twig_Template::getSource() in favor of Twig_Template::getSourceContext() - * deprecated Twig_Node::getFilename() in favor of Twig_Node::getTemplateName() - * deprecated the "filename" escaping strategy (use "name" instead) - * added Twig_Source to hold information about the original template - * deprecated Twig_Error::getTemplateFile() and Twig_Error::setTemplateFile() in favor of Twig_Error::getTemplateName() and Twig_Error::setTemplateName() - * deprecated Parser::getFilename() - * fixed template paths when a template name contains a protocol like vfs:// - * improved debugging with Twig_Sandbox_SecurityError exceptions for disallowed methods and properties - -* 1.26.1 (2016-10-05) - - * removed template source code from generated template classes when debug is disabled - * fixed default implementation of Twig_Template::getDebugInfo() for better BC - * fixed regression on static calls for functions/filters/tests - -* 1.26.0 (2016-10-02) - - * added template cache invalidation based on more environment options - * added a missing deprecation notice - * fixed template paths when a template is stored in a PHAR file - * allowed filters/functions/tests implementation to use a different class than the extension they belong to - * deprecated Twig_ExtensionInterface::getName() - -* 1.25.0 (2016-09-21) - - * changed the way we store template source in template classes - * removed usage of realpath in cache keys - * fixed Twig cache sharing when used with different versions of PHP - * removed embed parent workaround for simple use cases - * deprecated the ability to store non Node instances in Node::$nodes - * deprecated Twig_Environment::getLexer(), Twig_Environment::getParser(), Twig_Environment::getCompiler() - * deprecated Twig_Compiler::getFilename() - -* 1.24.2 (2016-09-01) - - * fixed static callables - * fixed a potential PHP warning when loading the cache - * fixed a case where the autoescaping does not work as expected - -* 1.24.1 (2016-05-30) - - * fixed reserved keywords (forbids true, false, null and none keywords for variables names) - * fixed support for PHP7 (Throwable support) - * marked the following methods as being internals on Twig_Environment: - getFunctions(), getFilters(), getTests(), getFunction(), getFilter(), getTest(), - getTokenParsers(), getTags(), getNodeVisitors(), getUnaryOperators(), getBinaryOperators(), - getFunctions(), getFilters(), getGlobals(), initGlobals(), initExtensions(), and initExtension() - -* 1.24.0 (2016-01-25) - - * adding support for the ?? operator - * fixed the defined test when used on a constant, a map, or a sequence - * undeprecated _self (should only be used to get the template name, not the template instance) - * fixed parsing on PHP7 - -* 1.23.3 (2016-01-11) - - * fixed typo - -* 1.23.2 (2015-01-11) - - * added versions in deprecated messages - * made file cache tolerant for trailing (back)slashes on directory configuration - * deprecated unused Twig_Node_Expression_ExtensionReference class - -* 1.23.1 (2015-11-05) - - * fixed some exception messages which triggered PHP warnings - * fixed BC on Twig_Test_NodeTestCase - -* 1.23.0 (2015-10-29) - - * deprecated the possibility to override an extension by registering another one with the same name - * deprecated Twig_ExtensionInterface::getGlobals() (added Twig_Extension_GlobalsInterface for BC) - * deprecated Twig_ExtensionInterface::initRuntime() (added Twig_Extension_InitRuntimeInterface for BC) - * deprecated Twig_Environment::computeAlternatives() - -* 1.22.3 (2015-10-13) - - * fixed regression when using null as a cache strategy - * improved performance when checking template freshness - * fixed warnings when loaded templates do not exist - * fixed template class name generation to prevent possible collisions - * fixed logic for custom escapers to call them even on integers and null values - * changed template cache names to take into account the Twig C extension - -* 1.22.2 (2015-09-22) - - * fixed a race condition in template loading - -* 1.22.1 (2015-09-15) - - * fixed regression in template_from_string - -* 1.22.0 (2015-09-13) - - * made Twig_Test_IntegrationTestCase more flexible - * added an option to force PHP bytecode invalidation when writing a compiled template into the cache - * fixed the profiler duration for the root node - * changed template cache names to take into account enabled extensions - * deprecated Twig_Environment::clearCacheFiles(), Twig_Environment::getCacheFilename(), - Twig_Environment::writeCacheFile(), and Twig_Environment::getTemplateClassPrefix() - * added a way to override the filesystem template cache system - * added a way to get the original template source from Twig_Template - -* 1.21.2 (2015-09-09) - - * fixed variable names for the deprecation triggering code - * fixed escaping strategy detection based on filename - * added Traversable support for replace, merge, and sort - * deprecated support for character by character replacement for the "replace" filter - -* 1.21.1 (2015-08-26) - - * fixed regression when using the deprecated Twig_Test_* classes - -* 1.21.0 (2015-08-24) - - * added deprecation notices for deprecated features - * added a deprecation "framework" for filters/functions/tests and test fixtures - -* 1.20.0 (2015-08-12) - - * forbid access to the Twig environment from templates and internal parts of Twig_Template - * fixed limited RCEs when in sandbox mode - * deprecated Twig_Template::getEnvironment() - * deprecated the _self variable for usage outside of the from and import tags - * added Twig_BaseNodeVisitor to ease the compatibility of node visitors - between 1.x and 2.x - -* 1.19.0 (2015-07-31) - - * fixed wrong error message when including an undefined template in a child template - * added support for variadic filters, functions, and tests - * added support for extra positional arguments in macros - * added ignore_missing flag to the source function - * fixed batch filter with zero items - * deprecated Twig_Environment::clearTemplateCache() - * fixed sandbox disabling when using the include function - -* 1.18.2 (2015-06-06) - - * fixed template/line guessing in exceptions for nested templates - * optimized the number of inodes and the size of realpath cache when using the cache - -* 1.18.1 (2015-04-19) - - * fixed memory leaks in the C extension - * deprecated Twig_Loader_String - * fixed the slice filter when used with a SimpleXMLElement object - * fixed filesystem loader when trying to load non-files (like directories) - -* 1.18.0 (2015-01-25) - - * fixed some error messages where the line was wrong (unknown variables or argument names) - * added a new way to customize the main Module node (via empty nodes) - * added Twig_Environment::createTemplate() to create a template from a string - * added a profiler - * fixed filesystem loader cache when different file paths are used for the same template - -* 1.17.0 (2015-01-14) - - * added a 'filename' autoescaping strategy, which dynamically chooses the - autoescaping strategy for a template based on template file extension. - -* 1.16.3 (2014-12-25) - - * fixed regression for dynamic parent templates - * fixed cache management with statcache - * fixed a regression in the slice filter - -* 1.16.2 (2014-10-17) - - * fixed timezone on dates as strings - * fixed 2-words test names when a custom node class is not used - * fixed macros when using an argument named like a PHP super global (like GET or POST) - * fixed date_modify when working with DateTimeImmutable - * optimized for loops - * fixed multi-byte characters handling in the split filter - * fixed a regression in the in operator - * fixed a regression in the slice filter - -* 1.16.1 (2014-10-10) - - * improved error reporting in a sandboxed template - * fixed missing error file/line information under certain circumstances - * fixed wrong error line number in some error messages - * fixed the in operator to use strict comparisons - * sped up the slice filter - * fixed for mb function overload mb_substr acting different - * fixed the attribute() function when passing a variable for the arguments - -* 1.16.0 (2014-07-05) - - * changed url_encode to always encode according to RFC 3986 - * fixed inheritance in a 'use'-hierarchy - * removed the __toString policy check when the sandbox is disabled - * fixed recursively calling blocks in templates with inheritance - -* 1.15.1 (2014-02-13) - - * fixed the conversion of the special '0000-00-00 00:00' date - * added an error message when trying to import an undefined block from a trait - * fixed a C extension crash when accessing defined but uninitialized property. - -* 1.15.0 (2013-12-06) - - * made ignoreStrictCheck in Template::getAttribute() works with __call() methods throwing BadMethodCallException - * added min and max functions - * added the round filter - * fixed a bug that prevented the optimizers to be enabled/disabled selectively - * fixed first and last filters for UTF-8 strings - * added a source function to include the content of a template without rendering it - * fixed the C extension sandbox behavior when get or set is prepend to method name - -* 1.14.2 (2013-10-30) - - * fixed error filename/line when an error occurs in an included file - * allowed operators that contain whitespaces to have more than one whitespace - * allowed tests to be made of 1 or 2 words (like "same as" or "divisible by") - -* 1.14.1 (2013-10-15) - - * made it possible to use named operators as variables - * fixed the possibility to have a variable named 'matches' - * added support for PHP 5.5 DateTimeInterface - -* 1.14.0 (2013-10-03) - - * fixed usage of the html_attr escaping strategy to avoid double-escaping with the html strategy - * added new operators: ends with, starts with, and matches - * fixed some compatibility issues with HHVM - * added a way to add custom escaping strategies - * fixed the C extension compilation on Windows - * fixed the batch filter when using a fill argument with an exact match of elements to batch - * fixed the filesystem loader cache when a template name exists in several namespaces - * fixed template_from_string when the template includes or extends other ones - * fixed a crash of the C extension on an edge case - -* 1.13.2 (2013-08-03) - - * fixed the error line number for an error occurs in and embedded template - * fixed crashes of the C extension on some edge cases - -* 1.13.1 (2013-06-06) - - * added the possibility to ignore the filesystem constructor argument in Twig_Loader_Filesystem - * fixed Twig_Loader_Chain::exists() for a loader which implements Twig_ExistsLoaderInterface - * adjusted backtrace call to reduce memory usage when an error occurs - * added support for object instances as the second argument of the constant test - * fixed the include function when used in an assignment - -* 1.13.0 (2013-05-10) - - * fixed getting a numeric-like item on a variable ('09' for instance) - * fixed getting a boolean or float key on an array, so it is consistent with PHP's array access: - `{{ array[false] }}` behaves the same as `echo $array[false];` (equals `$array[0]`) - * made the escape filter 20% faster for happy path (escaping string for html with UTF-8) - * changed ☃ to § in tests - * enforced usage of named arguments after positional ones - -* 1.12.3 (2013-04-08) - - * fixed a security issue in the filesystem loader where it was possible to include a template one - level above the configured path - * fixed fatal error that should be an exception when adding a filter/function/test too late - * added a batch filter - * added support for encoding an array as query string in the url_encode filter - -* 1.12.2 (2013-02-09) - - * fixed the timezone used by the date filter and function when the given date contains a timezone (like 2010-01-28T15:00:00+02:00) - * fixed globals when getGlobals is called early on - * added the first and last filter - -* 1.12.1 (2013-01-15) - - * added support for object instances as the second argument of the constant function - * relaxed globals management to avoid a BC break - * added support for {{ some_string[:2] }} - -* 1.12.0 (2013-01-08) - - * added verbatim as an alias for the raw tag to avoid confusion with the raw filter - * fixed registration of tests and functions as anonymous functions - * fixed globals management - -* 1.12.0-RC1 (2012-12-29) - - * added an include function (does the same as the include tag but in a more flexible way) - * added the ability to use any PHP callable to define filters, functions, and tests - * added a syntax error when using a loop variable that is not defined - * added the ability to set default values for macro arguments - * added support for named arguments for filters, tests, and functions - * moved filters/functions/tests syntax errors to the parser - * added support for extended ternary operator syntaxes - -* 1.11.1 (2012-11-11) - - * fixed debug info line numbering (was off by 2) - * fixed escaping when calling a macro inside another one (regression introduced in 1.9.1) - * optimized variable access on PHP 5.4 - * fixed a crash of the C extension when an exception was thrown from a macro called without being imported (using _self.XXX) - -* 1.11.0 (2012-11-07) - - * fixed macro compilation when a variable name is a PHP reserved keyword - * changed the date filter behavior to always apply the default timezone, except if false is passed as the timezone - * fixed bitwise operator precedences - * added the template_from_string function - * fixed default timezone usage for the date function - * optimized the way Twig exceptions are managed (to make them faster) - * added Twig_ExistsLoaderInterface (implementing this interface in your loader make the chain loader much faster) - -* 1.10.3 (2012-10-19) - - * fixed wrong template location in some error messages - * reverted a BC break introduced in 1.10.2 - * added a split filter - -* 1.10.2 (2012-10-15) - - * fixed macro calls on PHP 5.4 - -* 1.10.1 (2012-10-15) - - * made a speed optimization to macro calls when imported via the "import" tag - * fixed C extension compilation on Windows - * fixed a segfault in the C extension when using DateTime objects - -* 1.10.0 (2012-09-28) - - * extracted functional tests framework to make it reusable for third-party extensions - * added namespaced templates support in Twig_Loader_Filesystem - * added Twig_Loader_Filesystem::prependPath() - * fixed an error when a token parser pass a closure as a test to the subparse() method - -* 1.9.2 (2012-08-25) - - * fixed the in operator for objects that contain circular references - * fixed the C extension when accessing a public property of an object implementing the \ArrayAccess interface - -* 1.9.1 (2012-07-22) - - * optimized macro calls when auto-escaping is on - * fixed wrong parent class for Twig_Function_Node - * made Twig_Loader_Chain more explicit about problems - -* 1.9.0 (2012-07-13) - - * made the parsing independent of the template loaders - * fixed exception trace when an error occurs when rendering a child template - * added escaping strategies for CSS, URL, and HTML attributes - * fixed nested embed tag calls - * added the date_modify filter - -* 1.8.3 (2012-06-17) - - * fixed paths in the filesystem loader when passing a path that ends with a slash or a backslash - * fixed escaping when a project defines a function named html or js - * fixed chmod mode to apply the umask correctly - -* 1.8.2 (2012-05-30) - - * added the abs filter - * fixed a regression when using a number in template attributes - * fixed compiler when mbstring.func_overload is set to 2 - * fixed DateTimeZone support in date filter - -* 1.8.1 (2012-05-17) - - * fixed a regression when dealing with SimpleXMLElement instances in templates - * fixed "is_safe" value for the "dump" function when "html_errors" is not defined in php.ini - * switched to use mbstring whenever possible instead of iconv (you might need to update your encoding as mbstring and iconv encoding names sometimes differ) - -* 1.8.0 (2012-05-08) - - * enforced interface when adding tests, filters, functions, and node visitors from extensions - * fixed a side-effect of the date filter where the timezone might be changed - * simplified usage of the autoescape tag; the only (optional) argument is now the escaping strategy or false (with a BC layer) - * added a way to dynamically change the auto-escaping strategy according to the template "filename" - * changed the autoescape option to also accept a supported escaping strategy (for BC, true is equivalent to html) - * added an embed tag - -* 1.7.0 (2012-04-24) - - * fixed a PHP warning when using CIFS - * fixed template line number in some exceptions - * added an iterable test - * added an error when defining two blocks with the same name in a template - * added the preserves_safety option for filters - * fixed a PHP notice when trying to access a key on a non-object/array variable - * enhanced error reporting when the template file is an instance of SplFileInfo - * added Twig_Environment::mergeGlobals() - * added compilation checks to avoid misuses of the sandbox tag - * fixed filesystem loader freshness logic for high traffic websites - * fixed random function when charset is null - -* 1.6.5 (2012-04-11) - - * fixed a regression when a template only extends another one without defining any blocks - -* 1.6.4 (2012-04-02) - - * fixed PHP notice in Twig_Error::guessTemplateLine() introduced in 1.6.3 - * fixed performance when compiling large files - * optimized parent template creation when the template does not use dynamic inheritance - -* 1.6.3 (2012-03-22) - - * fixed usage of Z_ADDREF_P for PHP 5.2 in the C extension - * fixed compilation of numeric values used in templates when using a locale where the decimal separator is not a dot - * made the strategy used to guess the real template file name and line number in exception messages much faster and more accurate - -* 1.6.2 (2012-03-18) - - * fixed sandbox mode when used with inheritance - * added preserveKeys support for the slice filter - * fixed the date filter when a DateTime instance is passed with a specific timezone - * added a trim filter - -* 1.6.1 (2012-02-29) - - * fixed Twig C extension - * removed the creation of Twig_Markup instances when not needed - * added a way to set the default global timezone for dates - * fixed the slice filter on strings when the length is not specified - * fixed the creation of the cache directory in case of a race condition - -* 1.6.0 (2012-02-04) - - * fixed raw blocks when used with the whitespace trim option - * made a speed optimization to macro calls when imported via the "from" tag - * fixed globals, parsers, visitors, filters, tests, and functions management in Twig_Environment when a new one or new extension is added - * fixed the attribute function when passing arguments - * added slice notation support for the [] operator (syntactic sugar for the slice operator) - * added a slice filter - * added string support for the reverse filter - * fixed the empty test and the length filter for Twig_Markup instances - * added a date function to ease date comparison - * fixed unary operators precedence - * added recursive parsing support in the parser - * added string and integer handling for the random function - -* 1.5.1 (2012-01-05) - - * fixed a regression when parsing strings - -* 1.5.0 (2012-01-04) - - * added Traversable objects support for the join filter - -* 1.5.0-RC2 (2011-12-30) - - * added a way to set the default global date interval format - * fixed the date filter for DateInterval instances (setTimezone() does not exist for them) - * refactored Twig_Template::display() to ease its extension - * added a number_format filter - -* 1.5.0-RC1 (2011-12-26) - - * removed the need to quote hash keys - * allowed hash keys to be any expression - * added a do tag - * added a flush tag - * added support for dynamically named filters and functions - * added a dump function to help debugging templates - * added a nl2br filter - * added a random function - * added a way to change the default format for the date filter - * fixed the lexer when an operator ending with a letter ends a line - * added string interpolation support - * enhanced exceptions for unknown filters, functions, tests, and tags - -* 1.4.0 (2011-12-07) - - * fixed lexer when using big numbers (> PHP_INT_MAX) - * added missing preserveKeys argument to the reverse filter - * fixed macros containing filter tag calls - -* 1.4.0-RC2 (2011-11-27) - - * removed usage of Reflection in Twig_Template::getAttribute() - * added a C extension that can optionally replace Twig_Template::getAttribute() - * added negative timestamp support to the date filter - -* 1.4.0-RC1 (2011-11-20) - - * optimized variable access when using PHP 5.4 - * changed the precedence of the .. operator to be more consistent with languages that implements such a feature like Ruby - * added an Exception to Twig_Loader_Array::isFresh() method when the template does not exist to be consistent with other loaders - * added Twig_Function_Node to allow more complex functions to have their own Node class - * added Twig_Filter_Node to allow more complex filters to have their own Node class - * added Twig_Test_Node to allow more complex tests to have their own Node class - * added a better error message when a template is empty but contain a BOM - * fixed "in" operator for empty strings - * fixed the "defined" test and the "default" filter (now works with more than one call (foo.bar.foo) and for both values of the strict_variables option) - * changed the way extensions are loaded (addFilter/addFunction/addGlobal/addTest/addNodeVisitor/addTokenParser/addExtension can now be called in any order) - * added Twig_Environment::display() - * made the escape filter smarter when the encoding is not supported by PHP - * added a convert_encoding filter - * moved all node manipulations outside the compile() Node method - * made several speed optimizations - -* 1.3.0 (2011-10-08) - -no changes - -* 1.3.0-RC1 (2011-10-04) - - * added an optimization for the parent() function - * added cache reloading when auto_reload is true and an extension has been modified - * added the possibility to force the escaping of a string already marked as safe (instance of Twig_Markup) - * allowed empty templates to be used as traits - * added traits support for the "parent" function - -* 1.2.0 (2011-09-13) - -no changes - -* 1.2.0-RC1 (2011-09-10) - - * enhanced the exception when a tag remains unclosed - * added support for empty Countable objects for the "empty" test - * fixed algorithm that determines if a template using inheritance is valid (no output between block definitions) - * added better support for encoding problems when escaping a string (available as of PHP 5.4) - * added a way to ignore a missing template when using the "include" tag ({% include "foo" ignore missing %}) - * added support for an array of templates to the "include" and "extends" tags ({% include ['foo', 'bar'] %}) - * added support for bitwise operators in expressions - * added the "attribute" function to allow getting dynamic attributes on variables - * added Twig_Loader_Chain - * added Twig_Loader_Array::setTemplate() - * added an optimization for the set tag when used to capture a large chunk of static text - * changed name regex to match PHP one "[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*" (works for blocks, tags, functions, filters, and macros) - * removed the possibility to use the "extends" tag from a block - * added "if" modifier support to "for" loops - -* 1.1.2 (2011-07-30) - - * fixed json_encode filter on PHP 5.2 - * fixed regression introduced in 1.1.1 ({{ block(foo|lower) }}) - * fixed inheritance when using conditional parents - * fixed compilation of templates when the body of a child template is not empty - * fixed output when a macro throws an exception - * fixed a parsing problem when a large chunk of text is enclosed in a comment tag - * added PHPDoc for all Token parsers and Core extension functions - -* 1.1.1 (2011-07-17) - - * added a performance optimization in the Optimizer (also helps to lower the number of nested level calls) - * made some performance improvement for some edge cases - -* 1.1.0 (2011-06-28) - - * fixed json_encode filter - -* 1.1.0-RC3 (2011-06-24) - - * fixed method case-sensitivity when using the sandbox mode - * added timezone support for the date filter - * fixed possible security problems with NUL bytes - -* 1.1.0-RC2 (2011-06-16) - - * added an exception when the template passed to "use" is not a string - * made 'a.b is defined' not throw an exception if a is not defined (in strict mode) - * added {% line \d+ %} directive - -* 1.1.0-RC1 (2011-05-28) - -Flush your cache after upgrading. - - * fixed date filter when using a timestamp - * fixed the defined test for some cases - * fixed a parsing problem when a large chunk of text is enclosed in a raw tag - * added support for horizontal reuse of template blocks (see docs for more information) - * added whitespace control modifier to all tags (see docs for more information) - * added null as an alias for none (the null test is also an alias for the none test now) - * made TRUE, FALSE, NONE equivalent to their lowercase counterparts - * wrapped all compilation and runtime exceptions with Twig_Error_Runtime and added logic to guess the template name and line - * moved display() method to Twig_Template (generated templates should now use doDisplay() instead) - -* 1.0.0 (2011-03-27) - - * fixed output when using mbstring - * fixed duplicate call of methods when using the sandbox - * made the charset configurable for the escape filter - -* 1.0.0-RC2 (2011-02-21) - - * changed the way {% set %} works when capturing (the content is now marked as safe) - * added support for macro name in the endmacro tag - * make Twig_Error compatible with PHP 5.3.0 > - * fixed an infinite loop on some Windows configurations - * fixed the "length" filter for numbers - * fixed Template::getAttribute() as properties in PHP are case sensitive - * removed coupling between Twig_Node and Twig_Template - * fixed the ternary operator precedence rule - -* 1.0.0-RC1 (2011-01-09) - -Backward incompatibilities: - - * the "items" filter, which has been deprecated for quite a long time now, has been removed - * the "range" filter has been converted to a function: 0|range(10) -> range(0, 10) - * the "constant" filter has been converted to a function: {{ some_date|date('DATE_W3C'|constant) }} -> {{ some_date|date(constant('DATE_W3C')) }} - * the "cycle" filter has been converted to a function: {{ ['odd', 'even']|cycle(i) }} -> {{ cycle(['odd', 'even'], i) }} - * the "for" tag does not support "joined by" anymore - * the "autoescape" first argument is now "true"/"false" (instead of "on"/"off") - * the "parent" tag has been replaced by a "parent" function ({{ parent() }} instead of {% parent %}) - * the "display" tag has been replaced by a "block" function ({{ block('title') }} instead of {% display title %}) - * removed the grammar and simple token parser (moved to the Twig Extensions repository) - -Changes: - - * added "needs_context" option for filters and functions (the context is then passed as a first argument) - * added global variables support - * made macros return their value instead of echoing directly (fixes calling a macro in sandbox mode) - * added the "from" tag to import macros as functions - * added support for functions (a function is just syntactic sugar for a getAttribute() call) - * made macros callable when sandbox mode is enabled - * added an exception when a macro uses a reserved name - * the "default" filter now uses the "empty" test instead of just checking for null - * added the "empty" test - -* 0.9.10 (2010-12-16) - -Backward incompatibilities: - - * The Escaper extension is enabled by default, which means that all displayed - variables are now automatically escaped. You can revert to the previous - behavior by removing the extension via $env->removeExtension('escaper') - or just set the 'autoescape' option to 'false'. - * removed the "without loop" attribute for the "for" tag (not needed anymore - as the Optimizer take care of that for most cases) - * arrays and hashes have now a different syntax - * arrays keep the same syntax with square brackets: [1, 2] - * hashes now use curly braces (["a": "b"] should now be written as {"a": "b"}) - * support for "arrays with keys" and "hashes without keys" is not supported anymore ([1, "foo": "bar"] or {"foo": "bar", 1}) - * the i18n extension is now part of the Twig Extensions repository - -Changes: - - * added the merge filter - * removed 'is_escaper' option for filters (a left over from the previous version) -- you must use 'is_safe' now instead - * fixed usage of operators as method names (like is, in, and not) - * changed the order of execution for node visitors - * fixed default() filter behavior when used with strict_variables set to on - * fixed filesystem loader compatibility with PHAR files - * enhanced error messages when an unexpected token is parsed in an expression - * fixed filename not being added to syntax error messages - * added the autoescape option to enable/disable autoescaping - * removed the newline after a comment (mimics PHP behavior) - * added a syntax error exception when parent block is used on a template that does not extend another one - * made the Escaper extension enabled by default - * fixed sandbox extension when used with auto output escaping - * fixed escaper when wrapping a Twig_Node_Print (the original class must be preserved) - * added an Optimizer extension (enabled by default; optimizes "for" loops and "raw" filters) - * added priority to node visitors - -* 0.9.9 (2010-11-28) - -Backward incompatibilities: - * the self special variable has been renamed to _self - * the odd and even filters are now tests: - {{ foo|odd }} must now be written {{ foo is odd }} - * the "safe" filter has been renamed to "raw" - * in Node classes, - sub-nodes are now accessed via getNode() (instead of property access) - attributes via getAttribute() (instead of array access) - * the urlencode filter had been renamed to url_encode - * the include tag now merges the passed variables with the current context by default - (the old behavior is still possible by adding the "only" keyword) - * moved Exceptions to Twig_Error_* (Twig_SyntaxError/Twig_RuntimeError are now Twig_Error_Syntax/Twig_Error_Runtime) - * removed support for {{ 1 < i < 3 }} (use {{ i > 1 and i < 3 }} instead) - * the "in" filter has been removed ({{ a|in(b) }} should now be written {{ a in b }}) - -Changes: - * added file and line to Twig_Error_Runtime exceptions thrown from Twig_Template - * changed trans tag to accept any variable for the plural count - * fixed sandbox mode (__toString() method check was not enforced if called implicitly from complex statements) - * added the ** (power) operator - * changed the algorithm used for parsing expressions - * added the spaceless tag - * removed trim_blocks option - * added support for is*() methods for attributes (foo.bar now looks for foo->getBar() or foo->isBar()) - * changed all exceptions to extend Twig_Error - * fixed unary expressions ({{ not(1 or 0) }}) - * fixed child templates (with an extend tag) that uses one or more imports - * added support for {{ 1 not in [2, 3] }} (more readable than the current {{ not (1 in [2, 3]) }}) - * escaping has been rewritten - * the implementation of template inheritance has been rewritten - (blocks can now be called individually and still work with inheritance) - * fixed error handling for if tag when a syntax error occurs within a subparse process - * added a way to implement custom logic for resolving token parsers given a tag name - * fixed js escaper to be stricter (now uses a whilelist-based js escaper) - * added the following filers: "constant", "trans", "replace", "json_encode" - * added a "constant" test - * fixed objects with __toString() not being autoescaped - * fixed subscript expressions when calling __call() (methods now keep the case) - * added "test" feature (accessible via the "is" operator) - * removed the debug tag (should be done in an extension) - * fixed trans tag when no vars are used in plural form - * fixed race condition when writing template cache - * added the special _charset variable to reference the current charset - * added the special _context variable to reference the current context - * renamed self to _self (to avoid conflict) - * fixed Twig_Template::getAttribute() for protected properties - -* 0.9.8 (2010-06-28) - -Backward incompatibilities: - * the trans tag plural count is now attached to the plural tag: - old: `{% trans count %}...{% plural %}...{% endtrans %}` - new: `{% trans %}...{% plural count %}...{% endtrans %}` - - * added a way to translate strings coming from a variable ({% trans var %}) - * fixed trans tag when used with the Escaper extension - * fixed default cache umask - * removed Twig_Template instances from the debug tag output - * fixed objects with __isset() defined - * fixed set tag when used with a capture - * fixed type hinting for Twig_Environment::addFilter() method - -* 0.9.7 (2010-06-12) - -Backward incompatibilities: - * changed 'as' to '=' for the set tag ({% set title as "Title" %} must now be {% set title = "Title" %}) - * removed the sandboxed attribute of the include tag (use the new sandbox tag instead) - * refactored the Node system (if you have custom nodes, you will have to update them to use the new API) - - * added self as a special variable that refers to the current template (useful for importing macros from the current template) - * added Twig_Template instance support to the include tag - * added support for dynamic and conditional inheritance ({% extends some_var %} and {% extends standalone ? "minimum" : "base" %}) - * added a grammar sub-framework to ease the creation of custom tags - * fixed the for tag for large arrays (some loop variables are now only available for arrays and objects that implement the Countable interface) - * removed the Twig_Resource::resolveMissingFilter() method - * fixed the filter tag which did not apply filtering to included files - * added a bunch of unit tests - * added a bunch of phpdoc - * added a sandbox tag in the sandbox extension - * changed the date filter to support any date format supported by DateTime - * added strict_variable setting to throw an exception when an invalid variable is used in a template (disabled by default) - * added the lexer, parser, and compiler as arguments to the Twig_Environment constructor - * changed the cache option to only accepts an explicit path to a cache directory or false - * added a way to add token parsers, filters, and visitors without creating an extension - * added three interfaces: Twig_NodeInterface, Twig_TokenParserInterface, and Twig_FilterInterface - * changed the generated code to match the new coding standards - * fixed sandbox mode (__toString() method check was not enforced if called implicitly from a simple statement like {{ article }}) - * added an exception when a child template has a non-empty body (as it is always ignored when rendering) - -* 0.9.6 (2010-05-12) - - * fixed variables defined outside a loop and for which the value changes in a for loop - * fixed the test suite for PHP 5.2 and older versions of PHPUnit - * added support for __call() in expression resolution - * fixed node visiting for macros (macros are now visited by visitors as any other node) - * fixed nested block definitions with a parent call (rarely useful but nonetheless supported now) - * added the cycle filter - * fixed the Lexer when mbstring.func_overload is used with an mbstring.internal_encoding different from ASCII - * added a long-syntax for the set tag ({% set foo %}...{% endset %}) - * unit tests are now powered by PHPUnit - * added support for gettext via the `i18n` extension - * fixed twig_capitalize_string_filter() and fixed twig_length_filter() when used with UTF-8 values - * added a more useful exception if an if tag is not closed properly - * added support for escaping strategy in the autoescape tag - * fixed lexer when a template has a big chunk of text between/in a block - -* 0.9.5 (2010-01-20) - -As for any new release, don't forget to remove all cached templates after -upgrading. - -If you have defined custom filters, you MUST upgrade them for this release. To -upgrade, replace "array" with "new Twig_Filter_Function", and replace the -environment constant by the "needs_environment" option: - - // before - 'even' => array('twig_is_even_filter', false), - 'escape' => array('twig_escape_filter', true), - - // after - 'even' => new Twig_Filter_Function('twig_is_even_filter'), - 'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true)), - -If you have created NodeTransformer classes, you will need to upgrade them to -the new interface (please note that the interface is not yet considered -stable). - - * fixed list nodes that did not extend the Twig_NodeListInterface - * added the "without loop" option to the for tag (it disables the generation of the loop variable) - * refactored node transformers to node visitors - * fixed automatic-escaping for blocks - * added a way to specify variables to pass to an included template - * changed the automatic-escaping rules to be more sensible and more configurable in custom filters (the documentation lists all the rules) - * improved the filter system to allow object methods to be used as filters - * changed the Array and String loaders to actually make use of the cache mechanism - * included the default filter function definitions in the extension class files directly (Core, Escaper) - * added the // operator (like the floor() PHP function) - * added the .. operator (as a syntactic sugar for the range filter when the step is 1) - * added the in operator (as a syntactic sugar for the in filter) - * added the following filters in the Core extension: in, range - * added support for arrays (same behavior as in PHP, a mix between lists and dictionaries, arrays and hashes) - * enhanced some error messages to provide better feedback in case of parsing errors - -* 0.9.4 (2009-12-02) - -If you have custom loaders, you MUST upgrade them for this release: The -Twig_Loader base class has been removed, and the Twig_LoaderInterface has also -been changed (see the source code for more information or the documentation). - - * added support for DateTime instances for the date filter - * fixed loop.last when the array only has one item - * made it possible to insert newlines in tag and variable blocks - * fixed a bug when a literal '\n' were present in a template text - * fixed bug when the filename of a template contains */ - * refactored loaders - -* 0.9.3 (2009-11-11) - -This release is NOT backward compatible with the previous releases. - - The loaders do not take the cache and autoReload arguments anymore. Instead, - the Twig_Environment class has two new options: cache and auto_reload. - Upgrading your code means changing this kind of code: - - $loader = new Twig_Loader_Filesystem('/path/to/templates', '/path/to/compilation_cache', true); - $twig = new Twig_Environment($loader); - - to something like this: - - $loader = new Twig_Loader_Filesystem('/path/to/templates'); - $twig = new Twig_Environment($loader, array( - 'cache' => '/path/to/compilation_cache', - 'auto_reload' => true, - )); - - * deprecated the "items" filter as it is not needed anymore - * made cache and auto_reload options of Twig_Environment instead of arguments of Twig_Loader - * optimized template loading speed - * removed output when an error occurs in a template and render() is used - * made major speed improvements for loops (up to 300% on even the smallest loops) - * added properties as part of the sandbox mode - * added public properties support (obj.item can now be the item property on the obj object) - * extended set tag to support expression as value ({% set foo as 'foo' ~ 'bar' %} ) - * fixed bug when \ was used in HTML - -* 0.9.2 (2009-10-29) - - * made some speed optimizations - * changed the cache extension to .php - * added a js escaping strategy - * added support for short block tag - * changed the filter tag to allow chained filters - * made lexer more flexible as you can now change the default delimiters - * added set tag - * changed default directory permission when cache dir does not exist (more secure) - * added macro support - * changed filters first optional argument to be a Twig_Environment instance instead of a Twig_Template instance - * made Twig_Autoloader::autoload() a static method - * avoid writing template file if an error occurs - * added $ escaping when outputting raw strings - * enhanced some error messages to ease debugging - * fixed empty cache files when the template contains an error - -* 0.9.1 (2009-10-14) - - * fixed a bug in PHP 5.2.6 - * fixed numbers with one than one decimal - * added support for method calls with arguments ({{ foo.bar('a', 43) }}) - * made small speed optimizations - * made minor tweaks to allow better extensibility and flexibility - -* 0.9.0 (2009-10-12) - - * Initial release + * removed the "if" condition support on the "for" tag + * made the in, <, >, <=, >=, ==, and != operators more strict when comparing strings and integers/floats + * removed the "filter" tag + * added type hints everywhere + * changed Environment::resolveTemplate() to always return a TemplateWrapper instance + * removed Template::__toString() + * removed Parser::isReservedMacroName() + * removed SanboxedPrintNode + * removed Node::setTemplateName() + * made classes maked as "@final" final + * removed InitRuntimeInterface, ExistsLoaderInterface, and SourceContextLoaderInterface + * removed the "spaceless" tag + * removed Twig\Environment::getBaseTemplateClass() and Twig\Environment::setBaseTemplateClass() + * removed the "base_template_class" option on Twig\Environment + * bumped minimum PHP version to 7.2 + * removed PSR-0 classes diff --git a/lib/twig/twig/LICENSE b/lib/twig/twig/LICENSE index d06ced2a39..8711927f6d 100644 --- a/lib/twig/twig/LICENSE +++ b/lib/twig/twig/LICENSE @@ -1,31 +1,27 @@ -Copyright (c) 2009-2019 by the Twig Team. +Copyright (c) 2009-2022 by the Twig Team. -Some rights reserved. +All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: +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. + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of Twig nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/twig/twig/README.rst b/lib/twig/twig/README.rst index d896ff500a..fbe7e9a9f8 100644 --- a/lib/twig/twig/README.rst +++ b/lib/twig/twig/README.rst @@ -1,8 +1,7 @@ Twig, the flexible, fast, and secure template language for PHP ============================================================== -Twig is a template language for PHP, released under the new BSD license (code -and documentation). +Twig is a template language for PHP. Twig uses a syntax similar to the Django and Jinja template languages which inspired the Twig runtime environment. diff --git a/lib/twig/twig/composer.json b/lib/twig/twig/composer.json index 9807aa7711..33e46405c9 100644 --- a/lib/twig/twig/composer.json +++ b/lib/twig/twig/composer.json @@ -5,6 +5,7 @@ "keywords": ["templating"], "homepage": "https://twig.symfony.com", "license": "BSD-3-Clause", + "minimum-stability": "dev", "authors": [ { "name": "Fabien Potencier", @@ -14,7 +15,6 @@ }, { "name": "Twig Team", - "homepage": "https://twig.symfony.com/contributors", "role": "Contributors" }, { @@ -24,30 +24,27 @@ } ], "require": { - "php": ">=5.5.0", + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "^1.3", "symfony/polyfill-ctype": "^1.8" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4@dev|^5.0", - "symfony/debug": "^3.4|^4.2", + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0", "psr/container": "^1.0" }, "autoload": { - "psr-0" : { - "Twig_" : "lib/" - }, "psr-4" : { "Twig\\" : "src/" } }, "autoload-dev": { "psr-4" : { - "Twig\\Tests\\" : "tests" + "Twig\\Tests\\" : "tests/" } }, "extra": { "branch-alias": { - "dev-master": "1.42-dev" + "dev-master": "3.4-dev" } } } diff --git a/lib/twig/twig/ext/twig/.gitignore b/lib/twig/twig/ext/twig/.gitignore deleted file mode 100644 index 56ea76cc26..0000000000 --- a/lib/twig/twig/ext/twig/.gitignore +++ /dev/null @@ -1,30 +0,0 @@ -*.sw* -.deps -Makefile -Makefile.fragments -Makefile.global -Makefile.objects -acinclude.m4 -aclocal.m4 -build/ -config.cache -config.guess -config.h -config.h.in -config.log -config.nice -config.status -config.sub -configure -configure.in -install-sh -libtool -ltmain.sh -missing -mkinstalldirs -run-tests.php -twig.loT -.libs/ -modules/ -twig.la -twig.lo diff --git a/lib/twig/twig/ext/twig/config.m4 b/lib/twig/twig/ext/twig/config.m4 deleted file mode 100644 index 83486be4c2..0000000000 --- a/lib/twig/twig/ext/twig/config.m4 +++ /dev/null @@ -1,8 +0,0 @@ -dnl config.m4 for extension twig - -PHP_ARG_ENABLE(twig, whether to enable twig support, -[ --enable-twig Enable twig support]) - -if test "$PHP_TWIG" != "no"; then - PHP_NEW_EXTENSION(twig, twig.c, $ext_shared) -fi diff --git a/lib/twig/twig/ext/twig/config.w32 b/lib/twig/twig/ext/twig/config.w32 deleted file mode 100644 index cb287b99ba..0000000000 --- a/lib/twig/twig/ext/twig/config.w32 +++ /dev/null @@ -1,8 +0,0 @@ -// vim:ft=javascript - -ARG_ENABLE("twig", "Twig support", "no"); - -if (PHP_TWIG != "no") { - AC_DEFINE('HAVE_TWIG', 1); - EXTENSION('twig', 'twig.c'); -} diff --git a/lib/twig/twig/ext/twig/php_twig.h b/lib/twig/twig/ext/twig/php_twig.h deleted file mode 100644 index 6cc47b689e..0000000000 --- a/lib/twig/twig/ext/twig/php_twig.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | Twig Extension | - +----------------------------------------------------------------------+ - | Copyright (c) 2011 Derick Rethans | - +----------------------------------------------------------------------+ - | Redistribution and use in source and binary forms, with or without | - | modification, are permitted provided that the conditions mentioned | - | in the accompanying LICENSE file are met (BSD-3-Clause). | - +----------------------------------------------------------------------+ - | Author: Derick Rethans | - +----------------------------------------------------------------------+ - */ - -#ifndef PHP_TWIG_H -#define PHP_TWIG_H - -#define PHP_TWIG_VERSION "1.42.4" - -#include "php.h" - -extern zend_module_entry twig_module_entry; -#define phpext_twig_ptr &twig_module_entry -#ifndef PHP_WIN32 -zend_module_entry *get_module(void); -#endif - -#ifdef ZTS -#include "TSRM.h" -#endif - -PHP_FUNCTION(twig_template_get_attributes); -PHP_RSHUTDOWN_FUNCTION(twig); - -#endif diff --git a/lib/twig/twig/ext/twig/twig.c b/lib/twig/twig/ext/twig/twig.c deleted file mode 100644 index 171ed10bb8..0000000000 --- a/lib/twig/twig/ext/twig/twig.c +++ /dev/null @@ -1,1217 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | Twig Extension | - +----------------------------------------------------------------------+ - | Copyright (c) 2011 Derick Rethans | - +----------------------------------------------------------------------+ - | Redistribution and use in source and binary forms, with or without | - | modification, are permitted provided that the conditions mentioned | - | in the accompanying LICENSE file are met (BSD-3-Clause). | - +----------------------------------------------------------------------+ - | Author: Derick Rethans | - +----------------------------------------------------------------------+ - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include "php.h" -#include "php_twig.h" -#include "ext/standard/php_var.h" -#include "ext/standard/php_string.h" -#include "ext/standard/php_smart_str.h" -#include "ext/spl/spl_exceptions.h" - -#include "Zend/zend_object_handlers.h" -#include "Zend/zend_interfaces.h" -#include "Zend/zend_exceptions.h" - -#ifndef Z_ADDREF_P -#define Z_ADDREF_P(pz) (pz)->refcount++ -#endif - -#ifndef E_USER_DEPRECATED -#define E_USER_DEPRECATED (1<<14L) -#endif - -#define FREE_DTOR(z) \ - zval_dtor(z); \ - efree(z); - -#if PHP_VERSION_ID >= 50300 - #define APPLY_TSRMLS_DC TSRMLS_DC - #define APPLY_TSRMLS_CC TSRMLS_CC - #define APPLY_TSRMLS_FETCH() -#else - #define APPLY_TSRMLS_DC - #define APPLY_TSRMLS_CC - #define APPLY_TSRMLS_FETCH() TSRMLS_FETCH() -#endif - -ZEND_BEGIN_ARG_INFO_EX(twig_template_get_attribute_args, ZEND_SEND_BY_VAL, ZEND_RETURN_VALUE, 6) - ZEND_ARG_INFO(0, template) - ZEND_ARG_INFO(0, object) - ZEND_ARG_INFO(0, item) - ZEND_ARG_INFO(0, arguments) - ZEND_ARG_INFO(0, type) - ZEND_ARG_INFO(0, isDefinedTest) -ZEND_END_ARG_INFO() - -#ifndef PHP_FE_END -#define PHP_FE_END { NULL, NULL, NULL} -#endif - -static const zend_function_entry twig_functions[] = { - PHP_FE(twig_template_get_attributes, twig_template_get_attribute_args) - PHP_FE_END -}; - -PHP_RSHUTDOWN_FUNCTION(twig) -{ -#if ZEND_DEBUG - CG(unclean_shutdown) = 0; /* get rid of PHPUnit's exit() and report memleaks */ -#endif - return SUCCESS; -} - -zend_module_entry twig_module_entry = { - STANDARD_MODULE_HEADER, - "twig", - twig_functions, - NULL, - NULL, - NULL, - PHP_RSHUTDOWN(twig), - NULL, - PHP_TWIG_VERSION, - STANDARD_MODULE_PROPERTIES -}; - - -#ifdef COMPILE_DL_TWIG -ZEND_GET_MODULE(twig) -#endif - -static int TWIG_ARRAY_KEY_EXISTS(zval *array, zval *key) -{ - if (Z_TYPE_P(array) != IS_ARRAY) { - return 0; - } - - switch (Z_TYPE_P(key)) { - case IS_NULL: - return zend_hash_exists(Z_ARRVAL_P(array), "", 1); - - case IS_BOOL: - case IS_DOUBLE: - convert_to_long(key); - case IS_LONG: - return zend_hash_index_exists(Z_ARRVAL_P(array), Z_LVAL_P(key)); - - default: - convert_to_string(key); - return zend_symtable_exists(Z_ARRVAL_P(array), Z_STRVAL_P(key), Z_STRLEN_P(key) + 1); - } -} - -static int TWIG_INSTANCE_OF(zval *object, zend_class_entry *interface TSRMLS_DC) -{ - if (Z_TYPE_P(object) != IS_OBJECT) { - return 0; - } - return instanceof_function(Z_OBJCE_P(object), interface TSRMLS_CC); -} - -static int TWIG_INSTANCE_OF_USERLAND(zval *object, char *interface TSRMLS_DC) -{ - zend_class_entry **pce; - if (Z_TYPE_P(object) != IS_OBJECT) { - return 0; - } - if (zend_lookup_class(interface, strlen(interface), &pce TSRMLS_CC) == FAILURE) { - return 0; - } - return instanceof_function(Z_OBJCE_P(object), *pce TSRMLS_CC); -} - -static zval *TWIG_GET_ARRAYOBJECT_ELEMENT(zval *object, zval *offset TSRMLS_DC) -{ - zend_class_entry *ce = Z_OBJCE_P(object); - zval *retval; - - if (Z_TYPE_P(object) == IS_OBJECT) { - SEPARATE_ARG_IF_REF(offset); - zend_call_method_with_1_params(&object, ce, NULL, "offsetget", &retval, offset); - - zval_ptr_dtor(&offset); - - if (!retval) { - if (!EG(exception)) { - zend_error(E_ERROR, "Undefined offset for object of type %s used as array.", ce->name); - } - return NULL; - } - - return retval; - } - return NULL; -} - -static int TWIG_ISSET_ARRAYOBJECT_ELEMENT(zval *object, zval *offset TSRMLS_DC) -{ - zend_class_entry *ce = Z_OBJCE_P(object); - zval *retval; - - if (Z_TYPE_P(object) == IS_OBJECT) { - SEPARATE_ARG_IF_REF(offset); - zend_call_method_with_1_params(&object, ce, NULL, "offsetexists", &retval, offset); - - zval_ptr_dtor(&offset); - - if (!retval) { - if (!EG(exception)) { - zend_error(E_ERROR, "Undefined offset for object of type %s used as array.", ce->name); - } - return 0; - } - - return (retval && Z_TYPE_P(retval) == IS_BOOL && Z_LVAL_P(retval)); - } - return 0; -} - -static char *TWIG_STRTOLOWER(const char *str, int str_len) -{ - char *item_dup; - - item_dup = estrndup(str, str_len); - php_strtolower(item_dup, str_len); - return item_dup; -} - -static zval *TWIG_CALL_USER_FUNC_ARRAY(zval *object, char *function, zval *arguments TSRMLS_DC) -{ - zend_fcall_info fci; - zval ***args = NULL; - int arg_count = 0; - HashTable *table; - HashPosition pos; - int i = 0; - zval *retval_ptr; - zval *zfunction; - - if (arguments) { - table = HASH_OF(arguments); - args = safe_emalloc(sizeof(zval **), table->nNumOfElements, 0); - - zend_hash_internal_pointer_reset_ex(table, &pos); - - while (zend_hash_get_current_data_ex(table, (void **)&args[i], &pos) == SUCCESS) { - i++; - zend_hash_move_forward_ex(table, &pos); - } - arg_count = table->nNumOfElements; - } - - MAKE_STD_ZVAL(zfunction); - ZVAL_STRING(zfunction, function, 1); - fci.size = sizeof(fci); - fci.function_table = EG(function_table); - fci.function_name = zfunction; - fci.symbol_table = NULL; -#if PHP_VERSION_ID >= 50300 - fci.object_ptr = object; -#else - fci.object_pp = &object; -#endif - fci.retval_ptr_ptr = &retval_ptr; - fci.param_count = arg_count; - fci.params = args; - fci.no_separation = 0; - - if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE) { - ALLOC_INIT_ZVAL(retval_ptr); - ZVAL_BOOL(retval_ptr, 0); - } - - if (args) { - efree(fci.params); - } - FREE_DTOR(zfunction); - return retval_ptr; -} - -static int TWIG_CALL_BOOLEAN(zval *object, char *functionName TSRMLS_DC) -{ - zval *ret; - int res; - - ret = TWIG_CALL_USER_FUNC_ARRAY(object, functionName, NULL TSRMLS_CC); - res = Z_LVAL_P(ret); - zval_ptr_dtor(&ret); - return res; -} - -static zval *TWIG_GET_STATIC_PROPERTY(zval *class, char *prop_name TSRMLS_DC) -{ - zval **tmp_zval; - zend_class_entry *ce; - - if (class == NULL || Z_TYPE_P(class) != IS_OBJECT) { - return NULL; - } - - ce = zend_get_class_entry(class TSRMLS_CC); -#if PHP_VERSION_ID >= 50400 - tmp_zval = zend_std_get_static_property(ce, prop_name, strlen(prop_name), 0, NULL TSRMLS_CC); -#else - tmp_zval = zend_std_get_static_property(ce, prop_name, strlen(prop_name), 0 TSRMLS_CC); -#endif - return *tmp_zval; -} - -static zval *TWIG_GET_ARRAY_ELEMENT_ZVAL(zval *class, zval *prop_name TSRMLS_DC) -{ - zval **tmp_zval; - - if (class == NULL || Z_TYPE_P(class) != IS_ARRAY) { - if (class != NULL && Z_TYPE_P(class) == IS_OBJECT && TWIG_INSTANCE_OF(class, zend_ce_arrayaccess TSRMLS_CC)) { - // array access object - return TWIG_GET_ARRAYOBJECT_ELEMENT(class, prop_name TSRMLS_CC); - } - return NULL; - } - - switch(Z_TYPE_P(prop_name)) { - case IS_NULL: - zend_hash_find(HASH_OF(class), "", 1, (void**) &tmp_zval); - return *tmp_zval; - - case IS_BOOL: - case IS_DOUBLE: - convert_to_long(prop_name); - case IS_LONG: - zend_hash_index_find(HASH_OF(class), Z_LVAL_P(prop_name), (void **) &tmp_zval); - return *tmp_zval; - - case IS_STRING: - zend_symtable_find(HASH_OF(class), Z_STRVAL_P(prop_name), Z_STRLEN_P(prop_name) + 1, (void**) &tmp_zval); - return *tmp_zval; - } - - return NULL; -} - -static zval *TWIG_GET_ARRAY_ELEMENT(zval *class, char *prop_name, int prop_name_length TSRMLS_DC) -{ - zval **tmp_zval; - - if (class == NULL/* || Z_TYPE_P(class) != IS_ARRAY*/) { - return NULL; - } - - if (class != NULL && Z_TYPE_P(class) == IS_OBJECT && TWIG_INSTANCE_OF(class, zend_ce_arrayaccess TSRMLS_CC)) { - // array access object - zval *tmp_name_zval; - zval *tmp_ret_zval; - - ALLOC_INIT_ZVAL(tmp_name_zval); - ZVAL_STRING(tmp_name_zval, prop_name, 1); - tmp_ret_zval = TWIG_GET_ARRAYOBJECT_ELEMENT(class, tmp_name_zval TSRMLS_CC); - FREE_DTOR(tmp_name_zval); - return tmp_ret_zval; - } - - if (zend_symtable_find(HASH_OF(class), prop_name, prop_name_length+1, (void**)&tmp_zval) == SUCCESS) { - return *tmp_zval; - } - return NULL; -} - -static zval *TWIG_PROPERTY(zval *object, zval *propname TSRMLS_DC) -{ - zval *tmp = NULL; - - if (Z_OBJ_HT_P(object)->read_property) { -#if PHP_VERSION_ID >= 50400 - tmp = Z_OBJ_HT_P(object)->read_property(object, propname, BP_VAR_IS, NULL TSRMLS_CC); -#else - tmp = Z_OBJ_HT_P(object)->read_property(object, propname, BP_VAR_IS TSRMLS_CC); -#endif - if (tmp == EG(uninitialized_zval_ptr)) { - ZVAL_NULL(tmp); - } - } - return tmp; -} - -static int TWIG_HAS_PROPERTY(zval *object, zval *propname TSRMLS_DC) -{ - if (Z_OBJ_HT_P(object)->has_property) { -#if PHP_VERSION_ID >= 50400 - return Z_OBJ_HT_P(object)->has_property(object, propname, 0, NULL TSRMLS_CC); -#else - return Z_OBJ_HT_P(object)->has_property(object, propname, 0 TSRMLS_CC); -#endif - } - return 0; -} - -static int TWIG_HAS_DYNAMIC_PROPERTY(zval *object, char *prop, int prop_len TSRMLS_DC) -{ - if (Z_OBJ_HT_P(object)->get_properties) { - return zend_hash_quick_exists( - Z_OBJ_HT_P(object)->get_properties(object TSRMLS_CC), // the properties hash - prop, // property name - prop_len + 1, // property length - zend_get_hash_value(prop, prop_len + 1) // hash value - ); - } - return 0; -} - -static zval *TWIG_PROPERTY_CHAR(zval *object, char *propname TSRMLS_DC) -{ - zval *tmp_name_zval, *tmp; - - ALLOC_INIT_ZVAL(tmp_name_zval); - ZVAL_STRING(tmp_name_zval, propname, 1); - tmp = TWIG_PROPERTY(object, tmp_name_zval TSRMLS_CC); - FREE_DTOR(tmp_name_zval); - return tmp; -} - -static zval *TWIG_CALL_S(zval *object, char *method, char *arg0 TSRMLS_DC) -{ - zend_fcall_info fci; - zval **args[1]; - zval *argument; - zval *zfunction; - zval *retval_ptr; - - MAKE_STD_ZVAL(argument); - ZVAL_STRING(argument, arg0, 1); - args[0] = &argument; - - MAKE_STD_ZVAL(zfunction); - ZVAL_STRING(zfunction, method, 1); - fci.size = sizeof(fci); - fci.function_table = EG(function_table); - fci.function_name = zfunction; - fci.symbol_table = NULL; -#if PHP_VERSION_ID >= 50300 - fci.object_ptr = object; -#else - fci.object_pp = &object; -#endif - fci.retval_ptr_ptr = &retval_ptr; - fci.param_count = 1; - fci.params = args; - fci.no_separation = 0; - - if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE) { - FREE_DTOR(zfunction); - zval_ptr_dtor(&argument); - return 0; - } - FREE_DTOR(zfunction); - zval_ptr_dtor(&argument); - return retval_ptr; -} - -static int TWIG_CALL_SB(zval *object, char *method, char *arg0 TSRMLS_DC) -{ - zval *retval_ptr; - int success; - - retval_ptr = TWIG_CALL_S(object, method, arg0 TSRMLS_CC); - success = (retval_ptr && (Z_TYPE_P(retval_ptr) == IS_BOOL) && Z_LVAL_P(retval_ptr)); - - if (retval_ptr) { - zval_ptr_dtor(&retval_ptr); - } - - return success; -} - -static int TWIG_CALL_ZZ(zval *object, char *method, zval *arg1, zval *arg2 TSRMLS_DC) -{ - zend_fcall_info fci; - zval **args[2]; - zval *zfunction; - zval *retval_ptr; - int success; - - args[0] = &arg1; - args[1] = &arg2; - - MAKE_STD_ZVAL(zfunction); - ZVAL_STRING(zfunction, method, 1); - fci.size = sizeof(fci); - fci.function_table = EG(function_table); - fci.function_name = zfunction; - fci.symbol_table = NULL; -#if PHP_VERSION_ID >= 50300 - fci.object_ptr = object; -#else - fci.object_pp = &object; -#endif - fci.retval_ptr_ptr = &retval_ptr; - fci.param_count = 2; - fci.params = args; - fci.no_separation = 0; - - if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE) { - FREE_DTOR(zfunction); - return 0; - } - - FREE_DTOR(zfunction); - - success = (retval_ptr && (Z_TYPE_P(retval_ptr) == IS_BOOL) && Z_LVAL_P(retval_ptr)); - if (retval_ptr) { - zval_ptr_dtor(&retval_ptr); - } - - return success; -} - -#ifndef Z_SET_REFCOUNT_P -# define Z_SET_REFCOUNT_P(pz, rc) pz->refcount = rc -# define Z_UNSET_ISREF_P(pz) pz->is_ref = 0 -#endif - -static void TWIG_NEW(zval *object, char *class, zval *arg0, zval *arg1 TSRMLS_DC) -{ - zend_class_entry **pce; - - if (zend_lookup_class(class, strlen(class), &pce TSRMLS_CC) == FAILURE) { - return; - } - - Z_TYPE_P(object) = IS_OBJECT; - object_init_ex(object, *pce); - Z_SET_REFCOUNT_P(object, 1); - Z_UNSET_ISREF_P(object); - - TWIG_CALL_ZZ(object, "__construct", arg0, arg1 TSRMLS_CC); -} - -static int twig_add_array_key_to_string(void *pDest APPLY_TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) -{ - smart_str *buf; - char *joiner; - APPLY_TSRMLS_FETCH(); - - buf = va_arg(args, smart_str*); - joiner = va_arg(args, char*); - - if (buf->len != 0) { - smart_str_appends(buf, joiner); - } - - if (hash_key->nKeyLength == 0) { - smart_str_append_long(buf, (long) hash_key->h); - } else { - char *key, *tmp_str; - int key_len, tmp_len; - key = php_addcslashes(hash_key->arKey, hash_key->nKeyLength - 1, &key_len, 0, "'\\", 2 TSRMLS_CC); - tmp_str = php_str_to_str_ex(key, key_len, "\0", 1, "' . \"\\0\" . '", 12, &tmp_len, 0, NULL); - - smart_str_appendl(buf, tmp_str, tmp_len); - efree(key); - efree(tmp_str); - } - - return 0; -} - -static char *TWIG_IMPLODE_ARRAY_KEYS(char *joiner, zval *array TSRMLS_DC) -{ - smart_str collector = { 0, 0, 0 }; - - smart_str_appendl(&collector, "", 0); - zend_hash_apply_with_arguments(HASH_OF(array) APPLY_TSRMLS_CC, twig_add_array_key_to_string, 2, &collector, joiner); - smart_str_0(&collector); - - return collector.c; -} - -static void TWIG_RUNTIME_ERROR(zval *template TSRMLS_DC, char *message, ...) -{ - char *buffer; - va_list args; - zend_class_entry **pce; - zval *ex; - zval *constructor; - zval *zmessage; - zval *lineno; - zval *filename_func; - zval *filename; - zval *constructor_args[3]; - zval *constructor_retval; - - if (zend_lookup_class("Twig_Error_Runtime", strlen("Twig_Error_Runtime"), &pce TSRMLS_CC) == FAILURE) { - return; - } - - va_start(args, message); - vspprintf(&buffer, 0, message, args); - va_end(args); - - MAKE_STD_ZVAL(ex); - object_init_ex(ex, *pce); - - // Call Twig_Error constructor - MAKE_STD_ZVAL(constructor); - MAKE_STD_ZVAL(zmessage); - MAKE_STD_ZVAL(lineno); - MAKE_STD_ZVAL(filename); - MAKE_STD_ZVAL(filename_func); - MAKE_STD_ZVAL(constructor_retval); - - ZVAL_STRINGL(constructor, "__construct", sizeof("__construct")-1, 1); - ZVAL_STRING(zmessage, buffer, 1); - ZVAL_LONG(lineno, -1); - - // Get template filename - ZVAL_STRINGL(filename_func, "getTemplateName", sizeof("getTemplateName")-1, 1); - call_user_function(EG(function_table), &template, filename_func, filename, 0, 0 TSRMLS_CC); - - constructor_args[0] = zmessage; - constructor_args[1] = lineno; - constructor_args[2] = filename; - call_user_function(EG(function_table), &ex, constructor, constructor_retval, 3, constructor_args TSRMLS_CC); - - zval_ptr_dtor(&constructor_retval); - zval_ptr_dtor(&zmessage); - zval_ptr_dtor(&lineno); - zval_ptr_dtor(&filename); - FREE_DTOR(constructor); - FREE_DTOR(filename_func); - efree(buffer); - - zend_throw_exception_object(ex TSRMLS_CC); -} - -static char *TWIG_GET_CLASS_NAME(zval *object TSRMLS_DC) -{ - char *class_name; - zend_uint class_name_len; - - if (Z_TYPE_P(object) != IS_OBJECT) { - return ""; - } -#if PHP_API_VERSION >= 20100412 - zend_get_object_classname(object, (const char **) &class_name, &class_name_len TSRMLS_CC); -#else - zend_get_object_classname(object, &class_name, &class_name_len TSRMLS_CC); -#endif - return class_name; -} - -static int twig_add_method_to_class(void *pDest APPLY_TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) -{ - zend_class_entry *ce; - zval *retval; - char *item; - size_t item_len; - zend_function *mptr = (zend_function *) pDest; - APPLY_TSRMLS_FETCH(); - - if (!(mptr->common.fn_flags & ZEND_ACC_PUBLIC)) { - return 0; - } - - ce = *va_arg(args, zend_class_entry**); - retval = va_arg(args, zval*); - - item_len = strlen(mptr->common.function_name); - item = estrndup(mptr->common.function_name, item_len); - php_strtolower(item, item_len); - - if (strcmp("getenvironment", item) == 0) { - zend_class_entry **twig_template_ce; - if (zend_lookup_class("Twig_Template", strlen("Twig_Template"), &twig_template_ce TSRMLS_CC) == FAILURE) { - return 0; - } - if (instanceof_function(ce, *twig_template_ce TSRMLS_CC)) { - return 0; - } - } - - add_assoc_stringl_ex(retval, item, item_len+1, item, item_len, 0); - - return 0; -} - -static int twig_add_property_to_class(void *pDest APPLY_TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) -{ - zend_class_entry *ce; - zval *retval; - char *class_name, *prop_name; - zend_property_info *pptr = (zend_property_info *) pDest; - APPLY_TSRMLS_FETCH(); - - if (!(pptr->flags & ZEND_ACC_PUBLIC) || (pptr->flags & ZEND_ACC_STATIC)) { - return 0; - } - - ce = *va_arg(args, zend_class_entry**); - retval = va_arg(args, zval*); - -#if PHP_API_VERSION >= 20100412 - zend_unmangle_property_name(pptr->name, pptr->name_length, (const char **) &class_name, (const char **) &prop_name); -#else - zend_unmangle_property_name(pptr->name, pptr->name_length, &class_name, &prop_name); -#endif - - add_assoc_string(retval, prop_name, prop_name, 1); - - return 0; -} - -static void twig_add_class_to_cache(zval *cache, zval *object, char *class_name TSRMLS_DC) -{ - zval *class_info, *class_methods, *class_properties; - zend_class_entry *class_ce; - - class_ce = zend_get_class_entry(object TSRMLS_CC); - - ALLOC_INIT_ZVAL(class_info); - ALLOC_INIT_ZVAL(class_methods); - ALLOC_INIT_ZVAL(class_properties); - array_init(class_info); - array_init(class_methods); - array_init(class_properties); - // add all methods to self::cache[$class]['methods'] - zend_hash_apply_with_arguments(&class_ce->function_table APPLY_TSRMLS_CC, twig_add_method_to_class, 2, &class_ce, class_methods); - zend_hash_apply_with_arguments(&class_ce->properties_info APPLY_TSRMLS_CC, twig_add_property_to_class, 2, &class_ce, class_properties); - - add_assoc_zval(class_info, "methods", class_methods); - add_assoc_zval(class_info, "properties", class_properties); - add_assoc_zval(cache, class_name, class_info); -} - -/* {{{ proto mixed twig_template_get_attributes(TwigTemplate template, mixed object, mixed item, array arguments, string type, boolean isDefinedTest, boolean ignoreStrictCheck) - A C implementation of TwigTemplate::getAttribute() */ -PHP_FUNCTION(twig_template_get_attributes) -{ - zval *template; - zval *object; - char *item; - int item_len; - zval *zitem, ztmpitem; - zval *arguments = NULL; - zval *ret = NULL; - char *type = NULL; - int type_len = 0; - zend_bool isDefinedTest = 0; - zend_bool ignoreStrictCheck = 0; - int free_ret = 0; - zval *tmp_self_cache; - char *class_name = NULL; - zval *tmp_class; - char *type_name; - - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ozz|asbb", &template, &object, &zitem, &arguments, &type, &type_len, &isDefinedTest, &ignoreStrictCheck) == FAILURE) { - return; - } - - // convert the item to a string - ztmpitem = *zitem; - zval_copy_ctor(&ztmpitem); - convert_to_string(&ztmpitem); - item_len = Z_STRLEN(ztmpitem); - item = estrndup(Z_STRVAL(ztmpitem), item_len); - zval_dtor(&ztmpitem); - - if (!type) { - type = "any"; - } - -/* - // array - if (\Twig\Template::METHOD_CALL !== $type) { - $arrayItem = is_bool($item) || is_float($item) ? (int) $item : $item; - - if ((is_array($object) && array_key_exists($arrayItem, $object)) - || ($object instanceof ArrayAccess && isset($object[$arrayItem])) - ) { - if ($isDefinedTest) { - return true; - } - - return $object[$arrayItem]; - } -*/ - - - if (strcmp("method", type) != 0) { - if ((TWIG_ARRAY_KEY_EXISTS(object, zitem)) - || (TWIG_INSTANCE_OF(object, zend_ce_arrayaccess TSRMLS_CC) && TWIG_ISSET_ARRAYOBJECT_ELEMENT(object, zitem TSRMLS_CC)) - ) { - - if (isDefinedTest) { - efree(item); - RETURN_TRUE; - } - - ret = TWIG_GET_ARRAY_ELEMENT_ZVAL(object, zitem TSRMLS_CC); - - if (!ret) { - ret = &EG(uninitialized_zval); - } - RETVAL_ZVAL(ret, 1, 0); - if (free_ret) { - zval_ptr_dtor(&ret); - } - efree(item); - return; - } -/* - if (\Twig\Template::ARRAY_CALL === $type) { - if ($isDefinedTest) { - return false; - } - if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { - return null; - } -*/ - if (strcmp("array", type) == 0 || Z_TYPE_P(object) != IS_OBJECT) { - if (isDefinedTest) { - efree(item); - RETURN_FALSE; - } - if (ignoreStrictCheck || !TWIG_CALL_BOOLEAN(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "isStrictVariables" TSRMLS_CC)) { - efree(item); - return; - } -/* - if ($object instanceof ArrayAccess) { - $message = sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist', $arrayItem, get_class($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)); - } elseif (is_array($object)) { - if (empty($object)) { - $message = sprintf('Key "%s" does not exist as the array is empty', $arrayItem); - } else { - $message = sprintf('Key "%s" for array with keys "%s" does not exist', $arrayItem, implode(', ', array_keys($object))); - } - } elseif (\Twig\Template::ARRAY_CALL === $type) { - 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); - } - } 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); - } - throw new \Twig\Error\RuntimeError($message, -1, $this->getTemplateName()); - } - } -*/ - if (TWIG_INSTANCE_OF(object, zend_ce_arrayaccess TSRMLS_CC)) { - TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Key \"%s\" in object with ArrayAccess of class \"%s\" does not exist.", item, TWIG_GET_CLASS_NAME(object TSRMLS_CC)); - } else if (Z_TYPE_P(object) == IS_OBJECT) { - TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Impossible to access a key \"%s\" on an object of class \"%s\" that does not implement ArrayAccess interface.", item, TWIG_GET_CLASS_NAME(object TSRMLS_CC)); - } else if (Z_TYPE_P(object) == IS_ARRAY) { - if (0 == zend_hash_num_elements(Z_ARRVAL_P(object))) { - TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Key \"%s\" does not exist as the array is empty.", item); - } else { - char *array_keys = TWIG_IMPLODE_ARRAY_KEYS(", ", object TSRMLS_CC); - TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Key \"%s\" for array with keys \"%s\" does not exist.", item, array_keys); - efree(array_keys); - } - } else { - char *type_name = zend_zval_type_name(object); - Z_ADDREF_P(object); - if (Z_TYPE_P(object) == IS_NULL) { - convert_to_string(object); - TWIG_RUNTIME_ERROR(template TSRMLS_CC, - (strcmp("array", type) == 0) - ? "Impossible to access a key (\"%s\") on a %s variable." - : "Impossible to access an attribute (\"%s\") on a %s variable.", - item, type_name); - } else { - convert_to_string(object); - TWIG_RUNTIME_ERROR(template TSRMLS_CC, - (strcmp("array", type) == 0) - ? "Impossible to access a key (\"%s\") on a %s variable (\"%s\")." - : "Impossible to access an attribute (\"%s\") on a %s variable (\"%s\").", - item, type_name, Z_STRVAL_P(object)); - } - zval_ptr_dtor(&object); - } - efree(item); - return; - } - } - -/* - if (!is_object($object)) { - if ($isDefinedTest) { - return false; - } -*/ - - if (Z_TYPE_P(object) != IS_OBJECT) { - if (isDefinedTest) { - efree(item); - RETURN_FALSE; - } -/* - if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { - return null; - } - - if (null === $object) { - $message = sprintf('Impossible to invoke a method ("%s") on a null variable', $item); - } elseif (is_array($object)) { - $message = sprintf('Impossible to invoke a method ("%s") on an array.', $item); - } else { - $message = sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s")', $item, gettype($object), $object); - } - - throw new \Twig\Error\RuntimeError($message, -1, $this->getTemplateName()); - } -*/ - if (ignoreStrictCheck || !TWIG_CALL_BOOLEAN(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "isStrictVariables" TSRMLS_CC)) { - efree(item); - return; - } - - type_name = zend_zval_type_name(object); - Z_ADDREF_P(object); - if (Z_TYPE_P(object) == IS_NULL) { - TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Impossible to invoke a method (\"%s\") on a null variable.", item); - } else if (Z_TYPE_P(object) == IS_ARRAY) { - TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Impossible to invoke a method (\"%s\") on an array.", item); - } else { - convert_to_string_ex(&object); - - TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Impossible to invoke a method (\"%s\") on a %s variable (\"%s\").", item, type_name, Z_STRVAL_P(object)); - } - - zval_ptr_dtor(&object); - efree(item); - return; - } -/* - $class = get_class($object); -*/ - - class_name = TWIG_GET_CLASS_NAME(object TSRMLS_CC); - tmp_self_cache = TWIG_GET_STATIC_PROPERTY(template, "cache" TSRMLS_CC); - tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC); - - if (!tmp_class) { - twig_add_class_to_cache(tmp_self_cache, object, class_name TSRMLS_CC); - tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC); - } - efree(class_name); - -/* - // object property - if (\Twig\Template::METHOD_CALL !== $type && !$object instanceof \Twig\Template) { - if (isset($object->$item) || array_key_exists((string) $item, $object)) { - if ($isDefinedTest) { - return true; - } - - if ($this->env->hasExtension('\Twig\Extension\SandboxExtension')) { - $this->env->getExtension('\Twig\Extension\SandboxExtension')->checkPropertyAllowed($object, $item); - } - - return $object->$item; - } - } -*/ - if (strcmp("method", type) != 0 && !TWIG_INSTANCE_OF_USERLAND(object, "Twig_Template" TSRMLS_CC)) { - zval *tmp_properties, *tmp_item; - - tmp_properties = TWIG_GET_ARRAY_ELEMENT(tmp_class, "properties", strlen("properties") TSRMLS_CC); - tmp_item = TWIG_GET_ARRAY_ELEMENT(tmp_properties, item, item_len TSRMLS_CC); - - if (tmp_item || TWIG_HAS_PROPERTY(object, zitem TSRMLS_CC) || TWIG_HAS_DYNAMIC_PROPERTY(object, item, item_len TSRMLS_CC)) { - if (isDefinedTest) { - efree(item); - RETURN_TRUE; - } - if (TWIG_CALL_SB(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "hasExtension", "Twig_Extension_Sandbox" TSRMLS_CC)) { - TWIG_CALL_ZZ(TWIG_CALL_S(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getExtension", "Twig_Extension_Sandbox" TSRMLS_CC), "checkPropertyAllowed", object, zitem TSRMLS_CC); - } - if (EG(exception)) { - efree(item); - return; - } - - ret = TWIG_PROPERTY(object, zitem TSRMLS_CC); - efree(item); - RETURN_ZVAL(ret, 1, 0); - } - } -/* - // object method - if (!isset(self::$cache[$class]['methods'])) { - if ($object instanceof self) { - $ref = new \ReflectionClass($class); - $methods = []; - - foreach ($ref->getMethods(ReflectionMethod::IS_PUBLIC) as $refMethod) { - $methodName = strtolower($refMethod->name); - - // Accessing the environment from templates is forbidden to prevent untrusted changes to the environment - if ('getenvironment' !== $methodName) { - $methods[$methodName] = true; - } - } - - self::$cache[$class]['methods'] = $methods; - } else { - self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object))); - } - } - - $call = false; - $lcItem = strtolower($item); - if (isset(self::$cache[$class]['methods'][$lcItem])) { - $method = (string) $item; - } elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) { - $method = 'get'.$item; - } elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) { - $method = 'is'.$item; - } elseif (isset(self::$cache[$class]['methods']['__call'])) { - $method = (string) $item; - $call = true; -*/ - { - int call = 0; - char *lcItem = TWIG_STRTOLOWER(item, item_len); - int lcItem_length; - char *method = NULL; - char *methodForDeprecation = NULL; - char *tmp_method_name_get; - char *tmp_method_name_is; - zval *zmethod; - zval *tmp_methods; - - lcItem_length = strlen(lcItem); - tmp_method_name_get = emalloc(4 + lcItem_length); - tmp_method_name_is = emalloc(3 + lcItem_length); - - sprintf(tmp_method_name_get, "get%s", lcItem); - sprintf(tmp_method_name_is, "is%s", lcItem); - - tmp_methods = TWIG_GET_ARRAY_ELEMENT(tmp_class, "methods", strlen("methods") TSRMLS_CC); - methodForDeprecation = emalloc(item_len + 1); - sprintf(methodForDeprecation, "%s", item); - - if (TWIG_GET_ARRAY_ELEMENT(tmp_methods, lcItem, lcItem_length TSRMLS_CC)) { - method = item; - } else if (TWIG_GET_ARRAY_ELEMENT(tmp_methods, tmp_method_name_get, lcItem_length + 3 TSRMLS_CC)) { - method = tmp_method_name_get; - } else if (TWIG_GET_ARRAY_ELEMENT(tmp_methods, tmp_method_name_is, lcItem_length + 2 TSRMLS_CC)) { - method = tmp_method_name_is; - } else if (TWIG_GET_ARRAY_ELEMENT(tmp_methods, "__call", 6 TSRMLS_CC)) { - method = item; - call = 1; -/* - } else { - if ($isDefinedTest) { - return false; - } - - if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { - return null; - } - - throw new \Twig\Error\RuntimeError(sprintf('Method "%s" for object "%s" does not exist.', $item, get_class($object)), -1, $this->getTemplateName()); - } - - if ($isDefinedTest) { - return true; - } -*/ - } else { - efree(tmp_method_name_get); - efree(tmp_method_name_is); - efree(lcItem); - - if (isDefinedTest) { - efree(item); - RETURN_FALSE; - } - if (ignoreStrictCheck || !TWIG_CALL_BOOLEAN(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "isStrictVariables" TSRMLS_CC)) { - efree(item); - return; - } - TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Neither the property \"%s\" nor one of the methods \"%s()\", \"get%s()\"/\"is%s()\" or \"__call()\" exist and have public access in class \"%s\".", item, item, item, item, TWIG_GET_CLASS_NAME(object TSRMLS_CC)); - efree(item); - return; - } - - if (isDefinedTest) { - efree(tmp_method_name_get); - efree(tmp_method_name_is); - efree(lcItem);efree(item); - RETURN_TRUE; - } -/* - if ($this->env->hasExtension('\Twig\Extension\SandboxExtension')) { - $this->env->getExtension('\Twig\Extension\SandboxExtension')->checkMethodAllowed($object, $method); - } -*/ - MAKE_STD_ZVAL(zmethod); - ZVAL_STRING(zmethod, method, 1); - if (TWIG_CALL_SB(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "hasExtension", "Twig_Extension_Sandbox" TSRMLS_CC)) { - TWIG_CALL_ZZ(TWIG_CALL_S(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getExtension", "Twig_Extension_Sandbox" TSRMLS_CC), "checkMethodAllowed", object, zmethod TSRMLS_CC); - } - zval_ptr_dtor(&zmethod); - if (EG(exception)) { - efree(tmp_method_name_get); - efree(tmp_method_name_is); - efree(lcItem);efree(item); - return; - } -/* - // 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 { - $ret = call_user_func_array([$object, $method], $arguments); - } catch (\BadMethodCallException $e) { - if ($call && ($ignoreStrictCheck || !$this->env->isStrictVariables())) { - return null; - } - throw $e; - } -*/ - ret = TWIG_CALL_USER_FUNC_ARRAY(object, method, arguments TSRMLS_CC); - if (EG(exception) && TWIG_INSTANCE_OF(EG(exception), spl_ce_BadMethodCallException TSRMLS_CC)) { - if (ignoreStrictCheck || !TWIG_CALL_BOOLEAN(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "isStrictVariables" TSRMLS_CC)) { - efree(tmp_method_name_get); - efree(tmp_method_name_is); - efree(lcItem);efree(item); - zend_clear_exception(TSRMLS_C); - return; - } - } - free_ret = 1; - efree(tmp_method_name_get); - efree(tmp_method_name_is); - efree(lcItem); -/* - // @deprecated in 1.28 - if ($object instanceof Twig_TemplateInterface) { - $self = $object->getTemplateName() === $this->getTemplateName(); - $message = sprintf('Calling "%s" on template "%s" from template "%s" is deprecated since version 1.28 and won\'t be supported anymore in 2.0.', $item, $object->getTemplateName(), $this->getTemplateName()); - if ('renderBlock' === $method || 'displayBlock' === $method) { - $message .= sprintf(' Use block("%s"%s) instead).', $arguments[0], $self ? '' : ', template'); - } elseif ('hasBlock' === $method) { - $message .= sprintf(' Use "block("%s"%s) is defined" instead).', $arguments[0], $self ? '' : ', template'); - } elseif ('render' === $method || 'display' === $method) { - $message .= sprintf(' Use include("%s") instead).', $object->getTemplateName()); - } - @trigger_error($message, E_USER_DEPRECATED); - - return $ret === '' ? '' : new \Twig\Markup($ret, $this->env->getCharset()); - } - - return $ret; -*/ - efree(item); - // ret can be null, if e.g. the called method throws an exception - if (ret) { - if (TWIG_INSTANCE_OF_USERLAND(object, "Twig_TemplateInterface" TSRMLS_CC)) { - int self; - int old_error_reporting; - zval *object_filename; - zval *this_filename; - zval *filename_func; - char *deprecation_message_complement = NULL; - char *deprecation_message = NULL; - - MAKE_STD_ZVAL(object_filename); - MAKE_STD_ZVAL(this_filename); - MAKE_STD_ZVAL(filename_func); - - // Get templates names - ZVAL_STRINGL(filename_func, "getTemplateName", sizeof("getTemplateName")-1, 1); - call_user_function(EG(function_table), &object, filename_func, object_filename, 0, 0 TSRMLS_CC); - ZVAL_STRINGL(filename_func, "getTemplateName", sizeof("getTemplateName")-1, 1); - call_user_function(EG(function_table), &template, filename_func, this_filename, 0, 0 TSRMLS_CC); - - self = (strcmp(Z_STRVAL_P(object_filename), Z_STRVAL_P(this_filename)) == 0); - - if (strcmp(methodForDeprecation, "renderBlock") == 0 || strcmp(methodForDeprecation, "displayBlock") == 0) { - zval **arg0; - zend_hash_index_find(HASH_OF(arguments), 0, (void **) &arg0); - asprintf( - &deprecation_message_complement, - " Use block(\"%s\"%s) instead).", - Z_STRVAL_PP(arg0), - self ? "" : ", template" - ); - } else if (strcmp(methodForDeprecation, "hasBlock") == 0) { - zval **arg0; - zend_hash_index_find(HASH_OF(arguments), 0, (void **) &arg0); - asprintf( - &deprecation_message_complement, - " Use \"block(\"%s\"%s) is defined\" instead).", - Z_STRVAL_PP(arg0), - self ? "" : ", template" - ); - } else if (strcmp(methodForDeprecation, "render") == 0 || strcmp(methodForDeprecation, "display") == 0) { - asprintf( - &deprecation_message_complement, - " Use include(\"%s\") instead).", - Z_STRVAL_P(object_filename) - ); - } else { - deprecation_message_complement = (char*)calloc(0, sizeof(char)); - } - - asprintf( - &deprecation_message, - "Calling \"%s\" on template \"%s\" from template \"%s\" is deprecated since version 1.28 and won't be supported anymore in 2.0.%s", - methodForDeprecation, - Z_STRVAL_P(object_filename), - Z_STRVAL_P(this_filename), - deprecation_message_complement - ); - - old_error_reporting = EG(error_reporting); - EG(error_reporting) = 0; - zend_error(E_USER_DEPRECATED, "%s", deprecation_message); - EG(error_reporting) = old_error_reporting; - - FREE_DTOR(filename_func) - FREE_DTOR(object_filename) - FREE_DTOR(this_filename) - free(deprecation_message); - free(deprecation_message_complement); - - if (Z_STRLEN_P(ret) != 0) { - zval *charset = TWIG_CALL_USER_FUNC_ARRAY(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getCharset", NULL TSRMLS_CC); - TWIG_NEW(return_value, "Twig_Markup", ret, charset TSRMLS_CC); - zval_ptr_dtor(&charset); - if (ret) { - zval_ptr_dtor(&ret); - } - efree(methodForDeprecation); - return; - } - } - - RETVAL_ZVAL(ret, 1, 0); - if (free_ret) { - zval_ptr_dtor(&ret); - } - } - - efree(methodForDeprecation); - } -} diff --git a/lib/twig/twig/lib/Twig/Autoloader.php b/lib/twig/twig/lib/Twig/Autoloader.php deleted file mode 100644 index e34e2144fc..0000000000 --- a/lib/twig/twig/lib/Twig/Autoloader.php +++ /dev/null @@ -1,52 +0,0 @@ - - * - * @deprecated since 1.21 and will be removed in 2.0. Use Composer instead. 2.0. - */ -class Twig_Autoloader -{ - /** - * Registers Twig_Autoloader as an SPL autoloader. - * - * @param bool $prepend whether to prepend the autoloader or not - */ - public static function register($prepend = false) - { - @trigger_error('Using Twig_Autoloader is deprecated since version 1.21. Use Composer instead.', E_USER_DEPRECATED); - - spl_autoload_register([__CLASS__, 'autoload'], true, $prepend); - } - - /** - * Handles autoloading of classes. - * - * @param string $class a class name - */ - public static function autoload($class) - { - if (0 !== strpos($class, 'Twig')) { - return; - } - - if (is_file($file = __DIR__.'/../'.str_replace(['_', "\0"], ['/', ''], $class).'.php')) { - require $file; - } elseif (is_file($file = __DIR__.'/../../src/'.str_replace(['Twig\\', '\\', "\0"], ['', '/', ''], $class).'.php')) { - require $file; - } - } -} diff --git a/lib/twig/twig/lib/Twig/BaseNodeVisitor.php b/lib/twig/twig/lib/Twig/BaseNodeVisitor.php deleted file mode 100644 index fe99b25889..0000000000 --- a/lib/twig/twig/lib/Twig/BaseNodeVisitor.php +++ /dev/null @@ -1,11 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 3.0) - */ -interface Twig_CompilerInterface -{ - /** - * Compiles a node. - * - * @return $this - */ - public function compile(Twig_NodeInterface $node); - - /** - * Gets the current PHP code after compilation. - * - * @return string The PHP code - */ - public function getSource(); -} diff --git a/lib/twig/twig/lib/Twig/ContainerRuntimeLoader.php b/lib/twig/twig/lib/Twig/ContainerRuntimeLoader.php deleted file mode 100644 index b1f32f6823..0000000000 --- a/lib/twig/twig/lib/Twig/ContainerRuntimeLoader.php +++ /dev/null @@ -1,11 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 2.0) - */ -abstract class Twig_Filter implements Twig_FilterInterface, Twig_FilterCallableInterface -{ - protected $options; - protected $arguments = []; - - public function __construct(array $options = []) - { - $this->options = array_merge([ - 'needs_environment' => false, - 'needs_context' => false, - 'pre_escape' => null, - 'preserves_safety' => null, - 'callable' => null, - ], $options); - } - - public function setArguments($arguments) - { - $this->arguments = $arguments; - } - - public function getArguments() - { - return $this->arguments; - } - - public function needsEnvironment() - { - return $this->options['needs_environment']; - } - - public function needsContext() - { - return $this->options['needs_context']; - } - - public function getSafe(Node $filterArgs) - { - if (isset($this->options['is_safe'])) { - return $this->options['is_safe']; - } - - if (isset($this->options['is_safe_callback'])) { - return \call_user_func($this->options['is_safe_callback'], $filterArgs); - } - } - - public function getPreservesSafety() - { - return $this->options['preserves_safety']; - } - - public function getPreEscape() - { - return $this->options['pre_escape']; - } - - public function getCallable() - { - return $this->options['callable']; - } -} diff --git a/lib/twig/twig/lib/Twig/Filter/Function.php b/lib/twig/twig/lib/Twig/Filter/Function.php deleted file mode 100644 index 011d4ccf46..0000000000 --- a/lib/twig/twig/lib/Twig/Filter/Function.php +++ /dev/null @@ -1,40 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 2.0) - */ -class Twig_Filter_Function extends Twig_Filter -{ - protected $function; - - public function __construct($function, array $options = []) - { - $options['callable'] = $function; - - parent::__construct($options); - - $this->function = $function; - } - - public function compile() - { - return $this->function; - } -} diff --git a/lib/twig/twig/lib/Twig/Filter/Method.php b/lib/twig/twig/lib/Twig/Filter/Method.php deleted file mode 100644 index 5cd0628224..0000000000 --- a/lib/twig/twig/lib/Twig/Filter/Method.php +++ /dev/null @@ -1,44 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 2.0) - */ -class Twig_Filter_Method extends Twig_Filter -{ - protected $extension; - protected $method; - - public function __construct(ExtensionInterface $extension, $method, array $options = []) - { - $options['callable'] = [$extension, $method]; - - parent::__construct($options); - - $this->extension = $extension; - $this->method = $method; - } - - public function compile() - { - return sprintf('$this->env->getExtension(\'%s\')->%s', \get_class($this->extension), $this->method); - } -} diff --git a/lib/twig/twig/lib/Twig/Filter/Node.php b/lib/twig/twig/lib/Twig/Filter/Node.php deleted file mode 100644 index 8bb2899c52..0000000000 --- a/lib/twig/twig/lib/Twig/Filter/Node.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 2.0) - */ -class Twig_Filter_Node extends Twig_Filter -{ - protected $class; - - public function __construct($class, array $options = []) - { - parent::__construct($options); - - $this->class = $class; - } - - public function getClass() - { - return $this->class; - } - - public function compile() - { - } -} diff --git a/lib/twig/twig/lib/Twig/FilterCallableInterface.php b/lib/twig/twig/lib/Twig/FilterCallableInterface.php deleted file mode 100644 index 091ca97454..0000000000 --- a/lib/twig/twig/lib/Twig/FilterCallableInterface.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 2.0) - */ -interface Twig_FilterCallableInterface -{ - public function getCallable(); -} diff --git a/lib/twig/twig/lib/Twig/FilterInterface.php b/lib/twig/twig/lib/Twig/FilterInterface.php deleted file mode 100644 index 9b85f9760d..0000000000 --- a/lib/twig/twig/lib/Twig/FilterInterface.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 2.0) - */ -interface Twig_FilterInterface -{ - /** - * Compiles a filter. - * - * @return string The PHP code for the filter - */ - public function compile(); - - public function needsEnvironment(); - - public function needsContext(); - - public function getSafe(Node $filterArgs); - - public function getPreservesSafety(); - - public function getPreEscape(); - - public function setArguments($arguments); - - public function getArguments(); -} diff --git a/lib/twig/twig/lib/Twig/Function.php b/lib/twig/twig/lib/Twig/Function.php deleted file mode 100644 index 6646e746f5..0000000000 --- a/lib/twig/twig/lib/Twig/Function.php +++ /dev/null @@ -1,76 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 2.0) - */ -abstract class Twig_Function implements Twig_FunctionInterface, Twig_FunctionCallableInterface -{ - protected $options; - protected $arguments = []; - - public function __construct(array $options = []) - { - $this->options = array_merge([ - 'needs_environment' => false, - 'needs_context' => false, - 'callable' => null, - ], $options); - } - - public function setArguments($arguments) - { - $this->arguments = $arguments; - } - - public function getArguments() - { - return $this->arguments; - } - - public function needsEnvironment() - { - return $this->options['needs_environment']; - } - - public function needsContext() - { - return $this->options['needs_context']; - } - - public function getSafe(Node $functionArgs) - { - if (isset($this->options['is_safe'])) { - return $this->options['is_safe']; - } - - if (isset($this->options['is_safe_callback'])) { - return \call_user_func($this->options['is_safe_callback'], $functionArgs); - } - - return []; - } - - public function getCallable() - { - return $this->options['callable']; - } -} diff --git a/lib/twig/twig/lib/Twig/Function/Function.php b/lib/twig/twig/lib/Twig/Function/Function.php deleted file mode 100644 index 605d8d335e..0000000000 --- a/lib/twig/twig/lib/Twig/Function/Function.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 2.0) - */ -class Twig_Function_Function extends Twig_Function -{ - protected $function; - - public function __construct($function, array $options = []) - { - $options['callable'] = $function; - - parent::__construct($options); - - $this->function = $function; - } - - public function compile() - { - return $this->function; - } -} diff --git a/lib/twig/twig/lib/Twig/Function/Method.php b/lib/twig/twig/lib/Twig/Function/Method.php deleted file mode 100644 index 9e472c5d10..0000000000 --- a/lib/twig/twig/lib/Twig/Function/Method.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 2.0) - */ -class Twig_Function_Method extends Twig_Function -{ - protected $extension; - protected $method; - - public function __construct(ExtensionInterface $extension, $method, array $options = []) - { - $options['callable'] = [$extension, $method]; - - parent::__construct($options); - - $this->extension = $extension; - $this->method = $method; - } - - public function compile() - { - return sprintf('$this->env->getExtension(\'%s\')->%s', \get_class($this->extension), $this->method); - } -} diff --git a/lib/twig/twig/lib/Twig/Function/Node.php b/lib/twig/twig/lib/Twig/Function/Node.php deleted file mode 100644 index 8148ec32d7..0000000000 --- a/lib/twig/twig/lib/Twig/Function/Node.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 2.0) - */ -class Twig_Function_Node extends Twig_Function -{ - protected $class; - - public function __construct($class, array $options = []) - { - parent::__construct($options); - - $this->class = $class; - } - - public function getClass() - { - return $this->class; - } - - public function compile() - { - } -} diff --git a/lib/twig/twig/lib/Twig/FunctionCallableInterface.php b/lib/twig/twig/lib/Twig/FunctionCallableInterface.php deleted file mode 100644 index abc83ea4ae..0000000000 --- a/lib/twig/twig/lib/Twig/FunctionCallableInterface.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 2.0) - */ -interface Twig_FunctionCallableInterface -{ - public function getCallable(); -} diff --git a/lib/twig/twig/lib/Twig/FunctionInterface.php b/lib/twig/twig/lib/Twig/FunctionInterface.php deleted file mode 100644 index 915d6cc3a4..0000000000 --- a/lib/twig/twig/lib/Twig/FunctionInterface.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 2.0) - */ -interface Twig_FunctionInterface -{ - /** - * Compiles a function. - * - * @return string The PHP code for the function - */ - public function compile(); - - public function needsEnvironment(); - - public function needsContext(); - - public function getSafe(Node $filterArgs); - - public function setArguments($arguments); - - public function getArguments(); -} diff --git a/lib/twig/twig/lib/Twig/Lexer.php b/lib/twig/twig/lib/Twig/Lexer.php deleted file mode 100644 index 00d74cc47a..0000000000 --- a/lib/twig/twig/lib/Twig/Lexer.php +++ /dev/null @@ -1,11 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 3.0) - */ -interface Twig_LexerInterface -{ - /** - * Tokenizes a source code. - * - * @param string|Source $code The source code - * @param string $name A unique identifier for the source code - * - * @return TokenStream - * - * @throws SyntaxError When the code is syntactically wrong - */ - public function tokenize($code, $name = null); -} diff --git a/lib/twig/twig/lib/Twig/Loader/Array.php b/lib/twig/twig/lib/Twig/Loader/Array.php deleted file mode 100644 index 13f915c95f..0000000000 --- a/lib/twig/twig/lib/Twig/Loader/Array.php +++ /dev/null @@ -1,11 +0,0 @@ - - */ -class Twig_Loader_String implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface -{ - public function getSource($name) - { - @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', \get_class($this)), E_USER_DEPRECATED); - - return $name; - } - - public function getSourceContext($name) - { - return new Source($name, $name); - } - - public function exists($name) - { - return true; - } - - public function getCacheKey($name) - { - return $name; - } - - public function isFresh($name, $time) - { - return true; - } -} diff --git a/lib/twig/twig/lib/Twig/LoaderInterface.php b/lib/twig/twig/lib/Twig/LoaderInterface.php deleted file mode 100644 index db515bbdf4..0000000000 --- a/lib/twig/twig/lib/Twig/LoaderInterface.php +++ /dev/null @@ -1,11 +0,0 @@ - - * - * @deprecated since 1.23 and will be removed in 2.0. - */ -class Twig_Node_Expression_ExtensionReference extends AbstractExpression -{ - public function __construct($name, $lineno, $tag = null) - { - parent::__construct([], ['name' => $name], $lineno, $tag); - } - - public function compile(Compiler $compiler) - { - $compiler->raw(sprintf("\$this->env->getExtension('%s')", $this->getAttribute('name'))); - } -} diff --git a/lib/twig/twig/lib/Twig/Node/Expression/Filter.php b/lib/twig/twig/lib/Twig/Node/Expression/Filter.php deleted file mode 100644 index 85705715e9..0000000000 --- a/lib/twig/twig/lib/Twig/Node/Expression/Filter.php +++ /dev/null @@ -1,11 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 3.0) - */ -interface Twig_NodeInterface extends \Countable, \IteratorAggregate -{ - /** - * Compiles the node to PHP. - */ - public function compile(Compiler $compiler); - - /** - * @deprecated since 1.27 (to be removed in 2.0) - */ - public function getLine(); - - public function getNodeTag(); -} diff --git a/lib/twig/twig/lib/Twig/NodeOutputInterface.php b/lib/twig/twig/lib/Twig/NodeOutputInterface.php deleted file mode 100644 index 54009c0a67..0000000000 --- a/lib/twig/twig/lib/Twig/NodeOutputInterface.php +++ /dev/null @@ -1,11 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 3.0) - */ -interface Twig_ParserInterface -{ - /** - * Converts a token stream to a node tree. - * - * @return ModuleNode - * - * @throws SyntaxError When the token stream is syntactically or semantically wrong - */ - public function parse(TokenStream $stream); -} diff --git a/lib/twig/twig/lib/Twig/Profiler/Dumper/Base.php b/lib/twig/twig/lib/Twig/Profiler/Dumper/Base.php deleted file mode 100644 index 5bcb1ba633..0000000000 --- a/lib/twig/twig/lib/Twig/Profiler/Dumper/Base.php +++ /dev/null @@ -1,11 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 3.0) - */ -interface Twig_TemplateInterface -{ - const ANY_CALL = 'any'; - const ARRAY_CALL = 'array'; - const METHOD_CALL = 'method'; - - /** - * Renders the template with the given context and returns it as string. - * - * @param array $context An array of parameters to pass to the template - * - * @return string The rendered template - */ - public function render(array $context); - - /** - * Displays the template with the given context. - * - * @param array $context An array of parameters to pass to the template - * @param array $blocks An array of blocks to pass to the template - */ - public function display(array $context, array $blocks = []); - - /** - * Returns the bound environment for this template. - * - * @return Environment - */ - public function getEnvironment(); -} diff --git a/lib/twig/twig/lib/Twig/TemplateWrapper.php b/lib/twig/twig/lib/Twig/TemplateWrapper.php deleted file mode 100644 index 6cb4ce4f7e..0000000000 --- a/lib/twig/twig/lib/Twig/TemplateWrapper.php +++ /dev/null @@ -1,11 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 2.0) - */ -abstract class Twig_Test implements Twig_TestInterface, Twig_TestCallableInterface -{ - protected $options; - protected $arguments = []; - - public function __construct(array $options = []) - { - $this->options = array_merge([ - 'callable' => null, - ], $options); - } - - public function getCallable() - { - return $this->options['callable']; - } -} diff --git a/lib/twig/twig/lib/Twig/TestCallableInterface.php b/lib/twig/twig/lib/Twig/TestCallableInterface.php deleted file mode 100644 index 51ecb9a285..0000000000 --- a/lib/twig/twig/lib/Twig/TestCallableInterface.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 2.0) - */ -interface Twig_TestCallableInterface -{ - public function getCallable(); -} diff --git a/lib/twig/twig/lib/Twig/TestInterface.php b/lib/twig/twig/lib/Twig/TestInterface.php deleted file mode 100644 index 91664075a2..0000000000 --- a/lib/twig/twig/lib/Twig/TestInterface.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 2.0) - */ -interface Twig_TestInterface -{ - /** - * Compiles a test. - * - * @return string The PHP code for the test - */ - public function compile(); -} diff --git a/lib/twig/twig/lib/Twig/Token.php b/lib/twig/twig/lib/Twig/Token.php deleted file mode 100644 index e4d18069eb..0000000000 --- a/lib/twig/twig/lib/Twig/Token.php +++ /dev/null @@ -1,11 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 2.0) - */ -class Twig_TokenParserBroker implements Twig_TokenParserBrokerInterface -{ - protected $parser; - protected $parsers = []; - protected $brokers = []; - - /** - * @param array|\Traversable $parsers A \Traversable of Twig_TokenParserInterface instances - * @param array|\Traversable $brokers A \Traversable of Twig_TokenParserBrokerInterface instances - * @param bool $triggerDeprecationError - */ - public function __construct($parsers = [], $brokers = [], $triggerDeprecationError = true) - { - if ($triggerDeprecationError) { - @trigger_error('The '.__CLASS__.' class is deprecated since version 1.12 and will be removed in 2.0.', E_USER_DEPRECATED); - } - - foreach ($parsers as $parser) { - if (!$parser instanceof TokenParserInterface) { - throw new \LogicException('$parsers must a an array of Twig_TokenParserInterface.'); - } - $this->parsers[$parser->getTag()] = $parser; - } - foreach ($brokers as $broker) { - if (!$broker instanceof Twig_TokenParserBrokerInterface) { - throw new \LogicException('$brokers must a an array of Twig_TokenParserBrokerInterface.'); - } - $this->brokers[] = $broker; - } - } - - public function addTokenParser(TokenParserInterface $parser) - { - $this->parsers[$parser->getTag()] = $parser; - } - - public function removeTokenParser(TokenParserInterface $parser) - { - $name = $parser->getTag(); - if (isset($this->parsers[$name]) && $parser === $this->parsers[$name]) { - unset($this->parsers[$name]); - } - } - - public function addTokenParserBroker(self $broker) - { - $this->brokers[] = $broker; - } - - public function removeTokenParserBroker(self $broker) - { - if (false !== $pos = array_search($broker, $this->brokers)) { - unset($this->brokers[$pos]); - } - } - - /** - * Gets a suitable TokenParser for a tag. - * - * First looks in parsers, then in brokers. - * - * @param string $tag A tag name - * - * @return TokenParserInterface|null A Twig_TokenParserInterface or null if no suitable TokenParser was found - */ - public function getTokenParser($tag) - { - if (isset($this->parsers[$tag])) { - return $this->parsers[$tag]; - } - $broker = end($this->brokers); - while (false !== $broker) { - $parser = $broker->getTokenParser($tag); - if (null !== $parser) { - return $parser; - } - $broker = prev($this->brokers); - } - } - - public function getParsers() - { - return $this->parsers; - } - - public function getParser() - { - return $this->parser; - } - - public function setParser(Twig_ParserInterface $parser) - { - $this->parser = $parser; - foreach ($this->parsers as $tokenParser) { - $tokenParser->setParser($parser); - } - foreach ($this->brokers as $broker) { - $broker->setParser($parser); - } - } -} diff --git a/lib/twig/twig/lib/Twig/TokenParserBrokerInterface.php b/lib/twig/twig/lib/Twig/TokenParserBrokerInterface.php deleted file mode 100644 index f369264d9e..0000000000 --- a/lib/twig/twig/lib/Twig/TokenParserBrokerInterface.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 2.0) - */ -interface Twig_TokenParserBrokerInterface -{ - /** - * Gets a TokenParser suitable for a tag. - * - * @param string $tag A tag name - * - * @return TokenParserInterface|null A Twig_TokenParserInterface or null if no suitable TokenParser was found - */ - public function getTokenParser($tag); - - /** - * Calls Twig\TokenParser\TokenParserInterface::setParser on all parsers the implementation knows of. - */ - public function setParser(Twig_ParserInterface $parser); - - /** - * Gets the Twig_ParserInterface. - * - * @return Twig_ParserInterface|null A Twig_ParserInterface instance or null - */ - public function getParser(); -} diff --git a/lib/twig/twig/lib/Twig/TokenParserInterface.php b/lib/twig/twig/lib/Twig/TokenParserInterface.php deleted file mode 100644 index 800c971943..0000000000 --- a/lib/twig/twig/lib/Twig/TokenParserInterface.php +++ /dev/null @@ -1,11 +0,0 @@ -directory = rtrim($directory, '\/').'/'; $this->options = $options; } - public function generateKey($name, $className) + public function generateKey(string $name, string $className): string { - $hash = hash('sha256', $className); + $hash = hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $className); return $this->directory.$hash[0].$hash[1].'/'.$hash.'.php'; } - public function load($key) + public function load(string $key): void { - if (file_exists($key)) { + if (is_file($key)) { @include_once $key; } } - public function write($key, $content) + public function write(string $key, string $content): void { $dir = \dirname($key); if (!is_dir($dir)) { @@ -67,7 +63,7 @@ class FilesystemCache implements CacheInterface if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) { // Compile cached file into bytecode cache - if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) { + if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) { @opcache_invalidate($key, true); } elseif (\function_exists('apc_compile_file')) { apc_compile_file($key); @@ -80,14 +76,12 @@ class FilesystemCache implements CacheInterface throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $key)); } - public function getTimestamp($key) + public function getTimestamp(string $key): int { - if (!file_exists($key)) { + if (!is_file($key)) { return 0; } return (int) @filemtime($key); } } - -class_alias('Twig\Cache\FilesystemCache', 'Twig_Cache_Filesystem'); diff --git a/lib/twig/twig/src/Cache/NullCache.php b/lib/twig/twig/src/Cache/NullCache.php index c1b37c1243..8d20d59d8b 100644 --- a/lib/twig/twig/src/Cache/NullCache.php +++ b/lib/twig/twig/src/Cache/NullCache.php @@ -14,29 +14,25 @@ namespace Twig\Cache; /** * Implements a no-cache strategy. * - * @final - * * @author Fabien Potencier */ -class NullCache implements CacheInterface +final class NullCache implements CacheInterface { - public function generateKey($name, $className) + public function generateKey(string $name, string $className): string { return ''; } - public function write($key, $content) + public function write(string $key, string $content): void { } - public function load($key) + public function load(string $key): void { } - public function getTimestamp($key) + public function getTimestamp(string $key): int { return 0; } } - -class_alias('Twig\Cache\NullCache', 'Twig_Cache_Null'); diff --git a/lib/twig/twig/src/Compiler.php b/lib/twig/twig/src/Compiler.php index e47003ae19..95e1f183b2 100644 --- a/lib/twig/twig/src/Compiler.php +++ b/lib/twig/twig/src/Compiler.php @@ -12,23 +12,20 @@ namespace Twig; -use Twig\Node\ModuleNode; +use Twig\Node\Node; /** - * Compiles a node to PHP code. - * * @author Fabien Potencier */ -class Compiler implements \Twig_CompilerInterface +class Compiler { - protected $lastLine; - protected $source; - protected $indentation; - protected $env; - protected $debugInfo = []; - protected $sourceOffset; - protected $sourceLine; - protected $filename; + private $lastLine; + private $source; + private $indentation; + private $env; + private $debugInfo = []; + private $sourceOffset; + private $sourceLine; private $varNameSalt = 0; public function __construct(Environment $env) @@ -36,44 +33,20 @@ class Compiler implements \Twig_CompilerInterface $this->env = $env; } - /** - * @deprecated since 1.25 (to be removed in 2.0) - */ - public function getFilename() - { - @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); - - return $this->filename; - } - - /** - * Returns the environment instance related to this compiler. - * - * @return Environment - */ - public function getEnvironment() + public function getEnvironment(): Environment { return $this->env; } - /** - * Gets the current PHP code after compilation. - * - * @return string The PHP code - */ - public function getSource() + public function getSource(): string { return $this->source; } /** - * Compiles a node. - * - * @param int $indentation The current indentation - * * @return $this */ - public function compile(\Twig_NodeInterface $node, $indentation = 0) + public function compile(Node $node, int $indentation = 0) { $this->lastLine = null; $this->source = ''; @@ -84,17 +57,15 @@ class Compiler implements \Twig_CompilerInterface $this->indentation = $indentation; $this->varNameSalt = 0; - if ($node instanceof ModuleNode) { - // to be removed in 2.0 - $this->filename = $node->getTemplateName(); - } - $node->compile($this); return $this; } - public function subcompile(\Twig_NodeInterface $node, $raw = true) + /** + * @return $this + */ + public function subcompile(Node $node, bool $raw = true) { if (false === $raw) { $this->source .= str_repeat(' ', $this->indentation * 4); @@ -108,11 +79,9 @@ class Compiler implements \Twig_CompilerInterface /** * Adds a raw string to the compiled code. * - * @param string $string The string - * * @return $this */ - public function raw($string) + public function raw(string $string) { $this->source .= $string; @@ -124,9 +93,8 @@ class Compiler implements \Twig_CompilerInterface * * @return $this */ - public function write() + public function write(...$strings) { - $strings = \func_get_args(); foreach ($strings as $string) { $this->source .= str_repeat(' ', $this->indentation * 4).$string; } @@ -134,30 +102,12 @@ class Compiler implements \Twig_CompilerInterface return $this; } - /** - * Appends an indentation to the current PHP code after compilation. - * - * @return $this - * - * @deprecated since 1.27 (to be removed in 2.0). - */ - public function addIndentation() - { - @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use write(\'\') instead.', E_USER_DEPRECATED); - - $this->source .= str_repeat(' ', $this->indentation * 4); - - return $this; - } - /** * Adds a quoted string to the compiled code. * - * @param string $value The string - * * @return $this */ - public function string($value) + public function string(string $value) { $this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\")); @@ -167,28 +117,26 @@ class Compiler implements \Twig_CompilerInterface /** * Returns a PHP representation of a given value. * - * @param mixed $value The value to convert - * * @return $this */ public function repr($value) { if (\is_int($value) || \is_float($value)) { - if (false !== $locale = setlocale(LC_NUMERIC, '0')) { - setlocale(LC_NUMERIC, 'C'); + if (false !== $locale = setlocale(\LC_NUMERIC, '0')) { + setlocale(\LC_NUMERIC, 'C'); } $this->raw(var_export($value, true)); if (false !== $locale) { - setlocale(LC_NUMERIC, $locale); + setlocale(\LC_NUMERIC, $locale); } } elseif (null === $value) { $this->raw('null'); } elseif (\is_bool($value)) { $this->raw($value ? 'true' : 'false'); } elseif (\is_array($value)) { - $this->raw('['); + $this->raw('array('); $first = true; foreach ($value as $key => $v) { if (!$first) { @@ -199,7 +147,7 @@ class Compiler implements \Twig_CompilerInterface $this->raw(' => '); $this->repr($v); } - $this->raw(']'); + $this->raw(')'); } else { $this->string($value); } @@ -208,26 +156,14 @@ class Compiler implements \Twig_CompilerInterface } /** - * Adds debugging information. - * * @return $this */ - public function addDebugInfo(\Twig_NodeInterface $node) + public function addDebugInfo(Node $node) { if ($node->getTemplateLine() != $this->lastLine) { $this->write(sprintf("// line %d\n", $node->getTemplateLine())); - // when mbstring.func_overload is set to 2 - // mb_substr_count() replaces substr_count() - // but they have different signatures! - if (((int) ini_get('mbstring.func_overload')) & 2) { - @trigger_error('Support for having "mbstring.func_overload" different from 0 is deprecated version 1.29 and will be removed in 2.0.', E_USER_DEPRECATED); - - // this is much slower than the "right" version - $this->sourceLine += mb_substr_count(mb_substr($this->source, $this->sourceOffset), "\n"); - } else { - $this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset); - } + $this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset); $this->sourceOffset = \strlen($this->source); $this->debugInfo[$this->sourceLine] = $node->getTemplateLine(); @@ -237,7 +173,7 @@ class Compiler implements \Twig_CompilerInterface return $this; } - public function getDebugInfo() + public function getDebugInfo(): array { ksort($this->debugInfo); @@ -245,13 +181,9 @@ class Compiler implements \Twig_CompilerInterface } /** - * Indents the generated code. - * - * @param int $step The number of indentation to add - * * @return $this */ - public function indent($step = 1) + public function indent(int $step = 1) { $this->indentation += $step; @@ -259,15 +191,11 @@ class Compiler implements \Twig_CompilerInterface } /** - * Outdents the generated code. - * - * @param int $step The number of indentation to remove - * * @return $this * * @throws \LogicException When trying to outdent too much so the indentation would become negative */ - public function outdent($step = 1) + public function outdent(int $step = 1) { // can't outdent by more steps than the current indentation level if ($this->indentation < $step) { @@ -279,10 +207,8 @@ class Compiler implements \Twig_CompilerInterface return $this; } - public function getVarName() + public function getVarName(): string { - return sprintf('__internal_%s', hash('sha256', __METHOD__.$this->varNameSalt++)); + return sprintf('__internal_compile_%d', $this->varNameSalt++); } } - -class_alias('Twig\Compiler', 'Twig_Compiler'); diff --git a/lib/twig/twig/src/Environment.php b/lib/twig/twig/src/Environment.php index 1f80f3a873..39c659b4f7 100644 --- a/lib/twig/twig/src/Environment.php +++ b/lib/twig/twig/src/Environment.php @@ -21,65 +21,45 @@ use Twig\Error\SyntaxError; use Twig\Extension\CoreExtension; use Twig\Extension\EscaperExtension; use Twig\Extension\ExtensionInterface; -use Twig\Extension\GlobalsInterface; -use Twig\Extension\InitRuntimeInterface; use Twig\Extension\OptimizerExtension; -use Twig\Extension\StagingExtension; use Twig\Loader\ArrayLoader; use Twig\Loader\ChainLoader; use Twig\Loader\LoaderInterface; -use Twig\Loader\SourceContextLoaderInterface; use Twig\Node\ModuleNode; +use Twig\Node\Node; use Twig\NodeVisitor\NodeVisitorInterface; use Twig\RuntimeLoader\RuntimeLoaderInterface; use Twig\TokenParser\TokenParserInterface; /** - * Stores the Twig configuration. + * Stores the Twig configuration and renders templates. * * @author Fabien Potencier */ class Environment { - const VERSION = '1.42.4'; - const VERSION_ID = 14204; - const MAJOR_VERSION = 1; - const MINOR_VERSION = 42; - const RELEASE_VERSION = 4; - const EXTRA_VERSION = ''; - - protected $charset; - protected $loader; - protected $debug; - protected $autoReload; - protected $cache; - protected $lexer; - protected $parser; - protected $compiler; - protected $baseTemplateClass; - protected $extensions; - protected $parsers; - protected $visitors; - protected $filters; - protected $tests; - protected $functions; - protected $globals; - protected $runtimeInitialized = false; - protected $extensionInitialized = false; - protected $loadedTemplates; - protected $strictVariables; - protected $unaryOperators; - protected $binaryOperators; - protected $templateClassPrefix = '__TwigTemplate_'; - protected $functionCallbacks = []; - protected $filterCallbacks = []; - protected $staging; + public const VERSION = '3.4.2'; + public const VERSION_ID = 30402; + public const MAJOR_VERSION = 3; + public const MINOR_VERSION = 4; + public const RELEASE_VERSION = 2; + public const EXTRA_VERSION = ''; + private $charset; + private $loader; + private $debug; + private $autoReload; + private $cache; + private $lexer; + private $parser; + private $compiler; + private $globals = []; + private $resolvedGlobals; + private $loadedTemplates; + private $strictVariables; + private $templateClassPrefix = '__TwigTemplate_'; private $originalCache; - private $bcWriteCacheFile = false; - private $bcGetCacheFilename = false; - private $lastModifiedExtension = 0; - private $extensionsByClass = []; + private $extensionSet; private $runtimeLoaders = []; private $runtimes = []; private $optionsHash; @@ -94,9 +74,6 @@ class Environment * * * charset: The charset used by the templates (default to UTF-8). * - * * base_template_class: The base template class to use for generated - * templates (default to \Twig\Template). - * * * cache: An absolute path where to store the compiled templates, * a \Twig\Cache\CacheInterface implementation, * or false to disable compilation cache (default). @@ -110,7 +87,6 @@ class Environment * * * autoescape: Whether to enable auto-escaping (default to html): * * false: disable auto-escaping - * * true: equivalent to html * * html, js: set the autoescaping to one of the supported strategies * * name: set the autoescaping strategy based on the template name extension * * PHP callback: a PHP callback that returns an escaping strategy based on the template "name" @@ -119,18 +95,13 @@ class Environment * (default to -1 which means that all optimizations are enabled; * set it to 0 to disable). */ - public function __construct(LoaderInterface $loader = null, $options = []) + public function __construct(LoaderInterface $loader, $options = []) { - if (null !== $loader) { - $this->setLoader($loader); - } else { - @trigger_error('Not passing a "Twig\Lodaer\LoaderInterface" as the first constructor argument of "Twig\Environment" is deprecated since version 1.21.', E_USER_DEPRECATED); - } + $this->setLoader($loader); $options = array_merge([ 'debug' => false, 'charset' => 'UTF-8', - 'base_template_class' => '\Twig\Template', 'strict_variables' => false, 'autoescape' => 'html', 'cache' => false, @@ -139,54 +110,15 @@ class Environment ], $options); $this->debug = (bool) $options['debug']; - $this->charset = strtoupper($options['charset']); - $this->baseTemplateClass = $options['base_template_class']; + $this->setCharset($options['charset'] ?? 'UTF-8'); $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload']; $this->strictVariables = (bool) $options['strict_variables']; $this->setCache($options['cache']); + $this->extensionSet = new ExtensionSet(); $this->addExtension(new CoreExtension()); $this->addExtension(new EscaperExtension($options['autoescape'])); $this->addExtension(new OptimizerExtension($options['optimizations'])); - $this->staging = new StagingExtension(); - - // For BC - if (\is_string($this->originalCache)) { - $r = new \ReflectionMethod($this, 'writeCacheFile'); - if (__CLASS__ !== $r->getDeclaringClass()->getName()) { - @trigger_error('The Twig\Environment::writeCacheFile method is deprecated since version 1.22 and will be removed in Twig 2.0.', E_USER_DEPRECATED); - - $this->bcWriteCacheFile = true; - } - - $r = new \ReflectionMethod($this, 'getCacheFilename'); - if (__CLASS__ !== $r->getDeclaringClass()->getName()) { - @trigger_error('The Twig\Environment::getCacheFilename method is deprecated since version 1.22 and will be removed in Twig 2.0.', E_USER_DEPRECATED); - - $this->bcGetCacheFilename = true; - } - } - } - - /** - * Gets the base template class for compiled templates. - * - * @return string The base template class name - */ - public function getBaseTemplateClass() - { - return $this->baseTemplateClass; - } - - /** - * Sets the base template class for compiled templates. - * - * @param string $class The base template class name - */ - public function setBaseTemplateClass($class) - { - $this->baseTemplateClass = $class; - $this->updateOptionsHash(); } /** @@ -296,39 +228,17 @@ class Environment { if (\is_string($cache)) { $this->originalCache = $cache; - $this->cache = new FilesystemCache($cache); + $this->cache = new FilesystemCache($cache, $this->autoReload ? FilesystemCache::FORCE_BYTECODE_INVALIDATION : 0); } elseif (false === $cache) { $this->originalCache = $cache; $this->cache = new NullCache(); - } elseif (null === $cache) { - @trigger_error('Using "null" as the cache strategy is deprecated since version 1.23 and will be removed in Twig 2.0.', E_USER_DEPRECATED); - $this->originalCache = false; - $this->cache = new NullCache(); } elseif ($cache instanceof CacheInterface) { $this->originalCache = $this->cache = $cache; } else { - throw new \LogicException(sprintf('Cache can only be a string, false, or a \Twig\Cache\CacheInterface implementation.')); + throw new \LogicException('Cache can only be a string, false, or a \Twig\Cache\CacheInterface implementation.'); } } - /** - * Gets the cache filename for a given template. - * - * @param string $name The template name - * - * @return string|false The cache file name or false when caching is disabled - * - * @deprecated since 1.22 (to be removed in 2.0) - */ - public function getCacheFilename($name) - { - @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); - - $key = $this->cache->generateKey($name, $this->getTemplateClass($name)); - - return !$key ? false : $key; - } - /** * Gets the template class associated with the given string. * @@ -344,42 +254,25 @@ class Environment * @param string $name The name for which to calculate the template class name * @param int|null $index The index if it is an embedded template * - * @return string The template class name + * @internal */ - public function getTemplateClass($name, $index = null) + public function getTemplateClass(string $name, int $index = null): string { $key = $this->getLoader()->getCacheKey($name).$this->optionsHash; - return $this->templateClassPrefix.hash('sha256', $key).(null === $index ? '' : '___'.$index); - } - - /** - * Gets the template class prefix. - * - * @return string The template class prefix - * - * @deprecated since 1.22 (to be removed in 2.0) - */ - public function getTemplateClassPrefix() - { - @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); - - return $this->templateClassPrefix; + return $this->templateClassPrefix.hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $key).(null === $index ? '' : '___'.$index); } /** * Renders a template. * - * @param string|TemplateWrapper $name The template name - * @param array $context An array of parameters to pass to the template - * - * @return string The rendered template + * @param string|TemplateWrapper $name The template name * * @throws LoaderError When the template cannot be found * @throws SyntaxError When an error occurred during compilation * @throws RuntimeError When an error occurred during rendering */ - public function render($name, array $context = []) + public function render($name, array $context = []): string { return $this->load($name)->render($context); } @@ -387,14 +280,13 @@ class Environment /** * Displays a template. * - * @param string|TemplateWrapper $name The template name - * @param array $context An array of parameters to pass to the template + * @param string|TemplateWrapper $name The template name * * @throws LoaderError When the template cannot be found * @throws SyntaxError When an error occurred during compilation * @throws RuntimeError When an error occurred during rendering */ - public function display($name, array $context = []) + public function display($name, array $context = []): void { $this->load($name)->display($context); } @@ -402,25 +294,19 @@ class Environment /** * Loads a template. * - * @param string|TemplateWrapper|\Twig\Template $name The template name + * @param string|TemplateWrapper $name The template name * * @throws LoaderError When the template cannot be found * @throws RuntimeError When a previously generated cache is corrupted * @throws SyntaxError When an error occurred during compilation - * - * @return TemplateWrapper */ - public function load($name) + public function load($name): TemplateWrapper { if ($name instanceof TemplateWrapper) { return $name; } - if ($name instanceof Template) { - return new TemplateWrapper($this, $name); - } - - return new TemplateWrapper($this, $this->loadTemplate($name)); + return new TemplateWrapper($this, $this->loadTemplate($this->getTemplateClass($name), $name)); } /** @@ -432,23 +318,13 @@ class Environment * @param string $name The template name * @param int $index The index if it is an embedded template * - * @return \Twig_TemplateInterface A template instance representing the given template name - * * @throws LoaderError When the template cannot be found * @throws RuntimeError When a previously generated cache is corrupted * @throws SyntaxError When an error occurred during compilation * * @internal */ - public function loadTemplate($name, $index = null) - { - return $this->loadClass($this->getTemplateClass($name), $name, $index); - } - - /** - * @internal - */ - public function loadClass($cls, $name, $index = null) + public function loadTemplate(string $cls, string $name, int $index = null): Template { $mainCls = $cls; if (null !== $index) { @@ -460,11 +336,7 @@ class Environment } if (!class_exists($cls, false)) { - if ($this->bcGetCacheFilename) { - $key = $this->getCacheFilename($name); - } else { - $key = $this->cache->generateKey($name, $mainCls); - } + $key = $this->cache->generateKey($name, $mainCls); if (!$this->isAutoReload() || $this->isTemplateFresh($name, $this->cache->getTimestamp($key))) { $this->cache->load($key); @@ -472,21 +344,10 @@ class Environment $source = null; if (!class_exists($cls, false)) { - $loader = $this->getLoader(); - if (!$loader instanceof SourceContextLoaderInterface) { - $source = new Source($loader->getSource($name), $name); - } else { - $source = $loader->getSourceContext($name); - } - + $source = $this->getLoader()->getSourceContext($name); $content = $this->compileSource($source); - - if ($this->bcWriteCacheFile) { - $this->writeCacheFile($key, $content); - } else { - $this->cache->write($key, $content); - $this->cache->load($key); - } + $this->cache->write($key, $content); + $this->cache->load($key); if (!class_exists($mainCls, false)) { /* Last line of defense if either $this->bcWriteCacheFile was used, @@ -496,16 +357,14 @@ class Environment */ eval('?>'.$content); } - } - if (!class_exists($cls, false)) { - throw new RuntimeError(sprintf('Failed to load Twig template "%s", index "%s": cache might be corrupted.', $name, $index), -1, $source); + if (!class_exists($cls, false)) { + throw new RuntimeError(sprintf('Failed to load Twig template "%s", index "%s": cache might be corrupted.', $name, $index), -1, $source); + } } } - if (!$this->runtimeInitialized) { - $this->initRuntime(); - } + $this->extensionSet->initRuntime(); return $this->loadedTemplates[$cls] = new $cls($this); } @@ -518,14 +377,12 @@ class Environment * @param string $template The template source * @param string $name An optional name of the template to be used in error messages * - * @return TemplateWrapper A template instance representing the given template name - * * @throws LoaderError When the template cannot be found * @throws SyntaxError When an error occurred during compilation */ - public function createTemplate($template, $name = null) + public function createTemplate(string $template, string $name = null): TemplateWrapper { - $hash = hash('sha256', $template, false); + $hash = hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $template, false); if (null !== $name) { $name = sprintf('%s (string template %s)', $name, $hash); } else { @@ -539,19 +396,10 @@ class Environment $this->setLoader($loader); try { - $template = new TemplateWrapper($this, $this->loadTemplate($name)); - } catch (\Exception $e) { + return new TemplateWrapper($this, $this->loadTemplate($this->getTemplateClass($name), $name)); + } finally { $this->setLoader($current); - - throw $e; - } catch (\Throwable $e) { - $this->setLoader($current); - - throw $e; } - $this->setLoader($current); - - return $template; } /** @@ -561,23 +409,11 @@ class Environment * this method also checks if the enabled extensions have * not changed. * - * @param string $name The template name - * @param int $time The last modification time of the cached template - * - * @return bool true if the template is fresh, false otherwise + * @param int $time The last modification time of the cached template */ - public function isTemplateFresh($name, $time) + public function isTemplateFresh(string $name, int $time): bool { - if (0 === $this->lastModifiedExtension) { - foreach ($this->extensions as $extension) { - $r = new \ReflectionObject($extension); - if (file_exists($r->getFileName()) && ($extensionTime = filemtime($r->getFileName())) > $this->lastModifiedExtension) { - $this->lastModifiedExtension = $extensionTime; - } - } - } - - return $this->lastModifiedExtension <= $time && $this->getLoader()->isFresh($name, $time); + return $this->extensionSet->getLastModified() <= $time && $this->getLoader()->isFresh($name, $time); } /** @@ -586,19 +422,18 @@ class Environment * Similar to load() but it also accepts instances of \Twig\Template and * \Twig\TemplateWrapper, and an array of templates where each is tried to be loaded. * - * @param string|Template|\Twig\TemplateWrapper|array $names A template or an array of templates to try consecutively - * - * @return TemplateWrapper|Template + * @param string|TemplateWrapper|array $names A template or an array of templates to try consecutively * * @throws LoaderError When none of the templates can be found * @throws SyntaxError When an error occurred during compilation */ - public function resolveTemplate($names) + public function resolveTemplate($names): TemplateWrapper { if (!\is_array($names)) { - $names = [$names]; + return $this->load($names); } + $count = \count($names); foreach ($names as $name) { if ($name instanceof Template) { return $name; @@ -607,88 +442,26 @@ class Environment return $name; } - try { - return $this->loadTemplate($name); - } catch (LoaderError $e) { - if (1 === \count($names)) { - throw $e; - } + if (1 !== $count && !$this->getLoader()->exists($name)) { + continue; } + + return $this->load($name); } throw new LoaderError(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names))); } - /** - * Clears the internal template cache. - * - * @deprecated since 1.18.3 (to be removed in 2.0) - */ - public function clearTemplateCache() - { - @trigger_error(sprintf('The %s method is deprecated since version 1.18.3 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); - - $this->loadedTemplates = []; - } - - /** - * Clears the template cache files on the filesystem. - * - * @deprecated since 1.22 (to be removed in 2.0) - */ - public function clearCacheFiles() - { - @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); - - if (\is_string($this->originalCache)) { - foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->originalCache), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { - if ($file->isFile()) { - @unlink($file->getPathname()); - } - } - } - } - - /** - * Gets the Lexer instance. - * - * @return \Twig_LexerInterface - * - * @deprecated since 1.25 (to be removed in 2.0) - */ - public function getLexer() - { - @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); - - if (null === $this->lexer) { - $this->lexer = new Lexer($this); - } - - return $this->lexer; - } - - public function setLexer(\Twig_LexerInterface $lexer) + public function setLexer(Lexer $lexer) { $this->lexer = $lexer; } /** - * Tokenizes a source code. - * - * @param string|Source $source The template source code - * @param string $name The template name (deprecated) - * - * @return TokenStream - * * @throws SyntaxError When the code is syntactically wrong */ - public function tokenize($source, $name = null) + public function tokenize(Source $source): TokenStream { - if (!$source instanceof Source) { - @trigger_error(sprintf('Passing a string as the $source argument of %s() is deprecated since version 1.27. Pass a Twig\Source instance instead.', __METHOD__), E_USER_DEPRECATED); - $source = new Source($source, $name); - } - if (null === $this->lexer) { $this->lexer = new Lexer($this); } @@ -696,25 +469,7 @@ class Environment return $this->lexer->tokenize($source); } - /** - * Gets the Parser instance. - * - * @return \Twig_ParserInterface - * - * @deprecated since 1.25 (to be removed in 2.0) - */ - public function getParser() - { - @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); - - if (null === $this->parser) { - $this->parser = new Parser($this); - } - - return $this->parser; - } - - public function setParser(\Twig_ParserInterface $parser) + public function setParser(Parser $parser) { $this->parser = $parser; } @@ -722,11 +477,9 @@ class Environment /** * Converts a token stream to a node tree. * - * @return ModuleNode - * * @throws SyntaxError When the token stream is syntactically or semantically wrong */ - public function parse(TokenStream $stream) + public function parse(TokenStream $stream): ModuleNode { if (null === $this->parser) { $this->parser = new Parser($this); @@ -735,35 +488,15 @@ class Environment return $this->parser->parse($stream); } - /** - * Gets the Compiler instance. - * - * @return \Twig_CompilerInterface - * - * @deprecated since 1.25 (to be removed in 2.0) - */ - public function getCompiler() - { - @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); - - if (null === $this->compiler) { - $this->compiler = new Compiler($this); - } - - return $this->compiler; - } - - public function setCompiler(\Twig_CompilerInterface $compiler) + public function setCompiler(Compiler $compiler) { $this->compiler = $compiler; } /** * Compiles a node and returns the PHP code. - * - * @return string The compiled PHP source code */ - public function compile(\Twig_NodeInterface $node) + public function compile(Node $node): string { if (null === $this->compiler) { $this->compiler = new Compiler($this); @@ -775,20 +508,10 @@ class Environment /** * Compiles a template source code. * - * @param string|Source $source The template source code - * @param string $name The template name (deprecated) - * - * @return string The compiled PHP source code - * * @throws SyntaxError When there was an error during tokenizing, parsing or compiling */ - public function compileSource($source, $name = null) + public function compileSource(Source $source): string { - if (!$source instanceof Source) { - @trigger_error(sprintf('Passing a string as the $source argument of %s() is deprecated since version 1.27. Pass a Twig\Source instance instead.', __METHOD__), E_USER_DEPRECATED); - $source = new Source($source, $name); - } - try { return $this->compile($this->parse($this->tokenize($source))); } catch (Error $e) { @@ -801,146 +524,63 @@ class Environment public function setLoader(LoaderInterface $loader) { - if (!$loader instanceof SourceContextLoaderInterface && 0 !== strpos(\get_class($loader), 'Mock_')) { - @trigger_error(sprintf('Twig loader "%s" should implement Twig\Loader\SourceContextLoaderInterface since version 1.27.', \get_class($loader)), E_USER_DEPRECATED); - } - $this->loader = $loader; } - /** - * Gets the Loader instance. - * - * @return LoaderInterface - */ - public function getLoader() + public function getLoader(): LoaderInterface { - if (null === $this->loader) { - throw new \LogicException('You must set a loader first.'); - } - return $this->loader; } - /** - * Sets the default template charset. - * - * @param string $charset The default charset - */ - public function setCharset($charset) + public function setCharset(string $charset) { - $this->charset = strtoupper($charset); + if ('UTF8' === $charset = null === $charset ? null : strtoupper($charset)) { + // iconv on Windows requires "UTF-8" instead of "UTF8" + $charset = 'UTF-8'; + } + + $this->charset = $charset; } - /** - * Gets the default template charset. - * - * @return string The default charset - */ - public function getCharset() + public function getCharset(): string { return $this->charset; } - /** - * Initializes the runtime environment. - * - * @deprecated since 1.23 (to be removed in 2.0) - */ - public function initRuntime() + public function hasExtension(string $class): bool { - $this->runtimeInitialized = true; - - foreach ($this->getExtensions() as $name => $extension) { - if (!$extension instanceof InitRuntimeInterface) { - $m = new \ReflectionMethod($extension, 'initRuntime'); - - $parentClass = $m->getDeclaringClass()->getName(); - if ('Twig_Extension' !== $parentClass && 'Twig\Extension\AbstractExtension' !== $parentClass) { - @trigger_error(sprintf('Defining the initRuntime() method in the "%s" extension is deprecated since version 1.23. Use the `needs_environment` option to get the \Twig_Environment instance in filters, functions, or tests; or explicitly implement Twig\Extension\InitRuntimeInterface if needed (not recommended).', $name), E_USER_DEPRECATED); - } - } - - $extension->initRuntime($this); - } + return $this->extensionSet->hasExtension($class); } - /** - * Returns true if the given extension is registered. - * - * @param string $class The extension class name - * - * @return bool Whether the extension is registered or not - */ - public function hasExtension($class) - { - $class = ltrim($class, '\\'); - if (!isset($this->extensionsByClass[$class]) && class_exists($class, false)) { - // For BC/FC with namespaced aliases - $class = new \ReflectionClass($class); - $class = $class->name; - } - - if (isset($this->extensions[$class])) { - if ($class !== \get_class($this->extensions[$class])) { - @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); - } - - return true; - } - - return isset($this->extensionsByClass[$class]); - } - - /** - * Adds a runtime loader. - */ public function addRuntimeLoader(RuntimeLoaderInterface $loader) { $this->runtimeLoaders[] = $loader; } /** - * Gets an extension by class name. + * @template TExtension of ExtensionInterface * - * @param string $class The extension class name + * @param class-string $class * - * @return ExtensionInterface + * @return TExtension */ - public function getExtension($class) + public function getExtension(string $class): ExtensionInterface { - $class = ltrim($class, '\\'); - if (!isset($this->extensionsByClass[$class]) && class_exists($class, false)) { - // For BC/FC with namespaced aliases - $class = new \ReflectionClass($class); - $class = $class->name; - } - - if (isset($this->extensions[$class])) { - if ($class !== \get_class($this->extensions[$class])) { - @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); - } - - return $this->extensions[$class]; - } - - if (!isset($this->extensionsByClass[$class])) { - throw new RuntimeError(sprintf('The "%s" extension is not enabled.', $class)); - } - - return $this->extensionsByClass[$class]; + return $this->extensionSet->getExtension($class); } /** - * Returns the runtime implementation of a Twig element (filter/function/test). + * Returns the runtime implementation of a Twig element (filter/function/tag/test). * - * @param string $class A runtime class name + * @template TRuntime of object * - * @return object The runtime implementation + * @param class-string $class A runtime class name + * + * @return TRuntime The runtime implementation * * @throws RuntimeError When the template cannot be found */ - public function getRuntime($class) + public function getRuntime(string $class) { if (isset($this->runtimes[$class])) { return $this->runtimes[$class]; @@ -957,226 +597,86 @@ class Environment public function addExtension(ExtensionInterface $extension) { - if ($this->extensionInitialized) { - throw new \LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $extension->getName())); - } - - $class = \get_class($extension); - if ($class !== $extension->getName()) { - if (isset($this->extensions[$extension->getName()])) { - unset($this->extensions[$extension->getName()], $this->extensionsByClass[$class]); - @trigger_error(sprintf('The possibility to register the same extension twice ("%s") is deprecated since version 1.23 and will be removed in Twig 2.0. Use proper PHP inheritance instead.', $extension->getName()), E_USER_DEPRECATED); - } - } - - $this->lastModifiedExtension = 0; - $this->extensionsByClass[$class] = $extension; - $this->extensions[$extension->getName()] = $extension; + $this->extensionSet->addExtension($extension); $this->updateOptionsHash(); } /** - * Removes an extension by name. - * - * This method is deprecated and you should not use it. - * - * @param string $name The extension name - * - * @deprecated since 1.12 (to be removed in 2.0) - */ - public function removeExtension($name) - { - @trigger_error(sprintf('The %s method is deprecated since version 1.12 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); - - if ($this->extensionInitialized) { - throw new \LogicException(sprintf('Unable to remove extension "%s" as extensions have already been initialized.', $name)); - } - - $class = ltrim($name, '\\'); - if (!isset($this->extensionsByClass[$class]) && class_exists($class, false)) { - // For BC/FC with namespaced aliases - $class = new \ReflectionClass($class); - $class = $class->name; - } - - if (isset($this->extensions[$class])) { - if ($class !== \get_class($this->extensions[$class])) { - @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); - } - - unset($this->extensions[$class]); - } - - unset($this->extensions[$class]); - $this->updateOptionsHash(); - } - - /** - * Registers an array of extensions. - * - * @param array $extensions An array of extensions + * @param ExtensionInterface[] $extensions An array of extensions */ public function setExtensions(array $extensions) { - foreach ($extensions as $extension) { - $this->addExtension($extension); - } + $this->extensionSet->setExtensions($extensions); + $this->updateOptionsHash(); } /** - * Returns all registered extensions. - * * @return ExtensionInterface[] An array of extensions (keys are for internal usage only and should not be relied on) */ - public function getExtensions() + public function getExtensions(): array { - return $this->extensions; + return $this->extensionSet->getExtensions(); } public function addTokenParser(TokenParserInterface $parser) { - if ($this->extensionInitialized) { - throw new \LogicException('Unable to add a token parser as extensions have already been initialized.'); - } - - $this->staging->addTokenParser($parser); + $this->extensionSet->addTokenParser($parser); } /** - * Gets the registered Token Parsers. - * - * @return \Twig_TokenParserBrokerInterface - * - * @internal - */ - public function getTokenParsers() - { - if (!$this->extensionInitialized) { - $this->initExtensions(); - } - - return $this->parsers; - } - - /** - * Gets registered tags. - * - * Be warned that this method cannot return tags defined by \Twig_TokenParserBrokerInterface classes. - * * @return TokenParserInterface[] * * @internal */ - public function getTags() + public function getTokenParsers(): array { - $tags = []; - foreach ($this->getTokenParsers()->getParsers() as $parser) { - if ($parser instanceof TokenParserInterface) { - $tags[$parser->getTag()] = $parser; - } - } + return $this->extensionSet->getTokenParsers(); + } - return $tags; + /** + * @internal + */ + public function getTokenParser(string $name): ?TokenParserInterface + { + return $this->extensionSet->getTokenParser($name); + } + + public function registerUndefinedTokenParserCallback(callable $callable): void + { + $this->extensionSet->registerUndefinedTokenParserCallback($callable); } public function addNodeVisitor(NodeVisitorInterface $visitor) { - if ($this->extensionInitialized) { - throw new \LogicException('Unable to add a node visitor as extensions have already been initialized.'); - } - - $this->staging->addNodeVisitor($visitor); + $this->extensionSet->addNodeVisitor($visitor); } /** - * Gets the registered Node Visitors. - * * @return NodeVisitorInterface[] * * @internal */ - public function getNodeVisitors() + public function getNodeVisitors(): array { - if (!$this->extensionInitialized) { - $this->initExtensions(); - } + return $this->extensionSet->getNodeVisitors(); + } - return $this->visitors; + public function addFilter(TwigFilter $filter) + { + $this->extensionSet->addFilter($filter); } /** - * Registers a Filter. - * - * @param string|TwigFilter $name The filter name or a \Twig_SimpleFilter instance - * @param \Twig_FilterInterface|TwigFilter $filter - */ - public function addFilter($name, $filter = null) - { - if (!$name instanceof TwigFilter && !($filter instanceof TwigFilter || $filter instanceof \Twig_FilterInterface)) { - throw new \LogicException('A filter must be an instance of \Twig_FilterInterface or \Twig_SimpleFilter.'); - } - - if ($name instanceof TwigFilter) { - $filter = $name; - $name = $filter->getName(); - } else { - @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleFilter" instead when defining filter "%s".', __METHOD__, $name), E_USER_DEPRECATED); - } - - if ($this->extensionInitialized) { - throw new \LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $name)); - } - - $this->staging->addFilter($name, $filter); - } - - /** - * Get a filter by name. - * - * Subclasses may override this method and load filters differently; - * so no list of filters is available. - * - * @param string $name The filter name - * - * @return \Twig_Filter|false - * * @internal */ - public function getFilter($name) + public function getFilter(string $name): ?TwigFilter { - if (!$this->extensionInitialized) { - $this->initExtensions(); - } - - if (isset($this->filters[$name])) { - return $this->filters[$name]; - } - - foreach ($this->filters as $pattern => $filter) { - $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); - - if ($count) { - if (preg_match('#^'.$pattern.'$#', $name, $matches)) { - array_shift($matches); - $filter->setArguments($matches); - - return $filter; - } - } - } - - foreach ($this->filterCallbacks as $callback) { - if (false !== $filter = \call_user_func($callback, $name)) { - return $filter; - } - } - - return false; + return $this->extensionSet->getFilter($name); } - public function registerUndefinedFilterCallback($callable) + public function registerUndefinedFilterCallback(callable $callable): void { - $this->filterCallbacks[] = $callable; + $this->extensionSet->registerUndefinedFilterCallback($callable); } /** @@ -1184,171 +684,56 @@ class Environment * * Be warned that this method cannot return filters defined with registerUndefinedFilterCallback. * - * @return \Twig_FilterInterface[] + * @return TwigFilter[] * * @see registerUndefinedFilterCallback * * @internal */ - public function getFilters() + public function getFilters(): array { - if (!$this->extensionInitialized) { - $this->initExtensions(); - } + return $this->extensionSet->getFilters(); + } - return $this->filters; + public function addTest(TwigTest $test) + { + $this->extensionSet->addTest($test); } /** - * Registers a Test. - * - * @param string|TwigTest $name The test name or a \Twig_SimpleTest instance - * @param \Twig_TestInterface|TwigTest $test A \Twig_TestInterface instance or a \Twig_SimpleTest instance - */ - public function addTest($name, $test = null) - { - if (!$name instanceof TwigTest && !($test instanceof TwigTest || $test instanceof \Twig_TestInterface)) { - throw new \LogicException('A test must be an instance of \Twig_TestInterface or \Twig_SimpleTest.'); - } - - if ($name instanceof TwigTest) { - $test = $name; - $name = $test->getName(); - } else { - @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleTest" instead when defining test "%s".', __METHOD__, $name), E_USER_DEPRECATED); - } - - if ($this->extensionInitialized) { - throw new \LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $name)); - } - - $this->staging->addTest($name, $test); - } - - /** - * Gets the registered Tests. - * - * @return \Twig_TestInterface[] + * @return TwigTest[] * * @internal */ - public function getTests() + public function getTests(): array { - if (!$this->extensionInitialized) { - $this->initExtensions(); - } - - return $this->tests; + return $this->extensionSet->getTests(); } /** - * Gets a test by name. - * - * @param string $name The test name - * - * @return \Twig_Test|false - * * @internal */ - public function getTest($name) + public function getTest(string $name): ?TwigTest { - if (!$this->extensionInitialized) { - $this->initExtensions(); - } + return $this->extensionSet->getTest($name); + } - if (isset($this->tests[$name])) { - return $this->tests[$name]; - } - - foreach ($this->tests as $pattern => $test) { - $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); - - if ($count) { - if (preg_match('#^'.$pattern.'$#', $name, $matches)) { - array_shift($matches); - $test->setArguments($matches); - - return $test; - } - } - } - - return false; + public function addFunction(TwigFunction $function) + { + $this->extensionSet->addFunction($function); } /** - * Registers a Function. - * - * @param string|TwigFunction $name The function name or a \Twig_SimpleFunction instance - * @param \Twig_FunctionInterface|TwigFunction $function - */ - public function addFunction($name, $function = null) - { - if (!$name instanceof TwigFunction && !($function instanceof TwigFunction || $function instanceof \Twig_FunctionInterface)) { - throw new \LogicException('A function must be an instance of \Twig_FunctionInterface or \Twig_SimpleFunction.'); - } - - if ($name instanceof TwigFunction) { - $function = $name; - $name = $function->getName(); - } else { - @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleFunction" instead when defining function "%s".', __METHOD__, $name), E_USER_DEPRECATED); - } - - if ($this->extensionInitialized) { - throw new \LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $name)); - } - - $this->staging->addFunction($name, $function); - } - - /** - * Get a function by name. - * - * Subclasses may override this method and load functions differently; - * so no list of functions is available. - * - * @param string $name function name - * - * @return \Twig_Function|false - * * @internal */ - public function getFunction($name) + public function getFunction(string $name): ?TwigFunction { - if (!$this->extensionInitialized) { - $this->initExtensions(); - } - - if (isset($this->functions[$name])) { - return $this->functions[$name]; - } - - foreach ($this->functions as $pattern => $function) { - $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); - - if ($count) { - if (preg_match('#^'.$pattern.'$#', $name, $matches)) { - array_shift($matches); - $function->setArguments($matches); - - return $function; - } - } - } - - foreach ($this->functionCallbacks as $callback) { - if (false !== $function = \call_user_func($callback, $name)) { - return $function; - } - } - - return false; + return $this->extensionSet->getFunction($name); } - public function registerUndefinedFunctionCallback($callable) + public function registerUndefinedFunctionCallback(callable $callable): void { - $this->functionCallbacks[] = $callable; + $this->extensionSet->registerUndefinedFunctionCallback($callable); } /** @@ -1356,19 +741,15 @@ class Environment * * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback. * - * @return \Twig_FunctionInterface[] + * @return TwigFunction[] * * @see registerUndefinedFunctionCallback * * @internal */ - public function getFunctions() + public function getFunctions(): array { - if (!$this->extensionInitialized) { - $this->initExtensions(); - } - - return $this->functions; + return $this->extensionSet->getFunctions(); } /** @@ -1377,59 +758,38 @@ class Environment * New globals can be added before compiling or rendering a template; * but after, you can only update existing globals. * - * @param string $name The global name - * @param mixed $value The global value + * @param mixed $value The global value */ - public function addGlobal($name, $value) + public function addGlobal(string $name, $value) { - if ($this->extensionInitialized || $this->runtimeInitialized) { - if (null === $this->globals) { - $this->globals = $this->initGlobals(); - } - - if (!\array_key_exists($name, $this->globals)) { - // The deprecation notice must be turned into the following exception in Twig 2.0 - @trigger_error(sprintf('Registering global variable "%s" at runtime or when the extensions have already been initialized is deprecated since version 1.21.', $name), E_USER_DEPRECATED); - //throw new \LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name)); - } + if ($this->extensionSet->isInitialized() && !\array_key_exists($name, $this->getGlobals())) { + throw new \LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name)); } - if ($this->extensionInitialized || $this->runtimeInitialized) { - // update the value - $this->globals[$name] = $value; + if (null !== $this->resolvedGlobals) { + $this->resolvedGlobals[$name] = $value; } else { - $this->staging->addGlobal($name, $value); + $this->globals[$name] = $value; } } /** - * Gets the registered Globals. - * - * @return array An array of globals - * * @internal */ - public function getGlobals() + public function getGlobals(): array { - if (!$this->runtimeInitialized && !$this->extensionInitialized) { - return $this->initGlobals(); + if ($this->extensionSet->isInitialized()) { + if (null === $this->resolvedGlobals) { + $this->resolvedGlobals = array_merge($this->extensionSet->getGlobals(), $this->globals); + } + + return $this->resolvedGlobals; } - if (null === $this->globals) { - $this->globals = $this->initGlobals(); - } - - return $this->globals; + return array_merge($this->extensionSet->getGlobals(), $this->globals); } - /** - * Merges a context with the defined globals. - * - * @param array $context An array representing the context - * - * @return array The context merged with the globals - */ - public function mergeGlobals(array $context) + public function mergeGlobals(array $context): array { // we don't use array_merge as the context being generally // bigger than globals, this code is faster. @@ -1443,196 +803,30 @@ class Environment } /** - * Gets the registered unary Operators. - * - * @return array An array of unary operators - * * @internal */ - public function getUnaryOperators() + public function getUnaryOperators(): array { - if (!$this->extensionInitialized) { - $this->initExtensions(); - } - - return $this->unaryOperators; - } - - /** - * Gets the registered binary Operators. - * - * @return array An array of binary operators - * - * @internal - */ - public function getBinaryOperators() - { - if (!$this->extensionInitialized) { - $this->initExtensions(); - } - - return $this->binaryOperators; - } - - /** - * @deprecated since 1.23 (to be removed in 2.0) - */ - public function computeAlternatives($name, $items) - { - @trigger_error(sprintf('The %s method is deprecated since version 1.23 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); - - return SyntaxError::computeAlternatives($name, $items); + return $this->extensionSet->getUnaryOperators(); } /** * @internal */ - protected function initGlobals() + public function getBinaryOperators(): array { - $globals = []; - foreach ($this->extensions as $name => $extension) { - if (!$extension instanceof GlobalsInterface) { - $m = new \ReflectionMethod($extension, 'getGlobals'); - - $parentClass = $m->getDeclaringClass()->getName(); - if ('Twig_Extension' !== $parentClass && 'Twig\Extension\AbstractExtension' !== $parentClass) { - @trigger_error(sprintf('Defining the getGlobals() method in the "%s" extension without explicitly implementing Twig\Extension\GlobalsInterface is deprecated since version 1.23.', $name), E_USER_DEPRECATED); - } - } - - $extGlob = $extension->getGlobals(); - if (!\is_array($extGlob)) { - throw new \UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', \get_class($extension))); - } - - $globals[] = $extGlob; - } - - $globals[] = $this->staging->getGlobals(); - - return \call_user_func_array('array_merge', $globals); + return $this->extensionSet->getBinaryOperators(); } - /** - * @internal - */ - protected function initExtensions() + private function updateOptionsHash(): void { - if ($this->extensionInitialized) { - return; - } - - $this->parsers = new \Twig_TokenParserBroker([], [], false); - $this->filters = []; - $this->functions = []; - $this->tests = []; - $this->visitors = []; - $this->unaryOperators = []; - $this->binaryOperators = []; - - foreach ($this->extensions as $extension) { - $this->initExtension($extension); - } - $this->initExtension($this->staging); - // Done at the end only, so that an exception during initialization does not mark the environment as initialized when catching the exception - $this->extensionInitialized = true; - } - - /** - * @internal - */ - protected function initExtension(ExtensionInterface $extension) - { - // filters - foreach ($extension->getFilters() as $name => $filter) { - if ($filter instanceof TwigFilter) { - $name = $filter->getName(); - } else { - @trigger_error(sprintf('Using an instance of "%s" for filter "%s" is deprecated since version 1.21. Use \Twig_SimpleFilter instead.', \get_class($filter), $name), E_USER_DEPRECATED); - } - - $this->filters[$name] = $filter; - } - - // functions - foreach ($extension->getFunctions() as $name => $function) { - if ($function instanceof TwigFunction) { - $name = $function->getName(); - } else { - @trigger_error(sprintf('Using an instance of "%s" for function "%s" is deprecated since version 1.21. Use \Twig_SimpleFunction instead.', \get_class($function), $name), E_USER_DEPRECATED); - } - - $this->functions[$name] = $function; - } - - // tests - foreach ($extension->getTests() as $name => $test) { - if ($test instanceof TwigTest) { - $name = $test->getName(); - } else { - @trigger_error(sprintf('Using an instance of "%s" for test "%s" is deprecated since version 1.21. Use \Twig_SimpleTest instead.', \get_class($test), $name), E_USER_DEPRECATED); - } - - $this->tests[$name] = $test; - } - - // token parsers - foreach ($extension->getTokenParsers() as $parser) { - if ($parser instanceof TokenParserInterface) { - $this->parsers->addTokenParser($parser); - } elseif ($parser instanceof \Twig_TokenParserBrokerInterface) { - @trigger_error('Registering a \Twig_TokenParserBrokerInterface instance is deprecated since version 1.21.', E_USER_DEPRECATED); - - $this->parsers->addTokenParserBroker($parser); - } else { - throw new \LogicException('getTokenParsers() must return an array of \Twig_TokenParserInterface or \Twig_TokenParserBrokerInterface instances.'); - } - } - - // node visitors - foreach ($extension->getNodeVisitors() as $visitor) { - $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))); - } - - if (2 !== \count($operators)) { - throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', \get_class($extension), \count($operators))); - } - - $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]); - $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]); - } - } - - /** - * @deprecated since 1.22 (to be removed in 2.0) - */ - protected function writeCacheFile($file, $content) - { - $this->cache->write($file, $content); - } - - private function updateOptionsHash() - { - $hashParts = array_merge( - array_keys($this->extensions), - [ - (int) \function_exists('twig_template_get_attributes'), - PHP_MAJOR_VERSION, - PHP_MINOR_VERSION, - self::VERSION, - (int) $this->debug, - $this->baseTemplateClass, - (int) $this->strictVariables, - ] - ); - $this->optionsHash = implode(':', $hashParts); + $this->optionsHash = implode(':', [ + $this->extensionSet->getSignature(), + \PHP_MAJOR_VERSION, + \PHP_MINOR_VERSION, + self::VERSION, + (int) $this->debug, + (int) $this->strictVariables, + ]); } } - -class_alias('Twig\Environment', 'Twig_Environment'); diff --git a/lib/twig/twig/src/Error/Error.php b/lib/twig/twig/src/Error/Error.php index 2aa70f1538..a68be65f20 100644 --- a/lib/twig/twig/src/Error/Error.php +++ b/lib/twig/twig/src/Error/Error.php @@ -38,180 +38,87 @@ use Twig\Template; */ class Error extends \Exception { - protected $lineno; - // to be renamed to name in 2.0 - protected $filename; - protected $rawMessage; - + private $lineno; + private $name; + private $rawMessage; private $sourcePath; private $sourceCode; /** * Constructor. * - * Set the line number to -1 to enable its automatic guessing. - * Set the name to null to enable its automatic guessing. + * By default, automatic guessing is enabled. * - * @param string $message The error message - * @param int $lineno The template line where the error occurred - * @param Source|string|null $source The source context where the error occurred - * @param \Exception $previous The previous exception + * @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 */ - public function __construct($message, $lineno = -1, $source = null, \Exception $previous = null) + public function __construct(string $message, int $lineno = -1, Source $source = null, \Exception $previous = null) { + parent::__construct('', 0, $previous); + if (null === $source) { $name = null; - } elseif (!$source instanceof Source) { - // for compat with the Twig C ext., passing the template name as string is accepted - $name = $source; } else { $name = $source->getName(); $this->sourceCode = $source->getCode(); $this->sourcePath = $source->getPath(); } - parent::__construct('', 0, $previous); $this->lineno = $lineno; - $this->filename = $name; + $this->name = $name; $this->rawMessage = $message; $this->updateRepr(); } - /** - * Gets the raw message. - * - * @return string The raw message - */ - public function getRawMessage() + public function getRawMessage(): string { return $this->rawMessage; } - /** - * Gets the logical name where the error occurred. - * - * @return string The name - * - * @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead. - */ - public function getTemplateFile() - { - @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); - - return $this->filename; - } - - /** - * Sets the logical name where the error occurred. - * - * @param string $name The name - * - * @deprecated since 1.27 (to be removed in 2.0). Use setSourceContext() instead. - */ - public function setTemplateFile($name) - { - @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use setSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); - - $this->filename = $name; - - $this->updateRepr(); - } - - /** - * Gets the logical name where the error occurred. - * - * @return string The name - * - * @deprecated since 1.29 (to be removed in 2.0). Use getSourceContext() instead. - */ - public function getTemplateName() - { - @trigger_error(sprintf('The "%s" method is deprecated since version 1.29 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); - - return $this->filename; - } - - /** - * Sets the logical name where the error occurred. - * - * @param string $name The name - * - * @deprecated since 1.29 (to be removed in 2.0). Use setSourceContext() instead. - */ - public function setTemplateName($name) - { - @trigger_error(sprintf('The "%s" method is deprecated since version 1.29 and will be removed in 2.0. Use setSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); - - $this->filename = $name; - $this->sourceCode = $this->sourcePath = null; - - $this->updateRepr(); - } - - /** - * Gets the template line where the error occurred. - * - * @return int The template line - */ - public function getTemplateLine() + public function getTemplateLine(): int { return $this->lineno; } - /** - * Sets the template line where the error occurred. - * - * @param int $lineno The template line - */ - public function setTemplateLine($lineno) + public function setTemplateLine(int $lineno): void { $this->lineno = $lineno; $this->updateRepr(); } - /** - * Gets the source context of the Twig template where the error occurred. - * - * @return Source|null - */ - public function getSourceContext() + public function getSourceContext(): ?Source { - return $this->filename ? new Source($this->sourceCode, $this->filename, $this->sourcePath) : null; + return $this->name ? new Source($this->sourceCode, $this->name, $this->sourcePath) : null; } - /** - * Sets the source context of the Twig template where the error occurred. - */ - public function setSourceContext(Source $source = null) + public function setSourceContext(Source $source = null): void { if (null === $source) { - $this->sourceCode = $this->filename = $this->sourcePath = null; + $this->sourceCode = $this->name = $this->sourcePath = null; } else { $this->sourceCode = $source->getCode(); - $this->filename = $source->getName(); + $this->name = $source->getName(); $this->sourcePath = $source->getPath(); } $this->updateRepr(); } - public function guess() + public function guess(): void { $this->guessTemplateInfo(); $this->updateRepr(); } - public function appendMessage($rawMessage) + public function appendMessage($rawMessage): void { $this->rawMessage .= $rawMessage; $this->updateRepr(); } - /** - * @internal - */ - protected function updateRepr() + private function updateRepr(): void { $this->message = $this->rawMessage; @@ -234,11 +141,11 @@ class Error extends \Exception $questionMark = true; } - if ($this->filename) { - if (\is_string($this->filename) || (\is_object($this->filename) && method_exists($this->filename, '__toString'))) { - $name = sprintf('"%s"', $this->filename); + if ($this->name) { + if (\is_string($this->name) || (\is_object($this->name) && method_exists($this->name, '__toString'))) { + $name = sprintf('"%s"', $this->name); } else { - $name = json_encode($this->filename); + $name = json_encode($this->name); } $this->message .= sprintf(' in %s', $name); } @@ -256,20 +163,17 @@ class Error extends \Exception } } - /** - * @internal - */ - protected function guessTemplateInfo() + private function guessTemplateInfo(): void { $template = null; $templateClass = null; - $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT); + $backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT); foreach ($backtrace as $trace) { - if (isset($trace['object']) && $trace['object'] instanceof Template && 'Twig_Template' !== \get_class($trace['object'])) { + if (isset($trace['object']) && $trace['object'] instanceof Template) { $currentClass = \get_class($trace['object']); - $isEmbedContainer = 0 === strpos($templateClass, $currentClass); - if (null === $this->filename || ($this->filename == $trace['object']->getTemplateName() && !$isEmbedContainer)) { + $isEmbedContainer = null === $templateClass ? false : 0 === strpos($templateClass, $currentClass); + if (null === $this->name || ($this->name == $trace['object']->getTemplateName() && !$isEmbedContainer)) { $template = $trace['object']; $templateClass = \get_class($trace['object']); } @@ -277,8 +181,8 @@ class Error extends \Exception } // update template name - if (null !== $template && null === $this->filename) { - $this->filename = $template->getTemplateName(); + if (null !== $template && null === $this->name) { + $this->name = $template->getTemplateName(); } // update template path if any @@ -296,7 +200,7 @@ class Error extends \Exception $file = $r->getFileName(); $exceptions = [$e = $this]; - while ($e instanceof self && $e = $e->getPrevious()) { + while ($e = $e->getPrevious()) { $exceptions[] = $e; } @@ -321,5 +225,3 @@ class Error extends \Exception } } } - -class_alias('Twig\Error\Error', 'Twig_Error'); diff --git a/lib/twig/twig/src/Error/LoaderError.php b/lib/twig/twig/src/Error/LoaderError.php index dc5a9f1af7..7c8c23c194 100644 --- a/lib/twig/twig/src/Error/LoaderError.php +++ b/lib/twig/twig/src/Error/LoaderError.php @@ -19,5 +19,3 @@ namespace Twig\Error; class LoaderError extends Error { } - -class_alias('Twig\Error\LoaderError', 'Twig_Error_Loader'); diff --git a/lib/twig/twig/src/Error/RuntimeError.php b/lib/twig/twig/src/Error/RuntimeError.php index 9b3f36e050..f6b84766c8 100644 --- a/lib/twig/twig/src/Error/RuntimeError.php +++ b/lib/twig/twig/src/Error/RuntimeError.php @@ -20,5 +20,3 @@ namespace Twig\Error; class RuntimeError extends Error { } - -class_alias('Twig\Error\RuntimeError', 'Twig_Error_Runtime'); diff --git a/lib/twig/twig/src/Error/SyntaxError.php b/lib/twig/twig/src/Error/SyntaxError.php index 480e660621..726b3309e5 100644 --- a/lib/twig/twig/src/Error/SyntaxError.php +++ b/lib/twig/twig/src/Error/SyntaxError.php @@ -25,21 +25,7 @@ class SyntaxError extends Error * @param string $name The original name of the item that does not exist * @param array $items An array of possible items */ - public function addSuggestions($name, array $items) - { - if (!$alternatives = self::computeAlternatives($name, $items)) { - return; - } - - $this->appendMessage(sprintf(' Did you mean "%s"?', implode('", "', $alternatives))); - } - - /** - * @internal - * - * To be merged with the addSuggestions() method in 2.0. - */ - public static function computeAlternatives($name, $items) + public function addSuggestions(string $name, array $items): void { $alternatives = []; foreach ($items as $item) { @@ -48,10 +34,13 @@ class SyntaxError extends Error $alternatives[$item] = $lev; } } + + if (!$alternatives) { + return; + } + asort($alternatives); - return array_keys($alternatives); + $this->appendMessage(sprintf(' Did you mean "%s"?', implode('", "', array_keys($alternatives)))); } } - -class_alias('Twig\Error\SyntaxError', 'Twig_Error_Syntax'); diff --git a/lib/twig/twig/src/ExpressionParser.php b/lib/twig/twig/src/ExpressionParser.php index 9066ade169..70b6eb05cb 100644 --- a/lib/twig/twig/src/ExpressionParser.php +++ b/lib/twig/twig/src/ExpressionParser.php @@ -13,6 +13,7 @@ namespace Twig; use Twig\Error\SyntaxError; +use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\ArrowFunctionExpression; use Twig\Node\Expression\AssignNameExpression; @@ -24,6 +25,7 @@ use Twig\Node\Expression\GetAttrExpression; use Twig\Node\Expression\MethodCallExpression; use Twig\Node\Expression\NameExpression; use Twig\Node\Expression\ParentExpression; +use Twig\Node\Expression\TestExpression; use Twig\Node\Expression\Unary\NegUnary; use Twig\Node\Expression\Unary\NotUnary; use Twig\Node\Expression\Unary\PosUnary; @@ -43,30 +45,20 @@ use Twig\Node\Node; */ class ExpressionParser { - const OPERATOR_LEFT = 1; - const OPERATOR_RIGHT = 2; - - protected $parser; - protected $unaryOperators; - protected $binaryOperators; + public const OPERATOR_LEFT = 1; + public const OPERATOR_RIGHT = 2; + private $parser; private $env; + private $unaryOperators; + private $binaryOperators; - public function __construct(Parser $parser, $env = null) + public function __construct(Parser $parser, Environment $env) { $this->parser = $parser; - - if ($env instanceof Environment) { - $this->env = $env; - $this->unaryOperators = $env->getUnaryOperators(); - $this->binaryOperators = $env->getBinaryOperators(); - } else { - @trigger_error('Passing the operators as constructor arguments to '.__METHOD__.' is deprecated since version 1.27. Pass the environment instead.', E_USER_DEPRECATED); - - $this->env = $parser->getEnvironment(); - $this->unaryOperators = func_get_arg(1); - $this->binaryOperators = func_get_arg(2); - } + $this->env = $env; + $this->unaryOperators = $env->getUnaryOperators(); + $this->binaryOperators = $env->getBinaryOperators(); } public function parseExpression($precedence = 0, $allowArrow = false) @@ -86,7 +78,7 @@ class ExpressionParser } elseif ('is' === $token->getValue()) { $expr = $this->parseTestExpression($expr); } elseif (isset($op['callable'])) { - $expr = \call_user_func($op['callable'], $this->parser, $expr); + $expr = $op['callable']($this->parser, $expr); } else { $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']); $class = $op['class']; @@ -111,57 +103,57 @@ class ExpressionParser $stream = $this->parser->getStream(); // short array syntax (one argument, no parentheses)? - if ($stream->look(1)->test(Token::ARROW_TYPE)) { + if ($stream->look(1)->test(/* Token::ARROW_TYPE */ 12)) { $line = $stream->getCurrent()->getLine(); - $token = $stream->expect(Token::NAME_TYPE); + $token = $stream->expect(/* Token::NAME_TYPE */ 5); $names = [new AssignNameExpression($token->getValue(), $token->getLine())]; - $stream->expect(Token::ARROW_TYPE); + $stream->expect(/* Token::ARROW_TYPE */ 12); 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, '(')) { + if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { return null; } ++$i; while (true) { // variable name ++$i; - if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ',')) { + if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ',')) { break; } ++$i; } - if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ')')) { + if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) { return null; } ++$i; - if (!$stream->look($i)->test(Token::ARROW_TYPE)) { + if (!$stream->look($i)->test(/* Token::ARROW_TYPE */ 12)) { return null; } // yes, let's parse it properly - $token = $stream->expect(Token::PUNCTUATION_TYPE, '('); + $token = $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '('); $line = $token->getLine(); $names = []; while (true) { - $token = $stream->expect(Token::NAME_TYPE); + $token = $stream->expect(/* Token::NAME_TYPE */ 5); $names[] = new AssignNameExpression($token->getValue(), $token->getLine()); - if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { + if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { break; } } - $stream->expect(Token::PUNCTUATION_TYPE, ')'); - $stream->expect(Token::ARROW_TYPE); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')'); + $stream->expect(/* Token::ARROW_TYPE */ 12); return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line); } - protected function getPrimary() + private function getPrimary(): AbstractExpression { $token = $this->parser->getCurrentToken(); @@ -172,10 +164,10 @@ class ExpressionParser $class = $operator['class']; return $this->parsePostfixExpression(new $class($expr, $token->getLine())); - } elseif ($token->test(Token::PUNCTUATION_TYPE, '(')) { + } elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { $this->parser->getStream()->next(); $expr = $this->parseExpression(); - $this->parser->getStream()->expect(Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed'); + $this->parser->getStream()->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'An opened parenthesis is not properly closed'); return $this->parsePostfixExpression($expr); } @@ -183,12 +175,12 @@ class ExpressionParser return $this->parsePrimaryExpression(); } - protected function parseConditionalExpression($expr) + private function parseConditionalExpression($expr): AbstractExpression { - while ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, '?')) { - if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) { + while ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, '?')) { + if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) { $expr2 = $this->parseExpression(); - if ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) { + if ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) { $expr3 = $this->parseExpression(); } else { $expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine()); @@ -204,21 +196,21 @@ class ExpressionParser return $expr; } - protected function isUnary(Token $token) + private function isUnary(Token $token): bool { - return $token->test(Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]); + return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->unaryOperators[$token->getValue()]); } - protected function isBinary(Token $token) + private function isBinary(Token $token): bool { - return $token->test(Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]); + return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->binaryOperators[$token->getValue()]); } public function parsePrimaryExpression() { $token = $this->parser->getCurrentToken(); switch ($token->getType()) { - case Token::NAME_TYPE: + case /* Token::NAME_TYPE */ 5: $this->parser->getStream()->next(); switch ($token->getValue()) { case 'true': @@ -247,32 +239,27 @@ class ExpressionParser } break; - case Token::NUMBER_TYPE: + case /* Token::NUMBER_TYPE */ 6: $this->parser->getStream()->next(); $node = new ConstantExpression($token->getValue(), $token->getLine()); break; - case Token::STRING_TYPE: - case Token::INTERPOLATION_START_TYPE: + case /* Token::STRING_TYPE */ 7: + case /* Token::INTERPOLATION_START_TYPE */ 10: $node = $this->parseStringExpression(); break; - case Token::OPERATOR_TYPE: + case /* Token::OPERATOR_TYPE */ 8: 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; - } elseif (isset($this->unaryOperators[$token->getValue()])) { - $class = $this->unaryOperators[$token->getValue()]['class']; + } - $ref = new \ReflectionClass($class); - $negClass = 'Twig\Node\Expression\Unary\NegUnary'; - $posClass = 'Twig\Node\Expression\Unary\PosUnary'; - if (!(\in_array($ref->getName(), [$negClass, $posClass, 'Twig_Node_Expression_Unary_Neg', 'Twig_Node_Expression_Unary_Pos']) - || $ref->isSubclassOf($negClass) || $ref->isSubclassOf($posClass) - || $ref->isSubclassOf('Twig_Node_Expression_Unary_Neg') || $ref->isSubclassOf('Twig_Node_Expression_Unary_Pos')) - ) { + 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()); } @@ -285,11 +272,11 @@ class ExpressionParser // no break default: - if ($token->test(Token::PUNCTUATION_TYPE, '[')) { + if ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '[')) { $node = $this->parseArrayExpression(); - } elseif ($token->test(Token::PUNCTUATION_TYPE, '{')) { + } elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '{')) { $node = $this->parseHashExpression(); - } elseif ($token->test(Token::OPERATOR_TYPE, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) { + } elseif ($token->test(/* Token::OPERATOR_TYPE */ 8, '=') && ('==' === $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()); @@ -307,12 +294,12 @@ class ExpressionParser // a string cannot be followed by another string in a single expression $nextCanBeString = true; while (true) { - if ($nextCanBeString && $token = $stream->nextIf(Token::STRING_TYPE)) { + if ($nextCanBeString && $token = $stream->nextIf(/* Token::STRING_TYPE */ 7)) { $nodes[] = new ConstantExpression($token->getValue(), $token->getLine()); $nextCanBeString = false; - } elseif ($stream->nextIf(Token::INTERPOLATION_START_TYPE)) { + } elseif ($stream->nextIf(/* Token::INTERPOLATION_START_TYPE */ 10)) { $nodes[] = $this->parseExpression(); - $stream->expect(Token::INTERPOLATION_END_TYPE); + $stream->expect(/* Token::INTERPOLATION_END_TYPE */ 11); $nextCanBeString = true; } else { break; @@ -330,16 +317,16 @@ class ExpressionParser public function parseArrayExpression() { $stream = $this->parser->getStream(); - $stream->expect(Token::PUNCTUATION_TYPE, '[', 'An array element was expected'); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '[', 'An array element was expected'); $node = new ArrayExpression([], $stream->getCurrent()->getLine()); $first = true; - while (!$stream->test(Token::PUNCTUATION_TYPE, ']')) { + while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) { if (!$first) { - $stream->expect(Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma'); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'An array element must be followed by a comma'); // trailing ,? - if ($stream->test(Token::PUNCTUATION_TYPE, ']')) { + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) { break; } } @@ -347,7 +334,7 @@ class ExpressionParser $node->addElement($this->parseExpression()); } - $stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed'); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']', 'An opened array is not properly closed'); return $node; } @@ -355,16 +342,16 @@ class ExpressionParser public function parseHashExpression() { $stream = $this->parser->getStream(); - $stream->expect(Token::PUNCTUATION_TYPE, '{', 'A hash element was expected'); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '{', 'A hash element was expected'); $node = new ArrayExpression([], $stream->getCurrent()->getLine()); $first = true; - while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) { + while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) { if (!$first) { - $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma'); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'A hash value must be followed by a comma'); // trailing ,? - if ($stream->test(Token::PUNCTUATION_TYPE, '}')) { + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) { break; } } @@ -376,9 +363,18 @@ class ExpressionParser // * 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::STRING_TYPE)) || ($token = $stream->nextIf(Token::NAME_TYPE)) || $token = $stream->nextIf(Token::NUMBER_TYPE)) { + if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) { $key = new ConstantExpression($token->getValue(), $token->getLine()); - } elseif ($stream->test(Token::PUNCTUATION_TYPE, '(')) { + + // {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 */ 7)) || $token = $stream->nextIf(/* Token::NUMBER_TYPE */ 6)) { + $key = new ConstantExpression($token->getValue(), $token->getLine()); + } elseif ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { $key = $this->parseExpression(); } else { $current = $stream->getCurrent(); @@ -386,12 +382,12 @@ class ExpressionParser throw new SyntaxError(sprintf('A hash 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 hash key must be followed by a colon (:)'); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ':', 'A hash key must be followed by a colon (:)'); $value = $this->parseExpression(); $node->addElement($value, $key); } - $stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed'); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '}', 'An opened hash is not properly closed'); return $node; } @@ -400,7 +396,7 @@ class ExpressionParser { while (true) { $token = $this->parser->getCurrentToken(); - if (Token::PUNCTUATION_TYPE == $token->getType()) { + if (/* Token::PUNCTUATION_TYPE */ 9 == $token->getType()) { if ('.' == $token->getValue() || '[' == $token->getValue()) { $node = $this->parseSubscriptExpression($node); } elseif ('|' == $token->getValue()) { @@ -474,22 +470,22 @@ class ExpressionParser if ('.' == $token->getValue()) { $token = $stream->next(); if ( - Token::NAME_TYPE == $token->getType() + /* Token::NAME_TYPE */ 5 == $token->getType() || - Token::NUMBER_TYPE == $token->getType() + /* Token::NUMBER_TYPE */ 6 == $token->getType() || - (Token::OPERATOR_TYPE == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue())) + (/* Token::OPERATOR_TYPE */ 8 == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue())) ) { $arg = new ConstantExpression($token->getValue(), $lineno); - if ($stream->test(Token::PUNCTUATION_TYPE, '(')) { + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { $type = Template::METHOD_CALL; foreach ($this->parseArguments() as $n) { $arguments->addElement($n); } } } else { - throw new SyntaxError('Expected name or number.', $lineno, $stream->getSourceContext()); + throw new SyntaxError(sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $lineno, $stream->getSourceContext()); } if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) { @@ -499,11 +495,7 @@ class ExpressionParser $name = $arg->getAttribute('value'); - if ($this->parser->isReservedMacroName($name)) { - throw new SyntaxError(sprintf('"%s" cannot be called as macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext()); - } - - $node = new MethodCallExpression($node, 'get'.$name, $arguments, $lineno); + $node = new MethodCallExpression($node, 'macro_'.$name, $arguments, $lineno); $node->setAttribute('safe', true); return $node; @@ -513,19 +505,19 @@ class ExpressionParser // slice? $slice = false; - if ($stream->test(Token::PUNCTUATION_TYPE, ':')) { + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ':')) { $slice = true; $arg = new ConstantExpression(0, $token->getLine()); } else { $arg = $this->parseExpression(); } - if ($stream->nextIf(Token::PUNCTUATION_TYPE, ':')) { + if ($stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) { $slice = true; } if ($slice) { - if ($stream->test(Token::PUNCTUATION_TYPE, ']')) { + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) { $length = new ConstantExpression(null, $token->getLine()); } else { $length = $this->parseExpression(); @@ -535,12 +527,12 @@ class ExpressionParser $arguments = new Node([$arg, $length]); $filter = new $class($node, new ConstantExpression('slice', $token->getLine()), $arguments, $token->getLine()); - $stream->expect(Token::PUNCTUATION_TYPE, ']'); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']'); return $filter; } - $stream->expect(Token::PUNCTUATION_TYPE, ']'); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']'); } return new GetAttrExpression($node, $arg, $arguments, $type, $lineno); @@ -556,10 +548,10 @@ class ExpressionParser public function parseFilterExpressionRaw($node, $tag = null) { while (true) { - $token = $this->parser->getStream()->expect(Token::NAME_TYPE); + $token = $this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5); $name = new ConstantExpression($token->getValue(), $token->getLine()); - if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '(')) { + if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { $arguments = new Node(); } else { $arguments = $this->parseArguments(true, false, true); @@ -569,7 +561,7 @@ class ExpressionParser $node = new $class($node, $name, $arguments, $token->getLine(), $tag); - if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '|')) { + if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '|')) { break; } @@ -594,21 +586,26 @@ class ExpressionParser $args = []; $stream = $this->parser->getStream(); - $stream->expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis'); - while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) { + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '(', 'A list of arguments must begin with an opening parenthesis'); + while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) { if (!empty($args)) { - $stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', '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 */ 9, ')')) { + break; + } } if ($definition) { - $token = $stream->expect(Token::NAME_TYPE, null, 'An argument must be a name'); + $token = $stream->expect(/* Token::NAME_TYPE */ 5, null, 'An argument must be a name'); $value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine()); } else { $value = $this->parseExpression(0, $allowArrow); } $name = null; - if ($namedArguments && $token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) { + if ($namedArguments && $token = $stream->nextIf(/* Token::OPERATOR_TYPE */ 8, '=')) { if (!$value instanceof NameExpression) { throw new SyntaxError(sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext()); } @@ -618,7 +615,7 @@ class ExpressionParser $value = $this->parsePrimaryExpression(); if (!$this->checkConstantExpression($value)) { - throw new SyntaxError(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $stream->getSourceContext()); + throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, or an array).', $token->getLine(), $stream->getSourceContext()); } } else { $value = $this->parseExpression(0, $allowArrow); @@ -639,7 +636,7 @@ class ExpressionParser } } } - $stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'A list of arguments must be closed by a parenthesis'); return new Node($args); } @@ -650,19 +647,19 @@ class ExpressionParser $targets = []; while (true) { $token = $this->parser->getCurrentToken(); - if ($stream->test(Token::OPERATOR_TYPE) && preg_match(Lexer::REGEX_NAME, $token->getValue())) { + if ($stream->test(/* Token::OPERATOR_TYPE */ 8) && preg_match(Lexer::REGEX_NAME, $token->getValue())) { // in this context, string operators are variable names $this->parser->getStream()->next(); } else { - $stream->expect(Token::NAME_TYPE, null, 'Only variables can be assigned to'); + $stream->expect(/* Token::NAME_TYPE */ 5, null, 'Only variables can be assigned to'); } $value = $token->getValue(); - if (\in_array(strtolower($value), ['true', 'false', 'none', 'null'])) { + 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()); - if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { + if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { break; } } @@ -675,7 +672,7 @@ class ExpressionParser $targets = []; while (true) { $targets[] = $this->parseExpression(); - if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ',')) { + if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { break; } } @@ -683,35 +680,42 @@ class ExpressionParser return new Node($targets); } - private function parseNotTestExpression(\Twig_NodeInterface $node) + private function parseNotTestExpression(Node $node): NotUnary { return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine()); } - private function parseTestExpression(\Twig_NodeInterface $node) + private function parseTestExpression(Node $node): TestExpression { $stream = $this->parser->getStream(); list($name, $test) = $this->getTest($node->getTemplateLine()); $class = $this->getTestNodeClass($test); $arguments = null; - if ($stream->test(Token::PUNCTUATION_TYPE, '(')) { + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { $arguments = $this->parseArguments(true); + } elseif ($test->hasOneMandatoryArgument()) { + $arguments = new Node([0 => $this->parsePrimaryExpression()]); + } + + if ('defined' === $name && $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); } return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine()); } - private function getTest($line) + private function getTest(int $line): array { $stream = $this->parser->getStream(); - $name = $stream->expect(Token::NAME_TYPE)->getValue(); + $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); if ($test = $this->env->getTest($name)) { return [$name, $test]; } - if ($stream->test(Token::NAME_TYPE)) { + if ($stream->test(/* Token::NAME_TYPE */ 5)) { // try 2-words tests $name = $name.' '.$this->parser->getCurrentToken()->getValue(); @@ -728,92 +732,81 @@ class ExpressionParser throw $e; } - private function getTestNodeClass($test) + private function getTestNodeClass(TwigTest $test): string { - if ($test instanceof TwigTest && $test->isDeprecated()) { + if ($test->isDeprecated()) { $stream = $this->parser->getStream(); $message = sprintf('Twig Test "%s" is deprecated', $test->getName()); - if (!\is_bool($test->getDeprecatedVersion())) { + + if ($test->getDeprecatedVersion()) { $message .= sprintf(' since version %s', $test->getDeprecatedVersion()); } if ($test->getAlternative()) { $message .= sprintf('. Use "%s" instead', $test->getAlternative()); } $src = $stream->getSourceContext(); - $message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $stream->getCurrent()->getLine()); + $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine()); - @trigger_error($message, E_USER_DEPRECATED); + @trigger_error($message, \E_USER_DEPRECATED); } - if ($test instanceof TwigTest) { - return $test->getNodeClass(); - } - - return $test instanceof \Twig_Test_Node ? $test->getClass() : 'Twig\Node\Expression\TestExpression'; + return $test->getNodeClass(); } - protected function getFunctionNodeClass($name, $line) + private function getFunctionNodeClass(string $name, int $line): string { - if (false === $function = $this->env->getFunction($name)) { + 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 instanceof TwigFunction && $function->isDeprecated()) { + if ($function->isDeprecated()) { $message = sprintf('Twig Function "%s" is deprecated', $function->getName()); - if (!\is_bool($function->getDeprecatedVersion())) { + if ($function->getDeprecatedVersion()) { $message .= sprintf(' since version %s', $function->getDeprecatedVersion()); } 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->getPath() : $src->getName(), $line); + $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line); - @trigger_error($message, E_USER_DEPRECATED); + @trigger_error($message, \E_USER_DEPRECATED); } - if ($function instanceof TwigFunction) { - return $function->getNodeClass(); - } - - return $function instanceof \Twig_Function_Node ? $function->getClass() : 'Twig\Node\Expression\FunctionExpression'; + return $function->getNodeClass(); } - protected function getFilterNodeClass($name, $line) + private function getFilterNodeClass(string $name, int $line): string { - if (false === $filter = $this->env->getFilter($name)) { + 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 instanceof TwigFilter && $filter->isDeprecated()) { + if ($filter->isDeprecated()) { $message = sprintf('Twig Filter "%s" is deprecated', $filter->getName()); - if (!\is_bool($filter->getDeprecatedVersion())) { + if ($filter->getDeprecatedVersion()) { $message .= sprintf(' since version %s', $filter->getDeprecatedVersion()); } 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->getPath() : $src->getName(), $line); + $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line); - @trigger_error($message, E_USER_DEPRECATED); + @trigger_error($message, \E_USER_DEPRECATED); } - if ($filter instanceof TwigFilter) { - return $filter->getNodeClass(); - } - - return $filter instanceof \Twig_Filter_Node ? $filter->getClass() : 'Twig\Node\Expression\FilterExpression'; + return $filter->getNodeClass(); } // checks that the node only contains "constant" elements - protected function checkConstantExpression(\Twig_NodeInterface $node) + private function checkConstantExpression(Node $node): bool { if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression || $node instanceof NegUnary || $node instanceof PosUnary @@ -830,5 +823,3 @@ class ExpressionParser return true; } } - -class_alias('Twig\ExpressionParser', 'Twig_ExpressionParser'); diff --git a/lib/twig/twig/src/Extension/AbstractExtension.php b/lib/twig/twig/src/Extension/AbstractExtension.php index fa3245b298..422925f31b 100644 --- a/lib/twig/twig/src/Extension/AbstractExtension.php +++ b/lib/twig/twig/src/Extension/AbstractExtension.php @@ -11,17 +11,8 @@ namespace Twig\Extension; -use Twig\Environment; - abstract class AbstractExtension implements ExtensionInterface { - /** - * @deprecated since 1.23 (to be removed in 2.0), implement \Twig_Extension_InitRuntimeInterface instead - */ - public function initRuntime(Environment $environment) - { - } - public function getTokenParsers() { return []; @@ -51,22 +42,4 @@ abstract class AbstractExtension implements ExtensionInterface { return []; } - - /** - * @deprecated since 1.23 (to be removed in 2.0), implement \Twig_Extension_GlobalsInterface instead - */ - public function getGlobals() - { - return []; - } - - /** - * @deprecated since 1.26 (to be removed in 2.0), not used anymore internally - */ - public function getName() - { - return \get_class($this); - } } - -class_alias('Twig\Extension\AbstractExtension', 'Twig_Extension'); diff --git a/lib/twig/twig/src/Extension/CoreExtension.php b/lib/twig/twig/src/Extension/CoreExtension.php index 5f3cc24a19..b779858598 100644 --- a/lib/twig/twig/src/Extension/CoreExtension.php +++ b/lib/twig/twig/src/Extension/CoreExtension.php @@ -11,13 +11,51 @@ namespace Twig\Extension { use Twig\ExpressionParser; +use Twig\Node\Expression\Binary\AddBinary; +use Twig\Node\Expression\Binary\AndBinary; +use Twig\Node\Expression\Binary\BitwiseAndBinary; +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\EndsWithBinary; +use Twig\Node\Expression\Binary\EqualBinary; +use Twig\Node\Expression\Binary\FloorDivBinary; +use Twig\Node\Expression\Binary\GreaterBinary; +use Twig\Node\Expression\Binary\GreaterEqualBinary; +use Twig\Node\Expression\Binary\InBinary; +use Twig\Node\Expression\Binary\LessBinary; +use Twig\Node\Expression\Binary\LessEqualBinary; +use Twig\Node\Expression\Binary\MatchesBinary; +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\OrBinary; +use Twig\Node\Expression\Binary\PowerBinary; +use Twig\Node\Expression\Binary\RangeBinary; +use Twig\Node\Expression\Binary\SpaceshipBinary; +use Twig\Node\Expression\Binary\StartsWithBinary; +use Twig\Node\Expression\Binary\SubBinary; +use Twig\Node\Expression\Filter\DefaultFilter; +use Twig\Node\Expression\NullCoalesceExpression; +use Twig\Node\Expression\Test\ConstantTest; +use Twig\Node\Expression\Test\DefinedTest; +use Twig\Node\Expression\Test\DivisiblebyTest; +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\Unary\NegUnary; +use Twig\Node\Expression\Unary\NotUnary; +use Twig\Node\Expression\Unary\PosUnary; +use Twig\NodeVisitor\MacroAutoImportNodeVisitor; use Twig\TokenParser\ApplyTokenParser; use Twig\TokenParser\BlockTokenParser; use Twig\TokenParser\DeprecatedTokenParser; use Twig\TokenParser\DoTokenParser; use Twig\TokenParser\EmbedTokenParser; use Twig\TokenParser\ExtendsTokenParser; -use Twig\TokenParser\FilterTokenParser; use Twig\TokenParser\FlushTokenParser; use Twig\TokenParser\ForTokenParser; use Twig\TokenParser\FromTokenParser; @@ -26,43 +64,17 @@ use Twig\TokenParser\ImportTokenParser; use Twig\TokenParser\IncludeTokenParser; use Twig\TokenParser\MacroTokenParser; use Twig\TokenParser\SetTokenParser; -use Twig\TokenParser\SpacelessTokenParser; use Twig\TokenParser\UseTokenParser; use Twig\TokenParser\WithTokenParser; use Twig\TwigFilter; use Twig\TwigFunction; use Twig\TwigTest; -/** - * @final - */ -class CoreExtension extends AbstractExtension +final class CoreExtension extends AbstractExtension { - protected $dateFormats = ['F j, Y H:i', '%d days']; - protected $numberFormat = [0, '.', ',']; - protected $timezone = null; - protected $escapers = []; - - /** - * Defines a new escaper to be used via the escape filter. - * - * @param string $strategy The strategy name that should be used as a strategy in the escape call - * @param callable $callable A valid PHP callable - */ - public function setEscaper($strategy, $callable) - { - $this->escapers[$strategy] = $callable; - } - - /** - * Gets all defined escapers. - * - * @return array An array of escapers - */ - public function getEscapers() - { - return $this->escapers; - } + private $dateFormats = ['F j, Y H:i', '%d days']; + private $numberFormat = [0, '.', ',']; + private $timezone = null; /** * Sets the default format to be used by the date filter. @@ -137,7 +149,7 @@ class CoreExtension extends AbstractExtension return $this->numberFormat; } - public function getTokenParsers() + public function getTokenParsers(): array { return [ new ApplyTokenParser(), @@ -147,12 +159,10 @@ class CoreExtension extends AbstractExtension new IncludeTokenParser(), new BlockTokenParser(), new UseTokenParser(), - new FilterTokenParser(), new MacroTokenParser(), new ImportTokenParser(), new FromTokenParser(), new SetTokenParser(), - new SpacelessTokenParser(), new FlushTokenParser(), new DoTokenParser(), new EmbedTokenParser(), @@ -161,13 +171,13 @@ class CoreExtension extends AbstractExtension ]; } - public function getFilters() + public function getFilters(): array { - $filters = [ + return [ // formatting filters new TwigFilter('date', 'twig_date_format_filter', ['needs_environment' => true]), new TwigFilter('date_modify', 'twig_date_modify_filter', ['needs_environment' => true]), - new TwigFilter('format', 'sprintf'), + new TwigFilter('format', 'twig_sprintf'), new TwigFilter('replace', 'twig_replace_filter'), new TwigFilter('number_format', 'twig_number_format_filter', ['needs_environment' => true]), new TwigFilter('abs', 'abs'), @@ -175,28 +185,29 @@ class CoreExtension extends AbstractExtension // encoding new TwigFilter('url_encode', 'twig_urlencode_filter'), - new TwigFilter('json_encode', 'twig_jsonencode_filter'), + new TwigFilter('json_encode', 'json_encode'), new TwigFilter('convert_encoding', 'twig_convert_encoding'), // string filters new TwigFilter('title', 'twig_title_string_filter', ['needs_environment' => true]), new TwigFilter('capitalize', 'twig_capitalize_string_filter', ['needs_environment' => true]), - new TwigFilter('upper', 'strtoupper'), - new TwigFilter('lower', 'strtolower'), - new TwigFilter('striptags', 'strip_tags'), + new TwigFilter('upper', 'twig_upper_filter', ['needs_environment' => true]), + new TwigFilter('lower', 'twig_lower_filter', ['needs_environment' => true]), + new TwigFilter('striptags', 'twig_striptags'), new TwigFilter('trim', 'twig_trim_filter'), - new TwigFilter('nl2br', 'nl2br', ['pre_escape' => 'html', 'is_safe' => ['html']]), + new TwigFilter('nl2br', 'twig_nl2br', ['pre_escape' => 'html', 'is_safe' => ['html']]), new TwigFilter('spaceless', 'twig_spaceless', ['is_safe' => ['html']]), // array helpers new TwigFilter('join', 'twig_join_filter'), new TwigFilter('split', 'twig_split_filter', ['needs_environment' => true]), - new TwigFilter('sort', 'twig_sort_filter'), + new TwigFilter('sort', 'twig_sort_filter', ['needs_environment' => true]), new TwigFilter('merge', 'twig_array_merge'), new TwigFilter('batch', 'twig_array_batch'), - new TwigFilter('filter', 'twig_array_filter'), - new TwigFilter('map', 'twig_array_map'), - new TwigFilter('reduce', 'twig_array_reduce'), + new TwigFilter('column', 'twig_array_column'), + new TwigFilter('filter', 'twig_array_filter', ['needs_environment' => true]), + new TwigFilter('map', 'twig_array_map', ['needs_environment' => true]), + new TwigFilter('reduce', 'twig_array_reduce', ['needs_environment' => true]), // string/array filters new TwigFilter('reverse', 'twig_reverse_filter', ['needs_environment' => true]), @@ -206,23 +217,12 @@ class CoreExtension extends AbstractExtension new TwigFilter('last', 'twig_last', ['needs_environment' => true]), // iteration and runtime - new TwigFilter('default', '_twig_default_filter', ['node_class' => '\Twig\Node\Expression\Filter\DefaultFilter']), + new TwigFilter('default', '_twig_default_filter', ['node_class' => DefaultFilter::class]), new TwigFilter('keys', 'twig_get_array_keys_filter'), - - // escaping - new TwigFilter('escape', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']), - new TwigFilter('e', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']), ]; - - if (\function_exists('mb_get_info')) { - $filters[] = new TwigFilter('upper', 'twig_upper_filter', ['needs_environment' => true]); - $filters[] = new TwigFilter('lower', 'twig_lower_filter', ['needs_environment' => true]); - } - - return $filters; } - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('max', 'max'), @@ -237,82 +237,81 @@ class CoreExtension extends AbstractExtension ]; } - public function getTests() + public function getTests(): array { return [ - new TwigTest('even', null, ['node_class' => '\Twig\Node\Expression\Test\EvenTest']), - new TwigTest('odd', null, ['node_class' => '\Twig\Node\Expression\Test\OddTest']), - new TwigTest('defined', null, ['node_class' => '\Twig\Node\Expression\Test\DefinedTest']), - new TwigTest('sameas', null, ['node_class' => '\Twig\Node\Expression\Test\SameasTest', 'deprecated' => '1.21', 'alternative' => 'same as']), - new TwigTest('same as', null, ['node_class' => '\Twig\Node\Expression\Test\SameasTest']), - new TwigTest('none', null, ['node_class' => '\Twig\Node\Expression\Test\NullTest']), - new TwigTest('null', null, ['node_class' => '\Twig\Node\Expression\Test\NullTest']), - new TwigTest('divisibleby', null, ['node_class' => '\Twig\Node\Expression\Test\DivisiblebyTest', 'deprecated' => '1.21', 'alternative' => 'divisible by']), - new TwigTest('divisible by', null, ['node_class' => '\Twig\Node\Expression\Test\DivisiblebyTest']), - new TwigTest('constant', null, ['node_class' => '\Twig\Node\Expression\Test\ConstantTest']), + 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('constant', null, ['node_class' => ConstantTest::class]), new TwigTest('empty', 'twig_test_empty'), new TwigTest('iterable', 'twig_test_iterable'), ]; } - public function getOperators() + public function getNodeVisitors(): array + { + return [new MacroAutoImportNodeVisitor()]; + } + + public function getOperators(): array { return [ [ - 'not' => ['precedence' => 50, 'class' => '\Twig\Node\Expression\Unary\NotUnary'], - '-' => ['precedence' => 500, 'class' => '\Twig\Node\Expression\Unary\NegUnary'], - '+' => ['precedence' => 500, 'class' => '\Twig\Node\Expression\Unary\PosUnary'], + 'not' => ['precedence' => 50, 'class' => NotUnary::class], + '-' => ['precedence' => 500, 'class' => NegUnary::class], + '+' => ['precedence' => 500, 'class' => PosUnary::class], ], [ - 'or' => ['precedence' => 10, 'class' => '\Twig\Node\Expression\Binary\OrBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'and' => ['precedence' => 15, 'class' => '\Twig\Node\Expression\Binary\AndBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'b-or' => ['precedence' => 16, 'class' => '\Twig\Node\Expression\Binary\BitwiseOrBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'b-xor' => ['precedence' => 17, 'class' => '\Twig\Node\Expression\Binary\BitwiseXorBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'b-and' => ['precedence' => 18, 'class' => '\Twig\Node\Expression\Binary\BitwiseAndBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - '==' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\EqualBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - '!=' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\NotEqualBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - '<' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\LessBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - '>' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\GreaterBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - '>=' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\GreaterEqualBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - '<=' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\LessEqualBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'not in' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\NotInBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'in' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\InBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'matches' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\MatchesBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'starts with' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\StartsWithBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - 'ends with' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\EndsWithBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - '..' => ['precedence' => 25, 'class' => '\Twig\Node\Expression\Binary\RangeBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - '+' => ['precedence' => 30, 'class' => '\Twig\Node\Expression\Binary\AddBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - '-' => ['precedence' => 30, 'class' => '\Twig\Node\Expression\Binary\SubBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - '~' => ['precedence' => 40, 'class' => '\Twig\Node\Expression\Binary\ConcatBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - '*' => ['precedence' => 60, 'class' => '\Twig\Node\Expression\Binary\MulBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - '/' => ['precedence' => 60, 'class' => '\Twig\Node\Expression\Binary\DivBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - '//' => ['precedence' => 60, 'class' => '\Twig\Node\Expression\Binary\FloorDivBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], - '%' => ['precedence' => 60, 'class' => '\Twig\Node\Expression\Binary\ModBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + '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], + '..' => ['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' => '\Twig\Node\Expression\Binary\PowerBinary', 'associativity' => ExpressionParser::OPERATOR_RIGHT], - '??' => ['precedence' => 300, 'class' => '\Twig\Node\Expression\NullCoalesceExpression', 'associativity' => ExpressionParser::OPERATOR_RIGHT], + '**' => ['precedence' => 200, 'class' => PowerBinary::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT], + '??' => ['precedence' => 300, 'class' => NullCoalesceExpression::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT], ], ]; } - - public function getName() - { - return 'core'; - } } - -class_alias('Twig\Extension\CoreExtension', 'Twig_Extension_Core'); } namespace { -use Twig\Environment; -use Twig\Error\LoaderError; -use Twig\Error\RuntimeError; -use Twig\Loader\SourceContextLoaderInterface; -use Twig\Markup; -use Twig\Node\Expression\ConstantExpression; -use Twig\Node\Node; + use Twig\Environment; + use Twig\Error\LoaderError; + use Twig\Error\RuntimeError; + use Twig\Extension\CoreExtension; + use Twig\Extension\SandboxExtension; + use Twig\Markup; + use Twig\Source; + use Twig\Template; + use Twig\TemplateWrapper; /** * Cycles over a value. @@ -347,7 +346,7 @@ function twig_cycle($values, $position) function twig_random(Environment $env, $values = null, $max = null) { if (null === $values) { - return null === $max ? mt_rand() : mt_rand(0, $max); + return null === $max ? mt_rand() : mt_rand(0, (int) $max); } if (\is_int($values) || \is_float($values)) { @@ -364,29 +363,28 @@ function twig_random(Environment $env, $values = null, $max = null) $max = $max; } - return mt_rand($min, $max); + return mt_rand((int) $min, (int) $max); } if (\is_string($values)) { if ('' === $values) { return ''; } - if (null !== $charset = $env->getCharset()) { - if ('UTF-8' !== $charset) { - $values = twig_convert_encoding($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('/(?getCharset(); - if ('UTF-8' !== $charset) { - foreach ($values as $i => $value) { - $values[$i] = twig_convert_encoding($value, $charset, 'UTF-8'); - } + if ('UTF-8' !== $charset) { + $values = twig_convert_encoding($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) { + $values[$i] = twig_convert_encoding($value, $charset, 'UTF-8'); } - } else { - return $values[mt_rand(0, \strlen($values) - 1)]; } } @@ -408,16 +406,16 @@ function twig_random(Environment $env, $values = null, $max = null) * * {{ post.published_at|date("m/d/Y") }} * - * @param \DateTime|\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 $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 * * @return string The formatted date */ function twig_date_format_filter(Environment $env, $date, $format = null, $timezone = null) { if (null === $format) { - $formats = $env->getExtension('\Twig\Extension\CoreExtension')->getDateFormat(); + $formats = $env->getExtension(CoreExtension::class)->getDateFormat(); $format = $date instanceof \DateInterval ? $formats[1] : $formats[0]; } @@ -433,20 +431,29 @@ function twig_date_format_filter(Environment $env, $date, $format = null, $timez * * {{ post.published_at|date_modify("-1day")|date("m/d/Y") }} * - * @param \DateTime|string $date A date - * @param string $modifier A modifier string + * @param \DateTimeInterface|string $date A date + * @param string $modifier A modifier string * - * @return \DateTime + * @return \DateTimeInterface */ function twig_date_modify_filter(Environment $env, $date, $modifier) { $date = twig_date_converter($env, $date, false); - $resultDate = $date->modify($modifier); - // This is a hack to ensure PHP 5.2 support and support for \DateTimeImmutable - // \DateTime::modify does not return the modified \DateTime object < 5.3.0 - // and \DateTimeImmutable does not modify $date. - return null === $resultDate ? $date : $resultDate; + return $date->modify($modifier); +} + +/** + * Returns a formatted string. + * + * @param string|null $format + * @param ...$values + * + * @return string + */ +function twig_sprintf($format, ...$values) +{ + return sprintf($format ?? '', ...$values); } /** @@ -456,17 +463,17 @@ function twig_date_modify_filter(Environment $env, $date, $modifier) * {# do something #} * {% endif %} * - * @param \DateTime|\DateTimeInterface|string|null $date A date - * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged + * @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 * - * @return \DateTime + * @return \DateTimeInterface */ function twig_date_converter(Environment $env, $date = null, $timezone = null) { // determine the timezone if (false !== $timezone) { if (null === $timezone) { - $timezone = $env->getExtension('\Twig\Extension\CoreExtension')->getTimezone(); + $timezone = $env->getExtension(CoreExtension::class)->getTimezone(); } elseif (!$timezone instanceof \DateTimeZone) { $timezone = new \DateTimeZone($timezone); } @@ -477,7 +484,7 @@ function twig_date_converter(Environment $env, $date = null, $timezone = null) return false !== $timezone ? $date->setTimezone($timezone) : $date; } - if ($date instanceof \DateTime || $date instanceof \DateTimeInterface) { + if ($date instanceof \DateTimeInterface) { $date = clone $date; if (false !== $timezone) { $date->setTimezone($timezone); @@ -487,14 +494,18 @@ function twig_date_converter(Environment $env, $date = null, $timezone = null) } if (null === $date || 'now' === $date) { - return new \DateTime($date, false !== $timezone ? $timezone : $env->getExtension('\Twig\Extension\CoreExtension')->getTimezone()); + if (null === $date) { + $date = 'now'; + } + + return new \DateTime($date, false !== $timezone ? $timezone : $env->getExtension(CoreExtension::class)->getTimezone()); } $asString = (string) $date; if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) { $date = new \DateTime('@'.$date); } else { - $date = new \DateTime($date, $env->getExtension('\Twig\Extension\CoreExtension')->getTimezone()); + $date = new \DateTime($date, $env->getExtension(CoreExtension::class)->getTimezone()); } if (false !== $timezone) { @@ -507,54 +518,49 @@ function twig_date_converter(Environment $env, $date = null, $timezone = null) /** * Replaces strings within a string. * - * @param string $str String to replace in + * @param string|null $str String to replace in * @param array|\Traversable $from Replace values - * @param string|null $to Replace to, deprecated (@see https://secure.php.net/manual/en/function.strtr.php) * * @return string */ -function twig_replace_filter($str, $from, $to = null) +function twig_replace_filter($str, $from) { - if (\is_string($from) && \is_string($to)) { - @trigger_error('Using "replace" with character by character replacement is deprecated since version 1.22 and will be removed in Twig 2.0', E_USER_DEPRECATED); - - return strtr($str, $from, $to); - } - if (!twig_test_iterable($from)) { throw new RuntimeError(sprintf('The "replace" filter expects an array or "Traversable" as replace values, got "%s".', \is_object($from) ? \get_class($from) : \gettype($from))); } - return strtr($str, twig_to_array($from)); + return strtr($str ?? '', twig_to_array($from)); } /** * Rounds a number. * - * @param int|float $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 string $method The method to use for rounding * * @return int|float The rounded number */ function twig_round($value, $precision = 0, $method = 'common') { - if ('common' == $method) { + $value = (float) $value; + + if ('common' === $method) { return round($value, $precision); } - if ('ceil' != $method && 'floor' != $method) { + if ('ceil' !== $method && 'floor' !== $method) { throw new RuntimeError('The round filter only supports the "common", "ceil", and "floor" methods.'); } - return $method($value * pow(10, $precision)) / pow(10, $precision); + return $method($value * 10 ** $precision) / 10 ** $precision; } /** * Number format filter. * * All of the formatting options can be left null, in that case the defaults will - * be used. Supplying any of the parameters will override the defaults set in the + * be used. Supplying any of the parameters will override the defaults set in the * environment object. * * @param mixed $number A float/int/string of the number to format @@ -566,7 +572,7 @@ function twig_round($value, $precision = 0, $method = 'common') */ function twig_number_format_filter(Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null) { - $defaults = $env->getExtension('\Twig\Extension\CoreExtension')->getNumberFormat(); + $defaults = $env->getExtension(CoreExtension::class)->getNumberFormat(); if (null === $decimal) { $decimal = $defaults[0]; } @@ -585,47 +591,17 @@ function twig_number_format_filter(Environment $env, $number, $decimal = null, $ /** * URL encodes (RFC 3986) a string as a path segment or an array as a query string. * - * @param string|array $url A URL or an array of query parameters + * @param string|array|null $url A URL or an array of query parameters * * @return string The URL encoded value */ function twig_urlencode_filter($url) { if (\is_array($url)) { - if (\defined('PHP_QUERY_RFC3986')) { - return http_build_query($url, '', '&', PHP_QUERY_RFC3986); - } - - return http_build_query($url, '', '&'); + return http_build_query($url, '', '&', \PHP_QUERY_RFC3986); } - return rawurlencode($url); -} - -/** - * JSON encodes a variable. - * - * @param mixed $value the value to encode - * @param int $options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT - * - * @return mixed The JSON encoded value - */ -function twig_jsonencode_filter($value, $options = 0) -{ - if ($value instanceof Markup) { - $value = (string) $value; - } elseif (\is_array($value)) { - array_walk_recursive($value, '_twig_markup2string'); - } - - return json_encode($value, $options); -} - -function _twig_markup2string(&$value) -{ - if ($value instanceof Markup) { - $value = (string) $value; - } + return rawurlencode($url ?? ''); } /** @@ -687,13 +663,7 @@ function twig_slice(Environment $env, $item, $start, $length = null, $preserveKe return \array_slice($item, $start, $length, $preserveKeys); } - $item = (string) $item; - - if (\function_exists('mb_get_info') && null !== $charset = $env->getCharset()) { - return (string) mb_substr($item, $start, null === $length ? mb_strlen($item, $charset) - $start : $length, $charset); - } - - return (string) (null === $length ? substr($item, $start) : substr($item, $start, $length)); + return (string) mb_substr((string) $item, $start, $length, $env->getCharset()); } /** @@ -782,34 +752,32 @@ function twig_join_filter($value, $glue = '', $and = null) * {{ "aabbcc"|split('', 2) }} * {# returns [aa, bb, cc] #} * - * @param string $value A string - * @param string $delimiter The delimiter - * @param int $limit The limit + * @param string|null $value A string + * @param string $delimiter The delimiter + * @param int $limit The limit * * @return array The split string as an array */ function twig_split_filter(Environment $env, $value, $delimiter, $limit = null) { + $value = $value ?? ''; + if (\strlen($delimiter) > 0) { return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit); } - if (!\function_exists('mb_get_info') || null === $charset = $env->getCharset()) { - return str_split($value, null === $limit ? 1 : $limit); - } - if ($limit <= 1) { return preg_split('/(?getCharset()); if ($length < $limit) { return [$value]; } $r = []; for ($i = 0; $i < $length; $i += $limit) { - $r[] = mb_substr($value, $i, $limit, $charset); + $r[] = mb_substr($value, $i, $limit, $env->getCharset()); } return $r; @@ -850,8 +818,8 @@ function twig_get_array_keys_filter($array) $array = $array->getIterator(); } + $keys = []; if ($array instanceof \Iterator) { - $keys = []; $array->rewind(); while ($array->valid()) { $keys[] = $array->key(); @@ -861,7 +829,6 @@ function twig_get_array_keys_filter($array) return $keys; } - $keys = []; foreach ($array as $key => $item) { $keys[] = $key; } @@ -879,8 +846,8 @@ function twig_get_array_keys_filter($array) /** * Reverses a variable. * - * @param array|\Traversable|string $item An array, a \Traversable instance, or a string - * @param bool $preserveKeys Whether to preserve key or not + * @param array|\Traversable|string|null $item An array, a \Traversable instance, or a string + * @param bool $preserveKeys Whether to preserve key or not * * @return mixed The reversed input */ @@ -894,25 +861,23 @@ function twig_reverse_filter(Environment $env, $item, $preserveKeys = false) return array_reverse($item, $preserveKeys); } - if (null !== $charset = $env->getCharset()) { - $string = (string) $item; + $string = (string) $item; - if ('UTF-8' !== $charset) { - $item = twig_convert_encoding($string, 'UTF-8', $charset); - } + $charset = $env->getCharset(); - preg_match_all('/./us', $item, $matches); - - $string = implode('', array_reverse($matches[0])); - - if ('UTF-8' !== $charset) { - $string = twig_convert_encoding($string, $charset, 'UTF-8'); - } - - return $string; + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, 'UTF-8', $charset); } - return strrev((string) $item); + preg_match_all('/./us', $string, $matches); + + $string = implode('', array_reverse($matches[0])); + + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, $charset, 'UTF-8'); + } + + return $string; } /** @@ -922,7 +887,7 @@ function twig_reverse_filter(Environment $env, $item, $preserveKeys = false) * * @return array */ -function twig_sort_filter($array) +function twig_sort_filter(Environment $env, $array, $arrow = null) { if ($array instanceof \Traversable) { $array = iterator_to_array($array); @@ -930,7 +895,13 @@ function twig_sort_filter($array) throw new RuntimeError(sprintf('The sort filter only works with arrays or "Traversable", got "%s".', \gettype($array))); } - asort($array); + if (null !== $arrow) { + twig_check_arrow_in_sandbox($env, $arrow, 'sort', 'filter'); + + uasort($array, $arrow); + } else { + asort($array); + } return $array; } @@ -947,34 +918,110 @@ function twig_in_filter($value, $compare) $compare = (string) $compare; } - if (\is_array($compare)) { - return \in_array($value, $compare, \is_object($value) || \is_resource($value)); - } elseif (\is_string($compare) && (\is_string($value) || \is_int($value) || \is_float($value))) { - return '' === $value || false !== strpos($compare, (string) $value); - } elseif ($compare instanceof \Traversable) { - if (\is_object($value) || \is_resource($value)) { + if (\is_string($compare)) { + if (\is_string($value) || \is_int($value) || \is_float($value)) { + return '' === $value || false !== strpos($compare, (string) $value); + } + + return false; + } + + if (!is_iterable($compare)) { + return false; + } + + if (\is_object($value) || \is_resource($value)) { + if (!\is_array($compare)) { foreach ($compare as $item) { if ($item === $value) { return true; } } - } else { - foreach ($compare as $item) { - if ($item == $value) { - return true; - } - } + + return false; } - return false; + return \in_array($value, $compare, true); + } + + foreach ($compare as $item) { + if (0 === twig_compare($value, $item)) { + return true; + } } return false; } +/** + * Compares two values using a more strict version of the PHP non-strict comparison operator. + * + * @see https://wiki.php.net/rfc/string_to_number_comparison + * @see https://wiki.php.net/rfc/trailing_whitespace_numerics + * + * @internal + */ +function twig_compare($a, $b) +{ + // int <=> string + if (\is_int($a) && \is_string($b)) { + $bTrim = trim($b, " \t\n\r\v\f"); + if (!is_numeric($bTrim)) { + return (string) $a <=> $b; + } + if ((int) $bTrim == $bTrim) { + return $a <=> (int) $bTrim; + } else { + return (float) $a <=> (float) $bTrim; + } + } + if (\is_string($a) && \is_int($b)) { + $aTrim = trim($a, " \t\n\r\v\f"); + if (!is_numeric($aTrim)) { + return $a <=> (string) $b; + } + if ((int) $aTrim == $aTrim) { + return (int) $aTrim <=> $b; + } else { + return (float) $aTrim <=> (float) $b; + } + } + + // float <=> string + if (\is_float($a) && \is_string($b)) { + if (is_nan($a)) { + return 1; + } + $bTrim = trim($b, " \t\n\r\v\f"); + if (!is_numeric($bTrim)) { + return (string) $a <=> $b; + } + + return $a <=> (float) $bTrim; + } + if (\is_string($a) && \is_float($b)) { + if (is_nan($b)) { + return 1; + } + $aTrim = trim($a, " \t\n\r\v\f"); + if (!is_numeric($aTrim)) { + return $a <=> (string) $b; + } + + return (float) $aTrim <=> $b; + } + + // fallback to <=> + return $a <=> $b; +} + /** * Returns a trimmed string. * + * @param string|null $string + * @param string|null $characterMask + * @param string $side + * * @return string * * @throws RuntimeError When an invalid trimming side is used (not a string or not 'left', 'right', or 'both') @@ -987,478 +1034,172 @@ function twig_trim_filter($string, $characterMask = null, $side = 'both') switch ($side) { case 'both': - return trim($string, $characterMask); + return trim($string ?? '', $characterMask); case 'left': - return ltrim($string, $characterMask); + return ltrim($string ?? '', $characterMask); case 'right': - return rtrim($string, $characterMask); + return rtrim($string ?? '', $characterMask); default: throw new RuntimeError('Trimming side must be "left", "right" or "both".'); } } +/** + * Inserts HTML line breaks before all newlines in a string. + * + * @param string|null $string + * + * @return string + */ +function twig_nl2br($string) +{ + return nl2br($string ?? ''); +} + /** * Removes whitespaces between HTML tags. * + * @param string|null $string + * * @return string */ function twig_spaceless($content) { - return trim(preg_replace('/>\s+<', $content)); + return trim(preg_replace('/>\s+<', $content ?? '')); } /** - * Escapes a string. - * - * @param mixed $string The value to be escaped - * @param string $strategy The escaping strategy - * @param string $charset The charset - * @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false) + * @param string|null $string + * @param string $to + * @param string $from * * @return string */ -function twig_escape_filter(Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false) +function twig_convert_encoding($string, $to, $from) { - if ($autoescape && $string instanceof Markup) { - return $string; + if (!\function_exists('iconv')) { + throw new RuntimeError('Unable to convert encoding: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.'); } - if (!\is_string($string)) { - if (\is_object($string) && method_exists($string, '__toString')) { - $string = (string) $string; - } elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) { - return $string; - } + return iconv($from, $to, $string ?? ''); +} + +/** + * Returns the length of a variable. + * + * @param mixed $thing A variable + * + * @return int The length of the value + */ +function twig_length_filter(Environment $env, $thing) +{ + if (null === $thing) { + return 0; } - if ('' === $string) { - return ''; + if (is_scalar($thing)) { + return mb_strlen($thing, $env->getCharset()); } - if (null === $charset) { - $charset = $env->getCharset(); + if ($thing instanceof \Countable || \is_array($thing) || $thing instanceof \SimpleXMLElement) { + return \count($thing); } - switch ($strategy) { - case 'html': - // see https://secure.php.net/htmlspecialchars - - // 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. - static $htmlspecialcharsCharsets = [ - 'ISO-8859-1' => true, 'ISO8859-1' => true, - 'ISO-8859-15' => true, 'ISO8859-15' => true, - 'utf-8' => true, 'UTF-8' => true, - 'CP866' => true, 'IBM866' => true, '866' => true, - 'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true, - '1251' => true, - 'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true, - 'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true, - 'BIG5' => true, '950' => true, - 'GB2312' => true, '936' => true, - 'BIG5-HKSCS' => true, - 'SHIFT_JIS' => true, 'SJIS' => true, '932' => true, - 'EUC-JP' => true, 'EUCJP' => true, - 'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true, - ]; - - if (isset($htmlspecialcharsCharsets[$charset])) { - return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); - } - - if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) { - // cache the lowercase variant for future iterations - $htmlspecialcharsCharsets[$charset] = true; - - return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); - } - - $string = twig_convert_encoding($string, 'UTF-8', $charset); - $string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); - - return twig_convert_encoding($string, $charset, 'UTF-8'); - - case 'js': - // escape all non-alphanumeric characters - // into their \x or \uHHHH representations - if ('UTF-8' !== $charset) { - $string = twig_convert_encoding($string, 'UTF-8', $charset); - } - - if (!preg_match('//u', $string)) { - throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); - } - - $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', '_twig_escape_js_callback', $string); - - if ('UTF-8' !== $charset) { - $string = twig_convert_encoding($string, $charset, 'UTF-8'); - } - - return $string; - - case 'css': - if ('UTF-8' !== $charset) { - $string = twig_convert_encoding($string, 'UTF-8', $charset); - } - - if (!preg_match('//u', $string)) { - throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); - } - - $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', '_twig_escape_css_callback', $string); - - if ('UTF-8' !== $charset) { - $string = twig_convert_encoding($string, $charset, 'UTF-8'); - } - - return $string; - - case 'html_attr': - if ('UTF-8' !== $charset) { - $string = twig_convert_encoding($string, 'UTF-8', $charset); - } - - if (!preg_match('//u', $string)) { - throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); - } - - $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', '_twig_escape_html_attr_callback', $string); - - if ('UTF-8' !== $charset) { - $string = twig_convert_encoding($string, $charset, 'UTF-8'); - } - - return $string; - - case 'url': - return rawurlencode($string); - - default: - static $escapers; - - if (null === $escapers) { - $escapers = $env->getExtension('\Twig\Extension\CoreExtension')->getEscapers(); - } - - if (isset($escapers[$strategy])) { - return \call_user_func($escapers[$strategy], $env, $string, $charset); - } - - $validStrategies = implode(', ', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($escapers))); - - throw new RuntimeError(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies)); + if ($thing instanceof \Traversable) { + return iterator_count($thing); } + + if (method_exists($thing, '__toString') && !$thing instanceof \Countable) { + return mb_strlen((string) $thing, $env->getCharset()); + } + + return 1; +} + +/** + * Converts a string to uppercase. + * + * @param string|null $string A string + * + * @return string The uppercased string + */ +function twig_upper_filter(Environment $env, $string) +{ + return mb_strtoupper($string ?? '', $env->getCharset()); +} + +/** + * Converts a string to lowercase. + * + * @param string|null $string A string + * + * @return string The lowercased string + */ +function twig_lower_filter(Environment $env, $string) +{ + return mb_strtolower($string ?? '', $env->getCharset()); +} + +/** + * Strips HTML and PHP tags from a string. + * + * @param string|null $string + * @param string[]|string|null $string + * + * @return string + */ +function twig_striptags($string, $allowable_tags = null) +{ + return strip_tags($string ?? '', $allowable_tags); +} + +/** + * Returns a titlecased string. + * + * @param string|null $string A string + * + * @return string The titlecased string + */ +function twig_title_string_filter(Environment $env, $string) +{ + if (null !== $charset = $env->getCharset()) { + return mb_convert_case($string ?? '', \MB_CASE_TITLE, $charset); + } + + return ucwords(strtolower($string ?? '')); +} + +/** + * Returns a capitalized string. + * + * @param string|null $string A string + * + * @return string The capitalized string + */ +function twig_capitalize_string_filter(Environment $env, $string) +{ + $charset = $env->getCharset(); + + return mb_strtoupper(mb_substr($string ?? '', 0, 1, $charset), $charset).mb_strtolower(mb_substr($string ?? '', 1, null, $charset), $charset); } /** * @internal */ -function twig_escape_filter_is_safe(Node $filterArgs) +function twig_call_macro(Template $template, string $method, array $args, int $lineno, array $context, Source $source) { - foreach ($filterArgs as $arg) { - if ($arg instanceof ConstantExpression) { - return [$arg->getAttribute('value')]; + if (!method_exists($template, $method)) { + $parent = $template; + while ($parent = $parent->getParent($context)) { + if (method_exists($parent, $method)) { + return $parent->$method(...$args); + } } - return []; + throw new RuntimeError(sprintf('Macro "%s" is not defined in template "%s".', substr($method, \strlen('macro_')), $template->getTemplateName()), $lineno, $source); } - return ['html']; -} - -if (\function_exists('mb_convert_encoding')) { - function twig_convert_encoding($string, $to, $from) - { - return mb_convert_encoding($string, $to, $from); - } -} elseif (\function_exists('iconv')) { - function twig_convert_encoding($string, $to, $from) - { - return iconv($from, $to, $string); - } -} else { - function twig_convert_encoding($string, $to, $from) - { - throw new RuntimeError('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).'); - } -} - -if (\function_exists('mb_ord')) { - function twig_ord($string) - { - return mb_ord($string, 'UTF-8'); - } -} else { - function twig_ord($string) - { - $code = ($string = unpack('C*', substr($string, 0, 4))) ? $string[1] : 0; - if (0xF0 <= $code) { - return (($code - 0xF0) << 18) + (($string[2] - 0x80) << 12) + (($string[3] - 0x80) << 6) + $string[4] - 0x80; - } - if (0xE0 <= $code) { - return (($code - 0xE0) << 12) + (($string[2] - 0x80) << 6) + $string[3] - 0x80; - } - if (0xC0 <= $code) { - return (($code - 0xC0) << 6) + $string[2] - 0x80; - } - - return $code; - } -} - -function _twig_escape_js_callback($matches) -{ - $char = $matches[0]; - - /* - * A few characters have short escape sequences in JSON and JavaScript. - * Escape sequences supported only by JavaScript, not JSON, are ommitted. - * \" is also supported but omitted, because the resulting string is not HTML safe. - */ - static $shortMap = [ - '\\' => '\\\\', - '/' => '\\/', - "\x08" => '\b', - "\x0C" => '\f', - "\x0A" => '\n', - "\x0D" => '\r', - "\x09" => '\t', - ]; - - if (isset($shortMap[$char])) { - return $shortMap[$char]; - } - - // \uHHHH - $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8'); - $char = strtoupper(bin2hex($char)); - - if (4 >= \strlen($char)) { - return sprintf('\u%04s', $char); - } - - return sprintf('\u%04s\u%04s', substr($char, 0, -4), substr($char, -4)); -} - -function _twig_escape_css_callback($matches) -{ - $char = $matches[0]; - - return sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : twig_ord($char)); -} - -/** - * This function is adapted from code coming from Zend Framework. - * - * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com) - * @license https://framework.zend.com/license/new-bsd New BSD License - */ -function _twig_escape_html_attr_callback($matches) -{ - $chr = $matches[0]; - $ord = \ord($chr); - - /* - * The following replaces characters undefined in HTML with the - * hex entity for the Unicode replacement character. - */ - if (($ord <= 0x1f && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7f && $ord <= 0x9f)) { - return '�'; - } - - /* - * Check if the current character to escape has a name entity we should - * replace it with while grabbing the hex value of the character. - */ - if (1 == \strlen($chr)) { - /* - * While HTML supports far more named entities, the lowest common denominator - * has become HTML5's XML Serialisation which is restricted to the those named - * entities that XML supports. Using HTML entities would result in this error: - * XML Parsing Error: undefined entity - */ - static $entityMap = [ - 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); - } - - /* - * Per OWASP recommendations, we'll use hex entities for any other - * characters where a named entity does not exist. - */ - return sprintf('&#x%04X;', twig_ord($chr)); -} - -// add multibyte extensions if possible -if (\function_exists('mb_get_info')) { - /** - * Returns the length of a variable. - * - * @param mixed $thing A variable - * - * @return int The length of the value - */ - function twig_length_filter(Environment $env, $thing) - { - if (null === $thing) { - return 0; - } - - if (is_scalar($thing)) { - return mb_strlen($thing, $env->getCharset()); - } - - if ($thing instanceof \Countable || \is_array($thing) || $thing instanceof \SimpleXMLElement) { - return \count($thing); - } - - if ($thing instanceof \Traversable) { - return iterator_count($thing); - } - - if (\is_object($thing) && method_exists($thing, '__toString')) { - return mb_strlen((string) $thing, $env->getCharset()); - } - - return 1; - } - - /** - * Converts a string to uppercase. - * - * @param string $string A string - * - * @return string The uppercased string - */ - function twig_upper_filter(Environment $env, $string) - { - if (null !== $charset = $env->getCharset()) { - return mb_strtoupper($string, $charset); - } - - return strtoupper($string); - } - - /** - * Converts a string to lowercase. - * - * @param string $string A string - * - * @return string The lowercased string - */ - function twig_lower_filter(Environment $env, $string) - { - if (null !== $charset = $env->getCharset()) { - return mb_strtolower($string, $charset); - } - - return strtolower($string); - } - - /** - * Returns a titlecased string. - * - * @param string $string A string - * - * @return string The titlecased string - */ - function twig_title_string_filter(Environment $env, $string) - { - if (null !== $charset = $env->getCharset()) { - return mb_convert_case($string, MB_CASE_TITLE, $charset); - } - - return ucwords(strtolower($string)); - } - - /** - * Returns a capitalized string. - * - * @param string $string A string - * - * @return string The capitalized string - */ - function twig_capitalize_string_filter(Environment $env, $string) - { - if (null !== $charset = $env->getCharset()) { - return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset).mb_strtolower(mb_substr($string, 1, mb_strlen($string, $charset), $charset), $charset); - } - - return ucfirst(strtolower($string)); - } -} -// and byte fallback -else { - /** - * Returns the length of a variable. - * - * @param mixed $thing A variable - * - * @return int The length of the value - */ - function twig_length_filter(Environment $env, $thing) - { - if (null === $thing) { - return 0; - } - - if (is_scalar($thing)) { - return \strlen($thing); - } - - if ($thing instanceof \SimpleXMLElement) { - return \count($thing); - } - - if (\is_object($thing) && method_exists($thing, '__toString') && !$thing instanceof \Countable) { - return \strlen((string) $thing); - } - - if ($thing instanceof \Countable || \is_array($thing)) { - return \count($thing); - } - - if ($thing instanceof \IteratorAggregate) { - return iterator_count($thing); - } - - return 1; - } - - /** - * Returns a titlecased string. - * - * @param string $string A string - * - * @return string The titlecased string - */ - function twig_title_string_filter(Environment $env, $string) - { - return ucwords(strtolower($string)); - } - - /** - * Returns a capitalized string. - * - * @param string $string A string - * - * @return string The capitalized string - */ - function twig_capitalize_string_filter(Environment $env, $string) - { - return ucfirst(strtolower($string)); - } + return $template->$method(...$args); } /** @@ -1504,7 +1245,7 @@ function twig_to_array($seq, $preserveKeys = true) function twig_test_empty($value) { if ($value instanceof \Countable) { - return 0 == \count($value); + return 0 === \count($value); } if ($value instanceof \Traversable) { @@ -1555,53 +1296,36 @@ function twig_include(Environment $env, $context, $template, $variables = [], $w $variables = array_merge($context, $variables); } - if ($isSandboxed = $sandboxed && $env->hasExtension('\Twig\Extension\SandboxExtension')) { - $sandbox = $env->getExtension('\Twig\Extension\SandboxExtension'); + if ($isSandboxed = $sandboxed && $env->hasExtension(SandboxExtension::class)) { + $sandbox = $env->getExtension(SandboxExtension::class); if (!$alreadySandboxed = $sandbox->isSandboxed()) { $sandbox->enableSandbox(); } - } - $loaded = null; - try { - $loaded = $env->resolveTemplate($template); - } catch (LoaderError $e) { - if (!$ignoreMissing) { - if ($isSandboxed && !$alreadySandboxed) { - $sandbox->disableSandbox(); + foreach ((\is_array($template) ? $template : [$template]) as $name) { + // if a Template instance is passed, it might have been instantiated outside of a sandbox, check security + if ($name instanceof TemplateWrapper || $name instanceof Template) { + $name->unwrap()->checkSecurity(); } - - throw $e; } - } catch (\Throwable $e) { - if ($isSandboxed && !$alreadySandboxed) { - $sandbox->disableSandbox(); - } - - throw $e; - } catch (\Exception $e) { - if ($isSandboxed && !$alreadySandboxed) { - $sandbox->disableSandbox(); - } - - throw $e; } try { - $ret = $loaded ? $loaded->render($variables) : ''; - } catch (\Exception $e) { + $loaded = null; + try { + $loaded = $env->resolveTemplate($template); + } catch (LoaderError $e) { + if (!$ignoreMissing) { + throw $e; + } + } + + return $loaded ? $loaded->render($variables) : ''; + } finally { if ($isSandboxed && !$alreadySandboxed) { $sandbox->disableSandbox(); } - - throw $e; } - - if ($isSandboxed && !$alreadySandboxed) { - $sandbox->disableSandbox(); - } - - return $ret; } /** @@ -1616,11 +1340,7 @@ function twig_source(Environment $env, $name, $ignoreMissing = false) { $loader = $env->getLoader(); try { - if (!$loader instanceof SourceContextLoaderInterface) { - return $loader->getSource($name); - } else { - return $loader->getSourceContext($name)->getCode(); - } + return $loader->getSourceContext($name)->getCode(); } catch (LoaderError $e) { if (!$ignoreMissing) { throw $e; @@ -1639,6 +1359,10 @@ function twig_source(Environment $env, $name, $ignoreMissing = false) function twig_constant($constant, $object = null) { if (null !== $object) { + if ('class' === $constant) { + return \get_class($object); + } + $constant = \get_class($object).'::'.$constant; } @@ -1656,6 +1380,10 @@ function twig_constant($constant, $object = null) function twig_constant_is_defined($constant, $object = null) { if (null !== $object) { + if ('class' === $constant) { + return true; + } + $constant = \get_class($object).'::'.$constant; } @@ -1693,22 +1421,248 @@ function twig_array_batch($items, $size, $fill = null, $preserveKeys = true) return $result; } -function twig_array_filter($array, $arrow) +/** + * Returns the attribute value for a given array/object. + * + * @param mixed $object The object or array from where to get the item + * @param mixed $item The item to get from the array or object + * @param array $arguments An array of arguments to pass if the item is an object method + * @param string $type The type of attribute (@see \Twig\Template constants) + * @param bool $isDefinedTest Whether this is only a defined check + * @param bool $ignoreStrictCheck Whether to ignore the strict attribute check or not + * @param int $lineno The template line where the attribute was called + * + * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true + * + * @throws RuntimeError if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false + * + * @internal + */ +function twig_get_attribute(Environment $env, Source $source, $object, $item, array $arguments = [], $type = /* Template::ANY_CALL */ 'any', $isDefinedTest = false, $ignoreStrictCheck = false, $sandboxed = false, int $lineno = -1) { - if (\is_array($array)) { - if (\PHP_VERSION_ID >= 50600) { - return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH); + // array + if (/* Template::METHOD_CALL */ 'method' !== $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 ($isDefinedTest) { + return true; + } + + return $object[$arrayItem]; } - return array_filter($array, $arrow); + if (/* Template::ARRAY_CALL */ 'array' === $type || !\is_object($object)) { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$env->isStrictVariables()) { + return; + } + + if ($object instanceof ArrayAccess) { + $message = sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.', $arrayItem, \get_class($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)); + } elseif (\is_array($object)) { + if (empty($object)) { + $message = sprintf('Key "%s" does not exist as the array is empty.', $arrayItem); + } else { + $message = sprintf('Key "%s" for array with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object))); + } + } elseif (/* Template::ARRAY_CALL */ 'array' === $type) { + 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); + } + } 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); + } + + throw new RuntimeError($message, $lineno, $source); + } + } + + if (!\is_object($object)) { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$env->isStrictVariables()) { + return; + } + + if (null === $object) { + $message = sprintf('Impossible to invoke a method ("%s") on a null variable.', $item); + } elseif (\is_array($object)) { + $message = sprintf('Impossible to invoke a method ("%s") on an array.', $item); + } else { + $message = sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + } + + throw new RuntimeError($message, $lineno, $source); + } + + if ($object instanceof Template) { + throw new RuntimeError('Accessing \Twig\Template attributes is forbidden.', $lineno, $source); + } + + // object property + if (/* Template::METHOD_CALL */ 'method' !== $type) { + if (isset($object->$item) || \array_key_exists((string) $item, (array) $object)) { + if ($isDefinedTest) { + return true; + } + + if ($sandboxed) { + $env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object, $item, $lineno, $source); + } + + return $object->$item; + } + } + + static $cache = []; + + $class = \get_class($object); + + // object method + // precedence: getXxx() > isXxx() > hasXxx() + if (!isset($cache[$class])) { + $methods = get_class_methods($object); + sort($methods); + $lcMethods = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, $methods); + $classCache = []; + foreach ($methods as $i => $method) { + $classCache[$method] = $method; + $classCache[$lcName = $lcMethods[$i]] = $method; + + if ('g' === $lcName[0] && 0 === strpos($lcName, 'get')) { + $name = substr($method, 3); + $lcName = substr($lcName, 3); + } elseif ('i' === $lcName[0] && 0 === strpos($lcName, 'is')) { + $name = substr($method, 2); + $lcName = substr($lcName, 2); + } elseif ('h' === $lcName[0] && 0 === strpos($lcName, 'has')) { + $name = substr($method, 3); + $lcName = substr($lcName, 3); + if (\in_array('is'.$lcName, $lcMethods)) { + continue; + } + } else { + continue; + } + + // skip get() and is() methods (in which case, $name is empty) + if ($name) { + if (!isset($classCache[$name])) { + $classCache[$name] = $method; + } + + if (!isset($classCache[$lcName])) { + $classCache[$lcName] = $method; + } + } + } + $cache[$class] = $classCache; + } + + $call = false; + if (isset($cache[$class][$item])) { + $method = $cache[$class][$item]; + } elseif (isset($cache[$class][$lcItem = strtr($item, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')])) { + $method = $cache[$class][$lcItem]; + } elseif (isset($cache[$class]['__call'])) { + $method = $item; + $call = true; + } else { + if ($isDefinedTest) { + return false; + } + + 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); + } + + 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 { + $ret = $object->$method(...$arguments); + } catch (\BadMethodCallException $e) { + if ($call && ($ignoreStrictCheck || !$env->isStrictVariables())) { + return; + } + throw $e; + } + + return $ret; +} + +/** + * Returns the values from a single column in the input array. + * + *
    + *  {% set items = [{ 'fruit' : 'apple'}, {'fruit' : 'orange' }] %}
    + *
    + *  {% set fruits = items|column('fruit') %}
    + *
    + *  {# fruits now contains ['apple', 'orange'] #}
    + * 
    + * + * @param array|Traversable $array An array + * @param mixed $name The column name + * @param mixed $index The column to use as the index/keys for the returned array + * + * @return array The array of values + */ +function twig_array_column($array, $name, $index = null): array +{ + if ($array instanceof Traversable) { + $array = iterator_to_array($array); + } elseif (!\is_array($array)) { + throw new RuntimeError(sprintf('The column filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array))); + } + + return array_column($array, $name, $index); +} + +function twig_array_filter(Environment $env, $array, $arrow) +{ + if (!twig_test_iterable($array)) { + throw new RuntimeError(sprintf('The "filter" filter expects an array or "Traversable", got "%s".', \is_object($array) ? \get_class($array) : \gettype($array))); + } + + twig_check_arrow_in_sandbox($env, $arrow, 'filter', 'filter'); + + if (\is_array($array)) { + return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH); } // the IteratorIterator wrapping is needed as some internal PHP classes are \Traversable but do not implement \Iterator return new \CallbackFilterIterator(new \IteratorIterator($array), $arrow); } -function twig_array_map($array, $arrow) +function twig_array_map(Environment $env, $array, $arrow) { + twig_check_arrow_in_sandbox($env, $arrow, 'map', 'filter'); + $r = []; foreach ($array as $k => $v) { $r[$k] = $arrow($v, $k); @@ -1717,12 +1671,25 @@ function twig_array_map($array, $arrow) return $r; } -function twig_array_reduce($array, $arrow, $initial = null) +function twig_array_reduce(Environment $env, $array, $arrow, $initial = null) { + twig_check_arrow_in_sandbox($env, $arrow, 'reduce', 'filter'); + if (!\is_array($array)) { + if (!$array instanceof \Traversable) { + throw new RuntimeError(sprintf('The "reduce" filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array))); + } + $array = iterator_to_array($array); } return array_reduce($array, $arrow, $initial); } + +function twig_check_arrow_in_sandbox(Environment $env, $arrow, $thing, $type) +{ + if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) { + throw new RuntimeError(sprintf('The callable passed to the "%s" %s must be a Closure in sandbox mode.', $thing, $type)); + } +} } diff --git a/lib/twig/twig/src/Extension/DebugExtension.php b/lib/twig/twig/src/Extension/DebugExtension.php index 09b0223e2f..bfb23d7bd4 100644 --- a/lib/twig/twig/src/Extension/DebugExtension.php +++ b/lib/twig/twig/src/Extension/DebugExtension.php @@ -12,12 +12,9 @@ namespace Twig\Extension { use Twig\TwigFunction; -/** - * @final - */ -class DebugExtension extends AbstractExtension +final class DebugExtension extends AbstractExtension { - public function getFunctions() + public function getFunctions(): array { // dump is safe if var_dump is overridden by xdebug $isDumpOutputHtmlSafe = \extension_loaded('xdebug') @@ -33,14 +30,7 @@ class DebugExtension extends AbstractExtension new TwigFunction('dump', 'twig_var_dump', ['is_safe' => $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => true, 'needs_environment' => true, 'is_variadic' => true]), ]; } - - public function getName() - { - return 'debug'; - } } - -class_alias('Twig\Extension\DebugExtension', 'Twig_Extension_Debug'); } namespace { @@ -48,7 +38,7 @@ use Twig\Environment; use Twig\Template; use Twig\TemplateWrapper; -function twig_var_dump(Environment $env, $context, array $vars = []) +function twig_var_dump(Environment $env, $context, ...$vars) { if (!$env->isDebug()) { return; @@ -66,9 +56,7 @@ function twig_var_dump(Environment $env, $context, array $vars = []) var_dump($vars); } else { - foreach ($vars as $var) { - var_dump($var); - } + var_dump(...$vars); } return ob_get_clean(); diff --git a/lib/twig/twig/src/Extension/EscaperExtension.php b/lib/twig/twig/src/Extension/EscaperExtension.php index fc7f6dfeea..9d2251dc6e 100644 --- a/lib/twig/twig/src/Extension/EscaperExtension.php +++ b/lib/twig/twig/src/Extension/EscaperExtension.php @@ -10,16 +10,21 @@ */ namespace Twig\Extension { +use Twig\FileExtensionEscapingStrategy; use Twig\NodeVisitor\EscaperNodeVisitor; use Twig\TokenParser\AutoEscapeTokenParser; use Twig\TwigFilter; -/** - * @final - */ -class EscaperExtension extends AbstractExtension +final class EscaperExtension extends AbstractExtension { - protected $defaultStrategy; + private $defaultStrategy; + private $escapers = []; + + /** @internal */ + public $safeClasses = []; + + /** @internal */ + public $safeLookup = []; /** * @param string|false|callable $defaultStrategy An escaping strategy @@ -31,19 +36,21 @@ class EscaperExtension extends AbstractExtension $this->setDefaultStrategy($defaultStrategy); } - public function getTokenParsers() + public function getTokenParsers(): array { return [new AutoEscapeTokenParser()]; } - public function getNodeVisitors() + public function getNodeVisitors(): array { return [new EscaperNodeVisitor()]; } - public function getFilters() + public function getFilters(): array { return [ + new TwigFilter('escape', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']), + new TwigFilter('e', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']), new TwigFilter('raw', 'twig_raw_filter', ['is_safe' => ['all']]), ]; } @@ -56,23 +63,10 @@ class EscaperExtension extends AbstractExtension * * @param string|false|callable $defaultStrategy An escaping strategy */ - public function setDefaultStrategy($defaultStrategy) + public function setDefaultStrategy($defaultStrategy): void { - // for BC - if (true === $defaultStrategy) { - @trigger_error('Using "true" as the default strategy is deprecated since version 1.21. Use "html" instead.', E_USER_DEPRECATED); - - $defaultStrategy = 'html'; - } - - if ('filename' === $defaultStrategy) { - @trigger_error('Using "filename" as the default strategy is deprecated since version 1.27. Use "name" instead.', E_USER_DEPRECATED); - - $defaultStrategy = 'name'; - } - if ('name' === $defaultStrategy) { - $defaultStrategy = ['\Twig\FileExtensionEscapingStrategy', 'guess']; + $defaultStrategy = [FileExtensionEscapingStrategy::class, 'guess']; } $this->defaultStrategy = $defaultStrategy; @@ -85,7 +79,7 @@ class EscaperExtension extends AbstractExtension * * @return string|false The default strategy to use for the template */ - public function getDefaultStrategy($name) + public function getDefaultStrategy(string $name) { // disable string callables to avoid calling a function named html or js, // or any other upcoming escaping strategy @@ -96,25 +90,327 @@ class EscaperExtension extends AbstractExtension return $this->defaultStrategy; } - public function getName() + /** + * Defines a new escaper to be used via the escape filter. + * + * @param string $strategy The strategy name that should be used as a strategy in the escape call + * @param callable $callable A valid PHP callable + */ + public function setEscaper($strategy, callable $callable) { - return 'escaper'; + $this->escapers[$strategy] = $callable; + } + + /** + * Gets all defined escapers. + * + * @return callable[] An array of escapers + */ + public function getEscapers() + { + return $this->escapers; + } + + public function setSafeClasses(array $safeClasses = []) + { + $this->safeClasses = []; + $this->safeLookup = []; + foreach ($safeClasses as $class => $strategies) { + $this->addSafeClass($class, $strategies); + } + } + + public function addSafeClass(string $class, array $strategies) + { + $class = ltrim($class, '\\'); + if (!isset($this->safeClasses[$class])) { + $this->safeClasses[$class] = []; + } + $this->safeClasses[$class] = array_merge($this->safeClasses[$class], $strategies); + + foreach ($strategies as $strategy) { + $this->safeLookup[$strategy][$class] = true; + } } } - -class_alias('Twig\Extension\EscaperExtension', 'Twig_Extension_Escaper'); } namespace { +use Twig\Environment; +use Twig\Error\RuntimeError; +use Twig\Extension\EscaperExtension; +use Twig\Markup; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Node; + /** * Marks a variable as being safe. * * @param string $string A PHP variable - * - * @return string */ function twig_raw_filter($string) { return $string; } + +/** + * Escapes a string. + * + * @param mixed $string The value to be escaped + * @param string $strategy The escaping strategy + * @param string $charset The charset + * @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false) + * + * @return string + */ +function twig_escape_filter(Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false) +{ + if ($autoescape && $string instanceof Markup) { + return $string; + } + + if (!\is_string($string)) { + if (\is_object($string) && method_exists($string, '__toString')) { + if ($autoescape) { + $c = \get_class($string); + $ext = $env->getExtension(EscaperExtension::class); + if (!isset($ext->safeClasses[$c])) { + $ext->safeClasses[$c] = []; + foreach (class_parents($string) + class_implements($string) as $class) { + if (isset($ext->safeClasses[$class])) { + $ext->safeClasses[$c] = array_unique(array_merge($ext->safeClasses[$c], $ext->safeClasses[$class])); + foreach ($ext->safeClasses[$class] as $s) { + $ext->safeLookup[$s][$c] = true; + } + } + } + } + if (isset($ext->safeLookup[$strategy][$c]) || isset($ext->safeLookup['all'][$c])) { + return (string) $string; + } + } + + $string = (string) $string; + } elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) { + return $string; + } + } + + if ('' === $string) { + return ''; + } + + if (null === $charset) { + $charset = $env->getCharset(); + } + + switch ($strategy) { + case 'html': + // see https://www.php.net/htmlspecialchars + + // 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. + static $htmlspecialcharsCharsets = [ + 'ISO-8859-1' => true, 'ISO8859-1' => true, + 'ISO-8859-15' => true, 'ISO8859-15' => true, + 'utf-8' => true, 'UTF-8' => true, + 'CP866' => true, 'IBM866' => true, '866' => true, + 'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true, + '1251' => true, + 'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true, + 'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true, + 'BIG5' => true, '950' => true, + 'GB2312' => true, '936' => true, + 'BIG5-HKSCS' => true, + 'SHIFT_JIS' => true, 'SJIS' => true, '932' => true, + 'EUC-JP' => true, 'EUCJP' => true, + 'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true, + ]; + + if (isset($htmlspecialcharsCharsets[$charset])) { + return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset); + } + + if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) { + // cache the lowercase variant for future iterations + $htmlspecialcharsCharsets[$charset] = true; + + return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset); + } + + $string = twig_convert_encoding($string, 'UTF-8', $charset); + $string = htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8'); + + return iconv('UTF-8', $charset, $string); + + case 'js': + // escape all non-alphanumeric characters + // into their \x or \uHHHH representations + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, 'UTF-8', $charset); + } + + if (!preg_match('//u', $string)) { + 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) { + $char = $matches[0]; + + /* + * A few characters have short escape sequences in JSON and JavaScript. + * 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 = [ + '\\' => '\\\\', + '/' => '\\/', + "\x08" => '\b', + "\x0C" => '\f', + "\x0A" => '\n', + "\x0D" => '\r', + "\x09" => '\t', + ]; + + if (isset($shortMap[$char])) { + return $shortMap[$char]; + } + + $codepoint = mb_ord($char, 'UTF-8'); + if (0x10000 > $codepoint) { + return sprintf('\u%04X', $codepoint); + } + + // Split characters outside the BMP into surrogate pairs + // https://tools.ietf.org/html/rfc2781.html#section-2.1 + $u = $codepoint - 0x10000; + $high = 0xD800 | ($u >> 10); + $low = 0xDC00 | ($u & 0x3FF); + + return sprintf('\u%04X\u%04X', $high, $low); + }, $string); + + if ('UTF-8' !== $charset) { + $string = iconv('UTF-8', $charset, $string); + } + + return $string; + + case 'css': + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, 'UTF-8', $charset); + } + + if (!preg_match('//u', $string)) { + 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) { + $char = $matches[0]; + + return sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : mb_ord($char, 'UTF-8')); + }, $string); + + if ('UTF-8' !== $charset) { + $string = iconv('UTF-8', $charset, $string); + } + + return $string; + + case 'html_attr': + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, 'UTF-8', $charset); + } + + if (!preg_match('//u', $string)) { + 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) { + /** + * This function is adapted from code coming from Zend Framework. + * + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com) + * @license https://framework.zend.com/license/new-bsd New BSD License + */ + $chr = $matches[0]; + $ord = \ord($chr); + + /* + * The following replaces characters undefined in HTML with the + * hex entity for the Unicode replacement character. + */ + if (($ord <= 0x1f && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7f && $ord <= 0x9f)) { + return '�'; + } + + /* + * Check if the current character to escape has a name entity we should + * replace it with while grabbing the hex value of the character. + */ + if (1 === \strlen($chr)) { + /* + * While HTML supports far more named entities, the lowest common denominator + * has become HTML5's XML Serialisation which is restricted to the those named + * entities that XML supports. Using HTML entities would result in this error: + * XML Parsing Error: undefined entity + */ + static $entityMap = [ + 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); + } + + /* + * Per OWASP recommendations, we'll use hex entities for any other + * characters where a named entity does not exist. + */ + return sprintf('&#x%04X;', mb_ord($chr, 'UTF-8')); + }, $string); + + if ('UTF-8' !== $charset) { + $string = iconv('UTF-8', $charset, $string); + } + + return $string; + + case 'url': + return rawurlencode($string); + + default: + $escapers = $env->getExtension(EscaperExtension::class)->getEscapers(); + if (array_key_exists($strategy, $escapers)) { + return $escapers[$strategy]($env, $string, $charset); + } + + $validStrategies = implode(', ', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($escapers))); + + throw new RuntimeError(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies)); + } +} + +/** + * @internal + */ +function twig_escape_filter_is_safe(Node $filterArgs) +{ + foreach ($filterArgs as $arg) { + if ($arg instanceof ConstantExpression) { + return [$arg->getAttribute('value')]; + } + + return []; + } + + return ['html']; +} } diff --git a/lib/twig/twig/src/Extension/ExtensionInterface.php b/lib/twig/twig/src/Extension/ExtensionInterface.php index 72b31e9d1a..75fa237e1d 100644 --- a/lib/twig/twig/src/Extension/ExtensionInterface.php +++ b/lib/twig/twig/src/Extension/ExtensionInterface.php @@ -11,7 +11,6 @@ namespace Twig\Extension; -use Twig\Environment; use Twig\NodeVisitor\NodeVisitorInterface; use Twig\TokenParser\TokenParserInterface; use Twig\TwigFilter; @@ -25,15 +24,6 @@ use Twig\TwigTest; */ interface ExtensionInterface { - /** - * Initializes the runtime environment. - * - * This is where you can load some file that contains filter functions for instance. - * - * @deprecated since 1.23 (to be removed in 2.0), implement \Twig_Extension_InitRuntimeInterface instead - */ - public function initRuntime(Environment $environment); - /** * Returns the token parser instances to add to the existing list. * @@ -75,27 +65,4 @@ interface ExtensionInterface * @return array First array of unary operators, second array of binary operators */ public function getOperators(); - - /** - * Returns a list of global variables to add to the existing list. - * - * @return array An array of global variables - * - * @deprecated since 1.23 (to be removed in 2.0), implement \Twig_Extension_GlobalsInterface instead - */ - public function getGlobals(); - - /** - * Returns the name of the extension. - * - * @return string The extension name - * - * @deprecated since 1.26 (to be removed in 2.0), not used anymore internally - */ - public function getName(); } - -class_alias('Twig\Extension\ExtensionInterface', 'Twig_ExtensionInterface'); - -// Ensure that the aliased name is loaded to keep BC for classes implementing the typehint with the old aliased name. -class_exists('Twig\Environment'); diff --git a/lib/twig/twig/src/Extension/GlobalsInterface.php b/lib/twig/twig/src/Extension/GlobalsInterface.php index 1f54e25724..ec0c68292a 100644 --- a/lib/twig/twig/src/Extension/GlobalsInterface.php +++ b/lib/twig/twig/src/Extension/GlobalsInterface.php @@ -21,6 +21,5 @@ namespace Twig\Extension; */ interface GlobalsInterface { + public function getGlobals(): array; } - -class_alias('Twig\Extension\GlobalsInterface', 'Twig_Extension_GlobalsInterface'); diff --git a/lib/twig/twig/src/Extension/InitRuntimeInterface.php b/lib/twig/twig/src/Extension/InitRuntimeInterface.php deleted file mode 100644 index f71d9cb51d..0000000000 --- a/lib/twig/twig/src/Extension/InitRuntimeInterface.php +++ /dev/null @@ -1,26 +0,0 @@ - - */ -interface InitRuntimeInterface -{ -} - -class_alias('Twig\Extension\InitRuntimeInterface', 'Twig_Extension_InitRuntimeInterface'); diff --git a/lib/twig/twig/src/Extension/OptimizerExtension.php b/lib/twig/twig/src/Extension/OptimizerExtension.php index 3e137409e2..965bfdb041 100644 --- a/lib/twig/twig/src/Extension/OptimizerExtension.php +++ b/lib/twig/twig/src/Extension/OptimizerExtension.php @@ -13,27 +13,17 @@ namespace Twig\Extension; use Twig\NodeVisitor\OptimizerNodeVisitor; -/** - * @final - */ -class OptimizerExtension extends AbstractExtension +final class OptimizerExtension extends AbstractExtension { - protected $optimizers; + private $optimizers; - public function __construct($optimizers = -1) + public function __construct(int $optimizers = -1) { $this->optimizers = $optimizers; } - public function getNodeVisitors() + public function getNodeVisitors(): array { return [new OptimizerNodeVisitor($this->optimizers)]; } - - public function getName() - { - return 'optimizer'; - } } - -class_alias('Twig\Extension\OptimizerExtension', 'Twig_Extension_Optimizer'); diff --git a/lib/twig/twig/src/Extension/ProfilerExtension.php b/lib/twig/twig/src/Extension/ProfilerExtension.php index 7b21b9fa55..43e4a449e4 100644 --- a/lib/twig/twig/src/Extension/ProfilerExtension.php +++ b/lib/twig/twig/src/Extension/ProfilerExtension.php @@ -23,12 +23,18 @@ class ProfilerExtension extends AbstractExtension $this->actives[] = $profile; } + /** + * @return void + */ public function enter(Profile $profile) { $this->actives[0]->addProfile($profile); array_unshift($this->actives, $profile); } + /** + * @return void + */ public function leave(Profile $profile) { $profile->leave(); @@ -39,15 +45,8 @@ class ProfilerExtension extends AbstractExtension } } - public function getNodeVisitors() + public function getNodeVisitors(): array { - return [new ProfilerNodeVisitor(\get_class($this))]; - } - - public function getName() - { - return 'profiler'; + return [new ProfilerNodeVisitor(static::class)]; } } - -class_alias('Twig\Extension\ProfilerExtension', 'Twig_Extension_Profiler'); diff --git a/lib/twig/twig/src/Extension/SandboxExtension.php b/lib/twig/twig/src/Extension/SandboxExtension.php index 818c8c94c8..c861159b6f 100644 --- a/lib/twig/twig/src/Extension/SandboxExtension.php +++ b/lib/twig/twig/src/Extension/SandboxExtension.php @@ -12,17 +12,17 @@ namespace Twig\Extension; use Twig\NodeVisitor\SandboxNodeVisitor; +use Twig\Sandbox\SecurityNotAllowedMethodError; +use Twig\Sandbox\SecurityNotAllowedPropertyError; use Twig\Sandbox\SecurityPolicyInterface; +use Twig\Source; use Twig\TokenParser\SandboxTokenParser; -/** - * @final - */ -class SandboxExtension extends AbstractExtension +final class SandboxExtension extends AbstractExtension { - protected $sandboxedGlobally; - protected $sandboxed; - protected $policy; + private $sandboxedGlobally; + private $sandboxed; + private $policy; public function __construct(SecurityPolicyInterface $policy, $sandboxed = false) { @@ -30,32 +30,32 @@ class SandboxExtension extends AbstractExtension $this->sandboxedGlobally = $sandboxed; } - public function getTokenParsers() + public function getTokenParsers(): array { return [new SandboxTokenParser()]; } - public function getNodeVisitors() + public function getNodeVisitors(): array { return [new SandboxNodeVisitor()]; } - public function enableSandbox() + public function enableSandbox(): void { $this->sandboxed = true; } - public function disableSandbox() + public function disableSandbox(): void { $this->sandboxed = false; } - public function isSandboxed() + public function isSandboxed(): bool { return $this->sandboxedGlobally || $this->sandboxed; } - public function isSandboxedGlobally() + public function isSandboxedGlobally(): bool { return $this->sandboxedGlobally; } @@ -65,45 +65,59 @@ class SandboxExtension extends AbstractExtension $this->policy = $policy; } - public function getSecurityPolicy() + public function getSecurityPolicy(): SecurityPolicyInterface { return $this->policy; } - public function checkSecurity($tags, $filters, $functions) + public function checkSecurity($tags, $filters, $functions): void { if ($this->isSandboxed()) { $this->policy->checkSecurity($tags, $filters, $functions); } } - public function checkMethodAllowed($obj, $method) + public function checkMethodAllowed($obj, $method, int $lineno = -1, Source $source = null): void { if ($this->isSandboxed()) { - $this->policy->checkMethodAllowed($obj, $method); + try { + $this->policy->checkMethodAllowed($obj, $method); + } catch (SecurityNotAllowedMethodError $e) { + $e->setSourceContext($source); + $e->setTemplateLine($lineno); + + throw $e; + } } } - public function checkPropertyAllowed($obj, $method) + public function checkPropertyAllowed($obj, $property, int $lineno = -1, Source $source = null): void { if ($this->isSandboxed()) { - $this->policy->checkPropertyAllowed($obj, $method); + try { + $this->policy->checkPropertyAllowed($obj, $property); + } catch (SecurityNotAllowedPropertyError $e) { + $e->setSourceContext($source); + $e->setTemplateLine($lineno); + + throw $e; + } } } - public function ensureToStringAllowed($obj) + public function ensureToStringAllowed($obj, int $lineno = -1, Source $source = null) { if ($this->isSandboxed() && \is_object($obj) && method_exists($obj, '__toString')) { - $this->policy->checkMethodAllowed($obj, '__toString'); + try { + $this->policy->checkMethodAllowed($obj, '__toString'); + } catch (SecurityNotAllowedMethodError $e) { + $e->setSourceContext($source); + $e->setTemplateLine($lineno); + + throw $e; + } } return $obj; } - - public function getName() - { - return 'sandbox'; - } } - -class_alias('Twig\Extension\SandboxExtension', 'Twig_Extension_Sandbox'); diff --git a/lib/twig/twig/src/Extension/StagingExtension.php b/lib/twig/twig/src/Extension/StagingExtension.php index 049c5c7977..0ea47f90c5 100644 --- a/lib/twig/twig/src/Extension/StagingExtension.php +++ b/lib/twig/twig/src/Extension/StagingExtension.php @@ -13,105 +13,88 @@ namespace Twig\Extension; use Twig\NodeVisitor\NodeVisitorInterface; use Twig\TokenParser\TokenParserInterface; +use Twig\TwigFilter; +use Twig\TwigFunction; +use Twig\TwigTest; /** - * Internal class. - * - * This class is used by \Twig\Environment as a staging area and must not be used directly. + * Used by \Twig\Environment as a staging area. * * @author Fabien Potencier * * @internal */ -class StagingExtension extends AbstractExtension +final class StagingExtension extends AbstractExtension { - protected $functions = []; - protected $filters = []; - protected $visitors = []; - protected $tokenParsers = []; - protected $globals = []; - protected $tests = []; + private $functions = []; + private $filters = []; + private $visitors = []; + private $tokenParsers = []; + private $tests = []; - public function addFunction($name, $function) + public function addFunction(TwigFunction $function): void { - if (isset($this->functions[$name])) { - @trigger_error(sprintf('Overriding function "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $name), E_USER_DEPRECATED); + if (isset($this->functions[$function->getName()])) { + throw new \LogicException(sprintf('Function "%s" is already registered.', $function->getName())); } - $this->functions[$name] = $function; + $this->functions[$function->getName()] = $function; } - public function getFunctions() + public function getFunctions(): array { return $this->functions; } - public function addFilter($name, $filter) + public function addFilter(TwigFilter $filter): void { - if (isset($this->filters[$name])) { - @trigger_error(sprintf('Overriding filter "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $name), E_USER_DEPRECATED); + if (isset($this->filters[$filter->getName()])) { + throw new \LogicException(sprintf('Filter "%s" is already registered.', $filter->getName())); } - $this->filters[$name] = $filter; + $this->filters[$filter->getName()] = $filter; } - public function getFilters() + public function getFilters(): array { return $this->filters; } - public function addNodeVisitor(NodeVisitorInterface $visitor) + public function addNodeVisitor(NodeVisitorInterface $visitor): void { $this->visitors[] = $visitor; } - public function getNodeVisitors() + public function getNodeVisitors(): array { return $this->visitors; } - public function addTokenParser(TokenParserInterface $parser) + public function addTokenParser(TokenParserInterface $parser): void { if (isset($this->tokenParsers[$parser->getTag()])) { - @trigger_error(sprintf('Overriding tag "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $parser->getTag()), E_USER_DEPRECATED); + throw new \LogicException(sprintf('Tag "%s" is already registered.', $parser->getTag())); } $this->tokenParsers[$parser->getTag()] = $parser; } - public function getTokenParsers() + public function getTokenParsers(): array { return $this->tokenParsers; } - public function addGlobal($name, $value) + public function addTest(TwigTest $test): void { - $this->globals[$name] = $value; - } - - public function getGlobals() - { - return $this->globals; - } - - public function addTest($name, $test) - { - if (isset($this->tests[$name])) { - @trigger_error(sprintf('Overriding test "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $name), E_USER_DEPRECATED); + if (isset($this->tests[$test->getName()])) { + throw new \LogicException(sprintf('Test "%s" is already registered.', $test->getName())); } - $this->tests[$name] = $test; + $this->tests[$test->getName()] = $test; } - public function getTests() + public function getTests(): array { return $this->tests; } - - public function getName() - { - return 'staging'; - } } - -class_alias('Twig\Extension\StagingExtension', 'Twig_Extension_Staging'); diff --git a/lib/twig/twig/src/Extension/StringLoaderExtension.php b/lib/twig/twig/src/Extension/StringLoaderExtension.php index 93ac834ac2..7b45147100 100644 --- a/lib/twig/twig/src/Extension/StringLoaderExtension.php +++ b/lib/twig/twig/src/Extension/StringLoaderExtension.php @@ -12,25 +12,15 @@ namespace Twig\Extension { use Twig\TwigFunction; -/** - * @final - */ -class StringLoaderExtension extends AbstractExtension +final class StringLoaderExtension extends AbstractExtension { - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('template_from_string', 'twig_template_from_string', ['needs_environment' => true]), ]; } - - public function getName() - { - return 'string_loader'; - } } - -class_alias('Twig\Extension\StringLoaderExtension', 'Twig_Extension_StringLoader'); } namespace { @@ -44,10 +34,8 @@ use Twig\TemplateWrapper; * * @param string $template A template as a string or object implementing __toString() * @param string $name An optional name of the template to be used in error messages - * - * @return TemplateWrapper */ -function twig_template_from_string(Environment $env, $template, $name = null) +function twig_template_from_string(Environment $env, $template, string $name = null): TemplateWrapper { return $env->createTemplate((string) $template, $name); } diff --git a/lib/twig/twig/src/ExtensionSet.php b/lib/twig/twig/src/ExtensionSet.php new file mode 100644 index 0000000000..36e5bbc59d --- /dev/null +++ b/lib/twig/twig/src/ExtensionSet.php @@ -0,0 +1,463 @@ + + * + * @internal + */ +final class ExtensionSet +{ + private $extensions; + private $initialized = false; + private $runtimeInitialized = false; + private $staging; + private $parsers; + private $visitors; + private $filters; + private $tests; + private $functions; + private $unaryOperators; + private $binaryOperators; + private $globals; + private $functionCallbacks = []; + private $filterCallbacks = []; + private $parserCallbacks = []; + private $lastModified = 0; + + public function __construct() + { + $this->staging = new StagingExtension(); + } + + public function initRuntime() + { + $this->runtimeInitialized = true; + } + + public function hasExtension(string $class): bool + { + return isset($this->extensions[ltrim($class, '\\')]); + } + + public function getExtension(string $class): ExtensionInterface + { + $class = ltrim($class, '\\'); + + if (!isset($this->extensions[$class])) { + throw new RuntimeError(sprintf('The "%s" extension is not enabled.', $class)); + } + + return $this->extensions[$class]; + } + + /** + * @param ExtensionInterface[] $extensions + */ + public function setExtensions(array $extensions): void + { + foreach ($extensions as $extension) { + $this->addExtension($extension); + } + } + + /** + * @return ExtensionInterface[] + */ + public function getExtensions(): array + { + return $this->extensions; + } + + public function getSignature(): string + { + return json_encode(array_keys($this->extensions)); + } + + public function isInitialized(): bool + { + return $this->initialized || $this->runtimeInitialized; + } + + public function getLastModified(): int + { + if (0 !== $this->lastModified) { + return $this->lastModified; + } + + foreach ($this->extensions as $extension) { + $r = new \ReflectionObject($extension); + if (is_file($r->getFileName()) && ($extensionTime = filemtime($r->getFileName())) > $this->lastModified) { + $this->lastModified = $extensionTime; + } + } + + return $this->lastModified; + } + + public function addExtension(ExtensionInterface $extension): void + { + $class = \get_class($extension); + + if ($this->initialized) { + throw new \LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $class)); + } + + if (isset($this->extensions[$class])) { + throw new \LogicException(sprintf('Unable to register extension "%s" as it is already registered.', $class)); + } + + $this->extensions[$class] = $extension; + } + + public function addFunction(TwigFunction $function): void + { + if ($this->initialized) { + throw new \LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $function->getName())); + } + + $this->staging->addFunction($function); + } + + /** + * @return TwigFunction[] + */ + public function getFunctions(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->functions; + } + + public function getFunction(string $name): ?TwigFunction + { + if (!$this->initialized) { + $this->initExtensions(); + } + + if (isset($this->functions[$name])) { + return $this->functions[$name]; + } + + foreach ($this->functions as $pattern => $function) { + $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); + + if ($count && preg_match('#^'.$pattern.'$#', $name, $matches)) { + array_shift($matches); + $function->setArguments($matches); + + return $function; + } + } + + foreach ($this->functionCallbacks as $callback) { + if (false !== $function = $callback($name)) { + return $function; + } + } + + return null; + } + + public function registerUndefinedFunctionCallback(callable $callable): void + { + $this->functionCallbacks[] = $callable; + } + + public function addFilter(TwigFilter $filter): void + { + if ($this->initialized) { + throw new \LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $filter->getName())); + } + + $this->staging->addFilter($filter); + } + + /** + * @return TwigFilter[] + */ + public function getFilters(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->filters; + } + + public function getFilter(string $name): ?TwigFilter + { + if (!$this->initialized) { + $this->initExtensions(); + } + + if (isset($this->filters[$name])) { + return $this->filters[$name]; + } + + foreach ($this->filters as $pattern => $filter) { + $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); + + if ($count && preg_match('#^'.$pattern.'$#', $name, $matches)) { + array_shift($matches); + $filter->setArguments($matches); + + return $filter; + } + } + + foreach ($this->filterCallbacks as $callback) { + if (false !== $filter = $callback($name)) { + return $filter; + } + } + + return null; + } + + public function registerUndefinedFilterCallback(callable $callable): void + { + $this->filterCallbacks[] = $callable; + } + + public function addNodeVisitor(NodeVisitorInterface $visitor): void + { + if ($this->initialized) { + throw new \LogicException('Unable to add a node visitor as extensions have already been initialized.'); + } + + $this->staging->addNodeVisitor($visitor); + } + + /** + * @return NodeVisitorInterface[] + */ + public function getNodeVisitors(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->visitors; + } + + public function addTokenParser(TokenParserInterface $parser): void + { + if ($this->initialized) { + throw new \LogicException('Unable to add a token parser as extensions have already been initialized.'); + } + + $this->staging->addTokenParser($parser); + } + + /** + * @return TokenParserInterface[] + */ + public function getTokenParsers(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->parsers; + } + + public function getTokenParser(string $name): ?TokenParserInterface + { + if (!$this->initialized) { + $this->initExtensions(); + } + + if (isset($this->parsers[$name])) { + return $this->parsers[$name]; + } + + foreach ($this->parserCallbacks as $callback) { + if (false !== $parser = $callback($name)) { + return $parser; + } + } + + return null; + } + + public function registerUndefinedTokenParserCallback(callable $callable): void + { + $this->parserCallbacks[] = $callable; + } + + public function getGlobals(): array + { + if (null !== $this->globals) { + return $this->globals; + } + + $globals = []; + foreach ($this->extensions as $extension) { + if (!$extension instanceof GlobalsInterface) { + continue; + } + + $extGlobals = $extension->getGlobals(); + if (!\is_array($extGlobals)) { + throw new \UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', \get_class($extension))); + } + + $globals = array_merge($globals, $extGlobals); + } + + if ($this->initialized) { + $this->globals = $globals; + } + + return $globals; + } + + public function addTest(TwigTest $test): void + { + if ($this->initialized) { + throw new \LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $test->getName())); + } + + $this->staging->addTest($test); + } + + /** + * @return TwigTest[] + */ + public function getTests(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->tests; + } + + public function getTest(string $name): ?TwigTest + { + if (!$this->initialized) { + $this->initExtensions(); + } + + if (isset($this->tests[$name])) { + return $this->tests[$name]; + } + + foreach ($this->tests as $pattern => $test) { + $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); + + if ($count) { + if (preg_match('#^'.$pattern.'$#', $name, $matches)) { + array_shift($matches); + $test->setArguments($matches); + + return $test; + } + } + } + + return null; + } + + public function getUnaryOperators(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->unaryOperators; + } + + public function getBinaryOperators(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->binaryOperators; + } + + private function initExtensions(): void + { + $this->parsers = []; + $this->filters = []; + $this->functions = []; + $this->tests = []; + $this->visitors = []; + $this->unaryOperators = []; + $this->binaryOperators = []; + + foreach ($this->extensions as $extension) { + $this->initExtension($extension); + } + $this->initExtension($this->staging); + // Done at the end only, so that an exception during initialization does not mark the environment as initialized when catching the exception + $this->initialized = true; + } + + private function initExtension(ExtensionInterface $extension): void + { + // filters + foreach ($extension->getFilters() as $filter) { + $this->filters[$filter->getName()] = $filter; + } + + // functions + foreach ($extension->getFunctions() as $function) { + $this->functions[$function->getName()] = $function; + } + + // tests + foreach ($extension->getTests() as $test) { + $this->tests[$test->getName()] = $test; + } + + // token parsers + foreach ($extension->getTokenParsers() as $parser) { + if (!$parser instanceof TokenParserInterface) { + throw new \LogicException('getTokenParsers() must return an array of \Twig\TokenParser\TokenParserInterface.'); + } + + $this->parsers[$parser->getTag()] = $parser; + } + + // node visitors + foreach ($extension->getNodeVisitors() as $visitor) { + $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))); + } + + if (2 !== \count($operators)) { + throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', \get_class($extension), \count($operators))); + } + + $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]); + $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]); + } + } +} diff --git a/lib/twig/twig/src/FileExtensionEscapingStrategy.php b/lib/twig/twig/src/FileExtensionEscapingStrategy.php index bc95f33435..65198bbb64 100644 --- a/lib/twig/twig/src/FileExtensionEscapingStrategy.php +++ b/lib/twig/twig/src/FileExtensionEscapingStrategy.php @@ -31,7 +31,7 @@ class FileExtensionEscapingStrategy * * @return string|false The escaping strategy name to use or false to disable */ - public static function guess($name) + public static function guess(string $name) { if (\in_array(substr($name, -1), ['/', '\\'])) { return 'html'; // return html for directories @@ -41,7 +41,7 @@ class FileExtensionEscapingStrategy $name = substr($name, 0, -5); } - $extension = pathinfo($name, PATHINFO_EXTENSION); + $extension = pathinfo($name, \PATHINFO_EXTENSION); switch ($extension) { case 'js': @@ -58,5 +58,3 @@ class FileExtensionEscapingStrategy } } } - -class_alias('Twig\FileExtensionEscapingStrategy', 'Twig_FileExtensionEscapingStrategy'); diff --git a/lib/twig/twig/src/Lexer.php b/lib/twig/twig/src/Lexer.php index 697a6cfa1d..9ff028c87d 100644 --- a/lib/twig/twig/src/Lexer.php +++ b/lib/twig/twig/src/Lexer.php @@ -15,43 +15,38 @@ namespace Twig; use Twig\Error\SyntaxError; /** - * Lexes a template string. - * * @author Fabien Potencier */ -class Lexer implements \Twig_LexerInterface +class Lexer { - protected $tokens; - protected $code; - protected $cursor; - protected $lineno; - protected $end; - protected $state; - protected $states; - protected $brackets; - protected $env; - // to be renamed to $name in 2.0 (where it is private) - protected $filename; - protected $options; - protected $regexes; - protected $position; - protected $positions; - protected $currentVarBlockLine; - + private $tokens; + private $code; + private $cursor; + private $lineno; + private $end; + private $state; + private $states; + private $brackets; + private $env; private $source; + private $options; + private $regexes; + private $position; + private $positions; + private $currentVarBlockLine; - const STATE_DATA = 0; - const STATE_BLOCK = 1; - const STATE_VAR = 2; - const STATE_STRING = 3; - const STATE_INTERPOLATION = 4; + public const STATE_DATA = 0; + public const STATE_BLOCK = 1; + public const STATE_VAR = 2; + public const STATE_STRING = 3; + public const STATE_INTERPOLATION = 4; - const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A'; - const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?([Ee][\+\-][0-9]+)?/A'; - const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As'; - const REGEX_DQ_STRING_DELIM = '/"/A'; - const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As'; - const PUNCTUATION = '()[]{}?:.,|'; + 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_DQ_STRING_DELIM = '/"/A'; + public const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As'; + public const PUNCTUATION = '()[]{}?:.,|'; public function __construct(Environment $env, array $options = []) { @@ -100,9 +95,7 @@ class Lexer implements \Twig_LexerInterface $this->options['whitespace_trim']. // - '|'. $this->options['whitespace_line_trim']. // ~ - ')?\s*'. - '(?:end%s)'. // endraw or endverbatim - '\s*'. + ')?\s*endverbatim\s*'. '(?:'. preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%} '|'. @@ -117,7 +110,7 @@ class Lexer implements \Twig_LexerInterface // #} 'lex_comment' => '{ (?:'. - preg_quote($this->options['whitespace_trim']).preg_quote($this->options['tag_comment'][1], '#').'\s*\n?'. // -#}\s*\n? + 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]* '|'. @@ -127,9 +120,7 @@ class Lexer implements \Twig_LexerInterface // verbatim %} 'lex_block_raw' => '{ - \s* - (raw|verbatim) - \s* + \s*verbatim\s* (?:'. preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%}\s* '|'. @@ -160,28 +151,10 @@ class Lexer implements \Twig_LexerInterface ]; } - public function tokenize($code, $name = null) + public function tokenize(Source $source): TokenStream { - if (!$code instanceof Source) { - @trigger_error(sprintf('Passing a string as the $code argument of %s() is deprecated since version 1.27 and will be removed in 2.0. Pass a \Twig\Source instance instead.', __METHOD__), E_USER_DEPRECATED); - $this->source = new Source($code, $name); - } else { - $this->source = $code; - } - - if (((int) ini_get('mbstring.func_overload')) & 2) { - @trigger_error('Support for having "mbstring.func_overload" different from 0 is deprecated version 1.29 and will be removed in 2.0.', E_USER_DEPRECATED); - } - - if (\function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { - $mbEncoding = mb_internal_encoding(); - mb_internal_encoding('ASCII'); - } else { - $mbEncoding = null; - } - - $this->code = str_replace(["\r\n", "\r"], "\n", $this->source->getCode()); - $this->filename = $this->source->getName(); + $this->source = $source; + $this->code = str_replace(["\r\n", "\r"], "\n", $source->getCode()); $this->cursor = 0; $this->lineno = 1; $this->end = \strlen($this->code); @@ -192,7 +165,7 @@ class Lexer implements \Twig_LexerInterface $this->position = -1; // find all token starts in one go - preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, PREG_OFFSET_CAPTURE); + preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, \PREG_OFFSET_CAPTURE); $this->positions = $matches; while ($this->cursor < $this->end) { @@ -221,25 +194,21 @@ class Lexer implements \Twig_LexerInterface } } - $this->pushToken(Token::EOF_TYPE); + $this->pushToken(/* Token::EOF_TYPE */ -1); if (!empty($this->brackets)) { list($expect, $lineno) = array_pop($this->brackets); throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); } - if ($mbEncoding) { - mb_internal_encoding($mbEncoding); - } - return new TokenStream($this->tokens, $this->source); } - protected function lexData() + private function lexData(): void { // 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->pushToken(/* Token::TEXT_TYPE */ 0, substr($this->code, $this->cursor)); $this->cursor = $this->end; return; @@ -268,7 +237,7 @@ class Lexer implements \Twig_LexerInterface $text = rtrim($text, " \t\0\x0B"); } } - $this->pushToken(Token::TEXT_TYPE, $text); + $this->pushToken(/* Token::TEXT_TYPE */ 0, $text); $this->moveCursor($textContent.$position[0]); switch ($this->positions[1][$this->position][0]) { @@ -280,30 +249,30 @@ class Lexer implements \Twig_LexerInterface // raw data? if (preg_match($this->regexes['lex_block_raw'], $this->code, $match, 0, $this->cursor)) { $this->moveCursor($match[0]); - $this->lexRawData($match[1]); + $this->lexRawData(); // {% line \d+ %} } elseif (preg_match($this->regexes['lex_block_line'], $this->code, $match, 0, $this->cursor)) { $this->moveCursor($match[0]); $this->lineno = (int) $match[1]; } else { - $this->pushToken(Token::BLOCK_START_TYPE); + $this->pushToken(/* Token::BLOCK_START_TYPE */ 1); $this->pushState(self::STATE_BLOCK); $this->currentVarBlockLine = $this->lineno; } break; case $this->options['tag_variable'][0]: - $this->pushToken(Token::VAR_START_TYPE); + $this->pushToken(/* Token::VAR_START_TYPE */ 2); $this->pushState(self::STATE_VAR); $this->currentVarBlockLine = $this->lineno; break; } } - protected function lexBlock() + 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->pushToken(/* Token::BLOCK_END_TYPE */ 3); $this->moveCursor($match[0]); $this->popState(); } else { @@ -311,10 +280,10 @@ class Lexer implements \Twig_LexerInterface } } - protected function lexVar() + 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->pushToken(/* Token::VAR_END_TYPE */ 4); $this->moveCursor($match[0]); $this->popState(); } else { @@ -322,7 +291,7 @@ class Lexer implements \Twig_LexerInterface } } - protected function lexExpression() + private function lexExpression(): void { // whitespace if (preg_match('/\s+/A', $this->code, $match, 0, $this->cursor)) { @@ -340,21 +309,21 @@ class Lexer implements \Twig_LexerInterface } // operators elseif (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) { - $this->pushToken(Token::OPERATOR_TYPE, preg_replace('/\s+/', ' ', $match[0])); + $this->pushToken(/* Token::OPERATOR_TYPE */ 8, preg_replace('/\s+/', ' ', $match[0])); $this->moveCursor($match[0]); } // names elseif (preg_match(self::REGEX_NAME, $this->code, $match, 0, $this->cursor)) { - $this->pushToken(Token::NAME_TYPE, $match[0]); + $this->pushToken(/* Token::NAME_TYPE */ 5, $match[0]); $this->moveCursor($match[0]); } // 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) { + 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 */ 6, $number); $this->moveCursor($match[0]); } // punctuation @@ -375,12 +344,12 @@ class Lexer implements \Twig_LexerInterface } } - $this->pushToken(Token::PUNCTUATION_TYPE, $this->code[$this->cursor]); + $this->pushToken(/* Token::PUNCTUATION_TYPE */ 9, $this->code[$this->cursor]); ++$this->cursor; } // strings elseif (preg_match(self::REGEX_STRING, $this->code, $match, 0, $this->cursor)) { - $this->pushToken(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1))); + $this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes(substr($match[0], 1, -1))); $this->moveCursor($match[0]); } // opening double quoted string @@ -395,14 +364,10 @@ class Lexer implements \Twig_LexerInterface } } - protected function lexRawData($tag) + private function lexRawData(): void { - if ('raw' === $tag) { - @trigger_error(sprintf('Twig Tag "raw" is deprecated since version 1.21. Use "verbatim" instead in %s at line %d.', $this->filename, $this->lineno), E_USER_DEPRECATED); - } - - if (!preg_match(str_replace('%s', $tag, $this->regexes['lex_raw_data']), $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { - throw new SyntaxError(sprintf('Unexpected end of file: Unclosed "%s" block.', $tag), $this->lineno, $this->source); + if (!preg_match($this->regexes['lex_raw_data'], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) { + throw new SyntaxError('Unexpected end of file: Unclosed "verbatim" block.', $this->lineno, $this->source); } $text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor); @@ -420,27 +385,27 @@ class Lexer implements \Twig_LexerInterface } } - $this->pushToken(Token::TEXT_TYPE, $text); + $this->pushToken(/* Token::TEXT_TYPE */ 0, $text); } - protected function lexComment() + private function lexComment(): void { - if (!preg_match($this->regexes['lex_comment'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { + if (!preg_match($this->regexes['lex_comment'], $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]); } - protected function lexString() + private function lexString(): void { if (preg_match($this->regexes['interpolation_start'], $this->code, $match, 0, $this->cursor)) { $this->brackets[] = [$this->options['interpolation'][0], $this->lineno]; - $this->pushToken(Token::INTERPOLATION_START_TYPE); + $this->pushToken(/* Token::INTERPOLATION_START_TYPE */ 10); $this->moveCursor($match[0]); $this->pushState(self::STATE_INTERPOLATION); } elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && \strlen($match[0]) > 0) { - $this->pushToken(Token::STRING_TYPE, stripcslashes($match[0])); + $this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes($match[0])); $this->moveCursor($match[0]); } elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) { list($expect, $lineno) = array_pop($this->brackets); @@ -456,12 +421,12 @@ class Lexer implements \Twig_LexerInterface } } - protected function lexInterpolation() + private function lexInterpolation(): void { $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->pushToken(/* Token::INTERPOLATION_END_TYPE */ 11); $this->moveCursor($match[0]); $this->popState(); } else { @@ -469,23 +434,23 @@ class Lexer implements \Twig_LexerInterface } } - protected function pushToken($type, $value = '') + private function pushToken($type, $value = ''): void { // do not push empty text tokens - if (Token::TEXT_TYPE === $type && '' === $value) { + if (/* Token::TEXT_TYPE */ 0 === $type && '' === $value) { return; } $this->tokens[] = new Token($type, $value, $this->lineno); } - protected function moveCursor($text) + private function moveCursor($text): void { $this->cursor += \strlen($text); $this->lineno += substr_count($text, "\n"); } - protected function getOperatorRegex() + private function getOperatorRegex(): string { $operators = array_merge( ['='], @@ -499,11 +464,15 @@ class Lexer implements \Twig_LexerInterface $regex = []; foreach ($operators as $operator => $length) { // an operator that ends with a character must be followed by - // a whitespace or a parenthesis + // a whitespace, a parenthesis, an opening map [ or sequence { + $r = preg_quote($operator, '/'); if (ctype_alpha($operator[$length - 1])) { - $r = preg_quote($operator, '/').'(?=[\s()])'; - } else { - $r = preg_quote($operator, '/'); + $r .= '(?=[\s()\[{])'; + } + + // an operator that begins with a character must not have a dot or pipe before + if (ctype_alpha($operator[0])) { + $r = '(?states[] = $this->state; $this->state = $state; } - protected function popState() + private function popState(): void { if (0 === \count($this->states)) { throw new \LogicException('Cannot pop state without a previous state.'); @@ -530,5 +499,3 @@ class Lexer implements \Twig_LexerInterface $this->state = array_pop($this->states); } } - -class_alias('Twig\Lexer', 'Twig_Lexer'); diff --git a/lib/twig/twig/src/Loader/ArrayLoader.php b/lib/twig/twig/src/Loader/ArrayLoader.php index 6bc430f50e..5d726c35ac 100644 --- a/lib/twig/twig/src/Loader/ArrayLoader.php +++ b/lib/twig/twig/src/Loader/ArrayLoader.php @@ -24,13 +24,11 @@ use Twig\Source; * * This loader should only be used for unit testing. * - * @final - * * @author Fabien Potencier */ -class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface +final class ArrayLoader implements LoaderInterface { - protected $templates = []; + private $templates = []; /** * @param array $templates An array of templates (keys are the names, and values are the source code) @@ -40,32 +38,13 @@ class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte $this->templates = $templates; } - /** - * Adds or overrides a template. - * - * @param string $name The template name - * @param string $template The template source - */ - public function setTemplate($name, $template) + public function setTemplate(string $name, string $template): void { - $this->templates[(string) $name] = $template; + $this->templates[$name] = $template; } - public function getSource($name) + public function getSourceContext(string $name): Source { - @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', \get_class($this)), E_USER_DEPRECATED); - - $name = (string) $name; - if (!isset($this->templates[$name])) { - throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); - } - - return $this->templates[$name]; - } - - public function getSourceContext($name) - { - $name = (string) $name; if (!isset($this->templates[$name])) { throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); } @@ -73,14 +52,13 @@ class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte return new Source($this->templates[$name], $name); } - public function exists($name) + public function exists(string $name): bool { - return isset($this->templates[(string) $name]); + return isset($this->templates[$name]); } - public function getCacheKey($name) + public function getCacheKey(string $name): string { - $name = (string) $name; if (!isset($this->templates[$name])) { throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); } @@ -88,9 +66,8 @@ class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte return $name.':'.$this->templates[$name]; } - public function isFresh($name, $time) + public function isFresh(string $name, int $time): bool { - $name = (string) $name; if (!isset($this->templates[$name])) { throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); } @@ -98,5 +75,3 @@ class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte return true; } } - -class_alias('Twig\Loader\ArrayLoader', 'Twig_Loader_Array'); diff --git a/lib/twig/twig/src/Loader/ChainLoader.php b/lib/twig/twig/src/Loader/ChainLoader.php index 25ac55a335..fbf4f3a065 100644 --- a/lib/twig/twig/src/Loader/ChainLoader.php +++ b/lib/twig/twig/src/Loader/ChainLoader.php @@ -17,14 +17,12 @@ use Twig\Source; /** * Loads templates from other loaders. * - * @final - * * @author Fabien Potencier */ -class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface +final class ChainLoader implements LoaderInterface { private $hasSourceCache = []; - protected $loaders = []; + private $loaders = []; /** * @param LoaderInterface[] $loaders @@ -36,7 +34,7 @@ class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte } } - public function addLoader(LoaderInterface $loader) + public function addLoader(LoaderInterface $loader): void { $this->loaders[] = $loader; $this->hasSourceCache = []; @@ -45,23 +43,21 @@ class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte /** * @return LoaderInterface[] */ - public function getLoaders() + public function getLoaders(): array { return $this->loaders; } - public function getSource($name) + public function getSourceContext(string $name): Source { - @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', \get_class($this)), E_USER_DEPRECATED); - $exceptions = []; foreach ($this->loaders as $loader) { - if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) { + if (!$loader->exists($name)) { continue; } try { - return $loader->getSource($name); + return $loader->getSourceContext($name); } catch (LoaderError $e) { $exceptions[] = $e->getMessage(); } @@ -70,65 +66,26 @@ class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); } - public function getSourceContext($name) + public function exists(string $name): bool { - $exceptions = []; - foreach ($this->loaders as $loader) { - if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) { - continue; - } - - try { - if ($loader instanceof SourceContextLoaderInterface) { - return $loader->getSourceContext($name); - } - - return new Source($loader->getSource($name), $name); - } catch (LoaderError $e) { - $exceptions[] = $e->getMessage(); - } - } - - throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); - } - - public function exists($name) - { - $name = (string) $name; - if (isset($this->hasSourceCache[$name])) { return $this->hasSourceCache[$name]; } foreach ($this->loaders as $loader) { - if ($loader instanceof ExistsLoaderInterface) { - if ($loader->exists($name)) { - return $this->hasSourceCache[$name] = true; - } - - continue; - } - - try { - if ($loader instanceof SourceContextLoaderInterface) { - $loader->getSourceContext($name); - } else { - $loader->getSource($name); - } - + if ($loader->exists($name)) { return $this->hasSourceCache[$name] = true; - } catch (LoaderError $e) { } } return $this->hasSourceCache[$name] = false; } - public function getCacheKey($name) + public function getCacheKey(string $name): string { $exceptions = []; foreach ($this->loaders as $loader) { - if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) { + if (!$loader->exists($name)) { continue; } @@ -142,11 +99,11 @@ class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); } - public function isFresh($name, $time) + public function isFresh(string $name, int $time): bool { $exceptions = []; foreach ($this->loaders as $loader) { - if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) { + if (!$loader->exists($name)) { continue; } @@ -160,5 +117,3 @@ class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); } } - -class_alias('Twig\Loader\ChainLoader', 'Twig_Loader_Chain'); diff --git a/lib/twig/twig/src/Loader/ExistsLoaderInterface.php b/lib/twig/twig/src/Loader/ExistsLoaderInterface.php deleted file mode 100644 index 940d87618c..0000000000 --- a/lib/twig/twig/src/Loader/ExistsLoaderInterface.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * @deprecated since 1.12 (to be removed in 3.0) - */ -interface ExistsLoaderInterface -{ - /** - * Check if we have the source code of a template, given its name. - * - * @param string $name The name of the template to check if we can load - * - * @return bool If the template source code is handled by this loader or not - */ - public function exists($name); -} - -class_alias('Twig\Loader\ExistsLoaderInterface', 'Twig_ExistsLoaderInterface'); diff --git a/lib/twig/twig/src/Loader/FilesystemLoader.php b/lib/twig/twig/src/Loader/FilesystemLoader.php index 19b43a2954..859a898c53 100644 --- a/lib/twig/twig/src/Loader/FilesystemLoader.php +++ b/lib/twig/twig/src/Loader/FilesystemLoader.php @@ -19,10 +19,10 @@ use Twig\Source; * * @author Fabien Potencier */ -class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface +class FilesystemLoader implements LoaderInterface { /** Identifier of the main namespace. */ - const MAIN_NAMESPACE = '__main__'; + public const MAIN_NAMESPACE = '__main__'; protected $paths = []; protected $cache = []; @@ -34,10 +34,10 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source * @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()) */ - public function __construct($paths = [], $rootPath = null) + public function __construct($paths = [], string $rootPath = null) { $this->rootPath = (null === $rootPath ? getcwd() : $rootPath).\DIRECTORY_SEPARATOR; - if (false !== $realPath = realpath($rootPath)) { + if (null !== $rootPath && false !== ($realPath = realpath($rootPath))) { $this->rootPath = $realPath.\DIRECTORY_SEPARATOR; } @@ -48,35 +48,26 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source /** * Returns the paths to the templates. - * - * @param string $namespace A path namespace - * - * @return array The array of paths where to look for templates */ - public function getPaths($namespace = self::MAIN_NAMESPACE) + public function getPaths(string $namespace = self::MAIN_NAMESPACE): array { - return isset($this->paths[$namespace]) ? $this->paths[$namespace] : []; + return $this->paths[$namespace] ?? []; } /** * Returns the path namespaces. * * The main namespace is always defined. - * - * @return array The array of defined namespaces */ - public function getNamespaces() + public function getNamespaces(): array { return array_keys($this->paths); } /** - * Sets the paths where templates are stored. - * - * @param string|array $paths A path or an array of paths where to look for templates - * @param string $namespace A path namespace + * @param string|array $paths A path or an array of paths where to look for templates */ - public function setPaths($paths, $namespace = self::MAIN_NAMESPACE) + public function setPaths($paths, string $namespace = self::MAIN_NAMESPACE): void { if (!\is_array($paths)) { $paths = [$paths]; @@ -89,14 +80,9 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source } /** - * Adds a path where templates are stored. - * - * @param string $path A path where to look for templates - * @param string $namespace A path namespace - * * @throws LoaderError */ - public function addPath($path, $namespace = self::MAIN_NAMESPACE) + public function addPath(string $path, string $namespace = self::MAIN_NAMESPACE): void { // invalidate the cache $this->cache = $this->errorCache = []; @@ -110,14 +96,9 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source } /** - * Prepends a path where templates are stored. - * - * @param string $path A path where to look for templates - * @param string $namespace A path namespace - * * @throws LoaderError */ - public function prependPath($path, $namespace = self::MAIN_NAMESPACE) + public function prependPath(string $path, string $namespace = self::MAIN_NAMESPACE): void { // invalidate the cache $this->cache = $this->errorCache = []; @@ -136,29 +117,18 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source } } - public function getSource($name) + public function getSourceContext(string $name): Source { - @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', \get_class($this)), E_USER_DEPRECATED); - - if (null === ($path = $this->findTemplate($name)) || false === $path) { - return ''; - } - - return file_get_contents($path); - } - - public function getSourceContext($name) - { - if (null === ($path = $this->findTemplate($name)) || false === $path) { + if (null === $path = $this->findTemplate($name)) { return new Source('', $name, ''); } return new Source(file_get_contents($path), $name, $path); } - public function getCacheKey($name) + public function getCacheKey(string $name): string { - if (null === ($path = $this->findTemplate($name)) || false === $path) { + if (null === $path = $this->findTemplate($name)) { return ''; } $len = \strlen($this->rootPath); @@ -169,7 +139,10 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source return $path; } - public function exists($name) + /** + * @return bool + */ + public function exists(string $name) { $name = $this->normalizeName($name); @@ -177,19 +150,13 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source return true; } - try { - return null !== ($path = $this->findTemplate($name, false)) && false !== $path; - } catch (LoaderError $e) { - @trigger_error(sprintf('In %s::findTemplate(), you must accept a second argument that when set to "false" returns "false" instead of throwing an exception. Not supporting this argument is deprecated since version 1.27.', \get_class($this)), E_USER_DEPRECATED); - - return false; - } + return null !== $this->findTemplate($name, false); } - public function isFresh($name, $time) + public function isFresh(string $name, int $time): bool { // false support to be removed in 3.0 - if (null === ($path = $this->findTemplate($name)) || false === $path) { + if (null === $path = $this->findTemplate($name)) { return false; } @@ -197,15 +164,10 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source } /** - * Checks if the template can be found. - * - * @param string $name The template name - * - * @return string|false|null The template name or false/null + * @return string|null */ - protected function findTemplate($name) + protected function findTemplate(string $name, bool $throw = true) { - $throw = \func_num_args() > 1 ? func_get_arg(1) : true; $name = $this->normalizeName($name); if (isset($this->cache[$name])) { @@ -214,7 +176,7 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source if (isset($this->errorCache[$name])) { if (!$throw) { - return false; + return null; } throw new LoaderError($this->errorCache[$name]); @@ -226,7 +188,7 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source list($namespace, $shortname) = $this->parseName($name); } catch (LoaderError $e) { if (!$throw) { - return false; + return null; } throw $e; @@ -236,7 +198,7 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source $this->errorCache[$name] = sprintf('There are no registered paths for namespace "%s".', $namespace); if (!$throw) { - return false; + return null; } throw new LoaderError($this->errorCache[$name]); @@ -259,13 +221,18 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source $this->errorCache[$name] = sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace])); if (!$throw) { - return false; + return null; } throw new LoaderError($this->errorCache[$name]); } - protected function parseName($name, $default = self::MAIN_NAMESPACE) + private function normalizeName(string $name): string + { + return preg_replace('#/{2,}#', '/', str_replace('\\', '/', $name)); + } + + private function parseName(string $name, string $default = self::MAIN_NAMESPACE): array { if (isset($name[0]) && '@' == $name[0]) { if (false === $pos = strpos($name, '/')) { @@ -281,12 +248,7 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source return [$default, $name]; } - protected function normalizeName($name) - { - return preg_replace('#/{2,}#', '/', str_replace('\\', '/', (string) $name)); - } - - protected function validateName($name) + private function validateName(string $name): void { if (false !== strpos($name, "\0")) { throw new LoaderError('A template name cannot contain NUL bytes.'); @@ -308,16 +270,14 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source } } - private function isAbsolutePath($file) + private function isAbsolutePath(string $file): bool { return strspn($file, '/\\', 0, 1) || (\strlen($file) > 3 && ctype_alpha($file[0]) - && ':' === substr($file, 1, 1) + && ':' === $file[1] && strspn($file, '/\\', 2, 1) ) - || null !== parse_url($file, PHP_URL_SCHEME) + || null !== parse_url($file, \PHP_URL_SCHEME) ; } } - -class_alias('Twig\Loader\FilesystemLoader', 'Twig_Loader_Filesystem'); diff --git a/lib/twig/twig/src/Loader/LoaderInterface.php b/lib/twig/twig/src/Loader/LoaderInterface.php index 15be7a88cd..fec7e85ff7 100644 --- a/lib/twig/twig/src/Loader/LoaderInterface.php +++ b/lib/twig/twig/src/Loader/LoaderInterface.php @@ -12,6 +12,7 @@ namespace Twig\Loader; use Twig\Error\LoaderError; +use Twig\Source; /** * Interface all loaders must implement. @@ -21,41 +22,28 @@ use Twig\Error\LoaderError; interface LoaderInterface { /** - * Gets the source code of a template, given its name. - * - * @param string $name The name of the template to load - * - * @return string The template source code + * Returns the source context for a given template logical name. * * @throws LoaderError When $name is not found - * - * @deprecated since 1.27 (to be removed in 2.0), implement Twig\Loader\SourceContextLoaderInterface */ - public function getSource($name); + public function getSourceContext(string $name): Source; /** * Gets the cache key to use for the cache for a given template name. * - * @param string $name The name of the template to load - * - * @return string The cache key - * * @throws LoaderError When $name is not found */ - public function getCacheKey($name); + public function getCacheKey(string $name): string; /** - * Returns true if the template is still fresh. - * - * @param string $name The template name - * @param int $time Timestamp of the last modification time of the - * cached template - * - * @return bool true if the template is fresh, false otherwise + * @param int $time Timestamp of the last modification time of the cached template * * @throws LoaderError When $name is not found */ - public function isFresh($name, $time); -} + public function isFresh(string $name, int $time): bool; -class_alias('Twig\Loader\LoaderInterface', 'Twig_LoaderInterface'); + /** + * @return bool + */ + public function exists(string $name); +} diff --git a/lib/twig/twig/src/Loader/SourceContextLoaderInterface.php b/lib/twig/twig/src/Loader/SourceContextLoaderInterface.php deleted file mode 100644 index 78b1fcd40e..0000000000 --- a/lib/twig/twig/src/Loader/SourceContextLoaderInterface.php +++ /dev/null @@ -1,38 +0,0 @@ - - * - * @deprecated since 1.27 (to be removed in 3.0) - */ -interface SourceContextLoaderInterface -{ - /** - * Returns the source context for a given template logical name. - * - * @param string $name The template logical name - * - * @return Source - * - * @throws LoaderError When $name is not found - */ - public function getSourceContext($name); -} - -class_alias('Twig\Loader\SourceContextLoaderInterface', 'Twig_SourceContextLoaderInterface'); diff --git a/lib/twig/twig/src/Markup.php b/lib/twig/twig/src/Markup.php index 107941cdf7..1788acc4f7 100644 --- a/lib/twig/twig/src/Markup.php +++ b/lib/twig/twig/src/Markup.php @@ -16,10 +16,10 @@ namespace Twig; * * @author Fabien Potencier */ -class Markup implements \Countable +class Markup implements \Countable, \JsonSerializable { - protected $content; - protected $charset; + private $content; + private $charset; public function __construct($content, $charset) { @@ -32,10 +32,21 @@ class Markup implements \Countable return $this->content; } + /** + * @return int + */ + #[\ReturnTypeWillChange] public function count() { - return \function_exists('mb_get_info') ? mb_strlen($this->content, $this->charset) : \strlen($this->content); + return mb_strlen($this->content, $this->charset); + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->content; } } - -class_alias('Twig\Markup', 'Twig_Markup'); diff --git a/lib/twig/twig/src/Node/AutoEscapeNode.php b/lib/twig/twig/src/Node/AutoEscapeNode.php index a9403066ae..cd970411b8 100644 --- a/lib/twig/twig/src/Node/AutoEscapeNode.php +++ b/lib/twig/twig/src/Node/AutoEscapeNode.php @@ -26,15 +26,13 @@ use Twig\Compiler; */ class AutoEscapeNode extends Node { - public function __construct($value, \Twig_NodeInterface $body, $lineno, $tag = 'autoescape') + public function __construct($value, Node $body, int $lineno, string $tag = 'autoescape') { parent::__construct(['body' => $body], ['value' => $value], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->subcompile($this->getNode('body')); } } - -class_alias('Twig\Node\AutoEscapeNode', 'Twig_Node_AutoEscape'); diff --git a/lib/twig/twig/src/Node/BlockNode.php b/lib/twig/twig/src/Node/BlockNode.php index 1ffc8ca78a..0632ba7475 100644 --- a/lib/twig/twig/src/Node/BlockNode.php +++ b/lib/twig/twig/src/Node/BlockNode.php @@ -21,17 +21,18 @@ use Twig\Compiler; */ class BlockNode extends Node { - public function __construct($name, \Twig_NodeInterface $body, $lineno, $tag = null) + public function __construct(string $name, Node $body, int $lineno, string $tag = null) { parent::__construct(['body' => $body], ['name' => $name], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) ->write(sprintf("public function block_%s(\$context, array \$blocks = [])\n", $this->getAttribute('name')), "{\n") ->indent() + ->write("\$macros = \$this->macros;\n") ; $compiler @@ -41,5 +42,3 @@ class BlockNode extends Node ; } } - -class_alias('Twig\Node\BlockNode', 'Twig_Node_Block'); diff --git a/lib/twig/twig/src/Node/BlockReferenceNode.php b/lib/twig/twig/src/Node/BlockReferenceNode.php index de069093f6..cc8af5b525 100644 --- a/lib/twig/twig/src/Node/BlockReferenceNode.php +++ b/lib/twig/twig/src/Node/BlockReferenceNode.php @@ -21,12 +21,12 @@ use Twig\Compiler; */ class BlockReferenceNode extends Node implements NodeOutputInterface { - public function __construct($name, $lineno, $tag = null) + public function __construct(string $name, int $lineno, string $tag = null) { parent::__construct([], ['name' => $name], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) @@ -34,5 +34,3 @@ class BlockReferenceNode extends Node implements NodeOutputInterface ; } } - -class_alias('Twig\Node\BlockReferenceNode', 'Twig_Node_BlockReference'); diff --git a/lib/twig/twig/src/Node/BodyNode.php b/lib/twig/twig/src/Node/BodyNode.php index 5290be56db..041cbf685b 100644 --- a/lib/twig/twig/src/Node/BodyNode.php +++ b/lib/twig/twig/src/Node/BodyNode.php @@ -19,5 +19,3 @@ namespace Twig\Node; class BodyNode extends Node { } - -class_alias('Twig\Node\BodyNode', 'Twig_Node_Body'); diff --git a/lib/twig/twig/src/Node/CheckSecurityCallNode.php b/lib/twig/twig/src/Node/CheckSecurityCallNode.php new file mode 100644 index 0000000000..a78a38d80b --- /dev/null +++ b/lib/twig/twig/src/Node/CheckSecurityCallNode.php @@ -0,0 +1,28 @@ + + */ +class CheckSecurityCallNode extends Node +{ + public function compile(Compiler $compiler) + { + $compiler + ->write("\$this->sandbox = \$this->env->getExtension('\Twig\Extension\SandboxExtension');\n") + ->write("\$this->checkSecurity();\n") + ; + } +} diff --git a/lib/twig/twig/src/Node/CheckSecurityNode.php b/lib/twig/twig/src/Node/CheckSecurityNode.php index cf0a7a13d8..472732796e 100644 --- a/lib/twig/twig/src/Node/CheckSecurityNode.php +++ b/lib/twig/twig/src/Node/CheckSecurityNode.php @@ -18,9 +18,9 @@ use Twig\Compiler; */ class CheckSecurityNode extends Node { - protected $usedFilters; - protected $usedTags; - protected $usedFunctions; + private $usedFilters; + private $usedTags; + private $usedFunctions; public function __construct(array $usedFilters, array $usedTags, array $usedFunctions) { @@ -31,7 +31,7 @@ class CheckSecurityNode extends Node parent::__construct(); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $tags = $filters = $functions = []; foreach (['tags', 'filters', 'functions'] as $type) { @@ -45,10 +45,13 @@ class CheckSecurityNode extends Node } $compiler - ->write("\$this->sandbox = \$this->env->getExtension('\Twig\Extension\SandboxExtension');\n") - ->write('$tags = ')->repr(array_filter($tags))->raw(";\n") - ->write('$filters = ')->repr(array_filter($filters))->raw(";\n") - ->write('$functions = ')->repr(array_filter($functions))->raw(";\n\n") + ->write("\n") + ->write("public function checkSecurity()\n") + ->write("{\n") + ->indent() + ->write('static $tags = ')->repr(array_filter($tags))->raw(";\n") + ->write('static $filters = ')->repr(array_filter($filters))->raw(";\n") + ->write('static $functions = ')->repr(array_filter($functions))->raw(";\n\n") ->write("try {\n") ->indent() ->write("\$this->sandbox->checkSecurity(\n") @@ -61,7 +64,7 @@ class CheckSecurityNode extends Node ->outdent() ->write("} catch (SecurityError \$e) {\n") ->indent() - ->write("\$e->setSourceContext(\$this->getSourceContext());\n\n") + ->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") @@ -78,8 +81,8 @@ class CheckSecurityNode extends Node ->write("throw \$e;\n") ->outdent() ->write("}\n\n") + ->outdent() + ->write("}\n") ; } } - -class_alias('Twig\Node\CheckSecurityNode', 'Twig_Node_CheckSecurity'); diff --git a/lib/twig/twig/src/Node/CheckToStringNode.php b/lib/twig/twig/src/Node/CheckToStringNode.php index 5d67467916..c7a9d6984e 100644 --- a/lib/twig/twig/src/Node/CheckToStringNode.php +++ b/lib/twig/twig/src/Node/CheckToStringNode.php @@ -31,12 +31,15 @@ class CheckToStringNode extends AbstractExpression parent::__construct(['expr' => $expr], [], $expr->getTemplateLine(), $expr->getNodeTag()); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { + $expr = $this->getNode('expr'); $compiler ->raw('$this->sandbox->ensureToStringAllowed(') - ->subcompile($this->getNode('expr')) - ->raw(')') + ->subcompile($expr) + ->raw(', ') + ->repr($expr->getTemplateLine()) + ->raw(', $this->source)') ; } } diff --git a/lib/twig/twig/src/Node/DeprecatedNode.php b/lib/twig/twig/src/Node/DeprecatedNode.php index 62c0dd4ba5..5ff44307fc 100644 --- a/lib/twig/twig/src/Node/DeprecatedNode.php +++ b/lib/twig/twig/src/Node/DeprecatedNode.php @@ -22,12 +22,12 @@ use Twig\Node\Expression\ConstantExpression; */ class DeprecatedNode extends Node { - public function __construct(AbstractExpression $expr, $lineno, $tag = null) + public function __construct(AbstractExpression $expr, int $lineno, string $tag = null) { parent::__construct(['expr' => $expr], [], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->addDebugInfo($this); @@ -51,5 +51,3 @@ class DeprecatedNode extends Node ; } } - -class_alias('Twig\Node\DeprecatedNode', 'Twig_Node_Deprecated'); diff --git a/lib/twig/twig/src/Node/DoNode.php b/lib/twig/twig/src/Node/DoNode.php index 80c4cea79e..f7783d19f4 100644 --- a/lib/twig/twig/src/Node/DoNode.php +++ b/lib/twig/twig/src/Node/DoNode.php @@ -21,12 +21,12 @@ use Twig\Node\Expression\AbstractExpression; */ class DoNode extends Node { - public function __construct(AbstractExpression $expr, $lineno, $tag = null) + public function __construct(AbstractExpression $expr, int $lineno, string $tag = null) { parent::__construct(['expr' => $expr], [], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) @@ -36,5 +36,3 @@ class DoNode extends Node ; } } - -class_alias('Twig\Node\DoNode', 'Twig_Node_Do'); diff --git a/lib/twig/twig/src/Node/EmbedNode.php b/lib/twig/twig/src/Node/EmbedNode.php index 05051ecec8..903c3f6c7a 100644 --- a/lib/twig/twig/src/Node/EmbedNode.php +++ b/lib/twig/twig/src/Node/EmbedNode.php @@ -23,17 +23,15 @@ use Twig\Node\Expression\ConstantExpression; class EmbedNode extends IncludeNode { // we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module) - public function __construct($name, $index, AbstractExpression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null) + public function __construct(string $name, int $index, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, string $tag = null) { parent::__construct(new ConstantExpression('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag); $this->setAttribute('name', $name); - // to be removed in 2.0, used name instead - $this->setAttribute('filename', $name); $this->setAttribute('index', $index); } - protected function addGetTemplate(Compiler $compiler) + protected function addGetTemplate(Compiler $compiler): void { $compiler ->write('$this->loadTemplate(') @@ -48,5 +46,3 @@ class EmbedNode extends IncludeNode ; } } - -class_alias('Twig\Node\EmbedNode', 'Twig_Node_Embed'); diff --git a/lib/twig/twig/src/Node/Expression/AbstractExpression.php b/lib/twig/twig/src/Node/Expression/AbstractExpression.php index a3528924ca..42da0559d1 100644 --- a/lib/twig/twig/src/Node/Expression/AbstractExpression.php +++ b/lib/twig/twig/src/Node/Expression/AbstractExpression.php @@ -22,5 +22,3 @@ use Twig\Node\Node; abstract class AbstractExpression extends Node { } - -class_alias('Twig\Node\Expression\AbstractExpression', 'Twig_Node_Expression'); diff --git a/lib/twig/twig/src/Node/Expression/ArrayExpression.php b/lib/twig/twig/src/Node/Expression/ArrayExpression.php index cd63f934e4..0e25fe46ad 100644 --- a/lib/twig/twig/src/Node/Expression/ArrayExpression.php +++ b/lib/twig/twig/src/Node/Expression/ArrayExpression.php @@ -15,9 +15,9 @@ use Twig\Compiler; class ArrayExpression extends AbstractExpression { - protected $index; + private $index; - public function __construct(array $elements, $lineno) + public function __construct(array $elements, int $lineno) { parent::__construct($elements, [], $lineno); @@ -29,10 +29,9 @@ class ArrayExpression extends AbstractExpression } } - public function getKeyValuePairs() + public function getKeyValuePairs(): array { $pairs = []; - foreach (array_chunk($this->nodes, 2) as $pair) { $pairs[] = [ 'key' => $pair[0], @@ -43,7 +42,7 @@ class ArrayExpression extends AbstractExpression return $pairs; } - public function hasElement(AbstractExpression $key) + public function hasElement(AbstractExpression $key): bool { foreach ($this->getKeyValuePairs() as $pair) { // we compare the string representation of the keys @@ -56,7 +55,7 @@ class ArrayExpression extends AbstractExpression return false; } - public function addElement(AbstractExpression $value, AbstractExpression $key = null) + public function addElement(AbstractExpression $value, AbstractExpression $key = null): void { if (null === $key) { $key = new ConstantExpression(++$this->index, $value->getTemplateLine()); @@ -65,7 +64,7 @@ class ArrayExpression extends AbstractExpression array_push($this->nodes, $key, $value); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->raw('['); $first = true; @@ -84,5 +83,3 @@ class ArrayExpression extends AbstractExpression $compiler->raw(']'); } } - -class_alias('Twig\Node\Expression\ArrayExpression', 'Twig_Node_Expression_Array'); diff --git a/lib/twig/twig/src/Node/Expression/ArrowFunctionExpression.php b/lib/twig/twig/src/Node/Expression/ArrowFunctionExpression.php index 36b77da86f..eaad03c9c0 100644 --- a/lib/twig/twig/src/Node/Expression/ArrowFunctionExpression.php +++ b/lib/twig/twig/src/Node/Expression/ArrowFunctionExpression.php @@ -26,7 +26,7 @@ class ArrowFunctionExpression extends AbstractExpression parent::__construct(['expr' => $expr, 'names' => $names], [], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) @@ -44,7 +44,7 @@ class ArrowFunctionExpression extends AbstractExpression ; } $compiler - ->raw(') use ($context) { ') + ->raw(') use ($context, $macros) { ') ; foreach ($this->getNode('names') as $name) { $compiler diff --git a/lib/twig/twig/src/Node/Expression/AssignNameExpression.php b/lib/twig/twig/src/Node/Expression/AssignNameExpression.php index 62c4ac0b48..7dd1bc4a37 100644 --- a/lib/twig/twig/src/Node/Expression/AssignNameExpression.php +++ b/lib/twig/twig/src/Node/Expression/AssignNameExpression.php @@ -16,7 +16,7 @@ use Twig\Compiler; class AssignNameExpression extends NameExpression { - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->raw('$context[') @@ -25,5 +25,3 @@ class AssignNameExpression extends NameExpression ; } } - -class_alias('Twig\Node\Expression\AssignNameExpression', 'Twig_Node_Expression_AssignName'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/AbstractBinary.php b/lib/twig/twig/src/Node/Expression/Binary/AbstractBinary.php index 0600aeedbb..c424e5cc5f 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/AbstractBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/AbstractBinary.php @@ -14,15 +14,16 @@ namespace Twig\Node\Expression\Binary; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Node; abstract class AbstractBinary extends AbstractExpression { - public function __construct(\Twig_NodeInterface $left, \Twig_NodeInterface $right, $lineno) + public function __construct(Node $left, Node $right, int $lineno) { parent::__construct(['left' => $left, 'right' => $right], [], $lineno); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->raw('(') @@ -37,7 +38,5 @@ abstract class AbstractBinary extends AbstractExpression ; } - abstract public function operator(Compiler $compiler); + abstract public function operator(Compiler $compiler): Compiler; } - -class_alias('Twig\Node\Expression\Binary\AbstractBinary', 'Twig_Node_Expression_Binary'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/AddBinary.php b/lib/twig/twig/src/Node/Expression/Binary/AddBinary.php index f7719a19ea..ee4307e33e 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/AddBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/AddBinary.php @@ -16,10 +16,8 @@ use Twig\Compiler; class AddBinary extends AbstractBinary { - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw('+'); } } - -class_alias('Twig\Node\Expression\Binary\AddBinary', 'Twig_Node_Expression_Binary_Add'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/AndBinary.php b/lib/twig/twig/src/Node/Expression/Binary/AndBinary.php index 484597da77..5f2380da54 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/AndBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/AndBinary.php @@ -16,10 +16,8 @@ use Twig\Compiler; class AndBinary extends AbstractBinary { - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw('&&'); } } - -class_alias('Twig\Node\Expression\Binary\AndBinary', 'Twig_Node_Expression_Binary_And'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php b/lib/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php index cf286912b2..db7d6d612d 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php @@ -16,10 +16,8 @@ use Twig\Compiler; class BitwiseAndBinary extends AbstractBinary { - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw('&'); } } - -class_alias('Twig\Node\Expression\Binary\BitwiseAndBinary', 'Twig_Node_Expression_Binary_BitwiseAnd'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php b/lib/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php index 7d5d260079..ce803dd902 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php @@ -16,10 +16,8 @@ use Twig\Compiler; class BitwiseOrBinary extends AbstractBinary { - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw('|'); } } - -class_alias('Twig\Node\Expression\Binary\BitwiseOrBinary', 'Twig_Node_Expression_Binary_BitwiseOr'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php b/lib/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php index 7291987195..5c29785014 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php @@ -16,10 +16,8 @@ use Twig\Compiler; class BitwiseXorBinary extends AbstractBinary { - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw('^'); } } - -class_alias('Twig\Node\Expression\Binary\BitwiseXorBinary', 'Twig_Node_Expression_Binary_BitwiseXor'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/ConcatBinary.php b/lib/twig/twig/src/Node/Expression/Binary/ConcatBinary.php index f6e5938fdd..f825ab874d 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/ConcatBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/ConcatBinary.php @@ -16,10 +16,8 @@ use Twig\Compiler; class ConcatBinary extends AbstractBinary { - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw('.'); } } - -class_alias('Twig\Node\Expression\Binary\ConcatBinary', 'Twig_Node_Expression_Binary_Concat'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/DivBinary.php b/lib/twig/twig/src/Node/Expression/Binary/DivBinary.php index ebfcc758b6..e3817d1cd7 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/DivBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/DivBinary.php @@ -16,10 +16,8 @@ use Twig\Compiler; class DivBinary extends AbstractBinary { - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw('/'); } } - -class_alias('Twig\Node\Expression\Binary\DivBinary', 'Twig_Node_Expression_Binary_Div'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php b/lib/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php index 41a0065bbc..c3516b853f 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php @@ -15,7 +15,7 @@ use Twig\Compiler; class EndsWithBinary extends AbstractBinary { - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $left = $compiler->getVarName(); $right = $compiler->getVarName(); @@ -28,10 +28,8 @@ class EndsWithBinary extends AbstractBinary ; } - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw(''); } } - -class_alias('Twig\Node\Expression\Binary\EndsWithBinary', 'Twig_Node_Expression_Binary_EndsWith'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/EqualBinary.php b/lib/twig/twig/src/Node/Expression/Binary/EqualBinary.php index 84904c364a..6b48549ef2 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/EqualBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/EqualBinary.php @@ -15,10 +15,25 @@ use Twig\Compiler; class EqualBinary extends AbstractBinary { - public function operator(Compiler $compiler) + public function compile(Compiler $compiler): void + { + if (\PHP_VERSION_ID >= 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(0 === twig_compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler { return $compiler->raw('=='); } } - -class_alias('Twig\Node\Expression\Binary\EqualBinary', 'Twig_Node_Expression_Binary_Equal'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php b/lib/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php index 4dd5e3d32b..d7e7980efd 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php @@ -15,17 +15,15 @@ use Twig\Compiler; class FloorDivBinary extends AbstractBinary { - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->raw('(int) floor('); parent::compile($compiler); $compiler->raw(')'); } - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw('/'); } } - -class_alias('Twig\Node\Expression\Binary\FloorDivBinary', 'Twig_Node_Expression_Binary_FloorDiv'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/GreaterBinary.php b/lib/twig/twig/src/Node/Expression/Binary/GreaterBinary.php index be73001e5b..e1dd06780b 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/GreaterBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/GreaterBinary.php @@ -15,10 +15,25 @@ use Twig\Compiler; class GreaterBinary extends AbstractBinary { - public function operator(Compiler $compiler) + public function compile(Compiler $compiler): void + { + if (\PHP_VERSION_ID >= 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(1 === twig_compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler { return $compiler->raw('>'); } } - -class_alias('Twig\Node\Expression\Binary\GreaterBinary', 'Twig_Node_Expression_Binary_Greater'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php b/lib/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php index 5c2ae72ee2..df9bfcfbf9 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php @@ -15,10 +15,25 @@ use Twig\Compiler; class GreaterEqualBinary extends AbstractBinary { - public function operator(Compiler $compiler) + public function compile(Compiler $compiler): void + { + if (\PHP_VERSION_ID >= 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(0 <= twig_compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler { return $compiler->raw('>='); } } - -class_alias('Twig\Node\Expression\Binary\GreaterEqualBinary', 'Twig_Node_Expression_Binary_GreaterEqual'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/InBinary.php b/lib/twig/twig/src/Node/Expression/Binary/InBinary.php index f00b23060f..6dbfa97f05 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/InBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/InBinary.php @@ -15,7 +15,7 @@ use Twig\Compiler; class InBinary extends AbstractBinary { - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->raw('twig_in_filter(') @@ -26,10 +26,8 @@ class InBinary extends AbstractBinary ; } - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw('in'); } } - -class_alias('Twig\Node\Expression\Binary\InBinary', 'Twig_Node_Expression_Binary_In'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/LessBinary.php b/lib/twig/twig/src/Node/Expression/Binary/LessBinary.php index 2b202daa72..598e629134 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/LessBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/LessBinary.php @@ -15,10 +15,25 @@ use Twig\Compiler; class LessBinary extends AbstractBinary { - public function operator(Compiler $compiler) + public function compile(Compiler $compiler): void + { + if (\PHP_VERSION_ID >= 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(-1 === twig_compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler { return $compiler->raw('<'); } } - -class_alias('Twig\Node\Expression\Binary\LessBinary', 'Twig_Node_Expression_Binary_Less'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php b/lib/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php index 4fffafea6d..e3c4af58d4 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php @@ -15,10 +15,25 @@ use Twig\Compiler; class LessEqualBinary extends AbstractBinary { - public function operator(Compiler $compiler) + public function compile(Compiler $compiler): void + { + if (\PHP_VERSION_ID >= 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(0 >= twig_compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler { return $compiler->raw('<='); } } - -class_alias('Twig\Node\Expression\Binary\LessEqualBinary', 'Twig_Node_Expression_Binary_LessEqual'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/MatchesBinary.php b/lib/twig/twig/src/Node/Expression/Binary/MatchesBinary.php index ae810b2664..bc97292cda 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/MatchesBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/MatchesBinary.php @@ -15,7 +15,7 @@ use Twig\Compiler; class MatchesBinary extends AbstractBinary { - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->raw('preg_match(') @@ -26,10 +26,8 @@ class MatchesBinary extends AbstractBinary ; } - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw(''); } } - -class_alias('Twig\Node\Expression\Binary\MatchesBinary', 'Twig_Node_Expression_Binary_Matches'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/ModBinary.php b/lib/twig/twig/src/Node/Expression/Binary/ModBinary.php index e6a2b36034..271b45cac8 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/ModBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/ModBinary.php @@ -16,10 +16,8 @@ use Twig\Compiler; class ModBinary extends AbstractBinary { - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw('%'); } } - -class_alias('Twig\Node\Expression\Binary\ModBinary', 'Twig_Node_Expression_Binary_Mod'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/MulBinary.php b/lib/twig/twig/src/Node/Expression/Binary/MulBinary.php index cd65f5dff2..6d4c1e0b6b 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/MulBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/MulBinary.php @@ -16,10 +16,8 @@ use Twig\Compiler; class MulBinary extends AbstractBinary { - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw('*'); } } - -class_alias('Twig\Node\Expression\Binary\MulBinary', 'Twig_Node_Expression_Binary_Mul'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php b/lib/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php index df5c6a2388..db47a28905 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php @@ -15,10 +15,25 @@ use Twig\Compiler; class NotEqualBinary extends AbstractBinary { - public function operator(Compiler $compiler) + public function compile(Compiler $compiler): void + { + if (\PHP_VERSION_ID >= 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(0 !== twig_compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler { return $compiler->raw('!='); } } - -class_alias('Twig\Node\Expression\Binary\NotEqualBinary', 'Twig_Node_Expression_Binary_NotEqual'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/NotInBinary.php b/lib/twig/twig/src/Node/Expression/Binary/NotInBinary.php index ed2034e35a..fcba6cca1c 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/NotInBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/NotInBinary.php @@ -15,7 +15,7 @@ use Twig\Compiler; class NotInBinary extends AbstractBinary { - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->raw('!twig_in_filter(') @@ -26,10 +26,8 @@ class NotInBinary extends AbstractBinary ; } - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw('not in'); } } - -class_alias('Twig\Node\Expression\Binary\NotInBinary', 'Twig_Node_Expression_Binary_NotIn'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/OrBinary.php b/lib/twig/twig/src/Node/Expression/Binary/OrBinary.php index 8f9da43105..21f87c91b4 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/OrBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/OrBinary.php @@ -16,10 +16,8 @@ use Twig\Compiler; class OrBinary extends AbstractBinary { - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw('||'); } } - -class_alias('Twig\Node\Expression\Binary\OrBinary', 'Twig_Node_Expression_Binary_Or'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/PowerBinary.php b/lib/twig/twig/src/Node/Expression/Binary/PowerBinary.php index 10a8d94cfe..c9f4c6697d 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/PowerBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/PowerBinary.php @@ -15,25 +15,8 @@ use Twig\Compiler; class PowerBinary extends AbstractBinary { - public function compile(Compiler $compiler) - { - if (\PHP_VERSION_ID >= 50600) { - return parent::compile($compiler); - } - - $compiler - ->raw('pow(') - ->subcompile($this->getNode('left')) - ->raw(', ') - ->subcompile($this->getNode('right')) - ->raw(')') - ; - } - - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw('**'); } } - -class_alias('Twig\Node\Expression\Binary\PowerBinary', 'Twig_Node_Expression_Binary_Power'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/RangeBinary.php b/lib/twig/twig/src/Node/Expression/Binary/RangeBinary.php index e9c0cdf5e4..55982c819d 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/RangeBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/RangeBinary.php @@ -15,7 +15,7 @@ use Twig\Compiler; class RangeBinary extends AbstractBinary { - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->raw('range(') @@ -26,10 +26,8 @@ class RangeBinary extends AbstractBinary ; } - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw('..'); } } - -class_alias('Twig\Node\Expression\Binary\RangeBinary', 'Twig_Node_Expression_Binary_Range'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php b/lib/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php new file mode 100644 index 0000000000..ae5a4a4937 --- /dev/null +++ b/lib/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php @@ -0,0 +1,22 @@ +raw('<=>'); + } +} diff --git a/lib/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php b/lib/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php index 1fe59fb417..d0df1c4b63 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php @@ -15,7 +15,7 @@ use Twig\Compiler; class StartsWithBinary extends AbstractBinary { - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $left = $compiler->getVarName(); $right = $compiler->getVarName(); @@ -28,10 +28,8 @@ class StartsWithBinary extends AbstractBinary ; } - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw(''); } } - -class_alias('Twig\Node\Expression\Binary\StartsWithBinary', 'Twig_Node_Expression_Binary_StartsWith'); diff --git a/lib/twig/twig/src/Node/Expression/Binary/SubBinary.php b/lib/twig/twig/src/Node/Expression/Binary/SubBinary.php index 25469750aa..eeb87faca9 100644 --- a/lib/twig/twig/src/Node/Expression/Binary/SubBinary.php +++ b/lib/twig/twig/src/Node/Expression/Binary/SubBinary.php @@ -16,10 +16,8 @@ use Twig\Compiler; class SubBinary extends AbstractBinary { - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { return $compiler->raw('-'); } } - -class_alias('Twig\Node\Expression\Binary\SubBinary', 'Twig_Node_Expression_Binary_Sub'); diff --git a/lib/twig/twig/src/Node/Expression/BlockReferenceExpression.php b/lib/twig/twig/src/Node/Expression/BlockReferenceExpression.php index 0a56849c7a..b1e2a8f7bb 100644 --- a/lib/twig/twig/src/Node/Expression/BlockReferenceExpression.php +++ b/lib/twig/twig/src/Node/Expression/BlockReferenceExpression.php @@ -22,17 +22,8 @@ use Twig\Node\Node; */ class BlockReferenceExpression extends AbstractExpression { - /** - * @param Node|null $template - */ - public function __construct(\Twig_NodeInterface $name, $template = null, $lineno, $tag = null) + public function __construct(Node $name, ?Node $template, int $lineno, string $tag = null) { - if (\is_bool($template)) { - @trigger_error(sprintf('The %s method "$asString" argument is deprecated since version 1.28 and will be removed in 2.0.', __METHOD__), E_USER_DEPRECATED); - - $template = null; - } - $nodes = ['name' => $name]; if (null !== $template) { $nodes['template'] = $template; @@ -41,7 +32,7 @@ class BlockReferenceExpression extends AbstractExpression parent::__construct($nodes, ['is_defined_test' => false, 'output' => false], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { if ($this->getAttribute('is_defined_test')) { $this->compileTemplateCall($compiler, 'hasBlock'); @@ -58,7 +49,7 @@ class BlockReferenceExpression extends AbstractExpression } } - private function compileTemplateCall(Compiler $compiler, $method) + private function compileTemplateCall(Compiler $compiler, string $method): Compiler { if (!$this->hasNode('template')) { $compiler->write('$this'); @@ -75,12 +66,11 @@ class BlockReferenceExpression extends AbstractExpression } $compiler->raw(sprintf('->%s', $method)); - $this->compileBlockArguments($compiler); - return $compiler; + return $this->compileBlockArguments($compiler); } - private function compileBlockArguments(Compiler $compiler) + private function compileBlockArguments(Compiler $compiler): Compiler { $compiler ->raw('(') @@ -94,5 +84,3 @@ class BlockReferenceExpression extends AbstractExpression return $compiler->raw(')'); } } - -class_alias('Twig\Node\Expression\BlockReferenceExpression', 'Twig_Node_Expression_BlockReference'); diff --git a/lib/twig/twig/src/Node/Expression/CallExpression.php b/lib/twig/twig/src/Node/Expression/CallExpression.php index d202a73953..28881066bc 100644 --- a/lib/twig/twig/src/Node/Expression/CallExpression.php +++ b/lib/twig/twig/src/Node/Expression/CallExpression.php @@ -22,40 +22,40 @@ abstract class CallExpression extends AbstractExpression protected function compileCallable(Compiler $compiler) { - $closingParenthesis = false; - $isArray = false; - if ($this->hasAttribute('callable') && $callable = $this->getAttribute('callable')) { - if (\is_string($callable) && false === strpos($callable, '::')) { - $compiler->raw($callable); - } else { - list($r, $callable) = $this->reflectCallable($callable); - if ($r instanceof \ReflectionMethod && \is_string($callable[0])) { - if ($r->isStatic()) { - $compiler->raw(sprintf('%s::%s', $callable[0], $callable[1])); - } else { - $compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1])); - } - } elseif ($r instanceof \ReflectionMethod && $callable[0] instanceof ExtensionInterface) { - $compiler->raw(sprintf('$this->env->getExtension(\'%s\')->%s', \get_class($callable[0]), $callable[1])); - } else { - $type = ucfirst($this->getAttribute('type')); - $compiler->raw(sprintf('call_user_func_array($this->env->get%s(\'%s\')->getCallable(), ', $type, $this->getAttribute('name'))); - $closingParenthesis = true; - $isArray = true; - } - } + $callable = $this->getAttribute('callable'); + + if (\is_string($callable) && false === strpos($callable, '::')) { + $compiler->raw($callable); } else { - $compiler->raw($this->getAttribute('thing')->compile()); + [$r, $callable] = $this->reflectCallable($callable); + + if (\is_string($callable)) { + $compiler->raw($callable); + } elseif (\is_array($callable) && \is_string($callable[0])) { + if (!$r instanceof \ReflectionMethod || $r->isStatic()) { + $compiler->raw(sprintf('%s::%s', $callable[0], $callable[1])); + } else { + $compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1])); + } + } elseif (\is_array($callable) && $callable[0] instanceof ExtensionInterface) { + $class = \get_class($callable[0]); + if (!$compiler->getEnvironment()->hasExtension($class)) { + // Compile a non-optimized call to trigger a \Twig\Error\RuntimeError, which cannot be a compile-time error + $compiler->raw(sprintf('$this->env->getExtension(\'%s\')', $class)); + } else { + $compiler->raw(sprintf('$this->extensions[\'%s\']', ltrim($class, '\\'))); + } + + $compiler->raw(sprintf('->%s', $callable[1])); + } else { + $compiler->raw(sprintf('$this->env->get%s(\'%s\')->getCallable()', ucfirst($this->getAttribute('type')), $this->getAttribute('name'))); + } } - $this->compileArguments($compiler, $isArray); - - if ($closingParenthesis) { - $compiler->raw(')'); - } + $this->compileArguments($compiler); } - protected function compileArguments(Compiler $compiler, $isArray = false) + protected function compileArguments(Compiler $compiler, $isArray = false): void { $compiler->raw($isArray ? '[' : '('); @@ -93,10 +93,8 @@ abstract class CallExpression extends AbstractExpression } if ($this->hasNode('arguments')) { - $callable = $this->hasAttribute('callable') ? $this->getAttribute('callable') : null; - + $callable = $this->getAttribute('callable'); $arguments = $this->getArguments($callable, $this->getNode('arguments')); - foreach ($arguments as $node) { if (!$first) { $compiler->raw(', '); @@ -142,14 +140,23 @@ abstract class CallExpression extends AbstractExpression throw new \LogicException($message); } - $callableParameters = $this->getCallableParameters($callable, $isVariadic); + list($callableParameters, $isPhpVariadic) = $this->getCallableParameters($callable, $isVariadic); $arguments = []; $names = []; $missingArguments = []; $optionalArguments = []; $pos = 0; foreach ($callableParameters as $callableParameter) { - $names[] = $name = $this->normalizeName($callableParameter->name); + $name = $this->normalizeName($callableParameter->name); + if (\PHP_VERSION_ID >= 80000 && 'range' === $callable) { + if ('start' === $name) { + $name = 'low'; + } elseif ('end' === $name) { + $name = 'high'; + } + } + + $names[] = $name; if (\array_key_exists($name, $parameters)) { if (\array_key_exists($pos, $parameters)) { @@ -187,7 +194,7 @@ abstract class CallExpression extends AbstractExpression } if ($isVariadic) { - $arbitraryArguments = new ArrayExpression([], -1); + $arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], -1) : new ArrayExpression([], -1); foreach ($parameters as $key => $value) { if (\is_int($key)) { $arbitraryArguments->addElement($value); @@ -225,17 +232,14 @@ abstract class CallExpression extends AbstractExpression return $arguments; } - protected function normalizeName($name) + protected 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)); } - private function getCallableParameters($callable, $isVariadic) + private function getCallableParameters($callable, bool $isVariadic): array { - list($r) = $this->reflectCallable($callable); - if (null === $r) { - return []; - } + [$r, , $callableName] = $this->reflectCallable($callable); $parameters = $r->getParameters(); if ($this->hasNode('node')) { @@ -252,21 +256,21 @@ abstract class CallExpression extends AbstractExpression array_shift($parameters); } } + $isPhpVariadic = false; if ($isVariadic) { $argument = end($parameters); - if ($argument && $argument->isArray() && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) { + $isArray = $argument && $argument->hasType() && 'array' === $argument->getType()->getName(); + if ($isArray && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) { array_pop($parameters); + } elseif ($argument && $argument->isVariadic()) { + array_pop($parameters); + $isPhpVariadic = true; } else { - $callableName = $r->name; - if ($r instanceof \ReflectionMethod) { - $callableName = $r->getDeclaringClass()->name.'::'.$callableName; - } - throw new \LogicException(sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $callableName, $this->getAttribute('type'), $this->getAttribute('name'))); } } - return $parameters; + return [$parameters, $isPhpVariadic]; } private function reflectCallable($callable) @@ -275,31 +279,41 @@ abstract class CallExpression extends AbstractExpression return $this->reflector; } - if (\is_array($callable)) { - if (!method_exists($callable[0], $callable[1])) { - // __call() - return [null, []]; - } - $r = new \ReflectionMethod($callable[0], $callable[1]); - } elseif (\is_object($callable) && !$callable instanceof \Closure) { - $r = new \ReflectionObject($callable); - $r = $r->getMethod('__invoke'); - $callable = [$callable, '__invoke']; - } elseif (\is_string($callable) && false !== $pos = strpos($callable, '::')) { - $class = substr($callable, 0, $pos); - $method = substr($callable, $pos + 2); - if (!method_exists($class, $method)) { - // __staticCall() - return [null, []]; - } - $r = new \ReflectionMethod($callable); - $callable = [$class, $method]; - } else { - $r = new \ReflectionFunction($callable); + if (\is_string($callable) && false !== $pos = strpos($callable, '::')) { + $callable = [substr($callable, 0, $pos), substr($callable, 2 + $pos)]; } - return $this->reflector = [$r, $callable]; + if (\is_array($callable) && method_exists($callable[0], $callable[1])) { + $r = new \ReflectionMethod($callable[0], $callable[1]); + + return $this->reflector = [$r, $callable, $r->class.'::'.$r->name]; + } + + $checkVisibility = $callable instanceof \Closure; + try { + $closure = \Closure::fromCallable($callable); + } catch (\TypeError $e) { + throw new \LogicException(sprintf('Callback for %s "%s" is not callable in the current scope.', $this->getAttribute('type'), $this->getAttribute('name')), 0, $e); + } + $r = new \ReflectionFunction($closure); + + if (false !== strpos($r->name, '{closure}')) { + return $this->reflector = [$r, $callable, 'Closure']; + } + + if ($object = $r->getClosureThis()) { + $callable = [$object, $r->name]; + $callableName = (\function_exists('get_debug_type') ? get_debug_type($object) : \get_class($object)).'::'.$r->name; + } elseif ($class = $r->getClosureScopeClass()) { + $callableName = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name; + } else { + $callable = $callableName = $r->name; + } + + if ($checkVisibility && \is_array($callable) && method_exists(...$callable) && !(new \ReflectionMethod(...$callable))->isPublic()) { + $callable = $r->getClosure(); + } + + return $this->reflector = [$r, $callable, $callableName]; } } - -class_alias('Twig\Node\Expression\CallExpression', 'Twig_Node_Expression_Call'); diff --git a/lib/twig/twig/src/Node/Expression/ConditionalExpression.php b/lib/twig/twig/src/Node/Expression/ConditionalExpression.php index b611218d31..2c7bd0a276 100644 --- a/lib/twig/twig/src/Node/Expression/ConditionalExpression.php +++ b/lib/twig/twig/src/Node/Expression/ConditionalExpression.php @@ -16,12 +16,12 @@ use Twig\Compiler; class ConditionalExpression extends AbstractExpression { - public function __construct(AbstractExpression $expr1, AbstractExpression $expr2, AbstractExpression $expr3, $lineno) + public function __construct(AbstractExpression $expr1, AbstractExpression $expr2, AbstractExpression $expr3, int $lineno) { parent::__construct(['expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3], [], $lineno); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->raw('((') @@ -34,5 +34,3 @@ class ConditionalExpression extends AbstractExpression ; } } - -class_alias('Twig\Node\Expression\ConditionalExpression', 'Twig_Node_Expression_Conditional'); diff --git a/lib/twig/twig/src/Node/Expression/ConstantExpression.php b/lib/twig/twig/src/Node/Expression/ConstantExpression.php index fd58264dca..7ddbcc6fa9 100644 --- a/lib/twig/twig/src/Node/Expression/ConstantExpression.php +++ b/lib/twig/twig/src/Node/Expression/ConstantExpression.php @@ -16,15 +16,13 @@ use Twig\Compiler; class ConstantExpression extends AbstractExpression { - public function __construct($value, $lineno) + public function __construct($value, int $lineno) { parent::__construct([], ['value' => $value], $lineno); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->repr($this->getAttribute('value')); } } - -class_alias('Twig\Node\Expression\ConstantExpression', 'Twig_Node_Expression_Constant'); diff --git a/lib/twig/twig/src/Node/Expression/Filter/DefaultFilter.php b/lib/twig/twig/src/Node/Expression/Filter/DefaultFilter.php index 7c5e2d20aa..6a572d4884 100644 --- a/lib/twig/twig/src/Node/Expression/Filter/DefaultFilter.php +++ b/lib/twig/twig/src/Node/Expression/Filter/DefaultFilter.php @@ -29,7 +29,7 @@ use Twig\Node\Node; */ class DefaultFilter extends FilterExpression { - public function __construct(\Twig_NodeInterface $node, ConstantExpression $filterName, \Twig_NodeInterface $arguments, $lineno, $tag = null) + public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, string $tag = null) { $default = new FilterExpression($node, new ConstantExpression('default', $node->getTemplateLine()), $arguments, $node->getTemplateLine()); @@ -45,10 +45,8 @@ class DefaultFilter extends FilterExpression parent::__construct($node, $filterName, $arguments, $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->subcompile($this->getNode('node')); } } - -class_alias('Twig\Node\Expression\Filter\DefaultFilter', 'Twig_Node_Expression_Filter_Default'); diff --git a/lib/twig/twig/src/Node/Expression/FilterExpression.php b/lib/twig/twig/src/Node/Expression/FilterExpression.php index 6131c2fd40..0fc1588696 100644 --- a/lib/twig/twig/src/Node/Expression/FilterExpression.php +++ b/lib/twig/twig/src/Node/Expression/FilterExpression.php @@ -13,35 +13,28 @@ namespace Twig\Node\Expression; use Twig\Compiler; -use Twig\TwigFilter; +use Twig\Node\Node; class FilterExpression extends CallExpression { - public function __construct(\Twig_NodeInterface $node, ConstantExpression $filterName, \Twig_NodeInterface $arguments, $lineno, $tag = null) + public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, string $tag = null) { parent::__construct(['node' => $node, 'filter' => $filterName, 'arguments' => $arguments], [], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $name = $this->getNode('filter')->getAttribute('value'); $filter = $compiler->getEnvironment()->getFilter($name); $this->setAttribute('name', $name); $this->setAttribute('type', 'filter'); - $this->setAttribute('thing', $filter); $this->setAttribute('needs_environment', $filter->needsEnvironment()); $this->setAttribute('needs_context', $filter->needsContext()); $this->setAttribute('arguments', $filter->getArguments()); - if ($filter instanceof \Twig_FilterCallableInterface || $filter instanceof TwigFilter) { - $this->setAttribute('callable', $filter->getCallable()); - } - if ($filter instanceof TwigFilter) { - $this->setAttribute('is_variadic', $filter->isVariadic()); - } + $this->setAttribute('callable', $filter->getCallable()); + $this->setAttribute('is_variadic', $filter->isVariadic()); $this->compileCallable($compiler); } } - -class_alias('Twig\Node\Expression\FilterExpression', 'Twig_Node_Expression_Filter'); diff --git a/lib/twig/twig/src/Node/Expression/FunctionExpression.php b/lib/twig/twig/src/Node/Expression/FunctionExpression.php index cf2c72b635..71269775c3 100644 --- a/lib/twig/twig/src/Node/Expression/FunctionExpression.php +++ b/lib/twig/twig/src/Node/Expression/FunctionExpression.php @@ -12,11 +12,11 @@ namespace Twig\Node\Expression; use Twig\Compiler; -use Twig\TwigFunction; +use Twig\Node\Node; class FunctionExpression extends CallExpression { - public function __construct($name, \Twig_NodeInterface $arguments, $lineno) + public function __construct(string $name, Node $arguments, int $lineno) { parent::__construct(['arguments' => $arguments], ['name' => $name, 'is_defined_test' => false], $lineno); } @@ -28,24 +28,16 @@ class FunctionExpression extends CallExpression $this->setAttribute('name', $name); $this->setAttribute('type', 'function'); - $this->setAttribute('thing', $function); $this->setAttribute('needs_environment', $function->needsEnvironment()); $this->setAttribute('needs_context', $function->needsContext()); $this->setAttribute('arguments', $function->getArguments()); - if ($function instanceof \Twig_FunctionCallableInterface || $function instanceof TwigFunction) { - $callable = $function->getCallable(); - if ('constant' === $name && $this->getAttribute('is_defined_test')) { - $callable = 'twig_constant_is_defined'; - } - - $this->setAttribute('callable', $callable); - } - if ($function instanceof TwigFunction) { - $this->setAttribute('is_variadic', $function->isVariadic()); + $callable = $function->getCallable(); + if ('constant' === $name && $this->getAttribute('is_defined_test')) { + $callable = 'twig_constant_is_defined'; } + $this->setAttribute('callable', $callable); + $this->setAttribute('is_variadic', $function->isVariadic()); $this->compileCallable($compiler); } } - -class_alias('Twig\Node\Expression\FunctionExpression', 'Twig_Node_Expression_Function'); diff --git a/lib/twig/twig/src/Node/Expression/GetAttrExpression.php b/lib/twig/twig/src/Node/Expression/GetAttrExpression.php index b790bf7af7..e6a75ce940 100644 --- a/lib/twig/twig/src/Node/Expression/GetAttrExpression.php +++ b/lib/twig/twig/src/Node/Expression/GetAttrExpression.php @@ -13,68 +13,75 @@ namespace Twig\Node\Expression; use Twig\Compiler; +use Twig\Extension\SandboxExtension; use Twig\Template; class GetAttrExpression extends AbstractExpression { - public function __construct(AbstractExpression $node, AbstractExpression $attribute, AbstractExpression $arguments = null, $type, $lineno) + public function __construct(AbstractExpression $node, AbstractExpression $attribute, ?AbstractExpression $arguments, string $type, int $lineno) { $nodes = ['node' => $node, 'attribute' => $attribute]; if (null !== $arguments) { $nodes['arguments'] = $arguments; } - parent::__construct($nodes, ['type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'disable_c_ext' => false], $lineno); + parent::__construct($nodes, ['type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'optimizable' => true], $lineno); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { - if ($this->getAttribute('disable_c_ext')) { - @trigger_error(sprintf('Using the "disable_c_ext" attribute on %s is deprecated since version 1.30 and will be removed in 2.0.', __CLASS__), E_USER_DEPRECATED); + $env = $compiler->getEnvironment(); + + // optimize array calls + if ( + $this->getAttribute('optimizable') + && (!$env->isStrictVariables() || $this->getAttribute('ignore_strict_check')) + && !$this->getAttribute('is_defined_test') + && Template::ARRAY_CALL === $this->getAttribute('type') + ) { + $var = '$'.$compiler->getVarName(); + $compiler + ->raw('(('.$var.' = ') + ->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 (\function_exists('twig_template_get_attributes') && !$this->getAttribute('disable_c_ext')) { - $compiler->raw('twig_template_get_attributes($this, '); - } else { - $compiler->raw('$this->getAttribute('); - } + $compiler->raw('twig_get_attribute($this->env, $this->source, '); if ($this->getAttribute('ignore_strict_check')) { $this->getNode('node')->setAttribute('ignore_strict_check', true); } - $compiler->subcompile($this->getNode('node')); + $compiler + ->subcompile($this->getNode('node')) + ->raw(', ') + ->subcompile($this->getNode('attribute')) + ; - $compiler->raw(', ')->subcompile($this->getNode('attribute')); - - // only generate optional arguments when needed (to make generated code more readable) - $needFourth = $this->getAttribute('ignore_strict_check'); - $needThird = $needFourth || $this->getAttribute('is_defined_test'); - $needSecond = $needThird || Template::ANY_CALL !== $this->getAttribute('type'); - $needFirst = $needSecond || $this->hasNode('arguments'); - - if ($needFirst) { - if ($this->hasNode('arguments')) { - $compiler->raw(', ')->subcompile($this->getNode('arguments')); - } else { - $compiler->raw(', []'); - } + if ($this->hasNode('arguments')) { + $compiler->raw(', ')->subcompile($this->getNode('arguments')); + } else { + $compiler->raw(', []'); } - if ($needSecond) { - $compiler->raw(', ')->repr($this->getAttribute('type')); - } - - if ($needThird) { - $compiler->raw(', ')->repr($this->getAttribute('is_defined_test')); - } - - if ($needFourth) { - $compiler->raw(', ')->repr($this->getAttribute('ignore_strict_check')); - } - - $compiler->raw(')'); + $compiler->raw(', ') + ->repr($this->getAttribute('type')) + ->raw(', ')->repr($this->getAttribute('is_defined_test')) + ->raw(', ')->repr($this->getAttribute('ignore_strict_check')) + ->raw(', ')->repr($env->hasExtension(SandboxExtension::class)) + ->raw(', ')->repr($this->getNode('node')->getTemplateLine()) + ->raw(')') + ; } } - -class_alias('Twig\Node\Expression\GetAttrExpression', 'Twig_Node_Expression_GetAttr'); diff --git a/lib/twig/twig/src/Node/Expression/InlinePrint.php b/lib/twig/twig/src/Node/Expression/InlinePrint.php index 469e73675a..1ad4751e46 100644 --- a/lib/twig/twig/src/Node/Expression/InlinePrint.php +++ b/lib/twig/twig/src/Node/Expression/InlinePrint.php @@ -19,12 +19,12 @@ use Twig\Node\Node; */ final class InlinePrint extends AbstractExpression { - public function __construct(Node $node, $lineno) + public function __construct(Node $node, int $lineno) { parent::__construct(['node' => $node], [], $lineno); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->raw('print (') diff --git a/lib/twig/twig/src/Node/Expression/MethodCallExpression.php b/lib/twig/twig/src/Node/Expression/MethodCallExpression.php index f6311249de..d5ec0b6efc 100644 --- a/lib/twig/twig/src/Node/Expression/MethodCallExpression.php +++ b/lib/twig/twig/src/Node/Expression/MethodCallExpression.php @@ -15,22 +15,35 @@ use Twig\Compiler; class MethodCallExpression extends AbstractExpression { - public function __construct(AbstractExpression $node, $method, ArrayExpression $arguments, $lineno) + public function __construct(AbstractExpression $node, string $method, ArrayExpression $arguments, int $lineno) { - parent::__construct(['node' => $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false], $lineno); + parent::__construct(['node' => $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false, 'is_defined_test' => false], $lineno); if ($node instanceof NameExpression) { $node->setAttribute('always_defined', true); } } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { + if ($this->getAttribute('is_defined_test')) { + $compiler + ->raw('method_exists($macros[') + ->repr($this->getNode('node')->getAttribute('name')) + ->raw('], ') + ->repr($this->getAttribute('method')) + ->raw(')') + ; + + return; + } + $compiler - ->subcompile($this->getNode('node')) - ->raw('->') - ->raw($this->getAttribute('method')) - ->raw('(') + ->raw('twig_call_macro($macros[') + ->repr($this->getNode('node')->getAttribute('name')) + ->raw('], ') + ->repr($this->getAttribute('method')) + ->raw(', [') ; $first = true; foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) { @@ -41,8 +54,9 @@ class MethodCallExpression extends AbstractExpression $compiler->subcompile($pair['value']); } - $compiler->raw(')'); + $compiler + ->raw('], ') + ->repr($this->getTemplateLine()) + ->raw(', $context, $this->getSourceContext())'); } } - -class_alias('Twig\Node\Expression\MethodCallExpression', 'Twig_Node_Expression_MethodCall'); diff --git a/lib/twig/twig/src/Node/Expression/NameExpression.php b/lib/twig/twig/src/Node/Expression/NameExpression.php index d3f7d107fb..c3563f0123 100644 --- a/lib/twig/twig/src/Node/Expression/NameExpression.php +++ b/lib/twig/twig/src/Node/Expression/NameExpression.php @@ -16,18 +16,18 @@ use Twig\Compiler; class NameExpression extends AbstractExpression { - protected $specialVars = [ - '_self' => '$this', + private $specialVars = [ + '_self' => '$this->getTemplateName()', '_context' => '$context', '_charset' => '$this->env->getCharset()', ]; - public function __construct($name, $lineno) + public function __construct(string $name, int $lineno) { parent::__construct([], ['name' => $name, 'is_defined_test' => false, 'ignore_strict_check' => false, 'always_defined' => false], $lineno); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $name = $this->getAttribute('name'); @@ -36,7 +36,7 @@ class NameExpression extends AbstractExpression if ($this->getAttribute('is_defined_test')) { if ($this->isSpecial()) { $compiler->repr(true); - } elseif (\PHP_VERSION_ID >= 700400) { + } elseif (\PHP_VERSION_ID >= 70400) { $compiler ->raw('array_key_exists(') ->string($name) @@ -60,45 +60,25 @@ class NameExpression extends AbstractExpression ->raw(']') ; } else { - if (\PHP_VERSION_ID >= 70000) { - // use PHP 7 null coalescing operator + if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) { $compiler ->raw('($context[') ->string($name) - ->raw('] ?? ') + ->raw('] ?? null)') ; - - if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) { - $compiler->raw('null)'); - } else { - $compiler->raw('$this->getContext($context, ')->string($name)->raw('))'); - } - } elseif (\PHP_VERSION_ID >= 50400) { - // PHP 5.4 ternary operator performance was optimized + } else { $compiler ->raw('(isset($context[') ->string($name) - ->raw(']) ? $context[') + ->raw(']) || array_key_exists(') ->string($name) - ->raw('] : ') - ; - - if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) { - $compiler->raw('null)'); - } else { - $compiler->raw('$this->getContext($context, ')->string($name)->raw('))'); - } - } else { - $compiler - ->raw('$this->getContext($context, ') + ->raw(', $context) ? $context[') ->string($name) - ; - - if ($this->getAttribute('ignore_strict_check')) { - $compiler->raw(', true'); - } - - $compiler + ->raw('] : (function () { throw new RuntimeError(\'Variable ') + ->string($name) + ->raw(' does not exist.\', ') + ->repr($this->lineno) + ->raw(', $this->source); })()') ->raw(')') ; } @@ -115,5 +95,3 @@ class NameExpression extends AbstractExpression return !$this->isSpecial() && !$this->getAttribute('is_defined_test'); } } - -class_alias('Twig\Node\Expression\NameExpression', 'Twig_Node_Expression_Name'); diff --git a/lib/twig/twig/src/Node/Expression/NullCoalesceExpression.php b/lib/twig/twig/src/Node/Expression/NullCoalesceExpression.php index 917d31a3b2..a72bc4fc65 100644 --- a/lib/twig/twig/src/Node/Expression/NullCoalesceExpression.php +++ b/lib/twig/twig/src/Node/Expression/NullCoalesceExpression.php @@ -20,7 +20,7 @@ use Twig\Node\Node; class NullCoalesceExpression extends ConditionalExpression { - public function __construct(\Twig_NodeInterface $left, \Twig_NodeInterface $right, $lineno) + public function __construct(Node $left, Node $right, int $lineno) { $test = new DefinedTest(clone $left, 'defined', new Node(), $left->getTemplateLine()); // for "block()", we don't need the null test as the return value is always a string @@ -35,7 +35,7 @@ class NullCoalesceExpression extends ConditionalExpression parent::__construct($test, $left, $right, $lineno); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { /* * This optimizes only one case. PHP 7 also supports more complex expressions @@ -44,7 +44,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 (\PHP_VERSION_ID >= 70000 && $this->getNode('expr2') instanceof NameExpression) { + if ($this->getNode('expr2') instanceof NameExpression) { $this->getNode('expr2')->setAttribute('always_defined', true); $compiler ->raw('((') @@ -58,5 +58,3 @@ class NullCoalesceExpression extends ConditionalExpression } } } - -class_alias('Twig\Node\Expression\NullCoalesceExpression', 'Twig_Node_Expression_NullCoalesce'); diff --git a/lib/twig/twig/src/Node/Expression/ParentExpression.php b/lib/twig/twig/src/Node/Expression/ParentExpression.php index 1247283036..2549197184 100644 --- a/lib/twig/twig/src/Node/Expression/ParentExpression.php +++ b/lib/twig/twig/src/Node/Expression/ParentExpression.php @@ -21,12 +21,12 @@ use Twig\Compiler; */ class ParentExpression extends AbstractExpression { - public function __construct($name, $lineno, $tag = null) + public function __construct(string $name, int $lineno, string $tag = null) { parent::__construct([], ['output' => false, 'name' => $name], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { if ($this->getAttribute('output')) { $compiler @@ -44,5 +44,3 @@ class ParentExpression extends AbstractExpression } } } - -class_alias('Twig\Node\Expression\ParentExpression', 'Twig_Node_Expression_Parent'); diff --git a/lib/twig/twig/src/Node/Expression/TempNameExpression.php b/lib/twig/twig/src/Node/Expression/TempNameExpression.php index ce0a158981..004c704a58 100644 --- a/lib/twig/twig/src/Node/Expression/TempNameExpression.php +++ b/lib/twig/twig/src/Node/Expression/TempNameExpression.php @@ -15,12 +15,12 @@ use Twig\Compiler; class TempNameExpression extends AbstractExpression { - public function __construct($name, $lineno) + public function __construct(string $name, int $lineno) { parent::__construct([], ['name' => $name], $lineno); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->raw('$_') @@ -29,5 +29,3 @@ class TempNameExpression extends AbstractExpression ; } } - -class_alias('Twig\Node\Expression\TempNameExpression', 'Twig_Node_Expression_TempName'); diff --git a/lib/twig/twig/src/Node/Expression/Test/ConstantTest.php b/lib/twig/twig/src/Node/Expression/Test/ConstantTest.php index 78353a8b25..57e9319d57 100644 --- a/lib/twig/twig/src/Node/Expression/Test/ConstantTest.php +++ b/lib/twig/twig/src/Node/Expression/Test/ConstantTest.php @@ -25,7 +25,7 @@ use Twig\Node\Expression\TestExpression; */ class ConstantTest extends TestExpression { - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->raw('(') @@ -47,5 +47,3 @@ class ConstantTest extends TestExpression ; } } - -class_alias('Twig\Node\Expression\Test\ConstantTest', 'Twig_Node_Expression_Test_Constant'); diff --git a/lib/twig/twig/src/Node/Expression/Test/DefinedTest.php b/lib/twig/twig/src/Node/Expression/Test/DefinedTest.php index 2222e11cfd..3953bbbe2c 100644 --- a/lib/twig/twig/src/Node/Expression/Test/DefinedTest.php +++ b/lib/twig/twig/src/Node/Expression/Test/DefinedTest.php @@ -18,8 +18,10 @@ 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\TestExpression; +use Twig\Node\Node; /** * Checks if a variable is defined in the current context. @@ -33,7 +35,7 @@ use Twig\Node\Expression\TestExpression; */ class DefinedTest extends TestExpression { - public function __construct(\Twig_NodeInterface $node, $name, \Twig_NodeInterface $arguments = null, $lineno) + public function __construct(Node $node, string $name, ?Node $arguments, int $lineno) { if ($node instanceof NameExpression) { $node->setAttribute('is_defined_test', true); @@ -46,6 +48,8 @@ class DefinedTest extends TestExpression $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 { throw new SyntaxError('The "defined" test only works with simple variables.', $lineno); } @@ -53,8 +57,9 @@ class DefinedTest extends TestExpression parent::__construct($node, $name, $arguments, $lineno); } - protected function changeIgnoreStrictCheck(GetAttrExpression $node) + private function changeIgnoreStrictCheck(GetAttrExpression $node) { + $node->setAttribute('optimizable', false); $node->setAttribute('ignore_strict_check', true); if ($node->getNode('node') instanceof GetAttrExpression) { @@ -62,10 +67,8 @@ class DefinedTest extends TestExpression } } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->subcompile($this->getNode('node')); } } - -class_alias('Twig\Node\Expression\Test\DefinedTest', 'Twig_Node_Expression_Test_Defined'); diff --git a/lib/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php b/lib/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php index 05c8ad8f7c..4cb3ee0969 100644 --- a/lib/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php +++ b/lib/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php @@ -23,7 +23,7 @@ use Twig\Node\Expression\TestExpression; */ class DivisiblebyTest extends TestExpression { - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->raw('(0 == ') @@ -34,5 +34,3 @@ class DivisiblebyTest extends TestExpression ; } } - -class_alias('Twig\Node\Expression\Test\DivisiblebyTest', 'Twig_Node_Expression_Test_Divisibleby'); diff --git a/lib/twig/twig/src/Node/Expression/Test/EvenTest.php b/lib/twig/twig/src/Node/Expression/Test/EvenTest.php index 3b955d26a2..a0e3ed62c1 100644 --- a/lib/twig/twig/src/Node/Expression/Test/EvenTest.php +++ b/lib/twig/twig/src/Node/Expression/Test/EvenTest.php @@ -23,7 +23,7 @@ use Twig\Node\Expression\TestExpression; */ class EvenTest extends TestExpression { - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->raw('(') @@ -33,5 +33,3 @@ class EvenTest extends TestExpression ; } } - -class_alias('Twig\Node\Expression\Test\EvenTest', 'Twig_Node_Expression_Test_Even'); diff --git a/lib/twig/twig/src/Node/Expression/Test/NullTest.php b/lib/twig/twig/src/Node/Expression/Test/NullTest.php index 24d399781e..45b54ae370 100644 --- a/lib/twig/twig/src/Node/Expression/Test/NullTest.php +++ b/lib/twig/twig/src/Node/Expression/Test/NullTest.php @@ -23,7 +23,7 @@ use Twig\Node\Expression\TestExpression; */ class NullTest extends TestExpression { - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->raw('(null === ') @@ -32,5 +32,3 @@ class NullTest extends TestExpression ; } } - -class_alias('Twig\Node\Expression\Test\NullTest', 'Twig_Node_Expression_Test_Null'); diff --git a/lib/twig/twig/src/Node/Expression/Test/OddTest.php b/lib/twig/twig/src/Node/Expression/Test/OddTest.php index 2dc693a9ae..d56c711169 100644 --- a/lib/twig/twig/src/Node/Expression/Test/OddTest.php +++ b/lib/twig/twig/src/Node/Expression/Test/OddTest.php @@ -23,15 +23,13 @@ use Twig\Node\Expression\TestExpression; */ class OddTest extends TestExpression { - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->raw('(') ->subcompile($this->getNode('node')) - ->raw(' % 2 == 1') + ->raw(' % 2 != 0') ->raw(')') ; } } - -class_alias('Twig\Node\Expression\Test\OddTest', 'Twig_Node_Expression_Test_Odd'); diff --git a/lib/twig/twig/src/Node/Expression/Test/SameasTest.php b/lib/twig/twig/src/Node/Expression/Test/SameasTest.php index 75f2b82a5b..c96d2bc01a 100644 --- a/lib/twig/twig/src/Node/Expression/Test/SameasTest.php +++ b/lib/twig/twig/src/Node/Expression/Test/SameasTest.php @@ -21,7 +21,7 @@ use Twig\Node\Expression\TestExpression; */ class SameasTest extends TestExpression { - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->raw('(') @@ -32,5 +32,3 @@ class SameasTest extends TestExpression ; } } - -class_alias('Twig\Node\Expression\Test\SameasTest', 'Twig_Node_Expression_Test_Sameas'); diff --git a/lib/twig/twig/src/Node/Expression/TestExpression.php b/lib/twig/twig/src/Node/Expression/TestExpression.php index 8fc31d3aad..e518bd8f10 100644 --- a/lib/twig/twig/src/Node/Expression/TestExpression.php +++ b/lib/twig/twig/src/Node/Expression/TestExpression.php @@ -12,11 +12,11 @@ namespace Twig\Node\Expression; use Twig\Compiler; -use Twig\TwigTest; +use Twig\Node\Node; class TestExpression extends CallExpression { - public function __construct(\Twig_NodeInterface $node, $name, \Twig_NodeInterface $arguments = null, $lineno) + public function __construct(Node $node, string $name, ?Node $arguments, int $lineno) { $nodes = ['node' => $node]; if (null !== $arguments) { @@ -26,26 +26,17 @@ class TestExpression extends CallExpression parent::__construct($nodes, ['name' => $name], $lineno); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $name = $this->getAttribute('name'); $test = $compiler->getEnvironment()->getTest($name); $this->setAttribute('name', $name); $this->setAttribute('type', 'test'); - $this->setAttribute('thing', $test); - if ($test instanceof TwigTest) { - $this->setAttribute('arguments', $test->getArguments()); - } - if ($test instanceof \Twig_TestCallableInterface || $test instanceof TwigTest) { - $this->setAttribute('callable', $test->getCallable()); - } - if ($test instanceof TwigTest) { - $this->setAttribute('is_variadic', $test->isVariadic()); - } + $this->setAttribute('arguments', $test->getArguments()); + $this->setAttribute('callable', $test->getCallable()); + $this->setAttribute('is_variadic', $test->isVariadic()); $this->compileCallable($compiler); } } - -class_alias('Twig\Node\Expression\TestExpression', 'Twig_Node_Expression_Test'); diff --git a/lib/twig/twig/src/Node/Expression/Unary/AbstractUnary.php b/lib/twig/twig/src/Node/Expression/Unary/AbstractUnary.php index 415c3d407e..e31e3f84b0 100644 --- a/lib/twig/twig/src/Node/Expression/Unary/AbstractUnary.php +++ b/lib/twig/twig/src/Node/Expression/Unary/AbstractUnary.php @@ -14,22 +14,21 @@ namespace Twig\Node\Expression\Unary; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Node; abstract class AbstractUnary extends AbstractExpression { - public function __construct(\Twig_NodeInterface $node, $lineno) + public function __construct(Node $node, int $lineno) { parent::__construct(['node' => $node], [], $lineno); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->raw(' '); $this->operator($compiler); $compiler->subcompile($this->getNode('node')); } - abstract public function operator(Compiler $compiler); + abstract public function operator(Compiler $compiler): Compiler; } - -class_alias('Twig\Node\Expression\Unary\AbstractUnary', 'Twig_Node_Expression_Unary'); diff --git a/lib/twig/twig/src/Node/Expression/Unary/NegUnary.php b/lib/twig/twig/src/Node/Expression/Unary/NegUnary.php index dfb6f54295..dc2f2350a3 100644 --- a/lib/twig/twig/src/Node/Expression/Unary/NegUnary.php +++ b/lib/twig/twig/src/Node/Expression/Unary/NegUnary.php @@ -16,10 +16,8 @@ use Twig\Compiler; class NegUnary extends AbstractUnary { - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { - $compiler->raw('-'); + return $compiler->raw('-'); } } - -class_alias('Twig\Node\Expression\Unary\NegUnary', 'Twig_Node_Expression_Unary_Neg'); diff --git a/lib/twig/twig/src/Node/Expression/Unary/NotUnary.php b/lib/twig/twig/src/Node/Expression/Unary/NotUnary.php index 7bdde96fff..55c11bacf1 100644 --- a/lib/twig/twig/src/Node/Expression/Unary/NotUnary.php +++ b/lib/twig/twig/src/Node/Expression/Unary/NotUnary.php @@ -16,10 +16,8 @@ use Twig\Compiler; class NotUnary extends AbstractUnary { - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { - $compiler->raw('!'); + return $compiler->raw('!'); } } - -class_alias('Twig\Node\Expression\Unary\NotUnary', 'Twig_Node_Expression_Unary_Not'); diff --git a/lib/twig/twig/src/Node/Expression/Unary/PosUnary.php b/lib/twig/twig/src/Node/Expression/Unary/PosUnary.php index 52d5d0c89a..4b0a062087 100644 --- a/lib/twig/twig/src/Node/Expression/Unary/PosUnary.php +++ b/lib/twig/twig/src/Node/Expression/Unary/PosUnary.php @@ -16,10 +16,8 @@ use Twig\Compiler; class PosUnary extends AbstractUnary { - public function operator(Compiler $compiler) + public function operator(Compiler $compiler): Compiler { - $compiler->raw('+'); + return $compiler->raw('+'); } } - -class_alias('Twig\Node\Expression\Unary\PosUnary', 'Twig_Node_Expression_Unary_Pos'); diff --git a/lib/twig/twig/src/Node/Expression/VariadicExpression.php b/lib/twig/twig/src/Node/Expression/VariadicExpression.php new file mode 100644 index 0000000000..a1bdb48c23 --- /dev/null +++ b/lib/twig/twig/src/Node/Expression/VariadicExpression.php @@ -0,0 +1,24 @@ +raw('...'); + + parent::compile($compiler); + } +} diff --git a/lib/twig/twig/src/Node/FlushNode.php b/lib/twig/twig/src/Node/FlushNode.php index 6cbc489a62..fa50a88ee5 100644 --- a/lib/twig/twig/src/Node/FlushNode.php +++ b/lib/twig/twig/src/Node/FlushNode.php @@ -20,12 +20,12 @@ use Twig\Compiler; */ class FlushNode extends Node { - public function __construct($lineno, $tag) + public function __construct(int $lineno, string $tag) { parent::__construct([], [], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) @@ -33,5 +33,3 @@ class FlushNode extends Node ; } } - -class_alias('Twig\Node\FlushNode', 'Twig_Node_Flush'); diff --git a/lib/twig/twig/src/Node/ForLoopNode.php b/lib/twig/twig/src/Node/ForLoopNode.php index 3902093556..d5ce845a79 100644 --- a/lib/twig/twig/src/Node/ForLoopNode.php +++ b/lib/twig/twig/src/Node/ForLoopNode.php @@ -20,12 +20,12 @@ use Twig\Compiler; */ class ForLoopNode extends Node { - public function __construct($lineno, $tag = null) + public function __construct(int $lineno, string $tag = null) { parent::__construct([], ['with_loop' => false, 'ifexpr' => false, 'else' => false], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { if ($this->getAttribute('else')) { $compiler->write("\$context['_iterated'] = true;\n"); @@ -36,21 +36,14 @@ class ForLoopNode extends Node ->write("++\$context['loop']['index0'];\n") ->write("++\$context['loop']['index'];\n") ->write("\$context['loop']['first'] = false;\n") + ->write("if (isset(\$context['loop']['length'])) {\n") + ->indent() + ->write("--\$context['loop']['revindex0'];\n") + ->write("--\$context['loop']['revindex'];\n") + ->write("\$context['loop']['last'] = 0 === \$context['loop']['revindex0'];\n") + ->outdent() + ->write("}\n") ; - - if (!$this->getAttribute('ifexpr')) { - $compiler - ->write("if (isset(\$context['loop']['length'])) {\n") - ->indent() - ->write("--\$context['loop']['revindex0'];\n") - ->write("--\$context['loop']['revindex'];\n") - ->write("\$context['loop']['last'] = 0 === \$context['loop']['revindex0'];\n") - ->outdent() - ->write("}\n") - ; - } } } } - -class_alias('Twig\Node\ForLoopNode', 'Twig_Node_ForLoop'); diff --git a/lib/twig/twig/src/Node/ForNode.php b/lib/twig/twig/src/Node/ForNode.php index 49409a39b1..04addfbfe5 100644 --- a/lib/twig/twig/src/Node/ForNode.php +++ b/lib/twig/twig/src/Node/ForNode.php @@ -23,25 +23,21 @@ use Twig\Node\Expression\AssignNameExpression; */ class ForNode extends Node { - protected $loop; + private $loop; - public function __construct(AssignNameExpression $keyTarget, AssignNameExpression $valueTarget, AbstractExpression $seq, AbstractExpression $ifexpr = null, \Twig_NodeInterface $body, \Twig_NodeInterface $else = null, $lineno, $tag = null) + public function __construct(AssignNameExpression $keyTarget, AssignNameExpression $valueTarget, AbstractExpression $seq, ?Node $ifexpr, Node $body, ?Node $else, int $lineno, string $tag = null) { $body = new Node([$body, $this->loop = new ForLoopNode($lineno, $tag)]); - if (null !== $ifexpr) { - $body = new IfNode(new Node([$ifexpr, $body]), null, $lineno, $tag); - } - $nodes = ['key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body]; if (null !== $else) { $nodes['else'] = $else; } - parent::__construct($nodes, ['with_loop' => true, 'ifexpr' => null !== $ifexpr], $lineno, $tag); + parent::__construct($nodes, ['with_loop' => true], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) @@ -63,26 +59,20 @@ class ForNode extends Node ->write(" 'index' => 1,\n") ->write(" 'first' => true,\n") ->write("];\n") + ->write("if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof \Countable)) {\n") + ->indent() + ->write("\$length = count(\$context['_seq']);\n") + ->write("\$context['loop']['revindex0'] = \$length - 1;\n") + ->write("\$context['loop']['revindex'] = \$length;\n") + ->write("\$context['loop']['length'] = \$length;\n") + ->write("\$context['loop']['last'] = 1 === \$length;\n") + ->outdent() + ->write("}\n") ; - - if (!$this->getAttribute('ifexpr')) { - $compiler - ->write("if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof \Countable)) {\n") - ->indent() - ->write("\$length = count(\$context['_seq']);\n") - ->write("\$context['loop']['revindex0'] = \$length - 1;\n") - ->write("\$context['loop']['revindex'] = \$length;\n") - ->write("\$context['loop']['length'] = \$length;\n") - ->write("\$context['loop']['last'] = 1 === \$length;\n") - ->outdent() - ->write("}\n") - ; - } } $this->loop->setAttribute('else', $this->hasNode('else')); $this->loop->setAttribute('with_loop', $this->getAttribute('with_loop')); - $this->loop->setAttribute('ifexpr', $this->getAttribute('ifexpr')); $compiler ->write("foreach (\$context['_seq'] as ") @@ -115,5 +105,3 @@ class ForNode extends Node $compiler->write("\$context = array_intersect_key(\$context, \$_parent) + \$_parent;\n"); } } - -class_alias('Twig\Node\ForNode', 'Twig_Node_For'); diff --git a/lib/twig/twig/src/Node/IfNode.php b/lib/twig/twig/src/Node/IfNode.php index 4836d91f08..5fa20082a7 100644 --- a/lib/twig/twig/src/Node/IfNode.php +++ b/lib/twig/twig/src/Node/IfNode.php @@ -21,7 +21,7 @@ use Twig\Compiler; */ class IfNode extends Node { - public function __construct(\Twig_NodeInterface $tests, \Twig_NodeInterface $else = null, $lineno, $tag = null) + public function __construct(Node $tests, ?Node $else, int $lineno, string $tag = null) { $nodes = ['tests' => $tests]; if (null !== $else) { @@ -31,7 +31,7 @@ class IfNode extends Node parent::__construct($nodes, [], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->addDebugInfo($this); for ($i = 0, $count = \count($this->getNode('tests')); $i < $count; $i += 2) { @@ -68,5 +68,3 @@ class IfNode extends Node ->write("}\n"); } } - -class_alias('Twig\Node\IfNode', 'Twig_Node_If'); diff --git a/lib/twig/twig/src/Node/ImportNode.php b/lib/twig/twig/src/Node/ImportNode.php index 236db8900e..5378d799e2 100644 --- a/lib/twig/twig/src/Node/ImportNode.php +++ b/lib/twig/twig/src/Node/ImportNode.php @@ -22,20 +22,28 @@ use Twig\Node\Expression\NameExpression; */ class ImportNode extends Node { - public function __construct(AbstractExpression $expr, AbstractExpression $var, $lineno, $tag = null) + public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, string $tag = null, bool $global = true) { - parent::__construct(['expr' => $expr, 'var' => $var], [], $lineno, $tag); + parent::__construct(['expr' => $expr, 'var' => $var], ['global' => $global], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) - ->write('') - ->subcompile($this->getNode('var')) - ->raw(' = ') + ->write('$macros[') + ->repr($this->getNode('var')->getAttribute('name')) + ->raw('] = ') ; + 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'); } else { @@ -53,5 +61,3 @@ class ImportNode extends Node $compiler->raw(";\n"); } } - -class_alias('Twig\Node\ImportNode', 'Twig_Node_Import'); diff --git a/lib/twig/twig/src/Node/IncludeNode.php b/lib/twig/twig/src/Node/IncludeNode.php index 544db81eaf..d540d6b23b 100644 --- a/lib/twig/twig/src/Node/IncludeNode.php +++ b/lib/twig/twig/src/Node/IncludeNode.php @@ -22,17 +22,17 @@ use Twig\Node\Expression\AbstractExpression; */ class IncludeNode extends Node implements NodeOutputInterface { - public function __construct(AbstractExpression $expr, AbstractExpression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null) + public function __construct(AbstractExpression $expr, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, string $tag = null) { $nodes = ['expr' => $expr]; if (null !== $variables) { $nodes['variables'] = $variables; } - parent::__construct($nodes, ['only' => (bool) $only, 'ignore_missing' => (bool) $ignoreMissing], $lineno, $tag); + parent::__construct($nodes, ['only' => $only, 'ignore_missing' => $ignoreMissing], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->addDebugInfo($this); @@ -104,5 +104,3 @@ class IncludeNode extends Node implements NodeOutputInterface } } } - -class_alias('Twig\Node\IncludeNode', 'Twig_Node_Include'); diff --git a/lib/twig/twig/src/Node/MacroNode.php b/lib/twig/twig/src/Node/MacroNode.php index 6eb67955f4..7f1b24d537 100644 --- a/lib/twig/twig/src/Node/MacroNode.php +++ b/lib/twig/twig/src/Node/MacroNode.php @@ -21,9 +21,9 @@ use Twig\Error\SyntaxError; */ class MacroNode extends Node { - const VARARGS_NAME = 'varargs'; + public const VARARGS_NAME = 'varargs'; - public function __construct($name, \Twig_NodeInterface $body, \Twig_NodeInterface $arguments, $lineno, $tag = null) + public function __construct(string $name, Node $body, Node $arguments, int $lineno, string $tag = null) { foreach ($arguments as $argumentName => $argument) { if (self::VARARGS_NAME === $argumentName) { @@ -34,11 +34,11 @@ class MacroNode extends Node parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) - ->write(sprintf('public function get%s(', $this->getAttribute('name'))) + ->write(sprintf('public function macro_%s(', $this->getAttribute('name'))) ; $count = \count($this->getNode('arguments')); @@ -54,21 +54,16 @@ class MacroNode extends Node } } - if (\PHP_VERSION_ID >= 50600) { - if ($count) { - $compiler->raw(', '); - } - - $compiler->raw('...$__varargs__'); + if ($count) { + $compiler->raw(', '); } $compiler + ->raw('...$__varargs__') ->raw(")\n") ->write("{\n") ->indent() - ; - - $compiler + ->write("\$macros = \$this->macros;\n") ->write("\$context = \$this->env->mergeGlobals([\n") ->indent() ; @@ -88,19 +83,8 @@ class MacroNode extends Node ->raw(' => ') ; - if (\PHP_VERSION_ID >= 50600) { - $compiler->raw("\$__varargs__,\n"); - } else { - $compiler - ->raw('func_num_args() > ') - ->repr($count) - ->raw(' ? array_slice(func_get_args(), ') - ->repr($count) - ->raw(") : [],\n") - ; - } - $compiler + ->raw("\$__varargs__,\n") ->outdent() ->write("]);\n\n") ->write("\$blocks = [];\n\n") @@ -114,23 +98,16 @@ class MacroNode extends Node ->write("try {\n") ->indent() ->subcompile($this->getNode('body')) + ->raw("\n") + ->write("return ('' === \$tmp = ob_get_contents()) ? '' : new Markup(\$tmp, \$this->env->getCharset());\n") ->outdent() - ->write("} catch (\Exception \$e) {\n") + ->write("} finally {\n") ->indent() - ->write("ob_end_clean();\n\n") - ->write("throw \$e;\n") + ->write("ob_end_clean();\n") ->outdent() - ->write("} catch (\Throwable \$e) {\n") - ->indent() - ->write("ob_end_clean();\n\n") - ->write("throw \$e;\n") - ->outdent() - ->write("}\n\n") - ->write("return ('' === \$tmp = ob_get_clean()) ? '' : new Markup(\$tmp, \$this->env->getCharset());\n") + ->write("}\n") ->outdent() ->write("}\n\n") ; } } - -class_alias('Twig\Node\MacroNode', 'Twig_Node_Macro'); diff --git a/lib/twig/twig/src/Node/ModuleNode.php b/lib/twig/twig/src/Node/ModuleNode.php index aab2aa33f2..e972b6ba58 100644 --- a/lib/twig/twig/src/Node/ModuleNode.php +++ b/lib/twig/twig/src/Node/ModuleNode.php @@ -26,17 +26,10 @@ use Twig\Source; * * @author Fabien Potencier */ -class ModuleNode extends Node +final class ModuleNode extends Node { - public function __construct(\Twig_NodeInterface $body, AbstractExpression $parent = null, \Twig_NodeInterface $blocks, \Twig_NodeInterface $macros, \Twig_NodeInterface $traits, $embeddedTemplates, $name, $source = '') + public function __construct(Node $body, ?AbstractExpression $parent, Node $blocks, Node $macros, Node $traits, $embeddedTemplates, Source $source) { - if (!$name instanceof Source) { - @trigger_error(sprintf('Passing a string as the $name argument of %s() is deprecated since version 1.27. Pass a \Twig\Source instance instead.', __METHOD__), E_USER_DEPRECATED); - $source = new Source($source, $name); - } else { - $source = $name; - } - $nodes = [ 'body' => $body, 'blocks' => $blocks, @@ -54,16 +47,11 @@ class ModuleNode extends Node // embedded templates are set as attributes so that they are only visited once by the visitors parent::__construct($nodes, [ - // source to be remove in 2.0 - 'source' => $source->getCode(), - // filename to be remove in 2.0 (use getTemplateName() instead) - 'filename' => $source->getName(), 'index' => null, 'embedded_templates' => $embeddedTemplates, ], 1); // populate the template name of all node children - $this->setTemplateName($source->getName()); $this->setSourceContext($source); } @@ -72,7 +60,7 @@ class ModuleNode extends Node $this->setAttribute('index', $index); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $this->compileTemplate($compiler); @@ -89,16 +77,7 @@ class ModuleNode extends Node $this->compileClassHeader($compiler); - if ( - \count($this->getNode('blocks')) - || \count($this->getNode('traits')) - || !$this->hasNode('parent') - || $this->getNode('parent') instanceof ConstantExpression - || \count($this->getNode('constructor_start')) - || \count($this->getNode('constructor_end')) - ) { - $this->compileConstructor($compiler); - } + $this->compileConstructor($compiler); $this->compileGetParent($compiler); @@ -114,8 +93,6 @@ class ModuleNode extends Node $this->compileDebugInfo($compiler); - $this->compileGetSource($compiler); - $this->compileGetSourceContext($compiler); $this->compileClassFooter($compiler); @@ -166,6 +143,7 @@ class ModuleNode extends Node ->write("use Twig\Environment;\n") ->write("use Twig\Error\LoaderError;\n") ->write("use Twig\Error\RuntimeError;\n") + ->write("use Twig\Extension\SandboxExtension;\n") ->write("use Twig\Markup;\n") ->write("use Twig\Sandbox\SecurityError;\n") ->write("use Twig\Sandbox\SecurityNotAllowedTagError;\n") @@ -179,9 +157,11 @@ class ModuleNode extends Node // if the template name contains */, add a blank to avoid a PHP parse error ->write('/* '.str_replace('*/', '* /', $this->getSourceContext()->getName())." */\n") ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getSourceContext()->getName(), $this->getAttribute('index'))) - ->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass())) + ->raw(" extends Template\n") ->write("{\n") ->indent() + ->write("private \$source;\n") + ->write("private \$macros = [];\n\n") ; } @@ -192,6 +172,7 @@ class ModuleNode extends Node ->indent() ->subcompile($this->getNode('constructor_start')) ->write("parent::__construct(\$env);\n\n") + ->write("\$this->source = \$this->getSourceContext();\n\n") ; // parent @@ -203,18 +184,24 @@ class ModuleNode extends Node if ($countTraits) { // traits foreach ($this->getNode('traits') as $i => $trait) { - $this->compileLoadTemplate($compiler, $trait->getNode('template'), sprintf('$_trait_%s', $i)); - $node = $trait->getNode('template'); + $compiler ->addDebugInfo($node) + ->write(sprintf('$_trait_%s = $this->loadTemplate(', $i)) + ->subcompile($node) + ->raw(', ') + ->repr($node->getTemplateName()) + ->raw(', ') + ->repr($node->getTemplateLine()) + ->raw(");\n") ->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i)) ->indent() ->write("throw new RuntimeError('Template \"'.") ->subcompile($trait->getNode('template')) ->raw(".'\" cannot be used as a trait.', ") ->repr($node->getTemplateLine()) - ->raw(", \$this->getSourceContext());\n") + ->raw(", \$this->source);\n") ->outdent() ->write("}\n") ->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i)) @@ -226,13 +213,13 @@ class ModuleNode extends Node ->string($key) ->raw("])) {\n") ->indent() - ->write("throw new RuntimeError(sprintf('Block ") + ->write("throw new RuntimeError('Block ") ->string($key) ->raw(' is not defined in trait ') ->subcompile($trait->getNode('template')) - ->raw(".'), ") + ->raw(".', ") ->repr($node->getTemplateLine()) - ->raw(", \$this->getSourceContext());\n") + ->raw(", \$this->source);\n") ->outdent() ->write("}\n\n") @@ -318,6 +305,7 @@ class ModuleNode extends Node $compiler ->write("protected function doDisplay(array \$context, array \$blocks = [])\n", "{\n") ->indent() + ->write("\$macros = \$this->macros;\n") ->subcompile($this->getNode('display_start')) ->subcompile($this->getNode('body')) ; @@ -440,20 +428,6 @@ class ModuleNode extends Node ; } - protected function compileGetSource(Compiler $compiler) - { - $compiler - ->write("/** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */\n") - ->write("public function getSource()\n", "{\n") - ->indent() - ->write("@trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED);\n\n") - ->write('return $this->getSourceContext()->getCode();') - ->raw("\n") - ->outdent() - ->write("}\n\n") - ; - } - protected function compileGetSourceContext(Compiler $compiler) { $compiler @@ -488,5 +462,3 @@ class ModuleNode extends Node } } } - -class_alias('Twig\Node\ModuleNode', 'Twig_Node_Module'); diff --git a/lib/twig/twig/src/Node/Node.php b/lib/twig/twig/src/Node/Node.php index c890feb72d..c0558b9afd 100644 --- a/lib/twig/twig/src/Node/Node.php +++ b/lib/twig/twig/src/Node/Node.php @@ -20,7 +20,7 @@ use Twig\Source; * * @author Fabien Potencier */ -class Node implements \Twig_NodeInterface +class Node implements \Countable, \IteratorAggregate { protected $nodes; protected $attributes; @@ -36,11 +36,11 @@ class Node implements \Twig_NodeInterface * @param int $lineno The line number * @param string $tag The tag name associated with the Node */ - public function __construct(array $nodes = [], array $attributes = [], $lineno = 0, $tag = null) + public function __construct(array $nodes = [], array $attributes = [], int $lineno = 0, string $tag = null) { foreach ($nodes as $name => $node) { - if (!$node instanceof \Twig_NodeInterface) { - @trigger_error(sprintf('Using "%s" for the value of node "%s" of "%s" is deprecated since version 1.25 and will be removed in 2.0.', \is_object($node) ? \get_class($node) : (null === $node ? 'null' : \gettype($node)), $name, \get_class($this)), E_USER_DEPRECATED); + 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) ? \get_class($node) : (null === $node ? 'null' : \gettype($node)), $name, static::class)); } } $this->nodes = $nodes; @@ -56,7 +56,7 @@ class Node implements \Twig_NodeInterface $attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true))); } - $repr = [\get_class($this).'('.implode(', ', $attributes)]; + $repr = [static::class.'('.implode(', ', $attributes)]; if (\count($this->nodes)) { foreach ($this->nodes as $name => $node) { @@ -78,40 +78,8 @@ class Node implements \Twig_NodeInterface } /** - * @deprecated since 1.16.1 (to be removed in 2.0) + * @return void */ - public function toXml($asDom = false) - { - @trigger_error(sprintf('%s is deprecated since version 1.16.1 and will be removed in 2.0.', __METHOD__), E_USER_DEPRECATED); - - $dom = new \DOMDocument('1.0', 'UTF-8'); - $dom->formatOutput = true; - $dom->appendChild($xml = $dom->createElement('twig')); - - $xml->appendChild($node = $dom->createElement('node')); - $node->setAttribute('class', \get_class($this)); - - foreach ($this->attributes as $name => $value) { - $node->appendChild($attribute = $dom->createElement('attribute')); - $attribute->setAttribute('name', $name); - $attribute->appendChild($dom->createTextNode($value)); - } - - foreach ($this->nodes as $name => $n) { - if (null === $n) { - continue; - } - - $child = $n->toXml(true)->getElementsByTagName('node')->item(0); - $child = $dom->importNode($child, true); - $child->setAttribute('name', $name); - - $node->appendChild($child); - } - - return $asDom ? $dom : $dom->saveXML(); - } - public function compile(Compiler $compiler) { foreach ($this->nodes as $node) { @@ -119,156 +87,93 @@ class Node implements \Twig_NodeInterface } } - public function getTemplateLine() + public function getTemplateLine(): int { return $this->lineno; } - /** - * @deprecated since 1.27 (to be removed in 2.0) - */ - public function getLine() - { - @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getTemplateLine() instead.', E_USER_DEPRECATED); - - return $this->lineno; - } - - public function getNodeTag() + public function getNodeTag(): ?string { return $this->tag; } - /** - * @return bool - */ - public function hasAttribute($name) + public function hasAttribute(string $name): bool { return \array_key_exists($name, $this->attributes); } - /** - * @return mixed - */ - public function getAttribute($name) + public function getAttribute(string $name) { if (!\array_key_exists($name, $this->attributes)) { - throw new \LogicException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, \get_class($this))); + throw new \LogicException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, static::class)); } return $this->attributes[$name]; } - /** - * @param string $name - * @param mixed $value - */ - public function setAttribute($name, $value) + public function setAttribute(string $name, $value): void { $this->attributes[$name] = $value; } - public function removeAttribute($name) + public function removeAttribute(string $name): void { unset($this->attributes[$name]); } - /** - * @return bool - */ - public function hasNode($name) + public function hasNode(string $name): bool { - return \array_key_exists($name, $this->nodes); + return isset($this->nodes[$name]); } - /** - * @return Node - */ - public function getNode($name) + public function getNode(string $name): self { - if (!\array_key_exists($name, $this->nodes)) { - throw new \LogicException(sprintf('Node "%s" does not exist for Node "%s".', $name, \get_class($this))); + if (!isset($this->nodes[$name])) { + throw new \LogicException(sprintf('Node "%s" does not exist for Node "%s".', $name, static::class)); } return $this->nodes[$name]; } - public function setNode($name, $node = null) + public function setNode(string $name, self $node): void { - if (!$node instanceof \Twig_NodeInterface) { - @trigger_error(sprintf('Using "%s" for the value of node "%s" of "%s" is deprecated since version 1.25 and will be removed in 2.0.', \is_object($node) ? \get_class($node) : (null === $node ? 'null' : \gettype($node)), $name, \get_class($this)), E_USER_DEPRECATED); - } - $this->nodes[$name] = $node; } - public function removeNode($name) + public function removeNode(string $name): void { unset($this->nodes[$name]); } + /** + * @return int + */ + #[\ReturnTypeWillChange] public function count() { return \count($this->nodes); } - public function getIterator() + public function getIterator(): \Traversable { return new \ArrayIterator($this->nodes); } - public function setTemplateName($name) + public function getTemplateName(): ?string { - $this->name = $name; - foreach ($this->nodes as $node) { - if (null !== $node) { - $node->setTemplateName($name); - } - } + return $this->sourceContext ? $this->sourceContext->getName() : null; } - public function getTemplateName() - { - return $this->name; - } - - public function setSourceContext(Source $source) + public function setSourceContext(Source $source): void { $this->sourceContext = $source; foreach ($this->nodes as $node) { - if ($node instanceof Node) { - $node->setSourceContext($source); - } + $node->setSourceContext($source); } } - public function getSourceContext() + public function getSourceContext(): ?Source { return $this->sourceContext; } - - /** - * @deprecated since 1.27 (to be removed in 2.0) - */ - public function setFilename($name) - { - @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use setTemplateName() instead.', E_USER_DEPRECATED); - - $this->setTemplateName($name); - } - - /** - * @deprecated since 1.27 (to be removed in 2.0) - */ - public function getFilename() - { - @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getTemplateName() instead.', E_USER_DEPRECATED); - - return $this->name; - } } - -class_alias('Twig\Node\Node', 'Twig_Node'); - -// Ensure that the aliased name is loaded to keep BC for classes implementing the typehint with the old aliased name. -class_exists('Twig\Compiler'); diff --git a/lib/twig/twig/src/Node/NodeCaptureInterface.php b/lib/twig/twig/src/Node/NodeCaptureInterface.php index 474003f34b..9fb6a0ca14 100644 --- a/lib/twig/twig/src/Node/NodeCaptureInterface.php +++ b/lib/twig/twig/src/Node/NodeCaptureInterface.php @@ -19,5 +19,3 @@ namespace Twig\Node; interface NodeCaptureInterface { } - -class_alias('Twig\Node\NodeCaptureInterface', 'Twig_NodeCaptureInterface'); diff --git a/lib/twig/twig/src/Node/NodeOutputInterface.php b/lib/twig/twig/src/Node/NodeOutputInterface.php index 8b046ee766..5e35b406be 100644 --- a/lib/twig/twig/src/Node/NodeOutputInterface.php +++ b/lib/twig/twig/src/Node/NodeOutputInterface.php @@ -19,5 +19,3 @@ namespace Twig\Node; interface NodeOutputInterface { } - -class_alias('Twig\Node\NodeOutputInterface', 'Twig_NodeOutputInterface'); diff --git a/lib/twig/twig/src/Node/PrintNode.php b/lib/twig/twig/src/Node/PrintNode.php index 27f1ca4227..60386d2996 100644 --- a/lib/twig/twig/src/Node/PrintNode.php +++ b/lib/twig/twig/src/Node/PrintNode.php @@ -22,12 +22,12 @@ use Twig\Node\Expression\AbstractExpression; */ class PrintNode extends Node implements NodeOutputInterface { - public function __construct(AbstractExpression $expr, $lineno, $tag = null) + public function __construct(AbstractExpression $expr, int $lineno, string $tag = null) { parent::__construct(['expr' => $expr], [], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) @@ -37,5 +37,3 @@ class PrintNode extends Node implements NodeOutputInterface ; } } - -class_alias('Twig\Node\PrintNode', 'Twig_Node_Print'); diff --git a/lib/twig/twig/src/Node/SandboxNode.php b/lib/twig/twig/src/Node/SandboxNode.php index 2d644c3a13..4d5666bff1 100644 --- a/lib/twig/twig/src/Node/SandboxNode.php +++ b/lib/twig/twig/src/Node/SandboxNode.php @@ -20,12 +20,12 @@ use Twig\Compiler; */ class SandboxNode extends Node { - public function __construct(\Twig_NodeInterface $body, $lineno, $tag = null) + public function __construct(Node $body, int $lineno, string $tag = null) { parent::__construct(['body' => $body], [], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) @@ -34,14 +34,19 @@ class SandboxNode extends Node ->write("\$this->sandbox->enableSandbox();\n") ->outdent() ->write("}\n") + ->write("try {\n") + ->indent() ->subcompile($this->getNode('body')) + ->outdent() + ->write("} finally {\n") + ->indent() ->write("if (!\$alreadySandboxed) {\n") ->indent() ->write("\$this->sandbox->disableSandbox();\n") ->outdent() ->write("}\n") + ->outdent() + ->write("}\n") ; } } - -class_alias('Twig\Node\SandboxNode', 'Twig_Node_Sandbox'); diff --git a/lib/twig/twig/src/Node/SandboxedPrintNode.php b/lib/twig/twig/src/Node/SandboxedPrintNode.php deleted file mode 100644 index 2359af9110..0000000000 --- a/lib/twig/twig/src/Node/SandboxedPrintNode.php +++ /dev/null @@ -1,69 +0,0 @@ - - */ -class SandboxedPrintNode extends PrintNode -{ - public function compile(Compiler $compiler) - { - $compiler - ->addDebugInfo($this) - ->write('echo ') - ; - $expr = $this->getNode('expr'); - if ($expr instanceof ConstantExpression) { - $compiler - ->subcompile($expr) - ->raw(";\n") - ; - } else { - $compiler - ->write('$this->env->getExtension(\'\Twig\Extension\SandboxExtension\')->ensureToStringAllowed(') - ->subcompile($expr) - ->raw(");\n") - ; - } - } - - /** - * Removes node filters. - * - * This is mostly needed when another visitor adds filters (like the escaper one). - * - * @return Node - */ - protected function removeNodeFilter(Node $node) - { - if ($node instanceof FilterExpression) { - return $this->removeNodeFilter($node->getNode('node')); - } - - return $node; - } -} - -class_alias('Twig\Node\SandboxedPrintNode', 'Twig_Node_SandboxedPrint'); diff --git a/lib/twig/twig/src/Node/SetNode.php b/lib/twig/twig/src/Node/SetNode.php index 656103b9fd..96b6bd8bf5 100644 --- a/lib/twig/twig/src/Node/SetNode.php +++ b/lib/twig/twig/src/Node/SetNode.php @@ -21,7 +21,7 @@ use Twig\Node\Expression\ConstantExpression; */ class SetNode extends Node implements NodeCaptureInterface { - public function __construct($capture, \Twig_NodeInterface $names, \Twig_NodeInterface $values, $lineno, $tag = null) + public function __construct(bool $capture, Node $names, Node $values, int $lineno, string $tag = null) { parent::__construct(['names' => $names, 'values' => $values], ['capture' => $capture, 'safe' => false], $lineno, $tag); @@ -41,7 +41,7 @@ class SetNode extends Node implements NodeCaptureInterface } } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->addDebugInfo($this); @@ -103,5 +103,3 @@ class SetNode extends Node implements NodeCaptureInterface $compiler->raw(";\n"); } } - -class_alias('Twig\Node\SetNode', 'Twig_Node_Set'); diff --git a/lib/twig/twig/src/Node/SetTempNode.php b/lib/twig/twig/src/Node/SetTempNode.php deleted file mode 100644 index 918fb991de..0000000000 --- a/lib/twig/twig/src/Node/SetTempNode.php +++ /dev/null @@ -1,44 +0,0 @@ - $name], $lineno); - } - - public function compile(Compiler $compiler) - { - $name = $this->getAttribute('name'); - $compiler - ->addDebugInfo($this) - ->write('if (isset($context[') - ->string($name) - ->raw('])) { $_') - ->raw($name) - ->raw('_ = $context[') - ->repr($name) - ->raw(']; } else { $_') - ->raw($name) - ->raw("_ = null; }\n") - ; - } -} - -class_alias('Twig\Node\SetTempNode', 'Twig_Node_SetTemp'); diff --git a/lib/twig/twig/src/Node/SpacelessNode.php b/lib/twig/twig/src/Node/SpacelessNode.php deleted file mode 100644 index c8d32daf6b..0000000000 --- a/lib/twig/twig/src/Node/SpacelessNode.php +++ /dev/null @@ -1,47 +0,0 @@ - - */ -class SpacelessNode extends Node -{ - public function __construct(\Twig_NodeInterface $body, $lineno, $tag = 'spaceless') - { - parent::__construct(['body' => $body], [], $lineno, $tag); - } - - public function compile(Compiler $compiler) - { - $compiler - ->addDebugInfo($this) - ; - if ($compiler->getEnvironment()->isDebug()) { - $compiler->write("ob_start();\n"); - } else { - $compiler->write("ob_start(function () { return ''; });\n"); - } - $compiler - ->subcompile($this->getNode('body')) - ->write("echo trim(preg_replace('/>\s+<', ob_get_clean()));\n") - ; - } -} - -class_alias('Twig\Node\SpacelessNode', 'Twig_Node_Spaceless'); diff --git a/lib/twig/twig/src/Node/TextNode.php b/lib/twig/twig/src/Node/TextNode.php index 9ac435e904..d74ebe630c 100644 --- a/lib/twig/twig/src/Node/TextNode.php +++ b/lib/twig/twig/src/Node/TextNode.php @@ -21,12 +21,12 @@ use Twig\Compiler; */ class TextNode extends Node implements NodeOutputInterface { - public function __construct($data, $lineno) + public function __construct(string $data, int $lineno) { parent::__construct([], ['data' => $data], $lineno); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->addDebugInfo($this) @@ -36,5 +36,3 @@ class TextNode extends Node implements NodeOutputInterface ; } } - -class_alias('Twig\Node\TextNode', 'Twig_Node_Text'); diff --git a/lib/twig/twig/src/Node/WithNode.php b/lib/twig/twig/src/Node/WithNode.php index f5ae9246dd..56a334496e 100644 --- a/lib/twig/twig/src/Node/WithNode.php +++ b/lib/twig/twig/src/Node/WithNode.php @@ -20,20 +20,24 @@ use Twig\Compiler; */ class WithNode extends Node { - public function __construct(Node $body, Node $variables = null, $only = false, $lineno, $tag = null) + public function __construct(Node $body, ?Node $variables, bool $only, int $lineno, string $tag = null) { $nodes = ['body' => $body]; if (null !== $variables) { $nodes['variables'] = $variables; } - parent::__construct($nodes, ['only' => (bool) $only], $lineno, $tag); + parent::__construct($nodes, ['only' => $only], $lineno, $tag); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler->addDebugInfo($this); + $parentContextName = $compiler->getVarName(); + + $compiler->write(sprintf("\$%s = \$context;\n", $parentContextName)); + if ($this->hasNode('variables')) { $node = $this->getNode('variables'); $varsName = $compiler->getVarName(); @@ -52,21 +56,15 @@ class WithNode extends Node ; if ($this->getAttribute('only')) { - $compiler->write("\$context = ['_parent' => \$context];\n"); - } else { - $compiler->write("\$context['_parent'] = \$context;\n"); + $compiler->write("\$context = [];\n"); } $compiler->write(sprintf("\$context = \$this->env->mergeGlobals(array_merge(\$context, \$%s));\n", $varsName)); - } else { - $compiler->write("\$context['_parent'] = \$context;\n"); } $compiler ->subcompile($this->getNode('body')) - ->write("\$context = \$context['_parent'];\n") + ->write(sprintf("\$context = \$%s;\n", $parentContextName)) ; } } - -class_alias('Twig\Node\WithNode', 'Twig_Node_With'); diff --git a/lib/twig/twig/src/NodeTraverser.php b/lib/twig/twig/src/NodeTraverser.php index bd25d3cc74..47a2d5ca3e 100644 --- a/lib/twig/twig/src/NodeTraverser.php +++ b/lib/twig/twig/src/NodeTraverser.php @@ -11,6 +11,7 @@ namespace Twig; +use Twig\Node\Node; use Twig\NodeVisitor\NodeVisitorInterface; /** @@ -18,14 +19,12 @@ use Twig\NodeVisitor\NodeVisitorInterface; * * It visits all nodes and their children and calls the given visitor for each. * - * @final - * * @author Fabien Potencier */ -class NodeTraverser +final class NodeTraverser { - protected $env; - protected $visitors = []; + private $env; + private $visitors = []; /** * @param NodeVisitorInterface[] $visitors @@ -38,17 +37,15 @@ class NodeTraverser } } - public function addVisitor(NodeVisitorInterface $visitor) + public function addVisitor(NodeVisitorInterface $visitor): void { $this->visitors[$visitor->getPriority()][] = $visitor; } /** * Traverses a node and calls the registered visitors. - * - * @return \Twig_NodeInterface */ - public function traverse(\Twig_NodeInterface $node) + public function traverse(Node $node): Node { ksort($this->visitors); foreach ($this->visitors as $visitors) { @@ -60,20 +57,12 @@ class NodeTraverser return $node; } - protected function traverseForVisitor(NodeVisitorInterface $visitor, \Twig_NodeInterface $node = null) + private function traverseForVisitor(NodeVisitorInterface $visitor, Node $node): ?Node { - if (null === $node) { - return; - } - $node = $visitor->enterNode($node, $this->env); foreach ($node as $k => $n) { - if (null === $n) { - continue; - } - - if (false !== ($m = $this->traverseForVisitor($visitor, $n)) && null !== $m) { + if (null !== $m = $this->traverseForVisitor($visitor, $n)) { if ($m !== $n) { $node->setNode($k, $m); } @@ -85,5 +74,3 @@ class NodeTraverser return $visitor->leaveNode($node, $this->env); } } - -class_alias('Twig\NodeTraverser', 'Twig_NodeTraverser'); diff --git a/lib/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php b/lib/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php index b66c3c6f11..d7036ae551 100644 --- a/lib/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php +++ b/lib/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php @@ -23,21 +23,13 @@ use Twig\Node\Node; */ abstract class AbstractNodeVisitor implements NodeVisitorInterface { - final public function enterNode(\Twig_NodeInterface $node, Environment $env) + final public function enterNode(Node $node, Environment $env): Node { - if (!$node instanceof Node) { - throw new \LogicException(sprintf('%s only supports \Twig\Node\Node instances.', __CLASS__)); - } - return $this->doEnterNode($node, $env); } - final public function leaveNode(\Twig_NodeInterface $node, Environment $env) + final public function leaveNode(Node $node, Environment $env): ?Node { - if (!$node instanceof Node) { - throw new \LogicException(sprintf('%s only supports \Twig\Node\Node instances.', __CLASS__)); - } - return $this->doLeaveNode($node, $env); } @@ -51,9 +43,7 @@ abstract class AbstractNodeVisitor implements NodeVisitorInterface /** * Called after child nodes are visited. * - * @return Node|false|null The modified node or null if the node must be removed + * @return Node|null The modified node or null if the node must be removed */ abstract protected function doLeaveNode(Node $node, Environment $env); } - -class_alias('Twig\NodeVisitor\AbstractNodeVisitor', 'Twig_BaseNodeVisitor'); diff --git a/lib/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php b/lib/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php index f6e16fa7d7..fe56ea3074 100644 --- a/lib/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php +++ b/lib/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php @@ -12,6 +12,7 @@ namespace Twig\NodeVisitor; use Twig\Environment; +use Twig\Extension\EscaperExtension; use Twig\Node\AutoEscapeNode; use Twig\Node\BlockNode; use Twig\Node\BlockReferenceNode; @@ -27,28 +28,28 @@ use Twig\Node\PrintNode; use Twig\NodeTraverser; /** - * @final - * * @author Fabien Potencier + * + * @internal */ -class EscaperNodeVisitor extends AbstractNodeVisitor +final class EscaperNodeVisitor implements NodeVisitorInterface { - protected $statusStack = []; - protected $blocks = []; - protected $safeAnalysis; - protected $traverser; - protected $defaultStrategy = false; - protected $safeVars = []; + private $statusStack = []; + private $blocks = []; + private $safeAnalysis; + private $traverser; + private $defaultStrategy = false; + private $safeVars = []; public function __construct() { $this->safeAnalysis = new SafeAnalysisNodeVisitor(); } - protected function doEnterNode(Node $node, Environment $env) + public function enterNode(Node $node, Environment $env): Node { if ($node instanceof ModuleNode) { - if ($env->hasExtension('\Twig\Extension\EscaperExtension') && $defaultStrategy = $env->getExtension('\Twig\Extension\EscaperExtension')->getDefaultStrategy($node->getTemplateName())) { + if ($env->hasExtension(EscaperExtension::class) && $defaultStrategy = $env->getExtension(EscaperExtension::class)->getDefaultStrategy($node->getTemplateName())) { $this->defaultStrategy = $defaultStrategy; } $this->safeVars = []; @@ -64,7 +65,7 @@ class EscaperNodeVisitor extends AbstractNodeVisitor return $node; } - protected function doLeaveNode(Node $node, Environment $env) + public function leaveNode(Node $node, Environment $env): ?Node { if ($node instanceof ModuleNode) { $this->defaultStrategy = false; @@ -90,7 +91,7 @@ class EscaperNodeVisitor extends AbstractNodeVisitor return $node; } - private function shouldUnwrapConditional(ConditionalExpression $expression, Environment $env, $type) + private function shouldUnwrapConditional(ConditionalExpression $expression, Environment $env, string $type): bool { $expr2Safe = $this->isSafeFor($type, $expression->getNode('expr2'), $env); $expr3Safe = $this->isSafeFor($type, $expression->getNode('expr3'), $env); @@ -98,7 +99,7 @@ class EscaperNodeVisitor extends AbstractNodeVisitor return $expr2Safe !== $expr3Safe; } - private function unwrapConditional(ConditionalExpression $expression, Environment $env, $type) + private function unwrapConditional(ConditionalExpression $expression, Environment $env, string $type): ConditionalExpression { // convert "echo a ? b : c" to "a ? echo b : echo c" recursively $expr2 = $expression->getNode('expr2'); @@ -117,7 +118,7 @@ class EscaperNodeVisitor extends AbstractNodeVisitor return new ConditionalExpression($expression->getNode('expr1'), $expr2, $expr3, $expression->getTemplateLine()); } - private function escapeInlinePrintNode(InlinePrint $node, Environment $env, $type) + private function escapeInlinePrintNode(InlinePrint $node, Environment $env, string $type): Node { $expression = $node->getNode('node'); @@ -128,7 +129,7 @@ class EscaperNodeVisitor extends AbstractNodeVisitor return new InlinePrint($this->getEscaperFilter($type, $expression), $node->getTemplateLine()); } - protected function escapePrintNode(PrintNode $node, Environment $env, $type) + private function escapePrintNode(PrintNode $node, Environment $env, string $type): Node { if (false === $type) { return $node; @@ -145,7 +146,7 @@ class EscaperNodeVisitor extends AbstractNodeVisitor return new $class($this->getEscaperFilter($type, $expression), $node->getTemplateLine()); } - protected function preEscapeFilterNode(FilterExpression $filter, Environment $env) + private function preEscapeFilterNode(FilterExpression $filter, Environment $env): FilterExpression { $name = $filter->getNode('filter')->getAttribute('value'); @@ -164,7 +165,7 @@ class EscaperNodeVisitor extends AbstractNodeVisitor return $filter; } - protected function isSafeFor($type, \Twig_NodeInterface $expression, $env) + private function isSafeFor(string $type, Node $expression, Environment $env): bool { $safe = $this->safeAnalysis->getSafe($expression); @@ -182,7 +183,7 @@ class EscaperNodeVisitor extends AbstractNodeVisitor return \in_array($type, $safe) || \in_array('all', $safe); } - protected function needEscaping(Environment $env) + private function needEscaping(Environment $env) { if (\count($this->statusStack)) { return $this->statusStack[\count($this->statusStack) - 1]; @@ -191,19 +192,17 @@ class EscaperNodeVisitor extends AbstractNodeVisitor return $this->defaultStrategy ? $this->defaultStrategy : false; } - protected function getEscaperFilter($type, \Twig_NodeInterface $node) + private function getEscaperFilter(string $type, Node $node): FilterExpression { $line = $node->getTemplateLine(); $name = new ConstantExpression('escape', $line); - $args = new Node([new ConstantExpression((string) $type, $line), new ConstantExpression(null, $line), new ConstantExpression(true, $line)]); + $args = new Node([new ConstantExpression($type, $line), new ConstantExpression(null, $line), new ConstantExpression(true, $line)]); return new FilterExpression($node, $name, $args, $line); } - public function getPriority() + public function getPriority(): int { return 0; } } - -class_alias('Twig\NodeVisitor\EscaperNodeVisitor', 'Twig_NodeVisitor_Escaper'); diff --git a/lib/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php b/lib/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php new file mode 100644 index 0000000000..af477e6535 --- /dev/null +++ b/lib/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php @@ -0,0 +1,74 @@ + + * + * @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, 'import', 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/lib/twig/twig/src/NodeVisitor/NodeVisitorInterface.php b/lib/twig/twig/src/NodeVisitor/NodeVisitorInterface.php index 9b8730b48d..59e836dbdc 100644 --- a/lib/twig/twig/src/NodeVisitor/NodeVisitorInterface.php +++ b/lib/twig/twig/src/NodeVisitor/NodeVisitorInterface.php @@ -12,6 +12,7 @@ namespace Twig\NodeVisitor; use Twig\Environment; +use Twig\Node\Node; /** * Interface for node visitor classes. @@ -23,16 +24,16 @@ interface NodeVisitorInterface /** * Called before child nodes are visited. * - * @return \Twig_NodeInterface The modified node + * @return Node The modified node */ - public function enterNode(\Twig_NodeInterface $node, Environment $env); + public function enterNode(Node $node, Environment $env): Node; /** * Called after child nodes are visited. * - * @return \Twig_NodeInterface|false|null The modified node or null if the node must be removed + * @return Node|null The modified node or null if the node must be removed */ - public function leaveNode(\Twig_NodeInterface $node, Environment $env); + public function leaveNode(Node $node, Environment $env): ?Node; /** * Returns the priority for this visitor. @@ -43,8 +44,3 @@ interface NodeVisitorInterface */ public function getPriority(); } - -class_alias('Twig\NodeVisitor\NodeVisitorInterface', 'Twig_NodeVisitorInterface'); - -// Ensure that the aliased name is loaded to keep BC for classes implementing the typehint with the old aliased name. -class_exists('Twig\Environment'); diff --git a/lib/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php b/lib/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php index e5ea9b7cc3..7ac75e41ad 100644 --- a/lib/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php +++ b/lib/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php @@ -13,8 +13,6 @@ namespace Twig\NodeVisitor; use Twig\Environment; use Twig\Node\BlockReferenceNode; -use Twig\Node\BodyNode; -use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\BlockReferenceExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FilterExpression; @@ -22,12 +20,10 @@ use Twig\Node\Expression\FunctionExpression; use Twig\Node\Expression\GetAttrExpression; use Twig\Node\Expression\NameExpression; use Twig\Node\Expression\ParentExpression; -use Twig\Node\Expression\TempNameExpression; use Twig\Node\ForNode; use Twig\Node\IncludeNode; use Twig\Node\Node; use Twig\Node\PrintNode; -use Twig\Node\SetTempNode; /** * Tries to optimize the AST. @@ -37,63 +33,44 @@ use Twig\Node\SetTempNode; * You can configure which optimizations you want to activate via the * optimizer mode. * - * @final - * * @author Fabien Potencier + * + * @internal */ -class OptimizerNodeVisitor extends AbstractNodeVisitor +final class OptimizerNodeVisitor implements NodeVisitorInterface { - const OPTIMIZE_ALL = -1; - const OPTIMIZE_NONE = 0; - const OPTIMIZE_FOR = 2; - const OPTIMIZE_RAW_FILTER = 4; - const OPTIMIZE_VAR_ACCESS = 8; + public const OPTIMIZE_ALL = -1; + public const OPTIMIZE_NONE = 0; + public const OPTIMIZE_FOR = 2; + public const OPTIMIZE_RAW_FILTER = 4; - protected $loops = []; - protected $loopsTargets = []; - protected $optimizers; - protected $prependedNodes = []; - protected $inABody = false; + private $loops = []; + private $loopsTargets = []; + private $optimizers; /** * @param int $optimizers The optimizer mode */ - public function __construct($optimizers = -1) + public function __construct(int $optimizers = -1) { - if (!\is_int($optimizers) || $optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER | self::OPTIMIZE_VAR_ACCESS)) { + if ($optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER)) { throw new \InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers)); } $this->optimizers = $optimizers; } - protected function doEnterNode(Node $node, Environment $env) + public function enterNode(Node $node, Environment $env): Node { if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { $this->enterOptimizeFor($node, $env); } - if (\PHP_VERSION_ID < 50400 && self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('\Twig\Extension\SandboxExtension')) { - if ($this->inABody) { - if (!$node instanceof AbstractExpression) { - if ('Twig_Node' !== \get_class($node)) { - array_unshift($this->prependedNodes, []); - } - } else { - $node = $this->optimizeVariables($node, $env); - } - } elseif ($node instanceof BodyNode) { - $this->inABody = true; - } - } - return $node; } - protected function doLeaveNode(Node $node, Environment $env) + public function leaveNode(Node $node, Environment $env): ?Node { - $expression = $node instanceof AbstractExpression; - if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { $this->leaveOptimizeFor($node, $env); } @@ -104,33 +81,6 @@ class OptimizerNodeVisitor extends AbstractNodeVisitor $node = $this->optimizePrintNode($node, $env); - if (self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('\Twig\Extension\SandboxExtension')) { - if ($node instanceof BodyNode) { - $this->inABody = false; - } elseif ($this->inABody) { - if (!$expression && 'Twig_Node' !== \get_class($node) && $prependedNodes = array_shift($this->prependedNodes)) { - $nodes = []; - foreach (array_unique($prependedNodes) as $name) { - $nodes[] = new SetTempNode($name, $node->getTemplateLine()); - } - - $nodes[] = $node; - $node = new Node($nodes); - } - } - } - - return $node; - } - - protected function optimizeVariables(\Twig_NodeInterface $node, Environment $env) - { - if ('Twig_Node_Expression_Name' === \get_class($node) && $node->isSimple()) { - $this->prependedNodes[0][] = $node->getAttribute('name'); - - return new TempNameExpression($node->getAttribute('name'), $node->getTemplateLine()); - } - return $node; } @@ -140,10 +90,8 @@ class OptimizerNodeVisitor extends AbstractNodeVisitor * It replaces: * * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()" - * - * @return \Twig_NodeInterface */ - protected function optimizePrintNode(\Twig_NodeInterface $node, Environment $env) + private function optimizePrintNode(Node $node, Environment $env): Node { if (!$node instanceof PrintNode) { return $node; @@ -164,10 +112,8 @@ class OptimizerNodeVisitor extends AbstractNodeVisitor /** * Removes "raw" filters. - * - * @return \Twig_NodeInterface */ - protected function optimizeRawFilter(\Twig_NodeInterface $node, Environment $env) + private function optimizeRawFilter(Node $node, Environment $env): Node { if ($node instanceof FilterExpression && 'raw' == $node->getNode('filter')->getAttribute('value')) { return $node->getNode('node'); @@ -179,7 +125,7 @@ class OptimizerNodeVisitor extends AbstractNodeVisitor /** * Optimizes "for" tag by removing the "loop" variable creation whenever possible. */ - protected function enterOptimizeFor(\Twig_NodeInterface $node, Environment $env) + private function enterOptimizeFor(Node $node, Environment $env): void { if ($node instanceof ForNode) { // disable the loop variable by default @@ -243,7 +189,7 @@ class OptimizerNodeVisitor extends AbstractNodeVisitor /** * Optimizes "for" tag by removing the "loop" variable creation whenever possible. */ - protected function leaveOptimizeFor(\Twig_NodeInterface $node, Environment $env) + private function leaveOptimizeFor(Node $node, Environment $env): void { if ($node instanceof ForNode) { array_shift($this->loops); @@ -252,22 +198,20 @@ class OptimizerNodeVisitor extends AbstractNodeVisitor } } - protected function addLoopToCurrent() + private function addLoopToCurrent(): void { $this->loops[0]->setAttribute('with_loop', true); } - protected function addLoopToAll() + private function addLoopToAll(): void { foreach ($this->loops as $loop) { $loop->setAttribute('with_loop', true); } } - public function getPriority() + public function getPriority(): int { return 255; } } - -class_alias('Twig\NodeVisitor\OptimizerNodeVisitor', 'Twig_NodeVisitor_Optimizer'); diff --git a/lib/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php b/lib/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php index 97a7a3e6ee..90d6f2e0fd 100644 --- a/lib/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php +++ b/lib/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php @@ -24,19 +24,19 @@ use Twig\Node\Expression\ParentExpression; use Twig\Node\Node; /** - * @final + * @internal */ -class SafeAnalysisNodeVisitor extends AbstractNodeVisitor +final class SafeAnalysisNodeVisitor implements NodeVisitorInterface { - protected $data = []; - protected $safeVars = []; + private $data = []; + private $safeVars = []; - public function setSafeVars($safeVars) + public function setSafeVars(array $safeVars): void { $this->safeVars = $safeVars; } - public function getSafe(\Twig_NodeInterface $node) + public function getSafe(Node $node) { $hash = spl_object_hash($node); if (!isset($this->data[$hash])) { @@ -56,7 +56,7 @@ class SafeAnalysisNodeVisitor extends AbstractNodeVisitor } } - protected function setSafe(\Twig_NodeInterface $node, array $safe) + private function setSafe(Node $node, array $safe): void { $hash = spl_object_hash($node); if (isset($this->data[$hash])) { @@ -74,12 +74,12 @@ class SafeAnalysisNodeVisitor extends AbstractNodeVisitor ]; } - protected function doEnterNode(Node $node, Environment $env) + public function enterNode(Node $node, Environment $env): Node { return $node; } - protected function doLeaveNode(Node $node, Environment $env) + public function leaveNode(Node $node, Environment $env): ?Node { if ($node instanceof ConstantExpression) { // constants are marked safe for all @@ -98,7 +98,7 @@ class SafeAnalysisNodeVisitor extends AbstractNodeVisitor // filter expression is safe when the filter is safe $name = $node->getNode('filter')->getAttribute('value'); $args = $node->getNode('arguments'); - if (false !== $filter = $env->getFilter($name)) { + if ($filter = $env->getFilter($name)) { $safe = $filter->getSafe($args); if (null === $safe) { $safe = $this->intersectSafe($this->getSafe($node->getNode('node')), $filter->getPreservesSafety()); @@ -111,8 +111,7 @@ class SafeAnalysisNodeVisitor extends AbstractNodeVisitor // function expression is safe when the function is safe $name = $node->getAttribute('name'); $args = $node->getNode('arguments'); - $function = $env->getFunction($name); - if (false !== $function) { + if ($function = $env->getFunction($name)) { $this->setSafe($node, $function->getSafe($args)); } else { $this->setSafe($node, []); @@ -125,8 +124,7 @@ class SafeAnalysisNodeVisitor extends AbstractNodeVisitor } } elseif ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression) { $name = $node->getNode('node')->getAttribute('name'); - // attributes on template instances are safe - if ('_self' == $name || \in_array($name, $this->safeVars)) { + if (\in_array($name, $this->safeVars)) { $this->setSafe($node, ['all']); } else { $this->setSafe($node, []); @@ -138,7 +136,7 @@ class SafeAnalysisNodeVisitor extends AbstractNodeVisitor return $node; } - protected function intersectSafe(array $a = null, array $b = null) + private function intersectSafe(array $a = null, array $b = null): array { if (null === $a || null === $b) { return []; @@ -155,10 +153,8 @@ class SafeAnalysisNodeVisitor extends AbstractNodeVisitor return array_intersect($a, $b); } - public function getPriority() + public function getPriority(): int { return 0; } } - -class_alias('Twig\NodeVisitor\SafeAnalysisNodeVisitor', 'Twig_NodeVisitor_SafeAnalysis'); diff --git a/lib/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php b/lib/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php index c9403398f0..1446cee6b9 100644 --- a/lib/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php +++ b/lib/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php @@ -12,6 +12,7 @@ namespace Twig\NodeVisitor; use Twig\Environment; +use Twig\Node\CheckSecurityCallNode; use Twig\Node\CheckSecurityNode; use Twig\Node\CheckToStringNode; use Twig\Node\Expression\Binary\ConcatBinary; @@ -26,20 +27,19 @@ use Twig\Node\PrintNode; use Twig\Node\SetNode; /** - * @final - * * @author Fabien Potencier + * + * @internal */ -class SandboxNodeVisitor extends AbstractNodeVisitor +final class SandboxNodeVisitor implements NodeVisitorInterface { - protected $inAModule = false; - protected $tags; - protected $filters; - protected $functions; - + private $inAModule = false; + private $tags; + private $filters; + private $functions; private $needsToStringWrap = false; - protected function doEnterNode(Node $node, Environment $env) + public function enterNode(Node $node, Environment $env): Node { if ($node instanceof ModuleNode) { $this->inAModule = true; @@ -97,12 +97,13 @@ class SandboxNodeVisitor extends AbstractNodeVisitor return $node; } - protected function doLeaveNode(Node $node, Environment $env) + public function leaveNode(Node $node, Environment $env): ?Node { if ($node instanceof ModuleNode) { $this->inAModule = false; - $node->getNode('constructor_end')->setNode('_security_check', new Node([new CheckSecurityNode($this->filters, $this->tags, $this->functions), $node->getNode('display_start')])); + $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; @@ -112,7 +113,7 @@ class SandboxNodeVisitor extends AbstractNodeVisitor return $node; } - private function wrapNode(Node $node, $name) + private function wrapNode(Node $node, string $name): void { $expr = $node->getNode($name); if ($expr instanceof NameExpression || $expr instanceof GetAttrExpression) { @@ -120,7 +121,7 @@ class SandboxNodeVisitor extends AbstractNodeVisitor } } - private function wrapArrayNode(Node $node, $name) + private function wrapArrayNode(Node $node, string $name): void { $args = $node->getNode($name); foreach ($args as $name => $_) { @@ -128,10 +129,8 @@ class SandboxNodeVisitor extends AbstractNodeVisitor } } - public function getPriority() + public function getPriority(): int { return 0; } } - -class_alias('Twig\NodeVisitor\SandboxNodeVisitor', 'Twig_NodeVisitor_Sandbox'); diff --git a/lib/twig/twig/src/Parser.php b/lib/twig/twig/src/Parser.php index 0ea102cc81..4428208fed 100644 --- a/lib/twig/twig/src/Parser.php +++ b/lib/twig/twig/src/Parser.php @@ -24,30 +24,25 @@ use Twig\Node\NodeCaptureInterface; use Twig\Node\NodeOutputInterface; use Twig\Node\PrintNode; use Twig\Node\TextNode; -use Twig\NodeVisitor\NodeVisitorInterface; use Twig\TokenParser\TokenParserInterface; /** - * Default parser implementation. - * * @author Fabien Potencier */ -class Parser implements \Twig_ParserInterface +class Parser { - protected $stack = []; - protected $stream; - protected $parent; - protected $handlers; - protected $visitors; - protected $expressionParser; - protected $blocks; - protected $blockStack; - protected $macros; - protected $env; - protected $reservedMacroNames; - protected $importedSymbols; - protected $traits; - protected $embeddedTemplates = []; + private $stack = []; + private $stream; + private $parent; + private $visitors; + private $expressionParser; + private $blocks; + private $blockStack; + private $macros; + private $env; + private $importedSymbols; + private $traits; + private $embeddedTemplates = []; private $varNameSalt = 0; public function __construct(Environment $env) @@ -55,50 +50,17 @@ class Parser implements \Twig_ParserInterface $this->env = $env; } - /** - * @deprecated since 1.27 (to be removed in 2.0) - */ - public function getEnvironment() + public function getVarName(): string { - @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED); - - return $this->env; + return sprintf('__internal_parse_%d', $this->varNameSalt++); } - public function getVarName() + public function parse(TokenStream $stream, $test = null, bool $dropNeedle = false): ModuleNode { - return sprintf('__internal_%s', hash('sha256', __METHOD__.$this->stream->getSourceContext()->getCode().$this->varNameSalt++)); - } - - /** - * @deprecated since 1.27 (to be removed in 2.0). Use $parser->getStream()->getSourceContext()->getPath() instead. - */ - public function getFilename() - { - @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use $parser->getStream()->getSourceContext()->getPath() instead.', __METHOD__), E_USER_DEPRECATED); - - return $this->stream->getSourceContext()->getName(); - } - - public function parse(TokenStream $stream, $test = null, $dropNeedle = false) - { - // push all variables into the stack to keep the current state of the parser - // using get_object_vars() instead of foreach would lead to https://bugs.php.net/71336 - // This hack can be removed when min version if PHP 7.0 - $vars = []; - foreach ($this as $k => $v) { - $vars[$k] = $v; - } - - unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames']); + $vars = get_object_vars($this); + unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames'], $vars['varNameSalt']); $this->stack[] = $vars; - // tag handlers - if (null === $this->handlers) { - $this->handlers = $this->env->getTokenParsers(); - $this->handlers->setParser($this); - } - // node visitors if (null === $this->visitors) { $this->visitors = $this->env->getNodeVisitors(); @@ -116,7 +78,6 @@ class Parser implements \Twig_ParserInterface $this->blockStack = []; $this->importedSymbols = [[]]; $this->embeddedTemplates = []; - $this->varNameSalt = 0; try { $body = $this->subparse($test, $dropNeedle); @@ -150,33 +111,33 @@ class Parser implements \Twig_ParserInterface return $node; } - public function subparse($test, $dropNeedle = false) + 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: + case /* Token::TEXT_TYPE */ 0: $token = $this->stream->next(); $rv[] = new TextNode($token->getValue(), $token->getLine()); break; - case Token::VAR_START_TYPE: + case /* Token::VAR_START_TYPE */ 2: $token = $this->stream->next(); $expr = $this->expressionParser->parseExpression(); - $this->stream->expect(Token::VAR_END_TYPE); + $this->stream->expect(/* Token::VAR_END_TYPE */ 4); $rv[] = new PrintNode($expr, $token->getLine()); break; - case Token::BLOCK_START_TYPE: + case /* Token::BLOCK_START_TYPE */ 1: $this->stream->next(); $token = $this->getCurrentToken(); - if (Token::NAME_TYPE !== $token->getType()) { + if (/* Token::NAME_TYPE */ 5 !== $token->getType()) { throw new SyntaxError('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext()); } - if (null !== $test && \call_user_func($test, $token)) { + if (null !== $test && $test($token)) { if ($dropNeedle) { $this->stream->next(); } @@ -188,8 +149,7 @@ class Parser implements \Twig_ParserInterface return new Node($rv, [], $lineno); } - $subparser = $this->handlers->getTokenParser($token->getValue()); - if (null === $subparser) { + if (!$subparser = $this->env->getTokenParser($token->getValue())) { if (null !== $test) { $e = new SyntaxError(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); @@ -198,7 +158,7 @@ class Parser implements \Twig_ParserInterface } } else { $e = new SyntaxError(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); - $e->addSuggestions($token->getValue(), array_keys($this->env->getTags())); + $e->addSuggestions($token->getValue(), array_keys($this->env->getTokenParsers())); } throw $e; @@ -206,6 +166,7 @@ class Parser implements \Twig_ParserInterface $this->stream->next(); + $subparser->setParser($this); $node = $subparser->parse($token); if (null !== $node) { $rv[] = $node; @@ -224,98 +185,57 @@ class Parser implements \Twig_ParserInterface return new Node($rv, [], $lineno); } - /** - * @deprecated since 1.27 (to be removed in 2.0) - */ - public function addHandler($name, $class) - { - @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED); - - $this->handlers[$name] = $class; - } - - /** - * @deprecated since 1.27 (to be removed in 2.0) - */ - public function addNodeVisitor(NodeVisitorInterface $visitor) - { - @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED); - - $this->visitors[] = $visitor; - } - - public function getBlockStack() + public function getBlockStack(): array { return $this->blockStack; } public function peekBlockStack() { - return isset($this->blockStack[\count($this->blockStack) - 1]) ? $this->blockStack[\count($this->blockStack) - 1] : null; + return $this->blockStack[\count($this->blockStack) - 1] ?? null; } - public function popBlockStack() + public function popBlockStack(): void { array_pop($this->blockStack); } - public function pushBlockStack($name) + public function pushBlockStack($name): void { $this->blockStack[] = $name; } - public function hasBlock($name) + public function hasBlock(string $name): bool { return isset($this->blocks[$name]); } - public function getBlock($name) + public function getBlock(string $name): Node { return $this->blocks[$name]; } - public function setBlock($name, BlockNode $value) + public function setBlock(string $name, BlockNode $value): void { $this->blocks[$name] = new BodyNode([$value], [], $value->getTemplateLine()); } - public function hasMacro($name) + public function hasMacro(string $name): bool { return isset($this->macros[$name]); } - public function setMacro($name, MacroNode $node) + public function setMacro(string $name, MacroNode $node): void { - if ($this->isReservedMacroName($name)) { - throw new SyntaxError(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword.', $name), $node->getTemplateLine(), $this->stream->getSourceContext()); - } - $this->macros[$name] = $node; } - public function isReservedMacroName($name) - { - if (null === $this->reservedMacroNames) { - $this->reservedMacroNames = []; - $r = new \ReflectionClass($this->env->getBaseTemplateClass()); - foreach ($r->getMethods() as $method) { - $methodName = strtolower($method->getName()); - - if ('get' === substr($methodName, 0, 3) && isset($methodName[3])) { - $this->reservedMacroNames[] = substr($methodName, 3); - } - } - } - - return \in_array(strtolower($name), $this->reservedMacroNames); - } - - public function addTrait($trait) + public function addTrait($trait): void { $this->traits[] = $trait; } - public function hasTraits() + public function hasTraits(): bool { return \count($this->traits) > 0; } @@ -327,78 +247,58 @@ class Parser implements \Twig_ParserInterface $this->embeddedTemplates[] = $template; } - public function addImportedSymbol($type, $alias, $name = null, AbstractExpression $node = null) + public function addImportedSymbol(string $type, string $alias, string $name = null, AbstractExpression $node = null): void { $this->importedSymbols[0][$type][$alias] = ['name' => $name, 'node' => $node]; } - public function getImportedSymbol($type, $alias) + public function getImportedSymbol(string $type, string $alias) { - if (null !== $this->peekBlockStack()) { - foreach ($this->importedSymbols as $functions) { - if (isset($functions[$type][$alias])) { - if (\count($this->blockStack) > 1) { - return null; - } - - return $functions[$type][$alias]; - } - } - } else { - return isset($this->importedSymbols[0][$type][$alias]) ? $this->importedSymbols[0][$type][$alias] : null; - } + // if the symbol does not exist in the current scope (0), try in the main/global scope (last index) + return $this->importedSymbols[0][$type][$alias] ?? ($this->importedSymbols[\count($this->importedSymbols) - 1][$type][$alias] ?? null); } - public function isMainScope() + public function isMainScope(): bool { return 1 === \count($this->importedSymbols); } - public function pushLocalScope() + public function pushLocalScope(): void { array_unshift($this->importedSymbols, []); } - public function popLocalScope() + public function popLocalScope(): void { array_shift($this->importedSymbols); } - /** - * @return ExpressionParser - */ - public function getExpressionParser() + public function getExpressionParser(): ExpressionParser { return $this->expressionParser; } - public function getParent() + public function getParent(): ?Node { return $this->parent; } - public function setParent($parent) + public function setParent(?Node $parent): void { $this->parent = $parent; } - /** - * @return TokenStream - */ - public function getStream() + public function getStream(): TokenStream { return $this->stream; } - /** - * @return Token - */ - public function getCurrentToken() + public function getCurrentToken(): Token { return $this->stream->getCurrent(); } - protected function filterBodyNodes(\Twig_NodeInterface $node) + private function filterBodyNodes(Node $node, bool $nested = false): ?Node { // check that the body does not contain non-empty output nodes if ( @@ -410,24 +310,35 @@ class Parser implements \Twig_ParserInterface $t = substr($node->getAttribute('data'), 3); if ('' === $t || ctype_space($t)) { // bypass empty nodes starting with a BOM - return; + return null; } } 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 will "capture" the output + // 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; } - if ($node instanceof NodeOutputInterface) { - return; + // "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)) { + if (null !== $n && null === $this->filterBodyNodes($n, $nested)) { $node->removeNode($k); } } @@ -435,5 +346,3 @@ class Parser implements \Twig_ParserInterface return $node; } } - -class_alias('Twig\Parser', 'Twig_Parser'); diff --git a/lib/twig/twig/src/Profiler/Dumper/BaseDumper.php b/lib/twig/twig/src/Profiler/Dumper/BaseDumper.php index d965dc7542..4da43e475f 100644 --- a/lib/twig/twig/src/Profiler/Dumper/BaseDumper.php +++ b/lib/twig/twig/src/Profiler/Dumper/BaseDumper.php @@ -20,18 +20,18 @@ abstract class BaseDumper { private $root; - public function dump(Profile $profile) + public function dump(Profile $profile): string { return $this->dumpProfile($profile); } - abstract protected function formatTemplate(Profile $profile, $prefix); + abstract protected function formatTemplate(Profile $profile, $prefix): string; - abstract protected function formatNonTemplate(Profile $profile, $prefix); + abstract protected function formatNonTemplate(Profile $profile, $prefix): string; - abstract protected function formatTime(Profile $profile, $percent); + abstract protected function formatTime(Profile $profile, $percent): string; - private function dumpProfile(Profile $profile, $prefix = '', $sibling = false) + private function dumpProfile(Profile $profile, $prefix = '', $sibling = false): string { if ($profile->isRoot()) { $this->root = $profile->getDuration(); @@ -61,5 +61,3 @@ abstract class BaseDumper return $str; } } - -class_alias('Twig\Profiler\Dumper\BaseDumper', 'Twig_Profiler_Dumper_Base'); diff --git a/lib/twig/twig/src/Profiler/Dumper/BlackfireDumper.php b/lib/twig/twig/src/Profiler/Dumper/BlackfireDumper.php index a1c3c7bce6..03abe0fa07 100644 --- a/lib/twig/twig/src/Profiler/Dumper/BlackfireDumper.php +++ b/lib/twig/twig/src/Profiler/Dumper/BlackfireDumper.php @@ -15,12 +15,10 @@ use Twig\Profiler\Profile; /** * @author Fabien Potencier - * - * @final */ -class BlackfireDumper +final class BlackfireDumper { - public function dump(Profile $profile) + public function dump(Profile $profile): string { $data = []; $this->dumpProfile('main()', $profile, $data); @@ -30,19 +28,19 @@ class BlackfireDumper $str = << $values) { - $str .= "{$name}//{$values['ct']} {$values['wt']} {$values['mu']} {$values['pmu']}\n"; + $str .= "$name//{$values['ct']} {$values['wt']} {$values['mu']} {$values['pmu']}\n"; } return $str; } - private function dumpChildren($parent, Profile $profile, &$data) + private function dumpChildren(string $parent, Profile $profile, &$data) { foreach ($profile as $p) { if ($p->isTemplate()) { @@ -55,7 +53,7 @@ EOF; } } - private function dumpProfile($edge, Profile $profile, &$data) + private function dumpProfile(string $edge, Profile $profile, &$data) { if (isset($data[$edge])) { ++$data[$edge]['ct']; @@ -72,5 +70,3 @@ EOF; } } } - -class_alias('Twig\Profiler\Dumper\BlackfireDumper', 'Twig_Profiler_Dumper_Blackfire'); diff --git a/lib/twig/twig/src/Profiler/Dumper/HtmlDumper.php b/lib/twig/twig/src/Profiler/Dumper/HtmlDumper.php index c70b405b30..1f2433b4d3 100644 --- a/lib/twig/twig/src/Profiler/Dumper/HtmlDumper.php +++ b/lib/twig/twig/src/Profiler/Dumper/HtmlDumper.php @@ -15,10 +15,8 @@ use Twig\Profiler\Profile; /** * @author Fabien Potencier - * - * @final */ -class HtmlDumper extends BaseDumper +final class HtmlDumper extends BaseDumper { private static $colors = [ 'block' => '#dfd', @@ -27,25 +25,23 @@ class HtmlDumper extends BaseDumper 'big' => '#d44', ]; - public function dump(Profile $profile) + public function dump(Profile $profile): string { return '
    '.parent::dump($profile).'
    '; } - protected function formatTemplate(Profile $profile, $prefix) + protected function formatTemplate(Profile $profile, $prefix): string { return sprintf('%s└ %s', $prefix, self::$colors['template'], $profile->getTemplate()); } - protected function formatNonTemplate(Profile $profile, $prefix) + protected function formatNonTemplate(Profile $profile, $prefix): string { return sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), isset(self::$colors[$profile->getType()]) ? self::$colors[$profile->getType()] : 'auto', $profile->getName()); } - protected function formatTime(Profile $profile, $percent) + protected function formatTime(Profile $profile, $percent): string { return sprintf('%.2fms/%.0f%%', $percent > 20 ? self::$colors['big'] : 'auto', $profile->getDuration() * 1000, $percent); } } - -class_alias('Twig\Profiler\Dumper\HtmlDumper', 'Twig_Profiler_Dumper_Html'); diff --git a/lib/twig/twig/src/Profiler/Dumper/TextDumper.php b/lib/twig/twig/src/Profiler/Dumper/TextDumper.php index c6b515891e..31561c466b 100644 --- a/lib/twig/twig/src/Profiler/Dumper/TextDumper.php +++ b/lib/twig/twig/src/Profiler/Dumper/TextDumper.php @@ -15,25 +15,21 @@ use Twig\Profiler\Profile; /** * @author Fabien Potencier - * - * @final */ -class TextDumper extends BaseDumper +final class TextDumper extends BaseDumper { - protected function formatTemplate(Profile $profile, $prefix) + protected function formatTemplate(Profile $profile, $prefix): string { return sprintf('%s└ %s', $prefix, $profile->getTemplate()); } - protected function formatNonTemplate(Profile $profile, $prefix) + protected function formatNonTemplate(Profile $profile, $prefix): string { return sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), $profile->getName()); } - protected function formatTime(Profile $profile, $percent) + protected function formatTime(Profile $profile, $percent): string { return sprintf('%.2fms/%.0f%%', $profile->getDuration() * 1000, $percent); } } - -class_alias('Twig\Profiler\Dumper\TextDumper', 'Twig_Profiler_Dumper_Text'); diff --git a/lib/twig/twig/src/Profiler/Node/EnterProfileNode.php b/lib/twig/twig/src/Profiler/Node/EnterProfileNode.php index 8ffd3dc78c..1494baf44a 100644 --- a/lib/twig/twig/src/Profiler/Node/EnterProfileNode.php +++ b/lib/twig/twig/src/Profiler/Node/EnterProfileNode.php @@ -21,17 +21,17 @@ use Twig\Node\Node; */ class EnterProfileNode extends Node { - public function __construct($extensionName, $type, $name, $varName) + public function __construct(string $extensionName, string $type, string $name, string $varName) { parent::__construct([], ['extension_name' => $extensionName, 'name' => $name, 'type' => $type, 'var_name' => $varName]); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler - ->write(sprintf('$%s = $this->env->getExtension(', $this->getAttribute('var_name'))) + ->write(sprintf('$%s = $this->extensions[', $this->getAttribute('var_name'))) ->repr($this->getAttribute('extension_name')) - ->raw(");\n") + ->raw("];\n") ->write(sprintf('$%s->enter($%s = new \Twig\Profiler\Profile($this->getTemplateName(), ', $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) ->repr($this->getAttribute('type')) ->raw(', ') @@ -40,5 +40,3 @@ class EnterProfileNode extends Node ; } } - -class_alias('Twig\Profiler\Node\EnterProfileNode', 'Twig_Profiler_Node_EnterProfile'); diff --git a/lib/twig/twig/src/Profiler/Node/LeaveProfileNode.php b/lib/twig/twig/src/Profiler/Node/LeaveProfileNode.php index 3b7a74d026..94cebbaa83 100644 --- a/lib/twig/twig/src/Profiler/Node/LeaveProfileNode.php +++ b/lib/twig/twig/src/Profiler/Node/LeaveProfileNode.php @@ -21,12 +21,12 @@ use Twig\Node\Node; */ class LeaveProfileNode extends Node { - public function __construct($varName) + public function __construct(string $varName) { parent::__construct([], ['var_name' => $varName]); } - public function compile(Compiler $compiler) + public function compile(Compiler $compiler): void { $compiler ->write("\n") @@ -34,5 +34,3 @@ class LeaveProfileNode extends Node ; } } - -class_alias('Twig\Profiler\Node\LeaveProfileNode', 'Twig_Profiler_Node_LeaveProfile'); diff --git a/lib/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php b/lib/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php index 41ca9e1f29..91abee807d 100644 --- a/lib/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php +++ b/lib/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php @@ -17,64 +17,54 @@ use Twig\Node\BodyNode; use Twig\Node\MacroNode; use Twig\Node\ModuleNode; use Twig\Node\Node; -use Twig\NodeVisitor\AbstractNodeVisitor; +use Twig\NodeVisitor\NodeVisitorInterface; use Twig\Profiler\Node\EnterProfileNode; use Twig\Profiler\Node\LeaveProfileNode; use Twig\Profiler\Profile; /** * @author Fabien Potencier - * - * @final */ -class ProfilerNodeVisitor extends AbstractNodeVisitor +final class ProfilerNodeVisitor implements NodeVisitorInterface { private $extensionName; + private $varName; - public function __construct($extensionName) + public function __construct(string $extensionName) { $this->extensionName = $extensionName; + $this->varName = sprintf('__internal_%s', hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $extensionName)); } - protected function doEnterNode(Node $node, Environment $env) + public function enterNode(Node $node, Environment $env): Node { return $node; } - protected function doLeaveNode(Node $node, Environment $env) + public function leaveNode(Node $node, Environment $env): ?Node { if ($node instanceof ModuleNode) { - $varName = $this->getVarName(); - $node->setNode('display_start', new Node([new EnterProfileNode($this->extensionName, Profile::TEMPLATE, $node->getTemplateName(), $varName), $node->getNode('display_start')])); - $node->setNode('display_end', new Node([new LeaveProfileNode($varName), $node->getNode('display_end')])); + $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')])); } elseif ($node instanceof BlockNode) { - $varName = $this->getVarName(); $node->setNode('body', new BodyNode([ - new EnterProfileNode($this->extensionName, Profile::BLOCK, $node->getAttribute('name'), $varName), + new EnterProfileNode($this->extensionName, Profile::BLOCK, $node->getAttribute('name'), $this->varName), $node->getNode('body'), - new LeaveProfileNode($varName), + new LeaveProfileNode($this->varName), ])); } elseif ($node instanceof MacroNode) { - $varName = $this->getVarName(); $node->setNode('body', new BodyNode([ - new EnterProfileNode($this->extensionName, Profile::MACRO, $node->getAttribute('name'), $varName), + new EnterProfileNode($this->extensionName, Profile::MACRO, $node->getAttribute('name'), $this->varName), $node->getNode('body'), - new LeaveProfileNode($varName), + new LeaveProfileNode($this->varName), ])); } return $node; } - private function getVarName() - { - return sprintf('__internal_%s', hash('sha256', $this->extensionName)); - } - - public function getPriority() + public function getPriority(): int { return 0; } } - -class_alias('Twig\Profiler\NodeVisitor\ProfilerNodeVisitor', 'Twig_Profiler_NodeVisitor_Profiler'); diff --git a/lib/twig/twig/src/Profiler/Profile.php b/lib/twig/twig/src/Profiler/Profile.php index d83da40af3..252ca9b0cf 100644 --- a/lib/twig/twig/src/Profiler/Profile.php +++ b/lib/twig/twig/src/Profiler/Profile.php @@ -13,15 +13,13 @@ namespace Twig\Profiler; /** * @author Fabien Potencier - * - * @final */ -class Profile implements \IteratorAggregate, \Serializable +final class Profile implements \IteratorAggregate, \Serializable { - const ROOT = 'ROOT'; - const BLOCK = 'block'; - const TEMPLATE = 'template'; - const MACRO = 'macro'; + public const ROOT = 'ROOT'; + public const BLOCK = 'block'; + public const TEMPLATE = 'template'; + public const MACRO = 'macro'; private $template; private $name; @@ -30,7 +28,7 @@ class Profile implements \IteratorAggregate, \Serializable private $ends = []; private $profiles = []; - public function __construct($template = 'main', $type = self::ROOT, $name = 'main') + public function __construct(string $template = 'main', string $type = self::ROOT, string $name = 'main') { $this->template = $template; $this->type = $type; @@ -38,57 +36,58 @@ class Profile implements \IteratorAggregate, \Serializable $this->enter(); } - public function getTemplate() + public function getTemplate(): string { return $this->template; } - public function getType() + public function getType(): string { return $this->type; } - public function getName() + public function getName(): string { return $this->name; } - public function isRoot() + public function isRoot(): bool { return self::ROOT === $this->type; } - public function isTemplate() + public function isTemplate(): bool { return self::TEMPLATE === $this->type; } - public function isBlock() + public function isBlock(): bool { return self::BLOCK === $this->type; } - public function isMacro() + public function isMacro(): bool { return self::MACRO === $this->type; } - public function getProfiles() + /** + * @return Profile[] + */ + public function getProfiles(): array { return $this->profiles; } - public function addProfile(self $profile) + public function addProfile(self $profile): void { $this->profiles[] = $profile; } /** * Returns the duration in microseconds. - * - * @return float */ - public function getDuration() + public function getDuration(): float { if ($this->isRoot() && $this->profiles) { // for the root node with children, duration is the sum of all child durations @@ -105,20 +104,16 @@ class Profile implements \IteratorAggregate, \Serializable /** * Returns the memory usage in bytes. - * - * @return int */ - public function getMemoryUsage() + public function getMemoryUsage(): int { return isset($this->ends['mu']) && isset($this->starts['mu']) ? $this->ends['mu'] - $this->starts['mu'] : 0; } /** * Returns the peak memory usage in bytes. - * - * @return int */ - public function getPeakMemoryUsage() + public function getPeakMemoryUsage(): int { return isset($this->ends['pmu']) && isset($this->starts['pmu']) ? $this->ends['pmu'] - $this->starts['pmu'] : 0; } @@ -126,7 +121,7 @@ class Profile implements \IteratorAggregate, \Serializable /** * Starts the profiling. */ - public function enter() + public function enter(): void { $this->starts = [ 'wt' => microtime(true), @@ -138,7 +133,7 @@ class Profile implements \IteratorAggregate, \Serializable /** * Stops the profiling. */ - public function leave() + public function leave(): void { $this->ends = [ 'wt' => microtime(true), @@ -147,23 +142,23 @@ class Profile implements \IteratorAggregate, \Serializable ]; } - public function reset() + public function reset(): void { $this->starts = $this->ends = $this->profiles = []; $this->enter(); } - public function getIterator() + public function getIterator(): \Traversable { return new \ArrayIterator($this->profiles); } - public function serialize() + public function serialize(): string { return serialize($this->__serialize()); } - public function unserialize($data) + public function unserialize($data): void { $this->__unserialize(unserialize($data)); } @@ -171,7 +166,7 @@ class Profile implements \IteratorAggregate, \Serializable /** * @internal */ - public function __serialize() + public function __serialize(): array { return [$this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles]; } @@ -179,10 +174,8 @@ class Profile implements \IteratorAggregate, \Serializable /** * @internal */ - public function __unserialize(array $data) + public function __unserialize(array $data): void { list($this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles) = $data; } } - -class_alias('Twig\Profiler\Profile', 'Twig_Profiler_Profile'); diff --git a/lib/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php b/lib/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php index 04a6602f22..b360d7beaf 100644 --- a/lib/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php +++ b/lib/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php @@ -30,12 +30,8 @@ class ContainerRuntimeLoader implements RuntimeLoaderInterface $this->container = $container; } - public function load($class) + public function load(string $class) { - if ($this->container->has($class)) { - return $this->container->get($class); - } + return $this->container->has($class) ? $this->container->get($class) : null; } } - -class_alias('Twig\RuntimeLoader\ContainerRuntimeLoader', 'Twig_ContainerRuntimeLoader'); diff --git a/lib/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php b/lib/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php index 43b5f24eba..1306483926 100644 --- a/lib/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php +++ b/lib/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php @@ -23,19 +23,19 @@ class FactoryRuntimeLoader implements RuntimeLoaderInterface /** * @param array $map An array where keys are class names and values factory callables */ - public function __construct($map = []) + public function __construct(array $map = []) { $this->map = $map; } - public function load($class) + public function load(string $class) { - if (isset($this->map[$class])) { - $runtimeFactory = $this->map[$class]; - - return $runtimeFactory(); + if (!isset($this->map[$class])) { + return null; } + + $runtimeFactory = $this->map[$class]; + + return $runtimeFactory(); } } - -class_alias('Twig\RuntimeLoader\FactoryRuntimeLoader', 'Twig_FactoryRuntimeLoader'); diff --git a/lib/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php b/lib/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php index 4eb5ad8599..9e5b2048e7 100644 --- a/lib/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php +++ b/lib/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php @@ -21,11 +21,7 @@ interface RuntimeLoaderInterface /** * Creates the runtime implementation of a Twig element (filter/function/test). * - * @param string $class A runtime class - * * @return object|null The runtime instance or null if the loader does not know how to create the runtime for this class */ - public function load($class); + public function load(string $class); } - -class_alias('Twig\RuntimeLoader\RuntimeLoaderInterface', 'Twig_RuntimeLoaderInterface'); diff --git a/lib/twig/twig/src/Sandbox/SecurityError.php b/lib/twig/twig/src/Sandbox/SecurityError.php index 5f96d46bd8..30a404f282 100644 --- a/lib/twig/twig/src/Sandbox/SecurityError.php +++ b/lib/twig/twig/src/Sandbox/SecurityError.php @@ -21,5 +21,3 @@ use Twig\Error\Error; class SecurityError extends Error { } - -class_alias('Twig\Sandbox\SecurityError', 'Twig_Sandbox_SecurityError'); diff --git a/lib/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php b/lib/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php index fa0fdee72f..02d306360b 100644 --- a/lib/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php +++ b/lib/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php @@ -16,20 +16,18 @@ namespace Twig\Sandbox; * * @author Martin Hasoň */ -class SecurityNotAllowedFilterError extends SecurityError +final class SecurityNotAllowedFilterError extends SecurityError { private $filterName; - public function __construct($message, $functionName, $lineno = -1, $filename = null, \Exception $previous = null) + public function __construct(string $message, string $functionName) { - parent::__construct($message, $lineno, $filename, $previous); + parent::__construct($message); $this->filterName = $functionName; } - public function getFilterName() + public function getFilterName(): string { return $this->filterName; } } - -class_alias('Twig\Sandbox\SecurityNotAllowedFilterError', 'Twig_Sandbox_SecurityNotAllowedFilterError'); diff --git a/lib/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php b/lib/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php index 8f23f93acd..4f76dc6ece 100644 --- a/lib/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php +++ b/lib/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php @@ -16,20 +16,18 @@ namespace Twig\Sandbox; * * @author Martin Hasoň */ -class SecurityNotAllowedFunctionError extends SecurityError +final class SecurityNotAllowedFunctionError extends SecurityError { private $functionName; - public function __construct($message, $functionName, $lineno = -1, $filename = null, \Exception $previous = null) + public function __construct(string $message, string $functionName) { - parent::__construct($message, $lineno, $filename, $previous); + parent::__construct($message); $this->functionName = $functionName; } - public function getFunctionName() + public function getFunctionName(): string { return $this->functionName; } } - -class_alias('Twig\Sandbox\SecurityNotAllowedFunctionError', 'Twig_Sandbox_SecurityNotAllowedFunctionError'); diff --git a/lib/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php b/lib/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php index 62e13f49bd..8df9d0baa9 100644 --- a/lib/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php +++ b/lib/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php @@ -16,19 +16,19 @@ namespace Twig\Sandbox; * * @author Kit Burton-Senior */ -class SecurityNotAllowedMethodError extends SecurityError +final class SecurityNotAllowedMethodError extends SecurityError { private $className; private $methodName; - public function __construct($message, $className, $methodName, $lineno = -1, $filename = null, \Exception $previous = null) + public function __construct(string $message, string $className, string $methodName) { - parent::__construct($message, $lineno, $filename, $previous); + parent::__construct($message); $this->className = $className; $this->methodName = $methodName; } - public function getClassName() + public function getClassName(): string { return $this->className; } @@ -38,5 +38,3 @@ class SecurityNotAllowedMethodError extends SecurityError return $this->methodName; } } - -class_alias('Twig\Sandbox\SecurityNotAllowedMethodError', 'Twig_Sandbox_SecurityNotAllowedMethodError'); diff --git a/lib/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php b/lib/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php index 3bf530574f..42ec4f3869 100644 --- a/lib/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php +++ b/lib/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php @@ -16,19 +16,19 @@ namespace Twig\Sandbox; * * @author Kit Burton-Senior */ -class SecurityNotAllowedPropertyError extends SecurityError +final class SecurityNotAllowedPropertyError extends SecurityError { private $className; private $propertyName; - public function __construct($message, $className, $propertyName, $lineno = -1, $filename = null, \Exception $previous = null) + public function __construct(string $message, string $className, string $propertyName) { - parent::__construct($message, $lineno, $filename, $previous); + parent::__construct($message); $this->className = $className; $this->propertyName = $propertyName; } - public function getClassName() + public function getClassName(): string { return $this->className; } @@ -38,5 +38,3 @@ class SecurityNotAllowedPropertyError extends SecurityError return $this->propertyName; } } - -class_alias('Twig\Sandbox\SecurityNotAllowedPropertyError', 'Twig_Sandbox_SecurityNotAllowedPropertyError'); diff --git a/lib/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php b/lib/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php index de283b40cd..4522150e1b 100644 --- a/lib/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php +++ b/lib/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php @@ -16,20 +16,18 @@ namespace Twig\Sandbox; * * @author Martin Hasoň */ -class SecurityNotAllowedTagError extends SecurityError +final class SecurityNotAllowedTagError extends SecurityError { private $tagName; - public function __construct($message, $tagName, $lineno = -1, $filename = null, \Exception $previous = null) + public function __construct(string $message, string $tagName) { - parent::__construct($message, $lineno, $filename, $previous); + parent::__construct($message); $this->tagName = $tagName; } - public function getTagName() + public function getTagName(): string { return $this->tagName; } } - -class_alias('Twig\Sandbox\SecurityNotAllowedTagError', 'Twig_Sandbox_SecurityNotAllowedTagError'); diff --git a/lib/twig/twig/src/Sandbox/SecurityPolicy.php b/lib/twig/twig/src/Sandbox/SecurityPolicy.php index 31b6c34833..2fc0d01318 100644 --- a/lib/twig/twig/src/Sandbox/SecurityPolicy.php +++ b/lib/twig/twig/src/Sandbox/SecurityPolicy.php @@ -12,21 +12,20 @@ namespace Twig\Sandbox; use Twig\Markup; +use Twig\Template; /** * Represents a security policy which need to be enforced when sandbox mode is enabled. * - * @final - * * @author Fabien Potencier */ -class SecurityPolicy implements SecurityPolicyInterface +final class SecurityPolicy implements SecurityPolicyInterface { - protected $allowedTags; - protected $allowedFilters; - protected $allowedMethods; - protected $allowedProperties; - protected $allowedFunctions; + private $allowedTags; + private $allowedFilters; + private $allowedMethods; + private $allowedProperties; + private $allowedFunctions; public function __construct(array $allowedTags = [], array $allowedFilters = [], array $allowedMethods = [], array $allowedProperties = [], array $allowedFunctions = []) { @@ -37,35 +36,35 @@ class SecurityPolicy implements SecurityPolicyInterface $this->allowedFunctions = $allowedFunctions; } - public function setAllowedTags(array $tags) + public function setAllowedTags(array $tags): void { $this->allowedTags = $tags; } - public function setAllowedFilters(array $filters) + public function setAllowedFilters(array $filters): void { $this->allowedFilters = $filters; } - public function setAllowedMethods(array $methods) + public function setAllowedMethods(array $methods): void { $this->allowedMethods = []; foreach ($methods as $class => $m) { - $this->allowedMethods[$class] = array_map('strtolower', \is_array($m) ? $m : [$m]); + $this->allowedMethods[$class] = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, \is_array($m) ? $m : [$m]); } } - public function setAllowedProperties(array $properties) + public function setAllowedProperties(array $properties): void { $this->allowedProperties = $properties; } - public function setAllowedFunctions(array $functions) + public function setAllowedFunctions(array $functions): void { $this->allowedFunctions = $functions; } - public function checkSecurity($tags, $filters, $functions) + public function checkSecurity($tags, $filters, $functions): void { foreach ($tags as $tag) { if (!\in_array($tag, $this->allowedTags)) { @@ -86,14 +85,14 @@ class SecurityPolicy implements SecurityPolicyInterface } } - public function checkMethodAllowed($obj, $method) + public function checkMethodAllowed($obj, $method): void { - if ($obj instanceof \Twig_TemplateInterface || $obj instanceof Markup) { + if ($obj instanceof Template || $obj instanceof Markup) { return; } $allowed = false; - $method = strtolower($method); + $method = strtr($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); foreach ($this->allowedMethods as $class => $methods) { if ($obj instanceof $class) { $allowed = \in_array($method, $methods); @@ -108,7 +107,7 @@ class SecurityPolicy implements SecurityPolicyInterface } } - public function checkPropertyAllowed($obj, $property) + public function checkPropertyAllowed($obj, $property): void { $allowed = false; foreach ($this->allowedProperties as $class => $properties) { @@ -125,5 +124,3 @@ class SecurityPolicy implements SecurityPolicyInterface } } } - -class_alias('Twig\Sandbox\SecurityPolicy', 'Twig_Sandbox_SecurityPolicy'); diff --git a/lib/twig/twig/src/Sandbox/SecurityPolicyInterface.php b/lib/twig/twig/src/Sandbox/SecurityPolicyInterface.php index a31863f6c2..36471c54c2 100644 --- a/lib/twig/twig/src/Sandbox/SecurityPolicyInterface.php +++ b/lib/twig/twig/src/Sandbox/SecurityPolicyInterface.php @@ -18,11 +18,28 @@ namespace Twig\Sandbox; */ interface SecurityPolicyInterface { - public function checkSecurity($tags, $filters, $functions); + /** + * @param string[] $tags + * @param string[] $filters + * @param string[] $functions + * + * @throws SecurityError + */ + public function checkSecurity($tags, $filters, $functions): void; - public function checkMethodAllowed($obj, $method); + /** + * @param object $obj + * @param string $method + * + * @throws SecurityNotAllowedMethodError + */ + public function checkMethodAllowed($obj, $method): void; - public function checkPropertyAllowed($obj, $method); + /** + * @param object $obj + * @param string $property + * + * @throws SecurityNotAllowedPropertyError + */ + public function checkPropertyAllowed($obj, $property): void; } - -class_alias('Twig\Sandbox\SecurityPolicyInterface', 'Twig_Sandbox_SecurityPolicyInterface'); diff --git a/lib/twig/twig/src/Source.php b/lib/twig/twig/src/Source.php index 32a82163f4..3cb02403c1 100644 --- a/lib/twig/twig/src/Source.php +++ b/lib/twig/twig/src/Source.php @@ -14,11 +14,9 @@ namespace Twig; /** * Holds information about a non-compiled Twig template. * - * @final - * * @author Fabien Potencier */ -class Source +final class Source { private $code; private $name; @@ -29,27 +27,25 @@ class Source * @param string $name The template logical name * @param string $path The filesystem path of the template if any */ - public function __construct($code, $name, $path = '') + public function __construct(string $code, string $name, string $path = '') { $this->code = $code; $this->name = $name; $this->path = $path; } - public function getCode() + public function getCode(): string { return $this->code; } - public function getName() + public function getName(): string { return $this->name; } - public function getPath() + public function getPath(): string { return $this->path; } } - -class_alias('Twig\Source', 'Twig_Source'); diff --git a/lib/twig/twig/src/Template.php b/lib/twig/twig/src/Template.php index 3f7447c126..e04bd04a63 100644 --- a/lib/twig/twig/src/Template.php +++ b/lib/twig/twig/src/Template.php @@ -27,31 +27,24 @@ use Twig\Error\RuntimeError; * * @internal */ -abstract class Template implements \Twig_TemplateInterface +abstract class Template { - /** - * @internal - */ - protected static $cache = []; + public const ANY_CALL = 'any'; + public const ARRAY_CALL = 'array'; + public const METHOD_CALL = 'method'; protected $parent; protected $parents = []; protected $env; protected $blocks = []; protected $traits = []; + protected $extensions = []; protected $sandbox; public function __construct(Environment $env) { $this->env = $env; - } - - /** - * @internal this method will be removed in 2.0 and is only used internally to provide an upgrade path from 1.x to 2.0 - */ - public function __toString() - { - return $this->getTemplateName(); + $this->extensions = $env->getExtensions(); } /** @@ -66,44 +59,14 @@ abstract class Template implements \Twig_TemplateInterface * * @return array Debug information */ - public function getDebugInfo() - { - return []; - } - - /** - * Returns the template source code. - * - * @return string The template source code - * - * @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead - */ - public function getSource() - { - @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); - - return ''; - } + abstract public function getDebugInfo(); /** * Returns information about the original template source code. * * @return Source */ - public function getSourceContext() - { - return new Source('', $this->getTemplateName()); - } - - /** - * @deprecated since 1.20 (to be removed in 2.0) - */ - public function getEnvironment() - { - @trigger_error('The '.__METHOD__.' method is deprecated since version 1.20 and will be removed in 2.0.', E_USER_DEPRECATED); - - return $this->env; - } + abstract public function getSourceContext(); /** * Returns the parent template. @@ -111,11 +74,7 @@ abstract class Template implements \Twig_TemplateInterface * This method is for internal use only and should never be called * directly. * - * @param array $context - * - * @return \Twig_TemplateInterface|TemplateWrapper|false The parent template or false if there is no parent - * - * @internal + * @return Template|TemplateWrapper|false The parent template or false if there is no parent */ public function getParent(array $context) { @@ -169,8 +128,6 @@ abstract class Template implements \Twig_TemplateInterface */ public function displayParentBlock($name, array $context, array $blocks = []) { - $name = (string) $name; - if (isset($this->traits[$name])) { $this->traits[$name][0]->displayBlock($name, $context, $blocks, false); } elseif (false !== $parent = $this->getParent($context)) { @@ -191,10 +148,8 @@ abstract class Template implements \Twig_TemplateInterface * @param array $blocks The current set of blocks * @param bool $useBlocks Whether to use the current set of blocks */ - public function displayBlock($name, array $context, array $blocks = [], $useBlocks = true) + public function displayBlock($name, array $context, array $blocks = [], $useBlocks = true, self $templateContext = null) { - $name = (string) $name; - if ($useBlocks && isset($blocks[$name])) { $template = $blocks[$name][0]; $block = $blocks[$name][1]; @@ -233,9 +188,11 @@ abstract class Template implements \Twig_TemplateInterface throw $e; } } elseif (false !== $parent = $this->getParent($context)) { - $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks), false); + $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks), false, $templateContext ?? $this); + } elseif (isset($blocks[$name])) { + throw new RuntimeError(sprintf('Block "%s" should not call parent() in "%s" as the block does not exist in the parent template "%s".', $name, $blocks[$name][0]->getTemplateName(), $this->getTemplateName()), -1, $blocks[$name][0]->getSourceContext()); } else { - @trigger_error(sprintf('Silent display of undefined block "%s" in template "%s" is deprecated since version 1.29 and will throw an exception in 2.0. Use the "block(\'%s\') is defined" expression to test for block existence.', $name, $this->getTemplateName(), $name), E_USER_DEPRECATED); + throw new RuntimeError(sprintf('Block "%s" on template "%s" does not exist.', $name, $this->getTemplateName()), -1, ($templateContext ?? $this)->getSourceContext()); } } @@ -300,14 +257,8 @@ abstract class Template implements \Twig_TemplateInterface * * @return bool true if the block exists, false otherwise */ - public function hasBlock($name, array $context = null, array $blocks = []) + public function hasBlock($name, array $context, array $blocks = []) { - if (null === $context) { - @trigger_error('The '.__METHOD__.' method is internal and should never be called; calling it directly is deprecated since version 1.28 and won\'t be possible anymore in 2.0.', E_USER_DEPRECATED); - - return isset($this->blocks[(string) $name]); - } - if (isset($blocks[$name])) { return $blocks[$name][0] instanceof self; } @@ -334,14 +285,8 @@ abstract class Template implements \Twig_TemplateInterface * * @return array An array of block names */ - public function getBlockNames(array $context = null, array $blocks = []) + public function getBlockNames(array $context, array $blocks = []) { - if (null === $context) { - @trigger_error('The '.__METHOD__.' method is internal and should never be called; calling it directly is deprecated since version 1.28 and won\'t be possible anymore in 2.0.', E_USER_DEPRECATED); - - return array_keys($this->blocks); - } - $names = array_merge(array_keys($blocks), array_keys($this->blocks)); if (false !== $parent = $this->getParent($context)) { @@ -366,15 +311,15 @@ abstract class Template implements \Twig_TemplateInterface } if ($template === $this->getTemplateName()) { - $class = \get_class($this); + $class = static::class; if (false !== $pos = strrpos($class, '___', -1)) { $class = substr($class, 0, $pos); } - - return $this->env->loadClass($class, $template, $index); + } else { + $class = $this->env->getTemplateClass($template); } - return $this->env->loadTemplate($template, $index); + return $this->env->loadTemplate($class, $template, $index); } catch (Error $e) { if (!$e->getSourceContext()) { $e->setSourceContext($templateName ? new Source('', $templateName) : $this->getSourceContext()); @@ -399,7 +344,7 @@ abstract class Template implements \Twig_TemplateInterface * * @return Template */ - protected function unwrap() + public function unwrap() { return $this; } @@ -432,12 +377,6 @@ abstract class Template implements \Twig_TemplateInterface } try { $this->display($context); - } catch (\Exception $e) { - while (ob_get_level() > $level) { - ob_end_clean(); - } - - throw $e; } catch (\Throwable $e) { while (ob_get_level() > $level) { ob_end_clean(); @@ -480,254 +419,4 @@ abstract class Template implements \Twig_TemplateInterface * @param array $blocks An array of blocks to pass to the template */ abstract protected function doDisplay(array $context, array $blocks = []); - - /** - * Returns a variable from the context. - * - * This method is for internal use only and should never be called - * directly. - * - * This method should not be overridden in a sub-class as this is an - * implementation detail that has been introduced to optimize variable - * access for versions of PHP before 5.4. This is not a way to override - * the way to get a variable value. - * - * @param array $context The context - * @param string $item The variable to return from the context - * @param bool $ignoreStrictCheck Whether to ignore the strict variable check or not - * - * @return mixed The content of the context variable - * - * @throws RuntimeError if the variable does not exist and Twig is running in strict mode - * - * @internal - */ - final protected function getContext($context, $item, $ignoreStrictCheck = false) - { - if (!\array_key_exists($item, $context)) { - if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { - return; - } - - throw new RuntimeError(sprintf('Variable "%s" does not exist.', $item), -1, $this->getSourceContext()); - } - - return $context[$item]; - } - - /** - * Returns the attribute value for a given array/object. - * - * @param mixed $object The object or array from where to get the item - * @param mixed $item The item to get from the array or object - * @param array $arguments An array of arguments to pass if the item is an object method - * @param string $type The type of attribute (@see \Twig\Template constants) - * @param bool $isDefinedTest Whether this is only a defined check - * @param bool $ignoreStrictCheck Whether to ignore the strict attribute check or not - * - * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true - * - * @throws RuntimeError if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false - * - * @internal - */ - protected function getAttribute($object, $item, array $arguments = [], $type = self::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false) - { - // array - if (self::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 ($isDefinedTest) { - return true; - } - - return $object[$arrayItem]; - } - - if (self::ARRAY_CALL === $type || !\is_object($object)) { - if ($isDefinedTest) { - return false; - } - - if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { - return; - } - - if ($object instanceof \ArrayAccess) { - $message = sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.', $arrayItem, \get_class($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)); - } elseif (\is_array($object)) { - if (empty($object)) { - $message = sprintf('Key "%s" does not exist as the array is empty.', $arrayItem); - } else { - $message = sprintf('Key "%s" for array with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object))); - } - } elseif (self::ARRAY_CALL === $type) { - 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); - } - } 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); - } - - throw new RuntimeError($message, -1, $this->getSourceContext()); - } - } - - if (!\is_object($object)) { - if ($isDefinedTest) { - return false; - } - - if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { - return; - } - - if (null === $object) { - $message = sprintf('Impossible to invoke a method ("%s") on a null variable.', $item); - } elseif (\is_array($object)) { - $message = sprintf('Impossible to invoke a method ("%s") on an array.', $item); - } else { - $message = sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); - } - - throw new RuntimeError($message, -1, $this->getSourceContext()); - } - - // object property - if (self::METHOD_CALL !== $type && !$object instanceof self) { // \Twig\Template does not have public properties, and we don't want to allow access to internal ones - if (isset($object->$item) || \array_key_exists((string) $item, (array) $object)) { - if ($isDefinedTest) { - return true; - } - - if ($this->env->hasExtension('\Twig\Extension\SandboxExtension')) { - $this->env->getExtension('\Twig\Extension\SandboxExtension')->checkPropertyAllowed($object, $item); - } - - return $object->$item; - } - } - - $class = \get_class($object); - - // object method - if (!isset(self::$cache[$class])) { - // get_class_methods returns all methods accessible in the scope, but we only want public ones to be accessible in templates - if ($object instanceof self) { - $ref = new \ReflectionClass($class); - $methods = []; - - foreach ($ref->getMethods(\ReflectionMethod::IS_PUBLIC) as $refMethod) { - // Accessing the environment from templates is forbidden to prevent untrusted changes to the environment - if ('getenvironment' !== strtolower($refMethod->name)) { - $methods[] = $refMethod->name; - } - } - } else { - $methods = get_class_methods($object); - } - // sort values to have consistent behavior, so that "get" methods win precedence over "is" methods - sort($methods); - - $cache = []; - - foreach ($methods as $method) { - $cache[$method] = $method; - $cache[$lcName = strtolower($method)] = $method; - - if ('g' === $lcName[0] && 0 === strpos($lcName, 'get')) { - $name = substr($method, 3); - $lcName = substr($lcName, 3); - } elseif ('i' === $lcName[0] && 0 === strpos($lcName, 'is')) { - $name = substr($method, 2); - $lcName = substr($lcName, 2); - } else { - continue; - } - - // skip get() and is() methods (in which case, $name is empty) - if ($name) { - if (!isset($cache[$name])) { - $cache[$name] = $method; - } - if (!isset($cache[$lcName])) { - $cache[$lcName] = $method; - } - } - } - self::$cache[$class] = $cache; - } - - $call = false; - if (isset(self::$cache[$class][$item])) { - $method = self::$cache[$class][$item]; - } elseif (isset(self::$cache[$class][$lcItem = strtolower($item)])) { - $method = self::$cache[$class][$lcItem]; - } elseif (isset(self::$cache[$class]['__call'])) { - $method = $item; - $call = true; - } else { - if ($isDefinedTest) { - return false; - } - - if ($ignoreStrictCheck || !$this->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()" or "__call()" exist and have public access in class "%2$s".', $item, $class), -1, $this->getSourceContext()); - } - - if ($isDefinedTest) { - return true; - } - - if ($this->env->hasExtension('\Twig\Extension\SandboxExtension')) { - $this->env->getExtension('\Twig\Extension\SandboxExtension')->checkMethodAllowed($object, $method); - } - - // 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 { - if (!$arguments) { - $ret = $object->$method(); - } else { - $ret = \call_user_func_array([$object, $method], $arguments); - } - } catch (\BadMethodCallException $e) { - if ($call && ($ignoreStrictCheck || !$this->env->isStrictVariables())) { - return; - } - throw $e; - } - - // @deprecated in 1.28 - if ($object instanceof \Twig_TemplateInterface) { - $self = $object->getTemplateName() === $this->getTemplateName(); - $message = sprintf('Calling "%s" on template "%s" from template "%s" is deprecated since version 1.28 and won\'t be supported anymore in 2.0.', $item, $object->getTemplateName(), $this->getTemplateName()); - if ('renderBlock' === $method || 'displayBlock' === $method) { - $message .= sprintf(' Use block("%s"%s) instead).', $arguments[0], $self ? '' : ', template'); - } elseif ('hasBlock' === $method) { - $message .= sprintf(' Use "block("%s"%s) is defined" instead).', $arguments[0], $self ? '' : ', template'); - } elseif ('render' === $method || 'display' === $method) { - $message .= sprintf(' Use include("%s") instead).', $object->getTemplateName()); - } - @trigger_error($message, E_USER_DEPRECATED); - - return '' === $ret ? '' : new Markup($ret, $this->env->getCharset()); - } - - return $ret; - } } - -class_alias('Twig\Template', 'Twig_Template'); diff --git a/lib/twig/twig/src/TemplateWrapper.php b/lib/twig/twig/src/TemplateWrapper.php index e2654094e4..c9c6b07c66 100644 --- a/lib/twig/twig/src/TemplateWrapper.php +++ b/lib/twig/twig/src/TemplateWrapper.php @@ -33,66 +33,34 @@ final class TemplateWrapper $this->template = $template; } - /** - * Renders the template. - * - * @param array $context An array of parameters to pass to the template - * - * @return string The rendered template - */ - public function render($context = []) + public function render(array $context = []): string { // using func_get_args() allows to not expose the blocks argument // as it should only be used by internal code - return $this->template->render($context, \func_num_args() > 1 ? func_get_arg(1) : []); + return $this->template->render($context, \func_get_args()[1] ?? []); } - /** - * Displays the template. - * - * @param array $context An array of parameters to pass to the template - */ - public function display($context = []) + public function display(array $context = []) { // using func_get_args() allows to not expose the blocks argument // as it should only be used by internal code - $this->template->display($context, \func_num_args() > 1 ? func_get_arg(1) : []); + $this->template->display($context, \func_get_args()[1] ?? []); } - /** - * Checks if a block is defined. - * - * @param string $name The block name - * @param array $context An array of parameters to pass to the template - * - * @return bool - */ - public function hasBlock($name, $context = []) + public function hasBlock(string $name, array $context = []): bool { return $this->template->hasBlock($name, $context); } /** - * Returns defined block names in the template. - * - * @param array $context An array of parameters to pass to the template - * * @return string[] An array of defined template block names */ - public function getBlockNames($context = []) + public function getBlockNames(array $context = []): array { return $this->template->getBlockNames($context); } - /** - * Renders a template block. - * - * @param string $name The block name to render - * @param array $context An array of parameters to pass to the template - * - * @return string The rendered block - */ - public function renderBlock($name, $context = []) + public function renderBlock(string $name, array $context = []): string { $context = $this->env->mergeGlobals($context); $level = ob_get_level(); @@ -103,12 +71,6 @@ final class TemplateWrapper } try { $this->template->displayBlock($name, $context); - } catch (\Exception $e) { - while (ob_get_level() > $level) { - ob_end_clean(); - } - - throw $e; } catch (\Throwable $e) { while (ob_get_level() > $level) { ob_end_clean(); @@ -120,29 +82,17 @@ final class TemplateWrapper return ob_get_clean(); } - /** - * Displays a template block. - * - * @param string $name The block name to render - * @param array $context An array of parameters to pass to the template - */ - public function displayBlock($name, $context = []) + public function displayBlock(string $name, array $context = []) { $this->template->displayBlock($name, $this->env->mergeGlobals($context)); } - /** - * @return Source - */ - public function getSourceContext() + public function getSourceContext(): Source { return $this->template->getSourceContext(); } - /** - * @return string - */ - public function getTemplateName() + public function getTemplateName(): string { return $this->template->getTemplateName(); } @@ -157,5 +107,3 @@ final class TemplateWrapper return $this->template; } } - -class_alias('Twig\TemplateWrapper', 'Twig_TemplateWrapper'); diff --git a/lib/twig/twig/src/Token.php b/lib/twig/twig/src/Token.php index a0bb11af15..53a6cafc35 100644 --- a/lib/twig/twig/src/Token.php +++ b/lib/twig/twig/src/Token.php @@ -13,39 +13,30 @@ namespace Twig; /** - * Represents a Token. - * * @author Fabien Potencier - * - * @final */ -class Token +final class Token { - protected $value; - protected $type; - protected $lineno; + private $value; + private $type; + private $lineno; - const EOF_TYPE = -1; - const TEXT_TYPE = 0; - const BLOCK_START_TYPE = 1; - const VAR_START_TYPE = 2; - const BLOCK_END_TYPE = 3; - const VAR_END_TYPE = 4; - const NAME_TYPE = 5; - const NUMBER_TYPE = 6; - const STRING_TYPE = 7; - const OPERATOR_TYPE = 8; - const PUNCTUATION_TYPE = 9; - const INTERPOLATION_START_TYPE = 10; - const INTERPOLATION_END_TYPE = 11; - const ARROW_TYPE = 12; + public const EOF_TYPE = -1; + public const TEXT_TYPE = 0; + public const BLOCK_START_TYPE = 1; + public const VAR_START_TYPE = 2; + public const BLOCK_END_TYPE = 3; + public const VAR_END_TYPE = 4; + public const NAME_TYPE = 5; + public const NUMBER_TYPE = 6; + public const STRING_TYPE = 7; + public const OPERATOR_TYPE = 8; + public const PUNCTUATION_TYPE = 9; + public const INTERPOLATION_START_TYPE = 10; + public const INTERPOLATION_END_TYPE = 11; + public const ARROW_TYPE = 12; - /** - * @param int $type The type of the token - * @param string $value The token value - * @param int $lineno The line position in the source - */ - public function __construct($type, $value, $lineno) + public function __construct(int $type, $value, int $lineno) { $this->type = $type; $this->value = $value; @@ -67,10 +58,8 @@ class Token * * @param array|string|int $type The type to test * @param array|string|null $values The token value - * - * @return bool */ - public function test($type, $values = null) + public function test($type, $values = null): bool { if (null === $values && !\is_int($type)) { $values = $type; @@ -84,39 +73,22 @@ class Token ); } - /** - * @return int - */ - public function getLine() + public function getLine(): int { return $this->lineno; } - /** - * @return int - */ - public function getType() + public function getType(): int { return $this->type; } - /** - * @return string - */ public function getValue() { return $this->value; } - /** - * Returns the constant representation (internal) of a given type. - * - * @param int $type The type as an integer - * @param bool $short Whether to return a short representation or not - * - * @return string The string representation - */ - public static function typeToString($type, $short = false) + public static function typeToString(int $type, bool $short = false): string { switch ($type) { case self::EOF_TYPE: @@ -168,14 +140,7 @@ class Token return $short ? $name : 'Twig\Token::'.$name; } - /** - * Returns the English representation of a given type. - * - * @param int $type The type as an integer - * - * @return string The string representation - */ - public static function typeToEnglish($type) + public static function typeToEnglish(int $type): string { switch ($type) { case self::EOF_TYPE: @@ -211,5 +176,3 @@ class Token } } } - -class_alias('Twig\Token', 'Twig_Token'); diff --git a/lib/twig/twig/src/TokenParser/AbstractTokenParser.php b/lib/twig/twig/src/TokenParser/AbstractTokenParser.php index 2c2f90b7f1..720ea67628 100644 --- a/lib/twig/twig/src/TokenParser/AbstractTokenParser.php +++ b/lib/twig/twig/src/TokenParser/AbstractTokenParser.php @@ -25,10 +25,8 @@ abstract class AbstractTokenParser implements TokenParserInterface */ protected $parser; - public function setParser(Parser $parser) + public function setParser(Parser $parser): void { $this->parser = $parser; } } - -class_alias('Twig\TokenParser\AbstractTokenParser', 'Twig_TokenParser'); diff --git a/lib/twig/twig/src/TokenParser/ApplyTokenParser.php b/lib/twig/twig/src/TokenParser/ApplyTokenParser.php index 879879a2b0..4dbf30406b 100644 --- a/lib/twig/twig/src/TokenParser/ApplyTokenParser.php +++ b/lib/twig/twig/src/TokenParser/ApplyTokenParser.php @@ -22,11 +22,13 @@ use Twig\Token; * * {% apply upper %} * This text becomes uppercase - * {% endapplys %} + * {% endapply %} + * + * @internal */ final class ApplyTokenParser extends AbstractTokenParser { - public function parse(Token $token) + public function parse(Token $token): Node { $lineno = $token->getLine(); $name = $this->parser->getVarName(); @@ -46,12 +48,12 @@ final class ApplyTokenParser extends AbstractTokenParser ]); } - public function decideApplyEnd(Token $token) + public function decideApplyEnd(Token $token): bool { return $token->test('endapply'); } - public function getTag() + public function getTag(): string { return 'apply'; } diff --git a/lib/twig/twig/src/TokenParser/AutoEscapeTokenParser.php b/lib/twig/twig/src/TokenParser/AutoEscapeTokenParser.php index 2cd0cc69d7..b674bea4ab 100644 --- a/lib/twig/twig/src/TokenParser/AutoEscapeTokenParser.php +++ b/lib/twig/twig/src/TokenParser/AutoEscapeTokenParser.php @@ -14,75 +14,45 @@ namespace Twig\TokenParser; use Twig\Error\SyntaxError; use Twig\Node\AutoEscapeNode; use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Node; use Twig\Token; /** * Marks a section of a template to be escaped or not. * - * {% autoescape true %} - * Everything will be automatically escaped in this block - * {% endautoescape %} - * - * {% autoescape false %} - * Everything will be outputed as is in this block - * {% endautoescape %} - * - * {% autoescape true js %} - * Everything will be automatically escaped in this block - * using the js escaping strategy - * {% endautoescape %} - * - * @final + * @internal */ -class AutoEscapeTokenParser extends AbstractTokenParser +final class AutoEscapeTokenParser extends AbstractTokenParser { - public function parse(Token $token) + public function parse(Token $token): Node { $lineno = $token->getLine(); $stream = $this->parser->getStream(); - if ($stream->test(Token::BLOCK_END_TYPE)) { + if ($stream->test(/* Token::BLOCK_END_TYPE */ 3)) { $value = 'html'; } else { $expr = $this->parser->getExpressionParser()->parseExpression(); if (!$expr instanceof ConstantExpression) { - throw new SyntaxError('An escaping strategy must be a string or a bool.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + throw new SyntaxError('An escaping strategy must be a string or false.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); } $value = $expr->getAttribute('value'); - - $compat = true === $value || false === $value; - - if (true === $value) { - $value = 'html'; - } - - if ($compat && $stream->test(Token::NAME_TYPE)) { - @trigger_error('Using the autoescape tag with "true" or "false" before the strategy name is deprecated since version 1.21.', E_USER_DEPRECATED); - - if (false === $value) { - throw new SyntaxError('Unexpected escaping strategy as you set autoescaping to false.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); - } - - $value = $stream->next()->getValue(); - } } - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); return new AutoEscapeNode($value, $body, $lineno, $this->getTag()); } - public function decideBlockEnd(Token $token) + public function decideBlockEnd(Token $token): bool { return $token->test('endautoescape'); } - public function getTag() + public function getTag(): string { return 'autoescape'; } } - -class_alias('Twig\TokenParser\AutoEscapeTokenParser', 'Twig_TokenParser_AutoEscape'); diff --git a/lib/twig/twig/src/TokenParser/BlockTokenParser.php b/lib/twig/twig/src/TokenParser/BlockTokenParser.php index caf11f0b72..5878131bec 100644 --- a/lib/twig/twig/src/TokenParser/BlockTokenParser.php +++ b/lib/twig/twig/src/TokenParser/BlockTokenParser.php @@ -27,15 +27,15 @@ use Twig\Token; * {% block title %}{% endblock %} - My Webpage * {% endblock %} * - * @final + * @internal */ -class BlockTokenParser extends AbstractTokenParser +final class BlockTokenParser extends AbstractTokenParser { - public function parse(Token $token) + public function parse(Token $token): Node { $lineno = $token->getLine(); $stream = $this->parser->getStream(); - $name = $stream->expect(Token::NAME_TYPE)->getValue(); + $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); if ($this->parser->hasBlock($name)) { throw new SyntaxError(sprintf("The block '%s' has already been defined line %d.", $name, $this->parser->getBlock($name)->getTemplateLine()), $stream->getCurrent()->getLine(), $stream->getSourceContext()); } @@ -43,9 +43,9 @@ class BlockTokenParser extends AbstractTokenParser $this->parser->pushLocalScope(); $this->parser->pushBlockStack($name); - if ($stream->nextIf(Token::BLOCK_END_TYPE)) { + if ($stream->nextIf(/* Token::BLOCK_END_TYPE */ 3)) { $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); - if ($token = $stream->nextIf(Token::NAME_TYPE)) { + if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) { $value = $token->getValue(); if ($value != $name) { @@ -57,7 +57,7 @@ class BlockTokenParser extends AbstractTokenParser new PrintNode($this->parser->getExpressionParser()->parseExpression(), $lineno), ]); } - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); $block->setNode('body', $body); $this->parser->popBlockStack(); @@ -66,15 +66,13 @@ class BlockTokenParser extends AbstractTokenParser return new BlockReferenceNode($name, $lineno, $this->getTag()); } - public function decideBlockEnd(Token $token) + public function decideBlockEnd(Token $token): bool { return $token->test('endblock'); } - public function getTag() + public function getTag(): string { return 'block'; } } - -class_alias('Twig\TokenParser\BlockTokenParser', 'Twig_TokenParser_Block'); diff --git a/lib/twig/twig/src/TokenParser/DeprecatedTokenParser.php b/lib/twig/twig/src/TokenParser/DeprecatedTokenParser.php index 6575cff167..31416c79c1 100644 --- a/lib/twig/twig/src/TokenParser/DeprecatedTokenParser.php +++ b/lib/twig/twig/src/TokenParser/DeprecatedTokenParser.php @@ -12,6 +12,7 @@ namespace Twig\TokenParser; use Twig\Node\DeprecatedNode; +use Twig\Node\Node; use Twig\Token; /** @@ -22,11 +23,11 @@ use Twig\Token; * * @author Yonel Ceruto * - * @final + * @internal */ -class DeprecatedTokenParser extends AbstractTokenParser +final class DeprecatedTokenParser extends AbstractTokenParser { - public function parse(Token $token) + public function parse(Token $token): Node { $expr = $this->parser->getExpressionParser()->parseExpression(); @@ -35,10 +36,8 @@ class DeprecatedTokenParser extends AbstractTokenParser return new DeprecatedNode($expr, $token->getLine(), $this->getTag()); } - public function getTag() + public function getTag(): string { return 'deprecated'; } } - -class_alias('Twig\TokenParser\DeprecatedTokenParser', 'Twig_TokenParser_Deprecated'); diff --git a/lib/twig/twig/src/TokenParser/DoTokenParser.php b/lib/twig/twig/src/TokenParser/DoTokenParser.php index e1eae10f23..32c8f12ff8 100644 --- a/lib/twig/twig/src/TokenParser/DoTokenParser.php +++ b/lib/twig/twig/src/TokenParser/DoTokenParser.php @@ -12,28 +12,27 @@ namespace Twig\TokenParser; use Twig\Node\DoNode; +use Twig\Node\Node; use Twig\Token; /** * Evaluates an expression, discarding the returned value. * - * @final + * @internal */ -class DoTokenParser extends AbstractTokenParser +final class DoTokenParser extends AbstractTokenParser { - public function parse(Token $token) + public function parse(Token $token): Node { $expr = $this->parser->getExpressionParser()->parseExpression(); - $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + $this->parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3); return new DoNode($expr, $token->getLine(), $this->getTag()); } - public function getTag() + public function getTag(): string { return 'do'; } } - -class_alias('Twig\TokenParser\DoTokenParser', 'Twig_TokenParser_Do'); diff --git a/lib/twig/twig/src/TokenParser/EmbedTokenParser.php b/lib/twig/twig/src/TokenParser/EmbedTokenParser.php index 973ff2e45b..64b4f296f2 100644 --- a/lib/twig/twig/src/TokenParser/EmbedTokenParser.php +++ b/lib/twig/twig/src/TokenParser/EmbedTokenParser.php @@ -14,16 +14,17 @@ namespace Twig\TokenParser; use Twig\Node\EmbedNode; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\NameExpression; +use Twig\Node\Node; use Twig\Token; /** * Embeds a template. * - * @final + * @internal */ -class EmbedTokenParser extends IncludeTokenParser +final class EmbedTokenParser extends IncludeTokenParser { - public function parse(Token $token) + public function parse(Token $token): Node { $stream = $this->parser->getStream(); @@ -31,19 +32,19 @@ class EmbedTokenParser extends IncludeTokenParser list($variables, $only, $ignoreMissing) = $this->parseArguments(); - $parentToken = $fakeParentToken = new Token(Token::STRING_TYPE, '__parent__', $token->getLine()); + $parentToken = $fakeParentToken = new Token(/* Token::STRING_TYPE */ 7, '__parent__', $token->getLine()); if ($parent instanceof ConstantExpression) { - $parentToken = new Token(Token::STRING_TYPE, $parent->getAttribute('value'), $token->getLine()); + $parentToken = new Token(/* Token::STRING_TYPE */ 7, $parent->getAttribute('value'), $token->getLine()); } elseif ($parent instanceof NameExpression) { - $parentToken = new Token(Token::NAME_TYPE, $parent->getAttribute('name'), $token->getLine()); + $parentToken = new Token(/* Token::NAME_TYPE */ 5, $parent->getAttribute('name'), $token->getLine()); } // inject a fake parent to make the parent() function work $stream->injectTokens([ - new Token(Token::BLOCK_START_TYPE, '', $token->getLine()), - new Token(Token::NAME_TYPE, 'extends', $token->getLine()), + new Token(/* Token::BLOCK_START_TYPE */ 1, '', $token->getLine()), + new Token(/* Token::NAME_TYPE */ 5, 'extends', $token->getLine()), $parentToken, - new Token(Token::BLOCK_END_TYPE, '', $token->getLine()), + new Token(/* Token::BLOCK_END_TYPE */ 3, '', $token->getLine()), ]); $module = $this->parser->parse($stream, [$this, 'decideBlockEnd'], true); @@ -55,20 +56,18 @@ class EmbedTokenParser extends IncludeTokenParser $this->parser->embedTemplate($module); - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); return new EmbedNode($module->getTemplateName(), $module->getAttribute('index'), $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); } - public function decideBlockEnd(Token $token) + public function decideBlockEnd(Token $token): bool { return $token->test('endembed'); } - public function getTag() + public function getTag(): string { return 'embed'; } } - -class_alias('Twig\TokenParser\EmbedTokenParser', 'Twig_TokenParser_Embed'); diff --git a/lib/twig/twig/src/TokenParser/ExtendsTokenParser.php b/lib/twig/twig/src/TokenParser/ExtendsTokenParser.php index e66789a7bd..0ca46dd29f 100644 --- a/lib/twig/twig/src/TokenParser/ExtendsTokenParser.php +++ b/lib/twig/twig/src/TokenParser/ExtendsTokenParser.php @@ -21,11 +21,11 @@ use Twig\Token; * * {% extends "base.html" %} * - * @final + * @internal */ -class ExtendsTokenParser extends AbstractTokenParser +final class ExtendsTokenParser extends AbstractTokenParser { - public function parse(Token $token) + public function parse(Token $token): Node { $stream = $this->parser->getStream(); @@ -40,15 +40,13 @@ class ExtendsTokenParser extends AbstractTokenParser } $this->parser->setParent($this->parser->getExpressionParser()->parseExpression()); - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); return new Node(); } - public function getTag() + public function getTag(): string { return 'extends'; } } - -class_alias('Twig\TokenParser\ExtendsTokenParser', 'Twig_TokenParser_Extends'); diff --git a/lib/twig/twig/src/TokenParser/FilterTokenParser.php b/lib/twig/twig/src/TokenParser/FilterTokenParser.php deleted file mode 100644 index dc07dcfa41..0000000000 --- a/lib/twig/twig/src/TokenParser/FilterTokenParser.php +++ /dev/null @@ -1,59 +0,0 @@ -parser->getVarName(); - $ref = new BlockReferenceExpression(new ConstantExpression($name, $token->getLine()), null, $token->getLine(), $this->getTag()); - - $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref, $this->getTag()); - $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); - - $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); - $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); - - $block = new BlockNode($name, $body, $token->getLine()); - $this->parser->setBlock($name, $block); - - return new PrintNode($filter, $token->getLine(), $this->getTag()); - } - - public function decideBlockEnd(Token $token) - { - return $token->test('endfilter'); - } - - public function getTag() - { - return 'filter'; - } -} - -class_alias('Twig\TokenParser\FilterTokenParser', 'Twig_TokenParser_Filter'); diff --git a/lib/twig/twig/src/TokenParser/FlushTokenParser.php b/lib/twig/twig/src/TokenParser/FlushTokenParser.php index b25524fa8d..02c74aa134 100644 --- a/lib/twig/twig/src/TokenParser/FlushTokenParser.php +++ b/lib/twig/twig/src/TokenParser/FlushTokenParser.php @@ -12,6 +12,7 @@ namespace Twig\TokenParser; use Twig\Node\FlushNode; +use Twig\Node\Node; use Twig\Token; /** @@ -19,21 +20,19 @@ use Twig\Token; * * @see flush() * - * @final + * @internal */ -class FlushTokenParser extends AbstractTokenParser +final class FlushTokenParser extends AbstractTokenParser { - public function parse(Token $token) + public function parse(Token $token): Node { - $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + $this->parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3); return new FlushNode($token->getLine(), $this->getTag()); } - public function getTag() + public function getTag(): string { return 'flush'; } } - -class_alias('Twig\TokenParser\FlushTokenParser', 'Twig_TokenParser_Flush'); diff --git a/lib/twig/twig/src/TokenParser/ForTokenParser.php b/lib/twig/twig/src/TokenParser/ForTokenParser.php index 69278d98ed..bac8ba2dae 100644 --- a/lib/twig/twig/src/TokenParser/ForTokenParser.php +++ b/lib/twig/twig/src/TokenParser/ForTokenParser.php @@ -12,14 +12,10 @@ namespace Twig\TokenParser; -use Twig\Error\SyntaxError; use Twig\Node\Expression\AssignNameExpression; -use Twig\Node\Expression\ConstantExpression; -use Twig\Node\Expression\GetAttrExpression; -use Twig\Node\Expression\NameExpression; use Twig\Node\ForNode; +use Twig\Node\Node; use Twig\Token; -use Twig\TokenStream; /** * Loops over each item of a sequence. @@ -30,107 +26,53 @@ use Twig\TokenStream; * {% endfor %} * * - * @final + * @internal */ -class ForTokenParser extends AbstractTokenParser +final class ForTokenParser extends AbstractTokenParser { - public function parse(Token $token) + public function parse(Token $token): Node { $lineno = $token->getLine(); $stream = $this->parser->getStream(); $targets = $this->parser->getExpressionParser()->parseAssignmentExpression(); - $stream->expect(Token::OPERATOR_TYPE, 'in'); + $stream->expect(/* Token::OPERATOR_TYPE */ 8, 'in'); $seq = $this->parser->getExpressionParser()->parseExpression(); - $ifexpr = null; - if ($stream->nextIf(Token::NAME_TYPE, 'if')) { - $ifexpr = $this->parser->getExpressionParser()->parseExpression(); - } - - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); $body = $this->parser->subparse([$this, 'decideForFork']); if ('else' == $stream->next()->getValue()) { - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); $else = $this->parser->subparse([$this, 'decideForEnd'], true); } else { $else = null; } - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); if (\count($targets) > 1) { $keyTarget = $targets->getNode(0); $keyTarget = new AssignNameExpression($keyTarget->getAttribute('name'), $keyTarget->getTemplateLine()); $valueTarget = $targets->getNode(1); - $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); } else { $keyTarget = new AssignNameExpression('_key', $lineno); $valueTarget = $targets->getNode(0); - $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); } + $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); - if ($ifexpr) { - $this->checkLoopUsageCondition($stream, $ifexpr); - $this->checkLoopUsageBody($stream, $body); - } - - return new ForNode($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, $lineno, $this->getTag()); + return new ForNode($keyTarget, $valueTarget, $seq, null, $body, $else, $lineno, $this->getTag()); } - public function decideForFork(Token $token) + public function decideForFork(Token $token): bool { return $token->test(['else', 'endfor']); } - public function decideForEnd(Token $token) + public function decideForEnd(Token $token): bool { return $token->test('endfor'); } - // the loop variable cannot be used in the condition - protected function checkLoopUsageCondition(TokenStream $stream, \Twig_NodeInterface $node) - { - if ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression && 'loop' == $node->getNode('node')->getAttribute('name')) { - throw new SyntaxError('The "loop" variable cannot be used in a looping condition.', $node->getTemplateLine(), $stream->getSourceContext()); - } - - foreach ($node as $n) { - if (!$n) { - continue; - } - - $this->checkLoopUsageCondition($stream, $n); - } - } - - // check usage of non-defined loop-items - // it does not catch all problems (for instance when a for is included into another or when the variable is used in an include) - protected function checkLoopUsageBody(TokenStream $stream, \Twig_NodeInterface $node) - { - if ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression && 'loop' == $node->getNode('node')->getAttribute('name')) { - $attribute = $node->getNode('attribute'); - if ($attribute instanceof ConstantExpression && \in_array($attribute->getAttribute('value'), ['length', 'revindex0', 'revindex', 'last'])) { - throw new SyntaxError(sprintf('The "loop.%s" variable is not defined when looping with a condition.', $attribute->getAttribute('value')), $node->getTemplateLine(), $stream->getSourceContext()); - } - } - - // should check for parent.loop.XXX usage - if ($node instanceof ForNode) { - return; - } - - foreach ($node as $n) { - if (!$n) { - continue; - } - - $this->checkLoopUsageBody($stream, $n); - } - } - - public function getTag() + public function getTag(): string { return 'for'; } } - -class_alias('Twig\TokenParser\ForTokenParser', 'Twig_TokenParser_For'); diff --git a/lib/twig/twig/src/TokenParser/FromTokenParser.php b/lib/twig/twig/src/TokenParser/FromTokenParser.php index 4cce650d61..35098c267b 100644 --- a/lib/twig/twig/src/TokenParser/FromTokenParser.php +++ b/lib/twig/twig/src/TokenParser/FromTokenParser.php @@ -11,9 +11,9 @@ namespace Twig\TokenParser; -use Twig\Error\SyntaxError; use Twig\Node\Expression\AssignNameExpression; use Twig\Node\ImportNode; +use Twig\Node\Node; use Twig\Token; /** @@ -21,52 +21,46 @@ use Twig\Token; * * {% from 'forms.html' import forms %} * - * @final + * @internal */ -class FromTokenParser extends AbstractTokenParser +final class FromTokenParser extends AbstractTokenParser { - public function parse(Token $token) + public function parse(Token $token): Node { $macro = $this->parser->getExpressionParser()->parseExpression(); $stream = $this->parser->getStream(); - $stream->expect(Token::NAME_TYPE, 'import'); + $stream->expect(/* Token::NAME_TYPE */ 5, 'import'); $targets = []; do { - $name = $stream->expect(Token::NAME_TYPE)->getValue(); + $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); $alias = $name; if ($stream->nextIf('as')) { - $alias = $stream->expect(Token::NAME_TYPE)->getValue(); + $alias = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); } $targets[$name] = $alias; - if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { + if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { break; } } while (true); - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); $var = new AssignNameExpression($this->parser->getVarName(), $token->getLine()); - $node = new ImportNode($macro, $var, $token->getLine(), $this->getTag()); + $node = new ImportNode($macro, $var, $token->getLine(), $this->getTag(), $this->parser->isMainScope()); foreach ($targets as $name => $alias) { - if ($this->parser->isReservedMacroName($name)) { - throw new SyntaxError(sprintf('"%s" cannot be an imported macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext()); - } - - $this->parser->addImportedSymbol('function', $alias, 'get'.$name, $var); + $this->parser->addImportedSymbol('function', $alias, 'macro_'.$name, $var); } return $node; } - public function getTag() + public function getTag(): string { return 'from'; } } - -class_alias('Twig\TokenParser\FromTokenParser', 'Twig_TokenParser_From'); diff --git a/lib/twig/twig/src/TokenParser/IfTokenParser.php b/lib/twig/twig/src/TokenParser/IfTokenParser.php index 2631a20cec..c0fe6df0d1 100644 --- a/lib/twig/twig/src/TokenParser/IfTokenParser.php +++ b/lib/twig/twig/src/TokenParser/IfTokenParser.php @@ -28,16 +28,16 @@ use Twig\Token; * * {% endif %} * - * @final + * @internal */ -class IfTokenParser extends AbstractTokenParser +final class IfTokenParser extends AbstractTokenParser { - public function parse(Token $token) + public function parse(Token $token): Node { $lineno = $token->getLine(); $expr = $this->parser->getExpressionParser()->parseExpression(); $stream = $this->parser->getStream(); - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); $body = $this->parser->subparse([$this, 'decideIfFork']); $tests = [$expr, $body]; $else = null; @@ -46,13 +46,13 @@ class IfTokenParser extends AbstractTokenParser while (!$end) { switch ($stream->next()->getValue()) { case 'else': - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); $else = $this->parser->subparse([$this, 'decideIfEnd']); break; case 'elseif': $expr = $this->parser->getExpressionParser()->parseExpression(); - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); $body = $this->parser->subparse([$this, 'decideIfFork']); $tests[] = $expr; $tests[] = $body; @@ -67,25 +67,23 @@ class IfTokenParser extends AbstractTokenParser } } - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); return new IfNode(new Node($tests), $else, $lineno, $this->getTag()); } - public function decideIfFork(Token $token) + public function decideIfFork(Token $token): bool { return $token->test(['elseif', 'else', 'endif']); } - public function decideIfEnd(Token $token) + public function decideIfEnd(Token $token): bool { return $token->test(['endif']); } - public function getTag() + public function getTag(): string { return 'if'; } } - -class_alias('Twig\TokenParser\IfTokenParser', 'Twig_TokenParser_If'); diff --git a/lib/twig/twig/src/TokenParser/ImportTokenParser.php b/lib/twig/twig/src/TokenParser/ImportTokenParser.php index 88395b9248..44cb4dad79 100644 --- a/lib/twig/twig/src/TokenParser/ImportTokenParser.php +++ b/lib/twig/twig/src/TokenParser/ImportTokenParser.php @@ -13,6 +13,7 @@ namespace Twig\TokenParser; use Twig\Node\Expression\AssignNameExpression; use Twig\Node\ImportNode; +use Twig\Node\Node; use Twig\Token; /** @@ -20,26 +21,24 @@ use Twig\Token; * * {% import 'forms.html' as forms %} * - * @final + * @internal */ -class ImportTokenParser extends AbstractTokenParser +final class ImportTokenParser extends AbstractTokenParser { - public function parse(Token $token) + public function parse(Token $token): Node { $macro = $this->parser->getExpressionParser()->parseExpression(); - $this->parser->getStream()->expect(Token::NAME_TYPE, 'as'); - $var = new AssignNameExpression($this->parser->getStream()->expect(Token::NAME_TYPE)->getValue(), $token->getLine()); - $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + $this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5, 'as'); + $var = new AssignNameExpression($this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5)->getValue(), $token->getLine()); + $this->parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3); $this->parser->addImportedSymbol('template', $var->getAttribute('name')); - return new ImportNode($macro, $var, $token->getLine(), $this->getTag()); + return new ImportNode($macro, $var, $token->getLine(), $this->getTag(), $this->parser->isMainScope()); } - public function getTag() + public function getTag(): string { return 'import'; } } - -class_alias('Twig\TokenParser\ImportTokenParser', 'Twig_TokenParser_Import'); diff --git a/lib/twig/twig/src/TokenParser/IncludeTokenParser.php b/lib/twig/twig/src/TokenParser/IncludeTokenParser.php index 57aa4cf41a..28beb8ae47 100644 --- a/lib/twig/twig/src/TokenParser/IncludeTokenParser.php +++ b/lib/twig/twig/src/TokenParser/IncludeTokenParser.php @@ -13,6 +13,7 @@ namespace Twig\TokenParser; use Twig\Node\IncludeNode; +use Twig\Node\Node; use Twig\Token; /** @@ -21,10 +22,12 @@ use Twig\Token; * {% include 'header.html' %} * Body * {% include 'footer.html' %} + * + * @internal */ class IncludeTokenParser extends AbstractTokenParser { - public function parse(Token $token) + public function parse(Token $token): Node { $expr = $this->parser->getExpressionParser()->parseExpression(); @@ -38,31 +41,29 @@ class IncludeTokenParser extends AbstractTokenParser $stream = $this->parser->getStream(); $ignoreMissing = false; - if ($stream->nextIf(Token::NAME_TYPE, 'ignore')) { - $stream->expect(Token::NAME_TYPE, 'missing'); + if ($stream->nextIf(/* Token::NAME_TYPE */ 5, 'ignore')) { + $stream->expect(/* Token::NAME_TYPE */ 5, 'missing'); $ignoreMissing = true; } $variables = null; - if ($stream->nextIf(Token::NAME_TYPE, 'with')) { + if ($stream->nextIf(/* Token::NAME_TYPE */ 5, 'with')) { $variables = $this->parser->getExpressionParser()->parseExpression(); } $only = false; - if ($stream->nextIf(Token::NAME_TYPE, 'only')) { + if ($stream->nextIf(/* Token::NAME_TYPE */ 5, 'only')) { $only = true; } - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); return [$variables, $only, $ignoreMissing]; } - public function getTag() + public function getTag(): string { return 'include'; } } - -class_alias('Twig\TokenParser\IncludeTokenParser', 'Twig_TokenParser_Include'); diff --git a/lib/twig/twig/src/TokenParser/MacroTokenParser.php b/lib/twig/twig/src/TokenParser/MacroTokenParser.php index a0d66e7bea..f584927e90 100644 --- a/lib/twig/twig/src/TokenParser/MacroTokenParser.php +++ b/lib/twig/twig/src/TokenParser/MacroTokenParser.php @@ -24,22 +24,22 @@ use Twig\Token; * * {% endmacro %} * - * @final + * @internal */ -class MacroTokenParser extends AbstractTokenParser +final class MacroTokenParser extends AbstractTokenParser { - public function parse(Token $token) + public function parse(Token $token): Node { $lineno = $token->getLine(); $stream = $this->parser->getStream(); - $name = $stream->expect(Token::NAME_TYPE)->getValue(); + $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); $arguments = $this->parser->getExpressionParser()->parseArguments(true, true); - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); $this->parser->pushLocalScope(); $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); - if ($token = $stream->nextIf(Token::NAME_TYPE)) { + if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) { $value = $token->getValue(); if ($value != $name) { @@ -47,22 +47,20 @@ class MacroTokenParser extends AbstractTokenParser } } $this->parser->popLocalScope(); - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); $this->parser->setMacro($name, new MacroNode($name, new BodyNode([$body]), $arguments, $lineno, $this->getTag())); return new Node(); } - public function decideBlockEnd(Token $token) + public function decideBlockEnd(Token $token): bool { return $token->test('endmacro'); } - public function getTag() + public function getTag(): string { return 'macro'; } } - -class_alias('Twig\TokenParser\MacroTokenParser', 'Twig_TokenParser_Macro'); diff --git a/lib/twig/twig/src/TokenParser/SandboxTokenParser.php b/lib/twig/twig/src/TokenParser/SandboxTokenParser.php index 0f3ad9e3e6..c919556ecc 100644 --- a/lib/twig/twig/src/TokenParser/SandboxTokenParser.php +++ b/lib/twig/twig/src/TokenParser/SandboxTokenParser.php @@ -13,6 +13,7 @@ namespace Twig\TokenParser; use Twig\Error\SyntaxError; use Twig\Node\IncludeNode; +use Twig\Node\Node; use Twig\Node\SandboxNode; use Twig\Node\TextNode; use Twig\Token; @@ -26,16 +27,16 @@ use Twig\Token; * * @see https://twig.symfony.com/doc/api.html#sandbox-extension for details * - * @final + * @internal */ -class SandboxTokenParser extends AbstractTokenParser +final class SandboxTokenParser extends AbstractTokenParser { - public function parse(Token $token) + public function parse(Token $token): Node { $stream = $this->parser->getStream(); - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); // in a sandbox tag, only include tags are allowed if (!$body instanceof IncludeNode) { @@ -53,15 +54,13 @@ class SandboxTokenParser extends AbstractTokenParser return new SandboxNode($body, $token->getLine(), $this->getTag()); } - public function decideBlockEnd(Token $token) + public function decideBlockEnd(Token $token): bool { return $token->test('endsandbox'); } - public function getTag() + public function getTag(): string { return 'sandbox'; } } - -class_alias('Twig\TokenParser\SandboxTokenParser', 'Twig_TokenParser_Sandbox'); diff --git a/lib/twig/twig/src/TokenParser/SetTokenParser.php b/lib/twig/twig/src/TokenParser/SetTokenParser.php index eebebc6985..2fbdfe0901 100644 --- a/lib/twig/twig/src/TokenParser/SetTokenParser.php +++ b/lib/twig/twig/src/TokenParser/SetTokenParser.php @@ -12,6 +12,7 @@ namespace Twig\TokenParser; use Twig\Error\SyntaxError; +use Twig\Node\Node; use Twig\Node\SetNode; use Twig\Token; @@ -25,21 +26,21 @@ use Twig\Token; * {% set foo, bar = 'foo', 'bar' %} * {% set foo %}Some content{% endset %} * - * @final + * @internal */ -class SetTokenParser extends AbstractTokenParser +final class SetTokenParser extends AbstractTokenParser { - public function parse(Token $token) + public function parse(Token $token): Node { $lineno = $token->getLine(); $stream = $this->parser->getStream(); $names = $this->parser->getExpressionParser()->parseAssignmentExpression(); $capture = false; - if ($stream->nextIf(Token::OPERATOR_TYPE, '=')) { + if ($stream->nextIf(/* Token::OPERATOR_TYPE */ 8, '=')) { $values = $this->parser->getExpressionParser()->parseMultitargetExpression(); - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); if (\count($names) !== \count($values)) { throw new SyntaxError('When using set, you must have the same number of variables and assignments.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); @@ -51,24 +52,22 @@ class SetTokenParser extends AbstractTokenParser throw new SyntaxError('When using set with a block, you cannot have a multi-target.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); } - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); $values = $this->parser->subparse([$this, 'decideBlockEnd'], true); - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); } return new SetNode($capture, $names, $values, $lineno, $this->getTag()); } - public function decideBlockEnd(Token $token) + public function decideBlockEnd(Token $token): bool { return $token->test('endset'); } - public function getTag() + public function getTag(): string { return 'set'; } } - -class_alias('Twig\TokenParser\SetTokenParser', 'Twig_TokenParser_Set'); diff --git a/lib/twig/twig/src/TokenParser/SpacelessTokenParser.php b/lib/twig/twig/src/TokenParser/SpacelessTokenParser.php deleted file mode 100644 index 5b5656bc64..0000000000 --- a/lib/twig/twig/src/TokenParser/SpacelessTokenParser.php +++ /dev/null @@ -1,53 +0,0 @@ - - * foo - * - * {% endspaceless %} - * {# output will be
    foo
    #} - * - * @final - */ -class SpacelessTokenParser extends AbstractTokenParser -{ - public function parse(Token $token) - { - $lineno = $token->getLine(); - - $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); - $body = $this->parser->subparse([$this, 'decideSpacelessEnd'], true); - $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); - - return new SpacelessNode($body, $lineno, $this->getTag()); - } - - public function decideSpacelessEnd(Token $token) - { - return $token->test('endspaceless'); - } - - public function getTag() - { - return 'spaceless'; - } -} - -class_alias('Twig\TokenParser\SpacelessTokenParser', 'Twig_TokenParser_Spaceless'); diff --git a/lib/twig/twig/src/TokenParser/TokenParserInterface.php b/lib/twig/twig/src/TokenParser/TokenParserInterface.php index 4b603b213f..bb8db3e5cc 100644 --- a/lib/twig/twig/src/TokenParser/TokenParserInterface.php +++ b/lib/twig/twig/src/TokenParser/TokenParserInterface.php @@ -12,6 +12,7 @@ namespace Twig\TokenParser; use Twig\Error\SyntaxError; +use Twig\Node\Node; use Twig\Parser; use Twig\Token; @@ -25,12 +26,12 @@ interface TokenParserInterface /** * Sets the parser associated with this token parser. */ - public function setParser(Parser $parser); + public function setParser(Parser $parser): void; /** * Parses a token and returns a node. * - * @return \Twig_NodeInterface + * @return Node * * @throws SyntaxError */ @@ -39,13 +40,7 @@ interface TokenParserInterface /** * Gets the tag name associated with this token parser. * - * @return string The tag name + * @return string */ public function getTag(); } - -class_alias('Twig\TokenParser\TokenParserInterface', 'Twig_TokenParserInterface'); - -// Ensure that the aliased name is loaded to keep BC for classes implementing the typehint with the old aliased name. -class_exists('Twig\Token'); -class_exists('Twig\Parser'); diff --git a/lib/twig/twig/src/TokenParser/UseTokenParser.php b/lib/twig/twig/src/TokenParser/UseTokenParser.php index d2e39aabee..d0a2de41a2 100644 --- a/lib/twig/twig/src/TokenParser/UseTokenParser.php +++ b/lib/twig/twig/src/TokenParser/UseTokenParser.php @@ -28,11 +28,11 @@ use Twig\Token; * * @see https://twig.symfony.com/doc/templates.html#horizontal-reuse for details. * - * @final + * @internal */ -class UseTokenParser extends AbstractTokenParser +final class UseTokenParser extends AbstractTokenParser { - public function parse(Token $token) + public function parse(Token $token): Node { $template = $this->parser->getExpressionParser()->parseExpression(); $stream = $this->parser->getStream(); @@ -44,32 +44,30 @@ class UseTokenParser extends AbstractTokenParser $targets = []; if ($stream->nextIf('with')) { do { - $name = $stream->expect(Token::NAME_TYPE)->getValue(); + $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); $alias = $name; if ($stream->nextIf('as')) { - $alias = $stream->expect(Token::NAME_TYPE)->getValue(); + $alias = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); } $targets[$name] = new ConstantExpression($alias, -1); - if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { + if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { break; } } while (true); } - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); $this->parser->addTrait(new Node(['template' => $template, 'targets' => new Node($targets)])); return new Node(); } - public function getTag() + public function getTag(): string { return 'use'; } } - -class_alias('Twig\TokenParser\UseTokenParser', 'Twig_TokenParser_Use'); diff --git a/lib/twig/twig/src/TokenParser/WithTokenParser.php b/lib/twig/twig/src/TokenParser/WithTokenParser.php index 411e2b4a13..7d8cbe2616 100644 --- a/lib/twig/twig/src/TokenParser/WithTokenParser.php +++ b/lib/twig/twig/src/TokenParser/WithTokenParser.php @@ -11,6 +11,7 @@ namespace Twig\TokenParser; +use Twig\Node\Node; use Twig\Node\WithNode; use Twig\Token; @@ -19,39 +20,37 @@ use Twig\Token; * * @author Fabien Potencier * - * @final + * @internal */ -class WithTokenParser extends AbstractTokenParser +final class WithTokenParser extends AbstractTokenParser { - public function parse(Token $token) + public function parse(Token $token): Node { $stream = $this->parser->getStream(); $variables = null; $only = false; - if (!$stream->test(Token::BLOCK_END_TYPE)) { + if (!$stream->test(/* Token::BLOCK_END_TYPE */ 3)) { $variables = $this->parser->getExpressionParser()->parseExpression(); - $only = $stream->nextIf(Token::NAME_TYPE, 'only'); + $only = (bool) $stream->nextIf(/* Token::NAME_TYPE */ 5, 'only'); } - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); $body = $this->parser->subparse([$this, 'decideWithEnd'], true); - $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); return new WithNode($body, $variables, $only, $token->getLine(), $this->getTag()); } - public function decideWithEnd(Token $token) + public function decideWithEnd(Token $token): bool { return $token->test('endwith'); } - public function getTag() + public function getTag(): string { return 'with'; } } - -class_alias('Twig\TokenParser\WithTokenParser', 'Twig_TokenParser_With'); diff --git a/lib/twig/twig/src/TokenStream.php b/lib/twig/twig/src/TokenStream.php index 4597816959..1eac11a02d 100644 --- a/lib/twig/twig/src/TokenStream.php +++ b/lib/twig/twig/src/TokenStream.php @@ -17,38 +17,18 @@ use Twig\Error\SyntaxError; /** * Represents a token stream. * - * @final - * * @author Fabien Potencier */ -class TokenStream +final class TokenStream { - protected $tokens; - protected $current = 0; - protected $filename; - + private $tokens; + private $current = 0; private $source; - /** - * @param array $tokens An array of tokens - * @param string|null $name The name of the template which tokens are associated with - * @param string|null $source The source code associated with the tokens - */ - public function __construct(array $tokens, $name = null, $source = null) + public function __construct(array $tokens, Source $source = null) { - if (!$name instanceof Source) { - if (null !== $name || null !== $source) { - @trigger_error(sprintf('Passing a string as the $name argument of %s() is deprecated since version 1.27. Pass a \Twig\Source instance instead.', __METHOD__), E_USER_DEPRECATED); - } - $this->source = new Source($source, $name); - } else { - $this->source = $name; - } - $this->tokens = $tokens; - - // deprecated, not used anymore, to be removed in 2.0 - $this->filename = $this->source->getName(); + $this->source = $source ?: new Source('', ''); } public function __toString() @@ -63,10 +43,8 @@ class TokenStream /** * Sets the pointer to the next token and returns the old one. - * - * @return Token */ - public function next() + public function next(): Token { if (!isset($this->tokens[++$this->current])) { throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current - 1]->getLine(), $this->source); @@ -89,10 +67,8 @@ class TokenStream /** * Tests a token and returns it or throws a syntax error. - * - * @return Token */ - public function expect($type, $value = null, $message = null) + public function expect($type, $value = null, string $message = null): Token { $token = $this->tokens[$this->current]; if (!$token->test($type, $value)) { @@ -113,12 +89,8 @@ class TokenStream /** * Looks at the next token. - * - * @param int $number - * - * @return Token */ - public function look($number = 1) + public function look(int $number = 1): Token { if (!isset($this->tokens[$this->current + $number])) { throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current + $number - 1]->getLine(), $this->source); @@ -129,73 +101,32 @@ class TokenStream /** * Tests the current token. - * - * @return bool */ - public function test($primary, $secondary = null) + public function test($primary, $secondary = null): bool { return $this->tokens[$this->current]->test($primary, $secondary); } /** * Checks if end of stream was reached. - * - * @return bool */ - public function isEOF() + public function isEOF(): bool { - return Token::EOF_TYPE === $this->tokens[$this->current]->getType(); + return /* Token::EOF_TYPE */ -1 === $this->tokens[$this->current]->getType(); } - /** - * @return Token - */ - public function getCurrent() + public function getCurrent(): Token { return $this->tokens[$this->current]; } - /** - * Gets the name associated with this stream (null if not defined). - * - * @return string|null - * - * @deprecated since 1.27 (to be removed in 2.0) - */ - public function getFilename() - { - @trigger_error(sprintf('The %s() method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); - - return $this->source->getName(); - } - - /** - * Gets the source code associated with this stream. - * - * @return string - * - * @internal Don't use this as it might be empty depending on the environment configuration - * - * @deprecated since 1.27 (to be removed in 2.0) - */ - public function getSource() - { - @trigger_error(sprintf('The %s() method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); - - return $this->source->getCode(); - } - /** * Gets the source associated with this stream. * - * @return Source - * * @internal */ - public function getSourceContext() + public function getSourceContext(): Source { return $this->source; } } - -class_alias('Twig\TokenStream', 'Twig_TokenStream'); diff --git a/lib/twig/twig/src/TwigFilter.php b/lib/twig/twig/src/TwigFilter.php index 089a6d1b88..94e5f9b012 100644 --- a/lib/twig/twig/src/TwigFilter.php +++ b/lib/twig/twig/src/TwigFilter.php @@ -11,23 +11,27 @@ namespace Twig; +use Twig\Node\Expression\FilterExpression; use Twig\Node\Node; /** * Represents a template filter. * - * @final - * * @author Fabien Potencier + * + * @see https://twig.symfony.com/doc/templates.html#filters */ -class TwigFilter +final class TwigFilter { - protected $name; - protected $callable; - protected $options; - protected $arguments = []; + private $name; + private $callable; + private $options; + private $arguments = []; - public function __construct($name, $callable, array $options = []) + /** + * @param callable|null $callable A callable implementing the filter. If null, you need to overwrite the "node_class" option to customize compilation. + */ + public function __construct(string $name, $callable = null, array $options = []) { $this->name = $name; $this->callable = $callable; @@ -39,90 +43,92 @@ class TwigFilter 'is_safe_callback' => null, 'pre_escape' => null, 'preserves_safety' => null, - 'node_class' => '\Twig\Node\Expression\FilterExpression', + 'node_class' => FilterExpression::class, 'deprecated' => false, 'alternative' => null, ], $options); } - public function getName() + public function getName(): string { return $this->name; } + /** + * Returns the callable to execute for this filter. + * + * @return callable|null + */ public function getCallable() { return $this->callable; } - public function getNodeClass() + public function getNodeClass(): string { return $this->options['node_class']; } - public function setArguments($arguments) + public function setArguments(array $arguments): void { $this->arguments = $arguments; } - public function getArguments() + public function getArguments(): array { return $this->arguments; } - public function needsEnvironment() + public function needsEnvironment(): bool { return $this->options['needs_environment']; } - public function needsContext() + public function needsContext(): bool { return $this->options['needs_context']; } - public function getSafe(Node $filterArgs) + public function getSafe(Node $filterArgs): ?array { if (null !== $this->options['is_safe']) { return $this->options['is_safe']; } if (null !== $this->options['is_safe_callback']) { - return \call_user_func($this->options['is_safe_callback'], $filterArgs); + return $this->options['is_safe_callback']($filterArgs); } + + return null; } - public function getPreservesSafety() + public function getPreservesSafety(): ?array { return $this->options['preserves_safety']; } - public function getPreEscape() + public function getPreEscape(): ?string { return $this->options['pre_escape']; } - public function isVariadic() + public function isVariadic(): bool { return $this->options['is_variadic']; } - public function isDeprecated() + public function isDeprecated(): bool { return (bool) $this->options['deprecated']; } - public function getDeprecatedVersion() + public function getDeprecatedVersion(): string { - return $this->options['deprecated']; + return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated']; } - public function getAlternative() + public function getAlternative(): ?string { return $this->options['alternative']; } } - -class_alias('Twig\TwigFilter', 'Twig_SimpleFilter'); - -// Ensure that the aliased name is loaded to keep BC for classes implementing the typehint with the old aliased name. -class_exists('Twig\Node\Node'); diff --git a/lib/twig/twig/src/TwigFunction.php b/lib/twig/twig/src/TwigFunction.php index 374f07071e..494d45b08c 100644 --- a/lib/twig/twig/src/TwigFunction.php +++ b/lib/twig/twig/src/TwigFunction.php @@ -11,23 +11,27 @@ namespace Twig; +use Twig\Node\Expression\FunctionExpression; use Twig\Node\Node; /** * Represents a template function. * - * @final - * * @author Fabien Potencier + * + * @see https://twig.symfony.com/doc/templates.html#functions */ -class TwigFunction +final class TwigFunction { - protected $name; - protected $callable; - protected $options; - protected $arguments = []; + private $name; + private $callable; + private $options; + private $arguments = []; - public function __construct($name, $callable, array $options = []) + /** + * @param callable|null $callable A callable implementing the function. If null, you need to overwrite the "node_class" option to customize compilation. + */ + public function __construct(string $name, $callable = null, array $options = []) { $this->name = $name; $this->callable = $callable; @@ -37,82 +41,82 @@ class TwigFunction 'is_variadic' => false, 'is_safe' => null, 'is_safe_callback' => null, - 'node_class' => '\Twig\Node\Expression\FunctionExpression', + 'node_class' => FunctionExpression::class, 'deprecated' => false, 'alternative' => null, ], $options); } - public function getName() + public function getName(): string { return $this->name; } + /** + * Returns the callable to execute for this function. + * + * @return callable|null + */ public function getCallable() { return $this->callable; } - public function getNodeClass() + public function getNodeClass(): string { return $this->options['node_class']; } - public function setArguments($arguments) + public function setArguments(array $arguments): void { $this->arguments = $arguments; } - public function getArguments() + public function getArguments(): array { return $this->arguments; } - public function needsEnvironment() + public function needsEnvironment(): bool { return $this->options['needs_environment']; } - public function needsContext() + public function needsContext(): bool { return $this->options['needs_context']; } - public function getSafe(Node $functionArgs) + public function getSafe(Node $functionArgs): ?array { if (null !== $this->options['is_safe']) { return $this->options['is_safe']; } if (null !== $this->options['is_safe_callback']) { - return \call_user_func($this->options['is_safe_callback'], $functionArgs); + return $this->options['is_safe_callback']($functionArgs); } return []; } - public function isVariadic() + public function isVariadic(): bool { - return $this->options['is_variadic']; + return (bool) $this->options['is_variadic']; } - public function isDeprecated() + public function isDeprecated(): bool { return (bool) $this->options['deprecated']; } - public function getDeprecatedVersion() + public function getDeprecatedVersion(): string { - return $this->options['deprecated']; + return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated']; } - public function getAlternative() + public function getAlternative(): ?string { return $this->options['alternative']; } } - -class_alias('Twig\TwigFunction', 'Twig_SimpleFunction'); - -// Ensure that the aliased name is loaded to keep BC for classes implementing the typehint with the old aliased name. -class_exists('Twig\Node\Node'); diff --git a/lib/twig/twig/src/TwigTest.php b/lib/twig/twig/src/TwigTest.php index 5054965fe9..4c18632f55 100644 --- a/lib/twig/twig/src/TwigTest.php +++ b/lib/twig/twig/src/TwigTest.php @@ -11,77 +11,90 @@ namespace Twig; +use Twig\Node\Expression\TestExpression; + /** * Represents a template test. * - * @final - * * @author Fabien Potencier + * + * @see https://twig.symfony.com/doc/templates.html#test-operator */ -class TwigTest +final class TwigTest { - protected $name; - protected $callable; - protected $options; - + private $name; + private $callable; + private $options; private $arguments = []; - public function __construct($name, $callable, array $options = []) + /** + * @param callable|null $callable A callable implementing the test. If null, you need to overwrite the "node_class" option to customize compilation. + */ + public function __construct(string $name, $callable = null, array $options = []) { $this->name = $name; $this->callable = $callable; $this->options = array_merge([ 'is_variadic' => false, - 'node_class' => '\Twig\Node\Expression\TestExpression', + 'node_class' => TestExpression::class, 'deprecated' => false, 'alternative' => null, + 'one_mandatory_argument' => false, ], $options); } - public function getName() + public function getName(): string { return $this->name; } + /** + * Returns the callable to execute for this test. + * + * @return callable|null + */ public function getCallable() { return $this->callable; } - public function getNodeClass() + public function getNodeClass(): string { return $this->options['node_class']; } - public function isVariadic() - { - return $this->options['is_variadic']; - } - - public function isDeprecated() - { - return (bool) $this->options['deprecated']; - } - - public function getDeprecatedVersion() - { - return $this->options['deprecated']; - } - - public function getAlternative() - { - return $this->options['alternative']; - } - - public function setArguments($arguments) + public function setArguments(array $arguments): void { $this->arguments = $arguments; } - public function getArguments() + public function getArguments(): array { return $this->arguments; } -} -class_alias('Twig\TwigTest', 'Twig_SimpleTest'); + public function isVariadic(): bool + { + return (bool) $this->options['is_variadic']; + } + + public function isDeprecated(): bool + { + return (bool) $this->options['deprecated']; + } + + public function getDeprecatedVersion(): string + { + return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated']; + } + + public function getAlternative(): ?string + { + return $this->options['alternative']; + } + + public function hasOneMandatoryArgument(): bool + { + return (bool) $this->options['one_mandatory_argument']; + } +} diff --git a/lib/twig/twig/src/Util/DeprecationCollector.php b/lib/twig/twig/src/Util/DeprecationCollector.php index 09917e927c..378b666bdb 100644 --- a/lib/twig/twig/src/Util/DeprecationCollector.php +++ b/lib/twig/twig/src/Util/DeprecationCollector.php @@ -17,13 +17,10 @@ use Twig\Source; /** * @author Fabien Potencier - * - * @final */ -class DeprecationCollector +final class DeprecationCollector { private $twig; - private $deprecations; public function __construct(Environment $twig) { @@ -38,7 +35,7 @@ class DeprecationCollector * * @return array An array of deprecations */ - public function collectDir($dir, $ext = '.twig') + public function collectDir(string $dir, string $ext = '.twig'): array { $iterator = new \RegexIterator( new \RecursiveIteratorIterator( @@ -56,11 +53,14 @@ class DeprecationCollector * * @return array An array of deprecations */ - public function collect(\Traversable $iterator) + public function collect(\Traversable $iterator): array { - $this->deprecations = []; - - set_error_handler([$this, 'errorHandler']); + $deprecations = []; + set_error_handler(function ($type, $msg) use (&$deprecations) { + if (\E_USER_DEPRECATED === $type) { + $deprecations[] = $msg; + } + }); foreach ($iterator as $name => $contents) { try { @@ -72,21 +72,6 @@ class DeprecationCollector restore_error_handler(); - $deprecations = $this->deprecations; - $this->deprecations = []; - return $deprecations; } - - /** - * @internal - */ - public function errorHandler($type, $msg) - { - if (E_USER_DEPRECATED === $type) { - $this->deprecations[] = $msg; - } - } } - -class_alias('Twig\Util\DeprecationCollector', 'Twig_Util_DeprecationCollector'); diff --git a/lib/twig/twig/src/Util/TemplateDirIterator.php b/lib/twig/twig/src/Util/TemplateDirIterator.php index 1ab0dac59d..3bef14beec 100644 --- a/lib/twig/twig/src/Util/TemplateDirIterator.php +++ b/lib/twig/twig/src/Util/TemplateDirIterator.php @@ -16,15 +16,21 @@ namespace Twig\Util; */ class TemplateDirIterator extends \IteratorIterator { + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function current() { return file_get_contents(parent::current()); } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] public function key() { return (string) parent::key(); } } - -class_alias('Twig\Util\TemplateDirIterator', 'Twig_Util_TemplateDirIterator'); diff --git a/lib/webmozart/assert/CHANGELOG.md b/lib/webmozart/assert/CHANGELOG.md new file mode 100644 index 0000000000..56c8011dee --- /dev/null +++ b/lib/webmozart/assert/CHANGELOG.md @@ -0,0 +1,207 @@ +Changelog +========= + +## UNRELEASED + +## 1.11.0 + +### Added + +* Added explicit (non magic) `allNullOr*` methods, with `@psalm-assert` annotations, for better Psalm support. + +### Changed + +* Trait methods will now check the assertion themselves, instead of using `__callStatic` +* `isList` will now deal correctly with (modified) lists that contain `NaN` +* `reportInvalidArgument` now has a return type of `never`. + +### Removed + +* Removed `symfony/polyfill-ctype` as a dependency, and require `ext-cytpe` instead. + * You can still require the `symfony/polyfill-ctype` in your project if you need it, as it provides `ext-ctype` + +## 1.10.0 + +### Added + +* On invalid assertion, we throw a `Webmozart\Assert\InvalidArgumentException` +* Added `Assert::positiveInteger()` + +### Changed + +* Using a trait with real implementations of `all*()` and `nullOr*()` methods to improve psalm compatibility. + +### Removed + +* Support for PHP <7.2 + +## 1.9.1 + +## Fixed + +* provisional support for PHP 8.0 + +## 1.9.0 + +* added better Psalm support for `all*` & `nullOr*` methods +* These methods are now understood by Psalm through a mixin. You may need a newer version of Psalm in order to use this +* added `@psalm-pure` annotation to `Assert::notFalse()` +* added more `@psalm-assert` annotations where appropriate + +## Changed + +* the `all*` & `nullOr*` methods are now declared on an interface, instead of `@method` annotations. +This interface is linked to the `Assert` class with a `@mixin` annotation. Most IDE's have supported this +for a long time, and you should not lose any autocompletion capabilities. PHPStan has supported this since +version `0.12.20`. This package is marked incompatible (with a composer conflict) with phpstan version prior to that. +If you do not use PHPStan than this does not matter. + +## 1.8.0 + +### Added + +* added `Assert::notStartsWith()` +* added `Assert::notEndsWith()` +* added `Assert::inArray()` +* added `@psalm-pure` annotations to pure assertions + +### Fixed + +* Exception messages of comparisons between `DateTime(Immutable)` objects now display their date & time. +* Custom Exception messages for `Assert::count()` now use the values to render the exception message. + +## 1.7.0 (2020-02-14) + +### Added + +* added `Assert::notFalse()` +* added `Assert::isAOf()` +* added `Assert::isAnyOf()` +* added `Assert::isNotA()` + +## 1.6.0 (2019-11-24) + +### Added + +* added `Assert::validArrayKey()` +* added `Assert::isNonEmptyList()` +* added `Assert::isNonEmptyMap()` +* added `@throws InvalidArgumentException` annotations to all methods that throw. +* added `@psalm-assert` for the list type to the `isList` assertion. + +### Fixed + +* `ResourceBundle` & `SimpleXMLElement` now pass the `isCountable` assertions. +They are countable, without implementing the `Countable` interface. +* The doc block of `range` now has the proper variables. +* An empty array will now pass `isList` and `isMap`. As it is a valid form of both. +If a non-empty variant is needed, use `isNonEmptyList` or `isNonEmptyMap`. + +### Changed + +* Removed some `@psalm-assert` annotations, that were 'side effect' assertions See: + * [#144](https://github.com/webmozart/assert/pull/144) + * [#145](https://github.com/webmozart/assert/issues/145) + * [#146](https://github.com/webmozart/assert/pull/146) + * [#150](https://github.com/webmozart/assert/pull/150) +* If you use Psalm, the minimum version needed is `3.6.0`. Which is enforced through a composer conflict. +If you don't use Psalm, then this has no impact. + +## 1.5.0 (2019-08-24) + +### Added + +* added `Assert::uniqueValues()` +* added `Assert::unicodeLetters()` +* added: `Assert::email()` +* added support for [Psalm](https://github.com/vimeo/psalm), by adding `@psalm-assert` annotations where appropriate. + +### Fixed + +* `Assert::endsWith()` would not give the correct result when dealing with a multibyte suffix. +* `Assert::length(), minLength, maxLength, lengthBetween` would not give the correct result when dealing with multibyte characters. + +**NOTE**: These 2 changes may break your assertions if you relied on the fact that multibyte characters didn't behave correctly. + +### Changed + +* The names of some variables have been updated to better reflect what they are. +* All function calls are now in their FQN form, slightly increasing performance. +* Tests are now properly ran against HHVM-3.30 and PHP nightly. + +### Deprecation + +* deprecated `Assert::isTraversable()` in favor of `Assert::isIterable()` + * This was already done in 1.3.0, but it was only done through a silenced `trigger_error`. It is now annotated as well. + +## 1.4.0 (2018-12-25) + +### Added + +* added `Assert::ip()` +* added `Assert::ipv4()` +* added `Assert::ipv6()` +* added `Assert::notRegex()` +* added `Assert::interfaceExists()` +* added `Assert::isList()` +* added `Assert::isMap()` +* added polyfill for ctype + +### Fixed + +* Special case when comparing objects implementing `__toString()` + +## 1.3.0 (2018-01-29) + +### Added + +* added `Assert::minCount()` +* added `Assert::maxCount()` +* added `Assert::countBetween()` +* added `Assert::isCountable()` +* added `Assert::notWhitespaceOnly()` +* added `Assert::natural()` +* added `Assert::notContains()` +* added `Assert::isArrayAccessible()` +* added `Assert::isInstanceOfAny()` +* added `Assert::isIterable()` + +### Fixed + +* `stringNotEmpty` will no longer report "0" is an empty string + +### Deprecation + +* deprecated `Assert::isTraversable()` in favor of `Assert::isIterable()` + +## 1.2.0 (2016-11-23) + + * added `Assert::throws()` + * added `Assert::count()` + * added extension point `Assert::reportInvalidArgument()` for custom subclasses + +## 1.1.0 (2016-08-09) + + * added `Assert::object()` + * added `Assert::propertyExists()` + * added `Assert::propertyNotExists()` + * added `Assert::methodExists()` + * added `Assert::methodNotExists()` + * added `Assert::uuid()` + +## 1.0.2 (2015-08-24) + + * integrated Style CI + * add tests for minimum package dependencies on Travis CI + +## 1.0.1 (2015-05-12) + + * added support for PHP 5.3.3 + +## 1.0.0 (2015-05-12) + + * first stable release + +## 1.0.0-beta (2015-03-19) + + * first beta release diff --git a/lib/webmozart/assert/LICENSE b/lib/webmozart/assert/LICENSE new file mode 100644 index 0000000000..9e2e3075eb --- /dev/null +++ b/lib/webmozart/assert/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Bernhard Schussek + +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/lib/webmozart/assert/README.md b/lib/webmozart/assert/README.md new file mode 100644 index 0000000000..3b2397a1ab --- /dev/null +++ b/lib/webmozart/assert/README.md @@ -0,0 +1,287 @@ +Webmozart Assert +================ + +[![Latest Stable Version](https://poser.pugx.org/webmozart/assert/v/stable.svg)](https://packagist.org/packages/webmozart/assert) +[![Total Downloads](https://poser.pugx.org/webmozart/assert/downloads.svg)](https://packagist.org/packages/webmozart/assert) + +This library contains efficient assertions to test the input and output of +your methods. With these assertions, you can greatly reduce the amount of coding +needed to write a safe implementation. + +All assertions in the [`Assert`] class throw an `Webmozart\Assert\InvalidArgumentException` if +they fail. + +FAQ +--- + +**What's the difference to [beberlei/assert]?** + +This library is heavily inspired by Benjamin Eberlei's wonderful [assert package], +but fixes a usability issue with error messages that can't be fixed there without +breaking backwards compatibility. + +This package features usable error messages by default. However, you can also +easily write custom error messages: + +``` +Assert::string($path, 'The path is expected to be a string. Got: %s'); +``` + +In [beberlei/assert], the ordering of the `%s` placeholders is different for +every assertion. This package, on the contrary, provides consistent placeholder +ordering for all assertions: + +* `%s`: The tested value as string, e.g. `"/foo/bar"`. +* `%2$s`, `%3$s`, ...: Additional assertion-specific values, e.g. the + minimum/maximum length, allowed values, etc. + +Check the source code of the assertions to find out details about the additional +available placeholders. + +Installation +------------ + +Use [Composer] to install the package: + +```bash +composer require webmozart/assert +``` + +Example +------- + +```php +use Webmozart\Assert\Assert; + +class Employee +{ + public function __construct($id) + { + Assert::integer($id, 'The employee ID must be an integer. Got: %s'); + Assert::greaterThan($id, 0, 'The employee ID must be a positive integer. Got: %s'); + } +} +``` + +If you create an employee with an invalid ID, an exception is thrown: + +```php +new Employee('foobar'); +// => Webmozart\Assert\InvalidArgumentException: +// The employee ID must be an integer. Got: string + +new Employee(-10); +// => Webmozart\Assert\InvalidArgumentException: +// The employee ID must be a positive integer. Got: -10 +``` + +Assertions +---------- + +The [`Assert`] class provides the following assertions: + +### Type Assertions + +Method | Description +-------------------------------------------------------- | -------------------------------------------------- +`string($value, $message = '')` | Check that a value is a string +`stringNotEmpty($value, $message = '')` | Check that a value is a non-empty string +`integer($value, $message = '')` | Check that a value is an integer +`integerish($value, $message = '')` | Check that a value casts to an integer +`positiveInteger($value, $message = '')` | Check that a value is a positive (non-zero) integer +`float($value, $message = '')` | Check that a value is a float +`numeric($value, $message = '')` | Check that a value is numeric +`natural($value, $message= ''')` | Check that a value is a non-negative integer +`boolean($value, $message = '')` | Check that a value is a boolean +`scalar($value, $message = '')` | Check that a value is a scalar +`object($value, $message = '')` | Check that a value is an object +`resource($value, $type = null, $message = '')` | Check that a value is a resource +`isCallable($value, $message = '')` | Check that a value is a callable +`isArray($value, $message = '')` | Check that a value is an array +`isTraversable($value, $message = '')` (deprecated) | Check that a value is an array or a `\Traversable` +`isIterable($value, $message = '')` | Check that a value is an array or a `\Traversable` +`isCountable($value, $message = '')` | Check that a value is an array or a `\Countable` +`isInstanceOf($value, $class, $message = '')` | Check that a value is an `instanceof` a class +`isInstanceOfAny($value, array $classes, $message = '')` | Check that a value is an `instanceof` at least one class on the array of classes +`notInstanceOf($value, $class, $message = '')` | Check that a value is not an `instanceof` a class +`isAOf($value, $class, $message = '')` | Check that a value is of the class or has one of its parents +`isAnyOf($value, array $classes, $message = '')` | Check that a value is of at least one of the classes or has one of its parents +`isNotA($value, $class, $message = '')` | Check that a value is not of the class or has not one of its parents +`isArrayAccessible($value, $message = '')` | Check that a value can be accessed as an array +`uniqueValues($values, $message = '')` | Check that the given array contains unique values + +### Comparison Assertions + +Method | Description +----------------------------------------------- | ------------------------------------------------------------------ +`true($value, $message = '')` | Check that a value is `true` +`false($value, $message = '')` | Check that a value is `false` +`notFalse($value, $message = '')` | Check that a value is not `false` +`null($value, $message = '')` | Check that a value is `null` +`notNull($value, $message = '')` | Check that a value is not `null` +`isEmpty($value, $message = '')` | Check that a value is `empty()` +`notEmpty($value, $message = '')` | Check that a value is not `empty()` +`eq($value, $value2, $message = '')` | Check that a value equals another (`==`) +`notEq($value, $value2, $message = '')` | Check that a value does not equal another (`!=`) +`same($value, $value2, $message = '')` | Check that a value is identical to another (`===`) +`notSame($value, $value2, $message = '')` | Check that a value is not identical to another (`!==`) +`greaterThan($value, $value2, $message = '')` | Check that a value is greater than another +`greaterThanEq($value, $value2, $message = '')` | Check that a value is greater than or equal to another +`lessThan($value, $value2, $message = '')` | Check that a value is less than another +`lessThanEq($value, $value2, $message = '')` | Check that a value is less than or equal to another +`range($value, $min, $max, $message = '')` | Check that a value is within a range +`inArray($value, array $values, $message = '')` | Check that a value is one of a list of values +`oneOf($value, array $values, $message = '')` | Check that a value is one of a list of values (alias of `inArray`) + +### String Assertions + +You should check that a value is a string with `Assert::string()` before making +any of the following assertions. + +Method | Description +--------------------------------------------------- | ----------------------------------------------------------------- +`contains($value, $subString, $message = '')` | Check that a string contains a substring +`notContains($value, $subString, $message = '')` | Check that a string does not contain a substring +`startsWith($value, $prefix, $message = '')` | Check that a string has a prefix +`notStartsWith($value, $prefix, $message = '')` | Check that a string does not have a prefix +`startsWithLetter($value, $message = '')` | Check that a string starts with a letter +`endsWith($value, $suffix, $message = '')` | Check that a string has a suffix +`notEndsWith($value, $suffix, $message = '')` | Check that a string does not have a suffix +`regex($value, $pattern, $message = '')` | Check that a string matches a regular expression +`notRegex($value, $pattern, $message = '')` | Check that a string does not match a regular expression +`unicodeLetters($value, $message = '')` | Check that a string contains Unicode letters only +`alpha($value, $message = '')` | Check that a string contains letters only +`digits($value, $message = '')` | Check that a string contains digits only +`alnum($value, $message = '')` | Check that a string contains letters and digits only +`lower($value, $message = '')` | Check that a string contains lowercase characters only +`upper($value, $message = '')` | Check that a string contains uppercase characters only +`length($value, $length, $message = '')` | Check that a string has a certain number of characters +`minLength($value, $min, $message = '')` | Check that a string has at least a certain number of characters +`maxLength($value, $max, $message = '')` | Check that a string has at most a certain number of characters +`lengthBetween($value, $min, $max, $message = '')` | Check that a string has a length in the given range +`uuid($value, $message = '')` | Check that a string is a valid UUID +`ip($value, $message = '')` | Check that a string is a valid IP (either IPv4 or IPv6) +`ipv4($value, $message = '')` | Check that a string is a valid IPv4 +`ipv6($value, $message = '')` | Check that a string is a valid IPv6 +`email($value, $message = '')` | Check that a string is a valid e-mail address +`notWhitespaceOnly($value, $message = '')` | Check that a string contains at least one non-whitespace character + +### File Assertions + +Method | Description +----------------------------------- | -------------------------------------------------- +`fileExists($value, $message = '')` | Check that a value is an existing path +`file($value, $message = '')` | Check that a value is an existing file +`directory($value, $message = '')` | Check that a value is an existing directory +`readable($value, $message = '')` | Check that a value is a readable path +`writable($value, $message = '')` | Check that a value is a writable path + +### Object Assertions + +Method | Description +----------------------------------------------------- | -------------------------------------------------- +`classExists($value, $message = '')` | Check that a value is an existing class name +`subclassOf($value, $class, $message = '')` | Check that a class is a subclass of another +`interfaceExists($value, $message = '')` | Check that a value is an existing interface name +`implementsInterface($value, $class, $message = '')` | Check that a class implements an interface +`propertyExists($value, $property, $message = '')` | Check that a property exists in a class/object +`propertyNotExists($value, $property, $message = '')` | Check that a property does not exist in a class/object +`methodExists($value, $method, $message = '')` | Check that a method exists in a class/object +`methodNotExists($value, $method, $message = '')` | Check that a method does not exist in a class/object + +### Array Assertions + +Method | Description +-------------------------------------------------- | ------------------------------------------------------------------ +`keyExists($array, $key, $message = '')` | Check that a key exists in an array +`keyNotExists($array, $key, $message = '')` | Check that a key does not exist in an array +`validArrayKey($key, $message = '')` | Check that a value is a valid array key (int or string) +`count($array, $number, $message = '')` | Check that an array contains a specific number of elements +`minCount($array, $min, $message = '')` | Check that an array contains at least a certain number of elements +`maxCount($array, $max, $message = '')` | Check that an array contains at most a certain number of elements +`countBetween($array, $min, $max, $message = '')` | Check that an array has a count in the given range +`isList($array, $message = '')` | Check that an array is a non-associative list +`isNonEmptyList($array, $message = '')` | Check that an array is a non-associative list, and not empty +`isMap($array, $message = '')` | Check that an array is associative and has strings as keys +`isNonEmptyMap($array, $message = '')` | Check that an array is associative and has strings as keys, and is not empty + +### Function Assertions + +Method | Description +------------------------------------------- | ----------------------------------------------------------------------------------------------------- +`throws($closure, $class, $message = '')` | Check that a function throws a certain exception. Subclasses of the exception class will be accepted. + +### Collection Assertions + +All of the above assertions can be prefixed with `all*()` to test the contents +of an array or a `\Traversable`: + +```php +Assert::allIsInstanceOf($employees, 'Acme\Employee'); +``` + +### Nullable Assertions + +All of the above assertions can be prefixed with `nullOr*()` to run the +assertion only if it the value is not `null`: + +```php +Assert::nullOrString($middleName, 'The middle name must be a string or null. Got: %s'); +``` + +### Extending Assert + +The `Assert` class comes with a few methods, which can be overridden to change the class behaviour. You can also extend it to +add your own assertions. + +#### Overriding methods + +Overriding the following methods in your assertion class allows you to change the behaviour of the assertions: + +* `public static function __callStatic($name, $arguments)` + * This method is used to 'create' the `nullOr` and `all` versions of the assertions. +* `protected static function valueToString($value)` + * This method is used for error messages, to convert the value to a string value for displaying. You could use this for representing a value object with a `__toString` method for example. +* `protected static function typeToString($value)` + * This method is used for error messages, to convert the a value to a string representing its type. +* `protected static function strlen($value)` + * This method is used to calculate string length for relevant methods, using the `mb_strlen` if available and useful. +* `protected static function reportInvalidArgument($message)` + * This method is called when an assertion fails, with the specified error message. Here you can throw your own exception, or log something. + +## Static analysis support + +Where applicable, assertion functions are annotated to support Psalm's +[Assertion syntax](https://psalm.dev/docs/annotating_code/assertion_syntax/). +A dedicated [PHPStan Plugin](https://github.com/phpstan/phpstan-webmozart-assert) is +required for proper type support. + +Authors +------- + +* [Bernhard Schussek] a.k.a. [@webmozart] +* [The Community Contributors] + +Contribute +---------- + +Contributions to the package are always welcome! + +* Report any bugs or issues you find on the [issue tracker]. +* You can grab the source code at the package's [Git repository]. + +License +------- + +All contents of this package are licensed under the [MIT license]. + +[beberlei/assert]: https://github.com/beberlei/assert +[assert package]: https://github.com/beberlei/assert +[Composer]: https://getcomposer.org +[Bernhard Schussek]: https://webmozarts.com +[The Community Contributors]: https://github.com/webmozart/assert/graphs/contributors +[issue tracker]: https://github.com/webmozart/assert/issues +[Git repository]: https://github.com/webmozart/assert +[@webmozart]: https://twitter.com/webmozart +[MIT license]: LICENSE +[`Assert`]: src/Assert.php diff --git a/lib/webmozart/assert/composer.json b/lib/webmozart/assert/composer.json new file mode 100644 index 0000000000..b340452c7f --- /dev/null +++ b/lib/webmozart/assert/composer.json @@ -0,0 +1,43 @@ +{ + "name": "webmozart/assert", + "description": "Assertions to validate method input/output with nice error messages.", + "license": "MIT", + "keywords": [ + "assert", + "check", + "validate" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "require": { + "php": "^7.2 || ^8.0", + "ext-ctype": "*" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Webmozart\\Assert\\Tests\\": "tests/", + "Webmozart\\Assert\\Bin\\": "bin/src" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + } +} diff --git a/lib/webmozart/assert/src/Assert.php b/lib/webmozart/assert/src/Assert.php new file mode 100644 index 0000000000..db1f3a51a3 --- /dev/null +++ b/lib/webmozart/assert/src/Assert.php @@ -0,0 +1,2080 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Webmozart\Assert; + +use ArrayAccess; +use BadMethodCallException; +use Closure; +use Countable; +use DateTime; +use DateTimeImmutable; +use Exception; +use ResourceBundle; +use SimpleXMLElement; +use Throwable; +use Traversable; + +/** + * Efficient assertions to validate the input/output of your methods. + * + * @since 1.0 + * + * @author Bernhard Schussek + */ +class Assert +{ + use Mixin; + + /** + * @psalm-pure + * @psalm-assert string $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function string($value, $message = '') + { + if (!\is_string($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a string. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert non-empty-string $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function stringNotEmpty($value, $message = '') + { + static::string($value, $message); + static::notEq($value, '', $message); + } + + /** + * @psalm-pure + * @psalm-assert int $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function integer($value, $message = '') + { + if (!\is_int($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an integer. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert numeric $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function integerish($value, $message = '') + { + if (!\is_numeric($value) || $value != (int) $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an integerish value. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert positive-int $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function positiveInteger($value, $message = '') + { + if (!(\is_int($value) && $value > 0)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a positive integer. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert float $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function float($value, $message = '') + { + if (!\is_float($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a float. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert numeric $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function numeric($value, $message = '') + { + if (!\is_numeric($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a numeric. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert positive-int|0 $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function natural($value, $message = '') + { + if (!\is_int($value) || $value < 0) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a non-negative integer. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert bool $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function boolean($value, $message = '') + { + if (!\is_bool($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a boolean. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert scalar $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function scalar($value, $message = '') + { + if (!\is_scalar($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a scalar. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert object $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function object($value, $message = '') + { + if (!\is_object($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an object. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert resource $value + * + * @param mixed $value + * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function resource($value, $type = null, $message = '') + { + if (!\is_resource($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a resource. Got: %s', + static::typeToString($value) + )); + } + + if ($type && $type !== \get_resource_type($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a resource of type %2$s. Got: %s', + static::typeToString($value), + $type + )); + } + } + + /** + * @psalm-pure + * @psalm-assert callable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isCallable($value, $message = '') + { + if (!\is_callable($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a callable. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert array $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isArray($value, $message = '') + { + if (!\is_array($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an array. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @deprecated use "isIterable" or "isInstanceOf" instead + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isTraversable($value, $message = '') + { + @\trigger_error( + \sprintf( + 'The "%s" assertion is deprecated. You should stop using it, as it will soon be removed in 2.0 version. Use "isIterable" or "isInstanceOf" instead.', + __METHOD__ + ), + \E_USER_DEPRECATED + ); + + if (!\is_array($value) && !($value instanceof Traversable)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a traversable. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert array|ArrayAccess $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isArrayAccessible($value, $message = '') + { + if (!\is_array($value) && !($value instanceof ArrayAccess)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an array accessible. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert countable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isCountable($value, $message = '') + { + if ( + !\is_array($value) + && !($value instanceof Countable) + && !($value instanceof ResourceBundle) + && !($value instanceof SimpleXMLElement) + ) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a countable. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isIterable($value, $message = '') + { + if (!\is_array($value) && !($value instanceof Traversable)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an iterable. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert ExpectedType $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isInstanceOf($value, $class, $message = '') + { + if (!($value instanceof $class)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an instance of %2$s. Got: %s', + static::typeToString($value), + $class + )); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert !ExpectedType $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notInstanceOf($value, $class, $message = '') + { + if ($value instanceof $class) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an instance other than %2$s. Got: %s', + static::typeToString($value), + $class + )); + } + } + + /** + * @psalm-pure + * @psalm-param array $classes + * + * @param mixed $value + * @param array $classes + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isInstanceOfAny($value, array $classes, $message = '') + { + foreach ($classes as $class) { + if ($value instanceof $class) { + return; + } + } + + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an instance of any of %2$s. Got: %s', + static::typeToString($value), + \implode(', ', \array_map(array(static::class, 'valueToString'), $classes)) + )); + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert ExpectedType|class-string $value + * + * @param object|string $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isAOf($value, $class, $message = '') + { + static::string($class, 'Expected class as a string. Got: %s'); + + if (!\is_a($value, $class, \is_string($value))) { + static::reportInvalidArgument(sprintf( + $message ?: 'Expected an instance of this class or to this class among its parents "%2$s". Got: %s', + static::valueToString($value), + $class + )); + } + } + + /** + * @psalm-pure + * @psalm-template UnexpectedType of object + * @psalm-param class-string $class + * @psalm-assert !UnexpectedType $value + * @psalm-assert !class-string $value + * + * @param object|string $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isNotA($value, $class, $message = '') + { + static::string($class, 'Expected class as a string. Got: %s'); + + if (\is_a($value, $class, \is_string($value))) { + static::reportInvalidArgument(sprintf( + $message ?: 'Expected an instance of this class or to this class among its parents other than "%2$s". Got: %s', + static::valueToString($value), + $class + )); + } + } + + /** + * @psalm-pure + * @psalm-param array $classes + * + * @param object|string $value + * @param string[] $classes + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isAnyOf($value, array $classes, $message = '') + { + foreach ($classes as $class) { + static::string($class, 'Expected class as a string. Got: %s'); + + if (\is_a($value, $class, \is_string($value))) { + return; + } + } + + static::reportInvalidArgument(sprintf( + $message ?: 'Expected an instance of any of this classes or any of those classes among their parents "%2$s". Got: %s', + static::valueToString($value), + \implode(', ', $classes) + )); + } + + /** + * @psalm-pure + * @psalm-assert empty $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isEmpty($value, $message = '') + { + if (!empty($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an empty value. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert !empty $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notEmpty($value, $message = '') + { + if (empty($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a non-empty value. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function null($value, $message = '') + { + if (null !== $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected null. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert !null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notNull($value, $message = '') + { + if (null === $value) { + static::reportInvalidArgument( + $message ?: 'Expected a value other than null.' + ); + } + } + + /** + * @psalm-pure + * @psalm-assert true $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function true($value, $message = '') + { + if (true !== $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to be true. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert false $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function false($value, $message = '') + { + if (false !== $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to be false. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert !false $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notFalse($value, $message = '') + { + if (false === $value) { + static::reportInvalidArgument( + $message ?: 'Expected a value other than false.' + ); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function ip($value, $message = '') + { + if (false === \filter_var($value, \FILTER_VALIDATE_IP)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to be an IP. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function ipv4($value, $message = '') + { + if (false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to be an IPv4. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function ipv6($value, $message = '') + { + if (false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to be an IPv6. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function email($value, $message = '') + { + if (false === \filter_var($value, FILTER_VALIDATE_EMAIL)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to be a valid e-mail address. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * Does non strict comparisons on the items, so ['3', 3] will not pass the assertion. + * + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function uniqueValues(array $values, $message = '') + { + $allValues = \count($values); + $uniqueValues = \count(\array_unique($values)); + + if ($allValues !== $uniqueValues) { + $difference = $allValues - $uniqueValues; + + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an array of unique values, but %s of them %s duplicated', + $difference, + (1 === $difference ? 'is' : 'are') + )); + } + } + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function eq($value, $expect, $message = '') + { + if ($expect != $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value equal to %2$s. Got: %s', + static::valueToString($value), + static::valueToString($expect) + )); + } + } + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notEq($value, $expect, $message = '') + { + if ($expect == $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a different value than %s.', + static::valueToString($expect) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function same($value, $expect, $message = '') + { + if ($expect !== $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value identical to %2$s. Got: %s', + static::valueToString($value), + static::valueToString($expect) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notSame($value, $expect, $message = '') + { + if ($expect === $value) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value not identical to %s.', + static::valueToString($expect) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function greaterThan($value, $limit, $message = '') + { + if ($value <= $limit) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value greater than %2$s. Got: %s', + static::valueToString($value), + static::valueToString($limit) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function greaterThanEq($value, $limit, $message = '') + { + if ($value < $limit) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value greater than or equal to %2$s. Got: %s', + static::valueToString($value), + static::valueToString($limit) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function lessThan($value, $limit, $message = '') + { + if ($value >= $limit) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value less than %2$s. Got: %s', + static::valueToString($value), + static::valueToString($limit) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function lessThanEq($value, $limit, $message = '') + { + if ($value > $limit) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value less than or equal to %2$s. Got: %s', + static::valueToString($value), + static::valueToString($limit) + )); + } + } + + /** + * Inclusive range, so Assert::(3, 3, 5) passes. + * + * @psalm-pure + * + * @param mixed $value + * @param mixed $min + * @param mixed $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function range($value, $min, $max, $message = '') + { + if ($value < $min || $value > $max) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value between %2$s and %3$s. Got: %s', + static::valueToString($value), + static::valueToString($min), + static::valueToString($max) + )); + } + } + + /** + * A more human-readable alias of Assert::inArray(). + * + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function oneOf($value, array $values, $message = '') + { + static::inArray($value, $values, $message); + } + + /** + * Does strict comparison, so Assert::inArray(3, ['3']) does not pass the assertion. + * + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function inArray($value, array $values, $message = '') + { + if (!\in_array($value, $values, true)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected one of: %2$s. Got: %s', + static::valueToString($value), + \implode(', ', \array_map(array(static::class, 'valueToString'), $values)) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function contains($value, $subString, $message = '') + { + if (false === \strpos($value, $subString)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain %2$s. Got: %s', + static::valueToString($value), + static::valueToString($subString) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notContains($value, $subString, $message = '') + { + if (false !== \strpos($value, $subString)) { + static::reportInvalidArgument(\sprintf( + $message ?: '%2$s was not expected to be contained in a value. Got: %s', + static::valueToString($value), + static::valueToString($subString) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notWhitespaceOnly($value, $message = '') + { + if (\preg_match('/^\s*$/', $value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a non-whitespace string. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function startsWith($value, $prefix, $message = '') + { + if (0 !== \strpos($value, $prefix)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to start with %2$s. Got: %s', + static::valueToString($value), + static::valueToString($prefix) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notStartsWith($value, $prefix, $message = '') + { + if (0 === \strpos($value, $prefix)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value not to start with %2$s. Got: %s', + static::valueToString($value), + static::valueToString($prefix) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function startsWithLetter($value, $message = '') + { + static::string($value); + + $valid = isset($value[0]); + + if ($valid) { + $locale = \setlocale(LC_CTYPE, 0); + \setlocale(LC_CTYPE, 'C'); + $valid = \ctype_alpha($value[0]); + \setlocale(LC_CTYPE, $locale); + } + + if (!$valid) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to start with a letter. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function endsWith($value, $suffix, $message = '') + { + if ($suffix !== \substr($value, -\strlen($suffix))) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to end with %2$s. Got: %s', + static::valueToString($value), + static::valueToString($suffix) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notEndsWith($value, $suffix, $message = '') + { + if ($suffix === \substr($value, -\strlen($suffix))) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value not to end with %2$s. Got: %s', + static::valueToString($value), + static::valueToString($suffix) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function regex($value, $pattern, $message = '') + { + if (!\preg_match($pattern, $value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The value %s does not match the expected pattern.', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function notRegex($value, $pattern, $message = '') + { + if (\preg_match($pattern, $value, $matches, PREG_OFFSET_CAPTURE)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The value %s matches the pattern %s (at offset %d).', + static::valueToString($value), + static::valueToString($pattern), + $matches[0][1] + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function unicodeLetters($value, $message = '') + { + static::string($value); + + if (!\preg_match('/^\p{L}+$/u', $value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain only Unicode letters. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function alpha($value, $message = '') + { + static::string($value); + + $locale = \setlocale(LC_CTYPE, 0); + \setlocale(LC_CTYPE, 'C'); + $valid = !\ctype_alpha($value); + \setlocale(LC_CTYPE, $locale); + + if ($valid) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain only letters. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function digits($value, $message = '') + { + $locale = \setlocale(LC_CTYPE, 0); + \setlocale(LC_CTYPE, 'C'); + $valid = !\ctype_digit($value); + \setlocale(LC_CTYPE, $locale); + + if ($valid) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain digits only. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function alnum($value, $message = '') + { + $locale = \setlocale(LC_CTYPE, 0); + \setlocale(LC_CTYPE, 'C'); + $valid = !\ctype_alnum($value); + \setlocale(LC_CTYPE, $locale); + + if ($valid) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain letters and digits only. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert lowercase-string $value + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function lower($value, $message = '') + { + $locale = \setlocale(LC_CTYPE, 0); + \setlocale(LC_CTYPE, 'C'); + $valid = !\ctype_lower($value); + \setlocale(LC_CTYPE, $locale); + + if ($valid) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain lowercase characters only. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-assert !lowercase-string $value + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function upper($value, $message = '') + { + $locale = \setlocale(LC_CTYPE, 0); + \setlocale(LC_CTYPE, 'C'); + $valid = !\ctype_upper($value); + \setlocale(LC_CTYPE, $locale); + + if ($valid) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain uppercase characters only. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * + * @param string $value + * @param int $length + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function length($value, $length, $message = '') + { + if ($length !== static::strlen($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain %2$s characters. Got: %s', + static::valueToString($value), + $length + )); + } + } + + /** + * Inclusive min. + * + * @psalm-pure + * + * @param string $value + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function minLength($value, $min, $message = '') + { + if (static::strlen($value) < $min) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain at least %2$s characters. Got: %s', + static::valueToString($value), + $min + )); + } + } + + /** + * Inclusive max. + * + * @psalm-pure + * + * @param string $value + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function maxLength($value, $max, $message = '') + { + if (static::strlen($value) > $max) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain at most %2$s characters. Got: %s', + static::valueToString($value), + $max + )); + } + } + + /** + * Inclusive , so Assert::lengthBetween('asd', 3, 5); passes the assertion. + * + * @psalm-pure + * + * @param string $value + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function lengthBetween($value, $min, $max, $message = '') + { + $length = static::strlen($value); + + if ($length < $min || $length > $max) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a value to contain between %2$s and %3$s characters. Got: %s', + static::valueToString($value), + $min, + $max + )); + } + } + + /** + * Will also pass if $value is a directory, use Assert::file() instead if you need to be sure it is a file. + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function fileExists($value, $message = '') + { + static::string($value); + + if (!\file_exists($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The file %s does not exist.', + static::valueToString($value) + )); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function file($value, $message = '') + { + static::fileExists($value, $message); + + if (!\is_file($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The path %s is not a file.', + static::valueToString($value) + )); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function directory($value, $message = '') + { + static::fileExists($value, $message); + + if (!\is_dir($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The path %s is no directory.', + static::valueToString($value) + )); + } + } + + /** + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function readable($value, $message = '') + { + if (!\is_readable($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The path %s is not readable.', + static::valueToString($value) + )); + } + } + + /** + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function writable($value, $message = '') + { + if (!\is_writable($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'The path %s is not writable.', + static::valueToString($value) + )); + } + } + + /** + * @psalm-assert class-string $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function classExists($value, $message = '') + { + if (!\class_exists($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an existing class name. Got: %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert class-string|ExpectedType $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function subclassOf($value, $class, $message = '') + { + if (!\is_subclass_of($value, $class)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected a sub-class of %2$s. Got: %s', + static::valueToString($value), + static::valueToString($class) + )); + } + } + + /** + * @psalm-assert class-string $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function interfaceExists($value, $message = '') + { + if (!\interface_exists($value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an existing interface name. got %s', + static::valueToString($value) + )); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $interface + * @psalm-assert class-string $value + * + * @param mixed $value + * @param mixed $interface + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function implementsInterface($value, $interface, $message = '') + { + if (!\in_array($interface, \class_implements($value))) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an implementation of %2$s. Got: %s', + static::valueToString($value), + static::valueToString($interface) + )); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object $classOrObject + * + * @param string|object $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function propertyExists($classOrObject, $property, $message = '') + { + if (!\property_exists($classOrObject, $property)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the property %s to exist.', + static::valueToString($property) + )); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object $classOrObject + * + * @param string|object $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function propertyNotExists($classOrObject, $property, $message = '') + { + if (\property_exists($classOrObject, $property)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the property %s to not exist.', + static::valueToString($property) + )); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object $classOrObject + * + * @param string|object $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function methodExists($classOrObject, $method, $message = '') + { + if (!(\is_string($classOrObject) || \is_object($classOrObject)) || !\method_exists($classOrObject, $method)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the method %s to exist.', + static::valueToString($method) + )); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object $classOrObject + * + * @param string|object $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function methodNotExists($classOrObject, $method, $message = '') + { + if ((\is_string($classOrObject) || \is_object($classOrObject)) && \method_exists($classOrObject, $method)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the method %s to not exist.', + static::valueToString($method) + )); + } + } + + /** + * @psalm-pure + * + * @param array $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function keyExists($array, $key, $message = '') + { + if (!(isset($array[$key]) || \array_key_exists($key, $array))) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the key %s to exist.', + static::valueToString($key) + )); + } + } + + /** + * @psalm-pure + * + * @param array $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function keyNotExists($array, $key, $message = '') + { + if (isset($array[$key]) || \array_key_exists($key, $array)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected the key %s to not exist.', + static::valueToString($key) + )); + } + } + + /** + * Checks if a value is a valid array key (int or string). + * + * @psalm-pure + * @psalm-assert array-key $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function validArrayKey($value, $message = '') + { + if (!(\is_int($value) || \is_string($value))) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected string or integer. Got: %s', + static::typeToString($value) + )); + } + } + + /** + * Does not check if $array is countable, this can generate a warning on php versions after 7.2. + * + * @param Countable|array $array + * @param int $number + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function count($array, $number, $message = '') + { + static::eq( + \count($array), + $number, + \sprintf( + $message ?: 'Expected an array to contain %d elements. Got: %d.', + $number, + \count($array) + ) + ); + } + + /** + * Does not check if $array is countable, this can generate a warning on php versions after 7.2. + * + * @param Countable|array $array + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function minCount($array, $min, $message = '') + { + if (\count($array) < $min) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an array to contain at least %2$d elements. Got: %d', + \count($array), + $min + )); + } + } + + /** + * Does not check if $array is countable, this can generate a warning on php versions after 7.2. + * + * @param Countable|array $array + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function maxCount($array, $max, $message = '') + { + if (\count($array) > $max) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an array to contain at most %2$d elements. Got: %d', + \count($array), + $max + )); + } + } + + /** + * Does not check if $array is countable, this can generate a warning on php versions after 7.2. + * + * @param Countable|array $array + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function countBetween($array, $min, $max, $message = '') + { + $count = \count($array); + + if ($count < $min || $count > $max) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Expected an array to contain between %2$d and %3$d elements. Got: %d', + $count, + $min, + $max + )); + } + } + + /** + * @psalm-pure + * @psalm-assert list $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isList($array, $message = '') + { + if (!\is_array($array)) { + static::reportInvalidArgument( + $message ?: 'Expected list - non-associative array.' + ); + } + + if ($array === \array_values($array)) { + return; + } + + $nextKey = -1; + foreach ($array as $k => $v) { + if ($k !== ++$nextKey) { + static::reportInvalidArgument( + $message ?: 'Expected list - non-associative array.' + ); + } + } + } + + /** + * @psalm-pure + * @psalm-assert non-empty-list $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isNonEmptyList($array, $message = '') + { + static::isList($array, $message); + static::notEmpty($array, $message); + } + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param mixed|array $array + * @psalm-assert array $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isMap($array, $message = '') + { + if ( + !\is_array($array) || + \array_keys($array) !== \array_filter(\array_keys($array), '\is_string') + ) { + static::reportInvalidArgument( + $message ?: 'Expected map - associative array with string keys.' + ); + } + } + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param mixed|array $array + * @psalm-assert array $array + * @psalm-assert !empty $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function isNonEmptyMap($array, $message = '') + { + static::isMap($array, $message); + static::notEmpty($array, $message); + } + + /** + * @psalm-pure + * + * @param string $value + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function uuid($value, $message = '') + { + $value = \str_replace(array('urn:', 'uuid:', '{', '}'), '', $value); + + // The nil UUID is special form of UUID that is specified to have all + // 128 bits set to zero. + if ('00000000-0000-0000-0000-000000000000' === $value) { + return; + } + + if (!\preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', $value)) { + static::reportInvalidArgument(\sprintf( + $message ?: 'Value %s is not a valid UUID.', + static::valueToString($value) + )); + } + } + + /** + * @psalm-param class-string $class + * + * @param Closure $expression + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + */ + public static function throws(Closure $expression, $class = 'Exception', $message = '') + { + static::string($class); + + $actual = 'none'; + + try { + $expression(); + } catch (Exception $e) { + $actual = \get_class($e); + if ($e instanceof $class) { + return; + } + } catch (Throwable $e) { + $actual = \get_class($e); + if ($e instanceof $class) { + return; + } + } + + static::reportInvalidArgument($message ?: \sprintf( + 'Expected to throw "%s", got "%s"', + $class, + $actual + )); + } + + /** + * @throws BadMethodCallException + */ + public static function __callStatic($name, $arguments) + { + if ('nullOr' === \substr($name, 0, 6)) { + if (null !== $arguments[0]) { + $method = \lcfirst(\substr($name, 6)); + \call_user_func_array(array(static::class, $method), $arguments); + } + + return; + } + + if ('all' === \substr($name, 0, 3)) { + static::isIterable($arguments[0]); + + $method = \lcfirst(\substr($name, 3)); + $args = $arguments; + + foreach ($arguments[0] as $entry) { + $args[0] = $entry; + + \call_user_func_array(array(static::class, $method), $args); + } + + return; + } + + throw new BadMethodCallException('No such method: '.$name); + } + + /** + * @param mixed $value + * + * @return string + */ + protected static function valueToString($value) + { + if (null === $value) { + return 'null'; + } + + if (true === $value) { + return 'true'; + } + + if (false === $value) { + return 'false'; + } + + if (\is_array($value)) { + return 'array'; + } + + if (\is_object($value)) { + if (\method_exists($value, '__toString')) { + return \get_class($value).': '.self::valueToString($value->__toString()); + } + + if ($value instanceof DateTime || $value instanceof DateTimeImmutable) { + return \get_class($value).': '.self::valueToString($value->format('c')); + } + + return \get_class($value); + } + + if (\is_resource($value)) { + return 'resource'; + } + + if (\is_string($value)) { + return '"'.$value.'"'; + } + + return (string) $value; + } + + /** + * @param mixed $value + * + * @return string + */ + protected static function typeToString($value) + { + return \is_object($value) ? \get_class($value) : \gettype($value); + } + + protected static function strlen($value) + { + if (!\function_exists('mb_detect_encoding')) { + return \strlen($value); + } + + if (false === $encoding = \mb_detect_encoding($value)) { + return \strlen($value); + } + + return \mb_strlen($value, $encoding); + } + + /** + * @param string $message + * + * @throws InvalidArgumentException + * + * @psalm-pure this method is not supposed to perform side-effects + * @psalm-return never + */ + protected static function reportInvalidArgument($message) + { + throw new InvalidArgumentException($message); + } + + private function __construct() + { + } +} diff --git a/lib/webmozart/assert/src/InvalidArgumentException.php b/lib/webmozart/assert/src/InvalidArgumentException.php new file mode 100644 index 0000000000..9d95a58c50 --- /dev/null +++ b/lib/webmozart/assert/src/InvalidArgumentException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Webmozart\Assert; + +class InvalidArgumentException extends \InvalidArgumentException +{ +} diff --git a/lib/webmozart/assert/src/Mixin.php b/lib/webmozart/assert/src/Mixin.php new file mode 100644 index 0000000000..0f0a75e330 --- /dev/null +++ b/lib/webmozart/assert/src/Mixin.php @@ -0,0 +1,5089 @@ + $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allString($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::string($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrString($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::string($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert non-empty-string|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrStringNotEmpty($value, $message = '') + { + null === $value || static::stringNotEmpty($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allStringNotEmpty($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::stringNotEmpty($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrStringNotEmpty($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::stringNotEmpty($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert int|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrInteger($value, $message = '') + { + null === $value || static::integer($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allInteger($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::integer($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrInteger($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::integer($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert numeric|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIntegerish($value, $message = '') + { + null === $value || static::integerish($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIntegerish($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::integerish($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIntegerish($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::integerish($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert positive-int|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrPositiveInteger($value, $message = '') + { + null === $value || static::positiveInteger($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allPositiveInteger($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::positiveInteger($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrPositiveInteger($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::positiveInteger($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert float|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrFloat($value, $message = '') + { + null === $value || static::float($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allFloat($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::float($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrFloat($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::float($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert numeric|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNumeric($value, $message = '') + { + null === $value || static::numeric($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNumeric($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::numeric($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNumeric($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::numeric($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert positive-int|0|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNatural($value, $message = '') + { + null === $value || static::natural($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNatural($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::natural($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNatural($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::natural($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert bool|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrBoolean($value, $message = '') + { + null === $value || static::boolean($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allBoolean($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::boolean($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrBoolean($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::boolean($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert scalar|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrScalar($value, $message = '') + { + null === $value || static::scalar($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allScalar($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::scalar($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrScalar($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::scalar($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert object|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrObject($value, $message = '') + { + null === $value || static::object($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allObject($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::object($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrObject($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::object($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert resource|null $value + * + * @param mixed $value + * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrResource($value, $type = null, $message = '') + { + null === $value || static::resource($value, $type, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allResource($value, $type = null, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::resource($entry, $type, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrResource($value, $type = null, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::resource($entry, $type, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert callable|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsCallable($value, $message = '') + { + null === $value || static::isCallable($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsCallable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isCallable($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsCallable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isCallable($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert array|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsArray($value, $message = '') + { + null === $value || static::isArray($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsArray($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isArray($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsArray($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isArray($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable|null $value + * + * @deprecated use "isIterable" or "isInstanceOf" instead + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsTraversable($value, $message = '') + { + null === $value || static::isTraversable($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @deprecated use "isIterable" or "isInstanceOf" instead + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsTraversable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isTraversable($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @deprecated use "isIterable" or "isInstanceOf" instead + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsTraversable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isTraversable($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert array|ArrayAccess|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsArrayAccessible($value, $message = '') + { + null === $value || static::isArrayAccessible($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsArrayAccessible($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isArrayAccessible($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsArrayAccessible($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isArrayAccessible($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert countable|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsCountable($value, $message = '') + { + null === $value || static::isCountable($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsCountable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isCountable($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsCountable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isCountable($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsIterable($value, $message = '') + { + null === $value || static::isIterable($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsIterable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isIterable($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsIterable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isIterable($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert ExpectedType|null $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsInstanceOf($value, $class, $message = '') + { + null === $value || static::isInstanceOf($value, $class, $message); + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsInstanceOf($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isInstanceOf($entry, $class, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsInstanceOf($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isInstanceOf($entry, $class, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotInstanceOf($value, $class, $message = '') + { + null === $value || static::notInstanceOf($value, $class, $message); + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotInstanceOf($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notInstanceOf($entry, $class, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotInstanceOf($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notInstanceOf($entry, $class, $message); + } + } + + /** + * @psalm-pure + * @psalm-param array $classes + * + * @param mixed $value + * @param array $classes + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsInstanceOfAny($value, $classes, $message = '') + { + null === $value || static::isInstanceOfAny($value, $classes, $message); + } + + /** + * @psalm-pure + * @psalm-param array $classes + * + * @param mixed $value + * @param array $classes + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsInstanceOfAny($value, $classes, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isInstanceOfAny($entry, $classes, $message); + } + } + + /** + * @psalm-pure + * @psalm-param array $classes + * + * @param mixed $value + * @param array $classes + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsInstanceOfAny($value, $classes, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isInstanceOfAny($entry, $classes, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert ExpectedType|class-string|null $value + * + * @param object|string|null $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsAOf($value, $class, $message = '') + { + null === $value || static::isAOf($value, $class, $message); + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert iterable> $value + * + * @param iterable $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsAOf($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isAOf($entry, $class, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert iterable|null> $value + * + * @param iterable $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsAOf($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isAOf($entry, $class, $message); + } + } + + /** + * @psalm-pure + * @psalm-template UnexpectedType of object + * @psalm-param class-string $class + * + * @param object|string|null $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsNotA($value, $class, $message = '') + { + null === $value || static::isNotA($value, $class, $message); + } + + /** + * @psalm-pure + * @psalm-template UnexpectedType of object + * @psalm-param class-string $class + * + * @param iterable $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsNotA($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isNotA($entry, $class, $message); + } + } + + /** + * @psalm-pure + * @psalm-template UnexpectedType of object + * @psalm-param class-string $class + * @psalm-assert iterable $value + * @psalm-assert iterable|null> $value + * + * @param iterable $value + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsNotA($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isNotA($entry, $class, $message); + } + } + + /** + * @psalm-pure + * @psalm-param array $classes + * + * @param object|string|null $value + * @param string[] $classes + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsAnyOf($value, $classes, $message = '') + { + null === $value || static::isAnyOf($value, $classes, $message); + } + + /** + * @psalm-pure + * @psalm-param array $classes + * + * @param iterable $value + * @param string[] $classes + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsAnyOf($value, $classes, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isAnyOf($entry, $classes, $message); + } + } + + /** + * @psalm-pure + * @psalm-param array $classes + * + * @param iterable $value + * @param string[] $classes + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsAnyOf($value, $classes, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isAnyOf($entry, $classes, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert empty $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsEmpty($value, $message = '') + { + null === $value || static::isEmpty($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsEmpty($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::isEmpty($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsEmpty($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::isEmpty($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotEmpty($value, $message = '') + { + null === $value || static::notEmpty($value, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotEmpty($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notEmpty($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotEmpty($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notEmpty($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNull($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::null($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotNull($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notNull($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert true|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrTrue($value, $message = '') + { + null === $value || static::true($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allTrue($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::true($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrTrue($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::true($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert false|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrFalse($value, $message = '') + { + null === $value || static::false($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allFalse($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::false($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrFalse($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::false($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotFalse($value, $message = '') + { + null === $value || static::notFalse($value, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotFalse($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notFalse($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotFalse($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notFalse($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIp($value, $message = '') + { + null === $value || static::ip($value, $message); + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIp($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::ip($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIp($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::ip($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIpv4($value, $message = '') + { + null === $value || static::ipv4($value, $message); + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIpv4($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::ipv4($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIpv4($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::ipv4($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIpv6($value, $message = '') + { + null === $value || static::ipv6($value, $message); + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIpv6($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::ipv6($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIpv6($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::ipv6($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrEmail($value, $message = '') + { + null === $value || static::email($value, $message); + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allEmail($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::email($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrEmail($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::email($entry, $message); + } + } + + /** + * @param array|null $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrUniqueValues($values, $message = '') + { + null === $values || static::uniqueValues($values, $message); + } + + /** + * @param iterable $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allUniqueValues($values, $message = '') + { + static::isIterable($values); + + foreach ($values as $entry) { + static::uniqueValues($entry, $message); + } + } + + /** + * @param iterable $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrUniqueValues($values, $message = '') + { + static::isIterable($values); + + foreach ($values as $entry) { + null === $entry || static::uniqueValues($entry, $message); + } + } + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrEq($value, $expect, $message = '') + { + null === $value || static::eq($value, $expect, $message); + } + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allEq($value, $expect, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::eq($entry, $expect, $message); + } + } + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrEq($value, $expect, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::eq($entry, $expect, $message); + } + } + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotEq($value, $expect, $message = '') + { + null === $value || static::notEq($value, $expect, $message); + } + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotEq($value, $expect, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notEq($entry, $expect, $message); + } + } + + /** + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotEq($value, $expect, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notEq($entry, $expect, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrSame($value, $expect, $message = '') + { + null === $value || static::same($value, $expect, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allSame($value, $expect, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::same($entry, $expect, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrSame($value, $expect, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::same($entry, $expect, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotSame($value, $expect, $message = '') + { + null === $value || static::notSame($value, $expect, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotSame($value, $expect, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notSame($entry, $expect, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $expect + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotSame($value, $expect, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notSame($entry, $expect, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrGreaterThan($value, $limit, $message = '') + { + null === $value || static::greaterThan($value, $limit, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allGreaterThan($value, $limit, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::greaterThan($entry, $limit, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrGreaterThan($value, $limit, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::greaterThan($entry, $limit, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrGreaterThanEq($value, $limit, $message = '') + { + null === $value || static::greaterThanEq($value, $limit, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allGreaterThanEq($value, $limit, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::greaterThanEq($entry, $limit, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrGreaterThanEq($value, $limit, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::greaterThanEq($entry, $limit, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrLessThan($value, $limit, $message = '') + { + null === $value || static::lessThan($value, $limit, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allLessThan($value, $limit, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::lessThan($entry, $limit, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrLessThan($value, $limit, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::lessThan($entry, $limit, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrLessThanEq($value, $limit, $message = '') + { + null === $value || static::lessThanEq($value, $limit, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allLessThanEq($value, $limit, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::lessThanEq($entry, $limit, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $limit + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrLessThanEq($value, $limit, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::lessThanEq($entry, $limit, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $min + * @param mixed $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrRange($value, $min, $max, $message = '') + { + null === $value || static::range($value, $min, $max, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $min + * @param mixed $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allRange($value, $min, $max, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::range($entry, $min, $max, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param mixed $min + * @param mixed $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrRange($value, $min, $max, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::range($entry, $min, $max, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrOneOf($value, $values, $message = '') + { + null === $value || static::oneOf($value, $values, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allOneOf($value, $values, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::oneOf($entry, $values, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrOneOf($value, $values, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::oneOf($entry, $values, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrInArray($value, $values, $message = '') + { + null === $value || static::inArray($value, $values, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allInArray($value, $values, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::inArray($entry, $values, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param array $values + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrInArray($value, $values, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::inArray($entry, $values, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrContains($value, $subString, $message = '') + { + null === $value || static::contains($value, $subString, $message); + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allContains($value, $subString, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::contains($entry, $subString, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrContains($value, $subString, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::contains($entry, $subString, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotContains($value, $subString, $message = '') + { + null === $value || static::notContains($value, $subString, $message); + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotContains($value, $subString, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notContains($entry, $subString, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $subString + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotContains($value, $subString, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notContains($entry, $subString, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotWhitespaceOnly($value, $message = '') + { + null === $value || static::notWhitespaceOnly($value, $message); + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotWhitespaceOnly($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notWhitespaceOnly($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotWhitespaceOnly($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notWhitespaceOnly($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrStartsWith($value, $prefix, $message = '') + { + null === $value || static::startsWith($value, $prefix, $message); + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allStartsWith($value, $prefix, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::startsWith($entry, $prefix, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrStartsWith($value, $prefix, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::startsWith($entry, $prefix, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotStartsWith($value, $prefix, $message = '') + { + null === $value || static::notStartsWith($value, $prefix, $message); + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotStartsWith($value, $prefix, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notStartsWith($entry, $prefix, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $prefix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotStartsWith($value, $prefix, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notStartsWith($entry, $prefix, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrStartsWithLetter($value, $message = '') + { + null === $value || static::startsWithLetter($value, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allStartsWithLetter($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::startsWithLetter($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrStartsWithLetter($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::startsWithLetter($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrEndsWith($value, $suffix, $message = '') + { + null === $value || static::endsWith($value, $suffix, $message); + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allEndsWith($value, $suffix, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::endsWith($entry, $suffix, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrEndsWith($value, $suffix, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::endsWith($entry, $suffix, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotEndsWith($value, $suffix, $message = '') + { + null === $value || static::notEndsWith($value, $suffix, $message); + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotEndsWith($value, $suffix, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notEndsWith($entry, $suffix, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $suffix + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotEndsWith($value, $suffix, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notEndsWith($entry, $suffix, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrRegex($value, $pattern, $message = '') + { + null === $value || static::regex($value, $pattern, $message); + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allRegex($value, $pattern, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::regex($entry, $pattern, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrRegex($value, $pattern, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::regex($entry, $pattern, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrNotRegex($value, $pattern, $message = '') + { + null === $value || static::notRegex($value, $pattern, $message); + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNotRegex($value, $pattern, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::notRegex($entry, $pattern, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $pattern + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrNotRegex($value, $pattern, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::notRegex($entry, $pattern, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrUnicodeLetters($value, $message = '') + { + null === $value || static::unicodeLetters($value, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allUnicodeLetters($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::unicodeLetters($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrUnicodeLetters($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::unicodeLetters($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrAlpha($value, $message = '') + { + null === $value || static::alpha($value, $message); + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allAlpha($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::alpha($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrAlpha($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::alpha($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrDigits($value, $message = '') + { + null === $value || static::digits($value, $message); + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allDigits($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::digits($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrDigits($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::digits($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrAlnum($value, $message = '') + { + null === $value || static::alnum($value, $message); + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allAlnum($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::alnum($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrAlnum($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::alnum($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert lowercase-string|null $value + * + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrLower($value, $message = '') + { + null === $value || static::lower($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allLower($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::lower($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrLower($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::lower($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrUpper($value, $message = '') + { + null === $value || static::upper($value, $message); + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allUpper($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::upper($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrUpper($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::upper($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param int $length + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrLength($value, $length, $message = '') + { + null === $value || static::length($value, $length, $message); + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param int $length + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allLength($value, $length, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::length($entry, $length, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param int $length + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrLength($value, $length, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::length($entry, $length, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrMinLength($value, $min, $message = '') + { + null === $value || static::minLength($value, $min, $message); + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allMinLength($value, $min, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::minLength($entry, $min, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrMinLength($value, $min, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::minLength($entry, $min, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrMaxLength($value, $max, $message = '') + { + null === $value || static::maxLength($value, $max, $message); + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allMaxLength($value, $max, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::maxLength($entry, $max, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrMaxLength($value, $max, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::maxLength($entry, $max, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrLengthBetween($value, $min, $max, $message = '') + { + null === $value || static::lengthBetween($value, $min, $max, $message); + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allLengthBetween($value, $min, $max, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::lengthBetween($entry, $min, $max, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrLengthBetween($value, $min, $max, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::lengthBetween($entry, $min, $max, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrFileExists($value, $message = '') + { + null === $value || static::fileExists($value, $message); + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allFileExists($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::fileExists($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrFileExists($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::fileExists($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrFile($value, $message = '') + { + null === $value || static::file($value, $message); + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allFile($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::file($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrFile($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::file($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrDirectory($value, $message = '') + { + null === $value || static::directory($value, $message); + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allDirectory($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::directory($entry, $message); + } + } + + /** + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrDirectory($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::directory($entry, $message); + } + } + + /** + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrReadable($value, $message = '') + { + null === $value || static::readable($value, $message); + } + + /** + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allReadable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::readable($entry, $message); + } + } + + /** + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrReadable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::readable($entry, $message); + } + } + + /** + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrWritable($value, $message = '') + { + null === $value || static::writable($value, $message); + } + + /** + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allWritable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::writable($entry, $message); + } + } + + /** + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrWritable($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::writable($entry, $message); + } + } + + /** + * @psalm-assert class-string|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrClassExists($value, $message = '') + { + null === $value || static::classExists($value, $message); + } + + /** + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allClassExists($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::classExists($entry, $message); + } + } + + /** + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrClassExists($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::classExists($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert class-string|ExpectedType|null $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrSubclassOf($value, $class, $message = '') + { + null === $value || static::subclassOf($value, $class, $message); + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert iterable|ExpectedType> $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allSubclassOf($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::subclassOf($entry, $class, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $class + * @psalm-assert iterable|ExpectedType|null> $value + * + * @param mixed $value + * @param string|object $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrSubclassOf($value, $class, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::subclassOf($entry, $class, $message); + } + } + + /** + * @psalm-assert class-string|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrInterfaceExists($value, $message = '') + { + null === $value || static::interfaceExists($value, $message); + } + + /** + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allInterfaceExists($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::interfaceExists($entry, $message); + } + } + + /** + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrInterfaceExists($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::interfaceExists($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $interface + * @psalm-assert class-string|null $value + * + * @param mixed $value + * @param mixed $interface + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrImplementsInterface($value, $interface, $message = '') + { + null === $value || static::implementsInterface($value, $interface, $message); + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $interface + * @psalm-assert iterable> $value + * + * @param mixed $value + * @param mixed $interface + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allImplementsInterface($value, $interface, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::implementsInterface($entry, $interface, $message); + } + } + + /** + * @psalm-pure + * @psalm-template ExpectedType of object + * @psalm-param class-string $interface + * @psalm-assert iterable|null> $value + * + * @param mixed $value + * @param mixed $interface + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrImplementsInterface($value, $interface, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::implementsInterface($entry, $interface, $message); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object|null $classOrObject + * + * @param string|object|null $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrPropertyExists($classOrObject, $property, $message = '') + { + null === $classOrObject || static::propertyExists($classOrObject, $property, $message); + } + + /** + * @psalm-pure + * @psalm-param iterable $classOrObject + * + * @param iterable $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allPropertyExists($classOrObject, $property, $message = '') + { + static::isIterable($classOrObject); + + foreach ($classOrObject as $entry) { + static::propertyExists($entry, $property, $message); + } + } + + /** + * @psalm-pure + * @psalm-param iterable $classOrObject + * + * @param iterable $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrPropertyExists($classOrObject, $property, $message = '') + { + static::isIterable($classOrObject); + + foreach ($classOrObject as $entry) { + null === $entry || static::propertyExists($entry, $property, $message); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object|null $classOrObject + * + * @param string|object|null $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrPropertyNotExists($classOrObject, $property, $message = '') + { + null === $classOrObject || static::propertyNotExists($classOrObject, $property, $message); + } + + /** + * @psalm-pure + * @psalm-param iterable $classOrObject + * + * @param iterable $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allPropertyNotExists($classOrObject, $property, $message = '') + { + static::isIterable($classOrObject); + + foreach ($classOrObject as $entry) { + static::propertyNotExists($entry, $property, $message); + } + } + + /** + * @psalm-pure + * @psalm-param iterable $classOrObject + * + * @param iterable $classOrObject + * @param mixed $property + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrPropertyNotExists($classOrObject, $property, $message = '') + { + static::isIterable($classOrObject); + + foreach ($classOrObject as $entry) { + null === $entry || static::propertyNotExists($entry, $property, $message); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object|null $classOrObject + * + * @param string|object|null $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrMethodExists($classOrObject, $method, $message = '') + { + null === $classOrObject || static::methodExists($classOrObject, $method, $message); + } + + /** + * @psalm-pure + * @psalm-param iterable $classOrObject + * + * @param iterable $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allMethodExists($classOrObject, $method, $message = '') + { + static::isIterable($classOrObject); + + foreach ($classOrObject as $entry) { + static::methodExists($entry, $method, $message); + } + } + + /** + * @psalm-pure + * @psalm-param iterable $classOrObject + * + * @param iterable $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrMethodExists($classOrObject, $method, $message = '') + { + static::isIterable($classOrObject); + + foreach ($classOrObject as $entry) { + null === $entry || static::methodExists($entry, $method, $message); + } + } + + /** + * @psalm-pure + * @psalm-param class-string|object|null $classOrObject + * + * @param string|object|null $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrMethodNotExists($classOrObject, $method, $message = '') + { + null === $classOrObject || static::methodNotExists($classOrObject, $method, $message); + } + + /** + * @psalm-pure + * @psalm-param iterable $classOrObject + * + * @param iterable $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allMethodNotExists($classOrObject, $method, $message = '') + { + static::isIterable($classOrObject); + + foreach ($classOrObject as $entry) { + static::methodNotExists($entry, $method, $message); + } + } + + /** + * @psalm-pure + * @psalm-param iterable $classOrObject + * + * @param iterable $classOrObject + * @param mixed $method + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrMethodNotExists($classOrObject, $method, $message = '') + { + static::isIterable($classOrObject); + + foreach ($classOrObject as $entry) { + null === $entry || static::methodNotExists($entry, $method, $message); + } + } + + /** + * @psalm-pure + * + * @param array|null $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrKeyExists($array, $key, $message = '') + { + null === $array || static::keyExists($array, $key, $message); + } + + /** + * @psalm-pure + * + * @param iterable $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allKeyExists($array, $key, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::keyExists($entry, $key, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrKeyExists($array, $key, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::keyExists($entry, $key, $message); + } + } + + /** + * @psalm-pure + * + * @param array|null $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrKeyNotExists($array, $key, $message = '') + { + null === $array || static::keyNotExists($array, $key, $message); + } + + /** + * @psalm-pure + * + * @param iterable $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allKeyNotExists($array, $key, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::keyNotExists($entry, $key, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable $array + * @param string|int $key + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrKeyNotExists($array, $key, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::keyNotExists($entry, $key, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert array-key|null $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrValidArrayKey($value, $message = '') + { + null === $value || static::validArrayKey($value, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allValidArrayKey($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::validArrayKey($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $value + * + * @param mixed $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrValidArrayKey($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::validArrayKey($entry, $message); + } + } + + /** + * @param Countable|array|null $array + * @param int $number + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrCount($array, $number, $message = '') + { + null === $array || static::count($array, $number, $message); + } + + /** + * @param iterable $array + * @param int $number + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allCount($array, $number, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::count($entry, $number, $message); + } + } + + /** + * @param iterable $array + * @param int $number + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrCount($array, $number, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::count($entry, $number, $message); + } + } + + /** + * @param Countable|array|null $array + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrMinCount($array, $min, $message = '') + { + null === $array || static::minCount($array, $min, $message); + } + + /** + * @param iterable $array + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allMinCount($array, $min, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::minCount($entry, $min, $message); + } + } + + /** + * @param iterable $array + * @param int|float $min + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrMinCount($array, $min, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::minCount($entry, $min, $message); + } + } + + /** + * @param Countable|array|null $array + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrMaxCount($array, $max, $message = '') + { + null === $array || static::maxCount($array, $max, $message); + } + + /** + * @param iterable $array + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allMaxCount($array, $max, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::maxCount($entry, $max, $message); + } + } + + /** + * @param iterable $array + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrMaxCount($array, $max, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::maxCount($entry, $max, $message); + } + } + + /** + * @param Countable|array|null $array + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrCountBetween($array, $min, $max, $message = '') + { + null === $array || static::countBetween($array, $min, $max, $message); + } + + /** + * @param iterable $array + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allCountBetween($array, $min, $max, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::countBetween($entry, $min, $max, $message); + } + } + + /** + * @param iterable $array + * @param int|float $min + * @param int|float $max + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrCountBetween($array, $min, $max, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::countBetween($entry, $min, $max, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert list|null $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsList($array, $message = '') + { + null === $array || static::isList($array, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsList($array, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::isList($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsList($array, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::isList($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert non-empty-list|null $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsNonEmptyList($array, $message = '') + { + null === $array || static::isNonEmptyList($array, $message); + } + + /** + * @psalm-pure + * @psalm-assert iterable $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsNonEmptyList($array, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::isNonEmptyList($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-assert iterable $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsNonEmptyList($array, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::isNonEmptyList($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param mixed|array|null $array + * @psalm-assert array|null $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsMap($array, $message = '') + { + null === $array || static::isMap($array, $message); + } + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param iterable> $array + * @psalm-assert iterable> $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsMap($array, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::isMap($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param iterable|null> $array + * @psalm-assert iterable|null> $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsMap($array, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::isMap($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param mixed|array|null $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrIsNonEmptyMap($array, $message = '') + { + null === $array || static::isNonEmptyMap($array, $message); + } + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param iterable> $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allIsNonEmptyMap($array, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + static::isNonEmptyMap($entry, $message); + } + } + + /** + * @psalm-pure + * @psalm-template T + * @psalm-param iterable|null> $array + * @psalm-assert iterable|null> $array + * @psalm-assert iterable $array + * + * @param mixed $array + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrIsNonEmptyMap($array, $message = '') + { + static::isIterable($array); + + foreach ($array as $entry) { + null === $entry || static::isNonEmptyMap($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param string|null $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrUuid($value, $message = '') + { + null === $value || static::uuid($value, $message); + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allUuid($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + static::uuid($entry, $message); + } + } + + /** + * @psalm-pure + * + * @param iterable $value + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrUuid($value, $message = '') + { + static::isIterable($value); + + foreach ($value as $entry) { + null === $entry || static::uuid($entry, $message); + } + } + + /** + * @psalm-param class-string $class + * + * @param Closure|null $expression + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function nullOrThrows($expression, $class = 'Exception', $message = '') + { + null === $expression || static::throws($expression, $class, $message); + } + + /** + * @psalm-param class-string $class + * + * @param iterable $expression + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allThrows($expression, $class = 'Exception', $message = '') + { + static::isIterable($expression); + + foreach ($expression as $entry) { + static::throws($entry, $class, $message); + } + } + + /** + * @psalm-param class-string $class + * + * @param iterable $expression + * @param string $class + * @param string $message + * + * @throws InvalidArgumentException + * + * @return void + */ + public static function allNullOrThrows($expression, $class = 'Exception', $message = '') + { + static::isIterable($expression); + + foreach ($expression as $entry) { + null === $entry || static::throws($entry, $class, $message); + } + } +} diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 2077d0444d..513ea26597 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -17,8 +17,9 @@ } }, "node_modules/bulma-scss": { - "version": "0.9.0-1", - "license": "MIT" + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/bulma-scss/-/bulma-scss-0.9.4.tgz", + "integrity": "sha512-iMrNFlR6yEMutYEAi1VseAmsadmmWnb8A2DghUR7jZKJLaw6AibmHOkqwlEPjsXkqJ7Pp1z/bEJ6gFMG0WNdJw==" }, "node_modules/datatables.net": { "version": "1.11.3", diff --git a/node_modules/bulma-scss/LICENSE b/node_modules/bulma-scss/LICENSE index b60e6eb134..dc1a267c52 100644 --- a/node_modules/bulma-scss/LICENSE +++ b/node_modules/bulma-scss/LICENSE @@ -1,7 +1,7 @@ The MIT License (MIT) -Copyright (c) 2020 Jeremy Thomas -Copyright (c) 2020 Jim Campbell +Copyright (c) 2021 Jeremy Thomas +Copyright (c) 2021 Jim Campbell 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/node_modules/bulma-scss/README.md b/node_modules/bulma-scss/README.md index 859e627ff8..d99cdc4f13 100644 --- a/node_modules/bulma-scss/README.md +++ b/node_modules/bulma-scss/README.md @@ -3,7 +3,7 @@ This is a port of the [Bulma](https://bulma.io/) project SASS files to the SCSS syntax. -Currently, these files are based on Bulma version 0.9.0, and will be updated +Currently, these files are based on Bulma version 0.9.4, and will be updated with later releases. The files are converted to SCSS with [this script](https://gist.github.com/j1mc/ff1ff83e277b1e221761fc0c0ee3b164). diff --git a/node_modules/bulma-scss/base/_all.scss b/node_modules/bulma-scss/base/_all.scss index 6b8ca73fb3..19af71017e 100644 --- a/node_modules/bulma-scss/base/_all.scss +++ b/node_modules/bulma-scss/base/_all.scss @@ -1,2 +1,4 @@ +/* Bulma Base */ @import "minireset"; @import "generic"; +@import "animations"; diff --git a/node_modules/bulma-scss/base/_animations.scss b/node_modules/bulma-scss/base/_animations.scss new file mode 100644 index 0000000000..4849c5a4cf --- /dev/null +++ b/node_modules/bulma-scss/base/_animations.scss @@ -0,0 +1,9 @@ +@keyframes spinAround { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(359deg); + } +} diff --git a/node_modules/bulma-scss/base/_generic.scss b/node_modules/bulma-scss/base/_generic.scss index a48619d9fc..38c53848db 100644 --- a/node_modules/bulma-scss/base/_generic.scss +++ b/node_modules/bulma-scss/base/_generic.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + $body-background-color: $scheme-main !default; $body-size: 16px !default; $body-min-width: 300px !default; @@ -54,6 +56,7 @@ section { body, button, input, +optgroup, select, textarea { font-family: $body-family; diff --git a/node_modules/bulma-scss/bulma.scss b/node_modules/bulma-scss/bulma.scss index 14ae29a447..03f42b07e2 100644 --- a/node_modules/bulma-scss/bulma.scss +++ b/node_modules/bulma-scss/bulma.scss @@ -1,4 +1,4 @@ -/*! bulma.io v0.9.0 | MIT License | github.com/jgthms/bulma */ +/*! bulma.io v0.9.4 | MIT License | github.com/jgthms/bulma */ @import "utilities/_all"; @import "base/_all"; @import "elements/_all"; diff --git a/node_modules/bulma-scss/components/_all.scss b/node_modules/bulma-scss/components/_all.scss index 049cd91b87..31bd36d936 100644 --- a/node_modules/bulma-scss/components/_all.scss +++ b/node_modules/bulma-scss/components/_all.scss @@ -1,3 +1,4 @@ +/* Bulma Components */ @import "breadcrumb"; @import "card"; @import "dropdown"; diff --git a/node_modules/bulma-scss/components/_breadcrumb.scss b/node_modules/bulma-scss/components/_breadcrumb.scss index 3435f0ca38..e2493324e8 100644 --- a/node_modules/bulma-scss/components/_breadcrumb.scss +++ b/node_modules/bulma-scss/components/_breadcrumb.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + $breadcrumb-item-color: $link !default; $breadcrumb-item-hover-color: $link-hover !default; $breadcrumb-item-active-color: $text-strong !default; diff --git a/node_modules/bulma-scss/components/_card.scss b/node_modules/bulma-scss/components/_card.scss index bd68232123..45f491b72d 100644 --- a/node_modules/bulma-scss/components/_card.scss +++ b/node_modules/bulma-scss/components/_card.scss @@ -1,6 +1,9 @@ +@import "../utilities/mixins"; + $card-color: $text !default; $card-background-color: $scheme-main !default; -$card-shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0px 0 1px rgba($scheme-invert, 0.02) !default; +$card-shadow: $shadow !default; +$card-radius: 0.25rem !default; $card-header-background-color: transparent !default; $card-header-color: $text-strong !default; @@ -19,13 +22,28 @@ $card-media-margin: $block-spacing !default; .card { background-color: $card-background-color; + border-radius: $card-radius; box-shadow: $card-shadow; color: $card-color; max-width: 100%; position: relative; } +%card-item { + &:first-child { + border-top-left-radius: $card-radius; + border-top-right-radius: $card-radius; + } + + &:last-child { + border-bottom-left-radius: $card-radius; + border-bottom-right-radius: $card-radius; + } +} + .card-header { + @extend %card-item; + background-color: $card-header-background-color; align-items: stretch; box-shadow: $card-header-shadow; @@ -46,6 +64,8 @@ $card-media-margin: $block-spacing !default; } .card-header-icon { + @include reset; + align-items: center; cursor: pointer; display: flex; @@ -56,14 +76,32 @@ $card-media-margin: $block-spacing !default; .card-image { display: block; position: relative; + + &:first-child { + img { + border-top-left-radius: $card-radius; + border-top-right-radius: $card-radius; + } + } + + &:last-child { + img { + border-bottom-left-radius: $card-radius; + border-bottom-right-radius: $card-radius; + } + } } .card-content { + @extend %card-item; + background-color: $card-content-background-color; padding: $card-content-padding; } .card-footer { + @extend %card-item; + background-color: $card-footer-background-color; border-top: $card-footer-border-top; align-items: stretch; diff --git a/node_modules/bulma-scss/components/_dropdown.scss b/node_modules/bulma-scss/components/_dropdown.scss index c58d78e1b7..779c47ba56 100644 --- a/node_modules/bulma-scss/components/_dropdown.scss +++ b/node_modules/bulma-scss/components/_dropdown.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + $dropdown-menu-min-width: 12rem !default; $dropdown-content-background-color: $scheme-main !default; @@ -6,7 +8,7 @@ $dropdown-content-offset: 4px !default; $dropdown-content-padding-bottom: 0.5rem !default; $dropdown-content-padding-top: 0.5rem !default; $dropdown-content-radius: $radius !default; -$dropdown-content-shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0px 0 1px rgba($scheme-invert, 0.02) !default; +$dropdown-content-shadow: $shadow !default; $dropdown-content-z: 20 !default; $dropdown-item-color: $text !default; diff --git a/node_modules/bulma-scss/components/_level.scss b/node_modules/bulma-scss/components/_level.scss index 6a822f12fd..f709058e56 100644 --- a/node_modules/bulma-scss/components/_level.scss +++ b/node_modules/bulma-scss/components/_level.scss @@ -1,4 +1,6 @@ -$level-item-spacing: $block-spacing / 2 !default; +@import "../utilities/mixins"; + +$level-item-spacing: $block-spacing * 0.5 !default; .level { @extend %block; diff --git a/node_modules/bulma-scss/components/_list.scss b/node_modules/bulma-scss/components/_list.scss deleted file mode 100644 index 9db29fc496..0000000000 --- a/node_modules/bulma-scss/components/_list.scss +++ /dev/null @@ -1,54 +0,0 @@ -$list-background-color: $scheme-main !default; -$list-shadow: 0 2px 3px rgba($scheme-invert, 0.1), 0 0 0 1px rgba($scheme-invert, 0.1) !default; -$list-radius: $radius !default; - -$list-item-border: 1px solid $border !default; -$list-item-color: $text !default; -$list-item-active-background-color: $link !default; -$list-item-active-color: $link-invert !default; -$list-item-hover-background-color: $background !default; - -.list { - @extend %block; - - background-color: $list-background-color; - border-radius: $list-radius; - box-shadow: $list-shadow; - - // &.is-hoverable > .list-item:hover:not(.is-active) - // background-color: $list-item-hover-background-color - // cursor: pointer -} - -.list-item { - display: block; - padding: 0.5em 1em; - - &:not(a) { - color: $list-item-color; - } - - &:first-child { - border-top-left-radius: $list-radius; - border-top-right-radius: $list-radius; - } - - &:last-child { - border-bottom-left-radius: $list-radius; - border-bottom-right-radius: $list-radius; - } - - &:not(:last-child) { - border-bottom: $list-item-border; - } - - &.is-active { - background-color: $list-item-active-background-color; - color: $list-item-active-color; - } -} - -a.list-item { - background-color: $list-item-hover-background-color; - cursor: pointer; -} diff --git a/node_modules/bulma-scss/components/_media.scss b/node_modules/bulma-scss/components/_media.scss index 0b9dfe86fd..b2f3fbb76c 100644 --- a/node_modules/bulma-scss/components/_media.scss +++ b/node_modules/bulma-scss/components/_media.scss @@ -1,6 +1,13 @@ +@import "../utilities/mixins"; + $media-border-color: bulmaRgba($border, 0.5) !default; -$media-spacing: 1rem; -$media-spacing-large: 1.5rem; +$media-border-size: 1px !default; +$media-spacing: 1rem !default; +$media-spacing-large: 1.5rem !default; +$media-content-spacing: 0.75rem !default; +$media-level-1-spacing: 0.75rem !default; +$media-level-1-content-spacing: 0.5rem !default; +$media-level-2-spacing: 0.5rem !default; .media { align-items: flex-start; @@ -8,30 +15,30 @@ $media-spacing-large: 1.5rem; text-align: inherit; .content:not(:last-child) { - margin-bottom: 0.75rem; + margin-bottom: $media-content-spacing; } .media { - border-top: 1px solid $media-border-color; + border-top: $media-border-size solid $media-border-color; display: flex; - padding-top: 0.75rem; + padding-top: $media-level-1-spacing; .content:not(:last-child), .control:not(:last-child) { - margin-bottom: 0.5rem; + margin-bottom: $media-level-1-content-spacing; } .media { - padding-top: 0.5rem; + padding-top: $media-level-2-spacing; & + .media { - margin-top: 0.5rem; + margin-top: $media-level-2-spacing; } } } & + .media { - border-top: 1px solid $media-border-color; + border-top: $media-border-size solid $media-border-color; margin-top: $media-spacing; padding-top: $media-spacing; } diff --git a/node_modules/bulma-scss/components/_menu.scss b/node_modules/bulma-scss/components/_menu.scss index 8e0a186f2e..558ca5e709 100644 --- a/node_modules/bulma-scss/components/_menu.scss +++ b/node_modules/bulma-scss/components/_menu.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + $menu-item-color: $text !default; $menu-item-radius: $radius-small !default; $menu-item-hover-color: $text-strong !default; diff --git a/node_modules/bulma-scss/components/_message.scss b/node_modules/bulma-scss/components/_message.scss index 36dfc0da4c..f5992257ca 100644 --- a/node_modules/bulma-scss/components/_message.scss +++ b/node_modules/bulma-scss/components/_message.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + $message-background-color: $background !default; $message-radius: $radius !default; diff --git a/node_modules/bulma-scss/components/_modal.scss b/node_modules/bulma-scss/components/_modal.scss index 1e6a00694d..9fd23fa862 100644 --- a/node_modules/bulma-scss/components/_modal.scss +++ b/node_modules/bulma-scss/components/_modal.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + $modal-z: 40 !default; $modal-background-background-color: bulmaRgba($scheme-invert, 0.86) !default; @@ -28,6 +30,8 @@ $modal-card-foot-border-top: 1px solid $border !default; $modal-card-body-background-color: $scheme-main !default; $modal-card-body-padding: 20px !default; +$modal-breakpoint: $tablet !default; + .modal { @extend %overlay; @@ -60,7 +64,7 @@ $modal-card-body-padding: 20px !default; width: 100%; // Responsiveness - @include tablet { + @include from($modal-breakpoint) { margin: 0 auto; max-height: calc(100vh - #{$modal-content-spacing-tablet}); width: $modal-content-width; diff --git a/node_modules/bulma-scss/components/_navbar.scss b/node_modules/bulma-scss/components/_navbar.scss index 8805a5cd13..7d13e59eb9 100644 --- a/node_modules/bulma-scss/components/_navbar.scss +++ b/node_modules/bulma-scss/components/_navbar.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + $navbar-background-color: $scheme-main !default; $navbar-box-shadow-size: 0 2px 0 0 !default; $navbar-box-shadow-color: $background !default; @@ -46,6 +48,8 @@ $navbar-bottom-box-shadow-size: 0 -2px 0 0 !default; $navbar-breakpoint: $desktop !default; +$navbar-colors: $colors !default; + @mixin navbar-fixed { left: 0; position: fixed; @@ -59,7 +63,7 @@ $navbar-breakpoint: $desktop !default; position: relative; z-index: $navbar-z; - @each $name, $pair in $colors { + @each $name, $pair in $navbar-colors { $color: nth($pair, 1); $color-invert: nth($pair, 2); @@ -204,6 +208,8 @@ body { } .navbar-burger { + @extend %reset; + color: $navbar-burger-color; @include hamburger($navbar-height); diff --git a/node_modules/bulma-scss/components/_pagination.scss b/node_modules/bulma-scss/components/_pagination.scss index 5c048a3474..8821cc1154 100644 --- a/node_modules/bulma-scss/components/_pagination.scss +++ b/node_modules/bulma-scss/components/_pagination.scss @@ -1,3 +1,6 @@ +@import "../utilities/controls"; +@import "../utilities/mixins"; + $pagination-color: $text-strong !default; $pagination-border-color: $border !default; $pagination-margin: -0.25rem !default; @@ -8,6 +11,9 @@ $pagination-item-margin: 0.25rem !default; $pagination-item-padding-left: 0.5em !default; $pagination-item-padding-right: 0.5em !default; +$pagination-nav-padding-left: 0.75em !default; +$pagination-nav-padding-right: 0.75em !default; + $pagination-hover-color: $link-hover !default; $pagination-hover-border-color: $link-hover-border !default; @@ -27,7 +33,7 @@ $pagination-current-border-color: $link !default; $pagination-ellipsis-color: $grey-light !default; -$pagination-shadow-inset: inset 0 1px 2px rgba($scheme-invert, 0.2); +$pagination-shadow-inset: inset 0 1px 2px rgba($scheme-invert, 0.2) !default; .pagination { @extend %block; @@ -106,7 +112,8 @@ $pagination-shadow-inset: inset 0 1px 2px rgba($scheme-invert, 0.2); box-shadow: $pagination-shadow-inset; } - &[disabled] { + &[disabled], + &.is-disabled { background-color: $pagination-disabled-background-color; border-color: $pagination-disabled-border-color; box-shadow: none; @@ -117,8 +124,8 @@ $pagination-shadow-inset: inset 0 1px 2px rgba($scheme-invert, 0.2); .pagination-previous, .pagination-next { - padding-left: 0.75em; - padding-right: 0.75em; + padding-left: $pagination-nav-padding-left; + padding-right: $pagination-nav-padding-right; white-space: nowrap; } @@ -137,6 +144,10 @@ $pagination-shadow-inset: inset 0 1px 2px rgba($scheme-invert, 0.2); .pagination-list { flex-wrap: wrap; + + li { + list-style: none; + } } @include mobile { @@ -167,6 +178,14 @@ $pagination-shadow-inset: inset 0 1px 2px rgba($scheme-invert, 0.2); order: 1; } + .pagination-previous, + .pagination-next, + .pagination-link, + .pagination-ellipsis { + margin-bottom: 0; + margin-top: 0; + } + .pagination-previous { order: 2; } @@ -177,6 +196,8 @@ $pagination-shadow-inset: inset 0 1px 2px rgba($scheme-invert, 0.2); .pagination { justify-content: space-between; + margin-bottom: 0; + margin-top: 0; &.is-centered { .pagination-previous { diff --git a/node_modules/bulma-scss/components/_panel.scss b/node_modules/bulma-scss/components/_panel.scss index 1b8ba15979..3a33431d0a 100644 --- a/node_modules/bulma-scss/components/_panel.scss +++ b/node_modules/bulma-scss/components/_panel.scss @@ -1,7 +1,9 @@ +@import "../utilities/mixins"; + $panel-margin: $block-spacing !default; $panel-item-border: 1px solid $border-light !default; $panel-radius: $radius-large !default; -$panel-shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0px 0 1px rgba($scheme-invert, 0.02) !default; +$panel-shadow: $shadow !default; $panel-heading-background-color: $border-light !default; $panel-heading-color: $text-strong !default; diff --git a/node_modules/bulma-scss/components/_tabs.scss b/node_modules/bulma-scss/components/_tabs.scss index 640bdce1e5..441d2fd3af 100644 --- a/node_modules/bulma-scss/components/_tabs.scss +++ b/node_modules/bulma-scss/components/_tabs.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + $tabs-border-bottom-color: $border !default; $tabs-border-bottom-style: solid !default; $tabs-border-bottom-width: 1px !default; diff --git a/node_modules/bulma-scss/elements/_all.scss b/node_modules/bulma-scss/elements/_all.scss index af1e54fb6c..da21a65ffa 100644 --- a/node_modules/bulma-scss/elements/_all.scss +++ b/node_modules/bulma-scss/elements/_all.scss @@ -1,3 +1,4 @@ +/* Bulma Elements */ @import "box"; @import "button"; @import "container"; diff --git a/node_modules/bulma-scss/elements/_box.scss b/node_modules/bulma-scss/elements/_box.scss index ef48d1d3de..b2fe1b0968 100644 --- a/node_modules/bulma-scss/elements/_box.scss +++ b/node_modules/bulma-scss/elements/_box.scss @@ -1,7 +1,9 @@ +@import "../utilities/mixins"; + $box-color: $text !default; $box-background-color: $scheme-main !default; $box-radius: $radius-large !default; -$box-shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0px 0 1px rgba($scheme-invert, 0.02) !default; +$box-shadow: $shadow !default; $box-padding: 1.25rem !default; $box-link-hover-shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0 0 1px $link !default; diff --git a/node_modules/bulma-scss/elements/_button.scss b/node_modules/bulma-scss/elements/_button.scss index aaf16cf17d..c4bd3bae23 100644 --- a/node_modules/bulma-scss/elements/_button.scss +++ b/node_modules/bulma-scss/elements/_button.scss @@ -1,3 +1,6 @@ +@import "../utilities/controls"; +@import "../utilities/mixins"; + $button-color: $text-strong !default; $button-background-color: $scheme-main !default; $button-family: false !default; @@ -24,6 +27,13 @@ $button-text-decoration: underline !default; $button-text-hover-background-color: $background !default; $button-text-hover-color: $text-strong !default; +$button-ghost-background: none !default; +$button-ghost-border-color: transparent !default; +$button-ghost-color: $link !default; +$button-ghost-decoration: none !default; +$button-ghost-hover-color: $link !default; +$button-ghost-hover-decoration: underline !default; + $button-disabled-background-color: $scheme-main !default; $button-disabled-border-color: $border !default; $button-disabled-shadow: none !default; @@ -33,9 +43,15 @@ $button-static-color: $text-light !default; $button-static-background-color: $scheme-main-ter !default; $button-static-border-color: $border !default; +$button-colors: $colors !default; +$button-responsive-sizes: ("mobile": ("small": $size-small * 0.75, "normal": $size-small * 0.875, "medium": $size-small, "large": $size-normal), "tablet-only": ("small": $size-small * 0.875, "normal": $size-small, "medium": $size-normal, "large": $size-medium)) !default; + // The button sizes use mixins so they can be used at different breakpoints @mixin button-small { - border-radius: $radius-small; + &:not(.is-rounded) { + border-radius: $radius-small; + } + font-size: $size-small; } @@ -88,18 +104,18 @@ $button-static-border-color: $border !default; } &:first-child:not(:last-child) { - @include ltr-property("margin", calc(#{-1 / 2 * $button-padding-horizontal} - #{$button-border-width}), false); - @include ltr-property("margin", $button-padding-horizontal / 4); + @include ltr-property("margin", calc(#{-0.5 * $button-padding-horizontal} - #{$button-border-width}), false); + @include ltr-property("margin", $button-padding-horizontal * 0.25); } &:last-child:not(:first-child) { - @include ltr-property("margin", $button-padding-horizontal / 4, false); - @include ltr-property("margin", calc(#{-1 / 2 * $button-padding-horizontal} - #{$button-border-width})); + @include ltr-property("margin", $button-padding-horizontal * 0.25, false); + @include ltr-property("margin", calc(#{-0.5 * $button-padding-horizontal} - #{$button-border-width})); } &:first-child:last-child { - margin-left: calc(#{-1 / 2 * $button-padding-horizontal} - #{$button-border-width}); - margin-right: calc(#{-1 / 2 * $button-padding-horizontal} - #{$button-border-width}); + margin-left: calc(#{-0.5 * $button-padding-horizontal} - #{$button-border-width}); + margin-right: calc(#{-0.5 * $button-padding-horizontal} - #{$button-border-width}); } } @@ -155,7 +171,20 @@ $button-static-border-color: $border !default; } } - @each $name, $pair in $colors { + &.is-ghost { + background: $button-ghost-background; + border-color: $button-ghost-border-color; + color: $button-ghost-color; + text-decoration: $button-ghost-decoration; + + &:hover, + &.is-hovered { + color: $button-ghost-hover-color; + text-decoration: $button-ghost-hover-decoration; + } + } + + @each $name, $pair in $button-colors { $color: nth($pair, 1); $color-invert: nth($pair, 2); @@ -191,7 +220,7 @@ $button-static-border-color: $border !default; &[disabled], fieldset[disabled] & { background-color: $color; - border-color: transparent; + border-color: $color; box-shadow: none; } @@ -481,3 +510,21 @@ $button-static-border-color: $border !default; } } } + +@each $bp-name, $bp-sizes in $button-responsive-sizes { + @include breakpoint($bp-name) { + @each $size, $value in $bp-sizes { + @if $size != "normal" { + .button.is-responsive.is-#{$size} { + font-size: $value; + } + } + @else { + .button.is-responsive, + .button.is-responsive.is-normal { + font-size: $value; + } + } + } + } +} diff --git a/node_modules/bulma-scss/elements/_container.scss b/node_modules/bulma-scss/elements/_container.scss index 140508e0b1..65ca1bd244 100644 --- a/node_modules/bulma-scss/elements/_container.scss +++ b/node_modules/bulma-scss/elements/_container.scss @@ -1,4 +1,7 @@ +@import "../utilities/mixins"; + $container-offset: 2 * $gap !default; +$container-max-width: $fullhd !default; .container { flex-grow: 1; @@ -7,7 +10,7 @@ $container-offset: 2 * $gap !default; width: auto; &.is-fluid { - max-width: none; + max-width: none !important; padding-left: $gap; padding-right: $gap; width: 100%; @@ -19,25 +22,29 @@ $container-offset: 2 * $gap !default; @include until-widescreen { - &.is-widescreen { - max-width: $widescreen - $container-offset; + &.is-widescreen:not(.is-max-desktop) { + max-width: min($widescreen, $container-max-width) - $container-offset; } } @include until-fullhd { - &.is-fullhd { - max-width: $fullhd - $container-offset; + &.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen) { + max-width: min($fullhd, $container-max-width) - $container-offset; } } @include widescreen { - max-width: $widescreen - $container-offset; + &:not(.is-max-desktop) { + max-width: min($widescreen, $container-max-width) - $container-offset; + } } @include fullhd { - max-width: $fullhd - $container-offset; + &:not(.is-max-desktop):not(.is-max-widescreen) { + max-width: min($fullhd, $container-max-width) - $container-offset; + } } } diff --git a/node_modules/bulma-scss/elements/_content.scss b/node_modules/bulma-scss/elements/_content.scss index 851c43f24b..6cb610cb38 100644 --- a/node_modules/bulma-scss/elements/_content.scss +++ b/node_modules/bulma-scss/elements/_content.scss @@ -1,7 +1,11 @@ +@import "../utilities/mixins"; + $content-heading-color: $text-strong !default; $content-heading-weight: $weight-semibold !default; $content-heading-line-height: 1.125 !default; +$content-block-margin-bottom: 1em !default; + $content-blockquote-background-color: $background !default; $content-blockquote-border-left: 5px solid $border !default; $content-blockquote-padding: 1.25em 1.5em !default; @@ -14,6 +18,7 @@ $content-table-cell-padding: 0.5em 0.75em !default; $content-table-cell-heading-color: $text-strong !default; $content-table-head-cell-border-width: 0 0 2px !default; $content-table-head-cell-color: $text-strong !default; +$content-table-body-last-row-cell-border-bottom-width: 0 !default; $content-table-foot-cell-border-width: 2px 0 0 !default; $content-table-foot-cell-color: $text-strong !default; @@ -34,7 +39,7 @@ $content-table-foot-cell-color: $text-strong !default; pre, table { &:not(:last-child) { - margin-bottom: 1em; + margin-bottom: $content-block-margin-bottom; } } @@ -224,7 +229,7 @@ $content-table-foot-cell-color: $text-strong !default; &:last-child { td, th { - border-bottom-width: 0; + border-bottom-width: $content-table-body-last-row-cell-border-bottom-width; } } } @@ -242,6 +247,10 @@ $content-table-foot-cell-color: $text-strong !default; font-size: $size-small; } + &.is-normal { + font-size: $size-normal; + } + &.is-medium { font-size: $size-medium; } diff --git a/node_modules/bulma-scss/elements/_icon.scss b/node_modules/bulma-scss/elements/_icon.scss index 58e413396f..bcdccc53d6 100644 --- a/node_modules/bulma-scss/elements/_icon.scss +++ b/node_modules/bulma-scss/elements/_icon.scss @@ -2,6 +2,7 @@ $icon-dimensions: 1.5rem !default; $icon-dimensions-small: 1rem !default; $icon-dimensions-medium: 2rem !default; $icon-dimensions-large: 3rem !default; +$icon-text-spacing: 0.25em !default; .icon { align-items: center; @@ -26,3 +27,43 @@ $icon-dimensions-large: 3rem !default; width: $icon-dimensions-large; } } + +.icon-text { + align-items: flex-start; + color: inherit; + display: inline-flex; + flex-wrap: wrap; + line-height: $icon-dimensions; + vertical-align: top; + + .icon { + flex-grow: 0; + flex-shrink: 0; + + &:not(:last-child) { + @include ltr { + margin-right: $icon-text-spacing; + } + + + @include rtl { + margin-left: $icon-text-spacing; + } + } + + &:not(:first-child) { + @include ltr { + margin-left: $icon-text-spacing; + } + + + @include rtl { + margin-right: $icon-text-spacing; + } + } + } +} + +div.icon-text { + display: flex; +} diff --git a/node_modules/bulma-scss/elements/_image.scss b/node_modules/bulma-scss/elements/_image.scss index 5fae7dcf28..a62870067f 100644 --- a/node_modules/bulma-scss/elements/_image.scss +++ b/node_modules/bulma-scss/elements/_image.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + $dimensions: 16 24 32 48 64 96 128 !default; .image { diff --git a/node_modules/bulma-scss/elements/_notification.scss b/node_modules/bulma-scss/elements/_notification.scss index c6702c94f6..d6ca1dcdca 100644 --- a/node_modules/bulma-scss/elements/_notification.scss +++ b/node_modules/bulma-scss/elements/_notification.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + $notification-background-color: $background !default; $notification-code-background-color: $scheme-main !default; $notification-radius: $radius !default; @@ -5,6 +7,8 @@ $notification-padding: 1.25rem 2.5rem 1.25rem 1.5rem !default; $notification-padding-ltr: 1.25rem 2.5rem 1.25rem 1.5rem !default; $notification-padding-rtl: 1.25rem 1.5rem 1.25rem 2.5rem !default; +$notification-colors: $colors !default; + .notification { @extend %block; @@ -54,7 +58,7 @@ $notification-padding-rtl: 1.25rem 1.5rem 1.25rem 2.5rem !default; } // Colors - @each $name, $pair in $colors { + @each $name, $pair in $notification-colors { $color: nth($pair, 1); $color-invert: nth($pair, 2); diff --git a/node_modules/bulma-scss/elements/_other.scss b/node_modules/bulma-scss/elements/_other.scss index cd906a55a8..7e60fb495f 100644 --- a/node_modules/bulma-scss/elements/_other.scss +++ b/node_modules/bulma-scss/elements/_other.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + .block { @extend %block; } @@ -14,20 +16,6 @@ text-transform: uppercase; } -.highlight { - @extend %block; - - font-weight: $weight-normal; - max-width: 100%; - overflow: hidden; - padding: 0; - - pre { - overflow: auto; - max-width: 100%; - } -} - .loader { @extend %loader; } diff --git a/node_modules/bulma-scss/elements/_progress.scss b/node_modules/bulma-scss/elements/_progress.scss index a3fd439682..b620b43e80 100644 --- a/node_modules/bulma-scss/elements/_progress.scss +++ b/node_modules/bulma-scss/elements/_progress.scss @@ -1,9 +1,13 @@ +@import "../utilities/mixins"; + $progress-bar-background-color: $border-light !default; $progress-value-background-color: $text !default; $progress-border-radius: $radius-rounded !default; $progress-indeterminate-duration: 1.5s !default; +$progress-colors: $colors !default; + .progress { @extend %block; @@ -35,7 +39,7 @@ $progress-indeterminate-duration: 1.5s !default; } // Colors - @each $name, $pair in $colors { + @each $name, $pair in $progress-colors { $color: nth($pair, 1); &.is-#{$name} { @@ -75,6 +79,10 @@ $progress-indeterminate-duration: 1.5s !default; &::-moz-progress-bar { background-color: transparent; } + + &::-ms-fill { + animation-name: none; + } } // Sizes diff --git a/node_modules/bulma-scss/elements/_table.scss b/node_modules/bulma-scss/elements/_table.scss index 0aa2d2ac09..5bd463c238 100644 --- a/node_modules/bulma-scss/elements/_table.scss +++ b/node_modules/bulma-scss/elements/_table.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + $table-color: $text-strong !default; $table-background-color: $scheme-main !default; @@ -5,6 +7,7 @@ $table-cell-border: 1px solid $border !default; $table-cell-border-width: 0 0 1px !default; $table-cell-padding: 0.5em 0.75em !default; $table-cell-heading-color: $text-strong !default; +$table-cell-text-align: left !default; $table-head-cell-border-width: 0 0 2px !default; $table-head-cell-color: $text-strong !default; @@ -23,6 +26,8 @@ $table-row-active-color: $primary-invert !default; $table-striped-row-even-background-color: $scheme-main-bis !default; $table-striped-row-even-hover-background-color: $scheme-main-ter !default; +$table-colors: $colors !default; + .table { @extend %block; @@ -37,7 +42,7 @@ $table-striped-row-even-hover-background-color: $scheme-main-ter !default; vertical-align: top; // Colors - @each $name, $pair in $colors { + @each $name, $pair in $table-colors { $color: nth($pair, 1); $color-invert: nth($pair, 2); @@ -73,7 +78,7 @@ $table-striped-row-even-hover-background-color: $scheme-main-ter !default; color: $table-cell-heading-color; &:not([align]) { - text-align: inherit; + text-align: $table-cell-text-align; } } diff --git a/node_modules/bulma-scss/elements/_tag.scss b/node_modules/bulma-scss/elements/_tag.scss index 0eb9ea83ac..cb39fd25ff 100644 --- a/node_modules/bulma-scss/elements/_tag.scss +++ b/node_modules/bulma-scss/elements/_tag.scss @@ -1,8 +1,12 @@ +@import "../utilities/mixins"; + $tag-background-color: $background !default; $tag-color: $text !default; $tag-radius: $radius !default; $tag-delete-margin: 1px !default; +$tag-colors: $colors !default; + .tags { align-items: center; display: flex; @@ -115,7 +119,7 @@ $tag-delete-margin: 1px !default; } // Colors - @each $name, $pair in $colors { + @each $name, $pair in $tag-colors { $color: nth($pair, 1); $color-invert: nth($pair, 2); diff --git a/node_modules/bulma-scss/elements/_title.scss b/node_modules/bulma-scss/elements/_title.scss index 543429d09d..fbb1baad95 100644 --- a/node_modules/bulma-scss/elements/_title.scss +++ b/node_modules/bulma-scss/elements/_title.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + $title-color: $text-strong !default; $title-family: false !default; $title-size: $size-3 !default; @@ -57,10 +59,6 @@ $subtitle-negative-margin: -1.25rem !default; font-weight: $title-strong-weight; } - & + .highlight { - margin-top: -0.75rem; - } - &:not(.is-spaced) + .subtitle { margin-top: $subtitle-negative-margin; } diff --git a/node_modules/bulma-scss/form/_all.scss b/node_modules/bulma-scss/form/_all.scss index 49da5bfdb2..568f73354a 100644 --- a/node_modules/bulma-scss/form/_all.scss +++ b/node_modules/bulma-scss/form/_all.scss @@ -1,3 +1,4 @@ +/* Bulma Form */ @import "shared"; @import "input-textarea"; @import "checkbox-radio"; diff --git a/node_modules/bulma-scss/form/_checkbox-radio.scss b/node_modules/bulma-scss/form/_checkbox-radio.scss index 6ae1b5b6c9..4e115ced64 100644 --- a/node_modules/bulma-scss/form/_checkbox-radio.scss +++ b/node_modules/bulma-scss/form/_checkbox-radio.scss @@ -13,7 +13,8 @@ } &[disabled], - fieldset[disabled] & { + fieldset[disabled] &, + input[disabled] { color: $input-disabled-color; cursor: not-allowed; } diff --git a/node_modules/bulma-scss/form/_file.scss b/node_modules/bulma-scss/form/_file.scss index 1e6dc33d2f..55e9654af7 100644 --- a/node_modules/bulma-scss/form/_file.scss +++ b/node_modules/bulma-scss/form/_file.scss @@ -11,6 +11,8 @@ $file-name-border-style: solid !default; $file-name-border-width: 1px 1px 1px 0 !default; $file-name-max-width: 16em !default; +$file-colors: $form-colors !default; + .file { @extend %unselectable; @@ -20,7 +22,7 @@ $file-name-max-width: 16em !default; position: relative; // Colors - @each $name, $pair in $colors { + @each $name, $pair in $file-colors { $color: nth($pair, 1); $color-invert: nth($pair, 2); @@ -65,6 +67,10 @@ $file-name-max-width: 16em !default; font-size: $size-small; } + &.is-normal { + font-size: $size-normal; + } + &.is-medium { font-size: $size-medium; diff --git a/node_modules/bulma-scss/form/_input-textarea.scss b/node_modules/bulma-scss/form/_input-textarea.scss index 37bf4dc08c..c518a3eef9 100644 --- a/node_modules/bulma-scss/form/_input-textarea.scss +++ b/node_modules/bulma-scss/form/_input-textarea.scss @@ -2,6 +2,8 @@ $textarea-padding: $control-padding-horizontal !default; $textarea-max-height: 40em !default; $textarea-min-height: 8em !default; +$textarea-colors: $form-colors !default; + %input-textarea { @extend %input; @@ -14,7 +16,7 @@ $textarea-min-height: 8em !default; } // Colors - @each $name, $pair in $colors { + @each $name, $pair in $textarea-colors { $color: nth($pair, 1); &.is-#{$name} { diff --git a/node_modules/bulma-scss/form/_select.scss b/node_modules/bulma-scss/form/_select.scss index 105580bea7..844a27adb1 100644 --- a/node_modules/bulma-scss/form/_select.scss +++ b/node_modules/bulma-scss/form/_select.scss @@ -1,3 +1,5 @@ +$select-colors: $form-colors !default; + .select { display: inline-block; max-width: 100%; @@ -68,7 +70,7 @@ } // Colors - @each $name, $pair in $colors { + @each $name, $pair in $select-colors { $color: nth($pair, 1); &.is-#{$name} { @@ -110,7 +112,8 @@ // Modifiers &.is-disabled { &::after { - border-color: $input-disabled-color; + border-color: $input-disabled-color !important; + opacity: 0.5; } } diff --git a/node_modules/bulma-scss/form/_shared.scss b/node_modules/bulma-scss/form/_shared.scss index 4e36f874e1..4f224acb84 100644 --- a/node_modules/bulma-scss/form/_shared.scss +++ b/node_modules/bulma-scss/form/_shared.scss @@ -1,3 +1,8 @@ +@import "../utilities/controls"; +@import "../utilities/mixins"; + +$form-colors: $colors !default; + $input-color: $text-strong !default; $input-background-color: $scheme-main !default; $input-border-color: $border !default; diff --git a/node_modules/bulma-scss/form/_tools.scss b/node_modules/bulma-scss/form/_tools.scss index fb4abafc77..85f8edbbe9 100644 --- a/node_modules/bulma-scss/form/_tools.scss +++ b/node_modules/bulma-scss/form/_tools.scss @@ -3,6 +3,8 @@ $label-weight: $weight-bold !default; $help-size: $size-small !default; +$label-colors: $form-colors !default; + .label { color: $label-color; display: block; @@ -32,7 +34,7 @@ $help-size: $size-small !default; font-size: $help-size; margin-top: 0.25rem; - @each $name, $pair in $colors { + @each $name, $pair in $label-colors { $color: nth($pair, 1); &.is-#{$name} { diff --git a/node_modules/bulma-scss/grid/_all.scss b/node_modules/bulma-scss/grid/_all.scss index cd1c6b3c8e..ccd8e87e5f 100644 --- a/node_modules/bulma-scss/grid/_all.scss +++ b/node_modules/bulma-scss/grid/_all.scss @@ -1,2 +1,3 @@ +/* Bulma Grid */ @import "columns"; @import "tiles"; diff --git a/node_modules/bulma-scss/grid/_columns.scss b/node_modules/bulma-scss/grid/_columns.scss index 3b59de2663..1182fd27de 100644 --- a/node_modules/bulma-scss/grid/_columns.scss +++ b/node_modules/bulma-scss/grid/_columns.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + $column-gap: 0.75rem !default; .column { @@ -9,6 +11,7 @@ $column-gap: 0.75rem !default; .columns.is-mobile > &.is-narrow { flex: none; + width: unset; } .columns.is-mobile > &.is-full { @@ -62,55 +65,56 @@ $column-gap: 0.75rem !default; } .columns.is-mobile > &.is-offset-three-quarters { - margin-left: 75%; + @include ltr-property("margin", 75%, false); } .columns.is-mobile > &.is-offset-two-thirds { - margin-left: 66.6666%; + @include ltr-property("margin", 66.6666%, false); } .columns.is-mobile > &.is-offset-half { - margin-left: 50%; + @include ltr-property("margin", 50%, false); } .columns.is-mobile > &.is-offset-one-third { - margin-left: 33.3333%; + @include ltr-property("margin", 33.3333%, false); } .columns.is-mobile > &.is-offset-one-quarter { - margin-left: 25%; + @include ltr-property("margin", 25%, false); } .columns.is-mobile > &.is-offset-one-fifth { - margin-left: 20%; + @include ltr-property("margin", 20%, false); } .columns.is-mobile > &.is-offset-two-fifths { - margin-left: 40%; + @include ltr-property("margin", 40%, false); } .columns.is-mobile > &.is-offset-three-fifths { - margin-left: 60%; + @include ltr-property("margin", 60%, false); } .columns.is-mobile > &.is-offset-four-fifths { - margin-left: 80%; + @include ltr-property("margin", 80%, false); } @for $i from 0 through 12 { .columns.is-mobile > &.is-#{$i} { flex: none; - width: percentage($i / 12); + width: percentage(divide($i, 12)); } .columns.is-mobile > &.is-offset-#{$i} { - margin-left: percentage($i / 12); + @include ltr-property("margin", percentage(divide($i, 12)), false); } } @include mobile { &.is-narrow-mobile { flex: none; + width: unset; } &.is-full-mobile { @@ -164,49 +168,49 @@ $column-gap: 0.75rem !default; } &.is-offset-three-quarters-mobile { - margin-left: 75%; + @include ltr-property("margin", 75%, false); } &.is-offset-two-thirds-mobile { - margin-left: 66.6666%; + @include ltr-property("margin", 66.6666%, false); } &.is-offset-half-mobile { - margin-left: 50%; + @include ltr-property("margin", 50%, false); } &.is-offset-one-third-mobile { - margin-left: 33.3333%; + @include ltr-property("margin", 33.3333%, false); } &.is-offset-one-quarter-mobile { - margin-left: 25%; + @include ltr-property("margin", 25%, false); } &.is-offset-one-fifth-mobile { - margin-left: 20%; + @include ltr-property("margin", 20%, false); } &.is-offset-two-fifths-mobile { - margin-left: 40%; + @include ltr-property("margin", 40%, false); } &.is-offset-three-fifths-mobile { - margin-left: 60%; + @include ltr-property("margin", 60%, false); } &.is-offset-four-fifths-mobile { - margin-left: 80%; + @include ltr-property("margin", 80%, false); } @for $i from 0 through 12 { &.is-#{$i}-mobile { flex: none; - width: percentage($i / 12); + width: percentage(divide($i, 12)); } &.is-offset-#{$i}-mobile { - margin-left: percentage($i / 12); + @include ltr-property("margin", percentage(divide($i, 12)), false); } } } @@ -216,6 +220,7 @@ $column-gap: 0.75rem !default; &.is-narrow, &.is-narrow-tablet { flex: none; + width: unset; } &.is-full, @@ -280,59 +285,59 @@ $column-gap: 0.75rem !default; &.is-offset-three-quarters, &.is-offset-three-quarters-tablet { - margin-left: 75%; + @include ltr-property("margin", 75%, false); } &.is-offset-two-thirds, &.is-offset-two-thirds-tablet { - margin-left: 66.6666%; + @include ltr-property("margin", 66.6666%, false); } &.is-offset-half, &.is-offset-half-tablet { - margin-left: 50%; + @include ltr-property("margin", 50%, false); } &.is-offset-one-third, &.is-offset-one-third-tablet { - margin-left: 33.3333%; + @include ltr-property("margin", 33.3333%, false); } &.is-offset-one-quarter, &.is-offset-one-quarter-tablet { - margin-left: 25%; + @include ltr-property("margin", 25%, false); } &.is-offset-one-fifth, &.is-offset-one-fifth-tablet { - margin-left: 20%; + @include ltr-property("margin", 20%, false); } &.is-offset-two-fifths, &.is-offset-two-fifths-tablet { - margin-left: 40%; + @include ltr-property("margin", 40%, false); } &.is-offset-three-fifths, &.is-offset-three-fifths-tablet { - margin-left: 60%; + @include ltr-property("margin", 60%, false); } &.is-offset-four-fifths, &.is-offset-four-fifths-tablet { - margin-left: 80%; + @include ltr-property("margin", 80%, false); } @for $i from 0 through 12 { &.is-#{$i}, &.is-#{$i}-tablet { flex: none; - width: percentage($i / 12); + width: percentage(divide($i, 12)); } &.is-offset-#{$i}, &.is-offset-#{$i}-tablet { - margin-left: percentage($i / 12); + @include ltr-property("margin", percentage(divide($i, 12)), false); } } } @@ -341,6 +346,7 @@ $column-gap: 0.75rem !default; @include touch { &.is-narrow-touch { flex: none; + width: unset; } &.is-full-touch { @@ -394,49 +400,49 @@ $column-gap: 0.75rem !default; } &.is-offset-three-quarters-touch { - margin-left: 75%; + @include ltr-property("margin", 75%, false); } &.is-offset-two-thirds-touch { - margin-left: 66.6666%; + @include ltr-property("margin", 66.6666%, false); } &.is-offset-half-touch { - margin-left: 50%; + @include ltr-property("margin", 50%, false); } &.is-offset-one-third-touch { - margin-left: 33.3333%; + @include ltr-property("margin", 33.3333%, false); } &.is-offset-one-quarter-touch { - margin-left: 25%; + @include ltr-property("margin", 25%, false); } &.is-offset-one-fifth-touch { - margin-left: 20%; + @include ltr-property("margin", 20%, false); } &.is-offset-two-fifths-touch { - margin-left: 40%; + @include ltr-property("margin", 40%, false); } &.is-offset-three-fifths-touch { - margin-left: 60%; + @include ltr-property("margin", 60%, false); } &.is-offset-four-fifths-touch { - margin-left: 80%; + @include ltr-property("margin", 80%, false); } @for $i from 0 through 12 { &.is-#{$i}-touch { flex: none; - width: percentage($i / 12); + width: percentage(divide($i, 12)); } &.is-offset-#{$i}-touch { - margin-left: percentage($i / 12); + @include ltr-property("margin", percentage(divide($i, 12)), false); } } } @@ -445,6 +451,7 @@ $column-gap: 0.75rem !default; @include desktop { &.is-narrow-desktop { flex: none; + width: unset; } &.is-full-desktop { @@ -498,49 +505,49 @@ $column-gap: 0.75rem !default; } &.is-offset-three-quarters-desktop { - margin-left: 75%; + @include ltr-property("margin", 75%, false); } &.is-offset-two-thirds-desktop { - margin-left: 66.6666%; + @include ltr-property("margin", 66.6666%, false); } &.is-offset-half-desktop { - margin-left: 50%; + @include ltr-property("margin", 50%, false); } &.is-offset-one-third-desktop { - margin-left: 33.3333%; + @include ltr-property("margin", 33.3333%, false); } &.is-offset-one-quarter-desktop { - margin-left: 25%; + @include ltr-property("margin", 25%, false); } &.is-offset-one-fifth-desktop { - margin-left: 20%; + @include ltr-property("margin", 20%, false); } &.is-offset-two-fifths-desktop { - margin-left: 40%; + @include ltr-property("margin", 40%, false); } &.is-offset-three-fifths-desktop { - margin-left: 60%; + @include ltr-property("margin", 60%, false); } &.is-offset-four-fifths-desktop { - margin-left: 80%; + @include ltr-property("margin", 80%, false); } @for $i from 0 through 12 { &.is-#{$i}-desktop { flex: none; - width: percentage($i / 12); + width: percentage(divide($i, 12)); } &.is-offset-#{$i}-desktop { - margin-left: percentage($i / 12); + @include ltr-property("margin", percentage(divide($i, 12)), false); } } } @@ -549,6 +556,7 @@ $column-gap: 0.75rem !default; @include widescreen { &.is-narrow-widescreen { flex: none; + width: unset; } &.is-full-widescreen { @@ -602,49 +610,49 @@ $column-gap: 0.75rem !default; } &.is-offset-three-quarters-widescreen { - margin-left: 75%; + @include ltr-property("margin", 75%, false); } &.is-offset-two-thirds-widescreen { - margin-left: 66.6666%; + @include ltr-property("margin", 66.6666%, false); } &.is-offset-half-widescreen { - margin-left: 50%; + @include ltr-property("margin", 50%, false); } &.is-offset-one-third-widescreen { - margin-left: 33.3333%; + @include ltr-property("margin", 33.3333%, false); } &.is-offset-one-quarter-widescreen { - margin-left: 25%; + @include ltr-property("margin", 25%, false); } &.is-offset-one-fifth-widescreen { - margin-left: 20%; + @include ltr-property("margin", 20%, false); } &.is-offset-two-fifths-widescreen { - margin-left: 40%; + @include ltr-property("margin", 40%, false); } &.is-offset-three-fifths-widescreen { - margin-left: 60%; + @include ltr-property("margin", 60%, false); } &.is-offset-four-fifths-widescreen { - margin-left: 80%; + @include ltr-property("margin", 80%, false); } @for $i from 0 through 12 { &.is-#{$i}-widescreen { flex: none; - width: percentage($i / 12); + width: percentage(divide($i, 12)); } &.is-offset-#{$i}-widescreen { - margin-left: percentage($i / 12); + @include ltr-property("margin", percentage(divide($i, 12)), false); } } } @@ -653,6 +661,7 @@ $column-gap: 0.75rem !default; @include fullhd { &.is-narrow-fullhd { flex: none; + width: unset; } &.is-full-fullhd { @@ -706,57 +715,58 @@ $column-gap: 0.75rem !default; } &.is-offset-three-quarters-fullhd { - margin-left: 75%; + @include ltr-property("margin", 75%, false); } &.is-offset-two-thirds-fullhd { - margin-left: 66.6666%; + @include ltr-property("margin", 66.6666%, false); } &.is-offset-half-fullhd { - margin-left: 50%; + @include ltr-property("margin", 50%, false); } &.is-offset-one-third-fullhd { - margin-left: 33.3333%; + @include ltr-property("margin", 33.3333%, false); } &.is-offset-one-quarter-fullhd { - margin-left: 25%; + @include ltr-property("margin", 25%, false); } &.is-offset-one-fifth-fullhd { - margin-left: 20%; + @include ltr-property("margin", 20%, false); } &.is-offset-two-fifths-fullhd { - margin-left: 40%; + @include ltr-property("margin", 40%, false); } &.is-offset-three-fifths-fullhd { - margin-left: 60%; + @include ltr-property("margin", 60%, false); } &.is-offset-four-fifths-fullhd { - margin-left: 80%; + @include ltr-property("margin", 80%, false); } @for $i from 0 through 12 { &.is-#{$i}-fullhd { flex: none; - width: percentage($i / 12); + width: percentage(divide($i, 12)); } &.is-offset-#{$i}-fullhd { - margin-left: percentage($i / 12); + @include ltr-property("margin", percentage(divide($i, 12)), false); } } } } .columns { - margin-left: -$column-gap; - margin-right: -$column-gap; + @include ltr-property("margin", -$column-gap, false); + @include ltr-property("margin", -$column-gap); + margin-top: -$column-gap; &:last-child { @@ -773,8 +783,9 @@ $column-gap: 0.75rem !default; } &.is-gapless { - margin-left: 0; - margin-right: 0; + @include ltr-property("margin", 0, false); + @include ltr-property("margin", 0); + margin-top: 0; & > .column { @@ -822,10 +833,11 @@ $column-gap: 0.75rem !default; @if $variable-columns { .columns.is-variable { --columnGap: 0.75rem; - margin-left: calc(-1 * var(--columnGap)); - margin-right: calc(-1 * var(--columnGap)); - .column { + @include ltr-property("margin", calc(-1 * var(--columnGap)), false); + @include ltr-property("margin", calc(-1 * var(--columnGap))); + + > .column { padding-left: var(--columnGap); padding-right: var(--columnGap); } diff --git a/node_modules/bulma-scss/grid/_tiles.scss b/node_modules/bulma-scss/grid/_tiles.scss index 962c124e91..2b8c709752 100644 --- a/node_modules/bulma-scss/grid/_tiles.scss +++ b/node_modules/bulma-scss/grid/_tiles.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + $tile-spacing: 0.75rem !default; .tile { @@ -48,7 +50,7 @@ $tile-spacing: 0.75rem !default; @for $i from 1 through 12 { &.is-#{$i} { flex: none; - width: $i / 12 * 100%; + width: divide($i, 12) * 100%; } } } diff --git a/node_modules/bulma-scss/helpers/_all.scss b/node_modules/bulma-scss/helpers/_all.scss index 9831f49214..c856d233af 100644 --- a/node_modules/bulma-scss/helpers/_all.scss +++ b/node_modules/bulma-scss/helpers/_all.scss @@ -1,4 +1,6 @@ +/* Bulma Helpers */ @import "color"; +@import "flexbox"; @import "float"; @import "other"; @import "overflow"; diff --git a/node_modules/bulma-scss/helpers/_color.scss b/node_modules/bulma-scss/helpers/_color.scss index 557119700e..91214cce84 100644 --- a/node_modules/bulma-scss/helpers/_color.scss +++ b/node_modules/bulma-scss/helpers/_color.scss @@ -1,3 +1,5 @@ +@import "../utilities/derived-variables"; + @each $name, $pair in $colors { $color: nth($pair, 1); diff --git a/node_modules/bulma-scss/helpers/_flexbox.scss b/node_modules/bulma-scss/helpers/_flexbox.scss new file mode 100644 index 0000000000..b7b4d9b206 --- /dev/null +++ b/node_modules/bulma-scss/helpers/_flexbox.scss @@ -0,0 +1,57 @@ +$flex-direction-values: row, row-reverse, column, column-reverse; + +@each $value in $flex-direction-values { + .is-flex-direction-#{$value} { + flex-direction: $value !important; + } +} + +$flex-wrap-values: nowrap, wrap, wrap-reverse; + +@each $value in $flex-wrap-values { + .is-flex-wrap-#{$value} { + flex-wrap: $value !important; + } +} + +$justify-content-values: flex-start, flex-end, center, space-between, space-around, space-evenly, start, end, left, right; + +@each $value in $justify-content-values { + .is-justify-content-#{$value} { + justify-content: $value !important; + } +} + +$align-content-values: flex-start, flex-end, center, space-between, space-around, space-evenly, stretch, start, end, baseline; + +@each $value in $align-content-values { + .is-align-content-#{$value} { + align-content: $value !important; + } +} + +$align-items-values: stretch, flex-start, flex-end, center, baseline, start, end, self-start, self-end; + +@each $value in $align-items-values { + .is-align-items-#{$value} { + align-items: $value !important; + } +} + +$align-self-values: auto, flex-start, flex-end, center, baseline, stretch; + +@each $value in $align-self-values { + .is-align-self-#{$value} { + align-self: $value !important; + } +} + +$flex-operators: grow, shrink; + +@each $operator in $flex-operators { + @for $i from 0 through 5 { + .is-flex-#{$operator}-#{$i} { + flex-#{$operator}: $i !important; + } + } +} diff --git a/node_modules/bulma-scss/helpers/_float.scss b/node_modules/bulma-scss/helpers/_float.scss index 878262a30f..193dac1e67 100644 --- a/node_modules/bulma-scss/helpers/_float.scss +++ b/node_modules/bulma-scss/helpers/_float.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + .is-clearfix { @include clearfix; } diff --git a/node_modules/bulma-scss/helpers/_other.scss b/node_modules/bulma-scss/helpers/_other.scss index 94e4222c9b..a2d4cfed99 100644 --- a/node_modules/bulma-scss/helpers/_other.scss +++ b/node_modules/bulma-scss/helpers/_other.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + .is-radiusless { border-radius: 0 !important; } @@ -6,6 +8,11 @@ box-shadow: none !important; } +.is-clickable { + cursor: pointer !important; + pointer-events: all !important; +} + .is-unselectable { @extend %unselectable; } diff --git a/node_modules/bulma-scss/helpers/_position.scss b/node_modules/bulma-scss/helpers/_position.scss index 608f4f834a..dc881146c4 100644 --- a/node_modules/bulma-scss/helpers/_position.scss +++ b/node_modules/bulma-scss/helpers/_position.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + .is-overlay { @extend %overlay; } diff --git a/node_modules/bulma-scss/helpers/_spacing.scss b/node_modules/bulma-scss/helpers/_spacing.scss index e5819bcb33..1377f4db1c 100644 --- a/node_modules/bulma-scss/helpers/_spacing.scss +++ b/node_modules/bulma-scss/helpers/_spacing.scss @@ -10,10 +10,15 @@ $spacing-shortcuts: ("margin": "m", "padding": "p") !default; $spacing-directions: ("top": "t", "right": "r", "bottom": "b", "left": "l") !default; $spacing-horizontal: "x" !default; $spacing-vertical: "y" !default; -$spacing-values: ("0": 0, "1": 0.25rem, "2": 0.5rem, "3": 0.75rem, "4": 1rem, "5": 1.5rem, "6": 3rem) !default; +$spacing-values: ("0": 0, "1": 0.25rem, "2": 0.5rem, "3": 0.75rem, "4": 1rem, "5": 1.5rem, "6": 3rem, "auto": auto) !default; @each $property, $shortcut in $spacing-shortcuts { @each $name, $value in $spacing-values { + // All directions + .#{$shortcut}-#{$name} { + #{$property}: $value !important; + } + // Cardinal directions @each $direction, $suffix in $spacing-directions { .#{$shortcut}#{$suffix}-#{$name} { diff --git a/node_modules/bulma-scss/helpers/_typography.scss b/node_modules/bulma-scss/helpers/_typography.scss index 49cc0e27e2..2620efe1b8 100644 --- a/node_modules/bulma-scss/helpers/_typography.scss +++ b/node_modules/bulma-scss/helpers/_typography.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + @mixin typography-size($target: "") { @each $size in $sizes { $i: index($sizes, $size); @@ -128,6 +130,10 @@ $alignments: ("centered": "center", "justified": "justify", "left": "left", "rig font-style: italic !important; } +.is-underlined { + text-decoration: underline !important; +} + .has-text-weight-light { font-weight: $weight-light !important; } diff --git a/node_modules/bulma-scss/helpers/_visibility.scss b/node_modules/bulma-scss/helpers/_visibility.scss index 4b3f912626..36c545e98a 100644 --- a/node_modules/bulma-scss/helpers/_visibility.scss +++ b/node_modules/bulma-scss/helpers/_visibility.scss @@ -1,3 +1,5 @@ +@import "../utilities/mixins"; + $displays: "block" "flex" "inline" "inline-block" "inline-flex"; @each $display in $displays { diff --git a/node_modules/bulma-scss/layout/_all.scss b/node_modules/bulma-scss/layout/_all.scss index 253d448c9b..5852b1ec35 100644 --- a/node_modules/bulma-scss/layout/_all.scss +++ b/node_modules/bulma-scss/layout/_all.scss @@ -1,3 +1,4 @@ +/* Bulma Layout */ @import "hero"; @import "section"; @import "footer"; diff --git a/node_modules/bulma-scss/layout/_footer.scss b/node_modules/bulma-scss/layout/_footer.scss index e49a627633..28a817b7e2 100644 --- a/node_modules/bulma-scss/layout/_footer.scss +++ b/node_modules/bulma-scss/layout/_footer.scss @@ -1,3 +1,5 @@ +@import "../utilities/derived-variables"; + $footer-background-color: $scheme-main-bis !default; $footer-color: false !default; $footer-padding: 3rem 1.5rem 6rem !default; diff --git a/node_modules/bulma-scss/layout/_hero.scss b/node_modules/bulma-scss/layout/_hero.scss index 05dc084bd0..56707de459 100644 --- a/node_modules/bulma-scss/layout/_hero.scss +++ b/node_modules/bulma-scss/layout/_hero.scss @@ -1,7 +1,12 @@ +@import "../utilities/mixins"; + $hero-body-padding: 3rem 1.5rem !default; +$hero-body-padding-tablet: 3rem 3rem !default; $hero-body-padding-small: 1.5rem !default; -$hero-body-padding-medium: 9rem 1.5rem !default; -$hero-body-padding-large: 18rem 1.5rem !default; +$hero-body-padding-medium: 9rem 4.5rem !default; +$hero-body-padding-large: 18rem 6rem !default; + +$hero-colors: $colors !default; // Main container .hero { @@ -21,7 +26,7 @@ $hero-body-padding-large: 18rem 1.5rem !default; } // Colors - @each $name, $pair in $colors { + @each $name, $pair in $hero-colors { $color: nth($pair, 1); $color-invert: nth($pair, 2); @@ -79,6 +84,7 @@ $hero-body-padding-large: 18rem 1.5rem !default; li { &.is-active a { + color: $color !important; opacity: 1; } } @@ -232,4 +238,8 @@ $hero-body-padding-large: 18rem 1.5rem !default; flex-grow: 1; flex-shrink: 0; padding: $hero-body-padding; + + @include tablet { + padding: $hero-body-padding-tablet; + } } diff --git a/node_modules/bulma-scss/layout/_section.scss b/node_modules/bulma-scss/layout/_section.scss index dc28c4865b..6ea3276b47 100644 --- a/node_modules/bulma-scss/layout/_section.scss +++ b/node_modules/bulma-scss/layout/_section.scss @@ -1,12 +1,17 @@ +@import "../utilities/mixins"; + $section-padding: 3rem 1.5rem !default; -$section-padding-medium: 9rem 1.5rem !default; -$section-padding-large: 18rem 1.5rem !default; +$section-padding-desktop: 3rem 3rem !default; +$section-padding-medium: 9rem 4.5rem !default; +$section-padding-large: 18rem 6rem !default; .section { padding: $section-padding; // Responsiveness @include desktop { + padding: $section-padding-desktop; + // Sizes &.is-medium { padding: $section-padding-medium; diff --git a/node_modules/bulma-scss/package.json b/node_modules/bulma-scss/package.json index 4b54bf2a38..044ced5a08 100644 --- a/node_modules/bulma-scss/package.json +++ b/node_modules/bulma-scss/package.json @@ -1,6 +1,6 @@ { "name": "bulma-scss", - "version": "0.9.0-1", + "version": "0.9.4", "description": "The Bulma CSS Framework files converted to SCSS syntax", "main": "bulma.scss", "scripts": { diff --git a/node_modules/bulma-scss/utilities/_all.scss b/node_modules/bulma-scss/utilities/_all.scss index 5e3b143c28..d0ead7d723 100644 --- a/node_modules/bulma-scss/utilities/_all.scss +++ b/node_modules/bulma-scss/utilities/_all.scss @@ -1,6 +1,7 @@ +/* Bulma Utilities */ @import "initial-variables"; @import "functions"; -@import "derived-variables.scss"; -@import "animations"; +@import "derived-variables"; @import "mixins"; @import "controls"; +@import "extends"; diff --git a/node_modules/bulma-scss/utilities/_animations.scss b/node_modules/bulma-scss/utilities/_animations.scss index 4849c5a4cf..19885a4d0f 100644 --- a/node_modules/bulma-scss/utilities/_animations.scss +++ b/node_modules/bulma-scss/utilities/_animations.scss @@ -1,9 +1 @@ -@keyframes spinAround { - from { - transform: rotate(0deg); - } - - to { - transform: rotate(359deg); - } -} +@warn "The animations.sass file has MOVED. It is now in the /base folder. Please import sass/base/animations instead."; diff --git a/node_modules/bulma-scss/utilities/_controls.scss b/node_modules/bulma-scss/utilities/_controls.scss index b615f764e7..078b7ca8cd 100644 --- a/node_modules/bulma-scss/utilities/_controls.scss +++ b/node_modules/bulma-scss/utilities/_controls.scss @@ -1,3 +1,5 @@ +@import "derived-variables"; + $control-radius: $radius !default; $control-radius-small: $radius-small !default; @@ -42,10 +44,6 @@ $control-padding-horizontal: calc(0.75em - #{$control-border-width}) !default; } } -%control { - @include control; -} - // The controls sizes use mixins so they can be used at different breakpoints @mixin control-small { border-radius: $control-radius-small; diff --git a/node_modules/bulma-scss/utilities/_derived-variables.scss b/node_modules/bulma-scss/utilities/_derived-variables.scss index 54a03585d5..08e053661e 100644 --- a/node_modules/bulma-scss/utilities/_derived-variables.scss +++ b/node_modules/bulma-scss/utilities/_derived-variables.scss @@ -1,132 +1,114 @@ +@import "initial-variables"; +@import "functions"; + $primary: $turquoise !default; -$info : $cyan !default; +$info: $cyan !default; $success: $green !default; $warning: $yellow !default; -$danger : $red !default; +$danger: $red !default; -$light : $white-ter !default; -$dark : $grey-darker !default; +$light: $white-ter !default; +$dark: $grey-darker !default; // Invert colors -$orange-invert : findColorInvert($orange) !default; -$yellow-invert : findColorInvert($yellow) !default; -$green-invert : findColorInvert($green) !default; +$orange-invert: findColorInvert($orange) !default; +$yellow-invert: findColorInvert($yellow) !default; +$green-invert: findColorInvert($green) !default; $turquoise-invert: findColorInvert($turquoise) !default; -$cyan-invert : findColorInvert($cyan) !default; -$blue-invert : findColorInvert($blue) !default; -$purple-invert : findColorInvert($purple) !default; -$red-invert : findColorInvert($red) !default; +$cyan-invert: findColorInvert($cyan) !default; +$blue-invert: findColorInvert($blue) !default; +$purple-invert: findColorInvert($purple) !default; +$red-invert: findColorInvert($red) !default; -$primary-invert : findColorInvert($primary) !default; -$primary-light : findLightColor($primary) !default; -$primary-dark : findDarkColor($primary) !default; -$info-invert : findColorInvert($info) !default; -$info-light : findLightColor($info) !default; -$info-dark : findDarkColor($info) !default; -$success-invert : findColorInvert($success) !default; -$success-light : findLightColor($success) !default; -$success-dark : findDarkColor($success) !default; -$warning-invert : findColorInvert($warning) !default; -$warning-light : findLightColor($warning) !default; -$warning-dark : findDarkColor($warning) !default; -$danger-invert : findColorInvert($danger) !default; -$danger-light : findLightColor($danger) !default; -$danger-dark : findDarkColor($danger) !default; -$light-invert : findColorInvert($light) !default; -$dark-invert : findColorInvert($dark) !default; +$primary-invert: findColorInvert($primary) !default; +$primary-light: findLightColor($primary) !default; +$primary-dark: findDarkColor($primary) !default; +$info-invert: findColorInvert($info) !default; +$info-light: findLightColor($info) !default; +$info-dark: findDarkColor($info) !default; +$success-invert: findColorInvert($success) !default; +$success-light: findLightColor($success) !default; +$success-dark: findDarkColor($success) !default; +$warning-invert: findColorInvert($warning) !default; +$warning-light: findLightColor($warning) !default; +$warning-dark: findDarkColor($warning) !default; +$danger-invert: findColorInvert($danger) !default; +$danger-light: findLightColor($danger) !default; +$danger-dark: findDarkColor($danger) !default; +$light-invert: findColorInvert($light) !default; +$dark-invert: findColorInvert($dark) !default; // General colors -$scheme-main : $white !default; -$scheme-main-bis : $white-bis !default; -$scheme-main-ter : $white-ter !default; -$scheme-invert : $black !default; -$scheme-invert-bis : $black-bis !default; -$scheme-invert-ter : $black-ter !default; +$scheme-main: $white !default; +$scheme-main-bis: $white-bis !default; +$scheme-main-ter: $white-ter !default; +$scheme-invert: $black !default; +$scheme-invert-bis: $black-bis !default; +$scheme-invert-ter: $black-ter !default; -$background : $white-ter !default; +$background: $white-ter !default; -$border : $grey-lighter !default; -$border-hover : $grey-light !default; -$border-light : $grey-lightest !default; +$border: $grey-lighter !default; +$border-hover: $grey-light !default; +$border-light: $grey-lightest !default; $border-light-hover: $grey-light !default; // Text colors -$text : $grey-dark !default; +$text: $grey-dark !default; $text-invert: findColorInvert($text) !default; -$text-light : $grey !default; +$text-light: $grey !default; $text-strong: $grey-darker !default; // Code colors -$code : $red !default; +$code: darken($red, 15%) !default; $code-background: $background !default; -$pre : $text !default; -$pre-background : $background !default; +$pre: $text !default; +$pre-background: $background !default; // Link colors -$link : $blue !default; -$link-invert : findColorInvert($link) !default; -$link-light : findLightColor($link) !default; -$link-dark : findDarkColor($link) !default; -$link-visited : $purple !default; +$link: $blue !default; +$link-invert: findColorInvert($link) !default; +$link-light: findLightColor($link) !default; +$link-dark: findDarkColor($link) !default; +$link-visited: $purple !default; -$link-hover : $grey-darker !default; -$link-hover-border : $grey-light !default; +$link-hover: $grey-darker !default; +$link-hover-border: $grey-light !default; -$link-focus : $grey-darker !default; -$link-focus-border : $blue !default; +$link-focus: $grey-darker !default; +$link-focus-border: $blue !default; -$link-active : $grey-darker !default; +$link-active: $grey-darker !default; $link-active-border: $grey-dark !default; // Typography -$family-primary : $family-sans-serif !default; +$family-primary: $family-sans-serif !default; $family-secondary: $family-sans-serif !default; -$family-code : $family-monospace !default; +$family-code: $family-monospace !default; -$size-small : $size-7 !default; +$size-small: $size-7 !default; $size-normal: $size-6 !default; $size-medium: $size-5 !default; -$size-large : $size-4 !default; +$size-large: $size-4 !default; + +// Effects + +$shadow: 0 0.5em 1em -0.125em rgba($scheme-invert, 0.1), 0 0px 0 1px rgba($scheme-invert, 0.02) !default; // Lists and maps $custom-colors: null !default; $custom-shades: null !default; -$colors: mergeColorMaps( -( - "white" : ($white, $black), - "black" : ($black, $white), - "light" : ($light, $light-invert), - "dark" : ($dark, $dark-invert), - "primary": ($primary, $primary-invert, $primary-light, $primary-dark), - "link" : ($link, $link-invert, $link-light, $link-dark), - "info" : ($info, $info-invert, $info-light, $info-dark), - "success": ($success, $success-invert, $success-light, $success-dark), - "warning": ($warning, $warning-invert, $warning-light, $warning-dark), - "danger" : ($danger, $danger-invert, $danger-light, $danger-dark)), - $custom-colors -) !default; +$colors: mergeColorMaps(("white": ($white, $black), "black": ($black, $white), "light": ($light, $light-invert), "dark": ($dark, $dark-invert), "primary": ($primary, $primary-invert, $primary-light, $primary-dark), "link": ($link, $link-invert, $link-light, $link-dark), "info": ($info, $info-invert, $info-light, $info-dark), "success": ($success, $success-invert, $success-light, $success-dark), "warning": ($warning, $warning-invert, $warning-light, $warning-dark), "danger": ($danger, $danger-invert, $danger-light, $danger-dark)), $custom-colors) !default; -$shades: mergeColorMaps( -( - "black-bis" : $black-bis, - "black-ter" : $black-ter, - "grey-darker" : $grey-darker, - "grey-dark" : $grey-dark, - "grey" : $grey, - "grey-light" : $grey-light, - "grey-lighter": $grey-lighter, - "white-ter" : $white-ter, - "white-bis" : $white-bis), - $custom-shades -) !default; +$shades: mergeColorMaps(("black-bis": $black-bis, "black-ter": $black-ter, "grey-darker": $grey-darker, "grey-dark": $grey-dark, "grey": $grey, "grey-light": $grey-light, "grey-lighter": $grey-lighter, "white-ter": $white-ter, "white-bis": $white-bis), $custom-shades) !default; $sizes: $size-1 $size-2 $size-3 $size-4 $size-5 $size-6 $size-7 !default; diff --git a/node_modules/bulma-scss/utilities/_extends.scss b/node_modules/bulma-scss/utilities/_extends.scss new file mode 100644 index 0000000000..99be636588 --- /dev/null +++ b/node_modules/bulma-scss/utilities/_extends.scss @@ -0,0 +1,33 @@ +@import "mixins"; + +%control { + @include control; +} + +%unselectable { + @include unselectable; +} + +%arrow { + @include arrow; +} + +%block { + @include block; +} + +%delete { + @include delete; +} + +%loader { + @include loader; +} + +%overlay { + @include overlay; +} + +%reset { + @include reset; +} diff --git a/node_modules/bulma-scss/utilities/_functions.scss b/node_modules/bulma-scss/utilities/_functions.scss index 3724df3133..c7a7580d86 100644 --- a/node_modules/bulma-scss/utilities/_functions.scss +++ b/node_modules/bulma-scss/utilities/_functions.scss @@ -74,7 +74,7 @@ } @else if $exp < 0 { @for $i from 1 through -$exp { - $value: $value / $number; + $value: divide($value, $number); } } @@ -90,13 +90,13 @@ @each $name, $value in $color-rgb { $adjusted: 0; - $value: $value / 255; + $value: divide($value, 255); @if $value < 0.03928 { - $value: $value / 12.92; + $value: divide($value, 12.92); } @else { - $value: ($value + 0.055) / 1.055; + $value: divide($value + 0.055, 1.055); $value: powerNumber($value, 2); } @@ -115,7 +115,7 @@ } } -@function findLightColor($color) { +@function findLightColor($color, $l: 96%) { @if type-of($color) == "color" { $l: 96%; @@ -129,9 +129,8 @@ @return $background; } -@function findDarkColor($color) { +@function findDarkColor($color, $base-l: 29%) { @if type-of($color) == "color" { - $base-l: 29%; $luminance: colorLuminance($color); $luminance-delta: 0.53 - $luminance; $target-l: round($base-l + $luminance-delta * 53); @@ -165,3 +164,36 @@ @return lighten($color, $amount); } + +// Custom divide function by @mdo from https://github.com/twbs/bootstrap/pull/34245 +// Replaces old slash division deprecated in Dart Sass +@function divide($dividend, $divisor, $precision: 10) { + $sign: if($dividend > 0 and $divisor > 0, 1, -1); + $dividend: abs($dividend); + $divisor: abs($divisor); + $quotient: 0; + $remainder: $dividend; + + @if $dividend == 0 { + @return 0; + } + + @if $divisor == 0 { + @error "Cannot divide by 0"; + } + + @if $divisor == 1 { + @return $dividend; + } + + @while $remainder >= $divisor { + $quotient: $quotient + 1; + $remainder: $remainder - $divisor; + } + + @if $remainder > 0 and $precision > 0 { + $remainder: divide($remainder * 10, $divisor, $precision - 1) * 0.1; + } + + @return ($quotient + $remainder) * $sign; +} diff --git a/node_modules/bulma-scss/utilities/_initial-variables.scss b/node_modules/bulma-scss/utilities/_initial-variables.scss index 9af866ade8..32503f108e 100644 --- a/node_modules/bulma-scss/utilities/_initial-variables.scss +++ b/node_modules/bulma-scss/utilities/_initial-variables.scss @@ -16,11 +16,11 @@ $white-bis: hsl(0, 0%, 98%) !default; $white: hsl(0, 0%, 100%) !default; $orange: hsl(14, 100%, 53%) !default; -$yellow: hsl(48, 100%, 67%) !default; -$green: hsl(141, 53%, 53%) !default; +$yellow: hsl(44, 100%, 77%) !default; +$green: hsl(153, 53%, 53%) !default; $turquoise: hsl(171, 100%, 41%) !default; -$cyan: hsl(204, 71%, 53%) !default; -$blue: hsl(217, 71%, 53%) !default; +$cyan: hsl(207, 61%, 53%) !default; +$blue: hsl(229, 53%, 53%) !default; $purple: hsl(271, 100%, 71%) !default; $red: hsl(348, 86%, 61%) !default; @@ -66,6 +66,7 @@ $widescreen-enabled: true !default; // 1344px container + 4rem $fullhd: 1344px + 2 * $gap !default; $fullhd-enabled: true !default; +$breakpoints: ("mobile": ("until": $tablet), "tablet": ("from": $tablet), "tablet-only": ("from": $tablet, "until": $desktop), "touch": ("from": $desktop), "desktop": ("from": $desktop), "desktop-only": ("from": $desktop, "until": $widescreen), "until-widescreen": ("until": $widescreen), "widescreen": ("from": $widescreen), "widescreen-only": ("from": $widescreen, "until": $fullhd), "until-fullhd": ("until": $fullhd), "fullhd": ("from": $fullhd)) !default; // Miscellaneous @@ -73,7 +74,7 @@ $easing: ease-out !default; $radius-small: 2px !default; $radius: 4px !default; $radius-large: 6px !default; -$radius-rounded: 290486px !default; +$radius-rounded: 9999px !default; $speed: 86ms !default; // Flags diff --git a/node_modules/bulma-scss/utilities/_mixins.scss b/node_modules/bulma-scss/utilities/_mixins.scss index b1bca0be07..4aab21d783 100644 --- a/node_modules/bulma-scss/utilities/_mixins.scss +++ b/node_modules/bulma-scss/utilities/_mixins.scss @@ -1,4 +1,4 @@ -@import "initial-variables"; +@import "derived-variables"; @mixin clearfix { &::after { @@ -12,12 +12,12 @@ position: absolute; @if $height != 0 { - left: calc(50% - (#{$width} / 2)); - top: calc(50% - (#{$height} / 2)); + left: calc(50% - (#{$width} * 0.5)); + top: calc(50% - (#{$height} * 0.5)); } @else { - left: calc(50% - (#{$width} / 2)); - top: calc(50% - (#{$width} / 2)); + left: calc(50% - (#{$width} * 0.5)); + top: calc(50% - (#{$width} * 0.5)); } } @@ -32,6 +32,11 @@ } @mixin hamburger($dimensions) { + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; + background: none; + border: none; cursor: pointer; display: block; height: $dimensions; @@ -99,6 +104,19 @@ } } +@mixin reset { + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; + background: none; + border: none; + color: currentColor; + font-family: inherit; + font-size: 1em; + margin: 0; + padding: 0; +} + // Responsiveness @mixin from($device) { @@ -113,6 +131,12 @@ } } +@mixin between($from, $until) { + @media screen and (min-width: $from) and (max-width: $until - 1px) { + @content; + } +} + @mixin mobile { @media screen and (max-width: $tablet - 1px) { @content; @@ -191,6 +215,31 @@ } } +@mixin breakpoint($name) { + $breakpoint: map-get($breakpoints, $name); + + @if $breakpoint { + $from: map-get($breakpoint, "from"); + $until: map-get($breakpoint, "until"); + + @if $from and $until { + @include between($from, $until) { + @content; + } + } + @else if $from { + @include from($from) { + @content; + } + } + @else if $until { + @include until($until) { + @content; + } + } + } +} + @mixin ltr { @if not $rtl { @content; @@ -237,10 +286,6 @@ user-select: none; } -%unselectable { - @include unselectable; -} - @mixin arrow($color: transparent) { border: 3px solid $color; border-radius: 2px; @@ -258,22 +303,14 @@ width: 0.625em; } -%arrow { - @include arrow; -} - @mixin block($spacing: $block-spacing) { &:not(:last-child) { margin-bottom: $spacing; } } -%block { - @include block; -} - @mixin delete { - @extend %unselectable; + @include unselectable; -moz-appearance: none; -webkit-appearance: none; @@ -356,10 +393,6 @@ } } -%delete { - @include delete; -} - @mixin loader { animation: spinAround 500ms infinite linear; border: 2px solid $grey-lighter; @@ -373,10 +406,6 @@ width: 1em; } -%loader { - @include loader; -} - @mixin overlay($offset: 0) { bottom: $offset; left: $offset; @@ -384,7 +413,3 @@ right: $offset; top: $offset; } - -%overlay { - @include overlay; -} diff --git a/node_modules/selectize-plugin-a11y/.eslintrc b/node_modules/selectize-plugin-a11y/.eslintrc index b27d99080a..89c8b9f1bd 100644 --- a/node_modules/selectize-plugin-a11y/.eslintrc +++ b/node_modules/selectize-plugin-a11y/.eslintrc @@ -1,248 +1,248 @@ -{ - "env": { - "browser": true, - "node": true, - "es6": false, - "jquery": true - }, - "globals": { - "yTos": true, - "$Y": true, - "define": true, - "require": true, - "tc_vars": true, - "$M": true, - "attachmentLabels": true - }, - "rules": { - "no-alert": "off", - "no-array-constructor": "off", - "no-caller": "off", - "no-case-declarations": "warn", - "no-catch-shadow": "off", - "no-class-assign": "error", - "no-confusing-arrow": "off", - "no-console": "off", - "no-const-assign": "error", - "no-constant-condition": "warn", - "no-continue": "off", - "no-control-regex": "error", - "no-debugger": "warn", - "no-delete-var": "error", - "no-div-regex": "off", - "no-dupe-class-members": "error", - "no-dupe-keys": "error", - "no-dupe-args": "error", - "no-duplicate-case": "error", - "no-duplicate-imports": "off", - "no-else-return": "off", - "no-empty-character-class": "error", - "no-empty-function": "off", - "no-empty-pattern": "error", - "no-eq-null": "off", - "no-eval": "off", - "no-ex-assign": "error", - "no-extend-native": "off", - "no-extra-bind": "off", - "no-extra-boolean-cast": "error", - "no-extra-label": "off", - "no-extra-parens": "off", - "no-extra-semi": "error", - "no-fallthrough": "error", - "no-floating-decimal": "off", - "no-func-assign": "error", - "no-implicit-coercion": "off", - "no-implicit-globals": "off", - "no-implied-eval": "off", - "no-inline-comments": "off", - "no-inner-declarations": "error", - "no-invalid-regexp": "error", - "no-irregular-whitespace": "error", - "no-iterator": "off", - "no-label-var": "off", - "no-labels": "off", - "no-lone-blocks": "off", - "no-lonely-if": "off", - "no-loop-func": "off", - "no-mixed-requires": "off", - "no-mixed-spaces-and-tabs": "error", - "no-multi-spaces": "off", - "no-multiple-empty-lines": [ - "warn", - { - "max": 1 - } - ], - "no-native-reassign": "off", - "no-negated-condition": "off", - "no-negated-in-lhs": "error", - "no-nested-ternary": "off", - "no-new-func": "off", - "no-new-object": "off", - "no-new-require": "off", - "no-new-symbol": "error", - "no-new-wrappers": "off", - "no-obj-calls": "error", - "no-octal": "error", - "no-octal-escape": "off", - "no-param-reassign": "off", - "no-path-concat": "off", - "no-plusplus": "off", - "no-process-env": "off", - "no-process-exit": "off", - "no-proto": "off", - "no-redeclare": "error", - "no-regex-spaces": "warn", - "no-restricted-globals": "off", - "no-restricted-imports": "off", - "no-restricted-modules": "off", - "no-restricted-syntax": "off", - "no-return-assign": "off", - "no-script-url": "off", - "no-self-assign": "error", - "no-self-compare": "off", - "no-sequences": "off", - "no-shadow": "off", - "no-shadow-restricted-names": "off", - "no-whitespace-before-property": "off", - "no-spaced-func": "warn", - "no-sparse-arrays": "error", - "no-sync": "off", - "no-ternary": "off", - "no-trailing-spaces": "off", - "no-this-before-super": "error", - "no-throw-literal": "off", - "no-undef": "error", - "no-undef-init": "off", - "no-undefined": "off", - "no-unexpected-multiline": "error", - "no-underscore-dangle": "off", - "no-unmodified-loop-condition": "off", - "no-unneeded-ternary": "off", - "no-unreachable": "error", - "no-unused-expressions": "off", - "no-unused-labels": "error", - "no-unused-vars": [ - "warn", - { - "vars": "local", - "args": "none" - } - ], - "no-useless-call": "off", - "no-useless-concat": "off", - "no-useless-constructor": "off", - "no-void": "off", - "no-var": "off", - "no-warning-comments": "off", - "no-with": "off", - "no-magic-numbers": "off", - "array-bracket-spacing": "off", - "array-callback-return": "off", - "arrow-body-style": "off", - "arrow-parens": "off", - "arrow-spacing": "off", - "accessor-pairs": "off", - "block-scoped-var": "off", - "block-spacing": "warn", - "brace-style": "off", - "callback-return": "off", - "camelcase": "off", - "comma-dangle": "error", - "comma-spacing": "warn", - "comma-style": "off", - "complexity": [ - "off", - 11 - ], - "computed-property-spacing": "off", - "consistent-return": "off", - "consistent-this": "off", - "constructor-super": "error", - "default-case": "off", - "dot-location": "off", - "dot-notation": "off", - "eol-last": "off", - "func-names": "off", - "func-style": "off", - "generator-star-spacing": "off", - "global-require": "off", - "handle-callback-err": "off", - "id-length": "off", - "indent": ["error", 2], - "init-declarations": "off", - "jsx-quotes": "off", - "key-spacing": "warn", - "keyword-spacing": "off", - "lines-around-comment": "off", - "max-depth": "off", - "max-len": "off", - "max-nested-callbacks": "off", - "max-params": "off", - "max-statements": "off", - "new-parens": "off", - "newline-after-var": "off", - "newline-before-return": "off", - "newline-per-chained-call": "off", - "object-curly-spacing": [ - "off", - "never" - ], - "object-shorthand": "off", - "one-var": [ - "warn", - "never" - ], - "one-var-declaration-per-line": "off", - "operator-assignment": "off", - "operator-linebreak": "off", - "padded-blocks": "off", - "prefer-arrow-callback": "off", - "prefer-const": "off", - "prefer-reflect": "off", - "prefer-rest-params": "off", - "prefer-spread": "off", - "prefer-template": "off", - "quote-props": "off", - "quotes": "off", - "radix": "off", - "id-match": "off", - "id-blacklist": "off", - "require-jsdoc": "off", - "require-yield": "off", - "semi": "warn", - "semi-spacing": "warn", - "sort-vars": "off", - "sort-imports": "off", - "space-before-blocks": "warn", - "space-before-function-paren": "warn", - "space-in-parens": "warn", - "space-infix-ops": "off", - "space-unary-ops": "off", - "spaced-comment": "off", - "strict": "off", - "template-curly-spacing": "off", - "use-isnan": "error", - "valid-jsdoc": "off", - "valid-typeof": "error", - "vars-on-top": "off", - "wrap-regex": "off", - "yield-star-spacing": "off", - "yoda": "off", - "no-bitwise": 2, - "curly": 0, - "eqeqeq": 0, - "guard-for-in": 2, - "no-cond-assign": [ - 2, - "except-parens" - ], - "wrap-iife": 0, - "linebreak-style": 0, - "no-use-before-define": 0, - "no-multi-str": 2, - "new-cap": 0, - "no-empty": 2, - "no-new": 2 - } -} +{ + "env": { + "browser": true, + "node": true, + "es6": false, + "jquery": true + }, + "globals": { + "yTos": true, + "$Y": true, + "define": true, + "require": true, + "tc_vars": true, + "$M": true, + "attachmentLabels": true + }, + "rules": { + "no-alert": "off", + "no-array-constructor": "off", + "no-caller": "off", + "no-case-declarations": "warn", + "no-catch-shadow": "off", + "no-class-assign": "error", + "no-confusing-arrow": "off", + "no-console": "off", + "no-const-assign": "error", + "no-constant-condition": "warn", + "no-continue": "off", + "no-control-regex": "error", + "no-debugger": "warn", + "no-delete-var": "error", + "no-div-regex": "off", + "no-dupe-class-members": "error", + "no-dupe-keys": "error", + "no-dupe-args": "error", + "no-duplicate-case": "error", + "no-duplicate-imports": "off", + "no-else-return": "off", + "no-empty-character-class": "error", + "no-empty-function": "off", + "no-empty-pattern": "error", + "no-eq-null": "off", + "no-eval": "off", + "no-ex-assign": "error", + "no-extend-native": "off", + "no-extra-bind": "off", + "no-extra-boolean-cast": "error", + "no-extra-label": "off", + "no-extra-parens": "off", + "no-extra-semi": "error", + "no-fallthrough": "error", + "no-floating-decimal": "off", + "no-func-assign": "error", + "no-implicit-coercion": "off", + "no-implicit-globals": "off", + "no-implied-eval": "off", + "no-inline-comments": "off", + "no-inner-declarations": "error", + "no-invalid-regexp": "error", + "no-irregular-whitespace": "error", + "no-iterator": "off", + "no-label-var": "off", + "no-labels": "off", + "no-lone-blocks": "off", + "no-lonely-if": "off", + "no-loop-func": "off", + "no-mixed-requires": "off", + "no-mixed-spaces-and-tabs": "error", + "no-multi-spaces": "off", + "no-multiple-empty-lines": [ + "warn", + { + "max": 1 + } + ], + "no-native-reassign": "off", + "no-negated-condition": "off", + "no-negated-in-lhs": "error", + "no-nested-ternary": "off", + "no-new-func": "off", + "no-new-object": "off", + "no-new-require": "off", + "no-new-symbol": "error", + "no-new-wrappers": "off", + "no-obj-calls": "error", + "no-octal": "error", + "no-octal-escape": "off", + "no-param-reassign": "off", + "no-path-concat": "off", + "no-plusplus": "off", + "no-process-env": "off", + "no-process-exit": "off", + "no-proto": "off", + "no-redeclare": "error", + "no-regex-spaces": "warn", + "no-restricted-globals": "off", + "no-restricted-imports": "off", + "no-restricted-modules": "off", + "no-restricted-syntax": "off", + "no-return-assign": "off", + "no-script-url": "off", + "no-self-assign": "error", + "no-self-compare": "off", + "no-sequences": "off", + "no-shadow": "off", + "no-shadow-restricted-names": "off", + "no-whitespace-before-property": "off", + "no-spaced-func": "warn", + "no-sparse-arrays": "error", + "no-sync": "off", + "no-ternary": "off", + "no-trailing-spaces": "off", + "no-this-before-super": "error", + "no-throw-literal": "off", + "no-undef": "error", + "no-undef-init": "off", + "no-undefined": "off", + "no-unexpected-multiline": "error", + "no-underscore-dangle": "off", + "no-unmodified-loop-condition": "off", + "no-unneeded-ternary": "off", + "no-unreachable": "error", + "no-unused-expressions": "off", + "no-unused-labels": "error", + "no-unused-vars": [ + "warn", + { + "vars": "local", + "args": "none" + } + ], + "no-useless-call": "off", + "no-useless-concat": "off", + "no-useless-constructor": "off", + "no-void": "off", + "no-var": "off", + "no-warning-comments": "off", + "no-with": "off", + "no-magic-numbers": "off", + "array-bracket-spacing": "off", + "array-callback-return": "off", + "arrow-body-style": "off", + "arrow-parens": "off", + "arrow-spacing": "off", + "accessor-pairs": "off", + "block-scoped-var": "off", + "block-spacing": "warn", + "brace-style": "off", + "callback-return": "off", + "camelcase": "off", + "comma-dangle": "error", + "comma-spacing": "warn", + "comma-style": "off", + "complexity": [ + "off", + 11 + ], + "computed-property-spacing": "off", + "consistent-return": "off", + "consistent-this": "off", + "constructor-super": "error", + "default-case": "off", + "dot-location": "off", + "dot-notation": "off", + "eol-last": "off", + "func-names": "off", + "func-style": "off", + "generator-star-spacing": "off", + "global-require": "off", + "handle-callback-err": "off", + "id-length": "off", + "indent": ["error", 2], + "init-declarations": "off", + "jsx-quotes": "off", + "key-spacing": "warn", + "keyword-spacing": "off", + "lines-around-comment": "off", + "max-depth": "off", + "max-len": "off", + "max-nested-callbacks": "off", + "max-params": "off", + "max-statements": "off", + "new-parens": "off", + "newline-after-var": "off", + "newline-before-return": "off", + "newline-per-chained-call": "off", + "object-curly-spacing": [ + "off", + "never" + ], + "object-shorthand": "off", + "one-var": [ + "warn", + "never" + ], + "one-var-declaration-per-line": "off", + "operator-assignment": "off", + "operator-linebreak": "off", + "padded-blocks": "off", + "prefer-arrow-callback": "off", + "prefer-const": "off", + "prefer-reflect": "off", + "prefer-rest-params": "off", + "prefer-spread": "off", + "prefer-template": "off", + "quote-props": "off", + "quotes": "off", + "radix": "off", + "id-match": "off", + "id-blacklist": "off", + "require-jsdoc": "off", + "require-yield": "off", + "semi": "warn", + "semi-spacing": "warn", + "sort-vars": "off", + "sort-imports": "off", + "space-before-blocks": "warn", + "space-before-function-paren": "warn", + "space-in-parens": "warn", + "space-infix-ops": "off", + "space-unary-ops": "off", + "spaced-comment": "off", + "strict": "off", + "template-curly-spacing": "off", + "use-isnan": "error", + "valid-jsdoc": "off", + "valid-typeof": "error", + "vars-on-top": "off", + "wrap-regex": "off", + "yield-star-spacing": "off", + "yoda": "off", + "no-bitwise": 2, + "curly": 0, + "eqeqeq": 0, + "guard-for-in": 2, + "no-cond-assign": [ + 2, + "except-parens" + ], + "wrap-iife": 0, + "linebreak-style": 0, + "no-use-before-define": 0, + "no-multi-str": 2, + "new-cap": 0, + "no-empty": 2, + "no-new": 2 + } +} diff --git a/node_modules/selectize-plugin-a11y/LICENSE b/node_modules/selectize-plugin-a11y/LICENSE index d33fbaa649..e42d77363a 100644 --- a/node_modules/selectize-plugin-a11y/LICENSE +++ b/node_modules/selectize-plugin-a11y/LICENSE @@ -1,7 +1,7 @@ -Copyright 2018-present Salmen Bejaoui - -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. - +Copyright 2018-present Salmen Bejaoui + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/node_modules/selectize-plugin-a11y/README.md b/node_modules/selectize-plugin-a11y/README.md index 9c42eff2e4..bd1270fb0a 100644 --- a/node_modules/selectize-plugin-a11y/README.md +++ b/node_modules/selectize-plugin-a11y/README.md @@ -1,24 +1,24 @@ -# selectize-plugin-a11y.js -Selectize-plugin-a11y is a plugin to make Selectize.js accessible as a Combobox. - -## Selectize-plugin-a11y – Usage - -```html - - - -``` - -## Pull requests are always welcome -Any pull request to improve the plugin will be appreciated 😉 +# selectize-plugin-a11y.js +Selectize-plugin-a11y is a plugin to make Selectize.js accessible as a Combobox. + +## Selectize-plugin-a11y – Usage + +```html + + + +``` + +## Pull requests are always welcome +Any pull request to improve the plugin will be appreciated 😉 diff --git a/node_modules/selectize-plugin-a11y/examples/api.html b/node_modules/selectize-plugin-a11y/examples/api.html index a37aeb791b..b40b355b89 100644 --- a/node_modules/selectize-plugin-a11y/examples/api.html +++ b/node_modules/selectize-plugin-a11y/examples/api.html @@ -1,38 +1,38 @@ - - - - - - - Accessibile Selectize - - - -

    Selectize-plugin-a11y.js

    -

    Make Selectize.js accessibile

    - - - - - - - - - + + + + + + + Accessibile Selectize + + + +

    Selectize-plugin-a11y.js

    +

    Make Selectize.js accessibile

    + + + + + + + + + \ No newline at end of file diff --git a/node_modules/selectize-plugin-a11y/package.json b/node_modules/selectize-plugin-a11y/package.json index 8e4ccbf8f6..7fd1fb11e8 100644 --- a/node_modules/selectize-plugin-a11y/package.json +++ b/node_modules/selectize-plugin-a11y/package.json @@ -1,27 +1,27 @@ -{ - "name": "selectize-plugin-a11y", - "version": "1.1.0", - "description": "Make Selectize.js accessibile", - "main": "selectize-plugin-a11y.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/SLMNBJ/selectize-plugin-a11y.git" - }, - "keywords": [ - "selectize", - "selectize.js", - "a11y", - "accessibility", - "selectize", - "voiceover" - ], - "author": "Salmen Bejaoui", - "license": "MIT", - "bugs": { - "url": "https://github.com/SLMNBJ/selectize-plugin-a11y/issues" - }, - "homepage": "https://github.com/SLMNBJ/selectize-plugin-a11y#readme" -} +{ + "name": "selectize-plugin-a11y", + "version": "1.1.0", + "description": "Make Selectize.js accessibile", + "main": "selectize-plugin-a11y.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/SLMNBJ/selectize-plugin-a11y.git" + }, + "keywords": [ + "selectize", + "selectize.js", + "a11y", + "accessibility", + "selectize", + "voiceover" + ], + "author": "Salmen Bejaoui", + "license": "MIT", + "bugs": { + "url": "https://github.com/SLMNBJ/selectize-plugin-a11y/issues" + }, + "homepage": "https://github.com/SLMNBJ/selectize-plugin-a11y#readme" +} diff --git a/package-lock.json b/package-lock.json index 5fef3710e4..64a3eef24b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "dependencies": { "@fontsource/raleway": "^4.5.0", "@popperjs/core": "^2.4.4", - "bulma-scss": "^0.9.0-1", + "bulma-scss": "^0.9.4", "datatables.net": "^1.11.3", "datatables.net-fixedheader": "^3.1.2", "datatables.net-responsive": "^2.1.0", @@ -32,8 +32,9 @@ } }, "node_modules/bulma-scss": { - "version": "0.9.0-1", - "license": "MIT" + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/bulma-scss/-/bulma-scss-0.9.4.tgz", + "integrity": "sha512-iMrNFlR6yEMutYEAi1VseAmsadmmWnb8A2DghUR7jZKJLaw6AibmHOkqwlEPjsXkqJ7Pp1z/bEJ6gFMG0WNdJw==" }, "node_modules/datatables.net": { "version": "1.11.3", @@ -114,7 +115,9 @@ "version": "2.4.4" }, "bulma-scss": { - "version": "0.9.0-1" + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/bulma-scss/-/bulma-scss-0.9.4.tgz", + "integrity": "sha512-iMrNFlR6yEMutYEAi1VseAmsadmmWnb8A2DghUR7jZKJLaw6AibmHOkqwlEPjsXkqJ7Pp1z/bEJ6gFMG0WNdJw==" }, "datatables.net": { "version": "1.11.3", diff --git a/package.json b/package.json index d64922e112..3c1107b07d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "dependencies": { "@fontsource/raleway": "^4.5.0", "@popperjs/core": "^2.4.4", - "bulma-scss": "^0.9.0-1", + "bulma-scss": "^0.9.4", "datatables.net": "^1.11.3", "datatables.net-fixedheader": "^3.1.2", "datatables.net-responsive": "^2.1.0", diff --git a/pages/UI.php b/pages/UI.php index 77847b8a7d..7a79afe18c 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -14,7 +14,6 @@ use Combodo\iTop\Application\UI\Base\Component\GlobalSearch\GlobalSearchHelper; use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\QuickCreate\QuickCreateHelper; -use Combodo\iTop\Application\UI\Base\Component\Title\Title; use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Toolbar\ToolbarUIBlockFactory; use Combodo\iTop\Application\UI\Base\Layout\PageContent\PageContentFactory; @@ -346,22 +345,30 @@ try case 'details': // Details of an object $sClass = utils::ReadParam('class', '', false, 'class'); - $id = utils::ReadParam('id', ''); - if ( empty($sClass) || empty($id)) - { - throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); + + if (empty($sClass)) { + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); } - if (is_numeric($id)) - { - $oObj = MetaModel::GetObject($sClass, $id, false /* MustBeFound */); + $id = utils::ReadParam('id', null); + if (false === is_null($id)) { + if (is_numeric($id)) { + $oObj = MetaModel::GetObject($sClass, $id, false /* MustBeFound */); + } else { + $oObj = MetaModel::GetObjectByName($sClass, $id, false /* MustBeFound */); + } + } else { + $sAttCode = utils::ReadParam('attcode', '', false, utils::ENUM_SANITIZATION_FILTER_FIELD_NAME); + $sAttValue = utils::ReadParam('attvalue', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA); + + if ((strlen($sAttCode) === 0) || (strlen($sAttValue) === 0)) { + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'id')); + } + + $oObj = MetaModel::GetObjectByColumn($sClass, $sAttCode, $sAttValue, true); } - else - { - $oObj = MetaModel::GetObjectByName($sClass, $id, false /* MustBeFound */); - } - if (is_null($oObj)) - { + + if (is_null($oObj)) { // Check anyhow if there is a message for this object (like you've just created it) $sMessageKey = $sClass.'::'.$id; DisplayMessages($sMessageKey, $oP); @@ -369,8 +376,7 @@ try // Attempt to load the object in archive mode utils::PushArchiveMode(true); - if (is_numeric($id)) - { + if (is_numeric($id)) { $oObj = MetaModel::GetObject($sClass, $id, false /* MustBeFound */); } else @@ -626,17 +632,17 @@ try $oP->SetBreadCrumbEntry($sPageId, $sLabel, $sDescription, '', 'fas fa-search', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES); $oP->add("
    \n"); $oP->add("
    \n"); - $oP->add(' '.Dict::Format('UI:Search:Ongoing', htmlentities($sFullText, ENT_QUOTES, 'UTF-8')).''); + $oP->add(' '.Dict::Format('UI:Search:Ongoing', utils::EscapeHtml($sFullText)).''); $oP->add("
    \n"); $oP->add("
    \n"); $oP->add("
     
    \n"); - $oP->add("

    ".Dict::Format('UI:FullTextSearchTitle_Text', htmlentities($sFullText, ENT_QUOTES, 'UTF-8'))."

    "); + $oP->add("

    ".Dict::Format('UI:FullTextSearchTitle_Text', utils::EscapeHtml($sFullText))."

    "); $oP->add("
    \n"); $oP->add("
    \n"); $sJSClass = addslashes($sClassName); $sJSNeedles = json_encode($aFullTextNeedles); $oP->add_ready_script( -<<AddHeaderMessage($sWarnings, 'message_warning'); } - cmdbAbstractObject::DisplayCreationForm($oP, $sClass, $oObj); + cmdbAbstractObject::DisplayCreationForm($oP, $sClass, $oObj, [], ['transaction_id' => $sTransactionId]); } } break; @@ -1455,7 +1461,7 @@ EOF if ( ($iFlags & OPT_ATT_SLAVE) && ($paramValue != $oObj->Get($sAttCode)) ) { $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $aErrors[] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $oAttDef->GetLabel()); + $aErrors[] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $oAttDef->GetLabel(), $sAttCode); unset($aExpectedAttributes[$sAttCode]); } } @@ -1475,7 +1481,10 @@ EOF } else { - $sError = '

    '.implode('

    ',$aErrors)."

    \n"; + $aErrorsToDisplay = array_map(function($sError) { + return utils::HtmlEntities($sError); + }, $aErrors); + $sError = '

    '.implode('

    ',$aErrorsToDisplay)."

    \n"; } } else @@ -1779,7 +1788,7 @@ EOF $oP->SetCurrentTabContainer('Navigator'); $sFirstTab = MetaModel::GetConfig()->Get('impact_analysis_first_tab'); - $sLazyLoading = MetaModel::GetConfig()->Get('impact_analysis_lazy_loading'); + $bLazyLoading = MetaModel::GetConfig()->Get('impact_analysis_lazy_loading'); $sContextKey = "itop-config-mgmt/relation_context/$sClass/$sRelation/$sDirection"; // Check if the current object supports Attachments, similar to AttachmentPlugin::IsTargetObject @@ -1800,13 +1809,13 @@ EOF { DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj); $oP->SetCurrentTab('UI:RelationshipGraph'); - $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, array(), $sClassForAttachment, $iIdForAttachment, $sContextKey, array('this' => $oObj),$sLazyLoading); + $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, array(), $sClassForAttachment, $iIdForAttachment, $sContextKey, array('this' => $oObj),$bLazyLoading); DisplayNavigatorGroupTab($oP); } else { $oP->SetCurrentTab('UI:RelationshipGraph'); - $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, array(), $sClassForAttachment, $iIdForAttachment, $sContextKey, array('this' => $oObj),$sLazyLoading); + $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, array(), $sClassForAttachment, $iIdForAttachment, $sContextKey, array('this' => $oObj),$bLazyLoading); DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj); DisplayNavigatorGroupTab($oP); } @@ -1945,7 +1954,7 @@ class UI $oFullSetFilter->UpdateContextFromUser(); $aSelectedObj = utils::ReadMultipleSelection($oFullSetFilter); $sCancelUrl = "./UI.php?operation=search&filter=".urlencode($sFilter)."&".$oAppContext->GetForLink(); - $aContext = array('filter' => htmlentities($sFilter, ENT_QUOTES, 'UTF-8')); + $aContext = array('filter' => utils::EscapeHtml($sFilter)); cmdbAbstractObject::DisplayBulkModifyForm($oP, $sClass, $aSelectedObj, 'preview_or_modify_all', $sCancelUrl, array(), $aContext); } @@ -1980,7 +1989,7 @@ class UI $aSelectedObj = explode(',', $sSelectedObj); $sCancelUrl = "./UI.php?operation=search&filter=".urlencode($sFilter)."&".$oAppContext->GetForLink(); $aContext = array( - 'filter' => htmlentities($sFilter, ENT_QUOTES, 'UTF-8'), + 'filter' => utils::EscapeHtml($sFilter), 'selectObj' => $sSelectedObj, ); cmdbAbstractObject::DoBulkModify($oP, $sClass, $aSelectedObj, 'preview_or_modify_all', $bPreview, $sCancelUrl, $aContext); diff --git a/pages/ajax.csvimport.php b/pages/ajax.csvimport.php index de1faef78f..7b11875933 100644 --- a/pages/ajax.csvimport.php +++ b/pages/ajax.csvimport.php @@ -15,7 +15,6 @@ use Combodo\iTop\Renderer\BlockRenderer; require_once('../approot.inc.php'); require_once(APPROOT.'/application/application.inc.php'); -require_once(APPROOT.'/application/ajaxwebpage.class.inc.php'); require_once(APPROOT.'/application/wizardhelper.class.inc.php'); require_once(APPROOT.'/application/ui.linkswidget.class.inc.php'); @@ -239,7 +238,7 @@ try if (($bFirstLineAsHeader) && ($index == 1)) { $aColumns[] = ["label" => sprintf($sFormat, $index)]; foreach ($aRow as $sCell) { - $aColumns[] = ["label" => htmlentities($sCell, ENT_QUOTES, 'UTF-8')]; + $aColumns[] = ["label" => utils::EscapeHtml($sCell)]; } $iNbCols = count($aRow); } else { @@ -249,7 +248,7 @@ try } $aTableRow[] = sprintf($sFormat, $index); foreach ($aRow as $sCell) { - $aTableRow[] = htmlentities($sCell, ENT_QUOTES, 'UTF-8'); + $aTableRow[] = utils::EscapeHtml($sCell); } $aTableData[$index] = $aTableRow; } @@ -323,8 +322,8 @@ try $aTableRow['HeaderFields'] = utils::HtmlEntities($sField); $aTableRow['HeaderMapipngs'] = BlockRenderer::RenderBlockTemplates(GetMappingForField($sClassName, $sField, $index, $bAdvanced, $sDefaultChoice)); $aTableRow['HeaderSearch'] = ''; - $aTableRow['DataLine1'] = (isset($aData[$iStartLine][$index - 1]) ? htmlentities($aData[$iStartLine][$index - 1], ENT_QUOTES, 'UTF-8') : ' '); - $aTableRow['DataLine2'] = (isset($aData[$iStartLine + 1][$index - 1]) ? htmlentities($aData[$iStartLine + 1][$index - 1], ENT_QUOTES, 'UTF-8') : ' '); + $aTableRow['DataLine1'] = (isset($aData[$iStartLine][$index - 1]) ? utils::EscapeHtml($aData[$iStartLine][$index - 1]) : ' '); + $aTableRow['DataLine2'] = (isset($aData[$iStartLine + 1][$index - 1]) ? utils::EscapeHtml($aData[$iStartLine + 1][$index - 1]) : ' '); $aTableData[$index] = $aTableRow; $index++; } diff --git a/pages/ajax.document.php b/pages/ajax.document.php index 9257b0a265..3706532ba8 100644 --- a/pages/ajax.document.php +++ b/pages/ajax.document.php @@ -107,7 +107,7 @@ try catch (Exception $e) { // note: transform to cope with XSS attacks - echo htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8'); + echo utils::EscapeHtml($e->GetMessage()); IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString()); } diff --git a/pages/ajax.render.php b/pages/ajax.render.php index a704c407f5..66e5b1931c 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -364,7 +364,6 @@ try $sContains = utils::ReadParam('q', '', false, 'raw_data'); $bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true'); $sOutputFormat = utils::ReadParam('sOutputFormat', UIExtKeyWidget::ENUM_OUTPUT_FORMAT_CSV, false, 'raw_data'); - $sAutocompleteOperation = utils::ReadParam('sAutocompleteOperation', null, false, 'raw_data'); if ($sContains != '') { if (!empty($sJson)) @@ -378,7 +377,7 @@ try $oObj = null; } $oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, '', $bSearchMode); - $oWidget->AutoComplete($oPage, $sFilter, $oObj, $sContains, $sOutputFormat, $sAutocompleteOperation); + $oWidget->AutoComplete($oPage, $sFilter, $oObj, $sContains, $sOutputFormat); } break; @@ -562,7 +561,7 @@ try if (!$oAttDef->IsWritable() || ($oWizardHelper->GetReturnNotEditableFields())) { // Even non-writable fields (like AttributeExternal) can be refreshed - $sHTMLValue = $oObj->GetAsHTML($sAttCode); + $sHTMLValue = "
    ".$oObj->GetAsHTML($sAttCode)."
    "; } else { @@ -687,7 +686,7 @@ try if ($sFilter != '') { $oFilter = DBObjectSearch::FromOQL($sFilter); $oDisplayBlock = new DisplayBlock($oFilter, 'chart_ajax', false); - $oBlock = $oDisplayBlock->GetRenderContent($oPage, $aParams); + $oBlock = $oDisplayBlock->GetRenderContent($oPage, $aParams, $aParams['currentId']); $sChartType = isset($aParams['chart_type']) ? $aParams['chart_type'] : 'pie'; switch ($sChartType) { case 'bars': @@ -888,6 +887,9 @@ try break; case 'import_dashboard': + $oPage = new JsonPage(); + $oPage->SetOutputDataOnly(true); + $sTransactionId = utils::ReadParam('transaction_id', '', false, 'transaction_id'); if (!utils::IsTransactionValid($sTransactionId, true)) { @@ -916,7 +918,7 @@ try { $aResult['error'] = 'Dashboard id="'.$sDashboardId.'" not found.'; } - $oPage->add(json_encode($aResult)); + $oPage->SetData($aResult); break; case 'toggle_dashboard': @@ -929,7 +931,7 @@ try $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); $sDashboardFile = utils::ReadParam('file', '', false, 'raw_data'); - $sReloadURL = utils::ReadParam('reload_url', '', false, 'raw_data'); + $sReloadURL = utils::ReadParam('reload_url', '', false, utils::ENUM_SANITIZATION_FILTER_URL); $oDashboard = RuntimeDashboard::GetDashboard($sDashboardFile, $sDashboardId); $aResult = array('error' => ''); if (!is_null($oDashboard)) @@ -940,8 +942,6 @@ try } $oDashboard->Render($oPage, false, $aExtraParams); } - //$oPage->add_ready_script("$('.ibo-dashboard table.listResults').tableHover(); $('.ibo-dashboard table.listResults') - //.tablesorter( { widgets: ['myZebra', 'truncatedList']} );"); break; case 'reload_dashboard': @@ -949,7 +949,7 @@ try $sDashboardId = utils::ReadParam('dashboard_id', '', false, 'raw_data'); $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); $sDashboardFile = utils::ReadParam('file', '', false, 'raw_data'); - $sReloadURL = utils::ReadParam('reload_url', '', false, 'raw_data'); + $sReloadURL = utils::ReadParam('reload_url', '', false, utils::ENUM_SANITIZATION_FILTER_URL); $oDashboard = RuntimeDashboard::GetDashboard($sDashboardFile, $sDashboardId); $aResult = array('error' => ''); if (!is_null($oDashboard)) @@ -960,14 +960,13 @@ try } $oDashboard->Render($oPage, false, $aExtraParams); } - //$oPage->add_ready_script("$('.ibo-dashboard table.listResults').tableHover(); $('.ibo-dashboard table.listResults') - //.tablesorter( { widgets: ['myZebra', 'truncatedList']} );"); break; case 'save_dashboard': $sDashboardId = utils::ReadParam('dashboard_id', '', false, 'context_param'); + $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); - $sReloadURL = utils::ReadParam('reload_url', '', false, 'raw_data'); + $sReloadURL = utils::ReadParam('reload_url', '', false, utils::ENUM_SANITIZATION_FILTER_URL); appUserPreferences::SetPref('display_original_dashboard_'.$sDashboardId, false); $sJSExtraParams = json_encode($aExtraParams); $aParams = array(); @@ -979,14 +978,21 @@ try $oDashboard = new RuntimeDashboard($sDashboardId); $oDashboard->FromParams($aParams); - $oDashboard->Save(); + $bIsNew = $oDashboard->Save(); $sDashboardFile = addslashes(utils::ReadParam('file', '', false, 'string')); $sDashboardDivId = preg_replace('/[^a-zA-Z0-9_]/', '', $sDashboardId); - - // trigger a reload of the current page since the dashboard just changed - $oPage->add_script( - <<add_script( + <<add_script( + <<Revert(); @@ -1031,7 +1038,7 @@ EOF $aParams['cells'] = utils::ReadParam('cells', array(), false, 'raw_data'); $aParams['auto_reload'] = utils::ReadParam('auto_reload', false); $aParams['auto_reload_sec'] = utils::ReadParam('auto_reload_sec', 300); - $sReloadURL = utils::ReadParam('reload_url', '', false, 'raw_data'); + $sReloadURL = utils::ReadParam('reload_url', '', false, utils::ENUM_SANITIZATION_FILTER_URL); $oDashboard = new RuntimeDashboard($sDashboardId); $oDashboard->FromParams($aParams); $oDashboard->SetReloadURL($sReloadURL); @@ -1043,7 +1050,7 @@ EOF $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); $aExtraParams['dashboard_div_id'] = utils::Sanitize($sId, '', 'element_identifier'); $sDashboardFile = utils::ReadParam('file', '', false, 'string'); - $sReloadURL = utils::ReadParam('reload_url', '', false, 'raw_data'); + $sReloadURL = utils::ReadParam('reload_url', '', false, utils::ENUM_SANITIZATION_FILTER_URL); $oDashboard = RuntimeDashboard::GetDashboardToEdit($sDashboardFile, $sId); if (!is_null($oDashboard)) { if (!empty($sReloadURL)) { @@ -1110,7 +1117,16 @@ EOF $aUpdatedDecoded = array(); foreach ($aUpdatedProperties as $sProp) { $sDecodedProp = str_replace('attr_', '', $sProp); // Remove the attr_ prefix - $aCurrentValues[$sDecodedProp] = (isset($aPreviousValues[$sProp]) ? $aPreviousValues[$sProp] : ''); // Set the previous value + // Set the previous value + if ( isset($aPreviousValues[$sProp]) && $aPreviousValues[$sProp] != '' ){ + $aCurrentValues[$sDecodedProp] = $aPreviousValues[$sProp]; + } else { + if(gettype($aCurrentValues[$sDecodedProp]) == "array") { + $aCurrentValues[$sDecodedProp] = []; + } else { + $aCurrentValues[$sDecodedProp] = ''; + } + } $aUpdatedDecoded[] = $sDecodedProp; } @@ -1422,7 +1438,7 @@ EOF; $sEnlargeButton = ''; if ($bEnableEnlarge) { - $sEnlargeButton = " "; + $sEnlargeButton = " "; } if ($oSet->Count() > 0) { @@ -1851,7 +1867,8 @@ EOF // Save the generated PDF as an attachment $sPDF = $oPage->get_pdf(); - $oPage = new AjaxPage(''); + $oPage = new JsonPage(); + $oPage->SetOutputDataOnly(true); $oAttachment = MetaModel::NewObject('Attachment'); $oAttachment->Set('item_class', $sObjClass); $oAttachment->Set('item_id', $iObjKey); @@ -1862,7 +1879,7 @@ EOF 'status' => 'ok', 'att_id' => $iAttachmentId, ); - $oPage->add(json_encode($aRet)); + $oPage->SetData($aRet); } break; @@ -2040,10 +2057,14 @@ EOF break; case 'export_build': + $oPage = new JsonPage(); + $oPage->SetOutputDataOnly(true); $oAjaxRenderController->ExportBuild($oPage, false); break; case 'export_build_portal': + $oPage = new JsonPage(); + $oPage->SetOutputDataOnly(true); $oAjaxRenderController->ExportBuild($oPage, true); break; @@ -2163,6 +2184,9 @@ EOF break; case 'cke_img_upload': + $oPage = new JsonPage(); + $oPage->SetOutputDataOnly(true); + // Image uploaded via CKEditor $aResult = array( 'uploaded' => 0, @@ -2204,17 +2228,16 @@ EOF $iAttId = $oAttachment->DBInsert(); $aResult['uploaded'] = 1; - $aResult['msg'] = htmlentities($oDoc->GetFileName(), ENT_QUOTES, 'UTF-8'); + $aResult['msg'] = utils::EscapeHtml($oDoc->GetFileName()); $aResult['fileName'] = $oDoc->GetFileName(); $aResult['url'] = utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL.$iAttId.'&s='.$oAttachment->Get('secret'); - if (is_array($aDimensions)) - { + if (is_array($aDimensions)) { $aResult['width'] = $aDimensions['width']; $aResult['height'] = $aDimensions['height']; } - IssueLog::Trace('InlineImage created', LogChannels::INLINE_IMAGE, array( - '$operation' => $operation, + IssueLog::Trace('InlineImage created', LogChannels::INLINE_IMAGE, array( + '$operation' => $operation, '$aResult' => $aResult, 'secret' => $oAttachment->Get('secret'), 'temp_id' => $sTempId, @@ -2233,7 +2256,7 @@ EOF $aResult['error'] = $e->GetMessage(); } } - $oPage->add(json_encode($aResult)); + $oPage->SetData($aResult); break; /** @noinspection PhpMissingBreakStatementInspection cke_upload_and_browse and cke_browse are chained */ @@ -2407,9 +2430,8 @@ EOF while ($oAttachment = $oSet->Fetch()) { $oDoc = $oAttachment->Get('contents'); - if ($oDoc->GetMainMimeType() == 'image') - { - $sDocName = addslashes(htmlentities($oDoc->GetFileName(), ENT_QUOTES, 'UTF-8')); + if ($oDoc->GetMainMimeType() == 'image') { + $sDocName = addslashes(utils::EscapeHtml($oDoc->GetFileName())); $iAttId = $oAttachment->GetKey(); $sSecret = $oAttachment->Get('secret'); $oPage->add("
    \"$sDocName\"
    "); @@ -2495,7 +2517,12 @@ EOF ); $oSet = new DBObjectSet($oSearch, [], $aSearchParams); - $oSet->OptimizeColumnLoad([$oSearch->GetClassAlias() => [$sObjectImageAttCode]]); + // Optimize fields to load + $aObjectAttCodesToLoad = []; + if (MetaModel::IsValidAttCode($sSearchMainClassName, $sObjectImageAttCode)) { + $aObjectAttCodesToLoad[] = $sObjectImageAttCode; + } + $oSet->OptimizeColumnLoad([$oSearch->GetClassAlias() => $aObjectAttCodesToLoad]); $oSet->SetLimit(MetaModel::GetConfig()->Get('max_autocomplete_results')); // Note: We have to this manually because of a bug in DBSearch not checking the user prefs. by default. $oSet->SetShowObsoleteData(utils::ShowObsoleteData()); @@ -2632,7 +2659,8 @@ EOF // Navigation menu //-------------------------------- case 'get_menus_count': - + $oPage = new JsonPage(); + $oPage->SetOutputDataOnly(true); $oAjaxRenderController->GetMenusCount($oPage); break; @@ -2641,9 +2669,8 @@ EOF } $oKPI->ComputeAndReport('Data fetch and format'); $oPage->output(); -} catch (Exception $e) -{ +} catch (Exception $e) { // note: transform to cope with XSS attacks - echo htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8'); + echo utils::EscapeHtml($e->GetMessage()); IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString()); } diff --git a/pages/ajax.searchform.php b/pages/ajax.searchform.php index ae9067e28c..82ba670bf0 100644 --- a/pages/ajax.searchform.php +++ b/pages/ajax.searchform.php @@ -18,7 +18,7 @@ try $oKPI->ComputeAndReport('Data model loaded'); $oKPI = new ExecutionKPI(); - if (LoginWebPage::EXIT_CODE_OK != LoginWebPage::DoLoginEx(null /* any portal */, false, LoginWebPage::EXIT_RETURN)) + if (LoginWebPage::EXIT_CODE_OK != LoginWebPage::DoLoginEx('backoffice', false, LoginWebPage::EXIT_RETURN)) { throw new SecurityException('You must be logged in'); } @@ -94,41 +94,33 @@ try $oCollapsible = CollapsibleSectionUIBlockFactory::MakeStandard(Dict::S('UI:RunQuery:MoreInfo')); $oPage->AddSubBlock($oCollapsible); - $oHtml = new Html(Dict::S('UI:RunQuery:DevelopedQuery').htmlentities($oFilter->ToOQL(), ENT_QUOTES, 'UTF-8')); + $oHtml = new Html(Dict::S('UI:RunQuery:DevelopedQuery').utils::EscapeHtml($oFilter->ToOQL())); $oCollapsible->AddSubBlock($oHtml); - - /*$oPage->StartCollapsibleSection(Dict::S('UI:RunQuery:MoreInfo'), false, 'SearchQuery'); - $oPage->p(Dict::S('UI:RunQuery:DevelopedQuery').htmlentities($oFilter->ToOQL(), ENT_QUOTES, 'UTF-8')); - $oPage->EndCollapsibleSection();*/ } $oPage->output(); -} catch (AjaxSearchException $e) -{ +} catch (AjaxSearchException $e) { http_response_code($e->getCode()); // note: transform to cope with XSS attacks - echo '
    ' . htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8') . '
    '; + echo '
    '.utils::EscapeHtml($e->GetMessage()).'
    '; IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString()); -} catch (SecurityException $e) -{ +} catch (SecurityException $e) { http_response_code(403); // note: transform to cope with XSS attacks - echo '
    ' . htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8') . '
    '; + echo '
    '.utils::EscapeHtml($e->GetMessage()).'
    '; IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString()); -} catch (MySQLException $e) -{ +} catch (MySQLException $e) { http_response_code(500); // Sanytize error: $sMsg = $e->GetMessage(); $sMsg = preg_replace("@^.* mysql_error = @", '', $sMsg); // note: transform to cope with XSS attacks - echo '
    '.htmlentities($sMsg, ENT_QUOTES, 'utf-8').'
    '; + echo '
    '.utils::EscapeHtml($sMsg).'
    '; IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString()); -} catch (Exception $e) -{ +} catch (Exception $e) { http_response_code(500); // note: transform to cope with XSS attacks - echo '
    ' . htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8') . '
    '; + echo '
    '.utils::EscapeHtml($e->GetMessage()).'
    '; IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString()); } \ No newline at end of file diff --git a/pages/audit.php b/pages/audit.php index 3d5e7a4c95..b75bfc2c99 100644 --- a/pages/audit.php +++ b/pages/audit.php @@ -157,9 +157,7 @@ try { require_once('../approot.inc.php'); require_once(APPROOT.'/application/application.inc.php'); - require_once(APPROOT.'/application/itopwebpage.class.inc.php'); - require_once(APPROOT.'/application/startup.inc.php'); $operation = utils::ReadParam('operation', ''); $oAppContext = new ApplicationContext(); @@ -411,7 +409,7 @@ try 'percentage_ok' => array('label' => Dict::S('UI:Audit:PercentageOk'), 'description' => Dict::S('UI:Audit:PercentageOk')), ); - $oAttachmentTableBlock = DataTableUIBlockFactory::MakeForStaticData('', $aAttribs, $aData); + $oAttachmentTableBlock = DataTableUIBlockFactory::MakeForStaticData('', $aAttribs, $aData, null, [], "", array('pageLength' => -1)); $oAuditCategoryPanelBlock->AddSubBlock($oAttachmentTableBlock); $aAuditCategoryPanels[] = $oAuditCategoryPanelBlock; } diff --git a/pages/csvimport.php b/pages/csvimport.php index 886c163521..31b3e968d9 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -33,7 +33,6 @@ use Combodo\iTop\Renderer\BlockRenderer; try { require_once('../approot.inc.php'); require_once(APPROOT.'/application/application.inc.php'); - require_once(APPROOT.'/application/ajaxwebpage.class.inc.php'); require_once(APPROOT.'/application/startup.inc.php'); require_once(APPROOT.'/application/loginwebpage.class.inc.php'); @@ -52,7 +51,7 @@ try { /** * Helper function to build a select from the list of valid classes for a given action * - * @deprecated since 3.0.0 use GetClassesSelectUIBlock + * @deprecated 3.0.0 use GetClassesSelectUIBlock * * @param $sDefaultValue * @param integer $iWidthPx The width (in pixels) of the drop-down list @@ -237,6 +236,11 @@ try { throw new CoreException(Dict::S('UI:ActionNotAllowed')); } + // CSRF transaction id verification + if(!$bSimulate && !utils::IsTransactionValid(utils::ReadPostedParam('transaction_id', '', 'raw_data'))){ + throw new CoreException(Dict::S('UI:Error:InvalidToken')); + } + $aResult = array(); $sCSVData = utils::ReadParam('csvdata', '', false, 'raw_data'); $sCSVDataTruncated = utils::ReadParam('csvdata_truncated', '', false, 'raw_data'); @@ -353,11 +357,9 @@ try { { // We're doing it for real, let's create a change $sUserString = CMDBChange::GetCurrentUserName().' (CSV)'; - CMDBObject::SetTrackInfo($sUserString); - CMDBObject::SetTrackOrigin(CMDBChangeOrigin::CSV_INTERACTIVE); + CMDBObject::SetCurrentChangeFromParams($sUserString, CMDBChangeOrigin::CSV_INTERACTIVE); $oMyChange = CMDBObject::GetCurrentChange(); } - CMDBObject::SetTrackOrigin('csv-interactive'); $oBulk = new BulkChange( $sClassName, @@ -372,7 +374,7 @@ try { ); $oBulk->SetReportHtml(); - $oPage->AddSubBlock(InputUIBlockFactory::MakeForHidden("csvdata_truncated", htmlentities($sCSVDataTruncated, ENT_QUOTES, 'UTF-8'), "csvdata_truncated")); + $oPage->AddSubBlock(InputUIBlockFactory::MakeForHidden("csvdata_truncated", utils::EscapeHtml($sCSVDataTruncated), "csvdata_truncated")); $aRes = $oBulk->Process($oMyChange); $aColumns = []; @@ -523,20 +525,21 @@ try { $oForm = FormUIBlockFactory::MakeStandard('wizForm'); $oContainer->AddSubBlock($oForm); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("transaction_id", utils::GetNewTransactionId())); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("step", ($iCurrentStep + 1))); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("separator", htmlentities($sSeparator, ENT_QUOTES, 'UTF-8'))); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("text_qualifier", htmlentities($sTextQualifier, ENT_QUOTES, 'UTF-8'))); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("separator", utils::EscapeHtml($sSeparator))); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("text_qualifier", utils::EscapeHtml($sTextQualifier))); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("header_line", $bHeaderLine)); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("nb_skipped_lines", utils::ReadParam('nb_skipped_lines', '0'))); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("box_skiplines", utils::ReadParam('box_skiplines', '0'))); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("csvdata_truncated", htmlentities($sCSVDataTruncated, ENT_QUOTES, 'UTF-8'), "csvdata_truncated")); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("csvdata", htmlentities($sCSVData, ENT_QUOTES, 'UTF-8'))); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("csvdata_truncated", utils::EscapeHtml($sCSVDataTruncated), "csvdata_truncated")); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("csvdata", utils::EscapeHtml($sCSVData))); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("encoding", $sEncoding)); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("synchro_scope", $sSynchroScope)); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("class_name", $sClassName)); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("advanced", $bAdvanced)); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("date_time_format", htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8'))); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("custom_date_time_format", htmlentities($sCustomDateTimeFormat, ENT_QUOTES, 'UTF-8'))); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("date_time_format", utils::EscapeHtml($sDateTimeFormat))); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("custom_date_time_format", utils::EscapeHtml($sCustomDateTimeFormat))); if (!empty($sSynchroScope)) { foreach ($aSynchroUpdate as $sKey => $value) { @@ -641,7 +644,7 @@ try { $oDlg = UIContentBlockUIBlockFactory::MakeStandard("dlg_confirmation")->SetHasForcedDiv(true); $oPage->AddSubBlock($oDlg); $oDlg->AddSubBlock(new Html($sMessage)); - $oDlg->AddSubBlock(new Html(htmlentities(Dict::S('UI:CSVImportConfirmMessage'), ENT_QUOTES, 'UTF-8'))); + $oDlg->AddSubBlock(new Html(utils::EscapeHtml(Dict::S('UI:CSVImportConfirmMessage')))); $oDlgConfirm = UIContentBlockUIBlockFactory::MakeStandard("confirmation_chart")->SetHasForcedDiv(true); $oDlg->AddSubBlock($oDlgConfirm); @@ -682,7 +685,7 @@ EOF // Add graphs dependencies WebResourcesHelper::EnableC3JSToWebPage($oPage); - $oPage->add_script( + $oPage->add_script( <<< EOF function CSVGoBack() { @@ -788,7 +791,7 @@ EOF $oField = FieldUIBlockFactory::MakeLarge(Dict::S('UI:CSVImport:LinesNotImported+')); $oCollapsibleSection->AddSubBlock($oField); - $oText = new TextArea("", htmlentities(implode("\n", $aResult), ENT_QUOTES, 'UTF-8'), "", 150, 50); + $oText = new TextArea("", utils::EscapeHtml(implode("\n", $aResult)), "", 150, 50); $oField->AddSubBlock($oText); } } @@ -879,17 +882,17 @@ EOF $oForm->AddSubBlock($oDivMapping); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("step", "4")); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("separator", htmlentities($sSeparator, ENT_QUOTES, 'UTF-8'))); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("text_qualifier", htmlentities($sTextQualifier, ENT_QUOTES, 'UTF-8'))); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("separator", utils::EscapeHtml($sSeparator))); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("text_qualifier", utils::EscapeHtml($sTextQualifier))); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("header_line", $bHeaderLine)); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("nb_skipped_lines", utils::ReadParam('nb_skipped_lines', '0'))); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("box_skiplines", utils::ReadParam('box_skiplines', '0'))); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("csvdata_truncated", htmlentities($sCSVDataTruncated, ENT_QUOTES, 'UTF-8'), "csvdata_truncated")); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("csvdata", htmlentities($sCSVData, ENT_QUOTES, 'UTF-8'))); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("csvdata_truncated", utils::EscapeHtml($sCSVDataTruncated), "csvdata_truncated")); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("csvdata", utils::EscapeHtml($sCSVData))); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("encoding", $sEncoding)); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("synchro_scope", $sSynchroScope)); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("date_time_format", htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8'))); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("custom_date_time_format", htmlentities($sCustomDateTimeFormat, ENT_QUOTES, 'UTF-8'))); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("date_time_format", utils::EscapeHtml($sDateTimeFormat))); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("custom_date_time_format", utils::EscapeHtml($sCustomDateTimeFormat))); if (!empty($sSynchroScope)) { foreach ($aSynchroUpdate as $sKey => $value) { @@ -1179,7 +1182,7 @@ EOF } $aGuesses = GuessParameters($sUTF8Data); // Try to predict the parameters, based on the input data - + $iSkippedLines = utils::ReadParam('nb_skipped_lines', ''); $bBoxSkipLines = utils::ReadParam('box_skiplines', 0); $sTextQualifier = utils::ReadParam('text_qualifier', '', false, 'raw_data'); @@ -1256,10 +1259,10 @@ EOF $sSeparator = "tab"; } $sOtherSeparator = in_array($sSeparator, array(',', ';', "\t")) ? '' : $sSeparator; - $aSep['other'] = Dict::S('UI:CSVImport:SeparatorOther').' '; + $aSep['other'] = Dict::S('UI:CSVImport:SeparatorOther').' '; foreach ($aSep as $sVal => $sLabel) { - $oRadio = InputUIBlockFactory::MakeForInputWithLabel($sLabel, "separator", htmlentities($sVal, ENT_QUOTES, 'UTF-8'), $sLabel, "radio"); + $oRadio = InputUIBlockFactory::MakeForInputWithLabel($sLabel, "separator", utils::EscapeHtml($sVal), $sLabel, "radio"); $oRadio->GetInput()->SetIsChecked(($sVal == $sSeparator)); $oRadio->SetBeforeInput(false); $oRadio->GetInput()->AddCSSClass('ibo-input--label-right'); @@ -1274,13 +1277,12 @@ EOF $oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oFieldSetTextQualifier)); $aQualifiers = array( - '"' => Dict::S('UI:CSVImport:QualifierDoubleQuote+'), + '"' => Dict::S('UI:CSVImport:QualifierDoubleQuote+'), '\'' => Dict::S('UI:CSVImport:QualifierSimpleQuote+'), ); - $aQualifiers['other'] = Dict::S('UI:CSVImport:QualifierOther').' + $aQualifiers['other'] = Dict::S('UI:CSVImport:QualifierOther').' GetInput()->SetIsChecked(($sVal == $sTextQualifier)); $oRadio->SetBeforeInput(false); $oRadio->GetInput()->AddCSSClass('ibo-input-checkbox'); @@ -1317,8 +1319,8 @@ EOF $sDateTimeFormat = utils::ReadParam('date_time_format', 'default'); $sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', (string)AttributeDateTime::GetFormat(), false, 'raw_data'); - $sDefaultFormat = htmlentities((string)AttributeDateTime::GetFormat(), ENT_QUOTES, 'UTF-8'); - $sExample = htmlentities(date((string)AttributeDateTime::GetFormat()), ENT_QUOTES, 'UTF-8'); + $sDefaultFormat = utils::EscapeHtml((string)AttributeDateTime::GetFormat()); + $sExample = utils::EscapeHtml(date((string)AttributeDateTime::GetFormat())); $oRadioDefault = InputUIBlockFactory::MakeForInputWithLabel(Dict::Format('UI:CSVImport:DefaultDateTimeFormat_Format_Example', $sDefaultFormat, $sExample), "date_time_format", "default", "radio_date_time_std", "radio"); $oRadioDefault->GetInput()->SetIsChecked(($sDateTimeFormat == (string)AttributeDateTime::GetFormat())); $oRadioDefault->SetBeforeInput(false); @@ -1326,7 +1328,7 @@ EOF $oFieldSetDate->AddSubBlock($oRadioDefault); $oFieldSetDate->AddSubBlock(new Html('
    ')); - $sFormatInput = ''; + $sFormatInput = ''; $oRadioCustom = InputUIBlockFactory::MakeForInputWithLabel(Dict::Format('UI:CSVImport:CustomDateTimeFormat', $sFormatInput), "date_time_format", "custom", "radio_date_time_custom", "radio"); $oRadioCustom->SetDescription(Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip')); $oRadioCustom->GetInput()->SetIsChecked($sDateTimeFormat !== (string)AttributeDateTime::GetFormat()); @@ -1337,8 +1339,8 @@ EOF $oPage->add_ready_script("$('#custom_date_time_format').on('click', function() { DoPreview(); });"); $oPage->add_ready_script("$('#radio_date_time_std').on('click', function() { DoPreview(); });"); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("csvdata_truncated", htmlentities($sCSVDataTruncated, ENT_QUOTES, 'UTF-8'), "csvdata_truncated")); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("csvdata", htmlentities($sUTF8Data, ENT_QUOTES, 'UTF-8'), 'csvdata')); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("csvdata_truncated", utils::EscapeHtml($sCSVDataTruncated), "csvdata_truncated")); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("csvdata", utils::EscapeHtml($sUTF8Data), 'csvdata')); // The encoding has changed, keep that information within the wizard $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("encoding", "UTF-8")); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("class_name", $sClassName)); @@ -1527,10 +1529,10 @@ EOF $oFormPaste->AddSubBlock(InputUIBlockFactory::MakeForHidden("encoding", 'UTF-8')); $oFormPaste->AddSubBlock(InputUIBlockFactory::MakeForHidden("step", '2')); - $oFormPaste->AddSubBlock(InputUIBlockFactory::MakeForHidden("separator", htmlentities($sSeparator, ENT_QUOTES, 'UTF-8'))); - $oFormPaste->AddSubBlock(InputUIBlockFactory::MakeForHidden("text_qualifier", htmlentities($sTextQualifier, ENT_QUOTES, 'UTF-8'))); - $oFormPaste->AddSubBlock(InputUIBlockFactory::MakeForHidden("date_time_format", htmlentities($sDateTimeFormat, ENT_QUOTES, 'UTF-8'))); - $oFormPaste->AddSubBlock(InputUIBlockFactory::MakeForHidden("custom_date_time_format", htmlentities($sCustomDateTimeFormat, ENT_QUOTES, 'UTF-8'))); + $oFormPaste->AddSubBlock(InputUIBlockFactory::MakeForHidden("separator", utils::EscapeHtml($sSeparator))); + $oFormPaste->AddSubBlock(InputUIBlockFactory::MakeForHidden("text_qualifier", utils::EscapeHtml($sTextQualifier))); + $oFormPaste->AddSubBlock(InputUIBlockFactory::MakeForHidden("date_time_format", utils::EscapeHtml($sDateTimeFormat))); + $oFormPaste->AddSubBlock(InputUIBlockFactory::MakeForHidden("custom_date_time_format", utils::EscapeHtml($sCustomDateTimeFormat))); $oFormPaste->AddSubBlock(InputUIBlockFactory::MakeForHidden("header_line", $bHeaderLine)); $oFormPaste->AddSubBlock(InputUIBlockFactory::MakeForHidden("nb_skipped_lines", utils::ReadParam('nb_skipped_lines', '0'))); $oFormPaste->AddSubBlock(InputUIBlockFactory::MakeForHidden("box_skiplines", utils::ReadParam('box_skiplines', '0'))); diff --git a/pages/oauth.landing.php b/pages/oauth.landing.php new file mode 100644 index 0000000000..27019f4302 --- /dev/null +++ b/pages/oauth.landing.php @@ -0,0 +1,14 @@ +AllowOnlyAdmin(); +$oUpdateController->SetDefaultOperation('Landing'); +$oUpdateController->HandleOperation(); diff --git a/pages/preferences.php b/pages/preferences.php index c251fac7b4..b21b8fac1d 100644 --- a/pages/preferences.php +++ b/pages/preferences.php @@ -140,7 +140,8 @@ JS ////////////////////////////////////////////////////////////////////////// $oFavoriteOrganizationsBlock = new Panel(Dict::S('UI:FavoriteOrganizations'), array(), 'grey', 'ibo-favorite-organizations'); - $oFavoriteOrganizationsBlock->AddHtml(Dict::S('UI:FavoriteOrganizations+')); + $oFavoriteOrganizationsBlock->SetSubTitle(Dict::S('UI:FavoriteOrganizations+')); + $oFavoriteOrganizationsBlock->AddCSSClass('ibo-datatable-panel'); $oFavoriteOrganizationsForm = new Form(); $oFavoriteOrganizationsBlock->AddSubBlock($oFavoriteOrganizationsForm); // Favorite organizations: the organizations listed in the drop-down menu @@ -194,6 +195,8 @@ JS ////////////////////////////////////////////////////////////////////////// $oShortcutsBlock = new BlockShortcuts(Dict::S('Menu:MyShortcuts'), array(), 'grey', 'ibo-shortcuts'); + $oShortcutsBlock->AddCSSClass('ibo-datatable-panel'); + $oShortcutsBlock->sIdShortcuts = 'shortcut_list'; $oShortcutsFilter = new DBObjectSearch('Shortcut'); $oShortcutsFilter->AddCondition('user_id', UserRights::GetUserId(), '='); diff --git a/pages/run_query.php b/pages/run_query.php index e930ceeb6b..c197182ca1 100644 --- a/pages/run_query.php +++ b/pages/run_query.php @@ -265,7 +265,7 @@ EOF $aOrderBy = MetaModel::GetOrderByDefault($sMainClass); - if (($oFilter instanceof DBObjectSearch) && !MetaModel::GetConfig()->Get('use_legacy_dbsearch')) { + if ($oFilter instanceof DBObjectSearch) { // OQL Developed for Count $oSQLObjectQueryBuilder = new SQLObjectQueryBuilder($oFilter); $oBuild = new QueryBuilderContext($oFilter, $aModifierProperties, null, null, null, $aCountAttToLoad); @@ -280,7 +280,7 @@ EOF $oCountResultQuerySet->AddSubBlock(UIContentBlockUIBlockFactory::MakeForCode($sSQL)); $aMoreInfoBlocks[] = $oCountResultQuerySet; - if (($oFilter instanceof DBObjectSearch) && !MetaModel::GetConfig()->Get('use_legacy_dbsearch')) { + if ($oFilter instanceof DBObjectSearch) { // OQL Developed $oSQLObjectQueryBuilder = new SQLObjectQueryBuilder($oFilter); $oBuild = new QueryBuilderContext($oFilter, $aModifierProperties); diff --git a/pages/tagadmin.php b/pages/tagadmin.php index 02d7a72681..a6a6fede68 100644 --- a/pages/tagadmin.php +++ b/pages/tagadmin.php @@ -19,7 +19,6 @@ require_once('../approot.inc.php'); require_once(APPROOT.'application/application.inc.php'); -require_once(APPROOT.'application/itopwebpage.class.inc.php'); require_once(APPROOT.'application/startup.inc.php'); require_once(APPROOT.'application/loginwebpage.class.inc.php'); diff --git a/setup/appupgradecopy.php b/setup/appupgradecopy.php new file mode 100644 index 0000000000..18fdd3e111 --- /dev/null +++ b/setup/appupgradecopy.php @@ -0,0 +1,22 @@ +getMessage()); + $sMessage = "Failed to process class '$sClass', "; + if (!empty($sModuleRootDir)) { + $sMessage .= "from '$sModuleRootDir': "; + } + $sMessage .= $e->getMessage(); + throw new Exception($sMessage); } } } @@ -482,11 +487,14 @@ EOF; foreach($aMenusByModule[$sModuleName] as $sMenuId) { $oMenuNode = $aMenuNodes[$sMenuId]; - if ($sParent = $oMenuNode->GetChildText('parent', null)) - { - $aMenusToLoad[] = $sParent; - $aParentMenus[] = $sParent; + // compute parent hierarchy + $aParentIdHierarchy = []; + while ($sParent = $oMenuNode->GetChildText('parent', null)) { + array_unshift($aParentIdHierarchy, $sParent); + $oMenuNode = $aMenuNodes[$sParent]; } + $aMenusToLoad = array_merge($aMenusToLoad, $aParentIdHierarchy); + $aParentMenus = array_merge($aParentMenus, $aParentIdHierarchy); // Note: the order matters: the parents must be defined BEFORE $aMenusToLoad[] = $sMenuId; } @@ -587,15 +595,18 @@ EOF; { // Write the code into the given module as model..php // - $sResultFile = $sTempTargetDir.'/'.$sRelativeDir.'/model.'.$sModuleName.'.php'; + $sModelFileName = 'model.'.$sModuleName.'.php'; + $sResultFile = $sTempTargetDir.'/'.$sRelativeDir.'/'.$sModelFileName; $this->WritePHPFile($sResultFile, $sModuleName, $sModuleVersion, $sCompiledCode); + // In case the model file wasn't present in the module file, we're adding it ! (N°4875) + $oModule->AddFileToInclude('business', $sModelFileName); } else { // Write the code into core/main.php // $this->sMainPHPCode .= - <<sMainPHPCode .= $sCompiledCode; } - } - else - { - $this->Log("Compilation of module $sModuleName in version $sModuleVersion produced not code at all. No file written."); + } else { + $this->Log("Compilation of module $sModuleName in version $sModuleVersion produced not code at all. No file written."); } // files to include (PHP datamodels) foreach($oModule->GetFilesToInclude('business') as $sRelFileName) { - $aDataModelFiles[] = "MetaModel::IncludeModule(MODULESROOT.'/$sRelativeDir/$sRelFileName');"; + if (file_exists("{$sTempTargetDir}/{$sRelativeDir}/{$sRelFileName}")) { + $aDataModelFiles[] = "MetaModel::IncludeModule(MODULESROOT.'/$sRelativeDir/$sRelFileName');"; + } else { + /** @noinspection NestedPositiveIfStatementsInspection */ + if (utils::IsDevelopmentEnvironment()) { + $sMissingBusinessFileMessage = 'A module embeds a non existing file: Check the module.php "datamodel" key!'; + $aContext = [ + 'moduleId' => $oModule->GetId(), + 'moduleLocation' => $oModule->GetRootDir(), + 'includedFile' => $sRelFileName, + ]; + SetupLog::Error($sMissingBusinessFileMessage, null, $aContext); + throw new CoreException($sMissingBusinessFileMessage, $aContext); + } + } } // files to include (PHP webservices providers) - foreach($oModule->GetFilesToInclude('webservices') as $sRelFileName) - { + foreach ($oModule->GetFilesToInclude('webservices') as $sRelFileName) { $aWebservicesFiles[] = "MetaModel::IncludeModule(MODULESROOT.'/$sRelativeDir/$sRelFileName');"; } } // foreach module @@ -2006,10 +2028,16 @@ EOF // Note: We can't use ModelFactory::GetField() as the current clas doesn't seem to be loaded yet. $oField = $this->oFactory->GetNodes('field[@id="'.$sStateAttCode.'"]', $oFields)->item(0); + if ($oField == null) { + // Search field in parent class + $oField = $this->GetFieldInParentClasses($oClass, $sStateAttCode); + if ($oField == null) { + throw new DOMFormatException("Non existing attribute '$sStateAttCode'", null, null, $oStateAttribute); + } + } $oValues = $oField->GetUniqueElement('values'); $oValueNodes = $oValues->getElementsByTagName('value'); - foreach($oValueNodes as $oValue) - { + foreach ($oValueNodes as $oValue) { $sLifecycle .= " MetaModel::Init_DefineState(\n"; $sLifecycle .= " \"".$oValue->GetText()."\",\n"; $sLifecycle .= " array(\n"; @@ -2315,12 +2343,12 @@ EOF $sMainColorScssVariableDeclaration = "$sMainColorScssVariableName: $sMainColorForCss !default;"; $sMainColorCssVariableDeclaration = "$sMainColorCssVariableName: #{{$sMainColorScssVariableName}};"; - $sCssRegularClassMainColorDeclaration = "--ibo-main-color: $sMainColorScssVariableName;"; + $sCssRegularClassMainColorDeclaration = "--ibo-main-color: #{{$sMainColorScssVariableName}};"; // Note: We have to manually force the alpha channel in case the given color is transparent - $sCssRegularClassMainColor100Declaration = "--ibo-main-color--100: ibo-adjust-alpha(ibo-adjust-lightness($sMainColorScssVariableName, \$ibo-color-base-lightness-100), \$ibo-color-base-opacity-for-lightness-100);"; - $sCssRegularClassMainColor900Declaration = "--ibo-main-color--900: ibo-adjust-alpha(ibo-adjust-lightness($sMainColorScssVariableName, \$ibo-color-base-lightness-900), \$ibo-color-base-opacity-for-lightness-900);"; + $sCssRegularClassMainColor100Declaration = "--ibo-main-color--100: #{ibo-adjust-alpha(ibo-adjust-lightness($sMainColorScssVariableName, \$ibo-color-base-lightness-100), \$ibo-color-base-opacity-for-lightness-100)};"; + $sCssRegularClassMainColor900Declaration = "--ibo-main-color--900: #{ibo-adjust-alpha(ibo-adjust-lightness($sMainColorScssVariableName, \$ibo-color-base-lightness-900), \$ibo-color-base-opacity-for-lightness-900)};"; - $sCssAlternativeClassComplementaryColorDeclaration = "--ibo-complementary-color: $sMainColorScssVariableName;"; + $sCssAlternativeClassComplementaryColorDeclaration = "--ibo-complementary-color: #{{$sMainColorScssVariableName}};"; } else { $sMainColorScssVariableDeclaration = null; @@ -2337,9 +2365,9 @@ EOF $sComplementaryScssVariableDeclaration = "$sComplementaryColorScssVariableName: $sComplementaryColorForCss !default;"; $sComplementaryCssVariableDeclaration = "$sComplementaryColorCssVariableName: #{{$sComplementaryColorScssVariableName}};"; - $sCssRegularClassComplementaryColorDeclaration = "--ibo-complementary-color: $sComplementaryColorScssVariableName;"; + $sCssRegularClassComplementaryColorDeclaration = "--ibo-complementary-color: #{{$sComplementaryColorScssVariableName}};"; - $sCssAlternativeClassMainColorDeclaration = "--ibo-main-color: $sComplementaryColorScssVariableName;"; + $sCssAlternativeClassMainColorDeclaration = "--ibo-main-color: #{{$sComplementaryColorScssVariableName}};"; } else { $sComplementaryScssVariableDeclaration = null; $sComplementaryCssVariableDeclaration = null; @@ -2979,8 +3007,8 @@ EOF; */ protected function CompileLogo($oBrandingNode, $sTempTargetDir, $sFinalTargetDir, $sNodeName, $sTargetFile) { - if (($sIcon = $oBrandingNode->GetChildText($sNodeName)) && (strlen($sIcon) > 0)) - { + $sIcon = trim($oBrandingNode->GetChildText($sNodeName) ?? ''); + if (strlen($sIcon) > 0) { $sSourceFile = $sTempTargetDir.'/'.$sIcon; $aIconName=explode(".", $sIcon); $sIconExtension=$aIconName[count($aIconName)-1]; @@ -3032,6 +3060,48 @@ EOF; $sDmStylesheetId = 'datamodel-compiled-scss-rules'; $this->WriteFile($sThemesAbsDirPath.$sDmStylesheetFilename, $sDmStylesheetContent); + // Parsing theme from common theme node + /** @var \MFElement $oThemesCommonNodes */ + $oThemesCommonNodes = $oBrandingNode->GetUniqueElement('themes_common', false); + $aThemesCommonParameters = array( + 'variables' => array(), + 'variable_imports' => array(), + 'utility_imports' => array(), + 'stylesheets' => array(), + ); + + if($oThemesCommonNodes !== null) { + /** @var \DOMNodeList $oThemesCommonVariables */ + $oThemesCommonVariables = $oThemesCommonNodes->GetNodes('variables/variable'); + foreach ($oThemesCommonVariables as $oVariable) { + $sVariableId = $oVariable->getAttribute('id'); + $aThemesCommonParameters['variables'][$sVariableId] = $oVariable->GetText(); + } + + /** @var \DOMNodeList $oThemesCommonImports */ + $oThemesCommonImports = $oThemesCommonNodes->GetNodes('imports/import'); + foreach ($oThemesCommonImports as $oImport) { + $sImportId = $oImport->getAttribute('id'); + $sImportType = $oImport->getAttribute('xsi:type'); + if ($sImportType === 'variables') { + $aThemesCommonParameters['variable_imports'][$sImportId] = $oImport->GetText(); + } elseif ($sImportType === 'utilities') { + $aThemesCommonParameters['utility_imports'][$sImportId] = $oImport->GetText(); + } else { + SetupLog::Warning('CompileThemes: Theme common has an import (#'.$sImportId.') without explicit xsi:type, it will be ignored. Check Datamodel XML Reference to fix it.'); + } + } + + // Stylesheets + // - Manually added in the XML + /** @var \DOMNodeList $oThemesCommonStylesheets */ + $oThemesCommonStylesheets = $oThemesCommonNodes->GetNodes('stylesheets/stylesheet'); + foreach ($oThemesCommonStylesheets as $oStylesheet) { + $sStylesheetId = $oStylesheet->getAttribute('id'); + $aThemesCommonParameters['stylesheets'][$sStylesheetId] = $oStylesheet->GetText(); + } + } + // Parsing themes from DM $aThemes = array(); /** @var \DOMNodeList $oThemeNodes */ @@ -3080,6 +3150,13 @@ EOF; // - Computed from the DM $aThemeParameters['stylesheets'][$sDmStylesheetId] = $sThemesRelDirPath.$sDmStylesheetFilename; + // - Overload default values with module ones + foreach ($aThemeParameters as $sThemeParameterName => $aThemeParameter) { + if(array_key_exists($sThemeParameterName, $aThemesCommonParameters)){ + $aThemeParameters[$sThemeParameterName] = array_merge($aThemeParameter, $aThemesCommonParameters[$sThemeParameterName]); + } + } + $aThemes[$sThemeId] = [ 'theme_parameters' => $aThemeParameters, 'precompiled_stylesheet' => $oTheme->GetChildText('precompiled_stylesheet', '') @@ -3565,7 +3642,7 @@ EOF; { @unlink($sFilename); } - $ret = file_put_contents($sFilename, $sContent, $flags); + $ret = file_put_contents($sFilename, $sContent, $flags ?? 0); if ($ret === false) { $iLen = strlen($sContent); @@ -3585,6 +3662,8 @@ EOF; * @param $sRelativeDir * * @throws \Exception + * + * @since 2.7.0 N°2498 */ protected function WriteStaticOnlyHtaccess($sTempTargetDir) { @@ -3626,6 +3705,8 @@ EOF; * @param $sModuleVersion * * @throws \Exception + * + * @since 2.7.0 N°2498 */ protected function WriteStaticOnlyWebConfig($sTempTargetDir) { @@ -3725,4 +3806,19 @@ EOF; return $sValue; } + + private function GetFieldInParentClasses($oClass, $sAttCode) + { + $sParentClass = $oClass->GetChildText('parent', 'DBObject'); + if ($sParentClass != 'DBObject') { + $oParent = $this->oFactory->GetClass($sParentClass); + $oParentFields = $oParent->GetOptionalElement('fields'); + $oField = $this->oFactory->GetNodes('field[@id="'.$sAttCode.'"]', $oParentFields)->item(0); + if ($oField != null) { + return $oField; + } + return $this->GetFieldInParentClasses($oParent, $sAttCode); + } + return null; + } } diff --git a/setup/email.test.php b/setup/email.test.php index 82897516fc..8fe476987b 100644 --- a/setup/email.test.php +++ b/setup/email.test.php @@ -20,6 +20,9 @@ /** * Wizard to configure and initialize the iTop application */ + +use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory; + require_once('../approot.inc.php'); require_once(APPROOT.'/application/utils.inc.php'); require_once(APPROOT.'/core/email.class.inc.php'); @@ -107,9 +110,9 @@ function CheckEmailSetting($oP) } } break; - + case 'SMTP': - $oP->info("iTop is configured to use the SMTP transport."); + $oP->info("iTop is configured to use the $sTransport transport."); $sHost = MetaModel::GetConfig()->Get('email_transport_smtp.host'); $sPort = MetaModel::GetConfig()->Get('email_transport_smtp.port'); $sEncryption = MetaModel::GetConfig()->Get('email_transport_smtp.encryption'); @@ -124,7 +127,33 @@ function CheckEmailSetting($oP) $oP->warning("The default settings may not be suitable for your environment. You may want to adjust these values by editing iTop's configuration file (".utils::GetConfigFilePathRelative().")."); } break; - + + case 'SMTP_OAuth': + $oP->info("iTop is configured to use the $sTransport transport."); + $sHost = MetaModel::GetConfig()->Get('email_transport_smtp.host'); + $sPort = MetaModel::GetConfig()->Get('email_transport_smtp.port'); + $sEncryption = MetaModel::GetConfig()->Get('email_transport_smtp.encryption'); + $sDisplayEncryption = empty($sEncryption) ? 'no encryption ' : $sEncryption; + $sUserName = MetaModel::GetConfig()->Get('email_transport_smtp.username'); + $sDisplayUserName = empty($sUserName) ? 'no user ' : $sUserName; + try { + $oRemoteAuthentOAuth = OAuthClientProviderFactory::GetOAuthClientForSMTP(); + $sLink = MetaModel::GetHyperLink(get_class($oRemoteAuthentOAuth), $oRemoteAuthentOAuth->GetKey()); + $oP->info("The connection used is: $sLink"); + $sProvider = $oRemoteAuthentOAuth->Get('provider'); + $sDisplayProvider = empty($sProvider) ? 'no Provider ' : $sProvider; + $sClientID = $oRemoteAuthentOAuth->Get('client_id'); + $oP->info("SMTP configuration (from config-itop.php): host: $sHost, port: $sPort, provider: $sDisplayProvider, user: $sDisplayUserName, encryption: $sDisplayEncryption."); + if (($sHost == 'localhost') && ($sPort == '25') && ($sUserName == '') && ($sClientID == '') && ($sProvider == '')) { + $oP->warning("The default settings may not be suitable for your environment. You may want to adjust these values by editing iTop's configuration file (".utils::GetConfigFilePathRelative().').'); + } + } catch (CoreException $e) { + $bRet = false; + $oP->error($e->getMessage()); + } + break; + + case 'Null': $oP->warning("iTop is configured to use the Null transport: emails sending will have no effect."); $bRet = false; @@ -175,9 +204,10 @@ function DisplayStep1(SetupPage $oP) 'input' => "", 'help' => ' email address (e.g. john.foo@worldcompany.com)', ); + $sDefaultFrom = MetaModel::GetConfig()->Get('email_transport_smtp.username'); $aForm[] = array( 'label' => "From:", - 'input' => "", + 'input' => "", 'help' => ' defaults to the configuration param "email_default_sender_address" or "To" field.', ); $oP->form($aForm); @@ -279,4 +309,4 @@ catch(CoreException $e) $oP->error("Error: '".$e->getHtmlDesc()."'"); } $oP->output(); -?> + diff --git a/setup/favicon.ico b/setup/favicon.ico index 104162ad92..0b3cd45868 100644 Binary files a/setup/favicon.ico and b/setup/favicon.ico differ diff --git a/setup/licenses/community-licenses.xml b/setup/licenses/community-licenses.xml index 696a4b8605..fa3a9c6e20 100644 --- a/setup/licenses/community-licenses.xml +++ b/setup/licenses/community-licenses.xml @@ -2661,6 +2661,1773 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]]> + + combodo/tcpdf + Nicola Asuni - Combodo + LGPL-3.0-only + + + + container-interop/container-interop + + MIT + + + + doctrine/lexer + Guilherme Blanco - Roman Borschel - Johannes Schmitt + MIT + + + + egulias/email-validator + Eduardo Gulias Davis + MIT + + + + laminas/laminas-loader + + BSD-3-Clause + + + + laminas/laminas-mail + + BSD-3-Clause + + + + laminas/laminas-mime + + BSD-3-Clause + + + + laminas/laminas-servicemanager + + BSD-3-Clause + + + + laminas/laminas-stdlib + + BSD-3-Clause + + + + laminas/laminas-validator + + BSD-3-Clause + + + + laminas/laminas-zendframework-bridge + + BSD-3-Clause + + + + nikic/php-parser + Nikita Popov + BSD-3-Clause + + + + paragonie/random_compat + Paragon Initiative Enterprises + MIT + + + + pear/archive_tar + Vincent Blavet - Greg Beaver - Michiel Rook + BSD-3-Clause + + + + pear/console_getopt + Andrei Zmievski - Stig Bakken - Greg Beaver + BSD-2-Clause + + + + pear/pear-core-minimal + Christian Weiske + BSD-3-Clause + + + + pear/pear_exception + Helgi Thormar - Greg Beaver + BSD-2-Clause + , + Gregory Beaver , + Helgi Þormar Þorbjörnsson , + Tomas V.V.Cox , + Martin Jansen . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ]]> + + + pelago/emogrifier + Oliver Klee - Zoli Szabó - John Reeve - Jake Hotson - Cameron Brooks - Jaime Prado + MIT + + + + psr/cache + PHP-FIG + MIT + + + + psr/container + PHP-FIG + MIT + + + + psr/log + PHP-FIG + MIT + + + + psr/simple-cache + PHP-FIG + MIT + 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. + ]]> + + + scssphp/scssphp + Anthon Pang - Cédric Morin + MIT + + + + symfony/cache + Nicolas Grekas - Symfony Community + MIT + + + + symfony/class-loader + Fabien Potencier - Symfony Community + MIT + + + + symfony/config + Fabien Potencier - Symfony Community + MIT + + + + symfony/console + Fabien Potencier - Symfony Community + MIT + + + + symfony/css-selector + Fabien Potencier - Jean-François Simon - Symfony Community + MIT + + + + symfony/debug + Fabien Potencier - Symfony Community + MIT + + + + symfony/dependency-injection + Fabien Potencier - Symfony Community + MIT + + + + symfony/dotenv + Fabien Potencier - Symfony Community + MIT + + + + symfony/event-dispatcher + Fabien Potencier - Symfony Community + MIT + + + + symfony/filesystem + Fabien Potencier - Symfony Community + MIT + + + + symfony/finder + Fabien Potencier - Symfony Community + MIT + + + + symfony/framework-bundle + Fabien Potencier - Symfony Community + MIT + + + + symfony/http-foundation + Fabien Potencier - Symfony Community + MIT + + + + symfony/http-kernel + Fabien Potencier - Symfony Community + MIT + + + + symfony/polyfill-apcu + Nicolas Grekas - Symfony Community + MIT + + + + symfony/polyfill-ctype + Gert de Pagter - Symfony Community + MIT + + + + symfony/polyfill-iconv + Nicolas Grekas - Symfony Community + MIT + + + + symfony/polyfill-intl-idn + Laurent Bassin - Trevor Rowbotham - Symfony Community + MIT + + +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. + ]]> + + + symfony/polyfill-intl-normalizer + Nicolas Grekas - Symfony Community + MIT + + + + symfony/polyfill-mbstring + Nicolas Grekas - Symfony Community + MIT + + + + symfony/polyfill-php56 + Nicolas Grekas - Symfony Community + MIT + + + + symfony/polyfill-php70 + Nicolas Grekas - Symfony Community + MIT + + + + symfony/polyfill-php72 + Nicolas Grekas - Symfony Community + MIT + + + + symfony/polyfill-util + Nicolas Grekas - Symfony Community + MIT + + + + symfony/routing + Fabien Potencier - Symfony Community + MIT + + + + symfony/stopwatch + Fabien Potencier - Symfony Community + MIT + + + + symfony/twig-bridge + Fabien Potencier - Symfony Community + MIT + + + + symfony/twig-bundle + Fabien Potencier - Symfony Community + MIT + + + + symfony/var-dumper + Nicolas Grekas - Symfony Community + MIT + + + + symfony/web-profiler-bundle + Fabien Potencier - Symfony Community + MIT + + + + symfony/yaml + Fabien Potencier - Symfony Community + MIT + + + + true/punycode + Renan Gonçalves + MIT + + + + twig/twig + Fabien Potencier - Twig Team - Armin Ronacher + BSD-3-Clause + + + + Freepik images + Freepik + Proprietary licensed + + + + icons8 images + icons8 + Proprietary licensed + + + + unDraw illustrations + Katerina Limpitsouni + Proprietary licensed + + + + bulma-scss + Jim Campbell + MIT + + + + datatables.net + SpryMedia Ltd + MIT + + + + datatables.net-fixedheader + SpryMedia Ltd + MIT + + + + datatables.net-responsive + SpryMedia Ltd + MIT + + + + datatables.net-scroller + SpryMedia Ltd + MIT + + + + datatables.net-select + SpryMedia Ltd + MIT + + + + popperjs/core + Federico Zivolo + MIT + + + + raleway-webfont + Pablo Impallari + MIT AND OFL-1.1 + + + + scrollmagic + Jan Paepke + MIT OR GPL-3.0+ + + + + tippy.js + atomiks + MIT + + combodo/tcpdf Nicola Asuni - Combodo @@ -3526,6 +5293,511 @@ Public License instead of this License. But first, please read ********************************************************************** ********************************************************************** + ]]> + + + container-interop/container-interop + + MIT + + + + doctrine/lexer + Guilherme Blanco - Roman Borschel - Johannes Schmitt + MIT + + + + egulias/email-validator + Eduardo Gulias Davis + MIT + + + + firebase/php-jwt + Neuman Vong - Anant Narayanan + BSD-3-Clause + + + + guzzlehttp/guzzle + Graham Campbell - Michael Dowling - Jeremy Lindblom - George Mponos - Tobias Nyholm - Márk Sági-Kazár - Tobias Schultze + MIT + +Copyright (c) 2012 Jeremy Lindblom +Copyright (c) 2014 Graham Campbell +Copyright (c) 2015 Márk Sági-Kazár +Copyright (c) 2015 Tobias Schultze +Copyright (c) 2016 Tobias Nyholm +Copyright (c) 2016 George Mponos + +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. + ]]> + + + guzzlehttp/promises + Graham Campbell - Michael Dowling - Tobias Nyholm - Tobias Schultze + MIT + +Copyright (c) 2015 Graham Campbell +Copyright (c) 2017 Tobias Schultze +Copyright (c) 2020 Tobias Nyholm + +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. + ]]> + + + guzzlehttp/psr7 + Graham Campbell - Michael Dowling - George Mponos - Tobias Nyholm - Márk Sági-Kazár - Tobias Schultze + MIT + +Copyright (c) 2015 Márk Sági-Kazár +Copyright (c) 2015 Graham Campbell +Copyright (c) 2016 Tobias Schultze +Copyright (c) 2016 George Mponos +Copyright (c) 2018 Tobias Nyholm + +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. + ]]> + + + laminas/laminas-loader + + BSD-3-Clause + + + + laminas/laminas-mail + + BSD-3-Clause + + + + laminas/laminas-mime + + BSD-3-Clause + + + + laminas/laminas-servicemanager + + BSD-3-Clause + + + + laminas/laminas-stdlib + + BSD-3-Clause + + + + laminas/laminas-validator + + BSD-3-Clause + + + + laminas/laminas-zendframework-bridge + + BSD-3-Clause + + + + league/oauth2-client + Alex Bilbie - Woody Gilk + MIT + + +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. + ]]> + + + league/oauth2-google + Woody Gilk + MIT + + +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. ]]> @@ -3754,6 +6026,32 @@ 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. + ]]> + + + psr/http-message + PHP-FIG + MIT + @@ -3808,6 +6106,34 @@ Copyright (c) 2016 PHP Framework Interoperability Group > 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. + ]]> + + + ralouphie/getallheaders + Ralph Khattar + MIT + @@ -3842,7 +6168,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Chris Corbyn - Fabien Potencier MIT + + + symfony/polyfill-iconv + Nicolas Grekas - Symfony Community + MIT + + + + symfony/polyfill-intl-idn + Laurent Bassin - Trevor Rowbotham - Symfony Community + MIT + + +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. + ]]> + + + symfony/polyfill-intl-normalizer + Nicolas Grekas - Symfony Community + MIT + + + + symfony/polyfill-php72 + Nicolas Grekas - Symfony Community + MIT + + + + thenetworg/oauth2-azure + Jan Hajek + MIT + + + + true/punycode + Renan Gonçalves + MIT + Fabien Potencier - Twig Team - Armin Ronacher BSD-3-Clause - - Freepik images - Freepik - Proprietary licensed - - - - icons8 images - icons8 - Proprietary licensed - - - - unDraw illustrations - Katerina Limpitsouni - Proprietary licensed - - - - bulma-scss - Jim Campbell - MIT - - - - datatables.net - SpryMedia Ltd - MIT - - - - datatables.net-fixedheader - SpryMedia Ltd - MIT - - - - datatables.net-responsive - SpryMedia Ltd - MIT - - - - datatables.net-scroller - SpryMedia Ltd - MIT - - - - datatables.net-select - SpryMedia Ltd - MIT - - - - popperjs/core - Federico Zivolo - MIT - - - - raleway-webfont - Pablo Impallari - MIT AND OFL-1.1 - - - - scrollmagic - Jan Paepke - MIT OR GPL-3.0+ - - - - tippy.js - atomiks - MIT - - diff --git a/setup/modelfactory.class.inc.php b/setup/modelfactory.class.inc.php index d84962a913..611dde03f6 100644 --- a/setup/modelfactory.class.inc.php +++ b/setup/modelfactory.class.inc.php @@ -139,7 +139,15 @@ class MFModule */ protected $sAutoSelect; /** - * @var array + * @see ModelFactory::FindModules init of this structure from the module.*.php files + * @var array{ + * business: string[], + * webservices: string[], + * addons: string[], + * } + * Warning, there are naming mismatches between this structure and the module.*.php : + * - `business` here correspond to `datamodel` in module.*.php + * - `webservices` here correspond to `webservice` in module.*.php */ protected $aFilesToInclude; @@ -320,6 +328,14 @@ class MFModule return $this->aFilesToInclude[$sCategory]; } + public function AddFileToInclude($sCategory, $sFile) + { + if (in_array($sFile, $this->aFilesToInclude[$sCategory], true)) { + return; + } + $this->aFilesToInclude[$sCategory][] = $sFile; + } + } /** @@ -961,9 +977,9 @@ class ModelFactory catch (Exception $e) { $aLoadedModuleNames = array(); foreach (self::$aLoadedModules as $oLoadedModule) { - $aLoadedModuleNames[] = $oLoadedModule->GetName(); + $aLoadedModuleNames[] = $oLoadedModule->GetName().':'.$oLoadedModule->GetVersion(); } - throw new Exception('Error loading module "'.$oModule->GetName().'": '.$e->getMessage().' - Loaded modules: '.implode(',', + throw new Exception('Error loading module "'.$oModule->GetName().'": '.$e->getMessage().' - Loaded modules: '.implode(', ', $aLoadedModuleNames)); } } @@ -1806,7 +1822,7 @@ EOF; echo "
    \n"; echo "

    DOM - Original values

    \n"; - echo "
    ".htmlentities($sDOMOriginal)."
    \n"; + echo "
    ".utils::EscapeHtml($sDOMOriginal)."
    \n"; echo "
    ⇒ ⇒ ⇒
    \n"; echo "

    DOM - Altered with various changes

    \n"; - echo "
    ".htmlentities($sDOMModified)."
    \n"; + echo "
    ".utils::EscapeHtml($sDOMModified)."
    \n"; echo "
    \n"; echo "

    DOM - Rebuilt from the Delta

    \n"; - echo "
    ".htmlentities($sDOMRebuilt)."
    \n"; + echo "
    ".utils::EscapeHtml($sDOMRebuilt)."
    \n"; echo "
    \n"; echo "

    Delta (Computed by ModelFactory)

    \n"; - echo "
    ".htmlentities($sDeltaXML)."
    \n"; + echo "
    ".utils::EscapeHtml($sDeltaXML)."
    \n"; echo "
    ⇒ ⇒ ⇒
    Server Name:
    Login:
    Login:
    Password:
    Password:
    '); //-- TLS params (N°1260) $sTlsEnabledChecked = $bTlsEnabled ? ' checked' : ''; - $sTlsCaDisabled = $bTlsEnabled ? '' : ' disabled'; + $sTlsCaDisabled = $bTlsEnabled ? '' : ' disabled'; $oPage->add('
    '); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add('
    '); @@ -1047,22 +1054,18 @@ class SetupUtils $oPage->add(''); $oPage->add('
    Database'); $oPage->add(''); - if ($bIsItopInstall) - { + if ($bIsItopInstall) { $oPage->add(''); - $oPage->add(''); + $oPage->add(''); $oPage->add(''); - $oPage->add(''); - } - else - { - $oPage->add(''); + $oPage->add(''); + } else { + $oPage->add(''); } $oPage->add('
    Database Name:
    Database Name:
    '); $oPage->add('
    '); $oPage->add(''); - $oPage->add(''); + $oPage->add(''); $oPage->add('
    '); $oPage->add('
    '); $oPage->add(''); @@ -1476,23 +1479,18 @@ JS } } - if ($checks['databases'] == null) - { - $sDBNameInput = ''; + if ($checks['databases'] == null) { + $sDBNameInput = ''; $oPage->add_ready_script( -<<Error:Not enough rights to enumerate the databases
    '); JS ); - } - else - { + } else { $sDBNameInput = '
    '; @@ -1868,21 +1865,30 @@ JS public static function GetVersionManifest($sInstalledVersion) { - if (preg_match('/^([0-9]+)\./', $sInstalledVersion, $aMatches)) - { + if (preg_match('/^([0-9]+)\./', $sInstalledVersion, $aMatches)) { return APPROOT.'datamodels/'.$aMatches[1].'.x/manifest-'.$sInstalledVersion.'.xml'; } + return false; } + /** + * Check paths relative to APPROOT : is existing, is dir, is writable + * + * @param string[] $aWritableDirs list of dirs to check, relative to APPROOT (for example : `['log','conf','data']`) + * + * @return array full path as key, CheckResult error as value + * + * @uses \is_dir() + * @uses \is_writable() + * @uses \file_exists() + */ public static function CheckWritableDirs($aWritableDirs) { $aNonWritableDirs = array(); - foreach($aWritableDirs as $sDir) - { + foreach ($aWritableDirs as $sDir) { $sFullPath = APPROOT.$sDir; - if (is_dir($sFullPath) && !is_writable($sFullPath)) - { + if (is_dir($sFullPath) && !is_writable($sFullPath)) { $aNonWritableDirs[APPROOT.$sDir] = new CheckResult(CheckResult::ERROR, "The directory '".APPROOT.$sDir."' exists but is not writable for the application."); } else if (file_exists($sFullPath) && !is_dir($sFullPath)) @@ -1965,7 +1971,7 @@ JS */ public static function GetSetupQueriesFilePath() { - return APPROOT.'log/setup-queries-'.strftime('%Y-%m-%d_%H_%M').'.sql'; + return APPROOT.'log/setup-queries-'.date('Y-m-d_H_i').'.sql'; } /** diff --git a/setup/wizardcontroller.class.inc.php b/setup/wizardcontroller.class.inc.php index 93ed2f95ef..ca46643236 100644 --- a/setup/wizardcontroller.class.inc.php +++ b/setup/wizardcontroller.class.inc.php @@ -182,7 +182,12 @@ class WizardController $oP->add("

    Fatal error

    \n"); $oP->error("Error: the configuration file '".$sRelativePath."' already exists and cannot be overwritten."); $oP->p("The wizard cannot modify the configuration file for you. If you want to upgrade ".ITOP_APPLICATION.", make sure that the file '".$sRelativePath."' can be modified by the web server."); - $oP->p(''); + + $sButtonsHtml = <<Reload +HTML; + $oP->p($sButtonsHtml); + $oP->output(); // Prevent token creation exit; @@ -196,25 +201,22 @@ class WizardController $oPage->add('
    '); $oStep->Display($oPage); $oPage->add('
    '); - + // Add the back / next buttons and the hidden form // to store the parameters $oPage->add(''); $oPage->add(''); - foreach($this->aParameters as $sCode => $value) - { - $oPage->add(''); + foreach ($this->aParameters as $sCode => $value) { + $oPage->add(''); } - $oPage->add(''); + $oPage->add(''); $oPage->add(''); - if ((count($this->aSteps) > 0) && ($oStep->CanMoveBackward())) - { + if ((count($this->aSteps) > 0) && ($oStep->CanMoveBackward())) { $oPage->add(''); } - if ($oStep->CanMoveForward()) - { - $oPage->add(''); + if ($oStep->CanMoveForward()) { + $oPage->add(''); } $oPage->add('
    '); $oPage->add(""); diff --git a/setup/wizardsteps.class.inc.php b/setup/wizardsteps.class.inc.php index c5696326e0..00ee4dde9e 100644 --- a/setup/wizardsteps.class.inc.php +++ b/setup/wizardsteps.class.inc.php @@ -165,6 +165,7 @@ HTML $oPage->p('Sorry, the installation cannot continue. Please fix the errors and reload this page to launch the installation again.'); $oPage->p(''); } + $oPage->add_ready_script('CheckDirectoryConfFilesPermissions("'.utils::GetItopVersionWikiSyntax().'")'); } public function CanMoveForward() @@ -237,11 +238,10 @@ class WizStepInstallOrUpgrade extends WizardStep $sPreviousVersionDir = ''; if ($sInstallMode == '') { - $sDBBackupPath = APPROOT.strftime('data/backups/manual/setup-%Y-%m-%d_%H_%M'); + $sDBBackupPath = APPROOT.'data/backups/manual/setup-'.date('Y-m-d_H_i'); $bDBBackup = true; $aPreviousInstance = SetupUtils::GetPreviousInstance(APPROOT); - if ($aPreviousInstance['found']) - { + if ($aPreviousInstance['found']) { $sInstallMode = 'upgrade'; $sDBServer = $aPreviousInstance['db_server']; $sDBUser = $aPreviousInstance['db_user']; @@ -305,13 +305,10 @@ HTML $sChecked = ($bCanBackup && $bDBBackup) ? ' checked ' : ''; $sDisabled = $bCanBackup ? '' : ' disabled '; $oPage->add(''); - $oPage->add('
    Save the backup to:
    '); + $oPage->add('
    Save the backup to:
    '); $fFreeSpace = SetupUtils::CheckDiskSpace($sDBBackupPath); $sMessage = ''; - if ($fFreeSpace !== false) - { + if ($fFreeSpace !== false) { $sMessage .= SetupUtils::HumanReadableSize($fFreeSpace).' free in '.dirname($sDBBackupPath); } $oPage->add($sMySQLDumpMessage.''.$sMessage.''); @@ -333,17 +330,16 @@ JS switch($sCode) { case 'check_path': - $sPreviousVersionDir = $aParameters['previous_version_dir']; - $aPreviousInstance = SetupUtils::GetPreviousInstance($sPreviousVersionDir); - if ($aPreviousInstance['found']) - { - $sDBServer = htmlentities($aPreviousInstance['db_server'], ENT_QUOTES, 'UTF-8'); - $sDBUser = htmlentities($aPreviousInstance['db_user'], ENT_QUOTES, 'UTF-8'); - $sDBPwd = htmlentities($aPreviousInstance['db_pwd'], ENT_QUOTES, 'UTF-8'); - $sDBName = htmlentities($aPreviousInstance['db_name'], ENT_QUOTES, 'UTF-8'); - $sDBPrefix = htmlentities($aPreviousInstance['db_prefix'], ENT_QUOTES, 'UTF-8'); - $oPage->add_ready_script( -<<add_ready_script( + <<add_ready_script( -<<add_ready_script( + <<add_ready_script( -<<add_ready_script( + << 0) || (count($aChanges['removed']) > 0) || (count($aChanges['modified']) > 0)) ) - { + if (($aChanges !== false) && ( (count($aChanges['added']) > 0) || (count($aChanges['removed']) > 0) || (count($aChanges['modified']) > 0)) ) { // Some changes were detected, prompt the user to keep or discard them $oPage->p(" Some modifications were detected between the ".ITOP_APPLICATION." version in '$sPreviousVersionDir' and a genuine $sInstalledVersion version."); $oPage->p("What do you want to do?"); @@ -559,41 +551,36 @@ EOF $sChecked = ($this->oWizard->GetParameter('upgrade_type') == 'keep-previous') ? ' checked ' : ''; $sDisabled = (count($aErrors) > 0) ? ' disabled ' : ''; - $oPage->p(''); - $oPage->add(''); + $oPage->p(''); + $oPage->add(''); - $oPage->add(''); + $oPage->add(''); - if (count($aErrors) > 0) - { + if (count($aErrors) > 0) { $oPage->p("Cannot copy the installed version due to the following access rights issue(s):"); - foreach($aErrors as $sDir => $oCheckResult) - { + foreach ($aErrors as $sDir => $oCheckResult) { $oPage->p(' '.$oCheckResult->sLabel); } } $sChecked = ($this->oWizard->GetParameter('upgrade_type') == 'use-compatible') ? ' checked ' : ''; - $oPage->p(''); + $oPage->p(''); - $oPage->add(''); - $oPage->add(''); + $oPage->add(''); + $oPage->add(''); $oPage->add('
    Details of the modifications
    '); - if (count($aChanges['added']) > 0) - { + if (count($aChanges['added']) > 0) { $oPage->add('
      New files added:'); - foreach($aChanges['added'] as $sFilePath => $void) - { + foreach ($aChanges['added'] as $sFilePath => $void) { $oPage->add('
    • '.$sFilePath.'
    • '); } $oPage->add('
    '); } - if (count($aChanges['removed']) > 0) - { + if (count($aChanges['removed']) > 0) { $oPage->add('
      Deleted files:'); - foreach($aChanges['removed'] as $sFilePath => $void) + foreach ($aChanges['removed'] as $sFilePath => $void) { $oPage->add('
    • '.$sFilePath.'
    • '); } @@ -644,10 +631,7 @@ EOF ); if ($oMutex->IsLocked()) { - $oPage->add(<<An iTop cron process is being executed on the target database. iTop cron process will be stopped during the setup execution.
    -HTML - ); + $oPage->add('
    '.ITOP_APPLICATION.' cron process is being executed on the target database. '.ITOP_APPLICATION.' cron process will be stopped during the setup execution.
    '); } } } @@ -695,41 +679,78 @@ class WizStepLicense extends WizardStep return array('class' => 'WizStepDBParams', 'state' => ''); } + /** + * @return bool + * @throws \Exception + * @since 2.7.7 3.0.2 3.1.0 + */ + private function NeedsRgpdConsent() + { + $sMode = $this->oWizard->GetParameter('install_mode'); + $aModules = SetupUtils::AnalyzeInstallation($this->oWizard); + return $sMode == 'install' && !SetupUtils::IsProductVersion($aModules); + } + /** * @param WebPage $oPage */ public function Display(WebPage $oPage) { - $aLicenses = SetupUtils::GetLicenses(); - $oPage->add_style( -<<add_style( + <<add('

    Licenses agreements for the components of '.ITOP_APPLICATION.'

    '); - $oPage->add_style('div a.no-arrow { background:transparent; padding-left:0;}'); - $oPage->add_style('.toggle { cursor:pointer; text-decoration:underline; color:#1C94C4; }'); - $oPage->add('
    '); - $oPage->add('Components of '.ITOP_APPLICATION.''); - $oPage->add('
      '); - $index = 0; - foreach ($aLicenses as $oLicense) - { - $oPage->add('
    • '.$oLicense->product.', © '.$oLicense->author.' is licensed under the '.$oLicense->license_type.' license. (Details)'); - $oPage->add(''); - $oPage->add_ready_script('$(".license_text a").attr("target", "_blank").addClass("no-arrow");'); - $oPage->add_ready_script('$("#toggle_'.$index.'").on("click", function() { $("#license_'.$index.'").toggle(); } );'); - $index++; - } - $oPage->add('
    '); - $oPage->add('
    '); - $sChecked = ($this->oWizard->GetParameter('accept_license', 'no') == 'yes') ? ' checked ' : ''; - $oPage->add('
    '); - $oPage->add_ready_script('$("#accept").bind("click change", function() { WizardUpdateButtons(); });'); + $oPage->add('

    Licenses agreements for the components of '.ITOP_APPLICATION.'

    '); + $oPage->add_style('div a.no-arrow { background:transparent; padding-left:0;}'); + $oPage->add_style('.toggle { cursor:pointer; text-decoration:underline; color:#1C94C4; }'); + $oPage->add('
    '); + $oPage->add('Components of '.ITOP_APPLICATION.''); + $oPage->add('
      '); + $index = 0; + foreach ($aLicenses as $oLicense) { + $oPage->add('
    • '.$oLicense->product.', © '.$oLicense->author.' is licensed under the '.$oLicense->license_type.' license. (Details)'); + $oPage->add(''); + $oPage->add_ready_script('$(".license_text a").attr("target", "_blank").addClass("no-arrow");'); + $oPage->add_ready_script('$("#toggle_'.$index.'").on("click", function() { $("#license_'.$index.'").toggle(); } );'); + $index++; + } + $oPage->add('
    '); + $oPage->add('
    '); + $sChecked = ($this->oWizard->GetParameter('accept_license', 'no') == 'yes') ? ' checked ' : ''; + $oPage->add('
    '); + if ($this->NeedsRgpdConsent()) { + $oPage->add('
    '); + $oPage->add('
    '); + $oPage->add('European General Data Protection Regulation'); + $oPage->add('
    '.ITOP_APPLICATION.' software is compliant with the processing of personal data according to the European General Data Protection Regulation (GDPR).

    +By installing '.ITOP_APPLICATION.' you agree that some information will be collected by Combodo to help you manage your instances and for statistical purposes. +This data remains anonymous until it is associated to a user account on iTop Hub.

    +

    List of collected data available in our Data privacy section.


    '); + $oPage->add(''); + $oPage->add(''); + $oPage->add('
    '); + } + $oPage->add_ready_script('$(".check_select").bind("click change", function() { WizardUpdateButtons(); });'); + + $oPage->add_script( + <<add('
    '); $oPage->add('Administrator Account'); $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); $sSourceDir = APPROOT.'dictionaries/'; $aLanguages = SetupUtils::GetAvailableLanguages($sSourceDir); $oPage->add('
    Login:
    Password:
    Confirm password:
    Login:
    Password:
    Confirm password:
    Language: '); @@ -897,7 +918,7 @@ class WizStepAdminAccount extends WizardStep $oPage->add('
    '); $oPage->add('
    '); $oPage->add_ready_script( -<<add('
    '); $oPage->add('Application URL'); $oPage->add(''); - $oPage->add(''); + $oPage->add(''); $oPage->add('
    URL:
    URL:
    '); $oPage->add('
    Change the value above if the end-users will be accessing the application by another path due to a specific configuration of the web server.
    '); $oPage->add('
    '); $oPage->add('
    '); $oPage->add('Path to Graphviz\' dot application'); $oPage->add(''); - $oPage->add(''); + $oPage->add(''); $oPage->add(''); $oPage->add('
    Path:
    Path:
    '); $oPage->add(''); $oPage->add('
    '); $oPage->add('
    '); $oPage->add('Sample Data'); - $sChecked = ($sSampleData == 'yes') ? 'checked ' : ''; - $oPage->p('
    '); $sAuthentToken = $this->oWizard->GetParameter('authent', ''); $oPage->add(''); $oPage->add_ready_script( -<<add('
    '); $oPage->add('Application URL'); $oPage->add(''); - $oPage->add(''); + $oPage->add(''); $oPage->add('
    URL:
    URL:
    '); $oPage->add('
    Change the value above if the end-users will be accessing the application by another path due to a specific configuration of the web server.
    '); $oPage->add('
    '); $oPage->add('
    '); $oPage->add('Path to Graphviz\' dot application'); $oPage->add(''); - $oPage->add(''); + $oPage->add(''); $oPage->add(''); $oPage->add('
    Path:
    Path:
    '); $oPage->add(''); @@ -1181,7 +1202,7 @@ class WizStepUpgradeMiscParams extends AbstractWizStepMiscParams $sAuthentToken = $this->oWizard->GetParameter('authent', ''); $oPage->add(''); $oPage->add_ready_script( -<<warning($e->getMessage()); + $oPage->warning($e->getHtmlDesc()); } $this->bUpgrade = ($this->oWizard->GetParameter('install_mode') != 'install'); @@ -1993,32 +2014,25 @@ EOF $index = 0; $sAllDisabled = ''; - if ($bAllDisabled) - { + if ($bAllDisabled) { $sAllDisabled = 'disabled data-disabled="disabled" '; } - foreach($aOptions as $index => $aChoice) - { + foreach ($aOptions as $index => $aChoice) { $sAttributes = ''; $sChoiceId = $sParentId.self::$SEP.$index; - $sDataId = 'data-id="'.htmlentities($aChoice['extension_code'], ENT_QUOTES, 'UTF-8').'"'; - $sId = htmlentities($aChoice['extension_code'], ENT_QUOTES, 'UTF-8'); + $sDataId = 'data-id="'.utils::EscapeHtml($aChoice['extension_code']).'"'; + $sId = utils::EscapeHtml($aChoice['extension_code']); $bIsDefault = array_key_exists($sChoiceId, $aDefaults); $bSelected = isset($aSelectedComponents[$sChoiceId]) && ($aSelectedComponents[$sChoiceId] == $sChoiceId); $bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || ($this->bUpgrade && $bIsDefault); $bDisabled = false; - if ($bMandatory) - { + if ($bMandatory) { $oPage->add('
     '); $bDisabled = true; - } - else if ($bSelected) - { + } else if ($bSelected) { $oPage->add('
     '); - } - else - { + } else { $oPage->add('
     '); } $this->DisplayChoice($oPage, $aChoice, $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled); @@ -2050,35 +2064,30 @@ EOF } } - if (!array_key_exists($sChoiceName, $aDefaults) || ($aDefaults[$sChoiceName] == $sChoiceIdNone)) - { + if (!array_key_exists($sChoiceName, $aDefaults) || ($aDefaults[$sChoiceName] == $sChoiceIdNone)) { // The "none" choice does not disable the selection !! $sDisabled = ''; $bDisabled = false; } - foreach($aAlternatives as $index => $aChoice) - { + foreach ($aAlternatives as $index => $aChoice) { $sAttributes = ''; $sChoiceId = $sParentId.self::$SEP.$index; - $sDataId = 'data-id="'.htmlentities($aChoice['extension_code'], ENT_QUOTES, 'UTF-8').'"'; - $sId = htmlentities($aChoice['extension_code'], ENT_QUOTES, 'UTF-8'); + $sDataId = 'data-id="'.utils::EscapeHtml($aChoice['extension_code']).'"'; + $sId = utils::EscapeHtml($aChoice['extension_code']); - if ($sChoiceName == null) - { + if ($sChoiceName == null) { $sChoiceName = $sChoiceId; // All radios share the same name } $bIsDefault = array_key_exists($sChoiceName, $aDefaults) && ($aDefaults[$sChoiceName] == $sChoiceId); $bSelected = isset($aSelectedComponents[$sChoiceName]) && ($aSelectedComponents[$sChoiceName] == $sChoiceId); - if ( !isset($aSelectedComponents[$sChoiceName]) && ($sChoiceIdNone != null)) - { + if (!isset($aSelectedComponents[$sChoiceName]) && ($sChoiceIdNone != null)) { // No choice selected, select the "None" option $bSelected = ($sChoiceId == $sChoiceIdNone); } $bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']) || ($this->bUpgrade && $bIsDefault); - if ($bSelected) - { + if ($bSelected) { $sAttributes = ' checked '; } $sHidden = ''; @@ -2098,12 +2107,11 @@ EOF { $sMoreInfo = (isset($aChoice['more_info']) && ($aChoice['more_info'] != '')) ? 'More information' : ''; $sSourceLabel = isset($aChoice['source_label']) ? $aChoice['source_label'] : ''; - $sId = htmlentities($aChoice['extension_code'], ENT_QUOTES, 'UTF-8'); - $oPage->add(' '.$sMoreInfo); - $sDescription = isset($aChoice['description']) ? htmlentities($aChoice['description'], ENT_QUOTES, 'UTF-8') : ''; + $sId = utils::EscapeHtml($aChoice['extension_code']); + $oPage->add(' '.$sMoreInfo); + $sDescription = isset($aChoice['description']) ? utils::EscapeHtml($aChoice['description']) : ''; $oPage->add('
    '.$sDescription.''); - if (isset($aChoice['sub_options'])) - { + if (isset($aChoice['sub_options'])) { $this->DisplayOptions($oPage, $aChoice['sub_options'], $aSelectedComponents, $aDefaults, $sChoiceId, $bDisabled); } $oPage->add('
    '); @@ -2138,7 +2146,7 @@ class WizStepSummary extends WizardStep catch(MissingDependencyException $e) { $this->bDependencyCheck = false; - $this->sDependencyIssue = $e->getMessage(); + $this->sDependencyIssue = $e->getHtmlDesc(); } } return $this->bDependencyCheck; @@ -2264,8 +2272,7 @@ class WizStepSummary extends WizardStep if (count($aMiscOptions) > 0) { $oPage->add('
    Miscellaneous Options
      '); - foreach($aMiscOptions as $sKey => $sValue) - { + foreach ($aMiscOptions as $sKey => $sValue) { $oPage->add('
    • '.$sKey.': '.$sValue.'
    • '); } $oPage->add('
    '); @@ -2274,8 +2281,7 @@ class WizStepSummary extends WizardStep $aSelectedModules = $aInstallParams['selected_modules']; - if (isset($aMiscOptions['generate_config'])) - { + if (isset($aMiscOptions['generate_config'])) { $oDoc = new DOMDocument('1.0', 'UTF-8'); $oDoc->preserveWhiteSpace = false; $oDoc->formatOutput = true; @@ -2284,7 +2290,7 @@ class WizStepSummary extends WizardStep $oParams->ToXML($oDoc, null, 'installation'); $sXML = $oDoc->saveXML(); $oPage->add('
    XML Config file
      ');
      -			$oPage->add(htmlentities($sXML, ENT_QUOTES, 'UTF-8'));
      +			$oPage->add(utils::EscapeHtml($sXML));
       			$oPage->add('
    '); } @@ -2299,13 +2305,12 @@ class WizStepSummary extends WizardStep $oPage->add('
    '); $sJSONData = json_encode($aInstallParams); - $oPage->add(''); + $oPage->add(''); $sAuthentToken = $this->oWizard->GetParameter('authent', ''); $oPage->add(''); - if (!$this->CheckDependencies()) - { + if (!$this->CheckDependencies()) { $oPage->error($this->sDependencyIssue); } @@ -2428,12 +2433,11 @@ JS $oParameters->LoadFromHash(json_decode($sJSONParameters, true /* bAssoc */)); $oInstaller = new ApplicationInstaller($oParameters); $aRes = $oInstaller->ExecuteStep($sStep); - if (($aRes['status'] != ApplicationInstaller::ERROR) && ($aRes['next-step'] != '')) - { + if (($aRes['status'] != ApplicationInstaller::ERROR) && ($aRes['next-step'] != '')) { // Tell the web page to move the progress bar and to launch the next step - $sMessage = addslashes(htmlentities($aRes['next-step-label'], ENT_QUOTES, 'UTF-8')); + $sMessage = addslashes(utils::EscapeHtml($aRes['next-step-label'])); $oPage->add_ready_script( -<<add_ready_script( -<<', $sMessage); $oPage->add_ready_script( -<<oWizard->GetParameter('application_url').'pages/UI.php">'; - $sForm .= ''; - $sForm .= ''; + $sForm .= ''; + $sForm .= ''; $sForm .= "
    "; $sForm .= ''; - $sPHPVersion = phpversion(); - $sMySQLVersion = SetupUtils::GetMySQLVersion( - $this->oWizard->GetParameter('db_server'), - $this->oWizard->GetParameter('db_user'), - $this->oWizard->GetParameter('db_pwd'), - $this->oWizard->GetParameter('db_tls_enabled'), - $this->oWizard->GetParameter('db_tls_ca') - ); - $aParameters = json_decode($this->oWizard->GetParameter('selected_components', '{}'), true); - $sCompactWizChoices = array(); - foreach($aParameters as $iStep => $aChoices) - { - $aShortChoices = array(); - foreach($aChoices as $sChoiceCode) - { - $sShortCode = str_replace('_', '', $sChoiceCode); - $aShortChoices[] = $sShortCode; - } - $sCompactWizChoices[] = implode(' ',$aShortChoices); - } - $sInstallMode = 'i'; - if ($this->oWizard->GetParameter('install_mode', 'install') == 'upgrade') - { - if (!$this->oWizard->GetParameter('license')) - { - // When the version does not change we don't ask for the licence again - $sInstallMode = 'r'; - } - else - { - // An actual upgrade - $sInstallMode = 'u'; - } - } - $aUrlParams = array( - 'p' => ITOP_APPLICATION, - 'v' => ITOP_VERSION, - 'php' => $sPHPVersion, - 'mysql' => $sMySQLVersion, - 'os' => PHP_OS, - 's' => ($this->oWizard->GetParameter('sample_data', '') == 'yes') ? 1 : 0 , - 'l' => $this->oWizard->GetParameter('default_language'), - 'i' => $sInstallMode, - 'w' => json_encode($sCompactWizChoices), - ); - $aSafeParams = array(); - foreach($aUrlParams as $sCode => $sValue) - { - $aSafeParams[] = $sCode.'='.urlencode($sValue); - } - $sImgUrl = 'http://www.combodo.com/stats/?'.implode('&', $aSafeParams); - - $aAdditionalModules = array(); - foreach(json_decode($this->oWizard->GetParameter('additional_extensions_modules'), true) as $idx => $aModuleInfo) - { - if (in_array('_'.$idx, $aParameters[count($aParameters)-1])) { - // Extensions "choices" can now have more than one module - foreach ($aModuleInfo['modules'] as $sModuleName) { - $aAdditionalModules[] = $sModuleName; - } - } - } - $idx = 0; - $aReportedModules = array(); - while ($idx < count($aAdditionalModules) && (strlen($sImgUrl.'&m='.urlencode(implode(' ', $aReportedModules))) < 2000)) // reasonable limit for the URL: 2000 chars - { - $aReportedModules[] = $aAdditionalModules[$idx]; - $idx++; - } - $sImgUrl .= '&m='.urlencode(implode(' ', $aReportedModules)); - - $oPage->add(''); $sForm = addslashes($sForm); $oPage->add_ready_script("$('#wiz_form').append('$sForm');"); // avoid leaving in a dirty state diff --git a/sources/Application/Branding.php b/sources/Application/Branding.php index d488dbba10..d594fcf839 100644 --- a/sources/Application/Branding.php +++ b/sources/Application/Branding.php @@ -53,7 +53,7 @@ class Branding 'default' => 'images/logo-itop-dark-bg.svg', ], self::ENUM_LOGO_TYPE_LOGIN_LOGO => [ - 'default' => 'images/itop-logo.png', + 'default' => 'images/itop-logo-external.png', ], ]; diff --git a/sources/Application/Helper/Session.php b/sources/Application/Helper/Session.php index ca2d7585d5..9a90ca5b46 100644 --- a/sources/Application/Helper/Session.php +++ b/sources/Application/Helper/Session.php @@ -25,16 +25,18 @@ class Session public static function Start() { + if (!self::$bIsInitialized) { + session_name('itop-'.md5(APPROOT)); + } self::$bIsInitialized = true; if (!self::$bSessionStarted) { - session_name('itop-'.md5(APPROOT)); if (!is_null(self::$iSessionId)) { - session_id(self::$iSessionId); - self::$bSessionStarted = session_start(); - } else { - self::$bSessionStarted = session_start(); - self::$iSessionId = session_id(); + if (session_id(self::$iSessionId) === false) { + session_regenerate_id(); + } } + self::$bSessionStarted = session_start(); + self::$iSessionId = session_id(); } } diff --git a/sources/Application/Search/CriterionConversion/criteriontooql.class.inc.php b/sources/Application/Search/CriterionConversion/criteriontooql.class.inc.php index bfd291e7f4..f81053105e 100644 --- a/sources/Application/Search/CriterionConversion/criteriontooql.class.inc.php +++ b/sources/Application/Search/CriterionConversion/criteriontooql.class.inc.php @@ -35,6 +35,7 @@ use DBObjectSearch; use Exception; use Expression; use MetaModel; +use utils; class CriterionToOQL extends CriterionConversionAbstract { @@ -119,8 +120,7 @@ class CriterionToOQL extends CriterionConversionAbstract $aValues = self::GetValues($aCriteria); $sValue = self::GetValue($aValues, 0); - if (!strlen($sValue)) - { + if (!utils::StrLen($sValue)) { return "1"; } @@ -132,8 +132,7 @@ class CriterionToOQL extends CriterionConversionAbstract $aValues = self::GetValues($aCriteria); $sValue = self::GetValue($aValues, 0); - if (!strlen($sValue)) - { + if (!utils::StrLen($sValue)) { return "1"; } @@ -145,8 +144,7 @@ class CriterionToOQL extends CriterionConversionAbstract $aValues = self::GetValues($aCriteria); $sValue = self::GetValue($aValues, 0); - if (!strlen($sValue)) - { + if (!utils::StrLen($sValue)) { return "1"; } @@ -162,8 +160,7 @@ class CriterionToOQL extends CriterionConversionAbstract return "({$sRef} = '0')"; } - if (!strlen($sValue) && (!(isset($aCriteria['has_undefined'])) || !($aCriteria['has_undefined']))) - { + if (!utils::StrLen($sValue) && (!(isset($aCriteria['has_undefined'])) || !($aCriteria['has_undefined']))) { return "1"; } @@ -175,8 +172,7 @@ class CriterionToOQL extends CriterionConversionAbstract $aValues = self::GetValues($aCriteria); $sValue = self::GetValue($aValues, 0); - if (!strlen($sValue)) - { + if (!utils::StrLen($sValue)) { return "1"; } @@ -191,12 +187,9 @@ class CriterionToOQL extends CriterionConversionAbstract for($i = 0; $i < count($aValues); $i++) { $sRawValue = self::GetValue($aValues, $i); - if (strlen($sRawValue) == 0) - { + if (!utils::StrLen($sRawValue)) { $bHasUnDefined = true; - } - else - { + } else { $aRawValues[] = $sRawValue; } } diff --git a/sources/Application/Search/searchform.class.inc.php b/sources/Application/Search/searchform.class.inc.php index 4d06f4946d..a6f908e31c 100644 --- a/sources/Application/Search/searchform.class.inc.php +++ b/sources/Application/Search/searchform.class.inc.php @@ -119,7 +119,7 @@ class SearchForm } $sContext = $oAppContext->GetForLink(); - $sJsonExtraParams = htmlentities(json_encode($aListParams), ENT_QUOTES); + $sJsonExtraParams = utils::EscapeHtml(json_encode($aListParams)); $sOuterSelector = $aExtraParams['result_list_outer_selector']; if (isset($aExtraParams['search_header_force_dropdown'])) { @@ -493,7 +493,7 @@ class SearchForm $aAllowedValues = array(); while ($oObject = $oSet->Fetch()) { - $aAllowedValues[] = ["value"=>$oObject->GetKey(), "label" => $oObject->GetName()]; + $aAllowedValues[$oObject->GetKey()] = $oObject->GetName(); } return array('values' => $aAllowedValues); } diff --git a/sources/Application/TwigBase/Controller/Controller.php b/sources/Application/TwigBase/Controller/Controller.php index 81d746ac0c..6a2dad13bf 100644 --- a/sources/Application/TwigBase/Controller/Controller.php +++ b/sources/Application/TwigBase/Controller/Controller.php @@ -85,7 +85,7 @@ abstract class Controller * @param string $sViewPath Path of the twig files * @param string $sModuleName name of the module (or 'core' if not a module) */ - public function __construct($sViewPath, $sModuleName = 'core') + public function __construct($sViewPath, $sModuleName = 'core', $aAdditionalPaths = []) { $this->m_aLinkedScripts = []; $this->m_aLinkedStylesheets = []; @@ -93,7 +93,7 @@ abstract class Controller $this->m_aAjaxTabs = []; $this->m_aDefaultParams = []; $this->m_aBlockParams = []; - $this->SetViewPath($sViewPath); + $this->SetViewPath($sViewPath, $aAdditionalPaths); $this->SetModuleName($sModuleName); if ($sModuleName != 'core') { try { @@ -128,9 +128,9 @@ abstract class Controller * * @param string $sViewPath */ - public function SetViewPath($sViewPath) + public function SetViewPath($sViewPath, $aAdditionalPaths = []) { - $oTwig = TwigHelper::GetTwigEnvironment($sViewPath); + $oTwig = TwigHelper::GetTwigEnvironment($sViewPath, $aAdditionalPaths); $this->m_oTwig = $oTwig; } @@ -182,7 +182,7 @@ abstract class Controller http_response_code(500); $oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError')); $oP->add("

    ".Dict::S('UI:FatalErrorMessage')."

    \n"); - $oP->add(get_class($e).' : '.htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8')); + $oP->add(get_class($e).' : '.utils::EscapeHtml($e->GetMessage())); $oP->output(); IssueLog::Error($e->getMessage()); diff --git a/sources/Application/TwigBase/Twig/Extension.php b/sources/Application/TwigBase/Twig/Extension.php index 65d4a1005f..fd71ca08f0 100644 --- a/sources/Application/TwigBase/Twig/Extension.php +++ b/sources/Application/TwigBase/Twig/Extension.php @@ -15,9 +15,9 @@ use Combodo\iTop\Renderer\BlockRenderer; use Dict; use Exception; use MetaModel; -use Twig_Environment; -use Twig_SimpleFilter; -use Twig_SimpleFunction; +use Twig\Environment; +use Twig\TwigFilter; +use Twig\TwigFunction; use utils; class Extension @@ -26,70 +26,77 @@ class Extension * Registers Twig extensions such as filters or functions. * It allows us to access some stuff directly in twig. * - * @param \Twig_Environment $oTwigEnv + * @param Environment $oTwigEnv */ - public static function RegisterTwigExtensions(Twig_Environment &$oTwigEnv) + public static function RegisterTwigExtensions(Environment &$oTwigEnv): void { + $aFilters = static::GetFilters(); + foreach ($aFilters as $oFilter) { + $oTwigEnv->addFilter($oFilter); + } + + $aFunctions = static::GetFunctions(); + foreach ($aFunctions as $oFunction) { + $oTwigEnv->addFunction($oFunction); + } + } + + /** + * @used-by \Combodo\iTop\Portal\Twig\AppExtension + * @return TwigFilter[] Custom TWIG filters used in iTop + * @since 3.1.0 + */ + public static function GetFilters() + { + $aFilters = []; + // Filter to translate a string via the Dict::S function // Usage in twig: {{ 'String:ToTranslate'|dict_s }} - $oTwigEnv->addFilter(new Twig_SimpleFilter('dict_s', - function ($sStringCode, $sDefault = null, $bUserLanguageOnly = false) { - return Dict::S($sStringCode, $sDefault, $bUserLanguageOnly); - }) - ); + $aFilters[] = new TwigFilter('dict_s', function ($sStringCode, $sDefault = null, $bUserLanguageOnly = false) { + return Dict::S($sStringCode, $sDefault, $bUserLanguageOnly); + }); // Filter to format a string via the Dict::Format function // Usage in twig: {{ 'String:ToTranslate'|dict_format() }} - $oTwigEnv->addFilter(new Twig_SimpleFilter('dict_format', - function ($sStringCode, $sParam01 = null, $sParam02 = null, $sParam03 = null, $sParam04 = null) { - return Dict::Format($sStringCode, $sParam01, $sParam02, $sParam03, $sParam04); - }) - ); + $aFilters[] = new TwigFilter('dict_format', function ($sStringCode, $sParam01 = null, $sParam02 = null, $sParam03 = null, $sParam04 = null) { + return Dict::Format($sStringCode, $sParam01, $sParam02, $sParam03, $sParam04); + }); // Filter to format output - // example a DateTime is converted to user format - // Usage in twig: {{ 'String:ToFormat'|output_format }} - $oTwigEnv->addFilter(new Twig_SimpleFilter('date_format', - function ($sDate) { - try - { - if (preg_match('@^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$@', trim($sDate))) - { - return AttributeDateTime::GetFormat()->Format($sDate); - } - if (preg_match('@^\d\d\d\d-\d\d-\d\d$@', trim($sDate))) - { - return AttributeDate::GetFormat()->Format($sDate); - } - } - catch (Exception $e) - { - } + // For example a DateTime is converted to user format + // Usage in twig: {{ '2022-05-13 12:00:00'|date_format }} + $aFilters[] = new TwigFilter('date_format', function ($sDate) { + try { + if (preg_match('@^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$@', trim($sDate))) { + return AttributeDateTime::GetFormat()->Format($sDate); + } + if (preg_match('@^\d\d\d\d-\d\d-\d\d$@', trim($sDate))) { + return AttributeDate::GetFormat()->Format($sDate); + } + } + catch (Exception $e) { + } - return $sDate; - }) - ); + return $sDate; + }); // Filter to format output - // example a DateTime is converted to user format - // Usage in twig: {{ 'String:ToFormat'|output_format }} - $oTwigEnv->addFilter(new Twig_SimpleFilter('size_format', - function ($sSize) { - return utils::BytesToFriendlyFormat($sSize); - }) - ); + // For example a file size is converted to human readable format + // Usage in twig: {{ '4096'|size_format }} + $aFilters[] = new TwigFilter('size_format', function ($sSize) { + return utils::BytesToFriendlyFormat($sSize); + }); // Filter to enable base64 encode/decode // Usage in twig: {{ 'String to encode'|base64_encode }} - $oTwigEnv->addFilter(new Twig_SimpleFilter('base64_encode', 'base64_encode')); - $oTwigEnv->addFilter(new Twig_SimpleFilter('base64_decode', 'base64_decode')); + $aFilters[] = new TwigFilter('base64_encode', 'base64_encode'); + $aFilters[] = new TwigFilter('base64_decode', 'base64_decode'); // Filter to enable json decode (encode already exists) // Usage in twig: {{ aSomeArray|json_decode }} - $oTwigEnv->addFilter(new Twig_SimpleFilter('json_decode', function ($sJsonString, $bAssoc = false) { - return json_decode($sJsonString, $bAssoc); - }) - ); + $aFilters[] = new TwigFilter('json_decode', function ($sJsonString, $bAssoc = false) { + return json_decode($sJsonString, $bAssoc); + }); /** * Filter to sanitize a text @@ -98,10 +105,9 @@ class Extension * @uses \utils::Sanitize() * @since 3.0.0 */ - $oTwigEnv->addFilter(new Twig_SimpleFilter('sanitize', function (string $sString, string $sFilter) { - return utils::Sanitize($sString, '', $sFilter); - }) - ); + $aFilters[] = new TwigFilter('sanitize', function (string $sString, string $sFilter) { + return utils::Sanitize($sString, '', $sFilter); + }); /** * Filter to transform the wiki syntax ONLY into HTML. @@ -109,47 +115,67 @@ class Extension * @uses \AttributeText::RenderWikiHtml() * @since 3.0.0 */ - $oTwigEnv->addFilter(new Twig_SimpleFilter('render_wiki_to_html', function ($sString) { - return AttributeText::RenderWikiHtml($sString, true /* Important, otherwise hyperlinks will be tranformed as well */); - }) - ); + $aFilters[] = new TwigFilter('render_wiki_to_html', function ($sString) { + return AttributeText::RenderWikiHtml($sString, true /* Important, otherwise hyperlinks will be tranformed as well */); + }); // Filter to add a parameter at the end of the URL to force cache invalidation after an upgrade. // Previously we put the iTop version but now it's the last setup/toolkit timestamp to avoid cache issues when building several times the same version during tests // - // Note: This could be rename "add_cache_buster" instead. - $oTwigEnv->addFilter(new Twig_SimpleFilter('add_itop_version', function ($sUrl) { + // Note: This could be renamed "add_cache_buster" instead. + $aFilters[] = new TwigFilter('add_itop_version', function ($sUrl) { $sUrl = utils::AddParameterToUrl($sUrl, 't', utils::GetCacheBusterTimestamp()); return $sUrl; - })); + }); // Filter to add a module's version to an url - $oTwigEnv->addFilter(new Twig_SimpleFilter('add_module_version', function ($sUrl, $sModuleName) { + $aFilters[] = new TwigFilter('add_module_version', function ($sUrl, $sModuleName) { $sModuleVersion = utils::GetCompiledModuleVersion($sModuleName); $sUrl = utils::AddParameterToUrl($sUrl, 'moduleversion', $sModuleVersion); return $sUrl; - })); + }); // var_export can be used for example to transform a PHP boolean to 'true' or 'false' strings // @see https://www.php.net/manual/fr/function.var-export.php - $oTwigEnv->addFilter(new Twig_SimpleFilter('var_export', 'var_export')); + $aFilters[] = new TwigFilter('var_export', 'var_export'); + // @since 2.7.7 3.0.2 3.1.0 N°4867 "Twig content not allowed" error when use the extkey widget search icon in the user portal + // Overwrite native twig filter: disable use of 'system' filter + $aFilters[] = new TwigFilter('filter', function (Environment $oTwigEnv, $array, $arrow) { + if ($arrow == 'system') { + return json_encode($array); + } + + return twig_array_filter($oTwigEnv, $array, $arrow); + }, ['needs_environment' => true]); + + return $aFilters; + } + + /** + * @used-by \Combodo\iTop\Portal\Twig\AppExtension + * @return \TwigFunction[] Custom TWIG function used in iTop + * @since 3.1.0 + */ + public static function GetFunctions() + { + $aFunctions = []; // Function to check our current environment - // Usage in twig: {% if is_development_environment() %} - $oTwigEnv->addFunction(new Twig_SimpleFunction('is_development_environment', function () { + // Usage in twig: {% if is_development_environment() %} + $aFunctions[] = new TwigFunction('is_development_environment', function () { return utils::IsDevelopmentEnvironment(); - })); + }); // Function to get configuration parameter // Usage in twig: {{ get_config_parameter('foo') }} - $oTwigEnv->addFunction(new Twig_SimpleFunction('get_config_parameter', function ($sParamName) { + $aFunctions[] = new TwigFunction('get_config_parameter', function ($sParamName) { $oConfig = MetaModel::GetConfig(); return $oConfig->Get($sParamName); - })); + }); /** * Function to get a module setting @@ -158,34 +184,39 @@ class Extension * @uses Config::GetModuleSetting() * @since 3.0.0 */ - $oTwigEnv->addFunction(new Twig_SimpleFunction('get_module_setting', + $aFunctions[] = new TwigFunction('get_module_setting', function (string $sModuleCode, string $sPropertyCode, $defaultValue = null) { $oConfig = MetaModel::GetConfig(); return $oConfig->GetModuleSetting($sModuleCode, $sPropertyCode, $defaultValue); - })); + }); // Function to get iTop's app root absolute URL (eg. https://aaa.bbb.ccc/xxx/yyy/) // Usage in twig: {{ get_absolute_url_app_root() }} /** @since 3.0.0 */ - $oTwigEnv->addFunction(new Twig_SimpleFunction('get_absolute_url_app_root', function () { + $aFunctions[] = new TwigFunction('get_absolute_url_app_root', function () { return utils::GetAbsoluteUrlAppRoot(); - })); + }); // Function to get iTop's modules root absolute URL (eg. https://aaa.bbb.ccc/xxx/yyy/env-zzz/) // Usage in twig: {{ get_absolute_url_modules_root() }} /** @since 3.0.0 */ - $oTwigEnv->addFunction(new Twig_SimpleFunction('get_absolute_url_modules_root', function () { + $aFunctions[] = new TwigFunction('get_absolute_url_modules_root', function () { return utils::GetAbsoluteUrlModulesRoot(); - })); + }); // Function to render a UI block (HTML, inline CSS, inline JS) and its sub blocks directly in the TWIG // Usage in twig: {{ render_block(oBlock) }} /** @since 3.0.0 */ - $oTwigEnv->addFunction(new Twig_SimpleFunction('render_block', function(iUIBlock $oBlock, $aContextParams = []){ - $oRenderer = new BlockRenderer($oBlock, $aContextParams); - return $oRenderer->RenderHtml(); - }, ['is_safe' => ['html']])); - } + $aFunctions[] = new TwigFunction('render_block', + function (iUIBlock $oBlock, $aContextParams = []) { + $oRenderer = new BlockRenderer($oBlock, $aContextParams); + return $oRenderer->RenderHtml(); + }, + ['is_safe' => ['html']] + ); + + return $aFunctions; + } } diff --git a/sources/Application/TwigBase/Twig/TwigHelper.php b/sources/Application/TwigBase/Twig/TwigHelper.php index f6becd6cc1..6660038936 100644 --- a/sources/Application/TwigBase/Twig/TwigHelper.php +++ b/sources/Application/TwigBase/Twig/TwigHelper.php @@ -16,8 +16,7 @@ use ExecutionKPI; use IssueLog; use Twig\Environment; use Twig\Error\Error; -use Twig_Environment; -use Twig_Loader_Filesystem; +use Twig\Loader\FilesystemLoader; use utils; use WebPage; @@ -71,17 +70,17 @@ class TwigHelper * @param string $sViewPath * @param array $aAdditionalPaths * - * @return \Twig_Environment + * @return Environment * @throws \Twig\Error\LoaderError */ public static function GetTwigEnvironment($sViewPath, $aAdditionalPaths = array()) { - $oLoader = new Twig_Loader_Filesystem($sViewPath); + $oLoader = new FilesystemLoader($sViewPath); foreach ($aAdditionalPaths as $sAdditionalPath) { $oLoader->addPath($sAdditionalPath); } - $oTwig = new Twig_Environment($oLoader); + $oTwig = new Environment($oLoader); Extension::RegisterTwigExtensions($oTwig); if (!utils::IsDevelopmentEnvironment()) { // Disable the cache in development environment diff --git a/sources/Application/UI/Base/Component/Button/ButtonUIBlockFactory.php b/sources/Application/UI/Base/Component/Button/ButtonUIBlockFactory.php index 7867aefe59..0b9d5b8752 100644 --- a/sources/Application/UI/Base/Component/Button/ButtonUIBlockFactory.php +++ b/sources/Application/UI/Base/Component/Button/ButtonUIBlockFactory.php @@ -21,6 +21,7 @@ namespace Combodo\iTop\Application\UI\Base\Component\Button; use Combodo\iTop\Application\UI\Base\AbstractUIBlockFactory; use Dict; +use utils; /** * Class ButtonUIBlockFactory @@ -423,11 +424,11 @@ class ButtonUIBlockFactory extends AbstractUIBlockFactory $oButton->SetActionType($sActionType) ->SetColor($sColor); - if (strlen($sValue) > 0) { + if (utils::IsNotNullOrEmptyString($sValue)) { $oButton->SetValue($sValue); } - if (strlen($sName) > 0) { + if (utils::IsNotNullOrEmptyString($sName)) { $oButton->SetName($sName); } diff --git a/sources/Application/UI/Base/Component/ButtonGroup/ButtonGroup.php b/sources/Application/UI/Base/Component/ButtonGroup/ButtonGroup.php index 23828b6994..06c0d98ac5 100644 --- a/sources/Application/UI/Base/Component/ButtonGroup/ButtonGroup.php +++ b/sources/Application/UI/Base/Component/ButtonGroup/ButtonGroup.php @@ -25,6 +25,10 @@ class ButtonGroup extends UIBlock public const BLOCK_CODE = 'ibo-button-group'; /** @inheritDoc */ public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/components/button-group/layout'; + public const DEFAULT_JS_FILES_REL_PATH = [ + 'js/components/button-group.js', + ]; + public const REQUIRES_ANCESTORS_DEFAULT_JS_FILES = true; /** @var \Combodo\iTop\Application\UI\Base\Component\Button\Button[] Buttons to be displayed as a group */ protected $aButtons; diff --git a/sources/Application/UI/Base/Component/DataTable/DataTableSettings.php b/sources/Application/UI/Base/Component/DataTable/DataTableSettings.php index 178f44e472..ccd3e8ed48 100644 --- a/sources/Application/UI/Base/Component/DataTable/DataTableSettings.php +++ b/sources/Application/UI/Base/Component/DataTable/DataTableSettings.php @@ -12,7 +12,7 @@ use Dict; use Metamodel; use Serializable; -class DataTableSettings implements Serializable +class DataTableSettings { public $aClassAliases; public $sTableId; @@ -34,6 +34,32 @@ class DataTableSettings implements Serializable $this->aColumns = array(); } + /** + * @return string + * @since 3.1.0 + */ + public function __serialize() { + return serialize([ + $this->aClassAliases, + $this->sTableId, + $this->iDefaultPageSize, + $this->aColumns + ]); + } + + /** + * @param $data + * @since 3.1.0 + */ + public function __unserialize($data) { + list( + $this->aClassAliases, + $this->sTableId, + $this->iDefaultPageSize, + $this->aColumns + ) = unserialize($data); + } + /** * @param $iDefaultPageSize * @param $aSortOrder diff --git a/sources/Application/UI/Base/Component/FieldBadge/FieldBadgeUIBlockFactory.php b/sources/Application/UI/Base/Component/FieldBadge/FieldBadgeUIBlockFactory.php index 353254153f..e8cd1c94e1 100644 --- a/sources/Application/UI/Base/Component/FieldBadge/FieldBadgeUIBlockFactory.php +++ b/sources/Application/UI/Base/Component/FieldBadge/FieldBadgeUIBlockFactory.php @@ -10,6 +10,7 @@ namespace Combodo\iTop\Application\UI\Base\Component\FieldBadge; use Combodo\iTop\Application\UI\Base\AbstractUIBlockFactory; use ormStyle; +use utils; /** * Class FieldBadgeUIBlockFactory @@ -36,6 +37,10 @@ class FieldBadgeUIBlockFactory extends AbstractUIBlockFactory { $oBadge = null; $sHtml = ''; + + // N°5318 - Sanitize value manually as this UIBlock is not using a proper TWIG template 😥 + $sValueForHtml = utils::EscapeHtml($sValue); + if ($oStyle) { $sStyleClass = $oStyle->GetStyleClass(); $sPrimaryColor = $oStyle->GetMainColor(); @@ -47,12 +52,12 @@ class FieldBadgeUIBlockFactory extends AbstractUIBlockFactory if (!is_null($sDecorationClasses) && !empty($sDecorationClasses)) { $sHtml .= ""; } - $sHtml .= "$sValue"; + $sHtml .= "$sValueForHtml"; } } if (!$oBadge) { $oBadge = new FieldBadge(); - $sHtml .= "$sValue"; + $sHtml .= "$sValueForHtml"; } $oBadge->AddHtml($sHtml); return $oBadge; diff --git a/sources/Application/UI/Base/Component/Input/InputWithLabel.php b/sources/Application/UI/Base/Component/Input/InputWithLabel.php index 325d152754..09931a4f60 100644 --- a/sources/Application/UI/Base/Component/Input/InputWithLabel.php +++ b/sources/Application/UI/Base/Component/Input/InputWithLabel.php @@ -9,6 +9,7 @@ namespace Combodo\iTop\Application\UI\Base\Component\Input; use Combodo\iTop\Application\UI\Base\UIBlock; +use utils; /** * You might want to use a {@link \Combodo\iTop\Application\UI\Base\Component\Field\Field} component instead... @@ -134,7 +135,7 @@ class InputWithLabel extends UIBlock */ public function HasDescription(): bool { - return strlen($this->sDescription) > 0; + return utils::IsNotNullOrEmptyString($this->sDescription); } } \ No newline at end of file diff --git a/sources/Application/UI/Base/Component/Input/tInputLabel.php b/sources/Application/UI/Base/Component/Input/tInputLabel.php index a889d2b18d..d67f6b1e9a 100644 --- a/sources/Application/UI/Base/Component/Input/tInputLabel.php +++ b/sources/Application/UI/Base/Component/Input/tInputLabel.php @@ -7,6 +7,8 @@ namespace Combodo\iTop\Application\UI\Base\Component\Input; +use utils; + /** * Trait tInputLabel Label for input * @@ -88,7 +90,7 @@ trait tInputLabel */ public function HasLabel(): bool { - return strlen($this->sLabel) > 0; + return utils::StrLen($this->sLabel) > 0; } /** @@ -108,6 +110,7 @@ trait tInputLabel public function SetDescription(?string $sDescription) { $this->sDescription = $sDescription; + return $this; } @@ -117,6 +120,6 @@ trait tInputLabel */ public function HasDescription(): bool { - return strlen($this->sDescription) > 0; + return utils::StrLen($this->sDescription) > 0; } } \ No newline at end of file diff --git a/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanel.php b/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanel.php index 5570d0fd9e..f41609ac4a 100644 --- a/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanel.php +++ b/sources/Application/UI/Base/Layout/ActivityPanel/ActivityPanel.php @@ -50,6 +50,7 @@ class ActivityPanel extends UIBlock public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/layouts/activity-panel/layout'; public const DEFAULT_JS_TEMPLATE_REL_PATH = 'base/layouts/activity-panel/layout'; public const DEFAULT_JS_FILES_REL_PATH = [ + 'js/jquery.ba-bbq.min.js', 'js/layouts/activity-panel/activity-panel.js', ]; diff --git a/sources/Application/UI/Base/Layout/NavigationMenu/NavigationMenu.php b/sources/Application/UI/Base/Layout/NavigationMenu/NavigationMenu.php index 9baf49a4b9..6229d755cb 100644 --- a/sources/Application/UI/Base/Layout/NavigationMenu/NavigationMenu.php +++ b/sources/Application/UI/Base/Layout/NavigationMenu/NavigationMenu.php @@ -282,11 +282,11 @@ class NavigationMenu extends UIBlock implements iKeyboardShortcut // Try if a custom URL was set in the configuration file if(MetaModel::GetConfig()->IsCustomValue($sPropCode)) { - $this->sAppIconLink = MetaModel::GetConfig()->Get('app_icon_url'); + $this->sAppIconLink = MetaModel::GetConfig()->Get($sPropCode); } // Otherwise use the home page else { - $this->sAppIconLink = MetaModel::GetConfig()->Get('app_root_url'); + $this->sAppIconLink = utils::GetAbsoluteUrlAppRoot(); } } diff --git a/sources/Application/UI/Base/tJSRefreshCallback.php b/sources/Application/UI/Base/tJSRefreshCallback.php index fd25dfd25f..6b609b327c 100644 --- a/sources/Application/UI/Base/tJSRefreshCallback.php +++ b/sources/Application/UI/Base/tJSRefreshCallback.php @@ -40,7 +40,7 @@ trait tJSRefreshCallback public function GetJSRefreshCallback(): string { $sJSRefresh = $this->GetJSRefresh(); - tJSRefreshCallback::GetRecursiveJSRefresh($this, $sJSRefresh); + self::GetRecursiveJSRefresh($this, $sJSRefresh); return $sJSRefresh; } @@ -60,7 +60,7 @@ trait tJSRefreshCallback if ($usingTrait && $oSubBlock->GetJSRefresh() != "") { $sJSRefresh = $oSubBlock->GetJSRefresh()."\n".$sJSRefresh; } - tJSRefreshCallback::GetRecursiveJSRefresh($oSubBlock, $sJSRefresh); + self::GetRecursiveJSRefresh($oSubBlock, $sJSRefresh); } return $sJSRefresh; diff --git a/sources/Application/UI/Links/Indirect/BlockIndirectLinksEdit/BlockIndirectLinksEdit.php b/sources/Application/UI/Links/Indirect/BlockIndirectLinksEdit/BlockIndirectLinksEdit.php index 0bcb338924..81002fe49f 100644 --- a/sources/Application/UI/Links/Indirect/BlockIndirectLinksEdit/BlockIndirectLinksEdit.php +++ b/sources/Application/UI/Links/Indirect/BlockIndirectLinksEdit/BlockIndirectLinksEdit.php @@ -45,6 +45,8 @@ class BlockIndirectLinksEdit extends UIContentBlock public $bJSDoSearch; /** @var int */ public $iMaxAddedId = 0; + /** @var array */ + public $aRemoved = []; /** @var string */ public $sFormPrefix; diff --git a/sources/Application/WebPage/ErrorPage.php b/sources/Application/WebPage/ErrorPage.php index 815ddc0c9d..69d039f44c 100644 --- a/sources/Application/WebPage/ErrorPage.php +++ b/sources/Application/WebPage/ErrorPage.php @@ -60,7 +60,7 @@ class ErrorPage extends NiceWebPage public function output() { - $sLogo = Branding::GetFullMainLogoAbsoluteUrl(); + $sLogo = Branding::GetLoginLogoAbsoluteUrl(); $oSetupPage = UIContentBlockUIBlockFactory::MakeStandard('ibo_setup_container', ['ibo-setup']); $oHeader = UIContentBlockUIBlockFactory::MakeStandard('header', ['ibo-setup--header']); $oSetupPage->AddSubBlock($oHeader); diff --git a/sources/Application/WebPage/NiceWebPage.php b/sources/Application/WebPage/NiceWebPage.php index de11f57d1d..abae1ab386 100644 --- a/sources/Application/WebPage/NiceWebPage.php +++ b/sources/Application/WebPage/NiceWebPage.php @@ -227,8 +227,7 @@ EOF foreach($aChoices as $sKey => $sValue) { $sSelected = ($sKey == $sDefaultValue) ? " SELECTED" : ""; - $this->add(""); + $this->add(""); } $this->add(""); } diff --git a/sources/Application/WebPage/UnauthenticatedWebPage.php b/sources/Application/WebPage/UnauthenticatedWebPage.php index 26c44f97dc..9c9b17b9eb 100644 --- a/sources/Application/WebPage/UnauthenticatedWebPage.php +++ b/sources/Application/WebPage/UnauthenticatedWebPage.php @@ -73,7 +73,7 @@ class UnauthenticatedWebPage extends NiceWebPage $this->sContent = ''; $this->sPanelTitle = ''; - $this->sPanelIcon = Branding::GetFullMainLogoAbsoluteUrl(); + $this->sPanelIcon = Branding::GetLoginLogoAbsoluteUrl(); $this->SetContentType('text/html'); diff --git a/sources/Application/WebPage/WebPage.php b/sources/Application/WebPage/WebPage.php index 9d1fb358c9..005902b492 100644 --- a/sources/Application/WebPage/WebPage.php +++ b/sources/Application/WebPage/WebPage.php @@ -216,7 +216,7 @@ class WebPage implements Page $this->bHasCollapsibleSection = false; $this->bPrintable = $bPrintable; // Note: JS dict. entries cannot be added to a page if current environment and config file aren't available yet. - $this->bAddJSDict = class_exists('\Dict') && is_dir(utils::GetCompiledEnvironmentPath()) && file_exists(utils::GetConfigFilePath()); + $this->bAddJSDict = class_exists('\Dict') && is_dir(utils::GetCompiledEnvironmentPath().'dictionaries/') && file_exists(utils::GetConfigFilePath()); $this->oContentLayout = new UIContentBlock(); $this->SetTemplateRelPath(static::DEFAULT_PAGE_TEMPLATE_REL_PATH); @@ -1368,7 +1368,7 @@ JS; * Get an ID (for any kind of HTML tag) that is guaranteed unique in this page * * @return int The unique ID (in this page) - * @deprecated since 3.0.0 use utils::GetUniqueId() instead + * @deprecated 3.0.0 use utils::GetUniqueId() instead */ public function GetUniqueId() { diff --git a/sources/Application/WebPage/iTopWizardWebPage.php b/sources/Application/WebPage/iTopWizardWebPage.php index e2660fead9..af9dd10ec2 100644 --- a/sources/Application/WebPage/iTopWizardWebPage.php +++ b/sources/Application/WebPage/iTopWizardWebPage.php @@ -50,7 +50,7 @@ class iTopWizardWebPage extends iTopWebPage $sStyle = ($iIndex == $this->m_iCurrentStep) ? 'wizActiveStep' : 'wizStep'; $aSteps[] = "
    $sStepTitle
    "; } - $sWizardHeader = "

    ".htmlentities($this->s_title, ENT_QUOTES, 'UTF-8')."

    \n".implode("
    ", $aSteps)."
    \n"; + $sWizardHeader = "

    ".utils::EscapeHtml($this->s_title)."

    \n".implode("
    ", $aSteps)."
    \n"; $this->s_content = "$sWizardHeader
    ".$this->s_content."
    "; parent::output(); } diff --git a/sources/Composer/iTopComposer.php b/sources/Composer/iTopComposer.php index 3b57aee430..c66116d3da 100644 --- a/sources/Composer/iTopComposer.php +++ b/sources/Composer/iTopComposer.php @@ -96,8 +96,12 @@ class iTopComposer { $APPROOT_WITH_SLASHES = $this->GetApprootWithSlashes(); return array( + $APPROOT_WITH_SLASHES.'lib/doctrine/lexer/tests', + $APPROOT_WITH_SLASHES.'lib/goaop/framework/tests', + $APPROOT_WITH_SLASHES.'lib/laminas/laminas-servicemanager/src/Test', + $APPROOT_WITH_SLASHES.'lib/nikic/php-parser/test', $APPROOT_WITH_SLASHES.'lib/pear/archive_tar/tests', @@ -119,13 +123,15 @@ class iTopComposer $APPROOT_WITH_SLASHES.'lib/symfony/event-dispatcher/Tests', $APPROOT_WITH_SLASHES.'lib/symfony/filesystem/Tests', $APPROOT_WITH_SLASHES.'lib/symfony/finder/Tests', - $APPROOT_WITH_SLASHES.'lib/symfony/framework-bundle/Tests', - $APPROOT_WITH_SLASHES.'lib/symfony/http-foundation/Tests', + $APPROOT_WITH_SLASHES.'lib/symfony/http-client-contracts/Test', + $APPROOT_WITH_SLASHES.'lib/symfony/http-foundation/Test', $APPROOT_WITH_SLASHES.'lib/symfony/http-kernel/Tests', + $APPROOT_WITH_SLASHES.'lib/symfony/service-contracts/Test', $APPROOT_WITH_SLASHES.'lib/symfony/framework-bundle/Test', - $APPROOT_WITH_SLASHES.'lib/symfony/framework-bundle/Tests/Fixtures/TestBundle/FooBundle/Controller/Test', + $APPROOT_WITH_SLASHES.'lib/symfony/mime/Test', $APPROOT_WITH_SLASHES.'lib/symfony/routing/Tests', $APPROOT_WITH_SLASHES.'lib/symfony/stopwatch/Tests', + $APPROOT_WITH_SLASHES.'lib/symfony/translation-contracts/Test', $APPROOT_WITH_SLASHES.'lib/symfony/twig-bridge/Tests', $APPROOT_WITH_SLASHES.'lib/symfony/twig-bundle/Tests', $APPROOT_WITH_SLASHES.'lib/symfony/var-dumper/Test', @@ -137,6 +143,8 @@ class iTopComposer $APPROOT_WITH_SLASHES.'lib/twig/twig/src/Test', $APPROOT_WITH_SLASHES.'lib/twig/twig/lib/Twig/Test', $APPROOT_WITH_SLASHES.'lib/twig/twig/doc/tests', + + $APPROOT_WITH_SLASHES.'lib/laminas/laminas-servicemanager/src/Test', ); } diff --git a/sources/Controller/AjaxRenderController.php b/sources/Controller/AjaxRenderController.php index 4eb127624a..86c8f4baf1 100644 --- a/sources/Controller/AjaxRenderController.php +++ b/sources/Controller/AjaxRenderController.php @@ -123,12 +123,12 @@ class AjaxRenderController } /** - * @param \AjaxPage $oPage + * @param \JsonPage $oPage * @param bool $bTokenOnly * * @throws \Exception */ - public static function ExportBuild(AjaxPage $oPage, $bTokenOnly) + public static function ExportBuild(JsonPage $oPage, $bTokenOnly) { register_shutdown_function(function () { $aErr = error_get_last(); @@ -208,13 +208,13 @@ class AjaxRenderController $aResult['message'] = Dict::Format('Core:BulkExport:ClickHereToDownload_FileName', $oExporter->GetDownloadFileName()); } } - $oPage->add(json_encode($aResult)); + $oPage->SetData($aResult); } catch (BulkExportException $e) { $aResult = array('code' => 'error', 'percentage' => 100, 'message' => utils::HtmlEntities($e->GetLocalizedMessage())); - $oPage->add(json_encode($aResult)); + $oPage->SetData($aResult); } catch (Exception $e) { $aResult = array('code' => 'error', 'percentage' => 100, 'message' => utils::HtmlEntities($e->getMessage())); - $oPage->add(json_encode($aResult)); + $oPage->SetData($aResult); } } @@ -224,13 +224,13 @@ class AjaxRenderController * The resulting JSON is added to the page with the format: * {"code": "done or error", "counts": {"menu_id_1": count1, "menu_id_2": count2...}} * - * @param \AjaxPage $oPage + * @param \JsonPage $oPage */ - public function GetMenusCount(AjaxPage $oPage) + public function GetMenusCount(JsonPage $oPage) { $aCounts = ApplicationMenu::GetMenusCount(); $aResult = ['code' => 'done', 'counts' => $aCounts]; - $oPage->add(json_encode($aResult)); + $oPage->SetData($aResult); } /** @@ -826,9 +826,8 @@ $('#about_box').dialog({ EOF ); $sVersionString = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION); - $oPage->add("
    "); - $oPage->add('
    '); - $oPage->add(''); + $oPage->add('
    '); + $oPage->add('
    '); $oPage->add('
    '.$sVersionString.'
    '); $oPage->add("
    "); self::DisplayAboutLicenses($oPage); @@ -909,18 +908,9 @@ EOF // Display // - $oPage->add("
    "); - $oPage->add('
    '); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add('
    '); - $oPage->add($sVersionString.'
    '); - $oPage->add('MySQL: '.$sMySQLVersion.'
    '); - $oPage->add('PHP: '.$sPHPVersion.'
    '); - $oPage->add('
    '); + $oPage->add('
    '); + $oPage->add('
    '); + $oPage->add('
    '.$sVersionString.'
    '.'MySQL: '.$sMySQLVersion.'
    '.'PHP: '.$sPHPVersion.'
    '); $oPage->add("
    "); self::DisplayAboutLicenses($oPage); diff --git a/sources/Controller/Base/Layout/ActivityPanelController.php b/sources/Controller/Base/Layout/ActivityPanelController.php index f0cda3f5d3..479452fac9 100644 --- a/sources/Controller/Base/Layout/ActivityPanelController.php +++ b/sources/Controller/Base/Layout/ActivityPanelController.php @@ -116,7 +116,9 @@ class ActivityPanelController 'html_rendering' => $sEntryAsHtml, ]; } - + // Finalize inline images + InlineImage::FinalizeInlineImages($oObject); + // Invoke extensions after the update of the object from the activity form /** @var \iApplicationUIExtension $oExtensionInstance */ foreach(MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) @@ -126,9 +128,6 @@ class ActivityPanelController $oObject->DBWrite(); - // Finalize inline images - InlineImage::FinalizeInlineImages($oObject); - return $aResults; } diff --git a/sources/Controller/OAuth/OAuthLandingController.php b/sources/Controller/OAuth/OAuthLandingController.php new file mode 100644 index 0000000000..4b869d8c18 --- /dev/null +++ b/sources/Controller/OAuth/OAuthLandingController.php @@ -0,0 +1,13 @@ +DisplayAjaxPage([]); + } +} \ No newline at end of file diff --git a/sources/Controller/PreferencesController.php b/sources/Controller/PreferencesController.php index 187639219b..5fb731910e 100644 --- a/sources/Controller/PreferencesController.php +++ b/sources/Controller/PreferencesController.php @@ -7,6 +7,7 @@ namespace Combodo\iTop\Controller; use appUserPreferences; +use CoreUnexpectedValue; use Exception; use MetaModel; use ormDocument; @@ -38,9 +39,14 @@ class PreferencesController appUserPreferences::SetPref('user_picture_placeholder', $sImageFilename); $sUserPicturesFolder = 'images/user-pictures/'; - $sImageAbsPath = APPROOT.$sUserPicturesFolder.$sImageFilename; + $sImageAbsPath = utils::RealPath(APPROOT.$sUserPicturesFolder.$sImageFilename, APPROOT.$sUserPicturesFolder); $sImageAbsUrl = utils::GetAbsoluteUrlAppRoot().$sUserPicturesFolder.$sImageFilename; - + + // Check if we're still in the right folder + if($sImageAbsPath === false){ + throw new CoreUnexpectedValue('Error while updating user image, invalid image path "'.$sUserPicturesFolder.$sImageFilename.'"'); + } + // Check file can be read $sImageData = file_get_contents($sImageAbsPath); if (false === $sImageData) { diff --git a/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php b/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php new file mode 100644 index 0000000000..805686103c --- /dev/null +++ b/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php @@ -0,0 +1,5 @@ +oOauthClient = $oOauthClient; + } + + /** + * @return \League\OAuth2\Client\Provider\GenericProvider + */ + public function GetVendorProvider() + { + return $this->oVendorProvider; + } + + /** + * @param \League\OAuth2\Client\Provider\GenericProvider $oVendorProvider + */ + public function SetVendorProvider(GenericProvider $oVendorProvider) + { + $this->oVendorProvider = $oVendorProvider; + } + + /** + * @return \League\OAuth2\Client\Token\AccessToken + */ + public function GetAccessToken(): AccessToken + { + return $this->oOauthClient->GetAccessToken(); + } + + /** + * @param \League\OAuth2\Client\Token\AccessToken $oAccessToken + */ + public function SetAccessToken(AccessToken $oAccessToken) + { + $this->oOauthClient->SetAccessToken($oAccessToken); + } + + /** + * @return mixed + */ + public function GetScope() + { + return $this->oOauthClient->GetScope(); + } + + /** + * @return string + */ + public static function GetVendorName() + { + return self::$sVendorName; + } + +} \ No newline at end of file diff --git a/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php new file mode 100644 index 0000000000..667d5875a6 --- /dev/null +++ b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php @@ -0,0 +1,27 @@ + 'consent', + 'scope' => 'offline_access', + 'defaultEndPointVersion' => Azure::ENDPOINT_VERSION_2_0, + 'clientId' => $oOAuthClient->Get('client_id'), + 'clientSecret' => $oOAuthClient->Get('client_secret'), + 'redirectUri' => $oOAuthClient->Get('redirect_url'), + ]; + + $this->oVendorProvider = new Azure($aOptions, $collaborators); + } +} \ No newline at end of file diff --git a/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php new file mode 100644 index 0000000000..66230eb3e9 --- /dev/null +++ b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php @@ -0,0 +1,127 @@ +Get('email_transport_smtp.username'); + $oSet = new DBObjectSet(DBSearch::FromOQL("SELECT OAuthClient WHERE name=:username", ['username' => $sUsername])); + if ($oSet->Count() < 1) { + throw new CoreException(Dict::Format('itop-oauth-client:MissingOAuthClient', $sUsername)); + } + while ($oOAuthClient = $oSet->Fetch()) { + if ($oOAuthClient->Get('used_for_smtp') == 'yes') { + return $oOAuthClient; + } + } + throw new CoreException(Dict::Format('itop-oauth-client:MissingOAuthClient', $sUsername)); + } + + /** + * @param \OAuthClient $oOAuthClient + * + * @return mixed + * @throws \ArchivedObjectException + * @throws \CoreException + */ + public static function GetAuthorizationUrl(OAuthClient $oOAuthClient) + { + $oProvider = self::GetClientProvider($oOAuthClient); + return $oProvider->GetVendorProvider()->getAuthorizationUrl([ + 'scope' => [ + $oProvider->GetScope(), + ], + ]); + } + + /** + * @param \OAuthClient $oOAuthClient + * @param $sCode + * + * @return AccessTokenInterface + * @throws \ArchivedObjectException + * @throws \CoreException + */ + public static function GetAccessTokenFromCode(OAuthClient $oOAuthClient, $sCode) + { + $oProvider = self::GetClientProvider($oOAuthClient); + return $oProvider->GetVendorProvider()->getAccessToken('authorization_code', ['code' => $sCode, 'scope' => $oProvider->GetScope()]); + } + + /** + * @param $sProviderVendor + * + * @return string + * @throws \CoreException + */ + protected static function GetProviderClass($sProviderVendor): string + { + $sProviderClass = "\Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProvider".$sProviderVendor; + if (!class_exists($sProviderClass)) { + throw new CoreException(Dict::Format('UI:Error:SMTP:UnknownVendor', $sProviderVendor)); + } + + return $sProviderClass; + } + + /** + * @return string + * @throws \Exception + */ + public static function GetRedirectUri(): string + { + return utils::GetAbsoluteUrlAppRoot().'pages/oauth.landing.php'; + } + + /** + * @param \DBObject $oOAuthClient + * + * @return mixed + * @throws \ArchivedObjectException + * @throws \CoreException + */ + public static function GetClientProvider(DBObject $oOAuthClient) + { + $sProviderVendor = $oOAuthClient->Get('provider'); + $sProviderClass = self::GetProviderClass($sProviderVendor); + $aCollaborators = [ + 'httpClient' => new Client(['verify' => false]), + ]; + + return new $sProviderClass($oOAuthClient, $aCollaborators); + } + +} \ No newline at end of file diff --git a/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php new file mode 100644 index 0000000000..838a8783e7 --- /dev/null +++ b/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php @@ -0,0 +1,27 @@ + 'consent', + 'accessType' => 'offline', + 'clientId' => $oOAuthClient->Get('client_id'), + 'clientSecret' => $oOAuthClient->Get('client_secret'), + 'redirectUri' => $oOAuthClient->Get('redirect_url'), + 'scope' => $oOAuthClient->GetScope(), + + ]; + $this->oVendorProvider = new Google($aOptions, $collaborators); + + } +} \ No newline at end of file diff --git a/sources/Core/Authentication/Client/Smtp/SmtpOAuthLogin.php b/sources/Core/Authentication/Client/Smtp/SmtpOAuthLogin.php new file mode 100644 index 0000000000..d9e185e34c --- /dev/null +++ b/sources/Core/Authentication/Client/Smtp/SmtpOAuthLogin.php @@ -0,0 +1,123 @@ +setUsername($config['username']); + } + } + + // Call parent with original arguments + parent::__construct($host, $port, $origConfig); + } + + /** + * @param OAuthClientProviderAbstract $oProvider + * + * @return void + */ + public static function setProvider(OAuthClientProviderAbstract $oProvider) + { + self::$oProvider = $oProvider; + } + + /** + * Perform LOGIN authentication with supplied credentials + * + */ + public function auth() + { + try { + if (empty(self::$oProvider->GetAccessToken())) { + throw new IdentityProviderException('Not prior authentication to OAuth', 255, []); + } elseif (self::$oProvider->GetAccessToken()->hasExpired()) { + self::$oProvider->SetAccessToken(self::$oProvider->GetVendorProvider()->getAccessToken('refresh_token', [ + 'refresh_token' => self::$oProvider->GetAccessToken()->getRefreshToken(), + 'scope' => self::$oProvider->GetScope(), + ])); + } + } + catch (IdentityProviderException $e) { + IssueLog::Error('Failed to get oAuth credentials for outgoing mails for provider '.self::$oProvider::GetVendorName().' '.$e->getMessage(), static::LOG_CHANNEL); + + return false; + } + $sAccessToken = self::$oProvider->GetAccessToken()->getToken(); + + if (empty($sAccessToken)) { + IssueLog::Error('No OAuth token for outgoing mails for provider '.self::$oProvider::GetVendorName(), static::LOG_CHANNEL); + + return false; + } + + $this->_send('AUTH XOAUTH2 '.base64_encode("user=$this->username\1auth=Bearer $sAccessToken\1\1")); + IssueLog::Debug("SMTP Oauth sending AUTH XOAUTH2 user=$this->username auth=Bearer $sAccessToken", static::LOG_CHANNEL); + + try { + while (true) { + $sResponse = $this->_receive(30); + + IssueLog::Debug("SMTP Oauth receiving ".trim($sResponse), static::LOG_CHANNEL); + + if ($sResponse === '+') { + // Send empty client response. + $this->_send(''); + } else { + if (preg_match('/Unauthorized/i', $sResponse) || + preg_match('/Rejected/i', $sResponse) || + preg_match('/^(535|432|454|534|500|530|538|334)/', $sResponse)) { + IssueLog::Error('Unable to authenticate for outgoing mails for provider '.self::$oProvider::GetVendorName()." Error: $sResponse", static::LOG_CHANNEL); + + return false; + } + if (preg_match("/OK /i", $sResponse) || + preg_match('/Accepted/i', $sResponse) || + preg_match('/^235/i', $sResponse)) { + $this->auth = true; + + return true; + } + + } + } + } catch (RuntimeException $e) { + IssueLog::Error('Timeout connection for outgoing mails for provider '.self::$oProvider::GetVendorName(), static::LOG_CHANNEL); + } + return false; + } +} diff --git a/sources/Core/Email/EmailFactory.php b/sources/Core/Email/EmailFactory.php new file mode 100644 index 0000000000..2db059269f --- /dev/null +++ b/sources/Core/Email/EmailFactory.php @@ -0,0 +1,13 @@ + + + +/** + * Send an email (abstraction for synchronous/asynchronous modes) + * + * @copyright Copyright (C) 2010-2021 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ +@include APPROOT."/core/oauth.php"; + +use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory; +use Laminas\Mail\Header\ContentType; +use Laminas\Mail\Message; +use Laminas\Mail\Protocol\Smtp\Auth\Oauth; +use Laminas\Mail\Transport\File; +use Laminas\Mail\Transport\FileOptions; +use Laminas\Mail\Transport\Sendmail; +use Laminas\Mail\Transport\Smtp; +use Laminas\Mail\Transport\SmtpOptions; +use Laminas\Mime\Mime; +use Laminas\Mime\Part; +use Pelago\Emogrifier\CssInliner; +use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter; +use Pelago\Emogrifier\HtmlProcessor\HtmlPruner; + +class EMailLaminas extends Email +{ + // Serialization formats + const ORIGINAL_FORMAT = 1; // Original format, consisting in serializing the whole object, inculding the Swift Mailer's object. + // Did not work with attachements since their binary representation cannot be stored as a valid UTF-8 string + const FORMAT_V2 = 2; // New format, only the raw data are serialized (base64 encoded if needed) + + protected $m_aData; // For storing data to serialize + + protected $m_oMessage; + + /** + * @noinspection PhpMissingParentConstructorInspection + * @noinspection MagicMethodsValidityInspection + */ + public function __construct() + { + $this->m_aData = array(); + $this->m_oMessage = new Message(); + $this->m_oMessage->setEncoding('UTF-8'); + + $this->InitRecipientFrom(); + } + + /** + * Custom serialization method + * No longer use the brute force "serialize" method since + * 1) It does not work with binary attachments (since they cannot be stored in a UTF-8 text field) + * 2) The size tends to be quite big (sometimes ten times the size of the email) + */ + public function SerializeV2() + { + return serialize($this->m_aData); + } + + /** + * Custom de-serialization method + * + * @param string $sSerializedMessage The serialized representation of the message + * + * @return \EMail + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \Symfony\Component\CssSelector\Exception\SyntaxErrorException + */ + public static function UnSerializeV2($sSerializedMessage) + { + $aData = unserialize($sSerializedMessage); + $oMessage = new Email(); + + if (array_key_exists('body', $aData)) { + $oMessage->SetBody($aData['body']['body'], $aData['body']['mimeType']); + } + if (array_key_exists('message_id', $aData)) { + $oMessage->SetMessageId($aData['message_id']); + } + if (array_key_exists('bcc', $aData)) { + $oMessage->SetRecipientBCC($aData['bcc']); + } + if (array_key_exists('cc', $aData)) { + $oMessage->SetRecipientCC($aData['cc']); + } + if (array_key_exists('from', $aData)) { + $oMessage->SetRecipientFrom($aData['from']['address'], $aData['from']['label']); + } + if (array_key_exists('reply_to', $aData)) { + $oMessage->SetRecipientReplyTo($aData['reply_to']['address'], $aData['reply_to']['label']); + } + if (array_key_exists('to', $aData)) { + $oMessage->SetRecipientTO($aData['to']); + } + if (array_key_exists('subject', $aData)) { + $oMessage->SetSubject($aData['subject']); + } + + if (array_key_exists('headers', $aData)) { + foreach ($aData['headers'] as $sKey => $sValue) { + $oMessage->AddToHeader($sKey, $sValue); + } + } + if (array_key_exists('parts', $aData)) { + foreach ($aData['parts'] as $aPart) { + $oMessage->AddPart($aPart['text'], $aPart['mimeType']); + } + } + if (array_key_exists('attachments', $aData)) { + foreach ($aData['attachments'] as $aAttachment) { + $oMessage->AddAttachment(base64_decode($aAttachment['data']), $aAttachment['filename'], $aAttachment['mimeType']); + } + } + + return $oMessage; + } + + protected function SendAsynchronous(&$aIssues, $oLog = null) + { + try { + AsyncSendEmail::AddToQueue($this, $oLog); + } + catch (Exception $e) { + $aIssues = array($e->GetMessage()); + + return EMAIL_SEND_ERROR; + } + $aIssues = array(); + + return EMAIL_SEND_PENDING; + } + + public static function GetMailer() + { + return new EMailLaminas(); + } + + /** + * @throws \Exception + */ + protected function SendSynchronous(&$aIssues, $oLog = null) + { + + $this->LoadConfig(); + + $sTransport = self::$m_oConfig->Get('email_transport'); + switch ($sTransport) { + case 'SMTP': + $sHost = self::$m_oConfig->Get('email_transport_smtp.host'); + $sPort = self::$m_oConfig->Get('email_transport_smtp.port'); + $sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption'); + $sUserName = self::$m_oConfig->Get('email_transport_smtp.username'); + $sPassword = self::$m_oConfig->Get('email_transport_smtp.password'); + + $oTransport = new Smtp(); + $aOptions = [ + 'host' => $sHost, + 'port' => $sPort, + 'connection_class' => 'login', + 'connection_config' => [ + 'ssl' => $sEncryption, + ], + ]; + if (strlen($sUserName) > 0) { + $aOptions['connection_config']['username'] = $sUserName; + $aOptions['connection_config']['password'] = $sPassword; + } + $oOptions = new SmtpOptions($aOptions); + $oTransport->setOptions($oOptions); + break; + + case 'SMTP_OAuth': + $sHost = self::$m_oConfig->Get('email_transport_smtp.host'); + $sPort = self::$m_oConfig->Get('email_transport_smtp.port'); + $sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption'); + $sUserName = self::$m_oConfig->Get('email_transport_smtp.username'); + + $oTransport = new Smtp(); + $aOptions = [ + 'host' => $sHost, + 'port' => $sPort, + 'connection_class' => 'Laminas\Mail\Protocol\Smtp\Auth\Oauth', + 'connection_config' => [ + 'ssl' => $sEncryption, + ], + ]; + if (strlen($sUserName) > 0) { + $aOptions['connection_config']['username'] = $sUserName; + } + $oOptions = new SmtpOptions($aOptions); + $oTransport->setOptions($oOptions); + + Oauth::setProvider(OAuthClientProviderFactory::GetProviderForSMTP()); + break; + + case 'Null': + $oTransport = new Smtp(); + break; + + case 'LogFile': + $oTransport = new File(); + $aOptions = new FileOptions([ + 'path' => APPROOT.'log/mail.log', + ]); + $oTransport->setOptions($aOptions); + break; + + case 'PHPMail': + default: + $oTransport = new Sendmail(); + } + + $oKPI = new ExecutionKPI(); + try { + $oTransport->send($this->m_oMessage); + $aIssues = array(); + $oKPI->ComputeStats('Email Sent', 'Succeded'); + + return EMAIL_SEND_OK; + } + catch (Laminas\Mail\Transport\Exception\RuntimeException $e) { + IssueLog::Warning('Email sending failed: '.$e->getMessage()); + $aIssues = array($e->getMessage()); + $oKPI->ComputeStats('Email Sent', 'Error received'); + + return EMAIL_SEND_ERROR; + } + catch (Exception $e) { + $oKPI->ComputeStats('Email Sent', 'Error received'); + throw $e; + } + } + + /** + * Reprocess the body of the message (if it is an HTML message) + * to replace the URL of images based on attachments by a link + * to an embedded image (i.e. cid:....) and returns images to be attached as an array + * + * @param string $sBody Email body to process/alter + * + * @return array Array of Part that needs to be added as inline attachment later to render as embed + * @throws \ArchivedObjectException + * @throws \CoreException + */ + protected function EmbedInlineImages(string &$sBody) + { + $oDOMDoc = new DOMDocument(); + $oDOMDoc->preserveWhitespace = true; + @$oDOMDoc->loadHTML(''.$sBody); // For loading HTML chunks where the character set is not specified + + $oXPath = new DOMXPath($oDOMDoc); + $sXPath = '//img[@'.InlineImage::DOM_ATTR_ID.']'; + $oImagesList = $oXPath->query($sXPath); + $oImagesContent = new \Laminas\Mime\Message(); + $aImagesParts = []; + if ($oImagesList->length != 0) { + foreach ($oImagesList as $oImg) { + $iAttId = $oImg->getAttribute(InlineImage::DOM_ATTR_ID); + $oAttachment = MetaModel::GetObject('InlineImage', $iAttId, false, true /* Allow All Data */); + if ($oAttachment) { + $sImageSecret = $oImg->getAttribute('data-img-secret'); + $sAttachmentSecret = $oAttachment->Get('secret'); + if ($sImageSecret !== $sAttachmentSecret) { + // @see N°1921 + // If copying from another iTop we could get an IMG pointing to an InlineImage with wrong secret + continue; + } + + $oDoc = $oAttachment->Get('contents'); + + $sCid = uniqid('', true); + + $oNewAttachment = new Part($oDoc->GetData()); + $oNewAttachment->id = $sCid; + $oNewAttachment->type = $oDoc->GetMimeType(); + $oNewAttachment->filename = $oDoc->GetFileName(); + $oNewAttachment->disposition = Mime::DISPOSITION_INLINE; + $oNewAttachment->encoding = Mime::ENCODING_BASE64; + + $oImagesContent->addPart($oNewAttachment); + $oImg->setAttribute('src', 'cid:'.$sCid); + $aImagesParts[] = $oNewAttachment; + } + } + } + $sBody = $oDOMDoc->saveHTML(); + + return $aImagesParts; + } + + public function Send(&$aIssues, $bForceSynchronous = false, $oLog = null) + { + //select a default sender if none is provided. + if (empty($this->m_aData['from']['address']) && !empty($this->m_aData['to'])) { + $this->SetRecipientFrom($this->m_aData['to']); + } + + if ($bForceSynchronous) { + return $this->SendSynchronous($aIssues, $oLog); + } else { + $bConfigASYNC = MetaModel::GetConfig()->Get('email_asynchronous'); + if ($bConfigASYNC) { + return $this->SendAsynchronous($aIssues, $oLog); + } else { + return $this->SendSynchronous($aIssues, $oLog); + } + } + } + + public function AddToHeader($sKey, $sValue) + { + if (!array_key_exists('headers', $this->m_aData)) { + $this->m_aData['headers'] = array(); + } + $this->m_aData['headers'][$sKey] = $sValue; + + if (strlen($sValue) > 0) { + $oHeaders = $this->m_oMessage->getHeaders(); + $oHeaders->addHeaderLine($sKey, $sValue); + } + } + + public function SetMessageId($sId) + { + $this->m_aData['message_id'] = $sId; + + // Note: Swift will add the angle brackets for you + // so let's remove the angle brackets if present, for historical reasons + $sId = str_replace(array('<', '>'), '', $sId); + + $this->m_oMessage->getHeaders()->addHeaderLine('Message-ID', $sId); + } + + public function SetReferences($sReferences) + { + $this->AddToHeader('References', $sReferences); + } + + /** + * Set the "In-Reply-To" header to allow emails to group as a conversation in modern mail clients (GMail, Outlook 2016+, ...) + * + * @link https://en.wikipedia.org/wiki/Email#Header_fields + * + * @param string $sMessageId + * + * @since 3.0.1 N°4849 + */ + public function SetInReplyTo(string $sMessageId) + { + $this->AddToHeader('In-Reply-To', $sMessageId); + } + + /** + * Set current Email body and process inline images. + * + * @param $sBody + * @param string $sMimeType + * @param $sCustomStyles + * + * @return void + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \Symfony\Component\CssSelector\Exception\SyntaxErrorException + */ + public function SetBody($sBody, $sMimeType = Mime::TYPE_HTML, $sCustomStyles = null) + { + $oBody = new Laminas\Mime\Message(); + $aAdditionalParts = []; + + if (($sMimeType === Mime::TYPE_HTML) && ($sCustomStyles !== null)) { + $oDomDocument = CssInliner::fromHtml($sBody)->inlineCss($sCustomStyles)->getDomDocument(); + HtmlPruner::fromDomDocument($oDomDocument)->removeElementsWithDisplayNone(); + $sBody = CssToAttributeConverter::fromDomDocument($oDomDocument)->convertCssToVisualAttributes()->render(); // Adds html/body tags if not already present + } + $this->m_aData['body'] = array('body' => $sBody, 'mimeType' => $sMimeType); + + // We don't want these modifications in m_aData['body'], otherwise it'll ruin asynchronous mail as they go through this method twice + if ($sMimeType === Mime::TYPE_HTML) { + $aAdditionalParts = $this->EmbedInlineImages($sBody); + } + + // Add body content to as a new part + $oNewPart = new Part($sBody); + $oNewPart->encoding = Mime::ENCODING_8BIT; + $oNewPart->type = $sMimeType; + $oBody->addPart($oNewPart); + + // Add additional images as new body parts + foreach ($aAdditionalParts as $oAdditionalPart) { + $oBody->addPart($oAdditionalPart); + } + + if ($oBody->isMultiPart()) { + $oContentTypeHeader = $this->m_oMessage->getHeaders(); + foreach ($oContentTypeHeader as $oHeader) { + if (!$oHeader instanceof ContentType) { + continue; + } + + $oHeader->setType(Mime::MULTIPART_MIXED); + $oHeader->addParameter('boundary', $oBody->getMime()->boundary()); + break; + } + } + + $this->m_oMessage->setBody($oBody); + } + + /** + * Add a new part to the existing body + * + * @param $sText + * @param string $sMimeType + * + * @return void + */ + public function AddPart($sText, $sMimeType = Mime::TYPE_HTML) + { + if (!array_key_exists('parts', $this->m_aData)) { + $this->m_aData['parts'] = array(); + } + $this->m_aData['parts'][] = array('text' => $sText, 'mimeType' => $sMimeType); + $oNewPart = new Part($sText); + $oNewPart->encoding = Mime::ENCODING_8BIT; + $oNewPart->type = $sMimeType; + $this->m_oMessage->getBody()->addPart($oNewPart); + } + + public function AddAttachment($data, $sFileName, $sMimeType) + { + $oBody = $this->m_oMessage->getBody(); + + if (!$oBody->isMultiPart()) { + $multipart_content = new Part($oBody->generateMessage()); + $multipart_content->setType($oBody->getParts()[0]->getType()); + $multipart_content->setBoundary($oBody->getMime()->boundary()); + + $oBody = new Laminas\Mime\Message(); + $oBody->addPart($multipart_content); + } + + if (!array_key_exists('attachments', $this->m_aData)) { + $this->m_aData['attachments'] = array(); + } + $this->m_aData['attachments'][] = array('data' => base64_encode($data), 'filename' => $sFileName, 'mimeType' => $sMimeType); + $oNewAttachment = new Part($data); + $oNewAttachment->type = $sMimeType; + $oNewAttachment->filename = $sFileName; + $oNewAttachment->disposition = Mime::DISPOSITION_ATTACHMENT; + $oNewAttachment->encoding = Mime::ENCODING_BASE64; + + + $oBody->addPart($oNewAttachment); + + if ($oBody->isMultiPart()) { + $oContentTypeHeader = $this->m_oMessage->getHeaders(); + foreach ($oContentTypeHeader as $oHeader) { + if (!$oHeader instanceof ContentType) { + continue; + } + + $oHeader->setType(Mime::MULTIPART_MIXED); + $oHeader->addParameter('boundary', $oBody->getMime()->boundary()); + break; + } + } + + $this->m_oMessage->setBody($oBody); + } + + public function SetSubject($sSubject) + { + $this->m_aData['subject'] = $sSubject; + $this->m_oMessage->setSubject($sSubject); + } + + public function GetSubject() + { + return $this->m_oMessage->getSubject(); + } + + /** + * Helper to transform and sanitize addresses + * - get rid of empty addresses + */ + protected function AddressStringToArray($sAddressCSVList) + { + $aAddresses = array(); + foreach (explode(',', $sAddressCSVList) as $sAddress) { + $sAddress = trim($sAddress); + if (strlen($sAddress) > 0) { + $aAddresses[] = $sAddress; + } + } + + return $aAddresses; + } + + public function SetRecipientTO($sAddress) + { + $this->m_aData['to'] = $sAddress; + if (!empty($sAddress)) { + $aAddresses = $this->AddressStringToArray($sAddress); + $this->m_oMessage->setTo($aAddresses); + } + } + + public function GetRecipientTO($bAsString = false) + { + $aRes = $this->m_oMessage->getTo(); + if ($aRes === null || $aRes->count() === 0) { + // There is no "To" header field + $aRes = array(); + } + if ($bAsString) { + $aStrings = array(); + foreach ($aRes as $oEmail) { + $sName = $oEmail->getName(); + $sEmail = $oEmail->getEmail(); + if (is_null($sName)) { + $aStrings[] = $sEmail; + } else { + $sName = str_replace(array('<', '>'), '', $sName); + $aStrings[] = "$sName <$sEmail>"; + } + } + + return implode(', ', $aStrings); + } else { + return $aRes; + } + } + + public function SetRecipientCC($sAddress) + { + $this->m_aData['cc'] = $sAddress; + if (!empty($sAddress)) { + $aAddresses = $this->AddressStringToArray($sAddress); + $this->m_oMessage->setCc($aAddresses); + } + } + + public function SetRecipientBCC($sAddress) + { + $this->m_aData['bcc'] = $sAddress; + if (!empty($sAddress)) { + $aAddresses = $this->AddressStringToArray($sAddress); + $this->m_oMessage->setBcc($aAddresses); + } + } + + public function SetRecipientFrom($sAddress, $sLabel = '') + { + $this->m_aData['from'] = array('address' => $sAddress, 'label' => $sLabel); + if ($sLabel != '') { + $this->m_oMessage->setFrom(array($sAddress => $sLabel)); + } else if (!empty($sAddress)) { + $this->m_oMessage->setFrom($sAddress); + } + } + + public function SetRecipientReplyTo($sAddress, $sLabel = '') + { + $this->m_aData['reply_to'] = array('address' => $sAddress, 'label' => $sLabel); + if ($sLabel != '') { + $this->m_oMessage->setReplyTo(array($sAddress => $sLabel)); + } else if (!empty($sAddress)) { + $this->m_oMessage->setReplyTo($sAddress); + } + } + +} diff --git a/sources/Core/Email/iEMail.php b/sources/Core/Email/iEMail.php new file mode 100644 index 0000000000..55ae758ba1 --- /dev/null +++ b/sources/Core/Email/iEMail.php @@ -0,0 +1,51 @@ +span.attachments-count"), - iCountCurrentValue = parseInt(countContainer.text()); + iCountCurrentValue = parseInt(countContainer.text()); countContainer.text(iCountCurrentValue+iIncrement); } @@ -233,7 +233,6 @@ JS var \$oAttachmentTBody = $(this).closest('.fileupload_field_content').find('.attachments_container table#$sAttachmentTableId>tbody'), iAttId = data.result.att_id, sDownloadLink = '{$this->oField->GetDownloadEndpoint()}'.replace(/-sAttachmentId-/, iAttId), - sIconClass = (data.result.preview == 'true') ? 'trigger-preview' : '', sAttachmentMeta = ''; // hide "no attachment" line if present @@ -247,23 +246,24 @@ JS {search: "{{iAttId}}", replace:iAttId }, {search: "{{lineStyle}}", replace:'' }, {search: "{{sDocDownloadUrl}}", replace:sDownloadLink }, - {search: "{{sIconClass}}", replace:sIconClass }, {search: "{{sAttachmentThumbUrl}}", replace:data.result.icon }, {search: "{{sFileName}}", replace: data.result.msg }, {search: "{{sAttachmentMeta}}", replace:sAttachmentMeta }, {search: "{{sFileSize}}", replace:data.result.file_size }, {search: "{{sAttachmentDate}}", replace:data.result.creation_date }, ]; - var sAttachmentRow = attachmentRowTemplate; + var sAttachmentRow = attachmentRowTemplate ; $.each(replaces, function(indexInArray, value ) { var re = new RegExp(value.search, 'gi'); sAttachmentRow = sAttachmentRow.replace(re, value.replace); }); - \$oAttachmentTBody.append(sAttachmentRow); - // Preview tooltip - if(data.result.preview){ + var oElem = $(sAttachmentRow); + if(!data.result.preview){ + oElem.find('[data-tooltip-html-enabled="true"]').removeAttr('data-tooltip-content'); + oElem.find('[data-tooltip-html-enabled="true"]').removeAttr('data-tooltip-html-enabled'); } + \$oAttachmentTBody.append(oElem); // Remove button handler $('#display_attachment_'+data.result.att_id+' :button').on('click', function(oEvent){ oEvent.preventDefault(); @@ -303,8 +303,6 @@ JS } }); - $('table#$sAttachmentTableId>tbody>tr>td a.trigger-preview').each(function(iIndex, oElem){ - }); // Remove button handler $('.attachments_container table#$sAttachmentTableId>tbody>tr>td :button').on('click', function(oEvent){ oEvent.preventDefault(); @@ -399,15 +397,14 @@ HTML /** @var \ormDocument $oDoc */ $oDoc = $oAttachment->Get('contents'); - $sFileName = htmlentities($oDoc->GetFileName(), ENT_QUOTES, 'UTF-8'); + $sFileName = utils::EscapeHtml($oDoc->GetFileName()); $sDocDownloadUrl = str_replace('-sAttachmentId-', $iAttId, $this->oField->GetDownloadEndpoint()); $sAttachmentThumbUrl = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName); - $sIconClass = ''; - if ($oDoc->IsPreviewAvailable()) - { - $sIconClass = 'trigger-preview'; + $bHasPreview = false; + if ($oDoc->IsPreviewAvailable()) { + $bHasPreview = true; $iMaxSizeForPreview = MetaModel::GetModuleSetting('itop-attachments', 'icon_preview_max_size', AbstractAttachmentsRenderer::DEFAULT_MAX_SIZE_FOR_PREVIEW); if ($oDoc->GetSize() <= $iMaxSizeForPreview) { @@ -431,7 +428,7 @@ HTML $iAttId, $sLineStyle, $sDocDownloadUrl, - $sIconClass, + $bHasPreview, $sAttachmentThumbUrl, $sFileName, $sAttachmentMeta, @@ -482,7 +479,7 @@ HTML; * @param int $iAttId * @param string $sLineStyle * @param string $sDocDownloadUrl - * @param string $sIconClass + * @param bool $bHasPreview replace string $sIconClass since 3.0.1 * @param string $sAttachmentThumbUrl * @param string $sFileName * @param string $sAttachmentMeta @@ -496,7 +493,7 @@ HTML; * @since 2.7.0 */ protected static function GetAttachmentTableRow( - $iAttId, $sLineStyle, $sDocDownloadUrl, $sIconClass, $sAttachmentThumbUrl, $sFileName, $sAttachmentMeta, $sFileSize, + $iAttId, $sLineStyle, $sDocDownloadUrl, $bHasPreview, $sAttachmentThumbUrl, $sFileName, $sAttachmentMeta, $sFileSize, $iFileSizeRaw, $sAttachmentDate, $iAttachmentDateRaw, $bIsDeleteAllowed ) { $sDeleteCell = ''; @@ -505,16 +502,22 @@ HTML; $sDeleteBtnLabel = Dict::S('Portal:Button:Delete'); $sDeleteCell = ''; } + $sHtml = ""; - return << - - $sFileName$sAttachmentMeta + if($bHasPreview) { + $sHtml .= "\" data-tooltip-html-enabled=true>"; + } else { + $sHtml .= ""; + } + + $sHtml .= <<$sFileName$sAttachmentMeta $sFileSize $sAttachmentDate $sDeleteCell HTML; + return $sHtml; } /** diff --git a/sources/Renderer/Bootstrap/FieldRenderer/BsLinkedSetFieldRenderer.php b/sources/Renderer/Bootstrap/FieldRenderer/BsLinkedSetFieldRenderer.php index 450e4f5206..1d5ca6b228 100644 --- a/sources/Renderer/Bootstrap/FieldRenderer/BsLinkedSetFieldRenderer.php +++ b/sources/Renderer/Bootstrap/FieldRenderer/BsLinkedSetFieldRenderer.php @@ -54,7 +54,7 @@ class BsLinkedSetFieldRenderer extends BsFieldRenderer $aItemIds = array(); $this->PrepareItems($aItems, $aItemIds); $sItemsAsJson = json_encode($aItems); - $sItemIdsAsJson = htmlentities(json_encode(array('current' => $aItemIds)), ENT_QUOTES, 'UTF-8'); + $sItemIdsAsJson = utils::EscapeHtml(json_encode(array('current' => $aItemIds))); if (!$this->oField->GetHidden()) { @@ -115,14 +115,14 @@ EOF // Rendering table widget // - Vars - $sEmptyTableLabel = htmlentities(Dict::S(($this->oField->GetReadOnly()) ? 'Portal:Datatables:Language:EmptyTable' : 'UI:Message:EmptyList:UseAdd'), ENT_QUOTES, 'UTF-8'); - $sLabelGeneralCheckbox = htmlentities(Dict::S('Core:BulkExport:CheckAll') . ' / ' . Dict::S('Core:BulkExport:UncheckAll'), ENT_QUOTES, 'UTF-8'); + $sEmptyTableLabel = utils::EscapeHtml(Dict::S(($this->oField->GetReadOnly()) ? 'Portal:Datatables:Language:EmptyTable' : 'UI:Message:EmptyList:UseAdd')); + $sLabelGeneralCheckbox = utils::EscapeHtml(Dict::S('Core:BulkExport:CheckAll').' / '.Dict::S('Core:BulkExport:UncheckAll')); $sSelectionOptionHtml = ($this->oField->GetReadOnly()) ? 'false' : '{"style": "multi"}'; - $sSelectionInputGlobalHtml = ($this->oField->GetReadOnly()) ? '' : ''; - $sSelectionInputHtml = ($this->oField->GetReadOnly()) ? '' : ''; + $sSelectionInputGlobalHtml = ($this->oField->GetReadOnly()) ? '' : ''; + $sSelectionInputHtml = ($this->oField->GetReadOnly()) ? '' : ''; // - Output $oOutput->AddJs( -<<AddCssFile($sFile); } + $oOutput->AddJs( + <<oField->GetGlobalId()}").off("change").on("change", function(){ + var me = this; + + $(this).closest(".field_set").trigger("field_change", { + id: $(me).attr("id"), + name: $(me).closest(".form_field").attr("data-field-id"), + value: $(me).val() + }) + .closest('.form_handler').trigger('value_change'); + }); +EOF + ); } elseif($this->oField->GetControlType() == SelectObjectField::CONTROL_RADIO_VERTICAL) { @@ -226,9 +240,8 @@ EOF JS ); } - - } + $oOutput->AddHtml((BlockRenderer::RenderBlockTemplates($oBlock))); // JS Form field widget construct $aValidators = array(); diff --git a/sources/Renderer/Console/FieldRenderer/ConsoleSimpleFieldRenderer.php b/sources/Renderer/Console/FieldRenderer/ConsoleSimpleFieldRenderer.php index a4f5dad988..a8469a20cd 100644 --- a/sources/Renderer/Console/FieldRenderer/ConsoleSimpleFieldRenderer.php +++ b/sources/Renderer/Console/FieldRenderer/ConsoleSimpleFieldRenderer.php @@ -25,6 +25,7 @@ use AttributeDuration; use Combodo\iTop\Application\Helper\WebResourcesHelper; use Combodo\iTop\Application\UI\Base\Component\Field\FieldUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Html\Html; +use Combodo\iTop\Application\UI\Base\Component\Html\HtmlFactory; use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectOptionUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Input\SelectUIBlockFactory; @@ -53,7 +54,7 @@ class ConsoleSimpleFieldRenderer extends FieldRenderer if ($sFieldClass == 'Combodo\\iTop\\Form\\Field\\HiddenField') { - $oOutput->AddHtml(''); + $oOutput->AddHtml(''); } else { @@ -73,11 +74,10 @@ class ConsoleSimpleFieldRenderer extends FieldRenderer $oValue->AddSubBlock(InputUIBlockFactory::MakeForHidden("",$this->oField->GetCurrentValue(),$this->oField->GetGlobalId())); $oValue->AddSubBlock(new Html($this->oField->GetCurrentValue())); } - else - { - $oField = UIContentBlockUIBlockFactory::MakeStandard("",["field_input_zone", "field_input_datetime", "ibo-input-field-wrapper", "ibo-input-datetime-wrapper"]); + else { + $oField = UIContentBlockUIBlockFactory::MakeStandard("", ["field_input_zone", "field_input_datetime", "ibo-input-field-wrapper", "ibo-input-datetime-wrapper"]); $oValue->AddSubBlock($oField); - $oField->AddSubBlock(new Html('')); + $oField->AddSubBlock(new Html('')); $oField->AddSubBlock(new Html('')); } $oBlock->AddSubBlock($oValue); @@ -111,15 +111,17 @@ class ConsoleSimpleFieldRenderer extends FieldRenderer $bRichEditor = ($this->oField->GetFormat() === TextAreaField::ENUM_FORMAT_HTML); - $oText = new TextArea("",$this->oField->GetCurrentValue(),$this->oField->GetGlobalId(),40,8); - $oText->AddCSSClass('ibo-input-field-wrapper ibo-input'); - $oValue->AddSubBlock($oText); + if ($this->oField->GetReadOnly()) { - $oText->SetIsDisabled(true); + $oValue->AddSubBlock(UIContentBlockUIBlockFactory::MakeStandard())->AddSubBlock(HtmlFactory::MakeHtmlContent($this->oField->GetCurrentValue())); + $oValue->AddSubBlock(InputUIBlockFactory::MakeForHidden("",$this->oField->GetCurrentValue(), $this->oField->GetGlobalId())); } else { + $oText = new TextArea("",$this->oField->GetCurrentValue(),$this->oField->GetGlobalId(),40,8); + $oText->AddCSSClasses(['ibo-input-field-wrapper', 'ibo-input']); + $oValue->AddSubBlock($oText); // Some additional stuff if we are displaying it with a rich editor if ($bRichEditor) { diff --git a/synchro/synchrodatasource.class.inc.php b/synchro/synchrodatasource.class.inc.php index eb9d32b2c5..72de482b9d 100644 --- a/synchro/synchrodatasource.class.inc.php +++ b/synchro/synchrodatasource.class.inc.php @@ -611,8 +611,7 @@ EOF $aData['nb_obj_total'] = $iNew + $iExisting + $iDisappeared; $aData['nb_replica_total'] = $aData['nb_obj_total'] + $iIgnored; if (strlen($oLastLog->Get('traces')) > 0) { - $aData['traces'] = '
    Debug traces
    '.htmlentities($oLastLog->Get('traces'), ENT_QUOTES,
    -					'UTF-8').'
    '; + $aData['traces'] = '
    Debug traces
    '.utils::EscapeHtml($oLastLog->Get('traces')).'
    '; } else { $aData['traces'] = ''; } diff --git a/templates/application/links/indirect/block-indirect-links-edit/layout.js.twig b/templates/application/links/indirect/block-indirect-links-edit/layout.js.twig index 53328bd8c1..d94ed6e318 100644 --- a/templates/application/links/indirect/block-indirect-links-edit/layout.js.twig +++ b/templates/application/links/indirect/block-indirect-links-edit/layout.js.twig @@ -2,16 +2,17 @@ {# @license http://opensource.org/licenses/AGPL-3.0 #} {% apply spaceless %} oWidget{{ oUIBlock.iInputId }} = new LinksWidget( - '{{ oUIBlock.sLinkedSetId }}', - '{{ oUIBlock.sClass }}', - '{{ oUIBlock.sAttCode }}', - '{{ oUIBlock.iInputId }}', - '{{ oUIBlock.sNameSuffix }}', - {{ oUIBlock.bDuplicates }}, - {{ oUIBlock.oWizHelper }}, - '{{ oUIBlock.sExtKeyToRemote }}', - {{ oUIBlock.bJSDoSearch }}, - {{ oUIBlock.iMaxAddedId }} + '{{ oUIBlock.sLinkedSetId }}', + '{{ oUIBlock.sClass }}', + '{{ oUIBlock.sAttCode }}', + '{{ oUIBlock.iInputId }}', + '{{ oUIBlock.sNameSuffix }}', + {{ oUIBlock.bDuplicates }}, + {{ oUIBlock.oWizHelper }}, + '{{ oUIBlock.sExtKeyToRemote }}', + {{ oUIBlock.bJSDoSearch }}, + {{ oUIBlock.iMaxAddedId }}, + {{ oUIBlock.aRemoved | json_encode | raw }} ); oWidget{{ oUIBlock.iInputId }}.Init(); {% endapply %} \ No newline at end of file diff --git a/templates/base/components/datatable/layout.html.twig b/templates/base/components/datatable/layout.html.twig index 4db597a8dc..9b4550fda2 100644 --- a/templates/base/components/datatable/layout.html.twig +++ b/templates/base/components/datatable/layout.html.twig @@ -12,7 +12,7 @@ {% endif %} {% endif %} - +
    {% if oUIBlock.GetOption("select_mode") is not empty %} diff --git a/templates/base/components/datatable/layout.ready.js.twig b/templates/base/components/datatable/layout.ready.js.twig index 3b753c6c87..eb4d421f68 100644 --- a/templates/base/components/datatable/layout.ready.js.twig +++ b/templates/base/components/datatable/layout.ready.js.twig @@ -104,10 +104,14 @@ var oTable{{ sListIDForVarSuffix }} = $('#{{ oUIBlock.GetId() }}').DataTable({ } $(this).closest('.dataTables_wrapper').unblock(); + + // Disable hyperlinks if necessary + {% if oUIBlock.GetOption("disable_hyperlinks") is not same as false %} $("#{{ oUIBlock.GetId() }} a").on('click', function (e) { - //disable select action when there is a link - e.stopPropagation(); - }); + e.preventDefault(); + }); + {% endif %} + }, {% else %} drawCallback: function (settings) { @@ -125,6 +129,13 @@ var oTable{{ sListIDForVarSuffix }} = $('#{{ oUIBlock.GetId() }}').DataTable({ { $(this).closest('.dataTables_wrapper').find('.dataTables_paginate, .dataTables_info').show(); } + + // Disable hyperlinks if necessary + {% if oUIBlock.GetOption("disable_hyperlinks") is same as true %} + $("#{{ oUIBlock.GetId() }} a").on('click', function (e) { + e.preventDefault(); + }); + {% endif %} }, {% endif %} rowId: "id", @@ -339,6 +350,10 @@ var oTable{{ sListIDForVarSuffix }} = $('#{{ oUIBlock.GetId() }}').DataTable({ {% endfor %} {% endif %} {% endif %} + + // Set header and body datatables status as loaded + this.attr('data-status', 'loaded'); + this.closest('.dataTables_scroll').find('.dataTables_scrollHead .ibo-datatable').attr('data-status', 'loaded'); } }); diff --git a/templates/base/components/datatable/static/formtable/layout.html.twig b/templates/base/components/datatable/static/formtable/layout.html.twig index 9e23162176..8b8eb3327f 100644 --- a/templates/base/components/datatable/static/formtable/layout.html.twig +++ b/templates/base/components/datatable/static/formtable/layout.html.twig @@ -4,7 +4,7 @@ {% set columns = oUIBlock.GetColumns() %} -
    +
    {% for column in columns %} diff --git a/templates/base/components/datatable/static/formtable/layout.ready.js.twig b/templates/base/components/datatable/static/formtable/layout.ready.js.twig index 0560a9ba72..99d5c15bf0 100644 --- a/templates/base/components/datatable/static/formtable/layout.ready.js.twig +++ b/templates/base/components/datatable/static/formtable/layout.ready.js.twig @@ -37,6 +37,13 @@ var oTable{{ sListIDForVarSuffix }} = $('#{{ oUIBlock.GetId() }}').DataTable({ { $(this).closest('.dataTables_wrapper').find('.dataTables_paginate, .dataTables_info').show(); } + + // Disable hyperlinks if necessary + {% if oUIBlock.GetOption("disable_hyperlinks") is same as true or (oUIBlock.GetOption("select_mode") is not empty and oUIBlock.GetOption("disable_hyperlinks") is not same as false) %} + $("#{{ oUIBlock.GetId() }} a").on('click', function (e) { + e.preventDefault(); + }); + {% endif %} }, createdRow: function (row, data, dataIndex) { if (data['@class'] !== undefined) @@ -50,6 +57,10 @@ var oTable{{ sListIDForVarSuffix }} = $('#{{ oUIBlock.GetId() }}').DataTable({ { this.closest('.dataTables_wrapper').find('.dataTables_length').hide(); } + + // Set header and body datatables status as loaded + this.attr('data-status', 'loaded'); + this.closest('.dataTables_scroll').find('.dataTables_scrollHead .ibo-datatable').attr('data-status', 'loaded'); }, }); diff --git a/templates/base/components/datatable/static/layout.html.twig b/templates/base/components/datatable/static/layout.html.twig index c86274c768..c06a7b6b50 100644 --- a/templates/base/components/datatable/static/layout.html.twig +++ b/templates/base/components/datatable/static/layout.html.twig @@ -2,7 +2,7 @@ {# @license http://opensource.org/licenses/AGPL-3.0 #} {% set columns = oUIBlock.GetColumns() %} -
    +
    {% for column in columns %} diff --git a/templates/base/components/datatable/static/layout.ready.js.twig b/templates/base/components/datatable/static/layout.ready.js.twig index 30f94e0eee..dbe75ed5e3 100644 --- a/templates/base/components/datatable/static/layout.ready.js.twig +++ b/templates/base/components/datatable/static/layout.ready.js.twig @@ -73,12 +73,23 @@ var oTable{{ sListIDForVarSuffix }} = $('#{{ oUIBlock.GetId() }}').DataTable({ { $(this).closest('.dataTables_wrapper').find('.checkAll')[0].checked = true; } + + // Disable hyperlinks if necessary + {% if oUIBlock.GetOption("disable_hyperlinks") is same as true or (oUIBlock.GetOption("select_mode") is not empty and oUIBlock.GetOption("disable_hyperlinks") is not same as false) %} + $("#{{ oUIBlock.GetId() }} a").on('click', function (e) { + e.preventDefault(); + }); + {% endif %} }, initComplete: function () { if (this.api().page.info().pages < 2) { this.closest('.dataTables_wrapper').find('.dataTables_length').hide(); } + + // Set header and body datatables status as loaded + this.attr('data-status', 'loaded'); + this.closest('.dataTables_scroll').find('.dataTables_scrollHead .ibo-datatable').attr('data-status', 'loaded'); } }); diff --git a/templates/base/layouts/activity-panel/layout.html.twig b/templates/base/layouts/activity-panel/layout.html.twig index 66e927d3c6..bd547f8466 100644 --- a/templates/base/layouts/activity-panel/layout.html.twig +++ b/templates/base/layouts/activity-panel/layout.html.twig @@ -91,10 +91,10 @@
    {# Note: The "more entries" button is hidden by default to avoid a visual glitch. #} {# Otherwise when the page is loaded, the button is displayed even if the current tab only show log entries (which are all loaded) #} - + - +
    diff --git a/templates/pages/backoffice/oauth/Landing.html.twig b/templates/pages/backoffice/oauth/Landing.html.twig new file mode 100644 index 0000000000..578bce7b02 --- /dev/null +++ b/templates/pages/backoffice/oauth/Landing.html.twig @@ -0,0 +1,3 @@ +{# @copyright Copyright (C) 2010-2022 Combodo SARL #} +{# @license http://opensource.org/licenses/AGPL-3.0 #} + diff --git a/templates/pages/backoffice/oauth/Landing.ready.js.twig b/templates/pages/backoffice/oauth/Landing.ready.js.twig new file mode 100644 index 0000000000..4c8717fe70 --- /dev/null +++ b/templates/pages/backoffice/oauth/Landing.ready.js.twig @@ -0,0 +1,7 @@ +{# @copyright Copyright (C) 2010-2022 Combodo SARL #} +{# @license http://opensource.org/licenses/AGPL-3.0 #} + +window.addEventListener("message", function (event){ + event.source.postMessage(window.location.href, event.origin); + window.close(); +}, false); diff --git a/templates/pages/login/base.html.twig b/templates/pages/login/base.html.twig index ae3f390abf..a64172e198 100644 --- a/templates/pages/login/base.html.twig +++ b/templates/pages/login/base.html.twig @@ -3,9 +3,9 @@ {% block body %} - {% import "macros.twig" as Macro %}
    {% block login_header %} + {% import "macros.twig" as Macro %}
    {{ Macro.BlockExtension(aPluginFormData, 'login_header') }}
    @@ -21,12 +21,14 @@ {% block login_content %} {% endblock login_content %} {% block login_additional_content %} + {% import "macros.twig" as Macro %}
    {{ Macro.BlockExtension(aPluginFormData, 'login_additional_content') }}
    {% endblock login_additional_content %} {% endblock login_content_outer %} {% block login_footer %} + {% import "macros.twig" as Macro %} diff --git a/templates/pages/login/login.html.twig b/templates/pages/login/login.html.twig index 6e15cea1bd..c8ec9fcb60 100644 --- a/templates/pages/login/login.html.twig +++ b/templates/pages/login/login.html.twig @@ -4,7 +4,7 @@ {% extends "base.html.twig" %} {% block login_content %} - {% import "macros.twig" as Macro %} +
    {% block login_title %}
    @@ -22,31 +22,37 @@
    {% block login_sso_buttons %}
    + {% import "macros.twig" as Macro %} {{ Macro.BlockExtension(aPluginFormData, 'login_sso_buttons', '

    ' ~ 'UI:Login:SeparatorOr'|dict_s ~ '
    ') }}
    {% endblock login_sso_buttons %} {% block login_input %}
    + {% import "macros.twig" as Macro %} {{ Macro.BlockExtension(aPluginFormData, 'login_input') }}
    {% endblock login_input %} {% block login_additional_controls %}
    + {% import "macros.twig" as Macro %} {{ Macro.BlockExtension(aPluginFormData, 'login_additional_controls') }}
    {% endblock login_additional_controls %} {% block login_submit %}
    + {% import "macros.twig" as Macro %} {{ Macro.BlockExtension(aPluginFormData, 'login_submit') }}
    {% endblock login_submit %} {% block login_links %} {% endblock login_links %} {% block login_form_footer %} {% endblock login_form_footer %} @@ -64,6 +70,7 @@ {% block login_footer %} {% endblock login_footer %} diff --git a/templates/pages/login/logout.html.twig b/templates/pages/login/logout.html.twig index ba4f72b038..e1ce970364 100644 --- a/templates/pages/login/logout.html.twig +++ b/templates/pages/login/logout.html.twig @@ -11,3 +11,16 @@
    {% endblock %} + +{% block script %} + {{ parent() }} + + sessionStorage.clear(); +{% endblock %} + +{% block ready_script %} + {{ parent() }} + + // Redirect after 1s so the user has time to read the message + setTimeout(function() { window.location.href = '{{ sUrl }}'; }, 1000); +{% endblock %} \ No newline at end of file diff --git a/templates/pages/login/macros.twig b/templates/pages/login/macros.twig index c2aa0210c0..a116fe9447 100644 --- a/templates/pages/login/macros.twig +++ b/templates/pages/login/macros.twig @@ -3,12 +3,14 @@ {% macro BlockExtension(aPluginFormData, sBlockName, sText = '') %} {% set bHaveBlock = 'false' %} - {% for oLoginData in aPluginFormData if (oLoginData is defined and oLoginData.GetBlockExtension(sBlockName)) %} + {% for oLoginData in aPluginFormData %} + {% if (oLoginData is defined and oLoginData.GetBlockExtension(sBlockName)) %} {% set bHaveBlock = 'true' %} {% set oBlockExtension = oLoginData.GetBlockExtension(sBlockName) %} {% set sTwig = oBlockExtension.GetTwig() %} {% set aData = oBlockExtension.GetData() %} {% include sTwig ignore missing %} + {% endif %} {% endfor %} {% if bHaveBlock == 'true' %} {{ sText|raw }} diff --git a/test/GroupByAndFunctions.php b/test/GroupByAndFunctions.php index 0cc0454c5d..9f33b31570 100644 --- a/test/GroupByAndFunctions.php +++ b/test/GroupByAndFunctions.php @@ -19,7 +19,6 @@ require_once ('../approot.inc.php'); require_once(APPROOT.'application/application.inc.php'); -require_once(APPROOT.'application/itopwebpage.class.inc.php'); require_once(APPROOT.'application/startup.inc.php'); require_once(APPROOT.'application/loginwebpage.class.inc.php'); diff --git a/test/ItopDataTestCase.php b/test/ItopDataTestCase.php index 460c27139d..ad43fa5d9f 100644 --- a/test/ItopDataTestCase.php +++ b/test/ItopDataTestCase.php @@ -79,7 +79,7 @@ class ItopDataTestCase extends ItopTestCase /** * @throws Exception */ - protected function setUp() + protected function setUp(): void { parent::setUp(); require_once(APPROOT.'application/utils.inc.php'); @@ -101,15 +101,12 @@ class ItopDataTestCase extends ItopTestCase /** * @throws Exception */ - protected function tearDown() + protected function tearDown(): void { - if (static::USE_TRANSACTION) - { + if (static::USE_TRANSACTION) { $this->debug("ROLLBACK !!!"); CMDBSource::Query('ROLLBACK'); - } - else - { + } else { $this->debug(""); $this->aCreatedObjects = array_reverse($this->aCreatedObjects); foreach ($this->aCreatedObjects as $oObject) @@ -788,6 +785,7 @@ class ItopDataTestCase extends ItopTestCase // Create a specific organization for the tests $oOrg = $this->CreateOrganization('UnitTestOrganization'); $this->iTestOrgId = $oOrg->GetKey(); + return $oOrg; } /** diff --git a/test/ItopTestCase.php b/test/ItopTestCase.php index d527471d39..8c81c40b63 100644 --- a/test/ItopTestCase.php +++ b/test/ItopTestCase.php @@ -34,7 +34,8 @@ class ItopTestCase extends TestCase { const TEST_LOG_DIR = 'test'; - protected function setUp() + /** @noinspection UsingInclusionOnceReturnValueInspection avoid errors for approot includes */ + protected function setUp(): void { @include_once '../approot.inc.php'; @include_once '../../approot.inc.php'; @@ -45,8 +46,6 @@ class ItopTestCase extends TestCase @include_once '../../../../../../../approot.inc.php'; @include_once '../../../../../../../../approot.inc.php'; @include_once getcwd().'/approot.inc.php'; // this is when launching phpunit from within the IDE - - $this->debug("\n----------\n---------- ".$this->getName()."\n----------\n"); } protected function debug($sMsg) @@ -57,9 +56,9 @@ class ItopTestCase extends TestCase { echo "$sMsg\n"; } - else - { - print_r($sMsg); + else { + /** @noinspection ForgottenDebugOutputInspection */ + print_r($sMsg); } } } diff --git a/test/OQL/DataLocalizerTest.php b/test/OQL/DataLocalizerTest.php deleted file mode 100644 index 7ef28dd740..0000000000 --- a/test/OQL/DataLocalizerTest.php +++ /dev/null @@ -1,1141 +0,0 @@ -Set('use_legacy_dbsearch', true, 'Test'); - utils::GetConfig()->Set('apc_cache.enabled', false, 'Test'); - utils::GetConfig()->Set('expression_cache_enabled', false, 'Test'); - utils::GetConfig()->Set('query_cache_enabled', false, 'Test'); - $sConfigFile = utils::GetConfig()->GetLoadedFile(); - @chmod($sConfigFile, 0770); - utils::GetConfig()->WriteToFile(); - @chmod($sConfigFile, 0444); // Read-only - - SetupUtils::rrmdir($sResultFile = APPROOT.'log/test'); - } - - /** - * @dataProvider OQLGroupByProvider - * @depends testOQLLegacySetup - * - * @param $sOQL - * @param $aArgs - * @param $aGroupByExpr - * @param bool $bExcludeNullValues - * @param array $aSelectExpr - * @param array $aOrderBy - * @param int $iLimitCount - * @param int $iLimitStart - * - * @throws \CoreException - * @throws \MySQLException - * @throws \MySQLHasGoneAwayException - * @throws \OQLException - */ - public function testOQLGroupByLegacy($sOQL, $aArgs, $aGroupByExpr, $bExcludeNullValues = false, $aSelectExpr = array(), $aOrderBy = array(), $iLimitCount = 0, $iLimitStart = 0) - { - $this->assertTrue(utils::GetConfig()->Get('use_legacy_dbsearch')); - $this->assertFalse(utils::GetConfig()->Get('apc_cache.enabled')); - $this->assertFalse(utils::GetConfig()->Get('query_cache_enabled')); - $this->assertFalse(utils::GetConfig()->Get('expression_cache_enabled')); - - $aPrevious = $this->GetPreviousTestResult($this->GetId()); - if (is_null($aPrevious)) - { - $aResult = $this->OQLGroupByRunner($sOQL, $aArgs, $aGroupByExpr, $bExcludeNullValues, $aSelectExpr, $aOrderBy, $iLimitCount, $iLimitStart); - // no test yet, just save - $this->SaveTestResult($this->GetId(), $aResult); - $this->debug("Test result saved"); - } - $this->assertTrue(true); - } - - /** - * @dataProvider OQLSelectProvider - * @depends testOQLLegacySetup - * - * @param $sOQL - * - * @param array $aOrderBy - * @param array $aArgs - * @param null $aAttToLoad - * @param null $aExtendedDataSpec - * @param int $iLimitCount - * @param int $iLimitStart - * - * @throws \Exception - * @throws \MissingQueryArgument - * @throws \OQLException - */ - public function testOQLLegacySelect($sOQL, $aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 20, $iLimitStart = 0) - { - $this->assertTrue(utils::GetConfig()->Get('use_legacy_dbsearch')); - $this->assertFalse(utils::GetConfig()->Get('apc_cache.enabled')); - $this->assertFalse(utils::GetConfig()->Get('query_cache_enabled')); - $this->assertFalse(utils::GetConfig()->Get('expression_cache_enabled')); - - $aPrevious = $this->GetPreviousTestResult($this->GetId()); - if (is_null($aPrevious)) - { - $aResult = $this->OQLSelectRunner($sOQL, $aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart); - // no test yet, just save - $this->SaveTestResult($this->GetId(), $aResult); - $this->debug("Test result saved"); - } - $this->assertTrue(true); - } - - /** - * @doesNotPerformAssertions - * - * @throws \ConfigException - * @throws \CoreException - */ - public function testOQLSetup() - { - utils::GetConfig()->Set('use_legacy_dbsearch', false, 'test'); - utils::GetConfig()->Set('apc_cache.enabled', false, 'test'); - utils::GetConfig()->Set('query_cache_enabled', false, 'test'); - utils::GetConfig()->Set('expression_cache_enabled', false, 'test'); - $sConfigFile = utils::GetConfig()->GetLoadedFile(); - @chmod($sConfigFile, 0770); - utils::GetConfig()->WriteToFile(); - @chmod($sConfigFile, 0444); // Read-only - - $aCSVHeader = array( - 'test', 'OQL','count', - 'Legacy Count Joins', 'Count Joins', - 'Legacy Count Duration', 'Count Duration', - 'Legacy Data Joins', 'Data Joins', - 'Legacy Data Duration', 'Data Duration', - 'Count Joins Diff', 'Data Joins Diff', - ); - $this->WriteToCsvHeader(self::TEST_CSV_RESULT, $aCSVHeader); - } - - /** - * @dataProvider OQLGroupByProvider - * @depends testOQLSetup - * - * @param $sOQL - * @param $aArgs - * @param $aGroupByExpr - * @param bool $bExcludeNullValues - * @param array $aSelectExpr - * @param array $aOrderBy - * @param int $iLimitCount - * @param int $iLimitStart - * - * @throws \CoreException - * @throws \MySQLException - * @throws \MySQLHasGoneAwayException - * @throws \OQLException - */ - public function testOQLGroupBy($sOQL, $aArgs, $aGroupByExpr, $bExcludeNullValues = false, $aSelectExpr = array(), $aOrderBy = array(), $iLimitCount = 0, $iLimitStart = 0) - { - $this->assertFalse(utils::GetConfig()->Get('use_legacy_dbsearch')); - $this->assertFalse(utils::GetConfig()->Get('apc_cache.enabled')); - $this->assertFalse(utils::GetConfig()->Get('query_cache_enabled')); - $this->assertFalse(utils::GetConfig()->Get('expression_cache_enabled')); - - $aResult = $this->OQLGroupByRunner($sOQL, $aArgs, $aGroupByExpr, $bExcludeNullValues, $aSelectExpr, $aOrderBy, $iLimitCount, $iLimitStart); - $this->assertNull($aResult); - } - - /** - * @param $sOQL - * @param $aArgs - * @param $aGroupByExpr - * @param bool $bExcludeNullValues - * @param array $aSelectExpr - * @param array $aOrderBy - * @param int $iLimitCount - * @param int $iLimitStart - * - * @return array|null - * @throws \CoreException - * @throws \MySQLException - * @throws \MySQLHasGoneAwayException - * @throws \OQLException - */ - private function OQLGroupByRunner($sOQL, $aArgs, $aGroupByExpr, $bExcludeNullValues = false, $aSelectExpr = array(), $aOrderBy = array(), $iLimitCount = 0, $iLimitStart = 0) - { - $oSearch = DBSearch::FromOQL($sOQL); - - $aGroupByExpr = Expression::ConvertArrayFromOQL($aGroupByExpr); - $aSelectExpr = Expression::ConvertArrayFromOQL($aSelectExpr); - - $sSQL = $oSearch->MakeGroupByQuery($aArgs, $aGroupByExpr, $bExcludeNullValues, $aSelectExpr, $aOrderBy, $iLimitCount, $iLimitStart); - - $this->debug($sSQL); - - $fStart = $this->GetMicroTime(); - $aRow = $this->GetArrayResult($sSQL); - $fDataDuration = $this->GetMicroTime() - $fStart; - if (is_null($aRow)) - { - $aRow = array(); - } - // Store only to the 10 first entries - $aRow = array_slice($aRow, 0, 10); - - $iJoinData = count(explode(' JOIN ', $sSQL)) - 1; - - $aResult = array( - 'oql' => $sOQL, - 'data_sql' => $sSQL, - 'data_join_count' => $iJoinData, - 'data_duration' => $fDataDuration, - ); - - $aResult['data'] = $aRow; - - $aPrevious = $this->GetPreviousTestResult($this->GetId()); - if (is_null($aPrevious)) - { - return $aResult; - } - - $this->debug("data_join_count : ".$aPrevious['data_join_count']." -> ".$aResult['data_join_count']); - $this->debug("data_duration : ".round($aPrevious['data_duration'], NUM_PRECISION)." -> ".round($aResult['data_duration'], NUM_PRECISION)); - - // Compare result - $aFields = array('oql', 'data'); - foreach ($aFields as $sField) - { - $this->assertEquals($aPrevious[$sField], $aResult[$sField], "$sField differ"); - } - - if ($aPrevious['data_join_count'] != $aResult['data_join_count']) - { - unset($aPrevious['data']); - unset($aResult['data']); - $this->debug("Previous"); - $this->debug($aPrevious); - $this->debug("Current"); - $this->debug($aResult); - } - return null; - } - - private function OQLGroupByProviderStatic() - { - $aData = array(); - $aData["SELECT 0"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`service_name` NOT IN ('Sales', 'Community Support')) AND (`UserRequest`.`start_date` > '2015-06-01'))", array(), unserialize('a:1:{s:12:"grouped_by_1";s:51:"DATE_FORMAT(`UserRequest`.`start_date`, \'%Y-%m-%d\')";}'), false, array(), array(), 0, 0); - $aData["SELECT 1"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` != 'closed')", array(), unserialize('a:1:{s:6:"group1";s:22:"`UserRequest`.`status`";}'), false, array(), array(), 0, 0); - $aData["SELECT 2"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE 1", array(), unserialize('a:1:{s:12:"grouped_by_1";s:22:"`UserRequest`.`status`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:1;}'), 0, 0); - $aData["SELECT 4"] = array("SELECT `Contact` FROM Contact AS `Contact` WHERE 1", array(), unserialize('a:1:{s:12:"grouped_by_1";s:18:"`Contact`.`status`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:0;}'), 0, 0); - $aData["SELECT 5"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` NOT IN ('closed', 'rejected'))", array(), unserialize('a:1:{s:12:"grouped_by_1";s:22:"`UserRequest`.`status`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:0;}'), 0, 0); - $aData["SELECT 6"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` NOT IN ('closed', 'rejected'))", array(), unserialize('a:1:{s:12:"grouped_by_1";s:24:"`UserRequest`.`agent_id`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:0;}'), 0, 0); - $aData["SELECT 7"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` NOT IN ('closed', 'rejected'))", array(), unserialize('a:1:{s:12:"grouped_by_1";s:26:"`UserRequest`.`finalclass`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:0;}'), 0, 0); - $aData["SELECT 8"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` NOT IN ('closed', 'rejected'))", array(), unserialize('a:1:{s:12:"grouped_by_1";s:22:"`UserRequest`.`org_id`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:0;}'), 0, 0); - $aData["SELECT 9"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (DATE_SUB(NOW(), INTERVAL 14 DAY) < `UserRequest`.`start_date`)", array(), unserialize('a:1:{s:12:"grouped_by_1";s:28:"`UserRequest`.`request_type`";}'), true, array(), unserialize('a:1:{s:12:"_itop_count_";b:0;}'), 0, 0); - $aData["SELECT 10"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (DATE_SUB(NOW(), INTERVAL 14 DAY) < `UserRequest`.`start_date`)", array(), unserialize('a:1:{s:12:"grouped_by_1";s:51:"DATE_FORMAT(`UserRequest`.`start_date`, \'%Y-%m-%d\')";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:1;}'), 0, 0); - $aData["SELECT 11"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`creation_date` > DATE_SUB(NOW(), INTERVAL 7 DAY))", array(), unserialize('a:1:{s:12:"grouped_by_1";s:19:"`Change`.`category`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:0;}'), 0, 0); - $aData["SELECT 12"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`creation_date` > DATE_SUB(NOW(), INTERVAL 7 DAY))", array(), unserialize('a:1:{s:12:"grouped_by_1";s:21:"`Change`.`finalclass`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:0;}'), 0, 0); - $aData["SELECT 13"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`creation_date` > DATE_SUB(NOW(), INTERVAL 7 DAY))", array(), unserialize('a:1:{s:12:"grouped_by_1";s:17:"`Change`.`status`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:0;}'), 0, 0); - $aData["SELECT 14"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`creation_date` > DATE_SUB(NOW(), INTERVAL 7 DAY))", array(), unserialize('a:1:{s:12:"grouped_by_1";s:46:"DATE_FORMAT(`Change`.`start_date`, \'%Y-%m-%d\')";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:1;}'), 0, 0); - $aData["SELECT 15"] = array("SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE (`FunctionalCI`.`org_id` = '3')", array(), unserialize('a:1:{s:6:"group1";s:27:"`FunctionalCI`.`finalclass`";}'), false, array(), array(), 0, 0); - $aData["SELECT 16"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` != 'closed') AND (`UserRequest`.`org_id` = '3'))", array(), unserialize('a:1:{s:6:"group1";s:22:"`UserRequest`.`status`";}'), false, array(), array(), 0, 0); - $aData["SELECT 17"] = array("SELECT `Ticket` FROM Ticket AS `Ticket` WHERE (`Ticket`.`org_id` = '3')", array(), unserialize('a:1:{s:12:"grouped_by_1";s:21:"`Ticket`.`finalclass`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:0;}'), 0, 0); - - return $aData; - } - - public function OQLGroupByProvider() - { - $aData = $this->OQLGroupByProviderStatic(); - - // Dynamic entries - @include ('oql_group_by_records.php'); - - return $aData; - } - - - /** - * @dataProvider OQLSelectProvider - * @depends testOQLSetup - * - * @param $sOQL - * - * @param array $aOrderBy - * @param array $aArgs - * @param null $aAttToLoad - * @param null $aExtendedDataSpec - * @param int $iLimitCount - * @param int $iLimitStart - * - * @throws \Exception - * @throws \MissingQueryArgument - * @throws \OQLException - */ - public function testOQLSelect($sOQL, $aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 20, $iLimitStart = 0) - { - $this->assertFalse(utils::GetConfig()->Get('use_legacy_dbsearch')); - $this->assertFalse(utils::GetConfig()->Get('apc_cache.enabled')); - $this->assertFalse(utils::GetConfig()->Get('query_cache_enabled')); - $this->assertFalse(utils::GetConfig()->Get('expression_cache_enabled')); - - $aResult = $this->OQLSelectRunner($sOQL, $aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart); - $this->assertNull($aResult); - } - - /** - * @param $sOQL - * - * @param array $aOrderBy - * @param array $aArgs - * @param null $aAttToLoadNames - * @param null $aExtendedDataSpec - * @param int $iLimitCount - * @param int $iLimitStart - * - * @return array|null - * @throws \Exception - * @throws \MissingQueryArgument - * @throws \OQLException - */ - private function OQLSelectRunner($sOQL, $aOrderBy = array(), $aArgs = array(), $aAttToLoadNames = null, $aExtendedDataSpec = null, $iLimitCount = 20, $iLimitStart = 0) - { - if (is_null($aAttToLoadNames)) - { - $aAttToLoad = null; - } - else - { - $aAttToLoad = array(); - foreach ($aAttToLoadNames as $sClass => $aAttCodes) - { - $aAttToLoad[$sClass] = array(); - foreach ($aAttCodes as $sAttCode) - { - if (!empty($sAttCode)) - { - if (MetaModel::IsValidAttCode($sClass, $sAttCode)) - { - $aAttToLoad[$sClass][$sAttCode] = MetaModel::GetAttributeDef($sClass, $sAttCode); - } - } - } - } - } - - $oSearch = DBSearch::FromOQL($sOQL); - $sClass = $oSearch->GetClass(); - if (empty($aOrderBy)) - { - $aOrderBy = MetaModel::GetOrderByDefault($sClass); - } - - $sSQLCount = $oSearch->MakeSelectQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, 0, 0, true); - $fStart = $this->GetMicroTime(); - $aRow = $this->GetArrayResult($sSQLCount); - $fCountDuration = $this->GetMicroTime() - $fStart; - if (is_null($aRow)) - { - $iCount = 0; - } - else - { - $iCount = intval($aRow[0]['COUNT']); - } - $iJoinCount = count(explode(' JOIN ', $sSQLCount)) - 1; - - - $sSQL = $oSearch->MakeSelectQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart); - $fStart = $this->GetMicroTime(); - $aRow = $this->GetArrayResult($sSQL); - $fDataDuration = $this->GetMicroTime() - $fStart; - if (is_null($aRow)) - { - $aRow = array(); - } - // Store only to the 10 first entries - $aRow = array_slice($aRow, 0, 10); - - $iJoinData = count(explode(' JOIN ', $sSQL)) - 1; - - $aResult = array( - 'oql' => $sOQL, - 'count_sql' => $sSQLCount, - 'count_join_count' => $iJoinCount, - 'count' => $iCount, - 'count_duration' => $fCountDuration, - 'data_sql' => $sSQL, - 'data_join_count' => $iJoinData, - 'data_duration' => $fDataDuration, - ); - - //$this->debug($aResult); - - $aResult['data'] = $aRow; - - $aPrevious = $this->GetPreviousTestResult($this->GetId()); - if (is_null($aPrevious)) - { - return $aResult; - } - - $this->debug("count: ".$aResult['count']); - $this->debug("count_join_count: ".$aPrevious['count_join_count']." -> ".$aResult['count_join_count']); - $this->debug("count_duration : ".round($aPrevious['count_duration'], NUM_PRECISION)." -> ".round($aResult['count_duration'], NUM_PRECISION)); - $this->debug("data_join_count : ".$aPrevious['data_join_count']." -> ".$aResult['data_join_count']); - $this->debug("data_duration : ".round($aPrevious['data_duration'], NUM_PRECISION)." -> ".round($aResult['data_duration'], NUM_PRECISION)); - - $aCSVData = array( - $this->GetId(), $sOQL, $aResult['count'], - $aPrevious['count_join_count'], $aResult['count_join_count'], - round($aPrevious['count_duration'], NUM_PRECISION), round($aResult['count_duration'], NUM_PRECISION), - $aPrevious['data_join_count'], $aResult['data_join_count'], - round($aPrevious['data_duration'], NUM_PRECISION), round($aResult['data_duration'], NUM_PRECISION), - $aPrevious['count_join_count'] - $aResult['count_join_count'], $aPrevious['data_join_count'] - $aResult['data_join_count'], - ); - $this->WriteToCsvData(self::TEST_CSV_RESULT, $aCSVData); - - // Compare result - $aFields = array('oql', 'count', 'data'); - foreach ($aFields as $sField) - { - $this->assertEquals($aPrevious[$sField], $aResult[$sField], "$sField differ"); - } - - if ($aPrevious['data_join_count'] != $aResult['data_join_count']) - { - unset($aPrevious['data']); - unset($aResult['data']); - $this->debug("Previous"); - $this->debug($aPrevious); - $this->debug("Current"); - $this->debug($aResult); - } - return null; - } - - private function OQLSelectProviderStaticTTO() - { - $aAttToLoadTTO = array('UserRequest' => array('tto')); - $aData = array( - "SELECT UserRequest TTO" => array("SELECT UserRequest FROM UserRequest AS UserRequest WHERE tto_laststart AND tto_75_triggered = 0 AND tto_75_deadline < '2010-06-26 12:08:59'", array(), array(), $aAttToLoadTTO, null, null, 3, 0), - ); - - return $aData; - } - - - private function OQLSelectProviderStatic() - { - $aArgs = array( - 'ActionEmail_finalclass' => 'ActionEmail', - 'UserInternal_status' => 'active', - 'current_contact_id' => '2', - 'id' => 3, - 'login' => 'admin', - 'menu_code' => 'WelcomeMenuPage', - 'name' => 'database_uuid', - 'this->brand_id' => '1', - 'this->finalclass' => 'NetworkDevice', - 'this->id' => 3, - 'this->location_id' => 2, - 'this->org_id' => 3, - 'this->osfamily_id' => '6', - 'this->osversion_id' => '8', - 'this->rack_id' => '3', - 'this->request_type' => 'incident', - 'this->service_id' => '1', - 'user_id' => '5', - 'userid' => '5', - ); - - $aAttToLoad150 = array( - 'WebServer' => array( - 'business_criticity', - 'description', - 'name', - 'friendlyname', - 'obsolescence_flag', - 'finalclass', - ), - ); - - $aData = array( - "SELECT WebServer 150" => array("SELECT `WebServer` FROM WebServer AS `WebServer` WHERE 1", array(), array(), $aAttToLoad150, null, null, 3, 0), - "SELECT WebServer 151" => array("SELECT `WebServer` FROM WebServer AS `WebServer` WHERE 1", array(), array(), array(), null, null, 3, 0), - "SELECT L JOIN 176" => array("SELECT `L` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE 1", unserialize('a:1:{s:14:"L.friendlyname";b:1;}'), array(), null, null, 3, 0), - "SELECT P JOIN 177" => array("SELECT `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, null, 3, 0), - "SELECT L,P JOIN 178" => array("SELECT `L`, `P` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, null, 3, 0), - "SELECT P,L JOIN 179" => array("SELECT `P`, `L` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, null, 3, 0), - "SELECT L,P JOIN 180" => array("SELECT `L`, `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, null, 3, 0), - "SELECT L JOIN 181" => array("SELECT `L` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", unserialize('a:1:{s:14:"L.friendlyname";b:1;}'), array(), null, null, 3, 0), - "SELECT UserRequest 114" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` JOIN Organization AS `Organization` ON `UserRequest`.org_id = `Organization`.id JOIN Organization AS `Organization1` ON `Organization`.parent_id BELOW `Organization1`.id WHERE (`Organization1`.`id` = '3')", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 115" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (('2018-12-01' < `UserRequest`.`start_date`) AND (ISNULL(DATE_FORMAT(`UserRequest`.`start_date`, '%Y-%m-%d')) != 1))", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 116" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`agent_id` = :current_contact_id) AND (`UserRequest`.`status` NOT IN ('closed', 'resolved')))", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 117" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`caller_id` = :current_contact_id) AND (`UserRequest`.`status` NOT IN ('closed')))", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 118" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` IN ('escalated_tto', 'escalated_ttr')) OR (`UserRequest`.`escalation_flag` = 'yes'))", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 119" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`agent_id`) != 1))", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 120" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`finalclass`) != 1))", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 121" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`org_id`) != 1))", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 122" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`status`) != 1))", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 123" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (ISNULL(`UserRequest`.`org_id`) != 1)", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 124" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` != 'closed')", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 125" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` NOT IN ('closed'))", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 126" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE 1", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 25" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`id` = :id)", array(), unserialize('a:1:{s:2:"id";i:987654321;}'), null, null, 0, 0), - "SELECT ApplicationSolution 2" => array("SELECT `ApplicationSolution` FROM ApplicationSolution AS `ApplicationSolution` WHERE 1", array('ApplicationSolution.friendlyname' => true), $aArgs), - "SELECT AuditCategory 3" => array("SELECT `AuditCategory` FROM AuditCategory AS `AuditCategory` WHERE 1", array('AuditCategory.friendlyname' => true), $aArgs), - "SELECT Brand 4" => array("SELECT `Brand` FROM Brand AS `Brand` WHERE (`Brand`.`friendlyname` LIKE '%%')", array('Brand.friendlyname' => true), $aArgs), - "SELECT Brand 5" => array("SELECT `Brand` FROM Brand AS `Brand` WHERE 1", array('Brand.friendlyname' => true), $aArgs), - "SELECT BusinessProcess 6" => array("SELECT `BusinessProcess` FROM BusinessProcess AS `BusinessProcess` WHERE 1", array('BusinessProcess.friendlyname' => true), $aArgs), - "SELECT Change 7" => array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`agent_id` = :current_contact_id) AND (`Change`.`status` NOT IN ('closed')))", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 8" => array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(DATE_FORMAT(`Change`.`start_date`, '%Y-%m-%d')) != 1))", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 9" => array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(`Change`.`category`) != 1))", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 10" => array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(`Change`.`finalclass`) != 1))", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 11" => array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(`Change`.`status`) != 1))", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 12" => array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`id` != :this->id) AND (`Change`.`friendlyname` LIKE '%%'))", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 13" => array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`status` != 'closed') AND (`Change`.`friendlyname` LIKE '%%'))", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 14" => array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`friendlyname` LIKE '%%')", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 15" => array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`status` != 'closed')", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 16" => array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`status` IN ('planned'))", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 17" => array("SELECT `Change` FROM Change AS `Change` WHERE 1", array('Change.friendlyname' => true), $aArgs), - "SELECT ContactType 18" => array("SELECT `ContactType` FROM ContactType AS `ContactType` WHERE 1", array('ContactType.friendlyname' => true), $aArgs), - "SELECT Contact 19" => array("SELECT `Contact` FROM Contact AS `Contact` WHERE (`Contact`.`id` = :id)", array('Contact.friendlyname' => true), $aArgs), - "SELECT Contact 20" => array("SELECT `Contact` FROM Contact AS `Contact` WHERE 1", array('Contact.friendlyname' => true), $aArgs), - "SELECT ContractType 21" => array("SELECT `ContractType` FROM ContractType AS `ContractType` WHERE (`ContractType`.`friendlyname` LIKE '%%')", array('ContractType.friendlyname' => true), $aArgs), - "SELECT ContractType 22" => array("SELECT `ContractType` FROM ContractType AS `ContractType` WHERE 1", array('ContractType.friendlyname' => true), $aArgs), - "SELECT Contract 23" => array("SELECT `Contract` FROM Contract AS `Contract` WHERE 1", array('Contract.friendlyname' => true), $aArgs), - "SELECT CustomerContract 24" => array("SELECT `CustomerContract` FROM CustomerContract AS `CustomerContract` WHERE 1", array('CustomerContract.friendlyname' => true), $aArgs), - "SELECT DBProperty 25" => array("SELECT `DBProperty` FROM DBProperty AS `DBProperty` WHERE (`DBProperty`.`name` = :name)", array('DBProperty.friendlyname' => true), $aArgs), - "SELECT DBServer 26" => array("SELECT `DBServer` FROM DBServer AS `DBServer` WHERE 1", array('DBServer.friendlyname' => true), $aArgs), - "SELECT DatabaseSchema 27" => array("SELECT `DatabaseSchema` FROM DatabaseSchema AS `DatabaseSchema` WHERE 1", array('DatabaseSchema.friendlyname' => true), $aArgs), - "SELECT DeliveryModel 28" => array("SELECT `DeliveryModel` FROM DeliveryModel AS `DeliveryModel` WHERE (`DeliveryModel`.`friendlyname` LIKE '%%')", array('DeliveryModel.friendlyname' => true), $aArgs), - "SELECT DeliveryModel 29" => array("SELECT `DeliveryModel` FROM DeliveryModel AS `DeliveryModel` WHERE 1", array('DeliveryModel.friendlyname' => true), $aArgs), - "SELECT DocumentType 30" => array("SELECT `DocumentType` FROM DocumentType AS `DocumentType` WHERE (`DocumentType`.`friendlyname` LIKE '%%')", array('DocumentType.friendlyname' => true), $aArgs), - "SELECT DocumentType 31" => array("SELECT `DocumentType` FROM DocumentType AS `DocumentType` WHERE 1", array('DocumentType.friendlyname' => true), $aArgs), - "SELECT Document 32" => array("SELECT `Document` FROM Document AS `Document` WHERE 1", array('Document.friendlyname' => true), $aArgs), - "SELECT Enclosure 33" => array("SELECT `Enclosure` FROM Enclosure AS `Enclosure` WHERE ((`Enclosure`.`rack_id` = :this->rack_id) AND (`Enclosure`.`friendlyname` LIKE '%%'))", array('Enclosure.friendlyname' => true), $aArgs), - "SELECT Enclosure 34" => array("SELECT `Enclosure` FROM Enclosure AS `Enclosure` WHERE (`Enclosure`.`friendlyname` LIKE '%%')", array('Enclosure.friendlyname' => true), $aArgs), - "SELECT Enclosure 35" => array("SELECT `Enclosure` FROM Enclosure AS `Enclosure` WHERE 1", array('Enclosure.friendlyname' => true), $aArgs), - "SELECT Farm 36" => array("SELECT `Farm` FROM Farm AS `Farm` WHERE 1", array('Farm.friendlyname' => true), $aArgs), - "SELECT FunctionalCI 37" => array("SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE 1", array('FunctionalCI.friendlyname' => true), $aArgs), - "SELECT Group 38" => array("SELECT `Group` FROM Group AS `Group` WHERE (`Group`.`friendlyname` LIKE '%%')", array('Group.friendlyname' => true), $aArgs), - "SELECT Group 39" => array("SELECT `Group` FROM Group AS `Group` WHERE 1", array('Group.friendlyname' => true), $aArgs), - "SELECT Hypervisor 40" => array("SELECT `Hypervisor` FROM Hypervisor AS `Hypervisor` WHERE 1", array('Hypervisor.friendlyname' => true), $aArgs), - "SELECT IOSVersion 41" => array("SELECT `IOSVersion` FROM IOSVersion AS `IOSVersion` WHERE 1", array('IOSVersion.friendlyname' => true), $aArgs), - "SELECT IPPhone 42" => array("SELECT `IPPhone` FROM IPPhone AS `IPPhone` WHERE 1", array('IPPhone.friendlyname' => true), $aArgs), - "SELECT Licence 43" => array("SELECT `Licence` FROM Licence AS `Licence` WHERE 1", array('Licence.friendlyname' => true), $aArgs), - "SELECT Location 44" => array("SELECT `Location` FROM Location AS `Location` WHERE 1", array('Location.friendlyname' => true), $aArgs), - "SELECT LogicalVolume 45" => array("SELECT `LogicalVolume` FROM LogicalVolume AS `LogicalVolume` WHERE 1", array('LogicalVolume.friendlyname' => true), $aArgs), - "SELECT MiddlewareInstance 46" => array("SELECT `MiddlewareInstance` FROM MiddlewareInstance AS `MiddlewareInstance` WHERE 1", array('MiddlewareInstance.friendlyname' => true), $aArgs), - "SELECT Middleware 47" => array("SELECT `Middleware` FROM Middleware AS `Middleware` WHERE 1", array('Middleware.friendlyname' => true), $aArgs), - "SELECT MobilePhone 48" => array("SELECT `MobilePhone` FROM MobilePhone AS `MobilePhone` WHERE 1", array('MobilePhone.friendlyname' => true), $aArgs), - "SELECT Model 49" => array("SELECT `Model` FROM Model AS `Model` WHERE (((`Model`.`brand_id` = :this->brand_id) AND (`Model`.`type` = :this->finalclass)) AND (`Model`.`friendlyname` LIKE '%%'))", array('Model.friendlyname' => true), $aArgs), - "SELECT Model 50" => array("SELECT `Model` FROM Model AS `Model` WHERE (`Model`.`friendlyname` LIKE '%%')", array('Model.friendlyname' => true), $aArgs), - "SELECT Model 51" => array("SELECT `Model` FROM Model AS `Model` WHERE 1", array('Model.friendlyname' => true), $aArgs), - "SELECT NAS 52" => array("SELECT `NAS` FROM NAS AS `NAS` WHERE 1", array('NAS.friendlyname' => true), $aArgs), - "SELECT NetworkDeviceType 53" => array("SELECT `NetworkDeviceType` FROM NetworkDeviceType AS `NetworkDeviceType` WHERE 1", array('NetworkDeviceType.friendlyname' => true), $aArgs), - "SELECT NetworkDevice 54" => array("SELECT `NetworkDevice` FROM NetworkDevice AS `NetworkDevice` WHERE 1", array('NetworkDevice.friendlyname' => true), $aArgs), - "SELECT NetworkInterface 55" => array("SELECT `NetworkInterface` FROM NetworkInterface AS `NetworkInterface` WHERE 1", array('NetworkInterface.friendlyname' => true), $aArgs), - "SELECT OSFamily 56" => array("SELECT `OSFamily` FROM OSFamily AS `OSFamily` WHERE (`OSFamily`.`friendlyname` LIKE '%%')", array('OSFamily.friendlyname' => true), $aArgs), - "SELECT OSFamily 57" => array("SELECT `OSFamily` FROM OSFamily AS `OSFamily` WHERE 1", array('OSFamily.friendlyname' => true), $aArgs), - "SELECT OSLicence 58" => array("SELECT `OSLicence` FROM OSLicence AS `OSLicence` WHERE ((`OSLicence`.`osversion_id` = :this->osversion_id) AND (`OSLicence`.`friendlyname` LIKE '%%'))", array('OSLicence.friendlyname' => true), $aArgs), - "SELECT OSLicence 59" => array("SELECT `OSLicence` FROM OSLicence AS `OSLicence` WHERE (`OSLicence`.`friendlyname` LIKE '%%')", array('OSLicence.friendlyname' => true), $aArgs), - "SELECT OSLicence 60" => array("SELECT `OSLicence` FROM OSLicence AS `OSLicence` WHERE 1", array('OSLicence.friendlyname' => true), $aArgs), - "SELECT OSVersion 61" => array("SELECT `OSVersion` FROM OSVersion AS `OSVersion` WHERE ((`OSVersion`.`osfamily_id` = :this->osfamily_id) AND (`OSVersion`.`friendlyname` LIKE '%%'))", array('OSVersion.friendlyname' => true), $aArgs), - "SELECT OSVersion 62" => array("SELECT `OSVersion` FROM OSVersion AS `OSVersion` WHERE (`OSVersion`.`friendlyname` LIKE '%%')", array('OSVersion.friendlyname' => true), $aArgs), - "SELECT OSVersion 63" => array("SELECT `OSVersion` FROM OSVersion AS `OSVersion` WHERE 1", array('OSVersion.friendlyname' => true), $aArgs), - "SELECT Organization 64" => array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = :id)", array('Organization.friendlyname' => true), $aArgs), - "SELECT Organization 65" => array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = :id)", array('Organization.friendlyname' => true), $aArgs), - "SELECT Organization 66" => array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", array('Organization.friendlyname' => true), $aArgs), - "SELECT OtherSoftware 67" => array("SELECT `OtherSoftware` FROM OtherSoftware AS `OtherSoftware` WHERE 1", array('OtherSoftware.friendlyname' => true), $aArgs), - "SELECT PCSoftware 68" => array("SELECT `PCSoftware` FROM PCSoftware AS `PCSoftware` WHERE 1", array('PCSoftware.friendlyname' => true), $aArgs), - "SELECT PC 69" => array("SELECT `PC` FROM PC AS `PC` WHERE 1", array('PC.friendlyname' => true), $aArgs), - "SELECT Patch 70" => array("SELECT `Patch` FROM Patch AS `Patch` WHERE 1", array('Patch.friendlyname' => true), $aArgs), - "SELECT Peripheral 71" => array("SELECT `Peripheral` FROM Peripheral AS `Peripheral` WHERE 1", array('Peripheral.friendlyname' => true), $aArgs), - "SELECT Person 72" => array("SELECT `Person` FROM Person AS `Person` WHERE ((`Person`.`org_id` = 1) AND (ISNULL(`Person`.`org_id`) != 1))", array('Person.friendlyname' => true), $aArgs), - "SELECT Person 73" => array("SELECT `Person` FROM Person AS `Person` WHERE ((`Person`.`org_id` = 2) AND (ISNULL(`Person`.`org_id`) != 1))", array('Person.friendlyname' => true), $aArgs), - "SELECT Person 74" => array("SELECT `Person` FROM Person AS `Person` WHERE (`Person`.`id` = :id)", array('Person.friendlyname' => true), $aArgs), - "SELECT Person 75" => array("SELECT `Person` FROM Person AS `Person` WHERE 1", array('Person.friendlyname' => true), $aArgs), - "SELECT Person 76 DISTINCT" => array("SELECT `p` FROM Person AS `p` JOIN UserRequest AS `u` ON `u`.agent_id = `p`.id WHERE (`u`.`status` != 'closed')", array('p.friendlyname' => true), $aArgs), - "SELECT Phone 76" => array("SELECT `Phone` FROM Phone AS `Phone` WHERE 1", array('Phone.friendlyname' => true), $aArgs), - "SELECT PowerConnection 77" => array("SELECT `PowerConnection` FROM PowerConnection AS `PowerConnection` WHERE ((`PowerConnection`.`location_id` = :this->location_id) AND (`PowerConnection`.`friendlyname` LIKE '%%'))", array('PowerConnection.friendlyname' => true), $aArgs), - "SELECT PowerConnection 78" => array("SELECT `PowerConnection` FROM PowerConnection AS `PowerConnection` WHERE (`PowerConnection`.`friendlyname` LIKE '%%')", array('PowerConnection.friendlyname' => true), $aArgs), - "SELECT PowerConnection 79" => array("SELECT `PowerConnection` FROM PowerConnection AS `PowerConnection` WHERE 1", array('PowerConnection.friendlyname' => true), $aArgs), - "SELECT Printer 80" => array("SELECT `Printer` FROM Printer AS `Printer` WHERE 1", array('Printer.friendlyname' => true), $aArgs), - "SELECT ProviderContract 81" => array("SELECT `ProviderContract` FROM ProviderContract AS `ProviderContract` WHERE 1", array('ProviderContract.friendlyname' => true), $aArgs), - "SELECT Query 82" => array("SELECT `Query` FROM Query AS `Query` WHERE 1", array('Query.friendlyname' => true), $aArgs), - "SELECT Rack 83" => array("SELECT `Rack` FROM Rack AS `Rack` WHERE ((`Rack`.`location_id` = :this->location_id) AND (`Rack`.`friendlyname` LIKE '%%'))", array('Rack.friendlyname' => true), $aArgs), - "SELECT Rack 84" => array("SELECT `Rack` FROM Rack AS `Rack` WHERE (`Rack`.`friendlyname` LIKE '%%')", array('Rack.friendlyname' => true), $aArgs), - "SELECT Rack 85" => array("SELECT `Rack` FROM Rack AS `Rack` WHERE 1", array('Rack.friendlyname' => true), $aArgs), - "SELECT SANSwitch 86" => array("SELECT `SANSwitch` FROM SANSwitch AS `SANSwitch` WHERE 1", array('SANSwitch.friendlyname' => true), $aArgs), - "SELECT SLA 87" => array("SELECT `SLA` FROM SLA AS `SLA` WHERE 1", array('SLA.friendlyname' => true), $aArgs), - "SELECT SLT 88" => array("SELECT `SLT` FROM SLT AS `SLT` WHERE 1", array('SLT.friendlyname' => true), $aArgs), - "SELECT Server 89" => array("SELECT `Server` FROM Server AS `Server` WHERE (`Server`.`name` NOT LIKE '%2')", array('Server.friendlyname' => true), $aArgs), - "SELECT Server 90" => array("SELECT `Server` FROM Server AS `Server` WHERE 1", array('Server.friendlyname' => true), $aArgs), - "SELECT ServiceFamily 91" => array("SELECT `ServiceFamily` FROM ServiceFamily AS `ServiceFamily` WHERE (`ServiceFamily`.`friendlyname` LIKE '%%')", array('ServiceFamily.friendlyname' => true), $aArgs), - "SELECT ServiceFamily 92" => array("SELECT `ServiceFamily` FROM ServiceFamily AS `ServiceFamily` WHERE 1", array('ServiceFamily.friendlyname' => true), $aArgs), - "SELECT ServiceSubcategory 93" => array("SELECT `ServiceSubcategory` FROM ServiceSubcategory AS `ServiceSubcategory` WHERE ((((`ServiceSubcategory`.`service_id` = :this->service_id) AND (ISNULL(:this->request_type) OR (`ServiceSubcategory`.`request_type` = :this->request_type))) AND (`ServiceSubcategory`.`status` != 'obsolete')) AND (`ServiceSubcategory`.`friendlyname` LIKE '%%'))", array('ServiceSubcategory.friendlyname' => true), $aArgs), - "SELECT ServiceSubcategory 94" => array("SELECT `ServiceSubcategory` FROM ServiceSubcategory AS `ServiceSubcategory` WHERE (`ServiceSubcategory`.`friendlyname` LIKE '%%')", array('ServiceSubcategory.friendlyname' => true), $aArgs), - "SELECT ServiceSubcategory 95" => array("SELECT `ServiceSubcategory` FROM ServiceSubcategory AS `ServiceSubcategory` WHERE 1", array('ServiceSubcategory.friendlyname' => true), $aArgs), - "SELECT Service 96" => array("SELECT `Service` FROM Service AS `Service` WHERE (`Service`.`friendlyname` LIKE '%%')", array('Service.friendlyname' => true), $aArgs), - "SELECT Service 97" => array("SELECT `Service` FROM Service AS `Service` WHERE 1", array('Service.friendlyname' => true), $aArgs), - "SELECT ShortcutOQL 98" => array("SELECT `ShortcutOQL` FROM ShortcutOQL AS `ShortcutOQL` WHERE (`ShortcutOQL`.`id` = :id)", array('ShortcutOQL.friendlyname' => true), $aArgs), - "SELECT Shortcut 99" => array("SELECT `Shortcut` FROM Shortcut AS `Shortcut` WHERE (`Shortcut`.`user_id` = :user_id)", array('Shortcut.friendlyname' => true), $aArgs), - "SELECT Software 100" => array("SELECT `Software` FROM Software AS `Software` WHERE 1", array('Software.friendlyname' => true), $aArgs), - "SELECT StorageSystem 101" => array("SELECT `StorageSystem` FROM StorageSystem AS `StorageSystem` WHERE 1", array('StorageSystem.friendlyname' => true), $aArgs), - "SELECT Subnet 102" => array("SELECT `Subnet` FROM Subnet AS `Subnet` WHERE 1", array('Subnet.friendlyname' => true), $aArgs), - "SELECT SynchroDataSource 103" => array("SELECT `SynchroDataSource` FROM SynchroDataSource AS `SynchroDataSource` WHERE 1", array('SynchroDataSource.friendlyname' => true), $aArgs), - "SELECT Tablet 104" => array("SELECT `Tablet` FROM Tablet AS `Tablet` WHERE 1", array('Tablet.friendlyname' => true), $aArgs), - "SELECT TapeLibrary 105" => array("SELECT `TapeLibrary` FROM TapeLibrary AS `TapeLibrary` WHERE 1", array('TapeLibrary.friendlyname' => true), $aArgs), - "SELECT Team 106" => array("SELECT `Team` FROM Team AS `Team` WHERE (`Team`.`friendlyname` LIKE '%%')", array('Team.friendlyname' => true), $aArgs), - "SELECT Team 107" => array("SELECT `Team` FROM Team AS `Team` WHERE 1", array('Team.friendlyname' => true), $aArgs), - "SELECT Trigger 108" => array("SELECT `Trigger` FROM Trigger AS `Trigger` WHERE 1", array('Trigger.friendlyname' => true), $aArgs), - "SELECT URP_Profiles 109" => array("SELECT `URP_Profiles` FROM URP_Profiles AS `URP_Profiles` WHERE 1", array('URP_Profiles.friendlyname' => true), $aArgs), - "SELECT URP_UserProfile 110" => array("SELECT `URP_UserProfile` FROM URP_UserProfile AS `URP_UserProfile` WHERE (`URP_UserProfile`.`userid` = :userid)", array('URP_UserProfile.friendlyname' => true), $aArgs), - "SELECT UserDashboard 111" => array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = :user_id) AND (`UserDashboard`.`menu_code` = :menu_code))", array('UserDashboard.friendlyname' => true), $aArgs), - "SELECT UserInternal 112" => array("SELECT `UserInternal` FROM UserInternal AS `UserInternal` WHERE ((`UserInternal`.`login` = :login) AND (`UserInternal`.`status` = :UserInternal_status))", array('UserInternal.friendlyname' => true), $aArgs), - "SELECT UserLocal 113" => array("SELECT `UserLocal` FROM UserLocal AS `UserLocal` WHERE (`UserLocal`.`id` = :id)", array('UserLocal.friendlyname' => true), $aArgs), - "SELECT User 127" => array("SELECT `User` FROM User AS `User` WHERE (`User`.`friendlyname` LIKE '%%')", array('User.friendlyname' => true), $aArgs), - "SELECT User 128" => array("SELECT `User` FROM User AS `User` WHERE (`User`.`id` = :id)", array('User.friendlyname' => true), $aArgs), - "SELECT User 129" => array("SELECT `User` FROM User AS `User` WHERE 1", array('User.friendlyname' => true), $aArgs), - "SELECT VLAN 130" => array("SELECT `VLAN` FROM VLAN AS `VLAN` WHERE 1", array('VLAN.friendlyname' => true), $aArgs), - "SELECT VirtualMachine 131" => array("SELECT `VirtualMachine` FROM VirtualMachine AS `VirtualMachine` WHERE 1", array('VirtualMachine.friendlyname' => true), $aArgs), - "SELECT WebApplication 132" => array("SELECT `WebApplication` FROM WebApplication AS `WebApplication` WHERE 1", array('WebApplication.friendlyname' => true), $aArgs), - "SELECT WebServer 133" => array("SELECT `WebServer` FROM WebServer AS `WebServer` WHERE 1", array('WebServer.friendlyname' => true), $aArgs), - "SELECT appUserPreferences 134" => array("SELECT `appUserPreferences` FROM appUserPreferences AS `appUserPreferences` WHERE (`appUserPreferences`.`id` = :id)", array('appUserPreferences.friendlyname' => true), $aArgs), - "SELECT appUserPreferences 135" => array("SELECT `appUserPreferences` FROM appUserPreferences AS `appUserPreferences` WHERE (`appUserPreferences`.`userid` = :userid)", array('appUserPreferences.friendlyname' => true), $aArgs), - "SELECT c 136" => array("SELECT `c` FROM CustomerContract AS `c` WHERE (`c`.`end_date` < '2018-12-01')", array('c.friendlyname' => true), $aArgs), - "SELECT c 137" => array("SELECT `c` FROM ProviderContract AS `c` WHERE (`c`.`end_date` < '2018-12-01')", array('c.friendlyname' => true), $aArgs), - "SELECT datasource 138" => array("SELECT `datasource` FROM SynchroDataSource AS `datasource` WHERE 1", array('datasource.friendlyname' => true), $aArgs), - "SELECT i 139" => array("SELECT `i` FROM UserRequest AS `i` WHERE ((`i`.`agent_id` = :current_contact_id) AND (`i`.`status` NOT IN ('closed', 'resolved')))", array('i.friendlyname' => true), $aArgs), - "SELECT s 140" => array("SELECT `s` FROM Service AS `s` JOIN lnkCustomerContractToService AS `l1` ON `l1`.service_id = `s`.id JOIN CustomerContract AS `cc` ON `l1`.customercontract_id = `cc`.id WHERE (((`cc`.`org_id` = :this->org_id) AND (`s`.`status` != 'obsolete')) AND (`s`.`friendlyname` LIKE '%%'))", array('s.friendlyname' => true), $aArgs), - "SELECT t 141" => array("SELECT `t` FROM Team AS `t` JOIN lnkDeliveryModelToContact AS `l1` ON `l1`.contact_id = `t`.id JOIN DeliveryModel AS `dm` ON `l1`.deliverymodel_id = `dm`.id JOIN Organization AS `o` ON `o`.deliverymodel_id = `dm`.id WHERE ((`o`.`id` = :this->org_id) AND (`t`.`friendlyname` LIKE '%%'))", array('t.friendlyname' => true), $aArgs), - "SELECT t 142" => array("SELECT `t` FROM TriggerOnObjectCreate AS `t` WHERE (`t`.`target_class` IN ('appUserPreferences'))", array('t.friendlyname' => true), $aArgs), - "SELECT t 143" => array("SELECT `t` FROM TriggerOnObjectUpdate AS `t` WHERE (`t`.`target_class` IN ('appUserPreferences'))", array('t.friendlyname' => true), $aArgs), - ); - - $aData["SELECT UNION 1"] = array("SELECT `User` FROM User AS `User` WHERE 1 UNION SELECT `User` FROM User AS `User` WHERE (`User`.`id` = 3)", array(), array(), null, array(), 0, 0); - $aData["SELECT 1"] = array("SELECT `UserInternal` FROM UserInternal AS `UserInternal` WHERE ((`UserInternal`.`login` = 'admin') AND (`UserInternal`.`status` = 'enabled'))", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 2"] = array("SELECT `Contact` FROM Contact AS `Contact` WHERE (`Contact`.`id` = '987654321')", array(), array(), null, array(), 0, 0); - $aData["SELECT 3"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", array(), array(), null, array(), 4, 0); - $aData["SELECT 4"] = array("SELECT `appUserPreferences` FROM appUserPreferences AS `appUserPreferences` WHERE (`appUserPreferences`.`userid` = '1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 5"] = array("SELECT `Shortcut` FROM Shortcut AS `Shortcut` WHERE (`Shortcut`.`user_id` = '1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 6"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '1') AND (`UserDashboard`.`menu_code` = 'WelcomeMenuPage'))", array(), array(), null, array(), 0, 0); - $aData["SELECT 7"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '1') AND (`UserDashboard`.`menu_code` = 'WelcomeMenuPage'))", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 8"] = array("SELECT `BusinessProcess` FROM BusinessProcess AS `BusinessProcess` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 9"] = array("SELECT `ApplicationSolution` FROM ApplicationSolution AS `ApplicationSolution` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 10"] = array("SELECT `Contact` FROM Contact AS `Contact` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 11"] = array("SELECT `Location` FROM Location AS `Location` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 12"] = array("SELECT `Contract` FROM Contract AS `Contract` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 13"] = array("SELECT `Server` FROM Server AS `Server` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 14"] = array("SELECT `NetworkDevice` FROM NetworkDevice AS `NetworkDevice` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 15"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` != 'closed')", array(), array(), null, array(), 0, 0); - $aData["SELECT 16"] = array("SELECT `i` FROM UserRequest AS `i` WHERE ((`i`.`agent_id` = 1) AND (`i`.`status` NOT IN ('closed', 'resolved')))", array(), array(), null, array(), 3, 0); - $aData["SELECT 17"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 18"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", array(), array(), null, array(), 52, 0); - $aData["SELECT 19"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = '')", array(), array(), null, array(), 0, 0); - $aData["SELECT 20"] = array("SELECT `DBProperty` FROM DBProperty AS `DBProperty` WHERE (`DBProperty`.`name` = 'database_uuid')", array(), array(), null, array(), 0, 0); - $aData["SELECT 21"] = array("SELECT `DBProperty` FROM DBProperty AS `DBProperty` WHERE (`DBProperty`.`name` = 'database_uuid')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 22"] = array("SELECT `WebServer` FROM WebServer AS `WebServer` WHERE 1", array(), array(), unserialize('a:1:{s:9:"WebServer";a:6:{i:0;s:18:"business_criticity";i:1;s:11:"description";i:2;s:4:"name";i:3;s:12:"friendlyname";i:4;s:17:"obsolescence_flag";i:5;s:10:"finalclass";}}'), array(), 0, 0); - $aData["SELECT 23"] = array("SELECT `WebServer` FROM WebServer AS `WebServer` WHERE 1", array(), array(), unserialize('a:1:{s:9:"WebServer";a:6:{i:0;s:18:"business_criticity";i:1;s:11:"description";i:2;s:4:"name";i:3;s:12:"friendlyname";i:4;s:17:"obsolescence_flag";i:5;s:10:"finalclass";}}'), array(), 0, 3); - $aData["SELECT 24"] = array("SELECT `WebServer` FROM WebServer AS `WebServer` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 25"] = array("SELECT `WebServer` FROM WebServer AS `WebServer` WHERE 1", array(), array(), null, array(), 0, 3); - $aData["SELECT 26"] = array("SELECT `L` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE 1", unserialize('a:1:{s:14:"L.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 27"] = array("SELECT `L` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE 1", unserialize('a:1:{s:14:"L.friendlyname";b:1;}'), array(), null, array(), 3, 0); - $aData["SELECT 28"] = array("SELECT `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 29"] = array("SELECT `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, array(), 3, 0); - $aData["SELECT 30"] = array("SELECT `L`, `P` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 31"] = array("SELECT `L`, `P` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, array(), 3, 0); - $aData["SELECT 32"] = array("SELECT `P`, `L` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 33"] = array("SELECT `P`, `L` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, array(), 3, 0); - $aData["SELECT 34"] = array("SELECT `L`, `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 35"] = array("SELECT `L`, `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, array(), 3, 0); - $aData["SELECT 36"] = array("SELECT `L` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", unserialize('a:1:{s:14:"L.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 37"] = array("SELECT `L` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", unserialize('a:1:{s:14:"L.friendlyname";b:1;}'), array(), null, array(), 3, 0); - $aData["SELECT 38"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` JOIN Organization AS `Organization` ON `UserRequest`.org_id = `Organization`.id JOIN Organization AS `Organization1` ON `Organization`.parent_id BELOW `Organization1`.id WHERE (`Organization1`.`id` = '3')", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 39"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` JOIN Organization AS `Organization` ON `UserRequest`.org_id = `Organization`.id JOIN Organization AS `Organization1` ON `Organization`.parent_id BELOW `Organization1`.id WHERE (`Organization1`.`id` = '3')", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 40"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (('2018-12-01' < `UserRequest`.`start_date`) AND (ISNULL(DATE_FORMAT(`UserRequest`.`start_date`, '%Y-%m-%d')) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 41"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (('2018-12-01' < `UserRequest`.`start_date`) AND (ISNULL(DATE_FORMAT(`UserRequest`.`start_date`, '%Y-%m-%d')) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 42"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`agent_id` = '') AND (`UserRequest`.`status` NOT IN ('closed', 'resolved')))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 43"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`agent_id` = '') AND (`UserRequest`.`status` NOT IN ('closed', 'resolved')))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 44"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`caller_id` = '') AND (`UserRequest`.`status` NOT IN ('closed')))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 45"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`caller_id` = '') AND (`UserRequest`.`status` NOT IN ('closed')))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 46"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` IN ('escalated_tto', 'escalated_ttr')) OR (`UserRequest`.`escalation_flag` = 'yes'))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 47"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` IN ('escalated_tto', 'escalated_ttr')) OR (`UserRequest`.`escalation_flag` = 'yes'))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 48"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`agent_id`) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 49"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`agent_id`) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 50"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`finalclass`) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 51"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`finalclass`) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 52"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`org_id`) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 53"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`org_id`) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 54"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`status`) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 55"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`status`) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 56"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (ISNULL(`UserRequest`.`org_id`) != 1)", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 57"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (ISNULL(`UserRequest`.`org_id`) != 1)", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 58"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` != 'closed')", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 59"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` != 'closed')", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 60"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` NOT IN ('closed'))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 61"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` NOT IN ('closed'))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 62"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE 1", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 63"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE 1", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 64"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`id` = '987654321')", array(), array(), null, array(), 0, 0); - $aData["SELECT 65"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`id` = '987654321')", array(), array(), null, array(), 0, 0); - $aData["SELECT 66"] = array("SELECT `ApplicationSolution` FROM ApplicationSolution AS `ApplicationSolution` WHERE 1", unserialize('a:1:{s:32:"ApplicationSolution.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 67"] = array("SELECT `ApplicationSolution` FROM ApplicationSolution AS `ApplicationSolution` WHERE 1", unserialize('a:1:{s:32:"ApplicationSolution.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 68"] = array("SELECT `AuditCategory` FROM AuditCategory AS `AuditCategory` WHERE 1", unserialize('a:1:{s:26:"AuditCategory.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 69"] = array("SELECT `AuditCategory` FROM AuditCategory AS `AuditCategory` WHERE 1", unserialize('a:1:{s:26:"AuditCategory.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 70"] = array("SELECT `Brand` FROM Brand AS `Brand` WHERE (`Brand`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:18:"Brand.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 71"] = array("SELECT `Brand` FROM Brand AS `Brand` WHERE (`Brand`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:18:"Brand.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 72"] = array("SELECT `Brand` FROM Brand AS `Brand` WHERE 1", unserialize('a:1:{s:18:"Brand.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 73"] = array("SELECT `Brand` FROM Brand AS `Brand` WHERE 1", unserialize('a:1:{s:18:"Brand.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 74"] = array("SELECT `BusinessProcess` FROM BusinessProcess AS `BusinessProcess` WHERE 1", unserialize('a:1:{s:28:"BusinessProcess.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 75"] = array("SELECT `BusinessProcess` FROM BusinessProcess AS `BusinessProcess` WHERE 1", unserialize('a:1:{s:28:"BusinessProcess.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 76"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`agent_id` = '') AND (`Change`.`status` NOT IN ('closed')))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 77"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`agent_id` = '') AND (`Change`.`status` NOT IN ('closed')))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 78"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(DATE_FORMAT(`Change`.`start_date`, '%Y-%m-%d')) != 1))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 79"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(DATE_FORMAT(`Change`.`start_date`, '%Y-%m-%d')) != 1))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 80"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(`Change`.`category`) != 1))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 81"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(`Change`.`category`) != 1))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 82"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(`Change`.`finalclass`) != 1))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 83"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(`Change`.`finalclass`) != 1))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 84"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(`Change`.`status`) != 1))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 85"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(`Change`.`status`) != 1))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 86"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`id` != '3') AND (`Change`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 87"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`id` != '3') AND (`Change`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 88"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`status` != 'closed') AND (`Change`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 89"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`status` != 'closed') AND (`Change`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 90"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 91"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 92"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`status` != 'closed')", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 93"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`status` != 'closed')", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 94"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`status` IN ('planned'))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 95"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`status` IN ('planned'))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 96"] = array("SELECT `Change` FROM Change AS `Change` WHERE 1", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 97"] = array("SELECT `Change` FROM Change AS `Change` WHERE 1", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 98"] = array("SELECT `ContactType` FROM ContactType AS `ContactType` WHERE 1", unserialize('a:1:{s:24:"ContactType.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 99"] = array("SELECT `ContactType` FROM ContactType AS `ContactType` WHERE 1", unserialize('a:1:{s:24:"ContactType.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 100"] = array("SELECT `Contact` FROM Contact AS `Contact` WHERE (`Contact`.`id` = '3')", unserialize('a:1:{s:20:"Contact.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 101"] = array("SELECT `Contact` FROM Contact AS `Contact` WHERE (`Contact`.`id` = '3')", unserialize('a:1:{s:20:"Contact.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 102"] = array("SELECT `Contact` FROM Contact AS `Contact` WHERE 1", unserialize('a:1:{s:20:"Contact.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 103"] = array("SELECT `Contact` FROM Contact AS `Contact` WHERE 1", unserialize('a:1:{s:20:"Contact.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 104"] = array("SELECT `ContractType` FROM ContractType AS `ContractType` WHERE (`ContractType`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:25:"ContractType.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 105"] = array("SELECT `ContractType` FROM ContractType AS `ContractType` WHERE (`ContractType`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:25:"ContractType.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 106"] = array("SELECT `ContractType` FROM ContractType AS `ContractType` WHERE 1", unserialize('a:1:{s:25:"ContractType.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 107"] = array("SELECT `ContractType` FROM ContractType AS `ContractType` WHERE 1", unserialize('a:1:{s:25:"ContractType.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 108"] = array("SELECT `Contract` FROM Contract AS `Contract` WHERE 1", unserialize('a:1:{s:21:"Contract.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 109"] = array("SELECT `Contract` FROM Contract AS `Contract` WHERE 1", unserialize('a:1:{s:21:"Contract.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 110"] = array("SELECT `CustomerContract` FROM CustomerContract AS `CustomerContract` WHERE 1", unserialize('a:1:{s:29:"CustomerContract.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 111"] = array("SELECT `CustomerContract` FROM CustomerContract AS `CustomerContract` WHERE 1", unserialize('a:1:{s:29:"CustomerContract.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 112"] = array("SELECT `DBProperty` FROM DBProperty AS `DBProperty` WHERE (`DBProperty`.`name` = 'database_uuid')", unserialize('a:1:{s:23:"DBProperty.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 113"] = array("SELECT `DBProperty` FROM DBProperty AS `DBProperty` WHERE (`DBProperty`.`name` = 'database_uuid')", unserialize('a:1:{s:23:"DBProperty.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 114"] = array("SELECT `DBServer` FROM DBServer AS `DBServer` WHERE 1", unserialize('a:1:{s:21:"DBServer.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 115"] = array("SELECT `DBServer` FROM DBServer AS `DBServer` WHERE 1", unserialize('a:1:{s:21:"DBServer.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 116"] = array("SELECT `DatabaseSchema` FROM DatabaseSchema AS `DatabaseSchema` WHERE 1", unserialize('a:1:{s:27:"DatabaseSchema.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 117"] = array("SELECT `DatabaseSchema` FROM DatabaseSchema AS `DatabaseSchema` WHERE 1", unserialize('a:1:{s:27:"DatabaseSchema.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 118"] = array("SELECT `DeliveryModel` FROM DeliveryModel AS `DeliveryModel` WHERE (`DeliveryModel`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:26:"DeliveryModel.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 119"] = array("SELECT `DeliveryModel` FROM DeliveryModel AS `DeliveryModel` WHERE (`DeliveryModel`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:26:"DeliveryModel.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 120"] = array("SELECT `DeliveryModel` FROM DeliveryModel AS `DeliveryModel` WHERE 1", unserialize('a:1:{s:26:"DeliveryModel.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 121"] = array("SELECT `DeliveryModel` FROM DeliveryModel AS `DeliveryModel` WHERE 1", unserialize('a:1:{s:26:"DeliveryModel.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 122"] = array("SELECT `DocumentType` FROM DocumentType AS `DocumentType` WHERE (`DocumentType`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:25:"DocumentType.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 123"] = array("SELECT `DocumentType` FROM DocumentType AS `DocumentType` WHERE (`DocumentType`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:25:"DocumentType.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 124"] = array("SELECT `DocumentType` FROM DocumentType AS `DocumentType` WHERE 1", unserialize('a:1:{s:25:"DocumentType.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 125"] = array("SELECT `DocumentType` FROM DocumentType AS `DocumentType` WHERE 1", unserialize('a:1:{s:25:"DocumentType.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 126"] = array("SELECT `Document` FROM Document AS `Document` WHERE 1", unserialize('a:1:{s:21:"Document.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 127"] = array("SELECT `Document` FROM Document AS `Document` WHERE 1", unserialize('a:1:{s:21:"Document.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 128"] = array("SELECT `Enclosure` FROM Enclosure AS `Enclosure` WHERE ((`Enclosure`.`rack_id` = '3') AND (`Enclosure`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:22:"Enclosure.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 129"] = array("SELECT `Enclosure` FROM Enclosure AS `Enclosure` WHERE ((`Enclosure`.`rack_id` = '3') AND (`Enclosure`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:22:"Enclosure.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 130"] = array("SELECT `Enclosure` FROM Enclosure AS `Enclosure` WHERE (`Enclosure`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:22:"Enclosure.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 131"] = array("SELECT `Enclosure` FROM Enclosure AS `Enclosure` WHERE (`Enclosure`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:22:"Enclosure.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 132"] = array("SELECT `Enclosure` FROM Enclosure AS `Enclosure` WHERE 1", unserialize('a:1:{s:22:"Enclosure.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 133"] = array("SELECT `Enclosure` FROM Enclosure AS `Enclosure` WHERE 1", unserialize('a:1:{s:22:"Enclosure.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 134"] = array("SELECT `Farm` FROM Farm AS `Farm` WHERE 1", unserialize('a:1:{s:17:"Farm.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 135"] = array("SELECT `Farm` FROM Farm AS `Farm` WHERE 1", unserialize('a:1:{s:17:"Farm.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 136"] = array("SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE 1", unserialize('a:1:{s:25:"FunctionalCI.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 137"] = array("SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE 1", unserialize('a:1:{s:25:"FunctionalCI.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 138"] = array("SELECT `Group` FROM Group AS `Group` WHERE (`Group`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:18:"Group.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 139"] = array("SELECT `Group` FROM Group AS `Group` WHERE (`Group`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:18:"Group.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 140"] = array("SELECT `Group` FROM Group AS `Group` WHERE 1", unserialize('a:1:{s:18:"Group.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 141"] = array("SELECT `Group` FROM Group AS `Group` WHERE 1", unserialize('a:1:{s:18:"Group.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 142"] = array("SELECT `Hypervisor` FROM Hypervisor AS `Hypervisor` WHERE 1", unserialize('a:1:{s:23:"Hypervisor.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 143"] = array("SELECT `Hypervisor` FROM Hypervisor AS `Hypervisor` WHERE 1", unserialize('a:1:{s:23:"Hypervisor.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 144"] = array("SELECT `IOSVersion` FROM IOSVersion AS `IOSVersion` WHERE 1", unserialize('a:1:{s:23:"IOSVersion.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 145"] = array("SELECT `IOSVersion` FROM IOSVersion AS `IOSVersion` WHERE 1", unserialize('a:1:{s:23:"IOSVersion.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 146"] = array("SELECT `IPPhone` FROM IPPhone AS `IPPhone` WHERE 1", unserialize('a:1:{s:20:"IPPhone.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 147"] = array("SELECT `IPPhone` FROM IPPhone AS `IPPhone` WHERE 1", unserialize('a:1:{s:20:"IPPhone.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 148"] = array("SELECT `Licence` FROM Licence AS `Licence` WHERE 1", unserialize('a:1:{s:20:"Licence.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 149"] = array("SELECT `Licence` FROM Licence AS `Licence` WHERE 1", unserialize('a:1:{s:20:"Licence.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 150"] = array("SELECT `Location` FROM Location AS `Location` WHERE 1", unserialize('a:1:{s:21:"Location.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 151"] = array("SELECT `Location` FROM Location AS `Location` WHERE 1", unserialize('a:1:{s:21:"Location.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 152"] = array("SELECT `LogicalVolume` FROM LogicalVolume AS `LogicalVolume` WHERE 1", unserialize('a:1:{s:26:"LogicalVolume.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 153"] = array("SELECT `LogicalVolume` FROM LogicalVolume AS `LogicalVolume` WHERE 1", unserialize('a:1:{s:26:"LogicalVolume.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 154"] = array("SELECT `MiddlewareInstance` FROM MiddlewareInstance AS `MiddlewareInstance` WHERE 1", unserialize('a:1:{s:31:"MiddlewareInstance.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 155"] = array("SELECT `MiddlewareInstance` FROM MiddlewareInstance AS `MiddlewareInstance` WHERE 1", unserialize('a:1:{s:31:"MiddlewareInstance.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 156"] = array("SELECT `Middleware` FROM Middleware AS `Middleware` WHERE 1", unserialize('a:1:{s:23:"Middleware.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 157"] = array("SELECT `Middleware` FROM Middleware AS `Middleware` WHERE 1", unserialize('a:1:{s:23:"Middleware.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 158"] = array("SELECT `MobilePhone` FROM MobilePhone AS `MobilePhone` WHERE 1", unserialize('a:1:{s:24:"MobilePhone.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 159"] = array("SELECT `MobilePhone` FROM MobilePhone AS `MobilePhone` WHERE 1", unserialize('a:1:{s:24:"MobilePhone.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 160"] = array("SELECT `Model` FROM Model AS `Model` WHERE (((`Model`.`brand_id` = '1') AND (`Model`.`type` = 'NetworkDevice')) AND (`Model`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:18:"Model.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 161"] = array("SELECT `Model` FROM Model AS `Model` WHERE (((`Model`.`brand_id` = '1') AND (`Model`.`type` = 'NetworkDevice')) AND (`Model`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:18:"Model.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 162"] = array("SELECT `Model` FROM Model AS `Model` WHERE (`Model`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:18:"Model.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 163"] = array("SELECT `Model` FROM Model AS `Model` WHERE (`Model`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:18:"Model.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 164"] = array("SELECT `Model` FROM Model AS `Model` WHERE 1", unserialize('a:1:{s:18:"Model.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 165"] = array("SELECT `Model` FROM Model AS `Model` WHERE 1", unserialize('a:1:{s:18:"Model.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 166"] = array("SELECT `NAS` FROM NAS AS `NAS` WHERE 1", unserialize('a:1:{s:16:"NAS.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 167"] = array("SELECT `NAS` FROM NAS AS `NAS` WHERE 1", unserialize('a:1:{s:16:"NAS.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 168"] = array("SELECT `NetworkDeviceType` FROM NetworkDeviceType AS `NetworkDeviceType` WHERE 1", unserialize('a:1:{s:30:"NetworkDeviceType.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 169"] = array("SELECT `NetworkDeviceType` FROM NetworkDeviceType AS `NetworkDeviceType` WHERE 1", unserialize('a:1:{s:30:"NetworkDeviceType.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 170"] = array("SELECT `NetworkDevice` FROM NetworkDevice AS `NetworkDevice` WHERE 1", unserialize('a:1:{s:26:"NetworkDevice.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 171"] = array("SELECT `NetworkDevice` FROM NetworkDevice AS `NetworkDevice` WHERE 1", unserialize('a:1:{s:26:"NetworkDevice.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 172"] = array("SELECT `NetworkInterface` FROM NetworkInterface AS `NetworkInterface` WHERE 1", unserialize('a:1:{s:29:"NetworkInterface.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 173"] = array("SELECT `NetworkInterface` FROM NetworkInterface AS `NetworkInterface` WHERE 1", unserialize('a:1:{s:29:"NetworkInterface.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 174"] = array("SELECT `OSFamily` FROM OSFamily AS `OSFamily` WHERE (`OSFamily`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:21:"OSFamily.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 175"] = array("SELECT `OSFamily` FROM OSFamily AS `OSFamily` WHERE (`OSFamily`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:21:"OSFamily.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 176"] = array("SELECT `OSFamily` FROM OSFamily AS `OSFamily` WHERE 1", unserialize('a:1:{s:21:"OSFamily.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 177"] = array("SELECT `OSFamily` FROM OSFamily AS `OSFamily` WHERE 1", unserialize('a:1:{s:21:"OSFamily.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 178"] = array("SELECT `OSLicence` FROM OSLicence AS `OSLicence` WHERE ((`OSLicence`.`osversion_id` = '8') AND (`OSLicence`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:22:"OSLicence.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 179"] = array("SELECT `OSLicence` FROM OSLicence AS `OSLicence` WHERE ((`OSLicence`.`osversion_id` = '8') AND (`OSLicence`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:22:"OSLicence.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 180"] = array("SELECT `OSLicence` FROM OSLicence AS `OSLicence` WHERE (`OSLicence`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:22:"OSLicence.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 181"] = array("SELECT `OSLicence` FROM OSLicence AS `OSLicence` WHERE (`OSLicence`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:22:"OSLicence.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 182"] = array("SELECT `OSLicence` FROM OSLicence AS `OSLicence` WHERE 1", unserialize('a:1:{s:22:"OSLicence.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 183"] = array("SELECT `OSLicence` FROM OSLicence AS `OSLicence` WHERE 1", unserialize('a:1:{s:22:"OSLicence.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 184"] = array("SELECT `OSVersion` FROM OSVersion AS `OSVersion` WHERE ((`OSVersion`.`osfamily_id` = '6') AND (`OSVersion`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:22:"OSVersion.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 185"] = array("SELECT `OSVersion` FROM OSVersion AS `OSVersion` WHERE ((`OSVersion`.`osfamily_id` = '6') AND (`OSVersion`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:22:"OSVersion.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 186"] = array("SELECT `OSVersion` FROM OSVersion AS `OSVersion` WHERE (`OSVersion`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:22:"OSVersion.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 187"] = array("SELECT `OSVersion` FROM OSVersion AS `OSVersion` WHERE (`OSVersion`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:22:"OSVersion.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 188"] = array("SELECT `OSVersion` FROM OSVersion AS `OSVersion` WHERE 1", unserialize('a:1:{s:22:"OSVersion.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 189"] = array("SELECT `OSVersion` FROM OSVersion AS `OSVersion` WHERE 1", unserialize('a:1:{s:22:"OSVersion.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 190"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = '3')", unserialize('a:1:{s:25:"Organization.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 191"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = '3')", unserialize('a:1:{s:25:"Organization.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 192"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", unserialize('a:1:{s:25:"Organization.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 193"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", unserialize('a:1:{s:25:"Organization.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 194"] = array("SELECT `OtherSoftware` FROM OtherSoftware AS `OtherSoftware` WHERE 1", unserialize('a:1:{s:26:"OtherSoftware.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 195"] = array("SELECT `OtherSoftware` FROM OtherSoftware AS `OtherSoftware` WHERE 1", unserialize('a:1:{s:26:"OtherSoftware.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 196"] = array("SELECT `PCSoftware` FROM PCSoftware AS `PCSoftware` WHERE 1", unserialize('a:1:{s:23:"PCSoftware.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 197"] = array("SELECT `PCSoftware` FROM PCSoftware AS `PCSoftware` WHERE 1", unserialize('a:1:{s:23:"PCSoftware.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 198"] = array("SELECT `PC` FROM PC AS `PC` WHERE 1", unserialize('a:1:{s:15:"PC.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 199"] = array("SELECT `PC` FROM PC AS `PC` WHERE 1", unserialize('a:1:{s:15:"PC.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 200"] = array("SELECT `Patch` FROM Patch AS `Patch` WHERE 1", unserialize('a:1:{s:18:"Patch.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 201"] = array("SELECT `Patch` FROM Patch AS `Patch` WHERE 1", unserialize('a:1:{s:18:"Patch.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 202"] = array("SELECT `Peripheral` FROM Peripheral AS `Peripheral` WHERE 1", unserialize('a:1:{s:23:"Peripheral.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 203"] = array("SELECT `Peripheral` FROM Peripheral AS `Peripheral` WHERE 1", unserialize('a:1:{s:23:"Peripheral.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 204"] = array("SELECT `Person` FROM Person AS `Person` WHERE ((`Person`.`org_id` = 1) AND (ISNULL(`Person`.`org_id`) != 1))", unserialize('a:1:{s:19:"Person.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 205"] = array("SELECT `Person` FROM Person AS `Person` WHERE ((`Person`.`org_id` = 1) AND (ISNULL(`Person`.`org_id`) != 1))", unserialize('a:1:{s:19:"Person.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 206"] = array("SELECT `Person` FROM Person AS `Person` WHERE ((`Person`.`org_id` = 2) AND (ISNULL(`Person`.`org_id`) != 1))", unserialize('a:1:{s:19:"Person.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 207"] = array("SELECT `Person` FROM Person AS `Person` WHERE ((`Person`.`org_id` = 2) AND (ISNULL(`Person`.`org_id`) != 1))", unserialize('a:1:{s:19:"Person.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 208"] = array("SELECT `Person` FROM Person AS `Person` WHERE (`Person`.`id` = '3')", unserialize('a:1:{s:19:"Person.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 209"] = array("SELECT `Person` FROM Person AS `Person` WHERE (`Person`.`id` = '3')", unserialize('a:1:{s:19:"Person.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 210"] = array("SELECT `Person` FROM Person AS `Person` WHERE 1", unserialize('a:1:{s:19:"Person.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 211"] = array("SELECT `Person` FROM Person AS `Person` WHERE 1", unserialize('a:1:{s:19:"Person.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 212"] = array("SELECT `p` FROM Person AS `p` JOIN UserRequest AS `u` ON `u`.agent_id = `p`.id WHERE (`u`.`status` != 'closed')", unserialize('a:1:{s:14:"p.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 213"] = array("SELECT `p` FROM Person AS `p` JOIN UserRequest AS `u` ON `u`.agent_id = `p`.id WHERE (`u`.`status` != 'closed')", unserialize('a:1:{s:14:"p.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 214"] = array("SELECT `Phone` FROM Phone AS `Phone` WHERE 1", unserialize('a:1:{s:18:"Phone.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 215"] = array("SELECT `Phone` FROM Phone AS `Phone` WHERE 1", unserialize('a:1:{s:18:"Phone.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 216"] = array("SELECT `PowerConnection` FROM PowerConnection AS `PowerConnection` WHERE ((`PowerConnection`.`location_id` = '2') AND (`PowerConnection`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:28:"PowerConnection.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 217"] = array("SELECT `PowerConnection` FROM PowerConnection AS `PowerConnection` WHERE ((`PowerConnection`.`location_id` = '2') AND (`PowerConnection`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:28:"PowerConnection.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 218"] = array("SELECT `PowerConnection` FROM PowerConnection AS `PowerConnection` WHERE (`PowerConnection`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:28:"PowerConnection.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 219"] = array("SELECT `PowerConnection` FROM PowerConnection AS `PowerConnection` WHERE (`PowerConnection`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:28:"PowerConnection.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 220"] = array("SELECT `PowerConnection` FROM PowerConnection AS `PowerConnection` WHERE 1", unserialize('a:1:{s:28:"PowerConnection.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 221"] = array("SELECT `PowerConnection` FROM PowerConnection AS `PowerConnection` WHERE 1", unserialize('a:1:{s:28:"PowerConnection.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 222"] = array("SELECT `Printer` FROM Printer AS `Printer` WHERE 1", unserialize('a:1:{s:20:"Printer.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 223"] = array("SELECT `Printer` FROM Printer AS `Printer` WHERE 1", unserialize('a:1:{s:20:"Printer.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 224"] = array("SELECT `ProviderContract` FROM ProviderContract AS `ProviderContract` WHERE 1", unserialize('a:1:{s:29:"ProviderContract.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 225"] = array("SELECT `ProviderContract` FROM ProviderContract AS `ProviderContract` WHERE 1", unserialize('a:1:{s:29:"ProviderContract.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 226"] = array("SELECT `Query` FROM Query AS `Query` WHERE 1", unserialize('a:1:{s:18:"Query.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 227"] = array("SELECT `Query` FROM Query AS `Query` WHERE 1", unserialize('a:1:{s:18:"Query.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 228"] = array("SELECT `Rack` FROM Rack AS `Rack` WHERE ((`Rack`.`location_id` = '2') AND (`Rack`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:17:"Rack.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 229"] = array("SELECT `Rack` FROM Rack AS `Rack` WHERE ((`Rack`.`location_id` = '2') AND (`Rack`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:17:"Rack.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 230"] = array("SELECT `Rack` FROM Rack AS `Rack` WHERE (`Rack`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:17:"Rack.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 231"] = array("SELECT `Rack` FROM Rack AS `Rack` WHERE (`Rack`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:17:"Rack.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 232"] = array("SELECT `Rack` FROM Rack AS `Rack` WHERE 1", unserialize('a:1:{s:17:"Rack.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 233"] = array("SELECT `Rack` FROM Rack AS `Rack` WHERE 1", unserialize('a:1:{s:17:"Rack.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 234"] = array("SELECT `SANSwitch` FROM SANSwitch AS `SANSwitch` WHERE 1", unserialize('a:1:{s:22:"SANSwitch.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 235"] = array("SELECT `SANSwitch` FROM SANSwitch AS `SANSwitch` WHERE 1", unserialize('a:1:{s:22:"SANSwitch.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 236"] = array("SELECT `SLA` FROM SLA AS `SLA` WHERE 1", unserialize('a:1:{s:16:"SLA.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 237"] = array("SELECT `SLA` FROM SLA AS `SLA` WHERE 1", unserialize('a:1:{s:16:"SLA.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 238"] = array("SELECT `SLT` FROM SLT AS `SLT` WHERE 1", unserialize('a:1:{s:16:"SLT.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 239"] = array("SELECT `SLT` FROM SLT AS `SLT` WHERE 1", unserialize('a:1:{s:16:"SLT.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 240"] = array("SELECT `Server` FROM Server AS `Server` WHERE (`Server`.`name` NOT LIKE '%2')", unserialize('a:1:{s:19:"Server.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 241"] = array("SELECT `Server` FROM Server AS `Server` WHERE (`Server`.`name` NOT LIKE '%2')", unserialize('a:1:{s:19:"Server.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 242"] = array("SELECT `Server` FROM Server AS `Server` WHERE 1", unserialize('a:1:{s:19:"Server.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 243"] = array("SELECT `Server` FROM Server AS `Server` WHERE 1", unserialize('a:1:{s:19:"Server.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 244"] = array("SELECT `ServiceFamily` FROM ServiceFamily AS `ServiceFamily` WHERE (`ServiceFamily`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:26:"ServiceFamily.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 245"] = array("SELECT `ServiceFamily` FROM ServiceFamily AS `ServiceFamily` WHERE (`ServiceFamily`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:26:"ServiceFamily.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 246"] = array("SELECT `ServiceFamily` FROM ServiceFamily AS `ServiceFamily` WHERE 1", unserialize('a:1:{s:26:"ServiceFamily.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 247"] = array("SELECT `ServiceFamily` FROM ServiceFamily AS `ServiceFamily` WHERE 1", unserialize('a:1:{s:26:"ServiceFamily.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 248"] = array("SELECT `ServiceSubcategory` FROM ServiceSubcategory AS `ServiceSubcategory` WHERE ((((`ServiceSubcategory`.`service_id` = '1') AND (ISNULL('incident') OR (`ServiceSubcategory`.`request_type` = 'incident'))) AND (`ServiceSubcategory`.`status` != 'obsolete')) AND (`ServiceSubcategory`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:31:"ServiceSubcategory.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 249"] = array("SELECT `ServiceSubcategory` FROM ServiceSubcategory AS `ServiceSubcategory` WHERE ((((`ServiceSubcategory`.`service_id` = '1') AND (ISNULL('incident') OR (`ServiceSubcategory`.`request_type` = 'incident'))) AND (`ServiceSubcategory`.`status` != 'obsolete')) AND (`ServiceSubcategory`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:31:"ServiceSubcategory.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 250"] = array("SELECT `ServiceSubcategory` FROM ServiceSubcategory AS `ServiceSubcategory` WHERE (`ServiceSubcategory`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:31:"ServiceSubcategory.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 251"] = array("SELECT `ServiceSubcategory` FROM ServiceSubcategory AS `ServiceSubcategory` WHERE (`ServiceSubcategory`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:31:"ServiceSubcategory.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 252"] = array("SELECT `ServiceSubcategory` FROM ServiceSubcategory AS `ServiceSubcategory` WHERE 1", unserialize('a:1:{s:31:"ServiceSubcategory.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 253"] = array("SELECT `ServiceSubcategory` FROM ServiceSubcategory AS `ServiceSubcategory` WHERE 1", unserialize('a:1:{s:31:"ServiceSubcategory.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 254"] = array("SELECT `Service` FROM Service AS `Service` WHERE (`Service`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:20:"Service.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 255"] = array("SELECT `Service` FROM Service AS `Service` WHERE (`Service`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:20:"Service.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 256"] = array("SELECT `Service` FROM Service AS `Service` WHERE 1", unserialize('a:1:{s:20:"Service.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 257"] = array("SELECT `Service` FROM Service AS `Service` WHERE 1", unserialize('a:1:{s:20:"Service.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 258"] = array("SELECT `ShortcutOQL` FROM ShortcutOQL AS `ShortcutOQL` WHERE (`ShortcutOQL`.`id` = '3')", unserialize('a:1:{s:24:"ShortcutOQL.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 259"] = array("SELECT `ShortcutOQL` FROM ShortcutOQL AS `ShortcutOQL` WHERE (`ShortcutOQL`.`id` = '3')", unserialize('a:1:{s:24:"ShortcutOQL.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 260"] = array("SELECT `Shortcut` FROM Shortcut AS `Shortcut` WHERE (`Shortcut`.`user_id` = '5')", unserialize('a:1:{s:21:"Shortcut.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 261"] = array("SELECT `Shortcut` FROM Shortcut AS `Shortcut` WHERE (`Shortcut`.`user_id` = '5')", unserialize('a:1:{s:21:"Shortcut.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 262"] = array("SELECT `Software` FROM Software AS `Software` WHERE 1", unserialize('a:1:{s:21:"Software.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 263"] = array("SELECT `Software` FROM Software AS `Software` WHERE 1", unserialize('a:1:{s:21:"Software.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 264"] = array("SELECT `StorageSystem` FROM StorageSystem AS `StorageSystem` WHERE 1", unserialize('a:1:{s:26:"StorageSystem.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 265"] = array("SELECT `StorageSystem` FROM StorageSystem AS `StorageSystem` WHERE 1", unserialize('a:1:{s:26:"StorageSystem.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 266"] = array("SELECT `Subnet` FROM Subnet AS `Subnet` WHERE 1", unserialize('a:1:{s:19:"Subnet.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 267"] = array("SELECT `Subnet` FROM Subnet AS `Subnet` WHERE 1", unserialize('a:1:{s:19:"Subnet.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 268"] = array("SELECT `SynchroDataSource` FROM SynchroDataSource AS `SynchroDataSource` WHERE 1", unserialize('a:1:{s:30:"SynchroDataSource.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 269"] = array("SELECT `SynchroDataSource` FROM SynchroDataSource AS `SynchroDataSource` WHERE 1", unserialize('a:1:{s:30:"SynchroDataSource.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 270"] = array("SELECT `Tablet` FROM Tablet AS `Tablet` WHERE 1", unserialize('a:1:{s:19:"Tablet.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 271"] = array("SELECT `Tablet` FROM Tablet AS `Tablet` WHERE 1", unserialize('a:1:{s:19:"Tablet.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 272"] = array("SELECT `TapeLibrary` FROM TapeLibrary AS `TapeLibrary` WHERE 1", unserialize('a:1:{s:24:"TapeLibrary.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 273"] = array("SELECT `TapeLibrary` FROM TapeLibrary AS `TapeLibrary` WHERE 1", unserialize('a:1:{s:24:"TapeLibrary.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 274"] = array("SELECT `Team` FROM Team AS `Team` WHERE (`Team`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:17:"Team.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 275"] = array("SELECT `Team` FROM Team AS `Team` WHERE (`Team`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:17:"Team.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 276"] = array("SELECT `Team` FROM Team AS `Team` WHERE 1", unserialize('a:1:{s:17:"Team.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 277"] = array("SELECT `Team` FROM Team AS `Team` WHERE 1", unserialize('a:1:{s:17:"Team.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 278"] = array("SELECT `Trigger` FROM Trigger AS `Trigger` WHERE 1", unserialize('a:1:{s:20:"Trigger.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 279"] = array("SELECT `Trigger` FROM Trigger AS `Trigger` WHERE 1", unserialize('a:1:{s:20:"Trigger.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 280"] = array("SELECT `URP_Profiles` FROM URP_Profiles AS `URP_Profiles` WHERE 1", unserialize('a:1:{s:25:"URP_Profiles.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 281"] = array("SELECT `URP_Profiles` FROM URP_Profiles AS `URP_Profiles` WHERE 1", unserialize('a:1:{s:25:"URP_Profiles.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 282"] = array("SELECT `URP_UserProfile` FROM URP_UserProfile AS `URP_UserProfile` WHERE (`URP_UserProfile`.`userid` = '5')", unserialize('a:1:{s:28:"URP_UserProfile.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 283"] = array("SELECT `URP_UserProfile` FROM URP_UserProfile AS `URP_UserProfile` WHERE (`URP_UserProfile`.`userid` = '5')", unserialize('a:1:{s:28:"URP_UserProfile.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 284"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '5') AND (`UserDashboard`.`menu_code` = 'WelcomeMenuPage'))", unserialize('a:1:{s:26:"UserDashboard.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 285"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '5') AND (`UserDashboard`.`menu_code` = 'WelcomeMenuPage'))", unserialize('a:1:{s:26:"UserDashboard.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 286"] = array("SELECT `UserInternal` FROM UserInternal AS `UserInternal` WHERE ((`UserInternal`.`login` = 'admin') AND (`UserInternal`.`status` = 'active'))", unserialize('a:1:{s:25:"UserInternal.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 287"] = array("SELECT `UserInternal` FROM UserInternal AS `UserInternal` WHERE ((`UserInternal`.`login` = 'admin') AND (`UserInternal`.`status` = 'active'))", unserialize('a:1:{s:25:"UserInternal.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 288"] = array("SELECT `UserLocal` FROM UserLocal AS `UserLocal` WHERE (`UserLocal`.`id` = '3')", unserialize('a:1:{s:22:"UserLocal.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 289"] = array("SELECT `UserLocal` FROM UserLocal AS `UserLocal` WHERE (`UserLocal`.`id` = '3')", unserialize('a:1:{s:22:"UserLocal.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 290"] = array("SELECT `User` FROM User AS `User` WHERE (`User`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:17:"User.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 291"] = array("SELECT `User` FROM User AS `User` WHERE (`User`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:17:"User.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 292"] = array("SELECT `User` FROM User AS `User` WHERE (`User`.`id` = '3')", unserialize('a:1:{s:17:"User.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 293"] = array("SELECT `User` FROM User AS `User` WHERE (`User`.`id` = '3')", unserialize('a:1:{s:17:"User.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 294"] = array("SELECT `User` FROM User AS `User` WHERE 1", unserialize('a:1:{s:17:"User.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 295"] = array("SELECT `User` FROM User AS `User` WHERE 1", unserialize('a:1:{s:17:"User.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 296"] = array("SELECT `VLAN` FROM VLAN AS `VLAN` WHERE 1", unserialize('a:1:{s:17:"VLAN.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 297"] = array("SELECT `VLAN` FROM VLAN AS `VLAN` WHERE 1", unserialize('a:1:{s:17:"VLAN.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 298"] = array("SELECT `VirtualMachine` FROM VirtualMachine AS `VirtualMachine` WHERE 1", unserialize('a:1:{s:27:"VirtualMachine.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 299"] = array("SELECT `VirtualMachine` FROM VirtualMachine AS `VirtualMachine` WHERE 1", unserialize('a:1:{s:27:"VirtualMachine.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 300"] = array("SELECT `WebApplication` FROM WebApplication AS `WebApplication` WHERE 1", unserialize('a:1:{s:27:"WebApplication.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 301"] = array("SELECT `WebApplication` FROM WebApplication AS `WebApplication` WHERE 1", unserialize('a:1:{s:27:"WebApplication.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 302"] = array("SELECT `WebServer` FROM WebServer AS `WebServer` WHERE 1", unserialize('a:1:{s:22:"WebServer.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 303"] = array("SELECT `WebServer` FROM WebServer AS `WebServer` WHERE 1", unserialize('a:1:{s:22:"WebServer.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 304"] = array("SELECT `appUserPreferences` FROM appUserPreferences AS `appUserPreferences` WHERE (`appUserPreferences`.`id` = '3')", unserialize('a:1:{s:31:"appUserPreferences.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 305"] = array("SELECT `appUserPreferences` FROM appUserPreferences AS `appUserPreferences` WHERE (`appUserPreferences`.`id` = '3')", unserialize('a:1:{s:31:"appUserPreferences.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 306"] = array("SELECT `appUserPreferences` FROM appUserPreferences AS `appUserPreferences` WHERE (`appUserPreferences`.`userid` = '5')", unserialize('a:1:{s:31:"appUserPreferences.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 307"] = array("SELECT `appUserPreferences` FROM appUserPreferences AS `appUserPreferences` WHERE (`appUserPreferences`.`userid` = '5')", unserialize('a:1:{s:31:"appUserPreferences.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 308"] = array("SELECT `c` FROM CustomerContract AS `c` WHERE (`c`.`end_date` < '2018-12-01')", unserialize('a:1:{s:14:"c.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 309"] = array("SELECT `c` FROM CustomerContract AS `c` WHERE (`c`.`end_date` < '2018-12-01')", unserialize('a:1:{s:14:"c.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 310"] = array("SELECT `c` FROM ProviderContract AS `c` WHERE (`c`.`end_date` < '2018-12-01')", unserialize('a:1:{s:14:"c.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 311"] = array("SELECT `c` FROM ProviderContract AS `c` WHERE (`c`.`end_date` < '2018-12-01')", unserialize('a:1:{s:14:"c.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 312"] = array("SELECT `datasource` FROM SynchroDataSource AS `datasource` WHERE 1", unserialize('a:1:{s:23:"datasource.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 313"] = array("SELECT `datasource` FROM SynchroDataSource AS `datasource` WHERE 1", unserialize('a:1:{s:23:"datasource.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 314"] = array("SELECT `i` FROM UserRequest AS `i` WHERE ((`i`.`agent_id` = '') AND (`i`.`status` NOT IN ('closed', 'resolved')))", unserialize('a:1:{s:14:"i.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 315"] = array("SELECT `i` FROM UserRequest AS `i` WHERE ((`i`.`agent_id` = '') AND (`i`.`status` NOT IN ('closed', 'resolved')))", unserialize('a:1:{s:14:"i.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 316"] = array("SELECT `s` FROM Service AS `s` JOIN lnkCustomerContractToService AS `l1` ON `l1`.service_id = `s`.id JOIN CustomerContract AS `cc` ON `l1`.customercontract_id = `cc`.id WHERE (((`cc`.`org_id` = '3') AND (`s`.`status` != 'obsolete')) AND (`s`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:14:"s.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 317"] = array("SELECT `s` FROM Service AS `s` JOIN lnkCustomerContractToService AS `l1` ON `l1`.service_id = `s`.id JOIN CustomerContract AS `cc` ON `l1`.customercontract_id = `cc`.id WHERE (((`cc`.`org_id` = '3') AND (`s`.`status` != 'obsolete')) AND (`s`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:14:"s.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 318"] = array("SELECT `t` FROM Team AS `t` JOIN lnkDeliveryModelToContact AS `l1` ON `l1`.contact_id = `t`.id JOIN DeliveryModel AS `dm` ON `l1`.deliverymodel_id = `dm`.id JOIN Organization AS `o` ON `o`.deliverymodel_id = `dm`.id WHERE ((`o`.`id` = '3') AND (`t`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:14:"t.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 319"] = array("SELECT `t` FROM Team AS `t` JOIN lnkDeliveryModelToContact AS `l1` ON `l1`.contact_id = `t`.id JOIN DeliveryModel AS `dm` ON `l1`.deliverymodel_id = `dm`.id JOIN Organization AS `o` ON `o`.deliverymodel_id = `dm`.id WHERE ((`o`.`id` = '3') AND (`t`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:14:"t.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 320"] = array("SELECT `t` FROM TriggerOnObjectCreate AS `t` WHERE (`t`.`target_class` IN ('appUserPreferences'))", unserialize('a:1:{s:14:"t.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 321"] = array("SELECT `t` FROM TriggerOnObjectCreate AS `t` WHERE (`t`.`target_class` IN ('appUserPreferences'))", unserialize('a:1:{s:14:"t.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 322"] = array("SELECT `t` FROM TriggerOnObjectUpdate AS `t` WHERE (`t`.`target_class` IN ('appUserPreferences'))", unserialize('a:1:{s:14:"t.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 323"] = array("SELECT `t` FROM TriggerOnObjectUpdate AS `t` WHERE (`t`.`target_class` IN ('appUserPreferences'))", unserialize('a:1:{s:14:"t.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 324"] = array("SELECT `UserInternal` FROM UserInternal AS `UserInternal` WHERE ((`UserInternal`.`login` = 'admin') AND (`UserInternal`.`status` = 'enabled'))", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 325"] = array("SELECT `Contact` FROM Contact AS `Contact` WHERE (`Contact`.`id` = '987654321')", array(), array(), null, array(), 0, 0); - $aData["SELECT 326"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", array(), array(), null, array(), 4, 0); - $aData["SELECT 327"] = array("SELECT `appUserPreferences` FROM appUserPreferences AS `appUserPreferences` WHERE (`appUserPreferences`.`userid` = '1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 328"] = array("SELECT `Shortcut` FROM Shortcut AS `Shortcut` WHERE (`Shortcut`.`user_id` = '1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 329"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '1') AND (`UserDashboard`.`menu_code` = 'WelcomeMenuPage'))", array(), array(), null, array(), 0, 0); - $aData["SELECT 330"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '1') AND (`UserDashboard`.`menu_code` = 'WelcomeMenuPage'))", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 331"] = array("SELECT `BusinessProcess` FROM BusinessProcess AS `BusinessProcess` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 332"] = array("SELECT `ApplicationSolution` FROM ApplicationSolution AS `ApplicationSolution` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 333"] = array("SELECT `Contact` FROM Contact AS `Contact` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 334"] = array("SELECT `Location` FROM Location AS `Location` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 335"] = array("SELECT `Contract` FROM Contract AS `Contract` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 336"] = array("SELECT `Server` FROM Server AS `Server` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 337"] = array("SELECT `NetworkDevice` FROM NetworkDevice AS `NetworkDevice` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 338"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` != 'closed')", array(), array(), null, array(), 0, 0); - $aData["SELECT 339"] = array("SELECT `i` FROM UserRequest AS `i` WHERE ((`i`.`agent_id` = 1) AND (`i`.`status` NOT IN ('closed', 'resolved')))", array(), array(), null, array(), 0, 0); - $aData["SELECT 340"] = array("SELECT `i` FROM UserRequest AS `i` WHERE ((`i`.`agent_id` = 1) AND (`i`.`status` NOT IN ('closed', 'resolved')))", array(), array(), null, array(), 3, 0); - $aData["SELECT 341"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 342"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", array(), array(), null, array(), 52, 0); - $aData["SELECT 343"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = '')", array(), array(), null, array(), 0, 0); - $aData["SELECT 344"] = array("SELECT `DBProperty` FROM DBProperty AS `DBProperty` WHERE (`DBProperty`.`name` = 'database_uuid')", array(), array(), null, array(), 0, 0); - $aData["SELECT 345"] = array("SELECT `DBProperty` FROM DBProperty AS `DBProperty` WHERE (`DBProperty`.`name` = 'database_uuid')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 346"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '1') AND (`UserDashboard`.`menu_code` = 'UserRequest:Overview'))", array(), array(), null, array(), 0, 0); - $aData["SELECT 347"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = '987654321')", array(), array(), null, array(), 0, 0); - $aData["SELECT 348"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = '0')", array(), array(), null, array(), 0, 0); - $aData["SELECT 349"] = array("SELECT `lnkErrorToFunctionalCI` FROM lnkErrorToFunctionalCI AS `lnkErrorToFunctionalCI` JOIN KnownError AS `KnownError` ON `lnkErrorToFunctionalCI`.error_id = `KnownError`.id WHERE (`KnownError`.`id` = '-1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 350"] = array("SELECT `lnkErrorToFunctionalCI` FROM lnkErrorToFunctionalCI AS `lnkErrorToFunctionalCI` JOIN KnownError AS `KnownError` ON `lnkErrorToFunctionalCI`.error_id = `KnownError`.id WHERE (`KnownError`.`id` = '-1')", array(), array(), null, array(), 0, 0); - $aData["SELECT 351"] = array("SELECT `lnkDocumentToError` FROM lnkDocumentToError AS `lnkDocumentToError` JOIN KnownError AS `KnownError` ON `lnkDocumentToError`.error_id = `KnownError`.id WHERE (`KnownError`.`id` = '-1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 352"] = array("SELECT `lnkDocumentToError` FROM lnkDocumentToError AS `lnkDocumentToError` JOIN KnownError AS `KnownError` ON `lnkDocumentToError`.error_id = `KnownError`.id WHERE (`KnownError`.`id` = '-1')", array(), array(), null, array(), 0, 0); - $aData["SELECT 353"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '1') AND (`UserDashboard`.`menu_code` = 'Change:Overview'))", array(), array(), null, array(), 0, 0); - $aData["SELECT 354"] = array("SELECT `InlineImage` FROM InlineImage AS `InlineImage` WHERE (`InlineImage`.`temp_id` = 'adm83AD.tmp')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 355"] = array("SELECT `Attachment` FROM Attachment AS `Attachment` WHERE (`Attachment`.`temp_id` = 'adm83AD.tmp')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 356"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '1') AND (`UserDashboard`.`menu_code` = 'Service:Overview'))", array(), array(), null, array(), 0, 0); - $aData["SELECT 357"] = array("SELECT `c` FROM CustomerContract AS `c` WHERE (`c`.`end_date` < DATE_ADD(NOW(), INTERVAL 30 DAY))", array(), array(), null, array(), 3, 0); - $aData["SELECT 358"] = array("SELECT `c` FROM ProviderContract AS `c` WHERE (`c`.`end_date` < DATE_ADD(NOW(), INTERVAL 30 DAY))", array(), array(), null, array(), 3, 0); - $aData["SELECT 359"] = array("SELECT `DeliveryModel` FROM DeliveryModel AS `DeliveryModel` WHERE 1", array(), array(), null, array(), 52, 0); - $aData["SELECT 360"] = array("SELECT `DeliveryModel` FROM DeliveryModel AS `DeliveryModel` WHERE (`DeliveryModel`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), unserialize('a:1:{s:13:"DeliveryModel";a:1:{i:0;s:12:"friendlyname";}}'), array(), 0, 0); - $aData["SELECT 361"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", array(), array(), null, array(), 3, 0); - $aData["SELECT 362"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), unserialize('a:1:{s:12:"Organization";a:7:{i:0;s:4:"code";i:1;s:6:"status";i:2;s:9:"parent_id";i:3;s:22:"parent_id_friendlyname";i:4;s:27:"parent_id_obsolescence_flag";i:5;s:12:"friendlyname";i:6;s:17:"obsolescence_flag";}}'), array(), 10, 0); - $aData["SELECT 363"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`name` LIKE '%demo%')", array(), array(), null, array(), 3, 0); - $aData["SELECT 364"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`name` LIKE '%demo%')", array(), array(), null, array(), 0, 0); - $aData["SELECT 365"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`name` LIKE '%demo%')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 366"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`name` LIKE '%demo%')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), unserialize('a:1:{s:12:"Organization";a:7:{i:0;s:4:"code";i:1;s:6:"status";i:2;s:9:"parent_id";i:3;s:22:"parent_id_friendlyname";i:4;s:27:"parent_id_obsolescence_flag";i:5;s:12:"friendlyname";i:6;s:17:"obsolescence_flag";}}'), array(), 10, 0); - $aData["SELECT 367"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = '3')", array(), array(), null, array(), 0, 0); - $aData["SELECT 368"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = '3')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 369"] = array("SELECT `replica`, `datasource` FROM SynchroReplica AS `replica` JOIN SynchroDataSource AS `datasource` ON `replica`.sync_source_id = `datasource`.id WHERE ((`replica`.`dest_class` = 'Organization') AND (`replica`.`dest_id` = '3'))", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 370"] = array("SELECT `datasource` FROM SynchroDataSource AS `datasource` WHERE 1", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 371"] = array("SELECT `Trigger` FROM Trigger AS `Trigger` WHERE 1", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 372"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '1') AND (`UserDashboard`.`menu_code` = 'Organization__overview'))", array(), array(), null, array(), 0, 0); - $aData["SELECT 373"] = array("SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE (`FunctionalCI`.`org_id` = '3')", array(), array(), null, array(), 0, 0); - $aData["SELECT 374"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` != 'closed') AND (`UserRequest`.`org_id` = '3'))", array(), array(), null, array(), 0, 0); - $aData["SELECT 375"] = array("SELECT `p` FROM Person AS `p` JOIN User AS `u` ON `u`.contactid = `p`.id WHERE (`p`.`org_id` = '3')", array(), array(), null, array(), 3, 0); - $aData["SELECT 376"] = array("SELECT `p` FROM Person AS `p` JOIN User AS `u` ON `u`.contactid = `p`.id WHERE (`p`.`org_id` = '3')", array(), array(), null, array(), 0, 0); - $aData["SELECT 377"] = array("SELECT `p` FROM Person AS `p` JOIN User AS `u` ON `u`.contactid = `p`.id WHERE (`p`.`org_id` = '3')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), unserialize('a:1:{s:1:"p";a:13:{i:0;s:4:"name";i:1;s:6:"org_id";i:2;s:19:"org_id_friendlyname";i:3;s:24:"org_id_obsolescence_flag";i:4;s:6:"status";i:5;s:11:"location_id";i:6;s:24:"location_id_friendlyname";i:7;s:29:"location_id_obsolescence_flag";i:8;s:5:"email";i:9;s:5:"phone";i:10;s:12:"friendlyname";i:11;s:17:"obsolescence_flag";i:12;s:10:"finalclass";}}'), array(), 10, 0); - $aData["SELECT 378"] = array("SELECT `i` FROM UserRequest AS `i` WHERE (((`i`.`agent_id` = 1) AND (`i`.`status` NOT IN ('closed', 'resolved'))) AND (`i`.`org_id` = '3'))", array(), array(), null, array(), 3, 0); - $aData["SELECT 379"] = array("SELECT `Location` FROM Location AS `Location` WHERE 1", array(), array(), null, array(), 3, 0); - $aData["SELECT 380"] = array("SELECT `Location` FROM Location AS `Location` WHERE 1", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), unserialize('a:1:{s:8:"Location";a:8:{i:0;s:6:"status";i:1;s:6:"org_id";i:2;s:19:"org_id_friendlyname";i:3;s:24:"org_id_obsolescence_flag";i:4;s:4:"city";i:5;s:7:"country";i:6;s:12:"friendlyname";i:7;s:17:"obsolescence_flag";}}'), array(), 10, 0); - $aData["SELECT 381"] = array("SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE (`FunctionalCI`.`finalclass` IN ('Server', 'VirtualMachine', 'PC'))", array(), array(), null, array(), 4, 0); - $aData["SELECT 382"] = array("SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE (`FunctionalCI`.`id` = '987654321')", array(), array(), null, array(), 0, 0); - $aData["SELECT 383"] = array("SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE (`FunctionalCI`.`finalclass` IN ('Server', 'VirtualMachine', 'PC'))", array(), array(), null, array(), 52, 0); - $aData["SELECT 384"] = array("SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE (`FunctionalCI`.`finalclass` IN ('Server', 'VirtualMachine', 'PC'))", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 385"] = array("SELECT `Software` FROM Software AS `Software` WHERE (`Software`.`id` = '987654321')", array(), array(), null, array(), 0, 0); - $aData["SELECT 386"] = array("SELECT `Software` FROM Software AS `Software` WHERE (`Software`.`type` = 'WebServer')", array(), array(), null, array(), 52, 0); - $aData["SELECT 387"] = array("SELECT `Software` FROM Software AS `Software` WHERE (`Software`.`type` = 'WebServer')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 388"] = array("SELECT `SoftwareLicence` FROM SoftwareLicence AS `SoftwareLicence` WHERE (`SoftwareLicence`.`id` = '987654321')", array(), array(), null, array(), 0, 0); - $aData["SELECT 389"] = array("SELECT `SoftwareLicence` FROM SoftwareLicence AS `SoftwareLicence` WHERE (`SoftwareLicence`.`software_id` = 0)", array(), array(), null, array(), 52, 0); - $aData["SELECT 390"] = array("SELECT `SoftwareLicence` FROM SoftwareLicence AS `SoftwareLicence` WHERE (`SoftwareLicence`.`software_id` = 0)", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 391"] = array("SELECT `lnkContactToFunctionalCI` FROM lnkContactToFunctionalCI AS `lnkContactToFunctionalCI` JOIN FunctionalCI AS `FunctionalCI` ON `lnkContactToFunctionalCI`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 392"] = array("SELECT `lnkContactToFunctionalCI` FROM lnkContactToFunctionalCI AS `lnkContactToFunctionalCI` JOIN FunctionalCI AS `FunctionalCI` ON `lnkContactToFunctionalCI`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", array(), array(), null, array(), 0, 0); - $aData["SELECT 393"] = array("SELECT `lnkDocumentToFunctionalCI` FROM lnkDocumentToFunctionalCI AS `lnkDocumentToFunctionalCI` JOIN FunctionalCI AS `FunctionalCI` ON `lnkDocumentToFunctionalCI`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 394"] = array("SELECT `lnkDocumentToFunctionalCI` FROM lnkDocumentToFunctionalCI AS `lnkDocumentToFunctionalCI` JOIN FunctionalCI AS `FunctionalCI` ON `lnkDocumentToFunctionalCI`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", array(), array(), null, array(), 0, 0); - $aData["SELECT 395"] = array("SELECT `lnkApplicationSolutionToFunctionalCI` FROM lnkApplicationSolutionToFunctionalCI AS `lnkApplicationSolutionToFunctionalCI` JOIN FunctionalCI AS `FunctionalCI` ON `lnkApplicationSolutionToFunctionalCI`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 396"] = array("SELECT `lnkApplicationSolutionToFunctionalCI` FROM lnkApplicationSolutionToFunctionalCI AS `lnkApplicationSolutionToFunctionalCI` JOIN FunctionalCI AS `FunctionalCI` ON `lnkApplicationSolutionToFunctionalCI`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", array(), array(), null, array(), 0, 0); - $aData["SELECT 397"] = array("SELECT `WebApplication` FROM WebApplication AS `WebApplication` JOIN WebServer AS `WebServer` ON `WebApplication`.webserver_id = `WebServer`.id WHERE (`WebServer`.`id` = '-1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 398"] = array("SELECT `WebApplication` FROM WebApplication AS `WebApplication` JOIN WebServer AS `WebServer` ON `WebApplication`.webserver_id = `WebServer`.id WHERE (`WebServer`.`id` = '-1')", array(), array(), null, array(), 0, 0); - $aData["SELECT 399"] = array("SELECT `lnkFunctionalCIToProviderContract` FROM lnkFunctionalCIToProviderContract AS `lnkFunctionalCIToProviderContract` JOIN FunctionalCI AS `FunctionalCI` ON `lnkFunctionalCIToProviderContract`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 400"] = array("SELECT `lnkFunctionalCIToProviderContract` FROM lnkFunctionalCIToProviderContract AS `lnkFunctionalCIToProviderContract` JOIN FunctionalCI AS `FunctionalCI` ON `lnkFunctionalCIToProviderContract`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", array(), array(), null, array(), 0, 0); - $aData["SELECT 401"] = array("SELECT `lnkFunctionalCIToService` FROM lnkFunctionalCIToService AS `lnkFunctionalCIToService` JOIN FunctionalCI AS `FunctionalCI` ON `lnkFunctionalCIToService`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 402"] = array("SELECT `lnkFunctionalCIToService` FROM lnkFunctionalCIToService AS `lnkFunctionalCIToService` JOIN FunctionalCI AS `FunctionalCI` ON `lnkFunctionalCIToService`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", array(), array(), null, array(), 0, 0); - $aData["SELECT 403"] = array("SELECT `t` FROM Change AS `t` JOIN lnkFunctionalCIToTicket AS `lnk` ON `lnk`.ticket_id = `t`.id WHERE (((`lnk`.`functionalci_id` = '-2') AND (`t`.`status` NOT IN ('rejected', 'resolved', 'closed'))) AND (`lnk`.`impact_code` != 'not_impacted'))", array(), array(), null, array(), 0, 0); - $aData["SELECT 404"] = array("SELECT `t` FROM UserRequest AS `t` JOIN lnkFunctionalCIToTicket AS `lnk` ON `lnk`.ticket_id = `t`.id WHERE (((`lnk`.`functionalci_id` = '-2') AND (`t`.`status` NOT IN ('rejected', 'resolved', 'closed'))) AND (`lnk`.`impact_code` != 'not_impacted'))", array(), array(), null, array(), 0, 0); - $aData["SELECT 405"] = array("SELECT `t` FROM Change AS `t` JOIN lnkFunctionalCIToTicket AS `lnk` ON `lnk`.ticket_id = `t`.id WHERE (((`lnk`.`functionalci_id` = '-2') AND (`t`.`status` NOT IN ('rejected', 'resolved', 'closed'))) AND (`lnk`.`impact_code` != 'not_impacted'))", array(), array(), null, array(), 3, 0); - $aData["SELECT 406"] = array("SELECT `t` FROM UserRequest AS `t` JOIN lnkFunctionalCIToTicket AS `lnk` ON `lnk`.ticket_id = `t`.id WHERE (((`lnk`.`functionalci_id` = '-2') AND (`t`.`status` NOT IN ('rejected', 'resolved', 'closed'))) AND (`lnk`.`impact_code` != 'not_impacted'))", array(), array(), null, array(), 3, 0); - $aData["SELECT 407"] = array("SELECT `InlineImage` FROM InlineImage AS `InlineImage` WHERE (`InlineImage`.`temp_id` = 'admBBC2.tmp')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 408"] = array("SELECT `Attachment` FROM Attachment AS `Attachment` WHERE (`Attachment`.`temp_id` = 'admBBC2.tmp')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - - return $aData; - } - - public function OQLSelectProvider() - { - $aData = $this->OQLSelectProviderStaticTTO(); - $aData = array_merge($aData,$this->OQLSelectProviderStatic()); - - // Dynamic entries - @include ('oql_records.php'); - - return $aData; - } - - - private function GetId() - { - $sId = str_replace('"', '', $this->getName()); - $sId = str_replace('Legacy', '', $sId); - $sId = str_replace(' ', '_', $sId); - return $sId; - } - - /** - * @param $sSQL - * - * @param int $iLimit - * - * @return array|null - * @throws \CoreException - * @throws \MySQLException - * @throws \MySQLHasGoneAwayException - */ - private function GetArrayResult($sSQL, $iLimit = 10) - { - $resQuery = CMDBSource::Query($sSQL); - if (!$resQuery) - { - return null; - } - else - { - $aRow = array(); - $iCount = 0; - while ($aRes = CMDBSource::FetchArray($resQuery)) - { - if ($iCount < $iLimit) - { - $aRow[] = $aRes; - } - $iCount++; - unset($aRes); - } - CMDBSource::FreeResult($resQuery); - return $aRow; - } - } -} diff --git a/test/OQL/OQLToSQLAllClassesTest.php b/test/OQL/OQLToSQLAllClassesTest.php deleted file mode 100644 index bc24bd5b22..0000000000 --- a/test/OQL/OQLToSQLAllClassesTest.php +++ /dev/null @@ -1,335 +0,0 @@ -AllowAllData(); - $sSQL = $oFilter->MakeSelectQuery(); - CMDBSource::Query($sSQL); - $this->assertTrue(true); - } - - private function GetPreviousTestResult($sTestId) - { - $sResultFile = APPROOT.'log/test/OQLToSQL/'.$sTestId.'.txt'; - if (!is_file($sResultFile)) - { - return null; - } - - $aResult = unserialize(file_get_contents($sResultFile)); - return $aResult; - } - - private function SaveTestResult($sTestId, $aResult) - { - $sResultFile = APPROOT.'log/test/OQLToSQL/'.$sTestId.'.txt'; - if (is_file($sResultFile)) - { - @unlink($sResultFile); - } - file_put_contents($sResultFile, serialize($aResult)); - } - - /** - * @doesNotPerformAssertions - * - * @throws \ConfigException - * @throws \CoreException - */ - public function testOQLLegacySetup() - { - SetupUtils::rrmdir($sResultFile = APPROOT.'log/test'); - } - - /** - * @dataProvider OQLSelectProvider - * @depends testOQLLegacySetup - * - * @param $sOQL - * - * @param array $aOrderBy - * @param array $aArgs - * @param null $aAttToLoad - * @param null $aExtendedDataSpec - * @param int $iLimitCount - * @param int $iLimitStart - * - * @throws \Exception - * @throws \MissingQueryArgument - * @throws \OQLException - */ - public function testOQLLegacyAllClasses($sOQL, $aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 20, $iLimitStart = 0) - { - utils::GetConfig()->Set('use_legacy_dbsearch', true, 'Test'); - utils::GetConfig()->Set('apc_cache.enabled', false, 'Test'); - utils::GetConfig()->Set('expression_cache_enabled', false, 'Test'); - utils::GetConfig()->Set('query_cache_enabled', false, 'Test'); - - $this->assertTrue(utils::GetConfig()->Get('use_legacy_dbsearch')); - $this->assertFalse(utils::GetConfig()->Get('apc_cache.enabled')); - $this->assertFalse(utils::GetConfig()->Get('query_cache_enabled')); - $this->assertFalse(utils::GetConfig()->Get('expression_cache_enabled')); - - $aPrevious = $this->GetPreviousTestResult($this->GetId()); - if (is_null($aPrevious)) { - $aResult = $this->OQLSelectRunner($sOQL, $aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart); - // no test yet, just save - $this->SaveTestResult($this->GetId(), $aResult); - $this->debug("Test result saved"); - } - $this->assertTrue(true); - } - - /** - * @dataProvider OQLSelectProvider - * - * @param $sOQL - * - * @param array $aOrderBy - * @param array $aArgs - * @param null $aAttToLoad - * @param null $aExtendedDataSpec - * @param int $iLimitCount - * @param int $iLimitStart - * - * @throws \Exception - * @throws \MissingQueryArgument - * @throws \OQLException - */ - public function testOQLAllClasses($sOQL, $aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 20, $iLimitStart = 0) - { - utils::GetConfig()->Set('use_legacy_dbsearch', false, 'test'); - utils::GetConfig()->Set('apc_cache.enabled', false, 'test'); - utils::GetConfig()->Set('query_cache_enabled', false, 'test'); - utils::GetConfig()->Set('expression_cache_enabled', false, 'test'); - - $this->assertFalse(utils::GetConfig()->Get('use_legacy_dbsearch')); - $this->assertFalse(utils::GetConfig()->Get('apc_cache.enabled')); - $this->assertFalse(utils::GetConfig()->Get('query_cache_enabled')); - $this->assertFalse(utils::GetConfig()->Get('expression_cache_enabled')); - - $aResult = $this->OQLSelectRunner($sOQL, $aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart); - $this->assertNull($aResult); - } - - /** - * @param $sOQL - * - * @param array $aOrderBy - * @param array $aArgs - * @param null $aAttToLoad - * @param null $aExtendedDataSpec - * @param int $iLimitCount - * @param int $iLimitStart - * - * @return array|null - * @throws \Exception - * @throws \MissingQueryArgument - * @throws \OQLException - */ - private function OQLSelectRunner($sOQL, $aOrderBy = array(), $aArgs = array(), $aAttToLoadNames = null, $aExtendedDataSpec = null, $iLimitCount = 20, $iLimitStart = 0) - { - if (is_null($aAttToLoadNames)) - { - $aAttToLoad = null; - } - else - { - $aAttToLoad = array(); - foreach ($aAttToLoadNames as $sClass => $aAttCodes) - { - $aAttToLoad[$sClass] = array(); - foreach ($aAttCodes as $sAttCode) - { - if (!empty($sAttCode)) - { - if (MetaModel::IsValidAttCode($sClass, $sAttCode)) - { - $aAttToLoad[$sClass][$sAttCode] = MetaModel::GetAttributeDef($sClass, $sAttCode); - } - } - } - } - } - - $oSearch = DBSearch::FromOQL($sOQL); - - $sSQLCount = $oSearch->MakeSelectQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, 0, 0, true); - $fStart = $this->GetMicroTime(); - $aRow = $this->GetArrayResult($sSQLCount); - $fCountDuration = $this->GetMicroTime() - $fStart; - if (is_null($aRow)) - { - $iCount = 0; - } - else - { - $iCount = intval($aRow[0]['COUNT']); - } - $iJoinCount = count(explode(' JOIN ', $sSQLCount)) - 1; - - - $sSQL = $oSearch->MakeSelectQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart); - $fStart = $this->GetMicroTime(); - $aRow = $this->GetArrayResult($sSQL); - $fDataDuration = $this->GetMicroTime() - $fStart; - if (is_null($aRow)) - { - $aRow = array(); - } - // Store only to the 10 first entries - $aRow = array_slice($aRow, 0, 10); - - $iJoinData = count(explode(' JOIN ', $sSQL)) - 1; - - $aResult = array( - 'oql' => $sOQL, - 'count_sql' => $sSQLCount, - 'count_join_count' => $iJoinCount, - 'count' => $iCount, - 'count_duration' => $fCountDuration, - 'data_sql' => $sSQL, - 'data_join_count' => $iJoinData, - 'data_duration' => $fDataDuration, - ); - - //$this->debug($aResult); - - $aResult['data'] = $aRow; - - $aPrevious = $this->GetPreviousTestResult($this->GetId()); - if (is_null($aPrevious)) - { - return $aResult; - } - - $this->debug("count: ".$aResult['count']); - $this->debug("count_join_count: ".$aPrevious['count_join_count']." -> ".$aResult['count_join_count']); - $this->debug("count_duration : ".round($aPrevious['count_duration'], PRECISION)." -> ".round($aResult['count_duration'], PRECISION)); - $this->debug("data_join_count : ".$aPrevious['data_join_count']." -> ".$aResult['data_join_count']); - $this->debug("data_duration : ".round($aPrevious['data_duration'], PRECISION)." -> ".round($aResult['data_duration'], PRECISION)); - - // Compare result - $aFields = array('oql', 'count', 'data'); - foreach ($aFields as $sField) - { - $this->assertEquals($aPrevious[$sField], $aResult[$sField], "$sField differ"); - } - - if ($aPrevious['data_join_count'] != $aResult['data_join_count']) - { - unset($aPrevious['data']); - unset($aResult['data']); - $this->debug("Previous"); - $this->debug($aPrevious); - $this->debug("Current"); - $this->debug($aResult); - } - return null; - } - - static $aPureAbstractClasses = ['AbstractResource', 'ResourceAdminMenu', 'ResourceRunQueriesMenu', 'ResourceSystemMenu']; - - public function OQLSelectProvider() - { - parent::setUp(); - require_once(APPROOT.'application/startup.inc.php'); - - $aData = array(); - - // $sOQL, $aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 20, $iLimitStart = 0 - - $aClasses = MetaModel::GetClasses(); - sort($aClasses); - - foreach ($aClasses as $sClass) - { - if (in_array($sClass, self::$aPureAbstractClasses)) - { - // These classes are pure abstract (no table in database) - continue; - } - $sOQL = "SELECT $sClass"; - - // Compute Order by - $aOrderBy = array($sClass.'.id' => true); - $aData[$sOQL] = array($sOQL, $aOrderBy, array(), null, null, 10, 0); - } - - return $aData; - } - - private function GetId() - { - $sId = str_replace('"', '', $this->getName()); - $sId = str_replace('Legacy', '', $sId); - $sId = str_replace(' ', '_', $sId); - return $sId; - } - - /** - * @param $sSQL - * - * @param int $iLimit - * - * @return array|null - * @throws \CoreException - * @throws \MySQLException - * @throws \MySQLHasGoneAwayException - */ - private function GetArrayResult($sSQL, $iLimit = 10) - { - $resQuery = CMDBSource::Query($sSQL); - if (!$resQuery) - { - return null; - } - else - { - $aRow = array(); - $iCount = 0; - while ($aRes = CMDBSource::FetchArray($resQuery)) - { - if ($iCount < $iLimit) - { - $aRow[] = $aRes; - } - $iCount++; - unset($aRes); - } - CMDBSource::FreeResult($resQuery); - return $aRow; - } - } -} diff --git a/test/OQL/OQLToSQLGroupByTest.php b/test/OQL/OQLToSQLGroupByTest.php deleted file mode 100644 index ff7a76e271..0000000000 --- a/test/OQL/OQLToSQLGroupByTest.php +++ /dev/null @@ -1,305 +0,0 @@ -Set('use_legacy_dbsearch', true, 'Test'); - utils::GetConfig()->Set('apc_cache.enabled', false, 'Test'); - utils::GetConfig()->Set('expression_cache_enabled', false, 'Test'); - utils::GetConfig()->Set('query_cache_enabled', false, 'Test'); - - $this->assertTrue(utils::GetConfig()->Get('use_legacy_dbsearch')); - $this->assertFalse(utils::GetConfig()->Get('apc_cache.enabled')); - $this->assertFalse(utils::GetConfig()->Get('query_cache_enabled')); - $this->assertFalse(utils::GetConfig()->Get('expression_cache_enabled')); - - $aPrevious = $this->GetPreviousTestResult($this->GetId()); - if (is_null($aPrevious)) { - $aResult = $this->OQLGroupByRunner($sOQL, $aArgs, $aGroupByExpr, $bExcludeNullValues, $aSelectExpr, $aOrderBy, $iLimitCount, $iLimitStart); - // no test yet, just save - $this->SaveTestResult($this->GetId(), $aResult); - $this->debug("Test result saved"); - } - $this->assertTrue(true); - } - - /** - * @doesNotPerformAssertions - * - * @throws \ConfigException - * @throws \CoreException - */ - public function testOQLSetup() - { - $aCSVHeader = array( - 'test', 'OQL','count', - 'Legacy Count Joins', 'Count Joins', - 'Legacy Count Duration', 'Count Duration', - 'Legacy Data Joins', 'Data Joins', - 'Legacy Data Duration', 'Data Duration', - 'Count Joins Diff', 'Data Joins Diff', - ); - $this->WriteToCsvHeader(self::TEST_CSV_RESULT, $aCSVHeader); - } - - /** - * @dataProvider OQLGroupByProvider - * @depends testOQLSetup - * - * @param $sOQL - * @param $aArgs - * @param $aGroupByExpr - * @param bool $bExcludeNullValues - * @param array $aSelectExpr - * @param array $aOrderBy - * @param int $iLimitCount - * @param int $iLimitStart - * - * @throws \CoreException - * @throws \MySQLException - * @throws \MySQLHasGoneAwayException - * @throws \OQLException - */ - public function testOQLGroupBy($sOQL, $aArgs, $aGroupByExpr, $bExcludeNullValues = false, $aSelectExpr = array(), $aOrderBy = array(), $iLimitCount = 0, $iLimitStart = 0) - { - utils::GetConfig()->Set('use_legacy_dbsearch', false, 'test'); - utils::GetConfig()->Set('apc_cache.enabled', false, 'test'); - utils::GetConfig()->Set('query_cache_enabled', false, 'test'); - utils::GetConfig()->Set('expression_cache_enabled', false, 'test'); - - $this->assertFalse(utils::GetConfig()->Get('use_legacy_dbsearch')); - $this->assertFalse(utils::GetConfig()->Get('apc_cache.enabled')); - $this->assertFalse(utils::GetConfig()->Get('query_cache_enabled')); - $this->assertFalse(utils::GetConfig()->Get('expression_cache_enabled')); - - $aResult = $this->OQLGroupByRunner($sOQL, $aArgs, $aGroupByExpr, $bExcludeNullValues, $aSelectExpr, $aOrderBy, $iLimitCount, $iLimitStart); - $this->assertNull($aResult); - } - - /** - * @param $sOQL - * @param $aArgs - * @param $aGroupByExpr - * @param bool $bExcludeNullValues - * @param array $aSelectExpr - * @param array $aOrderBy - * @param int $iLimitCount - * @param int $iLimitStart - * - * @return array|null - * @throws \CoreException - * @throws \MySQLException - * @throws \MySQLHasGoneAwayException - * @throws \OQLException - */ - private function OQLGroupByRunner($sOQL, $aArgs, $aGroupByExpr, $bExcludeNullValues = false, $aSelectExpr = array(), $aOrderBy = array(), $iLimitCount = 0, $iLimitStart = 0) - { - $oSearch = DBSearch::FromOQL($sOQL); - - $aGroupByExpr = Expression::ConvertArrayFromOQL($aGroupByExpr); - $aSelectExpr = Expression::ConvertArrayFromOQL($aSelectExpr); - - $sSQL = $oSearch->MakeGroupByQuery($aArgs, $aGroupByExpr, $bExcludeNullValues, $aSelectExpr, $aOrderBy, $iLimitCount, $iLimitStart); - $fStart = $this->GetMicroTime(); - $aRow = $this->GetArrayResult($sSQL); - $fDataDuration = $this->GetMicroTime() - $fStart; - if (is_null($aRow)) - { - $aRow = array(); - } - // Store only to the 10 first entries - $aRow = array_slice($aRow, 0, 10); - - $iJoinData = count(explode(' JOIN ', $sSQL)) - 1; - - $aResult = array( - 'oql' => $sOQL, - 'data_sql' => $sSQL, - 'data_join_count' => $iJoinData, - 'data_duration' => $fDataDuration, - ); - - $aResult['data'] = $aRow; - - $aPrevious = $this->GetPreviousTestResult($this->GetId()); - if (is_null($aPrevious)) - { - return $aResult; - } - - $this->debug("data_join_count : ".$aPrevious['data_join_count']." -> ".$aResult['data_join_count']); - $this->debug("data_duration : ".round($aPrevious['data_duration'], NUM_PRECISION)." -> ".round($aResult['data_duration'], NUM_PRECISION)); - - // Compare result - $aFields = array('oql', 'data'); - foreach ($aFields as $sField) - { - $this->assertEquals($aPrevious[$sField], $aResult[$sField], "$sField differ"); - } - - if ($aPrevious['data_join_count'] != $aResult['data_join_count']) - { - unset($aPrevious['data']); - unset($aResult['data']); - $this->debug("Previous"); - $this->debug($aPrevious); - $this->debug("Current"); - $this->debug($aResult); - } - return null; - } - - private function OQLGroupByProviderStatic() - { - $aData = array(); - - $aData["SELECT 1"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` != 'closed')", array(), unserialize('a:1:{s:6:"group1";s:22:"`UserRequest`.`status`";}'), false, array(), array(), 0, 0); - $aData["SELECT 2"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE 1", array(), unserialize('a:1:{s:12:"grouped_by_1";s:22:"`UserRequest`.`status`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:1;}'), 0, 0); - $aData["SELECT 4"] = array("SELECT `Contact` FROM Contact AS `Contact` WHERE 1", array(), unserialize('a:1:{s:12:"grouped_by_1";s:18:"`Contact`.`status`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:0;}'), 0, 0); - $aData["SELECT 5"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` NOT IN ('closed', 'rejected'))", array(), unserialize('a:1:{s:12:"grouped_by_1";s:22:"`UserRequest`.`status`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:0;}'), 0, 0); - $aData["SELECT 6"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` NOT IN ('closed', 'rejected'))", array(), unserialize('a:1:{s:12:"grouped_by_1";s:24:"`UserRequest`.`agent_id`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:0;}'), 0, 0); - $aData["SELECT 7"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` NOT IN ('closed', 'rejected'))", array(), unserialize('a:1:{s:12:"grouped_by_1";s:26:"`UserRequest`.`finalclass`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:0;}'), 0, 0); - $aData["SELECT 8"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` NOT IN ('closed', 'rejected'))", array(), unserialize('a:1:{s:12:"grouped_by_1";s:22:"`UserRequest`.`org_id`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:0;}'), 0, 0); - $aData["SELECT 9"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (DATE_SUB(NOW(), INTERVAL 14 DAY) < `UserRequest`.`start_date`)", array(), unserialize('a:1:{s:12:"grouped_by_1";s:28:"`UserRequest`.`request_type`";}'), true, array(), unserialize('a:1:{s:12:"_itop_count_";b:0;}'), 0, 0); - $aData["SELECT 10"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (DATE_SUB(NOW(), INTERVAL 14 DAY) < `UserRequest`.`start_date`)", array(), unserialize('a:1:{s:12:"grouped_by_1";s:51:"DATE_FORMAT(`UserRequest`.`start_date`, \'%Y-%m-%d\')";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:1;}'), 0, 0); - $aData["SELECT 11"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`creation_date` > DATE_SUB(NOW(), INTERVAL 7 DAY))", array(), unserialize('a:1:{s:12:"grouped_by_1";s:19:"`Change`.`category`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:0;}'), 0, 0); - $aData["SELECT 12"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`creation_date` > DATE_SUB(NOW(), INTERVAL 7 DAY))", array(), unserialize('a:1:{s:12:"grouped_by_1";s:21:"`Change`.`finalclass`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:0;}'), 0, 0); - $aData["SELECT 13"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`creation_date` > DATE_SUB(NOW(), INTERVAL 7 DAY))", array(), unserialize('a:1:{s:12:"grouped_by_1";s:17:"`Change`.`status`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:0;}'), 0, 0); - $aData["SELECT 14"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`creation_date` > DATE_SUB(NOW(), INTERVAL 7 DAY))", array(), unserialize('a:1:{s:12:"grouped_by_1";s:46:"DATE_FORMAT(`Change`.`start_date`, \'%Y-%m-%d\')";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:1;}'), 0, 0); - $aData["SELECT 15"] = array("SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE (`FunctionalCI`.`org_id` = '3')", array(), unserialize('a:1:{s:6:"group1";s:27:"`FunctionalCI`.`finalclass`";}'), false, array(), array(), 0, 0); - $aData["SELECT 16"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` != 'closed') AND (`UserRequest`.`org_id` = '3'))", array(), unserialize('a:1:{s:6:"group1";s:22:"`UserRequest`.`status`";}'), false, array(), array(), 0, 0); - $aData["SELECT 17"] = array("SELECT `Ticket` FROM Ticket AS `Ticket` WHERE (`Ticket`.`org_id` = '3')", array(), unserialize('a:1:{s:12:"grouped_by_1";s:21:"`Ticket`.`finalclass`";}'), true, array(), unserialize('a:1:{s:12:"grouped_by_1";b:0;}'), 0, 0); - - return $aData; - } - - public function OQLGroupByProvider() - { - $aData = $this->OQLGroupByProviderStatic(); - - // Dynamic entries - @include ('oql_group_by_records.php'); - - return $aData; - } - - private function GetId() - { - $sId = str_replace('"', '', $this->getName()); - $sId = str_replace('Legacy', '', $sId); - $sId = str_replace(' ', '_', $sId); - return $sId; - } - - /** - * @param $sSQL - * - * @param int $iLimit - * - * @return array|null - * @throws \CoreException - * @throws \MySQLException - * @throws \MySQLHasGoneAwayException - */ - private function GetArrayResult($sSQL, $iLimit = 10) - { - $resQuery = CMDBSource::Query($sSQL); - if (!$resQuery) - { - return null; - } - else - { - $aRow = array(); - $iCount = 0; - while ($aRes = CMDBSource::FetchArray($resQuery)) - { - if ($iCount < $iLimit) - { - $aRow[] = $aRes; - } - $iCount++; - unset($aRes); - } - CMDBSource::FreeResult($resQuery); - return $aRow; - } - } -} diff --git a/test/OQL/OQLToSQLNestedSelectTest.php b/test/OQL/OQLToSQLNestedSelectTest.php deleted file mode 100644 index 9d6cbb6199..0000000000 --- a/test/OQL/OQLToSQLNestedSelectTest.php +++ /dev/null @@ -1,209 +0,0 @@ -Set('use_legacy_dbsearch', false, 'test'); - utils::GetConfig()->Set('apc_cache.enabled', false, 'test'); - utils::GetConfig()->Set('query_cache_enabled', false, 'test'); - utils::GetConfig()->Set('expression_cache_enabled', false, 'test'); - - $this->assertFalse(utils::GetConfig()->Get('use_legacy_dbsearch')); - $this->assertFalse(utils::GetConfig()->Get('apc_cache.enabled')); - $this->assertFalse(utils::GetConfig()->Get('query_cache_enabled')); - $this->assertFalse(utils::GetConfig()->Get('expression_cache_enabled')); - - $aResult = $this->OQLSelectRunner($sOQL, $aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart); - $this->assertNull($aResult); - } - - /** - * @param $sOQL - * - * @param array $aOrderBy - * @param array $aArgs - * @param null $aAttToLoadNames - * @param null $aExtendedDataSpec - * @param int $iLimitCount - * @param int $iLimitStart - * - * @return array|null - * @throws \Exception - * @throws \MissingQueryArgument - * @throws \OQLException - */ - private function OQLSelectRunner($sOQL, $aOrderBy = array(), $aArgs = array(), $aAttToLoadNames = null, $aExtendedDataSpec = null, $iLimitCount = 20, $iLimitStart = 0) - { - if (is_null($aAttToLoadNames)) - { - $aAttToLoad = null; - } - else - { - $aAttToLoad = array(); - foreach ($aAttToLoadNames as $sClass => $aAttCodes) - { - $aAttToLoad[$sClass] = array(); - foreach ($aAttCodes as $sAttCode) - { - if (!empty($sAttCode)) - { - if (MetaModel::IsValidAttCode($sClass, $sAttCode)) - { - $aAttToLoad[$sClass][$sAttCode] = MetaModel::GetAttributeDef($sClass, $sAttCode); - } - } - } - } - } - - $oSearch = DBSearch::FromOQL($sOQL); - $sClass = $oSearch->GetClass(); - if (empty($aOrderBy)) - { - $aOrderBy = MetaModel::GetOrderByDefault($sClass); - } - - $sSQLCount = $oSearch->MakeSelectQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, 0, 0, true); - $fStart = $this->GetMicroTime(); - $aRow = $this->GetArrayResult($sSQLCount); - $fCountDuration = $this->GetMicroTime() - $fStart; - if (is_null($aRow)) - { - $iCount = 0; - } - else - { - $iCount = intval($aRow[0]['COUNT']); - } - $iJoinCount = count(explode(' JOIN ', $sSQLCount)) - 1; - - - $sSQL = $oSearch->MakeSelectQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart); - $fStart = $this->GetMicroTime(); - $this->GetArrayResult($sSQL); - $fDataDuration = $this->GetMicroTime() - $fStart; - $iJoinData = count(explode(' JOIN ', $sSQL)) - 1; - - $aResult = array( - 'oql' => $sOQL, - 'count_sql' => $sSQLCount, - 'count_join_count' => $iJoinCount, - 'count' => $iCount, - 'count_duration' => $fCountDuration, - 'data_sql' => $sSQL, - 'data_join_count' => $iJoinData, - 'data_duration' => $fDataDuration, - ); - - $this->debug($aResult); - - //$aResult['data'] = $aRow; - - return null; - } - - - private function OQLSelectProviderStatic() - { - return array( - array('SELECT `UserRequest` FROM UserRequest AS `UserRequest` JOIN Person AS `P` ON `UserRequest`.agent_id = `P`.id JOIN Organization AS `Organization` ON `P`.org_id = `Organization`.id WHERE (`UserRequest`.`org_id` IN (SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = `UserRequest`.`org_id`)))',), - array('SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.org_id IN (SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = `UserRequest`.`org_id`)))'), - array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE `UserRequest`.org_id IN (SELECT `Organization` FROM Organization AS `Organization` JOIN Organization AS `Organization1` ON `Organization`.parent_id BELOW `Organization1`.id WHERE (`Organization1`.`id` = '3'))"), - array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE `UserRequest`.org_id IN (1,2,3)"), - array("SELECT User AS U JOIN Person AS P ON U.contactid = P.id WHERE U.status='enabled' AND U.id NOT IN (SELECT User AS U JOIN Person AS P ON U.contactid=P.id JOIN URP_UserOrg AS L ON L.userid = U.id WHERE U.status='enabled' AND L.allowed_org_id = P.org_id UNION SELECT User AS U WHERE U.status='enabled' AND U.id NOT IN (SELECT User AS U JOIN URP_UserOrg AS L ON L.userid = U.id WHERE U.status='enabled'))"), - array("SELECT UserRequest AS Ur WHERE Ur.id NOT IN (SELECT UserRequest AS Ur JOIN lnkFunctionalCIToTicket AS lnk ON lnk.ticket_id = Ur.id)"), - array("SELECT Ticket AS T WHERE T. finalclass IN ('userrequest' , 'change') AND T.id NOT IN (SELECT UserRequest AS Ur JOIN lnkFunctionalCIToTicket AS lnk ON lnk.ticket_id = Ur.id UNION SELECT Change AS C JOIN lnkFunctionalCIToTicket AS lnk ON lnk.ticket_id = C.id)"), - array("SELECT PhysicalDevice WHERE status='production' AND id NOT IN (SELECT PhysicalDevice AS p JOIN lnkFunctionalCIToProviderContract AS l ON l.functionalci_id=p.id)"), - ); - } - - public function OQLSelectProvider() - { - return $this->OQLSelectProviderStatic(); - } - - - /** - * @param $sSQL - * - * @param int $iLimit - * - * @return array|null - * @throws \CoreException - * @throws \MySQLException - * @throws \MySQLHasGoneAwayException - */ - private function GetArrayResult($sSQL, $iLimit = 10) - { - $resQuery = CMDBSource::Query($sSQL); - if (!$resQuery) - { - return null; - } - else - { - $aRow = array(); - $iCount = 0; - while ($aRes = CMDBSource::FetchArray($resQuery)) - { - if ($iCount < $iLimit) - { - $aRow[] = $aRes; - } - $iCount++; - unset($aRes); - } - CMDBSource::FreeResult($resQuery); - return $aRow; - } - } -} diff --git a/test/OQL/OQLToSQLTest.php b/test/OQL/OQLToSQLTest.php deleted file mode 100644 index 11dff955a3..0000000000 --- a/test/OQL/OQLToSQLTest.php +++ /dev/null @@ -1,960 +0,0 @@ -Set('use_legacy_dbsearch', true, 'Test'); - utils::GetConfig()->Set('apc_cache.enabled', false, 'Test'); - utils::GetConfig()->Set('expression_cache_enabled', false, 'Test'); - utils::GetConfig()->Set('query_cache_enabled', false, 'Test'); - - $this->assertTrue(utils::GetConfig()->Get('use_legacy_dbsearch')); - $this->assertFalse(utils::GetConfig()->Get('apc_cache.enabled')); - $this->assertFalse(utils::GetConfig()->Get('query_cache_enabled')); - $this->assertFalse(utils::GetConfig()->Get('expression_cache_enabled')); - - $aPrevious = $this->GetPreviousTestResult($this->GetId()); - if (is_null($aPrevious)) { - $aResult = $this->OQLSelectRunner($sOQL, $aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart); - // no test yet, just save - $this->SaveTestResult($this->GetId(), $aResult); - $this->debug("Test result saved"); - } - $this->assertTrue(true); - } - - /** - * @doesNotPerformAssertions - * - * @throws \ConfigException - * @throws \CoreException - */ - public function testOQLSetup() - { - $aCSVHeader = array( - 'test', 'OQL','count', - 'Legacy Count Joins', 'Count Joins', - 'Legacy Count Duration', 'Count Duration', - 'Legacy Data Joins', 'Data Joins', - 'Legacy Data Duration', 'Data Duration', - 'Count Joins Diff', 'Data Joins Diff', - ); - $this->WriteToCsvHeader(self::TEST_CSV_RESULT, $aCSVHeader); - } - - /** - * @dataProvider OQLSelectProvider - * @depends testOQLSetup - * - * @param $sOQL - * - * @param array $aOrderBy - * @param array $aArgs - * @param null $aAttToLoad - * @param null $aExtendedDataSpec - * @param int $iLimitCount - * @param int $iLimitStart - * - * @throws \Exception - * @throws \MissingQueryArgument - * @throws \OQLException - */ - public function testOQLSelect($sOQL, $aOrderBy = array(), $aArgs = array(), $aAttToLoad = null, $aExtendedDataSpec = null, $iLimitCount = 20, $iLimitStart = 0) - { - utils::GetConfig()->Set('use_legacy_dbsearch', false, 'test'); - utils::GetConfig()->Set('apc_cache.enabled', false, 'test'); - utils::GetConfig()->Set('query_cache_enabled', false, 'test'); - utils::GetConfig()->Set('expression_cache_enabled', false, 'test'); - - $this->assertFalse(utils::GetConfig()->Get('use_legacy_dbsearch')); - $this->assertFalse(utils::GetConfig()->Get('apc_cache.enabled')); - $this->assertFalse(utils::GetConfig()->Get('query_cache_enabled')); - $this->assertFalse(utils::GetConfig()->Get('expression_cache_enabled')); - - $aResult = $this->OQLSelectRunner($sOQL, $aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart); - $this->assertNull($aResult); - } - - /** - * @param $sOQL - * - * @param array $aOrderBy - * @param array $aArgs - * @param null $aAttToLoadNames - * @param null $aExtendedDataSpec - * @param int $iLimitCount - * @param int $iLimitStart - * - * @return array|null - * @throws \Exception - * @throws \MissingQueryArgument - * @throws \OQLException - */ - private function OQLSelectRunner($sOQL, $aOrderBy = array(), $aArgs = array(), $aAttToLoadNames = null, $aExtendedDataSpec = null, $iLimitCount = 20, $iLimitStart = 0) - { - if (is_null($aAttToLoadNames)) - { - $aAttToLoad = null; - } - else - { - $aAttToLoad = array(); - foreach ($aAttToLoadNames as $sClass => $aAttCodes) - { - $aAttToLoad[$sClass] = array(); - foreach ($aAttCodes as $sAttCode) - { - if (!empty($sAttCode)) - { - if (MetaModel::IsValidAttCode($sClass, $sAttCode)) - { - $aAttToLoad[$sClass][$sAttCode] = MetaModel::GetAttributeDef($sClass, $sAttCode); - } - } - } - } - } - - $oSearch = DBSearch::FromOQL($sOQL); - $sClass = $oSearch->GetClass(); - if (empty($aOrderBy)) - { - $aOrderBy = MetaModel::GetOrderByDefault($sClass); - } - - $sSQLCount = $oSearch->MakeSelectQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, 0, 0, true); - $fStart = $this->GetMicroTime(); - $aRow = $this->GetArrayResult($sSQLCount); - $fCountDuration = $this->GetMicroTime() - $fStart; - if (is_null($aRow)) - { - $iCount = 0; - } - else - { - $iCount = intval($aRow[0]['COUNT']); - } - $iJoinCount = count(explode(' JOIN ', $sSQLCount)) - 1; - - - $sSQL = $oSearch->MakeSelectQuery($aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart); - $fStart = $this->GetMicroTime(); - $aRow = $this->GetArrayResult($sSQL); - $fDataDuration = $this->GetMicroTime() - $fStart; - if (is_null($aRow)) - { - $aRow = array(); - } - // Store only to the 10 first entries - $aRow = array_slice($aRow, 0, 10); - - $iJoinData = count(explode(' JOIN ', $sSQL)) - 1; - - $aResult = array( - 'oql' => $sOQL, - 'count_sql' => $sSQLCount, - 'count_join_count' => $iJoinCount, - 'count' => $iCount, - 'count_duration' => $fCountDuration, - 'data_sql' => $sSQL, - 'data_join_count' => $iJoinData, - 'data_duration' => $fDataDuration, - ); - - //$this->debug($aResult); - - $aResult['data'] = $aRow; - - $aPrevious = $this->GetPreviousTestResult($this->GetId()); - if (is_null($aPrevious)) - { - return $aResult; - } - - $this->debug("count: ".$aResult['count']); - $this->debug("count_join_count: ".$aPrevious['count_join_count']." -> ".$aResult['count_join_count']); - $this->debug("count_duration : ".round($aPrevious['count_duration'], NUM_PRECISION)." -> ".round($aResult['count_duration'], NUM_PRECISION)); - $this->debug("data_join_count : ".$aPrevious['data_join_count']." -> ".$aResult['data_join_count']); - $this->debug("data_duration : ".round($aPrevious['data_duration'], NUM_PRECISION)." -> ".round($aResult['data_duration'], NUM_PRECISION)); - - $aCSVData = array( - $this->GetId(), $sOQL, $aResult['count'], - $aPrevious['count_join_count'], $aResult['count_join_count'], - round($aPrevious['count_duration'], NUM_PRECISION), round($aResult['count_duration'], NUM_PRECISION), - $aPrevious['data_join_count'], $aResult['data_join_count'], - round($aPrevious['data_duration'], NUM_PRECISION), round($aResult['data_duration'], NUM_PRECISION), - $aPrevious['count_join_count'] - $aResult['count_join_count'], $aPrevious['data_join_count'] - $aResult['data_join_count'], - ); - $this->WriteToCsvData(self::TEST_CSV_RESULT, $aCSVData); - - // Compare result - $aFields = array('oql', 'count', 'data'); - foreach ($aFields as $sField) - { - $this->assertEquals($aPrevious[$sField], $aResult[$sField], "$sField differ"); - } - - if ($aPrevious['data_join_count'] != $aResult['data_join_count']) - { - unset($aPrevious['data']); - unset($aResult['data']); - $this->debug("Previous"); - $this->debug($aPrevious); - $this->debug("Current"); - $this->debug($aResult); - } - return null; - } - - private function OQLSelectProviderStaticTTO() - { - $aAttToLoadTTO = array('UserRequest' => array('tto')); - $aData = array( - "SELECT UserRequest TTO" => array("SELECT UserRequest FROM UserRequest AS UserRequest WHERE tto_laststart AND tto_75_triggered = 0 AND tto_75_deadline < '2010-06-26 12:08:59'", array(), array(), $aAttToLoadTTO, null, null, 3, 0), - ); - - return $aData; - } - - - private function OQLSelectProviderStatic() - { - $aArgs = array( - 'ActionEmail_finalclass' => 'ActionEmail', - 'UserInternal_status' => 'active', - 'current_contact_id' => '2', - 'id' => 3, - 'login' => 'admin', - 'menu_code' => 'WelcomeMenuPage', - 'name' => 'database_uuid', - 'this->brand_id' => '1', - 'this->finalclass' => 'NetworkDevice', - 'this->id' => 3, - 'this->location_id' => 2, - 'this->org_id' => 3, - 'this->osfamily_id' => '6', - 'this->osversion_id' => '8', - 'this->rack_id' => '3', - 'this->request_type' => 'incident', - 'this->service_id' => '1', - 'user_id' => '5', - 'userid' => '5', - ); - - $aAttToLoad150 = array( - 'WebServer' => array( - 'business_criticity', - 'description', - 'name', - 'friendlyname', - 'obsolescence_flag', - 'finalclass', - ), - ); - - $aData = array( - "SELECT UNION 5.6" => array("SELECT `Organization` AS `Organization` WHERE ((`Organization`.`status` != 'inactive') AND (`Organization`.`id` = '1')) UNION SELECT `Organization` AS `Organization` WHERE ((`Organization`.`id` = '1') AND (`Organization`.`id` = '1'))", array(), array(), array(), null, null, 3, 0), - "SELECT WebServer 150" => array("SELECT `WebServer` FROM WebServer AS `WebServer` WHERE 1", array(), array(), $aAttToLoad150, null, null, 3, 0), - "SELECT WebServer 151" => array("SELECT `WebServer` FROM WebServer AS `WebServer` WHERE 1", array(), array(), array(), null, null, 3, 0), - "SELECT L JOIN 176" => array("SELECT `L` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE 1", unserialize('a:1:{s:14:"L.friendlyname";b:1;}'), array(), null, null, 3, 0), - "SELECT P JOIN 177" => array("SELECT `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, null, 3, 0), - "SELECT L,P JOIN 178" => array("SELECT `L`, `P` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, null, 3, 0), - "SELECT P,L JOIN 179" => array("SELECT `P`, `L` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, null, 3, 0), - "SELECT L,P JOIN 180" => array("SELECT `L`, `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, null, 3, 0), - "SELECT L JOIN 181" => array("SELECT `L` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", unserialize('a:1:{s:14:"L.friendlyname";b:1;}'), array(), null, null, 3, 0), - "SELECT UserRequest 114" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` JOIN Organization AS `Organization` ON `UserRequest`.org_id = `Organization`.id JOIN Organization AS `Organization1` ON `Organization`.parent_id BELOW `Organization1`.id WHERE (`Organization1`.`id` = '3')", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 115" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (('2018-12-01' < `UserRequest`.`start_date`) AND (ISNULL(DATE_FORMAT(`UserRequest`.`start_date`, '%Y-%m-%d')) != 1))", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 116" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`agent_id` = :current_contact_id) AND (`UserRequest`.`status` NOT IN ('closed', 'resolved')))", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 117" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`caller_id` = :current_contact_id) AND (`UserRequest`.`status` NOT IN ('closed')))", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 118" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` IN ('escalated_tto', 'escalated_ttr')) OR (`UserRequest`.`escalation_flag` = 'yes'))", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 119" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`agent_id`) != 1))", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 120" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`finalclass`) != 1))", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 121" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`org_id`) != 1))", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 122" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`status`) != 1))", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 123" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (ISNULL(`UserRequest`.`org_id`) != 1)", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 124" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` != 'closed')", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 125" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` NOT IN ('closed'))", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 126" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE 1", array('UserRequest.friendlyname' => true), $aArgs), - "SELECT UserRequest 25" => array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`id` = :id)", array(), unserialize('a:1:{s:2:"id";i:987654321;}'), null, null, 0, 0), - "SELECT ApplicationSolution 2" => array("SELECT `ApplicationSolution` FROM ApplicationSolution AS `ApplicationSolution` WHERE 1", array('ApplicationSolution.friendlyname' => true), $aArgs), - "SELECT AuditCategory 3" => array("SELECT `AuditCategory` FROM AuditCategory AS `AuditCategory` WHERE 1", array('AuditCategory.friendlyname' => true), $aArgs), - "SELECT Brand 4" => array("SELECT `Brand` FROM Brand AS `Brand` WHERE (`Brand`.`friendlyname` LIKE '%%')", array('Brand.friendlyname' => true), $aArgs), - "SELECT Brand 5" => array("SELECT `Brand` FROM Brand AS `Brand` WHERE 1", array('Brand.friendlyname' => true), $aArgs), - "SELECT BusinessProcess 6" => array("SELECT `BusinessProcess` FROM BusinessProcess AS `BusinessProcess` WHERE 1", array('BusinessProcess.friendlyname' => true), $aArgs), - "SELECT Change 7" => array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`agent_id` = :current_contact_id) AND (`Change`.`status` NOT IN ('closed')))", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 8" => array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(DATE_FORMAT(`Change`.`start_date`, '%Y-%m-%d')) != 1))", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 9" => array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(`Change`.`category`) != 1))", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 10" => array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(`Change`.`finalclass`) != 1))", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 11" => array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(`Change`.`status`) != 1))", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 12" => array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`id` != :this->id) AND (`Change`.`friendlyname` LIKE '%%'))", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 13" => array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`status` != 'closed') AND (`Change`.`friendlyname` LIKE '%%'))", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 14" => array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`friendlyname` LIKE '%%')", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 15" => array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`status` != 'closed')", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 16" => array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`status` IN ('planned'))", array('Change.friendlyname' => true), $aArgs), - "SELECT Change 17" => array("SELECT `Change` FROM Change AS `Change` WHERE 1", array('Change.friendlyname' => true), $aArgs), - "SELECT ContactType 18" => array("SELECT `ContactType` FROM ContactType AS `ContactType` WHERE 1", array('ContactType.friendlyname' => true), $aArgs), - "SELECT Contact 19" => array("SELECT `Contact` FROM Contact AS `Contact` WHERE (`Contact`.`id` = :id)", array('Contact.friendlyname' => true), $aArgs), - "SELECT Contact 20" => array("SELECT `Contact` FROM Contact AS `Contact` WHERE 1", array('Contact.friendlyname' => true), $aArgs), - "SELECT ContractType 21" => array("SELECT `ContractType` FROM ContractType AS `ContractType` WHERE (`ContractType`.`friendlyname` LIKE '%%')", array('ContractType.friendlyname' => true), $aArgs), - "SELECT ContractType 22" => array("SELECT `ContractType` FROM ContractType AS `ContractType` WHERE 1", array('ContractType.friendlyname' => true), $aArgs), - "SELECT Contract 23" => array("SELECT `Contract` FROM Contract AS `Contract` WHERE 1", array('Contract.friendlyname' => true), $aArgs), - "SELECT CustomerContract 24" => array("SELECT `CustomerContract` FROM CustomerContract AS `CustomerContract` WHERE 1", array('CustomerContract.friendlyname' => true), $aArgs), - "SELECT DBProperty 25" => array("SELECT `DBProperty` FROM DBProperty AS `DBProperty` WHERE (`DBProperty`.`name` = :name)", array('DBProperty.friendlyname' => true), $aArgs), - "SELECT DBServer 26" => array("SELECT `DBServer` FROM DBServer AS `DBServer` WHERE 1", array('DBServer.friendlyname' => true), $aArgs), - "SELECT DatabaseSchema 27" => array("SELECT `DatabaseSchema` FROM DatabaseSchema AS `DatabaseSchema` WHERE 1", array('DatabaseSchema.friendlyname' => true), $aArgs), - "SELECT DeliveryModel 28" => array("SELECT `DeliveryModel` FROM DeliveryModel AS `DeliveryModel` WHERE (`DeliveryModel`.`friendlyname` LIKE '%%')", array('DeliveryModel.friendlyname' => true), $aArgs), - "SELECT DeliveryModel 29" => array("SELECT `DeliveryModel` FROM DeliveryModel AS `DeliveryModel` WHERE 1", array('DeliveryModel.friendlyname' => true), $aArgs), - "SELECT DocumentType 30" => array("SELECT `DocumentType` FROM DocumentType AS `DocumentType` WHERE (`DocumentType`.`friendlyname` LIKE '%%')", array('DocumentType.friendlyname' => true), $aArgs), - "SELECT DocumentType 31" => array("SELECT `DocumentType` FROM DocumentType AS `DocumentType` WHERE 1", array('DocumentType.friendlyname' => true), $aArgs), - "SELECT Document 32" => array("SELECT `Document` FROM Document AS `Document` WHERE 1", array('Document.friendlyname' => true), $aArgs), - "SELECT Enclosure 33" => array("SELECT `Enclosure` FROM Enclosure AS `Enclosure` WHERE ((`Enclosure`.`rack_id` = :this->rack_id) AND (`Enclosure`.`friendlyname` LIKE '%%'))", array('Enclosure.friendlyname' => true), $aArgs), - "SELECT Enclosure 34" => array("SELECT `Enclosure` FROM Enclosure AS `Enclosure` WHERE (`Enclosure`.`friendlyname` LIKE '%%')", array('Enclosure.friendlyname' => true), $aArgs), - "SELECT Enclosure 35" => array("SELECT `Enclosure` FROM Enclosure AS `Enclosure` WHERE 1", array('Enclosure.friendlyname' => true), $aArgs), - "SELECT Farm 36" => array("SELECT `Farm` FROM Farm AS `Farm` WHERE 1", array('Farm.friendlyname' => true), $aArgs), - "SELECT FunctionalCI 37" => array("SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE 1", array('FunctionalCI.friendlyname' => true), $aArgs), - "SELECT Group 38" => array("SELECT `Group` FROM Group AS `Group` WHERE (`Group`.`friendlyname` LIKE '%%')", array('Group.friendlyname' => true), $aArgs), - "SELECT Group 39" => array("SELECT `Group` FROM Group AS `Group` WHERE 1", array('Group.friendlyname' => true), $aArgs), - "SELECT Hypervisor 40" => array("SELECT `Hypervisor` FROM Hypervisor AS `Hypervisor` WHERE 1", array('Hypervisor.friendlyname' => true), $aArgs), - "SELECT IOSVersion 41" => array("SELECT `IOSVersion` FROM IOSVersion AS `IOSVersion` WHERE 1", array('IOSVersion.friendlyname' => true), $aArgs), - "SELECT IPPhone 42" => array("SELECT `IPPhone` FROM IPPhone AS `IPPhone` WHERE 1", array('IPPhone.friendlyname' => true), $aArgs), - "SELECT Licence 43" => array("SELECT `Licence` FROM Licence AS `Licence` WHERE 1", array('Licence.friendlyname' => true), $aArgs), - "SELECT Location 44" => array("SELECT `Location` FROM Location AS `Location` WHERE 1", array('Location.friendlyname' => true), $aArgs), - "SELECT LogicalVolume 45" => array("SELECT `LogicalVolume` FROM LogicalVolume AS `LogicalVolume` WHERE 1", array('LogicalVolume.friendlyname' => true), $aArgs), - "SELECT MiddlewareInstance 46" => array("SELECT `MiddlewareInstance` FROM MiddlewareInstance AS `MiddlewareInstance` WHERE 1", array('MiddlewareInstance.friendlyname' => true), $aArgs), - "SELECT Middleware 47" => array("SELECT `Middleware` FROM Middleware AS `Middleware` WHERE 1", array('Middleware.friendlyname' => true), $aArgs), - "SELECT MobilePhone 48" => array("SELECT `MobilePhone` FROM MobilePhone AS `MobilePhone` WHERE 1", array('MobilePhone.friendlyname' => true), $aArgs), - "SELECT Model 49" => array("SELECT `Model` FROM Model AS `Model` WHERE (((`Model`.`brand_id` = :this->brand_id) AND (`Model`.`type` = :this->finalclass)) AND (`Model`.`friendlyname` LIKE '%%'))", array('Model.friendlyname' => true), $aArgs), - "SELECT Model 50" => array("SELECT `Model` FROM Model AS `Model` WHERE (`Model`.`friendlyname` LIKE '%%')", array('Model.friendlyname' => true), $aArgs), - "SELECT Model 51" => array("SELECT `Model` FROM Model AS `Model` WHERE 1", array('Model.friendlyname' => true), $aArgs), - "SELECT NAS 52" => array("SELECT `NAS` FROM NAS AS `NAS` WHERE 1", array('NAS.friendlyname' => true), $aArgs), - "SELECT NetworkDeviceType 53" => array("SELECT `NetworkDeviceType` FROM NetworkDeviceType AS `NetworkDeviceType` WHERE 1", array('NetworkDeviceType.friendlyname' => true), $aArgs), - "SELECT NetworkDevice 54" => array("SELECT `NetworkDevice` FROM NetworkDevice AS `NetworkDevice` WHERE 1", array('NetworkDevice.friendlyname' => true), $aArgs), - "SELECT NetworkInterface 55" => array("SELECT `NetworkInterface` FROM NetworkInterface AS `NetworkInterface` WHERE 1", array('NetworkInterface.friendlyname' => true), $aArgs), - "SELECT OSFamily 56" => array("SELECT `OSFamily` FROM OSFamily AS `OSFamily` WHERE (`OSFamily`.`friendlyname` LIKE '%%')", array('OSFamily.friendlyname' => true), $aArgs), - "SELECT OSFamily 57" => array("SELECT `OSFamily` FROM OSFamily AS `OSFamily` WHERE 1", array('OSFamily.friendlyname' => true), $aArgs), - "SELECT OSLicence 58" => array("SELECT `OSLicence` FROM OSLicence AS `OSLicence` WHERE ((`OSLicence`.`osversion_id` = :this->osversion_id) AND (`OSLicence`.`friendlyname` LIKE '%%'))", array('OSLicence.friendlyname' => true), $aArgs), - "SELECT OSLicence 59" => array("SELECT `OSLicence` FROM OSLicence AS `OSLicence` WHERE (`OSLicence`.`friendlyname` LIKE '%%')", array('OSLicence.friendlyname' => true), $aArgs), - "SELECT OSLicence 60" => array("SELECT `OSLicence` FROM OSLicence AS `OSLicence` WHERE 1", array('OSLicence.friendlyname' => true), $aArgs), - "SELECT OSVersion 61" => array("SELECT `OSVersion` FROM OSVersion AS `OSVersion` WHERE ((`OSVersion`.`osfamily_id` = :this->osfamily_id) AND (`OSVersion`.`friendlyname` LIKE '%%'))", array('OSVersion.friendlyname' => true), $aArgs), - "SELECT OSVersion 62" => array("SELECT `OSVersion` FROM OSVersion AS `OSVersion` WHERE (`OSVersion`.`friendlyname` LIKE '%%')", array('OSVersion.friendlyname' => true), $aArgs), - "SELECT OSVersion 63" => array("SELECT `OSVersion` FROM OSVersion AS `OSVersion` WHERE 1", array('OSVersion.friendlyname' => true), $aArgs), - "SELECT Organization 64" => array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = :id)", array('Organization.friendlyname' => true), $aArgs), - "SELECT Organization 65" => array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = :id)", array('Organization.friendlyname' => true), $aArgs), - "SELECT Organization 66" => array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", array('Organization.friendlyname' => true), $aArgs), - "SELECT OtherSoftware 67" => array("SELECT `OtherSoftware` FROM OtherSoftware AS `OtherSoftware` WHERE 1", array('OtherSoftware.friendlyname' => true), $aArgs), - "SELECT PCSoftware 68" => array("SELECT `PCSoftware` FROM PCSoftware AS `PCSoftware` WHERE 1", array('PCSoftware.friendlyname' => true), $aArgs), - "SELECT PC 69" => array("SELECT `PC` FROM PC AS `PC` WHERE 1", array('PC.friendlyname' => true), $aArgs), - "SELECT Patch 70" => array("SELECT `Patch` FROM Patch AS `Patch` WHERE 1", array('Patch.friendlyname' => true), $aArgs), - "SELECT Peripheral 71" => array("SELECT `Peripheral` FROM Peripheral AS `Peripheral` WHERE 1", array('Peripheral.friendlyname' => true), $aArgs), - "SELECT Person 72" => array("SELECT `Person` FROM Person AS `Person` WHERE ((`Person`.`org_id` = 1) AND (ISNULL(`Person`.`org_id`) != 1))", array('Person.friendlyname' => true), $aArgs), - "SELECT Person 73" => array("SELECT `Person` FROM Person AS `Person` WHERE ((`Person`.`org_id` = 2) AND (ISNULL(`Person`.`org_id`) != 1))", array('Person.friendlyname' => true), $aArgs), - "SELECT Person 74" => array("SELECT `Person` FROM Person AS `Person` WHERE (`Person`.`id` = :id)", array('Person.friendlyname' => true), $aArgs), - "SELECT Person 75" => array("SELECT `Person` FROM Person AS `Person` WHERE 1", array('Person.friendlyname' => true), $aArgs), - "SELECT Person 76 DISTINCT" => array("SELECT `p` FROM Person AS `p` JOIN UserRequest AS `u` ON `u`.agent_id = `p`.id WHERE (`u`.`status` != 'closed')", array('p.friendlyname' => true), $aArgs), - "SELECT Phone 76" => array("SELECT `Phone` FROM Phone AS `Phone` WHERE 1", array('Phone.friendlyname' => true), $aArgs), - "SELECT PowerConnection 77" => array("SELECT `PowerConnection` FROM PowerConnection AS `PowerConnection` WHERE ((`PowerConnection`.`location_id` = :this->location_id) AND (`PowerConnection`.`friendlyname` LIKE '%%'))", array('PowerConnection.friendlyname' => true), $aArgs), - "SELECT PowerConnection 78" => array("SELECT `PowerConnection` FROM PowerConnection AS `PowerConnection` WHERE (`PowerConnection`.`friendlyname` LIKE '%%')", array('PowerConnection.friendlyname' => true), $aArgs), - "SELECT PowerConnection 79" => array("SELECT `PowerConnection` FROM PowerConnection AS `PowerConnection` WHERE 1", array('PowerConnection.friendlyname' => true), $aArgs), - "SELECT Printer 80" => array("SELECT `Printer` FROM Printer AS `Printer` WHERE 1", array('Printer.friendlyname' => true), $aArgs), - "SELECT ProviderContract 81" => array("SELECT `ProviderContract` FROM ProviderContract AS `ProviderContract` WHERE 1", array('ProviderContract.friendlyname' => true), $aArgs), - "SELECT Query 82" => array("SELECT `Query` FROM Query AS `Query` WHERE 1", array('Query.friendlyname' => true), $aArgs), - "SELECT Rack 83" => array("SELECT `Rack` FROM Rack AS `Rack` WHERE ((`Rack`.`location_id` = :this->location_id) AND (`Rack`.`friendlyname` LIKE '%%'))", array('Rack.friendlyname' => true), $aArgs), - "SELECT Rack 84" => array("SELECT `Rack` FROM Rack AS `Rack` WHERE (`Rack`.`friendlyname` LIKE '%%')", array('Rack.friendlyname' => true), $aArgs), - "SELECT Rack 85" => array("SELECT `Rack` FROM Rack AS `Rack` WHERE 1", array('Rack.friendlyname' => true), $aArgs), - "SELECT SANSwitch 86" => array("SELECT `SANSwitch` FROM SANSwitch AS `SANSwitch` WHERE 1", array('SANSwitch.friendlyname' => true), $aArgs), - "SELECT SLA 87" => array("SELECT `SLA` FROM SLA AS `SLA` WHERE 1", array('SLA.friendlyname' => true), $aArgs), - "SELECT SLT 88" => array("SELECT `SLT` FROM SLT AS `SLT` WHERE 1", array('SLT.friendlyname' => true), $aArgs), - "SELECT Server 89" => array("SELECT `Server` FROM Server AS `Server` WHERE (`Server`.`name` NOT LIKE '%2')", array('Server.friendlyname' => true), $aArgs), - "SELECT Server 90" => array("SELECT `Server` FROM Server AS `Server` WHERE 1", array('Server.friendlyname' => true), $aArgs), - "SELECT ServiceFamily 91" => array("SELECT `ServiceFamily` FROM ServiceFamily AS `ServiceFamily` WHERE (`ServiceFamily`.`friendlyname` LIKE '%%')", array('ServiceFamily.friendlyname' => true), $aArgs), - "SELECT ServiceFamily 92" => array("SELECT `ServiceFamily` FROM ServiceFamily AS `ServiceFamily` WHERE 1", array('ServiceFamily.friendlyname' => true), $aArgs), - "SELECT ServiceSubcategory 93" => array("SELECT `ServiceSubcategory` FROM ServiceSubcategory AS `ServiceSubcategory` WHERE ((((`ServiceSubcategory`.`service_id` = :this->service_id) AND (ISNULL(:this->request_type) OR (`ServiceSubcategory`.`request_type` = :this->request_type))) AND (`ServiceSubcategory`.`status` != 'obsolete')) AND (`ServiceSubcategory`.`friendlyname` LIKE '%%'))", array('ServiceSubcategory.friendlyname' => true), $aArgs), - "SELECT ServiceSubcategory 94" => array("SELECT `ServiceSubcategory` FROM ServiceSubcategory AS `ServiceSubcategory` WHERE (`ServiceSubcategory`.`friendlyname` LIKE '%%')", array('ServiceSubcategory.friendlyname' => true), $aArgs), - "SELECT ServiceSubcategory 95" => array("SELECT `ServiceSubcategory` FROM ServiceSubcategory AS `ServiceSubcategory` WHERE 1", array('ServiceSubcategory.friendlyname' => true), $aArgs), - "SELECT Service 96" => array("SELECT `Service` FROM Service AS `Service` WHERE (`Service`.`friendlyname` LIKE '%%')", array('Service.friendlyname' => true), $aArgs), - "SELECT Service 97" => array("SELECT `Service` FROM Service AS `Service` WHERE 1", array('Service.friendlyname' => true), $aArgs), - "SELECT ShortcutOQL 98" => array("SELECT `ShortcutOQL` FROM ShortcutOQL AS `ShortcutOQL` WHERE (`ShortcutOQL`.`id` = :id)", array('ShortcutOQL.friendlyname' => true), $aArgs), - "SELECT Shortcut 99" => array("SELECT `Shortcut` FROM Shortcut AS `Shortcut` WHERE (`Shortcut`.`user_id` = :user_id)", array('Shortcut.friendlyname' => true), $aArgs), - "SELECT Software 100" => array("SELECT `Software` FROM Software AS `Software` WHERE 1", array('Software.friendlyname' => true), $aArgs), - "SELECT StorageSystem 101" => array("SELECT `StorageSystem` FROM StorageSystem AS `StorageSystem` WHERE 1", array('StorageSystem.friendlyname' => true), $aArgs), - "SELECT Subnet 102" => array("SELECT `Subnet` FROM Subnet AS `Subnet` WHERE 1", array('Subnet.friendlyname' => true), $aArgs), - "SELECT SynchroDataSource 103" => array("SELECT `SynchroDataSource` FROM SynchroDataSource AS `SynchroDataSource` WHERE 1", array('SynchroDataSource.friendlyname' => true), $aArgs), - "SELECT Tablet 104" => array("SELECT `Tablet` FROM Tablet AS `Tablet` WHERE 1", array('Tablet.friendlyname' => true), $aArgs), - "SELECT TapeLibrary 105" => array("SELECT `TapeLibrary` FROM TapeLibrary AS `TapeLibrary` WHERE 1", array('TapeLibrary.friendlyname' => true), $aArgs), - "SELECT Team 106" => array("SELECT `Team` FROM Team AS `Team` WHERE (`Team`.`friendlyname` LIKE '%%')", array('Team.friendlyname' => true), $aArgs), - "SELECT Team 107" => array("SELECT `Team` FROM Team AS `Team` WHERE 1", array('Team.friendlyname' => true), $aArgs), - "SELECT Trigger 108" => array("SELECT `Trigger` FROM Trigger AS `Trigger` WHERE 1", array('Trigger.friendlyname' => true), $aArgs), - "SELECT URP_Profiles 109" => array("SELECT `URP_Profiles` FROM URP_Profiles AS `URP_Profiles` WHERE 1", array('URP_Profiles.friendlyname' => true), $aArgs), - "SELECT URP_UserProfile 110" => array("SELECT `URP_UserProfile` FROM URP_UserProfile AS `URP_UserProfile` WHERE (`URP_UserProfile`.`userid` = :userid)", array('URP_UserProfile.friendlyname' => true), $aArgs), - "SELECT UserDashboard 111" => array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = :user_id) AND (`UserDashboard`.`menu_code` = :menu_code))", array('UserDashboard.friendlyname' => true), $aArgs), - "SELECT UserInternal 112" => array("SELECT `UserInternal` FROM UserInternal AS `UserInternal` WHERE ((`UserInternal`.`login` = :login) AND (`UserInternal`.`status` = :UserInternal_status))", array('UserInternal.friendlyname' => true), $aArgs), - "SELECT UserLocal 113" => array("SELECT `UserLocal` FROM UserLocal AS `UserLocal` WHERE (`UserLocal`.`id` = :id)", array('UserLocal.friendlyname' => true), $aArgs), - "SELECT User 127" => array("SELECT `User` FROM User AS `User` WHERE (`User`.`friendlyname` LIKE '%%')", array('User.friendlyname' => true), $aArgs), - "SELECT User 128" => array("SELECT `User` FROM User AS `User` WHERE (`User`.`id` = :id)", array('User.friendlyname' => true), $aArgs), - "SELECT User 129" => array("SELECT `User` FROM User AS `User` WHERE 1", array('User.friendlyname' => true), $aArgs), - "SELECT VLAN 130" => array("SELECT `VLAN` FROM VLAN AS `VLAN` WHERE 1", array('VLAN.friendlyname' => true), $aArgs), - "SELECT VirtualMachine 131" => array("SELECT `VirtualMachine` FROM VirtualMachine AS `VirtualMachine` WHERE 1", array('VirtualMachine.friendlyname' => true), $aArgs), - "SELECT WebApplication 132" => array("SELECT `WebApplication` FROM WebApplication AS `WebApplication` WHERE 1", array('WebApplication.friendlyname' => true), $aArgs), - "SELECT WebServer 133" => array("SELECT `WebServer` FROM WebServer AS `WebServer` WHERE 1", array('WebServer.friendlyname' => true), $aArgs), - "SELECT appUserPreferences 134" => array("SELECT `appUserPreferences` FROM appUserPreferences AS `appUserPreferences` WHERE (`appUserPreferences`.`id` = :id)", array('appUserPreferences.friendlyname' => true), $aArgs), - "SELECT appUserPreferences 135" => array("SELECT `appUserPreferences` FROM appUserPreferences AS `appUserPreferences` WHERE (`appUserPreferences`.`userid` = :userid)", array('appUserPreferences.friendlyname' => true), $aArgs), - "SELECT c 136" => array("SELECT `c` FROM CustomerContract AS `c` WHERE (`c`.`end_date` < '2018-12-01')", array('c.friendlyname' => true), $aArgs), - "SELECT c 137" => array("SELECT `c` FROM ProviderContract AS `c` WHERE (`c`.`end_date` < '2018-12-01')", array('c.friendlyname' => true), $aArgs), - "SELECT datasource 138" => array("SELECT `datasource` FROM SynchroDataSource AS `datasource` WHERE 1", array('datasource.friendlyname' => true), $aArgs), - "SELECT i 139" => array("SELECT `i` FROM UserRequest AS `i` WHERE ((`i`.`agent_id` = :current_contact_id) AND (`i`.`status` NOT IN ('closed', 'resolved')))", array('i.friendlyname' => true), $aArgs), - "SELECT s 140" => array("SELECT `s` FROM Service AS `s` JOIN lnkCustomerContractToService AS `l1` ON `l1`.service_id = `s`.id JOIN CustomerContract AS `cc` ON `l1`.customercontract_id = `cc`.id WHERE (((`cc`.`org_id` = :this->org_id) AND (`s`.`status` != 'obsolete')) AND (`s`.`friendlyname` LIKE '%%'))", array('s.friendlyname' => true), $aArgs), - "SELECT t 141" => array("SELECT `t` FROM Team AS `t` JOIN lnkDeliveryModelToContact AS `l1` ON `l1`.contact_id = `t`.id JOIN DeliveryModel AS `dm` ON `l1`.deliverymodel_id = `dm`.id JOIN Organization AS `o` ON `o`.deliverymodel_id = `dm`.id WHERE ((`o`.`id` = :this->org_id) AND (`t`.`friendlyname` LIKE '%%'))", array('t.friendlyname' => true), $aArgs), - "SELECT t 142" => array("SELECT `t` FROM TriggerOnObjectCreate AS `t` WHERE (`t`.`target_class` IN ('appUserPreferences'))", array('t.friendlyname' => true), $aArgs), - "SELECT t 143" => array("SELECT `t` FROM TriggerOnObjectUpdate AS `t` WHERE (`t`.`target_class` IN ('appUserPreferences'))", array('t.friendlyname' => true), $aArgs), - ); - - $aData["SELECT UNION 1"] = array("SELECT `User` FROM User AS `User` WHERE 1 UNION SELECT `User` FROM User AS `User` WHERE (`User`.`id` = 3)", array(), array(), null, array(), 0, 0); - $aData["SELECT 1"] = array("SELECT `UserInternal` FROM UserInternal AS `UserInternal` WHERE ((`UserInternal`.`login` = 'admin') AND (`UserInternal`.`status` = 'enabled'))", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 2"] = array("SELECT `Contact` FROM Contact AS `Contact` WHERE (`Contact`.`id` = '987654321')", array(), array(), null, array(), 0, 0); - $aData["SELECT 3"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", array(), array(), null, array(), 4, 0); - $aData["SELECT 4"] = array("SELECT `appUserPreferences` FROM appUserPreferences AS `appUserPreferences` WHERE (`appUserPreferences`.`userid` = '1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 5"] = array("SELECT `Shortcut` FROM Shortcut AS `Shortcut` WHERE (`Shortcut`.`user_id` = '1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 6"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '1') AND (`UserDashboard`.`menu_code` = 'WelcomeMenuPage'))", array(), array(), null, array(), 0, 0); - $aData["SELECT 7"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '1') AND (`UserDashboard`.`menu_code` = 'WelcomeMenuPage'))", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 8"] = array("SELECT `BusinessProcess` FROM BusinessProcess AS `BusinessProcess` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 9"] = array("SELECT `ApplicationSolution` FROM ApplicationSolution AS `ApplicationSolution` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 10"] = array("SELECT `Contact` FROM Contact AS `Contact` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 11"] = array("SELECT `Location` FROM Location AS `Location` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 12"] = array("SELECT `Contract` FROM Contract AS `Contract` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 13"] = array("SELECT `Server` FROM Server AS `Server` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 14"] = array("SELECT `NetworkDevice` FROM NetworkDevice AS `NetworkDevice` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 15"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` != 'closed')", array(), array(), null, array(), 0, 0); - $aData["SELECT 16"] = array("SELECT `i` FROM UserRequest AS `i` WHERE ((`i`.`agent_id` = 1) AND (`i`.`status` NOT IN ('closed', 'resolved')))", array(), array(), null, array(), 3, 0); - $aData["SELECT 17"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 18"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", array(), array(), null, array(), 52, 0); - $aData["SELECT 19"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = '')", array(), array(), null, array(), 0, 0); - $aData["SELECT 20"] = array("SELECT `DBProperty` FROM DBProperty AS `DBProperty` WHERE (`DBProperty`.`name` = 'database_uuid')", array(), array(), null, array(), 0, 0); - $aData["SELECT 21"] = array("SELECT `DBProperty` FROM DBProperty AS `DBProperty` WHERE (`DBProperty`.`name` = 'database_uuid')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 22"] = array("SELECT `WebServer` FROM WebServer AS `WebServer` WHERE 1", array(), array(), unserialize('a:1:{s:9:"WebServer";a:6:{i:0;s:18:"business_criticity";i:1;s:11:"description";i:2;s:4:"name";i:3;s:12:"friendlyname";i:4;s:17:"obsolescence_flag";i:5;s:10:"finalclass";}}'), array(), 0, 0); - $aData["SELECT 23"] = array("SELECT `WebServer` FROM WebServer AS `WebServer` WHERE 1", array(), array(), unserialize('a:1:{s:9:"WebServer";a:6:{i:0;s:18:"business_criticity";i:1;s:11:"description";i:2;s:4:"name";i:3;s:12:"friendlyname";i:4;s:17:"obsolescence_flag";i:5;s:10:"finalclass";}}'), array(), 0, 3); - $aData["SELECT 24"] = array("SELECT `WebServer` FROM WebServer AS `WebServer` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 25"] = array("SELECT `WebServer` FROM WebServer AS `WebServer` WHERE 1", array(), array(), null, array(), 0, 3); - $aData["SELECT 26"] = array("SELECT `L` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE 1", unserialize('a:1:{s:14:"L.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 27"] = array("SELECT `L` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE 1", unserialize('a:1:{s:14:"L.friendlyname";b:1;}'), array(), null, array(), 3, 0); - $aData["SELECT 28"] = array("SELECT `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 29"] = array("SELECT `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, array(), 3, 0); - $aData["SELECT 30"] = array("SELECT `L`, `P` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 31"] = array("SELECT `L`, `P` FROM Location AS `L` JOIN Person AS `P` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, array(), 3, 0); - $aData["SELECT 32"] = array("SELECT `P`, `L` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 33"] = array("SELECT `P`, `L` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, array(), 3, 0); - $aData["SELECT 34"] = array("SELECT `L`, `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 35"] = array("SELECT `L`, `P` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", array(), array(), null, array(), 3, 0); - $aData["SELECT 36"] = array("SELECT `L` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", unserialize('a:1:{s:14:"L.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 37"] = array("SELECT `L` FROM Person AS `P` JOIN Location AS `L` ON `P`.location_id = `L`.id WHERE 1", unserialize('a:1:{s:14:"L.friendlyname";b:1;}'), array(), null, array(), 3, 0); - $aData["SELECT 38"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` JOIN Organization AS `Organization` ON `UserRequest`.org_id = `Organization`.id JOIN Organization AS `Organization1` ON `Organization`.parent_id BELOW `Organization1`.id WHERE (`Organization1`.`id` = '3')", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 39"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` JOIN Organization AS `Organization` ON `UserRequest`.org_id = `Organization`.id JOIN Organization AS `Organization1` ON `Organization`.parent_id BELOW `Organization1`.id WHERE (`Organization1`.`id` = '3')", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 40"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (('2018-12-01' < `UserRequest`.`start_date`) AND (ISNULL(DATE_FORMAT(`UserRequest`.`start_date`, '%Y-%m-%d')) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 41"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (('2018-12-01' < `UserRequest`.`start_date`) AND (ISNULL(DATE_FORMAT(`UserRequest`.`start_date`, '%Y-%m-%d')) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 42"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`agent_id` = '') AND (`UserRequest`.`status` NOT IN ('closed', 'resolved')))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 43"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`agent_id` = '') AND (`UserRequest`.`status` NOT IN ('closed', 'resolved')))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 44"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`caller_id` = '') AND (`UserRequest`.`status` NOT IN ('closed')))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 45"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`caller_id` = '') AND (`UserRequest`.`status` NOT IN ('closed')))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 46"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` IN ('escalated_tto', 'escalated_ttr')) OR (`UserRequest`.`escalation_flag` = 'yes'))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 47"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` IN ('escalated_tto', 'escalated_ttr')) OR (`UserRequest`.`escalation_flag` = 'yes'))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 48"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`agent_id`) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 49"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`agent_id`) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 50"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`finalclass`) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 51"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`finalclass`) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 52"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`org_id`) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 53"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`org_id`) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 54"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`status`) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 55"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` NOT IN ('closed', 'rejected')) AND (ISNULL(`UserRequest`.`status`) != 1))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 56"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (ISNULL(`UserRequest`.`org_id`) != 1)", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 57"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (ISNULL(`UserRequest`.`org_id`) != 1)", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 58"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` != 'closed')", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 59"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` != 'closed')", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 60"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` NOT IN ('closed'))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 61"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` NOT IN ('closed'))", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 62"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE 1", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 63"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE 1", unserialize('a:1:{s:24:"UserRequest.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 64"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`id` = '987654321')", array(), array(), null, array(), 0, 0); - $aData["SELECT 65"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`id` = '987654321')", array(), array(), null, array(), 0, 0); - $aData["SELECT 66"] = array("SELECT `ApplicationSolution` FROM ApplicationSolution AS `ApplicationSolution` WHERE 1", unserialize('a:1:{s:32:"ApplicationSolution.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 67"] = array("SELECT `ApplicationSolution` FROM ApplicationSolution AS `ApplicationSolution` WHERE 1", unserialize('a:1:{s:32:"ApplicationSolution.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 68"] = array("SELECT `AuditCategory` FROM AuditCategory AS `AuditCategory` WHERE 1", unserialize('a:1:{s:26:"AuditCategory.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 69"] = array("SELECT `AuditCategory` FROM AuditCategory AS `AuditCategory` WHERE 1", unserialize('a:1:{s:26:"AuditCategory.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 70"] = array("SELECT `Brand` FROM Brand AS `Brand` WHERE (`Brand`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:18:"Brand.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 71"] = array("SELECT `Brand` FROM Brand AS `Brand` WHERE (`Brand`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:18:"Brand.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 72"] = array("SELECT `Brand` FROM Brand AS `Brand` WHERE 1", unserialize('a:1:{s:18:"Brand.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 73"] = array("SELECT `Brand` FROM Brand AS `Brand` WHERE 1", unserialize('a:1:{s:18:"Brand.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 74"] = array("SELECT `BusinessProcess` FROM BusinessProcess AS `BusinessProcess` WHERE 1", unserialize('a:1:{s:28:"BusinessProcess.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 75"] = array("SELECT `BusinessProcess` FROM BusinessProcess AS `BusinessProcess` WHERE 1", unserialize('a:1:{s:28:"BusinessProcess.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 76"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`agent_id` = '') AND (`Change`.`status` NOT IN ('closed')))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 77"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`agent_id` = '') AND (`Change`.`status` NOT IN ('closed')))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 78"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(DATE_FORMAT(`Change`.`start_date`, '%Y-%m-%d')) != 1))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 79"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(DATE_FORMAT(`Change`.`start_date`, '%Y-%m-%d')) != 1))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 80"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(`Change`.`category`) != 1))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 81"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(`Change`.`category`) != 1))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 82"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(`Change`.`finalclass`) != 1))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 83"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(`Change`.`finalclass`) != 1))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 84"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(`Change`.`status`) != 1))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 85"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`creation_date` > '2018-12-01') AND (ISNULL(`Change`.`status`) != 1))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 86"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`id` != '3') AND (`Change`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 87"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`id` != '3') AND (`Change`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 88"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`status` != 'closed') AND (`Change`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 89"] = array("SELECT `Change` FROM Change AS `Change` WHERE ((`Change`.`status` != 'closed') AND (`Change`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 90"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 91"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 92"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`status` != 'closed')", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 93"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`status` != 'closed')", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 94"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`status` IN ('planned'))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 95"] = array("SELECT `Change` FROM Change AS `Change` WHERE (`Change`.`status` IN ('planned'))", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 96"] = array("SELECT `Change` FROM Change AS `Change` WHERE 1", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 97"] = array("SELECT `Change` FROM Change AS `Change` WHERE 1", unserialize('a:1:{s:19:"Change.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 98"] = array("SELECT `ContactType` FROM ContactType AS `ContactType` WHERE 1", unserialize('a:1:{s:24:"ContactType.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 99"] = array("SELECT `ContactType` FROM ContactType AS `ContactType` WHERE 1", unserialize('a:1:{s:24:"ContactType.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 100"] = array("SELECT `Contact` FROM Contact AS `Contact` WHERE (`Contact`.`id` = '3')", unserialize('a:1:{s:20:"Contact.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 101"] = array("SELECT `Contact` FROM Contact AS `Contact` WHERE (`Contact`.`id` = '3')", unserialize('a:1:{s:20:"Contact.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 102"] = array("SELECT `Contact` FROM Contact AS `Contact` WHERE 1", unserialize('a:1:{s:20:"Contact.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 103"] = array("SELECT `Contact` FROM Contact AS `Contact` WHERE 1", unserialize('a:1:{s:20:"Contact.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 104"] = array("SELECT `ContractType` FROM ContractType AS `ContractType` WHERE (`ContractType`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:25:"ContractType.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 105"] = array("SELECT `ContractType` FROM ContractType AS `ContractType` WHERE (`ContractType`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:25:"ContractType.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 106"] = array("SELECT `ContractType` FROM ContractType AS `ContractType` WHERE 1", unserialize('a:1:{s:25:"ContractType.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 107"] = array("SELECT `ContractType` FROM ContractType AS `ContractType` WHERE 1", unserialize('a:1:{s:25:"ContractType.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 108"] = array("SELECT `Contract` FROM Contract AS `Contract` WHERE 1", unserialize('a:1:{s:21:"Contract.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 109"] = array("SELECT `Contract` FROM Contract AS `Contract` WHERE 1", unserialize('a:1:{s:21:"Contract.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 110"] = array("SELECT `CustomerContract` FROM CustomerContract AS `CustomerContract` WHERE 1", unserialize('a:1:{s:29:"CustomerContract.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 111"] = array("SELECT `CustomerContract` FROM CustomerContract AS `CustomerContract` WHERE 1", unserialize('a:1:{s:29:"CustomerContract.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 112"] = array("SELECT `DBProperty` FROM DBProperty AS `DBProperty` WHERE (`DBProperty`.`name` = 'database_uuid')", unserialize('a:1:{s:23:"DBProperty.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 113"] = array("SELECT `DBProperty` FROM DBProperty AS `DBProperty` WHERE (`DBProperty`.`name` = 'database_uuid')", unserialize('a:1:{s:23:"DBProperty.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 114"] = array("SELECT `DBServer` FROM DBServer AS `DBServer` WHERE 1", unserialize('a:1:{s:21:"DBServer.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 115"] = array("SELECT `DBServer` FROM DBServer AS `DBServer` WHERE 1", unserialize('a:1:{s:21:"DBServer.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 116"] = array("SELECT `DatabaseSchema` FROM DatabaseSchema AS `DatabaseSchema` WHERE 1", unserialize('a:1:{s:27:"DatabaseSchema.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 117"] = array("SELECT `DatabaseSchema` FROM DatabaseSchema AS `DatabaseSchema` WHERE 1", unserialize('a:1:{s:27:"DatabaseSchema.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 118"] = array("SELECT `DeliveryModel` FROM DeliveryModel AS `DeliveryModel` WHERE (`DeliveryModel`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:26:"DeliveryModel.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 119"] = array("SELECT `DeliveryModel` FROM DeliveryModel AS `DeliveryModel` WHERE (`DeliveryModel`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:26:"DeliveryModel.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 120"] = array("SELECT `DeliveryModel` FROM DeliveryModel AS `DeliveryModel` WHERE 1", unserialize('a:1:{s:26:"DeliveryModel.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 121"] = array("SELECT `DeliveryModel` FROM DeliveryModel AS `DeliveryModel` WHERE 1", unserialize('a:1:{s:26:"DeliveryModel.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 122"] = array("SELECT `DocumentType` FROM DocumentType AS `DocumentType` WHERE (`DocumentType`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:25:"DocumentType.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 123"] = array("SELECT `DocumentType` FROM DocumentType AS `DocumentType` WHERE (`DocumentType`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:25:"DocumentType.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 124"] = array("SELECT `DocumentType` FROM DocumentType AS `DocumentType` WHERE 1", unserialize('a:1:{s:25:"DocumentType.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 125"] = array("SELECT `DocumentType` FROM DocumentType AS `DocumentType` WHERE 1", unserialize('a:1:{s:25:"DocumentType.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 126"] = array("SELECT `Document` FROM Document AS `Document` WHERE 1", unserialize('a:1:{s:21:"Document.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 127"] = array("SELECT `Document` FROM Document AS `Document` WHERE 1", unserialize('a:1:{s:21:"Document.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 128"] = array("SELECT `Enclosure` FROM Enclosure AS `Enclosure` WHERE ((`Enclosure`.`rack_id` = '3') AND (`Enclosure`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:22:"Enclosure.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 129"] = array("SELECT `Enclosure` FROM Enclosure AS `Enclosure` WHERE ((`Enclosure`.`rack_id` = '3') AND (`Enclosure`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:22:"Enclosure.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 130"] = array("SELECT `Enclosure` FROM Enclosure AS `Enclosure` WHERE (`Enclosure`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:22:"Enclosure.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 131"] = array("SELECT `Enclosure` FROM Enclosure AS `Enclosure` WHERE (`Enclosure`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:22:"Enclosure.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 132"] = array("SELECT `Enclosure` FROM Enclosure AS `Enclosure` WHERE 1", unserialize('a:1:{s:22:"Enclosure.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 133"] = array("SELECT `Enclosure` FROM Enclosure AS `Enclosure` WHERE 1", unserialize('a:1:{s:22:"Enclosure.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 134"] = array("SELECT `Farm` FROM Farm AS `Farm` WHERE 1", unserialize('a:1:{s:17:"Farm.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 135"] = array("SELECT `Farm` FROM Farm AS `Farm` WHERE 1", unserialize('a:1:{s:17:"Farm.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 136"] = array("SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE 1", unserialize('a:1:{s:25:"FunctionalCI.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 137"] = array("SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE 1", unserialize('a:1:{s:25:"FunctionalCI.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 138"] = array("SELECT `Group` FROM Group AS `Group` WHERE (`Group`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:18:"Group.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 139"] = array("SELECT `Group` FROM Group AS `Group` WHERE (`Group`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:18:"Group.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 140"] = array("SELECT `Group` FROM Group AS `Group` WHERE 1", unserialize('a:1:{s:18:"Group.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 141"] = array("SELECT `Group` FROM Group AS `Group` WHERE 1", unserialize('a:1:{s:18:"Group.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 142"] = array("SELECT `Hypervisor` FROM Hypervisor AS `Hypervisor` WHERE 1", unserialize('a:1:{s:23:"Hypervisor.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 143"] = array("SELECT `Hypervisor` FROM Hypervisor AS `Hypervisor` WHERE 1", unserialize('a:1:{s:23:"Hypervisor.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 144"] = array("SELECT `IOSVersion` FROM IOSVersion AS `IOSVersion` WHERE 1", unserialize('a:1:{s:23:"IOSVersion.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 145"] = array("SELECT `IOSVersion` FROM IOSVersion AS `IOSVersion` WHERE 1", unserialize('a:1:{s:23:"IOSVersion.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 146"] = array("SELECT `IPPhone` FROM IPPhone AS `IPPhone` WHERE 1", unserialize('a:1:{s:20:"IPPhone.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 147"] = array("SELECT `IPPhone` FROM IPPhone AS `IPPhone` WHERE 1", unserialize('a:1:{s:20:"IPPhone.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 148"] = array("SELECT `Licence` FROM Licence AS `Licence` WHERE 1", unserialize('a:1:{s:20:"Licence.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 149"] = array("SELECT `Licence` FROM Licence AS `Licence` WHERE 1", unserialize('a:1:{s:20:"Licence.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 150"] = array("SELECT `Location` FROM Location AS `Location` WHERE 1", unserialize('a:1:{s:21:"Location.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 151"] = array("SELECT `Location` FROM Location AS `Location` WHERE 1", unserialize('a:1:{s:21:"Location.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 152"] = array("SELECT `LogicalVolume` FROM LogicalVolume AS `LogicalVolume` WHERE 1", unserialize('a:1:{s:26:"LogicalVolume.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 153"] = array("SELECT `LogicalVolume` FROM LogicalVolume AS `LogicalVolume` WHERE 1", unserialize('a:1:{s:26:"LogicalVolume.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 154"] = array("SELECT `MiddlewareInstance` FROM MiddlewareInstance AS `MiddlewareInstance` WHERE 1", unserialize('a:1:{s:31:"MiddlewareInstance.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 155"] = array("SELECT `MiddlewareInstance` FROM MiddlewareInstance AS `MiddlewareInstance` WHERE 1", unserialize('a:1:{s:31:"MiddlewareInstance.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 156"] = array("SELECT `Middleware` FROM Middleware AS `Middleware` WHERE 1", unserialize('a:1:{s:23:"Middleware.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 157"] = array("SELECT `Middleware` FROM Middleware AS `Middleware` WHERE 1", unserialize('a:1:{s:23:"Middleware.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 158"] = array("SELECT `MobilePhone` FROM MobilePhone AS `MobilePhone` WHERE 1", unserialize('a:1:{s:24:"MobilePhone.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 159"] = array("SELECT `MobilePhone` FROM MobilePhone AS `MobilePhone` WHERE 1", unserialize('a:1:{s:24:"MobilePhone.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 160"] = array("SELECT `Model` FROM Model AS `Model` WHERE (((`Model`.`brand_id` = '1') AND (`Model`.`type` = 'NetworkDevice')) AND (`Model`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:18:"Model.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 161"] = array("SELECT `Model` FROM Model AS `Model` WHERE (((`Model`.`brand_id` = '1') AND (`Model`.`type` = 'NetworkDevice')) AND (`Model`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:18:"Model.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 162"] = array("SELECT `Model` FROM Model AS `Model` WHERE (`Model`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:18:"Model.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 163"] = array("SELECT `Model` FROM Model AS `Model` WHERE (`Model`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:18:"Model.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 164"] = array("SELECT `Model` FROM Model AS `Model` WHERE 1", unserialize('a:1:{s:18:"Model.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 165"] = array("SELECT `Model` FROM Model AS `Model` WHERE 1", unserialize('a:1:{s:18:"Model.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 166"] = array("SELECT `NAS` FROM NAS AS `NAS` WHERE 1", unserialize('a:1:{s:16:"NAS.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 167"] = array("SELECT `NAS` FROM NAS AS `NAS` WHERE 1", unserialize('a:1:{s:16:"NAS.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 168"] = array("SELECT `NetworkDeviceType` FROM NetworkDeviceType AS `NetworkDeviceType` WHERE 1", unserialize('a:1:{s:30:"NetworkDeviceType.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 169"] = array("SELECT `NetworkDeviceType` FROM NetworkDeviceType AS `NetworkDeviceType` WHERE 1", unserialize('a:1:{s:30:"NetworkDeviceType.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 170"] = array("SELECT `NetworkDevice` FROM NetworkDevice AS `NetworkDevice` WHERE 1", unserialize('a:1:{s:26:"NetworkDevice.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 171"] = array("SELECT `NetworkDevice` FROM NetworkDevice AS `NetworkDevice` WHERE 1", unserialize('a:1:{s:26:"NetworkDevice.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 172"] = array("SELECT `NetworkInterface` FROM NetworkInterface AS `NetworkInterface` WHERE 1", unserialize('a:1:{s:29:"NetworkInterface.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 173"] = array("SELECT `NetworkInterface` FROM NetworkInterface AS `NetworkInterface` WHERE 1", unserialize('a:1:{s:29:"NetworkInterface.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 174"] = array("SELECT `OSFamily` FROM OSFamily AS `OSFamily` WHERE (`OSFamily`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:21:"OSFamily.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 175"] = array("SELECT `OSFamily` FROM OSFamily AS `OSFamily` WHERE (`OSFamily`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:21:"OSFamily.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 176"] = array("SELECT `OSFamily` FROM OSFamily AS `OSFamily` WHERE 1", unserialize('a:1:{s:21:"OSFamily.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 177"] = array("SELECT `OSFamily` FROM OSFamily AS `OSFamily` WHERE 1", unserialize('a:1:{s:21:"OSFamily.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 178"] = array("SELECT `OSLicence` FROM OSLicence AS `OSLicence` WHERE ((`OSLicence`.`osversion_id` = '8') AND (`OSLicence`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:22:"OSLicence.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 179"] = array("SELECT `OSLicence` FROM OSLicence AS `OSLicence` WHERE ((`OSLicence`.`osversion_id` = '8') AND (`OSLicence`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:22:"OSLicence.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 180"] = array("SELECT `OSLicence` FROM OSLicence AS `OSLicence` WHERE (`OSLicence`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:22:"OSLicence.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 181"] = array("SELECT `OSLicence` FROM OSLicence AS `OSLicence` WHERE (`OSLicence`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:22:"OSLicence.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 182"] = array("SELECT `OSLicence` FROM OSLicence AS `OSLicence` WHERE 1", unserialize('a:1:{s:22:"OSLicence.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 183"] = array("SELECT `OSLicence` FROM OSLicence AS `OSLicence` WHERE 1", unserialize('a:1:{s:22:"OSLicence.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 184"] = array("SELECT `OSVersion` FROM OSVersion AS `OSVersion` WHERE ((`OSVersion`.`osfamily_id` = '6') AND (`OSVersion`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:22:"OSVersion.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 185"] = array("SELECT `OSVersion` FROM OSVersion AS `OSVersion` WHERE ((`OSVersion`.`osfamily_id` = '6') AND (`OSVersion`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:22:"OSVersion.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 186"] = array("SELECT `OSVersion` FROM OSVersion AS `OSVersion` WHERE (`OSVersion`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:22:"OSVersion.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 187"] = array("SELECT `OSVersion` FROM OSVersion AS `OSVersion` WHERE (`OSVersion`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:22:"OSVersion.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 188"] = array("SELECT `OSVersion` FROM OSVersion AS `OSVersion` WHERE 1", unserialize('a:1:{s:22:"OSVersion.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 189"] = array("SELECT `OSVersion` FROM OSVersion AS `OSVersion` WHERE 1", unserialize('a:1:{s:22:"OSVersion.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 190"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = '3')", unserialize('a:1:{s:25:"Organization.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 191"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = '3')", unserialize('a:1:{s:25:"Organization.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 192"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", unserialize('a:1:{s:25:"Organization.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 193"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", unserialize('a:1:{s:25:"Organization.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 194"] = array("SELECT `OtherSoftware` FROM OtherSoftware AS `OtherSoftware` WHERE 1", unserialize('a:1:{s:26:"OtherSoftware.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 195"] = array("SELECT `OtherSoftware` FROM OtherSoftware AS `OtherSoftware` WHERE 1", unserialize('a:1:{s:26:"OtherSoftware.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 196"] = array("SELECT `PCSoftware` FROM PCSoftware AS `PCSoftware` WHERE 1", unserialize('a:1:{s:23:"PCSoftware.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 197"] = array("SELECT `PCSoftware` FROM PCSoftware AS `PCSoftware` WHERE 1", unserialize('a:1:{s:23:"PCSoftware.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 198"] = array("SELECT `PC` FROM PC AS `PC` WHERE 1", unserialize('a:1:{s:15:"PC.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 199"] = array("SELECT `PC` FROM PC AS `PC` WHERE 1", unserialize('a:1:{s:15:"PC.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 200"] = array("SELECT `Patch` FROM Patch AS `Patch` WHERE 1", unserialize('a:1:{s:18:"Patch.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 201"] = array("SELECT `Patch` FROM Patch AS `Patch` WHERE 1", unserialize('a:1:{s:18:"Patch.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 202"] = array("SELECT `Peripheral` FROM Peripheral AS `Peripheral` WHERE 1", unserialize('a:1:{s:23:"Peripheral.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 203"] = array("SELECT `Peripheral` FROM Peripheral AS `Peripheral` WHERE 1", unserialize('a:1:{s:23:"Peripheral.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 204"] = array("SELECT `Person` FROM Person AS `Person` WHERE ((`Person`.`org_id` = 1) AND (ISNULL(`Person`.`org_id`) != 1))", unserialize('a:1:{s:19:"Person.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 205"] = array("SELECT `Person` FROM Person AS `Person` WHERE ((`Person`.`org_id` = 1) AND (ISNULL(`Person`.`org_id`) != 1))", unserialize('a:1:{s:19:"Person.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 206"] = array("SELECT `Person` FROM Person AS `Person` WHERE ((`Person`.`org_id` = 2) AND (ISNULL(`Person`.`org_id`) != 1))", unserialize('a:1:{s:19:"Person.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 207"] = array("SELECT `Person` FROM Person AS `Person` WHERE ((`Person`.`org_id` = 2) AND (ISNULL(`Person`.`org_id`) != 1))", unserialize('a:1:{s:19:"Person.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 208"] = array("SELECT `Person` FROM Person AS `Person` WHERE (`Person`.`id` = '3')", unserialize('a:1:{s:19:"Person.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 209"] = array("SELECT `Person` FROM Person AS `Person` WHERE (`Person`.`id` = '3')", unserialize('a:1:{s:19:"Person.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 210"] = array("SELECT `Person` FROM Person AS `Person` WHERE 1", unserialize('a:1:{s:19:"Person.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 211"] = array("SELECT `Person` FROM Person AS `Person` WHERE 1", unserialize('a:1:{s:19:"Person.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 212"] = array("SELECT `p` FROM Person AS `p` JOIN UserRequest AS `u` ON `u`.agent_id = `p`.id WHERE (`u`.`status` != 'closed')", unserialize('a:1:{s:14:"p.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 213"] = array("SELECT `p` FROM Person AS `p` JOIN UserRequest AS `u` ON `u`.agent_id = `p`.id WHERE (`u`.`status` != 'closed')", unserialize('a:1:{s:14:"p.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 214"] = array("SELECT `Phone` FROM Phone AS `Phone` WHERE 1", unserialize('a:1:{s:18:"Phone.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 215"] = array("SELECT `Phone` FROM Phone AS `Phone` WHERE 1", unserialize('a:1:{s:18:"Phone.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 216"] = array("SELECT `PowerConnection` FROM PowerConnection AS `PowerConnection` WHERE ((`PowerConnection`.`location_id` = '2') AND (`PowerConnection`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:28:"PowerConnection.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 217"] = array("SELECT `PowerConnection` FROM PowerConnection AS `PowerConnection` WHERE ((`PowerConnection`.`location_id` = '2') AND (`PowerConnection`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:28:"PowerConnection.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 218"] = array("SELECT `PowerConnection` FROM PowerConnection AS `PowerConnection` WHERE (`PowerConnection`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:28:"PowerConnection.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 219"] = array("SELECT `PowerConnection` FROM PowerConnection AS `PowerConnection` WHERE (`PowerConnection`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:28:"PowerConnection.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 220"] = array("SELECT `PowerConnection` FROM PowerConnection AS `PowerConnection` WHERE 1", unserialize('a:1:{s:28:"PowerConnection.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 221"] = array("SELECT `PowerConnection` FROM PowerConnection AS `PowerConnection` WHERE 1", unserialize('a:1:{s:28:"PowerConnection.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 222"] = array("SELECT `Printer` FROM Printer AS `Printer` WHERE 1", unserialize('a:1:{s:20:"Printer.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 223"] = array("SELECT `Printer` FROM Printer AS `Printer` WHERE 1", unserialize('a:1:{s:20:"Printer.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 224"] = array("SELECT `ProviderContract` FROM ProviderContract AS `ProviderContract` WHERE 1", unserialize('a:1:{s:29:"ProviderContract.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 225"] = array("SELECT `ProviderContract` FROM ProviderContract AS `ProviderContract` WHERE 1", unserialize('a:1:{s:29:"ProviderContract.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 226"] = array("SELECT `Query` FROM Query AS `Query` WHERE 1", unserialize('a:1:{s:18:"Query.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 227"] = array("SELECT `Query` FROM Query AS `Query` WHERE 1", unserialize('a:1:{s:18:"Query.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 228"] = array("SELECT `Rack` FROM Rack AS `Rack` WHERE ((`Rack`.`location_id` = '2') AND (`Rack`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:17:"Rack.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 229"] = array("SELECT `Rack` FROM Rack AS `Rack` WHERE ((`Rack`.`location_id` = '2') AND (`Rack`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:17:"Rack.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 230"] = array("SELECT `Rack` FROM Rack AS `Rack` WHERE (`Rack`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:17:"Rack.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 231"] = array("SELECT `Rack` FROM Rack AS `Rack` WHERE (`Rack`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:17:"Rack.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 232"] = array("SELECT `Rack` FROM Rack AS `Rack` WHERE 1", unserialize('a:1:{s:17:"Rack.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 233"] = array("SELECT `Rack` FROM Rack AS `Rack` WHERE 1", unserialize('a:1:{s:17:"Rack.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 234"] = array("SELECT `SANSwitch` FROM SANSwitch AS `SANSwitch` WHERE 1", unserialize('a:1:{s:22:"SANSwitch.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 235"] = array("SELECT `SANSwitch` FROM SANSwitch AS `SANSwitch` WHERE 1", unserialize('a:1:{s:22:"SANSwitch.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 236"] = array("SELECT `SLA` FROM SLA AS `SLA` WHERE 1", unserialize('a:1:{s:16:"SLA.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 237"] = array("SELECT `SLA` FROM SLA AS `SLA` WHERE 1", unserialize('a:1:{s:16:"SLA.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 238"] = array("SELECT `SLT` FROM SLT AS `SLT` WHERE 1", unserialize('a:1:{s:16:"SLT.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 239"] = array("SELECT `SLT` FROM SLT AS `SLT` WHERE 1", unserialize('a:1:{s:16:"SLT.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 240"] = array("SELECT `Server` FROM Server AS `Server` WHERE (`Server`.`name` NOT LIKE '%2')", unserialize('a:1:{s:19:"Server.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 241"] = array("SELECT `Server` FROM Server AS `Server` WHERE (`Server`.`name` NOT LIKE '%2')", unserialize('a:1:{s:19:"Server.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 242"] = array("SELECT `Server` FROM Server AS `Server` WHERE 1", unserialize('a:1:{s:19:"Server.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 243"] = array("SELECT `Server` FROM Server AS `Server` WHERE 1", unserialize('a:1:{s:19:"Server.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 244"] = array("SELECT `ServiceFamily` FROM ServiceFamily AS `ServiceFamily` WHERE (`ServiceFamily`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:26:"ServiceFamily.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 245"] = array("SELECT `ServiceFamily` FROM ServiceFamily AS `ServiceFamily` WHERE (`ServiceFamily`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:26:"ServiceFamily.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 246"] = array("SELECT `ServiceFamily` FROM ServiceFamily AS `ServiceFamily` WHERE 1", unserialize('a:1:{s:26:"ServiceFamily.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 247"] = array("SELECT `ServiceFamily` FROM ServiceFamily AS `ServiceFamily` WHERE 1", unserialize('a:1:{s:26:"ServiceFamily.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 248"] = array("SELECT `ServiceSubcategory` FROM ServiceSubcategory AS `ServiceSubcategory` WHERE ((((`ServiceSubcategory`.`service_id` = '1') AND (ISNULL('incident') OR (`ServiceSubcategory`.`request_type` = 'incident'))) AND (`ServiceSubcategory`.`status` != 'obsolete')) AND (`ServiceSubcategory`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:31:"ServiceSubcategory.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 249"] = array("SELECT `ServiceSubcategory` FROM ServiceSubcategory AS `ServiceSubcategory` WHERE ((((`ServiceSubcategory`.`service_id` = '1') AND (ISNULL('incident') OR (`ServiceSubcategory`.`request_type` = 'incident'))) AND (`ServiceSubcategory`.`status` != 'obsolete')) AND (`ServiceSubcategory`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:31:"ServiceSubcategory.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 250"] = array("SELECT `ServiceSubcategory` FROM ServiceSubcategory AS `ServiceSubcategory` WHERE (`ServiceSubcategory`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:31:"ServiceSubcategory.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 251"] = array("SELECT `ServiceSubcategory` FROM ServiceSubcategory AS `ServiceSubcategory` WHERE (`ServiceSubcategory`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:31:"ServiceSubcategory.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 252"] = array("SELECT `ServiceSubcategory` FROM ServiceSubcategory AS `ServiceSubcategory` WHERE 1", unserialize('a:1:{s:31:"ServiceSubcategory.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 253"] = array("SELECT `ServiceSubcategory` FROM ServiceSubcategory AS `ServiceSubcategory` WHERE 1", unserialize('a:1:{s:31:"ServiceSubcategory.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 254"] = array("SELECT `Service` FROM Service AS `Service` WHERE (`Service`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:20:"Service.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 255"] = array("SELECT `Service` FROM Service AS `Service` WHERE (`Service`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:20:"Service.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 256"] = array("SELECT `Service` FROM Service AS `Service` WHERE 1", unserialize('a:1:{s:20:"Service.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 257"] = array("SELECT `Service` FROM Service AS `Service` WHERE 1", unserialize('a:1:{s:20:"Service.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 258"] = array("SELECT `ShortcutOQL` FROM ShortcutOQL AS `ShortcutOQL` WHERE (`ShortcutOQL`.`id` = '3')", unserialize('a:1:{s:24:"ShortcutOQL.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 259"] = array("SELECT `ShortcutOQL` FROM ShortcutOQL AS `ShortcutOQL` WHERE (`ShortcutOQL`.`id` = '3')", unserialize('a:1:{s:24:"ShortcutOQL.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 260"] = array("SELECT `Shortcut` FROM Shortcut AS `Shortcut` WHERE (`Shortcut`.`user_id` = '5')", unserialize('a:1:{s:21:"Shortcut.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 261"] = array("SELECT `Shortcut` FROM Shortcut AS `Shortcut` WHERE (`Shortcut`.`user_id` = '5')", unserialize('a:1:{s:21:"Shortcut.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 262"] = array("SELECT `Software` FROM Software AS `Software` WHERE 1", unserialize('a:1:{s:21:"Software.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 263"] = array("SELECT `Software` FROM Software AS `Software` WHERE 1", unserialize('a:1:{s:21:"Software.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 264"] = array("SELECT `StorageSystem` FROM StorageSystem AS `StorageSystem` WHERE 1", unserialize('a:1:{s:26:"StorageSystem.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 265"] = array("SELECT `StorageSystem` FROM StorageSystem AS `StorageSystem` WHERE 1", unserialize('a:1:{s:26:"StorageSystem.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 266"] = array("SELECT `Subnet` FROM Subnet AS `Subnet` WHERE 1", unserialize('a:1:{s:19:"Subnet.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 267"] = array("SELECT `Subnet` FROM Subnet AS `Subnet` WHERE 1", unserialize('a:1:{s:19:"Subnet.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 268"] = array("SELECT `SynchroDataSource` FROM SynchroDataSource AS `SynchroDataSource` WHERE 1", unserialize('a:1:{s:30:"SynchroDataSource.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 269"] = array("SELECT `SynchroDataSource` FROM SynchroDataSource AS `SynchroDataSource` WHERE 1", unserialize('a:1:{s:30:"SynchroDataSource.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 270"] = array("SELECT `Tablet` FROM Tablet AS `Tablet` WHERE 1", unserialize('a:1:{s:19:"Tablet.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 271"] = array("SELECT `Tablet` FROM Tablet AS `Tablet` WHERE 1", unserialize('a:1:{s:19:"Tablet.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 272"] = array("SELECT `TapeLibrary` FROM TapeLibrary AS `TapeLibrary` WHERE 1", unserialize('a:1:{s:24:"TapeLibrary.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 273"] = array("SELECT `TapeLibrary` FROM TapeLibrary AS `TapeLibrary` WHERE 1", unserialize('a:1:{s:24:"TapeLibrary.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 274"] = array("SELECT `Team` FROM Team AS `Team` WHERE (`Team`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:17:"Team.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 275"] = array("SELECT `Team` FROM Team AS `Team` WHERE (`Team`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:17:"Team.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 276"] = array("SELECT `Team` FROM Team AS `Team` WHERE 1", unserialize('a:1:{s:17:"Team.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 277"] = array("SELECT `Team` FROM Team AS `Team` WHERE 1", unserialize('a:1:{s:17:"Team.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 278"] = array("SELECT `Trigger` FROM Trigger AS `Trigger` WHERE 1", unserialize('a:1:{s:20:"Trigger.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 279"] = array("SELECT `Trigger` FROM Trigger AS `Trigger` WHERE 1", unserialize('a:1:{s:20:"Trigger.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 280"] = array("SELECT `URP_Profiles` FROM URP_Profiles AS `URP_Profiles` WHERE 1", unserialize('a:1:{s:25:"URP_Profiles.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 281"] = array("SELECT `URP_Profiles` FROM URP_Profiles AS `URP_Profiles` WHERE 1", unserialize('a:1:{s:25:"URP_Profiles.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 282"] = array("SELECT `URP_UserProfile` FROM URP_UserProfile AS `URP_UserProfile` WHERE (`URP_UserProfile`.`userid` = '5')", unserialize('a:1:{s:28:"URP_UserProfile.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 283"] = array("SELECT `URP_UserProfile` FROM URP_UserProfile AS `URP_UserProfile` WHERE (`URP_UserProfile`.`userid` = '5')", unserialize('a:1:{s:28:"URP_UserProfile.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 284"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '5') AND (`UserDashboard`.`menu_code` = 'WelcomeMenuPage'))", unserialize('a:1:{s:26:"UserDashboard.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 285"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '5') AND (`UserDashboard`.`menu_code` = 'WelcomeMenuPage'))", unserialize('a:1:{s:26:"UserDashboard.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 286"] = array("SELECT `UserInternal` FROM UserInternal AS `UserInternal` WHERE ((`UserInternal`.`login` = 'admin') AND (`UserInternal`.`status` = 'active'))", unserialize('a:1:{s:25:"UserInternal.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 287"] = array("SELECT `UserInternal` FROM UserInternal AS `UserInternal` WHERE ((`UserInternal`.`login` = 'admin') AND (`UserInternal`.`status` = 'active'))", unserialize('a:1:{s:25:"UserInternal.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 288"] = array("SELECT `UserLocal` FROM UserLocal AS `UserLocal` WHERE (`UserLocal`.`id` = '3')", unserialize('a:1:{s:22:"UserLocal.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 289"] = array("SELECT `UserLocal` FROM UserLocal AS `UserLocal` WHERE (`UserLocal`.`id` = '3')", unserialize('a:1:{s:22:"UserLocal.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 290"] = array("SELECT `User` FROM User AS `User` WHERE (`User`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:17:"User.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 291"] = array("SELECT `User` FROM User AS `User` WHERE (`User`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:17:"User.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 292"] = array("SELECT `User` FROM User AS `User` WHERE (`User`.`id` = '3')", unserialize('a:1:{s:17:"User.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 293"] = array("SELECT `User` FROM User AS `User` WHERE (`User`.`id` = '3')", unserialize('a:1:{s:17:"User.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 294"] = array("SELECT `User` FROM User AS `User` WHERE 1", unserialize('a:1:{s:17:"User.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 295"] = array("SELECT `User` FROM User AS `User` WHERE 1", unserialize('a:1:{s:17:"User.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 296"] = array("SELECT `VLAN` FROM VLAN AS `VLAN` WHERE 1", unserialize('a:1:{s:17:"VLAN.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 297"] = array("SELECT `VLAN` FROM VLAN AS `VLAN` WHERE 1", unserialize('a:1:{s:17:"VLAN.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 298"] = array("SELECT `VirtualMachine` FROM VirtualMachine AS `VirtualMachine` WHERE 1", unserialize('a:1:{s:27:"VirtualMachine.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 299"] = array("SELECT `VirtualMachine` FROM VirtualMachine AS `VirtualMachine` WHERE 1", unserialize('a:1:{s:27:"VirtualMachine.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 300"] = array("SELECT `WebApplication` FROM WebApplication AS `WebApplication` WHERE 1", unserialize('a:1:{s:27:"WebApplication.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 301"] = array("SELECT `WebApplication` FROM WebApplication AS `WebApplication` WHERE 1", unserialize('a:1:{s:27:"WebApplication.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 302"] = array("SELECT `WebServer` FROM WebServer AS `WebServer` WHERE 1", unserialize('a:1:{s:22:"WebServer.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 303"] = array("SELECT `WebServer` FROM WebServer AS `WebServer` WHERE 1", unserialize('a:1:{s:22:"WebServer.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 304"] = array("SELECT `appUserPreferences` FROM appUserPreferences AS `appUserPreferences` WHERE (`appUserPreferences`.`id` = '3')", unserialize('a:1:{s:31:"appUserPreferences.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 305"] = array("SELECT `appUserPreferences` FROM appUserPreferences AS `appUserPreferences` WHERE (`appUserPreferences`.`id` = '3')", unserialize('a:1:{s:31:"appUserPreferences.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 306"] = array("SELECT `appUserPreferences` FROM appUserPreferences AS `appUserPreferences` WHERE (`appUserPreferences`.`userid` = '5')", unserialize('a:1:{s:31:"appUserPreferences.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 307"] = array("SELECT `appUserPreferences` FROM appUserPreferences AS `appUserPreferences` WHERE (`appUserPreferences`.`userid` = '5')", unserialize('a:1:{s:31:"appUserPreferences.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 308"] = array("SELECT `c` FROM CustomerContract AS `c` WHERE (`c`.`end_date` < '2018-12-01')", unserialize('a:1:{s:14:"c.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 309"] = array("SELECT `c` FROM CustomerContract AS `c` WHERE (`c`.`end_date` < '2018-12-01')", unserialize('a:1:{s:14:"c.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 310"] = array("SELECT `c` FROM ProviderContract AS `c` WHERE (`c`.`end_date` < '2018-12-01')", unserialize('a:1:{s:14:"c.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 311"] = array("SELECT `c` FROM ProviderContract AS `c` WHERE (`c`.`end_date` < '2018-12-01')", unserialize('a:1:{s:14:"c.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 312"] = array("SELECT `datasource` FROM SynchroDataSource AS `datasource` WHERE 1", unserialize('a:1:{s:23:"datasource.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 313"] = array("SELECT `datasource` FROM SynchroDataSource AS `datasource` WHERE 1", unserialize('a:1:{s:23:"datasource.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 314"] = array("SELECT `i` FROM UserRequest AS `i` WHERE ((`i`.`agent_id` = '') AND (`i`.`status` NOT IN ('closed', 'resolved')))", unserialize('a:1:{s:14:"i.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 315"] = array("SELECT `i` FROM UserRequest AS `i` WHERE ((`i`.`agent_id` = '') AND (`i`.`status` NOT IN ('closed', 'resolved')))", unserialize('a:1:{s:14:"i.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 316"] = array("SELECT `s` FROM Service AS `s` JOIN lnkCustomerContractToService AS `l1` ON `l1`.service_id = `s`.id JOIN CustomerContract AS `cc` ON `l1`.customercontract_id = `cc`.id WHERE (((`cc`.`org_id` = '3') AND (`s`.`status` != 'obsolete')) AND (`s`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:14:"s.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 317"] = array("SELECT `s` FROM Service AS `s` JOIN lnkCustomerContractToService AS `l1` ON `l1`.service_id = `s`.id JOIN CustomerContract AS `cc` ON `l1`.customercontract_id = `cc`.id WHERE (((`cc`.`org_id` = '3') AND (`s`.`status` != 'obsolete')) AND (`s`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:14:"s.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 318"] = array("SELECT `t` FROM Team AS `t` JOIN lnkDeliveryModelToContact AS `l1` ON `l1`.contact_id = `t`.id JOIN DeliveryModel AS `dm` ON `l1`.deliverymodel_id = `dm`.id JOIN Organization AS `o` ON `o`.deliverymodel_id = `dm`.id WHERE ((`o`.`id` = '3') AND (`t`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:14:"t.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 319"] = array("SELECT `t` FROM Team AS `t` JOIN lnkDeliveryModelToContact AS `l1` ON `l1`.contact_id = `t`.id JOIN DeliveryModel AS `dm` ON `l1`.deliverymodel_id = `dm`.id JOIN Organization AS `o` ON `o`.deliverymodel_id = `dm`.id WHERE ((`o`.`id` = '3') AND (`t`.`friendlyname` LIKE '%%'))", unserialize('a:1:{s:14:"t.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 320"] = array("SELECT `t` FROM TriggerOnObjectCreate AS `t` WHERE (`t`.`target_class` IN ('appUserPreferences'))", unserialize('a:1:{s:14:"t.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 321"] = array("SELECT `t` FROM TriggerOnObjectCreate AS `t` WHERE (`t`.`target_class` IN ('appUserPreferences'))", unserialize('a:1:{s:14:"t.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 322"] = array("SELECT `t` FROM TriggerOnObjectUpdate AS `t` WHERE (`t`.`target_class` IN ('appUserPreferences'))", unserialize('a:1:{s:14:"t.friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 323"] = array("SELECT `t` FROM TriggerOnObjectUpdate AS `t` WHERE (`t`.`target_class` IN ('appUserPreferences'))", unserialize('a:1:{s:14:"t.friendlyname";b:1;}'), array(), null, array(), 20, 0); - $aData["SELECT 324"] = array("SELECT `UserInternal` FROM UserInternal AS `UserInternal` WHERE ((`UserInternal`.`login` = 'admin') AND (`UserInternal`.`status` = 'enabled'))", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 325"] = array("SELECT `Contact` FROM Contact AS `Contact` WHERE (`Contact`.`id` = '987654321')", array(), array(), null, array(), 0, 0); - $aData["SELECT 326"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", array(), array(), null, array(), 4, 0); - $aData["SELECT 327"] = array("SELECT `appUserPreferences` FROM appUserPreferences AS `appUserPreferences` WHERE (`appUserPreferences`.`userid` = '1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 328"] = array("SELECT `Shortcut` FROM Shortcut AS `Shortcut` WHERE (`Shortcut`.`user_id` = '1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 329"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '1') AND (`UserDashboard`.`menu_code` = 'WelcomeMenuPage'))", array(), array(), null, array(), 0, 0); - $aData["SELECT 330"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '1') AND (`UserDashboard`.`menu_code` = 'WelcomeMenuPage'))", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 331"] = array("SELECT `BusinessProcess` FROM BusinessProcess AS `BusinessProcess` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 332"] = array("SELECT `ApplicationSolution` FROM ApplicationSolution AS `ApplicationSolution` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 333"] = array("SELECT `Contact` FROM Contact AS `Contact` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 334"] = array("SELECT `Location` FROM Location AS `Location` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 335"] = array("SELECT `Contract` FROM Contract AS `Contract` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 336"] = array("SELECT `Server` FROM Server AS `Server` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 337"] = array("SELECT `NetworkDevice` FROM NetworkDevice AS `NetworkDevice` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 338"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`status` != 'closed')", array(), array(), null, array(), 0, 0); - $aData["SELECT 339"] = array("SELECT `i` FROM UserRequest AS `i` WHERE ((`i`.`agent_id` = 1) AND (`i`.`status` NOT IN ('closed', 'resolved')))", array(), array(), null, array(), 0, 0); - $aData["SELECT 340"] = array("SELECT `i` FROM UserRequest AS `i` WHERE ((`i`.`agent_id` = 1) AND (`i`.`status` NOT IN ('closed', 'resolved')))", array(), array(), null, array(), 3, 0); - $aData["SELECT 341"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", array(), array(), null, array(), 0, 0); - $aData["SELECT 342"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", array(), array(), null, array(), 52, 0); - $aData["SELECT 343"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = '')", array(), array(), null, array(), 0, 0); - $aData["SELECT 344"] = array("SELECT `DBProperty` FROM DBProperty AS `DBProperty` WHERE (`DBProperty`.`name` = 'database_uuid')", array(), array(), null, array(), 0, 0); - $aData["SELECT 345"] = array("SELECT `DBProperty` FROM DBProperty AS `DBProperty` WHERE (`DBProperty`.`name` = 'database_uuid')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 346"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '1') AND (`UserDashboard`.`menu_code` = 'UserRequest:Overview'))", array(), array(), null, array(), 0, 0); - $aData["SELECT 347"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = '987654321')", array(), array(), null, array(), 0, 0); - $aData["SELECT 348"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = '0')", array(), array(), null, array(), 0, 0); - $aData["SELECT 349"] = array("SELECT `lnkErrorToFunctionalCI` FROM lnkErrorToFunctionalCI AS `lnkErrorToFunctionalCI` JOIN KnownError AS `KnownError` ON `lnkErrorToFunctionalCI`.error_id = `KnownError`.id WHERE (`KnownError`.`id` = '-1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 350"] = array("SELECT `lnkErrorToFunctionalCI` FROM lnkErrorToFunctionalCI AS `lnkErrorToFunctionalCI` JOIN KnownError AS `KnownError` ON `lnkErrorToFunctionalCI`.error_id = `KnownError`.id WHERE (`KnownError`.`id` = '-1')", array(), array(), null, array(), 0, 0); - $aData["SELECT 351"] = array("SELECT `lnkDocumentToError` FROM lnkDocumentToError AS `lnkDocumentToError` JOIN KnownError AS `KnownError` ON `lnkDocumentToError`.error_id = `KnownError`.id WHERE (`KnownError`.`id` = '-1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 352"] = array("SELECT `lnkDocumentToError` FROM lnkDocumentToError AS `lnkDocumentToError` JOIN KnownError AS `KnownError` ON `lnkDocumentToError`.error_id = `KnownError`.id WHERE (`KnownError`.`id` = '-1')", array(), array(), null, array(), 0, 0); - $aData["SELECT 353"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '1') AND (`UserDashboard`.`menu_code` = 'Change:Overview'))", array(), array(), null, array(), 0, 0); - $aData["SELECT 354"] = array("SELECT `InlineImage` FROM InlineImage AS `InlineImage` WHERE (`InlineImage`.`temp_id` = 'adm83AD.tmp')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 355"] = array("SELECT `Attachment` FROM Attachment AS `Attachment` WHERE (`Attachment`.`temp_id` = 'adm83AD.tmp')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 356"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '1') AND (`UserDashboard`.`menu_code` = 'Service:Overview'))", array(), array(), null, array(), 0, 0); - $aData["SELECT 357"] = array("SELECT `c` FROM CustomerContract AS `c` WHERE (`c`.`end_date` < DATE_ADD(NOW(), INTERVAL 30 DAY))", array(), array(), null, array(), 3, 0); - $aData["SELECT 358"] = array("SELECT `c` FROM ProviderContract AS `c` WHERE (`c`.`end_date` < DATE_ADD(NOW(), INTERVAL 30 DAY))", array(), array(), null, array(), 3, 0); - $aData["SELECT 359"] = array("SELECT `DeliveryModel` FROM DeliveryModel AS `DeliveryModel` WHERE 1", array(), array(), null, array(), 52, 0); - $aData["SELECT 360"] = array("SELECT `DeliveryModel` FROM DeliveryModel AS `DeliveryModel` WHERE (`DeliveryModel`.`friendlyname` LIKE '%%')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), unserialize('a:1:{s:13:"DeliveryModel";a:1:{i:0;s:12:"friendlyname";}}'), array(), 0, 0); - $aData["SELECT 361"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", array(), array(), null, array(), 3, 0); - $aData["SELECT 362"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE 1", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), unserialize('a:1:{s:12:"Organization";a:7:{i:0;s:4:"code";i:1;s:6:"status";i:2;s:9:"parent_id";i:3;s:22:"parent_id_friendlyname";i:4;s:27:"parent_id_obsolescence_flag";i:5;s:12:"friendlyname";i:6;s:17:"obsolescence_flag";}}'), array(), 10, 0); - $aData["SELECT 363"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`name` LIKE '%demo%')", array(), array(), null, array(), 3, 0); - $aData["SELECT 364"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`name` LIKE '%demo%')", array(), array(), null, array(), 0, 0); - $aData["SELECT 365"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`name` LIKE '%demo%')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 366"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`name` LIKE '%demo%')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), unserialize('a:1:{s:12:"Organization";a:7:{i:0;s:4:"code";i:1;s:6:"status";i:2;s:9:"parent_id";i:3;s:22:"parent_id_friendlyname";i:4;s:27:"parent_id_obsolescence_flag";i:5;s:12:"friendlyname";i:6;s:17:"obsolescence_flag";}}'), array(), 10, 0); - $aData["SELECT 367"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = '3')", array(), array(), null, array(), 0, 0); - $aData["SELECT 368"] = array("SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = '3')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 369"] = array("SELECT `replica`, `datasource` FROM SynchroReplica AS `replica` JOIN SynchroDataSource AS `datasource` ON `replica`.sync_source_id = `datasource`.id WHERE ((`replica`.`dest_class` = 'Organization') AND (`replica`.`dest_id` = '3'))", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 370"] = array("SELECT `datasource` FROM SynchroDataSource AS `datasource` WHERE 1", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 371"] = array("SELECT `Trigger` FROM Trigger AS `Trigger` WHERE 1", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 372"] = array("SELECT `UserDashboard` FROM UserDashboard AS `UserDashboard` WHERE ((`UserDashboard`.`user_id` = '1') AND (`UserDashboard`.`menu_code` = 'Organization__overview'))", array(), array(), null, array(), 0, 0); - $aData["SELECT 373"] = array("SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE (`FunctionalCI`.`org_id` = '3')", array(), array(), null, array(), 0, 0); - $aData["SELECT 374"] = array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`status` != 'closed') AND (`UserRequest`.`org_id` = '3'))", array(), array(), null, array(), 0, 0); - $aData["SELECT 375"] = array("SELECT `p` FROM Person AS `p` JOIN User AS `u` ON `u`.contactid = `p`.id WHERE (`p`.`org_id` = '3')", array(), array(), null, array(), 3, 0); - $aData["SELECT 376"] = array("SELECT `p` FROM Person AS `p` JOIN User AS `u` ON `u`.contactid = `p`.id WHERE (`p`.`org_id` = '3')", array(), array(), null, array(), 0, 0); - $aData["SELECT 377"] = array("SELECT `p` FROM Person AS `p` JOIN User AS `u` ON `u`.contactid = `p`.id WHERE (`p`.`org_id` = '3')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), unserialize('a:1:{s:1:"p";a:13:{i:0;s:4:"name";i:1;s:6:"org_id";i:2;s:19:"org_id_friendlyname";i:3;s:24:"org_id_obsolescence_flag";i:4;s:6:"status";i:5;s:11:"location_id";i:6;s:24:"location_id_friendlyname";i:7;s:29:"location_id_obsolescence_flag";i:8;s:5:"email";i:9;s:5:"phone";i:10;s:12:"friendlyname";i:11;s:17:"obsolescence_flag";i:12;s:10:"finalclass";}}'), array(), 10, 0); - $aData["SELECT 378"] = array("SELECT `i` FROM UserRequest AS `i` WHERE (((`i`.`agent_id` = 1) AND (`i`.`status` NOT IN ('closed', 'resolved'))) AND (`i`.`org_id` = '3'))", array(), array(), null, array(), 3, 0); - $aData["SELECT 379"] = array("SELECT `Location` FROM Location AS `Location` WHERE 1", array(), array(), null, array(), 3, 0); - $aData["SELECT 380"] = array("SELECT `Location` FROM Location AS `Location` WHERE 1", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), unserialize('a:1:{s:8:"Location";a:8:{i:0;s:6:"status";i:1;s:6:"org_id";i:2;s:19:"org_id_friendlyname";i:3;s:24:"org_id_obsolescence_flag";i:4;s:4:"city";i:5;s:7:"country";i:6;s:12:"friendlyname";i:7;s:17:"obsolescence_flag";}}'), array(), 10, 0); - $aData["SELECT 381"] = array("SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE (`FunctionalCI`.`finalclass` IN ('Server', 'VirtualMachine', 'PC'))", array(), array(), null, array(), 4, 0); - $aData["SELECT 382"] = array("SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE (`FunctionalCI`.`id` = '987654321')", array(), array(), null, array(), 0, 0); - $aData["SELECT 383"] = array("SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE (`FunctionalCI`.`finalclass` IN ('Server', 'VirtualMachine', 'PC'))", array(), array(), null, array(), 52, 0); - $aData["SELECT 384"] = array("SELECT `FunctionalCI` FROM FunctionalCI AS `FunctionalCI` WHERE (`FunctionalCI`.`finalclass` IN ('Server', 'VirtualMachine', 'PC'))", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 385"] = array("SELECT `Software` FROM Software AS `Software` WHERE (`Software`.`id` = '987654321')", array(), array(), null, array(), 0, 0); - $aData["SELECT 386"] = array("SELECT `Software` FROM Software AS `Software` WHERE (`Software`.`type` = 'WebServer')", array(), array(), null, array(), 52, 0); - $aData["SELECT 387"] = array("SELECT `Software` FROM Software AS `Software` WHERE (`Software`.`type` = 'WebServer')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 388"] = array("SELECT `SoftwareLicence` FROM SoftwareLicence AS `SoftwareLicence` WHERE (`SoftwareLicence`.`id` = '987654321')", array(), array(), null, array(), 0, 0); - $aData["SELECT 389"] = array("SELECT `SoftwareLicence` FROM SoftwareLicence AS `SoftwareLicence` WHERE (`SoftwareLicence`.`software_id` = 0)", array(), array(), null, array(), 52, 0); - $aData["SELECT 390"] = array("SELECT `SoftwareLicence` FROM SoftwareLicence AS `SoftwareLicence` WHERE (`SoftwareLicence`.`software_id` = 0)", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 391"] = array("SELECT `lnkContactToFunctionalCI` FROM lnkContactToFunctionalCI AS `lnkContactToFunctionalCI` JOIN FunctionalCI AS `FunctionalCI` ON `lnkContactToFunctionalCI`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 392"] = array("SELECT `lnkContactToFunctionalCI` FROM lnkContactToFunctionalCI AS `lnkContactToFunctionalCI` JOIN FunctionalCI AS `FunctionalCI` ON `lnkContactToFunctionalCI`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", array(), array(), null, array(), 0, 0); - $aData["SELECT 393"] = array("SELECT `lnkDocumentToFunctionalCI` FROM lnkDocumentToFunctionalCI AS `lnkDocumentToFunctionalCI` JOIN FunctionalCI AS `FunctionalCI` ON `lnkDocumentToFunctionalCI`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 394"] = array("SELECT `lnkDocumentToFunctionalCI` FROM lnkDocumentToFunctionalCI AS `lnkDocumentToFunctionalCI` JOIN FunctionalCI AS `FunctionalCI` ON `lnkDocumentToFunctionalCI`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", array(), array(), null, array(), 0, 0); - $aData["SELECT 395"] = array("SELECT `lnkApplicationSolutionToFunctionalCI` FROM lnkApplicationSolutionToFunctionalCI AS `lnkApplicationSolutionToFunctionalCI` JOIN FunctionalCI AS `FunctionalCI` ON `lnkApplicationSolutionToFunctionalCI`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 396"] = array("SELECT `lnkApplicationSolutionToFunctionalCI` FROM lnkApplicationSolutionToFunctionalCI AS `lnkApplicationSolutionToFunctionalCI` JOIN FunctionalCI AS `FunctionalCI` ON `lnkApplicationSolutionToFunctionalCI`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", array(), array(), null, array(), 0, 0); - $aData["SELECT 397"] = array("SELECT `WebApplication` FROM WebApplication AS `WebApplication` JOIN WebServer AS `WebServer` ON `WebApplication`.webserver_id = `WebServer`.id WHERE (`WebServer`.`id` = '-1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 398"] = array("SELECT `WebApplication` FROM WebApplication AS `WebApplication` JOIN WebServer AS `WebServer` ON `WebApplication`.webserver_id = `WebServer`.id WHERE (`WebServer`.`id` = '-1')", array(), array(), null, array(), 0, 0); - $aData["SELECT 399"] = array("SELECT `lnkFunctionalCIToProviderContract` FROM lnkFunctionalCIToProviderContract AS `lnkFunctionalCIToProviderContract` JOIN FunctionalCI AS `FunctionalCI` ON `lnkFunctionalCIToProviderContract`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 400"] = array("SELECT `lnkFunctionalCIToProviderContract` FROM lnkFunctionalCIToProviderContract AS `lnkFunctionalCIToProviderContract` JOIN FunctionalCI AS `FunctionalCI` ON `lnkFunctionalCIToProviderContract`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", array(), array(), null, array(), 0, 0); - $aData["SELECT 401"] = array("SELECT `lnkFunctionalCIToService` FROM lnkFunctionalCIToService AS `lnkFunctionalCIToService` JOIN FunctionalCI AS `FunctionalCI` ON `lnkFunctionalCIToService`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 402"] = array("SELECT `lnkFunctionalCIToService` FROM lnkFunctionalCIToService AS `lnkFunctionalCIToService` JOIN FunctionalCI AS `FunctionalCI` ON `lnkFunctionalCIToService`.functionalci_id = `FunctionalCI`.id WHERE (`FunctionalCI`.`id` = '-1')", array(), array(), null, array(), 0, 0); - $aData["SELECT 403"] = array("SELECT `t` FROM Change AS `t` JOIN lnkFunctionalCIToTicket AS `lnk` ON `lnk`.ticket_id = `t`.id WHERE (((`lnk`.`functionalci_id` = '-2') AND (`t`.`status` NOT IN ('rejected', 'resolved', 'closed'))) AND (`lnk`.`impact_code` != 'not_impacted'))", array(), array(), null, array(), 0, 0); - $aData["SELECT 404"] = array("SELECT `t` FROM UserRequest AS `t` JOIN lnkFunctionalCIToTicket AS `lnk` ON `lnk`.ticket_id = `t`.id WHERE (((`lnk`.`functionalci_id` = '-2') AND (`t`.`status` NOT IN ('rejected', 'resolved', 'closed'))) AND (`lnk`.`impact_code` != 'not_impacted'))", array(), array(), null, array(), 0, 0); - $aData["SELECT 405"] = array("SELECT `t` FROM Change AS `t` JOIN lnkFunctionalCIToTicket AS `lnk` ON `lnk`.ticket_id = `t`.id WHERE (((`lnk`.`functionalci_id` = '-2') AND (`t`.`status` NOT IN ('rejected', 'resolved', 'closed'))) AND (`lnk`.`impact_code` != 'not_impacted'))", array(), array(), null, array(), 3, 0); - $aData["SELECT 406"] = array("SELECT `t` FROM UserRequest AS `t` JOIN lnkFunctionalCIToTicket AS `lnk` ON `lnk`.ticket_id = `t`.id WHERE (((`lnk`.`functionalci_id` = '-2') AND (`t`.`status` NOT IN ('rejected', 'resolved', 'closed'))) AND (`lnk`.`impact_code` != 'not_impacted'))", array(), array(), null, array(), 3, 0); - $aData["SELECT 407"] = array("SELECT `InlineImage` FROM InlineImage AS `InlineImage` WHERE (`InlineImage`.`temp_id` = 'admBBC2.tmp')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - $aData["SELECT 408"] = array("SELECT `Attachment` FROM Attachment AS `Attachment` WHERE (`Attachment`.`temp_id` = 'admBBC2.tmp')", unserialize('a:1:{s:12:"friendlyname";b:1;}'), array(), null, array(), 0, 0); - - return $aData; - } - - public function OQLSelectProvider() - { - $aData = $this->OQLSelectProviderStaticTTO(); - $aData = array_merge($aData,$this->OQLSelectProviderStatic()); - - // Dynamic entries - @include ('oql_records.php'); - - return $aData; - } - - private function GetId() - { - $sId = str_replace('"', '', $this->getName()); - $sId = str_replace('Legacy', '', $sId); - $sId = str_replace(' ', '_', $sId); - return $sId; - } - - /** - * @param $sSQL - * - * @param int $iLimit - * - * @return array|null - * @throws \CoreException - * @throws \MySQLException - * @throws \MySQLHasGoneAwayException - */ - private function GetArrayResult($sSQL, $iLimit = 10) - { - $resQuery = CMDBSource::Query($sSQL); - if (!$resQuery) - { - return null; - } - else - { - $aRow = array(); - $iCount = 0; - while ($aRes = CMDBSource::FetchArray($resQuery)) - { - if ($iCount < $iLimit) - { - $aRow[] = $aRes; - } - $iCount++; - unset($aRes); - } - CMDBSource::FreeResult($resQuery); - return $aRow; - } - } -} diff --git a/test/VerifyOQL.php b/test/VerifyOQL.php index 1b76d25271..b2ac5d9ef9 100644 --- a/test/VerifyOQL.php +++ b/test/VerifyOQL.php @@ -73,9 +73,9 @@ function ShowExamples($oP, $sExpression) } //$aDisplayData[$sTopic][] = array( $aDisplayData[Dict::S('UI:RunQuery:QueryExamples')][] = array( - 'desc' => "
    ".htmlentities($sDescription, ENT_QUOTES, 'UTF-8')."
    ", - 'oql' => "
    ".htmlentities($sOql, ENT_QUOTES, 'UTF-8')."
    ", - 'go' => "
    $sContext\n", + 'desc' => "
    ".utils::EscapeHtml($sDescription)."
    ", + 'oql' => "
    ".utils::EscapeHtml($sOql)."
    ", + 'go' => "
    $sContext\n", ); } } @@ -147,8 +147,7 @@ try { $aArgs[$sParam] = $value; } - else - { + else { $aArgs[$sParam] = ''; } } @@ -158,7 +157,7 @@ try $oP->add("
    \n"); $oP->add(Dict::S('UI:RunQuery:ExpressionToEvaluate')."
    \n"); - $oP->add("\n"); + $oP->add("\n"); $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot()."/js/jquery.hotkeys.js"); $oP->add_ready_script(<< 0) - { + if (strlen($sSuggestedWord) > 0) { $oP->p(''.Dict::Format('UI:RunQuery:Error', $e->GetIssue().' '.$sWrongWord).''); $sBefore = substr($sExpression, 0, $e->GetColumn()); $sAfter = substr($sExpression, $e->GetColumn() + strlen($sWrongWord)); $sFixedExpression = $sBefore.$sSuggestedWord.$sAfter; $sFixedExpressionHtml = $sBefore.''.$sSuggestedWord.''.$sAfter; $oP->p("Suggesting: $sFixedExpressionHtml"); - $oP->add(''); + $oP->add(''); } else { diff --git a/test/VisualTest/Backoffice/RenderAllUiBlocks.php b/test/VisualTest/Backoffice/RenderAllUiBlocks.php index 2e1f744956..ea7ed9bb56 100644 --- a/test/VisualTest/Backoffice/RenderAllUiBlocks.php +++ b/test/VisualTest/Backoffice/RenderAllUiBlocks.php @@ -402,11 +402,29 @@ $oPage->AddUiBlock(DataTableUIBlockFactory::MakeForStaticData('Static datatable' array( array( 'a' => 'A1', 'b' => 'B1', 'c' => 'C1', 'd' => 'D1' -),array( + ), array( 'a' => 'A2', 'b' => 'B2', 'c' => 'C2', 'd' => 'D2' -), - array( - 'a' => 'A3', 'b' => 'B3', 'c' => 'C3', 'd' => 'D3' - )))); + ), array( + 'a' => 'A3', 'b' => 'B3', 'c' => 'C3', 'd' => 'D3' + ), array( + 'a' => 'A4', 'b' => 'B4', 'c' => 'C4', 'd' => 'D4' + ),array( + '@class' => 'ibo-is-red','a' => 'A5 (Red highlighting)', 'b' => 'B5', 'c' => 'C5', 'd' => 'D5' + ),array( + '@class' => 'ibo-is-danger','a' => 'A6 (Danger highlighting)', 'b' => 'B6', 'c' => 'C6', 'd' => 'D6' + ),array( + '@class' => 'ibo-is-orange','a' => 'A7 (Orange highlighting)', 'b' => 'B7', 'c' => 'C7', 'd' => 'D7' + ),array( + '@class' => 'ibo-is-warning','a' => 'A8 (Warning highlighting)', 'b' => 'B8', 'c' => 'C8', 'd' => 'D8' + ),array( + '@class' => 'ibo-is-blue','a' => 'A9 (Blue highlighting)', 'b' => 'B9', 'c' => 'C9', 'd' => 'D9' + ),array( + '@class' => 'ibo-is-info','a' => 'A10 (Info highlighting)', 'b' => 'B10', 'c' => 'C10', 'd' => 'D10' + ),array( + '@class' => 'ibo-is-green','a' => 'A11 (Green highlighting)', 'b' => 'B11', 'c' => 'C11', 'd' => 'D11' + ),array( + '@class' => 'ibo-is-success','a' => 'A12 (Success highlighting)', 'b' => 'B12', 'c' => 'C12', 'd' => 'D12' + ), +))); $oPage->output(); diff --git a/test/application/DashboardLayoutTest.php b/test/application/DashboardLayoutTest.php index 26104cfc9d..93e992386f 100644 --- a/test/application/DashboardLayoutTest.php +++ b/test/application/DashboardLayoutTest.php @@ -24,13 +24,6 @@ use Combodo\iTop\Test\UnitTest\ItopTestCase; */ class DashboardLayoutTest extends ItopTestCase { - public function setUp() - { - parent::setUp(); - - require_once APPROOT.'application/dashboardlayout.class.inc.php'; - } - /** * @return array */ diff --git a/test/application/SCSSCompilationTest.php b/test/application/SCSSCompilationTest.php new file mode 100644 index 0000000000..bb47ef516f --- /dev/null +++ b/test/application/SCSSCompilationTest.php @@ -0,0 +1,38 @@ +debug($sCSS); + } + + public function CompileDefaultThemesProvider() + { + return [ + 'console' => ['css/backoffice/main.scss', ['css/backoffice/']], + 'portal' => ['env-production/itop-portal-base/portal/public/css/bootstrap-theme-combodo.scss', ['env-production//itop-portal-base/portal/public/css/']], + ]; + } + +} diff --git a/test/application/ThemeHandlerTest.php b/test/application/ThemeHandlerTest.php index 9556cd3806..ca9ded7017 100644 --- a/test/application/ThemeHandlerTest.php +++ b/test/application/ThemeHandlerTest.php @@ -20,7 +20,7 @@ class ThemeHandlerTest extends ItopTestCase private $sTmpDir; private $aDirsToCleanup= []; - public function setUp() + public function setUp(): void { parent::setUp(); require_once(APPROOT.'application/themehandler.class.inc.php'); @@ -42,7 +42,7 @@ class ThemeHandlerTest extends ItopTestCase $this->RecurseCopy(APPROOT."/test/application/theme-handler/expected/css", $this->sTmpDir."/branding/css"); } - public function tearDown() + public function tearDown(): void { parent::tearDown(); @@ -53,180 +53,6 @@ class ThemeHandlerTest extends ItopTestCase } } - /** - * Test used to be notified by CI when precompiled styles are not up to date anymore in code repository. - * - * @param $xmlDataCusto - * @param $sPrecompiledStylesheet - * @param $oTheme - * - * @group beforeSetup - * - * @throws \Exception - */ - public function testValidatePrecompiledStyles() - { - $aErrors = []; - $aDataModelFiles=glob(APPROOT . utils::GetConfig()->Get('source_dir'). "/**/datamodel*.xml"); - $aImportsPaths = [ - APPROOT.'datamodels', // Simulate env-xxx when looking for files outside env-xxx - APPROOT.'datamodels/2.x', // Simulate env-xxx when looking for files within env-xxx - $this->sTmpDir, // For DM rules - ]; - - // First we have to compile the styles defined in the DM in order to feed it to the themes - $oMFCompiler = new SubMFCompiler($this->createMock(\ModelFactory::class), ''); - $sDmCssContent = ""; - foreach ($aDataModelFiles as $sXmlDataCustoFilePath) { - if (is_file($sXmlDataCustoFilePath)) { - $oDom = new MFDocument(); - $oDom->load($sXmlDataCustoFilePath); - - $oClassNodes = $oDom->GetNodes("/itop_design//class"); - foreach ($oClassNodes as $oClassNode) { - $sClass = $oClassNode->getAttribute("id"); - $oFieldNodes = $oClassNode->GetNodes("fields/field[@xsi:type='AttributeEnum' or @xsi:type='AttributeMetaEnum']"); - foreach ($oFieldNodes as $oFieldNode) { - $sAttCode = $oFieldNode->getAttribute("id"); - - // Values styles - $oValueNodes = $oFieldNode->GetNodes("value"); - foreach ($oValueNodes as $oValueNode) { - $sValueCode = $oValueNode->getAttribute("id"); - $sDmCssContent .= $oMFCompiler->GenerateStyleDataFromNode($oValueNode, $oMFCompiler::ENUM_STYLE_HOST_ELEMENT_TYPE_ENUM, $sClass, $sAttCode, $sValueCode)['scss']; - } - - // Default style - $oDefaultStyleNode = $oFieldNode->GetOptionalElement('default_style'); - if ($oDefaultStyleNode) { - $sDmCssContent .= $oMFCompiler->GenerateStyleDataFromNode($oDefaultStyleNode, $oMFCompiler::ENUM_STYLE_HOST_ELEMENT_TYPE_ENUM, $sClass, $sAttCode)['scss']; - } - } - } - } - } - // - Write file - $sDmStylesheetId = 'datamodel-compiled-scss-rules'; - file_put_contents($this->sDmCssAbsPath, $sDmCssContent); - - foreach ($aDataModelFiles as $sXmlDataCustoFilePath) - { - if (is_file($sXmlDataCustoFilePath)) - { - $sXmlDataCustoFilePath = realpath($sXmlDataCustoFilePath); - $sContent = file_get_contents($sXmlDataCustoFilePath); - if (strpos($sContent, "precompiled_stylesheet") !== false) - { - $oDom = new MFDocument(); - $oDom->load($sXmlDataCustoFilePath); - $oThemeNodes = $oDom->GetNodes("/itop_design/branding/themes/theme"); - - // Parsing themes from DM - foreach ($oThemeNodes as $oTheme) - { - /** @var \MFElement $oTheme */ - $sPrecompiledStylesheetUri = $oTheme->GetChildText('precompiled_stylesheet', ''); - if (empty($sPrecompiledStylesheetUri)) - { - continue; - } - - $sThemeId = $oTheme->getAttribute('id'); - - //echo "=== theme: $sThemeId ===\n"; - $sPrecompiledFilePath = $sSourceDir = APPROOT . utils::GetConfig()->Get('source_dir') . $sPrecompiledStylesheetUri; - $sPreCompiledSig = ThemeHandler::GetSignature($sPrecompiledFilePath); - if (empty(trim($sPreCompiledSig))){ - var_dump("$sThemeId: $sPrecompiledFilePath => " . realpath($sPrecompiledFilePath)); - } - //echo " precompiled signature: $sPreCompiledSig\n"; - - if (empty($sPreCompiledSig)) - { - $aErrors[] = "Signature in precompiled theme '".$sThemeId."' is not retrievable (cf precompiledsheet $sPrecompiledStylesheetUri / datamodel $sXmlDataCustoFilePath)"; - continue; - } - - $aThemeParameters = array( - 'variables' => array(), - 'variable_imports' => array(), - 'utility_imports' => array(), - 'stylesheets' => array(), - ); - - /** @var \DOMNodeList $oVariables */ - $oVariables = $oTheme->GetNodes('variables/variable'); - foreach ($oVariables as $oVariable) - { - $sVariableId = $oVariable->getAttribute('id'); - $aThemeParameters['variables'][$sVariableId] = $oVariable->GetText(); - } - - /** @var \DOMNodeList $oImports */ - $oImports = $oTheme->GetNodes('imports/import'); - $oFindStylesheetObject = new FindStylesheetObject(); - - foreach ($oImports as $oImport) - { - $sImportId = $oImport->getAttribute('id'); - - if($oImport->getAttribute('xsi:type') === 'variables'){ - $aThemeParameters['variable_imports'][$sImportId] = $oImport->GetText(); - } else { - $aThemeParameters['utility_imports'][$sImportId] = $oImport->GetText(); - ThemeHandler::FindStylesheetFile($oImport->GetText(), $aImportsPaths, $oFindStylesheetObject); - } - } - - /** @var \DOMNodeList $oStylesheets */ - $oStylesheets = $oTheme->GetNodes('stylesheets/stylesheet'); - foreach ($oStylesheets as $oStylesheet) - { - $sStylesheetId = $oStylesheet->getAttribute('id'); - $aThemeParameters['stylesheets'][$sStylesheetId] = $oStylesheet->GetText(); - ThemeHandler::FindStylesheetFile($oStylesheet->GetText(), $aImportsPaths, $oFindStylesheetObject); - } - - $sDmCssRelPath = str_ireplace($this->sTmpDir, '', $this->sDmCssAbsPath); - $aThemeParameters['stylesheets'][$sDmStylesheetId] = $sDmCssRelPath; - ThemeHandler::FindStylesheetFile($sDmCssRelPath, $aImportsPaths, $oFindStylesheetObject); - - $aIncludedImages = ThemeHandler::GetIncludedImages($aThemeParameters['variables'], $oFindStylesheetObject->GetAllStylesheetPaths(), $sThemeId); - $compiled_json_sig = ThemeHandler::ComputeSignature($aThemeParameters, $aImportsPaths, $aIncludedImages); - //echo " current signature: $compiled_json_sig\n"; - - if ($sPreCompiledSig !== $compiled_json_sig) - { - $sSignatureDiffToPrint = $this->KeepSignatureDiff($sPreCompiledSig, $compiled_json_sig); - - // Temporary bypass of the test if it concerns only the DM CSS rules - $aSignatureDiff = json_decode($sSignatureDiffToPrint, true); - if (isset($aSignatureDiff['stylesheets']['datamodel-compiled-scss-rules']) && (count($aSignatureDiff['stylesheets']) === 1) ) { - continue; - } - - var_dump($sSignatureDiffToPrint); - $iLine = $oTheme->GetLineNo(); - $aErrors[] = " $sPrecompiledStylesheetUri declared in $sXmlDataCustoFilePath:$iLine.\n$sSignatureDiffToPrint"; - continue; - } - } - } - } - } - - if (count($aErrors)!=0) - { - $sMsg = "Below precompiled files are not up to date. Please run a new setup and save your precompiled files again:\n"; - $sMsg .= implode("\n", $aErrors); - $this->fail($sMsg); - } - else - { - $this->assertTrue(true); - } - } - function KeepSignatureDiff($sSignature1, $sSignature2) : string { $aSignature1 = json_decode($sSignature1, true); $aSignature2 = json_decode($sSignature2, true); diff --git a/test/application/UtilsTest.php b/test/application/UtilsTest.php index 1b425ccda3..1e207d980b 100644 --- a/test/application/UtilsTest.php +++ b/test/application/UtilsTest.php @@ -19,17 +19,13 @@ * */ +use Combodo\iTop\Test\UnitTest\ItopTestCase; + /** * @covers utils */ -class UtilsTest extends \Combodo\iTop\Test\UnitTest\ItopTestCase +class UtilsTest extends ItopTestCase { - public function setUp() - { - parent::setUp(); - require_once(APPROOT.'application/utils.inc.php'); - } - public function testEndsWith() { $this->assertFalse(utils::EndsWith('a', 'bbbb')); @@ -611,4 +607,79 @@ class UtilsTest extends \Combodo\iTop\Test\UnitTest\ItopTestCase '2G' => ['2G', 2 * 1024 * 1024 * 1024], ]; } + + /** + * @param string|null $sString + * @param int $iExpected + * + * @dataProvider StrLenProvider + */ + public function testStrLen(?string $sString, int $iExpected) + { + $iComputed = utils::StrLen($sString); + self::assertEquals($iExpected, $iComputed, 'Length was not as expected'); + } + + public function StrLenProvider(): array + { + return [ + 'null value' => [null, 0], + '0 character' => ['', 0], + '1 character' => ['a', 1], + '5 characters' => ['abcde', 5], + ]; + } + + /** + * Test sanitizer. + * + * @param $type string type of sanitizer + * @param $valueToSanitize ? value to sanitize + * @param $expectedResult ? expected result + * + * @return void + * + * @dataProvider sanitizerDataProvider + */ + public function testSanitizer($type, $valueToSanitize, $expectedResult) + { + $this->assertEquals($expectedResult, utils::Sanitize($valueToSanitize, null, $type), 'url sanitize failed'); + } + + /** + * DataProvider for testSanitizer + * + * @return array + */ + public function sanitizerDataProvider() + { + return [ + 'good integer' => ['integer', '2565', '2565'], + 'bad integer' => ['integer', 'a2656', '2656'], + /** + * 'class' filter needs a loaded datamodel... and is only an indirection to \MetaModel::IsValidClass so might very important to test ! + * If we switch this class to ItopDataTestCase then we are seeing : + * - the class now takes 18s to process instead of... 459ms when using ItopTestCase !!! + * - multiple errors are thrown in testGetAbsoluteUrlAppRootPersistency :( + * We decided it wasn't worse the effort to test the 'class' filter ! + */ + // 'good class' => ['class', 'UserRequest', 'UserRequest'], + // 'bad class' => ['class', 'MyUserRequest',null], + 'good string' => ['string', 'Is Peter smart and funny?', 'Is Peter smart and funny?'], + 'bad string' => ['string', 'Is Peter & funny?', 'Is Peter <smart> & funny?'], + 'good transaction_id' => ['transaction_id', '8965.-dd', '8965.-dd'], + 'bad transaction_id' => ['transaction_id', '8965.-dd+', null], + 'good parameter' => ['parameter', 'JU8965-dd=_', 'JU8965-dd=_'], + 'bad parameter' => ['parameter', '8965.-dd+', null], + 'good field_name' => ['field_name', 'Name->bUzz38', 'Name->bUzz38'], + 'bad field_name' => ['field_name', 'name-buzz', null], + 'good context_param' => ['context_param', '%dssD25_=%:+-', '%dssD25_=%:+-'], + 'bad context_param' => ['context_param', '%dssD,25_=%:+-', null], + 'good element_identifier' => ['element_identifier', 'AD05nb', 'AD05nb'], + 'bad element_identifier' => ['element_identifier', 'AD05nb+', 'AD05nb'], + 'good url' => ['url', 'https://www.w3schools.com', 'https://www.w3schools.com'], + 'bad url' => ['url', 'https://www.w3schoo��ls.co�m', 'https://www.w3schools.com'], + 'raw_data' => ['raw_data', '\s😃😃😃', '\s😃😃😃'], + ]; + } } diff --git a/test/application/composer/iTopComposerTest.php b/test/application/composer/iTopComposerTest.php index f139944fa8..d04db2827a 100644 --- a/test/application/composer/iTopComposerTest.php +++ b/test/application/composer/iTopComposerTest.php @@ -27,7 +27,7 @@ use Combodo\iTop\Test\UnitTest\ItopTestCase; class iTopComposerTest extends ItopTestCase { - protected function setUp() + protected function setUp(): void { parent::setUp(); clearstatcache(); @@ -40,7 +40,7 @@ class iTopComposerTest extends ItopTestCase public function testIsTestDir($sDirName, $bIsTest) { $isTestDir = iTopComposer::IsTestDir($sDirName); - $this->assertInternalType('int', $isTestDir); + $this->assertIsInt($isTestDir); if (true === $bIsTest) { $this->assertTrue(($isTestDir > 0)); } else { diff --git a/test/application/privUITransactionFileTest.php b/test/application/privUITransactionFileTest.php index 7cc7ec8de4..cbdf782b29 100644 --- a/test/application/privUITransactionFileTest.php +++ b/test/application/privUITransactionFileTest.php @@ -26,6 +26,7 @@ use Combodo\iTop\Test\UnitTest\ItopDataTestCase; * * @covers utils * @group sampleDataNeeded + * @group defaultProfiles */ class privUITransactionFileTest extends ItopDataTestCase { @@ -34,13 +35,6 @@ class privUITransactionFileTest extends ItopDataTestCase const USER1_TEST_LOGIN = 'user1_support_test_privUITransaction'; const USER2_TEST_LOGIN = 'user2_support_test_privUITransaction'; - public function setUp() - { - parent::setUp(); - require_once(APPROOT.'/application/startup.inc.php'); -// require_once(APPROOT.'application/utils.inc.php'); - } - /** * @dataProvider cleanupOldTransactionsProvider */ diff --git a/test/application/query/QueryTest.php b/test/application/query/QueryTest.php index 322d14fc33..9c2ab7f847 100644 --- a/test/application/query/QueryTest.php +++ b/test/application/query/QueryTest.php @@ -48,7 +48,7 @@ class QueryTest extends ItopDataTestCase private $oUser; /** @inheritDoc */ - public function setUp() + public function setUp(): void { parent::setUp(); @@ -191,7 +191,7 @@ class QueryTest extends ItopDataTestCase } /** @inheritDoc */ - protected function tearDown() + protected function tearDown(): void { $this->oUser->DBDelete(); } diff --git a/test/application/search/CriterionConversionTest.php b/test/application/search/CriterionConversionTest.php index 2cad3883c0..3a5bacd63d 100644 --- a/test/application/search/CriterionConversionTest.php +++ b/test/application/search/CriterionConversionTest.php @@ -53,16 +53,6 @@ class CriterionConversionTest extends ItopDataTestCase { const CREATE_TEST_ORG = true; - /** - * @throws \Exception - */ - protected function setUp() - { - parent::setUp(); - - require_once(APPROOT."sources/Application/Search/criterionconversionabstract.class.inc.php"); - } - /** * @dataProvider ToOqlProvider * diff --git a/test/application/search/CriterionParserTest.php b/test/application/search/CriterionParserTest.php index e23fb857c3..b785af418b 100644 --- a/test/application/search/CriterionParserTest.php +++ b/test/application/search/CriterionParserTest.php @@ -29,7 +29,7 @@ namespace Combodo\iTop\Test\UnitTest\Application\Search; use Combodo\iTop\Application\Search\CriterionParser; -use Combodo\iTop\Test\UnitTest\ItopTestCase; +use Combodo\iTop\Test\UnitTest\ItopDataTestCase; /** * @group itopRequestMgmt @@ -37,16 +37,8 @@ use Combodo\iTop\Test\UnitTest\ItopTestCase; * @preserveGlobalState disabled * @backupGlobals disabled */ -class CriterionParserTest extends ItopTestCase +class CriterionParserTest extends ItopDataTestCase { - protected function setUp() - { - parent::setUp(); - - require_once(APPROOT."application/startup.inc.php"); - require_once(APPROOT."sources/Application/Search/criterionparser.class.inc.php"); - } - public function testParse() { $sBaseOql = 'SELECT UserRequest'; diff --git a/test/application/search/SearchFormTest.php b/test/application/search/SearchFormTest.php index 48e34fc664..d71c0d4fac 100644 --- a/test/application/search/SearchFormTest.php +++ b/test/application/search/SearchFormTest.php @@ -38,16 +38,6 @@ class SearchFormTest extends ItopDataTestCase { const CREATE_TEST_ORG = true; - /** - * @throws Exception - */ - protected function setUp() - { - parent::setUp(); - - require_once(APPROOT."sources/Application/Search/searchform.class.inc.php"); - } - /** * @dataProvider GetFieldsProvider * @throws \OQLException diff --git a/test/composer.json b/test/composer.json index 511fa90834..61b5163471 100644 --- a/test/composer.json +++ b/test/composer.json @@ -1,10 +1,6 @@ { - "require": { - "phpunit/phpunit": "^6" - }, - - "autoload": { - "psr-4": { "": "src/" } - } - + "require-dev": { + "phpunit/phpunit": "^8.5.23", + "sempro/phpunit-pretty-print": "^1.4" + } } diff --git a/test/composer.lock b/test/composer.lock index 094a73224c..1dadb48f1d 100644 --- a/test/composer.lock +++ b/test/composer.lock @@ -1,41 +1,40 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e45f846daa710cb799f80d91c1f35aef", - "packages": [ + "content-hash": "e34c30ef7d10c0cee9ea3624f8378087", + "packages": [], + "packages-dev": [ { "name": "doctrine/instantiator", - "version": "1.1.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.1 || ^8.0" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "doctrine/coding-standard": "^9", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "^6.2.3", - "squizlabs/php_codesniffer": "^3.0.2" + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" @@ -49,47 +48,69 @@ { "name": "Marco Pivetta", "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" + "homepage": "https://ocramius.github.io/" } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], - "time": "2017-07-22T11:58:36+00:00" + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-03-03T08:28:38+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.7.0", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^4.1" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, "files": [ "src/DeepCopy/deep_copy.php" - ] + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -103,32 +124,43 @@ "object", "object graph" ], - "time": "2017-10-19T19:58:43+00:00" + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2022-03-03T13:19:32+00:00" }, { "name": "phar-io/manifest", - "version": "1.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", "shasum": "" }, "require": { "ext-dom": "*", "ext-phar": "*", - "phar-io/version": "^1.0.1", - "php": "^5.6 || ^7.0" + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -158,24 +190,28 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2017-03-05T18:14:27+00:00" + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" }, { "name": "phar-io/version", - "version": "1.0.1", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { @@ -205,39 +241,38 @@ } ], "description": "Library for handling version information and constraints", - "time": "2017-03-05T17:38:23+00:00" + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" }, { "name": "phpdocumentor/reflection-common", - "version": "1.0.1", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", "shasum": "" }, "require": { - "php": ">=5.5" - }, - "require-dev": { - "phpunit/phpunit": "^4.6" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-2.x": "2.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] + "phpDocumentor\\Reflection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -259,44 +294,46 @@ "reflection", "static analysis" ], - "time": "2017-09-11T18:02:19+00:00" + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.3.0", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08" + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", + "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", "shasum": "" }, "require": { - "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0.0", - "phpdocumentor/type-resolver": "^0.4.0", - "webmozart/assert": "^1.0" + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" }, "require-dev": { - "doctrine/instantiator": "~1.0.5", - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^6.4" + "mockery/mockery": "~1.3.2", + "psalm/phar": "^4.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.x-dev" + "dev-master": "5.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -307,44 +344,50 @@ { "name": "Mike van Riel", "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-11-30T07:14:17+00:00" + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + }, + "time": "2021-10-19T17:43:47+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "0.4.0", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + "reference": "77a32518733312af16a44300404e945338981de3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", + "reference": "77a32518733312af16a44300404e945338981de3", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "phpdocumentor/reflection-common": "^1.0" + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" + "ext-tokenizer": "*", + "psalm/phar": "^4.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -357,42 +400,47 @@ "email": "me@mikevanriel.com" } ], - "time": "2017-07-14T14:27:02+00:00" + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" + }, + "time": "2022-03-15T21:29:03+00:00" }, { "name": "phpspec/prophecy", - "version": "1.7.6", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712" + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", + "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0|^3.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.2", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" }, "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + "phpspec/phpspec": "^6.0 || ^7.0", + "phpunit/phpunit": "^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { - "psr-0": { - "Prophecy\\": "src/" + "psr-4": { + "Prophecy\\": "src/Prophecy" } }, "notification-url": "https://packagist.org/downloads/", @@ -420,44 +468,48 @@ "spy", "stub" ], - "time": "2018-04-18T13:57:24+00:00" + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" + }, + "time": "2021-12-08T12:19:24+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "5.3.2", + "version": "7.0.15", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "c89677919c5dd6d3b3852f230a663118762218ac" + "reference": "819f92bba8b001d4363065928088de22f25a3a48" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", - "reference": "c89677919c5dd6d3b3852f230a663118762218ac", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/819f92bba8b001d4363065928088de22f25a3a48", + "reference": "819f92bba8b001d4363065928088de22f25a3a48", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", - "php": "^7.0", - "phpunit/php-file-iterator": "^1.4.2", + "php": ">=7.2", + "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^2.0.1", + "phpunit/php-token-stream": "^3.1.3 || ^4.0", "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.0", + "sebastian/environment": "^4.2.2", "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1" + "theseer/tokenizer": "^1.1.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^8.2.2" }, "suggest": { - "ext-xdebug": "^2.5.5" + "ext-xdebug": "^2.7.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3.x-dev" + "dev-master": "7.0-dev" } }, "autoload": { @@ -483,29 +535,42 @@ "testing", "xunit" ], - "time": "2018-04-06T15:36:58+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.15" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-07-26T12:20:09+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "1.4.5", + "version": "2.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", + "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -520,7 +585,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -530,7 +595,17 @@ "filesystem", "iterator" ], - "time": "2017-11-27T13:52:08+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:42:26+00:00" }, { "name": "phpunit/php-text-template", @@ -571,32 +646,36 @@ "keywords": [ "template" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" + }, "time": "2015-06-21T13:50:34+00:00" }, { "name": "phpunit/php-timer", - "version": "1.0.9", + "version": "2.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662", + "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "phpunit/phpunit": "^8.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -611,7 +690,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -620,33 +699,43 @@ "keywords": [ "timer" ], - "time": "2017-02-26T11:10:40+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T08:20:02+00:00" }, { "name": "phpunit/php-token-stream", - "version": "2.0.2", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "791198a2c6254db10131eecfe8c06670700904db" + "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", - "reference": "791198a2c6254db10131eecfe8c06670700904db", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/a853a0e183b9db7eed023d7933a858fa1c8d25a3", + "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": "^7.0" + "php": "^7.3 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^6.2.4" + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -669,57 +758,67 @@ "keywords": [ "tokenizer" ], - "time": "2017-11-27T05:48:46+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", + "source": "https://github.com/sebastianbergmann/php-token-stream/tree/master" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "abandoned": true, + "time": "2020-08-04T08:28:15+00:00" }, { "name": "phpunit/phpunit", - "version": "6.5.8", + "version": "8.5.26", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "4f21a3c6b97c42952fd5c2837bb354ec0199b97b" + "reference": "ef117c59fc4c54a979021b26d08a3373e386606d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4f21a3c6b97c42952fd5c2837bb354ec0199b97b", - "reference": "4f21a3c6b97c42952fd5c2837bb354ec0199b97b", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ef117c59fc4c54a979021b26d08a3373e386606d", + "reference": "ef117c59fc4c54a979021b26d08a3373e386606d", "shasum": "" }, "require": { + "doctrine/instantiator": "^1.3.1", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "^1.6.1", - "phar-io/manifest": "^1.0.1", - "phar-io/version": "^1.0", - "php": "^7.0", - "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^5.3", - "phpunit/php-file-iterator": "^1.4.3", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.0", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.2", + "phpspec/prophecy": "^1.10.3", + "phpunit/php-code-coverage": "^7.0.12", + "phpunit/php-file-iterator": "^2.0.4", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^1.0.9", - "phpunit/phpunit-mock-objects": "^5.0.5", - "sebastian/comparator": "^2.1", - "sebastian/diff": "^2.0", - "sebastian/environment": "^3.1", - "sebastian/exporter": "^3.1", - "sebastian/global-state": "^2.0", + "phpunit/php-timer": "^2.1.2", + "sebastian/comparator": "^3.0.2", + "sebastian/diff": "^3.0.2", + "sebastian/environment": "^4.2.3", + "sebastian/exporter": "^3.1.2", + "sebastian/global-state": "^3.0.0", "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^1.0", + "sebastian/resource-operations": "^2.0.1", + "sebastian/type": "^1.1.3", "sebastian/version": "^2.0.1" }, - "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2", - "phpunit/dbunit": "<3.0" - }, "require-dev": { "ext-pdo": "*" }, "suggest": { + "ext-soap": "*", "ext-xdebug": "*", - "phpunit/php-invoker": "^1.1" + "phpunit/php-invoker": "^2.0.0" }, "bin": [ "phpunit" @@ -727,7 +826,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.5.x-dev" + "dev-master": "8.5-dev" } }, "autoload": { @@ -753,86 +852,41 @@ "testing", "xunit" ], - "time": "2018-04-10T11:38:34+00:00" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "5.0.6", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf" + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.26" }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/33fd41a76e746b8fa96d00b49a23dadfa8334cdf", - "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.5", - "php": "^7.0", - "phpunit/php-text-template": "^1.2.1", - "sebastian/exporter": "^3.1" - }, - "conflict": { - "phpunit/phpunit": "<6.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.5" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ + "funding": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" } ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "time": "2018-01-06T05:45:45+00:00" + "time": "2022-04-01T12:34:39+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": ">=5.6" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.0" + "phpunit/phpunit": "^8.5" }, "type": "library", "extra": { @@ -857,34 +911,44 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04T06:30:41+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T08:15:22+00:00" }, { "name": "sebastian/comparator", - "version": "2.1.3", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + "reference": "1071dfcef776a57013124ff35e1fc41ccd294758" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1071dfcef776a57013124ff35e1fc41ccd294758", + "reference": "1071dfcef776a57013124ff35e1fc41ccd294758", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/diff": "^2.0 || ^3.0", + "php": ">=7.1", + "sebastian/diff": "^3.0", "sebastian/exporter": "^3.1" }, "require-dev": { - "phpunit/phpunit": "^6.4" + "phpunit/phpunit": "^8.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -897,6 +961,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -908,10 +976,6 @@ { "name": "Bernhard Schussek", "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" } ], "description": "Provides the functionality to compare PHP values for equality", @@ -921,32 +985,43 @@ "compare", "equality" ], - "time": "2018-02-01T13:46:46+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T08:04:30+00:00" }, { "name": "sebastian/diff", - "version": "2.0.1", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", - "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211", + "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "^6.2" + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -959,46 +1034,62 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], "description": "Diff implementation", "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "diff" + "diff", + "udiff", + "unidiff", + "unified diff" ], - "time": "2017-08-03T08:09:46+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:59:04+00:00" }, { "name": "sebastian/environment", - "version": "3.1.0", + "version": "4.2.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", + "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "^6.1" + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -1023,29 +1114,39 @@ "environment", "hhvm" ], - "time": "2017-07-01T08:51:00+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:53:42+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.0", + "version": "3.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", + "reference": "0c32ea2e40dbf59de29f3b49bf375176ce7dd8db", "shasum": "" }, "require": { - "php": "^7.0", + "php": ">=7.0", "sebastian/recursion-context": "^3.0" }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^8.5" }, "type": "library", "extra": { @@ -1063,6 +1164,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -1071,17 +1176,13 @@ "name": "Volker Dusch", "email": "github@wallbash.com" }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, { "name": "Adam Harvey", "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], "description": "Provides the functionality to export PHP variables for visualization", @@ -1090,27 +1191,40 @@ "export", "exporter" ], - "time": "2017-04-03T13:19:02+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-11-11T13:51:24+00:00" }, { "name": "sebastian/global-state", - "version": "2.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + "reference": "de036ec91d55d2a9e0db2ba975b512cdb1c23921" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/de036ec91d55d2a9e0db2ba975b512cdb1c23921", + "reference": "de036ec91d55d2a9e0db2ba975b512cdb1c23921", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=7.2", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "ext-dom": "*", + "phpunit/phpunit": "^8.0" }, "suggest": { "ext-uopz": "*" @@ -1118,7 +1232,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1141,24 +1255,34 @@ "keywords": [ "global state" ], - "time": "2017-04-27T15:39:26+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-02-10T06:55:38+00:00" }, { "name": "sebastian/object-enumerator", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", "shasum": "" }, "require": { - "php": "^7.0", + "php": ">=7.0", "sebastian/object-reflector": "^1.1.1", "sebastian/recursion-context": "^3.0" }, @@ -1188,24 +1312,34 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-08-03T12:35:26+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:40:27+00:00" }, { "name": "sebastian/object-reflector", - "version": "1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "773f97c67f28de00d397be301821b06708fca0be" + "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", - "reference": "773f97c67f28de00d397be301821b06708fca0be", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=7.0" }, "require-dev": { "phpunit/phpunit": "^6.0" @@ -1233,24 +1367,34 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2017-03-29T09:07:27+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:37:18+00:00" }, { "name": "sebastian/recursion-context", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", + "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", "shasum": "" }, "require": { - "php": "^7.0" + "php": ">=7.0" }, "require-dev": { "phpunit/phpunit": "^6.0" @@ -1271,14 +1415,14 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, { "name": "Adam Harvey", "email": "aharvey@php.net" @@ -1286,29 +1430,39 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2017-03-03T06:23:57+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:34:24+00:00" }, { "name": "sebastian/resource-operations", - "version": "1.0.0", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3", + "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3", "shasum": "" }, "require": { - "php": ">=5.6.0" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1328,7 +1482,73 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28T20:34:47+00:00" + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:30:19+00:00" + }, + { + "name": "sebastian/type", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0150cfbc4495ed2df3872fb31b26781e4e077eb4", + "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/1.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:25:11+00:00" }, { "name": "sebastian/version", @@ -1371,27 +1591,151 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/master" + }, "time": "2016-10-03T07:35:21+00:00" }, { - "name": "theseer/tokenizer", - "version": "1.1.0", + "name": "sempro/phpunit-pretty-print", + "version": "1.4.0", "source": { "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + "url": "https://github.com/s360digital/phpunit-pretty-print.git", + "reference": "fa623aa8a17aece4a2b69e54b07a5e37572d1f1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "url": "https://api.github.com/repos/s360digital/phpunit-pretty-print/zipball/fa623aa8a17aece4a2b69e54b07a5e37572d1f1d", + "reference": "fa623aa8a17aece4a2b69e54b07a5e37572d1f1d", + "shasum": "" + }, + "require": { + "php": ">=7.1.0", + "phpunit/phpunit": "^7 || ^8 || ^9" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Sempro\\PHPUnitPrettyPrinter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Prettify PHPUnit output", + "support": { + "issues": "https://github.com/s360digital/phpunit-pretty-print/issues", + "source": "https://github.com/s360digital/phpunit-pretty-print/tree/1.4.0" + }, + "time": "2021-01-04T13:25:10+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.25.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "30885182c981ab175d4d034db0f6f469898070ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", + "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-10-20T20:35:02+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", "shasum": "" }, "require": { "ext-dom": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", - "php": "^7.0" + "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { @@ -1411,33 +1755,47 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07T12:08:54+00:00" + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" }, { "name": "webmozart/assert", - "version": "1.3.0", + "version": "1.10.0", "source": { "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + "url": "https://github.com/webmozarts/assert.git", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^7.2 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" }, "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" + "phpunit/phpunit": "^8.5.13" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.10-dev" } }, "autoload": { @@ -1461,15 +1819,19 @@ "check", "validate" ], - "time": "2018-01-29T19:49:41+00:00" + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.10.0" + }, + "time": "2021-03-09T10:59:23+00:00" } ], - "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": [], - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "2.3.0" } diff --git a/test/core/ActionEmailTest.php b/test/core/ActionEmailTest.php new file mode 100644 index 0000000000..3f78df997b --- /dev/null +++ b/test/core/ActionEmailTest.php @@ -0,0 +1,91 @@ + 'Test action', + 'status' => 'disabled', + 'from' => 'unit-test@openitop.org', + 'subject' => 'Test subject', + 'body' => 'Test body', + ]); + static::$oActionEmail->DBInsert(); + } + + /** + * @covers \ActionEmail::GenerateIdentifierForHeaders + * @dataProvider GenerateIdentifierForHeadersProvider + * @throws \Exception + */ + public function testGenerateIdentifierForHeaders(string $sHeaderName) + { + // Retrieve object + $oObject = MetaModel::GetObject('Organization', $this->getTestOrgId(), true, true); + $sObjClass = get_class($oObject); + $sObjId = $oObject->GetKey(); + + try { + $sTestedIdentifier = $this->InvokeNonPublicMethod('\ActionEmail', 'GenerateIdentifierForHeaders', static::$oActionEmail, [$oObject, $sHeaderName]); + } catch (Exception $oException) { + $sTestedIdentifier = null; + } + + $sAppName = utils::Sanitize(ITOP_APPLICATION_SHORT, '', utils::ENUM_SANITIZATION_FILTER_VARIABLE_NAME); + $sEnvironmentHash = MetaModel::GetEnvironmentId(); + + switch ($sHeaderName) { + case ActionEmail::ENUM_HEADER_NAME_MESSAGE_ID: + // Note: For this test we can't use the more readable sprintf test as the generated timestamp will never be the same as the one generated during the call of the tested method + // $sTimestamp = microtime(true /* get as float*/); + // $sExpectedIdentifier = sprintf('%s_%s_%d_%f@%s.openitop.org', $sAppName, $sObjClass, $sObjId, $sTimestamp, $sEnvironmentHash); + $this->assertEquals(1, preg_match('/'.$sAppName.'_'.$sObjClass.'_'.$sObjId.'_[\d]+\.[\d]+@'.$sEnvironmentHash.'.openitop.org/', $sTestedIdentifier), "Identifier doesn't match regexp for header $sHeaderName, got $sTestedIdentifier"); + break; + + case ActionEmail::ENUM_HEADER_NAME_REFERENCES: + $sExpectedIdentifier = '<'.sprintf('%s_%s_%d@%s.openitop.org', $sAppName, $sObjClass, $sObjId, $sEnvironmentHash).'>'; + $this->assertEquals($sExpectedIdentifier, $sTestedIdentifier); + break; + + default: + $sExpectedIdentifier = null; + $this->assertEquals($sExpectedIdentifier, $sTestedIdentifier); + break; + } + + } + + public function GenerateIdentifierForHeadersProvider() + { + return [ + 'Message-ID' => ['Message-ID'], + 'References' => ['References'], + 'IncorrectHeaderName' => ['IncorrectHeaderName'], + ]; + } +} \ No newline at end of file diff --git a/test/core/AttributeDefTest.inc.php b/test/core/AttributeDefTest.inc.php index 1ff8168d88..62bc0251dc 100644 --- a/test/core/AttributeDefTest.inc.php +++ b/test/core/AttributeDefTest.inc.php @@ -14,7 +14,7 @@ use MetaModel; class AttributeDefTest extends ItopDataTestCase { const CREATE_TEST_ORG = true; - protected function setUp() { + protected function setUp(): void { parent::setUp(); require_once(APPROOT.'core/attributedef.class.inc.php'); diff --git a/test/core/AttributeURLDefaultPattern.php b/test/core/AttributeURLDefaultPattern.php index bf647db2df..9230bbd4b1 100644 --- a/test/core/AttributeURLDefaultPattern.php +++ b/test/core/AttributeURLDefaultPattern.php @@ -9,6 +9,6 @@ class AttributeURLDefaultPattern extends AttributeURL { { /** @noinspection OneTimeUseVariablesInspection */ $oConfig = utils::GetConfig(); - return $oConfig->GetDefault('url_validation_pattern'); + return '^'.$oConfig->GetDefault('url_validation_pattern').'$'; } } diff --git a/test/core/AttributeURLTest.php b/test/core/AttributeURLTest.php index 9d75fcbb09..08d1831993 100644 --- a/test/core/AttributeURLTest.php +++ b/test/core/AttributeURLTest.php @@ -11,7 +11,7 @@ use Combodo\iTop\Test\UnitTest\ItopTestCase; * @backupGlobals disabled */ class AttributeURLTest extends ItopTestCase { - public function setUp() + public function setUp(): void { parent::setUp(); require_once APPROOT.'core/attributedef.class.inc.php'; @@ -38,6 +38,8 @@ class AttributeURLTest extends ItopTestCase { 'Sharepoint URL 1' => ['https://mydomain1.sharepoint.com/:i:/r/sites/DSIMyDept/Shared%20Documents/Architecture%20Technique/02%20-%20R%C3%A9seau/Baie%2025C/Baie%201er/Baie-25C-1er.jpg?csf=1&web=1&e=Il3txR', 1], 'Sharepoint URL 2' => ['https://mydomain2.sharepoint.com/:u:/r/sites/DIS/ITSM/00_Admin_iTOP/iTop%20-%20Upgrade%20manuel/Procedure%20upgrade%20Combodo.url?csf=1&web=1&e=DAF0i3', 1], 'Alfresco URL 2' => ['http://alfresco.mydomain3.org/share/page/site/books/document-details?nodeRef=workspace://SpacesStore/6274f55f-a25b-4762-a863-77f7066f2034', 1], + 'SF URL' => ['https://sourceforge.net/p/itop/discussion/customizing-itop/thread/707145b859/?limit=25#f53c', 1], + 'SF URL anchor starting with digit' => ['https://sourceforge.net/p/itop/discussion/customizing-itop/thread/b0a2d474ba/?limit=25#2b35', 1], ]; } } diff --git a/test/core/BulkChangeTest.inc.php b/test/core/BulkChangeTest.inc.php index 2010b2d403..9d2dd1b997 100644 --- a/test/core/BulkChangeTest.inc.php +++ b/test/core/BulkChangeTest.inc.php @@ -14,7 +14,8 @@ use MetaModel; class BulkChangeTest extends ItopDataTestCase { const CREATE_TEST_ORG = true; - protected function setUp() { + protected function setUp(): void + { parent::setUp(); require_once(APPROOT.'core/bulkchange.class.inc.php'); diff --git a/test/core/CMDBObjectTest.php b/test/core/CMDBObjectTest.php new file mode 100644 index 0000000000..fd2368fc0f --- /dev/null +++ b/test/core/CMDBObjectTest.php @@ -0,0 +1,194 @@ +Set('name', 'PHPUnit test'); + $oTestObject->Set('org_id', 1); + $oTestObject->Set('url', 'https://www.combodo.com'); + $oTestObject->DBWrite(); + self::assertFalse(CMDBObject::GetCurrentChange()->IsNew(), 'TrackInfo : Current change persisted'); + self::assertEquals($sTrackInfo, CMDBObject::GetCurrentChange()->Get('userinfo'), + 'TrackInfo : current change created with expected trackinfo'); + + //-- new object with non persisted current change + $sTrackInfo2 = $sTrackInfo.'_2'; + /** @var \CMDBChange $oCustomChange */ + $oCustomChange = MetaModel::NewObject('CMDBChange'); + $oCustomChange->Set('date', time()); + $oCustomChange->Set('userinfo', $sTrackInfo2); + CMDBObject::SetCurrentChange($oCustomChange); + $oTestObject->Set('url', 'https://fr.wikipedia.org'); + $oTestObject->DBUpdate(); + self::assertFalse(CMDBObject::GetCurrentChange()->IsNew(), 'SetCurrentChange : Current change persisted'); + self::assertEquals($sTrackInfo2, CMDBObject::GetCurrentChange()->Get('userinfo'), + 'SetCurrentChange : current change created with expected trackinfo'); + + //-- new object with current change init using helper method + $sTrackInfo3 = $sTrackInfo.'_3'; + CMDBObject::SetCurrentChangeFromParams($sTrackInfo3); + $oTestObject->Set('url', 'https://en.wikipedia.org'); + $oTestObject->DBUpdate(); + self::assertFalse(CMDBObject::GetCurrentChange()->IsNew(), 'SetCurrentChangeFromParams : Current change persisted'); + self::assertEquals($sTrackInfo3, CMDBObject::GetCurrentChange()->Get('userinfo'), + 'SetCurrentChangeFromParams : current change created with expected trackinfo'); + + // restore initial conditions + $oTestObject->DBDelete(); + CMDBObject::SetCurrentChange($oInitialCurrentChange); + CMDBObject::SetTrackInfo($sInitialTrackInfo); + } + + public function CurrentChangeUnderImpersonationProvider(){ + return [ + 'no track info' => [ 'sTrackInfo' => null ], + 'track info from approvalbase' => [ + 'sTrackInfo' => 'ImpersonatedSurName ImpersonatedName Approved on behalf of YYY', + 'sExpectedChangeLogWhenImpersonation' => "AdminSurName AdminName on behalf of ImpersonatedSurName ImpersonatedName (ImpersonatedSurName ImpersonatedName Approved on behalf of YYY)", + ], + ]; + } + + /** + * @covers CMDBObject::SetCurrentChange + * @since 3.0.1 N°5135 - Impersonate: history of changes versus log entries + * + * @dataProvider CurrentChangeUnderImpersonationProvider + */ + public function testCurrentChangeUnderImpersonation($sTrackInfo=null, $sExpectedChangeLogWhenImpersonation=null) { + $this->CreateTestOrganization(); + + $sUid = date('dmYHis'); + $sAdminLogin = "admin-user-".$sUid; + $sImpersonatedLogin = "impersonated-user-".$sUid; + + $iAdminUserId = $this->CreateUserForImpersonation($sAdminLogin, 'Administrator', 'AdminName', 'AdminSurName'); + $this->CreateUserForImpersonation($sImpersonatedLogin, 'Configuration Manager', 'ImpersonatedName', 'ImpersonatedSurName'); + + $_SESSION = []; + \UserRights::Login($sAdminLogin); + + // save initial conditions + $oInitialCurrentChange = CMDBObject::GetCurrentChange(); + $sInitialTrackInfo = CMDBObject::GetTrackInfo(); + + // reset current change + CMDBObject::SetCurrentChange(null); + + if (is_null($sTrackInfo)){ + CMDBObject::SetTrackInfo(null); + } else { + CMDBObject::SetTrackInfo($sTrackInfo); + } + + $this->CreateSimpleObject(); + if (is_null($sTrackInfo)){ + self::assertEquals("AdminSurName AdminName", CMDBObject::GetCurrentChange()->Get('userinfo'), + 'TrackInfo : no impersonation'); + } else { + self::assertEquals($sTrackInfo, CMDBObject::GetCurrentChange()->Get('userinfo'), + 'TrackInfo : no impersonation'); + } + self::assertEquals($iAdminUserId, CMDBObject::GetCurrentChange()->Get('user_id'), + 'TrackInfo : admin userid'); + + \UserRights::Impersonate($sImpersonatedLogin); + $this->CreateSimpleObject(); + + if (is_null($sExpectedChangeLogWhenImpersonation)){ + self::assertEquals("AdminSurName AdminName on behalf of ImpersonatedSurName ImpersonatedName", CMDBObject::GetCurrentChange()->Get('userinfo'), + 'TrackInfo : impersonation'); + } else { + self::assertEquals($sExpectedChangeLogWhenImpersonation, CMDBObject::GetCurrentChange()->Get('userinfo'), + 'TrackInfo : impersonation'); + } + + self::assertEquals(null, CMDBObject::GetCurrentChange()->Get('user_id'), + 'TrackInfo : no userid to force userinfo being displayed on UI caselog side'); + + \UserRights::Deimpersonate(); + $this->CreateSimpleObject(); + if (is_null($sTrackInfo)){ + self::assertEquals("AdminSurName AdminName", CMDBObject::GetCurrentChange()->Get('userinfo'), + 'TrackInfo : no impersonation'); + } else { + self::assertEquals($sTrackInfo, CMDBObject::GetCurrentChange()->Get('userinfo'), + 'TrackInfo : no impersonation'); + } + self::assertEquals($iAdminUserId, CMDBObject::GetCurrentChange()->Get('user_id'), + 'TrackInfo : admin userid'); + + // restore initial conditions + CMDBObject::SetCurrentChange($oInitialCurrentChange); + CMDBObject::SetTrackInfo($sInitialTrackInfo); + } + + private function CreateSimpleObject(){ + /** @var \DocumentWeb $oTestObject */ + $oTestObject = MetaModel::NewObject('DocumentWeb'); + $oTestObject->Set('name', 'PHPUnit test'); + $oTestObject->Set('org_id', $this->getTestOrgId() ); + $oTestObject->Set('url', 'https://www.combodo.com'); + $oTestObject->DBWrite(); + } + + private function CreateUserForImpersonation($sLogin, $sProfileName, $sName, $sSurname): int { + /** @var \Person $oPerson */ + $oPerson = $this->createObject('Person', array( + 'name' => $sName, + 'first_name' => $sSurname, + 'org_id' => $this->getTestOrgId(), + )); + + $oProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => $sProfileName), true); + $oUser = $this->CreateUser($sLogin, $oProfile->GetKey(), "1234567Azert@", $oPerson->GetKey()); + + return $oUser->GetKey(); + } +} diff --git a/test/core/CMDBSource/CMDBSourceTest.php b/test/core/CMDBSource/CMDBSourceTest.php index 81410ec139..588c8dac78 100644 --- a/test/core/CMDBSource/CMDBSourceTest.php +++ b/test/core/CMDBSource/CMDBSourceTest.php @@ -21,7 +21,7 @@ use utils; */ class CMDBSourceTest extends ItopTestCase { - protected function setUp() + protected function setUp(): void { parent::setUp(); diff --git a/test/core/CMDBSource/TransactionsTest.php b/test/core/CMDBSource/TransactionsTest.php index e1495b1548..00f9872d81 100644 --- a/test/core/CMDBSource/TransactionsTest.php +++ b/test/core/CMDBSource/TransactionsTest.php @@ -18,6 +18,7 @@ use MetaModel; * @backupGlobals disabled * * @group itopRequestMgmt + * @group specificOrgInSampleData * Class TransactionsTest * * @package Combodo\iTop\Test\UnitTest\Core @@ -27,10 +28,10 @@ class TransactionsTest extends ItopTestCase /** @var DeadLockInjection */ private $oMySQLiMock; - protected function setUp() + protected function setUp(): void { parent::setUp(); - require_once ('DeadLockInjection.php'); + require_once('DeadLockInjection.php'); require_once(APPROOT.'/core/cmdbsource.class.inc.php'); $sEnv = 'production'; $sConfigFile = APPCONF.$sEnv.'/config-itop.php'; @@ -248,4 +249,4 @@ class TransactionsTest extends ItopTestCase "History 13" => ['iFailAt' => 15, 'bIsModified' => true], ]; } -} \ No newline at end of file +} diff --git a/test/core/CSVParserTest.php b/test/core/CSVParserTest.php index 9e1d5ef894..e396cbd6b6 100644 --- a/test/core/CSVParserTest.php +++ b/test/core/CSVParserTest.php @@ -8,7 +8,7 @@ use CSVParser; class CSVParserTest extends ItopTestCase { - protected function setUp() + protected function setUp(): void { parent::setUp(); diff --git a/test/core/ConfigPlaceholdersResolverTest.php b/test/core/ConfigPlaceholdersResolverTest.php index 6f10409c49..0e99c56233 100644 --- a/test/core/ConfigPlaceholdersResolverTest.php +++ b/test/core/ConfigPlaceholdersResolverTest.php @@ -23,7 +23,6 @@ namespace Combodo\iTop\Test\UnitTest\Core; use Combodo\iTop\Test\UnitTest\ItopTestCase; use ConfigPlaceholdersResolver; -use PHPUnit\Framework\TestCase; /** * @runTestsInSeparateProcesses @@ -32,10 +31,10 @@ use PHPUnit\Framework\TestCase; */ class ConfigPlaceholdersResolverTest extends ItopTestCase { - protected function setUp() + protected function setUp(): void { parent::setUp(); - require_once (APPROOT.'core/config.class.inc.php'); + require_once(APPROOT.'core/config.class.inc.php'); } /** * @dataProvider providerResolve diff --git a/test/core/ConfigTest.php b/test/core/ConfigTest.php index 6606e79473..77026e81da 100644 --- a/test/core/ConfigTest.php +++ b/test/core/ConfigTest.php @@ -31,10 +31,10 @@ use Config; */ class ConfigTest extends ItopTestCase { - protected function setUp() + protected function setUp(): void { parent::setUp(); - require_once (APPROOT.'core/config.class.inc.php'); + require_once(APPROOT.'core/config.class.inc.php'); } /** diff --git a/test/core/ConfigValidator/iTopConfigAstValidatorTest.php b/test/core/ConfigValidator/iTopConfigAstValidatorTest.php index c3f0d629fd..b30b67f14a 100644 --- a/test/core/ConfigValidator/iTopConfigAstValidatorTest.php +++ b/test/core/ConfigValidator/iTopConfigAstValidatorTest.php @@ -12,7 +12,7 @@ use Exception; class iTopConfigAstValidatorTest extends ItopTestCase { - protected function setUp() + protected function setUp(): void { parent::setUp(); require_once APPROOT.'env-production/itop-config/src/Validator/iTopConfigAstValidator.php'; @@ -31,9 +31,9 @@ class iTopConfigAstValidatorTest extends ItopTestCase $this->assertTrue(true, 'The file is valid and interpreted as such'); } - //FIXME disabled test, is failing for now with error "Invalid configuration: LEVEL_WARNING of type Identifier is forbidden in line 152" - public function __testValidateFileValidLogLevelMinConst() + public function testValidateFileValidLogLevelMinConst() { + $this->markTestSkipped(' disabled test, is failing for now with error "Invalid configuration: LEVEL_WARNING of type Identifier is forbidden in line 152"'); try { $this->CallValidatorOnFile('config-itop_VALID_log-level-min_const.php'); } diff --git a/test/core/DBObjectTest.php b/test/core/DBObjectTest.php index 27faf48782..113aee49c0 100644 --- a/test/core/DBObjectTest.php +++ b/test/core/DBObjectTest.php @@ -29,6 +29,7 @@ namespace Combodo\iTop\Test\UnitTest\Core; use Combodo\iTop\Test\UnitTest\ItopDataTestCase; use CoreException; use DBObject; +use MetaModel; /** @@ -42,7 +43,7 @@ class DBObjectTest extends ItopDataTestCase { const CREATE_TEST_ORG = true; - protected function setUp() + protected function setUp(): void { parent::setUp(); require_once(APPROOT.'core/dbobject.class.php'); @@ -95,6 +96,39 @@ class DBObjectTest extends ItopDataTestCase static::assertNull($oObject->GetOriginal('sla_tto_passed')); } + /** + * @return void + * @throws \Exception + */ + public function testListPreviousValuesForUpdatedAttributes() + { + $oOrg = $this->CreateOrganization('testListPreviousValuesForUpdatedAttributes'); + + $this->assertCount(0, $oOrg->ListChanges()); + $oOrg->Set('code', strtoupper('testListPreviousValuesForUpdatedAttributes')); + $this->assertCount(1, $oOrg->ListChanges()); + $oOrg->DBUpdate(); + $this->assertCount(0, $oOrg->ListChanges()); + $this->assertCount(1, $oOrg->ListPreviousValuesForUpdatedAttributes()); + + $oOrg->DBUpdate(); + + $this->assertCount(0, $oOrg->ListChanges()); + $this->assertCount(1, $oOrg->ListPreviousValuesForUpdatedAttributes()); + + $oOrg->DBDelete(); + + $oOrg = MetaModel::NewObject('Organization'); + $oOrg->Set('name', 'testListPreviousValuesForUpdatedAttributes'); + $oOrg->DBInsert(); + $oOrg->Set('code', strtoupper('testListPreviousValuesForUpdatedAttributes')); + $oOrg->DBUpdate(); + $oOrg->DBUpdate(); + $this->markTestIncomplete('This test has not been implemented yet. wait for N°4967 fix'); + $this->debug("ERROR: N°4967 - 'Previous Values For Updated Attributes' not updated if DBUpdate is called without modifying the object"); + //$this->assertCount(0, $oOrg->ListPreviousValuesForUpdatedAttributes()); + } + /** * @covers DBObject::NewObject * @covers DBObject::Get diff --git a/test/core/DBSearchAddConditionPointingTo.php b/test/core/DBSearchAddConditionPointingTo.php index 16a80be559..9cb15e81fb 100644 --- a/test/core/DBSearchAddConditionPointingTo.php +++ b/test/core/DBSearchAddConditionPointingTo.php @@ -12,7 +12,7 @@ use DBSearch; class DBSearchAddConditionPointingToTest extends ItopTestCase { - protected function setUp() + protected function setUp(): void { parent::setUp(); require_once(APPROOT.'application/startup.inc.php'); diff --git a/test/core/DBSearchIntersectTest.php b/test/core/DBSearchIntersectTest.php index ed65813c03..55c4fba529 100644 --- a/test/core/DBSearchIntersectTest.php +++ b/test/core/DBSearchIntersectTest.php @@ -19,7 +19,7 @@ use DBSearch; class DBSearchIntersectTest extends ItopTestCase { - protected function setUp() + protected function setUp(): void { parent::setUp(); require_once(APPROOT.'application/startup.inc.php'); @@ -337,7 +337,6 @@ class DBSearchIntersectTest extends ItopTestCase /** * @dataProvider IntersectOptimizationProvider - * @doesNotPerformAssertions * * @param string $sOQL * @param string $sResult diff --git a/test/core/DBSearchJoinTest.php b/test/core/DBSearchJoinTest.php index d2f81e515f..e9eb965852 100644 --- a/test/core/DBSearchJoinTest.php +++ b/test/core/DBSearchJoinTest.php @@ -20,7 +20,7 @@ class DBSearchJoinTest extends ItopDataTestCase { const USE_TRANSACTION = false; - protected function setUp() + protected function setUp(): void { parent::setUp(); require_once(APPROOT.'application/startup.inc.php'); diff --git a/test/core/DBSearchTest.php b/test/core/DBSearchTest.php index fd151a560d..b7c310649c 100644 --- a/test/core/DBSearchTest.php +++ b/test/core/DBSearchTest.php @@ -54,11 +54,10 @@ class DBSearchTest extends ItopDataTestCase /** * @throws \Exception */ - protected function setUp() + protected function setUp(): void { parent::setUp(); - require_once(APPROOT.'application/itopwebpage.class.inc.php'); require_once(APPROOT.'application/displayblock.class.inc.php'); } @@ -528,6 +527,7 @@ class DBSearchTest extends ItopDataTestCase /** * @dataProvider GetFirstResultProvider + * @group specificOrgInSampleData * * @param string $sOql query to test * @param bool $bMustHaveOneResultMax arg passed to the tested function diff --git a/test/core/DBSearchUpdateRealiasingMapTest.php b/test/core/DBSearchUpdateRealiasingMapTest.php index dd090bdc95..7187e466dc 100644 --- a/test/core/DBSearchUpdateRealiasingMapTest.php +++ b/test/core/DBSearchUpdateRealiasingMapTest.php @@ -19,7 +19,7 @@ class DBSearchUpdateRealiasingMapTest extends ItopDataTestCase { const USE_TRANSACTION = false; - protected function setUp() + protected function setUp(): void { parent::setUp(); require_once(APPROOT.'application/startup.inc.php'); diff --git a/test/core/ExpressionEvaluateTest.php b/test/core/ExpressionEvaluateTest.php index eaadd576a5..8df6d632ac 100644 --- a/test/core/ExpressionEvaluateTest.php +++ b/test/core/ExpressionEvaluateTest.php @@ -455,7 +455,11 @@ class ExpressionEvaluateTest extends iTopDataTestCase } /** - * Systematically check all supported format specs, for a given date + * For a given date, + * for all different formats (1st array element returned by {@see static::TimeFormatsProvider}), + * compare value returned by : + * * DATE_FORMAT() SQL function, + * * FunctionExpression('DATE_FORMAT', ...) result * * @covers FunctionExpression::Evaluate() * @dataProvider EveryTimeFormatProvider @@ -481,7 +485,8 @@ class ExpressionEvaluateTest extends iTopDataTestCase } $sSelects = "SELECT ".implode(', ', $aSelects); $aRes = CMDBSource::QueryToArray($sSelects); - $aRow = $aRes[0]; + /** @var array $aMysqlDateFormatRsultsForAllFormats format as key, MySQL evaluated result as value */ + $aMysqlDateFormatRsultsForAllFormats = $aRes[0]; foreach ($aFormats as $sFormatDesc => $aFormatSpec) { $sFormat = $aFormatSpec[0]; @@ -489,8 +494,8 @@ class ExpressionEvaluateTest extends iTopDataTestCase if ($bProcessed) { $oExpression = new FunctionExpression('DATE_FORMAT', array(new ScalarExpression($sDate), new ScalarExpression("%$sFormat"))); - $res = $oExpression->Evaluate(array()); - static::assertEquals($aRow[$sFormat], $res, "Format %$sFormat not matching MySQL for '$sDate'"); + $itopExpressionResult = $oExpression->Evaluate(array()); + static::assertSame($aMysqlDateFormatRsultsForAllFormats[$sFormat], $itopExpressionResult, "Format %$sFormat not matching MySQL for '$sDate'"); } } } diff --git a/test/core/GetSelectFilterTest.php b/test/core/GetSelectFilterTest.php index 624c1a0a46..a3db7e145b 100644 --- a/test/core/GetSelectFilterTest.php +++ b/test/core/GetSelectFilterTest.php @@ -12,8 +12,9 @@ use utils; /** - * @group getSelectFilterTest + * @group getSelectFilterTest * @group sampleDataNeeded + * @group specificOrgInSampleData * Class GetSelectFilterTest * * @runTestsInSeparateProcesses @@ -28,16 +29,16 @@ class GetSelectFilterTest extends ItopDataTestCase private $sPassword = "IAAuytrez9876[}543ç_è-("; private $oUser; - protected function setUp() + protected function setUp(): void { parent::setUp(); require_once(APPROOT.'application/startup.inc.php'); $oRestProfile = MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => 'REST Services User'), true); $oAdminProfile = MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => 'Administrator'), true); - + $this->sLogin = "getselectfilter-user-" . date('dmYHis'); - + // Ensure that we have at least one administrator account if (is_object($oRestProfile) && is_object($oAdminProfile)) { @@ -45,7 +46,7 @@ class GetSelectFilterTest extends ItopDataTestCase $this->AddProfileToUser($this->oUser, $oAdminProfile->GetKey()); } } - + public function testGetSelectFilter() { $oUserRights = new UserRightsProfile(); @@ -64,9 +65,9 @@ class GetSelectFilterTest extends ItopDataTestCase //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Default behavior: Administrators, Administrator profile and URP_UserProfile related to administrators are visible // via GetSelectFilter - + $oConfig->Set('security.hide_administrators', false); - + $oFilterProfiles = $oUserRights->GetSelectFilter($this->oUser, 'URP_Profiles'); if ($oFilterProfiles === true) { @@ -83,7 +84,7 @@ class GetSelectFilterTest extends ItopDataTestCase } } $this->assertEquals($bAdminProfileFound, true); - + foreach($aUserLocalAncestors as $sUserClass) { $bAdminUserFound = false; @@ -103,7 +104,7 @@ class GetSelectFilterTest extends ItopDataTestCase } $this->assertEquals($bAdminUserFound, true); } - + $oFilterLnkProfiles = $oUserRights->GetSelectFilter($this->oUser, 'URP_UserProfile'); if ($oFilterLnkProfiles === true) { @@ -160,6 +161,6 @@ class GetSelectFilterTest extends ItopDataTestCase $this->assertNotEquals($oLnk->Get('userid'), $this->oUser->GetKey()); $this->assertNotEquals($oLnk->Get('profileid'), 1); } - + } -} \ No newline at end of file +} diff --git a/test/core/Log/ExceptionLogTest.php b/test/core/Log/ExceptionLogTest.php index 379434c613..2cb7d8a790 100644 --- a/test/core/Log/ExceptionLogTest.php +++ b/test/core/Log/ExceptionLogTest.php @@ -17,7 +17,6 @@ namespace Combodo\iTop\Test\UnitTest\Core\Log; use Combodo\iTop\Test\UnitTest\ItopDataTestCase; use ExceptionLog; -use MetaModel; require_once(__DIR__.'/ExceptionLogTest/Exceptions.php'); @@ -29,16 +28,10 @@ require_once(__DIR__.'/ExceptionLogTest/Exceptions.php'); */ class ExceptionLogTest extends ItopDataTestCase { - protected function setUp() + protected function setUp(): void { require_once(__DIR__.'/ExceptionLogTest/Exceptions.php'); parent::setUp(); - - // We are using PHPUnit\Framework\MockObject\Generator::generateMock that is throwing notice ! - // Changing config so that those won't be caught by \DeprecatedCallsLog::DeprecatedNoticesErrorHandler - // disabling devenv is easier than changing log config O:) - $oConfig = MetaModel::GetConfig(); - $oConfig->Set('developer_mode.enabled', false); } /** diff --git a/test/core/Log/LogAPITest.php b/test/core/Log/LogAPITest.php index 158d2d9f7e..70705ced15 100644 --- a/test/core/Log/LogAPITest.php +++ b/test/core/Log/LogAPITest.php @@ -15,7 +15,6 @@ namespace Combodo\iTop\Test\UnitTest\Core\Log; use Combodo\iTop\Test\UnitTest\ItopDataTestCase; -use MetaModel; /** * @runTestsInSeparateProcesses @@ -27,16 +26,10 @@ class LogAPITest extends ItopDataTestCase private $mockFileLog; private $oMetaModelConfig; - protected function setUp() + protected function setUp():void { parent::setUp(); - // We are using PHPUnit\Framework\MockObject\Generator::generateMock that is throwing notice ! - // Changing config so that those won't be caught by \DeprecatedCallsLog::DeprecatedNoticesErrorHandler - // disabling devenv is easier than changing log config O:) - $oConfig = MetaModel::GetConfig(); - $oConfig->Set('developer_mode.enabled', false); - $this->mockFileLog = $this->createMock('FileLog'); $this->oMetaModelConfig = $this->createMock('Config'); } diff --git a/test/core/Log/LogFileNameBuilderTest.php b/test/core/Log/LogFileNameBuilderTest.php index d150d70a93..e7bdfa923c 100644 --- a/test/core/Log/LogFileNameBuilderTest.php +++ b/test/core/Log/LogFileNameBuilderTest.php @@ -28,21 +28,20 @@ class LogFileNameBuilderTest extends ItopTestCase clearstatcache(true, $sLogFile); } - protected function setUp() + protected function setUp(): void { parent::setUp(); require_once APPROOT.'core/log.class.inc.php'; } - protected function tearDown() + protected function tearDown(): void { parent::tearDown(); // remove log files created in the test $aTestLogFiles = glob(__DIR__.DIRECTORY_SEPARATOR.self::TEST_LOGFILE_PREFIX.'*.'.self::TEST_LOGFILE_EXTENSION); - foreach ($aTestLogFiles as $sLogFile) - { + foreach ($aTestLogFiles as $sLogFile) { unlink($sLogFile); } } diff --git a/test/core/MetaModelTest.php b/test/core/MetaModelTest.php index 77d312513c..02b5c2622e 100644 --- a/test/core/MetaModelTest.php +++ b/test/core/MetaModelTest.php @@ -24,7 +24,7 @@ class MetaModelTest extends ItopDataTestCase protected static $sDefaultUserRequestTitle = 'Unit test title'; protected static $sDefaultUserRequestDescription = 'Unit test description'; - protected function setUp() + protected function setUp(): void { parent::setUp(); require_once APPROOT.'/core/metamodel.class.php'; diff --git a/test/core/OQLParserTest.php b/test/core/OQLParserTest.php index afeddc6641..63cd2c6269 100644 --- a/test/core/OQLParserTest.php +++ b/test/core/OQLParserTest.php @@ -29,6 +29,7 @@ class OQLParserTest extends ItopDataTestCase * @group iTopChangeMgt * @group itopConfigMgmt * @group itopRequestMgmt + * @group specificOrgInSampleData * @dataProvider NestedQueryProvider * * @param $sQuery diff --git a/test/core/OQLTest.php b/test/core/OQLTest.php deleted file mode 100644 index 7a0610101e..0000000000 --- a/test/core/OQLTest.php +++ /dev/null @@ -1,517 +0,0 @@ -Set('use_legacy_dbsearch', false, 'test'); - utils::GetConfig()->Set('apc_cache.enabled', false, 'test'); - utils::GetConfig()->Set('query_cache_enabled', false, 'test'); - utils::GetConfig()->Set('expression_cache_enabled', false, 'test'); - $sConfigFile = utils::GetConfig()->GetLoadedFile(); - @chmod($sConfigFile, 0770); - utils::GetConfig()->WriteToFile(); - @chmod($sConfigFile, 0444); // Read-only - } - - /** - * @group iTopChangeMgt - * @group itopConfigMgmt - * @group itopRequestMgmt - * @dataProvider NestedQueryProvider - * @depends testOQLSetup - * - * @param $sQuery - * - * @throws \OQLException - */ - public function testGoodNestedQueryQueryParser($sQuery) - { - $this->debug($sQuery); - $oOql = new OqlInterpreter($sQuery); - $oQuery = $oOql->ParseQuery(); - static::assertInstanceOf('OqlQuery', $oQuery); - } - - public function NestedQueryProvider() - { - return array( - array("SELECT User AS U JOIN Person AS P ON U.contactid = P.id WHERE U.status='enabled' AND U.id NOT IN (SELECT User AS U JOIN Person AS P ON U.contactid=P.id JOIN URP_UserOrg AS L ON L.userid = U.id WHERE U.status='enabled' AND L.allowed_org_id = P.org_id UNION SELECT User AS U WHERE U.status='enabled' AND U.id NOT IN ( SELECT User AS U JOIN URP_UserOrg AS L ON L.userid = U.id WHERE U.status='enabled'))"), - array('SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE `UserRequest`.org_id IN (SELECT `Organization` FROM Organization AS `Organization` JOIN Organization AS `Organization1` ON `Organization`.parent_id BELOW `Organization1`.id WHERE (`Organization1`.`id` = \'3\'))'), - array('SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`org_id` IN (SELECT `Organization` FROM Organization AS `Organization` WHERE `Organization`.`id`=`UserRequest`.`org_id`))'), - array("SELECT UserRequest AS Ur WHERE Ur.id NOT IN (SELECT UserRequest AS Ur JOIN lnkFunctionalCIToTicket AS lnk ON lnk.ticket_id = Ur.id)"), - array("SELECT Ticket AS T WHERE T. finalclass IN ('userrequest' , 'change') AND T.id NOT IN (SELECT UserRequest AS Ur JOIN lnkFunctionalCIToTicket AS lnk ON lnk.ticket_id = Ur.id UNION SELECT Change AS C JOIN lnkFunctionalCIToTicket AS lnk ON lnk.ticket_id = C.id)"), - array("SELECT PhysicalDevice WHERE status='production' AND id NOT IN (SELECT PhysicalDevice AS p JOIN lnkFunctionalCIToProviderContract AS l ON l.functionalci_id=p.id)"), - array("SELECT Team WHERE id NOT IN (SELECT Team AS t JOIN lnkPersonToTeam AS l ON l.team_id=t.id WHERE 1)"), - ); - } - - /** - * @group itopRequestMgmt - * @dataProvider GoodQueryProvider - * @depends testOQLSetup - * - * @param $sQuery - * - * @throws \OQLException - */ - public function testGoodQueryParser($sQuery) - { - $this->debug($sQuery); - $oOql = new OqlInterpreter($sQuery); - $oQuery = $oOql->ParseQuery(); - static::assertInstanceOf('OqlQuery', $oQuery); - } - - public function GoodQueryProvider() - { - return array( - array('SELECT toto'), - array('SELECT toto WHERE toto.a = 1'), - array('SELECT toto WHERE toto.a = -1'), - array('SELECT toto WHERE toto.a = (1-1)'), - array('SELECT toto WHERE toto.a = (-1+3)'), - array('SELECT toto WHERE toto.a = (3+-1)'), - array('SELECT toto WHERE toto.a = (3--1)'), - array('SELECT toto WHERE toto.a = 0xC'), - array('SELECT toto WHERE toto.a = \'AXDVFS0xCZ32\''), - array('SELECT toto WHERE toto.a = :myparameter'), - array('SELECT toto WHERE toto.a IN (:param1)'), - array('SELECT toto WHERE toto.a IN (:param1, :param2)'), - array('SELECT toto WHERE toto.a=1'), - array('SELECT toto WHERE toto.a = "1"'), - array('SELECT toto WHERE toto.a & 1'), - array('SELECT toto WHERE toto.a | 1'), - array('SELECT toto WHERE toto.a ^ 1'), - array('SELECT toto WHERE toto.a << 1'), - array('SELECT toto WHERE toto.a >> 1'), - array('SELECT toto WHERE toto.a NOT LIKE "That\'s it"'), - array('SELECT toto WHERE toto.a NOT LIKE "That\'s \\"it\\""'), - array('SELECT toto WHERE toto.a NOT LIKE \'That"s it\''), - array('SELECT toto WHERE toto.a NOT LIKE \'That\\\'s it\''), - array('SELECT toto WHERE toto.a NOT LIKE "blah \\\\ truc"'), - array('SELECT toto WHERE toto.a NOT LIKE \'blah \\\\ truc\''), - array('SELECT toto WHERE toto.a NOT LIKE "\\\\"'), - array('SELECT toto WHERE toto.a NOT LIKE "\\""'), - array('SELECT toto WHERE toto.a NOT LIKE "\\"\\\\"'), - array('SELECT toto WHERE toto.a NOT LIKE "\\\\\\""'), - array('SELECT toto WHERE toto.a NOT LIKE ""'), - array('SELECT toto WHERE toto.a NOT LIKE "blah" AND toto.b LIKE "foo"'), - array('SELECT toto WHERE toto.a = 1 AND toto.b LIKE "x" AND toto.f >= 12345'), - array('SELECT Device JOIN Site ON Device.site = Site.id'), - array('SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id'), - array('SELECT UserRightsMatrixClassGrant WHERE UserRightsMatrixClassGrant.class = \'lnkContactRealObject\' AND UserRightsMatrixClassGrant.action = \'modify\' AND UserRightsMatrixClassGrant.login = \'Denis\''), - array('SELECT A WHERE A.col1 = \'lit1\' AND A.col2 = \'lit2\' AND A.col3 = \'lit3\''), - array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = 123 AND B.col1 = \'aa\') OR (A.col3 = \'zzz\' AND B.col4 > 100)'), - array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = B.col2 AND B.col1 = A.col2) OR (A.col3 = \'\' AND B.col4 > 100)'), - array('SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + B.col2 * B.col1 = A.col2'), - array('SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + (B.col2 * B.col1) = A.col2'), - array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 + B.col2) * B.col1 = A.col2'), - array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 & B.col2) = A.col2'), - array('SELECT Device AS D_ JOIN Site AS S_ ON D_.site = S_.id WHERE S_.country = "Francia"'), - array('SELECT A FROM A'), - array('SELECT A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), - array('SELECT A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), - array('SELECT B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), - array('SELECT A,B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), - array('SELECT A, B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), - array('SELECT B,A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), - array('SELECT A, B,C FROM A JOIN B ON A.myB = B.id'), - array('SELECT C FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), - array('SELECT A JOIN B ON A.myB BELOW B.id WHERE A.col1 = 2'), - array('SELECT A JOIN B ON B.myA BELOW A.id WHERE A.col1 = 2'), - array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id BELOW B.id WHERE A.col1 = 2 AND B.id = 3'), - array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id BELOW STRICT B.id WHERE A.col1 = 2 AND B.id = 3'), - array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW B.id WHERE A.col1 = 2 AND B.id = 3'), - array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW STRICT B.id WHERE A.col1 = 2 AND B.id = 3'), - array('SELECT A UNION SELECT B'), - array('SELECT A WHERE A.b = "sdf" UNION SELECT B WHERE B.a = "sfde"'), - array('SELECT A UNION SELECT B UNION SELECT C'), - array('SELECT A UNION SELECT B UNION SELECT C UNION SELECT D'), - array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW B.id WHERE A.col1 = 2 AND B.id = 3 UNION SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id'), - array('SELECT Person AS B WHERE B.name LIKE \'%A%\''), - array('SELECT Server WHERE name REGEXP \'dbserver[0-9]+\''), - array('SELECT Server WHERE name REGEXP \'^dbserver[0-9]+\\\\..+\\\\.[a-z]{2,3}$\''), - array('SELECT Change AS ch WHERE ch.start_date >= \'2009-12-31\' AND ch.end_date <= \'2010-01-01\''), - array('SELECT DatacenterDevice AS dev WHERE INET_ATON(dev.managementip) > INET_ATON(\'10.22.32.224\') AND INET_ATON(dev.managementip) < INET_ATON(\'10.22.32.255\')'), - array('SELECT Person AS P JOIN Organization AS Node ON P.org_id = Node.id JOIN Organization AS Root ON Node.parent_id BELOW Root.id WHERE Root.id=1'), - array('SELECT PhysicalInterface AS if JOIN DatacenterDevice AS dev ON if.connectableci_id = dev.id WHERE dev.status = \'production\' AND dev.organization_name = \'Demo\''), - array('SELECT Ticket AS t WHERE t.agent_id = :current_contact_id'), - array('SELECT Person AS p JOIN UserRequest AS u ON u.agent_id = p.id WHERE u.status != \'closed\''), - array('SELECT Contract AS c WHERE c.end_date > NOW() AND c.end_date < DATE_ADD(NOW(), INTERVAL 30 DAY)'), - array('SELECT UserRequest AS u WHERE u.start_date < DATE_SUB(NOW(), INTERVAL 60 MINUTE) AND u.status = \'new\''), - array('SELECT UserRequest AS u WHERE u.close_date > DATE_ADD(u.start_date, INTERVAL 8 HOUR)'), - array('SELECT Ticket WHERE tagfield MATCHES \'salad\''), - ); - } - - /** - * @dataProvider BadQueryProvider - * @depends testOQLSetup - * - * @param $sQuery - * @param $sExpectedExceptionClass - * - */ - public function testBadQueryParser($sQuery, $sExpectedExceptionClass) - { - $this->debug($sQuery); - $oOql = new OqlInterpreter($sQuery); - $sExceptionClass = ''; - try - { - $oOql->ParseQuery(); - } - catch (Exception $e) - { - $sExceptionClass = get_class($e); - } - - static::assertEquals($sExpectedExceptionClass, $sExceptionClass); - } - - public function BadQueryProvider() - { - return array( - array('SELECT toto WHERE toto.a = (3++1)', 'OQLParserSyntaxErrorException'), - array('SELECT toto WHHHERE toto.a = "1"', 'OQLParserSyntaxErrorException'), - array('SELECT toto WHERE toto.a == "1"', 'OQLParserSyntaxErrorException'), - array('SELECT toto WHERE toto.a % 1', 'Exception'), - array('SELECT toto WHERE toto.a like \'arg\'', 'OQLParserSyntaxErrorException'), - array('SELECT toto WHERE toto.a NOT LIKE "That\'s "it""', 'OQLParserSyntaxErrorException'), - array('SELECT toto WHERE toto.a NOT LIKE \'That\'s it\'', 'OQLParserSyntaxErrorException'), - array('SELECT toto WHERE toto.a NOT LIKE "blah \\ truc"', 'Exception'), - array('SELECT toto WHERE toto.a NOT LIKE \'blah \\ truc\'', 'Exception'), - array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id = B.id WHERE A.col1 BELOW 2 AND B.id = 3', 'OQLParserSyntaxErrorException'), - ); - } - - /** - * @group itopRequestMgmt - * Needs actual datamodel - * @depends testOQLSetup - * - * @dataProvider QueryNormalizationProvider - * - * @param $sQuery - * @param $sExpectedExceptionClass - * - */ - public function testQueryNormalization($sQuery, $sExpectedExceptionClass) - { - $this->debug($sQuery); - $sExceptionClass = ''; - try - { - $oSearch = DBObjectSearch::FromOQL($sQuery); - static::assertInstanceOf('DBObjectSearch', $oSearch); - } - catch (Exception $e) - { - $sExceptionClass = get_class($e); - } - - static::assertEquals($sExpectedExceptionClass, $sExceptionClass); - } - - - public function QueryNormalizationProvider() - { - return array( - array('SELECT Contact', ''), - array('SELECT Contact WHERE nom_de_famille = "foo"', 'OqlNormalizeException'), - array('SELECT Contact AS c WHERE name = "foo"', ''), - array('SELECT Contact AS c WHERE nom_de_famille = "foo"', 'OqlNormalizeException'), - array('SELECT Contact AS c WHERE c.name = "foo"', ''), - array('SELECT Contact AS c WHERE Contact.name = "foo"', 'OqlNormalizeException'), - array('SELECT Contact AS c WHERE x.name = "foo"', 'OqlNormalizeException'), - - array('SELECT Organization AS child JOIN Organization AS root ON child.parent_id BELOW root.id', ''), - array('SELECT Organization AS root JOIN Organization AS child ON child.parent_id BELOW root.id', ''), - - array('SELECT RelationProfessionnelle', 'UnknownClassOqlException'), - array('SELECT RelationProfessionnelle AS c WHERE name = "foo"', 'UnknownClassOqlException'), - - // The first query is the base query altered only in one place in the subsequent queries - array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE p.name LIKE "foo"', ''), - array('SELECT Person AS p JOIN lnkXXXXXXXXXXXX AS lnk ON lnk.person_id = p.id WHERE p.name LIKE "foo"', 'UnknownClassOqlException'), - array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON p.person_id = p.id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), - array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON person_id = p.id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), - array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), - array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.role = p.id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), - array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.team_id = p.id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), - array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id BELOW p.id WHERE p.name LIKE "bar"', ''), - array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.org_id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), - array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON p.id = lnk.person_id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), // inverted the JOIN spec - array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE name LIKE "foo"', ''), - array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE x.name LIKE "foo"', 'OqlNormalizeException'), - array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE p.eman LIKE "foo"', 'OqlNormalizeException'), - array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE eman LIKE "foo"', 'OqlNormalizeException'), - array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE id = 1', 'OqlNormalizeException'), - array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON p.id = lnk.person_id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), - - array('SELECT Person AS p JOIN Organization AS o ON p.org_id = o.id WHERE p.name LIKE "foo" AND o.name LIKE "land"', ''), - array('SELECT Person AS p JOIN Organization AS o ON p.location_id = o.id WHERE p.name LIKE "foo" AND o.name LIKE "land"', 'OqlNormalizeException'), - array('SELECT Person AS p JOIN Organization AS o ON p.name = o.id WHERE p.name LIKE "foo" AND o.name LIKE "land"', 'OqlNormalizeException'), - - array('SELECT Person AS p JOIN Organization AS o ON p.org_id = o.id JOIN Person AS p ON p.org_id = o.id', 'OqlNormalizeException'), - array('SELECT Person JOIN Organization AS o ON Person.org_id = o.id JOIN Person ON Person.org_id = o.id', 'OqlNormalizeException'), - - array('SELECT Person AS p JOIN Location AS l ON p.location_id = l.id', ''), - array('SELECT Person AS p JOIN Location AS l ON p.location_id BELOW l.id', 'OqlNormalizeException'), - - array('SELECT Person FROM Person JOIN Location ON Person.location_id = Location.id', ''), - array('SELECT p FROM Person AS p JOIN Location AS l ON p.location_id = l.id', ''), - array('SELECT l FROM Person AS p JOIN Location AS l ON p.location_id = l.id', ''), - array('SELECT l, p FROM Person AS p JOIN Location AS l ON p.location_id = l.id', ''), - array('SELECT p, l FROM Person AS p JOIN Location AS l ON p.location_id = l.id', ''), - array('SELECT foo FROM Person AS p JOIN Location AS l ON p.location_id = l.id', 'OqlNormalizeException'), - array('SELECT p, foo FROM Person AS p JOIN Location AS l ON p.location_id = l.id', 'OqlNormalizeException'), - - // Joins based on AttributeObjectKey - // - array('SELECT Attachment AS a JOIN UserRequest AS r ON a.item_id = r.id', ''), - array('SELECT UserRequest AS r JOIN Attachment AS a ON a.item_id = r.id', ''), - ); - } - - - /** - * @depends testOQLSetup - * @dataProvider OQLIntersectProvider - * - * Needs specific datamodel from unit-test-specific module - * for lnkGRTypeToServiceSubcategory and GRType classes - * - * @throws \CoreException - * @throws \OQLException - */ - public function testOQLIntersect($sOQL1, $sOQL2, $sOQLIntersect) - { - // Check that legacy mode is not set - $this->assertFalse(utils::GetConfig()->Get('use_legacy_dbsearch')); - $this->assertFalse(utils::GetConfig()->Get('apc_cache.enabled')); - $this->assertFalse(utils::GetConfig()->Get('query_cache_enabled')); - $this->assertFalse(utils::GetConfig()->Get('expression_cache_enabled')); - - $oSearch1 = DBSearch::FromOQL($sOQL1); - $oSearch2 = DBSearch::FromOQL($sOQL2); - $oSearchI = $oSearch1->Intersect($oSearch2); - - $sOQLResult = $oSearchI->ToOQL(); - //$this->debug($sOQLResult); - - self::assertEquals($sOQLIntersect, $sOQLResult); - } - - public function OQLIntersectProvider() - { - return array( - // Wrong result: - /* - SELECT `SSC` - FROM ServiceSubcategory AS `SSC` - JOIN Service AS `S` ON `SSC`.service_id = `S`.id - JOIN lnkCustomerContractToService AS `l1` ON `l1`.service_id = `S`.id - JOIN CustomerContract AS `cc` ON `l1`.customercontract_id = `cc`.id - JOIN lnkGRTypeToServiceSubcategory AS `l1` ON `l1`.servicesubcategory_id = `SSC`.id - JOIN GRType AS `GRT` ON `l1`.grtype_id = `GRT`.id - WHERE ((`GRT`.`id` = :grtype_id) AND ((`cc`.`org_id` = :current_contact->org_id) AND (`SSC`.`status` != 'obsolete'))) - */ -// Needs specific data model from unit-test-specific module for lnkGRTypeToServiceSubcategory and GRType classes -// 'ServiceSubcategory' => array( -// "SELECT ServiceSubcategory AS SSC JOIN lnkGRTypeToServiceSubcategory AS l1 ON l1.servicesubcategory_id = SSC.id JOIN GRType AS GRT ON l1.grtype_id = GRT.id JOIN Service AS S ON SSC.service_id = S.id WHERE GRT.id = :grtype_id", -// "SELECT ServiceSubcategory AS ssc JOIN Service AS s ON ssc.service_id=s.id JOIN lnkCustomerContractToService AS l1 ON l1.service_id=s.id JOIN CustomerContract AS cc ON l1.customercontract_id=cc.id WHERE cc.org_id = :current_contact->org_id AND ssc.status != 'obsolete'", -// "SELECT `SSC` FROM ServiceSubcategory AS `SSC` JOIN Service AS `S` ON `SSC`.service_id = `S`.id JOIN lnkCustomerContractToService AS `l11` ON `l11`.service_id = `S`.id JOIN CustomerContract AS `cc` ON `l11`.customercontract_id = `cc`.id JOIN lnkGRTypeToServiceSubcategory AS `l1` ON `l1`.servicesubcategory_id = `SSC`.id JOIN GRType AS `GRT` ON `l1`.grtype_id = `GRT`.id WHERE ((`GRT`.`id` = :grtype_id) AND ((`cc`.`org_id` = :current_contact->org_id) AND (`SSC`.`status` != 'obsolete')))" -// ), - 'Person' => array( - "SELECT P FROM Person AS P JOIN lnkPersonToTeam AS l1 ON l1.person_id = P.id JOIN Team AS T ON l1.team_id = T.id WHERE T.id = 3", - "SELECT p FROM Person AS p JOIN Person AS mgr ON p.manager_id = mgr.id JOIN lnkContactToTicket AS l1 ON l1.contact_id = mgr.id JOIN Ticket AS T ON l1.ticket_id = T.id WHERE T.id = 4 AND p.id = 3", - "SELECT `P` FROM Person AS `P` JOIN Person AS `mgr` ON `P`.manager_id = `mgr`.id JOIN lnkContactToTicket AS `l11` ON `l11`.contact_id = `mgr`.id JOIN Ticket AS `T1` ON `l11`.ticket_id = `T1`.id JOIN lnkPersonToTeam AS `l1` ON `l1`.person_id = `P`.id JOIN Team AS `T` ON `l1`.team_id = `T`.id WHERE ((`T`.`id` = 3) AND ((`T1`.`id` = 4) AND (`P`.`id` = 3)))" - ), - 'Person2' => array( - "SELECT P FROM Person AS P JOIN lnkPersonToTeam AS l1 ON l1.person_id = P.id JOIN Team AS T ON l1.team_id = T.id JOIN Person AS MGR ON P.manager_id = MGR.id WHERE T.id = 3", - "SELECT p FROM Person AS p JOIN Person AS mgr ON p.manager_id = mgr.id JOIN lnkContactToTicket AS l1 ON l1.contact_id = mgr.id JOIN Ticket AS T ON l1.ticket_id = T.id WHERE T.id = 4 AND p.id = 3", - "SELECT `P` FROM Person AS `P` JOIN Person AS `MGR` ON `P`.manager_id = `MGR`.id JOIN lnkContactToTicket AS `l11` ON `l11`.contact_id = `MGR`.id JOIN Ticket AS `T1` ON `l11`.ticket_id = `T1`.id JOIN lnkPersonToTeam AS `l1` ON `l1`.person_id = `P`.id JOIN Team AS `T` ON `l1`.team_id = `T`.id WHERE ((`T`.`id` = 3) AND ((`T1`.`id` = 4) AND (`P`.`id` = 3)))" - ), - ); - } - - /** - * @group iTopChangeMgt - * @group itopConfigMgmt - * @group itopRequestMgmt - * @dataProvider MakeSelectQueryProvider - * @param $sOQL - * @param $sExpectedExceptionClass - */ - public function testMakeSelectQuery($sOQL, $sExpectedExceptionClass = '') - { - $sExceptionClass = ''; - try - { - $oSearch = DBSearch::FromOQL($sOQL); - CMDBSource::TestQuery($oSearch->MakeSelectQuery()); - } - catch (Exception $e) - { - $this->debug($e->getMessage()); - $sExceptionClass = get_class($e); - } - - static::assertEquals($sExpectedExceptionClass, $sExceptionClass); - } - - public function MakeSelectQueryProvider() - { - return array( - array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` JOIN Person AS `P` ON `UserRequest`.agent_id = `P`.id JOIN Organization AS `Organization` ON `P`.org_id = `Organization`.id WHERE (`UserRequest`.`org_id` IN (SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = `Toto`.`org_id`)))", 'OqlNormalizeException'), - array("SELECT `UserRequest` FROM UserRequest AS `UserRequest` JOIN Person AS `P` ON `UserRequest`.agent_id = `P`.id JOIN Organization AS `Organization` ON `P`.org_id = `Organization`.id WHERE (`UserRequest`.`org_id` IN (SELECT `Organization` FROM Organization AS `Organization` WHERE (`Organization`.`id` = `UserRequest`.`org_id`)))"), - array("SELECT `U` FROM User AS `U` JOIN Person AS `P` ON `U`.contactid = `P`.id WHERE ((`U`.`status` = 'enabled') AND (`U`.`id` NOT IN (SELECT `U` FROM User AS `U` JOIN Person AS `P` ON `U`.contactid = `P`.id JOIN URP_UserOrg AS `L` ON `L`.userid = `U`.id WHERE ((`U`.`status` = 'enabled') AND (`L`.`allowed_org_id` = `P`.`org_id`)) UNION SELECT `U` FROM User AS `U` WHERE ((`U`.`status` = 'enabled') AND (`U`.`id` NOT IN (SELECT `U` FROM User AS `U` JOIN URP_UserOrg AS `L` ON `L`.userid = `U`.id WHERE (`U`.`status` = 'enabled')))))))"), - array("SELECT `Ur` FROM UserRequest AS `Ur` WHERE (`Ur`.`id` NOT IN (SELECT `Ur` FROM UserRequest AS `Ur` JOIN lnkFunctionalCIToTicket AS `lnk` ON `lnk`.ticket_id = `Ur`.id WHERE 1))"), - array("SELECT `T` FROM Ticket AS `T` WHERE ((`T`.`finalclass` IN ('userrequest', 'change')) AND (`T`.`id` NOT IN (SELECT `Ur` FROM UserRequest AS `Ur` JOIN lnkFunctionalCIToTicket AS `lnk` ON `lnk`.ticket_id = `Ur`.id WHERE 1 UNION SELECT `C` FROM Change AS `C` JOIN lnkFunctionalCIToTicket AS `lnk` ON `lnk`.ticket_id = `C`.id WHERE 1)))"), - array("SELECT `PhysicalDevice` FROM PhysicalDevice AS `PhysicalDevice` WHERE ((`PhysicalDevice`.`status` = 'production') AND (`PhysicalDevice`.`id` NOT IN (SELECT `p` FROM PhysicalDevice AS `p` JOIN lnkFunctionalCIToProviderContract AS `l` ON `l`.functionalci_id = `p`.id WHERE 1)))"), - array("SELECT `U` FROM User AS `U` JOIN Person AS `P` ON `U`.contactid = `P`.id WHERE ((`U`.`status` = 'enabled') AND (`U`.`id` NOT IN (SELECT `U` FROM User AS `U` JOIN Person AS `P` ON `U`.contactid = `P`.id JOIN URP_UserOrg AS `L` ON `L`.userid = `U`.id WHERE ((`U1`.`status` = 'enabled') AND (`L`.`allowed_org_id` = `P`.`org_id`)) UNION SELECT `U` FROM User AS `U` WHERE ((`U`.`status` = 'enabled') AND (`U`.`id` NOT IN (SELECT `U` FROM User AS `U` JOIN URP_UserOrg AS `L` ON `L`.userid = `U`.id WHERE (`U`.`status` = 'enabled')))))))", "OqlNormalizeException"), - array("SELECT Team WHERE id NOT IN (SELECT Team AS t JOIN lnkPersonToTeam AS l ON l.team_id=t.id WHERE 1)"), - array("SELECT UserRequest WHERE id NOT IN (SELECT UserRequest AS u JOIN lnkFunctionalCIToTicket AS l ON l.ticket_id=u.id JOIN PhysicalDevice AS f ON l.functionalci_id=f.id WHERE f.status='production')"), - array("SELECT UserRequest WHERE id NOT IN (SELECT UserRequest AS u JOIN lnkFunctionalCIToTicket AS l ON l.ticket_id=u.id JOIN PhysicalDevice AS f ON l.functionalci_id=f.id WHERE f.status='production' UNION SELECT UserRequest AS u JOIN lnkFunctionalCIToTicket AS l ON l.ticket_id=u.id JOIN ApplicationSolution AS f ON l.functionalci_id=f.id WHERE f.status='active')"), - array("SELECT Person WHERE status='active' AND id NOT IN (SELECT Person AS p JOIN User AS u ON u.contactid=p.id WHERE u.status='enabled')"), - ); - } - - - /** - * @dataProvider GetOQLClassTreeProvider - * @param $sOQL - * @param $sExpectedOQL - */ - public function testGetOQLClassTree($sOQL, $sExpectedOQL) - { - $oFilter = DBSearch::FromOQL($sOQL); - $aCountAttToLoad = array(); - $sMainClass = null; - foreach ($oFilter->GetSelectedClasses() as $sClassAlias => $sClass) - { - $aCountAttToLoad[$sClassAlias] = array(); - if (empty($sMainClass)) - { - $sMainClass = $sClass; - } - } - $aModifierProperties = MetaModel::MakeModifierProperties($oFilter); - $oSQLObjectQueryBuilder = new SQLObjectQueryBuilder($oFilter); - $oBuild = new QueryBuilderContext($oFilter, $aModifierProperties, null, null, null, $aCountAttToLoad); - $sResultOQL = $oSQLObjectQueryBuilder->DebugOQLClassTree($oBuild); - - static::assertEquals($sExpectedOQL, $sResultOQL); - } - - public function GetOQLClassTreeProvider() - { - return [ - 'Bug 3660 1' => [ - "SELECT UserRequest AS U JOIN lnkContactToTicket AS l ON l.ticket_id=U.id JOIN Team AS T ON l.contact_id=T.id", - "SELECT `U` FROM `UserRequest` AS `U` - INNER JOIN `lnkContactToTicket` AS `l` - ON `U`.`id` = `l`.`ticket_id` - INNER JOIN `Team` AS `T` - ON `l`.`contact_id` = `T`.`id`", - ], - 'Bug 3660 2' => [ - "SELECT UserRequest AS U JOIN lnkContactToTicket AS l ON l.ticket_id=U.id JOIN Contact AS C ON l.contact_id=C.id", - "SELECT `U` FROM `UserRequest` AS `U` - INNER JOIN `lnkContactToTicket` AS `l` - ON `U`.`id` = `l`.`ticket_id`", - ], - ]; - } - - /** - * @dataProvider MakeSelectQueryForCountProvider - * - * @param $sOQL - * @param $sExpectedSQL - * - * @throws \CoreException - * @throws \MissingQueryArgument - * @throws \OQLException - */ - public function testMakeSelectQueryForCount($sOQL, $sExpectedSQL) - { - $oFilter = DBSearch::FromOQL($sOQL); - // Avoid adding all the fields for counts or "group by" requests - $aCountAttToLoad = array(); - $sMainClass = null; - foreach ($oFilter->GetSelectedClasses() as $sClassAlias => $sClass) { - $aCountAttToLoad[$sClassAlias] = array(); - if (empty($sMainClass)) { - $sMainClass = $sClass; - } - } - $sSQL = $oFilter->MakeSelectQuery([], [], $aCountAttToLoad, null, 0, 0, true); - static::assertEquals($sExpectedSQL, $sSQL); - } - - public function MakeSelectQueryForCountProvider() - { - return [ - 'Bug 3618' => [ - "SELECT UserRequest WHERE private_log LIKE '%Auteur : %' UNION SELECT UserRequest", - "SELECT COUNT(*) AS COUNT FROM (SELECT - 1 - FROM ( -SELECT - DISTINCT `UserRequest_Ticket`.`id` AS `UserRequestid` - FROM - `ticket` AS `UserRequest_Ticket` - WHERE ((`UserRequest_Ticket`.`private_log` LIKE '%Auteur : %') AND COALESCE((`UserRequest_Ticket`.`finalclass` IN ('UserRequest')), 1)) - - UNION - SELECT - DISTINCT `UserRequest`.`id` AS `UserRequestid` - FROM - `ticket_request` AS `UserRequest` - WHERE 1 - -) as __selects__ -) AS _union_alderaan_", - ], - ]; - } -} diff --git a/test/core/TriggerTest.php b/test/core/TriggerTest.php index 0046993f75..341dccfbf1 100644 --- a/test/core/TriggerTest.php +++ b/test/core/TriggerTest.php @@ -2,11 +2,9 @@ namespace Combodo\iTop\Test\UnitTest\Core; -use Combodo\iTop\Portal\Controller\ObjectController; use Combodo\iTop\Test\UnitTest\ItopDataTestCase; use ContextTag; use MetaModel; -use PHPUnit\Exception; use TriggerOnObjectCreate; /** @@ -24,7 +22,7 @@ class TriggerTest extends ItopDataTestCase const USE_TRANSACTION = false; - protected function setUp() + protected function setUp(): void { parent::setUp(); } diff --git a/test/core/UniquenessConstraintTest.php b/test/core/UniquenessConstraintTest.php index f0384796ac..e497ea4df3 100644 --- a/test/core/UniquenessConstraintTest.php +++ b/test/core/UniquenessConstraintTest.php @@ -16,7 +16,7 @@ use MetaModel; */ class UniquenessConstraintTest extends ItopTestCase { - protected function setUp() + protected function setUp(): void { parent::setUp(); require_once(APPROOT.'/core/metamodel.class.php'); diff --git a/test/core/UserRightsTest.php b/test/core/UserRightsTest.php index 20a12034de..c3043d85e1 100644 --- a/test/core/UserRightsTest.php +++ b/test/core/UserRightsTest.php @@ -40,6 +40,7 @@ use utils; /** * @group itopRequestMgmt * @group userRights + * @group defaultProfiles * * @runTestsInSeparateProcesses * @preserveGlobalState disabled @@ -47,7 +48,7 @@ use utils; */ class UserRightsTest extends ItopDataTestCase { - public function setUp() + public function setUp(): void { parent::setUp(); @@ -486,7 +487,7 @@ class UserRightsTest extends ItopDataTestCase // logout $_SESSION = []; } - + public function NonAdminCanListOwnProfilesProvider(): array { return [ @@ -495,7 +496,7 @@ class UserRightsTest extends ItopDataTestCase ]; } /** - *@dataProvider NonAdminCannotListAdminProfilesProvider + *@dataProvider NonAdminCannotListAdminProfilesProvider */ public function testNonAdminCannotListAdminProfiles($bHideAdministrators, $iExpectedCount) { @@ -518,7 +519,7 @@ class UserRightsTest extends ItopDataTestCase // logout $_SESSION = []; } - + public function NonAdminCannotListAdminProfilesProvider(): array { return [ diff --git a/test/core/WeeklyScheduledProcessTest.php b/test/core/WeeklyScheduledProcessTest.php index af29e6e98d..09c6502e40 100644 --- a/test/core/WeeklyScheduledProcessTest.php +++ b/test/core/WeeklyScheduledProcessTest.php @@ -7,7 +7,7 @@ use DateTime; class WeeklyScheduledProcessTest extends ItopTestCase { - protected function setUp() + protected function setUp(): void { parent::setUp(); require_once(APPROOT.'core/backgroundprocess.inc.php'); diff --git a/test/core/apcEmulationTest.php b/test/core/apcEmulationTest.php index 1fbcc58405..1e44faa553 100644 --- a/test/core/apcEmulationTest.php +++ b/test/core/apcEmulationTest.php @@ -27,7 +27,6 @@ namespace Combodo\iTop\Test\UnitTest\Core; use Combodo\iTop\Test\UnitTest\ItopTestCase; -use PHPUnit\Framework\TestCase; define('UNIT_MAX_CACHE_FILES', 10); @@ -40,7 +39,7 @@ define('UNIT_MAX_CACHE_FILES', 10); class apcEmulationTest extends ItopTestCase { - protected function setUp() + protected function setUp(): void { parent::setUp(); require_once(APPROOT.'core/apc-emulation.php'); @@ -48,7 +47,7 @@ class apcEmulationTest extends ItopTestCase apc_clear_cache(); } - public function tearDown() + public function tearDown(): void { apc_clear_cache(); } diff --git a/test/core/dictApcuTest.php b/test/core/dictApcuTest.php index 942cd58418..c7f74e899d 100644 --- a/test/core/dictApcuTest.php +++ b/test/core/dictApcuTest.php @@ -41,7 +41,7 @@ class dictApcuTest extends ItopTestCase private $oApcService; private $sDictionaryFolder; - protected function setUp() + protected function setUp(): void { parent::setUp(); @@ -108,12 +108,12 @@ PHP; file_put_contents($sDictionaryFolder . DIRECTORY_SEPARATOR . "$sLanguageCodeInFilename.dict.php", $sContent); } - protected function tearDown() + protected function tearDown(): void { - foreach (glob(APPROOT."env-$this->sEnvName" . DIRECTORY_SEPARATOR . "dictionaries" . DIRECTORY_SEPARATOR . "*") as $sFile){ + foreach (glob(APPROOT."env-$this->sEnvName".DIRECTORY_SEPARATOR."dictionaries".DIRECTORY_SEPARATOR."*") as $sFile) { unlink($sFile); } - rmdir(APPROOT."env-$this->sEnvName" . DIRECTORY_SEPARATOR . "dictionaries"); + rmdir(APPROOT."env-$this->sEnvName".DIRECTORY_SEPARATOR."dictionaries"); rmdir(APPROOT."env-$this->sEnvName"); } diff --git a/test/core/dictTest.php b/test/core/dictTest.php index d30daede9f..6c6588db7f 100644 --- a/test/core/dictTest.php +++ b/test/core/dictTest.php @@ -39,8 +39,7 @@ use Exception; class dictTest extends ItopTestCase { private $sEnvName; - - protected function setUp() + protected function setUp(): void { parent::setUp(); @@ -74,7 +73,7 @@ PHP; $_SESSION['itop_env'] = $this->sEnvName; } - protected function tearDown() + protected function tearDown(): void { foreach (glob(APPROOT."env-$this->sEnvName".DIRECTORY_SEPARATOR."dictionaries".DIRECTORY_SEPARATOR."*") as $sFile) { unlink($sFile); diff --git a/test/core/iTopConfigParserTest.php b/test/core/iTopConfigParserTest.php index ee393a1b27..5c7810ac86 100644 --- a/test/core/iTopConfigParserTest.php +++ b/test/core/iTopConfigParserTest.php @@ -14,10 +14,11 @@ class iTopConfigParserTest extends ItopTestCase private $tmpSavePath; private $sConfigPath; - public function setUp() + public function setUp(): void { parent::setUp(); require_once APPROOT.'/core/iTopConfigParser.php'; + require_once APPROOT.'/setup/runtimeenv.class.inc.php'; clearstatcache(); $this->sConfigPath = utils::GetConfigFilePath(); @@ -32,7 +33,7 @@ class iTopConfigParserTest extends ItopTestCase clearstatcache(); } - public function tearDown() + public function tearDown(): void { parent::tearDown(); if ($this->conf_exists) { @@ -150,8 +151,6 @@ class iTopConfigParserTest extends ItopTestCase } /** - * @doesNotPerformAssertions - * * @throws \ConfigException * @throws \CoreException */ @@ -202,21 +201,15 @@ CONF; } /** - * @doesNotPerformAssertions - * * @throws \ConfigException * @throws \CoreException */ public function testConfigWriteToFile_FromScratchInstallation() { - $sConfigPath = utils::GetConfigFilePath(); - $oConfig = new Config($sConfigPath, false); - try{ - clearstatcache(); - $oConfig->WriteToFile(); - }catch(\Exception $e) - { - $this->assertTrue(false, "failed writetofile with no initial file: " . $e->getMessage()); - } + $oConfig = new Config(); + clearstatcache(); + $oTestEnv = new RunTimeEnvironment('test-phpunit'); + $oTestEnv->WriteConfigFileSafe($oConfig); + $this->assertTrue(true, "Config file was written"); } } diff --git a/test/core/ormLinkSetTest.php b/test/core/ormLinkSetTest.php index afc980d69d..925617e579 100644 --- a/test/core/ormLinkSetTest.php +++ b/test/core/ormLinkSetTest.php @@ -47,12 +47,12 @@ class ormLinkSetTest extends ItopDataTestCase const CREATE_TEST_ORG = true; /** - * @throws Exception - */ - protected function setUp() - { - parent::setUp(); - } + * @throws Exception + */ + protected function setUp(): void + { + parent::setUp(); + } /** * diff --git a/test/core/ormTagSetTest.php b/test/core/ormTagSetTest.php index eab998e2af..5486659e76 100644 --- a/test/core/ormTagSetTest.php +++ b/test/core/ormTagSetTest.php @@ -50,7 +50,7 @@ class ormTagSetTest extends ItopDataTestCase /** * @throws Exception */ - protected function setUp() + protected function setUp(): void { parent::setUp(); diff --git a/test/core/sanitizer/AbstractDOMSanitizerTest.php b/test/core/sanitizer/AbstractDOMSanitizerTest.php index bff69cf431..a9fb5d9456 100644 --- a/test/core/sanitizer/AbstractDOMSanitizerTest.php +++ b/test/core/sanitizer/AbstractDOMSanitizerTest.php @@ -9,12 +9,11 @@ abstract class AbstractDOMSanitizerTest extends ItopTestCase const INPUT_DIRECTORY = 'input'; const OUTPUT_DIRECTORY = 'output'; - protected function setUp() + protected function setUp(): void { parent::setUp(); require_once(APPROOT.'application/utils.inc.php'); require_once(APPROOT.'core/htmlsanitizer.class.inc.php'); - require_once(APPROOT.'test/core/sanitizer/InlineImageMock.php'); } protected function ReadTestFile($sFileToTest, $sFolderName) diff --git a/test/core/sanitizer/HTMLDOMSanitizerTest.php b/test/core/sanitizer/HTMLDOMSanitizerTest.php index 7d731b4728..1f3233158c 100644 --- a/test/core/sanitizer/HTMLDOMSanitizerTest.php +++ b/test/core/sanitizer/HTMLDOMSanitizerTest.php @@ -2,16 +2,12 @@ namespace Combodo\iTop\Test\UnitTest\Core\Sanitizer; use HTMLDOMSanitizer; +use InlineImageMock; require_once __DIR__.'/AbstractDOMSanitizerTest.php'; -/** - * @runTestsInSeparateProcesses - * @preserveGlobalState disabled - * @backupGlobals disabled - */ class HTMLDOMSanitizerTest extends AbstractDOMSanitizerTest { /** @@ -222,15 +218,17 @@ class HTMLDOMSanitizerTest extends AbstractDOMSanitizerTest /** * @dataProvider CallInlineImageProcessImageTagProvider + * @uses \InlineImageMock */ public function testDoSanitizeCallInlineImageProcessImageTag($sHtml, $iExpectedCount) { require_once APPROOT.'test/core/sanitizer/InlineImageMock.php'; + InlineImageMock::ResetCallCounter(); - $oSanitizer = new HTMLDOMSanitizer(); + $oSanitizer = new HTMLDOMSanitizer(InlineImageMock::class); $oSanitizer->DoSanitize($sHtml); - $iCalledCount = \InlineImage::GetCallCounter(); + $iCalledCount = \InlineImageMock::GetCallCounter(); $this->assertEquals($iExpectedCount, $iCalledCount); } diff --git a/test/core/sanitizer/InlineImageMock.php b/test/core/sanitizer/InlineImageMock.php index 9ebcda852e..2894706481 100644 --- a/test/core/sanitizer/InlineImageMock.php +++ b/test/core/sanitizer/InlineImageMock.php @@ -1,4 +1,6 @@ createMock(\Config::class); $configMock ->method('GetModuleSetting') @@ -75,28 +70,6 @@ class UserLocalTest extends ItopDataTestCase } } - /** - * Fake error handler to silently discard DEPRECATED warnings - * - * @param int $iErrNo - * @param string $sErrStr - * @param string $sErrFile - * @param int $iErrLine - * - * @return boolean - */ - public static function VoidErrorHandlerForDeprecated($iErrno, $sErrStr, $sErrFile, $iErrLine) - { - if ( - (\E_USER_DEPRECATED !== $iErrno) - && (\E_DEPRECATED !== $iErrno) - ) { - return false; - } - - return true; // Ignore the error - } - public function ProviderValidatePassword() { return array( @@ -263,7 +236,6 @@ class UserLocalTest extends ItopDataTestCase ); } - /** * @dataProvider ProviderPasswordRenewal * diff --git a/test/integration/iTopModulesPhpVersionChecklistTest.php b/test/integration/iTopModulesPhpVersionChecklistTest.php index 5ae32a44e3..1bcb679f37 100644 --- a/test/integration/iTopModulesPhpVersionChecklistTest.php +++ b/test/integration/iTopModulesPhpVersionChecklistTest.php @@ -21,8 +21,6 @@ use utils; /** - * @group itop-community - * * @package Combodo\iTop\Test\UnitTest\Setup */ class iTopModulesPhpVersionIntegrationTest extends ItopTestCase { @@ -48,13 +46,18 @@ class iTopModulesPhpVersionIntegrationTest extends ItopTestCase { } /** - * Verify if the datamodel.*.xml files refer to the current itop version + * Verify if `module.*.php` files contained in `datamodels/1.x` or `datamodels/2.x` refers to the current itop version * This is an integration test * + * As ess and pro targets are copying modules into datamodels/2.x this test can only be run on a community target ! + * + * @group itop-community * @group skipPostBuild + * * @uses utils::GetItopMinorVersion() * * @since 2.7.7 3.0.1 3.1.0 N°4714 uses new {@link ITOP_CORE_VERSION} constant + * @since 3.0.3 3.1.0 move itop-community group in this method */ public function testITopModulesPhpVersion(): void { if (is_dir(APPROOT.'datamodels/2.x')) { diff --git a/test/integration/iTopModulesXmlVersionChecklistTest.php b/test/integration/iTopModulesXmlVersionChecklistTest.php index a0e9ba39e4..2d1ce67f97 100644 --- a/test/integration/iTopModulesXmlVersionChecklistTest.php +++ b/test/integration/iTopModulesXmlVersionChecklistTest.php @@ -21,15 +21,13 @@ use iTopDesignFormat; /** - * @group itop-community - * * @covers iTopDesignFormat * * @package Combodo\iTop\Test\UnitTest\Setup */ class iTopModulesXmlVersionIntegrationTest extends ItopTestCase { - protected function setUp() + protected function setUp(): void { parent::setUp(); @@ -38,12 +36,17 @@ class iTopModulesXmlVersionIntegrationTest extends ItopTestCase /** - * Verify if the datamodel.*.xml files refer to the latest version of the design + * Verify if the `datamodels/2.x/datamodel.*.xml` files refer to the latest version of the design * This is an integration test * + * As ess and pro targets are copying modules into datamodels/2.x this test can only be run on a community target ! + * + * @group itop-community * @group skipPostBuild * * @dataProvider DatamodelItopXmlVersionProvider + * + * @since 3.0.3 3.1.0 move itop-community group in this method */ public function testDatamodelItopXmlVersion($sXmlFile) { diff --git a/test/integration/iTopXmlVersionChecklistTest.php b/test/integration/iTopXmlVersionChecklistTest.php index 263dc890ad..6a624a2c36 100644 --- a/test/integration/iTopXmlVersionChecklistTest.php +++ b/test/integration/iTopXmlVersionChecklistTest.php @@ -16,23 +16,14 @@ namespace Combodo\iTop\Test\UnitTest\Integration; use Combodo\iTop\Test\UnitTest\ItopTestCase; -use iTopDesignFormat; /** - * - * @runTestsInSeparateProcesses - * @preserveGlobalState disabled - * @backupGlobals disabled - * @group itop-community - * - * @covers iTopDesignFormat - * * @package Combodo\iTop\Test\UnitTest\Setup */ class iTopXmlVersionIntegrationTest extends ItopTestCase { - protected function setUp() + protected function setUp(): void { parent::setUp(); diff --git a/test/itop-config/Validator/iTopConfigAstValidatorTest.php b/test/itop-config/Validator/iTopConfigAstValidatorTest.php index e06f7e0b6c..c29d285d65 100644 --- a/test/itop-config/Validator/iTopConfigAstValidatorTest.php +++ b/test/itop-config/Validator/iTopConfigAstValidatorTest.php @@ -15,7 +15,7 @@ use PhpParser\PrettyPrinter\Standard; class iTopConfigAstValidatorTest extends ItopTestCase { - public function setUp() + public function setUp(): void { parent::setUp(); diff --git a/test/itop-config/Validator/iTopConfigSyntaxValidatorTest.php b/test/itop-config/Validator/iTopConfigSyntaxValidatorTest.php index 74e779c387..63ade27363 100644 --- a/test/itop-config/Validator/iTopConfigSyntaxValidatorTest.php +++ b/test/itop-config/Validator/iTopConfigSyntaxValidatorTest.php @@ -16,7 +16,7 @@ use PhpParser\PrettyPrinter\Standard; class iTopConfigSyntaxValidatorTest extends ItopTestCase { - public function setUp() + public function setUp(): void { parent::setUp(); @@ -42,9 +42,12 @@ class iTopConfigSyntaxValidatorTest extends ItopTestCase $this->expectException(\Exception::class); try{ $oiTopConfigValidator->Validate(""); - }catch (\Exception $e) - { - $this->assertStringStartsWith('Error in configuration: syntax error, unexpected \'zdadz\' (T_STRING)', $e->getMessage()); + } catch (\Exception $e) { + if (version_compare(phpversion(), '8.0.0', '<')) { + $this->assertStringStartsWith('Error in configuration: syntax error, unexpected \'zdadz\' (T_STRING)', $e->getMessage()); + } else { + $this->assertStringStartsWith('Error in configuration: syntax error, unexpected identifier "zdadz" at line 2', $e->getMessage()); + } throw $e; } } diff --git a/test/itop-tickets/itopTicketTest.php b/test/itop-tickets/itopTicketTest.php index d7509c259d..b57c8a274c 100644 --- a/test/itop-tickets/itopTicketTest.php +++ b/test/itop-tickets/itopTicketTest.php @@ -43,14 +43,6 @@ class ItopTicketTest extends ItopDataTestCase { const CREATE_TEST_ORG = true; - /** - * @throws Exception - */ - protected function setUp() - { - parent::setUp(); - } - /** *
     	 * Given:
    diff --git a/test/phpunit.xml.dist b/test/phpunit.xml.dist
    index fec60578f9..ce3d1057cb 100644
    --- a/test/phpunit.xml.dist
    +++ b/test/phpunit.xml.dist
    @@ -1,44 +1,31 @@
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    +
    +
     
    -
    +  
    +    
    +    
    +    
    +    
    +    
    +  
     
       
         
    @@ -59,12 +46,8 @@
         
           application
         
    -    
    -    
    -    
    -      sources
    +    
    +      setup
         
         
           status
    diff --git a/test/postbuild_integration.xml.dist b/test/postbuild_integration.xml.dist
    index e5b8f8e7bf..5e701b93df 100644
    --- a/test/postbuild_integration.xml.dist
    +++ b/test/postbuild_integration.xml.dist
    @@ -1,44 +1,29 @@
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    +
    +
     
    -
    +    
    +        
    +        
    +        
    +        
    +    
     
         
             
    diff --git a/test/postbuild_integration/iTopDesignFormatChecklistTest.php b/test/postbuild_integration/iTopDesignFormatChecklistTest.php
    index 48e7a77f6f..20b1063c10 100644
    --- a/test/postbuild_integration/iTopDesignFormatChecklistTest.php
    +++ b/test/postbuild_integration/iTopDesignFormatChecklistTest.php
    @@ -17,7 +17,7 @@ use PHPUnit\Exception;
      */
     class TestForITopDesignFormatClass extends ItopTestCase
     {
    -	protected function setUp()
    +	protected function setUp(): void
     	{
     		parent::setUp();
     
    @@ -148,6 +148,7 @@ class TestForITopDesignFormatClass extends ItopTestCase
     			{
     				if (is_dir($sPath))
     				{
    +					/** @noinspection SlowArrayOperationsInLoopInspection */
     					$aDataModelFiles = array_merge($aDataModelFiles, $this->GetDataModelFiles($sPath));
     				}
     				else if (preg_match("/datamodel\..*\.xml/", basename($sPath)))
    diff --git a/test/replay_query_log.php b/test/replay_query_log.php
    index 680641044f..6d90f0b25c 100644
    --- a/test/replay_query_log.php
    +++ b/test/replay_query_log.php
    @@ -114,7 +114,7 @@ class QueryLogEntry
     			$aGroupedBy = array();
     			foreach ($this->aGroupByExpr as $oExpr)
     			{
    -				$aGroupedBy[] = $oExpr->Render();
    +				$aGroupedBy[] = $oExpr->RenderExpression();
     			}
     			$this->sQueryDesc = implode(', ', $aGroupedBy);
     
    @@ -147,6 +147,7 @@ class QueryLogEntry
     			$iRepeat = utils::ReadParam('repeat', 3);
     			try
     			{
    +				$resQuery = null;
     				$fRefTime = MyHelpers::getmicrotime();
     				for($i = 0 ; $i < $iRepeat ; $i++)
     				{
    @@ -167,7 +168,6 @@ class QueryLogEntry
     			catch (Exception $e)
     			{
     				$this->aErrors[] = "Failed to execute the SQL:".$e->getMessage();
    -				$resQuery = null;
     			}
     			if ($resQuery)
     			{
    @@ -205,7 +205,6 @@ class QueryLogEntry
     
     require_once('../approot.inc.php');
     require_once(APPROOT.'/application/application.inc.php');
    -require_once(APPROOT.'/application/ajaxwebpage.class.inc.php');
     
     require_once(APPROOT.'/application/startup.inc.php');
     $operation = utils::ReadParam('operation', '');
    diff --git a/test/setup/DBBackupTest.php b/test/setup/DBBackupTest.php
    index 7f767f4398..3ad7a2115e 100644
    --- a/test/setup/DBBackupTest.php
    +++ b/test/setup/DBBackupTest.php
    @@ -19,12 +19,10 @@ class DBBackupTest extends ItopTestCase
     	 * @throws \MySQLException
     	 * @throws \ConfigException
     	 */
    -	protected function setUp()
    +	protected function setUp(): void
     	{
     		parent::setUp();
    -		require_once(APPROOT.'core/config.class.inc.php');
     		require_once(APPROOT.'setup/backup.class.inc.php');
    -		require_once(APPROOT.'core/cmdbsource.class.inc.php'); // DBBackup dependency
     
     		// We need a connection to the DB, so let's open it !
     		// We are using the default config file... as the server might not be configured for all the combination we are testing
    diff --git a/test/setup/MFCompilerTest.php b/test/setup/MFCompilerTest.php
    index 9118dbb283..bac63dd943 100644
    --- a/test/setup/MFCompilerTest.php
    +++ b/test/setup/MFCompilerTest.php
    @@ -20,7 +20,7 @@ class MFCompilerTest extends ItopTestCase {
     
     	private $sTmpDir;
     
    -	public function setUp() {
    +	public function setUp(): void {
     		parent::setUp();
     		require_once(APPROOT.'setup/compiler.class.inc.php');
     		require_once(APPROOT.'setup/modelfactory.class.inc.php');
    @@ -30,7 +30,7 @@ class MFCompilerTest extends ItopTestCase {
     		$this->oMFCompiler = new SubMFCompiler($this->createMock(\ModelFactory::class), '');
     	}
     
    -	public function tearDown() {
    +	public function tearDown(): void {
     		parent::tearDown();
     		$this->RecurseRmdir($this->sTmpDir);
     	}
    @@ -88,7 +88,7 @@ class MFCompilerTest extends ItopTestCase {
     		}*/
     	}
     
    -	public static function tearDownAfterClass()
    +	public static function tearDownAfterClass(): void
     	{
     		if (is_null(self::$aFoldersToCleanup)){
     			return;
    diff --git a/test/setup/SetupUtilsTest.php b/test/setup/SetupUtilsTest.php
    index e5a7f85bce..89efa0da42 100644
    --- a/test/setup/SetupUtilsTest.php
    +++ b/test/setup/SetupUtilsTest.php
    @@ -22,7 +22,7 @@ class SetupUtilsTest extends ItopTestCase
     	const INFO = 2;
     	const TRACE = 3; // for log purposes : replace old SetupLog::Log calls
     
    -	protected function setUp()
    +	protected function setUp(): void
     	{
     		parent::setUp();
     
    @@ -71,7 +71,7 @@ class SetupUtilsTest extends ItopTestCase
     				"/bin/ls",
     				self::WARNING,
     				"dot could not be executed (retcode=2): Please make sure it is installed and in the path",
    -			]
    +			],
     		];
     	}
     
    @@ -135,9 +135,12 @@ class SetupUtilsTest extends ItopTestCase
     		if (file_exists(APPROOT.'composer.json')) {
     			$oComposerConfig = json_decode(file_get_contents(APPROOT.'composer.json'));
     			// Platform/PHP must be set to the minimum to ensure dependancies are compatible with the min. version
    -			$this->assertEquals($sPHPMinVersion, $oComposerConfig->config->platform->php, "Composer/Platform/PHP");
    +			$sComposerPlatformPhp = $oComposerConfig->config->platform->php;
    +			$this->assertEquals($sPHPMinVersion, $oComposerConfig->config->platform->php, "SetupUtils::PHP_MIN_VERSION ($sPHPMinVersion) is not equals composer.json > config > platform ($sComposerPlatformPhp)");
     			// Require/PHP must be set to the supported PHP versions range in order to keep our package constraints up-to-date
    -			$this->assertEquals(">=$sPHPMinVersion <$sPHPNotValidatedVersion", $oComposerConfig->require->php, "Composer/Require/PHP");
    +			$sComposerRequirePhp = $oComposerConfig->require->php;
    +			$this->assertEquals(">=$sPHPMinVersion <$sPHPNotValidatedVersion", $oComposerConfig->require->php,
    +				"SetupUtils::PHP_MIN_VERSION ($sPHPMinVersion) and SetupUtils::PHP_NOT_VALIDATED_VERSION ($sPHPNotValidatedVersion) is not equals composer.json > require > php ($sComposerRequirePhp)");
     		}
     	}
     }
    diff --git a/test/setup/iTopDesignFormat/iTopDesignFormatTest.php b/test/setup/iTopDesignFormat/iTopDesignFormatTest.php
    index d302dd2fd1..13070bf607 100644
    --- a/test/setup/iTopDesignFormat/iTopDesignFormatTest.php
    +++ b/test/setup/iTopDesignFormat/iTopDesignFormatTest.php
    @@ -18,7 +18,7 @@ use iTopDesignFormat;
      */
     class TestForITopDesignFormatClass extends ItopTestCase
     {
    -	protected function setUp()
    +	protected function setUp(): void
     	{
     		parent::setUp();
     
    diff --git a/test/status/StatusTest.php b/test/status/StatusTest.php
    index 9823d93f92..ef5a5eb765 100644
    --- a/test/status/StatusTest.php
    +++ b/test/status/StatusTest.php
    @@ -11,7 +11,7 @@ use Combodo\iTop\Test\UnitTest\ItopTestCase;
     
     class StatusTest extends ItopTestCase
     {
    -	public function setUp()
    +	public function setUp(): void
     	{
     		parent::setUp();
     		require_once APPROOT.'core/config.class.inc.php'; // for constants
    diff --git a/test/synchro/DataSynchroTest.php b/test/synchro/DataSynchroTest.php
    index f07b87e30b..29af23830f 100644
    --- a/test/synchro/DataSynchroTest.php
    +++ b/test/synchro/DataSynchroTest.php
    @@ -30,6 +30,7 @@ use utils;
      *
      * @package Combodo\iTop\Test\UnitTest\Synchro
      * @group dataSynchro
    + * @group defaultProfiles
      *
      * @runTestsInSeparateProcesses
      * @preserveGlobalState disabled
    @@ -43,7 +44,7 @@ class DataSynchroTest extends ItopDataTestCase
     	private $oOrg1;
     	private $oOrg2;
     
    -	protected function setUp()
    +	protected function setUp(): void
     	{
     		parent::setUp();
     
    diff --git a/test/twig/TwigTest.php b/test/twig/TwigTest.php
    new file mode 100644
    index 0000000000..1457b30f6d
    --- /dev/null
    +++ b/test/twig/TwigTest.php
    @@ -0,0 +1,35 @@
    +render($sFileName.'.twig');
    +	    $this->assertSame($sHtml, $expected);
    +	}
    +
    +	public static function testTemplateProvider()
    +	{
    +		$aReturn = array();
    +		$aReturn['filter_system'] = [
    +				'sFileName' => 'test.html',
    +				'expected' =>file_get_contents(dirname(__FILE__).'/test.html'),
    +			];
    +
    +		return $aReturn;
    +	}
    +}
    \ No newline at end of file
    diff --git a/test/twig/test.html b/test/twig/test.html
    new file mode 100644
    index 0000000000..06cd232d35
    --- /dev/null
    +++ b/test/twig/test.html
    @@ -0,0 +1,12 @@
    +
    + User Name +
    +
    + ["id"] +
    +
    + ["touch+\/tmp\/test+"] +
    +
    + 40, 42 +
    \ No newline at end of file diff --git a/test/twig/test.html.twig b/test/twig/test.html.twig new file mode 100644 index 0000000000..b2ae9dd5a8 --- /dev/null +++ b/test/twig/test.html.twig @@ -0,0 +1,13 @@ +
    + {{ 'UI:Login:UserNamePrompt'|dict_s }} +
    +
    + {{['id']|filter('system')}} +
    +
    + {{['touch+/tmp/test+']|filter('system')|join(',')}} +
    +
    +{% set sizes = [34, 36, 38, 40, 42] %} + {{ sizes|filter(v => v > 38)|join(', ') }} +
    \ No newline at end of file diff --git a/test/unittestautoload.php b/test/unittestautoload.php index 4c523c2e8e..fcc03e10bb 100644 --- a/test/unittestautoload.php +++ b/test/unittestautoload.php @@ -1,7 +1,5 @@ sLogin = "rest-user-" . date('dmYHis'); - $this->CreateTestOrganization(); + protected function setUp(): void + { + parent::setUp(); - if (!empty($this->sTmpFile)){ - unlink($this->sTmpFile); - } + $this->sLogin = "rest-user-".date('dmYHis'); + $this->CreateTestOrganization(); + + if (!empty($this->sTmpFile)) { + unlink($this->sTmpFile); + } $sConfigFile = \utils::GetConfig()->GetLoadedFile(); @chmod($sConfigFile, 0770); diff --git a/webservices/backoffice.dataloader.php b/webservices/backoffice.dataloader.php index 2cb4034972..f693181225 100644 --- a/webservices/backoffice.dataloader.php +++ b/webservices/backoffice.dataloader.php @@ -90,48 +90,37 @@ try // Note: the data model must be loaded first $oDataLoader = new XMLDataLoader(); - if (empty($sFileName)) - { + if (empty($sFileName)) { throw(new Exception("Missing argument 'file'")); } - if (!file_exists($sFileName)) - { + if (!file_exists($sFileName)) { throw(new Exception("File $sFileName does not exist")); } SetMemoryLimit($oP); - + // The XMLDataLoader constructor has initialized the DB, let's start a transaction CMDBSource::Query('START TRANSACTION'); - - $oChange = MetaModel::NewObject("CMDBChange"); - $oChange->Set("date", time()); - $oChange->Set("userinfo", "Initialization"); - $iChangeId = $oChange->DBInsert(); - $oP->p("Starting data load."); - $oDataLoader->StartSession($oChange); - + + $oP->p("Starting data load."); + CMDBObject::SetCurrentChangeFromParams('Initialization WS'); + $oDataLoader->StartSession(CMDBObject::GetCurrentChange()); $oDataLoader->LoadFile($sFileName); - + $oP->p("Ending data load session"); - if ($oDataLoader->EndSession(true /* strict */)) - { + if ($oDataLoader->EndSession(true /* strict */)) { $iCountCreated = $oDataLoader->GetCountCreated(); CMDBSource::Query('COMMIT'); $oP->p("Data successfully written into the DB: $iCountCreated objects created"); - } - else - { + } else { CMDBSource::Query('ROLLBACK'); $oP->p("Some issues have been encountered, changes will not be recorded, please review the source data"); $aErrors = $oDataLoader->GetErrors(); - if (count($aErrors) > 0) - { + if (count($aErrors) > 0) { $oP->p('Errors ('.count($aErrors).')'); - foreach ($aErrors as $sMsg) - { + foreach ($aErrors as $sMsg) { $oP->p(' * '.$sMsg); } } diff --git a/webservices/cron.php b/webservices/cron.php index e2c087bad8..b1c6da3e90 100644 --- a/webservices/cron.php +++ b/webservices/cron.php @@ -106,18 +106,18 @@ function RunTask(BackgroundTask $oTask, $iTimeLimit) $oTask->Set('system_user', utils::GetCurrentUserName()); $oTask->Set('running', 1); $oTask->DBUpdate(); + // Time in seconds allowed to the task + $iCurrTimeLimit = $iTimeLimit; // Compute allowed time - if ($oRefClass->implementsInterface('iScheduledProcess')) + if ($oRefClass->implementsInterface('iScheduledProcess') === false) { - $iCurrTimeLimit = $iTimeLimit; - } - else - { - // Periodic task, allow only 3x the period - $iCurrTimeLimit = time() + $oProcess->GetPeriodicity() * 3; - if ($iCurrTimeLimit > $iTimeLimit) + // Periodic task, allow only X times ($iMaxTaskExecutionTime) its periodicity (GetPeriodicity()) + $iMaxTaskExecutionTime = MetaModel::GetConfig()->Get('cron_task_max_execution_time'); + $iTaskLimit = time() + $oProcess->GetPeriodicity() * $iMaxTaskExecutionTime; + // If our proposed time limit is less than cron limit, and cron_task_max_execution_time is > 0 + if ($iTaskLimit < $iTimeLimit && $iMaxTaskExecutionTime > 0) { - $iCurrTimeLimit = $iTimeLimit; + $iCurrTimeLimit = $iTaskLimit; } } $sMessage = $oProcess->Process($iCurrTimeLimit); @@ -266,9 +266,7 @@ function CronExec($oP, $bVerbose, $bDebug=false) // N°3219 for each process will use a specific CMDBChange object with a specific track info // Any BackgroundProcess can overrides this as needed - CMDBObject::SetCurrentChange(null); - CMDBObject::SetTrackInfo("Background task ($sTaskClass)"); - CMDBObject::SetTrackOrigin(null); + CMDBObject::SetCurrentChangeFromParams("Background task ($sTaskClass)"); // Run the task and record its next run time if ($bVerbose) diff --git a/webservices/export-v2.php b/webservices/export-v2.php index 1c76922c67..0bd25cb6b2 100644 --- a/webservices/export-v2.php +++ b/webservices/export-v2.php @@ -134,7 +134,7 @@ function DisplayExpressionForm(WebPage $oP, $sAction, $sExpression = '', $sExcep $oPanel->AddSubBlock(InputUIBlockFactory::MakeForHidden('interactive', '1')); $oFieldQuery = FieldUIBlockFactory::MakeStandard(''); - $oTextArea = new TextArea('expression', htmlentities($sExpression, ENT_QUOTES, 'UTF-8'), "textarea_oql", 70, 8); + $oTextArea = new TextArea('expression', utils::EscapeHtml($sExpression), "textarea_oql", 70, 8); $oTextArea->SetPlaceholder(Dict::S('Core:BulkExportQueryPlaceholder')); $oTextArea->AddCSSClasses(["ibo-input-text", "ibo-query-oql", "ibo-is-code"]); $oFieldQuery->AddSubBlock($oTextArea); @@ -267,14 +267,14 @@ EOF } if ($sExpression !== '') { - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("expression", htmlentities($sExpression, ENT_QUOTES, 'UTF-8'))); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("expression", utils::EscapeHtml($sExpression))); $oExportSearch = DBObjectSearch::FromOQL($sExpression); $oExportSearch->UpdateContextFromUser(); } else { $oQuery = MetaModel::GetObject('QueryOQL', $sQueryId); $oExportSearch = DBObjectSearch::FromOQL($oQuery->Get('oql')); $oExportSearch->UpdateContextFromUser(); - $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("query", htmlentities($sQueryId, ENT_QUOTES, 'UTF-8'))); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("query", utils::EscapeHtml($sQueryId))); } $aFormPartsByFormat = array(); $aAllFormParts = array(); @@ -302,7 +302,7 @@ EOF } else { // One specific format was chosen - $oSelect = InputUIBlockFactory::MakeForHidden("format", htmlentities($sFormat, ENT_QUOTES, 'UTF-8')); + $oSelect = InputUIBlockFactory::MakeForHidden("format", utils::EscapeHtml($sFormat)); $oForm->AddSubBlock($oSelect); $oExporter = BulkExport::FindExporter($sFormat, $oExportSearch); @@ -362,7 +362,7 @@ function InteractiveShell($sExpression, $sQueryId, $sFormat, $sFileName, $sMode) { if ($sMode == 'dialog') { $sExportBtnLabel = json_encode(Dict::S('UI:Button:Export')); - $sJSTitle = json_encode(htmlentities(utils::ReadParam('dialog_title', '', false, 'raw_data'), ENT_QUOTES, 'UTF-8')); + $sJSTitle = json_encode(utils::EscapeHtml(utils::ReadParam('dialog_title', '', false, 'raw_data'))); $oP = new AjaxPage($sJSTitle); $oP->add('
    '); $oP->add_ready_script( diff --git a/webservices/import.php b/webservices/import.php index b88da870e2..a82707fa27 100644 --- a/webservices/import.php +++ b/webservices/import.php @@ -713,17 +713,12 @@ try } else { - if (strlen($sComment) > 0) - { + if (strlen($sComment) > 0) { $sMoreInfo = CMDBChange::GetCurrentUserName().', Web Service (CSV) - '.$sComment; - } - else - { + } else { $sMoreInfo = CMDBChange::GetCurrentUserName().', Web Service (CSV)'; } - CMDBObject::SetTrackInfo($sMoreInfo); - CMDBObject::SetTrackOrigin('csv-import.php'); - + CMDBObject::SetCurrentChangeFromParams($sMoreInfo, 'csv-import.php'); $oMyChange = CMDBObject::GetCurrentChange(); } diff --git a/webservices/webservices.class.inc.php b/webservices/webservices.class.inc.php index d3f781426a..352a075408 100644 --- a/webservices/webservices.class.inc.php +++ b/webservices/webservices.class.inc.php @@ -369,7 +369,7 @@ abstract class WebServicesBase { if (!MetaModel::IsValidFilterCode($sKeyClass, $sForeignAttCode)) { - $aCodes = array_keys(MetaModel::GetClassFilterDefs($sKeyClass)); + $aCodes = MetaModel::GetFiltersList($sKeyClass); $sMsg = "Parameter $sParamName: '$sForeignAttCode' is not a valid filter code for class '$sKeyClass', expecting a value in {".implode(', ', $aCodes)."}"; $oRes->LogIssue($sMsg, $bIsMandatory); } @@ -445,11 +445,9 @@ abstract class WebServicesBase } $oReconFilter = new DBObjectSearch($sTargetClass); $aCIStringDesc = array(); - foreach ($aItemData['search'] as $sAttCode => $value) - { - if (!MetaModel::IsValidFilterCode($sTargetClass, $sAttCode)) - { - $aCodes = array_keys(MetaModel::GetClassFilterDefs($sTargetClass)); + foreach ($aItemData['search'] as $sAttCode => $value) { + if (!MetaModel::IsValidFilterCode($sTargetClass, $sAttCode)) { + $aCodes = MetaModel::GetFiltersList($sTargetClass); $oRes->LogError("Parameter $sParamName: '$sAttCode' is not a valid filter code for class '$sTargetClass', expecting a value in {".implode(', ', $aCodes)."}"); continue 2; // skip the entire item }